diff --git a/.cargo/config.toml b/.cargo/config.toml index bd46659f7991a95d853711672a6a4eed9222c5a1..f113e9114acef51eaae6dd96666cc49781c8d41a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,41 +1,7 @@ [build] rustdocflags = [ - "-Dwarnings", - "-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 + "-Dwarnings", + "-Arustdoc::redundant_explicit_links", # stylistic ] [env] diff --git a/.config/lychee.toml b/.config/lychee.toml index 9b2ae069931769251fc26b57686fd1dbc5ff0b83..200521ac41eeb739228d202ac0fb2d80be305464 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -12,39 +12,43 @@ exclude_all_private = true # Treat these codes as success condition: accept = [ # Ok - 200, + "200", # Rate limited - GitHub likes to throw this. - 429 + "429", ] -exclude_path = [ "./target" ] +exclude_path = ["./target"] exclude = [ - # Place holders (no need to fix these): - "http://visitme/", - "https://visitme/", - - # TODO - "https://docs.substrate.io/main-docs/build/custom-rpc/#public-rpcs", - "https://docs.substrate.io/rustdocs/latest/sp_api/macro.decl_runtime_apis.html", - "https://github.com/ipfs/js-ipfs-bitswap/blob/", - "https://github.com/paritytech/polkadot-sdk/substrate/frame/timestamp", - "https://github.com/paritytech/substrate/frame/fast-unstake", - "https://github.com/zkcrypto/bls12_381/blob/e224ad4ea1babfc582ccd751c2bf128611d10936/src/test-data/mod.rs", - "https://polkadot.network/the-path-of-a-parachain-block/", - "https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results", - "https://research.web3.foundation/en/latest/polkadot/block-production/Babe.html", - "https://research.web3.foundation/en/latest/polkadot/block-production/Babe.html#-6.-practical-results", - "https://research.web3.foundation/en/latest/polkadot/networking/3-avail-valid.html#topology", - "https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html", - "https://research.web3.foundation/en/latest/polkadot/overview/2-token-economics.html", - "https://research.web3.foundation/en/latest/polkadot/overview/2-token-economics.html#inflation-model", - "https://research.web3.foundation/en/latest/polkadot/slashing/npos.html", - "https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model", - "https://rpc.polkadot.io/", - "https://w3f.github.io/parachain-implementers-guide/node/approval/approval-distribution.html", - "https://w3f.github.io/parachain-implementers-guide/node/index.html", - "https://w3f.github.io/parachain-implementers-guide/protocol-chain-selection.html", - "https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html", + # Place holders (no need to fix these): + "http://visitme/", + "https://visitme/", + + # TODO + "https://docs.substrate.io/main-docs/build/custom-rpc/#public-rpcs", + "https://docs.substrate.io/rustdocs/latest/sp_api/macro.decl_runtime_apis.html", + "https://github.com/ipfs/js-ipfs-bitswap/blob/", + "https://github.com/paritytech/polkadot-sdk/substrate/frame/timestamp", + "https://github.com/paritytech/substrate/frame/fast-unstake", + "https://github.com/zkcrypto/bls12_381/blob/e224ad4ea1babfc582ccd751c2bf128611d10936/src/test-data/mod.rs", + "https://polkadot.network/the-path-of-a-parachain-block/", + "https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results", + "https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html", + "https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model", + "https://research.web3.foundation/en/latest/polkadot/block-production/Babe.html", + "https://research.web3.foundation/en/latest/polkadot/block-production/Babe.html#-6.-practical-results", + "https://research.web3.foundation/en/latest/polkadot/networking/3-avail-valid.html#topology", + "https://research.web3.foundation/en/latest/polkadot/overview/2-token-economics.html", + "https://research.web3.foundation/en/latest/polkadot/overview/2-token-economics.html#inflation-model", + "https://research.web3.foundation/en/latest/polkadot/slashing/npos.html", + "https://rpc.polkadot.io/", + "https://w3f.github.io/parachain-implementers-guide/node/approval/approval-distribution.html", + "https://w3f.github.io/parachain-implementers-guide/node/index.html", + "https://w3f.github.io/parachain-implementers-guide/protocol-chain-selection.html", + "https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html", + + # Behind a captcha (code 403): + "https://iohk.io/en/blog/posts/2023/11/03/partner-chains-are-coming-to-cardano/", + "https://www.reddit.com/r/rust/comments/3spfh1/does_collect_allocate_more_than_once_while/", ] diff --git a/.config/taplo.toml b/.config/taplo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2c6ccfb2b34440686764c39ed6db1c73ed940f06 --- /dev/null +++ b/.config/taplo.toml @@ -0,0 +1,35 @@ +# all options https://taplo.tamasfe.dev/configuration/formatter-options.html + +# ignore zombienet as they do some deliberate custom toml stuff +exclude = [ + "bridges/testing/**", + "cumulus/zombienet/**", + "polkadot/node/malus/integrationtests/**", + "polkadot/zombienet_tests/**", + "substrate/zombienet/**", + "target/**", +] + +# global rules +[formatting] +reorder_arrays = true +inline_table_expand = false +array_auto_expand = false +array_auto_collapse = false +indent_string = " " # tab + +# don't re-order order-dependent deb package metadata +[[rule]] +include = ["polkadot/Cargo.toml"] +keys = ["package.metadata.deb"] + +[rule.formatting] +reorder_arrays = false + +# don't re-order order-dependent rustflags +[[rule]] +include = [".cargo/config.toml"] +keys = ["build"] + +[rule.formatting] +reorder_arrays = false diff --git a/.config/zepter.yaml b/.config/zepter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f701392d16b15aab8351b730efa13f3abffe2406 --- /dev/null +++ b/.config/zepter.yaml @@ -0,0 +1,40 @@ +version: + format: 1 + # Minimum version of the binary that is expected to work. This is just for printing a nice error + # message when someone tries to use an older version. + binary: 0.13.2 + +# The examples in this file assume crate `A` to have a dependency on crate `B`. +workflows: + check: + - [ + 'lint', + # Check that `A` activates the features of `B`. + 'propagate-feature', + # These are the features to check: + '--features=try-runtime,runtime-benchmarks,std', + # Do not try to add a new section into `[features]` of `A` only because `B` expose that feature. There are edge-cases where this is still needed, but we can add them manually. + '--left-side-feature-missing=ignore', + # Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on. + '--left-side-outside-workspace=ignore', + # Some features imply that they activate a specific dependency as non-optional. Otherwise the default behaviour with a `?` is used. + '--feature-enables-dep=try-runtime:frame-try-runtime,runtime-benchmarks:frame-benchmarking', + # Auxillary flags: + '--offline', + '--locked', + '--show-path', + '--quiet', + ] + # Same as `check`, but with the `--fix` flag. + default: + - [ $check.0, '--fix' ] + +# Will be displayed when any workflow fails: +help: + text: | + Polkadot-SDK uses the Zepter CLI to detect abnormalities in the feature configuration. + It looks like one more more checks failed; please check the console output. You can try to automatically address them by running `zepter`. + Otherwise please ask directly in the Merge Request, GitHub Discussions or on Matrix Chat, thank you. + links: + - "https://github.com/paritytech/polkadot-sdk/issues/1831" + - "https://github.com/ggwpez/zepter" diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml deleted file mode 100644 index ac13d862a4ac2aee487de33de56fe34d6507c0dc..0000000000000000000000000000000000000000 --- a/.github/pr-custom-review.yml +++ /dev/null @@ -1,63 +0,0 @@ -# 🔒 PROTECTED: Changes to locks-review-team should be approved by the current locks-review-team -locks-review-team: locks-review -team-leads-team: polkadot-review -action-review-team: ci - -rules: - - name: CI files - check_type: changed_files - condition: - include: ^\.gitlab-ci\.yml|^docker/.*|^\.github/.*|^\.gitlab/.*|^\.config/nextest.toml|^\.cargo/.* - exclude: ^\.gitlab/pipeline/zombienet.* - min_approvals: 2 - teams: - - ci - - release-engineering - - - name: Core developers - check_type: changed_files - condition: - include: .* - # 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/.*|^\.gitlab/.*|^\.config/nextest.toml|^\.cargo/.* - min_approvals: 2 - teams: - - core-devs - - # cumulus - - name: Runtime files cumulus - check_type: changed_files - condition: ^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$ - all_distinct: - - min_approvals: 1 - teams: - - locks-review - - min_approvals: 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 - check_type: changed_files - condition: ^bridges/.* - min_approvals: 1 - teams: - - bridges-core - - # substrate - - - name: FRAME coders substrate - check_type: changed_files - condition: - include: ^substrate/frame/(?!.*(nfts/.*|uniques/.*|babe/.*|grandpa/.*|beefy|merkle-mountain-range/.*|contracts/.*|election|nomination-pools/.*|staking/.*|aura/.*)) - all: - - min_approvals: 2 - teams: - - core-devs - - min_approvals: 1 - teams: - - frame-coders - -prevent-review-request: - teams: - - core-devs diff --git a/.github/review-bot.yml b/.github/review-bot.yml index 581e33762608a298fdb0c251fa3414b06ea01de6..ed719cefec8bc97c921e11a1751889433f0991ea 100644 --- a/.github/review-bot.yml +++ b/.github/review-bot.yml @@ -1,38 +1,24 @@ rules: - name: CI files + countAuthor: true condition: - include: + include: - ^\.gitlab-ci\.yml - ^docker/.* - ^\.github/.* - ^\.gitlab/.* - ^\.config/nextest.toml - ^\.cargo/.* - exclude: - - ^./gitlab/pipeline/zombienet.* - minApprovals: 2 - type: basic - teams: - - ci - - release-engineering - - - name: Audit rules - type: basic - condition: - include: - - ^polkadot/runtime\/(kusama|polkadot|common)\/.* - - ^polkadot/primitives/src\/.+\.rs$ - - ^substrate/primitives/.* - - ^substrate/frame/.* - exclude: - - ^polkadot/runtime\/(kusama|polkadot)\/src\/weights\/.+\.rs$ - - ^substrate\/frame\/.+\.md$ - minApprovals: 1 - allowedToSkipRule: - teams: - - core-devs - teams: - - srlabs + exclude: + - ^\.gitlab/pipeline/zombienet.* + type: "or" + reviewers: + - minApprovals: 2 + teams: + - ci + - minApprovals: 2 + teams: + - core-devs - name: Core developers countAuthor: true @@ -41,13 +27,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,28 +40,10 @@ 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 - condition: + condition: include: - ^bridges/.* minApprovals: 1 @@ -88,10 +51,9 @@ rules: - bridges-core # substrate - - name: FRAME coders substrate condition: - include: + include: - ^substrate/frame/(?!.*(nfts/.*|uniques/.*|babe/.*|grandpa/.*|beefy|merkle-mountain-range/.*|contracts/.*|election|nomination-pools/.*|staking/.*|aura/.*)) type: "and" reviewers: @@ -104,8 +66,9 @@ rules: # Protection of THIS file - name: Review Bot + countAuthor: true condition: - include: + include: - review-bot\.yml type: "and" reviewers: @@ -115,6 +78,7 @@ rules: - minApprovals: 1 teams: - locks-review - - minApprovals: 1 - teams: - - ci + +preventReviewRequests: + teams: + - core-devs 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-prdoc.py b/.github/scripts/check-prdoc.py new file mode 100644 index 0000000000000000000000000000000000000000..42b063f2885da148033986dfa49740f2b0416460 --- /dev/null +++ b/.github/scripts/check-prdoc.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +''' +Ensure that the prdoc files are valid. + +# Example + +```sh +python3 -m pip install cargo-workspace +python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/*.prdoc +``` + +Produces example output: +```pre +🔎 Reading workspace polkadot-sdk/Cargo.toml +📦 Checking 32 prdocs against 493 crates. +✅ All prdocs are valid +``` +''' + +import os +import yaml +import argparse +import cargo_workspace + +def check_prdoc_crate_names(root, paths): + ''' + Check that all crates of the `crates` section of each prdoc is present in the workspace. + ''' + + print(f'🔎 Reading workspace {root}.') + workspace = cargo_workspace.Workspace.from_path(root) + crate_names = [crate.name for crate in workspace.crates] + + print(f'📦 Checking {len(paths)} prdocs against {len(crate_names)} crates.') + faulty = {} + + for path in paths: + with open(path, 'r') as f: + prdoc = yaml.safe_load(f) + + for crate in prdoc.get('crates', []): + crate = crate['name'] + if crate in crate_names: + continue + + faulty.setdefault(path, []).append(crate) + + if len(faulty) == 0: + print('✅ All prdocs are valid.') + else: + print('❌ Some prdocs are invalid.') + for path, crates in faulty.items(): + print(f'💥 {path} lists invalid crate: {", ".join(crates)}') + exit(1) + +def parse_args(): + parser = argparse.ArgumentParser(description='Check prdoc files') + parser.add_argument('root', help='The cargo workspace manifest', metavar='root', type=str, nargs=1) + parser.add_argument('prdoc', help='The prdoc files', metavar='prdoc', type=str, nargs='*') + args = parser.parse_args() + + if len(args.prdoc) == 0: + print('❌ Need at least one prdoc file as argument.') + exit(1) + + return { 'root': os.path.abspath(args.root[0]), 'prdocs': args.prdoc } + +if __name__ == '__main__': + args = parse_args() + check_prdoc_crate_names(args['root'], args['prdocs']) diff --git a/.github/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/check-workspace.py b/.github/scripts/check-workspace.py new file mode 100644 index 0000000000000000000000000000000000000000..1f8f103e4e157a8c1c804a618652741193ca5a00 --- /dev/null +++ b/.github/scripts/check-workspace.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +# Ensures that: +# - all crates are added to the root workspace +# - local dependencies are resolved via `path` +# +# It does not check that the local paths resolve to the correct crate. This is already done by cargo. +# +# Must be called with a folder containing a `Cargo.toml` workspace file. + +import os +import sys +import toml +import argparse + +def parse_args(): + parser = argparse.ArgumentParser(description='Check Rust workspace integrity.') + + parser.add_argument('workspace_dir', help='The directory to check', metavar='workspace_dir', type=str, nargs=1) + parser.add_argument('--exclude', help='Exclude crate paths from the check', metavar='exclude', type=str, nargs='*', default=[]) + + args = parser.parse_args() + return (args.workspace_dir[0], args.exclude) + +def main(root, exclude): + workspace_crates = get_members(root, exclude) + all_crates = get_crates(root, exclude) + print(f'📦 Found {len(all_crates)} crates in total') + + check_duplicates(workspace_crates) + check_missing(workspace_crates, all_crates) + check_links(all_crates) + +# Extract all members from a workspace. +# Return: list of all workspace paths +def get_members(workspace_dir, exclude): + print(f'🔎 Indexing workspace {os.path.abspath(workspace_dir)}') + + root_manifest_path = os.path.join(workspace_dir, "Cargo.toml") + if not os.path.exists(root_manifest_path): + print(f'❌ No root manifest found at {root_manifest}') + sys.exit(1) + + root_manifest = toml.load(root_manifest_path) + if not 'workspace' in root_manifest: + print(f'❌ No workspace found in root {root_manifest_path}') + sys.exit(1) + + if not 'members' in root_manifest['workspace']: + return [] + + members = [] + for member in root_manifest['workspace']['members']: + if member in exclude: + print(f'❌ Excluded member should not appear in the workspace {member}') + sys.exit(1) + members.append(member) + + return members + +# List all members of the workspace. +# Return: Map name -> (path, manifest) +def get_crates(workspace_dir, exclude_crates) -> dict: + crates = {} + + for root, dirs, files in os.walk(workspace_dir): + if "target" in root: + continue + for file in files: + if file != "Cargo.toml": + continue + + path = os.path.join(root, file) + with open(path, "r") as f: + content = f.read() + manifest = toml.loads(content) + + if 'workspace' in manifest: + if root != workspace_dir: + print("⏩ Excluded recursive workspace at %s" % path) + continue + + # Cut off the root path and the trailing /Cargo.toml. + path = path[len(workspace_dir)+1:-11] + name = manifest['package']['name'] + if path in exclude_crates: + print("⏩ Excluded crate %s at %s" % (name, path)) + continue + crates[name] = (path, manifest) + + return crates + +# Check that there are no duplicate entries in the workspace. +def check_duplicates(workspace_crates): + print(f'🔎 Checking for duplicate crates') + found = {} + for path in workspace_crates: + if path in found: + print(f'❌ crate is listed twice in the workspace {path}') + sys.exit(1) + found[path] = True + +# Check that all crates are in the workspace. +def check_missing(workspace_crates, all_crates): + print(f'🔎 Checking for missing crates') + if len(workspace_crates) == len(all_crates): + print(f'✅ All {len(all_crates)} crates are in the workspace') + return + + missing = [] + # Find out which ones are missing. + for name, (path, manifest) in all_crates.items(): + if not path in workspace_crates: + missing.append([name, path, manifest]) + missing.sort() + + for name, path, _manifest in missing: + print("❌ %s in %s" % (name, path)) + print(f'😱 {len(all_crates) - len(workspace_crates)} crates are missing from the workspace') + sys.exit(1) + +# Check that all local dependencies are good. +def check_links(all_crates): + print(f'🔎 Checking for broken dependency links') + links = [] + broken = [] + + for name, (path, manifest) in all_crates.items(): + def check_deps(deps): + for dep in deps: + # Could be renamed: + dep_name = dep + if 'package' in deps[dep]: + dep_name = deps[dep]['package'] + if dep_name in all_crates: + links.append((name, dep_name)) + + if not 'path' in deps[dep]: + broken.append((name, dep_name, "crate must be linked via `path`")) + return + + def check_crate(deps): + to_checks = ['dependencies', 'dev-dependencies', 'build-dependencies'] + + for to_check in to_checks: + if to_check in deps: + check_deps(deps[to_check]) + + # There could possibly target dependant deps: + if 'target' in manifest: + # Target dependant deps can only have one level of nesting: + for _, target in manifest['target'].items(): + check_crate(target) + + check_crate(manifest) + + + + links.sort() + broken.sort() + + if len(broken) > 0: + for (l, r, reason) in broken: + print(f'❌ {l} -> {r} ({reason})') + + print("💥 %d out of %d links are broken" % (len(broken), len(links))) + sys.exit(1) + else: + print("✅ All %d internal dependency links are correct" % len(links)) + +if __name__ == "__main__": + args = parse_args() + main(args[0], args[1]) diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index b0f9cb32063a43cda58d2e6e8f25e0ff66a134eb..29dc269ffd23b1f51e1eb2b87a61544de0cbb57f 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 @@ -232,6 +237,61 @@ fetch_release_artifacts() { popd > /dev/null } +# Fetch the release artifacts like binary and sigantures from S3. Assumes the ENV are set: +# - RELEASE_ID +# - GITHUB_TOKEN +# - REPO in the form paritytech/polkadot +fetch_release_artifacts_from_s3() { + echo "Version : $VERSION" + echo "Repo : $REPO" + echo "Binary : $BINARY" + OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} + echo "OUTPUT_DIR : $OUTPUT_DIR" + + URL_BASE=$(get_s3_url_base $BINARY) + echo "URL_BASE=$URL_BASE" + + URL_BINARY=$URL_BASE/$VERSION/$BINARY + URL_SHA=$URL_BASE/$VERSION/$BINARY.sha256 + URL_ASC=$URL_BASE/$VERSION/$BINARY.asc + + # Fetch artifacts + mkdir -p "$OUTPUT_DIR" + pushd "$OUTPUT_DIR" > /dev/null + + echo "Fetching artifacts..." + for URL in $URL_BINARY $URL_SHA $URL_ASC; do + echo "Fetching %s" "$URL" + curl --progress-bar -LO "$URL" || echo "Missing $URL" + done + + pwd + ls -al --color + popd > /dev/null + +} + +# Pass the name of the binary as input, it will +# return the s3 base url +function get_s3_url_base() { + name=$1 + case $name in + polkadot | polkadot-execute-worker | polkadot-prepare-worker | staking-miner) + printf "https://releases.parity.io/polkadot" + ;; + + polkadot-parachain) + printf "https://releases.parity.io/cumulus" + ;; + + *) + printf "UNSUPPORTED BINARY $name" + exit 1 + ;; + esac +} + + # Check the checksum for a given binary function check_sha256() { echo "Checking SHA256 for $1" @@ -243,13 +303,11 @@ function check_sha256() { function import_gpg_keys() { GPG_KEYSERVER=${GPG_KEYSERVER:-"keyserver.ubuntu.com"} SEC="9D4B2B6EB8F97156D19669A9FF0812D491B96798" - WILL="2835EAF92072BC01D188AF2C4A092B93E97CE1E2" EGOR="E6FC4D4782EB0FA64A4903CCDB7D3555DD3932D3" - MARA="533C920F40E73A21EEB7E9EBF27AEA7E7594C9CF" MORGAN="2E92A9D8B15D7891363D1AE8AF9E6C43F7F8C4CF" echo "Importing GPG keys from $GPG_KEYSERVER in parallel" - for key in $SEC $WILL $EGOR $MARA $MORGAN; do + for key in $SEC $EGOR $MORGAN; do ( echo "Importing GPG key $key" gpg --no-tty --quiet --keyserver $GPG_KEYSERVER --recv-keys $key @@ -264,3 +322,115 @@ 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 +} + +# Filter the version matches the particular pattern and return it. +# input: version (v1.8.0 or v1.8.0-rc1) +# output: none +filter_version_from_input() { + version=$1 + regex="(^v[0-9]+\.[0-9]+\.[0-9]+)$|(^v[0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+)$" + + if [[ $version =~ $regex ]]; then + if [ -n "${BASH_REMATCH[1]}" ]; then + echo "${BASH_REMATCH[1]}" + elif [ -n "${BASH_REMATCH[2]}" ]; then + echo "${BASH_REMATCH[2]}" + fi + else + echo "Invalid version: $version" + exit 1 + fi + +} + +# Check if the release_id is valid number +# input: release_id +# output: release_id or exit 1 +check_release_id() { + input=$1 + + release_id=$(echo "$input" | sed 's/[^0-9]//g') + + if [[ $release_id =~ ^[0-9]+$ ]]; then + echo "$release_id" + else + echo "Invalid release_id from input: $input" + exit 1 + fi + +} diff --git a/.github/workflows/build-and-attach-release-runtimes.yml b/.github/workflows/build-and-attach-release-runtimes.yml new file mode 100644 index 0000000000000000000000000000000000000000..680a9ecffd312dba61c2eaee3a3e2e6a9d5b136c --- /dev/null +++ b/.github/workflows/build-and-attach-release-runtimes.yml @@ -0,0 +1,65 @@ +name: Build and Attach Runtimes to Releases/RC + +on: + release: + types: + - published + +env: + PROFILE: production + +jobs: + build_and_upload: + strategy: + matrix: + runtime: + - { name: westend, package: westend-runtime, path: polkadot/runtime/westend } + - { name: rococo, package: rococo-runtime, path: polkadot/runtime/rococo } + - { name: asset-hub-rococo, package: asset-hub-rococo-runtime, path: cumulus/parachains/runtimes/assets/asset-hub-rococo } + - { name: asset-hub-westend, package: asset-hub-westend-runtime, path: cumulus/parachains/runtimes/assets/asset-hub-westend } + - { name: bridge-hub-rococo, package: bridge-hub-rococo-runtime, path: cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo } + - { name: contracts-rococo, package: contracts-rococo-runtime, path: cumulus/parachains/runtimes/contracts/contracts-rococo } + - { name: collectives-westend, package: collectives-westend-runtime, path: cumulus/parachains/runtimes/collectives/collectives-westend } + - { 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 } + # Debug build has logging enabled and developer features + - { type: dev-debug-build, opts: --features try-runtime } + + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + 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.2 + env: + BUILD_OPTS: ${{ matrix.build_config.opts }} + with: + chain: ${{ matrix.runtime.name }} + package: ${{ matrix.runtime.package }} + runtime_dir: ${{ matrix.runtime.path }} + profile: ${{ env.PROFILE }} + + - name: Set up paths and runtime names + id: setup + run: | + RUNTIME_BLOB_NAME=$(echo ${{ matrix.runtime.package }} | sed 's/-/_/g').compact.compressed.wasm + PREFIX=${{ matrix.build_config.type == 'dev-debug-build' && 'DEV_DEBUG_BUILD__' || '' }} + + echo "RUNTIME_BLOB_NAME=$RUNTIME_BLOB_NAME" >> $GITHUB_ENV + echo "ASSET_PATH=./${{ matrix.runtime.path }}/target/srtool/${{ env.PROFILE }}/wbuild/${{ matrix.runtime.package }}/$RUNTIME_BLOB_NAME" >> $GITHUB_ENV + echo "ASSET_NAME=$PREFIX$RUNTIME_BLOB_NAME" >> $GITHUB_ENV + + - name: Upload Runtime to Release + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.ASSET_PATH }} + asset_name: ${{ env.ASSET_NAME }} + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check-features.yml b/.github/workflows/check-features.yml new file mode 100644 index 0000000000000000000000000000000000000000..53d6ac6b4dbfd7e3ccf1ca09ad9e1e70a49a9ff9 --- /dev/null +++ b/.github/workflows/check-features.yml @@ -0,0 +1,19 @@ +name: Check Features + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + check-features: + runs-on: ubuntu-latest + steps: + - name: Fetch latest code + uses: actions/checkout@v4 + - name: Check + uses: hack-ink/cargo-featalign-action@bea88a864d6ca7d0c53c26f1391ce1d431dc7f34 # v0.1.1 + with: + crate: substrate/bin/node/runtime + features: std,runtime-benchmarks,try-runtime + ignore: sc-executor + default-std: true diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 83b52e82313257affaea41eb66da44b164544116..1d1a8770058d33ba5e449c6a2e8b307e0ff02eb7 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -3,16 +3,12 @@ name: Check labels on: pull_request: types: [labeled, opened, synchronize, unlabeled] + merge_group: jobs: check-labels: runs-on: ubuntu-latest steps: - - name: Pull image - env: - IMAGE: paritytech/ruled_labels:0.4.0 - run: docker pull $IMAGE - - name: Check labels env: IMAGE: paritytech/ruled_labels:0.4.0 @@ -24,6 +20,16 @@ jobs: RULES_PATH: labels/ruled_labels CHECK_SPECS: "specs_polkadot-sdk.yaml" run: | + if [ ${{ github.ref }} == "refs/heads/master" ]; then + echo "Skipping master" + exit 0 + fi + if [ $(echo ${{ github.ref }} | grep -c "gh-readonly-queue") -eq 1 ]; then + echo "Skipping merge queue" + exit 0 + fi + + docker pull $IMAGE echo "REPO: ${REPO}" echo "GITHUB_PR: ${GITHUB_PR}" diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml index e8def665bd08f2e0cc15cfd5b23732f1744dcd5b..c32b6fcf89e06bb56cefc0517e1dcab1d1ef0f37 100644 --- a/.github/workflows/check-licenses.yml +++ b/.github/workflows/check-licenses.yml @@ -2,20 +2,21 @@ name: Check licenses on: pull_request: + merge_group: permissions: packages: read 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@v3.8.1 + - uses: actions/setup-node@v4.0.1 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" @@ -41,5 +42,4 @@ jobs: shopt -s globstar npx @paritytech/license-scanner scan \ --ensure-licenses ${{ env.LICENSES }} \ - --exclude ./substrate/bin/node-template \ -- ./substrate/**/*.rs diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 3ed6ba84b82f14e2b20cb4ab4553bcef4a11283d..903d7a3fcb3d94bb6913d94627418d9212397bf3 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -8,6 +8,7 @@ on: - ".github/workflows/check-links.yml" - ".config/lychee.toml" types: [opened, synchronize, reopened, ready_for_review] + merge_group: permissions: packages: read @@ -17,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Restore lychee cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 (7. Sep 2023) + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.2 (7. Sep 2023) with: path: .lycheecache key: cache-lychee-${{ github.sha }} @@ -27,7 +28,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.0 (22. Sep 2023) - name: Lychee link checker - uses: lycheeverse/lychee-action@2ac9f030ccdea0033e2510a23a67da2a2da98492 # for v1.8.0 (15. May 2023) + uses: lycheeverse/lychee-action@c3089c702fbb949e3f7a8122be0c33c017904f9b # for v1.9.1 (10. Jan 2024) with: args: >- --config .config/lychee.toml diff --git a/.github/workflows/check-markdown.yml b/.github/workflows/check-markdown.yml index 991fdd171ce6d5d09618e666ce697dd17f4ba43d..2b8a66db35b3adacea4f131a881103d48e3704ae 100644 --- a/.github/workflows/check-markdown.yml +++ b/.github/workflows/check-markdown.yml @@ -3,6 +3,7 @@ name: Check Markdown on: pull_request: types: [opened, synchronize, reopened, ready_for_review] + merge_group: permissions: packages: read @@ -15,7 +16,7 @@ jobs: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-node@v3.8.1 + - uses: actions/setup-node@v4.0.1 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" @@ -23,11 +24,12 @@ jobs: - name: Install tooling run: | - npm install -g markdownlint-cli - markdownlint --version + npm install -g markdownlint-cli + markdownlint --version - name: Check Markdown 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-prdoc.yml b/.github/workflows/check-prdoc.yml index 690f7a3f1333366dea41944b5ae81bc74f12b761..c31dee06ec54a0154efc3ad46ff24c79de4d0d7b 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -3,47 +3,65 @@ name: Check PRdoc on: pull_request: types: [labeled, opened, synchronize, unlabeled] + merge_group: env: - IMAGE: paritytech/prdoc:v0.0.5 + IMAGE: docker.io/paritytech/prdoc:v0.0.7 API_BASE: https://api.github.com/repos REPO: ${{ github.repository }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR: ${{ github.event.pull_request.number }} - MOUNT: /prdoc ENGINE: docker + PRDOC_DOC: https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/prdoc.md jobs: check-prdoc: runs-on: ubuntu-latest + if: github.event.pull_request.number != '' steps: - - name: Pull image - run: | - echo "Pulling $IMAGE" - docker pull $IMAGE - docker run --rm $IMAGE --version - + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + # we cannot show the version in this step (ie before checking out the repo) + # due to https://github.com/paritytech/prdoc/issues/15 - name: Check if PRdoc is required id: get-labels run: | + echo "Pulling $IMAGE" + $ENGINE pull $IMAGE + # Fetch the labels for the PR under test echo "Fetch the labels for $API_BASE/${REPO}/pulls/${GITHUB_PR}" labels=$( curl -H "Authorization: token ${GITHUB_TOKEN}" -s "$API_BASE/${REPO}/pulls/${GITHUB_PR}" | jq '.labels | .[] | .name' | tr "\n" ",") echo "Labels: ${labels}" echo "labels=${labels}" >> "$GITHUB_OUTPUT" - - name: No PRdoc required + echo "Checking PRdoc version" + $ENGINE run --rm -v $PWD:/repo $IMAGE --version + + - name: Early exit if PR is silent if: ${{ contains(steps.get-labels.outputs.labels, 'R0') }} run: | - echo "PR detected as silent, no PRdoc is required, exiting..." + hits=$(find prdoc -name "pr_$GITHUB_PR*.prdoc" | wc -l) + if (( hits > 0 )); then + echo "PR detected as silent, but a PRDoc was found, checking it as information" + $ENGINE run --rm -v $PWD:/repo $IMAGE check -n ${GITHUB_PR} || echo "Ignoring failure" + else + echo "PR detected as silent, no PRDoc found, exiting..." + fi + echo "If you want to add a PRDoc, please refer to $PRDOC_DOC" exit 0 - - name: Checkout repo + - name: PRdoc check for PR#${{ github.event.pull_request.number }} if: ${{ !contains(steps.get-labels.outputs.labels, 'R0') }} - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + run: | + echo "Checking for PR#${GITHUB_PR}" + echo "You can find more information about PRDoc at $PRDOC_DOC" + $ENGINE run --rm -v $PWD:/repo -e RUST_LOG=info $IMAGE check -n ${GITHUB_PR} - - name: PRdoc check for PR#${{ github.event.pull_request.number }} + - name: Validate prdoc for PR#${{ github.event.pull_request.number }} if: ${{ !contains(steps.get-labels.outputs.labels, 'R0') }} run: | - echo "Checking for PR#${GITHUB_PR} in $MOUNT" - $ENGINE run --rm -v $PWD/prdoc:/doc $IMAGE check -n ${GITHUB_PR} || true + echo "Validating PR#${GITHUB_PR}" + python3 --version + python3 -m pip install cargo-workspace==1.2.1 + python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/pr_${GITHUB_PR}.prdoc diff --git a/.github/workflows/check-publish.yml b/.github/workflows/check-publish.yml new file mode 100644 index 0000000000000000000000000000000000000000..b16b3d4e5c5c5061741e7ae698ff0a0e9e0c5084 --- /dev/null +++ b/.github/workflows/check-publish.yml @@ -0,0 +1,26 @@ +name: Check publish + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + merge_group: + +jobs: + check-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + + - name: install parity-publish + run: cargo install parity-publish@0.3.0 + + - name: parity-publish check + run: parity-publish check --allow-unpublished 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/check-workspace.yml b/.github/workflows/check-workspace.yml new file mode 100644 index 0000000000000000000000000000000000000000..81ec311ccce8153d7a28f68ff801cc917c8d1fd9 --- /dev/null +++ b/.github/workflows/check-workspace.yml @@ -0,0 +1,21 @@ +name: Check workspace + +on: + pull_request: + merge_group: + +jobs: + check-workspace: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.0 (22. Sep 2023) + + - name: install python deps + run: pip3 install toml + + - name: check integrity + run: > + python3 .github/scripts/check-workspace.py . + --exclude + "substrate/frame/contracts/fixtures/build" + "substrate/frame/contracts/fixtures/contracts/common" diff --git a/.github/workflows/claim-crates.yml b/.github/workflows/claim-crates.yml new file mode 100644 index 0000000000000000000000000000000000000000..f3df0bce72d501ed22c66b9792e032becdd4da93 --- /dev/null +++ b/.github/workflows/claim-crates.yml @@ -0,0 +1,26 @@ +name: Claim Crates + +on: + push: + branches: + - master + +jobs: + claim-crates: + runs-on: ubuntu-latest + environment: master + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Rust Cache + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + cache-on-failure: true + + - name: install parity-publish + run: cargo install parity-publish@0.3.0 + + - name: parity-publish claim + env: + PARITY_PUBLISH_CRATESIO_TOKEN: ${{ secrets.CRATESIO_PUBLISH_CLAIM_TOKEN }} + run: parity-publish claim diff --git a/.github/workflows/fmt-check.yml b/.github/workflows/fmt-check.yml index 55e67f2799b8f2363ca3d310b5130a610a7c0727..efcf278c46e83630a54fae3de01d0c9e19304dee 100644 --- a/.github/workflows/fmt-check.yml +++ b/.github/workflows/fmt-check.yml @@ -6,6 +6,7 @@ on: - master pull_request: types: [opened, synchronize, reopened, ready_for_review] + merge_group: jobs: quick_check: @@ -14,7 +15,7 @@ jobs: os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} container: - image: paritytech/ci-unified:bullseye-1.70.0-2023-05-23-v20230706 + image: docker.io/paritytech/ci-unified:bullseye-1.75.0-2024-01-22-v20240109 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/gitspiegel-trigger.yml b/.github/workflows/gitspiegel-trigger.yml new file mode 100644 index 0000000000000000000000000000000000000000..01058ad74d0b71385a8096964ea6c779fc6f4869 --- /dev/null +++ b/.github/workflows/gitspiegel-trigger.yml @@ -0,0 +1,35 @@ +name: gitspiegel sync + +# This workflow doesn't do anything, it's only use is to trigger "workflow_run" +# webhook, that'll be consumed by gitspiegel +# This way, gitspiegel won't do mirroring, unless this workflow runs, +# and running the workflow is protected by GitHub + +on: + pull_request: + types: + - opened + - synchronize + - unlocked + - ready_for_review + - reopened + # doesn't work as intended, triggers "workflow_run" webhook in any case + # the job doesn't check out any code, so it is relatively safe to run it on any event + # pull_request_target: + # types: + # - opened + # - synchronize + # - unlocked + # - ready_for_review + # - reopened + merge_group: + +# drop all permissions for GITHUB_TOKEN +permissions: {} + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Do nothing + run: echo "let's go" diff --git a/.github/workflows/merge-queue.yml b/.github/workflows/merge-queue.yml new file mode 100644 index 0000000000000000000000000000000000000000..cce326f44931bed8357fb661c8b053f60205f119 --- /dev/null +++ b/.github/workflows/merge-queue.yml @@ -0,0 +1,24 @@ +name: Merge-Queue + +on: + merge_group: + +jobs: + trigger-merge-queue-action: + runs-on: ubuntu-latest + environment: merge-queues + steps: + - name: Generate token + id: app_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.REVIEW_APP_ID }} + private_key: ${{ secrets.REVIEW_APP_KEY }} + - name: Add Merge Queue status check + uses: billyjbryant/create-status-check@3e6fa0ac599d10d9588cf9516ca4330ef669b858 # v2 + with: + authToken: ${{ steps.app_token.outputs.token }} + context: 'review-bot' + description: 'PRs for merge queue gets approved' + state: 'success' + sha: ${{ github.event.merge_group.head_commit.id }} 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/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml deleted file mode 100644 index b15d20c696fe83fd5c19cf834328458c8a5c6c3c..0000000000000000000000000000000000000000 --- a/.github/workflows/pr-custom-review.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Assign reviewers - -on: - pull_request: - branches: - - master - - main - types: - - opened - - reopened - - synchronize - - review_requested - - review_request_removed - - ready_for_review - - converted_to_draft - pull_request_review: - -jobs: - pr-custom-review: - runs-on: ubuntu-latest - steps: - - name: Skip if pull request is in Draft - # `if: github.event.pull_request.draft == true` should be kept here, at - # the step level, rather than at the job level. The latter is not - # recommended because when the PR is moved from "Draft" to "Ready to - # review" the workflow will immediately be passing (since it was skipped), - # even though it hasn't actually ran, since it takes a few seconds for - # the workflow to start. This is also disclosed in: - # https://github.community/t/dont-run-actions-on-draft-pull-requests/16817/17 - # That scenario would open an opportunity for the check to be bypassed: - # 1. Get your PR approved - # 2. Move it to Draft - # 3. Push whatever commits you want - # 4. Move it to "Ready for review"; now the workflow is passing (it was - # skipped) and "Check reviews" is also passing (it won't be updated - # until the workflow is finished) - if: github.event.pull_request.draft == true - run: exit 1 - - name: pr-custom-review - uses: paritytech/pr-custom-review@master - with: - checks-reviews-api: http://pcr.parity-prod.parity.io/api/v1/check_reviews 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 891f43e605c051a63325deb0638b342a97e2377e..67e93ee96574de1f1e3e29f1bf6d90085865100d 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -36,7 +36,7 @@ on: -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/repos/$OWNER/$REPO/releases | \ jq '.[] | { name: .name, id: .id }' required: true - type: string + type: number registry: description: Container registry @@ -61,7 +61,6 @@ permissions: contents: write env: - RELEASE_ID: ${{ inputs.release_id }} ENGINE: docker REGISTRY: ${{ inputs.registry }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,6 +70,7 @@ env: # EVENT_ACTION: ${{ github.event.action }} EVENT_NAME: ${{ github.event_name }} IMAGE_TYPE: ${{ inputs.image_type }} + VERSION: ${{ inputs.version }} jobs: fetch-artifacts: # this job will be triggered for the polkadot-parachain rc and release or polkadot rc image build @@ -95,16 +95,19 @@ jobs: # chmod a+x $BINARY # ls -al - - name: Fetch rc artifacts or release artifacts based on release id + - name: Fetch rc artifacts or release artifacts from s3 based on version #this step runs only if the workflow is triggered manually if: ${{ env.EVENT_NAME == 'workflow_dispatch' }} run: | . ./.github/scripts/common/lib.sh - fetch_release_artifacts + VERSION=$(filter_version_from_input "${{ inputs.version }}") + echo "VERSION=${VERSION}" >> $GITHUB_ENV + + fetch_release_artifacts_from_s3 - name: Cache the artifacts - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.3 with: key: artifacts-${{ env.BINARY }}-${{ github.sha }} path: | @@ -121,7 +124,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Get artifacts from cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.3 with: key: artifacts-${{ env.BINARY }}-${{ github.sha }} fail-on-cache-miss: true @@ -147,7 +150,10 @@ jobs: if: ${{ env.IMAGE_TYPE == 'rc' }} id: fetch_rc_refs run: | - release=release-${{ inputs.release_id }} && \ + . ./.github/scripts/common/lib.sh + + RELEASE_ID=$(check_release_id "${{ inputs.release_id }}") + release=release-$RELEASE_ID && \ echo "release=${release}" >> $GITHUB_OUTPUT commit=$(git rev-parse --short HEAD) && \ @@ -220,6 +226,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 +237,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" @@ -247,7 +256,7 @@ jobs: uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Cache Docker layers - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -268,7 +277,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 with: push: true file: docker/dockerfiles/polkadot/polkadot_injected_debian.Dockerfile @@ -276,7 +285,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..05c9d6a47f551860c51e318b01b495ca662e902e --- /dev/null +++ b/.github/workflows/release-99_notif-published.yml @@ -0,0 +1,52 @@ +name: Release - Announce release to Matrix rooms +on: + release: + types: + - published + - prereleased + +jobs: + ping_matrix: + runs-on: ubuntu-latest + environment: release + strategy: + matrix: + channel: + # Internal + - name: "RelEng: Polkadot Release Coordination" + room: '!cqAmzdIcbOFwrdrubV:parity.io' + pre-release: true + + # External + - name: 'Ledger <> Polkadot Coordination' + room: '!EoIhaKfGPmFOBrNSHT:web3.foundation' + pre-release: true + + # Public + - name: '#polkadotvalidatorlounge:web3.foundation' + room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' + pre-releases: false + - name: '#polkadot-announcements:parity.io' + room: '!UqHPWiCBGZWxrmYBkF:matrix.parity.io' + pre-releases: false + - name: '#kusama-announce:parity.io' + room: '!FMwxpQnYhRCNDRsYGI:matrix.parity.io' + pre-releases: false + + steps: + - name: Matrix notification to ${{ matrix.channel.name }} + 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: | + @room + + A new node 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/review-bot.yml b/.github/workflows/review-bot.yml index 178193da3389ad8cbebd2237c9540099ca70941f..5b036115b2386c366b2f1e78e9ce1dc7d526eedd 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -6,12 +6,10 @@ on: types: - completed -permissions: - contents: read - jobs: review-approvals: runs-on: ubuntu-latest + environment: master steps: - name: Extract content of artifact id: number @@ -19,15 +17,16 @@ jobs: with: artifact-name: pr_number - name: Generate token - id: team_token + id: app_token uses: tibdex/github-app-token@v1 with: app_id: ${{ secrets.REVIEW_APP_ID }} private_key: ${{ secrets.REVIEW_APP_KEY }} - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.1.0 + uses: paritytech/review-bot@v2.4.0 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - team-token: ${{ steps.team_token.outputs.token }} - checks-token: ${{ steps.team_token.outputs.token }} + repo-token: ${{ steps.app_token.outputs.token }} + team-token: ${{ steps.app_token.outputs.token }} + checks-token: ${{ steps.app_token.outputs.token }} pr-number: ${{ steps.number.outputs.content }} + request-reviewers: true diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 40e2918df2b2c6407cadf3939c56b9783787ac93..8b23dd30bb29ad7879543c064c3eb711cc87895d 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -1,6 +1,6 @@ name: Review-Trigger -on: +on: pull_request_target: types: - opened @@ -13,9 +13,14 @@ on: jobs: trigger-review-bot: + # (It is not a draft) && (it is not a review || it is an approving review) + if: ${{ github.event.pull_request.draft != true && (github.event_name != 'pull_request_review' || (github.event.review && github.event.review.state == 'APPROVED')) }} runs-on: ubuntu-latest name: trigger review bot steps: + - name: Skip merge queue + if: ${{ contains(github.ref, 'gh-readonly-queue') }} + run: exit 0 - name: Get PR number env: PR_NUMBER: ${{ github.event.pull_request.number }} 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/.gitignore b/.gitignore index 7feea8ada5c58d6ae92bafaa8740bca7a448e434..2f1631fb4b9d14496021907cca96b4cdf4902eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ **/._* **/.criterion/ **/*.rs.bk -**/chains/ **/hfuzz_target/ **/hfuzz_workspace/ **/node_modules @@ -30,6 +29,7 @@ bin/node-template/Cargo.lock nohup.out polkadot_argument_parsing polkadot.* +!docs/sdk/src/polkadot_sdk/polkadot.rs pwasm-alloc/Cargo.lock pwasm-libc/Cargo.lock release-artifacts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 069068369aba9d364e102c9d972ddca547c11f2e..7f8796ca51248acb8be92fcb9f76e280b18e605e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,6 @@ variables: RUSTY_CACHIER_COMPRESSION_METHOD: zstd NEXTEST_FAILURE_OUTPUT: immediate-final NEXTEST_SUCCESS_OUTPUT: final - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.71" DOCKER_IMAGES_VERSION: "${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}" default: @@ -70,7 +69,6 @@ default: .common-before-script: before_script: - !reference [.job-switcher, before_script] - - !reference [.timestamp, before_script] - !reference [.pipeline-stopper-vars, script] .job-switcher: @@ -108,27 +106,32 @@ default: .docker-env: image: "${CI_IMAGE}" + variables: + FL_FORKLIFT_VERSION: !reference [.forklift, variables, FL_FORKLIFT_VERSION] before_script: - !reference [.common-before-script, before_script] - !reference [.prepare-env, before_script] - !reference [.rust-info-script, script] - - !reference [.rusty-cachier, before_script] + - !reference [.forklift-cache, before_script] tags: - linux-docker -# rusty-cachier's hidden job. Parts of this job are used to instrument the pipeline's other real jobs with rusty-cachier -# rusty-cachier's commands are described here: https://gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client#description -.rusty-cachier: +# +.forklift-cache: before_script: - # - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash - # - mkdir -p cargo_home cargo_target_dir - # - export CARGO_HOME=$CI_PROJECT_DIR/cargo_home - # - export CARGO_TARGET_DIR=$CI_PROJECT_DIR/cargo_target_dir - # - find . \( -path ./cargo_target_dir -o -path ./cargo_home \) -prune -o -type f -exec touch -t 202005260100 {} + - # - git restore-mtime - # - rusty-cachier --version - # - rusty-cachier project touch-changed - - echo tbd + - 'curl --header "PRIVATE-TOKEN: $FL_CI_GROUP_TOKEN" -o forklift -L "${CI_API_V4_URL}/projects/676/packages/generic/forklift/${FL_FORKLIFT_VERSION}/forklift_${FL_FORKLIFT_VERSION}_linux_amd64"' + - chmod +x forklift + - mkdir ~/.forklift + - cp $FL_FORKLIFT_CONFIG ~/.forklift/config.toml + - 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 + # + - echo "FL_FORKLIFT_VERSION ${FL_FORKLIFT_VERSION}" .common-refs: rules: @@ -136,11 +139,13 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 .test-pr-refs: rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues # handle the specific case where benches could store incorrect bench data because of the downstream staging runs # exclude cargo-check-benches from such runs @@ -152,6 +157,7 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 .test-refs-no-trigger: @@ -162,6 +168,7 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ @@ -172,6 +179,7 @@ default: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues .publish-refs: rules: @@ -192,9 +200,7 @@ default: - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - -.zombienet-refs: - extends: .build-refs + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ # merge queues include: # check jobs @@ -209,14 +215,13 @@ include: - .gitlab/pipeline/publish.yml # zombienet jobs - .gitlab/pipeline/zombienet.yml - # timestamp handler - - project: parity/infrastructure/ci_cd/shared - ref: v0.2 - file: /common/timestamp.yml # ci image - project: parity/infrastructure/ci_cd/shared ref: main file: /common/ci-unified.yml + - project: parity/infrastructure/ci_cd/shared + ref: main + file: /common/forklift.yml # This job cancels the whole pipeline if any of provided jobs fail. # In a DAG, every jobs chain is executed independently of others. The `fail_fast` principle suggests # to fail the pipeline as soon as possible to shorten the feedback loop. @@ -265,16 +270,6 @@ cancel-pipeline-test-linux-stable3: needs: - job: "test-linux-stable 3/3" -cancel-pipeline-test-linux-stable-additional-tests: - extends: .cancel-pipeline-template - needs: - - job: "test-linux-stable-additional-tests" - -cancel-pipeline-test-linux-stable-slow: - extends: .cancel-pipeline-template - needs: - - job: "test-linux-stable-slow" - cancel-pipeline-cargo-check-benches1: extends: .cancel-pipeline-template needs: diff --git a/.gitlab/check-each-crate.py b/.gitlab/check-each-crate.py index adad4f5bd583543b4610cd010a9cefbd04bc5401..9b654f8071ac7237fe9c7c943540e8e020cebd6e 100755 --- a/.gitlab/check-each-crate.py +++ b/.gitlab/check-each-crate.py @@ -19,7 +19,11 @@ output = subprocess.check_output(["cargo", "tree", "--locked", "--workspace", "- crates = [] for line in output.splitlines(): if line != b"": - crates.append(line.decode('utf8').split(" ")[0]) + line = line.decode('utf8').split(" ") + crate_name = line[0] + # The crate path is always the last element in the line. + crate_path = line[len(line) - 1].replace("(", "").replace(")", "") + crates.append((crate_name, crate_path)) # Make the list unique and sorted crates = list(set(crates)) @@ -49,9 +53,9 @@ print(f"Crates per group: {crates_per_group}", file=sys.stderr) for i in range(0, crates_per_group + overflow_crates): crate = crates_per_group * target_group + i - print(f"Checking {crates[crate]}", file=sys.stderr) + print(f"Checking {crates[crate][0]}", file=sys.stderr) - res = subprocess.run(["cargo", "check", "--locked", "-p", crates[crate]]) + res = subprocess.run(["forklift", "cargo", "check", "--locked"], cwd = crates[crate][1]) if res.returncode != 0: sys.exit(1) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index fefa3739a9ff41bb412379dcf99a8e12033fc994..f8de6135572565d9d16465e68aa3f0bace915cc5 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -91,7 +91,7 @@ build-rustdoc: - .run-immediately variables: SKIP_WASM_BUILD: 1 - RUSTDOCFLAGS: "" + RUSTDOCFLAGS: "--default-theme=ayu --html-in-header ./docs/sdk/headers/header.html --extend-css ./docs/sdk/headers/theme.css" artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" when: on_success @@ -125,7 +125,7 @@ build-rustdoc: find "$path" -name '*.html' | xargs -I {} -P "$(nproc)" bash -c 'process_file "$@"' _ {} } inject_simple_analytics "./crate-docs" - - echo "" > ./crate-docs/index.html + - echo "" > ./crate-docs/index.html build-implementers-guide: stage: build @@ -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: @@ -304,8 +314,9 @@ build-linux-substrate: # tldr: we need to checkout the branch HEAD explicitly because of our dynamic versioning approach while building the substrate binary # see https://github.com/paritytech/ci_cd/issues/682#issuecomment-1340953589 - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" + - !reference [.forklift-cache, before_script] script: - - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release -p node-cli + - time WASM_BUILD_NO_COLOR=1 cargo build --locked --release -p staging-node-cli - mv $CARGO_TARGET_DIR/release/substrate-node ./artifacts/substrate/substrate - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then @@ -319,6 +330,18 @@ build-linux-substrate: # - printf '\n# building node-template\n\n' # - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz +build-runtimes-polkavm: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + script: + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p minimal-template-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p westend-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p rococo-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p polkadot-test-runtime + .build-subkey: stage: build extends: @@ -331,9 +354,10 @@ build-linux-substrate: CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" before_script: - mkdir -p ./artifacts/subkey + - !reference [.forklift-cache, before_script] script: - cd ./substrate/bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo build --locked --release + - time SKIP_WASM_BUILD=1 cargo build --locked --release # - cd - # - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. # - echo -n "Subkey version = " @@ -372,3 +396,18 @@ build-subkey-linux: # after_script: [""] # tags: # - osx + +# bridges + +# we need some non-binary artifacts in our bridges+zombienet image +prepare-bridges-zombienet-artifacts: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + - .collect-artifacts + before_script: + - mkdir -p ./artifacts/bridges-polkadot-sdk/bridges + script: + - cp -r bridges/testing ./artifacts/bridges-polkadot-sdk/bridges/testing diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 5a304d8bc4e2b35c6e90f4cd540d1306471bd329..52da33550508ede16c9577346e6985d869b5e8ae 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 @@ -21,16 +24,6 @@ check-try-runtime: # experimental code may rely on try-runtime and vice-versa - time cargo check --locked --all --features try-runtime,experimental -cargo-fmt-manifest: - stage: check - extends: - - .docker-env - - .common-refs - script: - - cargo install zepter --locked --version 0.11.0 -q -f --no-default-features && zepter --version - - echo "👉 Hello developer! If you see this CI check failing then it means that one of the your changes in a Cargo.toml file introduced ill-formatted or unsorted features. Please take a look at 'docs/STYLE_GUIDE.md#manifest-formatting' to find out more." - - zepter format features --check - # FIXME .cargo-deny-licenses: stage: check @@ -75,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 @@ -91,17 +80,22 @@ job-starter: script: - echo ok -test-rust-feature-propagation: +check-rust-feature-propagation: stage: check extends: - .kubernetes-env - - .test-pr-refs + - .common-refs script: - - cargo install --locked --version 0.11.1 -q -f zepter && zepter --version - - echo "👉 Hello developer! If you see this CI check failing then it means that one of the crates is missing a feature for one of its dependencies. The output below tells you which feature needs to be added for which dependency to which crate. You can do this by modifying the Cargo.toml file. For more context see the MR where this check was introduced https://github.com/paritytech/substrate/pull/14660" - - zepter lint propagate-feature --feature try-runtime --left-side-feature-missing=ignore --workspace --feature-enables-dep="try-runtime:frame-try-runtime" --locked - - zepter lint propagate-feature --feature runtime-benchmarks --left-side-feature-missing=ignore --workspace --feature-enables-dep="runtime-benchmarks:frame-benchmarking" --locked - - zepter lint propagate-feature --feature std --left-side-feature-missing=ignore --workspace --locked + - zepter run check + +check-toml-format: + stage: check + extends: + - .kubernetes-env + - .common-refs + script: + - taplo format --check --config .config/taplo.toml + - echo "Please run `taplo format --config .config/taplo.toml` to fix any toml formatting issues" # More info can be found here: https://github.com/paritytech/polkadot/pull/5865 .check-runtime-migration: @@ -114,16 +108,19 @@ test-rust-feature-propagation: export RUST_LOG=remote-ext=debug,runtime=debug echo "---------- Downloading try-runtime CLI ----------" - curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.3.3/try-runtime-x86_64-unknown-linux-musl -o try-runtime + curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.4/try-runtime-x86_64-unknown-linux-musl -o try-runtime chmod +x ./try-runtime + echo "Using try-runtime-cli version:" + ./try-runtime --version echo "---------- Building ${PACKAGE} runtime ----------" time cargo build --release --locked -p "$PACKAGE" --features try-runtime - echo "---------- Executing `on-runtime-upgrade` for ${NETWORK} ----------" - time ./try-runtime \ + echo "---------- Executing on-runtime-upgrade for ${NETWORK} ----------" + time ./try-runtime ${COMMAND_EXTRA_ARGS} \ --runtime ./target/release/wbuild/"$PACKAGE"/"$WASM" \ - on-runtime-upgrade --checks=pre-and-post ${EXTRA_ARGS} live --uri ${URI} + on-runtime-upgrade --disable-spec-version-check --checks=all ${SUBCOMMAND_EXTRA_ARGS} live --uri ${URI} + sleep 5 # Check runtime migrations for Parity managed relay chains check-runtime-migration-westend: @@ -137,7 +134,8 @@ check-runtime-migration-westend: PACKAGE: "westend-runtime" WASM: "westend_runtime.compact.compressed.wasm" URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" - EXTRA_ARGS: "--no-weight-warnings" + SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" + allow_failure: true check-runtime-migration-rococo: stage: check @@ -150,7 +148,7 @@ check-runtime-migration-rococo: PACKAGE: "rococo-runtime" WASM: "rococo_runtime.compact.compressed.wasm" URI: "wss://rococo-try-runtime-node.parity-chains.parity.io:443" - EXTRA_ARGS: "--no-weight-warnings" + SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" # Check runtime migrations for Parity managed asset hub chains check-runtime-migration-asset-hub-westend: @@ -165,6 +163,31 @@ check-runtime-migration-asset-hub-westend: WASM: "asset_hub_westend_runtime.compact.compressed.wasm" URI: "wss://westend-asset-hub-rpc.polkadot.io:443" +check-runtime-migration-asset-hub-rococo: + stage: check + extends: + - .docker-env + - .test-pr-refs + - .check-runtime-migration + variables: + NETWORK: "asset-hub-rococo" + PACKAGE: "asset-hub-rococo-runtime" + WASM: "asset_hub_rococo_runtime.compact.compressed.wasm" + URI: "wss://rococo-asset-hub-rpc.polkadot.io:443" + +# Check runtime migrations for Parity managed bridge hub chains +check-runtime-migration-bridge-hub-westend: + stage: check + extends: + - .docker-env + - .test-pr-refs + - .check-runtime-migration + variables: + NETWORK: "bridge-hub-westend" + PACKAGE: "bridge-hub-westend-runtime" + WASM: "bridge_hub_westend_runtime.compact.compressed.wasm" + URI: "wss://westend-bridge-hub-rpc.polkadot.io:443" + check-runtime-migration-bridge-hub-rococo: stage: check extends: @@ -190,6 +213,33 @@ check-runtime-migration-contracts-rococo: WASM: "contracts_rococo_runtime.compact.compressed.wasm" URI: "wss://rococo-contracts-rpc.polkadot.io:443" +# Check runtime migrations for Parity managed collectives chains +check-runtime-migration-collectives-westend: + stage: check + extends: + - .docker-env + - .test-pr-refs + - .check-runtime-migration + variables: + NETWORK: "collectives-westend" + PACKAGE: "collectives-westend-runtime" + WASM: "collectives_westend_runtime.compact.compressed.wasm" + 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: @@ -209,3 +259,19 @@ find-fail-ci-phrase: echo "No $ASSERT_REGEX was found, exiting with 0"; exit 0; fi + +check-core-crypto-features: + stage: check + extends: + - .docker-env + - .common-refs + script: + - pushd substrate/primitives/core + - ./check-features-variants.sh + - popd + - pushd substrate/primitives/application-crypto + - ./check-features-variants.sh + - popd + - pushd substrate/primitives/keyring + - ./check-features-variants.sh + - popd diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index a03d407c040904a6a1a4fd7095052a039d0019e0..b73acb560f67f93e540826b95fcf075374189846 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -63,21 +63,23 @@ publish-rustdoc: after_script: - rm -rf .git/ ./* -# cumulus - +# note: images are used not only in zombienet but also in rococo, wococo and versi .build-push-image: image: $BUILDAH_IMAGE + extends: + - .zombienet-refs variables: DOCKERFILE: "" # docker/path-to.Dockerfile IMAGE_NAME: "" # docker.io/paritypr/image_name script: - # - test "$PARITYPR_USER" -a "$PARITYPR_PASS" || - # ( echo "no docker credentials provided"; exit 1 ) + # Dockertag should differ in a merge queue + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi - $BUILDAH_COMMAND build --format=docker --build-arg VCS_REF="${CI_COMMIT_SHA}" --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" --build-arg IMAGE_NAME="${IMAGE_NAME}" + --build-arg ZOMBIENET_IMAGE="${ZOMBIENET_IMAGE}" --tag "$IMAGE_NAME:${DOCKER_IMAGES_VERSION}" --file ${DOCKERFILE} . - echo "$PARITYPR_PASS" | @@ -112,59 +114,6 @@ build-push-image-test-parachain: variables: DOCKERFILE: "docker/dockerfiles/test-parachain_injected.Dockerfile" IMAGE_NAME: "docker.io/paritypr/test-parachain" -# publish-s3: -# stage: publish -# extends: -# - .kubernetes-env -# - .publish-refs -# image: paritytech/awscli:latest -# needs: -# - job: build-linux-stable-cumulus -# artifacts: true -# variables: -# GIT_STRATEGY: none -# BUCKET: "releases.parity.io" -# PREFIX: "cumulus/${ARCH}-${DOCKER_OS}" -# script: -# - echo "___Publishing a binary with debug assertions!___" -# - echo "___VERSION = $(cat ./artifacts/VERSION) ___" -# - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/VERSION)/ -# - echo "___Updating objects in latest path___" -# - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ -# after_script: -# - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ -# --recursive --human-readable --summarize - -# publish-benchmarks-assets-s3: &publish-benchmarks -# stage: publish -# extends: -# - .kubernetes-env -# - .benchmarks-refs -# image: paritytech/awscli:latest -# needs: -# - job: benchmarks-assets -# artifacts: true -# variables: -# GIT_STRATEGY: none -# BUCKET: "releases.parity.io" -# PREFIX: "cumulus/$CI_COMMIT_REF_NAME/benchmarks-assets" -# script: -# - echo "___Publishing benchmark results___" -# - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/ -# after_script: -# - aws s3 ls s3://${BUCKET}/${PREFIX}/ --recursive --human-readable --summarize - -# publish-benchmarks-collectives-s3: -# <<: *publish-benchmarks -# variables: -# GIT_STRATEGY: none -# BUCKET: "releases.parity.io" -# PREFIX: "cumulus/$CI_COMMIT_REF_NAME/benchmarks-collectives" -# needs: -# - job: benchmarks-collectives -# artifacts: true - -### Polkadot build-push-image-polkadot-debug: stage: publish @@ -217,143 +166,22 @@ build-push-image-substrate-pr: variables: DOCKERFILE: "docker/dockerfiles/substrate_injected.Dockerfile" IMAGE_NAME: "docker.io/paritypr/substrate" -# old way - -# .build-push-image-polkadot: -# before_script: -# # - test -s ./artifacts/VERSION || exit 1 -# # - test -s ./artifacts/EXTRATAG || exit 1 -# - VERSION="$(cat ./artifacts/VERSION)" -# - EXTRATAG="$(cat ./artifacts/EXTRATAG)" -# - echo "Polkadot version = ${VERSION} (EXTRATAG = ${EXTRATAG})" -# script: -# # - test "$DOCKER_USER" -a "$DOCKER_PASS" || -# # ( echo "no docker credentials provided"; exit 1 ) -# - cd ./artifacts -# - $BUILDAH_COMMAND build -# --format=docker -# --build-arg VCS_REF="${CI_COMMIT_SHA}" -# --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" -# --build-arg IMAGE_NAME="${IMAGE_NAME}" -# --tag "$IMAGE_NAME:$VERSION" -# --tag "$IMAGE_NAME:$EXTRATAG" -# --file ${DOCKERFILE} . -# # The job will success only on the protected branch -# # - echo "$DOCKER_PASS" | -# # buildah login --username "$DOCKER_USER" --password-stdin docker.io -# # - $BUILDAH_COMMAND info -# # - $BUILDAH_COMMAND push --format=v2s2 "$IMAGE_NAME:$VERSION" -# # - $BUILDAH_COMMAND push --format=v2s2 "$IMAGE_NAME:$EXTRATAG" -# after_script: -# - buildah logout --all -# publish-polkadot-debug-image: -# stage: publish -# image: ${BUILDAH_IMAGE} -# extends: -# - .kubernetes-env -# - .build-push-image-polkadot -# rules: -# - if: $CI_PIPELINE_SOURCE == "web" -# - if: $CI_PIPELINE_SOURCE == "schedule" -# - if: $CI_COMMIT_REF_NAME == "master" -# - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -# - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 -# variables: -# GIT_STRATEGY: none -# DOCKER_USER: ${PARITYPR_USER} -# DOCKER_PASS: ${PARITYPR_PASS} -# # docker/dockerfiles/polkadot/polkadot_injected_debug.Dockerfile -# DOCKERFILE: polkadot_injected_debug.Dockerfile -# IMAGE_NAME: docker.io/paritypr/polkadot-debug -# needs: -# - job: build-linux-stable -# artifacts: true -# after_script: -# # pass artifacts to the zombienet-tests job -# # https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#with-variable-inheritance -# - echo "PARACHAINS_IMAGE_NAME=${IMAGE_NAME}" > ./artifacts/parachains.env -# - echo "PARACHAINS_IMAGE_TAG=$(cat ./artifacts/EXTRATAG)" >> ./artifacts/parachains.env -# artifacts: -# reports: -# # this artifact is used in zombienet-tests job -# dotenv: ./artifacts/parachains.env -# expire_in: 1 days - -# publish-test-collators-image: -# # service image for zombienet -# stage: publish -# extends: -# - .kubernetes-env -# - .build-push-image-polkadot -# - .zombienet-refs -# variables: -# CI_IMAGE: ${BUILDAH_IMAGE} -# GIT_STRATEGY: none -# DOCKER_USER: ${PARITYPR_USER} -# DOCKER_PASS: ${PARITYPR_PASS} -# # docker/dockerfiles/collator_injected.Dockerfile -# DOCKERFILE: collator_injected.Dockerfile -# IMAGE_NAME: docker.io/paritypr/colander -# needs: -# - job: build-test-collators -# artifacts: true -# after_script: -# - buildah logout --all -# # pass artifacts to the zombienet-tests job -# - echo "COLLATOR_IMAGE_NAME=${IMAGE_NAME}" > ./artifacts/collator.env -# - echo "COLLATOR_IMAGE_TAG=$(cat ./artifacts/EXTRATAG)" >> ./artifacts/collator.env -# artifacts: -# reports: -# # this artifact is used in zombienet-tests job -# dotenv: ./artifacts/collator.env - -# publish-malus-image: -# # service image for Simnet -# stage: publish -# extends: -# - .kubernetes-env -# - .build-push-image-polkadot -# - .zombienet-refs -# variables: -# CI_IMAGE: ${BUILDAH_IMAGE} -# GIT_STRATEGY: none -# DOCKER_USER: ${PARITYPR_USER} -# DOCKER_PASS: ${PARITYPR_PASS} -# # docker/dockerfiles/malus_injected.Dockerfile -# DOCKERFILE: malus_injected.Dockerfile -# IMAGE_NAME: docker.io/paritypr/malus -# needs: -# - job: build-malus -# artifacts: true -# after_script: -# - buildah logout "$IMAGE_NAME" -# # pass artifacts to the zombienet-tests job -# - echo "MALUS_IMAGE_NAME=${IMAGE_NAME}" > ./artifacts/malus.env -# - echo "MALUS_IMAGE_TAG=$(cat ./artifacts/EXTRATAG)" >> ./artifacts/malus.env -# artifacts: -# reports: -# # this artifact is used in zombienet-tests job -# dotenv: ./artifacts/malus.env - -# substrate - -# publish-substrate-image-pr: -# # service image for zombienet -# stage: publish -# extends: -# - .kubernetes-env -# - .build-push-image-polkadot -# - .zombienet-refs -# variables: -# CI_IMAGE: ${BUILDAH_IMAGE} -# GIT_STRATEGY: none -# DOCKER_USER: ${PARITYPR_USER} -# DOCKER_PASS: ${PARITYPR_PASS} -# DOCKERFILE: substrate_injected.Dockerfile -# IMAGE_NAME: docker.io/paritypr/substrate -# needs: -# - job: build-linux-substrate -# artifacts: true -# after_script: -# - buildah logout "$IMAGE_NAME" +# unlike other images, bridges+zombienet image is based on Zombienet image that pulls required binaries +# from other fresh images (polkadot and cumulus) +build-push-image-bridges-zombienet-tests: + stage: publish + extends: + - .kubernetes-env + - .common-refs + - .build-push-image + needs: + - job: build-linux-stable + artifacts: true + - job: build-linux-stable-cumulus + artifacts: true + - job: prepare-bridges-zombienet-artifacts + artifacts: true + variables: + DOCKERFILE: "docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" + IMAGE_NAME: "docker.io/paritypr/bridges-zombienet-tests" diff --git a/.gitlab/pipeline/short-benchmarks.yml b/.gitlab/pipeline/short-benchmarks.yml index 6d060077a7c7334ca6d3e50309bfe0d28ca6f9e3..bc6dd04264c8e3a46a7c99e427ef6b60243af481 100644 --- a/.gitlab/pipeline/short-benchmarks.yml +++ b/.gitlab/pipeline/short-benchmarks.yml @@ -49,47 +49,52 @@ short-benchmark-westend: &short-bench script: - ./artifacts/polkadot-parachain benchmark pallet --chain $RUNTIME_CHAIN --pallet "*" --extrinsic "*" --steps 2 --repeat 1 -short-benchmark-asset-hub-polkadot: +short-benchmark-asset-hub-rococo: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: asset-hub-polkadot-dev + RUNTIME_CHAIN: asset-hub-rococo-dev -short-benchmark-asset-hub-kusama: +short-benchmark-asset-hub-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: asset-hub-kusama-dev + RUNTIME_CHAIN: asset-hub-westend-dev -short-benchmark-asset-hub-rococo: +short-benchmark-bridge-hub-rococo: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: asset-hub-rococo-dev + RUNTIME_CHAIN: bridge-hub-rococo-dev -short-benchmark-asset-hub-westend: +short-benchmark-bridge-hub-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: asset-hub-westend-dev + RUNTIME_CHAIN: bridge-hub-westend-dev -short-benchmark-bridge-hub-polkadot: +short-benchmark-collectives-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: bridge-hub-polkadot-dev + RUNTIME_CHAIN: collectives-westend-dev -short-benchmark-bridge-hub-kusama: +short-benchmark-coretime-rococo: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: bridge-hub-kusama-dev + RUNTIME_CHAIN: coretime-rococo-dev -short-benchmark-bridge-hub-rococo: +short-benchmark-coretime-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: bridge-hub-rococo-dev + RUNTIME_CHAIN: coretime-westend-dev + +short-benchmark-people-rococo: + <<: *short-bench-cumulus + variables: + RUNTIME_CHAIN: people-rococo-dev -short-benchmark-collectives-polkadot: +short-benchmark-people-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: collectives-polkadot-dev + RUNTIME_CHAIN: people-westend-dev -short-benchmark-glutton-kusama: +short-benchmark-glutton-westend: <<: *short-bench-cumulus variables: - RUNTIME_CHAIN: glutton-kusama-dev-1300 + RUNTIME_CHAIN: glutton-westend-dev-1300 diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 7d7007acd8a8bba2a266422a096a1901734771f2..d244316000aaf152810c396e37e470694be506cc 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -25,11 +25,12 @@ test-linux-stable: # "upgrade_version_checks_should_work" is currently failing - | time cargo nextest run \ + --filter-expr 'not deps(/polkadot-subsystem-bench/)' \ --workspace \ --locked \ --release \ --no-fail-fast \ - --features try-runtime,experimental \ + --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" @@ -48,6 +49,7 @@ test-linux-stable: - target/nextest/default/junit.xml reports: junit: target/nextest/default/junit.xml + timeout: 90m test-linux-oldkernel-stable: extends: test-linux-stable @@ -68,7 +70,7 @@ test-linux-stable-runtime-benchmarks: # but still want to have debug assertions. RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" script: - - time cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet + - time cargo nextest run --filter-expr 'not deps(/polkadot-subsystem-bench/)' --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet # can be used to run all tests # test-linux-stable-all: @@ -96,44 +98,6 @@ test-linux-stable-runtime-benchmarks: # --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} # # todo: add flacky-test collector -# TODO: remove me -test-linux-stable-additional-tests: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - variables: - RUST_TOOLCHAIN: stable - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - script: - # tests were moved to test-linux-stable - # the jobs should be removed - - exit 0 - -# TODO: remove me -test-linux-stable-slow: - stage: test - # remove after cache is setup - timeout: 2h - extends: - - .docker-env - - .common-refs - - .run-immediately - - .pipeline-stopper-artifacts - variables: - RUST_TOOLCHAIN: stable - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - script: - # tests were moved to test-linux-stable - # the jobs should be removed - - exit 0 - # takes about 1,5h without cache # can be used to check that nextest works correctly # test-linux-stable-polkadot: @@ -238,6 +202,8 @@ test-deterministic-wasm: cargo-check-benches: stage: test + artifacts: + expire_in: 10 days variables: CI_JOB_NAME: "cargo-check-benches" extends: @@ -260,6 +226,7 @@ cargo-check-benches: git merge --verbose --no-edit FETCH_HEAD; fi fi' + - !reference [.forklift-cache, before_script] parallel: 2 script: - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA @@ -270,7 +237,7 @@ cargo-check-benches: SKIP_WASM_BUILD=1 time cargo check --locked --benches --all; cargo run --locked --release -p node-bench -- ::trie::read::small --json | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; - echo "___Uploading cache for rusty-cachier___"; + echo "___Cache could be uploaded___"; ;; 2) cargo run --locked --release -p node-bench -- ::node::import::sr25519::transfer_keep_alive::paritydb::small --json @@ -305,6 +272,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" @@ -313,7 +284,7 @@ node-bench-regression-guard: after_script: [""] # if this fails run `bot update-ui` in the Pull Request or "./scripts/update-ui-tests.sh" locally -# see ./docs/CONTRIBUTING.md#ui-tests +# see ./docs/contributor/CONTRIBUTING.md#ui-tests test-frame-ui: stage: test extends: @@ -352,7 +323,7 @@ quick-benchmarks: WASM_BUILD_NO_COLOR: 1 WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" script: - - time cargo run --locked --release -p node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --execution wasm --wasm-execution compiled --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 + - time cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --execution wasm --wasm-execution compiled --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 test-frame-examples-compile-to-wasm: # into one job @@ -395,8 +366,7 @@ test-linux-stable-int: RUN_UI_TESTS: 1 script: - WASM_BUILD_NO_COLOR=1 - RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace - time cargo test -p node-cli --release --locked -- --ignored + time cargo test -p staging-node-cli --release --locked -- --ignored # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/6916 @@ -439,7 +409,8 @@ cargo-check-each-crate: - .run-immediately # - .collect-artifacts variables: - # $CI_JOB_NAME is set manually so that rusty-cachier can share the cache for all + 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 timeout: 2h @@ -462,10 +433,10 @@ cargo-check-each-crate-macos: variables: SKIP_WASM_BUILD: 1 script: - # TODO: enable rusty-cachier once it supports Mac # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available # - time ./scripts/ci/gitlab/check-each-crate.py 1 1 - time cargo check --workspace --locked + timeout: 2h tags: - osx @@ -488,7 +459,7 @@ cargo-hfuzz: # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr - # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling rusty-cachier's absolute CARGO_TARGET_DIR + # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR HFUZZ_BUILD_ARGS: > --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" @@ -520,6 +491,18 @@ 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: true # TODO: remove this once we have an idea how often the syscall lists will change + allow_failure: false # this rarely triggers in practice + +subsystem-regression-tests: + stage: test + extends: + - .docker-env + - .common-refs + - .run-immediately + script: + - cargo bench --profile=testnet -p polkadot-availability-recovery --bench availability-recovery-regression-bench --features subsystem-benchmarks + tags: + - benchmark + allow_failure: true diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 64210d6a00ab5b83b6b018ca3e0c27d827f74de1..55120e66d0e53c740b16a7ee6276230f42c172ef 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,3 +1,8 @@ +.zombienet-refs: + extends: .build-refs + variables: + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.91" + include: # substrate tests - .gitlab/pipeline/zombienet/substrate.yml @@ -5,3 +10,5 @@ include: - .gitlab/pipeline/zombienet/cumulus.yml # polkadot tests - .gitlab/pipeline/zombienet/polkadot.yml + # bridges tests + - .gitlab/pipeline/zombienet/bridges.yml diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml new file mode 100644 index 0000000000000000000000000000000000000000..4278f59b1e9a2e33f32bf255436d6af5d31b30fb --- /dev/null +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -0,0 +1,63 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage for bridges + +# common settings for all zombienet jobs +.zombienet-bridges-common: + extends: + - .kubernetes-env + - .zombienet-refs + rules: + # Docker images have different tag in merge queues + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ + variables: + DOCKER_IMAGES_VERSION: ${CI_COMMIT_SHORT_SHA} + - !reference [.build-refs, rules] + before_script: + - echo "Zombienet Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${GH_DIR}" + - echo "${LOCAL_DIR}" + - ls "${LOCAL_DIR}" + - export DEBUG=zombie,zombie::network-node + - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${BRIDGES_ZOMBIENET_TESTS_IMAGE}":${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG} + - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + stage: zombienet + image: "${BRIDGES_ZOMBIENET_TESTS_IMAGE}:${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG}" + needs: + - job: build-push-image-bridges-zombienet-tests + artifacts: true + variables: + BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} + BRIDGES_ZOMBIENET_TESTS_IMAGE: "docker.io/paritypr/bridges-zombienet-tests" + GH_DIR: "https://github.com/paritytech/polkadot-sdk/tree/${CI_COMMIT_SHA}/bridges/testing" + LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/bridges/testing" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + RUN_IN_CONTAINER: "1" + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}_zombienet_bridge_tests" + when: always + expire_in: 2 days + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + # copy general logs + - cp -r /tmp/bridges-tests-run-*/logs/* ./zombienet-logs/ + # copy logs of rococo nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_rococo_local_network/*.log ./zombienet-logs/ + # copy logs of westend nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_westend_local_network/*.log ./zombienet-logs/ + +zombienet-bridges-0001-asset-transfer-works: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker + - echo "Done" + +zombienet-bridges-0002-mandatory-headers-synced-while-idle: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker + - echo "Done" diff --git a/.gitlab/pipeline/zombienet/cumulus.yml b/.gitlab/pipeline/zombienet/cumulus.yml index 3f2c6f64fbfe120ae15bf394982d6dbdd29405b0..c473f5c5fed755bfcceeeceea30a93c1d0c3403d 100644 --- a/.gitlab/pipeline/zombienet/cumulus.yml +++ b/.gitlab/pipeline/zombienet/cumulus.yml @@ -3,6 +3,12 @@ .zombienet-before-script: before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export POLKADOT_IMAGE="docker.io/paritypr/polkadot-debug:${DOCKER_IMAGES_VERSION}" + - export COL_IMAGE="docker.io/paritypr/test-parachain:${DOCKER_IMAGES_VERSION}" - echo "Zombie-net Tests Config" - echo "${ZOMBIENET_IMAGE}" - echo "${POLKADOT_IMAGE}" @@ -25,11 +31,13 @@ needs: - job: build-push-image-test-parachain artifacts: true + - job: build-push-image-polkadot-debug + artifacts: true variables: - POLKADOT_IMAGE: "docker.io/paritypr/polkadot-debug:master" + # POLKADOT_IMAGE: "docker.io/paritypr/polkadot-debug:${DOCKER_IMAGES_VERSION}" GH_DIR: "https://github.com/paritytech/cumulus/tree/${CI_COMMIT_SHORT_SHA}/zombienet/tests" LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/cumulus/zombienet/tests" - COL_IMAGE: "docker.io/paritypr/test-parachain:${DOCKER_IMAGES_VERSION}" + # COL_IMAGE: "docker.io/paritypr/test-parachain:${DOCKER_IMAGES_VERSION}" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" artifacts: diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 0402c194134b78256aaee00999e87c7a18397bf0..97572f029d0020f090a8fd16839028ac9f088cf9 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -4,6 +4,11 @@ # common settings for all zombienet jobs .zombienet-polkadot-common: before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export PIPELINE_IMAGE_TAG=${DOCKER_IMAGES_VERSION} - export BUILD_RELEASE_VERSION="$(cat ./artifacts/BUILD_RELEASE_VERSION)" # from build-linux-stable job - export DEBUG=zombie,zombie::network-node - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${POLKADOT_IMAGE}":${PIPELINE_IMAGE_TAG} @@ -12,12 +17,12 @@ - export MALUS_IMAGE="${MALUS_IMAGE}":${PIPELINE_IMAGE_TAG} - IMAGE_AVAILABLE=$(curl -o /dev/null -w "%{http_code}" -I -L -s https://registry.hub.docker.com/v2/repositories/parity/polkadot/tags/${BUILD_RELEASE_VERSION}) - if [ $IMAGE_AVAILABLE -eq 200 ]; then - export ZOMBIENET_INTEGRATION_TEST_SECONDARY_IMAGE="docker.io/parity/polkadot:${BUILD_RELEASE_VERSION}"; + export ZOMBIENET_INTEGRATION_TEST_SECONDARY_IMAGE="docker.io/parity/polkadot:${BUILD_RELEASE_VERSION}"; else - echo "Getting the image to use as SECONDARY, using ${BUILD_RELEASE_VERSION} as base"; - VERSIONS=$(curl -L -s 'https://registry.hub.docker.com/v2/repositories/parity/polkadot/tags/' | jq -r '.results[].name'| grep -E "v[0-9]" |grep -vE "[0-9]-"); - VERSION_TO_USE=$(echo "${BUILD_RELEASE_VERSION}\n$VERSIONS"|sort -r|grep -A1 "${BUILD_RELEASE_VERSION}"|tail -1); - export ZOMBIENET_INTEGRATION_TEST_SECONDARY_IMAGE="docker.io/parity/polkadot:${VERSION_TO_USE}"; + echo "Getting the image to use as SECONDARY, using ${BUILD_RELEASE_VERSION} as base"; + VERSIONS=$(curl -L -s 'https://registry.hub.docker.com/v2/repositories/parity/polkadot/tags/' | jq -r '.results[].name'| grep -E "v[0-9]" |grep -vE "[0-9]-"); + VERSION_TO_USE=$(echo "${BUILD_RELEASE_VERSION}\n$VERSIONS"|sort -r|grep -A1 "${BUILD_RELEASE_VERSION}"|tail -1); + export ZOMBIENET_INTEGRATION_TEST_SECONDARY_IMAGE="docker.io/parity/polkadot:${VERSION_TO_USE}"; fi - echo "Zombienet Tests Config" - echo "gh-dir ${GH_DIR}" @@ -44,7 +49,7 @@ - .kubernetes-env - .zombienet-refs variables: - PIPELINE_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} + # PIPELINE_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} POLKADOT_IMAGE: "docker.io/paritypr/polkadot-debug" COLANDER_IMAGE: "docker.io/paritypr/colander" MALUS_IMAGE: "docker.io/paritypr/malus" @@ -105,10 +110,71 @@ zombienet-polkadot-functional-0005-parachains-disputes-past-session: --local-dir="${LOCAL_DIR}/functional" --test="0005-parachains-disputes-past-session.zndsl" +zombienet-polkadot-functional-0006-parachains-max-tranche0: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0006-parachains-max-tranche0.zndsl" + +zombienet-polkadot-functional-0007-dispute-freshly-finalized: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0007-dispute-freshly-finalized.zndsl" + +zombienet-polkadot-functional-0008-dispute-old-finalized: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --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-functional-0010-validator-disabling: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0010-validator-disabling.zndsl" + +zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0011-async-backing-6-seconds-rate.zndsl" + +zombienet-polkadot-functional-0012-elastic-scaling-mvp: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0012-elastic-scaling-mvp.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export PIPELINE_IMAGE_TAG=${DOCKER_IMAGES_VERSION} - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${POLKADOT_IMAGE}":${PIPELINE_IMAGE_TAG} - export COL_IMAGE="${COLANDER_IMAGE}":${PIPELINE_IMAGE_TAG} - echo "Zombienet Tests Config" @@ -126,6 +192,11 @@ zombienet-polkadot-smoke-0002-parachains-parachains-upgrade-smoke: extends: - .zombienet-polkadot-common before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export PIPELINE_IMAGE_TAG=${DOCKER_IMAGES_VERSION} - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${POLKADOT_IMAGE}":${PIPELINE_IMAGE_TAG} - export CUMULUS_IMAGE="docker.io/paritypr/polkadot-parachain-debug:${DOCKER_IMAGES_VERSION}" - echo "Zombienet Tests Config" @@ -147,6 +218,14 @@ zombienet-polkadot-smoke-0003-deregister-register-validator: --local-dir="${LOCAL_DIR}/smoke" --test="0003-deregister-register-validator-smoke.zndsl" +zombienet-polkadot-smoke-0004-coretime-smoke-test: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/smoke" + --test="0004-coretime-smoke-test.zndsl" + zombienet-polkadot-misc-0001-parachains-paritydb: extends: - .zombienet-polkadot-common @@ -168,8 +247,13 @@ zombienet-polkadot-misc-0002-upgrade-node: - job: build-linux-stable artifacts: true before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export PIPELINE_IMAGE_TAG=${DOCKER_IMAGES_VERSION} - 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/pipeline/zombienet/substrate.yml b/.gitlab/pipeline/zombienet/substrate.yml index 9fb2f161ad73367a86279a6e7268f9116eae8f54..8a627c454f9f3853f04694827e1484571f5444a9 100644 --- a/.gitlab/pipeline/zombienet/substrate.yml +++ b/.gitlab/pipeline/zombienet/substrate.yml @@ -4,6 +4,11 @@ # common settings for all zombienet jobs .zombienet-substrate-common: before_script: + # 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 + # Docker images have different tag in merge queues + - if [[ $CI_COMMIT_REF_NAME == *"gh-readonly-queue"* ]]; then export DOCKER_IMAGES_VERSION="${CI_COMMIT_SHORT_SHA}"; fi + - export SUBSTRATE_IMAGE_TAG=${DOCKER_IMAGES_VERSION} - echo "Zombienet Tests Config" - echo "${ZOMBIENET_IMAGE}" - echo "${GH_DIR}" @@ -19,7 +24,7 @@ - .kubernetes-env - .zombienet-refs variables: - SUBSTRATE_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} + # SUBSTRATE_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} SUBSTRATE_IMAGE: "docker.io/paritypr/substrate" GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/substrate/zombienet" @@ -38,6 +43,16 @@ tags: - zombienet-polkadot-integration-test +.zombienet-substrate-warp-sync-common: + extends: + - .zombienet-substrate-common + variables: + # DB generated from commit: https://github.com/paritytech/polkadot-sdk/commit/868788a5bff3ef94869bd36432726703fe3b4e96 + # TODO: As a workaround for https://github.com/paritytech/polkadot-sdk/issues/2568 the DB was generated in archive mode. + # After the issue is fixed, we should replace it with a pruned version of the DB. + DB_SNAPSHOT: "https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-9677807d738b951e9f6c82e5fd15518eb0ae0419.tgz" + DB_BLOCK_HEIGHT: 56687 + zombienet-substrate-0000-block-building: extends: - .zombienet-substrate-common @@ -48,7 +63,7 @@ zombienet-substrate-0000-block-building: zombienet-substrate-0001-basic-warp-sync: extends: - - .zombienet-substrate-common + - .zombienet-substrate-warp-sync-common script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/0001-basic-warp-sync" @@ -56,7 +71,10 @@ zombienet-substrate-0001-basic-warp-sync: zombienet-substrate-0002-validators-warp-sync: extends: - - .zombienet-substrate-common + - .zombienet-substrate-warp-sync-common + before_script: + - !reference [.zombienet-substrate-warp-sync-common, before_script] + - cp --remove-destination ${LOCAL_DIR}/0001-basic-warp-sync/chain-spec.json ${LOCAL_DIR}/0002-validators-warp-sync script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/0002-validators-warp-sync" @@ -64,7 +82,10 @@ zombienet-substrate-0002-validators-warp-sync: zombienet-substrate-0003-block-building-warp-sync: extends: - - .zombienet-substrate-common + - .zombienet-substrate-warp-sync-common + before_script: + - !reference [.zombienet-substrate-warp-sync-common, before_script] + - cp --remove-destination ${LOCAL_DIR}/0001-basic-warp-sync/chain-spec.json ${LOCAL_DIR}/0003-block-building-warp-sync script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh --local-dir="${LOCAL_DIR}/0003-block-building-warp-sync" 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/.gitlab/spellcheck.toml b/.gitlab/spellcheck.toml index 025c7a0a461b05050d8cc559e103d4e8f1dc43d7..8c60bf6915d16c8ea091119bfa70940411157509 100644 --- a/.gitlab/spellcheck.toml +++ b/.gitlab/spellcheck.toml @@ -8,20 +8,20 @@ use_builtin = true [hunspell.quirks] # He tagged it as 'TheGreatestOfAllTimes' transform_regex = [ -# `Type`'s + # `Type`'s "^'([^\\s])'$", -# 5x -# 10.7% + # 5x + # 10.7% "^[0-9_]+(?:\\.[0-9]*)?(x|%)$", -# Transforms' + # Transforms' "^(.*)'$", -# backslashes - "^\\+$", + # backslashes "^[0-9]*+k|MB|Mb|ms|Mbit|nd|th|rd$", -# single char `=` `>` `%` .. + "^\\+$", + # single char `=` `>` `%` .. "^=|>|<|%$", -# 22_100 - "^(?:[0-9]+_)+[0-9]+$" + # 22_100 + "^(?:[0-9]+_)+[0-9]+$", ] allow_concatenation = true allow_dashes = true diff --git a/.prdoc.toml b/.prdoc.toml new file mode 100644 index 0000000000000000000000000000000000000000..01e2eebe54b83a263dfa85cc28af7d041fd57bd0 --- /dev/null +++ b/.prdoc.toml @@ -0,0 +1,7 @@ +# Config file for prdoc, see https://github.com/paritytech/prdoc + +version = 1 +schema = "prdoc/schema_user.json" +output_dir = "prdoc" +prdoc_folders = ["prdoc"] +template = "prdoc/.template.prdoc" diff --git a/Cargo.lock b/Cargo.lock index 90fda2d0ecc0e679116cbb7c3bd97af2844a2fcd..5c0066e9728348150c8620a119af0ff1403c5b7d 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" @@ -107,16 +86,16 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", - "ctr 0.8.0", + "ctr 0.7.0", "ghash 0.4.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -130,34 +109,14 @@ dependencies = [ "cipher 0.4.4", "ctr 0.9.2", "ghash 0.5.0", - "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", + "subtle 2.5.0", ] [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.10", "once_cell", @@ -166,14 +125,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -191,6 +151,78 @@ 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", + "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.53", +] + +[[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 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.53", + "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" @@ -229,9 +261,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -292,9 +324,9 @@ dependencies = [ [[package]] name = "aquamarine" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" dependencies = [ "include_dir", "itertools 0.10.5", @@ -305,16 +337,24 @@ dependencies = [ ] [[package]] -name = "arbitrary" -version = "1.3.0" +name = "aquamarine" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.53", +] [[package]] -name = "arc-swap" -version = "1.6.0" +name = "arbitrary" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "ark-bls12-377" @@ -323,8 +363,20 @@ 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]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-models-ext", + "ark-std 0.4.0", ] [[package]] @@ -334,9 +386,23 @@ 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]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -347,8 +413,21 @@ checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", ] [[package]] @@ -357,14 +436,15 @@ 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", "num-traits", + "rayon", "zeroize", ] @@ -376,8 +456,21 @@ checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", ] [[package]] @@ -388,8 +481,39 @@ checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.4.2", + "ark-models-ext", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", ] [[package]] @@ -398,10 +522,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", @@ -412,6 +536,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" @@ -422,6 +556,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" @@ -435,15 +581,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-models-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", +] + [[package]] name = "ark-poly" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -455,9 +614,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", ] @@ -465,18 +638,28 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=4b09416#4b09416fd23383ec436ddac127d58c7b7cd392c6" +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", - "rand_core 0.6.4", + "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" @@ -484,7 +667,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", ] @@ -500,6 +683,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", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -507,17 +700,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", + "rayon", ] [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=4b09416#4b09416fd23383ec436ddac127d58c7b7cd392c6" +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", @@ -550,41 +744,19 @@ dependencies = [ "nodrop", ] -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.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", -] - [[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", @@ -594,18 +766,6 @@ dependencies = [ "time", ] -[[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", -] - [[package]] name = "asn1-rs-derive" version = "0.4.0" @@ -651,145 +811,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] -name = "asset-hub-kusama-runtime" -version = "0.9.420" +name = "asset-hub-rococo-emulated-chain" +version = "0.0.0" dependencies = [ - "asset-test-utils", - "assets-common", - "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", - "cumulus-pallet-parachain-system", - "cumulus-pallet-session-benchmarking", - "cumulus-pallet-xcm", - "cumulus-pallet-xcmp-queue", + "asset-hub-rococo-runtime", "cumulus-primitives-core", - "cumulus-primitives-utility", - "frame-benchmarking", - "frame-executive", + "emulated-integration-tests-common", "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "frame-try-runtime", - "hex-literal", - "log", - "pallet-asset-conversion", - "pallet-asset-conversion-tx-payment", - "pallet-assets", - "pallet-aura", - "pallet-authorship", - "pallet-balances", - "pallet-collator-selection", - "pallet-multisig", - "pallet-nft-fractionalization", - "pallet-nfts", - "pallet-nfts-runtime-api", - "pallet-proxy", - "pallet-session", - "pallet-state-trie-migration", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-uniques", - "pallet-utility", - "pallet-xcm", - "pallet-xcm-benchmarks", - "parachain-info", - "parachains-common", - "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-runtime-common", - "primitive-types", - "scale-info", - "smallvec", - "sp-api", - "sp-block-builder", - "sp-consensus-aura", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime", - "sp-session", - "sp-std", - "sp-storage", - "sp-transaction-pool", - "sp-version", - "sp-weights", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", - "substrate-wasm-builder", -] - -[[package]] -name = "asset-hub-polkadot-runtime" -version = "0.9.420" -dependencies = [ - "asset-test-utils", - "assets-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-asset-tx-payment", - "pallet-assets", - "pallet-aura", - "pallet-authorship", - "pallet-balances", - "pallet-collator-selection", - "pallet-multisig", - "pallet-nfts", - "pallet-nfts-runtime-api", - "pallet-proxy", - "pallet-session", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-uniques", - "pallet-utility", - "pallet-xcm", - "pallet-xcm-benchmarks", - "parachain-info", "parachains-common", - "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-runtime-common", - "scale-info", - "smallvec", - "sp-api", - "sp-block-builder", - "sp-consensus-aura", + "rococo-emulated-chain", "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime", - "sp-session", - "sp-std", - "sp-storage", - "sp-transaction-pool", - "sp-version", - "sp-weights", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", - "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] @@ -799,41 +831,41 @@ dependencies = [ "assert_matches", "asset-hub-rococo-runtime", "asset-test-utils", + "cumulus-pallet-parachain-system", + "emulated-integration-tests-common", "frame-support", - "frame-system", - "integration-tests-common", "pallet-asset-conversion", "pallet-assets", "pallet-balances", + "pallet-message-queue", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-runtime-parachains", + "penpal-runtime", "rococo-runtime", + "rococo-system-emulated-network", "sp-runtime", "staging-xcm", "staging-xcm-executor", - "xcm-emulator", + "testnet-parachains-constants", ] [[package]] name = "asset-hub-rococo-runtime" -version = "0.9.420" +version = "0.11.0" dependencies = [ "asset-test-utils", "assets-common", "bp-asset-hub-rococo", - "bp-asset-hub-wococo", + "bp-asset-hub-westend", "bp-bridge-hub-rococo", - "bp-bridge-hub-wococo", + "bp-bridge-hub-westend", "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-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -852,6 +884,7 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", @@ -867,33 +900,48 @@ dependencies = [ "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub-router", - "parachain-info", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "primitive-types", "rococo-runtime-constants", "scale-info", - "smallvec", + "snowbridge-router-primitives", "sp-api", "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-genesis-builder", "sp-inherents", "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", "sp-weights", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", +] + +[[package]] +name = "asset-hub-westend-emulated-chain" +version = "0.0.0" +dependencies = [ + "asset-hub-westend-runtime", + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "sp-core", + "testnet-parachains-constants", + "westend-emulated-chain", ] [[package]] @@ -903,44 +951,44 @@ dependencies = [ "assert_matches", "asset-hub-westend-runtime", "asset-test-utils", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "emulated-integration-tests-common", "frame-support", - "frame-system", - "integration-tests-common", "pallet-asset-conversion", - "pallet-asset-rate", "pallet-assets", "pallet-balances", + "pallet-message-queue", "pallet-treasury", "pallet-xcm", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", + "penpal-runtime", "polkadot-runtime-common", - "polkadot-runtime-parachains", "sp-runtime", "staging-xcm", - "staging-xcm-builder", "staging-xcm-executor", + "testnet-parachains-constants", "westend-runtime", - "westend-runtime-constants", - "xcm-emulator", + "westend-system-emulated-network", ] [[package]] name = "asset-hub-westend-runtime" -version = "0.9.420" +version = "0.15.0" dependencies = [ "asset-test-utils", "assets-common", + "bp-asset-hub-rococo", + "bp-asset-hub-westend", + "bp-bridge-hub-rococo", + "bp-bridge-hub-westend", "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-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -959,6 +1007,7 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-message-queue", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", @@ -972,47 +1021,42 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", - "parachain-info", + "pallet-xcm-bridge-hub-router", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "primitive-types", "scale-info", - "smallvec", "sp-api", "sp-block-builder", "sp-consensus-aura", "sp-core", "sp-genesis-builder", "sp-inherents", - "sp-io", "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", "westend-runtime-constants", ] [[package]] name = "asset-test-utils" -version = "1.0.0" +version = "7.0.0" dependencies = [ - "assets-common", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", - "cumulus-test-relay-sproof-builder", "frame-support", "frame-system", "hex-literal", @@ -1020,18 +1064,16 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", "pallet-xcm-bridge-hub-router", - "parachain-info", "parachains-common", "parachains-runtimes-test-utils", "parity-scale-codec", - "polkadot-parachain-primitives", - "sp-consensus-aura", - "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -1040,21 +1082,20 @@ dependencies = [ [[package]] name = "assets-common" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", "frame-support", "impl-trait-for-tuples", "log", "pallet-asset-conversion", - "pallet-asset-tx-payment", "pallet-xcm", "parachains-common", "parity-scale-codec", "scale-info", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -1068,7 +1109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -1078,7 +1119,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ - "async-lock", + "async-lock 2.8.0", "async-task", "concurrent-queue", "fastrand 1.9.0", @@ -1092,7 +1133,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "futures-lite", @@ -1104,7 +1145,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", @@ -1124,7 +1165,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy", + "pin-project-lite 0.2.12", ] [[package]] @@ -1146,11 +1198,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "cfg-if", - "event-listener", + "event-listener 2.5.3", "futures-lite", "rustix 0.37.23", "signal-hook", @@ -1176,7 +1228,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -1187,13 +1239,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.38", + "syn 2.0.53", ] [[package]] @@ -1232,6 +1284,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" @@ -1249,28 +1313,30 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.32.0", + "object 0.32.2", "rustc-demangle", ] [[package]] name = "bandersnatch_vrfs" -version = "0.0.1" -source = "git+https://github.com/w3f/ring-vrf?rev=4b09416#4b09416fd23383ec436ddac127d58c7b7cd392c6" +version = "0.0.4" +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", + "merlin", "rand_chacha 0.3.1", "rand_core 0.6.4", "ring 0.1.0", "sha2 0.10.7", + "sp-ark-bls12-381", + "sp-ark-ed-on-bls12-381-bandersnatch", "zeroize", ] @@ -1280,12 +1346,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" @@ -1312,9 +1372,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" dependencies = [ "serde", ] @@ -1330,7 +1390,7 @@ dependencies = [ [[package]] name = "binary-merkle-tree" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "array-bytes 6.1.0", "env_logger 0.9.3", @@ -1367,7 +1427,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -1376,15 +1436,48 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.11.0", + "serde", + "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-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin_hashes" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1405,6 +1498,7 @@ checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", + "serde", "tap", "wyz", ] @@ -1464,28 +1558,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec 0.7.4", "cc", "cfg-if", "constant_time_eq 0.3.0", - "digest 0.10.7", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", ] [[package]] @@ -1506,31 +1587,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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -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" @@ -1538,7 +1594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "async-task", "atomic-waker", "fastrand 1.9.0", @@ -1548,13 +1604,14 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.8" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" +checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" dependencies = [ "log", "parity-scale-codec", "scale-info", + "schemars", "serde", ] @@ -1568,8 +1625,8 @@ dependencies = [ ] [[package]] -name = "bp-asset-hub-kusama" -version = "0.1.0" +name = "bp-asset-hub-rococo" +version = "0.4.0" dependencies = [ "bp-xcm-bridge-hub-router", "frame-support", @@ -1578,29 +1635,8 @@ dependencies = [ ] [[package]] -name = "bp-asset-hub-polkadot" -version = "0.1.0" -dependencies = [ - "bp-xcm-bridge-hub-router", - "frame-support", - "parity-scale-codec", - "scale-info", - "sp-runtime", -] - -[[package]] -name = "bp-asset-hub-rococo" -version = "0.1.0" -dependencies = [ - "bp-xcm-bridge-hub-router", - "frame-support", - "parity-scale-codec", - "scale-info", -] - -[[package]] -name = "bp-asset-hub-wococo" -version = "0.1.0" +name = "bp-asset-hub-westend" +version = "0.3.0" dependencies = [ "bp-xcm-bridge-hub-router", "frame-support", @@ -1610,7 +1646,7 @@ dependencies = [ [[package]] name = "bp-bridge-hub-cumulus" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-messages", "bp-polkadot-core", @@ -1619,12 +1655,12 @@ dependencies = [ "frame-system", "polkadot-primitives", "sp-api", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-bridge-hub-kusama" -version = "0.1.0" +version = "0.6.0" dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", @@ -1632,12 +1668,12 @@ dependencies = [ "frame-support", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-bridge-hub-polkadot" -version = "0.1.0" +version = "0.6.0" dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", @@ -1645,12 +1681,12 @@ dependencies = [ "frame-support", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-bridge-hub-rococo" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", @@ -1658,12 +1694,12 @@ dependencies = [ "frame-support", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] -name = "bp-bridge-hub-wococo" -version = "0.1.0" +name = "bp-bridge-hub-westend" +version = "0.3.0" dependencies = [ "bp-bridge-hub-cumulus", "bp-messages", @@ -1671,12 +1707,12 @@ dependencies = [ "frame-support", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-header-chain" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-runtime", "bp-test-utils", @@ -1690,24 +1726,24 @@ dependencies = [ "sp-consensus-grandpa", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-kusama" -version = "0.1.0" +version = "0.5.0" dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", "frame-support", "sp-api", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-messages" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-runtime", @@ -1718,12 +1754,12 @@ dependencies = [ "scale-info", "serde", "sp-core", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-parachains" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-polkadot-core", @@ -1734,24 +1770,24 @@ dependencies = [ "scale-info", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-polkadot" -version = "0.1.0" +version = "0.5.0" dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", "frame-support", "sp-api", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-polkadot-bulletin" -version = "0.1.0" +version = "0.4.0" dependencies = [ "bp-header-chain", "bp-messages", @@ -1763,12 +1799,12 @@ dependencies = [ "scale-info", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-polkadot-core" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-messages", "bp-runtime", @@ -1781,12 +1817,12 @@ dependencies = [ "serde", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-relayers" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-messages", "bp-runtime", @@ -1796,24 +1832,24 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-rococo" -version = "0.1.0" +version = "0.6.0" dependencies = [ "bp-header-chain", "bp-polkadot-core", "bp-runtime", "frame-support", "sp-api", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "bp-runtime" -version = "0.1.0" +version = "0.7.0" dependencies = [ "frame-support", "frame-system", @@ -1829,14 +1865,14 @@ dependencies = [ "sp-io", "sp-runtime", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", "sp-trie", "trie-db", ] [[package]] name = "bp-test-utils" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-parachains", @@ -1849,26 +1885,32 @@ dependencies = [ "sp-consensus-grandpa", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-trie", ] [[package]] -name = "bp-wococo" -version = "0.1.0" +name = "bp-westend" +version = "0.3.0" dependencies = [ "bp-header-chain", "bp-polkadot-core", - "bp-rococo", "bp-runtime", "frame-support", "sp-api", - "sp-std", + "sp-std 14.0.0", +] + +[[package]] +name = "bp-xcm-bridge-hub" +version = "0.2.0" +dependencies = [ + "sp-std 14.0.0", ] [[package]] name = "bp-xcm-bridge-hub-router" -version = "0.1.0" +version = "0.6.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -1877,16 +1919,96 @@ dependencies = [ ] [[package]] -name = "bridge-hub-kusama-runtime" -version = "0.1.0" +name = "bridge-hub-common" +version = "0.0.0" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "pallet-message-queue", + "parity-scale-codec", + "scale-info", + "snowbridge-core", + "sp-core", + "sp-runtime", + "sp-std 14.0.0", + "staging-xcm", +] + +[[package]] +name = "bridge-hub-rococo-emulated-chain" +version = "0.0.0" +dependencies = [ + "bridge-hub-common", + "bridge-hub-rococo-runtime", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "sp-core", + "testnet-parachains-constants", +] + +[[package]] +name = "bridge-hub-rococo-integration-tests" +version = "1.0.0" +dependencies = [ + "asset-hub-rococo-runtime", + "bp-messages", + "bridge-hub-rococo-runtime", + "cumulus-pallet-xcmp-queue", + "emulated-integration-tests-common", + "frame-support", + "hex-literal", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-bridge-messages", + "pallet-message-queue", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "rococo-system-emulated-network", + "rococo-westend-system-emulated-network", + "scale-info", + "snowbridge-core", + "snowbridge-pallet-inbound-queue", + "snowbridge-pallet-inbound-queue-fixtures", + "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-system", + "snowbridge-router-primitives", + "sp-core", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", + "testnet-parachains-constants", +] + +[[package]] +name = "bridge-hub-rococo-runtime" +version = "0.5.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", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -1901,7 +2023,12 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", "pallet-collator-selection", + "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", @@ -1910,15 +2037,25 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", - "parachain-info", + "pallet-xcm-bridge-hub", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", + "rococo-runtime-constants", "scale-info", "serde", - "smallvec", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-outbound-queue-runtime-api", + "snowbridge-pallet-ethereum-client", + "snowbridge-pallet-inbound-queue", + "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-system", + "snowbridge-router-primitives", + "snowbridge-runtime-common", + "snowbridge-runtime-test-common", + "snowbridge-system-runtime-api", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -1926,113 +2063,105 @@ dependencies = [ "sp-genesis-builder", "sp-inherents", "sp-io", + "sp-keyring", "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "static_assertions", "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] -name = "bridge-hub-polkadot-runtime" -version = "0.1.0" +name = "bridge-hub-test-utils" +version = "0.7.0" dependencies = [ - "bridge-hub-test-utils", - "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", + "asset-test-utils", + "bp-header-chain", + "bp-messages", + "bp-polkadot-core", + "bp-relayers", + "bp-runtime", + "bp-test-utils", + "bridge-runtime-common", "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", + "impl-trait-for-tuples", "log", - "pallet-aura", - "pallet-authorship", "pallet-balances", - "pallet-collator-selection", - "pallet-multisig", - "pallet-session", + "pallet-bridge-grandpa", + "pallet-bridge-messages", + "pallet-bridge-parachains", + "pallet-bridge-relayers", "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", - "pallet-xcm", - "pallet-xcm-benchmarks", - "parachain-info", "parachains-common", + "parachains-runtimes-test-utils", "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-io", - "sp-offchain", + "sp-keyring", "sp-runtime", - "sp-session", - "sp-std", - "sp-storage", - "sp-transaction-pool", - "sp-version", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", - "substrate-wasm-builder", ] [[package]] -name = "bridge-hub-rococo-integration-tests" +name = "bridge-hub-westend-emulated-chain" +version = "0.0.0" +dependencies = [ + "bridge-hub-common", + "bridge-hub-westend-runtime", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "sp-core", + "testnet-parachains-constants", +] + +[[package]] +name = "bridge-hub-westend-integration-tests" version = "1.0.0" dependencies = [ - "asset-test-utils", "bp-messages", - "bridge-hub-rococo-runtime", - "cumulus-pallet-dmp-queue", + "bridge-hub-westend-runtime", "cumulus-pallet-xcmp-queue", + "emulated-integration-tests-common", "frame-support", - "integration-tests-common", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", "pallet-bridge-messages", + "pallet-message-queue", "pallet-xcm", "parachains-common", - "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-runtime-parachains", + "rococo-westend-system-emulated-network", + "sp-runtime", "staging-xcm", "staging-xcm-executor", - "xcm-emulator", ] [[package]] -name = "bridge-hub-rococo-runtime" -version = "0.1.0" +name = "bridge-hub-westend-runtime" +version = "0.2.0" dependencies = [ "bp-asset-hub-rococo", - "bp-asset-hub-wococo", + "bp-asset-hub-westend", "bp-bridge-hub-rococo", - "bp-bridge-hub-wococo", + "bp-bridge-hub-westend", "bp-header-chain", "bp-messages", "bp-parachains", @@ -2040,15 +2169,16 @@ dependencies = [ "bp-relayers", "bp-rococo", "bp-runtime", - "bp-wococo", + "bp-westend", + "bridge-hub-common", "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-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2068,6 +2198,7 @@ dependencies = [ "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-collator-selection", + "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-timestamp", @@ -2076,16 +2207,13 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", - "parachain-info", + "pallet-xcm-bridge-hub", "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", @@ -2097,67 +2225,23 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "static_assertions", "substrate-wasm-builder", -] - -[[package]] -name = "bridge-hub-test-utils" -version = "0.1.0" -dependencies = [ - "asset-test-utils", - "bp-bridge-hub-rococo", - "bp-bridge-hub-wococo", - "bp-header-chain", - "bp-messages", - "bp-parachains", - "bp-polkadot-core", - "bp-relayers", - "bp-runtime", - "bp-test-utils", - "bridge-runtime-common", - "cumulus-pallet-dmp-queue", - "cumulus-pallet-parachain-system", - "cumulus-pallet-xcmp-queue", - "frame-benchmarking", - "frame-executive", - "frame-support", - "frame-system", - "log", - "pallet-balances", - "pallet-bridge-grandpa", - "pallet-bridge-messages", - "pallet-bridge-parachains", - "pallet-bridge-relayers", - "pallet-collator-selection", - "pallet-session", - "pallet-utility", - "pallet-xcm", - "pallet-xcm-benchmarks", - "parachain-info", - "parachains-common", - "parachains-runtimes-test-utils", - "parity-scale-codec", - "sp-core", - "sp-io", - "sp-keyring", - "sp-runtime", - "sp-tracing", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", + "testnet-parachains-constants", + "westend-runtime-constants", ] [[package]] name = "bridge-runtime-common" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-messages", @@ -2166,6 +2250,7 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", + "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "frame-support", "frame-system", @@ -2184,7 +2269,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-trie", "staging-xcm", "staging-xcm-builder", @@ -2331,17 +2416,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" @@ -2418,25 +2492,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "chain-spec-builder" -version = "2.0.0" -dependencies = [ - "ansi_term", - "clap 4.4.6", - "node-cli", - "rand 0.8.5", - "sc-chain-spec", - "sc-keystore", - "sp-core", - "sp-keystore", -] - [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2481,7 +2541,7 @@ checksum = "b9b68e3193982cd54187d71afdb2a271ad4cf8af157858e9cb911b91321de143" dependencies = [ "core2", "multibase", - "multihash", + "multihash 0.17.0", "serde", "unsigned-varint", ] @@ -2546,31 +2606,41 @@ dependencies = [ "clap_lex 0.2.4", "indexmap 1.9.3", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", "textwrap", ] [[package]] name = "clap" -version = "4.4.6" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", - "clap_derive 4.4.2", + "clap_derive 4.5.3", +] + +[[package]] +name = "clap-num" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" +dependencies = [ + "num-traits", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", - "clap_lex 0.5.1", - "strsim", + "clap_lex 0.7.0", + "strsim 0.11.0", + "terminal_size", ] [[package]] @@ -2579,7 +2649,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", ] [[package]] @@ -2588,7 +2658,7 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -2597,14 +2667,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -2618,9 +2688,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "coarsetime" @@ -2645,15 +2715,28 @@ dependencies = [ ] [[package]] -name = "collectives-polkadot-runtime" -version = "1.0.0" +name = "collectives-westend-emulated-chain" +version = "0.0.0" +dependencies = [ + "collectives-westend-runtime", + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "sp-core", + "testnet-parachains-constants", +] + +[[package]] +name = "collectives-westend-runtime" +version = "3.0.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-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2666,6 +2749,7 @@ dependencies = [ "hex-literal", "log", "pallet-alliance", + "pallet-asset-rate", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2673,6 +2757,7 @@ dependencies = [ "pallet-collective", "pallet-collective-content", "pallet-core-fellowship", + "pallet-message-queue", "pallet-multisig", "pallet-preimage", "pallet-proxy", @@ -2684,16 +2769,14 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", "pallet-utility", "pallet-xcm", - "parachain-info", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", - "smallvec", "sp-api", "sp-arithmetic", "sp-block-builder", @@ -2705,14 +2788,17 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", + "westend-runtime-constants", ] [[package]] @@ -2755,29 +2841,40 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "comfy-table" -version = "7.0.1" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" dependencies = [ - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", "unicode-width", ] [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#edd1e90b847e560bf60fc2e8712235ccfa11a9a9" +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", - "merlin 3.0.0", + "getrandom_or_panic", + "merlin", "rand_chacha 0.3.1", ] @@ -2798,15 +2895,38 @@ dependencies = [ [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.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]] @@ -2863,14 +2983,14 @@ checksum = "f272d0c4cf831b4fa80ee529c7707f76585986e910e1fbce1d7921970bc1a241" [[package]] name = "contracts-rococo-runtime" -version = "0.2.0" +version = "0.8.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-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2887,8 +3007,8 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-contracts", - "pallet-contracts-primitives", "pallet-insecure-randomness-collective-flip", + "pallet-message-queue", "pallet-multisig", "pallet-session", "pallet-sudo", @@ -2897,15 +3017,12 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", "pallet-xcm", - "parachain-info", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "rococo-runtime-constants", "scale-info", - "smallvec", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -2915,14 +3032,16 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] @@ -2956,6 +3075,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-aura", + "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-parachain-primitives", + "polkadot-runtime-common", + "rococo-runtime-constants", + "scale-info", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", + "testnet-parachains-constants", +] + +[[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-aura", + "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-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "scale-info", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", + "testnet-parachains-constants", + "westend-runtime-constants", +] + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -3017,7 +3263,7 @@ dependencies = [ "gimli 0.27.3", "hashbrown 0.13.2", "log", - "regalloc2", + "regalloc2 0.6.1", "smallvec", "target-lexicon", ] @@ -3091,21 +3337,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" @@ -3152,7 +3383,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.6", + "clap 4.5.3", "criterion-plot", "futures", "is-terminal", @@ -3240,18 +3471,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" @@ -3260,7 +3479,7 @@ checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -3292,24 +3511,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ "cipher 0.3.0", ] @@ -3325,14 +3534,15 @@ dependencies = [ [[package]] name = "cumulus-client-cli" -version = "0.1.0" +version = "0.7.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "parity-scale-codec", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-service", + "sp-blockchain", "sp-core", "sp-runtime", "url", @@ -3340,7 +3550,7 @@ dependencies = [ [[package]] name = "cumulus-client-collator" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-client-consensus-common", @@ -3364,21 +3574,21 @@ dependencies = [ "sp-maybe-compressed-blob", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "tracing", ] [[package]] name = "cumulus-client-consensus-aura" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "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", @@ -3411,7 +3621,7 @@ dependencies = [ [[package]] name = "cumulus-client-consensus-common" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-client-pov-recovery", @@ -3435,7 +3645,7 @@ dependencies = [ "sp-core", "sp-runtime", "sp-timestamp", - "sp-tracing", + "sp-tracing 16.0.0", "sp-trie", "substrate-prometheus-endpoint", "tracing", @@ -3443,7 +3653,7 @@ dependencies = [ [[package]] name = "cumulus-client-consensus-proposer" -version = "0.1.0" +version = "0.7.0" dependencies = [ "anyhow", "async-trait", @@ -3457,7 +3667,7 @@ dependencies = [ [[package]] name = "cumulus-client-consensus-relay-chain" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-client-consensus-common", @@ -3479,7 +3689,7 @@ dependencies = [ [[package]] name = "cumulus-client-network" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -3511,36 +3721,59 @@ dependencies = [ ] [[package]] -name = "cumulus-client-pov-recovery" +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-service", - "futures", - "futures-timer", + "cumulus-test-relay-sproof-builder", "parity-scale-codec", - "polkadot-node-primitives", - "polkadot-node-subsystem", - "polkadot-overseer", - "polkadot-primitives", - "portpicker", - "rand 0.8.5", - "sc-cli", "sc-client-api", - "sc-consensus", - "sp-consensus", - "sp-maybe-compressed-blob", + "scale-info", + "sp-api", + "sp-crypto-hashing", + "sp-inherents", "sp-runtime", - "substrate-test-utils", + "sp-state-machine", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-trie", + "tracing", +] + +[[package]] +name = "cumulus-client-pov-recovery" +version = "0.7.0" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", + "cumulus-test-service", + "futures", + "futures-timer", + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "portpicker", + "rand", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sp-consensus", + "sp-maybe-compressed-blob", + "sp-runtime", + "substrate-test-utils", "tokio", "tracing", ] [[package]] name = "cumulus-client-service" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-client-cli", "cumulus-client-collator", @@ -3548,6 +3781,7 @@ dependencies = [ "cumulus-client-network", "cumulus-client-pov-recovery", "cumulus-primitives-core", + "cumulus-primitives-proof-size-hostfunction", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -3574,7 +3808,7 @@ dependencies = [ [[package]] name = "cumulus-pallet-aura-ext" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system", "frame-support", @@ -3586,14 +3820,15 @@ dependencies = [ "sp-application-crypto", "sp-consensus-aura", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "cumulus-pallet-dmp-queue" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", + "frame-benchmarking", "frame-support", "frame-system", "log", @@ -3602,62 +3837,70 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-version", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", ] [[package]] name = "cumulus-pallet-parachain-system" -version = "0.1.0" +version = "0.7.0" dependencies = [ "assert_matches", "bytes", "cumulus-pallet-parachain-system-proc-macro", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", + "cumulus-primitives-proof-size-hostfunction", "cumulus-test-client", "cumulus-test-relay-sproof-builder", "environmental", + "frame-benchmarking", "frame-support", "frame-system", + "futures", "hex-literal", "impl-trait-for-tuples", "lazy_static", "log", + "pallet-message-queue", "parity-scale-codec", "polkadot-parachain-primitives", + "polkadot-runtime-common", "polkadot-runtime-parachains", + "rand", "sc-client-api", "scale-info", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-inherents", "sp-io", "sp-keyring", "sp-runtime", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "sp-trie", "sp-version", "staging-xcm", "trie-db", + "trie-standardmap", ] [[package]] name = "cumulus-pallet-parachain-system-proc-macro" -version = "0.1.0" +version = "0.6.0" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "cumulus-pallet-session-benchmarking" -version = "3.0.0" +version = "9.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -3665,12 +3908,12 @@ dependencies = [ "pallet-session", "parity-scale-codec", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "cumulus-pallet-solo-to-para" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system", "frame-support", @@ -3680,12 +3923,12 @@ dependencies = [ "polkadot-primitives", "scale-info", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "cumulus-pallet-xcm" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -3694,14 +3937,15 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", ] [[package]] name = "cumulus-pallet-xcmp-queue" -version = "0.1.0" +version = "0.7.0" dependencies = [ + "bounded-collections", "bp-xcm-bridge-hub-router", "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -3710,15 +3954,15 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-message-queue", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", - "rand_chacha 0.3.1", "scale-info", "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -3726,7 +3970,7 @@ dependencies = [ [[package]] name = "cumulus-ping" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-pallet-xcm", "cumulus-primitives-core", @@ -3735,13 +3979,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", ] [[package]] name = "cumulus-primitives-aura" -version = "0.1.0" +version = "0.7.0" dependencies = [ "parity-scale-codec", "polkadot-core-primitives", @@ -3749,12 +3993,12 @@ dependencies = [ "sp-api", "sp-consensus-aura", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "cumulus-primitives-core" -version = "0.1.0" +version = "0.7.0" dependencies = [ "parity-scale-codec", "polkadot-core-primitives", @@ -3763,59 +4007,84 @@ dependencies = [ "scale-info", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-trie", "staging-xcm", ] [[package]] name = "cumulus-primitives-parachain-inherent" -version = "0.1.0" +version = "0.7.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", - "sp-storage", + "sp-std 14.0.0", + "sp-trie", +] + +[[package]] +name = "cumulus-primitives-proof-size-hostfunction" +version = "0.2.0" +dependencies = [ + "sp-core", + "sp-externalities 0.25.0", + "sp-io", + "sp-runtime-interface 24.0.0", + "sp-state-machine", + "sp-trie", +] + +[[package]] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-core", + "cumulus-primitives-proof-size-hostfunction", + "cumulus-test-runtime", + "docify 0.2.7", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", "sp-trie", - "tracing", ] [[package]] name = "cumulus-primitives-timestamp" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", "futures", "parity-scale-codec", "sp-inherents", - "sp-std", + "sp-std 14.0.0", "sp-timestamp", ] [[package]] name = "cumulus-primitives-utility" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", "frame-support", "log", - "pallet-xcm-benchmarks", + "pallet-asset-conversion", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -3823,7 +4092,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-inprocess-interface" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -3851,7 +4120,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-interface" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -3868,7 +4137,7 @@ dependencies = [ [[package]] name = "cumulus-relay-chain-minimal-node" -version = "0.1.0" +version = "0.7.0" dependencies = [ "array-bytes 6.1.0", "async-trait", @@ -3876,33 +4145,40 @@ dependencies = [ "cumulus-relay-chain-interface", "cumulus-relay-chain-rpc-interface", "futures", + "parking_lot 0.12.1", "polkadot-availability-recovery", "polkadot-collator-protocol", "polkadot-core-primitives", "polkadot-network-bridge", "polkadot-node-collation-generation", + "polkadot-node-core-chain-api", + "polkadot-node-core-prospective-parachains", "polkadot-node-core-runtime-api", "polkadot-node-network-protocol", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-primitives", + "polkadot-service", "sc-authority-discovery", + "sc-client-api", "sc-network", "sc-network-common", "sc-service", "sc-tracing", "sc-utils", "sp-api", + "sp-blockchain", "sp-consensus", "sp-consensus-babe", "sp-runtime", "substrate-prometheus-endpoint", + "tokio", "tracing", ] [[package]] name = "cumulus-relay-chain-rpc-interface" -version = "0.1.0" +version = "0.7.0" dependencies = [ "async-trait", "cumulus-primitives-core", @@ -3914,7 +4190,7 @@ dependencies = [ "parity-scale-codec", "pin-project", "polkadot-overseer", - "rand 0.8.5", + "rand", "sc-client-api", "sc-rpc-api", "sc-service", @@ -3929,7 +4205,8 @@ dependencies = [ "sp-core", "sp-runtime", "sp-state-machine", - "sp-storage", + "sp-storage 19.0.0", + "sp-version", "thiserror", "tokio", "tokio-util", @@ -3943,6 +4220,8 @@ version = "0.1.0" dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", + "cumulus-primitives-proof-size-hostfunction", + "cumulus-primitives-storage-weight-reclaim", "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", "cumulus-test-service", @@ -3970,14 +4249,14 @@ dependencies = [ [[package]] name = "cumulus-test-relay-sproof-builder" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-primitives-core", "parity-scale-codec", "polkadot-primitives", "sp-runtime", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", "sp-trie", ] @@ -3987,12 +4266,14 @@ version = "0.1.0" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "frame-executive", "frame-support", "frame-system", "frame-system-rpc-runtime-api", "pallet-balances", "pallet-glutton", + "pallet-message-queue", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", @@ -4001,12 +4282,13 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-core", + "sp-genesis-builder", "sp-inherents", "sp-io", "sp-offchain", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -4017,16 +4299,17 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.4.6", + "clap 4.5.3", "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-primitives-storage-weight-reclaim", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -4049,7 +4332,7 @@ dependencies = [ "polkadot-service", "polkadot-test-service", "portpicker", - "rand 0.8.5", + "rand", "rococo-parachain-runtime", "sc-basic-authorship", "sc-block-builder", @@ -4067,6 +4350,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde", + "serde_json", "sp-api", "sp-arithmetic", "sp-authority-discovery", @@ -4079,7 +4363,7 @@ dependencies = [ "sp-runtime", "sp-state-machine", "sp-timestamp", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-test-client", "substrate-test-utils", "tempfile", @@ -4088,19 +4372,6 @@ dependencies = [ "url", ] -[[package]] -name = "curve25519-dalek" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" -dependencies = [ - "byteorder", - "digest 0.8.1", - "rand_core 0.5.1", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -4110,15 +4381,15 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -4127,7 +4398,7 @@ dependencies = [ "fiat-crypto", "platforms", "rustc_version 0.4.0", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4139,7 +4410,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -4179,7 +4450,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -4196,42 +4467,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", -] - -[[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.53", ] [[package]] @@ -4241,7 +4477,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", @@ -4282,17 +4518,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" @@ -4303,27 +4528,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", @@ -4359,37 +4570,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" @@ -4442,7 +4622,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -4495,7 +4675,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -4507,17 +4687,16 @@ checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=4b09416#4b09416fd23383ec436ddac127d58c7b7cd392c6" +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", - "rand_core 0.6.4", "zeroize", ] @@ -4538,18 +4717,44 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docify" -version = "0.2.4" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1b04e6ef3d21119d3eb7b032bca17f99fe041e9c072f30f32cc0e1a2b1f3c4" +dependencies = [ + "docify_macros 0.1.16", +] + +[[package]] +name = "docify" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee528c501ddd15d5181997e9518e59024844eac44fd1e40cb20ddb2a8562fa" +checksum = "7cc4fd38aaa9fb98ac70794c82a00360d1e165a87fbf96a8a91f9dfc602aaee2" dependencies = [ - "docify_macros", + "docify_macros 0.2.7", ] [[package]] name = "docify_macros" -version = "0.2.4" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca01728ab2679c464242eca99f94e2ce0514b52ac9ad950e2ed03fca991231c" +checksum = "8b5610df7f2acf89a1bb5d1a66ae56b1c7fcdcfe3948856fb3ace3f644d70eb7" +dependencies = [ + "common-path", + "derive-syn-parse", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "syn 2.0.53", + "termcolor", + "walkdir", +] + +[[package]] +name = "docify_macros" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63fa215f3a0d40fb2a221b3aa90d8e1fbb8379785a990cb60d62ac71ebdc6460" dependencies = [ "common-path", "derive-syn-parse", @@ -4557,9 +4762,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.38", + "syn 2.0.53", "termcolor", - "toml 0.7.6", + "toml 0.8.8", "walkdir", ] @@ -4581,6 +4786,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" @@ -4604,21 +4815,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" @@ -4626,12 +4825,13 @@ 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", + "serdect", + "signature", + "spki", ] [[package]] @@ -4640,21 +4840,22 @@ 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]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.2", "ed25519", "rand_core 0.6.4", "serde", "sha2 0.10.7", + "subtle 2.5.0", "zeroize", ] @@ -4674,13 +4875,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.2", "ed25519", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "rand_core 0.6.4", "sha2 0.10.7", @@ -4695,55 +4896,66 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", + "base16ct", + "crypto-bigint", "digest 0.10.7", - "ff 0.12.1", + "ff", "generic-array 0.14.7", - "group 0.12.1", - "hkdf", - "pem-rfc7468", - "pkcs8 0.9.0", + "group", + "pkcs8", "rand_core 0.6.4", - "sec1 0.3.0", - "subtle 2.4.1", + "sec1", + "serdect", + "subtle 2.5.0", "zeroize", ] [[package]] -name = "elliptic-curve" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +name = "emulated-integration-tests-common" +version = "3.0.0" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.2", - "digest 0.10.7", - "ff 0.13.0", - "generic-array 0.14.7", - "group 0.13.0", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "sec1 0.7.3", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" + "asset-test-utils", + "bp-messages", + "bridge-runtime-common", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "frame-support", + "pallet-assets", + "pallet-balances", + "pallet-bridge-messages", + "pallet-message-queue", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "paste", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime-parachains", + "sc-consensus-grandpa", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-core", + "sp-runtime", + "staging-xcm", + "xcm-emulator", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", @@ -4755,7 +4967,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -4778,7 +4990,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -4789,7 +5001,17 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", ] [[package]] @@ -4807,9 +5029,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -4832,9 +5054,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.30" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "837c0466252947ada828b975e12daf82e18bb5444e4df87be6038d4469e2a3d2" +checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" dependencies = [ "serde", ] @@ -4870,6 +5092,16 @@ dependencies = [ "libc", ] +[[package]] +name = "ethabi-decode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d398648d65820a727d6a81e58b962f874473396a047e4c30bafe3240953417" +dependencies = [ + "ethereum-types", + "tiny-keccak", +] + [[package]] name = "ethbloom" version = "0.13.0" @@ -4878,8 +5110,10 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", + "scale-info", "tiny-keccak", ] @@ -4891,9 +5125,11 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "primitive-types", + "scale-info", "uint", ] @@ -4903,6 +5139,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite 0.2.12", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite 0.2.12", +] + [[package]] name = "exit-future" version = "0.2.0" @@ -4934,7 +5191,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -4948,16 +5205,16 @@ dependencies = [ ] [[package]] -name = "fake-simd" -version = "0.1.2" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" @@ -4974,6 +5231,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" @@ -4992,7 +5260,7 @@ checksum = "f5aa1e3ae159e592ad222dc90c5acbad632b527779ba88486abe92782ab268bd" dependencies = [ "expander 0.0.4", "indexmap 1.9.3", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5001,11 +5269,12 @@ dependencies = [ [[package]] name = "fdlimit" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b" +checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" dependencies = [ "libc", + "thiserror", ] [[package]] @@ -5024,16 +5293,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" @@ -5041,27 +5300,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "fflonk" version = "0.1.0" -source = "git+https://github.com/w3f/fflonk#26a5045b24e169cffc1f9328ca83d71061145c40" +source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", - "merlin 3.0.0", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "merlin", ] [[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" @@ -5069,7 +5328,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger 0.10.0", + "env_logger 0.10.1", "log", ] @@ -5098,7 +5357,7 @@ dependencies = [ "num-traits", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "scale-info", ] @@ -5121,7 +5380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand", "rustc-hex", "static_assertions", ] @@ -5160,7 +5419,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" -version = "3.0.0" +version = "12.0.0" dependencies = [ "parity-scale-codec", ] @@ -5192,15 +5451,36 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame" -version = "0.1.0" +version = "0.0.1-dev" dependencies = [ + "docify 0.2.7", + "frame-executive", "frame-support", "frame-system", + "frame-system-rpc-runtime-api", + "log", + "pallet-examples", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-arithmetic", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-transaction-pool", + "sp-version", ] [[package]] name = "frame-benchmarking" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "array-bytes 6.1.0", "frame-support", @@ -5219,20 +5499,20 @@ dependencies = [ "sp-io", "sp-keystore", "sp-runtime", - "sp-runtime-interface", - "sp-std", - "sp-storage", + "sp-runtime-interface 24.0.0", + "sp-std 14.0.0", + "sp-storage 19.0.0", "static_assertions", ] [[package]] name = "frame-benchmarking-cli" -version = "4.0.0-dev" +version = "32.0.0" dependencies = [ "Inflector", "array-bytes 6.1.0", "chrono", - "clap 4.4.6", + "clap 4.5.3", "comfy-table", "frame-benchmarking", "frame-support", @@ -5244,7 +5524,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "rand 0.8.5", + "rand", "rand_pcg", "sc-block-builder", "sc-cli", @@ -5259,22 +5539,22 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities", + "sp-externalities 0.25.0", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage", + "sp-storage 19.0.0", "sp-trie", - "sp-wasm-interface", + "sp-wasm-interface 20.0.0", "thiserror", "thousands", ] [[package]] name = "frame-benchmarking-pallet-pov" -version = "4.0.0-dev" +version = "18.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5283,54 +5563,54 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "frame-election-provider-solution-type" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "frame-election-provider-support", "frame-support", "parity-scale-codec", - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "scale-info", "sp-arithmetic", - "syn 2.0.38", + "syn 2.0.53", "trybuild", ] [[package]] name = "frame-election-provider-support" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-election-provider-solution-type", "frame-support", "frame-system", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-arithmetic", "sp-core", "sp-io", "sp-npos-elections", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", "honggfuzz", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-arithmetic", "sp-npos-elections", @@ -5339,8 +5619,9 @@ dependencies = [ [[package]] name = "frame-executive" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ + "aquamarine 0.3.3", "array-bytes 6.1.0", "frame-support", "frame-system", @@ -5354,8 +5635,8 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "sp-version", ] @@ -5373,7 +5654,7 @@ dependencies = [ [[package]] name = "frame-remote-externalities" -version = "0.10.0-dev" +version = "0.35.0" dependencies = [ "futures", "indicatif", @@ -5382,10 +5663,11 @@ dependencies = [ "parity-scale-codec", "serde", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "spinners", "substrate-rpc-client", "tokio", @@ -5394,13 +5676,13 @@ dependencies = [ [[package]] name = "frame-support" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ - "aquamarine", + "aquamarine 0.5.0", "array-bytes 6.1.0", "assert_matches", "bitflags 1.3.2", - "docify", + "docify 0.2.7", "environmental", "frame-metadata", "frame-support-procedural", @@ -5419,8 +5701,9 @@ dependencies = [ "sp-api", "sp-arithmetic", "sp-core", - "sp-core-hashing-proc-macro", - "sp-debug-derive", + "sp-crypto-hashing", + "sp-crypto-hashing-proc-macro", + "sp-debug-derive 14.0.0", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -5428,8 +5711,9 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-timestamp", + "sp-tracing 16.0.0", "sp-weights", "static_assertions", "tt-call", @@ -5437,7 +5721,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" -version = "4.0.0-dev" +version = "23.0.0" dependencies = [ "Inflector", "cfg-expr", @@ -5449,28 +5733,29 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "sp-core-hashing", - "syn 2.0.38", + "regex", + "sp-crypto-hashing", + "syn 2.0.53", ] [[package]] name = "frame-support-procedural-tools" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "frame-support-procedural-tools-derive" -version = "3.0.0" +version = "11.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -5495,7 +5780,7 @@ dependencies = [ "sp-metadata-ir", "sp-runtime", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", "sp-version", "static_assertions", "trybuild", @@ -5537,20 +5822,21 @@ dependencies = [ [[package]] name = "frame-system" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "cfg-if", "criterion 0.4.0", + "docify 0.2.7", "frame-support", "log", "parity-scale-codec", "scale-info", "serde", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-version", "sp-weights", "substrate-test-runtime-client", @@ -5558,7 +5844,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -5566,16 +5852,16 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-version", ] [[package]] name = "frame-system-rpc-runtime-api" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "parity-scale-codec", "sp-api", @@ -5583,13 +5869,13 @@ dependencies = [ [[package]] name = "frame-try-runtime" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "frame-support", "parity-scale-codec", "sp-api", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -5610,11 +5896,11 @@ dependencies = [ [[package]] name = "fs4" -version = "0.6.6" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" +checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.8", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -5632,9 +5918,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -5647,9 +5933,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -5657,15 +5943,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -5675,9 +5961,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -5696,13 +5982,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -5713,20 +5999,20 @@ checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", "rustls 0.20.8", - "webpki 0.22.0", + "webpki", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -5736,9 +6022,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -5763,7 +6049,7 @@ dependencies = [ [[package]] name = "generate-bags" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "chrono", "frame-election-provider-support", @@ -5826,6 +6112,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand", + "rand_core 0.6.4", +] + [[package]] name = "ghash" version = "0.4.4" @@ -5852,7 +6148,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", ] @@ -5862,6 +6158,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" @@ -5870,21 +6170,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "globset" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "glutton-runtime" -version = "1.0.0" +name = "glutton-westend-runtime" +version = "3.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -5901,9 +6188,9 @@ dependencies = [ "frame-try-runtime", "pallet-aura", "pallet-glutton", + "pallet-message-queue", "pallet-sudo", "pallet-timestamp", - "parachain-info", "parachains-common", "parity-scale-codec", "scale-info", @@ -5916,25 +6203,34 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] -name = "group" -version = "0.12.1" +name = "governor" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle 2.4.1", + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.1", + "quanta", + "rand", + "smallvec", ] [[package]] @@ -5943,16 +6239,16 @@ 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", + "subtle 2.5.0", ] [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -5960,7 +6256,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -5975,9 +6271,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.7" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" dependencies = [ "log", "pest", @@ -6008,7 +6304,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] @@ -6017,16 +6313,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.8", ] [[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.8", "allocator-api2", "serde", ] @@ -6037,7 +6333,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]] @@ -6046,6 +6342,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -6067,6 +6369,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + [[package]] name = "hex-literal" version = "0.4.1" @@ -6092,16 +6400,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -6130,7 +6428,7 @@ checksum = "848e9c511092e0daa0a35a63e8e6e475a3e8f870741448b9f6028d69b142f18e" dependencies = [ "arbitrary", "lazy_static", - "memmap2", + "memmap2 0.5.10", "rustc_version 0.4.0", ] @@ -6226,10 +6524,9 @@ dependencies = [ "hyper", "log", "rustls 0.21.6", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", - "webpki-roots 0.23.1", + "tokio-rustls 0.24.1", ] [[package]] @@ -6255,12 +6552,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" @@ -6398,12 +6689,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -6414,9 +6705,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", @@ -6459,78 +6750,10 @@ dependencies = [ ] [[package]] -name = "integration-tests-common" -version = "1.0.0" -dependencies = [ - "asset-hub-kusama-runtime", - "asset-hub-polkadot-runtime", - "asset-hub-rococo-runtime", - "asset-hub-westend-runtime", - "bp-messages", - "bridge-hub-kusama-runtime", - "bridge-hub-polkadot-runtime", - "bridge-hub-rococo-runtime", - "bridge-runtime-common", - "collectives-polkadot-runtime", - "cumulus-pallet-dmp-queue", - "cumulus-pallet-parachain-system", - "cumulus-pallet-xcmp-queue", - "cumulus-primitives-core", - "frame-support", - "pallet-assets", - "pallet-balances", - "pallet-bridge-messages", - "pallet-im-online", - "pallet-message-queue", - "pallet-staking", - "pallet-xcm", - "parachains-common", - "parity-scale-codec", - "paste", - "penpal-runtime", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", - "polkadot-runtime-parachains", - "polkadot-service", - "rococo-runtime", - "rococo-runtime-constants", - "sc-consensus-grandpa", - "sp-authority-discovery", - "sp-consensus-babe", - "sp-consensus-beefy", - "sp-core", - "sp-runtime", - "staging-xcm", - "westend-runtime", - "westend-runtime-constants", - "xcm-emulator", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.2", "libc", @@ -6568,7 +6791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", - "rustix 0.38.8", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -6629,22 +6852,11 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" -[[package]] -name = "json-patch" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" -dependencies = [ - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "jsonrpsee" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b" +checksum = "4a95f7cc23d5fab0cdeeaf6bad8c8f5e7a3aa7f0d211957ea78232b327ab27b0" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -6652,85 +6864,85 @@ dependencies = [ "jsonrpsee-server", "jsonrpsee-types", "jsonrpsee-ws-client", + "tokio", "tracing", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b3815d9f5d5de348e5f162b316dc9cdf4548305ebb15b4eb9328e66cf27d7a" +checksum = "6b1736cfa3845fd9f8f43751f2b8e0e83f7b6081e754502f7d63b6587692cc83" dependencies = [ "futures-util", "http", "jsonrpsee-core", - "jsonrpsee-types", "pin-project", - "rustls-native-certs", + "rustls-native-certs 0.7.0", + "rustls-pki-types", "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-util", "tracing", - "webpki-roots 0.25.2", + "url", ] [[package]] name = "jsonrpsee-core" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5dde66c53d6dcdc8caea1874a45632ec0fcf5b437789f1e45766a1512ce803" +checksum = "82030d038658974732103e623ba2e0abec03bbbe175b39c0a2fafbada60c5868" dependencies = [ "anyhow", - "arrayvec 0.7.4", - "async-lock", + "async-lock 3.3.0", "async-trait", "beef", - "futures-channel", "futures-timer", "futures-util", - "globset", "hyper", "jsonrpsee-types", "parking_lot 0.12.1", - "rand 0.8.5", + "pin-project", + "rand", "rustc-hash", "serde", "serde_json", - "soketto", "thiserror", "tokio", + "tokio-stream", "tracing", ] [[package]] name = "jsonrpsee-http-client" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5f9fabdd5d79344728521bb65e3106b49ec405a78b66fbff073b72b389fa43" +checksum = "36a06ef0de060005fddf772d54597bb6a8b0413da47dcffd304b0306147b9678" dependencies = [ "async-trait", "hyper", "hyper-rustls", "jsonrpsee-core", "jsonrpsee-types", - "rustc-hash", "serde", "serde_json", "thiserror", "tokio", + "tower", "tracing", + "url", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a" +checksum = "69fc56131589f82e57805f7338b87023db4aafef813555708b159787e34ad6bc" dependencies = [ - "heck", - "proc-macro-crate", + "heck 0.4.1", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -6738,19 +6950,21 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" +checksum = "d85be77fe5b2a94589e3164fb780017f7aff7d646b49278c0d0346af16975c8e" dependencies = [ - "futures-channel", "futures-util", "http", "hyper", "jsonrpsee-core", "jsonrpsee-types", + "pin-project", + "route-recognizer", "serde", "serde_json", "soketto", + "thiserror", "tokio", "tokio-stream", "tokio-util", @@ -6760,40 +6974,41 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245ba8e5aa633dd1c1e4fae72bce06e71f42d34c14a2767c6b4d173b57bee5e5" +checksum = "9a48fdc1202eafc51c63e00406575e59493284ace8b8b61aa16f3a6db5d64f1a" dependencies = [ "anyhow", "beef", "serde", "serde_json", "thiserror", - "tracing", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.16.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e" +checksum = "c5ce25d70a8e4d3cc574bbc3cad0137c326ad64b194793d5e7bbdd3fa4504181" dependencies = [ "http", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", + "url", ] [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", - "ecdsa 0.16.8", - "elliptic-curve 0.13.5", + "ecdsa", + "elliptic-curve", "once_cell", + "serdect", "sha2 0.10.7", ] @@ -6849,18 +7064,20 @@ dependencies = [ "pallet-babe", "pallet-bags-list", "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", "pallet-bounties", "pallet-broker", "pallet-child-bounties", "pallet-collective", "pallet-contracts", - "pallet-contracts-primitives", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", + "pallet-example-tasks", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", @@ -6871,6 +7088,7 @@ dependencies = [ "pallet-lottery", "pallet-membership", "pallet-message-queue", + "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", @@ -6883,6 +7101,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", @@ -6895,6 +7114,7 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", + "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", @@ -6916,10 +7136,12 @@ dependencies = [ "parity-scale-codec", "primitive-types", "scale-info", + "serde_json", "sp-api", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", + "sp-consensus-beefy", "sp-consensus-grandpa", "sp-core", "sp-genesis-builder", @@ -6931,8 +7153,8 @@ dependencies = [ "sp-session", "sp-staking", "sp-statement-store", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", "static_assertions", @@ -6992,15 +7214,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" @@ -7021,9 +7234,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libflate" @@ -7045,6 +7258,17 @@ dependencies = [ "rle-decode-fast", ] +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -7063,9 +7287,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", @@ -7088,7 +7312,6 @@ dependencies = [ "libp2p-swarm", "libp2p-tcp", "libp2p-wasm-ext", - "libp2p-webrtc", "libp2p-websocket", "libp2p-yamux", "multiaddr", @@ -7133,13 +7356,13 @@ dependencies = [ "libp2p-identity", "log", "multiaddr", - "multihash", + "multihash 0.17.0", "multistream-select", "once_cell", "parking_lot 0.12.1", "pin-project", "quick-protobuf", - "rand 0.8.5", + "rand", "rw-stream-sink", "smallvec", "thiserror", @@ -7193,9 +7416,9 @@ dependencies = [ "ed25519-dalek", "log", "multiaddr", - "multihash", + "multihash 0.17.0", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.7", "thiserror", "zeroize", @@ -7220,7 +7443,7 @@ dependencies = [ "libp2p-swarm", "log", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.7", "smallvec", "thiserror", @@ -7242,7 +7465,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand", "smallvec", "socket2 0.4.9", "tokio", @@ -7278,7 +7501,7 @@ dependencies = [ "log", "once_cell", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.7", "snow", "static_assertions", @@ -7300,7 +7523,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "rand 0.8.5", + "rand", "void", ] @@ -7320,7 +7543,7 @@ dependencies = [ "log", "parking_lot 0.12.1", "quinn-proto", - "rand 0.8.5", + "rand", "rustls 0.20.8", "thiserror", "tokio", @@ -7338,7 +7561,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand", "smallvec", ] @@ -7357,7 +7580,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm-derive", "log", - "rand 0.8.5", + "rand", "smallvec", "tokio", "void", @@ -7369,7 +7592,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" dependencies = [ - "heck", + "heck 0.4.1", "quote", "syn 1.0.109", ] @@ -7400,12 +7623,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", ] @@ -7423,37 +7646,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", - "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" @@ -7514,7 +7706,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.5", + "rand", "serde", "sha2 0.9.9", "typenum", @@ -7528,7 +7720,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -7607,9 +7799,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lioness" @@ -7653,9 +7845,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "serde", "value-bag", @@ -7723,6 +7915,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "macro_magic" version = "0.5.0" @@ -7732,7 +7933,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -7746,7 +7947,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -7757,7 +7958,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -7768,7 +7969,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -7792,6 +7993,15 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" version = "0.1.10" @@ -7808,20 +8018,11 @@ 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.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memfd" @@ -7842,12 +8043,12 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.6.5" +name = "memmap2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" dependencies = [ - "autocfg", + "libc", ] [[package]] @@ -7886,18 +8087,6 @@ dependencies = [ "hash-db", ] -[[package]] -name = "merlin" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "merlin" version = "3.0.0" @@ -7917,7 +8106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69672161530e8aeca1d1400fbf3f1a1747ff60ea604265a4e906c2442df20532" dependencies = [ "futures", - "rand 0.8.5", + "rand", "thrift", ] @@ -7933,6 +8122,58 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minimal-template-node" +version = "0.0.0" +dependencies = [ + "clap 4.5.3", + "frame", + "futures", + "futures-timer", + "jsonrpsee", + "minimal-template-runtime", + "sc-basic-authorship", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-manual-seal", + "sc-executor", + "sc-network", + "sc-offchain", + "sc-rpc-api", + "sc-service", + "sc-telemetry", + "sc-transaction-pool", + "sc-transaction-pool-api", + "serde_json", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-timestamp", + "substrate-build-script-utils", + "substrate-frame-rpc-system", +] + +[[package]] +name = "minimal-template-runtime" +version = "0.0.0" +dependencies = [ + "frame", + "pallet-balances", + "pallet-minimal-template", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "scale-info", + "sp-genesis-builder", + "substrate-wasm-builder", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -7944,9 +8185,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -7964,23 +8205,23 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.2", "either", "hashlink", "lioness", "log", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "rand_distr", - "subtle 2.4.1", + "subtle 2.5.0", "thiserror", "zeroize", ] [[package]] name = "mmr-gadget" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ "futures", "log", @@ -7996,16 +8237,15 @@ dependencies = [ "sp-core", "sp-mmr-primitives", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", ] [[package]] name = "mmr-rpc" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ - "anyhow", "jsonrpsee", "parity-scale-codec", "serde", @@ -8055,7 +8295,7 @@ dependencies = [ "data-encoding", "log", "multibase", - "multihash", + "multihash 0.17.0", "percent-encoding", "serde", "static_assertions", @@ -8085,19 +8325,87 @@ dependencies = [ "blake3", "core2", "digest 0.10.7", - "multihash-derive", + "multihash-derive 0.8.0", "sha2 0.10.7", "sha3", "unsigned-varint", ] +[[package]] +name = "multihash" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815" +dependencies = [ + "core2", + "digest 0.10.7", + "multihash-derive 0.8.0", + "sha2 0.10.7", + "unsigned-varint", +] + +[[package]] +name = "multihash" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +dependencies = [ + "core2", + "unsigned-varint", +] + +[[package]] +name = "multihash-codetable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d815ecb3c8238d00647f8630ede7060a642c9f704761cd6082cb4028af6935" +dependencies = [ + "blake2b_simd", + "blake2s_simd", + "blake3", + "core2", + "digest 0.10.7", + "multihash-derive 0.9.0", + "ripemd", + "serde", + "sha1", + "sha2 0.10.7", + "sha3", + "strobe-rs", +] + [[package]] name = "multihash-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "multihash-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890e72cb7396cb99ed98c1246a97b243cc16394470d94e0bc8b0c2c11d84290e" +dependencies = [ + "core2", + "multihash 0.19.1", + "multihash-derive-impl", +] + +[[package]] +name = "multihash-derive-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38685e08adb338659871ecfc6ee47ba9b22dcc8abcf6975d379cc49145c3040" +dependencies = [ + "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", "quote", @@ -8152,15 +8460,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" @@ -8168,7 +8467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ "clap 3.2.25", - "rand 0.8.5", + "rand", ] [[package]] @@ -8252,7 +8551,6 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", ] [[package]] @@ -8270,17 +8568,34 @@ dependencies = [ ] [[package]] -name = "no-std-net" -version = "0.6.0" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - -[[package]] -name = "node-bench" -version = "0.9.0-dev" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "node-bench" +version = "0.9.0-dev" dependencies = [ "array-bytes 6.1.0", - "clap 4.4.6", + "clap 4.5.3", "derive_more", "fs_extra", "futures", @@ -8293,7 +8608,7 @@ dependencies = [ "node-primitives", "node-testing", "parity-db", - "rand 0.8.5", + "rand", "sc-basic-authorship", "sc-client-api", "sc-transaction-pool", @@ -8306,156 +8621,11 @@ dependencies = [ "sp-runtime", "sp-state-machine", "sp-timestamp", - "sp-tracing", + "sp-tracing 16.0.0", "sp-trie", "tempfile", ] -[[package]] -name = "node-cli" -version = "3.0.0-dev" -dependencies = [ - "array-bytes 6.1.0", - "assert_cmd", - "clap 4.4.6", - "clap_complete", - "criterion 0.4.0", - "frame-benchmarking-cli", - "frame-system", - "frame-system-rpc-runtime-api", - "futures", - "jsonrpsee", - "kitchensink-runtime", - "log", - "nix 0.26.2", - "node-executor", - "node-inspect", - "node-primitives", - "node-rpc", - "pallet-asset-conversion-tx-payment", - "pallet-asset-tx-payment", - "pallet-assets", - "pallet-balances", - "pallet-im-online", - "pallet-timestamp", - "parity-scale-codec", - "platforms", - "rand 0.8.5", - "regex", - "sc-authority-discovery", - "sc-basic-authorship", - "sc-block-builder", - "sc-chain-spec", - "sc-cli", - "sc-client-api", - "sc-client-db", - "sc-consensus", - "sc-consensus-babe", - "sc-consensus-epochs", - "sc-consensus-grandpa", - "sc-consensus-slots", - "sc-executor", - "sc-keystore", - "sc-mixnet", - "sc-network", - "sc-network-common", - "sc-network-statement", - "sc-network-sync", - "sc-offchain", - "sc-rpc", - "sc-service", - "sc-service-test", - "sc-statement-store", - "sc-storage-monitor", - "sc-sync-state-rpc", - "sc-sysinfo", - "sc-telemetry", - "sc-transaction-pool", - "sc-transaction-pool-api", - "serde", - "serde_json", - "soketto", - "sp-api", - "sp-authority-discovery", - "sp-blockchain", - "sp-consensus", - "sp-consensus-babe", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", - "sp-io", - "sp-keyring", - "sp-keystore", - "sp-mixnet", - "sp-runtime", - "sp-statement-store", - "sp-timestamp", - "sp-tracing", - "sp-transaction-storage-proof", - "substrate-build-script-utils", - "substrate-cli-test-utils", - "substrate-frame-cli", - "substrate-rpc-client", - "tempfile", - "tokio", - "tokio-util", - "try-runtime-cli", - "wait-timeout", -] - -[[package]] -name = "node-executor" -version = "3.0.0-dev" -dependencies = [ - "criterion 0.4.0", - "frame-benchmarking", - "frame-support", - "frame-system", - "futures", - "kitchensink-runtime", - "node-primitives", - "node-testing", - "pallet-balances", - "pallet-contracts", - "pallet-glutton", - "pallet-im-online", - "pallet-root-testing", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-treasury", - "parity-scale-codec", - "sc-executor", - "scale-info", - "sp-application-crypto", - "sp-consensus-babe", - "sp-core", - "sp-externalities", - "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-state-machine", - "sp-statement-store", - "sp-tracing", - "sp-trie", - "wat", -] - -[[package]] -name = "node-inspect" -version = "0.9.0-dev" -dependencies = [ - "clap 4.4.6", - "parity-scale-codec", - "sc-cli", - "sc-client-api", - "sc-service", - "sp-blockchain", - "sp-core", - "sp-runtime", - "thiserror", -] - [[package]] name = "node-primitives" version = "2.0.0" @@ -8476,6 +8646,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", @@ -8500,104 +8672,23 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "generate-bags", "kitchensink-runtime", ] -[[package]] -name = "node-template" -version = "4.0.0-dev" -dependencies = [ - "clap 4.4.6", - "frame-benchmarking", - "frame-benchmarking-cli", - "frame-system", - "futures", - "jsonrpsee", - "node-template-runtime", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc", - "sc-basic-authorship", - "sc-cli", - "sc-client-api", - "sc-consensus", - "sc-consensus-aura", - "sc-consensus-grandpa", - "sc-executor", - "sc-network", - "sc-offchain", - "sc-rpc-api", - "sc-service", - "sc-telemetry", - "sc-transaction-pool", - "sc-transaction-pool-api", - "sp-api", - "sp-block-builder", - "sp-blockchain", - "sp-consensus-aura", - "sp-consensus-grandpa", - "sp-core", - "sp-inherents", - "sp-io", - "sp-keyring", - "sp-runtime", - "sp-timestamp", - "substrate-build-script-utils", - "substrate-frame-rpc-system", - "try-runtime-cli", -] - [[package]] name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "flate2", "fs_extra", "glob", "itertools 0.10.5", "tar", "tempfile", - "toml_edit", -] - -[[package]] -name = "node-template-runtime" -version = "4.0.0-dev" -dependencies = [ - "frame-benchmarking", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "frame-try-runtime", - "pallet-aura", - "pallet-balances", - "pallet-grandpa", - "pallet-sudo", - "pallet-template", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-block-builder", - "sp-consensus-aura", - "sp-consensus-grandpa", - "sp-core", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime", - "sp-session", - "sp-std", - "sp-storage", - "sp-transaction-pool", - "sp-version", - "substrate-wasm-builder", + "toml_edit 0.19.15", ] [[package]] @@ -8609,12 +8700,12 @@ dependencies = [ "futures", "kitchensink-runtime", "log", - "node-executor", "node-primitives", "pallet-asset-conversion", "pallet-asset-conversion-tx-payment", "pallet-asset-tx-payment", "pallet-assets", + "pallet-skip-feeless-payment", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -8627,11 +8718,13 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", + "sp-crypto-hashing", "sp-inherents", "sp-io", "sp-keyring", "sp-runtime", "sp-timestamp", + "staging-node-cli", "substrate-test-client", "tempfile", ] @@ -8658,12 +8751,28 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.4.1" @@ -8743,9 +8852,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", @@ -8781,36 +8890,27 @@ 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]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -8844,9 +8944,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchestra" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46d78e1deb2a8d54fc1f063a544130db4da31dfe4d5d3b493186424910222a76" +checksum = "2356622ffdfe72362a45a1e5e87bb113b8327e596e39b91f11f0ef4395c8da79" dependencies = [ "async-trait", "dyn-clonable", @@ -8861,18 +8961,15 @@ dependencies = [ [[package]] name = "orchestra-proc-macro" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d035b1f968d91a826f2e34a9d6d02cb2af5aa7ca39ebd27922d850ab4b2dd2c6" +checksum = "eedb646674596266dc9bb2b5c7eea7c36b32ecc7777eba0d510196972d72c4fd" dependencies = [ - "anyhow", "expander 2.0.0", - "fs-err", - "indexmap 2.0.0", + "indexmap 2.2.3", "itertools 0.11.0", - "layout-rs", "petgraph", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -8894,36 +8991,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "p256" -version = "0.11.1" +name = "overload" +version = "0.1.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", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "p384" -version = "0.11.2" +name = "owo-colors" +version = "3.5.0" 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", -] +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "pallet-alliance" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "array-bytes 6.1.0", "frame-benchmarking", @@ -8936,15 +9017,15 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", - "sp-core-hashing", + "sp-crypto-hashing", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-asset-conversion" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8959,12 +9040,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-asset-conversion-tx-payment" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-support", "frame-system", @@ -8977,13 +9058,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", ] [[package]] name = "pallet-asset-rate" -version = "4.0.0-dev" +version = "7.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8994,12 +9075,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-asset-tx-payment" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9015,13 +9096,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", ] [[package]] name = "pallet-assets" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9033,12 +9114,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-atomic-swap" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -9048,12 +9129,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-aura" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-support", "frame-system", @@ -9066,12 +9147,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-authority-discovery" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -9083,12 +9164,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-authorship" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -9098,12 +9179,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-babe" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -9126,15 +9207,15 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-bags-list" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ - "aquamarine", - "docify", + "aquamarine 0.5.0", + "docify 0.2.7", "frame-benchmarking", "frame-election-provider-support", "frame-support", @@ -9146,8 +9227,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] @@ -9157,7 +9238,7 @@ dependencies = [ "frame-election-provider-support", "honggfuzz", "pallet-bags-list", - "rand 0.8.5", + "rand", ] [[package]] @@ -9173,15 +9254,16 @@ dependencies = [ "pallet-staking", "sp-core", "sp-runtime", - "sp-std", - "sp-storage", - "sp-tracing", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-balances" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -9193,12 +9275,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-beefy" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-election-provider-support", "frame-support", @@ -9221,12 +9303,12 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-beefy-mmr" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "array-bytes 6.1.0", "binary-merkle-tree", @@ -9246,12 +9328,12 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-bounties" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9264,12 +9346,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-bridge-grandpa" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-runtime", @@ -9285,13 +9367,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-trie", ] [[package]] name = "pallet-bridge-messages" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-messages", "bp-runtime", @@ -9304,15 +9386,14 @@ dependencies = [ "pallet-balances", "parity-scale-codec", "scale-info", - "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-bridge-parachains" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-header-chain", "bp-parachains", @@ -9329,13 +9410,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-trie", ] [[package]] name = "pallet-bridge-relayers" -version = "0.1.0" +version = "0.7.0" dependencies = [ "bp-messages", "bp-relayers", @@ -9349,15 +9430,14 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-arithmetic", - "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-broker" -version = "0.1.0" +version = "0.6.0" dependencies = [ "bitvec", "frame-benchmarking", @@ -9369,12 +9449,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-child-bounties" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9388,12 +9468,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-collator-selection" -version = "3.0.0" +version = "9.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9405,20 +9485,20 @@ dependencies = [ "pallet-session", "pallet-timestamp", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-consensus-aura", "sp-core", "sp-io", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-collective" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9429,12 +9509,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-collective-content" -version = "0.1.0" +version = "0.6.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9444,12 +9524,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-contracts" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -9461,16 +9541,19 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "log", + "pallet-assets", "pallet-balances", - "pallet-contracts-primitives", + "pallet-contracts-fixtures", "pallet-contracts-proc-macro", + "pallet-contracts-uapi", "pallet-insecure-randomness-collective-flip", + "pallet-message-queue", "pallet-proxy", "pallet-timestamp", "pallet-utility", "parity-scale-codec", "pretty_assertions", - "rand 0.8.5", + "rand", "rand_pcg", "scale-info", "serde", @@ -9480,36 +9563,90 @@ dependencies = [ "sp-io", "sp-keystore", "sp-runtime", - "sp-std", - "wasm-instrument 0.4.0", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "wasm-instrument", "wasmi", "wat", ] [[package]] -name = "pallet-contracts-primitives" -version = "24.0.0" +name = "pallet-contracts-fixtures" +version = "1.0.0" dependencies = [ - "bitflags 1.3.2", + "anyhow", + "frame-system", + "parity-wasm", + "polkavm-linker", + "sp-runtime", + "tempfile", + "toml 0.8.8", + "twox-hash", +] + +[[package]] +name = "pallet-contracts-mock-network" +version = "3.0.0" +dependencies = [ + "assert_matches", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-contracts", + "pallet-contracts-fixtures", + "pallet-contracts-proc-macro", + "pallet-contracts-uapi", + "pallet-insecure-randomness-collective-flip", + "pallet-message-queue", + "pallet-proxy", + "pallet-timestamp", + "pallet-utility", + "pallet-xcm", "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime-parachains", + "pretty_assertions", "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", "sp-runtime", - "sp-std", - "sp-weights", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-simulator", ] [[package]] name = "pallet-contracts-proc-macro" -version = "4.0.0-dev" +version = "18.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", +] + +[[package]] +name = "pallet-contracts-uapi" +version = "5.0.0" +dependencies = [ + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive", + "scale-info", ] [[package]] name = "pallet-conviction-voting" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "assert_matches", "frame-benchmarking", @@ -9523,29 +9660,30 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-core-fellowship" -version = "4.0.0-dev" +version = "12.0.0" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-ranked-collective", "parity-scale-codec", "scale-info", "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-default-config-example" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-support", "frame-system", @@ -9554,12 +9692,12 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-democracy" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9574,12 +9712,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-dev-mode" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-support", "frame-system", @@ -9590,7 +9728,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -9604,6 +9742,7 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-election-provider-multi-phase", + "pallet-nomination-pools", "pallet-session", "pallet-staking", "pallet-timestamp", @@ -9615,13 +9754,13 @@ dependencies = [ "sp-npos-elections", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-election-provider-multi-phase" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -9632,21 +9771,21 @@ dependencies = [ "pallet-election-provider-support-benchmarking", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "scale-info", "sp-arithmetic", "sp-core", "sp-io", "sp-npos-elections", "sp-runtime", - "sp-std", - "sp-tracing", - "strum", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "strum 0.24.1", ] [[package]] name = "pallet-election-provider-support-benchmarking" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -9654,12 +9793,12 @@ dependencies = [ "parity-scale-codec", "sp-npos-elections", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-elections-phragmen" -version = "5.0.0-dev" +version = "29.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9673,14 +9812,14 @@ dependencies = [ "sp-npos-elections", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "substrate-test-utils", ] [[package]] name = "pallet-example-basic" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9692,7 +9831,16 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", +] + +[[package]] +name = "pallet-example-frame-crate" +version = "0.0.1" +dependencies = [ + "frame", + "parity-scale-codec", + "scale-info", ] [[package]] @@ -9709,12 +9857,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-example-offchain-worker" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -9726,12 +9874,47 @@ dependencies = [ "sp-io", "sp-keystore", "sp-runtime", - "sp-std", + "sp-std 14.0.0", +] + +[[package]] +name = "pallet-example-single-block-migrations" +version = "0.0.1" +dependencies = [ + "docify 0.2.7", + "frame-executive", + "frame-support", + "frame-system", + "frame-try-runtime", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "sp-version", ] [[package]] name = "pallet-example-split" -version = "4.0.0-dev" +version = "10.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-std 14.0.0", +] + +[[package]] +name = "pallet-example-tasks" +version = "1.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9741,7 +9924,8 @@ dependencies = [ "scale-info", "sp-core", "sp-io", - "sp-std", + "sp-runtime", + "sp-std 14.0.0", ] [[package]] @@ -9751,16 +9935,19 @@ dependencies = [ "pallet-default-config-example", "pallet-dev-mode", "pallet-example-basic", + "pallet-example-frame-crate", "pallet-example-kitchensink", "pallet-example-offchain-worker", + "pallet-example-single-block-migrations", "pallet-example-split", + "pallet-example-tasks", ] [[package]] name = "pallet-fast-unstake" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-election-provider-support", "frame-support", @@ -9776,14 +9963,14 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "substrate-test-utils", ] [[package]] name = "pallet-glutton" -version = "4.0.0-dev" +version = "14.0.0" dependencies = [ "blake2 0.10.6", "frame-benchmarking", @@ -9796,12 +9983,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-grandpa" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "finality-grandpa", "frame-benchmarking", @@ -9826,29 +10013,31 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-identity" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "enumflags2", "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-im-online" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9863,12 +10052,12 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-indices" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9880,12 +10069,12 @@ dependencies = [ "sp-io", "sp-keyring", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-insecure-randomness-collective-flip" -version = "4.0.0-dev" +version = "16.0.0" dependencies = [ "frame-support", "frame-system", @@ -9895,12 +10084,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-lottery" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9912,12 +10101,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-membership" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9928,34 +10117,69 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-message-queue" -version = "7.0.0-dev" +version = "31.0.0" dependencies = [ + "environmental", "frame-benchmarking", "frame-support", "frame-system", "log", "parity-scale-codec", - "rand 0.8.5", + "rand", "rand_distr", "scale-info", "serde", "sp-arithmetic", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "sp-weights", ] +[[package]] +name = "pallet-migrations" +version = "1.0.0" +dependencies = [ + "docify 0.1.16", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "pretty_assertions", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "sp-version", +] + +[[package]] +name = "pallet-minimal-template" +version = "0.0.0" +dependencies = [ + "frame", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "pallet-mixnet" -version = "0.1.0-dev" +version = "0.4.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9969,12 +10193,12 @@ dependencies = [ "sp-io", "sp-mixnet", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-mmr" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "array-bytes 6.1.0", "env_logger 0.9.3", @@ -9989,12 +10213,12 @@ dependencies = [ "sp-io", "sp-mmr-primitives", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-multisig" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10005,12 +10229,12 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-nft-fractionalization" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10024,12 +10248,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-nfts" -version = "4.0.0-dev" +version = "22.0.0" dependencies = [ "enumflags2", "frame-benchmarking", @@ -10043,36 +10267,22 @@ dependencies = [ "sp-io", "sp-keystore", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-nfts-runtime-api" -version = "4.0.0-dev" +version = "14.0.0" dependencies = [ "pallet-nfts", "parity-scale-codec", "sp-api", -] - -[[package]] -name = "pallet-nicks" -version = "4.0.0-dev" -dependencies = [ - "frame-support", - "frame-system", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-nis" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10084,12 +10294,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-node-authorization" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -10099,12 +10309,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-nomination-pools" -version = "1.0.0" +version = "25.0.0" dependencies = [ "frame-support", "frame-system", @@ -10116,13 +10326,13 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-nomination-pools-benchmarking" -version = "1.0.0" +version = "26.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -10139,9 +10349,9 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 24.0.0", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -10153,20 +10363,20 @@ dependencies = [ "honggfuzz", "log", "pallet-nomination-pools", - "rand 0.8.5", + "rand", "sp-io", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-nomination-pools-runtime-api" -version = "1.0.0-dev" +version = "23.0.0" dependencies = [ "pallet-nomination-pools", "parity-scale-codec", "sp-api", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -10189,13 +10399,13 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "pallet-offences" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-support", "frame-system", @@ -10208,12 +10418,12 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-offences-benchmarking" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -10235,14 +10445,14 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-paged-list" -version = "0.1.0" +version = "0.6.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10252,7 +10462,7 @@ dependencies = [ "sp-io", "sp-metadata-ir", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -10268,22 +10478,41 @@ dependencies = [ [[package]] name = "pallet-parachain-template" -version = "0.1.0" +version = "0.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + +[[package]] +name = "pallet-parameters" +version = "0.0.1" dependencies = [ + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", + "pallet-example-basic", "parity-scale-codec", + "paste", "scale-info", "serde", "sp-core", "sp-io", "sp-runtime", + "sp-std 14.0.0", ] [[package]] name = "pallet-preimage" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10295,12 +10524,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-proxy" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10312,16 +10541,17 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-ranked-collective" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", @@ -10329,12 +10559,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-recovery" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10345,12 +10575,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-referenda" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "assert_matches", "frame-benchmarking", @@ -10367,12 +10597,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-remark" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10383,12 +10613,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-root-offences" -version = "1.0.0-dev" +version = "25.0.0" dependencies = [ "frame-election-provider-support", "frame-support", @@ -10404,12 +10634,12 @@ dependencies = [ "sp-io", "sp-runtime", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-root-testing" -version = "1.0.0-dev" +version = "4.0.0" dependencies = [ "frame-support", "frame-system", @@ -10418,13 +10648,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-safe-mode" -version = "4.0.0-dev" +version = "9.0.0" dependencies = [ + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10437,31 +10668,51 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-salary" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-ranked-collective", "parity-scale-codec", "scale-info", "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", +] + +[[package]] +name = "pallet-sassafras" +version = "0.3.5-dev" +dependencies = [ + "array-bytes 6.1.0", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-consensus-sassafras", + "sp-core", + "sp-crypto-hashing", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", ] [[package]] name = "pallet-scheduler" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10472,14 +10723,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-weights", "substrate-test-utils", ] [[package]] name = "pallet-scored-pool" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -10489,12 +10740,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-session" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -10509,13 +10760,13 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", "sp-trie", ] [[package]] name = "pallet-session-benchmarking" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -10527,18 +10778,30 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sp-core", "sp-io", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", +] + +[[package]] +name = "pallet-skip-feeless-payment" +version = "3.0.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 14.0.0", ] [[package]] name = "pallet-society" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10551,14 +10814,15 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-staking" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", @@ -10581,25 +10845,25 @@ dependencies = [ "sp-npos-elections", "sp-runtime", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "substrate-test-utils", ] [[package]] name = "pallet-staking-reward-curve" -version = "4.0.0-dev" +version = "11.0.0" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "sp-runtime", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "pallet-staking-reward-fn" -version = "4.0.0-dev" +version = "19.0.0" dependencies = [ "log", "sp-arithmetic", @@ -10607,15 +10871,16 @@ dependencies = [ [[package]] name = "pallet-staking-runtime-api" -version = "4.0.0-dev" +version = "14.0.0" dependencies = [ "parity-scale-codec", "sp-api", + "sp-staking", ] [[package]] name = "pallet-state-trie-migration" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ "frame-benchmarking", "frame-remote-externalities", @@ -10630,8 +10895,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "substrate-state-trie-migration-rpc", "thousands", "tokio", @@ -10640,7 +10905,7 @@ dependencies = [ [[package]] name = "pallet-statement" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "frame-support", "frame-system", @@ -10653,14 +10918,14 @@ dependencies = [ "sp-io", "sp-runtime", "sp-statement-store", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-sudo" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10669,12 +10934,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-template" -version = "4.0.0-dev" +version = "0.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10684,14 +10949,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", ] [[package]] name = "pallet-timestamp" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10702,14 +10966,14 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-timestamp", ] [[package]] name = "pallet-tips" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10723,13 +10987,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", ] [[package]] name = "pallet-transaction-payment" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-support", "frame-system", @@ -10741,12 +11005,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-transaction-payment-rpc" -version = "4.0.0-dev" +version = "30.0.0" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -10761,7 +11025,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -10772,7 +11036,7 @@ dependencies = [ [[package]] name = "pallet-transaction-storage" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "array-bytes 6.1.0", "frame-benchmarking", @@ -10787,15 +11051,15 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-transaction-storage-proof", ] [[package]] name = "pallet-treasury" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ - "docify", + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10808,13 +11072,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-tx-pause" -version = "4.0.0-dev" +version = "9.0.0" dependencies = [ + "docify 0.2.7", "frame-benchmarking", "frame-support", "frame-system", @@ -10826,12 +11091,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-uniques" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10843,12 +11108,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-utility" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10862,12 +11127,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-vesting" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10879,12 +11144,12 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-whitelist" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10897,18 +11162,19 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "pallet-xcm" -version = "1.0.0" +version = "7.0.0" dependencies = [ "bounded-collections", "frame-benchmarking", "frame-support", "frame-system", "log", + "pallet-assets", "pallet-balances", "parity-scale-codec", "polkadot-parachain-primitives", @@ -10918,7 +11184,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -10926,7 +11192,7 @@ dependencies = [ [[package]] name = "pallet-xcm-benchmarks" -version = "1.0.0" +version = "7.0.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -10942,50 +11208,62 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", ] [[package]] -name = "pallet-xcm-bridge-hub-router" -version = "0.1.0" +name = "pallet-xcm-bridge-hub" +version = "0.2.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", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] -name = "parachain-info" -version = "0.1.0" +name = "pallet-xcm-bridge-hub-router" +version = "0.5.0" dependencies = [ - "cumulus-primitives-core", + "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", + "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-builder", ] [[package]] name = "parachain-template-node" -version = "0.1.0" +version = "0.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -11023,6 +11301,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "serde", + "serde_json", "sp-api", "sp-block-builder", "sp-blockchain", @@ -11040,15 +11319,15 @@ dependencies = [ [[package]] name = "parachain-template-runtime" -version = "0.1.0" +version = "0.0.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-storage-weight-reclaim", "cumulus-primitives-utility", "frame-benchmarking", "frame-executive", @@ -11063,6 +11342,7 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-message-queue", "pallet-parachain-template", "pallet-session", "pallet-sudo", @@ -11070,7 +11350,7 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", - "parachain-info", + "parachains-common", "parity-scale-codec", "polkadot-parachain-primitives", "polkadot-runtime-common", @@ -11085,9 +11365,10 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -11096,43 +11377,38 @@ dependencies = [ [[package]] name = "parachains-common" -version = "1.0.0" +version = "7.0.0" dependencies = [ "cumulus-primitives-core", "cumulus-primitives-utility", "frame-support", "frame-system", "log", - "num-traits", "pallet-asset-tx-payment", "pallet-assets", "pallet-authorship", "pallet-balances", "pallet-collator-selection", - "parachain-info", + "pallet-message-queue", + "pallet-xcm", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-primitives", - "rococo-runtime-constants", "scale-info", - "smallvec", "sp-consensus-aura", "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", + "staging-parachain-info", "staging-xcm", - "staging-xcm-builder", + "staging-xcm-executor", "substrate-wasm-builder", - "westend-runtime-constants", ] [[package]] name = "parachains-runtimes-test-utils" -version = "1.0.0" +version = "7.0.0" dependencies = [ - "assets-common", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", @@ -11141,31 +11417,49 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-assets", "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", - "parachain-info", - "parachains-common", "parity-scale-codec", "polkadot-parachain-primitives", "sp-consensus-aura", "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "staging-parachain-info", "staging-xcm", "staging-xcm-executor", "substrate-wasm-builder", ] +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + +[[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", @@ -11174,18 +11468,18 @@ dependencies = [ "libc", "log", "lz4", - "memmap2", + "memmap2 0.5.10", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "siphasher", "snap", ] [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" dependencies = [ "arrayvec 0.7.4", "bitvec", @@ -11198,11 +11492,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -11310,28 +11604,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" [[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "pbkdf2" -version = "0.8.0" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "crypto-mac 0.11.1", + "base64ct", + "rand_core 0.6.4", + "subtle 2.5.0", ] [[package]] -name = "pbkdf2" -version = "0.11.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.7", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -11340,6 +11627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", + "password-hash", ] [[package]] @@ -11358,20 +11646,24 @@ dependencies = [ ] [[package]] -name = "pem-rfc7468" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +name = "penpal-emulated-chain" +version = "0.0.0" dependencies = [ - "base64ct", + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "penpal-runtime", + "sp-core", + "staging-xcm", ] [[package]] name = "penpal-runtime" -version = "0.9.27" +version = "0.14.0" dependencies = [ + "assets-common", "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", @@ -11393,13 +11685,13 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-message-queue", "pallet-session", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", - "parachain-info", "parachains-common", "parity-scale-codec", "polkadot-parachain-primitives", @@ -11416,10 +11708,11 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", + "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -11427,42 +11720,240 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +name = "people-rococo-emulated-chain" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "people-rococo-runtime", + "sp-core", + "testnet-parachains-constants", +] [[package]] -name = "pest" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +name = "people-rococo-integration-tests" +version = "0.1.0" dependencies = [ - "thiserror", - "ucd-trie", + "asset-test-utils", + "emulated-integration-tests-common", + "frame-support", + "pallet-balances", + "pallet-identity", + "pallet-message-queue", + "parachains-common", + "parity-scale-codec", + "people-rococo-runtime", + "polkadot-runtime-common", + "rococo-runtime", + "rococo-runtime-constants", + "rococo-system-emulated-network", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", ] [[package]] -name = "pest_derive" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +name = "people-rococo-runtime" +version = "0.1.0" dependencies = [ - "pest", - "pest_generator", + "cumulus-pallet-aura-ext", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", + "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-parachain-primitives", + "polkadot-runtime-common", + "rococo-runtime-constants", + "scale-info", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] -name = "pest_generator" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +name = "people-westend-emulated-chain" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "people-westend-runtime", + "sp-core", + "testnet-parachains-constants", +] + +[[package]] +name = "people-westend-integration-tests" +version = "0.1.0" +dependencies = [ + "asset-test-utils", + "emulated-integration-tests-common", + "frame-support", + "pallet-balances", + "pallet-identity", + "pallet-message-queue", + "parachains-common", + "parity-scale-codec", + "people-westend-runtime", + "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-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", + "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-parachain-primitives", + "polkadot-runtime-common", + "scale-info", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", + "testnet-parachains-constants", + "westend-runtime-constants", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -11483,7 +11974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.0", + "indexmap 2.2.3", ] [[package]] @@ -11503,7 +11994,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -11524,24 +12015,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]] @@ -11586,7 +12067,7 @@ dependencies = [ [[package]] name = "polkadot" -version = "1.1.0" +version = "6.0.0" dependencies = [ "assert_cmd", "color-eyre", @@ -11607,12 +12088,14 @@ dependencies = [ [[package]] name = "polkadot-approval-distribution" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", + "bitvec", "env_logger 0.9.3", "futures", "futures-timer", + "itertools 0.10.5", "log", "polkadot-node-jaeger", "polkadot-node-metrics", @@ -11623,10 +12106,10 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", - "rand 0.8.5", + "rand", "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", @@ -11634,7 +12117,7 @@ dependencies = [ [[package]] name = "polkadot-availability-bitfield-distribution" -version = "1.0.0" +version = "7.0.0" dependencies = [ "always-assert", "assert_matches", @@ -11649,7 +12132,7 @@ dependencies = [ "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "sp-application-crypto", "sp-authority-discovery", @@ -11661,7 +12144,7 @@ dependencies = [ [[package]] name = "polkadot-availability-distribution" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "derive_more", @@ -11677,20 +12160,21 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", - "rand 0.8.5", + "polkadot-subsystem-bench", + "rand", "sc-network", "schnellru", "sp-core", "sp-keyring", "sp-keystore", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-availability-recovery" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", @@ -11708,21 +12192,24 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", - "rand 0.8.5", + "polkadot-subsystem-bench", + "rand", "sc-network", "schnellru", "sp-application-crypto", "sp-core", "sp-keyring", "thiserror", + "tokio", "tracing-gum", ] [[package]] name = "polkadot-cli" -version = "1.1.0" +version = "7.0.0" dependencies = [ - "clap 4.4.6", + "cfg-if", + "clap 4.5.3", "frame-benchmarking-cli", "futures", "log", @@ -11741,6 +12228,7 @@ dependencies = [ "sp-io", "sp-keyring", "sp-maybe-compressed-blob", + "sp-runtime", "substrate-build-script-utils", "thiserror", "try-runtime-cli", @@ -11748,7 +12236,7 @@ dependencies = [ [[package]] name = "polkadot-collator-protocol" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "bitvec", @@ -11778,18 +12266,18 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" -version = "1.0.0" +version = "7.0.0" dependencies = [ "parity-scale-codec", "scale-info", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "polkadot-dispute-distribution" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-channel", @@ -11798,7 +12286,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 1.9.3", + "indexmap 2.2.3", "lazy_static", "parity-scale-codec", "polkadot-erasure-coding", @@ -11815,14 +12303,14 @@ dependencies = [ "sp-application-crypto", "sp-keyring", "sp-keystore", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-erasure-coding" -version = "1.0.0" +version = "7.0.0" dependencies = [ "criterion 0.4.0", "parity-scale-codec", @@ -11836,19 +12324,21 @@ dependencies = [ [[package]] name = "polkadot-gossip-support" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", "futures", "futures-timer", "lazy_static", + "parking_lot 0.12.1", "polkadot-node-network-protocol", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "quickcheck", + "rand", "rand_chacha 0.3.1", "sc-network", "sc-network-common", @@ -11856,15 +12346,16 @@ dependencies = [ "sp-authority-discovery", "sp-consensus-babe", "sp-core", + "sp-crypto-hashing", "sp-keyring", "sp-keystore", - "sp-tracing", + "sp-tracing 16.0.0", "tracing-gum", ] [[package]] name = "polkadot-network-bridge" -version = "1.0.0" +version = "7.0.0" dependencies = [ "always-assert", "assert_matches", @@ -11893,7 +12384,7 @@ dependencies = [ [[package]] name = "polkadot-node-collation-generation" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "futures", @@ -11914,17 +12405,20 @@ dependencies = [ [[package]] name = "polkadot-node-core-approval-voting" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", "bitvec", "derive_more", + "env_logger 0.9.3", "futures", "futures-timer", + "itertools 0.10.5", "kvdb", "kvdb-memorydb", - "merlin 2.0.1", + "log", + "merlin", "parity-scale-codec", "parking_lot 0.12.1", "polkadot-node-jaeger", @@ -11935,10 +12429,12 @@ dependencies = [ "polkadot-overseer", "polkadot-primitives", "polkadot-primitives-test-helpers", - "rand_core 0.5.1", + "rand", + "rand_chacha 0.3.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", @@ -11953,7 +12449,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-av-store" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "bitvec", @@ -11983,7 +12479,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-backing" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "bitvec", @@ -11997,19 +12493,21 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-statement-table", + "rstest", "sc-keystore", + "schnellru", "sp-application-crypto", "sp-core", "sp-keyring", "sp-keystore", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-node-core-bitfield-signing" -version = "1.0.0" +version = "7.0.0" dependencies = [ "futures", "polkadot-node-subsystem", @@ -12025,7 +12523,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-candidate-validation" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", @@ -12050,7 +12548,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-api" -version = "1.0.0" +version = "7.0.0" dependencies = [ "futures", "maplit", @@ -12059,6 +12557,7 @@ dependencies = [ "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", + "polkadot-node-subsystem-types", "polkadot-primitives", "sc-client-api", "sc-consensus-babe", @@ -12069,7 +12568,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-chain-selection" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "futures", @@ -12090,7 +12589,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-dispute-coordinator" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "fatality", @@ -12111,14 +12610,14 @@ dependencies = [ "sp-core", "sp-keyring", "sp-keystore", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-node-core-parachains-inherent" -version = "1.0.0" +version = "7.0.0" dependencies = [ "async-trait", "futures", @@ -12134,7 +12633,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-prospective-parachains" -version = "1.0.0" +version = "6.0.0" dependencies = [ "assert_matches", "bitvec", @@ -12148,6 +12647,7 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", + "rstest", "sc-keystore", "sp-application-crypto", "sp-core", @@ -12159,7 +12659,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-provisioner" -version = "1.0.0" +version = "7.0.0" dependencies = [ "bitvec", "fatality", @@ -12171,6 +12671,8 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", + "rstest", + "schnellru", "sp-application-crypto", "sp-keystore", "thiserror", @@ -12179,14 +12681,18 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf" -version = "1.0.0" +version = "7.0.0" dependencies = [ "always-assert", + "array-bytes 6.1.0", "assert_matches", + "blake3", "cfg-if", + "criterion 0.4.0", "futures", "futures-timer", "hex-literal", + "is_executable", "libc", "parity-scale-codec", "pin-project", @@ -12197,24 +12703,29 @@ dependencies = [ "polkadot-node-core-pvf-prepare-worker", "polkadot-node-metrics", "polkadot-node-primitives", + "polkadot-node-subsystem", "polkadot-parachain-primitives", "polkadot-primitives", - "rand 0.8.5", + "procfs", + "rand", + "rococo-runtime", + "rusty-fork", + "sc-sysinfo", "slotmap", "sp-core", "sp-maybe-compressed-blob", - "sp-wasm-interface", - "substrate-build-script-utils", + "sp-wasm-interface 20.0.0", "tempfile", "test-parachain-adder", "test-parachain-halt", + "thiserror", "tokio", "tracing-gum", ] [[package]] name = "polkadot-node-core-pvf-checker" -version = "1.0.0" +version = "7.0.0" dependencies = [ "futures", "futures-timer", @@ -12237,7 +12748,7 @@ dependencies = [ [[package]] name = "polkadot-node-core-pvf-common" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "cfg-if", @@ -12245,65 +12756,65 @@ dependencies = [ "futures", "landlock", "libc", + "nix 0.27.1", "parity-scale-codec", "polkadot-parachain-primitives", "polkadot-primitives", "sc-executor", "sc-executor-common", "sc-executor-wasmtime", + "seccompiler", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-io", - "sp-tracing", + "sp-tracing 16.0.0", "tempfile", - "tokio", + "thiserror", "tracing-gum", ] [[package]] name = "polkadot-node-core-pvf-execute-worker" -version = "1.0.0" +version = "7.0.0" dependencies = [ + "cfg-if", "cpu-time", - "futures", + "libc", + "nix 0.27.1", "parity-scale-codec", "polkadot-node-core-pvf-common", "polkadot-parachain-primitives", "polkadot-primitives", - "rayon", - "sp-core", - "sp-maybe-compressed-blob", - "sp-tracing", - "tokio", "tracing-gum", ] [[package]] name = "polkadot-node-core-pvf-prepare-worker" -version = "1.0.0" +version = "7.0.0" dependencies = [ + "blake3", "cfg-if", - "futures", + "criterion 0.4.0", "libc", + "nix 0.27.1", "parity-scale-codec", "polkadot-node-core-pvf-common", - "polkadot-parachain-primitives", "polkadot-primitives", "rayon", - "sc-executor", + "rococo-runtime", "sc-executor-common", "sc-executor-wasmtime", - "sp-io", "sp-maybe-compressed-blob", - "sp-tracing", + "staging-tracking-allocator", "tikv-jemalloc-ctl", - "tokio", + "tikv-jemallocator", "tracing-gum", ] [[package]] name = "polkadot-node-core-runtime-api" -version = "1.0.0" +version = "7.0.0" dependencies = [ "async-trait", "futures", @@ -12324,7 +12835,7 @@ dependencies = [ [[package]] name = "polkadot-node-jaeger" -version = "1.0.0" +version = "7.0.0" dependencies = [ "lazy_static", "log", @@ -12341,7 +12852,7 @@ dependencies = [ [[package]] name = "polkadot-node-metrics" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_cmd", "bs58 0.5.0", @@ -12367,7 +12878,7 @@ dependencies = [ [[package]] name = "polkadot-node-network-protocol" -version = "1.0.0" +version = "7.0.0" dependencies = [ "async-channel", "async-trait", @@ -12380,26 +12891,27 @@ dependencies = [ "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-primitives", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "sc-authority-discovery", "sc-network", - "strum", + "strum 0.24.1", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-node-primitives" -version = "1.0.0" +version = "7.0.0" dependencies = [ + "bitvec", "bounded-vec", "futures", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-parachain-primitives", "polkadot-primitives", - "schnorrkel 0.9.1", + "schnorrkel 0.11.4", "serde", "sp-application-crypto", "sp-consensus-babe", @@ -12413,7 +12925,7 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem" -version = "1.0.0" +version = "7.0.0" dependencies = [ "polkadot-node-jaeger", "polkadot-node-subsystem-types", @@ -12427,6 +12939,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", @@ -12441,9 +12955,10 @@ dependencies = [ [[package]] name = "polkadot-node-subsystem-types" -version = "1.0.0" +version = "7.0.0" dependencies = [ "async-trait", + "bitvec", "derive_more", "futures", "orchestra", @@ -12458,14 +12973,16 @@ dependencies = [ "smallvec", "sp-api", "sp-authority-discovery", + "sp-blockchain", "sp-consensus-babe", + "sp-runtime", "substrate-prometheus-endpoint", "thiserror", ] [[package]] name = "polkadot-node-subsystem-util" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", @@ -12482,7 +12999,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "pin-project", "polkadot-node-jaeger", "polkadot-node-metrics", @@ -12495,7 +13012,7 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "prioritized-metered-channel", - "rand 0.8.5", + "rand", "sc-client-api", "schnellru", "sp-application-crypto", @@ -12508,7 +13025,7 @@ dependencies = [ [[package]] name = "polkadot-overseer" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", @@ -12534,44 +13051,50 @@ dependencies = [ [[package]] name = "polkadot-parachain-bin" -version = "1.1.0" +version = "4.0.0" dependencies = [ "assert_cmd", - "asset-hub-kusama-runtime", - "asset-hub-polkadot-runtime", "asset-hub-rococo-runtime", "asset-hub-westend-runtime", "async-trait", - "bridge-hub-kusama-runtime", - "bridge-hub-polkadot-runtime", "bridge-hub-rococo-runtime", - "clap 4.4.6", - "collectives-polkadot-runtime", + "bridge-hub-westend-runtime", + "clap 4.5.3", + "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", + "frame-support", + "frame-system-rpc-runtime-api", + "frame-try-runtime", "futures", - "glutton-runtime", + "glutton-westend-runtime", "hex-literal", "jsonrpsee", "log", "nix 0.26.2", + "pallet-transaction-payment", "pallet-transaction-payment-rpc", + "pallet-transaction-payment-rpc-runtime-api", "parachains-common", "parity-scale-codec", "penpal-runtime", + "people-rococo-runtime", + "people-westend-runtime", "polkadot-cli", "polkadot-primitives", "polkadot-service", @@ -12600,45 +13123,52 @@ dependencies = [ "sp-blockchain", "sp-consensus-aura", "sp-core", + "sp-genesis-builder", + "sp-inherents", "sp-io", "sp-keystore", "sp-offchain", "sp-runtime", "sp-session", + "sp-std 14.0.0", "sp-timestamp", + "sp-tracing 16.0.0", "sp-transaction-pool", + "sp-version", "staging-xcm", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-state-trie-migration-rpc", "tempfile", + "testnet-parachains-constants", "tokio", "wait-timeout", ] [[package]] name = "polkadot-parachain-primitives" -version = "1.0.0" +version = "6.0.0" dependencies = [ "bounded-collections", "derive_more", - "frame-support", "parity-scale-codec", "polkadot-core-primitives", "scale-info", "serde", "sp-core", "sp-runtime", - "sp-std", + "sp-std 14.0.0", + "sp-weights", ] [[package]] name = "polkadot-primitives" -version = "1.0.0" +version = "7.0.0" dependencies = [ "bitvec", "hex-literal", + "log", "parity-scale-codec", "polkadot-core-primitives", "polkadot-parachain-primitives", @@ -12655,7 +13185,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-staking", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -12663,7 +13193,7 @@ name = "polkadot-primitives-test-helpers" version = "1.0.0" dependencies = [ "polkadot-primitives", - "rand 0.8.5", + "rand", "sp-application-crypto", "sp-core", "sp-keyring", @@ -12672,7 +13202,7 @@ dependencies = [ [[package]] name = "polkadot-rpc" -version = "1.0.0" +version = "7.0.0" dependencies = [ "jsonrpsee", "mmr-rpc", @@ -12688,6 +13218,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", @@ -12703,7 +13234,7 @@ dependencies = [ [[package]] name = "polkadot-runtime-common" -version = "1.0.0" +version = "7.0.0" dependencies = [ "bitvec", "frame-benchmarking", @@ -12719,8 +13250,10 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-balances", + "pallet-broker", "pallet-election-provider-multi-phase", "pallet-fast-unstake", + "pallet-identity", "pallet-session", "pallet-staking", "pallet-staking-reward-fn", @@ -12728,7 +13261,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-treasury", "pallet-vesting", - "pallet-xcm-benchmarks", "parity-scale-codec", "polkadot-primitives", "polkadot-primitives-test-helpers", @@ -12749,7 +13281,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -12758,19 +13290,19 @@ dependencies = [ [[package]] name = "polkadot-runtime-metrics" -version = "1.0.0" +version = "7.0.0" dependencies = [ "bs58 0.5.0", "frame-benchmarking", "parity-scale-codec", "polkadot-primitives", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] name = "polkadot-runtime-parachains" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "bitflags 1.3.2", @@ -12788,6 +13320,7 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-balances", + "pallet-broker", "pallet-message-queue", "pallet-session", "pallet-staking", @@ -12799,8 +13332,9 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-runtime-metrics", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", + "rstest", "rustc-hex", "sc-keystore", "scale-info", @@ -12808,7 +13342,9 @@ dependencies = [ "serde_json", "sp-api", "sp-application-crypto", + "sp-arithmetic", "sp-core", + "sp-crypto-hashing", "sp-inherents", "sp-io", "sp-keyring", @@ -12816,20 +13352,78 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-executor", "static_assertions", "thousands", ] +[[package]] +name = "polkadot-sdk-docs" +version = "0.0.1" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-parachain-system", + "docify 0.2.7", + "frame", + "frame-executive", + "frame-support", + "frame-system", + "kitchensink-runtime", + "pallet-assets", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collective", + "pallet-default-config-example", + "pallet-democracy", + "pallet-example-offchain-worker", + "pallet-example-single-block-migrations", + "pallet-examples", + "pallet-multisig", + "pallet-proxy", + "pallet-scheduler", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-utility", + "parity-scale-codec", + "sc-cli", + "sc-client-db", + "sc-consensus-aura", + "sc-consensus-babe", + "sc-consensus-beefy", + "sc-consensus-grandpa", + "sc-consensus-manual-seal", + "sc-consensus-pow", + "sc-network", + "sc-rpc", + "sc-rpc-api", + "scale-info", + "simple-mermaid", + "sp-api", + "sp-core", + "sp-io", + "sp-keyring", + "sp-offchain", + "sp-runtime", + "sp-version", + "staging-chain-spec-builder", + "staging-node-cli", + "staging-parachain-info", + "staging-xcm", + "subkey", + "substrate-wasm-builder", +] + [[package]] name = "polkadot-service" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "async-trait", + "bitvec", "env_logger 0.9.3", "frame-benchmarking", "frame-benchmarking-cli", @@ -12850,6 +13444,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-db", "parity-scale-codec", + "parking_lot 0.12.1", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", @@ -12883,6 +13478,7 @@ dependencies = [ "polkadot-overseer", "polkadot-parachain-primitives", "polkadot-primitives", + "polkadot-primitives-test-helpers", "polkadot-rpc", "polkadot-runtime-parachains", "polkadot-statement-distribution", @@ -12912,6 +13508,7 @@ dependencies = [ "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", + "schnellru", "serde", "serde_json", "serial_test", @@ -12933,7 +13530,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage", + "sp-storage 19.0.0", "sp-timestamp", "sp-transaction-pool", "sp-version", @@ -12948,7 +13545,7 @@ dependencies = [ [[package]] name = "polkadot-statement-distribution" -version = "1.0.0" +version = "7.0.0" dependencies = [ "arrayvec 0.7.4", "assert_matches", @@ -12957,13 +13554,12 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 1.9.3", + "indexmap 2.2.3", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", - "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-primitives", "polkadot-primitives-test-helpers", @@ -12976,18 +13572,86 @@ dependencies = [ "sp-keyring", "sp-keystore", "sp-staking", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing-gum", ] [[package]] name = "polkadot-statement-table" +version = "7.0.0" +dependencies = [ + "parity-scale-codec", + "polkadot-primitives", + "sp-core", + "tracing-gum", +] + +[[package]] +name = "polkadot-subsystem-bench" version = "1.0.0" dependencies = [ + "assert_matches", + "async-trait", + "bincode", + "bitvec", + "clap 4.5.3", + "clap-num", + "color-eyre", + "colored", + "env_logger 0.9.3", + "futures", + "futures-timer", + "hex", + "itertools 0.11.0", + "kvdb-memorydb", + "log", + "orchestra", "parity-scale-codec", + "paste", + "polkadot-approval-distribution", + "polkadot-availability-bitfield-distribution", + "polkadot-availability-distribution", + "polkadot-availability-recovery", + "polkadot-erasure-coding", + "polkadot-node-core-approval-voting", + "polkadot-node-core-av-store", + "polkadot-node-core-chain-api", + "polkadot-node-metrics", + "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", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "rand_distr", + "sc-keystore", + "sc-network", + "sc-service", + "schnorrkel 0.11.4", + "serde", + "serde_yaml", + "sha1", + "sp-application-crypto", + "sp-consensus", + "sp-consensus-babe", "sp-core", + "sp-keyring", + "sp-keystore", + "sp-runtime", + "sp-timestamp", + "substrate-prometheus-endpoint", + "tokio", + "tracing-gum", ] [[package]] @@ -13025,7 +13689,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.4.6", + "clap 4.5.3", "color-eyre", "futures", "futures-timer", @@ -13043,7 +13707,7 @@ dependencies = [ "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-primitives", - "rand 0.8.5", + "rand", "sp-core", "sp-keystore", "substrate-build-script-utils", @@ -13104,7 +13768,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-trie", "sp-version", @@ -13136,7 +13800,7 @@ dependencies = [ "polkadot-runtime-parachains", "polkadot-service", "polkadot-test-runtime", - "rand 0.8.5", + "rand", "sc-authority-discovery", "sc-chain-spec", "sc-cli", @@ -13170,14 +13834,97 @@ dependencies = [ [[package]] name = "polkadot-voter-bags" -version = "1.0.0" +version = "7.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "generate-bags", "sp-io", "westend-runtime", ] +[[package]] +name = "polkavm" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" +dependencies = [ + "libc", + "log", + "polkavm-assembler", + "polkavm-common", + "polkavm-linux-raw", +] + +[[package]] +name = "polkavm-assembler" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa96d6d868243acc12de813dd48e756cbadcc8e13964c70d272753266deadc1" +dependencies = [ + "log", +] + +[[package]] +name = "polkavm-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" +dependencies = [ + "log", +] + +[[package]] +name = "polkavm-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" +dependencies = [ + "polkavm-derive-impl-macro", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common", + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.53", +] + +[[package]] +name = "polkavm-linker" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.32.2", + "polkavm-common", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linux-raw" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" + [[package]] name = "polling" version = "2.8.0" @@ -13202,7 +13949,7 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -13225,7 +13972,7 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -13252,7 +13999,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -13350,7 +14097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -13370,9 +14117,9 @@ dependencies = [ [[package]] name = "prioritized-metered-channel" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99f0c89bd88f393aab44a4ab949351f7bc7e7e1179d11ecbfe50cbe4c47e342" +checksum = "a172e6cc603231f2cf004232eabcecccc0da53ba576ab286ef7baa0cfc7927ad" dependencies = [ "coarsetime", "crossbeam-queue", @@ -13391,7 +14138,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd" +dependencies = [ + "toml_edit 0.21.0", ] [[package]] @@ -13432,18 +14188,44 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.4.0", + "chrono", + "flate2", + "hex", + "lazy_static", + "procfs-core", + "rustix 0.38.21", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.4.0", + "chrono", + "hex", +] + [[package]] name = "prometheus" version = "0.13.3" @@ -13478,7 +14260,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -13493,6 +14275,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", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.8.2", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -13500,7 +14302,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive 0.12.3", ] [[package]] @@ -13510,14 +14322,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", - "heck", + "heck 0.4.1", "itertools 0.10.5", "lazy_static", "log", "multimap", "petgraph", "prettyplease 0.1.25", - "prost", + "prost 0.11.9", "prost-types", "regex", "syn 1.0.109", @@ -13538,13 +14350,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "prost-types" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost", + "prost 0.11.9", ] [[package]] @@ -13566,8 +14391,8 @@ dependencies = [ "libc", "libflate", "log", - "names 0.14.0", - "prost", + "names", + "prost 0.11.9", "reqwest", "thiserror", "url", @@ -13586,6 +14411,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -13620,7 +14461,9 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand 0.8.5", + "env_logger 0.8.4", + "log", + "rand", ] [[package]] @@ -13636,12 +14479,12 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31999cfc7927c4e212e60fd50934ab40e8e8bfd2d493d6095d2d306bc0764d9" +checksum = "c956be1b23f4261676aed05a0046e204e8a6836e50203902683a718af0797989" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring 0.16.20", "rustc-hash", "rustls 0.20.8", @@ -13649,14 +14492,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", ] @@ -13667,19 +14510,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -13736,27 +14566,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_pcg" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.6.4", ] [[package]] -name = "rand_pcg" -version = "0.3.1" +name = "rand_xorshift" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -13785,19 +14624,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", - "x509-parser 0.13.2", - "yasna", -] - [[package]] name = "rcgen" version = "0.10.0" @@ -13828,6 +14654,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -13841,14 +14676,13 @@ dependencies = [ [[package]] name = "reed-solomon-novelpoly" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd8f48b2066e9f69ab192797d66da804d1935bf22763204ed3675740cb0f221" +checksum = "87413ebb313323d431e85d0afc5a68222aaed972843537cbfe5f061cf1b4bcab" dependencies = [ "derive_more", "fs-err", - "itertools 0.10.5", - "static_init 0.5.2", + "static_init", "thiserror", ] @@ -13869,7 +14703,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -13884,16 +14718,29 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + [[package]] name = "regex" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -13910,10 +14757,16 @@ name = "regex-automata" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] @@ -13924,20 +14777,26 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" [[package]] name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "frame-system", "log", "pallet-bags-list-remote-tests", "sp-core", - "sp-tracing", + "sp-tracing 16.0.0", "tokio", "westend-runtime", "westend-runtime-constants", @@ -13967,12 +14826,12 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.12", "rustls 0.21.6", - "rustls-pemfile", + "rustls-pemfile 1.0.3", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -13992,17 +14851,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" @@ -14010,23 +14858,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#edd1e90b847e560bf60fc2e8712235ccfa11a9a9" +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", - "merlin 3.0.0", + "merlin", ] [[package]] @@ -14039,11 +14887,34 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "rle-decode-fast" version = "1.0.3" @@ -14070,16 +14941,32 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rococo-emulated-chain" +version = "0.0.0" +dependencies = [ + "emulated-integration-tests-common", + "parachains-common", + "polkadot-primitives", + "rococo-runtime", + "rococo-runtime-constants", + "sc-consensus-grandpa", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-core", +] + [[package]] name = "rococo-parachain-runtime" -version = "0.1.0" +version = "0.6.0" dependencies = [ "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -14090,12 +14977,12 @@ dependencies = [ "pallet-assets", "pallet-aura", "pallet-balances", + "pallet-message-queue", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-xcm", - "parachain-info", "parachains-common", "parity-scale-codec", "polkadot-parachain-primitives", @@ -14110,18 +14997,20 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "testnet-parachains-constants", ] [[package]] name = "rococo-runtime" -version = "1.0.0" +version = "7.0.0" dependencies = [ "binary-merkle-tree", "frame-benchmarking", @@ -14162,6 +15051,7 @@ dependencies = [ "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", + "pallet-root-testing", "pallet-scheduler", "pallet-session", "pallet-society", @@ -14206,9 +15096,9 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", - "sp-storage", - "sp-tracing", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", "sp-transaction-pool", "sp-trie", "sp-version", @@ -14223,7 +15113,7 @@ dependencies = [ [[package]] name = "rococo-runtime-constants" -version = "1.0.0" +version = "7.0.0" dependencies = [ "frame-support", "polkadot-primitives", @@ -14233,8 +15123,41 @@ dependencies = [ "sp-runtime", "sp-weights", "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "rococo-system-emulated-network" +version = "0.0.0" +dependencies = [ + "asset-hub-rococo-emulated-chain", + "bridge-hub-rococo-emulated-chain", + "emulated-integration-tests-common", + "penpal-emulated-chain", + "people-rococo-emulated-chain", + "rococo-emulated-chain", +] + +[[package]] +name = "rococo-westend-system-emulated-network" +version = "0.0.0" +dependencies = [ + "asset-hub-rococo-emulated-chain", + "asset-hub-westend-emulated-chain", + "bridge-hub-rococo-emulated-chain", + "bridge-hub-westend-emulated-chain", + "emulated-integration-tests-common", + "penpal-emulated-chain", + "rococo-emulated-chain", + "westend-emulated-chain", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "rpassword" version = "7.2.0" @@ -14247,14 +15170,32 @@ dependencies = [ ] [[package]] -name = "rtcp" -version = "0.7.2" +name = "rstest" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ - "bytes", - "thiserror", - "webrtc-util", + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn 2.0.53", + "unicode-ident", ] [[package]] @@ -14283,19 +15224,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", - "rand 0.8.5", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "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" @@ -14323,6 +15280,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" @@ -14371,52 +15337,53 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.10", "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "base64 0.13.1", "log", "ring 0.16.20", - "sct 0.6.1", - "webpki 0.21.4", + "sct", + "webpki", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring 0.16.20", - "sct 0.7.0", - "webpki 0.22.0", + "rustls-webpki 0.101.4", + "sct", ] [[package]] name = "rustls" -version = "0.21.6" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", - "ring 0.16.20", - "rustls-webpki 0.101.4", - "sct 0.7.0", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle 2.5.0", + "zeroize", ] [[package]] @@ -14426,7 +15393,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.3", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.0.0", + "rustls-pki-types", "schannel", "security-framework", ] @@ -14441,15 +15421,21 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.100.2" +name = "rustls-pemfile" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ - "ring 0.16.20", - "untrusted", + "base64 0.21.2", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" + [[package]] name = "rustls-webpki" version = "0.101.4" @@ -14457,7 +15443,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring 0.16.20", - "untrusted", + "untrusted 0.7.1", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] @@ -14475,6 +15472,7 @@ dependencies = [ "fnv", "quick-error", "tempfile", + "wait-timeout", ] [[package]] @@ -14534,17 +15532,17 @@ dependencies = [ [[package]] name = "sc-allocator" -version = "4.1.0-dev" +version = "23.0.0" dependencies = [ "log", "sp-core", - "sp-wasm-interface", + "sp-wasm-interface 20.0.0", "thiserror", ] [[package]] name = "sc-authority-discovery" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "async-trait", "futures", @@ -14552,12 +15550,13 @@ dependencies = [ "ip_network", "libp2p", "log", - "multihash", + "multihash 0.18.1", + "multihash-codetable", "parity-scale-codec", - "prost", + "prost 0.12.3", "prost-build", "quickcheck", - "rand 0.8.5", + "rand", "sc-client-api", "sc-network", "sp-api", @@ -14566,7 +15565,7 @@ dependencies = [ "sp-core", "sp-keystore", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", @@ -14574,7 +15573,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "futures", "futures-timer", @@ -14599,10 +15598,9 @@ dependencies = [ [[package]] name = "sc-block-builder" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "parity-scale-codec", - "sc-client-api", "sp-api", "sp-block-builder", "sp-blockchain", @@ -14610,14 +15608,19 @@ dependencies = [ "sp-inherents", "sp-runtime", "sp-state-machine", + "sp-trie", "substrate-test-runtime-client", ] [[package]] name = "sc-chain-spec" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ - "memmap2", + "array-bytes 6.1.0", + "docify 0.2.7", + "log", + "memmap2 0.9.3", + "parity-scale-codec", "sc-chain-spec-derive", "sc-client-api", "sc-executor", @@ -14625,37 +15628,46 @@ dependencies = [ "sc-telemetry", "serde", "serde_json", + "sp-application-crypto", "sp-blockchain", + "sp-consensus-babe", "sp-core", + "sp-crypto-hashing", + "sp-genesis-builder", + "sp-io", + "sp-keyring", "sp-runtime", "sp-state-machine", + "substrate-test-runtime", ] [[package]] name = "sc-chain-spec-derive" -version = "4.0.0-dev" +version = "11.0.0" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "sc-cli" -version = "0.10.0-dev" +version = "0.36.0" dependencies = [ "array-bytes 6.1.0", "chrono", - "clap 4.4.6", + "clap 4.5.3", "fdlimit", "futures", "futures-timer", + "itertools 0.10.5", "libp2p-identity", "log", - "names 0.13.0", + "names", + "parity-bip39", "parity-scale-codec", - "rand 0.8.5", + "rand", "regex", "rpassword", "sc-client-api", @@ -14675,17 +15687,16 @@ dependencies = [ "sp-keystore", "sp-panic-handler", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "sp-version", "tempfile", "thiserror", - "tiny-bip39", "tokio", ] [[package]] name = "sc-client-api" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "fnv", "futures", @@ -14700,11 +15711,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities", + "sp-externalities 0.25.0", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage", + "sp-storage 19.0.0", "sp-test-primitives", "sp-trie", "substrate-prometheus-endpoint", @@ -14714,7 +15725,7 @@ dependencies = [ [[package]] name = "sc-client-db" -version = "0.10.0-dev" +version = "0.35.0" dependencies = [ "array-bytes 6.1.0", "criterion 0.4.0", @@ -14729,7 +15740,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "quickcheck", - "rand 0.8.5", + "rand", "sc-client-api", "sc-state-db", "schnellru", @@ -14739,7 +15750,7 @@ dependencies = [ "sp-database", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "sp-trie", "substrate-test-runtime-client", "tempfile", @@ -14747,7 +15758,7 @@ dependencies = [ [[package]] name = "sc-consensus" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-trait", "futures", @@ -14772,7 +15783,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "async-trait", "futures", @@ -14800,7 +15811,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-timestamp", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", @@ -14810,7 +15821,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "async-trait", "fork-tree", @@ -14837,12 +15848,13 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-slots", "sp-core", + "sp-crypto-hashing", "sp-inherents", "sp-keyring", "sp-keystore", "sp-runtime", "sp-timestamp", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", @@ -14851,7 +15863,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "futures", "jsonrpsee", @@ -14879,7 +15891,7 @@ dependencies = [ [[package]] name = "sc-consensus-beefy" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "array-bytes 6.1.0", "async-channel", @@ -14906,11 +15918,12 @@ dependencies = [ "sp-consensus-beefy", "sp-consensus-grandpa", "sp-core", + "sp-crypto-hashing", "sp-keyring", "sp-keystore", "sp-mmr-primitives", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", @@ -14921,7 +15934,7 @@ dependencies = [ [[package]] name = "sc-consensus-beefy-rpc" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "futures", "jsonrpsee", @@ -14942,7 +15955,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "fork-tree", "parity-scale-codec", @@ -14954,9 +15967,9 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" -version = "0.10.0-dev" +version = "0.19.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.8", "array-bytes 6.1.0", "assert_matches", "async-trait", @@ -14968,7 +15981,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -14976,6 +15989,7 @@ dependencies = [ "sc-network", "sc-network-common", "sc-network-gossip", + "sc-network-sync", "sc-network-test", "sc-telemetry", "sc-transaction-pool-api", @@ -14989,10 +16003,11 @@ dependencies = [ "sp-consensus", "sp-consensus-grandpa", "sp-core", + "sp-crypto-hashing", "sp-keyring", "sp-keystore", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", @@ -15001,7 +16016,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" -version = "0.10.0-dev" +version = "0.19.0" dependencies = [ "finality-grandpa", "futures", @@ -15025,7 +16040,7 @@ dependencies = [ [[package]] name = "sc-consensus-manual-seal" -version = "0.10.0-dev" +version = "0.35.0" dependencies = [ "assert_matches", "async-trait", @@ -15063,7 +16078,7 @@ dependencies = [ [[package]] name = "sc-consensus-pow" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-trait", "futures", @@ -15087,7 +16102,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-trait", "futures", @@ -15110,7 +16125,7 @@ dependencies = [ [[package]] name = "sc-executor" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -15122,44 +16137,57 @@ dependencies = [ "paste", "regex", "sc-executor-common", + "sc-executor-polkavm", "sc-executor-wasmtime", "sc-runtime-test", "sc-tracing", "schnellru", "sp-api", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-io", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 24.0.0", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "sp-trie", "sp-version", - "sp-wasm-interface", + "sp-wasm-interface 20.0.0", "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", "wat", ] [[package]] name = "sc-executor-common" -version = "0.10.0-dev" +version = "0.29.0" dependencies = [ + "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface", + "sp-wasm-interface 20.0.0", "thiserror", - "wasm-instrument 0.3.0", + "wasm-instrument", +] + +[[package]] +name = "sc-executor-polkavm" +version = "0.29.0" +dependencies = [ + "log", + "polkavm", + "sc-executor-common", + "sp-wasm-interface 20.0.0", ] [[package]] name = "sc-executor-wasmtime" -version = "0.10.0-dev" +version = "0.29.0" dependencies = [ "anyhow", "cargo_metadata", @@ -15174,8 +16202,8 @@ dependencies = [ "sc-executor-common", "sc-runtime-test", "sp-io", - "sp-runtime-interface", - "sp-wasm-interface", + "sp-runtime-interface 24.0.0", + "sp-wasm-interface 20.0.0", "tempfile", "wasmtime", "wat", @@ -15183,7 +16211,7 @@ dependencies = [ [[package]] name = "sc-informant" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "ansi_term", "futures", @@ -15192,13 +16220,14 @@ dependencies = [ "sc-client-api", "sc-network", "sc-network-common", + "sc-network-sync", "sp-blockchain", "sp-runtime", ] [[package]] name = "sc-keystore" -version = "4.0.0-dev" +version = "25.0.0" dependencies = [ "array-bytes 6.1.0", "parking_lot 0.12.1", @@ -15212,11 +16241,12 @@ dependencies = [ [[package]] name = "sc-mixnet" -version = "0.1.0-dev" +version = "0.4.0" dependencies = [ "array-bytes 4.2.0", "arrayvec 0.7.4", "blake2 0.10.6", + "bytes", "futures", "futures-timer", "libp2p-identity", @@ -15239,7 +16269,7 @@ dependencies = [ [[package]] name = "sc-network" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -15261,7 +16291,7 @@ dependencies = [ "parking_lot 0.12.1", "partial_sort", "pin-project", - "rand 0.8.5", + "rand", "sc-client-api", "sc-network-common", "sc-network-light", @@ -15275,13 +16305,14 @@ dependencies = [ "sp-core", "sp-runtime", "sp-test-primitives", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", "thiserror", "tokio", + "tokio-stream", "tokio-test", "tokio-util", "unsigned-varint", @@ -15291,14 +16322,14 @@ dependencies = [ [[package]] name = "sc-network-bitswap" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-channel", "cid", "futures", "libp2p-identity", "log", - "prost", + "prost 0.12.3", "prost-build", "sc-block-builder", "sc-client-api", @@ -15306,7 +16337,7 @@ dependencies = [ "sc-network", "sp-blockchain", "sp-consensus", - "sp-core", + "sp-crypto-hashing", "sp-runtime", "substrate-test-runtime", "substrate-test-runtime-client", @@ -15317,7 +16348,7 @@ dependencies = [ [[package]] name = "sc-network-common" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -15334,16 +16365,19 @@ dependencies = [ [[package]] name = "sc-network-gossip" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.8", + "async-trait", "futures", "futures-timer", "libp2p", "log", + "parity-scale-codec", "quickcheck", "sc-network", "sc-network-common", + "sc-network-sync", "schnellru", "sp-runtime", "substrate-prometheus-endpoint", @@ -15354,7 +16388,7 @@ dependencies = [ [[package]] name = "sc-network-light" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "array-bytes 6.1.0", "async-channel", @@ -15362,7 +16396,7 @@ dependencies = [ "libp2p-identity", "log", "parity-scale-codec", - "prost", + "prost 0.12.3", "prost-build", "sc-client-api", "sc-network", @@ -15374,7 +16408,7 @@ dependencies = [ [[package]] name = "sc-network-statement" -version = "0.10.0-dev" +version = "0.16.0" dependencies = [ "array-bytes 6.1.0", "async-channel", @@ -15384,6 +16418,7 @@ dependencies = [ "parity-scale-codec", "sc-network", "sc-network-common", + "sc-network-sync", "sp-consensus", "sp-statement-store", "substrate-prometheus-endpoint", @@ -15391,7 +16426,7 @@ dependencies = [ [[package]] name = "sc-network-sync" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "array-bytes 6.1.0", "async-channel", @@ -15403,7 +16438,7 @@ dependencies = [ "log", "mockall", "parity-scale-codec", - "prost", + "prost 0.12.3", "prost-build", "quickcheck", "sc-block-builder", @@ -15421,7 +16456,7 @@ dependencies = [ "sp-core", "sp-runtime", "sp-test-primitives", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", "thiserror", @@ -15439,7 +16474,7 @@ dependencies = [ "libp2p", "log", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -15453,7 +16488,7 @@ dependencies = [ "sp-consensus", "sp-core", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-test-runtime", "substrate-test-runtime-client", "tokio", @@ -15461,7 +16496,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "array-bytes 6.1.0", "futures", @@ -15470,6 +16505,7 @@ dependencies = [ "parity-scale-codec", "sc-network", "sc-network-common", + "sc-network-sync", "sc-utils", "sp-consensus", "sp-runtime", @@ -15478,7 +16514,7 @@ dependencies = [ [[package]] name = "sc-offchain" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ "array-bytes 6.1.0", "bytes", @@ -15494,7 +16530,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -15506,11 +16542,11 @@ dependencies = [ "sp-api", "sp-consensus", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-keystore", "sp-offchain", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-test-runtime-client", "threadpool", "tokio", @@ -15519,7 +16555,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" -version = "0.10.0-dev" +version = "0.17.0" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -15527,7 +16563,7 @@ dependencies = [ [[package]] name = "sc-rpc" -version = "4.0.0-dev" +version = "29.0.0" dependencies = [ "assert_matches", "env_logger 0.9.3", @@ -15553,6 +16589,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-keystore", "sp-offchain", @@ -15563,11 +16600,12 @@ dependencies = [ "sp-version", "substrate-test-runtime-client", "tokio", + "tracing-subscriber 0.3.18", ] [[package]] name = "sc-rpc-api" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -15586,9 +16624,12 @@ dependencies = [ [[package]] name = "sc-rpc-server" -version = "4.0.0-dev" +version = "11.0.0" dependencies = [ + "futures", + "governor", "http", + "hyper", "jsonrpsee", "log", "serde_json", @@ -15600,7 +16641,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -15612,10 +16653,13 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pretty_assertions", + "rand", "sc-block-builder", "sc-chain-spec", "sc-client-api", + "sc-rpc", "sc-service", + "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "serde", @@ -15624,11 +16668,14 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", + "sp-externalities 0.25.0", "sp-maybe-compressed-blob", + "sp-rpc", "sp-runtime", "sp-version", "substrate-test-runtime", "substrate-test-runtime-client", + "substrate-test-runtime-transaction-pool", "thiserror", "tokio", "tokio-stream", @@ -15641,14 +16688,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface", - "sp-std", + "sp-runtime-interface 24.0.0", + "sp-std 14.0.0", "substrate-wasm-builder", ] [[package]] name = "sc-service" -version = "0.10.0-dev" +version = "0.35.0" dependencies = [ "async-trait", "directories", @@ -15660,8 +16707,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pin-project", - "rand 0.8.5", - "sc-block-builder", + "rand", "sc-chain-spec", "sc-client-api", "sc-client-db", @@ -15684,23 +16730,24 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", + "schnellru", "serde", "serde_json", "sp-api", "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage", + "sp-storage 19.0.0", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", - "static_init 1.0.3", + "static_init", "substrate-prometheus-endpoint", "substrate-test-runtime", "substrate-test-runtime-client", @@ -15738,8 +16785,8 @@ dependencies = [ "sp-io", "sp-runtime", "sp-state-machine", - "sp-storage", - "sp-tracing", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", "sp-trie", "substrate-test-runtime", "substrate-test-runtime-client", @@ -15749,7 +16796,7 @@ dependencies = [ [[package]] name = "sc-state-db" -version = "0.10.0-dev" +version = "0.30.0" dependencies = [ "log", "parity-scale-codec", @@ -15759,7 +16806,7 @@ dependencies = [ [[package]] name = "sc-statement-store" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "env_logger 0.9.3", "log", @@ -15779,12 +16826,11 @@ dependencies = [ [[package]] name = "sc-storage-monitor" -version = "0.1.0" +version = "0.16.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "fs4", "log", - "sc-client-db", "sp-core", "thiserror", "tokio", @@ -15792,7 +16838,7 @@ dependencies = [ [[package]] name = "sc-sync-state-rpc" -version = "0.10.0-dev" +version = "0.34.0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -15810,26 +16856,28 @@ dependencies = [ [[package]] name = "sc-sysinfo" -version = "6.0.0-dev" +version = "27.0.0" dependencies = [ + "derive_more", "futures", "libc", "log", - "rand 0.8.5", + "rand", "rand_pcg", "regex", "sc-telemetry", "serde", "serde_json", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] name = "sc-telemetry" -version = "4.0.0-dev" +version = "15.0.0" dependencies = [ "chrono", "futures", @@ -15837,7 +16885,7 @@ dependencies = [ "log", "parking_lot 0.12.1", "pin-project", - "rand 0.8.5", + "rand", "sc-utils", "serde", "serde_json", @@ -15847,15 +16895,16 @@ dependencies = [ [[package]] name = "sc-tracing" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "ansi_term", - "atty", "chrono", "criterion 0.4.0", + "is-terminal", "lazy_static", "libc", "log", + "parity-scale-codec", "parking_lot 0.12.1", "regex", "rustc-hash", @@ -15867,26 +16916,26 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "thiserror", "tracing", - "tracing-log", - "tracing-subscriber", + "tracing-log 0.1.3", + "tracing-subscriber 0.2.25", ] [[package]] name = "sc-tracing-proc-macro" -version = "4.0.0-dev" +version = "11.0.0" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "sc-transaction-pool" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -15907,8 +16956,9 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", + "sp-crypto-hashing", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "sp-transaction-pool", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -15919,7 +16969,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "async-trait", "futures", @@ -15935,7 +16985,7 @@ dependencies = [ [[package]] name = "sc-utils" -version = "4.0.0-dev" +version = "14.0.0" dependencies = [ "async-channel", "futures", @@ -15968,7 +17018,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -15983,51 +17033,82 @@ 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.8", "cfg-if", "hashbrown 0.13.2", ] [[package]] name = "schnorrkel" -version = "0.9.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" dependencies = [ "arrayref", - "arrayvec 0.5.2", - "curve25519-dalek 2.1.3", - "getrandom 0.1.16", - "merlin 2.0.1", - "rand 0.7.3", - "rand_core 0.5.1", - "sha2 0.8.2", - "subtle 2.4.1", + "arrayvec 0.7.4", + "curve25519-dalek-ng", + "merlin", + "rand_core 0.6.4", + "sha2 0.9.9", + "subtle-ng", "zeroize", ] [[package]] name = "schnorrkel" -version = "0.10.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ + "aead 0.5.2", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek-ng", - "merlin 3.0.0", + "curve25519-dalek 4.1.2", + "getrandom_or_panic", + "merlin", "rand_core 0.6.4", - "sha2 0.9.9", - "subtle-ng", + "serde_bytes", + "sha2 0.10.7", + "subtle 2.5.0", "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" @@ -16040,16 +17121,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" @@ -16057,63 +17128,47 @@ 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", + "untrusted 0.7.1", ] [[package]] name = "sec1" -version = "0.3.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", + "base16ct", + "der", "generic-array 0.14.7", - "pkcs8 0.9.0", - "subtle 2.4.1", + "pkcs8", + "serdect", + "subtle 2.5.0", "zeroize", ] [[package]] -name = "sec1" -version = "0.7.3" +name = "seccompiler" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "345a3e4dddf721a478089d4697b83c6c0a8f5bf16086f6c13397e4534eb6e2e5" dependencies = [ - "base16ct 0.2.0", - "der 0.7.8", - "generic-array 0.14.7", - "pkcs8 0.10.2", - "subtle 2.4.1", - "zeroize", + "libc", ] [[package]] name = "secp256k1" -version = "0.24.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.6.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" dependencies = [ "cc", ] @@ -16152,7 +17207,7 @@ dependencies = [ [[package]] name = "seedling-runtime" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -16166,7 +17221,6 @@ dependencies = [ "pallet-balances", "pallet-sudo", "pallet-timestamp", - "parachain-info", "parachains-common", "parity-scale-codec", "scale-info", @@ -16179,9 +17233,10 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "substrate-wasm-builder", ] @@ -16191,7 +17246,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]] @@ -16200,7 +17255,16 @@ 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 = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -16218,6 +17282,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" @@ -16226,22 +17299,51 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 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.188" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 1.0.109", ] [[package]] @@ -16255,9 +17357,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -16266,9 +17368,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] @@ -16285,6 +17387,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" +dependencies = [ + "indexmap 2.2.3", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "serial_test" version = "2.0.0" @@ -16307,7 +17432,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -16336,27 +17461,15 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -16402,7 +17515,7 @@ dependencies = [ [[package]] name = "shell-runtime" -version = "0.1.0" +version = "0.7.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -16413,8 +17526,8 @@ dependencies = [ "frame-system", "frame-try-runtime", "pallet-aura", + "pallet-message-queue", "pallet-timestamp", - "parachain-info", "parachains-common", "parity-scale-codec", "scale-info", @@ -16427,9 +17540,10 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", + "sp-std 14.0.0", "sp-transaction-pool", "sp-version", + "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -16438,9 +17552,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -16461,16 +17575,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" @@ -16494,6 +17598,12 @@ dependencies = [ "wide", ] +[[package]] +name = "simple-mermaid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" + [[package]] name = "siphasher" version = "0.3.11" @@ -16517,13 +17627,13 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "slot-range-helper" -version = "1.0.0" +version = "7.0.0" dependencies = [ "enumn", "parity-scale-codec", "paste", "sp-runtime", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -16537,9 +17647,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smol" @@ -16551,13 +17661,22 @@ dependencies = [ "async-executor", "async-fs", "async-io", - "async-lock", + "async-lock 2.8.0", "async-net", "async-process", "blocking", "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" @@ -16565,7 +17684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ "arrayvec 0.7.4", - "async-lock", + "async-lock 2.8.0", "atomic-take", "base64 0.21.2", "bip39", @@ -16574,27 +17693,27 @@ dependencies = [ "chacha20 0.9.1", "crossbeam-queue", "derive_more", - "ed25519-zebra 4.0.2", + "ed25519-zebra 4.0.3", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-lite", "futures-util", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "hmac 0.12.1", "itertools 0.11.0", "libsecp256k1", - "merlin 3.0.0", + "merlin", "no-std-net", "nom", "num-bigint", "num-rational", "num-traits", - "pbkdf2 0.12.2", + "pbkdf2", "pin-project", "poly1305 0.8.0", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "ruzstd", "schnorrkel 0.10.2", @@ -16619,17 +17738,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "base64 0.21.2", "blake2-rfc", "derive_more", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-channel", "futures-lite", "futures-util", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "itertools 0.11.0", "log", @@ -16637,7 +17756,7 @@ dependencies = [ "no-std-net", "parking_lot 0.12.1", "pin-project", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "serde", "serde_json", @@ -16649,26 +17768,425 @@ dependencies = [ ] [[package]] -name = "snap" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" +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.2", + "blake2 0.10.6", + "chacha20poly1305", + "curve25519-dalek 4.1.2", + "rand_core 0.6.4", + "ring 0.16.20", + "rustc_version 0.4.0", + "sha2 0.10.7", + "subtle 2.5.0", +] + +[[package]] +name = "snowbridge-amcl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "snowbridge-beacon-primitives" +version = "0.2.0" +dependencies = [ + "byte-slice-cast", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "snowbridge-ethereum", + "snowbridge-milagro-bls", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "ssz_rs", + "ssz_rs_derive", + "static_assertions", +] + +[[package]] +name = "snowbridge-core" +version = "0.2.0" +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 14.0.0", + "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "snowbridge-ethereum" +version = "0.3.0" +dependencies = [ + "ethabi-decode", + "ethbloom", + "ethereum-types", + "hex-literal", + "parity-bytes", + "parity-scale-codec", + "rand", + "rlp", + "rustc-hex", + "scale-info", + "serde", + "serde-big-array", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "wasm-bindgen-test", +] + +[[package]] +name = "snowbridge-milagro-bls" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "026aa8638f690a53e3f7676024b9e913b1cab0111d1b7b92669d40a188f9d7e6" +dependencies = [ + "hex", + "lazy_static", + "parity-scale-codec", + "rand", + "scale-info", + "snowbridge-amcl", + "zeroize", +] + +[[package]] +name = "snowbridge-outbound-queue-merkle-tree" +version = "0.3.0" +dependencies = [ + "array-bytes 4.2.0", + "env_logger 0.9.3", + "hex", + "hex-literal", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-crypto-hashing", + "sp-runtime", +] + +[[package]] +name = "snowbridge-outbound-queue-runtime-api" +version = "0.2.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", + "sp-api", + "sp-core", + "sp-std 14.0.0", + "staging-xcm", +] + +[[package]] +name = "snowbridge-pallet-ethereum-client" +version = "0.2.0" +dependencies = [ + "bp-runtime", + "byte-slice-cast", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "log", + "pallet-timestamp", + "parity-scale-codec", + "rand", + "rlp", + "scale-info", + "serde", + "serde_json", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum", + "snowbridge-pallet-ethereum-client-fixtures", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 14.0.0", + "ssz_rs", + "ssz_rs_derive", + "static_assertions", +] + +[[package]] +name = "snowbridge-pallet-ethereum-client-fixtures" +version = "0.9.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "snowbridge-beacon-primitives", + "snowbridge-core", + "sp-core", + "sp-std 14.0.0", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue" +version = "0.2.0" +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-pallet-ethereum-client", + "snowbridge-pallet-inbound-queue-fixtures", + "snowbridge-router-primitives", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue-fixtures" +version = "0.10.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "snowbridge-beacon-primitives", + "snowbridge-core", + "sp-core", + "sp-std 14.0.0", +] + +[[package]] +name = "snowbridge-pallet-outbound-queue" +version = "0.2.0" +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 14.0.0", + "staging-xcm", +] + +[[package]] +name = "snowbridge-pallet-system" +version = "0.2.0" +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-pallet-outbound-queue", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-router-primitives" +version = "0.9.0" +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 14.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-runtime-common" +version = "0.2.0" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "snowbridge-core", + "sp-arithmetic", + "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-runtime-test-common" +version = "0.2.0" +dependencies = [ + "assets-common", + "bridge-hub-test-utils", + "bridge-runtime-common", + "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-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", + "scale-info", + "serde", + "smallvec", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-outbound-queue-runtime-api", + "snowbridge-pallet-ethereum-client", + "snowbridge-pallet-ethereum-client-fixtures", + "snowbridge-pallet-inbound-queue", + "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-system", + "snowbridge-router-primitives", + "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 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "static_assertions", +] [[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.2.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 14.0.0", + "staging-xcm", ] [[package]] @@ -16704,13 +18222,94 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1 0.9.8", ] +[[package]] +name = "solochain-template-node" +version = "0.0.0" +dependencies = [ + "clap 4.5.3", + "frame-benchmarking-cli", + "frame-system", + "futures", + "jsonrpsee", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc", + "sc-basic-authorship", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-aura", + "sc-consensus-grandpa", + "sc-executor", + "sc-network", + "sc-offchain", + "sc-rpc-api", + "sc-service", + "sc-telemetry", + "sc-transaction-pool", + "sc-transaction-pool-api", + "serde_json", + "solochain-template-runtime", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-timestamp", + "substrate-build-script-utils", + "substrate-frame-rpc-system", + "try-runtime-cli", +] + +[[package]] +name = "solochain-template-runtime" +version = "0.0.0" +dependencies = [ + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "pallet-aura", + "pallet-balances", + "pallet-grandpa", + "pallet-sudo", + "pallet-template", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", +] + [[package]] name = "sp-api" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "hash-db", "log", @@ -16718,11 +18317,12 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-metadata-ir", "sp-runtime", + "sp-runtime-interface 24.0.0", "sp-state-machine", - "sp-std", + "sp-std 14.0.0", "sp-test-primitives", "sp-trie", "sp-version", @@ -16731,16 +18331,16 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" -version = "4.0.0-dev" +version = "15.0.0" dependencies = [ "Inflector", "assert_matches", "blake2 0.10.6", "expander 2.0.0", - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -16759,7 +18359,7 @@ dependencies = [ "sp-core", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "sp-version", "static_assertions", "substrate-test-runtime-client", @@ -16768,14 +18368,14 @@ dependencies = [ [[package]] name = "sp-application-crypto" -version = "23.0.0" +version = "30.0.0" dependencies = [ "parity-scale-codec", "scale-info", "serde", "sp-core", "sp-io", - "sp-std", + "sp-std 14.0.0", ] [[package]] @@ -16791,18 +18391,17 @@ dependencies = [ [[package]] name = "sp-arithmetic" -version = "16.0.0" +version = "23.0.0" dependencies = [ "criterion 0.4.0", "integer-sqrt", "num-traits", "parity-scale-codec", "primitive-types", - "rand 0.8.5", + "rand", "scale-info", "serde", - "sp-core", - "sp-std", + "sp-crypto-hashing", "static_assertions", ] @@ -16817,31 +18416,47 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "sp-ark-bls12-381" +version = "0.4.2" +source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" +dependencies = [ + "ark-bls12-381-ext", + "sp-crypto-ec-utils 0.4.1", +] + +[[package]] +name = "sp-ark-ed-on-bls12-381-bandersnatch" +version = "0.4.2" +source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" +dependencies = [ + "ark-ed-on-bls12-381-bandersnatch-ext", + "sp-crypto-ec-utils 0.4.1", +] + [[package]] name = "sp-authority-discovery" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "parity-scale-codec", "scale-info", "sp-api", "sp-application-crypto", "sp-runtime", - "sp-std", ] [[package]] name = "sp-block-builder" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "sp-api", "sp-inherents", "sp-runtime", - "sp-std", ] [[package]] name = "sp-blockchain" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "futures", "log", @@ -16858,7 +18473,7 @@ dependencies = [ [[package]] name = "sp-consensus" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "async-trait", "futures", @@ -16873,7 +18488,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "async-trait", "parity-scale-codec", @@ -16883,13 +18498,12 @@ dependencies = [ "sp-consensus-slots", "sp-inherents", "sp-runtime", - "sp-std", "sp-timestamp", ] [[package]] name = "sp-consensus-babe" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "async-trait", "parity-scale-codec", @@ -16901,13 +18515,12 @@ dependencies = [ "sp-core", "sp-inherents", "sp-runtime", - "sp-std", "sp-timestamp", ] [[package]] name = "sp-consensus-beefy" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "array-bytes 6.1.0", "lazy_static", @@ -16917,17 +18530,18 @@ dependencies = [ "sp-api", "sp-application-crypto", "sp-core", + "sp-crypto-hashing", "sp-io", + "sp-keystore", "sp-mmr-primitives", "sp-runtime", - "sp-std", - "strum", + "strum 0.24.1", "w3f-bls", ] [[package]] name = "sp-consensus-grandpa" -version = "4.0.0-dev" +version = "13.0.0" dependencies = [ "finality-grandpa", "log", @@ -16939,18 +18553,16 @@ dependencies = [ "sp-core", "sp-keystore", "sp-runtime", - "sp-std", ] [[package]] name = "sp-consensus-pow" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "parity-scale-codec", "sp-api", "sp-core", "sp-runtime", - "sp-std", ] [[package]] @@ -16965,23 +18577,21 @@ dependencies = [ "sp-consensus-slots", "sp-core", "sp-runtime", - "sp-std", ] [[package]] name = "sp-consensus-slots" -version = "0.10.0-dev" +version = "0.32.0" dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-std", "sp-timestamp", ] [[package]] name = "sp-core" -version = "21.0.0" +version = "28.0.0" dependencies = [ "array-bytes 6.1.0", "bandersnatch_vrfs", @@ -16996,77 +18606,129 @@ dependencies = [ "hash-db", "hash256-std-hasher", "impl-serde", + "itertools 0.10.5", + "k256", "lazy_static", "libsecp256k1", "log", - "merlin 2.0.1", + "merlin", + "parity-bip39", "parity-scale-codec", "parking_lot 0.12.1", "paste", "primitive-types", - "rand 0.8.5", + "rand", "regex", "scale-info", - "schnorrkel 0.9.1", + "schnorrkel 0.11.4", "secp256k1", "secrecy", "serde", "serde_json", - "sp-core-hashing", - "sp-core-hashing-proc-macro", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std", - "sp-storage", + "sp-crypto-hashing", + "sp-debug-derive 14.0.0", + "sp-externalities 0.25.0", + "sp-runtime-interface 24.0.0", + "sp-std 14.0.0", + "sp-storage 19.0.0", "ss58-registry", "substrate-bip39", "thiserror", - "tiny-bip39", "tracing", "w3f-bls", "zeroize", ] +[[package]] +name = "sp-core-fuzz" +version = "0.0.0" +dependencies = [ + "lazy_static", + "libfuzzer-sys", + "regex", + "sp-core", +] + [[package]] name = "sp-core-hashing" -version = "9.0.0" +version = "15.0.0" dependencies = [ - "blake2b_simd", - "byteorder", - "digest 0.10.7", - "sha2 0.10.7", - "sha3", - "twox-hash", + "sp-crypto-hashing", ] [[package]] name = "sp-core-hashing-proc-macro" -version = "9.0.0" +version = "15.0.0" dependencies = [ - "quote", - "sp-core-hashing", - "syn 2.0.38", + "sp-crypto-hashing-proc-macro", ] [[package]] name = "sp-crypto-ec-utils" -version = "0.4.0" +version = "0.4.1" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale 0.0.11", + "sp-runtime-interface 17.0.0", + "sp-std 8.0.0", +] + +[[package]] +name = "sp-crypto-ec-utils" +version = "0.10.0" dependencies = [ "ark-bls12-377", + "ark-bls12-377-ext", "ark-bls12-381", + "ark-bls12-381-ext", "ark-bw6-761", + "ark-bw6-761-ext", "ark-ec", "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", - "ark-scale", - "sp-runtime-interface", - "sp-std", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale 0.0.12", + "sp-runtime-interface 24.0.0", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.0.0" +dependencies = [ + "blake2b_simd", + "byteorder", + "criterion 0.4.0", + "digest 0.10.7", + "sha2 0.10.7", + "sha3", + "sp-crypto-hashing-proc-macro", + "twox-hash", +] + +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.0.0" +dependencies = [ + "quote", + "sp-crypto-hashing", + "syn 2.0.53", ] [[package]] name = "sp-database" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "kvdb", "parking_lot 0.12.1", @@ -17075,35 +18737,54 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "8.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "sp-externalities" version = "0.19.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std 8.0.0", + "sp-storage 13.0.0", +] + +[[package]] +name = "sp-externalities" +version = "0.25.0" dependencies = [ "environmental", "parity-scale-codec", - "sp-std", - "sp-storage", + "sp-storage 19.0.0", ] [[package]] name = "sp-genesis-builder" -version = "0.1.0" +version = "0.7.0" dependencies = [ "serde_json", "sp-api", "sp-runtime", - "sp-std", ] [[package]] name = "sp-inherents" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "async-trait", "futures", @@ -17111,28 +18792,29 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std", "thiserror", ] [[package]] name = "sp-io" -version = "23.0.0" +version = "30.0.0" dependencies = [ "bytes", "ed25519-dalek", "libsecp256k1", "log", "parity-scale-codec", + "polkavm-derive", "rustversion", "secp256k1", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-keystore", - "sp-runtime-interface", + "sp-runtime-interface 24.0.0", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "sp-trie", "tracing", "tracing-core", @@ -17140,30 +18822,28 @@ dependencies = [ [[package]] name = "sp-keyring" -version = "24.0.0" +version = "31.0.0" dependencies = [ - "lazy_static", "sp-core", "sp-runtime", - "strum", + "strum 0.24.1", ] [[package]] name = "sp-keystore" -version = "0.27.0" +version = "0.34.0" dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.7.3", + "rand", "rand_chacha 0.2.2", "sp-core", - "sp-externalities", - "thiserror", + "sp-externalities 0.25.0", ] [[package]] name = "sp-maybe-compressed-blob" -version = "4.1.0-dev" +version = "11.0.0" dependencies = [ "thiserror", "zstd 0.12.4", @@ -17171,28 +18851,26 @@ dependencies = [ [[package]] name = "sp-metadata-ir" -version = "0.1.0" +version = "0.6.0" dependencies = [ "frame-metadata", "parity-scale-codec", "scale-info", - "sp-std", ] [[package]] name = "sp-mixnet" -version = "0.1.0-dev" +version = "0.4.0" dependencies = [ "parity-scale-codec", "scale-info", "sp-api", "sp-application-crypto", - "sp-std", ] [[package]] name = "sp-mmr-primitives" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "array-bytes 6.1.0", "ckb-merkle-mountain-range", @@ -17202,24 +18880,22 @@ dependencies = [ "serde", "sp-api", "sp-core", - "sp-debug-derive", + "sp-debug-derive 14.0.0", "sp-runtime", - "sp-std", "thiserror", ] [[package]] name = "sp-npos-elections" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "serde", "sp-arithmetic", "sp-core", "sp-runtime", - "sp-std", "substrate-test-utils", ] @@ -17227,16 +18903,16 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "honggfuzz", - "rand 0.8.5", + "rand", "sp-npos-elections", "sp-runtime", ] [[package]] name = "sp-offchain" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "sp-api", "sp-core", @@ -17245,7 +18921,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "8.0.0" +version = "13.0.0" dependencies = [ "backtrace", "lazy_static", @@ -17254,7 +18930,7 @@ dependencies = [ [[package]] name = "sp-rpc" -version = "6.0.0" +version = "26.0.0" dependencies = [ "rustc-hash", "serde", @@ -17264,26 +18940,28 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "24.0.0" +version = "31.0.1" dependencies = [ + "docify 0.2.7", "either", "hash256-std-hasher", "impl-trait-for-tuples", "log", "parity-scale-codec", "paste", - "rand 0.8.5", + "rand", "scale-info", "serde", "serde_json", + "simple-mermaid", "sp-api", "sp-application-crypto", "sp-arithmetic", "sp-core", "sp-io", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "sp-weights", "substrate-test-runtime-client", "zstd 0.12.4", @@ -17292,22 +18970,41 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities 0.19.0", + "sp-runtime-interface-proc-macro 11.0.0", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-tracing 10.0.0", + "sp-wasm-interface 14.0.0", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", + "polkavm-derive", "primitive-types", "rustversion", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-io", - "sp-runtime-interface-proc-macro", + "sp-runtime-interface-proc-macro 17.0.0", "sp-runtime-interface-test-wasm", "sp-state-machine", - "sp-std", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", + "sp-wasm-interface 20.0.0", "static_assertions", "trybuild", ] @@ -17315,12 +19012,25 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "Inflector", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "17.0.0" dependencies = [ "Inflector", - "proc-macro-crate", + "expander 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -17331,7 +19041,7 @@ dependencies = [ "sc-executor-common", "sp-io", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 24.0.0", "sp-runtime-interface-test-wasm", "sp-runtime-interface-test-wasm-deprecated", "sp-state-machine", @@ -17346,8 +19056,8 @@ dependencies = [ "bytes", "sp-core", "sp-io", - "sp-runtime-interface", - "sp-std", + "sp-runtime-interface 24.0.0", + "sp-std 14.0.0", "substrate-wasm-builder", ] @@ -17357,13 +19067,13 @@ version = "2.0.0" dependencies = [ "sp-core", "sp-io", - "sp-runtime-interface", + "sp-runtime-interface 24.0.0", "substrate-wasm-builder", ] [[package]] name = "sp-session" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -17372,12 +19082,11 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-staking", - "sp-std", ] [[package]] name = "sp-staking" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -17385,12 +19094,11 @@ dependencies = [ "serde", "sp-core", "sp-runtime", - "sp-std", ] [[package]] name = "sp-state-machine" -version = "0.28.0" +version = "0.35.0" dependencies = [ "array-bytes 6.1.0", "assert_matches", @@ -17399,13 +19107,12 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pretty_assertions", - "rand 0.8.5", + "rand", "smallvec", "sp-core", - "sp-externalities", + "sp-externalities 0.25.0", "sp-panic-handler", "sp-runtime", - "sp-std", "sp-trie", "thiserror", "tracing", @@ -17414,23 +19121,23 @@ dependencies = [ [[package]] name = "sp-statement-store" -version = "4.0.0-dev" +version = "10.0.0" dependencies = [ "aes-gcm 0.10.3", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.2", "ed25519-dalek", "hkdf", "parity-scale-codec", - "rand 0.8.5", + "rand", "scale-info", "sha2 0.10.7", "sp-api", "sp-application-crypto", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-runtime", - "sp-runtime-interface", - "sp-std", + "sp-runtime-interface 24.0.0", "thiserror", "x25519-dalek 2.0.0", ] @@ -17438,17 +19145,34 @@ dependencies = [ [[package]] name = "sp-std" version = "8.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" + +[[package]] +name = "sp-std" +version = "14.0.0" [[package]] name = "sp-storage" version = "13.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive 8.0.0", + "sp-std 8.0.0", +] + +[[package]] +name = "sp-storage" +version = "19.0.0" dependencies = [ "impl-serde", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive", - "sp-std", + "sp-debug-derive 14.0.0", ] [[package]] @@ -17461,35 +19185,44 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-runtime", - "sp-std", ] [[package]] name = "sp-timestamp" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "async-trait", "parity-scale-codec", "sp-inherents", "sp-runtime", - "sp-std", "thiserror", ] [[package]] name = "sp-tracing" version = "10.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "parity-scale-codec", + "sp-std 8.0.0", + "tracing", + "tracing-core", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "sp-tracing" +version = "16.0.0" dependencies = [ "parity-scale-codec", - "sp-std", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] name = "sp-transaction-pool" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "sp-api", "sp-runtime", @@ -17497,7 +19230,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" -version = "4.0.0-dev" +version = "26.0.0" dependencies = [ "async-trait", "parity-scale-codec", @@ -17505,30 +19238,28 @@ dependencies = [ "sp-core", "sp-inherents", "sp-runtime", - "sp-std", "sp-trie", ] [[package]] name = "sp-trie" -version = "22.0.0" +version = "29.0.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.8", "array-bytes 6.1.0", "criterion 0.4.0", "hash-db", - "hashbrown 0.13.2", "lazy_static", "memory-db", "nohash-hasher", "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "scale-info", "schnellru", "sp-core", + "sp-externalities 0.25.0", "sp-runtime", - "sp-std", "thiserror", "tracing", "trie-bench", @@ -17539,55 +19270,67 @@ dependencies = [ [[package]] name = "sp-version" -version = "22.0.0" +version = "29.0.0" dependencies = [ "impl-serde", "parity-scale-codec", "parity-wasm", "scale-info", "serde", - "sp-core-hashing-proc-macro", + "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-version-proc-macro", "thiserror", ] [[package]] name = "sp-version-proc-macro" -version = "8.0.0" +version = "13.0.0" dependencies = [ "parity-scale-codec", "proc-macro2", "quote", "sp-version", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "sp-wasm-interface" version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", - "sp-std", + "sp-std 8.0.0", "wasmtime", ] [[package]] -name = "sp-weights" +name = "sp-wasm-interface" version = "20.0.0" dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "wasmtime", +] + +[[package]] +name = "sp-weights" +version = "27.0.0" +dependencies = [ + "bounded-collections", "parity-scale-codec", "scale-info", + "schemars", "serde", "smallvec", "sp-arithmetic", - "sp-core", - "sp-debug-derive", - "sp-std", + "sp-debug-derive 14.0.0", ] [[package]] @@ -17610,17 +19353,7 @@ checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab" dependencies = [ "lazy_static", "maplit", - "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", + "strum 0.24.1", ] [[package]] @@ -17630,7 +19363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.8", + "der", ] [[package]] @@ -17648,16 +19381,197 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "staging-chain-spec-builder" +version = "2.0.0" +dependencies = [ + "clap 4.5.3", + "log", + "sc-chain-spec", + "serde_json", + "sp-tracing 16.0.0", +] + +[[package]] +name = "staging-node-cli" +version = "3.0.0-dev" +dependencies = [ + "array-bytes 6.1.0", + "assert_cmd", + "clap 4.5.3", + "clap_complete", + "criterion 0.4.0", + "frame-benchmarking", + "frame-benchmarking-cli", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "futures", + "jsonrpsee", + "kitchensink-runtime", + "log", + "mmr-gadget", + "nix 0.26.2", + "node-primitives", + "node-rpc", + "node-testing", + "pallet-asset-conversion-tx-payment", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-balances", + "pallet-contracts", + "pallet-glutton", + "pallet-im-online", + "pallet-root-testing", + "pallet-skip-feeless-payment", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-treasury", + "parity-scale-codec", + "platforms", + "rand", + "regex", + "sc-authority-discovery", + "sc-basic-authorship", + "sc-block-builder", + "sc-chain-spec", + "sc-cli", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-consensus-babe", + "sc-consensus-beefy", + "sc-consensus-epochs", + "sc-consensus-grandpa", + "sc-consensus-slots", + "sc-executor", + "sc-keystore", + "sc-mixnet", + "sc-network", + "sc-network-common", + "sc-network-statement", + "sc-network-sync", + "sc-offchain", + "sc-rpc", + "sc-service", + "sc-service-test", + "sc-statement-store", + "sc-storage-monitor", + "sc-sync-state-rpc", + "sc-sysinfo", + "sc-telemetry", + "sc-transaction-pool", + "sc-transaction-pool-api", + "scale-info", + "serde", + "serde_json", + "soketto", + "sp-api", + "sp-application-crypto", + "sp-authority-discovery", + "sp-blockchain", + "sp-consensus", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-consensus-grandpa", + "sp-core", + "sp-crypto-hashing", + "sp-externalities 0.25.0", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-keystore", + "sp-mixnet", + "sp-mmr-primitives", + "sp-runtime", + "sp-state-machine", + "sp-statement-store", + "sp-timestamp", + "sp-tracing 16.0.0", + "sp-transaction-storage-proof", + "sp-trie", + "staging-node-inspect", + "substrate-build-script-utils", + "substrate-cli-test-utils", + "substrate-frame-cli", + "substrate-rpc-client", + "tempfile", + "tokio", + "tokio-util", + "try-runtime-cli", + "wait-timeout", + "wat", +] + +[[package]] +name = "staging-node-inspect" +version = "0.12.0" +dependencies = [ + "clap 4.5.3", + "parity-scale-codec", + "sc-cli", + "sc-client-api", + "sc-service", + "sp-blockchain", + "sp-core", + "sp-io", + "sp-runtime", + "sp-statement-store", + "thiserror", +] + +[[package]] +name = "staging-parachain-info" +version = "0.7.0" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 14.0.0", +] + +[[package]] +name = "staging-tracking-allocator" +version = "2.0.0" + [[package]] name = "staging-xcm" -version = "1.0.0" +version = "7.0.0" dependencies = [ + "array-bytes 6.1.0", "bounded-collections", "derivative", "environmental", @@ -17667,6 +19581,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", + "schemars", "serde", "sp-io", "sp-weights", @@ -17675,7 +19590,7 @@ dependencies = [ [[package]] name = "staging-xcm-builder" -version = "1.0.0" +version = "7.0.0" dependencies = [ "assert_matches", "frame-support", @@ -17697,7 +19612,7 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-weights", "staging-xcm", "staging-xcm-executor", @@ -17705,7 +19620,7 @@ dependencies = [ [[package]] name = "staging-xcm-executor" -version = "1.0.0" +version = "7.0.0" dependencies = [ "environmental", "frame-benchmarking", @@ -17713,11 +19628,12 @@ dependencies = [ "impl-trait-for-tuples", "log", "parity-scale-codec", + "scale-info", "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "sp-weights", "staging-xcm", ] @@ -17728,18 +19644,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "static_init" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b73400442027c4adedda20a9f9b7945234a5bd8d5f7e86da22bd5d0622369c" -dependencies = [ - "cfg_aliases", - "libc", - "parking_lot 0.11.2", - "static_init_macro 0.5.0", -] - [[package]] name = "static_init" version = "1.0.3" @@ -17751,15 +19655,15 @@ dependencies = [ "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", - "static_init_macro 1.0.2", + "static_init_macro", "winapi", ] [[package]] name = "static_init_macro" -version = "0.5.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2261c91034a1edc3fc4d1b80e89d82714faede0515c14a75da10cb941546bbf" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ "cfg_aliases", "memchr", @@ -17769,16 +19673,16 @@ dependencies = [ ] [[package]] -name = "static_init_macro" -version = "1.0.2" +name = "strobe-rs" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +checksum = "fabb238a1cccccfa4c4fb703670c0d157e1256c1ba695abf1b93bd2bb14bab2d" dependencies = [ - "cfg_aliases", - "memchr", - "proc-macro2", - "quote", - "syn 1.0.109", + "bitflags 1.3.2", + "byteorder", + "keccak", + "subtle 2.5.0", + "zeroize", ] [[package]] @@ -17787,22 +19691,34 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + [[package]] name = "strum_macros" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -17810,67 +19726,42 @@ dependencies = [ ] [[package]] -name = "stun" -version = "0.4.4" +name = "strum_macros" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" 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", + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.53", ] [[package]] name = "subkey" -version = "3.0.0" -dependencies = [ - "clap 4.4.6", - "sc-cli", -] - -[[package]] -name = "substrate" -version = "1.0.0" +version = "9.0.0" dependencies = [ - "aquamarine", - "chain-spec-builder", - "frame-support", + "clap 4.5.3", "sc-cli", - "sc-consensus-aura", - "sc-consensus-babe", - "sc-consensus-beefy", - "sc-consensus-grandpa", - "sc-consensus-manual-seal", - "sc-consensus-pow", - "sc-service", - "sp-runtime", - "subkey", ] [[package]] name = "substrate-bip39" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +version = "0.4.7" dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", - "schnorrkel 0.9.1", - "sha2 0.9.9", + "bip39", + "hmac 0.12.1", + "pbkdf2", + "rustc-hex", + "schnorrkel 0.11.4", + "sha2 0.10.7", "zeroize", ] [[package]] name = "substrate-build-script-utils" -version = "3.0.0" +version = "11.0.0" [[package]] name = "substrate-cli-test-utils" @@ -17879,21 +19770,21 @@ dependencies = [ "assert_cmd", "futures", "nix 0.26.2", - "node-cli", "node-primitives", "regex", "sc-cli", "sc-service", "sp-rpc", + "staging-node-cli", "substrate-rpc-client", "tokio", ] [[package]] name = "substrate-frame-cli" -version = "4.0.0-dev" +version = "32.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "frame-support", "frame-system", "sc-cli", @@ -17903,7 +19794,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-support" -version = "3.0.0" +version = "29.0.0" dependencies = [ "frame-support", "frame-system", @@ -17914,13 +19805,13 @@ dependencies = [ "serde", "sp-core", "sp-runtime", - "sp-storage", + "sp-storage 19.0.0", "tokio", ] [[package]] name = "substrate-frame-rpc-system" -version = "4.0.0-dev" +version = "28.0.0" dependencies = [ "assert_matches", "frame-system-rpc-runtime-api", @@ -17936,14 +19827,14 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-runtime", - "sp-tracing", + "sp-tracing 16.0.0", "substrate-test-runtime-client", "tokio", ] [[package]] name = "substrate-prometheus-endpoint" -version = "0.10.0-dev" +version = "0.17.0" dependencies = [ "hyper", "log", @@ -17954,7 +19845,7 @@ dependencies = [ [[package]] name = "substrate-rpc-client" -version = "0.10.0-dev" +version = "0.33.0" dependencies = [ "async-trait", "jsonrpsee", @@ -17968,7 +19859,7 @@ dependencies = [ [[package]] name = "substrate-state-trie-migration-rpc" -version = "4.0.0-dev" +version = "27.0.0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -18006,6 +19897,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", + "tokio", ] [[package]] @@ -18018,13 +19910,13 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "futures", - "json-patch", "log", "pallet-babe", "pallet-balances", "pallet-timestamp", "parity-scale-codec", "sc-block-builder", + "sc-chain-spec", "sc-executor", "sc-executor-common", "sc-service", @@ -18039,7 +19931,8 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-grandpa", "sp-core", - "sp-externalities", + "sp-crypto-hashing", + "sp-externalities 0.25.0", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -18048,8 +19941,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-tracing 16.0.0", "sp-transaction-pool", "sp-trie", "sp-version", @@ -18102,30 +19994,22 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" -version = "5.0.0-dev" +version = "17.0.0" dependencies = [ - "ansi_term", "build-helper", "cargo_metadata", + "console", "filetime", "parity-wasm", + "polkavm-linker", "sp-maybe-compressed-blob", - "strum", + "strum 0.24.1", "tempfile", - "toml 0.7.6", + "toml 0.8.8", "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" @@ -18134,9 +20018,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-ng" @@ -18219,7 +20103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "167a4ffd7c35c143fd1030aa3c2caf76ba42220bd5a6b5f4781896434723b8c3" dependencies = [ "debugid", - "memmap2", + "memmap2 0.5.10", "stable_deref_trait", "uuid", ] @@ -18248,15 +20132,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" 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.53", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -18315,14 +20211,14 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.0", - "redox_syscall 0.3.5", - "rustix 0.38.8", + "redox_syscall 0.4.1", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -18335,6 +20231,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.21", + "windows-sys 0.48.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -18349,7 +20255,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "sp-io", - "sp-std", + "sp-std 14.0.0", "substrate-wasm-builder", "tiny-keccak", ] @@ -18358,7 +20264,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "futures", "futures-timer", "log", @@ -18397,7 +20303,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "sp-io", - "sp-std", + "sp-std 14.0.0", "substrate-wasm-builder", "tiny-keccak", ] @@ -18406,7 +20312,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.4.6", + "clap 4.5.3", "futures", "futures-timer", "log", @@ -18452,6 +20358,20 @@ dependencies = [ "sp-weights", ] +[[package]] +name = "testnet-parachains-constants" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "polkadot-core-primitives", + "rococo-runtime-constants", + "smallvec", + "sp-runtime", + "staging-xcm", + "westend-runtime-constants", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -18460,9 +20380,9 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] @@ -18489,13 +20409,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -18595,25 +20515,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-bip39" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" -dependencies = [ - "anyhow", - "hmac 0.12.1", - "once_cell", - "pbkdf2 0.11.0", - "rand 0.8.5", - "rustc-hash", - "sha2 0.10.7", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -18650,9 +20551,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", @@ -18675,7 +20576,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -18685,7 +20586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" dependencies = [ "pin-project", - "rand 0.8.5", + "rand", "tokio", ] @@ -18699,6 +20600,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.2", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -18762,32 +20674,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.21.0", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.3", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", @@ -18800,6 +20723,10 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite 0.2.12", "tower-layer", "tower-service", "tracing", @@ -18856,14 +20783,14 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[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", @@ -18881,10 +20808,9 @@ dependencies = [ [[package]] name = "tracing-gum" -version = "1.0.0" +version = "7.0.0" dependencies = [ "coarsetime", - "polkadot-node-jaeger", "polkadot-primitives", "tracing", "tracing-gum-proc-macro", @@ -18892,14 +20818,14 @@ dependencies = [ [[package]] name = "tracing-gum-proc-macro" -version = "1.0.0" +version = "5.0.0" dependencies = [ "assert_matches", "expander 2.0.0", - "proc-macro-crate", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -18913,6 +20839,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -18932,7 +20869,7 @@ dependencies = [ "ansi_term", "chrono", "lazy_static", - "matchers", + "matchers 0.0.1", "parking_lot 0.11.2", "regex", "serde", @@ -18942,10 +20879,28 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.3", "tracing-serde", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers 0.1.0", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + [[package]] name = "trie-bench" version = "0.38.0" @@ -19010,7 +20965,7 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static", - "rand 0.8.5", + "rand", "smallvec", "socket2 0.4.9", "thiserror", @@ -19048,11 +21003,11 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "try-runtime-cli" -version = "0.10.0-dev" +version = "0.38.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.4.6", + "clap 4.5.3", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -19068,8 +21023,8 @@ dependencies = [ "sp-consensus-aura", "sp-consensus-babe", "sp-core", - "sp-debug-derive", - "sp-externalities", + "sp-debug-derive 14.0.0", + "sp-externalities 0.25.0", "sp-inherents", "sp-io", "sp-keystore", @@ -19089,9 +21044,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.83" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df60d81823ed9c520ee897489573da4b1d79ffbe006b8134f46de1a1aa03555" +checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" dependencies = [ "basic-toml", "dissimilar", @@ -19121,32 +21076,13 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha-1 0.10.1", "thiserror", "url", "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" @@ -19155,7 +21091,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.8.5", + "rand", "static_assertions", ] @@ -19183,6 +21119,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" @@ -19218,12 +21160,12 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -19233,9 +21175,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "unsigned-varint" version = "0.7.1" @@ -19254,6 +21202,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" @@ -19282,9 +21236,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" @@ -19294,9 +21245,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -19304,9 +21255,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394" +checksum = "ead5b693d906686203f19a49e88c477fb8c15798b68cf72f60b4b5521b4ad891" dependencies = [ "erased-serde", "serde", @@ -19315,9 +21266,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d" +checksum = "3b9d0f4a816370c3a0d7d82d603b62198af17675b12fe5e91de6b47ceb505882" dependencies = [ "sval", "sval_buffer", @@ -19355,13 +21306,13 @@ 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", "digest 0.10.7", - "rand 0.8.5", + "rand", "rand_chacha 0.3.1", "rand_core 0.6.4", "sha2 0.10.7", @@ -19379,15 +21330,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" @@ -19396,9 +21338,9 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -19448,7 +21390,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-shared", ] @@ -19482,7 +21424,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -19493,6 +21435,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" @@ -19502,15 +21468,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-instrument" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" -dependencies = [ - "parity-wasm", -] - [[package]] name = "wasm-instrument" version = "0.4.0" @@ -19522,14 +21479,14 @@ dependencies = [ [[package]] name = "wasm-opt" -version = "0.114.1" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d005a95f934878a1fb446a816d51c3601a0120ff929005ba3bab3c749cfd1c7" +checksum = "fc942673e7684671f0c5708fc18993569d184265fd5223bb51fc8e5b9b6cfd52" dependencies = [ "anyhow", "libc", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tempfile", "thiserror", "wasm-opt-cxx-sys", @@ -19538,9 +21495,9 @@ dependencies = [ [[package]] name = "wasm-opt-cxx-sys" -version = "0.114.1" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d04e240598162810fad3b2e96fa0dec6dba1eb65a03f3bd99a9248ab8b56caa" +checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" dependencies = [ "anyhow", "cxx", @@ -19550,9 +21507,9 @@ dependencies = [ [[package]] name = "wasm-opt-sys" -version = "0.114.1" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efd2aaca519d64098c4faefc8b7433a97ed511caf4c9e516384eb6aef1ff4f9" +checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" dependencies = [ "anyhow", "cc", @@ -19577,9 +21534,9 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.31.0" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f341edb80021141d4ae6468cbeefc50798716a347d4085c3811900049ea8945" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" dependencies = [ "smallvec", "spin 0.9.8", @@ -19590,9 +21547,9 @@ dependencies = [ [[package]] name = "wasmi_arena" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" [[package]] name = "wasmi_core" @@ -19800,7 +21757,7 @@ dependencies = [ "memfd", "memoffset 0.8.0", "paste", - "rand 0.8.5", + "rand", "rustix 0.36.15", "wasmtime-asm-macros", "wasmtime-environ", @@ -19853,22 +21810,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]] @@ -19877,16 +21824,7 @@ version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki 0.22.0", -] - -[[package]] -name = "webpki-roots" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.2", + "webpki", ] [[package]] @@ -19896,216 +21834,26 @@ 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", - "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" +name = "westend-emulated-chain" +version = "0.0.0" 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", + "emulated-integration-tests-common", + "pallet-staking", + "parachains-common", + "polkadot-primitives", + "sc-consensus-grandpa", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-core", + "sp-runtime", + "westend-runtime", + "westend-runtime-constants", ] [[package]] name = "westend-runtime" -version = "1.0.0" +version = "7.0.0" dependencies = [ "binary-merkle-tree", "bitvec", @@ -20152,6 +21900,7 @@ dependencies = [ "pallet-proxy", "pallet-recovery", "pallet-referenda", + "pallet-root-testing", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", @@ -20199,9 +21948,9 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std", - "sp-storage", - "sp-tracing", + "sp-std 14.0.0", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", "sp-transaction-pool", "sp-version", "staging-xcm", @@ -20215,7 +21964,7 @@ dependencies = [ [[package]] name = "westend-runtime-constants" -version = "1.0.0" +version = "7.0.0" dependencies = [ "frame-support", "polkadot-primitives", @@ -20225,6 +21974,20 @@ dependencies = [ "sp-runtime", "sp-weights", "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "westend-system-emulated-network" +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", ] [[package]] @@ -20325,6 +22088,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -20355,6 +22127,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -20367,6 +22154,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.34.0" @@ -20385,6 +22178,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.34.0" @@ -20403,6 +22202,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.34.0" @@ -20421,6 +22226,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.34.0" @@ -20439,6 +22250,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -20451,6 +22268,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.34.0" @@ -20469,6 +22292,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.15" @@ -20514,44 +22343,25 @@ 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.2", "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", -] - [[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", @@ -20568,9 +22378,10 @@ dependencies = [ [[package]] name = "xcm-emulator" -version = "0.1.0" +version = "0.5.0" dependencies = [ "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-test-relay-sproof-builder", @@ -20589,10 +22400,11 @@ dependencies = [ "polkadot-runtime-parachains", "sp-arithmetic", "sp-core", + "sp-crypto-hashing", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-executor", ] @@ -20604,33 +22416,38 @@ dependencies = [ "frame-support", "frame-system", "futures", + "pallet-transaction-payment", "pallet-xcm", "parity-scale-codec", + "polkadot-service", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", "sp-consensus", + "sp-core", "sp-keyring", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-executor", ] [[package]] name = "xcm-procedural" -version = "1.0.0" +version = "7.0.0" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.38", + "staging-xcm", + "syn 2.0.53", + "trybuild", ] [[package]] name = "xcm-simulator" -version = "1.0.0" +version = "7.0.0" dependencies = [ "frame-support", "parity-scale-codec", @@ -20639,7 +22456,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-runtime-parachains", "sp-io", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -20647,7 +22464,7 @@ dependencies = [ [[package]] name = "xcm-simulator-example" -version = "1.0.0" +version = "7.0.0" dependencies = [ "frame-support", "frame-system", @@ -20664,8 +22481,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0", + "sp-tracing 16.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -20677,8 +22494,10 @@ name = "xcm-simulator-fuzzer" version = "1.0.0" dependencies = [ "arbitrary", + "frame-executive", "frame-support", "frame-system", + "frame-try-runtime", "honggfuzz", "pallet-balances", "pallet-message-queue", @@ -20691,7 +22510,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", @@ -20708,7 +22527,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot 0.12.1", - "rand 0.8.5", + "rand", "static_assertions", ] @@ -20727,11 +22546,31 @@ dependencies = [ "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.53", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -20744,7 +22583,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c98fe6d1a3acf82794792ddd321ac1745229cfa3..01d6ef8e87bdaf4dcf0e4f73dfde59df76f500c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ authors = ["Parity Technologies "] edition = "2021" repository = "https://github.com/paritytech/polkadot-sdk.git" license = "GPL-3.0-only" +homepage = "https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/index.html" [workspace] resolver = "2" @@ -13,21 +14,20 @@ 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-kusama", - "bridges/primitives/chain-asset-hub-polkadot", "bridges/primitives/chain-asset-hub-rococo", - "bridges/primitives/chain-asset-hub-wococo", + "bridges/primitives/chain-asset-hub-westend", "bridges/primitives/chain-bridge-hub-cumulus", "bridges/primitives/chain-bridge-hub-kusama", "bridges/primitives/chain-bridge-hub-polkadot", "bridges/primitives/chain-bridge-hub-rococo", - "bridges/primitives/chain-bridge-hub-wococo", + "bridges/primitives/chain-bridge-hub-westend", "bridges/primitives/chain-kusama", "bridges/primitives/chain-polkadot", "bridges/primitives/chain-polkadot-bulletin", "bridges/primitives/chain-rococo", - "bridges/primitives/chain-wococo", + "bridges/primitives/chain-westend", "bridges/primitives/header-chain", "bridges/primitives/messages", "bridges/primitives/parachains", @@ -35,7 +35,23 @@ members = [ "bridges/primitives/relayers", "bridges/primitives/runtime", "bridges/primitives/test-utils", + "bridges/primitives/xcm-bridge-hub", "bridges/primitives/xcm-bridge-hub-router", + "bridges/snowbridge/pallets/ethereum-client", + "bridges/snowbridge/pallets/ethereum-client/fixtures", + "bridges/snowbridge/pallets/inbound-queue", + "bridges/snowbridge/pallets/inbound-queue/fixtures", + "bridges/snowbridge/pallets/outbound-queue", + "bridges/snowbridge/pallets/outbound-queue/merkle-tree", + "bridges/snowbridge/pallets/outbound-queue/runtime-api", + "bridges/snowbridge/pallets/system", + "bridges/snowbridge/pallets/system/runtime-api", + "bridges/snowbridge/primitives/beacon", + "bridges/snowbridge/primitives/core", + "bridges/snowbridge/primitives/ethereum", + "bridges/snowbridge/primitives/router", + "bridges/snowbridge/runtime/runtime-common", + "bridges/snowbridge/runtime/test-common", "cumulus/client/cli", "cumulus/client/collator", "cumulus/client/consensus/aura", @@ -43,6 +59,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", @@ -58,30 +75,46 @@ members = [ "cumulus/pallets/solo-to-para", "cumulus/pallets/xcm", "cumulus/pallets/xcmp-queue", - "cumulus/parachain-template/node", - "cumulus/parachain-template/pallets/template", - "cumulus/parachain-template/runtime", "cumulus/parachains/common", - "cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo", - "cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend", - "cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo", + "cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo", + "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", "cumulus/parachains/integration-tests/emulated/common", + "cumulus/parachains/integration-tests/emulated/networks/rococo-system", + "cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system", + "cumulus/parachains/integration-tests/emulated/networks/westend-system", + "cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo", + "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", - "cumulus/parachains/runtimes/assets/asset-hub-kusama", - "cumulus/parachains/runtimes/assets/asset-hub-polkadot", "cumulus/parachains/runtimes/assets/asset-hub-rococo", "cumulus/parachains/runtimes/assets/asset-hub-westend", "cumulus/parachains/runtimes/assets/common", "cumulus/parachains/runtimes/assets/test-utils", - "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama", - "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot", "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo", + "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend", + "cumulus/parachains/runtimes/bridge-hubs/common", "cumulus/parachains/runtimes/bridge-hubs/test-utils", - "cumulus/parachains/runtimes/collectives/collectives-polkadot", + "cumulus/parachains/runtimes/collectives/collectives-westend", + "cumulus/parachains/runtimes/constants", "cumulus/parachains/runtimes/contracts/contracts-rococo", - "cumulus/parachains/runtimes/glutton/glutton-kusama", + "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", @@ -91,6 +124,8 @@ members = [ "cumulus/primitives/aura", "cumulus/primitives/core", "cumulus/primitives/parachain-inherent", + "cumulus/primitives/proof-size-hostfunction", + "cumulus/primitives/storage-weight-reclaim", "cumulus/primitives/timestamp", "cumulus/primitives/utility", "cumulus/test/client", @@ -98,6 +133,7 @@ members = [ "cumulus/test/runtime", "cumulus/test/service", "cumulus/xcm/xcm-emulator", + "docs/sdk", "polkadot", "polkadot/cli", "polkadot/core-primitives", @@ -115,8 +151,8 @@ members = [ "polkadot/node/core/parachains-inherent", "polkadot/node/core/prospective-parachains", "polkadot/node/core/provisioner", - "polkadot/node/core/pvf-checker", "polkadot/node/core/pvf", + "polkadot/node/core/pvf-checker", "polkadot/node/core/pvf/common", "polkadot/node/core/pvf/execute-worker", "polkadot/node/core/pvf/prepare-worker", @@ -139,12 +175,14 @@ members = [ "polkadot/node/overseer", "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", - "polkadot/node/subsystem", "polkadot/node/test/client", "polkadot/node/test/service", + "polkadot/node/tracking-allocator", "polkadot/node/zombienet-backchannel", "polkadot/parachain", "polkadot/parachain/test-parachains", @@ -170,8 +208,8 @@ members = [ "polkadot/utils/generate-bags", "polkadot/utils/remote-ext-tests/bags-list", "polkadot/xcm", - "polkadot/xcm/pallet-xcm-benchmarks", "polkadot/xcm/pallet-xcm", + "polkadot/xcm/pallet-xcm-benchmarks", "polkadot/xcm/procedural", "polkadot/xcm/xcm-builder", "polkadot/xcm/xcm-executor", @@ -179,13 +217,8 @@ members = [ "polkadot/xcm/xcm-simulator", "polkadot/xcm/xcm-simulator/example", "polkadot/xcm/xcm-simulator/fuzzer", - "substrate", - "substrate/bin/node-template/node", - "substrate/bin/node-template/pallets/template", - "substrate/bin/node-template/runtime", "substrate/bin/node/bench", "substrate/bin/node/cli", - "substrate/bin/node/executor", "substrate/bin/node/inspect", "substrate/bin/node/primitives", "substrate/bin/node/rpc", @@ -216,6 +249,7 @@ members = [ "substrate/client/db", "substrate/client/executor", "substrate/client/executor/common", + "substrate/client/executor/polkavm", "substrate/client/executor/runtime-test", "substrate/client/executor/wasmtime", "substrate/client/informant", @@ -223,8 +257,8 @@ members = [ "substrate/client/merkle-mountain-range", "substrate/client/merkle-mountain-range/rpc", "substrate/client/mixnet", - "substrate/client/network-gossip", "substrate/client/network", + "substrate/client/network-gossip", "substrate/client/network/bitswap", "substrate/client/network/common", "substrate/client/network/light", @@ -234,10 +268,10 @@ members = [ "substrate/client/network/transactions", "substrate/client/offchain", "substrate/client/proposer-metrics", + "substrate/client/rpc", "substrate/client/rpc-api", "substrate/client/rpc-servers", "substrate/client/rpc-spec-v2", - "substrate/client/rpc", "substrate/client/service", "substrate/client/service/test", "substrate/client/state-db", @@ -251,6 +285,9 @@ members = [ "substrate/client/transaction-pool", "substrate/client/transaction-pool/api", "substrate/client/utils", + "substrate/deprecated/hashing", + "substrate/deprecated/hashing/proc-macro", + "substrate/frame", "substrate/frame/alliance", "substrate/frame/asset-conversion", "substrate/frame/asset-rate", @@ -264,8 +301,8 @@ members = [ "substrate/frame/bags-list/fuzzer", "substrate/frame/bags-list/remote-tests", "substrate/frame/balances", - "substrate/frame/beefy-mmr", "substrate/frame/beefy", + "substrate/frame/beefy-mmr", "substrate/frame/benchmarking", "substrate/frame/benchmarking/pov", "substrate/frame/bounties", @@ -273,8 +310,10 @@ members = [ "substrate/frame/child-bounties", "substrate/frame/collective", "substrate/frame/contracts", - "substrate/frame/contracts/primitives", + "substrate/frame/contracts/fixtures", + "substrate/frame/contracts/mock-network", "substrate/frame/contracts/proc-macro", + "substrate/frame/contracts/uapi", "substrate/frame/conviction-voting", "substrate/frame/core-fellowship", "substrate/frame/democracy", @@ -289,9 +328,12 @@ members = [ "substrate/frame/examples/basic", "substrate/frame/examples/default-config", "substrate/frame/examples/dev-mode", + "substrate/frame/examples/frame-crate", "substrate/frame/examples/kitchensink", "substrate/frame/examples/offchain-worker", + "substrate/frame/examples/single-block-migrations", "substrate/frame/examples/split", + "substrate/frame/examples/tasks", "substrate/frame/executive", "substrate/frame/fast-unstake", "substrate/frame/glutton", @@ -304,12 +346,12 @@ members = [ "substrate/frame/membership", "substrate/frame/merkle-mountain-range", "substrate/frame/message-queue", + "substrate/frame/migrations", "substrate/frame/mixnet", "substrate/frame/multisig", "substrate/frame/nft-fractionalization", "substrate/frame/nfts", "substrate/frame/nfts/runtime-api", - "substrate/frame/nicks", "substrate/frame/nis", "substrate/frame/node-authorization", "substrate/frame/nomination-pools", @@ -321,6 +363,7 @@ members = [ "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", "substrate/frame/paged-list/fuzzer", + "substrate/frame/parameters", "substrate/frame/preimage", "substrate/frame/proxy", "substrate/frame/ranked-collective", @@ -331,6 +374,7 @@ members = [ "substrate/frame/root-testing", "substrate/frame/safe-mode", "substrate/frame/salary", + "substrate/frame/sassafras", "substrate/frame/scheduler", "substrate/frame/scored-pool", "substrate/frame/session", @@ -350,7 +394,6 @@ members = [ "substrate/frame/support/test", "substrate/frame/support/test/compile_pass", "substrate/frame/support/test/pallet", - "substrate/frame/support/test/stg_frame_crate/frame", "substrate/frame/support/test/stg_frame_crate", "substrate/frame/system", "substrate/frame/system/benchmarking", @@ -362,6 +405,7 @@ members = [ "substrate/frame/transaction-payment/asset-tx-payment", "substrate/frame/transaction-payment/rpc", "substrate/frame/transaction-payment/rpc/runtime-api", + "substrate/frame/transaction-payment/skip-feeless-payment", "substrate/frame/transaction-storage", "substrate/frame/treasury", "substrate/frame/try-runtime", @@ -389,9 +433,10 @@ members = [ "substrate/primitives/consensus/sassafras", "substrate/primitives/consensus/slots", "substrate/primitives/core", - "substrate/primitives/core/hashing", - "substrate/primitives/core/hashing/proc-macro", + "substrate/primitives/core/fuzz", "substrate/primitives/crypto/ec-utils", + "substrate/primitives/crypto/hashing", + "substrate/primitives/crypto/hashing/proc-macro", "substrate/primitives/database", "substrate/primitives/debug-derive", "substrate/primitives/externalities", @@ -409,12 +454,12 @@ members = [ "substrate/primitives/offchain", "substrate/primitives/panic-handler", "substrate/primitives/rpc", + "substrate/primitives/runtime", "substrate/primitives/runtime-interface", "substrate/primitives/runtime-interface/proc-macro", - "substrate/primitives/runtime-interface/test-wasm-deprecated", - "substrate/primitives/runtime-interface/test-wasm", "substrate/primitives/runtime-interface/test", - "substrate/primitives/runtime", + "substrate/primitives/runtime-interface/test-wasm", + "substrate/primitives/runtime-interface/test-wasm-deprecated", "substrate/primitives/session", "substrate/primitives/staking", "substrate/primitives/state-machine", @@ -452,17 +497,72 @@ members = [ "substrate/utils/frame/rpc/system", "substrate/utils/frame/try-runtime/cli", "substrate/utils/prometheus", + "substrate/utils/substrate-bip39", "substrate/utils/wasm-builder", + + "templates/minimal/node", + "templates/minimal/pallets/template", + "templates/minimal/runtime", + + "templates/solochain/node", + "templates/solochain/pallets/template", + "templates/solochain/runtime", + + "templates/parachain/node", + "templates/parachain/pallets/template", + "templates/parachain/runtime", ] -default-members = [ "polkadot" ] +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 + +[workspace.dependencies] +polkavm = "0.9.3" +polkavm-linker = "0.9.2" +polkavm-derive = "0.9.1" +log = { version = "0.4.21", default-features = false } +quote = { version = "1.0.33" } +serde = { version = "1.0.197", default-features = false } +serde-big-array = { version = "0.3.2" } +serde_derive = { version = "1.0.117" } +serde_json = { version = "1.0.114", default-features = false } +serde_yaml = { version = "0.9" } +syn = { version = "2.0.53" } +thiserror = { version = "1.0.48" } [profile.release] # Polkadot runtime requires unwinding. panic = "unwind" opt-level = 3 -# make sure dev builds with backtrace do -# not slow us down +# make sure dev builds with backtrace do not slow us down [profile.dev.package.backtrace] inherits = "release" @@ -516,6 +616,7 @@ num-bigint = { opt-level = 3 } parking_lot = { opt-level = 3 } parking_lot_core = { opt-level = 3 } percent-encoding = { opt-level = 3 } +polkavm-linker = { opt-level = 3 } primitive-types = { opt-level = 3 } reed-solomon-novelpoly = { opt-level = 3 } ring = { opt-level = 3 } diff --git a/README.md b/README.md index 56b3481bafc038f7d3dd0606558e9e37243dcd53..63743a456f4c8f8561bbeee8c59d63b88d352285 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ way. The Polkadot SDK comprises three main pieces of software: [![Polkadot-license](https://img.shields.io/badge/License-GPL3-blue)](./polkadot/LICENSE) Implementation of a node for the https://polkadot.network in Rust, using the Substrate framework. This directory -currently contains runtimes for the Polkadot, Kusama, Westend, and Rococo networks. In the future, these will be -relocated to the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository. +currently contains runtimes for the Westend and Rococo test networks. Polkadot, Kusama and their system chain runtimes +are located in the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository maintained by +[the Polkadot Technical Fellowship](https://polkadot-fellows.github.io/dashboard/#/overview). ## [Substrate](./substrate/) - [![SubstrateRustDocs](https://img.shields.io/badge/Rust_Docs-Substrate-24CC85?logo=rust)](https://paritytech.github.io/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,11 +31,31 @@ 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. +## Releases + +> [!NOTE] +> Our release process is still Work-In-Progress and may not yet reflect the aspired outline here. + +The Polkadot-SDK has two release channels: `stable` and `nightly`. Production software is advised to only use `stable`. +`nightly` is meant for tinkerers to try out the latest features. The detailed release process is described in +[RELEASE.md](docs/RELEASE.md). + +### Stable + +`stable` releases have a support duration of **three months**. In this period, the release will not have any breaking +changes. It will receive bug fixes, security fixes, performance fixes and new non-breaking features on a **two week** +cadence. + +### Nightly + +`nightly` releases are released every night from the `master` branch, potentially with breaking changes. They have +pre-release version numbers in the format `major.0.0-nightlyYYMMDD`. + ## Upstream Dependencies Below are the primary upstream dependencies utilized in this project: @@ -46,12 +67,12 @@ Below are the primary upstream dependencies utilized in this project: ## Security -The security policy and procedures can be found in [docs/SECURITY.md](./docs/SECURITY.md). +The security policy and procedures can be found in [docs/contributor/SECURITY.md](./docs/contributor/SECURITY.md). ## Contributing & Code of Conduct -Ensure you follow our [contribution guidelines](./docs/CONTRIBUTING.md). In every interaction and contribution, this -project adheres to the [Contributor Covenant Code of Conduct](./docs/CODE_OF_CONDUCT.md). +Ensure you follow our [contribution guidelines](./docs/contributor/CONTRIBUTING.md). In every interaction and +contribution, this project adheres to the [Contributor Covenant Code of Conduct](./docs/contributor/CODE_OF_CONDUCT.md). ## Additional Resources diff --git a/bridges/README.md b/bridges/README.md index da46fe67d924acb2afffcf971bacb60b560f0cd5..a2ce213d2541c346361eb28125a06e3079e1c269 100644 --- a/bridges/README.md +++ b/bridges/README.md @@ -68,7 +68,7 @@ For example, consider the case below where we want to bridge two Substrate based ``` +---------------+ +---------------+ | | | | -| Rialto | | Millau | +| Rococo | | Westend | | | | | +-------+-------+ +-------+-------+ ^ ^ @@ -79,9 +79,9 @@ For example, consider the case below where we want to bridge two Substrate based +---------------+ ``` -The Millau chain must be able to accept Rialto headers and verify their integrity. It does this by using a runtime +The Rococo chain must be able to accept Westend headers and verify their integrity. It does this by using a runtime module designed to track GRANDPA finality. Since two blockchains can't interact directly they need an external service, -called a relayer, to communicate. The relayer will subscribe to new Rialto headers via RPC and submit them to the Millau +called a relayer, to communicate. The relayer will subscribe to new Rococo headers via RPC and submit them to the Westend chain for verification. Take a look at [Bridge High Level Documentation](./docs/high-level-overview.md) for more in-depth description of the @@ -94,164 +94,23 @@ Here's an overview of how the project is laid out. The main bits are the `bin`, messages between chains. ``` -├── bin // Node and Runtime for the various Substrate chains -│ └── ... -├── deployments // Useful tools for deploying test networks +├── modules // Substrate Runtime Modules (a.k.a Pallets) +│ ├── beefy // On-Chain BEEFY Light Client (in progress) +│ ├── grandpa // On-Chain GRANDPA Light Client +│ ├── messages // Cross Chain Message Passing +│ ├── parachains // On-Chain Parachains Light Client +│ ├── relayers // Relayer Rewards Registry +│ ├── xcm-bridge-hub // Multiple Dynamic Bridges Support +│ ├── xcm-bridge-hub-router // XCM Router that may be used to Connect to XCM Bridge Hub +├── primitives // Code shared between modules, runtimes, and relays │ └── ... -├── modules // Substrate Runtime Modules (a.k.a Pallets) -│ ├── beefy // On-Chain BEEFY Light Client (in progress) -│ ├── grandpa // On-Chain GRANDPA Light Client -│ ├── messages // Cross Chain Message Passing -│ ├── parachains // On-Chain Parachains Light Client -│ ├── relayers // Relayer rewards registry +├── relays // Application for sending finality proofs and messages between chains │ └── ... -├── primitives // Code shared between modules, runtimes, and relays -│ └── ... -├── relays // Application for sending finality proofs and messages between chains -│ └── ... -└── scripts // Useful development and maintenance scripts +└── scripts // Useful development and maintenance scripts ``` ## Running the Bridge -To run the Bridge you need to be able to connect the bridge relay node to the RPC interface of nodes on each side of the -bridge (source and target chain). - -There are 2 ways to run the bridge, described below: - -- building & running from source: with this option, you'll be able to run the bridge between two standalone chains that -are running GRANDPA finality gadget to achieve finality; - -- running a Docker Compose setup: this is a recommended option, where you'll see bridges with parachains, complex relays -and more. - -### Using the Source - -First you'll need to build the bridge nodes and relay. This can be done as follows: - -```bash -# In `parity-bridges-common` folder -cargo build -p rialto-bridge-node -cargo build -p millau-bridge-node -cargo build -p substrate-relay -``` - -### Running a Dev network - -We will launch a dev network to demonstrate how to relay a message between two Substrate based chains (named Rialto and -Millau). - -To do this we will need two nodes, two relayers which will relay headers, and two relayers which will relay messages. - -#### Running from local scripts - -To run a simple dev network you can use the scripts located in the [`deployments/local-scripts` -folder](./deployments/local-scripts). - -First, we must run the two Substrate nodes. - -```bash -# In `parity-bridges-common` folder -./deployments/local-scripts/run-rialto-node.sh -./deployments/local-scripts/run-millau-node.sh -``` - -After the nodes are up we can run the header relayers. - -```bash -./deployments/local-scripts/relay-millau-to-rialto.sh -./deployments/local-scripts/relay-rialto-to-millau.sh -``` - -At this point you should see the relayer submitting headers from the Millau Substrate chain to the Rialto Substrate -chain. - -``` -# Header Relayer Logs -[Millau_to_Rialto_Sync] [date] DEBUG bridge Going to submit finality proof of Millau header #147 to Rialto -[...] [date] INFO bridge Synced 147 of 147 headers -[...] [date] DEBUG bridge Going to submit finality proof of Millau header #148 to Rialto -[...] [date] INFO bridge Synced 148 of 149 headers -``` - -Finally, we can run the message relayers. - -```bash -./deployments/local-scripts/relay-messages-millau-to-rialto.sh -./deployments/local-scripts/relay-messages-rialto-to-millau.sh -``` - -You will also see the message lane relayers listening for new messages. - -``` -# Message Relayer Logs -[Millau_to_Rialto_MessageLane_00000000] [date] DEBUG bridge Asking Millau::ReceivingConfirmationsDelivery about best message nonces -[...] [date] INFO bridge Synced Some(2) of Some(3) nonces in Millau::MessagesDelivery -> Rialto::MessagesDelivery race -[...] [date] DEBUG bridge Asking Millau::MessagesDelivery about message nonces -[...] [date] DEBUG bridge Received best nonces from Millau::ReceivingConfirmationsDelivery: TargetClientNonces { - latest_nonce: 0, nonces_data: () } -[...] [date] DEBUG bridge Asking Millau::ReceivingConfirmationsDelivery about finalized message nonces -[...] [date] DEBUG bridge Received finalized nonces from Millau::ReceivingConfirmationsDelivery: TargetClientNonces { - latest_nonce: 0, nonces_data: () } -[...] [date] DEBUG bridge Received nonces from Millau::MessagesDelivery: SourceClientNonces { new_nonces: {}, confirmed_nonce: Some(0) } -[...] [date] DEBUG bridge Asking Millau node about its state -[...] [date] DEBUG bridge Received state from Millau node: ClientState { best_self: HeaderId(1593, 0xacac***), best_finalized_self: - HeaderId(1590, 0x0be81d...), best_finalized_peer_at_best_self: HeaderId(0, 0xdcdd89...) } -``` - -To send a message see the ["How to send a message" section](#how-to-send-a-message). - -### How to send a message - -In this section we'll show you how to quickly send a bridge message. The message is just an encoded XCM `Trap(43)` -message. - -```bash -# In `parity-bridges-common` folder -./scripts/send-message-from-millau-rialto.sh -``` - -After sending a message you will see the following logs showing a message was successfully sent: - -``` -INFO bridge Sending message to Rialto. Size: 11. -TRACE bridge Sent transaction to Millau node: 0x5e68... -``` - -And at the Rialto node logs you'll something like this: - -``` -... runtime::bridge-messages: Received messages: total=1, valid=1. Weight used: Weight(ref_time: 1215065371, proof_size: - 48559)/Weight(ref_time: 1215065371, proof_size: 54703). -``` - -It means that the message has been delivered and dispatched. Message may be dispatched with an error, though - the goal -of our test bridge is to ensure that messages are successfully delivered and all involved components are working. - -## Full Network Docker Compose Setup - -For a more sophisticated deployment which includes bidirectional header sync, message passing, monitoring dashboards, -etc. see the [Deployments README](./deployments/README.md). - -You should note that you can find images for all the bridge components published on [Docker -Hub](https://hub.docker.com/u/paritytech). - -To run a Rialto node for example, you can use the following command: - -```bash -docker run -p 30333:30333 -p 9933:9933 -p 9944:9944 \ - -it paritytech/rialto-bridge-node --dev --tmp \ - --rpc-cors=all --unsafe-rpc-external -``` - -## Community - -Main hangout for the community is [Element](https://element.io/) (formerly Riot). Element is a chat server like, for -example, Discord. Most discussions around Polkadot and Substrate happen in various Element "rooms" (channels). So, -joining Element might be a good idea, anyway. - -If you are interested in information exchange and development of Polkadot related bridges please feel free to join the -[Polkadot Bridges](https://app.element.io/#/room/#bridges:web3.foundation) Element channel. - -The [Substrate Technical](https://app.element.io/#/room/#substrate-technical:matrix.org) Element channel is most suited -for discussions regarding Substrate itself. +Apart from live Rococo <> Westend bridge, you may spin up local networks and test see how it works locally. More +details may be found in +[this document](https://github.com/paritytech/polkadot-sdk/tree/master//cumulus/parachains/runtimes/bridge-hubs/README.md). diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 0ccf30987e822be4bcc739c2806246caafe28ef0..fac88b20ca57901d4116e147bd9363a41ff35e36 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "bridge-runtime-common" -version = "0.1.0" +version = "0.7.0" description = "Common types and functions that may be used by substrate-based runtimes of all bridged chains" authors.workspace = true 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 } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } @@ -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 } @@ -50,7 +54,7 @@ bp-test-utils = { path = "../../primitives/test-utils" } pallet-balances = { path = "../../../substrate/frame/balances" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-messages/std", @@ -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", @@ -92,4 +97,4 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", ] -integrity-test = [ "static_assertions" ] +integrity-test = ["static_assertions"] diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs index ae6f40b142145dc265a69e8c78ef313f5ce9340f..2722f6f1c6d14f09ab215f8f020f2c449eda4d4b 100644 --- a/bridges/bin/runtime-common/src/lib.rs +++ b/bridges/bin/runtime-common/src/lib.rs @@ -16,13 +16,13 @@ //! Common types/functions that may be used by runtimes of all bridged chains. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use crate::messages_call_ext::MessagesCallSubType; use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType; use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype; use sp_runtime::transaction_validity::TransactionValidity; -use xcm::v3::NetworkId; pub mod messages; pub mod messages_api; @@ -92,8 +92,8 @@ where /// ```nocompile /// generate_bridge_reject_obsolete_headers_and_messages!{ /// Call, AccountId -/// BridgeRialtoGrandpa, BridgeWestendGrandpa, -/// BridgeRialtoParachains +/// BridgeRococoGrandpa, BridgeRococoMessages, +/// BridgeRococoParachains /// } /// ``` /// @@ -147,42 +147,6 @@ macro_rules! generate_bridge_reject_obsolete_headers_and_messages { }; } -/// A mapping over `NetworkId`. -/// Since `NetworkId` doesn't include `Millau`, `Rialto` and `RialtoParachain`, we create some -/// synthetic associations between these chains and `NetworkId` chains. -pub enum CustomNetworkId { - /// The Millau network ID, associated with Kusama. - Millau, - /// The Rialto network ID, associated with Polkadot. - Rialto, - /// The RialtoParachain network ID, associated with Westend. - RialtoParachain, -} - -impl TryFrom for CustomNetworkId { - type Error = (); - - fn try_from(chain: bp_runtime::ChainId) -> Result { - Ok(match chain { - bp_runtime::MILLAU_CHAIN_ID => Self::Millau, - bp_runtime::RIALTO_CHAIN_ID => Self::Rialto, - bp_runtime::RIALTO_PARACHAIN_CHAIN_ID => Self::RialtoParachain, - _ => return Err(()), - }) - } -} - -impl CustomNetworkId { - /// Converts self to XCM' network id. - pub const fn as_network_id(&self) -> NetworkId { - match *self { - CustomNetworkId::Millau => NetworkId::Kusama, - CustomNetworkId::Rialto => NetworkId::Polkadot, - CustomNetworkId::RialtoParachain => NetworkId::Westend, - } - } -} - #[cfg(test)] mod tests { use crate::BridgeRuntimeFilterCall; diff --git a/bridges/bin/runtime-common/src/messages.rs b/bridges/bin/runtime-common/src/messages.rs index ac66adae6614b168855b285b4ef4f3cd74ecb068..4aca53f3b98361b1a5f7d5dc89dc72ec0bc1323c 100644 --- a/bridges/bin/runtime-common/src/messages.rs +++ b/bridges/bin/runtime-common/src/messages.rs @@ -24,7 +24,7 @@ pub use bp_runtime::{RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvid use bp_header_chain::HeaderChain; use bp_messages::{ - source_chain::{LaneMessageVerifier, TargetHeaderChain}, + source_chain::TargetHeaderChain, target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain}, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, @@ -120,42 +120,6 @@ pub mod source { pub type ParsedMessagesDeliveryProofFromBridgedChain = (LaneId, InboundLaneData>>); - /// Message verifier that is doing all basic checks. - /// - /// This verifier assumes following: - /// - /// - all message lanes are equivalent, so all checks are the same; - /// - /// Following checks are made: - /// - /// - message is rejected if its lane is currently blocked; - /// - message is rejected if there are too many pending (undelivered) messages at the outbound - /// lane; - /// - check that the sender has rights to dispatch the call on target chain using provided - /// dispatch origin; - /// - check that the sender has paid enough funds for both message delivery and dispatch. - #[derive(RuntimeDebug)] - pub struct FromThisChainMessageVerifier(PhantomData); - - impl LaneMessageVerifier for FromThisChainMessageVerifier - where - B: MessageBridge, - { - fn verify_message( - _lane: &LaneId, - _lane_outbound_data: &OutboundLaneData, - _payload: &FromThisChainMessagePayload, - ) -> Result<(), VerificationError> { - // IMPORTANT: any error that is returned here is fatal for the bridge, because - // this code is executed at the bridge hub and message sender actually lives - // at some sibling parachain. So we are failing **after** the message has been - // sent and we can't report it back to sender (unless error report mechanism is - // embedded into message and its dispatcher). - - Ok(()) - } - } - /// Return maximal message size of This -> Bridged chain message. pub fn maximal_message_size() -> u32 { super::target::maximal_incoming_message_size( @@ -185,8 +149,7 @@ pub mod source { /// Do basic Bridged-chain specific verification of This -> Bridged chain message. /// /// Ok result from this function means that the delivery transaction with this message - /// may be 'mined' by the target chain. But the lane may have its own checks (e.g. fee - /// check) that would reject message (see `FromThisChainMessageVerifier`). + /// may be 'mined' by the target chain. pub fn verify_chain_message( payload: &FromThisChainMessagePayload, ) -> Result<(), VerificationError> { diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs index d80a88f1068c8975426105f881d963e1c3053ee5..0c7a9ad1a83d6a83e0c9fe1f5e77ba2c4cefc17d 100644 --- a/bridges/bin/runtime-common/src/messages_benchmarking.rs +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -22,25 +22,23 @@ use crate::{ messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, - AccountIdOf, BridgedChain, HashOf, HasherOf, MessageBridge, ThisChain, + AccountIdOf, BridgedChain, HashOf, MessageBridge, ThisChain, }, messages_generation::{ - encode_all_messages, encode_lane_data, grow_trie_leaf_value, prepare_messages_storage_proof, + encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, + prepare_messages_storage_proof, }, }; -use bp_messages::{storage_keys, MessagePayload}; +use bp_messages::MessagePayload; use bp_polkadot_core::parachains::ParaHash; -use bp_runtime::{ - record_all_trie_keys, Chain, Parachain, RawStorageProof, StorageProofSize, UnderlyingChainOf, -}; +use bp_runtime::{Chain, Parachain, StorageProofSize, UnderlyingChainOf}; use codec::Encode; use frame_support::weights::Weight; use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams}; use sp_runtime::traits::{Header, Zero}; use sp_std::prelude::*; -use sp_trie::{trie_types::TrieDBMutBuilderV1, LayoutV1, MemoryDB, TrieMut}; -use xcm::v3::prelude::*; +use xcm::latest::prelude::*; /// Prepare inbound bridge message according to given message proof parameters. fn prepare_inbound_message( @@ -172,7 +170,11 @@ where { // prepare storage proof let lane = params.lane; - let (state_root, storage_proof) = prepare_message_delivery_proof::(params); + let (state_root, storage_proof) = prepare_message_delivery_storage_proof::( + params.lane, + params.inbound_lane_data, + params.size, + ); // update runtime storage let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); @@ -200,7 +202,11 @@ where { // prepare storage proof let lane = params.lane; - let (state_root, storage_proof) = prepare_message_delivery_proof::(params); + let (state_root, storage_proof) = prepare_message_delivery_storage_proof::( + params.lane, + params.inbound_lane_data, + params.size, + ); // update runtime storage let (_, bridged_header_hash) = @@ -213,36 +219,6 @@ where } } -/// Prepare in-memory message delivery proof, without inserting anything to the runtime storage. -fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams>>, -) -> (HashOf>, RawStorageProof) -where - B: MessageBridge, -{ - // prepare Bridged chain storage with inbound lane state - let storage_key = - storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, ¶ms.lane).0; - let mut root = Default::default(); - let mut mdb = MemoryDB::default(); - { - let mut trie = - TrieDBMutBuilderV1::>>::new(&mut mdb, &mut root).build(); - let inbound_lane_data = - grow_trie_leaf_value(params.inbound_lane_data.encode(), params.size); - trie.insert(&storage_key, &inbound_lane_data) - .map_err(|_| "TrieMut::insert has failed") - .expect("TrieMut::insert should not fail in benchmarks"); - } - - // generate storage proof to be delivered to This chain - let storage_proof = record_all_trie_keys::>>, _>(&mdb, &root) - .map_err(|_| "record_all_trie_keys has failed") - .expect("record_all_trie_keys should not fail in benchmarks"); - - (root, storage_proof) -} - /// Insert header to the bridge GRANDPA pallet. pub(crate) fn insert_header_to_grandpa_pallet( state_root: bp_runtime::HashOf, @@ -290,19 +266,19 @@ where /// Returns callback which generates `BridgeMessage` from Polkadot XCM builder based on /// `expected_message_size` for benchmark. pub fn generate_xcm_builder_bridge_message_sample( - destination: InteriorMultiLocation, + destination: InteriorLocation, ) -> impl Fn(usize) -> MessagePayload { move |expected_message_size| -> MessagePayload { // For XCM bridge hubs, it is the message that // will be pushed further to some XCM queue (XCMP/UMP) - let location = xcm::VersionedInteriorMultiLocation::V3(destination); + let location = xcm::VersionedInteriorLocation::V4(destination.clone()); let location_encoded_size = location.encoded_size(); // we don't need to be super-precise with `expected_size` here let xcm_size = expected_message_size.saturating_sub(location_encoded_size); let xcm_data_size = xcm_size.saturating_sub( // minus empty instruction size - xcm::v3::Instruction::<()>::ExpectPallet { + Instruction::<()>::ExpectPallet { index: 0, name: vec![], module_name: vec![], @@ -318,8 +294,8 @@ pub fn generate_xcm_builder_bridge_message_sample( expected_message_size, location_encoded_size, xcm_size, xcm_data_size, ); - let xcm = xcm::VersionedXcm::<()>::V3( - vec![xcm::v3::Instruction::<()>::ExpectPallet { + let xcm = xcm::VersionedXcm::<()>::V4( + vec![Instruction::<()>::ExpectPallet { index: 0, name: vec![42; xcm_data_size], module_name: vec![], diff --git a/bridges/bin/runtime-common/src/messages_call_ext.rs b/bridges/bin/runtime-common/src/messages_call_ext.rs index 5303fcb7ba030fa3f00a74c817d97537243f0e24..fb07f7b6dd69110918af23b227708e226bede625 100644 --- a/bridges/bin/runtime-common/src/messages_call_ext.rs +++ b/bridges/bin/runtime-common/src/messages_call_ext.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Signed extension for the `pallet-bridge-messages` that is able to reject obsolete +//! (and some other invalid) transactions. + use crate::messages::{ source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, }; @@ -116,7 +119,9 @@ impl ReceiveMessagesDeliveryProofInfo { /// which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] pub enum CallInfo { + /// Messages delivery call info. ReceiveMessagesProof(ReceiveMessagesProofInfo), + /// Messages delivery confirmation call info. ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), } @@ -132,7 +137,7 @@ impl CallInfo { /// Helper struct that provides methods for working with a call supported by `CallInfo`. pub struct CallHelper, I: 'static> { - pub _phantom_data: sp_std::marker::PhantomData<(T, I)>, + _phantom_data: sp_std::marker::PhantomData<(T, I)>, } impl, I: 'static> CallHelper { diff --git a/bridges/bin/runtime-common/src/messages_generation.rs b/bridges/bin/runtime-common/src/messages_generation.rs index 3c550a9bd0fd546ba90753b8871e049e65a702e3..c37aaa5d4d5378a1b76507e017c73aec3c7aabbd 100644 --- a/bridges/bin/runtime-common/src/messages_generation.rs +++ b/bridges/bin/runtime-common/src/messages_generation.rs @@ -16,10 +16,11 @@ //! Helpers for generating message storage proofs, that are used by tests and by benchmarks. -use crate::messages::{BridgedChain, HashOf, HasherOf, MessageBridge}; +use crate::messages::{AccountIdOf, BridgedChain, HashOf, HasherOf, MessageBridge, ThisChain}; use bp_messages::{ - storage_keys, LaneId, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, + storage_keys, InboundLaneData, LaneId, MessageKey, MessageNonce, MessagePayload, + OutboundLaneData, }; use bp_runtime::{record_all_trie_keys, RawStorageProof, StorageProofSize}; use codec::Encode; @@ -104,6 +105,38 @@ where (root, storage_proof) } +/// Prepare storage proof of given messages delivery. +/// +/// Returns state trie root and nodes with prepared messages. +pub fn prepare_message_delivery_storage_proof( + lane: LaneId, + inbound_lane_data: InboundLaneData>>, + size: StorageProofSize, +) -> (HashOf>, RawStorageProof) +where + B: MessageBridge, +{ + // prepare Bridged chain storage with inbound lane state + let storage_key = storage_keys::inbound_lane_data_key(B::BRIDGED_MESSAGES_PALLET_NAME, &lane).0; + let mut root = Default::default(); + let mut mdb = MemoryDB::default(); + { + let mut trie = + TrieDBMutBuilderV1::>>::new(&mut mdb, &mut root).build(); + let inbound_lane_data = grow_trie_leaf_value(inbound_lane_data.encode(), size); + trie.insert(&storage_key, &inbound_lane_data) + .map_err(|_| "TrieMut::insert has failed") + .expect("TrieMut::insert should not fail in benchmarks"); + } + + // generate storage proof to be delivered to This chain + let storage_proof = record_all_trie_keys::>>, _>(&mdb, &root) + .map_err(|_| "record_all_trie_keys has failed") + .expect("record_all_trie_keys should not fail in benchmarks"); + + (root, storage_proof) +} + /// Add extra data to the trie leaf value so that it'll be of given size. pub fn grow_trie_leaf_value(mut value: Vec, size: StorageProofSize) -> Vec { match size { diff --git a/bridges/bin/runtime-common/src/messages_xcm_extension.rs b/bridges/bin/runtime-common/src/messages_xcm_extension.rs index 77c23db3b2ba2d01aef0d2c45a20377eaf8ea129..e3da6155f08a198d5469adbfc64e40213eddf8eb 100644 --- a/bridges/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bridges/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,32 +22,32 @@ //! `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}; +use xcm_builder::{DispatchBlob, DispatchBlobError}; -/// Plain "XCM" payload, which we transfer through bridge -pub type XcmAsPlainPayload = sp_std::prelude::Vec; - -/// Message dispatch result type for single message +/// Message dispatch result type for single message. #[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)] pub enum XcmBlobMessageDispatchResult { + /// We've been unable to decode message payload. InvalidPayload, + /// Message has been dispatched. Dispatched, + /// Message has **NOT** been dispatched because of given error. NotDispatched(#[codec(skip)] Option), } @@ -123,16 +123,17 @@ 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, + pub location: Location, /// Message lane, used by the sending chain. pub lane: LaneId, } impl SenderAndLane { /// Create new object using provided location and lane. - pub fn new(location: MultiLocation, lane: LaneId) -> Self { + pub fn new(location: Location, lane: LaneId) -> Self { SenderAndLane { location, lane } } } @@ -144,8 +145,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 +165,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, - ); } } @@ -321,7 +291,7 @@ impl LocalXcmQueueManager { /// Send congested signal to the `sending_chain_location`. fn send_congested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { if let Some(msg) = H::CongestedMessage::get() { - send_xcm::(sender_and_lane.location, msg)?; + send_xcm::(sender_and_lane.location.clone(), msg)?; OutboundLanesCongestedSignals::::insert( sender_and_lane.lane, true, @@ -333,7 +303,7 @@ impl LocalXcmQueueManager { /// Send `uncongested` signal to the `sending_chain_location`. fn send_uncongested_signal(sender_and_lane: &SenderAndLane) -> Result<(), SendError> { if let Some(msg) = H::UncongestedMessage::get() { - send_xcm::(sender_and_lane.location, msg)?; + send_xcm::(sender_and_lane.location.clone(), msg)?; OutboundLanesCongestedSignals::::remove( sender_and_lane.lane, ); @@ -342,6 +312,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: &Location) -> 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::*; @@ -353,9 +345,12 @@ mod tests { parameter_types! { pub TestSenderAndLane: SenderAndLane = SenderAndLane { - location: MultiLocation::new(1, X1(Parachain(1000))), + location: Location::new(1, [Parachain(1000)]), lane: TEST_LANE_ID, }; + pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![ + (TestSenderAndLane::get(), (NetworkId::ByGenesis([0; 32]), InteriorLocation::Here)) + ]; pub DummyXcmMessage: Xcm<()> = Xcm::new(); } @@ -371,7 +366,7 @@ mod tests { type Ticket = (); fn validate( - _destination: &mut Option, + _destination: &mut Option, _message: &mut Option>, ) -> SendResult { Ok(((), Default::default())) @@ -389,37 +384,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 +429,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 +440,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 45ef790d74482b01948a74cdb0a53c332aa41896..deee4524e8586d45c01f08126e84b9c619cfeba2 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -14,19 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! A mock runtime for testing different stuff in the crate. We've been using Millau -//! runtime for that before, but it has two drawbacks: -//! -//! - circular dependencies between this crate and Millau runtime; -//! -//! - we can't use (e.g. as git subtree or by copying) this crate in repo without Millau. +//! A mock runtime for testing different stuff in the crate. #![cfg(test)] use crate::messages::{ source::{ FromThisChainMaximalOutboundPayloadSize, FromThisChainMessagePayload, - FromThisChainMessageVerifier, TargetHeaderChainAdapter, + TargetHeaderChainAdapter, }, target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter}, BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages, @@ -44,13 +39,13 @@ use bp_runtime::{ }; use codec::{Decode, Encode}; use frame_support::{ - parameter_types, + derive_impl, parameter_types, weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight}, }; use pallet_transaction_payment::Multiplier; use sp_runtime::{ testing::H256, - traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8, IdentityLookup}, + traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8}, FixedPointNumber, Perquintill, }; @@ -146,30 +141,14 @@ parameter_types! { pub const ReserveId: [u8; 8] = *b"brdgrlrs"; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; type Hash = ThisChainHash; type Hashing = ThisChainHasher; type AccountId = ThisChainAccountId; - type Lookup = IdentityLookup; type Block = ThisChainBlock; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU32<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); - type DbWeight = DbWeight; - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type BlockHashCount = ConstU32<250>; } impl pallet_utility::Config for TestRuntime { @@ -179,22 +158,13 @@ impl pallet_utility::Config for TestRuntime { type WeightInfo = (); } +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { - type Balance = ThisChainBalance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = (); - type MaxLocks = ConstU32<50>; - type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; - type RuntimeHoldReason = RuntimeHoldReason; - type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; + type AccountStore = System; } +#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for TestRuntime { type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; type OperationalFeeMultiplier = ConstU8<5>; @@ -244,7 +214,6 @@ impl pallet_bridge_messages::Config for TestRuntime { type DeliveryPayments = (); type TargetHeaderChain = TargetHeaderChainAdapter; - type LaneMessageVerifier = FromThisChainMessageVerifier; type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -346,6 +315,8 @@ impl From pub struct ThisUnderlyingChain; impl Chain for ThisUnderlyingChain { + const ID: ChainId = *b"tuch"; + type BlockNumber = ThisChainBlockNumber; type Hash = ThisChainHash; type Hasher = ThisChainHasher; @@ -386,6 +357,8 @@ pub struct BridgedUnderlyingParachain; pub struct BridgedChainCall; impl Chain for BridgedUnderlyingChain { + const ID: ChainId = *b"buch"; + type BlockNumber = BridgedChainBlockNumber; type Hash = BridgedChainHash; type Hasher = BridgedChainHasher; @@ -407,11 +380,13 @@ 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 { + const ID: ChainId = *b"bupc"; + type BlockNumber = BridgedChainBlockNumber; type Hash = BridgedChainHash; type Hasher = BridgedChainHasher; diff --git a/bridges/bin/runtime-common/src/parachains_benchmarking.rs b/bridges/bin/runtime-common/src/parachains_benchmarking.rs index 63dc78385e46ebb537f06e88a139efacaeeb3832..b3050b9ac0f3ccec617399d3eb91647dcab7eb3d 100644 --- a/bridges/bin/runtime-common/src/parachains_benchmarking.rs +++ b/bridges/bin/runtime-common/src/parachains_benchmarking.rs @@ -84,5 +84,5 @@ where let (relay_block_number, relay_block_hash) = insert_header_to_grandpa_pallet::(state_root); - (relay_block_number, relay_block_hash, ParaHeadsProof(proof), parachain_heads) + (relay_block_number, relay_block_hash, ParaHeadsProof { storage_proof: proof }, parachain_heads) } diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/priority_calculator.rs index fd10344812517bab17ad33e5d276a5627e01c0c0..a597fb9e2f49289360acfd7ee305b44eb7874a3e 100644 --- a/bridges/bin/runtime-common/src/priority_calculator.rs +++ b/bridges/bin/runtime-common/src/priority_calculator.rs @@ -27,6 +27,7 @@ use frame_support::traits::Get; use sp_runtime::transaction_validity::TransactionPriority; // reexport everything from `integrity_tests` module +#[allow(unused_imports)] pub use integrity_tests::*; /// Compute priority boost for message delivery transaction that delivers diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs index 6d8b2114808588a83571de6dc02a141cc146d2e3..bfcb82ad166c3a2a60891c1e18f2ad22e085cb1b 100644 --- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs @@ -116,7 +116,7 @@ where /// Refund calculator. pub trait RefundCalculator { - // The underlying integer type in which the refund is calculated. + /// The underlying integer type in which the refund is calculated. type Balance; /// Compute refund for given transaction. @@ -195,6 +195,19 @@ impl CallInfo { } } + /// Returns mutable reference to pre-dispatch `finality_target` sent to the + /// `SubmitFinalityProof` call. + #[cfg(test)] + fn submit_finality_proof_info_mut( + &mut self, + ) -> Option<&mut SubmitFinalityProofInfo> { + match *self { + Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info), + Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info), + _ => None, + } + } + /// Returns the pre-dispatch `SubmitParachainHeadsInfo`. fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> { match self { @@ -844,7 +857,7 @@ mod tests { use bp_parachains::{BestParaHeadHash, ParaInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; use bp_runtime::{BasicOperatingMode, HeaderId}; - use bp_test_utils::{make_default_justification, test_keyring}; + use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID}; use frame_support::{ assert_storage_noop, parameter_types, traits::{fungible::Mutate, ReservableCurrency}, @@ -929,7 +942,7 @@ mod tests { let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect(); let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default()); pallet_bridge_grandpa::CurrentAuthoritySet::::put( - StoredAuthoritySet::try_new(authorities, 0).unwrap(), + StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), ); pallet_bridge_grandpa::BestFinalized::::put(best_relay_header); @@ -977,6 +990,23 @@ mod tests { }) } + fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { + let relay_header = BridgedChainHeader::new( + relay_header_number, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let relay_justification = make_default_justification(&relay_header); + + RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof_ex { + finality_target: Box::new(relay_header), + justification: relay_justification, + current_set_id: TEST_GRANDPA_SET_ID, + }) + } + fn submit_parachain_head_call( parachain_head_at_relay_header_number: RelayBlockNumber, ) -> RuntimeCall { @@ -986,7 +1016,7 @@ mod tests { ParaId(TestParachain::get()), [parachain_head_at_relay_header_number as u8; 32].into(), )], - parachain_heads_proof: ParaHeadsProof(vec![]), + parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, }) } @@ -1059,6 +1089,18 @@ mod tests { }) } + fn relay_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn relay_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, best_message: MessageNonce, @@ -1071,6 +1113,18 @@ mod tests { }) } + fn relay_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_and_delivery_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1085,6 +1139,20 @@ mod tests { }) } + fn all_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn all_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1099,12 +1167,27 @@ mod tests { }) } + fn all_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1128,12 +1211,20 @@ mod tests { } } + fn all_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1153,12 +1244,20 @@ mod tests { } } + fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1177,12 +1276,20 @@ mod tests { } } + fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1197,6 +1304,13 @@ mod tests { } } + fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn parachain_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), @@ -1393,6 +1507,10 @@ mod tests { run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Default::default()), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Default::default()), + ); // message confirmation validation is passing assert_eq!( run_validate_ignore_priority(message_confirmation_call(200)), @@ -1410,6 +1528,12 @@ mod tests { )), Ok(Default::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(Default::default()), + ); }); } @@ -1500,12 +1624,24 @@ mod tests { run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); assert_eq!( run_validate_ignore_priority(all_finality_and_confirmation_batch_call( 200, 200, 200 )), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); }); } @@ -1518,11 +1654,19 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -1535,10 +1679,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(100, 200)), @@ -1560,19 +1712,35 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 100)), @@ -1609,10 +1777,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); }); } @@ -1631,10 +1807,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1662,10 +1846,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1696,10 +1888,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Some(all_finality_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_pre_dispatch_data_ex())), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Ok(Some(all_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_confirmation_pre_dispatch_data_ex())), + ); }); } @@ -1732,7 +1932,7 @@ mod tests { (ParaId(TestParachain::get()), [1u8; 32].into()), (ParaId(TestParachain::get() + 1), [1u8; 32].into()), ], - parachain_heads_proof: ParaHeadsProof(vec![]), + parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] }, }), message_delivery_call(200), ], @@ -2126,6 +2326,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // relay + parachain + message confirmation calls batch is ignored assert_eq!( @@ -2134,6 +2340,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // parachain + message delivery call batch is ignored assert_eq!( @@ -2158,6 +2370,12 @@ mod tests { ), Ok(Some(relay_finality_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)), + ); // relay + message confirmation call batch is accepted assert_eq!( @@ -2166,6 +2384,12 @@ mod tests { ), Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)), + ); // message delivery call batch is accepted assert_eq!( @@ -2194,11 +2418,19 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -2211,19 +2443,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(100)), @@ -2254,19 +2502,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)), Ok(Some(relay_finality_pre_dispatch_data()),) ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Some(relay_finality_pre_dispatch_data_ex()),) + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Some(relay_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex())), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(200)), diff --git a/bridges/docs/high-level-overview.md b/bridges/docs/high-level-overview.md index 42efc8100bd080763c22ea3e4e813f3c3c87db37..d6d6fb3f0996dd15d4fc2987deacf79e4ecd4e5f 100644 --- a/bridges/docs/high-level-overview.md +++ b/bridges/docs/high-level-overview.md @@ -1,7 +1,7 @@ # High-Level Bridge Documentation This document gives a brief, abstract description of main components that may be found in this repository. If you want -to see how we're using them to build Rococo <> Wococo (Kusama <> Polkadot) bridge, please refer to the [Polkadot <> +to see how we're using them to build Rococo <> Westend (Kusama <> Polkadot) bridge, please refer to the [Polkadot <> Kusama Bridge](./polkadot-kusama-bridge-overview.md). ## Purpose diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index dbbe18febc618a0c8d16c6cd5edc5fd207a4a6ba..dccd7b3bdca3533cda4fec82ed0266d0b221b7a7 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -1,17 +1,20 @@ [package] name = "pallet-bridge-grandpa" -version = "0.1.0" +version = "0.7.0" description = "Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains." 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] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies @@ -37,7 +40,7 @@ sp-core = { path = "../../../substrate/primitives/core" } sp-io = { path = "../../../substrate/primitives/io" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-runtime/std", diff --git a/bridges/modules/grandpa/README.md b/bridges/modules/grandpa/README.md index 27b4d2389c4085538362e6aea17303c44ef50795..992bd2cc47228249310c56747416b07be6e1e287 100644 --- a/bridges/modules/grandpa/README.md +++ b/bridges/modules/grandpa/README.md @@ -27,7 +27,7 @@ for provided header. There are two main things in GRANDPA that help building light clients: - there's no need to import all headers of the bridged chain. Light client may import finalized headers or just - some of finalized headders that it consider useful. While the validators set stays the same, the client may + some of finalized headers that it consider useful. While the validators set stays the same, the client may import any header that is finalized by this set; - when validators set changes, the GRANDPA gadget adds next set to the header. So light client doesn't need to @@ -38,11 +38,11 @@ There are two main things in GRANDPA that help building light clients: ## Pallet Operations -The main entrypoint of the pallet is the `submit_finality_proof` call. It has two arguments - the finalized -headers and associated GRANDPA justification. The call simply verifies the justification using current -validators set and checks if header is better than the previous best header. If both checks are passed, the -header (only its useful fields) is inserted into the runtime storage and may be used by other pallets to verify -storage proofs. +The main entrypoint of the pallet is the `submit_finality_proof_ex` call. It has three arguments - the finalized +headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The +call simply verifies the justification using current validators set and checks if header is better than the +previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime +storage and may be used by other pallets to verify storage proofs. The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is required for the pallet operations, submitting such header is free. So if you're ok with session-length diff --git a/bridges/modules/grandpa/src/benchmarking.rs b/bridges/modules/grandpa/src/benchmarking.rs index 182b2f56eb1c57a165cf2eb1e86b585d70fd1801..11033373ce478fa9fefb613a1377449bb77daf1d 100644 --- a/bridges/modules/grandpa/src/benchmarking.rs +++ b/bridges/modules/grandpa/src/benchmarking.rs @@ -16,8 +16,9 @@ //! Benchmarks for the GRANDPA Pallet. //! -//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof`, so these benchmarks are -//! based around that. There are to main factors which affect finality proof verification: +//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof_ex`. Our benchmarks +//! are based around `submit_finality_proof`, though - from weight PoV they are the same calls. +//! There are to main factors which affect finality proof verification: //! //! 1. The number of `votes-ancestries` in the justification //! 2. The number of `pre-commits` in the justification diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index f238064f92bcacde89971479a501b60469f01691..e3c778b480baa51a8b9e5d04564ac54bc7a68a21 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -14,11 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, Error, Pallet}; -use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa}; +use crate::{ + weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, + Pallet, +}; +use bp_header_chain::{ + justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, + ChainWithGrandpa, GrandpaConsensusLogReader, +}; use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; +use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{Header, Zero}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, @@ -30,6 +37,9 @@ use sp_runtime::{ pub struct SubmitFinalityProofInfo { /// Number of the finality target. pub block_number: N, + /// An identifier of the validators set that has signed the submitted justification. + /// It might be `None` if deprecated version of the `submit_finality_proof` is used. + pub current_set_id: Option, /// Extra weight that we assume is included in the call. /// /// We have some assumptions about headers and justifications of the bridged chain. @@ -58,9 +68,11 @@ pub struct SubmitFinalityProofHelper, I: 'static> { impl, I: 'static> SubmitFinalityProofHelper { /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best - /// one we know. + /// one we know. Additionally, checks if `current_set_id` matches the current authority set + /// id, if specified. pub fn check_obsolete( finality_target: BlockNumberOf, + current_set_id: Option, ) -> Result<(), Error> { let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { log::trace!( @@ -82,6 +94,20 @@ impl, I: 'static> SubmitFinalityProofHelper { return Err(Error::::OldHeader) } + if let Some(current_set_id) = current_set_id { + let actual_set_id = >::get().set_id; + if current_set_id != actual_set_id { + log::trace!( + target: crate::LOG_TARGET, + "Cannot finalize header signed by unknown authority set: bundled {:?}, best {:?}", + current_set_id, + actual_set_id, + ); + + return Err(Error::::InvalidAuthoritySetId) + } + } + Ok(()) } @@ -108,6 +134,18 @@ pub trait CallSubType, I: 'static>: return Some(submit_finality_proof_info_from_args::( finality_target, justification, + None, + )) + } else if let Some(crate::Call::::submit_finality_proof_ex { + finality_target, + justification, + current_set_id, + }) = self.is_sub_type() + { + return Some(submit_finality_proof_info_from_args::( + finality_target, + justification, + Some(*current_set_id), )) } @@ -130,7 +168,10 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - match SubmitFinalityProofHelper::::check_obsolete(finality_target.block_number) { + match SubmitFinalityProofHelper::::check_obsolete( + finality_target.block_number, + finality_target.current_set_id, + ) { Ok(_) => Ok(ValidTransaction::default()), Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), Err(_) => InvalidTransaction::Call.into(), @@ -147,6 +188,7 @@ impl, I: 'static> CallSubType for T::RuntimeCall where pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( finality_target: &BridgedHeader, justification: &GrandpaJustification>, + current_set_id: Option, ) -> SubmitFinalityProofInfo> { let block_number = *finality_target.number(); @@ -169,26 +211,26 @@ 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) + SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size } } #[cfg(test)] @@ -196,20 +238,24 @@ mod tests { use crate::{ call_ext::CallSubType, mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, - BestFinalized, Config, PalletOperatingMode, WeightInfo, + BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet, + SubmitFinalityProofInfo, WeightInfo, }; use bp_header_chain::ChainWithGrandpa; use bp_runtime::{BasicOperatingMode, HeaderId}; use bp_test_utils::{ make_default_justification, make_justification_for_header, JustificationGeneratorParams, + TEST_GRANDPA_SET_ID, }; use frame_support::weights::Weight; use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion}; fn validate_block_submit(num: TestNumber) -> bool { - let bridge_grandpa_call = crate::Call::::submit_finality_proof { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { finality_target: Box::new(test_header(num)), justification: make_default_justification(&test_header(num)), + // not initialized => zero + current_set_id: 0, }; RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( bridge_grandpa_call, @@ -253,6 +299,18 @@ mod tests { }); } + #[test] + fn extension_rejects_new_header_if_set_id_is_invalid() { + run_test(|| { + // when set id is different from the passed one => tx is rejected + sync_to_header_10(); + let next_set = StoredAuthoritySet::::try_new(vec![], 0x42).unwrap(); + CurrentAuthoritySet::::put(next_set); + + assert!(!validate_block_submit(15)); + }); + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -263,6 +321,42 @@ mod tests { }); } + #[test] + fn submit_finality_proof_info_is_parsed() { + // when `submit_finality_proof` is used, `current_set_id` is set to `None` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: None, + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + + // when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + current_set_id: 777, + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: Some(777), + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + } + #[test] fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() { // when call arguments are below our limit => no refund @@ -272,9 +366,10 @@ mod tests { ..Default::default() }; let small_justification = make_justification_for_header(justification_params); - let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(small_finality_target), justification: small_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0); @@ -288,9 +383,10 @@ mod tests { ..Default::default() }; let large_justification = make_justification_for_header(justification_params); - let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(large_finality_target), justification: large_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0); } @@ -306,9 +402,10 @@ mod tests { // when there are `REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY` headers => no refund let justification = make_justification_for_header(justification_params.clone()); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target.clone()), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero()); @@ -319,9 +416,10 @@ mod tests { justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index 22df604bf18951073996f8f2195aefc78c62639b..ce2c47da954fa46efc4c70e9608864735fa16277 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -32,9 +32,8 @@ //! Shall the fork occur on the bridged chain governance intervention will be required to //! re-initialize the bridge and track the right fork. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// Runtime-generated enums -#![allow(clippy::large_enum_variant)] pub use storage_types::StoredAuthoritySet; @@ -152,7 +151,86 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { - /// Verify a target header is finalized according to the given finality proof. + /// This call is deprecated and will be removed around May 2024. Use the + /// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the + /// `submit_finality_proof_ex` call without current authority set id check. + #[pallet::call_index(0)] + #[pallet::weight(::submit_finality_proof( + justification.commit.precommits.len().saturated_into(), + justification.votes_ancestries.len().saturated_into(), + ))] + #[allow(deprecated)] + #[deprecated( + note = "`submit_finality_proof` will be removed in May 2024. Use `submit_finality_proof_ex` instead." + )] + pub fn submit_finality_proof( + origin: OriginFor, + finality_target: Box>, + justification: GrandpaJustification>, + ) -> DispatchResultWithPostInfo { + Self::submit_finality_proof_ex( + origin, + finality_target, + justification, + // the `submit_finality_proof_ex` also reads this value, but it is done from the + // cache, so we don't treat it as an additional db access + >::get().set_id, + ) + } + + /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. + /// + /// The initial configuration provided does not need to be the genesis header of the bridged + /// chain, it can be any arbitrary header. You can also provide the next scheduled set + /// change if it is already know. + /// + /// This function is only allowed to be called from a trusted origin and writes to storage + /// with practically no checks in terms of the validity of the data. It is important that + /// you ensure that valid data is being passed in. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: super::InitializationData>, + ) -> DispatchResultWithPostInfo { + Self::ensure_owner_or_root(origin)?; + + let init_allowed = !>::exists(); + ensure!(init_allowed, >::AlreadyInitialized); + initialize_bridge::(init_data.clone())?; + + log::info!( + target: LOG_TARGET, + "Pallet has been initialized with the following parameters: {:?}", + init_data + ); + + Ok(().into()) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(3)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Verify a target header is finalized according to the given finality proof. The proof + /// is assumed to be signed by GRANDPA authorities set with `current_set_id` id. /// /// It will use the underlying storage pallet to fetch information about the current /// authorities and best finalized header in order to verify that the header is finalized. @@ -166,18 +244,22 @@ pub mod pallet { /// /// - the pallet knows better header than the `finality_target`; /// + /// - the id of best GRANDPA authority set, known to the pallet is not equal to the + /// `current_set_id`; + /// /// - verification is not optimized or invalid; /// /// - header contains forced authorities set change or change with non-zero delay. - #[pallet::call_index(0)] + #[pallet::call_index(4)] #[pallet::weight(::submit_finality_proof( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] - pub fn submit_finality_proof( + pub fn submit_finality_proof_ex( origin: OriginFor, finality_target: Box>, justification: GrandpaJustification>, + current_set_id: sp_consensus_grandpa::SetId, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; @@ -189,7 +271,9 @@ pub mod pallet { finality_target ); - SubmitFinalityProofHelper::::check_obsolete(number)?; + // it checks whether the `number` is better than the current best block number + // and whether the `current_set_id` matches the best known set id + SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; let authority_set = >::get(); let unused_proof_size = authority_set.unused_proof_size(); @@ -203,7 +287,7 @@ pub mod pallet { // if we have seen too many mandatory headers in this block, we don't want to refund Self::free_mandatory_headers_remaining() > 0 && // if arguments out of expected bounds, we don't want to refund - submit_finality_proof_info_from_args::(&finality_target, &justification) + submit_finality_proof_info_from_args::(&finality_target, &justification, Some(current_set_id)) .fits_limits(); if may_refund_call_fee { FreeMandatoryHeadersRemaining::::mutate(|count| { @@ -249,57 +333,6 @@ pub mod pallet { Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } - - /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. - /// - /// The initial configuration provided does not need to be the genesis header of the bridged - /// chain, it can be any arbitrary header. You can also provide the next scheduled set - /// change if it is already know. - /// - /// This function is only allowed to be called from a trusted origin and writes to storage - /// with practically no checks in terms of the validity of the data. It is important that - /// you ensure that valid data is being passed in. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] - pub fn initialize( - origin: OriginFor, - init_data: super::InitializationData>, - ) -> DispatchResultWithPostInfo { - Self::ensure_owner_or_root(origin)?; - - let init_allowed = !>::exists(); - ensure!(init_allowed, >::AlreadyInitialized); - initialize_bridge::(init_data.clone())?; - - log::info!( - target: LOG_TARGET, - "Pallet has been initialized with the following parameters: {:?}", - init_data - ); - - Ok(().into()) - } - - /// Change `PalletOwner`. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(2)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { - >::set_owner(origin, new_owner) - } - - /// Halt or resume all pallet operations. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(3)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - operating_mode: BasicOperatingMode, - ) -> DispatchResult { - >::set_operating_mode(origin, operating_mode) - } } /// Number mandatory headers that we may accept in the current block for free (returning @@ -408,7 +441,9 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// Best finalized chain header has been updated to the header with given number and hash. UpdatedBestFinalizedHeader { + /// Number of the new best finalized header. number: BridgedBlockNumber, + /// Hash of the new best finalized header. hash: BridgedBlockHash, /// The Grandpa info associated to the new best finalized header. grandpa_info: StoredHeaderGrandpaInfo>, @@ -435,6 +470,9 @@ pub mod pallet { TooManyAuthoritiesInSet, /// Error generated by the `OwnedBridgeModule` trait. BridgeModule(bp_runtime::OwnedBridgeModuleError), + /// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match + /// the id of the current set, known to the pallet. + InvalidAuthoritySetId, } /// Check the given header for a GRANDPA scheduled authority set change. If a change @@ -662,6 +700,7 @@ mod tests { use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, make_justification_for_header, JustificationGeneratorParams, ALICE, BOB, + TEST_GRANDPA_SET_ID, }; use codec::Encode; use frame_support::{ @@ -692,7 +731,7 @@ mod tests { let init_data = InitializationData { header: Box::new(genesis), authority_list: authority_list(), - set_id: 1, + set_id: TEST_GRANDPA_SET_ID, operating_mode: BasicOperatingMode::Normal, }; @@ -703,10 +742,11 @@ mod tests { let header = test_header(header.into()); let justification = make_default_justification(&header); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ) } @@ -721,10 +761,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -748,10 +789,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -954,17 +996,30 @@ mod tests { let header = test_header(1); - let params = - JustificationGeneratorParams:: { set_id: 2, ..Default::default() }; + let next_set_id = 2; + let params = JustificationGeneratorParams:: { + set_id: next_set_id, + ..Default::default() + }; let justification = make_justification_for_header(params); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( + RuntimeOrigin::signed(1), + Box::new(header.clone()), + justification.clone(), + TEST_GRANDPA_SET_ID, + ), + >::InvalidJustification + ); + assert_err!( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + next_set_id, ), - >::InvalidJustification + >::InvalidAuthoritySetId ); }) } @@ -979,10 +1034,11 @@ mod tests { justification.round = 42; assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidJustification ); @@ -1008,10 +1064,11 @@ mod tests { let justification = make_default_justification(&header); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidAuthoritySet ); @@ -1046,10 +1103,11 @@ mod tests { let justification = make_default_justification(&header); // Let's import our test header - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification.clone(), + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); @@ -1108,10 +1166,11 @@ mod tests { // without large digest item ^^^ the relayer would have paid zero transaction fee // (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1139,10 +1198,11 @@ mod tests { // without many headers in votes ancestries ^^^ the relayer would have paid zero // transaction fee (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1168,10 +1228,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1193,10 +1254,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1218,10 +1280,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::TooManyAuthoritiesInSet ); @@ -1282,10 +1345,11 @@ mod tests { let mut invalid_justification = make_default_justification(&header); invalid_justification.round = 42; - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), invalid_justification, + TEST_GRANDPA_SET_ID, ) }; @@ -1450,10 +1514,11 @@ mod tests { let justification = make_default_justification(&header); assert_noop!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::root(), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), DispatchError::BadOrigin, ); diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs index f88a0a3e6a6ee187222a6fb8eb1628e22b37abbe..4318d663a2e17fe80199830d443f5b6a85fae441 100644 --- a/bridges/modules/grandpa/src/mock.rs +++ b/bridges/modules/grandpa/src/mock.rs @@ -18,18 +18,11 @@ #![allow(clippy::from_over_into)] use bp_header_chain::ChainWithGrandpa; -use bp_runtime::Chain; +use bp_runtime::{Chain, ChainId}; use frame_support::{ - construct_runtime, parameter_types, - traits::{ConstU32, ConstU64, Hooks}, - weights::Weight, + construct_runtime, derive_impl, parameter_types, traits::Hooks, weights::Weight, }; use sp_core::sr25519::Signature; -use sp_runtime::{ - testing::H256, - traits::{BlakeTwo256, IdentityLookup}, - Perbill, -}; pub type AccountId = u64; pub type TestHeader = sp_runtime::testing::Header; @@ -49,43 +42,14 @@ construct_runtime! { } } -parameter_types! { - pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 0); - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} - +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2; pub const HeadersToKeep: u32 = 5; - pub const SessionLength: u64 = 5; - pub const NumValidators: u32 = 5; } impl grandpa::Config for TestRuntime { @@ -100,7 +64,9 @@ impl grandpa::Config for TestRuntime { pub struct TestBridgedChain; impl Chain for TestBridgedChain { - type BlockNumber = TestNumber; + const ID: ChainId = *b"tbch"; + + type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; type Hash = ::Hash; type Hasher = ::Hashing; type Header = TestHeader; @@ -122,8 +88,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/grandpa/src/weights.rs b/bridges/modules/grandpa/src/weights.rs index 89ed70d13ac3359f23fa6b756d3a8e07ff8b7092..a75e7b5a8e4ada8ce880a040492c904d8035642c 100644 --- a/bridges/modules/grandpa/src/weights.rs +++ b/bridges/modules/grandpa/src/weights.rs @@ -23,7 +23,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/millau-bridge-node +// target/release/unknown-bridge-node // benchmark // pallet // --chain=dev @@ -58,39 +58,39 @@ pub trait WeightInfo { /// Those weights are test only and must never be used in production. pub struct BridgeWeight(PhantomData); impl WeightInfo for BridgeWeight { - /// Storage: BridgeRialtoGrandpa PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa RequestCount (r:1 w:1) + /// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added: 499, - /// mode: MaxEncodedLen) + /// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added: + /// 499, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa BestFinalized (r:1 w:1) + /// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added: + /// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added: /// 531, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa CurrentAuthoritySet (r:1 w:0) + /// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209), + /// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209), /// added: 704, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHashesPointer (r:1 w:1) + /// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4), + /// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4), /// added: 499, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHashes (r:1 w:1) + /// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36), + /// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36), /// added: 2016, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:0 w:2) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// /// The range of component `p` is `[1, 4]`. @@ -113,39 +113,39 @@ impl WeightInfo for BridgeWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: BridgeRialtoGrandpa PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownGrandpa PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa RequestCount (r:1 w:1) + /// Storage: BridgeUnknownGrandpa RequestCount (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added: 499, - /// mode: MaxEncodedLen) + /// Proof: BridgeUnknownGrandpa RequestCount (max_values: Some(1), max_size: Some(4), added: + /// 499, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa BestFinalized (r:1 w:1) + /// Storage: BridgeUnknownGrandpa BestFinalized (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added: + /// Proof: BridgeUnknownGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added: /// 531, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa CurrentAuthoritySet (r:1 w:0) + /// Storage: BridgeUnknownGrandpa CurrentAuthoritySet (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209), + /// Proof: BridgeUnknownGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(209), /// added: 704, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHashesPointer (r:1 w:1) + /// Storage: BridgeUnknownGrandpa ImportedHashesPointer (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4), + /// Proof: BridgeUnknownGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4), /// added: 499, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHashes (r:1 w:1) + /// Storage: BridgeUnknownGrandpa ImportedHashes (r:1 w:1) /// - /// Proof: BridgeRialtoGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36), + /// Proof: BridgeUnknownGrandpa ImportedHashes (max_values: Some(14400), max_size: Some(36), /// added: 2016, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:0 w:2) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:0 w:2) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// /// The range of component `p` is `[1, 4]`. diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index d3d68b338026360fe4156302c1d6f71ee06304e4..173d6f1c16448517b7051cfba2f96625ff3d525a 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "pallet-bridge-messages" description = "Module that allows bridged chains to exchange messages using lane concept." -version = "0.1.0" +version = "0.7.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 } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } @@ -22,7 +25,6 @@ bp-runtime = { path = "../../primitives/runtime", default-features = false } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } -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 } @@ -32,7 +34,7 @@ pallet-balances = { path = "../../../substrate/frame/balances" } sp-io = { path = "../../../substrate/primitives/io" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-messages/std", "bp-runtime/std", @@ -43,7 +45,6 @@ std = [ "log/std", "num-traits/std", "scale-info/std", - "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/bridges/modules/messages/README.md b/bridges/modules/messages/README.md index 457d5f5facfa70fdb11d05c5d544e75eb44f975f..fe62305748cd1d6030a7a8085bff29f24ee4dbc5 100644 --- a/bridges/modules/messages/README.md +++ b/bridges/modules/messages/README.md @@ -116,26 +116,12 @@ maximal possible transaction size of the chain and so on. And when the relayer s implementation must be able to parse and verify the proof of messages delivery. Normally, you would reuse the same (configurable) type on all chains that are sending messages to the same bridged chain. -The `pallet_bridge_messages::Config::LaneMessageVerifier` defines a single callback to verify outbound messages. The -simplest callback may just accept all messages. But in this case you'll need to answer many questions first. Who will -pay for the delivery and confirmation transaction? Are we sure that someone will ever deliver this message to the -bridged chain? Are we sure that we don't bloat our runtime storage by accepting this message? What if the message is -improperly encoded or has some fields set to invalid values? Answering all those (and similar) questions would lead to -correct implementation. - -There's another thing to consider when implementing type for use in -`pallet_bridge_messages::Config::LaneMessageVerifier`. It is whether we treat all message lanes identically, or they'll -have different sets of verification rules? For example, you may reserve lane#1 for messages coming from some -'wrapped-token' pallet - then you may verify in your implementation that the origin is associated with this pallet. -Lane#2 may be reserved for 'system' messages and you may charge zero fee for such messages. You may have some rate -limiting for messages sent over the lane#3. Or you may just verify the same rules set for all outbound messages - it is -all up to the `pallet_bridge_messages::Config::LaneMessageVerifier` implementation. - -The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation transaction is -received, we call the `pay_reward()` method, passing the range of delivered messages. You may use the -[`pallet-bridge-relayers`](../relayers/) pallet and its -[`DeliveryConfirmationPaymentsAdapter`](../relayers/src/payment_adapter.rs) adapter as a possible implementation. It -allows you to pay fixed reward for relaying the message and some of its portion for confirming delivery. +The last type is the `pallet_bridge_messages::Config::DeliveryConfirmationPayments`. When confirmation +transaction is received, we call the `pay_reward()` method, passing the range of delivered messages. +You may use the [`pallet-bridge-relayers`](../relayers/) pallet and its +[`DeliveryConfirmationPaymentsAdapter`](../relayers/src/payment_adapter.rs) adapter as a possible +implementation. It allows you to pay fixed reward for relaying the message and some of its portion +for confirming delivery. ### I have a Messages Module in my Runtime, but I Want to Reject all Outbound Messages. What shall I do? diff --git a/bridges/modules/messages/src/benchmarking.rs b/bridges/modules/messages/src/benchmarking.rs index 8c4e6fbf00ca42dd4a61c85c14d90fbff72ed042..4f13c4409672b3e76d36fd7d3dd2fab5c7e2ec1b 100644 --- a/bridges/modules/messages/src/benchmarking.rs +++ b/bridges/modules/messages/src/benchmarking.rs @@ -31,7 +31,7 @@ use codec::Decode; use frame_benchmarking::{account, benchmarks_instance_pallet}; use frame_support::weights::Weight; use frame_system::RawOrigin; -use sp_runtime::traits::TrailingZeroInput; +use sp_runtime::{traits::TrailingZeroInput, BoundedVec}; use sp_std::{ops::RangeInclusive, prelude::*}; const SEED: u32 = 0; @@ -443,7 +443,7 @@ benchmarks_instance_pallet! { fn send_regular_message, I: 'static>() { let mut outbound_lane = outbound_lane::(T::bench_lane_id()); - outbound_lane.send_message(vec![]).expect("We craft valid messages"); + outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages")); } fn receive_messages, I: 'static>(nonce: MessageNonce) { diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index b87c64d160752862a59a14f6591bf64f363004e8..a86cb326cf0404512b7fe6ad0aa2a696ff7d0a47 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -33,9 +33,8 @@ //! If this test fails with your weights, then either weights are computed incorrectly, //! or some benchmarks assumptions are broken for your runtime. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// Generated by `decl_event!` -#![allow(clippy::unused_unit)] pub use inbound_lane::StoredInboundLaneData; pub use outbound_lane::StoredMessagePayload; @@ -53,8 +52,7 @@ use crate::{ use bp_messages::{ source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, - SendMessageArtifacts, TargetHeaderChain, + DeliveryConfirmationPayments, OnMessagesDelivered, SendMessageArtifacts, TargetHeaderChain, }, target_chain::{ DeliveryPayments, DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, @@ -155,8 +153,6 @@ pub mod pallet { /// Target header chain. type TargetHeaderChain: TargetHeaderChain; - /// Message payload verifier. - type LaneMessageVerifier: LaneMessageVerifier; /// Delivery confirmation payments. type DeliveryConfirmationPayments: DeliveryConfirmationPayments; /// Delivery confirmation callback. @@ -517,16 +513,28 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { /// Message has been accepted and is waiting to be delivered. - MessageAccepted { lane_id: LaneId, nonce: MessageNonce }, + MessageAccepted { + /// Lane, which has accepted the message. + lane_id: LaneId, + /// Nonce of accepted message. + nonce: MessageNonce, + }, /// Messages have been received from the bridged chain. MessagesReceived( + /// Result of received messages dispatch. Vec::DispatchLevelResult>>, ), /// Messages in the inclusive range have been delivered to the bridged chain. - MessagesDelivered { lane_id: LaneId, messages: DeliveredMessages }, + MessagesDelivered { + /// Lane for which the delivery has been confirmed. + lane_id: LaneId, + /// Delivered messages. + messages: DeliveredMessages, + }, } #[pallet::error] + #[derive(PartialEq, Eq)] pub enum Error { /// Pallet is not in Normal operating mode. NotOperatingNormally, @@ -536,8 +544,6 @@ pub mod pallet { MessageDispatchInactive, /// Message has been treated as invalid by chain verifier. MessageRejectedByChainVerifier(VerificationError), - /// Message has been treated as invalid by lane verifier. - MessageRejectedByLaneVerifier(VerificationError), /// Message has been treated as invalid by the pallet logic. MessageRejectedByPallet(VerificationError), /// Submitter has failed to pay fee for delivering and dispatching messages. @@ -683,80 +689,72 @@ pub mod pallet { } } +/// Structure, containing a validated message payload and all the info required +/// to send it on the bridge. +#[derive(Debug, PartialEq, Eq)] +pub struct SendMessageArgs, I: 'static> { + lane_id: LaneId, + payload: StoredMessagePayload, +} + impl bp_messages::source_chain::MessagesBridge for Pallet where T: Config, I: 'static, { - type Error = sp_runtime::DispatchErrorWithPostInfo; + type Error = Error; + type SendMessageArgs = SendMessageArgs; - fn send_message( + fn validate_message( lane: LaneId, - message: T::OutboundPayload, - ) -> Result { - crate::send_message::(lane, message) + message: &T::OutboundPayload, + ) -> Result, Self::Error> { + ensure_normal_operating_mode::()?; + + // let's check if outbound lane is active + ensure!(T::ActiveOutboundLanes::get().contains(&lane), Error::::InactiveOutboundLane); + + // let's first check if message can be delivered to target chain + T::TargetHeaderChain::verify_message(message).map_err(|err| { + log::trace!( + target: LOG_TARGET, + "Message to lane {:?} is rejected by target chain: {:?}", + lane, + err, + ); + + Error::::MessageRejectedByChainVerifier(err) + })?; + + Ok(SendMessageArgs { + lane_id: lane, + payload: StoredMessagePayload::::try_from(message.encode()).map_err(|_| { + Error::::MessageRejectedByPallet(VerificationError::MessageTooLarge) + })?, + }) } -} -/// Function that actually sends message. -fn send_message, I: 'static>( - lane_id: LaneId, - payload: T::OutboundPayload, -) -> sp_std::result::Result< - SendMessageArtifacts, - sp_runtime::DispatchErrorWithPostInfo, -> { - ensure_normal_operating_mode::()?; - - // let's check if outbound lane is active - ensure!(T::ActiveOutboundLanes::get().contains(&lane_id), Error::::InactiveOutboundLane,); - - // let's first check if message can be delivered to target chain - T::TargetHeaderChain::verify_message(&payload).map_err(|err| { - log::trace!( - target: LOG_TARGET, - "Message to lane {:?} is rejected by target chain: {:?}", - lane_id, - err, - ); + fn send_message(args: SendMessageArgs) -> SendMessageArtifacts { + // save message in outbound storage and emit event + let mut lane = outbound_lane::(args.lane_id); + let message_len = args.payload.len(); + let nonce = lane.send_message(args.payload); - Error::::MessageRejectedByChainVerifier(err) - })?; + // return number of messages in the queue to let sender know about its state + let enqueued_messages = lane.data().queued_messages().saturating_len(); - // now let's enforce any additional lane rules - let mut lane = outbound_lane::(lane_id); - T::LaneMessageVerifier::verify_message(&lane_id, &lane.data(), &payload).map_err(|err| { log::trace!( target: LOG_TARGET, - "Message to lane {:?} is rejected by lane verifier: {:?}", - lane_id, - err, + "Accepted message {} to lane {:?}. Message size: {:?}", + nonce, + args.lane_id, + message_len, ); - Error::::MessageRejectedByLaneVerifier(err) - })?; - - // finally, save message in outbound storage and emit event - let encoded_payload = payload.encode(); - let encoded_payload_len = encoded_payload.len(); - let nonce = lane - .send_message(encoded_payload) - .map_err(Error::::MessageRejectedByPallet)?; - - // return number of messages in the queue to let sender know about its state - let enqueued_messages = lane.data().queued_messages().saturating_len(); - - log::trace!( - target: LOG_TARGET, - "Accepted message {} to lane {:?}. Message size: {:?}", - nonce, - lane_id, - encoded_payload_len, - ); + Pallet::::deposit_event(Event::MessageAccepted { lane_id: args.lane_id, nonce }); - Pallet::::deposit_event(Event::MessageAccepted { lane_id, nonce }); - - Ok(SendMessageArtifacts { nonce, enqueued_messages }) + SendMessageArtifacts { nonce, enqueued_messages } + } } /// Ensure that the pallet is in normal operational mode. @@ -857,6 +855,8 @@ struct RuntimeOutboundLaneStorage { } impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage { + type StoredMessagePayload = StoredMessagePayload; + fn id(&self) -> LaneId { self.lane_id } @@ -870,22 +870,15 @@ impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorag } #[cfg(test)] - fn message(&self, nonce: &MessageNonce) -> Option { + fn message(&self, nonce: &MessageNonce) -> Option { OutboundMessages::::get(MessageKey { lane_id: self.lane_id, nonce: *nonce }) - .map(Into::into) } - fn save_message( - &mut self, - nonce: MessageNonce, - message_payload: MessagePayload, - ) -> Result<(), VerificationError> { + fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload) { OutboundMessages::::insert( MessageKey { lane_id: self.lane_id, nonce }, - StoredMessagePayload::::try_from(message_payload) - .map_err(|_| VerificationError::MessageTooLarge)?, + message_payload, ); - Ok(()) } fn remove_message(&mut self, nonce: &MessageNonce) { @@ -932,7 +925,10 @@ mod tests { }, outbound_lane::ReceivalConfirmationError, }; - use bp_messages::{BridgeMessagesCall, UnrewardedRelayer, UnrewardedRelayersState}; + use bp_messages::{ + source_chain::MessagesBridge, BridgeMessagesCall, UnrewardedRelayer, + UnrewardedRelayersState, + }; use bp_test_utils::generate_owned_bridge_module_tests; use frame_support::{ assert_noop, assert_ok, @@ -949,14 +945,15 @@ mod tests { System::::reset_events(); } - fn send_regular_message() { + fn send_regular_message(lane_id: LaneId) { get_ready_for_events(); - let outbound_lane = outbound_lane::(TEST_LANE_ID); + let outbound_lane = outbound_lane::(lane_id); let message_nonce = outbound_lane.data().latest_generated_nonce + 1; let prev_enqueud_messages = outbound_lane.data().queued_messages().saturating_len(); - let artifacts = send_message::(TEST_LANE_ID, REGULAR_PAYLOAD) - .expect("send_message has failed"); + let valid_message = Pallet::::validate_message(lane_id, ®ULAR_PAYLOAD) + .expect("validate_message has failed"); + let artifacts = Pallet::::send_message(valid_message); assert_eq!(artifacts.enqueued_messages, prev_enqueud_messages + 1); // check event with assigned nonce @@ -965,7 +962,7 @@ mod tests { vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessageAccepted { - lane_id: TEST_LANE_ID, + lane_id, nonce: message_nonce }), topics: vec![], @@ -1016,14 +1013,14 @@ mod tests { fn pallet_rejects_transactions_if_halted() { run_test(|| { // send message first to be able to check that delivery_proof fails later - send_regular_message(); + send_regular_message(TEST_LANE_ID); PalletOperatingMode::::put(MessagesOperatingMode::Basic( BasicOperatingMode::Halted, )); assert_noop!( - send_message::(TEST_LANE_ID, REGULAR_PAYLOAD,), + Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), Error::::NotOperatingNormally, ); @@ -1066,14 +1063,14 @@ mod tests { fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { run_test(|| { // send message first to be able to check that delivery_proof fails later - send_regular_message(); + send_regular_message(TEST_LANE_ID); PalletOperatingMode::::put( MessagesOperatingMode::RejectingOutboundMessages, ); assert_noop!( - send_message::(TEST_LANE_ID, REGULAR_PAYLOAD,), + Pallet::::validate_message(TEST_LANE_ID, ®ULAR_PAYLOAD), Error::::NotOperatingNormally, ); @@ -1109,7 +1106,7 @@ mod tests { #[test] fn send_message_works() { run_test(|| { - send_regular_message(); + send_regular_message(TEST_LANE_ID); }); } @@ -1123,7 +1120,7 @@ mod tests { .extra .extend_from_slice(&[0u8; MAX_OUTBOUND_PAYLOAD_SIZE as usize]); assert_noop!( - send_message::(TEST_LANE_ID, message_payload.clone(),), + Pallet::::validate_message(TEST_LANE_ID, &message_payload.clone(),), Error::::MessageRejectedByPallet( VerificationError::MessageTooLarge ), @@ -1134,7 +1131,11 @@ mod tests { message_payload.extra.pop(); } assert_eq!(message_payload.encoded_size() as u32, MAX_OUTBOUND_PAYLOAD_SIZE); - assert_ok!(send_message::(TEST_LANE_ID, message_payload,),); + + let valid_message = + Pallet::::validate_message(TEST_LANE_ID, &message_payload) + .expect("validate_message has failed"); + Pallet::::send_message(valid_message); }) } @@ -1143,7 +1144,10 @@ mod tests { run_test(|| { // messages with this payload are rejected by target chain verifier assert_noop!( - send_message::(TEST_LANE_ID, PAYLOAD_REJECTED_BY_TARGET_CHAIN,), + Pallet::::validate_message( + TEST_LANE_ID, + &PAYLOAD_REJECTED_BY_TARGET_CHAIN, + ), Error::::MessageRejectedByChainVerifier(VerificationError::Other( mock::TEST_ERROR )), @@ -1151,21 +1155,6 @@ mod tests { }); } - #[test] - fn lane_verifier_rejects_invalid_message_in_send_message() { - run_test(|| { - // messages with zero fee are rejected by lane verifier - let mut message = REGULAR_PAYLOAD; - message.reject_by_lane_verifier = true; - assert_noop!( - send_message::(TEST_LANE_ID, message,), - Error::::MessageRejectedByLaneVerifier(VerificationError::Other( - mock::TEST_ERROR - )), - ); - }); - } - #[test] fn receive_messages_proof_works() { run_test(|| { @@ -1318,7 +1307,7 @@ mod tests { #[test] fn receive_messages_delivery_proof_works() { run_test(|| { - send_regular_message(); + send_regular_message(TEST_LANE_ID); receive_messages_delivery_proof(); assert_eq!( @@ -1331,8 +1320,8 @@ mod tests { #[test] fn receive_messages_delivery_proof_rewards_relayers() { run_test(|| { - assert_ok!(send_message::(TEST_LANE_ID, REGULAR_PAYLOAD,)); - assert_ok!(send_message::(TEST_LANE_ID, REGULAR_PAYLOAD,)); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); // this reports delivery of message 1 => reward is paid to TEST_RELAYER_A let single_message_delivery_proof = TestMessagesDeliveryProof(Ok(( @@ -1718,9 +1707,9 @@ mod tests { #[test] fn messages_delivered_callbacks_are_called() { run_test(|| { - send_regular_message(); - send_regular_message(); - send_regular_message(); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); // messages 1+2 are confirmed in 1 tx, message 3 in a separate tx // dispatch of message 2 has failed @@ -1779,7 +1768,7 @@ mod tests { ) { run_test(|| { // send message first to be able to check that delivery_proof fails later - send_regular_message(); + send_regular_message(TEST_LANE_ID); // 1) InboundLaneData declares that the `last_confirmed_nonce` is 1; // 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` @@ -1846,10 +1835,10 @@ mod tests { #[test] fn on_idle_callback_respects_remaining_weight() { run_test(|| { - send_regular_message(); - send_regular_message(); - send_regular_message(); - send_regular_message(); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); + send_regular_message(TEST_LANE_ID); assert_ok!(Pallet::::receive_messages_delivery_proof( RuntimeOrigin::signed(1), @@ -1928,10 +1917,10 @@ mod tests { fn on_idle_callback_is_rotating_lanes_to_prune() { run_test(|| { // send + receive confirmation for lane 1 - send_regular_message(); + send_regular_message(TEST_LANE_ID); receive_messages_delivery_proof(); // send + receive confirmation for lane 2 - assert_ok!(send_message::(TEST_LANE_ID_2, REGULAR_PAYLOAD,)); + send_regular_message(TEST_LANE_ID_2); assert_ok!(Pallet::::receive_messages_delivery_proof( RuntimeOrigin::signed(1), TestMessagesDeliveryProof(Ok(( @@ -2007,7 +1996,7 @@ mod tests { fn outbound_message_from_unconfigured_lane_is_rejected() { run_test(|| { assert_noop!( - send_message::(TEST_LANE_ID_3, REGULAR_PAYLOAD,), + Pallet::::validate_message(TEST_LANE_ID_3, ®ULAR_PAYLOAD,), Error::::InactiveOutboundLane, ); }); diff --git a/bridges/modules/messages/src/mock.rs b/bridges/modules/messages/src/mock.rs index aebb7eafa7871d13cd8140d5a352978ca36433dd..ec63f15b94b5205d744b1379bd6697a4ae43534a 100644 --- a/bridges/modules/messages/src/mock.rs +++ b/bridges/modules/messages/src/mock.rs @@ -17,33 +17,26 @@ // From construct_runtime macro #![allow(clippy::from_over_into)] -use crate::Config; +use crate::{Config, StoredMessagePayload}; use bp_messages::{ calc_relayers_rewards, - source_chain::{ - DeliveryConfirmationPayments, LaneMessageVerifier, OnMessagesDelivered, TargetHeaderChain, - }, + source_chain::{DeliveryConfirmationPayments, OnMessagesDelivered, TargetHeaderChain}, target_chain::{ DeliveryPayments, DispatchMessage, DispatchMessageData, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain, }, - DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, - OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, VerificationError, + DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, + UnrewardedRelayer, UnrewardedRelayersState, VerificationError, }; use bp_runtime::{messages::MessageDispatchResult, Size}; use codec::{Decode, Encode}; use frame_support::{ - parameter_types, - traits::ConstU64, + derive_impl, parameter_types, weights::{constants::RocksDbWeight, Weight}, }; use scale_info::TypeInfo; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, Perbill, -}; +use sp_runtime::BuildStorage; use std::{ collections::{BTreeMap, VecDeque}, ops::RangeInclusive, @@ -55,8 +48,6 @@ pub type Balance = u64; pub struct TestPayload { /// Field that may be used to identify messages. pub id: u64, - /// Reject this message by lane verifier? - pub reject_by_lane_verifier: bool, /// Dispatch weight that is declared by the message sender. pub declared_weight: Weight, /// Message dispatch result. @@ -84,55 +75,19 @@ frame_support::construct_runtime! { } } -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 0); - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} - pub type DbWeight = RocksDbWeight; +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); type DbWeight = DbWeight; - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { - type MaxLocks = (); - type Balance = Balance; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ConstU64<1>; - type AccountStore = frame_system::Pallet; - type WeightInfo = (); - type MaxReserves = (); - type ReserveIdentifier = (); - type RuntimeHoldReason = RuntimeHoldReason; - type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; } parameter_types! { @@ -161,7 +116,6 @@ impl Config for TestRuntime { type DeliveryPayments = TestDeliveryPayments; type TargetHeaderChain = TestTargetHeaderChain; - type LaneMessageVerifier = TestLaneMessageVerifier; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; type OnMessagesDelivered = TestOnMessagesDelivered; @@ -309,24 +263,6 @@ impl TargetHeaderChain for TestTargetHeaderChain { } } -/// 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: &TestPayload, - ) -> Result<(), VerificationError> { - if !payload.reject_by_lane_verifier { - Ok(()) - } else { - Err(VerificationError::Other(TEST_ERROR)) - } - } -} - /// Reward payments at the target chain during delivery transaction. #[derive(Debug, Default)] pub struct TestDeliveryPayments; @@ -466,8 +402,8 @@ pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { } /// Return valid outbound message data, constructed from given payload. -pub fn outbound_message_data(payload: TestPayload) -> MessagePayload { - payload.encode() +pub fn outbound_message_data(payload: TestPayload) -> StoredMessagePayload { + StoredMessagePayload::::try_from(payload.encode()).expect("payload too large") } /// Return valid inbound (dispatch) message data, constructed from given payload. @@ -479,7 +415,6 @@ pub fn inbound_message_data(payload: TestPayload) -> DispatchMessageData TestPayload { TestPayload { id, - reject_by_lane_verifier: false, declared_weight: Weight::from_parts(declared_weight, 0), dispatch_result: dispatch_result(0), extra: Vec::new(), diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index f92e9ccfd95c61ac9797e6423eae5d4e8f02dc2c..431c2cfb7eef3e8dd48e49c6ac37153ae64d57b6 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -18,10 +18,7 @@ use crate::{Config, LOG_TARGET}; -use bp_messages::{ - DeliveredMessages, LaneId, MessageNonce, MessagePayload, OutboundLaneData, UnrewardedRelayer, - VerificationError, -}; +use bp_messages::{DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer}; use codec::{Decode, Encode}; use frame_support::{ weights::{RuntimeDbWeight, Weight}, @@ -34,6 +31,8 @@ use sp_std::collections::vec_deque::VecDeque; /// Outbound lane storage. pub trait OutboundLaneStorage { + type StoredMessagePayload; + /// Lane id. fn id(&self) -> LaneId; /// Get lane data from the storage. @@ -42,13 +41,9 @@ pub trait OutboundLaneStorage { fn set_data(&mut self, data: OutboundLaneData); /// Returns saved outbound message payload. #[cfg(test)] - fn message(&self, nonce: &MessageNonce) -> Option; + fn message(&self, nonce: &MessageNonce) -> Option; /// Save outbound message in the storage. - fn save_message( - &mut self, - nonce: MessageNonce, - message_payload: MessagePayload, - ) -> Result<(), VerificationError>; + fn save_message(&mut self, nonce: MessageNonce, message_payload: Self::StoredMessagePayload); /// Remove outbound message from the storage. fn remove_message(&mut self, nonce: &MessageNonce); } @@ -91,18 +86,15 @@ impl OutboundLane { /// Send message over lane. /// /// Returns new message nonce. - pub fn send_message( - &mut self, - message_payload: MessagePayload, - ) -> Result { + pub fn send_message(&mut self, message_payload: S::StoredMessagePayload) -> MessageNonce { let mut data = self.storage.data(); let nonce = data.latest_generated_nonce + 1; data.latest_generated_nonce = nonce; - self.storage.save_message(nonce, message_payload)?; + self.storage.save_message(nonce, message_payload); self.storage.set_data(data); - Ok(nonce) + nonce } /// Confirm messages delivery. @@ -218,7 +210,7 @@ mod tests { }, outbound_lane, }; - use frame_support::{assert_ok, weights::constants::RocksDbWeight}; + use frame_support::weights::constants::RocksDbWeight; use sp_std::ops::RangeInclusive; fn unrewarded_relayers( @@ -239,9 +231,9 @@ mod tests { ) -> Result, ReceivalConfirmationError> { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); let result = lane.confirm_delivery(3, latest_received_nonce, relayers); @@ -256,7 +248,7 @@ mod tests { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); assert_eq!(lane.storage.data().latest_generated_nonce, 0); - assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), Ok(1)); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1); assert!(lane.storage.message(&1).is_some()); assert_eq!(lane.storage.data().latest_generated_nonce, 1); }); @@ -266,9 +258,9 @@ mod tests { fn confirm_delivery_works() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), Ok(1)); - assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), Ok(2)); - assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), Ok(3)); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 1); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 2); + assert_eq!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD)), 3); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); assert_eq!( @@ -284,9 +276,9 @@ mod tests { fn confirm_delivery_rejects_nonce_lesser_than_latest_received() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 0); assert_eq!( @@ -368,9 +360,9 @@ mod tests { ); assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); // when nothing is confirmed, nothing is pruned - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); assert!(lane.storage.message(&1).is_some()); assert!(lane.storage.message(&2).is_some()); assert!(lane.storage.message(&3).is_some()); @@ -412,9 +404,9 @@ mod tests { fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() { run_test(|| { let mut lane = outbound_lane::(TEST_LANE_ID); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); - assert_ok!(lane.send_message(outbound_message_data(REGULAR_PAYLOAD))); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); + lane.send_message(outbound_message_data(REGULAR_PAYLOAD)); assert_eq!( lane.confirm_delivery(0, 3, &unrewarded_relayers(1..=3)), Err(ReceivalConfirmationError::TryingToConfirmMoreMessagesThanExpected), diff --git a/bridges/modules/messages/src/weights.rs b/bridges/modules/messages/src/weights.rs index 5b6863984ec78c0e250c54a0ed3363d1278f196f..5bf7d56756079df8a5e469b9c50ba7607b65d983 100644 --- a/bridges/modules/messages/src/weights.rs +++ b/bridges/modules/messages/src/weights.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! Autogenerated weights for RialtoMessages +//! Autogenerated weights for pallet_bridge_messages //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: 2023-03-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` @@ -23,13 +23,13 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/millau-bridge-node +// target/release/unknown-bridge-node // benchmark // pallet // --chain=dev // --steps=50 // --repeat=20 -// --pallet=RialtoMessages +// --pallet=pallet_bridge_messages // --extrinsic=* // --execution=wasm // --wasm-execution=Compiled @@ -48,7 +48,7 @@ use frame_support::{ }; use sp_std::marker::PhantomData; -/// Weight functions needed for RialtoMessages. +/// Weight functions needed for pallet_bridge_messages. pub trait WeightInfo { fn receive_single_message_proof() -> Weight; fn receive_two_messages_proof() -> Weight; @@ -61,24 +61,24 @@ pub trait WeightInfo { fn receive_single_message_proof_with_dispatch(i: u32) -> Weight; } -/// Weights for `RialtoMessages` that are generated using one of the Bridge testnets. +/// Weights for `pallet_bridge_messages` that are generated using one of the Bridge testnets. /// /// Those weights are test only and must never be used in production. pub struct BridgeWeight(PhantomData); impl WeightInfo for BridgeWeight { - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_single_message_proof() -> Weight { // Proof Size summary in bytes: @@ -89,19 +89,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_two_messages_proof() -> Weight { // Proof Size summary in bytes: @@ -112,19 +112,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_single_message_proof_with_outbound_lane_state() -> Weight { // Proof Size summary in bytes: @@ -135,19 +135,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages 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: @@ -158,19 +158,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages 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: @@ -181,19 +181,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) @@ -209,19 +209,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) @@ -237,19 +237,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:2 w:2) @@ -265,19 +265,19 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) /// /// The range of component `i` is `[128, 2048]`. @@ -296,19 +296,19 @@ impl WeightInfo for BridgeWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_single_message_proof() -> Weight { // Proof Size summary in bytes: @@ -319,19 +319,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_two_messages_proof() -> Weight { // Proof Size summary in bytes: @@ -342,19 +342,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) fn receive_single_message_proof_with_outbound_lane_state() -> Weight { // Proof Size summary in bytes: @@ -365,19 +365,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages 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: @@ -388,19 +388,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages 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: @@ -411,19 +411,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) @@ -439,19 +439,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:1 w:1) @@ -467,19 +467,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages OutboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages OutboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: + /// Proof: BridgeUnknownMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: /// 539, mode: MaxEncodedLen) /// /// Storage: BridgeRelayers RelayerRewards (r:2 w:2) @@ -495,19 +495,19 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoMessages PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownMessages PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), + /// Proof: BridgeUnknownMessages PalletOperatingMode (max_values: Some(1), max_size: Some(2), /// added: 497, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoMessages InboundLanes (r:1 w:1) + /// Storage: BridgeUnknownMessages InboundLanes (r:1 w:1) /// - /// Proof: BridgeRialtoMessages InboundLanes (max_values: None, max_size: Some(49180), added: + /// Proof: BridgeUnknownMessages InboundLanes (max_values: None, max_size: Some(49180), added: /// 51655, mode: MaxEncodedLen) /// /// The range of component `i` is `[128, 2048]`. 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 0d1b61ddea8022db5b9aaafee03624e3be3959d6..e454a6f2888fa169a0b0795101172b2f260b4020 100644 --- a/bridges/modules/parachains/Cargo.toml +++ b/bridges/modules/parachains/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "pallet-bridge-parachains" -version = "0.1.0" +version = "0.7.0" description = "Module that allows bridged relay chains to exchange information on their parachains' heads." 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 } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies @@ -35,7 +38,7 @@ sp-core = { path = "../../../substrate/primitives/core" } sp-io = { path = "../../../substrate/primitives/io" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-parachains/std", diff --git a/bridges/modules/parachains/src/call_ext.rs b/bridges/modules/parachains/src/call_ext.rs index 198ff11be49512ef90ab38a093e6889a3d8f0702..da91a40a2322393ee715bf1c61840e4b18df23b8 100644 --- a/bridges/modules/parachains/src/call_ext.rs +++ b/bridges/modules/parachains/src/call_ext.rs @@ -178,7 +178,7 @@ mod tests { RuntimeCall::Parachains(crate::Call::::submit_parachain_heads { at_relay_block: (num, Default::default()), parachains, - parachain_heads_proof: ParaHeadsProof(Vec::new()), + parachain_heads_proof: ParaHeadsProof { storage_proof: Vec::new() }, }) .check_obsolete_submit_parachain_heads() .is_ok() diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index b2ef0bf52bd3d5b5f619a6b8e28bbf8228c1a72c..1363a637604d1202ffc4bf799bf7ced180d9fe53 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -21,6 +21,7 @@ //! accepts storage proof of some parachain `Heads` entries from bridged relay chain. //! It requires corresponding relay headers to be already synced. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use weights::WeightInfo; @@ -98,27 +99,49 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { /// The caller has provided head of parachain that the pallet is not configured to track. - UntrackedParachainRejected { parachain: ParaId }, + UntrackedParachainRejected { + /// Identifier of the parachain that is not tracked by the pallet. + parachain: ParaId, + }, /// The caller has declared that he has provided given parachain head, but it is missing /// from the storage proof. - MissingParachainHead { parachain: ParaId }, + MissingParachainHead { + /// Identifier of the parachain with missing head. + parachain: ParaId, + }, /// The caller has provided parachain head hash that is not matching the hash read from the /// storage proof. IncorrectParachainHeadHash { + /// Identifier of the parachain with incorrect head hast. parachain: ParaId, + /// Specified parachain head hash. parachain_head_hash: ParaHash, + /// Actual parachain head hash. actual_parachain_head_hash: ParaHash, }, /// The caller has provided obsolete parachain head, which is already known to the pallet. - RejectedObsoleteParachainHead { parachain: ParaId, parachain_head_hash: ParaHash }, + RejectedObsoleteParachainHead { + /// Identifier of the parachain with obsolete head. + parachain: ParaId, + /// Obsolete parachain head hash. + parachain_head_hash: ParaHash, + }, /// The caller has provided parachain head that exceeds the maximal configured head size. RejectedLargeParachainHead { + /// Identifier of the parachain with rejected head. parachain: ParaId, + /// Parachain head hash. parachain_head_hash: ParaHash, + /// Parachain head size. parachain_head_size: u32, }, /// Parachain head has been updated. - UpdatedParachainHead { parachain: ParaId, parachain_head_hash: ParaHash }, + UpdatedParachainHead { + /// Identifier of the parachain that has been updated. + parachain: ParaId, + /// Parachain head hash. + parachain_head_hash: ParaHash, + }, } #[pallet::error] @@ -137,6 +160,7 @@ pub mod pallet { pub trait BoundedBridgeGrandpaConfig: pallet_bridge_grandpa::Config { + /// Type of the bridged relay chain. type BridgedRelayChain: Chain< BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, @@ -336,7 +360,7 @@ pub mod pallet { let mut storage = GrandpaPalletOf::::storage_proof_checker( relay_block_hash, - parachain_heads_proof.0, + parachain_heads_proof.storage_proof, ) .map_err(Error::::HeaderChainStorageProof)?; @@ -707,6 +731,7 @@ pub(crate) mod tests { }; use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, + TEST_GRANDPA_SET_ID, }; use frame_support::{ assert_noop, assert_ok, @@ -753,10 +778,11 @@ pub(crate) mod tests { let hash = header.hash(); let justification = make_default_justification(&header); assert_ok!( - pallet_bridge_grandpa::Pallet::::submit_finality_proof( + pallet_bridge_grandpa::Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification.clone(), + TEST_GRANDPA_SET_ID, ) ); @@ -1470,7 +1496,7 @@ pub(crate) mod tests { ); // then if someone is pretending to provide updated head#10 of parachain#1 at relay - // block#30, and actualy provides it + // block#30, and actually provides it // // => we'll update value proceed(30, state_root_10_at_30); diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index 14afe38417105a789f9eb13ca913c1be91e6f789..3af3fd3e76398eaefdb7a9380344371ec44d27d7 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -16,18 +16,19 @@ use bp_header_chain::ChainWithGrandpa; use bp_polkadot_core::parachains::ParaId; -use bp_runtime::{Chain, Parachain}; -use frame_support::{construct_runtime, parameter_types, traits::ConstU32, weights::Weight}; +use bp_runtime::{Chain, ChainId, Parachain}; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, traits::ConstU32, weights::Weight, +}; use sp_runtime::{ testing::H256, - traits::{BlakeTwo256, Header as HeaderT, IdentityLookup}, - MultiSignature, Perbill, + traits::{BlakeTwo256, Header as HeaderT}, + MultiSignature, }; use crate as pallet_bridge_parachains; pub type AccountId = u64; -pub type TestNumber = u64; pub type RelayBlockHeader = sp_runtime::generic::Header; @@ -48,6 +49,8 @@ pub type BigParachainHeader = sp_runtime::generic::Header; pub struct Parachain1; impl Chain for Parachain1 { + const ID: ChainId = *b"pch1"; + type BlockNumber = u64; type Hash = H256; type Hasher = RegularParachainHasher; @@ -72,6 +75,8 @@ impl Parachain for Parachain1 { pub struct Parachain2; impl Chain for Parachain2 { + const ID: ChainId = *b"pch2"; + type BlockNumber = u64; type Hash = H256; type Hasher = RegularParachainHasher; @@ -96,6 +101,8 @@ impl Parachain for Parachain2 { pub struct Parachain3; impl Chain for Parachain3 { + const ID: ChainId = *b"pch3"; + type BlockNumber = u64; type Hash = H256; type Hasher = RegularParachainHasher; @@ -121,6 +128,8 @@ impl Parachain for Parachain3 { pub struct BigParachain; impl Chain for BigParachain { + const ID: ChainId = *b"bpch"; + type BlockNumber = u128; type Hash = H256; type Hasher = RegularParachainHasher; @@ -152,42 +161,12 @@ construct_runtime! { } } -parameter_types! { - pub const BlockHashCount: TestNumber = 250; - pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 0); - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::one(); -} - +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; type Block = Block; - type Hash = H256; - type Hashing = RegularParachainHasher; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { - pub const SessionLength: u64 = 5; - pub const NumValidators: u32 = 5; pub const HeadersToKeep: u32 = 5; } @@ -258,6 +237,8 @@ impl pallet_bridge_parachains::benchmarking::Config<()> for TestRuntime { pub struct TestBridgedChain; impl Chain for TestBridgedChain { + const ID: ChainId = *b"tbch"; + type BlockNumber = crate::RelayBlockNumber; type Hash = crate::RelayBlockHash; type Hasher = crate::RelayBlockHasher; @@ -281,14 +262,16 @@ 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)] pub struct OtherBridgedChain; impl Chain for OtherBridgedChain { + const ID: ChainId = *b"obch"; + type BlockNumber = u64; type Hash = crate::RelayBlockHash; type Hasher = crate::RelayBlockHasher; @@ -312,8 +295,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/parachains/src/weights.rs b/bridges/modules/parachains/src/weights.rs index 9182ec466117b2c642910148a1240a10ae438b8a..abddc8768947006e574bf6bca4d2301c2047199a 100644 --- a/bridges/modules/parachains/src/weights.rs +++ b/bridges/modules/parachains/src/weights.rs @@ -23,7 +23,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/millau-bridge-node +// target/release/unknown-bridge-node // benchmark // pallet // --chain=dev @@ -60,29 +60,29 @@ pub trait WeightInfo { /// Those weights are test only and must never be used in production. pub struct BridgeWeight(PhantomData); impl WeightInfo for BridgeWeight { - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) /// /// The range of component `p` is `[1, 2]`. @@ -97,29 +97,29 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) fn submit_parachain_heads_with_1kb_proof() -> Weight { // Proof Size summary in bytes: @@ -130,29 +130,29 @@ impl WeightInfo for BridgeWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) fn submit_parachain_heads_with_16kb_proof() -> Weight { // Proof Size summary in bytes: @@ -167,29 +167,29 @@ impl WeightInfo for BridgeWeight { // For backwards compatibility and tests impl WeightInfo for () { - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) /// /// The range of component `p` is `[1, 2]`. @@ -204,29 +204,29 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) fn submit_parachain_heads_with_1kb_proof() -> Weight { // Proof Size summary in bytes: @@ -237,29 +237,29 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: BridgeRialtoParachains PalletOperatingMode (r:1 w:0) + /// Storage: BridgeUnknownParachains PalletOperatingMode (r:1 w:0) /// - /// Proof: BridgeRialtoParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), + /// Proof: BridgeUnknownParachains PalletOperatingMode (max_values: Some(1), max_size: Some(1), /// added: 496, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoGrandpa ImportedHeaders (r:1 w:0) + /// Storage: BridgeUnknownGrandpa ImportedHeaders (r:1 w:0) /// - /// Proof: BridgeRialtoGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), + /// Proof: BridgeUnknownGrandpa ImportedHeaders (max_values: Some(14400), max_size: Some(68), /// added: 2048, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ParasInfo (r:1 w:1) + /// Storage: BridgeUnknownParachains ParasInfo (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: + /// Proof: BridgeUnknownParachains ParasInfo (max_values: Some(1), max_size: Some(60), added: /// 555, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHashes (r:1 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHashes (r:1 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHashes (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHashes (max_values: Some(1024), max_size: /// Some(64), added: 1549, mode: MaxEncodedLen) /// - /// Storage: BridgeRialtoParachains ImportedParaHeads (r:0 w:1) + /// Storage: BridgeUnknownParachains ImportedParaHeads (r:0 w:1) /// - /// Proof: BridgeRialtoParachains ImportedParaHeads (max_values: Some(1024), max_size: + /// Proof: BridgeUnknownParachains ImportedParaHeads (max_values: Some(1024), max_size: /// Some(196), added: 1681, mode: MaxEncodedLen) fn submit_parachain_heads_with_16kb_proof() -> Weight { // Proof Size summary in bytes: diff --git a/bridges/modules/parachains/src/weights_ext.rs b/bridges/modules/parachains/src/weights_ext.rs index 13bc9ad2bbce0f26d5946cf8198e689597e5f968..393086a85690fcc2846c1708bc788e1d67a61d66 100644 --- a/bridges/modules/parachains/src/weights_ext.rs +++ b/bridges/modules/parachains/src/weights_ext.rs @@ -31,7 +31,7 @@ use frame_support::weights::{RuntimeDbWeight, Weight}; pub const DEFAULT_PARACHAIN_HEAD_SIZE: u32 = 384; /// Number of extra bytes (excluding size of storage value itself) of storage proof, built at -/// the Rialto chain. +/// some generic chain. pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024; /// Extended weight info. diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 10b60c3006b0d657cf10dbc5a00b5a4db542d7f8..b78da5cbeeca65a4f448cbc38928894d51e8f7b4 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "pallet-bridge-relayers" description = "Module used to store relayer rewards and coordinate relayers set." -version = "0.1.0" +version = "0.7.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 } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge dependencies @@ -30,12 +33,11 @@ sp-std = { path = "../../../substrate/primitives/std", default-features = false [dev-dependencies] bp-runtime = { path = "../../primitives/runtime" } pallet-balances = { path = "../../../substrate/frame/balances" } -sp-core = { path = "../../../substrate/primitives/core" } sp-io = { path = "../../../substrate/primitives/io" } sp-runtime = { path = "../../../substrate/primitives/runtime" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-messages/std", "bp-relayers/std", 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/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index b9b98ca7e253fdb952125e2079752484e37241a2..ce66c9df48e01c829a55d03ffe4437c41f006a6b 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -325,6 +325,12 @@ pub mod pallet { rewards_account_params, new_reward, ); + + Self::deposit_event(Event::::RewardRegistered { + relayer: relayer.clone(), + rewards_account_params, + reward, + }); }, ); } @@ -369,6 +375,15 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// Relayer reward has been registered and may be claimed later. + RewardRegistered { + /// Relayer account that can claim reward. + relayer: T::AccountId, + /// Relayer can claim reward from this account. + rewards_account_params: RewardsAccountParams, + /// Reward amount. + reward: T::Reward, + }, /// Reward has been paid to the relayer. RewardPaid { /// Relayer account that has been rewarded. @@ -455,7 +470,7 @@ mod tests { use super::*; use mock::{RuntimeEvent as TestEvent, *}; - use crate::Event::RewardPaid; + use crate::Event::{RewardPaid, RewardRegistered}; use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_support::{ @@ -470,6 +485,33 @@ mod tests { System::::reset_events(); } + #[test] + fn register_relayer_reward_emit_event() { + run_test(|| { + get_ready_for_events(); + + Pallet::::register_relayer_reward( + TEST_REWARDS_ACCOUNT_PARAMS, + ®ULAR_RELAYER, + 100, + ); + + // Check if the `RewardRegistered` event was emitted. + assert_eq!( + System::::events().last(), + Some(&EventRecord { + phase: Phase::Initialization, + event: TestEvent::Relayers(RewardRegistered { + relayer: REGULAR_RELAYER, + rewards_account_params: TEST_REWARDS_ACCOUNT_PARAMS, + reward: 100 + }), + topics: vec![], + }), + ); + }); + } + #[test] fn root_cant_claim_anything() { run_test(|| { diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs index 4713ec91658afa9ad4e0c2d13055a0921cf1be88..3124787896c3e1ee20014fc21b87ccbc19e6a2c2 100644 --- a/bridges/modules/relayers/src/mock.rs +++ b/bridges/modules/relayers/src/mock.rs @@ -22,12 +22,10 @@ use bp_messages::LaneId; use bp_relayers::{ PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams, }; -use frame_support::{parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, +use frame_support::{ + derive_impl, parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight, }; +use sp_runtime::BuildStorage; pub type AccountId = u64; pub type Balance = u64; @@ -61,46 +59,17 @@ parameter_types! { pub const Lease: BlockNumber = 8; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; type Block = Block; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); type DbWeight = DbWeight; - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { - type MaxLocks = (); - type Balance = Balance; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type WeightInfo = (); - type MaxReserves = ConstU32<1>; type ReserveIdentifier = [u8; 8]; - type RuntimeHoldReason = RuntimeHoldReason; - type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; + type AccountStore = System; } impl pallet_bridge_relayers::Config for TestRuntime { diff --git a/bridges/modules/relayers/src/weights.rs b/bridges/modules/relayers/src/weights.rs index 2e064a3936df3a0661fc08af48be87520c04dba0..c2c065b0c0a270a254a60dccb62465d6c2fa4aa6 100644 --- a/bridges/modules/relayers/src/weights.rs +++ b/bridges/modules/relayers/src/weights.rs @@ -23,7 +23,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/millau-bridge-node +// target/release/rip-bridge-node // benchmark // pallet // --chain=dev diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index 56b9139d7d5f6771dd0dd048ff7a791a7e099c4c..20f8ff4407b2ad9882c64b334fa557a6c7dc4ef2 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "pallet-xcm-bridge-hub-router" description = "Bridge hub interface for sibling/parent chains with dynamic fees support." -version = "0.1.0" +version = "0.5.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 } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } # Bridge dependencies @@ -34,7 +37,7 @@ sp-io = { path = "../../../substrate/primitives/io" } sp-std = { path = "../../../substrate/primitives/std" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-xcm-bridge-hub-router/std", "codec/std", diff --git a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs index c4d1e3971e74777668b8bfa4dbcfdb88fbac3779..c4f9f534c1a479cd7dc4ba545353b9d92c45d2c8 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(Location::new( Self::UniversalLocation::get().len() as u8, - X1(GlobalConsensus(Self::BridgedNetworkId::get().unwrap())), - ) + [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 5cf94fc83fd9dffda2e1fa614608eb53ea13922e..f219be78f9e1b5469fb752eed3f662c954d0ec42 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -80,7 +80,7 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Universal location of this runtime. - type UniversalLocation: Get; + type UniversalLocation: Get; /// The bridged network that this config is for if specified. /// Also used for filtering `Bridges` by `BridgedNetworkId`. /// If not specified, allows all networks pass through. @@ -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; @@ -233,9 +235,9 @@ type ViaBridgeHubExporter = SovereignPaidRemoteExporter< impl, I: 'static> ExporterFor for Pallet { fn exporter_for( network: &NetworkId, - remote_location: &InteriorMultiLocation, + remote_location: &InteriorLocation, message: &Xcm<()>, - ) -> Option<(MultiLocation, Option)> { + ) -> Option<(Location, Option)> { // ensure that the message is sent to the expected bridged network (if specified). if let Some(bridged_network) = T::BridgedNetworkId::get() { if *network != bridged_network { @@ -266,7 +268,7 @@ impl, I: 'static> ExporterFor for Pallet { // take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset` let base_fee = match maybe_payment { Some(payment) => match payment { - MultiAsset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount, + Asset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount, invalid_asset => { log::error!( target: LOG_TARGET, @@ -316,15 +318,16 @@ impl, I: 'static> SendXcm for Pallet { type Ticket = (u32, ::Ticket); fn validate( - dest: &mut Option, + 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,10 +335,22 @@ 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 // - // the cost will include both cost of: (1) to-sibling bridg hub delivery (returned by + // the cost will include both cost of: (1) to-sibling bridge hub delivery (returned by // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by // `Self::exporter_for`) ViaBridgeHubExporter::::validate(dest, xcm) @@ -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; @@ -430,7 +446,7 @@ mod tests { run_test(|| { assert_eq!( send_xcm::( - MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]), vec![].into(), ), Err(SendError::NotApplicable), @@ -443,7 +459,7 @@ mod tests { run_test(|| { assert_eq!( send_xcm::( - MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))), + Location::new(2, [GlobalConsensus(Rococo), Parachain(1000)]), vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into(), ), Err(SendError::ExceedsMaxMessageSize), @@ -451,17 +467,30 @@ 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(|| { - let dest = MultiLocation::new(2, X1(GlobalConsensus(BridgedNetworkId::get()))); + let dest = Location::new(2, [GlobalConsensus(BridgedNetworkId::get())]); let xcm: Xcm<()> = vec![ClearOrigin].into(); let msg_size = xcm.encoded_size(); // initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE` let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE; assert_eq!( - XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm.clone())) + XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut Some(xcm.clone())) .unwrap() .1 .get(0), @@ -488,17 +517,11 @@ 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::( + Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), + vec![ClearOrigin].into(), + ) + .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); @@ -511,17 +534,11 @@ 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::( + Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), + vec![ClearOrigin].into(), + ) + .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); assert!( @@ -536,17 +553,11 @@ 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::( + Location::new(2, [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 2152b4eb28f336d86fcd531077c8ea65a3138fe5..54e10966d51b23e7be5010b39cb9cb7d6a3b0118 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -19,13 +19,12 @@ use crate as pallet_xcm_bridge_hub_router; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; -use frame_support::{construct_runtime, parameter_types}; -use frame_system::EnsureRoot; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, ConstU128, IdentityLookup}, - BuildStorage, +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::*; use xcm_builder::{NetworkExportTable, NetworkExportTableItem}; @@ -50,9 +49,9 @@ construct_runtime! { parameter_types! { pub ThisNetworkId: NetworkId = Polkadot; pub BridgedNetworkId: NetworkId = Kusama; - pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ThisNetworkId::get()), Parachain(1000)); - pub SiblingBridgeHubLocation: MultiLocation = ParentThen(X1(Parachain(1002))).into(); - pub BridgeFeeAsset: AssetId = MultiLocation::parent().into(); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetworkId::get()), Parachain(1000)].into(); + pub SiblingBridgeHubLocation: Location = ParentThen([Parachain(1002)].into()).into(); + pub BridgeFeeAsset: AssetId = Location::parent().into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( @@ -62,32 +61,12 @@ parameter_types! { Some((BridgeFeeAsset::get(), BASE_FEE).into()) ) ]; + pub UnknownXcmVersionLocation: Location = Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(9999)]); } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; type Block = Block; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { @@ -96,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; @@ -105,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: &Location) -> Option { + if LocationValue::contains(dest) { + return None + } + Some(XCM_VERSION) + } +} + pub struct TestToBridgeHubSender; impl TestToBridgeHubSender { @@ -117,7 +110,7 @@ impl SendXcm for TestToBridgeHubSender { type Ticket = (); fn validate( - _destination: &mut Option, + _destination: &mut Option, _message: &mut Option>, ) -> SendResult { Ok(((), (BridgeFeeAsset::get(), HRMP_FEE).into())) diff --git a/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/bridges/modules/xcm-bridge-hub-router/src/weights.rs index 62936e997f3cd0aaea17859b49d7c0edf6b65989..b0c8fc6252cd5e6eaa968cce06636a308e1c7e05 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/weights.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/weights.rs @@ -23,7 +23,7 @@ //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/millau-bridge-node +// target/release/rip-bridge-node // benchmark // pallet // --chain=dev diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e10119e864953f1777c43151092ae43a5e594b8c --- /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.2.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 = { workspace = true } +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..94ec8b5f106fdb9ce5e229a41579d26e789b5673 --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -0,0 +1,206 @@ +// 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, +{ + type Ticket = ( + SenderAndLane, + as MessagesBridge>::SendMessageArgs, + XcmHash, + ); + + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> Result<(Self::Ticket, Assets), 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, + )?; + + let bridge_message = MessagesPallet::::validate_message(sender_and_lane.lane, &blob) + .map_err(|e| { + log::debug!( + target: LOG_TARGET, + "XCM message {:?} cannot be exported because of bridge error {:?} on bridge {:?}", + id, + e, + sender_and_lane.lane, + ); + SendError::Transport("BridgeValidateError") + })?; + + Ok(((sender_and_lane, bridge_message, id), price)) + } + + fn deliver((sender_and_lane, bridge_message, id): Self::Ticket) -> Result { + let lane_id = sender_and_lane.lane; + let artifacts = MessagesPallet::::send_message(bridge_message); + + 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, + ); + + 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() -> InteriorLocation { + [GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID)].into() + } + + fn universal_destination() -> InteriorLocation { + 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..60b988497fc59e94cbfe1a6e30cd6f3039d8c331 --- /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: &InteriorLocation, + dest: (&NetworkId, &InteriorLocation), + ) -> Option { + let source = source.clone().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..4c09bce56d73eea717ad5149084e2ae337e48e87 --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -0,0 +1,317 @@ +// 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::{ + target_chain::{DispatchMessage, MessageDispatch}, + LaneId, +}; +use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, 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)] +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)] +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; +} + +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 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 BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into(); + pub const NonBridgedRelayNetwork: NetworkId = NetworkId::Rococo; + pub const BridgeReserve: Balance = 100_000; + pub UniversalLocation: InteriorLocation = [ + GlobalConsensus(RelayNetwork::get()), + Parachain(THIS_BRIDGE_HUB_ID), + ].into(); + 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: Location::new(1, [Parachain(SIBLING_ASSET_HUB_ID)]), + lane: TEST_LANE_ID, + }; + pub BridgedDestination: InteriorLocation = [ + Parachain(BRIDGED_ASSET_HUB_ID) + ].into(); + pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = 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 { + const ID: ChainId = *b"tuch"; + 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 { + const ID: ChainId = *b"tuch"; + 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-kusama/src/lib.rs b/bridges/primitives/chain-asset-hub-kusama/src/lib.rs deleted file mode 100644 index 94016c1da0cb1235dec6717bd6da79ff4dab74f4..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-asset-hub-kusama/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 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 with configuration which reflects AssetHubKusama runtime setup. - -#![cfg_attr(not(feature = "std"), no_std)] - -use codec::{Decode, Encode}; -use scale_info::TypeInfo; - -pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; - -/// `AssetHubKusama` Runtime `Call` enum. -/// -/// The enum represents a subset of possible `Call`s we can send to `AssetHubKusama` chain. -/// Ideally this code would be auto-generated from metadata, because we want to -/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. -/// -/// All entries here (like pretty much in the entire file) must be kept in sync with -/// `AssetHubKusama` `construct_runtime`, so that we maintain SCALE-compatibility. -#[allow(clippy::large_enum_variant)] -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] -pub enum Call { - /// `ToPolkadotXcmRouter` bridge pallet. - #[codec(index = 43)] - ToPolkadotXcmRouter(XcmBridgeHubRouterCall), -} - -frame_support::parameter_types! { - /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. - pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); - - /// Base delivery fee to `BridgeHubKusama`. - /// (initially was calculated `170733333` + `10%` by test `BridgeHubKusama::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) - pub const BridgeHubKusamaBaseFeeInDots: u128 = 187806666; -} diff --git a/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml b/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml deleted file mode 100644 index 9c1b1a1f326cd77daebca36ed64bbc496b673837..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-asset-hub-polkadot/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "bp-asset-hub-polkadot" -description = "Primitives of AssetHubPolkadot parachain runtime." -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } - -# Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } - -# Bridge Dependencies -bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } - -[features] -default = [ "std" ] -std = [ - "bp-xcm-bridge-hub-router/std", - "codec/std", - "frame-support/std", - "scale-info/std", - "sp-runtime/std", -] diff --git a/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs b/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs deleted file mode 100644 index 486fba60e1f8836d0eec2feece7919205cf6c372..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-asset-hub-polkadot/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 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 with configuration which reflects AssetHubPolkadot runtime setup. - -#![cfg_attr(not(feature = "std"), no_std)] - -use codec::{Decode, Encode}; -use scale_info::TypeInfo; - -pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; - -/// `AssetHubPolkadot` Runtime `Call` enum. -/// -/// The enum represents a subset of possible `Call`s we can send to `AssetHubPolkadot` chain. -/// Ideally this code would be auto-generated from metadata, because we want to -/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. -/// -/// All entries here (like pretty much in the entire file) must be kept in sync with -/// `AssetHubPolkadot` `construct_runtime`, so that we maintain SCALE-compatibility. -#[allow(clippy::large_enum_variant)] -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] -pub enum Call { - /// `ToKusamaXcmRouter` bridge pallet. - #[codec(index = 43)] - ToKusamaXcmRouter(XcmBridgeHubRouterCall), -} - -frame_support::parameter_types! { - /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. - pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); - - /// Base delivery fee to `BridgeHubPolkadot`. - /// (initially was calculated `51220000` + `10%` by test `BridgeHubPolkadot::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) - pub const BridgeHubPolkadotBaseFeeInDots: u128 = 56342000; -} diff --git a/bridges/primitives/chain-asset-hub-rococo/Cargo.toml b/bridges/primitives/chain-asset-hub-rococo/Cargo.toml index 088510adcec63b6d30c76b965100823c0d466827..4dfa149e0ea9ab4e0ac1804844a0c128f15bd5bb 100644 --- a/bridges/primitives/chain-asset-hub-rococo/Cargo.toml +++ b/bridges/primitives/chain-asset-hub-rococo/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-asset-hub-rococo" description = "Primitives of AssetHubRococo parachain runtime." -version = "0.1.0" +version = "0.4.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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } @@ -17,7 +20,7 @@ frame-support = { path = "../../../substrate/frame/support", default-features = bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-xcm-bridge-hub-router/std", "codec/std", diff --git a/bridges/primitives/chain-asset-hub-rococo/src/lib.rs b/bridges/primitives/chain-asset-hub-rococo/src/lib.rs index f1ea1e9c88c9ad5285d9c4561e372956638e4111..de2e9ae856d1f8756f0a2a6b9cae3da3e265e76e 100644 --- a/bridges/primitives/chain-asset-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-asset-hub-rococo/src/lib.rs @@ -34,18 +34,14 @@ pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; #[allow(clippy::large_enum_variant)] #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum Call { - /// `ToWococoXcmRouter` bridge pallet. - #[codec(index = 43)] - ToWococoXcmRouter(XcmBridgeHubRouterCall), + /// `ToWestendXcmRouter` bridge pallet. + #[codec(index = 45)] + ToWestendXcmRouter(XcmBridgeHubRouterCall), } frame_support::parameter_types! { /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); - - /// Base delivery fee to `BridgeHubRococo`. - /// (initially was calculated by test `BridgeHubRococo::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) - pub const BridgeHubRococoBaseFeeInRocs: u128 = 1214739988; } /// Identifier of AssetHubRococo in the Rococo relay chain. diff --git a/bridges/primitives/chain-asset-hub-kusama/Cargo.toml b/bridges/primitives/chain-asset-hub-westend/Cargo.toml similarity index 80% rename from bridges/primitives/chain-asset-hub-kusama/Cargo.toml rename to bridges/primitives/chain-asset-hub-westend/Cargo.toml index 3e53f9407ffcee75dee896f45565eea1757a7640..c9bd437562b86a97cdf2807c18b4905e695d1a5e 100644 --- a/bridges/primitives/chain-asset-hub-kusama/Cargo.toml +++ b/bridges/primitives/chain-asset-hub-westend/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "bp-asset-hub-kusama" -description = "Primitives of AssetHubKusama parachain runtime." -version = "0.1.0" +name = "bp-asset-hub-westend" +description = "Primitives of AssetHubWestend parachain runtime." +version = "0.3.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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } @@ -17,7 +20,7 @@ frame-support = { path = "../../../substrate/frame/support", default-features = bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-xcm-bridge-hub-router/std", "codec/std", diff --git a/bridges/primitives/chain-asset-hub-wococo/src/lib.rs b/bridges/primitives/chain-asset-hub-westend/src/lib.rs similarity index 74% rename from bridges/primitives/chain-asset-hub-wococo/src/lib.rs rename to bridges/primitives/chain-asset-hub-westend/src/lib.rs index 5ce66054d64aa8b0f2503c4be19895937cf7ac0e..9de1c88098942cdf7bd0684462a95ac3de412490 100644 --- a/bridges/primitives/chain-asset-hub-wococo/src/lib.rs +++ b/bridges/primitives/chain-asset-hub-westend/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -//! Module with configuration which reflects AssetHubWococo runtime setup. +//! Module with configuration which reflects AssetHubWestend runtime setup. #![cfg_attr(not(feature = "std"), no_std)] @@ -23,30 +23,26 @@ use scale_info::TypeInfo; pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; -/// `AssetHubWococo` Runtime `Call` enum. +/// `AssetHubWestend` Runtime `Call` enum. /// -/// The enum represents a subset of possible `Call`s we can send to `AssetHubWococo` chain. +/// The enum represents a subset of possible `Call`s we can send to `AssetHubWestend` chain. /// Ideally this code would be auto-generated from metadata, because we want to /// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s. /// /// All entries here (like pretty much in the entire file) must be kept in sync with -/// `AssetHubWococo` `construct_runtime`, so that we maintain SCALE-compatibility. +/// `AssetHubWestend` `construct_runtime`, so that we maintain SCALE-compatibility. #[allow(clippy::large_enum_variant)] #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum Call { /// `ToRococoXcmRouter` bridge pallet. - #[codec(index = 44)] + #[codec(index = 34)] ToRococoXcmRouter(XcmBridgeHubRouterCall), } frame_support::parameter_types! { /// Some sane weight to execute `xcm::Transact(pallet-xcm-bridge-hub-router::Call::report_bridge_status)`. pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); - - /// Base delivery fee to `BridgeHubWococo`. - /// (initially was calculated by test `BridgeHubWococo::can_calculate_weight_for_paid_export_message_with_reserve_transfer`) - pub const BridgeHubWococoBaseFeeInWocs: u128 = 1214739988; } -/// Identifier of AssetHubWococo in the Wococo relay chain. -pub const ASSET_HUB_WOCOCO_PARACHAIN_ID: u32 = 1000; +/// Identifier of AssetHubWestend in the Westend relay chain. +pub const ASSET_HUB_WESTEND_PARACHAIN_ID: u32 = 1000; diff --git a/bridges/primitives/chain-asset-hub-wococo/Cargo.toml b/bridges/primitives/chain-asset-hub-wococo/Cargo.toml deleted file mode 100644 index e1a5a262157afd77415ef928ba6bf0e61e070820..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-asset-hub-wococo/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "bp-asset-hub-wococo" -description = "Primitives of AssetHubWococo parachain runtime." -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } - -# Substrate Dependencies -frame-support = { path = "../../../substrate/frame/support", default-features = false } - -# Bridge Dependencies -bp-xcm-bridge-hub-router = { path = "../xcm-bridge-hub-router", default-features = false } - -[features] -default = [ "std" ] -std = [ - "bp-xcm-bridge-hub-router/std", - "codec/std", - "frame-support/std", - "scale-info/std", -] diff --git a/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml b/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml index 24cf7236d45333254dd62073dfebcd85ec526362..d35eefa1c45c3f7cf479dcea2ee4da87dbc31627 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-bridge-hub-cumulus" -description = "Primitives of BridgeHubRococo parachain runtime." -version = "0.1.0" +description = "Primitives for BridgeHub parachain runtimes." +version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -24,7 +27,7 @@ sp-std = { path = "../../../substrate/primitives/std", default-features = false polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-messages/std", "bp-polkadot-core/std", diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs index cd281324ee55fa9a0c4bfcfe454b34ecba9bfd07..c49aa4b856397d28746d017fd8333ae3ad10655e 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Primitives of all Cumulus-based bridge hubs. + +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use bp_polkadot_core::{ @@ -54,6 +57,12 @@ const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(constants::WEIGHT_REF_TI .saturating_div(2) .set_proof_size(polkadot_primitives::MAX_POV_SIZE as u64); +/// We allow for 2 seconds of compute with a 6 second average block. +const MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING: Weight = Weight::from_parts( + constants::WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + polkadot_primitives::MAX_POV_SIZE as u64, +); + /// All cumulus bridge hubs assume that about 5 percent of the block weight is consumed by /// `on_initialize` handlers. This is used to limit the maximal weight of a single extrinsic. /// @@ -61,6 +70,7 @@ const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(constants::WEIGHT_REF_TI pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5); parameter_types! { + /// Size limit of the Cumulus-based bridge hub blocks. pub BlockLength: limits::BlockLength = limits::BlockLength::max_with_normal_ratio( 5 * 1024 * 1024, NORMAL_DISPATCH_RATIO, @@ -73,6 +83,7 @@ parameter_types! { pub const ExtrinsicBaseWeight: Weight = Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS, 0) .saturating_mul(125_000); + /// Weight limit of the Cumulus-based bridge hub blocks. pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder() .base_block(BlockExecutionWeight::get()) .for_class(DispatchClass::all(), |weights| { @@ -91,6 +102,26 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); + + /// Weight limit of the Cumulus-based bridge hub blocks when async backing is enabled. + pub BlockWeightsForAsyncBacking: limits::BlockWeights = limits::BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + // Operational transactions have an extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING, + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } /// Public key of the chain account that may be used to verify signatures. diff --git a/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml b/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml index 387f5e8ade6e7dc501159266a94168dd7055a8c1..8d71b3f5eb7646a81af55c0030814c604ead82ef 100644 --- a/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-bridge-hub-kusama" -description = "Primitives of BridgeHubRococo parachain runtime." -version = "0.1.0" +description = "Primitives of BridgeHubKusama parachain runtime." +version = "0.6.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -21,7 +24,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-bridge-hub-cumulus/std", "bp-messages/std", diff --git a/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs b/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs index 66e0dad05895c7df72cda7158c39e589b78ada73..576e3dbee80d0babbdb7c0bbdfc420c5a636b68b 100644 --- a/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-kusama/src/lib.rs @@ -17,12 +17,13 @@ //! Module with configuration which reflects BridgeHubKusama runtime setup (AccountId, Headers, //! Hashes...) +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use bp_bridge_hub_cumulus::*; use bp_messages::*; use bp_runtime::{ - decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain, }; use frame_support::{ dispatch::DispatchClass, @@ -35,6 +36,8 @@ use sp_runtime::RuntimeDebug; pub struct BridgeHubKusama; impl Chain for BridgeHubKusama { + const ID: ChainId = *b"bhks"; + type BlockNumber = BlockNumber; type Hash = Hash; type Hasher = Hasher; @@ -61,6 +64,15 @@ impl Parachain for BridgeHubKusama { const PARACHAIN_ID: u32 = BRIDGE_HUB_KUSAMA_PARACHAIN_ID; } +impl ChainWithMessages for BridgeHubKusama { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + WITH_BRIDGE_HUB_KUSAMA_MESSAGES_PALLET_NAME; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; +} + /// Public key of the chain account that may be used to verify signatures. pub type AccountSigner = MultiSigner; diff --git a/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml b/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml index 40b386e22d224da198043f44210a8c1de65af3ac..4e89e8a5c9a173bc9a084af9e7c609a6bae287a8 100644 --- a/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-bridge-hub-polkadot" -description = "Primitives of BridgeHubWococo parachain runtime." -version = "0.1.0" +description = "Primitives of BridgeHubPolkadot parachain runtime." +version = "0.6.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -22,7 +25,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-bridge-hub-cumulus/std", "bp-messages/std", diff --git a/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs b/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs index c3661c1adcada619bb319ae8853c20623565c0c9..6db389c92994d74fb0d8176509cd81d64b806df2 100644 --- a/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-polkadot/src/lib.rs @@ -17,12 +17,13 @@ //! Module with configuration which reflects BridgeHubPolkadot runtime setup //! (AccountId, Headers, Hashes...) +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use bp_bridge_hub_cumulus::*; use bp_messages::*; use bp_runtime::{ - decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain, }; use frame_support::dispatch::DispatchClass; use sp_runtime::RuntimeDebug; @@ -32,6 +33,8 @@ use sp_runtime::RuntimeDebug; pub struct BridgeHubPolkadot; impl Chain for BridgeHubPolkadot { + const ID: ChainId = *b"bhpd"; + type BlockNumber = BlockNumber; type Hash = Hash; type Hasher = Hasher; @@ -58,6 +61,16 @@ impl Parachain for BridgeHubPolkadot { const PARACHAIN_ID: u32 = BRIDGE_HUB_POLKADOT_PARACHAIN_ID; } +impl ChainWithMessages for BridgeHubPolkadot { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + WITH_BRIDGE_HUB_POLKADOT_MESSAGES_PALLET_NAME; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; +} + /// Identifier of BridgeHubPolkadot in the Polkadot relay chain. pub const BRIDGE_HUB_POLKADOT_PARACHAIN_ID: u32 = 1002; diff --git a/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml b/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml index 05b8163e9fcaacc8f32c0b45bfafffc882e0f1be..1643d934a982ec0795fc370b221dc35d36d3b492 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-bridge-hub-rococo" description = "Primitives of BridgeHubRococo parachain runtime." -version = "0.1.0" +version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -21,7 +24,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-bridge-hub-cumulus/std", "bp-messages/std", diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs index ed3ef440c83d6a5137d93725b81a73f783911bd1..c4e697fbe9526b85c7f10cf739c6893d50190fe9 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -17,12 +17,13 @@ //! Module with configuration which reflects BridgeHubRococo runtime setup (AccountId, Headers, //! Hashes...) +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use bp_bridge_hub_cumulus::*; use bp_messages::*; use bp_runtime::{ - decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain, }; use frame_support::dispatch::DispatchClass; use sp_runtime::{MultiAddress, MultiSigner, RuntimeDebug}; @@ -32,6 +33,8 @@ use sp_runtime::{MultiAddress, MultiSigner, RuntimeDebug}; pub struct BridgeHubRococo; impl Chain for BridgeHubRococo { + const ID: ChainId = *b"bhro"; + type BlockNumber = BlockNumber; type Hash = Hash; type Hasher = Hasher; @@ -47,7 +50,7 @@ impl Chain for BridgeHubRococo { } fn max_extrinsic_weight() -> Weight { - BlockWeights::get() + BlockWeightsForAsyncBacking::get() .get(DispatchClass::Normal) .max_extrinsic .unwrap_or(Weight::MAX) @@ -58,6 +61,16 @@ impl Parachain for BridgeHubRococo { const PARACHAIN_ID: u32 = BRIDGE_HUB_ROCOCO_PARACHAIN_ID; } +impl ChainWithMessages for BridgeHubRococo { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; +} + /// Public key of the chain account that may be used to verify signatures. pub type AccountSigner = MultiSigner; @@ -68,15 +81,31 @@ pub type Address = MultiAddress; pub const BRIDGE_HUB_ROCOCO_PARACHAIN_ID: u32 = 1013; /// Name of the With-BridgeHubRococo messages pallet instance that is deployed at bridged chains. -pub const WITH_BRIDGE_HUB_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_NAME: &str = - "BridgeWococoToRococoMessages"; +pub const WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME: &str = "BridgeRococoMessages"; /// Name of the With-BridgeHubRococo bridge-relayers pallet instance that is deployed at bridged /// chains. pub const WITH_BRIDGE_HUB_ROCOCO_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; -/// Pallet index of `BridgeRococoToWococoMessages: pallet_bridge_messages::`. -pub const WITH_BRIDGE_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_INDEX: u8 = 46; +/// 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); + +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 = 59_034_266; + + /// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. + /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) + 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 = 5_380_829_647; +} diff --git a/bridges/primitives/chain-bridge-hub-wococo/Cargo.toml b/bridges/primitives/chain-bridge-hub-westend/Cargo.toml similarity index 85% rename from bridges/primitives/chain-bridge-hub-wococo/Cargo.toml rename to bridges/primitives/chain-bridge-hub-westend/Cargo.toml index 17c134f4412f7d74a45e089c920bd260bcd95008..32a7850c5392fd892ec40e9968fa365ec59cbba3 100644 --- a/bridges/primitives/chain-bridge-hub-wococo/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-westend/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "bp-bridge-hub-wococo" -description = "Primitives of BridgeHubWococo parachain runtime." -version = "0.1.0" +name = "bp-bridge-hub-westend" +description = "Primitives of BridgeHubWestend parachain runtime." +version = "0.3.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -22,7 +25,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-bridge-hub-cumulus/std", "bp-messages/std", diff --git a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4af895cc6d328bdb350fa95b0e0a74f0cc731b04 --- /dev/null +++ b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs @@ -0,0 +1,102 @@ +// Copyright (C) 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 with configuration which reflects BridgeHubWestend runtime setup +//! (AccountId, Headers, Hashes...) + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use bp_bridge_hub_cumulus::*; +use bp_messages::*; +use bp_runtime::{ + decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, ChainId, Parachain, +}; +use frame_support::dispatch::DispatchClass; +use sp_runtime::RuntimeDebug; + +/// BridgeHubWestend parachain. +#[derive(RuntimeDebug)] +pub struct BridgeHubWestend; + +impl Chain for BridgeHubWestend { + const ID: ChainId = *b"bhwd"; + + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Nonce = Nonce; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) + } + + fn max_extrinsic_weight() -> Weight { + BlockWeightsForAsyncBacking::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) + } +} + +impl Parachain for BridgeHubWestend { + const PARACHAIN_ID: u32 = BRIDGE_HUB_WESTEND_PARACHAIN_ID; +} + +impl ChainWithMessages for BridgeHubWestend { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; +} + +/// Identifier of BridgeHubWestend in the Westend relay chain. +pub const BRIDGE_HUB_WESTEND_PARACHAIN_ID: u32 = 1002; + +/// Name of the With-BridgeHubWestend messages pallet instance that is deployed at bridged chains. +pub const WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME: &str = "BridgeWestendMessages"; + +/// Name of the With-BridgeHubWestend bridge-relayers pallet instance that is deployed at bridged +/// chains. +pub const WITH_BRIDGE_HUB_WESTEND_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; + +/// Pallet index of `BridgeRococoMessages: pallet_bridge_messages::`. +pub const WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX: u8 = 44; + +decl_bridge_finality_runtime_apis!(bridge_hub_westend); +decl_bridge_messages_runtime_apis!(bridge_hub_westend); + +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 = 17_756_830_000; + + /// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. + /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) + 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 = 1_618_309_961_344; +} diff --git a/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs deleted file mode 100644 index 1e147d367d0e29240c30af060bcebad23b305414..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-bridge-hub-wococo/src/lib.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 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 with configuration which reflects BridgeHubWococo runtime setup -//! (AccountId, Headers, Hashes...) - -#![cfg_attr(not(feature = "std"), no_std)] - -pub use bp_bridge_hub_cumulus::*; -use bp_messages::*; -use bp_runtime::{ - decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, Chain, Parachain, -}; -use frame_support::dispatch::DispatchClass; -use sp_runtime::RuntimeDebug; - -/// BridgeHubWococo parachain. -#[derive(RuntimeDebug)] -pub struct BridgeHubWococo; - -impl Chain for BridgeHubWococo { - type BlockNumber = BlockNumber; - type Hash = Hash; - type Hasher = Hasher; - type Header = Header; - - type AccountId = AccountId; - type Balance = Balance; - type Nonce = Nonce; - type Signature = Signature; - - fn max_extrinsic_size() -> u32 { - *BlockLength::get().max.get(DispatchClass::Normal) - } - - fn max_extrinsic_weight() -> Weight { - BlockWeights::get() - .get(DispatchClass::Normal) - .max_extrinsic - .unwrap_or(Weight::MAX) - } -} - -impl Parachain for BridgeHubWococo { - const PARACHAIN_ID: u32 = BRIDGE_HUB_WOCOCO_PARACHAIN_ID; -} - -/// Identifier of BridgeHubWococo in the Wococo relay chain. -pub const BRIDGE_HUB_WOCOCO_PARACHAIN_ID: u32 = 1014; - -/// Name of the With-BridgeHubWococo messages pallet instance that is deployed at bridged chains. -pub const WITH_BRIDGE_HUB_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_NAME: &str = - "BridgeRococoToWococoMessages"; - -/// Name of the With-BridgeHubWococo bridge-relayers pallet instance that is deployed at bridged -/// chains. -pub const WITH_BRIDGE_HUB_WOCOCO_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; - -/// Pallet index of `BridgeWococoToRococoMessages: pallet_bridge_messages::`. -pub const WITH_BRIDGE_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_INDEX: u8 = 45; - -decl_bridge_finality_runtime_apis!(bridge_hub_wococo); -decl_bridge_messages_runtime_apis!(bridge_hub_wococo); diff --git a/bridges/primitives/chain-kusama/Cargo.toml b/bridges/primitives/chain-kusama/Cargo.toml index 2d63c3f374fb50c94e2572c09511bf20d63b61e4..0660f34602389d1cf9e1ec88b57d1a9aeb9a3830 100644 --- a/bridges/primitives/chain-kusama/Cargo.toml +++ b/bridges/primitives/chain-kusama/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-kusama" description = "Primitives of Kusama runtime." -version = "0.1.0" +version = "0.5.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -21,7 +24,7 @@ sp-api = { path = "../../../substrate/primitives/api", default-features = false sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-polkadot-core/std", diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs index d5748aa132cea6caddeabd102b62345bbdc6153f..e3b4d0520f61c858b54d78dfa4a45f57bac411fb 100644 --- a/bridges/primitives/chain-kusama/src/lib.rs +++ b/bridges/primitives/chain-kusama/src/lib.rs @@ -14,36 +14,39 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Primitives of the Kusama chain. + +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// RuntimeApi generated functions -#![allow(clippy::too_many_arguments)] pub use bp_polkadot_core::*; use bp_header_chain::ChainWithGrandpa; -use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; +use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId}; use frame_support::weights::Weight; /// Kusama Chain pub struct Kusama; impl Chain for Kusama { - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; + const ID: ChainId = *b"ksma"; + + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; - type AccountId = ::AccountId; - type Balance = ::Balance; - type Nonce = ::Nonce; - type Signature = ::Signature; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = Nonce; + type Signature = Signature; fn max_extrinsic_size() -> u32 { - PolkadotLike::max_extrinsic_size() + max_extrinsic_size() } fn max_extrinsic_weight() -> Weight { - PolkadotLike::max_extrinsic_weight() + max_extrinsic_weight() } } @@ -52,8 +55,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 d748f5aa933c1493d7d1f248ba7a7d0013a5a008..15c824fcbdb31c2c84f63bd56d4d0b3f90efc5b1 100644 --- a/bridges/primitives/chain-polkadot-bulletin/Cargo.toml +++ b/bridges/primitives/chain-polkadot-bulletin/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-polkadot-bulletin" description = "Primitives of Polkadot Bulletin chain runtime." -version = "0.1.0" -authors = ["Parity Technologies "] -edition = "2021" +version = "0.4.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, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } @@ -26,7 +29,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-messages/std", @@ -35,6 +38,7 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-api/std", "sp-runtime/std", "sp-std/std", diff --git a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs index fcc6e90eb1b298e703b7c4b1a83c914f0a018031..f2eebf9312470a42e1d3a1c7d67ab8b7a38af189 100644 --- a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs @@ -20,14 +20,14 @@ #![cfg_attr(not(feature = "std"), no_std)] use bp_header_chain::ChainWithGrandpa; -use bp_messages::MessageNonce; +use bp_messages::{ChainWithMessages, MessageNonce}; use bp_runtime::{ decl_bridge_finality_runtime_apis, decl_bridge_messages_runtime_apis, extensions::{ CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, GenericSignedExtension, GenericSignedExtensionSchema, }, - Chain, TransactionEra, + Chain, ChainId, TransactionEra, }; use codec::{Decode, Encode}; use frame_support::{ @@ -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. @@ -177,6 +177,8 @@ parameter_types! { pub struct PolkadotBulletin; impl Chain for PolkadotBulletin { + const ID: ChainId = *b"pdbc"; + type BlockNumber = BlockNumber; type Hash = Hash; type Hasher = Hasher; @@ -207,8 +209,18 @@ 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; +} + +impl ChainWithMessages for PolkadotBulletin { + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + WITH_POLKADOT_BULLETIN_MESSAGES_PALLET_NAME; + + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; } 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 539b10ef9c68f490756d4f7e0453b25c9b7fc5c7..6421b7f40106e404eeba04be72f5448fd5f65159 100644 --- a/bridges/primitives/chain-polkadot/Cargo.toml +++ b/bridges/primitives/chain-polkadot/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-polkadot" description = "Primitives of Polkadot runtime." -version = "0.1.0" +version = "0.5.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -21,7 +24,7 @@ sp-api = { path = "../../../substrate/primitives/api", default-features = false sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-polkadot-core/std", diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs index 61c8ca927d807ac6c169bb30d324f9720118e010..fc5e10308a8e33463a74c041f157daaef09cc9c8 100644 --- a/bridges/primitives/chain-polkadot/src/lib.rs +++ b/bridges/primitives/chain-polkadot/src/lib.rs @@ -14,36 +14,41 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Primitives of the Polkadot chain. + +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// RuntimeApi generated functions -#![allow(clippy::too_many_arguments)] pub use bp_polkadot_core::*; use bp_header_chain::ChainWithGrandpa; -use bp_runtime::{decl_bridge_finality_runtime_apis, extensions::PrevalidateAttests, Chain}; +use bp_runtime::{ + decl_bridge_finality_runtime_apis, extensions::PrevalidateAttests, Chain, ChainId, +}; use frame_support::weights::Weight; /// Polkadot Chain pub struct Polkadot; impl Chain for Polkadot { - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; + const ID: ChainId = *b"pdot"; + + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; - type AccountId = ::AccountId; - type Balance = ::Balance; - type Nonce = ::Nonce; - type Signature = ::Signature; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = Nonce; + type Signature = Signature; fn max_extrinsic_size() -> u32 { - PolkadotLike::max_extrinsic_size() + max_extrinsic_size() } fn max_extrinsic_weight() -> Weight { - PolkadotLike::max_extrinsic_weight() + max_extrinsic_weight() } } @@ -52,8 +57,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 3c4d3917bc219bfd7dd65ec9e8a0c283fe834e49..de373f0ae64b8d9a8124dbcdfcca3c2bf05e2787 100644 --- a/bridges/primitives/chain-rococo/Cargo.toml +++ b/bridges/primitives/chain-rococo/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-rococo" description = "Primitives of Rococo runtime." -version = "0.1.0" +version = "0.6.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -16,12 +19,12 @@ bp-runtime = { path = "../runtime", default-features = false } # Substrate Based Dependencies +frame-support = { path = "../../../substrate/frame/support", default-features = false } sp-api = { path = "../../../substrate/primitives/api", default-features = false } sp-std = { path = "../../../substrate/primitives/std", default-features = false } -frame-support = { path = "../../../substrate/frame/support", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-polkadot-core/std", diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs index 5436ad846468cda632aab9ffcf46748f4c1546f8..f1b256f0f090f048cc8db3a16c112ed8b938f6ce 100644 --- a/bridges/primitives/chain-rococo/src/lib.rs +++ b/bridges/primitives/chain-rococo/src/lib.rs @@ -14,36 +14,39 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Primitives of the Rococo chain. + +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// RuntimeApi generated functions -#![allow(clippy::too_many_arguments)] pub use bp_polkadot_core::*; use bp_header_chain::ChainWithGrandpa; -use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; -use frame_support::{parameter_types, weights::Weight}; +use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId}; +use frame_support::weights::Weight; /// Rococo Chain pub struct Rococo; impl Chain for Rococo { - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; + const ID: ChainId = *b"roco"; + + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; - type AccountId = ::AccountId; - type Balance = ::Balance; - type Nonce = ::Nonce; - type Signature = ::Signature; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = Nonce; + type Signature = Signature; fn max_extrinsic_size() -> u32 { - PolkadotLike::max_extrinsic_size() + max_extrinsic_size() } fn max_extrinsic_weight() -> Weight { - PolkadotLike::max_extrinsic_weight() + max_extrinsic_weight() } } @@ -52,12 +55,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; -} - -parameter_types! { - pub const SS58Prefix: u8 = 42; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } // The SignedExtension used by Rococo. diff --git a/bridges/primitives/chain-wococo/Cargo.toml b/bridges/primitives/chain-westend/Cargo.toml similarity index 80% rename from bridges/primitives/chain-wococo/Cargo.toml rename to bridges/primitives/chain-westend/Cargo.toml index 05901821b366b71d40d9c28392150172be44a0b7..e55a8d649a88b88d84f4f3547c23709cef67d872 100644 --- a/bridges/primitives/chain-wococo/Cargo.toml +++ b/bridges/primitives/chain-westend/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "bp-wococo" -description = "Primitives of Wococo runtime." -version = "0.1.0" +name = "bp-westend" +description = "Primitives of Westend runtime." +version = "0.3.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies @@ -13,7 +16,6 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" bp-header-chain = { path = "../header-chain", default-features = false } bp-polkadot-core = { path = "../polkadot-core", default-features = false } bp-runtime = { path = "../runtime", default-features = false } -bp-rococo = { path = "../chain-rococo", default-features = false } # Substrate Based Dependencies @@ -22,11 +24,10 @@ sp-api = { path = "../../../substrate/primitives/api", default-features = false sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-polkadot-core/std", - "bp-rococo/std", "bp-runtime/std", "frame-support/std", "sp-api/std", diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..f03fd2160a700eb3817a6feb629e9d366cc366aa --- /dev/null +++ b/bridges/primitives/chain-westend/src/lib.rs @@ -0,0 +1,78 @@ +// Copyright (C) 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 Westend chain. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use bp_polkadot_core::*; + +use bp_header_chain::ChainWithGrandpa; +use bp_runtime::{decl_bridge_finality_runtime_apis, Chain, ChainId}; +use frame_support::weights::Weight; + +/// Westend Chain +pub struct Westend; + +impl Chain for Westend { + const ID: ChainId = *b"wend"; + + type BlockNumber = BlockNumber; + type Hash = Hash; + type Hasher = Hasher; + type Header = Header; + + type AccountId = AccountId; + type Balance = Balance; + type Nonce = Nonce; + type Signature = Signature; + + fn max_extrinsic_size() -> u32 { + max_extrinsic_size() + } + + fn max_extrinsic_weight() -> Weight { + max_extrinsic_weight() + } +} + +impl ChainWithGrandpa for Westend { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_WESTEND_GRANDPA_PALLET_NAME; + const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; + const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = + REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; +} + +// The SignedExtension used by Westend. +pub use bp_polkadot_core::CommonSignedExtension as SignedExtension; + +/// Name of the parachains pallet in the Rococo runtime. +pub const PARAS_PALLET_NAME: &str = "Paras"; + +/// Name of the With-Westend GRANDPA pallet instance that is deployed at bridged chains. +pub const WITH_WESTEND_GRANDPA_PALLET_NAME: &str = "BridgeWestendGrandpa"; + +/// Maximal size of encoded `bp_parachains::ParaStoredHeaderData` structure among all Westend +/// parachains. +/// +/// It includes the block number and state root, so it shall be near 40 bytes, but let's have some +/// reserve. +pub const MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE: u32 = 128; + +decl_bridge_finality_runtime_apis!(westend, grandpa); diff --git a/bridges/primitives/chain-wococo/src/lib.rs b/bridges/primitives/chain-wococo/src/lib.rs deleted file mode 100644 index b1df65630beffdaad80ce8f22ba357dbf766ea0f..0000000000000000000000000000000000000000 --- a/bridges/primitives/chain-wococo/src/lib.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 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_attr(not(feature = "std"), no_std)] -// RuntimeApi generated functions -#![allow(clippy::too_many_arguments)] - -pub use bp_polkadot_core::*; -pub use bp_rococo::{ - SS58Prefix, MAX_AUTHORITIES_COUNT, MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE, PARAS_PALLET_NAME, -}; - -use bp_header_chain::ChainWithGrandpa; -use bp_runtime::{decl_bridge_finality_runtime_apis, Chain}; -use frame_support::weights::Weight; - -/// Wococo Chain -pub struct Wococo; - -impl Chain for Wococo { - type BlockNumber = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Nonce = ::Nonce; - type Signature = ::Signature; - - fn max_extrinsic_size() -> u32 { - PolkadotLike::max_extrinsic_size() - } - - fn max_extrinsic_weight() -> Weight { - PolkadotLike::max_extrinsic_weight() - } -} - -impl ChainWithGrandpa for Wococo { - const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = WITH_WOCOCO_GRANDPA_PALLET_NAME; - 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; -} - -// The SignedExtension used by Wococo. -pub use bp_rococo::CommonSignedExtension as SignedExtension; - -/// Name of the With-Wococo GRANDPA pallet instance that is deployed at bridged chains. -pub const WITH_WOCOCO_GRANDPA_PALLET_NAME: &str = "BridgeWococoGrandpa"; - -decl_bridge_finality_runtime_apis!(wococo, grandpa); diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index 19b2819bddce804f1db5e058871941db980c7bf5..205b593365ef8216a2e501e5751303185d4f7537 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "bp-header-chain" description = "A common interface for describing what a bridge pallet should be able to do." -version = "0.1.0" +version = "0.7.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 } finality-grandpa = { version = "0.16.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies @@ -30,7 +33,7 @@ hex = "0.4" hex-literal = "0.4" [features] -default = [ "std" ] +default = ["std"] std = [ "bp-runtime/std", "codec/std", 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..84a6a881a835b8afc3b5cde8992df1733859d29a 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -17,6 +17,7 @@ //! Defines traits which represent a common interface for Substrate pallets which want to //! incorporate bridge functionality. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use crate::justification::{ @@ -145,6 +146,7 @@ pub trait ConsensusLogReader { pub struct GrandpaConsensusLogReader(sp_std::marker::PhantomData); impl GrandpaConsensusLogReader { + /// Find and return scheduled (regular) change digest item. pub fn find_scheduled_change( digest: &Digest, ) -> Option> { @@ -158,6 +160,8 @@ impl GrandpaConsensusLogReader { }) } + /// Find and return forced change digest item. Or light client can't do anything + /// with forced changes, so we can't accept header with the forced change digest. pub fn find_forced_change( digest: &Digest, ) -> Option<(Number, sp_consensus_grandpa::ScheduledChange)> { @@ -229,12 +233,27 @@ pub enum BridgeGrandpaCall { /// `pallet-bridge-grandpa::Call::submit_finality_proof` #[codec(index = 0)] submit_finality_proof { + /// The header that we are going to finalize. finality_target: Box
, + /// Finality justification for the `finality_target`. justification: justification::GrandpaJustification
, }, /// `pallet-bridge-grandpa::Call::initialize` #[codec(index = 1)] - initialize { init_data: InitializationData
}, + initialize { + /// All data, required to initialize the pallet. + init_data: InitializationData
, + }, + /// `pallet-bridge-grandpa::Call::submit_finality_proof_ex` + #[codec(index = 4)] + submit_finality_proof_ex { + /// The header that we are going to finalize. + finality_target: Box
, + /// Finality justification for the `finality_target`. + justification: justification::GrandpaJustification
, + /// An identifier of the validators set, that have signed the justification. + current_set_id: SetId, + }, } /// The `BridgeGrandpaCall` used by a chain. @@ -266,23 +285,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 +319,70 @@ 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 bp_runtime::ChainId; + use frame_support::weights::Weight; + use sp_runtime::{testing::H256, traits::BlakeTwo256, MultiSignature}; + + struct TestChain; + + impl Chain for TestChain { + const ID: ChainId = *b"test"; + + 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 7a61643a0bc5d38524d1177310613101909739ac..8aa6b4b05e5efb2427a8548e91ec5f47ab494968 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -1,15 +1,18 @@ [package] name = "bp-messages" description = "Primitives of messages module." -version = "0.1.0" +version = "0.7.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, features = ["derive", "bit-vec"] } +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies @@ -27,7 +30,7 @@ hex = "0.4" hex-literal = "0.4" [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-runtime/std", diff --git a/bridges/primitives/messages/src/lib.rs b/bridges/primitives/messages/src/lib.rs index e48914f7591866a5621c791193e26edd03959b9c..51b3f25f7151867b52e8e5f49bc70b0a3632c05e 100644 --- a/bridges/primitives/messages/src/lib.rs +++ b/bridges/primitives/messages/src/lib.rs @@ -16,14 +16,13 @@ //! Primitives of messages module. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -// RuntimeApi generated functions -#![allow(clippy::too_many_arguments)] use bp_header_chain::HeaderChainError; use bp_runtime::{ - messages::MessageDispatchResult, BasicOperatingMode, OperatingMode, RangeInclusiveExt, - StorageProofError, + messages::MessageDispatchResult, BasicOperatingMode, Chain, OperatingMode, RangeInclusiveExt, + StorageProofError, UnderlyingChainOf, UnderlyingChainProvider, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::PalletError; @@ -39,6 +38,36 @@ pub mod source_chain; pub mod storage_keys; pub mod target_chain; +/// Substrate-based chain with messaging support. +pub trait ChainWithMessages: Chain { + /// Name of the bridge messages pallet (used in `construct_runtime` macro call) that is + /// deployed at some other chain to bridge with this `ChainWithMessages`. + /// + /// We assume that all chains that are bridging with this `ChainWithMessages` are using + /// the same name. + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str; + + /// Maximal number of unrewarded relayers in a single confirmation transaction at this + /// `ChainWithMessages`. + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce; + /// Maximal number of unconfirmed messages in a single confirmation transaction at this + /// `ChainWithMessages`. + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce; +} + +impl ChainWithMessages for T +where + T: Chain + UnderlyingChainProvider, + UnderlyingChainOf: ChainWithMessages, +{ + const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = + UnderlyingChainOf::::WITH_CHAIN_MESSAGES_PALLET_NAME; + const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = + UnderlyingChainOf::::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = + UnderlyingChainOf::::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; +} + /// Messages pallet operating mode. #[derive( Encode, @@ -264,6 +293,7 @@ pub struct ReceivedMessages { } impl ReceivedMessages { + /// Creates new `ReceivedMessages` structure from given results. pub fn new( lane: LaneId, receive_results: Vec<(MessageNonce, ReceivalResult)>, @@ -271,6 +301,7 @@ impl ReceivedMessages { ReceivedMessages { lane, receive_results } } + /// Push `result` of the `message` delivery onto `receive_results` vector. pub fn push(&mut self, message: MessageNonce, result: ReceivalResult) { self.receive_results.push((message, result)); } @@ -342,7 +373,7 @@ pub struct UnrewardedRelayersState { } impl UnrewardedRelayersState { - // Verify that the relayers state corresponds with the `InboundLaneData`. + /// Verify that the relayers state corresponds with the `InboundLaneData`. pub fn is_valid(&self, lane_data: &InboundLaneData) -> bool { self == &lane_data.into() } @@ -423,15 +454,21 @@ pub enum BridgeMessagesCall { /// `pallet-bridge-messages::Call::receive_messages_proof` #[codec(index = 2)] receive_messages_proof { + /// Account id of relayer at the **bridged** chain. relayer_id_at_bridged_chain: AccountId, + /// Messages proof. proof: MessagesProof, + /// A number of messages in the proof. messages_count: u32, + /// Total dispatch weight of messages in the proof. dispatch_weight: Weight, }, /// `pallet-bridge-messages::Call::receive_messages_delivery_proof` #[codec(index = 3)] receive_messages_delivery_proof { + /// Messages delivery proof. proof: MessagesDeliveryProof, + /// "Digest" of unrewarded relayers state at the bridged chain. relayers_state: UnrewardedRelayersState, }, } diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index 73092c3cce0a575283dd3a856bdfc197e0e4d969..f4aefd9735583e265c3e44713f13f81ae63ba276 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData, VerificationError}; +use crate::{InboundLaneData, LaneId, MessageNonce, VerificationError}; use crate::UnrewardedRelayer; use bp_runtime::Size; @@ -64,24 +64,6 @@ pub trait TargetHeaderChain { ) -> Result<(LaneId, InboundLaneData), VerificationError>; } -/// Lane message verifier. -/// -/// Runtime developer may implement any additional validation logic over message-lane mechanism. -/// E.g. if lanes should have some security (e.g. you can only accept Lane1 messages from -/// Submitter1, Lane2 messages for those who has submitted first message to this lane, disable -/// Lane3 until some block, ...), then it may be built using this verifier. -/// -/// Any fee requirements should also be enforced here. -pub trait LaneMessageVerifier { - /// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the - /// lane. - fn verify_message( - lane: &LaneId, - outbound_data: &OutboundLaneData, - payload: &Payload, - ) -> Result<(), VerificationError>; -} - /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. pub trait DeliveryConfirmationPayments { @@ -143,25 +125,25 @@ pub trait MessagesBridge { /// Error type. type Error: Debug; - /// Send message over the bridge. + /// Intermediary structure returned by `validate_message()`. /// - /// Returns unique message nonce or error if send has failed. - fn send_message(lane: LaneId, message: Payload) -> Result; -} - -/// Bridge that does nothing when message is being sent. -#[derive(Eq, RuntimeDebug, PartialEq)] -pub struct NoopMessagesBridge; + /// It can than be passed to `send_message()` in order to actually send the message + /// on the bridge. + type SendMessageArgs; -impl MessagesBridge for NoopMessagesBridge { - type Error = &'static str; + /// Check if the message can be sent over the bridge. + fn validate_message( + lane: LaneId, + message: &Payload, + ) -> Result; - fn send_message(_lane: LaneId, _message: Payload) -> Result { - Ok(SendMessageArtifacts { nonce: 0, enqueued_messages: 0 }) - } + /// Send message over the bridge. + /// + /// Returns unique message nonce or error if send has failed. + fn send_message(message: Self::SendMessageArgs) -> SendMessageArtifacts; } -/// Structure that may be used in place of `TargetHeaderChain`, `LaneMessageVerifier` and +/// Structure that may be used in place of `TargetHeaderChain` and /// `MessageDeliveryAndDispatchPayment` on chains, where outbound messages are forbidden. pub struct ForbidOutboundMessages; @@ -183,16 +165,6 @@ impl TargetHeaderChain for ForbidOutboun } } -impl LaneMessageVerifier for ForbidOutboundMessages { - fn verify_message( - _lane: &LaneId, - _outbound_data: &OutboundLaneData, - _payload: &Payload, - ) -> Result<(), VerificationError> { - Err(VerificationError::Other(ALL_OUTBOUND_MESSAGES_REJECTED)) - } -} - impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; diff --git a/bridges/primitives/parachains/Cargo.toml b/bridges/primitives/parachains/Cargo.toml index 11e9336f66af7dc8a5673fed37922a8aad35047f..575f26193eb68643c5c0a5fe6376d8b735ee2840 100644 --- a/bridges/primitives/parachains/Cargo.toml +++ b/bridges/primitives/parachains/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "bp-parachains" description = "Primitives of parachains module." -version = "0.1.0" +version = "0.7.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, features = ["derive"] } impl-trait-for-tuples = "0.2" @@ -25,7 +28,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-polkadot-core/std", diff --git a/bridges/primitives/parachains/src/lib.rs b/bridges/primitives/parachains/src/lib.rs index 262b9c6f977529e23d2a0b1515a578cc9455f7ac..692bbd99ecef38535bb65a18dac09a77f1f1eca2 100644 --- a/bridges/primitives/parachains/src/lib.rs +++ b/bridges/primitives/parachains/src/lib.rs @@ -16,6 +16,7 @@ //! Primitives of parachains module. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] pub use bp_header_chain::StoredHeaderData; @@ -173,8 +174,11 @@ pub enum BridgeParachainCall { /// `pallet-bridge-parachains::Call::submit_parachain_heads` #[codec(index = 0)] submit_parachain_heads { + /// Relay chain block, for which we have submitted the `parachain_heads_proof`. at_relay_block: (RelayBlockNumber, RelayBlockHash), + /// Parachain identifiers and their head hashes. parachains: Vec<(ParaId, ParaHash)>, + /// Parachain heads proof. parachain_heads_proof: ParaHeadsProof, }, } diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index e2bd4c295225d0346ff321f01543bd9ccc249fd3..c0dae684b5f2f3b7b9be096a808fc67d15dadfcf 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "bp-polkadot-core" description = "Primitives of Polkadot-like runtime." -version = "0.1.0" +version = "0.7.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, features = ["derive"] } parity-util-mem = { version = "0.12.0", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Bridge Dependencies @@ -29,7 +32,7 @@ sp-std = { path = "../../../substrate/primitives/std", default-features = false hex = "0.4" [features] -default = [ "std" ] +default = ["std"] std = [ "bp-messages/std", "bp-runtime/std", diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs index af39b5ab9babae2b2e6858bff83eaf8c29ef74bb..df2836495bbe131e9cf810c43eb4af5eefaf43b7 100644 --- a/bridges/primitives/polkadot-core/src/lib.rs +++ b/bridges/primitives/polkadot-core/src/lib.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +//! Primitives of the Polkadot-like chains. + +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use bp_messages::MessageNonce; @@ -24,7 +27,7 @@ use bp_runtime::{ CheckSpecVersion, CheckTxVersion, CheckWeight, GenericSignedExtension, SignedExtensionSchema, }, - Chain, EncodedOrDecodedCall, StorageMapKeyProvider, TransactionEra, + EncodedOrDecodedCall, StorageMapKeyProvider, TransactionEra, }; use frame_support::{ dispatch::DispatchClass, @@ -40,7 +43,7 @@ use sp_core::{storage::StorageKey, Hasher as HasherT}; use sp_runtime::{ generic, traits::{BlakeTwo256, IdentifyAccount, Verify}, - MultiAddress, MultiSignature, OpaqueExtrinsic, RuntimeDebug, + MultiAddress, MultiSignature, OpaqueExtrinsic, }; use sp_std::prelude::Vec; @@ -64,30 +67,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. @@ -175,11 +176,16 @@ pub use time_units::*; pub mod time_units { use super::BlockNumber; + /// Milliseconds between Polkadot-like chain blocks. pub const MILLISECS_PER_BLOCK: u64 = 6000; + /// Slot duration in Polkadot-like chain consensus algorithms. pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + /// A minute, expressed in Polkadot-like chain blocks. pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + /// A hour, expressed in Polkadot-like chain blocks. pub const HOURS: BlockNumber = MINUTES * 60; + /// A day, expressed in Polkadot-like chain blocks. pub const DAYS: BlockNumber = HOURS * 24; } @@ -229,31 +235,17 @@ pub type UncheckedExtrinsic = /// Account address, used by the Polkadot-like chain. pub type Address = MultiAddress; -/// Polkadot-like chain. -#[derive(RuntimeDebug)] -pub struct PolkadotLike; - -impl Chain for PolkadotLike { - type BlockNumber = BlockNumber; - type Hash = Hash; - type Hasher = Hasher; - type Header = Header; - - type AccountId = AccountId; - type Balance = Balance; - type Nonce = Nonce; - type Signature = Signature; - - fn max_extrinsic_size() -> u32 { - *BlockLength::get().max.get(DispatchClass::Normal) - } +/// Returns maximal extrinsic size on all Polkadot-like chains. +pub fn max_extrinsic_size() -> u32 { + *BlockLength::get().max.get(DispatchClass::Normal) +} - fn max_extrinsic_weight() -> Weight { - BlockWeights::get() - .get(DispatchClass::Normal) - .max_extrinsic - .unwrap_or(Weight::MAX) - } +/// Returns maximal extrinsic weight on all Polkadot-like chains. +pub fn max_extrinsic_weight() -> Weight { + BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or(Weight::MAX) } /// Provides a storage key for account data. @@ -273,8 +265,10 @@ impl StorageMapKeyProvider for AccountInfoStorageMapKeyProvider { } impl AccountInfoStorageMapKeyProvider { + /// Name of the system pallet. const PALLET_NAME: &'static str = "System"; + /// Return storage key for given account data. pub fn final_key(id: &AccountId) -> StorageKey { ::final_key(Self::PALLET_NAME, id) } diff --git a/bridges/primitives/polkadot-core/src/parachains.rs b/bridges/primitives/polkadot-core/src/parachains.rs index 223956171f86499397842f491d809eb2f8d81a63..433cd2845abd9ae95687d6f1d024765ee3bd2ebb 100644 --- a/bridges/primitives/polkadot-core/src/parachains.rs +++ b/bridges/primitives/polkadot-core/src/parachains.rs @@ -89,11 +89,18 @@ pub type ParaHasher = crate::Hasher; /// Raw storage proof of parachain heads, stored in polkadot-like chain runtime. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct ParaHeadsProof(pub RawStorageProof); +pub struct ParaHeadsProof { + /// Unverified storage proof of finalized parachain heads. + pub storage_proof: RawStorageProof, +} impl Size for ParaHeadsProof { fn size(&self) -> u32 { - u32::try_from(self.0.iter().fold(0usize, |sum, node| sum.saturating_add(node.len()))) - .unwrap_or(u32::MAX) + u32::try_from( + self.storage_proof + .iter() + .fold(0usize, |sum, node| sum.saturating_add(node.len())), + ) + .unwrap_or(u32::MAX) } } diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml index 93ad07892635b446b968764487443e50d95ee4cd..3bd6809d2789e0b3aced5b8d96448b63e1074ee4 100644 --- a/bridges/primitives/relayers/Cargo.toml +++ b/bridges/primitives/relayers/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "bp-relayers" description = "Primitives of relayers module." -version = "0.1.0" +version = "0.7.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, features = ["derive", "bit-vec"] } +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"] } # Bridge Dependencies @@ -26,11 +29,13 @@ hex = "0.4" hex-literal = "0.4" [features] -default = [ "std" ] +default = ["std"] std = [ "bp-messages/std", "bp-runtime/std", + "codec/std", "frame-support/std", + "scale-info/std", "sp-runtime/std", "sp-std/std", ] diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index c529eea536d73e91aa4293aa23813f9273f6f8b5..c808c437b54cbaaa5813067a6413fe7189336ee6 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -115,7 +115,7 @@ where impl PaymentProcedure for PayRewardFromAccount where T: frame_support::traits::fungible::Mutate, - Relayer: Decode + Encode, + Relayer: Decode + Encode + Eq, { type Error = sp_runtime::DispatchError; diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index 48f6722c982b71ae88e7958fcdebd9d70ec88bd3..22206fb2c376ce53fee9dc8ff806baaef3ce7c28 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -1,19 +1,22 @@ [package] name = "bp-runtime" description = "Primitives that may be used at (bridges) runtime level." -version = "0.1.0" +version = "0.7.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 } hash-db = { version = "0.16.0", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.19", default-features = false } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Substrate Dependencies @@ -31,7 +34,7 @@ trie-db = { version = "0.28.0", default-features = false } hex-literal = "0.4" [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "frame-support/std", diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index e1809e145248f1cb37022ed4c2bb29d1547b2b2a..9ba21a1cddf13896b21494045cea7fdd92259ce8 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::HeaderIdProvider; -use codec::{Decode, Encode, MaxEncodedLen}; +use crate::{ChainId, HeaderIdProvider}; + +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 +40,7 @@ pub enum EncodedOrDecodedCall { Decoded(ChainCall), } -impl EncodedOrDecodedCall { +impl EncodedOrDecodedCall { /// Returns decoded call. pub fn to_decoded(&self) -> Result { match self { @@ -57,6 +58,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 { @@ -91,6 +100,9 @@ impl Encode for EncodedOrDecodedCall { /// Minimal Substrate-based chain representation that may be used from no_std environment. pub trait Chain: Send + Sync + 'static { + /// Chain id. + const ID: ChainId; + /// A type that fulfills the abstract idea of what a Substrate block number is. // Constraits come from the associated Number type of `sp_runtime::traits::Header` // See here for more info: @@ -191,7 +203,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; } @@ -200,6 +212,8 @@ impl Chain for T where T: Send + Sync + 'static + UnderlyingChainProvider, { + const ID: ChainId = ::ID; + type BlockNumber = ::BlockNumber; type Hash = ::Hash; type Hasher = ::Hasher; @@ -280,7 +294,7 @@ pub type TransactionEraOf = crate::TransactionEra, HashOf /// - constants that are stringified names of runtime API methods: /// - `BEST_FINALIZED__HEADER_METHOD` /// - `_ACCEPTED__FINALITY_PROOFS_METHOD` -/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_finality_runtime_apis { ($chain: ident $(, $consensus: ident => $justification_type: ty)?) => { @@ -332,7 +346,7 @@ macro_rules! decl_bridge_finality_runtime_apis { /// - `FromInboundLaneApi` /// - constants that are stringified names of runtime API methods: /// - `FROM__MESSAGE_DETAILS_METHOD`, -/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_messages_runtime_apis { ($chain: ident) => { @@ -390,7 +404,7 @@ macro_rules! decl_bridge_messages_runtime_apis { /// Convenience macro that declares bridge finality runtime apis, bridge messages runtime apis /// and related constants for a chain. -/// The name of the chain has to be specified in snake case (e.g. `rialto_parachain`). +/// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_runtime_apis { ($chain: ident $(, $consensus: ident)?) => { diff --git a/bridges/primitives/runtime/src/extensions.rs b/bridges/primitives/runtime/src/extensions.rs index 44eeaad93c91603b34713ca477b5623f4e07aad0..d896bc92efffc4e8fcb427ffa7057dece6f17241 100644 --- a/bridges/primitives/runtime/src/extensions.rs +++ b/bridges/primitives/runtime/src/extensions.rs @@ -88,7 +88,7 @@ pub type BridgeRejectObsoleteHeadersAndMessages = GenericSignedExtensionSchema<( /// wildcard/placeholder, which relies on the scale encoding for `()` or `((), ())`, or `((), (), /// ())` is the same. So runtime can contains any kind of tuple: /// `(BridgeRefundBridgeHubRococoMessages)` -/// `(BridgeRefundBridgeHubRococoMessages, BridgeRefundBridgeHubWococoMessages)` +/// `(BridgeRefundBridgeHubRococoMessages, BridgeRefundBridgeHubWestendMessages)` /// `(BridgeRefundParachainMessages1, ..., BridgeRefundParachainMessagesN)` pub type RefundBridgedParachainMessagesSchema = GenericSignedExtensionSchema<(), ()>; @@ -102,6 +102,7 @@ impl SignedExtensionSchema for Tuple { /// and signed payloads in the client code. #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub struct GenericSignedExtension { + /// A payload that is included in the transaction. pub payload: S::Payload, #[codec(skip)] // It may be set to `None` if extensions are decoded. We are never reconstructing transactions @@ -112,6 +113,7 @@ pub struct GenericSignedExtension { } impl GenericSignedExtension { + /// Create new `GenericSignedExtension` object. pub fn new(payload: S::Payload, additional_signed: Option) -> Self { Self { payload, additional_signed } } diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 7f4a1a030b14562e17e9938ddffa1293cfcda204..850318923dc7671c26cc3edcf2f9d59bd7b987b9 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -16,6 +16,7 @@ //! Primitives that may be used at (bridges) runtime level. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; @@ -23,7 +24,6 @@ use frame_support::{ pallet_prelude::DispatchResult, weights::Weight, PalletError, StorageHasher, StorageValue, }; use frame_system::RawOrigin; -use log; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_core::storage::StorageKey; @@ -62,48 +62,6 @@ pub use sp_runtime::paste; /// Use this when something must be shared among all instances. pub const NO_INSTANCE_ID: ChainId = [0, 0, 0, 0]; -/// Rialto chain id. -pub const RIALTO_CHAIN_ID: ChainId = *b"rlto"; - -/// RialtoParachain chain id. -pub const RIALTO_PARACHAIN_CHAIN_ID: ChainId = *b"rlpa"; - -/// Millau chain id. -pub const MILLAU_CHAIN_ID: ChainId = *b"mlau"; - -/// Polkadot chain id. -pub const POLKADOT_CHAIN_ID: ChainId = *b"pdot"; - -/// Polkadot Bulletin chain id. -pub const POLKADOT_BULLETIN_CHAIN_ID: ChainId = *b"pdbc"; - -/// Kusama chain id. -pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma"; - -/// Westend chain id. -pub const WESTEND_CHAIN_ID: ChainId = *b"wend"; - -/// `AssetHubWestmint` chain id. -pub const ASSET_HUB_WESTEND_CHAIN_ID: ChainId = *b"ahwe"; - -/// Rococo chain id. -pub const ROCOCO_CHAIN_ID: ChainId = *b"roco"; - -/// Wococo chain id. -pub const WOCOCO_CHAIN_ID: ChainId = *b"woco"; - -/// BridgeHubRococo chain id. -pub const BRIDGE_HUB_ROCOCO_CHAIN_ID: ChainId = *b"bhro"; - -/// BridgeHubWococo chain id. -pub const BRIDGE_HUB_WOCOCO_CHAIN_ID: ChainId = *b"bhwo"; - -/// BridgeHubKusama chain id. -pub const BRIDGE_HUB_KUSAMA_CHAIN_ID: ChainId = *b"bhks"; - -/// BridgeHubPolkadot chain id. -pub const BRIDGE_HUB_POLKADOT_CHAIN_ID: ChainId = *b"bhpd"; - /// Generic header Id. #[derive( RuntimeDebug, @@ -139,10 +97,10 @@ pub type HeaderIdOf = HeaderId, BlockNumberOf>; /// Generic header id provider. pub trait HeaderIdProvider { - // Get the header id. + /// Get the header id. fn id(&self) -> HeaderId; - // Get the header id for the parent block. + /// Get the header id for the parent block. fn parent_id(&self) -> Option>; } @@ -275,18 +233,6 @@ pub fn storage_map_final_key( StorageKey(final_key) } -/// This is how a storage key of storage parameter (`parameter_types! { storage Param: bool = false; -/// }`) is computed. -/// -/// Copied from `frame_support::parameter_types` macro. -pub fn storage_parameter_key(parameter_name: &str) -> StorageKey { - let mut buffer = Vec::with_capacity(1 + parameter_name.len() + 1); - buffer.push(b':'); - buffer.extend_from_slice(parameter_name.as_bytes()); - buffer.push(b':'); - StorageKey(sp_io::hashing::twox_128(&buffer).to_vec()) -} - /// This is how a storage key of storage value is computed. /// /// Copied from `frame_support::storage::storage_prefix`. @@ -367,7 +313,7 @@ pub trait StorageDoubleMapKeyProvider { } /// Error generated by the `OwnedBridgeModule` trait. -#[derive(Encode, Decode, TypeInfo, PalletError)] +#[derive(Encode, Decode, PartialEq, Eq, TypeInfo, PalletError)] pub enum OwnedBridgeModuleError { /// All pallet operations are halted. Halted, @@ -375,7 +321,7 @@ pub enum OwnedBridgeModuleError { /// Operating mode for a bridge module. pub trait OperatingMode: Send + Copy + Debug + FullCodec { - // Returns true if the bridge module is halted. + /// Returns true if the bridge module is halted. fn is_halted(&self) -> bool; } @@ -417,8 +363,11 @@ pub trait OwnedBridgeModule { /// The target that will be used when publishing logs related to this module. const LOG_TARGET: &'static str; + /// A storage entry that holds the module `Owner` account. type OwnerStorage: StorageValue>; + /// Operating mode type of the pallet. type OperatingMode: OperatingMode; + /// A storage value that holds the pallet operating mode. type OperatingModeStorage: StorageValue; /// Check if the module is halted. @@ -494,9 +443,11 @@ impl WeightExtraOps for Weight { /// Trait that provides a static `str`. pub trait StaticStrProvider { + /// Static string. const STR: &'static str; } +/// A macro that generates `StaticStrProvider` with the string set to its stringified argument. #[macro_export] macro_rules! generate_static_str_provider { ($str:expr) => { @@ -510,6 +461,7 @@ macro_rules! generate_static_str_provider { }; } +/// Error message that is only dispayable in `std` environment. #[derive(Encode, Decode, Clone, Eq, PartialEq, PalletError, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct StrippableError { @@ -572,14 +524,6 @@ where mod tests { use super::*; - #[test] - fn storage_parameter_key_works() { - assert_eq!( - storage_parameter_key("MillauToRialtoConversionRate"), - StorageKey(hex_literal::hex!("58942375551bb0af1682f72786b59d04").to_vec()), - ); - } - #[test] fn storage_value_key_works() { assert_eq!( diff --git a/bridges/primitives/test-utils/Cargo.toml b/bridges/primitives/test-utils/Cargo.toml index 9836c1877f00239f72dd97c74b6b5fa8698b12e6..d379e950b86ef6f98754c651c980291c9b9c4012 100644 --- a/bridges/primitives/test-utils/Cargo.toml +++ b/bridges/primitives/test-utils/Cargo.toml @@ -1,18 +1,21 @@ [package] name = "bp-test-utils" -version = "0.1.0" +version = "0.7.0" description = "Utilities for testing substrate-based runtime bridge code" 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-header-chain = { path = "../header-chain", default-features = false } bp-parachains = { path = "../parachains", default-features = false } -bp-polkadot-core = { path = "../polkadot-core", default-features = false } +bp-polkadot-core = { path = "../polkadot-core", default-features = false } bp-runtime = { path = "../runtime", default-features = false } codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -ed25519-dalek = { version = "2.0", default-features = false } +ed25519-dalek = { version = "2.1", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false } sp-consensus-grandpa = { path = "../../../substrate/primitives/consensus/grandpa", default-features = false } @@ -22,7 +25,7 @@ sp-std = { path = "../../../substrate/primitives/std", default-features = false sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-header-chain/std", "bp-parachains/std", diff --git a/bridges/primitives/test-utils/src/keyring.rs b/bridges/primitives/test-utils/src/keyring.rs index eabf9c784eb8182f2824cd4c62defe4b92657450..22691183acf7a16d9889841b82dd7936f8694b90 100644 --- a/bridges/primitives/test-utils/src/keyring.rs +++ b/bridges/primitives/test-utils/src/keyring.rs @@ -24,12 +24,17 @@ use sp_consensus_grandpa::{AuthorityId, AuthorityList, AuthorityWeight, SetId}; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; -/// Set of test accounts with friendly names. +/// Set of test accounts with friendly names: Alice. pub const ALICE: Account = Account(0); +/// Set of test accounts with friendly names: Bob. pub const BOB: Account = Account(1); +/// Set of test accounts with friendly names: Charlie. pub const CHARLIE: Account = Account(2); +/// Set of test accounts with friendly names: Dave. pub const DAVE: Account = Account(3); +/// Set of test accounts with friendly names: Eve. pub const EVE: Account = Account(4); +/// Set of test accounts with friendly names: Ferdie. pub const FERDIE: Account = Account(5); /// A test account which can be used to sign messages. @@ -37,10 +42,12 @@ pub const FERDIE: Account = Account(5); pub struct Account(pub u16); impl Account { + /// Returns public key of this account. pub fn public(&self) -> VerifyingKey { self.pair().verifying_key() } + /// Returns key pair, used to sign data on behalf of this account. pub fn pair(&self) -> SigningKey { let data = self.0.encode(); let mut bytes = [0_u8; 32]; @@ -48,6 +55,7 @@ impl Account { SigningKey::from_bytes(&bytes) } + /// Generate a signature of given message. pub fn sign(&self, msg: &[u8]) -> Signature { use ed25519_dalek::Signer; self.pair().sign(msg) diff --git a/bridges/primitives/test-utils/src/lib.rs b/bridges/primitives/test-utils/src/lib.rs index 4d3b84759938b98116a7157b5445804df464cc66..1d80890779bf8310b393d585749e96f9577196a1 100644 --- a/bridges/primitives/test-utils/src/lib.rs +++ b/bridges/primitives/test-utils/src/lib.rs @@ -16,6 +16,7 @@ //! Utilities for testing runtime code. +#![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use bp_header_chain::justification::{required_justification_precommits, GrandpaJustification}; @@ -33,8 +34,11 @@ pub use keyring::*; mod keyring; +/// GRANDPA round number used across tests. pub const TEST_GRANDPA_ROUND: u64 = 1; +/// GRANDPA validators set id used across tests. pub const TEST_GRANDPA_SET_ID: SetId = 1; +/// Name of the `Paras` pallet used across tests. pub const PARAS_PALLET_NAME: &str = "Paras"; /// Configuration parameters when generating test GRANDPA justifications. @@ -125,7 +129,7 @@ pub fn make_justification_for_header( votes_ancestries.push(child.clone()); } - // The header we need to use when pre-commiting is the one at the highest height + // The header we need to use when pre-committing is the one at the highest height // on our chain. let precommit_candidate = chain.last().map(|h| (h.hash(), *h.number())).unwrap(); unsigned_precommits.push(precommit_candidate); @@ -190,7 +194,7 @@ pub fn prepare_parachain_heads_proof( .map_err(|_| "record_all_trie_keys has failed") .expect("record_all_trie_keys should not fail in benchmarks"); - (root, ParaHeadsProof(storage_proof), parachains) + (root, ParaHeadsProof { storage_proof }, parachains) } /// Create signed precommit with given target. diff --git a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml index fb079b48e42a7aca9ae9846ef9eaac383cc06649..9297a8603c0aa407e3dc5b860e21a0c227cf1bcc 100644 --- a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "bp-xcm-bridge-hub-router" description = "Primitives of the xcm-bridge-hub fee pallet." -version = "0.1.0" +version = "0.6.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, features = ["derive", "bit-vec"] } +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"] } # Substrate Dependencies @@ -15,5 +18,5 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-core = { path = "../../../substrate/primitives/core", default-features = false } [features] -default = [ "std" ] -std = [ "codec/std", "scale-info/std", "sp-core/std", "sp-runtime/std" ] +default = ["std"] +std = ["codec/std", "scale-info/std", "sp-core/std", "sp-runtime/std"] diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ad49ec1e83152f60b8386d97d522c798ee87e618 --- /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.2.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/scripts/verify-pallets-build.sh b/bridges/scripts/verify-pallets-build.sh index b8ac09e26b8bcd78e4a0c3022c098f2ebc93afd3..b96bbf1833b6b3ce2bb34d2dc34aa5b8f54eb528 100755 --- a/bridges/scripts/verify-pallets-build.sh +++ b/bridges/scripts/verify-pallets-build.sh @@ -61,20 +61,12 @@ trap revert_to_clean_state EXIT rm -rf $BRIDGES_FOLDER/.config rm -rf $BRIDGES_FOLDER/.github rm -rf $BRIDGES_FOLDER/.maintain -rm -rf $BRIDGES_FOLDER/bin/millau -rm -rf $BRIDGES_FOLDER/bin/rialto -rm -rf $BRIDGES_FOLDER/bin/rialto-parachain -rm -rf $BRIDGES_FOLDER/bin/.keep rm -rf $BRIDGES_FOLDER/deployments rm -f $BRIDGES_FOLDER/docs/dockerhub-* rm -rf $BRIDGES_FOLDER/fuzz rm -rf $BRIDGES_FOLDER/modules/beefy rm -rf $BRIDGES_FOLDER/modules/shift-session-manager rm -rf $BRIDGES_FOLDER/primitives/beefy -rm -rf $BRIDGES_FOLDER/primitives/chain-millau -rm -rf $BRIDGES_FOLDER/primitives/chain-rialto -rm -rf $BRIDGES_FOLDER/primitives/chain-rialto-parachain -rm -rf $BRIDGES_FOLDER/primitives/chain-westend rm -rf $BRIDGES_FOLDER/relays rm -rf $BRIDGES_FOLDER/scripts/add_license.sh rm -rf $BRIDGES_FOLDER/scripts/build-containers.sh @@ -82,8 +74,6 @@ rm -rf $BRIDGES_FOLDER/scripts/ci-cache.sh rm -rf $BRIDGES_FOLDER/scripts/dump-logs.sh rm -rf $BRIDGES_FOLDER/scripts/license_header rm -rf $BRIDGES_FOLDER/scripts/regenerate_runtimes.sh -rm -rf $BRIDGES_FOLDER/scripts/send-message-from-millau-rialto.sh -rm -rf $BRIDGES_FOLDER/scripts/send-message-from-rialto-millau.sh rm -rf $BRIDGES_FOLDER/scripts/update-weights.sh rm -rf $BRIDGES_FOLDER/scripts/update-weights-setup.sh rm -rf $BRIDGES_FOLDER/scripts/update_substrate.sh 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..6561df401120e9c5c5d6ee2762eb1423b5d6daaf --- /dev/null +++ b/bridges/snowbridge/README.md @@ -0,0 +1,134 @@ +# Snowbridge · +[![codecov](https://codecov.io/gh/Snowfork/polkadot-sdk/branch/snowbridge/graph/badge.svg?token=9hvgSws4rN)](https://codecov.io/gh/Snowfork/polkadot-sdk) +![GitHub](https://img.shields.io/github/license/Snowfork/snowbridge) + +Snowbridge is a trustless bridge between Polkadot and Ethereum. For documentation, visit https://docs.snowbridge.network. + +## Components + +The Snowbridge project lives in two repositories: + +- [Snowfork/Polkadot-sdk](https://github.com/Snowfork/polkadot-sdk): The Snowbridge parachain and pallets live in +a fork of the Polkadot SDK. Changes are eventually contributed back to +[paritytech/Polkadot-sdk](https://github.com/paritytech/polkadot-sdk) +- [Snowfork/snowbridge](https://github.com/Snowfork/snowbridge): The rest of the Snowbridge components, like contracts, +off-chain relayer, end-to-end tests and test-net setup code. + +### Parachain + +Polkadot parachain and our pallets. See [README.md](https://github.com/Snowfork/polkadot-sdk/blob/snowbridge/bridges/snowbridge/README.md). + +### Contracts + +Ethereum contracts and unit tests. See [Snowfork/snowbridge/contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) + +### Relayer + +Off-chain relayer services for relaying messages between Polkadot and Ethereum. See +[Snowfork/snowbridge/relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) + +### Local Testnet + +Scripts to provision a local testnet, running the above services to bridge between local deployments of Polkadot and +Ethereum. See [Snowfork/snowbridge/web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). + +### Smoke Tests + +Integration tests for our local testnet. See [Snowfork/snowbridge/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 `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/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c8999633c97abb00174e38e16ed5618e7baf0b59 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -0,0 +1,101 @@ +[package] +name = "snowbridge-pallet-ethereum-client" +description = "Snowbridge Ethereum Client Pallet" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { optional = true, workspace = true, default-features = true } +serde_json = { optional = true, workspace = true, default-features = true } +codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +ssz_rs = { version = "0.9.0", default-features = false } +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 = { workspace = true } + +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false, optional = true } + +snowbridge-core = { path = "../../primitives/core", default-features = false } +snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } +snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures", default-features = false, optional = true } +primitives = { package = "snowbridge-beacon-primitives", path = "../../primitives/beacon", default-features = false } +static_assertions = { version = "1.1.0", default-features = false } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false, optional = true } + +[dev-dependencies] +rand = "0.8.5" +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +serde_json = { workspace = true, default-features = true } +hex-literal = "0.4.1" +pallet-timestamp = { path = "../../../../substrate/frame/timestamp" } +snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures" } +sp-io = { path = "../../../../substrate/primitives/io" } +serde = { workspace = true, default-features = true } + +[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", + "snowbridge-pallet-ethereum-client-fixtures/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "ssz_rs/std", + 'frame-benchmarking/std', +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-timestamp?/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-ethereum-client-fixtures/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-timestamp?/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/ethereum-client/README.md b/bridges/snowbridge/pallets/ethereum-client/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0cd3b9f85587ea925a52e2d0b08eaee9628330af --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/README.md @@ -0,0 +1,3 @@ +# Ethereum Beacon Client + +The Ethereum Beacon Client is an on-chain light client that tracks Ethereum consensus using the beacon chain. diff --git a/bridges/snowbridge/pallets/ethereum-client/benchmark.md b/bridges/snowbridge/pallets/ethereum-client/benchmark.md new file mode 100644 index 0000000000000000000000000000000000000000..2e19fece7cbd7dbd8cfb8b9c7ff29d9527ecd9ab --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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-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-client/src/lib.rs#L764) +is the main bottleneck. Test data +[here](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-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-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_pallet_ethereum_client \ +--extrinsic="*" \ +--execution=wasm --wasm-execution=compiled \ +--steps 50 --repeat 20 \ +--output ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_client.rs +``` + +### [Weights](https://github.com/Snowfork/cumulus/blob/ron/benchmark-beacon-bridge/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_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-client/src/lib.rs#L764) +is super helpful. diff --git a/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..fd1914a7d30e81c3615c981d7ae8ba378a8a5724 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "snowbridge-pallet-ethereum-client-fixtures" +description = "Snowbridge Ethereum Client Test Fixtures" +version = "0.9.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex-literal = { version = "0.4.1" } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +snowbridge-core = { path = "../../../primitives/core", default-features = false } +snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "sp-core/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", +] diff --git a/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..facaffb8149cd88b0f3f369ed79dd922e9b2c983 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs @@ -0,0 +1,1225 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See README.md for instructions to generate +#![cfg_attr(not(feature = "std"), no_std)] + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, updates::AncestryProof, BeaconHeader, ExecutionHeaderUpdate, + NextSyncCommitteeUpdate, SyncAggregate, SyncCommittee, VersionedExecutionPayloadHeader, +}; +use sp_core::U256; +use sp_std::{boxed::Box, vec}; + +const SC_SIZE: usize = 512; +const SC_BITS_SIZE: usize = 64; +type CheckpointUpdate = snowbridge_beacon_primitives::CheckpointUpdate; +type Update = snowbridge_beacon_primitives::Update; + +pub fn make_checkpoint() -> Box { + Box::new(CheckpointUpdate { + header: BeaconHeader { + slot: 2496, + proposer_index: 2, + parent_root: hex!("c99e49787106733eeebab4d93eb326e1f2214575c9d928f0c4ab0da0776f1622").into(), + state_root: hex!("fbf8a08c86ef36bd173e37e733da4a78aa8e85fee99a990e858dd12a59087fde").into(), + body_root: hex!("a2a8ad06901447b2807a9059580a4c40d8a941f325b1343c69f7c7c6c90e4ab0").into(), + }, + current_sync_committee: SyncCommittee { + pubkeys: [ + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + ], + aggregate_pubkey: hex!("8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c").into(), + }, + current_sync_committee_branch: vec![ + hex!("3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59").into(), + hex!("93880225bf99a0c5ec22b266ff829837754e9c5edf37a68c05b8f803fd82fa45").into(), + hex!("4c60656ec9a95fcf11030ad309c716b5b15beb7f60a0bcfc7c9d4eff505472ff").into(), + hex!("22d1645fceb4bf9a695043dda19a53e784ec70df6a6b1bd66ea30eba1cca5f2f").into(), + hex!("a8fc6cad84ceefc633ec56c2d031d525e1cb4b51c70eb252919fce5bba9a1fde").into(), + ], + validators_root: hex!("270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69").into(), + block_roots_root: hex!("d160b7687041891b73e54b06fc4e04f82d0fa8fdd76705895e216c6b24709dfe").into(), + block_roots_branch: vec![ + hex!("105290e42d98ab6a0ada6e55453cede36c672abf645eeb986b88d7487616e135").into(), + hex!("9da41f274bcdf6122335443d9ce94d07163b48dba3e2f9499ff56f4e48b48b99").into(), + hex!("ecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5").into(), + hex!("b2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2").into(), + hex!("cd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf").into(), + ], + }) +} + +pub fn make_sync_committee_update() -> Box { + Box::new(Update { + attested_header: BeaconHeader { + slot: 129, + proposer_index: 5, + parent_root: hex!("e32b6c18f029e755b0273dc1c4fa2bc4979794c8286ad40276c1b8a8e36049d8").into(), + state_root: hex!("5ec9dacf25a5f09f20be0c59246b3d8dcfe64bd085b4bac5cec180690339801e").into(), + body_root: hex!("4080cf2412d6ff77fc3164ad6155423a7112f207f173145ec16371a93f481f87").into(), + }, + sync_aggregate: SyncAggregate{ + sync_committee_bits: hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + sync_committee_signature: hex!("a761c3333fbb3d36bc8f65454f898da38001499dcd37494cf3d86940a995399ae649216ba4c985af154f83f72c8b1856079b7636a7a8d7d3f7602df2cbf699edb72b65253e82de4d9cc4db7377eafb22f799129f63f094a21c00675bdd5cc243").into(), + }, + signature_slot: 130, + next_sync_committee_update: Some(NextSyncCommitteeUpdate { + next_sync_committee: SyncCommittee { + pubkeys: [ + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + hex!("ab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34").into(), + hex!("a8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac").into(), + hex!("9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373").into(), + hex!("b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b").into(), + hex!("81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e").into(), + hex!("88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e").into(), + hex!("a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c").into(), + hex!("a3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b").into(), + ], + aggregate_pubkey: hex!("8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c").into(), + }, + next_sync_committee_branch: vec![ + hex!("3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59").into(), + hex!("fd1e5ff5d4a15081efe3ff17857b1f95984c9a271b1c41c2f81f43e60c2cc541").into(), + hex!("e1c97f93bb7352d395d1ff8ee29881572cb7eb5d71634783701171dcd30cd93d").into(), + hex!("77fa2170ddbd89b15dae02f2e6cf9f76c8e00d1c4217320acffbe01576d0da61").into(), + hex!("e97288e0627219087a024078d69445f34f0583a6350a7c3c40c39fd1fa6f8d68").into(), + ], + }), + finalized_header: BeaconHeader{ + slot: 64, + proposer_index: 4, + parent_root: hex!("0f7bc2353778c14c7f6dba0fc5fe6eec87228b0d3a5447b61dce67b4d9338de3").into(), + state_root: hex!("feb990de653ce494c0a263f820eaf05a9300dbdc30cb6065ede602827bfccde4").into(), + body_root: hex!("f5235cd8c24f2695fc5b7989926305c10ad8cf5a87d62a739f675f5543df2ec1").into(), + }, + finality_branch: vec![ + hex!("0200000000000000000000000000000000000000000000000000000000000000").into(), + hex!("10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7").into(), + hex!("98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d").into(), + hex!("e1c97f93bb7352d395d1ff8ee29881572cb7eb5d71634783701171dcd30cd93d").into(), + hex!("77fa2170ddbd89b15dae02f2e6cf9f76c8e00d1c4217320acffbe01576d0da61").into(), + hex!("e97288e0627219087a024078d69445f34f0583a6350a7c3c40c39fd1fa6f8d68").into(), + ], + block_roots_root: hex!("6fcdfd1c3fb1bdd421fe59dddfff3855b5ed5e30373887991a0059d019ad12bc").into(), + block_roots_branch: vec![ + hex!("94b59531f172bc24f914bc0c10104ccb158676850f8cc3b47b6ddb7f096ebdd7").into(), + hex!("22470ed9155a938587d44d5fa19217c0f939d8862e504e67cd8cb4d1b960795e").into(), + hex!("feec3ef1a68f93849e71e84f90b99602cccc31868137b6887ca8244a4b979e8e").into(), + hex!("5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82").into(), + hex!("f5ff4b0c6190005015889879568f5f0d9c40134c7ec4ffdda47950dcd92395ad").into(), + ], + }) +} + +pub fn make_finalized_header_update() -> Box { + Box::new(Update { + attested_header: BeaconHeader { + slot: 2566, + proposer_index: 6, + parent_root: hex!("6eb9f13a2c496318ce1ab3087bbd872f5c9519a1a7ca8231a2453e3cb523af00").into(), + state_root: hex!("c8cb12766113dff7e46d2917267bf33d0626d99dd47715fcdbc5c65fad3c04b4").into(), + body_root: hex!("d8cfd0d7bc9bc3724417a1655bb0a67c0765ca36197320f4d834150b52ef1420").into(), + }, + sync_aggregate: SyncAggregate{ + sync_committee_bits: hex!("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + sync_committee_signature: hex!("9296f9a0387f2cac47008e22ad7c3cd3d49d35384c13e6aa1eacca7dca7c3d2ca81515e50eb3396b9550ed20ef7d8fa2049a186598feb2c00e93728045fcff917733d1827481b8fc95f3913e27fc70112c2490496eb57bb7181f02c3f9fd471f").into(), + }, + signature_slot: 2567, + next_sync_committee_update: None, + finalized_header: BeaconHeader { + slot: 2496, + proposer_index: 2, + parent_root: hex!("c99e49787106733eeebab4d93eb326e1f2214575c9d928f0c4ab0da0776f1622").into(), + state_root: hex!("fbf8a08c86ef36bd173e37e733da4a78aa8e85fee99a990e858dd12a59087fde").into(), + body_root: hex!("a2a8ad06901447b2807a9059580a4c40d8a941f325b1343c69f7c7c6c90e4ab0").into(), + }, + finality_branch: vec![ + hex!("4e00000000000000000000000000000000000000000000000000000000000000").into(), + hex!("10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7").into(), + hex!("98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d").into(), + hex!("958b8e43347f6df6fa5eb3d62d06a862381a6585aa40640dd1c0de11f1cf89c1").into(), + hex!("f107dce04faa86a28fc5d4a618be9cb8d4fc3c23d6c42c3624f3ff4bf6586a03").into(), + hex!("a501cdc02e86969ac3e4d0c5a36f4f049efaa1ab8cb6693f51d130eb52a80f30").into(), + ], + block_roots_root: hex!("d160b7687041891b73e54b06fc4e04f82d0fa8fdd76705895e216c6b24709dfe").into(), + block_roots_branch: vec![ + hex!("105290e42d98ab6a0ada6e55453cede36c672abf645eeb986b88d7487616e135").into(), + hex!("9da41f274bcdf6122335443d9ce94d07163b48dba3e2f9499ff56f4e48b48b99").into(), + hex!("ecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5").into(), + hex!("b2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2").into(), + hex!("cd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf").into(), + ] + }) +} + +pub fn make_execution_header_update() -> Box { + Box::new(ExecutionHeaderUpdate { + header: BeaconHeader { + slot: 215, + proposer_index: 2, + parent_root: hex!("97518f531a252bb6ca547b21aca9da767943ec99211d3b15c804e34c3a523f45").into(), + state_root: hex!("b088b5a3a8c90d6dc919a695cd7bb0267c6f983ea2e675c559ceb8f46cb90b67").into(), + body_root: hex!("0ba23c8224fdd01531d5ad51486353bd524a0b4c20bca704e26d3210616f829b").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("97518f531a252bb6ca547b21aca9da767943ec99211d3b15c804e34c3a523f45").into(), + hex!("5ce0db996bd499c2b4f7a93263d5aafd052f420efb617cce6fdd54e25516aa45").into(), + hex!("84f0e373b66011ce774c7061440c0a50a51cce2b4b335395eee3e563d605597f").into(), + hex!("48f9ccc5f9594142c18c3b5c39a99f0549329c6ab3ba06c9a50030eadca87770").into(), + hex!("f89d6e311e05bc75a6f63ce118bccce254551f1a88d54c3b4f773f81f946bd99").into(), + hex!("2edd6d893c22636675147c07dfcdb541a146e87c3f15b51c388be4868246dc9b").into(), + hex!("d76b7de5f856e3208a91a42c9c398a7f4fab35e667bf916346050ae742514a2d").into(), + hex!("83a2e233e76385953ca41de4c3afe60471a61f0cc1b3846b4a0670e3e563b747").into(), + hex!("e783a5a109c2ad74e4eb53e8f6b11b31266a92a9e16c1fd5873109c5d41b282c").into(), + hex!("d4ea1ef3869ee6a0fd0b19d7d70027d144eecd4f1d32cbf47632a0a9069164b9").into(), + hex!("f8179564b58eb93a850d35e4156a04db651106442ad891c3e85155c1762792f1").into(), + hex!("4cbb1edb48cf1e32fb30db60aaaeaf6190ffe4d0c8dbc96cec307daecb78be12").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("890a7f23b9ed2160901654be9efc575d6830ca860e2a97866ae3423fb7bd7231").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("d82ec63f5c5e6ba61d62f09c188f158e6449b94bdcc31941e68639eec3c4cf7a").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("8b65545fe5f3216b47b6339b9c91ca2b7f1032a970b04246d9e9fb4460ee34c3").into(), + receipts_root: hex!("7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095").into(), + logs_bloom: hex!("00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010").into(), + prev_randao: hex!("6d9e2a012d82b1b6cb0a2c1c1ed24cc16dbb56e6e39ae545371e0666ab057862").into(), + block_number: 215, + gas_limit: 64842908, + gas_used: 119301, + timestamp: 1705859527, + extra_data: hex!("d983010d0a846765746888676f312e32312e358664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("48498dbfbcfae53a7f4c289ee00747aceea925f6260c50ead5a33e1c55c40f98").into(), + transactions_root: hex!("5ebc1347fe3df0611d4f66b19bd8e1c6f4eaed0371d850f14c83b1c77ea234e6").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("f8c69d3830406d668619bcccc13c8dddde41e863326f7418b241d5924c4ad34a").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("f4d6b5cf9c6e212615c3674fa625d04eb1114153fb221ef5ad02aa433fc67cfb").into(), + ], + }) +} diff --git a/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1520cd715393e79f0265b07263fa9b762206c97 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; +mod util; + +use crate::Pallet as EthereumBeaconClient; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +use snowbridge_pallet_ethereum_client_fixtures::*; + +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::new_tester(), crate::mock::Test); +} diff --git a/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e5ded6e1f0d26cad99b5ee84f97eeb277605954 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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/pallets/ethereum-client/src/config/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba3ea47b94f9acf3dade1aec2bf39bd4d18b51ee --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use primitives::merkle_proof::{generalized_index_length, subtree_index}; +use static_assertions::const_assert; + +/// Generalized Indices +/// related to Merkle proofs +/// 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); + +/// Sizes related to SSZ encoding +pub const MAX_EXTRA_DATA_BYTES: usize = 32; +pub const MAX_LOGS_BLOOM_SIZE: usize = 256; +pub const MAX_FEE_RECIPIENT_SIZE: usize = 20; + +/// Sanity value to constrain the max size of a merkle branch proof. +pub const MAX_BRANCH_PROOF_SIZE: usize = 20; + +/// DomainType('0x07000000') +/// +pub const DOMAIN_SYNC_COMMITTEE: [u8; 4] = [7, 0, 0, 0]; +/// Validators public keys are 48 bytes. +pub const PUBKEY_SIZE: usize = 48; +/// Signatures produced by validators are 96 bytes. +pub const SIGNATURE_SIZE: usize = 96; + +// Sanity check for the sync committee bits (see SYNC_COMMITTEE_BITS_SIZE). +const_assert!(SYNC_COMMITTEE_BITS_SIZE == SYNC_COMMITTEE_SIZE / 8); + +/// Defined in +/// There are 32 slots in an epoch. An epoch is 6.4 minutes long. +pub const SLOTS_PER_EPOCH: usize = 32; +/// 256 epochs in a sync committee period. Frequency of sync committee (subset of Ethereum +/// validators) change is every ~27 hours. +pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 256; +/// A sync committee contains 512 randomly selected validators. +pub const SYNC_COMMITTEE_SIZE: usize = 512; +/// An array of sync committee block votes, one bit representing the vote of one validator. +pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8; +/// The size of the block root array in the beacon state, used for ancestry proofs. +pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192; +/// The index of the block_roots field in the beacon state tree. +pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 13; diff --git a/bridges/snowbridge/pallets/ethereum-client/src/functions.rs b/bridges/snowbridge/pallets/ethereum-client/src/functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..751e63c7f86afb6ae4161e9ec4b9ebe750d67436 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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/pallets/ethereum-client/src/impls.rs b/bridges/snowbridge/pallets/ethereum-client/src/impls.rs new file mode 100644 index 0000000000000000000000000000000000000000..300431d87707ddcfd15eb7937f8ef581c157aeed --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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-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-client", + "💫 Verification of receipt inclusion failed for block {}: {:?}", + proof.block_hash, + err + ); + return Err(err) + }, + }; + + log::trace!( + target: "ethereum-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-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-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-client", + "💫 Failed to decode transaction receipt: {}", + err + ); + Err(InvalidProof) + }, + } + } +} diff --git a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a54d4a05ac5840df4aa7b00389527a0ec94ee22d --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs @@ -0,0 +1,843 @@ +// 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(test)] +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-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 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.deneb.epoch { + return fork_versions.deneb.version + } + 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.clone()); + } + } + 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/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..799b14f4773e421a37b75d226d7829ce8bb7dcf6 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as ethereum_beacon_client; +use crate::config; +use frame_support::{derive_impl, parameter_types}; +use hex_literal::hex; +use pallet_timestamp; +use primitives::{CompactExecutionHeader, Fork, ForkVersions}; +use snowbridge_core::inbound::{Log, Proof}; +use std::{fs::File, path::PathBuf}; +type Block = frame_system::mocking::MockBlock; +use sp_runtime::BuildStorage; + +fn load_fixture(basename: String) -> 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.json".to_string()).unwrap() +} + +pub fn load_checkpoint_update_fixture( +) -> primitives::CheckpointUpdate<{ config::SYNC_COMMITTEE_SIZE }> { + load_fixture("initial-checkpoint.json".to_string()).unwrap() +} + +pub fn load_sync_committee_update_fixture( +) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("sync-committee-update.json".to_string()).unwrap() +} + +pub fn load_finalized_header_update_fixture( +) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("finalized-header-update.json".to_string()).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.json".to_string()).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.json".to_string()).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(), + } +} + +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}, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + 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, 0, 0], // 0x00000000 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 0], // 0x01000000 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 0], // 0x02000000 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 0], // 0x03000000 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 0], // 0x90000073 + epoch: 0, + } + }; + pub const ExecutionHeadersPruneThreshold: u32 = 8192; +} + +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/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..50b6a25c3428dba843303031018a4ef5e93cf696 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{ + functions::compute_period, pallet::ExecutionHeaders, sync_committee_sum, verify_merkle_branch, + BeaconHeader, CompactBeaconState, Error, ExecutionHeaderBuffer, FinalizedBeaconState, + LatestExecutionState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, +}; + +use crate::mock::{ + get_message_verification_header, get_message_verification_payload, + load_checkpoint_update_fixture, load_execution_header_update_fixture, + load_finalized_header_update_fixture, load_next_finalized_header_update_fixture, + load_next_sync_committee_update_fixture, load_sync_committee_update_fixture, +}; + +pub use crate::mock::*; + +use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH}; +use frame_support::{assert_err, assert_noop, assert_ok}; +use hex_literal::hex; +use primitives::{ + CompactExecutionHeader, ExecutionHeaderState, Fork, ForkVersions, NextSyncCommitteeUpdate, + VersionedExecutionPayloadHeader, +}; +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 }, + deneb: Fork { version: [0, 0, 0, 4], epoch: 40 }, + }; + 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 = Box::new(load_checkpoint_update_fixture()); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::force_checkpoint( + RuntimeOrigin::root(), + 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 = Box::new(load_checkpoint_update_fixture()); + checkpoint.current_sync_committee_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), checkpoint), + Error::::InvalidSyncCommitteeMerkleProof + ); + }); +} + +#[test] +fn process_initial_checkpoint_with_invalid_blocks_root_proof() { + let mut checkpoint = Box::new(load_checkpoint_update_fixture()); + checkpoint.block_roots_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), checkpoint), + Error::::InvalidBlockRootsRootMerkleProof + ); + }); +} + +#[test] +fn submit_update_in_current_period() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let update = Box::new(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), 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 = Box::new(load_checkpoint_update_fixture()); + let update = Box::new(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), update)); + assert!(>::exists()); + }); +} + +#[test] +fn submit_update_in_next_period() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); + let update = Box::new(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), + sync_committee_update.clone() + )); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), 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 = Box::new(load_checkpoint_update_fixture()); + let mut update = Box::new(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), update), + Error::::InvalidHeaderMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_invalid_block_roots_proof() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let mut update = Box::new(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), update), + Error::::InvalidBlockRootsRootMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_invalid_next_sync_committee_proof() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let mut update = Box::new(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), update), + Error::::InvalidSyncCommitteeMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_skipped_period() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); + let mut update = Box::new(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), + sync_committee_update.clone() + )); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), + Error::::SkippedSyncCommitteePeriod + ); + }); +} + +#[test] +fn submit_update_with_sync_committee_in_next_period() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let update = Box::new(load_sync_committee_update_fixture()); + let next_update = Box::new(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), update.clone())); + assert!(>::exists()); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), 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 = Box::new(load_checkpoint_update_fixture()); + let mut update = Box::new(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), update), + Error::::InvalidUpdateSlot + ); + }); +} + +#[test] +fn submit_update_with_skipped_sync_committee_period() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_update = Box::new(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), finalized_update), + Error::::SkippedSyncCommitteePeriod + ); + }); +} + +#[test] +fn submit_update_execution_headers_too_far_behind() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let execution_header_update = Box::new(load_execution_header_update_fixture()); + let next_update = Box::new(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), finalized_header_update)); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + 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), next_update), + Error::::ExecutionHeaderTooFarBehind + ); + }); +} + +#[test] +fn submit_irrelevant_update() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let mut update = Box::new(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), update), + Error::::IrrelevantUpdate + ); + }); +} + +#[test] +fn submit_update_with_missing_bootstrap() { + let update = Box::new(load_next_finalized_header_update_fixture()); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update), + Error::::NotBootstrapped + ); + }); +} + +#[test] +fn submit_update_with_invalid_sync_committee_update() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let update = Box::new(load_sync_committee_update_fixture()); + let mut next_update = Box::new(load_next_sync_committee_update_fixture()); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), 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), next_update), + Error::::InvalidSyncCommitteeUpdate + ); + }); +} + +#[test] +fn submit_execution_header_update() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let execution_header_update = Box::new(load_execution_header_update_fixture()); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update)); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + 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 = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let mut execution_header_update = Box::new(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), finalized_header_update)); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + execution_header_update + ), + Error::::InvalidAncestryMerkleProof + ); + }); +} + +#[test] +fn submit_execution_header_update_invalid_execution_header_proof() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let mut execution_header_update = Box::new(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), finalized_header_update)); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + execution_header_update + ), + Error::::InvalidExecutionHeaderProof + ); + }); +} + +#[test] +fn submit_execution_header_update_that_skips_block() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let execution_header_update = Box::new(load_execution_header_update_fixture()); + let mut skipped_block_execution_header_update = + Box::new(load_execution_header_update_fixture()); + let mut skipped_execution_header = + skipped_block_execution_header_update.execution_header.clone(); + + skipped_execution_header = match skipped_execution_header { + VersionedExecutionPayloadHeader::Capella(execution_payload_header) => { + let mut mut_execution_payload_header = execution_payload_header.clone(); + mut_execution_payload_header.block_number = execution_payload_header.block_number + 2; + VersionedExecutionPayloadHeader::Capella(mut_execution_payload_header) + }, + VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => { + let mut mut_execution_payload_header = execution_payload_header.clone(); + mut_execution_payload_header.block_number = execution_payload_header.block_number + 2; + VersionedExecutionPayloadHeader::Deneb(mut_execution_payload_header) + }, + }; + + skipped_block_execution_header_update.execution_header = skipped_execution_header; + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update)); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + execution_header_update.clone() + )); + assert!(>::contains_key( + execution_header_update.execution_header.block_hash() + )); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + skipped_block_execution_header_update + ), + Error::::ExecutionHeaderSkippedBlock + ); + }); +} + +#[test] +fn submit_execution_header_update_that_is_also_finalized_header_which_is_not_stored() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let mut execution_header_update = Box::new(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), finalized_header_update)); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + 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 = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let mut execution_header_update = Box::new(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), 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), + execution_header_update + ), + Error::::ExpectedFinalizedHeaderNotStored + ); + }); +} + +#[test] +fn submit_execution_header_not_finalized() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let finalized_header_update = Box::new(load_finalized_header_update_fixture()); + let update = Box::new(load_execution_header_update_fixture()); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), 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), 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 = Box::new(load_checkpoint_update_fixture()); + let update = Box::new(load_finalized_header_update_fixture()); + let execution_header_update = Box::new(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), update), + Error::::Halted + ); + + assert_noop!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + 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/pallets/ethereum-client/src/types.rs b/bridges/snowbridge/pallets/ethereum-client/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dcefea9f80f4e201d8de633a7a323f530220a45 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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/pallets/ethereum-client/src/weights.rs b/bridges/snowbridge/pallets/ethereum-client/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1a5578f46615e6a75400631ea7d0cc00a0d90cb --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-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-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/pallets/ethereum-client/tests/fixtures/execution-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json new file mode 100755 index 0000000000000000000000000000000000000000..319014249c12d9de36ee9e73e08cdeabcd954f63 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json @@ -0,0 +1,54 @@ +{ + "header": { + "slot": 215, + "proposer_index": 2, + "parent_root": "0x97518f531a252bb6ca547b21aca9da767943ec99211d3b15c804e34c3a523f45", + "state_root": "0xb088b5a3a8c90d6dc919a695cd7bb0267c6f983ea2e675c559ceb8f46cb90b67", + "body_root": "0x0ba23c8224fdd01531d5ad51486353bd524a0b4c20bca704e26d3210616f829b" + }, + "ancestry_proof": { + "header_branch": [ + "0x97518f531a252bb6ca547b21aca9da767943ec99211d3b15c804e34c3a523f45", + "0x5ce0db996bd499c2b4f7a93263d5aafd052f420efb617cce6fdd54e25516aa45", + "0x84f0e373b66011ce774c7061440c0a50a51cce2b4b335395eee3e563d605597f", + "0x48f9ccc5f9594142c18c3b5c39a99f0549329c6ab3ba06c9a50030eadca87770", + "0xf89d6e311e05bc75a6f63ce118bccce254551f1a88d54c3b4f773f81f946bd99", + "0x2edd6d893c22636675147c07dfcdb541a146e87c3f15b51c388be4868246dc9b", + "0xd76b7de5f856e3208a91a42c9c398a7f4fab35e667bf916346050ae742514a2d", + "0x83a2e233e76385953ca41de4c3afe60471a61f0cc1b3846b4a0670e3e563b747", + "0xe783a5a109c2ad74e4eb53e8f6b11b31266a92a9e16c1fd5873109c5d41b282c", + "0xd4ea1ef3869ee6a0fd0b19d7d70027d144eecd4f1d32cbf47632a0a9069164b9", + "0xf8179564b58eb93a850d35e4156a04db651106442ad891c3e85155c1762792f1", + "0x4cbb1edb48cf1e32fb30db60aaaeaf6190ffe4d0c8dbc96cec307daecb78be12", + "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f" + ], + "finalized_block_root": "0x890a7f23b9ed2160901654be9efc575d6830ca860e2a97866ae3423fb7bd7231" + }, + "execution_header": { + "Deneb": { + "parent_hash": "0xd82ec63f5c5e6ba61d62f09c188f158e6449b94bdcc31941e68639eec3c4cf7a", + "fee_recipient": "0x0000000000000000000000000000000000000000", + "state_root": "0x8b65545fe5f3216b47b6339b9c91ca2b7f1032a970b04246d9e9fb4460ee34c3", + "receipts_root": "0x7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095", + "logs_bloom": "0x00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010", + "prev_randao": "0x6d9e2a012d82b1b6cb0a2c1c1ed24cc16dbb56e6e39ae545371e0666ab057862", + "block_number": 215, + "gas_limit": 64842908, + "gas_used": 119301, + "timestamp": 1705859527, + "extra_data": "0xd983010d0a846765746888676f312e32312e358664617277696e", + "base_fee_per_gas": 7, + "block_hash": "0x48498dbfbcfae53a7f4c289ee00747aceea925f6260c50ead5a33e1c55c40f98", + "transactions_root": "0x5ebc1347fe3df0611d4f66b19bd8e1c6f4eaed0371d850f14c83b1c77ea234e6", + "withdrawals_root": "0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535", + "blob_gas_used": 0, + "excess_blob_gas": 0 + } + }, + "execution_branch": [ + "0xf8c69d3830406d668619bcccc13c8dddde41e863326f7418b241d5924c4ad34a", + "0xb46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb", + "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0xf4d6b5cf9c6e212615c3674fa625d04eb1114153fb221ef5ad02aa433fc67cfb" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json new file mode 100755 index 0000000000000000000000000000000000000000..f9d5324d57b15fc657876b46bbf4a8e716d4c30c --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json @@ -0,0 +1,38 @@ +{ + "attested_header": { + "slot": 2566, + "proposer_index": 6, + "parent_root": "0x6eb9f13a2c496318ce1ab3087bbd872f5c9519a1a7ca8231a2453e3cb523af00", + "state_root": "0xc8cb12766113dff7e46d2917267bf33d0626d99dd47715fcdbc5c65fad3c04b4", + "body_root": "0xd8cfd0d7bc9bc3724417a1655bb0a67c0765ca36197320f4d834150b52ef1420" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0x9296f9a0387f2cac47008e22ad7c3cd3d49d35384c13e6aa1eacca7dca7c3d2ca81515e50eb3396b9550ed20ef7d8fa2049a186598feb2c00e93728045fcff917733d1827481b8fc95f3913e27fc70112c2490496eb57bb7181f02c3f9fd471f" + }, + "signature_slot": 2567, + "next_sync_committee_update": null, + "finalized_header": { + "slot": 2496, + "proposer_index": 2, + "parent_root": "0xc99e49787106733eeebab4d93eb326e1f2214575c9d928f0c4ab0da0776f1622", + "state_root": "0xfbf8a08c86ef36bd173e37e733da4a78aa8e85fee99a990e858dd12a59087fde", + "body_root": "0xa2a8ad06901447b2807a9059580a4c40d8a941f325b1343c69f7c7c6c90e4ab0" + }, + "finality_branch": [ + "0x4e00000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0x958b8e43347f6df6fa5eb3d62d06a862381a6585aa40640dd1c0de11f1cf89c1", + "0xf107dce04faa86a28fc5d4a618be9cb8d4fc3c23d6c42c3624f3ff4bf6586a03", + "0xa501cdc02e86969ac3e4d0c5a36f4f049efaa1ab8cb6693f51d130eb52a80f30" + ], + "block_roots_root": "0xd160b7687041891b73e54b06fc4e04f82d0fa8fdd76705895e216c6b24709dfe", + "block_roots_branch": [ + "0x105290e42d98ab6a0ada6e55453cede36c672abf645eeb986b88d7487616e135", + "0x9da41f274bcdf6122335443d9ce94d07163b48dba3e2f9499ff56f4e48b48b99", + "0xecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5", + "0xb2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2", + "0xcd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json new file mode 100644 index 0000000000000000000000000000000000000000..5aa5a59f023761e248fd23ec3b29de19092e5e90 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json @@ -0,0 +1,31 @@ +{ + "execution_header": { + "parent_hash": "0xd82ec63f5c5e6ba61d62f09c188f158e6449b94bdcc31941e68639eec3c4cf7a", + "state_root": "0x8b65545fe5f3216b47b6339b9c91ca2b7f1032a970b04246d9e9fb4460ee34c3", + "receipts_root": "0x7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095", + "block_number": 215 + }, + "message": { + "event_log": { + "address": "0xeda338e4dc46038493b885327842fd3e301cab39", + "topics": [ + "0x7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f", + "0xc173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539", + "0x5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000" + }, + "Proof": { + "block_hash": "0x48498dbfbcfae53a7f4c289ee00747aceea925f6260c50ead5a33e1c55c40f98", + "tx_index": 0, + "data": { + "keys": [ + "0x7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095" + ], + "values": [ + "0xf9028e822080b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000" + ] + } + } + } +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json new file mode 100755 index 0000000000000000000000000000000000000000..202790c1db5b53068c7aedf561dc7972cb863f00 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json @@ -0,0 +1,542 @@ +{ + "header": { + "slot": 2496, + "proposer_index": 2, + "parent_root": "0xc99e49787106733eeebab4d93eb326e1f2214575c9d928f0c4ab0da0776f1622", + "state_root": "0xfbf8a08c86ef36bd173e37e733da4a78aa8e85fee99a990e858dd12a59087fde", + "body_root": "0xa2a8ad06901447b2807a9059580a4c40d8a941f325b1343c69f7c7c6c90e4ab0" + }, + "current_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "current_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0x93880225bf99a0c5ec22b266ff829837754e9c5edf37a68c05b8f803fd82fa45", + "0x4c60656ec9a95fcf11030ad309c716b5b15beb7f60a0bcfc7c9d4eff505472ff", + "0x22d1645fceb4bf9a695043dda19a53e784ec70df6a6b1bd66ea30eba1cca5f2f", + "0xa8fc6cad84ceefc633ec56c2d031d525e1cb4b51c70eb252919fce5bba9a1fde" + ], + "validators_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", + "block_roots_root": "0xd160b7687041891b73e54b06fc4e04f82d0fa8fdd76705895e216c6b24709dfe", + "block_roots_branch": [ + "0x105290e42d98ab6a0ada6e55453cede36c672abf645eeb986b88d7487616e135", + "0x9da41f274bcdf6122335443d9ce94d07163b48dba3e2f9499ff56f4e48b48b99", + "0xecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5", + "0xb2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2", + "0xcd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json new file mode 100755 index 0000000000000000000000000000000000000000..d9bf025ad354df6165f5b101f575dc2355831ee2 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json @@ -0,0 +1,38 @@ +{ + "attested_header": { + "slot": 8259, + "proposer_index": 0, + "parent_root": "0x877e9b66f04549e4c924ea6aeb4a33bb7d773b341845dd690f5d738145002f86", + "state_root": "0x724b67fde7de071886d930c5c10560896820cff056029f8519d74599ba244e60", + "body_root": "0x6a3cf016e2be639d86994dc76361195a9aec0a67a18978dbb512765adef02297" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xb817cf06ac29aa973099421c61a7d6bf91a2d04b825c4f860a999d59ccb78e4e53e604f6309f08c7ded14e9170e837150ad3a9994eb1c37d334bc03e35ddf9eadef503027485b339f16bcd5b79715a6bbd58bd823429a1a35d1be2d44a1152f1" + }, + "signature_slot": 8260, + "next_sync_committee_update": null, + "finalized_header": { + "slot": 8192, + "proposer_index": 5, + "parent_root": "0x15889c6c548ed2859150a8d46043ce2711f66a4b4bc61cc0185407d84304ad5b", + "state_root": "0x47f766a70bb799a34f9168e05f8e04b38f5a6c84398c519742a51e1fe7224148", + "body_root": "0x57fbc20d80ae3e3c6c837c98baa7885d9c7e016530625d231a58bb8bdeab2404" + }, + "finality_branch": [ + "0x0001000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x5e8a7e8804797705fbca4f2c30646ebea1e4a0da5d001a3455de0301a915e96c", + "0x067ba41a97d3634c7d6bcc5944487e35053644ce1cfe11868d27a97eadc6c012", + "0x995a48c6e5f01d6c345135705cdcfccd7e83d6f0f7d506a7ba9a2578a6ccee3a", + "0xaebdc6f600a419d1cdb7889921c93df7381e8dd934a31a448f31a25d6c2a83fa" + ], + "block_roots_root": "0x2fc4d44f8cea295d336a7f3341ea3eaa258533c917c1de3123fb605a5ed938d7", + "block_roots_branch": [ + "0xa94746addf566d1f83eaf46d2a4b78998b22ad7ae9a12b775d23cf8c50acb4ae", + "0x32a148bfd7e07c2ac056bcab18839d6a21227f3a9d96131c8462183dc42006d7", + "0x3ebcc8ac089dee384fab2122309f5aa64209da83dab2ba9985dcf8e146ed83eb", + "0x6588dcf3d33e1f7697a2d964e68c47df637e026a58b094335a133a726c0a063b", + "0x5ba1e52a00ea3dd089557faa97876f3222ee916ffa94437e0cb43c95ddddd0d3" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json new file mode 100755 index 0000000000000000000000000000000000000000..4937f66ff0cd2819f75d5e053b6c577635472dfa --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json @@ -0,0 +1,563 @@ +{ + "attested_header": { + "slot": 8259, + "proposer_index": 0, + "parent_root": "0x877e9b66f04549e4c924ea6aeb4a33bb7d773b341845dd690f5d738145002f86", + "state_root": "0x724b67fde7de071886d930c5c10560896820cff056029f8519d74599ba244e60", + "body_root": "0x6a3cf016e2be639d86994dc76361195a9aec0a67a18978dbb512765adef02297" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xb817cf06ac29aa973099421c61a7d6bf91a2d04b825c4f860a999d59ccb78e4e53e604f6309f08c7ded14e9170e837150ad3a9994eb1c37d334bc03e35ddf9eadef503027485b339f16bcd5b79715a6bbd58bd823429a1a35d1be2d44a1152f1" + }, + "signature_slot": 8260, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0xcdc4165a88ae33b52f88df39f4620b936e5d23296fb670b22c04774293e8c4a9", + "0x067ba41a97d3634c7d6bcc5944487e35053644ce1cfe11868d27a97eadc6c012", + "0x995a48c6e5f01d6c345135705cdcfccd7e83d6f0f7d506a7ba9a2578a6ccee3a", + "0xaebdc6f600a419d1cdb7889921c93df7381e8dd934a31a448f31a25d6c2a83fa" + ] + }, + "finalized_header": { + "slot": 8192, + "proposer_index": 5, + "parent_root": "0x15889c6c548ed2859150a8d46043ce2711f66a4b4bc61cc0185407d84304ad5b", + "state_root": "0x47f766a70bb799a34f9168e05f8e04b38f5a6c84398c519742a51e1fe7224148", + "body_root": "0x57fbc20d80ae3e3c6c837c98baa7885d9c7e016530625d231a58bb8bdeab2404" + }, + "finality_branch": [ + "0x0001000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x5e8a7e8804797705fbca4f2c30646ebea1e4a0da5d001a3455de0301a915e96c", + "0x067ba41a97d3634c7d6bcc5944487e35053644ce1cfe11868d27a97eadc6c012", + "0x995a48c6e5f01d6c345135705cdcfccd7e83d6f0f7d506a7ba9a2578a6ccee3a", + "0xaebdc6f600a419d1cdb7889921c93df7381e8dd934a31a448f31a25d6c2a83fa" + ], + "block_roots_root": "0x2fc4d44f8cea295d336a7f3341ea3eaa258533c917c1de3123fb605a5ed938d7", + "block_roots_branch": [ + "0xa94746addf566d1f83eaf46d2a4b78998b22ad7ae9a12b775d23cf8c50acb4ae", + "0x32a148bfd7e07c2ac056bcab18839d6a21227f3a9d96131c8462183dc42006d7", + "0x3ebcc8ac089dee384fab2122309f5aa64209da83dab2ba9985dcf8e146ed83eb", + "0x6588dcf3d33e1f7697a2d964e68c47df637e026a58b094335a133a726c0a063b", + "0x5ba1e52a00ea3dd089557faa97876f3222ee916ffa94437e0cb43c95ddddd0d3" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json new file mode 100755 index 0000000000000000000000000000000000000000..6bf20355c7a34ea3b1a9949f39cabc363a029590 --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json @@ -0,0 +1,563 @@ +{ + "attested_header": { + "slot": 129, + "proposer_index": 5, + "parent_root": "0xe32b6c18f029e755b0273dc1c4fa2bc4979794c8286ad40276c1b8a8e36049d8", + "state_root": "0x5ec9dacf25a5f09f20be0c59246b3d8dcfe64bd085b4bac5cec180690339801e", + "body_root": "0x4080cf2412d6ff77fc3164ad6155423a7112f207f173145ec16371a93f481f87" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xa761c3333fbb3d36bc8f65454f898da38001499dcd37494cf3d86940a995399ae649216ba4c985af154f83f72c8b1856079b7636a7a8d7d3f7602df2cbf699edb72b65253e82de4d9cc4db7377eafb22f799129f63f094a21c00675bdd5cc243" + }, + "signature_slot": 130, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0xfd1e5ff5d4a15081efe3ff17857b1f95984c9a271b1c41c2f81f43e60c2cc541", + "0xe1c97f93bb7352d395d1ff8ee29881572cb7eb5d71634783701171dcd30cd93d", + "0x77fa2170ddbd89b15dae02f2e6cf9f76c8e00d1c4217320acffbe01576d0da61", + "0xe97288e0627219087a024078d69445f34f0583a6350a7c3c40c39fd1fa6f8d68" + ] + }, + "finalized_header": { + "slot": 64, + "proposer_index": 4, + "parent_root": "0x0f7bc2353778c14c7f6dba0fc5fe6eec87228b0d3a5447b61dce67b4d9338de3", + "state_root": "0xfeb990de653ce494c0a263f820eaf05a9300dbdc30cb6065ede602827bfccde4", + "body_root": "0xf5235cd8c24f2695fc5b7989926305c10ad8cf5a87d62a739f675f5543df2ec1" + }, + "finality_branch": [ + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0xe1c97f93bb7352d395d1ff8ee29881572cb7eb5d71634783701171dcd30cd93d", + "0x77fa2170ddbd89b15dae02f2e6cf9f76c8e00d1c4217320acffbe01576d0da61", + "0xe97288e0627219087a024078d69445f34f0583a6350a7c3c40c39fd1fa6f8d68" + ], + "block_roots_root": "0x6fcdfd1c3fb1bdd421fe59dddfff3855b5ed5e30373887991a0059d019ad12bc", + "block_roots_branch": [ + "0x94b59531f172bc24f914bc0c10104ccb158676850f8cc3b47b6ddb7f096ebdd7", + "0x22470ed9155a938587d44d5fa19217c0f939d8862e504e67cd8cb4d1b960795e", + "0xfeec3ef1a68f93849e71e84f90b99602cccc31868137b6887ca8244a4b979e8e", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0xf5ff4b0c6190005015889879568f5f0d9c40134c7ec4ffdda47950dcd92395ad" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b850496cd4e14cd906565d488450b339a29f463f --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -0,0 +1,102 @@ +[package] +name = "snowbridge-pallet-inbound-queue" +description = "Snowbridge Inbound Queue Pallet" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { optional = true, workspace = true, default-features = true } +codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +hex-literal = { version = "0.4.1", optional = true } +log = { workspace = true } +alloy-primitives = { version = "0.4.2", default-features = false, features = ["rlp"] } +alloy-sol-types = { version = "0.4.2", default-features = false } +alloy-rlp = { version = "0.3.3", default-features = false, features = ["derive"] } +num-traits = { version = "0.2.16", default-features = false } + +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } + +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } + +snowbridge-core = { path = "../../primitives/core", default-features = false } +snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } +snowbridge-router-primitives = { path = "../../primitives/router", default-features = false } +snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false } +snowbridge-pallet-inbound-queue-fixtures = { path = "./fixtures", default-features = false, optional = true } + +[dev-dependencies] +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +snowbridge-pallet-ethereum-client = { path = "../ethereum-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-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-ethereum/std", + "snowbridge-pallet-inbound-queue-fixtures?/std", + "snowbridge-router-primitives/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", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-fixtures/runtime-benchmarks", + "snowbridge-router-primitives/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", + "snowbridge-pallet-ethereum-client/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/inbound-queue/README.md b/bridges/snowbridge/pallets/inbound-queue/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cc2f7c636e68b93fef8b3048409340181ef1ffc7 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue + +Reads messages from Ethereum and sends it to intended destination on Polkadot, using XCM. diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..64605a42f0d383d838429eb9b82b5f6cf238ab09 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "snowbridge-pallet-inbound-queue-fixtures" +description = "Snowbridge Inbound Queue Test Fixtures" +version = "0.10.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex-literal = { version = "0.4.1" } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +snowbridge-core = { path = "../../../primitives/core", default-features = false } +snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "sp-core/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", +] diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4f3445b2905364147d3974988fee0fad2f387d07 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::Message; +use sp_core::RuntimeDebug; + +pub mod register_token; +pub mod register_token_with_insufficient_fee; +pub mod send_token; +pub mod send_token_to_penpal; + +#[derive(Clone, RuntimeDebug)] +pub struct InboundQueueFixture { + pub execution_header: CompactExecutionHeader, + pub message: Message, +} diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs new file mode 100644 index 0000000000000000000000000000000000000000..b8d510e6b13d02420191591ff917eb0366f818d6 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use crate::InboundQueueFixture; +use hex_literal::hex; +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::{Log, Message, Proof}; +use sp_std::vec; + +pub fn make_register_token_message() -> InboundQueueFixture { + InboundQueueFixture { + execution_header: CompactExecutionHeader{ + parent_hash: hex!("d5de3dd02c96dbdc8aaa4db70a1e9fdab5ded5f4d52f18798acd56a3d37d1ad6").into(), + block_number: 772, + state_root: hex!("49cba2a79b23ad74cefe80c3a96699825d1cda0f75bfceb587c5549211c86245").into(), + receipts_root: hex!("7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095").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!("392182a385b3a417e8ddea8b252953ee81e6ec0fb09d9056c96c89fbeb703a3f").into(), + tx_index: 0, + data: (vec![ + hex!("7b1f61b9714c080ef0be014e01657a15f45f0304b477beebc7ca5596c8033095").to_vec(), + ], vec![ + hex!("f9028e822080b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(), + ]), + }, + }, + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs new file mode 100644 index 0000000000000000000000000000000000000000..82ff2283101e331395b06dd14f3876076e71722e --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use crate::InboundQueueFixture; +use hex_literal::hex; +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::{Log, Message, Proof}; +use sp_std::vec; + +pub fn make_register_token_with_infufficient_fee_message() -> InboundQueueFixture { + InboundQueueFixture { + execution_header: CompactExecutionHeader{ + parent_hash: hex!("998e81dc6df788a920b67e058fbde0dc3f4ec6f11f3f7cd8c3148e6d99584885").into(), + block_number: 338, + state_root: hex!("30ef9c9db2609de19bbc6c3cbeddac889e82bbcb2db20304b3abdfbdc7134cbf").into(), + receipts_root: hex!("969335c3132a007cb8b5886a3c23dd8da63cba04aeda29857a86ee1c13dae782").into(), + }, + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // insufficient xcm fee as only 1000(hex:e803) + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7de8030000000000000000000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + block_hash: hex!("5976f37f0e331d194eb331df74355ef47565c3a1bd11c95a45b681f6917085c1").into(), + tx_index: 0, + data: (vec![ + hex!("969335c3132a007cb8b5886a3c23dd8da63cba04aeda29857a86ee1c13dae782").to_vec(), + ], vec![ + hex!("f9028e822080b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7de8030000000000000000000000000000000000000000000000000000000000000000").to_vec(), + ]), + }, + }, + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs new file mode 100755 index 0000000000000000000000000000000000000000..2562217100eaf7cd0c5dff420c55826b277d3570 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use crate::InboundQueueFixture; +use hex_literal::hex; +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::{Log, Message, Proof}; +use sp_std::vec; + +pub fn make_send_token_message() -> InboundQueueFixture { + InboundQueueFixture { + execution_header: CompactExecutionHeader{ + parent_hash: hex!("920cecde45d428e3a77590b70f8533cf4c2c36917b8a7b74c915e7fa3dae7075").into(), + block_number: 1148, + state_root: hex!("bbc6ba0e9940d641afecbbaf3f97abd2b9ffaf2f6bd4879c4a71e659eca89978").into(), + receipts_root: hex!("9f3340b57eddc1f86de30776db57faeca80269a3dd459031741988dec240ce34").into(), + }, + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26f").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").into(), + }, + proof: Proof { + block_hash: hex!("d3c155f123c3cbff22f3d7869283e02179edea9ffa7a5e9a4d8414c2a6b8991f").into(), + tx_index: 0, + data: (vec![ + hex!("9f3340b57eddc1f86de30776db57faeca80269a3dd459031741988dec240ce34").to_vec(), + ], vec![ + hex!("f90451822080b9044b02f90447018301bcb9b9010000800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000f9033cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26fb8c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").to_vec(), + ]), + }, + }, + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs new file mode 100755 index 0000000000000000000000000000000000000000..86ba3f7ecc18f3743fd22592fc23a50f26b447d7 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use crate::InboundQueueFixture; +use hex_literal::hex; +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::{Log, Message, Proof}; +use sp_std::vec; + +pub fn make_send_token_to_penpal_message() -> InboundQueueFixture { + InboundQueueFixture { + execution_header: CompactExecutionHeader{ + parent_hash: hex!("434148c290f27ee4be34fa344cd7608bf942a4541b27c9d868439631b3f37a8d").into(), + block_number: 816, + state_root: hex!("595e643f9095870e30e85e2bbef7d9e3a39df5aae839d26cf455d3dbf3e5a539").into(), + receipts_root: hex!("c40ab2c4abcfdea4f42195e0ad822806e5423108021c3b542646c7193319a6c1").into(), + }, + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26f").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + block_hash: hex!("6c49a7f8fb2014a23e58a949c95a6743174589a7ce83434b073dc05dec402f3d").into(), + tx_index: 0, + data: (vec![ + hex!("c40ab2c4abcfdea4f42195e0ad822806e5423108021c3b542646c7193319a6c1").to_vec(), + ], vec![ + hex!("f90471822080b9046b02f90467018301d30fb9010000800000000000000000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020000000000000000000000000000003000000000080018000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000f9035cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000000000000000000000000000000000000000007d0b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000201cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cf9015c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26fb8e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").to_vec(), + ]), + }, + }, + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..931befa2ac67810eaf7aac1b8156a46fc398f7a2 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs @@ -0,0 +1,53 @@ +// 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; +use snowbridge_pallet_inbound_queue_fixtures::register_token::make_register_token_message; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + + let create_message = make_register_token_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/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs new file mode 100644 index 0000000000000000000000000000000000000000..826d535c2cb922610ba4811d607a9024de8d33ab --- /dev/null +++ b/bridges/snowbridge/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/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdc21fcf037025f933b7c11e92937744e83e1da7 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -0,0 +1,372 @@ +// 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::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::*, Location, SendError as XcmpSendError, SendXcm, + Xcm, XcmContext, XcmHash, +}; +use xcm_executor::traits::TransactAsset; + +use snowbridge_core::{ + inbound::{Message, VerificationError, Verifier}, + sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, + StaticLookup, +}; +use snowbridge_router_primitives::{ + inbound, + inbound::{ConvertMessage, ConvertMessageError}, +}; +use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; + +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::*; + + #[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; + + /// To withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + } + + #[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], + /// Fee burned for the teleport + fee_burned: BalanceOf, + }, + /// 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()), + }; + + log::info!( + target: LOG_TARGET, + "💫 xcm decoded as {:?} with fee {:?}", + xcm, + fee + ); + + // Burning fees for teleport + Self::burn_fees(channel.para_id, 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, + fee_burned: fee, + }); + + 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 = Location::new(1, [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) + } + + /// Burn the amount of the fee embedded into the XCM for teleports + pub fn burn_fees(para_id: ParaId, fee: BalanceOf) -> DispatchResult { + let dummy_context = + XcmContext { origin: None, message_id: Default::default(), topic: None }; + let dest = Location::new(1, [Parachain(para_id.into())]); + let fees = (Location::parent(), fee.saturated_into::()).into(); + T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset check out failed with error {:?}", error + ); + TokenError::FundsUnavailable + })?; + T::AssetTransactor::check_out(&dest, &fees, &dummy_context); + T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset withdraw failed with error {:?}", error + ); + TokenError::FundsUnavailable + })?; + Ok(()) + } + } + + /// 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/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..086b27cb8280a5357b56e50065124c0d20f0e5a7 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{ + derive_impl, 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::{latest::SendXcm, prelude::*}; +use xcm_executor::AssetsInHolding; + +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_pallet_ethereum_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; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + 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 PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + 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 = (); +} + +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, + }, + deneb: Fork { + version: [4, 0, 0, 1], // 0x04000001 + epoch: 4294967295, + } + }; +} + +impl snowbridge_pallet_ethereum_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 { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::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() }) + } +} + +pub struct SuccessfulTransactor; +impl TransactAsset for SuccessfulTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + +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>; + type AssetTransactor = SuccessfulTransactor; +} + +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/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a47e475b8c997a6fe4cc4d1860af3da0bf52a57 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -0,0 +1,212 @@ +// 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: [ + 57, 61, 232, 3, 66, 61, 25, 190, 234, 188, 193, 174, 13, 186, 1, 64, 237, 94, 73, + 83, 14, 18, 209, 213, 78, 121, 43, 108, 251, 245, 107, 67, + ], + fee_burned: 110000000000, + } + .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/pallets/inbound-queue/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2c665f40d9e5e00ff452ad3e1151152b428bf07 --- /dev/null +++ b/bridges/snowbridge/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/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f16a28cb1e457d9ebfb7804fa013e5b57858f79e --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -0,0 +1,82 @@ +[package] +name = "snowbridge-pallet-outbound-queue" +description = "Snowbridge Outbound Queue Pallet" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { features = ["alloc", "derive"], workspace = true } +codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +hex-literal = { version = "0.4.1", optional = true } + +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } + +bridge-hub-common = { path = "../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } + +snowbridge-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } +snowbridge-outbound-queue-merkle-tree = { path = "merkle-tree", default-features = false } +ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } + +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } + +[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/pallets/outbound-queue/README.md b/bridges/snowbridge/pallets/outbound-queue/README.md new file mode 100644 index 0000000000000000000000000000000000000000..19638f90e6a5f9fde34cb242f8a9fdbafb7cd314 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/README.md @@ -0,0 +1,3 @@ +# Ethereum Outbound Queue + +Sends messages from an origin in the Polkadot ecosystem to Ethereum. diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0606e9de33056c9dffae50befcc1da5e865dca44 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "snowbridge-outbound-queue-merkle-tree" +description = "Snowbridge Outbound Queue Merkle Tree" +version = "0.3.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[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" +sp-crypto-hashing = { path = "../../../../../substrate/primitives/crypto/hashing" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a3afef1d6713745fbda8581001b00b112ce5af6a --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md @@ -0,0 +1,4 @@ +# Snowbridge Outbound Queue Merkle Tree + +This crate implements a simple binary Merkle Tree utilities required for inter-op with Ethereum +bridge & Solidity contract. diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..8c91ccd04d9a6b001db3cf561b95ff0723d700aa --- /dev/null +++ b/bridges/snowbridge/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_crypto_hashing::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/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..cb68fd0a250a92e7f6a6693f3aebf1c8553308aa --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "snowbridge-outbound-queue-runtime-api" +description = "Snowbridge Outbound Queue Runtime API" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[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/pallets/outbound-queue/runtime-api/README.md b/bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..98ae01fb33dade1b77d132462acd16957583fe5b --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md @@ -0,0 +1,6 @@ +# Ethereum Outbound Queue Runtime API + +Provides an API: + +- to generate merkle proofs for outbound messages +- calculate delivery fee for delivering messages to Ethereum diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..51f46a7b49c8838eddf44d9d3ba18f07b57c5dcd --- /dev/null +++ b/bridges/snowbridge/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/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs new file mode 100644 index 0000000000000000000000000000000000000000..44d63f1e2d23f48f3d13d7834de5cde8d2c78dfc --- /dev/null +++ b/bridges/snowbridge/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/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee5754e86962f807b9bd68d0a18560ad34c08cb2 --- /dev/null +++ b/bridges/snowbridge/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/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9e949a4791a8a64d4c36f3f78628279c367939f8 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -0,0 +1,406 @@ +// 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-pallet-system`: +//! +//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver` +//! 2. `snowbridge_pallet_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, 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 }, + } + + #[pallet::error] + pub enum Error { + /// The message is too large + MessageTooLarge, + /// The pallet is halted + Halted, + /// 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/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..850b13dcf310e0b5660f5d80685787761380bd2f --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{ + derive_impl, 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; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + 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 PalletInfo = PalletInfo; + 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/pallets/outbound-queue/src/process_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..731aa6fa6d5cae98174d4936c8416bcf17ffccce --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! 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/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..03be61819973d5bb4bcb663b7cd5f928189c9d1d --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! 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/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..8ed4a318d68e99181e7f1e0793cc66e23829132a --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/test.rs @@ -0,0 +1,312 @@ +// 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, PricingParameters, Rewards, +}; +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.try_into().unwrap(); + 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.try_into().unwrap(); + 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 + ] + ); + }); +} + +#[test] +fn validate_messages_with_fees() { + new_tester().execute_with(|| { + let message = mock_message(1000); + let (_, fee) = OutboundQueue::validate(&message).unwrap(); + assert_eq!(fee.local, 698000000); + assert_eq!(fee.remote, 2680000000000); + }); +} + +#[test] +fn test_calculate_fees() { + new_tester().execute_with(|| { + let gas_used: u64 = 250000; + let illegal_price_params: PricingParameters<::Balance> = + PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: 10000_u32.into(), + rewards: Rewards { local: 1_u32.into(), remote: 1_u32.into() }, + }; + let fee = OutboundQueue::calculate_fee(gas_used, illegal_price_params); + assert_eq!(fee.local, 698000000); + assert_eq!(fee.remote, 1000000); + }); +} + +#[test] +fn test_calculate_fees_with_valid_exchange_rate_but_remote_fee_calculated_as_zero() { + new_tester().execute_with(|| { + let gas_used: u64 = 250000; + let illegal_price_params: PricingParameters<::Balance> = + PricingParameters { + exchange_rate: FixedU128::from_rational(1, 1), + fee_per_gas: 1_u32.into(), + rewards: Rewards { local: 1_u32.into(), remote: 1_u32.into() }, + }; + let fee = OutboundQueue::calculate_fee(gas_used, illegal_price_params.clone()); + assert_eq!(fee.local, 698000000); + // Though none zero pricing params the remote fee calculated here is invalid + // which should be avoided + assert_eq!(fee.remote, 0); + }); +} diff --git a/bridges/snowbridge/pallets/outbound-queue/src/types.rs b/bridges/snowbridge/pallets/outbound-queue/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..f65a15d3d2f90091b0f5090ca029764d78ef9bf5 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/types.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::{Decode, Encode}; +use ethabi::Token; +use frame_support::traits::ProcessMessage; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::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())), + ]) + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue/src/weights.rs b/bridges/snowbridge/pallets/outbound-queue/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..87eee1a31e87b8e172d301ccd25dd3412153588d --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue/src/weights.rs @@ -0,0 +1,81 @@ + +//! Autogenerated weights for `snowbridge-pallet-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-pallet-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-pallet-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/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5ad04290de044a2c8ed13aa092f5ea033aaafb97 --- /dev/null +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "snowbridge-pallet-system" +description = "Snowbridge System Pallet" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[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.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 = { workspace = true } + +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } + +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } + +ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } +snowbridge-core = { path = "../../primitives/core", default-features = false } + +[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-pallet-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-pallet-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-pallet-outbound-queue/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/system/README.md b/bridges/snowbridge/pallets/system/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5ab11d45eae2e28f1f571b6fb6122a886848e916 --- /dev/null +++ b/bridges/snowbridge/pallets/system/README.md @@ -0,0 +1,3 @@ +# Ethereum System + +Contains management functions to manage functions on Ethereum. For example, creating agents and channels. diff --git a/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..eb02ae1db529730f51743e79a322e54db44fee51 --- /dev/null +++ b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "snowbridge-system-runtime-api" +description = "Snowbridge System Runtime API" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[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", +] } +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/pallets/system/runtime-api/README.md b/bridges/snowbridge/pallets/system/runtime-api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..99827c9c2fc280cba46d05586b910866c81d2749 --- /dev/null +++ b/bridges/snowbridge/pallets/system/runtime-api/README.md @@ -0,0 +1,3 @@ +# Ethereum System Runtime API + +Provides an API for looking up an agent ID on Ethereum. diff --git a/bridges/snowbridge/pallets/system/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/system/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..7f119809546e473d9afd9efdbe46430f37340a19 --- /dev/null +++ b/bridges/snowbridge/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::VersionedLocation; + +sp_api::decl_runtime_apis! { + pub trait ControlApi + { + fn agent_id(location: VersionedLocation) -> Option; + } +} diff --git a/bridges/snowbridge/pallets/system/src/api.rs b/bridges/snowbridge/pallets/system/src/api.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef12b03e1d75871068e710be594e6052dfa5406e --- /dev/null +++ b/bridges/snowbridge/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::*, VersionedLocation}; + +use crate::{agent_id_of, Config}; + +pub fn agent_id(location: VersionedLocation) -> Option +where + Runtime: Config, +{ + let location: Location = location.try_into().ok()?; + agent_id_of::(&location).ok() +} diff --git a/bridges/snowbridge/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef908ad6a3f9dee2f269333a79b8d4afa5e6b7ca --- /dev/null +++ b/bridges/snowbridge/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 = Location::new(1, [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 = Location::new(1, [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 = Location::new(1, [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 = Location::new(1, [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 = Location::new(1, [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 = Location::new(1, [Parachain(origin_para_id)]); + let origin = T::Helper::make_xcm_origin(origin_location.clone()); + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + + let versioned_location: VersionedLocation = 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/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e5ceb5e9b1d42796567c3da5e549b2af3cfd4de --- /dev/null +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -0,0 +1,685 @@ +// 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)] + +#[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 use pallet::*; + +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: &Location) -> Result<(ParaId, H256), DispatchError> +where + T: Config, +{ + match location.unpack() { + (1, [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: &Location) -> 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: Location) -> 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 Location 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, + InvalidUpgradeParameters, + } + + /// 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)?; + + ensure!( + !impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()), + Error::::InvalidUpgradeParameters + ); + + 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 `Location` + #[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 `Location` of a sibling parachain + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn create_agent(origin: OriginFor) -> DispatchResult { + let origin_location: Location = 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 `Location` + /// - `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: Location = 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 `Location` + /// - `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: Location = 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 `Location` + #[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: Location = 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: Location = + (*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: Location = + ParentThen(Parachain(asset_hub_para_id.into()).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::(&Location::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/pallets/system/src/migration.rs b/bridges/snowbridge/pallets/system/src/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee94fc091bd1ecb0511789c998e65c0b8f665451 --- /dev/null +++ b/bridges/snowbridge/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/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..a711eab5d3d4ea5068af634fedbbc173f8186395 --- /dev/null +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as snowbridge_system; +use frame_support::{ + derive_impl, parameter_types, + traits::{tokens::fungible::Mutate, ConstU128, 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 Location); + + impl From for Origin { + fn from(location: Location) -> Origin { + Origin(location) + } + } + + /// `EnsureOrigin` implementation succeeding with a `Location` 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 = Location; + + 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(Location::new(1, [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_pallet_outbound_queue::{Pallet, Call, Storage, Event}, + EthereumSystem: snowbridge_system, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + 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 PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + 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 = (); +} + +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_pallet_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: Location = Location::parent(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)].into(); +} + +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: Location) -> 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: Location) -> RuntimeOrigin { + pallet_xcm_origin::Origin(location).into() +} + +pub fn make_agent_id(location: Location) -> AgentId { + ::AgentIdOf::convert_location(&location) + .expect("convert location") +} diff --git a/bridges/snowbridge/pallets/system/src/tests.rs b/bridges/snowbridge/pallets/system/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..09f24195a30ad8ee13f4587c54102bc24eed4134 --- /dev/null +++ b/bridges/snowbridge/pallets/system/src/tests.rs @@ -0,0 +1,633 @@ +// 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; +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 = Location::new(1, [Parachain(origin_para_id)]); + let agent_id = make_agent_id(origin_location.clone()); + 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 = Location::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 = Location::new(1, [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(Location::new(1, [],))), + BadOrigin, + ); + + // local account location not allowed + assert_noop!( + EthereumSystem::create_agent(make_xcm_origin(Location::new( + 0, + [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 = [1_u8; 20].into(); + let code_hash: H256 = [1_u8; 32].into(); + + 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 = [1_u8; 20].into(); + let code_hash: H256 = [1_u8; 32].into(); + 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 = Location::new(1, [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 = Location::new(1, [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(Location::new(1, [])), + OperatingMode::Normal, + ), + BadOrigin, + ); + + // child of sibling location not allowed + assert_noop!( + EthereumSystem::create_channel( + make_xcm_origin(Location::new( + 1, + [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(Location::new( + 0, + [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 = Location::new(1, [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(Location::new(1, [])), mode,), + BadOrigin, + ); + + // child of sibling location not allowed + assert_noop!( + EthereumSystem::update_channel( + make_xcm_origin(Location::new( + 1, + [Parachain(2000), Junction::AccountId32 { network: None, id: [67u8; 32] }], + )), + mode, + ), + BadOrigin, + ); + + // local account location not allowed + assert_noop!( + EthereumSystem::update_channel( + make_xcm_origin(Location::new( + 0, + [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 = Location::new(1, [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 = Location::new(1, [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 = Location::new(1, [Parachain(2000)]); + let origin = make_xcm_origin(origin_location.clone()); + 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.clone()); + 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 = Location::new(1, [Parachain(2000)]); + let versioned_location: Box = Box::new(location.clone().into()); + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + + // First create the agent + Agents::::insert(make_agent_id(location.clone()), ()); + + 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( + Location::new( + 1, + [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. + +#[test] +fn charge_fee_for_create_agent() { + new_test_ext(true).execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin_location = Location::new(1, [Parachain(para_id)]); + let origin = make_xcm_origin(origin_location.clone()); + 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 = Location::new(1, [Parachain(para_id)]); + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + let origin = make_xcm_origin(origin_location.clone()); + 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 = [1_u8; 20].into(); + let code_hash: H256 = [1_u8; 32].into(); + 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/pallets/system/src/weights.rs b/bridges/snowbridge/pallets/system/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e532a0d8a8c19339cc39574aeb09668468f34e9 --- /dev/null +++ b/bridges/snowbridge/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/pallets/ethereum-beacon-client/src/mock.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..d2cca373e92bd1a70f2ee68a7913214967b2dec0 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs @@ -0,0 +1,259 @@ +// 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 frame_support::derive_impl; + 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; + } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + 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 PalletInfo = PalletInfo; + type SS58Prefix = SS58Prefix; + 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::*; + use frame_support::derive_impl; + + 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; + } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + 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 PalletInfo = PalletInfo; + type SS58Prefix = SS58Prefix; + 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/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d181fa1d3945a704a3d1e1e28fea67b7dea0ee15 --- /dev/null +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "snowbridge-beacon-primitives" +description = "Snowbridge Beacon Primitives" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +hex = { version = "0.4", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +rlp = { version = "0.5", default-features = false } + +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } + +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 = "../ethereum", default-features = false } +static_assertions = { version = "1.1.0" } +milagro-bls = { package = "snowbridge-milagro-bls", version = "1.5.4", default-features = false } + +[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/primitives/beacon/README.md b/bridges/snowbridge/primitives/beacon/README.md new file mode 100644 index 0000000000000000000000000000000000000000..658d7c5be7df62d88af1ecc5e44546526b1597fe --- /dev/null +++ b/bridges/snowbridge/primitives/beacon/README.md @@ -0,0 +1,10 @@ +# Beacon Primitives + +Crate with low-level supporting functions for the beacon client, including: + +- bls12-381 signature handling to verify signatures on the beacon chain +- merkle proofs +- receipt verification +- ssz types + +The code in this crate relates to the Ethereum consensus chain, commonly referred to as the beacon chain. diff --git a/bridges/snowbridge/primitives/beacon/src/bits.rs b/bridges/snowbridge/primitives/beacon/src/bits.rs new file mode 100644 index 0000000000000000000000000000000000000000..72b7135ee2939bdabb98c9c06df801c43c3db230 --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/bls.rs b/bridges/snowbridge/primitives/beacon/src/bls.rs new file mode 100644 index 0000000000000000000000000000000000000000..589b72e67348f70122ad2362b8bc51474cb577ac --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/config.rs b/bridges/snowbridge/primitives/beacon/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa5fda706f9934148a74683d1bfa4ffc872ce8e2 --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/lib.rs b/bridges/snowbridge/primitives/beacon/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4c569d0176c21882aa3875024551848b1cc97494 --- /dev/null +++ b/bridges/snowbridge/primitives/beacon/src/lib.rs @@ -0,0 +1,32 @@ +// 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, + VersionedExecutionPayloadHeader, +}; +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/primitives/beacon/src/merkle_proof.rs b/bridges/snowbridge/primitives/beacon/src/merkle_proof.rs new file mode 100644 index 0000000000000000000000000000000000000000..a6ee6e9452c39d766565765e5aa5682e323c3f34 --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/receipt.rs b/bridges/snowbridge/primitives/beacon/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..0588f3f73f715b417476e5b9a9dd02b62388e585 --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/serde_utils.rs b/bridges/snowbridge/primitives/beacon/src/serde_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..07f5cbe724ed92bbda1d0cc7ded1a60c92a38cf0 --- /dev/null +++ b/bridges/snowbridge/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/primitives/beacon/src/ssz.rs b/bridges/snowbridge/primitives/beacon/src/ssz.rs new file mode 100644 index 0000000000000000000000000000000000000000..0925c3cc701d1b7493963599cde97bf939534724 --- /dev/null +++ b/bridges/snowbridge/primitives/beacon/src/ssz.rs @@ -0,0 +1,278 @@ +// 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()), + } +} + +pub mod deneb { + use crate::{ + config::{EXTRA_DATA_SIZE, FEE_RECIPIENT_SIZE, LOGS_BLOOM_SIZE}, + ssz::hash_tree_root, + types::deneb::ExecutionPayloadHeader, + }; + use byte_slice_cast::AsByteSlice; + use sp_core::H256; + use sp_std::{vec, vec::Vec}; + use ssz_rs::{ + prelude::{List, Vector}, + Deserialize, DeserializeError, SimpleSerializeError, Sized, U256, + }; + use ssz_rs_derive::SimpleSerialize as SimpleSerializeDerive; + + #[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], + pub blob_gas_used: u64, + pub excess_blob_gas: u64, + } + + 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(), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + }) + } + } + + impl ExecutionPayloadHeader { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::(self.clone().try_into()?) + } + } +} diff --git a/bridges/snowbridge/primitives/beacon/src/types.rs b/bridges/snowbridge/primitives/beacon/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..2af522f56b0dc4b00a155252e927a90e9bca9ed1 --- /dev/null +++ b/bridges/snowbridge/primitives/beacon/src/types.rs @@ -0,0 +1,641 @@ +// 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, iter::repeat, 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, + pub deneb: 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 { + let pubkeys: Vec = + repeat(PublicKeyPrepared::default()).take(COMMITTEE_SIZE).collect(); + let pubkeys: Box<[PublicKeyPrepared; COMMITTEE_SIZE]> = + Box::new(pubkeys.try_into().map_err(|_| ()).expect("checked statically; qed")); + + SyncCommitteePrepared { + root: H256::default(), + pubkeys, + 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().map_err(|_| ()).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(Serialize, 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, +} + +/// VersionedExecutionPayloadHeader +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[cfg_attr( + feature = "std", + derive(Serialize, Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +#[codec(mel_bound())] +pub enum VersionedExecutionPayloadHeader { + Capella(ExecutionPayloadHeader), + Deneb(deneb::ExecutionPayloadHeader), +} + +/// Convert VersionedExecutionPayloadHeader to CompactExecutionHeader +impl From for CompactExecutionHeader { + fn from(versioned_execution_header: VersionedExecutionPayloadHeader) -> Self { + match versioned_execution_header { + VersionedExecutionPayloadHeader::Capella(execution_payload_header) => + execution_payload_header.into(), + VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => + execution_payload_header.into(), + } + } +} + +impl VersionedExecutionPayloadHeader { + pub fn hash_tree_root(&self) -> Result { + match self { + VersionedExecutionPayloadHeader::Capella(execution_payload_header) => + hash_tree_root::( + execution_payload_header.clone().try_into()?, + ), + VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => + hash_tree_root::( + execution_payload_header.clone().try_into()?, + ), + } + } + + pub fn block_hash(&self) -> H256 { + match self { + VersionedExecutionPayloadHeader::Capella(execution_payload_header) => + execution_payload_header.block_hash, + VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => + execution_payload_header.block_hash, + } + } + + pub fn block_number(&self) -> u64 { + match self { + VersionedExecutionPayloadHeader::Capella(execution_payload_header) => + execution_payload_header.block_number, + VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => + execution_payload_header.block_number, + } + } +} + +#[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, +} + +pub mod deneb { + use crate::CompactExecutionHeader; + use codec::{Decode, Encode}; + use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; + use scale_info::TypeInfo; + #[cfg(feature = "std")] + use serde::{Deserialize, Serialize}; + use sp_core::{H160, H256, U256}; + use sp_std::prelude::*; + + /// ExecutionPayloadHeader + /// + #[derive( + Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, + )] + #[cfg_attr( + feature = "std", + derive(Serialize, 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, + pub blob_gas_used: u64, // [New in Deneb:EIP4844] + pub excess_blob_gas: u64, // [New in Deneb:EIP4844] + } + + 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, + } + } + } +} diff --git a/bridges/snowbridge/primitives/beacon/src/updates.rs b/bridges/snowbridge/primitives/beacon/src/updates.rs new file mode 100644 index 0000000000000000000000000000000000000000..1ecd32c6d7b79f34854d9387fba78b99cce333b2 --- /dev/null +++ b/bridges/snowbridge/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, SyncAggregate, SyncCommittee, VersionedExecutionPayloadHeader}; + +#[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: VersionedExecutionPayloadHeader, + /// 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/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9a299ad0ae92326a6d0bb0391baf81e6e5bad663 --- /dev/null +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "snowbridge-core" +description = "Snowbridge Core" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +serde = { optional = true, features = ["alloc", "derive"], workspace = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +hex-literal = { version = "0.4.1" } + +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } + +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } + +snowbridge-beacon-primitives = { path = "../beacon", default-features = false } + +ethabi = { package = "ethabi-decode", version = "1.0.0", 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/primitives/core/README.md b/bridges/snowbridge/primitives/core/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0126be63aebaf44b227accbd4d0388455f1d5394 --- /dev/null +++ b/bridges/snowbridge/primitives/core/README.md @@ -0,0 +1,4 @@ +# Core Primitives + +Contains common code core to Snowbridge, such as inbound and outbound queue types, pricing structs, ringbuffer data +types (used in the beacon client). diff --git a/bridges/snowbridge/primitives/core/src/inbound.rs b/bridges/snowbridge/primitives/core/src/inbound.rs new file mode 100644 index 0000000000000000000000000000000000000000..4b04470ad02615d86f1a1c530f3cbed809649328 --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed1af4225d24bb9ae8d08f7adda2925e7d5029d3 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -0,0 +1,166 @@ +// 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, Location}; +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 struct AllowSiblingsOnly; +impl Contains for AllowSiblingsOnly { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [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: &Location) -> Option> { + match l.unpack() { + (0, []) => Some(Vec::::new().encode()), + _ => None, + } + } +} + +/// Creates an AgentId from a Location. An AgentId is a unique mapping to a Agent contract on +/// Ethereum which acts as the sovereign account for the Location. +pub type AgentIdOf = HashedDescription)>; diff --git a/bridges/snowbridge/primitives/core/src/operating_mode.rs b/bridges/snowbridge/primitives/core/src/operating_mode.rs new file mode 100644 index 0000000000000000000000000000000000000000..9894e587ef5e7ff31d19a0b5593c3c78cb7c1f99 --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs new file mode 100644 index 0000000000000000000000000000000000000000..bce123878d3a456fc8b50f841cd64516a5d58dee --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/src/pricing.rs b/bridges/snowbridge/primitives/core/src/pricing.rs new file mode 100644 index 0000000000000000000000000000000000000000..33aeda6d15c4701ce4594b1e783d7aa69f84cc8e --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/src/ringbuffer.rs b/bridges/snowbridge/primitives/core/src/ringbuffer.rs new file mode 100644 index 0000000000000000000000000000000000000000..dcee20359a78ebc27fd5ce5c1479f8241d8583e0 --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/src/tests.rs b/bridges/snowbridge/primitives/core/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..725fff1a9c941ae2e270d591aabd80fc8fa95b54 --- /dev/null +++ b/bridges/snowbridge/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/primitives/core/tests/fixtures/packet.scale b/bridges/snowbridge/primitives/core/tests/fixtures/packet.scale new file mode 100644 index 0000000000000000000000000000000000000000..d5f6696ea69fffd243e7b5b8eb5cef9a7943802c Binary files /dev/null and b/bridges/snowbridge/primitives/core/tests/fixtures/packet.scale differ diff --git a/bridges/snowbridge/primitives/core/tests/mod.rs b/bridges/snowbridge/primitives/core/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c91063a8148092b7abf9c0b75e185e76c429b8fc --- /dev/null +++ b/bridges/snowbridge/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, Location}; + + #[test] + fn allow_siblings_predicate_only_allows_siblings() { + let sibling = Location::new(1, [Parachain(1000)]); + let child = Location::new(0, [Parachain(1000)]); + assert!(AllowSiblingsOnly::contains(&sibling), "Sibling returns true."); + assert!(!AllowSiblingsOnly::contains(&child), "Child returns false."); + } +} diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9fa725a6c0565a5f42847d89149878f8997d07a0 --- /dev/null +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "snowbridge-ethereum" +description = "Snowbridge Ethereum" +version = "0.3.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde-big-array = { optional = true, features = ["const-generics"], workspace = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +ethbloom = { version = "0.13.0", default-features = false } +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 = { package = "ethabi-decode", version = "1.0.0", default-features = false } + +[dev-dependencies] +wasm-bindgen-test = "0.3.19" +rand = "0.8.5" +serde_json = { workspace = true, default-features = true } + +[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/primitives/ethereum/README.md b/bridges/snowbridge/primitives/ethereum/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c295aad9040f9c186a4599a69cb0e50297c86db8 --- /dev/null +++ b/bridges/snowbridge/primitives/ethereum/README.md @@ -0,0 +1,4 @@ +# Ethereum Primitives + +Contains code necessary to decode RLP encoded data (like the Ethereum log), structs for Ethereum execution headers. The +code in this crate relates to the Ethereum execution chain. diff --git a/bridges/snowbridge/primitives/ethereum/src/header.rs b/bridges/snowbridge/primitives/ethereum/src/header.rs new file mode 100644 index 0000000000000000000000000000000000000000..f0b51f8c79de8fa3f1b37205c38d8a8640771f0c --- /dev/null +++ b/bridges/snowbridge/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/primitives/ethereum/src/lib.rs b/bridges/snowbridge/primitives/ethereum/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..1a10ea9abb7723180321e546e9e177e32685d94b --- /dev/null +++ b/bridges/snowbridge/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/primitives/ethereum/src/log.rs b/bridges/snowbridge/primitives/ethereum/src/log.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b8e35bb1133ec105cf8eaf080aadb2f55b7e02b --- /dev/null +++ b/bridges/snowbridge/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/primitives/ethereum/src/mpt.rs b/bridges/snowbridge/primitives/ethereum/src/mpt.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a2dae486dcc05ee5c078e0794ee2d27193eb207 --- /dev/null +++ b/bridges/snowbridge/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/primitives/ethereum/src/receipt.rs b/bridges/snowbridge/primitives/ethereum/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..665a93dbb1e213c0752cf4b64dfea5469a7513bd --- /dev/null +++ b/bridges/snowbridge/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/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ded773e0d38917b7834679b3e521dfbe9539e51b --- /dev/null +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "snowbridge-router-primitives" +description = "Snowbridge Router Primitives" +version = "0.9.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +log = { workspace = true } + +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } + +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } + +snowbridge-core = { path = "../core", default-features = false } + +ethabi = { package = "ethabi-decode", version = "1.0.0", 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/primitives/router/README.md b/bridges/snowbridge/primitives/router/README.md new file mode 100644 index 0000000000000000000000000000000000000000..45967cbf76ca57d9b180eeb3bf2fdb6228288cad --- /dev/null +++ b/bridges/snowbridge/primitives/router/README.md @@ -0,0 +1,4 @@ +# Router Primitives + +Inbound and outbound router logic. Does XCM conversion to a lowered, simpler format the Ethereum contracts can +understand. diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c20554c6d184412f6bff0ec332a775ca37c16a6a --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -0,0 +1,312 @@ +// 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: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location: Location = (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(PalletInstance(inbound_queue_pallet_index).into()), + // 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: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (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, Location::new(0, [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), + Location::new(0, [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), + Location::new(0, [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: Asset = (Location::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(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::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: Location::new(1, [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 location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [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: &Location) -> Option { + match location.unpack() { + (_, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + _ => 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/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..75670b05c1004d65f16e20848c05ca9b28c38452 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/tests.rs @@ -0,0 +1,40 @@ +use super::GlobalConsensusEthereumConvertsFor; +use crate::inbound::CallIndex; +use frame_support::parameter_types; +use hex_literal::hex; +use xcm::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 = Location::new(2, [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 = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); +} diff --git a/bridges/snowbridge/primitives/router/src/lib.rs b/bridges/snowbridge/primitives/router/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d9031c69b22b88a8ae9dfbcb8ed3025a36332a82 --- /dev/null +++ b/bridges/snowbridge/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/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ddc36ce8cb61b370e862de1784a4f6b9fc6f16b0 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -0,0 +1,283 @@ +// 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::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.as_slice() { + [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 = Location::new(1, local_sub.clone()); + 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 Asset + let fee = Asset::from((Location::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.unpack(), + (0, [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 reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .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/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..111243bb45a7ee8e9e06438e1d4eaffb16573d7c --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -0,0 +1,1058 @@ +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; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + 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( + [OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild] + .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_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([GlobalConsensus(Kusama), Parachain(1000)].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_without_para_id_in_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).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::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([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].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::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([GlobalConsensus(Polkadot), Parachain(1000)].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::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([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee, weight_limit: Unlimited }, + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: 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([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = 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([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = 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: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([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 = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = + Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = 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: 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: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId(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: Assets = vec![].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee = Asset { + id: AssetId(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: 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: Assets = vec![ + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), + fun: Fungible(1000), + }, + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: AssetFilter = 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: 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: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(0), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId( + AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId( + [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }].into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: [ + 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: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: 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: Location = Location::new(0, [Parachain(1000)]); + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: Location = Location::new(1, [Parachain(1000)]); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) +} + +#[test] +fn test_describe_here() { + let location: Location = Location::new(0, []); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) +} diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bf5e9a8832dcf48113d5f74a92a060687da2fe4e --- /dev/null +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "snowbridge-runtime-common" +description = "Snowbridge Runtime Common" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +log = { workspace = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } + +snowbridge-core = { path = "../../primitives/core", default-features = false } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "snowbridge-core/std", + "sp-arithmetic/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", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/bridges/snowbridge/runtime/runtime-common/README.md b/bridges/snowbridge/runtime/runtime-common/README.md new file mode 100644 index 0000000000000000000000000000000000000000..57d178ea2d2b03ba2fe0918db32f853c990b569d --- /dev/null +++ b/bridges/snowbridge/runtime/runtime-common/README.md @@ -0,0 +1,3 @@ +# Snowbridge Runtime Common + +Common crate to contain runtime related structs and implementations for Snowbridge. diff --git a/bridges/snowbridge/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..aae45520ff4bd84545cef260dc363c4fd6053ea3 --- /dev/null +++ b/bridges/snowbridge/runtime/runtime-common/src/lib.rs @@ -0,0 +1,151 @@ +// 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)] + +#[cfg(test)] +mod tests; + +use codec::FullCodec; +use core::marker::PhantomData; +use frame_support::traits::Get; +use snowbridge_core::outbound::SendMessageFeeProvider; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_std::fmt::Debug; +use xcm::prelude::*; +use xcm_builder::HandleFee; +use xcm_executor::traits::{FeeReason, TransactAsset}; + +pub const LOG_TARGET: &str = "xcm::export-fee-to-sibling"; + +/// 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 + Debug, + AccountId: Clone + FullCodec, + FeeAssetLocation: Get, + EthereumNetwork: Get, + AssetTransactor: TransactAsset, + FeeProvider: SendMessageFeeProvider, +{ + fn handle_fee(fees: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets { + let token_location = FeeAssetLocation::get(); + + // Check the reason to see if this export is for snowbridge. + if !matches!( + reason, + FeeReason::Export { network: bridged_network, ref destination } + if bridged_network == EthereumNetwork::get() && destination == &Here + ) { + return fees + } + + // Get the parachain sovereign from the `context`. + let maybe_para_id: Option = + if let Some(XcmContext { origin: Some(Location { parents: 1, interior }), .. }) = + context + { + if let Some(Parachain(sibling_para_id)) = interior.first() { + Some(*sibling_para_id) + } else { + None + } + } else { + None + }; + if maybe_para_id.is_none() { + log::error!( + target: LOG_TARGET, + "invalid location in context {:?}", + context, + ); + return fees + } + let para_id = maybe_para_id.unwrap(); + + // 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 Asset { id: location, fun: Fungible(amount) } = asset { + if location.0 == token_location { + return Some((index, (*amount).into())) + } + } + None + }) + .next(); + if maybe_total_supplied_fee.is_none() { + log::error!( + target: LOG_TARGET, + "could not find fee asset item in fees: {:?}", + fees, + ); + return fees + } + let (fee_index, total_fee) = maybe_total_supplied_fee.unwrap(); + let local_fee = FeeProvider::local_fee(); + let remote_fee = total_fee.saturating_sub(local_fee); + if local_fee == Balance::zero() || remote_fee == Balance::zero() { + log::error!( + target: LOG_TARGET, + "calculated refund incorrect with local_fee: {:?} and remote_fee: {:?}", + local_fee, + remote_fee, + ); + return fees + } + // Refund remote component of fee to physical origin + let result = AssetTransactor::deposit_asset( + &Asset { id: AssetId(token_location.clone()), fun: Fungible(remote_fee.into()) }, + &Location::new(1, [Parachain(para_id)]), + context, + ); + if result.is_err() { + log::error!( + target: LOG_TARGET, + "transact fee asset failed: {:?}", + result.unwrap_err() + ); + return fees + } + + // 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(Asset { id: AssetId(token_location), fun: Fungible(local_fee.into()) }); + modified_fees.into() + } +} diff --git a/bridges/snowbridge/runtime/runtime-common/src/tests.rs b/bridges/snowbridge/runtime/runtime-common/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d9a88f42933bd4fbe324aca5d2e30d56508d1df --- /dev/null +++ b/bridges/snowbridge/runtime/runtime-common/src/tests.rs @@ -0,0 +1,168 @@ +use crate::XcmExportFeeToSibling; +use frame_support::{parameter_types, sp_runtime::testing::H256}; +use snowbridge_core::outbound::{Fee, Message, SendError, SendMessage, SendMessageFeeProvider}; +use xcm::prelude::{ + Asset, Assets, Here, Kusama, Location, NetworkId, Parachain, XcmContext, XcmError, XcmHash, + XcmResult, +}; +use xcm_builder::HandleFee; +use xcm_executor::{ + traits::{FeeReason, TransactAsset}, + AssetsInHolding, +}; + +parameter_types! { + pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + pub TokenLocation: Location = Location::parent(); +} + +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 + } +} + +pub struct MockAssetTransactor; +impl TransactAsset for MockAssetTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(Assets::default().into()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(Assets::default().into()) + } +} + +#[test] +fn handle_fee_success() { + let fee: Assets = Asset::from((Location::parent(), 10_u128)).into(); + let ctx = XcmContext { + origin: Some(Location::new(1, Parachain(1000))), + message_id: XcmHash::default(), + topic: None, + }; + let reason = FeeReason::Export { network: EthereumNetwork::get(), destination: Here }; + let result = XcmExportFeeToSibling::< + u128, + u64, + TokenLocation, + EthereumNetwork, + MockAssetTransactor, + MockOkOutboundQueue, + >::handle_fee(fee, Some(&ctx), reason); + let local_fee = Asset::from((Location::parent(), MockOkOutboundQueue::local_fee())).into(); + // assert only local fee left + assert_eq!(result, local_fee) +} + +#[test] +fn handle_fee_success_but_not_for_ethereum() { + let fee: Assets = Asset::from((Location::parent(), 10_u128)).into(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + // invalid network not for ethereum + let reason = FeeReason::Export { network: Kusama, destination: Here }; + let result = XcmExportFeeToSibling::< + u128, + u64, + TokenLocation, + EthereumNetwork, + MockAssetTransactor, + MockOkOutboundQueue, + >::handle_fee(fee.clone(), Some(&ctx), reason); + // assert fee not touched and just forward to the next handler + assert_eq!(result, fee) +} + +#[test] +fn handle_fee_success_even_from_an_invalid_none_origin_location() { + let fee: Assets = Asset::from((Location::parent(), 10_u128)).into(); + // invalid origin None here not from a sibling chain + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let reason = FeeReason::Export { network: EthereumNetwork::get(), destination: Here }; + let result = XcmExportFeeToSibling::< + u128, + u64, + TokenLocation, + EthereumNetwork, + MockAssetTransactor, + MockOkOutboundQueue, + >::handle_fee(fee.clone(), Some(&ctx), reason); + assert_eq!(result, fee) +} + +#[test] +fn handle_fee_success_even_when_fee_insufficient() { + // insufficient fee not cover the (local_fee + remote_fee) required + let fee: Assets = Asset::from((Location::parent(), 1_u128)).into(); + let ctx = XcmContext { + origin: Some(Location::new(1, Parachain(1000))), + message_id: XcmHash::default(), + topic: None, + }; + let reason = FeeReason::Export { network: EthereumNetwork::get(), destination: Here }; + let result = XcmExportFeeToSibling::< + u128, + u64, + TokenLocation, + EthereumNetwork, + MockAssetTransactor, + MockOkOutboundQueue, + >::handle_fee(fee.clone(), Some(&ctx), reason); + assert_eq!(result, fee) +} diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4e8b311cb97812bb94140aa02405b3a174064a8f --- /dev/null +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -0,0 +1,201 @@ +[package] +name = "snowbridge-runtime-test-common" +description = "Snowbridge Runtime Tests" +version = "0.2.0" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex-literal = { version = "0.4.1" } +log = { workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +smallvec = "1.11.0" + +# Substrate +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-executive = { path = "../../../../substrate/frame/executive", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +frame-system-benchmarking = { path = "../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-system-rpc-runtime-api = { path = "../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-aura = { path = "../../../../substrate/frame/aura", default-features = false } +pallet-authorship = { path = "../../../../substrate/frame/authorship", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-multisig = { path = "../../../../substrate/frame/multisig", default-features = false } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } +pallet-transaction-payment = { path = "../../../../substrate/frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } +pallet-utility = { path = "../../../../substrate/frame/utility", default-features = false } +sp-api = { path = "../../../../substrate/primitives/api", default-features = false } +sp-block-builder = { path = "../../../../substrate/primitives/block-builder", default-features = false } +sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-genesis-builder = { path = "../../../../substrate/primitives/genesis-builder", default-features = false } +sp-inherents = { path = "../../../../substrate/primitives/inherents", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +sp-offchain = { path = "../../../../substrate/primitives/offchain", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-session = { path = "../../../../substrate/primitives/session", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-storage = { path = "../../../../substrate/primitives/storage", default-features = false } +sp-transaction-pool = { path = "../../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../../substrate/primitives/version", default-features = false } + +# Polkadot +pallet-xcm = { path = "../../../../polkadot/xcm/pallet-xcm", default-features = false } +pallet-xcm-benchmarks = { path = "../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } +polkadot-core-primitives = { path = "../../../../polkadot/core-primitives", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +polkadot-runtime-common = { path = "../../../../polkadot/runtime/common", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } + +# Cumulus +cumulus-pallet-aura-ext = { path = "../../../../cumulus/pallets/aura-ext", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../cumulus/pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-pallet-session-benchmarking = { path = "../../../../cumulus/pallets/session-benchmarking", default-features = false } +cumulus-pallet-xcm = { path = "../../../../cumulus/pallets/xcm", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../cumulus/pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-core = { path = "../../../../cumulus/primitives/core", default-features = false } +cumulus-primitives-utility = { path = "../../../../cumulus/primitives/utility", default-features = false } +pallet-collator-selection = { path = "../../../../cumulus/pallets/collator-selection", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../../cumulus/parachains/pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../../cumulus/parachains/common", default-features = false } +parachains-runtimes-test-utils = { path = "../../../../cumulus/parachains/runtimes/test-utils", default-features = false } +assets-common = { path = "../../../../cumulus/parachains/runtimes/assets/common", default-features = false } + +# 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-pallet-ethereum-client = { path = "../../pallets/ethereum-client", default-features = false } +snowbridge-pallet-ethereum-client-fixtures = { path = "../../pallets/ethereum-client/fixtures", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../pallets/inbound-queue", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../pallets/outbound-queue", default-features = false } +snowbridge-outbound-queue-runtime-api = { path = "../../pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-pallet-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 = "../../../bin/runtime-common", features = ["integrity-test"] } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } + +[features] +default = ["std"] +std = [ + "assets-common/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-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", + "scale-info/std", + "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-pallet-ethereum-client-fixtures/std", + "snowbridge-pallet-ethereum-client/std", + "snowbridge-pallet-inbound-queue/std", + "snowbridge-pallet-outbound-queue/std", + "snowbridge-pallet-system/std", + "snowbridge-router-primitives/std", + "snowbridge-system-runtime-api/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 = [ + "assets-common/runtime-benchmarks", + "bridge-runtime-common/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-pallet-ethereum-client-fixtures/runtime-benchmarks", + "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue/runtime-benchmarks", + "snowbridge-pallet-outbound-queue/runtime-benchmarks", + "snowbridge-pallet-system/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +fast-runtime = [] diff --git a/bridges/snowbridge/runtime/test-common/README.md b/bridges/snowbridge/runtime/test-common/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d582f87142b3f3a818c0991177b76215aaaf7ef1 --- /dev/null +++ b/bridges/snowbridge/runtime/test-common/README.md @@ -0,0 +1,3 @@ +# Runtime Tests + +Tests runtime config and bridge functionality in the boundaries of a runtime. diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..7455adf76170acefd50f06e8a40ef1c79028f49f --- /dev/null +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +use codec::Encode; +use frame_support::{ + assert_err, assert_ok, + traits::{fungible::Mutate, OnFinalize, OnInitialize}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use parachains_runtimes_test_utils::test_cases::change_storage_constant_by_governance_works; +use parachains_runtimes_test_utils::{ + AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf, XcmReceivedFrom, +}; +use snowbridge_core::{ChannelId, ParaId}; +use snowbridge_pallet_ethereum_client_fixtures::*; +use sp_core::{H160, U256}; +use sp_keyring::AccountKeyring::*; +use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; +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( + ethereum_chain_id: u64, + 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_pallet_outbound_queue::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, +{ + let assethub_parachain_location = Location::new(1, Parachain(assethub_parachain_id)); + let asset = Asset { + id: AssetId(Location::new( + 0, + [AccountKey20 { network: None, key: weth_contract_address.into() }], + )), + fun: Fungible(1000000000), + }; + let assets = vec![asset.clone()]; + + let inner_xcm = Xcm(vec![ + WithdrawAsset(Assets::from(assets.clone())), + ClearOrigin, + BuyExecution { fees: asset, weight_limit: Unlimited }, + DepositAsset { + assets: Wild(All), + beneficiary: Location::new( + 0, + [AccountKey20 { network: None, key: destination_address.into() }], + ), + }, + SetTopic([0; 32]), + ]); + + let fee = + Asset { id: AssetId(Location { parents: 1, interior: Here }), fun: Fungible(fee_amount) }; + + // prepare transfer token message + let xcm = Xcm(vec![ + WithdrawAsset(Assets::from(vec![fee.clone()])), + BuyExecution { fees: fee, weight_limit: Unlimited }, + ExportMessage { + network: Ethereum { chain_id: ethereum_chain_id }, + destination: Here, + xcm: inner_xcm, + }, + ]); + + // execute XCM + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + XcmExecutor::::prepare_and_execute( + assethub_parachain_location, + xcm, + &mut hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), + ) +} + +pub fn send_transfer_token_message_success( + ethereum_chain_id: u64, + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + snowbridge_pallet_outbound_queue: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + pallet_message_queue::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, + ::AccountId: From + AsRef<[u8]>, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_para_id(runtime_para_id.into()) + .with_tracing() + .build() + .execute_with(|| { + >::initialize( + runtime_para_id.into(), + assethub_parachain_id.into(), + ) + .unwrap(); + + // fund asset hub sovereign account enough so it can pay fees + initial_fund::(assethub_parachain_id, 5_000_000_000_000); + + let outcome = send_transfer_token_message::( + ethereum_chain_id, + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + + assert_ok!(outcome.ensure_complete()); + + // check events + let mut events = >::events() + .into_iter() + .filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode())); + assert!(events.any(|e| matches!( + e, + snowbridge_pallet_outbound_queue::Event::MessageQueued { .. } + ))); + + let block_number = >::block_number(); + let next_block_number = >::block_number() + .saturating_add(BlockNumberFor::::from(1u32)); + + // finish current block + >::on_finalize(block_number); + >::on_finalize(block_number); + >::on_finalize(block_number); + + // start next block + >::set_block_number(next_block_number); + >::on_initialize(next_block_number); + >::on_initialize(next_block_number); + >::on_initialize(next_block_number); + + // finish next block + >::on_finalize(next_block_number); + >::on_finalize(next_block_number); + let included_head = >::finalize(); + + let origin: ParaId = assethub_parachain_id.into(); + let channel_id: ChannelId = origin.into(); + + let nonce = snowbridge_pallet_outbound_queue::Nonce::::try_get(channel_id); + assert_ok!(nonce); + assert_eq!(nonce.unwrap(), 1); + + let digest = included_head.digest(); + + let digest_items = digest.logs(); + assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some()); + }); +} + +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, +>( + ethereum_chain_id: u64, + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + snowbridge_pallet_outbound_queue: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + pallet_message_queue::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + ValidatorIdOf: From>, + ::AccountId: From + AsRef<[u8]>, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_para_id(runtime_para_id.into()) + .with_tracing() + .build() + .execute_with(|| { + >::initialize( + runtime_para_id.into(), + assethub_parachain_id.into(), + ) + .unwrap(); + + // fund asset hub sovereign account enough so it can pay fees + initial_fund::(assethub_parachain_id, 5_000_000_000_000); + + let outcome = send_transfer_token_message::( + ethereum_chain_id, + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + + assert_ok!(outcome.ensure_complete()); + + // check events + let mut events = >::events() + .into_iter() + .filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode())); + assert!(events.any(|e| matches!( + e, + snowbridge_pallet_outbound_queue::Event::MessageQueued { .. } + ))); + + let next_block_number: U256 = >::block_number() + .saturating_add(BlockNumberFor::::from(1u32)) + .into(); + + let included_head = + RuntimeHelper::::run_to_block_with_finalize( + next_block_number.as_u32(), + ); + let digest = included_head.digest(); + let digest_items = digest.logs(); + + let mut found_outbound_digest = false; + for digest_item in digest_items { + match digest_item { + DigestItem::Other(_) => found_outbound_digest = true, + _ => {}, + } + } + + assert_eq!(found_outbound_digest, true); + }); +} + +pub fn send_unpaid_transfer_token_message( + ethereum_chain_id: u64, + 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_pallet_outbound_queue::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, +{ + let assethub_parachain_location = Location::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 = Asset { + id: AssetId(Location::new( + 0, + [AccountKey20 { network: None, key: weth_contract_address.into() }], + )), + fun: Fungible(1000000000), + }; + let assets = vec![asset.clone()]; + + let inner_xcm = Xcm(vec![ + WithdrawAsset(Assets::from(assets.clone())), + ClearOrigin, + BuyExecution { fees: asset, weight_limit: Unlimited }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Location::new( + 0, + [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: ethereum_chain_id }, + destination: Here, + xcm: inner_xcm, + }, + ]); + + // execute XCM + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let outcome = XcmExecutor::::prepare_and_execute( + assethub_parachain_location, + xcm, + &mut hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), + ); + // check error is barrier + assert_err!(outcome.ensure_complete(), Barrier); + }); +} + +#[allow(clippy::too_many_arguments)] +pub fn send_transfer_token_message_failure( + ethereum_chain_id: u64, + 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_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::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(|| { + >::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::( + ethereum_chain_id, + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + assert_err!(outcome.ensure_complete(), expected_error); + }); +} + +pub fn ethereum_extrinsic( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + ::RuntimeCall, + ) -> sp_runtime::DispatchOutcome, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + pallet_utility::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, + ValidatorIdOf: From>, + ::RuntimeCall: + From>, + ::RuntimeCall: From>, + AccountIdOf: 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(|| { + let initial_checkpoint = make_checkpoint(); + let update = make_finalized_header_update(); + let sync_committee_update = make_sync_committee_update(); + let execution_header_update = make_execution_header_update(); + + let alice = Alice; + let alice_account = alice.to_account_id(); + >::mint_into( + &alice_account.into(), + 10_000_000_000_000_u128.saturated_into::>(), + ) + .unwrap(); + + assert_ok!(>::force_checkpoint( + RuntimeHelper::::root_origin(), + initial_checkpoint, + )); + + let update_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit { + update: Box::new(*update), + } + .into(); + + let update_sync_committee_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit { + update: Box::new(*sync_committee_update), + } + .into(); + + let execution_header_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit_execution_header { + update: Box::new(*execution_header_update), + } + .into(); + + let update_outcome = construct_and_apply_extrinsic(alice, update_call.into()); + assert_ok!(update_outcome); + + let sync_committee_outcome = + construct_and_apply_extrinsic(alice, update_sync_committee_call.into()); + assert_ok!(sync_committee_outcome); + + let execution_header_outcome = + construct_and_apply_extrinsic(alice, execution_header_call.into()); + assert_ok!(execution_header_outcome); + }); +} + +pub fn ethereum_to_polkadot_message_extrinsics_work( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + ::RuntimeCall, + ) -> sp_runtime::DispatchOutcome, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + pallet_utility::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, + ValidatorIdOf: From>, + ::RuntimeCall: + From>, + ::RuntimeCall: From>, + AccountIdOf: 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(|| { + let initial_checkpoint = make_checkpoint(); + let sync_committee_update = make_sync_committee_update(); + let execution_header_update = make_execution_header_update(); + + let alice = Alice; + let alice_account = alice.to_account_id(); + >::mint_into( + &alice_account.into(), + 10_000_000_000_000_u128.saturated_into::>(), + ) + .unwrap(); + + assert_ok!(>::force_checkpoint( + RuntimeHelper::::root_origin(), + initial_checkpoint, + )); + + let update_sync_committee_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit { + update: Box::new(*sync_committee_update), + } + .into(); + + let execution_header_call: ::RuntimeCall = + snowbridge_pallet_ethereum_client::Call::::submit_execution_header { + update: Box::new(*execution_header_update), + } + .into(); + + let sync_committee_outcome = + construct_and_apply_extrinsic(alice, update_sync_committee_call.into()); + assert_ok!(sync_committee_outcome); + + let execution_header_outcome = + construct_and_apply_extrinsic(alice, execution_header_call.into()); + assert_ok!(execution_header_outcome); + }); +} diff --git a/bridges/snowbridge/scripts/benchmark.sh b/bridges/snowbridge/scripts/benchmark.sh new file mode 100755 index 0000000000000000000000000000000000000000..c9a561b33c48d299ca74ea535436a9b7e5c0394e --- /dev/null +++ b/bridges/snowbridge/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_pallet_ethereum_client \ +--extrinsic="*" \ +--execution=wasm --wasm-execution=compiled \ +--steps 50 --repeat 20 \ +--output ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_client.rs +popd diff --git a/bridges/snowbridge/scripts/contribute-upstream.sh b/bridges/snowbridge/scripts/contribute-upstream.sh new file mode 100755 index 0000000000000000000000000000000000000000..32005b770ecf44cb9af18c61f830243ed5287e68 --- /dev/null +++ b/bridges/snowbridge/scripts/contribute-upstream.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# A script to cleanup the Snowfork fork of the polkadot-sdk to contribute it upstream back to parity/polkadot-sdk +# ./bridges/snowbridge/scripts/contribute-upstream.sh + +# show CLI help +function show_help() { + set +x + echo " " + echo Error: $1 + echo "Usage:" + echo " ./bridges/snowbridge/scripts/contribute-upstream.sh Exit with code 0 if pallets code is well decoupled from the other code in the repo" + exit 1 +} + +if [[ -z "$1" ]]; then + echo "Please provide a branch name you would like your upstream branch to be named" + exit 1 +fi + +branch_name=$1 + +set -eux + +# let's avoid any restrictions on where this script can be called for - snowbridge repo may be +# plugged into any other repo folder. So the script (and other stuff that needs to be removed) +# may be located either in call dir, or one of it subdirs. +SNOWBRIDGE_FOLDER="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../" + +# Get the current Git branch name +current_branch=$(git rev-parse --abbrev-ref HEAD) + +if [ "$current_branch" = "$branch_name" ] || git branch | grep -q "$branch_name"; then + echo "Already on requested branch or branch exists, not creating." +else + git branch "$branch_name" +fi + +git checkout "$branch_name" + +# remove everything we think is not required for our needs +rm -rf rust-toolchain.toml +rm -rf codecov.yml +rm -rf $SNOWBRIDGE_FOLDER/.cargo +rm -rf $SNOWBRIDGE_FOLDER/.github +rm -rf $SNOWBRIDGE_FOLDER/SECURITY.md +rm -rf $SNOWBRIDGE_FOLDER/.gitignore +rm -rf $SNOWBRIDGE_FOLDER/rustfmt.toml +rm -rf $SNOWBRIDGE_FOLDER/templates +rm -rf $SNOWBRIDGE_FOLDER/pallets/ethereum-client/fuzz + +pushd $SNOWBRIDGE_FOLDER + +# let's test if everything we need compiles +cargo check -p snowbridge-pallet-ethereum-client +cargo check -p snowbridge-pallet-ethereum-client --features runtime-benchmarks +cargo check -p snowbridge-pallet-ethereum-client --features try-runtime +cargo check -p snowbridge-pallet-inbound-queue +cargo check -p snowbridge-pallet-inbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-inbound-queue --features try-runtime +cargo check -p snowbridge-pallet-outbound-queue +cargo check -p snowbridge-pallet-outbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-outbound-queue --features try-runtime +cargo check -p snowbridge-pallet-system +cargo check -p snowbridge-pallet-system --features runtime-benchmarks +cargo check -p snowbridge-pallet-system --features try-runtime + +# we're removing lock file after all checks are done. Otherwise we may use different +# Substrate/Polkadot/Cumulus commits and our checks will fail +rm -f $SNOWBRIDGE_FOLDER/Cargo.toml +rm -f $SNOWBRIDGE_FOLDER/Cargo.lock + +popd + +# Replace Parity's CI files, that we have overwritten in our fork, to run our own CI +rm -rf .github +git remote -v | grep -w parity || git remote add parity https://github.com/paritytech/polkadot-sdk +git fetch parity master +git checkout parity/master -- .github +git add -- .github + +echo "OK" diff --git a/bridges/snowbridge/scripts/hexliteral.sh b/bridges/snowbridge/scripts/hexliteral.sh new file mode 100755 index 0000000000000000000000000000000000000000..e34a2b9b5151a667b10354d987d89a187b807b1c --- /dev/null +++ b/bridges/snowbridge/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/scripts/init.sh b/bridges/snowbridge/scripts/init.sh new file mode 100755 index 0000000000000000000000000000000000000000..1405a41ef333e6af863080d83f854d3edb5fb4fa --- /dev/null +++ b/bridges/snowbridge/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/scripts/make-build-config.sh b/bridges/snowbridge/scripts/make-build-config.sh new file mode 100755 index 0000000000000000000000000000000000000000..a1b116a5dd0c18e00213599918d7f2825f9cdf2f --- /dev/null +++ b/bridges/snowbridge/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/testing/README.md b/bridges/testing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bd467a410d013c363913a8e4b2be8ca7b184e2dc --- /dev/null +++ b/bridges/testing/README.md @@ -0,0 +1,31 @@ +# Bridges Tests for Local Rococo <> Westend Bridge + +This folder contains [zombienet](https://github.com/paritytech/zombienet/) based integration tests for both +onchain and offchain bridges code. Due to some +[technical difficulties](https://github.com/paritytech/parity-bridges-common/pull/2649#issue-1965339051), we +are using native zombienet provider, which means that you need to build some binaries locally. + +To start those tests, you need to: + +- download latest [zombienet release](https://github.com/paritytech/zombienet/releases); + +- build Polkadot binary by running `cargo build -p polkadot --release --features fast-runtime` command in the +[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; + +- build Polkadot Parachain binary by running `cargo build -p polkadot-parachain-bin --release` command in the +[`polkadot-sdk`](https://github.com/paritytech/polkadot-sdk) repository clone; + +- ensure that you have [`node`](https://nodejs.org/en) installed. Additionally, we'll need globally installed +`polkadot/api-cli` package (use `npm install -g @polkadot/api-cli@beta` to install it); + +- build Substrate relay by running `cargo build -p substrate-relay --release` command in the +[`parity-bridges-common`](https://github.com/paritytech/parity-bridges-common) repository clone. + +- copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`; + +- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables +have correct values) in the `./run-tests.sh`. + +After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the +"All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet +process logs, which, in turn, may be used to track locations of all spinned relay and parachain nodes. diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml similarity index 68% rename from cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml index d83cf13607daebdb9603924ea7b659cbbb0b67dc..52271f9442131923f8a758b16df7610e73813d15 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml @@ -2,7 +2,7 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] chain = "rococo-local" @@ -36,26 +36,22 @@ cumulus_based = true [[parachains.collators]] name = "bridge-hub-rococo-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring", - "--", "--port 41333", "--rpc-port 48933", "--ws-port 48943" + "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] name = "bridge-hub-rococo-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring", - "--", "--port 41334", "--rpc-port 48934", "--ws-port 48944" + "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] @@ -67,18 +63,16 @@ cumulus_based = true name = "asset-hub-rococo-collator1" rpc_port = 9911 ws_port = 9910 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", - "--", "--port 51333", "--rpc-port 58933", "--ws-port 58943" + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] name = "asset-hub-rococo-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", - "--", "--port 51433", "--rpc-port 58833", "--ws-port 58843" + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] #[[hrmp_channels]] diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml similarity index 55% rename from cumulus/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml index 76b368cfa285a7b9190bfb0c38dee03025151f8d..f2550bcc9959638b21ea78043cca3bc12d3d23ea 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml @@ -2,93 +2,87 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] -chain = "wococo-local" +chain = "westend-local" [[relaychain.nodes]] - name = "alice-wococo-validator" + name = "alice-westend-validator" validator = true rpc_port = 9935 ws_port = 9945 balance = 2000000000000 [[relaychain.nodes]] - name = "bob-wococo-validator" + name = "bob-westend-validator" validator = true rpc_port = 9936 ws_port = 9946 balance = 2000000000000 [[relaychain.nodes]] - name = "charlie-wococo-validator" + name = "charlie-westend-validator" validator = true rpc_port = 9937 ws_port = 9947 balance = 2000000000000 [[parachains]] -id = 1014 -chain = "bridge-hub-wococo-local" +id = 1002 +chain = "bridge-hub-westend-local" cumulus_based = true # run alice as parachain collator [[parachains.collators]] - name = "bridge-hub-wococo-collator1" + name = "bridge-hub-westend-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8935 ws_port = 8945 args = [ - "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring", - "--", "--port 41335", "--rpc-port 48935", "--ws-port 48945" + "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] - name = "bridge-hub-wococo-collator2" + name = "bridge-hub-westend-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8936 ws_port = 8946 args = [ - "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring", - "--", "--port 41336", "--rpc-port 48936", "--ws-port 48946" + "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] id = 1000 -chain = "asset-hub-wococo-local" +chain = "asset-hub-westend-local" cumulus_based = true [[parachains.collators]] - name = "asset-hub-wococo-collator1" + name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9010 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WOCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", - "--", "--port 31333", "--rpc-port 38933", "--ws-port 38943" + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] - name = "asset-hub-wococo-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WOCOCO}}" + name = "asset-hub-westend-collator2" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ - "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace", - "--", "--port 31433", "--rpc-port 38833", "--ws-port 38843" + "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] #[[hrmp_channels]] #sender = 1000 -#recipient = 1014 +#recipient = 1002 #max_capacity = 4 #max_message_size = 524288 # #[[hrmp_channels]] -#sender = 1014 +#sender = 1002 #recipient = 1000 #max_capacity = 4 #max_message_size = 524288 diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh new file mode 100755 index 0000000000000000000000000000000000000000..66c9ddc037b8efb005d2239b174eb5710dddaf53 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -0,0 +1,401 @@ +#!/bin/bash + +# import common functions +source "$FRAMEWORK_PATH/utils/bridges.sh" + +# Expected sovereign accounts. +# +# Generated by: +# +# #[test] +# fn generate_sovereign_accounts() { +# use sp_core::crypto::Ss58Codec; +# use polkadot_parachain_primitives::primitives::Sibling; +# +# parameter_types! { +# pub UniversalLocationAHR: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(1000)); +# pub UniversalLocationAHW: InteriorMultiLocation = X2(GlobalConsensus(Westend), Parachain(1000)); +# } +# +# // SS58=42 +# println!("GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }).unwrap() +# ).to_ss58check_with_version(42_u16.into()) +# ); +# println!("ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# SiblingParachainConvertsVia::::convert_location( +# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(42_u16.into()) +# ); +# +# // SS58=42 +# println!("GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# GlobalConsensusConvertsFor::::convert_location( +# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Westend)) }).unwrap() +# ).to_ss58check_with_version(42_u16.into()) +# ); +# println!("ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# SiblingParachainConvertsVia::::convert_location( +# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() +# ).to_ss58check_with_version(42_u16.into()) +# ); +# } +GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT="5GxRGwT8bU1JeBPTUXc7LEjZMxNrK8MyL2NJnkWFQJTQ4sii" +ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV" +GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT="5He2Qdztyxxa4GoagY6q1jaiLMmKy1gXS7PdZkhfj8ZG9hk5" +ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV" + +# Expected sovereign accounts for rewards on BridgeHubs. +# +# Generated by: +# #[test] +# fn generate_sovereign_accounts_for_rewards() { +# use bp_messages::LaneId; +# use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; +# use sp_core::crypto::Ss58Codec; +# +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( +# LaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( +# LaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( +# LaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( +# LaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# } +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5BhiSGP5hbdsoVGtzi2sQVgpDNToTxLYeQvKoMPEm" +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5BhiSGP5hbdt5EJSapXYbxEv678jyWHEUskCXcjqo" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5BhiSGP5h9Rg8sgUJqoLym3iEaWUiboT8S9AT5xFh" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi" + +LANE_ID="00000002" +XCM_VERSION=3 + +function init_ro_wnd() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path init-bridge rococo-to-bridge-hub-westend \ + --source-host localhost \ + --source-port 9942 \ + --source-version-mode Auto \ + --target-host localhost \ + --target-port 8945 \ + --target-version-mode Auto \ + --target-signer //Bob +} + +function init_wnd_ro() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path init-bridge westend-to-bridge-hub-rococo \ + --source-host localhost \ + --source-port 9945 \ + --source-version-mode Auto \ + --target-host localhost \ + --target-port 8943 \ + --target-version-mode Auto \ + --target-signer //Bob +} + +function run_relay() { + local relayer_path=$(ensure_relayer) + + RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ + $relayer_path relay-headers-and-messages bridge-hub-rococo-bridge-hub-westend \ + --rococo-host localhost \ + --rococo-port 9942 \ + --rococo-version-mode Auto \ + --bridge-hub-rococo-host localhost \ + --bridge-hub-rococo-port 8943 \ + --bridge-hub-rococo-version-mode Auto \ + --bridge-hub-rococo-signer //Charlie \ + --bridge-hub-rococo-transactions-mortality 4 \ + --westend-host localhost \ + --westend-port 9945 \ + --westend-version-mode Auto \ + --bridge-hub-westend-host localhost \ + --bridge-hub-westend-port 8945 \ + --bridge-hub-westend-version-mode Auto \ + --bridge-hub-westend-signer //Charlie \ + --bridge-hub-westend-transactions-mortality 4 \ + --lane "${LANE_ID}" +} + +case "$1" in + run-relay) + init_wnd_ro + init_ro_wnd + run_relay + ;; + init-asset-hub-rococo-local) + ensure_polkadot_js_api + # create foreign assets for native Westend token (governance call on Rococo) + force_create_foreign_asset \ + "ws://127.0.0.1:9942" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9910" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } }')" \ + "$GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT" \ + 10000000000 \ + true + # HRMP + open_hrmp_channels \ + "ws://127.0.0.1:9942" \ + "//Alice" \ + 1000 1013 4 524288 + open_hrmp_channels \ + "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 + # SA of sibling asset hub pays for the execution + transfer_balance \ + "ws://127.0.0.1:8943" \ + "//Alice" \ + "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \ + $((1000000000000 + 50000000000 * 20)) + # drip SA of lane dedicated to asset hub for paying rewards for delivery + transfer_balance \ + "ws://127.0.0.1:8943" \ + "//Alice" \ + "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain" \ + $((1000000000000 + 2000000000000)) + # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation + transfer_balance \ + "ws://127.0.0.1:8943" \ + "//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 + # create foreign assets for native Rococo token (governance call on Westend) + force_create_foreign_asset \ + "ws://127.0.0.1:9945" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9010" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } }')" \ + "$GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT" \ + 10000000000 \ + true + # HRMP + open_hrmp_channels \ + "ws://127.0.0.1:9945" \ + "//Alice" \ + 1000 1002 4 524288 + open_hrmp_channels \ + "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 + transfer_balance \ + "ws://127.0.0.1:8945" \ + "//Alice" \ + "$ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND" \ + $((1000000000000000 + 50000000000 * 20)) + # drip SA of lane dedicated to asset hub for paying rewards for delivery + transfer_balance \ + "ws://127.0.0.1:8945" \ + "//Alice" \ + "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain" \ + $((1000000000000000 + 2000000000000)) + # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation + transfer_balance \ + "ws://127.0.0.1:8945" \ + "//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) + amount=$2 + ensure_polkadot_js_api + # send ROCs to Alice account on AHW + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9910" \ + "//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": '$amount' } } ] }')" \ + 0 \ + "Unlimited" + ;; + withdraw-reserve-assets-from-asset-hub-rococo-local) + amount=$2 + ensure_polkadot_js_api + # send back only 100000000000 wrappedWNDs to Alice account on AHW + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9910" \ + "//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": '$amount' } } ] }')" \ + 0 \ + "Unlimited" + ;; + reserve-transfer-assets-from-asset-hub-westend-local) + amount=$2 + ensure_polkadot_js_api + # send WNDs to Alice account on AHR + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9010" \ + "//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": '$amount' } } ] }')" \ + 0 \ + "Unlimited" + ;; + withdraw-reserve-assets-from-asset-hub-westend-local) + amount=$2 + ensure_polkadot_js_api + # send back only 100000000000 wrappedROCs to Alice account on AHR + limited_reserve_transfer_assets \ + "ws://127.0.0.1:9010" \ + "//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": '$amount' } } ] }')" \ + 0 \ + "Unlimited" + ;; + claim-rewards-bridge-hub-rococo-local) + ensure_polkadot_js_api + # bhwd -> [62, 68, 77, 64] -> 0x62687764 + claim_rewards \ + "ws://127.0.0.1:8943" \ + "//Charlie" \ + "0x${LANE_ID}" \ + "0x62687764" \ + "ThisChain" + claim_rewards \ + "ws://127.0.0.1:8943" \ + "//Charlie" \ + "0x${LANE_ID}" \ + "0x62687764" \ + "BridgedChain" + ;; + claim-rewards-bridge-hub-westend-local) + # bhro -> [62, 68, 72, 6f] -> 0x6268726f + claim_rewards \ + "ws://127.0.0.1:8945" \ + "//Charlie" \ + "0x${LANE_ID}" \ + "0x6268726f" \ + "ThisChain" + claim_rewards \ + "ws://127.0.0.1:8945" \ + "//Charlie" \ + "0x${LANE_ID}" \ + "0x6268726f" \ + "BridgedChain" + ;; + stop) + pkill -f polkadot + pkill -f parachain + ;; + import) + # to avoid trigger anything here + ;; + *) + echo "A command is require. Supported commands for: + Local (zombienet) run: + - run-relay + - init-asset-hub-rococo-local + - init-bridge-hub-rococo-local + - init-asset-hub-westend-local + - init-bridge-hub-westend-local + - reserve-transfer-assets-from-asset-hub-rococo-local + - withdraw-reserve-assets-from-asset-hub-rococo-local + - reserve-transfer-assets-from-asset-hub-westend-local + - withdraw-reserve-assets-from-asset-hub-westend-local + - claim-rewards-bridge-hub-rococo-local + - claim-rewards-bridge-hub-westend-local"; + exit 1 + ;; +esac diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh new file mode 100755 index 0000000000000000000000000000000000000000..0a13ded213f5d3a0920cb466fc974c129e9ad79a --- /dev/null +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +$ENV_PATH/bridges_rococo_westend.sh "$@" diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..c913e4db31f49184eb8214fda4d525c3594b358b --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl @@ -0,0 +1,8 @@ +Description: Check if the HRMP channel between Rococo BH and Rococo AH was opened successfully +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds + + diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..5b49c7c632fa4dd0ce77134858a2f697acbfff16 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo.zndsl @@ -0,0 +1,7 @@ +Description: Check if the with-Westend GRANPDA pallet was initialized at Rococo BH +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds + diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh new file mode 100755 index 0000000000000000000000000000000000000000..cbd0b1bc623ab77876ed5ce3beefd7ab72db2d37 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -e + +trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT + +source "$FRAMEWORK_PATH/utils/zombienet.sh" + +# whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc) +init=0 +start_relayer=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --init) + init=1 + ;; + --start-relayer) + start_relayer=1 + ;; + esac + shift +done + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +rococo_def=${BASH_SOURCE%/*}/bridge_hub_rococo_local_network.toml +start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid +echo + +westend_def=${BASH_SOURCE%/*}/bridge_hub_westend_local_network.toml +start_zombienet $TEST_DIR $westend_def westend_dir westend_pid +echo + +if [[ $init -eq 1 ]]; then + rococo_init_log=$logs_dir/rococo-init.log + echo -e "Setting up the rococo side of the bridge. Logs available at: $rococo_init_log\n" + + westend_init_log=$logs_dir/westend-init.log + echo -e "Setting up the westend side of the bridge. Logs available at: $westend_init_log\n" + + $helper_script init-asset-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-asset-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + + $helper_script init-bridge-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-bridge-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + run_zndsl ${BASH_SOURCE%/*}/rococo-init.zndsl $rococo_dir + run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir +fi + +if [[ $start_relayer -eq 1 ]]; then + ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid +fi + +echo $rococo_dir > $TEST_DIR/rococo.env +echo $westend_dir > $TEST_DIR/westend.env +echo + +wait -n $rococo_pid $westend_pid $relayer_pid +kill -9 -$$ diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh new file mode 100755 index 0000000000000000000000000000000000000000..7ddd312d395aa8733d2afea59277b48721c8a36b --- /dev/null +++ b/bridges/testing/environments/rococo-westend/start_relayer.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +source "$FRAMEWORK_PATH/utils/common.sh" +source "$FRAMEWORK_PATH/utils/zombienet.sh" + +rococo_dir=$1 +westend_dir=$2 +__relayer_pid=$3 + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +relayer_log=$logs_dir/relayer.log +echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n" +start_background_process "$helper_script run-relay" $relayer_log relayer_pid + +run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir + +eval $__relayer_pid="'$relayer_pid'" + diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..0f5428eed3b01c042f8aad3b3df51c3a800a9b72 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl @@ -0,0 +1,7 @@ +Description: Check if the HRMP channel between Westend BH and Westend AH was opened successfully +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds + diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..07968838852f7c0a00131db3080c460c07d08206 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend.zndsl @@ -0,0 +1,6 @@ +Description: Check if the with-Rococo GRANPDA pallet was initialized at Westend BH +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js new file mode 100644 index 0000000000000000000000000000000000000000..af4f18aee9b2710612ed142c50b28caf8313326d --- /dev/null +++ b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js @@ -0,0 +1,25 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later + const bridgedChainName = args[0]; + const expectedBridgedChainHeaderNumber = Number(args[1]); + const runtimeApiMethod = bridgedChainName + "FinalityApi_best_finalized"; + + while (true) { + const encodedBestFinalizedHeaderId = await api.rpc.state.call(runtimeApiMethod, []); + const bestFinalizedHeaderId = api.createType("Option", encodedBestFinalizedHeaderId); + if (bestFinalizedHeaderId.isSome) { + const bestFinalizedHeaderNumber = Number(bestFinalizedHeaderId.unwrap().toHuman()[0]); + if (bestFinalizedHeaderNumber > expectedBridgedChainHeaderNumber) { + return bestFinalizedHeaderNumber; + } + } + + // else sleep and retry + await new Promise((resolve) => setTimeout(resolve, 6000)); + } +} + +module.exports = { run } diff --git a/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js new file mode 100644 index 0000000000000000000000000000000000000000..bcce3b3a303f55a16e766c6558878650ed03ab80 --- /dev/null +++ b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js @@ -0,0 +1,6 @@ +module.exports = { + grandpaPalletName: "bridgeRococoGrandpa", + parachainsPalletName: "bridgeRococoParachains", + messagesPalletName: "bridgeRococoMessages", + bridgedBridgeHubParaId: 1013, +} diff --git a/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js new file mode 100644 index 0000000000000000000000000000000000000000..6a15b64a23b7c28f2b66a6491caebafc4c93dff5 --- /dev/null +++ b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js @@ -0,0 +1,6 @@ +module.exports = { + grandpaPalletName: "bridgeWestendGrandpa", + parachainsPalletName: "bridgeWestendParachains", + messagesPalletName: "bridgeWestendMessages", + bridgedBridgeHubParaId: 1002, +} diff --git a/bridges/testing/framework/js-helpers/native-assets-balance-increased.js b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js new file mode 100644 index 0000000000000000000000000000000000000000..749c3e2fec32ac0af4d244c53cb4ac1c6237817a --- /dev/null +++ b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js @@ -0,0 +1,21 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + const accountAddress = args[0]; + const expectedIncrease = BigInt(args[1]); + const initialAccountData = await api.query.system.account(accountAddress); + const initialAccountBalance = initialAccountData.data['free']; + while (true) { + const accountData = await api.query.system.account(accountAddress); + const accountBalance = accountData.data['free']; + if (accountBalance > initialAccountBalance + expectedIncrease) { + return accountBalance; + } + + // else sleep and retry + await new Promise((resolve) => setTimeout(resolve, 6000)); + } +} + +module.exports = {run} diff --git a/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js new file mode 100644 index 0000000000000000000000000000000000000000..979179245ebe9f5b250efca6f2e6199ef0ac86d7 --- /dev/null +++ b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js @@ -0,0 +1,44 @@ +const utils = require("./utils"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // parse arguments + const exitAfterSeconds = Number(args[0]); + const bridgedChain = require("./chains/" + args[1]); + + // start listening to new blocks + let totalGrandpaHeaders = 0; + let initialParachainHeaderImported = false; + api.rpc.chain.subscribeNewHeads(async function (header) { + const apiAtParent = await api.at(header.parentHash); + const apiAtCurrent = await api.at(header.hash); + const currentEvents = await apiAtCurrent.query.system.events(); + + totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ); + initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ); + }); + + // wait given time + await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000)); + // if we haven't seen any new GRANDPA or parachain headers => fail + if (totalGrandpaHeaders == 0) { + throw new Error("No bridged relay chain headers imported"); + } + if (!initialParachainHeaderImported) { + throw new Error("No bridged parachain headers imported"); + } +} + +module.exports = { run } diff --git a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js new file mode 100644 index 0000000000000000000000000000000000000000..8c3130e4fd960601d377dde5101520c95531cdf6 --- /dev/null +++ b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js @@ -0,0 +1,81 @@ +const utils = require("./utils"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // parse arguments + const exitAfterSeconds = Number(args[0]); + const bridgedChain = require("./chains/" + args[1]); + + // start listening to new blocks + let atLeastOneMessageReceived = false; + let atLeastOneMessageDelivered = false; + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async function (header) { + const apiAtParent = await api.at(header.parentHash); + const apiAtCurrent = await api.at(header.hash); + const currentEvents = await apiAtCurrent.query.system.events(); + + const messagesReceived = currentEvents.find((record) => { + return record.event.section == bridgedChain.messagesPalletName + && record.event.method == "MessagesReceived"; + }) != undefined; + const messagesDelivered = currentEvents.find((record) => { + return record.event.section == bridgedChain.messagesPalletName && + record.event.method == "MessagesDelivered"; + }) != undefined; + const hasMessageUpdates = messagesReceived || messagesDelivered; + atLeastOneMessageReceived = atLeastOneMessageReceived || messagesReceived; + atLeastOneMessageDelivered = atLeastOneMessageDelivered || messagesDelivered; + + if (!hasMessageUpdates) { + // if there are no any message update transactions, we only expect mandatory GRANDPA + // headers and initial parachain headers + await utils.ensureOnlyMandatoryGrandpaHeadersImported( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ); + await utils.ensureOnlyInitialParachainHeaderImported( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ); + } else { + const messageTransactions = (messagesReceived ? 1 : 0) + (messagesDelivered ? 1 : 0); + + // otherwise we only accept at most one GRANDPA header + const newGrandpaHeaders = utils.countGrandpaHeaderImports(bridgedChain, currentEvents); + if (newGrandpaHeaders > 1) { + utils.logEvents(currentEvents); + throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + messageTransactions); + } + + // ...and at most one parachain header + const newParachainHeaders = utils.countParachainHeaderImports(bridgedChain, currentEvents); + if (newParachainHeaders > 1) { + utils.logEvents(currentEvents); + throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + messageTransactions); + } + } + }); + + // wait until we have received + delivered messages OR until timeout + await utils.pollUntil( + exitAfterSeconds, + () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; }, + () => { unsubscribe(); }, + () => { + if (!atLeastOneMessageReceived) { + throw new Error("No messages received from bridged chain"); + } + if (!atLeastOneMessageDelivered) { + throw new Error("No messages delivered to bridged chain"); + } + }, + ); +} + +module.exports = { run } diff --git a/bridges/testing/framework/js-helpers/relayer-rewards.js b/bridges/testing/framework/js-helpers/relayer-rewards.js new file mode 100644 index 0000000000000000000000000000000000000000..5347c649604fc209042725c9cf269c9d3ca0290f --- /dev/null +++ b/bridges/testing/framework/js-helpers/relayer-rewards.js @@ -0,0 +1,28 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later + const relayerAccountAddress = args[0]; + const laneId = args[1]; + const bridgedChainId = args[2]; + const relayerFundOwner = args[3]; + const expectedRelayerReward = BigInt(args[4]); + while (true) { + const relayerReward = await api.query.bridgeRelayers.relayerRewards( + relayerAccountAddress, + { laneId: laneId, bridgedChainId: bridgedChainId, owner: relayerFundOwner } + ); + if (relayerReward.isSome) { + const relayerRewardBalance = relayerReward.unwrap().toBigInt(); + if (relayerRewardBalance > expectedRelayerReward) { + return relayerRewardBalance; + } + } + + // else sleep and retry + await new Promise((resolve) => setTimeout(resolve, 6000)); + } +} + +module.exports = { run } diff --git a/bridges/testing/framework/js-helpers/utils.js b/bridges/testing/framework/js-helpers/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..f6e9f5623b47b3cb3c642245e86654ae9f65358a --- /dev/null +++ b/bridges/testing/framework/js-helpers/utils.js @@ -0,0 +1,103 @@ +module.exports = { + logEvents: function(events) { + let stringifiedEvents = ""; + events.forEach((record) => { + if (stringifiedEvents != "") { + stringifiedEvents += ", "; + } + stringifiedEvents += record.event.section + "::" + record.event.method; + }); + console.log("Block events: " + stringifiedEvents); + }, + countGrandpaHeaderImports: function(bridgedChain, events) { + return events.reduce( + (count, record) => { + const { event } = record; + if (event.section == bridgedChain.grandpaPalletName && event.method == "UpdatedBestFinalizedHeader") { + count += 1; + } + return count; + }, + 0, + ); + }, + countParachainHeaderImports: function(bridgedChain, events) { + return events.reduce( + (count, record) => { + const { event } = record; + if (event.section == bridgedChain.parachainsPalletName && event.method == "UpdatedParachainHead") { + count += 1; + } + return count; + }, + 0, + ); + }, + pollUntil: async function( + timeoutInSecs, + predicate, + cleanup, + onFailure, + ) { + const begin = new Date().getTime(); + const end = begin + timeoutInSecs * 1000; + while (new Date().getTime() < end) { + if (predicate()) { + cleanup(); + return; + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + + cleanup(); + onFailure(); + }, + ensureOnlyMandatoryGrandpaHeadersImported: async function( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ) { + // remember id of bridged relay chain GRANDPA authorities set at parent block + const authoritySetAtParent = await apiAtParent.query[bridgedChain.grandpaPalletName].currentAuthoritySet(); + const authoritySetIdAtParent = authoritySetAtParent["setId"]; + + // now read the id of bridged relay chain GRANDPA authorities set at current block + const authoritySetAtCurrent = await apiAtCurrent.query[bridgedChain.grandpaPalletName].currentAuthoritySet(); + const authoritySetIdAtCurrent = authoritySetAtCurrent["setId"]; + + // we expect to see no more than `authoritySetIdAtCurrent - authoritySetIdAtParent` new GRANDPA headers + const maxNewGrandpaHeaders = authoritySetIdAtCurrent - authoritySetIdAtParent; + const newGrandpaHeaders = module.exports.countGrandpaHeaderImports(bridgedChain, currentEvents); + + // check that our assumptions are correct + if (newGrandpaHeaders > maxNewGrandpaHeaders) { + module.exports.logEvents(currentEvents); + throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + maxNewGrandpaHeaders); + } + + return newGrandpaHeaders; + }, + ensureOnlyInitialParachainHeaderImported: async function( + bridgedChain, + apiAtParent, + apiAtCurrent, + currentEvents, + ) { + // remember whether we already know bridged parachain header at a parent block + const bestBridgedParachainHeader = await apiAtParent.query[bridgedChain.parachainsPalletName].parasInfo(bridgedChain.bridgedBridgeHubParaId);; + const hasBestBridgedParachainHeader = bestBridgedParachainHeader.isSome; + + // we expect to see: no more than `1` bridged parachain header if there were no parachain header before. + const maxNewParachainHeaders = hasBestBridgedParachainHeader ? 0 : 1; + const newParachainHeaders = module.exports.countParachainHeaderImports(bridgedChain, currentEvents); + + // check that our assumptions are correct + if (newParachainHeaders > maxNewParachainHeaders) { + module.exports.logEvents(currentEvents); + throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders); + } + + return hasBestBridgedParachainHeader; + }, +} diff --git a/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js new file mode 100644 index 0000000000000000000000000000000000000000..765d48cc49848ab7a4389f6e0d9b9b3b8cb38f2b --- /dev/null +++ b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js @@ -0,0 +1,22 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + const sibling = args[0]; + + while (true) { + const messagingStateAsObj = await api.query.parachainSystem.relevantMessagingState(); + const messagingState = api.createType("Option", messagingStateAsObj); + if (messagingState.isSome) { + const egressChannels = messagingState.unwrap().egressChannels; + if (egressChannels.find(x => x[0] == sibling)) { + return; + } + } + + // else sleep and retry + await new Promise((resolve) => setTimeout(resolve, 6000)); + } +} + +module.exports = { run } diff --git a/bridges/testing/framework/js-helpers/wrapped-assets-balance.js b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js new file mode 100644 index 0000000000000000000000000000000000000000..27287118547f702b3e94eb635f9e3855d1cab535 --- /dev/null +++ b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js @@ -0,0 +1,26 @@ +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + // TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later + const accountAddress = args[0]; + const expectedForeignAssetBalance = BigInt(args[1]); + const bridgedNetworkName = args[2]; + while (true) { + const foreignAssetAccount = await api.query.foreignAssets.account( + { parents: 2, interior: { X1: { GlobalConsensus: bridgedNetworkName } } }, + accountAddress + ); + if (foreignAssetAccount.isSome) { + const foreignAssetAccountBalance = foreignAssetAccount.unwrap().balance.toBigInt(); + if (foreignAssetAccountBalance > expectedForeignAssetBalance) { + return foreignAssetAccountBalance; + } + } + + // else sleep and retry + await new Promise((resolve) => setTimeout(resolve, 6000)); + } +} + +module.exports = { run } diff --git a/cumulus/scripts/bridges_common.sh b/bridges/testing/framework/utils/bridges.sh similarity index 85% rename from cumulus/scripts/bridges_common.sh rename to bridges/testing/framework/utils/bridges.sh index 8d64c5ede52a25eaa4f0332addf1b39969ac478d..7c8399461584a85e4e8eedf5f347d9d74725f1c9 100755 --- a/cumulus/scripts/bridges_common.sh +++ b/bridges/testing/framework/utils/bridges.sh @@ -1,27 +1,21 @@ #!/bin/bash -function ensure_binaries() { - if [[ ! -f ~/local_bridge_testing/bin/polkadot ]]; then - echo " Required polkadot binary '~/local_bridge_testing/bin/polkadot' does not exist!" - echo " You need to build it and copy to this location!" - echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" - exit 1 - fi - if [[ ! -f ~/local_bridge_testing/bin/polkadot-parachain ]]; then - echo " Required polkadot-parachain binary '~/local_bridge_testing/bin/polkadot-parachain' does not exist!" - echo " You need to build it and copy to this location!" - echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" - exit 1 - fi +function relayer_path() { + local default_path=~/local_bridge_testing/bin/substrate-relay + local path="${SUBSTRATE_RELAY_BINARY:-$default_path}" + echo "$path" } function ensure_relayer() { - if [[ ! -f ~/local_bridge_testing/bin/substrate-relay ]]; then - echo " Required substrate-relay binary '~/local_bridge_testing/bin/substrate-relay' does not exist!" + local path=$(relayer_path) + if [[ ! -f "$path" ]]; then + echo " Required substrate-relay binary '$path' does not exist!" echo " You need to build it and copy to this location!" echo " Please, check ./parachains/runtimes/bridge-hubs/README.md (Prepare/Build/Deploy)" exit 1 fi + + echo $path } function ensure_polkadot_js_api() { @@ -47,13 +41,21 @@ function ensure_polkadot_js_api() { echo "" echo "" echo "-------------------" - echo "Installing (nodejs) sub module: $(dirname "$0")/generate_hex_encoded_call" - pushd $(dirname "$0")/generate_hex_encoded_call + echo "Installing (nodejs) sub module: ${BASH_SOURCE%/*}/generate_hex_encoded_call" + pushd ${BASH_SOURCE%/*}/generate_hex_encoded_call npm install popd fi } +function call_polkadot_js_api() { + # --noWait: without that argument `polkadot-js-api` waits until transaction is included into the block. + # With it, it just submits it to the tx pool and exits. + # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all + # transaction that are in the tx pool. + polkadot-js-api --noWait --nonce -1 "$@" +} + function generate_hex_encoded_call_data() { local type=$1 local endpoint=$2 @@ -63,7 +65,7 @@ function generate_hex_encoded_call_data() { shift echo "Input params: $@" - node $(dirname "$0")/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" + node ${BASH_SOURCE%/*}/../utils/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" local retVal=$? if [ $type != "check" ]; then @@ -86,7 +88,7 @@ function transfer_balance() { echo " amount: ${amount}" echo "--------------------------------------------------" - polkadot-js-api \ + call_polkadot_js_api \ --ws "${runtime_para_endpoint}" \ --seed "${seed?}" \ tx.balances.transferAllowDeath \ @@ -151,7 +153,7 @@ function send_governance_transact() { echo "" echo "--------------------------------------------------" - polkadot-js-api \ + call_polkadot_js_api \ --ws "${relay_url?}" \ --seed "${relay_chain_seed?}" \ --sudo \ @@ -176,7 +178,7 @@ function open_hrmp_channels() { echo " max_message_size: ${max_message_size}" echo " params:" echo "--------------------------------------------------" - polkadot-js-api \ + call_polkadot_js_api \ --ws "${relay_url?}" \ --seed "${relay_chain_seed?}" \ --sudo \ @@ -187,23 +189,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 @@ -258,7 +262,7 @@ function limited_reserve_transfer_assets() { echo "" echo "--------------------------------------------------" - polkadot-js-api \ + call_polkadot_js_api \ --ws "${url?}" \ --seed "${seed?}" \ tx.polkadotXcm.limitedReserveTransferAssets \ @@ -297,7 +301,7 @@ function claim_rewards() { echo "${rewards_account_params}" echo "--------------------------------------------------" - polkadot-js-api \ + call_polkadot_js_api \ --ws "${runtime_para_endpoint}" \ --seed "${seed?}" \ tx.bridgeRelayers.claimRewards \ diff --git a/bridges/testing/framework/utils/common.sh b/bridges/testing/framework/utils/common.sh new file mode 100644 index 0000000000000000000000000000000000000000..06f41320be1353720fccc76b7b76e69ba56a3b94 --- /dev/null +++ b/bridges/testing/framework/utils/common.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +function start_background_process() { + local command=$1 + local log_file=$2 + local __pid=$3 + + $command > $log_file 2>&1 & + eval $__pid="'$!'" +} + +function wait_for_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + local __found=$4 + + local time=0 + until [ -e $file ]; do + if ! kill -0 $pid; then + echo "Process finished unsuccessfully" + return + fi + if (( time++ >= timeout )); then + echo "Timeout waiting for file $file: $timeout seconds" + eval $__found=0 + return + fi + sleep 1 + done + + echo "File $file found after $time seconds" + eval $__found=1 +} + +function ensure_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + + wait_for_process_file $pid $file $timeout file_found + if [ "$file_found" != "1" ]; then + exit 1 + fi +} diff --git a/cumulus/scripts/generate_hex_encoded_call/index.js b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js similarity index 93% rename from cumulus/scripts/generate_hex_encoded_call/index.js rename to bridges/testing/framework/utils/generate_hex_encoded_call/index.js index 09f0e6aaf619a44642bfa4f065f3bf69f0bb7a33..30f89d754ceb7de1b24bc31413e09c862a461256 100644 --- a/cumulus/scripts/generate_hex_encoded_call/index.js +++ b/bridges/testing/framework/utils/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/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..b2dddaa19ed1561b98422b3a28f6777308b3ba47 --- /dev/null +++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json @@ -0,0 +1,759 @@ +{ + "name": "y", + "version": "y", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "y", + "version": "y", + "license": "MIT", + "dependencies": { + "@polkadot/api": "^10.11", + "@polkadot/util": "^12.6" + } + }, + "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": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "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": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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": { + "@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": ">=18" + } + }, + "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": { + "@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": ">=18" + } + }, + "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": { + "@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": ">=18" + } + }, + "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": { + "@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": ">=18" + } + }, + "node_modules/@polkadot/keyring": { + "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": { + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2" + } + }, + "node_modules/@polkadot/networks": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz", + "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==", + "dependencies": { + "@polkadot/util": "12.6.2", + "@substrate/ss58-registry": "^1.44.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@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": ">=18" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.11.2.tgz", + "integrity": "sha512-Ot0CFLWx8sZhLZog20WDuniPA01Bk2StNDsdAQgcFKPwZw6ShPaZQCHuKLQK6I6DodOrem9FXX7c1hvoKJP5Ww==", + "dependencies": { + "@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": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider": { + "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": { + "@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": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.7.35" + } + }, + "node_modules/@polkadot/types": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.11.2.tgz", + "integrity": "sha512-d52j3xXni+C8GdYZVTSfu8ROAnzXFMlyRvXtor0PudUc8UQHOaC4+mYAkTBGA2gKdmL8MHSfRSbhcxHhsikY6Q==", + "dependencies": { + "@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": ">=18" + } + }, + "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": { + "@polkadot/types": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@polkadot/util": "^12.6.2", + "@polkadot/x-bigint": "^12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@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": ">=18" + } + }, + "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": { + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz", + "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==", + "dependencies": { + "@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": ">=18" + } + }, + "node_modules/@polkadot/util-crypto": { + "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": { + "@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": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "12.6.2" + } + }, + "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": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "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": { + "@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": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "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": { + "@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": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "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": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-bigint": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz", + "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==", + "dependencies": { + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-fetch": { + "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": { + "@polkadot/x-global": "12.6.2", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-global": { + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz", + "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==", + "dependencies": { + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "12.6.2", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz", + "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==", + "dependencies": { + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "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": { + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2", + "ws": "^8.15.1" + }, + "engines": { + "node": ">=18" + } + }, + "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/@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": { + "@substrate/connect-extension-protocol": "^1.0.1", + "smoldot": "2.0.7" + } + }, + "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.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": "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": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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/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": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "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": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "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/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/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/nock": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz", + "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.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": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "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/rxjs": { + "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/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": { + "ws": "^8.8.1" + } + }, + "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/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/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": ">= 8" + } + }, + "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": ">=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/bridges/testing/framework/utils/generate_hex_encoded_call/package.json similarity index 74% rename from cumulus/scripts/generate_hex_encoded_call/package.json rename to bridges/testing/framework/utils/generate_hex_encoded_call/package.json index 1c68924db24400bc540d9b32f16cfe5573277834..ecf0a2483db100e688e06da4233f036c2383b3a6 100644 --- a/cumulus/scripts/generate_hex_encoded_call/package.json +++ b/bridges/testing/framework/utils/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/bridges/testing/framework/utils/zombienet.sh b/bridges/testing/framework/utils/zombienet.sh new file mode 100644 index 0000000000000000000000000000000000000000..bbcd1a30620252d8740473c3924e0988e5bff4d6 --- /dev/null +++ b/bridges/testing/framework/utils/zombienet.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +source "${BASH_SOURCE%/*}/common.sh" + +function start_zombienet() { + local test_dir=$1 + local definition_path=$2 + local __zombienet_dir=$3 + local __zombienet_pid=$4 + + local zombienet_name=`basename $definition_path .toml` + local zombienet_dir=$test_dir/$zombienet_name + eval $__zombienet_dir="'$zombienet_dir'" + mkdir -p $zombienet_dir + rm -rf $zombienet_dir + + local logs_dir=$test_dir/logs + mkdir -p $logs_dir + local zombienet_log=$logs_dir/$zombienet_name.log + + echo "Starting $zombienet_name zombienet. Logs available at: $zombienet_log" + start_background_process \ + "$ZOMBIENET_BINARY spawn --dir $zombienet_dir --provider native $definition_path" \ + "$zombienet_log" zombienet_pid + + ensure_process_file $zombienet_pid "$zombienet_dir/zombie.json" 180 + echo "$zombienet_name zombienet started successfully" + + eval $__zombienet_pid="'$zombienet_pid'" +} + +function run_zndsl() { + local zndsl_file=$1 + local zombienet_dir=$2 + + echo "Running $zndsl_file." + $ZOMBIENET_BINARY test --dir $zombienet_dir --provider native $zndsl_file $zombienet_dir/zombie.json + echo +} diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-new-test.sh new file mode 100755 index 0000000000000000000000000000000000000000..7c84a69aa47de84439091cb7b908233d02238175 --- /dev/null +++ b/bridges/testing/run-new-test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +trap 'kill -9 -$$ || echo "Environment already teared down"' SIGINT SIGTERM EXIT + +test=$1 +shift + +# whether to use paths for zombienet+bridges tests container or for local testing +ZOMBIENET_DOCKER_PATHS=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --docker) + ZOMBIENET_DOCKER_PATHS=1 + ;; + esac + shift +done + +export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..` +export FRAMEWORK_PATH=`realpath ${BASH_SOURCE%/*}/framework` + +# set path to binaries +if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then + # otherwise zombienet uses some hardcoded paths + unset RUN_IN_CONTAINER + unset ZOMBIENET_IMAGE + + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain + + export ZOMBIENET_BINARY=/usr/local/bin/zombie + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay +else + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain + + export ZOMBIENET_BINARY=~/local_bridge_testing/bin/zombienet-linux-x64 + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay +fi + +export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX` +echo -e "Test folder: $TEST_DIR\n" + +${BASH_SOURCE%/*}/tests/$test/run.sh diff --git a/bridges/testing/run-tests.sh b/bridges/testing/run-tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..6149d9912653c79968a0229759c8f1bf46f68a9f --- /dev/null +++ b/bridges/testing/run-tests.sh @@ -0,0 +1,138 @@ +#!/bin/bash +set -x +shopt -s nullglob + +trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT + +# run tests in range [TESTS_BEGIN; TESTS_END) +TESTS_BEGIN=1 +TESTS_END=1000 +# whether to use paths for zombienet+bridges tests container or for local testing +ZOMBIENET_DOCKER_PATHS=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --docker) + ZOMBIENET_DOCKER_PATHS=1 + ;; + --test) + shift + TESTS_BEGIN="$1" + TESTS_END="$1" + ;; + esac + shift +done + +# assuming that we'll be using native provide && all processes will be executing locally +# (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders) +export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..` +export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests + +# set pathc to binaries +if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain + + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay + export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie +else + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain + + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay + export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux +fi + +# check if `wait` supports -p flag +if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi + +# bridge configuration +export LANE_ID="00000002" + +# tests configuration +ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX` + +function start_coproc() { + local command=$1 + local name=$2 + local logname=`basename $name` + local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX` + coproc COPROC { + # otherwise zombienet uses some hardcoded paths + unset RUN_IN_CONTAINER + unset ZOMBIENET_IMAGE + + $command >$coproc_log 2>&1 + } + TEST_COPROCS[$COPROC_PID, 0]=$name + TEST_COPROCS[$COPROC_PID, 1]=$coproc_log + echo "Spawned $name coprocess. StdOut + StdErr: $coproc_log" + + return $COPROC_PID +} + +# execute every test from tests folder +TEST_INDEX=$TESTS_BEGIN +while true +do + declare -A TEST_COPROCS + TEST_COPROCS_COUNT=0 + TEST_PREFIX=$(printf "%04d" $TEST_INDEX) + + # it'll be used by the `sync-exit.sh` script + export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX` + + # check if there are no more tests + zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl) + if [ ${#zndsl_files[@]} -eq 0 ]; then + break + fi + + # start tests + for zndsl_file in "${zndsl_files[@]}"; do + start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file" + echo -n "1">>$TEST_FOLDER/exit-sync + ((TEST_COPROCS_COUNT++)) + done + # wait until all tests are completed + for n in `seq 1 $TEST_COPROCS_COUNT`; do + if [ "$IS_BASH_5_1" -eq 1 ]; then + wait -n -p COPROC_PID + exit_code=$? + coproc_name=${TEST_COPROCS[$COPROC_PID, 0]} + coproc_log=${TEST_COPROCS[$COPROC_PID, 1]} + coproc_stdout=$(cat $coproc_log) + else + wait -n + exit_code=$? + coproc_name="" + coproc_stdout="" + fi + echo "Process $coproc_name has finished with exit code: $exit_code" + + # if exit code is not zero, exit + if [ $exit_code -ne 0 ]; then + echo "=====================================================================" + echo "=== Shutting down. Log of failed process below ===" + echo "=====================================================================" + echo "$coproc_stdout" + + exit 1 + fi + done + + # proceed to next index + ((TEST_INDEX++)) + if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then + break + fi + + # kill relay here - it is started manually by tests + killall substrate-relay +done + +echo "=====================================================================" +echo "=== All tests have completed successfully ===" +echo "=====================================================================" diff --git a/bridges/testing/scripts/invoke-script.sh b/bridges/testing/scripts/invoke-script.sh new file mode 100755 index 0000000000000000000000000000000000000000..cd0557b071bbadc41e056a2e50c9f1aa0b677312 --- /dev/null +++ b/bridges/testing/scripts/invoke-script.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX` + +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend +./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1 +popd diff --git a/bridges/testing/scripts/start-relayer.sh b/bridges/testing/scripts/start-relayer.sh new file mode 100755 index 0000000000000000000000000000000000000000..38ea62fad524486c40cf88943c48a2e4df4b86e8 --- /dev/null +++ b/bridges/testing/scripts/start-relayer.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX` + +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend +./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1& +popd diff --git a/bridges/testing/scripts/sync-exit.sh b/bridges/testing/scripts/sync-exit.sh new file mode 100755 index 0000000000000000000000000000000000000000..cc20b098e7830fc164f3a0a643840c1e8188b7f2 --- /dev/null +++ b/bridges/testing/scripts/sync-exit.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +# every network adds a char to the file, let's remove ours +truncate -s -1 $TEST_FOLDER/exit-sync + +# when all chars are removed, then our test is done +while true +do + if [ `stat --printf="%s" $TEST_FOLDER/exit-sync` -eq 0 ]; then + exit + fi + sleep 100 +done diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..cdb7d28e940cf1ac90562e761cdbad00e95e1748 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send 5 ROC to //Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000" within 120 seconds + +# check that //Alice received at least 4.8 ROC on Westend AH +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Rococo" within 600 seconds + +# check that the relayer //Charlie is rewarded by Westend AH +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..a7bb122919b40187c49e89c489d2271d646bff40 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh --init --start-relayer & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 300 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir + +run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..dbc03864e2b6e5e10636532ad965860b381fa8f2 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# send 5 WND to //Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local 5000000000000" within 120 seconds + +# check that //Alice received at least 4.8 WND on Rococo AH +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,4800000000000,Westend" within 600 seconds + +# check that the relayer //Charlie is rewarded by Rococo AH +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..9967732cabe15e020060eb0a53fd8ce1440180e5 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send 3 wROC back to Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local 3000000000000" within 120 seconds + +# check that //Alice received at least 2.8 wROC on Rococo AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..2037b0baf3c0aac2a0e1a8f297c6baa155ac680f --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send 3 wWND back to Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local 3000000000000" within 120 seconds + +# check that //Alice received at least 2.8 wWND on Westend AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,2800000000000" within 600 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..6e381f5377329430c0d7a8723f9ea9081556bfeb --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl @@ -0,0 +1,8 @@ +Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds + diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..7d5b8d9273664b0861e8ffe1c528e9e1718c4df4 --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 300 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +# Sleep for some time before starting the relayer. We want to sleep for at least 1 session, +# which is expected to be 60 seconds for the test environment. +echo -e "Sleeping 90s before starting relayer ...\n" +sleep 90 +${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid + +# Sometimes the relayer syncs multiple parachain heads in the begining leading to test failures. +# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838. +# TODO: Remove this sleep after the issue is fixed. +echo -e "Sleeping 180s before runing the tests ...\n" +sleep 180 + +run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir + diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..b4b3e43679162feb8c3c5253f3f963d950f31d55 --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl @@ -0,0 +1,7 @@ +Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..07b91481dc7cf995b913a9bf84edd3728982eaae --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl @@ -0,0 +1,26 @@ +Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. +Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml +Creds: config + +# step 1: initialize Westend AH +asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds + +# step 2: initialize Westend bridge hub +bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds + +# step 3: ensure that initialization has completed +asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds + +# step 4: send message from Westend to Rococo +asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds + +# step 5: start relayer +# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script +# will be started at step 6) +# (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl) + +# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations +bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds + +# wait until other network test has completed OR exit with an error too +asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..a6b11fc24052aadf562bc34704aeda9ee115eccf --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl @@ -0,0 +1,26 @@ +Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH. +Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml +Creds: config + +# step 1: initialize Rococo AH +asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds + +# step 2: initialize Rococo bridge hub +bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds + +# step 3: ensure that initialization has completed +asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds + +# step 4: send message from Rococo to Westend +asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds + +# step 5: start relayer +# (we are starting it after sending the message to be sure that relayer won't relay messages before our js script +# will be started at step 6) +bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds + +# step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations +bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds + +# wait until other network test has completed OR exit with an error too +asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/cumulus/README.md b/cumulus/README.md index 19f9f3f113dd06655c418a4caf6b30a9cad6ee9f..7e145ad7b4abaf1aea3ecaf18236d231d2599b5d 100644 --- a/cumulus/README.md +++ b/cumulus/README.md @@ -4,7 +4,7 @@ This repository contains both the Cumulus SDK and also specific chains implemented on top of this SDK. -If you only want to run a **Polkadot Parachain Node**, check out our [container section](./docs/container.md). +If you only want to run a **Polkadot Parachain Node**, check out our [container section](./docs/contributor/container.md). ## Cumulus SDK @@ -34,7 +34,7 @@ A Polkadot [collator](https://wiki.polkadot.network/docs/en/learn-collator) for `polkadot-parachain` binary (previously called `polkadot-collator`). You may run `polkadot-parachain` locally after building it or using one of the container option described -[here](./docs/container.md). +[here](./docs/contributor/container.md). ### Relay Chain Interaction To operate a parachain node, a connection to the corresponding relay chain is necessary. This can be achieved in one of @@ -142,8 +142,8 @@ zombienet --provider native spawn ./zombienet/examples/small_network.toml # Clone git clone https://github.com/paritytech/polkadot-sdk -# Compile Polkadot -cargo build --release --bin polkadot +# Compile Polkadot's required binaries +cargo build --release -p polkadot # Generate a raw chain spec ./target/release/polkadot build-spec --chain rococo-local --disable-default-bootnode --raw > rococo-local-cfde.json @@ -158,11 +158,8 @@ cargo build --release --bin polkadot #### Launch the Parachain ```bash -# Clone -git clone https://github.com/paritytech/polkadot-sdk - # Compile -cargo build --release --bin polkadot-parachain +cargo build --release -p polkadot-parachain-bin # Export genesis state ./target/release/polkadot-parachain export-genesis-state > genesis-state @@ -172,15 +169,15 @@ cargo build --release --bin polkadot-parachain # Collator1 ./target/release/polkadot-parachain --collator --alice --force-authoring \ - --tmp --port 40335 --rpc-port 9946 -- --chain ../polkadot/rococo-local-cfde.json --port 30335 + --tmp --port 40335 --rpc-port 9946 -- --chain rococo-local-cfde.json --port 30335 # Collator2 ./target/release/polkadot-parachain --collator --bob --force-authoring \ - --tmp --port 40336 --rpc-port 9947 -- --chain ../polkadot/rococo-local-cfde.json --port 30336 + --tmp --port 40336 --rpc-port 9947 -- --chain rococo-local-cfde.json --port 30336 # Parachain Full Node 1 ./target/release/polkadot-parachain --tmp --port 40337 --rpc-port 9948 -- \ - --chain ../polkadot/rococo-local-cfde.json --port 30337 + --chain rococo-local-cfde.json --port 30337 ``` #### Register the parachain @@ -245,7 +242,7 @@ Once the executable is built, launch collators for each parachain (repeat once e ./target/release/polkadot-parachain --chain $CHAIN --validator ``` -You can also build [using a container](./docs/container.md). +You can also build [using a container](./docs/contributor/container.md). ### Parachains diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 5dd18f0c156d1581c09d73e2ab9d6422dbe9ab4c..0b2edb593c405bef266fa7e389a996fe3a2fcec1 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -1,11 +1,16 @@ [package] name = "cumulus-client-cli" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } url = "2.4.0" @@ -16,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 1b18ed064373fdd58224b70da022a969ad641eb6..a7b2eb19de88a5c585ec3f6dfe5ad46ef0399b88 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}, + config::{PrometheusConfig, RpcBatchRequestConfig, 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,30 @@ impl sc_cli::CliConfiguration for PurgeChainCmd { } } -/// Command for exporting the genesis state of the parachain +/// Get the SCALE encoded genesis header of the parachain. +pub fn get_raw_genesis_header(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(), + ), + ))?; + + Ok(genesis_header.encode()) +} + +/// 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 +164,18 @@ 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 raw_header = get_raw_genesis_header(client)?; let output_buf = if self.raw { raw_header } else { - format!("0x{:?}", HexDisplay::from(&block.header().encode())).into_bytes() + format!("0x{:?}", HexDisplay::from(&raw_header)).into_bytes() }; if let Some(output) = &self.output { @@ -175,46 +188,17 @@ 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 } + + fn base_path(&self) -> sc_cli::Result> { + // As we are just exporting the genesis wasm a tmp database is enough. + // + // As otherwise we may "pollute" the global base path. + Ok(Some(BasePath::new_temp_dir()?)) + } } /// Command for exporting the genesis wasm file. @@ -266,6 +250,13 @@ impl sc_cli::CliConfiguration for ExportGenesisWasmCommand { fn shared_params(&self) -> &sc_cli::SharedParams { &self.shared_params } + + fn base_path(&self) -> sc_cli::Result> { + // As we are just exporting the genesis wasm a tmp database is enough. + // + // As otherwise we may "pollute" the global base path. + Ok(Some(BasePath::new_temp_dir()?)) + } } fn validate_relay_chain_url(arg: &str) -> Result { @@ -296,7 +287,14 @@ pub struct RunCmd { #[arg(long, conflicts_with = "validator")] pub collator: bool, - /// EXPERIMENTAL: Specify an URL to a relay chain full node to communicate with. + /// Creates a less resource-hungry node that retrieves relay chain data from an RPC endpoint. + /// + /// The provided URLs should point to RPC endpoints of the relay chain. + /// This node connects to the remote nodes following the order they were specified in. If the + /// connection fails, it attempts to connect to the next endpoint in the list. + /// + /// Note: This option doesn't stop the node from connecting to the relay chain network but + /// reduces bandwidth use. #[arg( long, value_parser = validate_relay_chain_url, @@ -445,6 +443,14 @@ impl sc_cli::CliConfiguration for NormalizedRunCmd { Ok(self.base.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result { + Ok(self.base.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> sc_cli::Result { + self.base.rpc_batch_config() + } + fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result { self.base.transaction_pool(is_dev) } diff --git a/cumulus/client/collator/Cargo.toml b/cumulus/client/collator/Cargo.toml index 1d87efa443ce38f6b82a78d56c0fd090294e63d0..0e911b9f3abfec1a894b50d63620f914268b78a9 100644 --- a/cumulus/client/collator/Cargo.toml +++ b/cumulus/client/collator/Cargo.toml @@ -1,12 +1,17 @@ [package] name = "cumulus-client-collator" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" tracing = "0.1.25" @@ -29,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/collator/src/lib.rs b/cumulus/client/collator/src/lib.rs index f17ae488310608d461238a134597cd875311ba6b..83249186f626ff8fffbee108cd953c3cc041e467 100644 --- a/cumulus/client/collator/src/lib.rs +++ b/cumulus/client/collator/src/lib.rs @@ -242,17 +242,19 @@ pub async fn initialize_collator_subsystems( overseer_handle: &mut OverseerHandle, key: CollatorPair, para_id: ParaId, + reinitialize: bool, ) { - overseer_handle - .send_msg( - CollationGenerationMessage::Initialize(CollationGenerationConfig { - key, - para_id, - collator: None, - }), - "StartCollator", - ) - .await; + let config = CollationGenerationConfig { key, para_id, collator: None }; + + if reinitialize { + overseer_handle + .send_msg(CollationGenerationMessage::Reinitialize(config), "StartCollator") + .await; + } else { + overseer_handle + .send_msg(CollationGenerationMessage::Initialize(config), "StartCollator") + .await; + } overseer_handle .send_msg(CollatorProtocolMessage::CollateOn(para_id), "StartCollator") diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 8239a498746e3cfd8bda6190b125c95a17ebe735..e815e89d8ce3bebcdcf52b363917ba17c0382708 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "cumulus-client-consensus-aura" description = "AURA consensus algorithm for parachains" -version = "0.1.0" +version = "0.7.0" 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" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } +async-trait = "0.1.74" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.28" tracing = "0.1.37" schnellru = "0.2.1" @@ -37,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..5b7669c88f473b8765b6b343d1797aa707ed5916 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) @@ -251,6 +258,7 @@ where pub struct SlotClaim { author_pub: Pub, pre_digest: DigestItem, + slot: Slot, timestamp: Timestamp, } @@ -265,7 +273,7 @@ impl SlotClaim { P::Public: Codec, P::Signature: Codec, { - SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::

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

(slot), slot } } /// Get the author's public key. @@ -278,6 +286,11 @@ impl SlotClaim { &self.pre_digest } + /// Get the slot assigned to this claim. + pub fn slot(&self) -> Slot { + self.slot + } + /// Get the timestamp corresponding to the relay-chain slot this claim was /// generated against. pub fn timestamp(&self) -> Timestamp { diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index dc0078b0d6a9838534b5599c8838b261c470b7b3..52b83254951f0e0ba0fd9ad5420d7faca2402066 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -33,12 +33,12 @@ use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::CollationResult; use polkadot_overseer::Handle as OverseerHandle; -use polkadot_primitives::{CollatorPair, Id as ParaId}; +use polkadot_primitives::{CollatorPair, Id as ParaId, ValidationCode}; use futures::{channel::mpsc::Receiver, prelude::*}; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf}; use sc_consensus::BlockImport; -use sp_api::ProvideRuntimeApi; +use sp_api::{CallApiAt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; @@ -47,6 +47,7 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; +use sp_state_machine::Backend as _; use std::{convert::TryFrom, sync::Arc, time::Duration}; use crate::collator as collator_util; @@ -100,6 +101,7 @@ where + AuxStore + HeaderBackend + BlockBackend + + CallApiAt + Send + Sync + 'static, @@ -141,6 +143,8 @@ where collator_util::Collator::::new(params) }; + let mut last_processed_slot = 0; + while let Some(request) = collation_requests.next().await { macro_rules! reject_with_error { ($err:expr) => {{ @@ -170,6 +174,22 @@ where continue } + let Ok(Some(code)) = + params.para_client.state_at(parent_hash).map_err(drop).and_then(|s| { + s.storage(&sp_core::storage::well_known_keys::CODE).map_err(drop) + }) + else { + continue; + }; + + super::check_validation_code_or_log( + &ValidationCode::from(code).hash(), + params.para_id, + ¶ms.relay_client, + *request.relay_parent(), + ) + .await; + let relay_parent_header = match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await { Err(e) => reject_with_error!(e), @@ -192,6 +212,18 @@ where Err(e) => reject_with_error!(e), }; + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to + // produce multiple blocks per slot which very likely would fail on chain. Thus, we have + // this "hack" to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if last_processed_slot >= *claim.slot() { + continue + } + let (parachain_inherent_data, other_inherent_data) = try_request!( collator .create_inherent_data( @@ -203,7 +235,7 @@ where .await ); - let (collation, _, post_hash) = try_request!( + let maybe_collation = try_request!( collator .collate( &parent_header, @@ -220,8 +252,16 @@ 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"); + } + + last_processed_slot = *claim.slot(); } } } diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 57cd646fbcdef58fca265454eeeb677c35beee7b..161f10d55a193de35a2585e1a1f5725f30e19bf7 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -59,7 +59,7 @@ use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; -use sp_consensus_aura::{AuraApi, Slot, SlotDuration}; +use sp_consensus_aura::{AuraApi, Slot}; use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; @@ -95,8 +95,6 @@ pub struct Params { pub para_id: ParaId, /// A handle to the relay-chain client's "Overseer" or task orchestrator. pub overseer_handle: OverseerHandle, - /// The length of slots in this chain. - pub slot_duration: SlotDuration, /// The length of slots in the relay chain. pub relay_chain_slot_duration: Duration, /// The underlying block proposer this should call into. @@ -105,6 +103,8 @@ pub struct Params { pub collator_service: CS, /// The amount of time to spend authoring each block. pub authoring_duration: Duration, + /// Whether we should reinitialize the collator config (i.e. we are transitioning to aura). + pub reinitialize: bool, } /// Run async-backing-friendly Aura. @@ -149,6 +149,7 @@ where &mut params.overseer_handle, params.collator_key, params.para_id, + params.reinitialize, ) .await; @@ -211,26 +212,6 @@ where }, }; - let (slot_now, timestamp) = match consensus_common::relay_slot_and_timestamp( - &relay_parent_header, - params.relay_chain_slot_duration, - ) { - None => continue, - Some((r_s, t)) => { - let our_slot = Slot::from_timestamp(t, params.slot_duration); - tracing::debug!( - target: crate::LOG_TARGET, - relay_slot = ?r_s, - para_slot = ?our_slot, - timestamp = ?t, - slot_duration = ?params.slot_duration, - relay_chain_slot_duration = ?params.relay_chain_slot_duration, - "Adjusted relay-chain slot to parachain slot" - ); - (our_slot, t) - }, - }; - let parent_search_params = ParentSearchParams { relay_parent, para_id: params.para_id, @@ -269,14 +250,39 @@ where let para_client = &*params.para_client; let keystore = ¶ms.keystore; let can_build_upon = |block_hash| { - can_build_upon::<_, _, P>( + let slot_duration = match sc_consensus_aura::standalone::slot_duration_at( + &*params.para_client, + block_hash, + ) { + Ok(sd) => sd, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration"); + return None + }, + }; + tracing::debug!(target: crate::LOG_TARGET, ?slot_duration, ?block_hash, "Parachain slot duration acquired"); + let (relay_slot, timestamp) = consensus_common::relay_slot_and_timestamp( + &relay_parent_header, + params.relay_chain_slot_duration, + )?; + let slot_now = Slot::from_timestamp(timestamp, slot_duration); + tracing::debug!( + target: crate::LOG_TARGET, + ?relay_slot, + para_slot = ?slot_now, + ?timestamp, + ?slot_duration, + relay_chain_slot_duration = ?params.relay_chain_slot_duration, + "Adjusted relay-chain slot to parachain slot" + ); + Some(can_build_upon::<_, _, P>( slot_now, timestamp, block_hash, included_block, para_client, &keystore, - ) + )) }; // Sort by depth, ascending, to choose the longest chain. @@ -284,10 +290,7 @@ where // If the longest chain has space, build upon that. Otherwise, don't // build at all. potential_parents.sort_by_key(|a| a.depth); - let initial_parent = match potential_parents.pop() { - None => continue, - Some(p) => p, - }; + let Some(initial_parent) = potential_parents.pop() else { continue }; // Build in a loop until not allowed. Note that the authorities can change // at any block, so we need to re-claim our slot every time. @@ -295,12 +298,19 @@ where let mut parent_header = initial_parent.header; let overseer_handle = &mut params.overseer_handle; + // We mainly call this to inform users at genesis if there is a mismatch with the + // on-chain data. + collator.collator_service().check_block_status(parent_hash, &parent_header); + // This needs to change to support elastic scaling, but for continuously // scheduled chains this ensures that the backlog will grow steadily. for n_built in 0..2 { - let slot_claim = match can_build_upon(parent_hash).await { + let slot_claim = match can_build_upon(parent_hash) { + Some(fut) => match fut.await { + None => break, + Some(c) => c, + }, None => break, - Some(c) => c, }; tracing::debug!( @@ -344,6 +354,14 @@ where Some(v) => v, }; + super::check_validation_code_or_log( + &validation_code_hash, + params.para_id, + ¶ms.relay_client, + relay_parent, + ) + .await; + match collator .collate( &parent_header, @@ -359,7 +377,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 +405,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/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 4c7b759daf736f69de48b586b082a7d01534d7e3..6e0067d0cedb602face8943737f99f3cb1a201a3 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -20,5 +20,60 @@ //! included parachain block, as well as the [`lookahead`] collator, which prospectively //! builds on parachain blocks which have not yet been included in the relay chain. +use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_primitives::{ + Hash as RHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash, +}; + pub mod basic; pub mod lookahead; + +/// Check the `local_validation_code_hash` against the validation code hash in the relay chain +/// state. +/// +/// If the code hashes do not match, it prints a warning. +async fn check_validation_code_or_log( + local_validation_code_hash: &ValidationCodeHash, + para_id: ParaId, + relay_client: &impl RelayChainInterface, + relay_parent: RHash, +) { + let state_validation_code_hash = match relay_client + .validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included) + .await + { + Ok(hash) => hash, + Err(error) => { + tracing::debug!( + target: super::LOG_TARGET, + %error, + ?relay_parent, + %para_id, + "Failed to fetch validation code hash", + ); + return + }, + }; + + match state_validation_code_hash { + Some(state) => + if state != *local_validation_code_hash { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + ?local_validation_code_hash, + relay_validation_code_hash = ?state, + "Parachain code doesn't match validation code stored in the relay chain state", + ); + }, + None => { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + "Could not find validation code for parachain in the relay chain state.", + ); + }, + } +} diff --git a/cumulus/client/consensus/aura/src/lib.rs b/cumulus/client/consensus/aura/src/lib.rs index 6ededa7a92c11cb8c313f7da01017eeef256fb06..ed6f5bdd4d6984350c5f59a3753618c3a038f323 100644 --- a/cumulus/client/consensus/aura/src/lib.rs +++ b/cumulus/client/consensus/aura/src/lib.rs @@ -42,12 +42,22 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor}; -use std::{convert::TryFrom, marker::PhantomData, sync::Arc}; +use std::{ + convert::TryFrom, + marker::PhantomData, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; mod import_queue; pub use import_queue::{build_verifier, import_queue, BuildVerifierParams, ImportQueueParams}; -pub use sc_consensus_aura::{slot_duration, AuraVerifier, BuildAuraWorkerParams, SlotProportion}; +pub use sc_consensus_aura::{ + slot_duration, standalone::slot_duration_at, AuraVerifier, BuildAuraWorkerParams, + SlotProportion, +}; pub use sc_consensus_slots::InherentDataProviderExt; pub mod collator; @@ -61,6 +71,7 @@ pub struct AuraConsensus { create_inherent_data_providers: Arc, aura_worker: Arc>, slot_duration: SlotDuration, + last_slot_processed: Arc, _phantom: PhantomData, } @@ -70,6 +81,7 @@ impl Clone for AuraConsensus { create_inherent_data_providers: self.create_inherent_data_providers.clone(), aura_worker: self.aura_worker.clone(), slot_duration: self.slot_duration, + last_slot_processed: self.last_slot_processed.clone(), _phantom: PhantomData, } } @@ -156,6 +168,7 @@ where Box::new(AuraConsensus { create_inherent_data_providers: Arc::new(create_inherent_data_providers), aura_worker: Arc::new(Mutex::new(worker)), + last_slot_processed: Default::default(), slot_duration, _phantom: PhantomData, }) @@ -221,6 +234,18 @@ where Some((validation_data.max_pov_size / 2) as usize), ); + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to produce + // multiple blocks per slot which very likely would fail on chain. Thus, we have this "hack" + // to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if self.last_slot_processed.fetch_max(*info.slot, Ordering::Relaxed) >= *info.slot { + return None + } + let res = self.aura_worker.lock().await.on_slot(info).await?; Some(ParachainCandidate { block: res.block, proof: res.storage_proof }) diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 26d7ba1b142c07cd5a5e133d7e4594028755dccf..5a014b10e35f39b0a5e00ca01da7cfd3ecc50a5f 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -1,16 +1,20 @@ [package] name = "cumulus-client-consensus-common" description = "Cumulus specific common consensus implementations" -version = "0.1.0" +version = "0.7.0" 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" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } -dyn-clone = "1.0.12" +async-trait = "0.1.74" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +dyn-clone = "1.0.16" futures = "0.3.28" -log = "0.4.20" +log = { workspace = true, default-features = true } tracing = "0.1.37" # Substrate diff --git a/cumulus/client/consensus/common/src/level_monitor.rs b/cumulus/client/consensus/common/src/level_monitor.rs index 5f115ec2c4a3c885b60da5f5dd9f0fe3faefab16..270e3f57ae5a374aba2f22966c7376e0791020ca 100644 --- a/cumulus/client/consensus/common/src/level_monitor.rs +++ b/cumulus/client/consensus/common/src/level_monitor.rs @@ -98,7 +98,6 @@ where /// /// Level limits are not enforced during this phase. fn restore(&mut self) { - const ERR_MSG: &str = "route from finalized to leaf should be available; qed"; let info = self.backend.blockchain().info(); log::debug!( @@ -112,7 +111,14 @@ where self.import_counter = info.finalized_number; for leaf in self.backend.blockchain().leaves().unwrap_or_default() { - let mut meta = self.backend.blockchain().header_metadata(leaf).expect(ERR_MSG); + let Ok(mut meta) = self.backend.blockchain().header_metadata(leaf) else { + log::debug!( + target: LOG_TARGET, + "Could not fetch header metadata for leaf: {leaf:?}", + ); + + continue + }; self.import_counter = self.import_counter.max(meta.number); @@ -123,7 +129,19 @@ where if meta.number <= self.lowest_level { break } - meta = self.backend.blockchain().header_metadata(meta.parent).expect(ERR_MSG); + + meta = match self.backend.blockchain().header_metadata(meta.parent) { + Ok(m) => m, + Err(_) => { + // This can happen after we have warp synced a node. + log::debug!( + target: LOG_TARGET, + "Could not fetch header metadata for parent: {:?}", + meta.parent, + ); + break + }, + } } } diff --git a/cumulus/client/consensus/common/src/lib.rs b/cumulus/client/consensus/common/src/lib.rs index 08bceabb2bd4a49ac8917e82caf1b97cd9eac183..cebe34e7ea58828372a9261e3be94866e119546a 100644 --- a/cumulus/client/consensus/common/src/lib.rs +++ b/cumulus/client/consensus/common/src/lib.rs @@ -111,12 +111,15 @@ impl ParachainConsensus for Box + Send + /// Parachain specific block import. /// -/// This is used to set `block_import_params.fork_choice` to `false` as long as the block origin is -/// not `NetworkInitialSync`. The best block for parachains is determined by the relay chain. -/// Meaning we will update the best block, as it is included by the relay-chain. +/// Specialized block import for parachains. It supports to delay setting the best block until the +/// relay chain has included a candidate in its best block. By default the delayed best block +/// setting is disabled. The block import also monitors the imported blocks and prunes by default if +/// there are too many blocks at the same height. Too many blocks at the same height can for example +/// happen if the relay chain is rejecting the parachain blocks in the validation. pub struct ParachainBlockImport { inner: BI, monitor: Option>>, + delayed_best_block: bool, } impl> ParachainBlockImport { @@ -141,13 +144,27 @@ impl> ParachainBlockImport let monitor = level_limit.map(|level_limit| SharedData::new(LevelMonitor::new(level_limit, backend))); - Self { inner, monitor } + Self { inner, monitor, delayed_best_block: false } + } + + /// Create a new instance which delays setting the best block. + /// + /// The number of leaves per level limit is set to `LevelLimit::Default`. + pub fn new_with_delayed_best_block(inner: BI, backend: Arc) -> Self { + Self { + delayed_best_block: true, + ..Self::new_with_limit(inner, backend, LevelLimit::Default) + } } } impl Clone for ParachainBlockImport { fn clone(&self) -> Self { - ParachainBlockImport { inner: self.inner.clone(), monitor: self.monitor.clone() } + ParachainBlockImport { + inner: self.inner.clone(), + monitor: self.monitor.clone(), + delayed_best_block: self.delayed_best_block, + } } } @@ -182,11 +199,13 @@ where params.finalized = true; } - // Best block is determined by the relay chain, or if we are doing the initial sync - // we import all blocks as new best. - params.fork_choice = Some(sc_consensus::ForkChoiceStrategy::Custom( - params.origin == sp_consensus::BlockOrigin::NetworkInitialSync, - )); + if self.delayed_best_block { + // Best block is determined by the relay chain, or if we are doing the initial sync + // we import all blocks as new best. + params.fork_choice = Some(sc_consensus::ForkChoiceStrategy::Custom( + params.origin == sp_consensus::BlockOrigin::NetworkInitialSync, + )); + } let maybe_lock = self.monitor.as_ref().map(|monitor_lock| { let mut monitor = monitor_lock.shared_data_locked(); diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 9658a0add790da01ad9fe83f11956d1fbd88523a..bfb95ae388ae3cd31f5035a9c6195631adbb8809 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -136,6 +136,15 @@ impl RelayChainInterface for Relaychain { Ok(Some(PersistedValidationData { parent_head, ..Default::default() })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, @@ -1124,7 +1133,8 @@ fn find_potential_parents_aligned_with_pending() { let backend = Arc::new(Backend::new_test(1000, 1)); let client = Arc::new(TestClientBuilder::with_backend(backend.clone()).build()); - let mut para_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let mut para_import = + ParachainBlockImport::new_with_delayed_best_block(client.clone(), backend.clone()); let relay_parent = relay_hash_from_block_num(10); // Choose different relay parent for alternative chain to get new hashes. @@ -1279,7 +1289,8 @@ fn find_potential_parents_aligned_no_pending() { let backend = Arc::new(Backend::new_test(1000, 1)); let client = Arc::new(TestClientBuilder::with_backend(backend.clone()).build()); - let mut para_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let mut para_import = + ParachainBlockImport::new_with_delayed_best_block(client.clone(), backend.clone()); let relay_parent = relay_hash_from_block_num(10); // Choose different relay parent for alternative chain to get new hashes. diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index 29720a8f4791124536095ffba44189762de47f90..b37232bb4485d6f5ece63a3c940bd065c1d3f083 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "cumulus-client-consensus-proposer" description = "A Substrate `Proposer` for building parachain blocks" -version = "0.1.0" +version = "0.7.0" 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" -thiserror = "1.0.48" +async-trait = "0.1.74" +thiserror = { workspace = true } # Substrate sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } 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 ba077f624030b8838c23d3c46c42b3527dbe6b2c..3d06d6b89ef7447b6196b1fc395ed7be54e3ba62 100644 --- a/cumulus/client/consensus/relay-chain/Cargo.toml +++ b/cumulus/client/consensus/relay-chain/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "cumulus-client-consensus-relay-chain" description = "The relay-chain provided consensus algorithm" -version = "0.1.0" +version = "0.7.0" 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/consensus/relay-chain/src/import_queue.rs b/cumulus/client/consensus/relay-chain/src/import_queue.rs index 9ee03b95904c60fb3b26f60e54023c13c88a2808..f44f440932437ff0cf658f7644bbf2e23670f8cd 100644 --- a/cumulus/client/consensus/relay-chain/src/import_queue.rs +++ b/cumulus/client/consensus/relay-chain/src/import_queue.rs @@ -55,6 +55,10 @@ where &mut self, mut block_params: BlockImportParams, ) -> Result, String> { + block_params.fork_choice = Some(sc_consensus::ForkChoiceStrategy::Custom( + block_params.origin == sp_consensus::BlockOrigin::NetworkInitialSync, + )); + // Skip checks that include execution, if being told so, or when importing only state. // // This is done for example when gap syncing and it is expected that the block after the gap @@ -100,7 +104,6 @@ where } block_params.post_hash = Some(block_params.header.hash()); - Ok(block_params) } } diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index eaaf497ac3edcc74415ab0ba6a1c3caf6faff953..995ef606d270ed688af3751016be9c8c37f80305 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "cumulus-client-network" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } +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" parking_lot = "0.12.1" diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index e03f470753bb6c32c4410a7c694dea8312c74c31..d986635f961c914c9ec6fa970b20170f9f8b9cbc 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -117,6 +117,15 @@ impl RelayChainInterface for DummyRelayChainInterface { })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, diff --git a/cumulus/client/parachain-inherent/Cargo.toml b/cumulus/client/parachain-inherent/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e00f3ba26066c037aca5e11fcb539ae6d95a4c85 --- /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-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } +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 89% rename from cumulus/primitives/parachain-inherent/src/client_side.rs rename to cumulus/client/parachain-inherent/src/lib.rs index 52987d2da44ceee275b88a441f5a64951cf245f2..051eb6764c8ce97a8305a07bdddb8f06f61fe4bc 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. @@ -153,7 +159,7 @@ impl ParachainInherentData { target: LOG_TARGET, relay_parent = ?relay_parent, error = ?e, - "An error occured during requesting the downward messages.", + "An error occurred during requesting the downward messages.", ); }) .ok()?; @@ -165,7 +171,7 @@ impl ParachainInherentData { target: LOG_TARGET, relay_parent = ?relay_parent, error = ?e, - "An error occured during requesting the inbound HRMP messages.", + "An error occurred during requesting the inbound HRMP messages.", ); }) .ok()?; @@ -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 94% rename from cumulus/primitives/parachain-inherent/src/mock.rs rename to cumulus/client/parachain-inherent/src/mock.rs index 5168b46a14d03def1ccfe72c9244e71bfb202d84..22691006f93ed264e3e7d37b7b2120ea576e9da3 100644 --- a/cumulus/primitives/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -19,8 +19,9 @@ 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_crypto_hashing::twox_128; use sp_inherents::{InherentData, InherentDataProvider}; use sp_runtime::traits::Block; use std::collections::BTreeMap; @@ -61,6 +62,8 @@ pub struct MockValidationDataInherentDataProvider { pub raw_downward_messages: Vec>, // Inbound Horizontal messages sorted by channel pub raw_horizontal_messages: Vec<(ParaId, Vec)>, + // Additional key-value pairs that should be injected. + pub additional_key_values: Option, Vec)>>, } pub trait GenerateRandomness { @@ -166,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() }; @@ -186,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 @@ -210,6 +213,10 @@ impl> InherentDataProvider sproof_builder.randomness = self.relay_randomness_config.generate_randomness(self.current_para_block.into()); + if let Some(key_values) = &self.additional_key_values { + sproof_builder.additional_key_values = key_values.clone() + } + let (relay_parent_storage_root, proof) = sproof_builder.into_state_root_and_proof(); inherent_data.put_data( diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 2ce903fd35249a4bbf3863c24c6a34ef4907ef15..375a57a87c2aa3fb92a6317c756b18c8117736f8 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "cumulus-client-pov-recovery" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.28" futures-timer = "3.0.2" rand = "0.8.5" @@ -28,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/pov-recovery/src/active_candidate_recovery.rs b/cumulus/client/pov-recovery/src/active_candidate_recovery.rs index 322b19c796a8c10191f3bfb261b293aba4348944..2c635320ff4ae6f68f33bb9da5ca545098851f65 100644 --- a/cumulus/client/pov-recovery/src/active_candidate_recovery.rs +++ b/cumulus/client/pov-recovery/src/active_candidate_recovery.rs @@ -16,12 +16,12 @@ use sp_runtime::traits::Block as BlockT; -use polkadot_node_primitives::AvailableData; +use polkadot_node_primitives::PoV; use polkadot_node_subsystem::messages::AvailabilityRecoveryMessage; use futures::{channel::oneshot, stream::FuturesUnordered, Future, FutureExt, StreamExt}; -use std::{collections::HashSet, pin::Pin}; +use std::{collections::HashSet, pin::Pin, sync::Arc}; use crate::RecoveryHandle; @@ -30,9 +30,8 @@ use crate::RecoveryHandle; /// This handles the candidate recovery and tracks the activate recoveries. pub(crate) struct ActiveCandidateRecovery { /// The recoveries that are currently being executed. - recoveries: FuturesUnordered< - Pin)> + Send>>, - >, + recoveries: + FuturesUnordered>)> + Send>>>, /// The block hashes of the candidates currently being recovered. candidates: HashSet, recovery_handle: Box, @@ -68,7 +67,7 @@ impl ActiveCandidateRecovery { self.recoveries.push( async move { match rx.await { - Ok(Ok(res)) => (block_hash, Some(res)), + Ok(Ok(res)) => (block_hash, Some(res.pov)), Ok(Err(error)) => { tracing::debug!( target: crate::LOG_TARGET, @@ -93,8 +92,8 @@ impl ActiveCandidateRecovery { /// Waits for the next recovery. /// - /// If the returned [`AvailableData`] is `None`, it means that the recovery failed. - pub async fn wait_for_recovery(&mut self) -> (Block::Hash, Option) { + /// If the returned [`PoV`] is `None`, it means that the recovery failed. + pub async fn wait_for_recovery(&mut self) -> (Block::Hash, Option>) { loop { if let Some(res) = self.recoveries.next().await { self.candidates.remove(&res.0); diff --git a/cumulus/client/pov-recovery/src/lib.rs b/cumulus/client/pov-recovery/src/lib.rs index b050bc66799c7aa84db67374dc801890e0e7ce25..32aba6c8993a6da67cd3adeb394e87e6c067b46c 100644 --- a/cumulus/client/pov-recovery/src/lib.rs +++ b/cumulus/client/pov-recovery/src/lib.rs @@ -51,7 +51,7 @@ use sc_consensus::import_queue::{ImportQueueService, IncomingBlock}; use sp_consensus::{BlockOrigin, BlockStatus, SyncOracle}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use polkadot_node_primitives::{AvailableData, POV_BOMB_LIMIT}; +use polkadot_node_primitives::{PoV, POV_BOMB_LIMIT}; use polkadot_node_subsystem::messages::AvailabilityRecoveryMessage; use polkadot_overseer::Handle as OverseerHandle; use polkadot_primitives::{ @@ -346,15 +346,11 @@ where } /// Handle a recovered candidate. - async fn handle_candidate_recovered( - &mut self, - block_hash: Block::Hash, - available_data: Option, - ) { - let available_data = match available_data { - Some(data) => { + async fn handle_candidate_recovered(&mut self, block_hash: Block::Hash, pov: Option<&PoV>) { + let pov = match pov { + Some(pov) => { self.candidates_in_retry.remove(&block_hash); - data + pov }, None => if self.candidates_in_retry.insert(block_hash) { @@ -373,18 +369,16 @@ where }, }; - let raw_block_data = match sp_maybe_compressed_blob::decompress( - &available_data.pov.block_data.0, - POV_BOMB_LIMIT, - ) { - Ok(r) => r, - Err(error) => { - tracing::debug!(target: LOG_TARGET, ?error, "Failed to decompress PoV"); + let raw_block_data = + match sp_maybe_compressed_blob::decompress(&pov.block_data.0, POV_BOMB_LIMIT) { + Ok(r) => r, + Err(error) => { + tracing::debug!(target: LOG_TARGET, ?error, "Failed to decompress PoV"); - self.reset_candidate(block_hash); - return - }, - }; + self.reset_candidate(block_hash); + return + }, + }; let block_data = match ParachainBlockData::::decode(&mut &raw_block_data[..]) { Ok(d) => d, @@ -416,6 +410,7 @@ where ?block_hash, parent_hash = ?parent, parent_scheduled_for_recovery, + waiting_blocks = self.waiting_for_parent.len(), "Waiting for recovery of parent.", ); @@ -448,13 +443,13 @@ where _ => (), } - self.import_block(block).await; + self.import_block(block); } /// Import the given `block`. /// /// This will also recursivley drain `waiting_for_parent` and import them as well. - async fn import_block(&mut self, block: Block) { + fn import_block(&mut self, block: Block) { let mut blocks = VecDeque::new(); tracing::debug!(target: LOG_TARGET, block_hash = ?block.hash(), "Importing block retrieved using pov_recovery"); @@ -557,7 +552,6 @@ where }; futures::pin_mut!(pending_candidates); - loop { select! { pending_candidate = pending_candidates.next() => { @@ -579,6 +573,17 @@ where imported = imported_blocks.next() => { if let Some(imported) = imported { self.clear_waiting_recovery(&imported.hash); + + // We need to double check that no blocks are waiting for this block. + // Can happen when a waiting child block is queued to wait for parent while the parent block is still + // in the import queue. + if let Some(waiting_blocks) = self.waiting_for_parent.remove(&imported.hash) { + for block in waiting_blocks { + tracing::debug!(target: LOG_TARGET, block_hash = ?block.hash(), resolved_parent = ?imported.hash, "Found new waiting child block during import, queuing."); + self.import_block(block); + } + }; + } else { tracing::debug!(target: LOG_TARGET, "Imported blocks stream ended"); return; @@ -595,10 +600,10 @@ where next_to_recover = self.candidate_recovery_queue.next_recovery().fuse() => { self.recover_candidate(next_to_recover).await; }, - (block_hash, available_data) = + (block_hash, pov) = self.active_candidate_recovery.wait_for_recovery().fuse() => { - self.handle_candidate_recovered(block_hash, available_data).await; + self.handle_candidate_recovered(block_hash, pov.as_deref()).await; }, } } diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index bc8d0d430c774bb3255f9494cd1fdced5a2d944d..aa16230cd8aff0f313311c15269508156e57b544 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -1,11 +1,16 @@ [package] authors.workspace = true name = "cumulus-relay-chain-inprocess-interface" -version = "0.1.0" +version = "0.7.0" 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" @@ -37,7 +42,7 @@ sp-keyring = { path = "../../../substrate/primitives/keyring" } # Polkadot polkadot-primitives = { path = "../../../polkadot/primitives" } polkadot-test-client = { path = "../../../polkadot/node/test/client" } -metered = { package = "prioritized-metered-channel", version = "0.5.1", default-features = false, features=["futures_channel"] } +metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false, features = ["futures_channel"] } # Cumulus cumulus-test-service = { path = "../../test/service" } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 42a56b649f035bdf10aa18099fe3851a3b5d238e..6ea02b2e7c1f6d9b5313459890dd2147015359e5 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -21,7 +21,7 @@ use cumulus_primitives_core::{ relay_chain::{ runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, - ValidatorId, + ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -115,6 +115,19 @@ impl RelayChainInterface for RelayChainInProcessInterface { )?) } + async fn validation_code_hash( + &self, + hash: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + Ok(self.full_client.runtime_api().validation_code_hash( + hash, + para_id, + occupied_core_assumption, + )?) + } + async fn candidate_pending_availability( &self, hash: PHash, @@ -283,18 +296,19 @@ fn build_polkadot_full_node( config, polkadot_service::NewFullParams { is_parachain_node, - grandpa_pause: None, // Disable BEEFY. It should not be required by the internal relay chain node. enable_beefy: false, + force_authoring_backoff: false, jaeger_agent: None, telemetry_worker_handle, // Cumulus doesn't spawn PVF workers, so we can disable version checks. node_version: None, + secure_validator_mode: false, workers_path: None, workers_names: None, - overseer_gen: polkadot_service::RealOverseerGen, + overseer_gen: polkadot_service::CollatorOverseerGen, overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench, diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index 3da7ab0b0e8212d7a05d972ca3fb7c82a0955bd3..6e652b892104e929e5e0a5bb7ce8cf33d364e8e6 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -1,8 +1,13 @@ [package] authors.workspace = true name = "cumulus-relay-chain-interface" -version = "0.1.0" +version = "0.7.0" 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" } @@ -15,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" -thiserror = "1.0.48" -jsonrpsee-core = "0.16.2" +async-trait = "0.1.74" +thiserror = { workspace = true } +jsonrpsee-core = "0.22" parity-scale-codec = "3.6.4" diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index 3dda61635804d47440689872271751e0bba27d87..bb93e6a168c849fa9d41586ad8b9ec0013e6c01f 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -22,7 +22,7 @@ use sc_client_api::StorageProof; use futures::Stream; use async_trait::async_trait; -use jsonrpsee_core::Error as JsonRpcError; +use jsonrpsee_core::ClientError as JsonRpcError; use parity_scale_codec::Error as CodecError; use sp_api::ApiError; @@ -30,7 +30,7 @@ use cumulus_primitives_core::relay_chain::BlockId; pub use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -194,6 +194,15 @@ pub trait RelayChainInterface: Send + Sync { relay_parent: PHash, relevant_keys: &Vec>, ) -> RelayChainResult; + + /// Returns the validation code hash for the given `para_id` using the given + /// `occupied_core_assumption`. + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult>; } #[async_trait] @@ -301,4 +310,15 @@ where async fn header(&self, block_id: BlockId) -> RelayChainResult> { (**self).header(block_id).await } + + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + (**self) + .validation_code_hash(relay_parent, para_id, occupied_core_assumption) + .await + } } diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml index f132b1a76535d65a1d501ba643a7af0454413ec8..98240c92adab38ea103f14699e1e49f7449b3d24 100644 --- a/cumulus/client/relay-chain-minimal-node/Cargo.toml +++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml @@ -1,8 +1,13 @@ [package] authors.workspace = true name = "cumulus-relay-chain-minimal-node" -version = "0.1.0" +version = "0.7.0" 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 @@ -17,12 +22,16 @@ polkadot-collator-protocol = { path = "../../../polkadot/node/network/collator-p polkadot-network-bridge = { path = "../../../polkadot/node/network/bridge" } polkadot-node-collation-generation = { path = "../../../polkadot/node/collation-generation" } polkadot-node-core-runtime-api = { path = "../../../polkadot/node/core/runtime-api" } +polkadot-node-core-chain-api = { path = "../../../polkadot/node/core/chain-api" } +polkadot-node-core-prospective-parachains = { path = "../../../polkadot/node/core/prospective-parachains" } +polkadot-service = { path = "../../../polkadot/node/service" } # substrate deps sc-authority-discovery = { path = "../../../substrate/client/authority-discovery" } sc-network = { path = "../../../substrate/client/network" } sc-network-common = { path = "../../../substrate/client/network/common" } sc-service = { path = "../../../substrate/client/service" } +sc-client-api = { path = "../../../substrate/client/api" } substrate-prometheus-endpoint = { path = "../../../substrate/utils/prometheus" } sc-tracing = { path = "../../../substrate/client/tracing" } sc-utils = { path = "../../../substrate/client/utils" } @@ -30,6 +39,8 @@ sp-api = { path = "../../../substrate/primitives/api" } sp-consensus-babe = { path = "../../../substrate/primitives/consensus/babe" } sp-consensus = { path = "../../../substrate/primitives/consensus/common" } sp-runtime = { path = "../../../substrate/primitives/runtime" } +sp-blockchain = { path = "../../../substrate/primitives/blockchain" } +tokio = { version = "1.32.0", features = ["macros"] } # cumulus deps cumulus-relay-chain-interface = { path = "../relay-chain-interface" } @@ -38,6 +49,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 1e78df7115435449e6beb89224f0af32f3b3b988..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 @@ -20,13 +20,17 @@ use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; use cumulus_relay_chain_rpc_interface::RelayChainRpcClient; use futures::{Stream, StreamExt}; use polkadot_core_primitives::{Block, BlockNumber, Hash, Header}; -use polkadot_overseer::RuntimeApiSubsystemClient; +use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ async_backing::{AsyncBackingParams, BackingState}, slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; +use sc_client_api::AuxStore; use sp_api::{ApiError, RuntimeApiInfo}; +use sp_blockchain::Info; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; #[derive(Clone)] pub struct BlockChainRpcClient { @@ -53,6 +57,64 @@ impl BlockChainRpcClient { } } +#[async_trait::async_trait] +impl ChainApiBackend for BlockChainRpcClient { + async fn header( + &self, + hash: ::Hash, + ) -> sp_blockchain::Result::Header>> { + Ok(self.rpc_client.chain_get_header(Some(hash)).await?) + } + + async fn info(&self) -> sp_blockchain::Result> { + let (best_header_opt, genesis_hash, finalized_head) = futures::try_join!( + self.rpc_client.chain_get_header(None), + self.rpc_client.chain_get_head(Some(0)), + self.rpc_client.chain_get_finalized_head() + )?; + let best_header = best_header_opt.ok_or_else(|| { + RelayChainError::GenericError( + "Unable to retrieve best header from relay chain.".to_string(), + ) + })?; + + let finalized_header = + self.rpc_client.chain_get_header(Some(finalized_head)).await?.ok_or_else(|| { + RelayChainError::GenericError( + "Unable to retrieve finalized header from relay chain.".to_string(), + ) + })?; + Ok(Info { + best_hash: best_header.hash(), + best_number: best_header.number, + genesis_hash, + finalized_hash: finalized_head, + finalized_number: finalized_header.number, + finalized_state: Some((finalized_header.hash(), finalized_header.number)), + number_leaves: 1, + block_gap: None, + }) + } + + async fn number( + &self, + hash: ::Hash, + ) -> sp_blockchain::Result::Header as HeaderT>::Number>> { + Ok(self + .rpc_client + .chain_get_header(Some(hash)) + .await? + .map(|maybe_header| maybe_header.number)) + } + + async fn hash( + &self, + number: NumberFor, + ) -> sp_blockchain::Result::Hash>> { + Ok(self.rpc_client.chain_get_block_hash(number.into()).await?) + } +} + #[async_trait::async_trait] impl RuntimeApiSubsystemClient for BlockChainRpcClient { async fn validators( @@ -364,6 +426,22 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { ) -> Result, ApiError> { 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?) + } } #[async_trait::async_trait] @@ -398,3 +476,25 @@ impl BlockChainRpcClient { Ok(self.rpc_client.get_finalized_heads_stream()?.boxed()) } } + +// Implementation required by ChainApiSubsystem +// but never called in our case. +impl AuxStore for BlockChainRpcClient { + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >( + &self, + _insert: I, + _delete: D, + ) -> sp_blockchain::Result<()> { + unimplemented!("Not supported on the RPC collator") + } + + fn get_aux(&self, _key: &[u8]) -> sp_blockchain::Result>> { + unimplemented!("Not supported on the RPC collator") + } +} diff --git a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs index 216ca0d39658a908f5710e87d224a47c9e8909fb..f01ef8b05ecba77cf6458fdfe80e966b3a67e6a3 100644 --- a/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs +++ b/cumulus/client/relay-chain-minimal-node/src/collator_overseer.rs @@ -17,146 +17,27 @@ use futures::{select, StreamExt}; use std::sync::Arc; -use polkadot_availability_recovery::AvailabilityRecoverySubsystem; -use polkadot_collator_protocol::{CollatorProtocolSubsystem, ProtocolSide}; -use polkadot_network_bridge::{ - Metrics as NetworkBridgeMetrics, NetworkBridgeRx as NetworkBridgeRxSubsystem, - NetworkBridgeTx as NetworkBridgeTxSubsystem, -}; -use polkadot_node_collation_generation::CollationGenerationSubsystem; -use polkadot_node_core_runtime_api::RuntimeApiSubsystem; -use polkadot_node_network_protocol::{ - peer_set::PeerSetProtocolNames, - request_response::{ - v1::{self, AvailableDataFetchingRequest}, - v2, IncomingRequestReceiver, ReqProtocolNames, - }, -}; -use polkadot_node_subsystem_util::metrics::{prometheus::Registry, Metrics}; use polkadot_overseer::{ - BlockInfo, DummySubsystem, Handle, Overseer, OverseerConnector, OverseerHandle, SpawnGlue, - UnpinHandle, + BlockInfo, Handle, Overseer, OverseerConnector, OverseerHandle, SpawnGlue, UnpinHandle, }; -use polkadot_primitives::CollatorPair; +use polkadot_service::overseer::{collator_overseer_builder, OverseerGenArgs}; -use sc_authority_discovery::Service as AuthorityDiscoveryService; -use sc_network::NetworkStateInfo; use sc_service::TaskManager; use sc_utils::mpsc::tracing_unbounded; -use cumulus_primitives_core::relay_chain::{Block, Hash as PHash}; use cumulus_relay_chain_interface::RelayChainError; use crate::BlockChainRpcClient; -/// Arguments passed for overseer construction. -pub(crate) struct CollatorOverseerGenArgs<'a> { - /// Runtime client generic, providing the `ProvieRuntimeApi` trait besides others. - pub runtime_client: Arc, - /// Underlying network service implementation. - pub network_service: Arc>, - /// Syncing oracle. - pub sync_oracle: Box, - /// Underlying authority discovery service. - pub authority_discovery_service: AuthorityDiscoveryService, - /// Receiver for collation request protocol v1. - pub collation_req_receiver_v1: IncomingRequestReceiver, - /// Receiver for collation request protocol v2. - pub collation_req_receiver_v2: IncomingRequestReceiver, - /// Receiver for availability request protocol - pub available_data_req_receiver: IncomingRequestReceiver, - /// Prometheus registry, commonly used for production systems, less so for test. - pub registry: Option<&'a Registry>, - /// Task spawner to be used throughout the overseer and the APIs it provides. - pub spawner: sc_service::SpawnTaskHandle, - /// Determines the behavior of the collator. - pub collator_pair: CollatorPair, - /// Request response protocols - pub req_protocol_names: ReqProtocolNames, - /// Peerset protocols name mapping - pub peer_set_protocol_names: PeerSetProtocolNames, -} - fn build_overseer( connector: OverseerConnector, - CollatorOverseerGenArgs { - runtime_client, - network_service, - sync_oracle, - authority_discovery_service, - collation_req_receiver_v1, - collation_req_receiver_v2, - available_data_req_receiver, - registry, - spawner, - collator_pair, - req_protocol_names, - peer_set_protocol_names, - }: CollatorOverseerGenArgs<'_>, + args: OverseerGenArgs, ) -> Result< (Overseer, Arc>, OverseerHandle), RelayChainError, > { - let spawner = SpawnGlue(spawner); - let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; - let builder = Overseer::builder() - .availability_distribution(DummySubsystem) - .availability_recovery(AvailabilityRecoverySubsystem::with_availability_store_skip( - available_data_req_receiver, - Metrics::register(registry)?, - )) - .availability_store(DummySubsystem) - .bitfield_distribution(DummySubsystem) - .bitfield_signing(DummySubsystem) - .candidate_backing(DummySubsystem) - .candidate_validation(DummySubsystem) - .pvf_checker(DummySubsystem) - .chain_api(DummySubsystem) - .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) - .collator_protocol({ - let side = ProtocolSide::Collator { - peer_id: network_service.local_peer_id(), - collator_pair, - request_receiver_v1: collation_req_receiver_v1, - request_receiver_v2: collation_req_receiver_v2, - metrics: Metrics::register(registry)?, - }; - CollatorProtocolSubsystem::new(side) - }) - .network_bridge_rx(NetworkBridgeRxSubsystem::new( - network_service.clone(), - authority_discovery_service.clone(), - sync_oracle, - network_bridge_metrics.clone(), - peer_set_protocol_names.clone(), - )) - .network_bridge_tx(NetworkBridgeTxSubsystem::new( - network_service, - authority_discovery_service, - network_bridge_metrics, - req_protocol_names, - peer_set_protocol_names, - )) - .provisioner(DummySubsystem) - .runtime_api(RuntimeApiSubsystem::new( - runtime_client.clone(), - Metrics::register(registry)?, - spawner.clone(), - )) - .statement_distribution(DummySubsystem) - .prospective_parachains(DummySubsystem) - .approval_distribution(DummySubsystem) - .approval_voting(DummySubsystem) - .gossip_support(DummySubsystem) - .dispute_coordinator(DummySubsystem) - .dispute_distribution(DummySubsystem) - .chain_selection(DummySubsystem) - .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) - .active_leaves(Default::default()) - .supports_parachains(runtime_client) - .metrics(Metrics::register(registry)?) - .spawner(spawner); + let builder = + collator_overseer_builder(args).map_err(|e| RelayChainError::Application(e.into()))?; builder .build_with_connector(connector) @@ -164,7 +45,7 @@ fn build_overseer( } pub(crate) fn spawn_overseer( - overseer_args: CollatorOverseerGenArgs, + overseer_args: OverseerGenArgs, task_manager: &TaskManager, relay_chain_rpc_client: Arc, ) -> Result { diff --git a/cumulus/client/relay-chain-minimal-node/src/lib.rs b/cumulus/client/relay-chain-minimal-node/src/lib.rs index c8fba923ddebe93b43466048dc05e51da35acb56..4bccca59fe3ea2eec816513778885f43c9504d51 100644 --- a/cumulus/client/relay-chain-minimal-node/src/lib.rs +++ b/cumulus/client/relay-chain-minimal-node/src/lib.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use collator_overseer::{CollatorOverseerGenArgs, NewMinimalNode}; +use collator_overseer::NewMinimalNode; use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface, RelayChainResult}; use cumulus_relay_chain_rpc_interface::{RelayChainRpcClient, RelayChainRpcInterface, Url}; use network::build_collator_network; use polkadot_network_bridge::{peer_sets_info, IsAuthority}; use polkadot_node_network_protocol::{ - peer_set::PeerSetProtocolNames, + peer_set::{PeerSet, PeerSetProtocolNames}, request_response::{ v1, v2, IncomingRequest, IncomingRequestReceiver, Protocol, ReqProtocolNames, }, @@ -29,6 +29,7 @@ use polkadot_node_network_protocol::{ use polkadot_node_subsystem_util::metrics::prometheus::Registry; use polkadot_primitives::CollatorPair; +use polkadot_service::{overseer::OverseerGenArgs, IsParachainNode}; use sc_authority_discovery::Service as AuthorityDiscoveryService; use sc_network::{config::FullNetworkConfiguration, Event, NetworkEventStream, NetworkService}; @@ -171,29 +172,27 @@ async fn new_minimal_relay_chain( ); } - let genesis_hash = relay_chain_rpc_client - .block_get_hash(Some(0)) - .await - .expect("Genesis block hash is always available; qed") - .unwrap_or_default(); - - let peer_set_protocol_names = + let genesis_hash = relay_chain_rpc_client.block_get_hash(Some(0)).await?.unwrap_or_default(); + let peerset_protocol_names = PeerSetProtocolNames::new(genesis_hash, config.chain_spec.fork_id()); let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No }; - - for config in peer_sets_info(is_authority, &peer_set_protocol_names) { - net_config.add_notification_protocol(config); - } + let notification_services = peer_sets_info(is_authority, &peerset_protocol_names) + .into_iter() + .map(|(config, (peerset, service))| { + net_config.add_notification_protocol(config); + (peerset, service) + }) + .collect::>>(); let request_protocol_names = ReqProtocolNames::new(genesis_hash, config.chain_spec.fork_id()); - let (collation_req_receiver_v1, collation_req_receiver_v2, available_data_req_receiver) = + let (collation_req_v1_receiver, collation_req_v2_receiver, available_data_req_receiver) = build_request_response_protocol_receivers(&request_protocol_names, &mut net_config); let best_header = relay_chain_rpc_client .chain_get_header(None) .await? .ok_or_else(|| RelayChainError::RpcCallError("Unable to fetch best header".to_string()))?; - let (network, network_starter, sync_oracle) = build_collator_network( + let (network, network_starter, sync_service) = build_collator_network( &config, net_config, task_manager.spawn_handle(), @@ -210,19 +209,21 @@ async fn new_minimal_relay_chain( prometheus_registry.cloned(), ); - let overseer_args = CollatorOverseerGenArgs { + let overseer_args = OverseerGenArgs { runtime_client: relay_chain_rpc_client.clone(), network_service: network, - sync_oracle, + sync_service, authority_discovery_service, - collation_req_receiver_v1, - collation_req_receiver_v2, + collation_req_v1_receiver, + collation_req_v2_receiver, available_data_req_receiver, registry: prometheus_registry, spawner: task_manager.spawn_handle(), - collator_pair, + is_parachain_node: IsParachainNode::Collator(collator_pair), + overseer_message_channel_capacity_override: None, req_protocol_names: request_protocol_names, - peer_set_protocol_names, + peerset_protocol_names, + notification_services, }; let overseer_handle = @@ -241,10 +242,10 @@ fn build_request_response_protocol_receivers( IncomingRequestReceiver, IncomingRequestReceiver, ) { - let (collation_req_receiver_v1, cfg) = + let (collation_req_v1_receiver, cfg) = IncomingRequest::get_config_receiver(request_protocol_names); config.add_request_response_protocol(cfg); - let (collation_req_receiver_v2, cfg) = + let (collation_req_v2_receiver, cfg) = IncomingRequest::get_config_receiver(request_protocol_names); config.add_request_response_protocol(cfg); let (available_data_req_receiver, cfg) = @@ -252,5 +253,5 @@ fn build_request_response_protocol_receivers( config.add_request_response_protocol(cfg); let cfg = Protocol::ChunkFetchingV1.get_outbound_only_config(request_protocol_names); config.add_request_response_protocol(cfg); - (collation_req_receiver_v1, collation_req_receiver_v2, available_data_req_receiver) + (collation_req_v1_receiver, collation_req_v2_receiver, available_data_req_receiver) } diff --git a/cumulus/client/relay-chain-minimal-node/src/network.rs b/cumulus/client/relay-chain-minimal-node/src/network.rs index f39d7a26dd8838d693d8c32d62d51fa147c26cda..7286fab7907cb6d475b81a4dfd97c5d001180873 100644 --- a/cumulus/client/relay-chain-minimal-node/src/network.rs +++ b/cumulus/client/relay-chain-minimal-node/src/network.rs @@ -19,32 +19,36 @@ use sp_runtime::traits::{Block as BlockT, NumberFor}; use sc_network::{ config::{ - NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, + NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, + ProtocolId, SetConfig, }, peer_store::PeerStore, NetworkService, }; -use sc_network::config::FullNetworkConfiguration; +use sc_network::{config::FullNetworkConfiguration, NotificationService}; use sc_network_common::{role::Roles, sync::message::BlockAnnouncesHandshake}; use sc_service::{error::Error, Configuration, NetworkStarter, SpawnTaskHandle}; -use sc_utils::mpsc::tracing_unbounded; use std::{iter, sync::Arc}; /// Build the network service, the network status sinks and an RPC sender. pub(crate) fn build_collator_network( config: &Configuration, - network_config: FullNetworkConfiguration, + mut full_network_config: FullNetworkConfiguration, spawn_handle: SpawnTaskHandle, genesis_hash: Hash, best_header: Header, ) -> Result< - (Arc>, NetworkStarter, Box), + ( + Arc>, + NetworkStarter, + Arc, + ), Error, > { let protocol_id = config.protocol_id(); - let block_announce_config = get_block_announce_proto_config::( + let (block_announce_config, _notification_service) = get_block_announce_proto_config::( protocol_id.clone(), &None, Roles::from(&config.role), @@ -53,8 +57,12 @@ pub(crate) fn build_collator_network( genesis_hash, ); + // Since this node has no syncing, we do not want light-clients to connect to it. + // Here we set any potential light-client slots to 0. + adjust_network_config_light_in_peers(&mut full_network_config.network_config); + let peer_store = PeerStore::new( - network_config + full_network_config .network_config .boot_nodes .iter() @@ -64,8 +72,6 @@ pub(crate) fn build_collator_network( let peer_store_handle = peer_store.handle(); spawn_handle.spawn("peer-store", Some("networking"), peer_store.run()); - // RX is not used for anything because syncing is not started for the minimal node - let (tx, _rx) = tracing_unbounded("mpsc_syncing_engine_protocol", 100_000); let network_params = sc_network::config::Params:: { role: config.role.clone(), executor: { @@ -75,13 +81,12 @@ pub(crate) fn build_collator_network( }) }, fork_id: None, - network_config, + network_config: full_network_config, peer_store: peer_store_handle, genesis_hash, protocol_id, metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_announce_config, - tx, }; let network_worker = sc_network::NetworkWorker::new(network_params)?; @@ -111,7 +116,19 @@ pub(crate) fn build_collator_network( let network_starter = NetworkStarter::new(network_start_tx); - Ok((network_service, network_starter, Box::new(SyncOracle {}))) + Ok((network_service, network_starter, Arc::new(SyncOracle {}))) +} + +fn adjust_network_config_light_in_peers(config: &mut NetworkConfiguration) { + let light_client_in_peers = (config.default_peers_set.in_peers + + config.default_peers_set.out_peers) + .saturating_sub(config.default_peers_set_num_full); + if light_client_in_peers > 0 { + tracing::debug!(target: crate::LOG_TARGET, "Detected {light_client_in_peers} peer slots for light clients. Since this minimal node does support\ + neither syncing nor light-client request/response, we are setting them to 0."); + } + config.default_peers_set.in_peers = + config.default_peers_set.in_peers.saturating_sub(light_client_in_peers); } struct SyncOracle; @@ -133,7 +150,7 @@ fn get_block_announce_proto_config( best_number: NumberFor, best_hash: B::Hash, genesis_hash: B::Hash, -) -> NonDefaultSetConfig { +) -> (NonDefaultSetConfig, Box) { let block_announces_protocol = { let genesis_hash = genesis_hash.as_ref(); if let Some(ref fork_id) = fork_id { @@ -143,12 +160,11 @@ fn get_block_announce_proto_config( } }; - NonDefaultSetConfig { - notifications_protocol: block_announces_protocol.into(), - fallback_names: iter::once(format!("/{}/block-announces/1", protocol_id.as_ref()).into()) - .collect(), - max_notification_size: 1024 * 1024, - handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::::build( + NonDefaultSetConfig::new( + block_announces_protocol.into(), + iter::once(format!("/{}/block-announces/1", protocol_id.as_ref()).into()).collect(), + 1024 * 1024, + Some(NotificationHandshake::new(BlockAnnouncesHandshake::::build( roles, best_number, best_hash, @@ -156,11 +172,11 @@ fn get_block_announce_proto_config( ))), // NOTE: `set_config` will be ignored by `protocol.rs` as the block announcement // protocol is still hardcoded into the peerset. - set_config: SetConfig { + SetConfig { in_peers: 0, out_peers: 0, reserved_nodes: Vec::new(), non_reserved_mode: NonReservedPeerMode::Deny, }, - } + ) } diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index 0f09377e106ce4274ddf0d59028476af3127a51b..801712b1ad150a8a63e85c40ec0854a62b5969bc 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -1,9 +1,13 @@ [package] authors.workspace = true name = "cumulus-relay-chain-rpc-interface" -version = "0.1.0" +version = "0.7.0" 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" } @@ -18,6 +22,7 @@ sp-authority-discovery = { path = "../../../substrate/primitives/authority-disco sp-state-machine = { path = "../../../substrate/primitives/state-machine" } sp-storage = { path = "../../../substrate/primitives/storage" } sp-runtime = { path = "../../../substrate/primitives/runtime" } +sp-version = { path = "../../../substrate/primitives/version" } sc-client-api = { path = "../../../substrate/client/api" } sc-rpc-api = { path = "../../../substrate/client/rpc-api" } sc-service = { path = "../../../substrate/client/service" } @@ -28,16 +33,16 @@ tokio-util = { version = "0.7.8", features = ["compat"] } futures = "0.3.28" futures-timer = "3.0.2" parity-scale-codec = "3.6.4" -jsonrpsee = { version = "0.16.2", features = ["ws-client"] } +jsonrpsee = { version = "0.22", 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.107" -serde = "1.0.188" +serde_json = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } schnellru = "0.2.1" -smoldot = { version = "0.11.0", default_features = false, features = ["std"]} +smoldot = { version = "0.11.0", default_features = false, features = ["std"] } smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] } either = "1.8.1" -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" pin-project = "1.1.3" diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 96f8fc8b5563335eb7796bc8fd105fced15bc5e1..3a4c186e301eab295aa3befbd3c5549636fdb2c0 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -19,7 +19,7 @@ use core::time::Duration; use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -110,6 +110,17 @@ impl RelayChainInterface for RelayChainRpcInterface { .await } + async fn validation_code_hash( + &self, + hash: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + self.rpc_client + .validation_code_hash(hash, para_id, occupied_core_assumption) + .await + } + async fn candidate_pending_availability( &self, hash: RelayHash, diff --git a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs index 6fd057e170b715a1586dbd2dbf3f608268c539bd..9a49b60281b3c51fa1426903a0e73157a6f04e0e 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs @@ -19,12 +19,9 @@ //! we treat the light-client as a normal JsonRPC target. use futures::{channel::mpsc::Sender, prelude::*, stream::FuturesUnordered}; -use jsonrpsee::core::{ - client::{ - Client as JsonRpseeClient, ClientBuilder, ClientT, ReceivedMessage, TransportReceiverT, - TransportSenderT, - }, - Error, +use jsonrpsee::core::client::{ + Client as JsonRpseeClient, ClientBuilder, ClientT, Error, ReceivedMessage, TransportReceiverT, + TransportSenderT, }; use smoldot_light::{ChainId, Client as SmoldotClient, JsonRpcResponses}; use std::{num::NonZeroU32, sync::Arc}; diff --git a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs index 322bcc93dae6d8158a0e6cfb99ded9a29bee79c5..b716feef1c998d66eba3c5ea28ee1dd98c4959e3 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs @@ -27,7 +27,7 @@ use jsonrpsee::{ core::{ client::{Client as JsonRpcClient, ClientT, Subscription}, params::ArrayParams, - Error as JsonRpseeError, JsonValue, + ClientError as JsonRpseeError, JsonValue, }, ws_client::WsClientBuilder, }; diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 5924716adcb41d8b4c93b525ea4e1ae8093b413b..6578210a259c956b1c817f579f66e84258a8ab3e 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -19,7 +19,7 @@ use futures::channel::{ oneshot::Sender as OneshotSender, }; use jsonrpsee::{ - core::{params::ArrayParams, Error as JsonRpseeError}, + core::{params::ArrayParams, ClientError as JsonRpseeError}, rpc_params, }; use serde::de::DeserializeOwned; @@ -31,7 +31,9 @@ use parity_scale_codec::{Decode, Encode}; use cumulus_primitives_core::{ relay_chain::{ async_backing::{AsyncBackingParams, BackingState}, - slashing, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, OccupiedCoreAssumption, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, @@ -44,10 +46,10 @@ use cumulus_relay_chain_interface::{RelayChainError, RelayChainResult}; use sc_client_api::StorageData; use sc_rpc_api::{state::ReadProof, system::Health}; use sc_service::TaskManager; -use sp_api::RuntimeVersion; use sp_consensus_babe::Epoch; use sp_core::sp_std::collections::btree_map::BTreeMap; use sp_storage::StorageKey; +use sp_version::RuntimeVersion; use crate::{ light_client_worker::{build_smoldot_client, LightClientRpcWorker}, @@ -201,7 +203,7 @@ impl RelayChainRpcClient { let value = rx.await.map_err(|err| { RelayChainError::WorkerCommunicationError(format!( - "Unexpected channel close on RPC worker side: {}", + "RPC worker channel closed. This can hint and connectivity issues with the supplied RPC endpoints. Message: {}", err )) })??; @@ -597,6 +599,14 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_node_features( + &self, + at: RelayHash, + ) -> Result { + self.call_remote_runtime_function("ParachainHost_node_features", at, None::<()>) + .await + } + pub async fn parachain_host_disabled_validators( &self, at: RelayHash, @@ -615,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, @@ -624,6 +647,20 @@ impl RelayChainRpcClient { .await } + pub async fn validation_code_hash( + &self, + at: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function( + "ParachainHost_validation_code_hash", + at, + Some((para_id, occupied_core_assumption)), + ) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index b7c274ceecdcbb524ee57d228ef475699402ea49..2bafbee951a950ce7144e593cab9ae4387b1a5f6 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -1,8 +1,13 @@ [package] name = "cumulus-client-service" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" @@ -36,7 +41,7 @@ cumulus-client-consensus-common = { path = "../consensus/common" } cumulus-client-pov-recovery = { path = "../pov-recovery" } cumulus-client-network = { path = "../network" } cumulus-primitives-core = { path = "../../primitives/core" } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction" } cumulus-relay-chain-interface = { path = "../relay-chain-interface" } cumulus-relay-chain-inprocess-interface = { path = "../relay-chain-inprocess-interface" } cumulus-relay-chain-minimal-node = { path = "../relay-chain-minimal-node" } - diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index 82890666ebae3f197e291019d215ea9d446ef71f..950e59aff24ec19a2e0ed3b22215d6c77fb7dc5e 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -49,9 +49,11 @@ use sc_utils::mpsc::TracingUnboundedSender; use sp_api::ProvideRuntimeApi; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_core::{traits::SpawnNamed, Decode}; -use sp_runtime::traits::{Block as BlockT, BlockIdTo}; +use sp_runtime::traits::{Block as BlockT, BlockIdTo, Header}; use std::{sync::Arc, time::Duration}; +pub use cumulus_primitives_proof_size_hostfunction::storage_proof_size; + // Given the sporadic nature of the explicit recovery operation and the // possibility to retry infinite times this value is more than enough. // In practice here we expect no more than one queued messages. @@ -505,11 +507,11 @@ where None, async move { log::debug!( - target: "cumulus-network", + target: LOG_TARGET_SYNC, "waiting for announce block in a background task...", ); - let _ = wait_for_target_block::(sender, para_id, relay_chain_interface) + let _ = wait_for_finalized_para_head::(sender, para_id, relay_chain_interface) .await .map_err(|e| { log::error!( @@ -527,7 +529,7 @@ where /// Waits for the relay chain to have finished syncing and then gets the parachain header that /// corresponds to the last finalized relay chain block. -async fn wait_for_target_block( +async fn wait_for_finalized_para_head( sender: oneshot::Sender<::Header>, para_id: ParaId, relay_chain_interface: RCInterface, @@ -560,11 +562,15 @@ where .map_err(|e| format!("{e:?}"))? .ok_or("Could not find parachain head in relay chain")?; - let target_block = B::Header::decode(&mut &validation_data.parent_head.0[..]) + let finalized_header = B::Header::decode(&mut &validation_data.parent_head.0[..]) .map_err(|e| format!("Failed to decode parachain head: {e}"))?; - log::debug!(target: LOG_TARGET_SYNC, "Target block reached {:?}", target_block); - let _ = sender.send(target_block); + log::info!( + "🎉 Received target parachain header #{} ({}) from the relay chain.", + finalized_header.number(), + finalized_header.hash() + ); + let _ = sender.send(finalized_header); return Ok(()) } } diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index 78d25f2285e5310c4280352c9a8c1b81ea33ab13..ff30dce7b03394a523a811217de0db9b7dc5cd02 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -1,23 +1,27 @@ [package] name = "cumulus-pallet-aura-ext" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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"] } # Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -pallet-aura = { path = "../../../substrate/frame/aura", default-features = false} -pallet-timestamp= { path = "../../../substrate/frame/timestamp", default-features = false} -sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false} -sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +pallet-aura = { path = "../../../substrate/frame/aura", default-features = false } +pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-features = false } +sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false } +sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } # Cumulus cumulus-pallet-parachain-system = { path = "../parachain-system", default-features = false } @@ -28,7 +32,7 @@ cumulus-pallet-parachain-system = { path = "../parachain-system", default-featur cumulus-pallet-parachain-system = { path = "../parachain-system" } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-parachain-system/std", diff --git a/cumulus/pallets/aura-ext/src/consensus_hook.rs b/cumulus/pallets/aura-ext/src/consensus_hook.rs index 089ab5c3198b98129e53e155dc4fcc009bf0e6d8..592029803391179785bb3b5606079d1bee6b553f 100644 --- a/cumulus/pallets/aura-ext/src/consensus_hook.rs +++ b/cumulus/pallets/aura-ext/src/consensus_hook.rs @@ -54,8 +54,8 @@ where let velocity = V.max(1); let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot"); - let (slot, authored) = pallet::Pallet::::slot_info() - .expect("slot info is inserted on block initialization"); + let (slot, authored) = + pallet::SlotInfo::::get().expect("slot info is inserted on block initialization"); // Convert relay chain timestamp. let relay_chain_timestamp = @@ -100,7 +100,7 @@ impl< /// is more recent than the included block itself. pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool { let velocity = V.max(1); - let (last_slot, authored_so_far) = match pallet::Pallet::::slot_info() { + let (last_slot, authored_so_far) = match pallet::SlotInfo::::get() { None => return true, Some(x) => x, }; diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index 34a41557152d8df38e0abc2d612151b2f74dc779..7ca84dff7c513c2406d3c0de7b9c0ac26048f508 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -63,14 +63,14 @@ pub mod pallet { impl Hooks> for Pallet { fn on_finalize(_: BlockNumberFor) { // Update to the latest AuRa authorities. - Authorities::::put(Aura::::authorities()); + Authorities::::put(pallet_aura::Authorities::::get()); } fn on_initialize(_: BlockNumberFor) -> Weight { // Fetch the authorities once to get them into the storage proof of the PoV. Authorities::::get(); - let new_slot = Aura::::current_slot(); + let new_slot = pallet_aura::CurrentSlot::::get(); let (new_slot, authored) = match SlotInfo::::get() { Some((slot, authored)) if slot == new_slot => (slot, authored + 1), @@ -103,7 +103,6 @@ pub mod pallet { /// /// Updated on each block initialization. #[pallet::storage] - #[pallet::getter(fn slot_info)] pub(crate) type SlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; #[pallet::genesis_config] @@ -116,13 +115,7 @@ pub mod pallet { #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - let authorities = Aura::::authorities(); - - assert!( - !authorities.is_empty(), - "AuRa authorities empty, maybe wrong order in `construct_runtime!`?", - ); - + let authorities = pallet_aura::Authorities::::get(); Authorities::::put(authorities); } } diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 68e4a681c2b179fa5b2d7bf3fe0d90cdc9befd77..241a78466d61be3969930e031e1a52f4f3478482 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -7,26 +7,29 @@ license = "Apache-2.0" name = "pallet-collator-selection" readme = "README.md" repository.workspace = true -version = "3.0.0" +version = "9.0.0" + +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.20", default-features = false } +log = { workspace = true } codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.0.0" } rand = { version = "0.8.5", features = ["std_rng"], default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-staking = { path = "../../../substrate/primitives/staking", default-features = false} -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false} -pallet-session = { path = "../../../substrate/frame/session", default-features = false} +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-staking = { path = "../../../substrate/primitives/staking", default-features = false } +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } +pallet-session = { path = "../../../substrate/frame/session", default-features = false } -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true} +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } [dev-dependencies] sp-core = { path = "../../../substrate/primitives/core" } @@ -39,7 +42,7 @@ pallet-balances = { path = "../../../substrate/frame/balances" } pallet-aura = { path = "../../../substrate/frame/aura" } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", @@ -74,5 +77,3 @@ try-runtime = [ "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/pallets/collator-selection/src/benchmarking.rs b/cumulus/pallets/collator-selection/src/benchmarking.rs index 49999dc114df7bdc94dca2f8c563d7c1d8bb6083..c6b600445282534951b2c63a6c2a87181688acd1 100644 --- a/cumulus/pallets/collator-selection/src/benchmarking.rs +++ b/cumulus/pallets/collator-selection/src/benchmarking.rs @@ -22,17 +22,12 @@ use super::*; #[allow(unused)] use crate::Pallet as CollatorSelection; use codec::Decode; -use frame_benchmarking::{ - account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError, -}; -use frame_support::{ - dispatch::DispatchResult, - traits::{Currency, EnsureOrigin, Get, ReservableCurrency}, -}; +use frame_benchmarking::{account, v2::*, whitelisted_caller, BenchmarkError}; +use frame_support::traits::{Currency, EnsureOrigin, Get, ReservableCurrency}; use frame_system::{pallet_prelude::BlockNumberFor, EventRecord, RawOrigin}; use pallet_authorship::EventHandler; use pallet_session::{self as session, SessionManager}; -use sp_std::prelude::*; +use sp_std::{cmp, prelude::*}; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -91,24 +86,27 @@ fn register_validators(count: u32) -> Vec(count: u32) { let candidates = (0..count).map(|c| account("candidate", c, SEED)).collect::>(); - assert!(>::get() > 0u32.into(), "Bond cannot be zero!"); + assert!(CandidacyBond::::get() > 0u32.into(), "Bond cannot be zero!"); for who in candidates { - T::Currency::make_free_balance_be(&who, >::get() * 2u32.into()); + T::Currency::make_free_balance_be(&who, CandidacyBond::::get() * 3u32.into()); >::register_as_candidate(RawOrigin::Signed(who).into()).unwrap(); } } fn min_candidates() -> u32 { let min_collators = T::MinEligibleCollators::get(); - let invulnerable_length = >::get().len(); + let invulnerable_length = Invulnerables::::get().len(); min_collators.saturating_sub(invulnerable_length.try_into().unwrap()) } fn min_invulnerables() -> u32 { let min_collators = T::MinEligibleCollators::get(); - let candidates_length = >::get().len(); - min_collators.saturating_sub(candidates_length.try_into().unwrap()) + let candidates_length = CandidateList::::decode_len() + .unwrap_or_default() + .try_into() + .unwrap_or_default(); + min_collators.saturating_sub(candidates_length) } #[benchmarks(where T: pallet_authorship::Config + session::Config)] @@ -145,8 +143,8 @@ mod benchmarks { T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; // need to fill up candidates - >::put(T::Currency::minimum_balance()); - >::put(c); + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(c); // get accounts and keys for the `c` candidates let mut candidates = (0..c).map(|cc| validator::(cc)).collect::>(); // add one more to the list. should not be in `b` (invulnerables) because it's the account @@ -160,22 +158,19 @@ mod benchmarks { .unwrap(); } // ... and register them. - for (who, _) in candidates { - let deposit = >::get(); - T::Currency::make_free_balance_be(&who, deposit * 1000_u32.into()); - let incoming = CandidateInfo { who: who.clone(), deposit }; - >::try_mutate(|candidates| -> DispatchResult { - if !candidates.iter().any(|candidate| candidate.who == who) { - T::Currency::reserve(&who, deposit)?; - candidates.try_push(incoming).expect("we've respected the bounded vec limit"); - >::insert( - who.clone(), - frame_system::Pallet::::block_number() + T::KickThreshold::get(), - ); - } - Ok(()) + for (who, _) in candidates.iter() { + let deposit = CandidacyBond::::get(); + T::Currency::make_free_balance_be(who, deposit * 1000_u32.into()); + CandidateList::::try_mutate(|list| { + list.try_push(CandidateInfo { who: who.clone(), deposit }).unwrap(); + Ok::<(), BenchmarkError>(()) }) - .expect("only returns ok"); + .unwrap(); + T::Currency::reserve(who, deposit)?; + LastAuthoredBlock::::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); } // now we need to fill up invulnerables @@ -183,7 +178,7 @@ mod benchmarks { invulnerables.sort(); let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> = frame_support::BoundedVec::try_from(invulnerables).unwrap(); - >::put(invulnerables); + Invulnerables::::put(invulnerables); #[extrinsic_call] _(origin as T::RuntimeOrigin, new_invulnerable.clone()); @@ -202,8 +197,8 @@ mod benchmarks { invulnerables.sort(); let invulnerables: frame_support::BoundedVec<_, T::MaxInvulnerables> = frame_support::BoundedVec::try_from(invulnerables).unwrap(); - >::put(invulnerables); - let to_remove = >::get().first().unwrap().clone(); + Invulnerables::::put(invulnerables); + let to_remove = Invulnerables::::get().first().unwrap().clone(); #[extrinsic_call] _(origin as T::RuntimeOrigin, to_remove.clone()); @@ -226,10 +221,27 @@ mod benchmarks { } #[benchmark] - fn set_candidacy_bond() -> Result<(), BenchmarkError> { - let bond_amount: BalanceOf = T::Currency::minimum_balance() * 10u32.into(); + fn set_candidacy_bond( + c: Linear<0, { T::MaxCandidates::get() }>, + k: Linear<0, { T::MaxCandidates::get() }>, + ) -> Result<(), BenchmarkError> { + let initial_bond_amount: BalanceOf = T::Currency::minimum_balance() * 2u32.into(); + CandidacyBond::::put(initial_bond_amount); + register_validators::(c); + register_candidates::(c); + let kicked = cmp::min(k, c); let origin = T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let bond_amount = if k > 0 { + CandidateList::::mutate(|candidates| { + for info in candidates.iter_mut().skip(kicked as usize) { + info.deposit = T::Currency::minimum_balance() * 3u32.into(); + } + }); + T::Currency::minimum_balance() * 3u32.into() + } else { + T::Currency::minimum_balance() + }; #[extrinsic_call] _(origin as T::RuntimeOrigin, bond_amount); @@ -238,12 +250,41 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn update_bond( + c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>, + ) -> Result<(), BenchmarkError> { + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(c); + + register_validators::(c); + register_candidates::(c); + + let caller = CandidateList::::get()[0].who.clone(); + v2::whitelist!(caller); + + let bond_amount: BalanceOf = + T::Currency::minimum_balance() + T::Currency::minimum_balance(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), bond_amount); + + assert_last_event::( + Event::CandidateBondUpdated { account_id: caller, deposit: bond_amount }.into(), + ); + assert!( + CandidateList::::get().iter().last().unwrap().deposit == + T::Currency::minimum_balance() * 2u32.into() + ); + Ok(()) + } + // worse case is when we have all the max-candidate slots filled except one, and we fill that // one. #[benchmark] fn register_as_candidate(c: Linear<1, { T::MaxCandidates::get() - 1 }>) { - >::put(T::Currency::minimum_balance()); - >::put(c + 1); + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(c + 1); register_validators::(c); register_candidates::(c); @@ -267,16 +308,46 @@ mod benchmarks { ); } + #[benchmark] + fn take_candidate_slot(c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>) { + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(1); + + register_validators::(c); + register_candidates::(c); + + let caller: T::AccountId = whitelisted_caller(); + let bond: BalanceOf = T::Currency::minimum_balance() * 10u32.into(); + T::Currency::make_free_balance_be(&caller, bond); + + >::set_keys( + RawOrigin::Signed(caller.clone()).into(), + keys::(c + 1), + Vec::new(), + ) + .unwrap(); + + let target = CandidateList::::get().iter().last().unwrap().who.clone(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone()); + + assert_last_event::( + Event::CandidateReplaced { old: target, new: caller, deposit: bond / 2u32.into() } + .into(), + ); + } + // worse case is the last candidate leaving. #[benchmark] fn leave_intent(c: Linear<{ min_candidates::() + 1 }, { T::MaxCandidates::get() }>) { - >::put(T::Currency::minimum_balance()); - >::put(c); + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(c); register_validators::(c); register_candidates::(c); - let leaving = >::get().last().unwrap().who.clone(); + let leaving = CandidateList::::get().iter().last().unwrap().who.clone(); v2::whitelist!(leaving); #[extrinsic_call] @@ -288,7 +359,7 @@ mod benchmarks { // worse case is paying a non-existing candidate account. #[benchmark] fn note_author() { - >::put(T::Currency::minimum_balance()); + CandidacyBond::::put(T::Currency::minimum_balance()); T::Currency::make_free_balance_be( &>::account_id(), T::Currency::minimum_balance() * 4u32.into(), @@ -314,40 +385,46 @@ mod benchmarks { r: Linear<1, { T::MaxCandidates::get() }>, c: Linear<1, { T::MaxCandidates::get() }>, ) { - >::put(T::Currency::minimum_balance()); - >::put(c); + CandidacyBond::::put(T::Currency::minimum_balance()); + DesiredCandidates::::put(c); frame_system::Pallet::::set_block_number(0u32.into()); register_validators::(c); register_candidates::(c); - let new_block: BlockNumberFor = 1800u32.into(); + let new_block: BlockNumberFor = T::KickThreshold::get(); let zero_block: BlockNumberFor = 0u32.into(); - let candidates = >::get(); + let candidates: Vec = CandidateList::::get() + .iter() + .map(|candidate_info| candidate_info.who.clone()) + .collect(); let non_removals = c.saturating_sub(r); for i in 0..c { - >::insert(candidates[i as usize].who.clone(), zero_block); + LastAuthoredBlock::::insert(candidates[i as usize].clone(), zero_block); } if non_removals > 0 { for i in 0..non_removals { - >::insert(candidates[i as usize].who.clone(), new_block); + LastAuthoredBlock::::insert(candidates[i as usize].clone(), new_block); } } else { for i in 0..c { - >::insert(candidates[i as usize].who.clone(), new_block); + LastAuthoredBlock::::insert(candidates[i as usize].clone(), new_block); } } let min_candidates = min_candidates::(); - let pre_length = >::get().len(); + let pre_length = CandidateList::::decode_len().unwrap_or_default(); frame_system::Pallet::::set_block_number(new_block); - assert!(>::get().len() == c as usize); - + let current_length: u32 = CandidateList::::decode_len() + .unwrap_or_default() + .try_into() + .unwrap_or_default(); + assert!(c == current_length); #[block] { as SessionManager<_>>::new_session(0); @@ -357,16 +434,20 @@ mod benchmarks { // candidates > removals and remaining candidates > min candidates // => remaining candidates should be shorter than before removal, i.e. some were // actually removed. - assert!(>::get().len() < pre_length); + assert!(CandidateList::::decode_len().unwrap_or_default() < pre_length); } else if c > r && non_removals < min_candidates { // candidates > removals and remaining candidates would be less than min candidates // => remaining candidates should equal min candidates, i.e. some were removed up to // the minimum, but then any more were "forced" to stay in candidates. - assert!(>::get().len() == min_candidates as usize); + let current_length: u32 = CandidateList::::decode_len() + .unwrap_or_default() + .try_into() + .unwrap_or_default(); + assert!(min_candidates == current_length); } else { // removals >= candidates, non removals must == 0 // can't remove more than exist - assert!(>::get().len() == pre_length); + assert!(CandidateList::::decode_len().unwrap_or_default() == pre_length); } } diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 24493ce9d9cdc7f451f2e997615962ee5f0662e0..84bde5c9fac9f640692458f02b37f0ee108a734f 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -35,16 +35,36 @@ //! //! 1. [`Invulnerables`]: a set of collators appointed by governance. These accounts will always be //! collators. -//! 2. [`Candidates`]: these are *candidates to the collation task* and may or may not be elected as -//! a final collator. +//! 2. [`CandidateList`]: these are *candidates to the collation task* and may or may not be elected +//! as a final collator. //! -//! The current implementation resolves congestion of [`Candidates`] in a first-come-first-serve -//! manner. +//! The current implementation resolves congestion of [`CandidateList`] through a simple auction +//! mechanism. Candidates bid for the collator slots and at the end of the session, the auction ends +//! and the top candidates are selected to become collators. The number of selected candidates is +//! determined by the value of `DesiredCandidates`. +//! +//! Before the list reaches full capacity, candidates can register by placing the minimum bond +//! through `register_as_candidate`. Then, if an account wants to participate in the collator slot +//! auction, they have to replace an existing candidate by placing a greater deposit through +//! `take_candidate_slot`. Existing candidates can increase their bids through `update_bond`. +//! +//! At any point, an account can take the place of another account in the candidate list if they put +//! up a greater deposit than the target. While new joiners would like to deposit as little as +//! possible to participate in the auction, the replacement threat incentivizes candidates to bid as +//! close to their budget as possible in order to avoid being replaced. +//! +//! Candidates which are not on "winning" slots in the list can also decrease their deposits through +//! `update_bond`, but candidates who are on top slots and try to decrease their deposits will fail +//! in order to enforce auction mechanics and have meaningful bids. //! //! Candidates will not be allowed to get kicked or `leave_intent` if the total number of collators //! would fall below `MinEligibleCollators`. This is to ensure that some collators will always //! exist, i.e. someone is eligible to produce a block. //! +//! When a new session starts, candidates with the highest deposits will be selected in order until +//! the desired number of collators is reached. Candidates can increase or decrease their deposits +//! between sessions in order to ensure they receive a slot in the collator list. +//! //! ### Rewards //! //! The Collator Selection pallet maintains an on-chain account (the "Pot"). In each block, the @@ -56,8 +76,8 @@ //! //! To initiate rewards, an ED needs to be transferred to the pot address. //! -//! Note: Eventually the Pot distribution may be modified as discussed in -//! [this issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073). +//! Note: Eventually the Pot distribution may be modified as discussed in [this +//! issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073). #![cfg_attr(not(feature = "std"), no_std)] @@ -98,7 +118,7 @@ pub mod pallet { use sp_staking::SessionIndex; use sp_std::vec::Vec; - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); type BalanceOf = @@ -176,15 +196,16 @@ pub mod pallet { /// The invulnerable, permissioned collators. This list must be sorted. #[pallet::storage] - #[pallet::getter(fn invulnerables)] pub type Invulnerables = StorageValue<_, BoundedVec, ValueQuery>; /// The (community, limited) collation candidates. `Candidates` and `Invulnerables` should be /// mutually exclusive. + /// + /// This list is sorted in ascending order by deposit and when the deposits are equal, the least + /// recently updated is considered greater. #[pallet::storage] - #[pallet::getter(fn candidates)] - pub type Candidates = StorageValue< + pub type CandidateList = StorageValue< _, BoundedVec>, T::MaxCandidates>, ValueQuery, @@ -192,7 +213,6 @@ pub mod pallet { /// Last block authored by collator. #[pallet::storage] - #[pallet::getter(fn last_authored_block)] pub type LastAuthoredBlock = StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor, ValueQuery>; @@ -200,14 +220,12 @@ pub mod pallet { /// /// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct. #[pallet::storage] - #[pallet::getter(fn desired_candidates)] pub type DesiredCandidates = StorageValue<_, u32, ValueQuery>; /// Fixed amount to deposit to become a collator. /// /// When a collator calls `leave_intent` they immediately receive the deposit back. #[pallet::storage] - #[pallet::getter(fn candidacy_bond)] pub type CandidacyBond = StorageValue<_, BalanceOf, ValueQuery>; #[pallet::genesis_config] @@ -240,9 +258,9 @@ pub mod pallet { bounded_invulnerables.sort(); - >::put(self.desired_candidates); - >::put(self.candidacy_bond); - >::put(bounded_invulnerables); + DesiredCandidates::::put(self.desired_candidates); + CandidacyBond::::put(self.candidacy_bond); + Invulnerables::::put(bounded_invulnerables); } } @@ -261,8 +279,12 @@ pub mod pallet { NewCandidacyBond { bond_amount: BalanceOf }, /// A new candidate joined. CandidateAdded { account_id: T::AccountId, deposit: BalanceOf }, + /// Bond of a candidate updated. + CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf }, /// A candidate was removed. CandidateRemoved { account_id: T::AccountId }, + /// An account was replaced in the candidate list by another one. + CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf }, /// An account was unable to be added to the Invulnerables because they did not have keys /// registered. Other Invulnerables may have been set. InvalidInvulnerableSkipped { account_id: T::AccountId }, @@ -288,12 +310,38 @@ pub mod pallet { NoAssociatedValidatorId, /// Validator ID is not yet registered. ValidatorNotRegistered, + /// Could not insert in the candidate list. + InsertToCandidateListFailed, + /// Could not remove from the candidate list. + RemoveFromCandidateListFailed, + /// New deposit amount would be below the minimum candidacy bond. + DepositTooLow, + /// Could not update the candidate list. + UpdateCandidateListFailed, + /// Deposit amount is too low to take the target's slot in the candidate list. + InsufficientBond, + /// The target account to be replaced in the candidate list is not a candidate. + TargetIsNotCandidate, + /// The updated deposit amount is equal to the amount already reserved. + IdenticalDeposit, + /// Cannot lower candidacy bond while occupying a future collator slot in the list. + InvalidUnreserve, } #[pallet::hooks] impl Hooks> for Pallet { fn integrity_test() { assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator"); + assert!( + T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get()) >= + T::MinEligibleCollators::get(), + "invulnerables and candidates must be able to satisfy collator demand" + ); + } + + #[cfg(feature = "try-runtime")] + fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() } } @@ -307,8 +355,8 @@ pub mod pallet { /// acceptable Invulnerables, and is not proposing a _set_ of new Invulnerables. /// /// This call does not maintain mutual exclusivity of `Invulnerables` and `Candidates`. It - /// is recommended to use a batch of `add_invulnerable` and `remove_invulnerable` instead. - /// A `batch_all` can also be used to enforce atomicity. If any candidates are included in + /// is recommended to use a batch of `add_invulnerable` and `remove_invulnerable` instead. A + /// `batch_all` can also be used to enforce atomicity. If any candidates are included in /// `new`, they should be removed with `remove_invulnerable_candidate` after execution. /// /// Must be called by the `UpdateOrigin`. @@ -319,8 +367,9 @@ pub mod pallet { // don't wipe out the collator set if new.is_empty() { + // Casting `u32` to `usize` should be safe on all machines running this. ensure!( - Candidates::::decode_len().unwrap_or_default() >= + CandidateList::::decode_len().unwrap_or_default() >= T::MinEligibleCollators::get() as usize, Error::::TooFewEligibleCollators ); @@ -370,7 +419,7 @@ pub mod pallet { // Invulnerables must be sorted for removal. bounded_invulnerables.sort(); - >::put(&bounded_invulnerables); + Invulnerables::::put(&bounded_invulnerables); Self::deposit_event(Event::NewInvulnerables { invulnerables: bounded_invulnerables.to_vec(), }); @@ -394,24 +443,58 @@ pub mod pallet { if max > T::MaxCandidates::get() { log::warn!("max > T::MaxCandidates; you might need to run benchmarks again"); } - >::put(max); + DesiredCandidates::::put(max); Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max }); Ok(().into()) } /// Set the candidacy bond amount. /// + /// If the candidacy bond is increased by this call, all current candidates which have a + /// deposit lower than the new bond will be kicked from the list and get their deposits + /// back. + /// /// The origin for this call must be the `UpdateOrigin`. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::set_candidacy_bond())] + #[pallet::weight(T::WeightInfo::set_candidacy_bond( + T::MaxCandidates::get(), + T::MaxCandidates::get() + ))] pub fn set_candidacy_bond( origin: OriginFor, bond: BalanceOf, ) -> DispatchResultWithPostInfo { T::UpdateOrigin::ensure_origin(origin)?; - >::put(bond); + let bond_increased = CandidacyBond::::mutate(|old_bond| -> bool { + let bond_increased = *old_bond < bond; + *old_bond = bond; + bond_increased + }); + let initial_len = CandidateList::::decode_len().unwrap_or_default(); + let kicked = (bond_increased && initial_len > 0) + .then(|| { + // Closure below returns the number of candidates which were kicked because + // their deposits were lower than the new candidacy bond. + CandidateList::::mutate(|candidates| -> usize { + let first_safe_candidate = candidates + .iter() + .position(|candidate| candidate.deposit >= bond) + .unwrap_or(initial_len); + let kicked_candidates = candidates.drain(..first_safe_candidate); + for candidate in kicked_candidates { + T::Currency::unreserve(&candidate.who, candidate.deposit); + LastAuthoredBlock::::remove(candidate.who); + } + first_safe_candidate + }) + }) + .unwrap_or_default(); Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond }); - Ok(().into()) + Ok(Some(T::WeightInfo::set_candidacy_bond( + bond_increased.then(|| initial_len as u32).unwrap_or_default(), + kicked as u32, + )) + .into()) } /// Register this account as a collator candidate. The account must (a) already have @@ -424,9 +507,12 @@ pub mod pallet { let who = ensure_signed(origin)?; // ensure we are below limit. - let length = >::decode_len().unwrap_or_default(); - ensure!((length as u32) < Self::desired_candidates(), Error::::TooManyCandidates); - ensure!(!Self::invulnerables().contains(&who), Error::::AlreadyInvulnerable); + let length: u32 = CandidateList::::decode_len() + .unwrap_or_default() + .try_into() + .unwrap_or_default(); + ensure!(length < T::MaxCandidates::get(), Error::::TooManyCandidates); + ensure!(!Invulnerables::::get().contains(&who), Error::::AlreadyInvulnerable); let validator_key = T::ValidatorIdOf::convert(who.clone()) .ok_or(Error::::NoAssociatedValidatorId)?; @@ -435,27 +521,29 @@ pub mod pallet { Error::::ValidatorNotRegistered ); - let deposit = Self::candidacy_bond(); + let deposit = CandidacyBond::::get(); // First authored block is current block plus kick threshold to handle session delay - let incoming = CandidateInfo { who: who.clone(), deposit }; - - let current_count = - >::try_mutate(|candidates| -> Result { - if candidates.iter().any(|candidate| candidate.who == who) { - Err(Error::::AlreadyCandidate)? - } else { - T::Currency::reserve(&who, deposit)?; - candidates.try_push(incoming).map_err(|_| Error::::TooManyCandidates)?; - >::insert( - who.clone(), - frame_system::Pallet::::block_number() + T::KickThreshold::get(), - ); - Ok(candidates.len()) - } - })?; + CandidateList::::try_mutate(|candidates| -> Result<(), DispatchError> { + ensure!( + !candidates.iter().any(|candidate_info| candidate_info.who == who), + Error::::AlreadyCandidate + ); + T::Currency::reserve(&who, deposit)?; + LastAuthoredBlock::::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); + candidates + .try_insert(0, CandidateInfo { who: who.clone(), deposit }) + .map_err(|_| Error::::InsertToCandidateListFailed)?; + Ok(()) + })?; Self::deposit_event(Event::CandidateAdded { account_id: who, deposit }); - Ok(Some(T::WeightInfo::register_as_candidate(current_count as u32)).into()) + // Safe to do unchecked add here because we ensure above that `length < + // T::MaxCandidates::get()`, and since `T::MaxCandidates` is `u32` it can be at most + // `u32::MAX`, therefore `length + 1` cannot overflow. + Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into()) } /// Deregister `origin` as a collator candidate. Note that the collator can only leave on @@ -468,13 +556,14 @@ pub mod pallet { pub fn leave_intent(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!( - Self::eligible_collators() > T::MinEligibleCollators::get() as usize, + Self::eligible_collators() > T::MinEligibleCollators::get(), Error::::TooFewEligibleCollators ); + let length = CandidateList::::decode_len().unwrap_or_default(); // Do remove their last authored block. - let current_count = Self::try_remove_candidate(&who, true)?; + Self::try_remove_candidate(&who, true)?; - Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) + Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into()) } /// Add a new account `who` to the list of `Invulnerables` collators. `who` must have @@ -500,7 +589,7 @@ pub mod pallet { Error::::ValidatorNotRegistered ); - >::try_mutate(|invulnerables| -> DispatchResult { + Invulnerables::::try_mutate(|invulnerables| -> DispatchResult { match invulnerables.binary_search(&who) { Ok(_) => return Err(Error::::AlreadyInvulnerable)?, Err(pos) => invulnerables @@ -521,7 +610,7 @@ pub mod pallet { .unwrap_or_default() .try_into() .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)), - Candidates::::decode_len() + CandidateList::::decode_len() .unwrap_or_default() .try_into() .unwrap_or(T::MaxCandidates::get()), @@ -540,11 +629,11 @@ pub mod pallet { T::UpdateOrigin::ensure_origin(origin)?; ensure!( - Self::eligible_collators() > T::MinEligibleCollators::get() as usize, + Self::eligible_collators() > T::MinEligibleCollators::get(), Error::::TooFewEligibleCollators ); - >::try_mutate(|invulnerables| -> DispatchResult { + Invulnerables::::try_mutate(|invulnerables| -> DispatchResult { let pos = invulnerables.binary_search(&who).map_err(|_| Error::::NotInvulnerable)?; invulnerables.remove(pos); @@ -554,6 +643,154 @@ pub mod pallet { Self::deposit_event(Event::InvulnerableRemoved { account_id: who }); Ok(()) } + + /// Update the candidacy bond of collator candidate `origin` to a new amount `new_deposit`. + /// + /// Setting a `new_deposit` that is lower than the current deposit while `origin` is + /// occupying a top-`DesiredCandidates` slot is not allowed. + /// + /// This call will fail if `origin` is not a collator candidate, the updated bond is lower + /// than the minimum candidacy bond, and/or the amount cannot be reserved. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))] + pub fn update_bond( + origin: OriginFor, + new_deposit: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(new_deposit >= CandidacyBond::::get(), Error::::DepositTooLow); + // The function below will try to mutate the `CandidateList` entry for the caller to + // update their deposit to the new value of `new_deposit`. The return value is the + // position of the entry in the list, used for weight calculation. + let length = + CandidateList::::try_mutate(|candidates| -> Result { + let idx = candidates + .iter() + .position(|candidate_info| candidate_info.who == who) + .ok_or_else(|| Error::::NotCandidate)?; + let candidate_count = candidates.len(); + // Remove the candidate from the list. + let mut info = candidates.remove(idx); + let old_deposit = info.deposit; + if new_deposit > old_deposit { + T::Currency::reserve(&who, new_deposit - old_deposit)?; + } else if new_deposit < old_deposit { + // Casting `u32` to `usize` should be safe on all machines running this. + ensure!( + idx.saturating_add(DesiredCandidates::::get() as usize) < + candidate_count, + Error::::InvalidUnreserve + ); + T::Currency::unreserve(&who, old_deposit - new_deposit); + } else { + return Err(Error::::IdenticalDeposit.into()) + } + + // Update the deposit and insert the candidate in the correct spot in the list. + info.deposit = new_deposit; + let new_pos = candidates + .iter() + .position(|candidate| candidate.deposit >= new_deposit) + .unwrap_or_else(|| candidates.len()); + candidates + .try_insert(new_pos, info) + .map_err(|_| Error::::InsertToCandidateListFailed)?; + + Ok(candidate_count) + })?; + + Self::deposit_event(Event::CandidateBondUpdated { + account_id: who, + deposit: new_deposit, + }); + Ok(Some(T::WeightInfo::update_bond(length as u32)).into()) + } + + /// The caller `origin` replaces a candidate `target` in the collator candidate list by + /// reserving `deposit`. The amount `deposit` reserved by the caller must be greater than + /// the existing bond of the target it is trying to replace. + /// + /// This call will fail if the caller is already a collator candidate or invulnerable, the + /// caller does not have registered session keys, the target is not a collator candidate, + /// and/or the `deposit` amount cannot be reserved. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))] + pub fn take_candidate_slot( + origin: OriginFor, + deposit: BalanceOf, + target: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + ensure!(!Invulnerables::::get().contains(&who), Error::::AlreadyInvulnerable); + ensure!(deposit >= CandidacyBond::::get(), Error::::InsufficientBond); + + let validator_key = T::ValidatorIdOf::convert(who.clone()) + .ok_or(Error::::NoAssociatedValidatorId)?; + ensure!( + T::ValidatorRegistration::is_registered(&validator_key), + Error::::ValidatorNotRegistered + ); + + let length = CandidateList::::decode_len().unwrap_or_default(); + // The closure below iterates through all elements of the candidate list to ensure that + // the caller isn't already a candidate and to find the target it's trying to replace in + // the list. The return value is a tuple of the position of the candidate to be replaced + // in the list along with its candidate information. + let target_info = CandidateList::::try_mutate( + |candidates| -> Result>, DispatchError> { + // Find the position in the list of the candidate that is being replaced. + let mut target_info_idx = None; + let mut new_info_idx = None; + for (idx, candidate_info) in candidates.iter().enumerate() { + // While iterating through the candidates trying to find the target, + // also ensure on the same pass that our caller isn't already a + // candidate. + ensure!(candidate_info.who != who, Error::::AlreadyCandidate); + // If we find our target, update the position but do not stop the + // iteration since we're also checking that the caller isn't already a + // candidate. + if candidate_info.who == target { + target_info_idx = Some(idx); + } + // Find the spot where the new candidate would be inserted in the current + // version of the list. + if new_info_idx.is_none() && candidate_info.deposit >= deposit { + new_info_idx = Some(idx); + } + } + let target_info_idx = + target_info_idx.ok_or(Error::::TargetIsNotCandidate)?; + + // Remove the old candidate from the list. + let target_info = candidates.remove(target_info_idx); + ensure!(deposit > target_info.deposit, Error::::InsufficientBond); + + // We have removed one element before `new_info_idx`, so the position we have to + // insert to is reduced by 1. + let new_pos = new_info_idx + .map(|i| i.saturating_sub(1)) + .unwrap_or_else(|| candidates.len()); + let new_info = CandidateInfo { who: who.clone(), deposit }; + // Insert the new candidate in the correct spot in the list. + candidates + .try_insert(new_pos, new_info) + .expect("candidate count previously decremented; qed"); + + Ok(target_info) + }, + )?; + T::Currency::reserve(&who, deposit)?; + T::Currency::unreserve(&target_info.who, target_info.deposit); + LastAuthoredBlock::::remove(target_info.who.clone()); + LastAuthoredBlock::::insert( + who.clone(), + frame_system::Pallet::::block_number() + T::KickThreshold::get(), + ); + + Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit }); + Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into()) + } } impl Pallet { @@ -564,84 +801,122 @@ pub mod pallet { /// Return the total number of accounts that are eligible collators (candidates and /// invulnerables). - fn eligible_collators() -> usize { - Candidates::::decode_len() + fn eligible_collators() -> u32 { + CandidateList::::decode_len() .unwrap_or_default() .saturating_add(Invulnerables::::decode_len().unwrap_or_default()) + .try_into() + .unwrap_or(u32::MAX) } /// Removes a candidate if they exist and sends them back their deposit. fn try_remove_candidate( who: &T::AccountId, remove_last_authored: bool, - ) -> Result { - let current_count = - >::try_mutate(|candidates| -> Result { - let index = candidates - .iter() - .position(|candidate| candidate.who == *who) - .ok_or(Error::::NotCandidate)?; - let candidate = candidates.remove(index); - T::Currency::unreserve(who, candidate.deposit); - if remove_last_authored { - >::remove(who.clone()) - }; - Ok(candidates.len()) - })?; + ) -> Result<(), DispatchError> { + CandidateList::::try_mutate(|candidates| -> Result<(), DispatchError> { + let idx = candidates + .iter() + .position(|candidate_info| candidate_info.who == *who) + .ok_or(Error::::NotCandidate)?; + let deposit = candidates[idx].deposit; + T::Currency::unreserve(who, deposit); + candidates.remove(idx); + if remove_last_authored { + LastAuthoredBlock::::remove(who.clone()) + }; + Ok(()) + })?; Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() }); - Ok(current_count) + Ok(()) } /// Assemble the current set of candidates and invulnerables into the next collator set. /// /// This is done on the fly, as frequent as we are told to do so, as the session manager. - pub fn assemble_collators( - candidates: BoundedVec, - ) -> Vec { - let mut collators = Self::invulnerables().to_vec(); - collators.extend(candidates); + pub fn assemble_collators() -> Vec { + // Casting `u32` to `usize` should be safe on all machines running this. + let desired_candidates = DesiredCandidates::::get() as usize; + let mut collators = Invulnerables::::get().to_vec(); + collators.extend( + CandidateList::::get() + .iter() + .rev() + .cloned() + .take(desired_candidates) + .map(|candidate_info| candidate_info.who), + ); collators } /// Kicks out candidates that did not produce a block in the kick threshold and refunds /// their deposits. - pub fn kick_stale_candidates( - candidates: BoundedVec>, T::MaxCandidates>, - ) -> BoundedVec { + /// + /// Return value is the number of candidates left in the list. + pub fn kick_stale_candidates(candidates: impl IntoIterator) -> u32 { let now = frame_system::Pallet::::block_number(); let kick_threshold = T::KickThreshold::get(); let min_collators = T::MinEligibleCollators::get(); candidates .into_iter() .filter_map(|c| { - let last_block = >::get(c.who.clone()); + let last_block = LastAuthoredBlock::::get(c.clone()); let since_last = now.saturating_sub(last_block); - let is_invulnerable = Self::invulnerables().contains(&c.who); + let is_invulnerable = Invulnerables::::get().contains(&c); let is_lazy = since_last >= kick_threshold; if is_invulnerable { - // They are invulnerable. No reason for them to be in Candidates also. + // They are invulnerable. No reason for them to be in `CandidateList` also. // We don't even care about the min collators here, because an Account // should not be a collator twice. - let _ = Self::try_remove_candidate(&c.who, false); + let _ = Self::try_remove_candidate(&c, false); None } else { - if Self::eligible_collators() <= min_collators as usize || !is_lazy { + if Self::eligible_collators() <= min_collators || !is_lazy { // Either this is a good collator (not lazy) or we are at the minimum // that the system needs. They get to stay. - Some(c.who) + Some(c) } else { // This collator has not produced a block recently enough. Bye bye. - let _ = Self::try_remove_candidate(&c.who, true); + let _ = Self::try_remove_candidate(&c, true); None } } }) - .collect::>() + .count() .try_into() .expect("filter_map operation can't result in a bounded vec larger than its original; qed") } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before or after each state transition of this pallet. + /// + /// # Invariants + /// + /// ## `DesiredCandidates` + /// + /// * The current desired candidate count should not exceed the candidate list capacity. + /// * The number of selected candidates together with the invulnerables must be greater than + /// or equal to the minimum number of eligible collators. + #[cfg(any(test, feature = "try-runtime"))] + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + let desired_candidates = DesiredCandidates::::get(); + + frame_support::ensure!( + desired_candidates <= T::MaxCandidates::get(), + "Shouldn't demand more candidates than the pallet config allows." + ); + + frame_support::ensure!( + desired_candidates.saturating_add(T::MaxInvulnerables::get()) >= + T::MinEligibleCollators::get(), + "Invulnerable set together with desired candidates should be able to meet the collator quota." + ); + + Ok(()) + } } /// Keep track of number of authored blocks per authority, uncles are counted as well since @@ -659,7 +934,7 @@ pub mod pallet { // `reward` is half of pot account minus ED, this should never fail. let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive); debug_assert!(_success.is_ok()); - >::insert(author, frame_system::Pallet::::block_number()); + LastAuthoredBlock::::insert(author, frame_system::Pallet::::block_number()); frame_system::Pallet::::register_extra_weight_unchecked( T::WeightInfo::note_author(), @@ -677,14 +952,23 @@ pub mod pallet { >::block_number(), ); - let candidates = Self::candidates(); - let candidates_len_before = candidates.len(); - let active_candidates = Self::kick_stale_candidates(candidates); - let removed = candidates_len_before - active_candidates.len(); - let result = Self::assemble_collators(active_candidates); + // The `expect` below is safe because the list is a `BoundedVec` with a max size of + // `T::MaxCandidates`, which is a `u32`. When `decode_len` returns `Some(len)`, `len` + // must be valid and at most `u32::MAX`, which must always be able to convert to `u32`. + let candidates_len_before: u32 = CandidateList::::decode_len() + .unwrap_or_default() + .try_into() + .expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed"); + let active_candidates_count = Self::kick_stale_candidates( + CandidateList::::get() + .iter() + .map(|candidate_info| candidate_info.who.clone()), + ); + let removed = candidates_len_before.saturating_sub(active_candidates_count); + let result = Self::assemble_collators(); frame_system::Pallet::::register_extra_weight_unchecked( - T::WeightInfo::new_session(candidates_len_before as u32, removed as u32), + T::WeightInfo::new_session(candidates_len_before, removed), DispatchClass::Mandatory, ); Some(result) diff --git a/cumulus/pallets/collator-selection/src/migration.rs b/cumulus/pallets/collator-selection/src/migration.rs index 58b4cc5b06a1ab7da17b31ad3c457fd992c59136..5dc2fba4279a9b04dc9f1866fb2c36ea1abde80c 100644 --- a/cumulus/pallets/collator-selection/src/migration.rs +++ b/cumulus/pallets/collator-selection/src/migration.rs @@ -31,10 +31,10 @@ pub mod v1 { pub struct MigrateToV1(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { - let onchain_version = Pallet::::on_chain_storage_version(); - if onchain_version == 0 { + let on_chain_version = Pallet::::on_chain_storage_version(); + if on_chain_version == 0 { let invulnerables_len = Invulnerables::::get().to_vec().len(); - >::mutate(|invulnerables| { + Invulnerables::::mutate(|invulnerables| { invulnerables.sort(); }); @@ -45,7 +45,7 @@ pub mod v1 { invulnerables_len, ); // Similar complexity to `set_invulnerables` (put storage value) - // Plus 1 read for length, 1 read for `onchain_version`, 1 write to put version + // Plus 1 read for length, 1 read for `on_chain_version`, 1 write to put version T::WeightInfo::set_invulnerables(invulnerables_len as u32) .saturating_add(T::DbWeight::get().reads_writes(2, 1)) } else { @@ -83,8 +83,8 @@ pub mod v1 { "after migration, there should be the same number of invulnerables" ); - let onchain_version = Pallet::::on_chain_storage_version(); - frame_support::ensure!(onchain_version >= 1, "must_upgrade"); + let on_chain_version = Pallet::::on_chain_storage_version(); + frame_support::ensure!(on_chain_version >= 1, "must_upgrade"); Ok(()) } diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs index 44d531c971ee3bee08f572ac797a93d1759a5cfa..4a440dfe1e92f26685415d260e0af9b1eb56a9a4 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)] impl system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -92,8 +93,8 @@ impl pallet_balances::Config for Test { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -124,7 +125,6 @@ impl pallet_aura::Config for Test { type MaxAuthorities = ConstU32<100_000>; type DisabledValidators = (); type AllowMultipleBlocksPerSlot = ConstBool; - #[cfg(feature = "experimental")] type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; } diff --git a/cumulus/pallets/collator-selection/src/tests.rs b/cumulus/pallets/collator-selection/src/tests.rs index d4dae513df375145c35a83d7af274e581a94a60b..e8b2a4e146a2f95a1996e81f185f46c853f869a7 100644 --- a/cumulus/pallets/collator-selection/src/tests.rs +++ b/cumulus/pallets/collator-selection/src/tests.rs @@ -14,7 +14,10 @@ // limitations under the License. use crate as collator_selection; -use crate::{mock::*, CandidateInfo, Error}; +use crate::{ + mock::*, CandidacyBond, CandidateInfo, CandidateList, DesiredCandidates, Error, Invulnerables, + LastAuthoredBlock, +}; use frame_support::{ assert_noop, assert_ok, traits::{Currency, OnInitialize}, @@ -25,12 +28,12 @@ use sp_runtime::{testing::UintAuthorityId, traits::BadOrigin, BuildStorage}; #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::desired_candidates(), 2); - assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); - assert!(CollatorSelection::candidates().is_empty()); + assert_eq!(CandidateList::::get().iter().count(), 0); // genesis should sort input - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); }); } @@ -43,7 +46,7 @@ fn it_should_set_invulnerables() { new_set.clone() )); new_set.sort(); - assert_eq!(CollatorSelection::invulnerables(), new_set); + assert_eq!(Invulnerables::::get(), new_set); // cannot set with non-root. assert_noop!( @@ -56,7 +59,7 @@ fn it_should_set_invulnerables() { #[test] fn it_should_set_invulnerables_even_with_some_invalid() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); let new_with_invalid = vec![1, 4, 3, 42, 2]; assert_ok!(CollatorSelection::set_invulnerables( @@ -65,7 +68,7 @@ fn it_should_set_invulnerables_even_with_some_invalid() { )); // should succeed and order them, but not include 42 - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3, 4]); + assert_eq!(Invulnerables::::get(), vec![1, 2, 3, 4]); }); } @@ -73,7 +76,7 @@ fn it_should_set_invulnerables_even_with_some_invalid() { fn add_invulnerable_works() { new_test_ext().execute_with(|| { initialize_to_block(1); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); let new = 3; // function runs @@ -93,7 +96,7 @@ fn add_invulnerable_works() { ); // new element is now part of the invulnerables list - assert!(CollatorSelection::invulnerables().to_vec().contains(&new)); + assert!(Invulnerables::::get().to_vec().contains(&new)); // cannot add with non-root assert_noop!(CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), new), BadOrigin); @@ -113,7 +116,7 @@ fn add_invulnerable_works() { #[test] fn invulnerable_limit_works() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); // MaxInvulnerables: u32 = 20 for ii in 3..=21 { @@ -140,7 +143,7 @@ fn invulnerable_limit_works() { } } let expected: Vec = (1..=20).collect(); - assert_eq!(CollatorSelection::invulnerables(), expected); + assert_eq!(Invulnerables::::get(), expected); // Cannot set too many Invulnerables let too_many_invulnerables: Vec = (1..=21).collect(); @@ -151,7 +154,7 @@ fn invulnerable_limit_works() { ), Error::::TooManyInvulnerables ); - assert_eq!(CollatorSelection::invulnerables(), expected); + assert_eq!(Invulnerables::::get(), expected); }); } @@ -159,7 +162,7 @@ fn invulnerable_limit_works() { fn remove_invulnerable_works() { new_test_ext().execute_with(|| { initialize_to_block(1); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); assert_ok!(CollatorSelection::add_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -170,7 +173,7 @@ fn remove_invulnerable_works() { 3 )); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3, 4]); + assert_eq!(Invulnerables::::get(), vec![1, 2, 3, 4]); assert_ok!(CollatorSelection::remove_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -180,7 +183,7 @@ fn remove_invulnerable_works() { System::assert_last_event(RuntimeEvent::CollatorSelection( crate::Event::InvulnerableRemoved { account_id: 2 }, )); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 3, 4]); + assert_eq!(Invulnerables::::get(), vec![1, 3, 4]); // cannot remove invulnerable not in the list assert_noop!( @@ -200,10 +203,11 @@ fn remove_invulnerable_works() { fn candidate_to_invulnerable_works() { new_test_ext().execute_with(|| { initialize_to_block(1); - assert_eq!(CollatorSelection::desired_candidates(), 2); - assert_eq!(CollatorSelection::candidacy_bond(), 10); - assert_eq!(CollatorSelection::candidates(), Vec::new()); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); assert_eq!(Balances::free_balance(3), 100); assert_eq!(Balances::free_balance(4), 100); @@ -224,9 +228,9 @@ fn candidate_to_invulnerable_works() { System::assert_has_event(RuntimeEvent::CollatorSelection( crate::Event::InvulnerableAdded { account_id: 3 }, )); - assert!(CollatorSelection::invulnerables().to_vec().contains(&3)); + assert!(Invulnerables::::get().to_vec().contains(&3)); assert_eq!(Balances::free_balance(3), 100); - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(CandidateList::::get().iter().count(), 1); assert_ok!(CollatorSelection::add_invulnerable( RuntimeOrigin::signed(RootAccount::get()), @@ -238,9 +242,10 @@ fn candidate_to_invulnerable_works() { System::assert_has_event(RuntimeEvent::CollatorSelection( crate::Event::InvulnerableAdded { account_id: 4 }, )); - assert!(CollatorSelection::invulnerables().to_vec().contains(&4)); + assert!(Invulnerables::::get().to_vec().contains(&4)); assert_eq!(Balances::free_balance(4), 100); - assert_eq!(CollatorSelection::candidates().len(), 0); + + assert_eq!(CandidateList::::get().iter().count(), 0); }); } @@ -248,14 +253,14 @@ fn candidate_to_invulnerable_works() { fn set_desired_candidates_works() { new_test_ext().execute_with(|| { // given - assert_eq!(CollatorSelection::desired_candidates(), 2); + assert_eq!(DesiredCandidates::::get(), 2); // can set assert_ok!(CollatorSelection::set_desired_candidates( RuntimeOrigin::signed(RootAccount::get()), 7 )); - assert_eq!(CollatorSelection::desired_candidates(), 7); + assert_eq!(DesiredCandidates::::get(), 7); // rejects bad origin assert_noop!( @@ -266,42 +271,227 @@ fn set_desired_candidates_works() { } #[test] -fn set_candidacy_bond() { +fn set_candidacy_bond_empty_candidate_list() { new_test_ext().execute_with(|| { // given - assert_eq!(CollatorSelection::candidacy_bond(), 10); + assert_eq!(CandidacyBond::::get(), 10); + assert!(CandidateList::::get().is_empty()); - // can set + // can decrease without candidates assert_ok!(CollatorSelection::set_candidacy_bond( RuntimeOrigin::signed(RootAccount::get()), 7 )); - assert_eq!(CollatorSelection::candidacy_bond(), 7); + assert_eq!(CandidacyBond::::get(), 7); + assert!(CandidateList::::get().is_empty()); // rejects bad origin. assert_noop!(CollatorSelection::set_candidacy_bond(RuntimeOrigin::signed(1), 8), BadOrigin); + + // can increase without candidates + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 20 + )); + assert!(CandidateList::::get().is_empty()); + assert_eq!(CandidacyBond::::get(), 20); }); } #[test] -fn cannot_register_candidate_if_too_many() { +fn set_candidacy_bond_with_one_candidate() { new_test_ext().execute_with(|| { - // reset desired candidates: - >::put(0); + // given + assert_eq!(CandidacyBond::::get(), 10); + assert!(CandidateList::::get().is_empty()); - // can't accept anyone anymore. - assert_noop!( - CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)), - Error::::TooManyCandidates, + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_eq!(CandidateList::::get(), vec![candidate_3.clone()]); + + // can decrease with one candidate + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 7 + )); + assert_eq!(CandidacyBond::::get(), 7); + assert_eq!(CandidateList::::get(), vec![candidate_3.clone()]); + + // can increase up to initial deposit + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 10 + )); + assert_eq!(CandidacyBond::::get(), 10); + assert_eq!(CandidateList::::get(), vec![candidate_3.clone()]); + + // can increase past initial deposit, should kick existing candidate + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 20 + )); + assert!(CandidateList::::get().is_empty()); + }); +} + +#[test] +fn set_candidacy_bond_with_many_candidates_same_deposit() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CandidacyBond::::get(), 10); + assert!(CandidateList::::get().is_empty()); + + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 10 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 10 }; + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_eq!( + CandidateList::::get(), + vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()] ); - // reset desired candidates: - >::put(1); + // can decrease with multiple candidates + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 7 + )); + assert_eq!(CandidacyBond::::get(), 7); + assert_eq!( + CandidateList::::get(), + vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()] + ); + + // can increase up to initial deposit + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 10 + )); + assert_eq!(CandidacyBond::::get(), 10); + assert_eq!( + CandidateList::::get(), + vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()] + ); + + // can increase past initial deposit, should kick existing candidates + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 20 + )); + assert!(CandidateList::::get().is_empty()); + }); +} + +#[test] +fn set_candidacy_bond_with_many_candidates_different_deposits() { + new_test_ext().execute_with(|| { + // given + assert_eq!(CandidacyBond::::get(), 10); + assert!(CandidateList::::get().is_empty()); + + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 20 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 30 }; + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 30)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 20)); + assert_eq!( + CandidateList::::get(), + vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()] + ); + + // can decrease with multiple candidates + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 7 + )); + assert_eq!(CandidacyBond::::get(), 7); + assert_eq!( + CandidateList::::get(), + vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()] + ); + // can increase up to initial deposit + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 10 + )); + assert_eq!(CandidacyBond::::get(), 10); + assert_eq!( + CandidateList::::get(), + vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()] + ); + + // can increase to 4's deposit, should kick 3 + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 20 + )); + assert_eq!(CandidacyBond::::get(), 20); + assert_eq!(CandidateList::::get(), vec![candidate_4.clone(), candidate_5.clone()]); + + // can increase past 4's deposit, should kick 4 + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 25 + )); + assert_eq!(CandidacyBond::::get(), 25); + assert_eq!(CandidateList::::get(), vec![candidate_5.clone()]); + + // lowering the minimum deposit should have no effect + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 5 + )); + assert_eq!(CandidacyBond::::get(), 5); + assert_eq!(CandidateList::::get(), vec![candidate_5.clone()]); + + // add 3 and 4 back but with higher deposits than minimum + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 20)); + assert_eq!( + CandidateList::::get(), + vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()] + ); + + // can increase the deposit above the current max in the list, all candidates should be + // kicked + assert_ok!(CollatorSelection::set_candidacy_bond( + RuntimeOrigin::signed(RootAccount::get()), + 40 + )); + assert_eq!(CandidacyBond::::get(), 40); + assert!(CandidateList::::get().is_empty()); + }); +} + +#[test] +fn cannot_register_candidate_if_too_many() { + new_test_ext().execute_with(|| { + >::put(1); + + // MaxCandidates: u32 = 20 + // Aside from 3, 4, and 5, create enough accounts to have 21 potential + // candidates. + for i in 6..=23 { + Balances::make_free_balance_be(&i, 100); + let key = MockSessionKeys { aura: UintAuthorityId(i) }; + Session::set_keys(RuntimeOrigin::signed(i).into(), key, Vec::new()).unwrap(); + } + + for c in 3..=22 { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(c))); + } - // but no more assert_noop!( - CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)), + CollatorSelection::register_as_candidate(RuntimeOrigin::signed(23)), Error::::TooManyCandidates, ); }) @@ -310,8 +500,8 @@ fn cannot_register_candidate_if_too_many() { #[test] fn cannot_unregister_candidate_if_too_few() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::candidates(), Vec::new()); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); assert_ok!(CollatorSelection::remove_invulnerable( RuntimeOrigin::signed(RootAccount::get()), 1 @@ -342,7 +532,7 @@ fn cannot_unregister_candidate_if_too_few() { #[test] fn cannot_register_as_candidate_if_invulnerable() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(Invulnerables::::get(), vec![1, 2]); // can't 1 because it is invulnerable. assert_noop!( @@ -368,9 +558,13 @@ fn cannot_register_dupe_candidate() { new_test_ext().execute_with(|| { // can add 3 as candidate assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + // tuple of (id, deposit). let addition = CandidateInfo { who: 3, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![addition]); - assert_eq!(CollatorSelection::last_authored_block(3), 10); + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![addition] + ); + assert_eq!(LastAuthoredBlock::::get(3), 10); assert_eq!(Balances::free_balance(3), 90); // but no more @@ -402,10 +596,11 @@ fn cannot_register_as_candidate_if_poor() { fn register_as_candidate_works() { new_test_ext().execute_with(|| { // given - assert_eq!(CollatorSelection::desired_candidates(), 2); - assert_eq!(CollatorSelection::candidacy_bond(), 10); - assert_eq!(CollatorSelection::candidates(), Vec::new()); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); // take two endowed, non-invulnerables accounts. assert_eq!(Balances::free_balance(3), 100); @@ -417,136 +612,883 @@ fn register_as_candidate_works() { assert_eq!(Balances::free_balance(3), 90); assert_eq!(Balances::free_balance(4), 90); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_eq!(CandidateList::::get().iter().count(), 2); }); } #[test] -fn leave_intent() { +fn cannot_take_candidate_slot_if_invulnerable() { new_test_ext().execute_with(|| { - // register a candidate. - assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); - assert_eq!(Balances::free_balance(3), 90); - - // register too so can leave above min candidates - assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); - assert_eq!(Balances::free_balance(5), 90); + assert_eq!(Invulnerables::::get(), vec![1, 2]); - // cannot leave if not candidate. + // can't 1 because it is invulnerable. assert_noop!( - CollatorSelection::leave_intent(RuntimeOrigin::signed(4)), - Error::::NotCandidate + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(1), 50u64.into(), 2), + Error::::AlreadyInvulnerable, ); - - // bond is returned - assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3))); - assert_eq!(Balances::free_balance(3), 100); - assert_eq!(CollatorSelection::last_authored_block(3), 0); - }); + }) } #[test] -fn authorship_event_handler() { +fn cannot_take_candidate_slot_if_keys_not_registered() { new_test_ext().execute_with(|| { - // put 100 in the pot + 5 for ED - Balances::make_free_balance_be(&CollatorSelection::account_id(), 105); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(42), 50u64.into(), 3), + Error::::ValidatorNotRegistered + ); + }) +} - // 4 is the default author. - assert_eq!(Balances::free_balance(4), 100); +#[test] +fn cannot_take_candidate_slot_if_duplicate() { + new_test_ext().execute_with(|| { + // can add 3 as candidate + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); - // triggers `note_author` - Authorship::on_initialize(1); + // tuple of (id, deposit). + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 10 }; + let actual_candidates = CandidateList::::get().iter().cloned().collect::>(); + assert_eq!(actual_candidates, vec![candidate_4, candidate_3]); + assert_eq!(LastAuthoredBlock::::get(3), 10); + assert_eq!(LastAuthoredBlock::::get(4), 10); + assert_eq!(Balances::free_balance(3), 90); - let collator = CandidateInfo { who: 4, deposit: 10 }; + // but no more + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(3), 50u64.into(), 4), + Error::::AlreadyCandidate, + ); + }) +} - assert_eq!(CollatorSelection::candidates(), vec![collator]); - assert_eq!(CollatorSelection::last_authored_block(4), 0); +#[test] +fn cannot_take_candidate_slot_if_target_invalid() { + new_test_ext().execute_with(|| { + // can add 3 as candidate + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + // tuple of (id, deposit). + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![candidate_3] + ); + assert_eq!(LastAuthoredBlock::::get(3), 10); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 100); - // half of the pot goes to the collator who's the author (4 in tests). - assert_eq!(Balances::free_balance(4), 140); - // half + ED stays. - assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55); - }); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 50u64.into(), 5), + Error::::TargetIsNotCandidate, + ); + }) } #[test] -fn fees_edgecases() { +fn cannot_take_candidate_slot_if_poor() { new_test_ext().execute_with(|| { - // Nothing panics, no reward when no ED in balance - Authorship::on_initialize(1); - // put some money into the pot at ED - Balances::make_free_balance_be(&CollatorSelection::account_id(), 5); - // 4 is the default author. - assert_eq!(Balances::free_balance(4), 100); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); - // triggers `note_author` - Authorship::on_initialize(1); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(33), 0); - let collator = CandidateInfo { who: 4, deposit: 10 }; + // works + assert_ok!(CollatorSelection::take_candidate_slot( + RuntimeOrigin::signed(3), + 20u64.into(), + 4 + )); - assert_eq!(CollatorSelection::candidates(), vec![collator]); - assert_eq!(CollatorSelection::last_authored_block(4), 0); - // Nothing received - assert_eq!(Balances::free_balance(4), 90); - // all fee stays - assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5); + // poor + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(33), 30u64.into(), 3), + BalancesError::::InsufficientBalance, + ); }); } #[test] -fn session_management_works() { +fn cannot_take_candidate_slot_if_insufficient_deposit() { new_test_ext().execute_with(|| { - initialize_to_block(1); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60u64.into())); + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 100); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 5u64.into(), 3), + Error::::InsufficientBond, + ); + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 100); + }); +} - assert_eq!(SessionChangeBlock::get(), 0); - assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); +#[test] +fn cannot_take_candidate_slot_if_deposit_less_than_target() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60u64.into())); + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 100); + assert_noop!( + CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 20u64.into(), 3), + Error::::InsufficientBond, + ); + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 100); + }); +} - initialize_to_block(4); +#[test] +fn take_candidate_slot_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); - assert_eq!(SessionChangeBlock::get(), 0); - assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); - // add a new collator assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); - // session won't see this. - assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); - // but we have a new candidate. - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); - initialize_to_block(10); - assert_eq!(SessionChangeBlock::get(), 10); - // pallet-session has 1 session delay; current validators are the same. - assert_eq!(Session::validators(), vec![1, 2]); - // queued ones are changed, and now we have 3. - assert_eq!(Session::queued_keys().len(), 3); - // session handlers (aura, et. al.) cannot see this yet. - assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + assert_eq!(CandidateList::::get().iter().count(), 3); - initialize_to_block(20); - assert_eq!(SessionChangeBlock::get(), 20); - // changed are now reflected to session handlers. - assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]); + Balances::make_free_balance_be(&6, 100); + let key = MockSessionKeys { aura: UintAuthorityId(6) }; + Session::set_keys(RuntimeOrigin::signed(6).into(), key, Vec::new()).unwrap(); + + assert_ok!(CollatorSelection::take_candidate_slot( + RuntimeOrigin::signed(6), + 50u64.into(), + 4 + )); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 90); + assert_eq!(Balances::free_balance(6), 50); + + // tuple of (id, deposit). + let candidate_3 = CandidateInfo { who: 3, deposit: 10 }; + let candidate_6 = CandidateInfo { who: 6, deposit: 50 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 10 }; + let mut actual_candidates = + CandidateList::::get().iter().cloned().collect::>(); + actual_candidates.sort_by(|info_1, info_2| info_1.deposit.cmp(&info_2.deposit)); + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![candidate_5, candidate_3, candidate_6] + ); }); } #[test] -fn kick_mechanism() { +fn increase_candidacy_bond_non_candidate_account() { new_test_ext().execute_with(|| { - // add a new collator + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(5), 20), + Error::::NotCandidate + ); + }); +} + +#[test] +fn increase_candidacy_bond_insufficient_balance() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(3), 110), + BalancesError::::InsufficientBalance + ); + + assert_eq!(Balances::free_balance(3), 90); + }); +} + +#[test] +fn increase_candidacy_bond_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40)); + + assert_eq!(CandidateList::::get().iter().count(), 3); + assert_eq!(Balances::free_balance(3), 80); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 40)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 60)); + + assert_eq!(CandidateList::::get().iter().count(), 3); + assert_eq!(Balances::free_balance(3), 60); + assert_eq!(Balances::free_balance(4), 40); + assert_eq!(Balances::free_balance(5), 60); + }); +} + +#[test] +fn decrease_candidacy_bond_non_candidate_account() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + + assert_eq!(Balances::free_balance(5), 100); + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10), + Error::::NotCandidate + ); + assert_eq!(Balances::free_balance(5), 100); + }); +} + +#[test] +fn decrease_candidacy_bond_insufficient_funds() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 60)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60)); + + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 40); + assert_eq!(Balances::free_balance(5), 40); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(3), 0), + Error::::DepositTooLow + ); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(4), 5), + Error::::DepositTooLow + ); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(5), 9), + Error::::DepositTooLow + ); + + assert_eq!(Balances::free_balance(3), 40); + assert_eq!(Balances::free_balance(4), 40); + assert_eq!(Balances::free_balance(5), 40); + }); +} + +#[test] +fn decrease_candidacy_bond_occupying_top_slot() { + new_test_ext().execute_with(|| { + assert_eq!(DesiredCandidates::::get(), 2); + // Register 3 candidates. + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + // And update their bids. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 30u64.into())); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30u64.into())); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60u64.into())); + + // tuple of (id, deposit). + let candidate_3 = CandidateInfo { who: 3, deposit: 30 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 30 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 60 }; + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![candidate_4, candidate_3, candidate_5] + ); + + // Candidates 5 and 3 can't decrease their deposits because they are the 2 top candidates. + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(5), 29), + Error::::InvalidUnreserve, + ); + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(3), 29), + Error::::InvalidUnreserve, + ); + // But candidate 4 should have be able to decrease the deposit up to the minimum. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 29u64.into())); + + // Make candidate 4 outbid candidate 3, taking their spot as the second highest bid. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 35u64.into())); + + // tuple of (id, deposit). + let candidate_3 = CandidateInfo { who: 3, deposit: 30 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 35 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 60 }; + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![candidate_3, candidate_4, candidate_5] + ); + + // Now candidates 5 and 4 are the 2 top candidates, so they can't decrease their deposits. + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(5), 34), + Error::::InvalidUnreserve, + ); + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(4), 34), + Error::::InvalidUnreserve, + ); + // Candidate 3 should have be able to decrease the deposit up to the minimum now that + // they've fallen out of the top spots. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10u64.into())); + }); +} + +#[test] +fn decrease_candidacy_bond_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40)); + + assert_eq!(CandidateList::::get().iter().count(), 3); + assert_eq!(Balances::free_balance(3), 80); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10)); + + assert_eq!(CandidateList::::get().iter().count(), 3); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); + }); +} + +#[test] +fn update_candidacy_bond_with_identical_amount() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(Balances::free_balance(4), 90); + assert_eq!(Balances::free_balance(5), 90); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40)); + + assert_eq!(CandidateList::::get().iter().count(), 3); + assert_eq!(Balances::free_balance(3), 80); + assert_eq!(Balances::free_balance(4), 70); + assert_eq!(Balances::free_balance(5), 60); + + assert_noop!( + CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20), + Error::::IdenticalDeposit + ); + assert_eq!(Balances::free_balance(3), 80); + }); +} + +#[test] +fn candidate_list_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(CandidacyBond::::get(), 10); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); + + // take three endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(4), 100); + assert_eq!(Balances::free_balance(5), 100); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 20)); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 30)); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 25)); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10)); + + let candidate_3 = CandidateInfo { who: 3, deposit: 30 }; + let candidate_4 = CandidateInfo { who: 4, deposit: 25 }; + let candidate_5 = CandidateInfo { who: 5, deposit: 10 }; + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![candidate_5, candidate_4, candidate_3] + ); + }); +} + +#[test] +fn leave_intent() { + new_test_ext().execute_with(|| { + // register a candidate. + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 90); + + // register too so can leave above min candidates + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_eq!(Balances::free_balance(5), 90); + + // cannot leave if not candidate. + assert_noop!( + CollatorSelection::leave_intent(RuntimeOrigin::signed(4)), + Error::::NotCandidate + ); + + // bond is returned + assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(LastAuthoredBlock::::get(3), 0); + }); +} + +#[test] +fn authorship_event_handler() { + new_test_ext().execute_with(|| { + // put 100 in the pot + 5 for ED + Balances::make_free_balance_be(&CollatorSelection::account_id(), 105); + + // 4 is the default author. + assert_eq!(Balances::free_balance(4), 100); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + // triggers `note_author` + Authorship::on_initialize(1); + + // tuple of (id, deposit). + let collator = CandidateInfo { who: 4, deposit: 10 }; + + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![collator] + ); + assert_eq!(LastAuthoredBlock::::get(4), 0); + + // half of the pot goes to the collator who's the author (4 in tests). + assert_eq!(Balances::free_balance(4), 140); + // half + ED stays. + assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55); + }); +} + +#[test] +fn fees_edgecases() { + new_test_ext().execute_with(|| { + // Nothing panics, no reward when no ED in balance + Authorship::on_initialize(1); + // put some money into the pot at ED + Balances::make_free_balance_be(&CollatorSelection::account_id(), 5); + // 4 is the default author. + assert_eq!(Balances::free_balance(4), 100); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + // triggers `note_author` + Authorship::on_initialize(1); + + // tuple of (id, deposit). + let collator = CandidateInfo { who: 4, deposit: 10 }; + + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![collator] + ); + assert_eq!(LastAuthoredBlock::::get(4), 0); + // Nothing received + assert_eq!(Balances::free_balance(4), 90); + // all fee stays + assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5); + }); +} + +#[test] +fn session_management_single_candidate() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + // add a new collator + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 1); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 3. + assert_eq!(Session::queued_keys().len(), 3); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]); + }); +} + +#[test] +fn session_management_max_candidates() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); + }); +} + +#[test] +fn session_management_increase_bid_with_list_update() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5, 3]); + }); +} + +#[test] +fn session_management_candidate_list_eager_sort() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 3); + initialize_to_block(10); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5, 3]); + }); +} + +#[test] +fn session_management_reciprocal_outbidding() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60)); + + initialize_to_block(5); + + // candidates 3 and 4 saw they were outbid and preemptively bid more + // than 5 in the next block. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 70)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 70)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]); + }); +} + +#[test] +fn session_management_decrease_bid_after_auction() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5))); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60)); + + initialize_to_block(5); + + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 70)); + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 70)); + + initialize_to_block(5); + + // candidate 5 saw it was outbid and wants to take back its bid, but + // not entirely so they still keep their place in the candidate list + // in case there is an opportunity in the future. + assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10)); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CandidateList::::get().iter().count(), 3); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 4. + assert_eq!(Session::queued_keys().len(), 4); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]); + }); +} + +#[test] +fn kick_mechanism() { + new_test_ext().execute_with(|| { + // add a new collator + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); + initialize_to_block(10); + assert_eq!(CandidateList::::get().iter().count(), 2); initialize_to_block(20); assert_eq!(SessionChangeBlock::get(), 20); // 4 authored this block, gets to stay 3 was kicked - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(CandidateList::::get().iter().count(), 1); // 3 will be kicked after 1 session delay assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); + // tuple of (id, deposit). let collator = CandidateInfo { who: 4, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![collator]); - assert_eq!(CollatorSelection::last_authored_block(4), 20); + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![collator] + ); + assert_eq!(LastAuthoredBlock::::get(4), 20); initialize_to_block(30); // 3 gets kicked after 1 session delay assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]); @@ -559,8 +1501,9 @@ fn kick_mechanism() { fn should_not_kick_mechanism_too_few() { new_test_ext().execute_with(|| { // remove the invulnerables and add new collators 3 and 5 - assert_eq!(CollatorSelection::candidates(), Vec::new()); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + assert_eq!(CandidateList::::get().iter().count(), 0); + assert_eq!(Invulnerables::::get(), vec![1, 2]); assert_ok!(CollatorSelection::remove_invulnerable( RuntimeOrigin::signed(RootAccount::get()), 1 @@ -573,30 +1516,34 @@ fn should_not_kick_mechanism_too_few() { )); initialize_to_block(10); - assert_eq!(CollatorSelection::candidates().len(), 2); + assert_eq!(CandidateList::::get().iter().count(), 2); initialize_to_block(20); assert_eq!(SessionChangeBlock::get(), 20); // 4 authored this block, 3 is kicked, 5 stays because of too few collators - assert_eq!(CollatorSelection::candidates().len(), 1); + assert_eq!(CandidateList::::get().iter().count(), 1); // 3 will be kicked after 1 session delay assert_eq!(SessionHandlerCollators::get(), vec![3, 5]); - let collator = CandidateInfo { who: 5, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![collator]); - assert_eq!(CollatorSelection::last_authored_block(4), 20); + // tuple of (id, deposit). + let collator = CandidateInfo { who: 3, deposit: 10 }; + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![collator] + ); + assert_eq!(LastAuthoredBlock::::get(4), 20); initialize_to_block(30); // 3 gets kicked after 1 session delay - assert_eq!(SessionHandlerCollators::get(), vec![5]); + assert_eq!(SessionHandlerCollators::get(), vec![3]); // kicked collator gets funds back - assert_eq!(Balances::free_balance(3), 100); + assert_eq!(Balances::free_balance(5), 100); }); } #[test] fn should_kick_invulnerables_from_candidates_on_session_change() { new_test_ext().execute_with(|| { - assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(CandidateList::::get().iter().count(), 0); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3))); assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4))); assert_eq!(Balances::free_balance(3), 90); @@ -606,18 +1553,23 @@ fn should_kick_invulnerables_from_candidates_on_session_change() { vec![1, 2, 3] )); + // tuple of (id, deposit). let collator_3 = CandidateInfo { who: 3, deposit: 10 }; let collator_4 = CandidateInfo { who: 4, deposit: 10 }; - assert_eq!(CollatorSelection::candidates(), vec![collator_3, collator_4.clone()]); - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]); + let actual_candidates = CandidateList::::get().iter().cloned().collect::>(); + assert_eq!(actual_candidates, vec![collator_4.clone(), collator_3]); + assert_eq!(Invulnerables::::get(), vec![1, 2, 3]); // session change initialize_to_block(10); // 3 is removed from candidates - assert_eq!(CollatorSelection::candidates(), vec![collator_4]); + assert_eq!( + CandidateList::::get().iter().cloned().collect::>(), + vec![collator_4] + ); // but not from invulnerables - assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]); + assert_eq!(Invulnerables::::get(), vec![1, 2, 3]); // and it got its deposit back assert_eq!(Balances::free_balance(3), 100); }); diff --git a/cumulus/pallets/collator-selection/src/weights.rs b/cumulus/pallets/collator-selection/src/weights.rs index f8f86fb7dec2c08601eebe4d8b345a0f7c930b70..1c01ad6cd6fe8e8ed4bc02c3c2d6703eb2882df4 100644 --- a/cumulus/pallets/collator-selection/src/weights.rs +++ b/cumulus/pallets/collator-selection/src/weights.rs @@ -30,9 +30,11 @@ pub trait WeightInfo { fn add_invulnerable(_b: u32, _c: u32) -> Weight; fn remove_invulnerable(_b: u32) -> Weight; fn set_desired_candidates() -> Weight; - fn set_candidacy_bond() -> Weight; + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight; fn register_as_candidate(_c: u32) -> Weight; fn leave_intent(_c: u32) -> Weight; + fn update_bond(_c: u32) -> Weight; + fn take_candidate_slot(_c: u32) -> Weight; fn note_author() -> Weight; fn new_session(_c: u32, _r: u32) -> Weight; } @@ -49,7 +51,7 @@ impl WeightInfo for SubstrateWeight { fn set_desired_candidates() -> Weight { Weight::from_parts(16_363_000_u64, 0).saturating_add(T::DbWeight::get().writes(1_u64)) } - fn set_candidacy_bond() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { Weight::from_parts(16_840_000_u64, 0).saturating_add(T::DbWeight::get().writes(1_u64)) } fn register_as_candidate(c: u32) -> Weight { @@ -66,6 +68,20 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + fn update_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + fn take_candidate_slot(c: u32) -> Weight { + Weight::from_parts(71_196_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } fn note_author() -> Weight { Weight::from_parts(71_461_000_u64, 0) .saturating_add(T::DbWeight::get().reads(3_u64)) @@ -136,7 +152,7 @@ impl WeightInfo for () { fn set_desired_candidates() -> Weight { Weight::from_parts(16_363_000_u64, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) } - fn set_candidacy_bond() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { Weight::from_parts(16_840_000_u64, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) } fn register_as_candidate(c: u32) -> Weight { @@ -158,6 +174,20 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } + fn update_bond(c: u32) -> Weight { + Weight::from_parts(55_336_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + fn take_candidate_slot(c: u32) -> Weight { + Weight::from_parts(71_196_000_u64, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } fn new_session(r: u32, c: u32) -> Weight { Weight::from_parts(0_u64, 0) // Standard Error: 1_010_000 diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 2f3f660ea151d0c22eb2db893c61a85fcb4cb3dc..83ed994d04167607e3df54587c49e6b88576d4ec 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -1,47 +1,65 @@ [package] name = "cumulus-pallet-dmp-queue" -version = "0.1.0" +version = "0.7.0" authors.workspace = true edition.workspace = true +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"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ], default-features = false } -log = { version = "0.4.20", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -# Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +frame-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-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 } # Polkadot -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false} +xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } # Cumulus cumulus-primitives-core = { path = "../../primitives/core", default-features = false } [dev-dependencies] -sp-core = { path = "../../../substrate/primitives/core", default-features = false} -sp-version = { path = "../../../substrate/primitives/version", default-features = false} +sp-core = { path = "../../../substrate/primitives/core" } +sp-tracing = { path = "../../../substrate/primitives/tracing" } [features] -default = [ "std" ] +default = ["std"] + std = [ "codec/std", "cumulus-primitives-core/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", - "sp-version/std", "xcm/std", ] + +runtime-benchmarks = [ + "cumulus-primitives-core/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", diff --git a/cumulus/pallets/dmp-queue/src/benchmarking.rs b/cumulus/pallets/dmp-queue/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..91d1e0eab7e406a214088d8c0e47d03e20cd0286 --- /dev/null +++ b/cumulus/pallets/dmp-queue/src/benchmarking.rs @@ -0,0 +1,108 @@ +// 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 the `cumulus-pallet-dmp-queue`. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::*; + +use frame_benchmarking::v2::*; +use frame_support::{pallet_prelude::*, traits::Hooks}; +use sp_std::vec; + +#[benchmarks] +mod benchmarks { + use super::*; + + /// This benchmark uses the proper maximal message length. + #[benchmark] + fn on_idle_good_msg() { + let msg = vec![123; MaxDmpMessageLenOf::::get() as usize]; + + Pages::::insert(0, vec![(123, msg.clone())]); + PageIndex::::put(PageIndexData { begin_used: 0, end_used: 1, overweight_count: 0 }); + MigrationStatus::::set(MigrationState::StartedExport { next_begin_used: 0 }); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + + assert_last_event::(Event::Exported { page: 0 }.into()); + } + + /// This benchmark uses 64 KiB messages to emulate a large old message. + #[benchmark] + fn on_idle_large_msg() { + let msg = vec![123; 1 << 16]; + + Pages::::insert(0, vec![(123, msg.clone())]); + PageIndex::::put(PageIndexData { begin_used: 0, end_used: 1, overweight_count: 0 }); + MigrationStatus::::set(MigrationState::StartedExport { next_begin_used: 0 }); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + + assert_last_event::(Event::Exported { page: 0 }.into()); + } + + #[benchmark] + fn on_idle_overweight_good_msg() { + let msg = vec![123; MaxDmpMessageLenOf::::get() as usize]; + + Overweight::::insert(0, (123, msg.clone())); + PageIndex::::put(PageIndexData { begin_used: 0, end_used: 1, overweight_count: 1 }); + MigrationStatus::::set(MigrationState::StartedOverweightExport { + next_overweight_index: 0, + }); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + + assert_last_event::(Event::ExportedOverweight { index: 0 }.into()); + } + + #[benchmark] + fn on_idle_overweight_large_msg() { + let msg = vec![123; 1 << 16]; + + Overweight::::insert(0, (123, msg.clone())); + PageIndex::::put(PageIndexData { begin_used: 0, end_used: 1, overweight_count: 1 }); + MigrationStatus::::set(MigrationState::StartedOverweightExport { + next_overweight_index: 0, + }); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + + assert_last_event::(Event::ExportOverweightFailed { index: 0 }.into()); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Runtime); +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + let frame_system::EventRecord { event, .. } = events.last().expect("Event expected"); + assert_eq!(event, &system_event.into()); +} diff --git a/cumulus/pallets/dmp-queue/src/lib.rs b/cumulus/pallets/dmp-queue/src/lib.rs index eff4a625ef1b6ff88ae23cee2cc252af78921a67..79cc4bc895ec29b87eb95e629e0a9624fd7f62a5 100644 --- a/cumulus/pallets/dmp-queue/src/lib.rs +++ b/cumulus/pallets/dmp-queue/src/lib.rs @@ -1,915 +1,284 @@ // 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 . - -//! Pallet implementing a message queue for downward messages from the relay-chain. -//! Executes downward messages if there is enough weight available and schedules the rest for later -//! execution (by `on_idle` or another `handle_dmp_messages` call). Individual overweight messages -//! are scheduled into a separate queue that is only serviced by explicit extrinsic calls. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 used to implement a message queue for downward messages from the relay-chain. +//! +//! It is now deprecated and has been refactored to simply drain any remaining messages into +//! something implementing `HandleMessage`. It proceeds in the state of +//! [`MigrationState`] one by one by their listing in the source code. The pallet can be removed +//! from the runtime once `Completed` was emitted. #![cfg_attr(not(feature = "std"), no_std)] -pub mod migration; - -use codec::{Decode, DecodeLimit, Encode}; -use cumulus_primitives_core::{relay_chain::BlockNumber as RelayBlockNumber, DmpMessageHandler}; -use frame_support::{ - traits::EnsureOrigin, - weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}, -}; +use migration::*; pub use pallet::*; -use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; -use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH}; - -const DEFAULT_POV_SIZE: u64 = 64 * 1024; // 64 KB - -// Maximum amount of messages to process per block. This is a temporary measure until we properly -// account for proof size weights. -const MAX_MESSAGES_PER_BLOCK: u8 = 10; -// Maximum amount of messages that can exist in the overweight queue at any given time. -const MAX_OVERWEIGHT_MESSAGES: u32 = 1000; - -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ConfigData { - /// The maximum amount of weight any individual message may consume. Messages above this weight - /// go into the overweight queue and may only be serviced explicitly by the - /// `ExecuteOverweightOrigin`. - max_individual: Weight, -} -impl Default for ConfigData { - fn default() -> Self { - Self { - max_individual: Weight::from_parts( - 10u64 * WEIGHT_REF_TIME_PER_MILLIS, // 10 ms of execution time maximum by default - DEFAULT_POV_SIZE, // 64 KB of proof size by default - ), - } - } -} +mod benchmarking; +mod migration; +mod mock; +mod tests; +pub mod weights; -/// Information concerning our message pages. -#[derive(Copy, Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct PageIndexData { - /// The lowest used page index. - begin_used: PageCounter, - /// The lowest unused page index. - end_used: PageCounter, - /// The number of overweight messages ever recorded (and thus the lowest free index). - overweight_count: OverweightIndex, -} - -/// Simple type used to identify messages for the purpose of reporting events. Secure if and only -/// if the message content is unique. -pub type MessageId = XcmHash; +pub use weights::WeightInfo; -/// Index used to identify overweight messages. -pub type OverweightIndex = u64; - -/// Index used to identify normal pages. -pub type PageCounter = u32; +/// The maximal length of a DMP message. +pub type MaxDmpMessageLenOf = + <::DmpSink as frame_support::traits::HandleMessage>::MaxMessageLen; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, traits::HandleMessage, weights::WeightMeter}; use frame_system::pallet_prelude::*; + use sp_io::hashing::twox_128; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] - #[pallet::storage_version(migration::STORAGE_VERSION)] - #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); - /// The module configuration trait. #[pallet::config] pub trait Config: frame_system::Config { - /// The overarching event type. + /// The overarching event type of the runtime. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type XcmExecutor: ExecuteXcm; + /// The sink for all DMP messages that the lazy migration will use. + type DmpSink: HandleMessage; - /// Origin which is allowed to execute overweight messages. - type ExecuteOverweightOrigin: EnsureOrigin; + /// Weight info for this pallet (only needed for the lazy migration). + type WeightInfo: WeightInfo; } - /// The configuration. - #[pallet::storage] - pub(super) type Configuration = StorageValue<_, ConfigData, ValueQuery>; - - /// The page index. - #[pallet::storage] - pub(super) type PageIndex = StorageValue<_, PageIndexData, ValueQuery>; - - /// The queue pages. + /// The migration state of this pallet. #[pallet::storage] - pub(super) type Pages = - StorageMap<_, Blake2_128Concat, PageCounter, Vec<(RelayBlockNumber, Vec)>, ValueQuery>; - - /// The overweight messages. - #[pallet::storage] - pub(super) type Overweight = CountedStorageMap< - _, - Blake2_128Concat, - OverweightIndex, - (RelayBlockNumber, Vec), - OptionQuery, - >; - - #[pallet::error] - pub enum Error { - /// The message index given is unknown. - Unknown, - /// The amount of weight given is possibly not enough for executing the message. - OverLimit, + pub type MigrationStatus = StorageValue<_, MigrationState, ValueQuery>; + + /// The lazy-migration state of the pallet. + #[derive( + codec::Encode, codec::Decode, Debug, PartialEq, Eq, Clone, MaxEncodedLen, TypeInfo, + )] + pub enum MigrationState { + /// Migration has not started yet. + NotStarted, + /// The export of pages started. + StartedExport { + /// The next page that should be exported. + next_begin_used: PageCounter, + }, + /// The page export completed. + CompletedExport, + /// The export of overweight messages started. + StartedOverweightExport { + /// The next overweight index that should be exported. + next_overweight_index: u64, + }, + /// The export of overweight messages completed. + CompletedOverweightExport, + /// The storage cleanup started. + StartedCleanup { cursor: Option>> }, + /// The migration finished. The pallet can now be removed from the runtime. + Completed, } - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_idle(_now: BlockNumberFor, max_weight: Weight) -> Weight { - // on_idle processes additional messages with any remaining block weight. - Self::service_queue(max_weight) - } - } - - #[pallet::call] - impl Pallet { - /// Service a single overweight message. - #[pallet::call_index(0)] - #[pallet::weight(weight_limit.saturating_add(Weight::from_parts(1_000_000, 0)))] - pub fn service_overweight( - origin: OriginFor, - index: OverweightIndex, - weight_limit: Weight, - ) -> DispatchResultWithPostInfo { - T::ExecuteOverweightOrigin::ensure_origin(origin)?; - - let (sent_at, data) = Overweight::::get(index).ok_or(Error::::Unknown)?; - let weight_used = Self::try_service_message(weight_limit, sent_at, &data[..]) - .map_err(|_| Error::::OverLimit)?; - Overweight::::remove(index); - Self::deposit_event(Event::OverweightServiced { overweight_index: index, weight_used }); - Ok(Some(weight_used.saturating_add(Weight::from_parts(1_000_000, 0))).into()) + impl Default for MigrationState { + fn default() -> Self { + Self::NotStarted } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Downward message is invalid XCM. - InvalidFormat { message_hash: XcmHash }, - /// Downward message is unsupported version of XCM. - UnsupportedVersion { message_hash: XcmHash }, - /// Downward message executed with the given outcome. - ExecutedDownward { message_hash: XcmHash, message_id: XcmHash, outcome: Outcome }, - /// The weight limit for handling downward messages was reached. - WeightExhausted { - message_hash: XcmHash, - message_id: XcmHash, - remaining_weight: Weight, - required_weight: Weight, - }, - /// Downward message is overweight and was placed in the overweight queue. - OverweightEnqueued { - message_hash: XcmHash, - message_id: XcmHash, - overweight_index: OverweightIndex, - required_weight: Weight, - }, - /// Downward message from the overweight queue was executed. - OverweightServiced { overweight_index: OverweightIndex, weight_used: Weight }, - /// The maximum number of downward messages was reached. - MaxMessagesExhausted { message_hash: XcmHash }, - } + /// The export of pages started. + StartedExport, - /// Error type when a message was failed to be serviced. - pub(crate) struct ServiceMessageError { - /// The message's hash. - message_hash: XcmHash, - /// The message's ID (which could also be its hash if nothing overrides it). - message_id: XcmHash, - /// Weight required for the message to be executed. - required_weight: Weight, - } + /// The export of a page completed. + Exported { page: PageCounter }, - impl Pallet { - /// Service the message queue up to some given weight `limit`. + /// The export of a page failed. /// - /// Returns the weight consumed by executing messages in the queue. - fn service_queue(limit: Weight) -> Weight { - let mut messages_processed = 0; - PageIndex::::mutate(|page_index| { - Self::do_service_queue(limit, page_index, &mut messages_processed) - }) - } + /// This should never be emitted. + ExportFailed { page: PageCounter }, - /// Exactly equivalent to `service_queue` but expects a mutable `page_index` to be passed - /// in and any changes stored. - fn do_service_queue( - limit: Weight, - page_index: &mut PageIndexData, - messages_processed: &mut u8, - ) -> Weight { - let mut used = Weight::zero(); - while page_index.begin_used < page_index.end_used { - let page = Pages::::take(page_index.begin_used); - for (i, &(sent_at, ref data)) in page.iter().enumerate() { - if *messages_processed >= MAX_MESSAGES_PER_BLOCK { - // Exceeded block message limit - put the remaining messages back and bail - Pages::::insert(page_index.begin_used, &page[i..]); - return used - } - *messages_processed += 1; - match Self::try_service_message(limit.saturating_sub(used), sent_at, &data[..]) - { - Ok(w) => used += w, - Err(..) => { - // Too much weight needed - put the remaining messages back and bail - Pages::::insert(page_index.begin_used, &page[i..]); - return used - }, - } - } - page_index.begin_used += 1; - } - if page_index.begin_used == page_index.end_used { - // Reset if there's no pages left. - page_index.begin_used = 0; - page_index.end_used = 0; - } - used - } + /// The export of pages completed. + CompletedExport, - /// Attempt to service an individual message. Will return `Ok` with the execution weight - /// consumed unless the message was found to need more weight than `limit`. - /// - /// NOTE: This will return `Ok` in the case of an error decoding, weighing or executing - /// the message. This is why it's called message "servicing" rather than "execution". - pub(crate) fn try_service_message( - limit: Weight, - _sent_at: RelayBlockNumber, - mut data: &[u8], - ) -> Result { - let message_hash = sp_io::hashing::blake2_256(data); - let mut message_id = message_hash; - let maybe_msg = VersionedXcm::::decode_all_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data, - ) - .map(Xcm::::try_from); - match maybe_msg { - Err(_) => { - Self::deposit_event(Event::InvalidFormat { message_hash }); - Ok(Weight::zero()) - }, - Ok(Err(())) => { - Self::deposit_event(Event::UnsupportedVersion { message_hash }); - Ok(Weight::zero()) - }, - Ok(Ok(x)) => { - let outcome = T::XcmExecutor::prepare_and_execute( - Parent, - x, - &mut message_id, - limit, - Weight::zero(), - ); - match outcome { - Outcome::Error(XcmError::WeightLimitReached(required_weight)) => - Err(ServiceMessageError { message_hash, message_id, required_weight }), - outcome => { - let weight_used = outcome.weight_used(); - Self::deposit_event(Event::ExecutedDownward { - message_hash, - message_id, - outcome, - }); - Ok(weight_used) - }, - } - }, - } - } - } + /// The export of overweight messages started. + StartedOverweightExport, - /// For an incoming downward message, this just adapts an XCM executor and executes DMP messages - /// immediately up until some `MaxWeight` at which point it errors. Their origin is asserted to - /// be the `Parent` location. - impl DmpMessageHandler for Pallet { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - let mut messages_processed = 0; - let mut page_index = PageIndex::::get(); - let config = Configuration::::get(); - - // First try to use `max_weight` to service the current queue. - let mut used = Self::do_service_queue(limit, &mut page_index, &mut messages_processed); - - // Then if the queue is empty, use the weight remaining to service the incoming messages - // and once we run out of weight, place them in the queue. - let item_count = iter.size_hint().0; - let mut maybe_enqueue_page = if page_index.end_used > page_index.begin_used { - // queue is already non-empty - start a fresh page. - Some(Vec::with_capacity(item_count)) - } else { - None - }; - - for (i, (sent_at, data)) in iter.enumerate() { - if maybe_enqueue_page.is_none() { - if messages_processed >= MAX_MESSAGES_PER_BLOCK { - let item_count_left = item_count.saturating_sub(i); - maybe_enqueue_page = Some(Vec::with_capacity(item_count_left)); - - Self::deposit_event(Event::MaxMessagesExhausted { - message_hash: sp_io::hashing::blake2_256(&data), - }); - } else { - // We're not currently enqueuing - try to execute inline. - let remaining_weight = limit.saturating_sub(used); - messages_processed += 1; - match Self::try_service_message(remaining_weight, sent_at, &data[..]) { - Ok(consumed) => used += consumed, - Err(ServiceMessageError { - message_hash, - message_id, - required_weight, - }) => - // Too much weight required right now. - { - let is_under_limit = - Overweight::::count() < MAX_OVERWEIGHT_MESSAGES; - used.saturating_accrue(T::DbWeight::get().reads(1)); - if required_weight.any_gt(config.max_individual) && is_under_limit { - // overweight - add to overweight queue and continue with - // message execution. - let overweight_index = page_index.overweight_count; - Overweight::::insert(overweight_index, (sent_at, data)); - Self::deposit_event(Event::OverweightEnqueued { - message_hash, - message_id, - overweight_index, - required_weight, - }); - page_index.overweight_count += 1; - // Not needed for control flow, but only to ensure that the - // compiler understands that we won't attempt to re-use `data` - // later. - continue - } else { - // not overweight. stop executing inline and enqueue normally - // from here on. - let item_count_left = item_count.saturating_sub(i); - maybe_enqueue_page = Some(Vec::with_capacity(item_count_left)); - Self::deposit_event(Event::WeightExhausted { - message_hash, - message_id, - remaining_weight, - required_weight, - }); - } - }, - } - } - } - // Cannot be an `else` here since the `maybe_enqueue_page` may have changed. - if let Some(ref mut enqueue_page) = maybe_enqueue_page { - enqueue_page.push((sent_at, data)); - } - } - - // Deposit the enqueued page if any and save the index. - if let Some(enqueue_page) = maybe_enqueue_page { - Pages::::insert(page_index.end_used, enqueue_page); - page_index.end_used += 1; - } - PageIndex::::put(page_index); - - used - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate as dmp_queue; - - use codec::Encode; - use cumulus_primitives_core::ParaId; - use frame_support::{assert_noop, parameter_types, traits::OnIdle}; - use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, - DispatchError::BadOrigin, - }; - use sp_version::RuntimeVersion; - use std::cell::RefCell; - use xcm::latest::{MultiLocation, OriginKind}; - - type Block = frame_system::mocking::MockBlock; - type Xcm = xcm::latest::Xcm; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - DmpQueue: dmp_queue::{Pallet, Call, Storage, Event}, - } - ); - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub Version: RuntimeVersion = RuntimeVersion { - spec_name: sp_version::create_runtime_str!("test"), - impl_name: sp_version::create_runtime_str!("system-test"), - authoring_version: 1, - spec_version: 1, - impl_version: 1, - apis: sp_version::create_apis_vec!([]), - transaction_version: 1, - state_version: 1, - }; - pub const ParachainId: ParaId = ParaId::new(200); - pub const ReservedXcmpWeight: Weight = Weight::zero(); - pub const ReservedDmpWeight: Weight = Weight::zero(); - } - - type AccountId = u64; - - impl frame_system::Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockLength = (); - type BlockWeights = (); - type Version = Version; - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } + /// The export of an overweight message completed. + ExportedOverweight { index: OverweightIndex }, - thread_local! { - pub static TRACE: RefCell> = RefCell::new(Vec::new()); - } - pub fn take_trace() -> Vec<(Xcm, Outcome)> { - TRACE.with(|q| { - let q = &mut *q.borrow_mut(); - let r = q.clone(); - q.clear(); - r - }) - } - - pub struct MockPrepared(Xcm); - impl PreparedMessage for MockPrepared { - fn weight_of(&self) -> Weight { - match ((self.0).0.len(), &(self.0).0.first()) { - (1, Some(Transact { require_weight_at_most, .. })) => *require_weight_at_most, - _ => Weight::from_parts(1, 1), - } - } - } + /// The export of an overweight message failed. + /// + /// This should never be emitted. + ExportOverweightFailed { index: OverweightIndex }, - pub struct MockExec; - impl ExecuteXcm for MockExec { - type Prepared = MockPrepared; + /// The export of overweight messages completed. + CompletedOverweightExport, - fn prepare(message: Xcm) -> Result { - Ok(MockPrepared(message)) - } + /// The cleanup of remaining pallet storage started. + StartedCleanup, - fn execute( - _origin: impl Into, - prepared: MockPrepared, - _id: &mut XcmHash, - _weight_credit: Weight, - ) -> Outcome { - let message = prepared.0; - let o = match (message.0.len(), &message.0.first()) { - (1, Some(Transact { require_weight_at_most, .. })) => - Outcome::Complete(*require_weight_at_most), - // use 1000 to decide that it's not supported. - _ => Outcome::Incomplete(Weight::from_parts(1, 1), XcmError::Unimplemented), - }; - TRACE.with(|q| q.borrow_mut().push((message, o.clone()))); - o - } + /// Some debris was cleaned up. + CleanedSome { keys_removed: u32 }, - fn charge_fees(_location: impl Into, _fees: MultiAssets) -> XcmResult { - Err(XcmError::Unimplemented) - } + /// The cleanup of remaining pallet storage completed. + Completed { error: bool }, } - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = MockExec; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; - } - - pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::::default().build_storage().unwrap().into() - } + #[pallet::call] + impl Pallet {} - fn enqueue(enqueued: &[Xcm]) { - if !enqueued.is_empty() { - let mut index = PageIndex::::get(); - Pages::::insert( - index.end_used, - enqueued - .iter() - .map(|m| (0, VersionedXcm::::from(m.clone()).encode())) - .collect::>(), - ); - index.end_used += 1; - PageIndex::::put(index); + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + let w = Self::on_idle_weight(); + assert!(w != Weight::zero()); + assert!(w.all_lte(T::BlockWeights::get().max_block)); } - } - fn handle_messages(incoming: &[Xcm], limit: Weight) -> Weight { - let iter = incoming - .iter() - .map(|m| (0, VersionedXcm::::from(m.clone()).encode())); - DmpQueue::handle_dmp_messages(iter, limit) - } + fn on_idle(now: BlockNumberFor, limit: Weight) -> Weight { + let mut meter = WeightMeter::with_limit(limit); - fn msg(weight: u64) -> Xcm { - Xcm(vec![Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: Weight::from_parts(weight, weight), - call: Vec::new().into(), - }]) - } - - fn msg_complete(weight: u64) -> (Xcm, Outcome) { - (msg(weight), Outcome::Complete(Weight::from_parts(weight, weight))) - } - - fn pages_queued() -> PageCounter { - PageIndex::::get().end_used - PageIndex::::get().begin_used - } - - fn queue_is_empty() -> bool { - pages_queued() == 0 - } - - fn overweights() -> Vec { - (0..PageIndex::::get().overweight_count) - .filter(|i| Overweight::::contains_key(i)) - .collect::>() - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - let weight_used = handle_messages(&[], Weight::from_parts(1000, 1000)); - assert_eq!(weight_used, Weight::zero()); - assert_eq!(take_trace(), Vec::new()); - assert!(queue_is_empty()); - }); - } + if meter.try_consume(Self::on_idle_weight()).is_err() { + log::debug!(target: LOG, "Not enough weight for on_idle. {} < {}", Self::on_idle_weight(), limit); + return meter.consumed() + } - #[test] - fn service_inline_complete_works() { - new_test_ext().execute_with(|| { - let incoming = vec![msg(1000), msg(1001)]; - let weight_used = handle_messages(&incoming, Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(2001, 2001)); - assert_eq!(take_trace(), vec![msg_complete(1000), msg_complete(1001)]); - assert!(queue_is_empty()); - }); - } + let state = MigrationStatus::::get(); + let index = PageIndex::::get(); + log::debug!(target: LOG, "on_idle: block={:?}, state={:?}, index={:?}", now, state, index); - #[test] - fn service_enqueued_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(1001), msg(1002)]; - enqueue(&enqueued); - let weight_used = handle_messages(&[], Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(2001, 2001)); - assert_eq!(take_trace(), vec![msg_complete(1000), msg_complete(1001),]); - }); - } + match state { + MigrationState::NotStarted => { + log::debug!(target: LOG, "Init export at page {}", index.begin_used); - #[test] - fn enqueue_works() { - new_test_ext().execute_with(|| { - let incoming = vec![msg(1000), msg(1001), msg(1002)]; - let weight_used = handle_messages(&incoming, Weight::from_parts(999, 999)); - assert_eq!(weight_used, Weight::zero()); - assert_eq!( - PageIndex::::get(), - PageIndexData { begin_used: 0, end_used: 1, overweight_count: 0 } - ); - assert_eq!(Pages::::get(0).len(), 3); - assert_eq!(take_trace(), vec![]); - - let weight_used = handle_messages(&[], Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(2001, 2001)); - assert_eq!(take_trace(), vec![msg_complete(1000), msg_complete(1001)]); - - let weight_used = handle_messages(&[], Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(1002, 1002)); - assert_eq!(take_trace(), vec![msg_complete(1002)]); - assert!(queue_is_empty()); - }); - } + MigrationStatus::::put(MigrationState::StartedExport { + next_begin_used: index.begin_used, + }); + Self::deposit_event(Event::StartedExport); + }, + MigrationState::StartedExport { next_begin_used } => { + log::debug!(target: LOG, "Exporting page {}", next_begin_used); - #[test] - fn service_inline_then_enqueue_works() { - new_test_ext().execute_with(|| { - let incoming = vec![msg(1000), msg(1001), msg(1002)]; - let weight_used = handle_messages(&incoming, Weight::from_parts(1500, 1500)); - assert_eq!(weight_used, Weight::from_parts(1000, 1000)); - assert_eq!(pages_queued(), 1); - assert_eq!(Pages::::get(0).len(), 2); - assert_eq!(take_trace(), vec![msg_complete(1000)]); - - let weight_used = handle_messages(&[], Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(2003, 2003)); - assert_eq!(take_trace(), vec![msg_complete(1001), msg_complete(1002),]); - assert!(queue_is_empty()); - }); - } + if next_begin_used == index.end_used { + MigrationStatus::::put(MigrationState::CompletedExport); + log::debug!(target: LOG, "CompletedExport"); + Self::deposit_event(Event::CompletedExport); + } else { + let res = migration::migrate_page::(next_begin_used); - #[test] - fn service_enqueued_and_inline_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(1001)]; - let incoming = vec![msg(1002), msg(1003)]; - enqueue(&enqueued); - let weight_used = handle_messages(&incoming, Weight::from_parts(5000, 5000)); - assert_eq!(weight_used, Weight::from_parts(4006, 4006)); - assert_eq!( - take_trace(), - vec![ - msg_complete(1000), - msg_complete(1001), - msg_complete(1002), - msg_complete(1003), - ] - ); - assert!(queue_is_empty()); - }); - } + MigrationStatus::::put(MigrationState::StartedExport { + next_begin_used: next_begin_used.saturating_add(1), + }); - #[test] - fn service_enqueued_partially_and_then_enqueue_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(10001)]; - let incoming = vec![msg(1002), msg(1003)]; - enqueue(&enqueued); - let weight_used = handle_messages(&incoming, Weight::from_parts(5000, 5000)); - assert_eq!(weight_used, Weight::from_parts(1000, 1000)); - assert_eq!(take_trace(), vec![msg_complete(1000)]); - assert_eq!(pages_queued(), 2); - - // 5000 is not enough to process the 10001 blocker, so nothing happens. - let weight_used = handle_messages(&[], Weight::from_parts(5000, 5000)); - assert_eq!(weight_used, Weight::zero()); - assert_eq!(take_trace(), vec![]); - - // 20000 is now enough to process everything. - let weight_used = handle_messages(&[], Weight::from_parts(20000, 20000)); - assert_eq!(weight_used, Weight::from_parts(12006, 12006)); - assert_eq!( - take_trace(), - vec![msg_complete(10001), msg_complete(1002), msg_complete(1003),] - ); - assert!(queue_is_empty()); - }); - } + if let Ok(()) = res { + log::debug!(target: LOG, "Exported page {}", next_begin_used); + Self::deposit_event(Event::Exported { page: next_begin_used }); + } else { + Self::deposit_event(Event::ExportFailed { page: next_begin_used }); + } + } + }, + MigrationState::CompletedExport => { + log::debug!(target: LOG, "Init export overweight at index 0"); - #[test] - fn service_enqueued_completely_and_then_enqueue_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(1001)]; - let incoming = vec![msg(10002), msg(1003)]; - enqueue(&enqueued); - let weight_used = handle_messages(&incoming, Weight::from_parts(5000, 5000)); - assert_eq!(weight_used, Weight::from_parts(2001, 2001)); - assert_eq!(take_trace(), vec![msg_complete(1000), msg_complete(1001)]); - assert_eq!(pages_queued(), 1); - - // 20000 is now enough to process everything. - let weight_used = handle_messages(&[], Weight::from_parts(20000, 20000)); - assert_eq!(weight_used, Weight::from_parts(11005, 11005)); - assert_eq!(take_trace(), vec![msg_complete(10002), msg_complete(1003),]); - assert!(queue_is_empty()); - }); - } + MigrationStatus::::put(MigrationState::StartedOverweightExport { + next_overweight_index: 0, + }); + Self::deposit_event(Event::StartedOverweightExport); + }, + MigrationState::StartedOverweightExport { next_overweight_index } => { + log::debug!(target: LOG, "Exporting overweight index {}", next_overweight_index); - #[test] - fn service_enqueued_then_inline_then_enqueue_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(1001)]; - let incoming = vec![msg(1002), msg(10003)]; - enqueue(&enqueued); - let weight_used = handle_messages(&incoming, Weight::from_parts(5000, 5000)); - assert_eq!(weight_used, Weight::from_parts(3003, 3003)); - assert_eq!( - take_trace(), - vec![msg_complete(1000), msg_complete(1001), msg_complete(1002),] - ); - assert_eq!(pages_queued(), 1); - - // 20000 is now enough to process everything. - let weight_used = handle_messages(&[], Weight::from_parts(20000, 20000)); - assert_eq!(weight_used, Weight::from_parts(10003, 10003)); - assert_eq!(take_trace(), vec![msg_complete(10003),]); - assert!(queue_is_empty()); - }); - } + if next_overweight_index == index.overweight_count { + MigrationStatus::::put(MigrationState::CompletedOverweightExport); + log::debug!(target: LOG, "CompletedOverweightExport"); + Self::deposit_event(Event::CompletedOverweightExport); + } else { + let res = migration::migrate_overweight::(next_overweight_index); - #[test] - fn page_crawling_works() { - new_test_ext().execute_with(|| { - let enqueued = vec![msg(1000), msg(1001)]; - enqueue(&enqueued); - let weight_used = handle_messages(&[msg(1002)], Weight::from_parts(1500, 1500)); - assert_eq!(weight_used, Weight::from_parts(1000, 1000)); - assert_eq!(take_trace(), vec![msg_complete(1000)]); - assert_eq!(pages_queued(), 2); - assert_eq!(PageIndex::::get().begin_used, 0); - - let weight_used = handle_messages(&[msg(1003)], Weight::from_parts(1500, 1500)); - assert_eq!(weight_used, Weight::from_parts(1001, 1001)); - assert_eq!(take_trace(), vec![msg_complete(1001)]); - assert_eq!(pages_queued(), 2); - assert_eq!(PageIndex::::get().begin_used, 1); - - let weight_used = handle_messages(&[msg(1004)], Weight::from_parts(1500, 1500)); - assert_eq!(weight_used, Weight::from_parts(1002, 1002)); - assert_eq!(take_trace(), vec![msg_complete(1002)]); - assert_eq!(pages_queued(), 2); - assert_eq!(PageIndex::::get().begin_used, 2); - }); - } + MigrationStatus::::put(MigrationState::StartedOverweightExport { + next_overweight_index: next_overweight_index.saturating_add(1), + }); - #[test] - fn overweight_should_not_block_queue() { - new_test_ext().execute_with(|| { - // Set the overweight threshold to 9999. - Configuration::::put(ConfigData { - max_individual: Weight::from_parts(9999, 9999), - }); - - let incoming = vec![msg(1000), msg(10001), msg(1002)]; - let weight_used = handle_messages(&incoming, Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::from_parts(2002, 2002)); - assert!(queue_is_empty()); - assert_eq!(take_trace(), vec![msg_complete(1000), msg_complete(1002),]); - - assert_eq!(overweights(), vec![0]); - }); - } + if let Ok(()) = res { + log::debug!(target: LOG, "Exported overweight index {next_overweight_index}"); + Self::deposit_event(Event::ExportedOverweight { + index: next_overweight_index, + }); + } else { + Self::deposit_event(Event::ExportOverweightFailed { + index: next_overweight_index, + }); + } + } + }, + MigrationState::CompletedOverweightExport => { + log::debug!(target: LOG, "Init cleanup"); - #[test] - fn overweights_should_be_manually_executable() { - new_test_ext().execute_with(|| { - // Set the overweight threshold to 9999. - Configuration::::put(ConfigData { - max_individual: Weight::from_parts(9999, 9999), - }); - - let incoming = vec![msg(10000)]; - let weight_used = handle_messages(&incoming, Weight::from_parts(2500, 2500)); - assert_eq!(weight_used, Weight::zero()); - assert_eq!(take_trace(), vec![]); - assert_eq!(overweights(), vec![0]); - - assert_noop!( - DmpQueue::service_overweight( - RuntimeOrigin::signed(1), - 0, - Weight::from_parts(20000, 20000) - ), - BadOrigin - ); - assert_noop!( - DmpQueue::service_overweight( - RuntimeOrigin::root(), - 1, - Weight::from_parts(20000, 20000) - ), - Error::::Unknown - ); - assert_noop!( - DmpQueue::service_overweight( - RuntimeOrigin::root(), - 0, - Weight::from_parts(9999, 9999) - ), - Error::::OverLimit - ); - assert_eq!(take_trace(), vec![]); - - let base_weight = - super::Call::::service_overweight { index: 0, weight_limit: Weight::zero() } - .get_dispatch_info() - .weight; - use frame_support::dispatch::GetDispatchInfo; - let info = DmpQueue::service_overweight( - RuntimeOrigin::root(), - 0, - Weight::from_parts(20000, 20000), - ) - .unwrap(); - let actual_weight = info.actual_weight.unwrap(); - assert_eq!(actual_weight, base_weight + Weight::from_parts(10000, 10000)); - assert_eq!(take_trace(), vec![msg_complete(10000)]); - assert!(overweights().is_empty()); - - assert_noop!( - DmpQueue::service_overweight( - RuntimeOrigin::root(), - 0, - Weight::from_parts(20000, 20000) - ), - Error::::Unknown - ); - }); - } + MigrationStatus::::put(MigrationState::StartedCleanup { cursor: None }); + Self::deposit_event(Event::StartedCleanup); + }, + MigrationState::StartedCleanup { cursor } => { + log::debug!(target: LOG, "Cleaning up"); + let hashed_prefix = + twox_128( as PalletInfoAccess>::name().as_bytes()); + + let result = frame_support::storage::unhashed::clear_prefix( + &hashed_prefix, + Some(2), // Somehow it does nothing when set to 1, so we set it to 2. + cursor.as_ref().map(|c| c.as_ref()), + ); + Self::deposit_event(Event::CleanedSome { keys_removed: result.backend }); + + // GOTCHA! We deleted *all* pallet storage; hence we also our own + // `MigrationState`. BUT we insert it back: + if let Some(unbound_cursor) = result.maybe_cursor { + if let Ok(cursor) = unbound_cursor.try_into() { + log::debug!(target: LOG, "Next cursor: {:?}", &cursor); + MigrationStatus::::put(MigrationState::StartedCleanup { + cursor: Some(cursor), + }); + } else { + MigrationStatus::::put(MigrationState::Completed); + log::error!(target: LOG, "Completed with error: could not bound cursor"); + Self::deposit_event(Event::Completed { error: true }); + } + } else { + MigrationStatus::::put(MigrationState::Completed); + log::debug!(target: LOG, "Completed"); + Self::deposit_event(Event::Completed { error: false }); + } + }, + MigrationState::Completed => { + log::debug!(target: LOG, "Idle; you can remove this pallet"); + }, + } - #[test] - fn on_idle_should_service_queue() { - new_test_ext().execute_with(|| { - enqueue(&[msg(1000), msg(1001)]); - enqueue(&[msg(1002), msg(1003)]); - enqueue(&[msg(1004), msg(1005)]); - - let weight_used = DmpQueue::on_idle(1, Weight::from_parts(6000, 6000)); - assert_eq!(weight_used, Weight::from_parts(5010, 5010)); - assert_eq!( - take_trace(), - vec![ - msg_complete(1000), - msg_complete(1001), - msg_complete(1002), - msg_complete(1003), - msg_complete(1004), - ] - ); - assert_eq!(pages_queued(), 1); - }); + meter.consumed() + } } - #[test] - fn handle_max_messages_per_block() { - new_test_ext().execute_with(|| { - enqueue(&[msg(1000), msg(1001)]); - enqueue(&[msg(1002), msg(1003)]); - enqueue(&[msg(1004), msg(1005)]); - - let incoming = - (0..MAX_MESSAGES_PER_BLOCK).map(|i| msg(1006 + i as u64)).collect::>(); - handle_messages(&incoming, Weight::from_parts(25000, 25000)); - - assert_eq!( - take_trace(), - (0..MAX_MESSAGES_PER_BLOCK) - .map(|i| msg_complete(1000 + i as u64)) - .collect::>(), - ); - assert_eq!(pages_queued(), 1); - - handle_messages(&[], Weight::from_parts(25000, 25000)); - assert_eq!( - take_trace(), - (MAX_MESSAGES_PER_BLOCK..MAX_MESSAGES_PER_BLOCK + 6) - .map(|i| msg_complete(1000 + i as u64)) - .collect::>(), - ); - }); + impl Pallet { + /// The worst-case weight of [`Self::on_idle`]. + pub fn on_idle_weight() -> Weight { + ::WeightInfo::on_idle_good_msg() + .max(::WeightInfo::on_idle_large_msg()) + } } } diff --git a/cumulus/pallets/dmp-queue/src/migration.rs b/cumulus/pallets/dmp-queue/src/migration.rs index 63457ee493697cc7e0b6cb32433cd0d55b9e2244..349635cce547d81c1be527cb366f67679a471945 100644 --- a/cumulus/pallets/dmp-queue/src/migration.rs +++ b/cumulus/pallets/dmp-queue/src/migration.rs @@ -14,110 +14,100 @@ // 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::{Config, Configuration, Overweight, Pallet, DEFAULT_POV_SIZE}; -use frame_support::{ - pallet_prelude::*, - traits::{OnRuntimeUpgrade, StorageVersion}, - weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}, -}; - -/// The current storage version. -pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); - -/// Migrates the pallet storage to the most recent version. -pub struct Migration(PhantomData); - -impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let mut weight = T::DbWeight::get().reads(1); - - if StorageVersion::get::>() == 0 { - weight.saturating_accrue(migrate_to_v1::()); - StorageVersion::new(1).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - if StorageVersion::get::>() == 1 { - weight.saturating_accrue(migrate_to_v2::()); - StorageVersion::new(2).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - weight - } +//! Migrates the storage from the previously deleted DMP pallet. + +use crate::*; +use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber; +use frame_support::{pallet_prelude::*, storage_alias, traits::HandleMessage}; +use sp_std::vec::Vec; + +pub(crate) const LOG: &str = "runtime::dmp-queue-export-xcms"; + +/// The old `PageIndexData` struct. +#[derive(Copy, Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PageIndexData { + /// The lowest used page index. + pub begin_used: PageCounter, + /// The lowest unused page index. + pub end_used: PageCounter, + /// The number of overweight messages ever recorded (and thus the lowest free index). + pub overweight_count: OverweightIndex, } -mod v0 { +/// The old `MigrationState` type. +pub type OverweightIndex = u64; +/// The old `MigrationState` type. +pub type PageCounter = u32; + +/// The old `PageIndex` storage item. +#[storage_alias] +pub type PageIndex = StorageValue, PageIndexData, ValueQuery>; + +/// The old `Pages` storage item. +#[storage_alias] +pub type Pages = StorageMap< + Pallet, + Blake2_128Concat, + PageCounter, + Vec<(RelayBlockNumber, Vec)>, + ValueQuery, +>; + +/// The old `Overweight` storage item. +#[storage_alias] +pub type Overweight = CountedStorageMap< + Pallet, + Blake2_128Concat, + OverweightIndex, + (RelayBlockNumber, Vec), + OptionQuery, +>; + +pub(crate) mod testing_only { use super::*; - use codec::{Decode, Encode}; - - #[derive(Decode, Encode, Debug)] - pub struct ConfigData { - pub max_individual: u64, - } - impl Default for ConfigData { - fn default() -> Self { - ConfigData { max_individual: 10u64 * WEIGHT_REF_TIME_PER_MILLIS } - } - } + /// This alias is not used by the migration but only for testing. + /// + /// Note that the alias type is wrong on purpose. + #[storage_alias] + pub type Configuration = StorageValue, u32>; } -/// Migrates `QueueConfigData` from v1 (using only reference time weights) to v2 (with -/// 2D weights). -/// -/// NOTE: Only use this function if you know what you're doing. Default to using -/// `migrate_to_latest`. -pub fn migrate_to_v1() -> Weight { - let translate = |pre: v0::ConfigData| -> super::ConfigData { - super::ConfigData { - max_individual: Weight::from_parts(pre.max_individual, DEFAULT_POV_SIZE), - } - }; - - if Configuration::::translate(|pre| pre.map(translate)).is_err() { - log::error!( - target: "dmp_queue", - "unexpected error when performing translation of the QueueConfig type during storage upgrade to v2" - ); +/// Migrates a single page to the `DmpSink`. +pub(crate) fn migrate_page(p: PageCounter) -> Result<(), ()> { + let page = Pages::::take(p); + log::debug!(target: LOG, "Migrating page #{p} with {} messages ...", page.len()); + if page.is_empty() { + log::error!(target: LOG, "Page #{p}: EMPTY - storage corrupted?"); + return Err(()) } - T::DbWeight::get().reads_writes(1, 1) -} + for (m, (block, msg)) in page.iter().enumerate() { + let Ok(bound) = BoundedVec::::try_from(msg.clone()) else { + log::error!(target: LOG, "[Page {p}] Message #{m}: TOO LONG - dropping"); + continue + }; -/// Migrates `Overweight` so that it initializes the storage map's counter. -/// -/// NOTE: Only use this function if you know what you're doing. Default to using -/// `migrate_to_latest`. -pub fn migrate_to_v2() -> Weight { - let overweight_messages = Overweight::::initialize_counter() as u64; + T::DmpSink::handle_message(bound.as_bounded_slice()); + log::debug!(target: LOG, "[Page {p}] Migrated message #{m} from block {block}"); + } - T::DbWeight::get().reads_writes(overweight_messages, 1) + Ok(()) } -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{new_test_ext, Test}; - - #[test] - fn test_migration_to_v1() { - let v0 = v0::ConfigData { max_individual: 30_000_000_000 }; - - new_test_ext().execute_with(|| { - frame_support::storage::unhashed::put_raw( - &crate::Configuration::::hashed_key(), - &v0.encode(), - ); - - migrate_to_v1::(); +/// Migrates a single overweight message to the `DmpSink`. +pub(crate) fn migrate_overweight(i: OverweightIndex) -> Result<(), ()> { + let Some((block, msg)) = Overweight::::take(i) else { + log::error!(target: LOG, "[Overweight {i}] Message: EMPTY - storage corrupted?"); + return Err(()) + }; + let Ok(bound) = BoundedVec::::try_from(msg) else { + log::error!(target: LOG, "[Overweight {i}] Message: TOO LONG - dropping"); + return Err(()) + }; - let v1 = crate::Configuration::::get(); + T::DmpSink::handle_message(bound.as_bounded_slice()); + log::debug!(target: LOG, "[Overweight {i}] Migrated message from block {block}"); - assert_eq!(v0.max_individual, v1.max_individual.ref_time()); - assert_eq!(v1.max_individual.proof_size(), DEFAULT_POV_SIZE); - }); - } + Ok(()) } diff --git a/cumulus/pallets/dmp-queue/src/mock.rs b/cumulus/pallets/dmp-queue/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed72ce678e3efa416df78b95f18c0115c91e2e1f --- /dev/null +++ b/cumulus/pallets/dmp-queue/src/mock.rs @@ -0,0 +1,83 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +use frame_support::{ + derive_impl, parameter_types, + traits::{HandleMessage, QueueFootprint}, +}; +use sp_core::{bounded_vec::BoundedSlice, ConstU32}; +use sp_runtime::traits::IdentityLookup; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + DmpQueue: crate, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; +} + +impl crate::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type DmpSink = RecordingDmpSink; + type WeightInfo = (); +} + +parameter_types! { + /// All messages that came into the `DmpSink`. + pub static RecordedMessages: Vec> = vec![]; +} + +/// Can be used as [`Config::DmpSink`] to record all messages that came in. +pub struct RecordingDmpSink; +impl HandleMessage for RecordingDmpSink { + type MaxMessageLen = ConstU32<16>; + + fn handle_message(msg: BoundedSlice) { + RecordedMessages::mutate(|n| n.push(msg.to_vec())); + } + + fn handle_messages<'a>(_: impl Iterator>) { + unimplemented!() + } + + fn sweep_queue() { + unimplemented!() + } + + fn footprint() -> QueueFootprint { + unimplemented!() + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_io::TestExternalities::new(Default::default()) +} diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..a157d0584f2550bb9cacac730a0e942e3c91503e --- /dev/null +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -0,0 +1,234 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Test the lazy migration. + +#![cfg(test)] + +use super::{migration::*, mock::*}; +use crate::*; + +use frame_support::{ + pallet_prelude::*, + traits::{OnFinalize, OnIdle, OnInitialize}, + StorageNoopGuard, +}; + +#[test] +fn migration_works() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + sp_tracing::try_init_simple(); + // Insert some storage: + PageIndex::::set(PageIndexData { + begin_used: 10, + end_used: 20, + overweight_count: 5, + }); + for p in 10..20 { + let msgs = (0..16).map(|i| (p, vec![i as u8; 1])).collect::>(); + Pages::::insert(p, msgs); + } + for i in 0..5 { + Overweight::::insert(i, (0, vec![i as u8; 1])); + } + testing_only::Configuration::::put(123); + }); + // We need to commit, otherwise the keys are removed from the overlay; not the backend. + ext.commit_all().unwrap(); + ext.execute_with(|| { + // Run one step of the migration: + pre_upgrade_checks::(); + run_to_block(1); + // First we expect a StartedExport event: + assert_only_event(Event::StartedExport); + + // Then we expect 10 Exported events: + for page in 0..10 { + run_to_block(2 + page); + assert_only_event(Event::Exported { page: page as u32 + 10 }); + assert!(!Pages::::contains_key(page as u32), "Page is gone"); + assert_eq!( + MigrationStatus::::get(), + MigrationState::StartedExport { next_begin_used: page as u32 + 11 } + ); + } + + // Then we expect a CompletedExport event: + run_to_block(12); + assert_only_event(Event::CompletedExport); + assert_eq!(MigrationStatus::::get(), MigrationState::CompletedExport); + + // Then we expect a StartedOverweightExport event: + run_to_block(13); + assert_only_event(Event::StartedOverweightExport); + assert_eq!( + MigrationStatus::::get(), + MigrationState::StartedOverweightExport { next_overweight_index: 0 } + ); + + // Then we expect 5 ExportedOverweight events: + for index in 0..5 { + run_to_block(14 + index); + assert_only_event(Event::ExportedOverweight { index }); + assert!(!Overweight::::contains_key(index), "Overweight msg is gone"); + assert_eq!( + MigrationStatus::::get(), + MigrationState::StartedOverweightExport { next_overweight_index: index + 1 } + ); + } + + // Then we expect a CompletedOverweightExport event: + run_to_block(19); + assert_only_event(Event::CompletedOverweightExport); + assert_eq!(MigrationStatus::::get(), MigrationState::CompletedOverweightExport); + + // Then we expect a StartedCleanup event: + run_to_block(20); + assert_only_event(Event::StartedCleanup); + assert_eq!( + MigrationStatus::::get(), + MigrationState::StartedCleanup { cursor: None } + ); + }); + ext.commit_all().unwrap(); + // Then it cleans up the remaining storage items: + ext.execute_with(|| { + run_to_block(21); + assert_only_event(Event::CleanedSome { keys_removed: 2 }); + }); + ext.commit_all().unwrap(); + ext.execute_with(|| { + run_to_block(22); + assert_only_event(Event::CleanedSome { keys_removed: 2 }); + }); + ext.commit_all().unwrap(); + ext.execute_with(|| { + run_to_block(24); + assert_eq!( + System::events().into_iter().map(|e| e.event).collect::>(), + vec![ + Event::CleanedSome { keys_removed: 2 }.into(), + Event::Completed { error: false }.into() + ] + ); + System::reset_events(); + assert_eq!(MigrationStatus::::get(), MigrationState::Completed); + + post_upgrade_checks::(); + assert_eq!(RecordedMessages::take().len(), 10 * 16 + 5); + + // Test the storage removal: + assert!(!PageIndex::::exists()); + assert!(!testing_only::Configuration::::exists()); + assert_eq!(Pages::::iter_keys().count(), 0); + assert_eq!(Overweight::::iter_keys().count(), 0); + + // The `MigrationStatus` never disappears and there are no more storage changes: + { + let _g = StorageNoopGuard::default(); + + run_to_block(100); + assert_eq!(MigrationStatus::::get(), MigrationState::Completed); + assert!(System::events().is_empty()); + // ... besides the block number + System::set_block_number(24); + } + }); +} + +/// Too long messages are dropped by the migration. +#[test] +fn migration_too_long_ignored() { + new_test_ext().execute_with(|| { + // Setup the storage: + PageIndex::::set(PageIndexData { + begin_used: 10, + end_used: 11, + overweight_count: 2, + }); + + let short = vec![1; 16]; + let long = vec![0; 17]; + Pages::::insert(10, vec![(10, short.clone()), (10, long.clone())]); + // Insert one good and one bad overweight msg: + Overweight::::insert(0, (0, short.clone())); + Overweight::::insert(1, (0, long.clone())); + + // Run the migration: + pre_upgrade_checks::(); + run_to_block(100); + post_upgrade_checks::(); + + assert_eq!(RecordedMessages::take(), vec![short.clone(), short]); + + // Test the storage removal: + assert!(!PageIndex::::exists()); + assert_eq!(Pages::::iter_keys().count(), 0); + assert_eq!(Overweight::::iter_keys().count(), 0); + }); +} + +fn run_to_block(n: u64) { + assert!(n > System::block_number(), "Cannot go back in time"); + + while System::block_number() < n { + AllPalletsWithSystem::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + AllPalletsWithSystem::on_initialize(System::block_number()); + AllPalletsWithSystem::on_idle(System::block_number(), Weight::MAX); + } +} + +fn assert_only_event(e: Event) { + assert_eq!(System::events().pop().expect("Event expected").event, e.clone().into()); + assert_eq!(System::events().len(), 1, "Got events: {:?} but wanted {:?}", System::events(), e); + System::reset_events(); +} + +/// TESTING ONLY +fn pre_upgrade_checks() { + let index = PageIndex::::get(); + + // Check that all pages are present. + assert!(index.begin_used <= index.end_used, "Invalid page index"); + for p in index.begin_used..index.end_used { + assert!(Pages::::contains_key(p), "Missing page"); + assert!(Pages::::get(p).len() > 0, "Empty page"); + } + + // Check that all overweight messages are present. + for i in 0..index.overweight_count { + assert!(Overweight::::contains_key(i), "Missing overweight message"); + } +} + +/// TESTING ONLY +fn post_upgrade_checks() { + let index = PageIndex::::get(); + + // Check that all pages are removed. + for p in index.begin_used..index.end_used { + assert!(!Pages::::contains_key(p), "Page should be gone"); + } + assert!(Pages::::iter_keys().next().is_none(), "Un-indexed pages"); + + // Check that all overweight messages are removed. + for i in 0..index.overweight_count { + assert!(!Overweight::::contains_key(i), "Overweight message should be gone"); + } + assert!(Overweight::::iter_keys().next().is_none(), "Un-indexed overweight messages"); +} diff --git a/cumulus/pallets/dmp-queue/src/weights.rs b/cumulus/pallets/dmp-queue/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..e095d46a0739dd251a6bafef77c37374e6dc4f64 --- /dev/null +++ b/cumulus/pallets/dmp-queue/src/weights.rs @@ -0,0 +1,222 @@ +// 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 `cumulus_pallet_dmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-kusama-dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --pallet +// cumulus-pallet-dmp-queue +// --chain +// asset-hub-kusama-dev +// --output +// cumulus/pallets/dmp-queue/src/weights.rs +// --template +// substrate/.maintain/frame-weight-template.hbs +// --extrinsic +// + +#![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 `cumulus_pallet_dmp_queue`. +pub trait WeightInfo { + fn on_idle_good_msg() -> Weight; + fn on_idle_large_msg() -> Weight; + fn on_idle_overweight_good_msg() -> Weight; + fn on_idle_overweight_large_msg() -> Weight; +} + +/// Weights for `cumulus_pallet_dmp_queue` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (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: `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: `65696` + // Estimated: `69161` + // Minimum execution time: 124_651_000 picoseconds. + Weight::from_parts(127_857_000, 0) + .saturating_add(Weight::from_parts(0, 69161)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + fn on_idle_large_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65659` + // Estimated: `69124` + // Minimum execution time: 65_684_000 picoseconds. + Weight::from_parts(68_039_000, 0) + .saturating_add(Weight::from_parts(0, 69124)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (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: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_overweight_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65726` + // Estimated: `69191` + // Minimum execution time: 117_657_000 picoseconds. + Weight::from_parts(122_035_000, 0) + .saturating_add(Weight::from_parts(0, 69191)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(6)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + fn on_idle_overweight_large_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65689` + // Estimated: `69154` + // Minimum execution time: 59_799_000 picoseconds. + Weight::from_parts(61_354_000, 0) + .saturating_add(Weight::from_parts(0, 69154)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (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: `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: `65696` + // Estimated: `69161` + // Minimum execution time: 124_651_000 picoseconds. + Weight::from_parts(127_857_000, 0) + .saturating_add(Weight::from_parts(0, 69161)) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(5)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca7d95d3e948effbeccff2de2c182672836` (r:1 w:1) + fn on_idle_large_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65659` + // Estimated: `69124` + // Minimum execution time: 65_684_000 picoseconds. + Weight::from_parts(68_039_000, 0) + .saturating_add(Weight::from_parts(0, 69124)) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (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: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_overweight_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65726` + // Estimated: `69191` + // Minimum execution time: 117_657_000 picoseconds. + Weight::from_parts(122_035_000, 0) + .saturating_add(Weight::from_parts(0, 69191)) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(6)) + } + /// Storage: `DmpQueue::MigrationStatus` (r:1 w:1) + /// Proof: `DmpQueue::MigrationStatus` (`max_values`: Some(1), `max_size`: Some(1028), added: 1523, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca754904d6d8c6fe06c4e5965f9b8397421` (r:1 w:0) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca70f923ef3252d0166429d36d20ed665a8` (r:1 w:1) + /// Storage: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xcd5c1f6df63bc97f4a8ce37f14a50ca772275f64c354954352b71eea39cfaca2` (r:1 w:1) + fn on_idle_overweight_large_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65689` + // Estimated: `69154` + // Minimum execution time: 59_799_000 picoseconds. + Weight::from_parts(61_354_000, 0) + .saturating_add(Weight::from_parts(0, 69154)) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(3)) + } +} diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 1b367f94e33004d8addfdbfcab896b642a0b8e2e..7e0442f0b5856fa5153e29ff497cfee876c067ec 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -1,50 +1,62 @@ [package] name = "cumulus-pallet-parachain-system" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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"] } environmental = { version = "1.1.4", default-features = false } impl-trait-for-tuples = "0.2.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } trie-db = { version = "0.28.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate -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-externalities = { path = "../../../substrate/primitives/externalities", default-features = false} -sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-state-machine = { path = "../../../substrate/primitives/state-machine", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false} -sp-version = { path = "../../../substrate/primitives/version", 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-message-queue = { path = "../../../substrate/frame/message-queue", default-features = false } +sp-core = { path = "../../../substrate/primitives/core", default-features = false } +sp-externalities = { path = "../../../substrate/primitives/externalities", default-features = false } +sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-state-machine = { path = "../../../substrate/primitives/state-machine", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-version = { path = "../../../substrate/primitives/version", default-features = false } # Polkadot -polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false, features = [ "wasm-api" ]} +polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false, features = ["wasm-api"] } polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false} +polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false, optional = true } +xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } # Cumulus cumulus-pallet-parachain-system-proc-macro = { path = "proc-macro", default-features = false } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent", default-features = false } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction", default-features = false } [dev-dependencies] assert_matches = "1.5" hex-literal = "0.4.1" lazy_static = "1.4" +trie-standardmap = "0.16.0" +rand = "0.8.5" +futures = "0.3.28" # Substrate sc-client-api = { path = "../../../substrate/client/api" } sp-keyring = { path = "../../../substrate/primitives/keyring" } +sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-version = { path = "../../../substrate/primitives/version" } @@ -53,17 +65,22 @@ cumulus-test-client = { path = "../../test/client" } cumulus-test-relay-sproof-builder = { path = "../../test/relay-sproof-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ + "bytes/std", "codec/std", "cumulus-pallet-parachain-system-proc-macro/std", "cumulus-primitives-core/std", "cumulus-primitives-parachain-inherent/std", + "cumulus-primitives-proof-size-hostfunction/std", "environmental/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "log/std", + "pallet-message-queue/std", "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-core/std", @@ -73,15 +90,21 @@ std = [ "sp-runtime/std", "sp-state-machine/std", "sp-std/std", + "sp-tracing/std", "sp-trie/std", "trie-db/std", "xcm/std", ] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-test-client/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -89,6 +112,8 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-message-queue/try-runtime", + "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index cb5d9904c7cf3c37e4a0d8fe13fc175888cad20d..0a90c30e0331261026125f429efe70eff07ac069 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -1,19 +1,23 @@ [package] name = "cumulus-pallet-parachain-system-proc-macro" -version = "0.1.0" +version = "0.6.0" authors.workspace = true 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.38" +syn = { workspace = true } proc-macro2 = "1.0.64" -quote = "1.0.33" -proc-macro-crate = "1.3.1" +quote = { workspace = true } +proc-macro-crate = "3.0.0" [features] -default = [ "std" ] +default = ["std"] std = [] diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..5cde8eb5b78857618d7e6a44f777fe7f19305929 --- /dev/null +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking for the parachain-system pallet. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use cumulus_primitives_core::relay_chain::Hash as RelayHash; +use frame_benchmarking::v2::*; +use sp_runtime::traits::BlakeTwo256; + +#[benchmarks] +mod benchmarks { + use super::*; + + /// Enqueue `n` messages via `enqueue_inbound_downward_messages`. + /// + /// The limit is set to `1000` for benchmarking purposes as the actual limit is only known at + /// runtime. However, the limit (and default) for Dotsama are magnitudes smaller. + #[benchmark] + fn enqueue_inbound_downward_messages(n: Linear<0, 1000>) { + let msg = InboundDownwardMessage { + sent_at: n, // The block number does not matter. + msg: vec![0u8; MaxDmpMessageLenOf::::get() as usize], + }; + let msgs = vec![msg; n as usize]; + let head = mqp_head(&msgs); + + #[block] + { + Pallet::::enqueue_inbound_downward_messages(head, msgs); + } + + assert_eq!(ProcessedDownwardMessages::::get(), n); + assert_eq!(LastDmqMqcHead::::get().head(), head); + } + + /// Re-implements an easy version of the `MessageQueueChain` for testing purposes. + fn mqp_head(msgs: &Vec) -> RelayHash { + let mut head = Default::default(); + for msg in msgs.iter() { + let msg_hash = BlakeTwo256::hash_of(&msg.msg); + head = BlakeTwo256::hash_of(&(head, msg.sent_at, msg_hash)); + } + head + } + + impl_benchmark_test_suite! { + Pallet, + crate::mock::new_test_ext(), + crate::mock::Test + } +} diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 369281ccd8ee4fa6d43964f5bd795ea4750acdfd..1c01ef33c7e7534647712609cc48494803946c3b 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -27,20 +27,20 @@ //! //! 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, - DmpMessageHandler, GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, - MessageSendError, OutboundHrmpMessage, ParaId, PersistedValidationData, UpwardMessage, - UpwardMessageSender, XcmpMessageHandler, XcmpMessageSource, + GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, MessageSendError, + OutboundHrmpMessage, ParaId, PersistedValidationData, UpwardMessage, UpwardMessageSender, + XcmpMessageHandler, XcmpMessageSource, }; use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData}; use frame_support::{ + defensive, dispatch::{DispatchResult, Pays, PostDispatchInfo}, ensure, inherent::{InherentData, InherentIdentifier, ProvideInherent}, - storage, - traits::Get, + traits::{Get, HandleMessage}, weights::Weight, }; use frame_system::{ensure_none, ensure_root, pallet_prelude::HeaderFor}; @@ -50,18 +50,22 @@ use scale_info::TypeInfo; use sp_runtime::{ traits::{Block as BlockT, BlockNumberProvider, Hash}, transaction_validity::{ - InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity, - ValidTransaction, + InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, }, - DispatchError, FixedU128, RuntimeDebug, Saturating, + BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*}; use xcm::latest::XcmHash; +mod benchmarking; pub mod migration; - +mod mock; #[cfg(test)] mod tests; +pub mod weights; + +pub use weights::WeightInfo; + mod unincluded_segment; pub mod consensus_hook; @@ -164,19 +168,8 @@ 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; pub mod ump_constants { use super::FixedU128; @@ -196,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)] @@ -212,22 +205,24 @@ pub mod pallet { type OnSystemEvent: OnSystemEvent; /// Returns the parachain ID we are running with. + #[pallet::constant] type SelfParaId: Get; /// The place where outbound XCMP messages come from. This is queried in `finalize_block`. type OutboundXcmpMessageSource: XcmpMessageSource; - /// The message handler that will be invoked when messages are received via DMP. - type DmpMessageHandler: DmpMessageHandler; + /// Queues inbound downward messages for delayed processing. + /// + /// All inbound DMP messages from the relay are pushed into this. The handler is expected to + /// eventually process all the messages that are pushed to it. + type DmpQueue: HandleMessage; /// The weight we reserve at the beginning of the block for processing DMP messages. type ReservedDmpWeight: Get; /// The message handler that will be invoked when messages are received via XCMP. /// - /// The messages are dispatched in the order they were relayed by the relay chain. If - /// multiple messages were relayed at one block, these will be dispatched in ascending - /// order of the sender's para ID. + /// This should normally link to the XCMP Queue pallet. type XcmpMessageHandler: XcmpMessageHandler; /// The weight we reserve at the beginning of the block for processing XCMP messages. @@ -236,6 +231,9 @@ pub mod pallet { /// Something that can check the associated relay parent block number. type CheckAssociatedRelayNumber: CheckAssociatedRelayNumber; + /// Weight info for functions and calls. + type WeightInfo: WeightInfo; + /// An entry-point for higher-level logic to manage the backlog of unincluded parachain /// blocks and authorship rights for those blocks. /// @@ -269,7 +267,7 @@ pub mod pallet { LastRelayChainBlockNumber::::put(vfp.relay_parent_number); - let host_config = match Self::host_configuration() { + let host_config = match HostConfiguration::::get() { Some(ok) => ok, None => { debug_assert!( @@ -283,7 +281,7 @@ pub mod pallet { // Before updating the relevant messaging state, we need to extract // the total bandwidth limits for the purpose of updating the unincluded // segment. - let total_bandwidth_out = match Self::relevant_messaging_state() { + let total_bandwidth_out = match RelevantMessagingState::::get() { Some(s) => OutboundBandwidthLimits::from_relay_chain_state(&s), None => { debug_assert!( @@ -300,7 +298,8 @@ pub mod pallet { Self::adjust_egress_bandwidth_limits(); let (ump_msg_count, ump_total_bytes) = >::mutate(|up| { - let (available_capacity, available_size) = match Self::relevant_messaging_state() { + let (available_capacity, available_size) = match RelevantMessagingState::::get() + { Some(limits) => ( limits.relay_dispatch_queue_remaining_capacity.remaining_count, limits.relay_dispatch_queue_remaining_capacity.remaining_size, @@ -478,7 +477,7 @@ pub mod pallet { // than the announced, we would waste some of weight. In the case the actual value is // greater than the announced, we will miss opportunity to send a couple of messages. weight += T::DbWeight::get().reads_writes(1, 1); - let hrmp_max_message_num_per_candidate = Self::host_configuration() + let hrmp_max_message_num_per_candidate = HostConfiguration::::get() .map(|cfg| cfg.hrmp_max_message_num_per_candidate) .unwrap_or(0); >::put(hrmp_max_message_num_per_candidate); @@ -598,7 +597,7 @@ pub mod pallet { ); let validation_code = >::take(); - Self::put_parachain_code(&validation_code); + frame_system::Pallet::::update_code_in_storage(&validation_code); ::on_validation_code_applied(); Self::deposit_event(Event::ValidationFunctionApplied { relay_chain_block_num: vfp.relay_parent_number, @@ -632,15 +631,15 @@ pub mod pallet { ::on_validation_data(&vfp); - total_weight += Self::process_inbound_downward_messages( + total_weight.saturating_accrue(Self::enqueue_inbound_downward_messages( relevant_messaging_state.dmq_mqc_head, downward_messages, - ); - total_weight += Self::process_inbound_horizontal_messages( + )); + total_weight.saturating_accrue(Self::enqueue_inbound_horizontal_messages( &relevant_messaging_state.ingress_channels, horizontal_messages, vfp.relay_parent_number, - ); + )); Ok(PostDispatchInfo { actual_weight: Some(total_weight), pays_fee: Pays::No }) } @@ -665,16 +664,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(()) } @@ -688,15 +689,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) } } @@ -709,8 +712,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. @@ -764,7 +765,6 @@ pub mod pallet { /// [`:code`][sp_core::storage::well_known_keys::CODE] which will result the next block process /// with the new validation code. This concludes the upgrade process. #[pallet::storage] - #[pallet::getter(fn new_validation_function)] pub(super) type PendingValidationCode = StorageValue<_, Vec, ValueQuery>; /// Validation code that is set by the parachain and is to be communicated to collator and @@ -779,7 +779,6 @@ pub mod pallet { /// This value is expected to be set only once per block and it's never stored /// in the trie. #[pallet::storage] - #[pallet::getter(fn validation_data)] pub(super) type ValidationData = StorageValue<_, PersistedValidationData>; /// Were the validation data set to notify the relay chain? @@ -820,7 +819,6 @@ pub mod pallet { /// /// This data is also absent from the genesis. #[pallet::storage] - #[pallet::getter(fn relay_state_proof)] pub(super) type RelayStateProof = StorageValue<_, sp_trie::StorageProof>; /// The snapshot of some state related to messaging relevant to the current parachain as per @@ -831,7 +829,6 @@ pub mod pallet { /// /// This data is also absent from the genesis. #[pallet::storage] - #[pallet::getter(fn relevant_messaging_state)] pub(super) type RelevantMessagingState = StorageValue<_, MessagingStateSnapshot>; /// The parachain host configuration that was obtained from the relay parent. @@ -841,7 +838,7 @@ pub mod pallet { /// /// This data is also absent from the genesis. #[pallet::storage] - #[pallet::getter(fn host_configuration)] + #[pallet::disable_try_decode_storage] pub(super) type HostConfiguration = StorageValue<_, AbridgedHostConfiguration>; /// The last downward message queue chain head we have observed. @@ -916,10 +913,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. @@ -970,7 +963,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(), @@ -989,21 +983,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 @@ -1058,7 +1037,7 @@ impl GetChannelInfo for Pallet { // // Here it a similar case, with the difference that the realization that the channel is // closed came the same block. - let channels = match Self::relevant_messaging_state() { + let channels = match RelevantMessagingState::::get() { None => { log::warn!("calling `get_channel_status` with no RelevantMessagingState?!"); return ChannelStatus::Closed @@ -1086,7 +1065,7 @@ impl GetChannelInfo for Pallet { } fn get_channel_info(id: ParaId) -> Option { - let channels = Self::relevant_messaging_state()?.egress_channels; + let channels = RelevantMessagingState::::get()?.egress_channels; let index = channels.binary_search_by_key(&id, |item| item.0).ok()?; let info = ChannelInfo { max_capacity: channels[index].1.max_capacity, @@ -1131,7 +1110,7 @@ impl Pallet { // inherent. } - /// Process all inbound downward messages relayed by the collator. + /// Enqueue all inbound downward messages relayed by the collator into the MQ pallet. /// /// Checks if the sequence of the messages is valid, dispatches them and communicates the /// number of processed messages to the collator via a storage update. @@ -1140,26 +1119,33 @@ impl Pallet { /// /// If it turns out that after processing all messages the Message Queue Chain /// hash doesn't match the expected. - fn process_inbound_downward_messages( + fn enqueue_inbound_downward_messages( expected_dmq_mqc_head: relay_chain::Hash, downward_messages: Vec, ) -> Weight { let dm_count = downward_messages.len() as u32; let mut dmq_head = >::get(); - let mut weight_used = Weight::zero(); + let weight_used = T::WeightInfo::enqueue_inbound_downward_messages(dm_count); if dm_count != 0 { Self::deposit_event(Event::DownwardMessagesReceived { count: dm_count }); - let max_weight = - >::get().unwrap_or_else(T::ReservedDmpWeight::get); - - let message_iter = downward_messages - .into_iter() - .inspect(|m| { - dmq_head.extend_downward(m); - }) - .map(|m| (m.sent_at, m.msg)); - weight_used += T::DmpMessageHandler::handle_dmp_messages(message_iter, max_weight); + + // Eagerly update the MQC head hash: + for m in &downward_messages { + dmq_head.extend_downward(m); + } + let bounded = downward_messages + .iter() + // Note: we are not using `.defensive()` here since that prints the whole value to + // console. In case that the message is too long, this clogs up the log quite badly. + .filter_map(|m| match BoundedSlice::try_from(&m.msg[..]) { + Ok(bounded) => Some(bounded), + Err(_) => { + defensive!("Inbound Downward message was too long; dropping"); + None + }, + }); + T::DmpQueue::handle_messages(bounded); >::put(&dmq_head); Self::deposit_event(Event::DownwardMessagesProcessed { @@ -1182,14 +1168,15 @@ impl Pallet { /// Process all inbound horizontal messages relayed by the collator. /// - /// This is similar to `Pallet::process_inbound_downward_messages`, but works on multiple - /// inbound channels. + /// This is similar to [`enqueue_inbound_downward_messages`], but works with multiple inbound + /// channels. It immediately dispatches signals and queues all other XCMs. Blob messages are + /// ignored. /// /// **Panics** if either any of horizontal messages submitted by the collator was sent from /// a para which has no open channel to this parachain or if after processing /// messages across all inbound channels MQCs were obtained which do not /// correspond to the ones found on the relay-chain. - fn process_inbound_horizontal_messages( + fn enqueue_inbound_horizontal_messages( ingress_channels: &[(ParaId, cumulus_primitives_core::AbridgedHrmpChannel)], horizontal_messages: BTreeMap>, relay_parent_number: relay_chain::BlockNumber, @@ -1399,12 +1386,6 @@ impl Pallet { >::put(true); } - /// Put a new validation function into a particular location where this - /// parachain will execute it on subsequent blocks. - fn put_parachain_code(code: &[u8]) { - storage::unhashed::put_raw(sp_core::storage::well_known_keys::CODE, code); - } - /// The maximum code size permitted, in bytes. /// /// Returns `None` if the relay chain parachain host configuration hasn't been submitted yet. @@ -1421,7 +1402,7 @@ impl Pallet { ensure!(>::get().is_none(), Error::::ProhibitedByPolkadot); ensure!(!>::exists(), Error::::OverlappingUpgrades); - let cfg = Self::host_configuration().ok_or(Error::::HostConfigurationNotAvailable)?; + let cfg = HostConfiguration::::get().ok_or(Error::::HostConfigurationNotAvailable)?; ensure!(validation_function.len() <= cfg.max_code_size as usize, Error::::TooBig); // When a code upgrade is scheduled, it has to be applied in two @@ -1549,8 +1530,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) @@ -1577,7 +1558,7 @@ impl Pallet { // may change so that the message is no longer valid. // // However, changing this setting is expected to be rare. - if let Some(cfg) = Self::host_configuration() { + if let Some(cfg) = HostConfiguration::::get() { if message_len > cfg.max_upward_message_size as usize { return Err(MessageSendError::TooBig) } @@ -1616,7 +1597,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() } } @@ -1627,6 +1608,15 @@ impl UpwardMessageSender for Pallet { } } +#[cfg(feature = "runtime-benchmarks")] +impl polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet { + fn ensure(para_id: ParaId) { + if let ChannelStatus::Closed = Self::get_channel_status(para_id) { + Self::open_outbound_hrmp_channel_for_benchmarks_or_tests(para_id) + } + } +} + /// Something that can check the inherents of a block. #[cfg_attr( feature = "parameterized-consensus-hook", @@ -1700,25 +1690,38 @@ 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() + ValidationData::::get() .map(|d| d.relay_parent_number) - .unwrap_or_default() + .unwrap_or_else(|| Pallet::::last_relay_block_number()) } #[cfg(feature = "runtime-benchmarks")] fn set_block_number(block: Self::BlockNumber) { - let mut validation_data = Pallet::::validation_data().unwrap_or_else(|| + let mut validation_data = ValidationData::::get().unwrap_or_else(|| // PersistedValidationData does not impl default in non-std PersistedValidationData { parent_head: vec![].into(), @@ -1733,7 +1736,7 @@ impl BlockNumberProvider for RelaychainBlockNumberProvider { impl RelaychainStateProvider for RelaychainDataProvider { fn current_relay_chain_state() -> RelayChainState { - Pallet::::validation_data() + ValidationData::::get() .map(|d| RelayChainState { number: d.relay_parent_number, state_root: d.relay_parent_storage_root, @@ -1743,7 +1746,7 @@ impl RelaychainStateProvider for RelaychainDataProvider { #[cfg(feature = "runtime-benchmarks")] fn set_current_relay_chain_state(state: RelayChainState) { - let mut validation_data = Pallet::::validation_data().unwrap_or_else(|| + let mut validation_data = ValidationData::::get().unwrap_or_else(|| // PersistedValidationData does not impl default in non-std PersistedValidationData { parent_head: vec![].into(), @@ -1756,33 +1759,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/migration.rs b/cumulus/pallets/parachain-system/src/migration.rs index a92f85b9cd420e9e1027d1f82c643d90fea8b97c..30106aceab5a442c34360563fdfb096fdc6ac9b1 100644 --- a/cumulus/pallets/parachain-system/src/migration.rs +++ b/cumulus/pallets/parachain-system/src/migration.rs @@ -21,7 +21,7 @@ use frame_support::{ weights::Weight, }; -/// The current storage version. +/// The in-code storage version. pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); /// Migrates the pallet storage to the most recent version. diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..0b1d536ba7cd9153d1409a65a87bc8234a09bd95 --- /dev/null +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -0,0 +1,485 @@ +// 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 . + +//! Test setup and helpers. + +#![cfg(test)] + +use super::*; + +use codec::Encode; +use cumulus_primitives_core::{ + relay_chain::BlockNumber as RelayBlockNumber, AggregateMessageOrigin, InboundDownwardMessage, + InboundHrmpMessage, PersistedValidationData, +}; +use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; +use frame_support::{ + derive_impl, + inherent::{InherentData, ProvideInherent}, + parameter_types, + traits::{ + OnFinalize, OnInitialize, ProcessMessage, ProcessMessageError, UnfilteredDispatchable, + }, + weights::{Weight, WeightMeter}, +}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use sp_runtime::{traits::BlakeTwo256, BuildStorage}; +use sp_std::{collections::vec_deque::VecDeque, num::NonZeroU32}; +use sp_version::RuntimeVersion; +use std::cell::RefCell; + +use crate as parachain_system; +use crate::consensus_hook::UnincludedSegmentCapacity; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + ParachainSystem: parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub Version: RuntimeVersion = RuntimeVersion { + spec_name: sp_version::create_runtime_str!("test"), + impl_name: sp_version::create_runtime_str!("system-test"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_version::create_apis_vec!([]), + transaction_version: 1, + state_version: 1, + }; + pub const ParachainId: ParaId = ParaId::new(200); + pub const ReservedXcmpWeight: Weight = Weight::zero(); + pub const ReservedDmpWeight: Weight = Weight::zero(); +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type BlockHashCount = BlockHashCount; + type Version = Version; + type OnSetCode = ParachainSetCode; +} + +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = ParachainId; + type OutboundXcmpMessageSource = FromThreadLocal; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + type ReservedDmpWeight = ReservedDmpWeight; + type XcmpMessageHandler = SaveIntoThreadLocal; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = AnyRelayNumber; + type ConsensusHook = TestConsensusHook; + type WeightInfo = (); +} + +std::thread_local! { + pub static CONSENSUS_HOOK: RefCell (Weight, UnincludedSegmentCapacity)>> + = RefCell::new(Box::new(|_| (Weight::zero(), NonZeroU32::new(1).unwrap().into()))); +} + +pub struct TestConsensusHook; + +impl ConsensusHook for TestConsensusHook { + fn on_state_proof(s: &RelayChainStateProof) -> (Weight, UnincludedSegmentCapacity) { + CONSENSUS_HOOK.with(|f| f.borrow_mut()(s)) + } +} + +parameter_types! { + pub const MaxWeight: Weight = Weight::MAX; +} + +impl pallet_message_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + // NOTE that normally for benchmarking we should use the No-OP message processor, but in this + // case its a mocked runtime and will only be used to generate insecure default weights. + type MessageProcessor = SaveIntoThreadLocal; + type Size = u32; + type QueueChangeHandler = (); + type QueuePausedQuery = (); + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MaxWeight; + type WeightInfo = (); +} + +/// A `XcmpMessageSource` that takes messages from thread-local. +pub struct FromThreadLocal; + +/// A `MessageProcessor` that stores all messages in thread-local. +pub struct SaveIntoThreadLocal; + +std::thread_local! { + pub static HANDLED_DMP_MESSAGES: RefCell>> = RefCell::new(Vec::new()); + pub static HANDLED_XCMP_MESSAGES: RefCell)>> = RefCell::new(Vec::new()); + pub static SENT_MESSAGES: RefCell)>> = RefCell::new(Vec::new()); +} + +pub fn send_message(dest: ParaId, message: Vec) { + SENT_MESSAGES.with(|m| m.borrow_mut().push((dest, message))); +} + +impl XcmpMessageSource for FromThreadLocal { + fn take_outbound_messages(maximum_channels: usize) -> Vec<(ParaId, Vec)> { + let mut ids = std::collections::BTreeSet::::new(); + let mut taken_messages = 0; + let mut taken_bytes = 0; + let mut result = Vec::new(); + SENT_MESSAGES.with(|ms| { + ms.borrow_mut().retain(|m| { + let status = as GetChannelInfo>::get_channel_status(m.0); + let (max_size_now, max_size_ever) = match status { + ChannelStatus::Ready(now, ever) => (now, ever), + ChannelStatus::Closed => return false, // drop message + ChannelStatus::Full => return true, // keep message queued. + }; + + let msg_len = m.1.len(); + + if !ids.contains(&m.0) && + taken_messages < maximum_channels && + msg_len <= max_size_ever && + taken_bytes + msg_len <= max_size_now + { + ids.insert(m.0); + taken_messages += 1; + taken_bytes += msg_len; + result.push(m.clone()); + false + } else { + true + } + }) + }); + result + } +} + +impl ProcessMessage for SaveIntoThreadLocal { + type Origin = AggregateMessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + _meter: &mut WeightMeter, + _id: &mut [u8; 32], + ) -> Result { + assert_eq!(origin, Self::Origin::Parent); + + HANDLED_DMP_MESSAGES.with(|m| { + m.borrow_mut().push(message.to_vec()); + Weight::zero() + }); + Ok(true) + } +} + +impl XcmpMessageHandler for SaveIntoThreadLocal { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + _max_weight: Weight, + ) -> Weight { + HANDLED_XCMP_MESSAGES.with(|m| { + for (sender, sent_at, message) in iter { + m.borrow_mut().push((sender, sent_at, message.to_vec())); + } + Weight::zero() + }) + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + HANDLED_DMP_MESSAGES.with(|m| m.borrow_mut().clear()); + HANDLED_XCMP_MESSAGES.with(|m| m.borrow_mut().clear()); + + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} + +#[allow(dead_code)] +pub fn mk_dmp(sent_at: u32) -> InboundDownwardMessage { + InboundDownwardMessage { sent_at, msg: format!("down{}", sent_at).into_bytes() } +} + +pub fn mk_hrmp(sent_at: u32) -> InboundHrmpMessage { + InboundHrmpMessage { sent_at, data: format!("{}", sent_at).into_bytes() } +} + +pub struct ReadRuntimeVersion(pub Vec); + +impl sp_core::traits::ReadRuntimeVersion for ReadRuntimeVersion { + fn read_runtime_version( + &self, + _wasm_code: &[u8], + _ext: &mut dyn sp_externalities::Externalities, + ) -> Result, String> { + Ok(self.0.clone()) + } +} + +pub fn wasm_ext() -> sp_io::TestExternalities { + let version = RuntimeVersion { + spec_name: "test".into(), + spec_version: 2, + impl_version: 1, + ..Default::default() + }; + + let mut ext = new_test_ext(); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(ReadRuntimeVersion( + version.encode(), + ))); + ext +} + +pub struct BlockTest { + n: BlockNumberFor, + within_block: Box, + after_block: Option>, +} + +/// BlockTests exist to test blocks with some setup: we have to assume that +/// `validate_block` will mutate and check storage in certain predictable +/// ways, for example, and we want to always ensure that tests are executed +/// in the context of some particular block number. +#[derive(Default)] +pub struct BlockTests { + tests: Vec, + without_externalities: bool, + pending_upgrade: Option, + ran: bool, + relay_sproof_builder_hook: + Option>, + inherent_data_hook: + Option>, + inclusion_delay: Option, + relay_block_number: Option) -> RelayChainBlockNumber>>, + + included_para_head: Option, + pending_blocks: VecDeque, +} + +impl BlockTests { + pub fn new() -> BlockTests { + Default::default() + } + + pub fn new_without_externalities() -> BlockTests { + let mut tests = BlockTests::new(); + tests.without_externalities = true; + tests + } + + pub fn add_raw(mut self, test: BlockTest) -> Self { + self.tests.push(test); + self + } + + pub fn add(self, n: BlockNumberFor, within_block: F) -> Self + where + F: 'static + Fn(), + { + self.add_raw(BlockTest { n, within_block: Box::new(within_block), after_block: None }) + } + + pub fn add_with_post_test( + self, + n: BlockNumberFor, + within_block: F1, + after_block: F2, + ) -> Self + where + F1: 'static + Fn(), + F2: 'static + Fn(), + { + self.add_raw(BlockTest { + n, + within_block: Box::new(within_block), + after_block: Some(Box::new(after_block)), + }) + } + + pub fn with_relay_sproof_builder(mut self, f: F) -> Self + where + F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut RelayStateSproofBuilder), + { + self.relay_sproof_builder_hook = Some(Box::new(f)); + self + } + + pub fn with_relay_block_number(mut self, f: F) -> Self + where + F: 'static + Fn(&BlockNumberFor) -> RelayChainBlockNumber, + { + self.relay_block_number = Some(Box::new(f)); + self + } + + pub fn with_inherent_data(mut self, f: F) -> Self + where + F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut ParachainInherentData), + { + self.inherent_data_hook = Some(Box::new(f)); + self + } + + pub fn with_inclusion_delay(mut self, inclusion_delay: usize) -> Self { + self.inclusion_delay.replace(inclusion_delay); + self + } + + pub fn run(&mut self) { + wasm_ext().execute_with(|| { + self.run_without_ext(); + }); + } + + pub fn run_without_ext(&mut self) { + self.ran = true; + + let mut parent_head_data = { + let header = HeaderFor::::new_from_number(0); + relay_chain::HeadData(header.encode()) + }; + + self.included_para_head = Some(parent_head_data.clone()); + + for BlockTest { n, within_block, after_block } in self.tests.iter() { + let relay_parent_number = self + .relay_block_number + .as_ref() + .map(|f| f(n)) + .unwrap_or(*n as RelayChainBlockNumber); + // clear pending updates, as applicable + if let Some(upgrade_block) = self.pending_upgrade { + if n >= &upgrade_block.into() { + self.pending_upgrade = None; + } + } + + // begin initialization + let parent_hash = BlakeTwo256::hash(&parent_head_data.0); + System::reset_events(); + System::initialize(n, &parent_hash, &Default::default()); + + // now mess with the storage the way validate_block does + let mut sproof_builder = RelayStateSproofBuilder::default(); + sproof_builder.included_para_head = self + .included_para_head + .clone() + .unwrap_or_else(|| parent_head_data.clone()) + .into(); + if let Some(ref hook) = self.relay_sproof_builder_hook { + hook(self, relay_parent_number, &mut sproof_builder); + } + let (relay_parent_storage_root, relay_chain_state) = + sproof_builder.into_state_root_and_proof(); + let vfp = PersistedValidationData { + relay_parent_number, + relay_parent_storage_root, + ..Default::default() + }; + + >::put(&vfp); + NewValidationCode::::kill(); + + // It is insufficient to push the validation function params + // to storage; they must also be included in the inherent data. + let inherent_data = { + let mut inherent_data = InherentData::default(); + let mut system_inherent_data = ParachainInherentData { + validation_data: vfp.clone(), + relay_chain_state, + downward_messages: Default::default(), + horizontal_messages: Default::default(), + }; + if let Some(ref hook) = self.inherent_data_hook { + hook(self, relay_parent_number, &mut system_inherent_data); + } + inherent_data + .put_data( + cumulus_primitives_parachain_inherent::INHERENT_IDENTIFIER, + &system_inherent_data, + ) + .expect("failed to put VFP inherent"); + inherent_data + }; + + // execute the block + ParachainSystem::on_initialize(*n); + ParachainSystem::create_inherent(&inherent_data) + .expect("got an inherent") + .dispatch_bypass_filter(RawOrigin::None.into()) + .expect("dispatch succeeded"); + MessageQueue::on_initialize(*n); + within_block(); + MessageQueue::on_finalize(*n); + ParachainSystem::on_finalize(*n); + + // did block execution set new validation code? + if NewValidationCode::::exists() && self.pending_upgrade.is_some() { + panic!("attempted to set validation code while upgrade was pending"); + } + + // clean up + let header = System::finalize(); + let head_data = relay_chain::HeadData(header.encode()); + parent_head_data = head_data.clone(); + match self.inclusion_delay { + Some(delay) if delay > 0 => { + self.pending_blocks.push_back(head_data); + if self.pending_blocks.len() > delay { + let included = self.pending_blocks.pop_front().unwrap(); + + self.included_para_head.replace(included); + } + }, + _ => { + self.included_para_head.replace(head_data); + }, + } + + if let Some(after_block) = after_block { + after_block(); + } + } + } +} + +impl Drop for BlockTests { + fn drop(&mut self) { + if !self.ran { + if self.without_externalities { + self.run_without_ext(); + } else { + self.run(); + } + } + } +} diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 3f5b4f649e3231035d4e2d031ed6b574bb443f38..5ff15036fb6e48b5e1a0567730b1f9035357edc2 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -13,433 +13,20 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . + +#![cfg(test)] + use super::*; +use crate::mock::*; -use codec::Encode; -use cumulus_primitives_core::{ - relay_chain::BlockNumber as RelayBlockNumber, AbridgedHrmpChannel, InboundDownwardMessage, - InboundHrmpMessage, PersistedValidationData, -}; -use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; -use frame_support::{ - assert_ok, - inherent::{InherentData, ProvideInherent}, - parameter_types, - traits::{OnFinalize, OnInitialize, UnfilteredDispatchable}, - weights::Weight, -}; -use frame_system::{ - pallet_prelude::{BlockNumberFor, HeaderFor}, - RawOrigin, -}; +use cumulus_primitives_core::{AbridgedHrmpChannel, InboundDownwardMessage, InboundHrmpMessage}; +use frame_support::{assert_ok, parameter_types, weights::Weight}; +use frame_system::RawOrigin; use hex_literal::hex; +use rand::Rng; use relay_chain::HrmpChannelId; -use sp_core::{blake2_256, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, DispatchErrorWithPostInfo, -}; -use sp_std::{collections::vec_deque::VecDeque, num::NonZeroU32}; -use sp_version::RuntimeVersion; -use std::cell::RefCell; - -use crate as parachain_system; -use crate::consensus_hook::UnincludedSegmentCapacity; - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - ParachainSystem: parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub Version: RuntimeVersion = RuntimeVersion { - spec_name: sp_version::create_runtime_str!("test"), - impl_name: sp_version::create_runtime_str!("system-test"), - authoring_version: 1, - spec_version: 1, - impl_version: 1, - apis: sp_version::create_apis_vec!([]), - transaction_version: 1, - state_version: 1, - }; - pub const ParachainId: ParaId = ParaId::new(200); - pub const ReservedXcmpWeight: Weight = Weight::zero(); - pub const ReservedDmpWeight: Weight = Weight::zero(); -} -impl frame_system::Config for Test { - 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 = BlockHashCount; - type BlockLength = (); - type BlockWeights = (); - type Version = Version; - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = ParachainSetCode; - type MaxConsumers = frame_support::traits::ConstU32<16>; -} -impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); - type SelfParaId = ParachainId; - type OutboundXcmpMessageSource = FromThreadLocal; - type DmpMessageHandler = SaveIntoThreadLocal; - type ReservedDmpWeight = ReservedDmpWeight; - type XcmpMessageHandler = SaveIntoThreadLocal; - type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = AnyRelayNumber; - type ConsensusHook = TestConsensusHook; -} - -pub struct FromThreadLocal; -pub struct SaveIntoThreadLocal; - -std::thread_local! { - static HANDLED_DMP_MESSAGES: RefCell)>> = RefCell::new(Vec::new()); - static HANDLED_XCMP_MESSAGES: RefCell)>> = RefCell::new(Vec::new()); - static SENT_MESSAGES: RefCell)>> = RefCell::new(Vec::new()); - static CONSENSUS_HOOK: RefCell (Weight, UnincludedSegmentCapacity)>> - = RefCell::new(Box::new(|_| (Weight::zero(), NonZeroU32::new(1).unwrap().into()))); -} - -pub struct TestConsensusHook; - -impl ConsensusHook for TestConsensusHook { - fn on_state_proof(s: &RelayChainStateProof) -> (Weight, UnincludedSegmentCapacity) { - CONSENSUS_HOOK.with(|f| f.borrow_mut()(s)) - } -} - -fn send_message(dest: ParaId, message: Vec) { - SENT_MESSAGES.with(|m| m.borrow_mut().push((dest, message))); -} - -impl XcmpMessageSource for FromThreadLocal { - fn take_outbound_messages(maximum_channels: usize) -> Vec<(ParaId, Vec)> { - let mut ids = std::collections::BTreeSet::::new(); - let mut taken_messages = 0; - let mut taken_bytes = 0; - let mut result = Vec::new(); - SENT_MESSAGES.with(|ms| { - ms.borrow_mut().retain(|m| { - let status = as GetChannelInfo>::get_channel_status(m.0); - let (max_size_now, max_size_ever) = match status { - ChannelStatus::Ready(now, ever) => (now, ever), - ChannelStatus::Closed => return false, // drop message - ChannelStatus::Full => return true, // keep message queued. - }; - - let msg_len = m.1.len(); - - if !ids.contains(&m.0) && - taken_messages < maximum_channels && - msg_len <= max_size_ever && - taken_bytes + msg_len <= max_size_now - { - ids.insert(m.0); - taken_messages += 1; - taken_bytes += msg_len; - result.push(m.clone()); - false - } else { - true - } - }) - }); - result - } -} - -impl DmpMessageHandler for SaveIntoThreadLocal { - fn handle_dmp_messages( - iter: impl Iterator)>, - _max_weight: Weight, - ) -> Weight { - HANDLED_DMP_MESSAGES.with(|m| { - for i in iter { - m.borrow_mut().push(i); - } - Weight::zero() - }) - } -} - -impl XcmpMessageHandler for SaveIntoThreadLocal { - fn handle_xcmp_messages<'a, I: Iterator>( - iter: I, - _max_weight: Weight, - ) -> Weight { - HANDLED_XCMP_MESSAGES.with(|m| { - for (sender, sent_at, message) in iter { - m.borrow_mut().push((sender, sent_at, message.to_vec())); - } - Weight::zero() - }) - } -} - -// This function basically just builds a genesis storage key/value store according to -// our desired mockup. -fn new_test_ext() -> sp_io::TestExternalities { - HANDLED_DMP_MESSAGES.with(|m| m.borrow_mut().clear()); - HANDLED_XCMP_MESSAGES.with(|m| m.borrow_mut().clear()); - - frame_system::GenesisConfig::::default().build_storage().unwrap().into() -} - -struct ReadRuntimeVersion(Vec); - -impl sp_core::traits::ReadRuntimeVersion for ReadRuntimeVersion { - fn read_runtime_version( - &self, - _wasm_code: &[u8], - _ext: &mut dyn sp_externalities::Externalities, - ) -> Result, String> { - Ok(self.0.clone()) - } -} - -fn wasm_ext() -> sp_io::TestExternalities { - let version = RuntimeVersion { - spec_name: "test".into(), - spec_version: 2, - impl_version: 1, - ..Default::default() - }; - - let mut ext = new_test_ext(); - ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(ReadRuntimeVersion( - version.encode(), - ))); - ext -} - -struct BlockTest { - n: BlockNumberFor, - within_block: Box, - after_block: Option>, -} - -/// BlockTests exist to test blocks with some setup: we have to assume that -/// `validate_block` will mutate and check storage in certain predictable -/// ways, for example, and we want to always ensure that tests are executed -/// in the context of some particular block number. -#[derive(Default)] -struct BlockTests { - tests: Vec, - pending_upgrade: Option, - ran: bool, - relay_sproof_builder_hook: - Option>, - inherent_data_hook: - Option>, - inclusion_delay: Option, - relay_block_number: Option) -> RelayChainBlockNumber>>, - - included_para_head: Option, - pending_blocks: VecDeque, -} - -impl BlockTests { - fn new() -> BlockTests { - Default::default() - } - - fn add_raw(mut self, test: BlockTest) -> Self { - self.tests.push(test); - self - } - - fn add(self, n: BlockNumberFor, within_block: F) -> Self - where - F: 'static + Fn(), - { - self.add_raw(BlockTest { n, within_block: Box::new(within_block), after_block: None }) - } - - fn add_with_post_test( - self, - n: BlockNumberFor, - within_block: F1, - after_block: F2, - ) -> Self - where - F1: 'static + Fn(), - F2: 'static + Fn(), - { - self.add_raw(BlockTest { - n, - within_block: Box::new(within_block), - after_block: Some(Box::new(after_block)), - }) - } - - fn with_relay_sproof_builder(mut self, f: F) -> Self - where - F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut RelayStateSproofBuilder), - { - self.relay_sproof_builder_hook = Some(Box::new(f)); - self - } - - fn with_relay_block_number(mut self, f: F) -> Self - where - F: 'static + Fn(&BlockNumberFor) -> RelayChainBlockNumber, - { - self.relay_block_number = Some(Box::new(f)); - self - } - - fn with_inherent_data(mut self, f: F) -> Self - where - F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut ParachainInherentData), - { - self.inherent_data_hook = Some(Box::new(f)); - self - } - - fn with_inclusion_delay(mut self, inclusion_delay: usize) -> Self { - self.inclusion_delay.replace(inclusion_delay); - self - } - - fn run(&mut self) { - self.ran = true; - wasm_ext().execute_with(|| { - let mut parent_head_data = { - let header = HeaderFor::::new_from_number(0); - relay_chain::HeadData(header.encode()) - }; - - self.included_para_head = Some(parent_head_data.clone()); - - for BlockTest { n, within_block, after_block } in self.tests.iter() { - let relay_parent_number = self - .relay_block_number - .as_ref() - .map(|f| f(n)) - .unwrap_or(*n as RelayChainBlockNumber); - // clear pending updates, as applicable - if let Some(upgrade_block) = self.pending_upgrade { - if n >= &upgrade_block.into() { - self.pending_upgrade = None; - } - } - - // begin initialization - let parent_hash = BlakeTwo256::hash(&parent_head_data.0); - System::reset_events(); - System::initialize(n, &parent_hash, &Default::default()); - - // now mess with the storage the way validate_block does - let mut sproof_builder = RelayStateSproofBuilder::default(); - sproof_builder.included_para_head = self - .included_para_head - .clone() - .unwrap_or_else(|| parent_head_data.clone()) - .into(); - if let Some(ref hook) = self.relay_sproof_builder_hook { - hook(self, relay_parent_number, &mut sproof_builder); - } - let (relay_parent_storage_root, relay_chain_state) = - sproof_builder.into_state_root_and_proof(); - let vfp = PersistedValidationData { - relay_parent_number, - relay_parent_storage_root, - ..Default::default() - }; - - >::put(&vfp); - NewValidationCode::::kill(); - - // It is insufficient to push the validation function params - // to storage; they must also be included in the inherent data. - let inherent_data = { - let mut inherent_data = InherentData::default(); - let mut system_inherent_data = ParachainInherentData { - validation_data: vfp.clone(), - relay_chain_state, - downward_messages: Default::default(), - horizontal_messages: Default::default(), - }; - if let Some(ref hook) = self.inherent_data_hook { - hook(self, relay_parent_number, &mut system_inherent_data); - } - inherent_data - .put_data( - cumulus_primitives_parachain_inherent::INHERENT_IDENTIFIER, - &system_inherent_data, - ) - .expect("failed to put VFP inherent"); - inherent_data - }; - - // execute the block - ParachainSystem::on_initialize(*n); - ParachainSystem::create_inherent(&inherent_data) - .expect("got an inherent") - .dispatch_bypass_filter(RawOrigin::None.into()) - .expect("dispatch succeeded"); - within_block(); - ParachainSystem::on_finalize(*n); - - // did block execution set new validation code? - if NewValidationCode::::exists() && self.pending_upgrade.is_some() { - panic!("attempted to set validation code while upgrade was pending"); - } - - // clean up - let header = System::finalize(); - let head_data = relay_chain::HeadData(header.encode()); - parent_head_data = head_data.clone(); - match self.inclusion_delay { - Some(delay) if delay > 0 => { - self.pending_blocks.push_back(head_data); - if self.pending_blocks.len() > delay { - let included = self.pending_blocks.pop_front().unwrap(); - - self.included_para_head.replace(included); - } - }, - _ => { - self.included_para_head.replace(head_data); - }, - } - - if let Some(after_block) = after_block { - after_block(); - } - } - }); - } -} - -impl Drop for BlockTests { - fn drop(&mut self) { - if !self.ran { - self.run(); - } - } -} +use sp_core::H256; +use sp_std::num::NonZeroU32; #[test] #[should_panic] @@ -659,30 +246,6 @@ fn inherent_processed_messages_are_ignored() { CONSENSUS_HOOK.with(|c| { *c.borrow_mut() = Box::new(|_| (Weight::zero(), NonZeroU32::new(2).unwrap().into())) }); - lazy_static::lazy_static! { - static ref DMQ_MSG: InboundDownwardMessage = InboundDownwardMessage { - sent_at: 3, - msg: b"down".to_vec(), - }; - - static ref XCMP_MSG_1: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 2, - data: b"h1".to_vec(), - }; - - static ref XCMP_MSG_2: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 3, - data: b"h2".to_vec(), - }; - - static ref EXPECTED_PROCESSED_DMQ: Vec<(RelayChainBlockNumber, Vec)> = vec![ - (DMQ_MSG.sent_at, DMQ_MSG.msg.clone()) - ]; - static ref EXPECTED_PROCESSED_XCMP: Vec<(ParaId, RelayChainBlockNumber, Vec)> = vec![ - (ParaId::from(200), XCMP_MSG_1.sent_at, XCMP_MSG_1.data.clone()), - (ParaId::from(200), XCMP_MSG_2.sent_at, XCMP_MSG_2.data.clone()), - ]; - } BlockTests::new() .with_inclusion_delay(1) @@ -690,11 +253,11 @@ fn inherent_processed_messages_are_ignored() { .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { 3 => { sproof.dmq_mqc_head = - Some(MessageQueueChain::default().extend_downward(&DMQ_MSG).head()); + Some(MessageQueueChain::default().extend_downward(&mk_dmp(3)).head()); sproof.upsert_inbound_channel(ParaId::from(200)).mqc_head = Some( MessageQueueChain::default() - .extend_hrmp(&XCMP_MSG_1) - .extend_hrmp(&XCMP_MSG_2) + .extend_hrmp(&mk_hrmp(2)) + .extend_hrmp(&mk_hrmp(3)) .head(), ); }, @@ -702,9 +265,8 @@ fn inherent_processed_messages_are_ignored() { }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 3 => { - data.downward_messages.push(DMQ_MSG.clone()); - data.horizontal_messages - .insert(ParaId::from(200), vec![XCMP_MSG_1.clone(), XCMP_MSG_2.clone()]); + data.downward_messages.push(mk_dmp(3)); + data.horizontal_messages.insert(ParaId::from(200), vec![mk_hrmp(2), mk_hrmp(3)]); }, _ => unreachable!(), }) @@ -712,22 +274,29 @@ fn inherent_processed_messages_are_ignored() { // Don't drop processed messages for this test. HANDLED_DMP_MESSAGES.with(|m| { let m = m.borrow(); - assert_eq!(&*m, EXPECTED_PROCESSED_DMQ.as_slice()); + // NOTE: if this fails, then run the test without benchmark features. + assert_eq!(&*m, &[mk_dmp(3).msg]); }); HANDLED_XCMP_MESSAGES.with(|m| { let m = m.borrow_mut(); - assert_eq!(&*m, EXPECTED_PROCESSED_XCMP.as_slice()); + assert_eq!( + &*m, + &[(ParaId::from(200), 2, b"2".to_vec()), (ParaId::from(200), 3, b"3".to_vec()),] + ); }); }) .add(2, || {}) .add(3, || { HANDLED_DMP_MESSAGES.with(|m| { let m = m.borrow(); - assert_eq!(&*m, EXPECTED_PROCESSED_DMQ.as_slice()); + assert_eq!(&*m, &[mk_dmp(3).msg]); }); HANDLED_XCMP_MESSAGES.with(|m| { let m = m.borrow_mut(); - assert_eq!(&*m, EXPECTED_PROCESSED_XCMP.as_slice()); + assert_eq!( + &*m, + &[(ParaId::from(200), 2, b"2".to_vec()), (ParaId::from(200), 3, b"3".to_vec()),] + ); }); }); } @@ -869,7 +438,7 @@ fn hrmp_outbound_respects_used_bandwidth() { } #[test] -fn events() { +fn runtime_upgrade_events() { BlockTests::new() .with_relay_sproof_builder(|_, block_number, builder| { if block_number > 123 { @@ -894,12 +463,20 @@ fn events() { || {}, || { let events = System::events(); + + assert_eq!(events[0].event, RuntimeEvent::System(frame_system::Event::CodeUpdated)); + assert_eq!( - events[0].event, + events[1].event, RuntimeEvent::ParachainSystem(crate::Event::ValidationFunctionApplied { relay_chain_block_num: 1234 }) ); + + assert!(System::digest() + .logs() + .iter() + .any(|d| *d == sp_runtime::generic::DigestItem::RuntimeEnvironmentUpdated)); }, ); } @@ -1175,6 +752,7 @@ fn message_queue_chain() { } #[test] +#[cfg(not(feature = "runtime-benchmarks"))] fn receive_dmp() { lazy_static::lazy_static! { static ref MSG: InboundDownwardMessage = InboundDownwardMessage { @@ -1200,41 +778,31 @@ fn receive_dmp() { .add(1, || { HANDLED_DMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(MSG.sent_at, MSG.msg.clone())]); + assert_eq!(&*m, &[(MSG.msg.clone())]); m.clear(); }); }); } #[test] +#[cfg(not(feature = "runtime-benchmarks"))] fn receive_dmp_after_pause() { - lazy_static::lazy_static! { - static ref MSG_1: InboundDownwardMessage = InboundDownwardMessage { - sent_at: 1, - msg: b"down1".to_vec(), - }; - static ref MSG_2: InboundDownwardMessage = InboundDownwardMessage { - sent_at: 3, - msg: b"down2".to_vec(), - }; - } - BlockTests::new() .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { 1 => { sproof.dmq_mqc_head = - Some(MessageQueueChain::default().extend_downward(&MSG_1).head()); + Some(MessageQueueChain::default().extend_downward(&mk_dmp(1)).head()); }, 2 => { // no new messages, mqc stayed the same. sproof.dmq_mqc_head = - Some(MessageQueueChain::default().extend_downward(&MSG_1).head()); + Some(MessageQueueChain::default().extend_downward(&mk_dmp(1)).head()); }, 3 => { sproof.dmq_mqc_head = Some( MessageQueueChain::default() - .extend_downward(&MSG_1) - .extend_downward(&MSG_2) + .extend_downward(&mk_dmp(1)) + .extend_downward(&mk_dmp(3)) .head(), ); }, @@ -1242,20 +810,20 @@ fn receive_dmp_after_pause() { }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 1 => { - data.downward_messages.push(MSG_1.clone()); + data.downward_messages.push(mk_dmp(1)); }, 2 => { // no new messages }, 3 => { - data.downward_messages.push(MSG_2.clone()); + data.downward_messages.push(mk_dmp(3)); }, _ => unreachable!(), }) .add(1, || { HANDLED_DMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(MSG_1.sent_at, MSG_1.msg.clone())]); + assert_eq!(&*m, &[(mk_dmp(1).msg.clone())]); m.clear(); }); }) @@ -1263,54 +831,88 @@ fn receive_dmp_after_pause() { .add(3, || { HANDLED_DMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(MSG_2.sent_at, MSG_2.msg.clone())]); + assert_eq!(&*m, &[(mk_dmp(3).msg.clone())]); m.clear(); }); }); } +// Sent up to 100 DMP messages per block over a period of 100 blocks. #[test] -fn receive_hrmp() { - lazy_static::lazy_static! { - static ref MSG_1: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 1, - data: b"1".to_vec(), - }; +#[cfg(not(feature = "runtime-benchmarks"))] +fn receive_dmp_many() { + wasm_ext().execute_with(|| { + parameter_types! { + pub storage MqcHead: MessageQueueChain = Default::default(); + pub storage SentInBlock: Vec> = Default::default(); + } - static ref MSG_2: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 2, - data: b"2".to_vec(), - }; + let mut sent_in_block = vec![vec![]]; + let mut rng = rand::thread_rng(); - static ref MSG_3: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 2, - data: b"3".to_vec(), - }; + for block in 1..100 { + let mut msgs = vec![]; + for _ in 1..=rng.gen_range(1..=100) { + // Just use the same message multiple times per block. + msgs.push(mk_dmp(block)); + } + sent_in_block.push(msgs); + } + SentInBlock::set(&sent_in_block); - static ref MSG_4: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 2, - data: b"4".to_vec(), - }; - } + let mut tester = BlockTests::new_without_externalities() + .with_relay_sproof_builder(|_, relay_block_num, sproof| { + let mut new_hash = MqcHead::get(); + for msg in SentInBlock::get()[relay_block_num as usize].iter() { + new_hash.extend_downward(&msg); + } + + sproof.dmq_mqc_head = Some(new_hash.head()); + MqcHead::set(&new_hash); + }) + .with_inherent_data(|_, relay_block_num, data| { + for msg in SentInBlock::get()[relay_block_num as usize].iter() { + data.downward_messages.push(msg.clone()); + } + }); + + for block in 1..100 { + tester = tester.add(block, move || { + HANDLED_DMP_MESSAGES.with(|m| { + let mut m = m.borrow_mut(); + let msgs = SentInBlock::get()[block as usize] + .iter() + .map(|m| m.msg.clone()) + .collect::>(); + assert_eq!(&*m, &msgs); + m.clear(); + }); + }); + } + }); +} + +#[test] +fn receive_hrmp() { BlockTests::new() .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { 1 => { // 200 - doesn't exist yet // 300 - one new message sproof.upsert_inbound_channel(ParaId::from(300)).mqc_head = - Some(MessageQueueChain::default().extend_hrmp(&MSG_1).head()); + Some(MessageQueueChain::default().extend_hrmp(&mk_hrmp(1)).head()); }, 2 => { // 200 - now present with one message // 300 - two new messages sproof.upsert_inbound_channel(ParaId::from(200)).mqc_head = - Some(MessageQueueChain::default().extend_hrmp(&MSG_4).head()); + Some(MessageQueueChain::default().extend_hrmp(&mk_hrmp(4)).head()); sproof.upsert_inbound_channel(ParaId::from(300)).mqc_head = Some( MessageQueueChain::default() - .extend_hrmp(&MSG_1) - .extend_hrmp(&MSG_2) - .extend_hrmp(&MSG_3) + .extend_hrmp(&mk_hrmp(1)) + .extend_hrmp(&mk_hrmp(2)) + .extend_hrmp(&mk_hrmp(3)) .head(), ); }, @@ -1318,13 +920,13 @@ fn receive_hrmp() { // 200 - no new messages // 300 - is gone sproof.upsert_inbound_channel(ParaId::from(200)).mqc_head = - Some(MessageQueueChain::default().extend_hrmp(&MSG_4).head()); + Some(MessageQueueChain::default().extend_hrmp(&mk_hrmp(4)).head()); }, _ => unreachable!(), }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 1 => { - data.horizontal_messages.insert(ParaId::from(300), vec![MSG_1.clone()]); + data.horizontal_messages.insert(ParaId::from(300), vec![mk_hrmp(1)]); }, 2 => { data.horizontal_messages.insert( @@ -1333,11 +935,11 @@ fn receive_hrmp() { // can't be sent at the block 1 actually. However, we cheat here // because we want to test the case where there are multiple messages // but the harness at the moment doesn't support block skipping. - MSG_2.clone(), - MSG_3.clone(), + mk_hrmp(2).clone(), + mk_hrmp(3).clone(), ], ); - data.horizontal_messages.insert(ParaId::from(200), vec![MSG_4.clone()]); + data.horizontal_messages.insert(ParaId::from(200), vec![mk_hrmp(4)]); }, 3 => {}, _ => unreachable!(), @@ -1355,9 +957,9 @@ fn receive_hrmp() { assert_eq!( &*m, &[ - (ParaId::from(200), 2, b"4".to_vec()), (ParaId::from(300), 2, b"2".to_vec()), - (ParaId::from(300), 2, b"3".to_vec()), + (ParaId::from(300), 3, b"3".to_vec()), + (ParaId::from(200), 4, b"4".to_vec()), ] ); m.clear(); @@ -1386,55 +988,46 @@ fn receive_hrmp_empty_channel() { #[test] fn receive_hrmp_after_pause() { - lazy_static::lazy_static! { - static ref MSG_1: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 1, - data: b"mikhailinvanovich".to_vec(), - }; - - static ref MSG_2: InboundHrmpMessage = InboundHrmpMessage { - sent_at: 3, - data: b"1000000000".to_vec(), - }; - } - const ALICE: ParaId = ParaId::new(300); BlockTests::new() .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { 1 => { sproof.upsert_inbound_channel(ALICE).mqc_head = - Some(MessageQueueChain::default().extend_hrmp(&MSG_1).head()); + Some(MessageQueueChain::default().extend_hrmp(&mk_hrmp(1)).head()); }, 2 => { // 300 - no new messages, mqc stayed the same. sproof.upsert_inbound_channel(ALICE).mqc_head = - Some(MessageQueueChain::default().extend_hrmp(&MSG_1).head()); + Some(MessageQueueChain::default().extend_hrmp(&mk_hrmp(1)).head()); }, 3 => { // 300 - new message. sproof.upsert_inbound_channel(ALICE).mqc_head = Some( - MessageQueueChain::default().extend_hrmp(&MSG_1).extend_hrmp(&MSG_2).head(), + MessageQueueChain::default() + .extend_hrmp(&mk_hrmp(1)) + .extend_hrmp(&mk_hrmp(3)) + .head(), ); }, _ => unreachable!(), }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 1 => { - data.horizontal_messages.insert(ALICE, vec![MSG_1.clone()]); + data.horizontal_messages.insert(ALICE, vec![mk_hrmp(1)]); }, 2 => { // no new messages }, 3 => { - data.horizontal_messages.insert(ALICE, vec![MSG_2.clone()]); + data.horizontal_messages.insert(ALICE, vec![mk_hrmp(3)]); }, _ => unreachable!(), }) .add(1, || { HANDLED_XCMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(ALICE, 1, b"mikhailinvanovich".to_vec())]); + assert_eq!(&*m, &[(ALICE, 1, b"1".to_vec())]); m.clear(); }); }) @@ -1442,14 +1035,75 @@ fn receive_hrmp_after_pause() { .add(3, || { HANDLED_XCMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(ALICE, 3, b"1000000000".to_vec())]); + assert_eq!(&*m, &[(ALICE, 3, b"3".to_vec())]); m.clear(); }); }); } +// Sent up to 100 HRMP messages per block over a period of 100 blocks. +#[test] +fn receive_hrmp_many() { + const ALICE: ParaId = ParaId::new(300); + + wasm_ext().execute_with(|| { + parameter_types! { + pub storage MqcHead: MessageQueueChain = Default::default(); + pub storage SentInBlock: Vec> = Default::default(); + } + + let mut sent_in_block = vec![vec![]]; + let mut rng = rand::thread_rng(); + + for block in 1..100 { + let mut msgs = vec![]; + for _ in 1..=rng.gen_range(1..=100) { + // Just use the same message multiple times per block. + msgs.push(mk_hrmp(block)); + } + sent_in_block.push(msgs); + } + SentInBlock::set(&sent_in_block); + + let mut tester = BlockTests::new_without_externalities() + .with_relay_sproof_builder(|_, relay_block_num, sproof| { + let mut new_hash = MqcHead::get(); + + for msg in SentInBlock::get()[relay_block_num as usize].iter() { + new_hash.extend_hrmp(&msg); + } + + sproof.upsert_inbound_channel(ALICE).mqc_head = Some(new_hash.head()); + MqcHead::set(&new_hash); + }) + .with_inherent_data(|_, relay_block_num, data| { + // TODO use vector for dmp as well + data.horizontal_messages + .insert(ALICE, SentInBlock::get()[relay_block_num as usize].clone()); + }); + + for block in 1..100 { + tester = tester.add(block, move || { + HANDLED_XCMP_MESSAGES.with(|m| { + let mut m = m.borrow_mut(); + let msgs = SentInBlock::get()[block as usize] + .iter() + .map(|m| (ALICE, m.sent_at, m.data.clone())) + .collect::>(); + assert_eq!(&*m, &msgs); + m.clear(); + }); + }); + } + }); +} + #[test] fn upgrade_version_checks_should_work() { + use codec::Encode; + use sp_runtime::DispatchErrorWithPostInfo; + use sp_version::RuntimeVersion; + let test_data = vec![ ("test", 0, 1, Err(frame_system::Error::::SpecVersionNeedsToIncrease)), ("test", 1, 0, Err(frame_system::Error::::SpecVersionNeedsToIncrease)), @@ -1471,10 +1125,11 @@ fn upgrade_version_checks_should_work() { ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(read_runtime_version)); ext.execute_with(|| { let new_code = vec![1, 2, 3, 4]; - let new_code_hash = sp_core::H256(blake2_256(&new_code)); + let new_code_hash = H256(sp_crypto_hashing::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/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index ce3b724420f1c9e50b65a81074e023c6b735227b..ecab7a9a09311ae0f952a7842e9f1826b00adc69 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -16,7 +16,7 @@ //! The actual implementation of the validate block functionality. -use super::{trie_cache, MemoryOptimizedValidationParams}; +use super::{trie_cache, trie_recorder, MemoryOptimizedValidationParams}; use cumulus_primitives_core::{ relay_chain::Hash as RHash, ParachainBlockData, PersistedValidationData, }; @@ -34,12 +34,14 @@ use sp_externalities::{set_and_run_with_externalities, Externalities}; use sp_io::KillStorageResult; use sp_runtime::traits::{Block as BlockT, Extrinsic, HashingFor, Header as HeaderT}; use sp_std::prelude::*; -use sp_trie::MemoryDB; +use sp_trie::{MemoryDB, ProofSizeProvider}; +use trie_recorder::SizeOnlyRecorderProvider; type TrieBackend = sp_state_machine::TrieBackend< MemoryDB>, HashingFor, trie_cache::CacheProvider>, + SizeOnlyRecorderProvider>, >; type Ext<'a, B> = sp_state_machine::Ext<'a, HashingFor, TrieBackend>; @@ -48,6 +50,9 @@ fn with_externalities R, R>(f: F) -> R { sp_externalities::with_externalities(f).expect("Environmental externalities not set.") } +// Recorder instance to be used during this validate_block call. +environmental::environmental!(recorder: trait ProofSizeProvider); + /// Validate the given parachain block. /// /// This function is doing roughly the following: @@ -120,6 +125,7 @@ where sp_std::mem::drop(storage_proof); + let mut recorder = SizeOnlyRecorderProvider::new(); let cache_provider = trie_cache::CacheProvider::new(); // We use the storage root of the `parent_head` to ensure that it is the correct root. // This is already being done above while creating the in-memory db, but let's be paranoid!! @@ -128,6 +134,7 @@ where *parent_header.state_root(), cache_provider, ) + .with_recorder(recorder.clone()) .build(); let _guard = ( @@ -167,9 +174,11 @@ where .replace_implementation(host_default_child_storage_next_key), sp_io::offchain_index::host_set.replace_implementation(host_offchain_index_set), sp_io::offchain_index::host_clear.replace_implementation(host_offchain_index_clear), + cumulus_primitives_proof_size_hostfunction::storage_proof_size::host_storage_proof_size + .replace_implementation(host_storage_proof_size), ); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let relay_chain_proof = crate::RelayChainStateProof::new( PSC::SelfParaId::get(), inherent_data.validation_data.relay_parent_storage_root, @@ -190,7 +199,7 @@ where } }); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let head_data = HeadData(block.header().encode()); E::execute_block(block); @@ -265,15 +274,17 @@ fn validate_validation_data( ); } -/// Run the given closure with the externalities set. -fn run_with_externalities R>( +/// Run the given closure with the externalities and recorder set. +fn run_with_externalities_and_recorder R>( backend: &TrieBackend, + recorder: &mut SizeOnlyRecorderProvider>, execute: F, ) -> R { let mut overlay = sp_state_machine::OverlayedChanges::default(); let mut ext = Ext::::new(&mut overlay, backend); + recorder.reset(); - set_and_run_with_externalities(&mut ext, || execute()) + recorder::using(recorder, || set_and_run_with_externalities(&mut ext, || execute())) } fn host_storage_read(key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option { @@ -305,6 +316,10 @@ fn host_storage_clear(key: &[u8]) { with_externalities(|ext| ext.place_storage(key.to_vec(), None)) } +fn host_storage_proof_size() -> u64 { + recorder::with(|rec| rec.estimate_encoded_size()).expect("Recorder is always set; qed") as _ +} + fn host_storage_root(version: StateVersion) -> Vec { with_externalities(|ext| ext.storage_root(version)) } diff --git a/cumulus/pallets/parachain-system/src/validate_block/mod.rs b/cumulus/pallets/parachain-system/src/validate_block/mod.rs index db149401638aabeb65f3242fc450e27f8edb93cd..763a4cffd77f92171c1f102cfc70485a02e5f27e 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/mod.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/mod.rs @@ -26,6 +26,10 @@ mod tests; #[doc(hidden)] mod trie_cache; +#[cfg(any(test, not(feature = "std")))] +#[doc(hidden)] +mod trie_recorder; + #[cfg(not(feature = "std"))] #[doc(hidden)] pub use bytes; diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index 0cf68f25cc34b7be302b5983d5f7f27fcfcf13f1..a9fb65e11089fab87c543fbb6d7b54f6231a1dd7 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -21,12 +21,13 @@ use cumulus_test_client::{ runtime::{ self as test_runtime, Block, Hash, Header, TestPalletCall, UncheckedExtrinsic, WASM_BINARY, }, - transfer, BlockData, BuildParachainBlockData, Client, DefaultTestClientBuilderExt, HeadData, - InitBlockBuilder, TestClientBuilder, TestClientBuilderExt, ValidationParams, + transfer, BlockData, BlockOrigin, BuildParachainBlockData, Client, ClientBlockImportExt, + DefaultTestClientBuilderExt, HeadData, InitBlockBuilder, TestClientBuilder, + TestClientBuilderExt, ValidationParams, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use sp_keyring::AccountKeyring::*; -use sp_runtime::traits::Header as HeaderT; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use std::{env, process::Command}; use crate::validate_block::MemoryOptimizedValidationParams; @@ -58,7 +59,7 @@ fn call_validate_block( } fn create_test_client() -> (Client, Header) { - let client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().enable_import_proof_recording().build(); let genesis_header = client .header(client.chain_info().genesis_hash) @@ -100,7 +101,7 @@ fn build_block_with_witness( } #[test] -fn validate_block_no_extra_extrinsics() { +fn validate_block_works() { sp_tracing::try_init_simple(); let (client, parent_head) = create_test_client(); @@ -290,3 +291,42 @@ fn validation_params_and_memory_optimized_validation_params_encode_and_decode() let decoded = ValidationParams::decode_all(&mut &encoded[..]).unwrap(); assert_eq!(decoded, validation_params); } + +/// Test for ensuring that we are differentiating in the `validation::trie_cache` between different +/// child tries. +/// +/// This is achieved by first building a block using `read_and_write_child_tries` that should set +/// the values in the child tries. In the second step we are building a second block with the same +/// extrinsic that reads the values from the child tries and it asserts that we read the correct +/// data from the state. +#[test] +fn validate_block_works_with_child_tries() { + sp_tracing::try_init_simple(); + + let (mut client, parent_head) = create_test_client(); + let TestBlockData { block, .. } = build_block_with_witness( + &client, + vec![generate_extrinsic(&client, Charlie, TestPalletCall::read_and_write_child_tries {})], + parent_head.clone(), + Default::default(), + ); + let block = block.into_block(); + + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let parent_head = block.header().clone(); + + let TestBlockData { block, validation_data } = build_block_with_witness( + &client, + vec![generate_extrinsic(&client, Alice, TestPalletCall::read_and_write_child_tries {})], + parent_head.clone(), + Default::default(), + ); + + let header = block.header().clone(); + + let res_header = + call_validate_block(parent_head, block, validation_data.relay_parent_storage_root) + .expect("Calls `validate_block`"); + assert_eq!(header, res_header); +} diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs index 57876b87876feedad9f0ac8a7ccf3d5c99f6e704..5d785910fbe026fba487980d1244262146f86a5b 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs @@ -67,7 +67,13 @@ impl<'a, H: Hasher> trie_db::TrieCache> for TrieCache<'a, H> { /// Provider of [`TrieCache`] instances. pub(crate) struct CacheProvider { node_cache: RefCell>>, - value_cache: RefCell, trie_db::CachedValue>>, + /// Cache: `storage_root` => `storage_key` => `value`. + /// + /// One `block` can for example use multiple tries (child tries) and we need to distinguish the + /// cached (`storage_key`, `value`) between them. For this we are using the `storage_root` to + /// distinguish them (even if the storage root is the same for two child tries, it just means + /// that both are exactly the same trie and there would happen no collision). + value_cache: RefCell, trie_db::CachedValue>>>, } impl CacheProvider { @@ -81,22 +87,26 @@ impl CacheProvider { impl TrieCacheProvider for CacheProvider { type Cache<'a> = TrieCache<'a, H> where H: 'a; - fn as_trie_db_cache(&self, _storage_root: ::Out) -> Self::Cache<'_> { + fn as_trie_db_cache(&self, storage_root: ::Out) -> Self::Cache<'_> { TrieCache { - value_cache: Some(self.value_cache.borrow_mut()), + value_cache: Some(RefMut::map(self.value_cache.borrow_mut(), |c| { + c.entry(storage_root).or_default() + })), node_cache: self.node_cache.borrow_mut(), } } fn as_trie_db_mut_cache(&self) -> Self::Cache<'_> { // This method is called when we calculate the storage root. - // Since we are using a simplified cache architecture, - // we do not have separate key spaces for different storage roots. - // The value cache is therefore disabled here. + // We are not interested in caching new values (as we would throw them away directly after a + // block is validated) and thus, we don't pass any `value_cache`. TrieCache { value_cache: None, node_cache: self.node_cache.borrow_mut() } } - fn merge<'a>(&'a self, _other: Self::Cache<'a>, _new_root: ::Out) {} + fn merge<'a>(&'a self, _other: Self::Cache<'a>, _new_root: ::Out) { + // This is called to merge the `value_cache` from `as_trie_db_mut_cache`, which is not + // activated, so we don't need to do anything here. + } } // This is safe here since we are single-threaded in WASM diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs new file mode 100644 index 0000000000000000000000000000000000000000..48310670c074d1be2ec86f582c0c84622abc086c --- /dev/null +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -0,0 +1,297 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 . + +//! Provide a specialized trie-recorder and provider for use in validate-block. +//! +//! This file defines two main structs, [`SizeOnlyRecorder`] and +//! [`SizeOnlyRecorderProvider`]. They are used to track the current +//! proof-size without actually recording the accessed nodes themselves. + +use codec::Encode; + +use sp_std::{ + cell::{RefCell, RefMut}, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + rc::Rc, +}; +use sp_trie::{NodeCodec, ProofSizeProvider, StorageProof}; +use trie_db::{Hasher, RecordedForKey, TrieAccess}; + +/// A trie recorder that only keeps track of the proof size. +/// +/// The internal size counting logic should align +/// with ['sp_trie::recorder::Recorder']. +pub(crate) struct SizeOnlyRecorder<'a, H: Hasher> { + seen_nodes: RefMut<'a, BTreeSet>, + encoded_size: RefMut<'a, usize>, + recorded_keys: RefMut<'a, BTreeMap, RecordedForKey>>, +} + +impl<'a, H: trie_db::Hasher> trie_db::TrieRecorder for SizeOnlyRecorder<'a, H> { + fn record(&mut self, access: TrieAccess<'_, H::Out>) { + let mut encoded_size_update = 0; + match access { + TrieAccess::NodeOwned { hash, node_owned } => + if self.seen_nodes.insert(hash) { + let node = node_owned.to_encoded::>(); + encoded_size_update += node.encoded_size(); + }, + TrieAccess::EncodedNode { hash, encoded_node } => + if self.seen_nodes.insert(hash) { + encoded_size_update += encoded_node.encoded_size(); + }, + TrieAccess::Value { hash, value, full_key } => { + if self.seen_nodes.insert(hash) { + encoded_size_update += value.encoded_size(); + } + self.recorded_keys + .entry(full_key.into()) + .and_modify(|e| *e = RecordedForKey::Value) + .or_insert_with(|| RecordedForKey::Value); + }, + TrieAccess::Hash { full_key } => { + self.recorded_keys + .entry(full_key.into()) + .or_insert_with(|| RecordedForKey::Hash); + }, + TrieAccess::NonExisting { full_key } => { + self.recorded_keys + .entry(full_key.into()) + .and_modify(|e| *e = RecordedForKey::Value) + .or_insert_with(|| RecordedForKey::Value); + }, + TrieAccess::InlineValue { full_key } => { + self.recorded_keys + .entry(full_key.into()) + .and_modify(|e| *e = RecordedForKey::Value) + .or_insert_with(|| RecordedForKey::Value); + }, + }; + + *self.encoded_size += encoded_size_update; + } + + fn trie_nodes_recorded_for_key(&self, key: &[u8]) -> RecordedForKey { + self.recorded_keys.get(key).copied().unwrap_or(RecordedForKey::None) + } +} + +#[derive(Clone)] +pub(crate) struct SizeOnlyRecorderProvider { + seen_nodes: Rc>>, + encoded_size: Rc>, + recorded_keys: Rc, RecordedForKey>>>, +} + +impl SizeOnlyRecorderProvider { + /// Create a new instance of [`SizeOnlyRecorderProvider`] + pub fn new() -> Self { + Self { + seen_nodes: Default::default(), + encoded_size: Default::default(), + recorded_keys: Default::default(), + } + } + + /// Reset the internal state. + pub fn reset(&self) { + self.seen_nodes.borrow_mut().clear(); + *self.encoded_size.borrow_mut() = 0; + self.recorded_keys.borrow_mut().clear(); + } +} + +impl sp_trie::TrieRecorderProvider for SizeOnlyRecorderProvider { + type Recorder<'a> = SizeOnlyRecorder<'a, H> where H: 'a; + + fn drain_storage_proof(self) -> Option { + None + } + + fn as_trie_recorder(&self, _storage_root: H::Out) -> Self::Recorder<'_> { + SizeOnlyRecorder { + encoded_size: self.encoded_size.borrow_mut(), + seen_nodes: self.seen_nodes.borrow_mut(), + recorded_keys: self.recorded_keys.borrow_mut(), + } + } +} + +impl ProofSizeProvider for SizeOnlyRecorderProvider { + fn estimate_encoded_size(&self) -> usize { + *self.encoded_size.borrow() + } +} + +// This is safe here since we are single-threaded in WASM +unsafe impl Send for SizeOnlyRecorderProvider {} +unsafe impl Sync for SizeOnlyRecorderProvider {} + +#[cfg(test)] +mod tests { + use rand::Rng; + use sp_trie::{ + cache::{CacheSize, SharedTrieCache}, + MemoryDB, ProofSizeProvider, TrieRecorderProvider, + }; + use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut, TrieRecorder}; + use trie_standardmap::{Alphabet, StandardMap, ValueMode}; + + use super::*; + + type Recorder = sp_trie::recorder::Recorder; + + fn create_trie() -> ( + sp_trie::MemoryDB, + TrieHash>, + Vec<(Vec, Vec)>, + ) { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + let mut seed = Default::default(); + let test_data: Vec<(Vec, Vec)> = StandardMap { + alphabet: Alphabet::Low, + min_key: 16, + journal_key: 0, + value_mode: ValueMode::Random, + count: 1000, + } + .make_with(&mut seed) + .into_iter() + .map(|(k, v)| { + // Double the length so we end up with some values of 2 bytes and some of 64 + let v = [v.clone(), v].concat(); + (k, v) + }) + .collect(); + + // Fill database with values + { + let mut trie = TrieDBMutBuilder::>::new( + &mut db, &mut root, + ) + .build(); + for (k, v) in &test_data { + trie.insert(k, v).expect("Inserts data"); + } + } + + (db, root, test_data) + } + + #[test] + fn recorder_equivalence_cache() { + let (db, root, test_data) = create_trie(); + + let mut rng = rand::thread_rng(); + for _ in 1..10 { + let reference_recorder = Recorder::default(); + let recorder_for_test: SizeOnlyRecorderProvider = + SizeOnlyRecorderProvider::new(); + let reference_cache: SharedTrieCache = + SharedTrieCache::new(CacheSize::new(1024 * 5)); + let cache_for_test: SharedTrieCache = + SharedTrieCache::new(CacheSize::new(1024 * 5)); + { + let local_cache = cache_for_test.local_cache(); + let mut trie_cache_for_reference = local_cache.as_trie_db_cache(root); + let mut reference_trie_recorder = reference_recorder.as_trie_recorder(root); + let reference_trie = + TrieDBBuilder::>::new(&db, &root) + .with_recorder(&mut reference_trie_recorder) + .with_cache(&mut trie_cache_for_reference) + .build(); + + let local_cache_for_test = reference_cache.local_cache(); + let mut trie_cache_for_test = local_cache_for_test.as_trie_db_cache(root); + let mut trie_recorder_under_test = recorder_for_test.as_trie_recorder(root); + let test_trie = + TrieDBBuilder::>::new(&db, &root) + .with_recorder(&mut trie_recorder_under_test) + .with_cache(&mut trie_cache_for_test) + .build(); + + // Access random values from the test data + for _ in 0..100 { + let index: usize = rng.gen_range(0..test_data.len()); + test_trie.get(&test_data[index].0).unwrap().unwrap(); + reference_trie.get(&test_data[index].0).unwrap().unwrap(); + } + + // Check that we have the same nodes recorded for both recorders + for (key, _) in test_data.iter() { + let reference = reference_trie_recorder.trie_nodes_recorded_for_key(key); + let test_value = trie_recorder_under_test.trie_nodes_recorded_for_key(key); + assert_eq!(format!("{:?}", reference), format!("{:?}", test_value)); + } + } + + // Check that we have the same size recorded for both recorders + assert_eq!( + reference_recorder.estimate_encoded_size(), + recorder_for_test.estimate_encoded_size() + ); + } + } + + #[test] + fn recorder_equivalence_no_cache() { + let (db, root, test_data) = create_trie(); + + let mut rng = rand::thread_rng(); + for _ in 1..10 { + let reference_recorder = Recorder::default(); + let recorder_for_test: SizeOnlyRecorderProvider = + SizeOnlyRecorderProvider::new(); + { + let mut reference_trie_recorder = reference_recorder.as_trie_recorder(root); + let reference_trie = + TrieDBBuilder::>::new(&db, &root) + .with_recorder(&mut reference_trie_recorder) + .build(); + + let mut trie_recorder_under_test = recorder_for_test.as_trie_recorder(root); + let test_trie = + TrieDBBuilder::>::new(&db, &root) + .with_recorder(&mut trie_recorder_under_test) + .build(); + + for _ in 0..200 { + let index: usize = rng.gen_range(0..test_data.len()); + test_trie.get(&test_data[index].0).unwrap().unwrap(); + reference_trie.get(&test_data[index].0).unwrap().unwrap(); + } + + // Check that we have the same nodes recorded for both recorders + for (key, _) in test_data.iter() { + let reference = reference_trie_recorder.trie_nodes_recorded_for_key(key); + let test_value = trie_recorder_under_test.trie_nodes_recorded_for_key(key); + assert_eq!(format!("{:?}", reference), format!("{:?}", test_value)); + } + } + + // Check that we have the same size recorded for both recorders + assert_eq!( + reference_recorder.estimate_encoded_size(), + recorder_for_test.estimate_encoded_size() + ); + + recorder_for_test.reset(); + assert_eq!(recorder_for_test.estimate_encoded_size(), 0) + } + } +} diff --git a/cumulus/pallets/parachain-system/src/weights.rs b/cumulus/pallets/parachain-system/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..da7f64237e9b6a8b39096b761f6ff923f903529a --- /dev/null +++ b/cumulus/pallets/parachain-system/src/weights.rs @@ -0,0 +1,115 @@ +// 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_parachain_system +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westmint-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// westmint-dev +// --pallet +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// pallets/parachain-system/src/weights.rs +// --steps +// 50 +// --repeat +// 20 +// --template +// ../substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for cumulus_pallet_parachain_system. +pub trait WeightInfo { + fn enqueue_inbound_downward_messages(n: u32, ) -> Weight; +} + +/// Weights for cumulus_pallet_parachain_system using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// 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_625_000 picoseconds. + Weight::from_parts(1_735_000, 8013) + // Standard Error: 14_563 + .saturating_add(Weight::from_parts(25_300_108, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: 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_625_000 picoseconds. + Weight::from_parts(1_735_000, 8013) + // Standard Error: 14_563 + .saturating_add(Weight::from_parts(25_300_108, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } +} diff --git a/cumulus/pallets/session-benchmarking/Cargo.toml b/cumulus/pallets/session-benchmarking/Cargo.toml index a28971d66d3f667892676d7a9d756a40f1d9f4d2..43fde4ea6009cce7d2dedceb815e2948beb80196 100644 --- a/cumulus/pallets/session-benchmarking/Cargo.toml +++ b/cumulus/pallets/session-benchmarking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cumulus-pallet-session-benchmarking" -version = "3.0.0" +version = "9.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" @@ -9,20 +9,23 @@ repository.workspace = true description = "FRAME sessions pallet benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] parity-scale-codec = { version = "3.6.4", default-features = false } -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true} -pallet-session = { path = "../../../substrate/frame/session", default-features = false} +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } +pallet-session = { path = "../../../substrate/frame/session", default-features = false } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/cumulus/pallets/solo-to-para/Cargo.toml b/cumulus/pallets/solo-to-para/Cargo.toml index af419cc37db09ee4cb9fc0ecb7046d99a40c1274..f7dc5fe4de372b1b5a03ea00cc041e3e568332d3 100644 --- a/cumulus/pallets/solo-to-para/Cargo.toml +++ b/cumulus/pallets/solo-to-para/Cargo.toml @@ -1,29 +1,33 @@ [package] name = "cumulus-pallet-solo-to-para" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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"] } # Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } # Polkadot -polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false} +polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } # Cumulus -cumulus-pallet-parachain-system = { path = "../parachain-system", default-features = false} +cumulus-pallet-parachain-system = { path = "../parachain-system", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-parachain-system/std", diff --git a/cumulus/pallets/xcm/Cargo.toml b/cumulus/pallets/xcm/Cargo.toml index 853dd86bb4c8429eb6725a889b3dab5fd8c8ba04..63cb14b16e769fe8ddd309baf8d51594b47da49b 100644 --- a/cumulus/pallets/xcm/Cargo.toml +++ b/cumulus/pallets/xcm/Cargo.toml @@ -2,24 +2,29 @@ authors.workspace = true edition.workspace = true name = "cumulus-pallet-xcm" -version = "0.1.0" +version = "0.7.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"] } -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false} +xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-primitives-core/std", diff --git a/cumulus/pallets/xcm/src/lib.rs b/cumulus/pallets/xcm/src/lib.rs index 69b4f43754003495b4e6cec9b8e9e29e1ce73a08..90a0ec76defe2b0df8d99faacfb611c0f7834a3b 100644 --- a/cumulus/pallets/xcm/src/lib.rs +++ b/cumulus/pallets/xcm/src/lib.rs @@ -20,25 +20,18 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, DecodeLimit, Encode}; -use cumulus_primitives_core::{ - relay_chain::BlockNumber as RelayBlockNumber, DmpMessageHandler, ParaId, -}; -use frame_support::weights::Weight; +use codec::{Decode, Encode}; +use cumulus_primitives_core::ParaId; pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{convert::TryFrom, prelude::*}; -use xcm::{ - latest::{ExecuteXcm, Outcome, Parent, Xcm}, - VersionedXcm, MAX_XCM_DECODE_DEPTH, -}; +use sp_std::prelude::*; +use xcm::latest::{ExecuteXcm, Outcome}; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -52,17 +45,7 @@ pub mod pallet { type XcmExecutor: ExecuteXcm; } - #[pallet::error] - pub enum Error {} - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet {} - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Downward message is invalid XCM. /// \[ id \] @@ -85,6 +68,9 @@ pub mod pallet { SiblingParachain(ParaId), } + #[pallet::call] + impl Pallet {} + impl From for Origin { fn from(id: ParaId) -> Origin { Origin::SiblingParachain(id) @@ -97,75 +83,6 @@ pub mod pallet { } } -/// For an incoming downward message, this just adapts an XCM executor and executes DMP messages -/// immediately. Their origin is asserted to be the Parent location. -/// -/// The weight `limit` is only respected as the maximum for an individual message. -/// -/// Because this largely ignores the given weight limit, it probably isn't good for most production -/// uses. Use DmpQueue pallet for a more robust design. -pub struct UnlimitedDmpExecution(sp_std::marker::PhantomData); -impl DmpMessageHandler for UnlimitedDmpExecution { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - let mut used = Weight::zero(); - for (_sent_at, data) in iter { - let id = sp_io::hashing::blake2_256(&data[..]); - let msg = VersionedXcm::::decode_all_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data.as_slice(), - ) - .map(Xcm::::try_from); - match msg { - Err(_) => Pallet::::deposit_event(Event::InvalidFormat(id)), - Ok(Err(())) => Pallet::::deposit_event(Event::UnsupportedVersion(id)), - Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, limit); - used = used.saturating_add(outcome.weight_used()); - Pallet::::deposit_event(Event::ExecutedDownward(id, outcome)); - }, - } - } - used - } -} - -/// For an incoming downward message, this just adapts an XCM executor and executes DMP messages -/// immediately. Their origin is asserted to be the Parent location. -/// -/// This respects the given weight limit and silently drops messages if they would break it. It -/// probably isn't good for most production uses. Use DmpQueue pallet for a more robust design. -pub struct LimitAndDropDmpExecution(sp_std::marker::PhantomData); -impl DmpMessageHandler for LimitAndDropDmpExecution { - fn handle_dmp_messages( - iter: impl Iterator)>, - limit: Weight, - ) -> Weight { - let mut used = Weight::zero(); - for (_sent_at, data) in iter { - let id = sp_io::hashing::blake2_256(&data[..]); - let msg = VersionedXcm::::decode_all_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data.as_slice(), - ) - .map(Xcm::::try_from); - match msg { - Err(_) => Pallet::::deposit_event(Event::InvalidFormat(id)), - Ok(Err(())) => Pallet::::deposit_event(Event::UnsupportedVersion(id)), - Ok(Ok(x)) => { - let weight_limit = limit.saturating_sub(used); - let outcome = T::XcmExecutor::execute_xcm(Parent, x, id, weight_limit); - used = used.saturating_add(outcome.weight_used()); - Pallet::::deposit_event(Event::ExecutedDownward(id, outcome)); - }, - } - } - used - } -} - /// Ensure that the origin `o` represents a sibling parachain. /// Returns `Ok` with the parachain ID of the sibling or an `Err` otherwise. pub fn ensure_sibling_para(o: OuterOrigin) -> Result diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 358a0bec8bb0d78ba6769b1663b16e1047ed04a7..9078d5eda997526b8f3ed7d9f118cc9c927dedc1 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -1,22 +1,27 @@ [package] name = "cumulus-pallet-xcmp-queue" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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 } -rand_chacha = { version = "0.3.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../substrate/frame/system", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", 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} +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +pallet-message-queue = { path = "../../../substrate/frame/message-queue", default-features = false } # Polkadot polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false } @@ -28,7 +33,8 @@ xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm cumulus-primitives-core = { path = "../../primitives/core", default-features = false } # Optional import for benchmarking -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true} +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } +bounded-collections = { version = "0.2.0", default-features = false } # Bridges bp-xcm-bridge-hub-router = { path = "../../../bridges/primitives/xcm-bridge-hub-router", default-features = false, optional = true } @@ -38,6 +44,7 @@ bp-xcm-bridge-hub-router = { path = "../../../bridges/primitives/xcm-bridge-hub- # Substrate sp-core = { path = "../../../substrate/primitives/core" } pallet-balances = { path = "../../../substrate/frame/balances" } +frame-support = { path = "../../../substrate/frame/support", features = ["experimental"] } # Polkadot xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder" } @@ -46,8 +53,9 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/x cumulus-pallet-parachain-system = { path = "../parachain-system", features = ["parameterized-consensus-hook"] } [features] -default = [ "std" ] +default = ["std"] std = [ + "bounded-collections/std", "bp-xcm-bridge-hub-router?/std", "codec/std", "cumulus-primitives-core/std", @@ -55,6 +63,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "pallet-message-queue/std", "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", @@ -68,10 +77,12 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -83,8 +94,9 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", "polkadot-runtime-common/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] -bridging = [ "bp-xcm-bridge-hub-router" ] +bridging = ["bp-xcm-bridge-hub-router"] diff --git a/cumulus/pallets/xcmp-queue/src/benchmarking.rs b/cumulus/pallets/xcmp-queue/src/benchmarking.rs index 17ec60a2f3fafe9d257074fc803c6567b4422c31..49e2cc8367348ad6a81c0017405f8c3148734be5 100644 --- a/cumulus/pallets/xcmp-queue/src/benchmarking.rs +++ b/cumulus/pallets/xcmp-queue/src/benchmarking.rs @@ -17,12 +17,158 @@ use crate::*; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; +use codec::DecodeAll; +use frame_benchmarking::v2::*; +use frame_support::traits::Hooks; use frame_system::RawOrigin; +use xcm::v3::MAX_INSTRUCTIONS_TO_DECODE; -benchmarks! { - set_config_with_u32 {}: update_resume_threshold(RawOrigin::Root, 100) - set_config_with_weight {}: update_weight_restrict_decay(RawOrigin::Root, Weight::from_parts(3_000_000, 0)) -} +#[benchmarks] +mod benchmarks { + use super::*; + + /// Modify any of the `QueueConfig` fields with a new `u32` value. + /// + /// Used as weight for: + /// - update_suspend_threshold + /// - update_drop_threshold + /// - update_resume_threshold + #[benchmark] + fn set_config_with_u32() { + #[extrinsic_call] + Pallet::::update_resume_threshold(RawOrigin::Root, 1); + } + + #[benchmark] + fn enqueue_xcmp_message() { + assert!(QueueConfig::::get().drop_threshold * MaxXcmpMessageLenOf::::get() > 1000); + let msg = BoundedVec::>::default(); + + #[block] + { + Pallet::::enqueue_xcmp_message(0.into(), msg, &mut WeightMeter::new()).unwrap(); + } + } + + #[benchmark] + fn suspend_channel() { + let para = 123.into(); + let data = ChannelSignal::Suspend.encode(); + + #[block] + { + ChannelSignal::decode_all(&mut &data[..]).unwrap(); + Pallet::::suspend_channel(para); + } + + assert_eq!( + OutboundXcmpStatus::::get() + .iter() + .find(|p| p.recipient == para) + .unwrap() + .state, + OutboundState::Suspended + ); + } + + #[benchmark] + fn resume_channel() { + let para = 123.into(); + let data = ChannelSignal::Resume.encode(); + + Pallet::::suspend_channel(para); + + #[block] + { + ChannelSignal::decode_all(&mut &data[..]).unwrap(); + Pallet::::resume_channel(para); + } + + assert!( + OutboundXcmpStatus::::get().iter().all(|p| p.recipient != para), + "No messages in the channel; therefore removed." + ); + } + + /// Split a singular XCM. + #[benchmark] + fn take_first_concatenated_xcm() { + let max_downward_message_size = MaxXcmpMessageLenOf::::get() as usize; + + assert!(MAX_INSTRUCTIONS_TO_DECODE as u32 > MAX_XCM_DECODE_DEPTH, "Preconditon failed"); + let max_instrs = MAX_INSTRUCTIONS_TO_DECODE as u32 - MAX_XCM_DECODE_DEPTH; + let mut xcm = Xcm::(vec![ClearOrigin; max_instrs as usize]); -impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + xcm = Xcm::(vec![Instruction::SetAppendix(xcm)]); + } + + let data = VersionedXcm::::from(xcm).encode(); + assert!(data.len() < max_downward_message_size, "Page size is too small"); + // Verify that decoding works with the exact recursion limit: + VersionedXcm::::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut &data[..], + ) + .unwrap(); + VersionedXcm::::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH - 1, + &mut &data[..], + ) + .unwrap_err(); + + #[block] + { + Pallet::::take_first_concatenated_xcm(&mut &data[..], &mut WeightMeter::new()) + .unwrap(); + } + } + + /// Benchmark the migration for a maximal sized message. + #[benchmark] + fn on_idle_good_msg() { + use migration::v3; + + let block = 5; + let para = ParaId::from(4); + let message = vec![123u8; MaxXcmpMessageLenOf::::get() as usize]; + let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)]; + + v3::InboundXcmpMessages::::insert(para, block, message); + v3::InboundXcmpStatus::::set(Some(vec![v3::InboundChannelDetails { + sender: para, + state: v3::InboundState::Ok, + message_metadata, + }])); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + } + + /// Benchmark the migration with a 64 KiB message that will not be possible to enqueue. + #[benchmark] + fn on_idle_large_msg() { + use migration::v3; + + let block = 5; + let para = ParaId::from(4); + let message = vec![123u8; 1 << 16]; // 64 KiB message + let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)]; + + v3::InboundXcmpMessages::::insert(para, block, message); + v3::InboundXcmpStatus::::set(Some(vec![v3::InboundChannelDetails { + sender: para, + state: v3::InboundState::Ok, + message_metadata, + }])); + + #[block] + { + Pallet::::on_idle(0u32.into(), Weight::MAX); + } + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/cumulus/pallets/xcmp-queue/src/bridging.rs b/cumulus/pallets/xcmp-queue/src/bridging.rs index 0fc3f1f39ea38bfd1c86c76832cdf77cdb572363..9db4b6e74c3987ae08a6d38eb75ffcacb9e5e713 100644 --- a/cumulus/pallets/xcmp-queue/src/bridging.rs +++ b/cumulus/pallets/xcmp-queue/src/bridging.rs @@ -55,7 +55,9 @@ impl, Runtime: crate::Config> let sibling_bridge_hub_id: ParaId = SiblingBridgeHubParaId::get(); // let's find the channel's state with the sibling parachain, - let Some((outbound_state, queued_pages)) = pallet::Pallet::::outbound_channel_state(sibling_bridge_hub_id) else { + let Some((outbound_state, queued_pages)) = + pallet::Pallet::::outbound_channel_state(sibling_bridge_hub_id) + else { return false }; // suspended channel => it is congested @@ -63,13 +65,6 @@ impl, Runtime: crate::Config> return true } - // TODO: https://github.com/paritytech/polkadot-sdk/pull/1556 - once this PR is merged, we may - // remove the following code. - // TODO: the following restriction is arguable, we may live without that, assuming that - // There can't be more than some `N` messages queued at the bridge queue (at the source BH) - // AND before accepting next (or next-after-next) delivery transaction, we'll receive the - // suspension signal from the target parachain and stop accepting delivery transactions. - // It takes some time for target parachain to suspend inbound channel with the target BH and // during that we will keep accepting new message delivery transactions. Let's also reject // new deliveries if there are too many "pages" (concatenated XCM messages) in the target BH diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs index b62dc0a47fc5731177789543322565a0e99b7ba6..e92169be16b0b8466582f3dd143d9e3e1807a42d 100644 --- a/cumulus/pallets/xcmp-queue/src/lib.rs +++ b/cumulus/pallets/xcmp-queue/src/lib.rs @@ -50,25 +50,26 @@ pub mod bridging; pub mod weights; pub use weights::WeightInfo; +use bounded_collections::BoundedBTreeSet; use codec::{Decode, DecodeLimit, Encode}; use cumulus_primitives_core::{ relay_chain::BlockNumber as RelayBlockNumber, ChannelStatus, GetChannelInfo, MessageSendError, ParaId, XcmpMessageFormat, XcmpMessageHandler, XcmpMessageSource, }; + use frame_support::{ - traits::{EnsureOrigin, Get}, - weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}, + defensive, defensive_assert, + traits::{EnqueueMessage, EnsureOrigin, Get, QueueFootprint, QueuePausedQuery}, + weights::{Weight, WeightMeter}, + BoundedVec, }; +use pallet_message_queue::OnQueueChanged; use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery; use polkadot_runtime_parachains::FeeTracker; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaChaRng, -}; use scale_info::TypeInfo; use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_runtime::{FixedU128, RuntimeDebug, Saturating}; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; use xcm::{latest::prelude::*, VersionedXcm, WrapVersion, MAX_XCM_DECODE_DEPTH}; use xcm_executor::traits::ConvertOrigin; @@ -76,6 +77,9 @@ pub use pallet::*; /// Index used to identify overweight XCMs. pub type OverweightIndex = u64; +/// The max length of an XCMP message. +pub type MaxXcmpMessageLenOf = + <::XcmpQueue as EnqueueMessage>::MaxMessageLen; const LOG_TARGET: &str = "xcmp_queue"; const DEFAULT_POV_SIZE: u64 = 64 * 1024; // 64 KB @@ -93,12 +97,6 @@ pub mod delivery_fee_constants { pub const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001 } -// Maximum amount of messages to process per block. This is a temporary measure until we properly -// account for proof size weights. -const MAX_MESSAGES_PER_BLOCK: u8 = 10; -// Maximum amount of messages that can exist in the overweight queue at any given time. -const MAX_OVERWEIGHT_MESSAGES: u32 = 1000; - #[frame_support::pallet] pub mod pallet { use super::*; @@ -114,22 +112,30 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Something to execute an XCM message. We need this to service the XCMoXCMP queue. - type XcmExecutor: ExecuteXcm; - - /// Information on the avaialble XCMP channels. + /// Information on the available XCMP channels. type ChannelInfo: GetChannelInfo; /// Means of converting an `Xcm` into a `VersionedXcm`. type VersionWrapper: WrapVersion; - /// The origin that is allowed to execute overweight messages. - type ExecuteOverweightOrigin: EnsureOrigin; + /// Enqueue an inbound horizontal message for later processing. + /// + /// This defines the maximal message length via [`crate::MaxXcmpMessageLenOf`]. The pallet + /// assumes that this hook will eventually process all the pushed messages. + type XcmpQueue: EnqueueMessage; + + /// The maximum number of inbound XCMP channels that can be suspended simultaneously. + /// + /// Any further channel suspensions will fail and messages may get dropped without further + /// notice. Choosing a high value (1000) is okay; the trade-off that is described in + /// [`InboundXcmpSuspended`] still applies at that scale. + #[pallet::constant] + type MaxInboundSuspended: Get; /// The origin that is allowed to resume or suspend the XCMP queue. type ControllerOrigin: EnsureOrigin; - /// The conversion function used to attempt to convert an XCM `MultiLocation` origin to a + /// The conversion function used to attempt to convert an XCM `Location` origin to a /// superuser origin. type ControllerOriginConverter: ConvertOrigin; @@ -140,52 +146,8 @@ pub mod pallet { type WeightInfo: WeightInfo; } - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_idle(_now: BlockNumberFor, max_weight: Weight) -> Weight { - // on_idle processes additional messages with any remaining block weight. - Self::service_xcmp_queue(max_weight) - } - } - #[pallet::call] impl Pallet { - /// Services a single overweight XCM. - /// - /// - `origin`: Must pass `ExecuteOverweightOrigin`. - /// - `index`: The index of the overweight XCM to service - /// - `weight_limit`: The amount of weight that XCM execution may take. - /// - /// Errors: - /// - `BadOverweightIndex`: XCM under `index` is not found in the `Overweight` storage map. - /// - `BadXcm`: XCM under `index` cannot be properly decoded into a valid XCM format. - /// - `WeightOverLimit`: XCM execution may use greater `weight_limit`. - /// - /// Events: - /// - `OverweightServiced`: On success. - #[pallet::call_index(0)] - #[pallet::weight((weight_limit.saturating_add(Weight::from_parts(1_000_000, 0)), DispatchClass::Operational))] - pub fn service_overweight( - origin: OriginFor, - index: OverweightIndex, - weight_limit: Weight, - ) -> DispatchResultWithPostInfo { - T::ExecuteOverweightOrigin::ensure_origin(origin)?; - - let (sender, sent_at, data) = - Overweight::::get(index).ok_or(Error::::BadOverweightIndex)?; - let xcm = VersionedXcm::::decode_all_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data.as_slice(), - ) - .map_err(|_| Error::::BadXcm)?; - let used = Self::handle_xcm_message(sender, sent_at, xcm, weight_limit) - .map_err(|_| Error::::WeightOverLimit)?; - Overweight::::remove(index); - Self::deposit_event(Event::OverweightServiced { index, used }); - Ok(Some(used.saturating_add(Weight::from_parts(1_000_000, 0))).into()) - } - /// Suspends all XCM executions for the XCMP queue, regardless of the sender's origin. /// /// - `origin`: Must pass `ControllerOrigin`. @@ -194,9 +156,14 @@ pub mod pallet { pub fn suspend_xcm_execution(origin: OriginFor) -> DispatchResult { T::ControllerOrigin::ensure_origin(origin)?; - QueueSuspended::::put(true); - - Ok(()) + QueueSuspended::::try_mutate(|suspended| { + if *suspended { + Err(Error::::AlreadySuspended.into()) + } else { + *suspended = true; + Ok(()) + } + }) } /// Resumes all XCM executions for the XCMP queue. @@ -209,13 +176,18 @@ pub mod pallet { pub fn resume_xcm_execution(origin: OriginFor) -> DispatchResult { T::ControllerOrigin::ensure_origin(origin)?; - QueueSuspended::::put(false); - - Ok(()) + QueueSuspended::::try_mutate(|suspended| { + if !*suspended { + Err(Error::::AlreadyResumed.into()) + } else { + *suspended = false; + Ok(()) + } + }) } - /// Overwrites the number of pages of messages which must be in the queue for the other side - /// to be told to suspend their sending. + /// Overwrites the number of pages which must be in the queue for the other side to be + /// told to suspend their sending. /// /// - `origin`: Must pass `Root`. /// - `new`: Desired value for `QueueConfigData.suspend_value` @@ -223,13 +195,15 @@ pub mod pallet { #[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))] pub fn update_suspend_threshold(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; - QueueConfig::::mutate(|data| data.suspend_threshold = new); - Ok(()) + QueueConfig::::try_mutate(|data| { + data.suspend_threshold = new; + data.validate::() + }) } - /// Overwrites the number of pages of messages which must be in the queue after which we - /// drop any further messages from the channel. + /// Overwrites the number of pages which must be in the queue after which we drop any + /// further messages from the channel. /// /// - `origin`: Must pass `Root`. /// - `new`: Desired value for `QueueConfigData.drop_threshold` @@ -237,13 +211,15 @@ pub mod pallet { #[pallet::weight((T::WeightInfo::set_config_with_u32(),DispatchClass::Operational,))] pub fn update_drop_threshold(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; - QueueConfig::::mutate(|data| data.drop_threshold = new); - Ok(()) + QueueConfig::::try_mutate(|data| { + data.drop_threshold = new; + data.validate::() + }) } - /// Overwrites the number of pages of messages which the queue must be reduced to before it - /// signals that message sending may recommence after it has been suspended. + /// Overwrites the number of pages which the queue must be reduced to before it signals + /// that message sending may recommence after it has been suspended. /// /// - `origin`: Must pass `Root`. /// - `new`: Desired value for `QueueConfigData.resume_threshold` @@ -251,112 +227,68 @@ pub mod pallet { #[pallet::weight((T::WeightInfo::set_config_with_u32(), DispatchClass::Operational,))] pub fn update_resume_threshold(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; - QueueConfig::::mutate(|data| data.resume_threshold = new); - Ok(()) + QueueConfig::::try_mutate(|data| { + data.resume_threshold = new; + data.validate::() + }) } + } - /// Overwrites the amount of remaining weight under which we stop processing messages. - /// - /// - `origin`: Must pass `Root`. - /// - `new`: Desired value for `QueueConfigData.threshold_weight` - #[pallet::call_index(6)] - #[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))] - pub fn update_threshold_weight(origin: OriginFor, new: Weight) -> DispatchResult { - ensure_root(origin)?; - QueueConfig::::mutate(|data| data.threshold_weight = new); - - Ok(()) + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + let w = Self::on_idle_weight(); + assert!(w != Weight::zero()); + assert!(w.all_lte(T::BlockWeights::get().max_block)); } - /// Overwrites the speed to which the available weight approaches the maximum weight. - /// A lower number results in a faster progression. A value of 1 makes the entire weight - /// available initially. - /// - /// - `origin`: Must pass `Root`. - /// - `new`: Desired value for `QueueConfigData.weight_restrict_decay`. - #[pallet::call_index(7)] - #[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))] - pub fn update_weight_restrict_decay(origin: OriginFor, new: Weight) -> DispatchResult { - ensure_root(origin)?; - QueueConfig::::mutate(|data| data.weight_restrict_decay = new); + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { + let mut meter = WeightMeter::with_limit(limit); - Ok(()) - } + if meter.try_consume(Self::on_idle_weight()).is_err() { + log::debug!( + "Not enough weight for on_idle. {} < {}", + Self::on_idle_weight(), + limit + ); + return meter.consumed() + } - /// Overwrite the maximum amount of weight any individual message may consume. - /// Messages above this weight go into the overweight queue and may only be serviced - /// explicitly. - /// - /// - `origin`: Must pass `Root`. - /// - `new`: Desired value for `QueueConfigData.xcmp_max_individual_weight`. - #[pallet::call_index(8)] - #[pallet::weight((T::WeightInfo::set_config_with_weight(), DispatchClass::Operational,))] - pub fn update_xcmp_max_individual_weight( - origin: OriginFor, - new: Weight, - ) -> DispatchResult { - ensure_root(origin)?; - QueueConfig::::mutate(|data| data.xcmp_max_individual_weight = new); + migration::v3::lazy_migrate_inbound_queue::(); - Ok(()) + meter.consumed() } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Some XCM was executed ok. - Success { message_hash: XcmHash, message_id: XcmHash, weight: Weight }, - /// Some XCM failed. - Fail { message_hash: XcmHash, message_id: XcmHash, error: XcmError, weight: Weight }, - /// Bad XCM version used. - BadVersion { message_hash: XcmHash }, - /// Bad XCM format used. - BadFormat { message_hash: XcmHash }, /// An HRMP message was sent to a sibling parachain. XcmpMessageSent { message_hash: XcmHash }, - /// An XCM exceeded the individual message weight budget. - OverweightEnqueued { - sender: ParaId, - sent_at: RelayBlockNumber, - index: OverweightIndex, - required: Weight, - }, - /// An XCM from the overweight queue was executed with the given actual weight used. - OverweightServiced { index: OverweightIndex, used: Weight }, } #[pallet::error] pub enum Error { - /// Failed to send XCM message. - FailedToSend, - /// Bad XCM origin. - BadXcmOrigin, - /// Bad XCM data. - BadXcm, - /// Bad overweight index. - BadOverweightIndex, - /// Provided weight is possibly not enough to execute the message. - WeightOverLimit, + /// Setting the queue config failed since one of its values was invalid. + BadQueueConfig, + /// The execution is already suspended. + AlreadySuspended, + /// The execution is already resumed. + AlreadyResumed, } - /// Status of the inbound XCMP channels. - #[pallet::storage] - pub(super) type InboundXcmpStatus = - StorageValue<_, Vec, ValueQuery>; - - /// Inbound aggregate XCMP messages. It can only be one per ParaId/block. + /// The suspended inbound XCMP channels. All others are not suspended. + /// + /// This is a `StorageValue` instead of a `StorageMap` since we expect multiple reads per block + /// to different keys with a one byte payload. The access to `BoundedBTreeSet` will be cached + /// within the block and therefore only included once in the proof size. + /// + /// NOTE: The PoV benchmarking cannot know this and will over-estimate, but the actual proof + /// will be smaller. #[pallet::storage] - pub(super) type InboundXcmpMessages = StorageDoubleMap< - _, - Blake2_128Concat, - ParaId, - Twox64Concat, - RelayBlockNumber, - Vec, - ValueQuery, - >; + pub type InboundXcmpSuspended = + StorageValue<_, BoundedBTreeSet, ValueQuery>; /// The non-empty XCMP channels in order of becoming non-empty, and the index of the first /// and last outbound message. If the two indices are equal, then it indicates an empty @@ -383,19 +315,6 @@ pub mod pallet { #[pallet::storage] pub(super) type QueueConfig = StorageValue<_, QueueConfigData, ValueQuery>; - /// The messages that exceeded max individual message weight budget. - /// - /// These message stay in this storage map until they are manually dispatched via - /// `service_overweight`. - #[pallet::storage] - pub(super) type Overweight = - CountedStorageMap<_, Twox64Concat, OverweightIndex, (ParaId, RelayBlockNumber, Vec)>; - - /// The number of overweight messages ever recorded in `Overweight`. Also doubles as the next - /// available free overweight index. - #[pallet::storage] - pub(super) type OverweightCount = StorageValue<_, OverweightIndex, ValueQuery>; - /// Whether or not the XCMP queue is suspended from executing incoming XCMs or not. #[pallet::storage] pub(super) type QueueSuspended = StorageValue<_, bool, ValueQuery>; @@ -412,34 +331,15 @@ pub mod pallet { StorageMap<_, Twox64Concat, ParaId, FixedU128, ValueQuery, InitialFactor>; } -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum InboundState { - Ok, - Suspended, -} - #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum OutboundState { Ok, Suspended, } -/// Struct containing detailed information about the inbound channel. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] -pub struct InboundChannelDetails { - /// The `ParaId` of the parachain that this channel is connected with. - sender: ParaId, - /// The state of the channel. - state: InboundState, - /// The ordered metadata of each inbound message. - /// - /// Contains info about the relay block number that the message was sent at, and the format - /// of the incoming message. - message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>, -} - /// Struct containing detailed information about the outbound channel. -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] pub struct OutboundChannelDetails { /// The `ParaId` of the parachain that this channel is connected with. recipient: ParaId, @@ -477,37 +377,42 @@ impl OutboundChannelDetails { #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct QueueConfigData { - /// The number of pages of messages which must be in the queue for the other side to be told to - /// suspend their sending. + /// The number of pages which must be in the queue for the other side to be told to suspend + /// their sending. suspend_threshold: u32, - /// The number of pages of messages which must be in the queue after which we drop any further - /// messages from the channel. + /// The number of pages which must be in the queue after which we drop any further messages + /// from the channel. This should normally not happen since the `suspend_threshold` can be used + /// to suspend the channel. drop_threshold: u32, - /// The number of pages of messages which the queue must be reduced to before it signals that + /// The number of pages which the queue must be reduced to before it signals that /// message sending may recommence after it has been suspended. resume_threshold: u32, - /// The amount of remaining weight under which we stop processing messages. - threshold_weight: Weight, - /// The speed to which the available weight approaches the maximum weight. A lower number - /// results in a faster progression. A value of 1 makes the entire weight available initially. - weight_restrict_decay: Weight, - /// The maximum amount of weight any individual message may consume. Messages above this weight - /// go into the overweight queue and may only be serviced explicitly. - xcmp_max_individual_weight: Weight, } impl Default for QueueConfigData { fn default() -> Self { + // NOTE that these default values are only used on genesis. They should give a rough idea of + // what to set these values to, but is in no way a requirement. Self { - suspend_threshold: 2, - drop_threshold: 5, - resume_threshold: 1, - threshold_weight: Weight::from_parts(100_000, 0), - weight_restrict_decay: Weight::from_parts(2, 0), - xcmp_max_individual_weight: Weight::from_parts( - 20u64 * WEIGHT_REF_TIME_PER_MILLIS, - DEFAULT_POV_SIZE, - ), + drop_threshold: 48, // 64KiB * 48 = 3MiB + suspend_threshold: 32, // 64KiB * 32 = 2MiB + resume_threshold: 8, // 64KiB * 8 = 512KiB + } + } +} + +impl QueueConfigData { + /// Validate all assumptions about `Self`. + /// + /// Should be called prior to accepting this as new config. + pub fn validate(&self) -> sp_runtime::DispatchResult { + if self.resume_threshold < self.suspend_threshold && + self.suspend_threshold <= self.drop_threshold && + self.resume_threshold > 0 + { + Ok(()) + } else { + Err(Error::::BadQueueConfig.into()) } } } @@ -549,11 +454,21 @@ impl Pallet { ) -> Result { let encoded_fragment = fragment.encode(); + // Optimization note: `max_message_size` could potentially be stored in + // `OutboundXcmpMessages` once known; that way it's only accessed when a new page is needed. + let channel_info = T::ChannelInfo::get_channel_info(recipient).ok_or(MessageSendError::NoChannel)?; - let max_message_size = channel_info.max_message_size as usize; // Max message size refers to aggregates, or pages. Not to individual fragments. - if encoded_fragment.len() > max_message_size { + let max_message_size = channel_info.max_message_size as usize; + let format_size = format.encoded_size(); + // We check the encoded fragment length plus the format size agains the max message size + // because the format is concatenated if a new page is needed. + let size_to_check = encoded_fragment + .len() + .checked_add(format_size) + .ok_or(MessageSendError::TooBig)?; + if size_to_check > max_message_size { return Err(MessageSendError::TooBig) } @@ -582,6 +497,7 @@ impl Pallet { &mut &page[..], ) != Ok(format) { + defensive!("Bad format in outbound queue; dropping message"); return None } if page.len() + encoded_fragment.len() > max_message_size { @@ -627,7 +543,7 @@ impl Pallet { /// Sends a signal to the `dest` chain over XCMP. This is guaranteed to be dispatched on this /// block. - fn send_signal(dest: ParaId, signal: ChannelSignal) -> Result<(), ()> { + fn send_signal(dest: ParaId, signal: ChannelSignal) { let mut s = >::get(); if let Some(details) = s.iter_mut().find(|item| item.recipient == dest) { details.signals_exist = true; @@ -635,365 +551,16 @@ impl Pallet { s.push(OutboundChannelDetails::new(dest).with_signals()); } >::mutate(dest, |page| { - if page.is_empty() { - *page = (XcmpMessageFormat::Signals, signal).encode(); - } else { - signal.using_encoded(|s| page.extend_from_slice(s)); - } + *page = (XcmpMessageFormat::Signals, signal).encode(); }); >::put(s); - - Ok(()) - } - - pub fn send_blob_message(recipient: ParaId, blob: Vec) -> Result { - Self::send_fragment(recipient, XcmpMessageFormat::ConcatenatedEncodedBlob, blob) - } - - pub fn send_xcm_message( - recipient: ParaId, - xcm: VersionedXcm<()>, - ) -> Result { - Self::send_fragment(recipient, XcmpMessageFormat::ConcatenatedVersionedXcm, xcm) - } - - fn create_shuffle(len: usize) -> Vec { - // Create a shuffled order for use to iterate through. - // Not a great random seed, but good enough for our purposes. - let seed = frame_system::Pallet::::parent_hash(); - let seed = - <[u8; 32]>::decode(&mut sp_runtime::traits::TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - let mut rng = ChaChaRng::from_seed(seed); - let mut shuffled = (0..len).collect::>(); - for i in 0..len { - let j = (rng.next_u32() as usize) % len; - shuffled.as_mut_slice().swap(i, j); - } - shuffled - } - - fn handle_blob_message( - _sender: ParaId, - _sent_at: RelayBlockNumber, - _blob: Vec, - _weight_limit: Weight, - ) -> Result { - debug_assert!(false, "Blob messages not handled."); - Err(false) - } - - fn handle_xcm_message( - sender: ParaId, - _sent_at: RelayBlockNumber, - xcm: VersionedXcm, - max_weight: Weight, - ) -> Result { - let message_hash = xcm.using_encoded(sp_io::hashing::blake2_256); - log::debug!("Processing XCMP-XCM: {:?}", &message_hash); - let (result, event) = match Xcm::::try_from(xcm) { - Ok(xcm) => { - let location = (Parent, Parachain(sender.into())); - let mut message_id = message_hash; - - match T::XcmExecutor::prepare_and_execute( - location, - xcm, - &mut message_id, - max_weight, - Weight::zero(), - ) { - Outcome::Error(error) => ( - Err(error), - Event::Fail { message_hash, message_id, error, weight: Weight::zero() }, - ), - Outcome::Complete(weight) => - (Ok(weight), Event::Success { message_hash, message_id, weight }), - // As far as the caller is concerned, this was dispatched without error, so - // we just report the weight used. - Outcome::Incomplete(weight, error) => - (Ok(weight), Event::Fail { message_hash, message_id, error, weight }), - } - }, - Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion { message_hash }), - }; - Self::deposit_event(event); - result - } - - fn process_xcmp_message( - sender: ParaId, - (sent_at, format): (RelayBlockNumber, XcmpMessageFormat), - messages_processed: &mut u8, - max_weight: Weight, - max_individual_weight: Weight, - ) -> (Weight, bool) { - let data = >::get(sender, sent_at); - let mut last_remaining_fragments; - let mut remaining_fragments = &data[..]; - let mut weight_used = Weight::zero(); - match format { - XcmpMessageFormat::ConcatenatedVersionedXcm => { - while !remaining_fragments.is_empty() && - *messages_processed < MAX_MESSAGES_PER_BLOCK - { - last_remaining_fragments = remaining_fragments; - if let Ok(xcm) = VersionedXcm::::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut remaining_fragments, - ) { - let weight = max_weight - weight_used; - *messages_processed += 1; - match Self::handle_xcm_message(sender, sent_at, xcm, weight) { - Ok(used) => weight_used = weight_used.saturating_add(used), - Err(XcmError::WeightLimitReached(required)) - if required.any_gt(max_individual_weight) => - { - let is_under_limit = - Overweight::::count() < MAX_OVERWEIGHT_MESSAGES; - weight_used.saturating_accrue(T::DbWeight::get().reads(1)); - if is_under_limit { - // overweight - add to overweight queue and continue with - // message execution consuming the message. - let msg_len = last_remaining_fragments - .len() - .saturating_sub(remaining_fragments.len()); - let overweight_xcm = - last_remaining_fragments[..msg_len].to_vec(); - let index = - Self::stash_overweight(sender, sent_at, overweight_xcm); - let e = Event::OverweightEnqueued { - sender, - sent_at, - index, - required, - }; - Self::deposit_event(e); - } - }, - Err(XcmError::WeightLimitReached(required)) - if required.all_lte(max_weight) => - { - // That message didn't get processed this time because of being - // too heavy. We leave it around for next time and bail. - remaining_fragments = last_remaining_fragments; - break - }, - Err(error) => { - log::error!( - "Failed to process XCMP-XCM message, caused by {:?}", - error - ); - // Message looks invalid; don't attempt to retry - }, - } - } else { - debug_assert!(false, "Invalid incoming XCMP message data"); - remaining_fragments = &b""[..]; - } - } - }, - XcmpMessageFormat::ConcatenatedEncodedBlob => { - while !remaining_fragments.is_empty() { - last_remaining_fragments = remaining_fragments; - - if let Ok(blob) = >::decode(&mut remaining_fragments) { - let weight = max_weight - weight_used; - *messages_processed += 1; - match Self::handle_blob_message(sender, sent_at, blob, weight) { - Ok(used) => weight_used = weight_used.saturating_add(used), - Err(true) => { - // That message didn't get processed this time because of being - // too heavy. We leave it around for next time and bail. - remaining_fragments = last_remaining_fragments; - break - }, - Err(false) => { - // Message invalid; don't attempt to retry - }, - } - } else { - debug_assert!(false, "Invalid incoming blob message data"); - remaining_fragments = &b""[..]; - } - } - }, - XcmpMessageFormat::Signals => { - debug_assert!(false, "All signals are handled immediately; qed"); - remaining_fragments = &b""[..]; - }, - } - let is_empty = remaining_fragments.is_empty(); - if is_empty { - >::remove(sender, sent_at); - } else { - >::insert(sender, sent_at, remaining_fragments); - } - (weight_used, is_empty) - } - - /// Puts a given XCM into the list of overweight messages, allowing it to be executed later. - fn stash_overweight( - sender: ParaId, - sent_at: RelayBlockNumber, - xcm: Vec, - ) -> OverweightIndex { - let index = OverweightCount::::mutate(|count| { - let index = *count; - *count += 1; - index - }); - - Overweight::::insert(index, (sender, sent_at, xcm)); - index - } - - /// Service the incoming XCMP message queue attempting to execute up to `max_weight` execution - /// weight of messages. - /// - /// Channels are first shuffled and then processed in this random one page at a time, order over - /// and over until either `max_weight` is exhausted or no channel has messages that can be - /// processed any more. - /// - /// There are two obvious "modes" that we could apportion `max_weight`: one would be to attempt - /// to spend it all on the first channel's first page, then use the leftover (if any) for the - /// second channel's first page and so on until finally we cycle back and the process messages - /// on the first channel's second page &c. The other mode would be to apportion only `1/N` of - /// `max_weight` for the first page (where `N` could be, perhaps, the number of channels to - /// service, using the remainder plus the next `1/N` for the next channel's page &c. - /// - /// Both modes have good qualities, the first ensures that a channel with a large message (over - /// `1/N` does not get indefinitely blocked if other channels have continuous, light traffic. - /// The second is fairer, and ensures that channels with continuous light messages don't suffer - /// high latency. - /// - /// The following code is a hybrid solution; we have a concept of `weight_available` which - /// incrementally approaches `max_weight` as more channels are attempted to be processed. We use - /// the parameter `weight_restrict_decay` to control the speed with which `weight_available` - /// approaches `max_weight`, with `0` being strictly equivalent to the first aforementioned - /// mode, and `N` approximating the second. A reasonable parameter may be `1`, which makes - /// half of the `max_weight` available for the first page, then a quarter plus the remainder - /// for the second &c. though empirical and or practical factors may give rise to adjusting it - /// further. - fn service_xcmp_queue(max_weight: Weight) -> Weight { - let suspended = QueueSuspended::::get(); - let mut messages_processed = 0; - - let mut status = >::get(); // <- sorted. - if status.is_empty() { - return Weight::zero() - } - - let QueueConfigData { - resume_threshold, - threshold_weight, - weight_restrict_decay, - xcmp_max_individual_weight, - .. - } = >::get(); - - let mut shuffled = Self::create_shuffle(status.len()); - let mut weight_used = Weight::zero(); - let mut weight_available = Weight::zero(); - - // We don't want the possibility of a chain sending a series of really heavy messages and - // tying up the block's execution time from other chains. Therefore we execute any remaining - // messages in a random order. - // Order within a single channel will always be preserved, however this does mean that - // relative order between channels may not. The result is that chains which tend to send - // fewer, lighter messages will generally have a lower latency than chains which tend to - // send more, heavier messages. - - let mut shuffle_index = 0; - while shuffle_index < shuffled.len() && - max_weight.saturating_sub(weight_used).all_gte(threshold_weight) && - messages_processed < MAX_MESSAGES_PER_BLOCK - { - let index = shuffled[shuffle_index]; - let sender = status[index].sender; - let sender_origin = T::ControllerOriginConverter::convert_origin( - (Parent, Parachain(sender.into())), - OriginKind::Superuser, - ); - let is_controller = sender_origin - .map_or(false, |origin| T::ControllerOrigin::try_origin(origin).is_ok()); - - if suspended && !is_controller { - shuffle_index += 1; - continue - } - - if weight_available != max_weight { - // Get incrementally closer to freeing up max_weight for message execution over the - // first round. For the second round we unlock all weight. If we come close enough - // on the first round to unlocking everything, then we do so. - if shuffle_index < status.len() { - weight_available += - (max_weight - weight_available) / (weight_restrict_decay.ref_time() + 1); - if (weight_available + threshold_weight).any_gt(max_weight) { - weight_available = max_weight; - } - } else { - weight_available = max_weight; - } - } - - let weight_processed = if status[index].message_metadata.is_empty() { - debug_assert!(false, "channel exists in status; there must be messages; qed"); - Weight::zero() - } else { - // Process up to one block's worth for now. - let weight_remaining = weight_available.saturating_sub(weight_used); - let (weight_processed, is_empty) = Self::process_xcmp_message( - sender, - status[index].message_metadata[0], - &mut messages_processed, - weight_remaining, - xcmp_max_individual_weight, - ); - if is_empty { - status[index].message_metadata.remove(0); - } - weight_processed - }; - weight_used += weight_processed; - - if status[index].message_metadata.len() as u32 <= resume_threshold && - status[index].state == InboundState::Suspended - { - // Resume - let r = Self::send_signal(sender, ChannelSignal::Resume); - debug_assert!(r.is_ok(), "WARNING: Failed sending resume into suspended channel"); - status[index].state = InboundState::Ok; - } - - // If there are more and we're making progress, we process them after we've given the - // other channels a look in. If we've still not unlocked all weight, then we set them - // up for processing a second time anyway. - if !status[index].message_metadata.is_empty() && - (weight_processed.any_gt(Weight::zero()) || weight_available != max_weight) - { - if shuffle_index + 1 == shuffled.len() { - // Only this queue left. Just run around this loop once more. - continue - } - shuffled.push(index); - } - shuffle_index += 1; - } - - // Only retain the senders that have non-empty queues. - status.retain(|item| !item.message_metadata.is_empty()); - - >::put(status); - weight_used } fn suspend_channel(target: ParaId) { >::mutate(|s| { if let Some(details) = s.iter_mut().find(|item| item.recipient == target) { let ok = details.state == OutboundState::Ok; - debug_assert!(ok, "WARNING: Attempt to suspend channel that was not Ok."); + defensive_assert!(ok, "WARNING: Attempt to suspend channel that was not Ok."); details.state = OutboundState::Suspended; } else { s.push(OutboundChannelDetails::new(target).with_suspended_state()); @@ -1005,7 +572,7 @@ impl Pallet { >::mutate(|s| { if let Some(index) = s.iter().position(|item| item.recipient == target) { let suspended = s[index].state == OutboundState::Suspended; - debug_assert!( + defensive_assert!( suspended, "WARNING: Attempt to resume channel that was not suspended." ); @@ -1015,18 +582,67 @@ impl Pallet { s[index].state = OutboundState::Ok; } } else { - debug_assert!(false, "WARNING: Attempt to resume channel that was not suspended."); + defensive!("WARNING: Attempt to resume channel that was not suspended."); } }); } + fn enqueue_xcmp_message( + sender: ParaId, + xcm: BoundedVec>, + meter: &mut WeightMeter, + ) -> Result<(), ()> { + if meter.try_consume(T::WeightInfo::enqueue_xcmp_message()).is_err() { + defensive!("Out of weight: cannot enqueue XCMP messages; dropping msg"); + return Err(()) + } + + let QueueConfigData { drop_threshold, .. } = >::get(); + let fp = T::XcmpQueue::footprint(sender); + // Assume that it will not fit into the current page: + let new_pages = fp.ready_pages.saturating_add(1); + if new_pages > drop_threshold { + // This should not happen since the channel should have been suspended in + // [`on_queue_changed`]. + log::error!("XCMP queue for sibling {:?} is full; dropping messages.", sender); + return Err(()) + } + + T::XcmpQueue::enqueue_message(xcm.as_bounded_slice(), sender); + Ok(()) + } + + /// Split concatenated encoded `VersionedXcm`s or `MaybeDoubleEncodedVersionedXcm`s into + /// individual items. + /// + /// We directly encode them again since that is needed later on. + pub(crate) fn take_first_concatenated_xcm( + data: &mut &[u8], + meter: &mut WeightMeter, + ) -> Result>, ()> { + if data.is_empty() { + return Err(()) + } + + if meter.try_consume(T::WeightInfo::take_first_concatenated_xcm()).is_err() { + defensive!("Out of weight; could not decode all; dropping"); + return Err(()) + } + + let xcm = VersionedXcm::<()>::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, data) + .map_err(|_| ())?; + xcm.encode().try_into().map_err(|_| ()) + } + + /// The worst-case weight of `on_idle`. + pub fn on_idle_weight() -> Weight { + ::WeightInfo::on_idle_good_msg() + .max(::WeightInfo::on_idle_large_msg()) + } + #[cfg(feature = "bridging")] fn is_inbound_channel_suspended(sender: ParaId) -> bool { - >::get() - .iter() - .find(|c| c.sender == sender) - .map(|c| c.state == InboundState::Suspended) - .unwrap_or(false) + >::get().iter().any(|c| c == &sender) } #[cfg(feature = "bridging")] @@ -1039,84 +655,112 @@ impl Pallet { } } +impl OnQueueChanged for Pallet { + // Suspends/Resumes the queue when certain thresholds are reached. + fn on_queue_changed(para: ParaId, fp: QueueFootprint) { + let QueueConfigData { resume_threshold, suspend_threshold, .. } = >::get(); + + let mut suspended_channels = >::get(); + let suspended = suspended_channels.contains(¶); + + if suspended && fp.ready_pages <= resume_threshold { + Self::send_signal(para, ChannelSignal::Resume); + + suspended_channels.remove(¶); + >::put(suspended_channels); + } else if !suspended && fp.ready_pages >= suspend_threshold { + log::warn!("XCMP queue for sibling {:?} is full; suspending channel.", para); + Self::send_signal(para, ChannelSignal::Suspend); + + if let Err(err) = suspended_channels.try_insert(para) { + log::error!("Too many channels suspended; cannot suspend sibling {:?}: {:?}; further messages may be dropped.", para, err); + } + >::put(suspended_channels); + } + } +} + +impl QueuePausedQuery for Pallet { + fn is_paused(para: &ParaId) -> bool { + if !QueueSuspended::::get() { + return false + } + + // Make an exception for the superuser queue: + let sender_origin = T::ControllerOriginConverter::convert_origin( + (Parent, Parachain((*para).into())), + OriginKind::Superuser, + ); + let is_controller = + sender_origin.map_or(false, |origin| T::ControllerOrigin::try_origin(origin).is_ok()); + + !is_controller + } +} + impl XcmpMessageHandler for Pallet { fn handle_xcmp_messages<'a, I: Iterator>( iter: I, max_weight: Weight, ) -> Weight { - let mut status = >::get(); - - let QueueConfigData { suspend_threshold, drop_threshold, .. } = >::get(); + let mut meter = WeightMeter::with_limit(max_weight); - for (sender, sent_at, data) in iter { - // Figure out the message format. - let mut data_ref = data; - let format = match XcmpMessageFormat::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data_ref, - ) { + for (sender, _sent_at, mut data) in iter { + let format = match XcmpMessageFormat::decode(&mut data) { Ok(f) => f, Err(_) => { - debug_assert!(false, "Unknown XCMP message format. Silently dropping message"); + defensive!("Unknown XCMP message format - dropping"); continue }, }; - if format == XcmpMessageFormat::Signals { - while !data_ref.is_empty() { - use ChannelSignal::*; - match ChannelSignal::decode(&mut data_ref) { - Ok(Suspend) => Self::suspend_channel(sender), - Ok(Resume) => Self::resume_channel(sender), - Err(_) => break, - } - } - } else { - // Record the fact we received it. - match status.binary_search_by_key(&sender, |item| item.sender) { - Ok(i) => { - let count = status[i].message_metadata.len() as u32; - if count >= suspend_threshold && status[i].state == InboundState::Ok { - status[i].state = InboundState::Suspended; - let r = Self::send_signal(sender, ChannelSignal::Suspend); - if r.is_err() { - log::warn!( - "Attempt to suspend channel failed. Messages may be dropped." - ); - } + + match format { + XcmpMessageFormat::Signals => + while !data.is_empty() { + if meter + .try_consume( + T::WeightInfo::suspend_channel() + .max(T::WeightInfo::resume_channel()), + ) + .is_err() + { + defensive!("Not enough weight to process signals - dropping"); + break } - if count < drop_threshold { - status[i].message_metadata.push((sent_at, format)); - } else { - debug_assert!( - false, - "XCMP channel queue full. Silently dropping message" - ); + + match ChannelSignal::decode(&mut data) { + Ok(ChannelSignal::Suspend) => Self::suspend_channel(sender), + Ok(ChannelSignal::Resume) => Self::resume_channel(sender), + Err(_) => { + defensive!("Undecodable channel signal - dropping"); + break + }, } - // Update the delivery fee factor, if applicable. - if count > suspend_threshold { - let message_size_factor = - FixedU128::from((data_ref.len() / 1024) as u128) - .saturating_mul(delivery_fee_constants::MESSAGE_SIZE_FEE_BASE); - Self::increase_fee_factor(sender, message_size_factor); + }, + XcmpMessageFormat::ConcatenatedVersionedXcm => + while !data.is_empty() { + let Ok(xcm) = Self::take_first_concatenated_xcm(&mut data, &mut meter) + else { + defensive!("HRMP inbound decode stream broke; page will be dropped.",); + break + }; + + if let Err(()) = Self::enqueue_xcmp_message(sender, xcm, &mut meter) { + defensive!( + "Could not enqueue XCMP messages. Used weight: ", + meter.consumed_ratio() + ); + break } }, - Err(_) => status.push(InboundChannelDetails { - sender, - state: InboundState::Ok, - message_metadata: vec![(sent_at, format)], - }), - } - // Queue the payload for later execution. - >::insert(sender, sent_at, data_ref); + XcmpMessageFormat::ConcatenatedEncodedBlob => { + defensive!("Blob messages are unhandled - dropping"); + continue + }, } - - // Optimization note; it would make sense to execute messages immediately if - // `status.is_empty()` here. } - status.sort(); - >::put(status); - Self::service_xcmp_queue(max_weight) + meter.consumed() } } @@ -1136,14 +780,6 @@ impl XcmpMessageSource for Pallet { mut last_index, } = *status; - if result.len() == max_message_count { - // We check this condition in the beginning of the loop so that we don't include - // a message where the limit is 0. - break - } - if outbound_state == OutboundState::Suspended { - continue - } let (max_size_now, max_size_ever) = match T::ChannelInfo::get_channel_status(para_id) { ChannelStatus::Closed => { // This means that there is no such channel anymore. Nothing to be done but @@ -1161,15 +797,28 @@ impl XcmpMessageSource for Pallet { ChannelStatus::Ready(n, e) => (n, e), }; + // This is a hard limit from the host config; not even signals can bypass it. + if result.len() == max_message_count { + // We check this condition in the beginning of the loop so that we don't include + // a message where the limit is 0. + break + } + let page = if signals_exist { let page = >::get(para_id); + defensive_assert!(!page.is_empty(), "Signals must exist"); + if page.len() < max_size_now { >::remove(para_id); signals_exist = false; page } else { + defensive!("Signals should fit into a single page"); continue } + } else if outbound_state == OutboundState::Suspended { + // Signals are exempt from suspension. + continue } else if last_index > first_index { let page = >::get(para_id, first_index); if page.len() < max_size_now { @@ -1189,9 +838,9 @@ impl XcmpMessageSource for Pallet { if page.len() > max_size_ever { // TODO: #274 This means that the channel's max message size has changed since - // the message was sent. We should parse it and split into smaller mesasges but + // the message was sent. We should parse it and split into smaller messages but // since it's so unlikely then for now we just drop it. - log::warn!("WARNING: oversize message in queue. silently dropping."); + defensive!("WARNING: oversize message in queue - dropping"); } else { result.push((para_id, page)); } @@ -1219,6 +868,7 @@ impl XcmpMessageSource for Pallet { last_index, }; } + debug_assert!(!statuses.iter().any(|s| s.signals_exist), "Signals should be handled"); // Sort the outbound messages by ascending recipient para id to satisfy the acceptance // criteria requirement. @@ -1253,19 +903,22 @@ impl SendXcm for Pallet { type Ticket = (ParaId, VersionedXcm<()>); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, ) -> SendResult<(ParaId, VersionedXcm<()>)> { let d = dest.take().ok_or(SendError::MissingArgument)?; - match &d { + match d.unpack() { // An HRMP message for a sibling parachain. - MultiLocation { parents: 1, interior: X1(Parachain(id)) } => { + (1, [Parachain(id)]) => { let xcm = msg.take().ok_or(SendError::MissingArgument)?; let id = ParaId::from(*id); let price = T::PriceForSiblingDelivery::price_for_delivery(id, &xcm); let versioned_xcm = T::VersionWrapper::wrap_version(&d, xcm) .map_err(|()| SendError::DestinationUnsupported)?; + validate_xcm_nesting(&versioned_xcm) + .map_err(|()| SendError::ExceedsMaxMessageSize)?; + Ok(((id, versioned_xcm), price)) }, _ => { @@ -1279,17 +932,31 @@ impl SendXcm for Pallet { fn deliver((id, xcm): (ParaId, VersionedXcm<()>)) -> Result { let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + defensive_assert!( + validate_xcm_nesting(&xcm).is_ok(), + "Tickets are valid prior to delivery by trait XCM; qed" + ); match Self::send_fragment(id, XcmpMessageFormat::ConcatenatedVersionedXcm, xcm) { Ok(_) => { Self::deposit_event(Event::XcmpMessageSent { message_hash: hash }); Ok(hash) }, - Err(e) => Err(SendError::Transport(<&'static str>::from(e))), + Err(e) => Err(SendError::Transport(e.into())), } } } +/// Checks that the XCM is decodable with `MAX_XCM_DECODE_DEPTH`. +/// +/// Note that this uses the limit of the sender - not the receiver. It it best effort. +pub(crate) fn validate_xcm_nesting(xcm: &VersionedXcm<()>) -> Result<(), ()> { + xcm.using_encoded(|mut enc| { + VersionedXcm::<()>::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut enc).map(|_| ()) + }) + .map_err(|_| ()) +} + impl FeeTracker for Pallet { type Id = ParaId; diff --git a/cumulus/pallets/xcmp-queue/src/migration.rs b/cumulus/pallets/xcmp-queue/src/migration.rs index a54ddfb9cecd0888bb0b54ab19a28d5796e41fab..c7fa61a3e3f0513e53d67fcee4efd6c3cecf29f6 100644 --- a/cumulus/pallets/xcmp-queue/src/migration.rs +++ b/cumulus/pallets/xcmp-queue/src/migration.rs @@ -16,43 +16,26 @@ //! A module that is responsible for migration of storage. -use crate::{Config, Overweight, Pallet, QueueConfig, DEFAULT_POV_SIZE}; +use crate::{Config, OverweightIndex, Pallet, QueueConfig, QueueConfigData, DEFAULT_POV_SIZE}; +use cumulus_primitives_core::XcmpMessageFormat; use frame_support::{ pallet_prelude::*, - traits::{OnRuntimeUpgrade, StorageVersion}, + traits::{EnqueueMessage, OnRuntimeUpgrade, StorageVersion}, weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}, }; -/// The current storage version. -pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); -/// Migrates the pallet storage to the most recent version. -pub struct Migration(PhantomData); - -impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let mut weight = T::DbWeight::get().reads(1); - - if StorageVersion::get::>() == 1 { - weight.saturating_accrue(migrate_to_v2::()); - StorageVersion::new(2).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - if StorageVersion::get::>() == 2 { - weight.saturating_accrue(migrate_to_v3::()); - StorageVersion::new(3).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - weight - } -} +pub const LOG: &str = "runtime::xcmp-queue-migration"; mod v1 { use super::*; use codec::{Decode, Encode}; + #[frame_support::storage_alias] + pub(crate) type QueueConfig = StorageValue, QueueConfigData, ValueQuery>; + #[derive(Encode, Decode, Debug)] pub struct QueueConfigData { pub suspend_threshold: u32, @@ -77,48 +60,264 @@ mod v1 { } } -/// Migrates `QueueConfigData` from v1 (using only reference time weights) to v2 (with -/// 2D weights). -/// -/// NOTE: Only use this function if you know what you're doing. Default to using -/// `migrate_to_latest`. -pub fn migrate_to_v2() -> Weight { - let translate = |pre: v1::QueueConfigData| -> super::QueueConfigData { - super::QueueConfigData { - suspend_threshold: pre.suspend_threshold, - drop_threshold: pre.drop_threshold, - resume_threshold: pre.resume_threshold, - threshold_weight: Weight::from_parts(pre.threshold_weight, 0), - weight_restrict_decay: Weight::from_parts(pre.weight_restrict_decay, 0), - xcmp_max_individual_weight: Weight::from_parts( - pre.xcmp_max_individual_weight, - DEFAULT_POV_SIZE, - ), +pub mod v2 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type QueueConfig = StorageValue, QueueConfigData, ValueQuery>; + + #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct QueueConfigData { + pub suspend_threshold: u32, + pub drop_threshold: u32, + pub resume_threshold: u32, + pub threshold_weight: Weight, + pub weight_restrict_decay: Weight, + pub xcmp_max_individual_weight: Weight, + } + + impl Default for QueueConfigData { + fn default() -> Self { + Self { + suspend_threshold: 2, + drop_threshold: 5, + resume_threshold: 1, + threshold_weight: Weight::from_parts(100_000, 0), + weight_restrict_decay: Weight::from_parts(2, 0), + xcmp_max_individual_weight: Weight::from_parts( + 20u64 * WEIGHT_REF_TIME_PER_MILLIS, + DEFAULT_POV_SIZE, + ), + } } - }; + } - if QueueConfig::::translate(|pre| pre.map(translate)).is_err() { - log::error!( - target: super::LOG_TARGET, - "unexpected error when performing translation of the QueueConfig type during storage upgrade to v2" - ); + /// Migrates `QueueConfigData` from v1 (using only reference time weights) to v2 (with + /// 2D weights). + pub struct UncheckedMigrationToV2(PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrationToV2 { + #[allow(deprecated)] + fn on_runtime_upgrade() -> Weight { + let translate = |pre: v1::QueueConfigData| -> v2::QueueConfigData { + v2::QueueConfigData { + suspend_threshold: pre.suspend_threshold, + drop_threshold: pre.drop_threshold, + resume_threshold: pre.resume_threshold, + threshold_weight: Weight::from_parts(pre.threshold_weight, 0), + weight_restrict_decay: Weight::from_parts(pre.weight_restrict_decay, 0), + xcmp_max_individual_weight: Weight::from_parts( + pre.xcmp_max_individual_weight, + DEFAULT_POV_SIZE, + ), + } + }; + + if v2::QueueConfig::::translate(|pre| pre.map(translate)).is_err() { + log::error!( + target: crate::LOG_TARGET, + "unexpected error when performing translation of the QueueConfig type \ + during storage upgrade to v2" + ); + } + + T::DbWeight::get().reads_writes(1, 1) + } } - T::DbWeight::get().reads_writes(1, 1) + /// [`UncheckedMigrationToV2`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 1. + #[allow(dead_code)] + pub type MigrationToV2 = frame_support::migrations::VersionedMigration< + 1, + 2, + UncheckedMigrationToV2, + Pallet, + ::DbWeight, + >; } -pub fn migrate_to_v3() -> Weight { - let overweight_messages = Overweight::::initialize_counter() as u64; +pub mod v3 { + use super::*; + use crate::*; - T::DbWeight::get().reads_writes(overweight_messages, 1) + /// Status of the inbound XCMP channels. + #[frame_support::storage_alias] + pub(crate) type InboundXcmpStatus = + StorageValue, Vec, OptionQuery>; + + /// Inbound aggregate XCMP messages. It can only be one per ParaId/block. + #[frame_support::storage_alias] + pub(crate) type InboundXcmpMessages = StorageDoubleMap< + Pallet, + Blake2_128Concat, + ParaId, + Twox64Concat, + RelayBlockNumber, + Vec, + OptionQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type QueueConfig = + StorageValue, v2::QueueConfigData, ValueQuery>; + + #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] + pub struct InboundChannelDetails { + /// The `ParaId` of the parachain that this channel is connected with. + pub sender: ParaId, + /// The state of the channel. + pub state: InboundState, + /// The ordered metadata of each inbound message. + /// + /// Contains info about the relay block number that the message was sent at, and the format + /// of the incoming message. + pub message_metadata: Vec<(RelayBlockNumber, XcmpMessageFormat)>, + } + + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, TypeInfo, + )] + pub enum InboundState { + Ok, + Suspended, + } + + /// Migrates the pallet storage to v3. + pub struct UncheckedMigrationToV3(PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrationToV3 { + fn on_runtime_upgrade() -> Weight { + #[frame_support::storage_alias] + type Overweight = + CountedStorageMap, Twox64Concat, OverweightIndex, ParaId>; + let overweight_messages = Overweight::::initialize_counter() as u64; + + T::DbWeight::get().reads_writes(overweight_messages, 1) + } + } + + /// [`UncheckedMigrationToV3`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 2. + pub type MigrationToV3 = frame_support::migrations::VersionedMigration< + 2, + 3, + UncheckedMigrationToV3, + Pallet, + ::DbWeight, + >; + + pub fn lazy_migrate_inbound_queue() { + let Some(mut states) = v3::InboundXcmpStatus::::get() else { + log::debug!(target: LOG, "Lazy migration finished: item gone"); + return + }; + let Some(ref mut next) = states.first_mut() else { + log::debug!(target: LOG, "Lazy migration finished: item empty"); + v3::InboundXcmpStatus::::kill(); + return + }; + log::debug!( + "Migrating inbound HRMP channel with sibling {:?}, msgs left {}.", + next.sender, + next.message_metadata.len() + ); + // We take the last element since the MQ is a FIFO and we want to keep the order. + let Some((block_number, format)) = next.message_metadata.pop() else { + states.remove(0); + v3::InboundXcmpStatus::::put(states); + return + }; + if format != XcmpMessageFormat::ConcatenatedVersionedXcm { + log::warn!(target: LOG, + "Dropping message with format {:?} (not ConcatenatedVersionedXcm)", + format + ); + v3::InboundXcmpMessages::::remove(&next.sender, &block_number); + v3::InboundXcmpStatus::::put(states); + return + } + + let Some(msg) = v3::InboundXcmpMessages::::take(&next.sender, &block_number) else { + defensive!("Storage corrupted: HRMP message missing:", (next.sender, block_number)); + v3::InboundXcmpStatus::::put(states); + return + }; + + let Ok(msg): Result, _> = msg.try_into() else { + log::error!(target: LOG, "Message dropped: too big"); + v3::InboundXcmpStatus::::put(states); + return + }; + + // Finally! We have a proper message. + T::XcmpQueue::enqueue_message(msg.as_bounded_slice(), next.sender); + log::debug!(target: LOG, "Migrated HRMP message to MQ: {:?}", (next.sender, block_number)); + v3::InboundXcmpStatus::::put(states); + } } -#[cfg(test)] +pub mod v4 { + use super::*; + + /// Migrates `QueueConfigData` to v4, removing deprecated fields and bumping page + /// thresholds to at least the default values. + pub struct UncheckedMigrationToV4(PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrationToV4 { + fn on_runtime_upgrade() -> Weight { + let translate = |pre: v2::QueueConfigData| -> QueueConfigData { + let pre_default = v2::QueueConfigData::default(); + // If the previous values are the default ones, let's replace them with the new + // default. + if pre.suspend_threshold == pre_default.suspend_threshold && + pre.drop_threshold == pre_default.drop_threshold && + pre.resume_threshold == pre_default.resume_threshold + { + return QueueConfigData::default() + } + + // If the previous values are not the default ones, let's leave them as they are. + QueueConfigData { + suspend_threshold: pre.suspend_threshold, + drop_threshold: pre.drop_threshold, + resume_threshold: pre.resume_threshold, + } + }; + + if QueueConfig::::translate(|pre| pre.map(translate)).is_err() { + log::error!( + target: crate::LOG_TARGET, + "unexpected error when performing translation of the QueueConfig type \ + during storage upgrade to v4" + ); + } + + T::DbWeight::get().reads_writes(1, 1) + } + } + + /// [`UncheckedMigrationToV4`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 3. + pub type MigrationToV4 = frame_support::migrations::VersionedMigration< + 3, + 4, + UncheckedMigrationToV4, + Pallet, + ::DbWeight, + >; +} + +#[cfg(all(feature = "try-runtime", test))] mod tests { use super::*; use crate::mock::{new_test_ext, Test}; #[test] + #[allow(deprecated)] fn test_migration_to_v2() { let v1 = v1::QueueConfigData { suspend_threshold: 5, @@ -130,14 +329,20 @@ mod tests { }; new_test_ext().execute_with(|| { + let storage_version = StorageVersion::new(1); + storage_version.put::>(); + frame_support::storage::unhashed::put_raw( &crate::QueueConfig::::hashed_key(), &v1.encode(), ); - migrate_to_v2::(); + let bytes = v2::MigrationToV2::::pre_upgrade(); + assert!(bytes.is_ok()); + v2::MigrationToV2::::on_runtime_upgrade(); + assert!(v2::MigrationToV2::::post_upgrade(bytes.unwrap()).is_ok()); - let v2 = crate::QueueConfig::::get(); + let v2 = v2::QueueConfig::::get(); assert_eq!(v1.suspend_threshold, v2.suspend_threshold); assert_eq!(v1.drop_threshold, v2.drop_threshold); @@ -147,4 +352,70 @@ mod tests { assert_eq!(v1.xcmp_max_individual_weight, v2.xcmp_max_individual_weight.ref_time()); }); } + + #[test] + #[allow(deprecated)] + fn test_migration_to_v4() { + new_test_ext().execute_with(|| { + let storage_version = StorageVersion::new(3); + storage_version.put::>(); + + let v2 = v2::QueueConfigData { + drop_threshold: 5, + suspend_threshold: 2, + resume_threshold: 1, + ..Default::default() + }; + + frame_support::storage::unhashed::put_raw( + &crate::QueueConfig::::hashed_key(), + &v2.encode(), + ); + + let bytes = v4::MigrationToV4::::pre_upgrade(); + assert!(bytes.is_ok()); + v4::MigrationToV4::::on_runtime_upgrade(); + assert!(v4::MigrationToV4::::post_upgrade(bytes.unwrap()).is_ok()); + + let v4 = QueueConfig::::get(); + + assert_eq!( + v4, + QueueConfigData { suspend_threshold: 32, drop_threshold: 48, resume_threshold: 8 } + ); + }); + + new_test_ext().execute_with(|| { + let storage_version = StorageVersion::new(3); + storage_version.put::>(); + + let v2 = v2::QueueConfigData { + drop_threshold: 100, + suspend_threshold: 50, + resume_threshold: 40, + ..Default::default() + }; + + frame_support::storage::unhashed::put_raw( + &crate::QueueConfig::::hashed_key(), + &v2.encode(), + ); + + let bytes = v4::MigrationToV4::::pre_upgrade(); + assert!(bytes.is_ok()); + v4::MigrationToV4::::on_runtime_upgrade(); + assert!(v4::MigrationToV4::::post_upgrade(bytes.unwrap()).is_ok()); + + let v4 = QueueConfig::::get(); + + assert_eq!( + v4, + QueueConfigData { + suspend_threshold: 50, + drop_threshold: 100, + resume_threshold: 40 + } + ); + }); + } } diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index bc0710e34653d64797f6e7b6aa7da286cc4f0f5b..9d9a723cf8b538b604c45ff5b2bd366cd6f92c2d 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -17,10 +17,11 @@ use super::*; use crate as xcmp_queue; use core::marker::PhantomData; use cumulus_pallet_parachain_system::AnyRelayNumber; -use cumulus_primitives_core::{IsSystem, ParaId}; +use cumulus_primitives_core::{ChannelInfo, IsSystem, ParaId}; use frame_support::{ - parameter_types, + derive_impl, parameter_types, traits::{ConstU32, Everything, Nothing, OriginTrait}, + BoundedSlice, }; use frame_system::EnsureRoot; use sp_core::H256; @@ -29,7 +30,10 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -use xcm_builder::{CurrencyAdapter, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset}; +use xcm_builder::{ + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, + ParentIsPreset, +}; use xcm_executor::traits::ConvertOrigin; type Block = frame_system::mocking::MockBlock; @@ -54,6 +58,7 @@ parameter_types! { type AccountId = u64; +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -98,17 +103,19 @@ impl pallet_balances::Config for Test { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } impl cumulus_pallet_parachain_system::Config for Test { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = (); type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; type ReservedDmpWeight = (); type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = (); @@ -117,20 +124,20 @@ impl cumulus_pallet_parachain_system::Config for Test { } parameter_types! { - pub const RelayChain: MultiLocation = MultiLocation::parent(); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(1u32)); + pub const RelayChain: Location = Location::parent(); + pub UniversalLocation: InteriorLocation = [Parachain(1u32)].into(); pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } /// Means for transacting assets on this chain. -pub type LocalAssetTransactor = CurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + // Do a simple punn to convert an AccountId32 Location into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -167,6 +174,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type XcmRouter = ( @@ -179,17 +190,14 @@ impl ConvertOrigin for SystemParachainAsSuperuser { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); if kind == OriginKind::Superuser && matches!( - origin, - MultiLocation { - parents: 1, - interior: X1(Parachain(id)), - } if ParaId::from(id).is_system(), + origin.unpack(), + (1, [Parachain(id)]) if ParaId::from(*id).is_system(), ) { Ok(RuntimeOrigin::root()) } else { @@ -198,9 +206,58 @@ impl ConvertOrigin } } +parameter_types! { + pub static EnqueuedMessages: Vec<(ParaId, Vec)> = Default::default(); +} + +/// An `EnqueueMessage` implementation that puts all messages in thread-local storage. +pub struct EnqueueToLocalStorage(PhantomData); + +impl> EnqueueMessage for EnqueueToLocalStorage { + type MaxMessageLen = sp_core::ConstU32<65_536>; + + fn enqueue_message(message: BoundedSlice, origin: ParaId) { + let mut msgs = EnqueuedMessages::get(); + msgs.push((origin, message.to_vec())); + EnqueuedMessages::set(msgs); + T::on_queue_changed(origin, Self::footprint(origin)); + } + + fn enqueue_messages<'a>( + iter: impl Iterator>, + origin: ParaId, + ) { + let mut msgs = EnqueuedMessages::get(); + msgs.extend(iter.map(|m| (origin, m.to_vec()))); + EnqueuedMessages::set(msgs); + T::on_queue_changed(origin, Self::footprint(origin)); + } + + fn sweep_queue(origin: ParaId) { + let mut msgs = EnqueuedMessages::get(); + msgs.retain(|(o, _)| o != &origin); + EnqueuedMessages::set(msgs); + T::on_queue_changed(origin, Self::footprint(origin)); + } + + fn footprint(origin: ParaId) -> QueueFootprint { + let msgs = EnqueuedMessages::get(); + let mut footprint = QueueFootprint::default(); + for (o, m) in msgs { + if o == origin { + footprint.storage.count += 1; + footprint.storage.size += m.len() as u64; + } + } + footprint.pages = footprint.storage.size as u32 / 16; // Number does not matter + footprint.ready_pages = footprint.pages; + footprint + } +} + parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(RelayChain::get()); + pub FeeAssetId: AssetId = AssetId(RelayChain::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: Balance = 300_000_000; /// The fee per byte @@ -216,10 +273,10 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: impl Config for Test { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = xcm_executor::XcmExecutor; - type ChannelInfo = ParachainSystem; + type ChannelInfo = MockedChannelInfo; type VersionWrapper = (); - type ExecuteOverweightOrigin = EnsureRoot; + type XcmpQueue = EnqueueToLocalStorage>; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = SystemParachainAsSuperuser; type WeightInfo = (); @@ -227,6 +284,60 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - t.into() + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} + +/// A para that we have an HRMP channel with. +pub const HRMP_PARA_ID: u32 = 7777; + +pub struct MockedChannelInfo; +impl GetChannelInfo for MockedChannelInfo { + fn get_channel_status(id: ParaId) -> ChannelStatus { + if id == HRMP_PARA_ID.into() { + return ChannelStatus::Ready(usize::MAX, usize::MAX) + } + + ParachainSystem::get_channel_status(id) + } + + fn get_channel_info(id: ParaId) -> Option { + if id == HRMP_PARA_ID.into() { + return Some(ChannelInfo { + max_capacity: u32::MAX, + max_total_size: u32::MAX, + max_message_size: u32::MAX, + msg_count: 0, + total_size: 0, + }) + } + + ParachainSystem::get_channel_info(id) + } +} + +pub(crate) fn mk_page() -> Vec { + let mut page = Vec::::new(); + + for i in 0..100 { + page.extend(match i % 2 { + 0 => v2_xcm().encode(), + 1 => v3_xcm().encode(), + // We cannot push an undecodable XCM here since it would break the decode stream. + // This is expected and the whole reason to introduce `MaybeDoubleEncodedVersionedXcm` + // instead. + _ => unreachable!(), + }); + } + + page +} + +pub(crate) fn v2_xcm() -> VersionedXcm<()> { + let instr = xcm::v2::Instruction::<()>::ClearOrigin; + VersionedXcm::V2(xcm::v2::Xcm::<()>(vec![instr; 3])) +} + +pub(crate) fn v3_xcm() -> VersionedXcm<()> { + let instr = xcm::v3::Instruction::<()>::Trap(1); + VersionedXcm::V3(xcm::v3::Xcm::<()>(vec![instr; 3])) } diff --git a/cumulus/pallets/xcmp-queue/src/tests.rs b/cumulus/pallets/xcmp-queue/src/tests.rs index cf6d947609d2f47b983e42f2207a93fd43f7ff76..0b41095828f2f93810a2855d175ecd60a12853d7 100644 --- a/cumulus/pallets/xcmp-queue/src/tests.rs +++ b/cumulus/pallets/xcmp-queue/src/tests.rs @@ -13,247 +13,318 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; +use super::{ + mock::{mk_page, v2_xcm, v3_xcm, EnqueuedMessages, HRMP_PARA_ID}, + *, +}; +use XcmpMessageFormat::*; + +use codec::Input; use cumulus_primitives_core::{ParaId, XcmpMessageHandler}; -use frame_support::{assert_noop, assert_ok}; -use mock::{new_test_ext, ParachainSystem, RuntimeCall, RuntimeOrigin, Test, XcmpQueue}; -use sp_runtime::traits::BadOrigin; +use frame_support::{ + assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically, traits::Hooks, + StorageNoopGuard, +}; +use mock::{new_test_ext, ParachainSystem, RuntimeOrigin as Origin, Test, XcmpQueue}; +use sp_runtime::traits::{BadOrigin, Zero}; +use std::iter::{once, repeat}; #[test] -fn one_message_does_not_panic() { +fn empty_concatenated_works() { new_test_ext().execute_with(|| { - let message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode(); - let messages = vec![(Default::default(), 1u32, message_format.as_slice())]; + let data = ConcatenatedVersionedXcm.encode(); - // This shouldn't cause a panic - XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX); + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); }) } #[test] -#[should_panic = "Invalid incoming blob message data"] -#[cfg(debug_assertions)] -fn bad_message_is_handled() { +fn xcm_enqueueing_basic_works() { new_test_ext().execute_with(|| { - let bad_data = vec![ - 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 64, 239, 139, 0, - 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 37, 0, - 0, 0, 0, 0, 0, 0, 16, 0, 127, 147, - ]; - InboundXcmpMessages::::insert(ParaId::from(1000), 1, bad_data); - let format = XcmpMessageFormat::ConcatenatedEncodedBlob; - // This should exit with an error. - XcmpQueue::process_xcmp_message( - 1000.into(), - (1, format), - &mut 0, - Weight::from_parts(10_000_000_000, 0), - Weight::from_parts(10_000_000_000, 0), - ); - }); + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])).encode(); + let data = [ConcatenatedVersionedXcm.encode(), xcm.clone()].concat(); + + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); + + assert_eq!(EnqueuedMessages::get(), vec![(1000.into(), xcm)]); + }) } -/// Tests that a blob message is handled. Currently this isn't implemented and panics when debug -/// assertions are enabled. When this feature is enabled, this test should be rewritten properly. #[test] -#[should_panic = "Blob messages not handled."] -#[cfg(debug_assertions)] -fn handle_blob_message() { +fn xcm_enqueueing_many_works() { new_test_ext().execute_with(|| { - let bad_data = vec![ - 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 64, 239, - 139, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, - 37, 0, 0, 0, 0, 0, 0, 0, 16, 0, 127, 147, - ]; - InboundXcmpMessages::::insert(ParaId::from(1000), 1, bad_data); - let format = XcmpMessageFormat::ConcatenatedEncodedBlob; - XcmpQueue::process_xcmp_message( - 1000.into(), - (1, format), - &mut 0, - Weight::from_parts(10_000_000_000, 0), - Weight::from_parts(10_000_000_000, 0), + let mut encoded_xcms = vec![]; + for i in 0..10 { + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin; i as usize])); + encoded_xcms.push(xcm.encode()); + } + let mut data = ConcatenatedVersionedXcm.encode(); + data.extend(encoded_xcms.iter().flatten()); + + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); + + assert_eq!( + EnqueuedMessages::get(), + encoded_xcms.into_iter().map(|xcm| (1000.into(), xcm)).collect::>(), ); - }); + }) } #[test] -#[should_panic = "Invalid incoming XCMP message data"] -#[cfg(debug_assertions)] -fn handle_invalid_data() { +fn xcm_enqueueing_multiple_times_works() { new_test_ext().execute_with(|| { - let data = Xcm::(vec![]).encode(); - InboundXcmpMessages::::insert(ParaId::from(1000), 1, data); - let format = XcmpMessageFormat::ConcatenatedVersionedXcm; - XcmpQueue::process_xcmp_message( - 1000.into(), - (1, format), - &mut 0, - Weight::from_parts(10_000_000_000, 0), - Weight::from_parts(10_000_000_000, 0), + let mut encoded_xcms = vec![]; + for _ in 0..10 { + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])); + encoded_xcms.push(xcm.encode()); + } + let mut data = ConcatenatedVersionedXcm.encode(); + data.extend(encoded_xcms.iter().flatten()); + + for i in 0..10 { + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); + assert_eq!((i + 1) * 10, EnqueuedMessages::get().len()); + } + + assert_eq!( + EnqueuedMessages::get(), + encoded_xcms + .into_iter() + .map(|xcm| (1000.into(), xcm)) + .cycle() + .take(100) + .collect::>(), ); - }); + }) } #[test] -fn service_overweight_unknown() { +#[cfg_attr(debug_assertions, should_panic = "Could not enqueue XCMP messages.")] +fn xcm_enqueueing_starts_dropping_on_overflow() { new_test_ext().execute_with(|| { - assert_noop!( - XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)), - Error::::BadOverweightIndex, + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])); + let data = (ConcatenatedVersionedXcm, xcm).encode(); + // Its possible to enqueue 256 messages at most: + let limit = 256; + + XcmpQueue::handle_xcmp_messages( + repeat((1000.into(), 1, data.as_slice())).take(limit * 2), + Weight::MAX, ); - }); + }) } +/// First enqueue 10 good, 1 bad and then 10 good XCMs. +/// +/// It should only process the first 10 good though. #[test] -fn service_overweight_bad_xcm_format() { +#[cfg(not(debug_assertions))] +fn xcm_enqueueing_broken_xcm_works() { new_test_ext().execute_with(|| { - let bad_xcm = vec![255]; - Overweight::::insert(0, (ParaId::from(1000), 0, bad_xcm)); + let mut encoded_xcms = vec![]; + for _ in 0..10 { + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])); + encoded_xcms.push(xcm.encode()); + } + let mut good = ConcatenatedVersionedXcm.encode(); + good.extend(encoded_xcms.iter().flatten()); - assert_noop!( - XcmpQueue::service_overweight(RuntimeOrigin::root(), 0, Weight::from_parts(1000, 1000)), - Error::::BadXcm + let mut bad = ConcatenatedVersionedXcm.encode(); + bad.extend(vec![0u8].into_iter()); + + // Of we enqueue them in multiple pages, then its fine. + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX); + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, bad.as_slice())), Weight::MAX); + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX); + assert_eq!(20, EnqueuedMessages::get().len()); + + assert_eq!( + EnqueuedMessages::get(), + encoded_xcms + .clone() + .into_iter() + .map(|xcm| (1000.into(), xcm)) + .cycle() + .take(20) + .collect::>(), ); - }); + EnqueuedMessages::take(); + + // But if we do it all in one page, then it only uses the first 10: + XcmpQueue::handle_xcmp_messages( + vec![(1000.into(), 1, good.as_slice()), (1000.into(), 1, bad.as_slice())].into_iter(), + Weight::MAX, + ); + assert_eq!(10, EnqueuedMessages::get().len()); + assert_eq!( + EnqueuedMessages::get(), + encoded_xcms + .into_iter() + .map(|xcm| (1000.into(), xcm)) + .cycle() + .take(10) + .collect::>(), + ); + }) } +/// Message blobs are not supported and panic in debug mode. #[test] -fn suspend_xcm_execution_works() { +#[should_panic = "Blob messages are unhandled"] +#[cfg(debug_assertions)] +fn bad_blob_message_panics() { new_test_ext().execute_with(|| { - QueueSuspended::::put(true); + let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat(); - let xcm = - VersionedXcm::from(Xcm::(vec![Instruction::::ClearOrigin])) - .encode(); - let mut message_format = XcmpMessageFormat::ConcatenatedVersionedXcm.encode(); - message_format.extend(xcm.clone()); - let messages = vec![(ParaId::from(999), 1u32, message_format.as_slice())]; - - // This should have executed the incoming XCM, because it came from a system parachain - XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX); - - let queued_xcm = InboundXcmpMessages::::get(ParaId::from(999), 1u32); - assert!(queued_xcm.is_empty()); - - let messages = vec![(ParaId::from(2000), 1u32, message_format.as_slice())]; + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); + }); +} - // This shouldn't have executed the incoming XCM - XcmpQueue::handle_xcmp_messages(messages.into_iter(), Weight::MAX); +/// Message blobs do not panic in release mode but are just a No-OP. +#[test] +#[cfg(not(debug_assertions))] +fn bad_blob_message_no_panic() { + new_test_ext().execute_with(|| { + let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat(); - let queued_xcm = InboundXcmpMessages::::get(ParaId::from(2000), 1u32); - assert_eq!(queued_xcm, xcm); + frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages( + once((1000.into(), 1, data.as_slice())), + Weight::MAX, + )); }); } +/// Invalid concatenated XCMs panic in debug mode. #[test] -fn update_suspend_threshold_works() { +#[should_panic = "HRMP inbound decode stream broke; page will be dropped."] +#[cfg(debug_assertions)] +fn handle_invalid_data_panics() { new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!(data.suspend_threshold, 2); - assert_ok!(XcmpQueue::update_suspend_threshold(RuntimeOrigin::root(), 3)); - assert_noop!(XcmpQueue::update_suspend_threshold(RuntimeOrigin::signed(2), 5), BadOrigin); - let data: QueueConfigData = >::get(); + let data = [ConcatenatedVersionedXcm.encode(), Xcm::(vec![]).encode()].concat(); - assert_eq!(data.suspend_threshold, 3); + XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX); }); } +/// Invalid concatenated XCMs do not panic in release mode but are just a No-OP. #[test] -fn update_drop_threshold_works() { +#[cfg(not(debug_assertions))] +fn handle_invalid_data_no_panic() { new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!(data.drop_threshold, 5); - assert_ok!(XcmpQueue::update_drop_threshold(RuntimeOrigin::root(), 6)); - assert_noop!(XcmpQueue::update_drop_threshold(RuntimeOrigin::signed(2), 7), BadOrigin); - let data: QueueConfigData = >::get(); + let data = [ConcatenatedVersionedXcm.encode(), Xcm::(vec![]).encode()].concat(); - assert_eq!(data.drop_threshold, 6); + frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages( + once((1000.into(), 1, data.as_slice())), + Weight::MAX, + )); }); } #[test] -fn update_resume_threshold_works() { +fn suspend_xcm_execution_works() { new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!(data.resume_threshold, 1); - assert_ok!(XcmpQueue::update_resume_threshold(RuntimeOrigin::root(), 2)); - assert_noop!(XcmpQueue::update_resume_threshold(RuntimeOrigin::signed(7), 3), BadOrigin); - let data: QueueConfigData = >::get(); - - assert_eq!(data.resume_threshold, 2); + assert!(!XcmpQueue::is_paused(&2000.into())); + QueueSuspended::::put(true); + assert!(XcmpQueue::is_paused(&2000.into())); + // System parachains can bypass suspension: + assert!(!XcmpQueue::is_paused(&999.into())); }); } #[test] -fn update_threshold_weight_works() { +fn suspend_and_resume_xcm_execution_work() { new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!(data.threshold_weight, Weight::from_parts(100_000, 0)); - assert_ok!(XcmpQueue::update_threshold_weight( - RuntimeOrigin::root(), - Weight::from_parts(10_000, 0) - )); + assert_noop!(XcmpQueue::suspend_xcm_execution(Origin::signed(1)), BadOrigin); + assert_ok!(XcmpQueue::suspend_xcm_execution(Origin::root())); assert_noop!( - XcmpQueue::update_threshold_weight( - RuntimeOrigin::signed(5), - Weight::from_parts(10_000_000, 0), - ), - BadOrigin + XcmpQueue::suspend_xcm_execution(Origin::root()), + Error::::AlreadySuspended ); - let data: QueueConfigData = >::get(); + assert!(QueueSuspended::::get()); - assert_eq!(data.threshold_weight, Weight::from_parts(10_000, 0)); + assert_noop!(XcmpQueue::resume_xcm_execution(Origin::signed(1)), BadOrigin); + assert_ok!(XcmpQueue::resume_xcm_execution(Origin::root())); + assert_noop!( + XcmpQueue::resume_xcm_execution(Origin::root()), + Error::::AlreadyResumed + ); + assert!(!QueueSuspended::::get()); }); } #[test] -fn update_weight_restrict_decay_works() { +#[cfg(not(debug_assertions))] +fn xcm_enqueueing_backpressure_works() { + let para: ParaId = 1000.into(); new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!(data.weight_restrict_decay, Weight::from_parts(2, 0)); - assert_ok!(XcmpQueue::update_weight_restrict_decay( - RuntimeOrigin::root(), - Weight::from_parts(5, 0) - )); - assert_noop!( - XcmpQueue::update_weight_restrict_decay( - RuntimeOrigin::signed(6), - Weight::from_parts(4, 0), - ), - BadOrigin - ); - let data: QueueConfigData = >::get(); + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])); + let data = (ConcatenatedVersionedXcm, xcm).encode(); + + XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(170), Weight::MAX); + + assert_eq!(EnqueuedMessages::get().len(), 170,); + // Not yet suspended: + assert!(InboundXcmpSuspended::::get().is_empty()); + // Enqueueing one more will suspend it: + let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])).encode(); + let small = [ConcatenatedVersionedXcm.encode(), xcm].concat(); + + XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX); + // Suspended: + assert_eq!(InboundXcmpSuspended::::get().iter().collect::>(), vec![¶]); + + // Now enqueueing many more will only work until the drop threshold: + XcmpQueue::handle_xcmp_messages(repeat((para, 1, data.as_slice())).take(100), Weight::MAX); + assert_eq!(mock::EnqueuedMessages::get().len(), 256); + + crate::mock::EnqueueToLocalStorage::>::sweep_queue(para); + XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX); + // Got resumed: + assert!(InboundXcmpSuspended::::get().is_empty()); + // Still resumed: + XcmpQueue::handle_xcmp_messages(once((para, 1, small.as_slice())), Weight::MAX); + assert!(InboundXcmpSuspended::::get().is_empty()); + }); +} + +#[test] +fn update_suspend_threshold_works() { + new_test_ext().execute_with(|| { + assert_eq!(>::get().suspend_threshold, 32); + assert_noop!(XcmpQueue::update_suspend_threshold(Origin::signed(2), 49), BadOrigin); - assert_eq!(data.weight_restrict_decay, Weight::from_parts(5, 0)); + assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 33)); + assert_eq!(>::get().suspend_threshold, 33); }); } #[test] -fn update_xcmp_max_individual_weight() { +fn update_drop_threshold_works() { new_test_ext().execute_with(|| { - let data: QueueConfigData = >::get(); - assert_eq!( - data.xcmp_max_individual_weight, - Weight::from_parts(20u64 * WEIGHT_REF_TIME_PER_MILLIS, DEFAULT_POV_SIZE), + assert_eq!(>::get().drop_threshold, 48); + assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 4000)); + assert_noop!(XcmpQueue::update_drop_threshold(Origin::signed(2), 7), BadOrigin); + + assert_eq!(>::get().drop_threshold, 4000); + }); +} + +#[test] +fn update_resume_threshold_works() { + new_test_ext().execute_with(|| { + assert_eq!(>::get().resume_threshold, 8); + assert_noop!( + XcmpQueue::update_resume_threshold(Origin::root(), 0), + Error::::BadQueueConfig ); - assert_ok!(XcmpQueue::update_xcmp_max_individual_weight( - RuntimeOrigin::root(), - Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0) - )); assert_noop!( - XcmpQueue::update_xcmp_max_individual_weight( - RuntimeOrigin::signed(3), - Weight::from_parts(10u64 * WEIGHT_REF_TIME_PER_MILLIS, 0) - ), - BadOrigin + XcmpQueue::update_resume_threshold(Origin::root(), 33), + Error::::BadQueueConfig ); - let data: QueueConfigData = >::get(); + assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 16)); + assert_noop!(XcmpQueue::update_resume_threshold(Origin::signed(7), 3), BadOrigin); - assert_eq!( - data.xcmp_max_individual_weight, - Weight::from_parts(30u64 * WEIGHT_REF_TIME_PER_MILLIS, 0) - ); + assert_eq!(>::get().resume_threshold, 16); }); } @@ -262,11 +333,11 @@ struct OkFixedXcmHashWithAssertingRequiredInputsSender; impl OkFixedXcmHashWithAssertingRequiredInputsSender { const FIXED_XCM_HASH: [u8; 32] = [9; 32]; - fn fixed_delivery_asset() -> MultiAssets { - MultiAssets::new() + fn fixed_delivery_asset() -> Assets { + Assets::new() } - fn expected_delivery_result() -> Result<(XcmHash, MultiAssets), SendError> { + fn expected_delivery_result() -> Result<(XcmHash, Assets), SendError> { Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset())) } } @@ -274,7 +345,7 @@ impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender { type Ticket = (); fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult { assert!(destination.is_some()); @@ -321,8 +392,8 @@ fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() { let message = Xcm(vec![Trap(5)]); // XcmpQueue - check dest/msg is valid - let dest = (Parent, X1(Parachain(5555))); - let mut dest_wrapper = Some(dest.into()); + let dest: Location = (Parent, Parachain(5555)).into(); + let mut dest_wrapper = Some(dest.clone()); let mut msg_wrapper = Some(message.clone()); new_test_ext().execute_with(|| { @@ -343,16 +414,297 @@ fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() { }); } +#[test] +fn xcmp_queue_validate_nested_xcm_works() { + let dest = (Parent, Parachain(5555)); + // Message that is not too deeply nested: + let mut good = Xcm(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + good = Xcm(vec![SetAppendix(good)]); + } + + new_test_ext().execute_with(|| { + // Check that the good message is validated: + assert_ok!(::validate( + &mut Some(dest.into()), + &mut Some(good.clone()) + )); + + // Nesting the message one more time should reject it: + let bad = Xcm(vec![SetAppendix(good)]); + assert_eq!( + Err(SendError::ExceedsMaxMessageSize), + ::validate(&mut Some(dest.into()), &mut Some(bad)) + ); + }); +} + +#[test] +fn send_xcm_nested_works() { + let dest = (Parent, Parachain(HRMP_PARA_ID)); + // Message that is not too deeply nested: + let mut good = Xcm(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + good = Xcm(vec![SetAppendix(good)]); + } + + // Check that the good message is sent: + new_test_ext().execute_with(|| { + assert_ok!(send_xcm::(dest.into(), good.clone())); + assert_eq!( + XcmpQueue::take_outbound_messages(usize::MAX), + vec![( + HRMP_PARA_ID.into(), + (XcmpMessageFormat::ConcatenatedVersionedXcm, VersionedXcm::V4(good.clone())) + .encode(), + )] + ); + }); + + // Nesting the message one more time should not send it: + let bad = Xcm(vec![SetAppendix(good)]); + new_test_ext().execute_with(|| { + assert_err!(send_xcm::(dest.into(), bad), SendError::ExceedsMaxMessageSize); + assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); + }); +} + +#[test] +fn hrmp_signals_are_prioritized() { + let message = Xcm(vec![Trap(5)]); + + let sibling_para_id = ParaId::from(12345); + let dest = (Parent, Parachain(sibling_para_id.into())); + let mut dest_wrapper = Some(dest.into()); + let mut msg_wrapper = Some(message.clone()); + + new_test_ext().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + ::validate(&mut dest_wrapper, &mut msg_wrapper).unwrap(); + + // check wrapper were consumed + assert_eq!(None, dest_wrapper.take()); + assert_eq!(None, msg_wrapper.take()); + + ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests( + sibling_para_id, + cumulus_primitives_core::AbridgedHrmpChannel { + max_capacity: 128, + max_total_size: 1 << 16, + max_message_size: 128, + msg_count: 0, + total_size: 0, + mqc_head: None, + }, + ); + + let taken = XcmpQueue::take_outbound_messages(130); + assert_eq!(taken, vec![]); + + // Enqueue some messages + let num_events = frame_system::Pallet::::events().len(); + for _ in 0..256 { + assert_ok!(send_xcm::(dest.into(), message.clone())); + } + assert_eq!(num_events + 256, frame_system::Pallet::::events().len()); + + // Without a signal we get the messages in order: + let mut expected_msg = XcmpMessageFormat::ConcatenatedVersionedXcm.encode(); + for _ in 0..31 { + expected_msg.extend(VersionedXcm::V4(message.clone()).encode()); + } + + hypothetically!({ + let taken = XcmpQueue::take_outbound_messages(usize::MAX); + assert_eq!(taken, vec![(sibling_para_id.into(), expected_msg,)]); + }); + + // But a signal gets prioritized instead of the messages: + XcmpQueue::send_signal(sibling_para_id.into(), ChannelSignal::Suspend); + + let taken = XcmpQueue::take_outbound_messages(130); + assert_eq!( + taken, + vec![( + sibling_para_id.into(), + (XcmpMessageFormat::Signals, ChannelSignal::Suspend).encode() + )] + ); + }); +} + +#[test] +fn maybe_double_encoded_versioned_xcm_works() { + // pre conditions + assert_eq!(VersionedXcm::<()>::V2(Default::default()).encode(), &[2, 0]); + assert_eq!(VersionedXcm::<()>::V3(Default::default()).encode(), &[3, 0]); +} + +// Now also testing a page instead of just concat messages. +#[test] +fn maybe_double_encoded_versioned_xcm_decode_page_works() { + let page = mk_page(); + + // Now try to decode the page. + let input = &mut &page[..]; + for i in 0..100 { + match (i % 2, VersionedXcm::<()>::decode(input)) { + (0, Ok(xcm)) => { + assert_eq!(xcm, v2_xcm()); + }, + (1, Ok(xcm)) => { + assert_eq!(xcm, v3_xcm()); + }, + unexpected => unreachable!("{:?}", unexpected), + } + } + + assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed"); +} + +/// Check that `take_first_concatenated_xcm` correctly splits a page into its XCMs. +#[test] +fn take_first_concatenated_xcm_works() { + let page = mk_page(); + let input = &mut &page[..]; + + for i in 0..100 { + let xcm = XcmpQueue::take_first_concatenated_xcm(input, &mut WeightMeter::new()).unwrap(); + match (i % 2, xcm) { + (0, data) | (2, data) => { + assert_eq!(data, v2_xcm().encode()); + }, + (1, data) | (3, data) => { + assert_eq!(data, v3_xcm().encode()); + }, + unexpected => unreachable!("{:?}", unexpected), + } + } + assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed"); +} + +/// A message that is not too deeply nested will be accepted by `take_first_concatenated_xcm`. +#[test] +fn take_first_concatenated_xcm_good_recursion_depth_works() { + let mut good = Xcm::<()>(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH - 1 { + good = Xcm(vec![SetAppendix(good)]); + } + let good = VersionedXcm::V4(good); + + let page = good.encode(); + assert_ok!(XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new())); +} + +/// A message that is too deeply nested will be rejected by `take_first_concatenated_xcm`. +#[test] +fn take_first_concatenated_xcm_good_bad_depth_errors() { + let mut bad = Xcm::<()>(vec![ClearOrigin]); + for _ in 0..MAX_XCM_DECODE_DEPTH { + bad = Xcm(vec![SetAppendix(bad)]); + } + let bad = VersionedXcm::V4(bad); + + let page = bad.encode(); + assert_err!( + XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()), + () + ); +} + +#[test] +fn lazy_migration_works() { + use crate::migration::v3::*; + + new_test_ext().execute_with(|| { + EnqueuedMessages::set(vec![]); + let _g = StorageNoopGuard::default(); // No storage is leaked. + + let mut channels = vec![]; + for i in 0..20 { + let para = ParaId::from(i); + let mut message_metadata = vec![]; + for block in 0..30 { + message_metadata.push((block, XcmpMessageFormat::ConcatenatedVersionedXcm)); + InboundXcmpMessages::::insert(para, block, vec![(i + block) as u8]); + } + + channels.push(InboundChannelDetails { + sender: para, + state: InboundState::Ok, + message_metadata, + }); + } + InboundXcmpStatus::::set(Some(channels)); + + for para in 0..20u32 { + assert_eq!(InboundXcmpStatus::::get().unwrap().len() as u32, 20 - para); + assert_eq!(InboundXcmpMessages::::iter_prefix(ParaId::from(para)).count(), 30); + + for block in 0..30 { + XcmpQueue::on_idle(0u32.into(), Weight::MAX); + assert_eq!( + EnqueuedMessages::get(), + vec![(para.into(), vec![(29 - block + para) as u8])] + ); + EnqueuedMessages::set(vec![]); + } + // One more to jump to the next channel: + XcmpQueue::on_idle(0u32.into(), Weight::MAX); + + assert_eq!(InboundXcmpStatus::::get().unwrap().len() as u32, 20 - para - 1); + assert_eq!(InboundXcmpMessages::::iter_prefix(ParaId::from(para)).count(), 0); + } + // One more for the cleanup: + XcmpQueue::on_idle(0u32.into(), Weight::MAX); + + assert!(!InboundXcmpStatus::::exists()); + assert_eq!(InboundXcmpMessages::::iter().count(), 0); + EnqueuedMessages::set(vec![]); + }); +} + +#[test] +fn lazy_migration_noop_when_out_of_weight() { + use crate::migration::v3::*; + assert!(!XcmpQueue::on_idle_weight().is_zero(), "pre condition"); + + new_test_ext().execute_with(|| { + let _g = StorageNoopGuard::default(); // No storage is leaked. + let block = 5; + let para = ParaId::from(4); + let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)]; + + InboundXcmpMessages::::insert(para, block, vec![123u8]); + InboundXcmpStatus::::set(Some(vec![InboundChannelDetails { + sender: para, + state: InboundState::Ok, + message_metadata, + }])); + + // Hypothetically, it would do something with enough weight limit: + hypothetically!({ + XcmpQueue::on_idle(0u32.into(), Weight::MAX); + assert_eq!(EnqueuedMessages::get(), vec![(para, vec![123u8])]); + }); + // But does not, since the limit is zero: + assert_storage_noop!({ XcmpQueue::on_idle(0u32.into(), Weight::zero()) }); + + InboundXcmpMessages::::remove(para, block); + InboundXcmpStatus::::kill(); + }); +} + #[test] fn xcmp_queue_send_xcm_works() { new_test_ext().execute_with(|| { let sibling_para_id = ParaId::from(12345); - let dest = (Parent, X1(Parachain(sibling_para_id.into()))).into(); + let dest: Location = (Parent, Parachain(sibling_para_id.into())).into(); let msg = Xcm(vec![ClearOrigin]); // try to send without opened HRMP channel to the sibling_para_id assert_eq!( - send_xcm::(dest, msg.clone()), + send_xcm::(dest.clone(), msg.clone()), Err(SendError::Transport("NoChannel")), ); @@ -372,13 +724,57 @@ fn xcmp_queue_send_xcm_works() { }) } +#[test] +fn xcmp_queue_send_too_big_xcm_fails() { + new_test_ext().execute_with(|| { + let sibling_para_id = ParaId::from(12345); + let dest = (Parent, Parachain(sibling_para_id.into())).into(); + + let max_message_size = 100_u32; + + // open HRMP channel to the sibling_para_id with a set `max_message_size` + ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests( + sibling_para_id, + cumulus_primitives_core::AbridgedHrmpChannel { + max_message_size, + max_capacity: 10, + max_total_size: 10_000_000_u32, + msg_count: 0, + total_size: 0, + mqc_head: None, + }, + ); + + // Message is crafted to exceed `max_message_size` + let mut message = Xcm::builder_unsafe(); + for _ in 0..97 { + message = message.clear_origin(); + } + let message = message.build(); + let encoded_message_size = message.encode().len(); + let versioned_size = 1; // VersionedXcm enum is added by `send_xcm` and it add one additional byte + assert_eq!(encoded_message_size, max_message_size as usize - versioned_size); + + // check empty outbound queue + assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); + + // Message is too big because after adding the VersionedXcm enum, it would reach + // `max_message_size` Then, adding the format, which is the worst case scenario in which a + // new page is needed, would get it over the limit + assert_eq!(send_xcm::(dest, message), Err(SendError::Transport("TooBig")),); + + // outbound queue is still empty + assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); + }); +} + #[test] fn verify_fee_factor_increase_and_decrease() { use cumulus_primitives_core::AbridgedHrmpChannel; use sp_runtime::FixedU128; let sibling_para_id = ParaId::from(12345); - let destination = (Parent, Parachain(sibling_para_id.into())).into(); + let destination: Location = (Parent, Parachain(sibling_para_id.into())).into(); let xcm = Xcm(vec![ClearOrigin; 100]); let versioned_xcm = VersionedXcm::from(xcm.clone()); let mut xcmp_message = XcmpMessageFormat::ConcatenatedVersionedXcm.encode(); @@ -403,38 +799,44 @@ fn verify_fee_factor_increase_and_decrease() { // Fee factor is only increased in `send_fragment`, which is called by `send_xcm`. // When queue is not congested, fee factor doesn't change. - assert_ok!(send_xcm::(destination, xcm.clone())); // Size 104 - assert_ok!(send_xcm::(destination, xcm.clone())); // Size 208 - assert_ok!(send_xcm::(destination, xcm.clone())); // Size 312 - assert_ok!(send_xcm::(destination, xcm.clone())); // Size 416 + assert_ok!(send_xcm::(destination.clone(), xcm.clone())); // Size 104 + assert_ok!(send_xcm::(destination.clone(), xcm.clone())); // Size 208 + assert_ok!(send_xcm::(destination.clone(), xcm.clone())); // Size 312 + assert_ok!(send_xcm::(destination.clone(), xcm.clone())); // Size 416 assert_eq!(DeliveryFeeFactor::::get(sibling_para_id), initial); // Sending the message right now is cheap - let (_, delivery_fees) = validate_send::(destination, xcm.clone()) + let (_, delivery_fees) = validate_send::(destination.clone(), xcm.clone()) .expect("message can be sent; qed"); - let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else { unreachable!("asset is fungible; qed"); }; + let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else { + unreachable!("asset is fungible; qed"); + }; assert_eq!(delivery_fee_amount, 402_000_000); let smaller_xcm = Xcm(vec![ClearOrigin; 30]); // When we get to half of `max_total_size`, because `THRESHOLD_FACTOR` is 2, // then the fee factor starts to increase. - assert_ok!(send_xcm::(destination, xcm.clone())); // Size 520 + assert_ok!(send_xcm::(destination.clone(), xcm.clone())); // Size 520 assert_eq!(DeliveryFeeFactor::::get(sibling_para_id), FixedU128::from_float(1.05)); - for _ in 0..12 { // We finish at size 929 - assert_ok!(send_xcm::(destination, smaller_xcm.clone())); + for _ in 0..12 { + // We finish at size 929 + assert_ok!(send_xcm::(destination.clone(), smaller_xcm.clone())); } assert!(DeliveryFeeFactor::::get(sibling_para_id) > FixedU128::from_float(1.88)); // Sending the message right now is expensive - let (_, delivery_fees) = validate_send::(destination, xcm.clone()) + let (_, delivery_fees) = validate_send::(destination.clone(), xcm.clone()) .expect("message can be sent; qed"); - let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else { unreachable!("asset is fungible; qed"); }; + let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else { + unreachable!("asset is fungible; qed"); + }; assert_eq!(delivery_fee_amount, 758_030_955); // Fee factor only decreases in `take_outbound_messages` - for _ in 0..5 { // We take 5 100 byte pages + for _ in 0..5 { + // We take 5 100 byte pages XcmpQueue::take_outbound_messages(1); } assert!(DeliveryFeeFactor::::get(sibling_para_id) < FixedU128::from_float(1.72)); diff --git a/cumulus/pallets/xcmp-queue/src/weights.rs b/cumulus/pallets/xcmp-queue/src/weights.rs index 2d20bba6f8f6b18571ea4d241d57f08998d13d87..27daa791e19a334fe1a6189feb52a9ef6f206312 100644 --- a/cumulus/pallets/xcmp-queue/src/weights.rs +++ b/cumulus/pallets/xcmp-queue/src/weights.rs @@ -13,51 +13,239 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Autogenerated weights for `cumulus_pallet_xcmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-kusama-dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// asset-hub-kusama-dev +// --output +// cumulus/pallets/xcmp-queue/src/weights.rs +// --template +// substrate/.maintain/frame-weight-template.hbs +// --extrinsic +// + +#![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] -use frame_support::{ - traits::Get, - weights::{constants::RocksDbWeight, Weight}, -}; -use sp_std::marker::PhantomData; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; -// Implemented by autogenerated benchmarking code. +/// Weight functions needed for `cumulus_pallet_xcmp_queue`. pub trait WeightInfo { fn set_config_with_u32() -> Weight; - fn set_config_with_weight() -> Weight; + fn enqueue_xcmp_message() -> Weight; + fn suspend_channel() -> Weight; + fn resume_channel() -> Weight; + fn take_first_concatenated_xcm() -> Weight; + fn on_idle_good_msg() -> Weight; + fn on_idle_large_msg() -> Weight; } +/// Weights for `cumulus_pallet_xcmp_queue` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); - impl WeightInfo for SubstrateWeight { - // Storage: XcmpQueue QueueConfig (r:1 w:1) + /// 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 { - Weight::from_parts(2_717_000_u64, 0) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_000_000, 1561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - - // Storage: XcmpQueue QueueConfig (r:1 w:1) - fn set_config_with_weight() -> Weight { - Weight::from_parts(2_717_000_u64, 0) + /// 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: `118` + // Estimated: `3517` + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(16_000_000, 3517) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// 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(4_000_000, 1561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// 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, 1596) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + 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) + } + /// 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: `65747` + // Estimated: `69212` + // Minimum execution time: 63_000_000 picoseconds. + Weight::from_parts(66_000_000, 69212) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// 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: 42_000_000 picoseconds. + Weight::from_parts(52_000_000, 69175) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } } +// For backwards compatibility and tests. impl WeightInfo for () { - // Storage: XcmpQueue QueueConfig (r:1 w:1) + /// 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 { - Weight::from_parts(2_717_000_u64, 0) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_000_000, 1561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - - // Storage: XcmpQueue QueueConfig (r:1 w:1) - fn set_config_with_weight() -> Weight { - Weight::from_parts(2_717_000_u64, 0) + /// 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: `118` + // Estimated: `3517` + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(16_000_000, 3517) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// 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(4_000_000, 1561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// 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, 1596) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + 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) + } + /// 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: `65747` + // Estimated: `69212` + // Minimum execution time: 63_000_000 picoseconds. + Weight::from_parts(66_000_000, 69212) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// 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: 42_000_000 picoseconds. + Weight::from_parts(52_000_000, 69175) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } } diff --git a/cumulus/parachain-template/node/src/chain_spec.rs b/cumulus/parachain-template/node/src/chain_spec.rs deleted file mode 100644 index 0ca3c51900f20673b8b44452da3fdce9a117d932..0000000000000000000000000000000000000000 --- a/cumulus/parachain-template/node/src/chain_spec.rs +++ /dev/null @@ -1,231 +0,0 @@ -use cumulus_primitives_core::ParaId; -use parachain_template_runtime::{AccountId, AuraId, Signature, EXISTENTIAL_DEPOSIT}; -use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; -use sc_service::ChainType; -use serde::{Deserialize, Serialize}; -use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; - -/// Specialized `ChainSpec` for the normal parachain runtime. -pub type ChainSpec = - sc_service::GenericChainSpec; - -/// The default XCM version to set in genesis config. -const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; - -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// The extensions for the [`ChainSpec`]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] -#[serde(deny_unknown_fields)] -pub struct Extensions { - /// The relay chain of the Parachain. - pub relay_chain: String, - /// The id of the Parachain. - pub para_id: u32, -} - -impl Extensions { - /// Try to get the extension from the given `ChainSpec`. - pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> { - sc_chain_spec::get_extension(chain_spec.extensions()) - } -} - -type AccountPublic = ::Signer; - -/// Generate collator keys from seed. -/// -/// This function's return type must always match the session keys of the chain in tuple format. -pub fn get_collator_keys_from_seed(seed: &str) -> AuraId { - get_from_seed::(seed) -} - -/// Helper function to generate an account ID from seed -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn template_session_keys(keys: AuraId) -> parachain_template_runtime::SessionKeys { - parachain_template_runtime::SessionKeys { aura: keys } -} - -pub fn development_config() -> ChainSpec { - // Give your base currency a unit name and decimal places - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "UNIT".into()); - properties.insert("tokenDecimals".into(), 12.into()); - properties.insert("ss58Format".into(), 42.into()); - - ChainSpec::from_genesis( - // Name - "Development", - // ID - "dev", - ChainType::Development, - move || { - testnet_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"), - ], - get_account_id_from_seed::("Alice"), - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - None, - Extensions { - relay_chain: "rococo-local".into(), // You MUST set this to the correct network! - para_id: 1000, - }, - ) -} - -pub fn local_testnet_config() -> ChainSpec { - // Give your base currency a unit name and decimal places - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "UNIT".into()); - properties.insert("tokenDecimals".into(), 12.into()); - properties.insert("ss58Format".into(), 42.into()); - - ChainSpec::from_genesis( - // Name - "Local Testnet", - // ID - "local_testnet", - ChainType::Local, - move || { - testnet_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"), - ], - get_account_id_from_seed::("Alice"), - 1000.into(), - ) - }, - // Bootnodes - Vec::new(), - // Telemetry - None, - // Protocol ID - Some("template-local"), - // Fork ID - None, - // Properties - Some(properties), - // Extensions - Extensions { - relay_chain: "rococo-local".into(), // You MUST set this to the correct network! - para_id: 1000, - }, - ) -} - -fn testnet_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - root: AccountId, - id: ParaId, -) -> parachain_template_runtime::RuntimeGenesisConfig { - parachain_template_runtime::RuntimeGenesisConfig { - system: parachain_template_runtime::SystemConfig { - code: parachain_template_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: parachain_template_runtime::BalancesConfig { - balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), - }, - parachain_info: parachain_template_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() - }, - collator_selection: parachain_template_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: EXISTENTIAL_DEPOSIT * 16, - ..Default::default() - }, - session: parachain_template_runtime::SessionConfig { - keys: invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - template_session_keys(aura), // session keys - ) - }) - .collect(), - }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: parachain_template_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - transaction_payment: Default::default(), - sudo: parachain_template_runtime::SudoConfig { key: Some(root) }, - } -} diff --git a/cumulus/parachain-template/pallets/template/README.md b/cumulus/parachain-template/pallets/template/README.md deleted file mode 100644 index 5a6461233465c327d233fc5d97bdb3a6a85f8fd9..0000000000000000000000000000000000000000 --- a/cumulus/parachain-template/pallets/template/README.md +++ /dev/null @@ -1 +0,0 @@ -License: Unlicense diff --git a/cumulus/parachain-template/pallets/template/src/benchmarking.rs b/cumulus/parachain-template/pallets/template/src/benchmarking.rs deleted file mode 100644 index 8bba2a09867dea1d55e487661c01fd72acdf4dc9..0000000000000000000000000000000000000000 --- a/cumulus/parachain-template/pallets/template/src/benchmarking.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Benchmarking setup for pallet-parachain-template - -use super::*; - -#[allow(unused)] -use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_system::RawOrigin; - -benchmarks! { - do_something { - let s in 0 .. 100; - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), s) - verify { - assert_eq!(Something::::get(), Some(s)); - } -} - -impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/cumulus/parachains/chain-specs/asset-hub-kusama.json b/cumulus/parachains/chain-specs/asset-hub-kusama.json index e508587956237249c6e672cd256ae9b7ac5a8208..654275eade81e5379004e649e11eb7e4acb2ec1f 100644 --- a/cumulus/parachains/chain-specs/asset-hub-kusama.json +++ b/cumulus/parachains/chain-specs/asset-hub-kusama.json @@ -3,16 +3,10 @@ "id": "asset-hub-kusama", "chainType": "Live", "bootNodes": [ - "/ip4/34.77.217.152/tcp/30334/p2p/12D3KooWF63ZxKtZMYs5247WQA8fcTiGJb2osXykc31cmjwNLwem", - "/ip4/34.77.119.77/tcp/30334/p2p/12D3KooWGowDwrXAh9cxkbPHPHuwMouFHrMcJhCVXcFS2B8vc5Ry", "/dns/kusama-asset-hub-connect-0.polkadot.io/tcp/30334/p2p/12D3KooWMzvdGcUXxacLdMQzRVrsP1mJrZHcrz8LtGbhLzve84Qx", "/dns/kusama-asset-hub-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWMzvdGcUXxacLdMQzRVrsP1mJrZHcrz8LtGbhLzve84Qx", "/dns/kusama-asset-hub-connect-1.polkadot.io/tcp/30334/p2p/12D3KooWQmGf5z3DU1kKcZoLzMNgdbP31ybjuwxS1VGLKMUjq5ez", "/dns/kusama-asset-hub-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWQmGf5z3DU1kKcZoLzMNgdbP31ybjuwxS1VGLKMUjq5ez", - "/dns/kusama-asset-hub-connect-2.polkadot.io/tcp/30334/p2p/12D3KooWLm6iHcmA3YD4xn2zfbm4KLF5KSUqJJAnmt2UGr9o2PgB", - "/dns/kusama-asset-hub-connect-2.polkadot.io/tcp/443/wss/p2p/12D3KooWLm6iHcmA3YD4xn2zfbm4KLF5KSUqJJAnmt2UGr9o2PgB", - "/dns/kusama-asset-hub-connect-3.polkadot.io/tcp/30334/p2p/12D3KooWD8Bma5qPbq7N5qdED3Xy6GXHfvfk86TL8aVTQKxmWkHG", - "/dns/kusama-asset-hub-connect-3.polkadot.io/tcp/443/wss/p2p/12D3KooWD8Bma5qPbq7N5qdED3Xy6GXHfvfk86TL8aVTQKxmWkHG", "/dns/boot.stake.plus/tcp/34333/p2p/12D3KooWAzSSZ7jLqMw1WPomYEKCYANQaKemXQ8BKoFvNEvfmdqR", "/dns/boot.stake.plus/tcp/34334/wss/p2p/12D3KooWAzSSZ7jLqMw1WPomYEKCYANQaKemXQ8BKoFvNEvfmdqR", "/dns/boot.metaspan.io/tcp/26052/p2p/12D3KooW9z9hKqe3mqYAp5UJMhZiCqhkTHyiR43fegnGmTJ3JAba", @@ -26,7 +20,14 @@ "/dns/statemine.bootnode.amforc.com/tcp/30336/p2p/12D3KooWHmSyrBWsc6fdpq8HtCFWasmLVLYGKWA2a78m4xAHKyBq", "/dns/statemine.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWHmSyrBWsc6fdpq8HtCFWasmLVLYGKWA2a78m4xAHKyBq", "/dns/statemine-boot-ng.dwellir.com/tcp/30343/p2p/12D3KooWQNJKBaNfW6Nn7HZDi5pSSEFmHL2Qz7chr9RksQUDR1Wk", - "/dns/statemine-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWQNJKBaNfW6Nn7HZDi5pSSEFmHL2Qz7chr9RksQUDR1Wk" + "/dns/statemine-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWQNJKBaNfW6Nn7HZDi5pSSEFmHL2Qz7chr9RksQUDR1Wk", + "/dns/statemine-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWCKUrE5uaXQ288ko3Ex3zCyozyJLG47KEYTopinnXNtYL", + "/dns/statemine-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWCKUrE5uaXQ288ko3Ex3zCyozyJLG47KEYTopinnXNtYL", + "/dns/mine14.rotko.net/tcp/33524/p2p/12D3KooWJUFnjR2PNbsJhudwPVaWCoZy1acPGKjM2cSuGj345BBu", + "/dns/mine14.rotko.net/tcp/34524/ws/p2p/12D3KooWJUFnjR2PNbsJhudwPVaWCoZy1acPGKjM2cSuGj345BBu", + "/dns/mine14.rotko.net/tcp/35524/wss/p2p/12D3KooWJUFnjR2PNbsJhudwPVaWCoZy1acPGKjM2cSuGj345BBu", + "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30511/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU", + "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30513/wss/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU" ], "telemetryEndpoints": null, "protocolId": null, @@ -57,4 +58,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/asset-hub-polkadot.json b/cumulus/parachains/chain-specs/asset-hub-polkadot.json index 46f3e0e4a957c7020d7f1c2bb04fb559d06c4605..454060d2a87afb84407323263eea97d2f12d5a68 100644 --- a/cumulus/parachains/chain-specs/asset-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/asset-hub-polkadot.json @@ -3,16 +3,10 @@ "id": "asset-hub-polkadot", "chainType": "Live", "bootNodes": [ - "/ip4/34.65.251.121/tcp/30334/p2p/12D3KooWG3GrM6XKMM4gp3cvemdwUvu96ziYoJmqmetLZBXE8bSa", - "/ip4/34.65.35.228/tcp/30334/p2p/12D3KooWMRyTLrCEPcAQD6c4EnudL3vVzg9zji3whvsMYPUYevpq", "/dns/polkadot-asset-hub-connect-0.polkadot.io/tcp/30334/p2p/12D3KooWLHqbcQtoBygf7GJgVjVa3TaeLuf7VbicNdooaCmQM2JZ", "/dns/polkadot-asset-hub-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWLHqbcQtoBygf7GJgVjVa3TaeLuf7VbicNdooaCmQM2JZ", "/dns/polkadot-asset-hub-connect-1.polkadot.io/tcp/30334/p2p/12D3KooWNDrKSayoZXGGE2dRSFW2g1iGPq3fTZE2U39ma9yZGKd3", "/dns/polkadot-asset-hub-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWNDrKSayoZXGGE2dRSFW2g1iGPq3fTZE2U39ma9yZGKd3", - "/dns/polkadot-asset-hub-connect-2.polkadot.io/tcp/30334/p2p/12D3KooWApa2JW4rbLtgzuK7fjLMupLS9HZheX9cdkQKyu6AnGrP", - "/dns/polkadot-asset-hub-connect-2.polkadot.io/tcp/443/wss/p2p/12D3KooWApa2JW4rbLtgzuK7fjLMupLS9HZheX9cdkQKyu6AnGrP", - "/dns/polkadot-asset-hub-connect-3.polkadot.io/tcp/30334/p2p/12D3KooWRsVeHqRs2iKmjLiguxp8myL4G2mDAWhtX2jHwyWujseV", - "/dns/polkadot-asset-hub-connect-3.polkadot.io/tcp/443/wss/p2p/12D3KooWRsVeHqRs2iKmjLiguxp8myL4G2mDAWhtX2jHwyWujseV", "/dns/boot.stake.plus/tcp/35333/p2p/12D3KooWFrQjYaPZSSLLxEVmoaHFcrF6VoY4awG4KRSLaqy3JCdQ", "/dns/boot.stake.plus/tcp/35334/wss/p2p/12D3KooWFrQjYaPZSSLLxEVmoaHFcrF6VoY4awG4KRSLaqy3JCdQ", "/dns/boot.metaspan.io/tcp/16052/p2p/12D3KooWLwiJuvqQUB4kYaSjLenFKH9dWZhGZ4qi7pSb3sUYU651", @@ -26,7 +20,14 @@ "/dns/statemint.bootnode.amforc.com/tcp/30341/p2p/12D3KooWByohP9FXn7ao8syS167qJsbFdpa7fY2Y24xbKtt3r7Ls", "/dns/statemint.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWByohP9FXn7ao8syS167qJsbFdpa7fY2Y24xbKtt3r7Ls", "/dns/statemint-boot-ng.dwellir.com/tcp/30344/p2p/12D3KooWEFrNuNk8fPdQS2hf34Gmqi6dGSvrETshGJUrqrvfRDZr", - "/dns/statemint-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWEFrNuNk8fPdQS2hf34Gmqi6dGSvrETshGJUrqrvfRDZr" + "/dns/statemint-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWEFrNuNk8fPdQS2hf34Gmqi6dGSvrETshGJUrqrvfRDZr", + "/dns/statemint-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWLKxHom7f3XawRJqrF8RwiKK5Sj3qZqz5c7hF6eJeXhTx", + "/dns/statemint-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWLKxHom7f3XawRJqrF8RwiKK5Sj3qZqz5c7hF6eJeXhTx", + "/dns/mint14.rotko.net/tcp/33514/p2p/12D3KooWKkzLjYF6M5eEs7nYiqEtRqY8SGVouoCwo3nCWsRnThDW", + "/dns/mint14.rotko.net/tcp/34514/ws/p2p/12D3KooWKkzLjYF6M5eEs7nYiqEtRqY8SGVouoCwo3nCWsRnThDW", + "/dns/mint14.rotko.net/tcp/35514/wss/p2p/12D3KooWKkzLjYF6M5eEs7nYiqEtRqY8SGVouoCwo3nCWsRnThDW", + "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30508/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R", + "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30510/wss/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R" ], "telemetryEndpoints": null, "protocolId": null, @@ -59,4 +60,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/asset-hub-rococo.json b/cumulus/parachains/chain-specs/asset-hub-rococo.json index 064a2dfc0db8446340d05722b4df93cb44a18e7e..900d9f0ffb2c35645d59e16649ee49b7713a01f1 100644 --- a/cumulus/parachains/chain-specs/asset-hub-rococo.json +++ b/cumulus/parachains/chain-specs/asset-hub-rococo.json @@ -4,9 +4,7 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-asset-hub-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWRrZMndHAopzao34uGsN7srjS3gh9nAjTGKLSyJeU31Lg", - "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS", - "/dns/rococo-asset-hub-bootnode-2.polkadot.io/tcp/30333/p2p/12D3KooWA3cVSDJFrN5HEYbt11cK2W7zJbiPHxR2joJXcgqzVt8K", - "/dns/rococo-asset-hub-bootnode-3.polkadot.io/tcp/30333/p2p/12D3KooWPf3MtBZKJ3G6wYyvCTxFCi9vgzxDdHbjJJRCrFu3FgJb" + "/dns/rococo-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWAewimoNJqMaiiV5pYiowA5hLuh5JS5QiRJCCyWVrrSTS" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-westend.json b/cumulus/parachains/chain-specs/asset-hub-westend.json index d168803e1c3fdec079ad58399a41c2a1aaa32947..670935c9d2474242307d691d8707030c70f49982 100644 --- a/cumulus/parachains/chain-specs/asset-hub-westend.json +++ b/cumulus/parachains/chain-specs/asset-hub-westend.json @@ -5,8 +5,6 @@ "bootNodes": [ "/dns/westend-asset-hub-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWJaAfPyiye7ZQBuHengTJJoMrcaz7Jj1UzHiKdNxA1Nkd", "/dns/westend-asset-hub-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWGL3hpWycWyeqyL9gHNnmmsL474WkPZdqraBHu4L6fQrW", - "/dns/westend-asset-hub-bootnode-2.polkadot.io/tcp/30333/p2p/12D3KooWBkKDWhHzu6Hhe492adEpVV7wzuaWGxUfEnr6g5JCr7Gr", - "/dns/westend-asset-hub-bootnode-3.polkadot.io/tcp/30333/p2p/12D3KooWMGpzCmhD6np6eKqxL7AAunKn1dN86Dr7a9E2xgZ2rt6G", "/dns/boot.stake.plus/tcp/33333/p2p/12D3KooWNiB27rpXX7EYongoWWUeRKzLQxWGms6MQU2B9LX7Ztzo", "/dns/boot.stake.plus/tcp/33334/wss/p2p/12D3KooWNiB27rpXX7EYongoWWUeRKzLQxWGms6MQU2B9LX7Ztzo", "/dns/boot.metaspan.io/tcp/36052/p2p/12D3KooWBCqfNb6Y39DXTr4UBWXyjuS3hcZM1qTbHhDXxF6HkAJJ", @@ -20,7 +18,14 @@ "/dns/westmint.bootnode.amforc.com/tcp/30339/p2p/12D3KooWNjKeaANaeZxBAPctmx8jugSYzuw4vnSCJmEDPB5mtRd6", "/dns/westmint.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWNjKeaANaeZxBAPctmx8jugSYzuw4vnSCJmEDPB5mtRd6", "/dns/westmint-boot-ng.dwellir.com/tcp/30345/p2p/12D3KooWFZ9xqApB1wnFYkbe1qJ5Jqwxe2f3i8W25F3tKNXy59ux", - "/dns/westmint-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWFZ9xqApB1wnFYkbe1qJ5Jqwxe2f3i8W25F3tKNXy59ux" + "/dns/westmint-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWFZ9xqApB1wnFYkbe1qJ5Jqwxe2f3i8W25F3tKNXy59ux", + "/dns/westmint-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWDoq4PVdWm5nzRSvEz3DSSKjVgRhWVUaKyi5JMKwJKYbk", + "/dns/westmint-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWDoq4PVdWm5nzRSvEz3DSSKjVgRhWVUaKyi5JMKwJKYbk", + "/dns/wmint14.rotko.net/tcp/33534/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", + "/dns/wmint14.rotko.net/tcp/34534/ws/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", + "/dns/wmint14.rotko.net/tcp/35534/wss/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", + "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30514/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk", + "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30516/wss/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk" ], "telemetryEndpoints": null, "protocolId": null, @@ -50,4 +55,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/asset-hub-wococo.json b/cumulus/parachains/chain-specs/asset-hub-wococo.json deleted file mode 100644 index 6afb4f1743debef4b9ac05a4fae0626bad5057b0..0000000000000000000000000000000000000000 --- a/cumulus/parachains/chain-specs/asset-hub-wococo.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "Wococo Asset Hub", - "id": "asset-hub-wococo", - "chainType": "Live", - "bootNodes": [ - "/dns/wococo-wockmint-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJHWGqJW76brGnS4VXW9AFREKCW8L1mYmtTgChU1xrTCL", - "/dns/wococo-wockmint-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMFRVSLCGDgV9NR7whE1nYyRZvQPzPtwPEkatPi7N9Bpg", - "/dns/wococo-wockmint-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQXxjpGQn8MoYeGe5kfg7WvUcGtKXUihCfXaWooZc4TD5", - "/dns/wococo-wockmint-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMzMAbKH6o6yQpGCpNFqQRJCivCUDszxN31KCF1QCGj8w" - ], - "telemetryEndpoints": null, - "protocolId": null, - "properties": { - "tokenDecimals": 12, - "tokenSymbol": "WND" - }, - "relay_chain": "wococo", - "para_id": 1000, - "codeSubstitutes": {}, - "genesis": { - "raw": { - "top": { - "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xe8030000", - "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", - "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x1002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d6318141ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9657ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", - "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x267ada16405529c2f7ef2727d71edbde4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x0000000002136c670a700600", - "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da92d548e67179c2d8ec20a201550b5233b02d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da931e06c9b9fb6579408dd7c7efc6971d21ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b46e8b924e2d9b5085de1837f6346bf1caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ca6e2d154871bec94e6469892e75acee08d401e08c1173a01aa54ac8f8e901370077df2f8ddfa3cbf275c496cd17fd16": "0x00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d6da2c551fad8f5e3026ed1418ffcf4c7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x959220776573746d696e74", - "0x30e64a56026f4b5e3c2d196283a9a17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x3a63": "0x", - "0x3a636f6465": "0x52bc537646db8e0528b52ffd005804e9047ed30530125110582a271d6243fb8525139496cd603b47cb23211a000582c82f9cd0f269f878309c9b48a767dd4259df66ab796efb233f2eff50842e693b8abe55048f05e4ecc33eeb840f6bef46f6de64cbbda54c29a5ac13fc102111eb29abe20759a5357413f5eced701f75e6d97b186885b455c5ccb3b3003d14ab66cf2fdc4886a616e8b45545ccc7d796106b5e75ca2c8449da77adcae93dd229b700d643f8c96f57015a2420499930ddaaecbca9334703eb8bf43b92f2ea93bd5b2e8428f3da12628a9ff2a107e0b545e5cd43edf2b48d1c0dac8746f62f9facbf3ec99e0a61a26b901b8acdd1c0fa56ee729eeb2bb27ff9f54956adebbea6e057d46dd19ed753418b5d2da2e991ee8acdce5f3b8c9711f6f620565ef114616fef6b462f6a877e5d2e79c57fb5343dd2c1df4fb20a3a742d478623290f157e1ad0943b47f2aa9d1dbaf20a6acc0dc1e56860416f5fb9be80a6bce2ded4d3b4850efef65a8e42b9f12a3af435bf1ed23e3b7b7bd0951b3adae8698bca1bafedb69e3e7bede9478f835bf4b425049a575e31f475c9abfd75e686767be58ac0af3fc91e0bee86a1698acad2f63fbc9a3138afa62a065e416f678157d8b777bcd21695d6b7ef30100b75eadb9557d8cf17be5ddb4d19a86b1f85bc2a625fdfe1157bbb3290b64f431d65d5c6aa75e81a6febdbba32900aa96fad575f8e5db922ecd08fb0c38d57ac2d364353d6d4353557e95957a9d39611657e95807a536f7645edfa7395f8b565049adf5f5fae869df2facd5582ae1e14f8ead04392f3eaeda91299d0798e981e7e4739afae69fccf4f8df3b39df5d55356a9b757baf57de502a82f77e447ddf7b74a4c46106082d852818e7dcaeff66eefee36dfdddda93602a28cf38a9d5dc5ab099fd9532d20ac006a2166817ff63cbbb23bf9d140a7ad29635e5b53b4fc543d3b015e5b4a6b5eb9a11abad90571d76dbc52e7e8afae454fbfbd7d4d5be8f6afdea277f972437db97234b0fed2963b12fdfa66f4e81a7347ba8fae1c6415740d6eeded6bbd5d5ef4e5945573e7d7354db9eb9bacbfbcbd29a3777b13fee541acb53715f0ed9a72d8377f78ccdb9bdafaf6e84d65d50f3df0a004876f9fcaaa69c3b7cf38c3b70775f4f431efa8e3c489f3e6a3b7376378e8da6eed418f9ebe7235607f7947d8c76feef777b471e2c489f393f5edd05356ed0759b5fef39a02eb5a3845d3588549d3152ad0694b69e9a59957df186885d451bcd296d29657ef79754da90c9abad0adab6ba669ca2bdd5f57d77294570a79c5aeaead0637e595fe94afaa4383196c58a5dfd0dbd99b1be24f57870633de6095fe7a7b0abfe58624abda959b41b921f6205e32347d665fe62f8859bb3468ebebd5d0b594947ec26797eb526a0329fbcceed767f7703787dee66d0fe2edeeeef5ee6576f9fcf1206187ce7b649dbfc9debcbba4696afab36bfad369da42a7deaedc90fa72457e5052529af2fa29aba67c768d37e5d53aaf9455ec41dc1e6415bb00bad93db36b286db78e57ec3bbc6a6747f16a9f5db9a1861e1414fe094bfebafcfd7890ac435f875fd27e7fca0ded37f7d783388835c55f91bfa6c0ea5d33347d4dcdeed935c9abf9438530ddfadc9e7db62babd4b5f624abd403d0adb7ab2b57c37a517f925513be7a106bebf5739b1241da350f653b69bbad077f532248ebe68fb087a2e3d7d9392582b44a7f24c343d97e9dfda4449056e98f647828d7afb3e72811a455fa23197e9d5db9235049e9d78358e34d95c8fa6c5756a94b5ecdfd7675ad3dc92a750074edeaca1981ac52ef4fb24a3d8835ded6e3df9408d2b61ecaf5ebec9c12415aa53f92e1d7d997aba153faf58d57faecca1d59a55f0fe2200ee220564f59a5ae29fe8abad71458bcdc86a6ace900ba7575cd546862798002294c2a7c514104418610c44d10230449820a4a418410e40841c2086226489620ad540c524da4d804f912a44baa8dd40e8234915a23d583d49a5412a92ba93641b0a4e2a45084940c5246a454904a416a05a92b525f82780942458a072914a48a4889204544ea88d496204ea49648654929118404292d41944831911a2265454a8814961486908211524042ca4a8a4a0a895495149210844a0a882050048141902f825c0962444a8a2052a4a808628220270812454a291544ca4b6a8e149690029c2060a4102788174196489d2088529029a9292931824c11a48814185f0f3e345f199f19df19dfd237836f071f0d3e361f1a9f0dbe1aec8862870952a8a291e181068f15de133b86d09474aa6865ec58e3616142e3a1d1bde1d4f87043c7053ddaf8aa300a5070506f744a3d92f0369a4ca7e1c3ca8e3578aaf04ac0e30b8bf121448f568a0b4e8d1e6cec68a2eb02cee151d1834c8e10261ab415de193d4b34989c22728ce01ff06871d2628ad3a34cd7dac4e0a1060b2105371e1a54125234e0c1c647123d6210a44a902372a8f408634715531a1d21b81ce820913325c74a8e928f367c48d18dc92122c525058649d0c34cea891d3bd8c1048f3382201144053ee0d0d122d5da2144cf14ed8c46c5cba2cfe420a1871841c8f070c3f34110227280c8b9d221a179d0030829287ac0c0c7154182d8c2c839420a4208522665028d083d28f871a647053d637ab6e84941cf173dad1e2f7ac8f474d123464f183d55b418f00dd412504340c1817a831202aa08a82304b1b20da15be0c30427256c32d862b099e9ced8c630b9d12df134919a022507cf123e48e04389078914e2f80105ca08419010248e206f82c0e1238814ac74657286f041c50714bd829c207c74f1c144f7850f25fa8a931c283676b6e8f185ef00d503d41cfc65478cbb01e33825c1d9745b745c9cb0742fd8c8f81af6927a415442e98047991d627690f154e0ade0e404d7859606cf1a3e84a073042a0b141729334180904ac8a8f01c3da5ada08220af7c40ec113d72a053e69bd263049f528e12761061471c3972f458a207971e5db81ff4d8d243043949e8f1c40f267e28d143891e4dfcd0e283cdc9049a117a90a047143b38d8c9c1ce0d76d4eca4d95963478d1e26e8712515450f2374ce48559113a7c7102825f800228545902a78d460871a3b64ecb0b1c1009503c9821d6f768cd829620712506eb604cb65a7053b2e40b9e1a3042928a1a9f041066a8d1e259e389a0c3e3578e0fca0a28de1a3891d2d76c8c06403f7018f123c47482c3a303a2f3a325b1c3da6f0e2f460a287961e59bc397a60e901c58e0976aeec28b19365a7cb4e133b5f76bcec50b113c5ce143b27d8916247cbce969d2476b8ec78c18e133b47ec9060078a1d26764ab0b3c48e2e7670b163cc8e2d76b0ec886007899d11ec3ca193041d3974defc3043478cb8844e1850c953e3a30a8a085b0b9a20a70a8f333bc6ca60cd6c0c568c0d63cb2c180b8325b3adfd62bdd82e76cc72b12fd82d56ccba60b5d8166c181d255071a0e2a0d490436c607440682ebdc4f64593e0b4c422d14df1408f979e2f3d9cf89125070e0a093e86f031a577c0549a081e5e4e4c9c94f8d6f010e3e30828858f2d7a80c1a3c50712a730a72c4e22383571d2c1c9cbe906a7149c707052c10905a72f272e4e579c5820e750c10b4cc20f32b83824159e25f0284133e34305a836bc07a81df07041bb00c5031f59b418ecd0018a4d2786b7f1c1c579e03f3865e92c38317614811bc309c105c14dd9e6d8e0702fd8caa43087b3c15ba0d49ce6f8318686841f65687058881e2e7a5ea02981c7153cacd0deeca4c133070a08a7383c49a07ed085715a024f16940f78b09ce0c038a835df18720a0f131d0cb42bb42a3414685e7498a1a30c9d317a62806ad36c78e4f8b1448f12ec70c143c8469035910121df4838520e9904f98614827423cd9065c81948154036d20b76042c0e06072b023607c606d686549256303798103038b020603bc0d86040c0dcc0da603dc07c80f1205e81418844c8206415094434024302468448453c41f4227611a788524415c42ba215714cd422b620868959c428a209629708457c229620728924884ec42de20be2185106d14c8c4114238611cb4430220c2299d88a5f442e220b2216114c5c0104226689494425229688443c220211a7442a31885804dc017403ae81338063c035200b6016b00bccd26d1a8e1642fba0d74875d169601852881ea3b5e830cd82c6a255d062fa0441ac48a901cbf4117da58de822b01fb01cadc47158094d8593d042f01bec86dbb00f98070c07b7c141805358058c055f21e1e02cb80590090ec32be017c0167301c9c02f980a36019f008a80a78056ac1c7bc52a61d37011f00a076c12568dd5c1b6b16a768dadc17ed9142c0ad6060fd8334b03366271b02a5834bb0218056c028201612097b04eec96c5b2599c34e18051510e36d0b04534400a039628a1c004248084b546cc60a4c82e3164480109100a42c001628001689360c147880a074c49dbd7e5d6c8b8899e2cd140921e9610f1902489b6ad4313a224494c3c574e3a4670b243932346464e12908000e8c4a14f887268b2c46889069e10196980c9069af85024059d22104188ce107e74dc18192dc9c1c868490284c043d2069a1019c1a144443b247192942489870d3021ea808f4e10f48911932549443a242122da61059d37d4c90e4d9824f10012910f474998f4f0494d8838e0812536b80e107a747e4094f4c4034f7cc00127494f7c68a344493e6880095252d2121d9d1e10191939c901a5c30345226ad28127493b3480d3d98112e5d0c44993244b9230e9e1897268e2a401494d7c7092430e0f44878d0e4e7220d201051d37f48907888a923c79e20122244d9c3079f28408029e4e1bda01263d3c125111929309d4e0e9b0a144494b746062b4044a12221f887c70c00d3a106d60034b6e0880ce1a7d529403110f39aca14f929a102111f1d08489eba8d184c90e3c3a3ad01c983c69c20429e907a2241e30eaa18725499088743062e28350510e4e8c847a74d4e8931f3a39d025497a7470a01c48b22469890e4b928818a0a373830f30315ac224894913263c74d268510e4e8c34908428c9871c887e20d2a19386223949c0a663037d42c424890992131e927c58b243a706dac40993a425483ee89024c989d193244c7af825483e3c0f5d22230d2c496ae2c30e499272204262a24313264992f808927dc1c09c99c1975796a62d25a55f56fbb8b029d894ed613118249e104f38e18418642320bb550783988e62dcaaac18c6183386618ca92aa6ac0d9b59bb19630c638dac1cb9b556d6c13adcac3730b309dbc62b4015042075e88e54a8cc18738c10fef8f1e3c78e1d58373304ba5b4a4d15728eb67563acf024a1840da1005a0733840f7000d41ada00610d1a94cc3b4ed287b2895b636ec654595b99b7ad6b40b72d037837d86dcbbcaa18c6baa96edaad3d54bba15e18ebd5da596fdce5c5306c555987064c0712c5305535811baa9432c2085b428ca5aaa658e7b09a3055b8ab92554a0e9e7c4028197664960d777084124ad6b61f526e10c2eeae1322840516babb75e860eeeed6341d3ab8000440051580f0c05831e514987b98997bb8879bb91976c3de95b29b6163dadccd0d2137774a47c70486404c600844a70340e7a30304009dcf0258f9c80c9baab2911e3d984dba8c754b29314c37d9cdcc41308c21434c8b70f70eb6dcade5685a8f5e55e62db58a61d8ea5eca97aa5e7da95e3c545587326666056012424d6b5695524ad912c2d3099e4e10b2a6490865b7d692a1d45825b166d5b4d3c907c60cb7146facdcaa2d318961ccd82ac6184bd6d61edddd2db16eaca564960c5bb1865ace2a2f6b6429a584cdcdddcc2a99b937664e00f3a6da0d25ef76a79042967177376498a3dc7c623e0935e7aa0fb995394618e12e841884b065c36ed8aa1cc618646eb88bc55db9bbca61905b42c5b09c886152ca66868d410ce3d61377ab6a3343d81242c8cc0c5b99595519c39495316dd588618ac1c817e44bebc62ec9bdda98623dcd7573ddad90399390a5067748c99d20638a61580761da43310cc354b9b1968d2994cc58378432608a6dddccac90bb2133c78861cc52aa6a430821e466c80a95592a63aa8ab162caacaa1062aaaa182b3702989b9931d8aacacaaaccdacd0c15723343664c95a12aa6513562ca504ac57461c4b0c5604308176bacbba3d458752933fbc0585b310c63e686aacd50c21327a1b6435ed7d52d99b1861a94522a86298c5019c266e66ea8ca98546e86d80130ec93b2314cee462c0618887c9f01620e866111c3561802d43107468961a76e6ec6300cc3ba31cc8b18a6187635841a6c08b915e36666be1846ac195356a10915b6f48eb207909c7080880317d292a4157c783f8a7258a2012323270058fd40e104147ca07aaea41c963c69b20429294912130e70c0898ae7e36942d4430f4b7a9603272811d20e49909c242dc9c18991d1929e02e8408444a403929324ab020c80880952120006f082ba90b40489a809d1069082987400444f9e1021f5c001273924412282e2240722a4244b9ee890035112178a560518400f39103d01f21d98201141496200170a508001e84094c449520f1e40ca21851756407e70d281a41356051880a3860cc0852403202a5a62a481241b68c28487274b7840415d20e20093a4263e207d437648f20108110f49929a3041ea21490e4f9870a027531788a0101925f96109075030415dd88013a3241b68c264031b7062e463c779fc40a4431326499a3059c2430e3bece0e42380911193a21f023c5961003a8024274e5cd061091192ca03444549927e206ac2024f12d10656f851179e243d61f20311005ad001fcc0640722a41e7ea03421ea1d75214913274c9e2011414982b484c8288724441b5882e424e949121d8c98f090439224231f96ec40a44392a41f887ca034216ad5b60366b158502a8ba552a4b05851ca2a4bca25056349694e096b73a44881dd4a582b85c54a54cab25852a448814a582b458a9456a22c2952a45c523425ac85ac84b55296b54a58cb92c26a2552a448912245a5b05889949522859528cba444599912d64a59252a450a4b9522458a14d6b23025ac9522e5522265951595b096c56225aa4a58cb624125ca62b15a89b258ac84b5ac55a2ac552281988355d5e5c093251a0ad7d5552f30901452d714029db6be74799dd7969730af9c083ae8d1d7513d73a3b7d3402c2a2aa0c28b11507421860884a04b5e09e0a177601efa0eaf4e0ffd8b170fbd8781b4a5a4a45445087a4ad5402a04bfd99fd14fc744a75d0349a17514afd8eb76d8671d96d1a15e5b5544f1a6d756155b7e76fb15f90c5230050ac4f4ec083a6d5561e55320a60a2514b496bb81958430d706ba9c5d530e6b875eb0eff2a0fee1e117a35794c40f3f28fdea3b5e39242bbc7e5e3c0057b0b9e3c46118f8d5f78b90f6354e1ca14b9508f44944b7afbe2ea197c4efe572e5242083d0f569fbf4e73007394040cdd5b053bcc9faa1e89767450a5d1ebd1b584948ca2b14f8d1798e6c978fb0bf29e5864e4889cc18bfa39c879f974b57e92f4fdb4721371b4d9374d0a79cf0a36bcbcb14edb1e3ec9e074d59c35c5dfba18216ed35f9f9a2f68d55ab984be7cba14fa39f979eb9263dd94487b93e679e7c6d2029c42e650d50f6f7c3be263f7f7d45d0e307572e1bd1ddddebddcbcc0ed5582dd1a1d1cfcba4fe233c5250832a7e1d7a463ff0d7e1aa19dad8dc50b787b12fb7ce5cf3ee11bc6cd9dddddd650fe2f53615c25eac7801e2893aa6628ebf5e5b54bcf94582d6804e535f57f1ca0a95166214ab76d4e341ddf3ff5922beb6ba50691f753da90b107ab32b82bec2cf75f6c88a521efacc18a06e7cfcbc480f62574f7af4a0171b0b3742f2535233b0409fd9b70735f456786d95abc00a2f655991190dd59ef2fdcd557ae590e43cf4f6a0c087edaaadaa2a447db968da802d005ac0fce6d1d97bb2cfcedc0cfad957b49e31d2aa90e849f28f34ce6bce5c91cd4d26edf301f549a8df4f554874ce3c0502faa6a5cb6f9eb9a6c58d4d9eb13759cfce1c127824f3cd83d808e69aafa779e66dde1c12e9bb93bf5521eb996bdee4df0c8afca39e43296959d7721056a107e0350d4107fdf225dafce5980d3828a1a585785ac52b9ecb3da552c2c312534421144e78e3230bbc9a97ab943faaf49e61c34109afa29aa18157fbd17f9458fae82f44f8f351d198f9e8ca2b9e9fcae62357430f8f39ab275d295e45245e0e4992c75c62ce8a7daa2ce8a76597e972f5b998b3273fccbbbcc9faabe8e28e609863df0cc1b3eb5fec75917646b2d787649da3f76859def8cb8b3be5a7746d0db1d4693bbcba3c3ae48c489f21887364fdfa264f79e99ab292c4bc79b97a505f537eb9dd481e68338657a01c57ef78c5b9fa0eaf4caeea3d9ee295747515af2e67bdc9e35c7a2c4dd33c88378f477a06c49587ce51b95ce3a2e902d1692b8b150f5d83ae69acdd3ccb34d756f3a37e4d5f2be241827984d80943829d30cda56b99c9f533d357447ae6999a876ef2330f8ba267dfdcc77c73e9ed67cd1531994ca6af28fa84fe8455c313d66f5e7439ac013af6cdcbb72ff3a0fe4c6a71536efb6694dffcf226fccb73bce62aaa2eac10951cafa2eac2e52b14d49ba7c5edf2da733c15921e92ec2be21c3ae7ca0da900a6e3bc2fcdaf6f72ae7d3c9a631e9140e73e1eed4322fd72a95d9ec9b122293124129347a067dfbc5c734df3e6e63c99eb6b7e793cd95724ba76b97e96310f5d738db722936bdfdc9f976faef566f2369ffb1b7345b423daa72a646ade9e7d2a6466ded0b56f9a7cf3b4fd6b7fffdaf9420cafae2db78581a7bcfa4989e8abff0c91b22c136f496041d358a808b11444126076caa6adafe728562d52a7eb9aaa7227f80a45c7abaa6ac7aa95ac62301a3b3b4f8cab97c7137d1f734c07fa0adf8e397bd1972fbef8c2d653e74b2aaf5e1ed497175f8b1ebbb6513af68dbd45b8c508828e5d8b5c11557361aa1347efe757bf8e552b7cb3b0ed1968ba5688e87805af2d11ace0198ad71613585ef5dad2d23a9214849b9fac31d74f1fb288f1ea394b44d730786d91408edf32af2d27b478165e5b2350633923a632d6fc919277f3c41f4939fd4c6a430d14143f93be40a1b5f133c98836e2cf27376841005eb0f4f38998a557a9a4f4aa715efdd59909ed689fa160f3de7befbdc61b7b91bfb9af18e8b445459b8f69fcba16b7fd66cb04587e1daec78324a651d4aecedcd084c2631dbad6de91c68913a706efa3c76fb25e5f831e141e58fc39a6f1eb253112fff63466cec8c6f9a38d133f9e23ebed46d6fb4bd2b4d5e5ca27f1c76fb243d96f677d7bdf9ef79ca4bd3a2b6e5719f39bf3ed6bcbaede5f40f6160b41a72d24ac78e99891cbe4d29b262fca5c7a74ccdb35f636d74e1ecfc9f527eb4f8e7993f93bca792849fc27df723c9e93ef77aec18d5d638fe70854527acde1f76bce1e3f73464e9ee3758e792767afb71c0fbe0637cc53237c8ed7afc50df354099fe3f16b90572306afce37c73c5dc2e7685c8627cefbb9cdd5fce6b1775e8e07758e17b50873e94d03eb0be92aa51659c7963b82b9e439d2bf79fff64dd61825a5674f736db9289de6274ff329aff176f234d71a8ed72011348ed7e276f29e600f857f0348fb9a436e08049de61ad45cd3d8bb90650a27cdcb2801f127efe7359337e167dac7b9aa1013e79bc99bfd996fd966ca5cf390368e63df347693a71c0dca42a6ae1bbf8e55f0fbbe3f14ab4e8bb1d1903af8da42a20b52175f5b484ca12a24058edb368ef3f576e0388ee35c53dc1b4d339934cdd7db41d3344d734d6944c83229b34c3ae65996652e9548e69acabea26edff0ac00a12dc7533c8a552a0463660fb20a932a04f3f576d8017398299121a94478a069063a0c732dc7887e4abbbaafeefeb4fbc2fcf23615824149d4690b092c1f1d831ec4d8e518e69767e4c2780acd887e8e6beae9138f629542f1aa42d6d5db5f4e85e46c2a24c7d923ea4ecef9e69a137526cf5c3ae644dde571cacfc9f14d89b0e738a744f4739c73d4d0dab5f6728e60469ab9ec7296de994bbfbae5956998f4f6a0ec4be9d82af7a9dbfbf3975f9174cc956380ccbec9fa6cb328b2bba507f55194e775c947fd2cd983b8878e7db51c55f778dad7dbd9e3696f6f86a0576561628e797bfa9863471be72552d2d6c34b57ee8892972ed508e64de945ed986bd2fb908ed48d92d227e9789377276d6f72e9cad5d0c3671ec4d2433a92e15985b4275d2ff9db330f497ad2f526570e4992cf1c85cf5c7a4732fcaa90acb34f958576ae487429bd756ceeab476fb2af2b6764bfdbeb58a5ce928b91a169d4e9e85a6cd7966317d230fe26eb7979bf19e5d7d5c354551dd35fef28ca6347fdcaad07b196a37ed4a3ae499e0a59a59eeaa79763dec41c4af7465069ca4ff6230cce94380f45ceac08ba74fd66f7eaec6dec50b0232428d83f61fd7e43ea41addee59cc46ff2cb4ddfd1c279987949fcd03f9e23dbfa76e686da2f4fe7fae5ed41e18be7e5df3c8af2d027ebb1c5e437a3bc74f6969959fa3e61bd3afb51175167bf8efae1c3afa87d5d392410c97a7f45eaacd95e705895c312b41c3ae68e38e34aba1c94c8fab71ecac5359703953aa5df557a3d82b6aee5e86faeae3f9fa9c995c86e9eab909f57bf8e48cf932e3b88d7d35fcea4998a6439fb9a72438ae974ac3279e69a2f37b4df64fd669ec9931e94fdb99ff97a50f6b3cca5a70fb5e6ed3eed77d4cf53a47f7d3f5721eafbd2cbbe22e99a43ee48fee5daa79ea3516773e8992f778479f6a9af26bd293d337d93f5a61d3269df8cf29aaf9779cdab2caccf05b0be785516d6330feaa328df599bbca3fe76cdbc1ebaeedb83983d7dcc13f297273d6d37e9cde8eb98c7b3dfd1fef5691925a50fc0637b51a1359687dedeec8aa2aff0337e7b8555fada1a6249db2bbcd2874ea444907ede8b3a4b79e0ebaadff551a92244e8722b3b84aef06ae7a1ff50818b6e1afd5e6115f49f1dd840e5f2f520e6a19b7be5a14fddc1438fa2deb58f11b8b8000a178059a3a4247439145a4f09568184144044d18510952a4284d6adec1052a752858530b78200a1f5cbb38200a12857dcb01ec586f681be4b0ffdf22857dc80f9f545096a1fe8d817e58a1b2edf2f8ab70ff4ebfbd1566b7dea921e6108197809020e52c0061afed055c0a25321161367ba78e20932695850042115529f3f0f5db91b5c494968aff06a8587be5898e1139a2a91f9c3ca5999a04708a57c47371e7e715e1874ec6b727df372a3c6c2cdc7eff2d9df8cf2b091a81949fcf57991d1a794376271f9abc901a8b43efa84dffce1bb899a81e5a3cf0f3cbcbe26cae6e3b7f4c58b16d280013c62ccf7bcb68e78e235dea2e7258aba949c3871e27011ca36ce7b51eaa1061641f84009fa1e759c38ef4509c81127d0d2052948971598514aea41d9bc17a516d280811294035069fda0a4666011529f1ff8e8aa30a8b630b565840d3e69df0b10ca4628fb94948d907e51d4a53479c204379802474949487e4a54949484d46714ef6ff6a0c14d7aead06b674e022da441268e90ba7212d061430d2ae608224e1c21f59d6fbebad01a422ffa2e2663f476e975315ebc2d37c423682efabeb68c38c1a358151d7afb5a6fd0af08a1b70f9975a02e95486f4befccfbf2bebc3dbb5c5eed7d7963dedddae5e9c721364110b35fbe97f340dfe7e781525c1eb16b1f7a50aa10e520681a1d2a91cbd5a312b9bc17581545f03fac8aae5c4f9983f4063279745503691ebd87579055d2a30f5122ae42a447d7bc8e7da49b3cc93ed204d94028f6911e1dc5abcca3eb9b9f2f7c741394c87ef44b7a6bb93cc5a4b717c1e67b58259d7d7d39e93c98f7f3f360dfd1c679ccb56504095e5b468ce051ac927e1dc1cf3e65958c2a445d39a842d483f8f2f6a5302b7263a129afb076f8cd7db861de7e34121d3a47ef68e31469878ec41bca31a01dba74f84df910f3e58e7ea002c414a53f9a1d50c212e2fce54f587f7d4fa22babb06f42e7e819492a404cf9a38d337f600e89fce60f9a7246a243875c91cb5585cc8879fb9777b4717e3d0ec18fe7483bf476f815b1c3ef28cacf7e8ecb6068986bcb0d69be7974e971ae4ae4e4976b27af08ebe7e69a2b57449e8aa4ab6fde94beaeed6bcb082c1f55c8e541ccec9c27dde4ed6b9a720c60cf32e7bccc4d9eae2b37947d3373cdbc7dcd8358536f73cdd3d79433a2dfdc57df5ce34d33627a5521535dd376d3360f62dd344f7a1babda75e836d7dcf4531e91bf7d4599b37b33f3f5a0560e097bb6dcd1c679930775109b3cfdccd397fa97173de84156e9b6bd45d30a74d027eaa31cc2abf8f3d1a3bf101dd5e6a3f730100bc5183eb2f0d13b5eb1c718a36b2a7e45da92a2a9aeeaeaa029a6b3b10aeed8c0abe95d80fc54340f3d3a26a33f76f9d4348ff9105e4d5410afa69c8157b3e3815798c7c02b9ec71c075e69ab88328ff90518080ab5a6d1328fb9f2aae7e7cecf218f450fe21e8d3d2ccdaef1986bbcb1c78f62e331d79457fb74b4715e31dfe115740c73cc35857d45eaa6bf689aa4d316124a3f650b8a38be5d9fbd67d75ea4e2959538781dd5a34bcf3bef461a85ea810cb442ed5defbc114d630abab9b55689a4b8d3f4a421759055fab9f2904cb4764d4d6d59e96e9e67f7ed67f7a98d553f1ea0714491459a23b460892c4010524f07aade871001068885d863686f7ff3f1ecdb7edb6b8b882e0f3f14ab565b72687a818e25afb6209aded0b16ba72cb4d796952f8fead158263841133e60c3043188020742de6a0b59b5906b80ac624d53cec8f54dffcba5623a9055df4c6a93a378a5797b0faf300dcbb26f666ef20cdb58d5f10acbbc5dc3bc7d2d63d5e6cb1941fdf6cdcd4d8e79fa9b97d49fb9b6acdb27ccd3d73c7d5386fa75d94044b890b28faa90d9f3fbcdcba3b76bbd7b79fa32ac4b2f29fe6e1a6f3ffce5ea4dd99c623aeb0a398d3a1bab52ac6ac7dc59857d41ad1ef4e6befae525f5abafd7b18a7db921fde22759c5ddbb6c20a8a4a4a4a424d4ae5222fd9d7ad3c612b4b7d7806e5d469756ba3cfb861a62e9d97b22e41ad0adebbef7a0296b9a6eacfea1adaf67267e93f5d1d7d37a5b6f5fdb343bc498e686d6b009cfdd1bcfdee3713c0b1dafa6f6dce388aef1471be7a792e78aa89a57d7e206b75c5b9c02dbae21c0687d2984715338a96b4a443f55211b0e63dceb8a314208638c70ee4479ec9b50cab8d7eeeec28d0b312d767b19ea1bcc0c61733333ef2e332f431823f7f2a2d1925b8707569934e69598668a30f3a4b775579a5e0385f0eeeeeeeeeeeeee4a1e4023fa0ca1b2a0ce0558255d51d8e5ec3b17ccbc6eefbeaede306f7b87b57bee185ba1f636981d4b505e4e74c4302c0e613b3e74d03c94bd506e52afbda48851ea60cebc9b2eb7db3697315ddea03777777d779b2bb2bfc6183863def042e9b5e505989fa86f2fba7c176eb49ce8f3fa26bc2ee831865756b124ab262bba4efe8d57907feebc7a81e5b5e5459a47bdb6bc68fd7247b0387120906c9f9e3b51348839f64dd66b92c782aaa3ac520c7ed0d3c0351f5d536ea8c7b5ec82f04b03311828a6817090f08b0acaeb877b52dcb52da373ef0a200e3ffd8038f47520aedc0201e2da6e403c7d209eb9a69a6983bcca5c5ddb366e0a795dc22f2b1d7af0cbee57645dba84d0cb3ccb56582850c75bc8d60f88d735100403cd7c74ec9ba808bf44f825c22f53df446155f41ffcd2344a441f68d50788666380f8dc612060b0cc817844c1198b133cf21a5eb1cb2d3a321f4ff0c93278361f39cd03f1c96a50b8b8a12324fe40540fc431cf3c4db9ccd7839e7946d6a1af43879ff42b3bc19bfb40e4976215100f622dd3cdfaa42a0408dc80b01806c3aae899c761dce84c7ef9d437569603f269bca24ec7aa8882a360e50aafb63410fce14df7e6cdd43753f553dfcc1f93b727df93af5f9bea42bdc9d73b5e20bebef14a2e8f2f37d4e3382c0c9747af8012811edd02ba3279749f92880e5705c2d4675d879bbc566d615d7aaa42260abfee99f0eb9eb3403c15c274d2a39bbcad4ba72f3dbb34b92d6744fa3a9c991526305fbe4c06c3667889d3f0183ec368b8f5b1ccdb6003abb4222bae2ff0cb97cbdb77c39d7250701478c7b4ca30981320ab4c2e3dc92a935f5e110a8eb40fc46d905bac79e9d0b5131756751becf2714bfb44d72dacca41d7c351ee63fb09bf7c815f78753990e7d161cb0caf7c7874b8c42bcda3c330bce2ca3cca273cf3118af908c77cbc723af8057e815640306dfaa6d106dd049f0c463afc8ae0273f1a5885c2a7ac3239902f691f5ef9b221ba66c32b143c7ab76920f71d0e79253d3ab4d240dc05889b3e2b1fddf3a052fb44d738c815b93c1ba29b6ac648910dac8a9eb44f03aba25f5f11e4b2a581b84bfb44b8e5a36ba74d37cc64312956c586abf1c47304bec9e19bbe1982bf50f0da0daba2bbd771b04ffcf298f4b80cb7784c97066230ed134ff8a67c20dce58737f77ff8c515c1fefa1a0e19dd3a8ff3f01ece60d0701a5ef1787456a3b2f3d9f944fd8eabeeb872433d3c887bbc28de2ab12a5e1c8369201cda277a940c067e3cdf0fac8a8e721f3ef5cd167bf2f5e6909c9cc1f08af3e81c86c52811fde89143723a82dfdc3cd3ffe1e9fbf05026785edc7b78ca2acd79a43efa8e87b43bbcb9a17e32988faec343c270c85ce79bb2fb669793e6a3cfc03ed04f9cb745cebee9aa5fbf3ad95d0ed7f0f00a6c689a9a0ce62f1d9ee3b0fd724d537ea6a2654e74ea41acdda7ddf97a2b86a7bf22ed9d775f63de1e14c32b6cccb2a1405dfb5c8ef2810fe1afcbe726e12f577d0cf31c9deeea7ebaae35cc83aceaa18178f80edf5c1b68c7351fdf6e721edeaa4ffb8eceb7eff0547d66f66354878e0e9ffc05e51d4a87a6f3c29621daeaa266bee81298bfdca4f3c20fab303799ae2c96e641d3b4c53246e9db3bd730cc14211b3367201aa806b2c93153a6753a6dd77d50166e8b65b1b0eaf20bfbe67ec7859b6fd7e19a94daa6c3db31acbafca781b6d53e973b4abbbbb821ec5b316c74db642b6c05afba6f76dba681760b5b69a01d7e396b612e3cc574ce734cbe6b78d5b90ebf7c3296bf78cbe42e7f4dfef297eff06c689fcbb1ddd240bba67d2ebfb46f8be9e3be2d399fceb7e52fd754a56b209cc79c4a9b2b6e3e6a79cce7bef9cbbbeebc731dde9befe2782c8739233c7f7e1aa59ee7685fc7a1c06f3fcaf9d6e14a7cb91ada757c50f675beb9deb97245ba8f0556615eb41f0facba5c0756715c664487f9aee9b4d306fff265c32be9972baf329fdbe6af53c3a8ed106b9dcb990aaf30bf5c5595f3f6fabb408e37f7739c39ccdba4cec9b5f502a587be62d48c71d3e2d5d4a53258ccf06aae965dd3403db4cfe59acf0ea58557397eb9a77ff2380f697fee9abf7cb1f04afff21e1a08a53eed976bb7ea3b93baf9cb95f894975f20f3a467c3857d0ce6350529cde2f8042eb4ddba897a45f973e63328117ecd8d7ee055bbe6471408d53e9a6bae8a3a437583d77e5ef3a96cbc06e73557e953ddbc16c77a5b2cbdbef1da12c3e6b5e524c7f1ebcd0d79139679d862d57af3e7d7233724bf9f0bf25e7a5d279e23d74b872fbf29e5b10fbad1365669ae52678755976b4bccd2cf21af798c81579acfa066d48c4fd4a31a08aacffaf6cd4d62dfdc91dfec79d46b657453cdbcf63b03ab349f28ff950a24d5675d25e6d8279d065e5d1ff41978255dc35c5ba58b0b2ed63c17695e657875819a576540af79c6c099319f39743272a8f9cc07e65387a71d7e8c911bc2fcf229678389994f5583691f1e6a8169a04dd33eec7c351894fca6ab7e36988b1bc29cb5742a84e936cdcf4d837d4f414627e7a6815d9a6614f6cdaee19706469f9b66d72891a37583f95c36be86cc611a5e61cebe6a78059d7dd7281124d9d166f09b9255d82715f69a6b0a6dd5e095dad35f4237bcc21e4a2f23418762d50eab78b98e55dc1edd216c1ffcbca65ba5d39698d69bdc4deae633d75cb921d6dce4ca2bd3c50d996860a92e7de6d3c7af9af9cca7ceafb63ef399fdfa2ad090f6c986f6e78734e7da4326ea35de4c12f3214a843ff30bf0c0abf6cc0da040b27d32efc8a064f0997fe653cff84cdfd07cea9acf7c2a9bcf586055e6ebfd689f8a5519d4d9587579f4a9facc8764dc37e5cf219fb9d47cfba6347db3d3959f797346fc4d26d77c7d4d269349ddc43234e8d0cb7f1ac45af4a00ae9de40a7ad15b079a84494882e15bfedc92e0653c8065219b0198384166e70792384807740ecf8cdfdf664145d34025555d715e5215428b1e566d08f5f1287e4ba3c7e50f8615cde3a3ea253d75437906a0d78f90c4de1eeaa105eec5b554510c06f0c5efdd1575bd828cfdecc45ec72edba30ec3beabf1442d6b5fcc551c781f3aba42a040aef878479927ca4a48e5bd86f42bf8e40f9a9aa26f4cb9bab210a8cd0973b1282c82189dedf84df648690a63923ac87ad611bf363aec9adb923b079fb245d7e47fdd8c58022782e183755222c375d18788a69d38561376e83afbede2667d0ad4be736552226ee503082c2cbef08853f757fb46e5e1eed9a971ad77cba62cea64a24be46377967a6ec9bcb7135446fe8d293e12567448697df910c2f5debb45ec80d4f4ff8a15c2fdbb12f7ef093d95e37203384367f390f922b7a43065c3d355bc798abe1f28d2ebd9cbf3eb81f3f69248785ed6ea1051c175c796d6981e5bbd796164a986bb66c3a19a48019fcd24ccb396ac8fcf9a9fa9d3ff9ec79e89ac93b719c07a5e66d1e6495941ec45acc32e78ce7fa7a58b5ce7acc97536e48f25cdfb6f49a024bdb26a34ded591deae75ca4486a462c7b8657d82b176d4ff10a097abb8a851f5e9dbc670cdffe020399bc5d93de3e12a8b9d4fce4ed33c0f4cd9ee650a86733cb21814bbd0f7ff3bce4b8c9f362c585721c7a4752941e6bfd64a5efeee507bdb9c5cbdb88bfc937bebe69fa784c1ebf8b39d327a11ce5bc74ce7b494d9b8e43e9ca9629c08c318366cd1bf04da4c22bf6a85cb1791517ba025e1c92cbe137bfd74ede3eb7c0cd6f9e7148ba0cab7658d5be7d274f3fe33a56b5ef19cc1de18f0eb9212423fa263f79fc1937c47daa2d4ccdd79743c24842f09ab7195e6ddebe7d93915c5f97f9a6f4b6056bfeda9663400ee6ebd2e335797d860a612efb8eb6cdb0aa5d8bbaba319e2074cc837e798dc69b21f8f8f0eb2515d24bddecd6b7cf5efaf66e75195e99e195462c1bcb2bd80d84107afb405f247433e7a177b0bb7394c80f76611786f9f6a09bfe18d6b19052753ba82b5e3f49200219dd0eab227345f8f9d32174dd0e0a4208d52c843ee866f710b643873146e8c55e925517abb8a18da39ba8bf1cc5abcb7b78c57eb943bfaecbb78e579e0a61306c17db6b061293d0e4d8857d451886993ee81183df0eaba49452ba94524a99659964e616f0d00efb44ef58157d79d04dff1823744f8530ddc597b73f6da47900bcb6c2585184305cb270f3abbc7d2ad2af6fba22da1bdd099aa6a0c211012074537ea330103dc8aa08db74b3fb181dd5edc4285ad7b1ea07ddcc79ce51229b8376844f33a2bf46f8f7db41b7ae3f3bdf1bfbb4ab64550721d538689a526e8dd0c120860e1bc002309f7a6d6161e6f282fd50214cc77304c6af287e3cfc79b1e242d87775d4154277e60c98eb2bea9637053a9255d12f9f7259d0692b8b2e3fe5840fb3d802a6d3160bd8340bd42c169db6b258f39f459a87100b330fbd87f59b3baf46d6374e05305d74fd9455fda93a05b9cb14536801a3050c9515c4b1808e612320e795cc1071ac5ad1b16bccdd0c61bf7e33ca0759d5dd2755218a85065df7ade8d6b55dde20ab34e5ed863c4762f4fef8cd28af2a04fa4235033a8a57ebda72019787dec30203a950eae10b9f825adcdbe786146e3ac44874db71ab6fb752da2a29294d11d2874090976324baae5500d3f93ac3dd4253065aa59f21af03f889fa07722525252084d455410c048574a4f84118379070c40da4107241d7f00111729439c34d9b2e8456bebf72eb3f82152c7d005e5b2a08f35a8e4ea3eddba3a641ef08f692bf23ecaf29e519ee15cc2347ef4fb785cba117ad3c841ed4456c0bd8b20b34d574bb9c7dbd9ddb6b1fcf0cfad28de8cb4f05309dff5c312fbd9df59837fd31e94dcc19f3e8ac8f5e527cc81ed4acb89b46cd6fdfe36c4605537c015e5b57bcf1578cb942cb5f71e6b5d71c6ed9a587ae99e0f3c7e453e5a82131f00a7375f63457ffe13e1ce52637a1b32ccb8a389965dd7bd47267ec71f94d7ece7bbec9df136e716e469ff18bd957d4ce79cc5c831be495ae725cbd7f62cedeec9e2f5694fd786708fe79c2297e5e9ec445994bf9e8cdfefe66c726e41287e67ef385f1b7cffd26dcf29727f1c77509bfbde136dc86dbebb3e736e1af940ab4d13367af178bc9e4f2874fe9c367876a1fc8006e9d3db667df7e3cdc6291de9de03a50f09dcd79a87a1c618f35b0475fdf300b43f455214c97c41f6712fffae5136e79cc353f71467a3c7e45d133e77abcb9cf982f16203ee56fae0cb4a1f09df0cd4e7e53465e3f817df66c9c11f6a295ae7135b44e00f2fdac9f74f6ebd9c1d1a5c9cc6b1dafe73cfd8e1efcae2172996e0b73c3fcfac51961d7fcba9cc7c33ca8d9376f7afc1ae8a77da09ffcc737a58efbf866b7c351df4445377dd3d94df8a6eaa1ff349012ceb31edf54f137553cbee9f19bbef34dd48e6fa2747cb3d3f966f7f3e7bbeff4e5642699699aa671f2d36261b83ce89b70fbf85dd1d9f9870a5a7c45d1315fd75cdbdd6500e6d9a7f12665d74dd46bae0aa4eac3aec5235c3657cc671f8f74cc2f57d240eed0170bf4f6996b25059fee3e519ecfeed335297cdb6a1fe8d33f29bd4f1b2e16ffb47d26eae762c9fc82f0393ff9848f25c5d77c26c5b9621ef38b33a27911f7993c72356caff9b63887f0333f7d13feb5ade75ccad93d549f897aee9b49f133dfbeb962fef2897ae8abc5c2a0656e72cc157243d807b7fc7a1aaf971ef8e89a2ef36982e0e391f8f0e511edb16fb5d816ae6fe935052990d5c33c6f3cf1f34412c3c84a4a4a4a49ac832762803134d7aef5ba7e3bb73a689083db0f15b8e89ab92186be5cbb724774ac9491e2b73fa3185ec74a99287e1dbe72fbe507d5798a57edeb2a16e2e7acdaf652ac1252f3c40dba94e08a3748f0c346378424e067f4f331c618bd5de30da4bba7823efd53ac5a6fa0ae81b0cfdb675d0a0de02083861a3669d84028b6f0b1cc47d347333e1a211463fca6d1cfc3cf5d3f4f712ff8e93f71cbfa76f964a13563109a5ae6d5033edb9555ec05cf1eec02a9d6b59adc9438993804bd8755ea2a4f2815a2de435746128c13c3300c5e49ad69d94df20c73c843e730ba75d5fd820531baf5ee6ea89a286dcc436f8fcd74eb6da4651eee98d1ad7b01a1370c5e73c9414a040715b2ce9d36161848d5a908593a1addecbaef2733699c42f2811aa4a00658dee8070d0dac5afa3d65fb9c07c9e872bc3d28c7d3f6d906da619ff6f69dcd69d04dd5b78a859f210d44e50ae9555c28c7db1590a63d68065eb1a947fba67f9bda680892283250620a306fa4693e3365df7a0ee60d595dba5c20b7f571aa1ec54033b44f041ad23e11aae920cc41076dd0cdddb25b78c5ce5c788575b18257d8e7050c0f7ff28a6e3e37908c2efa0c38f019ddd4351f5d890e3c28f5c05178358d54bf3fa0f9787d4970c31460eefcdc2d908c6eb774dfcc4bdd54375b3e7a0f493ff0ea0b952ba05771a12978b5de25065ead949f919679109c008d366bde48d7c0bf3cf8d02bc0fa0e03a990148a2cbcdc16968416a44262187281540c8c521fe30beaaad442d8c36756e3350438638c51fb52554729a1412fe6c65eb8dceb6ae525c57c767763faa3f2c28be86998cf8875efe5cd1d9fdbebf7820a815f3482a6290dac5a63c3b7efb2ea6930faf92db3df901874d37cfbd01056b191aef9766eaf89aef922fe9605b7b718f2bc838a9b945d54e65687bcfa0eeabafcbdeb533943f69d2172631a9aa7cec2cfb396f90ba45e3d4a0f49b3e78d52afae2ff07e73888a051f5a876b34e4d5212bb39196615ddb4740ef56f5e8f3bad42ff5a24bd55b7b7ad1ad4f46d3adef1a9a1655a36b5174d8b3139530996e1d26c5f8b964d5fa7a51fc027efd45e16b77372bab163458d0a0011a251a98f136385383d6c3a5e818a63ee177f7fd4de91dafc819b93cba5ad922b821c53cef899df2b631ddd456d09006ea54f08a02755c842ec131dafcf63ace5a6a0006cd9b4763064d1734695e5b36da4471997717100ff5385fafcb71ada594123349d92d4d5f513b66ea26ebb8e99bfc98eb7cde5c363f8c197dccf2c4df84ebcdee37b2a25c7bcd10fcef5c373fa3675e917429cfbbd96e9bbfa4af773e974d767d73d97cf4f66baffe8a78c2bfe075495fefea9f06521303b0cbf7636f26f595c38023be97af958581fde2a07ec93389e7b2f9ccb9136744c7f92b6297cee9641b5fbee3721d1b6764bde8d271ed7278ed307d3ad8754cdc50a6e5b5eb3166666679711e3deebccc773c1e1ed4cb790a5d773e2f62fb72bed34a9fddcb4f625f113be7b2888561ee5fccfc45a3ed257be6fbf5cbaf88fdf2cca56b7b79f62535d08ecb225c7a33a99ff7e3c13cf3e8be45181965fdeb33a99f73cde75ef96ce5fa0faf38e9db37218fa47ee9dab7452c0cfbcd27ac976ef2cc157243199bbf3c8d17333bc3c7ae6f8bd816e2b7e3b53ade81068e1e42123dba40b9d4ebbae013297620c51653b8588192d0b56606368038d0023bd40eabb6bbbaab0c4686115548a8351a2c7854878613afe3b58586959f9d4330203c230c11ddce1948f8d9b91860562d98438d1a0871e58d126b2cbdd971861128c0229734c880a383316ce0800b3596cee82ce9800455b40183840a4ac046195bac40074b54e82cb980892a78686026ce951fc029411b4251cd9252ce521222a822c86beb0c34cee8e22ae3cfcc2af79b523a0a5f246587da6fa69e255c499fcdccfed844cd4ebaaafc26f649223a7f7dde7ee337bbef9e5f15e2ef6017df84aeb08b9a59d0b56f10425e1ea47162852d6f54b10286cb521339603017bcaecf4bb1ca8c3836a520e267f4f3d7755df2baae207c70d1ab043b6600664a1512ca088029707cd9028d08a6e881d08583bf6680c55f331863c5e111da08981a6fc26bab8c185c8c75a129afe06baa2bf6d9ddddbf6a46e26f85013223d138719e5986e74f59bd3d84196cca40c2ff603e597875558fd14023c6046fb480a0411042f1061fbd4369155d9a17e3a7eabb8c30be7d07d5402c26e618428b11c670a2cd0984da8c4ebeb6cae8a26b252b84b458424d0ed480019b2dd278c0162c8802cc0dde14a10856d880958519699431c61073d80007358000a7805370843349fca822078f2a104208239b2e8e2a683a308210e314440b64b040870c3082c8796d959125abf2c514448c31461e4a104669076da0516283c609ed04417c700454c831471341f8c28b2e684006111c194d5011e20828c89041810b4680069c238a90f55002e6064ba172d8208e135061038b385f9c31c616dd183248418c31c600a4b1042c55b698011b6008c5a531bec431aeb8e2c2185cae31a2a8c225e0f1da3ac303127037883bc6151940c151f4344abd3cb344841084d093b03b630610fc44e7e3b575a605bfbdb6cea0e067e7657406970eb2704209166451c51b3f3f5da4fc49a5180a08533dbab022cc9817b801471b2135e31fc0e5870b971473f9896b064d0cdaf008a051ea2574a2eb5e5b3248a2b7872d19142184107c083df5e3b565c6cd5faf2d333ff8e93f48d0eb52b8c34050e8babe6994facbd5934d749743895d977766d8fc755d57fc44d0e9438750a452bc05c214339162268c523d6286c026755ab31b6cc56f876578e94c1a346bd47c676d9efb6b6566a4ec3a140b8c4249d91efb2bc21fdd3f0669beddc9cc01e6a11aee88cfc334accae0601b63e966f6e6e2867e70956e666650d91b35ad5fcede706999d858b162498b63cb986d0d952f673837272d4a8437c6a2657e12d33eec12c2e9a8a8472b6fa67dd8cd983163e624a6a5ecba9398931835300ef80664f3ec1967c43fc23469bafe60c713fd72fdf815e16ffd44c935134766e60713d1656f78354f62d8c036bc9a9999e7d5dcd49cf9359999189450f97266dd6811c36a94a630d36f60816194086f3f2fc05729bb0e85cae0b08a7da9d356a604020099945c5018b224680becda5ff64685b0f3121d06d350316daf42980ebaf168a58150304d03656fda87ddbfa0ba745b3e6291b2eb50a8ec0d4c03d33cbbd6c32f3ceb44f1d7a58cf837aa81a4b34f6f9fd81fb76548743c50e0637e94f3d877f9ccde3cfb05659f07c9c6afa83dfa55a43f99bd9962836e78c50d3904bf6758b57ebab847125c1e726e94c8462df10a472a10ceafc33818a8c5b4ef6c36cf5cbec5341057867dd8211cf9cd6e51c1bed9669e79caa55e7a8e5ae21b8f049bcdae5c6a2897e4925cd3402a24512d97201b91df1fd7f2e492966eee9aa5e734bc68960cd5aafc17f52b3b8d517013ba65b7aa5ab585765d7fc8716eb0155c41e5fdb555450cde8a38fee7b565050c5e0c3761b4f1eb5a941ea5ceedfa92b00bbb5aec1f87c4f4ad0e3923985fadbaa5b9218d5e0c433e1e1a48bb1bbaab543fff059da0c381d33c3b0f1dbf29bb0dfcda3cfb0c0aa1dc544b6ee6a1ebdcd68a51a04e7dd4a1eb3e6c6f63d575842c59b264c992254b962c59b264c992254b962c59b264c992254b962c59b264c992254b962c5a16cd92254b962c59b264c992254b962c59b27490558aa14003230d0cce747976bed2ad8a55ec26e89a9a16a01fbfb93dfcc14a7473b13c6f5ab6f04a73f6e5a28457fa271dce6ec4fe4303f5707665a01d9863dec3e3a17dd879784a7ae753f691ce9fb28fa6c33b9d3cc79baca11cd722376432994c65dee4ac6f6ffadcd9aee31da7f3cda43f79f4585c77f2a0ce399d9434d00fcb359ab6b9c60d6d9c739e0a603aad88e9db5408d369fbfad1afac378e06d66b5ebaab815668a76334b916bdbdc2aa18d447d8376b8bda27a9eacc9b3bdf5d1e121c8ace1e1dbaf6ec5ec360e327aabbe8702687c650a1d6587333ec63d1e4fbcb9fc98b99c7abb92193c9e4ac5f6ffa2be91ef3cce4ad43c97a96d1377e6cc6b68300b73c34132609c22d5ac02d708bafc7beca10b69cbdcbd55b1ef6bdf23be6835883565efdd257e81737a4d7857910ef62ebfbca62f6dd4bbfc9baa2df11f6acbf0ebdb8bf310563c86441a36504d1c2ae775b5a6c45358e5f3a63c51768055261b3a635a6b9f4963771a0590263055f612b6dd894696d97e502e7cda75134ffc2cffa7eeaab0ebb402e70dea44113064c63e92b6fb43153660aeec24a70d4a4111366b52c1637467a66061ea253adaea678880ee6a87ef385576f6ee84785e46c19ddfc7975a8fce3b36d199d7a5c081bf669bdf7bbb4198babaeb3867679b36b56baebbaaeab5fd334879ee69737537f7d13d54df917e7931faac290da163cf0d7a5a3c379884e59a5e39b51beccd2772f5d635eee930e5fae48ff51bf8e4f55888e4b6f2055fbb4b7a71606056c0bdd7d13d5e57cb33b7d92fb66917ee89b4703abda5599b72b6061506dc1e48d9af295dfe4935b219fa6943fea87df0c2aa49d97e8a06b9f9ad44cfbe54df9d328c583a47bccbbc73e093f75d3ed85455b8697346da76ebdddd9d683d2b55a4affdafaa208bfdec9dd5dee1e5d2084305e266821370cc33099f52843db4c269349db4c6e9c368ee3b853ce15049dadebba4e47878ed756ab083bdb8e1d3b76ecf0380961c2d6a3478f1e26a0e0123f361f3e7cf8f8c1a32302205b0f37b4dbe33d3e38230b84d34e6094b743a7ad1696dff1d1655f18856dbd0d8b4c625784cdabdb0e37301843c68c1ba09113458c7123175be6a0128695248268238330ba74618089420a655191c415247803c712aa80315366093ec268a2082914083d30c29b2ace00230b2308c5189459b39579238b1405786d7de1c5f7786d7df1e51263238c9e11fdb8b301600117a5295a10042410417ff00332259042046068c941735759f30c9548fbf6d1792edf8f5e11fe8efbdd1ecfba3e4f91e5df57ee860efbf8b5dda211f5f8978b9f54a8001a78f542d00c38bcf0c2b3cf212f740ad43520230d1a285de1461942ec280562695952c38c125a64f1023484d85d81582478220973a0a902040a9e1062572910048a98811557d4c8c10be4d0355da665032d54f5d985425f5859de2e344da912599ff1152a110de21d06ea7ad8150628f2d7b1879b47b8fc972f5743f7d3e87777f73b8ab29f177f6d79d1e627fc8ecf283d07292047e7b473e75d8964aeee290cd2d553502298aba794c8e5b3e7a7934266015e5778de61a0a0f6e11f751844951bd231999cf31966d0d1133483b61430830ede37fb060eaf7a4c9ee3416de2bc24fece81e40cbc82a2d3ef5c879b9a1b3a99721c72433afe33f4c2497bdd39bdce6e235744c737e1ebf01d3e6408ab7cf81cf2ed82cff4be5d15c886f6619f3e80f88cdfae0b438fcfeb7bf5c3e77682cf8e473b54223bdedf348ae1db7f5835c4045dfbfc31f2c237a481781e5021ec3dde07b605761ef61fec27784b857dd86d88ceeee39b28a0be8932e1ebf1f1f8f68a0a61dff17e5021ec0b6767d128117ef61d0fa240061744b1b40239b020a3c501948a40032bae70424c1a42ec41bc3239bb0278a56ed2e19c8e3de2d977fcf0aa75b87226d7117d4dd13766bf615408fbe42e6ee8866bfbe1157cf61786f02aea7439261318715cb9c11180b094c6107c8d11485c397344112de80db36258c57d0b66091d84428c830dacfac1f23386081e84a9c10e949cf881c2c1620c1a4a5520b1244687d084155a24f1850d1c255033569ca104a78a1928097103abd8e49ba779504d1f5211f84751dee447e09b4e5833bf79fa5a7f5b8655db62150e36b4a22c155e5df96195d60a6b6bc1045521021246e0860470c81881b58412644ce981d252173037246184379e88620d29e44040126914e1cb0872500432cade3e4d30e1fb9b26b06b99a77fb51a4885e62eadf4e7a6f1ec721bda30ac62ee9508f6fa270c7939c1334f7aab2db463dee54115d2bead068a3d69f0587ae6be7dae99e71398d957ccb7603e4845c7ec37f06aee057835b5a503afe68f0dbcdae75da38499cdcf9f67fee1554b815858e0484205282893051c6e08b1af1905da82c00512843082244c510421f65d52205609daf801992b5eba98a30c21f64da340444f4851831860a9e28c2f8458a9877bd0f011dd940f3b253193b93c0d67d0bcb656b006fa0f41bd58e62a3d94b1eb50ee2ad594bf3309fe99876e865bac82be54a820dca26aabd5df447d1241bc04a3d034087441f681aefb291539a49f2a9176ed6e96dea62e44c7bc2a52884a1517a252a513527561ee20c0d4f9e89c1620faa6d141940a619e0ab5a78b4687002b2e747d7193becc467043845f40099941803c94eed979e8a52b6ca59dee289175f555182084d0a1b3077db505e8cb15e1e7a13ed05f4041082ac8439f1d90875aeb8e36906a6bad210a13858f5e3828f22f5735900ded137d5369aa25d547d76c800ebfc9e37be988ff2c7ae81aebf269bf1d15d2a9aa3df33655b54b4f2a5455fbcec3df9e9fbfa328dc77830d5548be4ea0296b5d9001e351396464a04c346537ab06b28998aa0ae9642719b6e9eeeb724535c6d1c07abe30a99d54ee308a03ab3293a665507a2a3738d12f85ecd053214ce71ac60d6d51faeeeebe66dad52b935bd749ed3a4c88ddddf69eec376f1cddab06858231c6c8e97cdc2084fcf08aa84ea250dbca3590f2827a3af3984b18e375c5cb932a75af6518210ad4cc36ec356ec032178c900d8410c218e736bbbfd8b5c63ca46d869863dc10c69fc618a4e1a824cb94d5b12007a16388686624000212531440403820120d4704224d15561f14000ba0b45058a20ab424c86114858c21c810630021608c88008cd0d09800f40f0c638b469e26bd230e98288ddc9bc24d1fb3585e0cd9468b594011a94601b63a690eeb2e266a487c948927d4143bf522d2db796244f0bb6edaafebdcb440be312768b2983e671bb2b2b6a1d67edb566789620659ace33f194a8a921be0b1c38a594c0eafb24a5103c5552ee5fe9f06310a98333fa6a1177262cc1b519040bb1c6731153f5fc46af61179a5451c1837a7ca0c8593d90e2bf087aa533eb4363fa66362f0f10da10f87f75f1160d9e4ca69af7a64b731bb74da88abf0460bcbd9377eb8ff83ca2dcda3e1b185d77a722969f118698265c58aea1d32816d7719fc1ec998568c540d347e4e5a04d2fd405169fc4d39a4a21b167a8034744bc485fe7d70445b58fa4ed54655f137dd0595a4e5465fa90730da2dd171047bd829d9341f1771f530aa60dfb2d44d2811bea6228e341d9734daba9f71bf0b800a4a3d1c016d5deb60fdcb6130e16df5b435fb9499a09ac060bc88abb06415564be5a6ffa2e577bd3b8689755b87f045c5c70d137c066b8647aeb9283e84ee489a9a471b8a250c169e2a6fa3cfe40ff80b3b675feb5ae24638e644914f6a2bad2ef78e1774e61daa4c4dcc95969e3518a67187faeab1a748b7850aeb0201676f8e86351388171f08ef32d05af18896e0a5d471dd1f112e7b4833d98069953ec3a5a4925356291095aa4bf134f487c5ec53dc7e24e1a8c095028405cbf0767ff21da01d9a541daa18afb1644ffc3027bf82108c58f4b5607cc97ff6135172d1a585a5074405ba64be41476bac379a7d92fcb6d60f289c002c62a82bdde4f97e78a462f0ea2014d366ee64ab4315ee966fc2ca87c700d9623873512e2c72522f8afadffb712ed3f04660fb02ff4e3c5a620982585d1c70250cdbbc77d574bf61334475db3b278706bfc0847e4fa2a2d24b500e76bcd596c492da6537026843d30525e3a2d55bd6e1bd4477a18de7b924ac6c15bf115ba1e2d4b33fc8b48bb842119ff2b38505a09cb7e338daff0a755ea194ccce3f2a2fa255ecb271ac591fe7dd55100dc14a9b0dca02a45beb3bbb95ae7753e75c7b5cb1078ec8917bcda6440f9457fbfbbd5edff7600e85b29fd3d0fb53fde9cc6ee8f9a49f3ec0dfb0a0255f8cd38eaf5a0b36fc818392988f828a7b104ed184f60101974c611bcc54eb22106f459c2b626815dd2a5895ac6ae3e11183856aee51194bcfd0270fd6a0abc266f7d6f1edc17b80af3b2dd9510931e4e6ae48aafa931af5fa228c244870597ae848c097843491a5d950321e7e0ec55271bd933f836ba0092545bae57aff489146b87f8e928af8f57bbea36c5a85d2234fc9e1db4e1829deda15cb2b1948b01e5e9323114525401f27be01a37813612cad467c836aa91e1602acaef3f93854ee459d89ddcab95e71c3444654ea0c1631c4da25e69474c88a03ffc42cb2998d468160408b1b6456b46c284dd586abf3f9542e56538356f6417c137c8e1dc92a3262d7ec7c9dfa5fc79a61e5e33e0dacc1def9e34134795f7cd1d54c18682821ac55ee7f519a2c7e4a2dafb18b23908197861b54e66687afecfdc4c0609b00f347829b45b1b8b4f2c7de0ca777f05c60eda0e7af9d17767512c19873024adfe3a251aa7dffc2d1a8d0184378f0416430c20b3675e165614495b2b6df9a4b3f16eff2a6b2b239329bb755701b8aec423e074d96e4c5020df3fd2a44d56ebe571c3aa34ceb8975d20395ae73bcae9c7f538f74fcfe0c6ce63bc23e5ecdc9d3a5e322bb77b6f5ec4761f709dc8a4559c4743a904d387cde1e2b4225bb71c40f08730ecfcfe43b8dd4440220d0d854bc1edea2f4de56e31a91a6abf65660d0fd89db123f5dc6ddf1e122785bbcf4df050537b61368815d8ca95fd36cc8d699c0a75d60a2cae47ab6e97850ef906824217c4e51dd2aedba280f8b83130947bcbeb050b8d68bb4500bba08a70ef4701a68b61cbc758c14f7c1a734ed12319fc872a26ef6697c22e841a761d643640b0f4935f199d3788f6328e7d92d8ad3c19c7b2856a740d52398a86c2bc161668a0a8c52000c159ea9dfee555d7175c371aee093bee505ab91d238cb8a7c4ea1f12c7306674349e6df8721805b4a1b732ad9b0dd4a0256231c528b3d258e0c3ed4e4b6dcda8d65183c87d2107db38ac28bf52b9aad0ca78dd9046d0cc6be3a3596fc4698589a5c317ed7c79b406bb469abdc0f159c01dc75dc4ffaa87c5f436f98c73c000a724d82a8d44518ed25cf930484e7eaf0e601df602487fe7a85b4a735d84b80e4b411c2187359f45fc3344c382bfea7e4c8a076b7298610baa97b24d50173d6ee222a8c032d3a1135425ac553c29739c8207869a5bbadcf2388f63f11b0c45cb970a23c14472453605c378c015554df9037ad7997d38f4f753cedc4adb6d6a65cd4eb0fecf4884ef1f4502ca554505325f56c39f3299d1ad4e378b622def40342d38dea50266bf274a943374df4860eb27362daef651775c933d2f57cf886c964ab42a19973cd87b225ec72ceca6ee3daf93d274b39a91f65c19828d123af5c45029273ddc1ed2811d90ca0cdcf23b0f13dfc1215d56c83b6cd61ea0456a8ba9d44733a791a2146e90282190e16c26d4523aed68506ee07ce3d121fe4913358145fc2f288b32c3e1ddf01ff1f3e2e1730a4b3864691e36a33e541749ffb09fba0e94aea336990a3cbb20eb138eee6da10e90ef51a27f30380e0229450f6e3d4061e75726c6b70235307332542acac56db381498ab8b3f0b773858f88cf392ea1e6b9e25a00dab566f3adc54fc883b43acf6e6a33050543d5220856b07b3d737d839d3af542d0e231177716fff1c3edff133835bc7fe2167a0a0ba158f08d73488608acac4a1ec51762f3c6731493e98a067eb193795ce221ee9455eda27aa2ab2548a390df17121a3e411740de0a55d6d147a2be6048cb17900bf0d30df0ac26125c59c05dbad5a37d0e5f758ca55ff294ce936178093a36df7a69cf629ecbd2ae658b2785baade697b94a201c229b12d76776e9544cbcfafbee214eb120b5b4c297085f5260c0f122371f4ea8c5d19192318a8150dc4198fe2af6c28581dd684c5bc4579c0087a129ecb663fc420efdc5c4022fa90e52c559cfd17743cc757b551b6516e3d97c8d099ce31eb815c0c0d6c5c7d0b7a641679132e784b912c0de4a4b3385fa3601574012c9be558602218950c26ae0bb51a08606624df26eeff7ba6a852e35e4f35a996956dbfd47fba0e3131fdd4eb419872a47d23ba5bfc98880a053aaa2d21b14e8fe6789e924003f08b0b6b229812a4e3c03a86e4cc82c56a76b05bf7d4e550c167cc010ce3d87eacb1b043d69b3978b657c732f777415deff1fd357b19ae6b5a18b002d57c4ae58a4d1d462299723bc93fb3bc3b9194af91cfe8e0ea6a2bbf01795126a96ba69ea2980d6e88f0ec0242f2ddad280107392eafb77105625d3c65ced3e6027d589e720470170480721530eacec86708d977f356b361eb25ab94c69da7fbde79ae89f5c0900c6027c6713a20188248e9557f4df7560c8acb22cd792a8f50a7ba66b19c85fb858d46f5cb37faef58f495f49cb1589e96fa46fe2d0a21c79604b4dfc3c0aa4d3015583414d7e847d7de299a7a0d402450123cf9042376eb852953767d360e578bf8742e24efb0c11a9be2a281b4bb5c745e9371336bad82854dbfb08a7bb31cec4602e6d46f889447e620182f0d748a113023661a506c5a8d8905152054315a337a42b5724b105a6081fe0c0e16b0a6e6af3a7721600cc90562ae812655cf20bf5d4e65003a9689b0564b43b52b312d73f9eeac5a34f3b81c1c62c1981a8d141e98998f23f68db71f102a921d937522eab846e522c4849d6d2ffcc3ea4dc7a9e24041b6aa111c0e3843b812007661a4df9a362033433842e382d0e218462aaf5d8ef4f2a40a5383506b34346ffe639a79d9b5bac57f6fb47167382f88a3a14dd8b5d940ebc3ded973d11a7e59f9a03385efdecf6c7f421795c4dadf011c1412db5f9db0ae979a7c3130696b23181c0688335058c2ae8003555766c888bfc6036202039a531c8d93960fa110f3ab33b37045a0b1f41074ae5e5cee115dc3f9f1beaf7d083dbee45328e8bc891dc9151d5310c7dfbea6621a3ebb6a71015c8a1b382cffa015b6d78d8b610f544d51bf1a45b9e107dac18fdb84a65c98fac414411d84bc3b2016f31f4220eb009ccd3f2c811f34775c0bacc852cc2aab06a993aac74b3e04087c0aeea7b274c5f9b6fb356435f726ece2ffd97563457bad56ca3a844419b76bf7743709242156257ebfe8e1f505d207798d6542652d698eaaa4c00d3b93a37ef99ed522b465fc11f675a353f7f5c054d47e11f4c4a061e4c57597a636bb1cb788a62a67ec6130e0f23a7bf10dfd9bda1801cb87373dd7cc81c915984dd991000c90e870fc4992543f8470bde6a401e9461d5207e11a2c2e0abb6c0e2eb34fb5c6bf555fb78b7e78fcd68a331bf85662367dfb4a6d7118696797ae71df5947fcf9179128573fc1a6a2d43b611669ed92fcd32c2f9053f5675b83c35b38483c8d75ad268a91300ef6108c89a6b27c0d920f3434058f7d90d514d15165ee95175f19d89b10aa2617da63d97a555058659864533393a6c0c79020095843281cf38c51d95f67d84f0a032b0cf3b1e9910b9fe4fba608237e3a3fedc5b139f80e53077ca3b1a1ec02c5fb6a6efb68ebc4905e9cfcc8fd31d6aee3d94fd29ae397175980451a1e5f51f6dfb8619b6e2c7664d3bd7fc94777a3ea9e46e09f1659ef89e960a9a205b8aaa4829151b7585d43e09beaef1489ba410e14fdcc87506745a358f3fcd4435e3037c1f007da92b03162669b1f1b1678460bca4968b755d44e73fd38d65a2dfaf632207ad84f4d8f0f35eb1b2d5239e98d5520d041a466fcd2713ed0d831b3b6b8d1526f62ae8dd6b0d5eb5d53f138948a7cbf834c3abd7b35640dceffea2df6660c0b4ac469dd60c6ca8f8251dfb61b1399da5a4de0b3102e807dff9cdab5c946dbdbefeb632d54206fa088c1cb33829411ebc24e4cec3f8f99865e880e3ab5fb00244799324929cc75dc0c39da0f1d302f1dac94ce082555854950b541bea897248697aa9cba58cbce16b55de492223c18f3a1221cf5b8289bf92e07eed09a533b9e3ffa0859f840f7e78352af87df808f7b872e82231d90b52978ff3747babac1a6a0c3937b479f68f9cf9cb8ee973f2fee1d9d47888521871b64cebd3a2c9b2e316f703c37ea6b959668a822fbbf072cf1e872d3fb9c3a6bebf4726d8256b216f4c210a129dffa8cc5e2634f54b3069cee1342c8c9a6d16e4f1cb74690d599d028d653b5af8a0ce8eeab66049cd6c7579da2557832abe30126336c0a9028608380d13e39172bf50964f260d2e9cafc67e105af6a68994fb1bf15aad540258b72fad748cc33b89786bef5653dc57179002e6af70b3d64d5899bb6fc1b709b4a21ee3d5d8536139f89bf703aa5adaf01a5dfee4738215ff5ab7fbe1fabd29c4a259d2bf57d0300013c800a1f2ee274421a7d4a21afdf44aaf1e317949223e38d2caa07d70ddd21d62ac3c7fd79d63fee566e23baaa75825527702ff9f0e895452f3225f2aad03d748d4e88a594ef64a05bf66e6678a24bc388039fdfcef666bc1431d047702b92f2d7a94ae4547706ba12296ea6152b82aa91482224e6b3aabf631573540fd2d6c35cc1dcd9e329ed41e9c444ea71cf2ccb3b7402d4b6646e355008b2eefcca9843bc5fc39308b259a2e1cf16b0be47b67169c1c5ccd2490a3a3ff7d59415df9735e0b7e2f711f9d0a7d14b567c40499432e598392a9c48d6c4f4cc2e2c82eb790541af7cf11589008f4169cc213c549b39a0beb8d262c4f0c201ee748dc10844d04e780d7392731f37e8600de71aef921a713a6930d4a682622d78c6348c78850c1bf20b75e1d2b7277b972006747d9b8e247b98348d19203ba9c55c2ec4d42a631aab15406f16c19ea3438c4b47ceec7350e402b36a2deb17f592e852d14f41f49ee60094f52696aff93105d5a1490295de9de82c96889b247b12e2a324c265309538b48e7b9f7ff9bb10c58d42b7960ec3287fc085af0ccf47ff7c0c7d699ac099f0f52ef12fbc3a1ed408aeed72ba7c1d24798c2688b14e6b1a8e433a67684657e695db23400293bc31be002f3f8423d95c7d653de15285cda8f031e8a56d9fede084d21f27b6d4c3ef7b026701b92d82472c6bae8c08832b09ae7a553272f470bc36ede1fdeb5d70b7b98df88eea7beee4fe7546619036d5c7c9a8bba8ff606930b1db53f61eb245d04c30d41878ea73889166398133037ed099fa2159910cedf6484179f9c681a90dd290d870f82d6daa4e17348422e519bfab74d4164c91990143402b1be2dc8df6592a78417528a611b55093aab15a2255c1672d7e917294e7707ba406dd044fd9de14bbe389b34c0ee5df491364b6aa6788247886bd981efec9975dc5f2e39945cb4bad367dba333efc56521f57bfbeefa1c1c4ca3b112c12012e73ae65608c3a17f12fc5289be6a0b40f865c1c85dae78bdc9a55a944891bd9d9591645df6aae48f1fedb4870fabd63bd66009315cd9e42bc95e5d65300d79a35821576cc8dc758498593400daa4ed2497288bf7bc8303c1d8e69066a46766ee19dc710ccf7c638aeccd617260c3701fb120dd207c5102c54def068b6c12accef365f6b7fdca8300ca308382eb5f124ccf340d58f543fbeece6ef0fc027a3e56925d3d96660bbd26aa825b492d3dbef7ad853e88c08669698400961687940f07146c95fbbbb3c27f9de6530843d8d163e541323a07eab94aa3f9f3fe2809baf19d268fa42b5d82ff0071fe6253bf9f0f89751067e64664d88879d183820600ff63640a25c5b2849b1a722d521e2f8603c6ad60b3eeead78219f8ff207dd85141088407f9fcdf48bbfeb2e9baee6315c851f041f4c9a08f0548cb839cf3714efbd0f6530ab08659776f1840ad38485132700ddc00f15cf3859eaffc567019fb880dccd8971006dbd1d9580846ef70a459ffde443e858a416b8b99068511d0f2d15494d43ab2ac9d0e918d27c74f7bd420f0986bddf2b19b10d3702d161f55c326e9b0f77638b72dd62b938b66b21078f727af3ae55e96035c6ee3f98059ce29a4d5b7a96fce8b9e892874f5382c4d88cdeb05aaea8c5d06284d8229616abc456629458524c29568b25c410b15a4c25e690deefe3e5ba99fd0f87eb32fb9f1ef7cbe47d5eae3753ff69b857e607fa6bdba257985fe4ab3f5dae9fd9f978dc3fb3f7f1b8bfccefc7e5f233359f167716272321c8e5871dcfa66cd82d5460d9ce09094b58a13425bf5aa04babd977b52475d88630d8ce652a9bc2ca2f0559b3b000a07668cd29df36bc5aec40c8dabdcc5346386b968ecc95720028263ac9bf122e5d3bc619c890bbd31eb130c7cb919293c1549552140eae8b9d6d02439878c10b7ab39542989b89b989bb73b66b11c09877657c6a58fb78d86dd3376340e104c23b906159834671b87860235ab8a3d4c5a18a4ac4c9288f73e8db446a487301b6836b8d0c4c10e01ccfc8606947eb05baba540107429f50f249d2b5f33c5867498215db707c21455f789ae760cbe8f3dc55d1d34b4c0046bdafb94bb78c64dbb8bfa625ec9ab6c0ac3a840718d98b0cb210ec5bf38c794e85933f950c9a7c9ed5f2ccefadaaad0f66730ffdf6280a40caa5f73b5a16c89c90b48ca04c1c10a1113220dc44d7f1f90b2224b779908a52245d7cb224d1e42a7cb5e6d5531bab1b477630e73ca900516d8e53f9a454cb0b4eac398af34f3740007020a821d52536c2217d3d7cf5b7cd1bce86a1372aa6e560068a93dbe816a3427a4ad6182acccae113299e78b038a13b44532aee7c9c7e102241839cd98f9735f319dfb8f4e108943ca884af8d134889d97ebcf9c76f5913be1b61eae0a6cd6529d79758bb3777763ada2e251858d2a4cf84fe5465d77c9b803cb705f9bfb46671d9c056083c366a8e8107db4af8a714af5f0f9c3c04095b3d1b801c7a7e8f11d71019df15951be6e9bf685eeeb2abc8839782261ebc1c5f2654b5dbbd61e8c01f8ccc0d345e1ce4e695d31d12c683d5b54f39850f41a932fa7bfe29d25eafe6a4effe70cdb86057b32f1a9c18fa7fd68725de60c3c8853338fe53913586e4b6d9ff5c589aa8d590f5fc4e6a2b76f1467f82d825a857d5c945df0c1c76434bcd6c2fa0730529a3ae71bec3f93661a8fd9db88f570663058c0268dfd432c83b7b88eeed3ffa7f41a1a2568560a079464269d0e8460b3fbbb8fe4f290210e0d3cc33ae14f05c4404b09d478cee03335b65d6a1faa5e03b9f3b9336e2bdef658efd73e23d0525bdefbfb3d75ca7dd305e90317a692612deb4f5c5bf471db4798eafbf78a56ba24e24f18a542c9aa3982295574a4c547aad9b4a7ab5ee87f81fae3e1d1cc0a026e1e298e80e592343817d6ff77bd6104be681f56eed27d624ae0c07c27b81fc2eed2bd622a64c06f2bbd967ac2e310422a3f2b20f1023c54f5393f8c7b93144370119e80010f7d31c0c7d6ab046b54c88c29b009470688d3b9cc61d56630d8c29c5018d7c288d3b50515ea25ef74334eec0a5116f812d16251fd853ae4f4ccbb84633762709d18f4b2d386a8aef427885a507536f02142613bac697bc2f6951e79da28287499a0486207d3ebb94b7be2b54e521e618f81888bf4cba72f5e2b7c414fec27210620d73ea78e2b9c7607becec99a79242a7eb37de320ff046f8282e4e9e93e75497113bfd8fe9f6e1a1a5929aff41682c7a4b53864f62f17540bbdde0be30e274b79ae4c965ad5e212cd400373ece183160c8d03823d412b04b2b75730ef988ad4e139d247613945386c2cd23c2d7bdc8f20b91b1137dddf00e882689f3a5bf69ad473fc8051e48e26e65efb0f0720a3588681f05cd6e143e32b013b8cfff3ad890719c694391095dcad567c8356e43e7af35a6e001f67bbef8409dfa85f412036f7c7f64213add10bf60d90920e45ad72f93dc2a4f6dd710fc698f6d0d362e06cd21db980f895879e008f520ffcafff913fec6dff933fe36fe7159016239a45a621ada66f9e77d330cacc76e7ac5d43a97c50dd22f2e2814747ce4a73698dad8892f9e86aba6bd43a56e883cdafbc469db6bea9c54bdae7546d66668d23ba9665df28ca87da2a9f9ab7a5de517a9e5ae49e35085bbc81749eb54d3f76e4063eb6e6f215db74bcfb2c66c8e5f435de916cf510d2fb8bcc6bbea96759641b5640e98ab64c965582a53c09c0fd93f99eac7f6ce8b526a33e1fedbc5843366f86c2ca74a0a33244e75421c7498bbea84049ca26533b765c860008b814ba7f05d5e67d8786c970b1ac3c7bff9cc60f38a8c7750a81271d9c8073bde2d71983ad9e818653574e8991d4b4d1e841b21d8116b9920a253751b96bd6e14877d8d871b5b4f35946c99dc026a41de58830f2381b918b6ad59b7483ce08959bdc3e83217d6388be12617fcffbb49f3dc9e0319965ec8c5098eec8547f77f5f620721500fbf80943c8553ce0049fef9b84ceafa23f823e03f0af29496eb740fec981ab42657df7cdedaefe26160e549e93420a9171a8d44909457caa6c51ead49645b3db764fd900444028f4b00b1d3ecbb803328e1a5f456dce63507ec11ebe45d9349c08806979c4ef00d23d483686bdc23bde33e2706b23e4be3d0e59ac19b83106316040a2f370159404bd2757cb6e62262856df72ce0e16226797f1c40aa81fcd34319a5a048e8855aad0d5b69e73d16087d18f49b257664b5c456d0cb1eb0f03603edc3cfea642a15a04c65b04647c5b1216faaca3755fa9ee4a9148439585556b7d9ff6f83e83218259cec91e9f9306c1a52c3520e429c0ed70bd509e6ead23306e2979591b92cbfc53b001ed7743b437d84634a3d26ee3a066d7e3b0148b4b199f2689e9f54bfe712b415901113899a9ee4713b13b370d8f46dc729c13e5d91e4a948356ab587429b53407d01fbffe1427ab945ef85cc4a74aeb8d1fcfd3a750bcbe6b645f1c16eaa960499aeb1f58177ae23ecd4070293a15a1b8a9931172df8dc589bb7696a438c3e942c7886a44a2de269f40e04ed4f38716af0536fc64aca773742d59f3c1b7dd544226e4000cd20eb8790a853a78dde80b8518bf223531dd016eda6d61595a57c228262104ed90720955f5429356c16c73a7a54c2aa6c5dd565ae13b1c5d092c470e81519e92de0609d63f82b597533c95cae84db26a6c15ded7c5229b18a1a931ca377635311a6947b700b43e19910c28e01fdbe0ce0e13d8b4f78cdcf4348a3d824ba9811257687758285244ed9cd6b4f1bafd8d7c2e1b7460270dc84a94eda4034b9b945f67cd3198eeae8f576744b040b1b6123e6f15ed7d625b5291975ab9575611aee5e3461f76f23b0a781327a33f141d9ac9b81f98b487bfa7abb2a8873e325198ef562d03c77332c5ea20fb21a74186741ca115d072281223183637e7f09fc94fd2e5c28a32d8b266252ee3297ec406e3833575f1d231b05e349a7714f421b3244520e574ab96c7c524b8dbd4b6ac7505c036affd33acc4c27787c278f4e6dc3993a8a9a9337b049605d384153c3a95e93196ca21b226924a938955d72fa96eb6b7495948b9a05705e2a6564bc8ed0d8d7bd52b53f892a4b4ac2218aebd64aac37c93913db16aea16397ff96fa8d155b32f0b1a1fc14b67f9d6e2143b7fc6542ce02f1427183451606927ad3abe70a43459b88b8ec4476c15b73d7238dcc0f8a1d55fbc6b0ad01c6cd6aa331592d62dd87dff5a452a33e4986f9e30dabc64f1a086fb5b645cbf519f639dae4bb4683c737b8ef81b215805559ac9a14eb4f03c8d3f23fdde53fc0ce8bc0f3c92c10a0d5f65e33228ea54c57435738e7a334e2822982b300ccb6987c114d9ceab047696276a99ce490270a6719eda079c51f17b65ffffac3c64450e21f8a8646735d84ac96efe525f8b5f42b94c83dd426f0e0cb1b4b701234d675df815a61f0258158c7df13530a2e04f93b4012b589499607d422f8c2eb33a05333fd9bffe1bde787ef52b383b540548f6a604d038694773709b05586bfb624ddb143d5eb0d6c00d8e7b35f6ac53139f20465589f01abc59bbbc0c459ef1450c8ef5cf421264ed6d4fe6c7e4348a7c8c95642baa13b2018fad9e98fe31db79ac5cef0b07b0e7dbda5f3d68802ca986a91ff60af882e10080722968c88729aabbd4809c34624359c80e92548c5d2dd2e6f19f4643cf4e0ac5a5a4e404843ed8191ba7221cc2b4d9d32f2eaf6c1f2d34b4eb12b588de8abd0d8a08cdf80f9ecf2e7af37373985506c837f2adbf579ca75cb1f79d5605c3ae4305460842984830ec95cbbac1839fa518c3643396a968afbed590b74c4a86b22e5b80b70f63589dbd1d61b142cecc7c8ac19c00b2dab60bd10df68a55b0116e95ffdccd45eb408daf1687fdb639d85e8739370df5823e7540a6bc570078dd72cf524e5ed3e137024f12a9b7dcf4b9ff7e0ddba0cdbb719a157becb628c9be2f79bc819107378ab9fbc6307e0e9f5dff1a8d85878a35b3cdd9617ab2d4dccf9f08f4f028f0ba9b25eec06701feddb049f77499cdb444ec518726a88df71175361c5bc24ef9b4f42ba802d7d95e863baebe0771af51c03992447918cca3fea3f673b66170c40c71042c8a853764018116ed540a5dad84f8e917b49609d7443ae6e7b8152596a6c9a6b192fb87e86aa1abbd65ebb9d140672747f466dd96caea25af4ee57473a619e21c74ed417bc3f80d45d2449bdcf018831bee489bc37827a3784ad5d3874605dc9e7a4744a2a8fcbcfef564cf51ca443c34380c10c27d698b827ba6222f64c483cd109f2f53b45e581f65437b63b2c0dbbfc152afff6fe2471e0ae45345d9bd05814a6a6bbbcb49c3d3fefef928669d1d8669297faa7ec24031ace190d22bd02b4e49810e6ef19ec7d1b3590dff463c137224d286bfb066b0bc02e6b2d6eb4854b9a59c6e83bad00b373a9ba4c54f5309557e367c3c1a64a122a672650ce44fad49dd7da68578527635c9fe2c75d5153e0e8e8797adcd2311c499b1934344e917d5bf54db9608c78187aaf74e0b0a674a9bdf14b3ab3a8fb73816df44d6a792f7706964cbf1a8e49872ca78cd4c3128eeffe99f07d509ab231b9f4912692e986e3a91c73217a5985b338c35eaae25677ec2ad662a076e41ea3455d178fa6d6d73786bd776b807c8cef0e641b7f6a6e72b33bdb8efd94e37f88d3a5edcd86f05c80d65d7578054f08a3904abaf13df4860b601d7fa70e3c2d326f3e5f13ef7aa37578a75fdcff69ca11b7d2dbe688cef4a7558907e948df5f3cd2f6a8c4c0489b7f8f164461ee7a25f8266276b86822ec0b87686a36ba831b4c6a8feac94a049b1be25a4f34291992459bde01600719c7d0a677e0eb24a18d54ee4991eb240ebbb5d9afb6554d272c4ee9a1c400669b8a527150e2ea068218f879ff0dfb40ad0d9a50e2c401e51ca7458a7ae08a69d0292c896bb49695e048959480d4b997637d1624134250954dbe5ee833d51faa04d561408ae7c65bd91d7ddc9d5db911261c3bbf7863a0566dc534bec8114e932fe25a4569848a3872bd68aed8006e4f6bbf76bca4d1897e74de0371f966430ca4f7be1b6fbeb81736e5776dc7fa6cdc927456ddc9f462e38880d3115726c403803e704eb58104fb1350ef54f495ef40c81a2769bb2b24f5d209a4fcd6bbdc3f05dfd3fc0e5707ab4dc03d74ae6873952191cdbb66d9aeff820b5fcbced5df5d8b06e2fa0e127ffabe46d03a71ae5e0ff242ee01c2d222a5f5a4e8a10312a93cb0ba491fa0f46260a4d4abec19674b4cf57e4272b4a9a6d5e45160495c2dbddbb5c87ebba2bc8c7ae91081d5035a38c9a8eb506ba8b30689ba7bba37b1911746f2bd4eb93b075a2a64d50d8add07ca3153ce3cdc02439d90116f8e7f7a914113073c109c42852a212adc8de2d955122f3f19b43f6a72456e7ca83ac21cdffe6b14198e3f92aca325f43b3ef2286e412c2694f4761a078265291018c3f0214137be14183cbb91300ccdbca75eaabb0ad50b5c561d1979db92b56d0fbdf4e175c2493e9f485ed74eb20dcd4301c76da4c9f98f6542bcabd06b3daee9c293fc2868af4cc309d5b6801030b4de3dca1b1d825492b49e6ced0a17c9d0f9b7324cca60f949f13b83e32d5b45dc3b29122c987f31fc6789facbb689a9684cef4680acc2a4f60f3d9e9d845b314ff0ba220135737d1bf037a3988997c481c19dcbd43f3eadfad23ed75749c505faa8e37d423eaa21217d0f48d0338fd2bec95ae346e9768e736704180b489b79ea383d31f695151039d06e3844a8e413852e889ebb57163ef292ea9995d1644181e021a743a7dfbcd35e339667fef0719c06327a2e75a90c1de28dc2551f69dbc861219cbd5437e4888bcc1c7ad1c87202f0859e234b9640d1144bce3647bd2ab2449c9e4cac9dc3ff15cb89abc150808805b8b6265046a64a280bfca17f34685109cc49b6cda27b29c49e5b374d727eb241709f3ca363a1795c49df012f056b68fb6069626416a8bab9a637defaa4c0e25bce48a367eafb8152b473f02b5546d7705cb61ca75579db5a8327ebba1d746273a32de8d2883c72ff960a5ec940cfd12c1c1087117a3d836cb7e4102d2e1127c4862227f28d26e641aacb905b076dab950586e867e7451596d29feca6167c56acdeb3c2df97756d21279774b74bd6046f6ecef31076889483ccc8d2af7fdc29bab05a5a9395ad74c62a788407d04f36df4dc5b7e0ae2ce3ca534e891133ff12508a7424f440eb1057730df69ecf8ea7259708fcce218e7bc2a67805b45e96cf72978c262d1b273a00d32258c49bd6eacf92f42316aa7c42b3660ac25bea765e08d5cf000e51b1ca3fb205d014cd966a0f06a0c4cbe7860f4152c099cd0a9ea47be8be2b93abd5fe2802c61239ce057b462ad4e6bd0f717623d7a2f9af7703cccb5d3955ba286d47746548e1fd552ae52338eebb8f0983cebd1c95511012d3fcc75934b6f0cb3197fbe0527f1f728ffeab2a2c881f30b40bde3e5a8f06af886ce269f9b8d50253aa5699a6eac1e1c3b71b144897527852921b422c3dc29477491dba2659c9e1011def6ea92529fe90c405af60b6203c44c893214c32c004e156e1c14f01295e49a9267c5db29f499e2d246f41c0fabbb8b2fce1e67c0aa9e08e76542cc6c9e264e24c5971fd55aea756f625122083ab4dc93e8a6b30a90dab7f0d2dbaf304ef7690206e30c01be64436ae2961c0cec50431a5efbb20a4c6bd9d1d6161d2463644b8d506c7fc45ee322ce4f18f52576bb71cfc70b19b701ba9a63542d85f8adf4e90102f23c98228c3228188a0f0284a9aa2c40b3d0f413bf4c8a043a3921b84a29e2d85798b7b3a083c3bd9ee8ff8a4473c49905039775a5a480f47f5b39463037e4c2a8824b5e3d1d4b34dfd8475af5d046484b56aa7a641bd12a08903447691cb5cb23c3f2b158779266bd67843d3edcdc7803f108a4b299d2e7b7676b4b886c89c98f733e7480a1464b58f64be06e4b093746c94bce0e677a2c411081260718290990e6a254b6561d3f1649954302061a25f220614493716ee9166b981fae1ec96b91ecc7c30026fab561613ec247dc300767d5f37ede27cbd7a68d75b4440342b4c2e21dd79c025ad662278ac1b161af115073cbd6f91f53646aa81a88dfef452a1a6e2750082dd89b000d2146086b894d714c73fe3bf00d0088eb090c146736221bcd501ad958766567051d211c6b3d7964ce7d3a8ee0e59087060056ce22dc16eb3fc50c7264ec881754a5bbb26711bd4fc3911020885823a00fba84a6095ff9a95c52559b7919bf277402ad70cb690345e3de94199a5c227ab182b02829d7a842fba006db1db8a7e923f232ef3a3cbf8fb2cdcd811fbdb5f66a4a37078a810edcf67d8a0f2fc4109c3e881d6dce057b82f8cbb53252d733c270d1c9d44ca91815df377f75d02a937b25dc4439a6faca5b5253fe07cc7131808d76cdcf659809546c3e75a30b56a7c094632401d08cc87fc9350d6475c19086c88285885754c05444e5ee1e5e78e745d6f310d30494d5744140c8280902cf041b0aeebb9ee10d935e8842987e32551b68fceb919b3ca1751885ae718c0442c0b07895c17bf921740c5adf18980ed136be6e0a4a005d3e0c68a566dfd36bdc1e723ce9fdbf73dbd4fd4568ea27b26204abe7149c39f1da578c7f64157e8d48227452b9c92f5b489ccc2125e079fd37ca23e4e2d6d88fc4c9fa88b648dff32864b2d524366fd5f1139e016b300f95bf07b0eff2b864acf83bf7c6c716c658c9fef920e3a450cd09f3543f48ac25f36ea288aa67d6f131e6e5bc47f1e49793afb26db171802eaee8f64c03dc5c4d52de00481c05ac5bf7e9668543128c7663e639d4ac16e141118d16da772c37b39f31d0c9845c9ce471a4be909dcad6c4ae5555bd255cc6fec4079683c9fefc232450feed3ebce2faa39dac45c849d16743f3d7d3eeeffeea8d62471989a6aac9342099f90b8ca833111bcecc44b31e17ea6a000f30e600193c43ccb0fbc7d9ea0d190a6941efaa1e85d264b55d8325e69988aa724492000c61993ef3b764c67aca8106aa1776ff5608e4d5921983793d507e18ec487d92fb53673a966b2ea9d54a894e207f6cba097ac96f0f5abb8eea71bb87d4d5c0ef5e3c232b9e7c423df326b5b15775d56a46e925cbe02762428d4ed6d89a62aec03527b7d9ae453ac732b80d1ae2ad62769fb27cf3c484cd875265d574a053d86b569c9d7e7d1c77c13f11a26c3a562961ae9e376eb7be9363d95742fadf843089b3d2e9849ca27fb7e2c39473afce0b1b11348527285fd350ce9b30eccae167560f2f80d3c5217910eb8e399b7ac008b36a22dd3e6fd0ec4a9c8f22049b5ca7cd576cf586f9d42f1bb55c9485a87e300e4b0ff683b40e165b5695da24e851755bc56004c01b2f62798817d985a7bc57276f9112ea9de06dc2abd705a58e133fad70eb106823f7d3e27bcc82dcbbca9131581c9d2f64dc7b223caee2937508ad9381a97bab59e8cb1aa800c2447e3220ed7f962f16bbabc21b668731fc50e0757003e3a37f8275b0aa121d3f0306d774aa017b81e5cbb8714c7afc0b8506c92b3beab8fa03704f416a1108d041b1851ad8d637c2d94feabe191b9322f32345f3538111e270d048d11c556539a028cc7fa8745ed495a83b7a83094fb3ee7c0a6d75c248c4a137a672f4e7826d404c8d0d922220f8efe16c73b21fde124461aaf11a3a0c91d5190bce3b244b8873c417cae45852226c4997692bc26a4a5efc80ba77b2474bfd4a1e3ee4828d4d5ab749ec6024d2d8982a1b54532194db093a62365772f10269a58c1ee994ef2e4227e430a2dd917f6ed541ba58bcfa45e234eef988f8a018d1d6687d3617647777f9a8da5b39b2b15f56a82f3387d3e7817a6e087608c7bcf8cce24983aac4cf4319036f99f1ed2239a351fb0d04597d14ef432a0b1552495eb1ae10e50c10f0a8865c99a035988d83cea2b768841121be26e068209b3a15363e11b9e2e5314f59ed46a8dc5687ca064b89c5d0b46dadda00b3e1c4b3faf85751bc89fbd45c1ec32990ca71ffcfdf4f2baf16826d646691d26c16cfe1c4ae2bc68bfb7f9e01babde0fd7c1a8617af0e8f510d5d8d209f319caf8c9dbf36e02d5fc038d05ed93b000716c2226229274082a3fb0506c1a3c06ee27ef69935f72d98a0dc926281a630062a1d337a4088d07c34ff4d004122549ed27d9df9869174bb29181456136b09a28fd5dc529b079ef698679891cec2c75d7773e9ac803113a59d2ea7bcd8214be581590fccd66b78c773b213e51af6c48fd26e824495298243444291c7ddf0ba92aee68fe97df73a1c83dc80c1eadcd57b0268eb3062945c9928fc4feb7ef1a05534ffdf8a6e58941fa26b406ab5b05969dc8c492a4e623a2e7054687ac4f45852b22eca37e2fc9716298c3ee707b6ed789d9e2ac8dae65b15d0f7fedc875c3d764114a945fa0d68bf058729b9420305859ecf544dd8e2ede3f89dadf7a5610027b3a4cc9285b85529338bba8f72207e7a64c1e7930628c4ba57da682935932600bd2a29ed23e770f65d6e998413dce62e3022e793ea38e71991b0a1663202371234533347eddb0c3cdc775459242377e8827ccdbceda28c29c008b52e62c0d7139ef8706ee322acf4a19abd7004af0567b80de4e7a4176d53d783d61d218ed093fea716f5499faf9aec6e9136683e0eecea19f3e56ddffe4a066178a676b17b036076d202cb71c66d3b5bc09c35a8e4a4bc41e7da2bc1569ffd8501de0545830c94818745b9125458e541f5af63be2b9a29dc6f110d58cfd2d73c46e3564a427f986c29e3bdd0ace0f800ceaefa764bf36da3b828fe94fcbaea10211222429ce42c9650934811902808332eaecc908f18948e3571898d4ab131bb63f9da35aa1b8e3f327af4787209b4940b923bc5ec40b92d01d716eeb4470b907e197ccc26581282cf608c5abe4c7d18f985453e063ddbb881c902da84b2cedd2101a93ac1fe40ae8156f07bfda353ed93b79fb34d74f583f5fa910459340113442121ebd6661c2ded3dd4461afb28f90ef129965e789b31d26a2ec20c43e95394418fe0fb8d6fa348177828407873e8f63c563f9517eb3e120ed79385cb966145742acc48d70ce44a90ba4d77171620a0e7376e03c2ed7ad5105c4062435be77601ab81083d6cf186b14d4fc02c4a4b79b07af7b922912e0807552375821cf6777a03c81af4cb974c3845aae9d26f77585e35dfca58214a0421cc4f16c4dce5de2593727c6203b9172f2206cf1f0aa68748f9cab3e82dd53c9976f592d13d6a63054c91c99370824116245d3551356ff92d912519904ce11cc07c8a724ed17a8570af8645bdb66896dfca684c7684184dd2ce12818fdfc8746c19929cba0bad00891944a8c8a38ebd46062773c5687c3f335564e9be1085293842f15685ffc990898aa1e2f39b0ac0cb201272a79eb1c27e6e22d8460f1e411bac61df4f3bd506c500060bc5f5edb6f3a460b112e948f9843684a946f1055899aefb88e012ea2791b4d203e4436f289f58a3a728ad58e77f06ac34c0ad8c74a9570839e6960ecca48fb8eb7f1981a1431432a328aa9b5c4b09d16fc0db8ef53e803f76dcfba0003815f60ca2016aa9832590e0a462a0a231b1308801f0890f7133c5e2b495213650ad4c60219f0d0554f84ba28c6bd3ca2ff1746dd558778901d7a3d9c0b7a32d2d7199b18b9111a06fabd2ec555851fe29d14d73ec1dce79941cfb1c4e54a05f90b3ffe050084c590f50a492d8d3c2f028823c4a7c1393dbb43803794150721c4e5de599b35d578ca73b3bf9926004a41423fc7fe70b9ccecc4d324759600e74dcb5d8bae1c7590e51c729e3a58872c1f9bc26b37808ec60a1e43aa713cdd325241e94ac490b0d3104b944d2163fe592ffbaab3006db118b21ad4b3a8c55a98569973bb815e3a4d9745a90ce81c84c7e1bdc5fd0d0b787412523535e6825f50c11db31c77e9dffafe11cfdb0a62f076ca08c004adbbdc86bd1103102a14cd17d79745c66e23535e038cea0fee7bb66a302c3bad5cbbbcb4016fee578c804fbaa5697cceb114a75014c806eb97c682a147efdca9f31a6973476144637a83faaeee95077873e7520165d341192feb85dfbcd54becfe4c44c3b8fe222f623c0611dab11bffbd1ed5bd1ecc247781423e0add3797f8c7c3ce008b702b5b6c364bc20faa6c138d1fbfe3ba8748ef5f5b366e96aa0fd1cd00ffe418212d8b9cdd8522f6a08cdb368595c10c49b2d39ca3be1059bee9e567defd8453727d2bf327a35825823b989c7d6b8065c7dc4d9b48201fca3c7776cffb09809a4a993f522d02e880ba737bc216a9370796cf4193e5057dade88b152ac189b6ec8f8485bfaa64b43879ba17f2bf3015d12308e30a2cbafa339504644d10f9948c437b3fa9c5ae3302b6e8c4ca289bb56d5a70c934a8a662d70fc411b02df4d43fb49ede293db5c8315380f330c3a1cc9c32ea3232e50c597fe64045eac81f1c60c6334f06794d0db0cbbf2194508a91bbc090cd329af8c62394a50dc80a674ff3928b11e0ff41d8d5fce2eaa7d31b29d82444df3cc4767452ba85aa376c965dcf31180b36a535a034faa06c1a29e690fa22eef2361da6a751e83c36ed562e6a66b3a4511a959490e1fc24900ce851a597a9b459536417455defaa11e218625457e853b5289bc40f5cd7cbb82a4791d08c548790d1e7f020ef80d7ed90f359577f42106d0ad8fc515a873ce5bbf9d859051784e9a038b8252901fb8babbb853e7ffcba9b9541156494ec04886476e542d43e365902dfefb44f474bf07c066a813bfb919111019d380b94d44f0af2830b4dc007148fbe471a2438112fd406c8698afb4f0e8c8ddb399ba626c317d75bdc6a2ee03c340f35f425c16dd614ea7a56a7c172afdb2b069d6b0f226b820a4560bed26ad953859e677d1ba02b20b8df2fb8000ac7e4e5520aca43a1101919f238e8cd1822261e74473cedc288ff904fddff6ebe38005c160d6f69ad53c13bd04e4f598752c3ce8f31b3a3abf13ac68a09c0967bfd1c9f33ff86130344f82832390644e9916ab61c49f81f2b5e7ebc812fece27858c3a7656a70debc51d2149a3f13408c2377b50aa392e15e6f7a8de440139f1c381fa170e26c111d7dc05b926dfb97617458aecb1a4fc56d2c46e9ea90872eb1741128ef55cc7671f2ec4fe8ea58ce52f8606f28efb2e16531f7789e7f09a31644cd635e2f40411cd45bc6ac05f88dd8dc4655de01443fb31333fd525a053b9b87f728b2aaf1a3dce1dcc76c58452a0649418e6b7d03ea1b09603794d8e3229884a90c651524c1cdb02652111929601e3c346ba2816fa866d08382d61ad09ba635bade0f9be85014d73b28b6ca0dbf641f289b0cfca23b5cdefd3876a6a55c5d94dbdb8a2a010d72b96fb27df6bf3b6c149e1a17c2f8d8757f1fda82f777644371ea32e2dcfbc83201da59e27afb9566206153351c9e2e002537444b045f1dfea3f5a804910a4cfe15e4bb3e76b60c627f5306f3f5db1df971c45c370c841b2b5ed8a2437814f57aafad6fa5ff120971f49862c9fe8757b216aa2b9b22492c4c9ad9c2bf5015ebfe6ad3bce67adc381142cf5d0a4d7ef68fe8c031a86d38e5ab4314e40f3b39fdbcc0412decf2af8cff66ec06eadc0e1d30a53472d580140a9e535e5eff866242a675ded0574aafbcde7931927833117bbc8c2a5e8976a1663160d5563fca60370238b7021edd93541ca478fda0a98ac594767355a1e5908263f61cb6b95a6c60ac6f92be98e3c44ff3b50ff6c8f1561b0598f9697a8857f776ab6349276de3a40183884cad79bbc7d85ec023484a5ea37b4a411e728ee5751c6e83c93e06fcd83b805fd603de55081518687b017231bf35e0fb97c784e574584fede3860d997a38e4049c0f709404611052a5d77c267587803ec9306818e8fa5ff793048a7cc60b8ec8c6212da4b200c1a9dd160f80cce51dfbff3a569245b0349ad3dfdce207e073f2a7f7f6411ee2f49b04372a8205703e96dd1df2b617271ac04dec5f768004ff68943772a3991776c71919f755e243b2c78ee6b7e701630dce508d47b8304d4cc0a027168131bc84a168cfbdd5181ee11a5112b5be66b952bdfeb88b8f94196adc57bf358b9f42b59dbcbbff08c07135f65a92467307cd8a321df1b074ee0a36da066c58dfc763abbf54d67e8cf92a425f9b32a33b4f42c07d8225237ac042da5fc7ab7ce06913aa704d1a4033c45dd3c6e580676c3047aab3f29ca6fa939be5c36e01690210f09ed583aac136004ddc7a043ce056735d89da36ac8e7b4bcf6e517d752d03f6ca77a9b4b7070eef5518a08944d2198e67cd3c9ae5026fe85e48bf6e88fd7c657d2ff78efea72a8dcfae4b4c3c377bd4cc5b51542225c0a788a41ca4fed4b9c2578929cb0bee6a45c1697a0f20001a611522b35b7928e1af7136e988c58653e57002c33ebc735b566dd5664fae29c4adbac2631862f603f0524d8d2f04557ac509249ac000edaba257c1d405fe1438d2aa6bac3a8b0604276730b125087f7203d844f4c8e0ea7278ccf584dc5aea99df689285145a1e348612e56f254df6bdc7312b27ca5c45e366f3e50135445787c0448a44de715079230878c6d00acc5e60f3aa50a4ec9f91efc3e72b1c0c44bc489ee37e578b6671fc0732990cfa6c4e838a84dca0ba9c95506d81b10566d0dceabe0aa498590d74211458e02e63cf6179871e38d0345a2d0b2410e8683b279c8cf1992088f2e3a7cbe3f2ef08a1c3329f8f7fd0d5ddec928a23d829e02ec8416e873ba4b9e6820df5c71c26e7645fb77d61a903df199b3c560040ecec79814b406dbf152abaa87a3803bb1dec7af19cdfa07458df478ce1d3842fe744720b3b777163c1726ef4defc58c93d93bff1b8c094f6d0637419271f6631817e536758318e1b51014a55614a6c7d6b65018082a1ab2973ef4530cf3b71e3075bdcef47d635008890cb52d9a4bd152d4271029496a39491cb123a3295bb361b58c3e3a58b75e391474946674f3714d6cbc5639bddfc0eb3e780eb60fcb41fc63e939d84b5fd981297409b643c9aca76ad1d53a5e7d09292eb42b37be2a5a51c5b2072d394b73692c1e40191fce536b565f9f89e22d593367ff385667a38b96dba2d4d21fece91246877892b58389d7315952b4d7615e628fe994efb80aaa9cdc03e545e3ea2b46baa4d25901f934e0c5a2202581262c22c8d3e87b87a1833ea34994483555297b5eec40c797b9c2a5ad53889fda7df48b111ded26fee88bc479aceb05abf450bb095f4f58417bb8b770c2538dc9c5e05ed0fe0d33f117dd2a12160ae0c9f58d89b8cd5890f06c6d8b19c4c2f0e0b160c2a936e993a9389a8bd3f305b885df364b75690309b066e251da413bed06d1ee6bd88020c11ac0041138d84a320f664d704756cf851b42527181e7a3d86fd977dc74c96fdb05b9eff17ea865f54195fe845fb32f67edf4f2013a1e71b04cf1943d0249bce9786fe3126814699d7960e0fcb9a42498baa5c7e7f46d72e24be8470581e321d22c29da6005cfde219560449aa565ddd7d8e753a4788d345cff0de655261d5b1f23f7dc50936e228970f92a4aada3509f81f9f42c4087b309318c3d478107c04e82fe64fa69f3a701930f5c0d9416afea1a1a00facfb58539464dda19434851b2e6a1a70f85983327797fc8790a1de2c681fa65a02caee539c2cf27e56c556b6b61dc086bf425b3ee30871f224cc1b038c3e15de9847cb6bcc36d4f11a7231772d51125febd86372ed7ba63e8423be2cdbfbd2b26f3c86cad78a87c59f0de614442cc717f28f34bd1c0b1c5fdca408f880bc16f25d7981785e14ef7a5ac34ad5eae715ee76c6f2ccab737a4a805a77d88b6c4ca5f42dc9082c82d53c29f5658bf66dd6d53b1f8966df883577b3fbe0839f000ae416e8b078765d414685fbb7d8dc0124120c3505cd4ae2a0931c7305fd9eafc8417e03add2d0c40cb3c40699f5092b62e4950bc50c1e69b07c7c004852868e1f1798915a25498bbe45c5295cc69220fe807009221a445878871f23a0bef8b00ad4e58ec245e976226ebc5f0c3d009bba44f1bcd6aa5e80f08584f955ad36500c09c077c59bcd938adcaef6f9ed306cfde4fbdbb0766f88840f8afd10629d1d8049d159b8c17a3c29e3730c5834b5be46c2405331e77c5f78bc62ab9392a85f9df3b0b5c610ca36a5e850e2b9b73d3ed04aeaaa78a73de1fdc2e3cda8985c603ab6a22bc424b93202a56ca6053dc1837ec0c85a435c5c80fa77d299f330d8947d54ed9ebdc5ffcaf088f3fffa48ebfe7d8fb011b53a414878961a59fc879096a54cf5fbb35e09d04b470af17b4374404b0b09e112f2e82fefa9dec0f68124bd7ae5eb04cc8f1cafd83ec7f711050eb413c6b87b7efa14640398be1f9290128e7c91092b923147d1b7264f7c38134b9723c1cdee335c391aa5bc6d678157bb6a82c88bfacf5ff0fd1a9f01586bb2327c385811c8de5ac7179f3f7e3e895472e3ff208eb864d004c0071001fcced8bb028af138d002d2a56265efb7d7f1c1321c4eca9cc9f33974483363869bbab46be806905a3a1b8d655af49fec8894130445d8e9ba8802ff0a99167d1de32657e5a75b4a73f69f02e9c6a7b24d2331a7e8ccc5c8455d066e3464fb717f8ab3f0dc54254593cc43ff262a43d16c321b2ac58d6c8831f38da10674308be5e2a93a9a11eb81d55a23c3abe7233fae1c3b1335557ba146861d39ae262c447ad2c6549abed5667851a0c0477537e36bbb9dac6d604b79e5797378015f34e22be96a0a9678542481fc5ad565e0af82843d17520452ea81970547f1d0745a927be2a4eafc9e23b303bf3fe1c2e03753b27565aa9ec6a85cca8537f79ca881db3ef3dbe22b2ef205dcf8b242834c13f714777f3545acb020690ddd6fe9d15e64745ebde4fd6f21d0289599540bcf22b045afdd39020de604f792db723c17adadc5874b35ba4de3a0047c802497fbd530de1ebdb0098231fb48d2b50462e29004bc8572ce17b840ef7f15dfc6d05003ece0f255a42c78145f82f7e9aab11ea30042afde303b0e3436f95f04bf570a3cd6e40096f9ac735ce4556732adf4ad88a562a97975ff4334c2e393ac80544be9181c5006a2336daead506d31a5c6b878059fd1cdeecb1614ef9d6c55805474817c84415bec3b385607d6e45ab1bdc534e9c26427c82fb292b46064215e49f71c1153b335617ca0b7477ea6c0d748a94ec9c9e5e5c41a91711702666c14701c839ef49275d15cb83f09d90bfe57ff095d7f0762d613f866667379ef08d4dd2a28738f01048041a87e1107fbbbdcb8d2a6d36678d3983891b63881c49e0a49c3a09647daa8c180371589c5b4903ea5e3d51c731dd91a2dff9c1059e0b3d04f11cd2ce182f98d14d9552def90272e3d8ac5230aff3e89d8bf932d27f97f27b8235ad11e71c24fe0883481ee44dbc2c62c017fc7118d705b925a1aa69790b59f8a9ff6e675737328d995e3ba8aa44d014e7bf924023f3d1d0475ab908617dd937dda620747940598b278d626bc7c4cdc871e6811d6065d0487823073775a8e88204e42c56b3af7f5cd10460fae7920093c9337c19d42a1caee5cb037579f0347401b4b70229684f5cfe9b6b54dc789aab3c50b031206f748f47a25926eceedea00ddb6df2565590018f4a77ce5266bb8a5a74251b95763c7ded8e46a20e79b139b76e55cf9d7c3d4af292612a93b10ed63bfc40bcc7ebd877f54b25d13a1b100e43be1ec0b283457b346fb3d9133ccc7142000014f430cf1661dd80877bd312aa99822d8e609865f53b09719ea606270f84146fd59056acc1e0432790bb38f5b0ef84a7d6bc32378c973d3a320168ee768fe1effac4a3b04f439431df56bb549a4a3c77052956bccde3712f8c2fde204a167ec571a8bc741aeaa4ae758ce242fb1009d32e4288dc5e3205755a5bce871492d7193adb34f7bfcf27f84ceb3fbfd878128778e5e45ec3bc074ad030d6f383f278576abacafb204bcf19376fc9301267c30f24cef41fbf90010791294d25325704784e6132e7e60f5cf4dbc9d42cbf46de4e530028ccda45c10a6a247a45889fd9a85f5ce9b546c39b29f81078cf7f39dc169fa09a7814535e1e635fae2bc10f71121d2cb13611eed994e4f9274cfaf295968671cccd4be2702b76628b93dbea3c0048a1071d33da009862ba4c405f1167a1489791d206f406cb15f4242c5bbb5bac320119777958e7486343c1caabb70e62049ca6d0e5aa37bcb9410740aac13c83eae70c05d571ac8fa51938790634d76b30463a166e0c93823ac653a2d0ae6aa8bf43fb9964cc7dfa1ac412a413b8ce11632ac0552cb7f46c2997bf0209f284ccf0c12307500ba1dc0337e17944693c2bd2b467c80d202f20e03af65efd4faa467bcc93dd9ba03d13056796216b401418850180b1f7bbc10350656ad1d6afae7d1a0071d8e7188afe81b7f8a30a498725daea0124b0a8723c94d31dfe1aa8ba9dda42c251bf18bd28a768f74043040454ce095debe5bf4a20219c245d494a4a25ef0a629a847aa78e964b3eed0f0a1e5eaa454ee77e7162f85aa19f87d0a5e7a1b439b86ede92b127a172a68c23e3de3f3e8059955446b10c958a5ae4d95950f3d99e05e72ce86f5436d7693009449291a84e9342a75c2165289f511ef05e6020556748e13a1292e4e0bc57494e453437c154230042c22eef61e8f2e49a617d8ee77ab6e6bb0902f06bf9bb43ab345fba8a6414c08534e7ebda421824c943bc88573aba9b5c1c652391a1316f65d47eeaa5b5bbe1c932d3741b308fc515f4be874a09310e8b837983465a835e491585ea27c63c3f45dba9331f13d3ce3a2e77192982b5dd4258a5105baed6923b2e66cd673decda6701578018cbbab6bbcc3eec0e8b0cf43220b0de3f4862ac5cdff67c2b6a0715fc81a586aadfd9b50599bb095a0351c421c43eac82ff305239fb4235eb089a22cae26a82f6d40365e5c02065bc524c4e40a4b572a3722104dd511042f2109e999fea80db0a02daff876644f9f1760772fd3de435316dcb89108091304f63a45459840fc55d695cd209fc56a9f98945eb8debeeeb5fbb6bc0841cb6aa0017d10e947c400ea0ef48c80aadc2f89250600b96755de0fc364c3370bc6741450fd933850e6ecb317b4bab4b780d235187dcb4c89041b9b96faa2d78339b6fb467f544403fbaeb801f5976769c1d95ce9f82f531a8cad0aacb0f86b74d773afd18285d3b5b337cfb5dad58c58f9f47c02249a471c428f3233a29b96023da3b66b34ff13c71136fe4bcd12c99227fd7d3ac6f9889cf85e04917a0efe000bdf5f460b5315fa930a538eebc438fbbb3a7c42ee136ab78255d60082e68c97564006fa7dd8edb3467091b3ed5ce0acf3bfaea8e7e5b520531a6d9841ce954c017d350d8ef99ea74e12af4242759e38db4eecd38a44b0458b8be6d834ee0584f6d853629cb19128b112cbb07c00ba3f66c5918e3352626fcded5478a2fa0c787c9d1fb2e08c87ad2c5d9102a2244f99865c7cc8412b9303638fe16f0977d6ceeb9acc294faa75cc18bf31d78becd677c57a7c6374703b7b9fbd75d74ba83fc2ea1b28286c9ae4c296836e89842f4192ebc391c9c26ea7bca03741c710956f5bad3630b7695f9d5e04e2d4c2999af622e05c79f6a5a8b204b6c4e93fb4f88d8aaa9b07280db9f7b9462e1502677b7504935f01ad003371e9e9bd27399150aba9660d68e8ef400ecfbf72d3bb91bdecc1844bb8e9faa176c01a9eb817d42a615c959dced413dd28512e40a7cd072daab2adcee8bd7390b61cc8b5945e7201fce487c81b7df2f247210e3af78434cb5a28110abe0a7bf94b919e5e129286e74a8fdd224aa53722f65bf95e565638ded7a732a9042f74b38ef5fb376f81d9de9e239d0bc883112b447cb71cb11c74e1530ffc1b945aa7d3902ac84947f4c6163e492b7d12c7ad38fe1037097cd07c9f02f3766927c9993d9189091136f17c860108fe5fa8f781edbe0bbf008ae1c954b0058305e0d3cd0fa1ff61b0ced78f314f9ac66923efa579e5e89890f8a267fa3fcd72e59818c0ab181b495da3a17b4ae5bbe4824572998cae423646650106881c8c9c824c5beb5b617c6e4049d38cc5dc5acf2c31151b2c47b65c2a8d8cb43457ec140b9079c795533134985d418752af9c0f1ca405b1f6597deb6028d4186343f1911ca8743825775ccd445af943154d1678574d4d2e404a417ee29c47b76d1d9916326fbacb898042633e165a3e81e337cda34f64226b8d0e6a264f6940ad142a2b79cb50a5978d5b0e233b581a91e5e4e67d810adb98c636d648c34874b924af0bdd5452c7a54c24c3ac3586a8d7dc2d19b2fff3de2a641df5c168a2faaf4f6a91165049b17d56061dd184dbdc98c5701aa2c912289b6aa7ed92bc3bf0822be20034078a4a18bd297c2ae2e003e65a07f637cc8633222faaf83cdaab087243fcccb7cb17b23b29a94722b03a7b245d0377757053256b562ff43d8955e6615f229f347f378083f9b41a3d439b7e7c935b10b46654cb1af15192286a201fd84936415ae9dc4e29ce55c77b08190b666f8b1d11ddcc6ad54499be68c48cc349800465d6c820a50736f834af2894bb77d70fe4881eee13d0922dafb55951028515dda9edc25de9c1d78830e76e8fa7781b03dc7bcbf1de5f5f0a382e1aa864245d2ad898750e90b35a49ea10748cd32a32f8040d7acf749f3cb92b23f1f5d725286a545549a67fd87ba07b4bc0b2af37ca57ed27e0c32830684911d39c90bf6a9fabfa7740d1585296098143f927b4189daffa354c55f500d8c0b04ee4058d963451eba57ea22a6ef3976a55be169333a4012e65e487f9682a70aaecf14aa5fe2b4439aabc30ddee577d9f5121d507ed423de2b3f872bd215b1e3f562e54a93833370fd4e6406bbf0c5d378d666ced966f57988e9455bce157d430c77ef30014734366a5cf1694c476d096d62060860e7574b8e3d355ce85a3f21fb8607e1fe097bbd8a7fa05a4e847ad0cdba43e805788213f94a8873379cf7e14e4f2839b59514454d4c37b0af6b4180862065d3a5cb1f2a6141b26ef496e2689d2f9058a848244128f09644cfb10c90c860f877a0ce018676612467281bb66ceeaa5323a4d34f09e91f156bc2afbfb32ee0bc5d6180a44910dae8abcafc707f02d5d9a8bd9f4bfb1409ce1265317ba0ced83f02fa47a39b1d634430f07988e3aaabb620dd46a85c81091cb8a3ba2f7cc54fd6f25a1d018cbc1bacd482174abe3299718615323b65ee54260c024dcdd006414bf8d8bc4a19420a8ffbbc035e16e3530c33dcbfa84d6dc3bfc9ecf8faa621fb010b8ed52d7754b524da3595a59c797d6780c85139eaef31aa35047ae383204e952f3d38aac87c259924de9a76f4a2915f53f85f70d239f7eaedbe41300adce85d452a05ab4b4fd6f9168e2001831bc6a9e49710c2f5cced6214a1c37393fb588edd980ca5f7c481fd6c50cb0824259b71604956d48c7d45681dd2d6d06f2b5346576fdb710b1c6c8ae2d29943f18e1301dcaeaba5140b82fb93d987bec8d31baee6134e8ab8785ce2686d6b3637443dbf9ac24104dfefa5d7aab0b13afa460cdbffebc0dc2f0dc9502f4c3f177f1a77552cf2286fb701f8c4dd379d7a925c114d850e8deea4ef93d9b9d92a81deb7bd1e7578ac22c770f8ddd40116a621b7723e4342a0c12a8a20ea9c9e2da579cd5531354419ac5c3f1c40c9ddb65799357bb60d138d55f5feccef2ae9b8768bd582db4746efb7b65fe8fa5e34f93f15384f32abb4338cefe82e6029009e08b82d3defdc4d28a67f8b11650f80b075b5c11cfe5b3f10145e7a1300d6e7117735f250013f84ed4202a2cde40724ccdc289dbcd65ffdd74ecf7547f7d6565b7cc8191deba0f0c935aaae38932063a89308dc3057ab36f6d54f240f0a0edc4e4e53073ef2bb3995daa20dd4ebc25a122955ba63b53d45808550bfba052158f1d9904b6e606330e91450706fbc5fa1fef6426268e2918cecafb46d8f6da499d54d5be9afe306bbb0b8ab3bc92265001826b6421c9c4d9d37a92434ffdafa70ac463f29a3d2dc146ae88bf84f30442d68075844571a28151b625510c743c71405900a2ec33a335d7b8e70758abe05080959f03c54242c016fa3d590a153f5408d8bd4e5f36c7c8cb7f4abca6168a4e7d510c1c17a7715e24cf0c8f7e521c981224eeb6504bd0b39213f676f090501c88113b605b3650ee10fa9eee810281e240efb6b4bfc71e6540b0b9d47ebd6cd2144f7110f71a0574b306422f10595d8fad4c0d1eb60c25ae0e216c4de916d92d6174b7bb47c792b3b516fb6f5c29fd159c5bb6117a2dd5cbf3ad68ffc7135b0064726cbdce4842e9152f9919ab7ae51c734f5c626f1cef5f29ec9d7fc6c05640344c67768b2caa214d201be8b7986d6634800d2665a8a26e031b36c0e94526f8ed2a0b2672571ef14c10d86d2020c44d2a5b1f9332deabc1bde39f1a8d1ff6a1943253a03220453575a3c32956bec26c13e3e75b0db01396fcf5d33c5cc9ec1ab55a39c8ae136910039f4122e2f7ada64b81c7ae820bf94cbf4217ebd3fd6f660771ba5b6d2fdb891516b7348ca35a510723892936dcca0fff87999bce2c94087e672d3791367358bd8192fc36b449b2d4f8d640af7d9754afe40e9aafe0571665e5ee78d56e6c44ce9ab54c10b718adddae310c5fcc17aa178d2d94afd7b414b439f2c659305428c17d806357515656e7ea584214f785844b7c466b527a5c99615855b1cf872a5061ac0515e1b1accdfcff4c5a850c250d6f18fb06c6aa7afd1eb63d3d35c76744b73825acf0c39d82b9a22fabaa46dda1392b0ed60b3149806ce792f5e0b7bf69ca27214524d266a7b2739be36039746a6573b3c102e6b58395aab29d0119c62c5310728e05a60be81ebde3c2ae4be17b128dcf39488111f4619f40f3fd6a36ccee88c93ea2bbc3ae4b08b63280251a89ba98f74720b1cf03da560521a148ae1a9791ee8f279c8b294c12e05b84dfaab6c7a4ca1ec3f4036365955285269718ca87671a04aa512a210803fba6b14661877eb9233501ec39f2f4d8cc26aeba398c0893d31d90decead177bf5a3394098b07fb206ce62cb085d551ff85e39c4739e6158ee17ef819fa0a439f848a644c38e6ee813f50dcbb0e2b7826f533d1b8c778140529b21a3ebc61d0c9e06c369729c1629dd3a8f45a6369221127b7d02be90ea0c43d9b9b20c28e8f18f00a392b221e825551549c6f8ca4fa9e902bb63e3b5162bc8330c9805e134b491c5be108b466237cc082a7a8b56da9df4b46027104b51c07d74fb38708f7b13461c7adf4edbcc69838984f308c04321c2cb0018e0687500391bb44d83c2c4328d9a0563c6bddb9307983407e46a9f885e61d166346ef1f416e90b2d7dd8369911c1c646ce5c84a2897fb2e84167802173eaebefe291c1145c35449bdfc7cd96836a915458addee29fd0d34e2d9e132c17301824a280abd0eee90b4b6fdf2b8111a9c324d8c95d6e2a866dd796f9daa2075fb32f4fb9b02ccaab183ae6777c68861faa8c1266d8a9a09f3a411198262e33c1af30091d0a73278279ecad31266a382e2f2ee3ed3041af317808a9fbc249bfcf144be6c71cc084e904955a58d4abb0eee3ab923f16494edd856bb26ff2c6752aea586c7cdfc04a111e78f40a9423039a90a682592090424e07fad2ac3f313bf0cefeaff6ce9c34dbe744ee5e2e9dc2ff989a4c3c27a8e124fee9e038409605adabc2712c04148012e60406b57e7af909a70eafbab0ae7c294e5048912b9b40f5abfd16d2f6eeb6a5dc3225295315089c08a70762107591182bc46861b11a077756dfc0f2d2378ca1c4652c2ec3c27c11fb75e2843df4e5e3d83142e481f4c6c7baf23f97e9b9c69db81377e2cf0ca92324e3b26f1ca54be92480b159c1193b98315232e30c2f8cecd0755555b516c9c0c41a2762509489e12595c07060c46852b3306070bffabd24db487bb71bd95057dd1d8694db4a5eddffdddd514d3d5d868561834bee322c8c18c040238cedabe26d8b1cf11f010911c21fed082e7c1f1b8d3c113e33845128420805cd6446a5cc1e82d13118693064599665990bb6335496f16589ec4b0b603843858c288db1c3982f9c4461f3ea32c060f3608071eb65181858dcef5d920c162b8d3bcbe54243a9eb054410f1450c982368b620438406e6881fa494528209421be308181e4c603c216563169826364829a794609268ee4e012762d228b5717283a2315fcae8beb0b1e1cc972d4e5fc638f32c24988081a1a463f0a28a121a222f70f8f2e5e562bed0e0fa971a3c39a3ba0c034388bb5d8681c103d2c6accfd9d42f1ac5ffa25152f76461a05206917a27b32f64435cb2a52424b0c8052324aeee99efffed903f3df872ef7b2743929e5b1d15fce54bfb027d7ff16b83dcec0184bb02f095bdfca022d9fb4f5b247b9741472ac032464031f4a2ef1f54c416c9fe8522d30e7597314e865efe2e3f32a08796a2d117af5cf8463a2a20e9a8e09fbd7f96027c71efd4de89ef9e752914e3ec6173754f7cf16f28a30b517f7a167e4caf7dbce1ee2788fac414badf7eb32fdc7cf7fc9adfdb2bf56d7bf80d4d85b9f03de9c29f4a5de33de73d356fbea3bf75bf759bd589afc521a4de817fc345272e5df8a788244e894511ca852f5990df6df46ffec6be907afafcea6e1ee771ec0bf36f9e5fdb47db23aa1e27f5aae7ee673ece5604915cf81d940b5fe5e9642ff452fd263d16e2ffa05efb7e167e4c7ffafe212a046dcf3dea5508eabebee937d8fda49e3eec7ee6df3c1022fa37dcd3bfb13afe39462ef40efc9437a4e370ca855f03625179f0713c9d0c6691d55bf50e0b2dc03b64080b29a4f04358f8d151c1f4a847d9175ef4e3b3f0a3a302ea85ee51cfaf9b8fb6877cffd4cfdf52cfdd4ff7f3e1bdd9a1135fe8457ff32e057e11991ea5c2f6a68fdd4ff7a8f79f9f6a5156a7ff84e413398f7adc3b264fa72d102d5ca8a774a3d02bcc855fa40ac948add83866207c819264c595c1da46a44be94bd52fc3bca8925929a5172ab8524afb0961e9c0bd11e0051077f52c2fa4c8000138643a00010c1723678030c332e332e332c362fe1e9da424498a998d63120c48ba408368003998828b26200870015b368e3c91274ee63133e69149e6219b7974641e11320fc839b2e70c5aa0ecfbb32cdbe87c38679649e9de1db38792d2cfdea7a9173f3e77d30e9917287e7b5f7f0b423732dce2034b18ad20096646933362782943bda42192524a19420f5ec6c8e00b242b8e08c34b17e9e5cb922c1c015e90322f591c8959fcb3ba88a520eb52579d85865857ecddc5510d5bbaa87224eb228ac3bcc0b8b8a28b973362d8e2858d912d5d18b97e19e6a50d0c1366cbc6ab8d2390fc6cd5da67defc0ffa989739c74b69ebf70e5cb560652806b2961fe6e10146faf88ad4a3de549fff84c83353529bdffdd679274b354dd3ec273ffb80dcccfbb4d7fe03a2d9cf75b5cf7a9ceca7fdfca815e2bada4f5b3794c7264bb7c845fd5ecb78476c41576ce9903b6c4fd49dafdd63faf82b1ccc231b41eb02f9cbf9d2d3fee7f64bf9b515d2bafdbe725ba9ea8697fad51b61a47545251bc78db98523b6bed68daad8d2c99e61629cb9fe3a99f5010bf3c595ef3382d18dbf42e4e19e86c53f21f230ac4bd1fd5c5d6e7c7db5c617910f65b91f8721eaa60c0d0d0dbd8e18d685cbfdf8cc8d9f79275031c53b410a23ef0429aec45644a223f23098fbe57e21dc13a8a0125b7cd9b2aa4c5b7b27c61bb115a770125bff55fa1116bd600b172e4a64a194c5128a59718c363f7a3407d5f47cf9a7699a36d9f5d01c6eddfd980fed07341f76fed303ca388d5e76cfd5657e62ab4b84f099999919323f33335316a26547182b308a512b87e9dfe8bb47edc7613829e9caf845fd78c9858ef41d3a74e8d0a12ff9922ff9d2d7826e3f78e5c67799e4021656b04072a5f6917a23e030575a20b734c981d898f5412cd9cfeda594524a29e5b6bd7cd90387a8e572e7efe81e9c8fffc33c6e521f7b3ad0f14d118cfac121fa5276f2b9cd6f3c209c433e8ee78a3b7284b882fc5455a296be4f3fca3b79dfdfed3bef5bdded39efab77b32cf8fde010c33e96825a9a59eb104b2afa19168b8b3b4b8bae020b2cb0c0e26366768662e69c33049f42e60898cbf43ee6c25c70b874308fed59370775931201c9bf6aa8709effbb27c7b2e52de555a9f138aa6fd5556ccd8f7f83e3e5781f77e69bbc566cdd787c53ffdd5323f5f16b7834eecc4f7935b6e657cf155bf3519e8ed89a7ff24688adf99d57426ccdf99b078473ec883bd344234e94426ccc929dfd5c5cecd73dec84b8ee085c17887bd37fae0b3fce7eaecb3d4b957a7ed5c5f99b9417426cc557793ae20eac37def70e7c14ebc2a8eabaafdd73533ffe8dfdb89fdfdfee4fde576f6739f622900b349fc26a741ea6fd5a77b2f7d5d3f3d328ead393d7ea9df8b47b4eddc7f9dcfcf9db8d3bffe471ef4c1a5bf37d9af33c9566a72b0eb131ab094e2221b3900045546ee2c20845a5df81eee186455d5a57bc142b6e22842b75c41df9f3b95b72922bb6648dad0f3ac9acaa8ae69ae5b8039bb18f360863ec7697de47e1cfae4764a272a441659b4551f9e5896917b9dedc24b58ca391e7eb97b25fa2d00f24d94666669ea7cf85eb973e5fbe2634eebd4d9879c039f8a507f907178e105b55eaf71fbfb6778497155bd1abd4f831ae1c8bcbd503a52e16cba5b8b39c4a77dbf0e476370f3797633618b95f7d266ebae8c24b182bc65822e9256b58d2035d128492299734312d49a29d41464912877339b64489c883d0b2aa414a29a5122074942c51839221db85e5840d2c35a2e4b87b0d486e8c2459d3c5922c19f128b125b0ebc1e5d8922d9c18b1246162349ce154dc87254a2cc96283d89223624b5ae03c7814d70eac1d76702663668848ae40a2041457b2023546e2104dc468488a2131e34d6092180d4a33780daa005c8e21a9e1d2cb312446ee57df091db1d5905d3754c6d1dc85feec510877e5432faafc1f779d99991976195fe2364008c970f8524a292d941a1b66f23f0823accef2249ec491701d23ece073e7123eecbc90427793dd04609b0b2d074188ef9d13d5081db04d8e4a2eb688aadf8f93cc83becc7eb60928f87ef805ca5ed2df340e763ffcd297edb04f52da217da9fd917d7ff505cc23d9df799c393a203247ca1ce319f21de5bf49ffe1ed9d09dd8fd8d232f3606f1873d818c6d09fcdf0c5cdde6be4c93e8f9d09fdc33ffb111fc82d3bfc60438f426e11930189477f2d617777b7f71187dd6ad444e82cfd066666666666666676c8cc9176b7c69c11b165936a9491e014a508eba24e722c2eb34927d214a508eba22499cd0c698a528475111a349291a62845181a479aa2e4ed682a7c8e3132121a1e02db60e11cfc94892d9b54a32e4e824530a53805c96536e9a4b308c294e21497d9cc262c8229652f33580473e92e4e8245deae8f93e2c7cfd5624046fa1ee21c14c9356575a385630cec7e2b19b0dc8fae748064cf37481c7a694f7f528fe8c88e57f648e84bd32cfdac57588018976d151dff2086bd4c90bd09a6f37847cbfcfe3a6c81cc779b02bfa6f5288d88115d31026b3195633214c58860311990c48c68d1989967672ea567cdcc4c698f3f9daddee1eed6e0e59986e6b15b55d55428bdb90653b5db602abd104c9d1782a9d98560aabc0da6fa85606a5fa8a6c1d478e199da602a0453f9c2ee66e61740bc0c5dc2acaa7076b894dd4b9d06323377a4dcdcabeeee341542f66e4d3399ee10d8a604b75ad93802d9194ba8c42d861063e906234d9861e5c92ba361cc0f59e7e00be69e1e7fe7d7c99e8990d0177ded277dcd3bb2e335a54e6681cce71ba4be6896d921a8def14420a327b1351b88ea35951f46c7c2395cb0f1c3e6a2765b206653e3336c4f0e5125ae5c89c120e500172629c560d0a1c916de32dc8681c95dcd180c4aeef72e2e9d4645835935a1ffd7dd7d0673dd3b90fc5f77b7730e57357fdbaf5b751be702f5d6dd4b1e7d72b4ae2ce4876da03be55a0dd558b3c15580e71dd8530f20bddd5b07955e8ec14064a6f6e5180c30b49e955d8e0dadb9dfbb620bb8a7328ece85495fa2b4b719557539363405c34007b8456cc807dfa256558dcbb1a124f7abef63623118c56050e28b17328c911c7091e59003a53065a4e164062a7ed0021231216d90430ed264c96231c88028076d96ee0797616dbcd0a0d22e495dc884e992d405c6d8ed2edb5dca2c9bd429ed08a97332cbe6a454cbb849a9a6994c1be71cd7117294d34ca66de3b84e8a939f4e1de1498b89db38aeeb4e2714c79dae9c94885f2b961b3f15afdcf8cd4d4778138d622b3e1b36514944c2260eb1594292a20b172990a4d002af4023284542291742896c96988c129309c117426429be10766c30c66e779979967584198cddee5266d3e7ec08a7cb2c9b9352cde4265347689a54d34ca66de33aefba8eb0336d1cd775a713aa7aad1d61ed4e2854ada9d40d8ee3e074843837382a558d1a39393656be5a7584ab14d738385c7baa8e0478572aae6b6c35b8f6726cb80d1b1da10d7ad2f9a1dd7e786d0d7ad2f9416f3fbd6dbf166ee4125bf14900c0f52c9c838cfad19b8579d4b8f12512e6c1b208843436661ecc0f70b9ba50eaf262012d585ab8b46069e1caaaa8bd050e6abc0c6ba3050c6db8749b2daeac4d1549ce18da4193e9595cd1c689b6a24d12b77b6a66eb13b1a4ebaaaa56495dbbe7bfe600257eeb592d3645f1bba7c23648ae94affad51a2ebdc3d71b8b34966e1a4add130d33d0209399419a96ce6cce9f73ce39e7194b72cd90949a57a9a79a454f96b8bb9b6ccde64472e75cb3e4ce396766b9b2fd2289a4c314279aac408d14a32946d0e092a1e1c506ea3740038ba6ad29926b9e2ca162cb10a533a24471521403353138b90116353a487244120eae3002c240c514ac29a6602dc1d45c11c552185ab89cb4c0a12524b98485961a97614b692c29b95c4f50ea6a1bb86ce052d3449a2d4ca4c1228626d248414313695c90c60a0d583460b19c7067e9c0ccd25df1f4cf00564d976169a6b4d240e91dbe144d14344feef72e16d4bba1567cb32c6b256a9691893bec73e64cdce15bef193637434383925e227e218324584924e17259a1d4c562adc09d25b310638307667a20e308326069626d9c28992183929819be3872ca828b252d2ad690d1a48c18a041451a4c94aa9041490b0d4a324872c40c9d1589440198a8e2654b15675af0c3cbd1788256a144690a1532a861036592b0220d1b2c49b460c344c9891223251a9640c2fdcaf59beb468610c0655819626ef0949158dda86678c0610836a3063b1ca5c9218b1228af5e53c618f432ac8c2ff7abdf040737d4180ed496c102265e860f45b10c1b70f05a5565d0c892e818944933835119336df485195f28a5410207931129a5946664e08c0c718619674e666230d243dc5c8695c1c13d5d869569a24a952a5554506688895c1ac3c6114bd0a4a1010d63a64063258d1d90d1040e195b984c778ec20c152b4062e2023162f0924b6418a1c8386249fb50868832409041e60888a652260565a27897d9a14c9319740a905863460c9728a620238b57a6a67d6032e5871e7ae8a187282edd250a678ca802863136ec808997a7b93e6689a873305d869151811363c28859b3039929469e909142f484cc0e469ee45c86913172e765d818362e970e94baa0b4132762b28c2145331163461231638e8809539b536a3e516a9a465ff33e97cce8cc4cfef4a53702d6bdd973aea5726c49eb1fad49562b2456b12848775e8e45792216254b94a4bbe2784314a3fbad6edb703b1605caedeef662f4f856df52bf15d111fc23ff3a8adffd1e9bc4760873390685cd55c0e51814282e0acaa0c2cf3c787fd2c843a46596eaef55220109f2031d00afd472fdb1e0604eed43e0ddb7bad9f79fbe3ba3b195a962cb646b6c490d524fc8ea6631ce69893804628fbf9cd55b8c506cfddb07ce452da17bb867f65fcb1f429972dabe3b6da8e74fa55229aed6cad55aaba5b1957a2e95f284b0525bada93fc1d3e9943a3d4cc1f789a8f7899b4f8435b39fe26abf1b39aa3d7928ce7ef5b954f53e1db7fe16b9cefb84badf4e5e7fca7efcdca3de27deeaf1457944e23d71efd39dc7b1b5713bba07226993fd5c5a547ecd9660e1b45fad99fd562bcb5a5c8a1a83e24e7c487469fceaba291a5b90720e0e4c856d356c1c514fb7a7af3dfdff6edcff00a07d2c1caed953ffab97a5657a7214aabb4fad6de3bcc979d9d5e4ed3cd8bcec3f98db16b3d7a8e697b26e705e7bcf79f16a9c07afe0aaa7f25656d47e1c547f5336bdd79e3baac5ef366d66b4c1c6ac38ffdbecc7badb7f8f23f2a0e8b3224f2a46dabd94379cad410512793e22f2aa3e47f5f283dcd878c9fd0dcfcdcb2f21f2985efe96e3a9bc4fa272cff7a328705fe3732c8d2dc953c372ef4754be1cf71fe58bc3fd8dc7bd0dee729ff2b8af1ef7288ffbd35f8e7a94b3dff6d956db72f226ba0cf54d902b3cc2f4d06308bf4dde27c4754dbf2141e86ddfde10deacc93399ece7baa69fdf8f7ad8537b52ad4f556fe5791415e5d5ade3788bf164faf93e6d824e047df18f1367a2990c2148076cf263508ab8f3fde7671bc7ae049fcbb685865d8f21f3b5211c7013906e5a1fad7933f840c807eee24acb9c43c2b69a076f773ee0d3f9dc6f363cf9395e7b384477be8e09fdf33df8d963f5f2eb57b5f6f5618702a572670c8a93bb7df7587d7b355e7aa7dfb8f3c19c43fbd8a13051d8beed89db3a12e6e33cf570fec603dafee6abf76d5f1fe5e9ac9e6fce73eaddd359bd8de7cb1d09edc19b3c1d13b6f7e0b7f7c0eaacac0fa18b7a1d136c7cea6d7c0a8811f842c29c804f7ef61f7424f453fa363cd4e778a8afe17dddd357794037cf1776284c1cfb6d4f6f3c20fa295bbd8fa2bc9307d459a1bb591ff3a9d5f1e1d21b02dbb4a674fb7da2469f48763516b4a4db432070fbb7c9c14e024233c0417f7613f09f1ebb21f0faf09fdfdc75e61cdfe9c64f876b861a2fc7a07cdc9c71087d7bf03fef6714dc7e413a3ef8af4f8ccc2df67ed82a9a60d0dddd0dbb0b213b6ea83cd144c80e22d2894818ac0c8085952c56b4b9701834ec1f1a1a222205d0a64d1b28a7704ad77f63318fd4f3fb8c1079e67f37543938d3c9e4c5ffd1f9cfc6473f5a7bdb384dd334ce033231d5348ef3807b9397517d4462d3cd7ef172ef131b767777d79ce9eb6fdb9aedff9a6b3b5fca6ed96c8072bec86d2f763f24197ba651d054f3b31cf84462ce43ef73a59bc12d49558b2ae00a2eba03da2c673e9cf0e1e7bc37612b74cfd7edb9e973f770e4c1f9b6ed7d1e58cd9a7e7ef6a3f31fd8afae6ce4fc90d0c9400774777f1f7cf69f2bddd99ff3a80e8559df03fbd940d9cedab05fbd2b5bd4f0571f5894fc9366bbefa094fd2b1bb61f05bd4c8f29a7dc8df3f87673dddd39ed6464bf4fd7f052aff2268e777a68faea7d3662673b1efe67e33ab34a883c37cf34f53bba6705cd42fbd9b8b0a87ba01194b6e8735d0d6ad502c9e75ef33237f673eda8b0959616976b33a9c3c5713f38f8f235096146ddc3366aad151a5d7f2fa3e372ff09d171eba3fe73ddc9fdd7a5bc15e7804f3b12b807aaf68393f380e1969dea5df842745ced51f673716c755e063a80dbac97c994aec9cb401032eb2eb4826f84e57237fba5cb80cbb127b01b8b3909739f4cb9db855f7437d476ba71b97b6e9e9f5615f426b4f5662e1454e4c7f4452eccb6ffc9be61ddac4fcc5040db9bba2f5ed356ad9b3edfdef424f4fddcd21ffff11fff712940fd45aefb74ff1f4aa193217d81fc3b14bec89107f51f2ca2db7f5a51b6d9cc7ed31289d76d96c1ac33da3e0193fdcc7ed0ad8ee9e94bb7df0fff9fc843ddbf54d109c8bf33ea295b69e6d5a13ffd81dd6d93d7e4f1cdeee6b9dfcd7337b5cf979e7ff46ebef3389497fd4068a2f329a4260be4bf3d9d2aeee21f7cb85920a790c2ee895bf7126e6feaacf63744fa721ec796c97edce5caeda7c8ddddbd08143ac50a83d8f2a1e363000b112e229a103181d20212e82006145ccc20091d62b0a48913242c405d8ee1d0c4f5cb31264ee05f683f3f0395c00e85faad1e7a10872270c0b26a23eefe6cafbc62fb8eff98daee2ebf050984a83f3e34038729f7ab4546b75f5584453e67f64a0b42f795114909087deff44b8f9b6beff4b730a6eef4576f774a4759209144d1d0d08b5f4740fe2db82d81002e84c5232e445df5b3dea56347e4f9be69757f0e7a5e6babbd7eb2314b29a59452caa710ca876c81e4bb7f0b42574a6dce39e79c734ea6f4e79c43e6d52cd0fc6cfedc3408e9fb440ae16681e86bdab72074a9a59e7955b56a1394406c5bf450e8d5d65ed71f3d9d1ff2a3058a2fad4e5b17bab0ebe19ece8ff8f0210a11a81fc8bf877fc34e15d38072039beb830548961baf70a3fa715207da5ff63cec5350b3e7ac1eb35d80e3c6c295331195c59bdd882dcaed506f782cc09bfd06335b05ba0761191bb37a883832a09ba73797524aeb0f5f4e914929a515c2dd1891b01139a7121dad22035a95438d4f99d8200895738c105bfd21c456ff8ed8ba41fd7694c05dab6ac5ced560c1e6035e55edaf81c3a2f44607af56a9209bad55477777e3d8d1ea68371e54048fdd8daa686ca7ce5e8e5949e2320036669a3ae10667628bad1471e19543549746f57bce15792015f8acd8035f3a2e8435c4c8d37101f5a3d7dd5b273ec7ac5cb97c5da8a106268473ca19b7861c4f71072797632b7072755c8e59e1e20ea90c816d8b5919326f0b383693564f1f65cb7f116857d58da69656a2a3f9da536dfa4f6d121dc13833abd3b26374629b95e8c8e5db8da308fedc03a3dbac368cc0c62c8f2d2ab35459e7131dc9ec5954056796bc11b9ebb18aad4e511cb125bf155b3ad4afd5922f694b84ece5e37051f99c7c55579f088b336c3ee08d3a57beb81cbb72c545c0e5d8152a746aef1e07a5a5d2c9fc8d47fdaa08a69fcf6a51b973c6ae2c4958898eb46724e6c95eabcc437a268fe8a875a2151b57a2a3d84f74c4f07915e3cbe8c13b3de6563501059f048a59f6838478853208c11c51ba9f0b07100df8c0b123487ae2cb872b378ecbec8a155bf11b50fb617f8540fef287bfc41179dcfa0c153ec74c763da4f5c14952a09815e5931a4585ffd1937f8d3c6dab6bc110c2673845e50f7a28b850ba8c3540f51439f2fac7070448a2c667f931faf3abaad48fde8f0265b9abd8825e09ea105ca0b8f02bdb18637b6c8830b121963ec6311bc7addb6726bd23082ac78630ba2e042f81455ce900861152186152050b2aa0d8e0743936c40d3dd420cd10479e644963a4c464cc0f323c7183105474a00249bd1c33da6224867feeee325466668fc2e5428352178bc5c69de58f8158c70c3ce9c10a1bae4c09c24a961c60335471819ce189519635555470b7cbb12a501a1a195549e27277a476cfcbb12a4baa3881b033ce609d11132216841a5f7221ce18491313628d106a9a5083060d0721850a0b7460c21a1a9e10c2488c8a127412c4951bcc28c0e55810484140d18519259792cbe51a8352178b25c69d554605676c10834a0c48c4a092840631bacb312a405cd4e5189526b12958623e34e1654c81118589a960e86a97632ab8329f764ffcaddfb431eaa317741484dbb6f8db6b36c8e9db06c18fb6d6de993f9f0046f89b6cffb6451b9f53e36d7c8d0f3a0aa282f0fd83c0777ff89a0db2fab641fd2b0bc4061d05c97968835cbd333fe76dd89cf9d0ceaf6183e6abacc9db1ee59dbef3381b74a45301d36f1fc4f49b0d3ad23e887bd36b3608ea4f360816f5ce7cd4c38796b3412bf4cefcf8db66a30d8243bd337fb345f1a10de2de89366828e82808f7d00675a077e6731ff4d5957a7e7112cef36bde3cbfe07c9f3679fc4279fca2de47e7e378414741b6bfb141f0b53dea53de0bdc9bbe7a2f1c05e12c9cd23bf34dffc21402b400652dc4d23bf3096004675f083a4a815f2f937d413ebf3e88e5be4e1ebf3a8f5f5c7cf84311d783cbb11f90eec691e3a98ae417a7c0afec55802ff962d64409759c43b09c1586cbc3804c2a7ce67ca0925d8ef920855e8ef9e0e4ae78eae5980f45dc556ca954dc3d91d33c550caaf61f77551fb897ea7f23f2401190fc2b8a10bfbfb626e51639ceeaa810bffb14e27756e86ade4737ef83577b395fe89a3ce642659b622c636bc5166c4fb6ec9e340e81408c9db58c2fbd28a394d2e37b73941354f85e21ec87d2ec933c7ffc7f8aa8c77acda3dd8aad222afccfe5ea6f0777d140e4694fc54b5cee5f8e3591729d68b9aecb31273a5c1f887a58baf2a9f65473d8dddd6e3f95db4fb3707624ccef67befe4ad43ba1847a71700e20d588fa71988fb9505aeb6af5df0af3d33dce71f95c492d2e5a5cde700a17ce5ce9ca29fc19fb97aaa4a454f453e444165d08933f59d7c3852bb3222933e945ee45b248baa7a103951c90dc8f48949e8e9b7dbfbb5377ff22b1fb33cb1004ffe835991b63190842fcec63d643987b80cb31295de00b3e7cce05578aad289dfbf491e75f30270ff44a7f0533f86c03223eedc75dea2d82f6df3ddd2fe1778fa655e10fe1ac41f5f9124678d7be1f47f46a5cc96f4b1b5ace3d6700d21cba7f9194a80b91e70bbaf37d898f9588c0db1e77334f155b120ed51588c49e1adfdbbbb18aad88458d485ce667ef9efc2ca34f23fc0d72974d9841977186c8e3d33b9c5742e4690b2184d095a82bc4567ccea77bc7e57a404602b2421aa184cd7e9456cd7eabd57fabe5ba2e38fc2864c7bda1f20413f842175a874e9ea8aa1cd46fb56a4aeb675ba44871fdb31fcc03e68023f8300fc83ce03781268ffbf80df9d96f385e777323f264b02b5fc704fad9ab5ef2edeee633efa3ff657f9ad4a35f43fe473fe321e9ca9f3c20ddede9cd53affe0dd4670fe737954fb78e63759d0fa19bfa1b91c76977f33854f535bc2ffb56fdf62aab73f33a26e07cf738df7d47bf3fe771bcd4df783e846ecae6540f0865af0ece66d973cca305b865f6a394c24be9a42b5c3a69072ea573da2ffbfe4deb4af0819d8fecfdfd9d7340d9e307e72802855471b639c9f767aa924e4deef6747aee9db539eab93ba1e449ca93e41cf0396af22bbb1edac3ce07fdd94f85b06e6730f3bc6357025fb7425826dcc86286177d821d467d14e554b7d97ef3e5a33c02145151af7d7dee50b504be284ba44d70d489729388940240c1e6428a12fefdeef9c6713fbdeb411f763edc3f227ea394ceb0a1a1a1eb345aa10dc62628a594d2497f52da9a169d6ea8948c97753d2b95aa1908000000f315000020100a88442291581c282a3f14800b819a44684294cc22a124466110434110031886100490210619800c5310950db8b9487decbb16b870bb779ab33b89eecd4a8736ee4e54b10a74597fd539dd3c0962ae8d94a0b1330dfefa2323acc8938b83c455d7f86edb66ff51907e74422133d08a4ad7300e99c72e07814335cfe86ca3a006d9734a71854a6b826f0c5db1304fc6b2d620fac81763cc28f36e067bc69415b3ad4ab3028bed93f7b979d2e5a812716d5d392472d9586bf546d884cc0c732e5928c989cf89625f56a243df0d1316b46f48df0618d0857824e96e94b9594f0c4307d0b54e336cc0e38ea1326160a069f2e3296ec59eb1d41affcf7112dbbc941e1f4b63a980115710f101ae39d11874ea4d80c0f82028c09dcbc7743a6ed5e8a38d256a3c1651ee822c30b056b596b27f5947acdd0cfb364e6c296a2b16924099aeec05bc53c675c1a74e8681c32e548f3c0236092f8ef9006a017a6c2621c001554b7534b16ffde79d7ba54e31097bee1cd9c32047e3edf741789181dd0fd16bf769ad2be274e143432ea3407aaec67c858d1d85993126b5c3aa42bb3173eb70935716d341e26363e602df3ec3f27d4a9b27da55a3364e4398825148020ebf0468f31d5d19a0c87c201c35ee498df889ca976b531921bae8b6c80f43728d8821cf433593f60ce394331e811f74346342a82c0ea2c6d7fa66e06a350536fa15b0e3c4cf1848538b03fba583abf762dbbf5f45c9f08740b0b0f87f844687a81b796da2bb314271576bfc5d8216e8704f2ca98a7dc53c7112d5a7b5d6b6cd57452d7a79bdb5ce7f2305542be036ad9a2a803bc091874075fe3dd87b84b9d10eb7329a0d4ab3e91a3171cf0b7afd53b34136314abb0731c7a88ba0388d2127477d9cfa8523e0d5312a8b2e8cd47f1608863599087f0571239a352f61bfece7320464a6c3c4a8aa792e1bce66e4361497d114c4779c27aaa826144e6988815d79d8390db00de99a02d6fcffce31b6d51be49e6a8bc1439ca2a6e70512c805b9b3193f5fed83387c4a2845fcb6289590de2562bb7a341a0ac7e10499c1e8886687076c7998f2c7fec71aab4479a7c674c087513d4009e4047cc0d8939218a39390e05018255475db8b34a728e05b490f0c76f4fa9e5074619f7efc3d7ed6c9421948c1cc795f1808677a882ba372c3741fab95bd364dd0002fc6171d719d664c2c7daa63a3ee56e445f36969bd6400643c365ff4bb8f42e4b3c91a35deb352ef7230ba2de2402ca190508478dd43515a067a616c03b4f2fb7d860796ef4e0f9fce28626e50f5717c3a3680da6ee64db7114deecc45f9cdeb08a7ab284565c0fdc1808b46ee8a2440d39f4d6bb67639fe41187d809afbf3f8b043ab4cc7c705f0bb34f27f301837570d1fe8adefc3c25e124a1c0baf9d95480ba510bfbed4fac13a93205b4b031226033be1d3475919e59c75fe09bc6d7aa3fec84fcde876b61543d6d61e3b5aaca99690d15a292f149d3609f94f56a046e42cc88d6227650e63c7e18433253ac1ec7667b8dc138dd4d8b2c84af624f26a7279dd3badb908c0b3aafd8d541211c0b54513d810e5dc8d0502a8ec2479924d5f5a1edcef96e161a83e8fb1947ec8883d49fe8103aff1b8bec7cc613775a7789043995124ffdad24b5646e5967bd841da040ac1132d0cb55e01c4d3c261346848786eb2daa08e77b14ef13836bd086d6ea5c22f1104d80bb463c7b48e8d8dce2382befe04bc0cfd980fbf44cbdfcf5e394aa4afb93a338855b0112368970740c275a3279508546f2a34b2b7dc24b76f7e65c7a35969a574c913d5ad60306ed916aed0f559b92232ad5b1ff2add0a210299a5642e93f518218e5ca1baf7cefe9330dcd7486f6e355988d5c4dfceae45db0af3d2d4c587d664680377e9bdd29230d1fc4db7d2180e062baee918b725d68933710f6874e5cfc41c515e897fe1b688fde9a96de822d5d7cede8b38b54a177b9cb040efdcec84bdf56a4582cd55295bccc1807b1500ebddc7c28b8da64c5bc8fe78af41ae0f1bbf48208edda6873cdcb5d77180bd3d038d47edd4703f5096dd03e5b9222fc90c6c28282974ba37fd8d973220824759d07750e240b91518543256221d55c328195f8b5a01083c69a90068f89ede62f0bc40601ff289d58f04445a6a34fb0a9ec00a2a647cc5e9ed3ebe39ccf64fa04a31e4e72e5cfc495ec2d35a194874765498a3400272cd1ec68591f90fe8c7176c4eb6961b01c5309602261ce577b12a4b0d32f514ff0acc1584fea77d1b11d3246faaf0169be8adb51eec3ff63e8312a24270a5d9a7593940605d4df10ed39a900265c1afe1f9e4b2dbaeb4401ec90b406201ca8f4558ee5e2522b2e89d0a35f1537ea907e6616b5cbc97ed074083794b280fbadc5d714fa10f39913684f6b6927cb1a6265924b5368f31b18f090911857a281306310447addf598e307c6036a3db7f42e2ed14e6fa85dcc021aea87c864d22b34aef201bffab1e24d36e457d92363e27959b628f0f0d797e33807c9082b4b50971c27b3e1522bb5ba9f8e787187e138d06b2539e5ea0d961ad06dac6325fbb99a238fba34ae8012be6b85d4f52615e9836f31a71f5b20eac2bb463267e6b51d227ef3746a84b6e7b4b35d125f40eccd4d5368ecce02026f0c64680f4d7fed8656b304943dc954e89393fd4be6feb13713f8d7998671f58f9beb62079e440f0254ca6c64ee414cf58d1eb072a2fecd70d0a6ade34b5ac4c5b41b67fcaf6fc09edf332bc71510efeb1af6136700c503a602c29ae2ea59a6dd709c6c06a3ed10a50d7b9de4cef4d66fb94e9dd6b5ef22740997b2d1b9c45024f392af3e89ffda40d0609606b6e7616edd4d614960b078fe5a23900549c6f060917d6e6e46d59a2bb9b33b2abb73c0930b2ee773d2208aa2e2362a046dd49093cf92944bee1c173c2adc0bec65b925a8d5533714cfe058c07b24b6f98a312736ea4d0a42ffb011c69ff4ce544a00f7446d48d49c7bc47c99f7b76d47a8f1652d263d29954b249c6713f7e3a304e8cdf03398cf55a77e5e1e6f15ca0967a5dc8d425665c0687f1284737aed8c1690cd41086267a71e2aa31c2d8042d0c2fc5948e18c99641d3e23fd7f4462ef690f89db443499ccd987304da9e70d87a373b5c40a131eff79891b11dd3de640e6c22ada37a7653c2bb75470d2a5e2e2caa2e4884e87972f283f8ed3f920f0274dc8b1efc3362a9b1b45d0fd03293d787508e785e9ba01e42cc26d9a6b9ab08b98feb471961be1efae4a4df4e973d91eb1f294098e48a4f0ce6d3a6fb0cf64583571ee60d6e69caf361de4f8f0e82a02386aa11ef6831a1d012ca84093dceca2fc40011a23e8c5e83dd60004e50e0302c709a87fcab13e4259c8ea84b1fecaa1e583e9dbaaac2d04ef6edb744cedd7fee52d7fafd3296ab6dc2dc591372dce6b29c939440b1183c6e0731b868fb7e7573c0b44b68d95325e38314d4d793c888fd799dab845e4c6632a5ce4b9aff7a715f60e5ff14f074a84fe71ddb953fe7c25c29c2e581074313cc3d31932876ddb4610aa8696206d56dd7e746ea00b45c788e38b96111e94d7d87a1fc21d4795624fd6840bcbb1213682968b06a4ac6e861f6c1158eef1c4e7d07b774a7fccb78ef705830c51b516750c2fdaf09685b7bba85303d3ff87f144a1182ec91a31d4b94ed7a8541a2b08fed867e22c70392c45e0e62799905b1af6ea7a2f8efd0205bd1b14d1898b6e28f2104a27759f642f9773c8343af13c66e2228857e79eddf3c62ee595c2d1bca7f36b5b5753903a597c8fd7b57df548db65ca90b965541996619ab58bb1bb3ad07834560a358c30a0617b3685261ad0166c2002aab63fe35b22d825a8b41eccc3116552507a7f1f17b5d4d9d16f9071041e746c94ff5b839310279c5f46159702d971c14e2df44420461e0a510adeb8a22212ea2667045358899340b78610a7ba14e5412ec69be891c8f5cdb21363b86a31a879dca993350d88a9531d1b266e8780646ae2669689341f97edd51b5118cd491979b5070eb2cab717f6490f36fb9901d34c1cd4f533a9436a4c320c58776b97b4d564f57057e59817a9ef1d262b4189816b94435dfa6bb2eb5d22438d0f717d699c013243ad78eba644d3f43910e116ebfb823d1d662ac444adb5182c40ff484fe3517597a783ffe08fa15d69710d149a93b40b7fe07c9a5cd639bef74c44d72fc88361e378ee0c656559918d69a9bfb51a7384823ba7bc63f9ea8ec1cef9c7f2d73ac70a683fa949536eb29e70e5ad58a9a97b1fd85e72b6c51c0eff1de7893d5ff6fffe39c484800e1d093f186495e34a65a5da04eedc4cb02fb768da16d0394cd0acc8b8d8f9173c24fd5c0d47c0838c04a42dc4b1fe328c4e14dc310fb4403a8697815b5508847f41421387bc8af3f47716fc5bd01be1a6352f93807b989261d63fe7ee0b84c71401b0201b5b9da8fe78f353013c7d6edf3742183a04e1a43fe5fb9f8fde412fee769b58b2723a59b1955ffcceca60143fb2e53d8308b587ab5cef6be365cae962f9d0b65178845ec43dd1eb40fd02f324b739d23a57d066aa2c8735695c768643061e99f143bc1e4b654419ab5ac74817143beec154700e8002c166b5375ca98ef663fb5f76a912cac0f5482bd1411091054a651928cff3a8641622a6af8635de343c35c1a8cceb78a3fee0922a1cd02b2561fa14ff7fad17392a8545a06eda029c0096b1028678b54cb318d9019a85784f0d5f2e658eca799dd549802c2e46a6d41f405dafb8be0551a4cf2e3d82ee2f367f454ebbb579a3ffbc66e80e7f3a133a7f5cb5d5caaf1798c0086db9765d0caf8559d7929cef8111cec84a186d8891eb2a2e8f9c129c4d38414fb5a4e85e1b3ed8893ed3a1d3c258749cb5bbe3db2888c06835d1a64484292c6e1617259f3aec67f794aac4490ba86f5a29d83633e030b1b21a6d2d5b4331ac7c2cd8c68a26c25fcf45777907ce75afed7414955eb50ea640c39f20033b0518d5efca057d259d9395154536c2b0f6f097756e0d4a37cdc647620e06391e597dc347de0449486b9c8cfb29ef231fd306c85b850a03086c67e6b0355989d161192651c9a9b92fc7b6ad534274c6cf743a80842cb26049b02a4ff37b5751edcc4279b18544a86cadb0d8b3fe5a5a65ac822b31a020001f0ae5618942296673da4a9411b8099590c4c812729762c4655e04b5d2886fc27b348b53f9d2ca39da2284d6c69c7e98a21af99a74e2eee6db01fad56f91f11933d7c2bc8753785b29a654123101be02d03e0f3206e3369d63ed00ecfd299e87a821b2d56b3395e6714f5364cba0b4723ae672cbd89d4a73869127575ad5a4b9e0e4f4a704bf99c404d1700c0803ac83ced0127b4aae3e56b7099a2375580e6fde3a9a6c781c033c7d309a0dad60d51e480096ab9d9038c5a04d3f1940f82356dfed575ef67186f2c7df97911e2922367658f1e86f7bd9f0a63285e4a67320cb0a43660647c9b8b1391eaf1c8f13b8ac490258dd9cecbe900baa477648680ac5ae59ced62e56cba955bd4752de1babfe75a4c2174922cd028aeb4fc36c8b44e95e410e6f192cdc0fb130bca33df78213df77fd00f2ea8699de55cb9f8d0a595585073e2600530404e2f7084851421a7d0748488a438a9e6b72da854a7282ba6a00745584b44923d0b4ac52fba92109d980a1911516b3edaf98d61580b4870c914a7beaf955999fbdd7c801b7bafeb0b3821c00623f7d2438ec0173e80f11fb535f50ab3ea49bf3521fdedd4338b322e6c0d5ca34f1834b7134b1fa43d2c52af31fd8878ff5fd50e14a10a314798be0d3153629d44d23224ced63a61044a2aec12350cf5221ea100e42b4a124cadd584f416c9a9f2b0d8a85c20c62bfb42005db7cf47d3dc2be9d6ee0d164b19d32124eb436030fb091f97a78b84a871efabc849eb455884bb70797262418ac2de5bddfad7bca2107e129f7ec268d8a9ec3d2c3d35a229558a3664590a6c2ca801b8064983c3146ca2398a49adc1429b7c32452da5e499ec1ce2e0a90740e5585c67a4aec6cb2344dcb8cb438fb8557b3a82550934e860ae36a32bf77bc9b7e5079eade869abe2af8f2dde1730d772be56d5fc6d2023c01476a9a1e3d9302dbc177723e2c777784709427e10665357aaf45dc339a5666fe89d12814298694857be9761b3cbb5e8c3be3a07309f086eb7e98bac93b4d5fbc158f082bac2d6467e934d90b35bd5901f8bced8e2fe727a290cf828470d60fd0e24824d76caefecad41cafddca4681fd53878a9d27a40a6f942ec94f635149ca394af4dfa1c2c591533aceefb97876573d11fbb6c921aec099b8269c530e0bc825a33f7ef40015cd423ce40dc2a053a9346365653c48868d939346d7f524b5c26f27d14e8e952b06c6062193255555d057c7eecd2d4f8a77acffce9088e43dec79fa84169eb2bbb4b234d39aef8340279a62544927f5ff96a38a3d127f40e38325965208328fcf0796cb72981ff1f102068bb170af5b0a0bad31281876559be5b84ab8a68b59cb6c04955729a9d4183e54cd1d7622d4aa7b6277a60de2e41baad3665a339798c59eac62e9d41f2531426c1a17c6f77e247408cd1f114eeef94733732a4c6907eb634f7982cab9a8001826550a84c02336dcc746d713c8a27e3ebf2183e38e732d194b5cc3c4f9c6e49609c931ac405cf550f3fb73081b5b38d2646448163da41e384fdb592ab434195ccba2becd05cc02392acfc361aff70cb22f8a161c4b33536a5a674f00f9c1d36f331b3c7c51386ae9363fc2f2331c2ceb3ee1ee9ce5cd7ae6949050555bb61bd1ed61374b74927a7287d01104bf872c00bf3cbd892ec7010133f5b22eba18fb5dc7addc06ce2971eb40539ee47952e672aba117abbd4535e38cbaa58a280a2d7d75525b00658c1049480955afc4a053f876860df136a589a49639e500accb05cb093027c85fd56415d2be7ed6623a6ac5547975801a15d1dcaa60a34bd242d377aa36d7149c8cfde3a027519af65ea79f89cf76aee485903e93a74fb231d0faaa6632c5fba8884323ba77188a968b8dffd8e2866469d18c165c3ae791d33f66f8db8c2e935f6b167d39528c97db2dbbe57a1ff3d35b8112d9560d6c2aef6a95f36dbca3b6351ba7aa1fa478473837a09a7475c930a6a96a2496da2b370f9ac70ecc03c05485d0c1a67503fa765a6ecbce8ca7f075761a6c797b6d098ad2ac07090313f5914ffd488c2c889e4db679ab53796a71116291d6c60d6dc33e9bcb429bfb611d52c2b981998fe1aa37cb392f75a0a7283bb990be9ca34c3e8441c8eae40df0a7368f1d3d0ab60575359016cb5225b507d4844d9e05e9be665dab6d23fe76ffeef260a900f49e82d4cb1418525cc684b49cc3e8db6b7d49aacf1f765204ee4955cdc0750c454e758d297f5057833d27fcf2d2a9d427554147ab0920d001d723720cb85f7e843f46244ad72aa78f645972518a27dc43114d4aedfef5c7d5f148f103b99669952880e65138c95eab664c10c34da16dd381a69812437dc9156106439e7a9fb369e1d14df1e8597bec0c68407048e936d5cece053592cd92e96312924d6e2a6117878587f7924dade00945045fdbab82824c93418fa500f46b19cb063e5bfd022180dfa39f21802c267fe9f01b01b309117eb69af7d1af750304ae688e64f29baa554f1572251fad8118f60c5f0cd2e0f0eb295082e46706da5b012ec4fcd5063babd2b5c647ae08a1dd3362fff943bf59e883850e7b4a3577fb0124df9588f2ecefcd117a505fa111da07946ae088bd31f9c5d6d38a7f804c2f6deb2a870f9ade1b6db8359ff539faf478671429a982d10704e158fd3d64f9a42b90610da7959d381945a1f5ae5d4cb7119a61747224bcc8cff3406d66b1dfa6a66491c476c45d80ea4ef6370933f87c703d35fe6de2114619bc2a92000ef7e0c8cc2fbe286cbb0e1a425ea21a8074aa488e3e77f5825e2f0009edcdd71cdc5b7a3625dc56855d5de79d35c3c0d60bd5f69ee0c684c5466b494a036783b3ccaa573a2bddcc6b27c48823546a8d749ca5b98da42af9d053d207b1c1e27686bfba4cca16a023abb002ffbf244dee271fa9090b4203b3a12335d5ad6940a4dc0e4a627d1c4d49d2207c5f328e6d0720fca9cfea414da7cdd17702766f89259b605e1bad68291400f8a588b6dea4e96e9a6f5aa7d377cab6b51283c79d7c5ec9744757e28b76774d3b1d623a4d80af039cc5231ad6936f3135882636286e7014818f48658b329341c2fa7ccd17cf5eb470dc9c84ce3461f5a54726204fe48edec4a252c28de8365dec5cce59a89bad6619cf9393785c68804d4ac01a99604e794d7a58fbe30d8fe69aa2854e57b685b1645d7e0146a76823bbc1bf492089500f20694a5b316a37aec1c3689fc80874dc34e864009918c34e54173c79082f344c03126c969100e4e02be20d952fe2693306bcf9c0782cba95434023bc7a4544ef949e11a689f9b75ba1b3a22a5ac42efc204add8b4e5e9f6edb3b0d9856baf4aca772b6509478d4f519552319ead2c22e2ac292a10e8b9f01174b7218810706e168c907fd9585fede152bbc54f70351dd0cb7a5a95b29b98c29f342c1e39c0b877e7673fd99eb365b046f3576bb992fe2a2fa8e2365c1a910082dcb1a24205c001ef6ecbcf132c7a7f2e992f8fa49b3d15a6885047f311493fd94a22e7cf2fb2000cbf7037134b6421554104862286af50ad02e8e13d797e8be1681191c2473ba0969a0093c9e25a3d3ddbf8be77d6a0947525a1de6da7c8e9b7b76349b949f89c3e245fda1431a7bc28399d8c9a370619824738a9786332260db3a684c28158fe4f2e3641cb9cf44f6fbe96420cdd6a0ddd6c2ce4e2685a89921f92713675d77527fc86cafb4a87b9a4aab5cb60b1e41976e241c9b0aaa654d5f3ef2830026001d15130508154eda19d8bcb8a66cd9354765d5458dbdf82075211a7af0225734d14442ef83eb60beb50e5c26514eee8087107191e817d74ab2466934c451a1259124b933c9ff92e87ec358388004ae971ff00a9a9af7924bf646144e1e5e3a6204374532bd829a214cae9ade28b3115a7be393938aaca27f85ef0648c137c6bb38256ad7e790bd513375069db6ccc594da745f97480ac1b09a59eeeda7ff32789d48ebb25ff9f75733fdee77c1d88b5b5c14a81a7947950e2532b0432e32b79002f266aa557f9a5c3c8cd4cf7b4ace54089d2617ab2c0d190bb391af53a9ab14dca93775bb612275e8908fd44dd7398fd333fb747bd9872063c2b5da235bc80ab1103ab1257c97e689cca3bc323c295bde0d8aaab0718c040f4feb759f3ec6f3856a1e01f0cbfa54b38fd51d11203a4e76ded5eff7ca605a4c88f9d81a8508cf2c7a15eefda10ad358f09ae2f8af4ed2d36308e2f7b2b4c9b6c074f3cef23b6055c66b0c85a0aeda674c922d8f5ad4d2d1f79ec1c1b85f152485ad2948e224512af3442daabfb9ba6a1fb3ac56eb49f14922a03f0f84ef49bdc7fbbb6e0e811b45b89d006710016e893f67852d5949e1591042fd44fc42873b6d01ef4f4d02b0d384435bdea148785606349262631986605f9384319135e845f96002ee7b71689d45010272f35dcac969ba312cbbbb787df33a5f28cfd731a6fc75a51b35bba175feb16b1ee2a030a8044c909e18d0c73f84c79cb9d3c9267923b6d83680644a10c17ba83c058ea8eab6c3c84a942cd629d637959fe56e6e2b0121fd36523abee45f8001504f75f671e5eca372f24437a5122ab714c777e4a801d36c869f052c43b447ac993fc5abae9c319c9ac985e7a7d6339016a477249a004cd2e327e09db219b1a3c07f6975ae1ee117a6fb321acb84ab7832c92d904a243a5500d5fd20218fd63da0aa9293903b8459eeb081e0255ab2ac67dc1ede5753141898b711060c9380a4e21ed9f4b08a699e5385926f858485514cf89c60d37eb0b2d35c0c0efa3913c1d63ec30a8cacf0d8b94c607a4211eef0066a1f16243269ed4e8842f075a4e5f22e332bf403916f5a79f0bc9874d6f1ec3349a81207a085115c3b82391c584d8c29d3175488bab3ef948aa3529533d41a053fd8abe4b1d0e6735305412f3811a3d79c3ad2e8ad074fd9530dc282d18c4836fac87960cd38089cf05044e0938254d846408909c40aef514b229dc2b4baa8aab5d154a0427f92609af96a8c9146851875c6f47db3824a43bdf4a052ac72c16d00a27fc26e26e9cbb3a222fd13233458c54496d9212fd5f09a505149179905a8274b9ceadde06238d0888150b30e2e210fe224bb19f005924c5221eb0c6c7c4853c1c24c6b0604cd9d39d9f9beefce2985d46675f9648450897535984c6b527ea3c8ccae5886087f3bea0528b6709304f2945cc28f046cbacaa08c8d19e125f843351fecc822b301fe6cbbee267821148ae0ec3b6749427a059114b267fe66b17f2d1d7d9a2cd49b18b99ef1e632f5ce4904e950663dd55e3da0de4551b5cbc5064c6b04754454e1807019e4a88d1ee053bcc61d34e45e5c9fe0c8e472ca14612347f2c84bb81099dd904f4b66c6bd5e8089facdbcca5a515dc0046d7853f1553bae35a39df80dc632762212158890be7de1366b22968e1995ed9fc44cdd29b3dc9d9224851d7396577528d203bcdb1036a5d30a6f66daf58bb4ae8a32b38117009e08627ec2635f90a6b0bf25d01bfdae576409297a6896e8430269c85151b495c113309f99046a99687ea3f7d8655ca1b023151be89b3c7dcdd71e671bbe3b8fdd785f62ca523b76b26e213b9f4a815ac73e3b623573d8a679e30dcf8c474c2b0fd899e5e9413ebae93b8cf5884e7c3b25d760a4d4e6c53a7e1ed1ec4c918c9327bdae0a8b7da141a84b9d538a924f8922e32420581414b934d1e80f1b61a9780430bdce3f5cab0a02bd9bd1f5dcfcab883fd9e86cf855437ab84e66b67235f03f477aac0fe90f77a891efa49875520bdd6063f1cfc76c1dfc9f761ac51034c07411f287621848b5aaa2f6ae92034d8e229df569284d7f2d00bc1288511fcd845c4972d642beec2a47d7064cc0578ebed4f788476fd7c5044ff7856216e24d6793f50935a7dba16608dafc7d83ec48fa872458e81acb505dbfc321310e458f68f4ff5f918b0ac1c1825d3c993088c64a195e0b155d6119de777d599abed7ff5a23e79d88cc00228602e88e5e1560bbcd958c32352d02dd2b73e0cfe146747186f1a681f00975a5e65cf045846c0182827835c4e7a1316007f96a981a1d0882cb982e2c8a36e954facb1a4a7ee06c48def682c91aee9e2995919aa1590da68494a87cd02c21500def56778225a8f07e6e38ce48e054814dd4a2c9f7a71bcdb208f097cd2e6894606ecb3e37d4fd6d18591c9455d62978bbaae1b821d437f3d771a624f26b04df3c2441fdf579541f5d6aa2633c7182c721a0b5197a7dfe0767b00b34d8f04636e1c6bfecb7750dc7364c8d1c67ebe7900c61dc44c43c5a74d886d7279c985d20b2b0483a449276887f41f8161bb05e6d4e751d61172841dce59efcd3684e9bd7be371d16d958c7be6a79fbaf2b150f87c8fa3ab2935ecd623b5be9b0b3b9d31493f75b0ab44a7e9a099fc4ac18a777e8be0e450c280ff1ad88cfad712088b877ae506d8afe2728cb27fdcee15163ce06f12e82e91d91f98178f1d2bc8759ff208aa359b0883a32ae70cd22b7ad0927b3e476474733d899ac2e79fc8385d90581558d3074a09a3bfbf04c26285aede37caf835a61579a9ab23cc1f40edc89de0568038c39f91a00e12ff10e3db4949d5d7ec06559566fd2ca9e27d6e1336527915c77fd6298f387197205f0b2cc3e645f11ee8e942f230b4efc0d83e10537fc69f573a59a10767c254d74f232a6771c2724217b850916b9ada09fbb5d424b9847280f3edfc104fef8d8670621b2370e369cd68cbd418e74c18b825c415777d09faa021125bcd283a794e86f3f724051ddd1acd0a30b1f8a564746ab5ab265ddc4ebdb8bdaa57f6e097390ce6bc424bbb0e87f0040c8678ccc0fb460c9ff604d741c21ef28030f9fcd1f161f91caf79d3dd1c9836acc994c01e6b04a3571eafdd96541925dfd66481a9962771c1015202cd1eca436900d82aca8ac2c2ca6b451ac11ad0662386d23b31c33f6982b1e7e0a6f636df85379dc78c058cc84d5545545515fbe1c3606a7a30702d223c62de54671b7082e0918a1d781e4f37468eb301e01ff03c29104a251833a2a4f8724a23b0650c7107063f2617774e151bff7b7db3d7eba90580a771ca1d931a7b82a70b796100840485d00d8818383bc8265ef44911b3a207bf05b36c8076c0b4f0bc91e25da8a96be76dc12047a3751b192c70172c43121a10ea50af10d8da1cae133eacf219dc9d8e8d92be1b9414fc2c0ca47bd6721d540f92db5da30491304d3dea28f1527da36d3493768e7c1968ef8487d0e08db5e28cab7475c0fa8a3dfba8499888b7ff171604c825e4c1787e9cc960a8f8858ed6ce4dc015bc12cd59933466647ae906e06e36ca8379c273c4f8a7aca8e35102abbb0c01bde36ae04ead0370bdc3b5427f4a033a4cef92f1daa026808ee65679c504c86664e9010660615349b458efebe62e57ef06d97db4f14eea0f14101d6b6920acfa14f37405224c583d80c2c0b5060f54ded923993f59053d549b01e1770f29595b5c9f240bd2871ffe2a36655f5226812d28a8c80cfdf815ea340efe713312dd8b0514d4b421cb09eccddf8e17b4d5b43f1b10cc9f6214cb3ec3511a39cde9aed1310a4d083ae3924e5fd23eda69e3c40ba06b3a3abd6e0d32c6583624a91e8a49c88b68c44c6fc87471f574aa4ef32e540c84907f0a2ccc099f78a77236c1c08df3acbe469ab359110eb2129b21a57ec81417ce1129ea579eb94e0c284235d6e52f0bfa38ccd7f08324e1b3d374280034dd5705322c022e3b895082ca715699363f9131175431aba4912e364e7559d89147d99d65a5761ae54950748c830a86d20fbb472d390466a72f1d0d1d02df2a09b5ea1b7fb9a939b0b49f904fc7fa90e4e1f4656149d39e87b68b9cfad8b8a544a0ec6a34a11cba620f315051aeb110f4878b314f7d3ad9bf0b84f04e0bd35519f967eac13e9b947edb83e3e877040691cc25d6b2b05f283d56bf898e8d7edfbe23bb900e2d2e73f067ba197c8ed56fae2e344238af3b5a7db379bd1205deb399b25e524e7e0bfe21f0c93b26c4fa6930887bc1bb42989c3266fb3eb540e4abc477283e9870d1773ca01f3501076a00e382243c5f61c5316e301c852bcd4274653e420171638ebf81a81920d598223cf4acccf62434c398a433a76a0545775253f32fba4274fa8871a21da06b38f50563a39468ad34b38f449e0f31315afca9ffce5a8ca59c0ffa79dd640660ab0a77a4ed2047a65047f8c8f65bf420f4a640beb3a3d0c30906d758452461b6fbd2e631921f5846066badff5059f0bcfaf657b157eadd4172a7489aaea04b47f18de38ae0ca5159f2f9aaea2712ec7c51c0d982ae69300ffc86039724651080d23f54fbf8b766248364b455970e5f917e4055629075ef5027e286706894d814c8924cf91fb989d5df962ba3f0c31b3712390acd4a36592ef0753d4b6f40cb5d31303c5a3d658c0fbb84f79924f576e78ddd96fbad03a0436d123f3d4eb73923f735e501eaf33b059a721eb7513f70370ead11b8d2f286a57f7f4304b097419ab560c009d82462de52da8725676e42a3de20ae6b405af9a1a5801fbc813afc487a928869e5892e24f1829e49ab82492307b7c5c9edf959ae2fee1e10d8293ba8822a2dd62fa3a66b56eb14592f4ae4ec840954a628291bc0f34d02f65b13bd5ff9cec78394e2c348d690e6a85030da89c08d06c96a55230c18675b05828e9df2530e4cad139a86f5b9eecf8f0ac386789a7b8115c76f6e746ec320efe1924780ed635e6beda5b8fb37bd72da64e1e5e5bcf565c8fa27d77836259a79bd4de05091fda4753aab91ecdc39a0e2d297e21b9fdfbd645bf2f51f508440ab7d0e699ef06a2526ca5883e3456595ab740ae758d8d6a5167174fe03b6d42fb2a5582716177294efb71e7b4f5b1c7d9ea1f241e02fd099ec2c4153733e60f557c21021fe0af14159397f4e4bb6b89be9455707269dc5386c1775f564254dadad5d74f96da078ece2e9281b99c1eb67e5059de3152c86bbdc07cfbabbcac93b1bb20a24cad543bf20974112fded5c1665e1f104f78579c085f0ccba22d346a9b62073cc8475153ac00c39a4bda38776a6f5e6b789e4a1ccec1991330aa50a05fc8112636d8cc3f7ecca1a5457e8e7b9a2c9788407c0bb49ae04e6ecc95c5e4d134dcd205a0268040abbcd2cdba9452b768a63e28fef1f09fbfe4ec8649fe922c0adb5270f94ae26f6d228ae9e705f20cbbec26c59df20b6f0b043f1073d52a85a4bdfcf27b53bd66bf8b27922e8c7b4c44eef2434629e2be5a983b1654a87987a9bc716cba4a409990a7d3dbf6594cf425a1ceab02b77cdf15a84ab39c0b428c4e10e38ba81b6cf9c12de399dc8bcb8e476ceeed16cdf9d4e883c9c9e75758be1406ddb95d3d14b0592981cd75d5942a128afc8260484925e28c68d6c161baec2c21173667f7ee895f33be9414d7f9c94e0ed0bdbe2cb5d929d3f479a5f2fa7ab21ade01200364b408b005965918cba90630d894ef04a1a46882850ec755cf8f1aae688faaa0c87b67093c211ff5f1dc78b9067eff494e4f6652495f0e7c26cd7e54ea31f74ae8a97bfb497c1982a3f16caac75f36c8a502c87ca982dbafe2140a9ed03cf1783b9bf7aeb81410ef0e0a90a4519e8f310ea43da4cd4af02efe95cede715b9a6e795878c4428569ee77109ae7502c81a8174c8e327e6e18b106f1f3e63bb2e7dfd5986577df20db447ef10e8a8183685337a7b02f3fa78852ad928486f227eb9e1ba0eba95cf2144a055de7b45bb5701ac4f8b234d70b1f3bfd031b847e9affef977fd8d817fe64f357051fe71a9ba0943075d1f7800ff1f9cbe8e02bfd3e75fc7356a5088b567e0f9a330a6fc8676f62f3d4786bdf53a8b204fa35dffe960bdac31c5e902df61ef559d8d286cbe82dc6f34709246c4f1fe75317f6be97877a27bd45b26293e3477091d93aba6283a42902258d3138e1a2a02b626f138442d391d18540690047105e596ac303012c5c2f8a3c1731f0fe948c7ceb8f21b7e1033b36ac5b202bea8a830150fd0925ba509e17831de5573db26cb4ffca4464066073b41ee3e0150b94a90c4954334cb5e181558294961434e9b78f3c0249e3386d6ea4bc6ac4142264220b897dab1d615f6f18950bc5c2f0268a4aa935bf9fc96190f2af3a75bfa08983653d20129f3ea123301c16f4dfbf1619e93e0bdfc70c7302796a3471266ebcc25b08c9eb7e99560ed179a276391e0498677215252363e6e5df1f99d3f2bd011c6c332c010a54a88fdfe32a4dc73dca00c87d96499fe55d9cbbb997146303462f48c8fa803e7674608f36e38fd146a5b3b250ea8973e544181a18d12ad1c540f27c72c48c0357feaaea7f1084bb669109b2d7a3b14e7369f143a3c2863cdcc0afb5ef2c71beca4a50b79552dfc6cf3be2d0d45fcd332cfbe81653e69da6d2f89634644d96a44b31e009f74e02d8978e91f9bb0a7b183a167d607e0476b2d1734334cd3b440b5e2f9875ea3621e6a76c2b926012072377c027fa7dd57428635e0f4c708010a15e1e9980da1f141b180fc896baa034bdd34a005a20355073870a0b3e9e1dd5338aa27025b4460414bf5972b8d99b995e73cc4827588f2e0c884fd38a243fd22ee0f5c56b263ee4acc6818e193a997a19d6dab0fdc84f04929351eafe02943d4c84dd64bc2868c7e19cc980976b4ca6526033853119b92a91ea141931b227b532199d19cb56d9a629d780010826c358fb0a2b974c2fb5ffb85261adfe1580eef178b850a29150019504273078634fdc3bf8117a4867639d84d34d524db60ba419d9bc406306f2a712656bd64458ac349d3890eef07303f273f309be11c27a2c93ac2ee51256585ac63eeb4d9517fab9918dfd2c690d27813249c12ec914586057a8df5cadb40e100259960a4064cfa844a1f2b1f075cef85308a0439ed680688464842bbe71f2561f2cf36edf7c279852205666c9782545055450b0471172bd6795ee1b447f37011f2b60506743e533df3b9cec9aad2e1b81ee89bcb815f1a9ceb955003559610e86fb7915321cfa7bec975d5c48a07a42119dc6042ea73263ade5617dbf3b88f8bbb536f4124ebb98aace766d12f66b5a40bfa6a5590f0b223c8e229404682313731e40c1d09e1b8f3d95ae190680d591077bd9004ccdf0a2b7ae7a9a61e1cce37ceb6bd0b1d6b9f4552822f249107e29df26492fb6938d3c94a4cda6a543eac138e05b89adac66be15748f4315e570e1df80311d574ac628b32ee87f39f31d9a4a7fc92492e38f201dbff072f95f81632129cd08366a47c8e1b20838dfe82a565c8868aed0a6ab3a70749b0f489649d6c6a6e7f494ba6b6686f6fca82a5e0117786da85c741e284a48c2991c57d637cf69b8cd0cbcd3778478cd9b7b07ad5265a38694432482eaf962516516d10aaf088016a9fb8edeb671c09574409d071951c844083cc127601d8069c6ebe8740d8d778fc76ac1b3dcb1f3994627f9d4c0fa20ad223a265d6637ef822e2acfa60fa1b84e5a5bc8c4130d41175c2d8a33083afab62f6bd0c06dd2bb4c6879f6a5d6676196cd1724f83d69e8df7f44ce4e95745f92454140554746b7a5f4bfd812dac211ca2f252b33f83889a5ea8b28b709b83b602b15d40dde01cbdd7da6a44920993e29f91d2826b2b3206785dc3f6f67c9e3d72fdba9a10bc143cc4d0d70abad5a4c3178876e17f8007590f821d68973c1a7e79cc6ded77806a47bf1468a097dbbb4123488bfa4ff066831b8d999a41e9a661500b4588b79bae5e90b97839d2158863c5f308cff6a83c1cfc3326c880fe98dec68fd5ee48eba4c5b5b1f2015fc2f08992f0106ab06ee8580b5d6c7897b55f07185dabcebff9b2365667973b70ab0e4ef2b0009e1defa78decca39be1f0579692e1df7b9f731510c9969d02aac610f178740e7eba302785c8ebaab62573e1c7fc25a360b2fafc0a6b57435809ae30bf5b29119c0a529dc0b280aa259f64095380246dd97b18bcc6dd5c4f19c02ddce3001dd344cf577d48cbd1424ee7dc089a3bdf6b326455e2dc38ba73d669b364b5845c38ba7fded13064bdc45c119a7fded732c8aa84dc315a77d6d7b2c99a4b2862beb42f1cc624824b9a53a87f26bc7df33e5b1160619201860e00cf08a5df4579c279e9ec3c07010f3eb17aa1e30864b0fe1beec384e64db0265821d0ff95577c3e7e0d64b9df228ea765790f171e8143ac8efd3da6702bbb05725d16b0543f8466cc59ed5647316315dc1fc73766ccfef09496080371ec9cbeb75e433fb552f6261edcf731df608713c89bc032df3243f62e0a47fadee91e5379662881582309b6f7780a102bfc4126ef058403c594a4a2e3568b976de7aa80297dbff9de38e404d10b386fd67d8347a37bce3188edee21109b3a84891639c5bc66d984dcdaef028b03837a5990f65f68c58670f058f4ea5771431ced58cac96311b5d5969680feafdcaf924c7084fb5a3b63039cc3385abb62bfec3a91f8fedde73228ee399f6b9295533e0332f10bea5ccf969c38e1fd74744cb9c4151a1f548c2ab2c2b75615dae928c4dc41aecb974c9b6000b669cf4674101790101729520cbbb961439ffec4cb518ad11405ddbbc5e7e5f74d8501fd41d81fc3d8a0b56bedf9494b80e90f92a9ee769320efe09dca0ad21f20c250f8bab1c27a19806cc2a0e30a6c336327a36454e74384294516b21337331123e0283958cd60b915f6e4b5d56cbeca3f165db05e6c098b4965eeb213efc1401f3e8958e946c0616fd48a010bca7713178a7ecee00ad14a874d53901c57c24462a0a20b09c481f319d14f045856f37f5bd2ccc6809a9484b4b03862bf40bcc00d7c3a8ad971de3b9f23cecdb891958297d0105f49bc23dd0e91cefc9c464925aa41a8c195debba4b4c1efa68d184ef28a6f27e3f96a25386d2c2057819bf71a17b1709ab11e5cd3d6789c0c86a03d611e577481efd7f9c777fc77726a8e4866768f5742638b8058434c7be31385a6249e729ce85baec8bf9ee2a2730648db6c84cc53d43b9988da93b23b3857ef12bbddbb27bc3bbd35b71e09a2d9a9081ba0d42b199020725b09ec35b0905ab8a0a982f656c2d65b4a814493ee6c19f34dd38a03ec0021a61fa0ea86c1068868c80cec4889836675d2467b39c41aaa436080596cd8263fb7735cc519cc135795155692f7b67c093aaf045dabc40f02c644f24907024611d3d5a0383f8731fe4dce40fb30c6fc8856b838c508f256cb5706de7afd8bb5267e67d8f2b60f41fa652d13dd356ce9ed7bb7497f474b42b93e7d48071c3d182bce81f45c19f619ad4358288a0d705a3dc122f9247967a817c0d73dd4f7e734a1ba055ac365fd843eac66ab8b168845904d8f417925844dc0cb416965828df841671bc66be8a44f8ddeec0c8cf35df36b5a7417460c0c11f7fa700333261dab2bdaa23a69ec3aa823a8122d82107a9b4a3c801895997f079b051e098cabaea0b792e3cd9df5187ea2e1f8355b17d3fa872ebf83a919e4a5fefdd3d4dc65eb46c3a5cfd6327e45a07eef17a1c395ca0e91a76c52545209bd27c160db8d304d38f7434cffaae8406057421250b4fe6cf67b0263ab548d57d1ac75411f6d06f4c36ca62e3c194e64cb8caba3e43b0403403fb2eed25a8bf98f2cd2adc55e433f1cabae5f28393f9b48926dbe92c62b838df6cf268a4460c90478c3ba5bad35192042edc056a4a53c4acb0b3206ee36f50d7ec7029734ce87806342a622c534e5329a0b7eb1f4d703e2a8df9f8f1a22612ab4f924be7fe4388404a083e2429feba6752b025d490f5c567888c520ab9a15847f260d878b5d0521492da5b583e845ac5e7e73b3281040f7b5a26ba38a33516344fb482acb93436c2414758cb148f026f24477c72720bc33682a456f0247da050a5734829e96ab83fdadb085b151fd82636fc11478b1cc1adf47b06eebb72b21d4ca44438ff35bec63d04bb8e7f78efb2a97abd6c50ad410fdae7d55ba23e3dc2f052bce11d5adbd21e2e216e2035f7e91f55a205dc71775f21239742f331593b3e8a94ee2bf6106e8693c704d7b63719819d4f621a6c7143767fcfbfef8466fdd1764fdc33b30f1bed29d281188603db9960a8693bcca8ca36772d8717a0e79c461f78837fe9d8f7348db3a784cbb915eb56ef0e4c8db10202ce49ff8ceb62e5628e1a645d86d0f14a85e4090aacf01c34eb41809b8147193021a182789bb272fab02d28a1e79d75b29df7f3e4d69a03d49b0eeffcc92544701b3375b6187eacc2b5dbfbb06e76e38693bf162b82ba7ca1d722d57648b73dab36c176fa4f176eaf5a4dd7ecea5fe30c08b189815c6e94669999d708556233033756d8f1867d579a12e347cb8fef58c9c514432776823415d31422555738a0e8341e141c99d600d7287f46e8dd6062809d2e6c7b8774843b92015cf8acd29084abd686c249ea5459d09c0ed238c04e58ecc4b6c7b7085208f45fb26f5b1ecaace9c34328defec703c4808808e003d6c0865857f1a1f3481aa3fe3ee2dcb20523bac3ebcf4d0de6a9caebafe506b7f299b1305ac0e9f96998d814c4d1d1d2b52ae8d5d848a922da1bb97bb8812954da07f076f91e031013d5ab634e8ff228f5a992d89d9534711ae3ce24b9dc734ec7d48633e80f3a85cf8bcb9629af263833f6a8ae05b72af015ad063f63157818d8dc2b48692987452f7037f5319fe86c5dc1b26e42a334fe56d286adc89059e2d6b9be48ec9d6dc7288d396bd267f7770abb5be0ff734184ac5945b39a737c2f2bdb935839380224c15d19cd358737b30dd6aed65f6fd8f5c4785993caa0055f7d4ce3a51d9df477637bd67969053298fb9e2365f562af11066043d4470369463909ad6b290fb1a725bc9579067ddfcd188cb86dabc9d86882e0dd3e5b8e112220730aeca3da047a1cb0ca5fe1ab6e3dea0a682713ea374e8f036eb6e61ef00f1ab0d457bc25daff57cfe1616c3ff39df08ab784574a044d9a92bb4e75b7eca9b316a8164a140cc296cffefa455541c3199970865cd34950a18d348d6ca6575625b9828a91026b77ed5b762c5cc76cc2bee91d8e0e96528649258370e9eae60ff9e81509c61a63193099bcf717025b40a64781e69ac384dd00d53db06dcf1937d1d21b610d545f6899bbe635497151aa58a2b4409d26b08746eb7107defbcc1816ecacfe67672f4cc715414f1b3b92347e1bc8a81211f10a87bccaecf88c15edee4c96f1430170459d0d40727becbabadf19b50971673092753ae6bef0c7398b0beba046d4650f79a8e185d0e891df4292c7923b21b7b129dcdf2d3deab3d347c41b72b6a6734bd5cd65a759c4c7220bfcd56da0da0c19eaea17ce3ca8b2be300ff2304d6ff3491bd968967a03ac6ee3a00b7b323ddc845a51758fb2fc5d4423929c692974a697292449473a2bb6cf54e54e7b06b08c93cfef9bd5456df53213551fdb27bcbcecdf39010ced95eb9b94c8f9931823cc52ad318169fabc421a5eea33f54d319205d37e7782e54d4cd87affdc1495e69a6972c0ad5ea496e4f544f3f4ba9b1a928594230b3a7261990953f56d3004ede33563baeef9c737a58cc5e002a179ac7f4667dbf06d46e638850b1ebbb785cd260ec7b8e5f5abf0d30c2114eb80ac260b8a1bcfcf9c17a7b0990067ba3b867b2e6671bbc74a0b0d26fde09fe36596024247406e9af2f0f91d07f423bf5cd424306c7f3804f6cfa2620bec1efac49de38c93ab8d6fc54d02447e3bc45e5cd9ad5a116271272c10337e7dae9e4fb2bb5a6b99fd87adf3f7f437d4e20dc0094a80d98e30705247df6f41ca6349054219a9f5b53d025175956c36c5c15b4746a5b417ff2c11ab49015fcde18676074b7f3e262a1fdf6e43aa10f917afc9e0dccb1bf7e707d42e95e2af6bdd6a07de57c1eba5aec36e0440c553d1ef03905901c0208553c5b7a3ab666fed918559b82449852ae2bcbc856abbb94d3076cb59bacce2a075d903135f6c863c5d0df1c08d92de5e45a6d797b4afbc0480def944c7cf49d2098e276cb1bb84d29cea20d3f2fd766c1f5609cf6d0639b1c91268cfa6804e76e32d0af3ed3a71de2b0bcee4282d64eac5c8369fec71d8f8d1e183ae0e5ccefdb7f80843ce85284f8934d26aaefabb2f5e87f7bd23b0465e65c390b3966c261abdf9af332d8d5b050ff0cad66b572e2cfcde4e90572d8a4abeaf70b3ba30221d53a29c044f981fcf06fef7219229e2430067a19812a8ed3801541aa4d20d23dff5d192216f15fe3279571b568abba97681cae8cf248e54c1b205bcd8c6beab7b99e3ca5cf5854c1a2089e5f015f9fee771d796f91cf4244997f6e9100f4d2230e3b5d9d76ecbef6e0ecb8ade5bfa0583fe143515905881fff3a3dd6063e200259347404bb6421affafa6e71248b2014e29cbdbc65365a17370a49f8f74727be19922277d085c3a3b4f007433abcb38770b87ff77c0f220e9b10d57c11d65d9d47d0418e6adacc05764d181705ccc810120f84fa4973f53c598eb7e1e7e2bda9be0b69d4d6b6ad3d67166da0c6554d3493b80b6f9af9fac4ec833331aad6e0b9ca0f4736fa73aaab133865a028047ed950aec2d413848bd55a9e3b43e165119ad29101200d014924bdca1a915c2ef7c31301780a9192c2a1139c1027d261b5abbabfad54c22823a18bc5a24ab577b3583189226671d6157dc29d25f91b6f04ddc8fe93a3853a9721da17f98a578f4b650898c47b451cb6a91d1504c5d7502db3e1180aa4ce9a8541a5d36a949a3dbd76ef180d9eadf756b81496b9cc69502d5de0c1c397557317f07d1829938d5f5aae9a0feba2471f1e0b41250e409fb5cd15c53cc1e7968d18f75618be3ff2de8c86824d36220e9f259ce0191db8cd76804885147dc06a7da99a78a756fd04268f7f2e5653f8a58dd1122fd4c314d2befedca15a1e5af8f46cf0f5f728a1b0a5b6c187e1d74edbbb0d8fd28b09b1aae5c5c2d101ca435ca39c392f8e9e65521ba37215945b9700459a55d55c574d19d71dfaabb59073697ed77dc3eff32dcb5df2eac5c06aa2b67151d8501da482cd1b0e1e465e8a72d453468ab4d7ea88580fb8763afb805a9801a680eb8939527c0992b4945a151d7c4b8618ddeb290a1cee3cd81210a688f1c46d1ccbe74fa8381138fae65331c4706ae49527f95e8a98b90b29349cffbc47b84e6c65e7ec1b28ab186b49331f6bdb7a1e404148977f07a5791fda942f3e43f0abc42059bb0e58cbb44c92540f9b796f4c72d6870a7701d5572da1cb290614d520e85b4a06a55c7577359183693cf9074e0fe9249c6dbfba39ee517774f8be997f1375b238e0367342b595d07433a9278f10e1a780f4e9e214fd0e07603e4dbe147f1c841dfa4d81d5427f6d3c16d5e8fb19fa38d6eb6bb8819886a8b752a7b68d3c951a16b6193ef55411dc369bc10b31c1371f43fadcfd315c0fb7f0c96d18a3b5a0b93154954a1a36de31d34137bbc48dfbf6e9c565c6eca88a0c274ebb6707ffc6963b885ef6a2d7702088a440178c4b5541f92bf5dda5d3acba75a0140fd3a765d3172f159985b57bb09bc1687e03ac02653cf152fc30ca18248d32c36e8bc0bcad0c069178df309bf415f016812d70596e30a34287d1f134d12cd64b5248465443ec1e2e36067a1221eaf5b8f63cb252cfc8bc27082c9d37c98e464590b24d8bb41e1f329ce7ad10efe2cb035ea4ba77991453f9b7b49493f67df5359201e5c54f5904b03aa334f60a9c1856c9e62cb72b1f1b91d93f35c8a3cb1fa2782446047c7e626068c4146e0ac02ddcd8adad43c95a138ad184851d10aa419b712ef67ccee478a6ac320c98a94a72351b89e65b1f8d9906c678205445b5eceabfa347e04e749ced433b413e59dfcfcbd59133cc3d7794a1881224106ea5a33c580800177a3c3df66835720bd098b95aacd64d284647be02079049cdbb7d387d6898acf6104c185ee10231297aeba00eaffa52ab2d6a59160c9f9be076e35a6ca29f8dc65969e0bad5b423c2fd73c06a49b73519cf5222e3f673e9075608b2639a2de9750dfc6693b17f6a59515d45b5bc4ee40de38843d99dfc0f32109e042fe0e24fd630c472c6632fe332761a1a4bc0de6deca9dd8cedf4c0caf69d958886b16a725d3122492d99303b1e986c078660e66aacf4b2ec202d4aaa22c98af9babf64c728ae1080f676d2e5e4a2f2d173db7145cac31381da05f17f4a4c4a9a1242664477c0514f24cd24c02f06ada9e1c3536960899f0a192a627de1eacb9759a1116ad6ff572c2814e33fbee04c020a2784715e6db9d70a3534f2a369fcff610762392e787c3feb6c423b042be20c65c7178ff284285745927b79ccb654aeb12e55e6bf57fbde4b0c25656ec7850a482bac79b2ec1fcaa39edcda25343d216073a761ccf5f3c70813627ab3eb43d918d50bca626118bd1be56957c2be9a6cb813f29c38a3cd92d9866e500b9c551b01caaf6c207336dd274934344e0ab255693ffbb84bf663f91a99ef93d18dc3ed1726e998009c69cbd6c7eb0b57ef3a196c778eb27618d6eda44b18d1a232f0645ce594931c99573aa1c89773228299f4f24f18d38dc81d52c411ec747de7d3e5cbf66e90e8d5786386e0db0cc8154561488475532e3cd488faa892bdf34d42557727924d6f518054906d7d70354a374876389ca3c8126644aff1892f7eb7d09188ed26de5b7b46d3c9f12b583ed1372ab797198aa8eefa1867f65115f40433af8c7468e52d8409cc0060a957501964b402109f5fcf3136d53499f135f051a7f0ec680da0762680bd0658a7b43fd30e61f881e04e3578041688e010a7cd7400c0c95939146b8aa81f8f96f3881b0896ec4bb8e148ab6de245df1e37af156de9c5aacd85a6b51490cc4886f90184b03f304ca323acdb7b054f81707b04c92aa05749b79813a5f1530388cfdac5794227a0dcbeb602492d9032f734143551ab04b2a9155928babb891084de5121b144818b350a2a36d11088fd7d7c0cfaf497b2dfb9c24d70c5caa1145c8ded7c4d40598249c1b3a8acc78212eb47f7d9cf7f869ed030921e7452674313f6a56603286d102c0fea8cd9cf77541a8173599ad0e9172e9521942aec678af80c8e9791d046535960a947a1e484162be717ff021f2cc1d14439651a24ad2f24f73b885436508a007e0d568e14cde40703fe595f90c47f334e148d82b80348d44f6975cd415506b4ec011a78b14d5c40ce514a1b69e56acfbeb65ed934fba59b440113890842f49440304c237a3d1c634772ac96a537a3471bdbf406284309951dcf1c53fb8ed877803d064adadb3b409de266002cb3295822112634528b7008e8db6e4d526865777777ef5705fa044704978990dbc4876db3c316e5b861beeb44c98c0a46898b1ae74d929a21331685321726a6a2d162ab4cb164967459ba2f8440f161a3ecc092e3cef0bd5e336a98978b4bc39b2b44cd153356479915c418d1627b9872a52cb964962c1042ac0c3e2e8e1d974a8e223ecba669864da1c9c575bdb932a8a945989152e6ba10636568a9c1f194aa03c74b7860b22c10b14c8034c97073b8ae0d1cf64dab7b3f2fc40c35fc76f8c2e145f77ee68f4c32d5e7d3397f7ea8ec605302a698a46860a6218a2e12132c92221815490a4414c90b44130910222447903829ca3a7c21e9921d8931632f19c9d04b2cc4c8ae0e7e7bf03b832fb529b24333ea778302c1e373a61892a90951c784c504adc3d70d4ffa044d2dce9662f46d47ce6869ce49eb104d86bbadb5f662a78c49b673ce39d34b524b30be23b7d486a58772581fdfabd56ac57992e5b1939922c7d4a46fd267473bb1b3b373c105da09da317d61c2a2b3ac692f5127bc8e9821c2428fb0796c2174436b0d416cece03b9c57203e2a10a5126a16ec46d71f687ebcb845dc31dc46ed61a85f249e75f8ba814d5775f8baa187eefd0fd209387c1dd9d22f15a6eee1211dbe8ef0781d69e18851861d0af491a8236632a0b50e5f4c524c4368136c510a2f0079213982c4894b5f38378d939e9eba85d29eda65db2a178cb74d48191ad8c4f0f4f110c9d02541d674d1af1d6b7894f0e221830454720c42e09e4f4f97db8bc851ec91401efe305d3e4fa2238f07f66baf752c38e0f180f6aad75ef5b0a76f50fa400a640e05ec3e9001d20732206a3dd91dd1d313e9cfccc1be49a0e8a9d85a7827642bc47262d150ea58829ad0a9138fd3fbe9b027f6c75282f00ac2244e5c86c79f93888692045cee9a7f05897a7a058981012d017491d0498b2f71e2a4a901a64b9638452e402969be0d5a2df86600f94898894e5cc8e13b402b09cb97c3d774011106337c0838fa2010d492402b3211f219c0b6a8c4d1b1c3c702d8038e6f44eb4c8b143e1ebe6f417d5ccb480be29223d08d960d9660c05185060a683e2c546cb87a5a1668edd8014a479714e0380111b383b7e248f996be0bb45edfd0c76a4df930d08a4264aee5a98822578ec068ba12a48696abb6c07c0068f9b026478c6205881ebe305fd4475b302f6aee8b196374a0c10508a3272b4a64b850c3f7e4d3a055e3bb52e5f369692d182512a1e4e3f24da0f5e48b7d73a755c407a4d33a01062f5022d2d307ad347d5beb06269f980f56e18965720b42c53e1746aea886cd9780561025710534f00748442b4e552844d40106c1e633e173f215a005e653402bcc9b4f95410bce07a3a018d8e6c670f3e32bd1e2624374fa20992803ea30e5b3c013dc5224c298fa220a3dbe29374ed8c1e3fb7e7624c1e1ab40005a43c6c42f54a0191648f8983edcbaf111a0a5e51bc0103f7c6bbe9c56035ab08c8f0fc9d7420480f8742b886fcbd764b7a20a4a4d5ef8e098183e1a86804b8af81cd022b2c317b3c02cab16ec12afd068e3838db225c3b780160fb304473e24a5d9f14ae251a5c80c574cf3024a7d8922f851a40c9a3466a468500172a6e58a2550e095b5209637369ca4be335f4453f441a9f7defb3bf7de7be9941b27e47ca5f4fb5c02c1179016379474ad07b4200c4ec5626b96ef67ab577ae69c73ce08b9f4e0e26393f9e9a957762080830a0f3b12c617b404caa2731d6e0260e1dce1c79757fb40eb8045a77dca37212508f2e757ed830f74fa93f3bace7521fab2be7cab9d90f346a552a964c021d507e09b8c31caae87c709680370482db5db08ef78dd0037e7e4d2678305f81c71cf47023e90ee67a33b88bd002c4d000ee90046441efee24784f11c4019810a680c04c0e1434be5d6219490f3b60853facc6ddb38eeb649a208af0131fb8c144bc4cfb252bd527eccdede0b42fd95ecabbf9c9793ba4d01844530fd3e8414d8d76908c23278b3fb5ef63aded5cd4c9002187473de934e35f7b81d8f169d66908c5e60908cf17e46ba52e6a4a49e710dc55dfdea66aca9a4aa3b2ad979f3c9fd0e4012f7ee4ca5fbf46fa7a3dada892c3f759d73ce5caddaaacb3d3252d58fb9ec73386fb3b859e585419283600512a8beec22c4a619349ba67c0283e693f89c93ef45a92e3b4d5b69efed9e7b601087baa702c2202cfbd8c998b34cc5d6b44ebb5deea171d9dba7d93483f89c4f6050002009fa34c2ba749a41d16992a02fefea55efc5a19ead3ed31eaf3a2fde00c02008b50ea83a2fd28f4ef777ee114bc4dc23cbb29cd4f3bdf7e61e51033b5971ebe14bdf7df5928b6b8b54df62644bd116285df7ec65d17cfdda431c638cf531278463ac3216d9af992cea34621c238e594f9f8fe17ec9ed87197d4c1f4b222c75649cd0fd113a554999bd248a25e467d228fba8ea843296ac114be08f19cd79cacae93c298b68975d2a3fb8347a30837e74a6f4299544116359d4bd15bf394d43b17ada6d69599ccaf85c671e4cdc792a362a5727ac6cea3abb5996a5d01d4ba22e7f47aebe8ab36f624a59c327da8f1f71ece0f4f805ddcf24317fbeac91ed747c934fb72e443ffbfbded62f0d21d3bad6f1dc9f19e4c12431bbbdf56cbf7eaca1c85e6877397b0f76e7a9d83aedb2462c91755eee648da8417d2b8b7a9435603960e01a1a5690c8c482c878c243a7cf8f3a05a2c30696231a638c31c61863a46f4232799cefc06d44dcd76a135f4e66e8133f421f28fbe4668792b5012b4ca40fec3b1dbeb0bce83ad34e64a0c4e7670d45a69a9d551383cb87401b4739379e1d840cf8a2358658d64927e7cd8f31c608bdd8d92775d239e99c4e66fa9c73ce39a70de2b483edc269872fa7581503a6afe1573a4f872fa7214e3d7068d8f39344fcdb867b3ffd470253fe0612884e6ffe6cf0726af5d9e1cbc946f77e627082d1bd1f3070aa97355ecc4c89593760943855229e6449420291129254d8617358e1b2e0ca8d7ae6ca50858a63691061e34c91282b864b967b14a5d5042b728700a97060d8a2e3167d56ca8a0c3b6585846ba54d8d2090ec706b1803c3161ba6985142e5062b4a96870c6b58b85028581caa1059a22a2e7c1451a7d2dc2b5296894c6dd3c5a8ca3581090e4bd6480d178619507059352adc18546a6ca162829d317597a801e2831d93c37de1cbad726506273d90d8d610db828fcbe3851f38ce4c29ba4a534eb0435a906488da821f6c0c65ae92305706161750ec100e503678f9f1e2719d72d8afa52425c68d22a585106c6a1a205ee830c413102dd6871418afabc5482c88bd11837581054b4617499419294429c13eb9b937a079c2430ca84b840b0d54ca2cf971c47ea1c1b200a98e692196c20950685436505edc296fee1721ec911e96c0ae0b5e88b0628534c1c2f48390a81e55ca05eb3ab2e1898d9be4098c1b048e85b2a60633b54cac05989be4d4e4092c0918221647920a3b8856e0e2e4c615e364c88a8963b51071bf33954c940d23c6be90658828f748533553a40601e432c17003cf3a9ef0fc59a32633ac0e4d5cdc2c6fea17354b6676286382189b468b7532e5de58b2c2920f422e0d1fd5cd8e38392c91cfe66032e30d131735ea4dcd41cd8561064d19eb8318bb434b0b5396dc1296ea9310383e9c769c90c3fef0591296ccb82d96b8a83bbcb141a8b92c98b93bca14116391b4344da93c2cb12d580afc592cfe946bbfa2d9df57697b4b1fddb3ceb39f9f473ebe9dad6e5c95d7e73bc639ff0de39c6f18e77c4749a29744de1c9244bfadb4adadf888aefdeaf14a7b55277736de776cc7766cc756f18a01d3d413d0e10b8c51d7ab6c3921ef3efd117aac5a57ebaafe4abbd8a33df6fc26a486cae67e6db5d2562bed4d4c2dfb159c907ed8a3b6ea346d6bbfb35527c2ec2b6f842e1443d0afbd09b9daaf75bbd3b8b6d2b44ea5f24ec0c2e37d0665f2e76b0e775e2fba364c296152ca28b794dc36c9925376acadd2f6de5b4a2959acbf40b23695d4f68e6d3ce330a79df1f59cf9cebff7de873d725a07f4c678f3083dc2f8f9be8979e7534ea83eecf1de4befd31941a0f9b3f572d41fd0e1ab4b941758f6e2e40529e7fcf9578f41c62083219ef6544321743b2f665bbbdaef7befd5b4577150fa689db759af62751eef563a2a6d5f9d751e8b8d63364afdeea80ddb513b6a85b9aac50c8a479fbbd8e24afef2dedc795c074a12b9f3767ac624f20ea7c36d2ab6cebbee72abdc799248b56d9268db766cc7766cc794b8bc5134ecf1a6ab4b38df462a37a65848841574fc7376ac61dd5b213dda57482fcee712d521d0e18b8b52972d5d92ba9e1afe2e3a3a023a7c7159d3b5dc76efdccf865efb3c24f1abba216fefbd72e5bcc4f839e7edce1fca20d66f0e9360759e4a753b9dbdd6a9d8fd6a141e0b217de967f9e3c49d50fcfb56f73abbe9e2f292a08a4c445daf464820edaf8410c2232109372cdaf289a6a56f3dcb792ab66ea1eab59652caa30db5942be45ed915743fef3cd979184bfcfb3dbaa9ed78f6e7cf28b5526a1a0ad5d36eab5674a53f5b451939e7c8d435de9b831d63bc6517655c1d6540bc37cd97f513b3ba9f2464c4121abeefed7ea3f6dadcaff1be5bcf1c89ba0d22939a91de51b5850e09345d12e8f2309ad288de30cd09693867adb5ce764c6a99e91dd5a356bb5af9a6556c1b8ddeb02da5a3ee5bbda3e2c7ce8b51b362bcd249568e77ac6a6dc376d48eeaf7e7cce4e5546a92a05fdf7a3a1ecd2495a23954aa53d5a6529a51d75a6bbd631b36b17c16c7b949a34b67493487b2a6f14d6fdb8ef190445a4a29a5de22afaff9b66351839cafd174729a41d8494ba27ef57b2ab6ab336da9a66d3bb663f7de1dd5e38ed5160000b3779186291545230994fd6ae84e9973ce4258e72ce5cdfb76aace931ad6ef3cfa3c55e563fd4386fa2996889fe9a82b354c43c9ced3b06efffed4bdac2c598f3b0d25f39cfa29670dd3b09eedaf3be33f8f661075cd7a5f27675f9df9d43573a8abcfd7501a2adb455d4b2ca5ccaf9f34cd65f7591ce7269545790acb1753386ba8782481e6d4d454ee34541c92457b1e79f3284ff5f8daabac56a7baa7024ef5fc2ab935542c113514d5b07e73ce5943450d2c55da2173637e829293260856a8400265cf730832377ffee5ac53f41261936b9da5949c90065752decff9ca55279457df2396907f71cc71af76e7c97e6bad55aaa8f5d62a3ba1fcdad7ccd6bfb6fecd4656f26326a6e4702c91659ff9ab86227bdcf1a48859769d02cbae5f87a0b3da698f5a8ad8a245c74d3d4b811f771406f7f0be1c84190f0d257ec2efc3fe3d3ed25a6933ff7b317e3db51338c79efb82dcfafc4d3b4e8419e7679290ef83ffcca07f1c84cbaf1dec389cb3098eae2fed7b527642d262250e218467b66ddbb6a6268d2b842bfa7c9c84cb1e3756b1bbec324a1f28a412485ea8a10e5f63da740574f81a33a5e3fdd4bfc3171453d773d78753ab1fdc0bc28d1327ce54975bc289f761d55258bce989979c0c5937c2e6e0077eab7580535889dfe2d87952c82e1f0523f058c01e82b55fbff6da41977602cf6d6246a8e956b131775f73573fa4da09ad3b1efdd9531efd90476f5d9fdb0f95d4591dbea08eba9e1b27e13e12a85b1fe12da0cb13f9a38a7268d3a376225aad038c84536d37d450e80e73deee18630ef0c6633f7b1ac2ee78ecd78e1f7ec67994c7761fd48e3b082db431708867783d45bd9e64c849392923e51e4a34dfc9a2e630ec396317380edaf2334e7ed434dc91c3f425fecaf70b7cfe7dfa3d9bcbb60bdcfb4ebf4702e5a7ef2381f0533146c4286d400373536d03b0d7c7f5df462855754aa9ba1fe6871e26aad3dff4379a8112c76f515c19a56ac67e1f7674ffc0f49cf3e78ab288de8a6eedf5846aa762773acfe82d6f0b0ee8ecf0294f7e2198dfee15175f8e45b71ce5f0eeb4e326170d95a9de8454bdd62a0e05ecaafd50e300a5afea549da75f7e55a954aab7aab7af82cfa29b9051e3368b9e39155bc7dd8e4dc8b1d051bac94918e3d873c7e301ecf951c09e3b9eec85ba0c94384f8afcf473473fab9cb76fc6d987da65d12fa762776b6374033b8d54d26e47d51d5525d1e7bf99c3dcedbc5d3b6fa7d34e6fd88e4da63e87489ff93807973b96c3e5e281d424c53524816097c1101e87a00a344cc3a4cfbd1ae6a274ba7aa57148eba8c14bced34f9dbea59342450da8d151af2f8ba44fedfa5c416762d658ed0a5cbe1787e21091042ac2d4656fc08c8e74f4d86dfc35318142c569cb170a5568e24dbc89a3d0c4040a15a72d5f24909e5fe697f99484a48494a48463671bf13d3e4a5fae3b0a07c70ff9ce36e27b7c50e071671bf13d3e3bb6a5b81312dff9a42626bc69c764136f9a7fbf297badb3a66d4444caf0b663302243b1a8c72ec66e87f3d67e1dffc66e733885a83449ccc7986f1bec465c287fd551290e8946229304b24c221409a4a7916964325d1d3f6a1fd09e230fe9e3923e481149027911b7728df68f387ed8bdcd65c4e388eff15940fcddbdd83d0d1535883aaadbd74225baa2ab33e9f3476071f539803e3f2a4920f8d282d4e7c7214ab9a32154a5a92495665074cd1af36759ee94228fae19343b0fcfb79c869a4dfb864b67a3d1d1d952554747e7374c474767c3747474ae0effd58a6bd4d6fc392cdee5e88ce8fc731ee7b0e8b6633bb66314b3f0a2c71b1dbebca0d1af75395c7bceb5e73787131aa16bd16a57cbaf5dcc2308f8335bb7f61acfc9e13939fc4d4caefd8a135271564ece73bee2bfd27272de8464a954cf73567ca5d9f738bf39fc572baed1ca591eee2c7e73540193aea8848b9a6f6ac8680600021263170000280c0a87c4b2284962100ffc0114800c6eaa3e5c42925065712005610c04410c82300c83208001002180106390219e7b12285989061505bbf42a524ffccab071422f8041f131fb41f4ea57c88942580a91d21206dfd5957c35a087cace3608d4af53f631e923629cf242bd6b238b34e904b0ce5b7f01a4f816031b351aac327b018fb6e08de38248ec3c0406579836e40df680f21543d19647dca8c1709e25d2d2e42223b002236802cc048eda398371100d96029b142d45552b44d513346ca8e30419721618688100e2d54cb00e3b6e9282cb298871f17224332117c69fe52952fe1b85d9ae43eec032b537537551b0903e41287062332d29d0ba2baa6a3f39c895732053a45a408ca04cc99e106cb320380fbfd17a8a7e306cc4011c7c5f4b210706b847d4b28608e61da6c0932a7ba02383eee82cb8e40bd71dfbc57ece02b6a45a909dae7f4883497ee5eb436ba06fb5b546009cb726e1bc5c89fe6c80ff25cbf94fa41454aac0bafa08a913962517afb442b59dd5ed0a99e8017a90a43f83e9afc886b1d7f8079f3dc592d22052a7ddf6c40140b0d42ba52b1c9e048e8362aa53c99aa07f45516c06e0cf9f2690b77dfd36d63d03e3cca2a721bec8c19d0228ec2e10829aac8b1405831afa231d419579ef4ac00bfdade159f3d0efd14ceba8280e363e965f39efcd065099f47954557afb14a5faae48d512b934522c405f60f06b4b096254871cbb5cac52baba6fd501e45310654c026fb9cd18109988ced438ad539f0b139629e627598d09441e38c8402e25cd909a437ff7bed280415d27d9089243b2b78c7ba06b7c7efe132664d2e6c06e8f985a165f394c045ac2ae8f0eecae05e13e2cdb3e988e7323852480c5fed4e68995bd69addd322b905fd46cc52afea52bcff65077c51bb8b7689cbd01447aaa1acfb753eb39a73dfee37e857684450a649bc244ccd84972ad63c19a925126af4d9415062ef4b201a9a97dd2e82f60eb319f71ee148742c8de8804d711ab217dc0ae1e09b79a24e16700be7e6457fb8ea0b59b5cdcba1ca07fcaae7c3580b324954192cbf63918b35a1be051838fc7a5cf816d2d3919383f194f3a70b437ffc2b827dc5ed0f73fce51379a3478b9be8cec21b7db064eec248d1d3c6ebec5b3454c7e8a638f52192201e70624712214a8517ab964c97c2925b8d20d8bb29a1af5e9b72a2398de0511876c6d453dad12a739f4878845cb17298466153c89b67e19e1f0031141a0c5a614fc19a72e4982b104f9dd0ca88750acec475a4ed8ae5f173d9852e2e6cc14114f76667149c0c1922f9b89b6f1690cbc52026c51fab2e231791abfe1364a9d1703db4d3d54ee868a487911be2de2b639eae8d9d2509dc853244d8a36d8e991292349b101136531b5a890c115872e508b048e2c652afbdabd055e2bd9e38ffeb16f0faada31a32bb2461f7623713fd2d18e0890920fe4b86636bce3053b147047be03bfb3529d72c5e5802e0204454be567f52fcfd73f4b9d52d191af71f7e44b740c1da1147d5298ffb3fa9a4652106f7fc200a56f16315ec2e512fb65b0ca402976666e1a09e065c07957871234dc00a8b73131cb9ea2c6ea911be9da639322c56a1fef3dd319152a81513ba9d7db4a096c4c9f1b9582ee01ca68de7f274c49598b2592fed116b8f4b26f0403734cd12cb4c5de65e8f1ba23ccc247b2058693a204c72ed78c5ccb124d2edb8493b71576b3bc9b2e165ce5449996e816f38be4a2ce4cb41969253a7850498d55d2251106d16cf359e3489abbb41597c40144ebbd46d2377c070c7135ebe492ffb985c4162763c21dea30a5ab08c8158ec2a03acee282df7511fa57ab1b9d9ef4942eeddbd80d92ef0ed341ec4f4cd0e73801824ac66a78e8e4454a8f7a7bdb7f10edf0589c70342278a66e72a7824058147320c3347881e748d8f6752101d7cbfc429a17f18be2bcd9f6a68d63bd8e323d12846ce5975e32e1334ae166e5c469c20769f21fd061015a5c6a3eb40387fef6d6632248b3daa0572d09a8479f3900a037e93a976d5a594096cc59ba950c3742f9a3dbdc2f69a65a0920bf3701580df063a4b8f21084e1f33fd4ac43110a498884761d376b4e5c63046106a9acdb857c31dd66d8923f189cc33bdbd3004074818d10e7980f6056e9a8895fe77bcfb8280a49bdb9070ea3d9a3af6f5f9ec5371f8f06fccce7f871141478448b44a8e36cb3d6db05181dd8a2794891e1029c7029ad671fb96cd3d50e690a0a4a88dacc32fa3b62825e302f65cc46387877117d1c1e0e79983c5153b82ac19c7571d527ad327204b76ee2f86e769e4d505f58a390d26ec42ba120b25001084303ea5aed2ffff6ca1981fc87bb847a8d005c42bac986e49cd9b637c73097021b1013f16566162967fd72fd9e57ff1150229e2a43f64aac838ac38cc276a86ab93e876324de5543403f7d0a8f7c8f75d20206c00fab05063d9d7d39135b3e6b3e9056543e58a08ef323f4a3fe72fee3b31b4e3e2bc567eb5f2cb0b2b6183a1d85f632008dccc3246c61919826e10675b4341898dfc49f4ba208786ef151d01f82abd2777e70631310b439bc71bd88e08b562451f6c13ad40c0c30c818959500701185f5487c856bc90a1a18cbf306f67f60de17f186b07dbbb02ec6bccedf91219e0756470ac1f42be1ffc999362cfafb21ed2c2968809468d316c612baf839c2f7c3f96f305a2e92ceb9e17d75be738f641608099517e481214e1b78e6977743c8974b94efd6c2bbd1cfc333b891694a0de3006aae9c5aa2b72561b269f33c0513a4ccd3bfd188cf69ba98caf9e8af7ab279a76d8ada680a440eb0cfecef94e105918882f59205d1c8c805825ad4a9c30e13bcb5cccad949ebbb605232e39140cded5843c395edd6e1097019807e8079fc79f5c34f247719512da4a5b3571b58d6f18bd5c351214db35de8ea3b67df0fde39b2f4d231fb912cf13ec7337544453f8b591dffa5db801340cc3948f456f740fcb19ecda00a412502a18ed7cd7fe7b866d2a25514f9be2482ef115d2e21ce96b640f7e9a5aa3d3affc44a1418858f65431158c1ea9568b6385a4e220248a4908f0b3645d5e3be1eff1cca793bdc2c9750093dc66bbdb94215f9635b507bc40b7b2819e7e980d9ce23ea910ead898bc62caff1b1826bbfe2ce9c05ef3af21e170fbdc531a4ae23715b071156f55832c1573de93b8530763c5d6efca80b8605a84c2c646eee8896322edf47f634035016c2e2657c11cc623112f60cbca60433ee5b4fe73b69dd64c5b8ddc2d865b194d82b2e4b6699c488caf6592c89228e0254a97d5235da22c752f6f5ec2f82c1cfff3a61ec180611ee5eb6bbc848c185e43ee1d0e5f93adb9e8d22793a353e13396236f9b55d7a822aa4f35cc4fe3c8e60a61220a9bc42ba7335503661e28fba1cdcc5cea3410881196f7705b84d8f50fd09efa5024ba82bb415be9bb3227c09ce1962f3501fd15b9b36a244154443b59c4f1c27fea4cc7e089a5f126476d5610b78316be5c8dceff1db8a25c9f33e2608181d0afd407b0d987211a4c8e28da6cfcd5221b035d6d71f88bebb36891dd84885bc75124140356cac656dcd8caa024b84ff8165b8f9c6a2c3818121612cc690631739fee5c18e1b05611c2bac91ee1c9b7f5d3e6332169fd6e3f2c84589b08071db8944460142667b47fe71ba0df5dd3d7687d4af463978018e47c083f352981c2a86dfc3899f982e1f23180c343487a21296c82b06d19ae2fa4794a51b7a8f5dd33f042970f955d609e6135fd93568c2f229850b97446721a79b9d3e6be7bca61d3e12ca5f68af2e4ce716438f61d1d5ef55e02f030485e598ddc21d2847a11159c042c5783e8c0be10f9b0707f5044e7197c0140e167f2b2bb8939c6da83b570aed6c3f3a6e765b60a645f251dc9c4f6195339fb9e3999ec0e384cf6391e53883e5cc08d34ae58db4a0982ced47f77112119ceca8bd38e65042d17133ecff691cec16dcadfc16f9a0fde0dd015060f3b1fcf5273701ff70b1b2487a496dbfd8d0f245cfc909181bea33aa8de057d789db5f2e5b807aac8c39de7fa5d679bb1d628699cf460801b8b6669941812a2d40932c863166625e0b2193c5a0058786e8d4889a6998b03ec721bf088f6fbf775b8bebbe8b4c3cb7615e176f14c1c415e5c33a5b60db46937f9d18f6ec146c054cf801d19a4081a93fc12c40675273d1d5dcc9edbf003030ccebecf46ad296ec9ed5872b9f75540330ac76d06810c7d9075f16247dfb12a9f812b2ab6e80ef9deaa2886b0f6250f302280bb3d723b937e952ab9e39b196242178a6b85590ebc58b78946e1ec9ae1b8d28387c3e6fcebc7d82c5453e46f00992d3da5dfc3b2b60e5507058aadc114aee88f436404c83e447a1391fc62ece3e738f277a906ace77c2f3b8c0b1282349c0200121b4f91af0ad8711b42df1c8fe04c71ab8f1856e3a68b9a3f1f4f2f246157312698ca91eb3357cf3bd475dc736ef1ebadaea30c5cd21add8456b732ae701fa56ca1fc0e9833386df92c52ef2268aaf62f9e5fc7c80be053f478914bc4f0c4a564a549ac72783b785dd0c96a852fb7b337dff1af8ac34a30f42c4e29fb5333bb488c24a98bf7a375c06866cd722e7b319d83ac6996d7e64c00d7abb808d98069381ad45bbc940d5deb2cd38b243aff4f44e06a1337b2bcea17606df621e91afb89ad78882813f158a856dade3c3598b37839078cd4492fd97df384ca8578e7901147ec689a552d7bea48d4fc0384825a2f1861224ca6ac400928a985d69e88e1039977920d81cdc332134d4a23a779b15c26b2e1524f8968353a2857e4240805d5990f042c0ac72828e1a62649b3effe9ae27487f18b48964d21050514d7dc4945646fa3d075f64901719752252f70acc0582d447288922c6f1796773c4746814de5999dcd63e3ec73b9f7e0091b4bf4e620a4f801bbf89c22d38a9f3927852d7850e4da72ddee2d453cf940f1032471a3bb34701fb9e343e1d996d1dbb7d27c7f51f26c4e96c7bc89b7ad0c85cf4a37e45cfa3ba7c10306364251f68eae030bffa6f5894d689b18570ff5ab356433e14b7a46fb573349eadccef529bb6c67f55e58c1762ed6c847d64b5d91124a74d0bd79a71c9f14161576cf6e8c0a8aff3bfc5bb80859d0c14a13a3289478f9f9151634025ee203c77da520574aa464fdb01de988b0fa2b5c54dbf78fe59cade7d20e649aacb0872c28e986c8437362d2467b7b6ddadad81e31651044962f96957d73496fc7f9ae35f6097496263e68589fef7aef3b796a7fad04909e621c2070c7f80f581425f54cb3edeeb1e0bc961d84abb4dac1f08b3bc099fff8de6ebfcb898699f891d5abc5cc4b8cf7c7634ebe5aa30b44793c123e30c614f037b082e747a6e9075ba7aab71e0edbafaca6d25a2a547597b12ddb0c1c07fd409e83fab13a9205e844b0e87012ca7638c8ec6ecd76796bf692ec240c2ce19b6f3bf5a0c69506fcdae18aac1a76c8b669ca3abc43785cb4259e83c8bdb52303a29380a708a14dd391666515f32b65c68f23a91c9fc51067ff88ba8b155d9b72d108c03023b7dbb6a17e558f03d0e328766e0263896953f0e825e356f4c58f0d1e912cb6f2f7022bf535543a9ab2bf4cd3d90103ee0042a53936025d20dc3ba23a1ff0a9dcafe11039eee0f7a626c48b4fcc0e0b92e579f9864523688d463fa306934b3996b8a02ef49112d5b81b2e971c3a48847323ad4920851c79cb4b0e80169ad5448ce4558a0916e547121810510ed0e206db320f318eaa94cade60999537d28117abcf165f80c38e05d015c6981421ad270bf5e14506fd90e0cb38d91a5fba838550d7f0e53db78d89737ddc21459bb68a1fb10150240c180d558e5876a365108e0b5380a4911750a06e3dddf2c1085ab01ddc5ed9bd80ab8984f0c034f1815de7f29218807a76e13d2627290025ff30a473e155ee4c183932c4506c04ec31d35016c15a9e321908e1637521640a3f59a0c85386ddb5c65cf297fb72404f00955036a9ce1cdab79fcd821f707ce6c22fc30875a307b204cc8e886e26a4387452694e1fd30ae90c7a81776952f4ce127e6e54d850a87abb3f9ef7e37a7c5e5e15ae1695d76d70c14e73b15b7570e0f3e871928fbbced0b11b4ae64ed249f9c7e0b94dd6d2acb43724a2f5accaa85d615a54f2066eee2657c9a10ef10145d074e6945eeadcf5db53b8c25d25d25b8f3c9ef0186d26c477df9b27a306732599a14406ec202ff63cf0653cb81b1d56d556c93f40d86381149fdf1e79a49582f8a0fc1399897142b0f91214e90908828049f645e7de5580cf685569c9475fdfee17f1465148f36754fe8f01649026902e616786659d5b70c6271e0eb5c59ecf1e266a256dcc6c11eebde91e0e72af53bf529ce83d43684e26c509cc1a1dfccfd2dc29efa726a541daf583573c8565f04460bbb2f8de6c0a915fae5674f9d082b654ce2a1421474a70ea084d44b4a1e250aaadb09ab39fe8a67a9f094abb653c09e457b62440d1faa9e7528e4f86c5e1ba3b1ee7a54275a071f2f424efa21608db5e4b22a4368da19d8efb4bb3abd0d055865992dd9bd6a27ab54ddff2893ba3082ddd5978919901ab251961bac96050f53a8ba042fb554cdb5416cca7b2ef1fd5c8c8a6267f5001292297a6b79877a0ba4120e485492eb0e21f87b02dc6a92bdcb56ef71afeb5c19721657a753ba7021a9e90a87710293847d215e54e2f04b3038f05a0d49b107b114546fd02f3ae9ba4cbed4deb3875b635dc26129f68b5541c494adea44b00187267b0fd0c9735815947d1059b7a061d2e3c49abce8ef25d780ebb5bc3b44ac192beff2b497750cfa1e8d62ca8458e7029105d176cb0aea24362e7dfc200a30326e1298b1b4c0b598acdfd859d3640539d36eb7ee1e1eaac9421060ff108486fbdc03260808a696d8401140ca51804f06885c508fb231f815b0acfb967e7383546c8fe8041dcc89671588d6626dbfabc87025513101d8a6cfa0f9e98597a06ce54771e1089cef20be2ba89d3718007a2ee898794eaecd5ff42d90c3e8c5313cecc040b9a6f5ff50bd43e7cc5cabfb2a90254057a557a159c4b28aec0569be68de25df16aa9ff960e317a413b2f9e4c17ceaaf41ecd9e49feb09592baad4effe2bcf2fc889cc243b13b9fab62dc6d40f9acfc35a6628e9bd37e6a7fb137abfc7171fec534bb7f980ff181c3f82f5538ba3d140c75e209d00bd0d6f0b62a95d17ea5b39193ca67ba8594ecc633dafb4cdae6dcdec0d6292da67ca0f655b99980e1c35b5f3134bd9203e5e1d7faf0bc0d5cb6e6f087f46472913bdd840d2fad7ae696df9a9b846a9720dfd629a18eb525e3d859e58a5029165f494e2fb00d5dabee9524fa6de3314125a49d374d24a1b896bf7ed338999ab745b0c0fb2ba080b0ebff10d1a393099f486c88461612896ec090cc736904e23d634da34dd12ac4b5a563e1df2aa1926058ea39d347a805de97926474ebd055ff46509dba7a027031f6a245e30e42d98624dee00bc7689bb25a10e27d6c1623d3c4e1b1a851480bf58e6730fa9e19fe7e9f6a51ab2852c1365fef5e1fd8dd6b309ee5e9fab26d68e8937d4b45e1c77a58626f6f25d574d9d0e050bb667a24fae67222ad1e765d1b8e006678dbb04660b2d7a6193d0f6f6ea33caaeaf441fef11d878e1feaf05c78f70bb86190e0639c1ab3aa64c4c084840fd7b30d2c3831740c3318f0e0934170f0e915a7915c9a2c159b28ce2e2d2d86e6f2d4408b272edbe4d56911993946165d64f7c081440636bd14cc770afe339e4d8acf4d8089e788e94fab001bee410f86ec80f87b8530d864e6a4bfddab618b59f03daae368d783c9e042c9c4f7d3740acadf9c775c782c92a11c6a5e4f3189de621f4efcb0104adfce065af74965aa2ffeead8b0f0cc23403becdaeb06fb72a15f02d72e0601251f4f90efe0264a8739d156fc5f090ada03e624b556b858a2bd77e437268867590b33c04eeea7c2f0b7f4eab650cb467e0660a2b3cd2a1ee5b486d6bb124caf1f245b27b27c600113743b1715efeb72b2f6c2803a1fb0bb932544c8fd67d209261fc84f0f6df66e06a615084c269313e0e0c0a61d743dcf8239e921a6b0f975d250f0c1a060acecf849f46dc8d6387192461fb83305eb4de2505ca67b0f130c53ebd04e013b052b70e1d6f80651ac5b0c0291067b540949203f798dc1d478169f657d7eb1bc177af49cc3a35a14a0fd366b692c7b9894b639fe22e2a5fe9a970570a17018ca1fbfa2ea6fdbce313847edc41381c4be4f979e7d6a5bfbefce63db63f10e30a23e0c73b1a9061a7f06e5b8f254d3f7e9051563bef0dc2ce32efd08c4736889925f82b215b1ff2cef9626919562f6cd90a5c28f34ebe8394c134f9a1c8f35195cc3b1aa5bc41c41d88b637089af2888602ae972a751868940625a3ec39d7c3933f0a99f18ca03fa168f59984f82acc4bc9fd9d366a6785b591b7a265d18dfefb5cd34323f1b54e0a0c4eca6a61d789e95c0a816c3191817bfeeaef73cf1ff6838138a22bf23c8b9b9fb9f95a7a27986f43f43b6e087e101755c7973749156020962581cd75bc33e07f673b1bc55371b139389e27c2ca31a6f6aa4d275a404178f30efb77f9415de105989bedb879c7c56f80ac5af07e7604836af78ddb1140bb143c2d0dd662223a06b95e43c41b17cd73e2ccd1ac3c6d0616ed0b4ed3ab6bbe207c441b9dd1cf3c36fb0d0a0dfbfd5214bd13da7862193be2f3a546537239453a80f2bd36d6da0adbf4ce2af086128a7052d23b3179f3204f5fcc6decbf62585b2480428f85fb3610e23869a89f88e4a8e4fa2a070c1da0d433090326bf6cda47c74c3785e624be7f6c5d88d51cab4e73988603a916d0e36fc439e1fa14e27a6980616d4e0f80fcebd48df93b26225b21641deb76f1884139037c067e155af355987d0f489192a704fbe03a82174555bdcb64bdd9d1eb963298b83902440086596f67381c96618d67871a8eca21ae62028e384bc311e5a4a3511761ddb4c5dadf27c14f608d89e28940626a0df67a85c8bf5c66cd834c5701e45af3746367031cdb69cc9b97d5d43c4ce67cd74ccd83ac2634f9332648199e7d56c599734ec1554cda40a7e2584371f6123b782ac90439f52460c5012325ff74a2b6c74f297a04582a19a756179e20de9348e4faf6589a3cd670a5468f30b19553be117ccb7fe32943cba3c7e108fcee4cf4296af09e10076743d21ef253978e8e2899609ce949569b7b2dd7b059d4937894d66689b3e05773960bcf6b29547d69924eb9aba1c787f87420ae1cfe9c047cd0a86c7e99f3a2dce783888b8e44c2f746d3a407026ac3f946b060fb174bfcd0c19960214b5c85112bac95c41f391faa72bf90bdc35ddce8a943e0450572bceffb2d3a84aadd1b137148a220a47e3ab0e8ab913e8dc071914541c3c0cb93da0678aa8423d557cf144f23c8c0233baa84e3642b3a748f654d8688f6690441901e499f264b1eefa2f9cc8dc45e85b69bfa38f004d2a7a9762eac54204f41a7d19d13b25802d24d0a9948a1a5b5dae39ab7a0188214801a98c2f45685870b8e43a7dadc8d1c32f8bf7e4e1355e6e9cef20d7acc4871fae953e4f42a99c077e6d846ae117e4918ee8c4871bcb12cec36d9b12d470d4f998009a754d30be60fc35a9174e18185bdf1967a98e490b415d0c9c791fecc99241bc6e4729683b5cc591c316239bb91029cd5dc31a2c0661dcd952fc537bedba7161347d83102befd36e1bd57e6fde339c3094020bfa18f3a326294f2c272f34a4062d5e008cd59ef7aea58f8a8394c2aa82226ac78bd019872126b5898e9a39481cdb622a6a14867927b6cf8f34bbd283cdf7e6dc2a04a5f2c92a545f4f2d977f30860eeb814cf72f1755d4bc5af94522eefe3b730c03d782438e4121d0b6b88fe5d1c99c4388ba2a9ce98d24129ac007d757a07fe5d49095cd9cfe5a21ae80af8220696d08769ca37150debe585b77de2a016532a4d2a140e6655dfaf23a2433bca41ef460180734b82a916e082d6e9b3bc2228fafa291b779bdcbe30b5e2b8d901d5249387b6fe2f5ad61d7d21c57306a4360bf3659fd426a22032b7bea299d3ef25340e1c076cebe9d64852c70026c2e3fa259fceca5fd8f2244d3b5a58dfb0b5ff6422b4f69245e1cd6fb97932883500260eb2d68f296b7a20c51215d7648b02f9bf84482320d08e793ce47d47d60171165805cd95515cc9906d2e9f5c0c9485949637da7bc3647b8f7eeaea06122b9e07904bb6df1d1f17412c3ef78976426b54e1040ac3a82f1e4f6caf8f9327c9893ca533167769f7e240e97296f99329238b84ec141e2a7f328494da066205f3255044e3029318d5e847cb4c81cf7f790aad5a7ae4ed28d2ccd5f25cccbee53c707d61c427e30d347ceb90acec50f65d61a6a2f890cf250aa64941464e14ec2366126f25142a266d14a055b8f6242e9d3e7da85a711111b6422473ad49fa98e4f86fa3bced385ad64b2e07c342946b7d784fe49819830a8c5be1c73d39199d1761c46b3bbcc75cf577c7fc9069d97ace6fdc979e33e3c5660fd70a2f9a278ecbdce5ae2badba3dbcee236eb56a523ce1062bafd9af739f3409c9a3b8d973d5ab30fe941fd8b480c86da1611a9857faf14dc76450146a2e34ff44221e4dce5cf46ff3b16c35b9898a9b23357eb11f39aa128b8808a9f872c32bdfc35b355a740ef920a07cb650edc618fed4e819fcf4902ddf231deff3baf3e2d005426a3c6933a65633b6ca17b4b059260ac8feb3679d400af5af73880b1a1a68f69a170cc890c149eb2996b462f0cbec85149498d0ec39b9c11127025d5c9c7c4628f828e5e8ed3042e9e0e56e5e184c460ec5d99e5305e4095f5eb2ba28c565806dba73142fc46352e6f2d0898b0ec5fe2aae431a180a728c2bcb0fe2904757f9c9d4c8a4e4e3bc326e28e440e33a64897f8650afefa86ee51084eedc1f11d5022bd61f3bdc0595a1f0edcf765c4f4d1d0fafa79a9a4a6cb09ad7a46c8e886e2ead591c7147c16b7bec0cbc30b59675c416c6090b7fb043292a833453b0bd4a0d9bb02e1fdb7dc0d4e462d173a722de011b2e9be966da638d6596fb3f247a2a0ba37e63463668994e2fee5c4e86f263ee632143260703a91b71d1497c01abfe73829a5198acb5351d245247038683f9838d149b315a615904fb3862316f7d8b9eac09d132d721c564911dbaf375c6c9c271ff7cfc42aa056ce8c2f0c4a8612d96fa46471bad3c7e1c4d7f9e44e2d0de736f31968186c92c1bbb2a080878e0b89e603530cf6187f1614783962eee9eb1e2ca3a4e287ca8c6ab2398880841d8d5ba065624038d87b9647b06a4be2868158c3c1466815e6ae82bb3b5ad461289629de1b2db17b9a499894cd6039f0930543f999096629b5a4263c0a601f9609077bd8bcd0a4b83c037a7a32ab084c68085439932f36ad1f4a45a016034b9f9ef193bf53289ca7681a02e71f6d151a8283650f5db6e4cab8659a8a82fc9ae127dcd64940313e3b9307685aa8bd49433304020ebe494efaa3e7dc44fa3520b10c7b9034e8309899bfec0ee83780edce770b854f45192c390ed701855f450a7b4a9cc452227b4e5857a447021664762c42ce19abde150adbf3e7ecadf593253d494a4d77b0067e4486c361be7331d891ef64ed1b1ac3030a64038a36da0a8913f39f3e3a7f16838b65fdc9b4aa634f25f21db90e0c8b70ef4cfd5f7f349eea38e844ef17bf5dc17793b70172a61a134517a36499326bc70152e2ebc18205d2a1ffe41aec2c4851703189fac2ca8c55b852fe34441251a64c7f21ad073d4506520b651e35a47eeef3e449b59207650b51a05d77cf7481aaff869549fdd2a4e24a1bc62cbd5b7630562946e5be0ae376631f3245c660764264ddc2985c177edc27d8fb43a4896b827c3c78823a30d77d0bc49457e590bbcf27948a8d72810d46504a8da90fe871804eec5a1295b3e572f97e2c31fe9fe3185852ad02aaa2dc04e3f111928342501ec9edfce65da23dc7e3cccf67c9f2e57cbd57901766dcd122a42c91b601ee7fc76e256f9e731e4f5541d83b778add1d1e7b7bb281a2a5e2443e76c6750de1f421a08628fcc34704cc650ded92c0f9ec53fb9da85ea49af64198f0c8b9c82bff6ca593217860836bd65f139c2074dd9c9b4140705951ca03f1e42cc862b6eacf24d1a82d08fa46405a394d50337aaf46910f4bcaa2632e79ca138e32c9f1d85a705ed96688147baca3c963d1bd4207efe9780d17d1aa1360c1a8acf853633b5a3fd880bcbebb72d911f69d5b1a3488100864cc59680ee77e1debf39e179d2a47d0dd9e60b0469e0fd7ba6bc4f50be18c4309011af8c21db2bc4410255f34b5fed1adc0fa4eb11fd62c1f5dc4a333d3bc7b4de6014e3e88a3f40a8f4afc110f2f5cbc8568342210cb0f61f9e972a2bb2e4c947b098721ad0ee6c49cac74b4c70e2245729aa2fcf8e7256cb6b7ef67b3194f67314eeb298239fa193e7703431279767fae8399a210818d30694293005300adf80fdc2318a2919195237bfad27d1836491ceef42962a7b4bc6680f56b8a01f77ee8c1a90301ff02232534d1ccbbcbc3c92cb2c650240d93fffafd94896c7eb1032fbf350921195bf06abb05f98aca9f5d8067a6fd00ec6463a355e81087336af0cfe12287396091ca8a4796245b03aa6cf94d22ce59bc2c7cd2eedf10d8bd95ac03b2295a242cc75e5757f96934b744267e53457275cae5cf58f90e8031a37df20d8330d6f42d8e8a581b973f4e240a344d70f06c5d1a60801add7ed2e737a717a77b7a050526a2d32c62e4805a38325e87f71b937bb9fecb981c819a010ef6f11743a75e6a72bdc9bca815ba41f7e0fe549b56a22ec79186c515b1e1098004bdd162624bfa67a331e73981669c58e1e2816097a6aa81fac4fd1e7b00d20b167bfa4a16c637e7afe33cf01d99051eb2a36aab6690dd4f3975e6135dc65f730ea8395415d152f31acad10f93cda52c40544a177239cc18e6681af5ae616a75a1b237c43ec838f76b1f8b8cdb518f1713a2a22825af8501b50c45f0856c42847205ffae3f0ed82fbb3757c8dc070f9c8ab53abf991707408fa8828887445b0e8f8981ba511548cfaa836ce425ca03e876f2c747f34365e227cc3faf9d2b80171c1fd9c9d64047444bf225a707d4e6e3244474758f1fa5c62bba87d405c23a23b2230f61fa23816403ecd6f17ba4fdf9d15223ff56c17cb8fdd8d441c0be21fc11e4211a15023b0308b91f28c66fe0904d778607d1e25c20e06d5e71965341ba1a7f1f37601f9d377ac8bc0507e8ed2b100fe3cbf5de83f67e330463ee47455287cead62e463f4e378a64e629e1edcd29d32759a3414c3d72ca89394af6e673621b20db2e223ece73bfd17ff0ae5d8c7e9c6e19b11c81168218013a426e112db81f3b392450a6c959cb75f8c9396af87c9c76c1f96c9d4584256216c2f2a15918d028595f8eaf81646f87e436dee4300c9f8e772c383ead6d17d64f0fba169c9fad534b4415569ffa6a178d0f215b509a4f688d21c4742330ec3fa2762c803ed66b17f61fd9b97d8e7edce9802e3ff78dc71017c63ee27194ef3f6e0dc41017623f7da78f5046a4441082e0e8088cf727d206ba88a0f41f7bb40b4b1f9bcb28a2c5f8a7795cd0fde77e2386100b22bf102cda9fb69bc5116c8d7df4387d8432223d441247000afdf975bbc07caa1d70842f885690cd8987200e842fa21dc432444243e1aa459a0b10af2196230243f7398a0b0afc791e770afd07efed62f4e38e2ee8f2e3b77621eaf370ac889096f243d11a18e2c2ff39adc3b8fc08daba416c05b1844045104428748415eecf27b70bfa27d4312238217a2fd102f589af7580680b618b116ba53f71ad13d1268432023d041723c002ea7355c38ad8f4270e45c5b89809087403bcd5cdb7db3b2bc1212f661c2322acf07d6c346bc4354232116afdfd681b5f202eac9fef8e11c10162178112822082a32330bc3f423a16f41f0b6d171a1f2138b5ea1f0b69179a1f213a2bea1fa18d19818e08b5741fa73aa0413edd6b17fb4fdd1b0b919f876d141118961f51b9a0c01f7da783c052048bf4a7e1da45ee07b51d86f573411754f747eb78856071f9f4ba31467423a48b6821ed036f6327a272d6c7dd89468c0f215fc45adb1fb46b17a29fa763450486d2a76a030e627c1041d99f1b4d24c4b888ff8876c41284a4a1a8b7d0f035c4c5be887fef0266dd18233fc86b17facf9d134388dde5098dcc2fafb6e344334614851815f10e4a369aeaa5b9f158555483afb1de6854c353986a8610c408858260f1fa20d9e811f588e8087e8968c1fdbcd90d41fae3b8b57284cb8a4fd5114888a07d8cea19c494a0dd789ee9f3765173f4f2bdb279da5290fb24d30e7b1792fdada4aa5dff89f154d2a0ca47e29a2f498ca0047a2e617cb4578d637c316eada3c472fb6e614ebbb07e4eb0b1e0fe6c6f65056144d0297b700c8e123f5a1b7f6f7d87f55a0de5e0dd4475699938fd07a8797684fd3e52be1394cd3fb67edc3eed37f8ffd7d510d52df22b727ed63bb660a63ad6af28df3db1047b41f94b12602db41bfa0ec6d224eb054bb18851614200f6238c58a91e290db6ba7e6295eb6b8efbef7a6af030ac4259df5c837577afd2bb3baee5fcb0501cd2d52bed735df50af3bace196d717d06a90504fe4d6e3b5515974ebac5fefaa0e474d50e375c91acf9046bcd1cff68366e9774ae785adfa914dc81696d7cdb4febb97ee8ddc9a03f20dcc010b1cfb61bbdf7e21e3e5d9e18578f832303b4af2d4f98cd2cbf1f61f3816c78bef2ff0a3d369b8492a4f3011fdefc44d98a54443b38229e97c63d947dcef200500af81f883ba464a2960df043638bf77fd3175c92f9c340e1a59a5185660cc284e261b1f3b53ab8a63e17ae00a39f09d16717f94b5e7d668556746e330c088eab2e1fe913c7078e32c6e0db53edc8a3d0b3a40a7bb2d5f418f23760b2835bac8e71eb878bfb2691399cbdfbbefb17fcdbadc8adbe50352f938a1a264c9bfcc8157b4d2e44eafa11f652fa6728a90c1a91f24a6cab701e0f50a127fd0abb2ce99d277c1365da65819b539beac30b05d63af0b32bc002ec0404faddb28b59505a19a5036d62ae53137757f5cf7a1da4f5b492c008255402581418215cd40f34590561612cbaf8dddc2b34d19ceca920f47cd678c72b13af283eb8ba0bd68baa6846fcd645de38241c69f2db2df7967bef2da59452a624036d08dd0877083eed23c4446449c1e2b1a4604109921364e7081cbbf18e11213b468c6c6c44c8100fb58f152c7c80a0852351bcf0bdf3b1e261e2d9d1bbc109673b1ec9383a1548082c3beb3011e623424d82fcfeb9faae1535a977e208660c2cf42851de05c14677efa84f74d1b6e22184511cc9fd47d98223b9dfde3f6de39c1fc392284bbc3b1265c81f7977a4899eaf3018f73c848b4479e7ece368b0cfbb1cba65b483a3c13bb817cf6a5284efc18d7db8171cb9a554ed4c94cc70f5850ac13a3a04eb7ee39c242c5bf8dc6f4ee70a58f0b9820bbec2dad92bae7408969dd3234d187977e40a507ec6bb234dd83cfc691207358927e730d12426422c75da03bb8303bbb50fcfb30dcfd63ecf50ba87e166bdcdb34bf6e15edcc06e941f4777cf40a8102c4f0c65c00e468f103ae721cf4588bc2bf2cea9663dcdc976db7d43cfa74b6df41cba7cfddac2154d122561492e58f8b113809df31f4d727e83b3681243e9cfdd483aacb18da45bf23b910a5574c7226a748995edf4bd18638c2f366bd613d5a344682ae5ab2fd26da829af4eb594d2b179bd3ae7f482e7d48f27a7842c5ea8943c16c8429d58ae6c48446d741b12fd8acd57748bb330b72cafb5662e43dd52cf9cf29a9f3e399669ee7257274cabf9e9b27c6a1b7c2b5a95e536fef1e84459c5a546a3ca53712c60dce9eeae79926b1274697e2925bc23e8ee9f47f85e7cef3d9a13a5c9bf7e16dbb817ec4d08014295586b739a04eb9ee5e833deb1b8ebde24c7d243c41863ecb6babb8775cbba6fabdb67443f2d6f6f67ebde886fb9cbb19cf268b5e65eba4b5f98cb4cbfa6634e69dea2aeb6ac8ad236172ff9879b0c24ee80e7260a41ef8e3891f2d06d942032f698ee5e0bb64deac13750d7247ecfe3bbf1d21cfb9cad92cab52de20a37d2db86b41c9a34e988000fddd4eb89d9c126d15b64721bd3952b9a149da8889366b860e9514767a742900a16a140b0d3e438e4b003157d9aee68f2e9d51d4d934f93534e3518f79482d40cdad447f441ab88dd2c6dd91c13299dc548ba2f54d097d0c121073aa94f3ad3a4e68109264b94d425748a10b18e10120448e50307081b9a0bccf0108125ece694f0b834b4f5655e6b047df2e9de1bf13dc8cc31c218d9868128d1a1040f0e4d6aaa8ad49c6f4ebf1b0f1a12d79ce235ab187d461e44f1ea8857a446d1a7ad66defbde8bcf88dd1c4f741d3c3cf4f6f012ef28ce3b8a334d6ae7418711f6390e17765db7e0ebd21ee0c3d6a1048fd5564a0597e8097a56c2bb2356c8f9aa2d1ebcf71e84d159db620e4dc2a149458c34a9fd0d612158901cfec138d6816852dff4b84036538108e868523fa7bc01d2736812d46644ffdebb076852ebc06e2c232454dca5f9ddd1f317764d9ab0330d47c8774569329ad495fee0dd8d878f48a8f8d3369a8777c892ae2f52082f7841e83322e8d05b45b3dd02fa125cebb59a047d04fdf9751fbc37a20efca3179a03fb7cc62ab1bc1e5ede7d81f75dab496eaa3ec5fa52c518237bbcce2fa7340f7d6a9b93f2f35558f3d09aac4a4342c559c55f7d9954ee01ba45bbd8c06ef4df7bef8ddee8b9f459e346ef5a1f7aa7430e4d723eba11fff9832f3bbc0c07956301040bdd5ff3819fe6c0cecb5dc47cce5a2f7fb4898d5e3948dd462d6b59d4e5d14514e5556318e630eeb13bb22e83f974ec4eb73e79cdd35c6e31edbab49aaf1ad5a81a8a504a59b230d9304d62a769a2f9e96eec92254f53333b7637eb0bf385938f46d42777d4e3b34f3ab99b981d3b669f11c5fac277e47ce4ee8df8ecec95d3a8114bf39b731a5dba72d78ee6f4792d4ab350b00468924b93a20cabe347df44314ebef90b0e3dc0a746d829baa0afc412e9742101a838674c2cdd423dba0cea3c49cdb10de581255518f24ecabb235072de6df46964122bddddcdf98ce87b244752925e8b5b95d4266ad117587ee7a3e9d447d39df7f4f8d2a71bb179f84dfa8c68d4a3be3762843da048ba1b0a148968014d126237fef1cf690facbc4fca69c82c42d43853d8b57b61e75c7d81ee6ea277d3f9a4b3e2790bc7f97cad03be6b1cbc92c1d24f5a216a52d7e077de36be38ef7a234aa1537d715e31cf39a5741e1dce1835f91c4e53a8bc35a971bff7d8bdab9fd64f72cf29e59453cef9a406ad263976f33dd9ef79939c4b275510244439420e129a2061c843b1a1e066fd4539a4dd92551cd557ca2baabe502e4d5deef2e0065dbab25cb561dd8dbaa5aeccafc969adb7ca6e745a6f505f6785a93ebdba2eaf9afb595fae8aad7a434d7a22e79856fdbaa24b5f94bb3ccba2ea0b75be611bdcc7c81ebdeb4b9c1aefc0b20fae11ca25e8e4cc2372684e0e8dd3742d28d18822449a34a48fc84981ddd888c73e7eb0116c84d52181cd6bef8e24a1c957d873f0b7be81154f9360103536bf354efb68d24d10bb7110fffc4d3e3988394d53add561dcd7eab17af41afd7259691bcd5757a5dcd5aae62b6d52075f0e54017d62dbf27e1b123d0b8e2a97a13c3a755de5ef6935516dc6fa694108bb452fa1722c36de61061a05e89b5f0eddb295c3ee5b3912377e03a65d5e4547e2724cf380df10032929a596c3b8b7eea8ba8ce5d3ad9a6f6db3be69ea373563fdd49acbbb6ab30dd54f0533501ebbd1520c9e7763deee82b2354d4e695d5f285bfd62cd6a30eead77c5e13db7f7b9b5d661dcdb3bba5cc6baf4cbba74eb358f6995bb0a53ddbaea8ce8abc9597379d434a50db13c27543c0f690e47a3a7213f35b182e54222db44344ee37ceb344ee338a99a8907fb49d80e217cef69e09f9477ab6919e3ab5e04e275ec0721ac2fb01bc78470c7ac61f7aee710f6cb2248a99a543c939029fc9886d8a07a0285f989cf1328df0c28f2d3a51f7972c44fa77c3aada065edf4ad9d5a1be593bf0a33afcc95a136289f77c617c9e05a267ff35953832f6a518ba865ad39ad79ef2643397579e409134f9d7a92e49f9b4e51f1a9a3e1e0d36ee9a17fe2a78e064dff049d834e3ae5ee0356a6292d0c63ba4bc855acc778348c56bcd1b3281ae85df1463746120cc362b03cef19b4ea32b4e86ef455a49850641775193fb9ab281a43c6c7b88426d2441955556137460cb6617229e3278661986377a389325e7ad562c4783f62ccc02a66207a47543f5f619745e2c7e26850985f16ada629059c525c992a85ab10a510f5cff3e45be531f265d14a464d8f6753c50e52e15bffc8fe61e7bb754f55d148e10cf9b2a8a489d4c5aeb84aa87ffa4724920c63a2581979e6d257f8d63d32acf0d1dde80c5706d195ef88afaaaaaa26a8429b9e42a3fcba00804eef267abf2e32005714ba9b0800d7bd162b57e5d270dd6b915d8b4d16b5a40c8f5b7439f550c9305c442259f956fdf2ad7ba89e97aec22ae59cb37f9e25ac91a155144102021b242050820404384884e0412284112442f8d0389077effe0725ef7e30f22e080fc2c843670f42c843df6a1c0d1e70d617579d76839f9dd33848ab0eecce67d561dead6604fde11d69d25993cc1cb52dfa88a33b490367ac3ab00e5bcd68c2973862c8ce9fac1507fb731188a27ca2289fe674ba6487997867d189ce48f9ac379e532eab8de9c27b47935b35fafb7b3a5320a84f9d6ea1ce497e4ef9f3c208a9cc746a635e78475fa3bf9f09939e1e263132e900931e0edcfc740ac474fa3c22e940d047972b72925368655a51817649b7bc14b506ebccbb4d954c867af4cc561bf4c24ba95ba37f7ad5eff2e6662d110b1602e0b0001d3317000227481115b15a1de8b1262ab25cb7c8509f6ed51b8cc4034068b5416dc886bcf03564f7e2a58301691add0709103cef90000104921f98784758fdcf370eee3b09234838a0e49ff34dfaf4a7c9e99c2351d2532e272af31c3ee76f92f451f427dff597d32c66ac0859345a74725ad9a8aeea95c6c2d250937af3ea95369ad5acacaeac1dfc5d7154f7f2ebd21cf5aa6d24caabaa6e11870cb54d4836b0e4ab6fb1f26a6a3decc07e3a0d9c778bfeaa0e75341dfa05eb1d4d3a791c4197ce020775a74f77decd7b80ee63840cef0efe9d294af71426de1d19439431e84c243646be82168c7146870870df7192385a7ce40ad33e9bb60d272d69e73b1dba37c938e5ab355abaa3b1419fd2e1447a799f0c7b1f315d62ec8e4545847dee64a64f23b0ec684c0e1dca489f95d4289fa4537763aa83f4c9725efa909fc81903cecf31d8bc7bea486c843c00de21b1b9797ecbb719278553e596f4a95973ced1f438494fdd9ab4a2267cd3e3019f999d3df246f39ecab0b38fa8471bf4ce399f3bdf28cd8444d81dd8b37bc12e7bc4e0f3530c3cef5e0c3bcf6940286dc8b25bae8dd89d4b863c79bdb0e7019f3a0dfc6889a81522eb6eecd5a7cbe397ab07e83cea64ddadea405d1e3c8a3e391ad4374a3ad42809e108fa74d824e8ce25846f8ea0bb6892ab3c4750b58fa2572ebac58901e7dd77df51bc2e34f01c638c32d17d52977d6f90de45990dfa70473748efee0e19eeeb22081a62b0f9db8407fc1dd83bf48da22233d39174ead3237b9c9c9aae0bca91f4fa8994254fe3dd11295382a64079172e9ae41c8d1ed89d7bf17c7b2e478f448306bea90496dfb984ce1fe9a34f796f903e4217ee85eb1d8401663a648da77c8d1afc7c47369cb3c3b82befab79587144779707bff4e76c4c1979655eb78de5539094873e253fbba4a873d54754454dd6a437a4bb6a55148c8bdb3d526cbe721ae5610d9a20ec1d4d5d6334513ee201dff90eecddb576b00ef4f2f057714077f7dd1aecb4faee68ba37483f9dbabc2e9ac4cef513da5ffbebc7ef9a5ad5d1883500439087fec030e4a13f99e79086a311fdedc05ef6837d23b30caae7e32504a47c748e62d3e4dba7255f5802a35e6d3c2bcc34dac1ef9c077cf61dd8f3ade474b7e7d063c5119d2f0f7e77dd16bd071bf67de8aa79faa96dd16978f27765aecca481771a6bb0da80b4eec0de07e75e6c93bb17dbab38de8553843c98073fa4776463faf37965a0a4e18f3bf877500c91a8a0cf20d0e917863c75092d76d049e7587a7483f4f0addf586698526077f0b7b3b868d2736fb7a20bdbb9f8e733b868890ec7bc1b432b426eb1d19707d750c4727d81dc42ba8bd546ddc13f7ddead7970bb4cbc3c1da85cb7ecf86137beee0b3ebe1d5eaa8027be60a313903e3186a09bdfac3f27a60db04c99029484277680a430fdd004f543b4f1c50a3f617842c40e12429c7882871e79a2e787c9c61e01e2019091279ef87142021c68020927b879c2092bfc3831c509293f4dbce0439f704209d5092970202259a901088c1003213cf0a4e786e8851b401d040103cb08eb0710644e04f9013a7482034340c8859f28d38f134350275c3ad10527a0fce62c5511288c99266e10052953863042083770179edd16f1002384c007064178b2851e708185850251caf2437f581c0b29a54cfb8b4d64c13a879076c15a8e86dca101e8903a1afcd02747833af4ea684cef875ef10e7e007c7b0f0e806f07c0b71be065a4cf7ba300dfde83d3797947360af0ed05f87637e5390aa856356147d46b9e6e81c8cbf0eec816747e64837af57ab75ad50a408f4e01e8916a153f66b6aa3faff5524eabc35aa3bf564f6e30ee6566a05300fa7b9546bd9296d6030d7ce75e488f9a0c7b81e7f9f9bdb0f3eef9be446fab5b9e7bee5d2e6d69d39fbfade6d92b68515abfa5f19477ddb26960036fa36daa3b62efc92b8d523bb8c6df1aef298d6a93666955f47872808585628185b5a000ca85162d5022063f34e1841260c03e4c50477c9c88e0881688f0d6bb235ae8c1119f2447b6d004ca052b093e5cf0c2089a00a507bc05daa3052df0e04536a40525c440adf098e5393102b502a582f3f8547cf406428d702ddc6fd86b01c7b5702fef165dd22a5815efbe27b0a9786e1848814700de871013767c5c16826fd887af103589f20c91c54204769b3183f44a5688c4423464824ed53e2054127a189b8818db07ec26a271ba054e8d85bb2c5834c99186b09bd3812c627fdc1a8b153c3f624f429ef325be9de7953c096137bd601af234b6e9c157d424e77e84dcfe19068208bbb110478158ddc2856e78e0d3449dc301e73c1a8d82dd9ca65bd883fe3929ecdc9da980a3b1837be1e2b91128449a08616289c825b104a02cb0a31b1196dc0d6c53723738c73baf091f8bc782125c4311bbe29b8a4b6fa50e7fe5ca642b93fb743719296ef58dbad1ba3299aff8ca8d8162abc9ae0ca53706c390d78a31e2c498df4dd4ac77855a3a5ea5434a9e27bdbab4304da39675ec3154f4ca082a57b91ed2e5ad6196cc31731adddaece2e831d24335368279a661ac0435f2152b21b9e7a96466253696a79b0c56a265748db2cfe5b3e212a8e425691d7ebac4cb225bf5e9d472ea912bcf9c536ead8a3f7aad38d34bade69a3459a7d762f6ecce589fb1fc7477ab5c6164d8e16757a6ba993563b1bc7569fa3bbecc7c7ad71bd9bc34fdf7551cf1f9b2120cc329e19c4551a64c29a599f142596ba8682b2eb5cb2b57d1567c6a93ab3015f9943b1c85a17ed1158d4e2b9a5718ea9a60932a5a83f2aab22fd6416a895658eaf4b21258ab0cf9c08f2094fc9c6cbdcc58c9a0eae280f5a57a1aa5fe1e5050b73ca72eefd517cb2bcd7ace3d75e9d32dee839ef479d57434367e4204935e4d30b0328cf3e365788724044136e2db6e7129ea16faac90ab2f34eed024c9ce5d434ab0d239233c6584272f5da2784189381a5a03723409c50636f092af732fdedddccebf1af3e6b80d8049f1295e3abbcba326ad0796b78622f6f5b833caa0076218d1e3ab79e9762a10403bedb4d1caac2d312935b1a528d6969a586b4b3c739bf6c21cc34a4e4a4e4a4e4a4e228e8d85b5ca9f0965ad70f28a450bd336fb353d205621368190b930150cc33cdbf15430986132484eebdb8417e6985bcca14b202020a030d85a6b9db25638790d758f9499b5d066d66621cf329bd92c44999969669d9999332aa59c3f1882840820594d8f50c87384de922c14ca3c47f69284a255bd86b22c94798e2ce42e2fa46d6f49c86d080b856cc86dcdb090cd429ed5cc5d9752f8964d1b4a81659be341e1ce39e736fb190a1bb29eb97b2b2a34d156c76e4c4c4c0c54695ac47bef65999457adb07fb2ccca32785957c6cc4cafeacccc7c5129a59c1ca8efbd7759164ba06eaabbe54f77cba0e8afdd4ed5ac7609248124900432f2de7b35ce18afe8571613b3d9afe97165ee725c99bbbc4cdbde929ab975dd2cab99d7cbcaaebb3d9de7d776657eb9e52b948a3be75ce982ae2dc6313131313e978024043d4842b0447cefbd2b4ecbf2184bd52a51cfaad5aa5e6a62952cbf2cccb228bf2cbfa85adda5b1aa72ab5e5b29fadbe857cb2deb6e33f1a6542a95e40e608510c24ad55a1dd6fea1aa5a29875447a9b5764fad9572ca72aa5699c5ae5aa7699a6a75153b79a6590dd3ac4b13bf929314599ac4cbe8cc91d5f4c832cf91bd255996836d982f49966533cbb2f7224d9c9366ce3967765996756573ce2ccbb2ccaf2cc3ae6a5597e6c5c488ec64847c6f02217fa4141944abeb76a60492405597de7b6fd6f4982526a526b3146582a5262528734e3984cec7788764081f44dc0c1134c40e7d3d55d49d8935b3e444642510e7c8a0278364d0ce8cdba1dcce9340124802cd09bb674a082194b37ba8ee9973cecd5ebe89328b55d7bcbc35ff69bd6a51dd4df4532573ea2ab67a66318db6a877a316a5a494d26a79a4722ce8d3996dd11cf3e96c98532bbe5915df444faf5395bbe22a7e79e8bdf5cbba9b2885ab70ea825038f5143e299cba0acdb916338546431abd1b8daf68ceb5c8ec45137ffa4613a70a3a45d1dc9898981891acb13d6540e17489b5cdb6454b4c4a1dbb4b5389e79ffbf801bc430202262f05890f261a898f24487c04e99ef7c59bdf66e20d45dd2dded450c4ce3bdff49e974aa51285d36e87053420878e2944067d45c529a5fc9932884e193431e953555c98d247fe4c20a6f4913f534a995302cd29835efa36a504622e481ff9238364d053af2aea52c229e4ab39e79440f2a7a2aa4afab824fc733450bc731d1208c50a5c12daed480944718157f0cf2510e576dc8e119e57779b713b124802491f1924815ac7eed8f6e1a763b9a3ee34e89846432bf6551445510fa3a81cd414c3365098bcf5e93cbba43629a38c32caca9f4b4ade0d4a2973c8b7e4b729bb6296d4736c2fc96fb39f4e0cdb407db6ce4f6787dd529d9d39c6182966a65a8b32cae84ede18ea86acbc9b0de939d806e9f2289dede954184b39666f0cc3a02b377455ae6c76142a28aecb6b0cddcdaadccddebe2c9fafaaf7a4945252d235813e318d7a5b3e69977305e58ca46ee996ec8a611b441be5caae29d7946baa5e19e9d854b515436176695ee51bcd9b143b1e8aa228e9540cc360974edd51f418638cc1dc625e7d733c4faf8b726bf9e4cbe3e5f1f278dd8d263a9e8fa1dc72fa4275bed22a197fd2dccb97524a77694a833fc5308c4965d0b99881503dcfc20c7f30aa1da451280ceb91b4c73d6b9d48e4308c266ed47a27518f72564d8f1feba214422b8a65f95cb1422b21abc765ce5a271239cba74513a54575bc928b4ab642d50ac3985b2c57329b39bd1b53dc77a389543e205f5996654986111d529426596822a5d44625db50e7a5ee669dcfbb51499f573c8e7a2e234da494e5cf7ad6b32e95b27b9e29a5543e150e255139165b081271342c871e6fa28f6ec1e996e9d0e38f08c4fa0c9c12cf57149532762565a9899c9c2b8f51cad601d617eaaccdf881f801ccc7d6f428312935e952144d867257778949e5a52655094a75959c5c5455e279e94c83870e89381a98c3db15dfa06e6944718949a94989a7c4f3b4aa30969f4e3e51e91b56849470cec0e95293971e2dbfb48a7a4595985022883c1624e268d0f0ce23906e8951c6ea5d616878ac084a04544fe4a9f1e6db65a0dfbcbcd06bc909c3a83c6e5811ff36aa47d650c4cec49b78e31587c584df200dbef2cdaa9cc857779b893798cf3f2f39a16ec9498949b7b89f4a3c4fa594b2e42494a392c32fc9fb115ac162bad5fd1ecb63792c8fe561b41ac590e1618fa29853ef870e9764d22adf223be6f1a1788fe5b13c96b765e0e5bcf71ecb03c2930cbe61efc74bef472f4d7f855d9afee796b00cf9fad2b48e577216350376633c88652bef3d96b88dde8f9fdecd3102ae5b3638197e8c30348022965401865e8cc1a37292524617849a68cf3763e411df9015fc504477bcc9a4f7a00088eb01c0cb3b29712f9e6b60b0ec5b0412814c91180e0682b66a86135848e4e14cbc719456be459b872a9443f764030f5bca43e81d0583d09b892e35ce6b61e3bd7351b0a96cf1c9bfe78df34a13e7b5784ead48e43d75a66f7104ff66f0d4b7f8a4e2b817cf278de46e2da5e1a97be9b817cf5184a1d268f88943a9b522914f9c7f3bbf4d9d1a287eba168f0770d8bde887935791a37bd75633ddb7014883df289c7f1e6f78082b7842e3cd6313112a09a1e8a70c62a5aca8c9a38e971e2d1bfed2279c17c6005c1ef1fe480f891c522821b56e879b024bd363ccb1315ad16bf1bc732cfb83114a1979c097b1ef6398820c03d900cbc6f2f1aa1e5df44a7666c6cf99023b27c9b5885e35e75a3c27a919158024e848fe143a52f4bef2c9f87246e7282305663cbcf0880d09200fa2877046f431b6e05952939ecbe063d9371249240025bc40095034054847d9110819fb8a20e25c122e5e1d950ace396e42db821a7c3109142faf0ef7e279ad82753cceaa421245b89d874de297824a640613d8e7ce0851ee08914678e95bb6e2e3953c588fe4919247eef00994e040ee3cac524081f3cf2da033c30a2c9ca174deb2b42da3741ec23bc4c21e7cf42d637979a9141db0ec1bb5f3d4ce3f977186d2b9e19ef8c0030f9d8e6cb45f0a503a0f1905ce5b5241af3007217337d41e85b0f9df757d04fd8d7dd0c7642282e5ee8679ef3d1808218431c618a394524a0983412290080fcfc37938add33a3e3e9cc339b3ef44047b434e7a6d350ffd397cf14222cf1bde68136ddc0b570379e61421dec41b2a9750350b4bbf5771604822bdb88089bee7dd3ec0c0505f83fa47dd98bac2697a8f85851fce87de8e9b5eb0638d50854d3b76b3f86bded517f85b7cf62db68d487babf9f68617ba172bddd0db4684b0c264ee06203a3039f50e544e79aca3044c4e7d01d5a5d4a5b1c2581b7c2fbb17b2c2602318f77d6b2062435ff315ad70ea8a0355d5054c392bcca5806901d39d71674c153aae08700fa78a85e5ab0226a70e98ae7bee4d072184909b5eed15063a8ef601867ae830144ff97e78a97bc1d4bd704e2b4cc560d89d4fd5065f3855984a525352d45197c73be5ddc1533ede910d0aba051c4f790dc01ad9758708964a283dca19b713049d1826bc1f73564aef08063abd35ff9e4b9f3206c3e89ac8dc848a3ec7dd3db17b62f5695a0ebbaad339ddcd25944994b834c9fd412aa59447dc599076a89649e01d183694413208ba1df9237d24101fc142a20f6cbb0fec0e0b680013294145e266417a3af4e95038cdcb9dcb34ab611ae9ba1e78c5b8981d9af4bc6a39683834e93945afe82724a41824b67d23915cb0583d6540c5184b3cb3c43361bc29954a4c4a14454d3ab1542a954aa552a9542a95269d1863a439b188cab1d876704cf0b51606550dece67a78782556f25a3ca7aca427064ae80e134c9af0744bd5d31345fa4820e9b38411d631b2840e4fac0303f629b212aec7480c7aa88411b203310c96525eb2111e662eb0042b713dac4402499f1e09c4ae4702b10f0a3a6bdb18a969e082181ac820868a2922e1e482294eb08884329030e6257918c330a084998d91ef51c7340d567963d8f1584370fed177d5497a43d6bec0724ab3e0b592b09407d733c3f5cc9831cdc0ca488f140fdd32f9f3990ad05ca007c537906248e5e32a87d6001d0dc8a183a58ee90532521bcc9881e5df663c3c000e39744be54f320cda519ee98c0bcc20000e39789899c130585e58b1e895665cb75ebaf950f3d379a38974068553b96a82f9d6504464ad90aa442722768b3931e7952823afc5731a73620e65e44d43accce4b132422d41e95049ba45524ba89d6ea9983069522a31295d6a082584224209e916e814916e993c514426fb144b3cd30dec7437c432f51621dd3284482c128d944a483ef04309e9962de6504aba65a384c49c28ff3c1e41a45b9e53432823dde2907c60e79f534bc41cca48a9444b4c4a9b0f35cc37523c4b6077ebf198472d26e68ac1300ccb5155130e9823e6e94cda85c56039629e0e86c5b00b7ae97c5ecacdf150216dc66bf17c45d3322d068b99f1eab5335c6065a847db2d35a2908bd1414a9398637e69fd32ba457ae69b7d4a928e69de2449338dd417e8d100cba4925a773b0b9bbb919e9b9b9bd9a7b368329a5a6cdfc6a213a313a313a313a31373f595849803833c20fd436eec234ef752ee1eb4973b0d73d7dd2245d52bd3b752b25e938c31ca28a5a4942975a63c6a14a07c4e1f3e7cf8b87ccc3927631c1dc3c12e0fd3aec9cc3e26fbe039277b5503112b7d8241e2c66fc85c5ad8944f94addae42e6f7aecae4e0c6b0dcaa955d1c9faa87d6144aa49889599936f842fc20aa7492ffef8c793166589475e1c327a8949a989139ec9a536cd59b91293529312cfd4625541b85142fec17869f8692704051b73fe0181da7579d0e18b1891e9c6cac9b967baceba49525f58932cd9861e9707eb8bf549b36e32946c430f4b6af94b6963c551bdbc6c735953e468487f55bdd5f4022b133d8abac5a3f59c5239f9794a2b990a75524e1dafc436b7de8a9d6d28b596b2b3cd7b5724aa31b974db32fc7bef35752939e7ea2b0850848022c4e514ca5f17c775dd0801e51202ca8d1050fa8710d00a91798c41f0e7f97414d9f3f1e6329426459f61ba6eac94bbca312b6b8bfa6ca5ceb5a8a188a5ae83d331eaca4c97da30bf1c8bce42b00df5ca5c4e51f36e35d795a1f56e0ce5a564ead2a326e3cb36137a6a855e464d3ea71a05422f237d9a7caa5c3b1d09eb2e0dab02563ca32a9ad52de454d1ec6bf164e6765214e53c39455977905a2b9ae4dd828560a96d7bb0b2c232acacd0959588a22573141e3d06db80e2e24071b79a47e19996394b8c4c0647a1c990790cb6c17de65c5fb2958c856d88f17e740bcde8ca4a0caa4237026c32748454acc7a02b2a5288a21078c4cbe920fcf10289f7fda054387b4ce1d137cae3ddde0f1a7c856f74e49bfd19ee96f98abb6e41e19b2dd170578caece0c57e4446ed221ba9b5b227277734908405150141edddfa39b0dad68f1577ca389323ea81ea8e48d767541ea108d000020001315000030100c060483b180381134c90f14800e8ea04a6a4c9788c32c88510c21840c31868c8008800080cc6c080a026262995f7dc3f061364b102a9cd224f894cf9989d9d7abe28ca134fed691d4ad167ee535d460ba4b3b008542912ced2c84a21057c24c35ea8e835668ddd855c92ae28cca036ad8204f98cd8d4a09eba371016da3777a644986f1d418b62c215628855282bca18ab33126a14b080503651a0b574709596e2c836ecc29fe1f6d35f617efadef51ce4f04a8f1b3d39bd124f11dbe3d7f69f405d7c013bf68573dcd6bfa20ab17d37187e0e1de63193af1cd10ebd38c2d9fcc42ce148f3f6e53ec6b95350daf7f9d2745bd1b8a5a2cd5517f045ad56bd03b6db36ea27227d637a8fb9c35be9b044aae1ab1301db053e8c03eed3454f8913fd0324cf54752741d82282ad2671b112ce1580c2cca5efbe71e034f615e2f840a559ab449c258ad1f8f666003812e8568a646d8631f38b0201ba8b3a74cc643016d27143bb2b4ba4bbb86d5c4100fae1b062a0a20961852bd12d4653757e87d7f9ad60a770e59727b3765cf160581e70700eeec91e90f8708b8b48a563a446c54e12d4133002fb894f324998dbadc7700928bfa788fe89eabdbdbed94be29879320c987fe2c091d050279322c035c385f492cfbd4a47071ca0e5457181b8f65c660d72148dd24ffb10d04f7e07b87835356d627d7510268816c0fe53f939ab68c8b1ef4c715ee3069a83301a7a635766d1d779850f93ab29208a3cc567e52b49959c926b0e00260859cf1c9e7cd18fd00da180f5a4b9d72175144793be699a27bf98f7f84a6b19b1eed3c02f2d320a4441ecd6ec108b044d38eba94c239471fa77749f56be59e314c9cf15d34d9d24995fa94a1b3f2082e3c5af88009f4fa5bd488fe8c95ce779fba204524039650d30365d255ebe0c978f084aa19b3df7a0ea2a4b4beac9e83315246fd24e5ab948fce4a9958167b2d2b6e6ac820eb10b7c1a1c624302486224a810f7af216907e48dfef4252d6509d1058ee8c51755dc28cfd3ae58acd21abd2de43b0bbc6a41823e3bfa6ca6a87eb1791a49794e33f66dac4bc78fbb72b26b8fb464db81c85cf752bdaaece1287e18527d11683bb0ea8310a2fd36d4d1153a06e8e1716c27f2226e1f575b27e395f525dfd23040df1d4da190082b038b7be5ac13dc1ce8ce17f407340e3d0f7bb04c546a547c13dbdbbe2fda7fb717aba89fee1932b1a4509be1ea624a1ecb9803265e3fde3494a3eb3e80f5342029c660d4a3b240b60d41b0a0c2f5aa26a293680c4f6c80635db8fa3045ac3ea5cd437fc6b667bd95c0a6b2b33f3735074d00d2ecc88e6b63ce4fae1a26b01168998aac0894161d2cfb9be36bebb7c21a44097d544204cbf4f131a2d7ab5af423c13e2cc1fca0475a6e934cf2ca2808485b4fa38b4691c7c3e254c86c8ad63e10e93b007c03c03299ca67fce6f0cb342265c7b665ff2c14cf7e185ba0a0fecf6bfd5cf2399e22235e68f62cff5d5e3eb2086bf80b3daef50a3980cb09132e6b7e92bb16ae16418e8c7ce5454a405e9eaff5a1f097845fc0f6cbaff40bc1bccb58bfcb6d17f28a163788a776908396ed6acc707fc16fa21a3b138cd146406b49df79ab30b39df7f23ce93769374abdf3ea6bc9c592071c8e1c9fab436d3e86e0c9ce954fb5116fcfbcb8b65cbc7125b97fc2c4a52b94eea629f855f0ff8720e619c65e2e8087729e6c1b3748df4d30fb3dde23a7cc6f6af7096736a9865005d80f909239d385948c7a2b61b73002795c6604261c5aea38072aa5cf087ee454b90234301140a6cf3c9e134a1f542e30002dd2f96b5a016c558058b82a53b02df8af6badb6f25dc9933509602a86b97f0d802830b90d86e6ded9b86d301fff3ed63abed1b69ac14c09a93d103f41bb05768070bd50a9c4064b552fa40af5479713b85ba2bb9a1bc1db5d6d7590f98be7b0e65fa260c1f79e39ae6eac39c7b910b7fbedc8ff0237057d54e137606bc7f845a71b858c7eefe3c8829a6ac6b8e4f1f58e29c1ae75be68b656765b899e7431bc533a0524b35f6f6a97732724077295f252d9fa8c206118bba49f474d1a6bd2794ea5df5488a50283735a881357564a07ee934dfbfce1d1b42a29e4cd47f32d3400922c113679381197b86fee9bc794b022c64c82aadf3d476a71971cc20d27cbddb9103b41aa3791c6d232ee02db31f34009b46659d6ee7e267e3764563cf3353aaceadd8b823aaaaac63a4c52041e033909ada7676a5b3941d0c5e0b1fe027b0411ab743bd79e9bfc3d88f7ecf61abd9458af6398bf09f8008873926984da88d0c785be49c10ae1df9c7d181dbeba8b6f46fb48e43924afb9fb985436b6186ac8b50b4d9f8d39ca73a78bb1c78d3346455f22d600e42d23cd222baa15eed22b03689b3d0309efaafc717274d6dd3ac5aa74ea90838902f7ee97a3d38106e4898adbbab313b84d2f5ce5d91127f533dfc75b4a1d8206ebb84f69e4c09a264e95230d40fd1c10d62771cfd983954581d7f58f3b74a463da1003fa70d2e1217886aaa4fcb1e7c6b55b1f5c922777328eeea8fadd6386fe65f459dbd721c67984e52644a16aaa3baef3bcc9f101b56e5c84186844109bcaa5724517893f377b28a9ce9cb0f121b0986c723c2c24e171f453c3852d0336d2f6f857cf87e917d6c920faad68ecdc99b82dae427d9774442c4771f394989e8345e0ea33b97dcc8539833da0ad9199cc979c4755f169659bc710b1fd93605b819d0d546f07e15fcfa750a5871e2e2ce71d9674bedc3d322e3fa21c1646daaba0ccbbcd2df5dde784521a96dd200e34400e3907ee9e39d9fa10b4893eb92f11df667bf18e47d92e17a7eb4492ec866f5049f968248d9e9ba697f77abc0f91e3d39127769ca5271736e9f683e422a027d998df4822c4e5d08d32536843270ef90b762c8cd4fd2b51c745dcba91fda233736599603c6e30ab9ec5ee2189d5ac8b988dcc66de423717a30a420ed0cd4fc78dbc0890a08da684cbe894a7a663554d96b9ebd688ac43521d9a3999aa20ce14506a7d99c28c9f37588297f803bb3b4b740030f99d77823d2d0bb1af527ab02fe35aa3fd9a17dc643a4ca8a28c6e2ea8a85f1986ef0fe2a5f23fafae08b5e54f93ee2ef7016128c0b9333b7b43e062548c222902b7fda8dc87adae41bb0708daf082b4c76e747e92283decbc40376bf48245edc5a30d6e5cc84a83125a155296fb91207630f88e46024194976443cb4add453427e6244ca80822bb9759240178000d132232a07cd256b1bf7af0921a8f5920a7e19ccfa1a3e3bcb75b0d67e98b88cd97f2c8b793a9940616c3c3f4b3656582353b362fb08178793f581113903024d37e4eb7ff4f89fd418d184bf8b6b6e45618f6523823d283ec12d19ff5725235c3f3c1fe3454c83336a34d70577c932245f3085108a7b56c413137b7b3fd0a933b917abed6bb2f72a6dcc49a5ce89e129c0d7abc6d3d1056a6a07d3113c16bb7847b9239d32fc16a117c9e9eef72503b76b5ff7449cb4f4b5a03dc59fb5465b833999279de7704aae65fa67eb8b4c9b9b95a70d8f63a625be00c7d37d19beb2c946cdd3c43cc9ed975709fce58ebbbf1ca65c5903984fe1aa5402cc712e589ba3183be66c71824f47b6be50750e5d815218ee1ced98a4c36be9d9bfb76460ce40aa7253e682146e1e32aff144ac833818e98a8bab69dcd492c3ff68dd760036675cc39f39f5d155aaf23cc2301d25f4017bdb8741c094e83a69ca5acf4ddd42247ee68aa279679e2972b16591805b44dea0b7d75d3b8501bca11197309cfebb90aa381ae3dd52a44b6fd94abdd19462a7654f6cdf5021102cf63a34f800a2210a78e71bbec56029dc2ddc1fbc10d27ff83575e6c1b76c7c4387d46d21ffed722bc6a81e1c70b2e559d9bdb255f68a99d7527aafb27b419e102f253bca064bb81cb2e3c8a394206de01b0dc4ea2874cabedd87ea77afd86219858190e980bfdbf231458bdb37ea2332588ca628f312a13b295a042df84fe47560fc93a0fa8657f54f0d73dcd654d74f508130f82d011048d734d31883a737dc411cdf5f6867b0e250f6d75b1260dc01419ff5e8ea1e23183803223400f1a2219e6680a9dfb1f7b483d1667bedffd86b5afeea6326a311b921a58628c9cc45bd32137f9c889abe0990f1d31da5709343a6a9a229ec87f10f4dd440af4baf48dc830dd8226a7f0d3ebb873c6cdd54bc39dd1d85f8634e452dbeea363606547a727727dffbbdd08e9785401d53f9911bdfcf0fdf82724da26ccd08aa97ca43c1d022366722523dd1c946f86ca473d1ff3afcf12f72c37d51197c0f3e9421bdb7ec88da166c20b490cb031f35243613e8dc588efcf4c6ea00feacf097eebc5573e2b4f670ac18399806ef375efce33f8a1a27c08cba9fd5e12ee614d3d0094a688772d6a7478f3b1e3d44206465ca1ae4908968c08260531bc9bb906c2818461040b7547b944c605d4bbacbb49a0ec10b7e9bfcbebaa9713f61fe244be4c01ea42a83dd0f1058c394e6ea4eedbc9594e5e4b2f1f1a3fa156a8448b4a04f844c87b5c5e82ff1800145aa0c7598315610046eaf426776b723788f0f3722fbfb30ac75757511a36262c98187b09b64324698187e22c55c9fa4b8ae8afb5f8e0717f50220f73032cbfcd1a3a27a996a3ccd83a7899223770440e189454cf7f4fca09194c8e916f526a9403204c138a9d5c485b3983d3072d61e95f5abe21ffb3349b8f5beff76da515a67eb26472ec1b8d1b502174abb650281419d33a84b25696000c7c4f669e3238532fdda31920cd334e62b410f2b99374efb6b7705e812b2af2e552f7e0339171b4031f2b5b113fdc3dae0ddbaf695ca68bda8d68cf1bbd432b55016a99bf6f5aa3c3dfd084e99c223e4c4c0f86424d1b1c1bc4c48ae565f640082e56e87f4eb126b11eef4f13e342cb8d514e3bbf610a0e68fb8ddb5793456154e2771a2a42651d35ddd2f3bbcb380a42274a162a2205686535fabd42505013ec11db280c5c7af3e048287ef28f55f03820d2af1c1f4e81bae524a3940833e0644ec04d82f51bdf89860de35be2ff48992b59ab531ffc20897ee47e57a449c0af34b2cb2a14b59c9ed646d3cdc7582f246d64e9e63ed68096d9d3265f1986c9618b6c519ee053d50810a71824d4c2a45320ca780fa8b0029b57de7c5d5d6e3378220347bc005518038f8dd876b41ea5ab36f70ff84e72c2cfef2efaeb2fdb9354c9d44626b59f78a24442ec55541a1d7f368f5c680032e1b6ae36eeb71dd45933bcb66be17b131c5e0f12cf9b271a7e33d16d0e562846654f2fc5e62146cc8944595663ce0301ebb19e1e1c5185aa8c9269296f103dc0bb2e748072180ae3fd19df21898cda76109e059317fddadd3aec190a947b17752d61b2d8bbdee6306689b8aedcfbbe86ec326e6adee2352383c49cadce56c377198aea8a49b0972e450905910e99ee71579dd40c21210e35bc74d593e1096bee36cdcf05e46a8c1eff2e67ff98d835f63da0d5871ac8cbfd3ce69f8b60272075c3645118c11a35663a2c59f0bc0ec15c58f9a2b8bc68fcd2be0a1f100bf687cd4abde48da17ba30eef33c0114397e7552b49d254f0f8d3f30649526adeb42a4b35394e705ad633e9eedac1a06c907c6b881532567283874488cd1fe6cd670a8baecde95d938e3b58177bd4e9a8ab305e44e4c856e03fac6f41d9aff5d8eb5e2936684fb93a6c1f1a468eae1878da4b890f89ca8ef450dc3e2fc514b18990dc865cd837594adf1268813999f37b3025a4aa47795fc0cf1854f077631ad5ae9f7c48379c300a395f71020f7a03d987092ed3b19c916d39ed68044306d043328169c50fee3ecc2e65798513640dc26b9f060311a563453d12f5f481ebfa83ab2714a6c54f6dcc399e516256e382b61629b185adcdba8126201d2340b73634f0b29348b4433020370f08d937a4b4ead6f35649b0bd0902b3a73adc9b7aa27f402a330903da95e03b898e221d5d0fdba3108ac38c75d9be439ffe906b409e6f6e5081b8982173825e510e86274e12bf89202f235f546f13ebb59f83d846e418d9060ef03f4b36365a5c4d37435f7c559ffa91f4a4ceee11f91a08e4590f928c75466592078e85347a96bbde727d1c91fa708a1b15e115b56efb58390170614f9614111981bf9a6bf336c2e8228a0c22c7f495e886f8f1c28a0f7b5413755a77c0eacfa9328ab53b8714ff5c249c6f6e9d5635537b1e0f93d1bc2919ed7c7901cb0c67e98ae00949c3411e88cd93ae20f7ccf35e856ee268ef9c743f0fe828f6999ea2fcb734bc0e4599c0cc5af46c6ddfa4b59e58cddd1e73105a8441c354a45511137b7aa42f7e8631914754270c58271ac99f0cb7301c6d452e859f92f0768547cce4ba6f2e82ce8977d8794d58b1f8571db8e0490ea90031426ad074a5d4cc814a1e4ab862254a89129425b9b764a853cf274ea180d26edfc36539fbd20bd11d963600afccbfb774e60b44424c3871313fb62b820510a0bae07f5d37950008ec3c9901257f2ea19b868580898988960c3df4d8c49d0043b3c863c57f761301ceaf3a6001d5402192004d9bddc086fdf974f2e5606c2012a4730b181f7d6eb6629f5a68bfcc1853ec962dd82449dff04da2325f2d46c37b1925c86e1d77c07485e7cfcc41710e7ca49e458b28704b28f951ea200761ec3188f51274cb3fd241cfa55224adacd95edb329e9d9150b26379520d5d907a7b958511b81b81da9a0922c3cf14817a2736e14d6eada46c1bd6a08b7663853bd0e4c0a3f5cb508ebbe2c0bdae641ccc4d76b5b4d6ee2b44ec7eaeb235185a35a0ff08f245d4c12d2d0e34a4800120bcf89dd853fed0fc6e2b46750b617419b8d07d12202082a4635ef9ac2de3bc80c6231b8b1b285f64cf413a47c25088a74ca7df0072efa9f6d5d619a9a23d75e9044feab6db14cf51559146881a2423e640d897f22c3809811c515bde6f810957a843c0e2d6ddc07a8a0ce1958c2220e230f56dc8bcf616c2383d919cece2c5b232b7893e8afb99deb7ec7101a927ddaba014d185c467259a82fee1070e064e2ebc75897249bbbff7a5f4908ed954ef4365788a74811a2251d0edab9a19db86e0b58c725fe574c130bef71e9c2f6d503e09be200e488cf2044516d4c62b51a0d894606d91576d7b8f72da2d4891f0e69fdea8b8f9046e4727b7a0ddf4b4d5bb1dc5815579bb70baed6616d44322629cba0e5b24182cc2f21a4c2ed183a7594d55896014fbaf3fd2cef131c23b58803091e9dfcc7f2ad248e444f69fef4cb3ad997539f327f819062a90ce495b72b94ca3b598bbba7d35a9a9b0293502b50b598fd45fe346387fb801a435d5f673561d7cb1af9c25cba7e06e4864341f81f1b635eedfed9821732f42a60e40b35deb7e51fa3d144d731d71ec81aeb77ea6df1e0430ff9f32809e4894d7ad671570b18a4126f5ba56f0d8fe1e89d42dbbf3237a265b0e1245da14f26a753645a6e85b047bf2e401780df4395efc8059110c102d9745472b68af04645571b626938b55c008d4370655a652204bed0080f93101e5f5ee2b77e80ac105f0d652d4263cb4d5df1b227251ebcc7c6e3c72ec85f9b3284d0397b2e2c9dafd52f5fc8d0fd362e3726c3c98eccb56962cd97e58fda6237717e0000a3b4e67906f7231946aa135ef0580bbb62fa894f5f1aecfa3e526f4eb9fd3fcd8e07e910c748b5f1e253a728416dd3b49587271e8ddae7182fc2958ced643a48525198e9ad64afb6a596fd133964e33a4b15c6298eea5c136db746b7ca628b147244c93ea3b53267001b9caf3c93c2301bee9f6ead6eb12557fdc68a918a5e21ae4a571357b82336d5059470aa0edd2f2e9c62598c399ae3a6f18e1ee8fc9fe348e87d81b18ea71a9e3a2cf244d67264066563bb46d39341fc312c9a2c960e846d98458c17f44d89fbee3c534324c2df6d46c2126667ad7dd8bb76194b3c62230d681901462e935eb9997f786a5800dc158c7ce41d0a68875841fb7c8126583778d4f789adeaa0a3350b9ac6d86491eb248486781564688a701445624b56ba6b69004b9c9e0c98e9dd44cad4ed11792b3fc2b847330adf82a4ac86f8a480d4d33d8af3e4b3ddc13ee3b95d1081e75e295760634a085f1180e5c2b30d09f2d5c2e8a0c408caea7edd07c4e16d11c527362467d587cbf3f3ce1b426a56b0d5c90dafbab34876b424387588d507a17009f3fd4f6f2dbe28f99240f0ba1d6be3cf6ed3fea416b29c0dbe6302c99d760b7fcc0511a12fcd035ca0dc8cd5fd0d6c182684bca149e339202a42a6d6a0743704d6f19f35be458326f4d11eb3e41a32871e78069b731940f89380721c710a3caaee3aabbae826008a22dd5e4f0f2e86e33d005e3692cef9a8321cfc2316775613e635514400c9462180185bb4582d3e18f83b94a407662dc284d4dbcde698dc66e362edc8423b764cff518ab26564042e22310bf0882b558dd52af0fecf93205503db3664a266dd45a168d8ce792c6b4fc09819a350415461fb2e0db7c1e866fa1fd492100fc6027b25c7441b00583a2c2a2403f2917ac4a350cce001bfacf260c769cb13a44f842debfe6ddfd279ce59ffed17bcc0e0494af8483e2867300856bb1a88b68d6c60c8ce3c9425868b39f7339bbdff732fb666c4b135f89f4ba412a804fbe09324222641ef9cc17acd90353bd570e1470ee91d354e26cace88b0b21ff578af2501994097850ba27daa490277c4308cbe5248ecc256d3dde23d7ff020a866bd1a09b42b87a24b41a28c06bc9b57dde569a6eb88be32bcc8ed4074ef3ce477d30ad2eddf0a787c4b246f9581afebe67af67f6dd98e61fc904c9006748e083c2f7f7e4b24e49ecebe5a214464fc9a89d912d8c4f66ce0739501001726d4d612f31037030f6d6e5b92ef9022c7d17961d416d7dbb62da438fd8a0c18b90900a434f7fa88946857aadb29765229650675d70000a09cf40136abd9b373f6afe1ecb3ca9bb20f388d0d752b75dfd5a97649660343aef0c33c1e94ce135cca1612abd0f73d612247ab91ce62ae0a3f167c6d6ee6da1964c94af51e1729f21847e1cf949febebb8f830d984dc0bf69c2b0093b01eb48428e33c4d2c801457126e73ba91bd874cf6f1375b506af307a203da386c3e348ea12dc78f4fe42d072dde70eeb452e8f7b7f63076607f6fe407301f5cacd2e64bcf4e88dfa51b99db8a010ac9f896bb1fe020c919a34979a7e48de1a66536d13207c6140b315bfa78e04b837e7103fc7941ea3abefc983743d34102d6165d60bc6c13dbea4dfdef81015ab801e8ea0485fbbd08347e2354c3d304c0b8e55695dcc5240447b770347d999fba47d2f88f4a98f05b4004e3f7889c53de1ef353fa5b4a02154a1954de4a4d1fdb3f5b915b728034d60b7fada566e7b4e6e10b1ac6e38c523dfa4118ebbe28f291d1b152d5cddc9656cf9c04ebe8eeb7e570815fdd4f42f172347bd8f6dcc747611e2048f67e112089c697192790745d253f63aee89cf9b68a1ce5dddd54167fb3216f919daec5e4dd9effd404a035038f45dc2453e8191cc94acd9ed29eaafb6229dfae5bc398f12433f8a88a09f45a4929a66fe4fffe9fb884945dcedb8392228d62365fda8ec25bcd8d7052c2455b9d471846b316e4e31407ebf682df2c52f850a333bd208ae510e0b7c79a1fd5ded55ff27559b38016da3fd49fe0d72371651f8805797079ef512e69735fedf85d97e87d1f2973c5b3fd1303b0d5ca6047aff7b1be68605c228bf86c369ffeb262420432d57494952bb225cf4e468f7aa72a85486dc4755a78995bc0706ff94df9466aec0951940c259edacc1a7c3134697c04f6bf025258765353bd0d736dee0020b1caf50969d3354e5cbf18d3fbc3e88212daa77fa507ed65f3df9c09361670de7e00c4748c6dc982b29853d1c1723abf3e932fcd9a467ac32cdac7228820358e81717144ea93c1e0bc7630e856270b83601dbc558ecbd56b6d002e7aca95381d106ca696ab2d2e95d4fafbcfb2475a67dda02d0507dda66e2c8816c100a8eb35033b2690c19df9d4272e72b1ca0f96cebbbcbcd99fe0eb0bef373b31a43c5288e19f3e9b01e21c1bf8ab9b126ea21fa5534416cf693c628a8697d7d1dc7dad7f866cdd4f0513ed9a12260f6a3cb167c3dff0340776f181ffb71fa6db5833f0a39c00a359e6530f1e68daa2b620e233040d00c457207ac0cf36c9486ffec20bc2610e66880aef46d1c75fc8e07a55207262374d0750025741847075ce1d8c78939459c98b223e27481460bfeacdb450a316e43e1a25c89100eb4fdc767d8b91cae516bc03e1053f9ba8e698a440f7aa4ddcecaf381ba46dbb572ecb1fdb2bbf08b55c118a6d24dba5af399778945daf0cfde248bac0440f92cd8ea5e5683b613526cc55fa9483720eb669b59d1dfab241d129dd4bfd341978fbeb5598aa2b9547bcc29460383c222edcbced03ddbb324dcecddda98bc55b1faa7b0a6f9760d80618b60a7e85d3aeb30e28ea1ca9cc2c78ec2299ee0d49904a8e8389f108a5f0872b9fd30638d94bd4a1f2e8cb545bd0f4777dc4c44860844da1f983337bc7cea3679e9277d9c171174b7043fa2d2c2d83635657dd0ff1446382742d3c78e39968065f8b5f9b3be3e0141749e9427d9b20cd797c3bc0844157dc00605967ad5a7ef8448d07a769f1a1ad4f6ab53f1cd9fe693151a109602b22754b7b69c33dbeee9ce2cad705623c14e9d4b8fbda8c3828f65c9539be4ec21d2554e7613a9a3737f3dc24bf0ab25572149af469ca7bbfa1cee3b974433a61b2621b6dcb1c3943ff6b3acc1a7ff9c81e8ea3ced069939d4e42ccc13c77574689e28a223ac4c5498627a05a7e7b8db5816e8f81a5f4652c9df5865049cf895a9aabfe943feb8075fb1dbff235daf2ecfd6232f0250a0f762e35c028bddfe2b62a92c72f10df20a123f11c8d03edfde687bf8213a1ed3f653ebf895e2ea961bc97f952035b1c7c9984425cff0c0ef99c515bfc27c97d55e6aa3571549daa0aac32509492f091f08a0233ad84ac4635032cc2685984d7ef4caeb0965683a945f216e3ba4e7fafda9aa3879902b5d9c9f08d6e75de03317330b5701da5d52be8ca23fd216a517a4943a255ada71035f99cf641ab0c5b127f7acef9f4ec7b01145bd04c7619d4381849dde276a7aa5d25010b8e75e355c309156606e7128335fb85694215f3a5c9b814b47b341091026171688de4fe1f92e6c0abcbac4c31f757e596c12a991656f93d8132c54b05fb7d42ec40132b928fa38f1326364760c8fef47744aa0e49b27c886075ddd02dfbd416623b08aee99831e93958b730a60df6cec854d5ffba4a63d09bb451569082fe8e574102ed2b1fac505ccac75a10f40a41ad403cba0843ac4bd2f8af23e6563d82fd91b79c357e758a25ae4ba8cce1c34d4c8317bb333d497876065a9e1fb815e37ce01c78862500772f3445cb4a4aee20f3ccc3f80a260f68c7b40f41bf9c4ca83df215a6de73f598767de790ff9988a4fbe89a0346732c40346218ad564c1e4448e0b5549f68c13c4efd01317cae6db92f318c4f7f815ced1747928b1c057827f5719fc6dbddee2483643ae47e02d058968fb9cf13077bf2278699c2ab108cab7e497c20c07e231c2ef36d0b4e897e374a6af736bd24995d1a5f10b67b229028b59a274d43b3800f22ef0cabe9fec5ddaa540c2d6331de77fd8ce9a1917cdb3f22447aa493a1844f629244348431cfa8e7a6e16224716ac44529fdd37ea0189d225f489e66ac969d4eccfa555ffad31220d5cde85a920a2ec86df8f4083344fa66dd2ca2e6fc64e800d89939d71c2d51df9c1103bdbca57372f64646c69c0dd639ca10c1017a6ab32c51999665cc0edd3ee56751915b00212a89955839de07ce0133260e4f4977dbe553aba8056d457b3a25f302e34702f8b58173811d4b74a619a57525364e56fe170d8ac47853aa99eaa3767c9f5bdeb572d4348bd6b4bde996ba5e9829b9862aa36c942aa2b85f5aae8f543a47cb7228bae2955ef2ec3e22e49892d6e3f5a09b0730c9e76b6ff85669aa30f913edcb422eab3d63122af68586d28dc9ee47a383f0117a1210714505864888d26fc8e8637c6715f4bec837e4b15f591887c459017fcaf5d3fd70158966233a4860ff381cdf1578a23a630953e250b65b22922e05b7c033a48d3b6d68ebaa6a9de9dd40272551632ea2afa9a060c1be94ce1e369d7d849d9d10415d7177be15603a682dc4c01da9cddb5f092982c6e7ff45ff7b5d0df1d1c13fabf2f6809d31b0a2892148c5c3fc92f9871517a4ca50b80b5927156570c9116d15c52f5de875724c5a69d00b1876120e9cfdd48d88ec930783e7d7e0180be2ab3de286c1db015e551ad08654facf8334c0d9c902aac38f0a1a66d8f38e8314a0355d6ff5342740402d00d53e8453f860f56cd0ef9373c55990420d921d5364c812a6419e44fe9ffa1daf0f063c16fc862592ddf83ffd773c00ed0ce83d942310fe5f18544f01bbbc6d5e159afe8c67bc4f5b5be595335464c2cc41c47c454d3b944f5950f6948ce0a6c86f8a9eb2b408c851e732f6da36a79851605c3ec8bd96039c046eee2d0ef836953752522295a936e659e521d73196d8c76b2fe4d3c939cd86e47e1235756a5c3449e7596597f978f3c99c3fdd6b6defa5616c9077e96264433405aa6ab4f9a2429e28876fb40420355e22c5a54c38c34c6c783cf4f0f5e8eae2d3be34ab0a2f98343c02d31b361c0efc990e847a028e0e7b7f4bd92f6fc7dddf79fe5ed80a18d685030c9d05a111793b02a6e9ef9b2e4a87c7764c39c5f326bb8feddc69e92bc90856f73305e2c9ef399d8332937d32022e0a946188c8c849b8a7975159ff93ea4b89cc7338ae2c7a7f3ed0b8d4a884b8c8795fb760d959ba3d1717998d50e1f95c021a178035940c33f71097b7d0d41bb5c8724570c45998b345bcbfca2623c71997b3663cbd03e5dae25e417ce42b56a0594712f3febf578e468ce6016abcb4b74fb3f38ac9061ec1397b465e33507b24a8b33f761e2a92c42c7298046f07b079a7afdc5da77d702970dd5c3efb805b437dd05715b64f52ddbbeefabb356d271f8b4c3db39d9614ceaf356f676443396001df22c4ff67a7fb227dd6a37edf7576c0c63a405c605aed2713de65dedc52c9246312c5bc850c0b06b480a62db872f1dff31df676b550c5fca5ff0bec1604a8c9c0d5551ca6ba52df275eb97a5c792d8cda278383a766f9ba57d1bc5db05bff767e9a9505d7dda820f76f2d8691ebd0cba10d0b5b6b76dc7a8e1ab682a35a89c3d31d235dfb4d1c00e9a25505a61b8afb4c758fd6d2fdcd9ea03e6079a8e4a0f9e0a071ef4bb11e52515d433641f087c90dbde3022a93928baed41b0735c5d5ea9bb4012dd393c5a49da8ff0c462eaef9f8bf587fbae1b652604b0bf692f59b0f3fb6887f7e2c24bc1f740f7890860c1ee4a93de2c2ba2178c6e21bfb760f20f43cc2ce331f17e62f8ccb27d3ea084125a163c3b24fbf19ff9c1bde3f73b7b57df88ac66c2c71b431fdcf0d9b43fc24950ca0dd8664fc9280a942c9e9d9c12a281516b8279eb786386a62387ad898607a9afa9003e96fc7328f7e2f9f1267ca1f727d463e4f26f5e327aa8e6b836bf488994cc6e47d026e9f4322c96e472058a146939bd4f5214dc1671c17c16586260d8909f30a9f7650dd055e9a1d6955e3a8206e9b997cb08096b9937b68974ea31cbd3678ff27f8d056c9715ac5c58cd0ed445a2826488debbb198f8245870f7f122c11dad097068c21cd278ac01a47e774c86fe4f8f2020b4dd5c1af527f5d73e57eb72fde5f4a4a4717e3356866578d0239d8272a1dff14dbf32c25515186c7f13c813ac616de048fcbaa25620c524dc47deccdebbde09159df2d8892991812351034bfecdcdcf948f984a19203d22003798fcd19934f7b98d7c6741959da44e0ef3be5c854de85a40008d0b1c743857f00681998d432839a69fd6052fe312c12762905eae0476a3794c278c222ea47baabbe0802f50b9b7fe4da2b96866806a3cd52f2867d150f2d89b2ade2b9bd8de55f02511a7c7a9260637213a08f492e0b3b607897e1dc724e584fac365895cfb4bf1009f88bf9bc0529e1bea327acd81039ddd5f46084e0f92c6ca7997b8584761e62979905d53a75ffb0a515016c8dba320dad8af8fd1fce116b87fb6103a2a7cd435b3f727d9fed8d126d433348df60b69327725b38d67a2683f538c0e4c97914ecdbf733043192a45f6d38343d8ad5845cc5712019712d7a5c0799637a5d93b3c52ab3e45e100e19a96e06bf211d28f5fcdf4b02b06f6eac14496d2346bbd821aee6c8b164518a596d6ea4a76ca0cbf34136fb2eefca0740dc2fd1c0e5d8d9683b3be3d126c7b002ef975b87581abded67975e87040d58d0a3be1f99949dd8338cdb5528d2cc12f4d228b7013c7162053db662a702093e82385a8331dfe9cb4829093676e93600eb099d03d82d51a56b699ba87e1a0adf438f212b44b9112e49326582bd78c360ebb23b0f9498704d8492760b35c32b5492ae22ad0005d66d4150eb60664eb712d12132f8af4705b09534c96380cfd6bab525ab1703bc46c3768de739062c6fe63fa7fb2cea630936a42aa553a976acdad634ca685f34f380c38bb662a80a827c3de2c3961a77f929242e72492274fab8873c1e446bf6ac616ddc9f76449cd486221c2af91f1772a8676df37f10832e51d80b7d0d3d6f4fcca1841ef6e16640b70759070156a33ab989fa1d68d47e5cb6d8736f5d0f73a8b1016a4c14169f650cfc253dc247ef4cb6838667e4f4b4fc4a540c09f9ee130cf3234e6550bc49fe1997883c66d4f71387aceac144cb0a1d6654fcc79a93aaf93f4736a384a0b4529e3e1a9da798537d382b27f978484d1813bfffec52c9bcef7438e8fbbf503d543e1b665ab3e86b41ec43305072206eb4ee5db95870b37e0bd675e66144d914c082cb5e99cc456ef9a9f140d48a1af78c469a0b8bfd2765e690b0e8dcf6b2d7a3db0ea3d01f5356deca49ada28a20790120c5cb727f8de66de057a4685d7778c2b34e6bd0286a745bb4191c5cf7ca5b800f2ca3c0405ce85f9a3044ea3ed0821434ef5740a6341e9687133615089dcf0b8bda1ef1508f1f789bda69743e1393532e8853c17f7c6c557dcc7c74d3006e9af13a7533f49153acf0279b974f0f8a580080cb61ff30001c0f713492fe6579f609fb848e9d1c5f862dc5ef59edc3786316ab698b1ababbbd46c4c49f83fa57e58feea606d7358bc28f48336c870014071d27ceb659fcfe91d7ea6631c39683884189c136635ee63f3026458238c7dd47f24c2bf2f6bb58e43777572f16938dbfea974145f2399db6fa640761faa7d60db9ed28fec122e0db74ac9bf410307aee5714502c329a4926f12af82b1283469339032c666e3be1545ad109a612b058624a78cf24d805bdce6dea5d0d4d60316a6a3bf80ccdea0659faeab7038b7106006c2de6fe3f8778c5c4cb6b9e13f2d778c58148f7bf93578cd540145d1194cfc962a52b3ea595011b7552c75404e54a0a6ca887ef3a1994d86245693312570600c8b988a6a993fe0034d59c98a4b3340fddc619710e6940884f4e06f469c2912c020873f883e85c3fec0f6085c9252bcb48ae0f791c218e541682ca09d59bff7661c5e67303f81f4fdb6d09b1e21b4702ce1831bcb503172285b3f9b467ee0731057cdc5e7b1c622b94e35772545730cc63fa1a9b58353b46364631a5f9aa089699b601e5d457075bfff46bbbb162ec1c222a23f95e704e31326858a671b1f6d5cd93b9e9507764f1e98facb987663ba544dccd762759370ab1dddb72c3062ab6066463ca2d15123c608af73853b46014c6b39f3b752215565a7364d38899a25790ed3e49a47bc8640aaa2723978ab2c846b2a614bf964a91bff52f032b5b0c9a44b22761214b72dcfa1246af1b7c70913e613a97777ce342bb8950e5e493b0158037c62413d43218e8aceba3db83ef224af2724823c486c27f9470f68115e5d677ff7172ae9f2af24df045826e0f9996d284daa7889aab381432ed6f88b48783730a7b0e0d4a77ac3e3b1dde044bf2bb76274b0d99c0178ab5f0acb00f03051a32e92f889f98a8699298692f8aebf3a04755d52e399a0c8584043776ec30d91ff6e6e64e4149a0e27fd9ccf5b6e0b175f629ca7ae9e76a3e075881fa9ab103faafe217284a44e8470c4a937daa9f66a90d3f3732c1c9411a21ed988ef595d3610851e489c6329928fbabeca39df6dce58b7426661532d42b4e564e7150105d6ee91bd1e18561527c20d1408e8747ec2280630ef565c9675d21ee0ae9a62ec72007fd606b3ce3557a8102650ac0d42f6d4cde5f2c5ea2aea909d5ab96b1215ddc26f32af4b301d240a3a6106de35d8074b033600e38cc4cbda2dc0c483fc377d2b8a27366eed47646205dc6a217bd6ba85308a04803e7b0798f4601fb0aeda827fdc1658733dc38baa1dee1c40a6aee7fa43216f3b2acf8434591c0a131783af728609183fb8759e285da75991edcbf2e0836fc147fcea20824f223b0a50e318ceb2c1d1b8cbabada0c2cfd6d15d27ff470a3866cd226b7833b805a4833f6e38aac114c40adf52386f4dd938216729ce9d7d7a68476c052204e5eb5b6839f63029bd3fb894ed923ab1dbbe9f2fcc889b7c853ba6eaa3136756a8c13c96d8c99adb0c181f260c10c841e565aae20300ee8c163150f473be8d1457abb9bca7bd56a6fffce8f5b0d55d8520862d657653c5f405a5418b88df85749eeef8c306641667f672d7c6f010e2786fdc33529ed6474623d3c3fc4de806c1f154bc7d5f761090bd9129a7592b707a8b918fb8fe271e2b5bd5ff3e0d630cffe2c9e94c4cb4b2806326c69c20760a422cae77c1deae7c54b45dd43571bd5b5ce03f662c5b05989bb3ab144dd4d8600f53a8f0239f4de13e835cfad604aaa2a9ef6effc08a3270e7116d36b39ff2e51d360690c0612e318ba065ab95c6f0d0f3bd2f98fb4eb663f68d055518a69a9bf73c459cf491b40b087a16c0fa78451e37ae937bb940f82b9df0cbbe1c568d1195f5731cdd972953b4484eee6e197065c95fde7209e3e3e5aef75193332a88c581b3ac5d7c3d0c3aa2792f5e2449f4668e32b2b220ff965261e1632bc89e3a5eaee41b594bece66df098b626bc00cfbf49fe2b0e81af755c5a7fcc9212dba4881a31767ea2c43049c33723ddd1b0bc5d58b35e4190d2d9ab3f8693a1e1a6074cb533eb5ab35c96f16c944775feb2ce144754c6d98506b2bc301e890a83758168e708258a2c2686f6bc66bf67b26d83eae92745d08d88056830214a11f88c569d35ed6a660e9a4c2d44edd3f2872f21ec4ff8d8f0de0c4fa6e5cced8f9d4f962f7fec38080c28399581ab84fbf712ec7dfc1963b1d8a246eb5ba4b6f0ae0eb6963a4a66270e18797603eab844eb94cb37ef837123b59aaea67bf09688f61f44e14fb56833a4b2259a5af914ca61f7cbaaab66fe810fcb97bbf092e41448661857b0a992dd930263ca23caeb82aa9d05e4565bd51f10e5b2824fa7e622a9c034c89bc6af1c0acb16eb7f783b9b06372dd715d9634267fdaa05bfbc9833bb984ed074d84e306ab5cbc9fa4aec792c7bab69f13761977ea90f7b31f7a386f6eaebc9883ad86faba4f3d915bf324affc391b0e1f67e213594a4ca259d1ee92a96e4d984fe4a6aed46e31085c327f663a150b456069d899357f8efd76750cc3775ebb4daab503ef1645f6e6f150f025280da97eccf58c4b4287a13925b86f37f4fc391957e42d04789efe224645b6c1fad34f0046eb0f64a03e9b097821094699a380ec956445867bc25b74f498d729cf66fe707900f5de70151cc31d38f62fc1fed42e1c021f418b3cb3eb836940a2479cc305a2fe001560530764c469a23ed6a89d89c6b0268c706b9147aa01cd330c84fda197d322cf938bae83d3769755ab3144039f59cc8a1567b33fef5f7fc020f14ff48e6484b9e972e4c523d778c714e1f5dd9e030d72e1ec22afe58b04b7f33a28bd39e45438bb63a9b3d050312a34c09eef51d228066e40317226518a91452d3a90b63a0f0e60ff49e5a08b54fb2a2423117f35c948f721faefa0040897570273c730ffcc365baae658c991de76b2f2a1d27f362c8f6f52c26d8d8c61219bc9eebaf643d9fffc589ea7f1305861b0720d355a432af30957f9d36523b1221cd2fb426e94d14f82073259f36ad424fbd424579ec20a38eeba68f8313d72e4a11ec813bc1cdfbe18725fbb71545bb4802e7c6a2f62700e1109f878ff87a6d96283dccdf615a0f1e6267b1a3ced268b840faa0435a4baa7a2b2dfcefe91a9c668d587b5e0828c81fc2fc9285bda90bc6441a6a45bc0dce3288b2c1e6c4854ed7b89fd8607a9ab089bb1d84a35915203cd56535dfdf425a2e1f64a9e8bcaf32630b5f216ca136656444dc3eb5df7b248d63a70fdf73bedd0dc475112e343813a8d049eb28772c1702986581e6527e429ce67591d6ea5cd0baaad702815448b8240a54b59ca49d8b69d54053783b7053008d074df7970b6a30032e09b20ca2264fda9cbad1be159c878218addcd697a048a2b3ab6a0766a8c971d7b636bc736525ff8586d4bd9057dd01f775dda29e4a8bfdc4f34181870df81994153b03e23c310cf4cb7d41aa32d5861343d8ab32a584f8e134f882141050a505cb786fc9f3e2fe56ac2b776ae453028dcfeb8c4f3c392131f599e0edb46a0dd069144a3ec031323931bac37c8c8bed84f649248d9fa4bb695001eee2b4dcdffb9041ffc40a9b61198ab830088e0e89ab026c0bda19a7cabcb8fa9bebb3dc496b97dc1bc805150e56a4a229785444b3412817b36e461518711d9778c599145abf3d6d9d7bc100c76079e263d55e26460deb9eeebcbd4d523710d925aaa8f98b8594e1a8f6d67b75deda36ecbf5818766e2ad99b4ef3f1d391b62c5389373abf06a7b9d12270c5a64cab4928149629c4d82e55edebe2da33720633024d971351ea0dc7f63facd3db4256569cbf89f83b1f1038f9e7c1a713451822fd52c25e6cd5a96f16ecc6fbc28caa60946ba209d9172a5b439284b3d957707b04dc748fe1ea519a8f6707732fefacdc55e97679a3a021861a177afc0d249fa4d2bc198bb54e17da150fa946e7d98ec55ac728f1eafe5073cf1b5621560eaed6d8b958e3980b335a1d176bc5614a470b6cf87e6e53de57a71be766a9ec119562c1f0458b76760bd29c50c50c28ac6ec4767e31b585fc19e48b807ddf325037ad1ecc375437a057806b58ab40a41b2c3bec17320f3a3d4be2363589924d6e2280c068e77b60c6c7d25c727fd9651e2bf413b87204a846586d56f3481454b7e3a132703d1517765445292f8f2238eca7b5d123d73afeac362aba791a7bb45e441e336d9170fc6ccaa087fd1d27c226983f873861b68836e5fbe115088f81f3d27109a014c4e13df9b9505a609b1a1937daa1861250d40286458d1d9cf4d5ea01d30d2497d46d27cc52d30ca3052ef6562977172da932fe3759611671c25387d2f568d2b02687c31b665abe4a0a6982a2c06fac6e361bc5a45427c84ef0e36a8c15bf2bad721ca0afa9f32a23363a8b2f133f914eae4bbd2c2c8432066c74a2a1c0052f799b516ba9e09d4f1e0f376be37427eeaafc6178172b4dbfce202a61db63fb4efbc264223d1d22c66309faea7212447fb4a36897c7a46134dbf2bf843a782eab96a37a2b56bbdb2044eb3bc0dd5e2dc2adce6cfde9f166275707d74ac40d378773a2bda0604578b98487a3dfffbc92d206121a4fa8ec7dc3733f534720ed897ef11aaf7731e4eb842ffd4f9830e8cfa21f51a9efd2a8040b6c7d2e362addb6437a74069a528ae6ea8cd43ec4a62de2c0b6917cc343439eb40dcb0e8320277fda44ce1897578212aa4196313719f990fe29d426936c52cbd8f5f5c8dd009dd7c530e237ef78216e8162d0dba75a2dd26866064ccec7911b308d00c283318192e46e449657af1d8bd0f08439b3671ac46badc731e4f086f3cb0c6a56609765b8d9da8ad00cc3e68bf1af5636ad6941f9190ac8ff1cc96a0000eb8abd5ad63949602b9e6bf490c8e84c971488b1b2fb6a07beb639cb1e648e5fa7d889f50e5ae0a6c440b871e1e60aaba596a2f055a00c775f779bc89878e9eb29f4a9a91c191a426154fe25780a06d1458845bd0c16b44e6434051119821abee230340ef5fb93fd9d9b9ac3064b40a69164499ae8da3d5b7982d6986b044ce6d29478d75b67b59432c2ee6fddbf9b82418df34026920dcd4c73beba72e26d16c887ac3369815e6e810f8a275424a107ec4f6bca411013c7f38681bb380da6e5857b80d1d41a88dc678a2d63a95aa5aeed55d5b2f3c6cf32549c53d0762ec993c1316b7eac9739055d7c360c18d4474c2c06a98f273bd95d5909324293f07369f30312f51443e34d11e30912dddbe516512bb77a84a08d8f3bb2a452e44456cab60d50fd069006a8cebc92efbed3c4200c0c2736686d6459a283f389ccaaefc0202d7b36b0aaea4457aa3ea19c0ab98201008f7b0cc277f34ad8735cf0ff9d0793b8421787b7bc76339a9fedb63edd002db08e8ee1f2a3636ab569a97f9565e8c4b35765b87934619d6ee749b4743b108c5241aa82642b4250b7386941cda118a34ac5e08ea80182842519adcd29b0407651ee8c3a2f2911880fa32dc915ea9472ed1716293a94d0e672ed8309edab30da3654e0928f19e3effed85766a890d358647be4646f40e19d2c56ea98aafd5a77faaa4155a68988fc37c1a8ba733344a18509d389cdcfaeda4a69083e9393cabb7cfd2925f3e3e3d1a177830f951ab0f1a8ba2e7ab4ce2b030df515f0919cf29a935c42362f8428cd82d68c6f73d97b3ebec2347cd3f66e6a41f814804f89bb1f81e282c3a6497f1f0244ced22383aab588528c827ca7d74fe7ced6591142e68006456e08680afaef6af8d1fabf8a5aa29580c04289ece3767258c2aca7b5470ab2af859712fece38d68bc59187e854f2b703bff3f1723ad00ed17ba74a7252126a8f062fe985f19239f8fab986a1f6bd0c957ac2da66c5c60a0209a0c16a4007a3aa300b714a1d1c6b8f12c2cabbaf3d5706f5aa1148711bf0e8055e0dc174146d372e1bfb6a229ce9ab0903ad05d0b9baa1103a5613c689718f18a46b81aa3653b726ed511711dde9f51dfa1073012e75442ae1144df4f43572d282b791af54ed1f7b58285f270d20a2f9d134ec749ac9f30e1abd5e5382580d82c974d7797fcd2b408743aa3d6003b0e5f05e681a2c9eea183a55523440307e112635e606c06134e8dc769baaa48b32eb73b4bdc914b6613b47d80826b477ebe5fceb340d772295b93f08b46fb94f354e2298a66823d56c5a72df8323343e7b96b41be075cbae8b6f2b8a402c26d1f618a32f0dc9b654d5bcadeac519a3ac4de23b7e89a9cf92e7e5a1e7eb625e19baf0eef1ff7c9cb5b8d137930bc4de9b773d5f2de2f3b92d819457c7ca1accc79fc2d0396826061549b3ffdb5bf2ddc93cb497e1b0370527e62b0e3d9ccea4014a362d74d2571d8a6d094123ff7b04459b796c1b1548654c02792b88f3ae50c0c2df5c226584e640a6a0df1b4b9bd1c4aca937b600267d62b5b255264850cc966bbe1e6213fafd47a9767c476de260b9115ae57de82b921e72a1a914529a5b1944831dd6ddc8bcd83db00354af0e0ef61b32e73875b3e7cfdf1db0bd4db7b609b842720d7274e2f772a17ce44a358bf7c02d2799d3aac462028734bb9c83ed44614d1d3d61f9b36c25ac6d121d85511442bebc458a9f781992bf4b52e35dbcae93b06b6d2cd506d284a5fc8f2e23d9aa553020c8764f2418cf9362a55ef9e8297c9d2c60f67557ae286ca2cead33dc1aada41852ca2b61650e9535ea29c4e25c4e1422901b12842bea25d730c683d0204feb9828cab7d5b6891c96096170f6fc9664502135456b7492f1b2154d10190014d5fd3ff8087b441b5c061b7370d40c495e35feb317971b8663f8373cc85de120d820414050ba7ceeab26735a8042c50a3abee020163675c1713922b5bf824fa5c4cc762ef0477a49b7ab2ccaa2f82ae68de12082153687bd5e3995851e1e95b297c1aa059e39e60c6531e0aa9f12bac35796ac90b56189a72119c1327ad4595695c30875e346fb5206193f6324d1b097d408e069519aeb4266f2026a036b437f3ad3937b3cf1d6617759288b0bcaf31d1f57303e5238ab079b9187a440f4687b70c3ba505df30308f9e9f4412d027180766458092d03e8c48ef9a8f4ea5623f7074ef2baaa6e4b829d2cf3931e9345decfb6a07e7cbccb7c769d16b5c18d15d97b0946c4bdc41e6491c698348683e3ab6c84b5aeebf51067b35aa67acd3deccf6b2688a1dbf1bed15c9418e559ed4057248fcf1099bb11f333808f9bbf88aec97a32474ac17c7573ac9dc1b8dde113e2c68239d5627d55abb511c63b4e4002e41d0246b2b56a74dbedb2c4870d68f1a83ab05a3762b8b0361261ede88e256f0d03f733848bbb3646ff443a4da7422813898b1db10fa73ff3a972a37f921660dea2e228cd4a645491bd90c9ad9b8d669fb804c6c1e3a94bd258a8b571624165dfc44bd87a6622d8e5eca56bcaea71dda525f0cf007beb78b1c698583d154c8f495eda7c7095ee0b751bc91b2792ae8d59712770377e04790ce791747425d1bc4b8e60b9104339f50575a2ae5118511c0e6011aa050bc5d1731ab849f9ab0d2616388a90999fa4b1f14b7d47263ece9fd25911d9e31140a9abb23b3db4baa83628699e83d79996ee29fc5b40773835d475c298e6050b82aaf5cdc045b38d44c48e29a24d84ed4b96d9f55a4692a71410e89d3854474879a2bfc20d2344fa47a1dd47295f24698a4f7db465950d096562ae73f82a1f34b285063e121ca27c1e63c83f41f3ae7b2aaf1211a98083d117dd42e2c5ba7b34f6de809fa3e79abb35452f8252cd83f2889fb3330505bf22939d40360a0c5ac8cf0f6602a8df576b55ed3c4ffe60ee54b5cb8969e3cf8b9f7a8ceedc91bc632fccf19fb339b42c974bbddd6782e24f5ffa72fb9d29bde0b5dc3bf56af8896782df8ee99877b6d7ea0e9fbafa8181db35b3dc42d6be2fdc681983698ff10c4ff46d895e29ef000397e823b4f0a005f9fa242a49bbd0aae74532de36b1256ed519dd58c046d09c8d37b4c78120677427ef594734b454657ad410e038bf67ea4b5726e2785c28f4ce5c258b5b289f9ab9eb58df0a905588ec868fe49faa0b41817c04dca81f9278237c218802136f842f0c416540c104def7a619b508331d6024a75ca279a7322a18dd2964d866295be75601ee8710590e9763b469d87fd0991631bfdb9c191c3dc5939306bda24208647fa384dd284ccb3634c8f8ab66dededbf3fdc5de98b701e5bcea1f97128666d866c78a2cada14a1ee0cb528306bba57a840102f0d9766e79c1253a80aa1e59716cb26152c68fd5b1846a537bb2063438adfe2aeb66b6648b80f77a4d14f1b68e33a451de1bf67d97a47144b90552380480a2c5b4c682b460d286c4fea80b6b00b0e12706d9bb8a3513b511e9be71f4634b0d21a3ecc90df9f1767aa495481a71ef8fae908fce2ecc43a1456ab8461bff14214143707b3de2e79dc28b5f355063a5ce68fe034ddaedb3c7eefe6e2a328f8571599145324e0222e66220e62cd174485a7aedd3db5e931acd7c24e21cd168b3ffc64ce9289f75726c37ffffbba00f896bd3a644f45f3ca09aa9492855987461cb3dfaf7fc97c3cb92f11c807e2e985f96972ac1563aa9220de9e0c6fed1bbc3dd27abdf9c6c6b34889244afbb93cba11e12c3890cda75e0fc3aa52bd4131900973fa0d8d96fbc7b8b5e21dce918857c8c9f9db35265431ce1b04f694c68da7a224af94d5c35e6a5c1349570eb8532a902da7fbb99f34d9e9ac980ce765aa1cd52917f8d5dd8a4f3ada09badfe433b13ef6788703abd68481ef5a6351a853e56ecdcb0d7f7d2741dd50e3f4ab88293a0e8b5598d0fdd63ac45fbdfcd75591f163aac212287582fd470843cec4ec8ea8b0f97259698ea9ede799151d56d2c129440871899d09f25749cb3340189d0d655fbb6b659377e0486a5e00c8f2636bdca1201d624ab11b009e3a89133ccbd99d7142c73e266c3e89724d6c2a0743cad396c948c42f1eef48086277f130a7d72019f76bc40237aab0f4b7e6854f6b5d0d4aba3c6f97ec1e099de84a0ef803bc4e2534e9586242efe3a4a6d24fd93096c9e7a844963a1d1404d6ee6b09a20883fc79a991db9b493e97e410e2492709de656273b3bcbb3bf7601e1a7e363fe288540e0b3bffe52c0b39ba58293c810d932bba503ecbfa3e3c98943b7c65f9b0891909fcb5ce292b23f41cb26b66d1d4fbc9b6aa8753f631d48a72cac221257d6a08bf403aea5b09df43e06f39a5139a43082531c873dcf77038d744416f636ce3aa2442eac1844d2ffe10b545d2b77b4b0d906c58b4c4cdd24e5fceea5ee26d143032eef562d3e16eb778117fcf24c0f3e5b42362af5938be381d4ce05ce86b8dd20e861ff8578e8864d2f471484f5cebf37329d49f0295ff3b02cd17e2e7f21bfe89d4588d52f47fd7975eee5a8f41735bc3242a039867879ffb818f03365c4d9fa9e8357db553df3a8688b7642e1f0f7d673e90a2859944c59a66d904110ad7c19d4fa19a27385c00baede75a6973ad20931d90f611208324c13e213f16f97c8693aa34b68d4a49f6be4ef7f11d7764ce4554f9f5abe24309e3bf1e2928c6548b573a1eb4483cc44d2b92771d74cf3ab906a5d3d90a395893de3bce8e8a33c93d4e0c2a654630ef28d121c77d6de5d04082f767e02ab0bbefb7899cab93d5b3ac8117009a971c62ca238fc08d0bd6f337eaa495eae4afc5b36814f94da3883dcb6630449d73e951060be8d796fe838e297f7469cce5352cb6a04effcffe2e324e326f187118698bdaa11a034fc5387655804e7b4fc50133e67318bf190786019af2494e02cffce085652fa99e18ccf3b927c2fbf2fdd4850b463e3b0aa0c439f89544165f371e89adfe927112bffaa7d01f4625d221fc07525833ec401e22d06f5a33227392ba1e7307c329808a00d467e15c95970469f11a212c6cd8a36e30c0232e692409d3cdc85860469e46305f7566eccc7b92d44c1f6b4456be6202b24089abfdf2272f75b0f4268cdb18c03a51a22481cd829c9189eb420a4549f1a855c085d0525eb011858e4a372b242ef8e5f39387e7dc529b0572233cbfd1528a08cf60d9759ce7f20cbe778a60e2b2a367606592d1f54e4195615c94bfd4429faddec68e0077f825b6a23f25d081171401c47d59f9ba1aea34320fa93b798f0085265279a73ab7d3394da5952b9ab1bddbde9cf35aef5579ef22d6030a493ffcb84dc7a93694edf862fa703a982767f702c361ace7e466b76ad3cc5cd8610848875551e9261b12dc99d02d63e826e68f39456a713ab5e8bb7ffdd3bcef8f358a56fc6a5ca4ceabc81fa52871c93769d31e633058d96e96d1acad8378ec83ef1743941c7d814e21c753458cbe4172b63f86c27fb0de6a41098bf919a4adc9aa2ce3f21ab7fef26339fe9347daee1dcc3b7d47a9790299023e487aab10d8e02cb4a8c53ef529d9934eb8967e6d77a33bf6efac2130cc62d1e532bfc1b41d5f2005be5ffe8e1036c664bd73c104b40d57d172ead4b6520deaf2bdb6ff5dba5d006a559abc422070ad4737f4f970580237634b68ebb903debeaabcb3397c67c4754671a99c3f3579145b81b506a4baa761a781597ed110b97e78eb3a9987cd93403ab4fed0c4fee7146e058d50524284d15453d5d5f1e6152968b7730c45c32f690b011b5fc409a1b817617af0981794e9171c93e293f3005db3dd72dbfd74f770395927bd9828a10bba26a88980034db73335cda3b302d8e3c5e75407c67c7c43df80a809ef51ee07db7711ad568bb32e5b374a375020f03ba5461d041baac20f31483837d05a4edbd5c5ffaaa26321b86dd2f6692797f7cad43aa1b18d38301680a0c36174a1a980f41d8871325ffbbb0541df7e8c61550ddf35d81c3d1684992a3473a5005df8c50db0ac9ef4fd0cae1939ed1d75708930bb098b518ab64cb8befe6faac889d19764abdb651cfa8938215b9e02e768120f35259c9cbdaea5b4f3656e662d1e202902b38629f02ab8bf472266ad73320b9f6f2a5eac226cc7f1f5ec0118f939f1b3acf9b4278452cb42b5ba8b0ab4350237233b40cb0074513c8e930cf1f1d7bc22a7b052f86d8617d2f35d54124cd6eacd5889f119540342feed2d09f62ea1013826968b6fdf46ba1c92a80eff6f0f1818a20370d02f62b662915a6adb9850389b150b22ce97b1e22768ab4d7db3268f930fe3ccfb1d34b3129963f300be678cad901ae21fa7cb99f9f1b91a92325fc18f91bd944137e4fc62214de3f8377e16e5150ba2f56ab1205be90102428a5386e8887355f3aa4a7e2864d288c056b336cb87e629c2c4f19cdec063e15d987f4b97b4864941aba20cfb64a6f03e379c713efde57cdbf6cc7caad6ce11966087621e682e22c6d5b8df369287d55b3900522368e7db70889b0b18b8cc4f7a87b560259e69cf969c509890f2be1879030bd1c2c925cbdf2f24cd1bc0c35c552da3b24bbe02bac1329881abc1cd7135ce200b4c01667b6017281d78496274bd9b4042c7069a34b27f4971d6779d9f610c65b4211ce832f7d4947def6715daf1b899cd8911b59303224eababd61788964b60f962705a638d26c83e73b26a90fd35395b95c4f9eb6def690a6f22e2a865a03e04e65164a58dbf8ecbf4ed962b1b045c73451b020fc7823dc4268d1ed861270cc7e8d0ce22058ecaccd96b13a127116a570af520008a1c66855f547ecbf6da1d6ac4ba327ff305961d41cc02b82340dcc6d2f198dfeb4ed998e1cc2914177adbe73f9d995fb96aeb0e03f2e86fac84ded4355642c83c45cf9af28557a1b274127f17dec6f42627e9fba8f44e621918de94aa76d6252312d636e2f585c765ace029efe0f8e3668189a74d8b6f10a8dbab4d9a62d81eddb111af568371cc3b3f3bb4b44aacbb4ddb0a3e9b6e77545726eb98cf5778681cf9c242edfec9eed37e66c58c6b885c6679af578e3069345727d8ac9b71bca6419990fe4e8af8896b3252a86dd3e474ed1bdf0668f73b54c85db13886edfa36cd14f2f57bce937b368d09375a24577d3760167a6cc1fb19bcfd18820213ce0e0bd89a6f2183d51839bb9dd1b7deae3e01c8e5d84ac6a7c7bceeb67d8bbbc67a425eb72cae6291e7f2d563d2b4e0d318887a71e67f93329f073ef82172c2dd24b1ab7100608633a3fa0573ad2d32974cd133c95d841a9fad651bf08b75e0c0699fad9a2767400365b77f1f1b39a420f07e8089710dfbaa14ea0f5ef08a112f841f80ac87f4b4be079a661e862b2630ef25e3868440f3c27019ade6ca46b5f8f36c131eff630d967ba84014916619a1ed5cadae250f9ede4202de4933799216c12fca17f77aed5186427731bd5589f75f49814d429c7db4dd4033c07ac977de8ce5c837f16e2869dacbda14a00aa2a96ab89c43792f3e255471ab030d98d7f2ad5aec5e765b9e35d6f1c1c109f52d2fd2864c61c89da2724f414519458125c1bca07ab929ea6a5cafbc4dfcc87bb1c659651a5a2909aa0a3970243fb58a461bdd82c8543978d9bcff132008968433c1508baa8affc05d0134f061966e0206f4d2c06eaaa595f8af87d445ac37128904338afe48bb8fc7716c1d5476b12d43d9206852a251a68b53a81baa28b56115352f25c1e7a7fcab9f9f72ddacece40f669a8b4c0ebdec05782ace22cc076125d07e581049f7b153b8dab3281105b283cdad25dbd6b27dddf02c92fe0577166e218ea6880638cc75a8c5882a139fc164bbc9ad212a0d5ba8d71246b9a9f62d678c858cdb525cb206ae7cf5603fd687c5aad19344ef181ca07f9ca6ba13ef91a132311823228d1861b477c0bf703e8d811248a210f45aa1a00944c56c7e407bd15609121881d2ff559ee19be6569e23c4d333849767fff36e8521ec7b8a275d89fa19d5bbedab98ad7d5601890a433dd101a10f3018321340c39afb85762932251c30f0f20baadb7c0d545f19d58aea3609921f60c12cff1cbc98b8044e4d34412a8e949e384de32996ea3cc2832fafa79e96fedfbf70b2c08130867f11380bb47f794c9f305eec75a5486168757c929f9c4e964ffe3c6b12cfe99dd53f5dcb35a84ee4110b2c2ef69b76943a8f18ce3fad2dd4e454a92bffe1d419f1887433105a49ef91040bb6a0397ca16d23d05347c58e9716135bd393cd990b8014b05e7703ac091d900a5db7f78869a5f481a1c52ef688d18ad8829134fe89cf46c4740e9199667995cae0867799b3ddce1644f0297439c59082a2fce0d091787f2e6cc3f275d732a31d4d9df213e65c2aca3557a9b6a56457a378c8b13b35203ccc583d263037519f566d053fdc159a52058b2e40863f2a9111d6092c0c465534b52a0a5b6cd9c4ac855230b0f26b7c78846bee87c00881f8b54cb1726dc08beec0bf82ea0f4a4bf398be3024b40299ca3da5706f6132e4f3a760e951f03c809ca3c0f87178770f122dbb57eb13ea0b53552bfdd76ff22c61720403cb9c7c45687f0daeb0422737f4a1bda05a498efddee2c47642d1e28a41d56e8a9e7a027c81f2caa2166bf217f5c8c20d4319b353b350a92aaa9c1b425815608d6906652175d1e89fa88ad934f0d4856bbda8645a174286a01f6457eedd415481a95af58cdada3bb01652a585d4e0d2e6be4b18a30006399aa641e212be93651f9db35b2bc7f4999fa8343cb45c21df2e6c43351d950f3cb16c034550ef895b498581630765fab9a8a458024721b1b74df6f4a55f051c1fc01ce19e394618623fee461a5dbf48789573308421452593b639b0d1f0200cc13e7461c31cb68bb1451e2de11cf5648b1e1440aa96990add281d1830ceaac377b64fa720a184af1b383fe1f77bb0e9fc4689111d508031524a6f50cde660bd36e92746c2af5b4e74f5864d4ad061056522d2e1055b23b96f0befdce189caa1d2afc1d5755cfa9d95f18a628592665af6744cc931d75e41fabb4a8643dbb15a9cd069bf49ebb7ae64b2c6354708c9962d93729ac9f8dd3696dd7281fcf4f832dd3021bd219631318d33a91b171744e26b30e2ca1df459483f4e8f299e39691a091a771ea4b35e66ef06cf5222bf790fa0f8a533926c094065d789bc1622c78a60e3b7b4bbeeb9ad577e671cbbe0474cac8c1e3b37cf511ad616942895754249c24099453cc805b0d5bec06696e082e1cc4f91b4341235899d5550bb9ea6d19a16ea0ade3a465a1d86162362270cc89f15a3144f8e1892e95bc2153800ace6f54ae8889c3c6c0ba231b3edb1e65d1839e89cc16f25a65d5f63ff28a789e9e623755ebef1e10c6d4658d4501b388b60293f687549fa4bd864a5f6ca42276de88e144b51e5569a95848e81ebda69e26b50390898a4c169562a8331687d9cc318b209ed58c16011930674411074d3866640cbabe8b2ed979da4c0f18e776ff054620acb1cf70b890ab483834b92b730be04a7a5a51697b4c0711722b35dfc6ca5d0a75021516da1281f9584fe0561dd0ea61b4ce2f124f6e8d7eb72fc51222289ac095fc0d8831b66a6456728d66fac2e39f29acc3b27aa214744a2eba89a0c49346a24577b740f34a0d296e4c5b177ff38eb742ef8ed76f47bb0877e1069a37e14ca73ae65f259b3609462914605251e19020f4072fbd81ff3d1329bc3c81d7345a02c9cf8e9689e0089b8c3a74b0e94c98f92c0c3c25ba8c5d423ae482dcbb0e6363c75843a4b8e6090130f2fb1abb075da3ce327c91b6f68d021726c83ce78561129612b57fd2aa4d4c278e66084a55a23e06d070b5d65283eb133dedbf1c71e186bfe5b08b944405a501a4d5958a7eeb27d11906f0db2f25575d725a0ec06b39bbf85d8ae8538b6cf563072134ea9200288409562970ac36ba1d3454f5f980d731f8d087d9b6e67e6884519b436217cbdc5972cb8af133c3846d98647d14fdfc4e6ed456d9256cd7143e07887b166dd538b20ad4806d751eeffd7da7e277f31e0e0223474aad525f2efadc9ab8909104872edcc575406543e111f96a9e8cd3202e59ca031373e787639a0d03c511d2a123e77c24e1c7ae1440205a472a524582200874a17ec3d5428f3a8ee4f04ef23072bc6e635a89b8d2715b69f482d9e53d608f5ec1b86a05600ea740d240ceb085850ed2992383d83f4432491c82c92aa465418d19cbb4ad7a13e67c299a072129f470b4118925ed5e1cd915862b9c22a25147bab781243608fc804507e42321b70f906db8b4d2df42b7e84ad796905478360a32d5685abf177fb3b75fedd93ae97f8f3bd3fd7907ece0821d68ba187427bb61e8ab54e2f9992e7b91f0852566341c27bf01b73052ef5b7d71fa14ca703907b6f353bd073fc9aaa73f652a6aa570187479a96e8c9b1ceeb99b08ea55a90697bb5ab1cd2270c2e9645a4846cc97f26cd9ca6544d9ef4349c4b8890b36b091ceb3f4c7f99428b0390f5e2d376501a1ebf234deeb5c1d60ccb79d531e45e76c5be11a91bc1ee85fd2cca54f1572a9353343422b34774213bf22f916502943e4042e119d2c2ef0dcb92f34ceb239c1ab79231964744b7798e7426f754af0d319a3f4e7e184466ec101275754065343c97e0b0a4cf76b0cf4513f7ffb9b2565468e0c94211025c0109ea7411825fe9e002c88d72c0231173fe6723e279df1de976214418d3c2fe14f9db51fa29830d670906ab40c30d642b226ee9f60fa069961b14059a050200def4c0dcdb3577ac48bfb00092f9264b0ba1ca71946a901beb7a7a7bcc0545453bc4940896c7ec34c020287096fa42f5d7e39431d908bfa8955f55e12d3a72b64fecb68ebd6197c7b63a751f228bdbb72a2648bfc4722545354d9c0460d77210340167941474d82b5932264217055b6b73b881ff7b1c11446644090f264a6b4a96c243db161d283cce45b9e600cc7ba8c05db4353d9c7b64396bf2e071ecc84b0786538f985dfa85fa78623027559ba6a3372c9c1868c28d6aa17fe2c1d0ffb39acfd566cbd202421c9b154cc55a2ac963fb53897f9d333903b7249476e838a500bf6d6aec65ca4c15fa3f9be688d609daac251a62c7a1316425e2c6bbb483cc83d246e1613361f4e5881d8d1110ae054a4a4a5792c40f82d5cb20a9e04e130a8d88ab1970a903f1ee0ecbaea469aa66e81e584aba9b67be7a40db1dbc42ccdd2314d93f7f2583ff3fed118a610edd23d3640c983e771a0fba5366c50ad051a26cf2132e4e4960808cdc5efe1a21fef16c266fb00bbf24e272c7cbef8c42349f3972dc85833e2a746111669bd8c8e5b5170b358f7daf8710bf13a81b3d2bf1ed5d0e3a053e9760497292577a19535ba174c861ee88b7392b59cfb6baf29198c0122b1f02977d655642f689009b74934b0b9830d292edbfd0f99a341f3bb52c0927933911beed2e64b5a5796f721ef99008883119e30bb52d608560417bd88fefc6cb586f2440a7ba6880f494f7c2ee1a841a08f4849ce66d27c114861ba430e4d47e8cc230f6fb34037a0b8d2e015d439f9e64de2901f9345d2f7a6b810590779b449eba70fa8fa888949a552289fe67c1bfdc6ca2fd0fba9b07b0d25a322f97bcaec729237c01f79d5657b8ec8568d34b8cce917d1217bf87af10896f1861451953c7949db2c4880a84c84afab0f61968ea42bfaffae30d09f4e1c387965a21370cd74d3f3dba26ee9e76037dfbf12dcfe92715eff808603e2479d963a0cf2c0ae1d3b8a4de1d289c23800a46748cad90b87db5d25ae5e45c9f41922c6e4dd025d8e59790e46575b31501891f1ba9d52a8bb0c5ff62e910040344104860f2ccc7023505c91c2f7b6e0c02f649b6e49abe99f768125d9c008528dd06256d7463c2139340c13cc33331b1f974bed10b06bd4231ca335b14108c96b02557d4952b7967699892e30ee4f06de485dda2f84e9e31e6d5d0bb124cb69bf3c5888f1400e45984d8c19966f611169f1d1916ab02e20c11e497eeb3b3e82abbc6f7145a345b1bf53c07a9da070a9e6e18286497557866e5956ba6263a6b15d9e6ac0669efcee4c52be8514e889e4dddf32c71e0f469f3ac0e8998ae0f4aa9c9014ae0cb2226e9c4b678897af5a0946674917541c042bded71004dcf034e965d3e03c2e7cd1dbb2cf988173d1ad814b806d31f5c1a40ff00c28786796ed2e2d0356331a884bba3f01b83347684b7eb483113426c0976c84d179839f47163b9a4b003d10d64e44f4eb15c34dde2e69f9553fa39d1d655e25d33082cb9983fa91c6dee441bc8182de776925cb6257fbc560c79f1fe8f6ea5f75e0e54e6c19a0173c61d4d564ae11ec229fc420cf1270bd5823be97f272c31ee9f65506b1a03eb655ba4e83254c648c3d53e00db9f963732c09104d5886e89bd3fc5f01f797730aac405adeffbd14a1fc14ba6040480e30f433409582d3b047476a47fe76c1fc20b92efd06a150187c711c9c6bf29b0687f78ad662b3156b74cfe648ca279de65bd9870bf5f35771a354499b3cb51385639e3d4c44d4a69231703cffa9bb1e19d889cc0718ecd52be503a6d5d5c948d23f6280909d56eef9a59178d2aa36abd093660c7964e5463911e8bb363975f54421d911b7a7276ca0f6238ae955712eb65ff003f6556f129d6146967ac25a9768761bd31526ed8f2cf435219458fc54cb2e3376ab14e33b719e70f7faa7518de98bd294da529be1775fc7ee8e57d0834353a30fd052ae36f91c3beaa7a03a4c42a883841ddea93c85de5bd14b72af5a089468e1da8aecde70fc97df0835462d1d9251c60122995437d613459722601d6cfd2609edacf6086f98ff7513ba940f213ac2e3f3c09041b9ccfc182bab075845b68e25c7c4f9ebab2eaadaa3988e1793b5af9379847e5a7f6526efaea48ecb609459a770ac3418c60ebb463ef06c510110efefe92a291d927b05499899f70b01a9efdb2eaa2b0d08cbde47af0b3c863a2ce7a4b5c7fedbd1017ad30ca2c42bd09770d5228a145d298198711f68801f7d8cd7ec8afcac83c6d223b8513cea754ec1df19497d2227a1d9184c4989a114a2394663ab4c78b24bd97cbde9cf912b3ccb168e07eb0dfcbc83805f8b3fccf627b459cd4878908167ef9b288b1cba52cf60300bc4a234ab776b978ef5d7163851f5ee7baf9f697ee5e03189ea3107df87c2b8c821c4b3264dbdf3b29a0d836f165b6e5767aa628109a30dc04409cb1e798cd4c7aec8b82059e8d903b5d062536525fa6156d23bcead481f190a4931a0b6672afa80db1936539111e773cc0b7f0ef5253a009aab9e524748c3ec2b4f98fd012b7690312ef493a4b8529dc3e2566169333c9b535aa5d202ee07e20dfcd7a296c573cbb33e17ffb8e409786f58ee8aa24b113dda5e11eb1b3319de065204629730c6db0bb12b5372d9287ce8452062f7912a1e78584d6e890c97ec520d11b549cd711a0949a0a56b84e8de3e03b90dc5b56a35e0dad3d22408b27420bbf9085a0627b02cdf6f0092c9658f25f814b0d1e1f3f9355245c7a7f7847d23c449346e62faf024cc5a3ab0313f982abfab15a146124e6a67d6dbbb688506271cc19033a5dc6785b30d8554d0775e59ea49176ccd1929b22d2040a364b4ffc3b47b2078c780f64f54edcaa792213bba4b0078440f061351e367dfe3f3ce007ca93d7fc850259fc570c26d4a89519e16eb71924b6b9166da295ddf6dedde49652ca24533c075f078f075c24729f8b0841c4d3f9d16b5ebff2d16db040b35086a2bfd02b165241cfc718bd5d523c176bb55a2d1d26584805b20f39c9479d763a630b1f3d7a0b1186f8f1c9474f4518a4973e167d08f343d73f54999ea37031f906439c8f493e547dfc2325e062c5c92f627461c18de036c58dde793bcc36b1063d5723cfde83ab9b9543f31e5c470399985ea78cd46b774f01081af4c46771236a99d28e43e5e8bc07d7a11d58bb8adae6c8bb8b1ef5eacde0c24fe71d1bbfebdaa3e1e6a94f6f5aeaa518837a875a6117a87328167681baa52dbcee74b0a3411d55c10eb4af43dfd07a5ec1715c6cae6568bd9d3929ad55d36cdb518f82ecbc9e7df368cca73f1bc7759de791fab20d4754d8edccb7df458931364a8ad7dddd6c43f5dd2a5d44954237f5ca4a5ce98f60e8009552c9b4c26262616129a9a4b0b0a05c1616968f85e4b1b0b0c02045bfe18ee0b8187bb24b297b66dcd1e8ecd9b7282e9dd5cecef5ec5aa93474adedec0f0cccec247d9ebd8be276e792146640d67d3ca7eb461b75fa16c5fdbc1d9721dc087e1e0d98bf285f8a8c91d218636c1bf2153995141594abf2913c15151595ca2da4e831c0acc038ee7399621bb20d7199c2658aedd571caa0efa1b631093444b93381ec71e3d09266c521298786603616ca2124bdd2bedd4a964592e4e94bd0c678b8118cd14ae7647b04ce3aa79eb3b6566badb5761bb2bdaeb5d65a6baf70fd7d4d5ae7969b10ab55bf0f044be349337db4c5ed5a756dc7e346d0a30f379b95c3824c50ea5ad3063b9034831b9d70d1de126e0e1ee4175fe8905801adc343fc42870432d179f476463e472846795ce7d26b8742e1d07f830eadef9c9b9d871a7987c2216ece71ddfacd47a830150375eb3ab43e07fb231f81d42f4b8d1ff0129cbd44a314d4c04b9f5ad4a128bef286ecf823c6178e7f19d57f43460f4816f5ca341e78c93a141575d84b80951d81508960cee608438441c60825095efa8c09e8cf806c1e272c257b9204df0df1db67d886d993898179716939b1ac984a2a2928f723791db78dac56e994cd366c9384f4191d639420b70ef146772f91524e29254b2925bb94523ee19861e88e31c68d1869d4d00322ade29f3ce79c737673ce39e33ee61cda3766ec40113a2762115440278c939469adb5564989b61df0b43c6d50659d13cca1299db3d64a6faf426a3d1a34afae7933506badc68308d7129756a7dd12397ab8d1b14c00ea3946de83eb6c5b650250ab43bf05696d8f86486901e80866a55ad5344a2b9550a8b55a5a6bad5cd8eef2122aa5d1867cd1dab4883ac46e1d67e3e608e7547fe37e63027205901bdb728b5a6d9cbe32adb5d227ccd36ce245d4d3a384a88709252ea2180d5cae9e2994c043cf6f523481ce968c2e3d82b30695c87660285f52c74fd9f3f306eda3e421e29a9e5b55a5bd37496522f9fa396770e7a41e969868e52b587b5a251dc6c76d241fca20db5df96a214f08215f752e999146a74d298db463bcd4da189d523a279db376482082f7ea9912beabf994de0cd56775eab63abf56a96b5aad14ccc009ecbd9152526b6fdd541f9efeb400d6a92169a4b7fcdce8d3e24c22a804bed27ba21af7f03aa6be39679d86083c1d596da46916ec00fd0a668082d209b5d6da94d25825d35aaba4295075c1234a212f2484ec8043e081d6711b65cea1be669b507195ea26bc21c770ba941a92a502a69c4e28f7f2c88eec68e496d62fbef8a2fe04a9125a9b3ea92e212f24847c32273cf15cf84983fa695bf8c4399acbf0719ae181e1cc87a7e78e39de4656036df0e0ce39277351e794f13d4c9854ce392538a30f1428e06959c004e8081c75c8860f581baa9ff65e3fb14e9bea074eaba49fec4906136ef41087d63aa7ea6713d6d246425b333277553f38148a55d4efbd2c3c9df52be86c43514f552d4792e0846afac0559fa85a678ce0bd41d67ae7c8e79cd3d96b756d6a5d022657a7cb59bd19260872ed716fe914bb6747844a14c8d127385ba09e8008e6d08f9a4f5bde20bf488216f35e785af3b4e4a99e6ce40956799db5ee6ce4f3a14816938dcc28b884bc9010126484a0a048238631c61841ae241f8d001a23141bd1cf4f6ac44fcae327ddf1d3e90f38f415836d743acdccb804bdf4de549a046d3b78e974be70a0031cbc7e124da1d69f0af315ac43df16b556cf07cf09c29c38c6384b4f676863536f2e7294d9262cb5bc3ce29cc82520ccdff7e1a55f4115db48ef86507d3715b2d12dc1aada7d9d34d57ec0dae8f80a205700e9884a1cf588a231faa5f39e4ea37b9294baf548dda5051b4cd768774ba7d84d57f0b2dd075e1fdd8587a7809e606b317b2f06244bf309d6222df6afdbdd219e7da8d52205481d0db4dc15377aa80dbd64a20f35db5d2df6808411b4d774a2230241acbe78d49790fa42a20a095a2188a0a0972bf6f3fa8ee879854dbbdf88abb5d6ef66d3421910f0fde6374ef23a1bb60f8c669d617ea5ad9a9408b59c53a51234f4d75ae708a6fcfac5943fc126f2de52547d8cb17acf52d7f9a7cb39d4352ffdf47b02724ba77a6ad58d116ee7e18ded5a70e35370b2cd9caae9013621caeba506c6ccd846965e2f5f1a18f43eaec84da23ea33906f972b61069956c09025b52b6b8d1c316a097324152e038a097ed73c2ccc929184a238d30d2615ac5301fbd63937822c175b7db638c31461baa8fb1d452721338fe6247d953d2f91b609b1a285b2d46a3bf06b661afb951d96dc46d9da7b21dc9fb48f743b92976964a5d89db46a592d54aa552641752734577ce12d5346b471b67a4eb3c8f44fabed229254545a5542a994c2b2b2cde4e113b319d4ea7d249e5743a9d524edde9c46da791d54ea753a5a7294f1d5b90b2344bf4543f2db6728c46dbc671a58e44fabed2bd2514942f25e553515121a9789d8a8a8a0a9db25522b7cc4969ad5a6c3dbbf5c299d9c2303bc7b267c791f102d7759e47fa5e289dee45414949515129ada0dc8fe4ad742bdc365a59b1dacacaca4a64174a6bd53495b5a391ddb69b176aeca8a66ecdb38fa436e278c4d2146ed8d118a3f37161c16d90e3220782b6a023b6a018bddb5d5cb7bdce594219ed8c2ec7759e47227ddfbd37cfce31a3a0dc9d128dfe5ba2d1cf6c6377483edc23ddddd35e8e79443794f1ecdc4ef77594936d3c52c7ee9e51f6fc84705152be9d52d476b8274f6ed8755c77b72a7ad3e4edb828b90d869d56e95c911d2d29bce17df5a3537e1d2f690694ffbec190021f4103b438d9b58dde301d43cb2980e76494f1a52451b0b379761724bdaae99a6501f0ec2e40b861677b45f34c2357d941705cf7926797438d448664d2aa55cd8ed88e366ee33aaf632f3ae1efa6fb41223df1ecdfeb19751915f6cff38fe776b58abdc849e7e896932227443023486246604682e20f571c820e117bdac9b34b225ec24e9748e0857e81bef0b15bca39696c29e7a4b44e5aaba6593bd2ec68b46d1cd77531765d17bd482275f1f348df772f0a4aca083a4685934b6ce295958e595052544a343a7afc28002e388e33c1e4c7452f8075aca491722593b8321ff48ceae2466f49873a1c3b2ed7dce9e9e97074383a1c376ade0c7682337c5726709c0a65f4bca54043710777e41c4b8e3d495b2cc1b42c71cf1927e73d63dc8ed8503d927884cee8708bbd75246f7b3bd2b5c63abd7f82a1037e83b5cababc28d66914eb68dfcc8e1af5121dc5ab5fd44b0493a07e3dc5b90ac32569ac57148c1f49639db35da358e7a25887db6077fa36c5a5d7addbb4a40d8f6451b76ed32359d5e75bafd18273ba2d3867e41405c3d505b09ef2f3d62b8cd7025a8782307c78b8339fb7600b9cddb066c65e036a03a85fb0662669accf6814540d91a4b1be028e1bbec45470dc7e7d6872516fc7649ae28625a21251a988733ab75e7ac23923b76e6a49d616b4c1de96886ecffb38e176731e75e21624696ccdca0e6e4d9164bdc4248d75f9d66b8824abfd64d3f312b3e1793bc1f0f49b1497dbd98adcf09bc5d2aca44589890f4a3cde7ac927458bb79e32e39ccf4d2e94fcb260d5b7e4addb6f8797c587920e7e3dde10dbd8701bea611beb5c0803146f1d0620ce99cee2418979aeaf9c1246ccdb8adcd0e4e2cd698b6da47417237805c3a755765bc16801abac872e3fdefa379b60ebf30d45f20fb5390c9f8602460b36226fddfa480b0b058c18900b3cde7af8cd3e86166f3d045b31666cd39f872fb1989f6e81420a246b88bcb75ec3a457249f2fb15e91bc66564344c40d61c4c29a195dbd3827e6633ee50937ac994d3087c9d52ad38f5659d34fabac9b5cadb29b911b9a5ca69f5ef15b37b9242bea589f28b0d52aeb1105f2b40a6c7d106d61bc0001464c0b07d0b2456ef4d0e4a2e1e2621b9bc55b0f5baa78eb2d31cef9dcca216f3d3c19f1d64f2657373d1a323fc1d95be73a00bcf55100de86dbd05bdfacbd770b6a8971ce74eb2e2ece916e878426d7db09bae0c336d653486e583393ad9f1ebec468c4fc070ebd48288f66858aeb42110a3e7c89bd751ce28735b3b75e33e39c7e1b84b737a8d8c67a0c35cf817d3add5bc3a455d66774a90ab78608ac99b5ca5a4fd9e2b6873533193db6657cc0712a349174c4d8b4a3405c1a67bd624289a81773858a1bd2998e0f19d6e3a7d71f9845af602d98ce3887fb4997f8fc0ceb8ebac3939036f9e954c6ac93a4993eb90fe9155fc37cb125296e056b8032b46aa670a10267ad62d2aa099aa024e486744654414ad42ad5a474262951a9865e85a754932b5932cc1696b8219dfda4b35e3513a25e15599f4ebd68559e5ebd58e7aaa357b567885ef57079fd5183f4eaa5c1db2ac7a182b464eaa901c7a9b0babe9d9341023c188a2b4e6032c4bea2c749d61e15cf776d7d4b962da40d4298271c72ce10a239e8950db1b23c116425e03737cf2ac9ba91803d2da0752a68254debb0a4c00b5a24430d4963617614ab397a555bbd92b7552702300cd2431758d527eac436ed36441824d8c23124ab82f6e5db4316c25310c66f5970dc90699164516f1adea8aee7154c537811f42cf4c4c9932cbe8891224f70ae9737289d5daf6e744117d6751124e2d4d15edd98ce1e85b5aa3d0a1e2a7b4963924616b9eeb390153d6f64f62c6405ec43ce47c88a57a431d837e7d28e00400f3ce80f3b3efa8500c7d127f7f1084fd0c3c8110484cee9d98b13e038cb5d2057ba0c674729a5dbf6e1ac0c5f113c283e960082e4e3fa90810ecfc7e743073bba1e43241f5604c111c1e3f91832c1563a19d163844f777d2c0188118f18e7030a27b02f906d384ea0b95c231404515b00645d50025ac4907684cf3cc2a7d64008198311313ec2d3f7543a15012469985e8924bee0c70b824896ac2b69a20bb994bca63983422ec3627041ec9e9808ba25fa2c14640a1923a49042f628cf5244c185f6a1b7c42aad4882bc669470679e8582ec8039c2f52631565c991f50715b6ab0828ba2c4cc12aef62cb4c41132495c9667a1254c204385fb3d0b01f1e2c31b93e316a974801045212046bc9ccf42405c70038e53e14ceba7b3ab554c74c34ff6c97a15930c4916e7d3bfd9c7a4579b4fff883e272b54dc9216bcda80605569c639bc9de0e79c5ef2e19cf60f0b667d3e4c23e31cd34f1f2d60d6077bc1cb92b40a0837fc60b2efc817ebd517f337c42f3ed717e47b7d4f7c401f9156cdefa755d37dbe1ead9a0e6bd577a45552bacb11577eb10f466a72c30ff6c1c20ff633ce4fe65f92253ffd1bea95f7d32fcf10cd8aafd7b7a408243f3f9f9f1e7e4e8a7e461e433ebcad9f57c7cff09bfd8ca81a8e04f1e955f80df1d3d5abf083c17a3544afa67f3d7a15bb98c287df8f9f92f3c18e9fde33ce4ff6ddce6d41bf057d8340373e7597f053b215b92cc423e863baf533ad9112292db41625af397d1aa664436056aa25f6f0c518638cdf73dd459a840e81547a48655d4a3214298cca28ac57473eb64bedd94b828b30b5640a384e852d44df416cd39c9cb447ab9acafc7bcfd1a691e9a41a89fab4aafd7b6e76294fb8e1f683a46549cbac55aa6e99f58a252beab4c45a64ad6a4fd9e2520f5b6241e0d01f6e3f1186488bf8f619236a7bad5071b71fce51f9f62dc8f6922c4a8402b5aa5b86be5b865a969c5c7e4e1ddb4e7d24eba546f50a8e20eae850bfa15382eb5d4488207e82e106f4adfd16647b81db4fd1951f6e3fdf2d44d5081ca7c2175aaf794b8f46cc5b97d1471c8a9952768e4dad0740eb8b38b1e4190acf459ad0f15c05badbe6ed5ca06b391f6f27d5a3559afbb8ba144fab523a5aa53933335fa03b9f5332cee1d73c8505b35a644ca3852cf0a858d8e1b527af79986af21aca872919e7612a8bd7bc458955c26dc2490adef2c56ba08b0faff90acf45f11447f11487fd308de6f6ded80293ac948fa4d11c46c3c339d62f8f290a9fd4cf6b9e824a0dc9e8e3d3ab1e9a0f3da534153ddc8c841bec690cc95394a7295a2a954aa594940f0bfa62560948d244a73b7855823905923448b24c1e9dc224abe4d1b76dfb641f9df6700ec9a3531f66a53865cb39d7a38f4cade74c282b69aa97501ca9bb4057fb0b74ebaba0a24e2a08a53e37fa16947a9950a9a1526a56232593349a5f24dc30f523fb09d2da129ce4e386c5471ad9c7ad099a1f360f69783e8630b0f80843f6b17b61f1d1e5c56a91499a1f2a78888fa1cb8e8f3e13c439decfc03e7a3873e4634a06fba813ba007d7409fa18baf47c74f171717d34fdf0d1b720ced93ebaa9c5392a7f3d34f17c4c2949c998468b9e1a02a78ef05a8a2702a0f52d32c94ac952b05ecdf0a4a8e0d58c095232c92a01cdf0f4aa04eb1913f0aa4405ab341530d524c5d3abe89aa792f4aa73cd5343bdfa5cf31494cc4cd2682520c992299234b05ec964c1252a4a404f7d68b9a81619db68245066886d344f31e1cef0704ee9359fe9f11919e770aff94c925e5dd77c0520ce6179cd69863887e45a9892cdf0d0147d6a091da99f56691ed264f15a48337b8da666785c3ffd434ac753d7e8cf530f677a5eeb79ea28289fa74f02290fdb50d0067de125dfb54a2b4971fd8be860c287a9d96b2592e760bf73eaed5cf0b64af3155a37bcafb5bc5e73970f6c196a554b12b6d1bc43e190c3fd16256ca3790b113886d6226b4911a5643125db7e7af52dc1abcf095669be35c1edf061f77acd4371fe49d7fc9352460974376f44b7b8811c4a46948b73629cc3b9e69b11bcea5e9b8b733ad79c79d539c1aa50ee605d9c33721ca2d6bd7a45776055bbe69d1339be5deb1dbe3d9ce1094b556c4ef303cf6b30b078cdc3199ed77c848221631b1ce287a952152919db68de1c48c3c3365a4baabff891e3105b649c135de3c0f942c916dbb48f5a5ed32c28c3c3369abf0ce1862db2d752b2d7fc85d6d62822b6a08fe8abb34ce73de9b9579d6ba83be5ec38796a24fbbdea3b10888b6323eaca20592dbd7a7509f44a7ef5152aaeecabcbc039295fbd861a9255d334d5ab0a060637c4f98a23a36ab2e635bf912c49c43931415fb5d7aa6b2835f440072843ab6ac8b08f482ae138a57a8e2047e146970ae04c9799d239e3cb7074fc74fa8459340a2c98759271ce0a129e287eaab438650163969d93bd516a38e2a455fc118cb238a3715ad5ce3f033c6931c61863acf235d4aa99a45538ad92812aba11c7430ace68d5741c1c9c19bd622e164193481a167a5cea214e12d6ad645910072795831be2489605dba341653f5d80d0eaa2c04de754281374430c31eff312f6f286b663936e904f9f03329f83cc53d74469b775794079cdcdd5a54655bfadcb83d6f3b27ac7cc3aa8360d1920b691a721b661818920b400b1bb1824c5d846ca4897818257dfcfb7840c918f99718ee630409c635da24cff7e7ac7b7c410530772871d519e182d5e7af80d790903450b06880331a253144b9a9061fde4d5ab908b1e28567ff413416ad8f001c9baa886913c3c3c91e726f2449ec85374c3c8e392f527bc41b2418d588c818c2d9ed141f2c4e307d7a1f2e4c38cc562356ac462b11ab158cc0737e4d88cc9291661e81883c662b11a3562b1588d582c666fd0a1296ec841b369ba1a6344a72932d8662f202637a4402fa0d72b08689b6d33a057a90434d87ce8c18e930d2eb2b8d20511ce01ead51c7a421624f603f631f6c9042d87a898c69e02d0273b4fb2d30d1123479024b93142093ae4a35316247ad55e4575f52a642b5e010b8830f48aba91a7958b909138e915e7486740a444548dc50acbd2033b3a01915a9d615f036fb8c837c872bb31e252a21e1fbd7fd0a2204f10f9e861edf99e3c823e3af5619ba0d92f3f6432d9d08c488b1b52a219d14c362b2292c9886643488845b8d2ef9054329340b5a847b8d3258db5b7a9df3cabae0d7168ebaccc6e486334265933f68d63874c59a880d0789c047153ac1ba669e728eb0b0d332fdd4309beccf0b1e7a778558fb0a3085cbc74fefe01bfcc2aeea98567a1193cd1840b7ca86da215538421452750c77d91a99a97ce49273d17d981e3836995744fe978165e7a8d07342b0a0db55aad1d74248f1c2f7f5e7a2843debcfce955081344c2cc54498aabf28838d157cd41af7434c92e4dd9a372e99c3445cafbbad12765a62e2951ead5505d7cffd284e4712188d1d74b5761024b9f6eb439d0f3f46ab73927b779a3fbb29f76abeaf4dd93a634db4c3b4d3029a59456d2c852c7cce23c64ab7d0618457b24cda43c3db9958e3d66d3931b5d7ae7449552cacde77d9ee35de96090b8d12533af984a2a2928f723b18c1a003e2393134e526a524af95d71a3cb92901ba994527630b831bc304035e95b74a377114411dde8a82e2e0be1a49494522925a554a394d670a48b32da3a12cb664b1f8a4a0b85d1177dd1179df4896be43bb68862ab361dadb058961378d2c13dd55a9b5521d05a5c28a537b3e5ed48fa7a75a3a1d6a173b979b9d6c7f5938f1b29376f47725cb09c5c64a44bb725b93174f60d6ee462e28cad38135b92459f787df3bc98f03ae9805f292dd86252ca39a79472ce29a59c73064929e79c1be7b16cde4a67fa6e4a8b8c542a2527ebe2dcc09a5035afb5a69c228dd3f376e4643959e9edc896c82a1168159c1ae7d2d95e491a69cb07947b899e0f46453308c68e50968bb8a75bc48d4e25b533664f2d32d245e6a5db925c39397a84e5e0c69899196994e9e4acd3674e1fc9a247604d614da02168fa7cde8e8405c17882a322b6691aace49847c1718c524a1965e4a6d46777addddd0d863d513664ecd803c7a9906b8d935226b975c5235d97ad061960b7e53ba5a8989849cd4d3d121806f44856162de2991d5240ebd0a2260798b101d38bcb8bd522c9db991c61ba98ce3efb9c382c8891e96c4ba774a6c3113d0450af8ee2d12082975e5112bce0090797083764222b599a759e6f51de9d6ebc24549d6b4ecfdb99a425bcc0a064e9de18944cb725b933dded0d891b85ae94d239ef28cfb72723453d41757382dde456885e8c30c9a25ed0a256b5a398c808bbdecee4f92efa8e41e625b8fdb00dea49d126b9d64b1119b952d6e817b7ce186e3ca52e2905e23815d216eb4c20ce89138ae78e5470daea188c982486bc627e0cf98929724fcf42437a60a27057a6e03b2461c60897f42cf492f1f02a12b3e40564c72d3d0bbd56e03cbe701cd7b4854ceb7a4490795d2f0624048a280851a20842583073c5557916129263a6e75e27313aae6512d383eb292122063df02aee7c168ac10e66b0b8a367a11804c500488809180eda99f907ed21a9ebc737edf1edaa93378fa4e919a20e788a614202757dbbcb106e487bc0d01ec9da7c58e7e4fa7609f2ccfce6c2e2460f7568e9d18879eab49f83f8fe08caf8f9704ef50e493bf876128c73a87f4d787c7bc7a459282ee9f9708ef4f66ed6ab89021b2248079097e3e5c537ca4fe70425c67dae6fd93026403f149caf984771495623e92428bd04a599a0b41394f60265821dd36146ceb1cede0cc3284aa9798ce252af1e8d1819616c43dda341e29083fd669bea234982c836d5abc6c96e921679362f86991f01b10df5eb73a387b4c5399ad3264fa9cf174aec29fd5cb2c539f3837911e40488c9eb29fd7e9e52d0078a26df3f7c7b88127b1e14cc09d08f0447406cd36eea428bf9cf25595a92a6695ff2c3e4e50448f322e83bc7b7871fec7bf3f9f6d65c7ab86c43654882972e5b7a94d467349d2557e9488d74523a5d0821203eaee3871e80e088a184142af72ca4041315c879bad7235b15244a3c81f3d34972fa95ac1054e10317301f4c7ee8d08955b8801028bcc80112217e64a033a92044458f89f2e47e3e3acb3e3a179b1456e0676cc63847e64bf0f2a577de8efcd92b193f453bfd1df9a962bb69b55a3c380e0182208416495022880f48d0995ef404f1b3db887cf48d48aff8a34ef453d8adf06109064fb048c28aef9f187c7b3b60e1c3ee6751121820f9e9fd00091afcf4395df3b0f8d1858c9f5e63ecc041e21c6fa757ec839ffd23c80b907cab60c144f810e6e70a5c7e0a09f1fa29f48213a84871e3113bbce96728e3e77ca157dd4fc73940afdaa70c2ce24e8f31b2ceec15f86d8492ef2d88e463af7db12b1ad5b4229a94408547cb54441474a4944e31f8fcc892c581d2a34745470a2e1143293e7ce8089d6fa8a4ed3645350c15cd0c000000008315000020180e0ac542c1601aa891e03e14000b77883e70443e188783410ce4288a81200621420821c600c30c4086ca661d0000badcfe841fd157c7ec1f1bc9896871c79ccbf4f64a99a6e1b85ce87f2a4e7f603bbebd24ec13f2cac82a548a8ae0270b905c6f6d64efed24022a2aa063f2da8144cdb4cb5614bb280dfea13418bdd945509a2fb764b92ad0257696dc5cdcc1277a72e04e0b69b5c1a692c13967412c39b1c659aa6e6215c2ab820bc0f35641f0d1a81c8fabce74423ff4f091f3e94e6e0f62e454c0ce435b500f4db069c42c40a55ea051fabfa89bfc366488364225f5a854653b534c35b656daffe6647cb4964db922ea7d965e91c5e68938e297bb06f77cdc959bceaff4c74faa96a99a263d3a9381f6bce75d4e45719f8f5d77d3fc947e1cf8caee41d4a124274373e18e9afb8880665a5d8a94ea1f6cb4f7f979dcc8324e563219a3460455d948e1d78faf9acbbd22b7588393afea505150437b81aa3cade1c181a5393db4fbc8c9bc747e1c480dcc817b4571a302fb208b30e14f307fa18e7aa6a3bd4d0fc5c0ca8eaef4fc6866371c3dea72f54c2e2222ed9d30196b3c84122059d66f1fd92efde006408c8619f40366908b03b7f20e13eac0103a2aabd74e1870411256efcb3a1ca179fb247262d183c2173dded7da818536aa320c1ed777c07eec4450c7172a4c872683dd16de4e1a3abce6048fcfb68a25d6b20ae34d7afff50cb56452cb8bf476bbe959ec88a69828413adf5702261a412b7cc8fa4a4661f4ba8e300de9ea9b9349fe06470d228a85ccc09ec2e9b24b7978b55ffa56a1225fa9bda988114c983fe0c1e3b1ceed7c2d147184c15ba36148a96b4299c0263e7432ea05df07c376d1de827a8da2e0775ad8f057e53f03372ef8a0090dd179d44b866ea3a64c7ac32e32308affe6444392ec7d79454d435f8d5bb993a69d21ec23ff22de7396b684b4c8663a46c44e51f53f53973422e2cce849c6c2f615d37d8369dcbc994a1f899690c7861106444c83b4ea275364cd310a4d4d29506c7c13f44c5d304fb8c95cf58c18ae1235018c9cc047a7c7f204f89f99124e4d1fad087f88e4971ec5d2d8991231216b3b4ca3efa484cc0139efbee8e3b449a847a9644ffcca7a88bf61b98a50b9dd71bf26f6b0c627f6d24626ae875a343ee17b58691baf091fae8ede8501ffa8399632dcce6641ac74ff90783be2bd9ab40976971ef15e148de6bb3f9a0a8f79d2a4be4d8c7cd4cea7795910d150d21e8c7154e55b5b5a87b5160261f3c40abec513380bf57f27cf5d5ead24b16feb141ed1f44d90b39d842cf393bdead2239a11e222ce87697a1c26e2faf3d50f05f18192f71752aae25079443abb4f26037a9e5345a9bde1cddd5b7b8d9deb57088c32e34a5b607079859a0e6951221648664b4e66e05140b73088122a44f4174a60c4f6bf00ac563f20647f4e5d49746d0c24c25e853a22ce0447a6ec4baf0a84cb7892b41589587acb711afb1617cd5fa746871429370cf41b5e668287faa05175028542c2d4df9f00dda73b3afe919aba14ee07a91f9fdb7922076bf44145e372cf733990d8c209d6289d107ec16a75c0a8ed7767e1eab8a14e8135619d05ae92ab24322265b2407641495d8a79f122b6b5c8ae806689d62335fdf47df7632e638857628a94f618cdc6acaf9cd8ecf62dc588e1146e2e55c62cd283e7a56047bd92311f870f8d2fc08a624df47a04a7d9497ef954409dab0b5060ed51f5c47cbdce3af30f61056f9e7125f42f3bf7db87d9acce50d065153c9011961247ca15cd49c73b9f7f633b1d2fb308031d405e66a63f64c2070bfabf3800a831861fa77c2aebf0f7cca463edf815434b7d3b1111783b462ec4c686f7f0e68fc58d50918020e3e4fc758f14623230896c0dacb6217c488e499f87c5687234a657a657e9d76d0fecb8a1cb1656cc578aaa620c35ba88bdc48da19963224af22bc5a751ca56d43e320a5b66d144ee9833564a3494081d7454c5df940d95fa95ca530d79cc28e4cdc63aaa15a6b20280cbe0969c515d23d4da571b719682d98a74d622f74c10c153bc27f4e003770ad92990cad610f0e68828281c22fb26ce1ecd97f92de7dbc026ea0b2f2b97f93411d9c0b8a0fedc828e102b8680efe4a34536989177c6047ca4041cfe7037a17fef085fd2b9b8ed66b11a31c78871fea2e372cd74d8b3ce3eb732afa83fb2c35fd2d40c2dcc685525ff706fb715b3a0ca6ad9718a9b4582b31f633053f18887af51713415d9fc13e197f38b2d10c2ac965e7fcef18e7a2dd99911bbbf0789d4310b1feb79180d18e188d0c807fc39d6fd2019271d2ed07535e6600a3c6c0c64d64876d56a3eee2795d3f185b79c66b01507d92a7eca6a23b7ceb2338031236594a2e752f115abbe18373ae2bc7554f91ce445d9c36245fc04fac7049c34f39aea34d506ce41eeb124e2cf0b48e848b82de31c095df9ad3258a0d1ceffeb51a6bb186c65ad0611a44aa988fe3593e9c89b18060486ca55398b72ff8ac2fe9f59076cb416ec155fc3f0b82ff0c3b1dfb22306ab80c000965486113d2e3d7749e7dac4a9748c5b5f4275313ab1bc0cdd9210b86995b951cb71d7b9a7a09aa65d1c5a486f64d910b96985171701956ebd2ad375a23b846b30712496dca5910d2f49021eebc6338cbd35d67158ef0442e7614431bb6f89d1b4a6f5c59420d04478ac23e3ce9e0e714fbac95573fd8fd283cd6fa273b7a42b57ad5d129213d0e4248bb11cae8a72693db1445de77a568f7b94df3cdba4b9b2d9042156b05e63ad5f487406566a84f9d0841f50795f414787628e5a09d4d9a321e5363cfe589e323e7dcf92f16b8091a9b03fdc25ea02454b381d14076dd99dd55b761e504b29147d25c0b75e912ae52ba752d9a0b0ab0733b83228b2496284ce5719eda759dd649d3a313810494160cf698839b1b53d5ecba2df2f5c124c4a7e945fca41f948d094b2e935b4e7ea478ca1a0755d779771b129924d74e41f57ab6d894ec23af173fc00f15fac46284e5bec142d712b71431aae625464604a7694c5284d58a158bfa54bd4bc80f6ecebfa720bf45db05bb0569a042d12417069af0aae1be4d3bd89f31815e4839d315048514fb0233a6e3defd03bd2c8da69f7ce1f1f0f552e4dbdfe39ece1c1c983c5a985e250dbb1396a9c06208730a34bdf1b2f4bb22abaf2957cf0fe1932e9a5c1cc9f7bb688d3347ec22d353b535330bab075a017d49e659c3a1c98bbb381a49c1c60be28f56668c8df5065324d340b2815035294c3c88b437f8e28879a4621ef0ed320fe017616b645b7c9044466cc46ace41ca5c377243d5c233e4a38a4cc4c5e18e6494c7ef407542494f60297d5cdde8e56f7aaac3a3e4f08528b626cf83755023dc108a1a80b81f59c261c5466bd8d8f29d880334abd2e4cd3c81641d31e94cdef6bdb60ad4f7350ece719733acbb3500221c480b9453ad31ac4de37cfe8a1caaa469a037b185797ff83761d347ae677142d64d205cb30bc24bd6b7f935234b974b29ee20932b2b54b70e371b42c268f904638810e61303a6a251da1c486e69d8e1f9176b331c1f357047538412b37591d5a0ac549c51bb255dae93c00f17cf6aa6d39e81f4ebce47bff5cb987770491d0dd32922a3275d15109a50f6d7bd517e653284086db1d1960cdf199624e457f15c1a22446070589b443b69770692ea9d567cd7fec0d14c58df92e8a19977c65c1cc3512d0a6b2bb1180033a053293a1569f79af2c3f89ae40c86bb9b755e833e643314583b51ad5e1c8863f5d215ee777b6a36a2653e0de2700594b63a92598f68390acc66f286ac71b7d7bc7b861a7d56e9a08c8f1690519c18cd4cd43bfe8863fa0234cf90ec221042b7434a16f3c03df6ab4f4e2fd838b22841ddef4e339ae0820c158af913a7896c0db2dbc6e4a17b0b3ed504215c5b2be95a7f226f38d697963320dc72cf496f7ee2c5deefaf30e9d4c8adf1fb2624a3f728b0cc07e8f4360fcb5f72694bd78355c064082d3ce64976e2ca795706aeb55f71fcaaa734ff4622a949a4afe22fede974cf099705694d6d3185067ad41c86cec5664a3afc220bcabc337eec9bd257693dfa81cdff96dac90f88337f07d35b46a6c41b5ac5bf68fe5b12ce5ce63f21d49ff176f0ac1386f1eab350d99977bf9cd66f20239a1e30271419ce36368bbf2b3d003f14854cc397ae02deef75447f780f1af15bb1f483723782bff14d45249d835c3c85d0632f20fe15310163066573b3f4c1ce53add089882822db2b1c83a849472073da6e529c5ec62117a79e508bc72733c28fdc120be0a1c73b557264b0f38a3cc645a0c3f26176ec74cc9c7d45a9d0d63038a402f57e8570c8821c0d7af53974f7db468b8d615b47d44b31cac3b7da922509f43f421b37903a7211c0ab330e958c9e6c8f8118d3dbb1c3dba81540480066bd618600aaa8c36261021eaa9ffe67ce145607f496cdaf3a261cea8af1e677b67af92009d949811956d485e60593ac01612fb86273d008193dd59c313eae005a74fe2608c8f99d0e782717a2722afdbec3b5b952048593af96b0d112a287c0538612fe6379edc24ed8925ac40cf083bedec90c4bee06dfb3189a1b980b1b57bc25937d2a8ccc50af65c46712a772996f51692d135f0c24c6224e9d10936b90ecdfd607b10a7fd40857f106e384f10e202139521b3235400ea63ce46c49c19957aed244ea348763bfd74f68d0216b0ae5c6ff8a50e37f88bcb525f520acb5d416d1b73375ef12153ae794ee329ff97813f25144343f834e26893c8fe40722226d7b43a2b613b2e9a5f494c12948f0b57260a17e9f89cc036136b45e33ef2ae6c4328487c1f0d7a9db2139320df9eff64aa0dd7ef2904906aabbb6a0094852eb1cf770e095ea1df8af2151514743c9cec0f0954cf0745a44c0a171a480f31e0d3a2f40fe3b83dcfeabe9f4ed80bf41d02608faf39a02f82184b6626a0cddc52c969f7c7f6a0f83c8cf2e55603d0fcc00a2f45c2be7de6027888ac180f16a36399c92659a8cb9aec6063290c98a5a865922dd3a1845fd54ea007ed5bbebe5199eb71ddf3bb3db5bfdb1473762ce12965d2e0c85e42a65ae9d9152184ab116261c86d7c42d3820645a51a154c04637ba15b1b3f1e534e3a59902d1a48984e23028af5463cb2ca091ba4189515b0e7412be59e0b10e9b75d814b6c889df91506d7b95184e251c949b9c9d4f2588afc452d2056ee01aa04ab4a8026bbce4848b94905fe3b3175742e6665fca79e7e8afd6e9575a32a02c563e388fe1b3262b8d5879110e8db18f414a7ac71f2ec61f898fb54d506ca6385cf3f8932e23124d53db30698f5d0091954814c6d5626871f3564bcc18e0df8f9a4752f959e03dee7703e4e36a71019c81dd02e3f9bf4e54e9413b46b5ffed94d6e39c6e51f6fd471230d1c830c9db1c3391303cc2599d2835f88abc74b2109ec8c7a09ba7837f69d4859c5235b1c24315e26bf5e12cd9775edb7c12ba882cd173724bcdfc8d1ede711f65da6ca35ec7b2d2232833cf9f925eb432429cfe5e642e01c9c6c57e9b534c40841dec084c4a661461d27926d07b7aad059621ffad7b3ee5755fa4322b3f844ae52014136609c8be4df82845c06a2b94c968444ed1917929a64bafed560d89f0551aa4e2f4ac6d56780456ec3680b6855269d89897eac1fc36066c92feac37abb178ec814f1e9356c16dcfe8bafab5e949e8f43d7ebc466ea05423a08f1eb9ffe5b569877949ff63cc85756ef5c5ecaad5f9829c212da2f83da0218deeba88330fa93e41cfbab3affec18a165e86a86b9166de4c00fa1872ee2d092a3106e8443b165c883b3c3896f91311dfd74b0796a6e143a1415ee96462b2a1224efeb5f9c3145591f6e5980de45a380cb7a9bec1181efb2dcced8ab1044c2965ff44388c0465c7b61f0b1fc38611464c9bc77131820bd0bc3d28108b3058aec9e44f4c9acb1d4c561f3cbd39615162c90e709dfa0e50a14b01e144210cf107ad0e35843b6e841e161f5713d2a0408faf6ee95013356bf3a597a5c0e9def986e9e9e1a6fd33bf2bbca7488f19cc3c1588a06a96291b409c6c2e113e92b3ce5894de5b9be45595353692d35ab3827b5a19ebe179b1144a736b4a89ca0087914ad317313646d21734ae100bf5af4aac863f2ac117cd606cf7180b28be51e225a6e6c2fd6873ab450ae99f2f7c9232ba0a60d9ed9bab5225ec0b6787666738bdd0b18df706b5f77067f3a42a251af6d1b0f75a413f4adf9d7873d974e089191ed0fe55e6353e806f66f3bb75e36e60eebabe6ca8ceadcefda50939fd2435211972f9d3fb74de5f8fea7179805d8dbdfd524c96ea5e98ca600a3de853763d0747494834a540be7f8d7eff766933ee63017e62b87eaa6f8fafd8ed84769a29b9ebc7454f46cfd08802c6e562124132d8ad1c943c599aad853d939a72a63bd42d2993217d63dad9fe4fdce1d1050bc9056b88d2372932172db05fc3b621b666547361c182fcf6b5b68747e74b2f3bef83fede2ba2bf61322f34e20aeb4a54549b223c93b21d83b11ab1663c6e096ce2c42d3a8d2840aaab93a56cb328de5fb4de5f2f514c0552eb78de3f13324ceeee0b2b56e050d54d404eab5865fdb6f119e21830b563adacbeaa86b035f4905d0df7da1f250007e44104c061c33837af0d93f0f33d29c452305dc78de05bf8c13b23b368e9220e66749626f9da71eb49f97790f8d248f833e66287a4b0652f2ed7af062edd8bc5db61eb8d08ad6fccbd908ba467b7fb02040d29479548970fe2dbc923204490042961e52810d8c0e88e1f81c935ddac642ae8ff5a79cdb4f1a814fa04cd55bdd2fda8308cdecb7f718ed667a362ea099dc9a2477c83b315f0b54c06368a71103a3ebc8f0071a3a56471acdc7b9aa7bdd3078a2196d06b068bced412379758d13d7c64cc3fa52c6618eaf69ab9f42e820f5bdd16b7fd992be9a28eac95c58ece805f5b74b4a5dd3e9bc480f17dda7ca8a98af124faebff6a45a3892d58d25cb5b4720c1ead3f665b2bba278c46b755cfdda7e7004154bac3c947cb21eada4bb60c4ca169b7f254feca72de9178f58d922f3a1e409effde9e4a823d8d46966d2f974bab4e15177aa79d0085cc604ed368da3c286f05c94817bc251442a0e567d21155fa6f3df1200a4d0f63b1a2610943b3bac6052ee33e0095b6e99532f1591f1e90aeed945723f6be3aa620e96a9c00a3b30dfa21a296456af9e23a85bdcde64c9f78c7dbbaab45e314d6177d8f5bc0c6a8aade8f34de8611484a7ad8454f1209809da7d374378c33aa97d17d809000ef302b81a7a9be2b3c75f27d6f59f078d37f6da62708ec3dabb4025ff9f97bc77544f77c188cc2d1ede8750032b308e96ab3eff947177aa640f8cc7f713bf31324ee86624d22d35378be4b3b3bf97f01c149598c82caa748092b5723e95ca9b83b1246cbe192021703ec0b8487227e6d73a41872bb68cbc3f410b31a355bea48a242c561c99ce9055064d55685eb6a127da2e0aee3b2bbf1760526af12e11c9f8caae58b59f82e56e0c8aec891ac637a01196027474d2006bf10323e750bd0588f656061ce8669168c96682c6f93c66a3a9f3f4213c7cf443ad1c81ddb27161d437ee81494dbada3f18c1659378abbe030d8c71bed929a15371be0dd4e6db5468be0514375756b7f223d95a1ed1c19c0537e66c9f2e2b5bc580aadeec13da66c996871258a85aa073f6db3cdd5105a20e2f962a538ac5d1af2a15574ccb255ece4e020fd6f1721e301bee447cd8591f67a8ce672fec5771f471e85e9c2dd240153fce30985481019bcbd97cb7bf5ecb07c901c2b7c8afa483155a351d78e9d5e5ac54e13bad52e6ff1d5dab930fe1a0a06172c06abc47d096fb3fb954abfe6bf31e26eb01298c96f82554899a6a769ec5445b9d7d92ee1de2be6517e3e213aa00462ce80f34f34d3c69152ce108b7103ca7cc98b04acfe861455594dc6444c2ac5a1b72428872feeae9f61e6b56e3ba141d47750f6bb520766e023f546dfe7a71ae0bb8b1c26156cbfaeb1bb30acf5b397cc224e481019a0e2e4b95248b1184caba49b58dd3a4f60fc7ff29c6589b5192df11e5ff95903649700141428e0b844989cb786627155906a280fd419c5dca9e4d1dcea4cc6fce2ebf2d79c28b29fddbd352c239f153cd9a01762c729e55ad2c578d2ec450106199fe760c5e7c720a71101da8fb33a8429b40c6de8934afce37d75e06c6b4f9c575d455dda8569475e782fb68a5b94d44a9ff10bf17ad1d014ce275f711646401ce9c40090f0b1718a83317afa619302a214836afa0f30e36fb18e2e0cd48cb2b7e008adaae922db3dbbf62f9ce9e9720bd8fa5bd3423a430a15afb446ff3c0cc9d7203d0373b64cf29479a54885504601a9a27c57eba25b41f26f53221257b394ab0ed5fc44bb49104c4b1e91f1dede351353f1e0295da6e43d4c1db2e388ac48637d100baa04990a4f00de11ebb583f335b2935e0c7378ed18fe628b12abdb7b7fefc3445f02399fab7034921d7a77a44a43c3218fc1e50ec7f080ffa0792b9a3f85eff496d5e38737e2611fd5dc37d3aa2db17a843555dd72a52248652606a3519e80c47cf76984b047920fd3daa70cdf43ff72e6427d13f195c1f643109d50969f23a00ebc4d5ce9719a2669c652e0a1d9c01b3a01b66fab14f0f64022dd0cea9bbd2072074b97c04f806c6e82ae357615d9a7bea20db5751e70a86cef95558c005f05cef1888347836dcbf4fa4960d25390ddecdeca3f88e75b6d6704e85af0902bc96586dd3ba704f5a4370f0a02bc3bf3777f39ec88598adc094b80700e0e2f4aca4eaad5475beb2d38fc5a34b13298b61b98200b8f6d9e43717dc354f9eb39d0fba9cf5c75b629e4eb539741273e3c088d48bde2c25e54ed2d4585ba15a2079da0468e17d29684d3872bd4152785f4e00b818aa566438a6b4ce111dc76c905d7b315dd6a58b211625b52d44bda9f4064d08e8b7e21ee82e7e226a998a0ed8e8629b63d2fd0abc14e2207430676824f1d7817e2e613954c695e470e79573a2fd81add09797bebc0fbb58ddda56d672437b3202dc237dabd458d67bd8797512a9d82bd3cd4cace84d013285ea01606840db8ff5593142db00d16ae6964201bdf90633a2e004b9a3d758ccfa015a9628379936322994b2731039284a19304d3a2efd2f2d1d14fbc4933a9e2753624ea0a9e7872d7fccf2665ba0ab223fbe9f486f512150cd64a05711b98482cb664e204150a925d2eaca6df8e1bb79714f787d5536bd24fb957479a2e942ae69164c606389d49d71d08f5eabfb21910fa5d1c8c39d2a6f989614d5c35fb6458eccce13a2c287c4d1dc5216cf7a8f8af1a4e9ef5a24495f7c349e142a2cad7cc9fce12fb17a5df12e98ebfba3a1874e909687591afc463e2069484aeb83f1a943d011f36272e37efd649ffeda1b7d7b0ef11121975b79c58cb7ad17de8b7a4896ada068d729eb785313d7650fb9ab21321ab3490e65c6051449b89c99919880464370f42698ad9a5ebca25e96e4a6ee932b449f8b14dfde4d59210849163686e7460a6ec0280c8c83f0d5ed2996b4139727dd90871e00eb01e701a55b18b2022b2e5bbc63307664a8af9c92dfe5e16b030a7e1368de1c61d4ddb5a70dff4e15b3125aea7d3ad12beba8a98572854abb75ebbb383339b4cb4f7b86a4df63ebc42322c0fdb47ddd83fe53d936a82172c449a3233df4bf5c27e2eba5d3036416ac6ef124ac0f2bc0ad0568439c3ee00ffbc6609d7f8576e87bda663ce89bef481f032ef44f1c20da456344129c2506dab63e26176a059f3c23b5f7b839f494dce1eb535ceda95335c361fad64f92ffa389ae20393efa3068e6c7ebe9db670228cf8b5d629f7e82fcf641e74512a11fcf106120ea4899c9d95128e18745ff5ffc6c6cc17581237742ef81d14b409466a49ecf99a0d41fc73a00c3aa5dd1c49c880e34dc03bfe6663a03211481de09ee773c62025b256b7c1877b87b122bf9395f220dcb67dbffa47554e9c22447b9479bad64bcde7c092d08bb1e6b8480dc415d509a460888300e3937406ef196461fadfef4d4546e14328db2b8e3d7c9e20fdfaeaff5fdd065f94e224062dadeac9461c9c93e8b1a8c9b2c8d3860bb4813210d01b0015e87494828cec6efafa35ecb8647f898248133b7d9ae4b421e06fb04658c74b134b4c1f0ab3c5e9c6cb322c0dfb4c80710cdd2ae1f235916da10172aefdaccead5a93a0aa393dc43c12b4ac620191de149c719e62cd0c6e98a6f3ab7514cea8e3fd1256af7d5ba7c77985913afb4bf4e3adab15611f489343ff02891ef795b7f5939f880abe4b60595731d096f12e6dfc0b20bef5121ee75528a0597bd58e4b2fc35051f631e9df9373744bf212f733f762af95b25a44404532f722cde623cf0cb2578b66fd2d843e6ccfbdca30373b1c7551547c55d0a76a911520b79bd134dce985632a72760e991599be0ce31acc9eb3c9ce11cf1c93e14ee8068f9118acd32be12a136f7ebfc46f0d880aa587f0077398034c32344369b470204e4ee926ca3a4d98a06516086fdae7bb599921e98e4f81d077df10eb015d33d7e1bacfda76d07074284e8d3078eae6e65650b65bc361978881a78c6a3888d01ad318067f2e1c1ecae143303412061750772347353cd800708c1034bb9c33c92c373b28606d0d6fcdac3c264a2b7d700c431f917e9d37c91dabe6060c0c5dc18e6fc1805818bad4b91c817f30eb0bcf7f595a414f3e26656b63d7e7a679ad9a885682a509a8f6b71f346757c2bb3b25335cf19cebea6f937654efcfd76060454914094f40d37fc105211a5106e5cb9ba0d3a031964d55db4c77645f5e2c6402831e4c051958c4d8ebb8ea57db5c565ca7740c02facd9c615de54863d5d38da19177a1f39d0be68187d64bedb398d473898c6c55438a04b848757e8e450ca0402f2315098e89024bbd97968ea26349636a59ca1a4f803449f25530e8d651c110623b92ee5b73877fa3dbf336cac9d6eafa415221a8bb54dcc0aa583121a21c208de32eaf47ba6611be5487a180dbd023c25f830becc456d6ccea638135ba558b3899192b22b3056e23711344d66c502b8a040ce484754f609fce187de09863c2fd7ed6b7ef1fc57730a6913351b28977f2507d6b54e67a711eefcc7d9dd8894654df6bd2488eb8417ca1822f233577a9b3a1d6217d42bbce54b793dca4b27fe903032c1936b9000e6db720be5ad4605f7c2bb8dd280dd77ff1db31dd10a46d61e4a33186e374e59d6555fafcab24484374d70a975774faea595b79d6914f4b583b6c7ce46c71efedde77c4b7778f64f89dd75c58041977b052f7cd2d30ba7e4c0a3a3024356a3cd6e09ac1976306800d10d06b31e58bb25373eb4c387138917e008a504c6c767e90b04b9f3998c042d82971910b1054c13f025fb28e92b57fcf646c9aa3b7e693ec898a2af00a6b40f248a29952bb1eaf8886dacf92d5edb0d465f03e5db01e4a5485268b3bbc0a3fc55f0053d5447c082db07333b65fc1971eb378ea3dfb06a7f61ce419e6f0cdac53fe4199b82f5a6daa33c1d4154faf1941482aaa77948fe86710113ae3aa5a0024e07e1656ce8a939c236c3f75b06691ef30be28d280e8b6cdcedaac4764217e2e4fbf7ce134b9cbc66b885056d0b805f5a47cea5f95b19331e1407ace8095f2eac8b69c9c49f5ab239c1326289011824487ca3e60016ba41c60a6b14ec80ef87a335410860b7f50d2ba396ba814b68f7bfead4baebdcef571c5f8a57ad4cac94712efa9ba73e7242730660c0f5b45f51fdb710efefcbeefa3c39d056b607fd3a76e71da461f21c4eeda03fb2624005c746e465241119188b3022db779ca5e6de643b36702808644f84dcbe79555f4fd6b431a2f9017354b5ae1b9aef465e58f03ead16d434526afcf7f6f97697e913c8460a94ff2323128e917a8c373f0de7c80a470a69d7358ea3d3353325bdf73c90b83126404ba61f7895aa0caf4e560848ab19a1f404361d744b428ed0768d544b297ea86085cf91cf18417d50a799d137e0ba758d7a5d8cb229dc343b7a941b8e3c0ba84eb48296b5accba2686a345a3f3f0a274afc8d6586bd8eed15e952413f404e4f4304ec398c924917ba2d62a817a7fd1449201c11c37ca2c2178d43989d4574706bad61e1f91feb055d8fcf442c9845e26c533d63eefc244f64951aaeb55a61cfc7bcf604655724069e631962ca3270c05b89a7dd8a3d1526bf3c1dc133956a0e89f7f0a48a3db281ad6a091385028172e998ca144cd6ab2cf6db96e525aa98f8e8bdf4cf1b61ce6e7bc6d2c45fc23ad3a96d4860a05a44537082c5a00a5107bc3dae369f3396a81b9b550d2f7e3236d7db6217f0b8e9ed32cd84e4eb7f0d3c97b0081f83e543d29e4a22dc82571808801502c0b9ead517491e2f8b17d6e38e3d9546127ee8b4702608597a7709bd3a38f35fb9ce300da90b42442f84b81db6a8e8d0410fcb288d3b7458ad387181601d8aa945632a9f4c7dd256fed1bd8aa8f281980d46f3b88f780055030971d5e16fdf885f85a9ced23754f1c2de5746c42f0a21921aad1ec9163b6ca9e0702c13e82b0fcb34c8b3b5147b2c7bb0396a3c23fa144ca186adc81bd090ccd75741ed03a36b148d85ff223c52235ad963f7892aa610db046112a604bde42897522a3e77d294ba2deb15274287e6b4eb39d0ae742b1fea8375d0c16105e0c0ac29517645eb1929deaa821b5b32537bf07e6b0ffef84f0591bff8d769efd6a6282ee8c56c93d12bc6bc49053244e2b2541ba121fb7b1f3db75551931ec0ad907d8fb25d194a59b21abd9901e6313af7d464f00c900bbcbf8b8c2aaff067575d424cbf3b3d5ab149c8c70e7032546d80a390bba006cdd67d97efa0d37da2d35649e8e121aac9e73f4d5761ce0773625675ade3d87a7db4c59399fb84472b128beabc2f3b1bfacb5d0ec766391bdd97078934a826e8f8d881f5ad398d8fbc34e8e7db6f4adb4e0b9002d96c7d8b47b86945b3b7f63e67bf8a31b26e0379e10e97758a86ea9026f73529b3df6ab8012cf64ed11862069cfdd54883a2d9bb2058363b870a933f26e70317677c3c340813ffd833df0e6adb023a0bd5a5fdfc92b3f20b4239105c2b2ff5316d268183196236cd3b44d150f67e0da2dab0929e8ae87f1be7f10dc755407be681a109d1c2ec3f7f1ec3f108b9ec2a2dac2c81feacfa7865d9339b69ba3a6a1aa077204520d879c0881eb80d7044065c29b820b7442073a6edcd39afad9ea6bc58e1f28db57d893355a6d55be26d75363c0e77760a9570dcbfcb2d5a4b44bc3f55fc92b9a7547132bc9cf0f28edae1eb6ba09cf2be90b8931a6b2bfc7ae0bcb72cc995139d7c6ad6a4744fc419c0a5ab3f3ca1fdcd0de74332c18623c4e9cd5a2b932f9777c2bc5e1ae7deb2b1f8312496f218e5cd79b922e1537c4507c9e7cb524911f1ded80e45386e7dc871a499fc9b29cd90982db9b0c856b2618ab4d1dc1fe547b514f953de5faaec5520896eee9c97e81ca3978f2eb3b7aee140f223f12803fdb37f8b241dc33af19f1b4d9ca397bb9bf20a45a0ec87aeaea1087343a21333af13c325b0abe710e536ffefc553df50c38aa6aacd79b0f823c06354c71c129a70ecbec0fdac46cea283f323ce972bc35018437c8b6b0030f695fc483406e275eeb22a8e0d72a12d0a6de504fb58000358bb46083638119ef0d06a822d2e933fc64130291ad7be3b734618f290ef51a99b9b7805dabf352f477cd58f9113ef7ceef708839be20f46197aba096da0aa821491a80482b40bb5b2d18e55e65a21df35473a1f4c34c9ab2a8a44c2b5402a397329a64a92d659e7429c4f4188f1a7e84daca1187a37250f4e3ae9a1ba8b44c205723cff6f0a0a1e6c75407207d7d85b70fb66504aa01c5f0df9396a8c44f20901194cdcefa9385fe63b3858234e3e0352b7a9318950a0aa7378e07799809a4224a49168aaa01afac8efd067eef39b7708f487cdd768349854bbd309ab86ca2f016e655be12b7aaa600487759ea3ca32bb10202017a6cb6c2ef8e562ba2dc025799c5767c97e39f1ed1345230698d25b423e5b91b82c99e39322256ac8053583e50d32fe8865fbe7e67fdcd3004ea245e9ebbf1aa4e3c06ebb1dc144fce7f20dfca3d7ec4f3914af38286ecd50226d5712bedce4ad2c6c71fdd46bfab6f101216bba741c4bf336ae9f28f06751c12d722291e780f93120e4daa3950161a4c04b0d08dfd5dc4be2ebc77ad8e50212206e9ff82eb42aae9405c2bdf0ce0f08a2ee2472219ca808b60481d09dff723c31348b54e2eb442d0281687907d0941d8c4130ac85722abdc4b831e1ba8102e12bef57073a0254916f248130e0db82c5ecf991a8120d31bc4052dd2e180293bce19816abc4e7daca40d0ed093b7e0f4bbddbe383a5c21b8acc5d3b7d6201da14afe7901efaaa7df914062de317e7a2b74af497c836049a932a06778bbf2449ec4ab3af25f847f5439b1534b2831ee9a7be1f706e984d5bd38e94301a7ac084784d09afd1d8d0e8a1f2cae823a99e563aa0f78867276d3aa9711b7157a18c83f7637db8e66e70585c82b89d89078c02ae93422c91183633104851db10f6b7a85ce309eb3c332dd425bb412262c334a59277252187611c726c5a9c507d7efe94645be73dd0e4873e33cf674b97e2c48a92e20e06beff3db6c3c0838938fc252f7812ca318ccfee7c29225a745c766d561e39d789c3a7810cff65bbcb3f62a9bae515d016261bbcced6e92ea993f13eec5c9bbb0493e080634855045a45003d8da5c49f49e1e4d7c3a0987bbba457a16560dc93a592f3fff637f91992ad21d26b79ba29f7f280f410e037aea407d4f2096ef3a9300ebc44250dd190c6646a45af4be1ff17ef0542e421c51b80c50c7acc48b691e63f6414287719a1fc949c76e6ddfa09e047065b730c2322523719114c572d9bda2c2bbc658c826aa7c4cbf75625f35ed00cf0a02e762250e22cb5d44317664352746ac6ce6f18439e54b438d9b74e8886e51740191277c4d87082bb7abf03c3821062d40cba03e72dd22917da6a6c3ee9f4f06cff242352f734d2780505238c40e13040fc61e123fc1943f4a42215b78db411c7ed527711e30454586e5ceb82a6f66ad296581a8077dfebc24e7062b8bdcd5c2f7d397659b49c2c911005185c1782e478366441e8b37c4a905f19337fe15da83950f44e7518df5418921ad9675db4ab0718704b25c86b81060db8c67b323a8e80be7c5d36be21df11c4c2eecaea43db3d7cd9bc5f56d667b84144cc0e116cca13891249011415d0495e2804f0cc7353969713aa80edfc7cfa31a1cb10523aa8df8aed15f9a98a37ff4bcedb89a158d75cf7977381bf70887c58bb69682a91f8427f10c9d7fff664b18309a137af53ad19afd57703574ad464cba76852211a14070a73533a705b6ba595f3b31d97c23bbe4627dda0f7e89a6dc07815f2b82ed4ab624477855a0f9c26d1bce610c9183ee1968b57e50bf8f32f7409937cbbde45f56bf28c3067585ba86afec39c963f401b239b986d84ca87887471b9c313ab15d760f61d52f4f8854a9838581480eaaedf8cef6a0e401a548b9a8ab4e905989ff41b9df4a517e111838a706613cf421fb3e69c294878e5fc9d09d1891cb473efc7d0cd44df071e280095d818a910cb49fd5b087dbeae0916e0d162c5cdc001b38a7e7043231dd340aeaa71ea84b6f728a1edd243098705173742cfd66ffcf62ab1b1ae1234b3218489287cd59355b3a9dad941eb2803fa9de45d354baa1725363a21f59fae913ecedaf4bb76502f2b1d37454e07db751c3e98f78d95780609128bda3b3b5a0a3cb84858481d4a2a2b69e8904383de9d6fcf03d7e5bc8743741a4ac333efaa160684dd4134f2dbd0c70ffec3c4d218b4352f4f1773b72a3bcee509a75fd9a492b70f5c391870a75d094795ac7181be8d02caaf27920c74717c8b6fee8dfc441517993d4a6a392df447a93e120f8060c981c4720bd19623f018be5125192b4db44cbe0f37a2922c10a2060123d7ab4c2412e88e4f852e1de5ad2f368f345b0ff33dc883659fdc71a581c28b9caac03273ea8b6693d152f25991a9eee82daf4d68191be2305dc6f46e3f030d5c25a47cec6780a029c03395e28c24395883f6fe40ec8cf977c17d26fa953b46ae04d087baf794321ec7bfd9487fcd05488d99738629497cef03ac3a40974501fe6ec0131bdffdae34d66c619b0a8381bc6578e3266d1b29b46441835801f247c9710c03b5e9b3e426638657c0aaeec2c21566b35d03c634b5264bd7b3bb8e8cae3a9da6d07faeb16ae8d4c0ca7891d28078801088a97ff80222cb7ed52870f17873b1d9f0326ce23321f1d13ae98258e82fe4cc57503814eb7170a903570d8e09990776c1501f7baf87f7b3e6f013b720e9c93fe4130d7e4e1f8c6707252b0dc1217c1543bb6510381bf89dd9a3def0ecdbfcfccededadc3e40cca5dd111762b5fc8795cf5bda07936d7cb572887a3f65414321163ab0eeafc09a884f16c05cb6a2140466148c3b21f9ac32a050b67a0f2879ee331ea5ed4fbab08b9a0853655ca2e7ddf1d10b3109cbdeecfd9d9c6b87cae677934f74a574a37bcd597b8eeeb6f1b73225ddb180266bf5203a8638d3348c73e527073ea3c24532b8bb9e46dfaeabdc221464ee1cfcf42f3a6b0d84b24cb114e4f849d4ac07532a26d50d4ec54288481349aa82467d6988fac140f922103c500c40428915989741c0de1102d95845f722f0b1dd86e7ff155b3749f96dd51bde9940ba7f3a6776768edb7507a18052e106b94e9d64d5ebd847b33fea1641da235dcca2e1737c6f66e19c83be6251ffa08b3b05ea1fb43ccf350112c681165adfebe7235efdc5d9c1bd94cc2a737d8ffb30fdaaa97708278d165eb86dcc2f134d697e5457b849e649a7ecbf3ee227379c389bcec296709968f09b618d10e357d56d94678d54af69da23a7db29c808294c307aca3398a9b396186af622d2ef6d3e4d0d9068af2bbdc5ab49900aa2394e3ed145f1061590d113edcc9211ee123002ef73a60962ab118fdd572a2bd03c0636f128e181ba3180024427cea99d21e3c16ff42a8e0b13a7f0561ee0d0e056312bf3e381ca04d86e6e7354c59bbbe1392d44457ba0d36aa8e5365ab004063aad747c0d4161aa8589438aec51a2c6c4bd604a890a0b9cbd3ab158ecec3f23dc00bb912a4e88edcaadbe799b53e44cdffc6ef030945375afe24580928c9372c53482ce65fcb8c5b0979d6e6d7ecb92ae7e1ac7b1ba88849bd84230273dc0ff3f94dc7440e7bd22669a03fc49911e5938841e99b5b26f521244e8ba36287e539bafb2712c36de20770c5ca73cc6d047c64ff9699a082d2234993a2d9868e6c604fb821ccd0d0bf8fa2f29ee9aadcb3004da32bb291114144fdaac7f092d48d52ba8bf195987fef9a1fec34363498ddeb44d44b4faae5de8dc8c7945ae98f4759b686caf96bd5dcede266f8b4a4de80d0443cdc277f965b3d8e5c8d9b543dd08a406757fbcac0c8cfef09152a457e62d42d1b72c756cd92b991f2009ca08f2577da71766e82832d2b567acfb202b7f183e1f943ac255ba32704e6ab9c5a939e02b2cad76106ce4e0060f33f9f8fb92938227a3f2c2b8f18bc46532fd33bd668e02ba9922799c982a5cddaeadec23295b8a4fe05177b378a3ee22994121bba50cb60cc312facdc200c30f9b24b2617032e5ceb16fd8567678ff4c17d37da79e694782ceea0083552850b604d1cda4f4b23b1e8eecbf7d4897d250ff636483e15c5c8e7c125ef4f3fce9df1bcf24aa70aec2d62cd34171efa78fefc92bbc0599adb0842d4b376e338b6def01778de7807905d51ff98822ddbaeb81b689e2142ebdf5b6b3e01f90251cd628b9aa7d3b76dfa1a769dae6f1c8cd03973bbd5a4aa26350e61ac496eb9139223448f6a19eef78ccaf7bc34e57485a6a346220f8a4943d21baabc9878c1cceee0a60ee74dcb3e2b6e775327063ce28fcff6bbae25c9395c49370d18a6304dc8977eacaee9f52a0ee725d2d30f914544d2d5b12d66d5afb4c06c3197d41444a7404798c0798cc32f500e411537446d079495a3fb0956b3e514e68c0d627936d91ba2f768572e8841eb335c6acd4bf7f2832140d60c02c6140e49dfc412273872efa4e9db176f43cf4f022ba1b7a0a22133d6e63d96bbd41daf1781fa1a51a5365e5bad1d9ebf5e7b53935edbbad7fa7c5128be1288fdf55584a72904695100ba52278951384d7e9156f95c1a4c0d793f67377d02672a1a1f16159d1566043aec27d0ea2c56bed0e89e2fb6dadc8bac152002509e4d4075cfacccfc259469eeb24c751c0994e77d2896936598f265de60e15fbace0771743fa1b3d2aa8d199175e23505728fba29a24f5d6123fd12238971772ec9f0de90651790de8cd5297e389448cdfdb44d4203bf7801eea4b245d479365d77b76231c78bb651eef44634e3cee94c63a75ec938c79ea98278ef5d4714f1cef89639e3ace53c73d75c6034f4f6a8e5168690888f8e25c9303b88f62560ec11dd5b3380a6794ceea289c519cb5a3704771f6e3e085336ab385cbbdfd8b2f1ccc9f9cf7140762dc088cc1564aa41d9e345ee2abb70762bd08e2df1b7e58fc56f9af4c7b91561847f3c5da4f5fda0b71882d497a3fb8ae213049517a8d117c37cc5de7084f7a09464b224345a769d5696b27bd14004fe6ea256856eae3abb87aed580ebd6ae70c7248f866a9830c2d89cb9352744177c41df522499a0adce2b078708e78cd03c3030243f68c76b2ea1a4ca7ba6cc3304202c15230f9cd202fcbb3a03d9d4f19c47ff0b4edcb851efee3a1bb61c1f41af7cb8603ffcaf8ce0e70a0d43fcec2ce5f7a4f4668adb05f60d966a70622aebb9aa87efa5606a645a9ed63d299315fcf6493edda247fc9c0f6e078ae61533cb60f2bccae4f3a86dab32a7bec240a07deeb953534f6c8d96313a9d06034f6d8003f658f271985471a404c54f5ade0722ead480c9b7fe6002d2bc832765e49019851a2e185b7de688ea839a7a7baf3b52484cdf5971327c4f037687fc90bfffcf2df3fc0ae59feb7f7151ce61a50d33874d0a0dc80af297c193649acb68f1873b1361b3e6377ac36c0c5c5250c2571ae0a50f38533214c40e1fd69b60548eb4190223197a241656a9b06f7c46aa558a83ce1f392910f1996b6b54a1375037ab274c5872c4a463614ae9d81c0615253dab9b0ee39ed36a9032f63f3a8321b81fe7c6a0668ed8c8f4d18a3bcf60e176c7c9e82384a3f6a412d50c33cda7f1cc205cf14962a564319b6ff26313e5fcdfee533a4d54b3951f528ce2e8c3f1b358691abf48edbfb4d030525d03ed927e8d8b9881088b1d78eff842f763d3b8fe0c24ae62112769565e5fda22e04d03136049f2be54589a455b655e2c27cb05b407965857679e0e115ef10026aefae170e1c8bb2797af364431d0947b2b5f8ae24dcdf932fc53794884d61d5d65df21765c820cbb07af79c56d2e2934a00cc98df96820c2f5e7ed48c688ce84de9242b7ef7cecfdf1d194ed9f814bc3ab9620f1886fbd99d9175e98162a51f254e4a7410cb3782c94cb08d1a5d2a81a145b92a681457c253d3c0dd3686385f0cf3e09e60d5cd8c77dc3c2c6a671943d0829a3ddd36486b15b34c2fbee569b51fd0e50fee522a359e20be65b8be63c5308db479d987b4e9bdde69ad11cc03107919978496458064be4616329490298f395cbc2946743fbfc5a8178fba4b8daebcd0d1981427f738710ca9f54584842117a73d854e952fd62d78717c61de3b0ae30ad96d259918f279174449aa97ac92e96819937a5ba5301d095e45e08337d1302b85beb6a5411028decd89d75a82c2676c988f37fccf3294efb00788a85f10d020c556020deaa0e080da67f22b8ef408d2fa1ff078eae7356fd93ae986c7ce08d15e41803f397d3b7bd55e5309393257f109a4de658498b61a9b57b275fc4a26f9e14d28269d476a619f1b7e018ce8b3f61809c2d3b139b488b1b20847b310639bbd8888270ffdc2dcfa4b90eff966c87689d2ec1110e003ed5705165e446b4a2a4f14f64331b40b3655563a9061bbb295fb526da5772f0070382283381474c3e27653f1a9119c50e04c39934495dbc5ab581406e1515b08f110177ffd0c7260b9108c3b2b7149d06f06c6e0e676deb7af8872722e35912972865c22e54e9d3bae013516bed511411ab448876e6bc55d17e608235edc44e5acf435fba51c3d9652837e4dc9bfe562fa23e8feb0319cc9fae8a447c7bad34ac75689f051fff185fd196182e1bcc9c7dfec80492a8a1161753b0190b94b31efafcdd237983f4f5016a1f8a4d0359de7a9f86907e7b31b384ffbc80565069c26d942b2c3c3358d7b000d39cb1cc719e45e3073d7c91d3d10e9983f14cae0b092dac66b4fd7801f6a84901b2f34ccf80b311cf06b4da3b53273cdc915beb11dceae657f576d90f19e96e0aa2405ef3cdb61c7f69369ee1ce08c4e2829b65b974810fac7c36a10b529aa989563874fbb38b2f29c57d8cdb639b2f28e1b1e9b2c0df9e3d9a77bdb1adfed130ab425ed897d076c3fce2b550ee6ec42117564f763f74d4b209a26bd417b1f7391ce1e23ef72c8ccd2d0b5803b5a2047107fb1685aaaa4c96ccfe9757f358c24f12cd85930ff93edbf79cc13083b4bbdd98457824e24b8a3c1bba6167a1e8852b8cdfd34afa0466db4806d78055b1ee677e66842b7d051c59266b1413b02b6c6d80bfd9bdd6f9801ecbeb064d5690a4422d8c977b5252904e9a992f4a15b8ffdb0dd21a1e4e17cc2183055aa252870c45fb8a9a7a1f555c0a619cc891c3d7d15978011495fae8a8e195ec7e0046899cdb0667d9aefd529742aca5efb45ae67cfb0d143b4ea20db46002c65d57b86b793c63a4806008e4677b9585999f216f7e52e06f83250ed251b8ea333d6ee0cbf876d1da391aa56d0f8d5d7ff4a6d588ecbb99b7416e352a3f14bf3879076602c7c3ab5c8a0368d63f90a2a83fc4110884ec66008ccda69bf56cd68850573e90b2989096f500ad4ca6cbbd01f37690ddcb2eb05085019774487c261cc3e389f7dfeabc25ceb63093a8c473a6fde2f91021593a846c04f18856f0241b0c5c3219efb55636bee5792a28c0f046bf1aff0195884d3f7cbc80062f2e019cd9637822f9160d08033200cf0eabfa02d95a399d9cb6081d7f6894c19c85680a50a961792bb40366b9bf8117d92933202f6fe662d81ba0d7ebf8081b7d7d8af4af066ccb9f6512a4599822a45a85003e575b8f448b79504473c6d07e7304ab11d59a41050b2e5666256b22f14a5fdcc8c8654eec057284c5a8e061d1a62f42e059d3db499a2a278e09678a3a7cba9dcf84899667ba9b47069a770c529959c4194c60f556034648529295904f5fc54ed12b2c34c864c584ada7f9bea6d735205e6e01dacd1beaebc0d72d92329e32c604c7b5a03d3f19cbfb80f19b98ab497936980f18104e8722fdcbda552c9571ae10f39db206284524e59a643949ca97041865510ec7c27fb01f02c74839565d2e70104e06702ab905b981ae15ae399fdb013e9334f31d7c955f658998c81209c5449e0b094b2fe595844cae1e08dd7198e8221641559ab33ca46b3ff86b8e82d6007c702b91ab2ea4e08a19b223e1c1d834496f480b8b5e1ea5be691508b790729c46548682f0441c3b95b3049bb599465eaed31bce9ef94326f93c5b2c1689d5dff16769ac1e6fbae2c66170cf397172a8536d707900b6ae7677d6fddae6d780a77648a4f1a4eae6ff4caa59f5a7853171e9096133c7f9e3bbfe267a112ca8dc570d6b4aa1a5ca4d42bbd4bd58d8794df97200b67291f7c3220f2c7dd4a259315e92ff9a07406d67cdef466ca946538fcfd7729303c686c1cabf707fde6e8e8e17fc2597faf000a8813f14800cfb1c205401faaa4cbacf14cb2b9fcc86ab494d829895738c23bde9a9ea8e400faa01ccc27d295397c66160e09d9e143b3ea68cf6687ff3288a344ed1a6514f065b842b13bc8f055d6121ad4fdecf114c9311fd54f98c6b3169e8fd2a074c5082b6b6a17ac6fb73ad9c5b68775983f58978cfc8c848101ef18f84af619bb81064ceb9ed43a883d73b21c4c5126281ca20ac3b1c785d6767154891a42288770745c1d7cca3cb577d330239eecefe9b1ca02409046e8684f4baa697990f0df184d2430915483369409e2e282ef38de4b2c60c255e45b4fde08bd7b32a00d61d9eb3fe3eb05fa7aa19a1589dc55c3644ece95c0bd3ec035f7dd230add45c172ecb6d418a99505459ef5aa2a5dfececc0bdb8dbe69e37090dd4803e73cead12209fff6612df404fdf931f34e748268ba0e60b6e6615459c2bab8e649b351edf43cac6676e6cc43bdfde4a8f583327447978163160f2f2f714a455834c767c7d828e4cfdbbcf0707fc6b76aa40139f28506e0572dffe403cf4a1dfc123dd057e662e39d3146b236ee89d2be2bd23ca584d94515e56a9f15e8178d1a78d37eb9d65eadb4c80def84d9fcb4da9a399374c90469289eab034b7286b253832a7ceda9f6c284405c733491131fc9e40eb8cb40e92b7b04fa18b3afb2c19751b3f04fbf7720784438334feb3f6cbe5ee75fdf4266825b309048abb4485dc9dbb29c022d4dae41d572b35d44204e4d3ad01fbf4b8747ef199dc90fc7e62fb31d00807af85fe5e418c9df9f248ee45fb5d133534fab1895cca2f0b6e637fcea6644ada9342a938e8decf2ae55023d0c689c6954801c6f9c0b019f399631c7d812577ed14e48efc52504c14561b459e7f938c1794b79eeaf880a4c052b1488c566104c46d5cfa2f1472317ad07341b6b70fcfe4fbab3d308827284cf9a1391dc557bbf95098ad7e1462875903e7492a888c12881c1b84dcaf49a8f26ae8ba54e0f0cdd1ede6728a6dc042f7a1d71b3136cee1401c8a8d3c3b1d8df4280fd202f8d7aaac4d825cf0cb006b4101afe34eadb6eb79bb012227c5280acde45cf009e4d3624d9208f3f9e6cfe8cc6ef477860e96309cc4b869ac8d21b8ec2e6f9d73611b943bdaa2ae820b005be95015f6c8340cc8b6276b6e0629a49b1fb304f6787859d1e9d58217296b47e5d567490a46283c92132cfaae069e9dd8870d46c24381a8f6281b5e62f211ba935f1c077b5485c2d5784fb019ad405ec4a33adcc385b72dcd33052f75228bd27b82dd28975592904520624cbb5e633b0bec451a20f480025de4c5c6dbbf04422e3281b942fdb7e8821936d6ef3601cce035d17471d492d2f2ffc16742bf3d3c37c3def1be94190606a2bfcaa02ff8aaf7e678547923d48e397fae0067269ec03b4b1ba5a990a2f06b335287d33b29437dcdbd59bbf1953ce9572075d613057295dae1888f6ef845c188b0d5671e5887cc8b21ce08f10eb2dc26898cd44e246e1730bd04e5514174c768cb91167c7214253e6883c3621c112e23b160bc7c7a0a792da6e4bd68eac40384a9e306e5f3b4c69a425889526230a377f954675a6b000738d503a589c3e33a5e17e107fe4be90b45c29105b10be8c99a2a71163f50b70a0e8456b76b46313a17d73fe7b38c4d7523203a05d05fd645caa27d67a2598d4b4400d51ca725ea81ed8fedad71be75403233404d05d0a1cee023e23bf43f8cd11f8f6a0c5145e0ead1f97da37e106313556f3669a7bd450a87d7249711046fee3222bd135a3877ee8a8ee70450ae2a0411d887e8095d7baa3b8b9f06c5726dabb4b22e658f141abf366f2a749f193c0d5dc4b7949a0c620445cc9f0a1d4bd430775124d6ec6c54084bd9c4923884002e38585a8ee4829e056111071d7271022a4a42a166fa2221c15009c100d62ab66df82ab19b135d85b356339522ee4518785fdd21e5ddce812c800e97c6f14a5184f451656ae92af7e267f1008e43f239f338aabe9aecb291a49d87c251f43bb945a68ad8f762859bd26f2abb322419bc851b9adbca7d381cbf42cb9836962c5c1568ceec2952a441535bdc2e66fa53a5a5c1674557111ff13e50c2a4c33e1d4dfebc3f9a1855d1c79f381989bfcd6e444b4a4fa297f324a6e31c8b77517cbbfe1d1d603cfa65f6b06163382b8c1be328336ef920910f126e78a1b5d0d97e2d1dd4136a62fc4ce89b5da4027c4080808610d4ec818587a4a6b2a50c88311119b75215ec4e45e24c42e1e25aa83363a2dc733f93230a2d2f5a3833d00c17738be237f3c05c2fb743a5f5cef877b1b2aa45ff728fc4f4645d56f335426d624ede1962b04f00386b2071b133b94bf0875fcf0a2b528ba9d37a577754048fb62781d5b02f615125b33e0a5af70f01e50bfab2b52098c5bc3ef8bfb5864936e34105061c630a5df3bf13f86112d60eb2e0c06c6308b96afd729f21d03d1e5eb1b08e85fc6465a921029bc6531aae52b5bf203cf83746008326ebc7880ac0cfa16ab0194e0bb1c20d23cba17cfb1175babb4d3dbdc07c86eb7f77719a8f481a83e9db0bf2302253336ae365cf241c13a8c991a61b8329e705e2b605049e95dfeaea2500d6372e62408c230930c90d97860e07dacc0b20f91f9b52a898a89060e32a4b9e8f9e3efaa3f3a7a8eb400958fd286b22cb16b64c8bc24c4c73557ad7a3aa342d304c3fe201e28efd0f206011d3f04e63ced348dbc69f78d6529415729d8862527e9f9d07ead5bb94cac67a09e9b2137a8f1d6bc73fc8ef4ba0a5b66bda247372ffe32958b9060e4d6152d4fe65e952f1b113dbacd89b6b911c19a5f29bfa529c8c7514dab93d56de7da4cbdac65e07b85c234d4d6028a057e695d3054aa9a0e6a0566043648df8a781c00dbbe4c0f8609ebec800eb7d3a5001c762504bcf2e59d22ae78d18e86707c5728c7c2ad6170e0c3f90b019d78f36f04c59a6f1b7774ac60b0c2cbef5d9430ebfc6ea365edde7b6fb9b79432c914db0664066c063e3d3c16ca1559406e3e509d74b82b70d934ecb2cf91271a8db683310dd34e34d50e4d89988669a7a6e130f016abb94cab284653f64476b7cda89330c30e0c55d4a64bad45d3615db0c9c08e10842c4de63b2f63340d7659ad1897cc0c0c76314dd9cd3a155d602354c519643567c8c27286b6212eecb3e50c714970f725c946aab0db7dd1d908792f4d7a52310e4474e57c23c065d74a0e96fd0889ab95640993272c20da3393c57c601f28b6583d2d2019588c0b0c4b3013a6bc8ce0c98ca6684546352223435aae17cc5553115d2a5ab86a2b2f4ab86a2c2f2c609171821e19a49720a49697222d2e2e5fb8bcbc2c018ab166b82036b55a0d06186ab51a0cb55ad76ee8b9e20c1108975ddb8270415a2f12e6aa05a19b157366d0f801975ddb585b4f107f202efef8b86a4d83cb56803b554c1f2b9e96cb99e0a1d7a908c4a5552481450190f7b88fff0099d96d0a72e3987057c401f4015d8e2a2d10726da6566895d68805b2c0d5d111124fe24b9864184d919f2cf6057d40b9c702a16be8d79faeed454d1bac7b41a2f380bceaf43e972eee0782e08b13b8b446df4f0becb1e91afaff81611013194dad80e49b25f968fe46b916a6bcd4c81415980a164d70f14a112b2c2fae55cc0fc922e3c50b1991a352a49ba6ec33121ba212e2a7202cdaa2ae57cf68453248620fa9258a1697971a5c5e4416ec4524127f6658a3939135b24647ee10f88279c10a26a606b8b446406c16415694b2688bba4a6b9445d7d018d90847e0d21a7d304795e0ebe371b087e4f3fd64990fc86657e08ba66016889a6c5006213004f7e28498361c41c1777a0e4e8540abe8c312c123f6f8fcd84dec61e35e84b8fbd2c3f5702d47957d850f161d70d37c9d230aa5468424c992192341386cf06cb00a58c0454db5a341e78941b8fc7ba1e2035f78e0c3a25d33c150dc61492c269bd16a2d5798f2c2452c45450ca2826fc02b3eacb0882b88bd0f8b0c17306246b69f214282e8c8202921b5bc18697111797801c24b2c82a883195d04e5c5bd7286b817cccb106c84628070d9453f8dc551cb6581aea931608e6c841460ee95fd539747ecf18115713f3fdc8b83d1548fc0013d600726f3c6e6d9c1556e31778f9bd9ff79c8425b5affc54cbdc2a4b0bdbf8f305ba529f83cab208123c049a0fb1b43f73706eebb124c8edb8d97c2e4d8befbedbbb17447ae347de63084c990232c26469ce0903f150e80abbc7dc9738b0a705965333663b5fdab8ca6eea55446ca9e8e25ae323ade6da7a9e97e7f95b5e0e5fdf2bb245ddc58cec880bf8d654c4ee1663296bd041c63beb1a9a99c3dbe7d3993cbd993fdbfd90c2a51a2448972e37180856d2cc9b1145f2c541a35f907a58c3c2aacf6b8e971c3425313f8336441277c1bff4a60e952d3078e9df2608f9b07471ee0a78c97e648f96fe4f1d520587657288179e37d87119837e14852930d35f58325981cb7c7cd5782c9d1bdf7dec8c3fbee7bdc7c9babb4b16a542abb74a91135f9df8bababbaa851396346345567d4e4b426e217654f52239aea1b5ac32ba35c5bd9bfca4851f0eb70825ce2eca3133c62e1f3bfd93d690a7d9814c652cc3d328909fc57e4126799b2dad1092e9fda803db65a65ffc1cc8855a6a58debb1b94a185fcb945c2bfb7f3d593e451d9214d1ed45531870d37c1e1c4d419f3062027b02d4508278623db2e9331d0723269e62409be6f380e399024aa6fdc4934c9f462fc9f34b96502e679e74089793c523f31cf175a4a84f6020b5e60cd9982ce86848ccc88ead7bd1c96e234f3a526caf1c9da11927feac603aa80b989ae67de941ecaa60c8d980fbacd3d1025c4e56fb9c9d270ae1e50c7d26c0e564bd669f8e146017b89cac17188a40709030c505292a4e5e40602420aa828540d17ac22b55d41596196020243e83c88d74434819161932c0e564b5a0439728e9d4d82dc4f9a801c3004db20f408b1c1464e40c91b098a570b869c268ea446343325027db661b2d7b29931f49478a961715c01cc5a36b6cd778d798dfe2e212c5e5451ce2457c09429cb1037d4affcea0c1030d181faec7683ead5694671bad32c7caa919374da225d9936447628328c3c418e172b28c4c32169333348208039d1100f07202009020968084e7c1e564c9ae9be6e320395fb20297936544262b8d338c39432588ab958c68564fd434df2f877556eed832b2bf4c0f81cb594e1e998cc853dc3bc9635cc667268fa3e873a5930cf7e4dfb1a9c96fc41a70395933948e0e9aba013041071368bd4fc2d035e8e7cc1b75dc40d3d0d41d7538c9948e4d4d02d0f1f2041b71147d0ac2484099c604653a81a82c97e4d7260440a684b1f42400608418181a33c4175b05c0b52e20c666755ca069fcb751c7075387862b246673af789a62114266475fc8b869ce7c60dea0abd9a6f9dd5dc9f3e7dbd0d44915b319673823575281fbfb2921cea55b91134734b64a8d8a6a765430604c4c88633fa6fa91ed079e2b1e4e4e1ed5bc010211ec70e20866fb2d045f237831a7c7d5a9c241001bf7fb4fa6be9163d2b86863d2f8077a571b703337b2f055e5b8aa46627337ee740dff2ada10c7b299d8a8b5daadb3d66edcddb60b76a3eaf7a94b0180efadccf04a6da76ae4366ec6dd6fb54e0a311098867260f7dcf61b57ef56b7a6b93bb83005cf750bc10368aecf715da5dc0782e0e7dcbc170cc350050a012e736357ef0427ca7eff4f1ad9bf9c2995ed1be2a4f14f0153421cfb2973146318025ceec6d935eef5becfe53ba78e169cf457cd1b13681aa740d770313b1d77b00ccff7be0e8627c60a0ce61622f2440c11f6720b1121c24324080a22455c41e445c577af285654ad140527a9f738e94f3abdc72612e23815c57bfd467fe9eeee3793a63f778d7e777f6bad1d4b73e2275c65c8e35bc98afd953f95eea2febef3b10ce7f962cc4935448b99654d947ba6a38d6c70449e470d65f2cc164d2ea74b091e324c489edfe50c8ac99610cae59c65da4cf27d39974ca33c7fd6e617b99c477912b12197becab9749e3cbf749697de53ba4f9edfe17749ca61b804153944400ec3c7b111e4f0c5d8921c3e18e284af72854fec06c4df7749cadf4794bf4f09a3fc2d9144fe94f8c9df7b21cee7022321ab1bcffb2ec4f13a1b10c1e9ea103132f75b88c335ea71b66a6f904b52b6310ac8d55134d9da1ad8906d0d90642b342448b649d0c8dc9de28680c3eba682b99dc8654ca64632a5482c91291251321523d3ee54005af04488d58dbf72383f14d4f0c316f1a01901b2bb0c212fc1238e88228895fdc9638f0641e08e907534f02287ade81eb1eab145dc113d37d41c245adb1125c8810619d9235e2ccf49e2558f80b1664e0d58d4d4f708201b72825cd4d437b4c157b1c32c08163f76e862842cf0a5618413e83003fce516e2c18798283ca4e08130985b88072372c0430d76e0c188900f4868b4327d9727b0d7a4535626d3f79878b5ae99df5f8ad58e9e918a4a2eedcc7a4634659168137d6a6544c89448a69e8b9a68984b9801bb5ca30fb04cd7e8a73f0201979e51f6b4e8547b465d3333a56064fa5e934c69b4c4a0b76bd4fc3f63c64a1700eebb36d56f9cfb325dc3e57e6c8469ee6da3dfa4dff67d0df0fce98c00f27cb0d61b01689af99c0c9d65e905326fde58830315e66fe6c6db19b7f460d5e9d4311bfbf2376f869e748dce37435d344d8bd3888b7c7333d4a4c9be19f2a2693acffc51b0029d6cb45d3d99fb71d37eeba3699e63697df0e4d945b96531d509a1f7d71f78c16a3ba5e491374042589af973309c2a741ec89c03e6fdb014fecd617942a9ca5d80ecbffdcca4f1f79ff917c5b1bc9e67e68d15268dbf3b065a47d7f03fd1940bbdc2bce19dcb26f23a4f03738e30297ceefe73f7e51c77749cf41c3748e124cfe6babfb9bb639f4a369a9c4f57de903ae704593e70ced1fb597ae1946d8aca4af36ca599a46bcc5f29f128cd55e781960568fbba7d3b39ca6abd15638c31a6791b4b1e1b7040e7ee1a0db019fff4d135ee94fee09889bf863843c47c0d71708bd2167e8b639fc67ca53e4e564b5b4ed64f29824b6779cb51e504ca8ea28ef27107e2a9bef9c9d58f322d95b4c751d5513c8eaa4f598eb22f245b92e9ca515d7f0229c9f5b5080223043fec23d78f29bdc0e8b295cc2172fd2e9a365230e5fa3959abce474386374a4f3276a6824eb268caad6853fd1fe42d72fd12bb17b93a943904123c7a92ae51bfa588b75c8efa71d48b06912161f90bb48f9f32cc1b3456bebe8e0623d76e926b6b91ebdbf094b3f0c7bccad78ff114ccd7fae4f7b591944dd44668cc31f27c3b8b9027108ba6c41963298af365ac2e63f92d632992c612cb7056aeffe1ac88e2bd260075f702d5c7a5c972727e4c09a6d49e5282cd5ee6de38adc8d347fd4b45999916bac178c224d7ef5e92abad61e7efd8e1e47c0af4e2337af881a36017554053f4050847d9a24a6bf51b91066c69767a78c3b6d9ad68ba1461ba11aeb2a9895122e099755a5dc2a8d607bbc54045391722709bb5d984ecec4281cb2e2a2a2ad2f164d117b50d70d945508a2cb5aed54a1240399fc2a89aa2112ec6c02909eacb0fa675192365b1489e54767bf9e1744fc07f000f738f025c4ed6aba84aa52a55dc2da239388557f513d9bf64e95459a89a76bb4d0612600b5268600291062b5a793ed6e1333de6a41b71d2c589ece6781549fd52d3e012ee16b3580c7c03b3229bc47c75df2509140483a6a14f6590a90dca77bc2f7f1de1d2c63c9aa34a1b94e9db204a65311b2bf2a8d8620b994c367bc1c0c0a585bd602fd90b082693c15e331aed038a0979ad8bde01b9ddb05f9e7080dcbf95ac8dcd1b76b45a340d7d3a0a4188b4341a0db3a5750d2a237292084d96ba9f0fa5a4cc1279eafee5c2fa75abf6c74d5ea3d12c4d59206ef2777f1248e5c9f77d5fede80897f6a87654fb6adbeae8fb8e6a9fe785e7454b4b4b8be77d37e0d2f33cafc5f3bc9616cf6bc9220bfbfaba567b2fc6b6464df4bbb7b21a4d591835d1c737569380df74a37d95fc467c6116edeb711475155923da952ec76d1cb76d5b589f86d6e53aaad9977d595babb5b5e6fa5aa110168bd58ac9bac0e5268bc962acd84cc662c9b658d87a71bdbc9ce0f29b7db34ff6d1bed98bebc5f5c95e5e9ee779d686814b6badf5acb59e67ad071ad580829ee0720b020a02028a058146a051d006548382496e21285c9984dc42370023639a7217cd42384822840324300f42c49193ee240f5783b280ecef412e73224ff1dc1814483ce5b272208e6a21295634e537feb42bcb5165adb55b6b1de2d42227a7a840314755214146668e82a96305922b0c85a1b47f1cd5425040c90bc8b42b8e53da0d824eb607d55a69e98553a65f4b42e89020b057cb6b9e5203092402c33cc8512e4779ae3097a33afb8fa35ef5e5418038e935c868c58b2797ee82cd317059817a49ae41c904c82db4c551fe265885b805c328787b1b15479bea97a24cae33b932e1ae1256531c7fc0d85ace75f4a2dd6d2ddd364b9b369deeca2c537c47e06af5f5e0725b657ab4b24747acd577f41dadec11b80a5de18b0b5c6eb36db6c968b3d015ba669b2c7cc97229b7901028b9c81392d00c68b984dc4242a8c8e50b4e807202720b398965db8ac2486ea12884e420b7d02c89556129519b11e5169a0de91be416c20193bcb528fdcd35e44314b4c46a88670641b9dc5a3300da5e4d0320b9ac4c327d5a52dbf2d95a3e99fee6b301c9344a2eed4f15bd108c197192facc494abfc4292fe3299e226c7b7f5b8ba6f0d86e029f8e30dd685b4eb6ba080988c85d056f88038e4dff2b5df086b98bb48672b7b5485d3762ec8510982b1f7953d9ac6dc92177911e7cc836661345999f91bb484b8b6cc7727b653a53492e5461ae56991b47ae6f6aa966283db072b5f19424c64aee2223b821d711c7c804bf00975bcbb6b6168d5159a65f6e2c9f852b2b405176c07e2e6b2d53fafe26c0a5cf6a751c22e5e9fb38496528a27ecae65289e1726b656ac3146eb63938110425790b716cd7a8e50bf9055246e224fd0aa329aaa44df4b5006a1244c1c8f44b5a24bb61b643a6b4d6225ad43594a0cf819a4b2fea9a6d8894f71245e2a994a74f678eaa4f3fa5647ddc29a551db2279813f975b8b875c6eaf726bd1184d6d5456a3bfad688a5b5153add916cc112e67c762798ad9dacdb5b56ccbb626e85ceb7317259c6ac2e66c02c9266c523a5f5a804b95cab6706bf2e01279703961aa20b23f1744d7a864484c57cf1f680b971316032283c16030123661b00963a5268f9b56b4c727af58a917dcb44101220b3213628960b44cc7d2fe54ee05cee69c74d65aab0ab98e4dbbbb7bb64f2a8a2c2982e4456925b32dcd6cc30778d98ef42b054d33acb4d64a29755add12c16b2170aab5dbc671e0bdddddddfcaef3be0f04c31a5e3a670a8893e23581fb555a9627510649250d2170d36f65c593e112cb904122b5b4b8b4b8b8b89064b0b8b8ac60171717159794d0c5c5a54997c6970369087bb1f7624e4e52c64deea64cc777f2c30137110e7872f95e11786485ad23e775ce6e9ce5ec66edb6826b6fb84f9f34d676736eadb59674d25a4b27383e81e79341f0109e5fad9d131b61376fab96dbaae58a783f9cb55c1375b35c13b5daadb3d66edcdd38cf36c16ddce68921f8d5ba719e6da27676fbe97e304df7b5721cb7d926ea9c4f27ddeac8d10f04c1cd36f179b59b28729cddb8fa75b34d78d56ed46e958e6211afe39edeceeb386eb31410779aa085dd394c181fe5eef7c05da53b4f2ed781fbaa1c2b2b2b5506778f743fb89d0e386d5e1e104c1f950856585858647424193264906e0b89446aa974879afc55de07cecf7b00f7f75942152c905f034e1cb558ee1a15d5ec6c5425f3333242f207698049d33533c439b5ace0164b63d2f8b7841e90264beeb7500511642ed76fe19e7e4ba8c29602f7f441ae256cc08b48ed46493368d0a081315671e26886db4a8d8a541f0d189810873e4c0d64fdbf5f304124259733425c5c081921c31746ad1bea3d990a311194297df194995001132e253f3f3f400001840ccaceda3f6cff8811c11f7fc32afb7dbc0318b07c5f96b173a24dfe2b515c4c4556199564ffaed6c950a5f539b2adaee1de11695477ac33726b5d73df8fa87f7de2a8fa9815fad726b5c8fb57226db7aee1deab935c8db2886f8873ca9734fc94e740103c4165e5ef77f7fdbd3276326aeac68ec9c417ccde3d71d2bf497fdac9684aec8ec9bc21529a4bdc31d9be33dd70773476b5ee4957f3c031c65263f87e8de1efff45b18bd11afb66792b4d232e329efe2cd9bfce64f58ede2af450a6e2df854be0f262c051fe2e740d7dcfadd335481ecbcb097972b3e79c73da6de356ee1c4ff544533adae4d7167160e6f2d2ba594a6ba5a5e9d74dfe9d32537d73a373690200b2df942ef9cb46d5918edea103eadf5c2f105d04e3398cf8e290cb27e204b99cb921972a2dca36928f7491cb4e92a90e3d55b05c61c4200835b741e8480f38175c3184c805434ab618e32695032eac98e2e7872282b8e9f1230a22782812044b06d90d2dca9844e6163a924466c92d7404964bfc4356b1c7ab2eb239609ce07efb3bbf7d39df8e3aa51d276bef386a6e73ce3996dbefe894723859673b736b9d9d6a6dc03ab57ef5af45f5075793188d3dee1869f63cc15d676b3e1d27cbc9d99aabf9bdc3c9499bb4499b3b73b6266bb27a1cd5f239592d67dd8bb128fe4f166dba2ca602ca03a60b3c5f70d2fbe9b8c34987a1efed96e5ac4a3b664f6fba63ae6070d27dc03bbc7b52a74e9d3ab545cc707cd7e8696d2d810aafa07d7a5a5b65f0018515889e4cfab35afb755a5b79eccc069f9a09a64f9f3e7d5277ea3a40a18b39cef0988ad1b19e3f847d3bced98293736211e7d8883d3149c4628eceecf445750a1c24233ce7580215761cbae842c74e0bb41674e8d8d9d16199d85a145c366d5b59a31f1b8bfec6dab69eadb5b1b6160db86c1a6d031af2f9379be02fb7737f3b95f2772cfbbdd58eaec4e3a8bf1b7938b972548fa9efbbb1ec71ae76ec38d1d4bc09ff7bef03598a806714e194c76f717478be0f6b1deec870e1ce2a0f7ab89452828940ca7fcf42ca7f630c45c4703fe5fb708c011c7b545113d942e9fdfccec727bd9f3a7648ef743a9d3caf44becc77ff32a4f76437ea7092ae306d7a298db960bd7e83c0aa0942bed6bbb2021114145cacdc886db5d5fefc5aabadb65a4ec4b56e5f6bb55cad5b3de56a6b137cc4c662aa1a6c666c5436aa136973efbd362a1c2a150e1b95cd89075825dad812b4dad8e91aef9b1413eb264db4682594b68caa26a0a25e577ac149fbb4e48293f6bd840127c11bb3d91ed0f0515a5d84db54ea7b5291a727450a43aedf29b4c97e14d92aa1f6cb53b6d3472ca76405f66f51256622e6115110b19863238ad83149c4624e157f9aa753d7504b2291c0ed55d464ed2720972a27ec974dc47e99806c634ee8d404dab4033304acc2e126fb5d2a02f69f3acc59cc0cb9bf3a7366cea15f470dd09fa33b75ead49f8ea5fd1c1bf78a82a3e6cf7628b8ec98cdbd188be23fa61636abe198b151d9a84ea4cdbdf7daa870a854386c543627172e55e4bd188be27f97a7a13172bd8d451ff141ec08911379521b39fe74b47132a7df7f5439d99fe38e6b29a76443df06859c6a93d3745439397d601ba12122e4f2c1bfe316b8bc79f2ec202787a64aa6bc03f2656448f29ffc8fa9c87f0cb8a0c305162e5b9643534dafac041f5dfbf1b50c89a6cbdfd35404a8689a4c7f9855d04b61fe4a9ceff6b75d2787bf06fced9863c7513ec26c9ff23d6eecdf4ec16c234c04bc0f3f87f7e16f1f8e3c8ad8c61cdda78c3c52be7b1ee17bdfe3c68ea58f3b3a3877d6094bdf570a1d10661f6728c13184df37317c6fbfec59df84251e45b0409308c16775f33df8df0c6174c09740df806353130b3489107a8872f38d2e5013fdae74f375a5bee91ed9c7161bf01c4514444795fed345d7c07cb7b94f67df9238caf0e7d691b9c78e9a3da9486bc065cf5a797e975be8091ba818f5daa2cdaf0e5e227287703b06b843161ce086c13765668081491623dcad6204ee906526e32f23417e8cfc58c5303ed56f2ef0cce965485ee0f2b148f202bfcccc49e5289721f7e3f8ffff07ff7281cb5b899e90c228b717b99f88a287fe2c92208c4c9f74db9c6c4cff77824f381cf5ba691fb97f85aee9aeb5de3b96e2a94dfd5e6e23403ff21c4bdc3f9fe012f7ac069be1a8c42c9aea69ebd48c00200049001316000028100806c562c15816a5b1a67b14000f6c883e64483a9987233910c32808622086c100c3184200309a006394a9a11e679e82f972111765844447f297cfbf8eeaf20beb3a636dadc7291082ec5ca3078487d5234f68f6fc0805e34b84ed51d1a3ce7842464aa59986700432f08561eb590755bd1799a0b65a7a307d311a62dda246d81e5278934c5e9231fc13c045c3b7bbd9dceaf1104a3d7bda274591cba5cd32799136772897790db01f1e46b4222f27c942714d14ae9ad80e5eedebfabab263691e0aca30ff0b1b7a46c831a9a24e378d00e1a80af4cf88e93d121c7d6866629429a1eddd0d2417b9cf794f89b811440a18af94862a29301db4409148e145a9f294b24b033f18bcb6268d5080881e0a0019f2c9a145c82b03dbe7eb314c4b3d99235396276eb174e91082295de3e36b499de9215109186ce9932ce7beef28db0ca9a24460a2378989ec6a29a41450f4cc0f49eabb078a46ab3791bfb18910a91d31698a4688548dfce9026a3a0ccce13602b1af83ba0c238a30d04962c674a6166da0d7909dc501cb249cab5c545ac5c9ce4893794a29aa354f496471780bc5860e46270e50b06b306672970f17cb63162da6a0143824c274cf8e47d9e6deeaa448569426ba839e8bd693c2fc3e2970e4d05c732745a61b91bc9d93a24cbd49815d3d17801c4be189f41dd9bd905408187e5ecc8e84a9a6701d09d7349d23b144ecf8ec3930b91d4cb5245591168930b5258a7ba448a4df0e905713fba66e6e07a43cd84167de0063cab55a64f61c186c3210522484387c10b1902fdd5c665d0b137dc53ff1931062238b0979cf912b8980233bbac8461f765f3718372d0e871c640008c1bf0515580b6d411059905910e2e7358685603ddbce31cfe9c662cc5317dcfea29d9e6dcf39a1db3b8553861d77502ee29872c7d52f615f0c1ee043d087b6f3cd533500db196299074e79923be82d72b950d3dd61f8d804903422938b68d799f9f305cdd645056207060aa9d3a11f3f9e4b138e946d75e2154be9731e8a28b1e72b5203a0984b539d2bd2c7844d099c93473b616e8ffa30e6f2e4522e89c86e8b2dec7d0091f421f77a80ae475c2be36a50164d32ca2394eadaaf6a129c25332f6dba39b10f50b72843aa0fca87546317a254633f34b0bcc44e0b9b3545544ed9cf1b8e867b3608953ae777afb3fbf0a6840eed269362fb8fb611f8535f59005f641d81e800ced37f722ab4627c3515ac8f91a13503e9ff8037826cedf2457abb0f438578f59e784d1e3efe9fd9d8bb8f617e44a12ac2eecc4b496bb0eeacb6a92280723def6b3db8594edf82ae1e10a8a3e6475a882fefe6e2590cb60d4d3b2c841e3f5ef717f9526ea51c8eaae90c659e0ba7784db9281b628b8ce5e297a32971aa49a9572f7a7b4d19e1f6b2568a0cd2dc3ab143474957fe8869530aec72bb7b4dc5ad290a6e821423bd0c507e8a1ae7dd1dbb4e4eddac85adc203350f9187d3517c5236190539f525e8662fbcf7e4a663e15f1d940de4017b56427c8bf8984aa0a45f3e7219dd94d20debc367e4b319caf19ee0c0f64befced88c8b30eee123ddc5a5f173c3922ef77a98f83dc30fbc4c6e5e3eabf3c819368e468fab3d879c7d6b91c9a7290c7a5cbc15af963cd85462a81402702b009f744552d280dcf0d4f6ba6b0c2455246ec89198fb072f1f5c20e1607b1d5cae89aad6b4ab2de079a01a5843003d4ca971a96113e046c8ade7987350737bfbcaf0e6e4b02901f69caea5990f070d3969586d6dbe086dc679c566e6870c9e92b968cc92ba0c42d9ca0ed405ae8512d6853ca216aafb0106c30ff63f8656851aec0e086a52275e49dce69c42d274fed00a9b4b7c8145face7c44a76ff0c15a183ad9081a33382726a24d2b3ae8f72064509e2f49ca7b1545568c510f3bab5f662341ea4a27dfa7a65ac178af41563b16a6f32dee052a01fea8b757bb0fecd88dd1cd0e23aac64200fd689e3193c44405d4fe384ecb68d783460d373bef539eafde0f021f7cc838f10d3903416a0737466b4cd91cdd5d46000e320dc735da777067e7a9cd1a08729ec831dd4a514f056c0a4ee24ac67181cfc3d0a159f186361324538763ae591bf6a4901ffb0e4cdc2dc85bb667b3eb446b0c04e2f89adadda66a56e03c12fb4abe3ab5c349caff53315498a38ac34cb7404c827fca0cb169f594e104409cc39f8bdbb81ceab2b39f6208464b174622888d29a2a59fbff22c2e1bd89fd940e76cffdf9bf5c96519b8e582c408835ebf0fe6d2689bbd32c3670e24c8dc52ac9775b6135efe61508789516079945181ba0606cd26734a1c98596e424d7da5245dd934f51656ef664ac01ebff5ec7104d5c40db9b7506238ad506ff5a994579c3353e72fd1739b7ed2f5904cd9183f51401b05e8fc165e801e7b72b561ed03551538df8f9f3083e83b9e4a92420341c5370fe29badeae2507c644c6e7258a819346e0be02ae0e160d31dbc3d9570e7e8d734c52dbbab5e9154dd348df81fc5a5a8c2b8ed8fc4641c3627f1c8097aa821e617a8d3e5f94b742892b1316467207947f0292522df20a7bcc7785cddd67118cc4140c28b006807282f451b065fc03fa48d528ea4b1ac2dd78afff4c1a6d155978dc219e736155d6cd3769eecac5d04683429cc1fb9a6f86a6da7c36a22b6158c1660b472caeb419829352b422cfcfaf53b049bf748a18c88e7abe293caa18ae0c58c4ac42b57553b549f06ce79a73318133ce33d11d71caf72f9933f063cdf35e1413b4a483892b06aca112a5b226079c7db60dec0eb1d0fd5873bf4c6b0663d43b8d24d3f1c0181b43246d2ec65dd985371548f371d0ec3a5ffdd06d02a95ae33e0f8fab61c1d97225c8f3ed309ee09d981541d71c462732145a3c6c7ca4e778fc0a50478f9f53437c6760ecc30ad6e2e156a0a250137c68a686804ca4e54de7a0ad329c0096accd83b349e512be862df5a95adf0504b5004d42a522b1a56f00d69fe72abd8df71707ec5584f2a51f9ad1a731afb649a77962ded9d7f3c876210c9f5607a0046c192aa2338142ec0d16581c5a9f0b9ef93af0e5058df663d1d20730c50ee7cd6b3efa1caa79431b7b43e0e12941382180b5b4fb45cf0963a92c2f020d88bdae3e214b4f038add727f13bbacf611b26b2795dcdb9638ee093373677ec4bc487701fc20e61f5ef92b5bf22add85f3d74ff9590360ffeb196c5e00c93d2e62b0d221c278334152c168640e759ab1b3e11c70934e322d0de30ca01090a2f6511216e10cbf611ccd1704eb883cc9eb67c2da92385ff025b3c827999f36e5acaa1d46a4e20891987642843256c8f11d644dbaf8f0883963afb41ac9082c71eceed615676b75772e2688ef468317c02429d3fcc44a67eebdde1f603020ae1cccbbd32d04af864e05eb03838a6f05e24a53eea03f20f12441e9009752ce466192e8314364d3f352efa8e53d4e68a665ba4fea34a31610ef7ae3e69afa8a32d5a0c5a218847f20df900118247a92ff878079d372920006af437f243826cb879c5401ad880dfeea032ddbd05c3f04b585c240c71367036a08660764a705d81791cd39bec18012ddd27496876b307bff314826fceab590d4cfe69d04df97295cc600e57fddce0bdbcc1d422e57e7fb6a0a3201c8fd664115f80624236dbd3e69b29a2007d2dff6a252f5c270e022a46bcdf3f5f190427cba1e183ed7b9332994932acf389bb5935813d6a1a2535be3ff002ee7377afb77d6071c5e11039c355cdad6254cb01dbb7af73dc85134a0e64734af01f35fecbac84c4e730b9fbee72b87aeaf26464c997cd85b6fcdc9570aa751b089439b6e99e0c964f5aa5398d2bb5ef976898eb6c05d3d38c2f72d6b03ac69d9c7328afb3730bfcbdce04239a228f6850b1d56cecdd40d5b57c5dd7060277e6ebed8ce78c9ed5b8cd1c05e160b79a00d12f9e7d59af386d37084176f7fa13a6ee0ce3fb5f8de8537fa6be4f480bb427a6fd380f72a1d0dbb9be5858aec8ddca4a2037b49003417948db297b58226caa397a76ad32bd0d51d3544fe5121e51159ea91175bf93caaa478fcdf7656f3261a2f6f93edcbd4830db3ba43ee278fafa37dd680b76cbde069baef1e7e33d449bdeabdf3d14af3f00a9d878861a2900afef4506ab02bb5e3a86d7e9ed5c59a3400d0e851ed56cea9b5efc81231bcc6f1a9090dee68adda7eb0a31844940a90caf44946069429074017762d8c658e31c07db8e4e1670006e406dde710d06251c9a06075116f35802d06d6471c946209e9ee2fe9cd03030517ef84e2e07ea834b1cc39486d83e79f9c0282fffe41b2e7ca22c551756d39d3418347a9307ee0f9829179af8db9e204ee2c72a487e0f0e6dfa10b1f923bff0babb5bfe564c709ef985f40d5eaed973e16cdba0827fc90bac89c94016907751bbbbac72ea1769a28104bdba36e10871b6c425ba9b581299c311ce3f83aa10130f67898f063019ab106a9aee2221cd1e37edab1fc7e7999501e455e2f454ad1a73776af626b39d1e7b5636189a39c8f22ea3a76f2b4ba8c4664645f3109d1eeb5ee7b61cfad9643070fbcfcfb6bdfce91d01b7531ef9a3a0d13034bc979082998801c72608d96d0a8f45cd0edbd63b81f15318f3cd126cbf97ca88f97981f5d3c9dca4b97f7183206a4d931419eb44ba82853cba44c6c03a4b81425bef20c6c48e1f969534fd6d5ce06b3b2004626a59c5da32c33de825f6ecb98bd83c0c7eed673de06608b26d046c1040cd2b4349af9d65efbac23ff96402828c64ef4bdcb18bd3154acf23c00bcdafd34f771d20aa0c5156f2d4f6ebaa19de70c4f1a592e55d348baf6bc4ce5494daa538a7dd45580f56d6a8a603acd180eb63a4b32807a683e2dfb3894870e5309f3860da5c4b276002fe86b7d80176750181e41a7820d0eacfe40fde9d64f9fcb120bcc312e0552b8953082f5034e5608209d31066b0af71a304fb69220e4a405e535720a8f6f7395a6df48a84f62a379de31e1cd787023563ce600b124b4269b7c2bcaefcd1967b555b5cbaeb0627e17a832dac937af67a8f17927a6a28e177339382579107fa2c93047bca43ef340229a3e1b020ecef68d984eedabab330ab48a73968ab920e2c411d0a19ac096b5300fd386f3dbd3fe5b48206d4b65fe17de80db2f0432d29deed5f1d06a7f29eabd14132f52418e60756f1895069cfbd66d8750f496cf77752aa091727710fd635ba266e8f6b216a90d41a635d8c111fdd55792f02d09df9ead0efb8a6c03763f7092aaabbe9ee6c8fa800bc19ef548d8c51091352932f0953368c26672bb1a5cde165f73eee1f65f3e5d27468624d22469ed77b56bda82595c346ebb44a0f1089b2273b660afb35a233462f50eca9ca512736a5e9a0cb367f683593b4ba5057766ae74a4b9f7ec4084dd5527dd0d48a01ab03c99240ffc4e5394ec390ca6a54dae6d35ed57603357bd5236f9439e3cc0be39f5add6e4ab745199d9754e2912712e28ea935c66cb03fb0064bfd3b481cfc3b614410ed76bd44373c35827fcd991a2b1ca492550c8173c0f887a92ed42d448e3dddfd873985aad7aeae9d08debc94e9a07dc651204a6d9d394fca570d5f19488b07b0842c566590ec015e134dead8447433ff8108ae5e87affb749d66439579ac8ba6ec096b9e2f8c544d1868991b90fd8e99617b1fcd608ca3e4320a9c41faf97980b585825d9aa9ce4526c2cc99eee241b3a0efb90ab5da3a9c727d774a6e734dcfea06d01dec636d1f6815566aa43d8466159d32ec1194c3a407e00e8647b76b96f074c6e239b11d391ea8d1844f22510b56004a66b77fde19d57050b2c2ff40b106aafa63b053f022c803daf0b0f71ee1a1cefa07d0fca1b18e425b5f159a0531933196994d121c0dc4ccc855e7e15399e47e9db5eadb68d6a0640fcb20ab751fa8a4122d654a081b9867a1f13d5d94a3aae656823ca1b5f673cd0ad7b9536116cc63f6702d1fa8a13e2d1f2403f33351f8b5987c1447fd8cd76142d8ba08c766e29c0b24da92d3b2c05b36742d629afd70c0cf412d8937649589a0dbbd852119fe3e7e2c669121ae7bfb3b24b8bd1a1f883da822d7fd80ae4f6933a99a62fd8f1ddedce7d14d5957d3de82a65866525a7d0fe8d697683af8787c98f5f7caf7016a25acb1c96c0a8bf02453bf75d78b04b41e43df3d588261911173ec70c15bda394f0107eeb9a549c52c27bbf7dd264bddd89b8987c4d05937d4260293d80a2beecfbd7fe2236131d7b2689c6ca0e09de14089a0b24ae99b0d4533fe35fb4229a77d2501106c3d83cf3f6e7e0fe0f43c00eff7203038719d609d71a30e5df156638674bb2818b11958c5074a91fc8ee13ec83e6bbaa87969f774c88d70f4f3825034ae935bd3b864fc90ec03eb0a3613171b52cf0a20c62abd5d30c6eb114bc4df5da9986b3c615cc79c982a7c312b2ab55d79f05295f4f52fe7d403f908643012f79332cbab69d7f6e4a159ff1fdd34e57f156dc859b2f9cf3729de957c1c483cdecd418ae97e34acbf3433e226388daf07d9c17cace4570c1325495386199d26efe09acf117b5169d88b885a79ea08a2c9b9cb39d7a7045384a49b174f3b97485472f69304c8f5101fbc81d9afb1afbba65b3eaadf4f31b972b656cd50d15640c034dba70e87b4d109cdb3f140d7a24b7be197bcdbc43e49f5cf9dc29dd6928b98ac1f4cf3addb2880106ee8ba396b1c85e5e35719b0a6d31af93cafcb36ee00ac87db5c19e59eeef77062be5863041f2a1439e7d040f915bebd8fe70381c82947e8fbd0a56371477ba9e86c422a576eba39ac6c39f67573d5c366bd70666e6a1f2786e64bb188ad5113dc16785afc8f2a31373bf1869f0ade7ed40e53999e56c09f7c9e0e0c54528e17f731827f7a617602ec2e4c5af91c8ec1545a2c91d49fa6298998650089c511a821cda0bcd2e3e26b57440bfb4a674ec404affdc4c1db3c8fdd9e55b5c6add2380c93711191464a5bda987d981a5cd89746767626497ef0293a08089871563bfc2c29044a506b4cf4770d9166a7b94696cb69a7417f9266529b62fa7c20b2e6f351495f324a97048d18665a7ae53fed2544def6f1f88ec2c0e5a7e09231090c983efc39d1a38f07fc5806851f8ffe19311998b92f5212fce4b9b1a8cd891e3b3e8835bdc156c5d54ea4053805de36716ec00f969f399e20115a1589b88c1d0c08715ec70b48b1bb83942fb4c106172c8290154477ebd129b2cea86344279d165b8213add6f234efb2c56183729697e7ee6c050cda13129a0406c2c3bbe5cfce02c44272e636ff7e151f450f523aefbafcc1b799d0199b70eeee05dff8854e40c7b4c9cd59931cbf35df21b2bf142437c7e9cccaba63743371dfe8b31b4be1174d25f50c7fd2a5db21637ea7d3df6fd15c0d0d88353f36ee9743d2d6725861b2a1dc499653fb8169e1597c9b24b04edae230accf47c40721682b2ea146cfd7e0fd3b9984574ce3dfe60c6e446dbb7593efa722f58e263453be0ad9705b0370de4401589292bc43220f0a05935f9c17327c63d100540575b6ad558c4b4a3463a2d6a9e09426aa5606262b551c8349fc8c74937b3d10eb0a14d627443488d2ede0fcaa01b6658f822bea820814c0391bd097035402ae77a8a94d1164a88a912c950c3632348d716739e0c6deee792912a04a10b485b30216dce47f3cb232b6071fc510cea232dcac18b11867a2182335fc56d7a03f4d56f6e783d33a73933b12d9722e42888554414e71d7270ce76c53e6fa20406b7fbe6a22a32c1900a8bb5144b90288d806d792a51b3f489e5248c0ca8be9cc40c142e0f631533b3273b31f0146d7501b377bce98b94ed8fb0b156b97e62b30924292c0be958c91010334b222f0354a5b55c7a4c727146f10df3d5ca3a000982b34587127f7dc01e2b7d57ac3c8efd09fff56b370933a2e7c9877c0ab7d46fd258d129f1f859229d5bec808e177a147e4932bd9bb7670d7bce8254c394027d7f3d22c055ec671954a977d31f877e2e485b850b7010ae83426ba00689a8bdf7bdbf913d3cca2a3811fc74d562bfc17085747c124e092bde589ae142581f30d557b09b1104e77025bcbd4528aaa4a1f466316eee522eebdc903ecdb707a8d1f1c9f97118e687ff2cac500ce0640aba2303091875df27bfa924e1ff341074e6114b920bd9a32f280be71505176c66dc3f923ac0a04a8bc1497807323336bdc292e90bece6c4367e73c0f880d8fa475f633389f15bfbfec1dcfd173e3a889d71993dfac5e371c693de912ab3c86ed0d0d0773eda3d2ad639958c14d4bff4f66fa980555b3a99c92363d779cd017732ff3e48964d16aaa7016dcb8920fb12c9d1eca8a3911704b15751f34791fe5eb8f4810eb21843f05b4e48389ec1ea395b87523fe05921b13124110b6da2117528638e5c40a8b4513344f5ea895abc4e1f9be90845e3dcf2753f8b5b1bd5e4c263b5bd3332851d3676d662c0cac878085e84b18967e34ae9c16e394270dab3276e0f5eb806418d613c87be36302af1bf9b78e768948dc8c1e4eaa1adc6b5c1674de9a8e3b84f11d93a89ba0a33bc6bdb822f969441f69bd4a66c244651ce3c2857364cac51592bc55640f53f0abad1153eec654242e3ac65a92bb3eb376dcaebf026aa220a69fa182550a5261567371243cca1c06844bfb575bb273ec2ffec995a7c17a19dc4a002464e2fe012a938d80416fa5ea5975691102cbb50ce33529d830de9a7d0348a43010bd613ba59591639eda6b893e7e6c0a9468d14ea599723c4f24cbd01c54e74e0b227de6d2afb8a45beb2226366d962a4b674c95b15287782db07fb8962a2b30032aeff7651d8932fcf8ebc4dd1e1723d6ff0f09d440c0e50735994a4270c8028299dc1ead3e0caea632b568b93efe57a6826ee2f127789b432e4911e77841ea2b41327dd82fc19ce72828586de2dc6ddce026a086f69422e0d667a09db97c11206c827c74597b2dd526b6b9a0c0ac7ee25d4a15737333ed5c2135a01dc9e6f774c4d39a12ae6e22c07297a366f9902c07136b06f09591091329dad9461c9c556066a0f8428e2ab62fec170b2c452d7691938fcc1b592db13be0fc635b95d352bd803608eceb735c1fc211e36e49842684635b9a22b458b8515b5334c2fc51577e6d767d2c63c782ea1678f61333707d654b91f0b27f17e4ac7a8af85355c488a0222ab7342c522dbf0633b4ee14718b6c12c78a9dc56712bf60613ad78326719c055ba4ffd68660b34e74fdcedb799302e4690003e6f5dbcc4d00018c8e5b226db91da9f743a0cf4e731bbef713213d2cf35c95a736743d460eda95e483a054d2d75e2c063606f88e8056f9182f19c558258fb32d9f1888835d17495a471cc4539f7212a4ef3555a384cf3eb0d2c01440babf3e863a8040ef31e7968a216f503ed116e8ddcf83ffc080c7957bdd4d95ca43a10235a12e5d7dada53b99d3aea5161106acc423a038474074eb11502a4612f1195bab49e22dac0c3767cb03f85369fa66b924697bf7f9efe21add3da4e35e0907032c8afe34f769dbc0c6c0a493ae0870f7a7e7a8312f5b51f55be8e8f3f5ad0194868f1b80eb62a58ed31ebb49d5b3fedddb6a2f344687b8557f64dfa2d047df3a791a23299137ba0bdc3c0a7402496fda8e8e5d36cd9c7a5561f186c31d6a805b045bba444114252421553924d552403b51a31dfc5e0e51da7526379a1fe7fc90a01b7834c6bab7488ca57a8fc9ec3e46dfc41955d19c3de36fac7588923a4b339c8d9ec86f58802ebbd40f54f4505ebe7da00cff214b0fb994f80f606f336ce0c8497a41894433a1ca8b6e9219ed83f429f513ba858a1a7c437efb701db9aa74a989da1a0956cd7a4734d3f003ab9e577d93a18f5c094592371b590e060126611df9558fff21de3aca20732ae496c89a2f89a898276432f56655034d405e0618e16e1700a9ffe3c714dc1344cb8f0ecb7cfcdda73e1f4e2940abc9c1a31a1b87231528526590339cc5f591c023a9f21b3b4c1aaf6872886a5e5c67173c7837fc0b3b5aa446bd97e42d7306a39ea191a82903aa23ba2b059fb09e457d3720c7b7a73ba47a982ad221a28c54d9fc1969b67ad33bf740911cb8478ce58720b8b26da81063004f62642d0e998b4cf65237cc7b67cad6ec7f36db4eeeb7f9a28a1628e9133b4eaa5142963483b603190438db6126660a545069d990498f4ea079e1ceec14d89e197378bbd8e9052e3198fe8a6509a1ae1be3b296742e2c4df5d4a06a7e465faa20ca0a32f7339ac48b7888952a8fc49310bac4ca0574793ec61269364aee148c1210e63b3703de4194cea53ce7aa8976e9e5a38427b7fd8b1f2f7bf0f24025967ab516da205b0b3dcebc80646ae43beecae1dcc8ee54615e776de3010ff4fcccba15e860562bde705f11ac9acc36cbd1007433e5f5968f6792f6300744fae4b0367bfbb9b7c84592d301ffcccc5b2ff3968d68306071eb934c25e8f3ae40293a6bcf0501a844705269ed8be7e4771f7213ba962e8e6a70868673b3957a8b8b5a531006c5bec906af2ed1341e815f02d45ceb68d75907e5098225b659af1b7409bc29b30db837a46679ccafb4e34d0975c7bbdb5f192ec04309179300cd6711760b34f5a838ec7315ce42095b5000d8e358defafd4bb542a77dad6c1bd4e2eabe834fe9fa89765c408856d7140f3152ff7d152eb6bd7f995fee4fc1c14137b574fd290134687493f0a921c4dbc1718b42076ceb900f7d30289f444eac28b32fa205a4ddf5cb6cd1d185d46bbee87b99e3421364560652f624c3d1ab75dfe734206eb40c7a3a03c0c69921601df9c6b0f42d74d695a96b878c7a92572d42cd49ecd9d5f40d557f966dfde9d95353650599ecd5ed38bda34233a6ef3cb903ad233b6b98b9fd144cbca89ca4f8533f7f6ed44d6eeb0f27ac5f3c3b13fc421479109ff384c52acb6bd46ac4f4d46f91a4356938673800bae08aa3b0695f672f90347126a37743390dc745208b47ade62b2a5d0f02d650b3bd85032cc722c22803056a05223aa19e226806e3f65a65e47f20307f8f86c35cbecb38b9ccd8133512f1c9cab808fff6a0c664b9c186aae313fbf52588ecd1f8fe3fb93ee0e62f60c289bfd0c25918f19eaa731cff5572518822cb141fa97c22200548e9f1808d8c080e955eb023f198124eb64582bb4a1304cec414de90fed9555d2488dc7bda00261e3428a6fdf0b6e335b41d83ac414159743dc2b45f53f2a7731a83137a6abb653d6803d1f12ad365910431cc690f244acd331edd9c802ebf3483964ef0f5096320182455ce3850bc51a9ba0c8e721b149ac6480ef3f6c7c67f83166c8d89bfcb0db90e791a6be042d3a9ae5fc1080a67ec8aa827efe4d38bac908915ef2bc5be19bf9378f638d6bbc2243160cc44451ce9767ed5bc57ea416470067d67793dc3ada6f2dc1381b816331b9943aede56244aaa78472971c68bea7962e19591b13d30984b9eb38b400b2d25331221747da8e4adcbfbaab38c6d4c4695092e64ad93fac8ed8e0e379119351c8acb05f9b1a99b84e2cb4e1ea822dedfd26f1ccb31972b1b44e4c1d35db996188e5f68bb9e4f0ad947ec4d8b10ccecca58ae419c55a5223db466b90fe3c313ceaa78b7c098e0ea0b92d779583f76a03f2d2080904ce46ba68dd01e2df47eb4841d0b89b6b2ccf2d7955f31d90dae0806a0565c84c45ad259f70596e5cd2a629276a5a8aa6b7336592107d28d2c75e2aa0ef9d01d3b07afefd91a71f201378d87bc95fda97940e7cb79ceccf0821d0a4ef035a26d95071ecfe164c43f9034be36a5b07a302b4e402751e69c1b7f476828a4e936850c929b92b2aac68e5a9c7c78b70e16d362515490e6039f2e14f7e62058f4676bd4da59c89f2b55970c3c818c87e4ceee0fbfb0e381bcc5f1f3be23f777c31074c12660cb7d3156fa4166db27e2b68a4b74122686674bee6911f95e74295b3cacb037dd4962bf51af8056c3742b6a861ec8ab83b96fae5bdb274b7750729f83af4412ecd3569c81047e14663811298591d9a3de4c9b77f92892a5909102a2dc7cfbd8cc9d571054572f43e375acd30658627c19c6c46322253918e2d566da70f67a10d5cee7609cef96f4ea93291de78765b43b5bf0b66aef4092e06b901943be974d36cb23e5de4f75794ab0722ea8c2ea4c59fe4acfaca2ae543d2ffe86c55ccd611d7b81339c7f489cadeb40521cf5658899defe52c095deca336153e1038ddab448cca086ae53d4d36c61ed144bb01ddfd12f02c2fe68beeb5b55118b896896b41fd50433c63d7ec1de4136cb60ff3d253c5648da53cb11070fa8590b6113d5810445f765329a4d99ae951cf3a08e968f96846e0a6ba52b50a321b89ab217442fcc7c7a61f07d20f616da8d8f943aba0254cb78421db65f0ec81f1d8dc37d20ffff01535b69c0d0bb853d5f90a7a922570771a251f20fd836e10a6b5f5c2ccd19a0edc90c43a0359bb66fc962be6f51bb80ca04b203a0a59562aaa35e12392d638acf247a8591d29b6d2f2f69eb7924855e6c730508fb18988e36e0577fabd83bc8ef6b0db32218b37293965809e44ce11bcfb87f62b376a357a75997377e34e8663b24830887a453295b384c75987b50de670130daf0297148bc6eb4adcae42b444bfd98ac8150d7837d8e98617793e9f17da7b336dc71b08a092357e521cbb97abaf2838aa9ffd42ef911dfa3dbb5de6a3e7d2b1f46ae5c901bd5c133bdb45768fae256ce880697c215879be0f448f891886e621949986345c3d885732a86c60d627c08239a85f4bfd44974574497de4c1418c18ea4ed1b42ebc04b271d5ecdbffa9c036bedafaa8cc840e2edc4dae7ea7903e6b3ea8096765a3708042be38cfb05fa3c1250f033338b226a2428e3ff955f911d4706a474e2124d8886c35961b84a09b54137326df694f4bc4bf644b252bed1ecebc909d5876c7d596536d108fc3f923f47ead8b09a30f50cf5bf586070524f5fa1c612ea6c267b4db0172ce3508778b59687faf1f85ae28d0f6c47809a67874f53dbfa86a593afc443fa79cb9145ff094da2ec2e8138f23a367b002ca66410bb3e69a2c78f248febe1f600a5d64b9384a8f2f3f87839ecfb01a9fadcb8fb51baf26777fd145e2309ff72ab178e79bb74c5cebb91eb04a26640df80d483fd62e5d9534eda08bea4ced347cd2080f9678535a899f5e04f04f763b450c28ad95df470518c01cc9373d622d75569cd849348769eb002e256d4c497ddb316448f3c9de3de0464268490565536a9adca10af873660d9efcb721086a82ca66e3511fb603da763a5c761715bfb14eb587c6bc35ca7d09ec51e31654ce420a02acf064e521e470e1c4db35291522261d14de8cefe12585ddab81c307a8b202e63efcdb284c52c1f0ec68cb096cc6677fb902597c6f2a68a1fb5b282d0dc66ac840186520b4053b587bb5c9c6e0a060c1ec910f985d0dfb26e4417ffc846a0a943712b2ff9aacb3fe16370a9e9e114ae9e0d1c3d9275302da7875b8896084173198349da90e6c18bfbff1ff1763cf7d6dc2e462686f985dd80b057ff69a3c80caf1a375b57053ccaa4ba25155738077e7650635227e853c4e32e62199220d38e70a8b0c408f1c5ad4ca83957b58c8c9f4049c9c47d39f36b55703ed8620a393b38be48e42b44395d29d5c805ac783ab4a1c3a86fc7131ef98bd79c65a2085e6b8a6ff8e22495ee2429e1c7dd103ab5dbb03905bc32ff8a034b41d5cfe19e3626e6dbb680807ddd519bc0a93056866f8210a65c09027b524d7cd092e8437322542fc2c121f62fa08d9a720e6fd8ca9f75f85f6352236bccfcf5af165aa82b9f3182ae4e877183ba0eea360895afe36dcb3413ae5ad34cf2a1ab94749420a4a70ab55865494f7257af2ad74aafa5247f103f3675a32e4dafc8a4ccb6c1a61c3d00b2b79e89be4d5c1e919d42cf231749234d7bdb27ad8982d4e5688510a6573dda3fcea7695c588fed7f0e0f01c0a741907f5c5bf568dcc98f5b086beb2b1981f0a9bed8901b771a633926631a6f086b16664cd322b60be78b563f1881e45aaa1e61d354221fa3e46cf732c98cada8bfed0d7686cc30b33e87792528c08cc32a9d50c92e748fd5f507083cfdf8013e120fbe630a278d6d1199010d1943ba721e27a916deb0234bc9f3c1ec2b6f3ec08040a16e99ad6ae4feb3827ab59fed0d744867da6170d656793d088573089356d941eda72bca2c4a562d3a1246de70638732f542938ce599405d197d9f9175f99568b0590e331a34a83e06d22f3717d69bdef5d3693611c54f15c9f100c899ddfc53fd3e158f8fa34d93cbce0fe3847ff406f18e07f5877c16dc468e87a6b2aa9598562e5f110d5a3daf30e55cf6ce79282361131dbdd0e5ab6674d5d4e19f5af16a236d8d666f5346ab42ef0ba8956e8bb72724b26e16724eba92518de6d0c81e8c223ddf7e0db9ee6e02f258709223d39f8c64ea1aa0b29578dfee14b80b571bd8d1e3435f49c29b36d692fb562250a4bd8cdec086b4c154ba15c4faf55cf8ca44c37449cc0c5129436e91fa6261b368b56444e844e444352fcd40fd738801f75fcea41daa081bef2a7b6aade21d8bf9a5a2cf4a64678d55648779018001a9614a4529ba17deee26e007640d29d7b7155b0a10542fe70e479a2dd38a52f412a02c8ec4b1b1a84b17636368889f3b62c9107ecec8c2d05f92e01dd93db1973278cdea77a2693aec142bbaf402395293e8fc96a9ad5598794479035bd6ba359e2f9205f2da214f2e5023901b6aafe79d1fc1f3bf8e573e0855c0e2b954d43d4a239f22f2c86cbb1be64d655f658166578748d5c4ebbfd307d79705785723ad012f10eca2dcc808e33ef7fbae639d8e6ae26069ae9c8c8a1ec9ac6168cca87f9b290295a956ab13ca94fbb68cae77653de28ea20d805b1f2cf008eed459a8028d96a24f2ae85598f56e158d75185754927f983928da954f44bf5d143485a3c98efe586cd336fc9c5d0238976f6d1e715020844c13acb16c324a6227ff9d7f1c3628d25bc257d00d8cbc7f1d04e59802e51d458e634d803478c3410179ef429a3928507d220ae45f1d558584d2fb9a1b8ee5365ffdd28935d04f842da0a83bc60c63d9891964971137d71c2c1e5e696bb022b221c436468448163a34ee21535518e2bffd9fdefdb8e42ca0af89c1e8452cfbcde150a8624e67bedf1e84d214311dd779720062fffc80e8d51f0529c8d055c303092cb8b6519a57344f2b27ca805a13d54ea5fbaea8793ecfc4abbb13370b864c3ad5c44b3725776c68fecab9449e58344312ec3ac1798c301ec7641c1a027541443a708ec7fa6bcc85b0580e423b9341c68c583cb04f3a63626b231e1f30e0637ca7bc2a6b746947877c41d6d6f1d58e8ffaa20326c9a4a683ccee11f251e78b7f8b0408cc82d5684f756dca7122d47f97adcd03b6047ae6d192a0fc54e793766fe07a197ec445e3f892cfe67fccdd30c7ee46cd102c88e7671af143e1665c456ec55bfd5766ca152658aa702e4806d4116c9107fdd0075a5f2fd72a5cf31c2dcdd0cd7f41a223ba8407e0d3f0f2fba0b9aa1933b99da69d992331533116d6107402a8dbdc88decb312a4c18b5fa669495f9d1f21252313518d72dd1eb9c45e92a68af0aaefe323b6c72566ec6a6aa953c6ca6a7a2ea2d2ca02f5120ae80cf9b8b4215dbeaf204cd89839fb279e31c6d3f873e0138fc1f902f8829fb62eb16dd91aab2b9d167c406dec2c7e7e725546f838c8742dae9663baf6e311906adcdfe6e6db80d01c4e411f9204f19b697c95350e8d1c64b7cd74a0c98afa1b879d3800deef10a99285a5ef4457d143e75478ebd46fea904dd1e420de8ac8451dfd2a93d39eccfcf20dd9e2a944f2996054733d7ca775fa1d02501313a06b9a0b2a948189971b418f6a222151f711749725de070f361f3daa70b997fe9921348f20d4f5e5439263462f8e876104a468bf78392e9b83d900328236c5416c8c333e769d13bb18498fd2aa4980d7be53f745ed473c9ae8e7f8959c66477b7fa71c53f4c409612556a5c5fd954910d69fa1435e7ce503b5167286d98a6a842d3874cf80df8cefa8cebfdaf06927f6a3285480225563ebc30b0ae6abb04904ddb79d7e986b9a19bc0e944334f1cead234bf5dc4a21cbeb73c952f019aeee758c65d9f01fa1474a3cebb49b94ede19036f1adae5d0aa15c67213594b3c5aee54df42190e67a44d0f6a1e4b686ff646183314bbd21035475050c0b89608c635e0c83c0c19c5ca6d34f2df4809df17f1b142952b9fe83a1296cefc6704676441ef64a38ac7f96e159ea06fdad09c9e7cb58bf535d576e847c8504668611fcee83ccba2ba928b90df28c0e8689a7eb297d4d6517fb1ed4e59fbd1c7e4068e2610d19484a5169718f76d8205b4efb923e3de4896b5fd41246afe1818a5cb73d19980a4d7d2e8fa7344a27a055edca58d49bb0b7d80514eafb62a4a443b13e733fa67139740ee9460c1c05b309c68c655f6e143a6d8ce66ffef418a5754cffe0329274babdc751d9691ce576137469bab81e856c0ade55d0fac6c1d54be5ef6a67b150d2c159eafe2785e5bf7873f25b0f168ffa77da3a41a31825f2c7f9f20f5b07f181801d85561466ef3835f963a21380cd66625706c8aca2cb5d3164e21a61d7944ea7f0f7f330615ab327db985cd29f226a5db4dc55df0d7dd15da9247fa00e01dbc200fd3f5f26456cdf06fe6f085a7a6941fa7fd59e1bf3d0d55dc030419af7cb2d8f1e2dbd9b5b49e8f3800e423717a3da2429dd8be8168416440704474c231d1157688c8018853c15cd2fcc415cd6fb1ac8e7c986080c75f45d6040344180c331b3f0fb335a0d0dc99e516d0070fa4c2fd6a58509eff2def3636ee89b20a0e3e3b1d602d97bad262dedf7848a185b98be921399aa2783b2c745eba4957c8fc09a6308c0a6fa302911ae70c657f9c4d171c00f207119a5cb172cc8a7add900c9dd248c00d7042da6ff918eb1d25aeb5f0e2d9b02e62a1807db0cf8b43c277c1ac25f0baf22ef15443ce19c8244ef8b3e873083365c0c2870456d08368f95ce19727f5eb95afa7b97333fcd4fd060bca5bdc9ea0de90702d9f5642669d6d3087c62ef3da997f7a93d4c93f4eb3c5d16a973a18b90bb9c77c9921e7af7ad82d52495dea2aa9f00d96dbd957205395f05ed3f30a4811660fafbd49105230c644045523fe3377ab673b45bcdf64bb391d465839442e7c6a6e251b4a2b29d7846af987ff47f8ca55f29ae9b448c5f90d49833d6a4f4d6bcf9b1ce7f7dd98a7ea73d8c65dcc693f1478376cda5ea58ff0b571557c0f91ddc4206245f60eca60c4ce00580317d1b532a078b2c0f226643e3163a8cb120f00c029602a95e1ef0b2256ee2b31e130451fa33a621d820e3ce5371745f04675434cbf7d634722048700cd02499d755ac56ab649e468f65029651947c5879bda1cbbb40ecb17d640c4260d9ba6aa891a632836f8fda94ea231df5317c650746a09fcfbd458f19085b4e3fd2d50644de94b72e6c0c0bd15b323158a2e2210a3ab7bc569c5e3e0fe117acd216982e1048e995122e045d71ffc17813fd02170769959616d7e90afe454057ac7e28e2942511e96b33b59be90219560fed3b2b7da5be640defff5eda52453f6ecf299f68920a37e09304a89227076e08b5fa6fc4e7f77d71483f111a6d6c92eddbf10b9634d201941fc1a4f47b11623866b606abd3927312703e373780e45411e86d54096ef9d0f9994089becf1e614c44669d6c6560a7325079f5c52d37ce599cfe809bb49123d0f91d520e7c0ec0fcaf3f723cdef62d1f893af5f7815b0179682f4e092defb385bba8ae131e880a372f103e5cb2db2ebc39aca303f67bc02d88108723cd5c3eeb9d8d85aa8e8a166f3c321717a9f9ba2530c1205465746699675cdc80eb4e3f295788c8722e128cd4fe7f5fb14e38007060496b45080bf1813bdaa302210a107d69cdf7db25313274d7be84e3f348823c661be5c63ef5b917d1ab46766f9e0878b6bd0da8a1a38fc53842d845fe0ab962ffa8289805aa5923424de6e2b220fe900cf1697b44d0350de384a400e63c57c60fc3063eefeec24663390b2da05b6fc4e07fac814872e6e043cacfc35ad034a49f181f866111d74b90e4834732e08faf2d2136d7c4970cdb4ece0875c26fac6b74792d840be431744775e8778a2cbc40abf381813e2abbb65cf4fb1df73f15d3302658125749955a7299590ec56762dd6accc8d61aa897e15c3e4d64f157f6ef2685a43fdb03d28b04d2c84ebc34efa83690d30ed0e9b756b9a719b04215e6460d5cfe3202b2521a1bd18a245e551ce9b95e6d8c05cfd53284b4d2982419794079275f20a90b9d3fbf9e49ad0824105c5271c2c7e00847fb6f18ebd7e151e5d93552a1be0498a8bd99c39a10fcf2e721ca340e99ecf7ae88d4ef28939b6ae14a925db08b167112579ef8b8dfb21ed6d4450275c35cc131191ea36ced9147c06c0819784007d1fbf2f8d307bce6cb9265addb2588731393e07c0a439cd084682dc3e8f658cfa8eba916a080602a00efd8a1130d78e1ecf12f1af8d422cf62f51bc1b55ac758943bbcaeb4913fd070bab68afdaba8c909dd85d62bde66fa885bea15569b6b31d2c9e6a0c7021ae875e77c0a6595e5c8a2b29e25838277bd1ab32ff6b925aeee51d728eec7b3450ca2695a7642412592471b4246371fb1e7118e48b74679fa162576750b8d71943580587bb82b01e1d54541cbc5b87480031c93b921948889281fc1c2a22fcbacb4b79cd08934f7002c0994f32dd07d1a33e15e18eede506983acd57c87e42992ea27d37bec3d08259f57f42bafb78d491f7381f164705b18ff431828478421e0104d0e0a4bf29170b18d2de611d8364421a5b665cc4c36b11b8573a0cbc9f280121d9eb730e0eb17d9703dbbbdaf740e0bb6b0551ba8468bccfec62aeeb772539175e7de4decfc44951bcd9b1613e250fec93cd1d3b0cd768a92f90812bcaf1675b4a1859c098c116a9e5a3b541820f9bb5a193ec64ad41aa604371e7071710f4f80f4aff7b5a8774247bcf4ec7e12ace99476a76d4bf4788e43ba83f4e114ae65df96609d7bbee1f02c44670b9d059bd330319855c8f573ad840c12255dd382b292df4bf6450dabf18fa9d4f87f7cf8318f328955f5ef380aa8e8bef3b48f75ae35e2de109237914dd2afdc47cc14baa4f6295013d07917249d4a96668f07fa67d0a1d1ff2588859742a79992da00671dd00f8fc30a5cc7ba0f397587222a43e4a134148371107cf682d614682db4c949260bba6ea701494281f95ddff0fedf0db34202c0502e13843e08c369856ee2f101f467ff64ad06a697684a15f4fd76c09be4fecd795d335c39a2e6f9c706b70d574c6392a15c6618f707c6049f8879f1bda1b09aa7f80cce672509bc444569d005fd5932ba023c4dfbc546cb2e826ec80f8a6c8efd02566714811d4e33ac36005a2a1a9344d4c64ee940c2749f680d2dc84cf5604974680d0db69e4c9b5075cb0ab898878511830ef187070313068a0992519e6b64014f042e6ffc9d2ec0ff3042116b9a05f1d48a014d002acf110d5d14b414fbfd66ba9297d026e29ef64cf23e8196e0f62c29236b08eeb234169de64471cb6258e45e38090f353fb9839cb9cbdbacca3ecffed7b025f89f6017ef7ece98dc83e7d9cf5438c68276752082f48211167d3369ae171ec35b7ec650c97f08d7af6af0ae1b22a00a8e03a27484300c22463d7854d7cca6222f7a9abec3212f685ba485339ff9ed7edaa8d0458db617da08d55521504019ccd3447420ddaf35a9cdb6482db3b496bf1192719f3e2702062c988c8427603dd474842bfa7879c978e8b2aa8fc62406c2d025ec8b5b6776da2f38db878bfcfbc6bb708b9742de6a1174120e2686c7e597f50095b0d05e05609d8e0743ed233c9d103b848a33c101fbc7d7bed600550246a3fa2c4b5b3ca439b1edd8396c4995142d2c92472e98d7e789f10e63b288e1f8c7d4893b39fd28a63f09407e389df826db23917f2bcf8f3c9c3a4db9d05c42b14d06e400faeb01eaa45fe67d8e3b37b04ce000d5d7c4c8f9be86a675234b0e97be63a67e58e8792456cbb2431ddbeef8281164f4ff0cb8a20738d9ed3a1eed03ea0219e5ddfe4412b6b8936194e3052cde333422bbb1f9f9ad5da37f49ab90659dc652fe0a4dc0cfc92965f4704422d9b89202e69a74dc10150d63b180d590d75835b6c823563eab5e398098dac618a3ff0878888d3c1585fc66014a857112853aaf55e74634f8707163136febab789054f13aea41804169ac920ff09397306713c81fc3a5cacf2fee8afede4006889151b5d0841cb6b8b7c5da03e24951317dabbb64cea5d2c0ff749797a9b58c457924d3c918ee9c13168efde109afe9b4c136fbbc4c3e26d5d13770f0b96990651fc03846fa50a2990085194a13eb504b5fe21992cabc71210dae6f683883852406883300dffbd01ff1af0c208183b8a78179b2110df8b8938f201608e967ea178b12606a22247abdd2088d3748144f5cdaff7800fcd0b1dbefb3188ff708ae9c6a8195da563555a163b8142c68e2645e73ecb98f405add7754191c33a35cc0f6a3ac125d5c6871751d5ebc4dd921d568a619a8fb334903e9d7d1ceb44686a130d66b6d20b42780bdeea88df54ad7619c8c86194c98cea2e421a66b8126e9794a50083011c0529322068d492cd269ee173eb19ddf4878f8d6210316ca49ce2aaeedcf5526c9aa52c5d87558c53d58833a726e49405ca38b73269f97084b40c0983232c881c58fd7b85fda863f831faf459b91a7eddb35856c303c70db07b4b1ca7b15f2e05189d2cc8300c0fb57976a01facdb2ce13cfa9d808ff66c39570ca062447938b2d3c9f407e3e3218c0b0f11080605cfa432edfe91d1f0b6e53399945d3f58a4ec51dc774d5956791f331cb8691d65e499eb380667226e7058cd9c9f739e8f1b9c48e1fc108dd4f6dc42ec84add75f1b6f5b530f1599b766f62300f5ab59a2727c62b205ef12197762d1418be958b8a9bcbd4cd29ff089f11f90ad0631562e15a0bddb2645cb0fd0dc4bd9b82c82d434e5d82fac24e6a4a8038946698b83e57be2d93fd0e0c4baada066a4eefcb92b94a736b4fb0b365fdabd334e492818300402c0a98abe0b3ea3d27dab6a3ff6607bec091e0a8b373f974659e8ae50f4c9c58c467d22d0fe13dd81ad28a734957e71c223753ab2086cc470f42b4dad61ad63d19065b95158c22f5687f4e0ffa1877867848aa814c517cf16313da4d59e9597a2e3dfe56ea115aa8b5ff7d99a04bda07b105d6e50f3dcd8e1a55430f9900d256347b28e447bce9a7279ba5c913589823f19926d588968f0c1e600e37a549c4db8da10ed3e5c1eed5f79b2abca5bf187311beec2f509a723dcc47fa832470758780995ea2528850994f63194e92b5967db7660b8994aa08e7eb35db7c4083d13d3dba70965d7aa5d3d5bc9ffbe5ebc6c5c62fa544ca5d75ecb6f6bdb6345e821a0eb7f532e2657a123a6672a8fac1306a7e7107059baed9e5acf80cd87909c222148225750be23fe876bc2c688d7b2cc4416775acb72cf2ec957b7815ab8c732ee645506f42dab9a7ccc8dc6ab412b90c15d4b45c3d19f9182398ad48da3786b355b251c9ef7673156d9cd566dc7c7040b67a31003589a1803ca2d76e4d58106aaa9d0f70523d7565bd53d07e0b8d04104403c4ad210bb1cc8d1dcebab3a507629817f1cc36062ad79320cb64294c2760c9c4093b52cdb215250c93554f073f100c2982fd111bf594db8ed6b9985db87e130025f58e64fc516ee2fa55df2338220c1ea5527b1d5da4972c21edf5df419522dafb2860f35d0185e7e03d02cf58e7a4eb98ebd8b69e76d3d5b1a7beb3911977e01687caca79a74af99403cc46f01ad8ff012bcb3d13ca96f4bffabe18acb89e82760d9dcd4318a785ba0c0961067e85aea395035c04ffa09b95254ae74d5691c022458ddb55497e4417b92fd8865681194f97d8cdb7c22ca8a206da9161a12667d7caaa1e65fe0efba1a84443ddb2413d1f74c7f703a3249485351c0c68a93c1a4e85c1b9f022088110dbc1d4f4238bfa87d517ef95ce4fd459139012dfc1007f4e134b9faa702440bae4310a0264a2b8061950468b993885d3b69e243359ef25a29cf7b94895f0635a816ee6a0514f5802277d19547ca58c9325ec858d2c53f23ae0f0acca34fa67aecbe8e845b93b6edcbb6afa58869555cf371e866bf0d288f556452c8fa0560a6687382dee62a45558a90269099e317797d0f179b4001f46bc5efc63409f33117d63b85db0355021f5e2a84c609288ab686219b827ef92c2f0aaddb9d97f4ed27fc7dcb42ede6a94b3d09208412768e4842b6cd61b25102999515e22a2888b545aba1be2eab6ce1e27a8563af452900fd554e3eee274e45028ffbd399bac41f34b16ea8d110b6189fb137d28e0bdd1f4d9085f4065fa42e01d532d637ff7e4430bc0f8afc49c9c866afc600c20239a90b411696cf01d49141c059beaee9289f74ab96d004b06101b2cadfcb7f782531eb1f0507397ade04bb71db6b47ce412d314c7caf7700e2443036cef24913254e46f9850c35306979417e7d59f2fa9a78278ee42babc033cd7488a9af66d625fc7e954c3a33acddfd07d082a2456832e8584fdb8d3d903f0e9e7ef0ed4167e1503c8b9fd94489ef9cd0a755cb1035634096c45b64405b51e650f607ccaccdd75417c3e21fa15d8f2d4ce786fb2b89982f878e1d4c5173ebaed0e9ebba977429fb9c313734ab77883e5db2740f1be8922f6dd2f9b86a4ede7e28e9e9a43ba6bd51db6369b7259f46e2462a0b656bf7acedd4ce59c2cae5087a2c49c8f3ca1f6e2f4a37b08f0a707fd2d92efa0c672f13067c0d2321e687f1c329755aa0436bf2475d146dd03fcdc65870be5b691f0046494be679b524a2e4bc2264df61ffaf08dc1bb5492a47b5dada7d693a0ed376b467787d93f7ebefc060779e8b49a5474e8407133d2440d6d4c89aa31f01badf1e436c24d83ef164f2e6690f07ab9e38dc8190b1c29efdd8572be180ab23a00e04944f75a8d3a3bf8a1e380c305f4bd5d798b249602b4f10536cd40f8871c70f951600a38c1dce051d9cecfee7b7b391fb7c4d26cbea9c7c8f811f386b279804a4b303ba07139d541060a96a3ae0498be44023be9b37472843a89658e56523b906cfd474bd885a6764e0726df59d0cd2dde13ed82d2eb3b6150622f75499cdbccca49c84534e2aebbb59a9a1ce5f91b06e24a4eddd6defbda54c49cafb09c70973090fc37d7d01ee0befc285e1716e01be85ebc2bb2eceb3705bf899ebfa7b59787abf1c0347871deb9c51472c77eb27662966471c1b6396fcf5dfaf43e6af99efd731e4affbfd3a8876b83604d70e350c2dd311c33a623aa8e098bf40f0fd588687fc95e4fb3191bf74f87e5ce4af0f7c3f36f29707be1f1ff9ab03df8f91fc85e4fbb192bf8e7c3f9ef96bfc7ebce42f0e7cffcc8fbf8c7cff4c90bf36f0fd33307f69e0fb6784fc9581ef9f99e22f0c7c5fe0fb6764feb2c0f7cf0cf9ab02df3f43e4af1cbe7fa6c85f14f8fe19237f4de0fb678efc85c3f7cf20cd28cdcc66965c3fae2017cc25e49ae28ab964ae211791abc865e43a7221b9945c33d712cd0f4d90bf807c3f0dcc5f3fbe9f4688660a4dcc5f3418e887198758e5f4503891e6fb69643443fe12bf9f8688a688c68886068946896646b354f35313e42f9defaf81f9ebf5fd3535536a6235327fb9f0fd35433544fe6ae1fb6b8a6a8c6a8efce5c24055ea43e144d7f7d720f98b00df5fa3e4af9befaf99f96b85efaf59f2d700be7fc78fbf04f0fd3b82fc65f3fd3b60fe0abf7f8790bf767cff8e29feaaf9fe1d317fe16ffa4fe9ccd3d7017fb4ea5ffaff4e4417a11c453e327f311399c515315812659e5e9fd63f6622d1e881d8f24cdf3bdd27963c082e8e61191ec244b8081be1238c8495f00c2fcdfccc04cdc0668466a6ccc46664334333443345334638cc20cd28cdcc66967e8260425362b221a222a32324a5d912cd0f4d100d8c4688668adbd0c868866888688a688c688e689068946866344b353f354135b01aa19a2935b11a59cd500d514d518d510d528d52cdac6669c7cf8ea01db01d423ba6b84c2fa50bded7882161e8c7170887ffa1871f113fcd0de2c4259826e636f548c9370bdf38df2d14e0fbb51f7fb9f0c2370cfeca79e9f0f87e6dc85f3bcfd3c300dfaf1df9eb00df08f8eef97e6de6af2c26e0fbb7207fc5e02f1fdfbf09f9eb079020dfbfc9fc25c3f76f43fe52c0f76f44fe12f2fd5b91bf66f8fecdc85f347cff76e4af057cff86e4af1abe7f53f2d790efdf66fe22f2fddb92bf18f0fddc8fbf1af0fd5c90bf7cbe9f83f9cb01dfcf09f9cb86efe7a6f8eb01dfcfc5fc55e4fb3999bf20f0fddc90bf22f0fd1c91bf24f0fd5c91bf6ef87ecec85f34344234309a209a1fd7926be6527221b98e5c46ae2217916bc82573c55c535c422e982bc8f533b334339b519a419a39729b19a399a219a29919d94c6c66ca8cd00c6c2668e607e31956c248f8081be1224c84b10cc7a8c0e1b284640b20b82e4aae4b92eba2c375f9c075f1c075e9c07541725d8e5c97f1ba70e0ba18b92e1bb82e1ab82f19b82f18b82f17b82f16b82f15b82f39dc170adc9709dc171ceecb0df74502f72502f70502f7a5c87d79c07db1e1c238e0c2f85c98065c18065c1822172636e4c2c86ab830430bb83044345c98a2192e8c91900b73a4800b8324c385510a726166402eccd28fcbfaf1715941315c16ec745942f2a63f01973545bcac58be2c59cf650d21e0b2880e70594506b8ac1e97c573593b97c5e3b2742eeb7573e4dc1c30dc1c2fdc1c2edc1c05b8395ab839646ed38f7373b0707310e0e6b8b93956b83906707308e0e6b0b93996dca63fbc313b6e4ccd8d81b94d3fbe3134df4be902f82c23240cfdae4b71205d70e142c290a348def4ffad43b712b94dffbdb5c86daa8b504fcbdc7ff08eac24582f18e91479c4b191e2208f38a664c7951d4732f75f82407e0a769489c94831e42f1ddfef49d1610f7d6c58cfa3cc04e7932759502144040cac8cccdd21739bfac4ca6622d6e1cc8f1c9599c1d7dae922d4a18b940e3b0200f8d673600402f08f5b3e3f5d30451a3fe2bf13b1755b3ead977919cc4412010562eb9d881e0885a53fd623249715d6f3588f706c87ac5573246ffed9b02e42feba2ff3a37581f9eb62fc1c87631596fbc11f2bf38863f4ff4a51c7e5ae1403707f4c2206c0fd3189d26d2a8ee11895b9526c5d29c65c29ba2461ff081c6bc168024e58eda344435153260fc52f45938518c89d895f7e206f9cfbc9dc1152762f62599d211c4d29f49736e436fe9a92db6cdc698adbf86b435b18feb358d66a432c96b59b9112be62c7cd28e834c55fdda3a6ccb2ffbc2ea758f67f29422db918cd207577c615f3d7ccc7c697a2ec4a681e67904c5bd897a2c9e392c1cb0fe48e8b06f2c6df632d2558ff9998bfa48facf1433a13cbfe324bd97f13c218f2f5491ba3c933250bf3fdb721c82d0c994337a3ec2d48c8be8515a9e663c6b8b81669a698609ef497b4d328d7223bbf28aca41c1de7fcc9f3fd7696337f9d333187bbb0fd5e4a0a901b719bf9afcd486ca3ec40b37dd33a07817857eb11daa1e4b88ed33637993a24dcb57aa9944d08596effaa117f9974f017d73ab388dc33ca64a1b7af2010222d9adedb4c5b58077f68df12428f508a8fe4692477baef1fcc4d8eb396c5facf23c74da31965f2cc2bf34aee19e5b6e8993e23fe72510af98b9bb94d3fb734e5271624830d7542b939218c721007d4e1cf95c9c3f9f083bce91f27109af6e909ef2568c3143dcca407d1843925b7491d5193c83872d5fc24dbaef094f44f966fcaa82a9cbfa9f325c386fea6ff26557cbaf76fb23df7234e36b58868cf7d4bb67cea9b5a12fb689ce93dee4ad151323bda6c7a071930c5eda5f7f4534bfe92995bdffb6cdf3dd892dfa48af74dbadfbe491510fbd0e7bec9f6ddfb6d49dcfab04ff71b6ec997a2877db8a7df84e296c4dfa1e943b731bd14b9cf91c5b6bcdf5e8a2d0fb78874bfbd0ddd6fb849151f7fee9bd0efde06ce9f7b29ca1fff89ed7bda647ae92f97e909afb64f29732875da617757aad1323cf749678270ad7117b6dda94f6d0446b80b3be545a2c9e47f51b87b6b2d7881f6821e82562d0909cd37e786d084d8b8242438efeacc109285ced4252161aa943ba3dd9d6adba6849d3a3894d25a65582c4ae9465b88fb6ba00fd7f0367302aa6d529c7389c770ae33f920a5d3ba2510b699b6b05cf8af83a3719da9cab0589367f250ee6bfa26d00785652255dcf0f6e3363328f8bcc54e9ece50cc0ccaf2046559c9ccdb0c11d6d481429ae49ae94b4e9b718392fbe76bfb99db93f91ae236d3e80443a260bf0e7bf26c5a0772b2ae07accdb5aa699ab6851159abac0e150084d4f23a96c2f81c98f15158b04600886c97a515162c7d11c58700a0616b9656a20c61a508a59cc41ade94c28403ca5208297f4a9949c89dee596e58354699c4c262afc82c5c2bdc20bfc7cbc8979c7826a144a666e47e236498041272cf1618a184dc2a90216d411aee13266fa80a9a307fa40d7def823e603dcc72d380cafef2c725929329b9bd27b7e7905b86cf5dd72caa373a20ad8a098cd30f38a845ece087263e2021f08d21c490c21604ae4488420d620b3162a005914a024a0a1ca021702145ac09207eb0e28d1e020bbac516486c6f0c318697c715b2bbbff5fc7da13981230720a1b08519db1b43705a954328e241129d1652100292d54544128200a5880930f8410ab820827145265081e683a258f5c1163f04111dfaa3ee0e33abf00c1da6aeccb81ec5519d206f669f09ece8332b58a4649951ef815fd3afe58e9dedd7a32cfea92b3b94a86fb92ed58ace51bdbc4b1963854d7df775a643078b655b48da5ce99cd5fb57a3ce49bd7fc5d239a92bf3eabafc045718499853efa5aecd9eea43a1c28c7ad9a8f77cce76afa7960c34af9ee6151e9b64d5a7eecc2fd795dcc6bd6fb92dd4a14bea5137f5de958c72a3d41d1d29a3b264146ace72eae7cb2c1da2264a4ba5fc73a432aacb28d44ff99222ea37d04783a83af3570b499163a5f018e6b1aab02749f159e7b498c0cf935ac85f1b12d782eb6c433abd9ffefbc1a6f011d4a35ef51b2803ea53efa1ee8f99a5bc51a52e7dd5adefcd0ff529d4a3b0ecb051cff538eafae09450b0ce5af2e8574809cda3cfc67a45c9f4ff193a47b2b655c374e149d738c33adb290dedd1af412132a6ca62b51a59cea7de6b4410a26adb777a99fc4ddcf54d200dae91c39f899bff394e8f203e3920e1010826252605d195cee99ce49d2e5015af334dd3371e71d6dea3c185c79609da24ecf852494f83b7b3942c8c9af70d0a6992b50f3bc7ebacbd09bb34383295336b610bf2c6c10d27996187b4bd7dd6137dedbe80236f5e7260c7f7ee065ebf87fa31fb878340ec373fcbff417170e89c5e9d33749bf9a1db3448c489e8e8546d037b82442648a4816ceeee9eebfb00011053ae18c88f214026300187f231e50df76de08f0efcc199e4eca7281ff43d940f10c8cc443e5082200882d333259971e4eaa4ce8e099e748d30475bb873f2e67b5a077f1d75ce94334e86949ab6b06307cbfe9e1b1dfab694fde5369337ce1db98dd74037a40e37a33ca28888b858b18db98d8361fd37a42d49a973e6c350018cad0a6fd35ee2ba694e44dec8b74d58af55ca2b044f4e28565a718292879a9f9d7648587f2a639322f924fd5c089eec2daccc316737f636ea706eb906c99b398b70f79e13069ea49452ea9ecdee584e303ccf64832674e01cc7b574b5af31ec69220aca1882f4c081844b866191ec2fc32aeb0d2c96b5d4fd871e1df60472a9c69422674152603f55872513a85f2c7fe9f4c87205cdb1cb7ffb206e6367e8b03fc8e7efdf13df402125688a6d873388bf8ab81f55249d4331570403fd754a0162ca4604d6b4e2451af90668e040f15060d8e93ee5747fad7ad849a1113689637077242afcb8e0085cb4414692e8ed82ecee65777797c9734a39caf4c8d228374deeeee209b9af04215bf1028adc596c8f2c5bb27c9d183a474a2973e400ba68220bab84a7554a29a5f552e9d8092bedcc22db8cf2d48eb447043b5f22d98ce613268b65adfcfaf52548838e7ca4b6f4975362253453bca4e67e6f281ed7399afc8dce49ddc6bdbf6269eb4fdc3d7477119e4b9d7bb925d8d95282f16758fab2254f4a2992963cdfdd65e40dad4e9d622ba58dc2a50beba61ed9f4d4dd44a9e947fa1ced6993897bcf744defa6af8ebd9a4c263c769873ff50e7eb159e943b9226216f2665b5d426fde6b4979aa5b4a58cdad7f7b4dada4bb7714aabd63d5959d3f0e8df2dbf623af6c8f305d60a79bab89791368ee517218d90832018e1e00745fc40a588ce91a02b238094e0b8a28b32d668c11862fc10071536c041185e74942a74393608e84911ad4407701cf1431950cca8010b9cc460075aa40193020ba63042afa00b20ba2388943133630da1280081835681c402041b51f080882a32286a58153a8a12826d77c72142f5c81f437709bc1882104b08d18009618894884cffe9132b3f78639392873ec9fdddd3460091653e21b16c295bb66c5a69a595b6d4ef295f1dd7dd653ca672e3f97db1b05aedc417da06b7fa696e8cc3e1c0aa701f8d711bdfe4e4cd7479623f735a9658a8000979c4d1983014abefc5606464391256debe9bd3e1ba570078afe06fdf41fe7a3c8774e06fc31ef8d25f32610c5ee19173171d8c4858f99b4ae7f8b7c7c2e307e9b07a413afc96b66ddb6a90cef9b60ff778173b613d2031e02093cacbd7d58fac961fadcb8f5cfe5ef0a4d261589f0661f23cebf1e036f5bda7a00f6f52e91c995f539c4b7505643b68d3be2914fe88c32559efc7b994038f1c0b8f16068fac173cbe0b1e433ce2e4d59d3cd4af1c67e592cb1596562c1d24c449a8a5a0be7ec73ae7fbfa1eea7a9abc5c879be68d93895cadc8bee1af0b3b84e3aaf778900e67710452e6b094b9895c1ae583d00f82322a4b2b5c089165ca93aeb105a3fcf23d184a096bfade3ac70a9149d9e223e0cbecc9378e38e3c79b600dbfb0238eed88c99def9b0632ecef74c4340bab39923ba7a789c91d8af37daf23d63e64a0d14588ab3f997ab84f3f999e301eabfbe01e09661949851da13fb3047dd02762fe79bcb801114b0834e64defc5dc6a32e17ae41284056c88ec593aec7a6485fdd7d79a94fb717a5c61bac4f04bffde7bcf70d89f5987ed865b580b917faedc63981bc77a075ffc80349b886247cabbac1c57878c757550d111fb8b03f30df34bf9e5bd982599988c4c47918c8c0ca69f366778451b4158a060c710f83826775057088a2419118ad0111b710c47e783821d5d8470f2638cb3a81f2324774edf4bc830664a4c2c4791dca18f7a9690dce95e26e6b9fc18b3548f645c6296b0df1f33a55b7e8ce954ab1f63966862f24686e2e8f8d111cb16b682c91bdc42246f4c482625d3ec9a96ae098e0efbc8c24c41f2a61f3543cdfc85e238d40c658452e363c37a3fa28c728fafdc0f73471d45b9ffe5bab45858f561a5c2317711aa3ea46efdeaed2c1393a1d261cbb4beff5f9ab03252742882ab444a3002017819dcf291996c18010d2251e69d883dfdd2c557418b981a296cb79463626dd331b1dc984ae778068192243a7cc0031d407264e480910d68200318b880052a9003052680c30d128800048a3cc00607f83480014486d4b0001a6610a200198200f9e123069a0488b907010730400f9e1d1e3aaf1c185e70a1002de0b0e0ba3304b85941684a4c36b483c85f4bbe7f47118ec91d8a9ae5fe51f2a0669285feeebdf0d6fc8e8b5f00377c9bbbe357b802f8015c9b27c05de16fee007ee6defcbd04787a4f79c905baa8f7ee0d80e8cdefde4ff4fabb17143dffecb542d746e9b0ffde936861f7a2441d5474c8b08e988e98920b1482ff41879f9f9f1f51040f82bbc327b920f80fdc24afc355f21db81f780f5c1dfec8edc023b91e780edc233f5e24bf81cb813772c7cfc0ddc06be01af90bdc0c3c06ae06be02f7026f818b81a7c0adc0e7702df0385c0afc046e0e2f818bc3df7027f010b812f808dc1bfe0117025fe446e01d701ff036dc22df80eb80ff5bf33ed78627721bf00cb83e5fc325f2432e039e865bc32fe00e792197869fe12ee065b8425e017786077265f8205701efe302f92517ff8f1be469ae8f8fe1fe78f1d27c026e0cdf73c5cf37017f80dbf308b8f97bdc03bc012e027ee7f6789e6b80d7b93bcfe3f27cced5f9d7e5f12fdc9c2a993525d9512ecd33a638aa81164804810c23713eeb394e4ad644aa67e050f0a3e0d7efa2f96e951d9a8615ba77d1801486d3075e2952d4f75d096229b1f781940c76a268b0aa8fde1691d3835fe4047ee005025fba8d279e40eba552df4767835c8763ea6d053a78ae3f6f7edead22b90fa73e60ab57ab6bdcf5a153c2505b44c0ffbe4811e93656d82472492a6527a213b169c59bbd40df4bb731d5406193c8a52c45295bd0fe87cbec77ec21fb530d14e242f6b65aabbf67edab207dce35f7eaee14ec4ae4495715faf4fa50184ae01ff5b01011e43a76991ea99fabcc4e32c779df0485781c71740eddbef34c3f72ef5d204fca16b8f9c349aeef7d9f5f296e54c82a6f55ba39a7a99e3abc435319aa51956d661f0a03a5ed3e1d9ed2867622e0bcee56a19476b74a0529a5db2d125425894e3e12662762f79e0465708a47edbb0bd43d942a4974f2126f0104e224f7d3b187910a5965ad06cffedb9dd9ab5f77abecd0597bcf3777e7ea89c33b346d4116f98aef88ef082051983a4d9b796269772b1652ca90524615b4d22264e1b66d63011613508992a59a0e30033186c40e73b4ebbcd16af807fdfa1d152d00129998be874e02094062157f27a2d75dd395a00f6eeb01e7b67c3ad36df998a4dbcc37f57815b256bfbb5234d5c0caf393ccec01569ebfddb1fea6c9669e588d22d130129614ebe532bd5cee7c6b900d9b923b32cb2904319c60855ca3ca0c5d44460d1c480b1e48a1a80bd1a3b30540305043a457ae5145f4aa16704041123dadc8882755446fd3820a133f103dee9312c54cf4b8af4a232a4dccc48a397a5b3e9589cb0cb1623bf3c4861d4e69aa571605c18327447a5d6688d2d5dddd86275de39de596cfd920109457a4983134c20892852dcb614b11826d5899b9fc2d750efdf9146cc37ac044948ff84761c7ae38b9df93a0fbf8cd5cccb02d933726220dcf9f0e4fa7d92773e84f4e1c4d5998aee469c2a2c957f76077addba050f6b3a08f1697a0318ebed908237337acb52d150632e7e9d320e4f9d448eef83b0cf27c9f0224f3bdf1cda44d3f8c14b9ff6b43be64eae3ae13d19372f5cd7c7cee26a26ff62d75d85e10442722cc10d888891d861982913911b9eb44b447b0fd49b9a974d894527f1859e7d4dc0f238510b91f66a873bcdc0f03a346bfba198c159d23b180022837cc15a3dc3217e411e628f707c3028a19e4112629378cb750eeec337cc1841bfddf6c4626e5b1a58c3ce59ca62c4c45b905090607b96164d266862ac82db03ce55219798e2aa43ce7c344215fd6bd0cfa98e08f16f09b73fae775b78b98a5a048e4ae64c30bd173f9badb42a881124b2277a51b6988decbd7dd16a45489891eccc7dd8e7b0e33a14b90a224764e444feb402000c828c99dbaeb44f4bc967ae9fffcfe99feb069ab0c1bc4c8e44d0be99bc1c8fc67c2c8c2f0a08c9ab9a898e5fe3a1d53d909f441af9d4ac0c828cc108591e5aed4e9fdbaeb44fc408a79e8d0e361e94507768491c1c81c4606331f46161386a55f6384602b8c0cf4315b2f7451642472578a77a6f50b4fe84cde07b23c93046dc7cd130db98d3a6a0946582beb1158d26b45a2569fb39b02124fecd0d94ea59461f56cb1539b74cb8eff0416941c27a9b79472764dc368028542a1502814ca89eff4360582e05b107502bf6bc1995a5a62fb39df544dbfe126de7429d196ccec1ebd4ebb0ea7bfc739ce5a79a22c96b52387c7ee65d7d5ee7b5a2671f087e70e73f2a6fe89091c144ebf110ec89dadbf763feac8dc775d8771c04f7d55fda7f37dfdeed20ebf4f5daec3cfbfd63adb3bd28ce224d1c8e54f03e261fabd4dffe61dfbe79c9b36e79cc29a4cdfd3f39a7af66c1f663bf51eb9e3d9bb8d721db71de13aaee38e70df4fb9ae67f2cc5c2bb771db4c150465514a29a5947263387eb8828e8bfb426e4e262f1fe5b3bd867b68d93045eda7a8e1a04fdc3e888a40da43e17ca8b861283fa044ed5bec69adfa18a261db39f4c7123598020a2b60c83c28b04c39b0238bc5f2a0b01463a2c2f6d29209072e6b7812c736ad476addea56ebcbd095ceaedd3df1a6d524a80cf66d09e923daf67db59fbfd5d0afcdeda50adb9da95635af74a382922ad5fca6ea5757b55a7d4fab56ab956ab552a956ab55ea57ab95cbaf52bf52bdac56aad56ab55aad52abd42ab54a7ddaeafd57abd56ab55aad562bd54aa552a556af4aad3ea55aa956aad5e55ed5f2aad5ea532eab4fb9acde25d5b24aad54abd5eabdef5bad56abd54fd0c76af528946a85eb5bd47d797a6d0ac645d5a27a515d995b5e7e822d2faaf754a9d56ab55aa956ab96a7b925f5aa3b338ccb4a3553aa4fb9b4b8e03aebd0bf45858fac5ef5293cae3eb57a55eabdf9ad5478fc1ea72d0d63734b1ca4504d6a52d3a8e6dd0a6cd39ff4ebe6ee9ba443d879d4a19cb2bbd6937c5fa29f9cbe52c9f5412bf267edf108be1472fa0a3ef8de7677e0c00e8f4572f77d95f49299b7ede9c7bcf6600d376bb83eaabe89c5b2f633f7f43d8e729cf6fea8db48c28c6ad477cee9fbf1742ab2de9fbc3f72fa5314de69dbb6292c0882b8e7a4de3e58bbc906b8b2b482e54ac6620511a414b941ca9ba420ecf420acd7d339adfeec5fa570dc6fb848de6c26f0c7a9f34b1556fe4f9e99639233d0bcc3cca6f736c97db7dfbb9f938e987ec91d4fbfbd768f9c9e43e5ca715feb8338c9cca64bab098c2cd6c1c092fd5d8dd3d2b30237b27f0cdfe35f7fbd47b45f32731dbbe7fe48f7f57d851b6fbb49b8efe8f6b57677fb9ab51d26d7399ef4e672c563934cf169b658e149d78f373f3979fae7e8a9af20f773fd9a4a6ed3a677d864a11fcf2c53a961fdcda24a73e60df3b94528da295f9388fac442bee8d745eefe9311769c59744e51d73b8f3a9c54a3b2c1efc7ce9e943c3369b2e0efe17104b9f23089ee54a343ffed9be56d12258d967b107f3329b70bb2d75adf4ef96a98b4f11d16c84622bb87a792bc31c2366c56d92725b39b4a4a4979be7647245f87484221a84cf13c1abfe97e1a7bc8dd0ea451879e4a491c6453b2ea515420caab4761a192558f7a8ffb3829a5cb87bad58d0e25e8e33381ad491d1615b172c894447dbd52830a572cb816c1ec9125337f1dcc1e51fd929951afbabee46e744884bd2dcfea1c7fd49d3965039bfa80f0d974a7a8a71985c726b95ad1a1cf594671e08f2dcf2b1da230ea6d878e51980564ab5287fe295c913af49f499b92dc49fdcc1b1aa94fe196ca8234a03e95c22dd586d439ad142a95fa542a85678062513fd619d22c8f3e933ba9f7f750976b01b2f653a877d5fb7baa3b738d82ab706b75026940bd4a855bab96eab57781a00ca85fe1960a0b09b3ea55f8b3ea6b963d832c5f46eec85aeba42b6cdbb605e99c4ddbb64dd3eaac5aed9c992b4e871d3652a833be30c30b6da58ca33cb2e2b04f3ce99239c7f5518799532dbd7ad9598b1bb9beec83c3431283883cf6984757f248dbeb4feb376a82ecddbdb9e62a87bd8d9baf55ce39d7aa341ad223dfd03d720f2b6658c9dede34064899523a6484e5c8a88d58474633644dc9416544343b6e1daa436cd83a14b272ec605d59a6b429a59456a4256418863a2ce1418739e34187f97d4f0fe9913b59bebc573240d07bd123084734fd014c3f4a2e8238117cef0241d111c10fe244d403017348452a8612a77ec5895155b155087e216f546c7d8fbaec494f4f7c3e27dce21e7c29fa3c6e71b84504f5e0db807a1037a9e2737af09ba03050908ee83d144e34bd0de009fb43e144efa5f8e1216e43bf27cb2f3205f3288960182517458890661371dffcd180e8eeeeeed93bf0fafbc0d3a99b9e49123d8e6b86383a7fc4e34ccfd4716a8001eb1bfedf08fd274e6f4407b9c365fa49e48ef7f495c81df0e92fe1a173a807de99694ff7b427fd681f75c9e38bf2b6e60f39612e0812ac7fcfe0bd3fc545dcff860ea9d36eca7146f0f83df8de77732872c32c9203f85f0e9d233d3c442748874558b060f1e91c24fb47a1ab731c531ac44a1957f268f3b332e53e18c745777777f7ec6e8efbbb11318535265dc2e5fa81a753d79ec9eb6ecbbdf7e9defb0f3301df7b26dffc9a8a5fc52dddddefbd0bfec9c475dffd09bfdb80f77aa68e4383364563d266a2431a9b32a5c6ea94f92c1d1e32a70ad9b0222143fa638f4caded96667d6891b0ddaf63c3ae22d123531eb3caaaf0a48b2a51b2c4e6f949bc0a5aea1fccde906f693484368ebfc0373dfd2072877bfa32c81c56879f97d392214d32e3a0626a076e079b1dbb3aa4a8ffee29fcd511d6f4a697a2f6d5faf73ea6ffde2d66f23d13cfbfe977cb13f4d122023eea7d40ed51d8e7dd467310df20f286fe77df4db7754506e95284ddb01d3917ed9cd4ae4e78312f54ec685bfa2be5e67798f37a411f743ac91ad6260b5286f3adb4ee55fc6cf4ff757a8adcd02fcb92495f810e28a8480e72072653e9048946cc77861de551118eb396daffff96c934411f9e3771cb49ee3b3e386f8dcc3e783ff03ac97e97d4fce1be3dee5ddb216d912100d9ff06c754eab4b4b46139cc7548b52b7f36275a29bb1055d2622700a2b895401f9abcf96e0b3ba27a6290b9ab697bcff33c27995ed3739db65d0dbbe3ced267c0badb34bf677777d3f6ee9e57ae5c9961e628620d25a44cabf721c1fa10b5811d25564287748e9ce2cc1e8edc01730f923a47a9737a6747a6f42791dc61656a839029c78523c18e8f437f801947ded0b764582d480e240bfef71b11290281ffbd0485c80e9b890e694c488ad7efa4e57f7a2722785bfedf3b118bae8cb335df7bb7a2c3eb03fef7f31201ffc34caa38f6f98e9ec983611ee7504f257943c597c214a2cf71d6d2f79cb03f873aa7c3417e5ad13959c81da76a1821098ce9c5cba76de88f538b4c3fe633c36e3808c759cb628d619e5634fd31ccae9e432d6d580db3be32466b913a2b39e0b035097536bfa25a548b94903a7cbb86ad4a156986fe59e6d4291dd6a125b2a8456cd8ce632dea91375e8b42b7a94b64916518f3d85887e67704db5f67146812bd4063ba2b299ddf61dae19bdce4a6cddfe47526d33451ee6639521c44a7437f1c3b4aa41c8c8c20993f27ab916c0e9d6304031689bc620611f9b39c53415dc77f067518da35ec14a9c8c308648e3592b39270f85b27d8cea30e0ff24647ee80998a15e7e0362b384620437f236ee3ef5284a59dc5f64f4f22ba22cc9e47693401222ee2901571dc188f4b7c68888a215a895c7a9022c3081c3cd939b21f8f483e6e0d4a59f64bfaccf892bea40c46f992de80030e2f8b87e421d17e714f7570a8144343540c0da5e171fde2c62b93929266921a4af06a5085e5468294e973afa34cb187a232b913001e42f008e201cb3c887804d1e601f33af3a4dca95fabd35ab9973f76399595f278b3345d296d2e776b6d37deab7bbb511d456f75233b13b9a76bbbf175bb517d5bc2938d9b3e11eb3b47b64b4129a5747381d741ed3c5172bfc603e311a23c4299a728f7b45f1e188f100f3e4f4e1eed27cb0f65bef34c77e6aebfebaebdef388ef3ba9f5dd7756faa613e97b9eed2dcddad7beefbadbbeebafb30d76187db95dd6f5b833e3609cac0fdc418680de1dc46fb21d985086b24870e3595ccf68f1249ce8cf094c85fc2e5296b0991bf7d67cf328714e40c12a2593cf88bfbfa5138ae88e5aca2c9c456dd264adbd41fc2598e659f559dfc482ec41183b84d155d88b03336a774a892d929ab52ba5837838eca0a583228fa618c5c6b665919e38a6c9a29b1429656b250912d929531cec81f9631920490a5952c42e4539656c4f0c17cc2001c229a84420b55c842892a8690de38a263db1040143d41c2ad4da3684be30adb72b3b432861219ccd24a1a37a844380162092025981054c41a513ce1c41351dc108a021939c841161fb4aa562b165180e24515573801054d34d14c88a1c5b4224616464c11032a6734a103358a441c31acc8a37dcd5f6eef7c82086e5011fbfa932fc4994516031a887d35a42f44176860022491c98b0a281c41ec018c024610a27f5f261d4cec612505d1f1a42e40f1c5510f3d27acca788478a2f0c0788ae011e21122421a696e68330d0a4434ac8580c1907c42087182a0134c2160486ad02bcbebe845c60b89082e8f4b6a2b69291869ced37d73ea740e51adf6572c270bfe74fea555cae9d4b194f3256c8acee9f17594c725346b23921ac403c64388dc3c82780c1185078c07ac9a8cb0f3e5d74a399c5c39aee3386e48e3be5efadc1da714b956add2cad1d7ee562b45025115ecfcfa52939ad4344dd330d7e144cae3924a97787fd799a4eca8dc362c270bdd6b72029305ca4396b47322e9a4c7353a1d73b40a540c79018791f428d9b3c83ded511985d20278148f02075215485c5d802fec283fa60a48540c79d18242a5067d6bdd6a1daadb76b79f43792a695264ffde6a4fd7f75b9d1b976cee57fb4d47c3234e76dcdb3654eb56b75aeb3654b7aa51a16ddae6dadf39d4a16fb572450ce56009a562c88bd64f921515c8acc09848ca1d652635016826cd2432e69559a6c3963ab0a34cd892fd674b52a697b28ae61b76fcdcd3d54d25cf50ca1c862bb07ae4269a2f03db30e10a3830724752ea3fb0728474c54a2b62fce49135b312c62c0ca51410acb4726464e5e8c7ca5191953066703861e78c0c14c0c41e3610e7179958fae84e951a76944b4546960f96b821d6c894cef9933559d9cb0186ed1c3445ee81a02040e43e688adbb78670b825bf7b29caecd3e2865b12b7b8b761fb0e37a9e2b3e196b481099325a2a8f12372b825f190eeb99e969307b7f9d95c88b039e0221dce0f9ffda8040d35a6490c154233020000000000f314000028140a878462a150284b6455d90314000c86aa50684c1709a420c75114848c21c41862083104114444484668b8011e4bd82e8c8d1b6a73b1a465a3c7f4085b59df3eda020849fe15f0e7c9eab5efc632e7921ea28e00c1c021e15014ae5a94779e7a1b77bbfcd0e728e76ebb03c508832c9128bc85613bee27218526ea4e1c383171679b33c83a5a6c15d8d20ec7b35581a6ba8444d66ca59de9f9ea5531089771309dc2caf0a1192f9305a30d598f3639c523e6040a8530c9e040b5fc3dd30ab56555df177efc65e63f2ab2e8e4b321f535312424efedb62263b7a457d42e0a4c6af202db2b3041e7b220d82c4c253fe802f47f8eea2de7873f326c03b3c6278c95147eb9afea9f058f0db93d21494d320f4f685294c0eeb6f1de380985438a8d96546cc7d20f1d6167a0272b7c8643182814e29dee337bb99ef6d239323a00c3c065012c7aaecbb64081dd506fe4da8c2f0abe175ac61bb25f3d52ed68a8a9deb40708185d706499488ca9c8aa49b1af6de49ebd647b7879313c6c8d5c82df0e6e8d94f2b927405cde685f165054497aa92d2c0cb2f6c8e4ee26ca2941c4f0ca902963035f37257e26bcec2a2ec48d3c0c67010fa7ca0dd606988d26a4f80ae2c41a5ce318b5e55f920f2bfcb3d2ed48085f76d06c3974bb9f7840b20b3198b423f45f749554fb8f605a0740770aa00810489255497c4dfab2e524d166127f94f481b2250c9154e23037a6148ce43c92d05792605e71ee82b06c0a438301c8c42ff9a663eeee95a95aa10b5c4bffd437c1e82d91c3916a2fdc61838b6c9895e430996267f187fb9b8b3201317f085eaa4005164c20874d620654f0b4895443692436d9eb3723e94127203da5d82c4d3412855a43c6c3200487858dd3a5193b52220c17bb4803656c0edfd9afe534baa0339aaf907f17fe46019c64d1f772b9d9716de1cd0595e16c25c4df02c03caa458ded593aad0e7e7cb917a2ff0123adfcad2bbe9e8b5dc60189ec16e85086db52c1b4d8d216dcd47fd74ed35e3dcd7b75d42ff0717106fe337399ee227479aca6a37136099d1298dc42836a4a324e1e8d0c5c6608b73fa2e49367e0e66837e952013dbf6d6488a5271211449f4c498674e146c638e8bb9ee1e65a17eb7b35143d87a194a98d88ce1137ff73c5636c00be994c8bc277cf711fbf8ef26a4757cd186094f545d6c970f4041f4000f6a8d1b759d6e2aca69df70e5be6cd32733925e5bdf8f10682c12fc8e7482230d918ef22e498bed91113e4b9a2da29dc4ba09af342c8106aad42c50cffe96e638feb422aa1218ef930118747d96490d81c411058738a631b274b53a51490a6c21b5506bd1195dc8d28e50a99dafbcb85fd80dd5554430d836c1b71a6763d835f68e08a21c7aed8b7020051bedff6f63cfbe99b814a0a744a253c1c5b499740528449891d08f89144bbfd07894de92aec0e3ee9a175187b419b1a910dfe80ba53139abbe66f07f795a1fa51fa1c700e0fd6fc332395e0dc5907a9f539dbd4a71d5388dd4f68e4cd62db23664e25600c63473ca691dbb6d53d539c4c35f8db2e9463090941b820fd59a998c1ecbbf7d3bb8e72c1062eecfbef5a732cfa785112a8cdbda865d3ae90225fa79b2e02859c06a8f64ede7c68464890ffbe81847bcfb401ddba72e7f84e9df904c847017d52d7a9c1953fd00d62c804211eedf3a8939016dc7ddd6c6f243e71b8cd030c3b5b25648e55409699fc3e215761e9ad7ca0e42026eaa6e16ccff8cfdd33e61f226ee430b261a9f4d1b0b7417efa952cd43c81e214c4c3fb8805c17852f18682bb2495a213f96f8586628db195b6a3e1401ca780e8ac84a51fd253a00bd85682eed4b340ba93981111f5f78c3b614cf8700111c9d29df340512226b2ae56f24bee80be6ee009d698cc575bf8b53997c07a4350be95adf02ee8072575f173992ecd9c836db599cb09cc7171c57913ae885fb3367268ae78d1ed53ef4f2db32a7332c2c965d4367827530b116d447d2e5b1de45a9428b854fdccc4079b6e5a2ec7c4ceeeba57ad7dfe3b82e0a025eac55455df4d8a9f23b2a0f7bd90497162d6295a84e92a61c06fb79aff40dd032818088d7f884423eb46185ae80b53d5da44a62be3955649f463a2b7540f38df896b111b8aba17c88b6a43c7a011367757ca2d2dd5163209400c56b795b53ddaf43d5cdb71932c20b9b1e8bdd2bf0958529baa402834bbf9d6fca770850c59be9cbc72113e85b69bf07dcd2a60fd58035df1fc14624aa1967c55b9737edd8dec4b526782162cb84b9728c54f52864a42b209792f33efd9ff2c586ad95a0e7387a12596a139b34d7f0421abd71aa3cb9bc23e4b59a3b0c21a97c3a918a813319f0117a6b9e390d3f3246acbd05e72df798df587fffb19959bbcd10c5a51365e463433bafa12d5e4740b34b98092ffb810174a14beb241ad5fd7436d48f13a180084a19bb334fac07bb8f8fdd0858aefa312b2053c133dd2bb25e3fd3705e5da2742d666b9488eb19761e889bc551429d3410d3779f1ad7e850ee8d60b8eb60f38fff7ddc04f3e33d16da20ad1b668fd75bd2754d02a9ef68e84b0afcfa6aa416002611ee95552711a07480ad61321a4a25db941c5c0464ed4cdc970424cb89bfcaef4984e76831f0ab54ed72affabfbec94a92c8b26ee4478581a55edcf65064b0fb477fe1c4fb2610e5cff682bc68c84d8465d79893cb70868f3ead3d908ec36a2d91b022553c7359438770f95d936229383d8a09a2fbcd872342343d4b614cfc5d532f33c9c282adab692797c9e972d9a347bdc8c7197ce785ebc49ebc088c7a41c3d12577528697d283a37ddf87827bfd02734ab9b8b2610a12483bff2bca3d3fc72efec69e026fa471a38f798957af436d37bdc110d3a0512fb2c1861f8ab30231158d7fc1289580e528323e133cf35d3e280ff9dce06d4baec07871bb90c68cc88d6445afd4df3b26719ea1eb1740732ab2f6d6b9e9b573263addaca7ccb0bfd5846bffdec951dc62fc80b0e71138f76a474177ad6e9ce8e44c29c6ed1c97541e6996e7ca01986d8ff3ecdd5c3c963a5d88cc243ed71520db18059181e7ac73963df6b1115795922fcc2b2be603f809499ba479afba4c449c831955b6515805a583fbf2ee87bffc4772eba89ff52792789e4a1b0aa2294aba15c342da063a088a4d8e0d98532d09767a3252d5143c5e3c062aaa7d8c7ca5c8d16a83e88292740f5758185ffca328c68f4ad4ca7cd5e86ca9f6d2affebcabce9d6422ceac08c36531e0646ad2788c7ebe8b7377432d0c3e05d5c23328480bbaef1171984e4fac33fe8bc4fc391e1489cd89f8920f52033023a543b2d02b92c4bda76f3d5125f96d13d5f217340d3c1977746b92100365b746111112ae3ad1c5384d0fc6b5b2abc251a2915653addb605f02fa83ba527af215f79a99b6b6fa393570e33b77af3350662e769be27ef92d12d7c600c60afc68dba2d4261c43730612c2b40120a7a8e8c4fa1367cdec174a9245c46952f977950c6108cecb4c11cdf3ada3501c1da9762f5e182ce5556b6d0dd90841197eaba1ae8f2bc92f73f729e18a5d2630e6b358a1455a593b60fb1c3e9cf7fa1db7b78e127df17704d42846a0526df648ab4c700e9c76a4d29f7a6691bd89268e27951fd0315bbcdaf6506656100a9cbc9e4aa99f4a01345a132a79da64319a4518f68b1b531b2fb64915eb92bc40b1911bb289eda064563adc8eefdd143ec249d5a3c9e1644d514010b3186f35c67d6c466b5e1e2481bb8d68645da776f9a168476348b6215b04c01c09f744ae384a49311118d27986ceb4bce441e07097c1322d2d50386d51b30596a8ac33af6bdbe07c50ebc467c242e193f9477edd85636a5ebc98be82ecee29e1c5362383ac3c426cd4b3809ab67a719ab217f78200b7246f1833af9d8acc74271c1b78ecfd9d5b07f64394e82d61aa20ce57b537ea0203929cf33649c351676916fe2cf010981a79fb872ab5abd524660f6d22d02a2bd7c42fa3b0f403416a92166cee8cf046685c57786cd4b8a8278600d13a043b30520e6b1e846600ae22853acee140a962a89e164669cd6a02a3b0408177baad8bf25ca958170aa025e7fa5e269877abb4966ba2aa391b4e1cb5882f50f9657f49c4578d575ec25c28f3be4b7290a5c06557ccb05b001be64dd484c78c35d208ee4c2bbf8d09d2317e3cd41782f3301e4bb263a3b6a621c84503daccd6b3c553a7cbae469783222d7a47dab7a834455a0035e5001655b28efd576084949d934e148e5b201412c3d98d341d5153d8cea030d64ea7d57b32a32ed722e84e235a435852380b5e44b646f82513ca250fb474e6adc12673fd3e210206ddbf3735b18c6434788a74dc1e7d2cb913c31be82d68c748d990df8ddafa0338a07eb2ae2abd8958b2368eb9e7560dfa25b8009b73f9a05d5500ae6020df76ac5576fe73b62e6e9bcfcbe13735e2c00e6a8a34d12e31eb5ea9388e210b8d9583002343f625dc01e72b242277ee70048150239e298edce44c5c227e1601428bc34539ef0a6e0ae39c93c590e8b60828016c97dbf949cf6b72e1242125f7d81e48ac156fb4a78797105e5984fc9f13383084644038561d5a76412102b4ad1eb322ce81958ec4a0a9cd6847b8730cc89cad4e8f81fa02ef3d70f2e9c65e892442f0fb338248a41294296c7a40802c3d794e56e115234729463c581ce7041a808162b9413b7dc3d7781b23a0d3231eaec7c8cb07172141d2c912744889000ae424f4301fd19e51212edcec9dfbb0130d8df88c7c93ef90367c71254ec0222a09c167253caa04b0e3f1303a3341ac5a39ed044667dd5d34db86cf1a0903c20752ad19d7c717ce20e5c8941021a831a306a202161b586d484ad5684d8e067ddbaa7a5a94c16030a1b7f6fe61ffbbe27a5c48d53c7c4acd4eb424a6f6c1e57c4761ff3767a1b62d3f9801b6023c38d4c16803d1bffdec91f4a176de863d51a96a7528cab6ff12323eebc2e6881f5a18ed412e73c6b8e04f8a87576703dd9a0a16d63992c067d0dda87edf3492971e31ce532f55e9c4dca64ed594cb8a58a7b3ef3885963625098fcc7cce62570af47b1a4d59cf99522e81c8cc117bcdb3deba7dd09b1fb1f2d1e570dda07978f71db7aaa91d8e378049d2837b89a29cb2d6851976a8671385cc45bd75014ce923eaac063afb815339de564b3b2abd085831cd3e93dcabb7740f33515e5853596b1f22e59c0da0a9b010639cc5ee31aaf65900e65e11f72987966c53d945a086cf6fdbdd3b71bc451f9a4b1044a65578d579a98e841a5a6cddc45284da99f011fb1e8b58f1d98abbab1cd4470dfb494986e32551f8ecc506d1e94bdf92daa273dcec3c0aecae007938ff3f073874413bf34cd44032bfdd71ed28a6b380491ad1703ef2b62242461e48cd5d465a90d32c16de31095e8032928301acc69b923699e9e2a39178eef9200b08cacea3d408a84b217966ff76ea6c12b0facf36bba0fe41122ea2e729d20043f16dadb871227d2cf5870706eeec9b8e5c160d0cc6a851058583ef22c257c44ef9c4d8ac192afbcffbf5057aeacc7b98a489ab01d002d88b07c4d39ca88986c5b3477a4b99464b23e01477d33f72921246a159c0b19e04c4e33747f9b1e6f1bda7b59eaa74990d51ad2e09fc0cdd3b3d72ae82fa0863bf8186af7ba7b4946908bc19829046decf26c5830a2ef2183c6a26a1d8be90116ba8886ca07d0fce6839fa22fdee58baa08cdf108de138ab9b494705d184061dd67ff68282543178074320eaaf3a963e34758b6acd1e6f13c3983830f23dd76eb90166b2a32f689120b81cdd6df7bfa764b5c754e4afb30f4711b25c7813ecba4e671a28ab50543608eaef9ca31083744be78ef1a38c214c21a37b6d43acfffbed7c42bce4cc8df78adb17ee535be66a7014f384c83f2eee09337f18dee3a57376b07663893edf010e177c3033ca1af4389af07c465f69adbf82adaf243671a5586650d9da2dc99fde172095672e1ead1947871cdcd82dde67f7609f71fe3ce5957c1ccc1e528e639adb652c8fe3ff59edc579fee1bf1bca43e758475ff921bf153d61f3ed0734b411345598e4239a96eae168fc88823d8ec2cc1658c8a25a54fb78ce0539694ff6d195da4e1c8a258ca03c0296ce34a42cb4edb1fda02b6071d5a9c7e30ab4d8c364f1f8d4f880af46270e56e6d9aa308cf5feabe423b4321a4fae9748b138742e6c8515a29edd5218f4211ba25d79404a51ad70691339916d7f01cc3ade0822cca1a058315650a1d3de6b38a885745ceaaed8e7332bb8452d6a638b5134b0a5e5c57d41f5d2a50ed6406b7ce3057b725592c415bd2829d52b4806ae3dbb8d417f0813bad305ee818ea24bc9011d5d06238e0efc3bcae386e9beb44c83b0aee15a65cbf358f92808fa152ebf7651bf11a5408bd71ea3c7a5f21c7d6feafbb4c23d0847d640dc538838fd59b865dd93736a03b4b64778dd51c891fd520e6f37319742f9b7d4e1927d25218fed7e30475283fd9c3853ff7d88a6cc5ab6713956892f86fa13174bc3a09e35198d5d0b92419307a2b02a7cef8d391cf93dfd8618957f32df169a165dee832ce3c241ba11296831176442a64724028775817aaffb2902e886a216363ecd8b2f6c5a7305c403fb492af21a6a4ff24a24705ad9f86d3b5dde9bae1ecc093eee3bd0a712ec302b94f78b7c354c2ad21dcde1349a3be2b6427064a00e42d74d9936513854a0c42ee322810475eb877b28068ce6f7dfe4715c2ab731d936a3d4784a9ec436255fb4a84726a69c79c82fd0b8df914b0cdb99ca5145678505055bf1f7a653804c7ae47ca9aeda59d10cc5faf4be5d7eb891aaecc30378a32a6a54096fdcf7bc97d6a48beb7d984c85d80965fff9047464b12668acf2797e418a2785b5bdffbbb2d6a029a3c71a03ed9b98982aed721d863f5d7ef1231d6b664ef3759aa093407e75ffb415e85b6f1fc6c8ea3aea8eb97e0fd0da7362a21d7cd0a08acde89eaa45977d3ba4cc69811eb11f61956c73e3036e9d4806654845be273edd1fcde51d544ed8342d6e8580385eb2acc2c22e2dae57240e64a8d30f9f778b9454d40e76029b43f80a44dfcb697605d3d01ea7e0961dd27e9a8b61dc83179454deefd5f6d111390e64c9090d3e35a229a00f130f3235360e229e1bd9d746abb925708689f98f80664bfe70b48c8e9d78957509b69659a1cfc5a773d2c8ead4df2416d0390ebd9327b6bcf2a937963b1b3e616023be24254f7361f93cc9c0c390273a3053bd7e59d996541be3f3a6745ce2cd2f2f019adddd010e50e27f3b4b07adba647d5e74e93b416fe9b5bc557d13f8228240598d2989e04f57cd22496a35373163f584263ac26ebd8e3ba706b325ed8debb2df14d0b80b527d00b07badbc40945561a2b0493d9a88b2bbb1067ec679e58b3fe49a82387778c11cf129c4cd7a0b23a0787e1531bf7942c0e2c1d39acd840191ff283afa71c61c3fb187b4e4260718f3f02b23e25131db23fb690e622651bdbd53ce604e1b999c4657457430e985bf806ad7c37999819a5c07f99562e51c6e92fbb785060d26a094cb0cd17775404cacc7ca56e6308d6d266d0a3dddd6b655aad22902c11e63b49c94f988bc0927bc0f151c6b488ca479aa9a866eb24cb90685dba0f5641e4dd50fd0f2ec29f37ad0f7443b6d2b70101eee01d38cecf068534c9a22e033a1095bc60600a4caf750eae5da05c955be289b26535495b72c77cec9a3e641be74d11fca76863ce55b5424838aeeef2d14809be9371d3ccfe1179be39483af25aeaede46c6414a53128103d8c1752d76ca9cfd117e0c99e67529e174fb936f593d88f0560b449744ba4099c47d6a45e1b70f04a46d23b780e3f20086e07ab6dca68fb31f0110fe918d9a84c289be0040d9d894e1e3ada537e11cf682c495ce86c55950818bae946f6a90693fa3f6ecebdbe288535274b3f1fc9f832d0a78e25de2dc6e44daaa839c1150a99bc71c4698ef4ecc3cee54b7a3d43266fb47bffcc48d04e856e22ca5de0ff7a65a968f2060313f0f0e13e8abd50847e790d3c919c042e093e41fcbb1be68970df0fa8e680b120ad6d6147e8c7dcf073b1a1526f7d9f35a66e190edd51a1ab230312cfa1bf4e76efa50b7fbad19124ef6d2ae3a4dcbbbeb734ce10da06f6c681aff3a74043584281e4f2bde57511701683f955b1800c27720ef5646421ebb107242b9b0bfeeb063ae7f4671e91d39b168194e31506458f743a97eaa206ccc06e613eb47de4ebc037351b25ad49d02259b14592539248e2d2c76d52cf0348b88ca9fef1f241bf6e81752fc8ef488cb7ef3f7fdb04e374e14b7768e339d39f28fc8f87dd20700580033cdc4c577a68c3c904637e8904a9944d2cdcd237759599489a2029b9e09e4c40b11ed7d667dac27071d8ce882ebe3391ef4f29673813dbeddf12960945a1860c6da97446a8f9c701cfb20fe5489ac05f47961b70ccdcd75a4ed83f8c243646bb6f8d58234aa715fa776d1169b724a63444aeab44b019c4813a26241d0a73c7d12300691c965cdaf4e7e1d7f399bb1aa40981256ab76eb43c527da4c085d1453ba0ed9c188e1880568a134c199a962ede24acea93acadf90653520e2fe6474acaef08cce2b0a6e36d822d59cec1b779f07c0758ec33f963125cb648db2c5a990b5de01ed2e693f18f49d30b80d6d29d8fcb4d966e0ccfcc331a2eb648475fea5b6b238a31fd6ba4dacf32ab13c731cba3fc99645081854f49f43472cdb75fe8b91d97a81205c7d1ac6ecbf0be423ca613413cbda19cd5b612ff833ae1bf2e74e1cddfe290d103c13bc5fd15b34012665910fb46b32dec1125debcd2616b62794298a8733b40bae728bafbdb6a26ad43210d7795edba6c60ff0cd0d1005f144c26ca8f126fb31e262f9e3220b9cdd0813c041f2af61b629c64fa10b3a3c8979dc9c1aa80f43cd6ba5066ad4afb0a35d9383e8e3a829ed1328e120494e882a6d7078a11893a94283828e380accb19cc7d3bf68c94f309aab54e1e412e6b8327b5503bd8ce0b5e95357efb5a04e28bfd25f74d3e2b6a27822b0ca195a4f0096936e3fcf29fc4637e8936833022a075cd94ef3ff2874d9a8279f9b36de4dade908ad424cc9fdd525cf65e6ee8dca0692abf350dae0217f86f4db95b626c8ece2bd23f380081557689ca8c460653bce6a842877183ac9a959298649789f919030da4952623370a4fb773b415812a8141e63c290d28d7a5fbf260519e189994e43c0999025bb3e4c114e1154442940db78ab5e8343f3fd100979f0b212b349b7ff748fd29e33fadadb06e53e633fbe35525b4f3d8514028c49721e232b5d8a2e48ba4fead439449740418c57748ee8633130d5c622c46e027a497d88286ae3a8dfa910d61e9b59bceaacf64b006ae69a19bfb7857b60790fdb091de1205ccffff9929d658ce939911c46297ba69af66a89a580229b4ecc4dc15922ab8e91c5a3e388ba16581dac90f118d24ccb9481f65ce0f41399e670168c555c5c53d48d09b4dd3701ad1165efb429191bb318417b0fc8343c7aaea4aa5688faa447deb1d05308c4d401f07e771ebbc7b8f76a6e658aa1ced289a9590ae2432541d7fa65fb2aed37d3a3dc525377c76809cece3ba76a1894539bf5afb196cd55303e638138bfe67b6ad6cafe5fa3158fb98f8e69d24578ed346121df83bdb1182279bc48697f9e816c5b5f810e62a1ad11cfdf58869b5f8819a13c24152308159040979162530e7935c6d84b57e882321e8825468c8bda7451b060a9c44d39666d8a8cea944ee8e18664f786f526b725005bd12a67ed672b8b992eb3d2736dcf249b4d001e238ed9f60c343ae720d1f183d814913c626bf57c888dec28c9b246b15cf92c81d09a96b3378d753db029479dc87c881e3980a108e67138781dc40f490443791d879cf4fdc026cea1f66e72d96b85954d261f9959bc60a8b7b05d54bc7616b38b1ba101f20e4eb0bf0590970bfa624c1f778adcf12e7a74ac7176fb9cb4abf7559f5fce50c95000ffb006bf74cb0d0f091baba1d3440bf4d62617c2c0b8d44e286415b7c186eadd2a899588b8c6a18dd159f3bb18ae0e6043863cd7a30100e680c72040901867edcbc5d1020a506995cfb8868d6472fb1f31e5becef1062cdc5d77efece6096bdd658384a3e60ba7c2606ef91eb7d497a8d19824c8912dc0fd73bb675a8e47a16addb66a24c69d244c5c04cf4968e3df54741f80cc5b5e73e78e0257eec04270c82933a57b5797e124a3ec57bcba917abe44be0e58e9b176fd6b1c680d3e7af698210cae21609e3cdbb4d35bcbc3a58472108ff0dac1e583c1df11b05db27bc1118e9ad29a81301c096f4f10bca498a94db839ef09351829a316b50c62f7ba6451a2910231f38c4cdebe4b8f42af995334cd728fc0a29435212d0bcf05aa7ab97b8485615db192bb0ef5a7f5ad025e07dded54f1f58eb7c81eb512b2e758e918683badcb02766102a129db241e4c427ceb5d14908604073fb46240917716998390b6eb2307751cb8d40d8772e94d103736d25830ad5b1752b236941815d66ee6e0a4bbd5bf6de62e32038c39ea84b51c87e79e39b527dc2f97df69714104f1075c6921d5ce8b8aed6f94898b49977b5713f9e3eec01cfdffd67475bb06b12f41e937f644a678aca0fbfb2f59d14f6599d8ea02fe13643c6ecd0cc97cd5583578782541af7a3c394a1bafc8de827ec5a0270094526295d40aeb02354a2109a3bbe3d7c02683c3fe1479a1fe1bf9e48cc4d7969ee385ea308bcc6bc67fc5a90ac6b587ba1c058a4ac690822ba4c10ce75058c4c41dc602a778e79b37431728fa58211900fc00be916a1eb74040b51dcfe4012dc47fc266c48e962c30da79e92047b7a58b27861940a63fb2333bbf32013ba05bcfe9d3b19f075e5d1824ddbd8987a60246623a6aa45d03ad73c3d3006e1225c9eb3c74b7444bf3b4987b3ec29c8f202a1398391272cdd35b3aa1a09974ba8218d08cf1caf11c6c6294ad7fea165905be30d74927de02360154abf528bd22838222dbe6fdc608cdb164ead026664545183d0453dab3a2d60d3b4f095fe9934c8a70525357de1e8ac424ab7e6f1ab4303af8c07237c122340d9e8dbca030ac3cadf8e0c9171f72595f14c4f7317752ba4d61174e7216ee0d5316f4c8f446f127cc369eae76349a960028ba8acc8be411f583f70be1b000e425eab6e6b8eafc7ececeb86b60978599efcf8635da956a59ca2e2393895dd33dc0ca0cf3ab88d559f9a83e7f62603fc16f75a9cfe1949f560c3784f5ac7a99b5fd9ca986da573b295d16a2b7a870eb7fa973b472776c65594aeff647a4f6562af10aca72273540df597c261a5cadc22464f9beebca081c08363e33f3a266da9ad18ee16c3927cbf50f280aa97223435cf0bd1745612d04b7f5e0795fca1b232bf30bc84aaed77e8146922c770e8e42ac25252ec2334a38e1fdd8e0d6d8a9bcd618e0012200f69330fe269573020bdbe09b885b294a908880997ae4ccfe9a2bf2f03b39cf87ad9fe36a04f4ed5e6dffa59de874f3da9a46c651b1e7223dcab3a4847541733e2e752a0c7007a49453c429f3c989104ce4a2644e85f1d36acd8f20a9843b70a47701b171152f3d65c24b8a6872e693c79ed778905f8245d166a5220568dfad13f010d24cde8b18a26d009a0cdba3162cc9476109838e24eefbe167dfe545aa7b4cd96ff455ba9063fe3f2e470159e73402d5d5413c1aafcf78ba3ecedabf9f1dc30c1c830f1eb5babce617c291820fa0e84d35313316886e122a718fce96dc8d5344ff3e3d1d29a4b3952d4b6d6e6c65b0532aabe6655c3d3a9c0e8a12c746d12d298515a5860ab293878d549efb8414a90a38c6401d69f64282a680e61949a259157df82908a4560bf0748754d5cbd09796305d462c46f7e4431a299a8bb6d7f48d43f80c22398c92c5c89abe6a7aa620964247598dfdca8ddef80ea17313a9154d0dd2e7a1f2a55843b64cca9382d4f8c72b6733b8f1587c6eadb8b0472c2b58be5c9d6f28fdc6fc0fcd716c705e6948efbe6d9fee2b06d10224057aa35c4adf9a0414d1eafcbc863d44cc9b4923a8167b8652f1e742af423a6580c08eaea0c9a0bc6cc864fc2c1b4f5ebed71b1e185f7137185c52e1c611efb7fae6f1e066139e2b3e16223465282ca42f64241c5cb6f2a0f7ec757f5c6b0c0518f97058dfe99ce21e9e6c5a8de41b37850c5dced977a2a4650e0c92052f7aa5a981d4a861aa1828abb7fcad2d36f4177b3668128ccbcd90ef485ef41bec7c606f36a27d83cb03ea5a586ac9ffa48dbbd046732d110903ac24558f1e56d4aeacf53c51b67a860d424b9fb002e9e0ec80f68f69e198426e137c837efed02c7ab4d8e414085ed854fb6ecad80e3dceaffe1a1636233a1ced3652daecb80f3012e1ea0d58bc3432be551a91997d5d565286e7f60702e3a9601ce4479da3a2eabbc2fa535f3884610ff59b71ff97750a313640c499371962f986e1582bc6183e47a484daa5ed5a169c396a7422132dd41b006113f6d4dc0e15f04f51fbf0ec9a7f643d01fdb3c84bf5b3ebda2439bd86fe0a3dc50571ec9adc20d95e2885688729fc58a22e39cd3370d627ec9787f05af0c36ace4b5a2c758c23a1a6ea425e87f7469bb64dd3cf8950980f2bc11c80965b0bb4d32e96ea040666ea8d7fd2f5d79e8a961b44f684719d7bf6e5267f741e3d1fa7566bb47396a6f66fbe102b680ac3f4ed0127949263ac43d2033a76a7a285be993b0276b4083afe44954160e0efef1fc8a7746ecefe5ab84f1328b7e7a2ca545fa14070aef32abdc5a453c2cfaa1255cdffe348eeb4947e7fcaaddc7d0afb7c6be740501e282d8fd261ee64b4f1ff745ad56deb5a5a72e2aa0b17773d3b85c4ce05f9408d14946d8069ab65959527d14c26759327842d13b83f5b0f85d5ee2bb7fe89bda29a18b581500a48970bd870417b35ec1a049760765caf199158b202d3ce8c184214897edc60c977b696878c2e90524128fd65dcaefac12bbb36e5cc75de89f6af4b4567684ed0d4e53e1138381fdee9e9a35506533b9a6a2cb5266b3ed80d415d7e78072e87f41af850ded82045bd764a52bfd94e454a8169a1fc2d865305dacf06e5cf24676beba79911a21ef2e7e99c2ff3845aa3ede25d3238940971abc05d4bf43c73e31a5729877bd8bc695077cb7def68e3141f72920cd2ee9dd19896de58af49376034589a787f44be316d8347c203bc93c70e7d83f03b45baf8f485219a7470ee1c1a1c94931012ed40e5b2558ff975dfc61e823cdd9911cfef6792bbb3d3e000a6cba1bddf9c636763768415d20defd9507a04054d257e5d8ffca23fafd90c8c1c973fb7b416e8137a8b0bef18c1b81a82847ce023caea3b3925915a2af5127e27b4b6f9fdc0447cbde65fbd1a392bff01fe3fe2917bde2c98b278f5e2f5b29fd0ad0cba7036eba038dc2af94e13b87e60f34f136bf9eff8196bfd167af62e9a10e2068fc6050f36dfa8cb47cc8262151200041af731041a3ffb94c0a2b43dc362835133ce44a2acc0e78f618b69075561ee14cb343d4ec2927ae66cc9ea41eedbd65a074ade2276d290f119d28eb0d5704f97876b1da0d0dc2d263daf8fbb2fed35eafa6087303370488d3d03809f7bc91bccce26626b40e87439a506bcb7b5ecec840a27cb59feea3592905bc0f50a59564dc123eb98c3832a3c87196a4c46d5cad4b2f31b63a29658335839f5becb29110353b3e49a60617beeb96b26c4d87cecab98c2d48faec1c448be8c383ba834c7a38d9bdbda8e005a596c5856165d296f0c0d0bcdca1e879c08643aae4135251b8ca467a39b4b2ea36b06b3dfe130f6502f4199a3628592fac2895ff04415728cf46436c847ee5e3f72187a645807bbc8508a10e4b85f7bc546eb49f7f26c867b44af1aaed1b07a8b1c38084acaa34d344385c331bb564a6ed838694a7aeda294619f86fe424295e75a879dc55776512ed8a64882238ea25ed23c551782eb9f6aea5d7b64d0e712f015f5e19d6dd9ecd736d515e1fcebd65717da1e62de97090e66dbcec80a0324df24aba8ecf7dda0b2f797d158276c73122e976de3d5aaddad5c8ab7ca873649df5950a43354505c383f33154239e23a63d6caf8c4bf9c52f5902fb7d425679a6c10b37176ad18a4478695e9b260ce643fbb85549bf3227bd7275a75cc96f6e989bf190c569f19123adccbf1ded552e272fe9869cd7a2d51e5fada4d03009b7c14bc7128565eb0e912e95d8f884484f11de5333343fac54534eef25fce9ba54ae5222b21f5a1fdd6ceb789dd33ab4d81507c442fe253916a89f17672de07a5650dc667b6b5622a453df590b1c8c7f10cc1ef91dd5bf6d739cd308486c34de174fd0ec2f53985e5a4241751ee1397bc6f53e1b9233041287c4c7f556d28a230299076996136eaa22bc980de7514050c5c7a83a56e9c02dee7727845748173b17c51d3db2b860ceaf2f0f4d465dd41139c831e31dbd49655cf6b664eaa4089bde1c583a88055211b7becfef6ee65885e7e6fb41103687dc259297343ce465191924470466cb6cdf983a893d916abfb54c3ecd0b1ae0999579d8d59292a722f263ad2649f9077e2f1cf77b6d9c06e861fdcfbd770ad29ec5d3a9a0f26ee4578a275cf0717016d2b5598e389099e838a61e1cbf5144565fb47eac33af65ab15a588caa1cc6aecd6d1e91d36dda087348414c10097f15008ea6aadb67bf03786967b6183b62c29b2514fe38a7bffbdc80ecb5f8c7b334809e6682e3940276a983a5fb0849ff0a30e04d13397d2c1577e89820d67fd303e22f5d7c2c24485a9684f461423006250baea2210c860458024756bec908cdd0593d9b1e134f9f777fa15436665445aabc0af0794a18eae9035e20d393efa889059b988680313eb823347804670df4a9c7284d68a9d84a865703e1813328d60ef9cdc81e79289a9d79090142d1e1998c7b971d004486c18ff9b27a6aec070d9228e96b55d6214c402542980200243538a27b4e351c86ca8336b633fce3b9059ac02a15956571dd3186f4c00596d159d060d85c00d307faf401a33caf44406e76f3da2753ae2b522810db61103343e16b72889238bc1afc231774a11e8a52d20e505389ffb1b65211451466e1ef057277edeac0f9aeb2124e696dcc046c4a1385b29c95a2bc0609fc2bf139c96d24be8954fd4728cda4cfcf463ad129e4b797cf809946977124110196045fd2508f57d07b9b95d0a125c8330097df51ff2c4188feabcc575f956dcce1fc2f62747b3e47cbfcb6663a53a0da0e2c282d6a667e7450146b6e5c6325c765949bcd08b5a725dc8b1e049c6487f09915156adb4ff30726003c03214ff538f2b44de30e37797bfc61cbd01594d70058e30f3b2697de5591b48cc6c4296e0c12f0e46b43764438dc411d15bc80c0d85dbcceb016939bc63f3a7a5bfa2d78cc0fe4ca2e6aba4890d6ad7ae39415e221ced1688abec207d1293fbdbd3a7414f1ec3e590d5cdaea04c104bdb9b7bcd45ee1498c5a4050518ed7383e2023bfbbbba64388d4f3b4a3d9318e47b38a9781ad8974734a06749c955d1dc962648a56f270eab5f9d11e862e220aad1c85e8b00b725c5d1b54c4ea06986e7f8b28286f8d6b86cb8b6e061af4ef93c99e806219a2b23b82d376a958e6195b4f6550c5dc8896f0205a97605b4ee10a60ec49c37eff895c1ce08968a4a8a0534d12950bc23a5acd8afdf893886ea47dacf52dc27bfd113d62339553f7908cd5bc7fb50917f45a03ac6b4c346e6a4eb38d99015fa89bee60856e01e256964522f352191278438e68924552f70e8c2b4d9ccc9b483d171d8dab6c45cfcace7b42be013918fbe8c25a5447405b62ccf51b26b93690b1f4bc0f137f76fbea556cb6194e7f7368fbe307048333edd08a9685cb613f351d657e2bbc87ac98c00e79bb23fa28c1d3c00b51240aa7cea672919416cbff7cfb88077df8f1e916c70a787f13ca0d279365a796eb3abec0efe6765a9db7c5342859e402941ff8763ff23645bd1344892b1fac829ced021c88768741100ee9ac7b62203bb21abdf743a7c09a28b24b367ee9da009e8f70aa6f52007002dfe66b98a4cf6a9220b92c527c85a7ed2f09b0a63ad01b1cb9d7901e7c44c34e2b81244464b899be0ca884dd051c386b3ced7de821ef51cca106a315df1c2f4e47b0f60a3e1a91579525a6b2792d656eca39ada1640e20d33d936beed218b6bbe321f59705d2fdc45100078c28c17496483530e4a54634ccccb25cbc669ce15a44cbb32d10028c06be7ba5e30a4fe1623fb9f71c24e8344eade2822a7174703b565b7c53301170e022a057aca296abb1bd3b0d9a61f5d91bcc6155eae6675cf00173e3240737ae7bdc121db54b97e5d636a9625fe1abe5a12803cf2a1b43831979e0286848f0587de02c7247df0534e2c0791fab1683c422eadda111b2cc6b4bb88b11b7b8d2e0b1e9a379c42ba50045cd9ee3b164db8c5de5219b5947ffe4a112781e898236aba231d30376193bbed2d8d81992c1d4643b6fcbf31c52c0ad1e7f4ee4032ef1abc5bae7be99b67182a07de2bb200aa19f42272d5136a697fa41199b88396aa38eb8cfae375ed037c79a5f51872e44699d8b644c4c3d73cf72d5c05a7d1a8b90fa4ad3690533d162dd697fa793fa34a6a2b5358c6c4bf19028d89625f729a9d88d28ac320b7072af633fe4e32e60ab484301198f80482810fab9e569e1b4cc330025ed4fde34b04d815e2aa3d273bcad2e8aa98e95805335c4ef288085e226bff5950ea5064a132319b043a062154acebeece7e09646664addfba021b12d6f9fd78fb774e04ecdc6f2a09ce0974ce61b0ae5c0cb97044d4ef28333f5dca7ac5551bc19697459a327062f75b4e3b5a7519417387b5683e2a737e7518f82d1b1309ad9c018ce4e32dd9d65d1979e7943f09fac38c3d1216446f2d1a790094ee3907f28aabdc9294a234a046b3d0a077361f1ab6f46e6893ab71e613419a42d384a14169df010cec43b05ad93fae70f1d563c57db53042f42ce839be9b3d790d6c70f173c092bee86f98ac5ad19137cdfa4fc826723cac18d819f51488097ba065108d042da42987ce40c2f599dfec806ed17b52acb61a1d8d8d018a846dcc0485e1034b270ab799099509bb4f6db0d78c7a2e5776bb5d5d1460ef7e86fd0c0967a98472663a4b34a938412f8eeba5ca2db4fc0c6d3b9474eafed1b5749d7de60c78e827fa54b5d955dabfa2195d5672871aa0173b456d70881ac1c5b34590c4346fef964d3b122e80c2cf33ec211a20d5a2911272d42746fed729244c698259f391413aeaeb5a7dc91ad0d5aa98c1e245ed3102c5c6c62c902e155153b0427206e3ced2d8cd8f22c089fa90e8dec3e3cc5c030786ebe2570d9697d43787af8964a6ea4516cb31cf166c1bba093b038d99c6b2f71017aacb39dd61c53aafeb7a987f6d5680333dd7f26d621b44a1976480f29f93a8af4c61baf6eca7a4b248376bfee4f50136ab34d497dffc2454c0b54aade4a0e502447ddcaedfa243804de74dbe312c3485fa32cd3d9fb5d7bcc9fa50ccb11ece6dd0c7a63baa0b6c856241548943be22c378575909f706f5b5efdb61be3aaddeef85f9f37c1c5cbc6a1a2020a7f5a43529535c38e4a2b143484984960b2ebee7a790e967c992de3c2d1bdcebc104718e99866a18215b4bccda717decfc6e9fdfecfc0d4dde2ee649517a4c01f9ff62d9ccf0775ca7842f70b22fca6dbb5cd77ee1118b516f40c8e058cd89fc7f6ec75fd4fa47c8401075350a59de8b6a6055b06ea37b93500621064eddd357bd985d3a5088db3ff6456cdb396a983fe5216e19d8aa11ab7fc3cabf000971cda98eb8911a7b2089d73d0ac5bc71999786cb346d51d447a26ab98c693f4e0ad557f9ab20d41227386304afa0f81a2133792da3b1167434db519694f97576a34dae6cdf321f6347b27659d6206d8861ba9ee1ebc86164aaf2dc66c20dca106570e75a8f55bf28372a3699136e916270c6904ba0c1e8f28c39cd808486c634e608445c11a693793721331ecf0e0eddeaadaf326add1389ebab0cf5569ddb36e8b50644a84269445ad7826a0ce78023e56fb12c83355dc1760397c821295d12cf74122724241f0f15dd2779ceb394524880a876e5a73ce63a98f2a79651166d83d39e874e51699c948459af76f0ddbfd97c3131eac20e5014ad62c0ce24b24f1019324ee2497a1d62f895bf8a981e965a0fff871da1aa7e12844bd0c4b47531277df55e09b81a46cc777b624f531658ff2c8cbb7b6770a23b029a85d55f4b5a3ccb288505cac627ddf60a2a6f3230676081ccbf0a31ec73ea8992c1d61ea1b8c3710d1fe636773ab1d5a474dd6504e58bc23f85bc3174959fb42639dc9cf4deb490cf8b886dfe2be1506ef435237a995d484baec46245f6ce33cc45b6ce9e59161a029ac7ef84532b3929de0c5341f2ef809322d92f51b9e4ed859ac6b6ba87126d69cccac03f287c91251a779ca2f81c629501970846508de6fb647cd29290e3225602b80791b61c4e7bff8f854bbd2c241f81beca9ee897a7bbb9e08dce69e4ce8432836e54dce4790c7caad508a71e434c27894f75760806e6b88236fedb939dc3cdf76bca7e10c6a07a48a0831d9448b420a054e3a08cce381bd15024f285058f7a9c77c0bfda4640002af632448a847bd100113596c85a20b9c00961cdb1b45ef6ae65bfb7d43b2f79d7d2ef5aeaddcbbd6ff9f79605ef7d4bbd6759d644cf0113300a40dea24cd4acc408541355f55135acad5f9a15151e10c1b38bbad241f328c67157ddde5aa8c436ffafe1c87ae5ec5f9482e049ced792020e2956d432cd8551c9749587172752226cd533e2d63d273936706e93407d0df80ace70c771fd59a8458ccb7996638e0b2f126a83ef5868b55b7c128d587289240b2937682753061b81edf9be23544ed31d4c7c6e4f404c07c3f11253abbfb48049552a2aa0f5591b29bd68d910c78eaf976d7228d8b5366bcfaaa7958078cf844e8d7509f9d4ef9b9735642c1403b662991b57c9df3501ba6b3abe13c20951b51b4963b3c726b93e2b8e860a85365dd87a273c0715a66d9de2fd49036dab25cb5704e26d13216534f7d5efb6893e85122e3ed14ccab3c621d80605ba40721f23c2c4888879e4bc537e311f2592a716d3af23f42b5e6828fd56f6e3f83367e33a5ed1f01071f25a173397a7eaf7fad57e7d04b3b195c9585f55a435803138fe9d672fba263578869827ec6407fd259be15789b8dc58c5557e9a9a2d76e3fbd1bf8b5ee889105c329539a110a6e20ffbcae5da43aabc5ab04da5e9660332b0c272379c90bfbb0ba864c3897556de44da39ea89e14495ac1939d2b022367cdd35c132068bcd97d2f965539d16e5e53e43aaba8380f70b1ae930c806f4d13dd036879db711bbad9d526815e1f1732c2f7b85e52b5060a952d83918630104d1821c9ea87872456c2c8bb99ddfb8f621f11f822eb495ece11e504043fd40aaf277258ab8b0cf418924affe93d04aebbb78ef5b777123468c20aaf8be6342f19e3dc11622591e043964307051670f3aee9a186533e10938568a74f39f03074fe9eb4ed9ea714ddcb0036ac2ec3dc5c2c7f8a49a48390a0e0f645746cd8530c71118700961e6949cbe11744f14863b53f76f349fa64561a17f3d6f8429c97bd07124b5debbbeb3077e7846398d25c1ccb86c8f8826651d917ea0c3248be257246cd15e3ce8a2ae5ce29af1d68b7e18e666f4438e16bdf30468643d9625f75e50e2e15a3ba61caaae40482bc9a4f04d20716361d71de819e50b44c72760e1b3ed1b62f59d4026f594baa030e7b6a1802157f125663553d0410b5c25deac9f2d93c62e0110b6d330cf2fb1c1e204c7cd1e48d96c409d54d4333eef55a05bd43f6782430ba8527cc5b05fcf8fbbe083f060c2038853115219dec4164ea68aa158e329500fe5f3b172a60e1be5f793b03803248eeb69c6cfcc4f8ba8f01d16618fe1c4982cadfc279b39c95ad0cad58040d40f97d338e9def73047c5dce41268728cf8a0f09448613847e4a5eb9ea74f0b5eca10135ce85233bde4f050055aea4fd3ecbd86f16630ee94f7fe1675d11f0af5cebff03c44579cb8292a0d08d7768cf5f929a5f894e6bfd0c249214b1d68c088f91464af3e31f0196782794d1e2785ec680d7d21cc428bd5eb197c443de6ef2d72b886d2a26ec243febe8d7a10e63f0270de1b423013083be65c55424616350d21155f26027016d8acef9bcc15fe9cec8d411f8f294f0998b5117092473bc2b5f761d2a86981d6c6e4bd4c1c4485138080b5295a799faff38823c066787b5f85edb6d276cd078750b8c9623478484f07e5889764ee6c538ead82611a68868e150afef0d6a46cf93aa47388a2a82f943d49e84e838468a0465cd6ecb0f0a874885ca1dc85c79e5a6307e901498132c16abccb3a9be23135ac7824836a60ee79803f68b90281a47f2996f9bae6aca84fbc03749ad748b77e0370b1a02d578dae0c026ffebb212046f27e27aaf8d8910bb92016c638e0c9d2ac770b18ca43778d1a8b56f762ce064f8ddde23d72564860e7e049f6f5170e15b8b7ddb67dae4ffd7f7041b484dc09b3113cf1d77343c4cec415d5b7a4611289b0e0be206eb6808d0e86c5d0200c90d97408dcccdc32edf922135bd9971c9d1fae7abecac07572448864ca9693913495d4a272696873c47987e8d2949429098433d241912867c1ac29bebaec29acc61cbc12e9b0af4cd9a35e094b033345b883c9427632a2b4a85cc6784105a038172532c31a80b389e9d999af6c3c32ca39b43d2ecb180f7845a91acd0d11193e7c86bfb6622bdb1c3748688f1a6bdf10fb2cc101fcbf740330c59df64afc8dd66f73d58e4784232cef3b8dedf1d54e609c1dc1149ddd459d786068f7ef9be040f554e544925b425f2f510361396214e8c41e3de5f8949d6764040e0f82b5c5c49005df607ba09f603d77d16853f96984207fb4ef466af4e29d16abacba792872760e402725608ba91077151d1a9d824be949b775aa0271e8e82896782d234bd858921beb0ce72851aa0d3e9ce20de0d39ce89b38585a88de8e756ba8efc2c337dc34c02bf6922c3bf3616016f2d75d38b29abff219dbac0b134c584d406b7ad3e21d301a41d998fc4cc848fcbdb5e89243231d0380d7ecea5cf97bc1ebd783ab1207aec2d88f401d553f3a493362675a8d268c891a035eb21f52082af508f78d71ef671c84fd88b09feb50b65d4972f9895e347a5d3b2ac4efb99dbcd51e00076d92165f6b9413102157f49f200a3f97d532194074ad482a106871e4876f1709d517828c2f43672b0392e6a8c5218c0b892c002857095907d9047ca098dbb2dd1b798ac17f31dfd2305c14ab29dce599166c1cfcf38209824ef4bfa0cccc82a4dc340219d38a10c108e8ff0b061be3452635569b5bbb2b16d0859f16dd58b2ada663f3a412fc0395a6ab04b7bfa2978a56899c031be7aa4cbed1ce3c3a05efdc9f243238ed538e49995714a48c68502ad7934190adb7ee0bbcc6c1bd11a8d0a811f6c0a9fec5379337b39128768f70f70c845b129228413b487da43b91dd9464c52066bf4d644dfd23f1eb767c0a8d7b3185e157e64e341fc83857a336f075eaa869b280e047f3d2ef04ad7a14c62f5f36309fd5351775b23ee75c76dd91b012945e7254bc71f22f06601d3223b8b2557cef83ea01ef147df191de714484c614044c4f5e36d7e494758b5cccf94d1841f59aa375d8303c924c3d4e1b223338e73a81c5ca1a802b3d424df6620141341cfc435c1c16ce675f4d70daf14d2ad368d8be21ba5e2cd5bc98efc5bc783105a995e2d80fb0ba9dbceae628fed6087c566cb5700b245c7ceeb4bd8f29edd6d9e4a4755938315b4c25a29dcf0ff9f6593b8ded3c28eb9a47a05b29c2f4f26392b59e7363ca2a0b65bd322b2fdb35a8e9341b6774288a057554574be8d8709d0f4100bdb8d01457cefbb4a305aecf6250a682f48c27d11a9f6b22c5da86fa873f0ab52a313a34ee3bd37a52a7e639d113f2aeb1b1e293b1b5780893eeeadcc3da0a019c992c0be65661b9b5e4002c939100bcb544a5884811c2179438397d40079870c5363be6ad7a16849bcb13fba0f72013509a4eb7ff960f2285d5dc40c739066387ac43aa024b6abeb6cf40809c9e7f585504c749f71e0e2873b9421d6ad4e5b6f22873f0a605ef14a07b9b6a945197e0fbf94f1045e437a057c972a445819c13a70c3b2cfe7a0d9bb2abc2239c7b8b37c89bb1a1753e5cf186f5bfa3c7e6ed870502410073e2eb79fafe9b98baa1edf716943ffde7a47938d995dc19fbe15bc634768f237160bb790254cc99c504f649887b2563e5736311354a41756650dcde11e070c9d21c5ada731654c65ac68210840415cbcd179b050a3a62188ff98b3e851915562b971adf705b74a375468cfa588a655ec973eb6003261106a3432c656103ed3a8912ecd139870648c94996622463f14e1a2294296f54a452c400d24675731d08f41e91ee913ae58637396faf362966062907fe9807d7b876f381c537b8a35b10bf7ede02323232fa9b363bbee97b0b80e64b9189ca02563c65df9200e471ff8e089c052a308493d0a98637d290d744f824e287fb6a0fc11966ee1d0870f056b0a44705e20c518820582d0c80efae0685cf68ea78b823111089d00a9621f9f97216d4728a974414dabc454d8d8173ab899fb2690e3f77b2a60c3aee7dcbe64fcb7e813137d63414a79cded4bcea85e8e9829cd3193398fcb3346ad852428a053fe521495d54e3ba5a4d48be4c71102c8f4e22b64f1e9cd78ee561775c68918185d235c4e8f54b023e1d3a8a9ae85822d6eb481dc5da8a658aeb7a2dc836d45234f2f59ce92cae5c58543e6fdcd204147f6b82ac6602605b09d861b3c7513dcbec1ff1dc11b6da735fe1d023c7ef41684ec00f53bc614f9fbf475f9921ff0a471fa3b69b2ef18c49b56d15e86b41f0d8f4dd1c12ff63141d41b42e3f50a5d1693e61967bf10ef1b0f44900856166102626277e3ca7c2ad960277f42d5d9ed45faaf0fb3b643f157ea3b18dbbae7f6c5a35d7d198821cb76c73c630399304bbae044d921f31a83bdd4eb2f5302d6994434cf4fddcdb32b419ff730423f2e9fc32680d6745e223e613c232afc02a04c5d7c99a3fd6a4c01ae870777f85339340613503199d81ee8f740ab372c5a53eb6d6a36dda6ea5f8390a27458065b1186c1824ea5384f838a46b4ee914d576a6d100ec8553549a98111d0b5c2c67b5a4d1123ff4a3117addcf739109e2aba5a5459e7839aa03d42f1d84da0f4e81a6af9877d7bd7b09e60b4db280ef2b3158d15752a39de038abc68285c5dcbce87fa93aae915a937ddb3c87b5c18ed69ccdadd3c3726734cdddf847ff65991b9ee51459197013eaa1fd8a35e0cbcb6a96c6a5e0dc2bc3aae66562f81de0da7612768579a2f59253ae299124c2879494882238c258925286b5f8edab47411460c95bde914402b76d8e683474bad3dee476736a7a5ff70705c1522c357145a12614251fbbd11a31eb296f1cc3fd4c6645ffd2b0f6a5b5042e9a073c9fad059b7fa1e63b7db96354ba9bba1997870575600ab36a70765d790133386463c4de25b778870d4aeac48e05a35acfa660661a445923602c853ea8288f004e8da4307fed0ebdf1e25b25a816972449e554bba56dac67ae335c4c197adff913bf05eb3cc15fc7fba7a8e28cd40de4cd0f39ba94a47760c9d3499ed4a5ab3e786a751791ea19a91b929b5f52a0af5de6a87dc0e47a1564aba9ba13305d1ee5dce272de12fda0d9ee2e2d2e82f1cd85b7863a970b4d9f27896a73d82c1f7bb3a14b9f6cecc07d5fb5300ac950f2f368636633389455624cef2bfb304bd97b3260b1cecb123f3eeb9912240f625924abf144e149c1dedd70a928c7743a3039c01851d6626c4a056311e5e203a304b8cd0153ae9dcf741c5337e901176b18a2f732b8ceb722f525663fe055d4acf28bd3cf1915329fbcb0f9655c0ac524ef3c7e328b6238e431063fc59f78f63899bbff30271a6fdc62ed77ce44ec31c6bb5c7d30654a9c7f12370efad13d52cfe1d935ff768c7cb7707fca375d2285790857bee20747886d3770c55ef08ffb7872c0f33c00d03ebee32309cb576b8df2f11d3d8cd94accefd36af31c7d8846d3a619a2070f5430e12aabad2c28843f70937567e0c7ad283f8416e58d55d24cf265136995bce7469ba9eecad7094f5631469caf8839c19527a26e206bbe89d1b594bcd4eedc23a8c68829c27107b488635cc2192279ce21b7214ebb80652ccb2adc18cd676af8ae4fd8c69da777e71949f9ed0e6284c021aa72f4c45379c4347735d92fd0cf5ff1c0f03f323737528abf7ce2623ba358fdbda1a0899d04bdce7781e1299590351aaaa3f9c0828a0e01bfc51f952a8c777a0aa8fdcb853bb40e96d8ac25b80332ef04ea0eccbd03bcc61a6b12030dc0d054ae5873ed05eb2e1e652fb9e445e9b855a0ad19320e83d75521007285ef40323c1652fc5753fdab693e6b297eebce41f1b349f1b249f1b24df5dea679d9a578d71ae895b16d0efc0bca391327c5576a6e94e47d79b670bd4d7f8e97a04d1c9b606c497c6c29fc4c243bc4cec8aa1cffe2930b8f789110107e0af3992a5cbef00745263e7c8ffca4d2063f701b8fe45cbba9de4fd81afeca8bc69c16b000483cf500571543bc76d8b34d64bf21b424e4e6e7eeebf0e5e7be1c32b70550919d38fbd077d565dcab90599ce9b2e2cc2299a7d39c2d633004989c116a8bb89052cb6833784fafe6ad84178bc82c1f795c281b914f409c3fcde9b3bed5690f37d32f9d1b0243639c58f18ee9f8c44024f8c8422a823060b2f77a4d462ace6b6bd25899747b7b9b2a1d37d36534355b4d58e8ef5077b0e9bce3f705b365efc3c202e670143cf5aaa10b6abc74f8f6cb2f27db48590dcc56180f4974ad49f5b35ef422955f9f3cfa1b1565eb25ab393bf57c7caba913ee6b958d53975f514c07ea4ddbee8d4d32d48e57b9a759c7b31703dcf691a0e05379a1f9e99fd0fbdeb24b189f699bc56333c0217ee00d55778b12343de934b8dd6ed1c75af9fc5447afa5df38d07e70493c357940a7fb51805a1d7ec60207f00848601935206f29081ade220020ac0a75859b7cb4d8a1f038a63ba654535a56a0e680c1d20b503ca4ccf22f9eef6317c4134334c8741dfa5e9605a4270159488ccb99b6299fbd770c84379444561cdc15199be815b9a0fde325fe2156e9172a0d9734e1186b33539197571d805f63252360b108e21ee41d39ddf5e236c8fe8f9cd0b9d67e4da11838aecca1602f949bc7d6bcb202228bd7137bb9def02b972e0a011ac87735c9d00671b12e207ed7291b45c9accea46c607bedc3671e1e51fa359ec5f4b53f279a6a784a7f6cffb99f9ed42d889fe615dfd06bfde619949e8e2f45deeb05a2a878746c79f5c5a2774b1ea468b6d0920507c86cf1ec85185fd605dd45102b13c4118d64634ed686a48d7eede74437a456c44f9ef3549c8b3d35fceb6af049dfdf905f3fc22cf1e1bf9ad58d8fb15f4f06bafd1f11853d522b902f404cab3764e365114c6075ef969df26a0d7ce0cf68dbdeb46c6ed52142ebfee76d305641f03fca594dc4961b50699db2d4af7a6eb90bd1854d4026d665022d1ff6c03ec07f604cf08c6831d7880cdccb68941a63a94693c4a30fec3caf919a1d9d3750631479a957f3f720885f845cf340cbcf0b589c4a1d50fe0b0bd6020461cec5ab006b24b4ce88b7d0ab9110fc37a20e88882ffd0cad794ee9618358abe7eb6d0e7985d117bc550785c1979a09a703ebcdd2dcb3d4dbf674efe06210f612b907c95bac4b98690d571804546237d853aa3183d5e31d30ae3ab626c1d6881fd75656a82562989a819d3349016952fb4cf4da6790ba36c59fe48aea5cb858b70ae3e9e26abfa1a7c011664b9c3ec9c7a0cc514a57059b867614ca5010f015ae43091422b717e54678e895144bd51d09f6304c8f4cf42e97d9594453adce4c42831035f8d974773dc8efc0252d64736652c93e56b578a0e68f5aab3e25958a5f1b53f6a834e7d9c8c0dc93080f182803de069df811974cbf83c7ef3aed8c219ac1aa8be443ae12028be99bd9799f87fc4e38450122927d588cd675a25457a76a31976e5a13463e77954e19238331cedb4ed265386918242b3d8dd0e4bd77924d549763f16ec05213f1ad4f7638451e8fcbdeedda32fc7b6551250802e22697cb27991887ea0140f6f687c408d3c655be128c2a8159602f863c303587b8547df2b6a023ae032895e97bca149cb5854a70afea199aa1c671ed12fec3ba22bd3a8e34dacb0be41524b0d9e33f7ff574700f8b7f17b77cee03f4eae154fc1b15b0f8eb3e46de433868a8d4132cbf2a6a19ea8a2d658069c99f56d64fbf66ac976bbfac6d81797d58f68cdbe1a5ff298f6b5c301b4d26fb7ae07598cea29d04c58ece6c1e093be816d77398f2fdf8df74904c681ae3b0c579f2f70ca91ba244a359e7d157c5cd6c88c87fb53b4dc157c142192cc4a958f3b2d0b24f3c6653494c9323065343832ea725a70df0710d0a24811b18601dcaed9d47dfa67c9bb06477be92edbb4bccb6cbe12a6eb8fa1631e1f5e672aaedb8482bcb547ab609b91ab8049bac55e9cb8495f79972b62c6ba4ac647988cf658eaf66e5b4cb0dd916b9ccc63445c8c848dc36931141f293f838f00aaa68e48ff9491d68b9704f0ad859c983398d39db5664cb201b7eac5886d3ecc0abb181d24e538c4f06d890a94d244c9c993973e671359fd5eee9acdca0f64159c73c50d5ee8fd2b9e88a5d347188b432f5e1e41df9973dce0017022d05b4c018e0b416a52a561eef4010126e2cc8013fbe6abbba1c723a50f34616fb40b57e644a411ca0b77e0856bad266bd8335fb99964964f5389c05350471704b5721f2618a48c92fb71a097434eb133e10350d0b3dec814272374d4ff3ca4e1f1bccabfce4b8924702470e33350e8404831cd7f718f2561f29131ddb832d584d3c9ba39ad451483ce1fae1f0d515a9122a018e3582f11202034c9a6a71a0176ca62a115db6f6bb5d667e55444834d5d6a55663f2ef56851bfa211f0adcca83509660c1132218accb9f443d848b8086c0b860930f2c931b8be16070f3bc67d2db39c64847b2838f75d82d1586ca9bab5314044acb9ae39ab93d977e68d55413290c39ebe944e40dc4e1ebe96e94595d70291aba04daa1be0bb0c5d8c760f80dd3953f8230dda69aa10326df2dc904035d69d57cee76a50da549bd9697296219d54ef90e2014b3ac727f5b83c58db08f23dbc1d07909470b608e7f92dbf9932b3279cd15337f22f0fac1130112ef6d22b1a1050218ba5d5fd372059e01864034187d10b31b10b3d8cabe72f7ceb876bedf5f5a4ccee77bf73bf4ad2f12a0915e321d0455ad38c41c82ab1e9f91cf427b0e418b34c44a98a83244dab79a883a8f2b8254164d5a45aab1d102caaa5bb256e8ad5690a0924b84d834a8cd82da03d84a180aba28ae10e0282265b6fa9842cd5ce5df3df57dd719344741ead48014c24d90e19cc3508fac963c2482fdc9962eda66991e155ac592add9ba1d7a19c9f8422fbccce6c3c99a893d00faf938786bcbb57c07e41910597c8058d20c925cdc7fcc40dae79900e87ef3f5f99092073ccfe464f1e1f7b298366cc510c195cd626fa4f61945ca51997e34e363c3ca5a05e5bb8742fb57df8608f0a5ed56261077d850fad2c5c31d986733163dec4653c06ef924ade4800f9d2b59713f76a2fb4e5d327e08e3d2f8a9d4f6169ac8c6da2d466d1ed9b2b1adaa44b2e543eecf47a33e52c2d386d5586e54f324c5640822538685eb43aba35baec564f5721c7638fb86720bb6a5fd172d673e7e6aaf6996cf85dd3085c9034411d4751a138e5c73615d457625d7604d07efcbd5b37b14389c08336a82aee5b17d72fafe79443a404c6bfaf34c733bb6dc8141ba1cc80b98613d617fb315aec4a7347771dbb8b9f6478e23a91ad70ffb6d08d49c485602b1b14a7cf39524702bd416a1c0858a5774617ca952cfc792e6d412666d434be355311cd538a491eb22dba3ef22c2b6cd04fd225c6c85bc70947bdcf05d183a2944a1234c3f7ce9be4bc07eef79607138fa10c39d63288f19dccab87b585b80b729979bdeaf0b45d26ca981fdbaa6d4cd8c9ae3db49666852fb2c5ff306ea6ae5f576b94030521d31e9c13a4df0dc29236afacee35208a1e669f310de0d50f30c306afb03b53a21db6d36d88986c44d6d83a784687b6453833ad3eaa49881685f7261da92929695abc9ac462d83564e3a706759556bb872183afcc0a83a0b3b93b79e92926c86125151b2ab0659f505379c49093f273b2245e13620389430881e91e1ae380bfa1554d0023ae865625b3052b29a0e65b0802c01264208ddf6249d56545ab7552366cc480dbb787f33b52d97632721dfdca9ae96a0dd5c6031988b8703608a41dc02105c8e343513d7cf1aca6676603ab6f770fb38f06839a1a116d2f6de7bcb2da54c49a608066a06db05958bdc16b51a6326dd5dc2b4387f9829aab1d15ea0cb96275cbc205c78bdae2f5d292bf02202e7016143569c21c2f2c7fe208dd3efd379b1f11924a18acd408c71847234a1c8b26ae377919999bab3b333575b639c48f3d52b0f553eb35cf77066c1618f3e95809260524a29a594724a29a56c18aa08461cb9b8285c9dc1368f148a41a0d3d84241bd72202d02f5d8e1a3570d0bea1f9fa59a8ebd968462509258ccc7536f43dbeff22202a949598b03851f9f9f18909f1f9fd88fb5d6fef8008901f1f9f101f2d323d66852445f99854fe99994d6080844559670f4cb96524a29a594d25a2bad94544e2965ebb06c96eeee2a659294524aa9e9008ccc06560a8c1e6c68dfba5bd60b8a98cb4657fc400c2fb824196d59d29ccb565926b6ff892c799104438ae2861c454fd4d1efff427ab97f6a08627451047283c581c28fcf4f0cc8cf8f4fecc75a6b7f7c80c480f8fcf800f9f14cbaf4a0e6b667f7df525b68f6fd9e4550487f44b2efec5fe81fd22bef18a3fcf844b82a6448af5c08114dc81022e08f1e3b7cf40aa6458b160a8059fc6b9e0621c4a565826cedd57b4a78dce0a13fb9c824a0ae7c18861029b255bed19c021d4408f8d32bc961617f749f8f7cf93b1aa7470b1ebd73d353f12f82fa93833c27fb2790fb396b7a23f64f1fd6745fc6003bf863df88e9516fa4267a342ea0def43df64fcf45d4f7981e0516e939bd45812ab7892dfba4857a6e75308ffa1e98478146a8f09c1ef5464c0f03b2db74600feaf428905bfda68f5ba78f5b3cdd1f39997878a76f7b07f2f08d7c4fa96ab0b01464ad7456aeca85a90a8a68f28f322cfde93ff8a64b037550e3786c1793f86505b681629728c6950f8302311a27f6f01cd4cbf7e1393040dd2f01ceb8305be13934dedbe796e9d973526fbf27f5168c7923a74f8134dea7fe48ab27e6655e0634623f06a4f1401a17623ef53c319f028b50f1be088c09f4409e984f7d0a2c82fa18f0488b5bf1b767def1be460cf5f68dc0fce98da0ded6d846a818393dcc1bb15ed846be773d3076e11bf935acb0308f3abdfd30d278cf737a98e7b18ffae8d178a00ba787f91efba867180c0603694cef7d185dceb1c8f3bc0fff16a1c203f328b0c80066e01282a80503aedc46fe0846442d6e516918ec65bef1b885fab8c59daefdb8f5c3f447ae090ce34dc9a0eff449806d644be123d7956f027f446f4b7d6129e88dc0f6471411d7dc17242e5e63bfd813bc14faa2a085edf7ec179b310930573e909fcce7c7c7e7b0b0d9878cd4a3c244b1b47bed7b3aedb937b27d07f670df7d071ad19e03b9957d8d58cdcd7ec526c8a7a10609e9b434d0866de4d3505b390930e1ca472115bf7e8d60caab28f04d0a0bdba0832acf86d992cb9191155dcc0c29966991a7ce4cc3a13dfd50d3b4d73ed3aad5ac6635ab8161ad5956339e1fd994544a29a594728b1439a59c50967474542c57e9488077b4c836adca410ecef1217d78ad96697064063103ff0b2d70e5d37f77972ee3bc2234c41c2296a466aecc06472c3879506c587b84cbbcb23285e6b4414be7642e21de8934b3e2414599948c281be24c34c8e14e1ceefc705ae1b9f8663efdac7c9132d4a0e9c537f36586907597d436f3bb181283a8fb22e54e2fa8e84e2f89953bc3ec756717ebd5fc191aecfc7073a1f826c2dcf83303b77f520ddc9ecf4929a38c7d7265475660473ba8a0d0dd5b8eb8324cc9964c1b542a7e568f67798bebae7eddda3e186d66057bc30c5cf9c516274d28d495b741cb373ecb90af59a74829e39d586c96554b3489b24934896ca5f2a395f3a857fdaf5075bb67519c4c706c43a5e21d15efc8a7d56d8e8eaa09396c4cb0a9c13935547ce3839d1f56cbab1865d24c228a21f958c19c387efcf001fb61adb53f7cf0c07cf0fcf0c1f3c354c58b99648b175b8ab26469e1b291156478f185d111ec46adb54a67adbe246cbf7bbdeeef387a50ab842d1162207152caa04faf7e7ad5f1e303f9e18780ff3c33fff800e955fbfc74f4699c78bb81f4770bdd3f3c87e04265e82929a5934ec9b334a43d823f7cf4cab7d8b05b12122312b252115812cc87b747a01b666739062ab5d6396f05555d18367e683fc638b6412daf7ab256067291566cb53a09a01fa6510d868c88b8082cbc904ac5c0d0d4541dd4ad117e37b4995aad60065a8992268f85148e740a3519eb44d8a7aa718ba6fc298bbc566b3bc6c25844801b461bf39c0fb04dbf0c4a362cddc6c17b00ba1d896a4861bb1948846ffabd244bb77b3201804790e91a427f638c32bac7d59c524a29a5ac4938c91c3233c7088633c6586f6d9cd92ea504a3bb8fb3b33333f3f34f779f93098e6dd02eabdaa6a16ce33c7de6ba0d490f5dcddab7982cd96b65216e1b87637cffd8695a707852e69b78e4cb0844506aaaf801931164f84110165c2c01c0096b2f1b7111e302690b2515eaf2b3e72083472dec005a086531c5ed9fee4ac2b8fe38dcdd5d5271c3d4083c58c282085854713be6091b53495c772a4f14218b201031059293255c54362c969470fbe3111e622d98e18b25a6e0018b1239e4c08b495d08402b41ad5d134d341bb82d311eaa0d42db163274c884ac683730a142aeea439259832b13c2259768d21137380b342ccd242c35740d4732c90957cc614108c96db826f50007166a28721babda22a6d485ede18b9042083fc414343c1116e6b2d1144e02803405954fc11457ba207a1b3fc0c10d9f1505c736c218bbfd128c1c23478e71ba4f9f3edddd9f99e3cf08cecf9ebf2019ec5629e594428610e92211a873b667bb7d433c1e71dbc1cad6be21447ad542c2b01148089121bdea1fb2f1a717256c39c2ceb04337410350105bdc70a508586a200586872d4fba1d4c4dc84087212bb83cf105cb96259e3341050ac228e28a2b5eae1401b5434c18455051c3cb0b103730bae207312760f560861d980650bc9060890e4244b18406475c2c90c116af1cbe60117ae9409b21891c24b2b881881a242185881918d9ded14d846410ea41092520b41022092396643c3cc00450849a50d2c475850caea862124193ee0143a908d8175eac8c81c410b3893351042c08207088c59888c1154d6a745b6c0b6c7f68ab74411558ca50c1165792d8e008ae41184b36a8e1063e40228b11572cc99a87fea8002bae6c21c30932beb0e04760c51052335536115c3f419529505cb96107323882728222c890226025134031220b295390200a0c8e20a1022f576cf172c1acbc002935c3e54ed5ca2a57c6a0580a5a02090f455034115664e99a87c8748976b99994bba339fed96308400c15f8b6b7840d55574a0ff9f9eb91cb201de3ab8909b2fca18a55f16bbc1bbf074fe748980d2b90c0a1736394ceed828e1fc3220769e6c4ae8e8b7fe6204d202efed1d012395c94103685c5bad7e72f9c42da0d2c3f08ba64cf5e0c92e3835882bcd136fd5dd339bcf2c2fe0d75685cbea2c3971bf29117cbb98ece0953bc3ba473289051ecc9ed76981137e4188dc5a2b82177c171c5ea6ec1ed21dc1087b4a9303e232c778c31466721403a9318638c3ca59cd2250faa9492e365b6c102cbbf39b1fcf2fd9db3ac726fd10571bbfb6b8dfd956f6275ef5ec5f75eb67fb6ee8e069ab557ff1d114a1c93c944402c61b22602e67426bd9a93c3aaaa6ee34ad006b34e41d8227e849756911862905addc7905a41cbfb22ac050f7c88b56260268610238756075279214381773af4dfbee7ddb64f876584f5df60604019659411c5f4f28bd27d94d3ef689deee3b68f2f8be312bc50badb6f4a562e1b2949815d36d202e9f6f01cd3672f3bc9f5519ffd346efb88e3b9fa6ddf71fbf6786c271047dbf4490359f1a6bcd8bf39e4e06e713350e763b94dff8c1236e4a31f1468a117d07d47f140a16f75204d7490c62444532b8e8fc585bde2a2721d8c5da489dfedcf59c0124fe3b051154a49ae167c08e74b0ad78d3f9da583a328de689cf85b6f4fbc74a6cb73b449d438f2c1d58d39ab367f9855c98ad116140723ee7c4fd239d9cb0aef44a52a319f82e440c0fcc9cb992ce97031a201c5e1c5374a53908e5e4b4b21ebbe58385e9ee331f19e1c7940c0bc294855624828bd963c57c8ba2f160e96cecf10144e40e22e5cae6e9c5edeb54ba7c5dddddd3dc618dddda94f778f364b8cb1633e30ff6ed7740046e6c30fac8e1d3bba720862071b651307706c239c44b72d58abb0f199c88763458ebf707b4ac1babdb63d7fa87dff0b1f83a19602f30dfd4ddbaec645e1dea63a87036db48dff6682067f083603ab878333b1229b48d101794a5097532ad5bcadb2a86b7927a2aee51dd4f5af7147f3c75e0ef6f9c6fb061c6f68e35bdee96766666666e6c992f9fd7f38933e3066666666698db4701e65886cc8b1587ff8e8c1fa78ad3542118b6e64db96659c941b2725a755ae7295dbbe6e9c161948d3836ff62af0cdc07abbec0bfbe9f6b53b72eb3764de0dac806b927905ca5ecd395fde25120d61437645268de38d9335a0cae0cca0f1fd97759cb0837752301f83ea1c4e934fe3f3f1b5f0f548813b78fa84a8e37209313060a873db8221eb04860c862b0fecc0f039700339c982216b6900df2ce0f6338c617782748c3823ca2cb8f833586037a02d6813da92685278a77ec848b24be37c691ca019d438dadf190e0c35156852f8a689ee068653e8ce20e9dd85bd14621937945dc22944a7b3cb2e9bcba3c43ae79c3229e906a11b92a0c4e87d61fd3b7f61b13058154e0620c5dc2bffa6a45995fe71dc2639d9799ef42fec78d8930723a5fc826c92512c5de6fd0b9f068d4863faf775308c40cc8c2e45a393f15431302a15caaa54aa93cae4a954aaedb5bd362738eb3176d3f8eefeeea0cb65e4a434be0fc5f7a0d839a9a4557644377e96a5525615b3b5d68e4aa9a951bd924af3a15bbbfa5cd76f8f470dab07c57a9406a145b657f4a307e55473fa1a6b512818184a7fba6799bbbb478f203f2dba0118c3fbe28624ec2c8694724e4a5535b5569d1390b4703a5da7aa49a554f6744aa9ac85818189898949c9c8c8cc9831c3dddd65475535b194caaa6a52aa9a544a65553529554dcada946ace4929cdb255ad75dbb81bb6529ad9b09cf5cce362352de571b161777bf0b8d48eebba21eb3e7b82a13479ca761d17dd7b86c87a103aa775f776b7deb9c6715c673dcf33994ca7ee746218989818aeb5eba638f377d5360e43719ddb7d5ad9ed75bb7b4bef097e30a4dcc1b0b31685b226eb81613d306cd8c574168a75073b9fe9816d96e9dc4fd7c1f002b7c1b823b6025b35558dfe1112877c07888d9492ae732ce87a90f864be8c7dd447d7a30f6f8185155847f605e9550585b88d5310035c82bfc037fe4e58187e78a7e6fa0b89364419aeff9c9af070dd456f02ff512ad842db340bf2059aec7947fbe8a1c4bd7fb54aee6efda32bd3e26313c28651886502eff0e5b8c69045e5644a29bfbc0c5ed68db1841b85b884fef0660000b81129f46e7cae1d1806891f88021b3f2b810da3900af14814ea4fc69bc291cb1acb59b23e0dff06648cce72d6edf92dc69cb58b418d13659617959169536a7a0d073bbf67dc21342bd00b86315c06e5d2ddd19ca6c44611aaadedbb8cd676a3716eacfe55aa2f071bfe0ce5f561135d5c8c1340972cfba1062ada44e07a649bf9dcb66994526a81eb1b687d08deee2ee79c9e65ef31ca8eee3e25a5e017a63a63e618dacd871924d6bd57c8ccdccd20b1da0c921924b39b4142bb1924593783c4ca1924d66f7c4eabfe838d1f767703f10e69f619247688cf20d9ba4d0533486cbc33482c6b937651aa1684f7e2e2e643337b1dcb5615c57639a70f72d2acb6367fdbaa6f7163e99aee1cb76931e9dc80f8c49ef8a51f8f8fec495f5a9fd42727e83cd1919b104e409da0c718637cbe911bc77dabedff04cf91eb4c52b26cbe1a5a2a484aab199d9de7db945bcde8949ec98ff8a573f9592b2d4a29a35633bab916dc764d27af276e73cd4801c736c28eed92c51de77d03798e0746a00e64079fe87022ac57bd7d443c8a10540d9baf003dcd36f37deb40bdf4d3d8667e7c2243b1cdfc6eb00253ca96524a197933493a297529a913eac4afcc9c644e6475529db8e6647330c88f13a5e604cc99d50eacc0f58ff3398d3ef79d31486d8322137927729d496788283f6471492a484a46c773b48f400cc4400c04c4b106ce89b0b884827188db3f64544ef74c5d112c1ef5eadb1ba881600d046b205803fd8cb014b8db404e804e4e4eddc6694559cbc060abf57253cf668f9248ecdfd8527db3e001adda0dc58205bedfe60e1cdb9047ae8c49752c226cd3bfb2f94f55c3a2765297ff0883618c723bfea0e2c12ea9b186d52ac6bca418e3979c1344854e97d73f0afc4002e213f3eb5f0b3d8e7866783e4ed0d14161c7fc70a83cd634510cf04dbf0743bc3f4f946850b5a80cf18410e904f4cff024122b6fe468706f2609722b062635a3e3e768a8663a43c9782b80a9984ca56dffa1c0ca44fe18189a8ebf5696cdd1574347ab497901bf7d351fd89a0ed60645c2075ab877ee9d7be7aab0719535e7542d44d5b89acbd4031baeeed378345efdae99bdd5bf8ab2d1abfaf3b3d7fe46c6a1093703695858dde93dc09fab345bf6d25bc19f0369b6edb7ed6f7c35206b3ad8c87a3022c2942b49a64c4925d5b870e631a464f0bbaa8413fb43e48d1f043785843c46d0a710ef643c7c04354813411a4a93f5f7f83768840a4ffdfe9e3652bf9f5b1e055dd0def434f3eb6f4f13c19efadb576e6d200dfd13d1047bb437bd06d2649909a4a1208d0bfef57bfa4fa011effb2b68840a8ff7fd46baf7e7568f6684dd46f6d437bd093452dd464e24155677a2aed83aab6449e692dbc8dfaccca25e4998267682d34ad1d12ce21b94141b4ed886c43bf3e5d665e95b8a2b804dcb95e116bbe14afeaa05c03806e0957c49c13a612bf90688eff3b3f934d984c99f5048ee4abe39aaa5a965ca95ab2e96ae9441199df26f4829b97a44760d02c73642ee75e773a6245276bd925c445db332e14b87a4e46ae3d416a74c49a49414883ba9943ba96b7ef6c56e1797a2df0a692a651fa5a7e78f3f893cc73f8653ca8da6eeee7f538b14cf3175139c4824a4801be21d7a2748e382bfe969b2ef3f3d4d4fffe9fb4f208dfc13d1b5204d06f6f89b4ccfad909342bb74cf2e7fe736b32c7c33fff4d1229912d80d9c48304d6c6dff52bd9abe729b3937f051576c05e9d05394149b8134d9b3aa5773d2d9857b125660e7d32a9df3148aa659a6c64403824aa145da4f97dbcc57a9c00a86fcfa70881aa7ef8c4d2512a6d8fadcd7e7c01952d7eca257732a4da4a965be09c8d2ef7a359fe536b5740eb74c32d8f9d1e351e97c9a5ab3a7aec66152e5cea7438dc3ddf9f4d538593a072646bb0071e78774ca9db4e84ea2194cf9ea22c50d29963b29d19d5f33fab590650ace6c1bdcf9ab2c37bc71e7e45e2819382d2a64efa06bee1aa785d52333735556596595d2a57c8d8c181930328ec2602c1f2f71050540add30eb6ff4464b9676f02dc9f9c586f85ca0561e9d5ea3e6ecdc46c685bfe55ab1e06715082f38bb2fd06f28b552ff58135afe11e79c814e8cbcebac894842a7fab9511ac415449219dbde8b0640b911456968041cb9dd0610aaeeb003e8c2f6374314696abe2c668c1eddb7dc471e37480de17b8579104eeb9410e3f15b22eeb5cd58de2898a4efa2390609880096d020c265a1333a440047dc394095eaa30f2b284972a920a1a5248a1194620b4a3dd5dedca80daa518b4c5a8a2c3a56328490ea11c425936e79c3387cb8ae906da3141f8a29b52450e0dc1400e3173103333771095ab5dadc4e5449228442917fbad0cf6e2fb17dddda95377774a9d3a6da7d4a953a79452a79452a794d214e29dd4d59d6447d330882acbfc67c711156935c78bfb4eb5d6ef6618efc7f574fa6eec660e2539847228f142ed4d2fbf5086d3c2fce9abe7a0fe048643e43d3df733a7b7603d813994c412ea2987500e25a7490484e99b5264209adf3ffbb9ca24cce1ba358712edb33587508e2439841a47490e57ad395e37fb6a2783cde1ead5911cae1cae22535325b8a36c661e04b81b63f420a0a3090cf1c6ded15547138eeb66d1dd4c1f5ff3dee4813a9a782a789f3dd7c4e311e76b62e995ff3cdafae517ea78a263e8ea68a2c3d538f3e3eb60a2c3a5a3898e211d472620ab6348c7908ea1d9c5a9850d414642502c75e19501a5a59cb56adad0b66d1c4729a5d493a64a4f276bdd524a29ad30eeeeeeee3533356564522919198f4783fdbdbd68f494f80955b755d755b187ae5fe5386efbda11ddeefaaf9c35e5faeb9cd0394193880a26ae572237a262e8faab6c6c2b346b4f3fbb13c83beaf100415108806e7b3ca47784328db429a56e4de1110ec46e83617ded63d98155e2206909e8d6d73e158bd153217b1a2f05e29d17bae7be5e8efb4f7bf1cdb48183f014b39103eb3de77d369cb0dd8733b6cb49b38efbaa66ba3b7c26350e0df74aa6eefc393463bcc3ddf9da8b7768dc39378f0737e78c3b7f8604d64f3115b83b80db81cc371c183e43a05ede88ee7ceda3610194604753b12766b44072ddf75784f69d9951c4baaa9a9afdfc1b9d097c90dd26957d2a2e0b9b5da894adf824ad644a680000004000b315000020100a87c3219150248e436df50114800d6e96406850321707634192e3400862180619630c31c000630c410691a2aa0d00ffe0ccab8c9176b2dac0dc7f04b15d3d286aa8ba43390d5822007e77885ee5db24d68ffabe04e229bfc2d5d3e416ede6ac302ff663ee64507444d876a60cd4054849c917a22db79d7ca11cca17e50b79570b5b7a25a87ce19504ef031b48e22e1ab9b8e3daed38048d57d441615a470d6f65b00af3ec903f162acf67517542e73babc83b2f3c4175f02865dbd748e3c00fee0af535a4e2005868d8be9eb4b80cd835aece6e9c50778e16d7a1d247003a7c316d71bc4420e1179fae2c8a989ec4845296d9ce5c1d030828e04b0ffc5eb016f5ad48b11aeec8f607a39ef13c78d657e4f69382dc3fe7768392c47608218c58b0a2a3db138e97d481bd5340f98d72316bee70c536bdd3b399a1f82665c643231567cdb0dcfef5f59c7dfd5e7a7faf63d3abb4a81fd6edc7defa6ec7d0bb380aa2cf72e6ccb8ac478d1c52e5f2d5deed1006ad57fbd8b917195dfeb197478ac48b45ca81ba99e1c766de7b98354930586abc68d68784d4ff1f1708860ed2e9111c73af8b934a64efb6dc257ac680cd7f3feced064e50b7abe58cb6b60785360a77e36a56601cb86b33228a2eff6adcad3fb102608ad556813a48225926356cb2b937159c08296c12440a805d992d6693bd33f6c481a244854038d6ebb3796ab28d2d9b7db3e6f1ed288eaf4037abf67d877a448026a1f16bae67602b50b052f8b502a8626bf0a87bb548f48e41c21c6f328dca5bbc70f682d870dd16d4c83070c78bac1498da0bdd0a0a4b673e94645c9c4969b48f1d35803f85f1adbcf1b0612899efc55e2c8f5623a4eb3ccfee2fb514dc5965709c95e967713f778563d389f303b45d620cae1a7a02cf6331e838d989f3e0a644451b56f17c5c5c1d337b7606c40360f6744098856b3db9063bd61d3bee0862f350f94e9b2ca8cd82e8f1971d8e482889e53b384da41264a93254be41c2ade9deb0cdbf32ab55e61a223800d6aa736561adbc31c33bc3a19622e9a07d06fce8914659b61c3f1d68b47b8cf3d7caaea442c6bf3d4b3791586d6803bb3a3f8f878f5ac6c3dda7c39b968c54d2ed1f12023a7dc21e3a74accc10343f47c56e509d8a28085aa31be809f6f357334890cfa51f483005fe91b128462bbfe49f0db6956ef710e878b76f227ad07732c5f8aca3c0784d73ec248ad38f6ca5af88117cc31826b6f375e07b77fdbc7b6d434922dae40c9c5130d88a5601d6d486ba3f57543e281b7f8181722f601caa6af7899a1e858be5c66903b94460bc8e198abbbc8eebd43ee82297a538c5ada08326f8b8ce045acbaf8e27bf003db21cccb3056e07c5f2b5b8605f305f5285b8f6916156977de5090eaa069e4b1828ded3e1af211825da7f9d0ba55bdbdcdb68c6170dab99c0f4a09515ae557272422a69c02dbd1c6f4bd23465e470129761c2df7426e61223d3651c126ad478fdd9499c00f6948895570d40610db496c247a154ff1101646acd12d5b2e5b1c991ce162951a5c30cdfe279569a1e1bcbc54d71c9f2a5b14a62bf45aa9a5f718a75ed80c70b8b25d2b88820f863128e73831c234894447d0c6e42ca275d85a8e7cae967bd5ac5addd1d0c97b5b2991824bbc368388c97aed14554d925f1ea833387c2a7ca064a6312841cf1cd431b160d8665fe59e41a0f5d4194843ef3d99d41a51219272a424ce203a377e2ef31d5fe618b44eb7dddcf9bda0f3076137a776aa37bfdfb430d7eb0b3a0c75e21150f9589cbed7b2d86a08a099a97579a752e40b53f5f76c8623ff943aef180cf34bb49705595b8b5ffff451c123221b61d6d333c93e479048f3fbed1b84e6631c09724933125e6b03c0317f633849b35ce6d7bc35d390be0df28888aa20beb7569f0c32edef2a3949958272ee990d9242e389f5aea4df7d90688acb62c726c6f69b8b3211411b284e5501626df54eba84e5db979208e5f434cf24c0191134b5e182f25a492083c0ab5585afda80e00805decf58384df5b5a138e33380375cabc71bd9c344ad763d7161c4d999388b9f690e8d4ade06dd090294c445a78d556497a30c3605eecc97ad988bd1053ac2633d2abc8be6b43ea45a004195d2846387d5e1f3c8d2f104608441fab2ae58f0165792693845de9547fc25741366f7ba486d8b1797933aa27e20f1396d38b1608c1b1798ea2e8b78e16bdb591fe4c6dfd6ba90ad29085b6ab229034aed62883595a9c251ff5873e997242a203f2fec87318163db436834388cf1c7823f329fb5608309f169b0cce8d04db9e40f82cae24462553a4872ad7e1dafcd05ebb0d61c5912f25dd4a867de2a15e6894e056b28df727ba80050e691dc532bfdf457b7fcc85334c06d30d1fcb57f88367be1a0e433e6ea2c21a0d223d8d03e3aeae82711993f6672722a7ce9b3ac5c05d7f22131a143f46556a5ba69929abc214d79ce4c853b59405ecae314b84261f716fe65bb5a6b96e7ea28f2fab354564ae997861cfe0400d636e966ca50f49ba948783e4478d164be185a7af4bf3e9cf44e44c8b02c854aad008342e759dbeac9fea174396cc2473044625d70cc83d11d5a6ce2f764cd9f875bd8fcb2725a175cd57ec0f2c879bdfd99602dae66ec10e424fc5cb8aa6e9bbd061beb3282738f3bb39a8811e5a29b5154ea66c05e4ec1dcc6fc49c8e9274c021e75f1a7fed38bae859b5de3238e6ebfcc2b747f62d095c37eb6b0dafbd624b5301da445f55116667a4d34d74bb5f0740cff48ca9da930357fe115a149aea6edd60441528c8f46c5e87ee4a8665db49341e96e125adaf37a5eff6312b6148c929ff6ce2244b391659d696940d75f9be9f722d053b6ca4f7f83772d52176004fceb7e4239bc6ed97d936b702b64d0a827c1a97a478f39f06c249558e7f56a29309711fbba118e018ba0cfb1f6d22862be3252921bca9397b0880cc7dd2a8779ba44c2de6283adcdfcb16014bc8df3135919caa078bf649717075a0f6c227be8ea6789895dfb12a85c96b445fc299b04f9354bf44e870a71972c0549f326615570bebd139b7060eda8875a242c5c9c3449badee9c1c08662748715acdf9fc5943bea9e34e1eff75310abb42140dd3e73276a7135274051fe18955700eeb5235585021d38c6f5c9e41e9b5aa23a343ef9941bfac62bafc069d32f4f70c23c555a22884d2811bbfc7ac2713cc26de02c7ca00c7e574556f839520ccdb10d39c205ec36534ab21ace4e138d6ac34857614ad9931e293911a8e4b2c87111b46b1732f1c6a0567e938b551262c74e53aade99e06c05be55fde979bdd8492e4a8daa2cf82ef67512cab6345f40487676653b3e3dda7ce98f3ff29d5bdb5cfa9ce1484c5d660d1d0d90e1120407fbe537e9d496c203eddcae97f17424c8a2dd8fe808856971c45c336140a0ea24acc54c7b6df655a5c6ef8e44f3df0229ab6271c9c68d9394b0050f38181097dd2e19e8226984e1cbfc6ee77c00b021427274f51139499ccefcc5f40066e3b3e758a3728b1d67204165f2034930b9c963d04eda615f609ff01ca36c16bd0bdad3f7b029916f7b46203ea04fc7715b55cac1255c0ee170c64ff2b5d269cba1156ef43b18e9c8183d3a40687fb6c4165c774286d62a042a5e0109436921ee9063ce2d4fd938043c0ac53e44cd5660e645841d8f5a64009ac8ee3cfef91e27af745a292190ca3ef4a50fd391e7f863fcf562b34b3b68db880a9d851284377609898391c4d6b19305aafc16c212f23045d9feed0366a8295ac3b48b6c8046141a10c1d188111d5de7cd67d48a83c61d84173e58a79c62ec4192808322894e18201cfd33733b3c27c10be90b837fc3810b69a2435d19b24b9ebb202805a0a60193e970022c347291db464b70c2bc2f119fc93023c481d5b3b98380d8011dd3e03c52d9045097cdf6c9d5ea0bdf4673e49a7706ca1c6207fe3a27dd31012720f1be7f5770814a4abe963507c74337587eae014174814744ec48c120b096a182f33f878169ef8a4d4055b86cabaccd81e86b85ffc4139f57304ee5e36625b240fc00bb00d2cab3f0ae8021c28bcd4a4c25ee2c7cfa01bd410ccc05198c42157c170bc6f736d3a020e18a82bc6b46484daa27dee4d81847e418cd9c6075d4a8a7dc7e327a51fa8e575ac9940c9f179c7370b94e920a306df32255913beffc35510ec9d36d871378f7dd8cd95a6d17d5d661c07c43e30cf719ef5b2640dc52aff427145c062ccd26dc94692e01ad54020ab0c2f759670a1847eb8e4f9e45d701d194448a1695d5230daf75644cb7260c86f87c67bf7154439468f731fb5c548123d199e3b7e1d92b6f6a50f584e828bc2836d0539de8aae2773d1c788428d870d006ccdda98f43f3b9f38d471471f4fcb7cb8e3d2d2e93ca74ac701b7999984d721cc7d013d5afc552e62a00ceb1ef1a2ef16b3eb5b10a96814367b283d9b3b2f02c8687231b3415987349ec21451a08c0bd67740ba8f93d09d2fb790e1bcecb56d9f9801eaa1d196cbd241d9a25323c23ca73d58ece798b9a8774bacb9d2dd42265b59d6f5c940354d992bec4795e0ba33f617926b8bbdf59247ca744b0cea638470c7b221624e6004cfc564af0bba1b34625a4fcf7ffd8b28fbf072112e9889796976c37736adf445eeeb1a84d697e5fae89ed91b2172e9c6416a204b9ca68fda0a83fb00c429f757d80473175672ab5ec2c77c54673998ab304a664f8e078ca9af0872b11029c4b8010efc720d35b378a5aa321e2b608feba4220ac028adde00a2929c926ed85ec88c00d33b79044f135f007c91f43822c0da74f86922d66d1b81a1e99384b35e42e1c2938a39458000d7ee00ed8acf80d0672810ad1f507e54aa6d61ad4d285391f61b6b10c6cc53005cfa72e789557d30e9e661de0c79ebe091180379b2b53d42a01aa524501374405910aa7fe5b9696ff5197f64f135541ca027a639c19e224dbe34d656a80f5909b2e129878e465d9d4194be7788430d92432954eebb0caf7573467b89cc9dfefb46c98c66c913b52579fca24fb599009ff3e913217b15de4d24677c25fbd395a35ae254deb5893cd7b9d74c946b337a6526c933596d02cc7a4f85c45c166c6217e4d29ac5f87559b61ac3bb9c15c2cab7178cb6899006eeb84d7a8c2435b9d223b891366b26570725997fdbab8b79d7e890a1c28b86e3693e5c8262a48bb63cc67bfcab7993dff89b00b805020a94e100c054cd7a863c0bf0d308494e07ab84862be3323a9cf9f1fe5cbdc25c6346842e9dcb291373a5a4f30a22d39d005ba5a0b2a35d41a908e02b9822e0c60588d8fce8285d05581936eb43f61545acba3878a1c6fbb5626525a567cc695acad540cc15ce9463a9ccb195d25ab7657a7dd30ba2518452d6b710a9d844997612bcf705d18e30d3d1c244a1b5f602fb8968b9deef00effdd236c1d4bf236190b445b5b2c4bf4f521afab098b2d14964e00bad524dbed11c508788759aff4c7722ef32006017a686a91758e7be8f0774a3c395c60f8324b360c61ba20282f8aa5cc28017542e23436e0684e6d2c0b5b9621ecfa04d4a541d2ffab14ffcb2941c9d42d61c624b1ab152439a72ead415ca705a549f8c28f488634e51b6d62b294f07ae47beade113e8fa4dc1bf94507610c3f0182789455290e7f1494a81795e2eabf8b562389a30acea0e565d2d11b78a2b63c3ca03c17b18160f1f4623e7a2651c7c9c94532f916fa8a77bc1e13597689477d4247e1fd94694d7fc0ac4b7881d4ae798e8248c10b5dfaf6e1c2fd7a45100ee65b4066c645458e90f33afe1bf3bd7f812b3f8392ff7e949f965be4e1af7ad139dad5b292f51bb1ae9c444a457e1ae4394f7cfc6295519888db191f202717588822fe4848c3aec0d37dc09e2cce0770b775c2c80a0d7549feb8e83703ed4e59ace75a3c4288c18d3c797c9a518b186f850f0a07ba5078073428d50b31cdece630fb81c160d18a2475cadb8f8ab631c3abff814f4a21eac9c07f3a1592f67a13cafc7d5ed54b31617acf10d011ea3850d6ab953b7adc010f3d19f4a9ae1b53abe30a23b6988e36bfe56cc80101363496ab304cfe673914fcaf4332bc2e253b5cc3ecfef074a3ed31dea2a46d098dd508cf9bd591f9179961fecef463f51acca7e0189ddce57843994467a00f846066aad9866916b82c5311e1038b3ccc9fabf03f1188027fdbda731943712e58d250df995f7e5bfd44fd6b3509ecf80ba584d9fbaa28ce870a1363d78b0683d31e60159d2533a929c2ec94f9d1d10ba1364400622734a9cdda9f33c350d41d6dc2941e3cf64829788ae7bdfb5acfa90cb382760cd613b5160e2cc47d19ef46197bacdd69cc6d48d12bb3577425834af5c1c94647a74942eac49b502e2952ed9acb0bd99c17ee0a99bc485a03688603083b0e4aa736047c1407da2b396bd041eadaa4c0e76d2229ea37321bc50390c310fae24f346022d659b44905452afa19018ba418af00ec99f1144763fa7a2183a0a6cbcb3d91de995397c9b31b7c14d0b3557fba3d8345412f080b5cce25728f89d04a10a1b125d12dedae34c887c8993e550128455d55f8aec5412d59255a83a03dc81d3595e2d3f629640e9b4d80715f5fb31772642dcec508c2f4b4114bf2e8c9e75f15fdc048c6d36b98ec44926450f709d9cadeefa6d29be099255923de37ade043bd651bd5fcc84c052d013304502f4ecdd45f9383c88ee5a9ba11a71fb2cee059cefcced3660a0672f385dd8333f30f01a16128ac57e0fd21deebf73f1db49fd1e82fb94cbdf535ebdff110af145fa696d6420706d4fc5acb554623b31da44e682a0d3b249129cba1d751cf51fa2dc4e9f3287f5204903cfb68da92e5e1433e3a2c35c1652c92a068a59b3377f28c621ddcd726d03f7854de46c865fb6a15442dad4f272b3b1b0cf0ca280daabbd8835d052d36dc4e6edeb15d1ceb386b6a754d76276dcda0bce60eaecac6bae8803db1b343a2fad44bd5476cf4eb2e691e3d388c02831c6d7aef55f469789895b952940fb11bbb1b75f2b940ec0f28cd098eda028fed5be1175eee5eadfaa35ea97b3d469a5f2292c76e30d48260054fc27b16941da5df8c64e1c4ad0c98b1b72cca81a731aff33810a5206961e71aa5881af6bf909e886ab2af533e66e4c57bcbb49e656556f354ca01308244e849d60704f07dc8d47629193a8084bee3077ff7794da039ae06db8f3784872953dc25260f43d15eaa286d808e9c4ed4c32ba94526f1bc3934b7c654780c5ee2f31ab77e0282506e4c990c71a34fceb5ef122290d10cbb514cb7e7f3e4f95925d13255b0171a299e09b21c1158598ec8a8bc70af77d177f6ad9414c1e34749ede85ff0262722e066fc76d95c4531b9678f1753aa3df20de573ad1a877eb88be59dccf9fc324be522a50d9ebcd5593989a1b49cb36edb1f90cd202e5766ac29c363bf478368eb29c30116da9270cbf9f4c1866eb23a632f926493e2a7763c0a3fad21cf9c26404cb6e0ccb29d8ce82689255a885ec8e5898d4c5eca8eb4bc26bba74abe56d60549884fe48089425e614267b5ae37a1ca6c641b29547dd0993cdfa2b9fbb122d39c4844972cc4f1a4a44f48f0ee4881dd7e681b41f44d8336fa73b4fba0cc122306772b45ef4a86f8449998d7a4573f4958830a9bb9659f7036ac38e9e18c7cde50c26598290454e78f8ffd35c50e2ed7873303975a7f9a97ea3d41545e19d001a0f6f6a2a5696de49f987f614a8cd1078e482495b736a0e2561735e970766db8ef1cc74b51b6851144cdafa3306eb8f61ebd5ca1fb4824b1799afc698c3a135083dc8be25c57842304914f71a212a9656c2c9dbd989eeb62908b0b0e1a87adde9374e0023de17da9fb8179884db62a12a2683806a081f708544259ba1ff2e400a4c4aba26a86f886857ca3498d7ebebda16e862455364cdc322d707c418d252ca70722104fddd458fcc0d48385b46dfd2ee68e6778f5850a062be5ef608606ea9ebeff7a03f6032be54d8182696227d8cec61654bea87fcb1a81e758059aa07ca7cf6d6a45a64bdd963641d2ccf46de93e6b32d4a127ff9b24de2bb1fb882d4cb6ee52c7d481a5522391932154cce1e2cb86d0df020d8783b8a275a1ff371629d0026673b3e4285bb0cf7d9d79a0a057a032643c76ff1df95e729efabfdbfe4263b3941f4222dd630c66d7628cdd61a86fdffde9662ff2791bfe4f7a6f8cca04f94ca536fc35f521636e494c716e5c87cda60a20b89721cd8b3cc55ccd182d9cf95b01ec293924c20c6a84d5fbc4071f06657aa077bb43a2cbdbaae1c6cbc6a6c402a48b4a4c8189af43561b3a2d99e45f80ebe1e40824b26882c25e5829fc63b0f595d673ac45aab2fc0c6c22fb864922214e94f90c025e9dd607479f552e24c5dc2f85c87f748f5df92a608f9c7479ed580aa7192ba70758c981dfc4208263dff6d751ad6aeabbc182da99ecb8f979dfa2d790fde6daf72c6e5136fb1b76466160edda1b764704ae1777ac11b99e0054db236068e44b027fef423b4af8f03f289993703d84160d0edabd99809fb268ba7b772b02859a6b1a8f53ba06c4424de90da3a97cf055ca6e36f035074b5ab85b8ec3bb2158483cb64356a4541c0c5015b0203b084658ac82dd9ce451a082aefefad1bd48e753ca0df361cf66ba24d3f2589efd6f0c304e239b9a824a119604beaec0cf8bc54268fb2a3b0416a70a71bbe3d8a3447bd58683e83874b95b500ecac42c8cb0ac2949b7f4c272d67c5a7a49a117862519c58cd4ea366f46fe77e8f6d1052fc5acc2cecd303ddb953a8f4871f3a3408a1dcf68bb4a8caab6b36f76d04bf9d7a00018c451f2fcc14229fbca8e2be58c1cc4bc28944731d9124f7dc02b5601bc1ed87e5f9b4e84ee03988d80be45f2707ce212f5c78ce89052c0502694e91a93f9d219edd54a919026334f4f068087688dc40bbacfbdb484228103fa24e011d17438628d2db03d85b872736d03471cc8fa2377585c38acae6e7923fc0b22ff8361e7bbc90cc0a2c1c775758a6355d0fefa32dac046022decccd3d7188c585d6b7a78790af160c8cf021da7fc362e4025aab7445fa2aa7d7aa4315dd8243839a9598fc3b60a90d78440c90101362cc6a6420b07b212cbcae9b6110fe554f6c6bbaf669dc9693bb46920980689a77d5f7a0ce312c4ab307a18e2ba8312b736b01477a672ae74cb1de6925797603b9472444da50ba28f1b90e834915dc2e7bd8144dcd8da3f801ca2d1a9882855513953b99fc48d4cffa005bc69e2db5eda68d46fc3bd00241d246b2f798aa175c466a6ceb36056acf556dc254b58d872c270710612aadb9f2a7048a479ad18a34d808e9cdc8e41a48092563e7226c2c1e1fd0cd2045ef71f90ccc5405988d4b2ba5542c56472ec5ff1d56887fb7d65c27c819fed2341c3f9cb3420096af5e6b6d5c38d3e5a9dbd856f21dad8730710aad07e61b2179aac91a701c3177dfe1e819c0b2e53bd068ca95ce17ea2037e2cc578d4fe3975ad187a8beb7e7dfca56a0c608718927f496c243e18dc8954a8973ce7d6e86bb541b4bfe61ec1e922cbd25ab6d80982e6b7a22a515a0f01f409cd3d0966f4b1fe1980358c132d43cf4407155e9495b2adda51c7a3559ca2732aab2df3e4e382454ea53ac6c2ac07413abbd2696c90ae1e6ce5a2bb71c5e75d2071a992a65a7ddc7f88f94157c5460bc8da0700ac4cefda332dcdb27dfae2c902e3e54adf646f0da46f0ea19658bd51c07deb653cb9082cc95fcfcf19f4a6e19cad34bc5377f09dd65e93779876f0a2e63106842957442e9795829667f42d9f032fbee9b2fc6ef3ac11b1ec31d1fdbab2fa8ff80900b6c6e67037c98cc12db38890de9afd696fb3ab4c68652759bfc8a0ce9efaea4cdb73534f8a933703fb975994649f7325036d728305cd41067a0dc4491e4260de32db6e17a9300bf7921e8159dd093578dd621ceaa8fdc23d71e492608674cb327141144982f3bc46376a9bc4e68b7e77aa190570eae84000007718c176ade64e5a59895b6e1b60a7d41a44925d6f38582367b8f63eb3857708471e524441d883c24fd9c3f51f8ff01d1ea7fbbf090e07a2112cbe4d11d38c5564f405de597696ccb2e3859dcee228ee493747ed16e196810e5ae8fdb3b64c4f81f4b5109a7b7633bf81582771a1de2f0bef400d10aac361f017d75e081d4042a973a044fee18b6a8bda988ee1d729494ee0b7dd7b846d21703a5874986ed46dcaa92ea0e82eadb529bc298dd183b0533ef39002baaa5ce6a0d80345174fb760a16a9a11058c8f06a461f5376d8ae466fcecf143d3b35cc12384be9649d38f862858dea067ea18b7cc008a1a47bc123d7ee99d079427644156a3cc8c09d7f804394293a9a1a060a0a586c8f43f5a0f7c85850c6243643823407014d33fa0a09722e64c0a72dbb9fb5e5fcf40348a2f318a01308a0b218b7acc0fd3c3157b1dde8d45d7c16bec5542afeeb7fedff9cfbd2449d4548086af49a67afdf4e78ef33a9041a3d55979928107e2cb60cc83bd9a675f1bad792da471e90bb653ee77b39b9a81b862f5509551a2987fd57060ca53c6c098a2c114a0acc62e55b89ca86bab6f77e8a69e1252520775515529b1bdd6c4b210de8291c7d0394f917c4a3cc0850b44312e00bd21ca8466fbc99df19933d01fdd519593738c69382d417a191b6511db0f75db125a66a0b671f784806dba8acbc4f199933119c8ec8cac15b22f501f58d45299c5f7135c133ab7d2d547f8ad00a94ee5d7a77526db62e950b6090c711536654c8337b68a1e6e9a7151801ef9a3797885e63ee1c8ca3c62faa4a45c87a845b78245f3b4219756e367b818a7716e0b1d196043ca22c981c5aaa7fa79cdd20a248548155361570fd1d2f2c9e99a380764420b8e742c1f8a36b9b81ca5d16006e0a32d07c17cc1f0ade477628b933a4a569572450bf1e86fc2ad17faaa8973f0cd075b8fa4b9924a3d2f2da426b4c93e806f51ce7186be0b88ba6edab63c21d43501f72a42b1cd46efde36953f2a7a745e2d36fae902aafdfaafc94e87f1a59ac8562a4d49af9fb7c64028d7663af1cebb5dd634375ebf17200c6da9b442e0467946a74bf61fe2433a9c2c426d0fd240c2a76109041288c3d0818ffb28145978938f1a9ecf4c08389834eebc39751ba1a63f4d56dd0d4cb907ed00b1dceb2cf6f14789a0f405986a9f68a3892ae8cc5642b00626f082d544185e3041926708a7a13c4a05bb4bee897029a7d7db452e2848270311573399a447e58f362cac51059af16a4a0806cb29500ee36052b5b71d5a6088d6be342095c8d85654d139c28ef1dec1c18bf64269c30d6be3eb47edd21711406c8d2b9a71f81228a9f73dea9bee3af8a3e5ba2ef592a3a2bfe8913cf51637471f467842cbb38bc19ba3db8a790244198d89a3e23c35460025781d2455b05f3f5bd611241d59ddd6a37b1ef7b695f19eff38799098db5fd2bab15c066ffb083e21617cb8e705011fcd88476725f479682488d8e0bedaae78d6d9989d427ec6da19a536c4b89817cd7f765451b1287bc80c45a2413f462417ff620c312474ff2dc2c31676468a1d086585aba0e1d2e8d710d778064154bcdbda2343000e86f3809fd5ac8b616ad57762d72b48ef096b8e4fb095a0b582c0ff68032e300a829e60a0c877486dcaf82736c46a68d157239f25f4fce023421c43bf10dc8b3980b839ec4303d905927b99a5b523b0be53289bffb1d5e5bc0a3fec4e9a7a1f52520a63fd4f697c777a0cc089d0a9fe48bb989c1db8126e0071d80e93d04d49b7fc9d28176d66188482267eb96816074c6b23d97b7633367b1f03ff0a4d993b199273de7904f0fabede261ad5888d49e30d7957c044cba9c56e6fd916858fedcab1562ad5c5a19297130d56c411fc4a18f13a7148a73ba9d5db5c45d2505c95187cfbda79b3c2ef814effb7dc99ed9ca87451185335f50002759b1f94d26a6611ae9525dfc739fb66c7949e4b2c4eb0a7774ae607220f5d973f198db79bcbe793898c0f4a36cc061a5fe8ffa86bfc66abc6e52e9beac085759372982ce0a543c92871e134e7618c4e8ad6f43c25b12cca3fe28978e13c9e359f68dfbfb44a067ee5765cfa5f6627497966b5b0ec8034f8e7eb859ce8ea601c92c75dcf262b3c36387a734a019b3bbc4caa8784ca38f7594ad87cde7e87c1f7187fe1e11e319b2e0e297c9b1d1ee6e6cff3404efade0a953d70fd66ee5e7614464793f1dc099ca21d99a937fea3fc311de1e0f4bd8351faa1bba18e09c6f968b9d4360e6f1ecb7a6c8ac54315de5f1c6a29d6e1c8d148ab32c60d77e848c10ccc73742a29cbf393983c3ce29261284c54f32fca47e0ebf0e0cf7e69a6d33e42508e1dc635499b480a4df9cd7364cf522e7ae2c63279e1834c52f1d9eac4e6e2d4598ca09f72226ed112ec51149ac68a6c265e053fa87e480a1f479b0a46d03d711eb08d92496faffb44b339cbe5c932684cb704ab568ecf6f1caca31578756be6badffad3634465e9b119f59d8faa4d5cb8652108f4a62b52cace3c9c01a9a892af9bf82640e6904380654bf12c947706c1142a6b94c9300f1a7f035f86c9e76127aea575068ebfb9eeb1b19959f4ae6fd394532b4b55c913f49ccce76e165b21cf387cadd367023deb1e51be415475086cf4eb7d74e68e85e4adbece3e01af6af329f77a7608a9d9b4b4fe5529e706c64ad9075555e2600d66bc5e5dc522de0d3c6dd17df6f680db3fc945f14171d9eff5d329cfe405def9bec2cf5664f5fc9af6c4aba73f211ad944c44f4ca76d1364f65c362d420dbdf76aad4affceaeea64dd4222ffd85a3f85b7fdd0fd469280b0337279a61cd04bbb7e9ed6dfbaaa82a5d5f8d31b2d65012b87bf3786c0678d842d8d7bfda5ff595fba8fa81ee009c7dbfb65baa6da69c1a4640add054c3474579a12d22911c1a97169cf9ffbe38227866b32f5e88ac678d2f6bc88658569541a154099c7ddad6bcfc46ddd0c85105d9b439cb43d4c2802cd1593f0b6a820fcb661ea463526710b3fc612a8ce9885591b90c902d3937ade4a02f75c2f5684a51c8db96e4a6d07d7b0d6d22118c8bf75aaf2984a93e02be6e9a42c84186bae221dc71f476a81720bfb8ecddad2947ff06bd3658e6f90fb7f0af6c52fa6edb5b6cd86ef2b8bf1e349401ca88d41b2ca8c721b4a0dc6b39bf4a906958aac0cda36bf06ec263ffed444c90b6ccc954366e5c22fe2ca5e755e5202aad0bbe270d4b239c33c8fd2efb42d1b4ac6f770eb38920cf7c34e671a2c430c3c93a23e59d0d61421868e2706c19d3eba347606addaa9c93ed5b25b32bf97b30a7e1bd9b98592ddad0aaaf1b6d8b4ef35e4d372add18dfd789a82517fe52d00869fa21cb314a6572baaf650f31df41d22342b3c6a6adeb720820e979c145e10944048d1bfe7c07bc016f69813e5c0ae9387fe54c754a8ee29df30f04893998e844c43005d3488e47099b7e6f35f5ff3b4ad994af82f7c0545ca94e6dc96e08926689c44887cb131b2dd24b9236b6910e213f5cba94b4dd33f99d3838b108ca04759938483a9b29000c581e0df21ab7a3f3187f09e151f6e4cb18fc13510b6c2d99c57fc9de4756dcdeb0c651e7107fa18320986b428303af513f762012b61f332d04b0a0b2c21f6bcd08407273dcc1a9fb30a79a67ccc8183270c45b5853a59385a7421010907dcf84f96f38ee2cd44eb2542cf66dfba483fc8b8e052d406ce49974ccaaadce6cd08ff578b3fefd5df5f7c2d0ea9a052a9e3d696b1aea5a42f438d7e0db27d69bac06ef6772d44340f9c90cf7428057343953caf519913807010e3fb7f38f5e3cf726b004d0779c046433719911bd150762c932b17757e29484afb54a5f044ae9d68045228b520a90361bb06b51c169ab08600bc26f5e06ec2c4a30db764e35306a28cfbc3164274d09aa0d98ed945ab0aa8274dbd18e6d85cb819624ea2bd72fc4adf9f10a64d519d74479222035e09aa65c7a0fdde8bfed3c2ff203d081e1c14c257c1d9d11ea8ff4016f1046a8539b2330db4859570659a2b48837fb770dd94e6dabf118af6cf4a4799ff93efe2e9fa655250aa57b5b5677d4c8d27a05b37616cdcbb172d404c05e38561da62eb144f63ea040acbdf097a560bcdb13f61e513013bb6485ac5a0d022cfab479b771edddb89f906a08a7a52828a26233b0be258c11c84071958d98d04ee5f980130b58a2e5f215ce978b54784e42901cbaf0d794688e47d631f282b122b432f951d5ca83f31e6701dcb3ea4f189b5fd05dd979bad15fd889a8e1a8b24074ef0bec3922f2e09eb583d560dfced8e28a1f261c2368364fcbc2f5db4ba139471caa0438f575ffbdc48d39708fe760cf87fe32f1e13a11be830b1e83c42732063c40b23e9c418fe0682e28dbbf62f738f3b9b789b73abd460da3f47ad770815b042210228319e818bdefd5b236d2faec84fdb6165913b55d15ca77b029ae1bfec51598c55b0a0df20ace5a6bdb34a69238409203750383c488c1863051e1e23fe8f9dc6b941b75d8d41a29f90b6b4cab9388265188f9d2a170cd67d561a9a1c746beed657e78a05840d4ccf207e63f1238f9aba01e3fc1191b75616dc057c66da126b43eac67023fb040eee03969c44873f882e6628534c8fc99359c74a509dad0f4c64987733a8b3ef283c722cda30d6189e920a97c61d214a1a8fa7a8f52ff0bd71ae55587c72eeefea104912b4379aad4f74d231caae50ea84f1e8ceb84cae893cb6414b0665c0a1c3338e4c152125314a7fa17f0c961ac010067b612d989f9ca9657645d61d81e3f21682e30525533bc708e1ba72f13e91df2f5150bc7ec0288f2234acae5a71ca7f2bf1abb4e94b79ba911d493077d8aff1816c86ea06585a7484a0e020b4c4b57b3c3b690234f99b0fa72adcd223cf7c15bfb5f601d97e58a0e6d9432c5006d5e074503dcd81ca934140ceb13b9bd2cf1b6cd913ed685517de48146573f79587d1da860d3dae8a2a60a565d04f1e36c61bc61fe580cb4bf15124cb6e4941bc94fe8cfadbb88b718da0b4dfbb468e00a0f21d09eeb574921ac0cbb425227509476b9f4e998743190f05f260b07b026ccd8868f5b5538066119eb68ae4d10230997508f3e5b3f284c9281028ea26040ded82a6eddfd4d829f759dadea2c1a4409664f75832bb1bf7b096df54be7da4596ee9eccc967c183fea8e232a9b65834aad2514d27f2d45b340c1eb0d7862ebf2620fc4e07f04e732be90c81feff04034664073c8d583ccdca5ac0740eb1e650465f3b3401e6e5b863e22ee3004927e0013dbb3d8578e90124928b1e07384908ee546599a4488077dd7c3f60df7cba6ebdb5dae02cb5c4aed54e3c37ae04997a39c9321112974042e34bf692ffb9e6400eb5dc3c6408fb29d525ccc86e14819072108ef8b940dfe8458e02ded7527c7f45a0355e56c9e97b61a5548ae13b7214d6a77a30579c4a3029864c998a09014bb4506673074b5b726dd149a31118f61903650923b906458db0afdc7e0c0c296172ed79067315e1042a86a64738305448722d56ac217100147d920ba1e452431a137a225294e05a595453ffd4621ca07113ecbe842425576c8e22530cef97fa7896e23b181f5a07293937469460f155b2f375f80903f870ed5f9cc3bbf22b68c1b52ed18f79a1f63dd676721554aad9a0cdbf3e29ee8bc178ac92d3bc29a10f051823be3f00ee8ea1508ac98cad1686cc2a573d79998e4297ebab0d750963e87f1caccba3294b2d5a1da04d2c32f4bddc883bc1820db8325ad01a5cc737d01b0fc0002f2c8acece26a6a9513ed0f1edf5289ad33f023c74673e65085b3559a98526440c3d852c3705028182206f62951101db27203abaa2c8ebaa223e7fc6a5be3f29ec38c95303a429b8f5181c8685b595a4da48cf9fd3fb1993dcc4b396539278c40383c973413c174fa54a337f5cb7ca09290a5f49edde4ac2400564e160182f3249a0f8da737487e4f47610862518490c0743eac22f05637bc5501f0f5357802a5ce57e6b1ac7aa4b012486a790abd12f3266e7321a57bd2e24a980d8ba7e8836ae8eb5984acdee6b2a5ad87f80c2ca9da8ac8dabde36763903461167a69c02071d06a12ffcf6bf6c3b16fb8bad8d2311251f80fdd3e67060bdff8d05c2dc31b25333899fceab28d0c38c187ca3aa64ef5e89f5a7b9274182ac2130f9e342809f4f4068a83890606b57e8107732e55fabd1ba7d62343a07b36ba83fe04467eb81f6b1536bcc2dac9ee49cc367c455be6d5bdebb9c9d702030e64ed0a17c96d5526e34668eecd3061a0c495507efc95e6432f57709bb6c95ee843db9ca1514bb8f0668ab01f0082e9e7bd5774c93a2c95c7b00569f01e8dc34bec2d9b2ad40dcd42c600211357419d0347b7dcd3b63451ac0f24c61fbd82d8d5a96e235a909beb4e70b2a46f3c20c9f18dc2714d7712d34a3863c110e5a71a02f12d1110d4bd807f0f60b29c605ae4e54dc90a10446ba46de1052076d6bb3714403a2d6b063fe4882bfaa5a726e5e8c88a0d4a06f5b56243982f6f805887ab43b343e84603dcdd5ae1645b25acd94020a67a60c02d314748a80d45a6fc6aca6f89200464fc3b485a5e95f0721613c6c0915d4b653c5c1b00e4c84dc645dd84de64ade0e4a0348f27be572481bc0035e32011c64350e45943224a71d117a6b92e09e59662b51cd47765e6f0e8109d7cc6d43e1212eaf91839468c92100a6a9e6189566e0981c32a0a7dd24fed434cc21839b4fff4d43985c1b68ac2775c175c71b729223142bb66f550fccbcd42c28acd7fab4149e0ab3fc4553f1404d51b25a5e6821abf3bada145033f23d735e68aee28700705184f5a5067a30884a5c29ab3cf938c31ea3318b14b041e5c752f7b45c2c2dc87ce82698622acfcec3043990afe688123bc2e94d4be1cf9b8a4824c5fab05a24113418385b5c25684bb32dfba1925188b569216953d6072a1086382f9126689ca765ca59625abe455d96e95d6909de2ad713847c2314b255011f46222abb6a058f9b537685f6194987a3ecf82dd404782105da2b61370b532e559096e5ab6d0087aae5af5ef4b57d589bef549d2799c863811f2d8ce73fedd564609ed9d6b8ac546058a1a1a1c43302c7d097ef62d880f658e1787af11b8bd3725b7e0b74e39946b6aa890994d62659cc69da5dafda9643e27d577b0e571124185c4334ce9d56c36e8fbb96541f01602d61d84394311b03c153cce91a814888ecce45ef04563b1127a93f02ae8a63b137c344a9adf548b593a5ed7b0fae39c04140fb1e8f545c7c0e4cadfa49c2942ef18fe535f3431335208e91989230f3dc142757fdb1a408335da917fcf8c8bb20bb5cc46ec132c68cef00ba302b792e0df6119579095e4fc7967794261493634ae402cce3e55a56d51ce6828927687b2dfbd5385cbe1cb52cc215d623553e68ac0098b2e12077faa737ff82b854ef1468d062302a65f49e7c34b50f544911c39ca3db7687bfb3f48a9f51c8fd1d26d44abb4c84218491f83150a507aab022f4b9447705da1c2b29fde6c7885c0fa4c84c5dd6c7ff453ca88f852d30ea96cbdc4b3726b6e683b93afadb3732ed085aa15cf42721d6f08916f551285133d2c014563845fadc66b86d072c737d04d4520fe23d34119594da97d909ca6953c0e4b99b8a05316f6e9756b1e04690a4c226b832e2cc8e7b5cdd75c68e3e05c66a1a5044359f2fc5e91e6e75a0e0dd1f87e3d7a508572a3348cf4c6b0f1c88b1e561d9f51bb68263fac56bdaede28db713695821707304b613f6e8115f1aa5fcc8480dc34fbeb775e340a152984b41b2949ec3adb84c11467e6c8e7b0bee2fdcef4f6d3c4348ab8507eefa9a9c1e6f4dd7700e7e3a94934efbcbaef5d57f119cd5cdc738ac46aa5090d76abcaf40984a424df2acc542b83d168b431b48424add0867478ef62f5a2b7cd77994158784c2387006974715d3f00ed4378371b72f87d0d9ebf3fa6cc5d502e35a7ea85f211f25940215612f5161421f534e828b6cd933048395298b37b084877273c85447ed731a834825653a58f229df6319790aa6778041ed88b769618607062fa8932380ec5eef78803460d8650e2019249cb5b6325297a016f74485500d3295727a355377b85c51d79e236e98127d114b8a6f998bd9ff43f11351f6862d1f438bbdfa47e3d62ed3b89b31961b9e00ad99ab8e5e8c5d656f3821cec7414a19cd39e49f74838027717a6c884db50e67aba37d3a41dcca94ab5bb6ebbcec72b91829169a7bee2623c594e747cf3c7f6c901aab8c5af5985340f491935b76269a83d5722aa12cc4644edbb2cb5063439b2573f263c65612e8e117fec17f124d151430c4b0dddb3290ba6c639b20af0d70b83b033a02dc8a0ca73c83936470dfa4617667b8d69aafe467973893967d4fffae8925219c4575b2309c49987a2189b5e6d2ee01018db081d31ebce5e52271b014d6b9e72919fa67b20a9259d9cd22db74d6c26f0e5d5069754d5bae44f9d86dd48f1a080dc45b2cdc1278cb2c34b3f19b4538ce341d5b4acd29b8698694c136fa832e3ea557ec139ba304aa6d35fa9d297998d7f0278764d9b8384389f2a196f67160c6e526cc1a2b8841f8771ebb460a83bafc08fc8c743c3460a89a3204351846894664f61ed73a2740a58c97f0d6e24f5442d8c699d9f4299c0b07ab06017783d8063ded9dc9b6a8e8245e5436df694ebb6be4a9c44942b454109efe4e87ff742c96b340adcc0d6a77e5d999423343fd99f431cefa34bb691d2ee50e70851545aa19667310e29f4958b4fe4c2de943b1d08eec843648ab2406dde76eadb5802b5b2d4434fb386b90fc94df816b1b1502090918f193a53feb66107db31f7bee57405f87aa11f0c940dc646cd778a69c8114eefc49009176cc47835ee98a0275c7a04da49d4a4833ce64ee21a24a285ff83ef127e30e5c34954d047c4216c19835b9527924c207061b2828cac52905c2605d51d590f13517ea65ca38ff4f757fda77ffbf9f8ba67f7ae06e2a772f6941056dd8721f8794f84a247261f014330bae724487baf509ecfd48a812af0ee6b7621e276d1e3d47c8418c9641f37ea6e14feec14fcd68ec0fe25842e95c4dd6374cc6f2be468006949508e70349941e2132681b29be27c34640cca8a2685f9ead64c5e268e85b5c55064853d399050df52b4d60145e355a9d76763278c84187360371d7b8b81e16d30e08bc128e0bec3f75594b15a334208fad929f4acbc5dafc327134508506efe2d583d6462b304d1f5201fd469fcab9a8d37dd4ee6e35191b4ffc1bb20a1d28ae287d7af9545e39d68677e13a10146989cd43c0661153e83d756452d07a34815080cf3cdee0dcc0f81fd51b6e5006862df4a8431d89f854c82570b14531f82ab3bc214a0cb1c04364b107265ded638f73ee93908bd15bae217c042bebb1fdd4c0721d3708cef8014886fe634700dc1ebd08980aa886bd38db3e8e9e7f6620554650543ddcf6ea3004cbbd7a28a3f3f5f0d2f12abd5b6179c58efe0049485917928de116d8b687649a3c0984e5c7b0a6e5107b096942efa81225155a49944d52e859584d484a0523945fdb6d0eacdf3fc8207efec6c04d298f17c42886469795bbab948d57d38b206162d04b6586d1f4547c193314ed95d3f49c61009c23ca72186e96380c9974696a89b3c304f32e2c8cb0f4fb2a9561d69d72e438d802f2ec039de5e45f416b837409ddc9581032b7c854771962964e42d4aec8d4dab8942bbc68bee390d1bffd2d7d276722531f0b35c7cc8e383da6b6d852892680dae4e4f40818c2e720aeddf7a458c1e4a3054f0df4bc70d442db1e54e2acd385c4e0703192d69ba3457b57435d4954291ed5d5187582c335edefe2be8d50443da637d94e95aa23b7a7cfc5b729b5f162271ba0bc2db0aea2815a5317f1e5b715009df5ca76bf7039e951ed298d49befce8ec61b76067af3be6ad8f0a25f00480b7aaba0fb07bdab85875b0706ffd32cec0a78f0d6c77456cbbafd8ddd9218e5e9df3ef6645beb2011f05a007710d5aeda225548e0bd3dadb5f00ac1f231857e6a545c6163ad81bde7b66ab658f046e045ff987b70202939915dcb95169970024997799408b699bf64db94145806e85324703b0bd56c1ddf24be7e657b75a06d9d2e741158ba200272f60686f1460fd2031c88ebf7d37a33cbcf915d73021e7c5d11c07ffbf8e9f50e9463db9285e97904744e49582e35e7ecbb58a18d2be2df55e760a5c4c24f01b41e8aee7361ed47ba408b0571aeba5461559642e315446927626a086444615fdbcc802a1cb5603d166b9e86d2e4c533d37afb0e0793e463fbc8066ce915cf532aab9c95e22e348b0c589767278e13d9d2a55b83a0c5fa8d290ef2657592cb8d377bbfcd8a7b8bf8e5c1587e869eb7b7e2b7744c042cb371c44c34a8ed5a61ea77f934bca6a1ca37a30795259d1189eff0c39d8f62a5504f0a32ae1845dda45d9604653c8a602f62e16dc6526e895ac59c7211b15296bf373947cd389739135ea2895a7ff334dbb11d64fa4dc2c8e809794d05bb028e4e93df4e7891200d73aaa0a96a4c933dca4b53b215168ffcb3211e7b1e8f2d04151540a78fcf07ac369d6689da31d99c6075eb8dbcf7cb5feeae2ce293d7c5039500595647324f363265d930a9019172a587c47a83e5a3ce016f757e334b539e9bb523fa481ed38e02935c8ac36875d2ef02c84f9a7debf463e3cce718efd093bb72fa449b989aa05e2409f55af8e6c46331ecc0574343aeca7feda47920182e1d0636fb3af3b26106b63eda0112caf482fd0442dc97668f9642e047634c977c061414c2f9f57223c87a91d298f4a38d0c7bbe5dc6d3dadce3cef23aebecd8dcf6dc6f852794c7ef6c71802fd81614db51ca884735875653ba60cd3924f249b68cc44900714ca421373825d7afa8eda903af5c10ebf78bb28e1131ed6d8a5088143d2f410240d5c4391425bb574d54100f01cc246c8f5eada193a402fefa77675e766d279dccb29d7bb7ac5f90c3dff958f008cb9c6650bc1cda76892a564993fff13cb6af821c02ac9339867d4b5edc5dc1b846fee4e8df4ffd72675fed3b604894593dfa472657cb49e12debb5b23fd438c98e75c3a0be8a547eea7c17cbd9fed10e701e133610696cac2e545bca0ff9ab9e56edf10a64847536df6d0953aa8d8f70e5ce1f40d7b878edca7673870d648ed683f2cf4af0eae88618e9cf0ad8fe14b46555ad781d292a5731d800b8c8d8708f6c17b39b0a06e01c9dd48756a6fc9bb07d4a722d8567e1e3c03d48eec858932ccba8d863ace37ad63134461e49a42d65ef9bf94608adf5bd628b7f67868867e9fff3c13d02de840c8c926aa90f3daf33052fce7a1801fa86b6559de6f6aa0b33ddae36bc71a671b7b75ab747901b19d4fc118d320c3d6fc8948b393d7e64839c655abd6bc1fe2c694fbcf8c617ce370724bfe2435d0ad1893d3244e6e0664a36349762f157c84db0c7f1186a4486ed1a2859d640c0c3190833ad73c155678599de49042439cc94a271b94a43d92f46d5eea4f8c283506d28d4a83935e1d9b5d2e683d75569691a53bbe24d15a4394efa3669878f5bdd1aca182cbf35bd68031b2f61573b6f7a1541d782e04d1a4d209c77fe0edddc0e8fddc5342776a4ad975efe666a5e82e3e00e8bcddad84faaf18a2c360aa9f5c71d04307775db3f69337701bfeed22005a2eb94416346d204c90063ede628722b34eaa446b7d8586a7d310f4c77c5026a528e16258614ef741ed6e217a200b94a9e5809a43b443d28604362a5dcf5e987e9a2a8e2720add9d5f4677c21c4150abd97e0328463265e968a19db1553157832b976c5c5fd05a52d15905e65ea926470be4af12d59dd09ba95caafe61565e9dd02532000651b15bef051e4525c6d0429b3aba601782f5f43c04a2f8f9ee988a35a31ebef7ab45bff0c501261cf2bae541c78ff49178df6aafbb4dc0ddbf23751cd18fec069a95c3c985496b9cfd0a7a5f259598e68c7fd94110e42fd1183a7e050f30bd83503f4189324256b215e457912ab108809544e3cd5883eef74d4c7af046e8696a0af5a05d27eb0c9289804ef2f4c7e867a70ea44653c542031638c636b8b71b30646346254db3092405a61976cb8afcc1311066cc5d2356e1bc9d6fc9bec8810148a13ba1ff1c5becc93e3621febe8669b8114191dbd66d99928e2e7ee4bbd740d82e51a3744954b341dc563b5d5327ca6d2db0c0cd28261bc94102daefb7448f68e98260c8ccb4a1bd8fbedd1044bbac856fceec8c6aa8a3e44d5c190e6f58b7c099701468e14fa5f83a3c5a6afac714df8c699614a81a39071027ca441dccea434cce5799286dde7eadb4967d0d4d594475bd365042d4568e65666b2d5760ee03dcb87627752c5107b198218c853e876a399f24a71cdfa515d335ec43a3a797f244acd8c7329415cc693666d3d01623ab998848d2ae3c3a5aaff62992c0c2bc88b7a812b5062b24dabb898e1bb84b1c059de9c26ffa31d8fa2b553c45ccf636369bd4a98739e309b33b2f746e685cbe569d27cabc7552cfb6a0ef80f18106c963cbfff47b36ddaa1d1f83d561acc32314b6dfb57acdd1281ba55c912369a1ae87f65aef7c08cee23062bc93bab00a039f6c0dcad031aedb43b616850c6aa3c293d12ef03c54cd474e5e6940e10ad555d6bdb72594bc1408f81f8850baca7381575c61d7372e16c436a54f5e63975baee1024c6e5d805b894cc66f83349fbffeefd17afca61415805a0f9d631801ea0fe655b35d699744791039b6be3375e3b1b01d4e43c90412d53e879ed7fdd71e36657a85deba2cd97e704c8b235d04eb1d820f65d5d70bb935c1165a0e00f8a0bcd95ef951ad8cdea9c4ecb3daa72ff558758d0331bc871412010118980402e1368b76c4a7e6793648732892b23846d66e755bd2585c0fc4f3745c952a1a090214d4cbe8bc270947d260d55dbc67491509982fb19d976b00299a47007f2e04f6b4203fab759fec7e014c43543d7c4782881b22821fed284227a55922e73dbfd8103fa72f2a82d01a4b46c15b189d16c5f87fbea1d09d6960f5b7ba096ba2039abb9b62990c28a9bf7b3ee35e7e95a015f18f51a0aba2061caf2d33a84f25a57fe554d63ed022caf87f6862b811ef4f39b134a71991b06e6e2d9fc6ee7f0b20c2d1b0c3a64af58815cd6166380e46c8ea59aa7c614c1bc9019103b06a10246f9f82fe9b908cdb6b4604455dad52a42f2f382480b2e0e4a520683f80aa2bd27abb994ab742117393af15a22ac92386a0f0a54bb2a0f6b80050c049706563f1a7912c8a532afab65b849c03119b6cd036b0162515a2f01904d205a301e5835cc50da15ccfa319b25134b3b73232196944d91af3fd1febc4815810645934eb97bc452b4111b746a8468a9f3392a94f7e2bae5dc24a448f88648067d88a00da03a60b5e1eab8316913d0d60c61c09280d242380c74b124ee17a31a50048d913855b50b2c05030a7d027c5c505815a70d1923b63b94f45053a5a645554f76e7292429c929b6de44bd60112d7a7d985c7ee2f19e419cf716c0fd4911ba3524321f41e80fd30f51a4d54a431cd44e9d8602856c660abd9970159fe4a83d42ea26092112d9dd9b5b06a207a007f507f0e5c60e37f91793f8571f62475c0f703beee85a7fd2fc9c71d2dea60f27edd81ee7c0319bd532971166d403fd0365fc10762ef45da16fa81473b0bebf377d6f6601773fe2afe129f56e5e52f90c43a16b0dafe16e7252fd9824e6c852453d94dcee6aa63b2859a250c011fd10688a044a6b332adef4e1239ae5f9c33f3a67fca37ea2f1488ecda19d4a7dec3fbd6990f6acbdd945dbaff5ad575b289e938826ad7d342821fc816491d74b90ca77e9ee067a69284b31898cf69f3db47f7dd91f2ca8247a732c5ee3237720fe394a045c9c37b254a30a457c04758d2a18f10fde38096afaaf6d68be263d5914b5f907e1c71825f48070b910fa4f17f831efe0edc079e735f2ce2ce0e4cffb8306c245fbac712fc775ddffcd8da9a77f326f205c668cf1faa761cbf4dc70f29db4837f13c78c192e2edff7df751e1f70b7042cec755fa91f52ddbf54e8ab859701aec56b2dbc1988e84b241289442227c5cd7320d96ffa4add9b02725318f69e7220f40958aa008542d35a84a615094d5f8abc0ac892fb2a4577c041ffa68ce9eb5f4e24fa4d93885ef4bbf6a01643929f3ef14f1db5291445ec8a048de877fd914522d18b44a2dfa12f2442b8ecebef696a89a60f4d73cefe83fb344dd3344d93c7e3b958f6fae100088024db02c402ba20d0dbcbd20266dec438ae7ba5302458ea75b529b3cdd3344d53cad71ed5473555191c3830dd59b132759aa649969cc64e93e370e2c395782d3cee7571ec107a9aa49aaacc0bc77d8d03e21f7dea294f0b38d1abf894dfec975868504e4a49f57012fea1e9ac99d1d4872d99a6699a32f3b472efd778dd8b63079dd64d4d0b941b9d86c5d33e2bf0268563072034ae2159b1ef72c247297ec3971095e741fca39e4a398dcd12c934999c745b5976f2264f5b18d6ba99be569ebee04bac3fbaae7b08bb5f9932f3f49a26159ff27b9aa0c048763c9e979330921dcfa6c2db3cf5b2d88bbe3648ffc062dddc7befbdd7b258f8e887670778092080a10810a26801218a22294cb0681e2b0f20b6c1e2b510cb52962c7dddc7595558a410c9e477d5d0a3214a8a8ef01ff4c2fb18bc084725294c5022b08d90c953d1d395a72efa42a239dd89d64dfec9f847a79bfca3f6ed9c2727ef26977112e863fe4d594680a4d0d3a74980249317fda64cd0f4377582a6bf691448de6f4a054dafa0a90bef7570f19a064d4490e572060d78035752860bd6a39b2e59d6b9b4fdd06bdb46ad3411adbcd79a3af61699ac6964e6cc23b315ffae706e309ed686ec87f21051dbce49db7ee73287987274e84dbe5a69452bef4d171e84386cea637a4abc7817dee97578f15a0a135c8c27a19944c73731e9f8226fc53b7dd89c277ff2a078126dcc97278a7e88a943cfe3a3435947e84df210518bf29e6f77ccece25f68c392e0a807071c1c625a6938c474a335bcc539e3fca967fc156f47bd322dfcfb0fe17f9647fcc39c07ba252a1e0e31ed547984d29b04c497a620efe36bc5835a8507f22fff53b92347f44d9a3e28ef7b92bdfcbd3ce2a47b13f222fcec9b8856bc0d7ef1dff340f08befc2ebe028be16bd1d23ac56e7ea1a89a6af848bafe12d972d8eebba7ff71c312f14d1d87f67f2f339a73f45bfe223b6c1e2bf08539fbe0cf0253efd192069e5e9d5289c4679085f50507ec593f09b3166cb5ed1f35f787abe0b6f4e91f7228889b79d20c4715da76fbc9be4b6012eb244de167dfdb8f22f44b7f8d79a872241b6e812e816e40eb5827c5128481765d12350962c41085930caf9ff1ab703fab7a25365abb592f7d5ef84c949adb5aeac7cf5bc139fd1533c3dd2d261491cffe8af7872e51ff5e6dd513f88a8f33f188ee8f976825eb888610a50c790a515e0883e9561a079d18519ed226fe1245b31069552802df4f76cb13cbd1ccbefee750bd9e2597ecbd60a28245bb22575a44b6f7676fecdb7d6e69f595433c5a137324ef29ebaf8ee296be6dcfcd777e1491c1fd167f124ab85278d986c9d9b5ee4326e92a5f89223aa9d189133ffd4396f0ce895973731a555fc862bf7e11ff51e2f5972d3e7d70dab57b22ce21f95ad1b39a26f6202fa93a737a1a7a11bbde255bc1db58acd94e51ffd5ff1251b65511645e224489338297ecae6744aca4398c2c2933afe55cc92a56fb25664d9f28f6ecadab2c5841c8f2f238b1aa139d87dd16b150fea142f88a8c2a3df797b4be1ed0ec50bc286286b83f4a6acd6a4ffc95277bf4f77b1373910c4bc696dde69acc0060bcb075be2c7bf0ca5c77884ec049c7ba4c1c81770bca09ad6be36dda54b2dcea7f4a5379ffe4ba867b651ee204201042fd8e0d0e69c73c6a9899031e2408b7287061d7a716e161e58fc8b80a67de2a6210c096fcc48cb1934ba47066c6182e381868637b27b62cc33600b7c19737aa064a2031e201ecffb02396ae0082734048286438e4869cd0384dbd2e63a122fdd9964269191e9d1c3878ff9d232e0a4cd4d82cb7e642859aabfb1644a8e8ae81d7332eb1af1526673594e9af4b26c6c64102944dadc4c1a8842c9987369fe2f62764a8aa25b12353fb86813c44b74464a1b908da63514551373bc85658a922aff1c25835094b4a1bea54d0abe44ed9bc640a3288ad6d0149cd694a37962c06176be67e8483ce1c8f671fbb87dacaf4181f3af5ebdfa57bc65f4abcaab78ce712bae75ab358b1e967df5ea14fb107f7377edf7fc704cfc2dab68af654dcb30fcb3011a3f0c2fd9540f4ec21e3f0ef8321fbff81d83c6cf75f9b7d4f85d40188bc3025fbc64f14f27718f3f4218ab1363d0c7f85f601bf846cf932cc5162717a79a08a30121d5a464493bcd542a851361b4140e382b59ca5cae956be55a69fa5327c26832325cb294ad56d3b59aaed574c944984ce70699932c65a9d429754a9d6a224c8663438dce4ea7542a851361b29a1a7056b284b95c2bd7cab5d2f4a94e84c9645cf2768ddd2b1361309d18322759c252a953ea943ad544180c8786bc5d63a754aae24418aca6256fd7d7e55ab956ae95a65f75220c261331185547d3bb9223faabea928930576706197d53a953ea943ad544988b23438dbea7532a657122ccad892f7034b52e39a2bfb2ab95a6cfe269417422cc9589315c5326e7ed7a5b1dfd2f5dd235ca5346d397df2786a802f222bbc82c2d725465950644d377b1e261714a69294d9fc5c3efd264565c2c5c2e4d46d36fa1b2a5ac74545629ab958ea69f5768a22c85a392a552b25496f2526428d929ab49919d50b213ce4e598da6ff2ab0ce95c9a470a1b85c998ca6bf7272ed4ae764855776a5a3e9b308591096c239c152184bd99cc2d1f457d42ab1530d76c24ed8a946d31799d092ebc2643496cb755d988ca6afc20437f5963977a56357ab958eca8d3888a641fc4be1582ea7705226c529ffa8eb7baae9eee99e34cd35ae350fa7cda8f08d7f58972c69a72b1361b255c4f2ee3a2ebb34bd321affe03c2d48b68a305a0d600cfa5463759acd0d600bcdf00dd79fec0902376509cfc8117d97760d739ca4e51127456ec649d8977278faf80746e11a277d9b3cc9117d2953a7936bcd460ba2a94eb2a415d1543b6938da49b3812d547b82c06d992b73654e64afa8a2d9e3190f801c6d3225676449e3b8997b39aeeb3ea5e96b1f4fc2cfdfe3591844d4425c1d84109c8ee1afd9c8920647aef5e0445fe84d3c9302eee21927e11c9e2a9143c43eb419361a239e99614a8f3350cf965562a6de7eea2d6f0cf88e7ff45f3c4fe2a32cab30fa39785148c489ab22fed18851b204219e91a5a852fda76596323fb4205e922939da341b4d5fa6f08cb4913251456bc678467b5e323c9e80dc0c29c2a5efdf2ccb340df34f520aea6a3ce59f949f43c06db7c99ee29032c33cbcbef753340ddbb0d98518923008e7dbaf1afd0c9b221fe8672a18fd2cab605ae61175dd214b25b6bf9e12a7d761fb69134495fdcd6e7631a791239b204e925a0d305516b5631a9625c03ff9d1937fb1ecca78b725fcfe40d43df6147b8ad5a716c78c3485565b0ebe601b16621bf735778f47053095faf34531f4b15cbf661e515b9b77b8a99a61b2d60cc70ffe8980db8ed272bfa3e00b0be08badd63ec57af86173cc836b3f79366d9371f3e9d944689bdd07f48fc2f8eefbefbafbf6b1c770807210f3079c7f741bcc46abafd55a6bad15be7f0c0d92b85b6bad31beb5b6fed5628c31d6df3fe84dafbfcfaf3b689deed98f3bfe9d975a69ddda785f7a7346992d40848ed807ec77dcb1636687d683eeeda8015bea7b6e38fa3731b2ce7071f9beffaee36af8573f9ab215e53eaf510cc3ae7f9f7bf7e5bc1ff4c63c9e1a4ec2ee6f9ba7c334b0d270081a0e2962476b3177b2747d84fda7b1e7f27e8c7af2be794297a7e4e9ce0e7d0f7dcfe73d1f0de4684e5327a940bdb1c6b0df1bf47c3e9f1abe6d52d36d6e2234910a9410086452528263ee53cf85a1e2f3dce7e9f5783c3e9cf69468fef13c0f718ce73f9e0f473f9e946d3a89528ee33e1cfdd08fd4d0e1cd0e262f1f47c9c9cbd7265173d807fa25254f6fc96b13afc483737c9e3ec63137d3ac795bdcb60d031ed4ae37065cabc448bd33a0413a6a4d7a8452eae9692cb608b78bee39cc47f2b18718864dec334dd3b4dfefd21bde783cefc1363c3db4583aeef22b785a4ea259e5f3d507cf539a553e2d27a9d0af2f7fc3319eff64159a69defc8b7488a8e3bb1127edfbdccb1df773c4cfe7358663ee538fd6618dbd0cad70c162c31e7bede3b9f6783bea1c1e4d633e9a96bcd49a6f97db600bf6d80738faf63d38c7a70ed980110db1a0e1100db8b4c662c366b81df5ca868180b3bfb9f7bc1051eff0bc7dcdf3083d8f7a3c25796340df4c3db79582fcf3bca67984daf47c3c4fae2157e8dd4597dedd0f7adf6ceb6ba5b634bdf01573ecdb5bc23d674dea6b35f427a13f09ad527473be9d6fef7f3e7ff2f993d756b693b7276f43a14701813e743fc6df5e26474323683864033a5a1b657c43934186172c2d5a781ee4fdb3c068d9227ca9978b9847d49f508a0ee5c453f1ddc9778fe26d15b9cb338517b5877d38f9108435dcfb67b162450a67ed6336140aa1a8c03a428fd2e11b36c724336c7efaaee3b85abfab5f2beb06b5e70c13e00b8ff8cae958d588937252e01cf3adc7137acf495d3969c7c97fbe7ebdd1423847bcfff90f08c79cfcc7a2e4efaea26409ea70395409bee15a84f220949f344eda61ff04be745f7f8600bea84891f22a547c0a6fa7a8c89d177509f6c1fe8907f5b62538c6fe498ab729f21053a33c8f8f46f97df5893e7994141f77626c11beb86cf103dc47638f8980c31e3b717b9af4c96b3560479864541ae553bce318944f918788fa85881a25bb29044328281ffafa9ed0631c8312925be7419dc2831ac5db273f5bbc631e51a37cc843c97a839e871df3e45fad0f8a3ba71d9271d28ed083be3e06fd866342a01bb618b23475a0960e39c2c42980c21c0a3d15f950b3d08f165ee0024a29a5b4568a824929a593ce39e79c1306dc9c73ce39299d343f341cc2011f34bede775e0ebc341cc20197761c77a6a3898e29fe42a9530faee843192c3f23010a802fb03e952828028bc562b1582c168bc562b1582c168bc562b1582c168bc562b1582c5611e29513c34a6284a3d7131a9c1f5946b4341c62448ed67ceb8e9036cc061261ae991cb47f210cf0a31b80a31da249e828710f48765efe15d12050de33a5a78cde3131c95bb6f4ca0aad1c9610cd71794b1a8d04b3b6e06ae5783c793b4ba798b851d900d1db96b79f744e175eae968eceb2f843db5618765eae7b554b60ad6e2a34a2298d8b7691db2bd3f2b7a6e1832685b931067c899308fc8a00bf389100472d2ecc26789d52433c6fa60a606906027ecf171ce0f79c81017e4f1cfce026ef2fa180a506e8f83d8b28c0ef89840fbf67087af89324c0523ce5f82d9d4080df920a31bf651670385703588a363cfc963ab8f15bfec0c66f29841dbce4fda5076029e60ce0b724810ebfa50af46ff9821aeeb9022cb94900bffd8900fc762800f05b1641c33f4bc092a7607efb105e7e7b1272f8ed4e98e1ef38ce02587256e9b7c780f4db6b80c36fd7810cc751c092efdcf0db8db0e1b77ba086df4e0217cfde3f320196244a8ee4c7f81da340c3ef788596dff1090dc3b5ef773432fa1d8720c3efb8c40cbf630fb47fc4012cc9951c4996dfd126c6404716c4f03b9e40fbc710c0927cdd77acc5efd8231aa11d4601b9a0fd27004b93468ea4e83774adf80da30099b0e21f035fe08b7d873800d08e83f6160dfafd2ab4976877014b932542b7e133e44540def700d900336660373af2be05c89bf321f790f70a469323ef4b80bcb9981346a3e5e3c87be5be78c8fbdec89bb361f3d2f277b091a5bb1a40de5787bc391d73e4d7c87b455e9400f2be01c89b0380e9a2b47c1a79af48bb0393f77dc99bcb21cfc87b45beb36e29efcb915838b064ecc8924ddd90f7b5216fae06df91efe2a818795f1af2e65a50d6a4e5c358c952cd912f59aa3672247f86bcaf0c797323b9aa395afe275731e47d5fe4cdb9902f96bc57ea89b6c87ba5cb79779c9737f7349745dedc8a7923ca7b45fe745195bc57ba94bc3b8eebe6bd1c27e9cca6332b7a6326317cc7cd1ff3076c893750c861ad7676608be7dac473ad71184d0e6c5218ca8811d8125f1808b01366dab1b1812dd135b7b2422b87257bc81e17e503954dcae572ad6d3bd70477e69eae090787b505572bc7518e822d71f35c6b598a891b954d92249a35d915581a3b634f39399eeb2ca70b2f572b4810d8625384d58d2acec419d8125fd77aaa4a5454a5a9334e00615b61d879b970c016d7da542d81b5bad92c9a05b6c4c75cb0455e213bd1202b220585964f68f91e2250cff5f45c632e1a17a7a4e16f2091b3681830d84d8c2165c0973ddd5d47fe74c73a668e1e87c0f9bb3097bc912c93c676b474ec157f634070c14cc0d14d067cf107808670090de1c641c3190eb30c1d1f3301173f33405c40ab0921142952a4489122458a142952a489269a68a2092244881021428408112244881069a289269a6862c7a5b99674f90f3d41e0a07f97f5837c172967768fbefaccd7bacf39e8fef37632e8f95bc27c31c6744fc28ffe95250b3ffa2b3385fc68fae9392489978eeff74a25b8f8726f3a460f0d38951b54c33da38c8ef97910a16f4418b1473c7996e0e4d38faff9d2f35d62b7d886cb1beeee237869fa8179f80d7012f71ceb725cd7fd579233349ce4912e27adf66299b661cf872b99375a3b01855052742a5254442b58acbcf7448b0dd3c48c34f6e11f7d962db26c326593b249c9d4a745c1eda88a425c7bb1c17831478ee8735e6c4521f146a5b28982dbdae9e4a41e5e8279aa822f9a8d66536bad2fbe5619eae86badb57e5feb8cfa5f6bad75e56b2dd5fcb5d65abdaf9554455f6bad55e56bc5a1b2f85a6bad2bbe5619f5dd556bad29bed61b6acad75a6b55f1b5da50415f6badf5e46bada1a27cadb5d6d0d7ea524dbed65aebc75a63d4f8b5d61a7dd0506badb5b6d43abf967ce5bed65a2b8c5a6badb5d63a43adb5b25496fab3d607611bb5858b1866944838c8b8c1861a5c62d0d0026386172c2f5864088dbe185eb8606971f22bff26d973bd6bc55522da54563a2bd14a65c5b158812352f97429de93a2a24b8141ab93ba5aad36141b4ae1804e3493558661595c71d4f5369b379adeac74b25c82711c37ab76dd4eafdcb59e5aed745c5d7b3c389e952cbd3cfd8a7f601f9806c6932739a236f045f250e325292710398308d198e5f14f1540982933fa3d57a0f1cf1ff0e57bfcd3e4a5793a9de6cc69d22471520c5040985943fa2dbfb0035f568e7869a6523a29275248729cc422091066e2e0f05b2ec1087cf1545e9aabd5cd6ab5922c8d5f0671d23f7e590308337564fc9637d0f8a50d7c5179fcd28797a64bfea8c12ff14b0f40182a73c36f09028d5ff6802f2b5e5ea2a7d3ce499a4ef2e4849344578030b4c686df8e058ddf5df025c5639a4a19491d49e9ac9c84b20484a13835fc762670e08b8a9497e86a65b352e1eef13b0b200cd571f9ed2ed0f8e4f1bb8c97e8eda1b1cb7dfcd0380a08536562fc8e6148025f422d2fd5d3c9757a9d768e3809c40408536b68f81d9ba0f1c71cf8521fd7540a2755047f54451c40988a1363d096df31071a7f0c025f4c1e7fa4f1525dad226a95c21f61fc8e23d0f86308204cd571c9114d32c36fb8058d7f0210c6cac419f8121f7f059c943d7e20e0cb7d17d7fac14bf624475486df9088c61f03616c4d8c413f95a22123fb41ff9b43c9a1bce5ed493a93f6fb66db63f8797c347edf2e77efbd17b66e77effd19feb9fcd77de95eee5e7db7873aee762fb7e9cf86f16f99468b75dc0c01eda7263dd9c31301b7553e1b738e4fcb1c8dc49c167693e6a7ed48684d47f67e822f3047b6bd63188661d826a15ca1a6f6f83f2548f4d0f8fd9444cba0e190134bfb4bfc9e3ae74469ec35ecb1df66f47df9f2b3aff7fdbedff70b458f157cc3f38e2d4b0002193b82b332b0bf1290e1237f1ada1f853a128386ad7a12c7ed4b1b9554018c619f7ab2c69328ff2c6cb18f7992c6731d77ad405b277cf440d292129398942ba9310cfb0861e42ac6b0db639ee7f1d19ef7d7362dae300d0d770e1a638c317e8c31c61f8c57f86938497b0ca3f1e700f1cf802fdb638c31c6f831fe64ac037f30fee00ff7f6f3bbe43f1f6f05c794e421a2e65a7215b9eb1f77777777777717611beef156601df8be0abe71dff31a0d9807b574777793bfcb1558874b77d8d2a961ab618b36a2a4f7f7fc9b1b20c231330f1135cd1280804bfa14a2a06d986b366cd3755a2db99a326eb5b7dd0fcec7eca1edcb2892c097ed4b9115066dddbe9ce874387944dbc87292f5c77f7f7bfeda8f46ecabe0184fdefef8c68efb9e77097842448d3304b023360e3454ee63af84c9db87809354eefd9b630d4a021e8093b7bf7932604b44c9d2499e3eca2a5410331a41a2edfb4324fe59087364dd80619808c3b00c53f1d88dcede5f236dd193ab4ea4a262a5cc9f8275cc77a902e7982e232bb2228be5268eeba49b222bb2dc84656dc696036a895cf6378c1d6d7fcb70b92c3931c730ec66d97b1e22dec0b2eebf2963cbf1cfbe0c1c33d29e1a709a8cad7af1e4d1e44d0f02fed9a79e041e005bec3b00c6b0b19325207c643fa7e5adae9b71e68426fab811064e7745980538697acb5bdef296b76ee8de96564f36bab4653a484fea979b65d80cbd73909fcb7fdd4b96b672a50424ed0ff4fb438f92e2e5e70061a096305a7e0a4fae7c641fc5830ef2a08fa09b78d0471c16c2f2b6c2d8cbdfdbcbdf5316715236c1315b1e226a9cf783b00eecb53c44f4967f767b8b8571e0e6ed9f650cc38698fa3e8f8fbe5f5fbb99123a7fc45134bc14519fcb7f9db5f6a5113a48993dd42173c663137183cb3264c101847b4ce228ef616344ee78bdde0c8775481a54444554444594fc5d1fea191c53676cd2fef7df751cb7f24881dbd15b517b2dda879b07ebb8efa8581353f0056afb18e7b8ef7b077e7f6fc97b39aeebde7a2bfaf08f6693abd5ca7fcb712646f3224df4e15171db4dda7e03608b852fbed1b08ff32a62a3e908843f033302c7496511d8625f7a721505ae7eb4ef5168c197fbf655a828299110b678de40b064eed73fab651d5adeb63f8763b2f71ddcb643cb91c63f1b7d60477071e6da8f3ea211ffec663fd23829b21a204b337ce47f73575c4e0bb622cbdf3a8813752c6f5f93b16526de0cae8b2cb98e8fec5b6bad4da2ad95d1d65aff9a8398f65d078b9199cb9b7ff661743921f5d869a3390f1b244104468cbcc0c87c2925037ef430e96cf3d080db1c948153081fcd87f8c605e29f66ab6709dc7695c6f1f9d8d327653f75ba2352753bbd8005d8f78fda6fa922ddc097f8f3b5f8031d7d90bfe5f85ce524958a63e46fb5e6cd135d5872151ccdbfd1f337367f97384beff0f7dfaef254761b0d56ff1aee790b01b765cb7774bcf4711269cf1b5d6f56264fe2244d06be6c46c017ec79ec6d0fdebca8339c496923814895101fa9ac7cc5fc7ab9112fe2381ee0b654e9d7cf974234e8c99744e22357b9e611c3a095f8a864e993e5bd5276f2ff83db07b82d5f42442d7fc8d64bcf49035fa0f69d2d91e0b6a49134aeca23e0e010234eaeda99d225f423ee2a7795bbea2666a4839812897f734f1f7abec3e9c3bf295fb0653e12dc0ef94248959e53b6e468b6769cb443aa84481cf8b244f5f6967794291ffca3f697291ce312887ff365969e2370db537afe7c9972928aabfc03e22415cf39f27c69e324f9f59fa51371a04d6f29c4a7ceb37422776d6e0ec45336dc7e0da7f19793e63b122715893498abfc9b9f792ec41431117012899376265ff2896c27f38a7d70b7eff107f40ff354b43c3dd2689f5147254bf2f59dab5ef2a38a9c9bc7dd6f6e807fd7bf19c4d4918607aa39ffa80eee9bf3c19d477dce4309c2d91d3c76c8ceff6ea89526747fb93cfd3b82d394735d9fc6c395ae73e2f07ac041bf86d37c834a1c4e82af02de3a82d283539b48f49c73c239e79c73d621477c7d1c4efa1e3a792fc75508b11470f3a585ac283a904416727c4c8c05aec35e829c8459f8735eecb518edfd9b81d07173d2023474ad416fbb6c17191d4a59e51034c8726784516f38347cbb353d3d4f995d622210333c0accd03bb4c7af45896346c6f907fa93161ddfa484fbcf4358c27014dff320ccbb776e2f3ded4fbc1d4389b73b4f0e22e6ce1301314343fa2981db3027ca382901fed1efbfebe05f15983fffe8bb58b1433a4a158c417fb7502ccbbe7781c83ffa54251fca972a19efcd892af28398580f4464594562a1a77ef21384fb5a1b2efed924afc989a3ed4f6c63de649b866d788070d65a6be5f45865ad1efbdffc0be244dd7eb751f38da8bb9bd9a804ccd70b9b9eaa9a110000009315000030140c074462b168988599a8e70714800c8198486e5a1bca932cc8910c52c6104500210000000000002032533500819a244309f0b0544a8228b654742aa02111537474b1f8cc7d87a84e3f0b706ccc1b0ec8bac01862ec4bfff4b149263911ab7ff72b095e2e26069fb6246d7e3615134e2563e6b5929604659317243dee2c2e70eef5dd0ac7cb84340287a63b95e2a1a69d15107e28380e926a0e910ca11a98142789cd5129cdda890318f638102f08e1b360ea2eb5bdbc5e8d7ac0c362282b796f07dceea2d9929983ea304a7489b13d24af6ff352b0a749d31e80208f94777f58aef39dab5e13a5814d9b742de55126bb9d4be05d232439a4105c1f83c9300150c118613dab975dbbf1642352489b48e02fbae7cbe7132d1e575db8a266fef2c1d0e4000d697134618c79bb871b22a194563f3a71faf51e8436070c82026030a57e7c510600d2a24276b340b241eeef29a5d314db36ad5d204c377b1e0ea011b65d14ed90df295ed3291bc72f2be5c064841729b8f78575252132c59a2fe42894cfddca1f9f2c4a0eb1dbdeb41c9e61413c1a521bf2ed68a8ed0a3478cf6832b99a1beaf10826fcbf986af4377555335e70a67978cba2474ed0c09e05125180040622edb80781d833373a2feb62795183f75f4c6d6613e4f754ed1a2e69aa7548b52e6e74fe28783bb410f7efb0fe8f3d3bf3e5097d195de649350e5b7a9f896f6e56ffabccf2dd5ab8df42710123f54671292e76f4e5f9e2bd8b70bfb4183c6a15afc12cb60b938faca6de3a23bab5579f985a805aa1864db5fc9458b20989b9acc1013da97883f0404842c4341e45dae4cee3068bba8b63818b2a7a95166cad62fa632e2de189ad9fb084ff7fc4fa53c3e0373baba6f73148d3cdfc554292d5c86943e8992de446170f0797afa5161f668a8a57cf032d5e6b24aeb6c8711a8cf98e0fcdf154a432283b3d9f15d85b46016215238c562a76ad46145d5fd44f2a54b487ae8bb57051b960d90a8ebe7b223a3956eca62195c276ddc1a072a8536d97b09394a10ddfd28aa4e07a145f9f2e8febda44b1c6f62773756faac7155afdbc67748bd6ff43449eb927e024ba24fcf9445f1de672deff6168493019659f924de818041434a8f1648258841d7fec148adff8749f97fbe7717533a1c6bbfe25aedb89ce5d29335b896552113e191bbf3bed247bc5a5d6f317f0a269fe523dececbdab59cf735abbf51ae49e00274de880f71637501ea5faf696975598a1d264d7a5420255852f36b810e0b17deeb8814d8865850508e993bc6ccd0fc89b10686ee9af20de60164d5bf7a233cc1e48f5352d28b5d2cfe7774b08d2cf3d69791d2c4c71b442822e180b0b5a069341590593202a459315aa9968499c961d3dc17491131ad63ce6e39eefeaba6fe7517d5db7d3524fd7ace008f6a8da40ea9af23bec791938b3a076d2b0d629e413569843e2bb8c99c54732e26ae61b34d0782ed03f2218977331d199c2e7e449d8ea77730f9efacfdd91881bd1170d70e4d542b7a213b975036e4e0a6d748efacc850a430b85443f761e1e15876ef8fa0dbf60ab544b951b3bc692d503caf38fe2aa9a3059ae1cf0c9ddf485fcaa1ac78dfa7e5ad2ac920ebdc85165f456556c66d08ac494a7ea53e38d111bf9b9c5a934055e6cbc8d5d5561fc44253330d288ba659efb1a233199520ac2f2142f1851c3b21ee04f48c3d56982f37efdfe96fff2217d2cad37e5287bcb3aab7081dd3eae45e6e5ba49fb02fc873d8bb21d90f070746efcc0ec63cb3cc967f8b7c032af7c198ecec73413cba1a31d29531db804046969e14aedc460faf7ac8e91c3aefa465a4f221660cef8e58b7d4481b8ea6e0429c31784b6e5da6d830072b0e316606b3bbd5108c0f285dbce02204b56001eff7b8637eda032a28972151d529170f87f2b4256565d5a3e2cc7ed4248f27f705d5ffc858ea7494a57f367d604cf5f32adca17f3c9bff0629063de91d4b8ab542c841bd0fdceb9c06a65d10e59cb255d80322e17c4200092847c81a2e0b302e472fff917a541030d0cb806b85d370f547c5ee1dfa2dfc344c02b70e4ed068d127d74f59098f3bf98d1d240fdf43e92d6f73416916e2de45eb1b1a7836f02a9bad7a46ed12a189a5b781e79d43f5dff95fa3bac4420fd5461c23e2865af7d609a331b537481ba416c7ad7276f9452b5a970503c7f024d2668a60340268962daa07a456f489666c1f665ba74858fc82c44806d66aa8fb40108faeec422034267573c720f9f52c78004d35c99be3e1f6bc814581122488c96d7b9f03f8785525327f7c0f01cdb58715b970484fa3591a35cb3094d6f18bc4548d1d065490d93b12f920acee93d91d78b864fd25673ccb245b9d2a88ce5e6af30decdccb3a7013031576f017e0ed583905cfa143a5e14f6cf470b0142a59810f5359ae9601834ba0b57d0d92711cf983c8c9ed05b9665a6254201b9ac1279874c174702313c23f59e14cfb116a472d22df611b53e76868e54aff6c05eb4c5e7357f4af0eaffee8fb3c887274d9513341238180bfcc949d3695ceaafa38bb6c913586e783df4b49a9a5204af522428ef95a766b8e4bc1f37fb575ce2fa8bac6e35277782d2937e3876e0bb929e1910f3ee7ad7a3d079a2013be0956616fd131aa17a312fec92dbb3177df88a0c4698a7454688ecd8ab13355381924a454a07561d959bf88c1c6a156b2ad4ff617dab32142af4520e451016e0ef28a3e7d6997570343a4f398a9d4c626b45286e1005a089cbefac12bedafaebc0c5bd008abc118efca299d2085c9b61919acb8d811623d8f1d0cff3b0cee5e9bfca2c8c5008b048aace79ae515bb047d76456f8792d7a5af57bfd9371262e0ebe6e0f127e3d2f09856546063dfedffb5151cdb33c5b5f9dc0268f4101722fc9dfa5a4c720a38410a60dc94db6685eee8b48d905de8052dd38afe8c536ab028b76963ae8cd082da9f405a22add4efc5f04c010c56c35863a56a75ae7794b6c0c215b4008836ca7649540c0ea3bf8e815635bfc8f9fb3127155e1ae6ab8f55709fdc65cf264876989e9b1084abc8ad067321afa4b9d924094d0d7f2d7a89ef49c09b8cf3a41db76514b0158ef9a915000f380a0fe6bc28a0cce7a2d71ad60ed7697114cf25292307eeefc88f19c49ba2238dede05e204f3e58ce8719dcb90278a3fe64820889bd0fdfb6fcb4e894311525b6ee01282e1cd8d05f7800b7453281b28b9fc0b04830c99fd560539d866429aa56056485e5eea23f31b477aa42efbd2ff0f43c6812975e09821e7edafb01d40504e378cccfca62502d6ac415cee73c2fb265a3c43cae973e5b6b402ca61d94d89535caba5c235885295545ecc685a0cfedbef5bffeec15ab205f7d00be97ff454b7875071d6af013aa8efa14c33da187e4b12441272cc1484e6f6923a0fadaab878bb1c182e8448700eb2acb301681bde998aab4e8233786695793dc4ac3bdfe73936de3f58f44b004a3392b15d550abb32a2dc98d1f82e5e7272e8d405c93c0f2efc2987822254d2022f6f5b692d2d7652edeb68cf4aaf117813706e29204053a476e518673827e13d20f59c4fb38e353a93878ff2e47e89f1798ee89d6fe612d1f9d0b8eea89cf3467594a7245b09ac7b69b1baecc05bf017cf5485f7308d2185b8de092a8ca5206677f70721b6f82a48ca238b6b95e21fb815a9b86852f0edbb0dffc2c8ce67325264771520445989fb5599ad1182187781aca19b3deed910c4f6ad607e56342f92326d684a349f626337713f1db79212ddc9b25705cb8348cf3d7a42e9c58ce55b0afc17ebf15e6dbfcbd8e0a5b37157572f39473bf7f1878114110bd029b7f7ade49955f09a1c86702ec1601439262bcad592bf6b50c9f081909510006ee4de0a0b52f81206d18e359757acb1205e09815eca89cb39ec8bdba38bfafff338b002e3b37545315e0ae20b0024825199c79e28299f41a43de1117a8917665d1e5f3a3274025a24ce74ba310d66ed2a43972e8283854351485ff0a0938cbd9a96fb9ff6c17179093be36ff6cd683d62aee8caefb05d9d08efb617299dc04658775d836b9fc0209abf4d2e1772e52d20660387082bd5ebb36ad19ea0fe372c323eb09cb77ca2159bcb56b7f0d08413181be048107e662b56a7c1d168ca9701a6cf4ad488da7f61050b3740889700ba8e8dd8bd1a1f9c2aba19f2c9242af4174815281f89726b0adc014a2ba94ecd676557fda6002903237b23bc3eef14d5e341448d01a02f7d72a2aa0f6155ddeb2aa5a83d3488aea5e06a460a0e35176d1a00f23c23e19695d6c63570bd120c30b48837e5f72a18438c8c661f83a34f048f788c9a861bbfd3ebdf3e2b106d1420b0359aba688313a908a83a44fb63e2598fa707391759ace116dc948dfb498bb41e51aa651897ac6a09377400ccfd33f3bf8ab22f7372282570f3d781f9102676698d9f1ee59fb8252d886acbd9b14294940eef263c888c42525fb2a5a633062eb58a5a8fa4726e9ca7202df94c8a304394d005dc398f1133d20334cd9ce73c643f5cdd561177997c2ea6b3d0c96691eee967f2a2edbda9bb53493f63ce9e3f8b13e189683e2d2021e79ca7456da6fc3a4faa67f43b2d6fd672201cb4b86e578d751b246cb283977677df12c0d91787eb589ca478393a8c0e861544b693b80eae747eadbefe80a0a2b121f848371bf2b4889105947ee4dbebf8995d9d30ee2b0b237e57517086edf941632f167e916b911149d1c5af3c405fe64a38934ae9f70da8ef6ca297af28d36255f410b825065b86e675ecb79a6a7d062ef055fafd4c50e0a3a5f43cd84e4d6a72f815ca1badafe370923c5e2ed8f7b612c139b8ce089b000a58bee0783914cf478d1578a6ea1d0e25f8d73855a0440c90ccd238684e327256c2664702a9a1e179e3bfcc8cefc8e3361ddfc91866e230f7fad0a2679e76133c19064bfed97ceeb50af7223e7f264a887cd761c89cfd3d2027a4fa89f8f49aacd55c2ee1087ff30adf76004b8734beb512afe4189b37f92382732a69e6257588f2d1029eef8b1e15c6d3bba9308c01b9a03dd454c9419cdf3dc24f0c88a1f09360be7313ff09a3040e6bdc4177f183b661c77fa9dcf7bb06909a263164ab33c8075b098ba6e1ca135560b37dc458ee07558e49c5e4175a74ac0902aa1ab6c8bca5392029288d151b2cbc34e996c01462622f2ce953ea5828f38ece978bb664620c2a57d0560cc153203802dbd157e5b25f71e153a3d955c9b45f51178d606507e5a1ce27783d92e63b2af5b0eb75ab3b63a8d08dc2d322c552fae26d55db2c67ca2e3b7d12290a74374b0f9e0ad77ad481e846eac63c4ed17c2c0cb98d662c1e0befdc447437088e5c1a83c183a82adc00317661b6e39d15a4a24fd010534f2528522dc4a65539de27f5e2584dd4e04b734ec2c12862b21a06927f65c21f551e693eaa4203a432d0f91358c21cd50d06c0f7dbc8a2af5e033cfbbdf375b22025d573be9f54c822c456f327949480b3c8f88edd29a41110c94b2f488cb83abf088da3f1e1287465264243cce840e3b52e76ca9600b344d21e78286aa6a7319b5c480b2ef327f8394cbc5416c40ac7c6c57f23ffc28ad01d3b151a0d969412cb075109684ba822e1e7f462038b50cbcbb97a1502ca4abfab9231cb4c502a35747598c094d45df41b81f369c52d2f89571d209a7f9440cd2c28198a8c6c45603edb133bce1a26d9da7e8c11ff121f2f18fa8738d21ff6806771b921a6bdb93f13e8fa6dc53121abb504145136d5d0d8164ec1ac7488ebfcbcb5afc31e3ac31ca761b48eb5d2a13eb4d25f19addc2bd4b73df90682b82be17c8c114a95325ae50657af626c4dc3ddf7c622ebbe5e8376441ca7965d56a4fbf9d45c9fe3ddfdaf34300bc56a7676e4e0efe70538ec7feca37ee7e8ec485f6a8b38a037ec4d6747fc6d459d0f5d04f6a4f0a27f983e19360567883450e8c33eefe3e8c5c62147019db0035683810e803d8f5eb31aefe4d5635eb29806babeefab61cac133e6e37b038d624df6443a041c27a4d9ec2926d8c75cde562a1cbe60fe3741db07ab52d3a9222b428037b97527c02e50e3bc4f796c06372eab17abfe116795e7669b85477ffa0aac31e82865f3a0ce1a3fc765b78c60c0c3ba28237f70c2a77c2292d3c70f5fb893db8cc2f5aa67e9322731b33336d6fa290204f0f9535a8fee5400d076bc5096d0da63338d782d22f6a4a14dc5140572e04b6623c32b1ba964868e0b299c0e8f8841e392a44224f9a9dae394abc059c0dfe15352920f984563d0057830f9921ffc8cd834f0479bc760b4004ff4f8d8523f5addbd42f798b750a532bb2085d04c53efabd4e8a024435fb98ff55728495394fba951b630ee0b0026ec3710951f52563f88e81c20934a32b4fb89f8388ae0f18c27663666af1f65d92b53944a70f3c0b985eca876963f7f109b8ca00fb825de47f993c6f8e3e03e8ad62b95cea7fcb33509df4759e28eeaa48c5bb9e7ec9abb08706a69b0c4e0b51632c9baace8b308b50975f5c268d2ff24518a7e81f5548e7da5d2ffa361dce1bf3b7c14b96072921c51fbc3794bcfc3a315ea4ba3b11f887ba43dca423494bd9db9cac09bb8a74832f2a6cdcc67afb836704ad115d0eeba56007059482048e9f71302e3924a599ac10776604e81e420740b0de29603c749a11c2b4d3c40eb223fc98330e439aa2db4b235a4c73263bd08c5fdda408adc274d471ac66507c048917883940e95f3604f344735beaa8a1b0ad76404fa15f4f969736f8e3012485c9d9e66a41801cf10a25988aead275533aa554d2e05041f2fb45129cbf3ccc538a937b7f2fcf4f55445dc1ce275bfa06c8ba979c3f96659dcb6adf90cd99fb135c8fb446ba10b5f742522eaac52c5c9478a86bd994070d9d41008b977cbd0e50496f86eef771c5f1c1a9cb1ef241d6f32fac44fcb51c226d4661e7c7f0f3629f36840cc5a5e75069d611985f188446ddbe759e41e4bc6daf5e9f0394b03db7c30822d00a4b8d44ef938de0588ecf56825515eae67ef240ee390511fb3ed97a3e128a2524946f7700ee336e575ec8c0892d49861949057b66b6c00ae72c97394932fa0d3625c6d1f4913ad4d9900a6e15366284dd4bbdd19af79d6521c1704d371226482e96dd2eaaaf8e950121f894f4c6d3029a0dbcb9bb388b165ad3e8c2943c51d9236e15def4589972d8ebac39fa795e702f702d7e0f0e880b171680031b0e80031b0e800b1587400b158340031583400b158f4590bde15fd437345fbd05cd13ff42bba0ffd8ae643bba2f9d0ade81eba2afa77d30311fb257a45fde072cdf6c8c6c4d1f9aca8b147c9cac6a86da5485f8e9a521aa117763b6c6f9c83ae907566ff2f988cb923d8f2f8a9b8d7905119b5b729bdd4fdb7e1275f3aa6090e3653812a562f657a2c60b4e761a52bb619e78ea6632a8c1a1376a7bacfe4516c71aab351184a70d2afd08acaa8ab3ffeee6f306c462540107d527195f57312bbbd55e6d37d24f3e4fb7bc98701d4a3c4c829bff0d2489a2e26e8babbce10d63c58b4623a250a8dbd4e3e3a99bd00d7f7d86ba14ead8cdd7a89074340487ca9067f3f6bb42ee5f01e18681ba3a5dc4aff33a5c952b260c70a068059295caefa17cd5ff8e09f8d56a51eb54f03ba69a4d4b4f2bf061621b260c160441c68de93928018000202477c7f365a93fad53e1ad06d49653941b0d4b22cb39f355e92f2bc67a007b07a7d718c945b093f339321380f06011164ac0f1ea369703e6a87ab3e8bf316fe993c6375199380dfc9515b273117ff2bdc322a63d470393ba21863abbd32ff6b79c434fd29bb4ec8b74d1b304db3e46eebb9994d8e02bbf43a02b12060cbbe9fbae5c834b4f167257ae7ce133d08129c35354a654d5034a2406d0b6de293216c0532af6d448d682851b473df87caa79e23e1c276bda47c0fb82946bb0f17eb84be621c74f88c5368da2455df31ae548089b70a8838cd4d39f661de1b6c1eae10d1827134effb3d4974453985856e0795c692ea7f86231666e2cc2242c377f71a9e649d4539d702f605dccc4ceb7f06c616b6c2453d0498a2c92eef6136c6c5db396b630f9cf9b4ec0f5bede273bdb842cce8b4f7253de6de0ba82200836bffb2676007b312f180519ec240bd8fad0d0103b9890be87e0d0eca0441ae568149be65c1ee9491ac03c3a93703423258bf937dac88c062456ddc4da57aeee35343f6a0643cf9944514038abe1b4e701445be53224222344c5428e672786ff89635a10fa986a98bab6162a0ef87782d12f28e5f68dd0ffa9b5d18260d781ad2aead97776356d0b10a1a3fbb218d52b968c09af444a5962f950778f910597d657bb5f7a0e667466f90790442bf3ba1c9d5bbacab9aa696ea4eaabd04d004f7aff61fd76a066d16713d385887de60fd5f23a5f7273e57f710de558d5bc0307b525cb889e7d062daed7106375f212e716c20ba3398a5f71bb73417ef3c19eba548a6afb54c10bb151760cd03209976b207618de25820c5813a2188e6f64e5fafdf7a9b9b56ba2faabece53b113f08c24be95ff23e4ef65b9bdf847c240570084951aeeff73e6db3461bace5c8d19b6d301f06d1bf339d582107147d41d36e8a51e01c5afe116dfb1ab35f7e3141c23eb5a737f9d3ea7934d31a61617fb3b6236b343af31074053478135ba2dfc6b39c86eb35b0249451e8089e4debe54b7594740917bec6cf2b2828f5a97628845bff531151373c09b6d35da6a16ac2540d3694c4b4f27c6b78db9d546834fca923b32160050d84c239ce968ce6adb620a2e92e00c5ca74e8133e66daad2422ec019759320c50889d0737f33f859ef9deafcb4d413eece92f24f48bfcfa9c6cfdd2710f320ba64d1523832b84029607c7c6eefe5653f97c4fbb92072a742823be40c21a4153544d496c8824925627e7fc5fcd365329f232e519c18ec2db9b0cae1cee02dd53a109d89ebc808b58d7ff9acac876af304218b80241dc31044a13992649b5f31fe6c83f27d9e251bb823d75074fec710840f1ecdb4a10af65a5c7a2f9cf5e3a000f8630445000883e8e79000408ff928bb4458ce40703fb9a1f77dff9b517e345b7e8b787d2a84202ce78d84409e09c60d05980115002992c5e107e74624cb1574f7b02b4129871e9130a24d2158233df256de689344f4d8c61a2807301f31821832683e4d2a4dff5d87a2335212e2c4c18572a1b6235e8636e9486803c86890462bd2572864477c57c144ebce2ef9c844ef3ab5127c140d262f6b72b47c8834f1c8c39d3a209e0432e667f046d1c08f862e79efd9f65ef25922db1760120e21804e384e1d79ef43e6058b3f99e750cd7ae8585f488024161287d0bdab14d8078dd3da9fcf4337672c7879ff6ea40aa30311c382a75898f8c387a758e18d7a86f1c2f701869ea73c090071e15ef4dc2862daf03f968efb19b28e4f51c062654b7556953cd3a8d4dc6b62f2309309d8b4ff3796ff77000ca4a28a87c468b18f4449401737fbf77dd6c210c3a13ca3134e51303a073ed5a7d320a2780f1dc85c6b311d2bb6e5c00fac505b0c061c6034e43afe12400a7fa594b7a1bcf066a37ad95a7ef9fadd654ce1189131856346c6168e8d8c59382632e6e45803f5d65bef62bd81ebc958efe30fbf33e68fe632e68f7619fb8f7619e38f7619e38f7619233ff6c57af0d6c3b25e1fae7c26d95244a1f14f936ae353055bad0fb02d76d804a805bae3baab2332ece14cde842afeeafc72f20444afac76e9ab9165d9046249a45b7e98e349f5c6e1733cd2337e618eb718b677eac8780065e34a3bdb17d99803eb7b505b9102d1346a2dda47e1cd08236fdec6c2d190a95a1bf2a22407c94a38f58609eb346a9c5aa2f143ca784dfd4f305e4bfd4e3986000366b1e6fd4d867ee34d2839dbb319a95723e7072f8381a9d9f846ef2dbe82f58beea016ee8a7565488de33021af729ab38fb14b281bfa023a9d08f54737b440e46cdbbce0288be95018c8a2b43b7b39c65811d2deab0a935b55b0f054731ad293894fb8f020886c9a809651fae3d4069e795e9978635642e6bd051d57d730dcfa893f5d959f05351e2e135ca2dae5a9e193300482455f478442e289190658e8d8dc139c268ca12c5b4e6354b2a0befe902885f31f7254835731b509a5243114c4be676866dc45b3b237d09f3251ac32c892b40398094f4577052ba06b2952340eea3ef71a47436c2eac4de6e5e86cbd4dc11c223fda5d5d8bf28e577c03ed17af29cae15d675cbcdee09acc8908b6a3629ef115032ccdfee3eabb3b2a2caa5420b97cdc8934ac89c76482ae76e2ad1eada3ef8bb865fd1038a6a219ef828ab8d5979d963d6c1b5cee84821c1f69e0c10d0b152682be681f0299390f4c58c410418c88c4884b8c984488258648229668f010cd6e17bff786eb9b4f3efaeedbd7be7decd3c7be7d2f5f4cf9ccbe12e2a7d51fa032c8201a233bef4ab95bf3a017de961b921f2f604411ae5d7e47a315edaed420b78941185014f3078028094eaff37eec9f78707789737ac1a6419bede2203d1ea38d8ae94d8cecea1c977c71d35a4a55201bac92cbccc29c7ce874a24a68a508c1787581b17a0773afab796b9b199ebf10f41db68cd7a554b15a756f2374d59467a5ba5c1f22ecc6544369b1ca5e46e8d634d55a71f93e22dacdb4aac2ea5a3d46d2a5699e552137a8dc222c7d8da48b4965b5d5eb7a8da04b13440bd525fb886837d3aa0aab6bf5184997a679568523f415c92e3339544e9d7aa4ba6c74be22da6d26544e5e7bace46515c511f63e14c4a081d7b431d2c625071574e0f48f63eebac6a251794a2b23736063498e644bb1074303291c3a0c545ee3b9d1cf207469544665b7b5b9bc13747fb221fbde466747b3d1d8d16cb476741bdd8ed646b3a3b5d1ece86df43bb75f1095c169f7e264d0aee4852c1acacb02bc80359467b2883ff130eedffe06633de5b932d1292b8607589df24cbb07474167681b2212eaf207224404a3b2e9e047cdde863269c5989222637cf02531af31c8cac55c9ca31578be9277e6ac2894258fb0dc69753ce50a7529c9117352999e7261093956493fdc6451cf464b7d481c2290208554d021d8de43d9a531e6dc0207e0c6c923d65336cd84150f8d1b9eac3f1e5e5cb6a8d94c35d9f70b98c543fe405eedb9c2fa29e7d60d066f00339f2cee324173f87cae8c90e19f30615e4d06f0267f0da91a0d7e423439ffe95e989311324edd4039d81ebc0301209e0e31f7739b2beee8a025dc0a13535f8ee712e0c72c80a7846f2fab694603370bac5057d734a16224dd7ec7ec1b1e67d63f576ec3ca6cf49fa47fc9c60dfdc986d68ae6015ecbf5117e2ed1aea176cb341fb58967217c15f5e3c07c60980bfd39e8a09bc9ccb8bbde50e2cc9de7f9af0d64d4e18f5dbe50ce883b0a5d36e996a8a730145d79230689c4b75a6aabf658a41779723d79c9d83ebe5012cd572f1b60c2ce5e40e5feee64c572c1791c7b26d49f7bde89ffc748abf064deac728b130f0af563e31255e1477442ccbab89a214e5fd0f582b652c4975fd6dd9874fee0215eed7a1b6704a7fcd3f577b57ef39d13b3d2af24edf55f519cad78e3bcbf5271de1ad36eedea07a8b1f4c8d8c8201e597c31336ad7b657e92296b31f29672f412cc48cef97d8fab31bf95dc6e19127c4659fc6efa5b851019e40ec3ceea9e2569cc6c67e6094d45cc96bb2e029ac0db28b50c98c6867b2afb7c0c48b6d3f7760cae3f721a654d34876189d789ab01cb928e087f8a7a330a82e8e1f224ce7eddd895935658e4bcd87ebd6c6d909e31315cc3e25697827a0b708380d18979519448d68357e5b38b619fc30f8d3c958e09053ed1f4055b6ed727dafa4d721212a51692b7a8b1b383c3a1fbe8e2b9634f662b0f04b84a7b628c0d08d4546a502f8ee15c591bb5abe97102d136259ffb0eebccfcbfe1d1155029149b34f6b3a346d9d2ccab17ee3a661efe9d2dc792677b7592068f4cfc27fce7767a09794a8385005e01f10310b5fab0fe412a2654424ea1b9694f7ef2610e85c7c131130ce5a57aed95dff3798183cc50459fdeee31cacb7046964eceb92d0729bad1dfe9a7f2a7d097b33a28605e8ddda6e5fec39af856304c6888f31720cac983ddc7ded5ed5e720e2ffcb95e20b6fe5c333b4cfea3e3e0f5b2fc89bae17123c34710da51a38a00e57e51ea110deb4c031b0ecba9b38acdecce40d3cc24301d4844b11ced61cc1de2cd11a2474f43ba162eb57d75eaba502ec5f17ba215d875c256b9d1d67eda2b25d3e4055f4b7bda276f4649e6ac1f0fdcfd19007726cee0c47f63e51f9ffde418213009e6e3744a5659ccf0221f8db9b6658b89e9ec0acd7b6bc5c582409f80cf37a7825a833225a987d28a79bff1fead648e78fbe366b879341a651613123d844fcb7d8d89baf24095b6471d126845001074491126289d0081c09adf652aaac9870810712443ee0b1370038fbb528f441b2bf81d1737f7f33fb6bfa6fe3a883cb650a51a6d1eb2092b656bd3c88898854bbb454243a44598890c3490d81263f57497ff99987c49050e06f50f92933fc5455305349566cb19e440ba4479a665d0d2be25f1034b0d708e42253eed9edd90353f3ac2323553f4aac83b0fbbf7117d0b22915ba98b90d808a0d121d6603592ecc527933488cfab5b1ab9d862e870a7fa77b598f53f8064a48ea5afacdd86cb702cff847399edb5fa8344388e7ff5b08cd5261e9db2230ab3e18d3493dbb9c58bdda20f6d17e9a50b97e6a8958da277f0d09669a9b38043398a9c43ac9022f7ad7d414bd290bc3e66acf997f3a7366a26233134f84441444e4730dcd271db06090e1128c188bcbd4672d87410ffc005bbb0c6a275496fd14945db621723b2d1e3489296ae4678962588fa68c1722314f5852001ee2e1d7f25320c289128666ea48668fdbc18d11f1ce34294f627223ad3db92d180b8ca10775a33d9f5e2b4e85e74cd8ed7c7b3ffdd78fb28b9e316076e05538679264e2e10288208ce23db945e74e3eabdb2f8383dfa67dd89878794a8c742bc5ea428afebe1981e022490d171e7037501a784f315f8e86b11f6eb911537bb60fe91f2488a1e2ca508c9fafb6385ac894ebb8692f3629d6ba8e61fe924a06880bd87415a52e33b1bba204ff306143e25307fcc65fdf33463acc4ed2e3a1d3b11a5af52a1d45dba0c29d7ae6b72f07882f73e5a590ad85130b9fe14fc39f48b552fda86b625649f01a4326053473fffbc850d5959ad5ad0863dadb32bbe0db19ac796b81ac5712b59579593272047281e619dd7ead4cc73e16180a78e4441efc615a0ec9c388cc70b3807fa2b72912f611873f4beefeec88fb26a6718a6ef6692055b831127420884fdecb016da25c6e5aa491fd60001891c71c90a62d8a26a964a7d710be5746f298c365590118e9790b615d8b7e63f61d99b9f83dcb7d0a7ec00172bf8ffb930b616a4dce08877e73f4b0246059ea078af3672344ce31a6198476f127e0b94f5e69c6fdc6aedc31febd34ba31e6d260cff4ebb3140cc7bfb5b6c646e6e3f5fc61d8a292b1ff60936dd9c2a6df5cc891e5c298630740497ab3a3c612bd3ab05687d6a8998b4818774d590df767a540d526899f924fa06a87090f6119128633ffec8c09908fd183db9ccc10b5118077082d850a598b0bd6708acca0d821cc08e6dc9b8eb4744485aa70bb268f05d3a0e3c1e8a1d2e305d050c0ceae0e8b23d00aa374e09e89d84abfdef0c1233eb40234aa5a306988e3f06388e0e8e3353e424d6952dcfb9261f79869b507e868771fc2b649dd1abf584e0f1fb71079ae41b453329352c8e1278bd99d1cf72d5c612f22a75d5eb32bfcc0577e2aaf5a73d6bdc87f4b0bde289667791e30d78a7b2c28ceb0bcdbf384c0022dd4ac417cec2c07ce982e7c9d28679438d947362cc2c9ca3dca9e8292780cf9239db0caf438bc193044a629a0d09fdc131a777885f6da2d00bdb7f548196f994b3154396087699140b6b56dbcd6344010049e89e501bd651590612b3ff23409d91c1cf76288e27b5877bff25232e0dda8842e01544da3001dee48004698a974aea21e341e3dcd3d7832b86c1806e4e319fbd130742661f758675e8244ece4550cf6e57986599576da09a7fb7c86eb010c03738d796bb7b8e02cee7a217878e798d6932dfcb06a22fbaf779a7d236d9198391c77012a899d9dbd4b523183ab55ef3292ad1a21eb90c5c889ffe4054efffd5e194fa06d54f18e7a1fa7951ec68c1659ca6e46bc5a93e005603d7a7f290a6616297e0339e2c2e6100a0c44a2ecf350cd5382073ebacc4f8dacfdc99ddde3e44712ba4a2442223269ea749cdcd2db41703a85a6769471772e6248bff3e0d41d8e7a9360c65b181f7bb0ff9d8f3595bc57d90d2172da3fb755f20acb3fc594bb3e65f9bc4577d988cc964d4b651dc283f3e4ce941d1153ea9233daf558c78aa9d611c53806217ab8f57e676a4fbb81d9f525c611e2f42b7c33d9856ce4dea374d56ae60c4667d727841580e712b73c5cc9fca6c48554a2c0e0e4f1a2a98264409d63838309e2d3295809d59ff214137e16edfdb6ab9f7c6ed0d0a78e1581bd664d4b2af821f8065c359eb6f08ba4da9c4d726be3ce566c876c6cb2d1dbad12cd22e7d7e9ab66c4010e2cf2a06d123f26f059352e25c727613325ba9ac092bc01ae76da6953b198c2d8157444813475c36510b32d587ba029d013b37d4c8079282d0a2e26a43292858c07a1a70062d213dbca8530f9e6eacba8504b4064abbb6818cef59b151e681a8123aae4fb20e634dd872692ea3378003d9e2290cd88ae9061160bc07ed8218d53fa173875fba2f6aca8ac23a966da8df6e1e73a30a4b18f1f315d3f0d436fd63d8f84eb42304effe1b487ba009807d74580060201a4a201d6f17123b5a525d1e6672dcf00948aa53bb852b17bc0e4e4184b476e2aa480594f9cbc1fb9f219a27919868bcf9d4a95691d0c60e5ab447d8f91f1b9384b04e4429e54c0af0652a200d4a6414d965fced59a60205809b3ad64113a7b83ec1f473c9cbdb95b0f3e556a24a6affeeb8aff7878960eeb3fcfc4ad1370bb0cca277af783d463d875ae11d12296dd129402ed3410f409e51c619a41cf8a56d4f178767938e56aac3084ac056dee0bd437059bc9131b48004dc1ed3b0d00cebfdde8b81f76b6797ea9732958e78768e782c0e43fb2e5c49448b62e1c91df5d1ef4e808161a716838563fa22d7651fe7b1cd3b12219702669af3b419034565ec2a21fd746c8ae9be4f9e30147cc221c0ba0ae2466a15fe65b73d391a3bdb7919c0326dd1cf7bc91b547063cebba25764cb0a79093e31d2e7927208314d5f5454db84310a369564da1897a8126622ba0596e262286d29c74a2db85be41e855f7feb7e23a879edfe715c3a1715a125827508931469019a345ec126e4b55da021afd4ba0d835dd76b3669c0b03a93c0188f49d3b1125b5d7f939b9377cbc5809db71b8414f9333b7c5355bd3a659290966d028c6049c7f983a5254b1cce27ee842a24bfe98d59e51ed35c20feec2487b730cbdf005c87d29212dc2b5e2e33dab26d6742f914267c2850c12260704391bf7bfd131dac87b770daad48e5ce91ba7378af526f2bb7d41e9762dccbe110f97651c6e2bb38f5ee4072c28b55e0e2650552660f524d8bd6a830088687b5bd21efa8dc112430b45d228b3c1c6358848c4a7a017c4015e97b839b324336a29cbfe4771587776d7e5ab6723e20d83e1a9154d6499fcd4b221b8cb280cd15bb067f5e527a23c019de3a40e227fea9c9fde6d8a8ad92a49b831c20934a0b2a7a839ed020274905cd25223eaa338dfe58850a1dafddbae18095bbb9400159fbc379ea643a7dda77a651c82af913dbcff48e584a03b05419f569654696bc02d40876eb3d161eb8b8700ab740d17ab50c87d90d3c0aa660afc7d1365913e899e544f9c35e37acaeedc16bfaba30aba17eb321590335b7ab66c571c2db21b967b374a10ccc2861778fb171605d5bb3be588fa0b62a098c2971545d07682cd7d4a116e6bf36bbb7be3f82d04650dcd4851b696f3ce8d3d1b0b1dea6be40870c5479e4cf27097925b52e8fbd0fdc512eb233be5c1516a0c9d97756cab99e3dad216b37d1d9fd3b689cb4b078531f56bd7b95a23e4a527c3900cfe955fb100b26369dc26bc4f82a0105cc4182bdf6e263e41ce9e127b6fa3e963a4e208487406b43ad06a06809bc60e28fec171c5bd0262236b0afd30841d7c73000ef1fc0d25040b3c49c4a93cb1db7fc8e86aa9e7ad238b2d21ace79dd221bcf29cc24ecb991152191373455613600db31a94fb7db0bccb2d3c71e394c2d87fcb990698f9290a4b52a829b0af799e8737b069145e1aa288ee81a0490f0382aa1fb07120bb68cc2fb5d4dcaa16f8f52590625bc828f2b8ab8106ca761128eb0baada1654274d40687b021d3d03f77e28978319594d1268313f3ddb246badf387a4cca56d157f14856a31e38fef3ac8f1ac7a8a7e732a5d1d9af0778928b3d899f33101066c092222e18c728d793f883076c7ecf109222c4c60c12d96d0b47626a02936fcdab001df26a236bedb708f483c1f030eb2a9b730d62970f8f714e91d1004370f2f2d68a3a5932d655604d0e2884414ca0389feded2b4e52fc23f03e2919443143f3c97ce99570610373c1a9756d4b290878efe4b09e612a906bd7c5902835a0b128d2103bafef229b22ba1586843158c2e4a2f7631f53c43060a336fc3fabaf2100ec45bf493ce9f3f122e3b71283939f44521fcfcec41af0ec17208e62565b669ccee36cc677f416e0dc9623248c815a505e3c9020a55025264c8db9dcf4b0b26abf17a8a0a918651e69d3f121f41ecba69936ed0c9c96d9bb8637c0d0e6d197795c64ac1ac47fb0610976946b7e7c3fc5899a47e24665970d48792a1a513152d62b8d8361cce1aa82685e69d9513161af1689e235f10debd9f1bec0c290ab1756b623b60b7681464903ee0361891f4c88ac576a6e2f9d84bca14d05c99ae0c0f1d51fcce7e9402f5b3927cdf21d22e86f0f56d81856b47781d436dd94373843d1fe861378dc8d36da44ef5d08ca77dadc74b9ec20fe9448b63e8ef67b1ee55fe1c68c99d61b91c97ae01a5f326363bff5f7404453314c86608d2d393d4075e27d9d96f038887dbc281d81a314b22431b4af5c88736f1824e6c3e4b7c3e38a3c9de503081236d98ba552839a7ecbb3cfc0f43ec4044cfb9b2f5a1569eb3b94adda1bd0056d9d11fc340d1d8ee97a22a312d360c40e296f0490d16ebc2ee64b1ea43953ce286dcac4e9c21e03872bfd4e284f71a9611b596af3f16504cb9fde8fb17fe40fcf1a5cdb00e979fc09cca1bd5e876e8dab2a1eeb0b2c1feb766bbe18266c44af867172ea92b729e856660ec5148687d61bdc869b2470d52f8f2aac663f9cd2ea32fd71dd0f15c74fc616ae572fbfd606b071f96329591e3aad2f55643eac01f392872c29c48665d515fa61b626b0b4e294ff7c2b9d0c6c561c0721c289b6bc0f831d8dba524414c4b5d2372dc53321bc0941e2644538f5b61a73162f8113a2cf3bfb222c4e63c639e9fa1f78fcdb6f0292e27056558e58a7d48e2c2ae717db3c82cd3fa93e48990ad9430a8ff9922f9e7f205e06ebe0e3d7713eda6a69002d86fe74cc4e8c9e5b2b934b565719cfc43ee28d4723ded801c16c5668c35494a155d982021e3c6568b930fcd8944af3f6f87f791166097a8b59abf371a05cd10f84097d10f76479cd68befffe3d940cc0510c81d791b94901026d9dcdfee8f4267390a331c292a10bef2c5ad641c8cc3456823461c1b91813a45eb65c8fd693882bde67c23ac98786ef4f2d00ad2737d8f0a7a6ffcb8d841f0324fcd4cc7dc7c4d002b4c471b7816d397cf53a5fbc6dd54f89c42a4769c4a946a8083f2e2b1c31124306ff7611ff4e9f9c9f61861d09793ac27559d35773a30ccfe763ad2e4899fc275cabf4d6bc995a38e8fa8592a7d3fb56345ec206ae719738b8ff3d3e938afae2c656ef36a3e6030d85f7cfc21311bac978eef1ce61e496d1b25dde87e9f509fc932d0dc582867f20b788eb3ebc2aaeaee51409dc105d5818da64eabd8ec6fb5779c08459de5671f02eadda91313bb7ef9038ab2e312484414a23d50079b064c9da048d0258d80e3751bfc82ddc33e244726ac0f2d36f71d505c337d442d4e7ab6484a4b47cf03eddc2634268ca402c6f8bb30ccb35745acd2c79a2483f646cda03d4d904897d14ba10b76f013506f448763d336042f765e3a495d0a6c980089566cf30da894f5f87379166a7928b50e4e527e78b13b10866acc9b46c96a5a2f92a61ad0fe08e8ae347c71324c1e5e15168c23f84002a2b74fe25e7a90ba03b702200aa26dadf7f6dfbe05bd6a22433c493d8d1fa11283e93579bc1e5c62a05d41b37f02f9f73c90214efa1615921264ae8bf1c72f8452d32eeadf6649e76bfcc612660d1c81b0f1269345ca7cd68afa719359518b53c72799c3068b61bbd82d457e60b58b3d66251334a89e5a781665bd51106f6950637a9f6da63a890700788616a521e9355da7785679a55a0154c22610e1b4d108659d204fbd06963dc05ed44c8012790f796e17fb329ad191b39f2b1feb449ce9388e14ae0f268b6d1af170e4e09776b147a55e452a9d99345629d928a1643f3dc1d3774b74eb3f5fa2137cf6d6f1a37d0dcc886281819d1673fc20ff5b1375476dd4ad446cd1252527da5620c967f5da24e97eb9ff3286bdf7c837a34f9632a4751971384c249de72d466a555a10970d0d44521816588010c80d4546fc8ac01d0f1a196a00634c13d8e4b7fb68d565c3b98ef8e77a07da922a686425e3d222aa91dbdf7e27365a4b3726a44c68ace407ef5e383c896763cf02352cac120ecfd1ca25ddaa6c4ca12d80cc401d8c91142a3eda4213067037bca9d1ff3ced0d5f11072b1ca8856d448225b944fe1cde8c1c263283982a764d0eccdc12cd05569b757948bdd757340f55554c36277607e4dcb8dd59ecafb39ade9d6947b5108ef4e119fed659ecd08813226ba8527b9cc5cebf319801e292543f4f798c2fe35a00791983f549320317a62b5add143c94202097d92981957710ac019c0124c629df7c1e9a40429f82fd9132bd038695fc7c5abd39254b48ce3b9547f20a2a9597c17114b1ebcfa1155209d214efaf82931a998e16fafb4aa8352ed1e60cac1a05d4e92aed94fa382aad80646c7c7a5326df626b7cfd1218931e8cd364bd7905ad551f3f17726c6c49d721b336a5a66278e423261d1aff3506898d47185ef3bb9b52ebf188e789984c2b33822733787c9429b9157d60656b5b05ee17fc5cc8fe23d2dcc4130ad86551061555922477fc8cb2bbb07a96dee9511983b08fabc9f76b912c936151a6e0ce509ac43ed7ee68a0b993923669b89494915868cefa88ddfa324b3ef913d64349e2320e8b46db1f1973e6d31c67c4ce7c80bc2774d9ff8041020944d040a73da6d6e4e5faba832e117a2aab92771e0f26331544a4755fe346ad1ab1531966cc9a31f4bc5a17ff358da83db3d408bfed5e430f397286c68a7631a815b16f4883d8c5b96cd2988d9705dce71e341810bcf4706419af45a61413c674ce622440353b437a577de17cc0bf86c9b9e0cb531a317c76023c70828a4c2f46a528fb0f03cf972eb1f119955620524abb09661fb80e490853444d44cb138d9e140455c4359c2ab5481857b0c0a1786d861d9b88fc0d9071bbf503182a13e1710b54a9604a0065395e5270f1ff584355fe441fed4cbf0554d70a342c1ebd55399c21736448220d14a0adacb85c01f1c22d9cd6f67ba63cd8fd90898cb15e3e9ba161cf793cd7565790570e7cfdf8028b747405f70b8524e93d5a77bf17024e73a126f5bf73fc70c7740efb8d95b08ed1ddab4deb669c6b87a478ce7e023348e6fc69e6d94b1f2f380d4ba986dc6e2d3b2a411f5a5ce386326ea2730142fddfe39a9542274f56859e53033320f454eb83f93e83b6c65c91029c347eff2ff7fa1ad5fc57fa8d5c68f9148418eaee7a1f8e510d3b7388d2fb4079253bcf2b0e405fb164b827545818c917f6e2b5bec82094a5498f4b1932cc703850f10b47311be2592357aaad30e136c6104f04ba496028866e16fdd47ad35155d352fdec2de82cbd170469979f4972b9bec064ea0b7ad609f53f77faa6470e2c9171d3bdc8beb1e02e5957e4d5b54442e33039db0211731f4e127a2bb0d2b01ffabb8e4074b9ddc366caef438276c98075802a537af338191917112d11293dc4374fcc88f7fcebcd0d15ab7ee89419b46b257d41b82b5277fa2d3c6bcfd79ad4b00711718e6378dde9e74b3150116ec82db605b7749eb37ebb444e5c22a1dca860ab7671020c35ee6ceec02b0ed157fa077c78af6a5cb822f23708327fc4ceab2207773a9c8a00c5d6cdf5cf8e17acc6c3d1f5c5489f4764868a361485007272f3e04eb6e697e7596c4d7818bd2c848425ed9d8eb805dbf8a3d284c726a7f29d8396fbea5d0e1719b83f278757ff4b5c9ab62af06aa98408ef4147214391a368750c4ccfe535c49d9f7b467e6892c73fbae0a88f9049f9ad2e8bec9c4bdb005481d9e33b0ab5f7b602708789959c68d756e8558a5aa6726ee0e8bd80cb580f1c4f3c56925c4904df8a386de24bf20f8212602a765943b92632afc6499e0ef98fc109cb8c7993c04bc3c1b0de727fcd93f543a4d203ab66a02c48fe342c83941282144ff32bd8a37bbf820b9388baf20bc85a84ffa69999f3a5da383324df4d70ad5d8d9e1aa882eebe5c6ec7f0c2c80368c43cef28ee955af45e096595f32d1d9fd7b8ef9b3650d6cf78d5f6fed95fb766c2487aa9fa4beaa123551f46dbe097fc80baf7c7aad1b775b4708e2cbeda3f7c4c9527296a020fb2ee35d87adad7db8ef5d71eb59b2cda6f99caf54aca93617e67b7dea88ccf4e39b559d8887a30f6a6201c694b469b67d0733b95867f7925e1050380b39228ce2ab481ca4dc57e61b073a025eec9af3dd552a523d93d62366a5a3d22aace6b34ec0c1a30aed82d774d6d7fafb7611e7625937aff9603c61d481abb23fe717552767fa5c140fd60ba24690c6fbe545536621ea3b7941355538f9ae2f515b88f8badea943ceac4a32bdc61e9c0e5456a0815fee161fded1b93960a77738fd2c5765141ae0daacb61e64ba41ab2b2353debe95ae3016199fe31e7459d58a0aaceaa2eac03180af4410a7ca72910ef038b8645050ef2899046f7b883a4abf562c0de72ccc4b039a300e84337d2ca35f19ea6e6480ab7fbfd4aadbd08b4dc63ff59fa1cffe1f25247c91f8d03684548bcc4871d49b3045c4e55902cc0a6003ab544c01a227e6f6f9d4d03f3f3dcc83a7048f23717bdd42c06e6bbf45d63b6520ccc4dde8469bc4fc31e7fd03509b3166344fecabf7053a93b9ad3a8f653fffd630f48b39678e31e4906e64b6cea983503f32cc509d7c77225ad12dfb6da9cca6a0c273b28e3954e947ae4fb2a8d748496246f550e233656a5f77850781b14cf8c808b5f13a9689848aeee3e1073be76de0338f8d99048c849d07b0a121d950b62c3f6065252b4bf09d28fd3022ec8ccebe91eef4c134ea29bf56ede7f009928c1e7203e86776711158f109a6764e41be4d3a1db47fd38b00daeb0ffd3ea4ef13818603f9e2cd502052c87828f0cac4ad5c53210a8d1782b0149c02ab8889a2fd27faf7eb2e45b6c6504dadc28314237529abf511c16e7e03c2ccac8fda30000183d0c76e86b9067537012b0dd497c340796c91cf174600f509ef9e5576d9a0e75cefd60a47734ee1737b70f16a9e0fc9a0d48383e942ce86077f20386eecb4b090e994cb63be40290c938622a6ce01296eb5b5141b1802db1d1d9c81994bddd169ab66a96e3829a9a205252ca4ac34a1ebc4d2eb8b3054b4e1c1cb8192f321e0b8983dc0bdce0b79f51f992e5a178ac2175b7adb0db7959b158e9da96fee18dd11f77483fb061a2ad805d7910c2885253bf46eee7d9e89ecb77aa68f318cfcbf939b427a283222d1d09f4fdd55d737df2afbb21ba4fa2c8cbb97583102b9583bf7d47947cf1921c969d08e5504a6a566af2c90088ab775ebecaec27f95c31d003858268fbf0f4743b096f53a90ad6dd2c8cc003200206f26467ed9c6473a7ec7f75fa7c95b879184ab9554ae50c3730ae51ebb65b013ae564346bb2d17a514f4e06344dac4e877537d846a279a85b95337207dc91d7da493d1a1858a91f289e740bc4dc8ae5e661d3cd858d408c4d39c7f1808f11baf3d9c1092eb4b6a1bf4c31a2575b3080fe8e1405d057e2aa32de40ef1d2e9bcc73af4c09c5cca957d4e16692da478d1e8cf81d575a0d7fa458e5b4bd5ce90e8d32ad994e4c9e83949d907724a9ae6a49b83f6768b40cda6587d49c3d723cc349514a5cc14688cf5b726e7fde91c17d5f967e64833d38808e093234b94c856b7c7b8fe913700872db1088b731544e99aa5e8a1001267a7aa79ab83e04abd091225f5f556d580ced66328d8487309c651830ac17625a5ac316aad68b61f9ce80f934590963105f42f26030bef15af7a0ca97b532164c813051c50b0d466caa174015057c1f454b034b72182735f4dcdd4bb0b8fc4f036a0d3d84c95054c632040dd3ecd25acaffc174b7c7832c6c7cb6cd1642ef49f636d18fbe955fb4171a9fcdf712d4fe605578630f761d317f1d7bc45a6ea3733d30091fd7abaa187816f5543c6a9238a4beefcb2ceac957692d40d3463a14b33f0c88e5aa9e7ce54afe04357f3057b3ac21ecfbad8d83add624dc29ad2a1c19cb06c30743399705229422f0e179ab463b2525e0f4e8e2694ffc049eac3dabcf0727004b54bbe51b48ce65f29404095bb3e9b2615d7141830c03ff75444c96f6882d2895788e2fbc0e2b1714691bc81dece9e9ec2c17915f7542980ac93af90f232fe4253a3a31a7e2999b1f157ae057bec815277fcd0974e79762799b92cba1b561fb2452beed21887dc0872160564510b395ee1bd7baf6784ae09abbe1dc8856e0e6a6faf1b622051d5e06ef73715f02a66bf38c7f9c150fefe49e8416c5a881c6a1a0a6fa3201b3ed002501167ba5dd4aa5036a04965ff24b6c3409672a6bbd0c52611f918e8abcd7e095d3f6207f718f7435b6e68aea6b702e56773acd800fe9bfc75a7358dd71e170b330d8dc57bd5de59b460645a894d671b7c02810f89230eed22c53ec55722d1a7b3c6223511ccf4f073687851885afb2c0e632a4b9f19fd2d4ad5eb98546127b60fb928ccc96c7bc8da937e5d2b27fc59ae96f5020892ab21f9a328b74bd41b76b46ee9400e0bad8ce9e22f02419a7571ccc7e8970c990b9a22339ecb1cabb07c220941747ab92db0ad6fd94ad9021cb782e4a4f2943602afba9a062ea38dc0b98509f130969687e1d0c35c7e20bb170bd1c50d96e033d847b47b2e781c83f7a6e817cfc24b9d49e522c05d88174548364a7a8b626d2a5291111d9d6bcf82318bd146dde5f5bacadf691383194a5785d7c3a817979aab2a0ac64fec7b2a42db78b2f4be64e7503acfc022a5112755a305a594d07b561824d6484300d6e3945b531313f219390c7ac8a2e9c4a7c107841dac0c81e9d4ad6021a40715d1ba9c70b08b270ce1d951fc80b60822a975d4a6f72855c77fcf35698702df5982c59696d6fb9b7dc5bca94a40cd9061c078f071c4ce79cba9fdf7df73592961751449c193d7d17a2ef1fe5b6fb8535ebe8e8b88e89ea88602783392611414db6b16394f7e792e944bf5c3557b373a8904ee7cc0fe70cca7192f324d31c89e303be9ec7b4baa15c0da9446e11938dce914c75563c8d0f6990b21abc0d360b49d6a13f60cf33312dc9f4a5924cdf0582ff2693ebe8d07c3fb902baf765e73a39df393a71a67bfa3d8438f3813813845c4454e92022a6bca73f57128623912ee84f247209eef5e4ca07efaf77efd5481846c234ed741ffbfb139feeb3327da739dd8f0730dfbf9f11d41b6921514f1d07fcd08fd4f0a1dbd0d0e2519f5cdd0f04ff6bf856abd5ca517786f461f6288fc22272310aaecc8e4ffea15ca1e0930cd22045f9cc3f1ec08c4247ef0388fb189d8fc99c6167752ac67c7fde9fd787fbf3e30c799c780033f7dc6f8e44c2a01f7d80904c0d2645a6d67b4da60f6b98f8be87654763fee4fbf580c9378ff028cb5924c6d41fe511160d89f208774cba3cc29c8bcb236c7d88cd23bc016d798435255a1ee1da35358f70f624cb238cbdb03cc21487e6119e9226c65439c2de23c6d48fd13b78fe60624cfd893b8fb0ccf1bb61e21731a69fc338e4fe4b2995b363fa316c575a62df1110624209dddddd36bcbbbd5d067557277d9ff42411b224abfe6104fac1e38c5e7d4de63ae76b404ef487067b34100d4e6992928a9cb8f2312131ce1c9126c8734a18e4193de8871fa6d7225913c32d5876f48b99676af04787f68329a6a8c2bdb7e3d9d101c1ba819a5bcf767273b3cf757fddfdceadba05411004c1296aadb556aed3ecc6759add3a6dc3b175afcdadba05c10b821dc88153d46dbb95ebb6d7baaf1ac775d65a6badb5d6da6aabadb65afb2fda5a6badb5d676b6b3b6b39df7dd771fb55f8eed756cd677783ca8513256e3a8d3f94e7dbed79087e6b5d65a6b0e1d4e27d775a10be5ecbd893813d4a939bff325dc7b226e93dc5f5ae95bfa987d4a5fdbb6c7366bad95dda72347c5e10db91f87fdec73e8b09dfc7be556dd823c20088253d45a6badf5be8f51be38662e871c431cb2e538efc57decb8c37d0e9dd29e8391b9c72172dd731cc7711cc771cf711ee7719cc779de87a2f73eec1f1c2cb86a6badd6fb745c1cdcf7a9a3b1fd7d2b808d07dcdcb7ede59bcff0ed2d06757678e887a3caf9e9c8e1db4fb556eb6aad5354add66a3beb71bf7d787fc3713f6efbbb611d97fb728406c05173744a47a755ebd8414d6bd92aa65510044110d4b4da794efb536badd1d6fad569ebabc37285810de3c8a103020eb0e075d50e9e1d1d107cddd7656934bd72df71777777f71771069ba194526db3580dfbfd902a4ef36fec1984a918265b91524a29a594524a29a594eae8546a6e2f3f0729a594524a492da594d2b718f6f68b5af7d6067dac4883ad067da60ccb7e957e7393456ad5ac7dc7b66ddbb66ddbb66ddbb66ddbb66ddbb66ddbb66dc3366cdbb00d737777777777777777cbda3e3944a3c1f17abd5e2fcc6eaced0bbbef050782200882a317bde5eeb5f65aef1bc7ebe55d4b448c1922ba60d538ea6607fc5b25cb7258d2c8212f1c3126eb559cd96e72f63d449c8936cda2385e4042716855593fdbb6d9ed63d5b46f6eb6d6c97d92c67e72086bdb86d0e078bd5eafba4dec3bf349bd6a03645fb11032f3f099629007d479b17d5166a71a38d38f176062e78d7e0326cbef66292971651109c37f869147bab8619a73d2eab4b38c312d6bb860234ce83a39fb97968f6f85babbbbbbbbbbbbbbbbbbbbbbbbfb77c8fa43aba490a3b2971e8586e4cbf767f99537bd4a6ac5a794505ebe0d9d22bd7c994e792fef4b293bcebefc1e3ab5bdfc1b9dd25e7e0370b48ac5cbdfd1a969e3a59b6b21577d9fc90c08577d9fa4a9ff6534423973f97699df23f0c9a61c938c6027dbc955fcf213d76781ff2936c202fb0d6b6ff1f61cb6df75178fdec3f749d82b61944fc1a55f81535e05af781356f9156c7a16bcf28e59fe84fd5b30965f3e46bb6ecb87a6fc583a6954f6f2d2e52c3e9bdff285a6ec94f517d6137e7999594a4adcfa2d99cb209fb2efa14eb560995db00f204e2e2e2e2d3cb87c7f8c6e69f9eec982f24bbf248fab7ec8864ed805b7e0da60c534b7e019fd272c9d3438d460f62c583e6930fb159cbd297b15dc2b70a7e0a629e146c1ddea220d66efe1ae699bbee923b85d8d9365afe1d6c1ada477702f6914930633ed3d67566efed43e9459ab1b0fad524abf8d7067df12156ed8af9cbd6c1f2972f6dd43bdb341bffbd5d3a9c803a532baeb98ba5485cddeee1055269e1d395a45fa7803cf4ef623013987185101314565b60344183d73dfb48d7d1bb1ed6b915cbfabcf89b66db3b3c3f699368848d83e2bf725f3e65fa1efd038cb069896b0e44c1c04df64dabef0d60fa6729b8eae06c7cd69c3e7ba5ef1990fcd9cbd672188f201a24ff41bce3e91e37003abec1fc3379c891e7f2602c84aa7868615e40343ac648000888c0c8735339165894992213ad9234f8e3d34182900a66812d4a4495093264d82b290852ac4295b147f8831f4dbb9298bccd8a2f8bd28ec40a6314754a1c4b47049f11cba389f24c2b26249c2ede2baae5be29efb8cbc473485cb43491fca27f2b62397489e381379e0615f0899453fbfadd0548830ee8f0769154ace7d52aded6c58120945341467264da7ba6fd18f9abbb7530a71e6c7cd508830fce5ce8d33ddf53e4512e1ceb75d4d6002089ec5d28e76481e499483cb3591c961fb70c10240729783d71361bbbc5d3345763ad04cc6755d7e3d1957750e228afe0b8a27789ad819824e0f32fdf005126baa58e676b5cbb3f7b4eb469d8deebdeb3acf1c963fc418bc6362deed58d1fd567e54fd459ffdee8e4b2b64fa8db18f9e7f3f5cf47d4cee435371b436db142ffa8eef4c9a9c77996e2c9e61df7f4a24c826a20bef67a72687822590a3845c38f64826bd37b3444213d1055dd2bd8388a2df3ddd8476820920d8969cae062a8b5c37943b9e0dd9a1a1ebd2447043dff1913b3baf9d1d70c7df77620cfd13c7b98ee7b8ca8b348abecd914c713275154417f43bec173b91fe69201674132884bc0f610a43a6dfa28e06cda2af7da2dc19e2feead4ccf43583fc1393931bd43e5902d126d9b3e1bdcf9fc883f7cd06bdfaa2fe85b2d5eda06fb1cc1c96d9c33b1aac14a08142c84e18b8205b31867ab659d716851ec8f6eb9f18e323ba027d8f22aae490a7e5d05f71a67bd24aee9a307b7f39e43df5a8e7bd1d6a247eccecbd8f51f6bef8223cf3f4af0d7acfa37a1f8f9e4f2be2d23f798de36a57833b35f5e6ba4c39bfd32e97cbd5aeedaf4321a2e873eeba54a859b85498c890a90d39f41714fef29e18237aad882bfa52d977b24f7a7ab21f0f6026799fbf1acca1382e222cc8f445520599facb553ba24f75729f98827cf479c9f658a2221d0dd2ef2c95c499eda3a17d42c85c7f7ce6dca109ab4c672f7979df8e60ccddbdc6dddddddddddddddddddddddde58bd62e2d27961593ca8a94120ac9bb2351c7d9adbedb983eb1173cbfe26cc346589c308b0b6ef9c62eafe1e63a1a58adb789340d1a57b178fadd93b3ed9c86e78d74f2f2a1a4c21fdbd9f10184ed6cbcbcffd6f1f0f2fef2aebd68dacb238dd348329ded443a555ff06c916c17652f98e623ae06e9bb60d982e5094b2458ae9854566099822513f96ab047fa0005093979d2ab0ed2a821f2060b4de3347547833f7cd7962c3944deb05c3407f9a653d1156762a6dfac4d436fea777fed9ad59a615a966935c37cc5d22b60d0a40a4daa40a5fc62d34929362746a51238b8fd56fa7c14a62762133844954c0dcfa10758937377daafd852c8f44399a8ea1c478de026bbc83189085eb672d575262bce20a2277d44f9c8f2f1f4b1e523cc47978f29161273a9b54e592631e9524a293bc7b14cabd734c36575ea53b5788a65453aa54d1b6e7b2bf8e8e894c8fecb1db904c645da68ad1bcad50cdfafbcf6cdfa4d2997c499154ec49992cb9d4cbdbe0cd252ae56335b7d9eca9bde34f10c4fefd3efe9b792ee25d953ad7bead33fb663667134a516f5e69c7168cef0f7fbf9a54eadbca7b0d746cf7d0e3114d029949fbf43a7587e7e0f9d3afdfc1b9d6a81f9976ff1f377740aff9c1eed6c4c6f4e6f7a2b5779adf79a4ccfc22edf02778ea32876f9160cf327dcf22cf8f42898e54918e52d26bd08dbdfb0e839bcfd68f41aee3ec31af62bd82ffe2a9ee14db87e0bdc481ad52c9347f30b0b6ff1317a051b69f1cd72955c61d887b2fff0ca68f49bc5a22761fb9505a3fc09b3fc0cb87e0b3e3d0c6ef916d8851cf53dc67ff1ca0b66f133e0fb2ef8e561b0cb7fd85b50ef8786f9e6cff0857762bc83057ec12eb805df6870fe09f7d0e07c16bc4383f3513009c7c801bfb841c60610a6c139e705b114253af576cec58d64ae4c2a4f7fb2e2cc8aa73f85c499d2d397b28848444c995e4029bd2feff4e6f42ae679d8f4aa099f3c157cf27e3ef6d6c3278fbeef34a8644983dd7bdd7b45b09ba01aef7126ac253ac4acc92072e53df65809fb0002c33a1fbcefba1ade7772a57d9e4492d33d8833ded3ea79dfbd8647de63d87b6f9bf3bcc7de5927efb527c1fd31b5cf913062aed3867c2490e1267b3922810c44b2f73327cfef46d0baefdd9f4282324c8e49843cc9b672aea45560d04f0fea4367b204f46aa0018579dafb0022736f3d8cfd08cb2092e57d9ce179dff4f0c91be193e73d9847df7923efeb8af92bb0cc29388859d2de753cef3dcff3b0d14f67352884fcce87386b7af75226266751671920aa643c4064c24b6573b5f2cffbee57430ee5ca769cc80a37eac8fbf465901513fa31d207c2cd9187c9a15cb532a593e67e8c992b99679cd1a20e3ee23ac76bed1c4dc3220d771ae6346c35bc6958d370d570a6614cc354d37a68325bd20d3ddf84e804866559668018dfeb6858140e05c5c6997dee3599fe419036927f99785ad5ac9fab0dca7cc38b194ebf99698e3195319e574215fe09cec2064b08622d1124854f17a8f00206184eb860c40b45a67015f1f372a1228ec1880e7810e40b31306204533c13406090052ce0dc60080a1c90610a3c5450a208424354e002142c21850b6292259a50e288248a900212f844f1391940c10c7a3084263c31849e9682cb0b5e2f3a28c2850c404e30051dd0c0c6064a3042838ec213714827db11d7a91f33f3781d996e01120001105860851218010a43c8f04fc651920439464b28e4100432640b6078094902178c90420c839024987821e182127064e8e418ed0109a4bb5bc0220a2ea088214884fc771510535ce108125960020a353232382d542419c204257c694812920cd989a28d88d1aab90221b050f23f3865734e209ce03486960d5e77e698a4a5c4ea6e392669bd2087375fac75840854629d2e206889a00b77340624b4604449e772536031c4e59cd0d2e4de22b4f45c151c548144122c7a902881cb1297cb31091244bcfc5c9b6392140c3591022c244981cf08b63ce79c1fde92c9a507979484962e5c941f38b92b49bc80e0d61c93a460052d65b8a51c93a420042c4e70bb30bcbce0764153b82b9ce042849b92639223861074bb1c931c8193a74d72840bb22cc724472071841239bcaf2db1f5bb7ef5639e3feb739d0a658a47f6f5fbb152dddddd3f1f188b080cd73f7eecaa0f99b3b7d9176b267fe6fad9c7f02ab3cf07f619919fefc04698f073f330823df7337733e6c434e8dbb79c7dad6aef63abdf8f953cfa70bea66ddac87eccddcbdf5eb3cfd99aeb5bcad5ecbac21867e69d281f027165ee7ef4deda683b9c59f30d7bae9d6bdbdfb62fce689bf623202edcbd0ffa2106f2fc1867b6efc78a86678d9a7dd0b7000fedeff3d03e763d00999f8f52fd7cd0afdf7dd57ee29a4bdf771fa32bd63e1fdb67a444834b7af9140577df03882bdf27e1fb318b3e763ac8558e31667ef7230cc495bbb72c34f0e062df7d5833f65de54090588510a4b005308c810c65a84246e989903258990c10009151fa4efb1a58b5d1d5c82ae55429d8a7e02036e0e1bfcddffc354cc30feef6b28a1c6a6fb32aaaa8220cb97e7735b4af5fb87d613552faee8cb05e46e9f340e4916183538d996d7cc08d3a5678e5b00aaf961f130881e40a645908f721678d9ae777e2a1fefcea03c5be707ea4e6766064c3704b6945054e0ce34fa6df12b336d787a802e30e3562ca45a68fd11d0210552fc20264fa5e0c6e1877462db8a109fb4e35ec57dd3b4e33b255dcb17b871e9e74993e065c6a1ae02428d31fa04c5d5408c9b60b4dc8617c92a9c4c217981135b9730f4ab0e97cc08ca8c9f5adec6cc46fef683c86e18c1a617d9461c3097b99b10fa4822e0141142c433726194293b150084cd2549d2bccb732ff200872f50747cd380459079025488425bcb27c2b7110334fda8006a7cbc498b923c6ccd711744399f940f4e0f1428108a345ba983fff07937f3271c61bb4b165f67cc9f3693022a40862e652839811ae94373488bd8d7d630c06fec5ae0dd7c11d31067b1d3f37bc197b9f4ef5632f8960cfa3070857c57014f6d8f7a040849143bac01efb1fe24c7d0c08c3de562c63b82a072acbc776b2c4c218858c6552568e0537ce78cd14838d302195f2023a9b4e12b363ec8ff3d24db39bf6b560c921e1f6abac670f2257d837eeace14a893438e3023a9b4e129b0e6dd55a737b602e4aa441ff6891069da6417f5a436de80d116e486926b5697db4a6c129dc90baa88be2a09ae62929eb54f3fc9c3853b956d97728c26a93aba4d9ca9f78ea6444b0496c3a32033c214bcd8df3e9377129c62faa2a96d5ef363e0257cad3d664c7345bc9d520db961e7b8a56624c471d2770b2632dcb6e8629c6f47b4bdcd913095094c0e6091b1bfada6fefdbfb97c195fd6643cc9aa669efe3dafb5777b42ac6a0eceef4eb6cd04fd35cf35595186894fcec83695022c9e92091e3907d00d91f8c33314465ff8b4758d4841bfa4de637aeea55b5379ab05fad225dc50fe673e430c52fbcdb17a3ca6f3e9806fd2b35b83dfd5ae86ba006fde94a6adf568a1ccd9d2f6ab555963f7d629e3f930a71a67bf91df631c27058bb6fee70d84eb43c4bf0673b5510637676e8b7b3246b79bda66d6bb52b6947e23859ced77c899870e37c89fec655a2cfa54b972e258fc9d3a05cd2e04e6b67a509b9d56ffc6689a9e5969cf3230d063125922cbf672bc6481efdea14edf1e994e5e1bf752ad22171067bf9d48838c3bd7ceb3e4699fb88c3ee0be564f9e0bf7138f49fac06e596656f2dfeace12024d68a342869f01cf29906d1399c2d15c4991548155d515612e1a4c1d4018d5ead5c35a38828f95264b96ac28590b6e62bc6c817c5e0fac71cd252b6e954385979156782206990a5936ffce4eef22334b3358b6cff30a6965bda8edc38938731b594b8a006720feb267fae82c8ac35e4f2252804149a6ece4fafb6cd6967836edbcbbc7d3cfc94fdf6356fb2c877ca36993b88dce42667e86890fa2a87d3b36ffbec66d4669f06c618ff0674ea54a3e6fe1e59fee179fb6e1cb28a3ca375cf06ee7b258b341844489c914c34abc3df60ec3e6c189d235bf2a59238635fd66b7ad06fb60fab8c9b94b2255d484e6e3eda6e3411a2e7001019a2a738042261f8105d6c2f9590b7e7beb02dee01882bf3f8e1d98806441fb3df089fe1207470c978850e464a3a159a4639b58e72b81c97ac3971fb5d5ae84d9c41f12f7d2f11c14e6d2e911572c3e5d8124e79894bbfb93a05b32d11533544b0371f44315821299b0f72db0fad90949c18c37dc8e5e4ac008a31fd304c6ef7a39c58038c941f06400c2ba9847465f985f729e90b691052846f463941c8d087d87038733044ae5c0eed4159d6e42a7aa4513892de90726e483b2552ce6db05fa5242d0a5ebc7871a301386ef0b4a63d99462c10aeb21fe8f409fd7e1ca24a0e1151fdadbdc82fe24c47b53fc917b1722862e50c032b9fd99d2d262fdc1bf3e795a11fde6c0a7f44b1cf7e843bcc590ccb80495cec79c8ac90b1ffe42ac6487123cd366a3894a138bca1f5903162df8656d9c70d6854d70cbaaacae8ecbb21699ec8dcd0a0d47aa362d42c3fb8ae92328c682f9f5c05b44afbfe1846b4efa6d1651007178b021014bc906d733668dfedcd0120b48fedf5b0c40906786e34cfb5bfddc83cfc7b58bcf917cacca0c175bff6909961c3fcae70dfe77b72c35f61444e50b80f06223952912311a8409263d79fd0951ad88255811d8d4e70e3c79ef17bd54de4863753ca5dac66dd0cfbb6d6aa6933e6db6ddbacc5388ee3baae13c518e37d3b1a89de5e6eeb2a0a6ef4194914cbaab691628c315ad98946d75b1169326f58664b39ca5d6e84822bdf7a9d0f2b049f2ab2109107538e3a36324588fc901c3f08a54e727c1ac4f2f12910508eab56503ebeb75c65faf85ee32a95ef3144b0df0431d543434c1ee43e59c5efa056f9cae3f7ab7bdaa77f1ac87952be4479dc67461f7f6e1f715ce13865c4711b674b5ce51c933afa7384fa903eacde8757241377b695a3344d037ac5b8c2619a8625e6b84945482d8da5caecd8b74e3121d934d8cf22b72802c85577283707544b5886528c1b718e67a63437cb34addb31cd26ccf1a8b0fc0e4d83445a5c11ae099bf61a6f8d3ebc475837c5c7513f8dda3eea9355bb559cd1d7dd2ffb5557353dc9b1cd225c0a2a0939197a4243d221b51c95e3aab1211da9285cc5259fd24f29a8245472d2a87e22de22b5482ed20da9452a426a71399680504cda86659e72247346e11c77d5d53220d89476c591c156188bfa502021934f8ce1388ee37ea440d2931bd45e8e1a12f2f9096a31e59a51cd6edd9fa89bc1bda8d3415652cda66123286f9b4b4991f9c4391e65d3abc6e6864681f326b72f2f2fdcbd19cfd8302c894d67c6c739f6b2c55c4d83cdbde0383087da4bd32a90f6d25ed5a7de3478c4859313a44a112b123a4485a88f467d546cff0cedbfd361661f981b9de086d4470753108e662a35d856495ffffc88beae3f6efbb81b56901c521c0dd32354892cbf6b10aa24911dd582738cb2826dc0802883a56a4444505bb5d66a692d951a96a3fa45b8e2744e45527550dc8a5cbfea6ab9aa2a31cd4f6b612c3a84ad2810a63e1a90e6c372150dea2ae42a4dc851ab4e695144908898d27a22d84e72bf6bac97d0c532773474982866540002d0cda0cff2846b8300b88ab91a9b23987335d8388deaf7d5ca949538cd0ba56c587bf1b88a2725673368017bed05a384ce06630c4b8ccb1883f2c546316995e994ad0bce314bdeb01194a7d8080b0be569045c47c9708e5732c546507e7ed5515225db2cc338529106358da5e6621fc34935da8b08439dd230ee06ce71295b11477f465c0b1ca4749d0e356b9ff68a31fd2d72704352abe52a4fc851fdde505d5d96894413d44141721051476385eb86a416a9857d6165c134c751f2517074ab4221a5acc0811b99e08e4e60eab04fa586bef68a33dc8a13951aeda552a3bd546ab4974a8dca8a904a0d07f4d32a144c6b39aabfa6e7e6c71594e34427f7effc683c3548a58606692d577147381cad8643a2b538259a8b5ba2b538269a0ed7a3ed703f1a4f86b557832cdc4dcd4edd8cecd35e5935719b660bc089b0cc26b82d1df6915a284fb2edfbb597d6d32a949b23ad52a969951a9ccb2131714a96ece47efb851c93dc3d2a2fee47fba9715529a851fd252737a5273748ce10951a222a3b294578728a4dee0f555e4a7248b279e968a864ec2b01b5f0011630fa9afd56c9289854a451fd2cba19da47825172c31ed485d16cbf5b32e1c03916658a8d9c7e9e9e3269d58aef3f61951a5c7d70ad71f24286fa501f134e3d52937d56301542c174c8843915ccf938aaa90f10c71363fa2be65acd15e16a389b3168dc13d157eee4834c3fab55ff0aacbdb056c21a90a3fabfa361cadcc7dda03c0a1948addc1c50de380e0bda0a729308436b4917402a36ad42f97e951a14acf534aa7f866e06fdb4d78a19047142aeea1367705ac51d69547f4fcfcf4f50909327fd2c3a1a2979d67c5be63559727fb1d66a942cbfc581dd8cfa60a7c3ccdad3d0e99092edc7dd68e1366d01e57e0b76353a6b8fe12c6bf437dccad2d4aa18abd4b4cd520506e758b339708e6bc6f03c00e738cb58e6629c69b92311a9e3eca6b108c04502bca52733000214e00603b4885834205e5ba0062b2bb8e1c7f0c5a458a0eeee94624e29362526f3a4ee0f4e0c9b946219c3308c62189d18c5e8779ade848479cf5ccbff9429dacdc0e494fe4e1b463e58eada322dff206dfa8539487fe18fe1172f029b06a8940c179645ec10d10c00000000e314000030140885c462d17028d01355b40f14800d889a4670549c89b324c8611043c618420c20000000040000044686030038e9a79673ed379b84bc61c0f8b408651b0301048105e2e98fa46fb35fcd229a6cbf572c0a942f0da41d82a02b8b977dcc6c4ac665a0a0e342f4710835f1f6feb1deba0cd1e7c4d7333ab9bfe4da659599425615583da3d297d0107859f3ccdd33462f63a43f920fd59a871dc228963b67cef06ca60882e5f6438e09d0f218d1b6cbaa7a6ddfe176a8ea382cb91ac6332768c9675ed693dca76d2bac85f40f5e8d152609abda3c646e999cd5cd55a64bb39a10f04d987477af93c5c284e0b13a0149f01ea33431ecd1c72e3b2280396de6f2d4fbc8885fd4da71ceec13c560a4c9a793ea70875cf32011787fe8c41b6dec1343498d047307b7654908202d515e2349474bc9056e7568a3a51e3b036a9875ce2370b4447f4daaa7f0cf9a1068224e0b86a117eced5ab3611f76512ddb5fb1a05df81e55529b3e97804b889f58565d024939ee624b3a0854c2c1d533984f4914e020983a0bd4b83824b5b0019c90cccbd950a330e4b9b35fef45b5dd658add3796b73d1a732cb04fddfc76a541bb1254f162a2b90cdcbe05bfefda9b156ff5da35cd4e5186f8f7d9f10ccb2ea7fc205c055405b3291d38f6889a7e759426098a62d6dc8618f1b63c9e863fd660513d9e2b1cee7d43ba6c37d583b195435a1270ff4f390fea69d08f1a2eb599e746fc260f6701e6d5813279d40fed90a916cbd941b5904d1fd170b1378464b1fc955417cc61392b32bf5337d6281f66c1c99a052eac6794c26b600e536072eec653441ed7da7af221fb58b11c27c0be130d21128eb1eb11128fb3bbba8a7536ca879a81a5975ffb2fe59929d80c2df8c21dbda28fa47c5348493d53fa7b896a187fd937a820d2128d7a165143d03d3a294bf5bafce7539f0237074e224d4925a182a4f745212123d39789f6d3f3bde6a714bea6c22a2c5d4d3845de8d0831e1b89a3ca345730b2ed050a2c162282a269dd511d0342791f0d531c6a74addcad7c82739a81b132d2f0963e270b905cb0b132526932929a72e73811ff03469e4400ec988887902bb89d6c29b6cd228542d7fca18519000373d7a291e166e235fcc8e45295500b485cdd404b49f344cd701fe74e7711ad172dca022c78201f8cbf03c42894aa3d2d55e53692f3fa7fd316916060c912901675771bfab87b39e87e2c298ba4690b1e8de03544630a195135afee58208fd53a540a132614277802b0bb1d7aec791efd0ca9c7881c17096c68ef1b18445d64b92c4e8d332b580b9d2ba0c9c5e9f002a21a35c9209c5ca8458dca0222c0c5221bbe043a598dc438f594086352c6a19890314485ce13ded7382c36fc7fc4f7702a32d45a0056999e254f0bf11f2328fcdefea256a90b7d60f99debfa9663036bbedfea862cf523096dec6c30ba28ac3c7c942ac1c1f82e1530e7bdbc3cf9801f799a358afb10c740ef33d6cd90e2c7772d6e394a54d7814fba2c505ed1bffb1721eb2cd2c914e517202b109ac44062327886b5c1a0c971069092b55c3e06e13fd8458da4b65a1db8c927f3bafa29e3313cfbe2a3c065e4ab87fa6a7336058301b20544af9412945765ac08278aeb85ca165b401df0a11f650ead212e88a2b342c461307364246fdef3c557f689640e03a95c81d05044f28a04610d2ef654a083ddd12f43f835669703d68b511a0b99bcfbf0a3d032484096d8a584d104fa414eea72bf143191d1b46985a5db038da83fd3d26261b170605dbb95abe96950338558065bc983d4fcec77e934cbd09f098eeab1ac62fa56dd826eebf0b8b56859d6643fcaa88ea6b93caf52dfb1131a1443b610682dd5206715ae576f3b7c88c8cfbf9bc973dbb564b4ee534225e7321023e9b8b2a74d510d29eb565c2ac483d5c552dab413357add755d40a36e75512213c9ff6aee02bda61df985ea212a0e747299cf2a3ac76d596ba34bcf87971f17903b992015f573100c3d10ccaa733ebd22ecfcecef788d39a9367cbd9e44efec68272f2ca0ef2320c6f6ae1b97c4128b77c88520df9380d9d8f6b9a2966f4c73ab62719bcac06e55ce8bb8ceba8c38ce6fa5e93cf67a6fdea23e4c9a159c0a0257955599eae22516963a9c977efe99a6c6bde4656c218ebd72c83dfa1b5e0a50e9bda1297d6db5aab2bee449dc99476d88627b9e64e509d9813aa8b31558188ca75d58fcaa8a49f46e3167825a2c5f5ce48f638230172aa44b7e00e89f2427810c8db3752b9e49cc5bdad227d5f2a225475851be063542f042688dd487e3d2d51535fdb41d1368f41e74b8fc6974ab3282461329d46cf01291f49932c63fa9b970780cc41dd47bac971b775182385226e146c621c3e8dbcff37582e2aeb37854bfe93fe06a1ea8208096c76b56b03c5da15ab96e7c60f7ea3dfbfd50b5853fdcc5a726ca7aabe5c056ffd35768a731ea1068f9d9dc68125edae6c24a6cc0c2f0a7d863dd7e913da23d90174580ec89f00e9f668dd8bffca37c9372eeb859140f03e8145e2ab0f9039489e3c0450e424c078c793e621fab94c3e58288d954bf0362ef8979c2406d6a07cbc3b6f75c9192cbc21cffc4ef11328574256e1876967a083a1c5c60dafc55c1fd24ce861084f694d6e8ac78306f5e33fc0658077197ed76aaa25414911acc8119b4d23cca209840aba63294b4c5e2974ac5442394ed353d52ea13ccd3267bd681503782f47893250100d5837d8009604fbcaecf51a7c3c47fbe369c0c2bc7050b78c3807e8007aa889876b359e5b3c0cc69da72900980321d4ba2f231e7fcf5cb321870d04dde470697eb08c4233480197bee084ec70175aaf0de6bccf7b0e0c3147b2f81c1a96935c98c5e48811c2680a163a5f2b25b7abdc4acf8986b6db134b9d670a02bcfe203134cdfb3d361032b1930f999aa3fb762c46bcafaab95b50e1775cc5eb0dd54dcb60e13406fcfe0b7e062721a9441e503ae197fa3c1262ce751560e89f4d6f37789c90429101cc4c772ba1b563585d95ced93e9c10e04c06de5df2947b1c2d09d3f659337390d111977ff970d801d56cc515f22641651433330854bf36b65b4036a2005acd18aa348094cace169cf9a5d951282595fcab4bd59b2b08d232b6999fb5ca2d7be494fd1d04baddd76a33438fbc31c58c5056c6bc2b41faa7d262a28b615c606812edb3c215f4b56614143ef3ca195d2075678495eee6a50243055e7a20a417efa88cd9b4858e2068974b40824c910db676f83e9bd20f399a89444b1fd8d899a88440894c641ed64eeb8d564044107e6622c02ab17287dcf4a621840ae0f101108635413e3af4e24960a50560acd8af01515dd76ba3042c8c9bb2cd67161a9e88b7ddb227a693202ba0581ab2ec30df3812de1dcceecb9a0fb569a672960ba4305b10b4ceb0fc95b74f93a4f4b0b0c2b7420ddd96740cb311375f59ccd56e1d42249849b06cbe91267c18d4fce1b8ef65df413b8749a0f31a977266eeef9209316c5d6ec8904069421894a9e8ccb61acb04eca69d15cc64f8416633aefcb81a591eaeb1da932cfc057bd9022023144364d10f50a2c917a8df03ab7223d9a0e615d76f9d1d5059579dcf9f747009d64d4003985349a080f0fcf03fad20508c8b721e32cf33220aed002742b112341434d3e8551d594f9ed8c38b96df4daa0692a39e64d9b6c038c14e932ab1595a73ee8bcee998481d890c267bd160ac5d0ff3fb1306e7c5ba538dfbb12f462ad4bc8ad0cf8027f49cc5455fd1a96837eb773c0ea128d80c01689a32096fc546a4c0823d05320cd9b1a6ead069161583287c0cce3f21a24f72493036b4af4b979609d5040d7a9990e82d306cd06e3ae61a8ec7f60eca1f3412bbfbf9a73d8c487e06af8f0a6b2379cd9c25f7e57957132fde60ca00081ebd4f5d22971bd8b3c88cb90e09e03af136c24b91a5ac2fc5551b9f449897f0db084a023ea4f9847a90eb364d9739843643eb0604047b4ce949c14b0e909b026257ec9c485534fcf8b159a4876c143b50f7a2da2d45aa2bf2740163a60ac1a537bc8c84161007c35c0265da97029e165f319120b79a8df3e0134358f2e7d32e38e622df0dc0c0c1d26ec6536d9b5da9630916e59c91d37b671926c5f4f8238eeb1df7df67bcf1eb7100d0fff3ff4820f2424e4314a55e5130924f05f43dd9c074ce20b5520e6a53092b4423ed69d1ff9c4cd402d8ca4d7a9a481b3a501cfbb60ea9e9ac13ba32fae05b85861de080459c567d6733593f1035667c57325f4000cad5425e33338086d66083e62fd40c693bae982ef8fa7e2eef627e028156860ab24b169586a86be28a4fc2a899793bb474e2cadfcea584d379e8cd0ca46785184caaf2c65c6884ef91a7ceefd9e4fd1e207923b0267ab322e00d48373d4a02fe287cc53f2710c799ffe94ded9ad0eb26eabd60e8eea5e898610b7e087dee323415a864496b110b3c26bb05fbc755e28a2b62fe178381c224e20479982ad153022664e67e6447591896c86f07f784a03cb5c223c4a8117d99401f4e07cb5f08f5526a20b8c1230b2c534199d0ac1ab3285e12c2a9f425abcee01229ab1c4487b6bfd90b8b9f381241e449a0e29f6d0dd9343b5979b7d7a36d2ec5465559e53fac0a0247258f4c5603d0f3ba22e470b7dd19382767895aa1b40519030c53017247999bfb6c0776b534d1fb864c23938ef2ed57b1b57d43080c0991c39d406de262908cd7c966cba1b9eb56d9415d8f3cfbf2e975ac96b5ee1471129cb02d24635b5b8a86a95506e30cd1ef853a823ca5ac570d77c240dcfc09b29290244e097e3feaaf17f970fe873200447c821e554f5e508ea5712b7c526610f56bf63038d9a6a3be7d3ae7e828155c1a87fcb4878453f1ca54206d32950323b62daf67f9cd4de9366256d34abab7d7f35f03def1c4e11b8ce51bd25b24c7ca8dc6ffecc9e8444f44a763eb6d6e1d7332744e236f4183eec4a7c06b3d1ecb724bfedb96f028fa979783991acf093640ac03b2ee392b8bb753681e360a1aef6a3c5232365075836c2ee451d5bea3b5b2ca385d4306b9f165326769af274db0e03a8d2d5e0538596cd3d25f5ff5d8e69c2777259a8e279fe0d86b5b03e8452e2b944d6bde7eaa1d5927a983e26e1b409fe1e95a4be5c699e046deba86732174c6200977f7e119bf37bcbdbdd17bb7739ea213b87f68e29c6164961b471b05434ff2cbc2b2c28c1825a7c15734bfb61461e0fb6e6c6e04a8de4d4634cfde01f4ba7060643a443303fe90376cbaa7a13144990c2bced8d1992552946e17abea5f5706675ccc847a218c20d472178ab8e11bb89430ca640a62ffff536cc103abac2d3d93d1b499cfdd9a39056e094e674f4261c3fdf8719b8af0dae8a2808376fa9c750d7bd9e473c0f7ca88342b05d558eafeb55fdee40664ab8bf7e87f54b5d085c35a4ac367072da6d8a7778187420fe2d5f0a60fdaed08b13a5923ff2a7583bb23d41b49c92f85285259ba1c8ed77ea3a2a64faba1d5aeac2850bbc2a66a75ccff0a90746515e958c3d17bf364461f910f4821b2d1943a7dba9268ea8c973070a752aad3c49972d18e8477703de759687bbcc4805dc4f17efb6308420035c6a45aa5d50a84e84ec5e43db1af37b6b98970c19e5eaaedd5804cf6e8b0bbe3942e4827c9490d640d37b1ecaaaefbbba41b4ebce550c12e5986dab280798d4733c5fcc480694e7a201d3f4651f841091891a501bb1f2248f49ababb1e9a5c8b832d0153348f08d527b3181a3e98397211b9a33e8815906f8864704c951ea8c9ab105eb7748d193c6d12feb788a291b5493c8aaed6bc22fc346ea7aaee8c7c042a23458ca590782fe8e21c20ce1f6028cfb7999158411954cf7c65948fa71ca7a12876b10b44200ecfbe99045f8878599cc31f48627aa48d0e990329bf03abb867f6e2558560ec5ab8e03e8638b7067dbe5f01dd2a9fb6dc613ab878630ff2b21f5dfa98873c7c49728570e4397d56a445666659e002208014f13618f7f896e862bbb8a4a24cb0a6ed4d6e0ec4c1642fa0fd4ea0019d4f84931b26e7a9c32a668aa592091168477d3cc51417050c82f78d5954ba0ec5a3597f401820f6ee108b44fa15dc8c175b946f4723dc508511247af9d064c7b95dc43afab6109a2aec207b0ba1636e86d00389be2143f6ae443ab1e221217ec460971a3db63ce4ceea2053efa67b6e3eeedd416b0951b16deedec2f8935e81dc130d95cb8aec4d2ca9371d9d26cb1054c657662c4bf5d0706effa6e6047247a18107912370ac7320214c3e9cd479ef20b1fb566c692a021a855a400ffa6d0e4fa96622d6e1f993fa46860fc33deee2dbb14ef348a3e0e623e928b5456a057374234bc9c556df72f0edf7ca668f49a2b8e362bd9a5b95699e02e99be3f47b305e50d58371a4e72153d7225cfc77b959725f50b457eef4f56cd787a038c319430d8cecf9a003bd7d1784936a8516e08a4d01b567f65c7d7ba0a11123eff6a0035ddfce0efb92ef934619f9ffcbb135c0b703ea803772b3f382cae9578e6ede8ffef490a3f11e2db3b1cdbbfbf5b832e88b26a0e802b24f9f4729b209ec50fed01b70c0bfb9a70b30cafc3acd3f9a2eb524de4ac89621285584e0fc74cc7c775968dfcabc579b1888111d8b7962eff9d7bb4fd0122da3440749ac5913715773d6c51791fa06eb32ec5f3ac2146b67882ce28d66c90dfc1e0660da61821c07bd0d43dddd8ec6868c52df5856e5e36e0c82c4a1f126b02154b525d79bdce34fe453e90a73a111ad12fce58d15cb9598aa494eb022e5e170e267c4c978f0ec6e677a3e63fab0e10d2fb974b6cc0714029af82e7094c67cb7b76ebf48f0d6eaa09c457872a475beb92f0279a6f37d7a8baf90ea9f1557e8feaa4d2c9ec8fa28f958ddd1fdd331f69983ecd08ffec8bb263193d4e49d714e63d0bae76a52671964683d1b90c5d50087cd61437c8da518ea3fcb1d801533e52b90cad6169f461e075f337012a29ab3503725bbf69ca166e8ec5d9520bb97ceb229408cd932ea78e12be6db28d77602667ba8c45721d465dd82f9ba62f9078f0f56656a0bb3b42d794dfed90d9d4777f627f9a36fa8fa627182fb4096182a96ea5127ef9b6c892e1ef7dfecfe336dd1d589620019770918896b6f6bdcddb27c825dc0cc283a38c4cdd3abf26ae92a6088080d5a941558e9adf36ef5dd3616685c564e79892204b8f6a69c7593c221b6425f4d4a2090edabf0c1b8715ed9216f81b3598eb71fdf94597ce20e61030cd92198c3eefdcf44a2e814d09de2f37543c8a48e70d511b37f13cdefc27206a51b91c180575ed07ccdd2a2442a94624af340010431a56b20e9ea867d51ec32abcbf4289a09f548fb81c1fd0717aa13935573b9e53e70eee9c83c0a371ec359dd4bd69c6090c0abb4ee3224f53305d07a228c8b16fd429083d881b8a6166db5aeac48c5b5c268cfeb0e1a287136ec2b0dfa4855bd5406eca60ce627c0316a6bf327367da6f4e76d97da9fc5f37ba70def7ebab780364d0c776dcc6326a2003b4dbed9aba8220cdb5f4818e020de7b57bde3366de6ac174c8e15671de21fe2adabd76a1075663d495c43fc59042aa5034865ac1e9e3533106b6eccf8953f3b57d84906af652e2ee05b5c867028d7d660042789fbb96eaf035ef11a0b3174ab2359546a12bcf2eadd2987edac16e1e2546cf780c0624c456bf9a192d1c4565df5b0287c85b8e5723cc74eb9e001e89feb577625177e1b27aac14b2ee742ac942ffcb428e39b49e101e554e8ea2806c590e3b7f48705a7dc1eb69fdf0ccb1ca9520b323a7405bb8b0d5f7d673017d0599d09457c693cf76410991012550bb92915b6efd9767a793c4019a7502e08446fd3eb61726a7ee40f15bf6e30e523a3620b220c27cfb61fca4e8bb94cddb3689c1ca3c00d29779cc0d132c7b78dee3c09efbbd2bf867952cda347dae8004d2004eab166e84d80a795b01dd5b43a69181b15ad789b8341e02fcac350121731a8647d07948b8e61cd3c5015ff6a76315e392be50f00c9b3d32feb7cc9965a2cd76a6e685462c720a0c6f4a465154dcacaa4a7066189d0c4b1d49bfea698aac80fe4199f647cbc65be1eb12bb887da0fe2942d3b07cfd2d31ad06bc1045b0ac96e8b8d3659fbd026e6c2fe1514c2b901e22f63269cd9466ff5b2e412ef72a816f36a8d5103da8fa2538dee2b7b88be9a246cc538ae285fd0667e9428f501a6d034cc4b837102145d0ac2988e2afbaa3dc68d5d0dfd2c32cb5d0bf6c1c872ff73b5d1f35caa75c00dbc9d232eb3b788025017718b88fd7bd2145fa785fd3d25c15b54f0ba551b5b24ed28e209280fb418b006125bf4a5a46a09fdb1c9705b4e85b3e10508eba5b936a29d4510fb6178bdd9fea0590cf1e51ea1b86f9b24c0c4028ec7e3af435bbe94197ac0235f9c72e8bc760dda8967da1c734392e25a7aaadd9389c21104bf0179f9aebcc9645c47ac83c309a890764dfdeedf966329a380addd28b2be0c9c4a7e650a8ab138234d9376463dcbddba6c8f47be538c651a5a2ed0ad1832e01054558f5e1452af162907db2e624198b96bb645dec053a388cd1f3f6031dc67eb68a42934dca141ec5842810caef514dabb40102749a7d06786780d28acfedafe71ce9b95e5331806e5fe1aab79953d1a469511ba2fc36dd04bb5e02cb9353321775be0c179a0469a2fd78183292e9b30ea891180e8ae104a39081986c43db55b9edfac6096e4d5cc468f8555de1ac24018b0d053a2a957a2b212ce91ed73e0437963b55704ed1656a99767ea81ffcb0cc82f969d2476b5f574a45e6af6da19b26f9124ae3b9d090385d29b26d6ed920f35068e13c77925d6f7bb7c72821294f9412239fc6159503fffaa51d79038401918548e779d28fc403151803096a898ba0e43065c6a53e28cf590c679c18981ec64f3954693343e7623b2efaee2437f877416eb46530d0dc3caa0762efde181a7ffc671fad96fd84affd2a03658ca1553f7483004f9b891d875d1065b52d5e0394792cb3c94d39050714332323769f5962496fe01e7567d6b858826024446d0cfc6ba158807aaf303810b4a2ae70c91fdf489554c5bc9d1f654090ac233554540cdc41640bd882427756a01b78608b4cefb5930e075485e094cc3e4dbfd50967284bc10e65d933fa635a14593a8956a8e518e23cef9d8cc00af0dc8a63cc60e33ab641648888ac3b47dc875a058424a2d2fc9d5f8c303e7b3ce7f93b44c8fa7818ada0bd3e4818e8bc0731ab1331f99b7053e7de0f4ca8ad331516c6f584aaae903909964944b549230667aa9d43d4a3fafa00621d446c0c439e8d5abde3418ea00418aef90c3febda25da8622bcfb1667163e230f6782d82562582bcc22fe2bd75af44aa547b77115382159ae47bccf6e431042f9dab285a436de5974379f2e9f14dcff21ba905f9638056159e55fa34b4bdbd2b3c69630cf7a96ad50c0d090f9b038a5b5823c3d4ee1b39d8da34f6b65ea0b8a3d4cc4cddcfb3bff226c11c6a8cf76f69f3735cbff51e196271a12a004c85b7009044b4b2729453e083d88631f8bac560ab40e4368a4183ef41daac5d3a7b10c7a574801c8150d7d0ea55535cb1e8cf57a8c61c24b16a8d3ed40171da780bd7f7de0e2ffe610a5dea567e892cb90041f7629fb74eff4698dcf84484c64c403f42a39887fe8d58f1471c3882224c42dcfc9422d291ea140b02557f1e022612ead063d126042f20755e3cbc55a68cdff5ad516b32711a3559c962722f30f9e7aa2cea8be6e42765841975e426502945b7ff3608120a07340a0ffdeb1f4f69960ded5995e99c76922732cc1bd960786fa8cb36f7563dce1afd8f885ddc6c5b858dd22000d3689f4534b3bd4c287cfa45d05ade8cebb5859073760911452f26672236e7f38600e2604714a703e0104b88296b86480e04e2c2125be407282ecffb7e649df0ee2927d905b80da40addeee2450df177ccbfe1a2c32902dc9c7fb5096e58091dd224bc7b4adc4e62d776afae08b1a400ff9daec4ec536c9f19f9a1133bd023fb6cc4519036c763b2a399fe13151da841d410ddb3b5c275ec63e1c6bd7fd7797cb66138dc60784cafed4e35e3b71b8179898c1d55b782904a7697a6698f097ab4d74ea83246794c92c3c7f3a9a0c82f070e73b6e22a248d1daac76ef882674324b0a39ac0bf28e46928cd351fa9538fddf2228f37ce66b279cff44d6644882bddcbbec6c64076ae750db884bbe5f689937e93ec3e0de2d6c162609b25bfce9c1adaea2ff0029b7d4895787672fce844e58b63173cb62917884ef0aed70c2053fa147037749805ac7568fb61cfd0fe681460181eca998ad2ffa5ecc220d4e6e9644d4ee0f1f1641a178edb2a41bbb76ccb4101b775ed3140ef972147f10da015f4baa624c24aac4d4db50d7f1247bbcc1c5ff0f765192b122b451daf63bc75910d9995d6fb5614a18309accf3737c047f38a14c345626d83ce054b509628b5e546a26f000c061faf2d7f2b1000d5288e985dfea1e6ef8524321a984728b1db9890dcceacdd8e2a717d32faee62732bce3f4eafe7faa8f83e13a17d769c7061501e01aa4782720b9862cfc26bc70cbdd2e8dbc98be67da6046e13c384531076b479d2424454c62f79230836492317a4acec936768659bd75676ea0454460136cb4bf9dd0c55cb2cccba515bd283009376babb8ef42e478cb9091b60b4b900e2381a05197f8a49ff20c89a1ea40a3c5bf8d447641d81f6d4ddda510bdc100c95ff4f7bea0cd7e6bb193fd4f021bc7aead0ffb6e1baf93181fe2bc105c06dd3b9027a2e61304e821db621a42091ec7762b3160fc0d13bb73d8f299ccc861aeea3be3af95e49115827ed216ef012cf8dd400836d5e8064389595327a4b56a6cfd32633ae56bbb52b56cad0a9129d9ca4a1246087724b7c100ec763eff2e0780cc4015046f27d6bc38655658aa468c328dce25c1bdffab7db5a2175a209997fc9ca26b58fc42094590606baca81df9581a5fc07bd5ce248d66f2a0d17769cc0ce1e6815892d8f45b594fc67c9d1540444b9706918e76dc93192d0492e7069a5699454ddd80d33f9171bb02915b0ba4779f5c74b0345dec52963911e150c38c83f8f06aca7417b2fe4599db9892a2524734da2c1744ccacbd60f32130021372a173ad470b2cc0e1c4cdd784b659a6dbcab32e5a17674955e58439f1f5674dde43fee4611bdc12c47404f64aafea38d80817ce66fe374d660b72a6d11487166e2c9bbf9ee410424128f12a0beb1b271bbf3cc5f0d11b6853d059ac3517219cb25e574a36674951d3da9f84fd327048c5fb73b05b8b09af90b2290a461e462db7492b83322a93ce45cb944140bd7808244a49fffb36cad2d91e0fe4379bd1665365c68cf565df15ac03098f412aa8804d6d4503ed6186858676ed6b471b90fa1f68643104c68563ce5da9c1a19e2670f263972a20e84128870f143af6f0f9cdeb4c4e4c7c94e25835fbc2b5084402b283b47c9fd321e1fbddd727c6994b7b45e95971f5bc50f8bfc0d979f25818783de68a597b2ef7d8f5683320b7fb0dff82b9f1c0f95023fea048da2605a9a8110364138230edec69fba5083736b223a8a585b0e95e6a2f4125e1164d9ad32f9b2fd142f6d1ee3456c355dac7b11209996887c73015aa1fc9a50d3af32db695bf15b50a8468f863d26479cc19cd9853a8af4a8bba4371a26bd4aacec34df1021a00bef7fdab361ff91d0bf99e29988619a8676f074d29c61ed68ab7cd3ef20709e669cb4c1829561590b9c627b1c4dc443ad6f5094b3c719e191287aa993d99280e0376ea7cd151e3695f2995627cbbd800b344ac232e82197917d615d619c602587a0c17afaae900a218cdac7803b416f60792e8e95cdaeaf5f5b728d1c8c57f1810b27a312e849b6d1567d4dc11286106378e23e84eb016e5ea112ed1538efae327bf50fab5af8e8607d2b3329d6f7747221d14b11c6aa39694fc86444d700f75e0d06a58b5d85965d133c7ffd0a992d5100fa983510464220fbc1ee1a0983f9624f3837b4ad9c2078118a9debecb06969a91077912291fa1b6414f5e44f1ec4a338d153739a9fa1b1cc24173a4889ba50d08dc91684a4106c012ea8b22c12f3facba12e4161f9ab89383509341ff38118a0056d201de19743838362b0e5ac5738cedc6086427561b3216ac5b9a9b165565715387be7cd7cd888833ba95a7974e18ec7436e9b6705b76bc837cbb028f235795f95a4d9c84b8c68f74935cf3d4ba08837df46b2f41e6c917689fd93994202e180f6e70b5db4308d0604755936cd40e71196db472cbc45aa798962796749d89a22090403bb0d166386be5049fd7a38c3128e32c9a1a586bdfd42a44cd7fd86e490863b2e53a52d2a28663ceb7b1fc4aa21c63c7fa8ba7ad39c2214240335bd36b961ec8b7492f761cc8982a0a0541edcbd70de21818b1b5bc74823a1a472f98857e5178ad2acac56326c883aad6a6d29b06381ef3a9ed38b554eeacd9ddb1db5ed6891f4ccef11c02c6d379cf3e112762dff16c45e8db03183f8da5373f6ac638888092ada9a3e818db64e472f3546a54e10b32e021ba97a96f828719c5a8c3665f34fce5642e69ecd9f938c1e7f1c8b26428df3b5f8e3fe5da239863f6671a18e25edab43b3b3dc2720e5b6180ca47350b0928b3bd2426824313ce47ff40cd9c5f1b94ebca42bcc76679137c6c9646ca836808856007923c6ade6545cbf18207dd3b2dc1fee10b44400c9ee39685a88e1ed7054dc32934a1af1762c056296ea7f86a448c29b57f7124d13b746a2d56f27882c023ff2ceb61e723ad9ee68ae7c50e6a355306afc92853e448966f1470926592618f0233b135bb477fafbbd4f37369b630558be2021f372e76426aaa5b0c90fce7d21549d88fd9bde074d30c73de75822941ded0bfc36ffcb4e650a66c82944011177d991b358163b413b45e613c69f0a7309dcc6b81d1c97165ec8a8bb9327178f30463c9ad957bbf3bd32eda7dea60b951748e5aaba7f4760f9313c76ac1440aa45894f6d08d6d008a2a75ffe97b15fd594054e749074706f91238ef9e559341bd9d8ddfb2b62159fa9da6fdff286dedcd6a5a7aed2dd7ee306b9ddd2c7909c0546e91e108e4728ad2443b4e0ff45bdef538ede6f7c09043b1a75d7097a0ce50bf6139e4232bf9a6893b19e1687e0b8a0930c024868f37006c3af5123263ccbe2a311e4bc99350670bd5e89ef426f9152c2b200457f86b6246faa48d0049a4be4d36f46f717b67e8b3cfd2439848d43c9be362ac1f995f8deca7809be3443205e8fbca76920a015436c54d5845a7962e68f34a16af81f5ef9fb12eec1972fb61f2e8ba88593e07c04fa923cb04872db16eb1c13944cfb17ecc40c23a7f8999f874ba0700564d10ecc5f8dc5813c03029b2bd50952955e431fa15fc473729c85183369819e7d78c9e671d901b6cb6c76ea705cab81219dc0c565f19188c60265cc9ba3c4dd7832c2205610aa9213f777b61ebe37c8d292d0ca11a4778a4ff0c9e0bc0ff61f47573d557ac6bb91a738565714805e2f230325230dfba1c6c4aa6de8bc7f21ea163986a631d6051d0ceede56a97986524bbfb90d9d4c181073eac54ad7362b6853c22a90506ae15212ef81b9d346094bb2be711701e763c9a1f863aee03596488687cfcef9a8588637357c78a999cba878c4ca76b989b0bd799a33d345c886af0842113a0dd35d46511d42447a0e7302438cfba989b247395f8428a18ada2c6676ae4da77115e5350667252a1210efbc865f6f40a9aa785a2dfad070e8a4c13508958d2ff034e95f16e9f48a4ee02521ddc2e3119cbe9c04eb45d03937a0167a5ce3d1d78881d2e236c61562f9417d64e92a1acb2cefb838f63757cbc384bb8e520690748d58c1c059d3a16ccb521eae07b10564d01899bc1a2fc2772a0be0470b2a88b9b5a33b3c672ed29cd5597a2855869c00c490c9b510d18c9809ec0a10dfc244b3c233cda83557ca0edcd41561e71ed4eeb602dc1dabaa623aa395280c6e0c99fde914e97923d62367104e40212a3e22c7f2688d9d38f8a69f22144386421b4456d65aa144a4c26766f1c720382c8c03d40a4fe89d4b1b3e6aa60d76eed9c3eba074350f6bd612cf20a4f73c2443c13997583893e29999cbac8ad58d3553209aeb1bcca6f65732fd50dee96c84f48cc9648e0c4bf3777f17bcde68d0ab30d321b31c2e8a82cd8ce2fc15411603cd55abed82f1fa3bf7a6d79f2b10748a1c021d42b2b82431a3844ffd353f9974e8e80d42f7ed9d57cd4d283d1fa1305f75e6c29adc5dafd6e0f249e6a082110744b83e018003e215f7e6093c501bd898b0d65e64778967143057ed3c2281b471d76b1f29c4dac25796d00cf838b6e335cf842d5335ccb525ecef1941d56dc5cbeb5617a787c6302f8081a760cc772156de57deaa6ad40b66fb30ddbb06628822c6d9d38db08abc0a9424cc1ae35df26b92ec458217b7ec604869a0f985635c43ac91d59da86961a9ecf7e9dc60670b0c09e9269ae7036bf17d158d9e3b214a1778fab5f1ea4ecc4354136134c3e71a26670637ff73605b8daaa282e348f61eb62923ae333b11af1bd83f49cfa55588930a4c3e42cf62eb24571d0fe9f9b6631ba111a15815af9823df3be2e7ae75b1555a3b7a13092ac21ae0f554b459fa14ba382bc326a9a30d7aca4609f46da1c2ec19ab07adae77787cf7f0f89ec363b850a8512f174bbbba0f0dc92fe04cead05473e0449055b1d8b44b6f24deff6b66f175dcad1ec64a8ecb605dd1d672e040b676051e94828cb87ffd9fde05cf0b7025f882a8c4954a09976537f75b25c3606e6389e6570097af072cb63321db7207d8a3f559de99310c5ae317bbeeac7a42a51c13a84f4fa0e92ea566c8d92b4c1f227b46f5e4c172fc663ebc490cc195ca79f7aff6037bf7acfe4e21b34034ffbc59082fb6dc52249759b7ab57c4b3ca4580d519313a10ecb897f7ee4a69c9a8356b8d6003865c6df97c95c023acb76b17ceb42fbf1d74a72a7932b65ae38f9af0418bf0b975e1e73c08b4b1411607bd1c5d5f63b109a848b709105ecc233404352d788bacd76338d5b9329f2ea77b99c15d2ebc7c856677d8f907fea88728414f5e0da58edf5e76c733acd8aca27b8a0ab9ca42a4177e852b282d8ca63b69c14cd543b3fb703f913577432acfa710325f472797c1bd46a58734c55262030a6170b1aa7bf50218ccd9668346b11974eac4697a4eb7c9000e399d3f3a9703e89e314945320bcc6fdd096a2377c56e479cfafc6c0e56a8b4bd3f61156789362d614022f5d5c5ed80e5515525814512ad406e71ee94ff13cd10bcd5a6d618ec3acd82ac21e77df4e23b099ed1e7aeb1e242063de8b7fd4c184e9dbb81ebc7bf51c92b5c363c22d20407dc9b22839c741fc829d768661ec47a2a6516ca3e8aae0f603c595cbec16fb9a3c769a4556f3d8f0400ed3cde0fbb3e0d33456534831dfc5d454f15d42c3f23261d5c4c10a48b2839a9ce3a32f9db3b32dcb652211fa2052da6e2ce3de85785935b2611d63ae550e8ee7306dcda4b1c750e5e974800be02b0c79a2e3826a45af2975420a0a0248ca1375c302420f55593020b10b207b470fb06c2af079e14f9b8f566885b4f10fd1575674a9fa06daf73d1da8ea75933551a05b6c17c5e330958f1a7840ba4a50104e34099a4332d2abda671fbbb5ef7e76e6a617b7c4ac676465a9c3e388e6abb3f40cd36c080ff39a8691eda22bc6670d71eb9b20a09b61a39084d7cd444f784a3beb6d70055eb97ef582a51a578295aea812e1d4068d3677f0ef775227e33617ec10d56004a33655244b722562d0f961fc2c01b4287b59591670da87cd8f20b75f4ded484e86c8795fd609674d6c8d1a16b10425d63696b32e174921e9ea1522d1e83f1dcc886d2213a895df8b9fa41d8225f2caedd557f0fe069cd71e533decfd80f710fcc4af09c3240d925fd5b7fab066860b10cbde729f930ddc839f7d840371242bf04a32613f9535ffd5c7385824d0ac7514a298e577f7cac09bb08384ce3faab28d8dafc553a48347fc9412adda8fd379f261918494ef74a82e664eb9d42660b13b46206cf49d2fe6d58407683895c42f4444fb11d86b25208d191711da790a988a26684e22538c4119ed4032b4b02cbf4f72160364674e582996892ec0a7869241329f2eee441b3299dd30eb79c2150e7721567327b6afa498b4449d376c285dc9d9ff7cac07dcf905ec288193c946eb25c306923b09a756fd4922b85b341af4d7125dfce1589fbb48c89de6aaf9b866cdbe906f1b7be0bf9a1744d59a342bf609a0dd287ba4821080fb34021e608f4aaaf5c0540ac0fa95b8635fdc2b49cd3040c3a1d5457f84aa3860913149372124c3c54c424011772141abb8898fe3cfedb77f6dd42b9f425c3232d5f0199dcccd61a6ccf6a891b524bd31adadb08defce71b230c8ccfce8d10fa140a48c4da84a6f17ef2ff3d3559236aabfe177d374cd2a0edaff0c2b1009a70bab0481085b126b3e99167835509e8959923bc81a499cd0c377ae3029a13f34d695d5e531dcde1466610a939ba922bdca0acf4a9ed1ac5e276cebc8d7f654b826335def958dd535a261be6436215a265c768d4f83a97afe4f676a595e597e3304958f72757a69213f6769337adb5b2abb3e03a60fcbed9582634a02ec2327776ff672ed33056e08e9cf9de5927f38be0008828b0e94f0ebb1971b0c8cb1c34be4a0d958c4561aceb551a12941801b1fd95fe5fc313250fdae9e02a95cd4b8b3d3a4a8606170104ad5c74d4264a44980465fd08926b0e7d66d7249f8501af0f3eb377ec3e886166bfd71523230f6f8decc9542ad9b3e8906466886e6f1512af320e674985bf9749418b6ab3b0612cef40599bb26d3cb9cb93710da9d5bcd135939807b20d17be6e5281bce9659abd04539a3692a35ca410edd55d4c0fc357230318123de9e8deac393da9bef45a5f2bc962bb8017bd799f558e5378dc5f64dd2ee1d22d6c41ea0ee91ff7ca4eff98f5f7f3d20468370ce0f685fb05e8814ee84edfda347424069d3ed7ebe0dc28052026c6053a43aaa1849316c2ecbac2c52da94d6391ec381efcba06bd2e839a000bf111aa9b178add626b0db9608cf4e7f9e116857a8324ba6f4feae96958729a2e409b459f65f934d22df1f0f47d2c6f063f6212bbb4deefcb4dcea871c61c236808e52fb8c45be874ce41c44c2e8cc064c6cea1d9e50a0022bda98b07cdac81fa7a21728aa0450e7a19f33cf5417f6a7071c8145319b015b27a605754fd901897177b7ddeb6d1c04581f0ff782af542e28a5f1515d76891ce0f2c9b50b3cfd8e9e3e809d1a710df689b6f82281661b89752635ab916d088fb875433937df2fb40e5561d494b4c8ae2c4c481a7ccf2ed0c18bc8255c3b8c8c8722955aa287fb7a22a45804de2aac5455169dd77ed63b808c5635099c02181e74c30888ecf81c044e3264e0858638e5d85567a8807111138488b2fb0ea43d7884bea063aa55bfb082527096a6d7d49ce7de5b1b0fda8326eae959f45afa1a14beac348441b1cd29f0b03091c4daa45a071db67d60af7d355db038c61fb8383b24b98f0998e5fa32bd67dc37abe18f7dea06ac97c0a8219965a5894ebcfaac74547cb2607595e5bdb1fa82cb38dd2dd343b4988fe1a5f1e0f60471fa88b3f40d0cba185a0974312412f0719415f8e41825e0ead04bd1c92097a39c809fa720c14d4bf1fe291024e94a900590fd4ac0d3716e06b41bd184fc4c81a02af358c9cb40fe44bda279ba09ea770308f8f043cba460a39cdd9b2ca35cfcde84c9f78e21817b5852c83e38a186b31ec88effcde9832ed59cc85467498e45016266267872a3805d51a0e2b607342f2c56433810eafcdba1d2e62b2d76cb010ada22f3332c042e46e4e972ff064091597328fc613806aa8691f9db76d12308f9783be51ecea68a7fb7efa0372d6e5950af603e863e515a7c454962d026fb80669afaa9c2495ff94bfc6e3d56a43438aefca09ab233f3d07cf4cae0a381db091dcf81f25b3d37b0242787fb7dbb185c16a27ae4e342b0d0678f24285e98a13ec58589734e063d57844f35c54ad0a5f7c0880f61dd3844bcd70ebab80b07b0f136b092860d2f866116f027e10e9d481a0aec9eec3f4e59889de93860de6a888d6c99b0fd13710ad014b15f375be59e22bc1131d3e7cf8960ca91383bbecdb36a10c23794b4165f4d950ed6cfc0b9664d949d60307bf0da228b6c42718e6610215a97e5da5c7810d38fa21ba39f1e93a079f5c9400d1c6731f5ea2f4b711683f75cb184ed3b77b91feeb95aa2c11681a67d798c10a86218c2a34d878c37bea15200f5fc9ee734c74c6e98a45f4e2454d4297f4b5f2fe9983f0190d3d2ca08a4b816218048d4f0fe861f1aa89d1b675c0a0864a729dd34a988243b6f6293e133484b79e4fb10e31ec7a2f3e80679bbf9fede1b124bbb5a898624fdddc046bb8c09c3c90732cf95bdd39df2fda2234306053fa0274f73ccf36dbc26437ee888c6e8ea42e2988507cbc966015694458fbd7d4d6c8968a7afcb8758d86e033cccd83bdf8840dd63db9aa5b80e0173adb7de7530339a7de8f994508f81ff6f50d7537724c152a6bbda552a85337ac46394b0ee2ed6509dccc41763ad44b6d4b15ac31ec605e7235640df223f5403a3587ac86bcb93e3d14da5846702a76fd74ed078003e68fb9806653e2ebd7beb8f1f51aa8a492a2e1effb2ffe7463bf947429fbd79b74757f4c88039d08986089a995be3ed99321a7738f1f210bfd1a73ca095c4382b8f59cc107714efa4aeadff63d921ff3bd657ce928da66d7a1657502cbc4c742c5bf142b709f28208d1b4f7cd1dac5d77ecc632d320289e57e40988c8b74f258628ab5420fd83cd8111305a039f9803765a26d20b2bbe27125b5083336e3eae56c15c8ec220c82af0edbc1a2bbc09d39d59a3e4ca28104f10a35cb4eb79a9113e4691e1541bd7073e9a02e890928d34088cf4f6c8032dc2937a2f6d84cc412e2785121670d01c98bc3f0985b582411c86b8a1da4b939bc49a33e53be3b9118c61c30b03de3629a67c1d028f48f7abd07d4ab1f1510b4daa2c8b96d0802a3db122da988f969709ce8bc23c1aa19052373894040548868cd9b2f60d57813c59e9fc27c21645acc399078940efd0e10056dd0f264d1cd67bbd164c4e412cdf212c528e6b0f846af7e4a9c892be2e104aa1ff2ee0dbfcbceff06ca386854c5500d00246e8f85e6ef012705c54aebbc7d382684381db578b2674f285636deaf9f564fe39d3c2009ea7bb39bbda480f9f6eeab81c1bdb3800bf303fbd4d4d3210d77983ff9fb47e0441b4b525c9ced316036a71c1b4b07a7e83ce5085ddfa3281709d5786a3c1299f3fe01c9604df21ab58ac6387cbd7e89fe0ba16d809999a059dc23bc788028d27843f4dddc7fcd5ccaa7109bb84915367cf9af058e9d16d69faf92e7b51669990dfbbf50d1ea1516c2bd6c67069be40c03c58d85f938b92c1f0d21273963fa658531887f3a9858d1160b6be85a6b186b3a6c6720cf7fb64c04d87b42ee8c056a2c1b85cf9cc885f1cb9de021f1f0f24b467ca24e81ba80a875b11b0a4ea494b72aef81c87461702b839fb400bc70f13a3f3f305e16e5ff1094534d003be971397dc1c51c0e902dd10b100b392a7c977463dc3d062c4e2f799229476a48549ec2aacffee94875060f239fea8a447deaf452c72568b031b4e0d8d27e0e35976e6887c5019eba49968e68e85c48d607b68c7f63911ea3fb4c8d1f460fb3f57ea87761f03868ba808074d7f86d887213480fdf6c3e7ebed7c31c67137ca771b68373fdcd404503d4c7b870df3da23822e09077d2d4ea4a7dc82044c26bc2ffeffe83f73b4b20202f90ccf5131ec8c0063a60cbeaffefe1878c750b1c9c7351828a3c7f4007f4433fb41c4feebf5164261966ca012cdedbbac80a75fd19405c3d7d2e314ae5e3b4c1fc784b67fbbdd1407d0c4a531cb924c609cc90e4ee02748e692a7d5658a07fec5efda2243e381f1d87af8fc16aa67d184280593a5835932c92c88ee90bb860b28cda45027e055e1f94262142c0568f150e4eb98d166559ba317a4631a348c10d99549812abf19c6da15c1fcd3589dc9932579d224a1c251a6dc0057d483d1a3609d6d82f5815c90e5cfcccce15a372aa98f97d5e9a2522889d4e03f4c107eb881a78e567462e6eeb0ff9305329b12a05f2f1567169649045669d9bcd43c3310d1635473e360dcf4ab4b8f600cf7904f918863ea2dc1e41b8a559bdd44a68b7aac1bc84c0adbbd8f132d430bd2ad8d4e37b901b1450b28aeb638f1d5f2c15d30d77d184fe07f02cc5433ac0b32994da2fe7f08ee9d29d0fa13e8406a9150f7e60490dafb3aa5cbb1e97266c927a8ff349d35af109417fe6e61ed89ee1e42287bfe5fd812ed2d0a489354f37256ebef14d21693cc49224a08dc72beb62f3daf907e0ebe245351e24ac4bef91818fbcb5cf9bc11f59f9b8569516860ef7570c77ac675ffcc78a185116d1b60b4e797425750125f5ac6c7fed0d5fef0a7c23020c63591cca7e097fe663c1dc742cb640fc19cd0e361e03e562bc99df002f36ac3e1c493a6e5f732effd5f3a94ce20e9ac4d0de61409085d969a2200316ecc9a4ca341e5b09fe5898cd2a6a33d68dc74e319a447a62778db4a62b5284336d97bb55ca7f19879432ff501d8f9cddc505e56eb4548298806bbe3d1c0827c0805223b09493a04632dfb5780c90beb388daef81415fff5aca1bb4697efd52b8cccc9d9aecc6f2ee74a298cb54ca8fc7a89f3052810353a7c556ce1f8f5953f49fc0cb889bc1126ecdde3028c9032eef0d70d2d41c915bbbe814903c7a0d832e24e73ace3360481e125cfc278186e7d3fe2215b1b4dc9ea007457b8599cd5c7560644a64a6e0bae159deffc00e64a0838141e073e05e45556e923707ee66d8291809757a2530075ce7e8916ad50db67db015d4c4c0cad04dfbe1f153a28d334381b9550a38e96e3075a627708c5a193856c523b621befc37aecbe33e4063c22a16e211dfeec09154416877099e1f988671125d8f74d157eb24ca709cc5feaaef269817549a1b22551ee6113ef237410d57e4df06682771c409f82937fe018a81ff0f612bde9f56f85ed6ed078536f0da2e50f17b087dc19055815a6fd30a26fa20afc614dfbfa03b4a12d6247de469f637ad243ba4c641f346b878ee54bc81f951570d4cadebcb5ac1de1f3cb78259fb6e00fe850330e5c1d800d0d1812abaf6e44765c7672483489cd8f1c9d430142f3b88e643ca7f4aac10b83852ca13a45e548b366f0c442d1690521009a27523da83c2deead17f8ceef9d5f8a9681193d9a119887f44aa84181d3968462671431715b8543b211cc89b7948500d762c2bb70432179d66fe62576e314b5b379f737c32f4a826f095810af0abadc90b711bf484a1f22df22ed45becbe8e2f8aee4118df8c34d8de49985f058efeaf4746ea4a92318c038413ac9b4a9fb526f8c958377a874cd9ea9585ff00406ba72d9762f81d97373aaa9d9379ed559360bb57442a59c994523667a260e9c81886be8184031920a605ed2f810d5ce0176d8cf788847f58b204a9aab2591a8e902c06261618019528cd15aebf00966acaaaef117b131557824c63ac9b37a3fa80a804c100be66298d00d64f04424bbc2100220d6863eaf7d07f8cef90a0ef1abc23f29da64f16e4983c52cecc16e85fefec6786da2630be421e62633b9dc6ea06411c89af566b2f9afdeab46c454ba997182f48262834899b973e6c74148691cdcd41d4b0793da09ea54e446521c46ec81bf41bff1ab39191d556b03c06e42f7c95c42266a20baccf882d21358feaea24d92a32a857e689d8a997334fa52e003850f8027d240f006ea7fc42012665f146e780598a922ca83c59325e32e03c84998c7343cadf628736591d495b4f0800d64140dd77f397494ef1ce1c2ba31656c7d582148c1dbeb3de9bd4d37db4f1fedd665f79438db663077cb331805ec76a7c102f220e9372e5157ab2f2cb033a8e1dfb6fc9c6beb923497c50537e6b1fdb56899c25dee5aa5aa407de44bdc85e1b0fa588dfed23302b9506b724490abe68a2b0d36e75cc008190a4320c676cff3f2787e1b3fa210ac5aeace6d2d2a32a3becf643a43ea87a3628fe3bc4aebf1dd4b87f6408d26614a0e2ecf4c4d73a0416cf569391012f44647cd6fad4d8e73d37ac4f57f5a2922a25f24f359b96eca163eee780f7d6969a0b30e032eb29f5dfeaccc2374396ba3030b37e831a659d31ca62931c460934a45939515cafccca41be3f51b623e599a5c6f86d4583fa65bdb75577005a4f2ddf42c94401f24480d6dc40923d210225ada0443b05dd7eb04ebf4bb8cecde77d890705ad3566836e2f906217393384535c050842f2231b275e402701c6d1dd4f0c023b76b926bd9c8da4cd114cf034af5ae758968ff2bb3ca685397ac6648cd0a647c1cddd1c646781806bc23214fdcff271872e35d41025a1595759e94a9fdc370af85be8ce0ed725b8866ccbc45488c0e3e517352761076c2c4c5a63eee912290414cdad4b033ac6ab4351db7e87522f7009b3847f954cf6f21a50733d86ad5ac0a9698b446c59ae46ea58a528520b7f5e1313a49c58016e255a66edfb3dd3bfb2086fe954a3ff11f400f8d7083fb451e4dcd37ab99239113432c749cc580d6288c0732d22b4fbf24a7fa33a0c57eda754cc864eaa272af431bf40c00d94d9785d8759f32e812ddcc5977997a5129788d70247eb238fcef06851c7861184acbe922afee58acef37747d70f3b20588c62c182fe39dad5c1817d6ef72ee5267948d24f8dfffd829e852279de88ffe45803216210e93f37bfe0b16c93fffd2d9c0f5ac014ff552a79bf11b02b582989963732308741c04de4b4513599b98e2370e7259efe6e892556311a3efedbc2207a19f70357bd08af87226bdaf78553ebc00a81d5cdd4201e90bce4ac8f243c712b117bc331fcbd21cdca1c537384abf8508e336fa2e82c7033404974df6a38977fd344baf324e343960ca90be27d9ae08c642cc1a611912f0c41d76c4e5d8d34b3505588a065f49c8c8be85d951069d1acb8595220f66600b4ddb7d0455ab56fa1a73874c49db72bfeaa5310c91d06d7dccb52beb089c8d966a32e1ab6ecf5e8bcb131f8b467dd18160050200cc2c2c8560181ed393746267368607dc52832ce8ec4eee2ab5b3fae96ae8d9d7e9b9fb43e5c456ca3b95f5c882bcc4c0d0ee8f78c774f6f71138ac248df59d5c8b548c1621e86ef95fdc7157ed1fa581f3bd5e6850441610374e81dbf699ff937c776adf079d47fd97992c4a09d1a3c58ab1a6a5246ff3441cccde0994f20001c9c8143d79718a5241c36f8990f5bdda442ff90dd3c310f929574695c20b40d6eb47f07d77e59afe3c8c8b7084849a9c854cb0d0bcbf3b83d20e9a6d25583f533cca0eb7e1d66f4d2738ea309cf04cb6c1ca0a4747fe00001466648ab799aa1885ee8165ff98500f3c8a738aa5a0e9611c786acf2de7889234390735d3d9280f14c2bbd1bd3924a47556a621cb7a51f6a7b6607a7b62fdce79999c13e4f4ea87dc365860869a65be9ea40cb64d7cbd5e8a09dea27340d33eb5dd8e38591ebba2126169e3fc4df3c9e615494e584b1d8d8b565f66b87a80522304406f95e3fbc14084554e12fd0f110dfcc6994d0870f2433ba7351f4e5111d565302f205455833f978dbcd134d7e799608a585b6439d1ed003bc680f6e6152ba74794c3e8a084ec978ff6a7604bc76035a5f2e7c5e4052324e0759199a61682b25558c5c7af0b928b44d3801e16f75b3bb914ec09e7599334eb9257889e4f9620e9293aadb93cf4718dc05f637361c9846ca0129461418f5e0251e1d4ed310ba4d74903497db1c2159d476b7bdb794322599028107ef07aa0732f0255be865ba5c100a36c207e640c2b7bdfd91b018192b01c9a02012549f0fb9221df2c629e34c0dab4eee8ca32cc2c3234728c9977c9540e39bfd4797fbb62b5749e82ac92a498dcf246d88b4e836890ec94aad90ad4ff645491531a087a9c4c3afdf1a1f9883b5f98a5c910e75155da397bb9794dbdf5370ab652dbbe2b60c3f1916e0eb933514f00a68041a7d503e190b9f6c05134a2900d59fb9c2daf898f8643ce31668f43d715dced0602578a55b356c2abad6bd6ab7bf7bf5cd1137800e1b1f6885153f19730ee7ff64beea57bdf8dea243b2a1194a24530262b1582734ca643ae18684b1f60677477ad5dd4bc88a9fec75fb3f59df40e9169532a483fd865034a71919530c941d4500829b73cea69532db1f7f292533bb5ce9734e06b9be094f3bfae5ca3bdb671ef38d7f37c7c9cbb20a1920cbef0fbec072adc9dddc49ccf7fc29959fcfcacc5f6b153971ca198add9c4cd85e2a65ab98997d454391d2f749e9a4e1c943ae6913482945ee9bf472ca0871ce523f0ba1d8859c6c818419c28319c2f6fb9c93cef09b1208fdc311275ae6f3e8d9420b11fc9003268cc0810e3ea83332b05f096c3f6abadaf43ee050031345846ea0a4a6c5163920b1aa9ac97af2b388112caa85996110b8267810032d5050821b28d15a2401a3864aa5048b5906465ea0918d61beb530df3e969989978cc475151964b0032890004108a08052a4c5cb12e7bc65f2e774b4ea08cfdd391c0782d00e8e1022a449932037f880129fb46e9cc9a562f90eac1a3ba0eeeeb47aa54e3fea5e53a3d619daea465edbba60b5aa3fcb73b26a54356a983e8e6bd468154f989a1aaa9adb5d6bb0ac7ddb014cafe4374c0f2490eaaae6e7c24425a626afe87729140c7f29e60398ae28037775aee3a8a17ac148a5c3cd9e20746881152888662ecf72b01285c662b9bc7bb8c8349c157af48db7b6398e2e0f3902d410687b4a431680501cadea2de45868c1112b7379a60309aef85e85688a208b1f267280b7a6ff27c32eacc8c5d42cfb49863038b03fd3218bcb8fd38515718c8436aed2374969e107b7f083bbdbd4965bda2b49e0e363c50a3b0b94c8458985d9b6d0dafb5df1542c6943be48628982bc1c8a2e7c40b0240a3aa40df952d52a51e78afc53e41dfea580b8a7290e47afe67d6f4d16d75026a269b43d331017579f0620ae09c4c52515e43d29a0fef639f5b79a339f72685d3abadbbbb1b8341429322384fd1b455ab1fe2cf4cdfbcae97c196dbff814c757ec7267c1dcc0ce70904e53cccec3952bfedccfdd6f3fdb614732b37f77f76f2918dc6782a8514376cfcccc3e7d4e4ecdf7140cce753f6b789793ed3dae9442bfd7eedd6f5bc7b77d6cdf0901b364c992c5e5a59a3ec79ccffc0053c5d678a497e14efaf1e8af0ee432c967ca85ef76ef3eb855f472ea74807ee6f0c4839fb714df9a5ec9c7f22c43313333a9494430fdfa2df8cff0c77cff1afe7092f383afe0f7e929ae531f4a86620d18941d3712d755da711b976ad796aa1ff879c1897f3e10d789c39302e8fbe7bc873a4e38cc711d7f1a9e3804daaca9bbade0d9435252ddb8ce27370b2bb44a078eaf6a4a95fa94c95733453b6e4bb1aba6da4553f5033fdd2d8329785c6af6a5b0d13479abfe26bfbf070ba915ba140e4be52b293bd597949ae3e8ea52ec846cca6b7471ede2520a3d6c2ae7c81a19ba4c54e96cb7824acc4589fdfb2ec32b56bec79c8393e1fae077e1bbf3eff6dbeb68fa95df5df82617c2481b73c4feec55c51559ec49946072a404931958f3e533c05bfeed050c0e2c0e4f5c0e45ab6a95f4c28aaa2bbb7b4776dd1e8d1485d4dd7d6f241a8b446318cb759845a56a154da95ae5c4fad3bf514250bbaacb519828713d67a94073f9815c0e45b93d155281e67645322a8400c6e25296bc3527d84a29d4a0c08a31b691bf3df5ea274b3b686ec7a94073bb7012b18dfc1d3497db42b12b7d27bda780485fd32dd2cb7f528a146572245aabe453219a444953ab4c608d33c2072b329671649dbef11667ed2967becf909b4f43202e7ef9d65b2bc86c9b184787557239b44e7f9cf061e68915edcdc1882bb23e6be516de1029a91866fbe4d7c111f172fae68319929dd3373148cb52422b892611f92094cb55ba06eadb59932585d122be303f1c38aa1a55cdf4ee64c75b8b08c3a55e4905f09a6addb68de3ba8e440273b0b4cff4a14a251c8983033c623d10e4f66cc76d955a3badf5b6d2320cd49bd8c62349ee1653779decbaaeeb5e42b8cf5d0e450ab8f48b76046ba3b740a3918528324e581134028dbc253934ba52071f5c1124026b1294d997d7451230186bff1200c5592ccbe4d1141e2c1cbf344dce071e97e0a10ec0e1adfb71e0a464a02c1ff0a0c4913783c4724a05f4253e73162b984d268e63b50a07070e8b007dc321cb5bd225fb07320a4cca7ed665e1f4e36895ec968f43e6105661e29de4b3ab5bb20e56f8c02659c43b4996cc5c92347c25dc6651cee03119149a8c248b618232f6d8139a5bc2edf75437696c061a639b0bc85beac28a24a1a6b12b9264a41849e836ea8989142391c01a8d01c8353235abfbc3085d1bc25480f25c910347d787b52f72e0a804c58242ad028d00653042582904cafa86a5d3a0be09a25b26fac4f33870745dbac0a51da90a509eeb43056b7f06a6520a34864c99607c76ce486145fb3f313ee61cd35f2f2bc48a76c6769cd427a9ba205fb5773505e43d2925e485711c4797f75d90fbab063673671db2222a08753d1a8a10b8d353a7dba538999e91b36786ace8af2f56020ebcaec1f4cf0de1f2db17174b048aac96cb6f83d8aef54de9f24f2a24f5f95df7e484e97a76b7c90a1bc3bced35752927375197fb6060eee42c37396f4d4ae7ec50301e5a5bdf9dd6b9c35bd5d1975c959e970f443526a1948f9373fa4fc93633332b6ebf718e1c78dc9e5fe92e4709eae29aa23419a334795d93e48119a92ed7aa269e7cbeb39e710a2838e426010a0230a20a2a6450c4072080c0c58f7745bbb3822e824c41046589902a4ff8304509e797673b1cb9a27d26df083c8927f1249ec493789224d43d367b66cfec993db367f6502a2bed381a4c1c92c522008ed7f8a76af838f522be117424eef26f95eb1bef1eef93cf82bee1927b915acc4aac16b31233f530e79cd3a547f2d399eb734ef7e639e79cf374800981eba1c8ccfcfd203233b7cc3489cccc95c2d862b52ab5cb5f63607e902e732194122b7f86f0fd40ebf6b2d51ac4792497cffb26fda1a9d2f3046d4bc9bafb0c6a09ba5c612340f958f9b4ba7fdf0fd5fdfbc13d4e64667ee99cf3117ceecc4f6b0cd08a64b6c1ca9fef3f3f4577f7191b2c3333cf394ddff78305b9b0f22733b33723d3a7190ff571894351a78652c2fca7520afccd2894084cb4f064a007821ed1d090515111d366664e28130d0c1a1931200e64cc785a2bb5018c1e4c3221ad750912184368cc92999308ee5486c42041d1d4b85319128344841a77283148667c8a04a732c47d6290a442127c88fbc4201981045aeb929821146609092a1ed56afa083e73041f1fc1a7041aee3e34a6af7ce6cac7573e356c9cda80ca2c317916356c6cb0a80c8162820936583ec4a1d860d13abd2e3159107c7905214908e3edaf2d24fb5e32590c53ffc834ac878675ac837ae820999917cfcccc0965ea284ddb19471a18586864c4b014565e3132fa8a8888c0ef0504c18f26930dd57aa8bd18bd18d13ef0fb3e1db8d5f17c60d3684deb789ad6f1803410ec785e40238484c0ef05049fc462140682b0d70b0cfaf969dad75080df4ba7334269035e020d195af5020dbaad92504f5254c3c69bd6342c6c8f1568dfcbc606ab691d0e1d4f7f6703b75a48a8d3412603b500c1ef05146a235eaebc18bd18815040192833c18417231b2c2664336ebdbc804da3b09797ae48c7f37ddf1127000e0e08b6a9694d039b0676adf6a0629dd027dc903702c0df91ef05ca5e577eaba334ad69348431081ff77d9ccc4cddb6d34ffd39a16afd41a168644c2273462363c694193fe55361ddb64a03d39299534dddb69f1d4c446a8c1f140d00a49c4224c60e32668c20e51422317600c00852ce62ec900a4558cd2944664f8c1d44a8594d22b327c60eaa55ddb69f18442a8c9f55091e2881864f558fab7aa6aaa7868d943d363e69f4388d9e49a3c7041b7566514f3f3393c8041b27dc984264c6629d70631299b3136ed4cde7f6f326d3f77d5f105a69a54b5c423c29e7d4d171e1232905c1747727e203c66ef3d02294e7e3be8f8b8111c3446b910c11d1913432a272a836337342996860d0c888e16af40421c4683494321bc85a6a3d8ca30cfa913f8c442826633327115c50346d03477960486023570557e3a6e0568fdc15e338cee8f153245098f7f8e21932fac07c602d3524e3081b023bf2f279c1c6ad080909df065ef0d512a318c9332e19615cad079eb11bc16784114a1328f29c56f5f068cc6de5a3c347c6c20a3e341a04832191ad6f1c6145a8a458aa2526cfa20667638345613da4472a845b9487a76df8be6f04a98c634b8df2388f09269452e0a8685573b521a3106e7135f1b301b615cd0ce16acec1a08cb4ceb1a62cb7841bbd456173848d3f58bcc9c43ae1841b1f94ef4600680f14469370ebfba8083e28230a8c8555d41f2bdbe5f27649214f32dbf405e2320627d520836c759bdbe461e6daf975be7ab84de684c735e24becf67b1e9456b5e7d550312699194974fa9146b2a8e88492351a0a4523c3442363068c192fb1cc68f129af16ca8468201483b164e654f30383f10ea6183f41281a00b81c79b223220000c711bc2abc9a3705b73821ef8adb4292133a4a7142a1082b793439a117c8c37142f3f572a98950b39ae0c8d3f332dd49db41058e2b707c610231e408a304b4815509f2c8abc92c1e28814697d3433555356c5858a18766e392068d49a3a4a347268f6c5c797483d3cf0c4864e57b2678351b27dc9047dc119c9057ebe754c02d4fc6deecf6739b2e383a38c5a526f396b1bc5aabfa59a5143c2a5ad55eed841be01070048570cbab8136c8a3492352f3963c3af26ab78f50b313c0f1864f70f4553f387a4b1e1d1d81e38e19b7c71900d91b01e800e090029059e411affa81c02d70044721e038b3f245701400ca7d4689873f7d192fb194161eb394a1f3e460d902d6e5c98fced4895dedea6e611777c0f33eb65205d495547852c6e5d9deaacdea9fb91e5a98641b3a735197bf55f3ac1bf95faffafbe6747906f596935dc62d6b9fdd0b3462ebd691bcafd4025a97971060503127d3c7796ba683645200ea1fa198af7ae898ccccc779abca34ac5ffd9a9939a14cde45789ae714830646eff4d82a1931544a7741b0128b3c9262c5eee542deaa270821268d7cd5359445beba4216c560a07e4c325ff85273204993b4d72733c12072203924674e22b8a0ba174d8de520d088ade978e85ebceae74e076e754dec92dceeda501233ba963a725bbaf6e23e28ad92053127416825c10728a594a230a4053f50f2822f606004f7850db268623b293392c4000a4b2fcf80a8904921832cf1b9bf69063482fbf56ce13222e288222ae0220436f40fc67a440d36541074427666f084541e3810ac40080f4a3bf5881ebc1b86504a29ede206da11468678b111a94cc0e007540891105422540891ef22402be85aaec8505845d83004c7cd6ddb503a98e244074ba0aeb0a6cb331d0871c56f42074a663a0892a4efb66d43d84dc869a218697145fb33a32860ff4c8e38122ecf8488491ae3145599e28a0a809002218e9c78c1103704e18aaa1a224e2dcb1559c6506a5fd066b52a2e787956a305d9a1affa9aff1db8259428419013d40f504a29fd6192e00335d89660a28b10705c94ae58438a16273ffc1071220646296881cb674f843c21bae2b3bab1d25a290caccbe55913534c995df167d65e9e3551021a214d0415a0899f6de6030a2ebd3ca3c5aef82c25248d1ac5fae5191008beb07d79067444ce00a483cc8155818a5cf159404046fc881cb8785458d39a2c8cb5b583d86d0624455b18eef2ec082957b44f3f6052c091a3197ca218ca62892e78d8960ca9b5d60a441d42db074d8440d64ed040e4861070db131eae3f9bee112a38220a223f60b93cfba1c87de2f26c04485cea8158a45cfa3cfb22884b4319fa62c074c50d8ce39863c62d5dfe147d597295cf3b9f8a3414e7e5dbb4a1cc10e55ec05e18665cf9e20cb99091785c23cad895bf71bf558e4b999ed853cea663b25b785a00bb54be726decd2e911ea48d70a3f493220c62b9fe7ba663e58ae2877aefcd9a503f856bd7d7632123cf1d93428dd03045c0648140c206d380e16172108b12e7679962cd7df533ba40df99dda21af01a40df9f334af7c17fa86674d76ae2c5d29ab0c7dc3b3a02ba2dc118ddcb1091057943ef3693b03a038a06f445ee2ca974797a928b144d0e5185c56420928aed79f2cf1e81d12050f7f8612a755d2001205796faca382fe7c1ad267a7930a1598776a41476e1173d16fc049be09462e1d138c5cf5e7cb2bebeb0451767e7045bb6262c4f5f80196674c82b83c63e2736d2ecf7eac5cebc22d96104c0826921421c515d776ededa0b03cfbb1c175968fc735adaa61b4dd56f83353426482428bcd96747145ae7db5590f395c91a5542a7bb6a4e8d2ae587339db5e549e1625f647843b5fa4456e44603a03df9450efcb5bdccb13ee8cb93d764ef549d6bd277d0bde9348ffa37b8fb640e29e145ad78f4af3d57cef7fd4dacb4aa379eb94e37df75ec8be92b5e6ad530ee9b927855ccc57f3b954adb44aab51e6d7da51dfec740bf5aa541871e73395a23babd19d154b1dbaf3b74aabac6fc4f915caec525f6355c85bdb8bb6c09b73c2b0642a924816b13c32ba541249a2dec26f15db5cae72dc96a40824c9920cedb82c491757545d5112957c74b0dc567fc695db3cb830974b3ce68b16e66ea12c6a157d59e5fb98a8c5c7fa4b21f9248820aea65b383d834671e8b714a9de562b57b9a32a89bc91e32451df7413f50d6f44a2298827319b320171716fbd95e3dfa18e93ed75bef7907dc585a2a9f41e5a9e80749d17e4b7a46410bda28ff3511924832411edc539622efd4a548baa91b74a5f8fbed4c9062b7251f72cbbc195bbfda90b6545542e47857a3344b24adff0ec899d202c7d44725b8a852841dcf48d7c7227e0ad7e7f4a24894426ba12864a53d5a53f8373f0f6729949e676c2831962d251f247317d762b610e9778dc37ccadf932147da66eb7cc8164a7214b2e646ddadd2dbbbb5b8a77d281bbbb33cb18785cdf70b34d7777770dbc10ac384a8995927b07ef65bbec96227317f15c05e77ac70c2ddbf0733e9893092b290db9d66a35aa14cb945cac9ed049810eed57718b647dd537aa2ec571bb6c32db2a552b57b9b975bcaa4fc3d6ef5771b5ce8d86b26f0728e956ef1ef9cc2979484be7e4a620cd9073941d84e5e70aacca4b368fecd147ce51eb88b8cdcd100537a6b65aabfa83aac0783ab6a1b93d7a6ba3a257fd5278a8bcaadcdeae6c583a68ec180db79a8c193348e390ee2a650da6f7106f6318e4f5559b647498ddd2b2099f74d65a691b516b13f5a7c4c2b9bc2997da785840743980bd3ee6bbb8147b94abe4d9d20baf6337057f8fca2abd473e25c213c62e7b8b84025ef51bb9228db14863cde48a341674451a9b119284525726d36e99a1103b5862b6b34b1f22f732e4685e4b9ef53ee1b95c7573b1806517138f6be83863a17449d332e848e471eca703b40c5dd866ce97f36b6a8a16b8fdb2a72cc2ab73b6d390d9c6bb6748c1431314dbdfdf11f0b6f932c55d0fe4ee76e70532a50648d017089572885f498afa5a754f56ac54b54bff9afca49d91314d0a438a1a23c5e642de722199cc7f9c8386dac8bd828f743dd4376e8455f50abef123f5ce4461453f72e5bf684f0f1326b749933bbda7c78d4c3762e44a8779ab7fead652bf83fddcedd670bb9e77d43731b77f260aebd56b0daa413f93d65aa5c984624175e827282624abb45aab43d569f55a2ba55ea9f37024170eb2b01791bd3ff36561af4a89b8206af2aef229b7f90fdd269db0b96d7486b2e50b7a7d419c43fa4be9512addaa43beea6dfec673641b6b9123232c45b7bf12d5216f79587ad5bf4354a4e88891cf910fdb18040fe7af99286c7db10bf28e5ad55608c75c2eed8071290ca2be8971fb67866c8d716908fb781ce62bc9bd70537eadb2a4e7f7dc37a25cc1ec7a112860820856d1229e983ce49da18cb10d25028aebfff305dd58cd4bc91709f643d4a5e4482bad3edf01a3eb983222203fef7ffb67a843f67738784edfc42d29b539b817eb6f53c53694866103a11de8f61e78ab15ea6b4a39e404e279d7f7ecab5f710e29a50c814a217dc9e3c6a521d027fd6f5c0f53a4947de17a78049d94aa0007a7334cf0247679fee0014ae9cf1a8adc932597fe6cfa30434c6b2ac1152aa376fd59067cf90a461156941104f37a790b0626e8fa53f005c2ae03995c7d5a29a59492b859da41734db2201941f40df72e03f3d6f7fe323f323e90b0d81dfe304bd877eba5647c5ae55d1a1adbc880b18d4b6145ca0323c980f1dc26bd62d8baf7f90e925e52045f0b28c1a082bc402d9fe3ae961293c2e9a25ffabe5d32306fc99081c9c06460d7df931173193119b12e3ce5b8abf4f20295429d0eb85ef4080c1d182fad21e7ab5aa7051864c5bf27d7c9c39302e6d36f413e873fc21f4e72f8a5eb87fc199e1a0607f61be585154d77473ebfa4011145a0cc50420d3edc0b70170690881794f10a1380a7067709d95c4e2e3065e3381ee111e2fa915b80b51ea8b7b854a5dcd75047e5ab7e8e76b4ddd965fb6652a61232952b1bf52b74463543ece0b892a388aa1bf18399de9e8517820dac7caf4f4d7d2602281706b74278cd84305181338d6642c4aec844b3a0d715b9ca8d44a1af14ad7c7983a3a6068edd0294e31297262e4d5c9ab8347169e2d2c4a5894b1397262e4d5c9af4ef98370a0d4138e4d886d906c695cf4914980a0e9e92a51a29ace42b3f058b8575e6e11c538873c4d09712245850e5ca5c468205b54b1a2f1ba05b8657fd9c35b14dc704b12be868d53bca14859d21d02907cc92254b16d78f20f4ebeb6c5fc3530bf5b9e7c21f5b68432727ae55fdae2ef5a0213571a2cae44160d796b33d2984e101cb3d4924c972bce7421d55977248bf853aef2ba7cfd1afa18f19724ababcb9a5385f59d9935d5a440e5c56d7b8ee49298ef45ca8632235c0fb2ed4f114c02ece57fe3ada12b1cb05924c22607e0d7d70fdb3a50e9cc0fe6b28eb0b2cd7b304711d077db174a7573df4e1ef9779f475fad3a31efaf09f30ad6a8769d5e9075604c119785c23b908c808700eb772fb25c0adf9fdc4155d30c0ab7e4fc9c036400c5d5ee2f20c88d74d4db1a2a445e99b79f402103c2a11f8ce0fba1a72f05ad560842312048719a45443961b6048b17571438dd8a38a73f4953758d246ad51d9982996f4dd83312b7e0f93af2a0ff6d4a7e6be85efb9d2ffe8ff9e5d2d94be14965c5e6955adc236f54b2996afea7fa91824561e85d2a855f55f40605d94463d76bc904393afea03715f43ae862c5f9958b77a28fead5ca883d563b7c7c08b4106ee82447700a91cd20685f18415712e3b0ee7385dba318994c2611bfa325658fed2b79c1c0c755e900f129dfcc107e22aea3a30ac0db1af3e1c5fd1184c6cf3dd7fef2fe2b052cfde97e253e9fbbefb2fe49eb72c4118af0c616476056ffd041cc75b5e8bf4f45ff0d6f79c88138a2c1c95af803a66f18a1f8793795d68b986863ec0cd098f6bbc5771c9243303cbf7d46387fbed5daebcfc76e4aceadf5744acbcdf3f5f8658bede7fe1a987f7a705b0cb0bbf3fb5205ddf4bd717be9bd8a6be0b10b67b9dafc7ce0ee977baf7c21ebeaaaf53bbcf21755d17beafeaebb093424f7e26140ed6b2cd7ccec444b2e9109ead2ff695367a7477bb09065ad4ae3fb7c9f74acfdf7bdf02e94be18fd293de0b7f38c961d78fee49df02f75ef8e37bee49dcff20b16bbef7405c5deafbd2e77ca5d293c2d37cd20371e980ff85a7199e14d0fdf739dd7f3f6798c33d29d4f99e7b1dd2775c82619bfa3a4e9c8840df8fff851df811b27c554a9de6037181a9d30c63c8f91ee44c0b6057e9c15047c786405cff9542db12da1640fdef4b2fb288f842ae071761d9457a916ba61410172905c4355fa678f89523e9bd5047c701be015fc1b000767dbf007679a417e5486223ef3b257fe4eb07f6a3c353df9352a5e75202facaa76106240c2d65a5187274beff42590ad9570b60d78e14f295ff577a767d2518e47f2976b94829e9e2920c637c4b892e982efd1a9ee64bd7bcf367e8801a6d4e088fdd55ed5284ddeee41cad007af976d8a30adb4558ee8af3f2741d2daa72a8d8865b2b5ef99b4c2e2ed75788497203922b32117839cace16d7997c3fd78ce3f8e3ea672ec5fcb264a7b09f9c2d22907b85564a2b0590f8220b2244f0a303962f6842b010c2ad2ecf6236040551626a1f9a28828873420b1a90545b2f70a5861348b1d2822cb014f9c09d28fa41110b33050cc80a141ed7ef7123719965d707ffec663aa774666eb2ec4e1f33e40e5bd2254988f4b9adc403080b92b8536e554a29258f395bce23af2f12b7be286f122c602424cb700670c28a91b8f488ebcfb23bdf2f102a7d051e738ecf2331775cb7649badd2c95ceaeef7ee96cc51f13886619e734a77e77777663699388e12993e626068bb77dc163da4aee3a815b5d6984b7d5ca7ed9e842a99302ec0d076a79c4b8bc5e226a5f58915244143e52e9cdc908406168800044c1a082ea0a20725dd0e135b4b1135d0275e5cf1595c0e374c6bb230d60e79558ded891334be60c109aa00c2105f2c692fbca03b593c508b6e184229a5d40ae7c48e0fe690a1bfa8a44d9825a964886600000001e315000018100a888462916092a6691a6c1f14800a6b8c427660361c07234990e4208a8290310620438821c41042c8d0d01401fc7811664c3064a2f02d43d8cf11acd17ca051d2f09697e1f76a65a29007809ca5adc019da970e4683608457a0af4fec5d6c1188a43d024eb6b0fd1bb722edb1f6c4ea34c3952c0acaf9c7b0b6239bba5561d8031f8ed34d94d1872d3eb946aa6caaec022f2b3de9bca2c215025260ae93fcdd2d7b66d3f3a55d2780b8b4f2a9033a50b277a5dbfd7ea33c27e031c8029eb2ff691aa9f4f1b3042c7a08e2a7cd5660777df337d7879a96f8de7ad7fcbc08c979b7f8ca02a72eb2bd908dc700abc949ca0c4260f862aa20d3130c397006f554a0096dc9d563a37d0a100cd961b8f09d2e06aff42161c82ad7bb00ba211620a96b54b0fd8df2b8db6f1ed9cbe0590032ca621088f6120273712f1c77977a2272507e45cbb68607227b123d6cd76ac3f47ae5e3ae4a46854b81b50d6e5563a9b305fd70b622f5e36c4e2e2f6dc27e001c1b82f6f2b2540b08f053deb98924f0bb0dd164e2783a9ccad045b316df1a058798b8ab4889144f672de2e775ba64691c91f3b16c29923f8d8a2804a42a00fa9e3e73093de78c102b1758d5ff330e45b0a95bcd7867f7e146f4c7611b890e75fc3b98e7fdef0b25f7b1e642360f9744be1c4ba87ea1bcd162ec562e5a74fe572b2cfa41df494708a6db204ffe5ad5034e96c33d11abcc4292f07892c0619a81f954609d9b385b24d0e65d47379611dad61091f97d6f32285a2a5058790b69e1142640a8ddf463caaa6882e47a78614f870c7b79ebaadbe61e89b2cb0ca6cd0440a73827fbda0b0ca9543d4eb3f86e598407101a0d330a7b141733c2aab5a24ff15f51e9fc78a79b8670426228be84903a909fa2ebcebc89f8285b5c09a48f7d83d5e2c64e76bf69cc20bcd7618d71f69542d25565ae021a404b017b6ad0e2242d8dada0bc72ca89219ceea64cc98c0d96dc7a09011e1f3c78705c708c9e9b11c145a74aa96d872cbbd23cb480748a02310829c56982d4fe7cddd5c507658357c584cf425f6643e23849f7083f12ca6f94a1d454b956d92386bf9a078bc19ca1991496a985769aab6182e74a80ca8a94eb078bae623877844d4ea9aff937ab2d904b68209526185112b3692dad47a6fb0fcbd0a8c05011430d2dc2dd109c021a080980dec1b7a0bdba5e98a899e5e58f997bdc5ed6653903565185a9f7e65c1e9f8bdfeaacde628c2e9fe4a792804b3a3cada7df6ae21bc983c5859bf8a6ed77960bcca8004db117932db80a60be38bfb3ae3e8d7deaf66211e3bfcfd750e631225831fa8a9929da2628b202a569bed63d9c0444d965202c263795cb1ae1ef391a035c8361aa48e252383f52f56e847cadbf55b643726717be881bee8b2a1bd513cd0808666ed7d2aec6f408aa3ff21376cd0380799e69f36b747a6f79e83242fde08f3b92a2471911afb3f77fc131bf827236f4b5894e23df3f5201607641ec80457ffd236814b5cebb9dce2efe8247d168939507f91db5b5c0a366c60e0fb19cd14eb59160933c741aee6c0e78b61695d8a649be6a7bbaa0577cb6a3bd91a5c5136137fec1227a2c1a37a4f91787b10a666c42065a2764fcb3303a39495b66b57cc4f3ab47dc273a4acd7f819184d74579ab1a386031e04af585da9642865b0d3295a1ea840e0ff594d0c460b1c8b4b01b486ac162d50db0b3ba4bc6702bd99beec2f132276cf8c623aec82f3446ad787faafe04d01b67c9c1713e70796014096b0be84996e6124696263429ffb98cc831bb38fe763e4250f8a88eb4aef942b2b4909735a5423023324e8181cbf2a8e264cc5d8d6d175428492e1d69d5a600358e08f06748780dc7abc015890afc6357ee15896c18a682d1ca8ad70413701240b114698e66169aae5e9a04206f054106f54076587c9f6ca46a611b99f768a0e2a581124dbb13857770390509d72a20b266dae6a4850034f00c7d1fdca4eda8cd57da680d8ac1665f2b185a3fbe16051602828d0ea58cc1e9d79f209eb7057849a258564897f4967f0bd220e8e11cc40256cb34b39d23a5d4ad8a8d242d41d44d82648317d52318965f555a0e71101c40889fd352b1818c94427e6f91d8cd59deff9b2f377b4b929befc917ea9a365fd00e598443055718bcfe00a820611139916caa11ddf8332a509fbaa41bb611233dff25a83156a9b16d677f236d5c16a35cd9455d62a579c69af979de71ef321e5ddb53d3d52af062079dac91fb2a51fcac18ea52e16bfee4aff954d19295e72564ac8e6b8ae85c32bed3a568657f8fb2a4f3403acb00eca32208c335c4ba198565cc980949d37bf57e97b379110ba4e478e85c6c41117e7b9b098f217b2597bc8c261625b4fe15e6c1a05ca83aaee92338db6607d77a4ee8b4cb6f5b16ecbc154a647722b75e5a81b7e39ed444ae53b75fe2a44dd51477fb44b424e6c8d38f5abfb8c498ab77ca35b3a865a0d1caf73d2a06b61080e26d717189b172471b3c60edaa5c3841e03918465728321430475ba747c78ba2e4d295a8c7dbe8656847461f3277b8de83a2d5e81142465a3d7d1adf9f10a78ca5c738bf082bce602efb74368fe3ecdce911d6d78821d9064e53cf42adc829eef3049f4c9b92b9122812c5af9001d6116c111a981ec2eefe3d5bd15bd7c2f39f07de5b922bdd0e652c6806a0c7cbbf0ebbd717282103ab8afe500a34c9228f35de29ab0e82ff47d6e353281f6496e3ab9740746013bf28ec77e29ba8e568f708a0f3b03fdf7ab372ff2b9964048ac98d28b792444d411d7bc742334b12c4424748430bc5ddb8548ed349420b28adfb83d7de8701e38a72a853ff9869b435fd60cc44e56349b643bb5a685bfbc8bcdb49dcc60d215a23dea857ca57f0bffc4be71862783bf8fe036e5f02701cb81d390392f4cbcc440fa1184ac88794df75880fee0c3d76411f775ed02d6e0f4a1be46f830ffe8b73fcba06f4e254c3e5f2adb7a9ac8bd65da26f2da6dca447e0abd828003651289dd890003a51a640455655d46412563065b7f3bca288c8388ff9367fa4ca056822f07960594bf4db83cab3f935fa3d28011bc42680dfe643c1a55c7eb6882fcffc4a8160c7ea60661ddf824490c9ea6c21a6508b9984e683666a1f94cfbd615aac3c1ea42732b5ebc8535b4c3811381e1a585f75b48c2e38c50e88433821a9b995fa1efe16360614a1dd6e4269b5643d39ce247dd2cdfd41aab5bb34ce5d1b675ba7e10cdb3f3ebaf4a8b06e1eebac368842716943929e7da5dc8026623f4cf9256ee2a637dda30726a0b99158c91608a72ec9e01639c8038ceaf67d5267258465531691ab913c05b467e81c0ad18048c48bb887db30f149c101fa473ae36012c400b06d30962d38a955e78db80fff0427cc1940839b7ad9edd828ea52de15e9345bd3d3464c2b972a61c43032c72501844df54442ac26d5045bebabebe84dc53946a3cd6f6282fde8172e23b436e29cd321d75ec5ef54c616b6a1e19be98edd98d18113207b8817efe9c56250896ad06e11e1fe85e7f21231cc2192e569b3f8d55f0a1ed4f400e14bc4faead48c88fc5063dce49125edf1521270813875e16dec4fb92d18b5f81b8dcfc9314f59d80a4765ab1ef4b9dbc4a70f8e6664d7fb40eabdce5fb105c0ede0132376c0d4d7dcd8a172898bd017fcdb7951d54f322ed1fd70f5bc115536c0ea20ad27c0d21da0020b2363f666f757016598aa613c4e249ae949a1b7179d033153441179fa1d2c5bd0c09a3fd1af209d87625ed487074c918fedc48dc386073d886fdcfe65719e4a877be65004f0e50f4bb3075106ae8371fda145816dcd4aa91ef4151aed1306014ad515ca8549787837ee696982bccacdd9889087d4455f669ad58626f7d11d570cd8dda5b38dea32f071882c1f3d5d0df18de45bc7d540386590900f4eae74329849253533d8049d19a9a787a6e5ce27006d778badbc4024c31cc2504e19eae2da56c725ed976b0421b84de0dbad5516fa648461e4a9fa414f5619c1bdd72f0805619c6beaa8afa44bec0d28d10cfd0768e3d15cfb5c533f4dc2a9d7c897bfee970927e3e8c1ca513f43d69c75ff8224f0665b0abaf5f01904b6f8acbb0519f0040f68217e59834b14ee19ef06a5b5f61eab1b0973d43ae5eaa86fabd76e4822c51350a1accd96d693739ebf7d291a68d2814040f1422baa156b12ccb0d2c2418880d1a49ba2ecfb0c70e96fd4b3833e47348e066e93a2509f52cc3611285f1488e9db78131a00291ba407d0cd23a52a7d4457a6bd8eef329a94426a409006b36a434173072245b1af53e08b04545f0740f62160c23a30bf11574e57eb7657aa1bc9d96f9fbb4789a9ff9961f411af18cb9251e0ffb70d43ef6a4ac4088818427b715eea571e67acc7a1be97f1f0134b3fe0de99a66e02e60a690345116d88cdf51611792b4ab14239460087f423d1ce32134f65a4e35c94a06d10c74443505d89164d3c3a4c9eb3cec65f3719359c313a60b40956e342b50d35ab8f1239e252fdb20867793bd97c92b34169acecb50d82a1869dbdbd0ec9451215b5f563143a9d63fba136a5031bf081d718b7c6b96926a8933a8032b38b0656f4733ff97af074a0257d1663cf31758d906f3dba60918e94e9e9f3c67fcf45af54b23a8062d2d87c258acdd320c2206e5fc297363afc10857140e6ca23ac3cd5d2b6af880a6e73e4c921b7a90e515f019de310dc5e5e75702c7ce393241ec6cf8a9d51b1dce2fd16beaca0009105dfb60e3015ef7c437bcfdb7af5362b15bfcc2b15fa4466bff0335e7c6d75b6932a69b978aa42c4b9ddeae9f24d8007bc1c44544f1c8f557b2e3d385a24076793dab2913366f128001ef838f2fd620273e3c6039a8716cece0d094419e047244623176818def76f9c101c67e0850aa9b8ce30a0f962ed715ed2d49a09ba9422ad1bb3212be08d0536fe0d5287b98bc5a635b5d1c5aa4dad3d540923cbe2a21756ef050051bec9d29ecd8824f1fd6bfd43b605ee370c82fd6501683e3c8c91d73ae22e0cb609b250257e652d07183b3090ed0da6f548326e0115ee60641eac96855887e7058a44ab7072c1e15074dacb3b4f01a459ef45d2d3f1f6a786d85f119eb9f16fa532be0ace5494fc8a5ef3861d24bc78fa4033c717fe4832da54db94515f80d15a9b347fa79ab148031860da8e56ef07fabd686cc4bd5b67bc17fda2043345bc6596bdbaf99732cb6f3881c2615ff0c8b2d2b143efd1385655f0f1b02f29c27e42f6a0617aaaae8dfe9a4a0f1b7c2d434bef4bea75d5d5a9899045977abc62de8f847f8812c1b90b8662c36b589753233b72be27fd0f86b023602fd0511e8fd4552433a76a0225d94db14ec88bfb0617eafd51b4ac3206b23c394fc7f50765322aba5d0865e7158393c2d2a9e29b714f367fb867052ea368e77434b006222baa678b7e2576ccbe97a5d8701c212c80aec5a487ae006a5dd89f5f5e2a504325114574a24faff91a591054f58c4582edc2aa9470c5d3d0c21498e1094ab25112bab0c308c975d6348f240511d0bf0650a68c8a5bbbae8d54bc440d378c90f762a509d809c4d8c4efd792941af63b0950d026554e720333729a0cbd0f37236be5c653f7683a359ac447e8a9a2814bb5ae1211cf54cc321a1723c13a3ee074a6b6eb7fab240acfe92c40282826bed5e48358b2e0ebc3936dc407b52860487d0567c5676b843a097d8a27a8216e0129986dd942e61626809cff245e3f1d421356f0913fff70c5ce303844026550518d87bd47c34bd98ef4423fc8a904889d0390b7d0ec23372b0344156bf57b1e251b2694f7e69f38d3a0602d0cc8eaa7a95c6cb284edcdda4762b37eb0ccab3f604031a1d414fb468dd65a1adbea43f42af5d176f94adc6861e278236b5a43896240631f0f0d3e208c134005c67554b0b5190b43cbadb531c880e9e5b864e228eee23b24c0336c2541037517cbc93c33158bbe5143784643489e5c111b831041111aea82805ac5c2e07267caf10c3ca167c454c80aa729c77e2a160170a01a8b16133cd42f7900b32059350847722f0f21b6a97b84187589782ab4c67560974bc88fc4e3d7e8ee9750d7b616c636e6646e2850b3c8285da6de075de88a6b6ccd3d4848ee98a9a8ab3f5016d0ccafec08d8303833aa81b5e8ad66b2fee74cab7190a94daed29cfe605c80335d6e9b793cda0b389c418a4b26ce03c94abe6d96a1f7354016d5446a68268886a21f1081aada0067d958182aaed53da5cc86c445ff8b4a0721a1b2b857107242ff892eceb4705572cbe74c48953915269538d0b10568484a1db5fe2a0b9528252bbac613e42e5dec9e4ed8a047ff689f7a77115792c68b22d0bbeea8d937760c7296e83b81dc32220646a5de9aa4b4c4ed13ad17c1eab0671ad6a515b6c3314f10beadf7bb95ca12cc81a00dfd16d507782cc8645f2b598568012b665ccef2e022faff313934b059b77326977f7b8e538439002ec4e7bcecc51dd8c5d5a75c4e13034f32498004a2794956c466a9df144977a56cb7f7f9c8465f061c52d394452222ea2357e31d3ce3af6af6e3cd35a77ae0c9b626b6ed4400e2b2b386be2fa9d80878b0219b336f5768aad9df7a4f83223e2e4015d02d293fc367cb20b2bcd192d26a2bf00369ac147fc28b2942bd09e01230a6aacc5803553e2055efe81bcd09526174a8c985025e4854c0b0489ab96287460ea9c2cac4e6f13e1ef67869b8d5e333da5740128028bba301a2a10978a5678f9612b844cccfff5916e103cc84fae7aed28132589c4b328757e712a2a990122c12a607049870ce9a081e743fdd70ef8bc0cd82797f981696ded98b2825c7ad4492036528ddf9c08698c1846b70f88ae13f54303e86d803f60a2a3b7d2a5096a750ed094e0d3a3416fc4c1271fce6770adb9a1d6ba4728780a3e36881dda02904c9a03aa71490f0520a28b884022f4c1fdc2da4733c6dd4d5b1cd541618044e16013151be2b6e73883a5e7754324b101ed34785ca85ad78660ad721dd7a9af736ae4349d7092fbe09e0adf40d7d24d07823c84862ec77e8c06152b3b3ea6f6885fac31c39d073c705b26a53705f40aec646e3bb82792ee89f5a3afa0ed9dc5b809bac5277dfe41aaf59ffcb9dcd9ac88f02d252408268ce471549c3d5bae07757f95b34506d6073abc1e44e95b6f86256a5eaf79b961010c29ee7e552a01a9918f0cf78aaa465c730526da98486368bb25cff5941cc5f4040e5016d397c112d247c96603aad5252ffa07e03de5d0af04840043ac689223b3ad430f36bae4e416d520b18dd227fd8a06dc06002fbd0c907acc9cb76b298bcd1968ddcbbedc3ac61e678a3dc78db1da078543bf0dc19acb231e623600e31a8718363f8707459f409ac8e7505ea799c230c6f3f82cdf0886ec79c5bc90b48fa0b94a642132653d03831f40652c3023013caf6ee8e57e5430b83a6b849b8081952bbd07a0f54809a1aed8cb9680a63c93e9fe9dd01a82a80ca5d105bfe76c34e5558b608c9e0edcef5e96e21f16de8124cc93a6efc428047f6b9b875267d5ef23cdfe5f4a45c20aae9c3d09d451a7e1fc6cf6ca2dd32de46afd4810b4540d7e64f50ba8522a0e580819a40579477cf5eb23b4af1551ade2b49746fb35edd2ed735679a26b46ed7480926dd5e89568068d128014a2b82d8c21c0c5250bd37db202a5b3ea62480764b9ee17a0c45ad9d2e507c7e5670fc218901916fc80cddc0067181623798aa751e5cedd7c2f3f06f55fb85dd1f0c40b81c6b0087df2d0c49fa7a6d6e2b21e9def437c128f4dc2d7030130522a11413e4d10a983808ec56b6d264ea8287c4ccdf01569c6b2559c503c467354628a07da9b24ec159ef2cfadff5168125ca86805e2c82a4d9ff5857cfde1ad576f550feb9c0e1f66d39e4d063b82f73136ef193473b82ec4b03dba330a8625f8a2e1e51f3ef8ea91020674343e479d45a2e60fdacff7e0a069d58a05a0934a11a969085d227923598237e218c0256f77f976b7ad0639adda1d1dc421a750f7249fff30f3f2b1f74313664c316c204a336877e7aaa671739c6bf47a6533a9efda1942e99ab68f88575fdec5637ff09925e66faa61ef5830c9aabbf3b8b681ce0de910fa0729d4162e6b753e77ede27b62d09db00c9b780d2385e14eff1d9e4ef16ce5a8d571ba824eb56cf751e9c50f646f2dcee8ba603b16ca5c67509456f118e1149141020aa5ed918a44fd08505e94164ac77332764648d61e37839218dfcc5284ad73db22d9c6c418492af0ee83e3d93f01b34d7406f7c4eb944c63b51056c409a8297c13edb2fa84c21862682678661267ae69baf1e2bca9dc2b9414295e89004d8bcbc80a92406856081416054ac7cbfb0c9b85c3405d2bcab48786585148f937d21246e23a34af23fcce8030451dcc75ea89a7f678949a23793ce9842fdc4adef9c0358990a36a9a5aad34ef932d3653829f95c4cfd961dfc3858c770e26ba08499eef60161824dd718d97e7de2957ddb606c40b17387729b4831fea21316297c7f3dd0097b67abb586eae1709df17e0fdfb062a6468cd2ca21e696d0f7c5932b61484f5b6faa30f81a12e7f797504ec59c0f806101c0dfb009933fb5f19d489502fb0f447c2b40f1b59a7da40bd22c9dfdf5470165b0091b4411b0462d059fa53374ea039eb1456f78653c4e5c81b39a3043a1e0918e007c3b694ebf554d9840dddcd65ad044156ed9c0a3c9b8aa29d40c43fbd66f32f045b4a8350b6042d36cf577bf44bebc8f0d7a775a83393f9542ad5ab7d1c159cfeea65e2258360f9b4049c89a18d1e8c795ed7b320a66aa3142a7677792b078722e08504440ccffac1127a169937f54e3bc5a59f818076cbb209da243230795c32c67b146665218d4eede59389d22a88747a6b442392d2c1c18eb8928e21e2f4126e90a0fb9ea4705d1d9f799719dcfa13fa9159d318815690c32d80615b1720d4534c232a0e317e8ce3132747b42d7bcde511110e34cf1e499bfa8806d56fcccec20d8dba2a4863becce29a06d6befb857eb01c1407c8e8754d4503638ac12227b013d7a0c9152fd597d530cb5aaa0c64599b08bf08f94857a91b310a6dc12145d6f8f0de5b6132b21b0b080ceb09ff1e6061c1cf41aa8ca7dd8c1ba69ad275ca4bd76b1a3f6020085576a852303ecff9786f976a0950cae755e3498687ce3a5304e36baa71da4d5df3dceaa3da7e7814bd2035b91f8a04710fd617e29dbc1bc67078638367651c646f70e1595f3ee3ebd8c8910db939d091804432d993445c9f3c06e889f94c2a541afbf660f26af87cea13f088566ad967d78775754c2cac9f32a43aa478da603994ac51e9c54286f3e6aeaf01ceb2ed5daecce741759864a5804f660f4736399bbd80a8b61807647e0013a40aa1adf98585409d2f902dfb0c2f898e00014fc34d05e622290053929df36589c26c61b488a45e4cbf785b406a8afe47903ea4909811b0cb288e1d8658e84c3c4c3c1b6ac34c5d0bc6c20dcb8051a15cd67480333ab7c9a323a6f995e0ec5605afe818692217f3ab0bf50f32497ce512e70f0234add80b2c5c1d55b15adce3edb857815f530e9b56b6577424eeb57ece0f222036ed2d33827a1537b6ee767bbf40655150b7c9f4ec7d577728c55b8aae71b6fdc4f89f28dd0473e2e5697f2c81391227a6cae786b1dbcdb83442f9ec9ec063a277aeccc89e99084741c3036dcbb4da6a69df2346b1ca92554ad685e2df98e935c9d6ec813666a0571d749b5d8d357f51ca50ce3245d5f06711e93d49ec346170ff72ec7d5bd6a48ba2527c8c40221862bae82953755c7cfa2b05dace9ee53382ad0524fa6742ef68722d2e3f1a8f5880fc2da37cb708d642029055b37582e1023e9840b93f70734b066d4101b91491960645a51c6bda8406a876f1fab1a6c2d667a51a9c78e0d185aa719453007c3913526da71a749a3319a0e2fff9ec990a5028335f1a39646ce3ddb89a64e34f16c2675faf1baee5311525117d85ce0dfbe2213580961559f1a47626bd98b08b7377b91b2191c0747ffb85bdda5276edd5e896ab93aabc2d57cfd707821805cc2bc20edbc1d9a34a9c631c3c42ae66cffc53c8fba3ce6e7fdab84926ffe6810b93c756035d4e55ae6a2ab57a16b07cb169fa824998eb47799bdb32effc51dda935befff861d629ed4d454126fe1e8d65cdf12d7367ace3cf1703fde693364e86effb715d43ec8e4764488a6a5e90c707f3805d5a76b404d4d99a897ac6be4ed26ee201662f04ef28390e3c4c08194aee9f0816779be33f12b7254d9bd48b5c7c80643b2b320fe700bdfea9aaa2c89165f06afccdf802af2bf0871b4ea852657108ae85f30bff1c9051dbe1bdd50f9315861185c46f90196da1c6542d68d5923553759618bb342ce5715c74d7c59aba9d01dd8000727183322459d4befbf6aa43438b45fff627734ba4b7a88034f08067be23a006dff326c4ffad87cb437249fe524ef8a69a989299af0d563384b505b647296968c83fa91d8c679cde3c964300239bc70239502821b176ef147ea7a48ef3d87cf6b0caefe648a1bf94b3524ff82af66e822bfb6b8f1857a71e31a38b0ff3bce201d810b7b73f2193766e043efb390983b714a19b713ee6422f41642996e983dad08049a887f691e0483894927ef3526a272f816cd1793d70d3540e9281fb35bb90e91f0d64d70f9c935bb875a03fd40cacd4c044cd1a48e60d31f1e2dc2144ee3f68700fae350dcdb7e7d1c7f2331bd15fe536dc0f1fb2ce9ac20b86d057e56fb002b6e0b59c40089c502214cb65eedd05f5eda98a5b6c7eff51af5e07b9a997110dce3fa441f73bc6599a05e4c152853274d2afc1839c8c5cac4a40d775ccafa334556ecb06e604ee2d727022f97f03def48dcf14a25cd07b5e2f54a4d4658926089bd90095f950a1b65a1404a2a7854a0f4337e8f75065a0e91caff48828840a3fa473a112c6b7bd02358c480cd4eb033310553e10d553bb5305adfd1be278b85de2c1e21c35cd3d65cdc12f68bc2bd3b22cd9234ce71b0cac360e540d62025d7aaeb679f6b6b47be20bbd88e0fbe9dd7f86b004c7f8e61554791d03d16d8d2ba499cfd232df4650dc971c0fc40613de10accde1f726b26603bdbab1ada0ad5210ac70b0045214e867427640fd633d579729a86ca8953b4e6136bce1345559e75997c92257792c89be1a7d0d7f7981a16bf8ac69954b785f36ce1e510b04dff100a00621f597c8c32e179183f62b658f030dbcb9a8640b50bd4b8f9ca001e8f4d5da2b9fcf1e008ac9056c7aabba0787872c348faabf56d2e7df751283403c98619c7b17c4b83812101e2ff85829ba8e993185581e4f0b4691b843b92347181fa03f11b0e4927090d85aec0175e947986828ef0cdf28e17fd0465e0080dc273b835365f83eda58d10b4449bab03d139a9053a573260cf3102323f0f4e6d59109c9a82e6ef3fe9f4450e4ed126beaf317082aa750ce9f0104b2d705a482e4b83fa3fdef8367452313660711f456d2df5e3b73569cddb88669385a8522d5b89a4d8ebab47a90c0c0738512c0396d0f1c1e8d28045edb8d737aa821ff83f5b157543b25bed6ef6544a28f09336e203d3f653a97846e61196b3d88c588a984b33c25269307178b2ba55daa04c510cf4130dfdf63b2699b61067ff29782a12d0da68a2d69c8a379cde914d23007012f9891f716258e867cdd52c504d09de94de627d59a26f9e28ced4838a49b6e43941a49d99c3f502b9d6786710e5c271532e0ba3573c766e89de2c59aceb43c86eb8ce74c1678a83df900c1ce724fc189131279522f2875eb813de5cc0644ccbebb7a6e8a8df82b38363daedf42fa138022c503080419d45f305e02d6cc581791cb571f5884c4e67587eb4a49c58503253002808c6b6bfa3783099f6bfaa93202b2e26284f27dd4a31bdc00e49db053898762fb8458465ce65374319ec0760ceb16cd420eacee70835c1f7321a14f48045428f22c87d3f113e8f5cf3a976a804eeddb9658b3f2fb1be470bab7d230623e7e9ac6d56e859918c4b9ea20cbc6df8e6c533c2c585301d2e951511de4f3e9355b8cd6539999f52403e842e9c40544d2802b44af017aec90038cad169016d739404d2519e2a0863750f6463f08118ba46af7abff262fce378f7afad1afe2aa7fd9ec87aa23793fa818a1ceece1319bd8bf333d173ca5ebcca5047d6a19c135b0a14c893545a6687a13a532e44645022467297c812aa18087b6f2ad63079fa9491e253e803bcd30c71a69477d29028fe7298517da32fc584e11553468f1aeb72d93033ef668ccfb14eaaff1abb9c710c2c4b347ab4b83cb108d517159871abd1f1bf6846da868060afd2d3efdb740e8890ff8d7a88e89354fa011eb97a0d153f6d39773adfe194a61f1dd93f4fdbf32e2cb91106d909611a324a06db0b79b34e058a7a10b5f0bc6fb4c932fd96c888d62046e8d2b0f204526273f9faafb0cfdee3e576e34d4a1dd7e0a5fa3645d3349d56e2da5d741179434b2113c2cd19d169d3215628912b12f0f16f9f404e300a965acf0865846c7d0121dbf749744b1fe003adee414eca15867d82bb00eb63d4293e490f9278518a4a4bc3fa3d612d7c0ba50a8faac92babafc2bcac753cfc1bfedfb5bb6b78e6b2df241c150d92772aca1261aceb770fd0ead28894b0a8fc05c8966846146c44be9568429b16bc2e2b18d11898d8fd990200bcae21eb3e689c9deda6f0296a61a2eed01b8afe948fe6a6614cb91d9b43c0bf8732da7e208122f005211a50affeedc66189ee8c703de96ff58f3c5015ec22987c170643cc87745c34191a22a81d45a2f2698a5248e938af83d4527c926d89f6a112c9ce69d9602d259c7275a9977f0b6ff5f718cb820023f383b7b2f5a34d92228018ca7b98b8cc3af8e73425874a80914ce12a29a8a106e03c9b896c86aa243843ee075183aa988449410b3a8067e7031556262c6174eac251b09708cae2528eef7510e84eff9016e546e6bed13d5e9745b25e04669b32950323bbaf1184417020c8212117dcbc047e44aebc81c8b7d9bae4ae8671a20592630c4755069c72ab78f7f9f13c96c10cf25f3cf72ac4cc986509bbd569be75acc307e3577f52b996191d22ac502bf87f4394ce7a8486c83fbfc08f437fa6396240e3b121a32720e2f9547e43901eb00e5e335dec2c7e400ad000cd33acf568bb709243df6d6e350c10d801a27397377746f9f334de2afdda9d25ec341dc9d13acc92a766b88b1ebc7a4f4b6df90f77753e2bd231339dd6354ff62dd36c3c8d40c04e428b5ba7828b41138310273adba0cd6108ab01777c4aff9fb5bef3709e49bde5064827ec1850b1f6720c51c7927cd09ce405b6d2f65ae94d4c39951c1352df10dcf298ce5b4c87fa6644a9122dd4abff89b36ce41a3ff54c03be4563a9645a7f907fbe40c3ed41906281eab80751d6dc7e56b58fd1daff1a7d248f363aaf062e4db47ea61bafa06ed9f341d5201484134df12d45cc1a0297ec67706294f16de476645744d555ac503badeb9fe515e93592b18b658c4af5b93d3f8b4b0d7ef3ab19beeee27749503a51f69061259383605f27c2ce10564d9cf1513fb64bc1a27060f192f7ef11253b9e8e9de6bd26001bfee7837b36c12105995a5a832c60c0cd09b64227d8c87984317cf59f4843a7d2732e57493f44e89c1c54660fce68a649ccef3c02b89a2e5aa45dd9ad5967abde67da1a247961ad22f1bc7aab86a99203201ad47fb9258d4827979983210d96350bce2365efdd6b8895d4bc6878c1f1ffee2681591525f10a422ec4b1e07a6fa5d8f3594586eb8a8871962ee5e2df0e3d30f78b5f880cbd7c68235cf2facb0e8199d9c6d18ae0223b23198d8ac667aaa171f961db8b15b2a5e48877052aa57440493c0156154171364591c27ee2e0cb65d06f88e4fbff642de9871dd7f6900220e618bb56029fcd2be1f5af6768b21f2eae202465c2f78a383521a99487ff5b20873adc344a82a5fe6c6892a02e5e23246004b6f4b7163dc279f724f6439e57f31866d570e6e7d4d2b48ed991555b94fd98f0cfdbbd97c117052d4e714b434ca6f32e51d207bbec918eb1249832d71239553742efc5a07036743b030b40d9f12768a6272551a3b00a65e7e1195ebf80eaf2cdd1e6b85a5232223bb9550952376739eeaa0b55fd6d7245e8dc10c1df9de250673eca969ea2ecb3ed022fb836000fb10e949f007ff66e2dd5dbb5636b750e885688941c21974a00f1a7263acd741ba318f9c2773478fce2c4afb68a84ead0d59838002ea2d218be81055b71a360a35b860cbd67f85222a5a897a3eaa000283dbf5fe5e75f51efc5e947a2d447fa681d2a595ec4f8f34fe9024d81ee2de0725c12ca80f72f967fe3c6f7a0738b7702751ed2e53e76f8d001483c1ee00709e6818e6d56a6cf3b056bd08f37a15f81857cc9072cb5831875d557afc30cbf254e4741b06696d13db8b15979ddecd99d514babc3a7cdb4dd0f508540dcaeebb1f3d944ca0dfd8a9b755251415fe92cfeb5b2f3ef645f94969745e6ec4d4f080eebf9187f3419565ce845d558ce06dddbb480bc864db2faa0f997038466b4288069444f240dd0f2692c8dd22fd4fca0aeb37c7cc55e3dfb7cbfa7a4550cc6b718e069469b07f82459595036322c28ddadc2b2297e4bce0a3fa4508a6f83a1db45f15a625d33d4aba314b4078c9499be7c64d585ea2fbe1a197afb6c02008a7c8cc4c6d91799b4855f16fcf018bb0e9440eb3a7dc341acf33bca88bd22cd025cabd10088ac4d74b6d1f85af012e6f27b92d5d591cbeeffcd1aa2c6d17ec284f23ef0f7304611cbedb7fff1d1cbabb14fe8617f508793f1a91ca69ee97930ff408be84c373ff82a08db68a546d619858dd94070df7122f3b6b2b09d6da2b62bce33bfd4ebea9403b400cc171954466076e65a5253299076353fa985d4b8d282f7207c136c8d3271f335690263c8d99f60813061ea7e9fbd681709e2326bec50520fbff18c6e7469c57dfe56d7499ea502f0674e171b0e17e915067ef21b6ce24f871ffb19a953d9c36fb1ce45a153038211794201b4b237a402aa3b262f5ab3d320322b304b2d5062f5b37318737c0a93da4538704b7738c6e139d45923d7a403d47c206f181dd72435d6a987041852569fd199433fead1b15d9f8f2e5a6ed616cc388e0947cf036df1e89bdff2c7459415cd3496741995d86bfc70ffeda43e38d2e040916836d3b4536e02ef08d3a39d2c8d74b2e0c9b4945da13c2c4d93ec6794ea41a5013d474c39f26a699d716ca741a291788e803a1e606b30081fdf636b9cbe8584bdf20dd797aa734bdfaa864a855af60429ba7ec6043db099d2ed8aeb8e6815375c21ec5bf97a79adcc0647f45d0c0c4ab87a8bef3201f7539ac2c6ce921a42ef7683c9e9dd44bfcf5550a07977deb5a1685457427e56f29dc04fbec2d1ffed786279a31c3cb1c83e1f5731abcaed24f145ae150010c9d043aff97275e114d76d463619539e617a207d329289c04f83f222c89f8263e98a3d92ccd79bf349bc90f1e1791f11113f6128210052ee0d198317a98074d61094d68c1d797e95dc5b0ae632d40c199c2897641e83715d55fae714c47110655415e3741bd91023638a964f1542acc298f6a953799984f11e09ecf944ff334da633ac6d7795df926a642e4df006bd8091a8ba5ce1393682f027b4f665c1c6cc091be2a654f1f767ae1b89b85ad930db4582eb3458a61d27c69ca4bb1e953fde0db48bbb7d35bc4359c1b89e4a078b2133bc30eab535946e0b39f164ff778e37931e10010ac6632b4cb2906bfb7307b387fced692f5484a780a706946401699fd9c7ac0a1f47c93bf4a631c1e711d9f50404259e99bdfab73eeb246c02a50bedd292e324032a29502191ba1166a31ca7a89fe300365a5ef65a05af4255f2f00d06a223a0d15268054ed25d1a7e1ada5e2669b840c7fb07f16956c3aa89985727c3073eccef8b31da6033362a82d30178649d2c30b58a429da1cb424d614a0ca8ca7eaf417521a7d61df265a416668b01c6633fb27adf3678fdd6ff15654531bb4fb19fd9562a68234001e8bc264532debdf6457d14f055c220e4f8e46b11d806768eaa0dd4d835dd663212cf7220b230e3ab3abbac1e3ff0b549bfdcf30a0e5841da41b0fe1dc03a3a35fcaec477626f11681aec6aa7012d5e4a03e321f119730e74fac3ca1221d27b01743708e8b7267672d8342fd6c83e70bc321c7848dccf209acbf8d204f45ec0a5317b97d383845ae1a7707ffa08b67683be61d76305c1f3268329b30d2f4ca5462f7a629f6974e5206f613e4c522d59e4c410371e17cde3c051b7c134ea3a92f9ffa7a6e8bd301613b5fe799527a1fbe8342479e42c7267c9b645fdc247afd64827e51d798bc48fe53f8262194d062adde060723960bef922b8ce564a62cdb16bbe9345454127d966b2c6d1db60239bc8c09d2028754824cb10b4696b5ef93e1eb7173cb40bec08613e486e26b2fa7c5025081ae42d86a5ac59ed341411d9423cb290f7bb0cc948415fae55bdfeda9c2c0308ba966250db8bb9384e3e2650037ff899b1f5bc830f3a142d14c899a632c17850fa6a58e39d5951ba0b691132e365858b9aebc709be7da8c2b30b2293206a267d2484c707ee7a7398d561c867edac1e50e74deca1491d71e2931b3440590fd3017fed49404c5447cf2cfa87a00cdc91efe5b2df7dbb2b361ad7fb2a6aac9c91c8b01afdb3e7b23b292c2ca61ff11766a0d8b97072fee1c611798e1b4874cffe24e70a1361890aaaf0fc1fd54e327e37bdfb1f667f14b3b92650e38ece4a829f2081b283e1239e7e9e404dc60311d5d7125e2c482cc0cfb82dfef2c3e1ea0736f7297aef345dee3666dda6b7d44588d5d761a0de0a9937da6a2176473748aaac2d08f494864d1f0612e3039dffd1fdfeb6915c006809641210f56289b130528c4e379d28417122957e3cc55146997b38c6edb49634e40be989e4276d243aef9e4512a111b7085dc3ce0d8c8793ef85b7d72f1b1388c52462f0c57915bf880df1eed1b85a191713c847439e2f1b57120d8e8173091171f44480370754c05354c88423cc4f706872236af7dab3215e5a4b6c29b728f0d1fd55e2dcdfbab9bfae2b98a3479196af298c7b8e67cf0e48930df222134f74e6d461e407d1d27d6e393db54a5a2928f5970c43ffb99be942597bc4c16f7c0e681bad46a97b197cbfbaa5c310b1737c8965dc8e07252107d1baf740f3e2d4f0b9129f297d5b2468b2a99d06b418691db88fc063c539d0e90f2a9ad858e70590d528a0df8ab8cd75d1bc4803fbd0b1cae1e862701f83486ee2971ed4780393c6ec51de3e1668badaf7fd61c7dade0c5a8e5d8d4b2cc79b04a2cd327c39361aad615be43ed393b026b3431edb195c81c14a0c71e371916cbc1c371b4c23aa2b99ff7e56dbce0b2311d1dbe6dc4ec682495506fc0793f917e149c3171556179eb5fdcd205587a5a78ea5b7919a08f9b814fb5c86bb445ab4d4ff065d8a5bc11d9efaec4e80d2582e333d265e202272865fc02438f9da1f22b41bbcb3c8306b96a3f1de4e9d3b5a149eaaa8a8a082bab189d479a2f916c5aeb7de58d452d39e7427705ac4b887683a7547117f9650b1e900818864d584802e7372d5c8d341892959211dd8f79b0a4ee659d54b1d690c5737fc009d93e4ea4a642e5131434db6301fb2c889536758c6ee74aa5c25d0375c2f0e94e376031e6fea9b0987a98efbd34f978049558e4656316e5db694d12af21cc40ebb5e77a28615906f9ab802ca30a947084d593ba482d7adae07a53b3517ab724bc7ee48528264e3d56ba426acef0a260a9cd58fd3a82c1406a1cbb62c055e77d2b41414d84e004600d34ec5e9c1f781ce2cb98c32753368871ba9292d659a2d2004468a145426f0e431996714131a27c0a1d43adb28db5441d3dc0988289bfc267ba391ab5214b254bbc45509d4f8d4ae29b3ab78f2bb42af8922be774fca94bfe1bc76f448d9c737f220572e420f91b28eef9f8e1c0be092dd4113c358cdb5ff181d679cc779c996541950814953ea6b5af1aa292920d0209b89df6b99ee1aab5d8a8731e79d524a44559195817c748881419fbf28e4e75604f4e1d7e58fbe24cdc49e923d544797d6ed9d22b9de8517f90710e1b53e55a5fe1e40b9df9515e7d19597ebd294d5ba5f5150e3ac533d56cb81b02f8f3325df92bd2b665609ac6f5a1e1be56256e9e22b312c69508c22afa8a32333b7dff4efe2c1e0eff8a3a2345d15cc762911f84df23ea086a569074c96b07e09aa01a5e0db7a811ed7358b6e84f71b880bd09621e1797b445550a58a9389863c36c270826541d24c51793e01c64655ac6bad5368fbb550c47fbe397942268b4c86e67af496c71e31e2a2d390db526f2fa5837fd283e8d441ed0f2a1c91cfc6e0b4ff84c1a7922535f76b9266859693701f6e72c231262311956668947091814905b4caee7f0000003068e90210d26f4135178f90d0118b2dd76b51b1c691cf57331b983d19cdf32e23b55b4c91789c02e5dc8c0159d0f2909a4a0c99aaae23c4bbd2a4aab01d16a90d7b2e48403f9d1d61876f206b66426d75b89e30219425ba098bb73ea578d2b4cee6eac1134ce17fec397060ca46ad962cd0a125b492308d7a208a71a32ab9ba76e9e6370472cda85f6e932ea90739c977b2c8e4745192ae571eb4fa892eb82938c54ac44acdde4762ac91e8b184b120fb9d392a9b35c8ad7b561bb2748ae0acfdee6cb46b1fe64577d0527bed46d03f929a283094b0f5e77ac2906d45c09f5902f879320f4493220712168dbf72cf916f47f4fd8630a650ad099cbfa737caa41789234d621c18a93dfd54d4a4f429297ca9cac060c6cf3130b93563dcd64bf31583925045663bbf1e10e10c7623d4722242c22f7a2f4d5f7fa644d7536d30ee3c6181e41ec10cdb5d6a458084d77dac5391ee0d3453b1214b1d719647c391100e2c24ce1fab2a0eb69794acb2a09ae5bb4b148c22856f8fc5d7efa80e1294daa264b297db886eb02128e1c9b381cc697a3bac432f30e42d6435e4365975698976f7ef34a4108fd484effdab9a6ed739d3ee55432a799290f37cedb577bb0eb10de96ade83f691f583082dec415633d65a05d27afe4abb872ddee616faf0ce102cb983a734315b68a96e91d5620652567ea6929e79f5d1b6107a62908ec4c8182c31bb0a52f5553078467d95a0f9889ddf0a44e5aac18f40f622fca6f21e0a60682d9471d130148b348c42c6778d3cc316d0267fc8912f0d4d147f446e87f7b0210b6da76c452c5fec7e88f38630be720a449e2299921f06b3550172a4bbc646643935d27b5465043a7a7f66c1f408a9793bf6c036d8e45d2e6df11feea7e316318ee428f2db1124393b23db81526270f00744ba5080d468fdcc8cc7070932813fab6f812bec8c6e5cacfd86a18377547921a2d36ffc6ec641289a1c205f428e104f670723bd25d1f54e906dae55ac497c4f932f39b961d4d50be017e280b05155aa5a61663a6909d22f1e674ebd3febc94e3e36baf637ba1732a2dd0e0878658a33a07dc9aefa12e0bb09b558b19e26c753494779ce9c9bfd0ae52ff08e08c6039742365695e16c2aeae46450704d3e8466904323896feb84d9f91f92540fe064a8ac64d6c2d5e812a39bef1999e05d72f6b66f6addd491b7c8f9395ee55802ba98e242853f6d3ece93a5017cdac1d6f693275c12c73c6aabeec61f5e0dc72934433a1efba339646c67147861357fe5888fee67440533a5ce1584489064cdba33771598b7d23bcddc543ed98fd8cc342e575a1e8cfbe3d769b0e860b76e66380eeadf776af77fc356fff24460469aefebbe0298e9af446ab6fe08540b7c9f8ae369415f8c68e6e850ee3e91a3c4b5acec034c3d32f6325f9b5dc376373c235fa1557bd781ce60a3fce5154b41b9ef0434d9c6b8d082f1f64f162da094518ba25d91ebe515dd2aaa1b6debf115f511c0238896431e4116a08af29b06efc305b88326e3f7152e85ce670d474b7ec597c472e4c6cc9df81dabdf06c88f38e67ae2f9c49adaba80acb8b57ae8a700a3bc9bcdd05c6c8fa175de5b69a3ad2eb03635fde355669af49fa393f21084f89c0785a118ddb859484c5463ca1647d57383e9a0104d7e363100de9da43762f262e61a2830bb23b58987df8ce1a3bc80f6e84bc25db304136c73cb4408093983deea5eb0c2e3b44456cc6abe1f1d5d6e959860e2bd0c4e8147d80f5006a8624de28a301b3895d51720b0e0a880125075d2098e364144ad741f265b54c68c6b1f8d4badefc5f34d5b3b3a32970b651d20bdbceaac8d91bc7a28e3e1d1f4a224262ccf5e9800148702c26b9458a0106bb39dcc614e6a9fab8a644efced4a2d77e95a498e78b73a9ecef499121ca5cccb0ba0f8f1d928c3086d02b4430a75b2e38f84d591c78589f7aaaac2c5e0bf6280569d7075c182af7356f64f5101691b7c85d95c4346b021acb8b490da5a5e14a54824d13cec3193907593d0ff4e85694910738692075006298f61ae0d220f17f3f05bc2913ab76c443114715dce685505d6541d4e81252ea2a8f295d7485e0bed3bf624844693ec3139e32323b49a8b2de9e8289cb0ea619835d4ae47546ef410d4691370d8d524d7b94a8a44d34a143a00d41ddc6268eba5b29528c930c84c886374746659ed8313ed326203b3616c23ea7d1b0ae3438447f417da6d45ad6a54be3d8599caed75a5aa88e6577d520b72bf2e398c491318d3093823e25a70f326274ecbffa21975a88411a114be2a02018dcf96679f902cfa0b4de854c65c252f2f5c4f255176d3e9fd75f44280822338e661701cf21d55c7c3303b99d04337ca6875f1a20d4a57ac0b78fbd9889ba9263aff9883b35fb4868a1b9dac762a45db2de77b67b89737e15328bb543a3e8dbb99cdf217c5318c9503f91041c5c73a7e578cdceb1a94dd99f98950ba1976c195905975066846ce830f92847115d60dc93840be2b55c23dde2f2086520b6d548b217614f12c438166d5d3ac3ed6276c98fae79f6a8c5d5df30d2b1705cade2ccbad3285e9d016c1282bda8172cd940cd6b6eab06d3fad1d2010ed546a368ea199fba37b0a56ec99c1cbd31553009c15b157122cf7ae1fae3457336b067a7f4f997347b82bbe091edb0e2271464f91cbe7c343abfc8e1345b7d5da868c9409292559ba92e66752075d911eee20c30dc22bec5ea51ec1b55510ac238c546d0244fbfcfdef310444ead9710720762f10fece53ca804507f939d77486aca63a4e960f29fbbb6e4e9b5fe5e09486d19a9b75b7b3cbc19b3f5c24d411f2abe8d7f9c6e289c65755a20e9c52ea0f9c2a58f97ed9b65909c5e61a9e70a28c65230400522e328eed6dca99d4d66bffa9daa00700cb9dcb81a85981c1b58ae0cfd96679b8df2730691821c6353cd627ac1a1946e0cf29f756b7cd32a4c75a7c3ad8a7d50a6269c74479008904794d8e7bd08a85df387cfb9681f7d9a879714d1af8a6990c64120afd652fb56d49e545082ab1e94be549bb2584a90eee9324d42e7f0a1ed2306779d9a4f5e50ecb20af2ed82688a911dfbba0be004b822d291c48ac352a3850a848fd37dc13ae922c71aa3244113d02cc79bc051a60041e61b845e140042a165e9ef869df734b9e890f0d8f315dfe73dc3ca6ad9881d6358343a539baf1febd6b7130287283410dfaf378b2d6fca3c9b6aa367757ba7df4cf837e78cda9518f48fb940b8a9ec81a5c89ebdc7926e47e9eb33ec6b18e638f6659de5910cf7e65b00394f4cf3763e977ff2d73db7fa0eff26d9f68f4dbcb1fdae73095121c7e879a176433846c50a1e24d41d0913369c4bd7d9b7ce561e6745d79b5a848008aa2e09bf395fa9eb43e011b5cce88eaaa7a888924abfa4c3db02b6c2829f0f4c6d25c110f0d1818df4c36e820bc63e91845131c352bab1ac7ec8b1d52d2e37e5f351e124ec5cf1745b271ddff4f8fcc730310f0b28fc917d112c51b9ad1468ccdf2ae102e00810e429384f0c0af8aaabf3b6257a87dca68a7db0068d1d900845ea9cb715f60bae116fed931e2c1dc5ffe63d0f8b51338ddf103a8a879755acaf4d95301d72c638cd32f23cde0eed0c57288c84df583846e0d130f44723c74f770c53dc39072d35fd1b7bd00ef9f698512ce6fcd6208e237849b347e39644457cd467177b2a7c71b66d2aca2773809556b1dea658f6228f926b976f76cdf070df359273b4286bc0a990d5223dfdaf7aa312730c069f370e1d26ea1752cf33f7870e2464b24895b2a3a7152b25063fb60b428014b0725371068cd8edc9fcdaa5b13ce4bb47147a67f10ed4f9941536a0af8926e12cf36296d784107307cf6a0a01c02f093412b7892589512821287fb83ded79541827d85aeb8797ecc11c1238726e9435d6b07f20a0237b58e58900ddb744a1287af7424a53991a7d81f5eb84cddd07d0b586477e1eb142f5c44934d617850ca5c3a32900494c905271d17d5148d295a7f2c3e6779ed78b3616a445832d10d49711c6aaefe242e56ac3497de0bf987ec5014932ad15ecb84615ab4f5b2cd8c4996c360fc1f7e4eab4b2976d01430c0dc59871f4b872d8cf52e3f3c2d5959eb9472982d087ea8ffc3eca28097242bacc8506fa329285db2b3ff1226116a534c4dc981317b92e9ce76f6630f1e55bcb29ca368784a8f6d3ea670391f3742359bb77d904c96bc065a129450ed061366bb204b4b762e1dc877a61494bad02c18da91c62b1ad0c0f0441030ad6a5d2eaf9c19425084694564b3b1425c6be823c7fd9055efe173464a6546e5182a5237b11b51ef199e5fe3cb527c52abcb1e258b4eb291423aa7460308e72c26bcb207b39153b90a9cdeb0062090a0ddbe7deadbbbc083a0517e46c627bc37e2355bf52ecbffa00cfa0fbc6ed5e5acd87dadca8524163c9b80c4f7e19a6a90057368b1c633bdb7f5671f0bf61d9bc987228deb004d3bbf72f159933389abfb838dffb61e7249e47cd8331a5c2d7dc3860a46847dc06f34c2d7f74daee0cc48268a5f8c8c3fed6bd8b3dee0be0fe34a5ab30d9356f81743b7949e42d1e402407e68d2f1dc98f8c26ae2a123bb1312255345321c6271d67eeed4c8027bc1b4d06ba8c31c8b080b3098e6788da583e0ca7078553a506da93962b29fbd0a5091dc4acc9a43292a8aeb235f4cba45e460bb666d78d651bea705e242bdf95e8eb9e5195cf01458350d600a0a42ed5df2b13e7373202dc3162dab3a102696d572d0673dc11f0a558e4f95b06c59596e191d3ebb10d4393901ae1771c290e72d87d84eb7366283e8422affa3124255c435172ea19e1e9c4039ddf7f91e9681908987bfe29c512b512abfb46fa60874f4e4a7d98a3e7e2088d0d2b5dfb59bcb6bfc1648b580d00b17c7162780cdfee83bd148e8da0f30a65d7080952ae955a276cf67030ebe838e0eccc1812fd94cae03b0fc636c5b119d2477aac5a8fb87323f6ff03b590bf9853b185d1fea972345230df361f84d99338f1a741b4e619192a512c69201aa63a6a58cb6434a83451f11506df211eeab0d873f41513e21b3b1cb7f84cfce51c145d6e442711af22be8181d1d3b8ac2378f8ca9342cdb8f9dd8eeac352cc5a3e58ad7e8ebd7058e14d08b57a03c007f6dd792008d986f6fff7ee7b210a1f9028346924e3df67acc5bb37a8361619768531c8425bf95966cd646c8108f16dbe566243259efd007daea9cd0d67a3f701677e636a823689595f435fd23c5eedaae99e6ff715bfcdbe54af8e3bde2d40d4693fb85d6cb400749d7341a17959d885ce4f29ca04a37d5188c19c7d262c94144741d748288b9e7ac670094f5889b1e66072b639d9d3c61b2903870c09adffa6add7602f993031088ef5016e2643602c0aac8c0be068d93194ca928d19271916a77f20ece75091eb75240430e30bdd4c5670a92d8665a8bb75ef5fecedc5dd9406f682434ac98143a11ca1767a167dde955195781428d7b4c8e615a8b90483cc0b22ca23fab88682b50427e5c38263705723d3cd4fabb352a2213fda28f0f11d1c1a2e7f1179ff4d44a037b88e54bb1086e8090c8e4f9aff2972eda2388f259c52dd28b63a499cfa50884a3000c98011763893ecb757da09e2fb0c8590bbfbdf352e46ecb3432e4cf41e1bd78cb2a24c6c4d5a233163c2ebff2aa1e3acae6de74847084433dba5bcce7a04480491ed723fedc2f0f01cf6c898aa810c48d6781cfecee0c88df363785f1e098afe3b6c43f79b8a1a72781aff630135a59e82dafade8bfd00110cbb60f83cbffa025fcf18c39bf05afe3376c6d8a6ada37ec85cb5a2f65e6f5fbcea808a5ded399b2ebcaf1f8ba3366ab2f6496aec4ac717c7919ca80c90190d26a51c948603294a515af66ca0e4717450b318a38a3a472648dee46fd1bfcdba74c093c5d469898c9938bdc12976cfc9badcaf04160d9a84694b9e85a86bbb8a65b557f15104c8dbe7aa15e868b38416b584972a7868311bd992ef4699176f236f2ab4c631fdca8cb4c4c2738ab6f429f729fd80626da8036c7466e1e268b88d2fe2100a09ba350f86bdf85a393aa5d148e891f1dcb0aef5a1edc51551c1a15417e737c5c0f083ca83e09280e4bee8e74672011c3ddaf535628415fb9e49ea7e1a4fc1cf27ba755f46dc307376157f46db5964d77993993a257f55ac0ebbf923399ed1eb47e1f7ca93c008ecffcbe09112489f2b3a440c73e24c2e1b5d5f66169df2f45d33572ac26322ce374eb331757dab279c10c485a0b8ed8ae863044e29e0f18651516249d78154b3c7633febd02701676d5f2e1d0aaa6f4ec26fa54dcd009417ca7f04a56897976e0d8d462b5a4a83b2c057c114a7960786398dbeb14f9ef5aeb3378e946f4b1962f7457d40a557c7672a9dce69ffa067c5b7f94b4f1ea01b8fdbbbde6b15f9dcc50cb043e5919839f97d87e7083925a6892b44d405bf47d2a77a8b20778b9ea9a133945e37ca6d25f24baa2bb8cc3e48097dd90374e12cc93d4a4fd303be25bd564eb095458cbbc6722097c1f71c168d0f3db41af877fe4f2f5f904430975b01b35899bd339e28aa9ade898b8b0ab3ed04af33e0d6a66daa1f03d59e6d67319f83ce7104fda8d4ce9fa2acadc163c108fac3e4997eb899010a6316fd36ddf6beb667390a28e5083c6da9a38646fc81003a70ca107aaccfa668909c06438a901c4a8e493134121cfc6b91a4a9cc3cce1bf0a1483de9b9a981a471c509221b0bd426dd20d38c8d0e20bc1638dd5a47f0310963a1af98f87289e4568e0b6fa2c1e897092daf2466fe9d8448e9e83572a3e8552ae942f5db33f97fb8e860e569620428f04a5475c65b6e9a01392c6848724912f8b6b85c416b2261217847cc56d43a303428867f95c6b755a3cc4661606979e9227d0b9e90f6384c4adc21c9e3def3614c179f8cfa5575d4a8b17615f77ba776b3bbca98821ead51a967a20cff7f9464fc39ddb03035714b80103caa5713467f8084fdb9e6f2431a3a8a3340f450a2e64aef381a6cf0a1b4401c011763416c45b6250917b8a7a608963125199c697aa80c772df22e94d9ea2ef973be320e7d00de26065e4ba250f3e353844728a2f30c32d5aa02f7d182605d512fe48b75db1d9479524ea5975a007be9ed4fea2db136840b6264fb62a752e53c2abe3c5cf8c34f77608e6e87a9b2f548b5a0fff9cde301891c4a2e7561415565d1fbd37ac6cb7d6fbeab2806e5d02bbb43fb7d085d376c261c62432c0ba652549a7a44f0043854acf42e33aef26167e3d5b8c7bd55d3def6fea56fba4da61b11ae576cb2fa449dc214d1e89bc673aae39842e66d8f0487debff8245ae9567e57ac315f46e7309ec4d5862e063a462aa2948f6e326ab2d8a51688e55b14fa28bc59bd8a5d7f0253ee7eb92e6314371e8bef5c4efcd82dcd6605c50dc09f7d179304b436358dc954be6f4e2210bb238a9a80419a78712b22440479491f06d4095c251f4b8feb3e79b3c51c609ebe059fe1115ec6c00c15c5b0a6130fb9f3b37bb36c7dab8ccde9862be45f0d81f157eac24d26b193fbde1cc8e6b5372de4b52262df7520e58ebe1393f60c7d390e36fb603ec656e319b650c37d42261843c8b911cfcf435def9c731231c7da615cedd21f31f3f7b6a421753f18c0467ec3df29b6c1bd6e468ef704a07fac3227ef529faf92dfb2195f4577e52b211595a6f18e01c8d38e30101d0d6dc68ef093b70af13187c0f6c552a2c756ea580d66be899b48d3c43b2c54fa991f68e9c2987fa35d7153a8448a43ba957861a55dabb0231d2e45859d69941706027be40ac7df8d2f84367336ca3322f67b3fc07cc7a0535fe4f40a4f8edc8e9dd8d68bfc8cd353bc6a66261820c3a5dd92c1d02992f1bb510a803b434897a36e462c59e251dbfcb93d79d99f81d4b28f82784e0b8ed8e42466177b62b78b0dbeecc881d194e43b1980171c52cab65c7b16f9d407ecbc908f51a3dbc11c673570c7c1e1ed91edda02de734d92b708387099a55a06b56725821ca79e79abc506c0b1840ed2cebac11c613ad1d5a4f4b2cf7ed97a3340f2bec30d70e8e7f321be8b7bf31c7ea195f7788d8dcd4c9d3dd052c2e070fe6c85837707acd6d7893a05d81eb04811340e2f1559078dc3a86a60e3c40a5d8c3c69f3bc5bd2ebbb7b493950c82906dfc24ce7d2fbfcbe681d33ebd0058bb187cdb5a8468350e7e7b96647e2c45e234414656d1bf5ff1b7fa962b81605c215c37b4afab77500716d468d402b2532af63ac7efb00fb7d7f62265dc4fa3a111b1c02b8e4d898d2fc701d46a5e16bf6a7d79868f10038adeabf1d7b06c432e82f5cb229b1cbd9f14de97ebb8d30e3604e7949278399bc8918e1951b9e18764de38f671f8efed0b6c81791bba2ac2262aee6e0839b14772fb02fdedb46b9e337b9909cd19863e077be3a8c45c8707c15aff13d3df843c85af21ab5b7594b48f7bc04c02d0603e04257d32ad2c4863d162f3a522858861e5185c383326df0c60816c1425b3d153c1e2190b8190c42b5d2ee3abb876803740c088ed23519581e11c1fe8d4589713ce009b1a96a08eb9e7c0e2cc27c715bc7b54e64dd83847bc560d8905302ef8c1cb70ca55c7004f37be100ebb3400911aa2506f10167872afb6d95d6fa458605bc59ca4d29e0d2cf46062eabb14022cb315083c8fad7e076c1cbdfe0b14a760865bcede26536794c8caaa1d9d3176f383a5c84cf61b03d9f239b5e63012631fcc1fcf87b21ca26ffde3702d6ebc92dcd81a2ff0ce40d04ab1eca0001896e1ba14e67229fa6db73e88e0b09c19247deac4d20bd2377243899448dd4d05b0a274ed8a118f2d3456f761905b33a242a36b20d8e74d169ca6e56eefd1ec49baacf59f292cf2972994bee46a6bda47129e364d08222dff5c45313381cb79ec318e69758b26bc91fb262c4ca2ae9b0628fc2990ee45bfea9429962daa4a3e489f36a227a6e5128e209271419bd8ee87d3c30826a8985f6c5397eb7dea31440527b9e2de3495baca1179a2f2b06459ac3e260419e5284fe10c502a789c10f7677049aea0cde463793bb85b428a217e479d2205901e7ad229ea322b04fdd887fe1376cf07845dd6d6bd2a7225cd3ffba8a7077d56361fff54977428f59230593863d71fb48ac1d3bc5387aeee979dfe78e23eb51fbd21b17dc5495e6520cdc99a438ddb6c6651cb40893e2bf386a1bdb90f33cac9f18f3e2ddb4d874648d9411b3bd80e1674334004b652f51720affdcc7480789e845154566c79b6117a05d6afbdfa895bba610cc6389642a2076a38ae6a1a52ca72fb1e7c8891a87c27c4beda246898d4712302d09ade1574e4f8c604c967ea7147542b176a40b37c293d823aeb422e43fd20ea10b81131d67e46c373a8e3d8b438e5d2b1018434a45d3bbbac949ba66ed2402554ead9e2ee95a1bcc0646ee4649743e9ffe01006c2de1d0dd24d5687eee9a8b21c9c414e238fdc0ab25dbea8ce71e9b29598f535e00421bf2f28269169be85a760e659161cacfd1598539e5b282953953fcb3707d5aaa1340c80c3fa0ee991b0060cd6c9e9c89e820c64b94f8649a5519deadb1aa55b32d2200ca83110e71b28041c636fb8069ec01e5fc0280f47d29c5e6ba0618c5141eca3eca9d9ff6c14d651cc8c2090264231e541f1ef397d4a67ddf12a06d3273400b6b78f6a16a66db4789e0d45f4de600fb02ba7e691330d414dc26ce388c63647043a5e435b2af231f26c4f86863dac91a57e067c6f0fc02dad40e0400e4d9557e0d1f9a9787dca030a0f56db7e5689cc4394724bdabff1a0fbca00331751ff0172d46cadd21337f53d41e915904dc8821ba56f35d143dcf477786aac2a423cbf4757532a076bdc36d5d19172db5a49547c1755878078ffafcdd7cdaa19b6d262a0a9502359691f2092f7de82990c8c789ba12609d3bfd714d371ebb6625f5256cd495e3e1193e42fbcd8eda042778cc246c15d42fbf08ce2012dd2fa7b83f10411bca7abd6d8f7f97a491cdde0c7ccdd6e91812137cbc33e24af098cd5c2d69697cdbf19003259ffb41c60a74b5f720d5505ddbea48e910d71d00b9e462626a40cc5d1c569f50863ff758141be9141abfd57e03287196e313f863710fbcf82286fab9f533e23b0b145ebacff1c81bb0cd79b9ffb695319d1701d04a4c403cac906915e30d45d4d07059e0a56069fb8baa44e37042b61e30714764b37db7172d0619c393edba046911fed20c3fd5712578519b80160b0bb73789f0383849f9f6c2ef69720750d1d89ccc9a140fb22ddebd07bb23d9a68d2dade6d4d6e29a54c320501091109de0857365d253b6480cca0945d59e5e336c81e8eb88943e28aaa3c5569caf2a9741923f8b32ce2440d1ce843c6b8af66e785ca0718c64c159ca35d88905db60bf913070556f0307566cc5f81058f3b386628c3083c36930fa31e3a7c01a9f7cfb163ec2378765ec86817323acf967cc9d292023d22f0e86fc3e92f0303121ff2e4217f050a421cfe921e9331a5a75a931113d1528c486644f446a2d2bddf37a594dbc663f26071bfc65ff7e5df6fec61f3e5420af88dff16b6700061071923b2b6aa0e5a766f2b4b3d7e76f80b67972e97821072ae4683c1276b0261f92b4b1f9b4925cbf261f86cf5c3bbaeb420cb25f2c81a6b907ac8d30548565c70c5873cdfbd18ad4380a68b5194525ac409d494395cb2fb4c884929a5d45f36474ad403b65a5b9176982133a971d3dd6badb5d65a6bedd9b367d7ce526bddec0b5b374b6b0db71a4aa9cf39a727b9bb7b0baf3ee79cddbddff72c64cf462277bdc9ed28779a79c82e2d64b7f2c5ca94f455e96cf1554e578f1ace39a7cc4d941bc7c615ca02b4cf296ef26b5cecc726c0f22ffe39695805779d734e7ba9b7c5565b9b766ddb5bdf89da52b6d2ed69cf466bbe0f95a2b65ece836243a5e8b63dc594526aebe53a0f8a220e94d94ba59e762954ea296a7b8ae25277ab36cb92697fda4ae937a7a2b6666b9625aa28cf7977778f34777628da22ea395447bbdb35ea5a255bf37da88b4a79930685c2e952a28db7b5fbd75e8e4ba132aae336ceab2c4c53d6729d6ada958a6e296ea5da62c1da220a655b44a13890ed456d9676f7d2e7bace9b5465844e5493d7b98704d736a3179b3fe996f2b04c0ceba3a9bf76b335bb840a535629d792ac124ccdf7e1d58b8a9956d15fb7426902d5517dff8ca9e33d8d6823e2b85aecfcd4b1b58ba910fcb5ced6b8b0f6659817ccc2b8e3bcef4508de9e6645d7ded769af0af4ec920d5bcb0196466079419b042ad95a109b55eabc18371c250ad626b958b9cdd2f0ccc28a155e8cfab4ceef15dd2e5af05a056d9255b2b594104c2775d9d4a517e37c25c4a51f2582d3e2d52382a4936cca0e4cc8db5b2fc6d65ab245e205b9d629391831e5ab19c540a23cdae489a34451145268907225cf222372282afa7101d1c448ad01deeab6d166f0a818ad9ad0d01d21a47a92844362c9bb622465a4d4c94006d49418c724bb473f29284db20d091494a724d6287665f040adf2b345a1216900959f282838d214a5e848038e8ca224d1d0802943516a306b40152223a0975962736b2d1042e024041108112107224832c291087288c2d1af14e796a28318f27871cd27897841e2058917a317a397234858cab77a35ba0619f59cecf0ea6138d79cd7a6c2e41403284c515e681d1c3233b48c07fa03d98956b50cfa058df409a1b1149d83a973b3788da5e42f992dd17c419bd023cba95d4bbecf4aca97b544beffe283c7abeabeafe99c527a31661e3603b66ed89ff999b0a7936002b3616bbe8a5fc5e6cdfd1fb00d6cdd98e969cd300587d9df5e45a87a0a7ba4ec8813266001e5c10db026a8ff01e366d73d45861d9880f5b880da01d6e4e55d985128016b82ca01369b3a387eaa0c50ad696574e42fd5ffc8627a19d9c81a623de5fb5e0fb8c9616aea4508deb82e8fffe2358c7a316cb462543355b0cc0feb69eaecc808e5fb3240f9cad0f29589e52b23cb576696efb3945846b4cef624e4fbac27feeafe3e8bc85faabfcf92d22e2aab8989b5946f0d0b296f216b48260cc7550557b2ce59c1d037f7efeae7887c8fe41b13aa98ea8aa6b2011e554cf9fe13adb3a3626a1732df15907cc3114f91efa8da41be3f44b384b72625f9fef8416ccf229a37f7db53c13e6b88842078bc43f78e5f2304c7fc7865accbc2fdbd08c1236b0802dc8ff4fd57c07ca75ab8e872f17e671a5cc863aed801cb59be7f652e59917cff0508e654a18f8c2e8c815ce6a87fb3a0e4cb32d2610dd137f751de640d75ceddc29e2637b6b70f00209a1889dd97bfd58c62b08609995dbac09af88f3895e92572f13e5178875cbcf7bd1b76705c1870bc433eda45eaefe3983a2b9aea5eee05ec156deac87b871ec775ee95e2379788ceb93f9290fbc136e10ed105b8d2e50b105c2b754ab38f9fcb254ed40b102cb35bd6106b459b3a7768dedcff3e8c8768f9de97fb3e5c0cabe0b19f64cc47fdb03b8735d437f73feebbbf4fe3037c87fcd545444df9fe7de22fefef5f29d204ff2b25df57dd57dde9c9f0b217dea1ceb9ff12c3633f0de5fbaca1f9f77ef77ddca7d69251fe242c8142a0cc5e70f2c2961b4e4a2abd1904ee515b844300710f858b0253b3f31e20336833ea7e6d8f823c7017cf25444b3e5a5e289222b2645a69edbb9ad22756b08445e94d9f3fa97ba5efce02a5594e29a54351449390249f250810e2c485ec967576adb576ed4e706d5365b77d0d4e86115ed6886df288f3d20a302825a24495dadb4de995364db957c4d7b52d4184519e51f28ca9611b224220803c7fbb7ec28ebfe841b6430ab2ccc0c91398cf5876243e6477777721e8952b364350c943e0204f150c91671111b22c7e3f7ea8b2022c3e39e8e0e4d465b643103b79ec868aae64ff3b0492ec5ff19c2e3052f3e69cf3c64af6a2211c70923cbfc59c734e1f217b09aebfa60f8a4c4922e4640744e4e043485234e79c406029040d2c381cc8bb6d75db425c8381ae40c9622c43260d22596562068fd10e628b0db22fbdb5a98385105204631a2ca0e1af0859d1932554932540f0832b34204f94e6cd0e30c83477d19318f2f82e1c382e08cbe76cf4538f0fffec2fdeb4ce8c9a01b227203b1334b70e0d74ce068e3011439641b2fbe81ca7e18e1c2ec24071fd51df375710045111294268333f8c7852e09afbbd8331019652e77edb489d1bca23972f19ce1f5be4f92d75b6703e9602cfdc02cd1c951947e74c262c2911e0faee62adf7625c419f982cc8c5f94212c8c5580c47e7cc9740610b32c73a673a1307828962fee84112c85d2688c34081e78f32766f0bd4c636a5d18f8bf36568c0743e8c506c4aa1285cdb8c5c2dcf274108fe099bc0feb5192500e1b4454a902707a04574ce8bd61d17db7a0b17db27fd279dd353bee8d5ddb1dfd6a154deadbe5c929bcbbdb27696dc9472727c719de05a3466ee9f53ba7cd22edce5afed49bb28e25b2e699d15e68dff658283969c7c4a70111fa7d6927b1ef5f4bf681de0215aac1f3733543c19110731c8f3595e8cf9cd9e88f16250130e90270e684f4491e7c37c468678c1d21436fc4064682e910116c251703fb1a4052f2b2b1dab260a056467fae189ef213ca97283990c62b00315e43083d24b877375f1f762df8049f657ada848568d0d4e0b1b0c81e50648b23ffe5e74792b24b04de78201e411478b090ca065c909226c6024fb775e0c7735c5623122734200890e538a58d2b4c4086ce24003926fce3939a55696187c796c21031ae4a009239a58aac193ecefd262030d66d93fb5e3ee5e640a1a777747f51148e4f9d7dddd062cb9b37424cfb7eef386eceeeeee5ebb4a1e3b560b268658f261e2853c3f185e7a8e90c72e3b964c7d0642b2fb0c42907d06409081e83218f941a6294c36e5b106ca09b23391fd2f8ec12cfb7fb1ecff59a0c83e8028700a0b93224b86b0081d31c202a5480d1d80b0207da0068d8605073830c1c067c8b6b13e10cc40f0e3379dc78b611083921a1ff881810e496a2c891d8dd0c30f4c4982624f828e7c0e413d605142848b6c895c82491ee75c6249f69f7372c9fe4b6a7217bd8088ac0451ee2228b13ca298b27f4cee222582b21143598c184a32e286273704e5cf6dc4cf928c6acafeee524a29a59c96a39452c984c754528bc86a9d49a7ecb4494bae52e6531e82c75a6d9d4d2e6ea94dfeaaede1dca2949425d8b2b2b4620552130d8d95d8f729a95081b4343363652623835463b1acc8acf8f82b26e6090686e9e54586b1d06a65a45221d13cefa8a9eb621c8734944a1d3da150b37b7fb68de849e64102ca3c484c72f7c625b63d6d4d9bd3c665063c5e262e99e9d6aed2cdb271b94059965cf40aca70cb02f2905d362d2efa6f4bb61f95cff6b3290932db6445649b0f972dc675432e7a0fafa993356aaea175a21282c7cbf484925d262b7499ee167f550bc40515b3414e2efad3107c63fe925b93bf6e53d3e6b45da6eb93a56f71f13acdf0788f8e84f2adf96bbc4b49fe927f97fc356e4d598bbfba680927d97f6bf2d7589dfc35ce277f8d9769e3224de8ec7f9d50b476d14b1d9335acb72cfea236c83ab14ef6692ba2644bcabe29657f1e2b49fe9a3fb9730be4b1926445c98a92952cfe9a36e4ce2b401e2b599696b6f86bd6903bd3803c485b9a9ab8f86b7a2077fe401e242e566256624849fe9a4a726715200f521292129252167fcd24b9f30cc883946569a988bf2692dc5906e4b15264363bf2d73c923bb3401ea4a35acd8a8fbfe62c778e0179acf85891599139f96bd2903bc3803c484e4f4f5afc358de4ce2f200f921626261f7fcd1972670cf220f9c86441fe9a4572e715c8831424248424c55f5386dc5905f2204941324232425ae2af1943eeec813c484b68b4a32dfe9a30e4ce1dc873b4e5a8e9a8898bbfa62c77e6409e232e4831a498137f4d22b9730ae44172323474e4e4af39247746813c474e474f474f45fc355fc89d2fc88354643653e2afe9939194fcfc3cf1d714923b539007e90911110f12137fcd20b9b3057990982001b50bc98304d4397ec4913cd2f90d6121a7404253680a09b9386e7e05679db556bbd9ad6edb5b77262eba8bfef85f145dd989064cc3d195693205132bb1258f95c95f5da48496ec5fb7787daa5c32169f9c93bb4829897a28c89e2c59f8e87933c2904079d2ea4f70742249f3590d5d68ea8cf02ec2bb0db5b8ce08a1d5e222845629f4f119e7f755a6d6cf65faa62fcd947dcb74dac2e4220cf0389926d3d4c1f3e97354130c1ca8f7cf54c107903ab75dd2041ff2499deebb1a011e4511c75f7502c93dc54f0aa5747316dd04ed8692a6a2172c71ef6d3a40b2e0623b80ca2c9b6e4e374ae9cfe9356f83958ce812d73674fe669fe35aca2e9987ec72c3160df9f565c8af3a5ee41b8ef333207fe4bad55cd616fc5d73a4d4ac366d2a9f82439d23937bc608c6d94b908f1a6a17332959bbe8234952880411499249920d7122221b2126a5b437e5da79d1812e1ba24b04c13641d0f5a07e7de4f9b4fa44810b92a4a4c027b7141d6ec8fdbd6550c6b58dbc1d83e253f27b9adcb8ef4f7b9adc48853d3dfdef0fb3e1d3f3668637f47fc0646cde50fa148ad4a3bec98dfbdb6f614f93560adba7fec6f6a92d9ce18ded51610f8c7085b0611b95b3794365bb700fea1cea4e6470c03e14ba908b14a6063c7f742127aa2f80e24e4cac5e5773816cf6d7d83405d5998b49bffef46cdef8026ccb46e71bd88677ded09781b37b31e8873655863ac77f7e8dcd1aeb1ce954a873649d237fe652b4c068889321492ff9325a574beec01a17fdeb1ca7469de3b3fe748e0b81b89318f2b6269b2480dc9b8cf02d5694441677af4112ec8c595508e27fb4bb657777cbee22eefa4b4ad7ce8b3af52968dbca9efe77ba96d76ce93940beb714f2a673e4778b9d236d6ab29c4fab7f3fc633049c14ffc377bafbf4ec3fa5e064dc1b6e1bedbfb37bebfe2decb1c794d3a1d0c1a2b158dd26f4a5f4a72cc0bb4b2a0bd03f3ba7eb5b4ae78f2a9d063d74e8bad6162cfb7d4e9f734a064800b84f9fd3fd32a00160d3394de587cb7a846b1b9fb4b36917dcf7e7d8e163ea7817f582db0800b9c349852d48d4b6a55e76cfcd96aeba61cb1f2fb843806b3ef726b07a2f74a1a58284792f615e28eb60ab9751988ff7432e4cb601b6578112e62bef1db6bd45a9bcb0db00db66b9d447590114e9decd983b3ba3cb3be58e7a3b3d1a4dc4a5426b6dc43fe2679cdf2429257dcf31751cb663eab45d52a9944d9d15a810b5932550d836e079e377071bef25d0d49144a9500e155901e9c50c773a072321298dbbfea2f954cc5819560ccc0b5ea9bc8e4ba1ee662b9d2e6bbffcc7f9de0e58562be80a9afc6515f9829ff578c0e04c96c917bc1e0f291260f94e29a594524ae9051b4629a5331da594d250a2ae87c20fba59af0607327d16e810b72237cd9755588fc6e5b60dac7de361e06e974371298eeb268833d7712a55e654dcaa9b6097b9976e82a9ccc5741b2a73311c8b93e9266833a74205f771345d0d4d6b856a7280f6cc806b9b1e174c40420111acc9fcfaaeebf28155834785d29042b10318c460f3090e3f31189de1add586b25096df23264f70b167a5d4dd2ffd79c111e71b8e3ba64a07fc1ca5238f1d19c5e16cc371474685e38ecc82bfdd7edc916dd8c37bf898e18e1e2eca078f708b863f6dcd6f0ae62e9a0128cf13e0b19b720b348f2e1fddc8da21f1f7fd8ba2cbb5c3da826772171d8992034c3980a094fd75bc4d0b279b6fa66ea6cea1839602cea749db1cb968149332298d3a699425ad73803892b576e184e589ecde2b4c9d1d77778f43019631e903e2a0c00ef7dd4e154a2354b8d5af2d191911657f7914f351924a8117309a3a32768d56d8016d5aec17471c597e8b4d812c7d86dff72f62d7513ce07c9a489c55127814493882c50c04a0a6ac6eb1ff76e8220e0e3b405092fda910d3129e57b284806fc3ece0548856d999cda6e0a244c479b1443e7d0af6f7e1d9a14b942e2a451ce931f177d7628386e2bc5112030ee5cc459fb445f92e43798fd07e1a5b09c796ec4f012d4d245f336e80c756c2117e962ee79115c5acef419b8e38531eed5aa1023c62992b38a77b3c3fb20c5ba8b907cd787634d370cf3955e802db1e86e31d3129fbe360964d8b0ec76fc41e2feb7644db9429866cb21c3fcf8b43d145d70c71c40fc7756c60de428cf93586b43f8a99c78a17a325f003d672fb96f2b9ad9b8fe3372e6ee3775b3eab667be0648a9f9d9e2296fde9ce8b3eea2826aa7139a594dd1dae17e80ad1c0805bbc40fe0a923240e011d3306dea5c8c693bb2a9b39aa26ffc8164874176c42fd0eaa973260c355b71058fae0b94fd5d2d30d1f58136317e62d19838a9993ee0d1871a0808c883fcd5a1a4f913974c68093ec0a3036121d0855c7c0f72d19b748ecbb04317722019d439fe0e34856658ce24f625ee2f811ca80b1e6ff66e5627cefe9cddd2b505cbf0ce9a9922f0f8d5ec3c1d89382f6c7cc03b334411c75ffd3fbe60e1b83845203c7e5f0d66551f74d38e27a1ecdfe34e39e794735af976b39bddec66ad75df51827feb09cba400c794689b71a66945f312fc2bb802a28901b720b3ac2281fc257f4573710534ae682b2632bc15d0d398c80e5150651a6eb5ce81e13242f85aa6b9525a09f9eb27fb6f492bda8ac9cac99625fb6dda92b65ae7d021bb94c7158d69ab6597712fc0e39d69b2487c4f5f0dce9087d5124af05f21041eef259a495acd8696ec7f63269530e92c4dfae5cbd9cffcfe0ea59cc9ec72e62f257256bfd90c67b55928675d9b85b3daec7acc99c0a38ca9b224e105dc6296b52d0532f60323a9c41fc60952e625c46478bc31bf226d204312547200f3f71ae051de792fc6dff74d246373672793f0021e9b8888fef4cb504e29e594121c75d0ae5dbb76d823ec31011ebbe9e77f37fd60470fe953e79cac2aadb4d2ca9195e55759c39e1d3e7ab46ec0a35c5a925a5ce763feb7934dd7074ec2da46a604f867f41f192dfb143f8865ff6ed2f22895e42543c0fe1e6bc96fc107e329fb9f70bb1a170e6b8697e02f8304e609b2c56fda0907e1f2f97c1151898b47c812bfe99e4923c64cd99ff5362d9acc1ff2b0fa3cd0e7a0c0a17b139e477c7094fdb10f8a48b145c761acf9325882cd299fc993dcc333066846bdf058e1c2f15ca14d4e763c5768fe72a0ce3c4741fe6a284eb8e4ce3c576832cf90e70a2db78d3b916be38696ec527e964478c89c01d985055d8690db838008ce83ac49ffd6a54b17210c01263d080480015d863084dcc5045d8690a707011172879787cc19e8974b32f52070735b0f0233735b273d146abe2eba1783bae842be298aba9b4da526752a9b525a5f9c887d014bc801367b9a4a14279a80b940699801964bc80166615823484083213017680c4f60f56dd8d3546410020bcc051a640a58c5e1bcb3db6f3f602ad4dfbeda960af7b7bfa1adbf85b6c337d3ce9937eaec6928454152602e4c1dc0eaff809520921004d30f745eb7944cb38606659634efeef4349510ffc31d09765c9c94d639a773b9e99ca655ac73ce49e7db9a8945496968682c7202ba35078b1144a869ada0f954ccc8b062605ef04ae5755c0ae503295a82642041102f425e8e68974296bf9aa54c755af57fc0e6572c69296ebea37edeb0a7097e2ebc9c4f14d01cf10d5b36b4e17367b426f7b9bf719f0b7b9ad48bba5768de5c50cec4ccca9cf4a41fc92425afb908ee9837f449c6a4cc9778e0eaf7df0af3dd4fb09b313a140be43163a8c45c574f7fe6ab8a4e087b2b50b133777af0936f7217f1604be65e64c9e590322abea7ecce9995e67b72322a3e19159f7cfc3158da971729038e171c3f1c5074b1bec8feb2055e1044c22d9a3a431b33e64d7d1bf3a6c284a30e168ad54455117ef8bf4781df7b8e979797eff620f00cd852f1332ff32bb0a5e2572f33d781139ce901e79893a50ac71e35f770b1fe8ecafa56c5e4c028b13e0e1e1bfe9a9ffaeeec05c71ff982323f4116e64fd0fef5c2160defb997e13df73f7297023db05dec68be3d193af2387ffb9127a883263c802c00cd76c7c5fa81232ba35e05d8428cd5d798799970b2c06f258bb4403138ea6015f1204beea4dc453c9065cebbf92fc4a9c3e3a30bc7751a266303fe11e7ef088f72e8c3cfc5fa2ba4c063337d7d5964ceb013028fcd54bbeeb9cee3c2560dee532f83f33c25bd1853089cc3878e1dfedaecd7973129f3d70d5b9b8cc320716d01dc453b78cae32d723262819b94524ae588b3a50d05e33ce58c5965e763eaa47ea27e7e0a35cff933acb5da7ee9825bf551dfb5580cd6aaa10dee533f93486a8194186c8a33fc557f9b787ef366b29ac06f43239ae7cf9fd12ebe3c5f9c3a3492a8222c67be122a74d5349fe6ed5bdb37ac6bd9a7d97a3fd6b450620bee67ebc94b05e1319dd26d2c2e93e55806fc3eab7f26a24c702f4060fa5cea03f8bfde9ada01d770dca98113c610af78472cce19f0a47986e38e588560f92c6647022c5d738a734ed9dd8deb6c3aa977777376f7a6ba41f4a4f4c739e79c60e7f4d7bbc94c29a594527a27a594764eb04a082c7f86077f992b94526a53b06d38b86143011cc34c679dbe6d281cdc40034bcb414ab073ea8715b0d65a300726eaff48dab998df343c4f8c31a5ad02c7b48e346ac5208f582ac5286523179728a501f15d8925a6dfbf4bee502952970b072bf7e123614a536992410750f2fcf0eb5a0b72b0c18f0d3328019509022338bc3448001d24c9f3ff26f181868512511491e78fe0c5984e002451ac707717224802ccbf51d09083a13cbf06cf0004b6bc18b4cb730b972d3be4f92bb023792203c512793e9d4f73754822530d050de073779aa72962a86285a80734cc0f24a9c1dddd5578311c0a2101b0edc5a9d34a85ad14e8cf4f8537e6fc1eeee96ff466aeda2e05decc6d5d0569e62e9d6e656ff7aa78d87207360c860896d1d9863c649756eae9a3be956afd6da13ef53705fadda7c01bf46fcce742279a37db57cf873c806574ea022e3a49d139dbcbe08073b8b8fdb63d4c0dd8860ffd18145cd7409ba527e347bedf20dd68965e8cb631df82324fb0411ed0060d42587e2b85f9a85761fe8dd4166ee18dd4f7f8a7409aabad5d162cff7be6a79e8637baeff1efde82d3b6a8df811cf81355fb4749246bd37a976ba97e83b5ea5b9884b5d74a61b5e2be03f457a10adcabc20ecce7c2163bc74a991aa40f586113d65e8c560adc7baf02f79ef71da0cfbd0ade7760bee73029c5452b9f748efd0bca22fac1946864bfa603b77ddf18f0f8e97c351f0c0cd8bebcf7a2e6f54b3357ad6c5aab4a863b57cf2870cb9c7b17c444728a88cb3257bb0bdeccd9ee829748e6b6abda72db6c4ddcfdb619b758b6cfc5d8267af1d75a9100c5d82624324a848408d50c09133549a936f15c61c263014f2e562eb5d600d4923455f74ef6dd2840589f5cb45c5cb43132c0ea34420d079a94414f59da980f8e4a2eb4320f6d6cded8f721613bb27f4cbe2fc351ca48e51b0e651fb18c86f3b8b11891dcc36b7c990703b2879246cdfe15ac5cea53b7baed907d62892c9375721dd4cc4af959fab14356c92ed92dd628db6fe94e4f40b1d12a59c224a83a5d61f254b9f8abc664d67e4daa4a3a5244f66b167f59999afc850a4f98a02bac3517edcb10c7456b7fbc20e0b1d66a2ddbb73fd94a97e2af5a1b81360ee2b858bd56292314a454a5db882e1b11c7b57382bfeaa7a8e0160a22e4a6ec7291db401bb33e2e9230fb618bfc0bfad6851db0b4f0ce9beddb4b3d172ed99f7963bf8542dd9aad14234bb2f5711d4b8bc5624060f66f70196bf2d846b51e6c2724c5c80af2d85cb245dd6dc85fa32b96ed8ff6a7e6afb12ed927fe924148c27e8ff628db5a1e6beca7c0f92e1bb33ef6c7d2ead2bf7dfbd2b7187511a2706de333d5101b72dcc3c897e00b4c3852f932f3031e258d2699482019b4a3878f9e997715cffaf1eac8fcf62a401fe08e79e3bfc27dbf60a53f1506060606e6c76ec2f9a53e56adfe05c4e1d8e347963288a62e97e8ee8f6d77058ff74a59651569be306e9d8041ae2ccb0f1d6d6fdb567b03bbeb46e5bb4e185d8e53734b390112495c5bf9f625cddf5a3b24546d8a6bd93d553c384c820d4b7d36d9e5a2f5509c8101fbe760a2f7a9f4e8e3f88dc5b12ba0f8e8e1af5187ad651dbe80fbf673ecc0d12e6ab78fa9733b47bb00b3fd1d5367c2be99fd9e2b7e70fd16382e5afb2b6cc038aea3a31bc56cc11a1e5b0987bfecdba780eb489b442d9ea18cb8b619fb3e7727e72f60c3dca660bc8bba2814eaa65029ee2f57ab17830798e7543d68a6654ec6704b49c12c73bf7ace639c3003c64e7ee12f16ee735f6b707f514df0ad0f63f5a9af7f024763f5dc77e1b82377ff02f6bc3c067bf0e3898201825b2a386ea9205f5e7ec3eeaf50ad1aa98c7a54c8822aeccea19fca9d6f17f6a099fb1407935127c0802d5408a37e535f6604f886306082603b6b9cdb2db44c47f8be5fb75a7fbb9755698440431418f4391c7fd1dfee9db1f5acf001cb980f17e907c1313f6f680c88e3226585ae5ba5943dea2637d4c6a25d744fff3916b6b7976b18709419e6b99d1d9d56ad1fb8e3a2e1ef47978c16e18e8b9487d6b75767aa803d12f61b8cfeec47a552a99ea37d8fbf62bef516465fca6c9ebefc993a354f5fd2a68e084fbf6fc479b55abdeb65646454c8a8909199a1a1990169a6915e0b3436a618ed220d5f845701fc9a6fdd60f1007800843d2cfe078c35857a3b72cc6ea68a8f7601660aa3dfb3a78ef7321fa3e2e9539affbe82df576ba5a1f9efdb8bb1e2fbfea3f97e05c8a36968c29d185fe85e0c15a81e34cf3ccaabc1802e5dbae4996fcf01302a409a67c0560d140b308fface1ced58803d2c7e46cf086f63ded00e840182653ee6e9db842d15e60824d47c4dd80116ef8feadee54260d6d3efeb4160995678fda67ecce36781ed624c485dac35dc1ec684013bbf80e02897b4403d1083adfaada7b1180c045b35b421c2d73c006826308ac1a68782fded3b9045e7d07f50722efa3b74ee205f578d11409c019a6fc8428689e19145a62f025803b6401bf3863eab09297bfa94d27eca94c6fd50e1333c00919032832cb52465e614534285a41c71f29226c87c3424c89020f70042a6d80a9430a4070f860fccd061cbee96dd56f67c49dd1fcb981358ce6c4ccaa48fa44570dce7c846d5d4c8e0484d48132260f22925080210cc41f90210183785d881101d92830e866a3741f9020f503cc1383b3b5ada5c20850c42d7ca0fb52030176a8e40252d91c128d54db0a7938e3cf104cc85991e7c5366dd151b9888794088024815245bd5800351d73c5003a617214722800122c4034b2e04891630c11c6c262d3039040a2a46300777a8c0da0a0b74688239b8d304e366ba09ce2e410465618239286946cc605b12488cf8c10fcc417ff202198cfba2189d4088a69b60436972d40473b02606306e05cdf7f50a118238cd408339b8a305e6824b7a04b7bd70d16b5e4e79e9c5bd7950f0885f5880b924754088f7abc161c99fd9bf9af952d662a2c0e3f713d45185b2fc727f353aaa50c61d5e1292e0f1fbfafbaf4a6288f0d83de11c3334444c0c100632c2dd9391bf466bd43de5277f75111725d9bfe3e2833b4cd43d61a318a0eec9a87b1a6380b2c700c5047531444cdbc72c652fc135babc9ad20fd8f86597ff3f6046d95b296c4fffc6f6614f1319dea07f4321af6728fb6f1e50f6ed67def80c639462b0d830a61663042321081e5d314626688fe6afd1328dde8ff7e394fdbd251e9325f0e8c2b14bfe92de8fabbb3f5c028f962977485df47ebc9f3006d87ff47e688cd512842c57d2269ac4600d6b32df27168771e4b1196bc9805b3756983733eadbf0850e98763ae09d1b3b2f62338adaea69852030b171430b0de8095c33c3f232b94097d63929d4d080b10c86ce71bc7a9abee328990c4714d1ea8996574d2ecadac87f06cf8c26e17cea95e5007b8d9158cc5573d195574a3082108109bb4cf306081ec5d84bae9662489c9ae27497c849d274e4f81cff4c396034136b47bbf809e7e70aa1c8a8548d3656410cd18c0804000400d314000020100c88844291382c20d444457d14000c78a44278581a4ab3288951180519848c31c4100000010240666a68462000ebe665e0446cf23d2e6a1efaec586a783271d83f00dade271851b2f580454f01a83dbd6d525bf8e45cac0428285b2ab7f5817d5dee8ce122ea3da2235c7cc6f6bb3eeeb5359514573c6631503fe45d8cbd014f192ef29e619e5799cc06850e93611d4cc197d7e42ae7a825684d77d2fe9ea1b18d7f53a14d0f7389a2018c500690bea0100159fb08d2429f1752c87ee67c5d87ed6c9bf888f97b45e6757e722ddc0f48034ee98b8b9ab7b6c651c1b126784300ffb624bb942dd07a560beb42fbaf0feed19a21a984359725207d89aeaf6e539e2c1f16dd4377c0a554bce3678ef593c0bf72c2e687961804101d5f06818d215e89b0d989346880d8cfdf222270db68d64aea144d9b56df0a022da91f75b5551b87e70037fe2922528c2654d68e05e6f9f26fe414ad8e7452f4e85a5334e803fabab7849c811d952a28908c6f9fe690d1686c827078c66d3088089d86cc7357d20a8ec4d1bd2ea873e3feadcf63fb2d82d15247c97b20e3dbc317af574269ecc28a3d1fe448bd2793027a0f593ec8cf1cf8ddcb87cf3cb3d4585422258fb79437f92ed722401bde5bd5b89cce02d39d553241215a4f66ff2a17f200dbbadcea1147db25ac2987b12dd88356102682bd91e8845bc27a6cf946384c34acdab58decee3490052103d1486c0303a15532b2747f189e42c69c07395645ef81258fd4a485a57abd462dc8551c130e37f7a0505e28978d0047f28842d2b233aa933cdc19ddf3d929e212e21b67283f0fda623a3b327746df6ea2faf529c752d102fa0bdd2a5e0c518f895039daf762a686efd3ee2856ddcb9183d2f57b17eaa7a492627fdaa743fbdd88e825f776224baebd683047c313b4eae69e04b2f9a972818c203d46a1d424e958f8e05339ce076ba9ca4638e08208a1fa9ccf31f9493468080a0d38c0ac8f167e891c8434c954948582e5df27185d054c8e5d04149d40c8007eb62ae947d0c8aba090b1098233516d791c197ee91dd636ef5c099b29f74a999da9a11b5c2004160598035815c29b652d709f92a324776fca477ea1810c2cdff3cab309e8bdca1620b038df73c1ff7516ab9520a8636f4f9203256e5c8d06e1ef1966cf6dbcea227956f692ace43b01f64e48ddb99f12b86b021bce6dbb5d146daa7c54b74b68b9957f6008275b05df108102c879a6b3a276edefe8ae41d5ea1d011c0a59a1c05f5046ae9d9bee2a8abc4970747c502a8d753a3487acfa8225e11bca47040f1fd30401a2fc8c3cd3796c06119ce506b23303cdeaeb410ae3a8b533e0d6696e848fe26b07bb0549b19ed6b66546458abd1f80557ff19bef01e55e12f9564dbd7a0d711c4d2b60e9915cd613f0b6994cd865b79a779fce211cf22ce02505b87ee0d382b11dac45c29bf7cd35713db9a1e536807a6627435934b174e5181c800f86ebad433a18b2dc423a2198ddcc70d73d7db99e17cc9000e78b780c4cd45a4b75587db4765ba7071d8ab147cfb7edd3b7c38515cdd0f200198dc8e31c0cc0b60230b23ebf3687a48c4bdeec73569e083a95446926b8c34711e0792cacffcd32d7f9ec7465709d6130d46996bcb7d908ddd9fbb70248f3ca3a70cf35a80b05c004fdff769d803c509bb0f7c8348329c34b107d681800aff2721e9a29148a1766e93cbfce0f422c667a8dbf865747c8231bd04c903de87f2bb0872d2b0407fcba625d5d39beb55bc53cd1f084c8b9117dd6d961b613f0c5d93a24eee60de1caee5a0bb600ad13b592fe76495d5d1f927461e40a4049fe9512b538012c8c3d2224db4e49b655db12df9a1346208acab710d90dc1c64e4fe97516221ebb69de0948995684705fc978a95a4cd3e38d4d0f77460e96ebc6e4e68bc6d059f00bbcb100c4189dcd7e271974cc7eb38ce8232218a53b24bbe8cc71b63148255ab50cf403482642dfe4b9f5229a765ecbcbf639fc02de0b67b69742a40c918b1c60420475e5b313fc1b7ccf9cd206f30eecdfeae2112e63191b56dd30f80acb282284210835453926e0d40660ea3e2046bc5258288c9ac00158d7fa4237c6e32986b896d34ed52ebf39628c29829cdf3216e22c5b0a19d6f73294a3bcd0e284b4d0717749f7c8b7f2a115252d364b3647e212ae6875c75d2252e8040b0a90c1019ec7e31acaabae7391f0c1ddae55aac98c87f6e654875ee9cf9c416ca8470676eb8a1ce5b9a6a0f1360a5e016b9fe26e590cb2459f0b805bd32699771e20ebe96de32dbe41c1c649893278fe66ae129a8c38fa26436169859660077957d51d54b6118aed293aabe4afe11d5b19c114ba51ddfa22d350fc26d04e1bc49269f8225f8f3869242b9c4f9bd9d559118f3cd899face4fd1c5dc92e233461ec50a34a5eaed4c9e7c208815ddbc51706e2d59217f16572074c56c3fa1a2eaada005ca5961a019620f2895e2ab443f96ad59ff2a45f8141d16683cf3828ab256af1e1ba0dd01531fe3bdf8821b067b1dfb6d0741b8278fa8f1648690d0879b8b9af842864ce54deaa381eafde5f228674afaa3126162a0abab4e075e505095567c50b3cee74451f580e8779a5c59a3fa35102559bd927d183473f9cea4366158eb729b13966a11f0fc4e016bd3926fb31b02dac8554ba17d8879ffdb93ee9f1728a9a621506d9540d95bcf912091000489c70cd37f740dd23f12a3c70dff3f15521ab11464553d2a64cddbc8394976bbe47a4a1b39e3bb4981a7cb7dd7ae3e3bd90651d0453e80df5e61c4c22d6c70a679186914d3c8a272728a714cc0ac129b0b47297b032bc2cac3ca0ce4db12dbb985aa2f702dedb8fabde69bf6fff69bd0a825ed218a58e82a65c0b38e1726e0fb4ce9f6c8751a2822b843e2868c7e664732b57295ea289dcc366ac064c09c616ec240f6c3192f7d6bf7652b42d990dda274624e4c82e2fcc41b5b1c7736bd673de0e3d647229275234e26d757d3d6a347c25d4774b4b1035421a476bda84197a9df7bf97893d13ea5501ae7183e3817147c905641211174083b45a8c927032b6b63ff4f7ca38ef98ee7e431986fc33b3a5bff1f6550f3e26fbfe44bed82a61c35993e7d1eb5597ab201395ebbbd5723ab69d13513314e8455a4ff740712f18357100075417eb137b7f67ec7a93422b49ce18e0fdd0d6e393d8c4d7d5895123d769f1337bdbf8ca0f70e26eb0d625f8ea1387c8db2ebbe50928cbfdbe116718b81a25ce74d48e20e0dfcd6130a06d4c6886527ea3de5fb75a69c91d3255e49b6c770989abbb3bfaaaf6f18473493f7d61ed3a074c1245db5e937b774cec8b43cc3b3cd3bde6701e425eb7bff99e071bcf5818ad8b07bdaa55c28886617c1b7df2e36130c2bf9fdcf5b4ddee0727e06c73f2bf7003a13acd924fe835437b13b46189f7e7118ab3c911dae6415b20e6b9c6c16b093ae9b86882206ddea4f535b925b84d90a87a344d7bee49121358d3ae319e69aef4eae9e72769b1e7a32f2be4c94e6757d24b88dd6749fab3c8d18cf80109967e09a3436441c439aeb2f42273622a30270fed77c3d7316665a701f7ef69a89f521e2e2872d1972f3acc7d4ea2bb598e1e616a829ff84166b971b0911be397f7d91e480b8b14f02faa0aaa81f56acff0fac3e879d9d6bb1a0b4d259412c733f666445f84bd277156b212bca195ddc2f1bc4ba4cd093a32605a4753f8d388fd32fdeec75da9a76931917c960e0666aa8bec45aa4ac2df3461971d1e0d6b9825826582cd57e9295c8aadaab65a89a438b9a6f9b24726fd8bf9b04a7da986beda48d70d1ebd2803f299b904aa09e75fcf18b7d26d3b3d80a855bf35383bf195876018183d9667d49b76fe0d311ec33482b4c489af6907d5cc33d94b92987de2f3018f2272de368a489f0adf74a5cb8ea43c81fe849f2f973792dfcb741eef1e37edb0fe19fe2d45f3e0fdfc20b755a83d9238346ecd9f8937f9963b17a06ae164b3abcef8122e0dd853ce65548605d2605dd1f648378e2fc4438a27d49851ec38803e7d05ce3816f5380ca0c544137e20b0b0c2c7c8ba24c0c6fa5ea22a4ccc3d4582425580eea79a69850cd4bd629e1a208319e338ccb0fcad8a2cebd56889d615368705cffbef86cb900488c9492f1b9d9283e1f62747021db74e273005563c0c8544ee67b706368b4ea69bf6022f331096831ef985e304ac9b213a73167f54ca6a7c72d1f3b6e3157007ec9e4117560ff5ba75030db64247fd9c799ac43b19eaa7bfa31bac931712061d94c4edf4a9cf410b331fbcc218a1a7bf58e4052744d70ac416c2ee4322cc87277cf8644306c4c4a16318570d42f4a596e74b06c5ca9b74ed1febaa6c847b120c168408cd76ad615a781e14db87e3db94901985bb820b01e1fb90ad184cf0e415f430456f3be8abe37ce4659d412251c949a6ffd0a845f67ec5a872fd522b5f2667d00352692ef9a48d0a732129b4896fd67355d75fd918ecb57342407e2d4c3d694a7f61a00c11b4d1080bf9ce5399946e86f846ac7284e8c599e0371b09eb1eec3906efd5fdf80d5192568e2d26044df6e0601fe0f83a47ad505c9f1f30489f8523b38472fe2d33fc7be2f4524bb07dd542aa85a8481ff6e8e5a255f401e1be0ba7786f87d38a9fd9e8e4d5c22f24eddb129118a60fa07493ac5e01bc4ca8b511c5823937fb6a6dfe0834b338256f5fc9563d5bb73eb20670083189c744c130dd47ce53c122272833865990fa16e8819a166443b1093dae859164bee73d338c6b605fd1424722a632c37f59f62d04b30ca7436cd292e6f281d8dd0e2dd2d8bcb02e6de83b942097302239f0ba4b2e04e58e1196c60b2dbed24ba088dc073c59ffcac25821912fff94142767a916b48425f7e141132d11c129b481eec022b5124db73927986d13609a9f05b4424606f68ef911617cd17e181efee72f49eae511379ba3748ea918b9d971b0cb85e4c6f0b7bd1764c504d737aace1af9f153791a6afade8c4586d1e30ebcc797b628349400139a3fdadc43cc1b905245684df043c7b1f29cc8da21f03c4e11e5fc63745bd5d24b67c1f87b58b090205127adc78908c555a9f7cb72743d939ae9611c912a7e54795c971f39f903870a825f1c2997f5ae000988bb8a1f78882d5baddd7e6688111affb1ce759ae5d7db86d3519cb6920dad8ea66d41651ff44421ab76f6a8f97a43a739c792f009c89739c050489244b43e59e25620de7ce00d4ac6573d15779f283f8a36c18ae48184394e28d2271dbf27bb124776c07879dc7bf71a440cf75c7fb18a0ca24f3342daf994e973a2825375b732421e3879270e45a1aa9b5168eda945953ddab0e66989aa135345b9bdc53df7d0ea84ea798f92f57d007069a6df186bc24418dbed596d1b19483975a9ccfcb8f262b0cf519ec6611c5a411a2fa2a6e228c53e3443db6df4dbe7d79174ac7a6d519425ad7dffa8aff5982494339a75bd9c18d589ad24f1955ce1d67a29a388099a8f8b15929bff0d93cc31552cdca6bdee050972211b9d88c6631630c4cdd51171d46aa6b86ece4dbbeb48ab5a7876a7b2a5be28de1fd0cecf7b077f1d8fc9ce2c83d640a49212687ca89d9240551f235a6ece0848e5cb7229594b53791c47e5d73e686e219b2d6467a1ea85c02acef46c745d3402db354055ca95a13c511aa0e7ad8f517cdf8730db68df23cc991e27800041ce890351f0d5969f851f773676d582048235416769f0caf72353c65c1f58f793e7025a805ed531538cc98831c72c26835e2e810f20d836ce39f165b71aea137863d99c3e73ac81c7868104e84a88570514d98360edea3dc3b5246cf91a2faf0b328a2193b7dfc650949a378e50a2b809c5971cbb18acd79c8d98e77508cb3fe17dcde9d97aaa24267a62ea3060fc7ddcaa08c876c02c1c826ac42871d7193c122869431fc0b59e0edb911b58402f10351d396d14d739bffffa2a56b377419f95d41f280c330f5fb33d06bcfa9b58aab62806524dd217a4f243a97ad4ee9274e87d04fadc41c0c477e9f9ff780a40f563d297267ad58a6fb651fe721ae5df3b4f06b323a7e2528d4090fb6c187b4e6232b72dd5c1e1527f0e1bcdf73272b1f3d38af51302c7780a2f2481e8598182ecddeba1843f13a0f607311f73d2321d260672bef3fa1726eea02f22c7861bdebb4600904a57f6339cf602647481c2d7724a8faf59be7788f2bd54c903a70c1e2f30c07b72ea14a28e9e2b852b9a993e58651952448def33c4226d288f2d7c885be765ea857a145c705a9048822b8f8db8242766a3100f0593f07adc38212000e68cf2c4e17d6ace956c0e5f7e4fc6296ab1503471a6b455791b1dbd391b2541fe6c46ef2bf8b8448f62898792e55778d28fb66bf2128ae9cb07f5cc796f3bb370482117bce36381c5c18b11a8dbc12d5013602f8f4da83ac33365f0429dec6cc193e0bb24f9867a00b9a2fa8f83db099233d0b44a7eed09854eee5dffd73f15e7c5c6f64dc9dd4b798e355902edfb78529186ca447db98f543edfd1a3ce99057aab104e5f3d2b70512fa4345c706874325f04ae7427f758ef43eda92e33ec5925e52644fd9d110e34945fd02b14e9275a1640f0b215414598128552310eeaa903c7a099e8547acd728e16e457f7689cd3f5ea981cf5bed5e5035566c30f1d14d55ced075faa6bd25d44d878fb86f0614790926ab33060f331f95ca0b0fd51e88a78adfc6d5556fc6c8000dc56cfecaa6ef4256b745f610382f5edceaa5b668cc7310988e64f076d98a1d496b675885250c24afd9fa034117d6351006211910980180154efef8abdd971dddaf3245ec1cfbe59925f67ad7644919b71bc25a7dbb03f490dcb26d0f42c9d0ea5c39aaf5889b265ca45cbd5cb0d258ad26ec1766018e62d8ee30c48f62522fee4d008065e7498da07a5b14695b1e2e36fdaa254e8949cd900bd3fcf6fd64b5c00807f1db6e67eea235a553a10fcdbdf5ddff86cdb5b7ac7ac3da409aa4ff06f4f6cf2e2184d6fb4f7dc2429dbda0a9fea2aa921a16e475cad40276bb9b11f0a0d74de645bd8d37b74611143b99545717d5965c3857da467d7e59da7d1d8251061d21b70c006063623f494d017ebee2d6d2feaba2c24562a0856da5f2c5436e4332b4906acaa8731b42329d49c5a96997d582ad4776ab835e4192348c7a56a5287a54f4a98e8f08be228cd228626a7054af6d1a8fd98a4a81b7cf350e8103d2c7e9a191642b00748abddcd19e760bb54a17d7e9ec251ecd8f1e9b0f17ed1f15fb2840ab19b2c099b557e0afeee731cf5c779014a2f1b7065be32a0ae10e3bc51207d11ffcd3045d9fa7c83733a0176e288d0ed7c8f4040052450a9d953f0a56e6f5d394d9bedaf203d0c1b32d9d072799319dd51f69515667bb08057567a50b716c2c61c03f2c88ade2ad640fc080f11795dc8205b07ee33f33a94324a2b409e42848e774ef38110362dc6c40b0c9b2b0c8e451518c0be1f357127eae9e7ebff1e95a4e3828f6f0f569630c3a1c2ed746a3aebcf98cff4700bfb747d154d889689c38dc4f44939abdf096e2839ba633fb5b115abba4571bf08f630d02eede69e8fa0719a75cef2b640bf0d9fbfc2134b9e0a4a12b008140856693ffe5b40bc7400033e06cbe14a5cd917399a7a97ff17a2298c4acd888ee5eef178e8e98d0661939a2555ad1c84399386b126ba084b3680fa0b8f26308b339f46a26f288c13120c969beb3462dfd6a39203cabab92af58ad3ca75f071208409df02fde48ab905aede74db1130bb0b4900b315ce0120e2fa5d1b085c46473fb3b81fcb16fa8a73d5b68d18ebc6d1f8d38a065f14257ccdb73adac21e8874f32c0825f03d0257e38b75ea752d53606461552d80b6340be4ef2e27969d096193c6f8aa000866227da48ec95f822b214b41dee3fc0582060c50beb32d947831df6810e07d1f5e4fcaa42c4b6d2a345d219fd95de876f5a945d6f4f5811aa2440cd935a0947c020f26832c7cc64022ca1944bfd4e5ea7a9651885d1a08421eb45b84e44be5cdd3cec898f17bcd90593fa695e1af369d0919988ce79737d5b281139c00f3bf8cd65cd7ff598b1528d6c05b244af0e03782a05357a49e35c0d32befc22068df0e650809a4a341735144cacfb819a35417535dea380c79d37597531ae8aca9090830d289f0901be3860539484618ae5f7763ac42c9a79ccacb66c2ebc4413dd6b5567397712f2fb99bf4b8a584a3fc71e0151b6c91a7b9414e0969f8d2971c4590d53a294687ad5cf3ca77ba96d7bd8db31f99b4ca1f8cd99bed0d4504c0134b3ca2952f71d5b4bfffab342da901558fa497f4a7ffd8814f71193603a91d71b1e075b3db347f5d6e5f6829a0c871c6863dbd0582c1f4938b492621d87506b48bfc95041cc51f8a25173242a8421f932f2d5806bf451c766fdccf2541905427095154df74a53b8e714ce559a85203d597c90a074a10ad4845081731065777505a9e81ac536962bb3b3cc0b7b521c6196541ea8941930266e87d280b655d1a0334cc4a3013abd933b096b352dd36b20e84eedfc38d442326000e069a52b1d976ca32d6a9ea7ec2239be7fe7096ffbdc5c14018b39fdf4726225ca38e693d3ee306e1f770999363ca079f8450267c56890cdd8e75a322731ae240e59cf31a9e7b7144f25cf74381361cdcceed60643051c61cd0537e3d87a6cf229e6608662050a85bb0b49431eb8930960ea17446733ea643d0704611dfb165445751734909cdc81a565a465a47b396d576094bdfcc96329f5126203aaebf30503469a50e035595f7c9381211c482951aacf23abb93180c3ce55f075d3ae9ed4f52342128081a1c7a2e2b244a273116d300a13cb3b1ed1f47e748b3bc3282d38e3d4b80f55d3c4ba317840c2b910d51ff530fd5de9cc55e6227ff634d3d326c9494eab34b093efb1666e879d76796cbe2497793e44e08cf1e652df10aa37dc04764a5262b2b91da1d1b7df9294be9169779824678d7c8d6cd17bbf5d20c42b1ab95f5dbf2c9eb33eb1b505ed95fbd33ee6b150be589d340b9b447110cca5dae5966a00bf1d752bc85abc8a335aec4b748dded4bcd0bcc0af2b97b9b04f2df1fcfc4249613a28bfc8471e48cd3369aed4511654fa84c004408d9fb262d9f1a3e71035cf399d60f17f81e2235e68512009c60d6f57ac47f88d0bb1b7f14ead3723a810cd96418b7d7bdea590567f96e5242bda035ee18eaf68f3a6b2c35e126c1f0bcfb35e071456307a30413be1ad64970b120b9c95c7c0a6907f664a8d5758ec178e31aadd0a4c8f9658b2c2fe1ed0a85b61ce010bc8abc883d97e00da474d2c71f01dbf7def97bae95f13a34ed88dbc48e09b8afa0a677eb6d62a40dfcdbde44be1707878342b15b62ee26c3a5a9d3ebe0047a12362788d3120312c82017c5112ac3362b22ab94a52b107765560d53ca7d9f18798163b03c0863b414dce7ffa145062e7da7c8608c9c1e6784de269b07ec1bc05915502bce745205138d6050011e80d2e5eb6171289adc816fbbae6cc032dcfa629b31628e02b63eb64b12430262fc2fab0075d3616e59d9ddc84a785b2fa0d96ca65b55fff82617ac6d03e59dbbcf02c8243d1d6b24b5c65f53cc760272b02067bfae9c5139f8c633a7b8273821496af7f0f0e923831c2b7bdb047fe7fe78f9dfe498bdfd23e044fc2a717d15a0eeb1b661ea9a6b3ab589a642df510f26849edcae0a9dd5f1bc48ee4f7e719f98f19c3cc8eadf4e22bb8d8740a0ddeffc0c7f12da0371560b4fc67f767517f47c0f8d2bb94f1286990ac79996158260d8540e3cffdad4f733e5985dcb29fbdbd18c2c2f4945ba5efabd97db8a0eb1583bd78c8c0838ec243b4070a318dd777ea358b0fee0f01b4a633994c83fd1d4431919fd865763351f973028f2bebdd241a6521b7238e6bdbd1cedb054786d0c2d01f16061841507d8a04a9e86aa746e25f7d9a48e7ad77bee11c1e7c41b60a390d500a7777cd893f86a8dce5f9e64db120e921e7aa2490a90c5f0ff65fac594731eaa3caeada74d1580e83c738d233497f86a7ffb978e6f092e69dbaf5d2f4a7a384221b65bd10ccb13e738ffa628862ff307d0e7bd7ece788279a77153156122befbcb5ffa3500db565400a984a18d2517263a342caec24646c0e580415c57753ba6716b9d00c7ecff5e7ca99769bd0c6c026853c4770139c8f40a49afd931444299e3b5936bb9b2c05c917fe24c80278f83e3d02a3ed5c94e2b2907557b4464b7e038bad6566cb0e790c80ac9b8400682dc3f680d6a770837537a496dd2f2ce9126d7ed23240132a157c0704fd7e8b74a711a2861f84ada316e3ec8a4181aa3d13976651277b21881102f171aa9a26746fb3220f82a2c0bc0528e10201cae826f64efa1dc0a7d58d3357a087f410704292272af85d466742645624c55669138d682c702b7b85456b0ce0a580d920b84fe0350a5c7ae77b43b71260d5afbb3f0ecabb7f1182227cffb0faf60fe0fa0787916544502ec0080f3aa2efd6eb1c3d3d726937c978eb8ca2745622671ee3a7c62121cef44099a10482e046d30fbbf52bf748fdbe786163397573869bbce1264fa86846e3264d376ee2c0a0c966d02c3dc0a0aea7bdad16a5d51baf3238a5f794cde08eec5151fa7a5614f1c120aa27de60fb4469632bd1a607a142da4dbc0f6a5f5c577611c9d65db7a9f7e2ea1e5feaf20657b898430584f55ddce1ba343d0c369dbc45cf31be69db351535326843f8df0710de2d19c28d4ab51e8d565a4f1d38b4e1eb07901d5c936e572af46350b49f610b718ce3e073b35b044fc5cd5f32d908d6fe923deb2ff064b8a21e95badf95300ca461f356520439bd485c590b260ac78308a52a868a794f6ba1d19a56467347a4e7e2e49efb3065ecbd4158762560f8a78bd286a2cae589d7647165d55d7916b0da25ab56197b217da76fd66cd610a2c8fee56276b927018fd8feba8865a422e681bbc6e0bbaef6c58dbc10e580db40294d67e15f63960785c4b35c3f125906a69bf50fc6955266503f1783246ef697e7b7e9ca3a18ff5ef114d40683308f5529664c74c02c93e16a10494fb4e2ce3a63f05280efdea1843425d740e99a29ba560ad74ef97528bd06ca5c47f96b51f03aca5fff38193ac835d68a83e2076798ec6e3bef1db81caa3e2ef1c505fff0a05a0f1232efb09aac6d1c440c9adedc7dc044880e3a64e802e90aac199f986e857efccbca46d82e932ed85fa9f0b867aba4ce1598f0226733a1d3806f845c5d63ab3df0b3cb08b9a2369bb1c19805e9658a34e4be8c984fda307a59af33c2a36786d37f19f40d935e2ec27540103e992d5e8d4204402f7b56730cc0c5ad7cbdea75c5fb08f8bc5c01ba5dfa2543931dc0e7e518c93983945cadfdaeeedf5203cb50a700038df3728dd05329f16b8f67433c88b35ae8a1d1aa3df3f95e3d87bc4c1918af38c3abb1262f73cfc8117f81ca36fe8fd51d42a7f8eaec602c1be0f6abdbe8670661b3d2e53ac986b792c751026154f5b482143c3abd65126c7c4266e2f90d4835014b6d93394ff62d2ab2715c8acaf8f9e938e0e26cb587d0c64e3e52ac728a178eaeed04d7d351e16d325e8962cba01e7b5407b9e652e2667333f7972a704edb68062ce0d0f59af293936b74f6c21c1ff08795304dbb8e65725c734e94bda027b5b1f27e17658c3b7c544182193193cb6e5157f64120f6e104408e16708ce3c926ef8a1d2697f71a347bc0c17a7f6e1c80761285c77078b3b6f534cc37744d2cc49f7ef1726ec4bd277957b87682f0649559b196cdceee9c92a3252f2ee5b11d48a18e83fb096211629aa0fb1f413503f9686e9e09a5ecca4cfdc1904d22fad2630e94964b6e8d9a3a2ce41498d4bcb0aee3d2a5b75910700ce718b46c99284f3b1d27b88e4b08525916bab874025f17dca841c642a8175796b603b56bb4acc94148322de9f807818169d37451837639704a81b1bb3900678ef07d3aed355bb73d5fb4c48304e231a20613a2d5c13361afd2601cda023e88928443819d16f122b456f7274e578d71a51259f0a285bca56634842e18f4370a3f4c42e51034da59e2dacd48e7073f0d5c7e39b61b12a1da7d318cefed375af90ebb56f8396afa201410d5b84612e3facbae4d367a841c54b4381264bb0628df383d9024d46c9f621c5a305b1252c80657ad2bfc3d7936054e19c3d27fd1480360f89db7a940ebd170d0a90aba53469383596c3343fe2821188b6623331aedb625461d630d5800cfc7c01c570699e75e00fb784ebfe355a4c7c7ad2eed68f52880ea5fa1dcb1edadfec10a939da3d6f2d537b5a016b76a59ecac1c0d8e0c7885ce2120d400ef9e955048bede0bdfcf211898e65bee4956bcba2299e18d58d2a0a60e75bc435d779054fdc6a4e59f790144dce41de521552ac286f24cd3aa6f26babac4b7bea29383a294321e9682772c4731945bc78ee2e4e7b53cbe01830d8a508fe9306aea68a7a486be5b122dbb42726428239e30bb8c15694bb15f4330202ad7c15ef75780cc13da6a6c3db7340f2805ab4d0d3295cd245ddc606fe64f9ff10b223ad7b699054d0511e6eafb6325012b25e6f3d4fb6fc3c035619a701e9f4a7fed5b2842078e3a28e329848a930d36295e1a252c9b052cb029577e145cf9ccb62717dee7f0211b5dfe330037f399f04a06723c9ee927ab2d999fee278180e620d9de4ae1482f585df8ff57f375a3bd72c9a624fdfcb07aa58791b92ad49693163cbe4196b74021812582569e5317fe5528ae404f85fb0195a8868f56e0182eefe9f2007fc9075f80d2551d1c5c2846302710761163532022a94e9ca8c9fa3375c0bcccdabf62c6cb063333aabd23f087273748b54ed70b3a5859935b22334b9aad75d470d7d1659db552679bd407fb31191c7dfad636daf6498c3b200b2f024376d5f45f6f7cdf5289719cc686af69692a299eb5875326918f96aa62f82da6f6d5d029f990fde936e84512ba3a040a023592c5f0d1dfa8996dede511ef65516c6d086674c2b0def0ad48249567e4d969e88c6dff218562ce34da261f091ad2eb8fec4d733ca3c4a087224f60ca91b61eb0372686de3af2b3c13e8db39e98539c243a8f4456f57c4a580441e221b080d066c62face9b1667b8962d1362ab149d2f165cf83f9de1cd2928bbe5475afd8ca1d2d915a83d87cd40fba74a42104b07e38f749ad61df8c6fd611a092733f9999db20722a9f57c141e483800e3c9c565b92eafa56fbd78ccf406a62c0c2024e3a1fde7355cd720ca859e933178c89c58b017acafdeec82ba012800d1ecbf7013ac7e067c68f568c27a6950e005505976e989d9b893804951800076fdcfaa3f376c4e211bc8e7edea121cd6086e52be79f2c5445bcbb6cf75194057554ec5296adc26c4aba4a95758ebe0573719e4afdcf058583561e331f8f376b1c6fd5763678dfb612e6d77a4106426e818136106ff5057abd2e3c6ed5386b5bf2fb3255b01edd9f244da1cd2031db335c019fde1d4d393b13600b2c7af62f0f596d673168c17ad77820b3bfd83a7fcbd4022ab49405f122998b8d6b995b485837cf659d3b807a09230e6a874016a8d3b1ce0ce99d28e7e1a693d6e1dfe9e97b7db0948bae1843e0799365654ed549bfa1226c58eca0b95c0abb8b97ce54b76d6d5051d74a1d25b35b708ac9c37ceab3d4711a1150892f9799845e0893c7ef583ff586c1c62d1cee85dcd6e26f17300121931f20337c9e4130dbe5285a674a736249304c010220bdb69a8fd822aa739c7d5613874f50645275eef160f3ac9741fa1e1f5fa16478553efdca7b292a88225b661cb8cbb0afad2768ee536ed52e0b5a11cc57aaaa24e6313502ac4f6d395af1116e786d1f1e3ac00366ddbd696e217d7947018f9608b21ecaff57a3b93487b8bd4b94b696183215d3f0eee8833d1b525b04305cce926a55b352a489929641ac2eb73f74124683f291f20217523c3130d40b3e8b9ffec42a91e7f41cf42db71fcad196b3305db0bab15cf51f4d33e743179698b17b3fbee906defdbfa18141a70f269d560b683e39d18f17d1afeac060b63e1496632d353779b3fd69d4d2ae62ab66495aac995afbe604c515fc6b0d3d0bfeb63f070a0a74b02157272cb5169a636acde43a81a30931716d26932b856b56d8d69f6d21617294d1fcca760c7cc04202f723b1c70676dea9a8305f5394c8485172dca2fc340a2324b4e03a5a7c7573628f55197f73e6257ed4ff0a0485d5a6b86b0b2100ce18f8a2ebe6d9add354945307dbe3056473b8a3521208840e68c4373d2c1800014344b83bd61a4c204c5bd36241d314f1d8fffa7a07475ecc275a923f6003a28721252409e20719724be63dc270129ccc0c6f81b0f1254bbfa88acd7eee2d337fda7d6481b6a61150aa49e305d873dda2bf6ad9811bf266024ebbc58eca404e2649cc68d6124ef5ca95a3d1e03a1f0d76446dc114411a228c9eeae2a2f231d46394059891d0e9e2e468ac816f7bb1fbb665d3d416d382f1123a0affcd35c19ad7636fd748172013e5a219243562dc4a8f8e48d41a5e9be0e8c6211879a474c795aec39505d6e3171cdd21fbec8f0e32745987d26a96e3a550f5ff91c11dd85b44b0340b18a81e51518a27372436a84e93cd80bba2df37cc824be62f05cefdc94071405356bc2001f9d2b277f4155dc5019c029d7e78ff5f307cc7241a0cd658bb68a6ab7f961423696457d39cca7124f6fe435f43edf4b9b7a1637bad167799fa1b14064aca735717c4b4048e31e9c5a29be950f6f9fbd84bce80efe7c11e626cb9ec4408f0dd4d08ea3ea6ccfea17210bfb9ed62cca3ee66e48365bb92339ae22983be17f74c5ecff37950cfb3e1f3e0ad32fd5892a881013a640189c2f7efa7bde3ecaab7dc7e43e21de7f664bc9900cf99e158b79f7c84ca612a51a46e809029125bb7cba52186f2dcaffae2c0db1e85e65ebfb975c7085144fe1ba7117db36040586b351ca7a826ff905f207feabad8879f699afcfa4780769e566f11e5732b8fe7e525c43a3ea17d54e89707e2b16d2d5463b14e9d4805d393f2ed1a054ca7166452a45713f98b0283afbcf7321dd67325edfb78d1eb8cbc578a462c28081a6630493b000416f35491c8325e6e3b964231f785048cf8b19ee674c2994fcfe9289f65011b654913571ff25af79d145e8d37d2969a8059f5e8e9a97e8b9fcc9c7eeebcc00aab0586d4dc50d80d8d67d39c72c9fbe07c82af97aa068c6e57c84ee55d982e04d94e2f5f6204dc9b692a9c2b4bcd81c18c98ee44b4b67432252dd0dad6d3335fe6b985c3e171a2d485c13ca62adafc4bd0397f4abdecddafcfc640cc1aab69a6b9bdfeaab33254a012389564f36502cd68212c503e4849d5caf6ac72973b83b3fc9ba06be805cb72f3708367d9e80079c2ee591476a6d216b0518c4705b5f5201019edb77011ee16b4aa65335d3baa6283bd63e047aa78f77e41296aac56236bd4692d2f51f6b7a0a564790faedd74a44afeaf79fd9bd13de212cf910a6073fde9b2b73a3916b66473cc5e0264509b0809954363cfd552b5020bb0fd65b77b07afefe2dab30c03385a8e6b28136060006f646e4fa6ea2d113e084d7bc2003bf97815ad60715b3376572c437a34b32819a4d7d001645f07c56e6d61a00e529861cee6bfaf6f73c8ddadb1068cd8ec255d6eb94ed92acbf961059c6230d45b0699e9a085d18941db1b6d3c75c274cd56550eeb830ab9eef635acfed51b7ff1e29c47e0016d18032470b8e78f975b136f3b792b3df962a3c1996e28a601eb02c69b436393e5c3fb2807871cd3038c67400d5a976285ccc82cdcb0352b5e5e26722a4901cb931d3a9302e858bddee250518e6ec1d1440e8e9761968c5359a1e046a58e2d49d5b925f7c56a346d11ae9135a851bfd6ea2e09c69a1802a159aad09783dad565b8615b7f7e6d881b09c0a474836c7f56789a882440b6252f348d21e2a8c13c774e4c820e552080ae8b98d97bae180bccad8d11656192c52115c7900263a763362c81fb409a8fa2d89d504e0c254091632a5a42345466838958de497cfc4309177274f5e5a60438b1bd7f94434d709cdc21170df7d4ba0435b62285371c1ce7fd6835ba5043ed4a013a574d61eec8045542794fcbc079c165a739f1e5e47f9faae0691e8d7e9be4740a1366586221f7fb7beba61cb0fded34812d63d0efda82511c2ce2b3ddf8aea84a1969aa02d4c0cfa5caa0e66a8ee715fa51e866ab9c95abd83979b93665e3f22f13c708dcebaab03384d89e2dd574ac965951c7e1415bdbeb7ef85de9b7641ef0e0f351410666f703c1a51b54529f9e5f54ea7b4bd42ce2a7c80175d8b425487d0a5f28a28de25d70ebf268480319b12e92fbbb6f2fab9bf1ecdf0067341de42eb1407922bc6910f74d943b7d3735b551ae8bf2b83f07642da73d4247e266943c60064e60b8a21e4e94b9b022cf1d8596dc2387f5acf21d0c93c5985e0ec739f6d1c896f96de8abb6dc858d858e60cdb6bed0a9af35722fa030953fbeaa63f7351f7b5fe66878613c1d7cc9e0efebe56b5f24b5f6f51e472b4c077e08e42e4061a8f55b135a5b5fe75fb4b0c88dd48f0ecff8649d575d4b33e44b35f5b6fff408bc391f170827cbc92b185418ae796401840775b00de39bdb0280cb9857a848d73aa3a85d4799757bf6ff72291ff1fb9d1e29984f8454c5c396d5b465491ee32e74ae65e9f81570321d7e6687e291fecdf7af5b87f687870d940a28adc23af0e944c2cba54006bd15948746e2e1b770f2578a9501b9175509e23c6e37acd288e7c73f8d59e96a3e9e7804c7343892fabcf9ce97ee7423bf142ef2db4a14143a88d94dc873cf36470052a3575114b9d1bc372bf58f7af959f055291f5c4e07df9dd4226a57d24f7d7667defb8f562be1a1e17b14ea5102e2c082b2894f5c3699989270128bf2e0bfbec2d8a9aebd2ca9fccd0f682ceb7283d98497706d738eb3f8cfa5d4482bffd24e53961e579734c3b0863bfa959a81ea556ed176c1ebc5372ab6cc0ad1314e174780c87ce1348d8b33604e2864902771b06e771f76f3a01834c14673e951a56d2712c067a161cd9a1ed80b500aad41deb129a4afa19f2944700b1ee7d4c203a21c7526ad9abe307571d5b802e3960a455e9099152e167afa28b6edf9d2d18e04cea3c6d3b2648988134fe3fd4fb92581524648b410b5fff8c642c01e1ab5f8f5729c5d4c64bfbfd9b0215271c19ba86b3162afa018d4671cc4505ad23011f061d874596bdcf98bdedc3389887c9bbd44c0b01aa877f46c4ca860301fd717c2e8045014a8cf1ba555ad873d085bb11429ccb40add6580b7cb0ee8ac6a6e5303d42cac99a4ca9857c5774c209441ceea1a469b82beda36ccfcab7aeb8972bec34df4e6fa95081ec872d49f1024db5f7f3ba3585dfed2b0db2f11741e7dac86d564509952eb86c9cc230937be72793f3a3b4d404c3f9be632dd8da8a1a30f01977f9fd472f27192d0dca31ec0b0f31e539b0469782edf927e9e25d0088880c59f39388ea960b8ebb0264111c4eff602dd3860191562f93cc5677e380e3c0f03873fc9ce77d3c50571f76b675199d6b5713a97f9100decd588042be81be77969fe08e6b218f67a29b7ed1268b08c35a80c884e4e078d9b123abdf0fccd683dcc0950ccb21e3e25d36a97e7e2cc13476542cd7922076d239e694442d14fd5a87c9284194d74571477327b3711f7946f169e6a26a41eb4cd524166c73281d72ecb38166ecfd0bcf0ca485e30cb66a954fb081f7d36af4bbe1da022b31625543f73f58925ddb347a57ae7a2efdea877b5a083c404c6bceba6f676d76fa5dc008555e00ec0349abf1f90380453acc6f1e5658efedb8483a3aa184c50deb8276ce7ee02cb312b05ce863b6950ac7c398a41b5e609b11900666d1964b6ebef81bd1cf7b725ef9468a7a5364618f5fc99daf0982b8e5f93a1d90a3bc0a6f3ac57035aec70b97df1d81b6de19173b2a8aa715806d023c7f49cd8e135d90726a943b34951d336851f224b27c37ac0c6ed80182fe35f96c1ce5b0eb2c11f9910e1c88f29db44f61a7ff8a7c8a372f416bc100cb8f061e7650f0171458a85ed1a82d8469b08398322b916700dacab77abedd4ccd1a42518b062a53ec26c6c168d000218201b79ec24e6a2b33536910126417e11a1d299ffc692c40ca5572ab286e6af1ddc2d80a8350d94a4b44869fd681372f124adf8c3d9c6142d4a5964f082e3db8e8fc9ab81c65f5ea42aac52b6def9c1b92d45fc094dd87a00cf381af9eb7f0be6cfedb053bc515128a1b441770ec9e2ca908b0a99dfe70509abf9ef0f8084ee622c48445c7140cceb08472de10ec97cb1afe1b94c0c1d2e698939e2f3db372d0dcca67b0e723f0b0b4bd3b8be2e1e4bd25b97c4b2e39565b7c067b5a88dfa24b7080f4e26676f85f6dcdae2e1802f1f3347c42a113df0ae7812c1c811e888529245adeff6968b203d50086bef6f905afaf4070e23e91c21670bd47a25291886d16a7a918fab4e6821671149b153941ef252f9bc8391d3c6efe09c9e7c3b64a0e3e02325e07ff2f00e336ae0f613eae90111478943248ffdc7c9f467226827ed44b226ee99b47948c409bb370159a3a8aa2e7ce3587ee38ed1731cee9420eef1ebfe22d8be72603028e34a72572d01edb722f509e8cc81b2756aece90b0728604a5a3f6c1744761f28ba3301693c20cd2790a2cab6715817e86e763caf30e2fbd0db37378d61486a113989a441988244cc45d1eb8d5c34fa2096e4b95e31518370262b37069c62bc51e64955e4eaaaba4a373e2b8b0bb59c73428e56513356dd89734e525bbf05de22872594441be4deb7c955ae72cfb9c449f58b52a7673a9ce49dc50c2515bfcd33563da05e4e419786dad16a4bef85be143adf5e12f4b784c3ab8faf6db9d7be27c130c43bb8fa3bf6cd9f0c9fe25bca0128782555b17e481c63c816ebd4c4069b8c5dacda83fd41633c5facfe8cb14da760c56a0ecc67ef8809fa8a425ea9335b4bdaf407ec261801025105326f093faf8b63451dd4c4580616eba55bfd71476614ab3ace94260f1f74ab5fab42d87c1bba8ae0c23e4e1f5db3b0e2ec99fee82b80e64aa4fd37ba81bb916b53d90fad1a7c2637b3862f6b4010ae22cd9807af1ef9e4c76709d8f250e02bcd55cf5f234a7090dcbf0ed2361353bd9f5f3fe69f8293a9208f7d705efc626e8d369819e7bda417b175852b298d056d3b2927b1b36cb3c951462a1e648b32eeb2e998049ea680e8158a574f51accf1a2a4f2dc98879e0f998d429226ac533761d16e17f8b115d45ec80f7041eea81994480af2f0051065376c00306250509e482c74b9222144747fdeeb70ebf976961e90e252bb58ee958c8cb27c71a9a69bb0aab9952f1ccc88dd444a5a4a304ddc9e98aa596016c63d28e3f48518dfafc02963e5faabae96a1748659413f23c056f58d4eabfd1bff185f2efc4b54c61e0fb5d31b7e210ffa43b0b89f1584b1d5d0a88f23b4f26e14cea156a1912e5932c5472e9ad52ab224799f59ac4eca30396a05a5fadd2f1acb38171447f14ff2692c8d92a6a009f6948bdf0d8e6efe83be8a110103f70688e235eda15467165edb7e02c371768ee062d332a0c6be2a5342d10afb0f10f95a1f94feaa0e623ad81176ecb9ca18773e087a9922b4e6315b04f289268a2b2351c432aeedef0cf39298e8a977fa41b28eb36e610cfd4b48b9bc124119ee59805f02cd2a614a75025bbc1725e94e356ab2e04bab5c422f3968f9c80b50f6c05d5c36eafed867c78ca907af885371f1dff9d08e32136515d4207fd280c4247b37b1482855b77eadf3164e6f1a3570f4be45a5f0d71a914a53abcc0f7656e8eee3a87131a6bbac344b48135024b077bc5e4debd4224425fef7c26b26d86f54a3714f0a3e67088bf40ba6f9a41196a7295200e0574880b41ad5c553b019e8c8047b4a630760579322444ad0ef3114870f91d3d6feecdec8a5e861291ce0d1d3f1db4c2057d1cd8972a3357d16e9589ab3da5262242f20fbe60637a83fe663336758e620d28d3a1704266679ae275c32319ae239b0b3160bca3cba1aee69a9d5279d26e2bc40a423427519e174ceeeca138c7a5f0ab36254ac4085dd9fa85a8cc80aa3f3e573ac2e813501e67f490d1ce42d9af0065d92bb69c59a49e4fdc91fbf0216c9155554756ad9d5c216c80a3ca3214abfe6776bcb21186287e4640bde9a23e0121dbee14426ea3993ccc7dce0a2706be85b60f006028c754e08d815113cec64684b9455654e87171b82699de68144fea653b866a2a20ac3bb419cceede45a7fafc925badcbcf9231ab44dc7237df0f75dcad6017713cdcc1cc6b6d6a8005c39cb45a60e15fef1f7e7c515da62ca2911e30bb5243d97af976cbe0d5cdb00b0a5a1a783e94bd4e845ddf02ff7d2530315679af89d37d8919e801972c13de59d155c3e8c38b10e442e8930b7fabd2ebb5decb1b0d239511b126922fa33fdc988502f4244ea8ea4c1d723085c2d90e527d6973de756580f15d94c981be8e22ab204b3e4e2df4db55d195f4eeec8fd5b0517f140cdf2b47b4c3ff2ac969a063d5e3ad7b62543001bed6e520707a8c776844a9615ddb4df7c8d7f5a1e2e8a7f4227053768f867f4ca53c5f0c6bafa3537877ce26117a606480cc81e38b3284574e735717da83679053c951eee27f5761a486cd62620f598e7c6745f8549a885ec5a0eb5a492ccd7a490e2544e965f10dcab8e4ab666c66848a445b29baca0594802aa0048a865650259440d1d01aba4049a8024a4013680d454149a802ad13f4e4f606640025e6c445b147313bea6544137c4411847774b3b422ee5fdba9da2b9f126882fef22e7091accd39021652d122353c0cde39d12218c6a0e5d381f111af1cea589ff5b0c066e69c8d4c1fc7fd67ed25f3317725f0205143babed27bab0f5539be90e40ab0d4922bd1a5a1065ae1edc163130f1a3164ea094205dc74a91382e4838b69eb38cc035f002d2b9dec032c3afb67c1d47ccfd48d7e1f34d246debd6b9ca5b2ce48edd25b2c6b7fbe2e007935c83e337ec9f4c4c8e89cf85d0a23a70c0d7702ba19409ff3dd93736acd44479bd2678c18bf72cde0256476571eae39749b8468b76106ecbeca04fad4371c99691f1615f5147db831f1b9f06aeab2839955500723ba1ab117e6601db96ba18ca1906363d8244489edbc998728ce603c546b5c2096f72d3eb5c5ecfbf58c492898c9f6c92c6703381dbdf55aa8c566a952a329a8ebe11798a5a0d12bcfac263a2a7b0ec410674fde24ac760aaae2fe61ca47ed91a35fda2224fb5800d21186122c19e5fd67a5891fa757b3d0a138f1960cd466ef0cab614e139e70200d1eb06556fda3de0156536b21154961bac56f84df4fcf9a38162a8fa2c88c21f48ae8d51637063a352e84f62e71740bd284aca43a4d668998f379ae42da24e221152432e10edb57bb2c2feaae13314a81a73faceee30938c4b8df51e86bda29f431240624a2574cd54d1aac39f30b78e164668ffaa9e7c36fcef91003c2f4028c3c0b9f7ba6bff0e6c6c5bf44e1c5ed828bfb4d1fd8a26d2b0ccbd7292fa1c6f1886c8b2670520cb35d83130d1e67d25882533d396c11af05a9a96964db22f3ab2f208bc5050ecca13d014698e510aba2d7ff852876d8b58fc0ce5d4f59e5d00cfb6846282a632f440d41f5571d348d40e985a11ba5b88ee272032f0a87e663e943799d6bd4cffa199b7559bbec320cccdb018bd02e1171657f56855cd4e481cfbf543cfc0cc7b0b73311b166f350c8078044cabcb448a03c9e2185dcada02f9c1eda8738cf0299f9c1db47f98197ba6c14cbce275a40356dffc8d5726efe9fc20ec2aaf481eb2515530ff4c76fbaf5bb395de5e8c2167a566c906c1260fb0a9dfbd8ef95882b6446474fe04ab573ae716110461c8b46ab8a35a5469ce7f7403107887d2b80a98705c065ed88a36fec5e2154d2a888880b342f911ae45b75db99250868263148f487a81c53b601f957ca9d4f89854827bc95dae670683e79da787afb3f91a10ef4399ed0331fa56430ed03c2c60fc67bf3b3f3196011e62addf2ff9427d06643aef90d0689e13be221e9a723949566b7d74c9da0129e16de6d34f79f8b75c7b59c1961228214d9a29e85cb29ecd9bc524928581cc475db277c134dbe2a5a6b95db593db23f9d4701e30ae680f7a6dde8750fe002af7b030d74cf04124b500806f26ce0bf962551318b2aebfe1cd0d6b4a30942754a6101bfc47a58315f079dd48e5e1a2780042365717e933b95ebb7fff5e582a516861f005b3dd55a70df19e3df9c004c4cab0e9752352591bce830067dea94fff261e3f0189d0d66103cdd80e24f419a15b2240e6795f25bbbc57b0f68733556b586e433bdd4894b4a1fe238aa01627fa064f53c8fc4af4b2e5cf6bc9ac12b69f914cca65294b7071d0730529b81143793be3922c091295264f10b2814391ddd22ec26802cae596e1e729659b54b95c444c71adeb85131cf9a3919aadf36d8aa55802082d47749ceb53011bc0e807b967cf9a1a4e78112d8aeff6528f5b5da06c93a41e996f28b6fe68447466b6c0e7579f646e8b89f12cb194ed629381b4751b350a76653d28063a2141784ed2fd72799a6930503ccb2723ee12b7fcb1d4c14c78a160e6b0bc4b25f0264100da43d5414a6a6e105deb1aa666a4ad8dc190f018618143a9396566c723f1d76a180a29221470a0d890cbd2c2aa9234053fb88a40a77bee82c4c6a7706aeb4050f9b692043a8cf01bbb8d9eafaa805115557d7b13f8113b70f83b7918d2e1915900c5e6d61cfb4fb4ac3cffe7b9a336ab3c7ca22816bd2fd74afaf8586d9ee0890f8c1ae9bf1eccfa7ca9377293a1a738a92dc13772a4770c5f4a404ea9ea3fd09f1155cd858b76b0ceb0fb688b9b6c861bc20e7377e9752c1aebb5ec090a2e2bfef2d02ac58b75cfff436d73b6bd108dd0e2b2f718145370bc51541f980032c23ec39a44164cbd3ab41863dc44b4145ba7105e1030d85d650768f348c3dea3f66a78c6878c7446ce8184d9d0483f4b01ae4bf71089c7a5b7ce73be6c2bc05c62ad43710d1d17ccf46f0ffd0e68162d96fd5c9283ff2db8009295ce308b91d751b844b2a339f82cf893df86e338a572d1f86287adeaa764527eaa571905685961522fce4558805886e816bf3ef25412db1a309395a5268d49ccf24a4ef53221f7ca3e6cd0652116d55e51ef2135b2461a6efa70e84528450f08bab96d103b8c67182f489f765c806bc04234e04bbad163e80d42e0f1e5fc548cc58f5a109a97a744a145839e1db1cc6b5832b7048006d6142be208e623983d9aa28f83a566010c80f8b390bfe862a1e7ac520ef1c0387a8fa54d093b325dbfb15deeb0bd9ab4821bc009a674996815c1ab1f3a257f6ec79878001dec21627cb15da58b3606a7b239ce9070778a7b48269cbaf54b9ad87c7e809e6a686905e3d758947f79d1e3297ec836833ff9bd374bfc6629b0cc01b8c472bc0da8dc65ac06dcc7ba2f9c6bb7e6934c2704917e4fd211cda28b02c8bb70d2d5ecdeee422e1cbe18230f21758a4f3b3a430011896e9a57ed70adccb2be71742a7b04c68a6c5bee506bed12d99a478de0631aece731524d718749c35e8cab17130101dabafe7fe36b7e086f62a7f221cfa3bf68e4774cc9cd3a82f6e6b2eedd6027afc1c6915404eaaf714adf44239710ec36cc9ee25944ea0c112f1e73314c147d3b4253d477bff59a479103faac005e90a7b26dafd31064995ab3188ddfc65a407bb749164fff69a1fbc0aef4450d23970d40240abf40ec28cefbe81d0ce307399c699f847772f0f3cba9350dc84b0355cafefc111085058e299026835914830bc90685cdb7a8e8e540cb41171497fed3e40122f42e39ccd648df29d364870e5b1d41a81405fa320541a5d5a0ba0129ad3703c11bb20bcfc96c6cb17a855f2cfe46d5cd973c595140e48f1b64bda1820eb926736a75973ca7efe06e831f352ae8b56fa36b2d9e1fd23af5b0e0b5c1e6ae2270c6f050247b9fc1959513672b63b304ee92ca593c6a42613fd5ba328b6d50d7edba0704078d58c0ad23ea54870b7e72f26eabaa30eebf47de1564d99fba498b7a9673d995e5c172a033b76607642303828979796bc62569794aff5c2fab13f75af1851c56a5c429914be54028b177a390b630449b82874fb6871347d1ecc91594b9657be2c442b481287dc62d647ab65d63e16189f3afb109a8554f0c687566a9ab0296cc29bdd110a22698286216f83c7daa0da1abdb0c59fa4ae86db59ee07f79eea50ab313d730d64a040305480b057d02f771de9f038f0c074b4f2606ffac0465c7dd5d552cf174d046c9d322e2b08e3732604950e4ae20571818659becd83dbe61285e8dd7e97235da2dd928a98742c8ce35d2120d6b55423b38bf53abfc97ea38a9d582cd3c80fc668b18854bfbf7e5c946d6d7ec9f5ef713ff6ea935a0d9ea69624b7d37366b91e792a6a3d625ce8324d363e1fb5eb3ba77032d5006f868577fc3b5306fed19a8eb4657f3afd4ca8559f1383e6ce906c337f21ab65a7fc888da5e87353da86efa1af17dd49c3923ebad5530650195b96256d57c5960c6aed813be257a80155087ddcea8889175b8a409e59ca23b7840cc217db78bccd87eac116252ac3fac899583e3a23b95be0d9b5b72f10ab600235789e618f3c8dec83078a9985bb00f69591c23ec37f09de453c339c3dab4531495968604e253dec7860f6e7da8745ac90b46e35125b9f667134074872f425bb56e6c2d290b307d010de63f7ec916f349dc30ea3a91e0d8c82c9af4676dee700d6c303f438e09227768ef44ccab2cf4cdabe26953a5ca39d27afeb39623cf0f062350d05461afd90a3162ac91dd9ff1ddf0c00bdc68949613410787042f3be4b5fb478b0e7063f35b1762f1e6022d99f10ea2a56b881bf5662de01651deeef1ba20e34018d7afd966c9c6be7d25aeb076833fc3a193034b11d5d409032246e4d0710b82d4a659a149adea8ee1636dbee4f73b0549ac541a2350838eabbb203ffed2ef6a47261d468f316ce2f564f054fd221a7c6cc86a2c680358e49d6871c22bf206c72a64ecb1fb181aa70379c189a56c3161877a82d0568650833de9b99849278c9d44a4d8fa858acd13341a581385aad96998493baa8d92c4d43fa1f3e715c10a1b2e90108388a3e0ebc29e17a743ec5cad59cbc90c7382aabd60aec96a02a4b98ea8a54f147e3f57eab57031d295d819d815f75657d4a697ff3dc34d43f5a4379d646ab9176f148a1ed5ab260bb267a7b400a87e2ccb535cb1aa8c7edefc2453f0c7bb856b3564dc290e14035b0ac6f654edf62444c0234c90557879c753bd9c8b611196e84e9e12c0cac99467c761fa6ecddb72d1285e68672e822b7e5350b48c4f2e210ba91dbce47c478b0e8698bbe1be6369095dd16cdb3b86d5ece224dd10d11983a45b8cdb678566bdf002390c5731eaaf738ab668138326b56062143100e83e276bc0e37f469338eef36f326a996633bdd740472012f1b3d331a0b90cd2c7a0a6a79aef27bf530dc943e02ad74795f51376967708b1ee7b9c904a550d8ea9f6f2f1d836483bbe16436ef77aaa6d8eb056f826776d176aa5550c04f4774dad1aa555876f8bbe0d49d4e4560093fb73369a9a2170bf95d0d9f2a65c27e55e9f5855cb562d61430078c5f557d6e8b1ca5a0194ea13d5884edf783c1b144f00f2cd1f30a130703871265adf43370f594d28e554d2837613d910c202b7259e0022a6f6e632c78a4b2be3a00b574cd444eeddd0316792380173ba12c64e77957982e84ff890b7374294c4ad946fefae4c92b5e4f525951c9886f7f37ef8ba023b31acb4602232198101bbd9a3ff3569987c4a76eca9d32df3d29d3bc28351582a8606b1e320a7c5a09291302691e5dd3c3df403563e98d49eb8893f672d65d27d74877b7e80f815c69297a3724ae99596c2ca899954616288c021c37eb2ffa11e5a8979bb03cc02e63dfeb77971af4bf12b503e5d80ec845ef433962c37ab60c77cdece229350cd8bc8527b1123f389d57b751a7b1bc1f6c4c0f0f948bf6cbe13fed8f93a084316063a5c12d8b624e9a9bf6c1495112a69c7a66924c94de569a60e84838ca30270bbebb3b8a250909d6be9d22d15ef77bb12067490aea8e20b09100cfef1247747fbfa28c08c9c852410fa58371cf3576812b1b881d9c1bddd596054d24a3619a5059026411e52acc5251a4d67e1ccb228bac1792bc0642f81b8bf42ac95b7c8f32bf2a0dc39a500001269e45be5b760a63796fdc1a5c4c6b0a5e0400753a455f941e6c8c9e88803f06767362cef57e6af18995edbedc218cfe0c702fc061731cdc1862c3d61ffd7e5312e99bc740bb578fcde31a2cca655262be50d29606eae22342ba740f42e6d719efc385e3c64daa23304123538002217854a4ebef5ece36b7708b2e8fd929240c81f73953bc9755fb36fe5c4bbf7547193a6e47f1fc34348daa70dc9a9be35128fdda8c2db782d86833fd7749e8ad419e3ba30f566442d43c7941c7f050d312a7fb437c8621f07668c6c223972385e476fd3624c7507ad270cbe12e72b48f8ca1cf3ec7b502912184fcc715d032bb877827b17bcc48f1453fa3d9d0cbad39bf92d0930d0909507ba3096b347912b7667d7d958c7e807e596188d080c0f268f530053cdb5e5515392fbe940c01369803557eedce8f3afd2495658081bdf2a910e3c7709d9eb78a469b50dc576d41b09f291e49554d2223addb52c740b5ecd7539670b589fbac0924bf6b63aaa93042fbfb9f54c51d33573c4ac6607715ef5fd8038745298971b82c25d27b77ce634bcd16d3e614d6ca71df928292374510e184d3e674a619114b1dde5d00ebd100f181757800841b92e07a241c1f0013e11b16802d6371853a4d02bf4fef1f879f09b44c1494660d891385b54e8f18c171650b6f1ec3d0d10ae0e18893535df316aea0e738a433a4c69494a8215b9b08685f40472e2907442de74a4216810b753f2144be9eba9cb51307973655691dee5834e74ebae656f428845d12eb1ad37aff269e06eac3cb71e62e9bdbee7281600379bf87b0423d45ac288402147ef9907f791199e90cd8a9941c3145c8970aaba887d2d59abc9d61319f6e9a2fc8a7a92d5e5fab95ef2526736361b62225223cce531d441745932d755928cdae188c8550cca706dc47d92dbefa5a5ece458a51ac9672275a99cd8b0a60e75fd51669ed555237c7e6192d178331bd41f83d3919be0a149d4b22d4673a542831c0c7093b5690e7cc178a006cce97ad4e8f784633f877dd2de9f230a7d92213c7be4aba259cac1fa145e649ec30035c72839674c3e7b55f7e77669264615da84b051ec01476eaf1f26f3c1c76f9dd092c84ab31273507c3b2085558b6f4ba46bf7d9cf1983a700414bc019f20fd3e97fefab64f559c60a78abe4a48c46ba48569b433380d1dacbec79f9907f80bab8544834e270f6b3f5d2d4bde2d5be6e3596d7a2d4965fbe4c61322b982ec6f00a5e57485170d02793770a874c82322b956159095d8781e9cb28de785c99075905a39b69dc516734e27ad276fb8adaa35543b8b3efda7293ba8c3c7b91a91c2c595a2abb2eca73fa05c9c57ff259284dddcd00d2d53938522e8e2808d6be3dd35181c61945e72628b9cd69c4aa3f971a162d58809d324a055c5b516c1c7f832b17295fdfed5635f22ed1f9e19292566030662bc370f6084931bf74c253e450f7c5f82c7c93a5ed71c6b6e000da86f7bbcb9a011d785aa35e99cbcd0d8b8878d24489a8a9b4c1ea6154f41d7da8cbd2840b6bf7ef6a7c9ce3ad3ac06ed46e51dcce42cdf8e7c95cb3fc11e380d97e7fbe611c32e2652a87126c1e4816a97a602be2e698cb38f0facda0d86ea0c358a257927684303195497586000830ed6d5a85cf18aa27469d0a50a8bdb8eab7a1edec7d2130d0c8d0315e1dff5ca1bc3c1db7801c53c1d44f4bee1509b2c37bd58e661fc43ca83d5215003dfa85263bda401fda9d9cd08d9d2b076de6d37e519bef57586729f064cfb99f80a19a5cd4d12cb7cbc9559f82e0ce1c7345e68be8865be43df7ff8a32e776cb46806793c1dad1a53201f92470e446c6cd0a4e7402ae854cbb23c8e659268212429bd7b8584140cef6db939c0c00d71bd8fc7e9f52e14b152b55112823feea005427102c6ced5f812007e0cc1d00d3a100ca05dac1c6330b04a0f28b55623db7c89bc274ce58ed75b4bbf77af47501f21888b4c88757804cc82019cd5a3d463a4d70fcf15244941890797e1e9d14cc0216e4d2335b40fccc9656ab2ebe2545ea3312b032e4f109f9bffd51c946ed36a9e3288cf127b66993be5623eedde9f037405a8f60849266bff8446dba9cb159571b87809c9463b89c3d36b9ddeaae4766019b26d6c0b56061582f95d83408b550450fe9188dd984a65d4c83402f575bb26f9aaa12089c0f962d88d14245b441a3297d66b472ea8d007e3fc288e5be680482e5d64192945985afcb97abe97c02c6801d11e94080aec8c5b02f8305df5ffe3cd63184068adc1166c8d1a9a097ca7143a52336c20630c1da5842c0fdb820d9bd907e162513fcb6d242a6f86242aa5468910e30a37c49ddc534eb53aa43979c7b13af9abdd71ce16f964417c2802ff8deb7ecaa8b1bb7823a9e03dc9536a5ad7ce466989b352ee34c03d50c78919d8d331238301f8f49c0ac4f6b2bdbb36cdcd33b2ec48fbf6a8182fd2dadb28a0d97c8c3e069d932a000ff13aafda7f672826d56a83c360da8d862640033fde81b3e1d90dfb41807a1ff754a3881803c7a99011045133e2c4c64c6d75a973e5812a3772aa9d122a23ef79f74a69d1c95c9d5ffb0b59e3dc50a1cfc5f57cf3055b96c8f4f90ea40a19692ad2a745efa90a53a112fcc32ca55f2ca91dce2d1952134b533e693207cd4f1615395bac52982a4289873ec548d64f41d07963b4c8e0e1c588c813eb8e78e08dae3156f8ddc30d59915a5bd3a0ca3bfec6f62573564c4734194c67a4c2bc38546fe7db1917c4ca821a85d61b1d9bf098bf59989cac9d5c529b4fb0ae16fc89162e3a495eb74723e13ea34f0c415b7834ca2227d31ecc460aeebc0fd0f15aef198fc04a667b76d74ba2d7bd9c72b847840c7535aca20b6af5ee62e7dd54eb76f6ee6dc560971fc5f55846f11f85ecb71afdc14f65fe4635e0e6cc125cc6a279b35f80d7f6f28c79bbf1d50034b643a7619b7c013d1499ea05f8c050eb57db2d6d2b5028e3ca1ccc0c83b3f4abca58765274f6e7088969586c56f037071d23805250a794afd6c433078bc02c3ef0de9a77c65aed82d9a6186ccc2197944cc6be19f4f09c9f907d7a3b65772039fcd97949093832b5537f98d21f0a3bd598e1ca4686fce62923fae8a7b19caa7e0a77418e552462f06b0d78f1c778d47dcd67094bf4f1a5cd121a63a658eeae783d9c0e3ac870168681c7d213fc6783f979bb4a60659a964c874b759608005cfcaadab6cf7c3b4065e57c77e23caf1da1f0f31adfa12665099b14e576f4b8ac15ce94497db96e416c1e8de1746b689072ed3420d07dbd22df8f2454ed6f42c7ea04c094f609e2de9fbcbd3279633936f2e45ea666f7daa63c86532ca4996bfca8bed4dd3cc915b95b44f4b04490a817e2c72bc109e685dd200ee0f4dfadbcf20c9d323157e536919f3a0bf381da0a3d289f446164509c9cb1e3d4c5310baf9714973b5f0e822852f6c890ea077ef5c8659ba3ae540fbe7e068f440a0ed512531346e30a6236a49e1f29ba4afe265a5be4485920d63294f8ac9b1722131720676aa284f9589ed68709311e8a2ab7ae5890d04f6e927978646fa3d3640bc22207032b9357841a0599022d821828dc616d41551805118053fd2b184ae7e357575d3460fa04cf9aaf6b4d913465f92db14823d0eec3edb190bcffc2802069d08141a284816797e75af65b6a4b2547f720c7898ec9195c66faeb8d7e0c53c5ece7f24aebdf1f0f464302a55af8b0711ebf0af4dac801d0760a381706f1e03943fb2c43f0b67fd81278011a304adf607f54aa628e310f516d8d9273f77f3ace4c4ce811fc3a01953510ef605ccc599bcfa8181af52d73a4c3daf39d2d79b7a57eca27896871054e868def16f7f24634e01ada2ed9c8fa4a23478fa46907977476d08edc0f27509f02cfdf681c6f2b661a1ce3e7c31872e0131db416b8b1ecf111192e5498336c9c833fa5503528c935f95e3ce5cab34b8f0f0fadba8134880c9e82fab0ae829fc4c6768255b4f0035ad216935c305232520729af2d9cb2c8182327039c4df0ca5daa311c847114c2911a6aec97e5a623338e5e8540848964a342c56000d526220a8190425000fb1df5d0fd45d6db1a58ba0e90716cac7daf4ebf0af803d60461db265beebd654a4906c308e308fb0822156b7a53a7809071e34f8464cd2a8acc3ce95962cf8877e606cac6cf994ad9d894e5bf8927ae7311ed4b0d33ef33248b870d0e538a79d8cccfa96a989ffc67be97e810cc548d1992c55f5cff2641133325f30d8b89e23a508d0f7972bbcbcc17f3e43f4635038d0acc93ffcc27cee4c30fc14f8270fd6f8cec339a46c7325133e31ce958c71d6369ec310a6961dce9f2c888c4c3e01aff70743026ca5f343a15d3ad98a563314f8ce3415c939ae2fa7371c5e421981e6596de0491b9526208e33814aef1f71ee6a98af8841035a19aeb304b1c66e97fc46bb5d664fa2fcbb651a9aecc0fd6f68b84834981168a63667ae699bb3be9d2df5deef38cbbde33515ed21ca0594b7cde54d13fc5173d11f145a4122694842ff6e8c5174925a21f9134e0bcb1eb0ee4b0eb4efab84f90952ba6ca71e05a72d77ffa6de8769d5ec675e31a77dd5fd77de6ea7b7f97493155fd649e7c982ad6324ffe345a15554c295040a12cc7d1860d12c97dfa7afe5698c633dcf51779b0de7fbe02ca2bf9a52896bf71d23aa23c5f8347d08e524a29a58ce35b4729a5948273555eeaf4f98a2b268a3e8f30977beee8b4525ae335022c435cb6489745daf33acff33ccf871499d77b76594a820ca02f6c41230822838712cf756f85c0ba3cb7edfabb7cd7fc3467b556cb553324d3dc962e8b5bdd45add61291b0decce9d6cce798a65828b21f93f3e46a5db3e1859d393324cd4e0d36c7f4b37e5d9ba981b5f153d48932d357666cd8bad6a5f8b2de8b1f56113dafa691652d57d56a6db74a1b9fe9b2b972990b9d331369fa97dc4e40e9db5b73d564aaba09e84d260e06aa0e738501d3ed69d58cffcc387d0591ec2b5dd6baf437bd8daf85a6b7f133dd9a2877f9d36ab60676bc2ca6717b61edcbe24e048fbf2f641ea25137992a9679b7e64af4de372ae689a9985d93b9aabb98e168e455acd8d2aa9ef9c9e78a04db5cf1e8ce82eb14e83d8651d8be35ee3a8e9965f54cc6b84ef1f57e9f33917fd354e1569844cd5bfece3157f5953ef93b96eb1d13914d138565f10d1a2caba9b4fc3b51278462aff83b28fa9ed229999f287f364d94bff8637d14bead81dd71591d76c8ee02f335623e48db9f220b23cdef70af9566581b23d3c6ae31f2901d79cbc85946d238bb2c91ecc38c4c9362a6ba8b5906cd545f2991ac29bb2191ac1b9bf285649535990b9d334376098a6575abd52971e7e52a1ef3d48f825d3fe1b6006efbc700c1ba90372524eac8c64c54977862da06d0cba3d168b72cb8dbf7de0d77a3429b287f5b8697bb7971fd3f29e6f7647e3478399eb99a5f15d7ffcbc2e5595f17d7591f14ac4fcaf7f970fd6bc23b50cd8ea155a6209164d92ad5738f6495d599095f41bafc795eaec66bc4de09b488d9ac3b75f717524821851a5c2d36aa0389e752a740764b9734475a1cb37886bbb4b14c147d5d203b64db0e59be403c9bab112efd4b9f8598899965ca44d11f2c93952cbd2c9378cc80c92810da5f7fd69dbabbdbd8ccdc19047483cb7aaf26f0682c526159dd08442a6cc7d3a533f15a1e4f0c9065793c1e0f8e56752f98cbdcee055786be2c8f67041e8f47c344b905f88a39cb32793cf74d2ee8a07469badde3e813d9c1aef35cd5048241e0053501841a40d73fc7b7064d947f6ea2fc431e6a957f3d5dfabb8b097bdb322ce55c58c30acbb9ebcffa9cb8fe9f0fadb92aafffe7f349f99e5cff8648e37aad22d2d47d2d1b58be4998184226095f24dffb8744e357c70fcaf7038e9f6bfc5aa3cf88effc9cb8c1360fe74a3f96f5b538373b92644d2b7cc544f5d76a2d7f5fab06d047464908f72702be934a94885a897fcfee4463a7a8f79784e86eb06badf4bf71e670a5cf62999256c284f74a9c9fc5f7c821fdf55b647287890dfe2069437f25c5d96529876eff01f3e77d9ff77d1fd903f5843a57e7ea442fca433877d48eeb47f391342eb8d7774f4297e4f07bcdc1ba501ffc21f541320913de27a15f499acf2387809fc4c1b1569f826d94692cc435a6d59866b256ea649b0bacd43085106bd1a265562d25e58e320af841f1e552fa947d0721659cee6efeda22b0cecb1dcb80b093693468e4debdbaec5eb7dfdf858898a6eb35a036868090b8414a0de1dcd14cb265d0b800bef834dfbbbce86986b8bce85deadf50735c5e44d2f0933e7208f8e2832493d64d8ecbdf50734091a461fb44464f1abd1d914a46ff42e6b0ecc89661bbcba236a20b3487524a6f3afadfd84c268abe37b60d1345df6534c03412ce2c2efdc19a5b707129a594465da2d7fd18a75b8cf35dfbf90f968686e98b5e0933b9428bdad53e64d7eab2bb491fc5c9839d44444fe92b015f347388d057e24fc9cf139aa8efe9d8b9fcf4bda8963c60c191a6fb91a63e4d07824f534906d0179134f519e00f9234959c39251fecbcfd895e9dab811aa8639d9ab332c4e8baaedbe1a3794e3edaf5caf008703d1b156559efa9e77d0ff10476bd6711e07a0d047694c94479ecbd2dc3d2eb799ee7950152ef1b68a2bc1fedc0f34c3f352e7a5ecd5cf5f5dee42a1af0fd97e478a0e8e9832039247c27938c3efc24a2f7e16814c73446a06804862370ac371c8dc0b1e270437104ba782ed525caf4530ed863c1179134df8b9ee62387d01fe21f86230c7ef25e34967cb020595fa04ca80d130543912ebd9e791f8ddeba571d52a4f3b1d56403cbdf394921a896d6a673753ef576b57bd1269dad36a61482dc7479336a266d831023385bbb2c79c07a5d0b42e61eac87bfe379b0de83de0efe8d68a74438dcf9dd02effc5034a29daa38dcf9b527059f0bab46f5ae4df77d0f315144f0a9fbae97e8236ef7ac9bbba5cbee3dd765d9599ccd714e076699a8aee6d262eb0f314120d7ed5c7325deee3fb2814661d8fe7675aaa3f96ed70dc444755f47d29de247aed0b79d4cd49521c28ac89daff9e3ebdf3dcf0c28cffde89d2b9678f37de8e3f710e8d4e8fdf4fdfc1e72e3aa7e2205888a2bde50060496e54322d19689fa587659b363af89a23dea2753d550ccd3f71f0bbcfd1a69b1a4cba217021198ab7abfa7a3d562f9ca30ead75c89f7bb4c766ca2be1f91f1f5ab53ac6983758aa5badf7fdf53bc1f8f2cb2336daab96a1ad18f7e49fda487e8264dc31996e48429a524128944f221b9483eec506e5aac53caeece41909a30b53b23f6ef8fccf03dc3ca978d7df01b85f889eaec80640de0275992037e3f733eb2062541dc471adf01c959c9231f69c45a7bbbddf8e6d1e0eceeeeeeceeeeeee547c98ac250b0f7b889767f51329dda469e03370c05e80dcd331f8b7d059282f3f2e4e98c91c2259a5bdd24d2c9dbfc72f4ae2e9b47c34d429a42f7057482c54631acdb9935cac8a0f0571ac69a83c3d44fbd04d582827cb0d2961592897cb0129fdddbac99ff26b488adb67032d063f24bb9cb0f5bf37d2a9282597ab4ab5da6ffc1d1470119306734577cc959bb6b057700bb8044c5c17552553e9655e09ccc7bc92d2cb7cf74068b49d9db96a1ec24573b14b49ccc3bc12992fbd48d33f24e6617e88cc977e8a344dba10f3304f24864cc2c410982ffdf413111932889ffc4b64121e52b61cc1930343da99c34478d3a9907416e2faf398380cc4acf12f3dabc2f0ef9019d9354e3fc18c31949702fd61197e871fc6508a469b282cdf07d2a5cfeb3b37a4a3d67143236f328907e87f791e29d0ffc2d77ee82fa36561f4a327d118c0e5f959707926774c14e997dc2992e2b3e82591ac18ca6b97dcf07b9c97c579bb3e226797dc3357340cf9875d36ecb2b1a1d73d08f7d84a965ef7d22e4ebeb00161118bc5b674e9b906c3766731c5262ae6a21db2ddc58260bdff98f68deed3a5ab4bfadebc8d1bd55060594c4b51afd7854c327af12369c049537fe6b0805cffc2ffc8f2fb5ec559d038468b895fc8359269a52ad67ba60dd1844092a9d86aaa60571e5600a3f5a8b55617d202969b20a09e4bf2c0d21874ae53a45332f023a9570624f6721450cc1af71f21390264bd58ecc671376d6f9659086bd238c68492fa217d17ec12e0ce20217a2e8bac24fcfab43e91f02b1122a207c9242169f293bfd4c0894c889f7282dc8861060521a9586621852efd19098e981b7a6f8c40c5b266102147baf480f5489bd2092cbf80be76c84c2f7398cd9562aa984a9ffcab5411944517d7a9f833a75d5ec9e85f5cfec545f47d90eebeb3327ab1560bebdae758da6d0e833914351b6451ab8276a5f83c16a3035b49b6714dfc80fdface8912a1d80e6ac154317b21de84bc6573c5b75be6e2a44fe729e4da9ce18d906cefee3875425dd40776a98f88dcb129dfe6e90becf08583b933c8892fc8b094b28b5d2f34ffd88985f949e5d75c89ae3314ec044bb948baac24979eb09fd7793f2f9711164997ee22bb4eae99e39bd21f87a32c0bea9822e0914e31d7441ff2500bf9b9e99eb088ab4824924b5d640153c55dd466ad8808c57afd66710d86d11947fce433876b6c9a62a15cfffe7166d2cb888bb88af46eeb92d459124f3b56b6d56771fd232fe3113fc562a4dbaedad127ff1e3220b1b9e29a0b5d8546f44bae8864f1152580a9304f05668abb98e54cf19559facb98b9c613c71357c455ccc3368e31c73a555dc81c6ff1a13eb98a6dfee3a4069226cbb59105e2e5231a6dc7c789cfcdc7ba5c5d52ebf2f1f171756a861fa48fb891195c554d6f2a9f613b47e68ae6d2476212816da4734cd4746969f3652559a6ff4826ad93f5e6ed80a5a765d225b511e5650b6b5bb6655b3c5dd2795996e7d2b73cb6d5a9793f48c3e0aafaf48bb8ea33427d824c14e520f4c6a50f81227ef2294b96c965bd1a09dc764dd4568fbb3bb373c794d93f080876cdb8e2f38614fee9eeeeeeeeae3ea23ec08b772db77c9ccc70657ab094ac2fea83cff5916c5ee19af816c7b61c51843257dc03938e3253b4a9986dc56c31666a8c99aaa1488924e64ad4f13f3784f0bd83c6d25804310e12aef1efa1f6418771afd4e6020313e5df4ddcaadcda4fdcdb4d883642f6e262bf44829189a9619ab1f1568b6874f9707cf93a921e1c4bff8d31ef8d324f47d377e3ccb38c6f73c5e3fa336eae62b6dc78ff9b1b6b61905892c5e3da8779960843b26c58d5d23c8f3ac610c61c9f129f45b6c164923573b217172b2b91605c26a60616c6f19fcce230ae69c6c68b5c7315fbe418d9879dd08cecba31f20f141ba6991a32313025927d198f7489c49398285742871e98dc70fd7b07f324c48fa97d008e2dc62cc798564c2a66d9613496c6629e90304e0761608adb576a5c8460769459faeb184318bb8929c434e28a4ff0ed757bf1ed4504df82f0213911b2edf687a1949cee594b3c8ecdd5cc5cd15ac564b114b73f6cb1ab5d7edfe73ae08460f2b9d3b8738cabf4cbed6715f3c04141f0dce6da6dce72dba7b78b47b2de48975dd637c9f4609d04b24909e70e29fcc3cccccccc2deee1ae0ba202edf20be9d4cbe53f92a4551c848f93d9e5671b5cd6bc85b22027635cd6dc12d4c4e7b2e695cbdf351d09f113bf35d937b1cf38839ff8bf717ee312d397976f82c8f4609bac53c6afae3d2342afd56a31a06707f29803f91453e854ba641ce3d881ba8c395017b85a6daec49ad552ab716e94c58e7e76d9bd1021340700594b2e48b6e0fde1d7d92ec1a7a2ce101239e4c64ffecea3312c8b6bf54aa76a05c19f39e087f3e549fff24448ff422a616288fd975732fd442289bc3ce949a4124b563f89fe45f4568bf5cbe21a3b195d844413ad231b63c2f12e1b3568850e3e20ed99e8c16f17ee769b4d54e93f5d5631558d8379f2ef5bf3ecd6aa22429dea7f93151b74fdabcce027ffae0c56b16bae48b8e15d796f0a8145825d39b0ab03ecfac1f5ef3024594bd8efab8d373dabcefc4f54db204db5ce9064106f560ce3ed17c97eb98cfdd3566c7d9a2b5c3776638b2dc1dcc249e054e6eae5597ec5756f81a3e0fafb0f53c535770fb49669e3dfb8b91abd7b08ae3b12d7fd03e3b4deb73ea53d13fbd602cb37aed51892656172ae6a979f62ae9ccb555995c95bece5767b66fbe62e3f5d0cc976914ef66d849bdbe5be753bd64f5e848511f8d4ef3ca69cc5d9c0c485c522370053ca96e012814b587e9f281e13b5c24449a0e403d32519cedd0a8d67333d9bcd3c63992b1ca6eb303dca4c85603a15d3ad980e8687c138232ca6cae4a720c601c2354d4c87c23843b8c6df74c5c4456a8a5be589159189f2ef61aa7e84a8d55a93e9bf2c27eeba886c5f4830a59899175f34ba7c1d5ffe1bed7b63e9bb11e619a8677335c25cb95cff1632bdbf988435f9077547e665c8249cd3fdfc9b1e2e700e730b0896556a71cb1e36a4195c254396a4908a3aa4916f976658d6dc62334bff202ac494c6e9a7156c6e059284dd0ecced172c0ec896db7f23ab89ba0a1cb91e02745cefabb729dc29fdd43558180f66ecd1276a33f6e8fc4b2338b2a6956701b91d597679c54fe9b9979452276d8b326821a1379964617673095111266b5df5ff5dfe0e8fc5265b76d9e57fa9533fa66d1d77f4a9bb89c6e3c06689d0b37958bc9b67f36cd7bb756af21613a783c235fcfc3b2c6461dc41611cde82b9b8fc24b66cf260260f66f260260fc6ccece1b4082dc097bf5d8599909dd96c36eb9f968e57c30a2b6467ae7c7631bd9838e86498dffd8f8f9f7a6ced94b3d91c67edee16e2a7c67540e1dc31629f2edde7bacb7d769838315cd3299a15fac23ccd01e67c989275f9c9dd5df5f54c147f0ce3f80eb3867b6075b7d8f542b2aae94b8fb4b9945f8608eba4f5170cd3198d8ba1b6d57ace9169330f58aec9bc14fc303b3b3b7b0b463a12866e34d219e9729eda080c10b0c10001233da71018ba879425d75ae74df67db69b3730cb1a7d0ec73c6dd366c4080b862e7bda6030b253040c31c21105b676d9fc252d6cfb444138774c667ea6f133cbba6426f9a18421cc193067c09cd14f290efab2841996554550ba6c2a5d760ca8cb16055996084814eb14d0287a894a39b0f359a2978b044cc3035b7e290576b6371156c42a5de7753e0bd2de3a3809c2088e00c2944991f367d94f995be6259c0630520f8648e0440c8060693190a2f67dd43bafa333da7a05019d60cbe7b4d65a6bede87bb5abb73baf73197fb787d113ceb98427c8b4a9dca22c772699875bdeb7d75e7b1eb7bae461cadc6ae6619e1e5cf57e083db4433c43b61bf2401aa3321a446355680c77635a454663cd4404267621af794f3ff82cf099741a387aad1f7c0a80cfa0d73a5534364ffee087a2d16b4deb52a84b775abfd3da5b5dbad3689d6251d8754a6114b6438b653d0ee20d5dcc613e8586327f0ad429965ba1303a85ad748ac668b82dd6df5bde6a894c6cf79ec8c2e419e2dca4311aa3311aa331b761e23018b3c63f34828df8c09481276aae1acb4489c82037b3d70d8c6d3405966e9f3d46dfe8a74f504624f41df1c0facf0b86ed5a2f3e61bbef810d4b0b85b6e53c13e52372928dcbb19c0658973d0897e8601d50c3b0d8aedab0d9f047ef4063962e592d45cb7ec8b2f8369bcd46685816260bfc23b230a1b8a6e81b0683916042bc8a48d6b764ad9bf0c1256cc42a4ca205b500eb180ca89d682abc45d64233e9d2bfa343f7d52e86b95a5dfaa4350f0b591a066b29deb02cb021ce3251ed392dcc555fef1618c7b11830b620d17ed0dd3d7705c15a2b188eda48f84ac0b082fdd54f21f84ac22712823f7a22e08f461f86a37e96a99bf454509bdd5ef5befa8940af63dbc5209c3b58353e976cd2299689eab85d7f1db7b93a5d7f1db8b9a2402c9997daf5eeb502298ae53b4530916d8ba55a5896c964aaf11943cbe4617155cc296c5485266cf7d4394897330f62978dabbe770ea242f3cb63342c03312c16abd69a4cff6569138b31942efd77d8491a269ff97b3089e82be91f8b3114f3e4afa4fef73d914facff8948eb279f7d023b619f2e7dc75534ec4c1211fd68144e52059b209d9a3eb6a333a8661441d2468ac0e08007cc553fdd61637096dff2d01380c113736710d01964501c42e68a04d2a53fa453ddd7ffe8f7f5a3f4a64b1fcb3e03fb8273e21fdad8f4149b8bf05782821992cc152d01139c04103b8ce003e994bbcaf46daa619241f448ea750941ab8179f267720625315fe395c8bce9f987c47c8d1f22f3a69f220d932ec47c8d2712f335923031a4c6cfbc69a646932c0b2cb9a50f9f555e9887f990f668f46177256b6148249817981238228df685e6323265614d2c575427cc1b2013e52f3306b1f95ea4ae3790794d276191cbf55cd6bcb2a3bd1aa6aa1b639e727355ced5cbf5f768f03a30579eabebb24c56b7a547ab5ae8d35c75a43f0afe26ff4e95a0f7188d28cc9c3a362993b9c1e64c385ee6d5d520e373299ddd6d7671eb7073652fa5ed850fbe887aa3903a8a46ea4247bb1bed1d843ddc6fbe6f19d8fb4e257a242d1a63680a4c947f374e60d6dc6cdfd9fd4d0a2d2434f24a444f6b14117f3716195fe8932b197d387a713412a7c80860b2a2c076ff455e982bf0fd1b30530298a57f0aa3e8236dca1bee82b0f0bc04a10587fb737fd82a4c947f9886fdbe5bc85c8d606dd83a26e534a13e383269dde454f2869aa3ba5e9ec032e17aa40a5f6f614a229c8c23328e78e7bb6721059b20d046bc54f7be036bf258ad0c9ee75126349947798e98d27677c701e7eeee4e9b3b6f6e17af92ac8780110747939f2810172d1c33b33b3b7577671b765248902e4599654d59d008a85cc7c57e9899ddd93f77070224c8891d2ecb644607dcbb47bbd75aabbb7b3bcfb294ddf08df5a16f3d6416f1221db9d38d45baafd5da223b436893b9a35da46d6c96655364c8ec8599373501edc00c7c235afdbb59e3d400ded547526f3751de062a5849da71e0d2ef56a05d03a176de836820c0d348039ca568814bbf76487978fcc159334272313074e97ba20164001f49bde044f98b5818b8a24ec5405f50f4415a4456c02f48d689cac0fcee89af26a58dbb751903d4e1ba9b8dc1713d1c7d93ac49938105995b014e237c7921bd9c0ee86131516ea7b02c0f8bebcff2681ecd3d2c3c1e652ee802cbea6ee0cbdc297edd10ddad1bc23dd1bb8c32f0f562314c3a63ba741c345706084b4e6b6d9d0fada056b7bac7a75bdd6a9f4ecd701ccbc0811d3dab5bb732bad56ab5bad5fa7c44397093de2ce548975d77ddcdee481fe9ba27ed724224d9a856abe1b6e470b84985f84aab7e60589eb83a289c90d22e53d9ae2b1d6b2a3da561ed92220667d956ebb254c5962e7342b82d70381c0e4784895549b2135427dd50e385bb552ad588eae47be28be2e3e29bc1e7830f8d2a44fd4035412dc1e7aa51ea941a84ff97c6b7e5b3c127547b2a0e5f0fbe2c9f185f0caeffa7820ff659f99a28c3dd63aedfa8546a09aa09aa10f503d588eaa406f1a551a3d4291587daf3f9e043e39bc1c7c5d7836fcb67834fe88bc197e513e38be27be2737daf4f055f131fcccb79b98f0a2fe7e5bc1c11337027f9905c249f9b16da50b4145d45b91c93e6b1b11175bcb7a926fc00e22592cd66fb3cfa9acde8abfe8ce746b221f1d95e361bd799ab3a2667744e665dcf8c672897f3e1c3c70f20a2994c26bb52abfdf8f103c80920ecf5aa128b010102e48454e5168987c796fb6117bb4e38e18494ea23d188b994ca7ba1484d75d30dd9a1dda0405b10324361e543ac70b7320551a7fb14fe3d27b7ff4675e0e0883adde3a42000b6b9096de08c6bfa4ba46eec78e890cd551d9319e3d03372b76df446d1a0339a731d2974af1c2db2ce69e7947ae7b47ede480c459d531c3948364ab7a2348f9f4430985b8e1074f84b098ed1f1342693c90629c20ddced761bc791765e3f9bb73c3126c6c49818133b110a5148b422438a601241041176f098a9591a4d6607c9d2ce6d365b28ba72c55bb59a388ac5624242eda242a57e5e5f9645e19828bc128adc451cd54211f8d598508d7d3121af5d54aaeb6b1715191e23c850f7481075fc0400272690e8c7fb48286178e87c0f00ea5ef74999a866d88f28abd2a5e8c5e755da39105005f253b7bcd5b658cc5f0ef316e3f8136ff9147fe2556c4ec516034000ac8d00f478e98172419550420d16150808a8025512cc1a050bac80c16d9babfcf57ad9fc657b4d547f09a28ef725d41a137c90acbf68e7b6b6c5625cd36fdf5b9fd74f1ad9657d7cd847945589891daba4588528caaa78cb556290280a5911a37089b12a6c6c6c4cf021629edc0c39134c30c1c70fd0880d061a1f3e7cfc00526d1ec90aa940343f7efc0072c277c348d00039e184544aa5baf15b81a2dcae403731e63db3acc66ef35b93535314cb1fa4511075fc51a02b1cda39c9d6af02d9b8a6e7774586598639942e7d8a8d71fcc9aca9241095db3147c26b1b25fd55858e8fe6f33a642e34a1361004c391c8457c016970e488f91933ce79b247aa74f97263b11070501d4b9c4a970d04a4e31de63093c9648314e186fb78cb5b505e2ff04752041b15480a415668cc99388ff390229886729d1341041176f098b1d5bad6b52d381c8c8b5fec339f795a455f4c7c70e75f60e2502633244132572f6f679617786070e7bb3071280fd7f4db6c6e2b326467ae444332d9952197f9951ce201b854ca21ea903ed4318a509230e49ca8528944b2965cdb3252d4b1248dd231d185327111fbfd6514521e1791f2dc16916e1b729bdb865c76c5652ebb2244a3f11841860a50038dc02d228184fa8df8c34e3405464209d350fad52f1e3c441def4728551ca291ca28ad42d2d80d3168152a33d19818fde37291400209a5ad46169bb49a8ac6da898f0f000260bb47ec16f740b9f0500e5582b805d765ffac19c94c311af32cb79bd77d3bb3d67d0d6ae2b8adc75c7ddf72196d36ebbc978d712814afd7475baea2412e631c0a0515a22d6aa5e5322c369b8d8d8d093e444030182c482633c104137cfc002bfbc4b45a505e2f1f3e7cfc00528fdc8cd8f8f1e3079013be1a7304cc0690133c15829c90ea52b8b949a928101b1bd5bcf11e5d96fda26117b31275c05fd5236cadb57642402c0adbf128a020ea808fc20ac7c655323f77bcfcd420387168abca8c31229148445270a4325a85be5c4583688b71281434465fd40a7dd128688bc672882b6a2bc0d09c95ce29fe14754297ff4f64a1415138125d5e2ca904135363a6f37155e949394a24120d8e1c3c348efec9cea51c21d0d4a85163e6c71b3319cb64b59a0e1da20e7d1d6f63566deccd28271b8d723cca8d681d9d213a117b5e33981f4d30e3389222cc00c11806438ebcd7fc1a2f2fee6126a3f0c78c3bbf0213a77ffa56a4553d736171e72360e2b4ebc85cb9fc9cbfd3c25c819c73e2846b5bb6d08807e0cbfc34a28ed5318ea86880b6967cb1fff2a5915d2fead89717a3b0cfa87f4661ffdc7e771189ed1a853370c4b976c2b976b2a556db0223c20e991d319da25f27e24142c7fc20585f04d6eee331028c0822883adeef1895cc290b2e01bd7134d4a24e1d0d893c5878f0e031c29c2bd287ebd9ebf5cae57055c85e992db4aa6d7d9b314e7bf16deb2ced450fbd7acbed99f264674e00780140005c02d043ece14d363edc3cf567c654f5cbd1b8fdb5061d7eb85ddde6ddafb64d54bf8d1e3d449dee7b3c6ad4d566db5e2f20adea979f027039c735dd3de39ad7fdf733ccce7be55eafdc0addfdc8516ea2fa459ed1d0cc552293e61ae38cce9835fda213eb62cf088d89eae7da2857420925d4d8888cd860b0d5d4d4d89800eecc84d8d8d898e0a3ded8d8d830c104137cfcf8a298be9c0d1f3fbc1f403a2027d01352a3b6798b719b049b281e46b954d3fae56ef3cde846d4a97f23ea44694ad28e562fa752893af455220b3d51583990ab4a4f7ad387eb17d77c7796c027812008de1a4b979da54f2fc6692f664d7f6ff962a2ba6d968ab6e51057c8713f1f1e2c3fab6f72848083e6860d530e9739519452caa3802bd6da6aa24d9b3f6ba2d8237830128201891ac0cc216ef94069014c124fd460205e5c97bf5f0dbb72590dd4545cbaca7d795607f54c0928f7e55fde45d47949f9b82e2f8a3a2e7476c524aeb8e28f441d9135bae116374c82892028b51056f002b55cf0aba80356201778c19353ebe87e59dcefab1648d060a108918527c7f30670bb9f473ca1ea2d2c98018d871573eb932e9ed83067eabb5d4abb21ac8629bc251ee899c2678827b6c801085b60a1d5b480a5a4c4aeeb26195a7cf06304142018a28a50773b100401423ac2dae430dbdec1eda75d788395167387db7541467c71bb5711b713add81d2c84c2ed6ad7bdcd2d81e20a1a9a6431c60f4c8491d37dd92987c1eddea653dd03b95161a7aae036edaba2da6aaab4c64f1618283c323f3f68524a821653440d3d9094b0c114e1811e1b0557024193175aa5b8a4bc48364a1317265a16a8899884cf0b143b8c624c517c5cea14ad10099a58c40b442c7c711b1551d3018c02147ef1438df569caa6ccfa34afc854440f78f8a4e4402639784a541e6ce8a0d0be1f6e80021b020729b3570f0b64d8a634518227080425c861899935c1a2c82ecbfed721a8408b10b4681242931c408450023474d89e0812082df15a8490c65bc9b185871c523c151f031c58fc14386eaf448e30706002440e2e6c74d8d024cb657dab091affc47634b8c0926ed0b1c5ca08a1c30cfb32250421ac8d220426acd091021b7367500e34d021440e5a7484c0bad410021176e4430833b0b5881c3eb0a5246860454fc0ec480a2e2cbde26fb05e163b58172e72046161ee0cd2010c1d3fb6de19a443ae89a53cbf85a54c748cc0bab460967467504f10f781b0f6cea09e283e58f1cea09e2774dcece8cea09ea020ac7767500f4d8528b199254889313c6807105c7a679012627c4294b6a9cb57c1d4d4f4ff6fada983e0b0e110e64398b1d49130413f3e23985af303b4ebfddf9032a1b61e9fdbcf23dc7e864d15133188082fae6ac636837eb0dce69c19d444d4511b6858efbdc5efd2b7a906a5ffa8e46c173741ead08c00004010006315000028100a060482e17838d2d3c0d70714800f6ca644725618cb046a10c3384c1965083140000000000088ccccb601aa72704a7be73df01bcfa6356de84462478a11e119a7e9066a5caa7a5e3ee6eb4058900eb35831fe8fc1708a81bd2d302ec422cdb06495114692760254d93a7fabc0dbd7fd05277337c406f0e5935eb608bdbbd9ff6f635c7286539cd3ee5598fd284380df2309116a13cd93106991e2aade18b60e90c93c090fe2ea4a2dac1081b1ed0e897593c09c4d8458ea4a93ae8858075b6a5c22318b1b108be704c5fe125762914362a5fc19fddcc034910d28c5c6d9c79a78f076ed65abf9b10b34d6724122c7213767e180192309eb27f8ddb40680bcdd7c80933d91953c09f72f217484cd9b4be890af9527d8d9863929356d16ea045a1f9f3e6e0a481e3b9943573408b7c642215c78ee8e8816843abd00ce76f0683643cf339fa89bd8353bd6aa95aaf045a1a42b024f29eb2e55ee699867ce8aa7087c081be6c8c24d5866406d993cc4f974c3714a0f2a5b811204ef1c8112645fbfaf4f011791ae515254fac5472bf63b8410e9c09a06860ffd47812a376d3ab3c4c363086cd04f5871834affa7c5502c7586a614ca72b05584179268307cec07d5e742ec3bf81c85377f6d11f74a89b8f1763818b0e99430c4a8e0573a7a17ee2442319bcffa10ea049ae1946a016708fe78fa57366160da98a00e42f734c4c6b00a5b0711740b132adcc7d59b595e72045ce02c8572bb59674a14478369574a1155e8c8ea95a75774f44c76e5d4adba2ee3f486d7a6995961f73abe303a6225420bb119a3a2e1eaec84db3661646bc7df021285aab8d8fbb04978c9cd56f538a7862ce98cf8ebb900b0108055f3b6adfaeb85adbcdbf5bdabcfc87a2d518de55fbf8025a2af3e05a707868ddfa43b8f939e68f2e9f71ed930b16828961a5abcde44f736c4f33e85e34a86486c121db93306720431070ec8e2a51091d3f8698d9cc61e5f145509ef9e81d45b543aaec46e1cc512fb23b0b6df32209767bd9a82cedc74efb22b6e0601f9b059ac97fa28df0040edb05b9089c9c94368a48a06eb0c8b869873526fd58e182927f510ac32202a7b48e6f911902db39893b38659e41f16f7c3a8d4f076c15895dd4db32aa55c7ee35b7f28787d97acf7d34d36bd12701e2c577825b081b88fc53fde755294eb7f65f0ca1d11f5f2a8c44fc77b0f7c7af19475b637bdb219f4ce50e9f7466bbeb831a913185b28e426220c3a59ad4bd8d2beeec4c425a03c814595942c4a65732633d373094e3fd3b6646e1bde7d09cf3e858d7331b1488a124ea4be1ad6cd823f702d9314f5ec6e48f02d91c3cfd5c7385a82c880ba3ec4975101a0707c1787a1499eac4629f68fde0556fa5ed80a9cca1902bd5178ff8e0f89e59178a2568a27105d2c259b658a19e3b4dddff7c25df4b3518c9cbc145f744ee8bde8b6a4f86bb4c9f7cba8ea89f30aa2be4a142f157cb8dde2dd16da4e23ebab9a3c6926a0c7700696d0ab6fba901e617e653af689ac338e8a1bc6b80722b0d218087a5b4b8f2e541a06d91c1b356c0b30649b4562c645707bf1a5132d714259c2779b25d8fd22c8368bdd1dc4281a4f2e3644c5688a8fa5815a125716f12ddfa1cdd6b81fd5c36669dcca258e812738d08a6ea25bdfc8e42c5bd4beb3364b87b4412a5ebbe3e84f8cf0a053a70c29431f88a6958fd30f1a508b93b7be495fedd86fb3c8d7712ff35d280974a53618c8ce7880fc58ad247c6dffc8b1451d7e77993eacd15a8422a0b70747ece9a52276c667ff2c782a11c4336a1e3c7db6f5297371ba8dae77e1b19487e7da342f08742b20d52c0432602a752e8f9dad9988f83a73133bdc37d8d03f7805b3eed9d6d79d17f2d170e61e3324c4c8aa42f3b22fb1436b8ad535e2cb87479e2fc7ea39ab3ef4ae987b3691591bbaed06ec7b31a6d45f4aae0bfaa532f646c4abbc1be3d7724527fcc81b448f1d3eab3407015dfb15d06dc9b089f94a184176c7c9cc88ffce64e8113b7f834b99a52cb02fe40887359c1b4b472d367c6f78646b93a4c0b4faaa10d30946b48cb30852ac59e278c8e1fa5a88a6bc6f701925af2d0805f1182fb49faf9f74f010a6759620628733e1bd143d6f0cf94a2dc435f68e808f843d5313502a957e6f0fb544a0a5bda74cfda508352917132ddd3dd25fae90e21f0175ee167824072cdf453dd43df307c7820088e26700d568a88034dd3bd2978841a59404ba2bfeb76b09159d2cc5d5143f902244d0cd16d17083ef00b1e7deafc5cce7cf49b6c381033cf4814a79f8f548728017203c78f401f219ad18450852d0204bbc0481a07291745c726da4c26695df5516416003548a2980f8a9b3eb23e4750de15df96d8474c7eed05da1c58d066e2dc157e980b4d3717dc44764a0c70645b26b99db484a19c356cd56c9472ffd035ff3e893def732e9cc9f09b34e5f7d37216a17f959347a7891b79aebacde267f2f927eb318808bf2d5ee69a29782782272d0c0b3f0925e9f78bb119d9a62241445304cfb6ce2e8ba537be601001d105a10516b8824d53b7fe6351e3d30cec765de62e2a186bc3e4614a50f9037ad7604ecb94ad49bf95ee612d941f76b9128edd265e69ff40f3c092680dadcf4f6a9e6ea8bd1bda422c30d79e7cd5d516e11f6265851341cd33b0e3ba67c2f6c03622b745bfe1a394743d849fbd0ed63139df1ac02b160230814dcb2d57cacf5aa1e29c3d16013b25e28cec74c2d7829bf1652e20d1b9be6be28aa027b2af093e6eb3daa721ae19ea346cc325b32242696c2be6b6c9e632380cf9fe935ff358a58100a3be33e4d80e44d965f1bd0f0c3dcc3d1e0cebbec5f169e26bca753cb3a8c83ad0ebd2817b8a9debcf22e0327121437a70a80af910f3772c103796e5023f784802234581e5601576ef1fd8b80016b511478050a6947544953c6532b248a4e661cdededec4f54067f13e4e14496ed96978a514655743865ffa37617be256ddf6447bc3ab02d073a0d28f0f468514428aa9a761e669671c2d0a25cfb11f448344119f3a716a2f3ae863acbd68297c23aac0416f57291bea68895c7b66fcf8073d3047fef06cc58b6a62e88d91e6e9fe4d7e17f31359f20d366c7a066525412dcaf09b9968448aec0ebc367386077112eda944f509b16bb69a88611d5de0cb5ecb8fa06e2626ac1fdc5f1c51634c0b47c051fedc8a359371fefd8f5edd4b99628b033e63c16cd74b2e9c0e151b559e626d6b02d7125eb8453e7a4ca99c4838c98331abe0adb1c6d7dc3cd7d83997a0b0c6d1ea40abc3f0a670e7540f0b732e4071c3d20526b6938b83f21073110d8a9c985da628d5fe8203a7bddd2f25544904a0694b9855b004019d78d1cc61ef0977394850a6e98d84b199d67770b7c40bc028f781dc72f22242fb00ae655ccc192bfc4fa417681e09941b5a7a730d5b0a42c3dc83bb8d7439d6c85485082018355d61e6ce336ead30890a8cb5a8a5c62412a30db7eb8144c5ba2aadd3fd48bd5c2a07db1f6caf9cb91a846564a39e27c1887109bf050b92b367a92b1afdb04d589f9749c340666888a16731fe38fe2558d058ec54327e55928e8daffe40e07cb7239e7ea3619f8f39521905cd136151da11e000bebf88a0e0770a88632079d82cea422b1213eddac7b6efab0dd2c54934e2b16f085c60803633573bfd53a3c8d98e88cef89b539a9fc78cb946dd6acc3b5c7fa4153fd59ddcb9b8376180951e1fca00666557498e068c9e71677092c2938ef22821e0e569555dcb0bbf500de7f083f7a5f46a7af5a62c444b23744ed19386ef2b62899760688e3ea07be4312b523aec3e90242d7f442be2b208ca80c17a84a8b99e01d646276f0f1273deb634ba694e292153c77dfd9d7d4a3ee62d7d593e488de572119977782060d24e1b5f44400f23af008cc90cd9b465024baefe4401c74b1acf6d580216bd912c95b263cafd7aed579a13e5ae27e135432afc6b090108dbf5a68b7d4fa589b14968a5a18cae59d5ff42f646211f214a070a989d2c2a93f0051202aec5edb4d5c11a87d24cf0714d6a6b92e6c2b3e60286e494ee3f755a710291697cd2b51b5eb945b3fe6c2ca2907390fe67c9f4845c397f2f0dc429da0a2f12fe37817b581527f3b8b6d079d3ff98c89448ec595d93a43a1a8d6e52051891a3ad1796123251cfe51e5c884a4cc63dad95d7f31d80a2f891f0503b414341f6f02ae315e17418992afed80040b3826ee7bf3a41a54e95ca1abf4fa5a465c2664ec4d421ed1c9dad8419e4506cfb0fe351c1fe0e0574a52ab3956eed34185aab1af379a6f94c127a21a39f0e1dfe0067a9b2893f50e4d20726177df5c4d1d3feefc918ba273190e26ef73d28148f3a0d69c9538b79e8a64300be6835bb67bcb27f67fb396e7820e14c532faa46f515b50ca50d3ec26d82903106904d1d356af3f505ba9f360bfd09ab1c29619811921cb48294017c37ce72e756153402e20f35fe7707fc0be6960e812c19c2b819e66b8846ae9230ed90e4c02a762c2264f640b4be5a848e0ef9746bddc108bc4ed4dcfceab1c5230582c3efc98f97b625d49c31d028e1ad17c654d727df10c5e454c961fb1cd376c0757ea4bc37346d984ce2fa5094a7f1ffea4fc5a0748e0ddd21fe31bf23aa526b3a12e3071fc86de3e1d5ccf09ebbb8c559d8e7583f2b98ab7e2070ddc44b6516b68406673babded21b5163df4515d45852d6690611374e5a817491cf5df1389057cd82a134a34de18f1c609f657cc58b32778e5944ccded57b6c149371d1074c38de42dcedb3a83307862329daad345f9934110fc62b027b021af054a9dbab52ae8f526f9f6a6ea1515237416683b470f4b828d20eb75a73d1b33f4143b2f1ae458eea97de47418f9c2cfe366d8a214fbe43eb22bfde41e044b6854fe583e6f6efa76d2e3ba04dfb1aa9bf54c0364f0b8a89689232c9df4388c736bee0552d8d02b810e25b3a979f9b1ac4a1fa3952ff2f06191e0107d9ad0f7d258061690c3ff72aee53ed62be02322708ccd76b369bd6493aacc9e5b84c3195d8718a8b9b16d6645ac423982785d1c5ee94acd632321f4c7c9c1c1ad0eb3f27d2c16ac3a89100e28f3c353c740a655f25adca50271afba488873afe2ad0109d56b16297eef1e39765a56aeee9b53d28c6f6d3ffee594ea8ee300a5c61466e1029ad70d00b5ac90778838e42f215439d39f035079f2cd84bcedef4778aa1f1dda2daace70367564c463af545559e8046fa77486cceb2217df07e761435f3cacc5cc7bc99bfe8c6b92134a1fa88c09a1be6c92c07c2fe57a20fd7ab8142839d024612f1db5f9fbef46f01989649c3a15d3582d6bf8f4772e6120083e4cb5872c94d77ecee4f1be3b6cd77e0637c7864aedd20b2e6e203fb15c627c027cd7393e2f03072c5e0a93c5586e73299a2019d6652f3564e9eb343312c7ac2fcca460d0f8ddaa14c28d6593d19ed94f4e51d8ef6ca108a9f0775e6ff6518ef9dfa5e02e506e9b88c38278697b38ae02ef9bbd1d51fb4f9f59e3a3892c3f575ff6fc04ae44988e24f4ef4e377815713dad4fa8f897855a63c732bb32f6d525ced1f43ee04206e6b4985264b48ba6826fcd8812a836ccb2c38318be8c3a80e0f08b242a341c3d3bc30641d4ae042a491c919bf9aade8ccbca3e1a8bec4f0d3c495fd3302b2bab3ac1150d317e41e2a551dbd8dc8e824c6acf83a14fd90bf7b961375ef7346c06567369525e20d86dcb8ea1a259d086a5ebb50a46c3cc5a55299f5d8c10e8b5981773d976eba7feef35190e24e371bcbca94c9fbfd24dbc4f5f28ba1a4ed1068cf905bc543f907b39649d4c7c84acf64f8efc7b0b4ed776c4b3309b448e0c30c5830a07d8997127ca96687d8f1dd2bbaba9312fffd7183e9ff61cd2a059fc5bdf4cd08423cc7fb8c61f8395b84fdddaa82b2688e6cfcdf5b3118d9699d92ce66118db15828d1176eb63ea6ab3cab35652e1faa603e13a0d9c4617d7d37a87f3d66b351e76e734e3abc786a6a380cd0030d8e5c4c428a532ad44e3e78b7115289145e33f64ef77610fac3807bb2a89a16f7cbfc203e43eaf9d262c91526b7127fb81f57d862b4aef3b5dcf2b1ee7296cfb60fb5ed9fc2daf510a41c6324c301760479eda827f2e8140331f91758fb44d24741b3ebff3ee5265e8d4e85ace09b29b74f9cf4a40cb14ed60e1a729f886a92e79857957780a2d00d50bb984418413be3d711a527f230b1a4427c158076bbd1c7cbeff96d4c7d7911f4ccc192bf5bd9dd6b2cc5620f67866104edd63c7b221949ccca46ad929130645e06d5231b978a1917278829ab14781d04a2a94b131b623ed606b690aa2158316ab1480e744e6f34d1866246fb9884a4593ed0b37843e9a10f342748faf4c788361de7ec4822b53d60a77571567b439759d59bc8a97d2262638f2aa5e398d9d1548126442873f7fe3a43a4c733043d1a3e7d0345c04e1dabec300b2738847a998e82386f5e57b7a26965564e9ff0640c2fee4f31e125c2895a493de5ca4d2200846b00d5d127c40d00f379e7ff6302542daa803ab7dce132133833b300b9df186f05d5af10f9d9d70175879c14d1ac4b247a571fc62e2a208ea05dfe7037b187d56a0e660c047a69c3ea036c842a9ba0eb24a5433550769b8ae0808bf0a5354e01118ab54612d9cbb8050f6a4b5a84d53bcc06d147864497ed3582823a055a26ea6e58dda4590261590a20b77caa2b9d1e617d3f302a3749a642234e5288add5e94ad14f155e9e95c5a114def32a293205693ef0747260d7bc244538ae11ac2281c39225ef2e39f880d62bf5b965b5ac9294d428e6b15b34b1f888e72df463638dec7a11b5edf9762c95bcb780ce57e79d75040a08500ee96f3b3c4c31ac70ab19117e60e5a8c59d4c6b4704e420d26f2b6a83fca05c3fb91e0a6e3706fc46780421973198d6f9fe2b3e648aca499ad9de3926136289c3830ca0672a0923e0b6c8cd54c2ef125d48388c66faea2fd098dc287f89e14f33e945241c106a539a00ab22ec6266bd28852322b979e50f3d8b765d0fb0d460cbe8b1feb85477466b82f12ec251b733919bae22ec8af82566ae93a512e4d03ccc0aa7af77f24c14f9591d95ad66544b8138b3de9c2280a928bb8599de023fb377c9ac91089ab10cd4975e08389c00de5f1109f58567a8a63a75ab8802e4c157bf631f9859a532398ab5925d44c2813bbd193af9d051d5333810336766273a06d4dc2359a152c7201e6ed1560ca13178e7066a9144b446f7e91704d08d91aebffe12fd7ee02a5bee09ddda19c60b41ee2e4caec35b015b70d57c17808283515a80c02f5610ca10233f05527c812f86608abc5603351bb12f7bf4e8df47d2fce8957b97d9ac3586b2cec96b5457721df2206015c45aa700e5fa8a40b5a55ab488a0f538b55a4f6680b5aba0b92f3f1fc57e0903cdea88d1a4b72cd1f3f4612b11e0597a2b66c577d33464e246407746dfe606ac4ac8e834664ccaeb28ecc203b9b38b7ef8af1472cd5d1932d1843354b70a710a7a6f9c30a934275642982722561df2c392fc01a4f6887859609e4175f6daf58d05b9a86ae9353b5d8f6b7340f7c9dd3dea7a41420db2739405621c0eaa2c918191d6ba69632cc7f6cd770485d3b3619809af248b5e869fac7757d62e1970ed972d9d9f5fcce478c04b30ccbf2a1e337816694a01a9734b33c456852c5dfaaaa5161dc72cccb4e9b52604cf6d10669e888caeb51944dfb15d94f4b6dcccc95a9a68b1bc779a34ff2fb3e6138fac32bc35ddc6a06181dac9733465867f506427ae8b8442f88a2117181d4a2b5fa07cfbb18fa6569d6ab6c42b38dc79400dd23d01b6d19536d2fa62297666aa9fc15c1d9aa54db68b0d3ca0fbe8590b87cf684939ba02579cb6ee248991329fa1dc0017c631f2563ab38750535df86865b0575ae15ddeeea0bb3c4899485b6c49db5c369ea1fc6d9388d40fb564ff6f599777f4c097d33aae2f953abf979962cb4b9af8dce33d2ef4ee5f0e66a754f747a4d5130da69c3e85b6f421a56043fb7dd828393d7532f6b0c67d6611d962f2d12b490b3a0f01d0e5cda3e4e06b3babdd9aab19003c627f635bfb71080d919e24a198bf3dfc9bc03d33feab9e8efbc934306f1320bb26923d39e5320a23949c82816f1078481bf17b3973c8013f919c823018507fc0cca88cb08c7cee87a6af1ad0c884d6972b7fc2a014df3d120056165c0452c1ef76420fe7ea13581623221cce527e911deef5206e5de4e7268bbdb866b0995110a3a7eea2b8f9d0695db87ce09fad2b05360927bd53c53cf617dab87aea5898772d90b9ace349405a2c108774d699d38dcf4fc4179a16d4cb8358084f313afb5f5a62962fa8a7bfb188a87985119479dad34fb3c9381901bcd019cd7540f41269bae1840b99557f013c56e1a5906ccdb6127e215ccb07fc120080433961065ceaa84b04d93ce13154b1e9a8361fa18300326feee0bc9663fc1907582c937386fa2d5b55e514dc8774f2ccb16e6843cdee94df1a4a227e94e5b794b348112a400ae2fb5f600a60e6f362c714f047e788a022ef3f4747fefe7664cd7963134e8909147e6fc62aeb47a6d007213d3e385e3eb8feea1b0c5e3a3c90f44423e88386713bfb3159fa0285cf13eaf4889b666cef2b46466757af06a11c0b27d45ec52a81c4e6977772e4a515ec5854e8a560c141bcdb2d6c7172e0ac89a6dfba4bb128378fd41e7224bfe4da57b2e314cf8f5249ab263892931c042d13a127231e6f23423c48ea1a6244dcc18456cccb7874cba73e65e7eff957a82883b98ed4d69426c5508867305ec0412e21f78578a2eb108f5a9ff1cfff991715eacbd79913c2ac6bea13a35019bf6398854e65143cfd2b919245c704e2194246173481b7a1c57aeed024081e6aecb102e8648c21b91dfcf300eac91a6bcbebae4f3fff571e3514a761b270d2dbebf30803eef6407eb2d6f8840be90401f949b55f570792aa6b17c94325d0ba67c7442a31a6f99e8575cc5756ae3f9a1e996563dffc495eabd9aa28c9a815ae316adb49cd329eca9896d6b7c7a3ea40c5dc028cfa8810b31ae9d645953f3d60824590beb360c320630ba30c601101e700a67f8363b12ff09286e9cf01b6cf213b4898203c657e441f8240cbf4d2075e04d201ebf713b5fd58bbc86dd963bb336c7dfa946b7cb35dfb52196e142dac014611efdb1aacc4682c2cf76c855fc487cf8a851f0c61ff064099adfb166e4504f618d0a55f1a521c2b77686a5acd799e07f7623ff656659ef5e63e470c317ffb0f3000ac3905a8468d4124a08637bd187f41821749c7790f1f58f3d6c20026238e3886347cc69aedb6a1a08bf844158b31cf69d4900e449831cf17ffaf7f4424c298676559b1bb14b4c90dd9413ec2274f585656a52e31e5288b2e474b48c05081dac382cbbae7aca55e80bc01be1b63cdb08ac8306118f385d95f65d52d98a99d40928e757abd84724af74f2f058e06ff00faac44c72eff978a014aa9666cea8282a5044ddc459b981b2e4ce59aa15b2a92a58a4b757daf6851e77d8d35b085869aa7fe00d3426dab06f11c07e2146551f01da24a8b70fa10b0c860b9639bf46efc3cd1339ab78b58eb33df7ea7942e5219354708b49aa93191c349d226fdb0e350acc8d521603b8440bef2457e0351602923811a405c485608909ee31a4a16c8579b02238a08b85832db9db7fc92889192a42f54fe46b91b4d6bb03a02b8d3d806c2fdc12f57604b2dcfbb5a80c98242579e846506908137d19193cf0084c549c9341124be119389f23283fc2e6bc49688cd2bed9d46cb44fb45dd0acacce0067b6525aa00716351e0c74b9bad5349baaf82370e102234089d2f72ba9eb66171077c1c7eb7a6890cda91a29fcdef3a44304d5651db1c92a87f3428e271ce36699e0d5b82437a8f8e62b9d7481cd983161907d1dac2d55317407f1b63f80f59b4553fe2dd7355cfc2dd53c8028e5543983fc2dcafb0dbef58613823650d1e6b461da0fb5503463e955618492019c6208fe8d6fe201e4f89c0e1ce2ee69288f375569d016b947ba089a8af79f435d51bc5aa7b0dd1f5064869af2fd9510dea90442dd617d4bd2a4017a82138136da7b5e0652ee0887492a18d437fbd8a2a91a02ae0eafb783005a30ab81ebb04815de4650feeb023558046d2889aa1c5975bb3c943b301477fcf2fb0d4e85c335e8885ecc11611914d66e4eab8466a7a1937c2d8431555a9c0f9808c7b5ae879b743c3e28489a2515b74871ec1ed5e86d2f08b4e1275d4b13b8340db2d94ca532d8163a4a71657cf589ed10913ead31a4e5548553f23f432d9494deb5cf48dce1a778a8dd17c49345c898b355f03c7d4543c75e46c9eeaa812a995b7d8c74fca8f597be2cdc174d1cbb21e2e371eb0c091a0dd93eda8f4c999a65f487bd6da1ed2a2d0415b1b9008e97fe6a3ee0c6d79b06aa0aeaf678e2c095dcfe181ca381e4cea8cb93a974c97b2a50e2c4b2bdb7d76f744111bd193e598865a5fbdcf5b656d13a0b8f709727cba236e5a22d2399ace88311882c56a1472a2030c0914779f43624a917431072a2130416eeace4fac6adbb14a8775ced95fc69e0ad0e403f81599750962905fb4af55f33f2473cced57fb3ab14531e5e3f63ee4d8f1a951efe5925100b86b5fa4098d45834f59ce2d372fe4f6078958ef91fe09046b02c4fe57c04eb72af3fd0d8c0e155ab854c80ed5e1f83daef167e987c0fd35c309f639531ff6f3b40af1fd4d187ef52d3d3f861c7c77fb965864804e863395fd7baca1122e3a417a714b1abae7d82f3fea339894e19f8fd9598df3658267506012ccacd03ded5ce59ccca2da710091e136a6f44dc1aedb86d2730d746ef64e94da0ef435c870c3c59d11c1ebf86e5780fe735141e53d64a2d7f34ac2131e0ca3aa0dadcf33dbd4985021f2ca4948b768c647dc0eba1298a298a39407d110263a6a5689203b7913b7aae4a1a656757b3d38d7d46feee20069cca1623009a7c020ab667473514ac4c3c0d8faae0010dbcb2bcc6e1c051d0fadf8b758a120bee57b9556c0e99cb34df8447a3b16925e772e0a7cbe75f0b4a1557cd2d366b7136f5c2cf1c6f193466daacdf3dc94e38f7bfa53368ff4d22a7bc079c07a4322463c2838951b723c382c0df1382144d600e207139ab73ee8669dabe9a1df15d1a1cdca4c2fe544ca6fbf4017f3a2f34fc6a19a1d9b2850f29f70367c7cac1b7431d524f7908b52dfb344e9040b2a4f630a094dc72b51a521505771e8672b7003d6d777c1e0148caf9dbd6b1d7045ebbe373730b95a4ec4bf41d7cfb54ca1d4635260bcbda2aa18260957fa23db1a577157db79ca00aa75037bd8c56d093a4e127afc296c5675020385c9948bff70117da9af3a2b054f7fb6ad920637173bfb2a8af07ed05594555baa11021b86e664ec4ccc2d57e8d19d347ea2c5addd40973bc3aa84ee2d8f8fd804e42118221c7eba1f32ef2dd9123eae1db2ef9f2bcaa3377ae7a15604542d4fc3c9f7679bd46361e103867a1597b4d7e10edf880b8e3f00a6af0e8ca8a3fd8aeae4313afc64df21579eaa252b1c21a702702882f1e4983fbba96c565a90d0cd77f0595b5eb34144be271158868492db0e6cd1176ed216a60ed23883129042bdd69ca1cff09dd7ee9cfc676c2d55b836652b2ba906ea367f084d1bd5ccd9b4ec9a146affbbd31b5c8f685babb71a731caae771e2094cf994a8ee8f3e6512e374b647326c07648064d25e1197c67877bf0b4c26b3803100ef328099ae73f4f372511871a2d38c612be08386408830846f0122ce47dbdf40951e0b06c70d92f54a46b15d3eb7d789754d36ce06481caa2b131a3e1c4eccd81dde17812255501c372fd60b182d962eebd98951c47c514a6e922c7824c7c9ebb18d92383165e7f35dcd252e9aeb0691c856d35cfdcf00b61f16e808ad7b1a7fba0d94454a619e5c4609c311119a2bdce5eed864f0e110d5392bba9fc6312c4e4c5d81777a164f3fe61e56c8f1688016a445fe8e076c5c9203397dfa04fa81addfd3d7815dfd4531491bea0a6c6536e2af078bd6ac132d290f98e534c794c5feae5d237218149166a41ced646526c280cd9d6ad8f7c67e4df46b044c1006623b984f9d99d312c1a794ef18713c43bab531c3bf66a2e2914cfe028484529a9c6983e4ca477957e410878d2e4dc0160f85e745aa54f6817e1a3d98b4201c5a048e12079af968f66158f88652237187e5559e583156cc4235beb9487a671e987cc12cd3f61914604433a506edba4e91b43b4241bf9542ba7dd0f41d41d9ebc18712503c497ca7bddfc472da8fc3eb926a16539b57b0042d837662d8fac51052dcf98352836354edf83e6e15531f351f0f442242f1b063b471f56162eed1afe5c6385721f2291466ff06aec63c0fb942f5ce1a25bf23bbbf88ca0233a8bacc7905a9106276ecad26d026c034ea1547b5075cf28f456c3350385ad0c6356d7a8d1d816df228d1fb2e9ae49d76e5255a5742d75c5362047694838fae1084143c5d83493e4a5498af1511f5706cd230aae8bf3d337ff37815395c109e6e58f7a002a8a8efe13e83802b0a54a2d70a899aba18ccc30520eeee78fa766c20020fee69a5db67383986a77eb241aef039e56a6613d6d3ac3e36146ede7820562912bf41e8007f8e4c99544de9f425ae5966bc382efe896416cdc50004720e3d2e8e72bd5db343a87c12779cac7d919bd7ef9f6ae6c23ca112588345aa78fb843f51d58fdbfbb4b04d0415e1b4631a2f446f621d4e8b50cbf6fd84fac199389e785151e6b7a7c4b0ea909cf5113f3f4ec44f5947a69f17f92abc637d0bb1fd09ff688bed586320c5456dfd78b05d12b9f1811ca32caa0535e4d38f7aea48e32d3c9940998057c566431560fab8555f6942329a2b4c07debbc5b1cfbb41f4082ba362a5062dfdd5100a2fed117a37649b40abf3bfdd793ffdb6849897823dab86fd98e0c089c31394ad2882248e9aeca84244c42911a6909c2592bc0ea313cb1e5fc4c7c0b2fc31229ce0771d0080084e317e7ea53bb21401a1a5bfb044b7075fd4541d1c66d4e3393c7420458796a00ca22ae7c9c27d7134e91ea79020602cc4d59121235fa1266e47d6d45ef14f052682f83d64cc7098a8473202498f88530679cb864a0cbb940a3398b981c27a04a417049ab4dcab9577d5927a7c70a63859163768f28383265754d36141ba79be93b9f88f2053ccef8af3321c54338cbfb25f2cbb888f065e40c4fc4aeaea43553e721627512d91bb5b32e350c988f8da08c9b26e1b577c40794f84707dc31eba941606f903ae7055ad72568ceb84fb25b2728edb32d1829ab00562c305997674a588c9ca779fa9aeaf1b2853710bd061ded76cceaeaceb30d14297a03c33de6100e6368f7c8ffdf3c0661e7a75063da58bc180fe5282ab1349953dd2f4cc65e9101b7784c4a2d8bc4967ece49dac708b5bda88f0880273ca00388e6562ee01655d7eed760d32a9f647641d1b5434a2cc211d76d50bca64b13c54051bc70bcb173a0ede1a108373d43be01929d14c192488e7d11a0f1b88eabe3b29def7687f6834f39f7a7aee56ff31dd50937b7161f133893ffe3e98f8726ce6064b206b1e447000f790bec58aea556a22236b83e499774e105af1ea30aa1545288f1d3da16c3e1effe926851bc2fdf203acffd56c13c1e4e824fc4bd16ab98bf00ad424a0ca591005ac96ea9c0740302b8791c5d0e91e77916722c3c0b6ebe1d6c1ab48ca6860ee6cf65d400f6a3432b8f074e340a092e482828ac2ac0937197913ba0ad902a840ab90fa2bab91048a608d29a6672e5e91756a43ada69f90d02f3dc21c3d70f4495619cfedbf9336e715853461f0d298a4f9f8d1fc390596c22d8d2ae0e4650461b1e4ec377e4c583d88b144b45ead3976808bd7429ad01c4a3959d640b87ab6782c9a92b5ac09b9b330a73583126106a706678de101a331a5f225059cfcb6befd0956ede7c137ba832cf8d05f20ee35320869b8c257323f0f68861e28220eb467f183e479365b0dbe39a046d8ed54a59a2f1fef79205af2dbd92fb1e606dc53390102625c80d8080180c71ed31ba3487832cfc17cdab726114bde6c4930553ebb70b9d0b36af9b84fdf8f8ec283363319881903b1e6a1c3288e2c6cdb3fc7a837e8637f6095f90772a9128963abb5c782442b15032e3c80a431078e19e58c2e8295c67e073d9eb170627412601cbb096d20c4950ce669b28826bb4f693ec27e7da3d3e91f83a5cd8e4251a0c1ced026e23903312f8f64fb9ed2cad1555b44466e1dcd878b02b2787164b52854e806498a06894b7508a7998a2ac479cd156885c9b27155d41245ce4d671866812a120399fc2181634ecc8252a59324d1257311b0e90b9a51a09f1a6e95ff6221d119b398eebfaee0e5530a005549634c83f611ba2a6b32d3ed1fee452f55d1c34910a057605514fa3d9580555c8e22335e8b833b85a5942a34c55dcfe63b7a170f48c1de0c5e7e573dba7480d9df55ef1ef7284712a79ad8e4161731e51fc60f95acfbed2a634aeb224beb976f81a880af3b22a04e83669dba299a04b1e607fdd93438557c19ad12ca4df6718006ec2918b5f3e9d423ebd68952f48e047df77176dd752513c5181befb463420515e597cd1a032504017ad29c45cf24bc8f2039ae278ea6961805bf59293a009bf711d5416d4e53be382a7c01cb78c7c557a22817a49afc39647ba9ab1e1d098d50ea539b49953e4829c4b439b6964989033cf33ac29904b35fc33531e5683e7344c6da282d209b4cea3dc19ae82eba688beb51d4a2a3a45aecc499baf09baee71eb92d2125351e92243f4378d7ee4fc5bb2d862673a85a22130d2449492f558eb7b57e446730958789fc91ecc4c125216d66cc0c4651d5aab51338ae9dff5e895f2f886a58a334438a84a86e0e26c56b03481ec57aa88afdca6c802cb928b3ba3251a4151f8eabddd66d9b8512163ad392d53fd4c761e65a213471e0a4e888a32a25a3b1ca7ed61144fa5fe835b00d4013b9666063ad55cbe7f1842b3f0bb8aeb5fad70c442fe90498147fa225ebd5204c4238b3b7af9f6c3c4acf3ea023cc92898ce6db8c1a9a92b7693b800818a8b4d6c89ab603140729fdb6fd404090803934a077e333e549686b5a98bf84d51b42fa1c45b967bdf2c5a97eef2472855d11cd32d85eda65cea0bcecfbd221f5b7835f5dc5032558fa80dfb7c0ced44aa2c5eb74604ad277605ce7b71a2efbafd08cd90c5d0f745c175978cf135e5b7ee23aa90e3bb87435f08a4f76191087667ebb48f1fb602700908b0eec5aed19cc86bb95a77d512910c04c6560cf5f5803d70eda1a83a035300fdc628910c5db01c84f115ed57391a7f96c1af04a79316a6bbd18ad4e3d7342feeced5a61ab4642aa34888e373e1aab704166fe11f83cdcd1549da375ac98b7034b9b2c04993676206d4210c5c9cf07a0db30066e6289d8b95b458bdb2df87f5d15cbf2de026a57156b54c232a18ab40a733f2230f807725705061cc407c6aab867a4eaa3da4467942e21fe12f999390ca5717bf7b58d9c885b543cf3f091c530a9a2e15eb52330a77e600b93903e0e39dd49264c3361f9062803385cd4045c2190e0b87bd80576202e8f5cab23f6a5de1183bebe0c6a6e7dfae95963a62b614cf7b554b3ef7b222049721ac694cb50943f012c1e9d2131d65bf9368c4184a2f33e02c60d157d155c7a9e7d1a9de18c853a7d4cdc8f48f75232a6cd18a6f77ca9b1ef68f4429b613e2ea8168a20cbc06e039269c338a592e0fd1a0401d9524a7a64504ccb12cb693483764689c89e3a5b00ba32a7f40bc41d3e483d1adb06ce98f6be3258afeda74f7b166bcda9f97538d5ba656d1f154f89088a5e5de74bd0a375223a487c7adbbf1f7493b2ae27033019122375b5e1d01236c352e82031d7504d33b99ff19c4941298d92314f21ede3a43cf4e8bf947d0b4bf21638b996bb36b5a6b4f74a283aa9b1767ff531932dd0ebb7fed0562396e5581f20410297997b6e61a6b369c1d6955d937f5a44d8b280b068355a4df229819b739c2a793dfbe74e159d61b44124cb44365c8dcef37f0614090a3283c5f115b3a2750b194a868ce78c8dac53c16cc7823c4ffda7dd883ec15b84db907bb1d6b73b6850870b31a20b368d05bca606e7bc2c66e6c8b46faaa37e70fe98de24950e19bd6bdb4bf3ee1aed822d62b90d36683403ca95339c3538908bb82d706dfa6b33bee0391a9f3846dd57df39c5c87c15e9e3dbc87df31420bf0228ee3f3033db8d4023131e6495582ba8407f45a3c0d42aa5d19ebda2e110234b55134953bf20ad951c6e334be86ba72f504275b4d1391895ae357bd74cfffda96286cda4ef002a962183e3d3f860281a9b4c341f6cdb9eeb343439bb7c4d6f4cf0675d29c636e1d7853103dd2503bbedc586e373b6e5137a272cb341bd4f5c501d5a8f6739101a32587a4063020092109947228e1548232855f866e9f0ddd1e61e295ef2e55df753c33dacda56d0702f50e3b6bc6fa15e6c2d6c3bdbf244049ee51abe33346de630a53e1a05a8a76deefc97f95b4f9b4df377399a4439be42639562fdbe6474bd3f7a1a257dc6ef8fb9e7845fde71aa1bfee328c8deac69dfe8be18c6e78d5c900824ae93af95fe1654f775a87050d9716678d811e535593d7db17ac0b220a87a6d09d6397749c19091a341591007200bf2af4dca5b869dec8fd83c0bd8d26b68b238cc479cb6949cc2e0f04378db0ec763ed3d7cc07086afd25418ba8f0a861b198b575f53a46406607537b0579fa557bf17439932515fac444519c7b4770ad31e5b53a625c2cfab032538ac14e5e1f052e0e3e6448eb70a665fd66b7d8fffe04d314d034057e1966bf97c89db656513c3a8c35ad4d126f0322c6d919ae63d6579f62bc5cf1ea1455c4c6b014d32b099c7a975327a495ced53a6127fd6e049c6a1742e270fa7d78010b7b3c25977c998742528dd34b46009f75e3f6fab154fed8dafca05d74fbc9443584a156313e8cdf9391b1371383a21db22ef20eb6a7c3365a9b715b8fbd50390338ac2ad69bb02ab50c987583722b03f019831d9effb06a910655be2cacfcea83232d2bed421e3fca5aa99df350212000e46175d7440a872134c9c564a73bec0657d96bfa5e4b75b0afef5ddfc380a066d72f78155888d88fe6e1f16f408faa51996ae98364e0bc383cf6ff2521c6c77c283decd1ca6f3470c379241f6d8f24ec83b98ed8c0a0ebf3f3f6c8407a51defca174c5039e2cb8af3a31c3e5f8e163ba35536520a51c68d20640144464b3920b2002423bfffe7e3928414b2e8166b598147dd9b7881ff33c8e2c91f5e9696c870ef2ec2943097dec3b2300fe27259ee7d346816879c5a266a48478bfcb27dda4d65114ad205c4f92120f8e71dc71393a629df3674c733e8d8329f2a0261ac3687a75048059e2e8bab4b509dd39935c40200eb0cc7aa4f63e6773a074bbb85e010640146291c63e9b000630d48a9898eeb1093a1aa7d52846c7c0728b45b56e511b8dbe3f3eed2d1b29a3f8e709973d18c4abf3b0bbe590c70aba0c516a19fe3da9456fdd20c7d7cac258bde6e2d69f6d56c8befffa48f5187a521eec823b34865dd9530069d3a211c1cd56a905d105eabafaeeb0e536a201002491bb2054a7bc5f5d5ba92f8646d858bdafbbabce8c80a0ff04a0302bb301db914e4c3c6086700c891081d8a4546658c00b478f81772678ee370ec048b3eb20bf8cab7e25f3a11140b0f7a513bb89e2db36a0b0ddefb06801036a8687d8325869f9f3a1de40a640e106bf546df426722240defb1ef9a1f3a1a81e8c063f01b10c09edd405705024ce11b00f75264198f5bd6521c68ae6d32c1e3003417ab947e334507a35a12cef9e9003c84ebf7b9ea80270bb75679303ea1fc3f640283781bb2bd8ddedd466f791b101446f7a26fa5aa5b41a5e8ad94b0e2aa829301f94898df6f02d00b1a0cfa779e9530ff8dcb1eeaa48dff708de59c2f673c8710b33fffde679bbe8c25e13a963170128edd7562c6d5992977efe8b6e3fdaf721ac2e31e483d57b0468a6c0f32acb620c0f05f860d0105b08991982daa095961083d6ee2d0d8341ddaacb01511214d0513248c0a61fbbc5febc7055cae10064634ea20dc15b1aaf7ff169bf911648934e605ead6fe96e0f45b37cdfd4c8ede7757c3708b38c09deed513798e69316c61b24e41bdc08f734552f33d71175e96a964f90ba07321dc6934061476ae6b5ecc344ed0b629bd93ac7f24f1cf203acbbe59ce9a357e8a171913cbb34cfb01c0f0e7aba1f429854de1e46cf9a00f38ae14741372cbb6317035432e4471043ce6cfc083db11c97945629cfbec2cb9a84b64197526b141c2f57a776325590d1c75d228693cf226dcc5c6dec4161c7035b114c589694612fa15dfe670b2c7029eed947e22bb20752f10ed1d4fc6d54b9ee568adccbaabe58d6c4ba34a0a0f160aa5edd322a3c451b9ad9698feb7b8235ea953241c2f49c0e638e076724ee58d7d2723b347d9c85b941ef2c0114e7364d7c6269e4303d4c9714099b612515069957140fd3456c96888981998cf1ffaa2164e38da326821593cb13a689ddc71ce1bcea26683c949d38fc47deb548c34371c529443b0fd45889bb1e10524e8e65e58fad88e630ad04a92cba11b700dc5fe71acd875a5c18da0b0e7ffb7de7dd7ce93ab696166c57328c933a039abc7b5b37bfd7690d8bf841e578b2252b8046ba54034d725dc2c5145c2a547dbf6a791dfe367e553f06ad19d9318e7b6c14467b816def1e459e6d0bae391823097afe08fb786635cd64f2bc41f0301fce6e8a537247ee3ed73069c34e4cf50223ddd8e7cc0e8d105a0eff8ac1b8d42a7b47035785ddb26d9cd558298123cbd8500d79fce6122f35d4b9ebcafd21ed1be7cd43b7284ef361bcbc51f738356c0418d2f690a314185a11fafe20249599e9e2771034418146e26a425a1bcf9da8fb478fd2f3994ad4aca7e9f08a459960f333eeebd64d6f6f43f0c676eb4b1bd68562cc2061c14360d2cad6c341538267b63258df2eb293f2a509c91625735046e5c11eb822ff2dc67d90ad2bd07c8ef18f5297636f8c17b9fd6393214210207aab183df350555c2c63064c0399253708aa637001fe8316be0cd91a7db685e33fc973eba23af446d24f472b24e5a905a70dba6ac731a0acc5db067b322bb7874a3df1472bb3318bdfd44d62b31f09b1053f3ef0cc0dbd505a321f07d636def6bb59011c5800fab2ac069c40b6dc5940ec8e21f0fdc8fac759ca8fe22586e6d4f975592736d83a02c110837f82ac10109cba4c5a46c397627cdf7605fa93a56ec1ba6c840324f8d9a8859a000d92750160ffb3802955f2f38e7774005d9961b8b6d52ef80ec925910826d165a2437034b38268c965b05c1e6daf879fe4bfa93cd9f0c05a8c18c00812133b52c7613fc599ec7fdc210c0f2957f19b4d6138a2496e852ff25aa460314bb81bcc473187e47527aaea1b41be94e345205a81e444381007f5839446c32d0d80e631e7a787156891b8bc0a6497c897679c1b56ee40616fe08a942f3244a6e1ce09c27b8768df57715d9f0fd02b4076c4d8d5502b674aeceea43786e03bd2980e09090292c1f256e0f53dfb42471c93ca5705695683bdcb57ce65f2548214e854203bbd17f67056af2b8ac78d0768793c53a541869baa30e94c1261bc7ff292a8763cc1ddd385f74505b4bf16a3748540a7dfb1f99545855b49bc926b8b6df56b16ebac9b1c70a8ad26f26b81673059d73e781ba4df93eb211706d7e158b384c5570b8f942ea030c465214eaf3a8748e246f59a80a2c6fdaf63249cdfa3aba944584aa0a42c2d40c67b7e52c82f52a695da4ed534aad6695e4115b4e80cc00ace798178e0591c9c41db18dca67c6e35288626cff21f357228010fdc35a94f7ee6cb822f510d85d948fc006983ae34467f200fc1cc2fcb19142d41086335460440e9a35041cac6bf09d53baed841c5f69323f35ec054820633f1fdda41fa802db21a5e0d2b1ba1180b9f555ce06aae1d3a2a756ac2168d647387d35e211c372f50543e6aadcae49ec3dad59ce979f5f39b35e689b7a13a83eb8b2c6a9e08afc1bd06d434f4cbfc26f3af220bf2bdcc9b784f7d8ad994d5e0a771811e41613133de66cf96646243b25f40a9026cc6937d59a9105261c033c948a3f94712659de4a1544089ca9f900c6dcc6c5ddd38e5828339b0483a5a60fb46e7e09c6a90ef3a133d45fb5d82dba8773c2117a030d4bca1554d05da9b8f5e1671d809e6977c72586ccf000c7108f7f55eae8c5b3b626faea6534037e7ab956b47920f458d3e1256c48abe7899f7136d0901c01aff0331ad1c7027591ee7b1b441f2c4e148f62f836bb9a092b0327954eafce0900275772d011fca4105c2b84a3c8321d2fdd9d8bf21c2c465f1dac959a9f8d2f942670b2deeb555ec83eb3a74f1ccff3b76f09961bc03216b32a4bb3ac8caaddadd3f13d4d556c528d532a4e6c04cce386f3f7c019defe813ae3697557bac6451fae93db102a4b3093d141d07f17747464c0cd959782f31142293e72a84fda47f89a6e3ff14c81c8447489ab29f252b12617439ab466337e81ed3f4b04ebf0b5a11aa6ce4cced076f781e1cc4c263b5496160174beb2e05ae8e2c3e9e28428902221dbe2d174fe9f65367b6b6d78f9a72e4932c60b23481d29d6554902a9c5d9b606ddd71c4a5fdcce8591a12037a51a2c5834184c5c0d782ae459764b6ca755b472af8dfffde7e2559febb9601b5ef951ab0fb237794aac28f16403d6a3e469402f3e07c9938997354f5b55860ffdeef9a438cca205fae3aea09e6d733dbbb6c813b5b058abb56db6768ec135176aa6356003503153d17a15fc3a96e8cc54831a04b3ff3ee0d63fedbaab25727a44fae79ead5122a00275ff50b74f65a0b2af6d9062eb7f18955c4a7256360f4bcbb78c6d7375ef8bd1682d33958a1a098e6d54e40786132e5950015261c209a2def1cc7683e9608b4932b360c4e44354c0fb804f2222958f7db3a779c9109811afc3947dc28716b881edd9fb06424e85ed714c2ae539107255049509a9f2aa88f837af4205e920ea3fbc2301e3f1d3b20ab072ec25b2afd108092104c93dcf828f1b0c155f8501ab78bc2f0432d3b78e632b30971e58b291199402594477e792878f4254c797bfc92585b3098428e959a0d0b83e5569f77c998e7296b82a1b874d3be12327c91654b1ac86c2408a524058f09b90f472dbb0efab1754a5fda91abedb66a85a35d3654a1a9ae9bc14e4385daee5723f487fa2006069fd9b1bd9c954569a0943087d5c021fc4ef3a372a3f7df34d399a5dfddaa62a6554520433721fe47e14f05226da0ad55a8c05c076f9d1a28b4335151cbd7e44bd8611ea48aedfc33861b25efe46c28074caace7864c8018658643919a1abcf12a135c90f0a0486c50411b212a79303a1510e76cf10575e1599a1ba5d1ce29cfcba68fa106990a815f2ee5d3a634c743a1eb494f9bca641a8928f01d6f9e1f73396acad2cc19668ac7e1c8e00af6b28db21362e597d8cafacf7e8444ce86e7ca8666afc8a7e7ecbc22b311c239108c4c9348dbf96870a6224c51b8beecfa5b2f894badbf8329fba9ca8a6f67f53c70ee62e63c8b8403dc32058f4f8177821d9155de350806ed2efebb4a4f002121ae70be5309dba016a59beb1b7cb5f22f082df5283428337178bebf8d4c1708d45efe82acead7b4410a821dca93d99c2d58e18c39af3418c58fdce8333255e142178d2a0c9b48ed86392952ef348be6edae7dd2a3de999cd7f4e059eb068c8ac26a6b96b537fc6ddd82a4f621a154cb5dac85a5e5b8737a1d9ccddcc021cb978261f41e99fa886d7082a152a3fec008c1c568b5e6f1167379aa493a4eedd95f237dd4655412fbdcf97e272c06776c1e0a6dcc416bd39ab7fe04c49cd6544ed39a119b003b23100249aacb40e81e8ca11bb221a3020b01be13e7dfb71b040a7211c2a33ea036ad3ba1022d5dd2271f8780a564072141b13e602892e24b798160d462e6246019c096894eb3343c5985620952794aea9147d4ae302e93238a0161e8976c1b4726f1f7410972d0fb9958851881e9462d4342ddddbf06b8032dd6da9db51fd7caf37046fa27ce1ce3ee2b675b34fc5452fb843711bd38b64c043a0158e48311d8a1dc1cfd2443108faec1c6fc1518596765952457d61e17ef51e7b7442e54b4be66affbdf8721c32cc17ed38f794709210df2789b403ad91a6236382237b4ec126fadab4442eda70413baff178abac6a37b02bf88e3ab76623a027866eede827a20b7a860f843e48960fab2ffb17e77cf3f3820644c98786999cbac367693b3e0ee50894f3df5f872d19f50c96ba4c21f165aed6987875e66b35eb6f6f1b964aea139edf0c3a3124b90978dd6a37460c20d5d2b48a7c48635f8d80a1a012fb71a7fdb527456fb9c2a5dd7dc56c0cd15ceaefd74f402ca8cdd451ac7eedbc70cb5ee3c3612c5f231e3e5aeb1d73d50f921941bdb12679ab2f5d5c37dc9d4b441bb733b7be5d08623934df1ed0519e2de97f4d842aff68445edac99e825c77f1494576347b5271b0b449b50bf4be2562eb20d1711d1d4c4e180f99c3693de6d6bd30746de09860e27bf300fabdca836594270494771da9bdcbe8694d1eeb136b5e488f8cfeac2f272475e530fa43744ff43b295ca95aa05fb52c1612c0bde325785cd4948cfcc3947aee6b36ce8855f1ca34a1b0db8e4d16f409c1b5db58e0b50bb7cac1411abfbc90ed5e958800b2ea2c02f7fa382020afc4cd1292c40c59040796c13602333761314504f37e96c9277fafe2fde60b14aee76260b08002e4305d07ab0559d04412319206b802d5ccd20395c40c2f1e6f9b4b2919610633ecba8a3d0d69f0586e153494fc48828fec3dcec0fea770010fd638b249d0ada780c46288910bade0bf9a3ddf8d216e09b208cef15ecede08181e1e78058846a64adbf75d1b49791ce3f6b04ca5093cc69bcce8fbe3104d5a868a47f466a76368faa1941dffb875dfbd4b204aa49a2ece945b64cd4b859cc36a5d99c7aaedba10a6da992699691af8318421b0ba8271110b0835ccc84e54dcb4f3c94145c0848ce7d60108257133f1a8516f03d4cf59e4f6eb3172240cf072dbdc5bba7c2d1932116fa628cce0ad0c0a62f4e133c445a4012a905dccf23a0e744d4ab986e60ab272de57439c42759afdcc92ee80f128614df0d927f114d9250df34eb4bc385aabb4128be5a3e4cd591a7368b3bd471d4eeeddd0d35bf2358bde422f7638364e5ca314529e56590660863b7668479e649f03dde49e8f1b7b386ae081a274f8fd68f1d642ba45f38f38a8a3935b05c4d7357aafd13ec68d0aa1fdf494bd089688ac9af3bfef2e573179ba511162169f7badcf55a49b8b7dd200ab3f89a276fb95c5ffbc36ee755312187788046c6c9fde95850b14db205c01d128c1a99d69d4c30751eb5eae4864ab59552274c4463a3efa9a12fb7e73515e640a1620f44fea1a64f632a4e1a1c584f7461108fa98b529ba4a9cf5f3c812f8fded36dc6166ecf3226069a6b512e7bd9e08eeb2ee722f0099a23173117b66cc4596580c8139c852601d51fb281562c05f29ff9ce8c05e3ccef0d4418520761a5836b79a9749c59557e704954eeb0282ad9c1130dc2c4dea071354a6deaaf4b4ce54820eb2e76ce44393a398d2ddec646aa12cda8749efa5a40959cdde2eb66c07e0c608082e1b10e7b4b326213cf1bafca22a9111c0bc6d3a508464fa12b7da7cdcd545915be3899d0293b9007342467ccea04dd378e12c8844ebd8b695d95a94993f5b46b785160cf9b4e4c096594a5e3b8e873d0cfd39bc6c0450f781a5311e69eb147bbd12c856c8ade0d2851eb29f0f3ea10b6f7c040250bc2997b6d145611e0cc096b0dda413386e26878d33cb1e0354fb0346f029bcca415686944163d912bc256729fa0f2d748f6c27f383da3e6f0a30329bdefc9168981c8617670f23f0e5634813587aa82721cf1eb100937e79661597fafe6ef3a74910573a03594a0d7e9eb53ad474a98a48c488de1d70f224851688f49486ba2bf33aa14fe3f17f41392dbcdfa55cebdaa07967bc9062448b310c5a161e7aeda517b139be1b777941e5fe7a1e14be3e1fa18302f117e17ac5b13254681dd1862804d49f8670b3062e94c43728859f3f286f4e2e42e7965594ff4f6211d9fa21a0f138869a140e536fbf8d012d60e45c49ccd5cb7cf2e611aa0a670b4e0c4bbaa423305601414415455fe5a0b22b5ae9c3c14f9039c7809dc66a28bc5f26461eaa4796c1cf79ea7ed0e94e7e52a642db77734ef34984f894092ef7c5c2c598404743d962858b8beb3bfa289e56e6107c165a853f49f7634c1995fcd69c2395d6aa6fc4c57d106c5fb9b0d12eba4a22c09bc9b8237a07a5a0cd951947fdac37a4cc6e9a835292a6d489ae91a6ed0756dc2f8db297f144c97591cc8d70da84878dc4bc05794368c6d6c29b2055d5b54cbf7dd28a22942e9480110866664fb30343ceaba86357c26449f12ef38fb18620170003ac7e1809386f296d8b0482cad98c98aa56e98978b32ef4f5e7001bc304675594861a1183c71637a4f12efadd85267ab192197510bb86d49211e9475fb1b85edbf2ca886196f274edf29e9f1ce1a5df5e237905aeaa49e96810a8abf2ba5ae90cec73d8b82dfca35a7bfafedb2c736752390278318c26ef19ee870f7c26c704ec30251668e448496e53218416a22df41a2d5f7114a3b39f4ee4cdfaf8896f96cfcb396971fb20a0a5c5c0e57c3a8386f69b186394c4e5feb4447303293d09fa493be3c3f14802557869de543a7882f9a140c00aa63702edc20290d44a61281e5fbe2afaf4f646b51d79122d81ca2549599fb0b812f5ed5f297e9245b8b7883d24161a5c74e9f3bd04043d1b72d652a75335d191acbb2635ba4a521f8195f15f7ea7c80f73214bd45767129ddbcf0acffeca859f1e405848a0ca4995e9424a717323f1118e091e39d7efd1861238e9474e60fd292e3b77fe58b655f76ba12507de5c2132cafdc038677d8b15d8d97ccfd4ab07c0cb074c2ea195143b173ae002bba737ab11d9f1c597fb0f839ab66d5ab838c2d53d2ad89bf06bcbc15fea8a71c2cddc4a186e752b75a3674e388a564bde04f0816aa37d091957ac627117268b1b5870fc0b30ca90a648ed7f42cf8e8acca112c55cdd6e640c61f4e5141ea793e5d0fa0d0f6eaab5514c43c5080b2be12c1ed7b98f7a8536d8d812a97b3b53cf45349aca811bf7cc7db283e3d3cec71a42372cc3d6c223f56df84d60bccc8159164ae134c447a348f3a73ba95a2a960613e3284ec3dc8ac0408f3fbb906534339e913bd43626f10a0065605d2f667ef8f70a44fb3bcf590f0d08c3bd3b72178077b0502ddb0a06ddad3c3331eb59cbd7318ba6032b7d3fe87a60c53580e46490f6b3c971db0613922a03776fb39c2590f14fe988f4b58f8ac5711b574749095968ac24a9da6c3abb1ce8f3aada2493b3f3244f44ce9f4f94b9e1a104601056052aa028f1f3f80f0d096b74921ed8f4903bdf9bcd9dbc4ac555397939a9249f731544d9ce9647c42a540528095b0a16146e3f86c66e9a1f45f71e56402d811b610b116c16ad23631890eaae2bae501dbe5e3a9759caeb29b0201d5164053b7ec5f1d325efb9a6fffa9033e3c93e137d5a0a07f22b4d537051024beabc820c656ac45054fd0603541171459a776981881cd4bb65e725eca4bb6b2ac1134375c05c3423609b82c552cb4c3bee7109c7bb7deba1cba16fa0013a83206ab53407f13019be2224ad19486cc1f4f44da4ee7ea266be4d2f660f22e7ae2884eaf530c9e774562de72a796ae180b6bd0b585731b118a9320868139d2d119a5129b27bd0ac187f347079941c721846ec7a3e81f6a8e6828fb7b38d5aac2cb7c11cf263b035588f630bf4c0fe3a533176e7749050c2a63ccc62dda3810cbdbf059460f935640aa46fd53dc748ad0d8fadc81ac280d3aaa8f59c3c8a993fa18595e4e8929b29cb802b3c28fd3ce822a901e5917c1e391820105fcc315f341fc40766e4fe3a6de6340339f65f61ab219c638ccd38f3eb3d65c7013e8d08fed518a78cbb737996cadeb5ef288b2c257575d5676fdb010404ed1615672f32c705d6846224229f282c77e8a1545b0ecf9d5a8915698a332df5c50b4093068a4a34325bf66bd58c0919c88b61687299289872216201cf4702b40f6871877c13ce0bb8c909a6781bfdabd56f1604a4bf569ec6d4637942d046e66c0d3aaf0a89630ba00404f7225c121f54bf760205685d7864363cb8ea034891dc7c16ecf12acaf0ad9f81d9665b9023c68f4c400d3b7e4ab73b4fdbeab2618ea3b96eaa03bc56590ed868d784a88809a6aa18ec76b6f2170b8db4c0c5b09b1f22239ed0e8c035403fa0c89a20d4ef46c9e56b96eea83080ec9cb8853ac965d19ad170f8c37d78409041fe6a484e12278a6a54632caae72121d3b7a248ce52d684021c74ac44940c0deac1272dd50a805ce61730f670762380c078cff1f0fa0735306c8b50d9ebf8f65f5584d3e7096000f0fa4738ebc710bd4fe313f9134447bbf34ec052767cd9d62cf4d6475e74eacf59d616548284f7309b934c6457600354cb12e0b2779e527a38f22f503d920301d5a4ad78ca881a8bc8c3ad3c3d5325e80df0d5bec9e8d6c2413f64ba77b2e77cbf73ff90396a47d9099c988bbf5077fac8d783b74b768dbc8ef76e5b53e90123e351982b7065fb37263d0cebbf16a11b6fc1b87a05f87aa1ad8c3d39dcb26da8c8852ecd5983904eb164885b73fb9447191f71353424f51eb0ec8d67d98d303c8c7f5e26708c96c9cc19bfe8a4305e8bfb5464737331154b18858b9fad5fd350f816afb1103bc3a3b38e43374f679b8a20676335cff8fc4fb19dccf8fa479376d60b21d4b0511239223ff397f6f9cdc90244f758131c80197d72de5f516adef6ce36ba74ba5fa71a4abb4d3fffea74b6ca83fb0a36e6096faf053c6e7e6c290aa1baa271062b8afe6c0997fad6c38dba1bb87eb80d20ba7e39f383acfc0aaa74b404908244eced4ef9fd90a2d9553fe9d9e3625c13c1cd41e7bb7c05d400357e891438ab305fbf63ee36b0f777b29d04d875d5b434362ad224649cf17c3ce0ab3e452e01dbe6449d930e00d4006cbfddc767bac3c111625af4d400fe80c40968bfba1052e03d1e1be15caf58453a97e677a47d9884a228940961d53b81dc77030a396249618f5afac1fec111e2dd138522e184117d59bb1ed93e198c9017ec4c29ec3b4c5bf096e1cc2877d023a1538cf9746e9e42917ffc4e89a1306697d4d998dc2e4d53adfd466941920e6ee016755044eb897075c8aa18f24f5b4b7d080be06a6242086e79d17548f0056994192ce0c650e08ed63c9ac414a8c5fac611bd5039fc6e259746f1ae8c15034a3e3b97092d31d229d47141b2a1d5ae28109366a50f2e99ab5c6e995ce7091e46f73c11a15276670b3141d6f853657bd1f2ba5a90821d2796f26e52289097401ba9fd0b576c951ebe046150b7ca800bbd7801e2ec08a7f1b8a924e14901c53c83e17605747cd2fd55e407d4573da0583bda9bb7d436abcb8850605b68806817fda40709774b198279055301e5d98878b054b2417a26141b949128f380343ba4d1daa4e8365cffbd67e16b31c388de36cb950084c62f0108dd065132bfe6c5d6a5f4e908a0c1c5fadaec151b26215b503415e5e26672606603cca3026032a5667e76d3242a34f6cad004bd529deb288a639ce687b296a957e5457244cc1fec03e28f9802401ff4583ba8db6c77558a3613c9af058aa96aaa567656a7d04285b8940165581da79db9c8c0899189a7ba1a5c3223c73508463f48f804b49a7b37d876f0f2dce6e4433dbc5d4a161ece82e81c50877862a6cf555032884a4a18699c8c5687b6bee12202c351afc8f5fe9ae9717bb0867d6977691f01f74a5e02b1fb3af949d9b583caaffc6adabc0a558297b2636bf291644a1d048d02064004042afc0f7559b82c5da69acdda464496d86902982f93e46cb8ce838d76904b5aa163391031a099aa115fb08efc34df527b8e098fb412847fc21db350c8385453b6271bb7780aaffef2ac8cbacbd50a0bfab08122a4c09e24c0ce72e84f1707f8a73291807acabe94d5cf4364be9cdedc05fd858f6e7c0acdcbd9e302a0c3fed2a3732d6f1214abff34ec662614850f4f62a2bcc8425bbdcd630abe27d6d28ee733b67a980ccd683888bef1d910487a846c0f3f5eddcc2cbe9e926b0f6d8f52bc9f0e8d65ddc61517759970f28a2c7bbfb9ccf564275f83d66484924b695065270b12f00fd4d74a7bd3b0e6ca0240d73033816fda09f8687996f19215019982d4e202eee2645917eccc1d741f75e62bed956e1f6e65db8efc7222d0f248e2d064610076f6067ff07d8e6ea6cc450878a6032804b25445b99c9b6add9ace5ae2ee59785c45ca9020b1889bc6bd06a349e0a282452675189181718ed3e511d76fb9b11177b7ca1be2757de44e9ccc71f7b7bce1a66e16fe65776de32e03ec4f8d0a0e83cba611ce8bc5091e112157c1327c302c3617282586a82d709fcb846c6dd56fdb1066f8c4694454038dd04d3215966c5bc7d53482470aa868bf4999ec623586b38b7383c814b5d0eb06046851943b27ff80b03760c11646c4cbc259aeb997e403cc0d3c736ffedb58432d408e6d3e4adf158972be0dc8d374ad80f9d0677430886a1ee8001e0b7f0b380d713a6880f481c6b413fb254ad37c5be7701d8cc3d2623c1d0d892e962db36d4104790795b2ef7cef04de1700b63ae3c97462b5e11f504433ca8da5a81e4287caf14848b53cac98b5cd0249a1936441cd517ea62afe9dfbed7668527d779b1d2f42c08cf7b134b45e99d073d2b9f35f140ce1323c58af15d32588508cd81a535c52583fde722b78459e943e011c963ca5cfa56fd13826a8cccf31b7c6982bda74730a1d751002315593ed9e4c57333d4e8add10ba56f33d4ab55e2d4cdfb3583d8d6f028f46ab9140927e71aaa61fe66a8ed14420e45c911e832e60ada91488d35d1551bcc94d93d244ca1154bff0704cb1fa2eee43dd844c7f153ec8a64d1758d2583d27f187b3d6fbe0063a2342a9dc1a8105cd7696825ae251e88141c066df100cd25ab7b49398f0fc867c8d31badc960187599a439cfb5d5649cae72371ee1826c0fd3b23183bc728637c31783d8148b8103a4382d3589f091be8427a39088015f7920051d0c944004ce3106c8a6c181297409049ae16d793748dfd771438e8c7dbee63961d1f2e648973ad3d9e66109d8546dbe4228ae2b147bbed2bf2720403317992bd2f336ac75451e4f300ee27340e3b9c7e05fb33d9edade87f923fa1c449c1ee15b144384c77bdf2cb82f7c454650e2771ad4ddf5893fce4cf7f6ebee01b973d8aa4bf06500a646c97caa6ed17299f8652fef80b1ac762335433703a7dd4d158f145f4211f3056b6bded9cf937d6a2132dbb23b6b01975ed9a869ec6b0398a83df80051c5f443f3d7a6dc99748cfcc4eb8a07fc6d2262fb2cc84831ce312e8a51be9f171f05423a678319b05d85bd8bb5723b248e76a565720f3891f87334f9b98d866512377a0c4814d9f3e3e476170b9519bf86c688e12da032a5f1f706f5fd0b7a20448679727c8073d9884c83bbbd88ca7c5a4d52c6c772528c728208ad62adbf229813c5d4cdde92aefbff8baf0deb064e2b8dd599aca974d678655656019a6420866c49c0ddb527b43eac98f3bb509a0d010660fc4a591fd10fe129127da4b9d8036b186c9b9739f6836e3e64048ccc5dbfe7af766ef6b65ecce2d6ae9f5e076dff9a6157bd9fecb52bee42eaf5a51ed0b4b227537ab0db9b708e22237c418e0335c5001e5568259cfc110aae1b19acff55ae9379ca6d1c84fee79c0a261e5e031d29a137c3d8c06d868404f54fc0c160d540af90f75fca0f8951eacd1acff19e9b134fd2979a3c0dbbf307607147cbbfb71e1ee6914afa3d4f1212b6efd1c06a642fa26f72d1a9174fd848b0f21114d0c1ac030d8944524f5fe7e60524e1361d9f0ed3b169c217315eaf937908708bcdae190bf4205cf2e8bd9bc7e41974a68455746027fc550421417c50bea3a418115cb1aa5e8eee070f36ec735ee2213aa712f94a365c15d6e14c6308e0a3da310cd9c68bd8ab0557f06acbc0d192b1365a1c0570910624b54ef59395af53b4a51b4d454654a3ebbbd6acc6de4b9c1772ab93ea38f06f27b51af15c14e232bb509c38a40e9ba6886aaeeff1ebef2f86e4f37498a2784365fe5a4300b4860094520cd2db5a0699e64546064930da29262eab05e411ba8d018b23c22093dd210e6bd0e9ab5e8c349ed0149df6f127538ae06334a70cdabcbbe27458a1cf665c6e242459086da73925062236f6a8087df022e08d65b327ec83f6a210eb7ffa71bec4fabc040f0d28764a2d07afa10e1b02bfa419e247013605ba4bf4a29078806daabd024bd32bef35d22e8236d0243d75be5845d1283a1187653c238adeeabeb7ec7a9b17e58d49f6c878400354d4b407a83a841eeab1a29bbab25345c71cc62597505639a62177fbd63250d14e4e2a2132a44494a9bb5d05044a6d03e52a5e7d567625850a037e2700c4f3ae13d31e92d67ebd9004ad782038929b3cffff2082bb58cdc161bc79e6988479a8e73fce48a2321e577c18f54e5abe012fe28c9512ce3515c310e547b8e259791d74d3292727e7cd2373873e86d2696e8a214e753e11eb64fc9f6e8fc0d5362a593ab5058d080e07f2d393a42c9aa648e097bf1644ff789d7e0b8fb4ef79ca6f237f7cf693b503edd15bf1d6ac5e85c2e0ef644c1cab0cc341f300b38eb4810fc6e48dd58d23e3b255b3c3cdb3703ddebe19aae5796ea03a24135b96497b38679795a8e5614ee552a781de29b77cb65d7348e048913d5b9d92052baf05d6e26bba4e2674ee1721b83be5baf63d59b0e84ef4602ea8214d1822009f729527c22d402c80bbb9120cdeba216fd9c1390f06bee746c7abefdd74a6a564cc6bbb261bc7961ada4d1cdf7cb45cd6955c1f9c522a7018e6a651fc193955d451a49eef794289bb8ca54a26642ebf16488092a54999a1e866997628d7ae5d79f0222d7bbf727b5acb4db125c074d0138adebb9fcc1e53518a3de989f42801ac5ce29bf88b9d50e6a0280d02dd753168afd6589a49cf5a468dac63cb83e53a27469684b2dbc3c189af3591f9e4db5144318d9822dbeabf5f5d00b3cc42aa7509151b8ee323ea288ff54a2be6b58ba97234b810d36d90d1d2286ffc79bba8bf605153bb3c15768e8a3b0d77e5eb90770ee046890051f23c4f33e0b43fa2e72455c3777bd950151ead8e4017f81f9532a0b8d8c1059431c8813cae7c284b170f214b070b30b2f423cd884ec197e309e5f0ed352c781a323df0a6de68c8c71490e9323b02b318ac9ba61915204ef4f0239f1dec68730a7752d0203b28b3d03a6575f574b1ed3fb0cda517aebb57348a71601888f9280b5ac6dd5dcf0c2a0494a94aede69f51a5166e6d3db2a70c6e218f6be9c54ca6bfa4b4b3faf329bb8f718f6b2792cc1aae0fe3748dea4973fec949b2d15e5de15c8c5fd939cb8eacbe91d853514e9654f4e7f5c053eb11bc2586adc376608ee0d539e3991f1192be7cd09811a78a514d18284aa7737eb4c595fff08bf70e2e19b0d8d631d20c442eb5db81c5bdc034f55cf4fbd43436d2c8e45f2df201a6c1e6ada8efa5530dafdef48a223fa51171a58fdbff5dc4553fc28ff490b37601ddba33d05f8fc9a6b335824a363c6a93ec3f867231301af935d1c43842e2bccc19326900dc84a4434e80f82a4d2a0c804b98b9d90d8e2da34c8383228c67c7e6a95b6e075419325650dcb48cbe5d322d4923cbfcc6e949c9057916d3adad76b406116f1294700548a225506084185816eea6cf656cac7e8b6a411763dc50d1417a384e98f795f6cd3edc7c65ac2421cf232aa43dd5c28f9d1d76d2726eb71a5c3a5d73c3bde557a70704c45ff910cc6703ea71f67b5b3054324e079584438e31f5c54c9b8ddefccbfcf6b4ef4e30cbf35a89ce355b6284eb464cbd76b181ef3fc69defb10d3418da718ef54148c429278063f2c0d27900f614f43002e7ee8877612a3cd66aff0b6a7befbda59432a524658d079807200829071283bdd447ff0ec6e60face7bf20119f28e9dfc13066761d8042fc6486eb0a37f3a0c2c1af488d0ef9b8a952dbead69f566efd69c56c527fb2a08d77ebcf56697e71ebdce2d6a945498a5b27945b6713dbb74cad55b5039b8a39b1d6af34493ed6d74fc126f030e153fc5c404360803631a702ad18202a0a91f3e57c399f77914564b57d6153f5d6fa46d0af5fd32891f7957534971da1cb1e28ddb6176dad99e82d358aeb726ca3b62eeeb44cfd51105604f53246a9d02ee04cdda9cfbab5b266b8b41cea925b53a41b8f4099ca40d83455579b1b84019ca99f2aed68990ab78850200af2409b1c3dbcaab1d65a2b8e675ac1b4c314b30367eabfccc0a6224f4dad70ebd79a82af28f56b4d451844a0aeb3cefa77d68f30d800b4893c70a67efd201cf0bc2abbf8b54ce524ec55561777b62a36e5ddfaf57d34ca08cd7ead0f43a3e4d7dfd128fffa1adda4e626d8f5a5a93e0d7c8ad5c4aa8b55d5d5341a3c006db8d6971a3665a67530da77305a674308e29d00f72da227817cee27207adb91c05ff422b097be4855d328ae83615107c31dbf10fba2e7ee07d7f29cd662b2da33635cbf457b510783bd7cd1731dccf630d8bb3ff7fcdb6336b0ead287807cd163a61ff2455de5bad8d332f59b8b2f4058ade38f1a56b1c099fa9989c76881ad81bdfa58b701f66aadf25788e153fcfa1e9ff8ebc398da75c0066d3988d2a59b589ae08552ca8fa66a921c4d7e35eacd5289c4b287c160b20cf35a86d297cfb2a7af781d1c5da4f0030a56a070040a3ff80f871528248183091c597000218527fe87ef01052aaaa4808214986882c2141c3da099724e89420f96c6853e3350626806477866a0021c26b02e4b7814581bc68f6d09028e30ac8645ab881f1c4e2c76a14f114e1881c98d2014d102318ae0e1055663020a4d8e60c4ce30a018e20731ecb605163cc031c412170e2fac76a1cf92289a2c79228514587aa1cf129e252d560f1595e2e4ac66d44862a1a10bffa1b7bbfb084208a194f2c55da4b4724a29a57429a5acee3b2d4f9d25f22a07e16aab99066bbbe36476c3205cd52aa22dd376b784dded2da58b9452c297bf5e7ae4a240524a29dd9d2b7dd8552ae594d12b9f2084104629e527691ce08aaed6ce882477f70d0849975eea6ef9524a29a5f476f7197cd4104228a5f476f717fcc5dd45da6738e37e8324638cb25ba8bb7bbbbb8b2a876dd956356dabd9d6188427ffeeae22da32adbb7bbbbbcb36fd8d31babbbb3b8422a59cc15fbabbbdbb63babbbb6180aeee96524a29a5bbbfe04c72e8ee5276777b77c774f428eb5f08a1494a192f8cf105f61cd21015a08573a35b4a986e5fd9a87d42b7cb5992b2c6a441bbdb5d6259367366e4b8272b0963f751b641b8aadddda5fe6ee952eef4bb9015825136085b5c24eaeecd575cb51b84da0b75b90734c2487506951342295b3564c94fe8dbdd2424babbbbf209420853b27561dc34936cb54ccb581d48960f20090da6a494524a299b6df409de5e82700559a34e1a544ab929f149dfea004b492925b6424e2561dc2da5949dbdbc94b2835fd98b524a693b25a594bd59ae45246ae1646f104aab834d09af2f3a98d95c01ba4618a9cea09b920b2184d294a2a9214b7e42cb4d09cd85b5660f3b0835134dcbc0cf4c9b948ec20d181b27a080430c91c66d2ff0257eebb44eb7cee865f472e4e747e7e747e74767f4327a39f2a363472fdca689523001c0ebac7dfbce27fba317dbc1231bc76d3f9a88db7e7e7e74ba75b24a658c8dc1992cc31ecb1eeba0a442a9549d6e1d2a54747e7e7e747e74ba75ba757e747e74462fa397233f3f3aa397d1cb911f9dd1cbe8e5c88fcee865f472e44767f4327a39f2a3d3add3add3add3ade3d8e865d6d1cbac47eafcc174462fa3971fcb2f38412bad9556e933d3b88d56d28834ba89ddcd8df4491ad12a7d9246485c4a9f5fa5d2bf1b5a699dd2bf4aa7f4ef86565abf1b5a69258da674d20849a553fa77431abdd0ca6d5a267dbab8b4bc7410ceb8b8d82e35e4b67c7cd2a8a573241bc76d9f9671dbe752fafc2a95fedddc6815a3b25239a3369d83927a51312f6e6ea8ac4ee59c53522faa7b71237dd22a7d7e2ea5cfaf52e9df0dadb47e373737b4d2faddd04aeb77237d9246b44a9fa4111297d2e757a9f4ef86565a49a3299d344252e994feddd04a2b6934a59346482a9dd2bf1b5a69258da674d20849a553fa77432bada4d1944e1a21a9744aff6e6e6e6e6ea89346a49123a1feddd04a2b6934a59346954ee9df8d0b47a0051a261d93ee8d49a7b38ab2164cbab671a216ebf23222cda051a374820d181c9b7dc10eca32d443710a7b0c240490a993d2ae76d9b0612326870d1f413bdfa7daf9543cad562b68272868e7537d9f2acaf701e001e03139bad2204ccecf87862213162b264769686808eeece4c891438500d4e895aa555fe7a860fbc5252828c8c767d3beefebe9f11d11f744a5d279a2d249c19403be9898633825ccdae6b3693e97bf5b445ccf268b7c65f4ec3cd9d979a2d251a974b20c0d05c09b310508c82365746a3232b133134bc1dfa4525c320218f9f4f40480eb41df8bd0264aef89d1fa4d22b73c8bb4b18567912a1b5c0fef7ce8090f8f8c4c15c0cc003a28688705950f0d499c1c66262cd60a333609efcc0ca045272767002c888ce0b0405364b5a221804bd5507066beec69942c4294993fc990579abb53f5996aa5c2a89496affa5f40d4c401cacc9fd87c412fa67cf129255b7756e193c6efb59baa965b7d21fe5b57bbfead1bc2438359721a55abadf46f35b9c42970663e363f9bf3aba9528eb029bd2d1e3aa30793410d95bb8428d9233f590589c3a9b584c3ad4fb7a0403408daf4a54aa056e8172948966c613d8da24b6813f9511e59c515eb555ef68a75b962b507e2b4e756dac3a71405a29811f99a2c385353944597401485427b288bf650166589615394b2f894922cdae2534a3e419950277ce2afa54b7be04c4dd19e281271222e52b9950251200a448134dac92009c41e907ca2bbfad53b29d432558e82b0f4e73b2685d87bc9d7c48c60403c7bb53ed0ad4035545a185e6b4c0e88f2d132f53fd58266a58d70ed5bbf7378b815875ab300ce47a3b80986726bfd6eadaf59e17259b12edac7f04988c6423c74eb0be1135761afbee6c278268133150e6954ab4dd4af58e58136dcad0f5f70a6fe0e765d179f2aac82a5f029fbca2d80bb5ac72e38539f5d4248006d34ad6e6d822de3eedd0444cf3dd79120d364fdaad5f8d23e058726a7398133f51f006df88a7e088fee60344deb7ef48b3a21a2ef17c2bd8bb8f99aa669a2a9bdbf48fbc6beb3e7e843c05ff49ae987bfa87bfde8e73a16e2177bf527bf3493cb8557c3b4860d8850882771225441d8abf50587cbd1ae5cf9377dcba6e477a9fc52295cfa548a04923d7c4ab9f4e1537f9488258e01675cf2638fd67c29401bbef383c0d028f5e00c1236055d3ef8440370e9d37a01195ee013fd1d30f009ab948b700f7086c6730500daf0a5f437db28f99471a4d08f46a12efad5f0095eb1dd3c536e3ffd5c544aa55fdda92cfad18f7e4c6c8a7e3534845cce5d9f410c64852c03493a3b13bcd68a9d9fb239342aaf2a5d039f6aa77d35dd13826c73add6a69ac70703bd126c8a73aaa906f6868634d618a697db452c70a67ffae4c1a5c53839fefd4156417a06f87ac23c56e2eb7bc557fce217bfaf5141e20787e217593c156854d734a1a3681fe9d2aa48581778f2a6409b29854ff4db7f850b9f7c515cdfe7f1e96b54e411a90cdd0bf00973b694b907f064b5cc0df2d28730fef5e1d5a8a9ea58fa5d1be05fbbd490ebab4ecdd5eaa6e68a9588e6aa51737e73f5cd15ed522508380f5137875aa6ff876c4abebc96524a29e512be2ee04c3f35bd5cc851a1b247f2aca03f2579be19421a632a1aad2b57e2eb15bfd8821ae5bfbdee9c4f9b501f30bac2a81414ca9d953641974c315c9ae88a16a138748543576ca2aa39c45e7cc1192454358766960d673332b3549f1a45fda12bbaa2aa39278d1691972017e7e16902ca4c8ec7e7a75c6ef7401b9756c29d6da5bfb8b3b5b8b3ade819abccd7ab4ddf5df36831b029fb8a559a49b7f834bf597ce22210e0ccfc8f53a04d0301ea8854882084f8722c54a20e1fc3934085e7d53cad66b137e74fff97a0c9c44865a581a04d5077941e6a2c5da55f0dd4418e83ecdad540fdb5ab5d0dd4400dd440edea8f3d6f577fedeacf4914e676d23ca7f840a906ba2c28a26b08dd900f4b2c2b2e7ffda08d076db8460246ca0e06029a265f48f65af7431382bdd6c5a114276936d5179529ec55a0fa65f1c68b552b7086bff6b0d34ff1b87f0f9fbce35a6dfda00c9b6a0f0f7b4f2a8ffcca539ff44cae87579edac3b2eb167bdcacea73b902551f3e71407887bd4bc4cb8d2f68239fbf9334115047ece28927bc9707672215e95dfd6a08d51d2490150e08dae4880f94021a38b7657715f65ae8c55ecfd8518b694508da6cb76316d08653a1f59aafae75cad7ed170d1c145050411bfe1e3853864dd5da1b8cbb2ded70c00d12ecbfed51025908ef9cb2bbbbbb4bb7a7742734629624ac1c9d26234a793572791999e857643efd28b502da5c016da450203ebdfc7c3a8506512af4359f7192b098b49ae4e8ecf08a524ae995128de7e5e82461f10a67d6dbe44a93193bd851c7aa38c42b1d172c63d48c0175c4213292c04f6a300c1ba2fc84320eaf28fdee9cdf952b34c610bad30817e92355d897f7e7934b7fed82362d777e5bd157b494069adf414d854f2d3fbf5f5d45d5a8193f9f8df014fdfc39e773113ee1d0f8f95a84d0a65b0a2833bbabf133b89f0433be067fcac24060c6d7f81f33baaff1ddfd20cdb9914c71a865e6d730f156c30479cc300931e2c78ca7d10999d1c154f6a29f4fe223d0862bd7c35ab1a387af8db8bb3b3e62027243bdda6326204f12b22f9dc721295c4c6ef2510c03cecc1f995c2e4d185047c4820431e0e89f8c4c3262893aa40e9085f9f30477feb4417f2fa6fee0cc74e99a87bdf9a9fe449fb9159b5df826d1632291c845afcdcd8aba96ecb5c83dc000377bd1d3e7deb91e5c96752f4222de90b458b142801f7c8a3fdf833639ee7c1a081170e7d7397f07974a3fa74f122a5d4491328589934864860ac8e0c11075a29158240b962a9ee7b56b051a8f3dafa58806635224d79d75562bee7cd904a2e47cd98236a23ba514774a2877ca265aee943aeccdc984bdd9627d7d2b24598d8a3ce6cb1635c91d99237362924824fac428a00d8c4cb2ce08367f0646c99fffc2d7cc9fef41ce3261150deb52a5295907e1ec2094a95daad63a23eb4e16c925b5d0f8351a48c3365e8cb03035e472d7027b4c2c8420b872056a8d721e2db4606384838d1fb11b55f2e0eecd03f4e07bf3a6e4f9a3a8a31208219438f3760721843142a98233ed2dc77cb011e1eede44407777778fedee2e65c38e3c02393202ec329e52c9daa8ea25d8c85cb8943add55a44e64ed00556179d7dd192b08964e1162c9baa453c6a6a2c7db8ba6f81f495608dbf26c8895a7f52a02cf0a9e973de1c952c023448b9192f5960987fcf832cac601a58a7d5a5afd24f6628c5b7d00dfee6e1b2c1c5704f4b22e5fb6df803147b029b9135119938c0546c6ea8e4c45fce1b24d2cf400072a1eddc1fce8c91bd76ab78881e208202892bc2ecc8f7ef903450c543cfa87f030c28dff81a823def81e8dcf08aa01c6162a1e90eb614337a109971444450a06818a1decc52a420e06164e8440c4144c9cd800c70c6ccb853e4e9864e144e7c6090a7068a1c4124a344cd06674d9dd5ca2feeeef3d4beeeedddd34ee2e2784dd10e607e5419faf12da0921010fd832f0575aec6e686fd0288cee60de708607bfc04d503531a89c70bbbbbb3b084b04b10229404438e1d162dc9642662db0a9f7dbee8e85cbe5e78680107eb2c0c245173808c208b7bf85ee6e958d5054921851aac0326484d7111dc60f537477d7e802bbed20f1a978c4f7a212ad515541c99520c2802a38429c800160f07089255f5092c1cd914b2afd853e3e18a1d961480e2768ad6c3005055830a90287e0174fa02c09c2810fc018028c22533c64e20a1454b820079592a022089195485890be064c030ca24042900f389c0087fec227e31f7ee0018c5719485c542cd12f1da68c01c51346907165065e38b1f0a788ece4b3b7d861082c7618021052486922b0a8097cecb0f00b6ac2698a13229a1861c7ca0fba0b2e9204922a81a092c40b2adb2551564b7450224a1742c00195245840970c396289244c9080051026f8e1228913b4b04724b89c5c02c15c8c78481046f0f0426284942b5c3084204836c23e57dcfc800b1940c111daa2090f2a330b5e0364bc9ec82204248220450735a1a236616b65245868f28493900c29ae218e00c5ca0d8f109a30219997307c1de105173260c242c2154168c282162316fe34e9017bc630acab10562b3bc843d6d84d40c8420588a083650a1f2499c112405002a9073665b9099a6922b1178f38811358c8a8010dc228a2a7dc405e2572802c9f248090041537653f8abb0f5d3792c30d0db2900208238cf40f8650e3babb3bf763a1e56ebec2dd9d5960a18afe217c052c7d401b29a53c4205603cb9a2ca22c8071f3d824dd5aed0a67907070f095003113f3f0c60a58980931fb8e841c90fab1568214416f3ae27e0900f434be4e0a5a3e30350112e0953441aef22374711543db8e921c9122b78780fae3bbd3e0409091e458331b545099b62e1fab35092800663522dc298c42073593a9a8dcf725cc7b54e4c92459cd5cdb8c54e988783b80aa3e0163dfc6487a3b08b81980af7ec2479805fee3597d26c45cf9e1b07c3712a224764b999dfec230e3cb223b0dceca1cececd54990a0920b28873b3e762f3d0cd3e6b1edc6c323f6b75d34cf4b555cb641b6b184cff90db5dea0a9be2d6cecd3e3eb7fa33d1679a0986fb2197eb52918db097310e7bd9bbac92d5f31075bcea8c89b007afe47a70dcabd8cb9ea4848d5ac0eca39665d91640fcf267594099ec8170b32e6ef6358c9b7d86e566af75a9146e06bb28650614d973e69f65cf3c580f9f349f8fa3441df066affdf69ac6711ab741ae3feb3ad0aa162034e979313ee112e7b08c7c51575b464a1b4bb098c6ac31db28c3ee7801061ffef27fc0f8431b56b82d725b8c0feb128b7de4b6982432a922648487a6759565b12e1593dca8137772a0052c84e2050e142f5657fe0b8c8a3c20142a3b5742d900199390d82716890f7bb0d440d4065846fe0e96915f5db03ea5285fece9b6a2478a5addbb943da2e7eab8d0c788d5d5c1a6aaa5a1f1bce7ae31d5c0999e9b1376e353b709655ad4409b78bb9bdbbb695208e84db483f521832632c1bb8313992a7ba297bf03860eb2c775cede2683a4293d56ad163b6d4addec940f6f9683bd9c8fb5f83479a24677f4a8989999995905275276cb26a4d854e94b5e5d4a29da81fa4f7a834ff4fd4b73be53e9d3a794fe03d7f66f67e61fa2c11808eb1536552ad5d85b8d6807ebf149c25be213d7d2cb6e07b75c28465072211743cb1dd260cce8ad8d4f01f78f1189622cc3a6fac67597ab09cb7f833d1a178521c6e71e7c1481363d401dbc02237c03842b4a2fb282a843f670b30217103912e7c675f7a7269a76bd4c10d556b4ebe3b7eb69d7412df3a3023ed873bf21771c4b0dba2e12f996041b5fe39a8c742cbca9c7225723af264d2d17723e88c0b62de34b8033fed2e47277401bfafe310bb45902d4c14476b8ee40e29d2e8d10893a241090057f97415c3735109c7157d71f7bfe32e4b58c370fff68a18dbfd7a80ab840d48532ded27aeda4330eafd8f377a01ab443c5b972e50ab65af11057451aa757bdea557ffcf180f18b40415746e9f1f97a64cfaa87bfcf07dac828721075d4fa848707888a382b68339968b596b08eec0891e3c32af67c1fce87f3e17c381f0e6761cfc7a3c8c193db3c2b38d3cd434b8c25af23414200f9e052f1107b2f4352c61e2158fe54af561507c3696eceab0f50a62587c103dae0e434abbbf545717df18b2e76b107c49e4781365338155107fdb6ddc327ef7e8287a7496b09eb7677370e77d76a6da994e200dc5424c25e47f129e00c1591856e9f1ec68133ed034f129a2c791d091262a562af5fb2d8d4e6de38ee6282158c853f76f1107f71276f7167abe6ccb0396793566b4e6ecd39a598f38333b3359d70cb0ab79cf0c7dfc71f0462207ccce9b9084d31a3e7ce8a6593a74b039a3f8fcc9dd9e4ce2537003d53f6d46cca2f02de7500f6fc406ba50fbf524ae9c35b6b1696bfd66ca21d6cc9cbe86359cd9ed2c7b24a318ad54a13b3698f91d8c330ac7ef6a95a79603dcb32d10eb6e455acb297fdf615c3b60cd3302dcb600cd67580e609cbdd7d060eac7777373b19fe9918dec8e4dd8527ee2f5b2e69646141241c0ad2a8c862ed780db4f1e6621777d80bd2a83a34df2dd6c59d9c1ca7dd955a86eb11737a3ac1fac862d27ac2a79ef86598a9729db1238db55a3b592de33d1805d674636b5e9a5daaa412c29e072109616de58bcdce65978aab9ba201065dff56640d613fe772076be00c15cb9faa2e910f7876a46c677797dc524a29a594ef52be94524a296f60fda54bcf8b6850fb011422e85c98e56ab4bac02822396cbfc3f3d85e052796eb31956c38d9967dc6f5e08e94eb1169d4342d723db0af26786b85a9f56bf6d534c3064c34b00c65d87efa55eb60304cc4c1006fed60ead7fed917e07ad0ee077bde26f7f7c127e86307d3dfd1b5966ab46caba9a9e1b11bb8a9eeeecdf3c8a3e28d4d1fb9285ac2a6ac076933f009de1858acc6851d3bd88b2eb87f199dd7a8b21674655282ec79676262b78a8595ff36547052ba011d4e6fd9d21d07426f5a80ad815d007fcc41e396ce2f4d36c446b90d2a5d19379691953df9339e587fcddde3c69eacec49fff8ed31c6969dbb8f9cb0fceeeeee52d4c019efae420316fa30e1bad087891e9a0b7d98c0b95a0bb5ddc1ea51d3ba51088032feee3ea306b6bd183d57ca83412bad49f096d8ab1d1cf558faf3629faa358a75f6eac367ec2b1616eb207b35db0eb6742b7bdad7ec53376ecd3ef5b76618d6b116b7e6876f44839a0facb317611800a91c72a129c26f7f6a1a722334a522cf8db0bbf908e7d8532ac10541e8d8982358540b345bad43f40e181a55034d73df2d32954cb665ba87ec615f63966559d6f66299571a875c3afd77b4f4ee8e5f618f0368da924ad6e5bdc69a6ccb78f7a22e6edb6b730b7263ea0606c03bbbed2bd7daad42cb729b699a38532a7ecd1eeee02157cb6ab5d1c29f194b22da7b3425082124e9606d4c7f3961ed640ffa97cef931a63b720c80974aae47646e2663474ec965b921087bf231ccc5650994912d5deab56982f187773a94ef5c8f968f4be0a95fbe7caaa26002ec0e96d97efb9e8d884d41212d8884db026ee86a10c29e84ab2ec59fc1cb2213107953910524de20afd3b4d0a81a646b816300bc94bb1f72db943255155ad4b1d7fa7774168362a783ed74b0fe9a53f1b979110465b8901b18573e4b0a210d7bb2863d296b825aa49962eb097b7f99bd1814833a28764141b38370a6bbd8622f3e614f46155ab6c6ed875c0c356e74d2a381f8666a164b1b9161bbbbbb03c2d6b8d06709a0ebef2eb470dd9da56d250410e04299ea224db0e746ceb5f8caa1a1f1bc0f7ac2a6604f90cf123e57be03f8028c823ca85cf934352ab4ec3ba3462fdff28913f0f2a9155c3e45ba297b65ea061556a3461d6c99972fb9946ccbc8954d55dea971a3b14b19618c31aef0971d3aa1c11877f70eb83455f69899ff7ae73d285fa803c80dd5a1169f4d374420f161608cd786116cea69e2cf5fa1053ed95082cb3fe7c77832d026fbf933d006fbf9357c62930df10e513d717d967072e797ecd3c0e7ce8fa1997f834f28dcf97075278f29bafc40ecad1d4c0ca2cb2fbafc295c18ece1ed000cd6352001971fc88e5b3967af0bae814f740a892a0df3ad089b82421127fbb8d5906559f614429899da44678c1833ecb0a71fa5acd873c79199233316dfb12c7b2df3cc33cf9cf498c9868e5c8f28c48a42426a0812a9c4d70d3048063eb116b31b66f81142086117a3297bae2e61071942860c533490628c94d6c0a72cfe74eaa6e88a5280d88b1d35d1306760cfa50c0bef297e8481e6e253a9a6086c2c55e0b31466970b173e95532a155ba584e56649d1dc9a2e72f9532e3491183be9fac107a8234216a2f46f68336b5c6a6a950f9f5234e0f049ce9cba693b9e8506511ee3e37da3471063c5f2a7dc9a547062fb21978111194d2043e84776e0856b075e7cd73f466154e4e13b7082c8f5cf5f9b0c349674a2f04e0c932af63caaaefb53d30d41d8f306c8dbdf9d1028b441159c5858e572120dc6fc604f63933fb39b2c7b3075032764f977749b68d8e4ed6e13e570a38d91a52d0046512245bb308a922f8ea1e530410e1da816e37b2bd3604b0b8661a0628fc9fe5819cac88f22b8fdb132f4fa63cd328aa4d22e625d8aafa45a4691cc39e79419b8edb30caf51ac1aeb035c7e89033c15007a8c802eae7a76c0f3b1c737d80c82a75192e7e3d35ced009e660a78781a95790165f8598ccb3f57dd9faab5c2cc95ca64f8bbea686b702e06cbdb5cc58e73c5938bc16f9c371055671097bfd223754863b58fd8082302e60a853b578d2a95ac9dff2d385cfeccd5a2ca5cdc1176ae23341893d256d0c69f7ecd3ed629c3027a0c5fee342aebe1019e32177b9c8901a14cd6d3a80c287bb187053c6560402f036a94ac02f6e032d06d69a1ff49579499ad2fac360af2c85e7c923c19e73958e71103a2602a4bc2739b7bcf1e3e88d46e5ebac5a046c82256008e8bbde00c17c16a730a8410ba8416bae4e1c2a189a8140c1b2b7ac9d0d000000023160000180c0a864482812408c238dfe40314000d719c4664522c1a0883410ee32808c220640c30840002806106d0ccd4b0246e0d20652ba8f095c1d1a3ec35fc89e1759822a8b81ad8903a06f03b78734c089949820bdbd4deefacb93cb3da812aefe34c918a670ec0a46f9cf48545754dd2eaaec0fb4b17a00d0099ec8aa7af300b6758e93f868ebf541ba6ae9c661ded331dad80c4d80e50d23cda86ff3982c21fff96d1156d0b8823fbf7d89e5892c66089668cb1467ba5ebbcf9cafed965745d4da7e6dcbaaa4f4bf9adee8f73692161f0ccc7c4fd1efc0525060b7c2eb423dd38ce969411786a8fd6b0e470ecb9467c6d6ff63fcdde623196ef75dee4525c94fe27ceac182c3ffa17142e3d0779754a65157bf236c9cd5115af12bdcb7d95bdcdcb298ccf3cf0637d9e6357f5887176284ab6a4aac15ad1c2ac389fb30d97215231a145c21485e5ff41de83116d2b6cf39b1c76aee4dbb887ef610308a9ed76c70bda1f9008c8ed931e0b5b9c75cd26e123c35c6483b85e30d21b35e4ab875f91ef1ab923dfcba2ca28883df317e3833fdb4ebf877fa973b22718ea047da7ad73451d7e26ca78a152b3476d9f527f863dd669b458ca942cca94aece4212c377b051bebd183ae52ec3cfcc1d586e88c8218d4357a07405cf1909a500b1ea20835c3e1a6d49e51e984019f12809566990a2c9e6073fd7066fa4401111e80927887f857142a9fc9d8ae14a05401c67ee1ce4da9267f9d416c5fcb6950b600ef928e83ed3213ed3de9eb32410a73c31c635ba56b95518f5a4b4a6a2fdea973009c1c6fa8664a18d394c3357f58fcd8d693938f20f96688e8c5e421b8dd2cf15d879e918f390f8eaa4def9670139a9b0d0225c53b7973cc4fbea0d859c5629eb1c1e9de71831fab0b85d4d9b9cb60ed30323f4197981442a1eaa8a478cf6316a300edd917840c740830d21115360d557664f873c11850917a70dc455d267e8efe75aa00693d08f2853ccfdbb8353d503dc74b715ac46ece0eddb9c131a09c65ac3cf5728ab5d42047988c499bd9d7852f20d23c6f5b2bf7cbc26b26b43f9cdadde78fde7955b73c720b3afb1a6eede153726f25973baaff72aa7c4aa3047c1ec553294105f25ce489707555ab535d5ac9f8bf8bb9cceb950395358a70d30a2d374ca3212a787117ebc13b8b2330a3a51c8ec16c1e9670b18f6a84bcde71dcb4bf3db7eeac791d5f095be30ac6885a9052bc81cae397811fca7ba03b4047ff0741024424e3efa18230e626614d8cd07c8b68841a30f28f5e415d1fe265d96bc31b2a6860fe148d52efa7623e2255d2809dda28f801389cd1d626dc7803fb270279045fa24c70b7a3cde8a93a25be5b48fbe3251e96bd31196595c286e753157d023b2468d648656c454fc144dcbe8bf6e53da7b3e835d03b5ca568bdbda3702bc15c0b11d7ca3b189c51a4cd80bb505091070bb57800272b38d7b0d2073b0502aa62f3595643a0f3e6d3b753fd16d4aae3dcfa18554f9a9f8600170619b49bc27586bc5f1145aad4dceb6806a233488e94033b81ee5fd27f2db754bac394c32aac4eac06941337aa9667b63070adc674a931b6217bfff1033a0bc01982860640c547a4d360ad0f061025d07f79cf4563f0a8ced79547cb0e43701b3679e80fa5f077414713b710934fc2f8b3092de4a74df27d20676fd43b83060ce6d386b4629932544d209c8db3b7202012b89fbe9336cb02d1d696b9e3b3dc837cbea921d04ab7f72ad06ce152b0b826cabf8f25d9d1c0da2e3a367f169dcf86eccd057c6079bfd131df694509e6faf895287be9d1f3d574400acad06997b02bbb6148174236fc861568da06eb32bd5c6cad4e57624a3764a3d224557b1210e4a7db9cfb6c9f8bd8e8c95faefc67fbdb2bf557ad0b3e87633e11df661d7ed5ee28e3262ce1f754afe4f23659e6090403322a23b6d56da16b151d92f9755bc5d8cc85a011cdb30404a3ce2f9e0bb84a279d5ef3bb215ddf038a6b7d90e13a375c29953d2adefde4c2c967147a1559651de09b9ac0f34a5e284c999219ea1b7fe1793a56b05e4d2064472b787bdaf0580a0cf525dec59dae74a70632dcbc9bc65aaa582a402a2c72e45a6941bb236cc58138c184a9ccffae354e468893faa15ccc1f56159f743419c9794c229b799e64f5b1b4e7aa0353b3b273a3794f2dd0bd254838bc735650957568a64e6dedda5cac4fef9fc8ead274cb022d57303644be6aa8d3b85082733446334f6afe903c853df2e365bc3f83a20ab51b0bb50eb93af286e85d6e9a23bb5560d6ab4b31b3e75efdd9e31d80ede9299ec00c7085368a54e85760f64d901175814124640bdd2f20f88b97f60319307202d1735696ddb0f0b432a9ed22837d86b015a9e318413c68af13ff3c37d72c776a0511eab2a5a6a4e869163800ff3475f5f32945be249c5a1b88478d288448b403c2afd9274627a233443c54fca1f9c98b1c11c1199c69c72b407d87f691610d821ddd3b694b5a9968e2d72851fc959abbe7a820903f61c92b9bbf3ec051a1d0ddee799b26f9f9491d09c06aee1c0cdf1ea2a537463faaaa647184660b21d344b55b05043d2fa8ea53374b28aafc977d4688653c8351afaf8d66073b228496fceba61d5519d3271035af63a8958a8862a060ad323a6000f170d853ddfa127feb039d1f55b904728d7fdb31b5ec0514db3ed3d76ad780d761349eee99019c43f799045b54622d3ff798532547eab0e2acea29ddca09748b7ac6861d1a37539894071157275a229cd64d0cf0312d1900d0e7b9e73230c56a6354876579489f38095870d73f815e9a16349c0206883f6ac6b3c746755e6fb7f1d85952ceb559d2a1bbe4020c6f157c771eccfae293802c6202b62d24a19cf192721c46e21ee6783c4d565f242681627f8b7fe1e0fee8755918e9b0c72ca77f8b03f4d69831ef535d30ac5025912e885591ac5e68e0a67dd053f72fce759b99428cbe144a62e55d8b794d098c32cb54254001152c0ce10855dcdec7a4770dd32971efc35971d7bd4d8bc63ad4d79026c737b31011cfd5577b78241779b3f01418a4770c36befd3766f5acc217ff8b9adb0d5e9e01e8bd3cc2979a7036da02f6ba29e8512e4f802f82c04de0e4ca85b362324ad88228972fc8c015d796b9a89cef0e2743800605008bb3fc0cd186bf317b0dc1c31b2c20d2d1a04991b84fafe12b38cf84dd11998485686d457184a4430221886aaca718a3a5ffcd37ee18eed5b053348481ba1bed21800916a4450f8a80aebaa202056f5ec4d44a91b80270367425b91afb47d20fa37f3aa79f4087104c62a4f67917ed7a20b230402e9ba1d30c13bf968fcea4b9519692b0c8a2bc7e3faad8309380e031f4748c31a0e61ed2c3e4975517da771fe309a43c819f6681bafc0ec012940b8ee472b0eae2b8f88f9d39189912d749c31958636a9f4e9a45fede6aaf039090355a31e8a305b6055afcba3e755a5592d929648a969d6aa79d193ac16dc7c47bfd802b334525d254fbf4da5373a3656f6e5e1213de3deccc115bbf7fdf575d93e78f87c0d9dfe4288c76b7b21446103f203e638bb28926518716968647484edd651b97ab6a35a5724619d7f9b8530677c469f1c373adc2643f9e02d2ff74b2aaba5c7a8ac62d70a08a824ca0f376417d75720fad6f12f037e15c7f09ba1d923f5866f6afc1108adcf89e66adb75c589b35359d3e4c7262dab1681c8ae7d468dcf694b730ce99c1df7334e121471a2c5fd8f098d42ef3f3752b8e170f4c9daa82dd9d2cb22e7e4de0ecc064c69c3a5e752ec2c57c9baf2c036bcaea7e53aa6fd80e98dbc54103249c9c8823def52fb767f328ec083d58dff86c22521cdff813585fa2db70323105fb1e48a04e307bc9a8865ca514058146ba4c7e9ab4586c48899b3bb23415a347496f223f211ed47140632f25b2a0fdd9f1bd7c2c5b4c71a4baad1e5fafa4592271e21e30bb2860b2a3cf8546754ca0eda754ad08d2cdbcb55526a513907b04f582840f0d5e73cfa6f08faafa958470489c507711144acfa6651229ae8d4610a57cb6a3419de5d2bc52daa20fa08f42d87a86edf1961c83715f755b76085c75a1f159bf239d5be7b771ceaa8f8a6544a7ae65dd97adb34483c1c7380160af42853789557641960355edbca298161caa082bd9ca4b541e1cfbbf08db73c1c28cff51308f0c9ba56025494adafb008fb78e9b959cf639a06db03a8c83c8cd6bfbaf02809153929608b10fe128fab9586ff31c24be0c62cd84f5555cf2645856fdd5990ecd7fe63e63101922354ff844880d6385cc103cc3aed05d3c74c8f0e9c5515d4988904915f454de2cda5420a71ee8bd2d09da5534677f6be4fda7064ccfc101a7194970c9b3d55459e579e3415324909ab1b073e4a6206ef3809b795b4bb6672c6778c6491250e858e1c3410191a029a413c6f3a16093e0fd0296bedeeec4a720bd85d8ec6cda8d0c2b0d01d598b4d9679a175d0cb6415fcae4b20a760759b222f3699b43256c4e9b4e4592bd040605b8b2e3d3d957836674b7a63f5322aeac72014e6ab7eee11abca7feeb12b85f0ab0090cff285b5c0cd56d107b54982532193bd7d98c8afa3414ec222bb06d97d06b4d8e0699e46816dd802224ac4cef9e6920d3878f864d4f96c1d0ee2f7c8a814ee0919df363e493326e252006ae46c7561d1e43174b8a01df804e25e46ae4d4c9fc166ec854c652d0d7ef322572335e3cebc781fad0a9595b8fb970a35c5c1a3c79bcdf0e1fc1460ced2bb6a0283b19110be25070931a42fb6bd2c5e3ad43ca2024dfac5e4c24907d9b367c97787e73a6719ff52dd49b33f9c41bef8c4a67944de9419def43cac50c0447894d6af78b13c75a5c92f73b37562f4fc28b449769760489c231429103ca75d5af67560d0753dabce4669c9561a3c2e5737debc072e361610fbe000a5906a9e046047356cd8725433497991e037d52e41ffe103a8c970bc83083c261f35ee18482765402ca377d1da19d5c7f2b28014f6f8937ae59e3b14d09037b3774a22c706ac7fd993f784821f6a1ba202b925894baeccff7f22b31440d8cbd6aa2c734cd698483d94c583ae608bac5b50823d669574ac90a39ae346b674d3257e090c41b82f1d0f23fb8161f48807b4f2de6173d28d6241dacb47e2ef2622b707a1070fc80cb0d40703dc407096dc79d5e24803814b7643988a2536dc4af370cad372e45ab5292a50a791a1f0211ef29dabd3b411ce2e1ad8baafa17a2545d18aa46c615a3bcb2929d532b9f151897672fdb5a47d4bc83654963760c58ea5827ac6b64455ee07bb7ec15804cd0116d4bd04344b51c957be825a0e526de6d3cc2c33b41555c561b3f6d9c718356b747055a1051f8a6b8ddad6cc17841be88733fa3348fb19c944d14b37aa3fd4248dcd720cca2396822862fcf411092e6aa16329f40996054ce04009142eb6fb9c0fa2167e08ac99c19a0fa661ac1479ea020833f252508769c12fc08db31359863b831c8aac352a83c094d43938aec054a342e3d9fa8beed2799946178f06e1c631e84bcb24b136adaca8752ff1b70a8ba4cc89eb527194f13635091da168b4503359818999f2ed386e797ce48d723b0ffda9983cd5457aa7eba6919da211eeee64a2feb60fd1c9eb7903d198a47f62b8498d7c84836efe3d25a4262a8be342899088a6109c99f43394e383b84f242ebe09199ea861132177cb0fe5f70636b510cc25250542885399226d4662ee6d7010b274327772b3982702faadb8e60e1ab13c25b886948f851ddb35244f51e6eefc03fb6d62310e149747b504d8ecfa3e219edde48e140ede814e884f2fc0cb450265d28260b30b06088c4be5441891172d2455e04be6146643d1067f49d5760046ffb0f762fe10e6d7e04a82c0b0d2cafe0db6684ede95e849ef0aab1b9fec73f45cd5465785cc3ff6065c69efc2aca537ff6ad495a24e6495509b30d2268f0f136d5418c4edd6cd5ec19ad702b660729684cdb81097b6733c17a6df4073261b134e38436113d6979e738462f825e7172e4fbabb9403992321812a2e270e45a3c2f01e27d7227f039ccb18f745512fec8417f2c4fc48f44679b607a0e66ac8e5b766f7dea92f44546d42f63af7bce65080d2f4eaaa33a7fc8d73eceaa502ef4c44a61f2384f1225c909e7310436e1ffe06962ec2d23564db89bda2254b548b74fba279b0ea02f4e1a6a7059881669c19ffadb4b8881229271d3e41bea1741941203745a384821df442b0516902403c444a4422bf5d4aa23c4e69af363f21bcb9c1c5ede8d8e73477168871968239259369150b47b2e851db6d10d84a75488bb7047f9ea54663ac65a0ee8fa9a130da26f2de11b60c70bdfe5055fc992388120d3f45b4d4d6b0d9611c03d788339e24063ce2e669dba48c4df430941f3f05e47928c43d2dc95e231e05d28b54062f510759089a58c2fc387a87d56a9933b2a03e5d2efa18acc3271b65c41fe7fbfcc696a3d8dac67bfd6d5146d81fdaeb48e28b81a596a28d7dce43935448c1f1fcc9dde4f730b267dab1e8148b25681bef54f67dff7757be4976079cacaca5edfb6a7a897fbf8e4d6ca7a792c590d382cb75db213c4f1393c50a1b1b62da169d7367403a2d226347ef987b9ecd209d66e9603d1605591ed0bfa291651735034cdbc331d7cda059197a254edf47e8c4be922c5fb853194a47a73369714b66f82dc8ea536eef849256ae37314b8d21702c122dc61e00db0c32cf9e9e09af12dde2605254fda86fd079a060a57521f8de54ac21d479407ffd4eca1709118c998b0b98a5d993b87f4941325582c33bd4c72efa2b7bc07a885d2c3c60260f5b514a22f012e0093eb8dca067ccc9ee6250865633d931d7cbf8b3057913e0e76f546296924c6138df0c70fce37c376f60ec7d7781c04d1ee4c5bdba76b6d40968052ac3ffd0fd655db8567c93331ec89a659a01b35768773b1c5a99930a0ba3995209ac944c4869d60d82da85127de3fed53efa5002c0927bc6e24c4e2c61268312068af8c50f8c29f0a1d6693768a0ca9c1f8986faf23aa601b7c180773890809931206e5686f3a12a0d3182910413ccf2c7451e4b256a5880b9c7505bcce3ddaf079a80df2125d8f6887a1ee429e20de3d09c51aa992a00ca9adb5f611715652d732dba67b481d701b1850f13d3306e8114f8f4acff9734a25a4efd41340741570c02e39ff50f3979a92b40f3749611dd54f507cbcb5922c5af087d96780a6679da15f8ac0abbfe115706d6614bbc9d49d45099c4a6936b4427ef1369e6f841284be4c3653811a4a5d2c48a36e0168f40951834a1c240c3a09dcf0a039ef5be46c0d2a48710e7ed404f0c60fd9d59248491b2fb9341c4205f5244bcdff7abc80c3d5172974594b0ac8ba341b90b02fd4b524f433f5b18f7e76612fcd4d82d0294aaa36e4ebefdee6396f65b2062169a6c9240b7aa7df59d2796c9e21624e4301428aba44829cb4dd67a424af9c4f5784ae265ba79466a4c8fab508cb978251dc373effc3a79ef4a64f65a54ad7ba9ee4884277b0fb8863db415e135c54602d2bcc4593406e0c147dd565bfe74835893d652295fa1f301be604a2f3543a27aa479a1641ea699cb65d31b5c66eb32bb89ace7373591da38fa8e07e5a4d48b548b2df97bf2bc8e0f0bf397182f14386c59b28400ed3b431a465cd673880e3dc87db17d4fd4962b3e1433bb86ce212ce1b644e2a96e5c13aa0ba8f826343d0cb7f2eb1b5e71c63dbe9ab027412e31e2b8c61be798b200df993a31c4707ccac01cef07aad7febc0ed3c459c4df5178d9604f06b764204e6797483539454179977400c6871c1e19ca01ac643ef2d99c5851898c428a47e6122c95ebc4c2ed0c847b56f4991039cecdbe7435f0c3ebddcbe1e8a410c4b6d9574cf4c3f4731cdf706f3350414d5066c86ff082328c77263200e4213c1264a5bdb9d397dc4a002f8f1c562c7a18cc925818241f0e0a09f848a8ff537f1522807ac66e50b1e0a2d5666ae9220ee07d61c5e40fd30265222ee5bce4a14d54d564ad4edcb2304be909cba1db202c4c1b2ef876e70289f8b0c4567257b1567340ea7e2039e5e81c17f3876c2b98c99fd46201a54ac678c2fbf133c329d9eb91bea6317ce87e384c83b3663b712eaace87cbd2c10f169772ba49a024078785c9b1773ce67fd86243e91b619329b6e0da966a8304fec5c77787c22e5cd4401c5beec740f0e9da8a7ef17aa2844602801f52c7498d77cc8f234a8b5390ae0475b7c75baa882c08e1dbbb78edefcaae029bdc69409c8cf52f00c3842b2052c7b7903e8888e83ce8120d11fe6236a232acb9f2f860085a8aa647a3301fadbdec035d47382a919c293630020227abf2df75cd38f02a0541dd341136277b269bbf70718cbfa6e2a827db3bc6bc9ac3c310249401a8d0667b4578bc4a98db2a7bcf890c1536805e2aaf8787eb77bba45c15b6df3fb8c08fb439bca3657f8cab7cf182061cad8f8d89a502d7d43a7c53d2b5625dd6a8782ed85e3e31544507223f658d6c334c5df62c5369c38577b39616fd93ad17184714abc2616e3497e7a459041a2a747075ad01f508a2d18f480a363f07fc7eb3d76362720b271ea70db8d95b767e6fdf0a28dce69ceffdae8d41ecbc050e68ca2900e14a42b466bef941c8b1304bf709b12fedcae1c0fd311375bf00942f24aba9b4706d2bfa263f1fe1beffdfb53503afffb6a1258fc9137c412efa112a037692cc39ec1fd0f48f5978265a547309c0aef7e55c1a44047da451e9b27d4b521be3ce2c0ce575bf243e589412b19308d587f59dfdc700eb0a76ddb2d029a0820a4cbbdf31f71d952166fd575c0973a953bbd216e00428474fb960d6312bf6f2a8946e6ca823cc3eca31fb3d161f16cedb512136e9e43e5ae8c9883f396b5f8b10445ab628617e5f5b1aa23af1f5fdcc4bbb842773e3e469b3e26fd461a8e51a44218e93b1408ec4b1d14a0ab88df9c73db1b66b2ce4e0b4372d423d59316760e5d880230b099dffa45151a5b411e6608eec9aec038b07341ca67f5da6590a73971591aa25ecdf9f93d896fd3d56ee88b8fb9797c1b4cd3c772bfc4d8e148b9dfe25c30b950ac2837c652003348941dfa312fdc2453b68d129159cc7979cd8420e3b562dc14e28314bb9ae28e62003eea1892968ed2401b211d427decad48f00e72e8078844816869330e8c5b3306702aa16bde6e2baab80709ec2b8d2d45f97c2b8b2b815e7635fd5412ebf760f6e958585789da424b2e5339ed6956c62c7b7c582d7e0115f76c930d54ec9c1992c2e0a6605b79ffadfc85fe101beccdffcadac593de44889aeeec0f38edeb8d1838649820e6b481ea5d17c8394ffd78d2cb217f1ac8143b1877f379b0217d6c491473b35347fc8e9e1fcd7ad5e86d63f796083e99cea36bb88ba878e8fdb1d6b7a626474c356ef545d43c378b416fd4d0a7452de79fd90628219465b9597644da8c5d3212219c35bcef42d3bec974aa51e5da9d01ececb742dce019e41be78180334fbb63f2cbd729aebd67488ebbef95ef121bec1aa3ec76c21e23a053e10f3599e2ab4ce8360e31500dfe2c4f114099e902ab55fc81c990025a7f8cb98ac12b821e028c1abaf024d440e1021069d901017b9aea09dc2da159fb1aa1035f597a81bd313d201cea5443bdf38731a1e5de2681325c8de6681d93bd6ae01f751f5a45eae76ec6ce6bbcc33af52a634115ee4ed244b46c958120e09067172e18e708a95d48c433cf3f5afd09964b1fd770208226358c8d80d2e96cea0acf142879197361030e120d57b7195c0dcffe4591ba5cf09a914fc8e0b59c92540e7bfebd7bf8e3945bd34f9f038264314dd0e7402a1e1720788653b1dba7cbbaafcd96f0ce966ebb5b85937ca758fb28cb1cd2086d3c1cfddc1228816a5224e440ca53c680a9ca1b33a432f66ef7dda282c7baa678d3d44301912925eb57835eb3037da630898bcdfc5afffbcc32576cea87868dbf787ba335f4be2f21ffcc3dfba8608c9e22fc1c71c70846039322f54f09ec622e5f5d895020075d7ec04881c2af15b1059fa9604aa45a1a0cc48ff6576ce1099fcbcba394b722283f490c5967f092964ce1ad28d06a2a2436116d50bd25b4192eac568cbf21968f1a90a7c385bf353d5f5a63b4eb9c77a0f071c08dc4bf8f1ffebf77b3e83c442de1ac9422d71dfa5359e9f39b259540dbae871ff152f53b0a882d3465f40cc8fa94ca5a9bcf71784ffc4ec276806cb9c44027fa05a180900c682aecea3369ee27df1f5c5febb0c02ffb7dbfbeed825cb85c040e41bad7d92d53be038083df7270feefe14eec98118c615142b07f661c8255ae7531d0ebcc6ea172abdd41cbddf4c950739c2d7f959cb1d8cd6c8cb948051460b6de3fd2c4bc786827d6531654b1987fcb1449704cf954a739ee193e043616f472e657c789298d1441388114db6506f80132d80bccdde6b636249be29ea2b36e0b1119dfb3bc842f8e1e213ace37ac2fbf00b31fc097f9ef832716357de6759ee919886cf3bc2e70f15936e18c4c917b26fbc1d66cab8f7aaba25058542fabd1c278f404bf2c698da31d9958e626855c3b68ccb4dea9c958714fb3ff859939034ffa4f8711e4d1c02152beb49e2c98fd1187ca96bce8e1ff00229969670ea2b831087c26414e7405b43b9e4741d5f88ce12a62e41f89aa96d1a33d6194db75f3875e9416e336f2c657bc464999f39e545de247f7a055a783bfb525e3c90b4f4bf8577151ed49f3322f52228b109dc271ad865e3efbc8a83c438367f07092e545be64b62ad9e1f234c8a98ca5c48af69787955302f0d2222d4c367d9a03c746dc602244a3fc51d3366089cc8760b8539885ed1380fd7cb1a35f39ba010bf00ccf4e097e0eeca98e6313712826d2b1696777d0141b4842fe3567d3e5ac61b526b64d2073a5a866850bc1ab5903c1f839de92c88535ffc987bfeee7f2f964f5f41a1ee83ddd7a9d8dc85db6224863d412007d38fa151b3282b2a443595685a6dd4b9cf85b5268231f8c40ceedb048591267f26c138db016f55ee8adc00153f0889aa5b7dedbcdd4a928b4309b9d82b7c5cc75ae1282fbef4c1e505bd43bdaafa98086fddf49904a66bf47b44d711ec3436eaa75a738571ec703e561bed207580857451e77ee96ac895fc5bd317af37f44e54f7688ee5cc0533c9d3e57bd4b849f7c83b432d81382e7b505551e37df678f81a077f7aa17117880ec70fbc4318e6e83e0fd1bcc8e252d0142f2f6331907190c25c6e220e03c02fbb42cd1cd245d5b55b6db46d351eca715bcc81790e4f76cc246ae6529d45518a42d1d7854edbb12303db3100fb716f0487dbd7b6c443c9e8eb2054b7fab6d88fb091f1fe914445b83f158216704ab45d8012ab205c71f6c954684a6264da54e36edf8a657a60d956f084e04eb6ad89e2c036ae899a72afbde7e0ccc35ae97f128d2d0ba4f23ccc022092a28f0f1e1320491c5ac40adc1a7492c5d42c3a5c892b86b3740ffd90aaf56b944b9f11d5bb175b5c904450f08c3ee3f4967ebe65952c0cbdc4b62dd6a33b2396d70549abad1ea38d886b04e6430fef4b55fc780ced22afdff5a20ca6b48301ad063c622418c873fd0e0e3d497cf9a3c837ae3c20da7d3a1bea9cb4f4814c04c17ebe3611b1b9b4a6c2d6b77c260f0887385975e7e498a378930b4e2e1a8c94280313b5d88824c824d061be8232928f89575a16d9bf9f779fa7ab166caa198fa60184bfb26d761dfaccd3956d93ad36e5d659825080ab037faf16239e6886bc010f6b08831aa24076457a4123d703b5a4f70029e1d8c6ba21d0909287632ad2224c5d6be4bd2c8abd82d2b7a1fe0c37e00bca21e7c0d17ba0ac0e2a9d3471157fef448d4200809e0da8fdbed84f5f2df474abc8f728c0ce9cb576e6f9afe79abc3d63636e85bc696ac80c69579a55254d6a9d69f04b4b9d59b63e3aa7a308cfdbc2a481a6a390a5e93e9f83a83ba2da9a9c384ed5b9e818a123a2577344da43bf2640f4708f5cd3e0b7c90de4500371e714faaec4cc1f48bb97a2336eb06c0322f6798dce6a7d4d44ebb7d751015939941004c64562d30b8d26e11011462900a6b15cc4b0ac7cbc9cd2208a5a090bbadc09116040dd5a7ad3a000374f877f93e941c92d45b811d6d3cc853e4eeeb4bfad8c505a56c1f1e3f139e97f8c3b3dd5d9c241a45bdb4f9ff8a8347057b448d91a50e578c3e6a09f7f51c1b293aa2320dcb27b0716610eef4aa9afde72d4b3863329a0258db7416464548ae610187aa7adeafeb0d6b4a3e729cddae85f711e0d49dead3534af8330bc06b05c7b860729929454b5233ca20e0e2b1d4d239f7f5ed28ea2bbdeea7333d68dd9275c412b47a1adbc1ce3c88869997eacd0ac0e39af05ba834b4195515e93ce39343834e6a276bad33c814f8ddf0509a494bb37f549484260551274e0da5d50b11051c4afc6825884d5873a8b2dfdf4c51031ed60b5650d0fd0d7a1ee3b77fcd4538f6e3adc06b44b3a261dbe168f5cda18a05c2f27bfe154ad6c2eb39c54516a04ce8883b2e27d0810e165377d20257e8dc66ef566fec8b6b4d4a1718ddc7258451ebbf72a746751bb203a49a3e41ed1ff26cb009f80527f9efc4a29a9817615f942c44db5382c77210e7fbf3ae142acf039ed526470ef1b0907b63d3ed99b654c8b0baea0227cf37f5c42583d1cd9e404364401dc232d6ebfe3b9e8a114c8a60e987d38160ecdfe016ef27b94ed22c115414f413fa85a6bd8ba936080d121403ee49041db9182c62f32f618ed2c77ce71a8fb379ca1e9e4ec588cc6cecbc92f0f7b0d71d271c644a03b0d9af3279446b395dbd6a799af1f0b6d06ef3793c5d60945f45102558b2609b16edb5eaf42a2080126a10dbae659ded65aade0bc91c7dbbce66f45497add1182a5a60f4de12a8ece47e04521450e6e55aa63a33409bea80ce7272eeec43d01f9ceb9c80f607681fca02c4c2d9419fc171cfc855896b7acad0514f7c4f040d501197c48f497739526c0143792ed75562fe1055f5c22c9c07050874027b93316c494d046415d07d9cd94b8b0d8844901ac1f3aee811f0246568573b5b70031b57d92580841753d32abf0eb3467d282b40fd34ebc55188fc7e69a59e8bbd13162be4161e16445911b7641158725609bccf87786ae9a8a9166ab28778e3f5196246918d176377da8ad1c91f22dd6eebd12544df480c6c47b042c1ad433decdce55d34def568e3b3576466e619a9182db456c69156d042a0c53d9809e253a42409677fd159db82964d8254d77c7cb6348d51f7b99098f723d249483e4289d6785247d698a6038c5b9b93e8ce4ef534a62282625c65a69cb0425477f85913b0525f5075180829c4c014a8fdfb2fc097aa5a24aca2975c187d39a56682f27302cfbfaaba9fdd875c42962e4b4621703d2565ddbc455598e044eda4eabbfd90e2594764a455f5bbf1c6b958deeb85752dd6934a43e6d9de862d6195823c776b155db5ee9c7a5430b0825fc0cc4fa1181fcdbc5f9c270bd3b5a1fb9eb2ef01935ecac9bf48cac4fe01b173ea6719f3c1fc5761416c9f656cd184a5ec09676b3f302bcd138f410656b0674fe990b79076401e327ddd4909aa1aba2f50d0d28fe1b2a30ca16a38eed70fb9294351c38582ac2635ad29b1e8f4c7eb7cc5678444c2dc2c55660bbeb96f064a1947d90a18b24189acf21627fdfce475cb1f43456fd19e9e908dde5d9438ed5d38c2168e44773eb631d4a738410fbe40640a218b66368887899d1e26117c554c0a933ee21c2690593bfd1aa4f4ed7030d6e93ca312f6caa574da68aba9195a8a98b20825f05cfb5205c46775b2e7f4a2226f5a3cd1395f69b4ddd2c5b2f35ca2c7f3ce559608a9fa20d7698cdc48608617514443f643a7eaa4e071961dd715a85688666de804845af008ad30fbae730614a0e1935834a364d355b3dc87933e07c230f532d824a87547f10613819f20b23119698f3c618d07105cb64fe816db521937e3688d5ab3d066a11d58a3e4e8ba2a0a763fe4f1fc54fb9097e67f0cb0667a49b421fab2c4906c598374047603a67e490c01f87b872b4de8ccedc64286b702b9ba19dc6079c500357c0e6170feb6bfbc41d018337e2888084180391d422a0a339e3bd211d0c5ea54f054b7a2c034093880db8ccfd2e91b7bb4c4efd1ae63266bf32fb861a680d9e8beca82e18cd582a94a20fc0b9bd9e512ba34a16dee067b6ae468d6199ded0710dfe3a296eab2f4a755bee54fcc4631bd58c6d56a85e0ddb6ec404c68cea2059363ea8ab005e27dc3039ef00b0805a3656a92a41554b61a3dd6ee9aac27da34fb15d226534798fa2788912133772dea2744182cefa9ce4b125a712e1f7d747302b0a8c38667baca4037515be7a017ce2ed7bae2a817e8edcd8b877d934591d0f9fa98104b13836578ca73c0fdf60a127e7890cbdf98698ce7b9a086beeb5a6648de039aa0f41d5b4452ee0090e64f6b5b173a8f881a08c401287fef2d7aca09e8cf8a7df34ef6502bf656d89433216230eef1c7565c9e4a262137da708e61b715742d2f119611ba380edc00a22bc7066cb33af0d8ec1490ac8080048d55c2e604360c71c263d275df1f21116a23aed8071c099bc3e6a6294ccebae9141e7a4ffb9ea23e1434a506d4178532d5b9dc04316304468d321166a7f397a8363b0762684754996dca5e24a674b53812c4f0fb82902ba80d47ce940c2e82dea29bc11c56bfa58d47c47b4b64f100d7aa0830f413e0cd0786ffc648caf890f715f765601f465eac8224c978906569ee4b8c8fe059f99a4703561fd8504656029c9b04b7560a87cf7f6639501823cd3b2e6b284824790f121b8f781774095a4c00284e6752a9ee1c8853a28fc8994f58632a752ae322bbb8f6bb60918bd164cb81d643c6dad07d7ece59d631dd9355924129cf908bf60ff88000ca0f2628b83a9551845659752c755d93c9680824bef67e10a9c079930d14c283ad300fa3850301ca9906fb044156b1b06cadd6d5fe2e66d9fdd626f6b140881fb833c69eab35cf74e20c1a5915d03502661e03494b6485822ddc0bfc74518ab112385cee134edd81c601989a3b8c90b81668c91dff3d1c9508ef20faa91beae119b7d4d9cb192b601b9caffa1cd9aded04a15e28c75f28726b3812b87bcdf0235d5945c0c4753076c0c6b0f0f623838cbeea1328da4ee76ce2fa38b3764de771abb85bfc2fc32fafe8d116e20c5320f057b56487e198b957a071d4419e15771abca83b084f15cbe1c06462a3299720ecdd741085fda62872549905483a053746c2cc1b52a4636401321548cd1c6ae6989ffb6ab3448de565c8e0d7d4418d9d678223a57544404150f647604d315afc537cb652ef4ba6218a5166e053256087f1f7a33f696bff797252e8d9dbec7aaeb4a37b74547f19af46468f9da2cce5ef362e22715a48d4015f56c55dc0207edcc202f05bfa4eb67b44d9261d1b64fdbf929079d004ca05538bd612c0eeef0541404983b05f6460a48eab81240eda3ee2e1ba1cf29efcf5d55963265463cd9b47dd1002cfee2d99246ed3d2dec7b86770ba9ca427a912c27dbba26452b988103c22f0e2d314c16ea6425cfd53b3e7d89325362a7651cff2d8292c471360178b152d8165ba7bfa93360c5d35094e13c30e8c753a6ab51179ad65cf97197465f352ca94762e39fccb628246c193d6b079f1005abbb3880e8f5588548957e8435c71f98b5ef892ba70e02cbc1578ce60028e93c702bdece314a4bb2d00dd47f33adf62f655580ad1c0c4a2621f483cbcec34916b65f32bd0f63c7ff4c5465263b1711c90ab079c49fd6d24dad528e7caaf2465703ef74d43b1856e0aa4b80f57de504fcf05ebb8f38822141bf5eaa51cfbf73a2bb961126b96c2f9a5c1d8823449206487c5879ac517931e2c14121bc937d7b94d5fbbe7008e524f44a027cc60cdaf23b20f28d2440d2c37ea045bacc1517df436910ddcf459fc19c0cbd723640f801e43dd975cf31e35487f81e56716e64745588b2e0a71b3b57ce73c8204db206fdde0085d2db6eb3b02525e339583d759ca1298b74836169146eb0fd431319fdcb6eb8a93e060d41e20e43646640fc87078c760d07483cb050e72f881347611d258842679d853968443563b287c0ac8d86c69f715f229106e2acbbb4f61bb976282a7427b7caae2ffd17376df9c1069a48332e567b3211ebb6135bdb62f4beb2791a393bea981495de42f15c7dcd7e4d5b086094b309a8e361071579203bbaf3e8c369d90257ddea5b14bcd94e45d25bc5fce32d85092a4ae9364db839b96f468b0a5af0d6b95609faacb6899f6c5f2b0ff85934ccda97df4081b52728752a8c3069824329fadbb4abb91202c9db14c1c2c0211fda81f5c4d2345137c916b8e451585042d691a87efd2bcab98629e6b949d57f236a1b3f764a07384abc3f3b072807e15097a88a8da10291f9dd038d71e271f1253903d2a4045c997d94cea1e8f12ae653d21e6c78766c31fc6330e201b862b770e6d1afde718a8a32fd0652c2f5adcdfc342182bb619ea4a1305598e7598f2cacc46b169ef11ac117823659c1be87a0c3aa247723095ec3b10c102fdae4f31935a41b377b4b5d8211100cd2bc642c344e7b2ac362f830c6750c31311ae881495ce01892f6a1624f5c229b569e2f064b128eb31cb2368ef882719cef2c19fb87358bfe9dcad12a2b6aa5eb13e1a1b0b4aec3b13c6ac262198dc149585c413dc0604deda8a6f94f5e2a7c8c66fb9230dfeceaf3977042967525e0a08297fe10a74ee31f5d71c5704a911413d4ce30dcbf37d2e243263138154a75c073f1b1130b9ccf69df01e42c1808227079c58881173362f32616ef14954af8e8c5fb97fd951060757f285b2f3b21706424e70223a05f3cac17e6005b085c551ee8b17e9c3dbaa13820598fdff838504740957f0f67414cc52a82115caaa67025725a8884f0ac47e116dc0319057a23b4c58c1a72ab36681236872d57ebe0fbf6b3847e95fdba19a4036e7a03ca284a5f85089065e7c772be2799913ea03daeba46969b6867ef30e9359fd1fe490c55aa85c207b89d29ae752b9aa2ef23c6c648d0eaebba5e63823c9dc44783f30e74ba05c41d8880ecd8cc178685099c86aac1c564da8e970cd6f7e6fa8789b7d60c4bb05bbd439c151ba3851b93800f4520f24e61bc603f22967acf87b766b45b845ca6d5ce57ca6eeeab3c9854a3b3217e6e8ea543755073e5eeaf0687fd2fdd6323f8f86b917244d57842440cfc7974c3aca6aa7d2cc30d89fc972a08a2396b67d1a1458f884b0c44447ab2051723e43bec218ebceac017138dc9345db1a438764e2a5e48fa72daed1d9a08ba7da1060887c247df3bcfa79437df3a6754731ea91fde4b4586c794bc3b850005ccee128ebb55646119f85514e412e21331dac4e49ce01ccd8107c03ae23873c9990f45c8e1a0904bc73bbc4db00f16f618adfb4505e56dc8f004576de3679223a8dc56ddfc90513b3de9bf6af15d39d71c8ac9a816b30e44e9c92e6a7cf250348621285f7c5a45a264f39860006bfaa83a307606c61a88228c0ec08431c5f67491d41b1e3a25c67e2413a0888b10f3566dfad936b0e1761649d83c3cba6ba298c24416bcaada522c4340c37e543ce2a33a550872aa884e294767c6be3b494ef207f4f3f57e3e40d05854965ac3f2916a03d34074fb5524a9f5b18c30b29f83c23f2ea70f83b151560913dc9c2b465713df9c3d4d5a8239de421cdedd139f9a0c05906422965e6eecbb5f39a0d0b2b19503ee70c7a2ae871a65c4c1c4b90bf7fa50616792fde022add6b017b15590db154a95d19a5a4590e30589ff58008c5ed1cf3c808d4adcb530ca1f5c9f74fb68ad80cac278cd82c420e765b4280643ac79519b274ce820a33ee2bccc6ea73dc4fd1d27818372811d4affe988843a83636d0e87bc9a9c9fce4e8faf22d2b16e2c44fbc72d0bea2028aafa2dc42e98048e11d0e61150f6fa5c7b6a82a28023a649d98afbe07f3cd450bf96f36760b5f04ec9335a024428aa0d35fef0718d8778ba49093791693b70f4f76ab270cca840fd8b44d1bdbe97a35cab0a7591228320a0dea03c73af32551415b14e8c70c8ac82628d4543731358c9a312ffef468fecc320d10921fdf8176a96dcb4629dabbbe4ada5011312613fc43fe19d590adf31933996b9aa1b18516a93b5552cc983c7260529fee8c31784f867d4e65027e61c09e8fc53c4cc0ad0c26187509362f8103aad6026f6cdd2b9f01a13136b0b8cdac42b243325094aa54c15e4484a3aac3d00325352fdfba8e8c9220af078a5fce64f4fa089227414b63c6ff85dbfa7419885e05fdf171e1d3fffb2bb2994bc48a9784c48f7ff8e04878fe15699bdb533e71a7d88868c29f3746ec6bec332214aae3c00b1fc834952f2bc2279804f9d2889ad62637750e70f96e73a2984318bc1edbee232d79e486528f1725e856739c62a070ba8e233ae3091bb8c1df875467b3917357cc90623adb944ef11ef14e436468dfa6ed9cd19a2ed406d25817737b903ace3b96d2e5f5ae3eba278071778adfb356470cfd6faf449846b8037b1de1dfbf4d1b0e6d18623e0aff60a913656172d7f642a6ba01dfdd95e206c5157c24b49e854d0d9e6b2623e3f672272fa0c036e26ca8128e29db3593734ba0fbca696f5e58b01ffe6011305eb77d832d6a22cecc5a295c871886dcc959a666c69ee6e5bba4aa06ce81a7da3e6d14ca013afa751204769e2f71d86f3ff413b899aaa3e5427bf3397abd089b6f7eecec97a0bab972de67be00c5755469472fc8f9a2a092dab91a18c9e48058aab28c65277354878e8301c239701a910f59f1c119f085c2290dcc9c4e5ddc612e29c10a8e64a40aaaf0f7bec649e3a4fad6c9fdf7b069fbb9a6e46f5ad08be6ca39afe14430d1ac2d0c8b802317393cb257905bebce626579cd5fc7b9157d82df76af9fa88e4a81ba4d89253904dc435857884d6951cf47a21a5ff7c112a3edb03f7a5c9029752b8f6bdb859ebe1a44e595b6047a272ed43f49e938a70d8040741c0ab437b239193000778c46308604d533604964962848d91e16f0a26e5703d83ae5c273118273ceb46433fd34a86901b05b74c66373f47fe4c1bee67466b636bae057cfed1a7226b5ea3eac776c22fe49a97028fa2a21648d937715d9a02a063912e4bc8e92930e65cba0911239e2f3e4ff332cec00cbcb263b780c385d5bffbb3d82fa639f973d298f101dfbbbfabcac658e8b58ceba5d8c1c95645ec8490119229bbf2d032327c7809a0a27bf86dceb0255a866f09487fbaec9e80e048327103e09aea1bf67efecd9bad8188b96a0f9a3ecf7f8c9b5978712e6e1d32dc2c62907c3c68231a2bf433a0d9672df068e44c1406328cb70ec72c3c803b5683e59470101233cfa8f018c478677c3eb630d96f8f28e755b45a43b365e07735908094794fa41a7fbdd906995f4a7acff0268f649154cb022db18ca61602afdcac61575a91a8ed5c05604fc3a81864fd3c2353198108461d5ce0a6f483fe7a108b073765927e55d6989ad85b5089dc8952a6eec862c60f3c5d57a07d1c90521c4f85213e467b2ca24b0be271272a3c02f7b326948e157525e8d2b091f93a6d7b2123af043803620e674e395020e0399ca3c11d48e5808f5ba78c9e0308eecc4b7e32e26694c3438c9e191cd6e4279e27a04ac4f1d4858977a27470920b7070aa10e04726320d83b0de33626c9cfe6236a9588fb1694489d44be59e462191320c1a832e95e58a07366e616ed532daa6f40c16bdc5ccb0545d177dc7f2c9778bbc9988aa1f38feef58f6a9addfb15584b7a33b1a60f1daf68dfb80af1a595d3af92f8fa28ae954234b87283f536708ef2a42b5fce5ef8d4312119cc5cb27d91f19cef2b11dbe26a198fa8aa79d3c6f25f1c73ab606dbb5607868d3d9ec1b04079cc8705a2549faa790ab4d7406a84b85e29a9ccd6f91cb65a8f4d57c0312ee134dc301eff04099d45d431e195a5e59acb4984c0ccb82436625b6dc014e934ea473065f9e695b0e71256feeb5087d5638cf6b720f01becb7ba1792bd168c7012bdce04c68c3e83cae1f27790e5075fa9151dc2f3e81f2b5bc294e4dcbd5ce596c31bd8833472c9c9c6d556ac5906489779873bd317da0c298e01904d6c574925520fc3af2ea884784ebbc93735b54de8b69528389dcf4449df745ba93a1ce857ed46437b6a67a528cca0ca1d35755ba92c9b8c6443bd21aa6f4755f0e7ee4d33fb58d27960fe1eaed169ed4ff514047ee8699b3b39350945a2acd2f97d77ce8a14128f1b9875c09cf9104f7e129dedbe526a24da632c0dd47e3ece01a5f430b51c116612d6ff0569d8c8af1308c5c52353d9bb34e94e838494897e6d7dfbd3ff6d6f9f88facebf7894dc68afa11f247563ff789a190e641cd62f2968f5364ed0b898e3808b33a53306664c742f30844a4cdb7cbd9c46b4979060dcc94b090e2b3c8b4c45bf1cc20f93e23eaa1eeef8744f32f777a3d3fe8ac5c4e1766a474c5afec55bab39888fae0923b67791082621fe0fa61378db13a36365c3fceb4b15a9d17d4bacdc84273d084a2ec220f5f33c8cee0501b07399e1ea5732af6504363903dcffa6c598b66d478ce68559c6a525438fb825214b9cf53a42c1e0b2c7ef1280b2e9d082a7901ef67db7a88cf588af0d4c64b1e1c48a38c58ae82288f848e192e98029489923418e060a555ec3352c80133b5d3e9d69905fa6795e07ecdf2c5094a6e6c5e6c66974749ca9a0c6c4cb958b86bf8559e9b47b466040ca06d190b43e1344c3d65715864aab2410a8a7eb2e1706ffca217fc212bd870c8c1f5f10d1a78540f5a44c8ded5f6f9ff4e504b6ae13498bcf799a936fcb18a87cb78aedab8be2ce0de587fa89b2c9786216f6703d8f87c789499feca24eb4fe35194aff016919bbca91552d206b2d30d7d8b9f1df6c320a00eed4409025a7f9a5d8b805d549d583c91d56c21bc548f6361ce9d9a2069f5776c0420ee9146e305bb7b3a9c06de17e80fd16103140bee39a5e9a58128885b5b7e3ebf21d76f3e994544c886c97ef0123bc23e8d443f83fc6c61fc122aee40858ee44e75905029c602472e8122331709ebe539188f49e3ef28a32ca55853db68091fca0b672bca2c3c17c9842e278e9d272854ba12eef4427f50ecd36cb9b4acf1dae5713a1b784a249537c0b71dc1ec997ccb687c6d59002692a52cb3650ced79abbe250747c3bb3c4acbf4ec4767957da2266180c3d9786dc421ae403718e7fcf7568a014e74f16bcaf53d7cb5bdfe17b13d7060fcb72373512a17ce30a1d92e21823f89ef07f00a6c6e9283a813cfd9c3c9560eda0224d896af8102591295a39a86c06944c22f89eeedf78f5e391e03729786824a9e3ae1e19e4e1b08ef386a0d0e743a7f0bbe87092d2fff14d4f1896ad09248bd81ca662fdea35fe02f4c1c81de27ccf816cd39438bb60bcda3a0039411767d7f5484ef9445d9db7ebd186cb862a1e5ed34af359441488829ded939a058e5bbb52425d77ba6987a85a7a96898432ad063082d8e5da5ac79ec83fd84dea919ee396fa0258354f9b3e90c477e312e0b10f7baa0bacc21590caa0da65a54f8a269e43c48470d9e9c6b9989669abf32737ec12999005be003b59295dce0cb0694337fac5f49e0166717b8104360ce2a1d8bd8c8c630a134645a4586b5db2b80d34dc2bdb7d71ed6703b0bd8bec089c3a23d77c3ae5f72d35811880154362f0245386a08f2eb8fa320ea34303b21863e073ca46ab0c4390ad1b8d2a24bb335755da0cfb5c9ea121812294bdb0f675c6c295a815474f351d2e1ee7f1e441417c266e2dd918326fefe8acbec9034242c9bdc2ae202ad788dd964c51450395c06b63197b0d6be6590505883d397bc3692be83fac80edf09f94bb97933afc6ce5cabfa7df65f58f907dc42ad61f80235a540c3718b3544dbbf885365944569dce3163d546d72bffa681d04e55571c2f8c44450972b8e80315ad1f171cfd15708a5cd97c90d73803294916b1e28f43d442bad894708d1f075546fb631d599a3fc5d452344c38dcecef7873e24b4666966a6ef5d976134d7808f3582f0fb4eaa1d1522fb4b680a62df8b737d480e93cb8b1c36074502654b7d6a3ba55aa2358c60163a6e39d0cc32e89ae3404c1ba96bf759b4948cb31a7397fc3f7950f1552f63d154d406f10bc255b9f87bb97895a88c592fd71ee3c404340d2299c368a08c4d1275ab3ad05d6c4373bd470a5a725325ac483112e7d7d0c5b8862f92fa853dce53f32052c289b65da16722202a36e40c7a4bf69806244ec53d2da92d4d0904911eb8adf4494ce5674bfeec0193bcd18ddc7c6e175d73d3ff6060487ff2024b139ad5440fa1aaf3f85f9683dbc919167f2bfc1c67b31dce02ec2cf34d96276e8fadd9f01cbb02619b88c9fd031b68e43ea0fa3e09fb9848bbd9816ef59fc4c8ce76138038e43fec617bb3be6a08ba6ca0133ecb925b261ee4a3436aa813d609516424e5552c97344896bfceafe2f2765a574149c7ef5c456ca61e9eebd16998a4b23b513992aa21744503aa5fa3b8a7950d0165090549b3042421360895574268f595ab610dc6084b04240989340b3351b4da016155122c3cd0a3880a456bc4d48ab487d6ed9d870e23e8693e55ef22efeb9262af8849b58a7cae11d31da18807abc557d663b5be41d33d9b531a77015619af976ac41f3c8ccc596302acd36322bb683a2981f11cbf9a3a974d48958232277ac4340b136e3541b4929899564bf5d7e306df5efb2891c404f1fc18741234d850c970ceaf951d54dcd0781815b4fb79392416fc95937060b335f14472dbb8b825661bd417cb39f9b2afb12880dbb9da948c28bda2f2e8834562a2b7ff929854b3e12afedcf71d747b222d32a49899b74b2a0ebbb96d997481abde8b30a7a8e9958cd1feb90b809e86818065b7e68739af4c04506f3df8d18917f8818eb685b3b0411e9c84d936ed0cb5246ce37b58aab013dcc1323c8ee00d328238c8a4f1532b58efc734319ed39cd0aa451c3880923069e55d5259d640327a76edc72404e2ef91dc52242cdbcc523c1a091bf4b3d9ff51b94aaffe5c1ea75c7d83540d9569b751c4ddd292ce774c2ffddc9776f47ca734d2f77ed1622a90d0cc54200a1adfd4bdc12f99a2f5f9c9c1219c9acc745fa26538fbe75cc95d215180731c4c5a5d2e26c2df3ef15681ed8c985dba20a59b9c4aa1a9ea6e655751733f39b42a68f742152bc3302f92f82ed83545ce923d50e07427e811a2578ad51ac4d3c5d6a1c2aeff88f2d7d82997e1570f68fcaa6e282dd204ca8d48362027cf4d2aea62ef07848b26488318a33ad295c4417d19140588211ed372c4c779c450f47991f4f20209a6e02350d20f4b5d7bb7d8d87b07f9ab2f4f58faa2f5491c6a9417cb64930d99807f67d605bea332910e5d2b08569540169d68ec47d6a4a6a17d41d23210bd4efae5bc0b02b731a59765e1c60cd3765b582353731892ad175815592f3ec72edf1eec97d447c6b8ee4c4b9403d146b1ce2efd8437e9d3c598dea18e8dd02c5f69814fe830d24a0463609684ec4ff6856e9dd41719f2a6c6509cbf4b6822cceaceb2a004ea9f625c2f812ca851dd04ccd5a3be818dd47a0337b8075c90606aac27d7a3591ea282fd6ba95dd9275365f229fd9ac5fa8ff4e5fcd610a1df63597aa2207dea09dcdf36e6f00b9db54b3f6822417fd08b5d4504acf6893766d8c56e65f1caf5baea3e1893bee37ae56dd58b8a248a6e409ed18764664c5b50c67f3c342e91f21b1e12a90b5241c46757ea40ea6782c72fc76666032a9b48074f021bcb1d952c416a5d71347d83b3c88499ac4138308b36f29a4c5d8334e19fc06471a2267d02b788f88f24fa4b91b87b4626397ea46ac3d2ba84a2af57eadd592c431f4c63860bc161640150c94b1f108cb2eea5f9888fd463a22381ba9e736b641e23988a37ea6c89d45620cd3b5d7424d0ebc91f54c91c633815f694d99214adc0016f4ecf3e2517a8f8e55bb3dd13118641fe9f0ddc361c566c40ec8d4285e5e6866f3271c506a0e0966c9ea68a4280c78241b05487d6f21c5ffa2ad8391dab1f6933ccc21293f425210e991bae1df5ed7b7dbcd6e7934484f57111bcd28cf47d9f2d4483f6c384984fc23936c63576bd9ad8b80bb22a1213f375db55a711a3dacce3d5905c8c913880aebd3874e7d12a97b80c98a5fbad154c66c710e5f4d9bd6aa8f1e0d6baf2218e2cd1b1de061ceb55eadbfbafdff4d3d448f03743ac758a19f4326fc01db927d0f5466341334b8e2ccc6e92247a1290a85f847c6733bad9f46e15251a61ac25f9196074b057e4e53494f9bf463a3f8b07d18fa46c549a31359d8a3334ff4d13a63636193a2a96c714c1650b06709ccb24bec60793f9123b4ab24c4ab6fa996585c0e590e13c5bcba5f795af6383f6052dadc189f0a4b52cfd13c5453190166a0b6989cb39fb2fba247f71e9a09d5443be3083291ad594fd6fc17e0449e6880b35d8fe6784f1ef09a1b4577214666765c2a20df96932a48ae0f91d8fb23afddc64f9afdfc89172628e5deb8cffb7a0fa38da90ba1211d59185924317a9703b5b9fae55734ec3ea43978028fc3797033909178c46dc2b515aba7ed58b5e59ad53807df8943b2b95a269af958861ab6d286235a2348c41eb61bff222c9ebd37096cf98cc3c8528daea9948b32a94e04f74344a2f1b9940b8bb80cdea62bbc2acafff32c10f623b411a8396cd8bb800b02a141e9423103a0bd373d93dc1f44c4105890f84794da95afd2101a1b24aa550a31ffdcdd75febace2f52f1672b35b2fb724973fda309edfd4b1945c11c1401091e41f300a9f1d7fff98806e41dc98dfd9e3ab119205594e4d8faf7a28df80bc2825c457130144c750d24ba1e4f469206d2d17eab72e54d23b7937ae878464b0ba798b9429e71202c7fad4974eb83c7e2af034607113859320bc90cc50c617e35d2fa57461cd160e2935c86b9c520c9fa490e6b0d1e675f360522ab9cf2a1eeb26c4dcbcf5b8acf42e8c2ab8bf54afba878bdfd5881807a75f8845a89f97edea749d752e051ba7d2b1341425f1cf58c6857087f89acf6936a63d064983ce453417c39c5cea6ae6f7da0d39684d8f1a86fc44ffbfa9c210b27dda65208df4babdeb77169033d45a7222b1f4c3a9d5a6725cc0235ddd7bdf625028dc70667aace42d1bbb54689d19765d3ab39ac00a5fcbfb82769d250e88263c6a0b3b66a2bba751c662ed88e9a9a6c98dfaa27b7b23bcbc09dad93d5a918f88bd01d6aa32e934a1c727f4eb9dfa20cdee13aac7fa240fd1e7e6cfd56bd5f254d95400d453389622743db2d2a5a287d669534bada5b5c550e13d1176a6f30fd16ae69c56a562c5f9c39139f812d92451b0bf87d02aa874ed0ae1a6bc1ce80be0342bc811d3c0a916a19d933a4940e6b7a46fe1172b290a9fce3e92298a1a1db7736f7f457df5946f76cbd59e97687c1d5af4bfd2ca6f2dc1f345ffe580722996f966d952a9be8a4b5b1437125cb2cafcca5e05d19f4edcc05568bb82f46a453ac84ebcdce660706cec272e703626b202a30086c2ffa6809e5b00405b45701fff53629eca174b3d00142cc6091bcabad4a721192ea0ba541a7fd71063ba5da5bcdf9cfa78a939f3ae419e6554d715ca8dd9654d153ab460953213abd44e48505a36de95a0deeaa0746e8fc7103cc8150e018ade3e2affff0844e8158d0dcb6fa0a7301e06b3e945e599d2420d3b0148f253edc0018dbb641f04c145eb5439ab2a6acba7f5ce45f0f812b8fdd2ddd2cc5bf2466eb17ce1a36db634dfd201d2dd66a648033edd9ad2c3f0b8d8e149f89e4c3c4f0fc62ecf711b1ebec2f6804a3693a6ad850e00274b7bcf484a7a7f1e5c32bd44cabc666201bf57aa571666179e0a3736cecb2e59de55393ac62531ca7098f008404f01a20f6ca5997563647455fc1afc0ae5819aefddcdf4b02a92765331757c6ba074c3c286fa50530ed85dd0cd1cf16f0bdce8f8e00d8e76f151f0d2f009deac13dc0cbf06852850dc474d55bcc1615844119d5345b55c72577ac3168b00f48f1db94f0f3420a38adf95fe3e09ddad9986c3ef8477eb3855a55077831b3a6c05aa4bfb0523f20755607f5aca9a39bf0bb8a23839b77b0d99fc0731dfee7610a854ff4a22f8349506696f103ac4c5ad3b88222d93fd4ab0e8665e2aa46b8632a7a06e1d10a40f761770b3a3833739bc064ed71550dd077b713cbc9903a0db50b9359b037217bc9163b37c0bbc34f9056e76b01edf8a289d9fc01b1cede3a36061660a7bebd82c9f0a093d23a0e72f11fcbf84f2ff957a4fce84d7b298ccb7511fdf0c3f535534542af5cb4e3e4ef38130bf8057d2ecd26b734925ec6da35000dc0b52a9e859381a27318f18f388bf37067a173df29fc1892be4c990a49ec892bd3b537ea90097966e54ec09b21aa40212c2c7e6f179c14c5ac0c5e992ee6e43c40975e40b4e198da4776b647b57f2dc2463d636cafe5d5be5501b9bf3ff985fcaf0852f61d7c621a0afb5fd602973db1ebd8d645e74766a3b7e2b2b13ab7412d54e74cad977dacbe4f43238b88f17f472721796540a3ecce0af53a91c37264d45ad3d1476d51b52a6c2c44064b446b62f25efd63b159525e59b53e88b1c97b299f58dbb0fc432ee427b6a869b4a7d79988d2f4c445845239df8bce1070d8d05b900d94491d7bf3f3b6ab03c55b1abd8a1853cf1971f6bd23c79e999dc21b0ea602e44850e640cbba4932a38622ebb03e83e7428619598b7deb2daa8a89be0591ba3bc9db29eec7029db59d3f83d421dd43d330839f3e206dcc155427416fa079632f7dd27e852779941c899835b512d746a07ef8462d385c68c4ad017ac5fdb933d2e649f351abf67c3d619839fb8db963f1994d25f05a26d4543e9fd3a1b4e78f0e4de56fc7450d4e60a886c270ae5f7d3db6ae04327675be0bf06c3e7de11581dd252ea9ebead051e71746d1b3f5b7034f39e01aa587eed70016ee00a801b70d50935aebdd8a400faf09c2aaa26f2ae9bb293d781272d683a608d47c62e2ead6af169e7b9a602fe87067faa72db1b05a25b631aa5b3b12a64a8eeaf29c308ba2ab54b82b242fac25afb1821dee1acc989c0a4fc924a02f1ffca94f52bb99e64f520b717b9bbe4d28b588897b5e1e40183a017c077ea04c757a140f8d183822e0dc595a4bd6e81bf5e05882b5ff6850ee2a6bcf6f3a581abfa8a484e69cc7afbfdaa04b194975c38c65b70794181dfd5aa30ba6707c13c49c6aa36508cad563c9efa2ac153e99cacb52bed62a3db2d230825a9a5c9fd28db5ef082c6a9585040466143cbcfda992e570e6b6b3213d713b0814613d5770d4922cb8533d1b9c487798df312749cce6e440f0e54b1232f28655086f920f393374a12a546e14e648a7e3ad764a17ef4c147ffbf5ee60dd1730dbbe2a3048a5bbf7a68b886fa2fb3853c49305dfb2632044e45e4267b11072020698ff3935a16681f17cf5ccfe97a9b743162c5feb9bb229e6569fb1a92ea1f5765548e9b4b89237be48205e50a284923ad84ef94190bdac2fbcd69d0a0bc923f3fad3940d69e0947737286e23820202b8ab05dc7ca4934fa3e023ab02204de222c2a231cf7ae0c52920624a96033cb99cb55f9ada9d92c5ea31edae468fc2b722fa15b2e5cbc2ce342f50a7a70e5f7bb8c678e4d8332024b3c32a15b577d8578ad03150b9fc0c99628daa7c095afae2162e943646929a4743d1cced4066f6835ff23d85d3166b1846f0a6e461e8127fcefb676108766887aab02c4ccd882d5c6254b23b6928008e2ec9d1f81668ad8b09ee424c300ca075040214aa60d1f975a03a7fcbc0fc99944a505433895f23e88574bc06d34300d5f3fe7187cdf637a3b535d2568c5da32700a88175d8b7f2dcfe637239154680646e293feb5db7049184bf68112f2d891bff1ab8f79ee0f17c922501badc4ddd49aba38ea29c2daee186ecc06a73c4d26db02344e039a84b6865d301416710ec9796e67a89fa396782ec6d1fa3cdae7d1f6bc79703b52cd69fcb291967d173c35392f8775c4932f1d64d62cb6708fcf41fcb900a1d7079478108be53afc56330691d0d1894f36d0ef3efa59d35d51b4b2dd448894524a198d0851080a082252fc29e18c5144304aa887837e8a4b88a90881206212d5bc9fb68c64080b71421f75f5b252be7cc1817a76998aa55e90f08e85e462788576d51863b78c9ae024b84b5076cdefc3e4d8bfbbc59357ea904a96d2e374fe4221100882362c3739b3e0b0b8f6890857b3b95361db749debb6c4a1ebfb3f1abb05932ba7d393c81bd2bfcbb73c87fab9194a65a1a054f3855b2914aff413b565baa2aeb46cdff41495b59be8d24bbf34093e516bdc7721c126aa0af75b485048bd719f8504672058a3652e4e103c52572e5a57432a4159ad71ff658b3048bf614e6844e7a1082acdfdd0476541945a1a95663f8e4f8db98f82f2a3a37e2eabe4a39a62177575dfa502466d5663eea750dc128fd4d515b44ea4aeee8b42aa7987f45b2ff533852e49a14952e84c7622439c4c86e8358184b0104797a2eca5093f4199c7bdba7d4e02a9d7827c8274fb2b25164214033c929f0d1b210054b061e3c60dd0065b1b7abb2b8d4caaf35294e988ff8204d47322774fad61aae1956806ead496890bd36ddff6adc4394a949a94d444578866a81f1b37582cbc710f0bb4d1119f05eac832bdbe0c70972c282570700b8481b06dbb2c13ed6e215496698a6a9a6ab805ba4433dc02f5602e6a4b5483babae19628c52b30bd2cd3cf9541659940d7a9d24f535f296bfd86e559e2a2f2e4a2198b0bf5835bfb66516409872ca12597782373d0819ba628dd74da65953a504dd0855ba21675f50302d145bfef126935e63e488a3211d4835b6291baaac14c94e29628a4aea0883b3f97252e51cdfb57b47191a4a78b47fa05e19f7e86b25004a18f4ae3be890c53bcba5f22c3180e7f84e80db7c2170e61ac17257017264c5c6c97e561237e00c1373fa2e09b27519c3c61729d38e91775a6d07e3f855e1690d42bd5026dbc3285cea450d02695022285a680c85d4e01e18d40186ee57b82314c8254d898f47ad759dbbbae9e6b0da943fa1089c565029c1af3835b26199ef5fbe7899b8148dfbf8bee78a7b991e9fb0b287b016dc0212ad0268582b2149a42595ce60c14503881c5c51203da803188e06788e3eaf7c3d765e1fc08d110c8659d3c70da3528ab34b59ba92f047ab254e158ed1e566e06947c39f07c4dfe2129a7b035a49c4041417dc1e59c8340440e82308220dc8420c6450aee45696902ffba48ad80c91296f400094ba618b2048a16db9219fc58c282254208f132043e0aa34508bebd00724b25e12324a92614f981ef26504b6c31d8c29684b60d41821165b381a0c7e6431425605c16c7450872ceb945a684c9164aac5042fb6184ba42c9109a122556a024055828c1818283509af0951b1259609193c59127b2e0e1c942c89bff6266a1c3930510287f04f7bc4821818f94f8e0811452a0c000821230008209522c00c20f66e641e2e45f78b080d2bf5e7980e0817ebe8ccace6dc2e807114678c1143d6e508af8008a1246261768a002248c647103286e6a7412ba7e6eca359a874c3779239e266a3a6882d64ffeafd1ce6887e79c73d6dfa3cb0c549adbad0dd7bc37f0b866066ab8e6fdafe69c6bd0ba9491e25caf69830edb6b88826b1c41ee9b956707affe9755517d89a745b75d74307ae74cf0418105133d44b62062ab818228823ca149e1811eb798283c554049a180810d4d0109750746c47e6a68a2a8a9cf2d0bb5abf0ba0299acc79021af0fbc8137f006dec01b78bbf7de5a036fdc7ebdb7d65aa1c881b7107e7abdd65c73ada587c578734282fd516b504eec7c84e3d187203e1fddf72879e8d1c39354f41972d7755dd7755dd7755c596b8daecb39e79c73e672ce39e79cb9aeebbaaeeb3add755dd7755d97bbaeebbaaeeb9e870e775dd7755dd7658ee4776577119fdd6f537e787dfbf7455fffab5b93e7973f606bc20f6d6620953e907e871e6a48ff71200cfa8da4fca8f2e0d76b82e5f9d2f58fe0733faabc4af9e60896e635adc601f4677e0be0aea301d2cf402a7dd88187309e50c2c517509810460de97bf450432a7b8c3e2c4b1c40afbf0b9138d8306adabbf24547f42392075be3430da30687a54654fa207d0f1e7af4e0e527552aba7d12d983879827869004145c1c39a2a726fc1e74aef0c0125c50440b3ac0831a1289f4025cd61a432a7fe0b8e5a354d363071fa59a1e3c90c2300cc3300c476118866118fe07666ac2b2c7068a383104134130c209d29ad1af7c0f1e7a5ce04624481727b8010b9450133e0fa4b287e86f6e6e70666a48ef036e82ab06c7eca2b85c3538a61ad2fba0f2e372b96a70bc1ad2dfdcf8e0e2e3d590de07176ca61ad2dff850c398b95c1f306b48ff81991a52d923fc951f4190c256041122f0d4dc1f912b24af2bdb43ce8d0e6e8e30124508b59a3b8413558c20084292f8a8394d5de58a96a00dc119f2bae0f77925ad498fc5c901afa65784d71cdee196ad0991df83825c33f43807c6a1232bc93d8b410fdafcb2422c50c82bc26be73e07f672c876075f7d6102fde99587895b95a2571e2660dd82369e0ff8cd39e75a6bae357b1e13ae5e796ce063ff8ea0571e99ad7f15b4f17af8b5f62de84b41997879d6072b8f97fe71159c9282b1a9d794514a4a0ac93fe5b9a8d630ccfa2bb949153285445d937c31614819d2068e6b5aef096e7b1ecb171b5a4f0736e79c6df0ddefb9f75a7c7bbe165b7c6fb5768acf4be25dc16bc846b15178984c1b250a7effb44feadfb2076b2d91af7e597c40e0f943bf5005b25f86d740db86efbd1fbfddfbbf6ff3fddff7d90cdae472ccb9e4f706b43f33be3f83217004c107c7ec7df67ebff7a25d47d7bc5db6a4e83f4bd321294af9d3eb96669fd81f95d76f9f5c5656798b9ef64990cbc225ef3e4cda6e6f4eb48d0d72cdef73604e863e07562173c83d8534bb1a5ef84ccef3dea5794d0deed2a241f8697f5e08edbc627f3ffe8a4b9d15f0735c0e04962308f4a095726565eb9feddfe10520acffc324ea9ada944203defd69aac1bce62e9f06d6a2f534c16deb0b35b42cba4b348ab6a87d8be6bb51c0df02b140427a5bf4a222273eef073c94a53937c1f1e78c1f0401e7463b7efddea99aed9d8e8ba097a581bc2e0b96b36397955ed678fff7ce6579b22197c57359dea66d231bc9658db944cd3a8ae79a246a086ae79af83b122543a5a08eae933272b8eda76a76a252d485543773f49a13508089496165ce90118306ab35f3a25aa1a46101470b64005cc8d16bb2e63c50e8dba0cca79a75ac9a5d96eef8553c68c7afa25d96a9e357f9a8a2dc164d4d65e4b22a4f14381da35458d8191116f453f5f91089413f554f543aa352d4ce65e11f02c4f888ae4d828bafbea4fcb85adfbbbd5cb1c5f6d46b95a1027e7aaf6edfbb32e860bb282d4b0fd6ad2848e7e2848e2a8d8bbb01d9b40ffc550751ab5641aff5ac3c1d7765a85663f0115a4b053787ac69315ac0ef2825e17057080c1659a5c95f8d84c322048c4ec2f94a16598dc1cf5d1de3e72e8ec36b1cc928ad3425d04f8dc1e449d3340d06337c3ad6dfd2e2ba3204bb5ce99d9c33b99bd4717582414542b08e6f0a1545faa992034ba1c2e4aa31415654725662b4805f54ff2c0843aa97688dc11f23097e9a92cc78b1a4e5ed8a254c493a3e59525bce78bd18e1f7cf94ade34fd92acda8256dd9b18fbf44edf7b3bc75fce5cf2e6f2a173fbd8e576a1d7faacff0d145befa9261be2ec70b69c718f348f1ea1863de7176cd3a4c0a5bef388cbfe8f892b59ab67cb926d6f1fe71cb2b7905f94599c0ffd2c54909261c0380917961061af02fa0664f061ac38bdd2f7739cfca00829f1a064bf54c460f7e6a184d1bb92ccce5a43b323ce2fb4658d3df5069be3eeaaf4c32add2943a7e9d9970958b9f5cd7b85069829f2f3d95a645ef689cae87b46c53f43075cebd79dbf432849f2fddd46db63f53696ebfffc2dd5bcfe4d681c73dc04fd334b91f19af3462bf3f81faa4f3a8b51e85fba98d2070a5804583141f51942087d7c1ebf6c9fbf6ce60569a53d72f9303b7405adff2ab82ef0741ff95230e9523e6f487be1af04a7b1f19226b63ed19ebf734fcf77d8a4c68e633fff19fe626eb48c3fed2c32b1a42e7cc83bc5206afeaabdcab5fd0324381a0b4efa37d1faa25f5a30b687fa53fb198ba2ea6338ee2eb9adf78a285b01809566b0401853924888242ec9ead04848148dc9a808c807e9356e42382854084382031e79a222c8485b01016a62c5fcbd7b212964699993d0ec52b1cbc729d6e371d3869515730595a6426a4ae7696487244d6ef0b164bf10a8cf1580b19e69048241710a9d3d2f2c2ab103682be51e974e4543b15a1cd64e20b26c6c4244e3430166b496b0c0caf46b61acd8b93910ea2e12f24be64b1ef1bd3effb4a1017829d66a010977522b295882a00d1c614446b496b8df1556e1f5193130f0802f105dab09c7b238491d20f44ea9064f80a8b808538a108443ea22f766d57b17bb60f38048882b09eed03c64872dc4943225a44f82ad2ef8722381199c95217527c89b02fb8a882317501913a2e2e20da888e3835667c8d3f461fd5bc3fc24e444eb393ec34a4df3f89b3ea23cac4b48514632211e2eb8b9075898d631ac2be128c8140692ccc39228cc1f6298a9cc6be07913a2854f9e4ffeed93f808de9fe62fb887c445ec06025092a516bdc2749f005558524ea8dfb2309a678250367202dd602d1c421e28e18a473b1089188ca42886369549aefc7d7e8fa8111fdb24622aa39766044471fb65a8db93fa6b8755a52575ff4f423757559a722222f4e464e3f63dac5986a31a635f125c2ae99134b65331a5e9d68b1d847b3d1686108fafd96948d10001fedfb4eb431155fb44dab3544307088da0a810051dc0a717a7c7a7a897398482412d50006b3f1d1be3284bd700b4451f155bb2cd1ad8aca123511f9e0d6f681e116881602515b5bc9ee095f61112b221f1f910f882682d144b05204838daf1136a2a71f51931188e876596310db07b74e608c383b8cb103dbe7d453634eb4d3ec944449ad5fdc3a15a9ab224e3cfdfe894835efdf938f929316b4d3927e5fa41521125169884688303126a6a24c9ce195f86ab969718de9988ee9988ee998b6fc18d37e7f4cc79d5136ee8ce9988e3ba31263aa44eef2a804fafa40df07cab597b8be45d204227548659813c2742c16644cc1d8988e69088b85b0302726befa7d3116135fa211224ccc89b5a4f4b0727528c1a889eb7a180803734058bf6f433874413d211caa5fb197f6faa1186ed5b11ad1eb8788a82cd33561b875fafaa157a5117d254147200032d2eb838a54d60da05aa5e15f1f44ab34e1d7ea53515f5fcf706bfcdaf25527515b3943c93fd7bc4068970b8c5e3f77515bda85b27c7d0dc3add4d795af3e4adcf89224476a4bbf5afa89baba7f665aaf9f89d496969d7948af9f855496a65db3566952b2acd2e8afbad66f5ef0854eb3e780830e5ee74a5f7f06b74c0fa281c4afbbeb44d2a809eec12cd6616181a2b5b6250971aef981363a2c208b8b44b96a8d217775c96bc9141711848148986ea626a81f5418a21a88d2dabe81b0b8abef416f4d35d1ccc485e986436eb441e2166d1532d54dd81369e58b09585cb8e5a125d22c812a2aa08d0eeab44fb5d260528756429e7add1bf73d337b9c4bfcc12bd1a7477c227691a4ae44b3184c489a445de5741554b35684480c86835729291494a5488e22596eb8457d2412c94b6dd3ee1fa9834201a1040e5e81682c2e178bfd5ac0fe917bf4f510b981571f591a0b71d0578d096144d4d57dd026ffa77a588a3224ae476b0c8a57a3da4cc6cb531cd2bf245c8977c882324ef35e2edb05b40971bf474d706e97a01e2e042271438501ea49a1e4a3b506892ce1e0127485253884a83c653a0805726f843821ce0f3f595c28f4b2f287368f7dffa08dfdb74f823a72cf9814b9b8bce9b24cb76b9a9a54f3be687603042a0b05256fdb16c2be812e500784812978035da00c447349a1d7546142e8b1882cb99adc84a428999ec660214e885ec1c320ca3d7282a7489dd48752a09e10b74baebd761abf02ece35c7237daa66ddaa66db987bbe9f8ed65e9ac9f5c96e65cdccd6579383f3c232e2be7b2469bd36b722030dd28d84319077ac16bf7ba0f67a14cc6f3df7cea92bb15bd2f5d684f7ba10deaad41adf73662bfae89a26a46801ee881db886b5e12dc1b4575540d09aa078bcbd233ad7f03d940f6ab33a14c2fff1d957a1c28a72922e02b9fe2839fa5d8bd3704a14c25840ab5714d5c2b03d05a841deb7804dc7edec17534e01a279730bbc64f545a6952422a21d187ae6a8a8e3f8fa4e03b4219064d53ebe87ad638350695c184a8ac5ad1fb3826db4d70af949981cc0a796ecf074c735083bcd411c1b01cc1af5d87f46dd007daa0947204953aa32f0d214b3b6429c83549de08d536a55495826eab31f84bb1d90e5838bbac133afe90e7b6ca50a69a6d5435d359ab985c567773a27a7259dd4f94979bcb1a73b64106ca84d39c266db824dfe886ed3467bd63d4aa0ba881f6f202e4b2c65cc271cd991772c860013fb5cd9471043f55332e034efad19fa19edba81c1150e280287e0f35f6b9cf0ea4724400e94d6f2a39307ad2d79ad15b70c7080162c96f25ef19913eddfec7711dff715dd9cece10fee3ea3a6b43da26c313e160118a5ba357cd78ba15cd5efc8a66b46e5fdb6c221cbcb22f964468b72fa264276ad66740a578a5843321f5d53afff8403e512871a3f3dde865dd570cba679d4f3ec3adfadd7b2f3704c5a3b98c5548543da9ac44edcc88d496b6d54e08b595ebea7e938efffb4c54d7c58e043f51294a268255f05335d361560ae848f55ceaf0c07d59a201465ff1ca82363252ae9fe33c7963f00007187dd5585badb5bea894f302de35cdaf200574985d87d9f39792a2e2a936bc29f7c56b2d9943bea08e1ebacee8732932a29ae1d6a8a4a9a8d83d3ac277a0521cda409b92c93531560da142553ab514a54a553bb7c57317fd7ef6a934f9ef97622693fffd19dc1a7de923febda75f824adf86037e6a5bf97359311d7f19e5b654466efa25fd0edc1a952a9ebbc23fea0197a1ce3a5922b50daff0e8491f136b390fd4ce5d0d21519acc252a3dcdced2d6f1d5d7ec2a1ed54c45134936ce3f93898504da7839ae0ce1ab7c8e7b411d292f7afe211d5c46daddbbb9a51176b08230bcc59787a9dfab9a5dd334034e7ea2f0891007e10e422a62a1527a592974fca59ddb527dd7aa662223d7b4b670441277bdaceea5c64368a5114129f8a99a699c7eaa66232bf8667259a3000670cdfddab659a529755a57d56898c1ebdf688bb0a834a025ba1559166b6b12e2d6b1e75846482ac8721a4fe388fa50bbe216575331c6da36c3aa99adc53369349ec99a0b6a65954e520839eb24ade48252ec96189f3a765fb014a9b2037e6e14955ed618425f74909798e15369ee9b5aa14cb57b29f6eaa7a983d92bcf90b66fc9ed16971d32c2eddf7b2f396697ffc81da282db1fe5af55f06c5fe47875f3d7ba7dcd411b6e5ff60b68c35f472e3df04d5aa5d13544440da2fac8200c1d4eef4e97d3d4b9f6d133c903dfb44d33b966680a6e9feb9b595e548a4a513bd7ecb744a545386e898f3d5eaa78ca8dde8bdab92db154d1ee9091aa87a7f318f0d193fe0cf5747bc3add2e3d73613598ae156a9c4abcb5ac9e155db56a0706f446a195e95231e8036aa598dc1ffad7cae89574f463c8851fa5cb37c724dfc556076868ee077bf2565c8006df67fa609daec5f7d279ca09aa9662a9e14686c1919d026e5bfafd7078de020a757235244e92af46ae40647e837f60dd026456f4ed5a3aaa9681f685b500717e98fd3bad39dd65f17fabeaffbbe50a74272cd1ed58cfcc1adde20d0f3f406819eca6f10e889b8ca732a292c1668f3d50fb42ba803b4575801b4f9be9eba0563b860141cf43a4e8340afe374e8bd8ed3a2170fad435fab05da74ffa96c0bea08bd8c23785a0011ec9af64541c81f4859a5d9d9b4c39602c2d0f55dea1db8a57dd495be0fdef7ae7e1079baf4b3f69c9e908eff23cb53a7215087d973e9e3dce8066d442c8881037c72250f9d76efbd0de6e774373ab242080a6e9ff4e28f30a47aeea3d75f3146a5189562de6f05f3986732da866d24148d340af7e52e879aa16aa8192a45a5a81d940cc583a2a1765043503214cf3569a834f42f02ef8378e81bb150c38763d7cb32778c6afcdc6828c39ec7b203d5f7fb432a20f855a80a071dcf2a4da8ab665515456d953eb5f4f1409b4b727510f892049f8e4548b8066abdfb941ff75eee7236aa9a8d2355b56b62d50c54cdb08a87a642a2ea0149158fcac835552a24aa1e151904cf3c745afa943f9be4e4984b9f7b962cecd10e38efc1ad9186d18f08288df74b6ffad2d79a51c97b461a483ffa51b9c388801107c65beab0fce8c51f7dad2195f59627b775fb2289329128161295de1b585413f574fb2212b951728cc192a30cd6d56fb95ffd763e053f37bad1fd42a59e673295626f9aa598698f74c06fe5be5e96a8d7cbe2444644b56e4548ba35c24296d27b03bfa9143bc0ad29c5bce0229828e7b2f487b26dc46da950bcc2286e893948c7391dbfb6917e54a2642002465fd2b65369482767240f8a5ba5d85de1c7fcaad052cc00ba462c512313aeffdc68ed1155c14f6dbb8ff3630edde86ff46e58948ef1e7f413350445c99654166a89caa4b2503ba8740b3edeebe1a89f5b8021d567e46ef6faa71fb86ae6ca656f895f4725256a4b2cf70baff093780dfaa82c3dd1f19f409b3da282f78460338fd6edd7baed09c0ae3d1d2fe0a2d7c4badc418ac066d2cc1a6e914a31266acf328eeb24d2a4795f1f19e1e7463789346bb1507659b59af9f1874ce452ac635d9a341df0ee4fd56ce79a18bd2c1c027e6e74a33a5afa0c9f233c1ec71e365dcbb5f03caf6302c694a93497ab71dc0c34b87e2cc0c90e9f1f7b93f54bc60095a6fb6a2a42c4fc1ab5253303af9d95480f4ff4fb33d46ab5cc955f63c8ce00cf93a934b7ef21386c7830d8e49735f3525bb57e5dd91b44d70194c1ebb93c67d8d906f66e3c0414c6e4384e731a87b5dcefca915e35ab79f2e899ef9ebd9c778edd78e0636faccbc26a58dc169e05b1436c0cdb59c73aa3b6171023befda1afbf49af9a3837ff67646404b063065b7158b7bcfde850830d3eda04215b8846ab6111a29da19e90911092217a0e3697668d095dc1cfd02c340bf1d0e0900ddbccd98c4c97a92b3b53571677669fe93860c0864ccf250e3b7cc76102cff9effc4408074ac8e5c3049bd8c42636abcc016a8d8957a217e880e62b3c2f7b9ef674e6b6cdf64195c6d6017750c8021d2883a540bfb900b9e78a6fc529dee7b85e2e3dd005fa3561604c10888b2dd136ae2d8c0565e07e80755dde7b73ce45f09591b151030f1ca49891995a0f1352ecb24e8f945ed6499291829076424afa190aa29f3241f45acad8e121b4cf481e7e92601d3f0946ca412b8db74933ac84285ab2ce99cb9c2ecdef60ea0c466b6cc2983041c0981c6352a41fcf342ead0472c96dc793b0aa3497cb1ca7399d5729a480313ec1b24e6be678408de14e605655a430a6a9bb75dd0d3c5e393ad89b09581c8b763a83adfda78d0ed34ad12a5f55c805dc15be012c65c614f0b3d6301640bf8ff1cdab2bfb20b2d270432e390668d8618619408f9f861790697333ce49928545545b2bccb0a080c3b81d0bb038406c983da9e1c7ba74e672982f1899c171cb1c7885abc993001e7ed69ed939b38004c8e059809bc17171e49c6770cce0807120f0b8cc719ad3d9f3383799fe4d5366664600df13ee651e803c2e739ce6740679e58b4ed76d5cc54bcd3ae7ac5fd0a11170dd5d9cb5ce9a8a6c49dcafb5176fdb83cd15a7d8b2d6989c71ffc173a609b3c3061b3f3c3295566b150b0c5c966b077bf32bac50a3c66a55710c7585cd910baec365ddcf4240614c8ee334a7bdbaf23a0e18e8f7deee9ff3dfb88adcddaee33aee5a30062fdb6c54a5b93a6bad73e6405f1f5569b4364d180d734d1d50f9c25d41e159862baa316104ba7d8cf30a8dfb231eddc680b7691feb8b2d4ee13e97a7084602dd9af65a54a5b999db027dc93ae7ac37fe1c3e5cff69da6ada37c11872016c0cd9e5b29546dea1dfc79fb5abeb32075d63ecdf93877eb3056ed7250ed8be59a24a0faf701d650d04af86e338cd699006afc62b6b5dd5d8dba1bc8d3af1d59717ae69abc1061eb52837589705d2f1737a478c868ac567ad57f10aff4e013f2b0de352a77bfc03284d5c411b2fe912ec1334ac0c2a0f4fadd7af2b9f05475570ef3baf1c6148f5ae5d35a6d3ef7d9a04bdd779dd5790f79a4c758d5ed6a97152bd2b358a6df7dd7ddcbd462fcb2b61c8dfd9aeb49f039f79894c02418b7cf7eb4b1dd57f8d7d28b30f3e99fc19d723befa32fa00c73807e0ab2fa31b9e5f675df3d59aef2a7fad01ffea03d7b906c2d716e107ee95078b9f6eba6612be9ab30e18c05765f76a735005b75f5d35c6e21eb0a56164c5c88a9115232b46568cac185931b26264c5c80a11091109110911091109521252925113396c1a638c31c61ac735f5abf4cf8240a215b4c7438fbadcec4818cb711cc75d7e4d8d390e5f72cfaec9734d8d31d67a764dad675acfd47214df37e2814d7acdbad36b7cf66bb514f8eacb5923eddc635cebe79c73ce39679c31ce18e33a42f5af0cc54a8f77d5cb99047b0a4cdab93f374c906baac02451571c4c2cc5879f293485a6b84fbd702bf4dca7b84fe5dcd6f7dca7825c562ac835c954ce4dbdc8547acd9d6b72dfd2fb77537abd2c9c52b9650a9631f75fe5ca9fd2e79a5df0d2a77cc2cd80e5f7646672649f54e7f259794c1cb76adea4c6d1285ee50fe5239d8b752b3e4047527a3a17c3ad15db5d71cff1155be742b1cefdd7fde0a37326262bb61f3af71d49ce6a0c974299a4d06b721fa3053ca350ab31dcc748829744c8f4943ea0f294494244e738999e1ac3e53f67c03a37035669469dfb19b14a933bf733b89ff1e4b246d0572628d42a4da9730fba3030b2ce717fff848975ee0a725669bce7729aea35d29424be2fe5073f5f7a4a0878ad2b7b71d65ce78142df065354b6ada2501422a179a66b86a0b5365cf11cb062bae608cc398f484fbc28249124de6ebe2ec49258e264209392a964d24ea4f4c0c46262b94511d330ef9b82e5c472ba15b9c1bcd3781af1cd93dbedd6733bc297cc60ef3da2b47879266b5251515149b58cc26871697151d9e245c97f48465ad4b4ccf42e1f03c160f2430b24d974cd959595952771f20809b9492412892cb7787d45ca5d2a954aa5cadb4db0b0b0b0a85ebe188ce338bec488651b0e2163868c195bacd4669ecc9bb15d5c5c5c669833cf5c99ab25643056db7271c256a954aa13504061c7881123060a4e6c19a4c5a41093421164450822bbc55c5e0a3452a011d3020c0f5482860c0d990c44a6864c8deec56b00a006006e68a783444e0107800a00f854c84060fca6820d1554d0af2637dc13d33569d0a041c3860d2dbc1bbb468d1a356edcb83df1401efb0969cdb45a20da1612eaccac3033d32b4f135a5f81668515f692cd8f785acc9a40c1028586051a4bd6afa0ce35c6898f1314989ce0784d7e3e235ccf0b1670b0c0821638b8571e2d6e32901f29c1d1020e1c4560f2f5cad3040631ee49365914dc5e672248010eb98516401b5d636a4c1190f473f338e9c16c0b262123324e8638d9393580579e2d66ddeb95678b4a5369aa29009b0516586021000110957061b7d0420b2db8e002c90439b60b2eb8007201e7f01cc02bcf1619543dd3352b8d14626050386165ce90492980ebab2f678c4fc7d703c17defd59ae346dcf6401ef89ed51de777779cbef9e65247cc99bbdcbd57670fcc2497d18d2a6087d5598336609ed5a8a40c7638412ad5cc08000000b315000020140a06448201899ea58198dc0314000d74925074569849e44990e3288a428818400c21ca18024000684668c641004d45f237f4d674b779a477583b0691b499f681152a71519ce4f8e4b85b2e6a4743248b01adf5638698b39b6626e50aaf0e092c6c970dbd63109436d4f959881282f0ee6105a5b1bcbe41da753aaafd201e64195d2ccd483ea98529e25ca32edf87cb427480bd8efe55008a6f80ed8851c314a38aaec8b647d7076eef45dfc2f12aa59fb169373fe820fa612999167658e3a63b8e9c1303e6e7b7eec16443811b4378431c3494123f1320a87195dde569f2896bd3b04af380953731aeb41c6e63ffe6941fde280131581244f01722474a052d0bfcc4b22d5e8b0f7fec8a33f0db84c8a83646b4aa37da8a5cd3103e724f96512d00c45e20601d1b6aebfd5546e99987b2d88705a39d967d48bd8b5ff24dc5c0ef2ee322f680d9a64cf88f9c8c80e10aff397924b661f8711f1478d9f7173a9b3d0b78102c47ed2f6d202d54f9941ad4eec06960ea6f002be2f51cb14554be43dafc902e55cb6b2853707b9206d680016214aec191fa77c41f14db707afe17882ce89a09f3288c991090a6926099243ec50786aca515fb26518b2320e5228114c575b28ef9267f5862b92ba9a0c472a384cf0d84c96ee256a076b45f2f338691d04371264153e39209fb880c36a340a9ecdb831f702d65532aa655ba91e3212762478738b29e767b6dde970b084d6cf46b87945b8a171f687436a0fdcc6e4073b75a734870d974eb72d9005438e6a424a25f0fda0292f8782056499284791a78392b4c5561db6ef489360b94ceb29820d2bf87c4a2d2c557c71c810b23ee1fc9b37085eab5787330a40c71a126efba5492cc55ebe58918b0ea2535d50399a273582939f353408f258058378038933104051504b2002ba6d6411c562fe7e45130b9bed7c09611a1f1e0e81980d577ed6ef49d0c3431be5d382e7581df5c000c5c083dd74bb113de3a7eb62c0ee14e9e7d82517be26a3442d9049ad6b827a4b9a753554d25ee647743b02f914b3da1443adef98274ef47ec2d4970fb5e8fe2f616697b3fd2cc5e64740383683e593bdcb3976cfd259adb9509dfb4196f64d137b26d06ce80ce801ef20c1a43d0201c8d3103baa6dc50d2c71ab742a1c29d4f5f2203dc6b2f202a1c9a8c801077762b3a397f569910a94ea644fc260ad825c0ddc1eaded84d6205cbf645c7251c46b613fd132f1739b8f9d48ef70461aa03fd3e59d5a806b5d648ecebe997f428a0fc1c1fcecb636b0c05449140575963dd1d244f200555c0165cb69acb94dffcadb7cb6071699eeedb73a521a7c65a641f7836875d3a2ba9f43ecab5d053e690d1c3cdb2e5394895184b02d0a0f8e98416dfe6160ca1f462b4a102b44f5a648b3468ed860f2b982e4df3b79da81ca450c2368105d4aaa896c2994f06c6ac67e23c3094af15154341600d502736403da23bbae80e822880c1f626f193391651dcc5ff41c79a4fb94e1981166b7075ccbae73512ff558cc57d028d24945e76497fba0651d9225c1385c62e6033dad6ae7dc995d57978daa4e9091498811accb4fce4d8a3596783eca7ef52e1daf0133e212277d49dedbee304f65878229b011ad5e3afab3f4562c38049d353e84713f7dfc1ba0f882de3812c76036a24fa7e31116f9949b008c328baea9e8fba20c57658210ad66d53c03e8032044bfcf114bc321d248fa19916bca2ef6bc85f10baae0871e132753422d5d1cb109a2f2bb99a991c403d3ecc5892b9cad4852ccd324744612363fe3d1f44594befa9539a6a1ff3b888dcd65a6d3a2bdf5c6c57cb4fb64f1ef407635e398db879d23a34a7e0e4107ac43fb639a8f60b5303bfe58c1808931f3075c0985e23d51edc4e477ecfae08ab6ca591139c0be92fa2d54ed669a820b0700bd1db345b0fda74e47747541261ba62c4d84b519f18717c76051b10511a2be2d7e81b977f116cb9c78163a82df4c76f3a8cb8de50491baed2a6d540c22888424619987ffc51141c6cd423ee0eeaf60cead497d794a6f024998ff0cf8a4668205d8c134e305dde30a9e7c4c3e2931b3da4c1200ae2d0c15e137852e64393f927f5c8f62854822529c9630948f135a3043ac89dff58711a4686017abd872b453a36413ce23b3791730523d3f485a145f9a18ef1503df17b24f0c9e4d90af09df35dede149e0bc07df610cc378fc2efe23deb18f69bb77e0f03ec2b83c5779c2674185b95d138f296a7b7c0c2feef33e170449885773414c1a2c82d938177be1a324b94b46f73a2e99ae15fe20189ffc7ff8a7b63b7a42f94eb4248e44e6f4bc90184be45b992107fc61428848120a9cc8a80595e67c15eade9b1f36d0d8f72eeb4f00a9704114b346c5b030f90701bc519d1334f09ce48b0f411d22aae237a31038b8943ea8b6f656c86c4f765856e350c0e75b7345066a9f25cbbada174478a60b1b5469120a992fb6bc1d0670e7d3bf84c480ab7ce0e95080fa07f7db966230a512d87927d09e1ed29c39aa5985e1101eacf7dfa095d4a6dd126b1cc899e63c5f348ec5b2436ccbc0c009d3f9e0922d57342436ca2756e3be64cf8705e4ba4825f3ab2164d69b191988650e4bb078502fddb6be526eff1df04e356cb70799e9a9e32ea662257e9954ff92a183b689bfc9a3f440338d45049ef47da4e7451318abc01c48a16f0313a26042a4fc52f87b60f3ab57864cf0be7ec4bee0e3edbe425e7c9a2f2e1e7ae92dea4202575287df96bff5e854faea9c59ef0b16bf103e82f98593c65ce38d8e1de1574218f0d54bdd6b90cd82b55c9d2462f84bc2553717815d9aa27342f79c07a0e39518013bd24c4ee6d0f2988d2f7b7800f9e79cddf04bd85d7050a0ce599ca76c41898967f12347c3971180cc38ee69037636e6571e6d38ce1018e0a887019adcb69c75959b5942e8085a6fcc2b6e4c4f24747596309fab55ddf270aba4b705102ea3ce02ae3bff4bf1ba9421ebe75f91d1e617823adde607613be14531a2f7950fb19579e6c1d02431850ef4800b53916c6353b456c604f652a2bd8a61675559821e1ba54c223ebb80bb3a2cfd74f000c286b2462a73563506fa6561c0c3fca19503039a5b21d46a14748f2b4693f3258a0023b7da4845e314416b26a5ce6c9352aa3aeef62eff1f32905c564ea943f2005e9e5d21554a5c3f275cdea10e5494653dcd590c411673ed0ec04f99cce31dcaa38e1170f0a4b46f75302573c795447bd53de29a37a6e8231b6b64cc9f455e0fec5ea05db01fb5039db09286d35c2b425acd06a78fd2536ad421fb9ca79b1623604d208ea5408e766448b7ab4c650d15457adc54ff811436a01e44313da42bd72216897bbc0ace300c0286382f536816b3a41d839aa84c0565bfcb50e298ed88c5656359b2e3fbba2b447618558bf678de86620c7a89016758210c34e89c11034965004bb544ee911d40ce71d61dd82e5f42046e48ee8c18271ebd11b05ab4a563dc45230a9afc8d4f8c8c68c634d999b26397fe95db1eff5e3de9ccb656a85b2d7410324d66361dd6c40a933ba5ce40ac30dad4447ba1d573b6a7d7af4223695624d732cc7aacc4493487fce4511143cf8ae56cab2bc3342eaec230c3aefa4f1739e9045d9afe891c8c428076d6ed6a198e2d05f705128dcc602579e602c7f4646364e9fdf2dda4d82b9b2797e09e164bbbbd296f1b4626e4bd3b5dd48163ca309a908e0c703638bc241f9f3a32dcb61a5452696b0bb6858fc85290268bc6d7888ad71cfc17c2ba8f58f3db2e15c9db28927742f95f24e7485018b2dabc18f0a252d60a6df770711933cb9b319a216bc8d02dc68aed526f22e928032408b75ce4594df6acc4f59b0532a92a1507ed568dc9a82a069586cecdd950d194699d62a64e2d4b2fbadc5644a2b7901e8ac57a8207c1fbb6e10cc1c2a038978b7f5390fd2d66af54a136f290e605a92c0394b94bb6458dabe88d574e7f47ce01fa0936b62dfa600f84fcb6b79bcd5362252c610a18868a8900f846711054115e6780079bbb78a7e59ac70103798c3f8cdc58ab2623822a5a89997afd130eb3297aec3e02107cff6aa0defc74889524418d3cc1c4f965db33b16c616f0877a20feb8821b1c2e717be7fc74cd3bd166cbda09640297ea224b4efa24670b319135f4c264779000c958bf2df634fbb264810b843db0b0f5d993b8d45aee47c2e9ad34b17a59adb8320c664d3602e2a082993b3d84221e609fff0950610d6459b2a5d0656ee14bde3a58498728ce2dc20a1d34d75bd75b96737b9776300656fbe95d66112bab9697c490417a4c3234242a7d452802def9f0fd664447094aaa935f79e89b2791bf10ed6402baf146e968725e16b585833ff3ee7f92f68f482d53686f7acdc1e405187e00d12f21c19c91ee542084620e82a17c7d41ac31c0060159ab9ae4b577022b4f30227f36658a12c7b5fc779a38f30de425bbb26877446e1deae07b753610f57fe58eaa5e2211ccfad7770fc9d67e35a3f4777ce0f7a6e51570fda94b19d23cf5954016d0bbc05d801b44a017a2f228e6d9c047a2e51c7377626e8142a2fc638f51f34c781e969420c2257153b1570ed80b63824b09ec4d279102858bd56f3ffd111706a9b91fa2b0b07f02f0e37586112fbe47d35c8e68651b7be526a8164742b467d11def594c3b1ebad65e38b55025db90894f0d7e4ef1570910228c8772ca0a8ed5ed9b4a29ca28510d78fcf1751445f6b6ad9e8ba1c0ede7db1c7145c890dd2c420e66917ada17fa953856c308bba1b732fe8813da0d4716f9e1bae3aae8d158d95137212869af7f324e94a8ea93d69355a2663e8310fab0caa9dcda1c77731cee80c8b1235477de491beb850e49de4ab06208fadf7b0f8ccbe8187c63c61fc48b29194029e6c7ea972b397642ae88897314a1dfba4a1e901321d3689ece08960d870502683c32011c215cee909e263cc2125611a07ed50aa9d357e1446c120e895263c461ebe28d11870fe3f156b30300c299d7ea214719735222be949b8c233af2d14fb3340b244cea806e9c5beba133df42ca7ef587335b2005771b85026628bd499262c1abfb482f59f5005ef5bea154cf98160051b4566f2069be72684d9c9dedab219dabb704e4e69d47e4c308e2fbcaf48651656adebd3b447998ba624c01fa4349b6ca141b14440020f399d6f83d474485e96c18be6cac725374a534539d8bcb6f68d95df306ae9ab39d38b4990b91bc0d7f6c3dcb828421921e2218a15756cee8233803e5ede3c571894842ce979f3424e3d5bfc33dae7617e4e40302d24b3632fabb84dff62d4532e533c87873009e87d44ec75fafb2c4a76c2a280a75553f11bbdb42742a253302572b4c31ba65f3511474e280d63f7bc08d599f6e713f771e790967488c1998356d8bc9095b980e092d8aae020e2ab147e7f57163895561cecdf1506098e5402629a2c089fdf950af6c3c96d0f27050de279f67b3b4acc63798d493b485c321e4909c6215c263260598719dce18d2e766dbb942e334cf212e0049c5dd970fc487947a638a7bf39c055f4340cc9e8b603e841e9961682944ba26d2118df315970544ff3e277602d383d30a0a4f303c009c9877fd3a27139656b5258d7bf1e31a9656686110b002d44a0ddcc75400300b15d7397cc541029ed2004471e184dc66f01628eb8a24c3b909601e781549b3109f443773e6d4f9844491488ab110b82855c9e450200719efa05ffada5c47be8b32c5cdac1980ffbe1fecd811ebd4d69e2c8d8060da5cb8649a45e634b176f97ed1081e1673996231d87004b991c8362ffb6a7db95da8dc7b626e3acdec8222d92625b141ce2b0eba7d8bbf232a3e598fd29903f57ff57f8c93f2dfc88a1da5d8f81250b58b28e05cb58638d359658606d5845c42cb063083ca429fb8d4ffa1ec77215721a167a85871e81314c5af30387481580eade3efd08bc18d01ff355e909c07f23ac0ee962e13ac86348a7918cdfe2150b70046f244bbbc32a1c0c4ce7906bcc5986a2c1384bfc980cd442881426c0fb7f87141085c0636be2318263a2bcfbb10578fb03ea369cb19c9ad379fd014ab39ce3c47a8a3855f3d970cf5a82d07e95aceb5a8e85185c595f6da70aecc10b738797edd39d867d483e665ec5fa2a71eefea558f429f5f6ff5372219a72bd2953af7add9386e9ee7536bca752e16b95dd7fdb8e0a0133ded54b47c965c88e4c18204d875a13116bc70185bdafdcf2b3b9498d92c4f8b24f19ae980fd655a033c0e8bdb4c7c6f14472b806e84c5bd6a003ddb95943d0670b17b07adafcfd8203a43d2dcc77eef4652259a4acd7ae0904958cd6439ac90ce0a3cd36d286bdd1cd4f238ce020ff32f0175458e884c30402f60f80aa9910fcb2796cda51a1ff18995141eab871cfb12b86110090ffabb7d853026fad827888dc9e1b6f5e45bf07f88f3b1a6130607c7d60a991c35990c53c4e16ab74ba8751c08bfc355edca9178131e8eb6c1f1b76d45cd61d08904929b8807c9e5d353df92130fd7d2514da5b7970511d1be8b60d098aa10a0b33d804d3b27e6fdc52959e26f4cdbd62f43342b304448a1518e398e677a0511d408d59c1220145aa30a6083eef48ad8a04584b63cf4bae50f03f529f818d9695ee0f3945f6d52c27a5fce2bc8eabc78277e2597707e37ddb4e8c6fbd26673a72c85ca6217190cfbf924c807a7e3f270bdccc41420116eb67d348c85d9965a2a1c8fce6ebda96dcabe48eb630c1d941265f77100489ea8d415323bf78a1a923bba243c965fdda907ee1fbe978742ca380854d0d4c96e0be6ab9bb2b65f748524d26d8bdb7ea98f860ef9ef1d608233898af7004c164ba43865db47d6fdab31621bb48204649841050b34400d9de154d8ca83e023896fd7fa2f0a0c433f9acdf6280cc72cab8106e93b87a14ce1ab744260f90190db2106731855614701ea3c7168342fd0638936d70cfb8f67e72f67a091b8da47143df929e08a2260a3ba4bc9c4a1cc1cbdc498d69de77254909cce41219841379e78f7e2d30cbc21744d0cf7d257e5ebc95988c88fdc539c5d1ac24f16dfc477a2726344793340252e05dc8045c884a0f70c0041a4dd1921c00324384dc6c1f539203c36a6b2a0bd595239fb791c94807401b89646c7547c2d2ba36e99a1d17a4e0a0455141e77b041991471f4266d6abe8b7fb6dbbbb866165469bd56bd3ae358ec71273d3bab8113fa2627151d28b85bf5b484b98cc3fae651646345525a9c3a165ca63bb4351cb3e2750320dbedee971b6a13ace50f9c816bdac2c42d87a70a1fdbe8ba2fb37eb088936d0e01310fd352ebb7c17f10372e196b8d5c4268fa52d8818b4923b72a9f88b627a6fa4240031c93fdc314a4d6e2e4d38e0a518773fdaa0d4a411e9c0ed4ca97a0c1de009d9fca920085aa2811a3c606b7a47d9e1043776fb139d7ca08b20939d26706c8abac48911beb4ee136be89e98fc1646cfab8c815399ad5371b6910861b72bbf3d95f7ab24e47d885c1396234c53c55ba083a81a997054da4484929b62f32f12a9a5bc1b786d380e2d4bb4e1140eed9c4559411959ee6a3ce724c6776eddbd46154ad41036886e8ad19f503b85a69cf23f080016f93356f88b741e82034a21a798427fd8a87dd8d36f8d100eaa438d2ab05015c43b2e4ea015d78a4d5c48a59c3265c819a97b1923d74eff97322b1bf14c6b05c14a08f31f134567577fa6c88cb53a3c433f5ccf5d5aa703e10513fb44353640c37bdde9fa79d1a8020c811da411de2df16a3dcdd3736b3ff1f9ba5e4961b248bf71400bf9a91f187d6ea24f72604b2369afeb05b3af13adb02705e7afcdfa6fee38a102f5f5a2266ecaa4b9d80317ed1bb95ae8f47b539ea3a54144fc815bc68d49ea232641dc7650c24a2f93508bc48e9ccc5750d24a307ad694bcbd8992e5e4de97cc4eeef6ad5d020e051aab1f4cf268507ece609aa1035cb58a71c823193ed9010a1c86b6168f6c06f173d4d34fd9ca1f8608808b394578cdc3a256557c580bd7fcaf40c41b84c5a211f047e2c09110e6158af96b273acc6674656417dbbde1f4c79ef68e38a2ae7b184789b33f04da185b2802f1324b0bbe3eb16ee2c8d213953545d157c2773ffa75712cc16d0c9185002c14781f23609c8d2a27955c1e6536a7b1bf84054bd4a21d1c1d4127e109c81408add52892f5c7d57e6c9773564d30026202433fd762279c3e6c7ce47a618fe1cb47385300b65a1a75ab0963aa07c8ccc681c9f614efd2bf0858bdaee7858bfed316cc736b713f87b7f7008ab04850ac3b7275a3f79189abd4f1b9ba2a9d238e179f870ef03240ff893760b04d18fa4a420748e32046ec71a03bc4239c0b0c966c51d664e7834dcdce539014f7d0741e41c2d3e73bd69ce2132e01c0a949d6e1157bc0f5128013cbec60e1b6d9d171bdfca5a732b6918fb04768be422a83056847379cdf67e79c803b7f280f556709ff34ab5f4c7067a9771712523a8b1a14338ce1c00fd58f4ee25e568aa16ca9ccf58ad0944c53769c7c73a3aee6c0a1c29b7b303d8e652f5edbea40bb0ec74cf21751d73e66176ab959377983499d671a61107b6aec173be87e9a670d58e6db86b61d9f2841518748ce7e254b3019a4fae53b4eef04d9270cf127daad677482ac71a6cf974d9bd9497fe202dd7ed8480f93323b1812f942116171e80b76baa16bf4d009e1e1b8d6d8b7f47f98def075a3a24b476dbd01725b2c1a27420aad62612e2004266224fa73714acb038b1290f77724a14a0ca2c2a11c5ef556a413a484086ff5cb398d081e2e35224f0fc7b277d6a2e144ecd66b61031ae6d34f2bb63a0967ab89e89c590f36505b8d9873e1bd3066a869a5fb3016f0128f5385b238f019deda700d00268a161b38a021c8f7e33326566d485306128475c0cd40458e650807f01ad57615629bc7102139c302ee12e666981139cc646d37e363ff326b6eb4f22e5467ab1cf6af522aab6d5c0820bee3f33854d2d9ca47499b14bbc3aba5d61683e91281f585119ca0d120f082adec3bb625084b5eae7ea77c30973635043735a3871b04cba42951c44250b2d7d997f7efbc0127ac35a24439216de76d4cbdc09ea7cec9bceed29996c71e834f26eefb9c71e163e7853a3b1eb0bdf3f900f6f3c67f4e6e3561c6b4a95f32b7484f5ee02f0c4009bf53b132a599d0c59edad9d58c9978f37967cc3efda69846ae86f6beb459a5e55b8fcf262a86205243e10a838eecfb7d267c67c21964d640eadc0ed7fcff69dee506b0712dfc49f1b2228ca8da3d47b270e159c03fcd395bbfa6a0c98395825241f79fd53b5409e55866ac886ef5ebad40a9f56255d7093782ae92a7282ccb0742b911b9b0281c69a8b7c1ea1ea1c8437f1599c64992e42598911a7c7a2c45199cb8f065bc6de856ad963235172f4ea06c0a396a953a9ccab31fa07b8bb66fb8c9167f5818b41a2fe2eb2c66bf25e0d9f1d88670b713e7eb890c19034b3832edd9331fbd31ab26d46c04a43912516e3857785ffc053d1642109282c98d273e0e0cd2fdf0d7bcfb14b107fd2a2e23f521564a183e0904458a90efb9496f29537447ad51e677ae9a6bfe415e0046d8936da33477383530e8869d27974c560b70137ab304d51d8540e37ad0d54e9b66e21bdd5d054d267b12b56448883b63c4314248c945f86c4e98f2388f3740be1c33160deea59c59783bd01b7604965ea85a89ff121a1d0864a51c300ae7d7904abed21662d6dc0e82f1fb73b63fe1ad752052b9f356254ca360a0df11211617527d645a5c2982725dfda7e37fafd609c39488aeb100f292c5a91421dab3af5249c4cad932b831218cd999390f418df42e42fe13581816a9c87fabfd8db4d1f5b40c27c0d190fa67562557d43a6bb7a0006ce61b94b330c376b08b6298daec07c93dfbaa6ce4271011c8169399f905e3481d14a686500aa3d7f7d4905e3d2848bcf7d1b044ae79d8a35670fedf19552f760b29e00d49d17124dac6f898188a12ec4181ce849b27e3fe6c7c2a1991a902f46c294886d4ded80e1843d59ac312d1296cbc40b0c1fd2736547f2ee224d8ce40452d13f3bed42e25f845680c32413e6543039066b00dc5d044cec66588282f33394dde2342f086c653353d72d3a346476cba0a534681937c713f33c847e449efb60f0dd3b564187c223e8b1d59f04c514177485c11a62f1849488081010ea332312ca7813b60346f98e9fc53e90f89d4a60a605cc318969c930ddcedad297edb2ed2fc1054251bddf40e75aa6aced7110d69367fd2e378e4ff30b8879e8dd06e98cc6684703d5fc300dfd96bf67e0a66dc2a6ea22560b0e595cd5ae117d0d431ab2041eb7ae14d105b2455e0d8a1f3c06b4d96ab90ff84fd6b2e14e68a716564660fc278de1bb53e29026e9420d891a421eb7f21e4d3c86f6859c991ed9db012b297ec9344a7e8f3b0f6a30f53b17f2bf7192737e94ce492e17e5249ddc717e25df06e4e187111677f7d515a39b54bfd163f511c5d525316ce3e8b0a65b7a0f85909a853768d8c57bfb9d81f82114f319d988a68960961064247155414666dd0011290a98c185e09bdc74763995b5a22e9807e2420569b82520382c676798412e819cbf001cb68d96fd0a80ce7be682ceab669134d5687f23499c027323b1de5ff05e6026024d726424d8ba39b7387c6bc0e5a0430f94f6148040ed2e5a387ca8020ab6d1f2cb525ce1f17200b99e9707511b6fb9d93c0ec8686694cd544e9de53701bca65aa9c03e9ebc22853de7c56dc32f1befe1bba01fe83ae7a265100548c4da3cbfd84bdeb6e4f1a1223cd37325612ed4575dd299864841376fc00389fc6dcc625f276d33d531ac0462d8953a5ee9633aca1df73f61be89779f3ea286193aaa2a2f45f8ea320e0300e5106aa2343ed5d5f90125080760e5b261187e100f435250be8c6ad25462dbf58fa3a3782c57066aefd6388695c4bbf88db7e2c5772912037156cca2c7769da005d4d23d8a05cb121aceec87a705a7d87506266a880e2bbe39173d3bb2ebcd0490dc28f39fa4d6e5e147b177af174b0b7e7df31fe941dcfcefc336aa2bd7cbc51f2819cf4e60abf9f850a8cfea60c7e49091c15ff0c2842a7abe8acc150aa12751b957ab3585602813f6bbec11245aad8a47f5897316d3004c6fdfe3ac689d220e8f46d536b6072c33dc56fe3ce6e009b3b64bf08c71d48f191e8559b61fc8e308873f377d6c24a1582fc6c2866b073e51eb6fa02cdfe2959fd98af0a06635264da82ab07db987932f1384b3061f74b67cca7551eb9944a6d1ef8d35cef13763c28dcd7547aeeb78e6c2386e2093de9dfa5c0ca41772dd9b4a011b74d44d463149a8bee34007cf500a9f2bfd1aa040f2a11a999887c34ad9829824f29a8b3dc09112215f7d8777c91d8dada0bd0eff107561f82260d0afaa7e99503cb7ee31c9c6958f145c64d3770c650810d765a20f4fa788b6e00d7d1d364151535b2fae02a6f816fad99f4fe3af15a554c9b1d124daa6f7f3d1a0201deb1303e1866423943c4a5043458474ffa778d7fbf4da2528737bd23c67dcdf6c54a54eecbac8b8dd1304b74d16c2cf9640ad15261d5bae7056b2ea931fd716acf1efe94929c02f4fd2a2ab93040117da256f4d32985a07a31328adefc383267205d85460f3b63f3906f9703b79cf604eaeb6b4cb6a450a58fe25569eaad1fcbeb371749b7e5d7a1b200e38b0dfa769261a040aee5ebdf4d8c9621a4424f146d21fd408dbed4c6b75c698bc568ff0c2cbab2c36894dde47f29fa6c74a632223115a93144dd4566aec330ac421a544e488ed3ceb4ea6d4631f36207d05118ff3c3773d757ba90550c511386cf89f2b2d73b83032c0ce314e1cfddbd743cf325252da681f7d4d695c5309ef56b5109d32b1b2078c5e19e5084821850b824b44da426917a8d0564c04149501afd47a32f049ddee62ea4789e90774ed2c49047c147d805997e0e01fa224b13bf683c369d3ebfce966a5ae53bc2cd27900f39a736734a0506880660b36fbd788e2240efda1116080caa09d88159abd68e7a08362571086c1016bd547843f8feec085961d7d1666ee7a67842a4d10f9e6d259e2de3fe4a97ad8dcdedfacf482e682fb80d26b3a5479844a8f6d3d804ee01fbd3c5ca988b184873b7ee3e1ffde24b34744f721693c6f92776af46010cde4d423fa8714b7b5be8f2cf8c24c06b80c1912cf4b02a78100ad4e6089b064f57521da2a4fd5d36515de5d4d12e0d61cfb8c509eccf5081c38230ee8746e086529689c4c3c5aba6155de42489356592001d21fe9fd80449656f6162e9e9d8d5503bc298a51dce4677f731004ca9c7827f0c7aad57406daa1d3959d1f0ba12b08eb757b909d2de289f6885ea9da8f484e782eec7f177bf0676f4c5b62b693a98738a38db79a8b996dad707c79935f0b69fd6b8392a4ed761ce3c5a9e488da83b4d4193b03474807c05d7b1d40999ecbe968d254f30181a1190d4f47d3e1688c7a15cde2af29b055ca8c018e38c1b46109cdc914006ea85e819a9940f3292e59b094ba70889d86f82a3494a35b0d36946a8fe50a9d19390e05736d20bdf78e03931676bf167661fd334dcdd768c6d3d389eaea29a6c0c9ad7e3b22d814244c9be1da04c76f6b734d5801854c866608d565c27b532fe235324948ba665d7581a63ae4c74edbe239a4c299668634eb6f9b0debcfa1740364d05b8b6dfbbfb3ad59f90164a6c4e25d1079e218be9a8d90f4f975e11a40231ad31f0a84193adf9a586624ede8fde2f82930a034ef9bf78e5302465132301587cc224a1c933ba1fb15e3e06e64d354b80083a5aec0df16677cb308aed3ecb4127c8fbe2bbb75b6d74bc80595c843b44ef0cb97c1fbbe2021c2e0abcbbf6387bc26204f06f0b54b5f366da6894476a326899fb2f054fdb7322ae7acccc9f98519bd0ee26383a26494f72bd4c3a9c29df189fcbf22e970e0ff452e23eb14fd572066fc21d78306a6d7791ff970ff3439012ecb68a8172e4259929826a70aea7f481f67821cace8ac89d24bc674dfcb0eda7bb6719f65255199012b2a0fa261b43e6092aa89df42f50fd8719f80d65f8bf2d44fb9d255c0d675a8bb559a3e00fc239f6dfa1ae13fdd90254aeecd439a47f160c5f3ce4967f909769996f0a2f605642b3ceabbe8d4b5c35b304588cde22d8315e5a6c25513d55fc7badf020e44620c11885955597b75b322b38099ed792c45ad7a800974c83478639ed74817a829a6b3e3cbd93efb3369414890c21a2cfdd138d233c5c3c86819ac4946c183411815c800750012aa8975f21637f80c5ca9820c9031dbef41018ebe665d94989022d5dfc4cbb48cc5304a37b3d0346c2206a6923713a9b5861090ec766004b062763117437282185909c46607920d650369b2c6ea4a60176b84195b3b466b48ac0c0954a27aadfa43141ee41a623fbf7973fa7abc3fd39aeb0da67e375b560ad173a81c549642c38b60feca4f5af5a22d2c0c052d957b26a9114d603ac1d72949f16e22b5a7e50b1fa8f41b5e3335ff344a7ca275fe532bf69c59cc7088259dfc3c70b84447a1820631a44acec47c17475ca1d3e4f66c5040bcf20116ec46d6be440599ff79e595459c025144d068d7e99a99498eca2b3cb0fe1ebbd680441e38b012c9107c2a0a4f0986db80ca43dc29169cbf6ea652067996e0fb18a593e72fd946a31b5a96d6e13cbb289e9b0363e06d2787136a20934898bf9794754014809172f12c3b188849dcf440b2c71b86c74aa9b6cf422be1f4b2c5959e701861d0e0152384f8469cc383c767a27aeabf820251249ac1dee2bd776c4e76fc5c9f52d33e557b2a4d2014d75b0970c737e309cadb5328f2cdec42756402f26ea47e9442074bb7d1e68d1dc0130939bd5553ff3dd19d6a891795fd0ab5b450ec39c3f2e73e6c3598ce56aa5a70046b8f76313ceedae0f41c1138eb5d0b2262e84078465e65299098ad4572406314dd45d2398c0b1320fda86f30b05adebc45607328145457364660a6a9907a12789480a8e0d9904e5faa5f489be67d18493ad1993be93621cebebc13b5f1d1647ee2d276de5521e6a7ec6f51c645558e5d5b30cec18cc57a321c0ea8c4cf64295eac0bd343625cf27e48626ab847c61ef6fed9341490f20a6ebda6db61b0bdc57600b9a0292bdd4a116ecc1bce08c1a4f104a21f072ef00d17606449a2095d2c45a157573acd79ca63d812ef1eee0a1f946bb511cbd042c5bee9de85f765186dde72d07766f78d3d34f050597d1f0fd7bd1266d8212778f48ab5cc12f1db670f083d33d90b989fed56e9c7c90950d5f44b5439c0eae9729be93e96ba3158dc50d9b4165c13778c497defc7303d0168018ab4e5fcab976cf19b94395c3f782181ca596adf6c9224a71cd085102231fc997ad06d3a5a4cea30063baf0d2a62dad5ba7cfe73adced2d2c90155a18924c3b1092eac582421a705f7261d6834cbdd97932f04fa78d390a43053f33e41d56c1dbe387396300b368f428cc59a3e0c8b0ef87f992b537ad644c422e55c24f8dbd6ff740c2e15c655917d74b04bc85c0a83c56c6fb06fe208a445510beb4bdad52a40963b3d590be07a4c9485e4e5844c59ee5bbd09d326fe29dcad0d0e0ed05aee42da68e5092f3ee3ad436950a929bef4539bc8f5bf1610f7ce38d95d2b1a93a2cfff75a330a852922087345bbf615ab986a51688a6406addc0ed5f03c980b63a4afdb9357e08dc8eeb1696747a1148f779669adc202828e278703436bace20337e90717c110d4b61136ecf94c9717cb333ff5ece9ee08c37ac3c50278baccf622d7927ace33704064acc5fc66bc9c5fba9f35bc137306b46a63de0d28dd95e11f2d96956d492c39bad242644418775c16352869ca5f80bc2745694d6242af24c5cb9d038db97b310bca45cc20185d0242496d4c48565ea456203078a4fc0ec64e64a3ec548ab1de996825a0438f2129b59135e5c5186c1f002bdf6fa6d9f1363962552075e0abd7ee4fc5a587a83b4364dff816f0fe143b649a1d7df5605320ced0dc6b3d19347dfe18d7f0199835fece7d9c86cfbfb5f7b4b9c4016821f5d2aa24085c001ae0e8ab2d9f30e61c746ec71cea7027c16b12ee449d26ac6e52f310331d58f0a5ca36f0227f99089db0b30531a33373d4e6c405c26f75770fa29e1d321ba8638c98c5987e4626b8588ad13e5ef8e2e31bde34682c0aa34c2be8b60a15c3cca23db2154e73e5399c80621ba192b8ca80917044b094641cf43ca5f737664ff36f0189ba654eb00a8529418297bf49d361f9434c6131a5298a0fe5db4b67295400a56307315487f2b39f0f33b405c39012668c39ef7f6e9a43fa59cb280e4b866332adf087d14ae9b5773800d170c3eaa01f3d35e32f408aab31b58525d349eb0c0e8f4c97b62cc1d5932412d098e6947b452fff9ded6a1a56a0111ffa4812fff117d6d4ba210af2b773ddd26e721a61153c3de58082c1e00002844b749bb652a3a624bbdf4718a93395b7a6265190a110a8f4b7a8c1dae729e4a35b9587ddaab465b107d2338f780a8f7192e09c5e69289e0450653c24faa6617461fa75fc73fb1558be986fbd0a636128c85953b957bd8d9296300e0eb954ee36e72ed60ee1e43f8e6dd6963cfc8f6e32a616928489138758ed0f9611c0eb98a88e84285176331b4ab0e99989b42be257b1d19f2a4801a1e641cb795f29b421b8e9d081fa56c4bd3efae4b13ab0f1bea7b85a8d425a3ca95663a5ad7a52f37ab0af765e9a5093f55d3e019ffdfff0180f0d2818150f74399b2a2efd7b6432f5a4614646f380d689fddbf60ef914e0b44a6f7d7d528be244536e6b6d17d3bc87e8ae5119a203ed875021b7e7fca0ddeb01a52cd6e4a246907eb598d3232712e2211d77565d393dd469693ae8b7f6a5cf325a7deff5298f38b2c191b47ef5fc6351aef20a742f13dae7e8f7ab937eff480a1381f4d4554cbca35f4614b707c2e3310b5979ec03d0405bec3a295ed7bd8cf016e6946312a74bb253e8b47085470e24061ee27ac9f43b74c44a5303609fcd2b6be03dd0ba2b70752654016958df71bc344af3f31b803d40b7236c4526b5255b86c72a60572ad80a5f6998173c021f0e7333d66216929bcc0c246969ada4955aad321a31304acb11a81455d777aef47c514a0beaf061e24dc2ed94048794ec3c5a9bec9b8d6fe76ad78ae24044fd6fac84acd796ccf710a7abd956aca4cbd7a24a22151e914ecba2162dcb5b8d3442528ce94d4d5c7e1e89b67b1d546be1c74a0fc06a0c04498c8b67505d2aab21342c84a5cfe936224aa71a1bd51ea284257ce1104672ad584a17040e90263ce628c2a5376ce45d18f729576314cabb534494466fa2df5c7210bc707c703c22c78932b3f8340151f88ff9c028b7f67fd904f680d17f55c70868609a758e3fef4d07c93c2fbd112ce0fd5e4cd596ce464f610480d4f73cec00cdeb8fcc5c4ccf204c27118462473de3e85b89505cbf5d08a72c620935cfe7453399f75c45821b99b64d7acf9cb9ca4ffb3fc10fd88032937badde58d4218027658913aab49d79bc65f864f94a0977b0a34943bb36f432053b4825cc65df4a97545efeba569052a495ff2a4d4538d40bc56a9f0704c7b80c67dfa52b94a62cff5bd1d29d8695b21030f1f9fee98d8c4b5b93e1e42bd52630c80ec87f9f40729ba5cee27814e3b6075ff5d654f0b93f3767ee4bf6654ad529735bed904190bfc5e97d3f2652414763024a3cc136e92764a1589dc1ccde22c5975e0e5b20b8b740cf6853b806565d12b9520ffacb859c06e4426d1ded073e95561727c46d960131f4206cd75a2814725afcda1825fa61b144d181ea9f8d43c9de5ea121c8098dac626e061b9eb62be86260e5213dd172b35ac829855b59eb9b5dcf3025a1219f58b24e2035ea7f61d64014ac2536720fe90f9d98f9246ae4703bc97ce21b830df9e5cd0289ec126f4942a710c2c3072b19e29842a7dc60ed08436a6d33580c0f68c4e33e48ece2c354cb2a0134b7d685c8a24bbdf2e4422713b72b537073085a6fa7c7b7d0f872e894a3cf3b60fd61ff0be38d3b58bde1f55f3b7d9964db85c0a3e95c286f6899dbf88920deaf9d0a90093f566f923d19f0b58c815275dcd7a284b8f6f7784414025b548d726d4b3f05a67f9c09dd1c66d24331cbe8d738101287b9272622346f96956073d08c5df87696b16029fd93ece548de6c72b3fe295f429959568967b1f2225937b818cc20e8253ef9b405468cbfd23f25109a37db9663176fd03ffde17bbb80bbd5e2262949a7ecfdd308fab406673c2a52006d3d60e50cb72f4d0e5e46eb517d538f00b11e123edd34d181d4f44fe262734d021511dbc677522b0dea7bbd1b42878c62a3fdd17034b2d03d5a98ac1f68501ce9bcc81b470a8f6ba0abe137fdc5af29aa3f486510027896ab21baea3ac6134a4585d611d5c2b8372c42e697a1939a88eeeb494bf0690c592e90c234625e8ed72194739753792523d795be7e4733a08a7363c8d08ae7bf2d146a47828802e2dc769d9b07dd34d951456d1527cd241b865a2d400c1035c840f3d74259e5c6915c6fdd9fa6c9e6e1ccad9464a36fff0e6a5605a58b3b7660263c94e35b75e78f8af24422bbe3048bb13cf2a1a2a6bbecc4c1a7a8a8941b9c8720dce78deec0ac38556f1d5fab83f3b1a1a242f584255e23ee4c67c9a93ef0c1aba6cc596088c8e43e5414df768357c7367df5ffa3088d4a328a1011ded1246cbae7ad6894c8088393d09f942370566d2cc41563b65fa25114e40ea51442fffe473e89353c489a25a7d8a34a79c81799a7067652363b504177e7e54b62fac3e822fbf0ad35bddf71084a22e2be13a54eaa80f05acc97a9ff5d35e6a5c793603d4bd04aa5472127dcce509dcb9f9bf463cd91c1d4807fe9e5e989aedf7046c2381e028592474c51598cf149ee8eabba40b05a4316f8605fc8511c4d74eb645abdb918fc1b2aa54953a3d1251c0627a264815d4a82631e46c3bf66e074d84250ac5f1bd7fc6968b09692043ef573c1847f8c16f8d441e06b4aada16cd4a5b792ad3dc18c337dbfebe6b0c6e0bb64aa0f4b843aaf515638700d876b50b803f0be13036622a982a5e2001565f08359c2ee6215bccdb79f620e47b409c778cc1147fede915eb8842acf6545278938023b6006720b57f0802f9a504fd35819cfacde61c00a9ff2b718b5149f6f65434f0d4693474c42b528c49b5fc6552394070b6c858274e3a25010e109ab43b4a26dd22b70d6adff72344f5d55a9c3e73da594ad135f1d4edaa6a50fd5ef21d4f4a5c68c2cbf4b3bff5655087916f7783aae35a82c3bab2af7c8521f5ddcf412ba18852bb729a1356ca98dedb4bcb0fe71c805977cafba89e5da7e86b6dab5c1bdf68411ef17167cf3a719871402e4dc8f8f3444da7d746c66d3f6c3622f3dad72eb0d0b98f3cc090c54d00de6cdf2889680c940d841bfa21e4a8cc3a45efd3e6940206afba56456fd0216e63131efa281f7845d331b507d1ccb5a5701eb803afc545027b4d95c1f6564fc2b56e58b95a5904829ba1090bef28ecaf62eb3536a79d2b9e998b50cd7874020afec42203541518e969c61cad4c4c8665a25dc4bb4968edeb6512df8dcb85a15d1c4f9441c9d64514ac82102721f4cdaebf567a2fc7e37dddc628bb7334ea65e9f4cf792279aeaf6096d3b22385c5838e5f0ed219d85b1be6f4eef2fa959a75d2d4be9ae92400f8cc8c62b114ab36213429e02a0f1d07f181ac88de9b76f7011f16d0a9d8caf43574324239fe4491d86299faa1fb848e19ceae83dccf71fe7c3c6b7e5b0c3d4370f45049faf9403a8fa2aaef4402658e4bec62bbbaeb3e1b06392e67d9eea08dcbae4e9f64ad31197db66116c0bec7a595b288a9324db450825bbd12b684603a24117822d05afa2fbc39eb3382d58ee0c7452c43da3040da42269400758be391baf90861d3370a845c4730d8ae270f8e3bff94d60fecd961b75bb13e27097a59a082a958338d4c0792f5b93007c4d4f92400f0e83780731c983ec89bc0a4926346119e59e5f485fb4f7d567df0a63185f863501dd2a0b3be51a06a6321514ce4e7b682efeea68ce1a7dc27738deb3b2d8b90c7d567c824c21dd1bd9eeb2bbc528cbfb6b69546cbe93a14a7aa35fb4de91bac81c2ba3b92828aed16d1232220487c7c2ea1aa953a4fe469d6f3030ed17d1895f39ecc3f092390e43fbae83b43a7a5e1ab86a1ae2278d6091858e9bc754c9cc8dea319048a02adddc9345f2fda6a58a58ef64b78d2c7e24af3b791a9e2dcc8158c3c2dcb7e686ec73900e58d3ee95d2e34a30086d95be0f5eb4841809cf55689c096b31d6f4061c7ea13eec72706b332def3a47154f815198a2e0ba1e480de02839c343e4b79f7308b8d8681d86d7ef443772690bd7e9e7032b672e701a1b77a1f1c012814a8d4005f75dd937bafa0a26985eee3d3db041c22d2b8a0a436534f0f4c2c093c97e9f135850b66495e19c20bb8759c98236eddb2919099e9878419de9fc54a7b9907ebe81c0689007bf4ca459d1aa172628cbd32a1f1fed75f3506e979f0d45459595d379a2881819746a2336489d4bbfdbbd9ae03e1b18e10761c6052a2a56e284d304ba824b9892ddc021871b8776b841850381692935a850e5a10aec46f1f9dec850a14f02bfbf0aaec46dd4e6baad848215c2b2e15d83ca3898fcfa339e4cb37036b13e969cc012052b2ba1107e66cd596914515343b53b74a8d5de08f637b71178111c1b7e65aa17ebc5af6ef1271debb0c87454c91ecebb182fe33aa1530e56408c11fa37f5580f0c0b75952d7be916699d9d01185ed9ac7f36078d64f4d1eea2ce8e1878a8fc4ee5cd4eb702b92f7e860bd8f52760e116cbf6d9b568091c5b8c2e9602f266effc628b96dc3ab2dfeda7f6c326089c468f040853c3aa0f13d83d11a3d4e75053dbb152071515b9a283532158f64f4c652f374969e4436a4794ed8552865ad94dfa775352e2319e4b46d1d1e81fa9e14019ba50846d43b8922420e08c988aef30a081808e97ba41c6cc0c964ec048819f5eb37ed4d6b8994af54d514fa30294a14a039ad000b14e338d1014f6d873e91f060efa87016c9033a5f87cab3f3f9aa1cc57292ab995187a317454173fa1f9b9744fbfd2a40bb3e63947e791f2d51df44f1e0c00015080e3132a7d4791d1511fedf7f2936868398bce67b9bedd0228fa2ca34c116cc7bbe7552aa1b24240c8eae1cc2d9f85a7f27b7ef7ae9b316914326167e00cd18879021c8ea9dc0a6f8ae67b572377612a494e45075dc35865f069c24e8059be9a5c13e9af7c611b896847c1f03e819f413cf03272bcf79003473b6c0976f02a59e70a29140375967e9cdec2794f75874f2c006f9668cc93d864985b46262d1bc2d6b17002072c042e695c64ae6c2e58e8063368cbc3c20d313067b92986b83ab227e3d9068e3ae848e12a29b8eb248be304a5694fd2957f2837be2a28a6768ca68a8bd3ea9cd6d10b6bb0cbecf4d5d84a45aa3bbc956bb2ed23fef062a0b81fecdc27ed88e0437e8e6c73b4f295a700ef81251c9252640f06950500d43170be503554c960815499545217fcb7194a4bf8e4323df60e4cba08aea9d00034a2b5fa48c6ad1126ab0fb56eec15be0d1dce35c5b015887de28fcc32078044abcdc2fb231d6103761d5e58c9045b9a161865842cd0c54704d8743b7a4c9d90a02e696ffba520d016aa113d2ace435f7b380e5a25595b6800b7cee72a68bce49b9da8efeb729e36ec92e9e51397950a54100df63699bd805fe6decbf377acb324f3122d65e2af427509c93191d2b512779123fcba49d46f00873274375c66360419d7c5dd213ce0a6a714b8acb725784f78e1cae96efc77a520d0482042a6166b48bbeb59bd1b56bca418d8da9d0e8909f948b26ac966743eeba951325a4c24b8fa46425cf6432cb4bd3a9713503d5a19971225c1fdf99b2b7de7c96b2b84f1363005e394d0e14d61eaacb6592f128cff9413fbc0b979da2701b44400f2f95f32b815ec22b738a759113d5de81b40d3d055f00eea14f9f8f956291863907a10a4c9c722f03ac62d0ae340a5bf2e45a1a31ab193c5fd4c29c8d34215b2f8380506f698c905d9cd088204674443303b0610509a5d0ef6ae3fcfb051aab067a499a56f9652dd57cadbc336aa1a604f9bce98a11684e67f96c680418aa58cee1c35f226fdff9f4b2519481df1a0f004d363b3183ff34216c800bb432dc179cdec2a5df6c515fbdef0913a918360b40fd0830e4645e2735e912e2d5907a9b72086cc24e44d56fd4bfdb0354f22d954f4c7c287c08db5719ffb7c40bccfe553f8c0014489fce2a8624a4c569713765cb07480516d85eb4eb72f3663e85b533feff82b131503e092ee821220f7da0ba49d7564d45c265452193766df6032c1b6032dfb40a2b525951148f52950d02b3b4841102aa3c1acddfa889c1a8c91211a20892331c5ba92124691382bc62033c56148a88ac42e35a9ed48b738a760b664a79361e1984b0a2f53964dfc967bec26582f1c9b21fdb3efb5d3985971cc75d9520d005013c27586f2a6bf9281242f278a3eec18077c871f94db9b98588a60bdde584f475d110ceaf88687646cd71c25891184dbecbeb7601e9d9dcd03e020fc1aa61d1b85b5fa359b4bf1e925ea6ad73633d8c07a99c21674921a76c36d76c083fc961e704eb31431a425802dc9403a9d895117c4d2e0cd147b69e2c0a99a9a6e0248f07342ece195264261c33432cfe5a8127cfc091fdae626946d9d8f1cee116957638f4338148cf66e4372d8e157495405b6e0bca98a4d41df697f15c298daba333df5c23655197c7733ea9ea84145def363c79f9a7bc67ebe10de92a354f44b8de685271093a2dbb5e252928e0e16e4850198eb0a3553098d6de4fe1da9fa499f3eb0239416bf1e4f80e12a5fafa55380434d98444c62797bf7a444fa45948d666c3614328eab7f97f8fa9442a241db98e80318938341cb2a3c01a772c611d4ce35c167df9845871da8bff67cd3993824d44195b778e68476f621f3df3b2a9cbe7ceedde9da6cc1230d9dcf70660288434c153264dc6617562a8036515e3dcfa6e7b555162f91bf39b7f08eb1895eaebf96303ba5ffbeb0806f57ffb7f8f80afdb51a29e3d7afc61cf28ca3cc916ed3bdaa65eddc1350e71d5605571aac8560f51cecac153be656e214b95da89b1fa663e26f71d049d17a7a86d3bed7c85acbc34826f4d5a17bd54d90eb8d6e54d029518fe6772412130ac4e96e59ec8c45f95e26579602668fd0ca4c047cdddfa79f1129979e19910b698811f2165cb7636702ed1179cdb3f16feb0def9f245f20ba7c2ff3a51f4b7f4ee9232c10d37bbc46afd53d3862784ae5d68aa873491d35d2800a1d73d4419f619868e4961f92a6a699c125520f372bd0cca00f6966c6066b5e2763ded434a4730d2a8f76bf5900799af3f39e13c230d950e38eca3b01200f43ec84717daf35d432039860fd8971640e114191f1f4ae6edb292f65db8fab53dde48846b3cbe80fa77775e5957547eec46dc43b177f783a4e46ee98dd831a85f2b0a03ca209de8b1226168b7c4380db22ca4494f2444154dd7bd8c9e47345006642a686d40cb5f837c0eff347a8d2fe9f249119083a9c4e962531b15b3c951f5689902cb1b85f6a5dcf93c1a540ec5f00054c142216ffdd1236590249a0601ee2c0e2aee3724f14b268f0028d8523f04c727d2a8f84194d5b06ee3185d798892672b922d94952ff0ded5dea8a185d8f1b25a3e7ecc0502a5fe20afcf161009f37256f3df64762592685b123c4ab7b84b0ae271477d14922d11d5e326d62d9ba8ffdd0fae16c34696ea3dcf7b8f1daf01f606a6c2c88ae4a9979ce89826d669587d390db0e90c8cafb0990862493fb8eb54fa263e65f81836147140e73a77b231bac3756d8dd829d5a3059e94d4843108e1582717a6f12a618470ea7101dc686c0341e984b3bc7893d51fa9875b68c22760bb7c9db138ce43d00cc369e0bc571c6eb18872d3e211aefddf6c043770fd6c8b0aae0f65425e61e7115fba984c9d845167addc3642addb1450085984d4a31662d672b0bf66f5354dc3011ae6907e180ee36a31981255d6ec592249ec84a4586672ce94613fb9907eb26219881754ad51c23ce5932f1afd3a5c3c84b5e89a05cb5439fcdc1ae48cb957bd3c9bd5f85e06a6d5d1123ed20a3df4db57c45cd07f1af013aef87fb2bb958208b171648a2ac8fb2183b6b62cd3fb9e0dd3134d82df2df65e5fd3ab7cc77e881cb9df712062b3e5b1be243fa1147b1fbe1ced2df8aeae8979b2339cb0d70a0562c1c043db1d055b39ae475be5098aaabfe80997278844cad0a231aa6f3dab49f928f1d4d0a16988eeca011595c59a0ced3e1423b7450a38f9db997f296743c8d1b5def11d1613288b223255905068ecafed7aa00a1af062eba874a8348dadb9d32b06b43b4586eb64ef5e058ce6620eb6a17ddde60a76ea607380d543baaddc44311d6c7c80b963fa2aacfd614108f445b40d0c02b029734290db470341611566293d400f85eb8166f548cbb1dfbeaef88c068d324c15ab82bb7295cab13b024b041c2add193f8bfe61715d5f23002cde023760f080008376ca7de31ffefb8d8b6c1cf653d381248747215b9d3570051ccc3e674417ca20b486e2692f1ed70c9890d13480729c14570fe440de35219f699e594c051c7a11d625ab3c9b7e98e8818f58022fb61b15240ceba819ca65d2690c7c2bcebc003393a398bdc59396ef9a75cd5c1b121b4b4796de6b9e46a6f91d8b858d89aaec65dcaf3114bef330ce8c19846ad572026002a7f0dd36deec271f99709e8d1dcbf4b3040db181352469cc48d5c5d44894ac45a0296682e1dd030281c8d8e7412c4a6fde2db46699f7ff035cbd6c58a2d942b97fa1a0f257058d5fe71cf7bc4819e9cbdda692bad2c515b9f48fa3385464cd7ce0151157ba8e0a7e1ff44cfe15e5d8bc1eeb81edd7bebc598465a6bf690c1c0a31c4290afec3a9e09b0bcf87c99e44c6bece0d42bc05a381d72b9b2561f679f77e0c0ba6961d3de1649756b09b94f37e6c772e9ab8736ba41f01a8e7ecd8905c5fff0ceb7e59f3b01ccea3e426900eda33fcff4b02b4f076be4738f1b2211dec03e28c330f5e2d086f174fa01644902d20ba840a6b12aff28e1b1dca4d2d38946699026cefe8f1ece0536c450605d683e8a01bb1fbf74e066ec795f2a1c6661532b126d5b6403836c74cf35cff57d0ef9f02fe89aef94124892bd3fa0e9d87940b483b221b7c92ed2dde8595a1f93087ae7dc8419b5c440acbd4d8b45e3060010c8160c2d18064d1a5a91ae82a97486f35aedcb14fb5d89b1d9ca994909fe19ddb11644f4fcd8e47953fe6b00c35e8d99eb5b1dae21e7fe2f60349bf04d6beb6fc96770a064850411ea670470271ee2105641f14680b0caae2ece2f046ee682dcc9d63b62350b0ccdad4e7e86d0d7594894c56f5719e379ce28e644045633ccb4ba7dce6e2a6c9312e84a878b01efe2c5f6a9aeaf740fe32550a17912945b01703099d463aaa541d1e700a7a2e5c8d42a839637cc212123eef5421eff5406522135315499996991f49829aca33a1f517f6f5d33e42ec0d88a4cef113a1053fbc07a64035a6b28dd49e6aee4a5c110e68cd5b1c2ba565279fa6360130050cf8ceef7c8cea388bdc3b09a5ca20faed41997acc20ee9b8afaef603f4546f6bb672b9da21aef65d587a9cb5c3ad29703fd10e23ba9bac206ad0791b4529e5a5c975eaeac8635f3096f14f5db3687729bba2a97b28d5915a43ff3297c2fdbc13f276d7f76a1ed087608dfa547fbd73f66cdf59b0a0c969b01755fc548fe04b6a8915bb0c01175813df53bd3a13718577734dc23e0448c0a48c7aa0f9d8270c8375fa4e9e1603299fdbd43bc770517210ea6d13379415fcc439cfede53417d3eb2cd83842de20d02e6d3b1b61a7970e578e8d9eb31e43e92fbe28c43f9339616cc69af8c6065689aae160d713c93928cd09bb5606ae4234dc20cc64c64f576d240850e24749b262dedde3ad3b5295c31021da15c22d5dd293a3570ff6a4768a9ec808b6c932776e7ecec236bc0057b0ef0811f828acd8122eccec051e9638d6f562d408dd885effae47b873c2b0424ba616bb52b59244399892e05824ec2adfad668f52bd84c4976d30ff522997c22d3a28d5cb5cd1d6fb6c3c08a659ec20cf76a7e2cb5dc29c3a499626cc7668b119f739c1617425025336ff2dd64905e645b2e9c3e47ca3a3d864138700cbb272e8c3099d296181726c66b7c53638485b9c189dc469f3945acfa0e4d370c8abb4a99fea7c732d76084c89216b92719cac7092e7f6d2e0f9237cab4d0c88647a4a175bdbca639a23910dd628579fd0f985d8cb7bdc378438141c51b9116f2614b45a9d5a333f646dfa57b86405f3b352613d3fe30c87ef85a0abecad88ae8b2eb00aaf61f40fe6fc9acce73f155ddbe5a9e8f80414e5b70d1b2a17e834094d9edc6758d2d3bdad6b0c60794d4f9fae44d16e0b13e667faf0eaaa65894fd0ba53109f4182549c641049a1d7bae5ecad04bf57493bbfbdd00e4b33e46fc45b5e0a538a536ff4854779a29f168279f6f25fa8c666a0acd1a4e0a28f654719340f0b06375c2666c7df29e773baf0ae3e5977b45b071fa512b68838e83f8c0980bd7e12315bd11c8d27a5a31f506ffece67a6c82268dd1aa86c32d2fd581c492326605fb898a71711395910ba65c015e91a7bc3deea6ca4c05505d984eae5095893a170ee46358debbf06634bce0eeb479199df6ff9fff7d983d706cf4f8a47d53d8cb454912edd4b681215263fcab0394c73bc579563761222463155feb3e421cd4919752c24aafc5c0178b3d7d5c85cdf02eaddf1625d3010baa20e855c06a510f80a6463fb24c967cae0c76295a2d2ab246d0c7b8b5be492fee4785666b7daca9b81bc52f531d78677725ccb0f2010c6dde6e19da7e71a929082fecdfadc835873bb6bf5c3a10e4d4164efbdb7dc52ca24a50cda05de051c063deed8d93a1f260f1f3b25268e7d5414fa3e586380368b07ed4f9a4c4713c7de919c399abbacf756553b9ac33a9bbbece7ac6db46d3b9cbb8e2c2885ae0f4ea92debeeeeeede755dee7297bbae1b4d46a6db8c14daed9b8cdc74ab3a366bb226a36d6768289876ce39e79c13354eaf35c60bfdab1d748d154ddf3d00ce4f11a157b3b6a4847ac60a2da3517a25caa63120515450f47c102608fdee62815e0df1b76fc719ec106f86fbf9edb8f2b1f8ad8f2ddceedd1b5befadd70e8fbb68cc69adb76aab15f70170de9033f4f7f451059b6aff391354d29c114c1e74ec503071eafb36f5d040db840435d56688ea8c11123341ee12f74c159a34d54c3587cdcc986a33333c13e4304c7b781493870926ac2b62ba6eb4878f5d8de68cdfeb8503070d1aab958c0c2d85b43f390324dbe4cf19a05d9f9a9e90409b884653adb66a4c4eebdac24f6bab2bb51d49dd955b85c7cec85d479a9c31d5bad25403a598c165259eec8b15d4d66aabfdec673f6bad1d7968968e7658976082d0645d5a85e18ff8e11389c1b4e030705c99f41389d1af4b93476dc15df46158a30d346912599a48cf48a143d6bb8b06b9922e76d0d764822836858625367d1faa3a79d3ca32c09caed507a16189e99a7b8a2964ca04d1ae5f1ca8b68ad4a5da4a095172e26e3347daef93be678ffbb57ee6f971d3d5c70f09e48fc00680f6f79865e389f6ee7d5cc7494f419ed81284b4f8b21c06d4aeef301ba33770de90398752a16cb2e7819e97bdcf60ee7297bb9cf38fb44804e89126aa3431c5899913323a6fe4bc795fbd28a7204f14d5d6ac15d9de48beeae844d17622caa64f8bd09992940a34498180d012a080de990654cc1cfa643ec940cf2226aa6c321f55d9f4c97c33da92b7803624822a6a42c926f351134736fd6c4556aa3ab3c8c8cba65be60429a51bac5aa7305b24ad1bb451d360763185fca06f107c9e9a71d079f4becba2456a2b5709bf7cab3add3873458a08ddfd8c15da737eb14b77f320d0ddf603bcc70f7ade48ca50db9e69e7a39acea212100b1e13c717fc3d160cdd6de63c9d635120017a164d9951ba69d114d9a63446e9671a010a761eeb7b90feb0c36700a5ed7024ebd3fdc2fd8fe57977db3e522b6a8b3a8cd24085b648815235e88c1bf3516dd18f81429344e8ccb9982864a0c840690148eb6fafee95ce6a65763ffaa831ad22c81d511b250ada73e28e34119a084d73ed42f1cddec070b3551fb8f7de4b5bc248d1121cd09e942f3466a1bc3d8ba4e4a0080423bef068c018637c0407401a3017d8c8081a7cc83aa454b1f59e45529ac0369c328da2ea6ad7d18e56f72f89eebaaec35dd7e10e7739e36be7076eb5d65a291330e3cceefed626dfdcfad3551fdf7b6fbdf88e36875a6d54286dadb5d6ba7bb5f606fdc423e9c2fe5defb5eed616f1d562ecf866dbeb5eeedcf1686dadb5669dfaa4dd825fcf5c5b1a1760fe03f0a86bcbdd059a54a96c3ec76b4775c75c5bf3c61d04d4bfd606ccb2c5d65e7b31cb477561ef30d0b9a882db99737de2505a270e7d9f814e9c5be9c4d674c74989582b165b8c3bdcd94cff6619f0c519c0d7bb00ee3a8b475203dbfed89cede541fe9f38ffe15294a525b2cabed4223d2817249088b24577d124cde5bc44a9336c31bef8da0f7f9676f7def7dcdd6bf3b5b9ebba71def791ced6c5d8e2773c4a00578cbb3b7e5fcee1da7baf9d3757f97bcb5f3f6880d079e2d0bf4366c4ba714e17ad310b037badc516dfb0a6a9cc890ee203efa0ea581e8b81ac98e9a2ffb1367d87c0ae97e6d3ae5677568ab88412aa2f871c1d9a3c2a3dba1d5d39c2295971c5f6a0329839fef34587a6804e6892de6895a1d0d2d0258b4dd5a9dded3adce1eb0e2b02be0d1f9b7304efa79e3cbe9fa0c8a2d3853f64c5400008dddf789a38f8343f9276778e502fb0e9d36eb4207935b02908da549dbaef0dbaeaccaaabcefc80aa558d7ceaebfa9aaecaf32348d5b979b6eacf5a6d9b8c61536a3b8b032240faf3e6d27bedb5b44e4a85c0a3aebb1e312fbed75e8b5b3930c6e3161affdb1cf666f17410a4432b877ef0419374365352f31ae8f01d11033f63a6272951ccd3ff69e203fee97fbef781f998f1c7e7f430e30f3d1a7f7ea8d1d167f41d8114e64db55bb170c721ddd7d590fcf4f3e368c0c48b0e68404031d3c77c131ffdde7bde9bdec77b53ccf8e38db50cf29eac32af06b94c6949a6b4848962b0f00c2bf529d525f04997a1fe7f9af878afdf93092016f3a6f1a7898fe951e30fea4dafc79f263efa4de30f6aac46473f5566b481c662c6d3cfd8e9c94a741a57f889c4f0ed670c665c614c848ddc65b5e8195b0da131ef69ccfbd3eb6fe2636ab26201e64fef03f3277f9891c807e6f5f8e330a313b992d28c7d4f7ad0379644c46600c795cf87ff8dab21e2872f8e3f4d5642be37c08c7da3ff10bca483161290c0080828e646b21a037f0361f89ef8e1cf988d22f48c4df72337f22838ace08927ae0001c5660c1c67d8c008e48d2b214f3a0a74008ad8f6746293ae8489302a8cdc76ef80b67fa92dcff9526db9b32ccd5dfe9f3dda16b7ddce2c90bbdc0ea167ec8b518f21626b2ae975c6aa7ba9cd8ddb3bded029a5994ae7e59858dfd1636d3cde717a403bb1dff75d91cedf66b0f53736daad084403646be39f9f4ad58d64f71e88df7b107f4016b06f36fe7a3d56aea1bb8ff177f82b1eb5457938d0b604388c360d14466048a1a44a9659851525d00a17b21c2a5a2c41a5cb8bca143bc69e45546c9bfc575705a84fa0e9eb30d7cab2b5d28aa3153579d42edbcc9b990395b591d9ba3ffdad9eadfb1edab17bf990dd3171ec9cd3babbbb75777cbff53ee77cc01dc970eb10b3e69c1d189a7eed4293768bf806d57d4e95a6eeee292dd6babbeb52d3afd74e9a47b06920d137c370eef603a8aead1a7a13a7e62f56bfda04ace9efa2698efb9c3a70f69c9e635b8a63bbdfc0ae6ecee9445d5a4439cf399da80b0844eef6f3c2137be64fd1a6e9dbf84251a9c6cd26e79cd3babbd3b0ee2110211073cef994524a4320466bc2d6dd5d8736359c6aebcb192e639373ce9965623e40f5cc9f36a8d59c359b7477a78911a29c5c55ae3a73ce594333b392529342ed396dc2efa2e7d77040d35dc3017d8964ba68fa3694e182dd657ecb19324e3f43bf85195962cf7c77774b33cd3be79c2fc345bc41ee9936bb37bc09514e6ad800a1e572bfed89e306cac9c511a29cdc0c5c992ed6e608756d2765a48370326bab03a7e6e870777fb97b8ed8333f2c82babf740f29664a8aaeeefed994f0a9c0e431f72cfa768437f90a8d85cb6e2160de4b1d20e21e9b89534dac0f268e03aacb1f05ce1b3971f368bb603a429b94e83ee3234c6f986e71a72c924ea93a5482a8adeacc9cb5a6d1b9b3a90b0e9b73fa4c1e75c7c32bb0ec39b984aaaf427c2a80802a6277c7a30b2dd056937ad7908260d8aa04f584d600de07534968921629025a3c7640d77b04264e71cabeb4c8e8afd0f54711e8fa7446679645672bd0f48fd4d6fd99ed1ec42c3a9b5436a3b2aad37d47c11dbbe6b8b1096b157b6ac8a2453270245fafd677363aa3b6db5240d5b9ef000a54d88c7dd9f785d41f41eeb76e36fd4865a6b1a3494cd77d26f685625f2af6a5365b05ca151402ca0a72bb87feadca94ff1dfbdd75bf9cb4acd9ddddbdc87d95eac8e8febdf712b140df9fd5286b550bb4ac957c195af0fbf96290a446d44ddd63d6fd693471eedfd7be3717632fcb75b526ce7dfbeec20b0ef8a9b01913e2736be58cbfe5300664d1ca8117ad6e74016787a0b5d65aeb4d6d5d8bbf7abf8ac79bfb42aacebdeff365c350613376eb5fcbc274ddbff7bdbbf12864e2dc6f010502e36b6ee917b96333aaf95683d496fd99c354982e6bed93947683fd03b429c2e689b63fb7fd59ce5aab541f83c376a8bdf9a83a64e6711bce6bf3ed61b64f6ad7b6368f48c8d74ed5b1ffda61adb5498093daa0671115d9b6ff20ea89a69452eadb8ee4cf8ed39e37d869763bee78d5d638d3f67ff8a83a33063a50daee5f5bfe4e18b160e972c06a9546699d934392182ced00071f324aa33c2c9bd46fadc0691c3a64c1840996e8e24b962f6258ca1113608c313ee2e4868b45670489c89e208a0eff51cc222a76ce942540dc14a229b5ec82f9d93e805af9327bdcbae777341a7bbaa9d61ec01c1d3ca9244dabe3f8e0f1a2fd736c0e5b553cee0a62c5ae55ea8a8b6edd1c7c63c70be68327bbf6fc9c244c88be400229cb134474a0073d2460490f5ad04390ed8a2d78e0441059ec7b551a7543ce983f2fcea43fef0decea5af36b9e0da0d76b67e786068db4d9ef283a4e1e2c1026fcff1993870c59cc07ab191a598c99af4893a9d491aa43c200942a22ab3ad4c3cefefe8ac0f8c10ad828f9d95258683205e42ffb9d8fa3bd74d66696719aa16140166d36ce59c30608adee059b4c01ad9cd010caae2c1565cdfb39c28ff5f2fc3839e10c5baa0670deec6004587256e4a28096fa4f3bdb14d601d1221ac8b4de945e1c6d93ad4d8168f0c49e5e278c11da955eb5daee567164d791d4ce0a6b2bdbf0a5880aa34db36711153825b81cd9367b2ab1a5dc20cc4a88f744623edbf8c1afabdfadba6fb90bfff8fbf8a37a22b1df71177e22b16e948001664cff6452659fdcb9b119bbddb7e02edc8d1230c08c09197f7eb80b1389fdf74462f5bd11087cefebd80402b1d51c5709a8ffbd8f7ff8f3c3f1a7c91c7d6aacbe3f11bc52a17b5a4521e0b812f2fdcac7fbc6186561c6bc2e8f6e44571b10d0ae586e4e80b6c792c15dd8674dac3c7c3136d2212bddf213251d96a3ee688908f0d19110dc328c8d2d9e620b8c315e210b90c334c65364d918a3f0a84aaacdb83205144153d85453104961b449fda112336fb086b4dba087156fd0867e3778436b7d835d884514ec0627f8596b3d8badb5d62db6d65e6badb536e7b0d6dae90230034b2c306676772752539ab73d9d050616955860e81b840ec26f38eb642da1a9a52c302c6b495377f7da0171e3b62b7e2f3f055a3ac06c7dd511525bac1c7ef1153ac88f9cb3a784c355d13e6aabbef7e1705870123a45856edda0e9a77055c75ff45155a4e9832275f07680a903ce20834b4e342ed68d21d75eebf6c4a2192db6af0a6b3d2cfc3185ab295c0e04733dd18033006fee1da9fc85dafa4060725c0e772b4ae1dc15c30466ab7e0a078299a5dd95979c683e0be48dbde9ba3f8114ce61a799caa5943660026f30ce53c19b8dc1137b3c0687a5aee0be3ba6705de06eb8a25860bb5c036da352e52ff758ff7c061aa5ef05dd3df4241bda50f4248e93dc2ca2298b09a6f5f277ede38c9b942a66a93abaf658d7a77b63aefd02883b8cd8e3299b0e343dcd700183dfa8edfad92f06c3f946362f0c6da35299b2c924a66c5e15da66a2c9c1844668fb3a95b289480e7a8a0a7d2a21dc16cca9291aa5b2f91b0d024dbf869f08029db3d62ad5bf97caed68e5f81b958d4ed96aaaa8c284dcd8a46ce078036be4bc81352132d5b5fb7bfd6f06d640142ab7a96851b99db2cdf2a6e30c560c52396bad527d6da1f0ed946de2885750d5da50ecb193e545ea48a7c26dc1da789b916d2adb363ad2e45e8849ecb1421c962aaa64ca368f7c4cd9583bd0f4c9946d7b5fee70bdd6a9e7a108c4ff3e78a3968a32bfc6f62abf5a0c510d394a69d1dc556fa2d0e4cd926b69db78a251b94dde9b0d0697f304e545e7108d39b5da8a8942e7d476fd1a25688d2d89c9c3fe7db246093f598345f7644d15f9c99a28bc276b9ad8957ece23a97537d2f0483e854deaba236963d391fcb68d4d5e1a0b850fb5553db5c6b62b0d202334fd7c83264e8e0d89a12e20def3c6a9fba45680ae50ea93fa17cfae64c69c41eea6080edb90f0b2ad4f07e38b5a8d748b05a63abf3012032341543cd00418b1eac5aeb5d65a6b9146a5408733c021bbde324657c7abd62592ecfa5ac9aeaf52621745b1c5ae4f65aaa850d0b0ab942454af2bc6984aaea31246128c24c92541905d810ede3921028c316a627cc36d8c317eab2fc6f8c6656325318caf1477cf222b465f918a366104144146287d90115f3e78a2657210d23340f870c40b6458d1327b161d010513475ce9c0115c58b1b96beea992b16711125254d9b2c90fc20443556726e14394994409b6f83b0bdd7bbffa56761cf244ac1d7f88c860d4c20b3f8c94e6913dfdf7768421e7d2ca7aac951d7f087d709c017cfae30c24becdd8e977c4ef4fe38f6afc69893b99283ff89e8afe8c7d1f38ced0c407e6c19fe1f41fcc833f63e44e02c40f61582bbbb2e35ce18f65fa982712ab944846861a7f68194454ca68500c0b13612316498352e5ccab9e482c4659c41fc452a41d9919516185204a2c17fbca9243eb8185756befbd1863ff4e4cc17a1f188a26187d8a9141a56662a85eaf0f645857e216950319333e890ed9b3c6eb4e6b9b1251db8883c6cd8c468b9b1a331a2d6ad898d168610384198d1620b4bcd084a1bad27221b96ee0e8018e6bbdc31967a118589449adbc41b7ba353972e0b06837c0d1a1a423a704392f252f0018014008464210610797a7ed18816675acdf995f96d8638de8c076bcec90e085041e3d7250438b1e256451a3073b46304100d7ad97995f9b850e4c003cd7ad97995f9b05093c1be70e63715dd75cc346fcf2e8b163a6ebd6cbccafcd8287c7ee98bd8e677eb1cd62c77a103b218c137cb8e511721e21cb23d48382100a6e4f10f21384ec094229fcb0585e5c722c0e8514560062fd7af1a070565b2aa820f6f80ad6fa955126b5658da8b0421025968b7d65c90112a40619001d000126ca08015e228450c20f275bb8c384615c2844b8a43c2acbb2949144870ce370ae3cc2398c9625c6951857625c897125c695653883e105250ac49063522b4b26b5b2fc91a13c2a99d4ca124a599665e964c2c25989049430a874a26d44b5b274a18523213f8ccaa39249ad2c7566488022530b67e1ec1454629c937016ce68344210a184009cca12e330aec6a41c026351ee90c04367f48623134683a8128ca3326a4359d292da5096e10ce3caa3f24938ab31c138da839e60088c45599618e764c7f5c163cbd16aa1b4b651a96efe55315f38a3610e9486b3274a65ad2ccb12e3988441e12c9c695ba8240c0a67e551c9a45696e12c0c0a69e551592b6959ceca70562bf374520e3161e1ac887076b29540027bedb5d75e6b67560b24947ec802dd29a54872487248724872ee5cf0204bd2976bb1c54343f88abd7287acbd5fecbd82830ecc64322d3df460ed4562af6c69498b9c962fd77281c45e2dd77281c45e2d185f8b65f66ab9960b24f66ab9f65a8cc45eace50b0fec901d1a721fb243766828490b167bb94062afccda8bc45ed94c4b163dc864b2571837283458dd5a8b31c6d6fbc05034c1e8538c0c2a351343455363a33a7922adcb895076159dec3a652cd41895e56c42194974c8646090f52c1c74648d37461a2e1a37ad9b1a5040a8c962d4b06123e7aefa40af6afd6bcf9d3dbba032b1cbe5acb54af52f9b91656143a481e01282068de6fa46ec375b8f02b5d5138327cb9269b0eac60d1c2c9a0ac70eda7b9196e34f1e4d32612238aa666c92cae80eae2d96b5a597e0e84822432693a18fe8c8f920e765f302400861f8f3b7b05e70215c98b8ec3090d582101144d8a19ab163049d191d584a07b01d12a0c8d4449abb52b95ab763ca1c90c04388189a481369325968344210a184009c9eb8ed18210026e8276e3a624ff730136028d5e9bc176932a74ae9cc6eb1c3d31d75504c57fdaeca84759d155dae93e9d13d41732a95c93adcae54d6e59e20bb9b09a10962cf7d135ee73eaa4797032fbd452a9de12e477bd01b260e13a7db82ca64b2088047889046fb82e7b5af914d52195012a009c6f64ad8d9f111e484978ffcf211aa5437ff39b6d8027b50c8a08aca42169d798f10fa5b704414ecf63102348c0db3a4e9833e7a4215422a134b9126d2ba1069f426fe508ab414c49efb43e7e29f97e8ce6437a4f0e3b542de69111d61d199bb2a95fd168751994cc70c0b88a3c20a75052014489024bbca280e73d210ec2ad26e08f202c02be6e6d51e34903103e903241ba415520d120d920a2906d28c6721a9906220cd20a59050483248ac1149062906e984a4916090461a4830482624112944a27183142281481fd24d0da40fc943aa6103c943b201021208ad104c3da9e12e5bcf95d6551aaeb4eae1bc52494a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4a4addd0430f3df0e0075add5edc65ef0343d104a34f3132a8d44c0c154dcdca0647035ad5d0a862a43e9031c386766fb899144a26e634e357349f9d348ca9bef8ac1a3ac3393104316ba499e53eaf1b69a86a709bfe38dbf52ba5e5198de9d5441452db13b168faa47733b3678dd4b5a9229fc32e7eea0058a2af77839adac44d1c7ff75a9778e1c5b4afaa42c59015a129bae0c106422f770f8aa795d680bb8e8669eeb48bbd77895d62c4ec41d3aa040f3cf0c0030f3c0c0d0d0d0d0d4dfb378f3bb08ab327a3b410e00e0ce4b5f168535ba1d0f77abdb61d6f44215f02de0051addbd06d1d02f42177f7a16a021cb87f2018b239eeed62dddd77189ab89a15b2425ea928ed405b2b343fafb39dcde9d6f6b2d0e761215add6128ed4fdf473b6b9d94a8744dba042d642302000000029316000020100a0704e2902049b33059740714800b638436766442988683912086511404410c8310468c318418e30c324534550172c2fd4a2ca22a0e21502697386856d96c8e98174866b2c61a8b23d7afdf69d01f888788813b8cea6df0c2f3caf42055ca66e61a85fdcac03e4c0c81ca13ca2ff5d0dc811c723ac5a5c51753922699e8b76fd88c65901d099598256ad52db85ba062029bdc616f31581b2ae08a63f316a3e6e0bf5779e677188d450d27c7f834b8d1deb67b6ce969dd49349238337c2bb66860cebe099d000d36255ecc0bb10de39e5f2cf5fa974a22c4b86519c0a10b770e37c5ca610eb9bc43d358f1ea605ffa545f891f645b9ebe1cd26dede932215996876b8364593dbc0c7276fa48ae0afad053705b9f747fb3aedc6c52799aaa4dad93a98d9e277c172cd6caf720a14e1343713b33da7a28bc6ba25e7fe5e7bcbe0532b7b358ada9d0cc40beea7a57c35141a83a1e1ab540b5b8a52d67b9143af3d3ef2ca0ad753085e5c5129923b3d87c61a35034ba79d225ce3e0a7dd3d17748745421487567528f018dd83418335a068441686fd07377dd783942d95f06c283d236daebe08153261f915a1a30c827dc25aad18cda610bef52d5fdba0b2fdd6b50f3becf53358e945e3b9476bac8044e6f1258028575b2cb400c745a6c02d05f307e59f8488ef72157cc97c1dbc18e8784fceb784dd99042ffaa5f5ed6ad70b58e3a18abcb379c543c1e1612baa5ea803192d603154c3395674a60dee44000b9a899e66190e44127e398e52ac4e51737bf425071b4725c9fd4127662f30b530a199cf36046d82a99836f078b5e2578f9d09eaca5a566c80002beb6d2013c4c0404325ed97f6d48f5fe55e85902dfcbda25001b245f81aa566aae124052ad8e8a3fb80d5b8667f464124850e2f62011179c59778de3d9d3ad74998141a4661ed9e3c296946a3800421fa94176444c2f1ed04f4fa0c3171e78845f649ef47a124dda2b5adf6ee126793e1a88215c1582d1081795404523906e00cf50eb8579612cec06e69e08a3819a8c30e2ca1d2a4961013be0d77b15b14de9690adb577ae2607fc6c5af402f206731a9d46f773a7948c28b7918ccb3f5ed3d6140809dd197b3d8362686b2dde4014acf9cb0bb4bdbdc0471485815fa1c4778801933c2c190f0bfc8b87bdd61d941c21fee9e52924743aa2fe177cf50ac49e7a43fa186499928c27504875efffbb9e9497912ec9de58d3a45826df2f9eac392c9fb627d549217738d807d65607f9cd8839f51ff753711fb46f9260e2d8cdb1a164725f0b9b45fc23378d84555b9f6236630b03c3b913da6b425fc52f387e96a89813f2060ef0704ff63ea675cff48881db12608302ffe12fe61bed740ad6bae300c0e4d91108fa648073bbdcf41947936c241d8ad92c1047fb80e6fb3c416e5556425dc141e4ef72b7d43ccdeddeee89df47ab0e049c5b3dac60c33cf4d1b9a8ae104d2198a3f6538ad7454c2d5f3d17f59ec4597f26da2e80df6e4277ef7adafd543711fe51011f24cffbb77ef32b2502cb771668daa23bf75f9e9b31db12ee50ddc7828535d3bca4758c269742c20ccc88d77033f705e86bd8f8173d268fefa286835e1837b50578b6bc40780cf20ddf299bc3ce4394f90242fbf81cbb27c37090c6bb85a30ca43d37da6cf3c7c0f612b1ebf7799b88c6233d97b53dc22ca07d76eccb350a157d8a41d35bd7ff9d08c006515ec3060d83856809fb7575786b44f541c355f0705b69bb1bd3778efc8798b32e616b408de588b8b22a4f79e5517485b727851a0bcf7e88f1a8b3f4f221ad0097b8c51edc0aa5b1933cf34b563043f4109e1cdf0ad7f9ec1b0897e4f0ba5570151f3a7e381e289cd1ed316357b55a5287240e075834634552f0100790cc49f7228f5f58da4e431803c8934fdb5ab3e8d81254f03563301baaa015979dc0a2813cb5b809bda6d8ef0f365612c26f3a63ea97a6449cea192afb1149abfb395e4fac0aa61655c518a016b942a14fb055b41e3ab5d1dae3ad0cb04e49206a3583b8bcb041a621e309c97d357639b139b8891047e52e58c79bb01868017720b6977891831f6e3eb719b134394c9d650ea5fe72a7d5375360325a052364d8491abc85a11c782e94ed804ce620dd0104a1da15542d2c06a08fe2b8c4ac3af0c3d4c516ed9104b861263285178834963604a59b7c07b81f7c8566480a17d8dd18479280132d2be7a90636324f61415db7fb716c66fe533436df83218bb96984e4099e1c792867e9a685c106f04fe3a7752d70a0baa37e309fd248a289375b3915b2b14f2d5b22c9d8c9db460e3c9454eac7e5882d8723939f84657b3693776c27dd57c655fe63c63e9ef01212268e8c93d9d60d7f093b153737984bae72e57dd80ca5e2ae3d7dda231dc6688846fa6c6f83b6106e0e1c515b3951ce99c3484ad0fc728a1a905a40607c9626cca705eca1fe7ceda15c10b8064efa32f3ba0e8c23210ba65898010e48a87dedbd4ff69864c184b1a91c82dcc3007fe82890005ca72e6fb4412edee020171e8fce8fc9a5c81ee5f0413e4f03e79399cb35ee6ffcb5962306e8285e6d871df80db262e82448fc9066d221ddb961bce4b310c15ced45cdcdf248eafe89d5a690ae17eab52a3829edb0071ec611204c1a89fea42960a29b73fb1ce3acd06fd81f9a4ef6510c16d83e2e22c14aab904b9fdc6fb32d54d32d371fe28f45631037095ffd9549c4f0b040ed3d7f265eb7c9a2480b6f6fa1b5315f0789331e0026d4b09f4ede64e1b57325068b862d01b9a982a3cf95924f58cde02534d0bd0ecaaaecf6cf4cbf9ebb09899c910ba4d4411bb0f0a895893aa0f46554626e5470a0c0131df94e921756011dc0a6e2c10c3b9237e23006334e84a018d4c8f346a04817975d80d4401feac7f704315703c9b071ad2fa6d01762b002c91e56b355d383b20e8fd65612e0015248d408360ed078f97e3d392d53d8356050231a4469080bc8f4ca11fd4ef41372c862976b7d1a5f829d29c8ae12debc6cc8931abd388eb04213e020e749fffe476e4f936e79394da8322aabbce7a51448dd789aca23f3626c3eb96fb5b1c8cdd74fa60d7fd30a6745b5904c99fe1d77ee60bf28dd881495f38657693fbd748ec47d25fd797db092d30c00ac234a0bdcd60f97c5575848ac7766eafab6b1ba5a47c826361f547c1d07c12df8ed49fd4a4cf7482ca34f7e0ab0424f3bec931d1dae1ac18043877a595a52ca61d883278bdf008df6b9c74b8efaade3c7e298ed2f4cb4fc3b8baadc34ecf7096fba9f59e4e124fe84d33993d428bdb7d621667784886b20d9c41fe9e59af818a5ce70363a7dc6e2f19440e15ae428490a8b4981e411cb0d50cd0e872fd76eae34a886cd8ad2276d480b8d3b062a3f3aa75ed3e57ff19ad4cdae7ce0394076f9ad41201e039da056c1384e91cb6e8fa482db720cf99f59492774e3087393cc269aaa17fc6b711d12bae0a78265f96ec31c249ca4492b46728a7738fe072f94dfac5b6782eb87503ae6e03d77a38774013dac1f2734ebc7020fe13101a69581240154411542d46f09d770825e9171b577f3221cf427fed8c93d03fde7e7339de08f20385649354f9b38ed40226d2964c9cf57650c6902fc59806ad5ca2b04bd5ea0814e8e2c61353edf6bc05d7293802f243dad2b6a51bafcf471212a4ba9954323ad21721a4d070e547a6489e5ea1ef5b493bf4f83c61240c2d2b318d0c9ac20b4d58ff8e67022c70f480681fce29cee73d5cd56e34e1814dc712b6ec5cc5c9b8eb55de842e649b422141b71405ef701396e3a53d566e5f9be722140c99375e2b1cdabcb6cb7d346864b3a2b6421edde3af40e72eec4b99c914b56798d5eade2def73c0903b6d656227e446f30db76ce1cf2fa834a499fef24619c31b26fc479bed871031cd5326804d6fa07982120f983a20a17bc717804fb3f6487a44bb0ab886fd729736f6a2a3d835baeed0ec047be5a5f6fd8754cd8f8535643cd44c8fcd349a63c75ae51bb222a0fcc574260feae6985f46a79f0be1c2f9980b75bbdd42845ff82e45382e4031dea96aff3c1adbf65a0e882725e68efe619a516340eae949b3b682acc7099d85a18565c4c6bd809417e03e657f10cedf3d8f4233a8d294d72ff230bb277a814c7a2555fabd7abdfadd3a9aba9a7a9bba3b05cfe4cf41af55575faf5f5757bfb55e57e2f125d269afd7d4efacd3d4a929397ef6124d9dcd3a5dbd9a92c1372ed1d5dbacd32831fce117688883d3e938d028054804e28866725f5a01096c6e6d0f7d2f2b22909a98828ec13813690d9d325c3630e6a50eb3897bf1eda42817c5f535f251f673134a6bb19201918a64299e2513ca7d70a5d5fc993accc3073a90a777dacc8d0f5a771a9bf6bc691c53ab995eddf46aa6563fb9f211a3ce34327d1c08e3d8bad2d8d43159a9b4991b67964abb09e1d3581e31ed32e8e67c8f43a2e868a92ba45125eafc3a92d1c1edacaa8e3451543e045cca7d3f5df7f1769c4ef771bf1ce7e171dc2ee7717c108efdaf5ffc6fa2deaed073fdd8d60cd92bd15655bfc4a7edb737b5bcbe86988ad171a0ed057ae293177297741fb579594885b178a489092abd2401f0eb309b7ee7dac966def803c96997532201811940bc5066ea153524e788fd72554b989c9799391a0b06f485dda831cbe0334b6f5cd728420b6d7224eedfe16fc4c9fca62468babd54c338f2e7c546ca6c13a038fb2f2314644ad4583fa14eeb5f996826ca5e92db270d6647ffbe9cf5e8d972171c387750d7eb91ab38f93f33efad0930a280d563b24fbc5c4ef8fb84db36c0a56d593c748141ea46b0104ddb1be19b7c2e61e9064dcf0f762e921fb2d2e94c9a37146bca8d35db2e805ac23b9bd5c226299962e8db4f06a0bfe98666327d6e7606398698c6b83522b2693bb9a0b176813e045cbccf87520d2b06fc856542a8b033885f0106188b4e120687712dd2e3339f89fe0b5df5cafff6a1740a3a6bc1eef86acf8cb3549047cb3dceb0af58d218e164ccc4decab26dd1e8fdc05a1cd61f7163d7884a0afba5893130bc7ff3d112c2f0dc088925c5bf67c526a3949b462900fa378da5c1e683fdc6a14bec75c16b63fc3e414b422fbea838fe4412694b40a0f1ae45722e3af6862ece817d9f8d72227825eef38361f7cefbe42e88b6146fc942414f749dff87d15184bf2e1d10288e927595f201b466d5b809a9ca30f65665e8c492b34b48a35110677da953bd5080de42d9cb797230a9703652b86b9f6cd91cf21fe4c9f4ac9f6e3ba795e8ed92962019a2686cac10025ecc39049fd859daa272e3081a2c8bfdc53955c4a481e8f6d31a9e4f171c1a0ba8746fa726c10f4cfa486090fc2c4624806d8d40dde88416fd4a63ba744c0c4eab6cdc449b134718607f71b9312cb2350010364027999a3f3a0508553df011ce7c602a4d240c709b588dee672c57957ae47e845df6eca16ee5229b0de341f481ee486dadfa278e276dc5c964218b88f238091f6b9b2c46f84dc649b31dbd7a7a5c6e5d497e8b34dc93e0da313ef403f9f60bc945e83ebab20faf1abae3c095a3e70fb44f561f3f086a6121b9b297b0758f0392bba9de4f7cb39a3d2cad76eb07adce12779eb108c2402cb641815a2e1430eddb435d40ecc66a191c1b289f2e78b90310132e5c011695dba319bdc58d5954ccbc6a2a3f5cc788c583d19cc3a051cb8b725293bb00ce73412a29d0b459658670856f98e6e9e3705e517cb624e9c0208baef6a76281dbff4684333adeaef4408506bfa23f3a8ce5d4105dbb06d1297aa54a1c7efb6b78dd2c09497d4bb6a5498dc3f460ade6c2afc8f97065305e000802f6365746b8a7ff36da968889b7dcc36130143311da74a3e322d88c1c9938653338cf64a97f38f5760b00e1f63bb6020535c28f45a6214ba71542473a528d4fc0e8deecc208311bfd761d8f7caececfa497b85bbe5c07cfa674392f2eb1d90d45031dc9831fbd601a510cc908a7178bfe66dbacef88d0ba0bfef21b535174c5bdc8348dd6385a30b28b4aa61f214fd160900bd2045235206ecbbb85f7cec17a67f2205a5fd4bcb16e01f780b440e367785e4bc3a8773432aa2922d0d0a1fc0829283da41626dc289b67d6009816d5004cb33a76554ec946401855de4ebe458a16c2ab8acf42f4fe01c9de09b5cecf14ee2c9e83b90632ad4cea7d2c254fccf361239969c107e231252c9b1d1e05dc4d245e21d174bd9789d61b5cf4d62dfcf1ad977913bdcf598236b1650488ef24ad320e3ab7a515d70bdcf9fd2b6e28c352457fa60ef7073592473d1e38ad529921d8cfb7db116e2024d9d8cefd0ce6d66bc309fa72e255fdc23a539c83f44817aec1c03012eed2eed6cc0ca236949423b12552a1034639c7e8b2c3a535e2cfd6656bc611c0ae710b3522bad97f57ed5766a4649c7f0804e784cf85d1f2a298543c4a0db8a7fd08efe90d800b44a08831a8d3b371fd7b830285774d0f05afd86371ad0b220d397d284d6ce68d03527d1f24729f10207eac1094b806a7064fd28b6ec21d664500d800b29936c7f7a121caa28736d6a4c17f55e51d07a3617bc55f46ffc68cf202c74330b6004c647e22ad5448692492a81637688ce0d317f8f24674277e75969e7f330555624e06483faf711177d2a958ab62366909f6a579f5112e293c1743d2c957dc237f970fedf47fcdc7109b28450ccbd2ed52ed359c04cd87c2c022b8b01b07084156a2f35ca4965bed5fca6ef9aca36edb824aae5df3e1995cddf5ba5511311e82380f22ac727b0655c222a5290e0295531d2b481f85a3737bd976d3692ecb95fdc915de0147b36369208d7296786f340f0dd2abdceb57746fa2779e2dbe43a9f697bbc05c8705ffc0bce225dd1f5dd680b149738954a802b2c52ad7c48d5eee78854dc75ff9fb3c9340ba78680599e1d722731dc5e810ff7ff01bff97dc5dfd8fbe272e3ba8b63604848ac1dd0607169b30562c709192c23f275ecf40274baea715cc46c7ffcb6c1b32cc229be49515d07580f5b3391b898b6dc0cecd0533a6cb56eab9a37963c7c2e896775d2c6c0cb59c35a70b9c12827ace57c6ee381f7a3db819eb6763e6a7de3ef39749b870ad34af5b81edcfae5e47933db36cbb71925951825e7f3d49a082b19a04122eca3f6eb60450d08a161bd399f09d06ad8649a8dee8d14717ebcbb95f00c889ca0d8b70789072d9cd02cb9f8323db09a5a14abdf3ca0cf190edcfec75718831e213872326a3b4bebc010ad414bd61d5e3ad9afbc9d209fd605414a3b80f42d0eb666a8698c29816423f9437d5bb7a5f935305a95b3dde7378584eac78b9b30acb6d64a58d0d1e84ab44de4b883ab89da0c212fc326881e39970a04f19a3e354d610ddc08e0223b7d9fbd359d6336f00263295a9b60aeb401f20e1ecc79aaabc2c48a180b5bf223f31af9d2d4544cb04bb216f5c927c2361cefc97376f33ba9d5d41a0c8b4310d88ab4497323057d02ac96dd50009eee678f183536b7cad1467d927d2916e91f55d58cef959c0bcbcfe8557d5bbaf4da5b71d3061b98c9da641079d802a2160e6ea94503b8c75e0af28ba718ba802c91a33829919d63aa43244a9c241570da74595da1aff440a713b0b6beda2093304822b1a10f83a432b831a9934a9f94ab33ee5b6cb8524e9477a41796b15274cfe6d72bcd40108652a96b36cc72dc8b71f3a6e3f58693b456c7a20fe72273a40f1c3800e535fd399c739435b82dcfa673c08b978886810a887594e8fd5eafc232375d3292fa06b0b52df20d5047dee9cbbe27b19a9a8a81fad8924e53f212b15d9b72341ce662296f8f9b6515c7ad2f964ad7869910c4e0eb30f7c1568d2ab9a93833e95b8e9dd5db9e4d923de1a6d3019c40fcfdb3a65601c65a7514e4b46c4695a943a5f02bb4c36f9e550fbd18fec5803113bc958a6e3e8ec38dc3353dc94171d2822976025ebfed87ea0b5abe3b0afd45b00791df9ae3f1111de0e3ccb056e101d371b6d5fe1f2dc34df157ae6b979e072fc19ab9113dc17ea68faf0e27989b459cbfd098f225aab7b1f1b1665913490235d82fbff49923f6a360df8a8f92955dc610044e2ad33f1aac81ef83e384afc74a3c1da906fb548a0ada5a5aca40e4fe33c69ffc1578e34049b56282879a23bace25093fb38043614fbc85049c9abcae992fd944331153b9de53ea8ae6694acae6623cb9911a25d63cc23e5ece4f59b23c900c1c4c010120bad416b02b5674e4c75626b6be603afa138233a089c206fb654869c2ed9663dd90953d69261ec865a6ca025143576cdd018e8a06bbb15172ccd9aa94cb9ffab993ac462a6a2e276ae384ca2c4f1a0385cb8249de3dad629b6c326912f4fe21d2b517ea938d230504d3a10c8437873822bbf20ef9df0c75b0b57fe566c4a6ca1d6c39caa71f950b8e5467a575c9d87a2f619ceec2a5e84e708bef9f624268939b9a5d371bd48723b57fb31d668d93ca9f9b624edd3d9c4d0302b8d8ffd043ac95b23463e26c46dff0c607e8145d4ad31818d54d55171ae65eb2bda9f486ae42737b91408d4dac37ce566e664a22d6cd017bf5dcf41add106e08995f203ba94be6ed5c2b9ecb7dc25937474cc0fa388c28d0b2ad44574341168c42a53d1602a4f2d58e7dfb1689810d244d662af66ae06da52de3f12415cb74f900034b2c0f3afcbec728fe3d879c4cc8cdcf5605d5c157ab0fece8f5818b6989540dbe67f0ab934b1077c0b2bbf2f448f3aab217df6a7453bf8a54e956585597d55679b84298202f35442afa9c73c97ea76f0792c653e8a2b33942c7720a1cb7572757f85bbea2b7662ca1216a40480f6c795ef4f41be473af3893e97fb84a7f950ac999b0f2ebfa0a575663efe48256627d1d44f48c9f194e2f8345d40ba32f1c9062f4d422c04d6b0482aeb10881142cbc35c4430a4644296ba257c0d25ef3f1356f10367581113c7023bd66a1a6effcd396ca4514687c9fb88ac5ec136e3af67d3cb1f6c9d525fe373bf268e3587a7a6b9cc670c7a30e2bce60c5425802f4b6da4e29c663b4eb4bd32b28e505e090d14961a23f894c611b73a03bf6003c1efbed0fa1cd564cb0346330bbdd0a9b5aad5ded5d3766a7ffa870dad0ef9531e265f5141fca4d85fd418e537812b68ae8a6ab8d19faed6c81073a90645efeca52a4ad9fa6f8d9de1e8364f1ce429fd755f53aa46ab705f9b833d3bd0df88a78f635141e4d2c9254e1c129af94b7b6cc1b3579e65cf702dded87a756e7ab24f41749bf9a31833822e9b5151a4d7c4a5990d206abed2603b29a8d663cfee114a18f323fcc8c806d8360b3830a550e1165dfc627f835326467c8c957bb1f4c028fb5b22c276bb203016924a9290d35f82089826174e61ab7c19c8ad02a9b1b55bd8e0b8fabd3c6c64e7741174d9e886a910958574240ab0f7a61ccd088411f267243c09054c5d5fc7f619152ee210a1fc4e7a901d119a19f41883629692260e1b23d238f1e23173e1df89596ed1ea665c0030593db114c64afc936e9bd6e420eff78b8e5a4554640d508014f09c08d2b44862f4577904388c0a8dc2671eb4a1351052562a43ddd5abce40ccd62c9323d34f0dbf82b311b08f1ed3338685e868d6321cf0de1b0d9324f81c0cb65bd2eed7699d659a04eb140a08eac62d7d7a6ac2239e85f3ac764755f89deb9816b35755d8e688c8dea57607e452684df4006c559cda29dc0b63596fd19384b4efaa8b93172092e55a752f1c897612385ae335dd502e398a4e48b7088f660611c7f55ba3079c860357d566d29b382d673b900c560e049f4ead38d0c9af20b80081225a84c25b1ff9ac689861d52347e896547e2dc45f9d6a01c87763c2ae187ec8773c326aa6df6236be004bb425577b5e50d30d7f116bca8a7c6395b190cdc86a7769f84e4398eeffbaa4093f96163da6be71a7e8614ef4eab7430ed6cc7acfca808007a55cb626297896ba982fe358ab5fed468ce141293c0a32020abaa0f755bdf522235072b18a23ed3b0280517f566d4e6df4a8bfea51a051c59c78a9936c4d9dc68af8432a8ebaf732e0901544dd90d4fa45e8c2a4f2373d01a6163a69fa77210e10992d178ac1a5ecea303ef4e9a483131080725f8af31f4314bf81f0da48322c7ae332d6f62d6b63ac1e0deb71283cc28ed8082b9a5e09516cf577eb55f05142cd684427f7eb902e8461a41708f50733085d3ce72d75964b3669fddc57a8d233a36187630cb1c30bbfb48fb047d64f5102505b723e4ef78ebd8cc22ac82f580330edfc213f70bd6ddfa1e4b1c6b426c100aec1b4e6cd547bf3eda6628255f2eca1e446c5adbd32e20f19b05f0b617167c03f5645b31c2a00935517583159a5a5d213449a61456f949785974095d1d83a7301ca1da6fce46b8606d80119726cb5c14c237d7e22dcafdb286783a7dc63eec0fe25edcc201c8b59a16fc382afcbfe393bac11b0a944f8a11d5f399bfb2a9717d557016b2a8ea8401faceec0dd332b0aa30c60f6902e34b5fcef9dd348a9a825db14512fe4808a2f71fbc087b268ba4ce017061519b8be5860616816e89d05ba6a269b2c6b067f6ab1c3056e62bbee6e530544076c7b8d37470a238792b419e726c9d730585f4d819d86ed5691c3a31938d0ef90db2daa712ffd302b5b804d4dbc1e7f1d83fd9bdd05b25d067013163a87e8fd2cdd3c646fa813b8831b9550e074cd4b20d23bd99e516461fedaf0f7fd10f62ffab9df5cc3bdf615e87747b59a4eaabe555884e202d660a24ddfde20694ca78624274c429b4cee802306153f2189f6d1c66ec4f98bfc4751feb3641dd676e90362343b62749b5f726d5de1710dbb9088212171e5b2c0e84d4b54c7a4196d660eb6052c20046ad5a79af2a1c86d0b28aef33f018a057e7c04d5e9cfa0738998360a3e9f8231686c10e517cf6ba70dc749515a80e50b7260420648f8417eecd2e727ec72bbfa909d604917a044294933afa6db7dcf41740b75db5f78e23fb34af849f9bdaea7f94104945b09fbb62f788424c379b0e5c8054b9e2e78b801ac0287910f5d88c138a20d933df97a986486450c6d96154bcda77652e47395223fcee7248cda8cabe0edfc8c6e7232eb47b9c0953261378f31d37ccc569df1439f18fb045b464c09b873b0b772479d167306c97e3009e3e643fee1075cba6ac8b01289429e93aa5c15e961d59eac8118d3800beffb0a3d49f660450591d59244d0d3aa5578b4320098357378b3236ce2e1828766829a3940692af4f9eee666b9d7ca5015529ef3b064ad91ee892af915921b37b84c0dc853e06bb1065377a15ccdbac22b59d1c558d33b29adac69698a139bb51b88ce075672fc6d5718ef12b940e3751e006aaec91c709a5353333f09711fb995730394152f5f6b4d52c14a737ffc1461ef934d47b172dd557ca395dcec760887e51cc4f15533d75c3df69ad6fa26d6253104276e5e9e9e4032291e9b0ef608a257b70f773a5e3eaa4b66e45e92b9ae9c64495c93c36ea59192fabab56ea3d7961e7831316aa1bf31aa5aeadabb39f62396ba7e734aacab30cd9a29cdd08e043da6944186682b996cfd32223b8d51b1a02e1513dfb063a76460af6008f81ab9c69fdf75d9b6bac5baa8b9b55af95ac4d09d4827564dd7c38fa5ffa0077890bfc287fe74c15df09d2aeeed07649a67f6d313d45efbfe3e72ac44b49c591bcfe22373bacd474a1655dca47cb0c6f234ead603cf22ad8ef5ee99cfb7089dd0dc1d00c4746def1fdcf0c7e184ab8eb45633aee61a4554094b89c2180d90ac84d774f041d7aa57e3c5efaf295574233ac853d941e77a363a2f2274138e4c16cace41c05b92932984eabd8241f18fe3207b89e7fe3816c2cab3a2b0dd1fecb8ce7ed971f57598ce3ef58971d82fd821966f4fded78c5a60528e543d996639d73a320ff5ada00626b8a1df6da27186819f1691285d5e80b45e8b792adc8539d8452d7c6ad22b38d9e140120186859e6bd0bba8f2e1a37c87cb7d584cad5da5ebec68e53cab166af7565a0a57cb20bd97357ec58d7c4e351a6350cc01cb7719701f7f7f17a207cb7442bd9b76378288a7e5b067420b2ead8ab3d0619e213cb19dbba36ecf56126037a76d6cba0ad13fc0bd0dd7ea3f5ce5ddeca2a0d9d381f823b2aa9b1045d84d20f5dd40f905e2f2096e764b075087c516dbb186772fb9fcf0ccbdfca5fd4e6e32221e107ed7acdd660fe839fb10398077b96a9f2001b640d0b2589a940698c906e5aea7b1cb2b013e566cb2040f860a8e2680705dbc57c09f45dfb7f4978015c2199d6dcf00914b719fb6562e50dd017dbc72f2ad60c10baead4e32e3d201d983423b2977da75043e42badd4861cd3538f4e1918e82f37ecbd54f5a257f0f5695b159af6ed29fbfee520657d891e2b606cdf6c28ba879024f88ca9c5f0582a355a0a055bc5069c7a6f4aeb105c8e81b9708da2dcf047ed1177b40b2b2724e97df609e7d34877ebc6fae22a2e60708167a025ab00a8afcfc49ba4a6e416f670487c40e5209a5f7e659d1e2bb6fa097701a7aa82c92fc17eb2729a6009d115b53d1e2c1e4dcd61ce5276359d7039cf81bd8b4e16429c5a434e380f9ff19685753f211bda77f15c58a0b0f7dba575740530e0b1b9ac7adbd5cf8231b75d76dadc40469a4bd155aac33d3014dd565790e11f04ac79c1e3eb07bb5462cf11dd2cae49e82f11ce07e676d8cc725f2a98ca554b294de72cab5db53c88b1e8e8c57daeb5401abfe04b33ccab9702735e7c3a38e43db884fc13766973d673e69dc124ad41625644bae61aa8e3b7869e5069b628cb5a815601192bc3b57ea72f3b4c928df4edcff98f65a178a747d2a024fe1c5019776f9b2ad8ec41ca9aaafafdd5e56323541a1ffc6566c77f1698bb4be2be4dbefba154a6b534a39301b7980c0ee3b3b4a341c133cb1c774da5f54b3b9f15f59ec29bb437ff5b2cadbe47851be63fffdd7a332a5d3b9371be0f37cfd277a90b9f3399f2702f7c73b414e5ee85f4975e55e31b2eb1fb57fda46f521bbcc6936a3119cf9ad954e8d19c9b5be5670617820769ef73ca8bd4e89b4a29cf45bfdbb124e3a13001a23d6a73d29e81150e7e5c80069fd9115d8a282ea026da0d2112f11b6dc50c5c1d100b539afeb0d087422d19034a24fd06ecc9a02112522ac625cd204b81b5f02b01c679a3e31d94480ba66ae1e6c9cc2789ca04ad234729266dda3572e04f0c45f08c0751b60e5c5e14032e9e9a44ebbd695fae3321c8c08cad87aebbcadcdc2fe7b6d2fccbde760598c8c2c140e9a2a8d75d082a4940921c3fc3a7bc2d144a3e18cfff00e76f47068d91e41da436f656691c4a001392b09a22297641d7ae50a34abc334b477577f767e4690b6ae1b1037ef0305580d5959f4068aaa88fb32f962b220509a94ef172d54ded6f2da42cb756659b44ad5ccd45b751570e53122f74be2dffcfb7dd90aa29af2bd518865221b8321afcec2404d027d0cf85b84bf51dc3af8ba74c34b92d8dc107ca537b18523708ffddba1a7c7552df3a785241b8089397ae4fb161fc6bf333edb936f3efcc961e17c83fac7a88e016b2df03a3387c799f7f18de5377087786a8f1c05a9267393c42b5f9708a8b9e02762dc6eddc9b8740ad3121fa5509144d6894e4fba500e01b0dd16f424a88ac7fab34e7fa91f7a8e578b3c735542e4cc22820102b5dce51419c8051420a57cf887c174c3af2705119a9708cd9cd2aa805e5c913e239b121f4abd5c618e626ec275814ad149155359107f482d5c358f879c9cde29e7bada823cdb1057d76bd8cef07e572fdfc2ec255450bc18b3205c0169cbfdcf067dcf33e9c13e9aa31de92beaef3b1ffd1b77e03bb8339713a6e3be94f78d54e8f17291867d70df95869e10ad9b35ffef2a80031548aebf898d2aa855a44c18fad105e1ab697c287013f08324cae8a5cf948657468cc70d69a802e7f386b78b804c6410b3afc277824471a0a95d6ec8a8bf412184bb3e70d30334cd11081acd55e6f9c26dbb9c8daf6925a919c0cc704529c94fe0d75a51a662c854ee0e4e8ae9846861972ea0582180b14ec207b0c457feb1280014cd9f445eca13ce27aa113d6a3c9400409b50ee1ed3bd035cab3afb48f29fda9eee826bc8a35a3df38165e47e0eb5a8db050fa383ab3017ce50a1b933e4e8f58c16d36d61b75d4a5ad71ab171ee204b2cd211cdbacc90e899bbef76679a3916c02b2c3a934722722e9a76da05679bf3711ccd795cc2d954941e904c03feb6879dac997d6504aeb8a2419ba12318a554ade3827187c4df2c93e0b98e2434663e280578df60cb0dab8c850d7982ad8539724a241e6ba2cefe6cb610d6d62fc34011757cb64607c8c553f7205d1858e698515fe61a68a015e2fbe1420ecbd69e158d7ad21c67dac2279c7edc08f1e8978fd55398a1860d6d506b8111c10f61f95ba990ba22107a120c6dc7910fbf329b0543edf897c5e75004378aff88dd3ac81090f1f4dbe90a595d3d08e5f39d14d389a66fe02136af2470da733c9db154061966081c33de6e40d721c2a49e6ac4d57963b8d9255c3c82c81d3b66f08f2cf388947b8c381bfcc3624441bc525d6ae07a6035f5d59bd862697d382e4cec7a355d6ac26075c50eadb7587ae8cbb383b47f17a1a1b1e9782dd9682fa8dbb12c6af00bfcbfc91add166d9a6d14d0587442d5cfb1eb3a734c883b692d87d8645cbc03b071c5e36f957394758d1a78b81caf1171f169ede81bf21f931abea8d4abf490682a29bcbbe79ece730b8c5d933dd909af9068c3f52f7c1d74581af4f0897282f9ac0220640f06d0937045ff81acba10df1e7cb62b236e1336d6a50181e2112585fbf5475d9f5a0107031ccf4807cb7da007c41e6206a567fb3584031f31b76b0ac010228391006ea262e5ceafb1bd7e88f0e0b537b0aa55c952721a1d9bfed8b8ab19541a202b098b4654dce3d740ee29bdf893ed9d14a0641d19fc0d4569e1a321f9b392711a7f49e1bc6dc14f3eb72d8a0e494c839d0bc77244ebc19d70d9211d5ee621cac7b7968e2a7ad3bc67cbe1e1369597bafd3ae5651d99018a7b6cb84bfa72aed0123297975b2981aa1751aae92f6d4d5c210fd0eb98472ccc639aa5c464541efe2de4b503aebbeb7555fd2a84c2038f3bfe9a8bdd2ecfc1b338b6cfdc5aa8f2482b5e6262158ff5ac45dc839dddf36e76629bd7861e6080b0cdd1475933120dadc3d631563d158c05118f45d620d57ac34ab50d7261c8a11b7f720538a62470e0a4effc499bc095ddd3337c6a81f40365aeb4a2a98fbe3afd85387c8cd82afbee13067c4ab01b372ddf50cb4b8e87eef54f54dddf49376d0522c3ec93d6b90b59f667d71c365b79e89ed486e5f6fbf69e14cc467783055532dec9b173d69d59e7b41fb138336141b54f12db7cced2899c1784d6db97f67c0d18053c7a73ed7813c49b73388cf541ce6ff3058c4d67db5ed2647d0649b704886422d0105eb395e6c93bb84211000ef369f87754ffde307fee2c1392464ed84eb129494f78c8391e49508d7c89ebe9323ba630cb605478bb51c6657f99fbfd0e2f27add890d6ab858d229c0f39971371bdd3ec7dd505cb1858ad4d358f1154108233f7ee3ddbc2368d49da0ee4a77c142bed81e26310eb5de37bae23b16f4da3c336da0bd2d0118c10599cff46571014f887bb92b6d8d7d3891e94cf1e951fd7d44b45f8dff0ba1ce302abc28fb837933a3894296b01ddd1c61afea2fa38c903256d9fd2f4595ba9856243260fb622a9c0ea6940d115577616a0dc7876338caeefce9022705888292c7af70bd98dea6e0dad8a3a61f81ff471eb0614e340b97a0acfb05bf49f311344fac69c2477bb60760622b42025af8fc471c2e26050b106ea038d2708f393edd6c0c90fa6eb7c2166d00d9e527420e3a520a77f0e470087991d6065148c4ebf1b5ad9bf143f3ce470b28279548e504fb5be24fa4e00c5d803215047f83e53c300d0418ff0b51060fc4a9f038f93222b87447b12794cb98599e82c28e13aa959a961c7fbe2906699486890cf372be42a49de1b5203681fa2415151ab5ca3ef7f64ba94d2e847081cb232f1219055830f261ae55e57be6bdb283a5bd96c55400a84278927dc1f495f9957214b4e00841710e08712a4ba5a04109c7267e3f9c2aa77eb3de04fa7d510a008c882d95e54a57aa3da30d2ebf2aad3ce57405dc9164f3583f2db3d656788e9ee05345f99642a035db23a7d4d3bf6402baa2ea387f6a97f85d23dc72d0bc0971c4879ce2586c133c5e5ba373b302f56d44452e79eb4f7f4bc92bd3d741752786896bad114349412b72709da1d81361e43f1ea1baa1872214daa6ec7b5f87a5d8b2fd76ab5e5f35911ccb1ae7375075e0d0e8a3fbf34f667766427cbccd16c4ccd3a9c4b3773daedcc5ea8ca29325552304e23b706ca1a5866cadcd222eb042a54c09e2e43ebcb99af6b81625dc36b440eb1eb79cf84334322c52a44f52c6c3e3497dcac649c84001ae2da56bcb1be261257745dfd80e5f431e86bc92b58accc5a8c4e0c82b3044fea12535808bd798ba77377966614b1d65907b39d0a7b395c099425c1e842bdce3c8fcb5a2241136fdaf523d8b76c552d37185b4a60359d5184220a0d9b9f9c872532fc9e98bc7719f905c00bcca46e4d469a1b2aa04411bf6db32874a3a1a94aad6992add1d4ca85c72af36df9facf825d22209446583d977a30e07a5ad6824dbc9be6f7d66f89dfb4342efc8fd319123c9028c48248184faace8f9fd7ab4b6b1eef9dd32358b33b5e007260fd55b47c5323c207f64ea0588675d512563f7da5ce604325420ac3326c66c542d66336b5a296ab844c3d8cd0a918394e3f7f4d4a50be2514b3cd63d222982286c2b4a88e10136339fb08c72f867a9ca7c157a74ddb2282bed47e0dee18d8fd3048d0d856597bd19344c3e442a8c5d19e9a304b8a3a334f332a12c249a19521dfd3b017c84addcf77b9ec3c892659a404603df85dc3b91dbcb7483b24508c7eba2b2b92840757a2c7734d6a03d2b84f026f79f71e9f0c989147b0531319f842b46e860675067fa9b355ddda5baab43e992d0028d1ebbab48e58490ea7a6e59ad33f851be3b7e99d118b9d3ceece970f83bf9e66e4a5170e5b2870dace34b9bd07e1e7fcaaecd5af3098f00e5571e52c0d717c15c1e821942c7810b4a9d55a2c0a432f184b2a4701daa09e1ba069e4d9509ed5c83f713ca6d46975a0506cf05a90a2f46480e369edeb50abec4de23d6827cfefc72d37efdb688c67731e0c68561cc6d50727967086f48a1a00fe092a095a417b1b3299e76d7e49b26cd3f43cf42a23278c5487b708daa77a36ae36390a5e0af6d50b8b048b289723c24fdd4644609a4b4a7f9851568daa49656b0c641a0f7f11808c7626004e6928711a36f7ef5a8ff9efad5fa4a909cc4b345e82ecb01e9f5429bc935c6d4ca5cf3047530c454a5c9fa63dd636327325fcc4344a89db7c37f27d70adbe82eabcbcf365df45579cf95033c74fd094d3e631c85adced52cc375f6803264f8926939bc4bf8158275126346a63bc736869047447b6a91512be7263676b8474a120396268746789fe53158aa59a31c329818fdef31514513319694b5758e20005103bff8340d0365c3a6a1c5055b3839f365eb1129b8d0fe498676776cc0d6f3d8f9fbc43ab6f7092caf5142641f2f6b5d347549f482a8154cae9a9054ef98f9020a4373b6bed121bba388523a5690e28847c1ca709f090ad97594d9a2519b311b7a0cc6572486bebb71a79fdc1d42fd569b0ed7bd96253425ffa4f126cc4068f173c068c685f4cfd56b533d26a75ee1e60e408432c67591615c9775efa22ee12f5e9f1d80583a3c36bb66baecd1c515271bb70fca33c305a64c3d5a6c031ab7cd10ba448b3e88fde44d15722dd71d698444c36f8e81fe46a110b7da2c0be1631242db247a2c2243112222b161d21887e84a33116ee8a0952f96157fa847eb906c5855a870a9d4e832ab472039b4d079ad5a3c1d549895108a5f77699a42760ed680ce9eb61c730c08298c99764fa8b88823f84442634eef1d519f83d4b94ed78ab54c6353ce963a6c7a20ff8ed5fa0ecf620c4950c92fe89861e9877f63ed468567fe4fc79a64f47b36e6d891dd5a11a289d89fe805176a8c1a82c1622f60ca36aa08bf1322e95b92ee61fd3eb49c18a57706ee454c35ba28dc972148b16c454a6d3cd5950d5574ade5350657bbfd991bbcab31b310c5d1a0fcecbb768fdfa621a2cc074d4fe22c55b1c6b26c379efa74997ab986f8381f271a72474aea2b9fee5d356b71cbc98c62137ca0c5ded0b682dc017cd67c41b6478964dcf4803d0c55d0e412305809eaf4f1ca26847c7a452e482437f5193ad7d19f167777c955a4445e57ae3f1fead41657d639074a912c7449042b9fd68e94a29c0be37a8bb1de7ce13d59391cf7573b56cd819db1f504638027bf092a971721373f6e6e60271a2ff4feb7f7d50fc9275397af31a81215b3cf79321b277f99f93db4161429ff1953909c1dc8004b10a920110513703827545060cba8e3e97db8db87f12230b910bfc2ae39f7447ebdcdf8a2ee0b0eda1f847be7205ada5026846b140be8aeb914d3c47fba1ea6423388da1dff81144a983f0db8bf87c55dbacf84530cd4509c22eeb268525ea3882989a5356860673950254b0dfbdbc08c0a1e702396779e95b11c226eead67c92621a66ff7c7189ad03bf75901ba2528f0881ae900fcb8d37ab47f6fe06e3be9c43587236c2d642de14d99805f1c4e569064d242c96c5d370327c1c3e029270fc1e3d3e49e8cb7cda29939195e8c8b15709955c00094c1f04b129c0951a5ca975ac5d1aad0c9030d955e190cdfdbd970095d18c319c7d5ca35136aeb2a701e37e40b8166a3706f421e49b91f1781e7b8f1509934932ce72a8febc61f2107874eb55712b340628001cc7db577b820a91307bdc87c09d61f747c1ee8b28fe151ab1691eb414fe807505e721fc9fa21ba51ebf9f50ba9a55f7c6122038b8763745ad156414270304db2e4a35f95d13b0ac93517b4c3e2217022e8c65a54d9da44e4a91d2d2f0a72c54422983e8284a155ce0d182f7384a6ca28801382e484eeb344632c4a63abdea380961a65f4d2992f202b65704f368163d758804514993b96c3dfc9955e353d02b594e376887b1fe104b60547a5fa2799753312b6fb9d555eac772064df1a2b7a31e681f31dbaf05fa903fe3dd112e62e9fec93684a9368139cee6997f593f9c154a5c12b02373e6e301b2b3e56d4cda24b756c3de3402a9ca979b1493eae1733b570ee6cbf07a48f693a345c67314d05f6f39c40a703dcf14b866dee443f2f3aad0219aec4cac2b2372be619b69beb6a00ac1020804420ce8f9a63585eea40275b555f29e5af47d7a8101e6aac1184f050eb157a1430f75733113c7b3d4c5760206200c408b7174aef0e4c049278e8951bdcab8a15fe4953999c349caaf1eab00b04eb2afe96f0ee46d84b0eddc6f38d90f3c8ad2f20516f40ed784632be4470ebf753201bc293b22b1467160862ee3f11cf0b850fcf0f9152ebdf1fff8334a27d228888851364da3057819b62e0fec8969f9f63f85ccdbf2134811147fb0366114619b3ca98b1cfc191f4e185de21cd0794dcd6fe5d90f8c101a3e1d6134db68a4916616d4206ec77d4a1a3ccc761d0bf2aac83ae6694c29210b75df37b6f6b37442408db26b8d36a30556bb4d99d50469bdde0fe3a844857dbd8ac60ec8ab18d7b5a6c2a37e9d67c84fe4c7b0225aa3f3616692a25b1486f44a6d29a42229c3a385274e31ad92671f1b92f24b3c4bdd7189c2c518dac36df16e32976c3a77e4130c239edd18575a1c01fc5ce5a82c3027f00b8e77f03187b3821c51ead59cbe6957c44324a7548c17113f03d0223d16a822ca414f1b1ba2a9514091bf4de602a254b214fa30689e0dbd7b7b5ac0412cf7a2e42496b3474f8acf211a2046c10450593f1b0d7586d5644453f9257d7120d20c289c64d7d5d2496af930b7b8b4901d8a852d668f165d10c9b95fc566804e2561705e21551a5283f1faf73a6b08da582eaf656b9b3a27960ed392b1db68c173c8f002088f9a755556b467134e62481e45c695b56a1163c03c0fbf88c2b49e929e06368e4554b625e06b38146a4491a890e5fb374ec361ba93a1b15e3101ab7d336180779cb69c793c7eb58af40c062076b0b6e3500e98bf11d9a939ef8cb77503a7b61fbb57321976fbbee949e50c5952362650cf52ca246c193f7f8ccdfbc9e476014bd4321ff786d9d0b28bfd134c84f33607cb1510b668ff0c04d09350c0dbf21f3fd01c2d01df0b12ca6f74a8124c87c7bf20b53b06a146d7d2acc9ec88cfaab5711f4505c33cc80df76a58589f7f9849c8f723d116dee6d7d673f56fd559d5b76a448818c923612f77100a9b2071cf2b932b489aa00a28a227b61a0aee92b226f83940294036ee587bfa125885a1b51526418e26d32a2ed43a69a04054d0384f4616982a17470ab94415f6a509760fa613e4eb00987ffa1dead20f60b4a12984c68d369634a80ad2a3223ae9e86576c29e2477749256c1962ea15d024fdcca34acab32881cbe85daa7a129af5065942caa2b822c4075c2f2e3fe1f6e5ebee3b6bce6c57bdb521a4aae0f819c44277152de237f16d27d9753d80aa584e385ef3094752ba7a0e43456613aa18dd3552d9c56b386b789945c31de01341ea68f320d2281b4e8dda74d524431de4ec8dbf3dec38850b027ec596f8d4f926e357f5cd892326e522a902b2ece9c04ab6d21f06aaa38c8dc2df04f1bad9eaff4f137a826b96520c436cd49e6910312542afe80c29e696a58edc8988783cb6e6aaa168845b14b19de07e8363125c97a8abdca943b7ad546d7dddf387e8a7e89d00b1cd35ac97ce9c7c4777f139c1e4a18a1423b14838b892a96ddc2d8ba254aa9c16dee5eecd0e0ba8e820dbf1b80f0f5f3c868fefb5699ae49507097fdc97ace03293adfc5731343f4a680d8c3891de6b30e8eff323463a64fbbae26a0b18e1d63151b68f5c918703b26e9430f3ea5b6b26b8339106468829c81caaa7d4c7ef00662de87a958acb8de5c600a1bc43cb52e3b9429fbdc01f9a03584c7bce957902f564172571407b36c19a5a8ba5f379cdf2b8620f3eed6b2e37d862809438206c6dea62b8d06a9631ec638f90a8e0bac7ea7b6be05a9106873ae975f028f4f501ba3b90c5fa6e703140d668ca30d7acae65f6d61b90f719745b2f2f96e5bab340705d7a86acb9b0b93e5ca3fbab80419c992dc6ae784aa197fdc2119cfbc4570ce58520777628391c5012b68f053d24514d3bcd353590f90f00b07c12a71e7a8484412502fe806505449a6461459cb57c0c65737f17326aa1351f9d2dc79cc47d20e2ded2044f5616d85f72254ea3ad85a3afc346a610eeb3379c81b1174f923680a90da52be45a28a21bf87caf3695281716007d55f90115a894f623c210294ffc581e1a9f121f0ab62504d68f466c7e513b2509fe197163ee3b1e8037e0ed6a4592801fa928058502aa96cdbe8cb3bb49635c3593b8547092b7eea8d721cc21c770425f0bcc7b6d58d0fd7928c1e12156b045eca58d6454390002a8c81d3d51478e3d536202765785fb51b0a63f4a09bb6ccea31075471dd941fac98e750b3f0d745a4ecaed30680b364d0b85d2a38961bda81c879f132b3007aa1ef54a0bf24866512feac7076dffee45fe6cffd9b6079dc40e16645edd49f4d7aa3046fb97282b3aa7aa27d26b812fbf7b0894da7552ef0b7c5ddbf3c4f2ef4e6ff061aaff61b5873afe5b90829aa085d3f8a05fc351e8363a1c3d8b72e28b2ff4510d425c68972f33e0795a96ac208e2b7b688fe493c877e4208c66f83e117e152da6a5450a5ee5d704b84ab1a2c022c6b76e0b99d90011d55ba11daef8019da1a7ddc3bc4e6e87a9a6c5a60f66642e3263d383e13d5e4f259908fee96f164693d10089c55523f834a7dacfc69280b4daee4b4182603d3b81aeb62ad4a3452fd6ee6c11378ae2fb0b9184e31eb3b779ce4191ea6fbce50f2ebb7a83820e20a64d0b18063eade90642f9e518729c59b44a4c992482b644cbe7cf1833c462970dedffe40bf09225da904810a08b62f0f7fa422397f8a4faeea94d1cab261feb2cbba3479fc5dbac7d491fa7c6f87c64e73ba37818179d9ecf67f26b2a11d2f9ee8a831b33b2b1c514decd1dd84f4921f61c271c79aa319d62005be196c11e3e73d0114dda515208253c4bfd2b5a5224056bc62cb8792bf684f3d606a15018a851d1fd1abab9b5bb0bf03b8ffbb4260ec103b6f9a6c0d3c8ea3c45e47cc42ce0a095b34aa4a9a49d9c53667b8a00311e2adaa00e90000d40307df35ce2af54da7db0a2a587036d719c34df906c4d24ee88410a43ca074bf50cf90e27323093a7e3e9ed5e0cd7ece9f203082c0c53b52bdde8ae3a44bbebadec56d1eaf9584dca8c8db71012f8a98af2b1021d7c83ae05025d8d9340a8531919b31086c48ad4347f751c7beb82dd25027631188327384aa4d9bf81d53373a649b38c5facfd80392f9977f72f924611781f59bb00f0885beb756b158197a23c92007de7ed39e8557d1ecf49dd4d14b0ddc94e5bca66b86320bfba52932b9fe1ae755ce892378730d30d07315ba2392bc4e895b0611f589743ef50fd4ab1445cf9aa79ea728d0f1cc8ad3d3634c21a61b05fc0b86ec5fc14be31bb66c17f35d1367a5f62ef307645bb407b6483c9f35c300d8b485b24a5ceacbd73202884f9205d6a6511b1ae94ce2da64e1011a8b8dff57d35422c6b317272c40aefb414212ec0aa525051f6131eb443be03b9c3faa0d3c2026f413b307a9d56c035abca7e8d683d3ef29c76d39dd69b46a768a748a75a3b8f62bf99038683115c08a43c95317ca20976c3b403c2c26a621cec62a82e9c4c22262e715b6969bece155b1efb0e60fa7913c3c30ec57220b3758e0ce5b400862126317c25bf72dc9a520ea97610d9bb0818190d657c2ecdf5ffe10e0e642f4e92b73d781769864f381b0bf551199436997af111bfc52be47b542b9e1af05ff9a81349c705d419b49d255cb08b9bc14ea21c9397c2c593ba02c4db2e619c78efb828f6f9b2504d1f7bf6c19d66dc3f5c2e159c4b0fe14e1462b728dfc7dcaf355eea0c3acf8dcea7b51ef2116f8d893e9e302bd1651cdecf2550d3a9e5f5fbcb9aa0413877e817e671f05123008ccd680bf681fb03c15a7261e935ff1922d6479139d06bfcbc8e8400fef123760c0f6cab874c634d382b3854f103cd2d9c3fdcd6ed5662540f944cf92b11408be17e161356ff97fad0b0d072a310ac3301859428a686fa60feeb13c6126f7b4962b51f7b2090917f7b9b723c9046aad2ab907a2f9430762f7353ce3fe41a5348199cc65e0cbd4b8a011f3ab0d8a75d475359b60e82f4b2ec1f1db37f0055124a144eaa9feb3bb839674dd0043bad5dd8133c98b3309bdb396dafa7cce84982be34aad977dc24763522d78812c650c77a8096aba59b813baf8b3dfbaaf5315207c2b3babbf6f39cfb404a087d846b972cd8b2903b6d0f5d925e0be91a082ffab11f960a16b38c794f9001619b4f50f7430bdab5ad81e108d5de970279be24a02858582b17ea00ce0c1631bf121864b5a4a6b1c6a7072af5db140805ba08918a832b4cf530560d1d96fb20e24c35f3acd99a502d11e34b16222fc011e03818e69b3df611ae3ee6657d528d2ffc006aee6a7bef91524ec03e033b61f6a10271eb84d9a4e1c3d06319c40345a528e64444a251720cb68a18cf2fb9e7afebc20c2b5e5e271fbeb8d8e1f1d23e5d6b1830e0bd8414e5d89603f62a8d30066573d07ef38f235e89b110b8b24f048939385979c671b1f68708b6b68913b6a0e7b13811dda9ad33cd893fdc78f2daf96e3999bcf726774a58ad8a4b13b1d1811439dea2a45a1ddb73bb686156109905a81cdadfe62a9e50f8cb2713eba56b2a43e5b1441f24d0843e41a65483ad23e417288af689e2518f440f207184ff6210c97a9f0177cd34bc7715e9f6fab838ba4a086b911bf9e6ac38f5e91682442e1fde277b6d04c57700defbc94723c7acf8e94391aa621f279b64fd94fb21f7cfacc767aa8cb618e764321cf7497be9b37f72b1831904f4ef032a2273a35a20da15a903dabbe387d562b8beac141333e58d876ad49078a78feedf2bc8bce27c058301981390cf18a372cf0aa0583f00a046c5b113c0a170b076a052a1ef83922ca5fe299bebda12f08d8cd24a8b108bf6de7bcbbda59449ca6f0afa09f909b5ba153c70eb6a3b5dad89e9148b59a9e710dab7f3c89ece9ab7046d4d6842a1200521b77808e3a7eb2e7904633d7505ac0e42183b3a44a4342122b4671322b95dce2644707b3621f2b34bbb9dc80e8820a191a200410803053d8cd8e991ef9e36238c6c0a13ae4461c4a28438a16497a21348f66ce284ceb7671327687b367122b64bdb84159a80b2298a0503895ae7ac73c6105e2f3a7f8e0aa041f40cc3babf3a87c760dd4f5dfab02707ecbeaf3a00ddd997ac64e9438e0cebbec6ef257988b6258f7453d3d806123975a7a24911284d8a00edd9a488932deed9a4c8cf9e4d8af4ecd2362932830deed9a4c8914db18fd20e6b8e66a34274c708375fba0e690aaef025950823b8bf78bb71d5a9e82d7aa2d4c65fcb16bf4e5cd15b043d9145fbdbf24b62caa0fe561c3ddea3952c5219ddce60529104cdc29910415dc20a0da38455e5e9cf0f4bbab4e79c298d9e3edd99332c4f9f0acd9995a7f42b6dcee4a75f737346f4f47629d8fe1ebd62fb635fb2fd3f22b63f588d9052623b6987ae0f26104c9082ce09f40a4fc90f4484234d0ac315cdf26158c515cdf2a35ef9f0963469d4f96fe7e10f0c454f54d1f62910ead314ecfaf48a5ddf97ecfa95885dbf1a61d7b74aecfa35ded55f6d1fecfaaa6d825d5fafbc8a4e7d5f764ed8f555740bf8a5a764d7ff44158d0aeb8f34e955f449fc120761d7d7a63d7ef925b1eb87f8f51ce91b4d77a60caadd3665d017e94a9b32e8abe89a9b326849cb49c5a64994348b4d1fd3f774a7cbba844dffeab7df88864308e74f1836227c368dbd74992d507f96f5ddc9a69b06d9931ced4fcd016b8368cfd2beea00e0b6240fd19e3fabd831bb9c53b6a638d90b309f8e3c9e4611f6c8bea74f137c10ea090baba63d4092030be9123d3958e834c8d5a009d73252c0aa5e41a1568385578a1c58e82dc145102cf4c4aa9d0851fcc0c2af0645108183d12762708385601584606128564d77c044102c14c5aa7de88e48220856b50f33ac408921988a58f50048c0450c1666d1f50c9ac1aaa640b42a6cb0308b55d39e223458c822ba5e0501ab7a3a51842584e8124a088251253bf8818524b16a6a83190e1696c4aa5d053598c1681744f4c0429413414a100e16b6a862b070e574ae6811584499b2030b53b4064d703ca9154e086ce284c460a18bd3e9e2d38428b0aa1d0547b8800b1b2c5439142b7471c309f12846c066142362b0aa553158f8c2e97c6155ec1bc10843b0aae9135338b1b3030b575de848e107563f8a11b04aaeb27f9844758009a4daf463b0b06a991844f9410ce6c384c2065cd860d30b5092aceb891302ab9a2e718221dc6014ca100d16be10abf64210840716bee720091c1bc5a181a64038192217a30456495274ad9a0256f5ca4f096e301ffc044982602d88558731c4aa7d984f34a9edf8b0120256c9e905284518302d901a060606060606fcb0f70203030304e61a080c08f5c13d2c72ebbdd7af833bf4dedbdd7befbdf7de7befed94dc077beeb5b63953daf73eae2f0b4e286c1b7f385b395b67b78638ab35c369cd681b4b71166ec5364b8a6ce34471167e285b68e3ef71dfac2067e197c971edfa583919241bbf4c6d631610485bb6a01c2ba4899f202d5bc5ac987db264cd6cbe0fa7d52013b93cfdb860991cd5503522e39f7bfa343164df5dc2802b4f3f3bc85dee4d4a979c3d7d6c50367e189ef8cbc260c059d92e658c90762993d343874af51366b54a478ac5f31316ae82983e3503fcc860938167e31c197a367e1cb93963daf80f509b3373e31c191fcc978d2c260db539033a61052004a962e3e923c4c65fca2861db10524226c0507dd9c0116009d365238800390204d5571773d9b062b2f0970400dad88301d7bad9c0d5974c8e8d2077c180b361c574c170845026a7be5a3cb5d5ea71d611a6aba583c9fa3e9d7c5f26a7b64435c8a54cce8459994c8ecc8e030097dbf83d1db4f1b7788474309fb47abc081b3f0086367e9e9c5d92ad9e4dd636fe106605acdcc67354000f74b3868236fe281b1320b7442677367e56cd922a1d9df7dd4f98c7eaf961fffe84d9bf24ab23593bb585af2d4f3f2bb95ddea01ccec27f132375033afd585879c241d133d4c5ec0f1bff2c6f1429ab5dde200f4d3fd764f34893adb6f0d5a6a0dac2569b72a79deffbb43ee99c8c84ae4f3c27243567b1b07c7d50872723559f744e3b27232394e8f4233afdb470ceefba1564c3d94c3fad5c6de1b7a4e9a70607bfd4ca260592108c5de472fae46cfc612ba865ab2f1a6ab5853f8b1f523174af48ea6823539b76b491d909afb863e48e36323a5de779187f1f08e68432f8d8c8f013ca6013c571545111e988461b991d1d233248646a9367cac8466cf278c36e999d15911484bcf22ba38d4c6d86dc0c40cec22ca38d0c1267e10d94a915e40b8036191e67c9d4e68824b280e07fdff77d9f8c920bb046a34c8fe94392681a6d6496380bffa74b6f1a6d6494380b7feffb234b066c999ab370a964329d4ea38d4c2b575faa1cd692ad5ccb7bdb0202f348d498c3330d914b999c95530bea4f40a796969311eac28e8f4e0bea6403f7c45d36c8c67fba9d70b5854f3f4ac8e5f491d9a92dfc57cbf0d416feb0525ae78a4af9810dd2507317908d3fb4221d6da0a1565f3630e853bb630dac6d43e804793a21b4f193339256dbf8b14c0e0d357795d3099c0ddc9ca11bb76c30e064727ab069e009491a789c856336484309daf647d6380d46dc55c9ef7368d0a181c75d93b5f187d357c2d30988a53a9135f83e853eb22d06099da4c4c83901b4f19f70f56561f84ffa8626487060ce94a1e32e1bdb7117c9088fbb5490c8d4dc357afc324adce55224e0c659ba9451b2f1977429d3b3f1937469b26dfc235d9a721b3f8b2e4d421b3f95d1256a8fba875dd1256a67ddc36e912e4d46362677380bbf4988a79e7c72f5a4e32f0cc31fd2e68c69a8660adaf8cb9311a15dde2015b234dd72dbc4c3b3cbd3cfea565f7fca71d74949b6d5d784615cce6d5cae8270b78d7f362e0db96ce3136de332ec61119253af7a7f316894924b999c8d4f4d4eb79313999c50d4373b7c9848b5c47ca4c0b941f5f47383724277e842c137cac6578aa84ac1d8f3edc6787c8cf1e3788ce7ebb89ffa39eab0af226549c007e26149462da323b3436dc2efcbf9f40540db91d4d7381e4512d457e846c13239323acec29fd23273bcb941b5853f2b21fb9f7edc358174cbe62c0c9e7e367e15087e7ffac17f6a82ff74c37f1ac28f92e13f39296138016d7c0adaf824b4f149cac6b88d1ffc1c069cbbca960d8627f86bd9dc55cae47c323958266742616371034c01e7b6bb37cdf2b45f82b51db056c19daecbd913df9cbf3ab7ddfded48db35c2849edd5d255ddeddedd91d15ca75618c0958ecf236d9dd638fd6ba0795740f4ed925b804f4a9fbd2ac6d62f0f683bdf6da6bafb5348b05cd082123848c103242a8d62db200852748b9b6b39d8e4e0764812e12245b6c51abd5786a3c3c75869a6a8db0d58a196aa4d4ec2c6c98ad32e8581974aa0c3a3668a85587065bb78edd3a75ebd8d45081ac7082ddc2083bb3d6083bbb810301b46a458541460d2666b3d91f01c36c369bd1727670684ba0311cda4c071cdaac0a55a0cd68334f0b1cda8c46842734a3cdec86d95b3b5abd9d8dc2867501b331c5060dd5d610a935446c0d119b1a88d450ad0d22d506116b83c80d1c1626dee33e4d76fd54cb11b2ffcd0ed18be1b346cfd85bfe76bc11a9fc287e2803ff735b6ddd6a2be742ae82bda7d6a195dc75962becd418aa63abc181a2a1667328765bf972e2666e064da195096341d5179dd11ef535361f75127b6b68cebab67afaf157b4f8242d7a16adf25da9542a7d772265a35f79afb39dde56dfc068ab7e78bbbf5f9ae2071e5973bf9cb6eebdbfdd7b7eff9650f555535bd5e69a4749ac9933e3aea83fbd0e147922555807c6a7d711fe8904bf07130996f084759a3422555685bc2b1c3b76f071cbc56a6c1948e7a20a56dfa27a79a7808f587d8d3f31b65a0fa5e5ecf07cb61f1fb55577d02137c79b5492cea6b40f95cbcae67e484597d5ab7ed4aa544af5a95fa5542fd23dbc7ccb67dd43921c2fdff23dfca7eeabbe95caa525e52d644c8ac40740c0fc993f6156922d267194f38787ea236f56aaefe58139f4913ebef0b3e187eefae1ac9afa962f6be8969bd54af575f4de750f1c26ec922a7f1d2f3ef53a5cbee5717d69f2bfb79a87e32afea0342dbcfe1c2dbcfe6f819cb565c91cfa49f2c7cb6bf0b5cafe8b4ffd8b56f9ab2c99e3c5a754395cbee55dbee5433aaafc75a4fec5eb687997fb0b78917a173d431b438cb6539be5f0f4f818aac5501490cf4542a8fde1ae9a71ce28d4d7a0dc0e530895511f53efdf1aaafc1cab40e1846132ad48a4d50e1dea0bfc989dfc33680a8d58f228a6eeb8e4dfac24b1dd8edae27156fd2e5cf9f14f5a7c162dfa915679d393def4a0268521e9c33785a4fcdf7b2ba40cf5a79725b92f0302c380c8b0f065b31858f8406418e8d9556c288ed21d3908793f14c7a9c74c275210b4ebdf507f684c45a1dc9bdd77745461b0013b38603e613261c7c4ddef2ea901ba3d962e72f7dd8dc944ba0943f06bdcf5a35e36fad37b9a070ba9b22f928d489565790d00d976468d1e006dd50727254029c3ae3f6901ca994bc64c19f5c318d5024c3ffa1da46f793ade9476d0d868e6a51fe9953f699637e9f0491afc3266e5519f63e551a55f798fccc1f227f2c7e859c237bd8ef04d640f49728c1efc1e3c12d9c308fcb959b4caca8400c92f93895e367ef8329597890fea90011ad3a1ae2d91fe76e05680742eb2c14e11b28805b914e16cfb4567d3ff5d8a70ee9a2253114aba9f984e0cb16d17d9dca5aaa16efaa29f2af211dd3615f1e028602729e989fca2a04d45b8971d213021a2c99e48d893c91034d6eae7c7d89412d9bf8e3fe98de25afe444da41df54573413b9ca411732326e97234f51732bc2c60c24a1f52f145a6fa17aa1ff58b07021b1f0720b091fc0104f623498e173fbeea73fc60c11028416023f9e3070b864089c15ef4c061c24aa42cf52dbffab92263b28a8ca9e5194a6400f4458bd056fdcac202825b03b4c65d08f9fb8f870a7cf1313f2326c692944506f9e3e16f9f0549fe7e8e3dfc2da901ba6f0c3938abc6947c770fcbd245fefe9b42473b7a8fa3be5cc8596fea6b45ceda0a3f09bc72f9b265973467ceb4ccd9a238d855896d57fa6a1e4ee230613219ea532f3b7dcbe33061a50fad98c303e68b8715a06460caa89fa764dfaa5ffd8e17fff273bc71c171d134e745d3156d865ff6f22e3d54aa7ff117dfa8fed353b580d5ab5ec7ea55640f4972b8bcea7b7859bd0ed5bf207b7021e75f3db7a7ba51f9b9c75f699557e9f1533a7f8b163d4ab3fc49af3c498b6fd2a3f75af484a1f4849df48499f64d8994c984883f7a998ce565f9555eb6f232d18f36074d7734ad51db48d35bae30163d612bb031a77444d661474e2601f93c2711f11373ca807699270542f207306c5a8b1848aa0b780063077a8b168a686992e74f2f0c2d89d278cc942497a8ff477d0cac4aa3d6175f90593470bcb20fa1ed3ea0f808dafe3e6afb02de0df7ec505b9624511a7aab48d49efe24f13e326bcb0699beffd46207b29298ace5b927a5428bec64fb948104afb9097298b8797d203da8aafb3d6a767d207b8e365475491bb605ead739839d2ff75eeddb279db25d973e04d9fe934e3aabcab53f79d0297bd2ed5a557f6e0bccaf93b453b6fb9c32701155f797d278104a43afada61b64faf84becf9e5c6cbc6248f979b370f2ab431cd8dd555ba2b121370bf0775e9bbfe073ea6a37dac67d873b4e17ee97bbc31c3c6248f1976a5d6ab55d1806f0e741b93ef911d69c3252d893fda15a1864fbb6f35aa80317bf4650c9b65b268cc0ac3db795f48da31013f76891f072048d4826c618227920c410930ac755903fefc234d774dc57a6e92460565fa2a61386a5b431fa2067f862059fe4039cb49190b570a4c6411b4bb57d9d0fd7d51fd2f01dd7f259db2f194fd910ae81e6bd1f6ea0dd1b6240fd106e2518a03da767bc597ed30569d00a8b8815ff854fccdc31fe882d81a5900b8b7a7d27a44904603a4d517067768b0768c159616662331a3aa8651926b381a6d6e2555910d49e2ce8e9e8d36375b5b123136bdc0010e7ab60b6a458496946a857279617a29d9d7b49c4b7396bb4012dc217b5a5c692e02d06be04713ffee5c2566ed22e9e9401a38e422d9f4f28821485b58819163d0cef360c41c5125a3ba15d2a3482b8fc2a4ecd11d0260fc66e8bb6a8bfa664bf43a1db2441a6d16c6c000602fbfb50170a10a40ccfe02699746ab229779e697166367a4405a1656684fccba608c67b1694c0dc18b64831abf000386edefab9fdcc3ee558ebb6438a1bdb05dc75fd85d2e6c37b27db56f8e0fc00940adbf88904bbba70044b40244f1f104294f988207272dd07d4dc025add39c5c9f07cf936e7f7fea4ec21bb37e587ad0ae37e6a60f64d3727e9ddf4d20fb921d154047690e72a217f9ca398bd29ccf2797a22c12a9d0d702a1fb39501af0adcd6e4ae7d0149ab91c04280d4896f442c07a0ed42120b4adae5145dcfd6332c605d9ffa77a248e7a1fdfabc3b410b8b7dab67d81c7d3097551baa179ebb8e8b8e8a274421d17dd10cff6340574c03be470249734b633505f371337873aa18e8bea3a872a8517eef5c4f35eb90bdcd92b09af272fdc2b09afdcd5b9393d53c69015e4f2f64c7a67e5ad4dfdc2bd92505ddf21a0ad6ae14328d00e45b719a5b95f2b90f54a1e2a1da2a3435c4a67960e418186ecaa7f658de472dab2b74431fb1094866e6beb2d0739f5bdb794c6bee7594bce996dd7915de7d67a90cdd99e2639cb2d0d677d2cc879d650d63f6abe72575d53bb85fdc9d9a1883e7629a134187f6973c45ae98dfe509b77f8521bd88e2ab136b039eeaaffb3afae4163b4a93bd4d7bcce22a9ae4941de01574b96a85de2b0acdb2bcf16b581cdd9558b76a7e3c2884799ce9af6765f7ad02ee9771d59f27037b501dc94eca800e677e30defbb4a6be751dc457ffe35b2b1a63165d0fa596badcf69adb5411da5a17b46d973bca475d6ec41f5b1f86378f021dbee9207ef7af7e7244bd2253fb2a4dd680338ff8e37bcaff5ed0028cd103bda4c9f8d358d189c756b7c49e49b8af19c13cf18e600280dddd6569d63d0c4cc898f136e3e403edf4e8dc75d24ab4b3adb752681d2d05d6b98db73ac6446f9ed8e8052ea01a803c480085c603144912c540186c4a72683309358566238a142a6c6f4d93e9b5cc189ed506001070b339022440a11b67f0c8d23b6fb648109db7dbaa8c1f6cfb721365004c3154ab0ebab4831a70f62982fc4b03d88b5fd71a66d7fd45015dbffb1d0d9fe2c22b65b8108db7fa45123054ea0800452ac0087053087012d07eeee5e16be91d4d8f5c39f2ad056d8882bba26b898624f31c51b6d7c1405158a00b3fdbb112421df79a38210bbbe153d11050ae47601e054a1e2eeee360a0e761de1327dd21653f6cc76541026a5734e2a3292f1dd22365ffeb7d2d16f125036ff097b9ea7050ecea5795ae41c61711cd11d21419d5d2d72800227a17090e028c159e2ad89d2a28a22b488284548f1d6dc25ce3e5a6471c40f0e236e8ec23101a2273c010bae88bd302102172b6451e7c51272c8f3c4154b3471c50f70704510565272b7a7cf15391b206a4c84e4a6508489902725b0face6be79c77de17603c8a55b707a4e3513b72f2a8d66226e8d417406d7af19db5de492b9dddca74d6fd89c4efbd8f474a9b4821dbb3be1c45c93286d27a5fdb951ba45862dfd2969f9c63bfd6da5d5b3d2788ec7b1763dba74183bfbe18e9c9b6372300d98c00b48f20c42e6b6cdbd17900df549c5d37278929cdb7e7cb9c35f70d7653d25bac0ead5bb9410aec2edbc5f6485f421eed692bc215fbd26837249c13c8d3270b297bfa6401659761cea6ffc29e3e59d8c29d4d5f55dfeefaaafa1386bfc428d6fce968605ccebd1c17335f98e766beae0df3501e67619edaca0d522871d6e441e22cfab846621e67d167a122971198b2ed8760dbf79f98a7bec05b6dd1a7377dc3144a9c35774a28fbcf7d7fee7b7dbc652bc59c2c718fc5d5175662b6e8cf2087834d9f898dcbe184308f631e27f4419cbb449b7e1873d7dc94b640087ce2aee92384db94478495a0d0a6e0d0a6e06d535809e6367d5b9d07a746eb196223f253e48684eb6cfab4f42a367d4a6f0f367d5a7a50367d8a8fe02314095d42799a388961979407080a8df2825d52299b2ab14b976d140bc50241cc73a33d1e14777942de9027e409794378e609e11c1efab7073470d0f8e9725dd0b5d9aecc5d77766797766777e7ce6ead0a1f62304ceea8ad1ae409c3bf436dd19f40334807fe8f54dd7c73bcc1a46a3a0b088c67d357422e1d57e3a92dfaf7a6e3ae9dfac2b5ea3ca8fc28164feef9c13cd786c49c71af9554d99f56fbf85164ced4c7c00526900d9754753ff79d403bd5d18f592273c69f02ed23373f88a64ce92206c3642c89bf0f130896c4bf921667368436c4891124613b2d47c771dca5a23b469c451ff3609e4d6bb8f3c2493867fd8d53c5ae64994372ce230cc56c0f8a71d7dcc15d94f40cec30674e35f79869851c4488ed22d85ec52c6716db6f4c02ccd7cfd6fc2ab6ff9c4eb3639cd0147096ff9c4d44b4099a3f0f582cd92e739b0f164798420c764975b65f3b8132c9f26c3fb2cb1adb27490177cd7fcdb07dfa60e3b03267ec6f8c31ae2d18f599a7f1474ed2f31e7bb6e4a1ee5a4b4cc33b08e74acc5eed1d6d68d4c016060bb5ea481e0ae3bf21da78cae83c0f7f3622131572896fb86c711c6d52478051b2545249c84e96abcfa3cd5ca12b574575806efbfb76b264c09e7305a20d9e3274f982375d054f19f3cb0a6c4a8255989462209bee9a0c2809b8d8947c229c30e68442ae7b62304321fb68c384a267778fc36ca9be95b3ea875fd834cae98332f564c75334c1391dfcceae33ca59d5da7f5be33628ffef9a5b86f2574b96b94e18f51211ca92f4b80f333d104feb0b25a2c6b716978ba1316df367de6eadb5d6ee3dd0cba9175db78834ea55740b08b63cf828b0e5a63e3d65d4bf9aa402a84f4d1a86b7ea87f8ff512def3d8ffbfef6be6314aae5e7e84d4d621e97b4dd76ed6d9990d30b31bde779dd26b168c4dc6c8e790e4621b1accc55163905e83bfec1a1e27d60288e2adec571c371f31b46f845fafa2189e5f3d3b187ff68c432ca58e7d14f98bb7af4e001efbcc5d7477e9fad96c0044a5667493ab0e4a7a3caf17737230e600ee4ede40dcec2dfcdfb6161c95f47959337dfe33061df8fb42c7c9044cac417ad7cb5223b1a597b03a5f1d9ae0bf89ee593f8ff58c9813f933f4424bef15e657c317cf0bb0183a2ca178e1eccbbc1615d1cc14e76518dab548d0f7518b1984e3b9467c2c44e071dea6bc26a680cd1b0e0750a80d6512e2d1f5211a593ac5c3ed4abd58befb26a2165a737bd0ad7382c242d8d24b99bf1c00c7d7d600ed1984334bc1aef481f1e266f5c56dfd19833f8ebcbd0a081bdb79e7dec1e0d7751c059f5256473eee2b27a1fbbdf515faa4fbd7dfca18b7ef9957ef12addf2298d0a129b33de467d25b25da81058fc97cf21fecb47e608ff05f903f5a1f82b1dbe4a4f98068094fef432d29b9e87eaa6857cf1e7ce71d22a6fd2e39774067fa457e62e8df484ddc8565ec642817014ad882ad9661310cf555a60c593ca48841aabfde12efbf276e24c2f7ac2acb5d692b892d35616d9cae717b54c7cd18f7492ee9258f4af68fc59cb80c8b0d27b2fd2406458cb8b7a16032bbdb52f5ee55ff4f8a747bd3efd8b468922eac53f89a8f7ef05297379d3abc0132aac28d4c9abd653867dd7284dd296fdd0eb97e4cd09f5f704c3b62e84e43ff8350e7ea713aa92ab777999ea4daf0120bbfbb0454f988bce6fd2a25f699657a5f4e83dd58d8acaf821155d8ea0de437d8b9e65d8e94b5a06440cecf4b30c4399507ad6d68da9a427ccb26a6b45e3a82dfb59cfda12ed5b5b02823dc4b12e2e5fb26894dedffe0e1dea4b7ccb6243cb620aadb5957cd428c9fd6ea4abfdeefbfceef2299717756a1c533fbecb9862d1aafb2c3f61a156dd0f5fa47b4892a3e547df83ea4bafa27b68f9d17f5fe33e8e2e37bbb55eebadd55a6badb5d65a5bafb5d65a6b2d6a0a213b4d896effda1fedbb58bb63cab0cf2283ecd5eb7b9dbfbbb8a4c651fc1aafbac747aaf0eb407de9759c7ef45d4aabeaa71e08cc45ab2af9b5bef8a00a043f5b84675b6b51429688b5b626837d2bf2835d521cf0ab4576b6fd18ec78c3e5738cef42fe48f2fd8afca12255f5515ffaf13f3287f829f2470ba9c2e402505f7a552573a0c8d29f7ef4aa1ca71ffd899c2ffe3591aa5b2255f77ebecff232597e99ca8b2a3a03b5653fb3e851dba1908ab63602e2798ee5f2ade29f1598ad51531fa4af01d056cdf1631baa31fff19b078de8507dd5d850cc7b76dc78cfed8652bcc7eb877af6b0fce1498b3fd2e15b8dc384c9569ef46952066238047fa50b1848fe00c2c3c91bd9294875b34292c81bff0f5503d6405b1447a1a037fa03daaa6f3bef28fdb16f49a415efc3c19451bf06f71cd0567defb15f7acf7d5b037d511ed0160fe814b4559f369932a49832ea536ff7b03715970b82d1997d7be4f4561f9961d8e9a73b3942fa923eb9219156de8e2a8fbc01c1ef57f48d96b190f2e39de9cfcd898e5c88c2483299ca8ba1290a54a9a7a4a170a1e92a474a5fe327eda6211d1456227f0081d121edb4daaa3cac6c9a22fb120f73873728287bbbbc41612e08ec62dc0973ce124551e4f1ba9ff5be6559c2d0cce7f613a589bb4a9167bcb94b5424fb937290cb3077dbfe62933943eb4b7c7f518a49333a610a60a8be4472b4d5967f48962f8e180025007629f2a8609b6d88922398851c024d19fe653823c18f38db8138440f4422c42386300b6fdbc599494a2ec3dcf65f62f3d9fee11646824222520891d8564c1716d345c319a04224dbc3a0ed415e69983365e894e18e3834dae8681b6db5c228c93768d39110e8b98839afbbbbbbbbfbc52218e4faa1bbd3d1c61de3aecbb1b91ff171dcf890e5b8c17140a2f8a22f450ee02dfefc298a74ada2288aa2e80f5e71ce8c2a2a9a357e4e167dd9c572fb278e39435542402959de1d7444743f4c5710d375bbd84f37bfbb1e13fe5ae17f7320799b305ddfbbb0f8b9298efc3fac65de5d7c0fdf718ca318823a3b4b6c3e3920d1c31f84e69c373b623839e410a20ee5de1871229339e9ba229e14e1a48928488c8c38c426127230fc2a0ccbb5e5ab0e2594298571ffb77f507de1a7e40b9695e3070c4230ef314e94cd040747072769d4568c901b0d5d8ded67ee0667390e2d72397fae0ff2cdd9db4dd7e5088158c8f633aae6861569fafc0b76dc545f19ec2b833933b7925dbeb598521a1d2939dab4e07299adabe69cefbdb42135263336278dd119add1251487cae88e539d8fc668f87ddff77d3f9f6a31fcf44f19fe1806b9a4318b82ed1e041532693a1dead91df54563b5e594065301f8489698c43adb69ac62eae335b2e9975e5f5f5b355e7fce504be9a4b56b80fbb883c6f687698cc640f0cb9932747696d8ba9d500517e37fea187f47e99c9452ea2cb2c47f7f77d7de6ba242ee30a6189c227737e762ef3f5c53d2b3e486292e6255d65e77777777777ba9d3e96e434d42022aa6d427560c99a30332194b72df8724f4a9aae508f93e7d55f74076479674535a276b09cb5933b8ed183b871760f8156361d622af502bc6e68c6fa794524ac5184b159952b204a56cff300b53864b9933599834e30ec45809da7420c662e39229c37f1c7fc8d126c6dec6048662fb5f2a6e0f14b9fc9eebb2ff5d776dce7803228a98c7a811a319aa814313441365fb7f5455c3b8ebfb8b7ce919303df54592336a303d3851a050f74d83aba42847038546480454c5952379466dce8c9474924543a30741dcb84576d768e8e502a22167e5584fae16a0648ebdd8764de5ecfae2e7069336a35612a2142bcd7685e981e999ad1b7c901750ecfa3822bb3ecd116870336a0f8ec01109bcaad248352a8d46e4dc2228ce0a02b508e8558ab190b112a62755ab2d3791a91a481a8123d268f473b3984aa0092c81e0cf6d7affd28885049aa02cd9fe54c859db248af262db9fa81224832fca39cb06244540f5c5b64543222939e62e304614241adaf54bd16dd75284dbb53e0d13d30582b63933daf5534ad85ee354ec2a72727936f877a7be66d4c0d412e05f21a62b557b907c1024c15bdb336af5458383e971d612d305a3831934b8fa8aa971960ea6cbc664f97b578aed90d6c7f636675cea83b63dbd0fb2c1ff5e904bf06eaaa2303d304b606c30413051fc35819083f131a249aae6647b85e981c9c140d94e1ffc6aaa96ea7197bba64d297157c5c212a95a6ac90f0cc6f5697031722e04c1f4c0f414208612303d336a650c6e9622697035b329c34b10b7fd6970337a06101b80ac0034b84c83a3a8166230b4185b4081225485a21c91cb19b59a6dc60d46482e67d46638715709e270ee2a69704f3e253deea23eeeaa336eee5289607aa81601695190b3fc6b207197ea6190b63f8d7f0d9e39136eff9ad99c396d676def7116bd4b7658453ade089f92214c12a2dc4a95fa16d4e9fd4d51e4920677e4697073a6b5333e4c0f8c2cb2cad3e0ea2bc62cc60c0606b9a4c1cdb697fec507b9ab04c55d2fde9f46c85da6f7a7197297caf473bfbc3f8d1477b9c464eec271970bc903dd363477fdfbd7185243c75daad2cfedf2feaf672c31e9194af48c1e67f9bfd0336a253dc345cff08965d4b7bce87db429e9b95d4e339474bf72247f34b8efa3c1d13c71d7646204d0f6a7c9b9eb65fbd798d518e2af29858d06c85d9309cdb67d060d8e66e8875dd688754c8e4862973570b6e7afd2193dee2aefcde6aeb214fb71570933a336c3451aec92b483cca8c12cf1578c1c4c8fbb4aef3172a41841db617a6a60ed121923e79925466ebbea575fcea86dd5d3d12695577ea5639ee8985c4ac700e998201d0345c708e15aa6c865aab65fe45e707366fef77d9f9acd19d3cbaeb584130d9562de36917786a394fea84496191cdafe32d2385c991262fbb3685c997709d39323d2a5a802d3b3fdbf32552bbd5dcea86d7f910677812b0212e5be2f411c78776a8bfe1562b2280ea86f4d5f25ceea59e2f3435fd3e0687051c45e5c4647abb5ce3a67eaccd54cc5af7e1ffe70a55ea91ef74d75f7ed31dca5c45f5f4ce63fdc71a59ee8cde98d735e9ab33e8b71ad18e3a7fadbd35d38ac6e5d8576e38d71ca70daf27717b693dbf5915c7dd4d7f84ef57456472e60facf188fa136372d50641c77e1f7efab3747c7eab27634151c542f57db91a5b8ebdf7b6ffd7a5b96c8f567b5b6de6a1def4a53304a729d2109a81466f6177108bcf71ef4dcd22085596d09ecfd88703b229d57499948f6447cbfebc087f12705221f16434c43a256fbb492160970f665010924e8ad4f2d1690c0dd15c21a34669ed6637096bfc7d8db7d59e41758acdbdd22328cffceb32b9487ebd4c1d3d64c8c3fec89c0c375888cf7641204151bc724081f172e32b8279320729099042183673d0c160eda5008b2684f2641ec2891f39e4c82a0b182983d8cdc2e3f068839e77477d2ddab57afb5d6eab5563bdddde79c334791e9572a2ab960cee95e175e6f5491edbbf6eda5e0b262c4f0c2131a97556bad3cd04d81a0b02437bcfefc162972c9daf52e719765b1585d774fb0438182b6ef800225312730c1490c084687840461971e739f39cef6d2a9929eed305428c14591118400c8c90c76497facd825bd6ddfb79a0e5ab88abbbb046e5294c00841b8a2083aa8ed92e6fcd0c42ea9916dab2d1a535f4076206382125494c0c70945c0ac0c46b0cb690b3117410891c49212902006308fc1f6cf4fb6fbdb26bb5cedd52e616cfb8316dbdac759675b2632296cfb6f836d1f8f3696c6850dbe37da801bf583fd7ddf8d365f0d160c120545a696def0c3106f4d5a5f28f2f6436ce3c7efaed23eea5725de1f5241c806a31453ca240428d89e8df7b1ddb9cd6d412eec5b81c8a5df72a8582c16025b2c06a36f35ae2fcf798ea7a4048d1dc3125d90e9fbcd83dc555f14e63b422a3a617d8a8a9103c6aa09cb270a35279d9a56d1fdb654166acaa8bfc2835cde5a37c7969fcbe32ef7502814c6ee5d5b118476fdb9386edc1563d7f721cb51a750c5e1ae492775776a8ae2841222d3205f5bb57e2bfb47cfbceb4391cbf973714f708884bc1e9ce9bcd1b032a099a7105490032c4a60c52dc82cc911389516320810bb3e0a4701756289c22486825ddf9445a082122d0961ca0f767d920f72175418c2f61f61ba138bc586c839a2e2a8f4ac5576412d999a19400000004315002028140c078482d160982469b0990f14000c729c507c561c8983491203290a42c818648c21c4008091919111920600e3758b202a1e72c41db98935736a6ab3042b74b50864388f23b89f1cef87130ab440f987866fc0b81c16a5a7b15f9827464fe9397232f79a89aba806d058cb464e689f2d6a5c1afa8779fcf659668d6e8c11302245d84c1f7ca6055dda1f1253094126ea5d382b01b6acec852040193c054fd35d735f8d04f3701059b12ad698eb7bdc6ce190b639d1305506caf29770f3302e33f0271fcd4ad636ea9ad74f5f04fc0347585d7061798fcb488da1e9319297521b02d8a5471614e62bdc8470e6652b38cd95d28d0404ec0f6acf8588f1805560be229a670f67fa56b9b001a47a4909f6451b580acadbc5c21ff9066cac40338e7b209eaa51d81eef17d5aacc85ba7ae517685d148bc0592b645cfb3c63f5a8d12cc34d2ef8d9563d9980efd8ba0d422d5eba585fe643ab56346d51d85eeb5110726d904f6784043a10fdebee40d7ffe9a474d342df80aee9009b6659489cbf888df8a3dcb024991f2c824afc4b90b1d20d9ef977668f74c9a895e11ecb94a22f4ba13d2e27160eed64aa480c6c4a108af29a3ee69af3929cee885db8c380f05ce6cdf66a177ef195750a9158fc7b004d33d295f6511afa93eb2eb82a299eb14bf646e826562998fe70c9bf49c8ac2721de92391a27bdc1fc2dc5cfa90dc257ca1f047f6a99b1bfc5c8cea8886364d470cd0b8ed95b3ff93ceaaf09b25c91f115a2f3d780378ee16ff985bb273ff6c3cdc6ae663f7b3b380d9669bc3db5505cd0d7680308eddd52355cd00f799f9cfcc8966f4b3b232833cabf90a35060f76c59eb190b6bf652eb8a42b6c6cd1b29bc340e015557e4b0b5fcf784a010b427bf274f72b0dc296937c0a78a8b6c5ae41ebab2cdcc75b30be81e7e8d475109addeea09abedc93572652bc2f594f6f0588005a656ff451623b88ba977b3ba91d11d3ba3e95585c967a472d7d761f3b2ef711801223812eaee67c49353c15de4788f4514b30a5a00261b38a08eae8332b99ba0d73fb2994b564dacfe9d7be4123f45128cf216c3e1f0a6a32c0dec15dfac6d15750fc3d664da333bcf6b7e30583c288357a387be2857ab9f38b615bf76966d2cf073f457ece1eeb495ae2cbdd9c8c5177f6051789489a45f1d8990462189c1f7097e358318afd18cc4c1bdf506df3a7909a1d7051f93afba7a147651061f0ae9bc0c43dd61ae2adb73fa9e748f207b13ae924318af2ef83111e50d47870baf8e3948d995e843ecc8dbe19553444240b2d03f0f551d4ecd2bef8821cccd29a41f06daaf61fd69ba6c9c17efb695e95da7121e988e92dfbfa2081e4ff1155359e6c723064d90ee50a1469cdf2258362c554c3310829b9e18c1e6beb5a362f339069b1995e7125909f5b252cc2e26c1e7529e68cd134cbc9945178e19a75f654b6ca873bc05c9e20ff029c7e781c60d70ecfd386484b0d5f8659a5051b38a5a5483ff29bd09732d51cd19a4a830e8b6dfb60aa497532bffb9f5ae606b6a4f6292e6e5fa708f17ca35958072feede7fc7c0b5218673de0f83a8dcc294359b0dd9f63485085230deb8178f265fdcc57bd33746d2a863a15a14922cf1605a2283f412000f11f959cbd7c09373d8fb83afe02dc04b92b7c404c1225f42629606dec67de6b88720626ed139a2d72cb2554edf94fc2e38b348b8cdf0b53cc244440686c934ce12a56e82abee2ecaa45f5a66dd86f7c184e12c42026397644b80d5c9573083f8f29e26bf4bc0ae056d9b4c99a4ff36daed93b3b60061444f1a71c9080f4b1d28dee88aff081eb07998ad813b3234a38b52b371182043ddaba1a7c106cd61e5c8c71e6f7e73dfadef3a3d249a9a0d14001bd45b43d08638d65078da8e003f40f3d452db2a67574ee27794e75342b75b9bd5daf3bbc3522cedc10b6ca9a87c952cbadd9b046973c99913c0297cb90fac76439971ef00c116549668beae4db26367afd70d6a291b2b48f0fc06a998ec1111eeceb6d4cfda1a1cd165a3eb186dcac270f387238bfa720301d7682dc95b28f13304239ab055321323bdc54e940b117475bdc702605093b0c645c1881c0769c2631238a51b1a0a82a2faeac7272b981ae62ddd824dd76ce61a97551717e797e6c72b7e70048905b22ceee1229db752d05ce167002416a544f9e8100a1d42422428f3041558c6cfea05225007ca35248491f678df18fc0fa48200933dd817c692970cd6710bb6c22e18dde8844d43188211f4aa455d24577b2b9112ca0e74dc1be4636eb3bedb62337c3d4ad232916b9ae8e7a905b371fc61c89159e876a4f883754305e0cbb5c6280d35c1f350d9f82eea7e61452f0bcaa2988934237967b20835e1e06c47368a749e5b2134f348bbdb50e16e739eda7c0fa6882ded8dba7d4ed4150dd442b2734c52de149d344c86cd59a709a53ceaf008c4e9ac6624f9992f559b4001fbec11c0aeccb3f128cf9166d3b011c275d814d4601aea0f3ed156cdb638bdc9cabc3f61105bc1d2b98159778b6fd2747002a8f775f1048b1724c8149cfff1a1c6838d440194643932795aa1595bbf75bd56c8b63226e4ec92e64438ea0a07139d3893ef3e380312b33010d58f0894b3163032560c7d368b5a94209d0771bc00c6267389626ae4b54a313048c7ca42a6df5e86c652f09e11f6c7645201f4f9e1b38745238d8ddad3f9d9e7f4da780aadb41e9d72ad87fc7b889bc202243af4fb062de12b0946c0e2e05d286973f0f0a3ff4d4e1c12419c01b61037ab3234142a3fd27f7563fd2821151d7781757237838849b6265f2d513dbe6bfa5da50e7ee425bd3ca76c6dc7ccd6f9e6424b6206c3452cbafaeb4e53623e56f41613e1e3d107530348360cb45c9d48d13dabfc20ce3ee6653f1ed13f431c762a55c2b83130d7a92b67c9c8a2f163c04f937dd395cdde4daee943b2ebfe73cc4a6fb1066fc0b1b3e2fab42f58d2a58ba76deb620914fb2ab97c7efb215bc7fef6ba91b3e80661b2216a98baa786e99db0c562d4c865f84cd717819f21345435fff68e45039671dac4017da97dde48a8ce6cc56fbaab4ac9105bd7f7942fdbad9145d5efa2005f5e6eebbf674827054b788c55618638b1774ca16963d7178723b1bf98db2063e3797861fe081da9ad39c9e668d48e589efaf373ad5534b57e276e400ba3f4c37492900d20f0fc1a930bf387b728ab24b0131e3c0f5e0a525501382dc874a91d20c4172656bb5eb6cf86b3d3c8e402f5367156b522b48d852c3183e1e8a187d1687561bd1bb6e2db17f2dcedc13abfa6ab8fd17d385fa1e47a22e365e879a3946649b731c4415b8b7f37ae47947c8f27440c7fab87e99e02ab8cd06ef4c44dd70f62d52590d7e3527071ed162a48b04cb670787e1248f4d1bf93a1a050ad9d8a65c837e2cae5e2155d4a21610dde28e8ecd9ab6e8d4e71a185b5fc6a23b7661ea4b7d8047da4d54d7e12830eb27ce6df137cc76593963fe41979d2b0458bab5b83f051c6931006a33553d2fdd8570f3d1130165fdce89daee85df92c0ca858f1eb49c18ada93564206ea584b5dfd2572eaf48b6ff0ffdd73a0811f7162940d5111f0cacac09433af95496841cf4dece0b5b98419e4479f523be02128a5ea908898b79b608b6db3f0e1d87edb4959dc5dc898dc5282037a1ced9084a88f0ceaa39705964ea34a9598a69024f28a6e5088e1a1c347150d81910150933f85eb92f4170fc727a7d04b4553564f4edfd62b7c678859e978e02c37e56f7444be10b998f579167f743b44f203c6e32af55f3802c664b42dce381d2528ed306dd6b3cbf903a289c6dc99a5f918bfc4e097bc220f017c94ceb1059fcde4a26f19814e25a6080b740e4d741bb2ddb6d504cc434ce454117299cbdfd809e830b4f45be70d28c15b3844218c11568e9a286542ae7907c72cd6ebe684e5f5b968968279f4e1f2092ad8ce69136c162cf872cd3766dd968f952940f9ffbb8071907892abcb8c4c8fcda91de1f8744d9f9b444f856b8acd6797b23e1737ee54a6a2b38e6f11e468fd68ac82643ff0380976861327b70f58690094630009b76b3b52343800e13245c1601d6d525812e99c988ebe5d98654e91da79a6aee3e2e61ab83770c0acc3b3395f6f028eef91f4268b88b8b9b9abe30fa1ce680949a152f174552ca2c1a1534b5768d82f96c0d8d13cfe1de833cf0406f3856f04fe42f6a0600ac606f0a344a8150e97ea69272035cf84e4447a675196edea64d30506a5e4e50c913e0bbe6017b9fe34eb4db393cda67c276d7dbd0c229de47fb74807d1a73170bd442d94a42a58b5a8640723fa13a692a834a1cc88670e03b6014020d8eca83e48daa326ed16e5b7b2537d12c8ab287bf8096ca7ed7c405825371c4254b1e3b222b893dd1697223686863514cf99b047a58631a828ac94445c3928de4e23a81354d1533a11971676a9deabb636e4c6e145f93fad9a9f721ae4238a3074226d5c8159069e2c36d7edda427f8db01e3837c65f382145a42b8f8b956ef80d85af735e448081344a457d088f9f6dacbe97790ab25ceb10339020dde167dc1db66e27b5d43461d47c478601e2d105a86a6d126d351ed2f55e2981e39d16c5e55ae5893f3cf6e85aa93b88a907daea89470a10790af9764b482ef4d407b318aa0affb9536fc4e2ad0c7204710d96e2586cab8d10a2a50876b8dbc446e46ca85dfdfd7064aaf2a4c5b02c6d17d1265ab083336bc4db4e8c3d8c927c4b2c1708a09031ffcd677ef6922a885a971b61c3b3d549c017e6aaf8d02b7c651a9125338c267ab709f41f7ce236b581b5cd84be08db75e53543e7967a4485bfeee1886de3372f83573b0b6dbb0f40c57ff1d0c60b068b98875c7555a072dbd5ef46d402a2cd207de09639b88a0f0c0a5ebb70db2d076b0e1a9136e5b1e39cf7ddce79eb966e515b2681d0ee2bf8543cc7c7caadcacf96d415ab06d56fd86e20ceab0df146b5ba2d6a277ea2b25b2cb563bfb15257e196baf948c82ae1cf18417c8c6350838ab3dc84d05507b8e7813467ae10b7b07cb4be47d8f61aec70f68c254dbba40881eeb69805a493bcbde2ae322a8ccff8246f31425688a427473d4993ca6eeaf1b24b2f04f8caf72d452b9c2890bd0f61d33fa15929b21286e9b01d4c1a2afd2476b2d372f35a669bc0fc637f9e8be2415ab4fd59ec2e55a8bdd05979da7aabbb9b392139d7d1b090591142ef5c6eaca30c4a985434f8bb6c8e12079e802dc0e09f1c7f837c2fcde8a726d54053e13aac9ed357479f9616085aa58b4468197a7df1acdd7d359b18f1a98a08d1fdcd1ba30afb0cd92fe0e0e78717f96f1b0a26774c673ca7bb46f45c812c6f8bc485613332fe73dfb2bf272ec562b9e8087e7400fcbad266cfb7e90a141044f3848480928d18d52d6a87bed42ae852105d468711408689eae4ee4d7599c573188b17b50eecbe8a4346de7b1051a1b4493c0b958df40ff3928b86b491ae4a089f1e5b0cda2fe9844c190ee8cf9c3c36676f9f96a8cd3e97b0314582818cc80e8a03c2fa4d2d45bb8445454f1a787a6bca6a2669d289a3db793c20ceb5ad8bfd6c4b9414eb3007aff56c89dcf1956e3cd07c1aec35622e113065c5cb323585bdc02c5a9be18b79d73534eb8f9e26b4a8680fafeda0e09ed5691cd3273d56556377b8d697937e4518b2a09ede5457b04f058f01e6b07933a645ff93f1ad69c71fe82e495577882d222d573d5cc10630b066b40e7cda386a9685ceaaad60c5d8e2b206d976d3ec0c55e5bb921f4cca4ac5cdae0d5a94b1d7c2eca063f81f397bc552a2de1d1c7ddeee31fa085330321afbc68675e08fe8be2a5bb4dcfb708ff10f21947280e21b39692d01b72139c20980742318e2280a7a452e99697f8609f4a450b774d468c9541d3919eb1baa92d9d6de813b9df42517ab8981b7bcfe9608a2fba0fdfaff9c41f1b20e99be0d61de69bd21786b13ec22e0cdbc98982b584a6c33292adfe62350a8ed44775d9d92a14fe354c804b455e2afd506cacb44be78162c8c7d8da41908520d33b32a6a8d6e887e8698da069a3a9b3d42f0b9f510cd648a37187df8b7463a6c5bd91726cc744d149ee95823f3169355ff9530e87a8e988937488f44a3e2e53ee13729b6954afc3dd72b595f7fe4277dd6c301ee873fd2462d3b848433a26dc70e28fbbeaba6b44a6a350002b747924ff3e1f2d51c508c378b2509a3e9cde6561740b17e368b4aecef8dd09d5184cc63821d690700a6b85a03eb28f4c6013ff92f3156ee2ab71b2451d9b4231ab4d01f4df7df5fb4e3f83117a80793d120c58548464a2fae93b5370c78e772700eff7c27b5d5fe4d2c28ed0348e7fd1ec44787f78dc811bc6dd8351073e1e9691dfaf502f391bd8835fac95d4a91ba8cfbe73b8bc93e12cf7b6ea3751bf45d8613a26c3f6fc403cd5d2d9ee54505232f7267b1a00e9c93442f8b31cd15c14ce232d805bf743c7fafc722efe841339dd3bdb409de5a335380b8ed0b7dd30e1cb3439f055c1919b8025cf9a2ecd444821ea3592747403ed15b23e57b47185d6af28ae4683b32203e5f0670bff56091ba01975b91a757cd27f291f77552f6c8e6650e9ab76f2d30811a3632db0341da1422968ff103a8815d65012ad4ca3a37f0b64b8f6cb651e9e269e4b6f626dd8d817199c4e2bce4bdad160cb8b885b4cb286ddd95f57b2ad4b0d41638c4142640e7bb08482739d4852f8d00885e3b0b5b5bfce10aae1ea4deea25d626df82c8165f5deb3429dd484217ab8e453eaf13056c307b6fc4d11ac4d316b2c5d6b0b5c8b812ffd00712c0a3929f3a0a45db5f97710cbe403dfe16a5c9235b68bfd336791c50110fb3b42f7fdf98b3d8e58f7cb11dbb78456fd7e3e581b27cc140cacfe6a14edc03363a6470ce89e3b464e0b438bd98c92a52a15c54237ec4d457bce3a931b16f51356f14e9cc8bcc7aef7fc477a1cb68b8afa260378194773867dffa719a685085b839bccdfeef89962ca4a95868424a8a014b0582837491b93180c39886cca990992a6a4c0703c5a6577bc3f2a4951a73a93c8a5f18948b30243fa94fa4b5721e9192307ea7232f07e27024abe363a7ec8bad0993e80158d5c1a04ca15481122482534ca7937e3a964a5520984b3492c9dd946d9d2b1df096c05160721394da1528c8960f8e1b43d6857da62dc88fa8566dab87847d19526595725f3b2e0809a75e099a624338d5656b43e4dbb21767b9d1402367c060de9c882824fab390cb1e7abc118485f20406edf04080e48655234e61b2b5b98d5153b175cb63ae781b0c7f8c1c1aeb2c95c8181d8df860d5c5f78a58974423d6a904354e6f8fc82d28c061c17ac6083ba1385cd0384e14c6a8969ccbf1552ab3856b253e4c00f017610c554c03c104b6c9d3534eca49a0724a6035af34448dfac8721913e3018cd2d3b5630541c898773229884de3a89428327d50b73a54b81d49f084a0a4b0f26b71832d4a7874feef0a48b3572f93412674732101dada9c07550a87b9886b1b8adf2dbae83603408d99c7cd841d74122fc5e0ac84195dbe60b101a6449fd05881ba2d7adfc9dc7f9160c38c3029e8d69dc46a59312d0b91dd119093e80bf259b286ea442804f758791036e126172d03eb2f027c1b85ba419c0d3c82e5953832d870e6f1be630893108a82dba19227429a7d91581369ad699f227b9c6b18645821edd9799d7b5c1bd05ca15cf22b77962316d2e8afe4c00bad0f2c0e9734fe3f93baf4b074363ea37e81d6221912a81ca0c13d299610cfaa1af5bb6ba2e06314991bfb55798a50c4759a2b7f5956f75962dcf2fe6e854eb2b1a66f465fc5a03c374cf48b2514fa195c8ed37168b51d8b4a15616d6854c5c4cc70633955a1c21f6a665e42a26015cd5a12cae32241a84fb3ce4f6c9ba99446f90847fe213148b605dab1787175ef81d645f872e6523272a4a1a3fbfdc543b847f2788b5f5a734f2883a0d3dccb385040d3a0e8f69b43c48fc9d82d22bbb07c516310cc5332cf614f0439a4c48311866a37df6c565da45cf53e49a11ca1ca2990e5b34317988949ffe74fc6be1a2f14b1095071bafd37fbbbed6a2576a70015944bb314934a66745c4b3342d7af552dfd2223b056110225a00eb2e6d0e2dee84677748df189263ee71cef0cc67f05b1698104f9e79c03d92e92490725d04f38cc535195e9ce6eab8c72e2142f191f5a320c62fc9e8b8ff78e13aa463c4fe8f75a37aa01baa3dcb22b177cfce03c4e74990e01b544198f44974c4d6c4e149bd50b370520dc7a04d45f004d1343c7ca6ded5bfd2daf41ed62483ad23b18d385e630460b1de898c2ed7b18e3f90e8449296152b6298e706a8088752e5ad1d980ad326e6be2cdbad110505589c49d5d4451a9f4b76873bfb153c3044fbf2d5b21d11676ec1b57e7ed2d604cd98fe4f550fcc13369e13600074613f06f7fc33869f54bd2558162c151b1413dee90153a1400edf283fdfdb29503270f870a78ab6fdea4a5b68cb72a23828734c169f5a7f5d10dbe42ce246291125bf5b28b2ac51d6fbd2facab446e9ea542a81add497958790b044efc71838d24a207e202ef09a877432bb8eac20945fa325105be1ae97b637ed9dae97f436655a0c1f37113be9b0eed2854a0b437b6143cdeb4ef225861d015288d7edf685c89bd39b1c2fe3365d3e31e7b8c6ee0a425d3536127c09832d9e1f591ac9cc2e710c24519a0b3748948eb007d3a2766bb08281aedb60481bd5d68e1ad2bcaa68f06db274b638c988635d94e46ddf5f60e4f161826ee422adced021b1ea7495d978349ced9fa4f01ee3d040b06746c135e015c7b7828b9c711c32f642273b9e4b9ebd4ca3071d4f40d142ff925035794d6d29392299848770b58a71baf0048319c55bb8ca6eb03effd3c2569423322eb03fa68747c6d7b0ea3828b3d93869987158865ac9a220fb9ab7ed0f30583b654c5e505e8d3932e7c617004001bfe4cd3eaa4cb47eee0fed0a520a04eeb9193e8a6277212b68ed541b707682469b2cd5ed95afd7307ef165631b2efcf5c74a28122021b79b000e799736c0e58e68516d22aa9ca980f92e788efd822f2342410f163f4016a2453820df3737a6fbdbcc59b12e1199b1aa8ee0affe1365670b518c3b99972bcf63b4c810c0653fc02a39fde8182d7219cb323a14428afb22d88a35f6edfd879e6b0be47cb57f11f683a3be987cd86cc633ebca576e047e4c85127ff4c209521cf14c7ff9526bd4cf3532d440f657b58b1a3a2a23f0326724e7936f2d30eb2da1684b1a26f479e8081a75ed35eb1a3273364d31d20fe70ef6a627a9669891d3deafa9201610921eea3bc853a64064054fc969d6f6038c54a760f4d6b94932791f4eaa136ce3716f64de56d62242ae1a19238a6ade88bded3da87c68edb04bcc69d8ab20cb6fa85d1da95707f6f3ba7df8c3724bb6b85c90243df8080bb65a583e20026ca70d2801f2d3baf07073d78265f5f9fb9dd8bdd0adacc337d00274a26a13a2ef2145d9f1a80f4b6f40adcb55d422fb0d2faf001bea550c4901a482516700c7f58e11be55fe14d0b739c2d6cc6cee2bed74a0ab59bf05b61d0ae6bd835e8728db26cb0ff3734f37adccd8e4308816a61c28f3f9a298c8c7b3b9447aca707b280527b9bb58f7c220bf8cd30429767bff57c1698b10051549576909ffedc719f2ed4473d241c1900b0abca7652783a14d5521c602ab5b8f7d64ca2a279e62017a54d45c10845d9b2c40641d606b6f512e7a9bd2dc73cf948cd911564c5b1f404b81f779869f655af84a5935e3e221bb3a88c2ec3c8c5c3c460e6d2ca66d5df081641946e700fb66684a61475554b21db9895667465eaefbe315174d8a5ac3f8f54f0731d7e10ea433bdd0735a75f6723ca8259228c6d75dc637a181db9e4789b768c67f3e5309fcc9f9023188f97602d88a4874a2d09a4b2b894e7e42c7c52cc5f11fd22a14c04662eb6ed34bcf30a456ce82694fa62fdd45d108d509ef2c9fdce020c4d2d0bb1f56b71da11d867a0f43743d0002364726ebef05fcbb689f3d20a1eddc05653d017946fbccdf7f8c93d5929100a6d9fad6a8ed4414f826a3e3822092da74e6c1f8c5e0feeead6d626cc9b840d38fc6a7500b59f871a67d3460f9aace0c8df595d644159d91c7b4a03eaac5066f46e3bfacfcff0c44a196dfad3c70df2784e4f04eae3b8e151b1d98d3f28a9c01ac7d73dc355e4e41648bb032468699dff73296609c2604ff1e1159768dfabcf8e2c6395711340ad4a205dfd20a7e93de7fbc95542dff58b00b1b0e8079b83ba32843c69a266deba3600393fbf908c16c93fb5c56d3fb93d944b0629e8401074130be0549141d508403cf1580c4ac0295ab642c4405a0c871ecd2c8c3415cf2a3931dfe42e68db793187b05e7c5a23d045d9259cbc4684da1516c7c782725b6e4f9819022a1ea664a7af0e8ec56b5db3698cf03bd8c27af509a9fc63c591c33f7c848da54f5a1f0304001739c1756e5d62457a611e0e8350092b002ee7d940e665d905b5e12bee09bd8892fbad8c90c6bc1df9ae58887b9da4367f363af8a0ce588077184a7d9b6e41f8eff9ab4c9085451986aeb208ee607c778391c26d07768f92ea6b47d3e0b67b3665a0b54b0c1548dcf72cdc930cb44e5519bfbe217e8d360b0a68ce38b6c0f1819036d6ce664ce4b8468db54e6969033574496d8923a5e8b58c4efc44fb17e768e0e800a7432a16289e7826481f07e58fb59ceb75fc13c8285d331a90440b6fc7e362400226cec93b0a413a4350a85cc5509e9b5d009593aca6ecba5a1565a7b9f7c2c1e49c112e43cd73d20dc29c0795b0dadd3946bd61f64eb615a0b18dd1b1b297b73c3e485cb6fa2f62c3b6c3028b3f62a387e1b4e6304e678aac2d696e5a956b72d4199836cd0ae72030ea55016d4b884ee0ec1575ac349b409ec8718e5a98a02f3715f9f9f35b296e3c392f310f99823aa162200e331b7a4790e8d3fbc0c06a7692f9492919c037452f9ba98466e657c7baec16a474539d2a142f65f01cdab84691590420c1f4c2131702a35a660de8d6682899f9dc43868747041386b5c8c94674a964b77a0180c73fa68bdaa0092f316f1ad5c8587f80f56e4c70042453b0a1dc86f84459c9a78b30f588a8aacbbd42b1ae2174490d17d16138ed8348708bdaab59c82407a07b8624943279d95fe56529478eb58831863d52ae4be8b912a6b6e88da10fbe20ccb270fc7f5a4ebc5bb0692289d5af9c1528a533d1d940c6f148332152880eea6a038770af014272438ae9dc67bbb0ef289f2fb428aeafb45560d2c589f2d6a8428ca679fb42c735f739f786a048704e55b28e875db37c8b7295c4b033e5c105013230b5e85cea8e4998858e910a5263c6684e01ea0409159de118a2547eee4497e72461524fea81f6c966ef79983944d56f24e612884c646922ccc80c6a5e0df96e4b5b89f9e2e52657e2dc6a8e20683623875452177374b776f444c69a2acc5e4d30e371b99d621ae0c391044b260c9ccc500fbb25961999b83e66c0a3b9b389a1b117a33a1f550a4c5dbedd81f1df607b4d37e19dbcc40328a004df00164e436a8414bf15d1b6b55ab0a65a8461984ab553442a7cf1ca383699b07bb8463ad55d55b1f015a3bc5486be8d69278b243f4d04c80c0886d24841a4adf0ed670ae56f9ec22b0fce7935c5c5d8754dbd764ad12c08815569406f055d35fdefa33d2efdfc7b726c9af0192eb51ece1f6875282f436ed546cfa1b721f4e801c83b474ed7da24e260abfe1910e9df79fb67503f3ede6a404fa946ab5b1449ce5fab81d9c554ae85eeb7c4d1983c365822b79ab41bac6630b005053773801362cc7a8d64fe8e6a77dc1ebc1fcc70bf7878ea2974d84bd2a2fc07f855230b8f026a6726269b45f0475d3b21d0b2774dd4214b611d81ab08f0d88cb80bb68e99cae2cdc4e0d4e803810ca9d4b7826261c56497821d2566f8aca3f45885c7584c7d1d2a0854ca59994332cda981854cbfef4e8b316c506ef70e59aa591418f0b7cca38bd5e083746271f242dd8bec361bb898886582b19b21150fb22dd176cb1385184a5f01c3bdc291caca8814074e201b29b81335b57c4ea9d4bf06ab9001a71967d549cd03018627daab51f4cf776ef496c2f26732dcad6636f61eac10b37474521cb76fe1e8fc9d2f08577620df9570e3755907206f9476c7bf503a4c1898ffb2865e9d400375b108ee297fa6e47ac7205a27cf379dc002c20a1f6ba6a6374cedcf0825ae6fc3a3b369a1ca5b79e35a67a2e628a44fe00978b80280c44f813237851ead1dad0c064f21fa415437475fb84a27f6ea2d67dc4a1f635f3938a6561916c3148da8ecea4db9d4a4c86168f065f35ac9eb9666850059b316849440314a5f64b8eb80cf3b03f7b700a446e61d831964da97ccf90934e4bb268280070ddd708e9164f336cacd291a5ccd8b313984516a062ea0fd7533b63fbcc4539fe2b00670c159703e0b6b49e6d636bc3c2232046249359c158e934fdf9e3212a8ecd153cc991d98392dda2be7a5ef6803618a5c3409bf49de7eb1e3681c5fa31af3e24f90702ee330c8b01b2d7596df8f6d51c325c8e0851a24795b0af5feb5eee7b5d6ce4a32256bc79b04b597b9a23d5ea75953d29c20baf956ada9bccc3ff12f4d0f3de0f35a13f8e4817dda1dbd1077eb5ba4721148d4caa915f4fa1d6625f4f03d6b79638b59d6c66701de5b830210536590ebd5b586d1232e230dc12d458a4dcb690770e20c38506c2e2bc59ab2fb47446b8cc270466da69fd841b6029fa3866f0a99c4bbc20fd7c2a2c4504da5e23aeb0b63db5689593396da905736732204e6ef0bee161a3a4193302b3b43e2bb6f8ee43af0100051db7737092dbec401652a074da90c3297e6d29f26ed26312203bbeb1489ce99eab575fffc6aa45fe99d3cd23d7a6f3549ac17c7be5d914f091aca4447f5d0f144d74991d922e20c2be1b62f20437eae0867bce25897c38e1a785c66a21ebd62fc1c852bf5d3d8718f45e721db50b6dfe96838720e8bf0b168a9057dea970b858ff54e0b06dbdcdcb810bd08d4bee46594dbcfc2fc0da9dfacf0cdb51bf9b9e85d3b59c3eadb9689ed89811b295a79f3bca10192c7c78fa4cd06f536a41a059bc300bac32e7d38cce47f57a20b719cb3620f768b49138b8c39e52b14864aff7800235e010d411acf5838745e4bfc14051a3b62da5676f4baf24dc8967051e35963aa1a201a8e7d0a2d518cee27f918089aaa7c6a070185167a6b64348952cf8e581b444df81ccb5ec1c67f82ecd1429955b918bc2991d381e007f35188aefc306aacb2bc186a75e7385eef542ce8238ccf80604384634ab267a526a3ec892ba3c08da3819228909d45b21afd218f98ad369085d4c64e3de7d0e2e32a053aa2f94d847ad65aa51058ec3bceaa23b75b8717363f9609071e7acf45c48e93cba727bfe1fd2f76297c402fd094e7efd06f7c4e2b1432dda5b44ff93f7f84a28afcbcb6e08849348515064507317aa17bc95bf8cf452e13264e792e652aff02df3b593e0ee90113d8ade15e5fd383fa212cf3420ac15827d4a974c2f215ed38b83610c957b8ac24abffa47b0832e0f0d73dbf5ce5b919a7ea3068c54f88847a238455dc0bb3fe4cd312a4eb71951c03274e269263c7bfbd9ae346a555524a4dc60d52aff7f6dad4f452614c6eab787f25adb4f7ea2060404021dc21d4f73f1c653372d0f2a0288b396f9a5d679a13852c49ee9047ac70c8e36c751321d8984291293b684501eb06f2b8afac4ebb6c96158bad625efa71beb772606d25cbba0f26e7e89b25c1e89b7ef4487c7af9ae8a25c7cc78fce350735d9200b154d4c1be93dbd3e364ed8fa3cb07b41197543f346ec925a2e493b222da7eebf9ed786acc1a8ae4356d09fa6bcc0b24d258b6520881a2112b92bc150ea0cb777238b68d2b662b9946a69b4a4978d28cb892305e4aad590112572225d68e6bad87dca0ae91e3f53978fc62d9fb773549dcd808352930509aea00db9afbbf84c86972588ae2214481435152443e5ca24e73e9b4a1d5e3bd82b94d05d6ffaa64585517bfb257c6a915d8d5225496e561f74edd69d6cf0b006d74c21dc4953e34ead6eaa6f29050ef26884a58541b2e751929eae219cc43658e6cb36403267104b4a8179f5f2b889830b71891b213cf4ba957e8c80020c03e7094e15e31297b0f83eb3f77980c2b4803141f16929ffc0f22dcf63202189ff3cc94262c6466be6ad7a190f7fd646ecd51745d9ba33f44ececde0910b9bde7f1eb1dda25050ec64cd0befa6ffd71f4cfc06d4865c15015f693c2c52148b89d99c281632d91935727bc35aa94d05c08b645bc07f0e6a1179b050249212acc15ddd01b1417cfc45a9583f7a948ee6e08a8ab31ef7eddf72d9ec6567da69315f7922eeba98807c01b7a3f69dd2610cce35f2470894031ec2b5ae9c53aec2e4e9ec1217ed5c4e125f615c2e466c31fb4044f120c8f215d519924e6070abf6b88ece52fd93dbe33a148acbdbaf459860942c0a51047530c2aced2f492ae83ff38bfb49fc6512e292527d18dec7ea382baa3774a43a09c1fe0dc702db66c1b87254350049d6340533187958a42cda72366636329411a0747e29936ef2c93fe60b6bf81095872c0d8e1d277cd1949a45e58f95e6c72e58ad5fe9cecd87d0aacce0dda99a075629a808d63a5e252ee12e385d06b1ada0ac4b9115410e5e9ad1774f252502f7da091ee5e12b94903d9a9c7c2bb22fb31af832056bbd4e91525e1d607de21551515e254b01f11b5415b911092b52de0c3bc11f6cf72726de85d3708f81e24f332c3dd65646cf97a5571b1d5ef7814aa3c3588c924054f80331e3027095a19007c68e0b11a8e87b363a05ce5a8d7169b5f62e749fda115662bbfcd3b2d579309ad984ba0026630458e5685e9f523e30d3ce4b12f015293d08ee4ae6e49ea0854ffa80a8ba632520920336a09c6eed6e9691267e81b270f74112353e441f34b229e457486ddbd2907666c8c966880414f3a3a4adda0f0686d6a0143e16fd1a343d2f81270a381bf700893835838715c83dc97f116c68448086c3efa8aee42c2d6376aa43571b0417863140ae12d09ea3147a3476b79bcfd5e785cc87181adf00425a83a92bb2abe5fd087043c7f3452f72798e06118044dfbb8636e61c42564a3cfd1ad860ff00a0dabc24d24b5f1aa4c52579aa9d43fc83c5a0bbf1e53c346dadf2727b821774609f939315b0d4f55c9f44f10b20d50470fa6a73f8d55f3fe60425eb12d2ad34480296554ff78284b01850a3ef4a404993874e98792cc1cc1b361b5052b5ffd69c255805259be4e4ce3f620d0c69e10d76b310fb8e626b854afa8d90477c500bc5456c34da7e1b182a5edecd892bc1a8b9bb972687f450d459e3b234b9ec12c636bc4dae4d1eea6b88bcf2b72751ddd79c4c0993bfde2c7363421b477673e0d44008e6091bd311784a94c0df7397b9416de42e9057544f788b3d4df875b233c97c009c26cd5bbf482f519f00a4245fcb06dda9624eb2387ad06cb9b1bdcf944a457bd78c0c662e1115e1af68df9dcffd0e55a65ff68ba1bdc5dd3b4b9f960078162027a351034fdb23d103fa8cfa5278af4132a26d7419c4f13e2761590792ea1c2b15c4dd1efc2861af06846f46f80e0a2ac58b7236eb5117baedb8a1a1ed6de97e99c2a4674911e01fbb1629f0000b4c7cddb2c127880f9b6b6116e31a901b9bf676b36387e4eeab3be72c338455113327b255b744ecd2524273b02a3108dc62131fcb0917b3a2e0558ff1a91f502e8ebd630a19a14690e64639285d9873c0a2105208b65ad312bb4fa8ed8075d0aa91b6a6af9cef8a62026950ecc8173e1f49f7338ad52d6156672a83efe6bb74f13f6c34763668af4998b60278bd958f973832401005f364183948922185a954959088bdfa2b3ba250e1c3acc1b4024208baecb3c45147205370620daa5ba562ba24334a522920e7f929e54c02ea9ff4268bc8ed355f306cf45f223b0ddd30031fd9ee6b5be1c89952ecb0956380167c001f95ad5ce9373c00a4d8c315639515f329ae8382456a448c7f43bc095a8a87b34f628e9f2fd56af823b8df01d6f01ef64843121fd090b11057d76ab194d90e82bdeea95c2f60d292e4a208724332052c69edee0a2d20214317b06b35e930c97df8898b395f6b0785227a01b7b68043e417805abe12c11ede8ea688703a3b9882da994ca942f357768ce878cea2d103210ea2c8b128911432f971ad08db67991e403cbdb29098f0a3b62f92154882fcc3bf8f3e461d7e99e94073508cc4891c097b5a9455fb2082ed105483f54e122fd5234b420917c7e17c1d71a8b8bfc0aca7e1bee7168bac38e060ab14a4278c986316993c8cd2f7fec82135c028bb95d8abb6dbe6b607a4bca18a1d0fb733198944d4943443fa9cd83364aaf752c8331b7924dd3194d14f73872193e334b6c3f000c498e9b34f10f2d46270192191b5220a42ba68ff18027716ad64be27d808879decfca0794f66aae7689b5b4afa7d1249ee0affd6afb05cce505ff80081dc4357ac80ac1290b5fb784d74da73fc6e23a8916475a67e723a6c479b93cc04d669723dd4d28c2257b59c9a5a650101c985a033ef7b701741b61628b266438a173fa079041a203618e85520a8bb9d68b708bcc57f7027d54f47162dc28f7f676e095180a8efc2fab2ae7b26d1ef24a4bfdd561f2e3d0c28f1098ddb36dc613c1b3f8812d2043fe8e2f0498c8efd42d56699e485f132609a11c3b85fcabfbfc097a44835f173d6ff4b0eefe9eed3034f47a21722c87f267bff8531498de390145539e11711386bd81557e4acd9c3fada595c1420d14e9062c541239822bc05d22d6baa6fe5c03418a62dcc354ca992fb53ac3327e3487049953cfe5590075c319ebb0030ed1eaf7c6a70055b0d00425b8ddae47fe9f3a005ae30d32c8ca003a22d86690d2f401eda42668edb66c6f0e5455cd445c8400989a0fffa73993d4b546a480ab056bfd18599f1075b1f45e2d076dd44c1c3b8b9dc5ab285d69d1f3413961961f8d3f42d667b9177f30978faee6adaf53fcbea3d56568afdd97c4774cb3c4c1d5fbaa03afa84e4240b201ed527aa44cfc2b5aa57957ed404010410823915d05f739171a6c8be15659463918e2a840b5e2727c323d828185c4cd99e6600f4d435c274baf1d9c283fa4057cfdc849f0602189d07124868d816f11530b5f20f50bf08bc8ddccba14316876d1ae88f07e9dea45a8adad5e3b274d6ee0fdccb19af46ac882e621f14953e6f4e49161bf929128cf47964a157c4b1e50d676a908405856c2e78089beee565e42ff186220361c16f7533344e10886a514728b6baa1aaca50331a13229fbde87e5cbdb1098bd40ee1cf665919ac35ddb53d892b62ebed7e45b008c7332ab40ec37eec0998c1d078d344f84f5afcf1db5b7c3b0c8566c7e9a06c02f3fcc4ec018c849943bd4f6976bf463014f4b0a1101e5d9cd3f112158ca3315f1d115df69c97cd4588a06848538588c770a57be6eac7a79804dd2b415e8838b46be8aa42ec487a07161b0da100ca43122c6f6f06cc6e8c4c3714e98937ab90d43d0e2f402011d8948a067bc3b6030fc106481fe43857695e994b170e9e0c603d26b84afd81a71659cd279eb5bdacae0a506a426b31725f874398086867008bec47dba3cae298528248665869d6bd2e7fd6cde3596b9df4166f6f211e097f5b7c335c65cc53c58540b39488bf8b699d4f4d0815f40d70a0c9494f053c3d7f4165fd79901be0b090ee3c177a3fc355a900d837fda6a4e70702f10085bec0f53b4037811166c4831b90af93a1f9c256f04062fc78a06c80f7217300f6467df19c3ea9ee99852958e05180414a5f7b7478d017cc1374878ca1981ac8f0faeb19f5e75780d4303f2e93a32858c5385a23d577639ce39c42ccfe2a846ec048cea73252a12a2f80156b58d2f7e3af6131af816fe68d348ba680133df2983829006a313bc6307201d06819f77700e9de18a00b21082a751267f9a19bbfd67eecbdd31c6c926fabd1871b0eae7f684cb0b137dee6086c7a0d1399ed94f6c7084f5a1468057f9f82da858ec042bc3ff1a8c8efeb028bff81673cc1196c17158cadd03061c2b6992d48618c29fb9bffc318f83795e10664e5ebd96ab7cd0851840780b016648b59a09302ed8be1915f64d19370f002d1c8edb3792c7a4e0a52c857806073ed4a812762bb86a6c8982f4dd21a873d0abfb5f024d0bc53162f8fd779c40584e9c0291be95b50493b82751cb7c087f4811ffdc69415c8c7a91a6b4bef9413280a56f55660c142d89c9b3f118d46ff6a711754927c2aaaf059122e3c2a622998d825d66d24d64de0d634c1edec6db7e49a56b505eab9155a45ba18a6ea1d33f28c8340514d1138e2c40fe87f063716559b4ac0604acb3665b9a88ec3f52407a26a3d917fe507951d098df843496005aa0f43c1604098db357868cc190398d41b413c1fcdf27fff1895f4f7f32004c0b2b1ece9f6802d73a0c02fee2227f8b6476282419e59cb7d74611b95761ed76ab9e286b7abd2b8f794558a7c49acb9b014640cd7d3ecf7ccc8d580f37659b18d2c978a86f468c6aec042be808f1a749d2ade374e373d05c27b3ddbd332f6216e7c40e72f327147e4a48d4605ca40e987bf26486946cb87ff25266cff9547b8b2bbd4774431a0aa1f24725cb5baf1e62ee03879bd60370dbc9d8b6da8e1055b7c462dc62e06eb03bd74ed76fdd18a8daf085bb4d50cc2b0b0a0a52fe069f7720a86552a426778cfe750256fcdf016a329bd314a6d19451c5f1e320ac19e2feb8c89b09ed988edfa9808cb7e497cbf7d81c2101d387691a705e374ee8bfdaa03076072b07f1759d46038c1d9469bae46d51cd0510517dd0a977267f0c2ca162a899b0bcee1c229043906fd91de1de815ad1fa893c49295c4d2f4c27b581353d1920bebb4c673b1c752822071353d121e59e52ca65521cbd1b7daff218a8b39171b62baf930be198c27942449a25061be331f664610a4526d7baede5edd22d71c029b64379971e910af8305c20018df70cfa52a8250d605c1a87a728ecb5ac9e55642a90a63558024e4c9b9ea673fa3e3a046885a1fcd57950d72774a83a969b41c88d5d7b72b1d8558f2fafa3df90e414689a1cb4270e695d5fd30ebb049aa9061804010dc350863cab0f5240c0d351bdfd366299f4137ca44ea34b3ca6e012c4d424fe878db42da11e5ab507124a691f2624139668ae6602ee1d638a6b464a0a30d6023695e5ba5c203b6cbb98007229fb81bc8ea2d25563fc1e5f642d03bf4590d7bc099a1789ecc35851f308f933805d953c230b8dcd397a60950c27ebc319a7c56456580e51094ba12b1b08bdb708aed04b76e88b4b938e4b9a306b75d054a038d210754fe68c9a57679d2d33cdc62476fb654b067f71539d21406014cc69b799a2f97fa7c9bf9d58261286b356ea18e89a53b591a69b1ce27816f9108978ccc8901f5a23409580216f053a360492dfd4cc4567f7002f9ea26c72b6a33710232bf47c26314ccfea09f8ab1943ac0df70e1ca2202d9e854fc08bf0a993c38006f662a75a27f77248b6f70fd85ba4a2104ae0208b10542a94a586006ea86a154a0a141a315bf97fddb0223c2b5b507ba6f24d25ca2e8bdf824ea4d0f0f6ae51285b56a5731f11011539a824dcc24105f5c162744b5dbb6e5431f95aaf08174f9d02b1e9eefd513051f327d332821917324ceeddfd88a8e30b9cbd199a3a6d6619652d924b7c363c9dce01544452c85e2b1a137d0b802ce6c24d951c8808c892f04f26578d1cb182b741da8158270c803c44a5ee6995282813074e331c73dc7f59e118f55488cff258ed32e57eae0ed8cc82b0b8d8e6fa3a9bdba8669c0386cbdf4d51d3d9b6b6ee85b4d526e2cd8fa2acacdea13e1ffad1f5095e087af9a4f7c98a4e6d5d8efe4d15bb03023c3bdfa654ae25a7706dd8c554d1ebd85a5999a6745e766853886b239ba044895d0fbba9dad3a02524d5622d107599ec912abc3bef9ac278297b995a86216fa409d52eef9c9a2d8c9e9f91166f0091c00ecdfc53aa88264a0bdbea56bf5bdde72d4ea60384227a0d0f05e09a3807bb1d83d30e14c960effcd8e0ecd9722bed40c9976ddb7bf78cfc6f0c7cd32d503c2760b735e7178d3d5db8ad4eded23ebc892d9f9c95252bd809f9ad4fcb14e2b41d627270b69362fe8d4ac4e9add5c72210e25b6a03302b1490ad1b40cb4786acbde23ce079973f142dd1043adcceb18d84b5d290d1fb2caa8ac02ecce824a6e9b0b94c6ec950cdc68aae56b712131585e41f3bdb27b5136984c1c32aa2a3bb3a75012d2cf6dcde95e636fabc6423875349eae95b43a2090e31cd0f9f2e9cf7e700901e4597cc2336758fc0fbe2f4aef7e3ad0e790dccb8a94174f1391945603068a16a2558bb76e42e06b6f9104e06a3256ce6c13b44a2c34471f0cbe9e2ffb85a15d672864aff6ed8e6710391a8134369993281b9b7cdeeac43f23a54efb71a4449403716b2f26e69a9b4810173248071250b5131b56ce47cd41caaa3d50096fc5046d0785275906172b546b05747d0efabbcaa85c3db784c9908ea9215fc39f98b0ccd6e4c389436b70259f35ba155567457cef71478d65dc05db08485253967d62cc1f10e0c5aaf6d0982421fe6925d73a07dd474b476609d9fb63b9f6871ce092c7b04f0f6c013acd9e891874e5425b08e78beb0f12726c25217f6de1e0515c1874b1ba636b61b737f62fdc50a782470b0249147ffbf3a732291798c614cd3e47812c60501d6c8fbab24c07153d8c4ed5aef3e9f2a4f7379d2ff03b69fd1672f99a74ce00ca196b066113d07002ac43fc38c715080f8ff8b0c850a0ad5817be4c42c3d0e69668889bc5c1bf8ae2d1c377db27bf92f85e522ea64b1c26c3029de5bd1b063b7de6ed4a49035daeb1e2af8577eca4132658cc979d1a037e3cdbaae8596d417743d1ee1aa5f9569f01a460b55e895e2377a082be5eb89a14f4f96e70ee5e85f34643cafd7b42a683ec97e4c4254d59f0af8b52583a87866fc946559c4957eba08ee7ebe2ec51a560162c43d76ed2ece053d59246762e0041dfde9ce67a87fabae2cd95c4d414f4c91717bab4830c99c212c76ae2b9a801904b858b89657b086f5bce94d19d6dc673924fc1fa694cc4df2cd34e6b1fcd718d3c12a067b90f7f8181b6b3bc7a80821d7d7431f34d65d36a372ae3760c66401ef32a98a6803baa31195f72fe508d0426655c051784d3b456d2bfaa441352236436381b9d030b44a7e32b162174e93c743199e461e306af6610d9c7e4367b4f375281d14586df7c38f650fe8de4ea46b1048c99932075bca4e326b8c49645213582e732d01cfa4cbbaf571c32b1bf87a01419aa76053d5bf4502fd3994301beb2aef693459e55c8320a903276917818261a7ce47cdb9f6289e227c63d765b653f6c0487f42078c8bd800d065d8fa18e45dda633621019446ffe2763361f21df7f32ac205f9217066e8b5d60ace189c856787255fb064fe4c970ca03bd25be57053623c70a318ec92659f2fd29a0a0b43d3d4e591a4ea0578a2f5b95d2b9b962714240efdf0160fb97126a275dd020a4b804be0603551edcee12e8d110d013d3918e9530c58032b418ee513c11eff44bf93a883771ed4471825e9b43b34964d6cd25a5a196cb26fbbcd278b0758e573a4f96ea4480eeba59f9196e0a1ba3623fd5e4d745ad921ce291610572851a4495774e3c47923f9c06ebcb6d30046688260178540c75d5d3c884f5c753762fbfeab89d0ac1a5f1515344f23b347dfe65f969d46525b4a0fdaebdd9a0a22bc6e3a944155344f44eec16153e9a68ddcc312152a79a593aec6cdcfa42bc47ec93b05666f6a6a1be4c305cac67d0348caeb39c4ceefc01f5d08407c3c590724afa34418c6e97350e4ccd9ddb43ab41462ad8f59de0a671385b74590b816cdb27123142108f5f78ee98e3134b24118906f6b6e9837bae4a475f9f371463891c04d9837e4ff3ac8def19d6df74c00f2cb355a40df3b4da5348bea0edd68778e3c22c88757b416666f46647cdbfe7d4921ce356921aa1b19800259f3ec9f98c9830deaf665b317786eaddbcf12789e1e8768142013ef96b9cf590fc741700df791b97b5bf4462135e357a16d3992dd178bf8e1bb355961abbab0016062f78f489768559c234b2bcc563e8243b8d3d1ad4bf6e38b72ad23aefac0e49d4a6f38f8c45a2ecb0ad41c02beb385c27528766011f056e085ad01beeb776888ce964fd3101368a90d214be9ab65d0f9b92ec14f7998f1c9a7a945fc2c1a35ae33517be9cf11dbfd2674adba737e4b2f8eace698e26aaff596ea3899d8744b5f975982f38c4d5d0a6f352b9a19ed03a832691d006673402fb83879f707d9aec0025f304f04c90bbda1390f216db332e8fbc7db4bc24c77d73b73627f402245ca3e26536d02d4d623f815efc30965d96a8dca08eba91fcda7165e0e5d63342602293a38420dc45f8a6ef6a430adcc1ecc2ef7554c357d7038269372a44a4e97450b582c9e55dd9bb2820faee78f88db6ef92fd34b6f0110533d3708c075b1ac105c041f73c657c04c95a85809c8fa35d294ba28e656c7b30b55d60582df88bd1361cc42b93444908880a43048627d9cef367a64495693e099f0ea443608b2ededc8cbb695c3f9f23de8a5ec1d1f86281e16bc212495c72e1f0c83e17fa7f55d40f6056ef27bf95c4ec0401dfbef19264c9b686d859d0b07167735344b915e9e0da6b2e69288914007309344f173f4a0922a5478a2c505e5a65bd7cf984d9baf16f84f050be745450f509750a62ca22c683a23f7bb5cc7d2958a81c1a7a8c61aa30dbf6c8a94248c3f44230af700974191944dcd2a526c7dba97780871b5e750748eed0bd6bfd6351e21a2bb7418cc7411b0ddefb78a03c9ebed3855c447c91428974e2a103fd103597c771fa9ec39187068e9d97eab44ab98a3eee8c3c30f03ed7722b848580243643e3b063c4bf80b911c5db2722010cf8a8fb25cb575065f89d5aeaad453da78a6e4647dee83e23624cf0ee45700d016fb51a14d99305c42d3cfb92178ea53b8f2f52e1ebb0276dc0f1242a9a1a2024ddb391419777f94efa7cc75a371c241a3aea706991da73f6e9fc1ef0a872011ae12b643c49fd50f8f241cc9f37fa8a2931d10e86573a1e6d8e341fd64578b254cd1c15ecc26bb1a7df93d1b8377b1fc21a783241f5a004ff6280f93f666496a47eec1712c27747640614c8d04a8e77fae348cfa33f1153216dec503588d13f33ccb2c500dd1f8701f68099915a7f3345002f7ca53e3dd2fe01d4d1d68e24b7b2b044182242dbc51424c8a0b0acee71e1546840937e92b998324ba946caf6c1d076f16037d5835c42e7e43cd87f3deb7cab181000b3abda286899148977bd577a71e0251d27b6a905bf2e2ff6897e66b103b8af3aaa079803afa6153e61afcc61e73ae8d11f6f213db1ed2e118e49abe5868a67964e131f2fecc05c91c61261f70b0eaaebe90395953cabdb468a71652d310e68751abfcdd72c5fc4da6ffaafe5922a7f219d6f493ef251a9b09a6ee9912ac686565523853e542c48b9f833697c97497b7874f825de324f98213c1c2a0c80211d11dbdaa31cfaf4eb65ea78698103c9d99cb6df81e1892acfc44962800286f11073096108d4b46300328677ae324996464387fb270641f56f5ff91f4877276c0bc3bdd580c50a05985821f50d1d2d94df76d5598770ca269817088fcc4b5540538e0bcae23e26ed8e109ef6f529561b98de7503ba01daaf4dea755bcc46a6e1d71a78ec757955bf7fd3c2f108a5bfa486624d4811375be59977c2170d93c1f712fd1211ae6a7804d9a8a1a0108662060bb104cb6480e161518ef10fdcbf7a58dc1265a5c32f242587314145c5fdbf02d9d00d3126b8646f671d055d051f486757071a7cf0d7aa6c6e4e870b2778f97437215f97c182067bbe96646e2488da641e8150d896cc339cc4fa09dae5f113207969dded914689de0bcf9ab796e4a00befc21390d328888bf7b5116cac6fd08faa171126870d1964ebf2e9d2acd04d3cee8a070fe0581952b23e98d491ea7d7a48df8700d6aa5936a06f87f4c448281884b40b0b434dadce673cc8814a82657053b510972489b66cc9b65cae5c939e1271a5cd2c5529a090260e3e31b59106c406d541d347c36583d570d6dd449ede9fecf5c29bc579a6a8c047b92a216302047672484167522987d0032582060e0da1069a868bfecfda1023aabdbe91ad72132f29122a9a890124d9f3899c9cebca95755857e9764448e979f43de555952bd1712cc04108bc59356af1d43a731912045ed63cd355ba8e5917c78d3055de7f766181f08c6973d440288176e87b5257b0c560ac25962bdd40248863d6558e0d98cd624988aff9123148586dcc9a25e63fe3a05ea750177720afda58e0a7144bf4dc401fe6796cf268e95f070db5d48e6463d2f3d310e4ef8317949112768c65a43ee85684349f4284023fd2e31176afd9369c647676a78392cc3e32121188700450693f024f2c8f74d1ab23ec5bbe1c34fb1652c7a465d2420c8cb9fe838f2bfc4279082c411970854ee0e5ccfd6e78863f3e47ebeeb0bb12e9f4a00881a28fd33533d1d958c279ba339d227adaa40d798577526b92445b368bd5471745b14e13cbeed7e7642486c145ae0942e193905d63bf94141480c843588bc5e633c6b6841fc1354375395e1d6839d11c4e7a4823184a2bc3fb82fcdbc9516ae9c282f3e8473755151a475dc92cd3312f204947cd2eb8cde95c466e5856848934709ba9e30871fce6d782857e0a216a4eaf4b37719700222aea041e7aec4addfc99df3205aa9198832715fae4f84844df42c094820cb756d59afef235b95e641a22235b7c6cab7ce6171a6d5117a9ac308166eed3fd62722d54d96c34a62cececbf5abfcd2ec15e6d8a348b441f304b1c7ebb772fb794bc5b0820d32255349b33ce158734e02556aa3679b433c70411393a7e913346a50296f6832c74eb657c0521b860f89906114c5f36d5983ef70f900d9802d41a3a08c1df354f79636bff8c737eb5f0cbf3435a18e4405a54b30f6a4d604f9f5cb02dc31813dbd6e61e9374b4b8d1f5e2713563db4e83f166907d4745bcfaad845df906daf491b8695903f669a35e14b4c04acf36ea9ebbb7dad0ebc96d97f6a9e6d7aa68a8837c6f8365a42f25ad8d9b804266e342a82d809ca0042aa5fbb02f579b333890b754d1c46f0f1fb2e7592516afde79ecf3c0f20ab539f5e7c702162766863890209c36bd1e57ea9b2e72face499d785189d613338f2d3067f3ac8a5126b8ec7665bb7349c31b1b6b68f9cd0787fe7d5f7e295df8c7a945ce511a52fff156696bc01244150c8c18370a123cf977c3f38b497a9559fb12fd7e662240091bea39bbf33afcf87c4db4b99d98aa487fa3ac835309ff9116a8a9dfe6f781ec80c36cc5033bf2fca1174e43383a2b15c14b4dad40cbcb3c59bd09a6c5c14f0fb83db55bc176c2c27adf5a5ee5c1472278da43f635acec0ba4771fc3d1eb2c4be97a30fba3a8f64e2d7b70306ab2a63726c4efcaaae97af574f4750ae0b4ed33238926a124c10a2a0a88d9fc2e5c5c2c4aa93c4020bf181199edd2fe823991da472f27dae2f72151cf9c7dbf43910b37230806dc841e741f407566353bf875d82b14034824e8c1c15a45403e1a9c401ffd515287e0cfe1c8728a6e9da34b18a7c6109ace3ee0e4211c93523ac121b83edfd4e5c1f0586a00fac7e7d06221787053c1ef0ba0939b4f4f4078e08a5fbd06e33431bb96b0b6c6db3fcad6a7934be97aed1202550fb6fc014c4b1c355415306256c06d9cec3c623dd6fd41d7f55e68e67456a6ef48e195736ac51bbb852b4ef47bd734735e48f00fc8e1dc334e066460256597f2dada9ddfa82580c8a643d1b31beb2d3ad8df94bf79922755d18949fa633ae36b273c4a5984974df21977823dfd4cdede96bd125551dc7ed9ef674edbb75f9367e98405b344bc55d9442d68364ca925d66881e5c7f1a350aca2a487cbf8818c4e1071a82da83f1139fd1b5c5aa66b208a51f9c672c8eab0d94399a8b0c6e98957bd3ef5003b1d11c238c0800d4ca2916806053f2be3d20c686858263c122bc22bc886374c029406888c08ea58a6355e064fce599c95d5576d854b43c521a30f17bf0e6f0f01a95daf32c4f0fbcc9d1539f52cbc4cbd03e3a0376729de415755bfe54afd7ed2fc0a8cee3062465045f88d5fcea9ab96bdbad46108f8619ee92ba58a7490624a83931df7759a529740c00481d4850098185e6dfb8aaf2cfcdf21d04d8497ca029c11cc76e5e464f7f46f2023b78467170232410569885fdcc0950cc1371aae27b5e464097ccc9d677db051ddbc829f962ff39898030169d3fdeb38120a0b6c3ab540dde04d5aaba10e147f2f6c76ba2b717251043bef2d680974e3dddb287de49356c0f46411baef662d7d98ee361ac41221a5caa7c3f0ebe787263267f38500eeeb78b7f79f2668912a501f1db8c633a6ece19b2ca35b97f39655733a988dc9c050b1487f8942374d647ee81d56618b3aa048599b17232fdaf2655a91c3c9f32bd7525aa25dbb0077ff07eb47f645905306885e0e05a91b14709ce5b2cd4a880582bcb92896df1401446662714e98c1be2871c818e1e85453c1881e003111d4fb62d66bd0bc9601cc19382a74bd568536fc4a127131740bcee58042a9603de6282d6b0cccda7f5bb9090e81dd842faf2afd6a1f16663f57d9d18f676bc43f751bf4a1985a178664f86dac90d10e2d43cffd0873481e89abbf5c8e49fdb503092c1c0f27c4cd3e08d48fa6dc91c26d3a3994315f86e9c02573fa072397daa1d77d9d6dd689fc994c0e5373da8156191f97994cb6a17e67c4fcdbcf3587b79754a0914031547ecfda050b8925532bf14e1d5ad0e954de067ce7046251246f2225be55c7335f32dd0b43441edb1ee405d615abb14690fb5a76e395b6497c09b27a2ef04d44f432fa4806fa8b2e1d36584f0b92c6047123b9b056728e5a9b4e8b6a65dc4c512c261a963c760a549c02a0b4f36b587d0501b714dea7073402343ded9db5d4f32d86c659ea781170dfadbcc1440bd4d0d6f967558485b9062e0221a9afc828c794bafc8c362d5110f299a1e2cf986634f53b91da1b3897739ca2793f8b5b1a80bc1fc171e96900b5863acb9a10eb473cd89e8c4f0b77575f5a3c77f718ef3e8d8fe18a10349a41f7532898f5409d76f113b445f0cf9ea51cb66113ec9903e6a3229cba27f31fc8708db75a81b723ba5ed6ba27e78d7614dabbc4a839e4145e3978efdc018a796039092d587b2f60d68237516a493a4b6972441d4e2bc0a607521cb1d56cf6c104807f4c0c4040e2cb4bc0fbbd57496cd798a4ec77eb83947f2ce49f573f6af2f9469f16595afc7b84f70080d12a7cfcf5ecffa7d5165c342c90057dbe75c01cb8c831590526c9145dc4769afc5fcd61193722c5dfc89761cc07642f199088201c6da7aceac1a2987eeab2e5ca9d2b0e1ff6bc10098715ca9f3d776bcf4c9b5e1345736f65da0f17bb1d9c1cdde974f38f857fecd476e3d614074ca8a44160e63a6ff24464a3e6ee854a24fb2b03a7b16ae667856b04d55179d0da9758e0c370723854d8fb1e568fa0e2ef72f4b10e2134b7988bbfcb27a55857df25e3a605461e5eb2fd4245fbc6fc422d4a63346a03777e296f4134dc6c756170fc218dec5270e31bdac6e310fa3038d40347bbce9852da406ed6d88c7d514324b0854d47272b2984086a3db454a512be7ccf87111378372c257447d8965bf1597380de5300c41b569467cc21b019ebf34f0655c2e0e67146d8d6d0318ef4b2820f1e41c6ae1e1d5c852b724882deb5bac8f1c9c0779ad78d72aeb6341dda394392b330d20871a29aafee6e7b1aa85b1b9367d23f2e9414c41b2d848865d18a2bd5101611fb55a6b834047516a16cf978055a2e62c641ef023845dfc6afeb3ed72ceda20b64d2e79388b53b0d572478b84bceb1ef21860a13c7e190a5758302faee5e50684d5085187c90518489a91fc49b273efa58954f853638a6e92dc3b7be46ef93245a07dcefb0fa78f0008748ddce928d4b3a67b22c2f50e937683b02e48e99f7e122c04f2ce36b0d4ab1f9d205ab8de9bf664c9a75ca33992b541793cabeb7f44a1a0b4966d0e6c2de08ae4deb872e2869050d26f418082daa0bbbf0428082e46e4b6a9a0fc4ae89e9d6c812fcb8401833719c3e2b54bc0f2f02422c76d5d3e3477320fdc657d61498a46516ccd28e394a946acf7961e8aafb20a3832b2cb4938010413809105b135c2d668f998f564cd5d05fb7cea89391c298a6470810b5a174dae137fe4361880b883ccb21bc56fe771e84eb9bb06c9264e31286b89457bc21e3855b76bf177ebf5e26b8f7925c14f6a801570e3bf1956afdea2da4452fd05c399888354e2885eba554dcbf484390d447d7c532d3e71c200a5e1a2a915d48b0b0dd2855049c39ca15576c3b948cf78382cb0bfb053ee08eccf4dd8d12db3edaf317ebb34d673775d7f343079374e0ff7b7b73697443f13964c6b0215cbb2eb6fb1d0beb492817e3b81558cf6e0a4a1e0e0940e69e293d6f4b586d4fde8a7ec74cbf40a9c1473da8bbb610eea6f530837d939f06c3b087bb7db3e023d97142c3c360e3e7403869f9746c26f096a48046beff5310a29dc49ffb84b128241ca4902340d6698938d6098806d745344198379ba07780e5c1ab93e3f5df5ad05603c6a24f43f160ea687f1143afd44d3dc845870f40851dc8a25f2b1f8c80947c6922010dc7f4cb5ec074c9457b0b923b83210e1d879d56b11bf234f76cab29fbc426b3e6fc30cca5c91335802ec60ba620686e1e0abe574736f097bf4ed7d24a6fba444da1a124107720e1c995298932e572dbf50167d743e30b9636835e1862c85ce675be7296e407c76216f11c0be8c2c2e05fc51ac84f2b746174fa0bc8fc569fd114436cbe968f166f19176dca8a86ec8e214d976753fbaeea39f7c0578732e89e5ede113dab4b13cae5e675e1f98d32a68e8b80ed41c14a29e19adb0f3f63ffabb1973f238bd04453aaff69577180c9fc57a4eee89de0f4c51e45d3a7b273e35700f51a9ff605c98038fd58105ddc6af511354f6aa5050c28dddf28dc375ddb86a13901b03525ae22212bec2638986db8236de27ea66a1ca3c9ffbc2945e2083e4e05db08e38c27a79fa010372caebb76441ca49bf80e6252f9e00e9f895fc2f144f019dff9fd4fa6f128556f4afb7f35c989a2d9ad48e3b12e0c6931d761e7f8a6e8c4e1683ce6d8e5712a695ee64977cf7814ade927c3671b25da2174b3f366e875632be45ec179fc10110e574732ea1144e3f65ec1ad9f76d2b0afc6489e0c00e9c635544e8dedaa019e6dd03abf2a9f51c13d506b728e1005c54e23122f54af9d8014862f5d6c84772172a2262261c381a60036f926988c9c9199322c53cd61ab950bc67dc480ba836ee5e35b06411e98240cdf64e76ad5d52d394a3f5ca8e80a5e4df017bac917a1ca774398c3fd8072296ee09b27c6fb8ea0bdbc11af36634d987550482c5c75db8ddc6733e47f05981781a14dfe092da5343f18505a5c9d1dc52f5e6fb8e849d490acf0c0f558d1f389c0373531e0862602bcf189901b0fbd442db496f5066ea8b8a953bc09cc2acb512eebeb6d12792c1f62e189156d08a433ff82f43411ea6a93338c19026396c7d0190bbe15f149449d18718f003af32f35370c6e0028b5794d9d108cd4d70ee861864b175aa905955e39638400bd66ddf442945ac0ee05321c1fe4dc4446e343f70f6e882e950b6817414caf2d5a61aabe16288dd3331b60ccf15f62a7682b6008d2777ce3f0b5113c6fd517939beb2c5f5dcfe3beb3165923618927738773e05503f263da54890f626f29c79fd8d6f69fe8b05d29162d06bca5f17b2dee6d1aa16037995e0a8387b48b1d9cbc272905c102f78db8bcd46fba6c505c1a80f7df0e65cc6147babe222bbcd9d51ee5e9760f407bfedbce9b1ed4b2fc0d5003df49da2d3883eac91f76b34c95f767017e6322f094bdd524d0c0e5789f379c7856c1c0c8c80623b91b43cddc6c2df2c0dd4a2d323a625904e8bfa5290e997f67524c9ee40e612d08ec9ed1303e9dbe612b6d46e2ee6c8fdcac3b312d0aec9db507ba73525c8990f1d7021332bd6b3934c14321bd1b72e602bcf098ebfabcb50e43a33d6cdede4ceb89116f3894ec4d4ab8de58f483a8164dfb7d9d6c5e4dd17738daa138fde5917177568f0ec532fe4ce2e44ab51609874813d88cbbec72149d2168dcc1bbc90823eb7cb4c4785a73f100592a8dd3350b20c6e5bff04c657ff982545cadad14a26316d8184fbec64dd11f798455dcb5164dbfc013334d6c0ddc107cfa068edc5f79539d88f7c196a552a550ba660788f9ec57fe14f82a5ff02a2fade5ad368cf1aa0576d2081a593cb10a80c234cbe5603859b7978ae458fdd1c255f6514bc6b2ff7c002486efe42044ece791aa1ad2bd52748b03ba86f28258305e11a903c923d591a7f1273a5afbcf371e72cffb38501d6968ec494c76dcae7484786933e02e0b38dd81b785c9a985012af52c0560b914e3eb9bb718c009f4e8093714815b04bf451061443321646fb9b794526e99a40c08081e081708283c5ff3355f5d87bd6b695a359af60182d316fae2423c0875719a0f99b449732139cc65524580edef2ec451f3d268caf15b1749a4276150860dc51a2247a3bbcf1e8bf140d31b8fe1d214e226a72e8d1fb4b3dd65be43932c0eba007ccfe717b10915da05a00b3c0fca32e06141431f3e7f01c9f2093300be279c347a72190de7ca4d5f112d4e215e69dab6af86f04d270b1f8ad0159481270dec416e1b0ee11298f4f3fd4834053e184e5bcbf61bcd617ee3b149932a97c9141b9d3912e541e4a442a23c0a97adaca8a8f8a4511af599cc079a9a3a9827ffa75e28bef7a229fff7e28a0e66cac1ff421186e77328a23eef85e201b63857df85e200b63857d308533685b05dfb409b06f05139802774d416276d83210c1f5c464f7e237283f698c35c7643d161930a79f21757886c9f45b6bfcb668ec300dfffd21c655de6329781a50ae327999911237cf9cb5ffeb2135ab42df7c3d5b32ff4cbd5cb19e7fc7d39eb9864262d1397c9cc24c7a487896cfb9bd498c44c8498bc4c6e485a242d921649eba5bd4a274729e528891aacb5d6d24cd2922a6fb9c96bac0713b5442e37bd7404ec9e2149cd0e6d91a445d20ac08f00dc10d9fea1adf463fb976038fcc0c18744619854d56cb3bd8aed8f5293aaf9263eb6bfc9cb64874cd5953cf9cf1d6caf47c894a8454f1509929657057d228516452d1c7648d4470687965465d4e70738d4ec4f122633a9eafe8386163ba4ca7b500e9396e7830a93aaf95e19562b514b5423551546a61441a26a0ea4a9aeeaaa0926ad7b5bf28a66fcf303891225216a7d9210ddd020928072e017cd38aada902651121c8fcd89a29b48d27ae9ce31ca14ad16a25b8b964c11626e12131ba2cc8268b1ae4cfc8a7dbfaeba30857b5ca22d3285e3429e0fb64b2acae079301e8e1f928aba9229dec1648abf090e38d838aa9ab4b6bf89c9bd302c19936ce4070bd9d7fa665fced7a3e68b7d42bed777b3fda5bda3cbbce31d41ea52e518e34af204084653de3b28b6fd8e3cb6ffe7ffb9a46a24e7786fb0fd436cd0e2d7aab17a0343494fb6c67ea8352a39703e75895d971b03f9023771c71abb638ddd11df7c32dfccc772d3d7628d2d256ec8933f0e53cccd71c7af0502d938aa7edfbd2c641e17082023b5a46c39ca4933520ea90749468a9184905ea41bdad23692cd5a9b8d8e5e14d6e894c8810b8b785b5454ee78db9ea77f1f6b4133e8f931a8b5dfbd2d91c0baf608e6779f2d2d82b3518cb5269154dca0394a3f86969ebe49aafcb10d472516a127792258442516de973c112c996e9f9be4094d6a4955b8fd493522de9ef7802f8c412fdb359928f8026d385422f9d17ba51f445f12d62879d167921238c3e207d2933c29046758e02779d2979458fc60f2256f12822d7a2285608c9efc41193d390b1c5ac0594abe2404494210e6a24570a4c5517864046fde0f8137e869aae451a1f7ef4019232f1d35128a13c4122a7d4500955280b797df2b2939cbc84853a4711c19c7919134122a813469a4a93ce671f44a4a48ac8dd4b236f9c3daac6bfe884145b6545f973e2dfb604988f98136ed7d2309c82b0cc124763bef1defeb75f77a5e77492d94977d6d7f92ebbe8fde9fd4c351b3ae46258ea50817902ca2c7d5da9cc5da688a84b126d95c905ad444726dc7a39f5bc728acf1021651486add92dca4962580cc1211f297c930695bec685dd6c6bed8969db13decca5aa9724a7900b77d29495368ebe2a92c5536e48babc26f632ccc76fb92431955d9a2948db6a89233123225cbf8c9c150236999655b58dbda50b43788d831af328ca3aa18b678dad3dfdeb0a3cd61632c8eed5d685be8c9336bfbc7c8cc92aafb241e7ac716738eed17a82cde9c45b86db12ddd9e9ead1ccd79080e33ab95666998c6d1321a46bbf48d9ed135dc89edc446d327357a42853c9962fb4914dbeb09907d12abb31fdb7f14338249157ded118fd18e1ca79554ddf7cf631e479af268f4e48f692eafbaeacca37934cfe6ddbc2b2847294729a5943bb19dd8b8b07e0c3fb1d193634d02c0164f6cfef9b4fdde7b2fa594524a29c7716ad09ccbc4131bc8033eb1d1d428c7f6b7ed68a13cc63d7add60fb0b31f74aa6ccd21557b04ff0d9f3473132556f8c585235bfd5ca91232606070ebd1a9d418b75b6e278b87b8675bcf9c54dbe0af3e826bdca2b8ab1d6249269db6646348eb2bfd22bbdd22b8c57302c99191c664653188173ea1cad9c9da53be7042cc6321e7c4f40a6cc37808a297b5f060dc0c3c7a33f201d028d8c46df287bdd6884af1d8d46539e705c96b55a6bb54844eabacef3bc9cbf6fcef9a0c7436a211291340824d22323229168341a91909094949490482413939bd4393939119d8c9c9c9c9c844e4e4eb277d2e17b727262eb09474f7c0ea0560bcab8b89bd39b2ea41622520b11087a3c1e11a98588d44244128d8c88449fd168e4198d46a351e5a88fa634a9f65e8c3b3de77b3b7bbec8135b88e67c7065071d408fe7f3d1201d44245028343222128d462424df2c9590481f297b1d8944229148539edc8b31ee3a93e77939e7ef23d9d173ce39e7fc7ca8cf8ec39d0034e9d339abd0ae23d9d1397f2018e35f9d2ee67b79829e39f59c7382a69fcc681a84ad558b3e7ced4807821ecfe7f3d15a8340211d64acf0e05b8e8c8844a2d16884b1e7f3cd9f932b9d404ad9b669ad4119209fd42312b13f7ace0902cd506864442402658c46a00c2799170402658448369a64a3455f1e7d246ad094869f9b26c94917340da5ad1c2d11f9c4120472401a466c200c848131d0e6a81d1007ac6d7ff006a4813aa00c94de62b5756981ddd85499ca9a737a0b4dd51aaeb5f9b5566b34a51d7663135346e794d3ade70395a46bba465b9cab307f3eb0f419e1a2a9efcbd1840ccceb1b6d9e9b27146d9129bb7d2d9f8ba3c05bc4daecede2b837ae8d8f28b4b66fbce1ff8d52f5c911037af1c3f111793c1e8f67f42af345a542ba983fbf50633488f9a23a41baa0a94ae3a797f9a2c6481773be4c060539a4a62261b3e9f70769f9b948469cc8722bbbf22ce7e41eb926c7b290fcca37ce4dd05a6b2dcbfe94924469de215315264f2da9922abbc272144076fe917d6c4f79180a2d05a90b2f06cfab9d61fb7bdc18646a2cde13c1d285a2c661bea1f89e8d9e6a744aeaf148461d7a61857931e9855e8ce5281967c0678aedff999dac5c7bd36173c26c5a252868ee452fb69d880dbbdcdafefaa56d1c5573cef79ec83e48608cb387b327052daa6c1d262a2551f5a513d8012f6ca62452e5ec63d455519199b1a953708d735cbd6e605cb3356d266bd24cdb61dbe10747a92280547d5cd6a05746eb7315ced14de274992b99e22efc396e96a3ac8ca3ea388e302c19177ec265415012b4586760e0f7b7b6e27be9a9be5a60106330073007ec01cab63f5803c64021e00bbcd9feb8a5bd968b5481eeb55ab885334887e6403a4edb1f744995e52c7882d20ce1782df76adc73857648d4875a52353f00a19a4d80ed0f9a49d57db986edaf5b52d5bd0701571a0f8044551d9508b50d6b10ef8e20ecaf34558725827db1b641bc3b5a640a680a226cbda30dfbeed8370479adee8598fe5eeb4afb5d096c855e211b475db005b6c056d7b16466685ac83e979ae7679880453344107374f1d466787e96405aa67868098da29389ed439ab0a940021249c4e612ea8c698250ac4ec001388ab3088fb442a2e188fba24cc3f628c5d7e3f1783c9ef7807488e90956398b6ff6bec7b872977aec537cbbc7d9fbb68aac79dec15a6dc6e7e3819171d38c8d4c81ddd0726ad6a5c5e39f13b0183d98a0c6042040d39109c15a6b93499b74e53830f5994c94454cb4ac2d1f305138c4f1b745dcda60c515b7913acb8254f99e5a905d9028ee1d0c45bcf14c7251d00ee3da98a64c267737e5c02688435c066daa9c7d939996390a0771d485c162749dcc8ca5799e8d107a95950f04b1830e329903c309a984484621929111f448c5d9fef539fc16a6357f606ff4e0be7d1a5295278ccf17b7ed694528142b188a501b0d8562c5d9f35dcebedfc115e0797f047842b1f660cf7715a9028944a1587fece9381e8ab5b5e7571c478d84d24d3ac667193da5a15028140a918c4846d762afcb9f87342bacde388c918acac84868042a551999325f7f3ca50a932937a51acb912935370db1a34cb12f76656baccbf618715006d6da0edb0e8f28a1adb5d676b8c3b6d2b89725337eb0aecbb33ccbb3590fef3a52adaec9e2468bf9e6bae55bbe65d1ba6668caaeec6ac67e33df4cd7ddef4a419076cd3137651c2e68d3066b60acabbe7561ecb2ae3caba08cd216f3ed465396873cf9fb68f96181acb6685ddb6f56c64d725bd78b7926a9d3b0d6722ccb4a2648d0628ed518cdb26c03dfd8fee158e59b4c71c11c75e3285c7bec29e22d639f62cf63cdd0b46a5c1facf30f0c5652cacaaa1961ea8d46c5b7869c50092d2029a5297c71e5ac5d5d1900d1b981c28cef1c40372018900bd402d98058201ad2eb4d4bd3c6765b449ebc46f7f971a9067eafc5c54d9f17cf8e9b3e2d3e9cc4f40efdb8e8d86a9e1ccfcc2364fbe705d63638643b98030a0169321aa624bd125e42832f6e02632b3769104c8b3a160371a31c3aa66302f80f8d1365dc44820983fe2d3e687c98013893494f9ac22fdaa21e4faca0453cd293b7e8c94d2615157165f483776dff77398a091cab3d7500a9681c2d731410f40c77585911c1163dd21468d4a31e41a31ef5081af5081af5081a75cd02b66c024667eb180c4c4eb8651330b01cd812b0651339b0d93a4653201807ade615349a02bdf8c91f06cf5004c968100de943038d2d1f195d8416c199147ac880331c0cd9e08ca640e30cc6053361ecd72d992062db1997348e9fb00d75cc0a6ded101913b03dc4489ed0a0950862fd307d68e2089c2d9b38e2658ba02d9b38a26683469ad2b1d878dd6da881c8946feb984c711868119cc54023cb4d3ab6fd59dc9739bb091c086902072d8de3301eaf2d8246180f97ed0f7af1d46491b023e88064b63f4e0b0aabc9529292e5fbbe1a2d8258a01668b43a7608677324c771b6c6952ccd06d9fe336e721a4aa92d8146d00b68f5e98064404338ae4a5a7393f809d9feb9e526ff6ab68357e818fd6e4653208c9ffc65683435db1f082b82c733d08641dc19350ab030c1106ef2b78920d6f61a4f43dec097184d7d3abe13dbfee08ba63edbeb1342db9f0bbf348c15dbfe2db2d69eb98046ae25048da071fbdf1568048d94da640590b13c874d039379394eab078bd5834b0f3122448810f1f719ef2010b2428bceba994143da66cc1edc63205fd02b384b3a4bc69c4583f88baf463741c05133ecc159b6c65195c572560fa4949decf4a724c409e615fe7542807b08bcbdcfb9d6ea53db5280f70c69ae7ad32f5653bd49551199c29e0fbffed425e6131736999028eb8444d50bbbd50a5e49ee6b29f5dcaf45e4c9df138ad6c6f607c32f14ed0dd2b638485bc4d574c32693e775375ef305abe9dcd06c66436278b258ee5f27274e5695617fb2ae7467f98cb7b60a3dcd97540560fbcf1ba9026df79e34defc441f7c69aefb0bb2a13be0274ffde0a718f9c202d2051252850199e2effe201b1abf3859a08c01b8a44eb725c84397b70f41786a06710bc8d4f471236f76d841071d0c60000108608b52365faf29636971b258d22535598316e7eb66be982559fac14da0162dced78ba6b4b61c58019923c3b79c2e66a869ca0253e0388c9807b3b912b3a5b9492a313b739410477973ceefd3b6cf16623199df5c82e078cd777ce639566a97395934855958c47eefbd31a91ac0bed371ec4fd16fb6bfcbdce4b189e3b712e40b0a86c91282e6b88dc3fce531378154cd8fc914ff13c8941791274f39108f499884798c010c30994a25131310c86f32c5e79c99ac193373ec983c262b3613128bc5eeb57d4080e52c936517cf83b6879bbc4649b27472835d48e345e7d0beaceb095a54f141721c37ed5631d1b12bb9841259e0706091b03b90a89b3fdb243a7cbbec791db681ddc42c2cbbe48cafed7268a6450fe6d160de8da3841802ef0fe3386ace84b897e3d91c755f88213c5990dd79433c9dedefdddebbd15e2cf4606ef260f5e69c73d65eb6dfa754df53ebebd2fc0aca98f7fb94e4f6d40f63fcde835f411920f7a117eb0b7f8611fafde590cc4cabc6d5e3d5e2ddec68ad85b12fb6656d2ccbd26090520c7e18e4829edf18b498a9b0fd3314dbb93d658acb9ba3c41c1362c680a0428642aa648a7df114985f784648d5cd01c4375bc12bb39ce39672129926fb9819f30c5edd5618752d8ecfeeb0396ccc17da7126e790aa2efbc83cf28eeec318630c82618d17b07c3f8eded7611896cc0c0d188594d2931e0d8ef3c0cf033f2868faf845292bc29f060b5a40844459cf9b466cff0efb90af63ea75b71b8d9c6d6037ab6ec7002e94d1518ef242b941814171a1b4506c50582834db9f4a9bce2e33db68aaca64d926b3d59bed4fd39a61b9c9e73753282f7e721165a4b945a6c8a4caabacca505e5070c8d415c0f6475949d5a42863898da66e8832a2a08c25b6129491a64aa69027ff92da0eb69750b1bde4454f5e622b7995d8b67fb6855fd0146544b921535986324ad524e5c8349418190a0e2ecc346e022370370d516a504637b564caa88517998d2a146105296264305fa0d9fe55d6e2b2f39889c8937f2ef2dab01bd96543196ddb1f658ca1a0c4f634800b846265ed0c18cc390af2806360165068d1b6b4388a7ba73368ea7bfc5bc6a029903e8e2153fc6dbdb80b2bcf2d8e8c7dc15897bdb133b6c65bad28f53ccff3bc8fe713ce44bb3242a69e9e5ece22e60c66cb7cb1030c606c7f87f1540c7af27c8454759666bb95c9194b95b7e8d431fc69238a7369eeaf947748174490295e937fa821dbd8728f5a6e795e28da18d64ca6715467633c9687dd6173644f685776e59f3d9ee7799ef79ee77f0715138cd5ea7b7ff02b61983b6303bbc9b20f8eae93c964f4e422a329124946921293eebdd7e28fe1244b025b44b63f18854c819f2944aa425331050988fe41037d5c29b67fa80d5abcb25babfbcebaae6dfbcb68f8cae849091d72394afc68bb870d06458033008d90291539c2d1ea854f0c12653fda7c11c474e12f65341ac944a6ee7d91b6cef8235dcb510ffefb9229feb23adb9f4653a00d79f2bf41c361db600cd872c356a7c30346c1a34553a00bc80f1f1b6c812da08d1c03e2006f6c7fff7cc4ee2a41a6f87795205f7435b8b27aabb72b932939ec5eb2cbe8a61619d8025bf28b602b7b1ee7877431bfd5831c21419878b97e90f34dac45f65115d6da0edb0e3fa12597bd0e67afc3d65a6b3bdc615b59321726e7942b2b52d69032255a6a2c2f903f5c44602cf7e593e40ef8e51601b0e59b48182366c10d184d2c38acb172637981080a2c3714f902fc451477e9d96e2ac917df74e199a1aed1b7b6b54cf16d02448b2bd60d724e297138914813894a69b1219086952eb070b8a325c771b564b2cdb49ac0d1aab0267000e1ea740adcda3699696bad90a5001c95c31e02023bc8213256e974f9cf2f535c7c8ee37e659f369d35c42b5893e8ca4b95afc470565656aec85200a2e6585c0e33c8a49932f33571668b9b524a2a31a63080824524d5903a3a7ad0b0bcc002b9028bcd2153345a6ce7483e348f1d5bc61780692a86c4124b2cb1c4f903323267b6fc45f98519235df88b928a08b074a04579a329c9f2012ed4ee772fe808dab728691b007f78fb3274d8f0f35311e68eeef064b51cc551166bb26a85817d31a0bdbeeffbbeeffbbeefde5b83a5cafebd1c9df47b7d546cffef877cc1a4ca6effa688623b90ed47c08f3b99ba3b5a5debb6648affc7054d8323e41de73442a26e205114bc33b58584fdec49cfa9a7cefddb2530f78d814c0143ab9563efbd63eb8577f0f0f1e3b65ead188ec3c0afedafd7bd16d7191a5a12f0adf86241735e87afd7e16beb0a86f5aaa9d99a8d45a3b31a32ca5c97ee76a3e13269d0949438e47d39eabe650d60bbbc3bf6cd21ef0d796d74d6daaeebba4eef68af72474f1ab46c922d9bb8f1b239f00473db97dfef5e9433d1e6ec92ad599b9b721c255a7be3396cb7383ae8305d581fdba525834cad74ef6fc72055df4f61b758b13d0ddb47a9b23599e243a66e8c3cf9db2a6c4d02913f18c00093a9543231b1a3add9cb9229ee1f0002b4010d87f11cc817380642d81a7691297eed0dede8a6900ecdbd68c7eb2ba3af648d9029fe40d094b5b20c56d6aca4d9dab8bbf95d285a22dbdf768ec23888d4680ab4dd1bd7461130b4353bd63e2f478c68717860d7755de7d1c184dafeb59aa3fcbf0c7a24abeca29c6cb3367bbb33b7c6ee5c9dbbba34b7e5be783c1e8fc7e31e924cf1847668d917e425c3beb6ab85db8521b3ed59d8fe570aa99aff8d4b5c2940815fbc4cd8fe72a2ee8bf7092f6c6dba5842cdc7f6bf4a4815e8e7cdb1af1158dbe64c17fee2d5c1f6bf39902acfdf98db83ed1f841a6c5fadac8e547ddedfaa41bed0ef6fd92055e0536bb343e60b39128a60281c32555e4c40a5933823d14844225d6b3d76ac81a188770de44310b2ef33876ff7d34553d6464f39f3c5944d17fe20f88539a56982f962be4c176e6d362341c0f8be8c9729eeeeb1580fd6d2a2b044d7755feebe0cd3f7f37f567f3ca0fe78c0aeebbaeecb5feeb0e75d578f97cd973bbf56cc608cb387b337a3a54ce174e4bff6bdec610cc3ebe6cfd0b46ab010c59c2c99199a3bc45aeb92e737f222e94dd37bac47423645f212c6e845d2cd7692cd768cb5269148fe4526d39431bc081068a43f9eaeeb7467ade5f2f7596bb9ecfdbda49638b7bd80b38c7ea4c4e28711921f7922584225163f845e147a225844254ff225252f123d495432fad149f719e907813ebafb749d1ee98fa71b8d44a2fc41af98f7b4a1fa231e1dc92323a1919191d0883732121a19190959ab1d005898b1d676d87658095aca142aaa98e10edbbae2ad9737ec5c41f306096bb1bcc064b3e30d2d5dd775f9eb3a4a697ea9927d4a2e97aa17c850c540ebd2c0d97a3b1c8bf5e079588a524b8b8f8219628dd9b3a5ebbe8c311dd181025bbbee76b806eb9ee05cb8197372bb84721fe73164cad63a7ac69cb25693d993a3b5da8c5a615a6c781e0d0ffc3cf063424bce5e90fe782c203dcff33cf0033fafcb19a625535c6eeaf162d1d3fc0fd70008b3da9c73561a15df8a2f0f4d5f54a151c45a6ba5945285d6d095cfbee2bef27d0c5f890186351ae00263f13c112cd7ae7870d87e80fd797d3e3db4046584a0d0fe9ed29c69d5ba45398b80694f16141ad3d30b71bbaf3265027a72154893bff759059e3cf91b4153ce4d57c6c90eb410dcfcf64ccd1618e3b37ce1a4f1d34442a6c448172c9912ce19111b74e80c5ab2ec9cd373cfca65b3dd666a76583a2b1adb625faecd26efbdf786bca02f8b76578ea23ba48fe11f983b52755fdc1ad868369aedbecc5849769a21c08b4153f9060e4cf398c7d15665f4e4195f50d2ea4d4653b5489661d9b55d1d9e98221f8bc3deb036bc4f586d37f22855f84939b6ce312f661c9f7befbd1f9badfb60cf0c4d0bd4b1f5a0ae68c19355c10ab24a516bde17b6db5aaca5141c6d8bed3acee67a6df62abe9db5b5fb6ed775f6f36e386ed8f840f0fb56302c999921bc5ef3f57a590b6256a6cd80f8c00823820ebcd4a4ce0c88500e2d7a37589e652be427f21299083909f9959dc84cacd80c84ccca4764236c86414e22ebd8b9883c83ed368affff9fda6cb521b319109966869399eb3b87dc23bb783b99956bf298616eadd66a0d0dda7efee135db903eb6e71e39099a63f28d5a93b2de68cabfe25a23ed195e9917ef0ec42b83eda16877b627142d1bb68767d82eda30ccc008328821554121526c28a2609db04fd4d4578fb0656d6ac5b89329af8877f36ede4db2dc7c70c19b82f86dc95aafb5796b8c4e7c7115446a63d0e2eb51bf380cd0bbbd77f36ed7d2c0340d76a894e25bb91017b4586d76b63fccee48951be48bb985873dec658a4f21d565bc2d339c17c3fe0ee93ace03761e507a421f5a94b4c9d11569c5a53cd3867ce1bd2abb385f529eb01b7da5b9bddc815d873fd0fbbeeffbbceffb62b8c733dfe646056765b5caa0d7c1b06466685a1e1911ace65083549a0aabaf8a535bd2ba575695a93230d3823cd8626555d6b695c5ba17862523a2c1f7a9a87c1fcff7f130418bb6e57bb1c65a741c7555a68aca0cc53ac59e2a9c9bbaaf54631463ad29e9f7178af5f5f17c42cf9755c02f7b1d4d8dab4767b38415c6586299bd12b8fdc3b2ed4fc3185d7811053346204b20515ca692c6c0d72bcdddd9e01c524a4f06f1b4077e1ef8f940cb14eef10356542f1c2051f689ed997effde772b2b2b397b1d9699b181dd546c6382cbb22ccbcec482581a987d591cdbb23d6af5baea752349680d4c22ba0a49c55ea1a95a8bc06c6cf73aafab36cbaa36cbaa357f1000f101ac8954516bb5406501828579d81d74cee979618d17b0746f5996d5b93c31dc2dde96196e9458cd9c8e14c673a810bfd117afb98e7599d5686ada665235009a548de4dc6c1c2df4d9cfcf60d02d5fe37db7732f77e76ddeba699bb7a9739b3b6e722aa47bfa62a8c55162e865f6f2bd64f1eef319f07ca10bbe07bf074316ddeb2782c505e0b3d880e7f5fff079d0775df84326c2c74fc987169acaef1f9eb9e0f3d9f7997a3e1b69c8253486c4b9136ad969b1b1981c2823cfcea31db5d3e6288e4a4bd5e0332466b2998ecbcd3ecc86388c5c9b6d7f9ff9ecda1c5a5d223ad7448882c57294e3180e8269b00c7e611cdcc23d3a87d1144c3b4c13d1443411ad710efe21537e6d21d2143a8aed3020241f211dfdd5e60c7e61163d2d018b0e5be2c6997027fc099932396cbe8080c9612687e98b869c9b068962834459215322982e3c766d1a2453bec35bda5b5a7b2be7ca68ea16b141db376a36cc721866d19407b35a8eea5ec707ffd8fe380766e119ec03f3c03b30ebda885c1bbe816ddc221ead3d1e0499e23f43aa401aa9ca41902f3a5c6b0266d5302b48be89e5d061f8fbbc09023d88006632ddc462399885591f4f09ecf3910c388ee3569c32409efc7fc47c65f3608b7265ced20e32c5e5c95f04806c02488e8333c42b4fd02b56d0b456cecef24ca5c94a7394af64ea901cdb6261aaadeed44aa3e14aa3511a0aa2456fc751a5ec8354e1f79f43ac1b2a4d5669b24a932976c7dbc92ddecd4d54ae98d0920a4d7945b20deaddbc5ba5895256ab70ce7267d89eacd6644aad82bb218c9a6cb1d25a69d8b3e9388aab55d06ad6462d52896cff4af36e5e11ef9671e41bd986fdbe5c6995866f5ec1b03288c3169b31d9cd5d82e0ccdadc99b39993dd290542ce28e63e206633a942e5708673feb9d7543235a70b04774ce716854c01414f6eaba236a7a0992e204c1a4a9cd30ef1e986ed530ddb5768196242be36e8ee73d6ef1f022036c0010f48d4fdc8b0fd573e61499766d0d49c6940a22610d901899a3833b6b232f3198bc9a275a8640cf807b9756846000000b315000020100c870322a160405416a5b50714000d6e804a64543e1b88a35192c3380a43c618c40800c6000018209129ad0244c28338cd58698393effe591d967f640e121aae91d78a86dff55ef5974638076546ae99bd1de72826466c5cf3b2cd858240eac77e99ba23f809f9c4c37253eb1e3f5255291c22459d5e2790fce19786127a04dc2f1e398387747ca9ad0c333f4cdbc863cd62c4af09729acdbe5c9684997e6085dd5293103431a97d8d36596b48775cbfbd0024ef2b990fe6127608fc7a47a6a2e7958a9886aba0a93a961f26f834a935a00473a074bc8b4cf0bf575547b6544f03973abb172a6a6b7763bc59bb30d5679c3a5c12fa3a9b71706b1c3db9f8ecd247d666dd08b66f1b9ef45118b83126304cca9c4b8a5afea51cfad57265eae6f7b98e34383bf384cfc1f111bc7994b7d830635cbdeccaaadcd4bf2f57b0e8e8644579387634568c0810e7282c001c82b1f8939161ff7be9fb0e779ff089ed9b69ed0879fcf023a0d42b1e73ee98c2ae3c4840c34a297f53d63b4512e69ff1110d2e972b68ac53af758f919b46b697afffa9805615a11db1dac4c195995d090142167851ce8ae134ff5d9992289cfe048b47675fdcda41f4a53478ada3723bb67ed024232ac7b75e44515820e54826583ea898729a42b01b8de9180787d5661765491ce73e9441156391dc63ae4964393e1809eb137f28adc5be5c5e66ed36092c4480fe1db1160d2bffc7f87527263ce7c5983d545aa02d5031f5bca6c2b7724438225c9ff51ce76f0a15dc290044f64ca2fa65530630ced4374be44eadb5c23e25562302365d532ea814915281d9f4951b3259d58b627a6d8b7c01619b6c9e064af0b49321dc3eea364f9bd862430be7a998c933b541be230e5590b3ade4d51603b2cfc262d04cbc4fb6913109e7695de1691923416ed03252f1f4d461cb0f2fa5b1d3ed99691beeb66e2f4d483d4d06ed69f62a38afb3cd287a3e4dfaf3746b54ee09ffcc9f6fa855075bad0b3dec92193ba55a47052543e25322adce374ff108e0d38581091222f94384712d551be4a81660256550c62f04be7ce4b60f94b06821f946769e71c7114370ddc28fa463cc70f9ac14d8621abdee8b779760200d14b16dfd84e2e132d4baec5f1b6e73ebfe74b3e0e76e3e089e68b1ecd62a56765758c1b9199924fd8c60a415123a6defe7d67d7de7df257b6d5edfd06d2b3b61e28f7a0d1e476ae2a43df1a63f87148776e71469b42bd3e8d98813ec8b37f93a2e3d6f9f45411f2cd17962dec2794384132cf3164439213b4ad9841b85e084256ff6f4ecf184a26242ad6a3283e85b89c1c4c70e9a7398d982515e8ec9a71a8011ac2edd33a57a3f784f1cb29c6f429f4e5edac8e1daf9ee6f5c26949629b5801d7c0013340bcf9de7785ed153eeb40996a8789d21bb89306b31d7169af6e62c95f237a1ac5734aae697d054314d0c72bd330c5c7d05282f03d059d0be5d6d1cb4b15aba4b9fb3c5205db3cb378baf02817da588c3e1e25c71519ea7e8e6d07b82979666faf3f22ba57e027a64247e1fcd84f91a1d53c5a659e35babbc4e4cff8670e7563e33c1478ab0457b56e501d7c3992ab6e386533bf1bc0e59260a5c764b36d29ecbe7b29515ccd99ad75b74113cc7ed72110fd1f46098a9d416701443a06103847d4668c060fe13a94097b36874bde38114b655f8b3dafc7880f2ace606136441b5d7483693bcf83c7a6e72e30f9bdc9cf543aac886b8588d3d1bff84899c74c56c30b6e4c34fb6b89543feb1f28e3cc038e401cf72f8792889a36c98f53b3b10cf64567d477f4bf23cfad51090aba5e6ac1ce6965faa5e925f18a3a62895d01bc3a7c1522c9651a1920e88aa87ba0ca88acca4f280cf3e9ade16202a412d52b2bee06207f7244b64aee6edb78f5f63904d355b82fb3e430cc752335db88ff5d7962ab6ad9eb14682f37193e16154df2107eb8877281fc36dfe68a9ecd99c067c9f61af26790edad1aad164966a13a448b44b7de97830ba068eb1e831315fc438a2f07d03ea402706657cbf2071f551feb65fa2f05b906540feac2102dca6a89153fa522d947b77a9616b23478adaa059eb54bb3660a191564b3d4bfa214d716a3c257a2b9499061efcdff070549cd6cf1e94802d5c91b70ecaf38587c2f124bb7ffe0a2263339e17714260ff3894808a3360fb52870c43ea82e311678f8e912dc8d5091e13cca87484fa46b2ac40d009c18ee91b8422cb08041d316b6e4c708e3e0aa1576d4d28102175654ef3f00698f508981986ee5a74c0cc43df95e0779407035793e875133065e52324c435636481077680b0d98ff94573ff8c9197194beeb052c355588ea0c08a8eb0787a87ab5ea4c7e5cb975f501c2116dc559f09160e9bfe488c75dad4770d9e72f09e82ea6c118c0612a11e92c30931a877d2192e9c8962b5a868e397dceb4de90d73974f5b442bc3465b0944556ffe2761e6e6dfc44540e56421fedda3e3ca6b3a2f0c9abb3f361878cdfd6e16dcc265bd0f08b2967f755f902c0e5566e1c3ea43626a25d9bab8d94c553db44aff4130c0b860800a402781e127a1cb5ae11526c9b6a437ea045d254a3471c0140975d02503cbc888e94415ec5a8001ba078acf7f22b5287d4e01728cddd500a3ac7412d1d51587ed7f477c6b60bf9a3fe0f0ece99646b74359976bd9f0593a0c0e8680e6275fc45c51c198459228742627b2fd6d37a0225bd945f6a486a753ba29bcdd2b29f9aa4a07108a021dd43d41d1c04b14b144f042b733022d460c4a2ff53d46b1815fc1f82a2bf8d129a64337da57aa06262f35f3c2fdf6246b636ef53170c32569541c892a6d8207b05d6372bd453eaa9ac508dd64d6dab0b9ed584e244582be809c58bf11b77c1b3aa628828c821a9b22851273c92817e130061dc8c75af7a03901f766417632cecd091f54476c7a9d36fcbe6b7ef0f78a589b7036f36d1f1520624bc226662162cf34a3bb070b42861693d08b36d440941f002811cbb5bf5a1d1ed1b9788612a78e5784288a56108cc1d47af444ecf219166b17f221b8436c60e0346eb0ebeaf0cbbea7f3683c229e86d8937b4332b6021584bcc4bbe81547d53226cc7350665570059b1d646ac75c96875b7b892f3bdb82f85efa3b3e4452230c58979adb6b46a5cebd9d4cdb785b79a8e36e3d8df6df6bfc3554ab23a64bd9a9f145ae7d8dce35643bd4d6cb790087571de77930fd84667af185c7d21be08951b49f32b9e9497624ae7b1ee30e564a796b2a8d3719baff61f0a925c26703113e3d040b9789b0afd3699f8f8886959c8fe933fa58db0584d5cd6e5c609da723ab8ef60fc3c206caba6cc39dd4439b6b1a0d502846a470607c1c594098f8f3cd22a25397d92ed29e2dbd350915e4715656995697cf00dbd76d8c4d8d2d53eac08c794231cba59db7b557eb5c2f55b72fa4b7af60555ff8747befe42015deb612288bb1f2ddbe52fd421c52da26ecd7acb11c7cfcc2e17b5be18d9905584b6a5220e0820a1b24e1b655a988bf1a48233b4473d906b2446b3ef110413f52dcf5b902283e51fc178b47a87c0a36527492dac0d221844bb366349df1e4abc8909bf891784db3d688f16b5f87c17adf666723c2211b2c262876fa191232374dde5a13ef26ee7d6698fb75796d8f1e548d172c2ec7340434e278651beef17b511985f28daef089cd11affc449d2f3aa54e04b6cec53305c4b18eecd2f34788aeb6eb0f202fe2b6b7eb3b8df236a6f73b4a778f2871606bbe58152e204be1f1e7f5579034fb933a6d2e1380d2ee5f533e13452f4a8375ffa985573219c95646ebe4fd2fdd7a51801aebd790c696249466d83c2a1137a0798ab300708ec013f4844b4e00890bf5dd54f7e1c4a21216d08a3fc9bc564adae431127a056c7564cae523c752608ddc1e7ca1a4a000322b37e84a82a7679f3de268832ef50af19344fa5917cd64e53739083883954adf65c29af9eeb5294b80264ae8c4866ae3cdeaa62eea4292699aca48e7fc8ca277c96de99662bbba36f572ce2e496b67db7854e14bae3fee71856f8a2fd0034d26692a46891726d8206bc7bafa14e6a91e19403b0043f82d998651d24725f24a71a3206cbdea3e7d92dba00bdc5a10901194417c91958ee376d78ed693e9720f557d26fc12d820618ac1b1d5cb90b05743c1c7cb196d9d58c31013d1d499ab078dbb0c4e1993bb382be4dd300277c090250f2b38d0e65e0f3fd26608d55b51034088401b985282b095e3e6564f4cd31a013d5a1968a46b8d11f28251550926a8ca76c4a52c7420027c9b6a5c9a93484d2e6ac6c9d742f20ef3d74d39bdb1c00be9c4e26818049be35536065ba241c445685505d1c862c8e27f447f8881ced525a46da43365674ee30837396a36de4e79f23134c13544d3c3aef586c34bfd937c0e4986b2557cd164243b547e86f4483f17b895aea7f3c117cef6f536ac7ff4d374116cbdf9f9d1674b54afb297d623d8a8c5e9a67c62e10a3770d0acbfdd71239e78b6b3503c021c34ef4945ef1845fb14da7ef3a82402a6f9c26652165a7422070252d6b420e8969afc491f452bd19ffd2f13cbb5593c227aac6f47aa7c87f6effa5acd18873cff974474c60bf3483463fd847def0cff8404669c03e2c3d7ff53d41a23f6f45a66246414101a9211a4e8cca0c10599ee0ec37a5dd0730c6c6f9b2c406d1a1928b623e88d100f67d13ca3ec84165ad2b3652612e0a799b31f82de248530e35c6cbba0534d191a3e3ae82ac47a82765e48412741b14824e5b7811c6c658d550710a0e2bbc3061121fdbb34d619c6f87729d704035c4942381fb4c380a856cd14a601a7fa948f9b5a54f33c61c015536844bcc25431004ac7b3d0761ef207810150fb8e5ceff88f7a94e4651484e22b6905ca842f05e292f868981840412519918283b304423c788cb8dc27d7bf30128e08f8051409fa8507e974463c134b9ff85661484be44455187ea632d1a72a02ae6672daa93523a90c491deb57cbf806b8c5a4df806ca64d64b34831a9bd214d873a3c6ac009fd31e85ff062087db1bca9867fe752baa2d79757d31a5a77126f5d34ffd95208668241b721887b1fc622a2f0bc90645f7a50c9b22e3368cf662ce8ae18343f18c0574ff3133203da6ffe60896ea1733e6156c5d9500f7bfd7102d3162adf9b8361a129adc4ca037ad045e446748032cfb791c6d097f33599da2faecf09bc3b723b95fe02a7e19724d72f78e929ecbd300db8c1603ad6a0ee143a8f454838697825c9877d430b850a2681e783af50a435aec06893ba96441facdc388adb43351408eb19d0a51b128d8fdca4cc1cd61de3e00800fdcd09b746c1fcb0765134c4d4aa8f77f890e0cae327c5e43ab1baa80a3eced5e75cf3a60055925fff78aef7503897f57248bce38a3473b00b81ea4615dbf2ef1d8961e748c1460b1876885e23fef95040fd42b301a089630758257bb5c5bdddf5da4a2b68fd280c5dd17d26734807d95e35f4a508447a83811c149bd2aa6e8418889875d94360f133d8bdee58bcf77af15d543baf15576c524d387f798b64e1b5205cd9d0bb26c0bc3301be9c4c2e44d7fbac4ae3e42bba41c5f729e1d5e388fcef2501a66ac8878dd21360316d94c9843688c96cad499ea16c6560424cdabe3f082b2d6e0171597a2e3cdafc245776894054d644092b0f667c1d3e24f8aca00dce8b4c7257260d6dd3c49ec99ebc1bf15354913c5e8ad29591ae6d3c886f1a4b7e026274c501166f7f51b276f6e72c49ecb467e5dc366fa722039de2272c6f3b1f054573455496a8e78f294171d24bc42f0232e0ba47c0930a36e5b6a2f33f2c2b91e551e70ff04d457d5e7e3776c1f425e7cf4c01c2e79afe0660bbce7d156b6a4d342012d599404ab1fd1b5d38c9f1518385854b81ccd3435461273936df3500be3590a775eb1731344e21d90d0ffecd194342e2e92a36689941d30b8d938912b319e0e389c9c671e8d1c89d3e5004e6172455363f01a747a2112a26ed80612d8fcefe509a4d3eadfeb64554eb1608ddcfd5104a8df38d8ea8737d20d76505e5fd4fca923f5bb8f48dea5033cbdfa68503dd495b29541550e619d62187c2b00e51e26aca06e1c7cc8ffa6293c574caa4cf733c9a0b8f305f41feb3c238d3fae4713499401fc6a4bfd91ca41dc33878fc4b2819d70dcb4b4b44ec4580417ec8ddf724f9e8dfdea65dc89065794ca4f2f60c249f354b77bce96867451bd8b443c40e65527f4625108879313fb57540ed04cba855f8e682c439c527d416183dda2de0d223ac16508a29d58f228e936ba2390a358bf0c8431ec6228f70133614b042db7a980524bb32b44938a614d6c1f25df281f429c865d806c856fd86219e3337169801f438ec8604353485cc84dc291b6945b3b52ccb4c2ba5d86be9c03c1bd7f8c705b4bcd7ca36ba8d9fb8e7221e7f4187fc63fcd4cc33c95f9959e70359642280ff53101df41234cc5989e24638e392fc521eec0b531fb3cc1ac4689619bf19aa4e5a74e9b1d4d2917b31c10cfad644a615a57cf1f90be158f5ef1cc1498a6441f00b7b35d82dd73e112411e55d680ce0672bd29afc7d37898326c95e5837c9e471ee073852296fca2bef97c0d7d473ee4b0edfc2483a6e00ca733795f43008f373612f5ac12d4cd7e07e8782c1b03a993ef8e4e7e41f574f49ff87d9768b4cf7a7c7fb372136c416022b3ccaec0bb081d4bc8a37309a1c5911a85193e0e4b2f37e4e84f8bd8f5f3ef827e9aa8e3e7fac167f5cc5bfb878d2858f281a81feb45f42b5fa1ea2d4abea40391359ea0871abcc398d6419b7df1690fbf3973f9ec617b626ddcaf8233fd41480fb2a00b77962d50063b82a0081dcd2599a56b1e84312344d814793547f86f149fcd88e9558004275ae22d58a8086fbc4b22bd05bec06a6ac31683e5d0635e76312159bdf1fa50961b89cb21fdad53494671acc962bde8232e923828863fbb738fc7eb815755483a5418dbe6b0453d1baed1b4f788b196b378525710be67d63d9a199c3499d864e2edc47205335d179f79d3a2e2de4cec4bf3b935e95dc441f12dec531d3965207838e1a31ef2e2375dc677712ea969777e0cd13646c1eb0b4b27b9be7314f0ca44848b70ffa591a86103d024e2a265c91f52ab91996ca8eeb976343505defa53de920751ac8a7860033609046d6474eedfa9b28544ccde783642a2e83b462debd4852716be32e51200ce98eb450aea0033d1ce0e4b2ff404df2629e9b939618be46c5044db3be85525754591c47b3450f2dea4e8585cd20c11b2aa67a56e1501dc70efd75c3f2f474ff730783405aceb94d528f8ad99d8a264956d29eff53cdd7ad67d3b88ae9357d0231974bfa0b49fb11db8b27d3ca5ee5b1309aea1ba953973a75824bdfba1caf0fe9988a692ab0de77203190deb68198205c5b567259869e8fa45c5966b77083b38a8cee5aff48b3beb2d7d77023e5492a25e82b6744351521ed076ed549efb04856e4a390caa29ccf2b054a0c92301f77e2d646ddcad76ab2c60195712f9f4ac0173ccfad8050545d2218f4a4c0fd9d9c9197f7dc6c53a2311c58db49af573785c8a31ba5670afde1507f62421e2ec50854a920541e695b65158877303c23b0992dd06d6eabfedf15b020096f99f94caff0cf158fff9dfca9b494fa64a2691585aaa9730fda13da4fff2aa58442c54bb123d33ce641c89bc5e497df5176eb6f008f4ce8e16d3866ad837675fdbb82e28df03880af389f80671379af10e747e66f257be3fbcb86929d1f763eac21d5198f621e93cb06c3d0fa3bf6bc7da5e332f111ea320431fa7cd05dbdec79eb41d3d6d0f593381080ce203bb92dc08d75b4ec13930070b615ffadaec779d6f215454fafbb35c2f2bd61be9a71bf4bbc1edf1491008ac68bc6366313a44afab823f62cfc4c211fdc7e3b4cc559b17eb6f5c6aebca388c1e540d961034cf67bab3f32a8b241cb12c45b021b54f9c93ea6b6b702afc1d5479eb05472a1cf85281ae2f32a5f5664061eff8e0104b70295db75c02197d0e84c298f7de4659f7770908ae3684f9f1c267ddeae453aed25475ceb621fe9f3539350d6494f8365b4a3077fe09153126bafedfd2d3222ab0e4f20039323028f0b83a78470d77ea3f8c4efc8ab6cbe316fda3148a29772958b2ad786065d2c5bbb51e6e3ba48b885b36f9ec9480671b108783d5fa6708c0bcf45028b49052767cc9cac53f61eb335cddd23ab7dbd2923b5487044c530deca9d420d7631ef360cd2fbf495bf105517efc01fd6a3f756312a50c080672eb16f781a8b0cad685674038e6fea90a42203e66849d17e60f2e69b5c50ce0aa26fc0efd00ed519106bbb19760b1d9a821a461f41f15933f25f8dc4ff9ef48870bd78c15320aff6f5b6f6419537b183a7534a396ca7b75bf014cf9e9ff932d279fccc8f10cde89a89440cd425239f06972c8da4548430a330110be5164d386268cb98cf928fc7747c63af9a9287888d4c6b210d914d6f051cccf3567ecb613d4c364576f8984d9dc92327e2863addc59ebc0e96074830ec5f5f57cde3b8491eba89ad30f98c5ac97ae76ad9f04c14d56a87f8aa6c60c0f8306e655402025188948d25b4aee5793ed5f6f8b179b6ab5c5837cd7188a69a1704a6866cff105eae9f5d5ce77d21f2c58dabca4440f374fc9d1e94f2bb204afee2eba09a8c5bd4ea8f506139132accdf768090c00a8eb7ac3a984a92ae6cebc65d6796744240031312a4e4034a695b876af2d142bfa410ce6f6554cc7e65364d4e2224c64bbc3b78ce672add79546625efa5c1c4d2d8de6e3164d533c215e66b26262fd7465322745c12825ffbdc79c0e3303e72baf2b1365cc47cc9ef946fdb0bd3e199a390346daf01658ebbd3da2fd1e7c18dfd73ad2a20eaee2b6cf5f28502ecc90ee06dd2cd2a405fb71e9185eeaa6adabbf3681bc3958912920762ed18e5f1bfcd9c007895b4a09e1895d57e01109e54a6050170419da530b9b54aec5908dc3f61ce8e275babf0a06b83a5d6baf0bca2fd74343b5921ea61bcf4527781ca4024d3806d46160eba7cc51a8440e1b1d65aec3d0525ac9c7adb651932a14f00e5ee7af2a0b4f1e01988a4db1f3d3ecc78d0ff6a2d0bee6c79907cc5d466183a64701fa5bec105038116b85d756df849cfdaa4f4a54fee70caab6c0fdb70129a113297a8e01502ec25ee7ea23c592b8d5fb3dc9e4327276b9700496a64d9230dc5844a5ab1eb4f6838b496a5185648a25ba7c243006214096906254e72a4707827ba253b1dcd783f04f5f60fc583c82e02235ab84ebc19e55dbf2de7d8c1d555332a26c13e297233496fb353d0cc3c98ca6fd5b46857d227e2e150dd5b61771e4dfcb14afd43073fa2a009dda0d68eb21ec2b2082f4fa061ea6a83673a34788840cc6ac7f5589d29c8d7c9eb14a3d4aaa54056f85051c2fb7c40aa48c028e2594580a175a93b37f55ae8d55b28a90b0a791c6f103b34cf704e293da6a6377e10975bcbf433e8860eff5a2b730ba9bdf23f3286a5296ed119b8cb2b837bc9de2455857a895eed2e4e12481c2456232b09026b2324f089b04434ea2a1ea278f75ffdb970353c8b35ac8e4311032c50229f74105526f88d00892e633d2ce3fac4f8fd27f0f69ede5b9dc7458ae678c9b8ce647421aa09d88703bebc31ca836402c96b8ead9c24429140851f739f99b572a0bc5a0177f91f4106b7e6dbd85125955cf2dd1600e76a798c3743fdcae1970059e8ec742478495981d961002b8108d230509d847acd0742e03253873f4345b0861b943bd86749a194620ac86ffed42ba5d978eda8705a810c003b4c7bb9a80418c11ae3b9bff96028aa1898cdb8738998dffa450d69f49aee02ea70bca3e7a6be8b77b48ab8c2523ca88d991a14a728337d12bd39468bc1890d765003e705d41bbb260594f5d960005fff9b2c5128e7c7922a52bf569a66573d2b6d228a955e52f09c279af68327137aff33b88f13ea02faf77a1c0eff65d4c612fde828c65d941b066e3844b8ed5bf270d5312e33da911f4c8a1cbb35bf30e19a6f1c5b6af4b0a2645529d0aebc336217fa6fc1b8c9a7a651d44de67fe2f3b377f4b5e036f0bf13e64104375680ba7bb10fa75185d89c6a4522baaad700314d98f8bc3425c235dee57f9d475fca2681218b7477c8601bee1af0c1c47da8342aa03ae7a80fa463c8d9a472f36e7253231e88b0c4396cf6bc3ca1f07836fffb9a786ea3409f23a12cf1804d5e0673883c89c37ddd2f678090c7e9f16300011a9724a0c49da9e5787f0e3d8ac44940d6178237d96e678b15b3503e2bf781e1eccac302a740181d577acb427263fada9e8584048a57c667f380a890de2d6740c0b9095cbc9f6b1b68f9176f460042c7fd1351f6e5aa8c224632345cd2015f85d7b3378ae5c9ad7c6a86eacdcc3fc6a7add483ddd089fa7d836bc503191947a6ded76aa231403bd0a8a57ce7ec838e0bec635d01584c6756e94d78b346b51ace557c51455e93c16a0284b279dd2a19b26432ad92d15a652a8084fdef7a32d0de0b896499059850ef42bdc0e8b4719b9626853440912555a447021dee440e4dcea3028a2963d9ae4fc1aef74d04e0368850933717662bad4496e3ff58a715503a036214b5312a0bfbb45c974c954d679fbb68109068ddfcc74d3fc044e895f098c930d786caddfd4c3fba14573705c0dc9f7c2870a82c675c6d068499a4e9441311dbf15abf32fbb560c9cbacf1defdd0c38353719c01d4f65d12f804a72968067fb468349c9351eb358d87625b5ba7098fd3ad530187abd1ec60c064a9b8b54b051d0ac169e212a9a724e7e266a549ac6d80a4c148c756748291275117e49f8fd0551216bc6496b52020c708515012877b6e024a57a9f8945ebe8f3b50caffab79e2c7148d194f8fbba48803d0be0400a0ff604c75828b6951a729b2a6bee7057c68286648014aa705ba6fa3e8ce6a3c9e742383dc556b9404db31221a9494809e35b68e3c0ad67e55f00eba32ee2c2ca3668714406a0b8e3be11b51163e2c7b0d26b68f48fb89fb0b9f413a49f72bf9e07353559ee24921a20790a217bda8e1cba6eb4a884c532efb8f2adf1d9ddad6a01d8ba09628405adafd4fcfa897e342b8dd74b78e839712b39627be40aad215bbbce44d534456eff61db09435f41874b27d14ae0ac466776c6b7e91332a3a2c544aee55f982b337cbe68cab922a032d34143f0aeb56b96c20729049cbd27d2fca1528be365ea0e377f35b785327944a963a6ef9cdec86f32ff74dddca333564b6cc9c4099ec416b1d05780aebb0529a3b0dd531c5e7e94c302989f7ff11e465098307d3bda1d53f8018a74da8353a2d83fb9b70cca531fe38d9d24af474e0f9485ae83178d58854a008ed4534b2b2e82f136fd03c04eb71516ccd79529b1570344104ef557b55991ba656df93bc6db82d5556554d1a6865a327557fd551177a8ba6233657a8db1b841ac492fa269981a7f303d1dfd14635cfdd459e3eadf60a6c0c180e35829a89115aaca833b6540f855cbab605cc6b604acd599c810ce7fc821f8689c88957fab375409f2065c0d6488f21e5bf65641a627164f3f2e9d49741874c7af04ea7f2cf81b27d488fc2c6f2f5ae659245dc9a177d7b83072fe0c5c4606631c520675d92494a58b079458322666d81b2fb453afdb01852442d0740ce5a29f96a9e2a62d8b7263e7f3425e4ec5d2cf37832988127c11695d0b23b298f68df50e94f2676249796623c44ae3935b5ac8e9b5dd1f9b8c51787c40821ecf05f04afa545fe512149f2485c4414ae9191178fc94c5546becf82a14cc43637470c5b691be8006149a688b5ba19823bed4dbd178d61b1320fc9e02828eb2245b0a041278a930065aabd575de1610277d706ce573f68f292542d09a2d8050405064bb9da819e95d78b3e79cb584728192ccb274637104b7385baab6b6127763b0362fd51eccfcdc10b2a32c5369043f7323dba0c8c896158b8d749cd5b5eb8bb2c7be77c50471c790e256630b49945f0b339de9cdad1849c0b6a3988f7985dc5d534505895cd922f01592b4a94b0959bf5f6b418c48ae6f7df9c15ebd1e774dddbc62149568d5b8b4f3b9fa46282971c5c2da559c23ff1aa8fb5561c7cceaf97aa92a4140c6808a152347f0ff3cf9b7ecdee0d66bfb1327386431ea081e971d19c2d69af95b268cf3e4c180e058a1263fb8c1b646cb972892e007fa177b4ac95ca1576a2cf6f171a8a2e4d26d8de43090e5c302d8b82797466040b923be3e25224c52d9d798f0180293bf1e84fb0cb539e032967bd47b0da82e4661d0acbb25ce019717d43931ece3340765556e1c42b6c66f4b7af47fd018e38240074778943d43c113eab231615556531c886d658609408cb979ecf3ce5d3efed53bc598c4fa5307706e6be2affcd3c31cbc68eee1ae547ab75602606ac748d56107ff5fc629a4d5bc1ff8d6cad0386406e06d1c2f5519ff43dc47abebd2c2c6ba86b2ec26d3c2bb0fad90c81c3664e661cca8d89687b79ec05495e6db6f7c211244a246e0590270cbe019d141568f8fae227f2898fb6569cffd72e296a276dd26b8069fcc8c0c13b6f9a7b585158f9204e48d24faee2c84fd889bd2924445768b299402a0dd7a7d77a6c9bf6c8ffa3891b484ca950e8abec849b59cbbe05c7eebf48e0eb037d1b29c58a501ae5b6357dfe00aae2252419c2833881749f5b872d9350d461a4cca256efa63d362e9e83fada688532f0f46f7eaa42500bf6b6f0e44dd137d5e7df349bcbe3d9212c156bcc806f0901baab86f19405bac358bcb256c32d2c6857ab8bbe8223c3eab0b8cb2972250e0a36ed1f4dea4fd7a4acefd17c97518bbd18ef09670bf6d37ccea654391791640b4eddd0096722b621c966b916b0b9264042c1e0eedeff0815266f1f19bee3a808c463de1155b811d6457a1acf5b616f358141e68a5d1b004651a644b291481049ebbe66e3fcc9662d38cb622777e5a9ff9d08c5da97c096927bf7ded065af300cfcf9a65e2af3f181273ae682d72c4cbfe9bab093dc00f3c237055149148e79474f9d78a26a862742686d59c91955ce726f391a649acaa1f040cac2b73ccb6651e1461a28a2954b358903c463cf407ea97790c4bbb9c85dde276b54ebb9992985f0f1ca0dac08096e313bf765834ce993c94cb3303b3811d9ee758d9e948022f2a5ba8f9135acd2e8d81642af337b0c2cdc8a2d248aca10b22dec90a9a1db79cf033bf67092ceee8864d1f25dbf569cba550ee0b2670bf64d4c8d32231cf815f727daeefdbc5dd64587b47dbc6c51a3c517119c8e3043fd59b56fc2226b751afc6e0135a74ed227e8db7a60189c7c8120872484ab6e281d25d90c227d2cbc4d077a2d55aa6077d8a530c0fc9f8fef413469833ce002931a06e31a39c993825ddbad8e27956b423da208e9790913971d0ac1463bd890a3cd79e21655a638b6c2716abee84784d4262c5420eb92847a5d691451a6f0d615fedfeffd03d91bdb482181867f08842b347f8c59b061a7a8828bab157c8ed2d5069301484a9bb0e4b597707ae582f45e539c5291233fbf5b6af038f9004f677df96776b0ac4e3b49e8532b9e381696b147e0505c92c14710e2c9c7b8c05e03ad2fc5602a149c33b81bdef9cc9ed1ffbaa8e82fb356f4879c444cb8a7a1543cc8c1c21ac6f408f7f1e6f48b8c716970c21abe4c0ea9eb6d37c6538896b13a4789469c0e111bf20cb29aae64b8477bd61084133894dfa0ad979bc0f90d639ee8798c74e104758d4c2271e9b08b7503847a3d7846e6d34c0d7c8bc7fd3ca407bda5db87a70830ddbb7d8bef66119c63f046cc25818a040a2a2ffff49e7323ff33ca52f1d7aeba158e49108e105bc921acad37ae235ca2104f2651d9a67d8b81650b6b456c2112e99ab38c70a0b2cb703e311342888ce21aa97ba02591b0ce81c81f733e70883fafa236f41776260264e88d4ec72259832a73ec36b6e412b183465d4db618af418e2ce93fdc3d36f0a62758f4657332434f58f4e229b319a00e5fec7dad46e71e75d601ca4efddb659a7e3c5738e730906fa4369c30fff61050a06a3899c9c1b64d8d045cc7883955c46d3562a95dad410028db5ebe7dc739b7354614d97eb104e437bbde623addc071fbf8879cd689a73723277e1134eb46577084dc56e6887bc67e191dcc639ba1c8b6de9d7f5a08d17c4287067482933e75316648817190a7ae730b1bb8541fd6639e9a2e708cb54e87d78f3426fa9cd98f7109c1f8708950129572e4993a72bba79388174b2ece1275640a33e892a6e182d8e77be3daff03dc476d334542a820ee550327171d4c3f369e26403caf98c103a549b4225128d304c5f1d2c7b773c5ef1bd2eac4b1cbebbd0b5e6f97f49b154e61aaf575f84ef63219cd668f0dc18be688272421f6881859559db4db3e83938f11c8fb321051393d17dbd0b7b4e6bf31c0f9223c0bb154404a5dc4f010a1fbe35ab3b925b5c303ea11ff9aee8db6f550baf0efece811f9a9bba530f601e409037af2a380bd6e30ca8d581d3cb7df5b02c7309fddd6a113cbf0699351fc616a3c3144aecab2f6551e5aeb18f4d008c271f23cd27c8afd919adda98824892806c6a714792191d083c121e23cfee917fc2b4dd84e50bb8541b076dd3d6415d05b7c31ff85b841ea6e31536b5fefec6a36d9315185a1a03b8a0eca798d08b5df6ba9aca37502e4b1e87768a284aa56120065669f7f7c0867d4027ed9c87b66e8afd8183eb71a669caa103d369b77bc9989de669a8e0b54516677f8f255d3f817589628e28ce21edae0a25f311b11e41da0dca98d0755546943416e12ed992214ba291ccae3df36e011db25404ec37f45cee071d7c9643d14a176379b1fe31d1b9accab10ec0c555b9c45943c44e95925050b7b77e571c54f96dd89aed7fbfa1e6ee958d6502fd9d820e8ef960d4cad74716cac785c1e7b45780845c065052c45589475b81084426919104757ffad3a2392d086127e171c5ecbac7c3e348ea1bb9d108a310a7c83e2df7d4ae421d66504574be33ebd26c06e4a34ff41ff4de48e58cf824497d6e824417ca3613eb60fc3a8b4fa5a0b2ab3a1517686dc166ebbcfb9ab461ff03aa2ec960687a084a48e75cae9a6044f28ea070bfcb61f8249106737c147fc4efe5442f0f9c80452468b60bfbf04be263bc8311c6e350774cc5ca4c88d981d18d4064fc09b17aaa03b57b7731ec7dd6a5052584eda59ea59e6fc2573119ea04bd3a94d16fbeb1fc924a573e432ff84a96cc80c3ad0aad3dd6a53c2f8e8f34322ac2eb090566216f641432075b5df230329a8c4769b216a69001867f5931897abf78d19fbe8b8849c17b3645faf5761ffe3b841a5ab23ee002c04965423e8c2bf28d60202f014099910560f7135c3202bdb1fa091ba7729c3a3189fb20a5908142dafcc2c92cf5782b3fb10600d1da11f3071489119bbfcf9b262b8003da840efa69712f6fa9a2921ef2532854a244de86e869297e59cd1778da67dfc601b0e7524753f4274a15b70b5376abf96a27e68703a7a39133b035e8cc212134fa1177979f815a9b0329bdc5ac548aefc28cc3bd2ede93edbf6147e00111d6cbabce781c5f486d207c20032fc0273e54fbc2d553fa29a2f4d05e63a8e31232808ed18ba086ad02a501f6b4901747fff09abf5bb74cdb34dff7571294d8f2d147d22f656773bb696e9611e1b6fe808171111bb609165a699b33f2fe86145637b5fbad367864bb338eb77deb1f6b94a19b58e71480502c8281705d69b4601a2d9d61841d9b3b610297fbf292ef21c5ae0ed6c90bb1b6b8bfd60aac495c308ad2d04f4cd29e41a764ada98237381bce5c17fd2da22bc84d31ea861ff49204d50adc4bb70136bec5ba4b89de0f4db03c6d6168bec9008447f492ad0c6e271f6f8a84de624e1862c9a398184ef826be4337d93a163622f0a94bc96215c7db2d07c247dcbd6bdf0f7044a0becfbf856bf253c476019f4f4b4f4090f5796c5172ee209ddc624e5e2af5c1fc4cff4c837e4cad6449b6c270377760a0df4d742be9a45cf02a094b923a2876b7bf0c725e0fa9bc2cdfb504206a14d6d5574558a8625bb29791f7ecf4796051a0fa441129efcd60c8d203030282df1eba4f4eb2de2c36bf48b1670ca2208a846ad16ecb4448cdf01de9dccda13891204dfe495926405793113640fd5e6dd2def4febb6d14c6d7e33ad05c19543bd494651b0186707c18c43b7398bb5fece8bdfb9278c7f0b8a95799d8f60c83246fb85fb7ee869ca5e2db62ef4d59025fd14aea387612629dfe9001a827d135d82b18583a3912d81644d54323fca75985a3809f63fa2732c3b90ac59a5be61bcb08f78cc2c3a1a7ff06c6cc568d29bebcd13036c2652832ecfacacec434cee8c26e76a20fc3820b2cbb90124e11ff2c4e7b2cce2b0189bd522716d7f6ee31948c276951587eb4712de0835bd97eacc0b86523b7c4632a5704f167133327f74fd7e8e5f8740b5f7f478bbbf650358a03ef9ce1d02fba6f440a68796710d0e123e3e13705d2951d4f57ebb5329597a10b877bc189e6101de972cbcf4c1017b94860a5a3d56dd88eb454c31292dd75663550ae664d4ed083700a89db1f32b79f2ebad722edf937338d39b42d6894e72be82c3b093423202fd7a43c9057401567799a4b3583ee5650db3854acce8795d33aed8012ab67739badaf0627be9e02e9fb8f1c3421adb400649b585e3b91a5b423ec5ca44350d34c9b6c2182bda976eef6ca7661ed12fd1992b84cedea470360e61e1e2f506a5944346698e9517c77f29435955e995cb3064905db10d40ce867d44ee7cd302e3bc1b282107cb5c8c9e5c7a5cd9e248f3a2ae03ec16fb03c2d33cd3d0c4a4910f1485f351098f523e6335ae03e59d089d90386000fa2775e78c12040af3c3d20a8fe8ac5f455ed3d06a56df8693af848852ff08a2c1789359cd1419f4040b2bb5e623166629896f02f9dd8c3649e41e4bd04e9317fbb054817039a56d9b90a47d0da571b06074cbfe1ae3c004318a5d06e696b48d3816e4621196fb1382529c955b98bfb5e113df452055a502803bf43423d60bdabdb7982d4f6f370f203d28d468f47b0aeefa0ca4ee82a6f75038821012c413473291dfedd7f6289b2c957dc484d4bae147a873649c8a1043a85170396b64ab83ffcdda733da82ae9e6af4ee3c7e737ce61e3ff181c14912cf655fe8003483991433522a8c03f91a59c04f591497dcc4cc26710a60e99331d56857e9e2765f0a6f31b372bc779a7d6fd0a6170e3e99c06b5003103d45317402ede425bbcda89409dd40641e00ab7930e4e2840e3bb0ed051c17a94139e2c70a9a5fbb8cd018b740302161c4cb58bef807918f20310cf8fae73750a05b52a41c6ccabcd2219e69ea65855591e2d5c534b090c8ba8c49dc89b0dcd7e8970d65c3340fc278d957bd9cf99b6b86059259dfc05a5528a7185dbcbf8231f1ff02346a782301f8d970664394ff08c6f42bc45fc0c399f52e1f6a3cc3de404210b155c2e3b204ce73cdcd63dc3c8a1eb19100a9c331b965cc1efd56b5ea05a33245eba2bc826ca86abe0afad578f6a6f15991362fc4910b5b537ca8cd58ba92af8970c9d0c209f0e923acbffd7145e6fab3ed7b145ac73328637dcd8a921b583b89ddca72245d53ca863e830264c2ca7b8cd2de64aeb0049d9886b9bf5aa0591b2577ce24b2df26179a6ad9997725c94399f1638f8a77af85715bf1ab2391b19c1b2d4f1fb3881534a4c866275a9f02797b55394572c02b3e6f0c700bc9387aaf8ac47bcbcc6968cf919546d0562e15ded546558dc209950e5c4170914792b564eb9fdacdea8f2c210becb476e365d5b2aaa3465d9a395618ca4634f8f15a133ba78ba544942405f798c11995e9bc8e876766619d663d1789709f0eb8a9024f932fa77b57ee5a0cbc3820b7b0a2be4a5bda709c7b10f90683fad7a13b06d468a129dd2d06cf408d89f7add213cf3ec052f5bdcc992f1e00d5bf33feb59bdd834216e757c17a9d7d6879936b8744616a3b2c4db6aba54f69758131e472acb7c3c46dcceecf00d56caff800668c17633db7df8b1d4afdbac049bf3dade79f750e76acdad179bce4089b25d9f281edb4a64b99b77ca1ea7e7c4f367c8dd09141b77b6ab81d3ea7d8dac810907310196cb34d3d1758c8f666600f9511bfd08dc3537cc6151b141177e2ab3b254e1ed8f247b685c650d7e1d646735184d4b610a47f96e574fd8a761e74adfc28a15af52763b74160da84c3aa2b422c4dce4128fa378115ac662144eca1088394e164de93f75552ec11cb23077b3954a937243ec1552535ccae9f88b6c3a3d7f544f953d4d550e3be8252ef036952855469b2a0b1edf644b49222dc4222e1088d05bfcf20bcdb9a40825d889371a0432a01d96b2f89d358b1214a7aa0c9988aa7faa5b9312dfd24e38d751a4646a5282f197149820d3552f0bd3c30706dad2b073dcd738e2ead9525303986b775e5cb4adc434eac19b72b1789e07ddf241e5ec94de3bd5011db7700b7b765937835bdddc368470a09a15f6e8f77a12b278389fd16f58ba73f1b08fbe5222ab4a70b17b2dcd0fddffcd67886f92e06dfd62ca902e7fd6119f0e77b3ac3449052a1f6dc690084a3fcebe25b77af99cadd9de6c03695c36a6d5ace641faa2b26016e0ea3ed305aec20b0cca1e022588a41ea3f0c5969a3674d7dae4d70960ecc701322e643542ba35057535372b5fb3463e0ba229702b55d88c6c5a052eb62bee9a1b1f6af2ac30b049720411d0a4b6ca5fa09825e58fd66654bc28bd3bc7ab3303030acd3ad7e230b34ccf149a24f2bbf409896b84d38cc276025d827897c8eb50cde2a07f02bedde995f81f857bfac4e439f2fa0657cad1dbe4a4af17f24364f8cf9cb8cfe664f2242cb3f2648763fae3e62eed2ed0256f0bd2070ccecce8c2117b1988b0a54a9bb4d86568858b14b990cc768cc0f4b86479285abd7b0cb4f3152fb417751bd2a77ee3eca1d55892b0051e064a67755adbf5f5f7e2cd88de063619c8c3d7a16566a85560e380aeb15e20437b331710de08b25aef74f2e3802ce2c1fb23642b8e3730a98fa46d58c19f35cf07dd67e1b1c10a289407a7c11ca036bb65813eca27e6f89884ea0da62f409564ebe8a6b5c5b3f7c6c0320b962b9d5a8e0d4ddc56efb979202ac792c763183614b9ace2793d0eed25bcee0e927e93cb89956d9ec9f4d544e958893a523ae8f4b747c5945b91186f36e6dfa89ae7f806b515ae4c5ae3fad55bb21a601236d126701b8715bfe0635e23a399d4d194011a65897b8f8422a099514e13bec7364b958ba06c8b15abe9e250ce0b21433756890dba156908343d033fa136ee190a163b436cc9703c691b11927bc1749b3344c4acd1adaf7b423b5482eef64a5bcc97675a92d90bd891de87853e773e295827c273c06e012d0a80d97d71b752e5b170b09be51a370d5db5d89c4938ce1d8fff1c979a4bc037d0d42b86c0c6b305a7649b43b562ed66f166d767850a088293fd804df47a735e514a369260313f7ee00235ff8ea394a91be96c09d72900412d10c0f7e6595814c81623a3d0e76a76023879ec3cd8d468a0298409250f6b066a02927e2146f7a77ec8f39c06f45cc0a7f3f99a61176c93c4e84dd2e8d58367e8157db114631e39a4eeb7891ee23011fa987bca823c6a01b7d937878af909c6ff5139512d09fe42d3c21d1e51949688692101ee4df866a8829b881c846027c536fe4c060345222243d0820451fb6e6a2984bd2f6fa0de083b66824e0a8985c40f9091266b617bab6e07c27a09a1c4147396c9676d0bd21500c8635b200f18d8c1b1e88c1d383ae6901c671af3bef937a3a00b05a89a1502db5bc4c1cf1cea17b9f5b64387f9a8a3ae8a78a99f0da4cbee79434c00789977e6da599acadae7cb8ddc50d3aae8a55f98bab748f1ea23d7f5d64425fb30d30585c7e40216092ad92b205c396337b8669d3025b2a85c06a7365718a01604c74cdbb9dc56f9c5526051840027b1743c33b38bed5a427cde5fb8e898c1f7929d12b9efc1d341298ab35d998197ff4654e6ea7de01d5e5146c9e74ff113c776d5939b2981d4a6080780abd31cabcffd0dd93e0e17737e6f58276d0188a7a3ef075d1789a3ae08b9bc1d10751ff19e5baa9c3b11d580538f99b87507e4087b12ad5def4133dec4b3ee85e9e61a2a4f55b0dd87195077a284ec617101470b887ec825b51d060391c64521304150021220d0208059acea101fad2dda188abf18205e46375d7561d2384ad239049dd25dff4332382366df1620198f1acf61ae7e91cf01655350a0ae27808bc5f9a70d4e025cee8a1a84b8c5c52f685dc61cceaae889b9eadcab3ed984c3ff3885d58e675aa2aa89b390a415f28d80aec6383f8e3baa397a450be55c8b183544216e870a4c0b66c417e346dbf99fa3877c8ae71e3d58bb8cfcfb90af97e11b1a1f831524c1bc32b474e6c6ee748fb1bddcc4f9e1ab0c354c21cf1adfbb0a75044850b545bbef942e8f5180ae0c17588daf09f9c54aa9fa55ab2b8600055117ec8741444e08afca9a541d19152de4fc1b2c3f64b40b3118724152861dfcbb71683402b388f360bb935a860a021630514fd95fba47588ef6f3c3c8711fe8968362193c5f2a75e76c1d63fdf625d2fb3624e00b67ddf9a5184d9501393fbf22f8528d359326e89fd6079a0935fe2937a7266d9952cc426e3cb3961be42b36208682f84f34235166c39e50c82c476f65425437b88c4a1171e9f47764de28e9dd84745d4fc75d5eda811434aaf36e926c3aee5679d58af09d8fd78314a9310671330f2c1ea52b9f2df761e6438d61ada02ecad1ada455c4973c57ec04060e93e4083dd04002d09c53cb3fb9de55ff360479a8434cfbbc26427654a3e57e9e85f6b6dd1fcfb50fbbf95edd6f670706ce18097221b0048975b3463790a2c07eff27b24188c8da125d3a1931559950a5d63c14647dcee043a4f475c94e36d45e60d851166d4e15ee64f13508dd6bec62eefb62da78e5dfb78408cb7076f3546a0d7056a0f2a268755cf35dc3481d8e66555a4708803e7692977d3e19cf41db6edff485afcc23c7046401a802f437fe5fd8ef53616c651ccbfcac29b0ef8b64328f3537201e882a5bdeff5ffc8a2340a421956dd5030f6cc3114e299fe789cadd85a56baff185f9581e106e3353da30bc4a273f1b4c58e066b453b9777525e815e154cc5d3711cb22aa90d79ab4ceb4fc10c71cb723459040e1117e87971bbb4bc0b2057911cad8abe8889b68e1e9e93e1835fc2fd89a343cf05a26aafbefdea52f143958c9ca929a491db3d5a161c73a4f76a128fe98752b93c0855e94b61eba3aea94b2a9d25445601bcd8a83cbaf6fc913233aafd7bb80d94d00c1958c48018b66a85303f4d83bb17dce5a0c3f86b5be390d77dc621b136638b8566397485333f568f49bf29f851a67814b079ccd26bab565969aa6b2c742541406d308e2eed0580660705ed53232541a08f1824c31880caa88a8a2221b0a9ecc05fd52b1bcac05ecfe157b7ec9db1fbaf67096c66d47b4ac121b2f63c0245aef55cadc8a5811c04f5a9ad0e3ea98d9c8e87f2e6e91335b7643b4e04fe3b7d80d0df83fb6fadcf6387df9b3d5e5acadfcdfadba8ee0ad229f257f8126df2a0e60e38f47897ee6e8b55d0bae76cd91288df14ae1b877981c58908ba3dbbe77a963fc9ca629ba81c8f45524f7d597e09aece8f5bf4016be766576cdf50665c9b1af74abe246d38a7d2182476dfc7972ab50b06296f0015113fd35c3aca7fd9565e5e76bcebdaf1921bf1b8e3ddf3534a61ab46386ecf72d3b0816637ac010206e4d92843d0bf52c8725afa7ae3047b7e42a91424e25b9d52db7a3d4cd73e841949eaed6abe886da14601c12ca63763b385fc15e14b0361897cab831578e73be2ad9f6da43323f211cbdfefe4ef33cb4bd69737811c9fdf7136864de70be029683f4b8ad62d0426c2fc69b932e83227a8dd477bfe063d42820972e4a565576f3324d2be1119670458124e88a07b19df4e9ed04130f81ecf7593bffa4c5c7d00325c8bda710cb554b4081040a2ee8817ffa94f93f89e9a3511a48d654a9abc5c7d7bd60fa9b7bf16e6add11618abca062c29d8c3edc21af10c607ada659b4ea3e1a3361c220c4b8fe2b1b382877bde62db14c09fa17962dcf2593b444c704271cc7c18c46e4f332b382ffc11ee370d9c2cc07ac32ce765a3bb9b7e5ec281a25e7d6508ca4259255502570f262f95c0c9cc83c6b63a3967f89a3fa0b8dcc72e4d90c0f3216a712633f5ad3b0ab47376db5acc5e2ba612fb0925fb0ea7835ba257414538e64f67ecc4e113d8d9f4f3922ead2d7bef1e69642f801fd6cf8464463d6628a4c537be9985e8eb9d47ac8d38fa9e15f779e69f60be8ccbed97bf0778a6c6648adf087cd62f93a7b792999b573ef4622c97d33d2185c86d70ea7913156725e99a2f29d8c445cd34b468c60fa768a4f5cc0cb93d9cbee54cb976753b895b8db3be400f2c74360ad857a0225a86fe7780f5a4a531647f1b14840b63229a8d557ec51ad88754dc2db3a56de18b6cc7c2a8b8a30868b4e12bad67dd3ff36a68a6a73a72057244b670b73793548abbcee02c6f680a975692fb96bfde7b5097ec57f7a55fb45feb7f73afd6598b2bde1c97f3f0764edb4d559309bece42facbde9ae8f3e1ce7810bf87f6f42980de8f8ee240216f626ff06917cb6346091f18ee6e65e58055bf22eb481882262c5aeb8aa558d4ac014f2d798b7f4942d1d80b628394fa02b9aa8b4a8823206b0b96eaa1fbf648e4a854ea8261e0d13fd88cdbb8447517178163d4eb3a878e03639f76b64d7d0c91007528285d99234fbe01909c309716357a8c4ecb7dbd7abb3062e48404204572fbdb4b1605762c77b3d91549361138d9ae0104e82ed8f18fb7d11bcf5e65561a52d6be161e52d66e4126af90b8b8e4afa5b9a88f7ee7330f661f2fbaf6a9c8cd6356cd9d23909e358f95961d6b1e4df8dea0fba2c2fa1cda38132af5649be79d626cda65fc3659ae665b188983c60314a222348e4958e552e6840052b0a50c9aa1c3739ba269f5ed94c4384af43a1cbe1d6010996238a0bd50714fcd6c5d05c165184755a52d002b6862f8eda088b2eb4cebdd5ec9c36a53efa12af3c955cb6baea91730ac368d66090644962666a292dd3eabfa62b504343253cd30858b8753267f17a734bfbcfcea7055e9b443d92860cfc29d6339b048fceb6b10eaddc3e0c752b38d5f91cf49f04166e375de07d267c17625ccc2f84042843a8719972eb3017eeabd7a897ae1f0bd1b4324c5f32ea9956bed80cc5199be5ded8a1c45ca7b43de5b53b96370b087f0162b3af12dab1681049107ef6a617d85c215e46af2db2da3e1bbf9e576ed48be4642088b617ba9b78475536f5e874df1a23b789d18b99ab715ff2ac6f12f4dca6fd8c74c604bfc9de35db14865452e2b2693b85acdb2f6e27712b63210cccc4af89500a19543a64bd90e2559d2823d4b1c7753563c9c3e83b71245e105d736655f8adb0f722462a28c323544215d2f14e6d86ebe362fb3cd7699bd4c67cccdf2f38d10448e90e47297227a0a7cc0d7f5020a5f4a345236f248e290cbab77548a7e812d967d8c5d0750315ed2abd84b88ebbcec9654d70b48a35e5773e075ff689fd58d973cc143772e68aa4b472daffc0d178a49492fd49c5f2f4726f18ac49d9c8c0d8a0da908c28d3a356c659a6ac3c5f5e119f244b9dacfb86430418114484a4136a6dff2a8dc0cde3e0871b531bbc647c9fddfd3824832f34d18ec5c888d9ac49ea5eb3295a1e45063c18205230e47013ed8bbc7f0ddb7794ded10b8ad5d88ad0eab61cfeeb854b2c25a4b8862457fded58de78c0873d8cc6ccc09494c346d3670454eed8a68fe36e15d6118a48a3b7a9ddc95cac4ba7c94f4006c52d01c07e6bdd28f75f5daf085158f3b7b634136821e545e1e17e0b8952a68b6277696d7739c279b5937def6d71f43221c3b542348af3f753bd0e68d1f9f9143663aec33ed12ebe42833cbf1c9302d958b0e5335410555cd2b2993bba6a14265fabba40bef42e911209bef4737559ecf56ad21ee61c465903e921b38ae60b407b111cf448487f710b1eb8f5cfca670fa29716db1ccd05a30f2c50dde13a0dcbcbf212f58815c6e4e2b80308abd1c6c1f2725d555140df8a7c55f29a6f213d76f1bb8fcfdafab9eff2feaaec1aceac9cd4147ff20b701aeab54686af40b3822abd2b5c3ceb9a6e0752a1947198ad1da43f9842d8283c46833c7cd6b65668b39648a36b2640a8512e232f3dc81646119c22c6cd4bfbac4f340e62acb10ff0778e7aca57c030d70cf6fbe6d7ca04bcb16c813a4e7fc082e7a548f99092049d2e90602dca3d32a9e8dd1c5f4494c362290b0e0fd5c8e566c2b716917619d95c2c8457906d451a1a9964acab25bee4928749ceab103ad46f135a6d252344f132dded693eb002348745f84b05ec19e83d339a2099dcac302eae487d0248075e53acf6a1dcc30b4520c3060d96feb7e5a5499011935e7a3347db9bd075003c93258830df2ba3d3573025c45e5b5157827096af78aa5054c2c686c80f2dfc5b93a3097d674eb407271687aedd15357af786204c0c887892044691124afac8ef70eeb96a083b32106318ca189c42d3d28afaaa832be768eea68efc1c547066cf581b015d6f22d9c782178881d54c7085825acb6b6a73bfe8d5fc0746f89ea494bb802a0908a8fd0792120ef80ff636a42e8ba0a46816508d4e9dd339128c185a248a425c9e3c4c15026e6dcdc837f089ced4a0960b3cd87344b0e77d0704ce7b09f70fb640676374e5052820040ecf08fe498b96c09b01cbe9078580adfdc29bd09eb87a6b1b022480d75c5ac6370f99c9197f9e325f010d82e080415ad4f2f5cf1b952adb2680594df56615c355ad333a5e00fa3355869370526dd75122aa57a2c861f7abea783b35a078336b640e821e0164c12879e96286bb7ea6f97e53e4913d15ad67ef6631d9d3320569206753bdb20d276f8351cdd07ad5973c298216be156f188f381186b8402a545ee9a435699d309a3369024657f73109a146c4affc0774652ec384e775859478d751d375f1fca058dc8a61f427956a92c0a8fc159c85c7c0bd81c65557051b6142957f5860e36a084b0dfdbbe6dad6197df55ad55cc13bd581cec6f0dd9ef4daf735db05883ed7a399e914140fa41f2b1b8492203016d2ae281f1d97a510a5d46f618d5ecef9dd42e2953d3e3cd3199c1459adb2f214166c418e98b71a83c928ec0a7300a39efe5f9a9bb46a780da8d8a561aa7030ac449a6ff05f18d92c8da063d771760ca7776b0a15b2da2b31841fa9a6d35fb9a5fdc7e725bf9e2cca55d133632bb8d660e8e0748ee67245015a1843bf8cf711f3a52cb194189c4eb6ce760659f3e1c996e0119d3a9a30b74076cc19ba9afa13650d100f766da004752f2a9b76afa2b6e2e6e0ee23cad84bf7cac57ec1eb3a84db84b8d185d3cbd76779a85b7a4249082845a86f345966ee52d6814d91ddb8daa1fae5ba1b457854faaaa349868c097903f8a7f4efea3bdd66600e47031f17c1c7a868cbd20bc0ad9698f4f39165b148e3206eb445afdd6ad09c52eca39acb4e18671483402090d9eb4efd50041e74136ace3660f40a2e6feadfc62a10becc8c29c52af402386a10d5a0e06da445e698d0c4ba557d1225855b9cdf120b5164bc00937eff52b07116adf097e95b4b21cdbf426219870bb16a422cbec04395b0fe68360495745765699d7329661e852c6dad342589cd54d0eb807f6a450f2c31dc6f2c8a081552317dbfa0e32a8e24d0d3b0c1c0d158be389d7ac7cd51d365648fce65cb3f69487871da128288ea794eab5d87aeb3db4610c242114b5532ea2b15abbd63aa1d4db36a2fccd8dee5679f705aeccd89da0dc953421c85ae9231cc69d51f317b0d9c4c56055d6904e64cf1ad1c7164cbe5933c32916914b0188e2ffa0b8934e3d89758b15ce977bfe2572db97bee8b446d6e8fd45960235f267b8eb83e3f0ce68579908bf48f8e0b2ea86f1f49f9e8bbd6795e617042dd301f663ddb3b68c505e49606250b4adbc1a68a0a18391a940164cbc28102b539472bcf1102831df4689dee5809981d911a307e848ca58d07d599b15d03fd4d37d2275e2bb0dcb2373d5ea8a817a24b58a93f1e99161933a24bd96a9b2a9caa7d554bcd9a7899c4af38a03eae509f55bfc4cdaaa7bc1425c6bcf19d7f6700e491146c6a0a12270afcd43d5ff02f56ef09274a37a7eaf9ed00256802d8d62d7e2b6964c6c3dfe5bf3999c111b3f7a460a5a808b93ed6d656061250344647eb05d76b40e0758e2c40cdafdec86a9ef8c8aae2c510b7cf44eb9ba0e5850507ac9b8e924d29c65d0d7b97f1092f026d095da01604bfe27d2aaddcada7e36cf16943225f5f0ec35085c5d9a9502dd8b5e76e356dae14208c895889595ddb9a46030ca60c69a612194ec31a966e5421dfedecc228fe79971e7a787f9342dd28207b88c6137c03ecd3c2fbcea50cef43519bdc9ffa8fc4d79e4a839630234480710d87b50100d22597416e4e4828a18102b9f65b8caf13452ddce41c508bf13399ceb70c870229784935190df1b34163aebde26f0fd51bd9710676fb9f92f238a55d8a982bea674d2713961e1ee86672226febb68f0e4f1ead2fef3f5c236732dbec6aeeeeafd9e1e04490cb8e9c66617d388d52550fd95a24368a35b7cf254d65b9126c1cbd4ace8dd14476717b30459c636904f632bafbc70fb61e1af774be88223b13664cafbf07092d8ff0c8f6422338c6bb4db7402bf5d0aae9b7539d6065c7c7e86789f072148936432d187315080e0a3a6cccc5265792e793c188246ffc24ff23928b5c2036676220d1e426dfc1a0d2ea0d380e8497a34fd3afb6388377dcd33035ed4890ffa8a5b650514779a2a6f3b5938cd6c38d84b36b49d6922ee696882b520298b78b21e526af06921691613dafc203375511cff347bb7add3ac84d36a8842572a41ec41c8c25241fcc870e288e504ded9ab969c4643525f0e96d8b5dd7d7d8d637ab8c046927ce4a1e1eca7525fbd0ec52cd2aad96400e8e6b979ce53d184615e0d6f4d41faa4115b11e6bb35dc01b3df3e765a6c6d2d11e0ee25e34d2947b15e23325494cd86e2cc6349b781b3e870b928dc5988c041b0528f10de04f8bed58303bed17b2ce25953807486cdcc54e691defe45337408b45080b3e1c1fff00e749926989326a1868fdcefb64f7c34385a545b1e77cf06b4a199e71dc35919a7640b2c32c1b2c35c96bee60977e02c3e141a27340731369018e5f1bb10c9836f2317f92ff410c48201c195e9cb13fd4070e6ec56ea3bfd86600fc5b3e4b2c23a513e4c0955b78c6432bca7e8ec8d8518b9bc52f71cedca7a3001c3cada8895e61a2be2aad5098106a3b20da0d246ac47034e1a7980f862f28bddd21d5d572f45adba2a798cffd7ca5b0320bfd32af6d130f17a8d1b14b762ec95b38213de2c5efc088058b17de610bb4a7fc9c9003ad1a156110e7fb174f1d64b41779fa55141ebf245b4597f58ca6dc187baf035f05b1c2fefcf7469d09f6ed94010005d8bc01bb7e5188c1d7527fdfc3f4fbeb1577cfd7b305b7e70a8d9e68820b9378492b5d54cffb949acdf37d261af60f1d2604c175f99b6158d2bba594632a49fb3be159e45c5db318235a793b364bb8b5497b77a4e159fd9c6acaf9c33ce472f22dbedf0ffc83c059680abaf77ca1475012d621c58cde5c78fc0948d54f3217c9cf79ae8b57070e8ffd6c4bd69d912403a6fcf046a90c4a33ddf959b7ef88154171bb3e354a37badfdb88aa1a35d5cb31e2d0f7fb6d257533b631841de10ccb074381f354745fcc2c12579356bf16fd73de21c15467ae4781e4099c0aa2616d75ffd202271c42ee9a975cc8f4a3c9d0c912af7ee9e42f33ac1621b137a136cd12a3a2e71eaba276d9a07a0f3525bc505cc0a63edfff263f9c49c729bb6e9e82304f30e86c9ebbbb00995bd49547135f49991a721aae77bfbfdff834506673ff28367fccfe4278c8a50f7b19b9a2d1952c35baba20cc523da5270296230c27d1fbdd0a5beb1e77ab0c689d55f82763c844f937aeea3e336bba5d48d428b293bc48a1c41bb013c1116e17bf82396f040d05e418921e48c5abd2caf5ec2e471050bda1d0c14b656902b16038a7a680a7267be8aca8d2ca45fd17e5b14ff2284e4900dd770b8d404cc6464a4d15b22724f835ba6af02512b51341f31a25785f2c0587b8d4d5550ac7dc55d4ec3c15d5fa0fe360effb262cf1b487c5d1f96a523f837e4473ad309939b335fae54de8e979c7b1169151b3a830f21e14b3a7aa44171b6be3c1ebf9160fc1e8db3ee9857e5f20b610d6bec5a5df3efb642bbbe02199ea1f432075d856621f0ca1f8a613401279963cd46a5a7968e44e2b42b78e881603b938013270b789ad74d44a277e3b36dbc340336c6f581512a41cfd1196d5b1b896fa6cb27ea30b819d076d7c6f934c67d94d67be6357eaa55020b728d52752650b7a809f2dec77d4ca1223bc60bb5172e34ab566d1a03679aca4b8ea3039877a3e03eeb5eccbab4164e6d4d00cb04cb90c1f66d03b916358d297fcf6153c6be26a75852bda5372487f86775a5b159020110f53273a4a0ff5a0abd2dc8e122699292846f6e965fc5028302029a56da8f24c03aa473f84706b48485aab74d9394aaaa4b865d374fa159cb4ac22a4e57a757d0d023cfa001394c54cdfb8ea80144ea60e999abd097407484ed201799e1a00048cf6ca18fd534ec02239c4d58223b30c46398cb9b8a2a64716ac034fd626da882a15b8dffac7cffff0c22415762297a8d858308e85af1bb57cda415c56839ca3d1abc0a69d39bd8d1cda230f8381454392592005e47c0bc0afe278290c7fedca1ae7c68e139096bf69cf5d70bd3f392ab21676e2206793f7a700075cdb023ad961615c309a15a903692cd16a9a53e663bc734f48f3ca8d11207b23f26d75ae4c6df1d7a841cb2524025bc40a375bab50bb66f503306d1556c1180319dc94eb0086c963c3c347120e149137fdf04dc7f3aae03f9009215bcabda59472cb2465f205e505190622cc0bc691378934d4c2ef9d869df03b0d31d50acb891fe1770ab4b69984d2c5e8a2c6c510d3e5bb0b1b4c19efa2e5a245e322cc0be665edbb68c5608456851fa2221415b0a3153e0f548c4115be025430af7abb3ef3302f9817c6dd65b56aa613b41a701cc7cd6c68fd3f7f4eeb6447beab90fe6cad075c9c331b1be0662d362a6601320a88c14cd898183066f4339bd9e86ac69c9716eebddbddb67befbd35df2e667b3b21d36525ec6e6cc06b17bd9b9bd0e2f0e68eb76ddb70def0dd2cbef6a3dbb518bfd0247793d39a1ce54af34457ab6eadddbc1bb6de7aeb15110e0801b08470203c540fae86c0c52cc65095487984bfade0b873386b20d7fe2b2e7763ce4b35d35a4f3d799fa8ae83baee2f84939be0ffb66dff1dc8eacf1e79f0614ce033bc21f194394d1bd7dc5a3c310d2d113eb8e176608db8e974c7b9ce8c2b5d5d876feebaaeebba10a80b81621c642fabb67c8b8c02134c998b4365e08e236f1cb5e5a90c8d1aabe6180e0e1a79d66a791be17bf78cd1a8e1a819735e3a5747416b9df96577185e374a2765d164dbb66d1305e139fc3e5fd3a5095c7471137e7771633d1c6ceaf6999f7423a70bab05c3d533f3bc1b394fcff33c8f14041e7b85df41ddebe21def48a59ab9dd64fbf5c0ac9dee98b39c22d7759057ba40e64479fd47e3e8f5abaaa70eea2ca17855ac9b20421a376acbd730e39b9944f0f9a0ee37d070baa468cb276159429f4b646fdf0081bc0dba8890d3054475c971dce4266c868722c10bbac7f8d3e676497f1078f0fb8b30bf28124df07b0d776fa18a0915a1c3ebbbf4a18e2272d8ddd301ea2112ed39843aa8bf47a21f0e5edf5d07efd321f481be98928b31610c8dfded140906776f9e9956109eb02e44eb9c909890f356b79a73ced9f3c1454ba33d09926dd654534a6f06612ee38ed339630de2b4d619a439ad3507ca39e70cea405cc660ab66b401f1600da1dafa0e2c24b0b81a0efe4760ee5a8edbbacc5d8bafc55de6b85a0d07eb0a56148a7dfb6ddbc46eedddc27479234aec2479dd8b7772c6e1b5d6ffe79cf37fc17c1a846f83f0291576883151bf02ba053a45f8148a1b5cd3e53b4a11f07dfc899e66d41612ef33e49efc0f55c2c3c1ccaaa14f1c71efdbbe11e7dd77edbebce415ec149633e73846bf7da6661e3e0086b99368df6eff230fd28fc21e8c3d486306854d1a0f28d8410425a051156c0de8992edf6119c36ac61c8e84176edbb67960b67ce758b2f0daaab5d735c30c3332c8c4ec80e3329c0fdaedde568cb6e31acdfcffffff0fc28c728bd9c8c5e805128c69dac497d0e64bbf7deecfe2b8cb040db661b19ac703c991bdf7569231c63c36e79cf26ddb38876d9f799ab1334f606d814d14385128a31edcdbe104dcb00ddbb0d208d8444d315152a0329b0a2b09490fc84226be192204777a09dfe02161bf89bd49044548de24052ae37bf5f214d3e5b19210cf1ebc992eb22176c257827595e4bc48d886398002331e3025304ddb84e527c20763407cb361229e37e10ddbb00de3b81c122d94c4b6fd7fedb694811bf5abb17d312f327e80d9f27df432655bdf425b9a610d1ada12388e76cb53ca6c696756f6834f2190461802122413db9c9903d1b9dca948c28b91cbe8af68b81255f89df3f09ac8072ff94ff8bdd23949387c8acb1d8af639c3ea2595bb0823e4332a64e14cb178c2c54e27fcde9576d7a5aed7127f4d975ce2482040e44784df397f42070af3c4b99119a322fcae4b34454b4176cfa5554eed4f751f529a2bf1558994035f9168e77ad737efbe910ce9973f3e721fbf79d17a51f326dcc15858582cf78514f0fb75f7d67a2fb8523e08e34a660fca587acc53a864594d140db18458422c567091cb42b237cd2220a147f89d3fa132b77fe73ccf654cf80f95d1bd60f5a43f0e648e303e86607c8471d9187e5f29df44f9ec4d3c5c5955f2a68d86b9b4a56ae98619d69c59569f6259d51397c95632162e6391d5d39c93cb26977d5ca76110eecbd1432af71e5461b59f38825599acd631a42f46ce4355331b3758351fe8808a86369f703666cc4449c2c5397bfba4033728555d41b13085f0ab98560863ea69ca30a3fdcc66369ab0332b53029b917b0ebaf70e045faadd99a7395b6ef7b04d52b9dced2786c25d22e5e0edee799ea87d42da74e1f1a0cb2b7dcac26c2ce59548baef2f876da2051ea91eaa155245d8ee434ad3bd696cefd35af6820a38852d79e91e32898fcd7a70aff66ab3b48f2cb8b68f4d436568f8283a25949c077fee9566b146b1fad9322681a924a4284566cb77232d255076c2ef281b85dc377bb557b5529658f621bf480a22545996b396e9d9ea4d532be1d1b5ed4397fa27fc9e2f0ebdc11ee114df7befb513e5652638ad4e9400c295b0aca7ddbfc5ea684da3791e7c2dc3fd8e32cd7ab0f56279bdeba4db439dc4bdf68abf4bee0eea3ae81efa7474bd881cba4febef8ede773ff1de84af5da4ec4df335cf9fe62bb1304a65b9eb5233d13251bbb2ce91e571a270ce44d123a6cc6e221b65b2fd438474c9ee17447751bb6449e6e9aa56b6876597b4ac4f1e5244e552b9542e996bd43df3a459b5e5b7cc7de2de31ea19ab9e74cf5759987565acf2399337d91d3db27ada33273e4bb4cc3544bb260a2b61a408def7de7bbb6ebf6c071e004f25a6914f9441eb9cb7ed48aafc2b32d833ec6e9fd4aeaab6d46eabf6432a10fb846b65423002c9915ceeee8fd9f21d481632e4b6fad6fd454393cd564f4395f0d0dafba025fdc93cc4bf11094659f423754e2610efe37980ba48af5069ba64faf000fac40ff34bec634d7bb711eb47517018a1380ac1880a6125e1b9e34e4665dcc7b94cc6d58c39b48d85ccb251f9c85bcd39e7bcbdbcbd553d6d6f454949f0176d8686ddbf036f630393034ee64f9f79f557e74abac794f1830f204ca889d1ac66d649f8b5e95203a2c70e6d69b41e2b203fb89c73ceddde5478a9542b578e8f1b1e2311568d55a93a6e643bce769c135dbed8721d672fb69cb55be66ece397343840051a9f04b438134eedadebaa6bad944783c69e1ac3a1e20084ecd63af6cc2ddea3411fd64ab746df7eebeb4aba4c991cf07ad4c1381f3ee31b133f504824c6833253a383851543bd13550d78ad0e19170e03dd475a01ea87b24da53ba0f248ad2731075944e3b0a4a4ff972781da5e7e09ff7e580613402a64af14fd47d48bd2d7f46a599bab07a12d5134aef36749db47b4a9f295189b43f120e1ca5eb20ea299f0e501775944f47113980bae8bbaf50e71f0e9f804fc00c5ed9bd9e0d1a54d2215d863ae6035edbfa0b010df59cdaebc1570f6ebab5ca149a095cd4319ed7446d188a29e3d140d7eeeba56bdde7d1741a89ecd3b107f717c4eba28edd9b1d970442dfb19ef44e923bc6ee18bb634cd762f7b577cc9376d596c7bacb9fb881ec1dfb47adfb6adab512f978130724fcbe776cd5feb181d83db4aec576746ca2b45ea27b741259c7cdfe56fab584e27c753d7b66126e47d7746def6f218dfa475c1873e63a67dd65dd09a1a29466bbd5ef35a6642293d5f01961486c5e35e4911b22b6d5b21d673bce868bb7ffb843ae900ca4429173d65dd61d8c8b73f6bd0336eeb55bb6dbb5db9da9b89c951979c957f9a333bbb2cbe4c22e920d04419c07c1bb8589e2029581e2881874d48453618a11139a684d17141f2e5e32e72f69adf568b7db2d7249ceb7ef92300c9102674c8936f6f44548698f3151b8d3befdc9528a4b16a6f360ce76bb39f4be2ef9e06d5d32e70895a1fdbfd7ed9297a432ec2bc67b789646c2de74e44df592ad6ac750044392b496666506eab16ddcd6dcd63713c0000d0a54609a2c17e3f0b5f8086b6dc636631ff08ae30a5bada596d65eabaeb56a4e729d6be8955fd5ee5a6be62427310087b473e10ec54cbfd0ebc15808c83459d28e7608134422fc2e6e3be16ffdc220fc5ec789bafd88f0bbed721efc0e8909be1190d032b830d846f011b477391d12d50832306235dd3b46082b1019d4b11b09edf66db1d0ebf6d744d9adda5a7bbf97a6f5d22c18e29533e776bb39e79c75a7bb5d3b0a10b84855e2cd59d571a6cafd6ef4d2551078be76bbf7b6ea29e6b6ec76637c32c7d58c3977093d6388d3091818181818181818181818184bb52888adb5d66aedb6618ba9b5d65a51100ef3caa220d8878b2e5a3078de24ce2494b3378931a8623c707d043090c090853f755430606f0a2a2b2a2b2a2a2a4434cdd3c1c8dda7bacfd0f5d97531a9eee330f28b175f10bc061a109851c383e10202107e9f3334e665d6d85e4f3689ed91a9642fd5b50d98a933e69c3bd6da99ab9a7787eac705a2dedcd1b46a760fc771938b51d18670f1ae4ea32e66094fc49c708214b70710a31fe10359c57039a666cc795d57fe9991e4abfcd199e5c898c0909064599665599638f375ed5e15b8487b05c175e918f51c2bf021d5a2b3e82d587a8b79f294982f653d3d4b67c1d2c3e3d0f0dcdabd65296215edede65581dfeb62c0edbad3db75d7d386aa3b392ae5d0e1441536a91199c3c24577f1e56891bd097cca05cb8b1625afd5a2933eaf45ea7a8b4eea2f4aa41c00d05f74007cdef8e223759df4792f58f7e1f02917ddc5e7b93e8f7c81c788f5a6ce2b535629aa141fd72bbdb29e46addaf2ca51cb2b47ad11e7a3960a38e9b9caaf878b1e4a1f410e53fa77afac2717fdbbf7f32695bed2e79b563e917ea18bd29652296d172d7a4a890439445129edd2a746643da5a82a574911a5a86a8aaa86768a0ac545a76110175f8e1e522d3eae6cc970cb89ea64e866aa685422c12b882152bc54aa4ad193992076eda8b36029cd14f5f0375bc0b8cec642966449962449be58585890008095b017594f2c3a94858545c952d6d388244bb2244b92245f2c2c2c48f20e60967d507952ebf5210ce1b44774ecccfed8d89d31de846bcf2f84dc7de1f6d9ab1376f0040c58a91e6810228515a4782814fa72d01a68a189540f2c2e48f119c24e4665eeb73bd93633c383df2b95390002a60b7d91828bf6f53c6c30ae52619b20f382cac81dd445eb04fb040cd3eee20ba1d7f90b2a53002ae33b2fc08b0e0ab34bd23d418514e1f7de59a10054a6e3a1323a2bba280cd0c9ecab93bd58256248ec12254230db9364e7d5c9ba249dacf3e996743d21ce43f6655f39c4a269d5808ab8f7ce3be96a45e94b9db32304811376bdebb7bfbce078f0bf7fea05ada921da6de721508976aedbadd4f5ead9704b0de82a950ac7834fa92a04848bb3d6e927764e8493dcf5a02bfc9e79c77d7abbcf69ba32b577b4739d976c09002105c516d42b8939c45d892b890108f1276e38deb4a148c0754c99fb912087b57fcf3151b5571c4eaf01738a907e41407de66d5c8b31a8b66ddbddee0ccb7b28820fcb5bbed7621e587be3bdd096b87f0f81004216313de60e371ea4a6edc17a638757c5187bc0a756518ce8d6f60fec80f2e26207e9abf6a42c6fd0b458dc0f9e12f6ae3b04da215011b5d809706cca82d57e544f74eb765781199bb1df1a07eb877961735e18c2898476cbfbe4ca26cf95fe68b5ba4e57b78e4b5a3d3af7755b3f7899c4ab13fe44cdad05de75fa267a6306f3148369ba54a6062ac3053389f0c728b899bd1a2ed2d5aac5c4a4324b4c9407c513a113e1731b28beb9c3f2fd7185982eb766c816706a36b4166c058bedac62321e9f1d586c494f12586d818135c4bccc2460b05d33e6bc745668fc07a2ffffffffffff2993d5a89133e7a31108bebc4451a33484c2ea76618d7afa140b04790e2aa4c7c80fa91eee1136299ca3f65091eae10029dc59940564d62f12fe0f1112ae6a063e011ded79a79ede4cd49c289b0122e1531606a6cb94b1c094f14170f15bb023dffab19e701f851f6cf4ab7aca1f89e28f44bf9c9ab5a58abc2346efe1d314701b35aae0222d298c2e9927aa048595949694969465040f8205e0eab47f0ca0ddd61269eb7404167fa4cd09efe1df746ac3f6816004b60ef3f23733d89839c529abf12652032c9da1451068c2c686222dbf454b26ff4f3885c1645bfd96b53429549484e88067d8bd332010e133e7bcf3d6b85cbef8ced07218e6c197db73e7ac0db66a6f72b5b54513380de774a121d7c03619899cf39c74ce496b6c9df3b6c13cf8c22fc0819cb73b2fe52f251399ac460e0fca38dec4787060362f24ae9c71cc19e46ddd7119b46ddd71a214f05c5b70d7b207374b65c94f2379b0bed647545486b6e4f6cd4fe4335b64a667b35a33b240ded6dd0be8b3348f6637cd6a9aed6896a3d9ac91681d0d9b76b423796fc691d42cb1bd7a5267d3b8f6cdde74f21048771e0f8174e7691f5aa52d15ede01b8b27df5df534531335c3da7390a0e83c0f4a11bb13fe10ac131e0e9ff2fa9c2190062dcf39c43968340ad7e26bf1e674e63c8ecbdbebf6de9db7bbbd77e7711cc771209047e405071482069df3eac25f28905c968dcac7d533bb75facb99b4042eead5fbd0ac3755fbf3a7dd399f29838589ca3d78d8275be7abefed34d76305e4071059773b167a596be71ce96ac3176128663c9ad4b3efb59071bb8dee0dbdae129060cc7d3826f2a6bcc130432a6406b447379dd0c7919a292a03a232a01af183083f065d7c16883efae49bd30a2b84ff51297c0bfe872aa8781eea04aa848f2681f32fe12807094ce0c995f8bb816fc7a51b7ee0e259cee0500e856c5e201df0f1ce7be9a573ce3c7f6606d7712c9828859fd2fda90ac783b9b6905cff9e73ce20b9e3a0c43a6e07ffa80ac78339566a78c9afc5988f7a06d2dc8787cfe4c40079fd3b38430d345e60264a0c11f8f5077d443e9a976c0f89b4e6a519864a33d4200dca79134de929293637382e22a4e8bb1caeba3c6aa034e194dfb08573ba6c321f4c8f5e0f875c5b362fc8ed229d80c615638cf1fc0ca23c509491188a10d65ec6a2b0c4138ab084b0761115fe12e02b540927a80c6b9f79fa024e598d2aabb29f3299ac06c65c75e4d2a7c1382b1a5cc3813b0e771c29093834c7dbd61d8731c6b8e33a0edf9c5b35237e0cc5b85ab1481d1a978f9b9c96cd6a9571c64478ad19676cb71821e69cf76ed5de7befbd20ef823c22b8484371d42af263a78bf4c8eb84bb95133e06684cc0065b0dad14de56ed566dec65de160d5ebf187ba38d2b480c56ad55a57cd3afb4566c547e56582bab951a95da0a8ff07b9db57639a21d9fa8f84c955285c99b6cf85d853651f5767145a5e224fcdea9942f95f2550ad5a6cb7795b29e4256841f92f1e05fc9c590cc7e5d4e48d6912a2aa54aa952aa941f83859d90f3eb35536bce59a320c1c5598ab98495f7ce57ea01330bb8e15533e6743e1dcd5a1a36639bb1112eda725402a15cb2a38cd1e86863ab35c290d8bc6ac82337446eab853371afe12f32a4f642fe7fcc96e738fead2738d6c2ab805e41bbf0acff61449b34898941a2aa3dce3197866ca381b044ce39efdce9ac77eef4dedad3debd9dd7413980bab7bbee3a6c0df25a50b1f5fe6ab65bce1b0f0c36a366cce1beb38268463ef92a7f7472feff338da679d01d689c8eacdd369f1169ea798161a250464eb84ff84bf809e5878b4fd69fdaa46681cad849a3bd89f480dbb77efb2c32c989dabed5d3eb277e92d1ce1b7912e74de2cbbb3e0bb509edc9e952a41ca2a79584b5f324fdc171fdcd83f926fc3e01bbe57ced7643e0e4fb0ed3e5fb93f9c89b3049921c57338a66a026381a8f19138e86b7bd1a93d2084ec189a259c7623b0610010490314d56a6a5825ca7d1713150cd39412090888344fc085c9c37af21f880881908311a417710ae6eb84035afc0c2147e8881ee6e4ccc9c53773b86f6339bd93882850bafdef4a40ea6f1816f700e6e611b2e6f96dbae6cc83c5d5bc39e1246422094621ee177cc7af0f1ca946f14e15f1e2aa389d56be66f4f0dc289a2fd06f31211fe8fe9f2b78579a8cc9d793cab7af2462bcc59fcc3eedfb10b26aac3e30cc22f274ae3d574c1b8075ed912af6c895732c80082a5528b16a212d3abe496b3076b3499323907b255b6992e4c6c79677736b34ba68c2a669756e2952da70c038159addf356fca78c8c62c2c0403c17865cbbbc33ab13e1dc7711c870344e78548c78109aff04a8fd3c5e686858b8511d70e817608c4030cd0a040056619bb326071977559ae2144a218118b15118b951b9e42516c260abf485710510e6e6cd7690da511f9107591aa4054a389ca158f99ee35c8c8e0a1bc1eea200e059509c544223a321f44f944229148c46285c5d757fab598cbddd65e08c4fbcc77d1919898181414111f3d9daec43e5838ae7d850b63180c06c318634ce610818bba1c59b0b22ccbc7e3e3f1f1f8787c3c3e1ef1bdaa7a370b635cfac088cc818de338da3b8ee30863b1c20b64b67c2fa21acb51bc6385591e52bf82c0454dbe96f894a63b1e09458b83832f7783e3ca19df6f09098136818b57d56779034d43417941595141595149828b5d4c3379d3a673744cc33412fa8897d6d144348eb6d142e81f9aa5575aa5475dd3d576c7d339d1ed4c148877b1942e7656a4744d1e05a5530203429c9aa894ec9a8cad643300000000331600001808068442a16050341caceb900f14000e62785062523a1c89a3c13047610c84418c31ca186010008420406088a66c03d6ffd7ae16ab37ffc335f98e0b5642d2f738caed22a0bc783ca8b5335ff8f7a9afd6ec0b8fd4e91d63d0d9be1e9a323419ef4e3dcd95510a7faf87914c11470a611ffb06d6c6c81a6465ad44e61d573b06cfaade495bfe6f40d19be29ba962f9cc70d0fbb84e27446beda2f6e6aaa4bb9c176dbd325699958e8a3a210fd860c3dd3637528b1e8d865881b18b613b211153bf1bb73118519e8d25eb389da645f6f89d58e5a1173f7ee867a7a91d1ad9fbedab51fd3b2aa815e69b93eb856ac80891c9cded0e49fc623daab269bf34afa7411d7573c85345372ba4106e9e598fc4a76c0b48773ece975f9d8da31034f6dcb1e4f17bbc2a17afcbef5a80c5d1a5f385a14cb71114153115ae6df32f13525baf42be8a5bfde6fd6645986319306cbe4a6557ef929aa98314b1178171eef8f0e05b540eb8e881b7e4318101356da771ec6bcbc336c8a5b30f9a40da7b01079affb78053b42c3f036c5d19661b9fe2564a5c7b0a2050da0ffa2e1de5cd6e6cf36eea3f321278b0e945f36f2bec877fcf51778b7af1571c1b718d4f3bae0735dbecf4b7e28005c99c2edc497eaa9b07ceed62edf6bc29f635d70b0a1ff55e6bbec85e859b802f73a5924bd275298ee7ac6acc034960960d642e87ee21924e2e88cb643e168bde8225935881f31897fb245c1e47a6ad9f72c9eac9b7e6f53c1da24e459e2c21fbe70b5da100eca43b2821485424314abee5b161f00bbee2b9a39d35ba76059ecc669424949b672f2213e9d9bb46a304a29ef03adb4561f6772eac50692bf5e0809260ec84e95d280c7294f41f7bc68a5fece9a58f284914d1b3ea83d7111d15060539cd4dff194aee0e7b155e261679c108728490834a3a302bdc868d2189b964122e18b5030f73a1282ccb1c5714d28bdc4b51c3be04160041aa1f934d755018cf0c1eb0f06904db52a90d258392537313a3cccf459e72ce2125133f845725f2568786364d01a93b726bdd8ae290b735549aaf62ff8b91923481aa484d8e9488c6edae472a7eaf2ed245b413db2beffc60563b967d08cd0e2b64dc328ac27a2056f45c72cc366230a06e477f96ef63e727f699fdb4129bea3bbe04eb82f9d10da432092b22652e796237474c2bcf54931d9354d0c40b897a0eba0c61c02348825e2a107eb3c661a66d935aa64e4542a445e4211ec890d0e071154f659ee1fbb0361c3a2493202bed0a20ef05a78459351bc0be2eee55d1182a39c31b9f7268559610b694b3f71ccdd1fb5084fb36a41b7287a92a897a297a100242db8b1a7c1fba9939fa10626a9852093cecc187c6d11b94c1165f60144ca9ed51c59e7eef7520f38151b39be954b4232978d4c888fdc86294dea7ffce7844e363a22cc8dd3cc2cc6f7b87d7744414a48e365e52e127076a1ea9fe6106e85fcae118113b1fb79c1a3b45ce1d209073ac2f17ba04aa44a81558ae69f7a2416297326d532b1a19555eb00cf8463795dc8c23f125eee67db4ee1c21d80da46795708eefb929a8aff245cb693974d3314ff29cc078138f2d4654982e89f23a62b9a9c8bd7bbe971bc659c98950e4a89c252085abac54cfb69b9f9f88228ee294f172e6708385e116a66e9fc529f9b18931fde303e2d179d406fb3b81123892494788c18852eb87c6bd0ff6a132fbe9c4f7a8f51aec513db795a5d348a192e60c78d4b91cb745b939d639d0683b6f7ff656c91c6ad61a0a38a47778e2977014b88722505ac10aac96e1d1b343a0e2410300ae0db755a007328474884d4acf1ff0f202b3f0c78e2b084336b92c04ab94b18e2dce06269c2061e5e070de4428d8d131f4874a1ce25cb19801de0785f1082247b2c0745ffa7a61aba8723c59bc5e2529d9494b8628f9876d5a496c33e9e96a738880f6198c01c21ec8d0aa9e04d774ce55b801f6123baa7301562d524ec63b27bb9861b303b50db2b745313e78e262692d3a0ee65830dfe93b575b1f4c941a77a65fea14a51633ca37a1dcf7ce6bc3aa14de1f1e633e112b092bcb12b7806271792157022758bd383724e88cf4911e13377b9cc90965a3053c1f1b53baab8d197d531094dd4ee6b2bb4d593e103e576d51bc85edd4ad1803638220667fc988aacfe784714980698a9da44ea3381c5091f9864009ab6064b3b2505af6c7d8467414932866d6d0dd2335e5a68124abf7212062f4888481bfe479826108882da40c653acdb0cec3fa9c82ee0c616ee6d5c5fcf57ef1205cf2de2d0cd1ec67b4def165755928759aa2eddcf39c01860aa08199c9e78f410762e9acfddd4b2cac425c7ce8c26881e4e9f6af739455a92b148429d0b50cb6c17941660fc199f14cb75758b7264b90846cc5114079db10c88a617d06c6768219d71a22ffc7c27647a6df0d59c4822a9f32027eef64d200714f63112139e664ea0bdd011532157c766889029ce1e6f8e2b6c21d1493721a6e2b2868540045e48ae030fdfe66c6094bee92028b608d7eccbc3e6e65c0f9e38dc589c3c59191830834ee0ba4cba0e54143fcd3261c26c4b9d0990da635d8a9b7ab6ce2e315509380154ab395619ac514822e39767fe047498ac5603ccd6965015880c8a4f50816a96aae68e43d672ab8600b44ef8ca71b67fe1b9d2c2c0e941b4b59ba6d520c93e2003313c2528bae39ed3d589f2507fd7f1b27f81fe5c5986054993dde1b6ba8acc0ac0ff19561ab6ae276de03727b9b2035328168e9bbfd0b0dfb02f7d64445ca905ba1648d6d20979a465a7dd85b8d55077d312aa8652eb5eb1cfd5b8ac490c4fc737e370f5cbca72d2d350d8ba790f1dde5cac3167dc8dc420d5b2b0621c126e11d978c143262ef4a9508742838943033b9f56c7ba68c2a19d9a02d56eaf7921ea2a50e34e0e3295372c978638cde13112a1c08897fdd71bc5aab83fcebd1c9e4d47697fcc8082350424ca64b789c1336442234370c335d9751285c614a6c894ce768d350d68720c4261a4d6370d3da85896af4cc8ec34dadb6b41b229654e22283a1e450f55f5abd56e083814e693bff0117c0a0cd289057396ed021eacb17ffa6a9cd76dae7a3842c23b35f18dc1853add828f78a42d72045d1faf4e1dcf0c9b2e40da877c277baf0929442775b36c343baa6ce821bf1aa62329539b6070ec81824872b8b2896db16ec22f7cfc2744a0d5b6b66a49f7eff7454051da7905e65d48ce88c251050844798f36d58ff9bdc81fe62ec1e10a07dd5e17d87e2388e0e0a5632cbe3863f111cdd86a3ed3e4cdedf1d0f387c0a2a778b58bd454f4ff385046f8d2c50e4c9f5998167a4ba17d23ef328024660b75723da253b0d50b9cc8f41a3ec0f27084b46cb24ec14b0e1fbfccc75321262c84d309b01a303170f92356bfda59946992408971e755e741730b33cb2960209e48401b4d1cc95fcf558ca0b81e72339995aae0e1b02f32a91127027b4d17c2a135a8549044685f6d67c1bd3eed385eae1c65ce02f125942445ed3797258fe0bb0da29ee94ef0adba22f8340f6e00ec8cdfc48e8615122240bd00a8a24cbc978d63624105b81dd21d75876bdc0fb4354d09b9053badf4794a161c9a713eadc371ca232da6e96048e27e3bc8a8023dd6a866c465ee436b4d825b31c46c793dbd4701e59192cafce631fbac3a5cd61c4131198a138911e466affc48d19c531ddb2c9cac37dfc2f1f3d8cf4a0d561cbb5e2227d573c06239225b0da1a24d52e5701b926463cca7fc818c0555775e6a8e2e4a78cc1efc2dd4cea8a8ff32fb4f5360de9452083acc915981462b1d9ca18d42688d40c98b178c2a3525735832fb3cec453589874ef032973abd64091e0bdd4dfc8d869cf9ca3bc64b54280336fe2f6d21220980c29569102730aeff2225a30299804ffe34fbaceee1e204e807e77d6c38481491735ccfd268e81ea1b61dbbc4bd6fccda131a0c6ceb224a1a26f6a641d8aa17b01048bf8370519be6e021a54c9f34a2ae595591a0b619c9dab3f64aeba0e688555338ac924759012b61f00f94ae682bc9d9a2d1bdde1d55932448019bef4896f5fd3c210b1276233d1bd580a53ba07f8dfca1026ef06a96c9da20eb195e4c15dd78b8d9b93db1cd78490ad03ccd9d64cd526a858bf559a1a9d31ffb04145ab42b97447d11694ca4887a4fabb3313c2d9f36e3c3220c4ff36aa0277ddf766256f90cdff2686db40e1055f7f797e8bc7dbdce8e66cf781badadc3327c510378a0c3b6a83a6734cf3c2cab0fa26928b2cf336282b847c42a2820a3d7e2525eeaab947c9369dd69c7d6efb675183ac6717565392f74532e0d503f891362185006ec278007285804d48c6bc3682c8f2f869c072180a82792e13abb6429f96ac90af1de4dbc404e1c875e30ca7dc1ac1a5682e18a0877e0e0dbf5863fea31d226a0844a3cf9abf513c24996b42029640d0ac6c289932d168415b79585f078b0ea63420b78c4dc8e1d3f873d5fdf7cfa49e1fc96a7875049b7f0405b9798cfdfa1f12677bd784ec9c23c2309f174674da3e190819188603a48a76eae01346172c714946725d61e19c0cbdf544598ab93a3cbebcb84111f79fbe62dd065475fb6bc2c628fbeedef8fd714a38a2417b1545187e131d5b42e642dd0a7c4a388d72c1146e367932689e86aee750cf45217c256638e194135fb5374120309746c1a6b5120e7bd0634ed5272de40fdc73c2a5faa101b46e3c2579677f41292d397e9ae0df52e2a1aff2381fa1ecb781016fb2b03585916241820e24de56cb52ed21a2bb164347c1cdcdecb2509aa2d37403674586daf45b1ba3f6abac8cbc55fb092bfe23b8b3e471759dad260963faf23bea4b3701bf57632762ec47e892de7b84b927578b58d128245f6515d9f8c86d61d54755890802c23f13cf1ef75addd4ca46986ca1ced9723374928f18731293a26f2b53e1c11fa1eec2528b1f254b0e8a639b44d6af3d12be0e1e0f4ec2e594ec34526b59326065b386bb639579b819f4381aeed3f0ed93565df1b6bf4fab33d3e1ad471e7c0678397a9f341d32040bd133b958acd0b2393693091a9712a453d3434eed24d7359b6136891add8338cb83fb3085ed324b25f109e83f5022cefd32a254282291fd06604537e2dfbf79085aa5370bc0aa319ac280c0f4d288e0a4b75a5561b3383b70fd7826b125c078462aa8b32d9b100e555170be122333e7869938056c36dee3d889f4435569089c1d3d473be55a5c078f56c35c88c414ec92f45bd6995df98d1e1905dcbf5000eae3623e8e6aba9057c684dd11b716f1cb7ffa01de04b48794545b613c2d8020f8a4a8f699e73810d26da4135115b84f2a2e438453257ac99bec00d97da1985248da0fa7948e4251df67479aa6fd7c903fd6e0df16686258b6d340a41eacb22b6bf58254550c67251d9819c28ca7e6946800506d8af5a3abe19ea205614d2ee56f65d39e63e8984ee36c327f084ef467074ef433e7d8d05206452ad1424f9cf5efb3cc768b429a6b519596e604bfa52a4aea17926822de1d310c5dda93f6ccbb2d7268cd1e64322c1c7ebd1ded38dbcbb6e83b2619cdb7c460573fb52c5c4b3afdb0ec2d92d62ceefda9aa00e9fa04f10009fbabb72472f3f7130a1b2926caf6fb64d75bbf6da19232fca0f1a40af3173d4705c7978be41d720b12e1030b2b6ee5d7989ac11239d9d7cd496bf74c47f2ebdbf8555f9224db8954328620074191e4743e62f549e8be2f7f7a7dba09a007ef81cdea24781d69a5b9668c34c105a2a27891b13f26fb118597d28401d370a0a1249c89f03b79f3aba47f352b12ac5584ac14900d9491d8a55b94b81bf1bec02ebe595c0dc6a4384d28321e23d35fcd7ff6f6f3f2d56620588bf9ef13d44647add66b276e9db33fbdad0df179701cbffde15f05b494381375f7577b21969dfe5461a16d12c7616b02021747fe57cb06fc9da36dad8aa2bb276c3620ebf41a1118777b79528b1c32d8e368c7be830dae914f05da1b0f1932070af372a77d4991316d4ca77d60aa13940af4f718a60abb045d2794f3f87e3504ae919c76c09a60eecd8931ed4cc35f6bccc69b21c27e9a6d498e89d4038625a1bd7001aa9d092d359b81f89debea234e1bbf1298275e26fd2ad21e11da7284971b2ff5c01a0d852e030efc9b0981cfe9c0698e9f420447c99a6a2aa0e5e8ab39a820f72740f078547ac0202242ae8b39d74fe4c0407c4e3f168e4e69d7649324ac91cbddcb66a001b8e0a9269b1231b651d4e5e16fdd64136cca297493c268aa7e1982cd8d9a6c18d56f4375c6d7af08921ca5e8b926dbfc55d8734d3633a3822e0356a1c9bb265b79df35ac286ca7cabde89816856e4a751785197153106214f08d2d0217ce37e783986da0e263fbd51ca3031f36d202aba6a021bd265b700b2a55942d53521daa9aea50cc106e0f126a01b68e6a0ced42368bca56d4f5642e72efeb9b27dd6c67acd3b17ad5a34018a6acd3fd006475ba979ed4e4486c4b7aab981008c0e00810d17242496804a8a91059c3246bb34ac4bc3ef5b0acf666844bb96aea5c7e05a6b0b0b23165a8d49d320f6ab57ce50e95097c231ef246b2e59774ebbed1402f692cbf9a61e5d77a955fa9319921043e68a6fd169ffc62087d74935fab00c9932aa418cfe8add2fb4eb6372b2ed86b7d0a0e87d01437709af7965c2a8bded27e2b28f1ab6ffb057312fd105b704bc4fbc3956675ecc945cf0ef741e5cbd1ca494b63fb74a50d493ecc85b509841d37a239cf0c2936c5664f03aaae5c47da3b48c434e10d093c95e4a94782a6a4c614d448144be958f53a1a42e8da8859c7de824b3d8b3e668d11a1bd3392fd86aea64be0eadb8542bc3a52bbeb29d05c1cf60ea2710c9a7d9e6912a73931b8b3164148c4259282102c49e06688820cf9a8830f04de5e5ab61f8632ce33912ae765425859058fab168e96953c92a20356473a3239195874efc5c14ad45a871fde5cd8629ca8d18286e121fd79cd3e0e1c0b25122037024aea309f84d4ec85b4d9cd0c5c919750c0afd87fa29c10c21b60a14c0c6819380878bf8a20ca4c1415a40228a47f092bd110801f31d0c619d2a376a084dac02cf499192e437c29d39fcde476c65dd12ea93fbffff3831b9de16448598bb79e39cca616ea0dc0a6c4845f511c8eb479584310c5e1683f1c38b687749bb23bf872e3f10f6aa16968e89ffa8d996e349b50b40d86c6d590bc2707268621284a75a303522774301849c5e478d909af1af35c8b22232511b928a2f525bb17252246980e90d48792d73fc33d3ceec708bda71d7ca97956392dc99d93be17b1bd8bc87f785b1cb09d15566b0277e6015591a590314d74268afc72f2f1aa2b690a095084a04965b31c5648e21754f525f1b61c2662e1afc0db010e0ef8a57487663e99d86a6b11305cb78c3b78db6376e1582f93ba08f198f455f2808f33969be18ecbd7b45e1528d4753fa514b066d84e8fa0d83cbfe573e3eb4909dd004af78364fb5e66b379bb2a673745b7e076a67f06280370323da170f4a3dbb90c43f3763297914fe22426b8303dccb0ec9ff9d87f6ee17898e5e0e263478f44111bbf78bc7c65a2dc5743c5143214f4e3fbe04e5ee37233eb75b68cf6edf973e1cf26f9c2d28e65ddc8b41dcd3c2d8ece8d8536fa1b8650aeeb881138b96d5f8036db160c3c87a7a352931aa88b13c85399cc8c03ba15291affad2d79120a6b9b9d18fad2fb0e87b06c62268a22b1f34bf9139650512dee86507d08dc3f1593d9aa1008a11d9c1a79a84ebf98c38a8e67444f1d14999a32eb58a3657fa629955dc0bd57806e1b78b1c3b9592790631372cf9a41c63e4702a7dbb40a59ee48a0522164187179165e3122d68d5d8595b10029f67dc5839dfe15e5b93de27af68d46b14266b35ad6b8658cab832d6dea5efce7fd8a3e878336089c31f46712abc0cc5399702b3c10722ee3706a91e92a23322da436e9ccd0af60be08b3eefbdaf50ab2de3085104c5f0253bf18c75721d3c473131b64489f5d2e6a435310fe877c5e278f6b6cefa3ef195f17c3262fa10731ef8f6d5d85d66229210b7e3e39b708b4173ebde148c1d2c6d263e9604c5c1a05a25e0f5bd6b455285fa8c70dcee4079057a15c64aa803461508d255ffd97397700958347818ce07bbf277e8aa0bd32f24245ecd394a6333006698c66e25e40f86ef4e0569114efc0a5107283891b90a667decf2eb935c2b91390513b2a0ed242c34b0ab075e43b750207c957a7d11509ed0139679b6f452a4ec16debc94a228863d184a83f966918b9bcd395f51426c110a1523f37646e33305ed25a812fb8a9a20d145f86f5fff720336e0207803ca61345b94ed282f324dd27610698493d816050c10163721e269f842d107b794636e806799a6e8a9c78980a7768fed54a145f6e9256c691ac114d2c45ab3dbbf30ddee9741821abe6ae6a0d906f305939645698fdad4783fc390035953c818595524b6a4ed19ee2897b1338467508bcb6b81a0468262bb698571c06d1a104da77726515d977504c86c875001cca6f8cb394c82ac1480b03879f591881ab8ea65849662791f9122e0ea626825a8696de4a594ac72b940da366c3bc5488630fd56234faa15d6ce57f13756a6987a63a98e342d5bde3bfbe845eff28bfed58f3528b235c7732026f7aa7b5c717f3e25b98064182866bff501f4d5796b0f2f235a73713d822a3c2048947b010d30f7d2aa40dc57c4cd06d1a8cea90d694a1bec5d0fca3ee54cd78a5c992b03e5370c02c8fc32e99a4f7ae108c58c65a09976b2231062ab9b9c7d45a336aaf69889b96c15e9e6620deaa2b5a08555b58b3862f8eab2464ed822e3832c88e2893740642d6114149adfbbba1d5cfe535a4f05ce1253534f33a74f81f50faa9d130b30441c2e232d7186690a465aa22d50e05e8519c73a7cfc8ae4e362b259176cb6d7a3cf133fb2bd7c5649570c772c7b0b3ae6ce3a8c97097b635dc8f03e5fafba5352c7a6ae0754a86c62404f188eba2a2034b3791e40843be988995f9345978f3b4c44b2f10e11e028baedcf66053272a2f02496c1546446c6156f9d8871a39efd5c2a81fa487f70917ad51b4c659ef7dea315554a809ab6a4e3d0957636cd57496fea8dc1dd144482a60700720a422f6f516f2e190e256c3d7fe4194c302ef29823f2139cb623e70ff84a2c0bb0e4b904cee1c247c43c154a439427070932cb1a91dfd11e3490c570cc7237bb39fd6ca870a3ddb93f559255d84e459eb5f6f40bee22f62ebebe8388d2be3939946785e6d571103b8f2ae243d6ae707e814f1db3f9769e5095a7c328421cf541262e73bb61a061bcf9b2521ac558eab97065d18dcc2c0ccddccb8135fea5fb730e5e1b76db2c8bfa6a72b9e62c5e0897aac33acda84e48220fbf71bc0b53dd0a1c55a7b49f66c5cb9e1b0427eb09e6efb48d77ab2cec0044bcfbcbd11089fae49c9d4bb1d9f25852d3216fd3f26e437fef0ed9365e3adde23c3390f0ca44419dd7b6ff1c0a4f94edc8f889a85cc1d7240e882313ef6fc0b3ef29c1449900c41362e5ea25a01e19a461ff69142b4c72de7e4084653548f2aaad7b8f56b0908c6ff67948735cc7c077b3b8ce6358b48e1fd0416aab6bc59b3d01efb4bd5ad3f661eac3846a5afac1e750f1c96a44ecf480952d92734f274cc80306dc3a36e6c76c5b3af57727a7206a17c1a82697414bfde08eb95b0ed07eaaf73eaa22b631952b6b68cfd655c781bfc287c001dbded128f53845ee34a6c95091efc529160195f8745caff43e857ded195e5b5d3174007a5832baf0e9c328a0a519af13c0c00da3cf86c4727b5eab420d9d3d481f61d5cc09365f2456362370825f98cfe315179ec6fc4796a2464703982fed6d124bb080aa03350a3e24c5145f437c532b14abe12115001dd4bd9d4ceec20c1707850d4025acb6980c1a9a53803db4fbcae13e13927a2e2902090aed8e620c1cc748313e8b23e718ac555f2be3dd86c4036334c8efe73e8c0b79ecfd5ff15621bf7a7294ffbfa981ca76a11df5d781c6b102f8a8c33f16a922d700361e4334314d880dc8e742ee68c7a8ef16c4bc222fc0ce2fb0e8ab2f444a84a483314049565ae4797fe2457bb11c483313e4ac5f72dae8873c5876e2e8059479d910c70261720cc09a44a9b7ffbfc55ec010c4785e3b67080f280f997fd66dd30a9afd6294c262b7d6a10b95eb2a3343e062b3eb1b8b7760be4294472b2168ad49138c0723568a4ac2e823a08d082d9d741725864091a7319088604ab0de0ce78672579eb583a1a59cb487ea045067b5431ffd804bb7e4cc85541330a22ac634e528a0fd2fd14ac623ff528999b794688904c738e504efe2291e0ef349b64aa6d0ca28110883a91bc6ea7af310bd65be49cce4aa96d8c42c0cc1050f2470ed08927794151e744d860effd2ebae0812c8e5cbd3ef5500aaea402fd68607b4ea4f0fe68309b69ef7a66b75b190801b50c823867e7c389d17656a593066bfe083269e0978f4352dfe8b7f6fb2b3c250d9008d901cd14c3181e88af87e9cf776189b81078bc157e878242977ed4b84402d21736a4715622516d988e28f820d392aa07c129907e2f1090673d0a25f5d744fe85868ea0fddbe6c2a5686bce6f4c19dc30d90dfb2ec9e3a038a1c6deef61cd22a2f655b8773d187a4b5121af14035a00489df081ce7d1a3aae029714e95a5b1d9bc44ec7bd45e211f75a58ce0566f49961d4fce0427fa6b180de23608a8bedfc89a167bf50632bfd6a5186ac4fd551cdb074c8722722d776772f840ab88bf9a9be982791123ae753227100de4bb6682b5beb2427ec43aa7d1ad9ed7ba8e6d8acbfca9a73e3fd890fd33099e2ba9cea5bf679d2fe186213cd7a82d091ebb4eb0d3ffaa50a122027015c1481553a93a7e3015029c8123d389b814f614dcc1e7b8b5737ad746e82b2e156943b69052c54c607cc8e15092287a13dd5325a994ed88beb61eb71aa2168e3882c6d7521ad3adb2d906636e38ad1e22733b1362eab4e72c2898a3d0231f5731befd0697645e6bbd275782fbb0b1d81c9aefa01efb4367ae5c1d1b39fd65f106b47d3d6d4879ea90026d17aebda2613722f3fecc0185910103d5c4d79653bba188263937d615c0b508c0eca66dc1f354a9f3536403a875f1b9856823a73bef3197dcd4028f697a88f7bc31bdb84dc74165a2b4555c4d2feec549d73c1bd342c4664042bcad06842a86db9f895bb71ac95be506e4b0c9b990a900f5c5e9f0c4c03cdcc46ed755bcf691fe9c806bc420fb97380c4509d3ecece013e641e50dfb82251853ffcafaa9bb38e3497aaef6963e3aa4704bc65c12b6e2c955910c72d56698e8d0bf3f7b769c21f1689ce832f85963321b12383efd5c8b4b8a93f1cbd8fb7347a859a80ac27abdf05a710a4abeadf0b2ebfa2cfa3b27b55163678f4126eddee808d689792fecbde5164d48f1dcfb2ad736ad861338689b0bae2b2e0c1e5d1031606da77246f0753334177667de63aec199e86483883a1934d5d835925b8f11dc1015b657bf490838e78da163c4060ba62a3c5b21fdc4440edfaef56c905aabdf4a862e22f83df5bd8c050849df6db0c41dc88eff9ee4231ece26f6ff1adbb08a4197289b56203bf2ca30a8fbf38d01d9b5b70435f331844268c8754a388545f93dd1121c9e1efa245bdac4db358c22b9413c90de54b694990f2f23786a6d03042521c3ccc147fa3be6059c7ada049728a14cec7832fa1cfa4ec0780e9900a4aa782a4a07ec54f451377ae037711df04f8ba1905be09eeed2fc1583bb297011f458c5884f5e62dc92408f39c123eb27937de760c31aee60d7602cccecedc0b1c85f84c92e6211fcb45c97b61564c553252e7315eae820887b7e4f18c418a90e2599a0a180f26a13725a05a431e6976589bb09a9f32eca189a366a2a9266a1be686a3c0c386fdd59872e86904fbdb6004176152d2090b6fd49ca44467b28299805966358ad803432809c26ce0d1d4be382ae89ceb4f0ea58d82fc695f2e12a72397b5df37aba50adcc5f901c43fa8ecfd5652e934c02ebd338ac997a6d09664bed7b815c77ce96a342edbc33b0749ef8aac338de890e0dbc3e6d2b7134bfe1b0de9cbba6401a31a3faa612903a5c41f05ce0c300d48ec835dae8a564a5561821ffc56a4e1f56adb7ce9f8f38aff59463fc4989052181148507038726ccf382a8d9bf41e0bd332d7d7c325324f2c06776aad7872988c7db0de5d69ee3ad5a4a90a3ee6ad7a43c7534a73c93a7711b1ed1bd61b5a36a75a6d1fce5a7c1c90d9cd327b336b786cff0c61bddf146b234660db956699ced5f258cb60b7088d34af66610e9daca67e51083c91385f8b1d977065cb7b557f24d05a2859f665e23c25493aa19dee0f5f35f1a9011fae21808b116ddc15145ff1e6b5b7b70d1a19ca149b8c975009fab77e51704a32106cae0782d335188b61d1be98e73cdde16d87db1d819d745cf2cd8b0b2e3268f5aaf4ea2c7438df2a2ff25d93d2257f0960f96b2eeb96ddab1a06c52eacc907a8c5ff29134981b4fca4c1ac851b44d2df0e0bb7ccf390b531020ede3f3bc550d2a80f1b12d407ddce18879b50e950bf14695ffb322e3e3abad72d67613e52deefee0ce025516422ef8f50075182c72b4aa99115f86e1702a5ee42e15298e24849fb0d88bf9b7453f408135610bf200906c3200ad24282a19046253a2d0132d4cae1ca53d9953868cd79f87d07dff9cf3216c17de5c8b1c2f892d1e96b95bb0f7e7dc12709f9957590a5fa6f03dfc47db37744e98914854b1c6ef01132e601bf1d04d6f4d56f8e4360845d3f27424bbb7e75c4d46beada36e0d2b56facd436f0660d5ea6b523f1a0176cac9dcb2f8139ca77fe0031696466a6358d2950591da9dab21a2fee4cc38efa4c4570cd9cfdf4603161d3178cbd07d1e29822d5cf7f89f4902a598350e6e66c0d027d1a2c6c9db9292f827afa1d846adf6fa08f637ab1b70842bc265a4d4b894793e1f0ac7469648bad456fbb631974ce13931e4d08a19d69923a46082d54edd3d8d2455c2f7190d6bd96971f9c412c46153a0d7412b1f35760d4eaf480721e732a408f722b048f07c6b14399141f3492a516e5452d402b66955c8ea827400872dd168ed00a561b82e1fd6491e3558e948063e94f13ab65235db110da197c9a9a6a9ec271fdb8126ebdbe36041bbee664f411425d1b047524d697bc7c970795fa2e2ba8efe5043f77addb1856b2e4621c178696ff4e736ee8aa87592a0747a55cfe574388737d7100a589030391b4d08aa03fb419084bdb50601186cafa924e8ae958dd4b61aaa481a7f73d911c72a481d255d0ba1e1469a6c844f4403c5f7518095522d2ed72b8c3fdc55bc24b907b4da5a87fad5bfe18ab49fcfe362984194124c45cbb68a00bc8d6aae4225d8345865be100a91791b2087e54de7474b7238a83e36781c671a917dd91c81134760475f3c533ae4adeefc20dbf8ed9a54a80048ef2b930c0a634be2f4a81b8cb2e3a0db3b733c9978248f5c16d226039ff9e903a49cff01c4276f8c65589d085a6031b69e30a9144e9c0d8206dc548cb691726fc65deb726574cece81dfd58c30c8bae9d51b178fd164845d583aebf1f12bbab64297a32112ede275df69b78f91687a9ef3299604cf2b3a01a365cb4bcd74193dcfed438cf488d9b90491c33bcd60077126f1f6a5c3b05715baad7f07a1a774bead622251c08f17ee4701926e14227a1d07f25956d2f979934099b2926155d7efb7ce3f0a9ea0e5b386a8dfbb9f7b8407db6d6e8628f3eb135789d3bb375485df72b3e041ae70ae1ee84bee0a721b79466ec4e75d5d640f65d0638d7a144c9e52d6e0d595ce7f8ddf8f84ff15ecf38d9ecb08a1a53038b54cded2b4be81c889bc8bdd930a032a1abe95ee82fe239975376ac7543131978c6ab4e9e81571a876395338234ec4674b482f586162a5be8ad00f92d3f8cd71bd14c51e2a8aa9d177296f19f30c7f0a760a2bb722ee80b3fc8513a5596111a0f0ca389ce53b25ac1a9acb3b572d3fbb70838b5dc9cd87dbb3853072d208ce5700a9a743f673b0812fcb046ccd50f6432cb4d9d04804bddff1f8e9d7e2c9ad43eaec1205c0d685395fb810302a1eb2ed129fd0792b63a4ca97220f8993c05e6a06aa2846790635640fc1b5aa187b14bd382c4d91d64173c5b647089393be9b1e213fb4a020ccbbc2f7c050295c75f82de0896306b4ee33c1a9f1659507580a53580aed1fcbeb8939b80eb8b195008b4791111c65eb6ad0834ad139c8e034dd64c04c43ad81ce94eb80e27f57551bdef62d50a936c9aca2ec720887221c28eede9b268f039a5a319fa68725dc04486d85f90571923883a935fec4a711d703206d50ed76d6e6caf25d1dbacdaee8b1045971e66184227aeb0d197473a44b437779479798a201568606b520f513463a43356eb43a81e52b82c2bb7f7688b529047de4806aba87342e21be9edc5b73c64f7f5eae95da8ee315047010de3993152a55e377f920474656ab0b7180e4a2443f6a65b656ea8882f4ea77f0472b32db3264589e434b791aa99cd5f5e4780a0fe8fbbb24feed7d42e7ba49a1bf6d1e94cde8308826a9031b82d2be9135206125afb85d44b2100954050d9028c1285da359ea4d53ab43490a0121159ddd3d4d3940d632524a6fbf3956620f237f2448532a4018ca370b3f40aa816e10844ce5913f80d155a08c18080d6324086df0903e0784046dd4334980fae58276919ae2b6906250a4f9d07e312531a14d0abb572f4342d6edd418c3e82856c077fa981e7abee2df0301dfd4cd931196ebc68a69ace35d34f5fa0a969a5ca0d36c5449a240aa04c4f99a0ab00e321149f596a18b7df80323a33cbd97112d73f9b9d5960498f96fbd92d648179db6976937287917ac1138585de8c0ff8024269f51ecdec1202c4b93b100eaed543796617b83cc5d6619e0d552c2f2cfcf348fa3c2ea0c547c6f3be2e08d2dfe29fa335e6d2eb9622469c268660a6af17c0cd338b14c37a6ba3de0365f4ac13608b7d22ca4a937e1939e7ed5289d87766a9c6f48e910f0a7dde22a81cd19fe70cc7b8335f87d2ed98a74eee575119fc937d00b1c3056980dbbc26efb4ffff10930121e9d4e006607e92d9ea8cf82aa4edf3184e35be645e7c29dfb28885caa404daabb38c8ecddd26c61544c34944188f63066120cd01c70ad3b3170f0bc0079ecf6a615b0efe0fbd2c1098a4b2a8a3d88930bd5cd395c525fffe069dfc1722e3e7f63b92b8205f47cac3fd6ff5762b8caa3dfc7a6251fcc8e36aa9747d040ef2df0cb44b8dd96ca77d2e4eb071e2c653161de2c5684cab72d156bec59aaa1c315516d2e41c1832eb21067d129a5fb1cab3cbb951f05d0b2b7877315a799a2ccbd3742c5267e1a0b14057b5a2a7ff8a84f06679f08ffe70f91604f2a0272312572ccbf4e5ecc06e5ed4243962ed6a37ef0987bf0a59021156faa06464085a213f2f3e1fe0fc418257419918154b0a047e31b8bfe8f09cf61f2cc8bd9c143ce82430779963bde423dab1fbab10a9dfc05c0747ae671832bb96626c194e4c632acbcb2bd476dad822c55a02285c0006752675c0acfa6821bb3b208ea29073a60c975a9a9c1994eda4b0009938157184aee24205c3789f90d2ad9b306a08948d260a81469478a182cb184d426a4f0ffb6819e1bfbf71b88bdf049b423739411049e23b4ef9f4030c0a2a139e61121270b9671d1c56765fb74c94f300f91556429c640c1f29af0c363e7a309db79e272a3c6f14c75151c5c02adee39d2aa70cc03ce7ad746fb3a5bd00a86d7a0b7ae8122a542550ee006a2c776a0dad40f8d8eb8acaf285762319012604a61a26ec0321d1362634b3374a31adc5163676a0e85cbe91d8af663a68dfc7b0352dad2244f9f5837e4b27ee6a9764ab94228d9090b1003f837c012559edb9d8316ef4a6b5cdf9c230d43f2d057ff2e4c49336a6b026d8bf5b16f794751d30bcca622cbe6ee90b541afaa6c680ede7b63a068621162c6b6c22bcb16087a3801d4320428a6c917d79a92fb1eedeaca9345a1abd63c5d2101eb87c27c5019ddc57508108da3458c8bd748464b81a90ebe5c0023a0c085645aea88262a50c7520350ff9b1d8ccc442d814a4dce6fdd9458ac56ec5297762b76b6c58daa2cb427b8c0b79e5b696242cbac36f7c07c77419df26f83fb5c114374946d7797fc95c3cc1baa8ba0906819b8e538f32764f6032ba09261929d029d3d2763ae9f68c83edd50ccf0c4cca830ff2137b0b53c4280c16e7d98f48cc4d1ce9c2cd996a9fd3be7e262075fb5f021cbc8db4ba342134de8fe5028980b5075ac4958195bf95236e3124eeea9ab7738e93f2d68a6aebaeba78b943b1594f9d6f357f1683d32c0ddb0f51680bf5240638cd728f589db4ff0ca314219fb00509917d37075a3e076b7d133c03399839351c1c1c75a8840521c5fc04ee62281882304e0d58c88b7c205dd817abac678825bd7fdd62a3a2684532695ddbe8a5344802d308f344ed5e090c09de5c3930a6cd1cd014f8cc4ee78e9dd23fbe9d11ac2d49c7aeb1a758f38e8e0ebe1844d79abb73c03e876a2d31a2eef1a62c9c2779028a21a7178d2a85fa3623429187534dda35fd89630ee559d1f2bf8b5bde64a23292c65c3b53794582d6394b8342f93fd88934600456a127ce2ce2efd8b6fda8568059343c4e0ad60173a5617196130b89741a0f50311a6dc158e1915bf60e213d24deeec95572ac72b9317e7c12ec01e30a8b9969b2cc5c2491e314b42e5ce3783dce7792a0e1e875b9cd5398d00e44a0c7611e7c25c49bdb5c54665a1793c9d2365ef9e547c503a38347eb59647a7bcfbe8d55af84a38e065cb90d82be2899cdac68c752e2f859efe17afde6c7c20cff6884cb62f08a63cdffc1fd61043444f0fe70a7c00287e638b9327463869dcce083b66384365acb621831ead9753759895241deae9f67948df50082ee02097d4aeb7a1eb790178e2cf98fc33a6c8bba1a9acd16a63155b219d8a6a7c11d613879fded8e73d4d29d80035b91f9503ce17702d9cd77ba1abfef941684abdce170bcf50e545a8c1e325de55e98b15d946f53602c72087a4dc6ccc187ae0afa97c9f4e8d370cfbbdd4bee0402b4a05e2308228a5d2446f24c5a4a7a1699ebc09a2ef8d26ace1f87db139b00311b7c98e3347f3f7f17db0ff64f01f79cb0df4cbc48a69c19802678d0fea7af7a292a1284d2792b8a6032630dfb0c163608398f8267d39dd2824d23449c930089ca939cc2083db71b21f3fb1f33341a6d3583452b91b9e92a08d9acdcd3e996704b2a5f2a1271c05d64de7ceb78fb57ac061732b488095564ca64490fafc2bcaf68e2f49296ca3d1abde1061ea7112ad1cbfc4535e6f69d162735d7358b8745a9d0e5dd666491396184fe85cc4998f1cd903c4156a8dac0d19dc9c30ae8e9d429a61b1d1b4ce1364ffbc8786a672af24d80b72ed363d23782dcd0a6ae6987b32ade920ca976947437ced29caa7c6f0d63d74bc39d801348e221d3951229fc56c79069aacddd370e8738eb17607cb6f03cc474b49c0ab7079837e81c329e44b41865eaecf21a6eae9b261cf15aa0572c4f2683bc0fa0212d2979e8b9acee9ef22d6dcebdc3a09e78d82becda90418eaa707069b726256e164fba4a4a4b48d30c81f3953efa0ecbf7fd81dafc5094cecfd1aef8aa922fbb6a16c896fd15a1ee3bc0e31209036fc9c36e1bd2891e75001a76b4022d3bccf8c4b4c4523fa1d1f133022ed60d4ca37102ad848dceea9d5634c2353e4e3eb19aff13c2d63fd93579708030c0fa2922d08a03026328d4c559c1041a44cb334a3490a8f32b4474a129ab30b987cced8bfd32319e258012baede06234ac8bf5a206e9efff4372990133acff1940942da26eddbe4591696b3c3dce18df8d50ec0f5b4d35f96067cc225aaef84c49fb443b6b9067d390bb6f7d8275eddbc6092ac93983276c8f328a55530c14e371de6ba72cbde4cbcbfde35a07950f8fcd770ba4d805e4d3127a63ec193114668cb0c91c1bc5bec72c1860b34255780c66415627f4ae980c3784c8d13dc54bd6e2b2b76a1ce84d3132301b676af4275b3246ccd43895e8215fa756e1640a4aa3970cc54d404e391df4c0df7a1007cde18d5f9abbfd9a3aecb307e408317fee1f90d507f2151464ee3371717305e90fbfabc9b3a764c527a80d0c2435306a5916fa070dce34d9931bb27aa78888a2c2823ee59451909dc8b7e4305b8b2b78ec705667b07a3a9a7a2b136ce41b7e8354b74af61be25c4300a1854aad0e2163f603d1d8ed81d3ce7c277b6db832d236224868fb0fe670013bdf62b23b331bc2bc7731ce0aaa4454e3c9eafee87ff8a98add86913ea16080e37b98262fee06e91c8065830224ac0e9c4985c49060f1fa87a1653c34e52436c09ea54560534611a2e61339dec82b410543cd2ca386d0f5705164aafddcf765e2912c0bbbea40cd27e2e76331387d1fe7fa2c73961ca5b7bc70d4cae4c43de7429d5fd19f4751c875967fcc26a078e69a0f5f21a64a67993e0166b3ec9a9f7989edb7c2974a21536c6903301481bad1b032efc7b786da4aa21cd678796a3676bc92f5e4404bc43ff73b40cae8f278cc5d6c849688b67c6488a6910398df11aa80083b46c4587ab977b2ee8299d170529f5cd253af904267b5539383c35551d88e06e0123d335a691f1baa6d53641ac5868b665d38d230eb39f89d40abf306a07bbe2ec74346f817458106cf8e593c0cd3a9d1e6d7a681e67a6b74faccbb749de2e4b3ea439bf0d67a7252fc79ee9c7b6b1d6b91dd438bec690b2ec9b7e879274cc0c585478d18938bb48569296e62cbc55ab7b7e4c961e68a869ba62a0da38d6ac2843c40be15d0475f17233e44fac319a7832a16de387d8efcc548cf07a704012b826ac2dbddc43108666de60f935b5836f9dce1d118a3f52322c4ec111578db5f8319d671a1297b607f10176f16f9d2e7655d6e217aa83b401ce4a1e2206b38124f1c4686d3f9e07693c054aa79644cbfc3283e516b2faea1e56e4e10d7d57aebf0768af421959d7664066a9ebab0f1b89fbf0ed4b0bc8e8565ccab056df84b9332747d5ab2a1c62fc467d3473f0a52da451b64f2385bb66199e2a8dc00280fd4c7cb956d9ff36facbe374ab76812691054d268a0e12813d3676f5ccb2217c49592654951a1631433492fe4064350d2425022a949bb5ce5f97ced883ce63e46185329fcc886d9c85da35bd0792558487095211e3ff4e1b4a0b3bc05501dc9bbe4114a4480a457da012ef69fcfc381473f56a50d0f9589644eacce6352a0c3d3edf3400f0a1129aed098a2a66078451f7cfda869abfa72f4800196a75a8a8cbd5b5b270b008d1840d5f5bd152dc92432f2892d3840cf134e8bbe64b819d6c3dee11423ff68a05506d707b215176d8ce8cf1c6920e4837f7a814ab523703e3510b37089eaa764b443e769d58dfb9e92fa27314f492fb6328b1b5de241c531906c64410e75031f304744bf7bb75e91f782bc00f11edbc22dd06cea927e11da89770eb7e98c6a65b5726b2c7c50902336d8675e65e4ac2613a3d5924b102601e3e32a9d24b8c4ea00e6f46eceb50fef3d204d253f35a3c5d9a40e854c8f4c3ea7bbabb9d388f397acf26ece5243627a4315d9019b35832528591bf2b29f5f4dd250785d52da6059e64e127590fd68739104c5cc227e065abc3b09f3c26071516fe64d81d8d78ea9e5844b2b2e1df198062be120ed26ba39506bcb406b4103855a0e483bda8ae858123e6eb76cea6213cf6f2b2de49b24cf32cb5f4452dc79eaa48c2874192927d52cedabc2a54f373187cb35cc3d8960d289a7c4afea636edd9dfa904138e522d7fc3e490b3406f8074b9f85b455404462712704d7bc1ccc678bba1bab425612663ba4060441703863e5c6841dfb8ea653b76c4fb73b20778e726f9f7293cc1bd27a7e216e7fc03b2779050952331a99377f42b163e1fdc44fff845d62ecd47f2c39130f4bfdb72cebd948b05c168817de28fa8fe46165df1166522c8007a6ad72a95c6e8be6f619118f4c6b874d0ee3d1c006930da934697f11c6f3c9accb00fec80511eb5038a0507c0e954cb2f53d3b1d9cc9d42b7896e8b194a765d1a3d7bd67fd637dc49c50c860379dd322d750e409eadb82470664062faef63fd993f2c3cc948b1951b81e39b8e4e6cfcff8ef6bb0c0a24087f749f118bf0a83033893a10a7c348f819e684db23f43085800a7bb2181f18555e26b2bfc65993f55f69afc457a87a4bc9f9cfca85b37e0800b0b33983798b5c9410c0b44f6eb720a86695e0bc7e035ecba9347646b05b67dbd8559e9b90d0d0b04fc047d4d2f81af9dc0852b1d0ec1e9fa1106f932fb40f3535c9126432561ce5a134280aeb01ef7eb7ce7e300cc6cb7f4681ff6a425495100b4d989ca2a5350df1bf78256d571925ec7fdbd31a5a9279b2e7604ab5625091b5c72d2f7116d54d81f9ef5a6ab67ea891b39478f086eee6af5a0fa51d5688ad1063d739c149817bba1eb5af3ec6d541040366df555d16d9a1191c9689adef50f7dd6ed61eb5c5320946386d8ec43d2f4211a63f60950ac544f86a140bdaea384b6bb0a84729548ecec2b30ba2b7c1e58a6e69985373f3403b13586099a61c361409d0c8973b2e77f0ac3432d29f93b40db701d5a418a3fd4e5139f1ddc5bac72c3286a1e491b14702035433535bddde623ecdc0bcbcc095e82d43c6d1f4f4fabd71f31f3f57af7a0ed40c2c26b8de036f402c89c353989266aa44693dd9f3ac1e55febe405ba1eea7d7e66b9891aba3baa93e7e86b49295deca8c35f7e2ae21e50cf345ef322b08ec098b98a8e3de2d56f35bda8e61dadcdecdd70067105becd685cbb94658f302c12cb43278718952d245aa6226073c9a189889b6ddc7c372baedb298f08f3f3c25365986be05bd66fedab19f6ffb004c62c61258f08c2a6d7540646456db177aa05f1eabc68becf1a250ddce8f0d9f7614226e32e6a8b345248279f407b6f29f6a014744bdebee09a0a340464110569c36730abe1b7d7ffe2320424b1b8fa3a5e1521ba3effa008ba0a119ec007678357dd40e11531c7d55e78899be66fe1e1c7e0c37a30e7e6b100cfc70aef722617a82a29afe5aa03425565119df9e59f01c8b7fca39511cb10cc739a01a0d9776cd2612511c88e7d61267e8c71cd9276fd1a546102f1322c7cfa75501f9b0d46982ff1b6a3414f40725474cd8cc89498d049ea6f4e4b11f1ec62fa27eceb79d0ab9464bb0fc46c0660ddf3c19299aaf4039aa5c7d56780de3c411ee765c0168aa00ee2f5f92049038e0d93a7c53bf69e3f248bbe0982c61fec927041ec71597cde3a6d1dc9dc97b860e5f28e0f1ad8a233ea2329030e81b7f8d0f74299c80bab7730dc262b54ebb3c7e775adf3b4e27eeea6353f2f2c9a9e70f57f734d66689376f6baf040ee407d3024053409ce2abb827e798076251d1d72eda8f781653e9b5be618296c5b7ca6aacb38c50b11bed0fb891ae8476f7f8e96ea12376d9ff0ad6b03a381176b2b5bbce7898475551dedac931fc3ff63ceec567ec6c500ed80fe4b9214cef61fa9ce431c46b6c1fa77b7cb39d68732f0ea59faa5b3b210dd1cb6a05129fd07639a8f45440b029b056aafeb5f6abf75a9dd0820c7b169a55b63959d028763d699a6e9d8bbf3641374c1099972548bd7bc4960a32b1db6b154aaea0cc9638fc9e779a94a35583e14495814c3aeaa6cfd72f233fea7904ece4f6e4aba4ee3029f546b08ff05dafe4db7c774a3f844a28e61ef34d2370ea18b5752812e2fec2bba8303687cace6e240e27713f7b1e118185c66f384f7a3f7468a42e580d394bb9972747db4c4a85da690c06d1e39eaee043ba1dc0bf8d5505c9ed3ab08fd998f2ae06814973075785f9c371aededa3c12905f7a1adf2732f3c947e766f7b4e3e8d71a771d5a3bd20a228b2a3f5e03fbc48764e97b15895bbbbb9e6859ae8ec3e25297b78d9ac2d9a5a5bb97cab8dbad7321937e78834b71ff597b8e62f383140681ae4999e401d7383fe3778df708396352d2ceb22a52e5b3849ee42d55ed8cf3c4cf341a20a169978f3286b1720e94f6dff9109756791dca1407e1b95c37a26c2c14ce302e4f2b3139cb1513929be336a559e4efc0eb06e8bac991b71968d4c8f8ec5966e3153544ff1c8197a7e9a0ab5278e914840568feb8ec56c2790e106266c7da87f4bda31a71e6d9e04e3da051e1e55cf5e368fe770255ab976d44e65d27a2c8f65073d1bb36513bf237e587a47e330519c77c93caa360f33577cfd48e8e28c740b9ac445038bd4de8678a10eaea4f3ccab707373b494ca675af299b6f08d7cc40f1974c4084aa050eea4241e150a43535c30cb342bca1dcda02b43f85a79b35d2117f149843cb1b0ddd809c9684e0750a77a67a647a3350c018ebbc06f2b097880b2b446b0491790c562ec6800f91de0bcee536221dddc422f97979d16c78e0b230f0c117ad74b365689053a6a65505d791ded8cf0333c159b81397cedd457e5da7a707e1592a44341ecc8e2a990a9fc5b5bd7aeb3e79a139920290a7cbf2d25ce11697a72ce3dafb4c5e8a3775cd99e9052d856351f3d843ef5d0380b9acecfc6ff92a54ca7b19fe9252eacec2671ce376e4761a4ec5e58827cba4786df963da71434fd37ddc0778768ec1595341053eaab3e1fab9864b6b211458e7130e181f0406fe8c6e12257a0ca09c3c31f507860852b5d10f81f85e622d26a9bfeaa54b52551c1ac4d9a4dcf4b63b7f6285b7be91a22082f045ef9ddbdc155091c29564dfea111fc4c5cfe81cf3a027365e5ffe85ae8f92e7de1270ffbd4088abf1314c74c2abb0a2affd4b25cb3aa0652c74cd05c622c4bab665888087907f68b0a99850a36062dbee98a831730a8553f85f5790a91d12998569f5eae4f9f019082cdde3e6d7b0266ebc798a6e7df5b86100a9582a9f1f87b1352a870ab9516c87a89f96aba42b17a7a88b4a826250c4506d51f4383aaeea101bcc4bc3f8d916ea3b066c7ec61128d928c148d58ffb028722bbdaf854a7d5cbb4474a8a62cb4b851ccc274c04157864c0cbdb8437eb78049b13eaf6dfacdd816d4090ca618d921386af3534feabe97e1a6466a02b998028b63c50eb322c166055e748503accc11338109793197305ba5481f03fc9a52ac4aae8dd0a195691e23880ae992cff1dc1e4cbaee1004bbaea5c8a81f21d1054f9115b4c4806d81ec1a548d5f375314288753c35e50da67b855154b970c1038fb297a9d8ddb7b6c04a580a2b3adaf912b8aff9868dc360303f8585ad4bcda78b31235801148bb46ade5bc96a8d746346840801635e1c1c6484872c581a98b43988bd4ff475318dcf9a6c306bf8c789e6e1a3bf55e965f4960b57fb7306a211705baa7ca3c0124161ad2942bcd6d4fdd8a8f1608096cb80d3a378e5814fc4e40d59fb87358d99ffbe01271042a0b0d8a41d543c8ac7aaa846e7bbdc74b4d77014339e033eb64bfad28b5cbff2d8e32057e9d687e7b83a4fb151278bf87c97ea0eb72891d324849e43a8cb78eaf31a92c77a649d534795a6948e2f357e2c4a11e90192497d206421a2c75a9a7fc20e282344da39a7d4c0da392d95e394b89a0052bbc136bf86531658d78464da3f695d13d329e298c0d2b4db22983f7b1bf3d41eddd089e8e45ecf282344f708b6ea5b9298d1fd2c1f907e71c9817a1278f2a4d83bc2bdc388c91bb64d0baa1bcb2d44c34e82d743ed2dda345489f366f340f71e7aa61e0831907e9cb65ab303432f79c4815a413de97f63a3d31870d0df3a4827b47ab5a0690a2f901a858d57fac65ce3eaa314b6fcbb83e4ee8d88c00dd7eabd77e233e66a330c3daad1537b7974b4e780e208e3778e1ded45bb1871aab49c7e4d5bf3ed2afeb72e51fc732fe81503795c5f99d4ca65cba80dcd7586f90f32735f1f10887e2a3f6d5b2020dc26c8d293f6a8f01188912392cbf6a8816109ac3e75b467a9314052bb6e28a678e519a96370e2bb215725e5652319780b128c21c8111e9ca0e4119700d63c948cd3a05c119c64c0114ff03ea3457a25a3092324a8b0134ae13e1fb72561459a6accf8ae8f1105e11b721075c95808ee17528429008046fa1812fe0ce18617c993a2ee67a87c7345e630c16308697956e7cc491e900f29ada44fed62ffaadb5269288904d441289ecee1d0d0a3d09e10a9bead2d67530a9948ed11f0ceaa4bf54d6dfb76ddbf4d7b332795fcfcac8de6666beb76fba63d31d7b4bed2dff444d265df2366a644557db6664d373ab4dbf6ddbd6b3ea59adaa0f900f91910d6fb30274d0e265a5001db4f000060535db2dd18ed2efb42bc7e9af473bce845802777baca6c359aa1d7719ee547fa7cd6ddbed396d56439df229758b3ad519e7c7c0dce6d376ee18d59d6a5b0b236c11f86cedf56a7a3bfdb4fc2e6fbfdb6b8d18857aaa551de7a9ba6be7b6add3bfdfb84db59b394ef79cf6cdbf9a27bfd33c58f37a4e9bee39ed8c710a150373429d4edbb95feeb4719c76bf9d3b6d57e36cce1a11130bc9b676cecb5ef2be9e5d3299aec9fb4edc6947d761ed329fa64e7f399bea4dfaeb396997d3b843cc719ddebcafc76453faf2deb6edf532a6d2653cbb795ffef5ce6b21ee92a679386e9b6ed25fcf69739db6bd7ada4b5fd63d278de3ae71dbabf7f5ecfacd2381289ff3beb839ee1aa7914094edfe7a4e5fcf091f47668f80919e0d6f332c83091db4e839ed1662194ce830839a521077e29fbc0fc93e954e4f6929733aeb0fc9a6fae3be1d67defb9c5ffae52a9ff34e3cf891e67c8825e043697e29cb15d69fcfc687270fee2d1e1e7b9f8f840cc8b04808caa6d7e0d8f430f5d2dbf9f1c09cbeea4fe650a2a05ddac49c764f9dea0f75285754c3f0e4dfea5930c473686cee31616c7aed3bf7140be15ef51782bc5b5041995540e0c65a753a4fe9f8278d5fa2c799d5c3bfde579a40f7d3277ebee5d120a623d9a5478c423ed5aad355a7e37325d5490381f7a7fbd3091f7b1f12fa957e0189bbfbea4d9a842632be90ee58f3743f699e9873c791252f09dd9c4e4277a7694c9078ee11efc46828a393ccadbd9ed35f4a53944e3237cc2bcccdbfd9e33c2a5739736097e6865808dd14850fff4b32378cfe96584dc33eb2532173534ed3244f32f3e7b32337a5f8945e1341e56a36b9cfae05ee641b3f7bd9b9897d64bfa55369435fba953d74bcf45bf2bebceb04ba97b667933475f55d3d7dd5e7be791a3e8edcaec3536de7a12f7dd3f4a5534df549e7ec7163cd93fdee64f73893cb87bf28601b3ff3f221ce8721c6261916e520c5becf25cd79d5fb90a8be1dca1e3a5eaff2a0a439e9cf6e8ebb0e0f6796bc16e28679f53ed42fac797290b89381c464af5ee91dcc4d5ce9f5a5d7671acad5e9f8311e4f4953b93ac157bd9df34c87f1bae34c93f7a1b4bdde778f2fc414805b75faf6d331f79297bd0fc9ce3f79dcebb9d79bf0378f87a33bff28bd2b7970675f12da791f937d55dc79eaf3394d025156edd4e773cf9aca553dd650aeb84d7f4cf63dd40ee3ce3e9f7d7576fa0df98e7c48363cb4d2065e8b5a34fefcfc8400c94e3237de41edf81869f2a156ebb2744e7b599526511f7ab0e1445207f9fa0448afe0ec5189264dbccd71841176ec42347b7066409af834768c41d8f1f1403b725ed2b8f69721d52e301b13c955fc8ea10c238439687ee1e69c538321c41bdc4f05aa3f3bca53a26fbef04938241b120d29c3903f93e6079640230cf1fa9934f1f6b3a9efd7cf8ebff445b4e3efa6040c3be217768c55d01002da86605d3fb387b645d2c487014446101a3b4636768c55763cda5ff643f403b5a7d68ee42a5e87930c4fbd09e7f6f99cfbb98034a41d2da2c842dc449047006ac8f09f45a445273e74ce39a9e74686fff28e70d3b8c4858fd267df49b5fa43e529d0feeacf8e3f186a8e0cffd51f4ae98face1c2c319b8279487704eaf8d0c1f6f9057fe723a57054cdfdc4c9aa8834ffbbbb991dbc43a9c2a05ecf62ac09c2631581dc3a5e17d36c53a79d7182bd6e9525bac1ae48f6e5317f9b33aa7cda980c02daf03b7d43edb72c911ebc48deda9b1283168b36d1e47cacfebc8b8abfe46b0a986aa9d6cc72df537820d290dee8f38477d8c52d6cf3927a594ead45d5f6badb5621d6b4bcfb22ccbb2763d25a24448483a05837c9ad7a97a6aa9553bf3f23a53d71fedb371a40f8d9712576054ab88d4a8c5942772ea7d72326a9d661eb44112246d187d707e6a37f29753f3a9554d0ab2460507f983463713670531e80651aa00f2217f46f638499a7848638c29d041d472642e8adca920c37751f20e976635f35444323869a8a7b2f7c9819a56fdb0ae2a32afaa88ccbe24d24aebf5115bbd23b3eb2d4fb593daf5a98d2379b0577bcbfb92c88d69d54eb5ba05b979acc3221c9052dbf260110e3f1b496a6769452aef7ac94feb754ef929cf33eb65c913151d1caa229ed4c5914d91c1c8ca289de040c86270393237361bb20db42e3dc8de507d2103e58b19468eb7ae5b9752e632727cce564f6ad7ea04bcfea8944eae6c0925474a287bd00853c6c55eb58a48bd5eb5ec965611b16e2d9f1c28032b19910c9e900114488390ad3684fc595b5454c415a07cb244504cd124b5238dfca568004a0d5da1e1e7e80a0d53ac5c791274054a179f09c00bf9bb3664650bd10d70ac0ad046fe56f927890a1448402451b30020e4af06fbc1c1cb3dc2418e1b13bee4ef66c76bd7033269e23b39f207ada091c50a328660984759ae48c962050c8610e941ce3c2347264d7c1645fe60970e4c249326fe69e40f7aa94955e812589440891bf9835fd438e24aaa420d2a44550ca9c1a58a34befc3449ca5ffce16125882f80a0c491bf080406104604c60f05634ab56283a02fb604458941f428088cfdc91cd810922247fea250152b20da7eaac041cb4caa620933b8a12a9800aa228a0a86a89c91bf3854146586a8080a1dbae2091c74c51757c6a872c41bf98b55c290392ae2614b52110f6a2015e92045110f44103511896492440a22c922a2e2e40929027085fb9162fe5841e528490a1c560411b4e55552472b3b5e8956a2513c22237fd1287ab9f283263b3e26491b981d2f855248910d1fc081b46304929f0c9252768cf174cad8858cd845da448b884a035c5167adb1c63967aa099eb0d65aeb4ccd14bc200f1176d74587fca5523a409b4a456808299b62919f73ce69cd4a67a59615452833c292e1a4c1b022664dba1a0069dc9725a5ac434852638d379e847d721f61d7c46c10131cd275ddd8582ce5bfd77a5dd775d92d5df8b2af47efbb3a76c8f190a802d52994bf9b1b13e08d94211b84d015c0dd72a95c2bf789abe556b94d402b91a83a717faacc1ef70639b19f6448b333da48bb338225409d7591ab2b64e50a5da1392d2eec0f768461f1287681318cf82526c926110983f791eaefd84d07431017d062b9a34797f518643192086fa611188f4eb847474719667feed1159415cb42423ad2241e993d208c1b84237324ec981d2d21ccb38634124be86a828d2f3549ae2c2943969014992200c7d6a45b3f50019186200feb077b745942479690ad7204153f6843502841849095464d7af2420d8286b12624a40545119282b2b2b93184619462dac5b4eb255bab5e31c85ffc99bfb1b22d734e6b5a37b3f46a95b3552e3591bfabe56a912bccde2a570b0b518bb48933c80aa217b3d5ca6e66d92aadccb26a26a404113feca02a414454a1020a256c161445480afec2020c8b715e167b9131ab629366d76a580c5f92f975b720d5f24d9a07e65b8fb6376d676d4bddd36f3127d445a1e6d4baa9a1b0cfd97198a69d4b623a9536a89b6efaa52a93e99d76eae83b9d92ab9277333dca2b9d67be7b49cf779fbac326ad711b87619cb6936c5ac2999d95c160d92b6ddde93bed301e4f67c2dfbcd2515ece9edc25ef3be92bfbb2ee5a673abee99a573af5b62fc9e66ef24820ca9b76ecf1603ddace3e263be3360c35ef5ba1034968115c8ae8428b45702982cb8e9731d2d40288747ff810cd530a4b93cb3e442a4efff021f2218247f028a396e6f9cce90f1e71c799390eedee9c478ff3f175e8e0674c67c647794e1f2278e443048fa60f110c323c4ac1207f3df0081e31096282e443b4e1a195d1524613f048464b194c6c5961df5fee94f3cc0adb462badb4d27a32d53ad333d3338342a5626050cfc7b8442bf78a8574bf9fd974a31bdde846b74e43badde47df8f4fb99bd2fe7bc02973d15c618e31576fed5df0abb7ec32753cfcce99be639fdf6ccccec53a9b4c5c0a050a88ccadb763754deeeb6c2e676ec7ada55dbdf0c94ab1f94d20e08f70dcb545a42e9d38c69a667a66726afb06738ee74e2ea7742fdc4715cc984fae69d8ef2764ea6154c25dd33b342cf4ccf8c4f940d9f58e1260d1d18cddca4a1032c5038dcd296bf6f3c279dfac3e78e3331ee4ca652b7e19c655f920aa966b2a61814b439ad093f3bf62ac63f4d9a1deb2f04df2de8d8f8721ad34ffc5c3af799795f56677764e3b9659e6a3bf7ccda2f6f9cffa5b66ad340e0e6f0919d610b4766de97bdcb74925a4ba5739ae7744cbd2fd349ea2e697ac2a7187a0c83326926133665189b32fc8ba1b5d0528bb1877df3be237bb3ddd49f0f84f666cf2c81bc9d4b9d83f07438b3d6290f421afa0ce3438cf108230000002288b063874aa543878c4c2a1513030383429d4e2653a9d4753973dcb661ac69f76619b629b667a773be4ecfffea9e2f1d42eb64d29fb5e7b76b4fda79f48336f93893725e0b71678cf1e1f5be237b624d3fbdfb7c2ba6f8d3fb8e9040946f927abd2475d324d56768439c233b2138b2935008cc31c71c324240b22f47b75a856cd5bb34e773f9100bd970128a21b5d06a266873ced7a8fe5e85dce36f55ab38bd73357ec9fbae29e3d23312fceca9badf674ed5e91d8df596bd2fbf944dc79ac7f44ef368f792d09d93d06dca5a8e8139a14ea7edb4ddedb4dd4edba5d3f9d2bd3a6f7abb10eef7bad79bf4a497f1a8a4e128ed72f5bebc91eced5066ab1bcedcbc2f3b7df34c3f793b260de50a6fe73aef43b2f1abd7bdd4795f86bd6ff370e7ed601d773d57390f0fd9f0081218998fb467dbd7a35d8873cfa69bccddaba765dbd4be9ed3c974611f9829e6744b8731bdfbcd5ec5df5ebb4f2c847bf7ec7da79df32fa5dfeabb4d77dd044adf1e4b34dfa2f4f756ad06c9be51efeb4121bf76a7bf9d87bfe1cedb20fea1bdebd9d13a249beb7ed1d2b38985944e354fe99be6b9a7efbc9ed357d234ee5c0d298ce9a4bfb84d2653a6b9336dd807d66d987623dee93c8c72f547d69f4ff568ad9283f4d9bbcfb347eab9569310fc14c6b00bef60197cc59dc7a33d7bb7d59297737e7d2a25b3feb6c3979ef238ceeb39edee58fbec3a6fe3e0dee95e3ac4d533e9af077a3adcf38ea679d9314fd3dbb34eee9add76875827734c6acfe96aec11e8a914254f425c38f37b7f754cfdd1631fdc69c6cd99e96f63efeba940d9ac182b6dcf9b45a1f4a9b3f6d3a7fe4cd9e9b3e3dfcccbb2d2bcf4990822be33bbdab94f9ae1d7d9dd852f28e04fade23e9f9d7a2a15a75350e467cfbf99f7b930adacd3da33cda36515e8a35ae30c553a7d76326d25bd65dbcdac8cbb956dfa5bb277260a37e3329b33766dce8e5d8ebb3a727ec43ff48735ce827677ec71a79ed6e51414993e6b8d5dcd833dc37476b199659f94e2530f9fb6b0ddba9a673e7bc4415cf8b2bb77faeba9403e98de530f3f7b9cdcd8fb367d655f7611f84cef73c14b4191273d3ecec4be1e7bec7d4c5250e4afe763b2b343cd07c38e33b10f89971124b9f065c3db3cf7ce8c5274a502cd284556ec781f54e45ec0f63019cc32ec59ea59cc33d4b3532683c9a05027cd633f9f82c9322c068561319f8f41a1accf84f1be2ab5d0cff9d47c8c857dd8791973114e310aa94f6de98e4ffda98e854c1d637a774d5efdbddeb6957ea976bded269d38078edcbcefb43715a77f987ebfe5e3ebe42ccb32d7c18ed2b4a9c9e81deca863a83ba9c99b877802a7738738727e7b9df330f3a920d8bb578cc2867ae7a9e6b377a5926aea1fa897f4d7b34b9aa665ea656daa00701d1d97319926ea2600e81d1d97b90ed465327a11d0ee53c99ed741e94c7f3dbb9b150389d8290612f7e99df7d1639a8426f29c58c8e953f39cce691e98cf97bc8f9e36980923824e3277eaf4537f3b3455c5c41df38fc99ebfd7bbf36ef3c6ccd339efa63f3a2f9573dacf53fd25997b4e22f2c484643bbb84a97fa68d7a87429d4a9b98a36ea58dcc51d7a40dcc51cfb084d351bf57d3280fe3f9a9bf9e8d7faf56ddab76ba73d7e934e773cf5de79ebbcff638cf831dd67188633e371dbf5ab5138238c73e7dea4c7f3fb57693e6c99d1d476ad101f412f579192fa551d4ebe0014c9fa68dd2df29c6a372653a8ca7c995e9272f431ea6534a7faf4335843c34843c7c36c6d7d934d43e7bbe3bf5bef998d2218e31bd4b6dd3b992de81f93c8ce926adda397d5e04539b7ed259b5933faf63fad427b932d91d9c79ad16b1108b1dc341b09d1d66ded5c99e3f31eaa2f44ef6fc2c63cc651e76abb5e348ea6dc79918767b29e77da7cd1d4a9bed1d97371d61846f237850d2607dfaf58bfacc348f3d86d9633f502a7cd58ee9dd754c9df6c1efae83df75389bb2632fceb1212e956829d33bf8f4f876185f045867c8a3d35fcfc5d7876ae7ba536fbbde77d23da7ad13e7d83e38ade2ae53d210f2281d6b087998eec31d5f873bbecfa6fb729ec92be55355d63bdce9390de58a6aaddaa1e72e024e67c8e39eea0c79604eab76b6d3eb6c58d31f938d1d9eea9e938fa598b6d6eb31e2a34e3a4590de96cbfd7a9f917d7f6dfe571bad557f46b6fa8d7bdc4ca7ec54daeabb5a2bc7ad38985a310c2a1573fa7bb7eda87a9855bdfaeb59dd6fdbb66df7f5f7f574bbda767aebd5087dd5aad2ebb5739eaaf4eedcb67d6f25adbd7b77ed9b91cdd5bb6d9a275fd33ca86fc7919cee59edac296a7bc43b288de937fda534a530a753dd7e378fdbeaa6d5ad9ed6ca6db46ef5b4d65a6b35799f916d42fdcba71f5c8dbdc956024396104e74f082285eb0903842891886ce885fb608028d25a604d1052867eaa00738d8df6bea19359fa163d026506678e10235c901a80c0c08402778c3e80470c430c6d096a12d2a098e0883882744a4000756681b160d5931f444957b64d7df468b52ec9867310c420827c491503fc1116df800c830e79c73ce392777845c41ae8a5cc15318dc1823dc734e19f7ac357a71436913bd146a8e755d5a09931c4bc34943e7949933b82ce8401674588831859a1329a5147a50de421ed3935a09939ca8e1a499524a4129bd02094984c5a2a91d72562d6f1a638c96f73db10f59ad47a594b2c6e983cb524ae965adb5f6f2be69bdc53eec7549cd421c69299db3022591e5a3fca8b630ca5a449cd95c4a9b7bce393569f3e51dad6f0aab47f7c43e628c71462f4a2d11e9a2944e3aaf5b3cb10a89948428727c8d3546293ba8d4d66addd6d77ab82dab56ab561e8a34d5885456b960828a9c10b247452f90022315399184a4365fdeb2e54f72c7db94567ab82ba5946a242b74d041072952a440a83dc9d4aab56aa161d75a6ba5da5a6b6a012912d90ccb42828522578eb496b41eadaa772c6512657422b38324ba208496452febda0c5f4c3bc47066efbdd7db9906b67dc434b8809b9a1a2c4776ce19259dd56a8708f03419ad26d7d4cc7b8a9d66bf12c618e976358d2da0a686ca49b33a610af6c83462286f86d17da3830938379ab575095aff51086badb55a2e1ffdcc8673ce3977540fcaa4b68536f386a449e93c9dd45e36d3ea8c9f811990f9ff4070c8131802e464942d0da48b5c6990f98b26e1681541e79cb55ab43ac95020a5527d23a5bed24f092d03424b4e3aa9a5cdd8592fcb66e7ee17b0610136c7f5c89ca67275b369acddc86002ce8d091ece4d1cd9c255bda5748bef871f7cd0a489298cb8c841cd62c5942c6b6859e6a8214b0e4a63f890038c0f50d800ba41961eb02c42f480640a42147b5116c44d6b9c8fd6552d4d6dad54f26062b652a91e0657d694d7efb49087756c5b24884546c8c1b455022ad1b016270239f555cd8db59346083a212e32428ea521a4c9567260af47fdd5cbce6a59d34648e50eb05f979439ea0fbb757b1d041b3bf5be9b4d79b0cfe78a77f0ce754ccf9debf3caf1b9984439b15c0b616ad2705c8cc810dacc7f32c61919e3bc5cc1e823a5ecc185333060f5776a95d24cbb2c9dd6b10c07c19edd560cb3b49d564e41e6683a75039db5feba2e1d6115252e778b6587df306073380d6d096c84ab6a822d8bd866372fc20e0412efe49823f705a909a2a561a553ca4cd21764a1c16659015016636829b0b2f881bd020e19c0207a03cb0d7400258a264859166ee47080296958244c460683d596da4a4e9a2b564b18de6f866c5a7f63e99b9b239bd2d5ece143035fe4efc646e26820c8c9ce54cace23150548b2c000053b201114c404c5411643e02c9a684116376459489901aaf163032a8670a30c28a61003cb179bc5cf0d53b6f06001e005598b0f5738f1c14a12d4b21b16f9f0c4946300550c2186175cb0a1c40f39561c588850588aa018e9005301162eae111a2a40c550050cc8e0228320316e80654ac512040d3e44cc6089278080638a2ba28461340786050887ff079822c5cd07ad5c99e2449d628485491396700d007a1dbc3141f660927f7e7e5e98aff3d63ca4968473cacf5a35b387d573c89c9a26d120209149a171d94ee0443d819111aec18a9acf741b164d91617fafc1686054f3190ac6189719327cf5999f7e8119f9f0c4a84a7d02c6fe2ec445bfe8418e316e3802055b3871c3141c98f0c18a132092a00cc1839c69f499bbc6fe5e63fdd0830ee6849f3a4a2d238453c6d9c4674f2a3fa5256dd0d089c32d3dba2f0725dda4869047951edd3e35c6254a5c1861bdf1e43e81c5b559f350e9f94c2652462e96cfbe30c250393b7bcc43f948a9a638c80f9fafdbd6eba7273deb36080e02826dfd1292378f55e5961eccf1b1984819e730c27aa386a4141cb3a2c6b461d114217945cd0556c04697285ad0240e39640d56502c78b0c6b8cc9021c31e90b847c4b041193d0c6911522267da4088ab46dbb0a8871d5c4027cfd4390251684540ca77c3a21e6cd0f677a2790df9640419f2b56191143474f0240958e9161b92c8f5146ae1a488093852cf27127481c50dc818830d2138a15db08882f550434ee553ce4eb235673e3dd95fde3487cc064b830dd9c6d8244b065c77f21c78cd1e4269436fd56ba33de26dc397d24a69a514b392c6cb7265adb55a851deea927f681e91b4c35dd92e3219436d7fef2ecef757ddfd7758500c9ce6e3766ed2f4c7f482ecf524da58d2657d72fada2877b7e05a058da1bae8c92d21833894d4b35cb823ce0a494ded2f456cbaa17b5139359ac1966ef16ab94125b22628c90523b69e2cc8efa1963d4288c931291e40508724491618b1f6c109b54c0035388c1e5871abab061092f54907286005c71849417ae78128610b40b1520264616423120c3881fb441b9a88086941ddc30c58735ac70d952018d8827b8606963871cbca8c18e204881c30d6ac0831aa6d040837475ba9258186129e1a496619ffcd55bd57f9fa9af61502427fb0a2c3fc89c805234450beba668174941c22aeae1047bfe246db43d9faa990752dab0480a0ffbdab0680a16fb7b4d1b1908e38c278038aaf2060e5ca8f0861b2ef832842ab83042ca510f690cb10614353411832d54c8e1270b1b8040c31a66cc40b59028e0e203a436a8e0e2848838665081369468028e2f6004c9e08b960ab810148414429882872f4db2b4b046500f86a862040ebcc8428503a480448d2fae7c89430c242d5854807338d9122506416801079191126eb46028a9054ac481058b0acc1e1401a38925e628220d2f39b84205e8250c379618421c36986307b448081b90e0e1cb0fe27042d40413a221880082110f5ec470c50535354e3001855af1d9a3e78b116de4a0872c57e45053d3c4cc019458060872e04116379041143940c10314f845872b6780620920886cf0658d358e805dccb0bfd48e3fc470024b0e7280e1060d6815220862074140f1421449fcec20a154405291507470011047ec800551a061e413639472021b6b4c11c710a6c41005a542850b732c7c86159036527a32cad873ab088389a42c40a471643444d0507c62c31241354d5c01a307202d3e6872b4e309950b6393ebf6973ba4e7b1bf62d0c48e3beea2185cd959a64d90abb86335d497b49959ed902babe54af3c21e8b720beac19c5b3d2573072b30ca61727fe40f0d43394c4c40c10d5890534f3d25304b0e1378454ed548a8e27d76bc338a0cbce43039801b380441713841500e939a1750308395521281ce38ca61229372a06400b41bf401167254c9a1de9d41cef4e2a6749ed2d56ad24415ac3a3fe7e7fc6a356922a722627f5d057b6d7a799f0d55f5d655530455eb643ad996318e4b7fb9682dd5745a4be9752485514e2a836e84aa1d4a75e49e744626f7e6d3eeb4d27f2e246d55ab5dcdb56a4ddb348c4f59fbcd274cbbfa2c64d33abdb3d1ece5735bcdda2d8d7bd5aaa7da3ad5a6775c48722169d3eeca95d359f39472d0ce3d3548cb30266c3269266dd34cdadd345aab9755a00a4573dc23a7bf4d6f25aeca3e7dc3278fd35f0dda19a842912ba19df5d753836ed5ea21ae75cbdef69c6bd5b6e79e1aa4554df3b6a44dab415b4f0d1a41970d4780e442920d5a00d5201bb49061e619b49bbc2fc93675cf9fefcee92fc99efadb5e3acee4b8774fc915a749ee99cb9e6da77ad3dcd7a3691785a152f774ee976edbcdb4edb45399ced35dbb496befb467ded799368de34a5f92dd3d6214b84fad327d9e7ef354a66bffecee54260d046e153e4f76d37176d333d3352f9e9d34f2d90fd45e569d58502b6570b870e69341bb4e6bafebb2183614852c8661d9b558f40269643c638d2d8fb2136425f1a804d847fda59a5693615c029b58c34140202d224dc33eeab51a698c316669631f7190ebf619f661f5177f4c9da9858431de387a44d4014b13618c372d777308168f8e4cf808b02111d1be68a003a21622a3214062d29196e88068c651b44446d101d10cd924c34164c4492b5dee75d8c77c97ba09226b19fb98cf58476e7a9b991d1ec6a348140fe0d918841793e40a0db98a6c481a19cd904de2913e03f288445e32162f988c994a1d43d07a59d6621a86657902767a1b31cc8c20ec176a5986d9cbaa5c56903652a655afcb5e3b69e6b33cc17e7e5ed6ce69377004960034b1398fcd89659856e293e3d5cc0943832c277dc1e61893f6e5e0113c8cb7f139c6a776cc1ef127be86a80a4bc315c336b1a39568b4c414fb8b4731c63076bc518932f6cd8e611f19a684d0c6b00f6b055116fbba95001f568ffd69bb3e6057a21992d022533abbcc2e6600a68d097b1e71c69e4850b1670b74d8b30540d8f335b028891b8ee061cf39c338234ad2bed18a91ad98b804416f7aac0b31549bde6e61840b683635cd20cfc4c34f2b628a6d804d6f8b4063d35bd807cd330254dac8a7e42fbf92d796524ad951c9f143a095b94f9bce14203ad9df024c8057b4ec0f6e0963534ad928c0fe201c22ec2f36d93446a154c0d85f3c220b1ae10b9b9edee6d457454238d9f4373840860ca1824d6fe488159bbe034866b0e94bc0822f7eba0041091ac206211da4181aa28d2a55b010512922e2079b5aa9c14ace32d852fefcfcfc04514a6996df9c7e08a594c29c4902151db6a4118371e186d45a9e04aa0193e2e5e200c5f620940161c3d5c311a6c40c160f568a7d5d57ddd7755da736e8aa010d56906e908094278a805043051298e2258615f010b4430d12e021a909268638356a2abc2265c8dd0e6c646bc40e1ae4cc04005690392b461842d66880836c1243842a79dbb068873446704186d9b068873960904f1b16f1b0022684260ddc94da25b49c11b361110f6a2c619485c21e4d9c3ce77adc50833d0a40e5a9a77939ea212c7dcd9d55ca21692deb9a506e435838469656e2936364d268e9d136ad4b58ba442e4182887ca69764af9b9a11832e6b5ad6651d47da9435e59571916393ebd69b60cf8c580fc9a4a15e525da49c3509d31dd43b491aeb9727a4f2bdde959a94d7a5975c9fd7e797584b45630d8942f10920385148258f5552ab52a0bf5e64ce5f1a5a3248b952f1430e6009d5dad6758c2d2b465b3d08401ed6b12cd3f0e186e44b0a3267d3d663767f9043f896257411b1ad230ae536c405c4bab4129f1c9c49937947b0a32c1f85e25743276c0b02dbb2563527c012e2b60e015842b512259684756156b366248df51a7982bc653d4e695f883fb084a8435682ecc9cd70acec66d258433a3069ac5b982cf207bf2891369f2d81b4891f615b1600b66559d9fa080018aa305cebf75ac6964b56d5cb0de57d628c41f05e6bcedf287fab264fc84e437f79823c963d4e29ad98595b8363a4226d597520294a9ae55ac027a72665a1813de00a96b0ed28034b908154409819e4af668f3bc4e8c60bc90b523c4746e4d098c016546c2c2591e1907baf2aa2a32447865045a98c548076b7e387c0318e0a206d6051105f76e44cd8d10a348a36f19c1669436fd8f12cece44d3f876cc79173ebf190c855fcbc8ebd8975dba41ee20ac09c7a1cc969f1563796e619b935353ea746ee487346ff38a31d4f331f2ab1a17b7f37ebf28c72764dd3aa14ba0efb92fc4eabb477d84b2f615aa569c7c8d0b02e7ad11129de9b359c56db49730ff1101c59da21675f8204117afb259c56695f42b1ab344d643ba622526f5fb56ac8b6a98ae86a7feb82d0be08bdad5f829d6a153c3dcc295297d81f12bede8b61401ef7d5fba4b9a7f7a81d722ce190b35fecf651fe78ab4973bf7970d268faabd9f71a91333873354f4ca2725c38f3554a29a5358a3c06af0a3d28b3b19db1ee514a29a594424217a594526bb512263997168a32ca58d3b8f091524ae1a4796243017b54a14ac445152c3b0e51115fa3481bf9f8d1ec78ccc21252de8e325fb7a30521847456a1d9038334f01e8c431589a201e7bf6efe337df584f96f08ed963784c4b05f8ae5b15bdef4806ccebcf59a38b9c9991ac8e660f166f7224340b5f86476605a3a57d8d44a7c723e696e70260d85d922f7481b7afa0e481b95459f9a534af925988a058b051ff5432ce6c19cabd25bf457a5d691e44c4f65e9215facb7922bfa1944e5f5037aebf7d78b64b7f412ebd92fbd040922d6b32fc9510db9aeab8a5841e28e8f92888c71cf4b3cc43ec6214da684716ef3f254f3d7b117b16ef5126d7d09f6eb30a77240ae689e3414bb6e2c3b6968078ed82429291093ae105fd800ec01e111584295d963deb22ccbd22ceb9af52f12c978914d8f790cda4d8f013f46ab6a8c5655ad4a41bbe944b45a7d72ea7553758d19d93a9c34d79149637dd08b11192f32fee9d1872a054d331d7f89ea7a4c114c63b4e9f32a22a6cf9bb49d18c39eeabab07f35fbb76d08b3e161ec511337c4d7744dabb04bcab0efd4ac2777e65d5b621faa21d8f489ef956822f826ad641e3ff3ecb6715f48b0a7c28ee92130076543bea47561d7be98779dbb760c70d7b4ca5ebb4f0e0636d3b513315dd34a90b057d2dd74ab8994f24bf789d8470882c46daf02ccd17ef9b830cfda9876596c88c9543a8ec45444e8354d645e8569550a31d75e24e6da979c1ef322f83087d130e7f2eabe9aa762210441e2beae02cc81390b3eb00f81b96ac8e92a55915b18cfc88c54bc5845aeacdb98e3e348cd535da7c3bc8876945e82ba7618ed4b607e3accc1282d06e69483359369d2925e32afc17d599af56c59c79d750e685b9665a952c0d79058428fb5ea2aa27dc9bca655974ec99575eba733f2edfe609714604e13fafc63c98c446fa71919e6a886d4ab8858bff2fa8d5ab44eafab5e7703f0d83f1923b004994d8f6d6aad56e293939a34d49a1e10095383fcc12ed2269eea900123c7294fa39c3dc02c5d87e60c5cc9cf0a65cf3927016098f7f37a8c2f20c016b28e33af2f35802dbf2a14ad1db5c708c396b71eab1231462f220c5f5402f29855a85a1aeba03d8f21d52b282b9ae6b29cdd0c675996116ded8a1cb7a6e19bb9b8698cd1b87fc8470bb332eb5aaf4f608f3cabd5802d6fa1acec3c7b542b5836230752ac63b77db532ad95c18cebd56aee0be421b38e28cf7f595293c9052169cb5338c4458ba5a6e68ab53536d70bb275fa8199cd33af69f6f45fcfccb59e8d41be0eb7ac74c3d9835a212dc41d35d51f98d955ff9033dc3532dcf3baa4999527f27529e4d21fc8acc468c50865c348639458b66855101629269434a7bcec6f87bc54d775236deaae37e1caae04d8b58a50b5644165efd8dfcdaeb45ac10208750bd262a4824aeb2f6bd202a4a4cd577369f32d60d7571e9ccad8df8e5d77b0bf1dfbb3bb565ae56b7d4d01963b1948eb9c734ecbaa5ef8c460861942a88208a12d309011a43ba8246c986a8316226533000080008315002020100a878462b1683cccd234167e14800d7f9a46724e97cb834114a3288a410a114308318018038680cccc147100baa10f89f234fc6b5e17a5c9d3c451df25d19cb17c44a0c8cb2c3d7d608799a59e7acab7870af1cc7434c6449f2a908c8df6332ada62789ef5b619dd533746527f3a9a66793680790ca0ef8a2cd8ead99d4d607979d594c105870be9b6ac540b182229e02428adb26aa49ae0b59ea6bef23a3cea1d88c8fa13cfa321923250dad80b55596d7ee2f131aaf965cbb775a10d2dbf74f2915f96b9fc0aee1af51cee483a5303423ce675cc962c3f160e410635688c5182613be506f85df042c4504ea256021a5689453c96758ddb22e3dd142adfbc6cbbe5cca4dc0de5faaf6b290135159b848743fd97c4ef269a1be299b731a8acb93a8d68e7231ed4040255e08a66708c0d4d914063261f1d47e4bef09630d2b5bfcb3efd8934b56368107d22551f097dbe117de3575bba0b89b978c6ef0728994f08fc3782676922bc81e930d96db3b7d939833146de0a868773ecd286c7d2f164ab97ad7c2a1f88bab8a9da8414fe286016cd4f7df83305154c1ed6c8aa3ff182d33e844e1ee250b072ce09ed2cd51ca2af1585e140d2b88d5c98f5a0b8f68dfa8e33206e811b0e4d63f6be97ea585a07b5203448c214161fa46419caec7d73b829b8f478f4fe26234fcfa8e18a6c068fe5455574070cb2a506f106ac9e74112361038b01d6934e45bc7692e7f3ac3392c18de252250123043493b236bd35548913de19645e49b0c9bfb6643a2af7a2bda0507600e12224c808a0e4ce26e0a35e4e98b813e67b028dfe09efb4ae1eed3be03dfc16557c9d81458fd65209134f498b114ab1c1cf74e230b48edd91d64bcd128317243f91f09803f80b19134181a3afc75f14f6f85cc24dd243918e8a032cbe2787c323669bd830c64f345449b82c43a20b098e32aed6fe3cebdfbf9c6048e82d418db3227a3a1084dc83853a61160c491db40d24e50f9012214eb1898aa00c9456a98d95b1196a9e42b044bc464a53942e63e475644ee4b69755df71aab1093106c2c75915f7d614de2ba9867bbffaa3aa09dd358e81b6cb21bb42098de697b91dcc70470784a7535d3db838c0a67258ed8c90b40cfcf244ca004f5ad94376c418c69c7c33e11716890706c0388bd96ad5fcb3711389d6f9a6411f6bb884d45cc1ef4b0ad820e43d1e63ba60fede857e36e56b74af5a2259145cd899e92bfdf4a83d16e405d2c423c5b801d4f98933b96fef0fd05246df76ebe86e84b2663ee20292d459e94a33a681756040e8f81fbc47d2f50625e973ca4073aac978850f1b0e7b753dd3f9e2d32c9a0374d9a7a6e9038f99e307e6ad2a1af24b43ad477d1060d35607c2df4f68f9b4e79638c53d9b6b2b1887a2a631893396d69f4d023da4ad6b6f65f9f0bc6a28ce64e2f4da45e665264db3ec271e816457bfe18fa474af9ce288dc57ee2629cbc81c20de99fc41cb6be0ec3757febc28a6c6bc86a0aad57e2d7ffe357f4419aa6ff2b89e94f42d8f9718cd6949954c655ae649031c77153659d46929d8c1c3ed669109c2c9f4eb7f9803662a5f9470540576f6825a4057576c4fb243faf23a3ba8b8b814bb7ef650751a6e5b22605dc13880812c301c2b827ecc25c39f5571e5b2a44903381bbd6cfeffaf346a3e94dd0b83a647f8407ad623d9f38421fe3e4e0a9465a3afe9cc8c956ee0984816b09019b435953349fd33a3733a85706fcdc70905af3563b7bbe521412738f16e246c2db9ce912d20e833668819930ad0288508c3bb4356dd4d4214738c5b0ce79beb1c17af41d8c1093fc7bdad304374fdfc2355e9d28c7761432ee0ca28855bc7c1530182be5366997e2805c85e3a3959b2cf3f8f24c2efa0c7f5ea9256e2e562bd82400a720a3907e648de4e10a08f874919b7404b992278b506f6d30471cf957b4b5785a2c4295d043fc31b8da15bd54ac8cb7b11cded52dc1d55fcaaaab62c6b4dd16f979b88c87e4985bdf4d76e17390f48be7ee908672403832a3ea4ec95feb3ab51e32cd29ea8d56ed575cbe7f0d017d65a61f4e7d782346e0afdcd15fbebb5eb800bfa54e8994c5a68ce1b2ee415076738327c5fd5399fd96f5ecd460844899e09c2b32355b1d17ac9d6b7f5a44f9bf66b8e6482d6dc5f2f475641a9f4e3e91c633b6e984d6e96972a6be95c13adbadd88e6b5e1be2589430b3b08bde858a73981f97a59d7a471bf0ea75ab9d787fdf4be40c1a2d03e92673b3714f92cbd6605f1ea806374576bc64fd6424aa336184c93016be13463bb3e13bc725b5aeb7983f56ee8fe7ade29a7835e27756027d3d59d6a2104f3e92facaaa07a279522edbd2a910b0404d18d5359271da6ccc904e1d04fca8719b612bc089bd74acec490526183931d9451c18153150805fce57134425c76b240f1e094b1f1c90fd2311eaf8dd9606759a1f70392e06726231b0d530ed595fcf50b01a9224132221101662846709b411f18a7d62df648e666b3028c79519f5b62d034472677b7f91a2c45a73bf09e52e74dcaee675476c18479a369ff4a719156001ecc412634df24c622d55ed9f9f53e9ccb64da069e8178ec42a3a7c016a05b346b67fad6501c4440494db64c8cb71bdae65681ef1cebecec61998f6d31738a5cb63780e92167034d59d1c77137a7471765583f60a6d0133dfcba8016a500264ccfe6c5dc4d762d57db135fa4c501a667da8ed5c0fa0f2b2d943aee22b28afc10542890031a7d31a6a491d12af99869295a794850e4c91929169bfbb8919ad04d4dcbec7608a0f195311256dfaebb6f72447e8e51e445b31769dadd1090fb5a65fb1015f41f9e141de21c592155757bf440522d514c478a1afcdf11536dd0d9b9dabeec3c1d4dd04e9e7b96add719f0d8e138f577b1e3ed77ec8e9c8532893991b9aff5179ea75e0ef39aa2d3ab814e094e65829135527151d5686e7451c8febb286485e46a87345c88bcf98d8e5d738026ea98d43fe8c69fa40d185cc450b6cc44f7f588247c8fce4e501015d7dcbab1436c417e18f8ee1f8c282f249e910cf23ee7d1772f9f878a9e43755cda6573e21fe4280c837dae7f1f2ef9acf86ffedfca1e930e3e812161ab51efcb68ab22b70b93a8fe57bb01e67e88da5db79d68267f8c191c138b9300991c07b5df7a6551dc8644812577a2d32ba1c79eb04c131b840a020cb840dcd1488f2a6eb2f1b088859591630397f5753017f4c5776eae9f07b26c1a60e6ae278f71750de18308de9f357dbf8b1ac31f67a818a793b3579078cc2b4bd531639c7f854897e50a86c16e408b0781753d0827c3b05493cc70698212bffe5b29e3484d89b24d025bbb937b2de8fc04477ac85debf6e549e0a0c59fad351c55ef75c7e8ef8525c3f5a99e339eae6a1a27c2b3a18f2c2b7f206a7308db1d7b79fda1198a4b79b4e53c0761cbc2aa73884880c1948cc5360520f72d1f525d36fa615691703ffbc82f5acc9a4545a7cbb55989e9dc6fedbb091cfa73d163481cf6bb1b5714e40b49a18cbeca296a605d894914daab45056de0133a5ae3acdec821077b5a315d8244188c3740205664da121ba3a57089a6e2fb4a9e8797fe62f0b10555f0e92c94c4df68db6d11c49540e2cc55e38ac3fa8a222814742475e0ee78ac9cb56b88d689645a4665e286cb44a29c62203ff7a107ce94134ebd51670f49d677d3ae8e3a5ae161117739948cb455a81a07c3c08d113781b889720dc2305db37e3515b9157d418770f29722f8da68dd694826493319614431a789f3771d0e67c28cfb99e98e5cd3c22198c07bdd00f4d48c8039de33c4e587efccabdd4f11bdcbe3ce98c18706b3d98ef29c2895e2d18eda2435752ef889c9fa00411379ec80f36b0a84d5566e92b5c719d46b439860d4a00a517e2c5713449fcb9187e2e5988c812dbb95d5257ba3c5f7152ec5e31585858c4e82d2f944e3a60218bc1be25fb82a83fda22ca2b74c40257064b5bd3d3f9d5609f61c4f69224fabf0fcfd83d2fe5ae3ac661eb1443ea50923e907798be478a7d552868cb35ffa9626919526ea5bee411b4aa610067beaa2ee0e97a3abfcf98ef5a1062d5fc38325bae39d536f19585941eeb55a313a8d8b0dab2f0ed18502876bfe4f26c216e87187dd53a9e4b668c8f7451d7a0c888c8e19de94d171e59443fc54cd21aab3b4e73fc39c60ad97ca3878688d802ffdaf97d3980c8099fa30604bdd4e5acbeb9ae680a30358da257bdb7b9bcd7ddcf7582909dd39579982d4196f8e0fd0967a70c14cd17f7df4f3c56c5d5d2b33369a70c92717eb5c9dee9a2d69d9c111b570524e02ec3f7aa2963f28b23ea7d6a9dfcb6a0e69652ffc3aa0d44ffc3e75ee0cb789dab890d408b357075c16fdd2b1e266d6194b5b9bed9d4c565a2edcf2ce01baf6911aadd9be3cd3ccd9c91be662ad93c0f29e720152dd10a7bc8908082da46f0cb2f401d804d74a554e344f4eb9204a9fd0fe7adc5322678fd81382d03cfcb3d0f6453ee884ee1fa040c8d99c48b0653bad479a21b83bb73febc341948647dd2ff125b3bf92ba031e77d8b2213be1950831951b31c0192d8ae225031cfdcc51be359afb56760f2002b578e1c248233521671daecf26472aa679aecabdeb92c614b88b9b6fb6914bb63c4d45a5f9abc5067d5ce8e78a100f0d7b25642b3aeb46b70291ca2fd86e116227d936e7e60d4ef9be70e88504dfdf20135012a9f72a602f025733adc1628a402a7d4cbc08435742f816df2446dc2568aa7b4c51f36f83f59a68849d93b561aa35ac799a406e369d66baba66229e64cf532a75100c7101715c7496281b16613d16747b02c6831df5a90914e3a1747dbb382872a664997c59f0af3bcd6da080cf701bee5aff628d82ff5b8cdc492cac0d7b35a9007fa8e8bfd01a093b8f561b89499785463be683c8690013a0e3683d8f0c8106bd3219bc16dbb56110e12ed49204c6c9b4d6a4f903a6620262820ff0a44a19c5a6eb7164fa339789fc0d7e17bee90af46fd2b9d9f5f78b638cb32d48b84ae4a592708702a9e2b101c18cceab236cb8b24537f6a143a7b1730b12950a59d70258e8ed240874d5b24e2ba398960eed43ee3c9137e78c9c9fad7f8fd6b4880d0871eb438483add505bb54556833bf5b34d9ae5e5651c6acfabe32578801e85572ac07d73e276bbe12a5672e04a211b082159133b2e4851a0164d652ed47b5d7dd37250c9c26601bc852bcbdf66a2241990f68936434293787d6966f066ed58bd300b9206e8a94e8751fcb42091e3c7c4b3d56447d91ba3420e1d2a576ce5afc21f35efe49dcd8540a76d7c3a5f681ac59b7162f85c17fd0571a979ef6a0281beda6f97dcf164315c9d17426b774746c3fe649e451cf63a10c80ca498b662362a9e653f1a116e117a016fcdd49461f6f63836f9dab79c1e389aef4591a1602e8c7eebe3ef1a08e149d9ce9ec0d8778a3b1e59d368ec55482d29491b44e6d4323266e1cb52cad584a0871e0d432d63afe5a911880f8128e6bde5a8f1fa5e229baafc1a76c5948f695aa504cb834595eef0a67d34eb63f84cdc0b25975093eea163e6d0096987087881b28c6a1524727d8da006bfdb80eac56153126af5e8a7b0f3028482fcd8bf424b40cbc25d21abb7baaeda9f096600e0d2af33da52bd11697888022a01fb4e996c19b79e6f4a7db0ef74b4ce5345c0f9068daf348339c6460676a023a28cb6c9d925ff27318f6279d982f7fdeca049b8c9f3104c31a01d97edcaf3634e889955f8b1327acde3ffa3ecde6092da700955a29bb02a774cb95dc6458b30d6f0d42056cee3e1e7d538f74f71785850aa743ba80208b5483f1c8d0c09f5408b7d942dd811ac955f4efe8e265006d3e25035b0ed2020374e2b87d75ba017f7e249a19cb8dc3c320e9aaecb542e204b8a737419e685ed23598ed6bdec54bb64c2d5250038911abaee28cc296102eaa6d99f8c1899dea8c62f390318fdbfbbc8bca098b2176a02e505b6e5fb4bf789d660e7d7338425a560558f3b587c667216d3c82da68517813c25e6e7401024f9341e330071c2bef1b786d89f3546e8f7edb5019526a2617b78450eb41169b95ae1b4631df0fa59f8d1967680d059e1b15f82fb8afbe0d43d540771c6caf7eefa9a6f0222a39994e93f3ae1a8a752a8b01fd81d596a3267ed0cb33d9271abf3e73418f3dfd3d9f28b8f6334591f4af71ada3a5cce1da2d40c23b962b7887010bad51c5bbfc487dbacab6cb560203a1e68e9a63c744a8d951f19d501d7f628d93950c1039ff94cfd5e315e5e34bdc480bbe57509f9c81d4c61173cf017cca0193ebc224a06f901da69c62b78fdae4d7338a861d001fe99f24a6b186a609c93bcebca152521cc612c0ef0434f5c40424ce3a0517c2835d623451e833197471f10a115cd694d01b37054c909334252ed5bf9461639675dcf674badbcc30a0cd84b4bc0d8818ff0b569b67eb22c8bb6306e3d52136a35d3329f6eb5aae07d0381d2352d841b7d7966f2b8c816304ae3112f0fe6c792f2036385bee83a671ba3e93be28f8a8bc3a7e4ebbe46fb38a790634b665bb6e21d30fefd168bba28bd13b7dd5a1063fff4ef95f3eeb7954a05b8064891d2a860daf372af810288c5fca9ed96a4757aaeb66fa12955c0a963fc58125297c860f286241f5a90e8cc670b20b1408fdb3f12331212c9756edc9fe0680c069dd9dc9da1966fd7541b8d8445ce2a35c308589646d0733e9902e07055db53c0c9de5db050198ede41bac7e650e345ee18698ccc226d986373cc666aab86be4380e2a77319d85a4e30b4ad4ee898a4433296caecee931e834a7201b20b3a650f8bfe02c02839dec9ad281a822ed32fedeaf81896296f74677322235221d8741dbfbbb15ed9f420add80f0738566df5f708a7c6094e80dd01b6337a6313ae69892c7813ec46e7c24a7c8ef6a9c0000fb7609e750d8d17f0112a58d2c5fd01e82c59e10eb06d9520ca4a088c9f38ec2a511424318bbc51f88531c423bbc9589c87f19af3d899a03e8bed3b8f3ce2793463cc01225cbbbbbe3b762332590e0fd6aa2c6e31305a5e62a2df54454efadfe7ffa2f18f43be34106a581c0ec1cd78ca22e999d779400d466876d2b87df013b61c872247b3017002d94b05d64ed35962b20dca08ae15dd169644a73894b3bfa26b8c1b2f97574bbec781f6671a49ac8ddc7a0a993082beec2a5f831e2b3f6397a26e39c509e472bf46932efb1991266566b11d9f4b414cabaf13a92ed2ebd3512edee9041bc8d0abc31b3d12627221d6bb65b36bc04116f652703607918686f1619edbf340abc6eb63522d31ad3a8caebbdf65c3444fff43433af0790e347e61e3f9d7a1235f132fe16288831838e7cdeceb3e11e4a643a08d9f0826476dece4eec88e405b5c572e62a4fe71f20345329f68c3b69df219c09fe9b9e0f8e2ef25140609b8ddbc705ec73980c1893bf4f23281dac59c6c946039b9ca0a8df98d3a677e245721e49752d3c2b30ce751387d129422e2b7b64c7a322572b8c4681a0bcf6f7b63908a4a684c4666c79d619ffb3221269a4c39727d451db5d7893bdfe21ec87b78714120542f5313f83a208db0a15af1ac72906da8b21cd57c4319f5480290afa1c691be8acf4504a6f2004240cb84fdb34f4ffc7d5343ed4024fe5185a46c91e4d855ecc5d6164e300df803801c3ecc1de082b1d1bfedd88c20b03b8feec7dcc4b1ad45dc04bbc4847f8e553e14838962d1784712c1103cb6c39048a9acaadcbca4cb827044fd6b741287345059d4b2c9648220a149934ef5ee6dc4b270fd4e3afe38692dbf746eddcf11de113985553e49a0f822170ab0721eb837a22f27a967670cdc4a1858b9fba479e2fde65d8c99a08b9ac10aa55b2f049b08ed04135763ebc42bf05600b384144e91cd9f6df2107d1eec0d225f5d057a32ce8b1bca17920514bc4d6bc3f2eae663c0c5f5a985aa6b6a418ba35132c5e38908aee032a17e88dcb86b542238bbaab05e0a6849bccc7dd55a5bf1043f1733e7a0cd748572e466f55700313256d8ead1e2f0005edf23fe8bb317a21827e93fb239db4516184733bd59d89e549d2ac2a1e128134410fa0f4702d0bf81fad35082e4ca17aaa7bd917207b34ab781922496307f8857adab99d853a803b17f68c0c10ea837df5326d53f409553d52a39f5fa23287adc33419064a20758573024a74ffaf53bb22e07245dcd1f9986248e9a08c5ea70fe010b80ef82422bc618c59dbb389cf20682f54042fae6d4f8419c98f3a1f027db364ebfe3acaa0f3e443c8a1798623ba81a7264cf75f446d3cf53c007f7deff9821d02bd170ad99bdf0c0d57bbf00ce2634329fbaf65b17261a023289c6766d416e54a80a1e9ab40334b7d49ecf145a9c30910451ec23374ddc535c3fc64561bf69df0af5e70dc9cb67e5bb9b921d1426ff5f6ff7d0bc09b1989b9a795a375f3dc294f6c54c8fd57e313148813984f2079d9e53e362b9ca3c7095a5ee88c3eb39a70e7ecd4b3110d73e7e91acec5c4eeac72c670005f49a0c160c75322706c5b333c3ff8b92eecde823e9630ee7b3f119349eed0804b6f5551e5635d2bb7925a2b69e6d3205ea78e938fabe18ab049343bb4fd5fefc44c97657930e9af5b4bdcc866fccae9ef023e6759ef60970a715d0a3bfd81717a08ad449c2ee50760f7a207392b77ba5820736db7753918eeeb0d19172f200828148ee5d1a8590f8a26818f30d5c4b887e4ce9c47bfa4eccc9bd4dd5abca8df0629ba8bd9c5596b9abf48faa756dc93497740bc44b69189502267d5f5740e65432fe08dc157715748195bec0c489fc2469a5bf9aef2d5773da10143ec415fa5f69e7c249362b05243e673f503bd326f342b8d01138b7bb22097498f32c30f54c9a00bc5aaf06a8819cada25d89c234c40721818d3418ea52f96fe0f14ea1ca0289a06fcf64c1e9b92c96c25cb7bbe566a4c3680784fc0171f78cb2d8a08ebeec7848126bd703d12c39f640869cc59d4c83921225ad82535258305a5bc5061d49f51211a1cbbd2efe672b429bcce77d1f77b0cfc5087a6db6084e11d62c82c82fbc930157213313a1bda33d46bd6b1a6d99061201d6771a699f7fb3ae22938cb3fdcfc64514b3e17befa08b850de0c740dc700de22947bfef13082fea51ba28919ec0b8c7d0669055314d31090a88c629e1b4e6c7d8602e1fb8ef45156f95f336501c678a46de46e060da278a9b983926cdc0ab12ea0f72e20588dbdc628789b195053d6bc24e2a90c57fd39e838c4f35d142dda8d1292a02b0a453362582aad149d9cac255770e523df659eeaf54896f7b4a3754ca87827374bd86c1f17b737b6491e9f7e60e9bb31e204b75d586558df494a4d51dbb45af0030fd2ee3039f5b6ca66873e3327cc21a2afd9f387c6b1437240b271c8a25dcaab290f39135cecb69dd2e0a9a3f65f260c51cfe96319824b8660adde710c6026387d1f0d226c10be636c3fad946bba98c4aefe753867620403b4917001f2b4fff0cc478d0b4e74341e5562ba6d2651a47de90c20030a537c50c45f0ee6772a933d045e5f7d10d7ca659ac42b38dc4dbca9b771289d553a54809bfbefc9b53a60f52fc110b463b419d1641d841261950f21ee187a63bed2ce7a40c493df2d7fe73c0407fef5e3e895a720de098c59db6e14d97a53d5942178367122e5a43146c84c583d5db8500355142918fbf706790d0a390b61ced3a3b70a3b70157ca76859ac27cfa5f6cd8758df7e6cf2ac4e1b7d988316666f35b0c26bd0d48dd93ea34a490b9df2c513ba4bce3912a0b70b3405c65ad1d97b03ffc342c338b30a06b232cf28eb80763ac3d6a64c2b75bd34a72987e84c37166b125e5f1787351bc7dcbe010c350a47400763ae0699ae1a31e07fe1cc236a9c806eabf4636a827bc53838b43b9495d9a1ad3eb841c20ff3fb056445e09952e2b66473dc7a6e854b630104e9fac4bf7785b22767bc04c7655a31ef823f72b70922a9e7262f7be319364c9b3a02f06f030c88297147509b0eda19a46ceacc3c9b35085bb96156a1c9952d4e2514dab284e25d5961bc09b65ce9dbf2289bd8233aeb46b31e4aca5ce65eed72ee68fdbbf1d655495f1a55c4ee202a8695c9855d0e27d24c7e1818275b5acdae30f2df7af23dcc4f239162cc669ca2c965c1d0f7768f267aa2d592eb21e54687fb19f9fc3d9a6a4c36a5511970db2f161076ac62ba455412a833d21157022baaa0f8351df4d2d01e5705e4e76ad257706d81f0b4502f9bfc992c06382e668e7e6aa27b048982d670be175addbe1782c177b40e4af3c58e6667c5c58e16285c45612aba37a4042f3d8b8f1ba1051f00563f02b304dfd56688cdcf34d19575c25ca50adb356fb6850e8bff8636a3170aa44ac45ac5f095e1f3cf8795066be38439a53521096393efe778133d6c76e8b7891214f279a1f2fd38ee48cb7669181ed1a837c6799dfb948074a196c6856752eedace7a5082edbcf986c56dd2582917ee9f5e134e89d165a75a416c3abd22fc2b75378dfa0e0415d4357c1467dd3df66e2aa18ae4871eaab77fc6518062764375f6a3de93429ce59e290c04fb9339ba3d5bbefe308aec5d737f4d7e8820d0275f4f87c53852d5fbde31a811ca7dac1094638858ef1db2689a651e8444363e2dc8fd223a5b109d355a9cc0581259aebac593b6a5451966db142e277b325e2fc2d0f5777ba9afe387854d58e933ab755bb8b96cd5393705d31ce4232e057d41af5ab42c1b2cd2b5c378a2e63caeea392659ddf1460035f99b08d1b47db0746288cff65365c1a13f7ebff2ee1fef8efd87617d462aad06750495879b438ced6e0203976fa3479c077e60b3b7d74083d9cae65170eca3c84bb8a1f319e63a1fe4905fea6b96a9623e4f89634561c722d8fd47f51370b27b18883eff8a19ce352cdcf71c40f8eda60a2c19329f979646fcacba542a06d7cbbedb405aadc429bf4a62af4ccb2f3094aff87bf6870c2a0ace4ef18e3d1a94ff8045bebb8c9861a0a02c8f1313e75262e43eb1e0d336224423217fc3e8cfa08d9220550beeb7f05ffe2bf185982b87f63cd68ef79850e532ba7f67cdeff6e3be5e8aae67756602a58053e1e7322432323bce8076a62fb452bbd68f0f850aadc113e3aab1d2ec558ace5816f5b3ec2901b58fbc1b6f68090a997e8904f827d51fdfc12c5bd824ea82f19bf0e89fc5a6135dea9ceb07cb6e8c3b2d8dbdbda8d138b22f78ace24dd0450d8dc75049800b3303a57e51594eeb56a03b9673b801900e92ceb687a28fcc7eb1e06b35ac3a379e8e7aa9316cb3e3e86af37a433fd7a452d7a1597be7b3679112251e87d2c0547ee35e8340060d1863db24c827dc12b9697f5b99ab9d4d376cb81bb81c624427f1183e3cf2cd919f317b96e6e4114c67bd6a0636de5fd13b680482deca185e0c38cb331117a17dcf64f544a3251aec125db3ccd3f177d16411f959ef7915142f5247c6efd0d41cb5b4117943f0e0cae1d03e27ce11ec707c085cc67d05669c5d04f5273129127f27a544182a67a4f5c1f9cf9290a62d111c8cce70384bd663309888930d25c9bd1c466b8e3c91e718c242e752aaf3878aea6cfc1796cf824445dc72a8bff5ea916fe0831a7dc259da74e48b4f0a7b937146268d073efff88c16146941fe22578a3871ea3b2c8b0eff00e86f1c4347eb5bd20868d3c11d7620137027cab297ec0cbfbdc98ba8f811028c276a3628478062fea0c49cc8c18b2e925af06fc0a4c40f84b67917eb408896e96ba9b43990ca6ce1ac3a55839f44acd19b1da284d6c9d749a819d13746fbb5cb33f9b195936df5d345f9c90509956065740eb8facc7da0c13bfe7738a6ae9e59b24995d4b66b8800b1b04af99f3e1511696913ec92db68247a9a9d07b08082dc2fffdb6c18dff5288f0861b779873be24d7d5544f80ebbb2d155a987aa2378d7cb56a6c549e4bb2d080ccf84384f78ddb9baa083eade9eaa1681602aebb86adc56c46e5887fb0344705efe81a081849f3cc411e68091b1febb184d88b37cb2ad9bdd88a2b0317da26924ad1076b031f7b4a3d87b161aabb02879a7d3876fe748589fd106780d9e05c016125c64cbba498c20fc5a5373ed7e8982a3c251e8077498d10ed37391bf07c245f03aea32a2e3e162e013b8ef1f4c245d1e0cf941e16290912d9c8d3237aeba240b2d130ed4483e374ad5bbe064c405754052a9f0b4127f90277c921f672a22105cd040e6898844a39cde1c36af82541620a5bc77c83910cc58a2ea08d1840ee81d5539b2045e0d13615bc11e28406398609828de186f05969a40a32dcadf37310295a1e74cb1636bc0861b64cf9a119052243b689bcfcb424432f049d63d79ffd1b831ce697e02658a0e73b749c76ee72e92eba0bb38fdac61e90f0f4336ecf48fdbfe3f48eec508a67ad2634c9fa290ff39c2a01332c67891d33e7d7b211f6546c66e5c55cd0a659a409c73bb189fa366d9b19e637993e3aad5f1ec60bddc014266a06b2a9706038ab2b783c24b62a71480f7c516d1985e2d9d5fce36d9422ca70c0fb3342f866800b0e7a23e38ae6fabc7b74d162892746716ea9120add7e5a9c007411b98dde59080f280b3c71a0fb8cb95f3fcccc23ee30c9bfcdc3c80e8117cf86616ac22018725a07f18c56424d62866a26acc634219990f541e06b9984b5c3f78ddc93c049d27f48b0d8abe36f205c06a16265433a34a6e9ea1efe10d525c59b9518ec93b66d91c942b9441f65c11cdbdf01e45c66e6a43fb335c8d43f1b4a6f050984e100a160718526a8ab08d16482d922c03f9834f1f0762d3f8e42b466287c32d1ba7f9f97b950c4ce4a41a35ee76c14ac5f5fac2330528f17a06497d382be709b625206d52e1ee46062e5ac6bcd5945c8e37458f85ddd5900ce05459cd26ef123e636fc30b98003732c78e54b4b70b5bde2e37da60d7a2ad8c6a78313b0dc7d54e3f984eb0a17d606e6dae93b71a7bf15a50bb447243e919e07d5f28a699b638af4b0e4befc415aeb4a4a1fd3e4439529c17aeca55fd01729a574906825f43ef1561ee75f5723b212a82fc156379ead7c8b23e9ba56fde58a8b9e0179f1deedb53e107b400a23372f3339a1e5e4c7db6c27a5f00f1b39e677849a50ecd1cf326eba083f7972a58021abdeb2153d02e90d542fa09a996beb1b4bb98b8f3171d96df9db69826ed6c8ccf4256f5ebc8da0470ce598591f6d482d2a418d5aa1555aefe0990acd46412ef97b95edf8f14f56549e36f6780250ffd051ac7333dafcedc2a5db1891b053649236e8fb0fa13c90d44ecaf0c62682698f164ba57508d699343cbc24537448479ffcecabb91eb1b05c05d050bf06097b930686a5284a38e2d3c772d8ad7a95b250a9e7e919207c131b91ca953b185d687d18be38a8854dc27c3fe4763c18588a10bf5f6fb82aa586903a02471d3288305435c5e99980336be60e1f08b1c782e05a52d974424394a61a57c6ac658613ccdf96289125d427796e946e76a3a60c04843505506b6bf3968b84e89a332d165341b35947f3e0a7117aef753ca6ddd0e62f7602830391090399fabf03ccd6f5671ad2c2bcc983f35b118172ef4fd4d98cbff0119adaf668d209c5dac3e1e8dcf794cc114007db77e25da9a017f2457a1262f0c626f51d85aec750c99aed78b9c301eb8ad7ff9e7541b8746c20d5db2cb202556162fcce788fc6fa5a12d3f024f7a6f05b2a47ecb5844e0bd9e664dc70b67e1897aabe20f80c82b775f8a9e95d6d11ada40de925157b2f8733e0ba0f5c2658d268f30a378457048d87652cc48b6e486a34842cec3d3c332423d590d80d77289289c1e3c15a373a1f78df9553eaa0348237766b63158577474a0f9542a46a26ce3ce0f5a804a1c0b2c4261c11ff269e7a409280052492891159207c181034df0a8a943a177c6ddc2544144549ea7850e843e772b964cfddb250074e758f22c4d75390159bfac4f8ba4c7d55e676695254d31a46828c2bb132fef170cdd7930028a9afb3a3ff217b568517934be07353a72cfb526507fa5af8bbc92e41edec4aeb0dcb4b6c71ed7e02e398d11d13ddf001d495559df8bb5b9d241919e99bfc2a8a84df365687e57e86a91b279effa58909d078b1ad39a7c2007edb6e7de588d2851833b0045450522262fba1c4136ead1e4003c804eaf83b5634bfe5876cf39601e3b589c31b62f43a8c73b885f1003a565db70f964c6c995595692aebe3d0e7c8c8ba440826591a81909babf5952951c9bdaaa14deb0aca46715008220261dba444098767fbfb6d09b013c246c87b32ead89154e20b6241407b935435d57fb29eb505e975d31b6b6d384cc5213f55b251c3a34d4e88fae10892ae0130d3603c4125d280738cac91a26e09bf104b45f39859e63a601a9d5140ac09c6737b06461b3459ab6f23f39dc6668502d52bcf66135b192b60cdec205effa64acf7197b86062c0e8bbd10c8b1ee0f0e5a6a46e5161f5ca19beb0d260a72c2bdb4308ddbf589e1b5524ed5536a53275a6c9ea96dac9541f19ac4b79497a1758dee785849370f8edd20708987c1c18290de6e422e7cf950e3571ca7c0a4f2003ff575bf5274b6db289eb899e0170c5e39f4eaa27b277b204db6d59be3eed714b8a4822183382a194ca848f1d9aaa2a66f1e6a2baebd8e531e42737413ca557a4b0ef425ab66d0d2ead68a3a85af603735089da07991f486d4d6bb085923a81fb39e34306265d4b0fdff9e375515899bd2065404147f5a36d319342d9a4daa4fd7827ed54ea42475590185a4722c0f7cebfaffb0efe54d9f3e3c01fc073775f5195f0dc47e2935c8fa977cba749f17cd751cd86dda7b387a4c165ab8285aed3ce548e2bf2b40a192e5000a48208664d85484b267c584cac4a6dcfa94e4e1b9826dc06a921ec02da33605e36731405effc5d17f88ea0d210036b8294fe5cb38ea755bebb9076564aa0f3953081b657131465940dd4a4c7f9b839a1320aeffe2f627ee7fe1f697eee04f4a1e7430e68185ffe0304ba1fa6c01e9c52ea9acbeb9b232bfd56ab0a85b475558d4130bade0eb15afd45fe67c1963628d8dc439f9adc4ce060460693b7eed2814ede30a27377132ed8f0eaccdfcad24a0e57d42203712220ce0ff5625b3545f9beac14ea84047e6a9556ff62911e140d9fd7c4076304d520bf67e5cacb2ee949227b471682cd720ecb1942d4ad9d2e7995247e976bee3b0bdee6420ac06b7515857036c3e10abab036d847a14833674966858fac509db2c2546cefdac3be0062a2bca3dbc8206e8b0a464b7901533aece2fce0ad7c5492052871d18faa58bbeba258e408cc50bc4b39439c4768b4ba63221308eaad5283667594a479c8a049a43cc4216a61ee29aa0644c5bc282f5b456a472266743e70f2e517083b924ba831ae531811075282a277aaeed9a45b787392a3a1c2be45ce5cab6eb8aa5e8acf59c548f77700ce44ac77189cbc2ec9512b3a4ae0bb6733ccc0603be0425d02d46a33e4c626d30a1a0170a8dd83724bb0f167d829011d5e3bb129bcc814b17932104333d42423100429b3e51d0fe50baaec32e8b1931674f2ed175009728d92b9a11ba56e106312e51e0c344d905d5c41a8492361e723596bdb82a926a825802a60e3abcf51436de615b8fd8a20b8a80ab875e7ab71255c324564265c490dc1a6989d6609510ae445ac587b14ca8d430819544ad63d070c5c472b37d806b8e662d67af9ab4165606f5dbd8953a91468af5225918fb11b71546e63af3c1b8701f0f56b34ee342761dff8ece80763ef4e90db75f3598af7f841f310beee8701daf4ba02d5302022b8e7b0b3bb70e46877a410c67817a2593b0b9e2407b7f23ba253fae7f8f8e0b04dca5edc05eb04198e4d306a9f3fdaf2c0c7c6c2105023cc88cc063c20f6eade482469de186fe868a74034aaf5b23c149de51d491f7373d977251c0033a37e047aedaf323ff8a3b8599ec0a073e4cbcccad00c874c055feb0025c3af28d7d6cf232b9d023b6eb5a97275044182b5f75e058ff7e33226f21b878e14a545bdc42167d6faca889a55186e01afde367c8e83c7061ba90c00b249c71ecfba41a1a16c40e06ce61acc5c084949ae13a7b0defd521184479d76a3b194d52e8a35bc2d00e41fe448ac7e25dde549ebfa8fc88d3b0db25e0a775dd17fe4f4e311aab0e7af5df89ab1cbdec5f020c141fe1c1e8cd16e049a102228ebbb6f0841ba2611bf1c50d9e1ec830bdc5b5434bf0c95d46aa384d1482a33b5627f545aa0a64087d3dbb8465c6d3822c2868214fead9377da792244c5eb52b35adad7c2c5a8c290243f4dabff30b8d1c605c65dfc5421221c92a0ce1ca9a75319589202ed8834a4bcd004e3d2e81bf7c7a2d818d2df2d2e90e0b4f402c467a29d0218138ef452650471e49f1ece81345cff73f861155511712c6b3c38bed72d280d4c132d21fb6a085f8459069f1b71737769b0e8621e9948fc44c9237c0dce1cd39e026d3c7616462e1391e98348dffa8d495a5327fda63d7af3ce2e4fa3401a2b5d4a876b45d01e63e1afe0f5deb7df7279edc44916ce23b6a08e1e9b2e9a4ba346a95b98c2783aa895f0bd000c3bcdecce2fe253f5b75521c4ca8004f97ed65e8a25a875c0d1de041fb788b6fc5913b2977cac4871c2508150527bffa05f7ee9a918908c7c33a3ebefe144452afc53ad6de8c1667c0f6b3346ef6f1b3c16ba51d41c8db63c49a6b84f3a2ee3cc7221c3ec272c879d33a9ef8085c780c4ee91f327dadc6657f5a16b78c0b30c00cf180405b000eceb9cba2d7543f209ffc601fe3903c68c7596a7b165949b7dbe2404f498e109df2a14c86dd96a70832972dc2c26912eb9abac982be7b7146981d3a7312aee89639c28ac85074c3bd365d38cc7ad0919a1833242c3a5281cbc1854ace95a9fd10d770dffd2051c3d2f42747b9d785ff36ec4fde35c177f793014f83cfb0cf64e4e76b6b503859662a1ae2a73aad78ad25ba28e534968109204e919c82dcb06f571ba06d208b3a7c4249a141b384c624c555ebc5b12ce977dc7e6502094a3a42558745f92f1f83811be8b0ad367c1ae8597d1b2ba8e236645c03bb36ffc1a537c7c63cff8874a78c7ebd45a9973367e8b1502c8422c7aa910cf020c51e18c1de097dbbb2b2e4cbf41199b6c296050b9afff3b2181c80c2eac266f8e83159969300d06d81dba66d768b77c209d4ba5f94bb212f464a62f3b9760bcdf8edf8b181a42c4a3e1c1859c2cd7320fc5409bed82b05de6545666e2f5ecbd342eac51e817f12b2bb15b6095e7cd4634d0d5d2ae1eb0a718a5ef93d904c840dcc06c63022bccb316c7521dc0738060ac9ad4005350675b4b847fbfca04cd858fc5870ed55018531e0d1e11083f8af0f492700d52b8c82ea48a04bfd9c5a114a6d20d71746023e36661cf378b35a9477db120dd795e034ff758ef59e10eee4413480b4852ded13b6450770c56177532136991b6acd7e016c8e8d8c901386a041f365136d3a27241effd8124250482d0f21b6027c6d2f3d27f0295da937d6720504e1c84ca996a134e15140c0d84d26e8c4a33a35355565e7962b5678d6540a95524b2d019e44f50223bd508216d9691411dea74561dc4f0352120b6c64adb94ade106d009beb6a02473a6bb9d5267f6da2765de9705c3dc4aa5befdb335de8b22d00f08518d504180f236cfc04446617954e9d703652950f31b61e0796ad71cab9dd4346d5b873920caabbfcc0527839711743417b4a2f8ff801daebfc72c0f87a18032267d834d1aced639640689090665c41bf8d9a2c6cb9811a9971f89c56253ccddb30e46d42908ac36984974411485412cc07225d694522271e57373a261a4afd1f6288ed4efb2b205f7218d646e59c22ceab5cefa0bfb6e5b8c07d6f7cbedce33d621b1d75ae5c818c4cb43a6c0e200650098b4ffbb3c190a67c8a04ed8d4256b3e708aac7d548947c82b6794ecdc882a5df3f98495433ed3087944531bc30db6028b5c7927a55828d77af0bfaac203529da40b34dcca31dc63ccb8a132a26c108219ccaf0d18a613d9ef9d42100c4c4eac75e194301c40b9f632fc8677f9f043e5d0c74efd97187ced91555a9f595d8c9183c7e2033d7208e8f3076897dc8a8550caa46c536406025c0ea65035911b6e4d838139c2c46a6cd0ea461bdd6d90b613c1a82803c7a4cdf528feeaceac9d81ac8dc43693e0a98f11268b9b63c2a2827992299775ae711c44b912c01731e65bb4d3c4e12e6bea33b2c6e372bdef81d1994289333760495896f9441e523a2234d4beb452a233a9a6469be6bb5dd04351e62badc458a8ea068f098d2c77d7464cf7110dc5a4eaa498b53bfc6a6a0ea3ef6a44399d038a67b62ec8c43eb312cf57083885868514864b0a16fa4b73962dd5c425c5d70d49e566112a43b389a40432085d9ec3fbe2fe5d31ad3202680dec1ab2ee037ee24fa4e5a5236b2cdba4ca3e4f2baefbc30ba2c087bfeec6f3582d06d415d8d02b7b121d4bec1571ba3441dbae5af46b677598ae9ba5bcfabf1f2b4cff50e738841a22df87234ca7e5ad39392d755f4e68c6bf189fbf49a334ae42b05085e481fec6a08425aeb0933ee018bfe8832267d7d7928805130bf62dfc9bc1fb2f8abef44f612a76e7ac471e5de269ab60740f084f387a613f62faeacc22e4ad32d84a618b312df535053978d2179376f0bd8bdf73e59b47a543e6f68904f7db3185fcc7279deced1baa1f062e2b160d6a87d02332eff6dffb126a88c817152c06809ea21e61f9475f3568e1a63c19e3e33eb19b01ce8ddfae1c9bf6290f470c94886379f3a054fbe32d8a34696023442c296635f80c25d7079d6e46729442814ac583bcd35cd1fc676dba2e549b36cba2934f9ffd3cf826a2d47f4a373f9834d015e16d5e1f0f82d584f5d179b824d32ce2805dd28c14a1dc4f30dd5ae9572b1a8ce1d56b84e4b61940ff2fb8eacfa8956ce0e3598027926b6a404d52aead6c7e6c337bed17147aa449f8cbdb73419a50c3d52950c1aac806408920a41c83f3e4048d8592dab55241cbedf84c6140a1dc294c251f4af27599b40616467ae53f7a62fbe98eb94664fd07a4f3e463d1c9091e5c77aa4f3daebe083d1bfb3b922fbd45e0f30d3ff311773cbd3f35b381effee7e50e871ddb58545892bbf85da86308087f4cbe3ad860a7331312bd57326a51077caf02d1cc99c45163044fb4954b5e53219c1a25335bb0f320f0f1e60aba97b54cd03a830394fd5b38d6e81dc74df76fe8d39ce0033ac9189fabf9e8376dce56640194d25c51786276a8d3b12a7412614c5172195462117a0d8a1b499857c7fed8ceb0c6b7012c32c4dab304847c93ab5076327b473d05d9a60eb27ac8a7c0b917dda33975795ff2e1ff7bbd97ae9475d4f016b770d7a173206d139cb49398eec138448377a8e445e4664ca8f32f6c6927712ec3a65c85292f7561dd5f3c63a13724b6e9bfe5d48ea882f481909a3b0de246a9c6735e153f9be32efc0f1bb2360ab6e42fbd48f975a90939092977915a03b6521a647263aba1f897a8d30d08ce3256308bc2afea2da4c56b328f68b2cec2214566e27b81e0fdc8520d3232d3093fb980b5fd0f2e48aa0fca16e8a64078b95000f44164aad4c9a8b15b2f7838fc5f61290d436ceaae8d542e1d1ccad379308fd32c6f5f8fdba95c5d008e3ce3469a8115008f2694743fc2edd4995d4f09b3b081436c4c731147117518129202b5c549ea1eaf644bc3bb877af9a92d9616d9374a6103743c61735d605a759329d908ad1951142466287d9310421921d8ab1d1aa01f0177b3368839c7144f3c6b62ac15d66a44ad326c15fa17626f613ee6ea2fe43ee48e867c89d04fd87dc91d4cf90bb49fa0f7727a99f217726f51bee6ea2fe43ee48e867c89d1e040ae5ebef3b985c97efc400cc902ceba6f6cb68f5ea844b39d6509e3dbe1db89f9c1bf18a8409f40fdf835201d6e83d9d597623badaf91b703e0bf836d81135654907ab033d0d40199267114b19552c1d5b46f74f8138d911f0cd012e3680a24888d6d5d95d62f2c89ad7d5f8f9e779d2c1c827704fa4deed7ececc923f090766a9acfc91b3b27135fbd53f6b9006a0fbb2274007a8ec5f0218f19a48c8c1488970ced82a78fb1de04792fff8694c22d13179e101d09f7b30dc36e2960f348de7f24cbf2bc4fc30ca11a2db6b661a232c14b59e668abff48a352f927cc66fdf80017244939e0dfe6e0a57239bbda070aaa813d41e88e252173832227335aa5d1f6b23c86df53414e002a430a826c937186bd06fec0223348ff9894d6985e7ef7e6c41c9870528b157ca6fca6ab5bc61546b179ceffbff1bf049968358f3fc46081076b21448a5f0b629834228bb772bbf1b17b3cea1af80221a292020eb95052f084843ef4b97866a9e035f6eafff628d2932ba995ee27c86cb15234423125ecfcc22eff4f7679cc1f968fc6b8c06aeb42079681a5ac95fc9e7fe2dd676296159f397b14ba3f4b53bc1f8d750163d29917f019428ae1d594f00cecdddc880128023a08fb92443754b290ee31bc056044d65fea024eed10bb068b629a34bfa4e05ecbcb2023ed8e66173f3e58952aeb30270b9b520f959526b49350d07b9e569dc6955f5724e0822f641b618f5c734f2046bdb1c24fc20a179f16b7bca08f9d6a88695dde85a8582cce250efe84566e300ee565fd19aeefbfc1966b4b28e805eb5b6daeda8c8e45730cf467a14d105895e66ac82cc5aeb8ad7d4cd58aea136aa360f9bd8270821354b40bc874647074b61fbe6e312d8e11a05f53353eed06eb87e46a95197272316195c3a7fb632f972c00a58a90f9916873c2ce17c94f3019a8e4a0ad699cc8414a5ee82c7073c43c124bda31bfead21b13b85bb8ff97caa3872f9b5d660fadfcf7c9b68b1070c163ec9c9d52907e0112225c99c6ba7900ab1a4dba39c4d5ee52487c9f7b3e75f96241f7b57480ebbec503aae70a1dbe677686064ec626bf09573b1adf6e34c921743a0d89734bdeccbde53b689e047a56af676b1f51091d0164c0c2617e10ed26603ba34e682edbaa4fddf5292f8a2237987a1bd58068a3f866792abaf6f866ab5a3b4d1c1ba301283e9c90df8062d1b5ad70f1a3f9418e1ce1323d49e67372745d1dc5714e68dcd5919213cab3d8671646013a496042311860c4f5267d2bbddd69fc5d4190dc5ac968beaec7367202391a30bdff4c07d0804a2a60e210ef4d63af5e566d6115e5e73bbe86f6cc6d21e558a397b4ecaae195385409f299aa987654b6588daf9933cb234db3ae27a7735a6dc0b819dd1c455d218eee3eae75d86abcd5a565a3fca51f5262920759573e5605385a3a4d04b74e6135945c89a765c3a8462dcd43d6307dbf393a1c8269c358b9f3ab67a814f62121d79d6244bd58d1bee0c0ceac0151966c5e02fe84752dbb94486ccc50b35e0bba3aa121b18a542106706a5a45e1e1bbb4757a0f22c63df3aebe585e70a00d846265ec3294a870c03a23763ded81e1fc06c94479fae00b652af2e50ef0d685fda3f2788f873d0788e589146cd27bed58de0827db8d156544cfaa8844c149a83e487f3b3eed1eea7979e75dec0f8e53cc8d53383cfaa74f831cd24bd8f770ed24923c65f6fecdbdc72c3983dd34027c707f94b59439fa4641e44629da7a32604eb1f7da94ed05a2b4cdf11d0fe27f190daddc0b5a2831514ec8a6b6717f2214f7b1a060c4025a20babd889f2f2c39bf54462cc1a3a648f5594f487f82848182ece367a5bef7c04b867a6b36cefbd68dcd14c79561cfacf5a48c60f20ca5789f3216c39a61354c0b86cbecec77035e05dd0f321189b9f1742d6cfbfb2ffa66ed60364a4867bf03d1f05301ae327f5dd0011d3f24ec049dbf6f985d768b6b5d68f1068e0dd1294483e75719134a740dc89722bfe3a2cf2a13de7ef20382e647acf5f023b8216f527e854ad3f8ae45f5f1cc22b6ffaa8a5ac0c90007ebe46420a174e2b939ee6aa973450938ee916e072c1626ee10bb744ed4e9704259365364ab2bbcf92904871c4bff61a056b9a11cec45d48b0f5000ec455c5511489333d88bc596ded7baf5a5d4db13258b52b771c5f06830781b8b5cad33df090fa4dc1ac014038ffbaba5a2966ba562cc333aa8c80cf64de1185f9f4dc896ecf55a885bb9edd27d30bfa129dbab9c7f74079f323763b06e64a2372ef02af356ac2bab7060d8acff0b3df2fddb3d380fa50035f387ce18f0d85249b5756f9f4c30fd0045b59a9eb0eb6d802f0d1a504f6a13c6ff638089de3c605c53cca9441254a1f10ea2f9e3f14a3c1db6d8942248af5fa3a6823fa4833fc140594de92786d7e3cae926a9786cbae74e797ff144c45b18d73ed19a0f6dc8646b17ac6663be6d0190897e32dbb7a71af5fa467f9cea8313524cbee9ab927ca6fb082fa53695935cb4a17e95458677fbe3e7416d7c9ad09dd2d8cecc31934f2d8e0e217b9f35d12a943562dc1fa4994697f8653f36930cf126d818d89cbf29c7a5983bf5114e4ad9903cf38ae7c9ed5048b2abda7c46d72b4813b16a438725bf267f22b014896fb2c867bdd8817ceaefe92e4749cf9b87bef6033774b1bd3308ce252c1fdaf201d3c5875f758145a4f428e604d25b705823053863d1a1f90edd84d14c4f946a0a75cb858bd760be0c72f27162584afd48f7ac3548a42c62dda68918a9cc1fc6e9ffd0edc20c198a10e6f61aeae8276f09af04c6167ee4233f4a024a2cc2e7306b8a0c159436458a6c21877cd59cc9a81694d7830ab6c2a5f5fb15d14b455444d168048fb22a0863bb81dff9fe49f12d8525aa09b80231a789b9de5857e70e59da58b11dcf34a2ab68ccc8046b4ee5b73945b1a5282c419b9f51ad5ab9943f78d0553042e8eaae3a40fe1202dd4f585946ad25458b003e0c5b9535d191db4201153e9b71cbb70784a57568e76dc533be5e8a5ca267959df29490fa13b740037e3986e5922948f8b627589ef9a986bd3984fb9c2b64441910aa015b72f6655ed66213d242b2bd1c80f30802ff6cc68ca9c2fcb2647e95c326eb5d6aaad52da84f56fa309a570c804501ad160081a324c28efac0314bab57f4f1cab86be57d3ee272176749bd93442f690774463f2869cddd49c5127edc586460829ede0accce6db8959ea084cf589bf60ca16c2bb25b6b016f78b1851a95a965c710f048cb8f29bf6c8a0baf7aa46da2300c34384caae4d21176326acce6cac8fd8e82135556966616620154ba261d3806328393e764f40f17585a01965fc2d939260e1f084541382f57ee5ee0c5379fc8d09763008158e5c1ef5fa2dba09f19f0ef82a77cde3d654ebea2bde7d936adbcab0182f2d4b3922ed03312e7665dbdc481e96f72fea856690c6776d8a99da687d15f24194ab9ccf47ef173ad4580e368061e2e8f559186816bef0537e5a9fa891c27201751fc1fcaa69f2317f776da4701faa6b57eb24ab7f8ba9b61a4361039d990fd07d4cb7e3f3d8ee1cee0ed32efb85cca6018bcf288ddfb8eb7fcc074db2b70d9d6d2ccc707f3c7f380d7581a5358fc344fe1971498f3348787cef15e12d80ee9d75a3d763d6ff39c487ac6034d2460aecaf406219f5a38e01b5d8d522bad0693181dd21b4237aa0a9120a58b23764bc05baea09a81bb1438b4a48e335a51b3a3c7a471c7e266cb0d353a5fc7746795db908466f775f5dd49e18b9f7d81767859117451857a81ffa812021a1c6d80e04aa007d9102432f5cda60fa9ae4a41cc32731f862e63525a36818e658638f0fb0d79454d8066975b07b8a6ceb81b8327b438fb5e1c9377ad1fc6318c5093f216ecc92c2458dae3eb41151cb888652edea2da719181ebdf23a99b424ed36ead26f493ed61f3a283d1ab79c126732a301979486956a391a6e47a789eaef9c275b50fa369888ee881cb10697dd6349d837a7a58bc98596731aafce171e78d9c0680c6764eb8e2b3bd96c009a7d0cc1a9f81b9d15c86d8b66b391ab84e8bd93995caa0eda5e00defe3847cbb08382154dc6026f24121589c4b541d7845f78826b2dc2350089cd84aea4286ceb04ff472bfd8b72e366d2eb6d7d5083c2a25cd235de42fd7dad31b815872c8290ecc1366b2f94912bce9a8247e243da8f41d042183b5760aeee24be520b7436a78d11ee03390ea7a49911aa46ca4f60a1f805e6a2807914cf6a3842865c7933538a079423acfd5a40733024d4d539b07ff1e71e37ef6a5f2a4d2280db48295962a72cfc9205bcf4535f91657726a2445895055581a670c4044709bea6800bd9a671aa6119f10a36a0df70d918040dd2664244a146032b198e6152c39dc1527090bef695974e8276a5a052c5bd08521463b51865a70f8dae742951535777a6894586af3382cd65911c3b923cc00a2b64166af76ac8f9d08549cf35d5ba8fd8ed4982b8d8b48a34023792a4b55692445a95429312da3327bce1fd2cfb47ccd4491176642cba7da7388b36c98c0d650f1a78b7db26b880031a9dd6bc7972447c0aa7e1b87ef0190630548b4474c479ac86ec0123b17f09c4dd6fd9cd99d151a2ae29b9df7994dbbef9203a1cd1d0832101d929f532351e4bf1ff69faee9cc2844dbee1f965f3c5bb6a2ad2eec21a5841d64f85e70095ab535f90987e365e22a15598162244d9ad1828bde2ebe9057f0a5ec982a153d4237f5b56c3734a12848c22653f292466f567e9bbe3012630fefbd1948c63998c5567178c30314ce75e2c8b8aa204fe754eb6184a6abd6b52056f12350fc010c4f7f94ac6f329bb3ac20a764020d353ba05c1574354b097baded4a9704f01cb78a1bbfcdc9aeb76f6106a5ce095d376756e33cc9dafe7c2036cd2fcd90535d41c62fd0c4c2f7987e32d012602bc2c9c582e334c41a036ddbe948499aa142b8927414395e07e26f9f298919d550b8e40c4f482eb7766533d988d401dd6b07cebe50e99752148bff3abac8d01d76ad2245f2386adcbaa5e628ff7fd02522b89dbaf6ec300c5c239cc21c5e060c2726bcf677586047f0b3820e0b7db7a5854cf40ca5487cdff625d8418984d754180fa09f22b1a2bb238dc765a93ee4145ab884d33ea5e972f5bc6377ac6975111109ae09194ee857d5936fe4410ab5ba479537d1334a3d457b6c711e4d797cd83646ce826bbb60efabcd8a9ef965c86edd9848ab3e5731717c6cfbe17212a7f234737a3acf0f0b19a8e7c82c886341c30915a5f2e6d1a5ca0259f9c051e451a7f2e9e93c7327a70744fdd27d1c7f062b481a66ab6a0e1217084fbed28b3cbbda567a2253a9a6c89838eeaedf7105f6660e5838470225c1ec0dbc9f73a02df2b9eed172d75f28dc7212cba58b5cd4bf02d5b35a6ec4e520eaf42c4ebdb114ca31a7f973434021ec966334285a4097b31f2678faf87ec91fb0dcd79af7b3fc9154dc750e7004969434d875c3a8d639b01f26001cca378b46d7f206f7c1bac9f4006513c958d2902a54123badcd85a8bcb96dcabf63372f64f4b01686940f58ef10521b5fa5b3be111dd0d44b3037fa5661ac35becf38d260502ade33aa6c1cc7760941e1747004603514addc22c028355a29b87e1a38ca7f384062d750933562471008890f0a2b2c60037453fc8d65bb993fc16f1515c932b9fe3c48ba2460e02f5ccb523fa223e6d8dd14df214f8d793a77648d12a339a403fc2b9a9f286e78b9a32c840aa1d220179f2e539852b0e96061fb701def37220d66b5c2c7eccd31936106b3d8a95748785a03007d4249c5d8a2b5cd5432f4d4146090f3273b3bfa1b73ab871138a277800f51f8e85af69518980eafd6119309387a150915664bdfeb6802d7994388653fdbaa0725ba869c84a10785705bebe9eddfe41cf8118d8691b488d6d80550784b5babe66adcb5734efcb41755e12d3af1fbe46958c7e09e686f9b54a728f0044e39e6da4ef1d7e91af8b5632d3ecaca005e0a1a942b4f7355c3ab2afea6aee26dc7dcf42ec01aa87bde2283fe029aaacc9315ee5b622bf92d46c4c8f3adcf36b3cb09348710ad4dff0211dbef4ac2dc1e61fa376afdfd9db335b602deace9682be4e36a54daddaef542223690e6cdf244ec40345457788487a0b4285b2092bbd3e30b8d1d24ede41c184e6fc5572118d4481e3c94d3612515f1d896584f3de4e47c2c8faadb43d43c6ca63948654c32cdb3ad8e24e097219a91a728df6b55ad0162f5d285f226128775942c4e128b26dec4441c7b91bfd4051912d5f0bbd0a2b7507fae295e3c5cb4fdfff498e87bbb5679e0982788ddbdbab384c0d529fa475ebb49a604340c0aea894614b65ae8f89e38539b2a801bc0fb652723b59320a5aeae58e9ae92f9df5533ca3570f7d620c530644e5958e54e809f89bda6fb76924846736b635b550832580c754ec40636f0752954a9891f8d7db22f47f9e80d27b12ad177a8ffc83cc3188d0fabb0d86684560d6785102371285278a5d440308389c9c6e9d3ebad3970f51d6958be4f5d570af8e9de9198ad5094bbe9a2fd24aedf7e5f8105ac344c76d4765185044832dce2ca93577a8e0dff031ded8686b8e8a753a074c3b0c9c991fdea404057a578f0187997c3d0ca3bac8caacb6991d0b6c0f8d7fa4d557b633b0be189b35c3715677644a61e796646d34e8065268cf19f2c9a5a11f1deb67b535769c40c7b3cdf5408c48626ff935eb1519b4bead4e94570657338878d7aa722c11353f70f3b762e04d8ac03914af092e3f534af8df7f74e4700861500757356071dd7b8ecb952e88b940e46d8151f45a5c36d195a8b6001166187949415c5f264cadbdeea00f8bbd6a3e4a5e7d15742a340c4d16bbc9999be2ef35e16a5be18b82c924ac7a46b936ec7df6f7a5385284bbe8605562be462b0f575ef88606312229d0fb8efa10c560162903a98f4a03b985a0e726cb83a1a072382a0b01316d6e9a8361825443b9e90e2c83c266773f147fb434d43f54fa862f62d3720219969040ecc49a9b1545200e04c66c202f905918fbd0234e009643cebca92f98f5504ccdb47423559ed87d9871fcc050a79cbe3d958a94f52b491098ec9532c9f859735e4f3d3a783e6525927d5625ee47f2686bf640527f47159e7074a0dc0784d5fe8c87f3ad0c3989615eb43b497f01a0dfb2c82de1cbdd5f34e342d87d60666038395d04d4bc4e690152bb5105a8da52c44d02d8ae4f855ccfb0f562b53d8a821607aeb34a47df6bac1b09ca19715cbee8f2ea0b6d917070af41c0db339235866a4a6ca28b7becdb454a4982acf09aef94ec077b708d80ed22624082cac36aaa1d6877850ddab16894a98e41aab58b8d2219d2d99312039e2385e3e28a7469729579d72a0a035e1a6f7e451761050671880e3544d754bc58451585f2cdd206b815e944665a43db5983204dd16831650d6d39a2131a7eab227a5caf6df3907e44655f58a3db168ed4be6e15a2c1d5c3bc32b233c82434b188f8a47f6469de0c62d5d111fbbc802fae181e571f803dc816da3c2ad6bfc2af34dcf8825cdb0aab0583c154ff58c722e10a380577dfa0ae22792f46ac1dfd975fe2e77ebbe7c0e768b8f0e9a75327d9924d1735a3da6ab683b908e64636ba74048e183c5c4f0ee73986e2d48d540c6513aca02f9aed9582a9616906ef7a3a14b42c9d89165a614506ab65d92122e3066bbf50b5cca158ea80b989a0464a40ba6a148c60252a9dcaf5ee0f60e208ae83cfe74f8151c746c4c642e63d23864fbe94828e89d5585bcea4f437ca08b4a087d3d9679f100206270d80f77e142f4c1426a1f264c8918cc4636f21dc3c012d84d59855079446828cf2e23b0bbd7cf8c718cbe8e135cf302a78664d8e20b881a01980df1209417897de716ecaf387d42889af1e6744584573c374c955779bb871e9b131295eaa6e6356e774fe1c5c5161d082a046dca1194dfd30d662efaceb4c4e782609bf29b9e47cef4bf39f9f18c59dbe86a1622fd135ada457cbfdf4780aba1cb402191adf675f221577fe7e8f5b1b3efe54efd1a0f4c0b104bd214c1f34b6e11b42e982630d7a4b983e68ecc33741e90dc71ef426a61b3476f0ada174712c5555cdb27f2fb8bff61ce0a41ee7b73897d86def16161289681d973d1118106020ee11ca0723dc063da22f7b76b2080de807f34480619fa0ed581e77c53c6280f842c303fdcf8bd6e3c74cc624944bb7974c1d4d2c2de2888289b7b6dbefc265040d176896014ac54ebb2ca4956322c044d99f35ef130cc34613582708c061ad5187aed7a82b2057d806f69bd5df396462b98e4fa0bad8537e7be92c3a37341fd82a1f2c4d12090571d8723ba8f68050c498026477ee2849c4e7894f01779fb9f687436bbb5f15eab1b6282e920a2256c9a7083a1d45882ef5da5e1dc5194635cebd5869cb5ffd2706e18e033291656d1f4a79a97bc0227abab1472692acfdeb5d4926eab4dc810a0fe112019a72a4d817f161153017da23f7e13aa357d24facfce5ba354dd4012548a8bf8efb0d74f64884bdb24da3fb476437eb6e5c6da08e849124bd58958b84eedd05213f3b7a75b267974b87a4abab6e0be67233b13f5b02a2a959eb4902187f079f770f92a17337f857f1b7256e2c47becea686b1095db95b72f6d3054488a1563c4af7ccb86d414bb70b4d31758bfbc1d5d1edcf8367e8d72d7e5b3ca56c39cdf5edfedca0c34a3bbb1e8428e1c4fb8fd85dad95bbdf8c996002cd6fb895f6eb48f52bd1104b94dad48fc1fbd60c618f1da180b725a1dfe710043ecdcc46baf3dfa7d73ce7192172b016fdec8101635aa91237d4aa1bd6b2cd74b8dcb75aea19faef3f42d0411a72700cda56b828ceb6af8ddd2542049d65725907baa0ea70826835add125e864ef087bcc3023e60391db909f1d193a9a189f809adb27bb3b15e425e512d43220479cfa3b5885b78e2fd149439f89edd42db20c6bb54bde0100d66a82890e071841638281c39505513f8bac30fea393d2ac23e32b181ce2403ec3f16800e742ab3a11170a713ce451960a8c5ed409f3ae43e3e6edaebbde49ea2f2ed6ed9b6bc9d260c033ee14c0b85d430eddaea42db0daf8e876c506bc66aaaf5e59d11eeb87195fb62a7e68e5c8fc632d656201031d9f140ea361c2e11e6c1f86192000b1030e0f717651b8ed9a9c21fb23183505d765ff2952424bb9a0779d9172e848bcfba559f8c06ef14e38943241e6dd99c0a89fbf07e39058ea56b55a7d1aae72f724822cfc0e3a5f6795aa08c9ea8dba33f1916e3fd891a36355c659d01c8ec839b1e51ef75873919501f748f291cfa02ddf76b04dea4e2069380bc2eb68193ae22104b36ed3afbdb85362bd02ad21483b25a1f0e01eb744cdf481a1580ea7da32b556d8485f186b1dbacabab8f2ea65221e140b2484788bf2a96456ce9df5c4c174b906a5b0df801f20d5682b3592f1c971bd17a171de9aae25e31be92ac1bad2214947366d89b2c4f81c90749aa8b0fda87f735a8fbed918c7f1b96c631d1916682ebf86a478742428195c24a51d85372156b137608093db3e5d7a447da4ce4a3dda8976527aef943c045ea2e3b0382454d831f90ae864ff92e8b45bed8fe4cf159a5a36cc88ed1892d8d68c2dd96c552c1f6de06be8802d9b7dfe69bf5283530158d7f635e129eb4a02026b2171784934e46e015b8a09e52cb16766357b024d85382165b3bf4edffb1378b7f14bde3b141fd1c477392c3cc8da3d257167a1bf6cb3cb9cf257bf6b48cb2b60447cd46cca0d225b91d6b4f5b68a940e4a1ef7394a8c760bf2fa31641b94a70ee22921d1a7135b60153270a69b1175761a677c4ea563353e311d2d806e0ee50d5f734a8f03a2a553cfa9b15ba1dcfc6dc2ee4d9035802fb5974a67a10d8cb286b3b30c4f2b289d9dd5593a12d72103440f726ea186f3df08b11f493d83487acdd254654e93a75d2d4bc1a405bb9258edc574a1604558865e19e11098bfe2830ca768a5218cb942caa64257cd00177e812ad38e82c78f7808a441fba3d25ce2178cdca2a6cea161bf60ee009af673b5f2a44caea16f40784755f45273ade457a199577567d47b611812efcb1e0c097c41e5b89839782a845b2e3344c66374da87b62ae671f6722873a4b26254903e56ba86f925e6fce9da05671976a09b3f10641a3e5154c698bad0ee462a1191bd3a263a8802b07c481798f30f025eedd26b33a64abd0e3081d3909bc5fd4deb15a044ae985a1a8f9419f380ee23e71fa230102de25bf25c57a615252520e7cad843be453d71652e4c85c0fa56e600f70b2ae4f3257d99c1a47e67f9321ae704d6ed2aa0990569ad1c071640574da4ed630103a3ad3205f7bb34b6e53e18575e5830c52ac220fabf3be5792c25212c89511f7fd9638ef1fd4cd23fc2ff6c7deaf0be02edae1f3d6d02182300989aaa1fff2b59df019842c55754f61bcef0e8f468f34f5629f948b6c179d31bdcb8c4334aa51d84cee1f683a272cd760a1cd43e00c0528774291739eb87e55d0966995116e119273465bd7be3e79b766c1eb3c52e299e9b35f2416dc758dbcf62f31504fa7668dcd5309b29639f3a453da778966a2430e426c906a0250b4a094a41c1c20747080972c1cbdc72185b68ce74dd5e586e7f179b2f85e9536647ca35ae96b2d8008bb19c709db6998b2b6326d5857d65f72f036b923ab0fcd004a930026bb1e7e5f701f2ed295969864238943cd00e5f7a2238422d8403ef2fdd36c060344f043e3fbe336c0cb26ed78cc21e7f8576e7a6241616fa9401ce22cdeca27843cd9575248e408b052937f9eab33f8349c316397f6140ec607711a3091261e13fc5af6ca9ffc8ba5ac23426fa53d6e34c9f6818c6b2bb71016bb891677fe73b2f1b0168171c8ed50c4c7b41b6d1cff5161c1fd6efb7f76b5c7020400c5edaad3722ab30aa4f61a572827142e7ab15820b9a71567c7e0ad21daa2bbd532d1a7b882b0c131cc662b8cf868881a7dd6e9e3b4fa0407f83087c22fc4997ea3080150b5ffb7e762049e5797f2d3ea52f32de8b8c76b5316631616eaa421bf596c49b7a363627ef80fe46dfe2ba51fcc84c6ed66e7833d6ba761bb017299768216745a4b60936da821f0c7fd8366ef52ca672a7416a99dd1836935ab2e8d25de1a795cd7e324a71da4a60f611526bba1c1f0c9499341fd030d0e03735425e342093a6c62318038d123235427d11653b2813bc73c2a8c4ad9a0fde969041858f72814847e639342930f0a89c6a19b5745f6058a52e9a277e8115f7d4df53c73fd264743c2bd04bbe82c98044bba1de408398f6ea7c08eb17b5eb30f85cc3bddf24d39ee840866b63faf9578f9fe1ef24d689f146289c7753a1c0260703785d6d6bbc59e642aa3f0f8eb81f7a2f7da25f4e30ef3a1eaf3eb86d8df04ec99915c41da85a1856cbef0e3293c88e65009b1d14473efa09d4a318e418034af4c55e5a50d815a503c51db83d42ba5da4ffbec1a971d05a2098ec8073f205c4463230fb0db4299697f010b25a8fa93ebe6a731606807e13d1efc8cabe2cee50b53fe94038f499cdd0a3978ba0df066280df1249c8de7b6f29a54c292519dd05be05e0053cc4262a38536b9b21de830ea55f3733aae04c29e52597885486982a382a433895219c8a1073d7b99f1816036fa049f42a437c583e7fe383f560114d1008e542d9dddddd14d04c4949494999014fd5516d069eaa91b82931d8be5260a385922aaef75a50456eafb0001083b99f07e38b07630bef3d185c783735cfd160c28beaa86822499da13a2a96d8a2ce9b10ccaa87ead443eddfec73722d3d7c6a5c4b75a30713a6250d60a67daea8fd311bb23dd47e95eed7bec61180c97c35f43d73957d6701bb6939e810362f2c52976ec9f207a3a464a6b9389d256d3b7c738a326a2e4e6749bbedf08d3f1821b38bd359d2b6031fb38bd359d2e6cf7596c46284e6a2c3ccecc262c4dc676666e79c9218331e94d5e59919f5c669c769c769c769c769c769c769c759c13c13251483be80d38ed38ed38ed38ed38ed38e93128a2152a12f78e3b4e3b4e3b4e3b4e3b4e38866acd0a02fe0b4e3b4e3b4e3b4e3b0d0b0415ff0c669c769c769c7b1f114a71da7bdc2c9a8374e7b85435fc07921012e70ffc0dc47a2bbbbbbbbbb542a954aa552a9542a954aa552a9542a954aa552a9542a954aa552a9542a95bc4072c4723776b79e420895df4f53448fb179e3d6fddc6fa27b10945354f3524e8250476ab1a3c5e5d7d5bc974e55992e2653547e6cf330993e67f34ed307c441547e14128fb86b7aec665a23a9fca123301c068ce685213d2b3fa8babf75a956a43ada35a6d6216d49a6e63b3164b443a9c237bc844dbc046f9cb3f3a4060969cb3935d169534de3e09a9a9a9a1a6ea2e364279a4c2693c9545353535353634d945068e7490d929b6d734e4d74b66ddbb68d44229148a4d631f18dc96432f112d312f079b7a94d6d6a539b40a118a2946d73b6898e936ddbb68d44229148a4d6d9d1d17952c34898f4b66ddbb68d44229148a4d6699dd6691d9d1832665c2895246d6e76c99a826cdbb66ddb46229148a4cd3935d1e96ddbb66d23914824124965e5a2717d060a62b9275de33b5be3d8b66ddbda666f76499b9677dbb66da3315de4cb9673bad909321afd8b443bd2c9ec77da9bb8437de7f98efb4efb8e8d4b25c98481c9e49bc6c17f1ad5afcc2f2ec79d9eaca8366ffec64f12e381d5c752c4c923e59c22d1ff68e44e9c03ab0e015ab02c3ec1c064767ff31af1e10412fce0a40b2054218b033c28d4f7d3299145f7c03900aaa73a2a9498c117163b0358b30bd3fdbeb3c107d7d00dc3a3e50e58cee5d8d7fad9db3923bc1ab09cd311dbb9fd767db1937b6a6ff71eed8e90e525dadf97324b278ff83dc77459a4f6cb6bb6f39327e373105e904a0e58cea7f6a318aa0b742c228f30bef7e45153123e08ffc9ec4534720fe9a7c09a5d98fd3674fbe09173553c6fd7dd576b51ba6e0704ab9db6d5bdf7febafb27a300c5fbdd9df7c6cac0e2a82c014a85612e1500fefb6b65d820cc9cc46486ebe89018d529cdab6de3dc39e7bea98847fdddedce6da152ad4f6bccfc422b03e4d1323f6691484a48e5acfccdfc4ea5f64319258d317a74c7efe38391c2f7e07b4afa06eecf5dc617f9f9d5c5ca08dfa5444a86017cf3450c087cea657f942d7f34f508e17b7ef5152f8216d86862c21b1b535022055f8412247802881a257a90c2a49e8cc00a34a898708b1a55ccd8842e90c0850a5a50a30713c507349090820b266a0c61564878e1021a3b366001136c606303d263b21633a3809867b7fcd4ee6dd7ddd86531734531b38cd5b5500d3e66f6f6d1efee4ebbfdbd97b93b2f737777f332a77860a082dce8ce39473587001b55a63b7b77f75d0adfbbbb76cdbb9bdd29be7d9d692fb2d7a226458428a38c123e2fc9089fe74419257c5e92113ecfd9811046096584cf73a28cd257c69c28a384cf4b32c2e739cf6194cf61c9df739823e3f39c28a32c9564cc89324af8bc24237c9ed3ff8364e676eac6c6c6669bbcf7832dddaa227c5685e332c2d7553c481d81c813295bbaf556b532c2f8b125d5d277775d7615523655b574ab8af059158ecb085f5781c5bc9791b2ae966e55f045abc27910be58854bf8ba8aaaa55b157661d785c5f85f8d6355382ebb0aab2faca55b5584cf0a42eafbf74fd609818778d547359aea2ef00acb72943a0558ee9d3a2f753c7693c0bb163131b7babf8a7ae92204db626a31642b0d12e01cd18a10d654d9838b10d7c28f6d3149bddccbbddccb4bb2ac8d59e09a1d916814030d1f5b36d5565f8db5a7617d221ed127e6d6c7e2c2395e5c3497cb6dda5391fa308fbc214bc31e198958aa598d6ae5522beda5565c53e54af1c2840b0d462f98bbb297fa30b53d8da4e4d1f4a22ef8fcdca1aad82aeb506d4e7eac92985ab5b9444935184b98523ad722290c4c263b74baed17a61a987a306da61ad366b2c2d4019a6a603299b628a60643cd66aa31ad6ca39b19d024c50c500f3a545b1253831bdc1c015caa8d46dd1eaa8da52e9561722da4a9d19061aa34742ac3addae026c334bab9a1c56059d412736be51b9d3b4780a54095b1307580a72ad3c4d4aad3e95467a85b4551e56b209ce75297aee8b89627a606c38d4e85e1a6b2d4ea54df0fd9aaade8547e7ac5b0d3c5cd6e4c7989147927e656bf91b349b0988aae90eea7a89309d3ee523bd336881a5ce4df8037d547eb10e264d94921464c253286c964e19164b995badbbfabdfdf8a8aae65faf95bfa8f152032ce4977f168995acee2bdbcb8b88c46522492b2b1bd20f499fa1ece852199452a6e9678c170061c71d7ed3fe75ccb567b60a0421896ef85253987ccfbd6668c3c519f3b197f9fa2bab61ba4570a315e8626e217659affbeec86cb617d95b255ca0d1e715d2c2612a9bcb1cd4ecedebc130f446aacda06eaf2102ba49a91fa249490aae87b2e478a01cc50df6b38eafb94ff3cf656ca6b5fdf8cfa5e03d5f71a55dfafd321faf74234856a3660bc44a1325e4465d00f8d419dc3e1c1a8366ba416d54435f2b0812a79d82aa966a4aee321d40914ae7eb0de474739e7a4a4da5b3aab4b759cc4e59c73104208ddb7745d586e9da31afcf8bbae9dfbe8ff5ac4cb8585ceb97ddfbbbb8fce5c40bcf7de7b0e1f7c0fbef581db7c5f627436442229e93ff768ff3e5fef6511bc20ee3de7dc730efa09dcddfdaabbbb8314c07787104218a1433ac4eba35738e7dc8c20d6bdbb7b7eb098fd1a8b680996a4debdf5645754bbc79711c648e55583d92f8520b26287c3e17a0877174d3ab9e0dc7bfe9e7bcfdf73f439103eddb9bb3be7dcdd331f8ce48b6805cfddbd5f3f7cef457fef02a67fb03a70f48da4cb0bedf7de6bd992d2c0eaeff0c1ead48ae9627777776bf0fde37734121d9db96a47611385119be6a880628bca9486ab0544954654d9c8ee10ebd77aee877ba632eef9e153cff1d0337e5dbfef189db9b4bfeaf5fcee2f3ab3d9ca8fbdf62beaa15319f77efd775f547e05a945514d6e8dd80fe61cf3a2daf594e5ae2bd3f8aff7eb5deb78ebe3ff3d5bc7a40df9379ba1a9b2996b5ad9cc55292ae3ded5755486df3d9569d2f3fb9675659ad7cbb1f74bbb280d577f6ae6aa32d6bb4ab522d5d266bdde5d1faf97fb63997cdf0ad35526c34391eaef2a956992ced054d68cf0d0332d835975c5e90f7ef73f445573cfeff6c69ac2c2803c99a7797f81addcae3aff102a437ce62efb100fbabb3bbbbbfbe7727777be86983c41eefefd9e3f738a0c2cc796e584982c1ba02284b5a56a5b96374060f680fb2c6fb33774ce86e732ca7d0c2e2359515675599807fb78e6073451b29450e80a5915150ac9180a85d6a978d87ad2ee0fc628e58ecab929ac1dd785611ecfe71312cd898292120ac1801123060d8fdccb7057a8ce79d1ed1f18a57bcb398aaa2acbbd15125dd7f47c44313314039aef41389292a2aaca39e79c7318e61ede98bd0215ac60aac0a9a0602a992a44cc8b1d888a89a9248f2c0bc33c9ecfc73910080565c6a4b80b8bf1fc3099d967ef7adc87b7d72fba1f9ea20942c1623e9f6b82b018a6cccccc1c7359cc444cedba769f991f1217a379b10ffc3cef0feff480eaa732d5225097ee30e2e34a580cb73f185bdab40caa5be25adc57565f8ec97675ba16cf27c78ad920942b05eb7633a3ba2c9906e3e22c1482016361ec37a08a52421f18312e2cacb9eb3a8818076b8f564879dbc354dbef9fa1953adff127f85b9f1c1f8e83cbe883880d3ce21859ea66a1d92487d6f14d7cd880c383efdece3090177b10b2fcffff57c145ca97ea8849c308bea889b975e30e1eedac0e8ba16134ea6be98e2366884727235c5c3c1c982fbb2e417ad0c01575a3569926aa6e9fd2f75896e0428bbacf372acb125c3061c4230e4c4d5621474cad3727424841d42148c952fa16a5c8deeba0de5ddb2cc438fa1d836cb0e20a149da12da0eccc950edddff2e8110bf87d55f88d050c2a7c20fcef1f95d9e7af43b07c30912275a987170c95c1443135b7830ea22a433d111aac470d3c7a9207111ebd8a068727a1c3154881b8c38147fccd52ab0c879eee747a779a35700e8aa228aaa9a62b409c6ed6df54d3415465fa3773e1113f0df25ac79ca3dfa5c4d4664d1502ebb57d9a3357e41ba94e34a933aaa3c2899d951ff2b1c7a886bd7c23d5c276f07faf837fb564b6dbbb8bbdd58f619487ab4e663d5cc530aaf5705566c675f07f31905e6dc14ccb86ac6b814fe32f93d97f7f839a3a85123bd52dc562dec539ba5d7b60f8cae25b9566c4aa32ae17e7d036d003d7d45dd977cfc3d6aa5ad9d72ae5d1b5a56ba12eccb5ec43d15e1b39608301968d03f0bba43f246f60190776dd4020507dab91de3da2fa41882710e18e881f50555052754088e70efbb1cf95ec748188de7d0863165ff426e203330a0ec65a76f7a52ee3a15909cb39e780e450640737c4df65fe261e2d93ca2cd7e47ef7f75e76ecbabfc7b2bbbb0b618c514a8aaaaa252e795d18160316a345f7f7dcdd9d6a3dbac1a3bddcddddbddd5ff676af4f5f7d4de999168394d5a93681da94e63302eb1ddd8b6368275dcb3e8ecafde2bb2a55cc1e3b5c53b7dbfbfb034775cf0a8f579d36b34768af77c0da75cedd39e71cd576616039e79c734e52c037980ab797171717917c54f6fcf9732c263e5c5f9f35353534f0f3c5175f6ccf802504fb5febbea9af359219df3629af351029af3508ea825eeb23336ec4cbb235766569ac8dfd5f199a844f35296d504dd29835289d91d5f8194fb32347b0fcca7be00f70048d6f137f6f34608581c964f994af5183e5254b0d16fa66d448d25bf71295cc2b8dcccda0b5cd0a0bc9af95ae2c99732d336a50c7a395cc55ae35b2240d0219b406e29ab195065d61a12ea03446cf8c956cc6ab64aec64a3b6b9bba33421f2383513fe5456f3d2c4614daeab0508a947352a2acb7aca7c83a27832aa799f2d587f8a6ee5b9fb293a16c196849d9ac2aa7f9f918d9a7aa9ce6fc1899a7aa9ce67ee8650d51ad0235f43132acc2d80f513e613128fdeeaae052b5de9c0e09d35a6badb5deaeaaf5c642a85a6f33bd8c041313749b6a23c1a4041be8c6025bdd8731947059e8e0876a7359e8c003d5e622e5b5bc4972584baabb547ec50258e2c12cfa90b816f9327bef33cffbb79bdd06db506dac7aadfaa51ad65585c2623c54521e0a5ba6867caeeaadeab2b6565686bd1c7279de7aeaab6c08cdac580c65613650d72283b65b65f63cceb5acc46262c78cbf43bbfc1eaaec04239e85267e4a86cdb4511dd5e8798da5f68dda6dc57f8f653760606259d4a4bedb6b15231f3e7f1c551e55e68bbb099b67da8eea28afe14859cdeb5e75866a887ecaa33c88b2b623dbfa2f25d5fce56ba2f932e7d77b58a9edf7197507d71f20245314b10384648a927b4e134e6a7fff3bd430709a4766ccf66f067fdf61be37a717d48626207b735a52db616de2c26ecbec9322ca9f87a2211eed432a2f21a6ab4d9f066a2c529dca1a906abfb5cea93ec093ea2e249e1c01f6fde3154e477cd238fc3d16a1ba472854973694d2a99adb9136b349e3e88fe749ed8f4f607c5a101521a60622d5fe5c8147fdd9712d3dbf3efad191f1112dc1626a32b6da75e9ea700efefe8f161f279c636d908290039a4b8b2433d71182bc176f873ab7a0f11e95f5f27af892e4d01e10f5c9d1a7b81c0270a3feb64423064200effbfdabacedfb3e8665f0a9bfb29967aacfed60be87ef64a076d5d5fc29d9580c4dd71f0b851451f870f54121051455caea30f9304c73c917352346d4f72ed3a40c8c2c51030e0fa82f100a8162c2ff28e91ab13790aecd0c8fdafb89b48e9184d345a2ffd1c845c8cc0e34fec6e84f7ebbfc96fe5e9daefc8032902e0087ead4078fdaab4704a22653029ca35f9c0d9c6301fc007ed98b7e88f9fafd53e34f1b5cc09f7b22169f8cf9bfefcb094662b3a7eb09e4c03ee88f54d07b86c4c4bfdeb23cefbc8da8284f5ce1092daadb9ae9a8c0411035e5f9dd563f6f7932d0877ad011e4578098ef178502d907bd85fd487910d53c293462318e7da8663e4c0dd08aaf12e47989c57864ca07267ec462a284ef6a64eaa23daec7bec7ac9e9869f13f1a0dd7948720aacd5450a8070ffb454d79e75a34ec237603ca0794c978e88cae3a06643f7e4ae679989ed94c6825521ed7630e4208210c6532fcd83706e4ba641713be0c7fca6bfc28dfa0f73c7c944f01655f617f3e9eecabe7410fb11bde7bde7de6b9daf38e47120ad3f34d2596f18780f063efc11ef416289b6f392c26e5419996427938d7f279e75a3c0f42c9e683f6024dbaf2e3f3d77bfef2d078d12c0bedc3cf5e425cb5dfbd5d85e1f744c05bb53eee8753b4aaa82ae6568a7a18ee2cc6d6a672ab2e8b1e31eac61e30e29a7a0b194588b790524a29aa548c31ca2d9438f7c54df530d31e920a7faad476237e23b58d98aa7ccd0812b55f25035fd4a61ae4c1dc7a6d0544adc9a07c48513795ba42f6a05125a54e10de4cd12ed5e4576f398f10767d7f5fd4f1e85d376c571e1d7cd9b1faf854456fd848519092d4297920f8f835453aaeaa89dd2b930f3f46195d3af8fc7386bcaaaaf8f0a5942b65c5ce0367bd6772771d13eebe5afc1e2b5951d5acaa8d102e101b75e166b1b89f21989d11bdbc2ca009bfe8407bd9c13bc5e66c3bececbc8f9c1c8fd992f6a671d010e1d1d41cbf8f16659712e69cd105ee1186d8a3c3189d00a13716acbe30f901c977a02cf350ed0155522d3e7462db735955b7b4a86eab0a52a5745654ccf9620bfe0dce111d0ccfbee89493aa3237920070c0526ff14bf81be4e10a48f592a26eaa51dedd71b455c76a542fe7524eaa2dda906a37aa7c4085bf12888d1adf799545aab5af887eef1b245373db15842dc618df488dee326edbb609a1bea24e2819d7e49b695785ce4910b6f6766f47a2b285a9ed66053fc138f8a790564c211cc1d43aacdf96738a44a691897f9364616e51b5dd5c10b6bef142fe36678b88d458c5483598cdad5d451e21e7117b55bd3f3b6d51a5a47c29e50629f564343120f0657c2a6f702dba7d78f11bec3a11d51a2c6abc274c00b080c1104401c0020658f80073646e0d929a0b8bad699287746ce0114c0d2fdaccb3a9918ab935c230d6c3c1b0a5e4c6b2a99e58364faa27413e0934e8a0be57a75a4bf693a0003f80b544e340c1a584a9b9ad7a4284263b30db36b2965437ae853f09539324d810685985ffd2dd0fb7d92e346475b3212bc4b59ca47c4376d7860f3b51d45e3a93d5b6e1c30e14b543709aa48785e55ca6f56909236795926ad54b261e42f8707e8dce49bda4a4e890ad9aa83625618410be574885f0e8d1c9a3194ec70ce3e8ef17328386e30adddddd310ee4404e492a70a76a4929bfb34953bd4875a75acc2414f27a5c4cf89ae8abcc64fa5ddb7ae9b0acec06e3e0af963e17f3ba81bdb0dc0dcddaa9fb8c833425e1468f6a13788f6a3b541652f955b40ce854a65afbd8c1d4a0121e1159e1d1157e111baa1c1f9e2493d2e0d6fb6be2fd4d42fef6de3caa088f36871c78b49a75aa4e95ca34e745b57860e9d47d151f36d4259b638b70a3e5bde111e4e74c67dca9f65cfdd95d9995734a32f96ff048f67565d6a9f773681d960e0fdc16e17238b7dfd4ca712d3f982c6fdcd86ac7b5ec5f57cc7d0d6e6cedd89ceae2a0e4c6884502e126d9239ad3c9617d1d0728e68abf91ea56f8e658c139bc83f068658f2421adcddef06817c0a12578b4afd2dbce8ff9bd325de13752d9b456d4e57f1eada96a2b37565e2ee05566dfeef7287bcccceedfe3e7a8cc7ba7970ce60ae1d1ce00c1d2fdd36584233b348e7e229c433a8bb32a3fbe9565d40edbdc2a5f733b95666b6a7aebd6ec91de81a63752953fc62375039fc3a8342e598c18aa1901000200b315400020100c088542a148948591aaf71e14800b7588406e5c3e9807b32489611c06316390318410420080088c8ccc900101ecc02562137ae00aa73c7103a00a5467cd4da7eb64ca3bede0adf3e1b58cb66f55d9a1b9d00710c69421c0ab99ce89f00d55c099e4f696a18ce0380e7e7a38587b2b55d120bd6865cc968e3cc4584f5a424bb58e4588392d5f3906be32e8fab3a6a9b4f868083e3ce9aa944d32c42b78fa526ae0be3326d629bf8dfac14fa580050ab8033c07fda6fd5474056bc64bb0ed79607e7323499d62dc0204766d7ed20930e8e73a40f99bd81278a1f3b934f43a68be3d313e8710e27839f6f5c57beb83c1f46129af0e31a6486f5cdcfdc86bd50c25089a6d3ded862d37cdb9c35e7d215ee4891656a30cdcc596f156f2194a309365e1963c4dbd7a79893a3b050349b00e3dafff629eaf5208a92940addafb4d17c40d5b1c13b82772fec1ed198863d2815d0b9618a43708ff23c2e98a4105d98061b24819c0f8ecd2847c43c221f1faabd98169aa0dc176f9ad460c84ab08e4b7ae35bb484d67030054a333b29848b45624f6c38bfd046ad8dd3135fc04780ea6c855dff682f4cc3b8b283c933a49196f44850c25e178bebd0f2ec387c338942e6456a6c89ffddcfe11ccb1dcc175f553a2c37573c90e22290941b08ae8399080d6cf6c9168e8d7864ff93766eb47b23adfa8d60950c0748539c664d29da68d4692628ccb65f60d05db7fdcca77836fd0f75622e58fe837704738dc5ecdc55caae58d120eff9ae172b50ebabd4d60ada6a809cfd8c1d878937a4131e52a3489642bdefb272a3ecae270e01817605be5f39a5e4b09c114f19c9c61b73dc9c6aed456f55ae191d84cf1395a37bb66655f92f4f14707690b9cbd9e9530f74a2361180e480e45dd1632f4616166b2a250f2344d254eeea81cb1cfdd5f27606a2bb4b1df3e062bf1ee0a5bf34f1b56c30d322b0b21d9f48442428962c26216394345d936fe851f27200f12025cda125ad467ea3fdd7f449980054003a0d645df662149e096f12916ebfecb6154be8cc26d0bfae1fe6cc7b95bd0eea4835181ad646842e593dc3c3b61f00939cecc840b0b248590e38a08c6944350b484362bfe66e7cc18c197e50ff157412c6f8ac3d52ce2337eaf2ba087acf4868ff2873f6cb8498babe7ddb6442add86e269e4063bb81ccdbd92a74b2ab26f32522e3b8cb50f9bed254900e5f6aab6aa71a2a1431042698799e3ce0502292be5f76555a4c97c74e6bb37ea20f2b0f070fc2f9cf13425255ed877821a83890397cf839783bbf0042c2ba49b64b36644a71727a3227a4dacc2ab2e21aedeb02b3afc2c2a24e7a28a71ddeb470e4a09e8d8d95ea510897107c9d56163b16074d6640efdbc1c57aae2f70c70f858d1cd1839e37766c58fbe92fd0810dd794829fdb26002af37a585217c92c04860b68b0458ec1ff5699f0d381d22fa6c49c888fe6b9603547683775bfaee1aa0fd812dbfdf732cf8671c4a4d2a98feda940bd0ebd635bb43bd829d421873edfe883669b804c8115ac524fdab58749df327cef7db71b0a1acdce90a5ac67957395fe8b27f42f09312c6063ff2ed0c2334ddcb5f04d85c0708fd9e08340ed2ccbdc4a5b25ff48b8799e34c07bbf04c0b2ed3fddde5c722ffcf8e525b583fc9502fe249472b65a142c7cfc5ec130a18cc5229f8e34c440dbc9e341ab4e11cb40e5c62c42a625bb18e05987ecff8124715199683a908aaa6c158bd77cc6fc618dc00464911b5e6645cd03bfdaa41a2f370982367a88596f1b7faa919176149f891b57b3de5f8965c886446c29202911c01b391b041acb8750a8ab97dc82ba518aca3981d0dd5e60c270675d4f77b3c28bc1637ac8d49a5e818c0eb5e2028982642cb5255d4d831fba44184d43dd1a19c2d52e619280d510b412a0f68c141759ed2ba590378a309b0829de9a8881b99fa7e351ae2359ee82517071844b4614b4ea5bea04c0df7a1740113ed021384322c524bfa25f202b47a99b59ce81a6d4ef3e04ee71d4eda7cf7153dcd3fe9d76cc42395a545f65595a919561377fb9c8a5ab342da16ceea4fae99da635ad76a7dd3276ef1c76bc57efacdb9f67364cdc6b4a898568c572bd760ed2b0171b1af8a09b2ce9aa9cca8d93125c958f5ba7cab572c9810a6b483a0214a41a83ddb5396ee60208d3ba7376b506bab9160e9b02d6b72e62ab99fc7324a167ca4d620ea5c7166ff8e5707190f07350884a5f335b6b048b13993b516395833ed832e18c46de0c87ce0682ac582591afb5396c2c13ffeaad83d3fc06fb6ab55783754f1dbab42c3fa43bf9c63055f258a17e6eaa0909d2714e6d64c30b8066e8c4b9ff66f44d19cf54a41f1719915b8c5fd1b2dc6bad0c420ae9b4bc9dbe5e29ea19d710ebed9b6cdd058ff41252542c6e76c23b037e021c7dac1b4025544feef064130a818fbd3d9d9e18962df020fcd20481d06e88dbd916b7093ee13ab453dfa20f49023640384f5d6c76ab3ebff07671d928be101f1d82be47a8d91f7b713d6bb74f7e30f7e9a850030b3ca1d14f896f470c465c0565311e6210ac8aa76614fbfbf455b9bc8d1ed3ed24118675ad4b871d5e7ba30827be70754adfacf3fc3faa41fb9624082d3ce3364eb650e1fdf1d2af71f5c971c38f329baa0cadfaedcaee7d92bfc755612dfc0e093f5d662ee80ed95d1a57467afb6e3d27265d37845ae8aed8fc63d907a72f6f4277af06cf29149d9dc690ebeb23499e184494f732521a7ad6d146b91729593ececadb717aaa00fc7a7ac9a50df92b2087695334397c8b0ea88a69bc6f47ddee2889ef4c8943e76b2809fd1401e4dd87a640cddd8cb0f8ee675b21c97b5386038b79d53ad1b21a0db85f4c90df75e883805e566dd0230b9ace0feb5f5b2679a10701410359ac97519bb13fe01b3cf056cbb9d73fa1d703235ce3de7b46486c074ab49b17e3844536eaefd4d196a888d07c66120c989b7ac981f65a7dc2f26929c3e428c918e92d7d023b6d713cf9df1aa40bf91f8941294007014abc944b4b420d93e9ff1e82bf34efc58a438e52aa0e6e0364d925e8373bdba4c044898bf49b854b6cfd820138f7c35b3e5a99136a9521e954cc59b5184d11740b78b0a19ceb25de891e0605be27612113a5db6a80c177d8b0389fb70d3153f1763640c6eb107e04a10619d43349fbf7ece42d3adfa7d3622118630fa30db9642acb7115d3cc64fab7639be5db92c6050a788ec048720947d570035c8cff3b19473106c136ac09f4070877486248002c7cfcd022e154c4386bc604cb32974f74e6f35a25c7ee66bc2a64a5637f73b3e7484f881abfce9897284661577f6e4f8c262a70510382838c3dca3458ff4761f776499d23f3e577fb256d7b3e37b4661d88bfcf7a1872863dbb630fb1c4ef0fa63fd5dbfbc60d4ae93a0cca8bcd796b429a056766140bd324be8ea8b22b25f858e01d2c4a2d5584a37f595bc746ad777231be2e1c26f280f4a06c0c7041ca538ed959e7ea06aa8fe67eb9ce588c2bc987dcd530ca348ce00114a77a870ee7314bc7b9921b8d396e688be69c0838d6695998b0af4a4d4388fe04c58b2368ea39ed4aad47793514381625a07e678d1f938f3401c3de4474ebf8742059d247b9ceb13c616bce8685e1d91e773354a4d9b8ae0127c44bdad813af92035830287fa27e41810d62227eeaf17b51410f0b881c7909eebd8c73d1788dc234c3ddd0ef46d55baa72fe0c88b146ed01062e6b16cb737cdbe8c6c84d008e0686698ea2be0660dc99d3db8a48f8fa657833db16a3e10d649418ea04b44160869a5209faacf014a68c22fe52c05964b16185166529463afdde998a2a53ca89edbb84051b711d9013984e56bc3d4ced3065b0ccad6337f1ec1262c2e7f4fe1fc22bef5fe718aa38d4ca672dd01e0c340562721bd0aab60260a451b8e80f01814a1e5c8327f412dc984f2408bdb360e49ec2fee956bfc6e16541ca7cf31d5cb4478675c54721bca0a0d42635886a4b112feb2ccd69ccb7646a2528d59ac543670ed0a04cc65faa16edcb199692b409dc8341d15361502aac036acaf443c620800505abd86a5487193f8e869444a5d9e3b549623558e7fab91ab18778afd79da1124480952299a2979a5c7f211f93b383f78798a7a6814b12719d8f44814d01c3924886b7e707e092200605d608569624c6939021c5e72190c9de7faa173588aa6a5dac0a38fd72f4a9d70dc003a42c9b66cc509786635670e0283420fc7bb2670c888bf467e113231ea8992e51dcc4d02d8ac58c0190631bdf45e2c7b90832d4fe26626786ee16a62af4b603c79067d813914dd8b81b5d55fe13cbb454882acab894085afa31dbf3fae0d5ebdf01c6a8e472e86184e5e8108391abf7f168f0f60aa40b719717e682c3c730aa4ef636808d54462350187b96a402f25602a442ccb6aab2cb03ddadafe5ccd7aca9aacbe551c8a24a2bbb5f5fcbaee2a8644e85fa0b7222d117cef5702ffb687b28633e9428be32aeee1caba6dc3c5b84e767fa5fc7f5ed7d245910ec4e1f4e9388c647055b288d122a3da8f7ff6a787649d0783770198be05be26fd0a6927f9f6fde2272b8aaa3d439bff9a191e777617de38d61623fd0d656e311ced655fa3c74c46da2bea12f20bf3831fae325f15a44dee59ffcbe3b723e0a6b2f09631b78fdaf1e9d86e4ace8734892afd4347fffd5218dc11cea009c6aed8fdbeae7a35755cf4ee13f1709650f3260a8245be2156db2f900efcd7b993292a0278b617a2c68c01a94a5852cec6b6b4e0c34c8d9fcb96fecaeb933b355673768b8db64ed7f5070cb654755266d9bb6fef9803c333ea9d4fb81fb42ba398a3a51a3890c2c48fce1f881f67c2117f993af76b4b2ad1105d4eaf39eaa92f1c12c79e0c1d12001e72b18a8dff185636514f5cdb9e815fbdd880b1c0f5192a1d65958469ed9a71602761b9ffb55a0f1e2d516c8c9c62cf82bab2fe0d478afdbb25bacf0bc208700ad0f5065f26e94618f4ce1b03c1e0bb6118591afff99b8de57b0a42dfbba7bbc8912f7c3311f15b5a50230348e92c0e1d028aa30e25c4a35d93baa0a3a4ee3857b7303eeb6c02882e3ee0fa0f5ccad7e0e2aac38198de7eca47b5741e578fc86801b9e8eb05e70d626992cceb79215be1e6672ebc6efbb53d37e9408120c824e50711cfbfc15ae0e91b9989b7f884aef5e1769383f6e67c06d8f5ea84738c0a3f36f2680313fca0a8311bbb048ef884ee417494e3b546efe30c0286494ed8b5d4c14a881e6600a3235c5d6c9485fb8e280a16fe710ddabad6096b43c3064b141cb8f64e40bd036196c27e1e6673e943f84da832ec601eefbaba8bb66b25aad22f41b1a834a27a0c82311f814504558511d48b9ad4ee70c296e091161c9d09fc177a75f3a7350d70690a7d17d95241328779e1198261a1199a34939558457bfba8caa2bc8631e1801468df229de11147b41ebcdb2921ec34372efd857d319eb7c4aba086ea98d46028ae460c5b6c8e5cfda471b8d352c089fe546a2e85d12b57e22c58e33a6ea6e61849cb3f82c6c91bcdc965bdfb5b1ff7e5ed3f5b07403163d10e8c03876e94755cb4c9472b27b4a0f611fdb5dd7c20e3c0ed0f285919728ab02fb68ad3eb7db454b897412957cc2ecede7cb35daec49bed8ca272608c9fd0c102a15c803bf909fc3e3e50828e73c1d3bf9ac9b0f6e889210434f279115c45720c00c1b424634f80e2f2f41db23b7a67d13cdcca8d073bd353efa612a9480588f6f03439172770737cff03a1e726cb32a62563e2e91f69bf0f8d550574390bb708e6ff3135c25baf44e7289e09ec954e3b0d750f3531064cccea243f9036976983f27347e80e44780f9421f6d69a5267d90397837c6e011c023def795e93798041d50f9b3b9c58f097dace323a92a08c6ea45d6a7e465750110056f85ee15915b7e938d879a6f0b00e28ad1de887ef72b10530b0d6259d2f06a95ec633aea9439385c51dae195d6112558c91769196875c700a65066a83f5e8a0dc800661b64ae0150e357d72ba9edcd9cfe802d3e385a209490d36f77fa45d21922475b1e1fb348fad0dee24998aa1e6a248980675d7d7428fc98c686b672926a751085b24aa0c44e46334b708d074237a1487568ca9822006dbf61d12884a4805e103f6781eac94c68784e6e4d125b02802e056aed55581d2cf58a264f5a4c4fe849b36301a2485dfe9386854afce4f037a4746bbfd12272ac8a01eeddce31cb3830748155dcb2cfe87ca0b01b86ec189e7452152e1b73b404ddfa21c150ba3531b412f1b74cb16c1c10e1a6e0d9c06c910d90dfb3bb7ccc6f4aceca2235e80557fad85bc619c985914a39d20122036ec28e6e7fcd6bd48733668ed96ecb19f040debf87c6050c591b43c426383afcf6a4b006afdc7922fe0b80f7fa50bfed6edafe9488c809e8aad8a34d0e8609bac246acb1b8e2a34af35dbc6fad598f968e584aad4296c6c1f3e6fcb7494e1c8f252eabf20f765c3aa7f0adf328768249714ca6b6a0c4767a48543d9c6b8491f088eefea43d4134c26cd6c03cfac3e22f242dd5676401182d703e25e31411e0216b9bae4a0cdbf3eb48a824f3f2a759b2e43f7293d647651488fef6e545d74d8290b6d7ead49726be493fbe138a5021b2f9dbc225ff9d585ba12555b8ee19f4c06599adb91d61fa995d7a1c8abb0849a8ea8b60aab1962ef673c86595d37dc8390131639e982d06b700259eaf65d91c4f8c8587d90dedeca54e1755942a6cae8a567ba605e2407ad06e5b59a23d7766d6a3b25b2701dfaeb775d57ff4c554a1708d76dbb906fc04414e424b9170951e7146e5423933e62eb89e84561680c9f6142deb504180af499791ddb5a7ee6ca1915413934f2a8b1eed42443af266bf670fe681344c1331857a88d5944c38af7faa42236c62aff0772a0c131c94592c4ef86f6977834ab78311c6e2bd6c67ac5a1ab5cf84b002d49678faeb605e32f1269462d58f7f6b5f92a86507b151801c463e71a9e508f17108e7c654c55c8ac754be86a784a0c25650ac492be48a23d860062d4c1f2a25291f342834c6f5808df8a3e8f46fdf5e629682d09b97c7d3724d08e8fb2c8dfab39c441dc611016e33970f1ac9b09ccf180ba1047d4d30fd4610105fe77700f5f980085ec584d810269ee3d06fce7f81095ef26622b7ef65d0961fd1ff998639365a09a2595862e69a2fb1b62374e662619f5092dcd02abc83d1dcbb86d0f0f99a0853261bb3b7baf435016f146f7e07bf9a3f35d605b409cf2cf711e2f6a1096fd5a0dab2b72e74fc1d5afce511b35c98c2c8da9a12970977e6d4a8e7d8fc5f22a691b89306e130ee1c2e517804909244d675c7acd4abe02f781d5eaaa9fa9c729b82e061bd513b04b806bbdcac1bbc856d39e205499502f7bf28d7977310a9a4c9aeab286dab83db8771e324d38b705e656d0ca04c51187731c3e590dad3eb95af4699076994575c9b4bd175c76b12738b47a66c9cdb4c3f9a66ac7ea721edfaed5bdfdd4df474e36d006c6ce3d4367b3098d9a2240829845a6a9aa54ec8f64cbc59891df66269da409faacc52b02f626da69bf1ec4e344b644428b9400e972e6e85806c3130a61208dfa5095540be8888c6f3fc4b892c4594386413b410e59383ebd173d543a2e98db82bc2ce4f2acf610aad507f7c6f92b0dd2313e342bbad8dfb71b3a4827d892ff740eacbae81f1ad01535d1ccddaf960bf6d1488a3ec18ad22b45f9fe8327212e024eed4207fa0f5cea64718fb191070598f562dc060085db967740cb0d5a26d4bd6ea370a243c974ea22ea6439dea0465141e3f05688f0e048a5b6059787fc11465553c4151720745901fd11fbf4f7599e952c2a2b73a7cffd119b3a0c283c00365d58b1c56d147b2482de5124f3f4d9152fe60da4c7aed7ad0e516bb3d65e176182a69b51da12694e9c4f6d4b16b26bdf0d56ba67f92f9760835230b862901d22bc1df129c9d10ee48978bab8c64bb5791458240b9ac5e830fc74d476d6ef98c8b9cd5f02a1aed497e7b53743ad97b73fce323b7aa4f592a3dd49767e9234fa39bbb676a80ac2257e7439c3f21eb0104e84fed2d563b6b391bce9c513a185860a6101f9d4afd2d3a8ae1bca79ebcbc0486d3673f11f369e403b1e8bb324ae41213a5350cfebc844273a1102f7a3e7161563d53f6153df035af3e47603cc759d8bd167f4823bf63a8c7aac14f82d5838f2454afb29b6ca2459e07afd1581d247397b6b00f0eb339c1499a131c87df070e78887b7a72f35f1889def40b1d1fc861c84294f5a9420b49bf659654979c86aa97332845fdf3e83da8053e5645ebe328da9f0bb5922f1b382171297ccac865c8c20fd9a220ba5a78f1e43235f6b277a47f4b0f6045b88079e5e28626ad716231e204566755ca46716582a61f0bdf9115b0a318d9e6d762f372ee2f5cdb23829740376a5417bcd22c01d3258dee163bf3702fe75fc42e27878f48a6d477fbb0f3fb64c249e4aaee9f317eed6655089fff965b842bb499b230008ef56bd62f1b2b31a0873a15ed3c1e04be31ee0c14e6227ed3b1facc0b4c2bc0317698623c75cc46acf457aed3a3400b840776460d346ffc47ea07a45e0a6b3ec49062187da726d29566ccfbf518d0894a0b3cdd99c0064b6b5aca8b2942f71fe9b87d56024f0ed1df1d73ff348b86f768dadd6de4991bd07768239d48cf4145d46fc29071553d47b2433a961d776e123d6a8b344560d7c695c1fb1bb79195a1611052c7910b1e1fc3ed10cb2728ae5199ae1a04452cab1d6af8e76b1a6cf960d842875712f33cdeb76a18ba594228e2701ad64930315437d1543e1a5701a00a6679060f3578cb567d6aa6154990348887d6ebe608a62435e82132253ce6e1e196c9b59e2612a4884415bc5d3157a96c97ad30636a89894382a7d64d1cd5ea171c4359987f3713552f49245ce5ca16e042fa50fc7db6a4e81c34f060a5e5afe8e12103b6a503f8a3a2dfb14b647ed829e16d51c14146c3782af4dbe19ac58b21b02a875d054e6b9db184657f02de52e2c00e08185690705e5c4649accb9e228001d690b22ddb4019cc65b851b509e58ed3c22ae2e34937e720e055b899bf2673f80cd10b699b785d3db75d0207cc30e244d557ae17afb29a5646f12568f73c57d2702453016262a2616b861e4aa26e5be516d99b51e41cd9e0e5d83a0d2b78fb44379b150b6d608e5f0b7b1f1aca11075170bb68aa22cebcadf9b654ec11df20e710ccd1b4340f1221045be055ab3aca9d4b54a6e11a5f6e0409a922d036c673dfba7b5e428e8af1dddfc475ded69dc133a83ef96e5d018f9f5eef16747f2a08fa81fdb088cd109fce616d09efa8182a1527436fecfdf58b932f7e2a10f7bfaa59c29c438c35af956d08e9e1b4b12cab0d959aea008f314c2f4a59ba2622b5eb43a33bd4855c14271e1274bbea44cff9e0ad0038ec2f28ecf48c083a5e94b33f21f6878192c94dc740271a8aeeaae9e86990b47906d3131076d90ff9334f02e6f6da8434f17d207de58ba14b0857eabbc31baf1a7550df646aaa780790317bd0237b16ed14ef774d0846a8031ce18cfd6fdf3126732be6d61fc53c0f4b521923a4a9ca5685cc17252c4fd35afb73ee41533fe41888bedd8e81262e7a85ca87862f5084271813f91359d005e35a86c68f4f61191e3e999cfe70932c638d74c0c2d0d27f5ce6e80f6ae455c6f96ef29d070ab22aa5c6a39cbf80a8441ab8b649d3a20045e3e1f4c0d998aa0c2f5e4430bd6212a34b19737987529e3557613a666d2300f2ccfb872030a178635b69d146bb5081298e2d273d99c726137d31ccd1104505cae38e14112432e937ae2efa8c962a6d8d28ceb61b931dd143283729509e61c8698fa63c86da10f6f72d3b065286004b9f8db5c57e4d9a3395931e91fbed26143934e5dac0730fb4379e47cec2424603fc7fe23d68d3770c1ed214ad192580059c875e143ca3bd9f53e26a42e4c2cd389424ab12ada83b25196c80c4e1e41a5f72744df4921e8de9e264df9e2fcf04ba7205d24e1d73af5d11f67d86ce86d865c8a70a2824656222b438a303e3c117ceac86e985080d45b46c4939ee6b8bbb4e21400d5299731c1f3020cce55dded194e7c3d086b0820d1819e4f501c561538de13c28052a95527caf8a5feeea7dd3f9a721c4b1d2cb5f8cad46d110b4ccf2b74d69c0a5dc18cd54802c102f921b65fc1efb1478988b26bc300abc69324dc87d148f6c842d46b3eb3e866a9f102188b74cf140219eb8b4db4021e5a5188cf272e30fccc2037a0237737f3bb23941a2255b1d1ce2b46e50539c284bb2906fb47786a4a9496946febfee6dd5dc8f77a1a004f4394891dc74e591361cfaddc17eb9b7a2a37d6ee62d530754887e902535510f5d24cc518b15ef6e47ed4942b3d6add868ab5954f5ac892a22147f0005b87c50690ab03138c718474ede6567d12a983a00ad0a56fad72c78481c80fa1a95030e398bfb8ee05881a4708e6549c21cf2ec96c50916f46faaa7a3c9e20a0c2d9a9e84dca4a8697fbc10ddef46899385ec65e22ba408c28b86d7b514c58ee833a233e53b84f305b44a8dbf737546295b27dd93fcf92ab8fe276c0f3a18a345625e11e0e1aa142a7fa86742c245aba0663025c9f18658fa179263400d4987a3a347f6a7a0c20aa94aa2f620e5c6075adfd2cadfff74f5e4c9bdffe66c81c4842d8af93c2242aceb1fa93c5f9a760af24222eaf32e05825ca12764df9cc87fc5ed12e2e1f08630e4b4c182bee26041be70a8fe0f7d4018086b9e257c6ca9dd9bfac67932400afe2f83dccbea5cf45b5578909b3fa15c82acedfcd0dfc647799b10974c68db6846c0613e7e15b4bc81af719da52b2e2c46913b3a608781b75bfa4a3c5dd1885e773ff1c5c32daf3fcfe52fd2fc415ef7a13b7b5529e726674389a874e768d43afff369887ca1bd144156b9a7f85f8c3b0e032ee42ef3fac80d7552d373243637651ed555708971bacebd5289947e789c0be0dfdc3ef9332052259a028eea9febb3d11ef5a0c19fd179771f57d700e1b9e820ce5494f86a298c2d1555034bc5337e8e5ee468b756f6032e6f8223b8f2eddcc2ecdca926320df4217baa8393eab2101fdff69102ea0324d01f245cc1b5327ac58c4592aa2c0dcc2dadc2220a5c9d25f5a21e8b182221cbdc05446496940a85d48ef7de27159dbfd08c454904d6fe510876b307ab976561e628b0266242690312101e197d9b3c079529b8d005ff072d8bf86ef063c3fafdbcc2c9f5f1466789a1031fa2b45716b5ba788f242718f5f37d5541b0b00b51bf0b1d84450a3b45d996a90ef923c6b430548a60a2a19d3f7b811e9bb830e4a2db4cc73b73f8626b4ca8d9a7fcd3386a87475df5cfed805185a56cdb7cb0b19cb56de9aaaada4e737016d8ade766ec04ea83f9d60644f9d0423cea0433c7415b1ec893241e80326c0f54fb87349b0b434e23d5b38ab4a40f61883c5b3a553bf4a2d2ad68739c31e0483aa1d15f13934b45cad20e050d7682879ec28062d82b58c8fad8f05fd11147685de03ab36b633ba7d12a6931aea847b6200010d890832a6b9804795a686b7e5035192c9f3b6adc68895b835b8f34448da50f143bdbb2e3500072a6240e386599ff8eac15165c9a179f5d91dc1dc1e90721a0c0d5603735489bbb2508709535291f56a378a40ad3195906fc7d29d667c610cc80629f1c95981c393fe8e496aa34b2151b66913e5ef0b165041790590c18dc704e926c4553c50bd90f378cef8613d4056f264139db532f1c71cc2f89a755a9c71eb0de71d26386fc6be746d2f65f14553fd8491f0791e0e26c1f0c714d3331b645ec4140e3c2b7a102517ccc26ea2562f11514d0d1a26c9cba23fbf870949b67a298f8c4e66bbd8c09c755fd110410b5fb303adc2f567ca28f4df631be02279ff6d42c1f88d7954cbe57067c180f7817ea2cb2c60b269b52bebba8ab5cf83e7a022786e09710af82f2ccf197ca86fe84df0301f307e1f9df56c193a91a6e5199d2df61d61dc652cff078690112b4c9e7f51bf03785b3247f60d429eee5e710c51b6100096881e30684a0546bd45673f7425387ac67086205e73af388f4f85843bb1363ccac34188b20cb8e7495cbf626d575325c6879efe5086063d68ce29b5f85d0a9b744a511e7497a24fcada529371bbd1307336877586582e7a064f01613fb4e56d66eda2e8d55d4691fa163dc3d8f286915ebd0bddb238897c84a2aefe602b11660b46369fd408cff16f8025f62e33ffbe02a1d7bab423821d443bce4c83da3e9699936a1271a86724828fa670a0a8e763c825a6011bc7bfba3a05c26fc4553be6912e524bc7b7aacf6d47dcebfc0fbc31b806c1c16035fd1a85eaac051455a1d47bcef7ae1cdfd7e9ec9d8340124c603a527e4ab8a7d96ad982ed8ca4076759acbac57497a73a220ba78cb68a5079f03d9dc9aee0d9ab333dba3b5548688d24fc2f1d476c83dda66bc640e6a50e71dfe7588d62412531e3d905550da321c2eb7702a019778866015857528915b8b6a682e9bfb1a0b5c8c4c9f96fe4f7ffc3eafe718590e3fe82b0fee058f0cfb3bb2606bd919f05b5aacb754a591504f8471919d6e6dc84a22539dc89fc5bc706fccfbddbbb2de5ee39f7a766ef363287874d58e993332fe20338e3b29197e03d5e0129bb78f6e5ddf4ad08d6ea83fd222fe793c94ecdcd6b377da70850995b92d97efc631620c1803150e08ca1e3203d8b3afc1c18a4d959a95216e9cf3725800188dda5ba9826768a6041dda8e45893801411a091485a39252082acfe2ec344f4aa86b619877e75463150c52a2c06d609037dc0286a3da74bc30cb2ea4311a2217f19770771f5260d9a4a4aea03775ce3810432b7ce032a3205ca902d7ba11012a15abe70089cd3dc8e60ef0a3a087e41ab1f1747151616998955fdef5aa940b0e847d7621819b01a6e8fd338b45676fe15d593d3f533dead3df18ab91b162663cd2061542837a5ff8595428aaac4ed804a92b2f4a926590f4b144f3d01ef54e3c6b7fb95c806a843b6f2401f0ce3362ed040b9ac5ba23355bc5ef745480d6158942e5ca3c6997f97695eb37aa6c6c0a7cf743a64c1809a5cf49a3ca9693ad903f1c59f41965a171319232e26938b9881ab0de9972d8965860b5fb6d3b24b7964dc751f2c07b85876ccdbe316814e2eb8124870994c2d70a2f9c602f06c098d2e86bc6a555e336c2d2645bfc72d3862e3dad1748d1043418552a24cf685841e1f99a149b121cbe4627c27800a921840163e606d68802174b31aeaf2598cf302a1ac007e42591583ca3a52670054097ce20b9e6cc7865c789d6498dceb80b786fe5a25b4215572557cfea369cb276854fa20f47ee8bf566d8653f07d6c9b3d2891db9ec155c6135886865e580a34f5881ff6a19c5875f8cf3418fc2775639060b5472bd3c1f07d923a83e396407229c3d561948dba2c3e94a0b7f0c14885bebad52e7c10151474a4e2d0b157906ca1c4d2108c8f76ad9002b876a8c1ee3fffb0b902daaa4ab68357fcbcd1c840b4c67403a78d07f400171b548411da2e8e36bb23c93c06066089b214c3aa3e5797d9545c0036b8a6293c1c5fa29934bc661b692f8c6353a1e524835e87a4e2d834015bd24e62a7e50a5c05d14693873dc1f890c361287876f6bc936561c2e29b192c956f3d72af1d32d6d38504df1a2dbb3391b59f23500b2603c869cec10b74e50276caf948e3c3666201e8f4ce29be5807b86754c76e6674ef284ddcd1a393a402284fd3f8e844d4bd2af7f64ab899cf1c2ab7b58df854abf849dc2ec118b8ada1859bc5a5a0e28d636b83eb8f5a7cf46af5b74139b269189f3200e029605fc7071153cd88c7b20463e96ca1efe2480fbf0375a8595ad604dde6413de5077d37470b8e969c327a5759132681f12890e158a8bfdbffd42bc2d2a637d36b20083b9aef43a8d9acf5ce8f78dcdebdd07483428af52d9cfe90027ca6948ecde4f2ca4dae4b8bcc5c52032f59e2cba58c24403a4437ca57865098ab7c9a064ed831ce7e4c5ac33b5131ddaab44e3d8e6715f23a6d601c8db17164deb8a7d9e809f8216caf139f7aabc5facc6b08f2d568b44f84d5f1d1130699b37de5397d493823ddc1c8b7550d27fabf3a480e42f4a625098148d973c9b6bbcf350274b10188c396563e0caaf3c5eba6d44b0a892120f679b88400b923de027a77f9f7372b758a5cc309d8fd5e58909f49b8d2870cfb93d358a71f12c96a48427a6fe8c085e64d04b6d207fd54b7413c039075a8bb7dd78fa9b8c3312e0fe7158d29229761e58a103061eec6df5e07c7f1a25d3c7815bd0ad4e14746b201e60a02ed656bce9433b49f0eb7a0c18edad64213f5d12b9fe5b580417707a637b2a806dd2161f4d77b3db2d7ae9f6df970a8fd8a78dd84fa9afb0f312c65dc95ef0eac7e526156998c4f1ef4f4f6625ba38fe0dd6a603c63f07cff632cb6a29f693a741c19d969c76984d0a2120d0097e4817bf3230eef13b300dff81db9f23a78c41e52f97cab4506ec4be38d2a47130a2163b4639b716c836926c84d6588e5110d0b28a597c784480a0f5e0f946f647fe2cf1d250412675202e13b01f0f813a6a23cde528ed50133290a99b81601078c0d1d454e6f560b5c95344d4ec95be51b60547e184c84de36d08bef6d63ef31a18a579b0be45e5fbc524fcfa6130dd173c218d9b59e8f097e1dbdd81efe5741dbfdc2329b3d78e2798cad84c07be3199c89b06e04c6ec27b79d979f5ffcff3e3eaa5790de05f71351b3402a7fb01462cc1616515339a7b1a61b6c4b79dd14dedbba4f8d16bd08ffeb732d3c0d3eebade3f5d25afba43710379991906d391a910b598dd0fb8ae545a64804565ca02e2473fce61089c6baf1ed5b1ffe70b2fc270d9cd7d37fd38939e6c351e48102bfab3b395d141ef5a63c8587b4b979d8ce4bbd06ca0a81575bbf5a52b7f0cc3f105bbaa84c25671e0fd43e50456a1b3991e2eeda12bbe7e746487f6d32616558f0d892b344687a5db3d600dd1b8050b3c5aba9b28b9c6ebb5656e9004bc0cd9dee81f56b14559102d62fce0c7f25fd6e41df05996c465ec00f4e4eed35beec27e3e8a1a18f218621db9b08c6fbd7933d1986e1cb97e1c50a98327b90d08fbdd1afc7cec764a34ec866121a08483bd178af11c747c37348e61d1f6d8502b4fbf8280fac00e72c2211b6067b8c5fad3dbee8eb73d66fdeb30b8ad9388e787a842cd903019b25bc406a382ff1b86424a845ce438e66d77f37a0be80021a1c22b2ffae60ba80f106381f057ceea72b7b058ffab2c05169144d9594f04c2f0ad6cb1b9f5ef463d37fa88be00f21ac1926b67d4014a6111a0561008ebf4dc8e7767c159dfd7c4fc6bc0971e85eba605d362ab21585adf24b462c4917f689bedf4954ceb05bb858174d5295597e0bad44c879a2e706a080fe0fb2c25fce7af2d5efcb0216f103fd4d71911b1ac10babc1774c7b4d326e5dc8ef3e8722d4a8856956f407cd283cb8c2b4c590310eef4e01901e4723e97e623c6d84ca00760a002f9b3c08406ce566cabb7fd602e1fb091ab75718ddece22481f8b9680483b61a1a518d89d5dad2be51add4c979c0be31d93d3370612bb959a1a081e95e06ee263c86cfaddbf8a38d4ce989c64132e588e953b240f2c455920a5aa1406a1851dec42402dbaaf7b45ea3b3c954ef018c6f1fe54e580cef31d6e9f93f72b4b00918ea688d9cc93f6bd42f70ecbbb49663917d411779a07b71034202a113372ba8cb4d2c0b80a94569a3fad08fc6dbeecb17ed7a8201eae762ab83d9b7ebe7895f4f40137f80af8f00af3ad61eb4ac277e2002bcecb30eeda5abb0444400ed6317d552e0fec61a82205b6353f01a2b71c8ed4b6bd8ace186ed97c745173afcaea1f27657b28e894b89e73b6b67337efc61e54d0e9d9a3cce46166b35453a704c9fa55bee46d0e0dc2b571709a98a7ba6000c80e62b5281b78391b2082e37e3905d82b06c026f72c25e8d9874731bb301b2dff60310b29401dcfb81012a9e30c233f1943814b90cf5fc50adbe4f7f3e36b49f0eed79d105728008ace0d2a700a4a06a7e0ced6d8d12c3899b2e25485bfac89062b5e0c034bb76814915c53a3f5c9dc17221e5566ab047d512374e34f47ff6640d07921940126f0d68fa0549b59ea8d3641f7f1ecba06fa9ac0d920b1e76e175e50f8e5f4486f28a35fb1d91303df43bb045cfe1ce5d6377478769b5b01adaea042e1c2e54804196c418794f0767453cc0aceb4b63538c1da1754a82cfa015d73b99ddb256254a518fada7223499950f244c718200ccd6fc1d23c396b0c975f7bd6fa975df9759a9c02cdaae39eb3b9d1d5e40e0227a8cd2272e1541abb871f5d50f906ba9a48dfee2641b08d3b0f244212255d0bc6e137b911874ce28f27fa4c17c3b851112701b7f32522078ed4471dc27e92f35601d677b89fa4ea8e9c06f5193e1830c4c0ffb43a83511268ec3f2be51717a9e0e383c93a824fcb63091fc92f0601c4af6a3239fe7b30152988a86befb0b909ec173da97bdb6a8cf27e212dccf80c7415190513ea7a00da4d7aa575222badbcd2bdb199612c24b351b78be7492a4bcef17f84ba5be83a94373bc2f59f865e390039268fed1e0ba0c50d65bf3ace85b005202906da79c2aeac29022c8ec2f935e054892a6a91d2acf39c0bed8f476af8f87bfa674af517b345dd7b8ecfe727048867e944f68f5c9fa8443a850f93ca079eac0ddf13df11382a368a3fa5fb2da4486cef15f16f045fc0cd9c22984a33103ce56fbea729efc6679eeb7e85bc9dc25f48de58ff24057b319ffa2db6efb930da5e28582edb3a771e99df66325cbe90fd00bd5ca12789cfc1d8e8c400ebf2b2af74879960347f46791a72490acca44ad119af99a47bc4b02ae86a878de0bf036b17a69936ed77a47ba4aa5ddec477fff46da332041ff1d02a315b368db90bab1c235a4bf513d562d0f1627d318f4ab3c62f808da58a558840376ede86df34aedff1fd081ff980556b71c14edac6e5bca560978e5ae16d9bc7ce7042336356aa71b824c5ee2ca106b9868abb1b9e3f39b9e796ec05a27a3ad377ee04942cd64a2abdf7b8e33b5cfccb2419833c6290c6df3bf2452aa4e426c87e9bc11e389ddec676bf1c98d26679eea2ace7d3c74dd33cc7fb36e81579d9563e2262d7e14d4cc60818d564eda381521e55dc70981cc8d7a5b981c6385d2dd5c59869658984d368f4095befe6088f489b5a7a82b4002ce750a507fe758353e958680e5975c901434509b813c336774bd220740196f7a65ba5bc759397b1871c6f547286968915eb90e2ddba3d0ec6519305b942e4e16bd925eb8a2398d8f00b5d602a5bfec6d2a1b50303092263e23fbe3f0d2c0ad9c39426239f60ca27610e9b0aca2460ef842ee72f4371e96f7574a338d54bbede4b8bcb97f2b9cd737b0a4b39bf2197fe4e897c4a80a4aa9d814074ce3d419a2620fbce2c8810166011d1264523fd4b618630e60fc4d84b96f5071816f17316596dbbd949615cfbb0c142d4bc32b4525dcf62d632b62e044b492a77e7c5541e22593d5ce7154361c5602488a519ed15fd700eb3e8ed0156878774bd27530095fc98e46b226e1778a2f28aaf430cc149a44bbf374bed30bae81d7e0bf349403aafe58c22008ce067e4b2714d10f95027662e9b1932fc972dea43e5142e64d5d2b94b034a919645a18c4d0348369c3efe7086a76866d227cd7382ccadc08aa9f5d86a251296d92a4241a2812f011c919c9497094e42f960c0dba5cf0f6817f37d88afe9cd6e7aa350b3cf9b3a099cefe88d37434d875cfa87c2ec86f4fe145b0c9a470a5ff735a4ec865f2b580f178f51dfd054d430b680aa11f9c5a11dacaa07ef971c114f42dad8e9639348f70a88994d4fa2507847ffea030088cab89e6d8f5f31835ec40b781b84d61a819a846cb641791328abc980dd5d4fcb9f687cb0df91a9e3b01819164694f40ea02f6cb023989b6de30cfc9c63cbacc4dae4f5ad4ef289a6c8632736c0b3a82c49ab87eb6129b47654311bb06161b3fe53c12d679b07811f98a7d984e7fba0f20f8bced8d5d964f4aa804e6f43f7a8f29b33520f55cf3fba1a61751303adfade4ef51917d701d7070f65a90beca38be32393beb90ce71f0cda466603d601f0d143e1eacc8770705cc4b84b4134713e6b1d48174871a9ad384b9a0ac0fc52d0361bd19a3ac3b1453534eedcd26b974d1bbcba94ecc5b1d0051619ec5e4d62b325aec532d18ccb22e689acf21852c12898c45ff306cd91881f5b3fd56cb9db26df7f872f890e4c928eb5077a0bd0469e339f50578dcd1b771118369dce3efad948eb4174d93b2d3f603a3016aeb3cff69a0152beec80260cde047b3bc522c6dffb4583ea3fbbb9cc42f859db67fea8d7a0dcc4b7f83d21de6dc4c322a7e276ba7eda8eb6053eb37a974dabec406191bf0a8612d8c8e10a6dbf7ea929d1e5d10b0d356fb9ff5a68aa9a901a75fbd467cdf2640496d81da8c6b2d0196b8f20156deafdb2238b0702b1285d91a062ff7577c61e717fbb3e04ff72ee3517cf22bb4dd9238b3f09364e7323576b7270ca4cc10bd40352778b9adea81d4be041f8ffecba5d3cbbcc85593baa98eb2306cd81d1e30a6b7d8f5898e3aed429debb3d6f90118b8d758af58db3b2affb08d0dcd01541f3601527e1c8539e66256a74de71c408f764e83903ae001733fd935116867252bccb39e7dd1d2ce8dc0d9ea51f209dc560ffa70dd2a6653dc1c54372838f292da3f1651b635faa9d467527323b7b7e0de097ba02a6ad8982af8495c11407f03a58d5f4a5eddc88ea798f736ceb17d3d5de4fdaddacbc13fad717ccd5da1935a6f99441d24ef38c73c3d12fdf45c871cf6c68f8532101f3ac77419ea3e004226bcde5ff05604606dc7c1cd81c2a8392bd055f60aecde4fb6134b678b0b08a10a36099f4a03d50e5e243e8b07ad409f439d571e287b67b94183c8d105df7acf18766849e021bedd89382255fa2541f67b636db517ec05bdd1d1a47af627f4d4622fb18adfc88cbbc835557253663542f295e006788e0db77c7e95888fc07d9c58f63da098f70029c8085fb7c02b118568af9f5c69c9e96e1d66044a7c31627ead3796621c8e9555f4725eed13c924ed17196c43b5232078e5b4808f15c9dc78d13051671f70e6d88350ee015550ba93ff7648f2efc36ef500160920bf009b12655f6051a5061645a1f8ae4d7e6915177ea4d143e452c3d3d485889707a0a86452be417a5fe447f4f404b0b4248ec2912738df13b5d52a452d0801a88cd3d3c0c309f836dc22edc2145289ba31055d6fc2ee8324fcc4864c45ebfa3fde42484d665501533db52fc48bfcd5b3ccbb424fae030e2c12e73ba1803de8be5b1a9cb9bdd6f8de2a4f937432e6b45942530e8ebb84073b4392b90e5c1429d75640259e7aaa9d3bdbac5bc419a050ca72d9d0e05ae4826966c535f76208c2153c70db828037ccf720f756a2a76fd40fe68c7f55a45b03cd4e128261e60bf6f7203521e5a651d937d215a275d7de733975934024800be91d05cc4f3313593c2db3e33149774118e0cecd8ee7fd0c432b9c2ab76849e56d7729f3b63d5ae384284225eb03e6da906b53c605004a97e983a28c781718b35b4ca9a53577a92cc0d2bb42601196b76ee4ffe45a16c7724964d0700410af9d8f838b2c0ad2cb52190586cf1619cf5855047903bbd519ea985b855bfa8dcce444f71c8e6fa24764010202b9c5cf3432457d1475cf124af8f635c10be4152ccf9171af2463da00816e0ee261a1ef2f511b8955f39b808f8a9eabe60e9164a12045949a8e865dc8a8fadb40542ef685dc10126450c9702009195c42eaa43434f78356d7a61f91cc6f9e53b8d45e2c3a77a9b58b067aa69018486ed0f9dede21ad221a342b1c64969fc67124d600bbcffe8c8a1b66b113ddd173ec9444d3cb11ea85793c435d7daf3acdbf135f34d51bed882814c29a538f82ed5ffff063834007eb045f231c83a2543e1d2c70e23924d5c234126891c959e1335b53ba80cba50afbff22d2ff0c628333bdb15729f23daf6793b2bfa8011542fc4da4a2944ff5bd6b12762ad2a3c9b998a0f628cb4c595e387c2c06291daf15eab5eca9403479218664d74b2d78099b79dc329d0e32cd76396404f238c235501113dba1eec009ff4bc5914e2b06e65e6d6c306442b1ea8849522e70e0f4ed0151b8524614eb0cb3b3ade4bc5f7a8fa1184aecde17752ce97e9955f206e2f30628298edf8f69b692e8054c14b34d2e759365e0e058c6e51d96716e3edf9b6c3b295bf203d4e1a9e01cd9a7dffc119bb02a21400a082fef9ac72b2fb129549e043113594ee5353c0149c10008854018f13cec1ad25f0d5e5e3a1082c93a916dc827cc42f165c45ad6745b072d9b255b06e1da63df4ad2867e59609315b9321857a97d2bd2b25d80326f356022c6b81291c65b26688fc0206b3ab43145c4db7c3bb210028d2904a0a1b9f90fd8b086987d91cce4c6ef7ee04183b66cce213ff575512dc47fc3a571b56f531fe95753221359dc1b580bb83368fa40002a2b3559b54cf9e5fd36e3d0f9affd99afb21b8d9236b4dd6e47f6ab2781837697ee4023fd7e8be6fcb788b02338cfeffbc2ce914e263239a1224704ec4b46293905a748cc3f21057f83d95dccfe908e0a4cc06398854b2c5f0a1fa478b5b3d7c05b6d7ba4235eff7313f38a37970a6c65917ce82ecdef423d29fb88e90ba660cfe056d42d38666a057131d4109ce1eff00c3442329a9018f73125cc21d5a9370aa8d7852a075215a7da371569e1430d18df42e8efa755df7db1822dc19cc369210eda084e0ea6a0cc8bc1e4350ab5feeb0cc751e797f874544e6d09c17ca8ab2e15b241ceefdf7599a1760c9f26bfaf399cef1b902495eb8ccfe1929b5019170f165720410d6834d369ccd3455e746ffe74a2e6b2cf229c161f3663241ecb90bfda2d904ec43ccbf6e91519ac463d4bef157b0020a42470168ef99f77762906bc41c7c6778cd2279f442a1b7315516eaac5082b5afa6b567ddc50ba53427f815dba12db643851058d617dd649d6391294ad822a7be7606620289426f412edcc6c9eb928afd4d8f2c175bf9cde8fd6c9d0c504c29e39c6d087d95c81bb46917a35169d3505d129f6c0f4d94474418678718bc7c1b49209f3b93ed18a6a7237ae7cae6f74a298dc8c8b605160ba0e6f5bd394279a7e8b533c87a6954cc8c7f58d2e2a93bb71f3697da289cae46e3cebcc5de222b00f2a56aa0cf4da44388342b4718acfd1582913f9dcbed08a62b86b3cbacc6e3131e8875ad2bfb8c5e3605ac984f95c9f684535b91b9778c8342d66a94b6c18fbda44b84141bc71c5cdd15899893881a9350c67dfc6297eac067a99a94970a953231a7a7c88a234ccafabdb42d4c1e7aea07d553f844e20754e577822a66cccf396df93c41fd39eb53c3e2e952521480c95e955ca234160d00f94007a179a113d816e899e4083a267a0bfa17c15fa333410ba197a10ba191a0c3d60a77f72818a7d72838a3db9b88afd1bfdaae8267450f4273456f496a2cfcc05127be6aa12fb77fb2af4676828f468b6ffe50215734547d57d80bac373bf53ec4d327761b3296ff68be2e6040fc8e781c63d9d19585787dee711adef8c345f793ff163b38d32942d8256f13202601c65488e39235ab8feea708c13ff18bda5fb3e3fc1e2e3cbfd9c096767de664b644a2270a40c47d8b9490c4568837cd7cfa388509e1ac2ea149a005dd4cd1ddc194f0c1675129eb9d8f9d2226c08f8a50511261139688a5310fb353938a5d8e0adbed8c484747848d15b0a3ea48399b0908c4e6cb1879c5664c0eef81db240db17235b4b7e09c8c7fbbaa6383d4a681938662df7eeffa29eb6b7224c7bbc8ffbb1261da07840d269a91ab5e7f9b9f753d0822d8c7fb1a4c0b5af30495eadcf246fc800ca1b81855560f12ab078a9a9acb53f6598ad25e13080fee638a5600da006a081af0b0834bf6d11dbf4350ba8d6e99562a104dc9243e0ab16fea1343d9fa48ae0d90800b7f1188a4c08a46090402db3cd3b70fe897e38d83fdfd1080ff26cbe83607a610d6a31a229c638d1585520a68c059a19ed84f573f39f371a8379e2ee6e1666682025000c2aceb5f5bfa0962129c8f4010ec16b873457620989d23002cfab59fc1cac70c6c905631fc748704cd8b6ff92f542155833dbc039475d42b3da8333b3bf1d70e1e10c90cdf5b7ddebe099514ac28746de433d19cd61a9b4fe7a1bd2b1fe5598f1f7b5a200b82b3009374503a13f21dac07b8ae6c139a26a4fa62013796c9fed56e9283a1022596efd983bd0da47ec525d42d7dbefbca03479dec2e1730e9fddea9a4f6f5eba5ca4b42df521b51f0c3354cd7e889864cf1a5b0ed9b1567b421d0b7c06edc4bc6ac116cce0fcd68448dbcbc5648fe3ee95999635534543d2fc67993df8c0f0ca3c2df43d998c9f72989f9fc453169e5ccbf37f53e4a011acb13290a68c039a09b042458c08311100daf8185da0c386e935d88789ac5ce8bb3ba0b0ca4ea7886494d618c14bbc4e2c5513b95f51878fe0e49586a11f60b9bb013bf5014c67c87a652fead9be89279997faec80adfafb1e0aab13236b474ece92a8245084c3084a1e32999041f3121b7e50fd80b3b93a56ca472b3f7bd8a2866b375ba58663175d0adbe549e6f5c27aaae24488890e2b0263c218a0bd29b7e0b767f7d229d5e7ef64ed5a4d1c52a123ee138af40553d92dbd2d5270c37d0faecc0dbc29f59a1f483fb38f2be97e22e4f9928fb0e264a116b4550d6f7e6e9f7cebffa921ddac839d9979d52a643401636e8a98ae178f63c1c1c116ef0cb615b73775fdcca99211cd757fc312ad5b5dc6d29e988bc71f4e6ccf6fe0a6184f50c848c37a7c9e7ace20c16d03bb9319fc7690885a0b2d1d8323d815568611a4bd79a35354b850f5e0590fec429aa305a6465ba059e9bb46321b05b207a806f1e94d48b6f2d6b868f2f06f9e7601cd750fa225d954b9983c8df417dd9f795bcb6d03c5133a5797331a021e6892c6e462a972ec420a6420442d55ee0c8349c17b0b531607e5e734eb968b591bf21d0e43a41f9c54d1960a73629cacd636f89ea2837c667664622c0f36d22a023294ead2ea31281d901910e1dbfb04530b586324a944da76cbc37c4fb53f65dea70508bb7b29e8527df04f3cb35a48a45a8c7c706407ae1b2591c1a4182a2a07dd43672841c4864fe6204684858a78eded04581d46dc1a6baa43bcfcbff78e1dba680eee92e6e5d23b04cc4964d792675f700ce2f44d30d8226e638112fc3187cf725da723687bd5187f124642f02462b09f2ca4ff3c5024aaa2f16cdc998a4c58347f991b06ecc7cc4587c73abdda61e88c6bd14622b5c302c7f3bd1de205ac7488f2497ad16a22dfb448f04a82c2579aa970112f5437d7bfc0ed5ba9e278d962b1a6f61edcd260302a538a1b8e72a9395cbf476ee5a84e040b7db16ea98ae60b6bcaef9620d5f085f48c3975efd6de58fef1796193f96f47ff3efdfe38bcacc7140ac7ccb689ef329b0896d19fe3e2d3bcfd530b8228582626a8d240ed9eeb43e880727be9163286da3520193a5af63de148023d2817b34d9b012bf5ecf8b69982250fd4986f955a255f9112be105943227fc3be5f69826be1651c20dcd5ef5f4140a14b8346d22d26af6edf882fcc5336e32572544cc5716720f90a67f7dd1ad50c34a09c1b88cb31d0d77202ca84285fa72440e58413dc7cf61fd7638416863c30549a171aa2b9dca94dd88563abb430140d5427854fb91e6d8848292097a071b4afdb33bffa17563e5860b9ba3e37ccfb91756f964e04474fb7ae48fc27b8c889ae5808a415db7a631b7a5c62b876830f4d15e618f45c092c5c30a3ee46056433f12144a5c39eb346c4b64905a1d0d491d4d491cb8aa45878f08d65561851d3eb4d2ced0eb39a007b8d3375ab7adf47c1868560a9d44137463c29df81784e1567a22e0ef09cd25c2c1451098717364be39fdc60a3124a8778fd700105d578a0b6bace3a2a61dc3986e8303a22841a8191a6f261d8d8f53a9a75286aef24a6bbc851ea8624f6ba320e54dc024087097ceda58577d6e6ad3fff03992d4f1099ba9af27843e2a055b90bc1976740e9c42ff17312d522e1c48121ba2315a8151aaf1c0e723ed68461c021efc403095e91a66b424e25780e817b9f81424f8447bed092775117dc7a9d616006000742431e645d7f95fd65e24103956d8f9f932c552d7977afb90b596c477709a3ed124727785bad0b0cea5966518ee1d3ade9eee8e8a463a540258c29d32147a603c2923485ce3e9689b3fb358fa9d980484955952602e96269bcbb8bebdf126fd33ef027eb0ed9f0a0076d4cd5df110b228bf9f1e9f4c0521507ea3db8c0bc224f885b68d889cd2d1a0d0c9db89aa29b697845f422ca287479808a91f76d281f19d3b851bfba0bae218566b1cd145173228b35cd0d31718a1d6e1e56d037da563870c97c24b9231bc1e8b896864a5efb9d6e05ca11e3782b58ed79b8df1910cc350382cce51b7f946818521348a807c42d1bffa4cd8f5f75081d3aa5cb57a9dcfa295cbd0855cfd65189e50d343d1bfb092dd5df2ff4ebcddec8143ca5386cf83f46b18b25450c49c81db0b634dc07ba003de5d6d022489ab6a24cb8ff81c5cca886885db569863b6208207b3ffbc16a96ea3a50d84f003e09be5c28ae60d632b2691ab0f8c59a3a2c7a609afbc7dc261afca8b6f12d94274def3b3f3347218517d818c68197c4770758f4b3924bb8c5904526606c23af07eb0ce3d170a380966659abad4834b3deb72f26c6812c8d60339597160544ccd5f3183bf6489eec28abfbf23bdc2792cda4c0bc05767c00286039d33a3e1f03a17472368f3c202134eb07b59c4c5f414baa704106f797760d2581314ffd26f1c7dda8525c0339d240632b07cac804df4d3edd3ffd0f6ab85025f39697abdbcaf94cc577bb383ec5b6c43c527e762f22017d14120f14a714326b10b48dc03c34797aa70e8bf84fcf62f91232f202b4f23d374b3f710b0c378929d562a8ecc5433e55ff3a0d22f4e07ad1269b643d29659b734b4737bc52ee626faa574a130ae0521b0dcfe1e48d5df97772723ff57ce240318a0fb8ec695a32819410f53603cd5a6e348c021d81e3161958fb31e4c54268f87051c9c9c19871c917bd8348ea0e8ea41981c56ffebc8730f141ba1bd56c778aa5880737c83b0792f48e238635ce160c04df56151bd5e4f27a641c11d0b37ffa422e3c789cf2ff56eed24e13f579f91fba2ea7fe6a2ac79154daade768714de98050cfe87d5e0214f417a83edb1927e8e1964a575ec1273705595e66119ee5e92bd217bfd8cb21e2751131f25680f5d03d5a23a89000bed80db91bee64711e92c53414452cb89bfa026cc44d4df3f82491bdba5cc762ed3da1e4ea10a75c2328fc138abb6115a2b3a6135cdc287e14357eebe6c71114ca69cd1e3180ebd67fbc92c9bfb7f2a7d69e34d969bb768c2e889bcee403e48623607e4deb24c907de231a2f9df6e01f6b1138fd8d2b8d755b1f23ee0a4376e37b98c093624e3992ebc9528007b2396680c09c537e245a4086d58a8d85d05f46607ab00add5ff859eca2e4004a3f94f12097248065bdc3fcf5216a93f160b3edcbfe0206a15522d59512f3e2b1d7e2d632d7023256cfae88d7b46d7b55b2ac5bd3dcf4788bb6e015bafdc5fd03c6886dd859468adccda38afa03b04a09fdcb11ce882163a99a66e491979f0033a1a0f8009767af81b8bab5f703901fb7077a2671d36daaacb5d4f8251387c5ac5676545d1bfdbfdbbfe94b136c4e88c34f7f71970ab3161d4354fb67eff85c452f08a9e9c73f88ce46aa3b1ead4ad09993416ebd9773b5ac42893b62dfffd1250b49afb189c482a1a7bfe84d7f4694342b687eebe1f9895144416a303716881645e1fc2620f53e2518a2600ac111c3ab7eedd12438eae424e850ff186bb0a8196c849eb5a526e923a310e5713b202e448ad20f4ba64ae1c12d168e8e52b1de92e1e6d35e6526f96f9f88c040c4c73f04564b57702c986bd394ddb6e718eb980bc78c891be607b93ae07b2dcf1842d5714628d6615b698509dabff82cd9a6e69e4dc2f26a893619593f6df3ee2aa4a2f203488b6e8242d7edbbd3cb171f2f3a9c2a8d852d7c5b16d4c88fd430771b2357850ca30c913360f5ab887fa24b1736fe7c2b61a219a97cd11eeacf57ee61d30db956870131f2e03186547417f9eafd69f51c88e882240183aff28838e318c46157dd0b9e4cc7ec07f359e3ff36a33ea3a3964326b357ffc39cb3a5c80b4465d93938dd2cb0f61d045045c025bc00f5e089522da5835b513887631167ebaa50f826c93b3c9b1a51da2c2123adc24d8d39a2419d0b18b2026e8c7f6d47ae809b1a73a4aefaa4ad60a41efa20380eac6c8be0566d39c56ceb4d652471ffb13386919b9b75a94ce190ab436528bc018148aaf2e09683f831343e7ef4bf1fbb22bba019a60e2e469fb6df50531a0d39d1a01a34abcd42a23391a606274ba88b88e2e9b76575113187971530c51a5418b1424716f0406f05b01538b17db599c9ab43ca155b13eb6049ef61aeafdcef6db6fc2d805e81f58cb0c9c655dd2839222e8949b9f175e08c9cc0f76fac92a146173e75b831f58ef5e0e664848a6174b9f90c9b85cb0ff5ab8db549beede657421485cd4a4a0a85822ac45ad5f185536be9c3a94121346b3eaff78aa62091b242754de579a805ca4a5e4d9c212d4e2294916e68c50130e7d00976b8b04ecf3256ff03ace4c5603e06e9da2ac1349d629764822a09c932a14440fa3af8b57207ce57221adcb52eb137fdd84793216ea582c45cf4080d812e74a3a0986c7bc9acd1d7344f08b52fe28b4a01cd8d9da4e154c864406c6fa9587a26e7f4b9ceb82fca064ac807112b0422a278f66db9ba44c4c13a9c3630f07b1f411dea5817f5001d0880b26f2ff33f911d04de8b3a200a18701a5079ffa56481abdd943d3071e54b2397c88176836f8f8460d60aa8f25c87dd4214f7039aec26985a20d03a4e075517ab8d3043898c5e74ec103e3ff36a3b3098ff4d9851f5bece81d227038fac7a38b3b5b90000dc121a75df84127104b15c875146c4832e1d4e160863b446c258ec75173f792575f493ca6f7b77db724b295392326d0a3409ef08755699434374c690cc201a044d119a42484a93090a1308854e8603c070a36186a40320e0c66e88f1820b08f8a1b5d65aab035898400bdecf7791a46024498b4729a400fad84f418504d05a779c251289427c803c4354f854007decab90802494d66b395b53d808aed45dc751a976fc27a1b55628a89824491205acd09e021400faf82b80aeb0005afd72b6da5a93c0f365ddb6c8125521aaad6aa93d382453bcd4ade245f5de2c81c769cb402df6dff0028f0e74bb92f781a6197c42d1a46a6ad850d9dcb8c1c19193e357a18e1d1ff0e8c1f201c2f7010f09a48788edf8204e1d7edea0d281946664f91290ae69933db20c235d56c44e14f1c93751750d98cccfd80b92eec558a5fa17c54e1a3bc9a6099d4f6469c96124d90a205da62bb294ff05b8a2a2935af682e62203f249777d76cee0acf6114e984ce22c0a9eaf8f9d1d2288f049d9a32353d5cebb0bfcee6507ae58d0d1018978e6421786c032e8c070b33a4eab8e0e0002f08d20160a215d2d9b46f8dc1481bd00aceee7ee3f77c02338fba53b200abe338200de79469c1d469cd50fae4018b203f671590268e570f719e3f118ab95e372f17c2b2139c8e90da0e746e847cf8d20413e21dff785338684ebfc10224408014850899ee7d952c7d11f201080041b0ec2f541822c6176279b5569e7a6042dbbcef90089d011583b3b6209250cc92f07b4ab934c3082e427090b920a550f0640cef24369eba44efa218aa28d472c663f4b99254f5bf6dbe26457741443c20ac54a108bc562319b188fff0f328dd712409f12c013269d4baccf2d0144c14b78a10a5000134ede8387092698e04304e3a8785c1f49c4044ea488890811225d9123060069cb649d64b34d5bd713ca8c11b159a893669019a565427452386324b8754e813283cc28f38aad8a0d8ac524a01f1839f249d933637fb1151f1cf92e36c0376f2019a074c2091d122428dcd88d3144140e50855243313fc027dd7f7a08f89c22000108f849a116355119b91b705f5f28258be3b89b3999d8e3e707f4b9ff53779efb71a665ce6af0933d2b8a73b0a58ea3b5c74de153e1f329c566053cc02334531554502101493a015f02401ffa0948f229a065b671c6a6908d09845a94d2e2b4cd20338a0472a5281309618bf1e8e1452b24790e82322da658803fbb87685830f0f4a41775d37f49e656abb1856e354e1e327c97e45ec950f30d1f3081d0e756a3ac8d3464bad33fe6528cab90ace5fc588370fc58a154890318a491fb53dd3a7973316aaf6bb7eddec2886eaefb76c740873c4ea13925cfa12a25efe6e28836757fcec50cda6bb04d4221e8db33dd6367396a1a2d4b1ebb06e6f463db1a29778fe92420182d298f1e947bf49894dc3e45e6445dac208f5e94dbb3d4fce8b32db99d8bd3dce6450d79f49b277999dc2365020b8a59c92315ca9d87aae4a6572851eea74d675eec208ff428956f7ea4b4dcb40bce8f148c531bcf174fa0e4b0822066607b75993cd2a42f9ac89549ee9c70bc5f3cc9389e766b47c6f137a00f0e55b7c6eb8551c6c1f91ba00fcecddb803e3737bc80e51baf027d6e4c228200ca366f03f4b1517191555f03f45195b28daf017d6c0820d7e802875ce353a04f8d9c5cf334a04f4d8af6829c7a14e8934a659a3f813e343e328a1644463d067d50a79f017d4eb88c8cdf04fa605994671e047d667e6413174c64d37fa08fa9d5403ee097bfefef0df267148311e4eff3bcef401f6f8b5cda2205b9d475cf813e1d0cf23dcaf783298292107a71dc57d087b35c60b5d62218a8c087ba7b0be52539b32605b331c6345f7006832934b9ffd4ddd90719401193f2a50645e820cb1247a0b8f46b3e9e307002c6400b1a98308622c33ef4cbbe25bb164b647f998518d9ff037ddc05595ce1d3dd9a2a972a7a456ae92209303aa8cd6008297240851724efe22231c0a398e7f4a159053995dd0b43089cdc7f412cb2c0a126f77334483772bf055b50c12cc4a3a0118eb2e47e0a6eb1038b1078810552ee77af7693c7ef0545e47eda462f4823776bc1c51758fc61e402297237be37530feebd3b215851450840382348c78a2d78f051a3fdb0228c282c2f3710ac28f3440453f331db81871e628058332174e031064c9c9560870f6810eb316382871d299597201e332c4d748cf1c1ac053c8465c876cc683dacc2c074ccc880fd18b6705603207204adb47cc0490e12d16b79c2030ea45b0e2d23d801c706cbd152a5879b1a0e2d2b8072e38c221c2d5834b10123bbd142cb414506ec86962f4f6c1cd918694123a846112f45aa1b74a82902c6060f3aa452aaaf5f23ca139a30866a92e80155835baa054e049d6e319a166889612418aa055e42305303a2530bd008c254060cef000524433653440e5f1935930972f0cc90814de4500ac3f64db143378323ef8a2d3adc2f42a52bb880c28921d45d61c6132b66e85e5126875a240c8ddb82430eb448cd6e09f222b65add32821dbac8ed886ea9f2647661912fb32d5138c12a1974899570c337ce972964624ca105870d53d0b07c01620a1a90608aa421f005c2410377b08fe2494e962372f45024050e11609b2c8d8ab4c8712b32e3a5c0a62c8db0d840064bd0103847964658a4bc152c2bc8e3bf008b1659b078c9b10406b334c232e6478053355c8171b234a2e2071c55a8582247188cb334a2a22806541cdda82043092a763084511fc8918267449073064e9520070b30ea891c5e70b785097cb7c83992418e14b8e4250c4ccdb852450df2f853f097a5511538b8218b0d59ec12f4051546497c11451e7fa723c108381c419b10e5f958a57a0093395f082e3369f6c3a00b438a943ea97fa527bc94346e128c1040b2c806a715d70a9e409f7b0a1ea9cf85a3bf053ef046f980d7d452af4fb99498449132d5d1b07f29bbd36aad0d5276767676a6a494562a9c49650244a9fdee55365ccbf27f5c3b12cbd0ac8e617afb9147d8861320908b7e47ee481f2fed9c62f7ae4056cb952fe4fa08983807ec2fa4f2fc98a0d56c3fddf7deeeeebb33bf2370bf37bde3e54269ebb464543ffec8d2260bac7220591a85016414450c287b335fb7925ab0fddc21cea234dc71d6f4a1695032497914d3c8230f0ef2286737c8a3a48dc96860800c2b4851965842288dd7b4417e7202296cd0e549920ca8bc4e595c61fb410c9a172a5e1df8418a1c90a901d0131c5ef387b86b09131044c1841969a4f1c414af15c6fc0074a4a50c1164bce6b7e0ae0bfc30451a48559ec0c189d77c18dce537c092c30eb2b8408889d7f4a662a898244f74d16207464e8c31469431b52c74008418b01a40406f2fa0c00c3e70a484182db280426b20c9342181110d0a31585c11a2da5a435c650ce8199302118931a2882aba3062c78a184550a8ec80091939d0000723242a280c8cbec0628444825ccad208298603e3b245092b88d07204450a420a5996464f24651e591a45c125db7fe126fbca40e9c52fc8395f95abbb872d84495a9c48602d80f9140a1e67963cb39caeccb2844e6e8b5f8b68e0fef76af2651113245fc9a21b8a8e7ca8ef7f73b3825da102b5386df63a6b3e7696b3b8e0bfdd6e0d34a5cc200d345d046052f4ea9f41f205412f79a3827c3191ce9a37373ffe2ac897872bf853f130f78f308334d08bd40c2c5f2c20be0401f492af25398431abcdb0101a27ac85fc0bcdee389ca5f55a8ea32bce6768d16f9a78261e5e90200c0134e14842abc69fc231041b8f25ed7190af3c23498e61231479d408c79d106a5a29a19f99e4acfb437a22b578e7cdd6e2bda97cefa74e5f7b30ce0a89e1dea178cc5d58f6f4c8dcf74df6d4c8dc93cc711cf77865c4597d6b917b1ab6ad45eeb91f67b5cd1d7ce9269b1a4e3deac779ab79f9e25ebaeb86d2fc0aa7af093d80fa132afc7116f7a7701e398b7b0fa442e92c1cde09d29f48f3e794996b20268b7beee20662e2138aa651600a475597a089c81c0d0f5408275bcb49b039abb95a8bdc77d8b416b9471981b9f7e6099fc2190bb9e7fe95d0cc12a50e074ac113d636c9e32cce94bdfeb8ef20242d723ff373037d4d98ec699bd489b913d96aa4d944b63a8c14b9e764110fcfce8e28feab5053b89e5dc664f9ec306dbb21e2b8afdfafcad2679e52c74be6de8b019c79d3908e80744d98b7207be873ffe32ec9b1f2288fc4b41952877bd3aac34820dc83abb6491deebf5523b5c8fd8439f75cdf9cc53d3794392a45649547c91ceb8630558723768e730273fd5dba55e3de042348e40e2eb400aaeb13144fee185d267dac9cf19071b97134da99b382f0984ad130b067574a8ac8fa3e3ddb826b2d8269b18243e05126cd50c4ae9470a0b3129a3ba9537b966bf5afdfb376993bf1e48e1125cbdd7b5b8abaf7271d91bbc0efdebbf720d953d375b57b70f5e32c77d262e7410e6b119504a64febde7b028f0ebb7990bb4cdfa964f7331cbd88eb482c9147772277f8d663ba0c100beebaa17f6ac15d3734c90cc67f0a69303d7e1ac09ff925af8e3ecdcbd7ccea868637299c50bf027e9ad003338f1f15e2900567c997e79fe5fac8651f39387fda789901df03333ed39b4856f7920567753f539b365e30f8a6bf6f53c3f867fe5efa71e07cfc1e308dd7e7867e12fca677f0868629e0077f8599308639e94f95cdd8c530bdc9bd0ebb0eeb21d983c4edf0a97b6875bf431ac0507456f7dd6db17b997e6ec02ff0bdeb6a90aceebb9a0aa2a89e3459c9177dc9d02d195a2c022475baf796c0487e1c8ac78898aceefd47fc7312315d1e649ac1a79fb1998289024cc37196e024f2c061085d6bba4c2109799cb51f87398b76dd53941178945b86244a5653386f33e9769de72854969ec364758fc1c03360609392e94ea44ef774e53b489dee67cd5dcec36475df3911b9fb0ec779cbdd4b772072f71e8ed396bbc760e09a1c03553233073a5319e22c2a3cceea7a88756beca11eea56129f9f1be80beca44eed1c2675baef3ce6acee3b77e24da4d87d57248b7878767644f17fd672f70da6d02f0f9ad2b1d0adae89d4e968e830a2ee9174abbffb9f247247175a0025a3ad029353e5cb19389ac035683872e0824701068bcaf8d2448e2cbea0208717b894a5d197299fe50b9437c117980ef8860638cec0388e7248806b3cf150e01b591a7911c241c238b234f27243147c638b02631a0e19e06f0b0a6a584a5043c10d6a2310aae18043033139c4601b5ee48802d718e1d0f0e9ca53c1780a951944119ac10e4718ef0047104e85c1d1034e6d210446559183258a9c22c0b0720230aa84018611385de02f4b2330448003033082c080c920c6839492babbbb7fe01e848b3589d37e90b4289f524a4dd0e1ee4edddddddddda95f4a57ee94525a69adb55213de524aa9a434fc7196a465ea4e3d07e734071fe1eebe537174eeee52de784e6fcc6c6103aa4c22a5366624ade16e1384bb5321eed4ddab3b3db9bb4b49290d8dbbd3ebeed4dddd5dbabbbbbb3b75a7eeee7ebb25e58c0d777777779fd9628bbb1f818fc047e0f9d6dd7d02ee93de94dca5ecdc714c4a6f0e75f71ceeee4eed7b5d19a126d050a4eeeeeebdc3dde90e9fee7495e574771ea615ac50010f29a5fc1c180b1b2b23ad22cb518291f8db21a50eaa9af28d94526a3f09ca703492a50d123c2f579462777fee4e13504a29f59ac346ca1c29dd717c14874a79236f502aa58dfbf591524abf9d16e5bbdbc0354e744aaf9959a2c0f451a5261c21676196343ee79c7336f53967db0481a7dd4129a5b27dbaa36abe13a51fb6f1cd6c4129a53654b09b289512bc316fb7a494f2a9d32aa5a4725249c122354b6bed47a9941ea5b384a3cbf9a4bc39bedbe2f45086cef22d409b93d27e525659ddfda33a3e7c02ea3b593aa51fad734e7a29a51f4c1b2a97ebc1a3c7911a2ced2d29d8a284e5a80e49e029031dc8f9a83e7676886069e9411774904107864e8aa00674748258597cea24cb86029ddda13c04c162b5725c42b3fee8b9112488909eb3a711aef383b3b472f676f4077884824000126c3808d547a597b3542e674760edec58ae52cbdd1fa2287a0f1070951a312795d9b367cf9e22e0f1f55aae2475bc0d64ff594227756a8903b9fa74b68417aa000530e1e49ca5f57256c8723e447cce2a4ea488c92f572f67290fdcbd1c0f3cd40fcc3040e984133a241e12147cce1b5f44140ec0513f80a59ca595b3f7de1b52cb899ca5f5729608cb95ba5ba5587bd83c2127adb45ece56a9f47276462bad97b3552abd9c9dd59ffe7304672bad1cc7892c9d1ee28e08b6d65a6bad3d64358c74d9244e56c44e14f1c9db262a9ba4a4aec662b11a45d658b5d505d526b9ab565b6db5d5565b6db5f54a05e2030400d424db586305905656069e6fad7581b555b44b5811a8574b69b5dcff83da6879cced56862655264040d5c90fb0c22a0c003be89600d0199d512ef4881ea9ba3569e52e750283d1a07e2054e8071a44abd0213a744526838288280b2da22308a0d62a8474595bad1056006efb6eb91f002015af95866a653c090020a52d17adb5d65a7da85ee4b3dcafe3442be7e2477ed485e6b97868adb5d61a7399cb78727cca005cc885aa0c0da087d2954bf1588b22f09e201fc57128411e1444c88d903b718709b9d16508d0499de44c1cc8094002b5f56608242060db6c63fa460209a08f7d124a18529b4b1fd90471d5565b6fed1202d8341a985aad8421363a4b17156df901f6cc1b92a9577b395b6bf5f00748845e01412683a2899a2867d0877b15c0044bbd5aee5a6badadb5569b4d9ae24348a84a0fd91e88d70bf4e10a60824fa55e2d77adb5d6569bbb6aad434811aa44c4643299acc6a23414d1c30eeaa096d2b18e553af3aa990039e98699801ae373bb95494a22426bad15ccacd5906c458c605aafe56c9d5be66c729947475d68b42246666c92c8525464e488090a22222346401fee8d1c3901895b6b2f676dadd5d65a69adb556eedaa419a45ecbd96aab0fd5561b9419a446b9526ba561f5615e91c94ea0b5d6b1d37bd7e3b37fe0bd5704339216ddfd4f356f0a41f10b6feaaf5c61ea62b2834dc3b0ef795fa6d65afb4dab618a0f756bbeb539b27df73ecbd96b3b697f48df60c1d67d7697162dad33bd912518d8665a87e18e4d6bf02685faf457a84f4326309e17f838dc579391ed8b91ed7bc9f657a06f0a99344d8a601f87fb02c3ee42b2c2489625635f72e1a882a591adfd991233c110484b7396cdd2b3b08b5ab46f51409d1537c8e0d4e9e7cf90668fa1fb19d64802cb8cf31e4ea9ba75fafedf71fa06c7e45186cea7067d4c8f42a150a84779fd9d56338f97e42943cd33a19d404053aa5b32b7b5f282e1b7caf7a5ce0d39bb7a6195e7fbf457f5a1311838ccb2cef69133cf5075c3e3979b0beee6e786f9aa7f837dd54763e6687ae5f9218a3bb2c766fa23f0744bd5224dfdf07f773199affaf47d25b6283fd3ce27a983c517678eba0c2f47465581478a5f1ec38c610e40f2f0e013b6f1d42cb359d4227d5412781e59e15d4e488b1cf7813e5efd2ae853ad955287fbd2e56069c43033f72357e30a0c9b41b20727df9f501e77e920d808f0f8e58bc2c1054f8039d8bda5eeaaf084cd2177dd9faa22e9a570c2a4ce7d1b2fd8c24adf95ee77e184b578b18c7341966ecd769c55335d22532470545ea7143a85a44c8074d9508666d1afa16c160d29052275a0fc008660478a10c5a1df4445060614111e670cc8946e8d1f9b52e69003913d3fb586b407150c2a5c349b33c352ec029f93d2a27d70156ba75356cbf6c73b6dd97e0e787e5f116d3decf794b69d77956cdf5738dfa295b9e02ed3db8741fa9b5673e62cfbd349bb2652b364dedb2c42848c30c20fecad3a4673570f396b8b6cb594a945b63a8a14ed5b59248b787876764407c38692ed43f9b9a15f18a402f6805de8d6678b81c9b26f2fb61898ae1672967d2b6d7fc371e2c003ec16f39809c1146bd9d94fc79c657fe264db43f6cc9506665642b38d4aead88fcd264e6c4e9cefdaf781197739e72b392275fca792d98318e6a781c79bea1d64ff1db2a7fa8dba23def048b3ff11088e327dd364011e7b0809296db20b2fc0d02defe9f7bcdd49da783fa477b448a5bc2fbf456a7bce396b902e1176c609d42bf1932e1baeaed236567da55926903af447474202a10f82a08ff56805c06faa5c984542b9569b453d8b6abec1748be66983a83ffd0c2383a0420e5c47c954a0822973d7f432a734378f2617dc6ade2617d34b0d7c70d5df200d0d083e87aa09511c2a9cb55943d998616a3c453dada91152b1f1a79f3629c4468d8d1f4aad7080fc5e8c4bf49b06b4810a658b334f7d9a91e973ddaccd23ee4bab18661e7732c5e1ccccd377400bdd9a095f34853b4698e611c7bb749c251b74dec6cc5b0f7569892199df32a5ff7d69c5c23c423581674dd6adbed2ad51da8864cfec2c5a0b188e8d25d32f1cdb892f07f8880517dc6543de5b0a4bdf2b1b44ce42428a5586868686bec352b82375680fe1643ad4fdd7620f35d150f7c8f229a51f70a16865b9abacae64fa43dc8f3dd44bc82a4c64dab2c9ad58d9ae62e080d8df2175fabbd5b7d8a3ca26f7ffd3d3d15c42d32a2982b2f71ee8e1f8bc55f64aef7d2775996ed1af2e247bbaf7de837edcf144af746309dc5ffab99a493fb8936e797873a474d32f9764ef86486725d97b07aaefbdc3dabbefbde7795e0ab7e87d8806f6a8fcc860fbdeb737927bdfde0e9bf7a3f57e7aef9ef7ee799f3d8f882080bcc7f1fe5141f68b9e1dc99e30d36f2edde2fe3ead60ba657320d963ddd5d1bf1cd7b31fa898be5b3101bb2f75a5f7fe7b6fd6740d2e39f8e919771a734a3add66a71b02dad4b3a6f5ccae9ad6b5b6ad7a2645a0ff751699bed75b64faa5e6a2b5481618077fc0edb5eb170a969e7e9716bd52cf702bd1a694fa946e0d790dd295a459ed45ba66ad87eca151467f429b8c983098892630150c4ee9dc557afab294a493348bb664f595603295485074696f0adc5dba69dd1a25974c2badb7f451cf9abe5f67d1da0ca9f99a3567519ad4a14f6ddda5bb90227dcab34369d36a5d731655d13a4b6a9bb516297d941128a033ba39d45dd4458d45b23a55ee76b27b9158e677f7bba74ebad53d85d2ad21d943898a648f8edc3d0dbb2e729745eeb4d0d2eaaebeb9ba2dbdfb22a9d3fd0d163c3a51cf2bcdea1e8adc755f0abbaeab9ec5e13018b8c6f77df23927920196953b7d0d2dd8ab60112aa7ff5aec5e1651327bc03df81fb0ff31c1df83dfe3705f4c70f817131cf00b7c93068a749122b7c58e870cd3ee7be5472d767f5a39cd59dde395134911ba9f712cb97bd34aca008c6e94bb07adc8dd7f2b7742b2ba9fcea5c5ee3bcb13dcb32e027fe8229954c5653ed4fd888772f75d943b2f889cd57d07d462f71eced062f7a313e5eecb5df729dc5ef3fe5060670dcfba357e5bba6553cda51b4c237dd87ec9b64cb6d6afb468894297b568ed7f64f0f8c98a68803265d22dfaee45ba54cea2c99e1e99bed764cf0e5ac30e191743dfb6edf75419dc011efb96696a90ac2e2cd1840e74a3025cf333a7de893e1aa2a7f9bef7da49b75041b287e6f414ffccf7131904871cb899c669f090124a0cc55a94d222ed9f398bbe6321dbb180f9cc5ddd44b2e8539ad38f7d5ad17c83a8f7e6c7239f62d863b946184a0f870de3a1ea273208076eb6f1f4db4943b1115251fdcc779014a27afa35549f5a61a7c91ecf14049d46933dd3699e34a9a40e9d422edd9d0675a2b87bee69fb40ff362c7b1e7d96e98afb2e530ab95b8fa92e64fbdd9cf5244ef651c3c01d9d45c8124804cc1d4e21a943ff260adc30a7d16e95c2300aabc1046ed0071e32fcd3e340981254491d4a69c33215fa719ab3e837931629cc815aa4a94cdf81f209f7698b94524aa958caf6559225e5d734403438603070cd0483ebd72eba58f55988b4904844ceaa3ea5c5fa43ba2607ec432ed462adff91c1475dc3f78cde9512488392cef7158f3c57313490c6926778a54ecdf397f89c27a17c855abc50f4bddfb2fb5dc3946e0d75959974bf81644f4dbeb74c8bf7febccfc22af544df3bef4d3960022edc114668f176cda77e943416eedf7bef1791ef774d0894efa7c2d9e4a392fa213d93dc3565aaef3d119c29bd7497d00c6791d4b9ef2d8193ba956ab14cb71ca672d70326ebfec5a61f55a71f45d48f3b343ff2e407ccfba90f79f459b621571283ebd386c11ad6b0ef6deaed3b16d9a251a448e4ae54e8b7f155a670fc0186311bf7d68437fb85f47341db2e563d2ddfac5093f26a561eb0a1e66b3c0d35c29b1aa1076ad050f335420cfed484777e35ab252f09fafc9ce0e6fa2de5b76cf1d01c9a43736886a42423d7c32555f7978bd9b71797c211ab5e25da9bc7fc964481288c06d198ec99415240597e17816d3121fb3e64df65f69dc84d4d738498bd196badb5f7de8b461554ce48e5f9dc1d6365082fcfb7773e9e36dc4a797ec541154336d03b1f5bc1e5cbf33d8905a63cbfefc5d87bb56af1ce39a9e8a29639bf2f8899b32115351caf925b0e8208024a901369bca60dd9c351953b1c7f47b750f94e530b3c9cc7f1e4a494d2d9d46950a6efc91eeaa1212b3e87845de65dc8166d224598bb4aa1d7de3e775ffe9081f77e187b2ad54d183c36ad83700e9b41b8d06b13480eb2c786fe45ead00f294cead09218ecdf34c959f4efc558a5faa4d96c369bcd66b17b5595e7525b79eec5aabf62e690f0e83559cd764ba24014267b644b5ef4bdc85df405f4a2ef47f413e3f98b33888747c213a44effe823f711109016a2972c8d907f8cdf171e91a5d01af18255dc1579ba678001950596799437ea030ceef2171199676478147fa4b3c6478900dff873ce71dc75ee4be18d87255aa5489d5abf5bd504512b0a084c77847e98dc90b649d5622582b990362cdd307dff3ca42b7696ccbf4c19840358a440ca36596291e228072965f97206913af6c7c6138875577757d859f668867978b254b94b74964dc15f5df8780bdb4f5aaa01e6c26f0baeaf6a71ca9fee87b4e5acb39c355f82a52270373503a99dd8ffe6501e55398c61869159c9ec812d5ad10485b9d1158839b3e6c1288c2cd73ceaa8756bcc9911756bbc379b2bdd721d744b1ad980b2bf0e30a38e313650647faf8227d874d4dc255ffedefd86b49719a106a314bb0923281c738e8254a8af9c99b36e6ab87fd451138364cf7cff1b35ee6344dc4a07cd596270dc6c8ac69c990e20a99333cb9939cb75d4747891ae4ef64c1d64b2eb1093fd471d61b277a18eab52e9a88535283524945d7c12d3511bca2e06499d2af0a8a3967d6523ab39b35ae9e7cc72b6e41ce524e560d2ae2a94c30509a6a3960326c79673cb299343cbfe954e1db5225bb7461b990e303a90e6aa3fa748f6ccec8fc38f64b2a77bc49bb757c63a918f25fb8fd4892c6bfe5db6657f69c59c81ad818becfe58649f734ec999461ab2921cb9dbc08cc1864d63312942331ba02964d8ae10156d19b2dd90c6d8108491358ac8100262c758c331b07cf9376ad08cd4f1f799fd6b1559b76a7037ff7aa55bd2480c2ed96bcd22460e32583446cc2b51f6d935d6ad71a64a711ee4b14ec97e83c7153c237bfaab119edc21e77bbdf2a48e7f2781f8749f23140808082877d849d12748573e32f78a03cd358882d79e6eea6fff7189cfcc32d37066ffb4c0b1826305c70a8e151c2b385670acd8a0a0221c8e151c2b19c70a9e2faf3723b30489d070b440b667a484aa117386637d92fbe5d71a64ab0a55a0b9aac9f4e92a06223eeeee1d6dfaccafd1041e4db5dc3f57375df0d87d8f265aee57327b30a4e7a5d23d950bd2ae8bdc5fe7ac2035d53a5aa864f6a03bba49e2afd2cf4c251ca33be2bebf06c91e8bc3bfba5b9fc896c9d6627f9bcc1829962377d1dc8b314d776433998185494ceeee56232b516badb5873cce1409d1fcab54183b8d981490ec11d28891fd299aa37b31a639721f2aa54fe2af1225aab02a001a8edd915c5da9333f258481f044809212c4e996e9ec2ca4cb8466d1377509c3c50bbd21c3fe93ba001eb9ab3e15c2e32c5a9429750fc73e3a7297f474a35738cefb2b4ee4509426d02c8ee3b83199dbf12d72e384f9aa6d30ee39aff11b32987b1976970c2e7dfdf17e9bac89852b25ede688b5bf2483aa1b2e59d0c79b73ce69ed6a49f6aff45d62a1452e3c92cab2c57973ee5ea798c34103d38a671c870d34d3b009f30e9ab00663e33a8ee3be6fb7061a23e676bbdd6e37dadb6c76dff13bbae5a62366c029d8b6970ae62aa66b7faddfabae7d645446422fea6652a733c263cfc020e5fab2d3d2bd4f9d2f4125271491b9d6ef2f1ad8a3de146c1f8381bb0bfefdd26a3ae8e399c21b23329bbe88cca65f927b357e6e21bb694557df4b105c2dc9be92a1e62f7cc00472bf5734e45bffaeb82d7256dddbef164599aea611157d8e95a955cd14f55055f0fc0912a91fb6512961ad4a05b8c85780ea5aabd35a6badb5d65a6bad95565bb97a6badb5d65a6badb5d65a6bfd4a69fd5bb95adfd6ea4d81bb4bd71b25f04fadb5d65a6b9da17eadf56bfd5abfd65a6badb5d65a6badb5d65a6b450169d1b2d934fe79fe852a2fbc97a6ac60fa23cd7f9b566a2fbdb4a3253a6bde9782beef82c0bf41a6e782acaa62224c220f5a08dff56294d249e9dfd7a5d3f6c9d6ecd2c9d6e4e1d9d911e99d77d6e4118db68c073f6123423e4f282f449f41ce9a41b267d2a40e9427b235bb90a23fcf8f3bee2d7400fce2cd3ef2e4034c7c0a81c78fd1b904237b0cb28b3c503b219c785a74d29ce5535af4a1b085fc3d1944eb9681cd25e1d13e65d40915de64f05d669cc914e9e6ee9a42b7642bfef9dc1c63b2e873df851e188e6f0a477126149a791c4ea131a6ab6524bc291c5b5f38e60ce1e08c9c3c4a1a6d862d49eba1e930071abaa859439d9e8ed02e1a9a45658f7d2ab3081132c20838d7691b923a4de40006c85623216955750a5153f00c9d2cd0f620b03d209e887e679935142d524ae4b1805fbee8c4337f0a5938bd69fad8d4f0293c999ec90db307af139cfe8517a8c0e6eb8626a2827b88d6dcb5436dcc1a7d7a7187b4e94741da45cea28f7a301c49f868d39af76889769496a046e491da09216ccd9104f0a70a157b68d672ec4c293da66f2d52a943ffb46a272416232b726b912c5b0fbc64faf46752e70cece1148a813a60ffee955844aea40e9df9a50e7d22518ab5527f4a805330f7f7e2c8f75dc6ddbfdcbdf7fe5ce1b46802dc3b909dd46564cf7c158e3c7612de215fff40beefcdd59c92c571f7729fe440b22727df7726382dae544d78b5c6d24b1d285f8fbb96b60d3b28bcb7afd75d0e9452475461bee8537c1fec01cf58033590b33a49ea045dd9ea3252bcad2623c5fb976747bc49aa69b3c9fba3e5e92467dddb7d77d1bff7beb88312ea56e0c91d52ceccfccc8f63a7b5c3096506a657a8740c196c72d229220100004000e315000030100c088562a12c0f933cd36a0f14800d769e4a6a5899ccc35110e420649021861800000080004360866a4a1b00a52bfc0ae355071c08c03c4842e2fd3f6fd982c04150d95de1f8e592df0ffad3948cdc69483c300d979698a56e6ee7fd1107f4ab03ae709fdaaca246dcaa45287290b536f41b64b9fe91f076e9f7017e4badbc7dc9bc0d5cb1d8ed1740eeca15b96624ecf8c11b3275662fef299a218efda80d821fea72b34ce201a446428803cc01e39361ba00912cdbe168e48c4ec4210fb4d6109a2c3dd699aa0af569f6d33258106e5b8fe042ca4bd3c008cba0876942d63129c14c15a9f8bec41de2ab4e60c8d9a41c4faa4de03ac58e546486fe57fc4b815ecc408e1d6887c2769326cbf8b85880ce599e25c247046c6c6a91be89b99bc87468b2e6d711223583fc43975c6117c1de222245720a3ffd695cf0841110a2ab2a41732de6a1fa995529ea8991f821c598ef9530c36a8f569aa0fc335b38362806259c5adcd4220da58ecd53d6c03f804b9792faa896bef1e9cbae2dc021bdfe668260c16a7ea63a362c4e8821570eaf7aaf3967810092e9e185ae3b2c9eb2b9c0ddb918a8405b569870a9d361b73990f892e3b93369e3b5744569977342db9737f1437fa82b8d89423861d0459e5f2ce19cc56848fd0561f58560c39233be6ed4dc1b37ddd1fde5d072c4da4b5baee039395658ad53897ab31e9b7bc70147332647bbb9794610261e5ff68666ba77c0e0269da6d3698f4d7d35d128d5e395b11629a087e624ae0c59d8ca085b40f6b83c6cbc6f41ce27715925508a840e23051b1e95aa584a9442cedaa3b0b597e115c3b2f73384a2e4ee24bf08afb748da54e98cc551933bc478049a5edc4af108ba4f6540ee8d460f1cdcb28c589c65c4a6419e57d2452e2bb93dee17344ddf4f1358574109a18622c98ee2f52d0acf85e3ea0efbc291128f20267647228d3e55d8b56e9307afa7f81680a49e61b3880dd0c8702b129fa099feee548590a78dc21289f75e5fc3b47902ef21c158f222cbcb19e12f302e0ae071d0390e80da9247261fbea3a78ac31268228cdeeadb9e482b68467e8853c8a4b3ac57eb3893533fb2574dfd3d4f8ae8dd07a0abafa946ddcfa45bd2f2f567605974b016d0a9556d2aa75e3f96cdbf663ca8e9532e59d09ad11bfb849df3f73cb6b7ef8a86d3b0824b4ab9ca19c6c1a7fa9e1de2f16ad8d415baddac043b224d38f2f0a715f2ef24172d499770d3ef6a8dc46be9d5638ae2a3909d4da40d09d8621d54ebdb46cf1bda1049041064fba4a184c6cc0af35071d32a88f5c94e7086ea1bcbabf740d22dc3d9799df2d0b6ffb32cf590d32d3c1d6a5312e05b5d9468000ea6a3553d49b3ca5dabc46c55e87a33432d8b150f94b904083eb8e90275aeaa350edd53e28635cc9ae6522c26eac8c00b82e1784af10f64cf36f9b1ecaeaf842100b81aae3b4c88438792e2136ab014db3e8b602de6be58ca1e795ff1faed8b887933300b34f0d729bd6e550382dbf729367a8619751ff5f92b3b45b3823b832097d4d9ac015e92db6295550041b080c90be8f2bea6a85f6abc13190e217352be390d4b5ba4260fa9b54734ea9964830e75b3aca1ffae2b297563b208307a04c5a94222dc60403c233662bd721929ed55134524de9825b6c36789f5f227c4ebcf3cb51fde524f3100a9006967db472915ffd8f01172369450b021695557dcc019ec36a5bd51a840c0d492db2b9f692a6eb3a6d522ac79bf5b0cd34af1ee05f5f86df80405bdd93a05b39d951ea5272a8015e2a235020ec004783e8b4549d72f902b410db6eaf4eea3942d15aa6c88aece9bf956019f32b386a56faf7d5ec203443cbd9a41d89d0e1f6af9f2b7b64b020caddc610cf03c68d4ee91b7e10517cda527129660e3931fa9321283abd32998409c4a54420db8c2f9ae9893b46f85ccdbc459f7b0e4d5d187fad310f30544bfde6c33fd9c829340041e9d2da287296f65eb39128208e6625aac81ada5d8255721eaaff717e8d28f63068469cf0b37180dd1b593093b5f80458e010f46a5cd7c204a10c7b06e10b33029f5ef462b5fd2162e3a3cfd4b2990a140fd443795183f90b16e89773efdb29d4817a841c848e553ab9f69c3d2a070b88b32068978301cc3f3cff577520a20c056d028d096689d2fa84d51c46a8636f9bc5959805c490a0f4b997159796c7f0a1b6f9a4bd1ad9b0b54813eb2d1071cf7d8221ffcac029c4e0398e003e821229b82aaa31719e6b20e114c5bc470470fc7f7ff03441320578b8103f50f0c208d1f52fbb26de78c395318e0a40039ec86cc89ff71b2a20e3f47a91bc92d08f9431afe628c43ed108eeff4e31aa5373e9ade4416cd86784a71cad902bf02c8fa86abd810183aae3a420b690342588d4aed63dd244cb97177b173891e1c23fc009fd5f8ffd979ce770e79c43360f0eccdc1a69cce084fab330c5f1ffab9b614dd36a91356484dadb3acf1bae74f09761a5de6711c5ca43ec219c58f0268056559a298a607091ed5108243163ff5cf95d6902996e64b173bb0d1aca8fa5869f60eda80f2d8945630473975d9eeb960261d543097c0ed2f3bd5e9b363e4487f8a4d4bfb889e349cfcd72019cae1be4721cbde682e2be2ec73b5f6530bcb2a84ecd26ab0b36b3175bd3bd4e441a0321fb3429b7a6d868978f0f439134a5f551566cd6ee0e885d9b331aa0d2bc4a95a78f9ef2a233e3644d80cdfe6633f1e4c45bb5b18f505225c4328f3959674ae87aba2c94cc963d2f69c40ee9ee1265ecf5114652a18eb4731540eeaa2133712c1a0112532d52c4f10f42491c6182c20c68bbe7642cdae63c56f2b9a8326235d353dd7c363c18c2dae6f048837eb107f471098aae0e1b8fa41d95ef64a82bbc4a9a1828f1df2bad12c606c6acbf76b0940fec98e309b74b866b662f07d0a0e203a4a97b4d9ca16fc492e52a4c67a415d6d07b2a09dcfca4279983be60d9186ba2890d8080974732bd52e5f909283c254e7c88033d9a351b238ac7c1b00b85696920c5513fc7112806df0623b53fcfe5e77a717119b34f51cca28b15e03a62d14bd5e9aecd2f16efb912f119905f23f57ccac73815e3015bd678d750460ef461cc647bd7c9141890ab81ed6ef1a6dbd20db4b6d8b09762a73b3d7bc3c13189881b700942589cdd083642d233873abe8602de47d379206380c0703b81e715787f3798e102466503c7d083c976059360b04d864cd9543caa0f7d44a97a95bc5ad95543c005d1104d5357b725b6aa3688b00b532b75faefbe8c60f15510aa570f078a50d0598081f7df01e11a0eaac89860295836e31d82c21159edbb60d0c95de6bf34d1ac53d2fba50b5bc845bde77120eff0698aef6503ebf1014c936d5af8cb0c29a151cb33ca8956bf77e59f0567748a2c42fc9aed9902ad2d2c04b672ae9d0ce90ad17dd10c8bae5ea07988a5faed64f4fb61d5af633fc070ec65c01b2572d11b27f4168b9fd6b22c0a4c83802d0c84b62033b419069840721f1a7da5fa8220d3c94540733be6be6426ef15d43ee69487632d9880bac970dda8e85dfba7f1b358dfa0bb5a23c2cc71b2a3347868d8f49b7cbed6ea8b5a8f0517003514b6ab1b8ffd4432db23584e83fb8122a287175acc4413c97f5a812e4500bf3e555a5625f5e05518b834ab4a98d1d10b2fd3948d9f6638ac429d9fb6819413612cd35b9fb8b15d3c14a3bc0ef835e17f0ae142d5232e4a85cb6dbe21f539dfb40d85a9980f75f0cf13a240aa67a2124e329ca6abeee694a51dc1324f3ea04739b82040c7ea6293bfef85d0bd9cc198127d399720d9be02f491837cf34c29f5d721d29e98ee48a50dbdae0cd81d7a7b3904f2671735b5d408b2b7871dfd7e162796e8e1c37c1933a28d4475be4362e0d01f6c20f5380d50edebd1a792c852aa7883514b41813ae6768fd1efe3fd86fe4cdc2351710302abf7131e47df2c382e00412e8c60b3cf2f50ba13adb862eff581a5b2fcb10b7f6beb5715c53dc9fddf352d2f3f12792bb97341fd324894c71808350c0ef0f44c0b732e788c7b2eb98e7dcc9778cd2df7e3ba06fbc2fd7de7790366118a2789c607b300c99169111d0816a2629e5655914add96420c04c412fc45a9df983b60a8ac93770fd9036d62f0ca9dc0ed7db11dffdfca6b9bfb5538612fcec6f4143cd5a0d814c3f5fc81abc551ca5119bc432d38622201270e1b438f4754bad179e4b53afca5388c7dd60b5934358ae0107636a6b544c7ef83f9cdddeb512020b0bd646b96f413fc7e0e9cb0604b93e2d0be3a8c6186cbe6be23d1553554100d52f96464828ce8a05de011b2b663e58603be0920d89ba98c4c90b9b596b571fb4d159ca828595bac85d163aaf53cbec5cca8b35ef8a40efb658d0cb5c621fe5f2fd9d14fac52699415744212aa2a0b27900c3a0c179dcfcd1c1532166629c85c3c81798bdc92173e8a1210365e55a3892f9636e34cd103d157efb8a0ef5a2b7f2f6a71dc78c6297000c877f391406241cdb133bcb28b24dce2fc943655b80233ea6c2feb54517dc5a011c720c47df296e538a699705e045f2167f06ce6a722859058264f65924409bb213a24681dc52893c4f55393ebc1799d0bea605000c1f33cf000223cf8f06c8057ccc81ffe388f7f58e68ab96b8d915c61fb7ed638612dda35c597a9119f4833124c97392cbeacdfbeffd241b2568fe2f055da0fac6902f829d37b75c2a6083b7dd4414b74c5deee1bbd2e145199da48023573aa3254956cf28b0037e5c761c7ec35201d7382f559c38e6977e291f27dd04712f4f7789a4a199442b44c6250839eb6112fb8226c10ff9adfe828badc9d6a0f75aa688a5e32d80ba869fd4f8073d3e89459d6d129165090d5251c7f610fec597786b6b38dcb4f44e238983e922a4fc9bb5ee2ca70a205bdcdddd6a691f4399d40d263e5479503840f7d7b0255794c2e612ee9fe76910ce4936157dbabcd6440220c0f138a6237d2c300e6243690fbb573c8bb331b8fe10cecd448993653a4760248b2b5642e388c0df70aec192c7fb893f89a3d2fdb4f73af2e2b1ba38660bd8cd1b5cff4103b4061a262e407ef5c6865673a5eb05dd2791c1560a8ceb02c8398b75b6884460bed17ff4d35edd9e3ffd756f5960c4caaf8b6da984e7db487d9d1544ed560a75d81eae0ff2e7bef25c0305c98dca2f3904c4271f555e1585c28e937b9899c93ff9c4a9d5a5b882982ec7015e35ccf07f1c5d27ecf172f53e8d6479dc76a97ca73d036627854fa1f558c5c455acb99d7500d99e86f757f89c0bb0db5ca9e59128c63be3d464d5c51023453b3ad2f7da8d508f8143a6285d09633ac105836d2dac1c4df8625ddc9a01738bbb8e376cef29901c0102c59587bff8ebe467262480e12ef9d8f5b3ec22ebc5351663c07d1434a950bbaf439d246390d87b18d4764e3f68328cf1301e024005ca9511d6a39cb3bb44ce87a4f066b67432259d0b423fb1fa4eb4f8103130acd928857fef6587d7be5d3495c5be9f511764dc0ee05e3609802aadab439596dca91a9714d867c5db9cd249c5bb6108091809212bde8d5189cdbd6e33711358608b6b6d9edb06c93add419d8e8500d2b06eec9a7452af6832c9110c0935d2a097ab6e540abb2a3f2526be0089173c57ef19ac6061941aed50e0b344e4fd8a5e02a63f3ffa466913b46b0da0c9dfcafee80deee0b0706f9a64046f136f6ae8ead5e461770a330a31ac027f8c7e0e470d41e95598ec3a60390eb30d5ded207b59286318150b3fceb6808d0dd96e2b59ed92b441d3d5a19d198ebbacf4c5883a44c498c6cc752cbf47b6f0186ad466ae45fe0ecfa1b5a5a2205b2808c1d248c512a4b7ed91f188e0e6984e43e70189821041b4f986c13cf1236cc0175052bde824649e51e35eba04761a6437ddfc649d805a1fd82c019ea32dce89b29cebe4493ff100a89933608f723fede9821e05e39ce3962519fa07db26659f4faba6c5d5b46954b092c80775d690fe87a8f12469dd26d2e890cbf589b8fb4b4f63ac1ac61979fd9245a66173d038b834c708949a82480459b41e2994f6c972eefd80df54524c8c56fe2bff9cbfe49b9e949ac717d8fe9db55dbd1d074be059ddea17a9c9bcdbaf3bf237eb0f6e4205eb0c6f276e0d6554ba116a2fd207f0cc1120a1657573b7832899aaba2ff60c309c9a187ff41f27039b9af101168277c9b04278a79472e4658b228af00330870aa5180ee21f6d9cc2740ea76efb7178ae7fcc12ac0a935eaf1675d5c8d51eb7ca5518d6702d3e06a9084928f33e0d4b8be3146db3e6e84e9771a87d159442bd4baa60aec273bb16b6052cb9301840a2af72c32257d21a83ef7b0c710878640ea055dd541ce024e560bace0b6d0e4bc42c96340db2f1054b729b7d8caa7af40240956cf027ab9ef928b8641274111a7959ecd82654560c6c738ec677b4e266d658c1fa7dbc0a9546065214e53f23251b3e52b5288f11a4e33007233d9d39c0323ac3ec7046f7ee1716646e1344f468729b474bc009a94e675858e9d2fc16ac605261dad752eaea4910be660d080401c0d31f134b0361958afff753b27213fb586a86959fdac5462b0924117eda2437c34d9ca363d9676333f93ee66ec272b25eabf078c0f49c551960d46886c108662621eef82bf83b2cd15cf804fb6ede030cfeb59cce9b79d77cc0cd8abf545ede19a21f4623ef712c489339d60b556a5cbb3279c6de0b6448090c4820f02cda870d90b9b83bd7e56eea806c62e754db5cb3886c5c649f0748fd786d2a6b770cec71535960f4a5a0379b62332650a239d14cbf2b8342fdaf7707cda71aa432a72d6a95c931a5694b7570a62043b25c42bc607258b453fa02e2ad939575e2fdccd68f75337f602522d93833c224dc20f7c418c9827a98601befe02ae0ba3d30d2d62e2136c295854df7d59c377a5c6df367fdf2e7309597495f7e8385ac065cac7ec6504a6e950734e0982be3ddb76e7d3c987387704bc8cbb712192c4f6fe39d910d4b6812e200fc56128eddb777a137c3a789ff67009a8fa7a29b48d09e1ac4e0d54829f3665e43fd5cb8adff3471777d11ea4a8daa142166093701aec84b53630956a139c2cdd6c8d749b0db9ad58639b47a3cd512b871f4f731cb33d3f5f20b0751964a1ad33d05ecdd61bae4b3a4e0d0f9343a3723cdf1201b74f9821376dec3451429ed4ea6f71d3866f71042154fd818cb12f87b9a375661a1446eb33a87eb805be5557530fd5a2ac7b449905feb02ab44f6f8295ff85e8b091aa6b9bff99ecc284efbd7c404d66f504f7a140d7581806e866c59288bd6649c3672356026c94d0b2ead4eca5d35ee8ce7eddc5193cd43b359150089ec11da1f82d373ee9c987d6ce2381bdddce6910792c4c6e1edf5832d147a06d956f59d3face9a98d27657e508624cbd37813d7c07c4f2a0f90df3ab570062c05f414a981787efba6b973c62f0623dc493be0de5e8780b01ed76ca99506a21cea933f65a9ff5a86cf7a7d04c6a395710dc25b1d91df6179c2e6f84b4ef1ea49b81f387d26ea3be84dc433e0116461aa73fe25557ef495bc4026805f744e0dbe35b8b39048744fd1445f58e3df835dff2d47163fd6e963cd50ee73933986387329f9a83abafd0cdf66d4a40f65bc73675c0a8c1b68d5ce251885423b19740443a1dab4ff18d9081d63f40f7699124b42bbac703db99dd0deae645a009bb7f3deecb6448ca2b1df80e4b2b988d10f944a99221edc9f7adc481d2346052834e21d373f612a0258ac4cbb27198354dbbb0057251cc0751ceda4be21209d67b8caf22d1f181c71af882877ad4dbb0a927033df36e1cbbf1c9c1cacc0c2067abe79df1bf4ca98712f5bd36e090ee8d58534597996cadf4558a5d47b28a6149444c7f62917a755a4504d9eebfe1b144e71d95b08baecd65cafecae7fb041f6ad236ecb50e90c6465ae1ad11958eec3e832218c2fedf9940b4ad3d2a91787051858ed07bef8cefed98e9e3186f2ee28804d3cc1e17c1aa059227fa7a5595b6b39a212f51e05823fd8eb913b774be448bc1cfe660db6641140948fee413fce040c9a3659308ee50e250b76592e48c2173c7345173923985b5b691cd419d2890e28fd3bae34f17a35d902aed7416078fe988ca6dcb17dba28033a03494d9477a1117225c4d5f13852e4ced69f4e4f7989ac8301319f412e3becf85fbc666e65c882f6865e75e0e8fc4e0642c806159f37f05b206e8a156801f8aa0aef7e3264dfcb1111f9195c8a0e02a54e9c2237feecd9c9bfa5a38a8869fe03c4e61bf1120d5c76923b67f6cd212bf8072f8f6023377bc351505a41ce2f6838af168c3b24b0b2267cc0144069c00559934eeb4f162e05ef4d103d6961684e8e98cdf2b98dccaba7880be8c48f817640e2f5b937039c3cd7235e5bf319a57bcb362b07965a633b57d6be522665b47c4835cb2f74eacc4a9530dee024bd09649ad6468655dfee60780b7ba2703b1ab3a3a9aa02d16845b8bc390fb5ba1fec9754283d8477d5286d793daa00c72234856b10936c3b0a439416fa6afd94c4c88e466f1d60d3250c82b1f408deb93e833c3355847ae850d675c877fb4c4355aa027546cdc2e731a767e8d30cbe039286de58d6ba35fc1cd30e49d9e158961b34d678c5929e0a28354a71ad751f815ef2e1c75ee8e3b9aee5e66c8de1f85581789dc1a8b71180e8a4bc51e00a7838f445d13035958b8bde06e04b90432a05b638c64702b0b05b0d0492492de4b9e1ae3a506b7d5df2834e79eb337f15d041138e6c4f2743de43f48116ccd4105e738781658a40996183a581a50220c8514b05bc96a9216bf94e01c068084c3a8d71523724a789f516afeeac78e3496cd548a7e6c09576058a5eda5078647e4e3c1795631a64ad3ac8131a5eed70eafe9922cd782af7bcce3e24cc1c5289a0984cc189db655a313939156e00362802e84827e8b9ce6a04dd3841cc54e7e812670a8f7229cd4a52b2e02ba83eef1659f6e23392c7954ca743006f824f38ecda34cebbc4b88931ea2e9fca5ed5aa35fdcab5642904cb9d6c1fe442b438f492ce0c2339b1858742f6d0ea8072f1153e482d95756c5161b865cb751090d2e6961a739cb669425292b36944f2c07bb54334878aefa5b04da4dfac1ac5f5fe2e0fdc8ccf29db445faadec65c1dca39a9e4048e837c387201b73b9c570548c5cbef378f91c60b221cc441c7f840908fb0e870c6d30e8fc559e4a2371ee46f091e594cb9175da78f8fc99c0197092206be0ed56fdf175062d9a118674035298eae9905847935c161c4b42c11a44749c311f07037fe8f8ea6bd8a7626604719ea8371153f5b923d46fadc7285b433a3eb3d32eccad6041cec7490e684f102cfa797fb7fa9ba1136d3a20f3caed1fc0c0b7e2e95cca9b24ed518a64c42963dd9cde47daf7080b94ea5db0f16138855a675db0391a8cc6a078e8597cfb7421c78ebae845b3eabdcf33a5aec0f75c19271527e005845aeba3a42e5b53c2cd94d190b48deb9a396f81a293e899c91cbe7920817a2c5a2e87106cded6c718eb3a6b1cf7c96c7bbea6eed50fdd12627bc99c1ee6bcf2e9e70c0ec5aa3323b217816b4b67fbbff5cbe1f803082c71f764aa44b45dd01db33847a1070484b326593a53fc4ef4fe267e8b348b25cd0c96ce943ec46574a988271692577cce9fa9704bcd17bf7ec8c20b07308fb9847e581cecc437489817f7b87dfc6afb87fea244c206133e9120e0f951a7bdb7b8e5e700bc9c1bf9e01ce2113772739108d089db15dfd8f252f39eeddd50ebc94b243992563a16ebc4677a833cb733ad43534c1b7356553d9bcd5fe519fedeacdf8fd68a06e6a790a2cb0ab81231d066f20609fd9fe755b54a68bfcc355933d83e910c69f4b6108844d287a0ee3b47b3b4a64470f3c4bc64874c7671472e1a18ffe9563d135f3db9ad708e7195f6c81bcc50621e01c44f310e21689aa3168afb349e6e4d58128a23dfc86e8d4c45854268818be651c85c0ad9608be965f65dfe1521a471d740d43b0cfcec86e7009ed047cb6a4e8fc937a5fa15d4a46ad6d53d274bb3d944a121eda95734d4045513361dfe82819aa21aeec79dd34331ecd5cc80c34ac8b31c14338f9464f539c5d29ef545d5f4341bc0800a80ea4cc5f340bab19d9fa16901f140de7b8aab587da944e428e7718658e886387008606c63477d36e95370a8a8d7e6190763f290aaba94f47caa4e5aa83e5e9b5da84232ab11a92b7429e386c30a304f71ce66a89ba8131061b826f687f292093a78dc128d40bfdca2e7da179d6084a56794251492b194cde427c4548cf20ec8118ae8ad863f8a0bc0e76099c23121d47771ba66e06580345bf144d08163327230386b9b913270220c01cb5c3fd08e95d0e9de564940bba0be7c1d1e6318e6dc088b9fe7285ebd8dbc8c9e8cbf58a9e4165fab59c5ac30cf5e637091930acf1103e173e911117d037ac52c1d3895eec6b5f44f87ad855a14a2d384b1036a72a98e34b63379cebb9585776cdbe8c22801b80a0d3e6b1dac9e30532bc2f294ee80e709368e55d269b96044244a50ee9e597e6e46bfe6b92ce7bf2a827b54c64553a5a1dfb00756859ab8be975969e8940d2a65abdb0eed843b9d4e6cf3ebc7c3a4c78283618f367137e15e69e1c8ef10ec6b3ed0a86c487e8ecf026b1470d2b7a72c6171d969aba18f94969873f59f63caf3f77a7fd05e2cf6c91fe4ca590e4da9855182371e63d709429c07601944eaf645132f62d9212184543b03a1c2a01526ad9a63f81a4a712e4c7815a47d003ed4524d28f6ded2baa7ca4fb179dd1dffb1e9a0574e3d0bb9aecdd713a0bd4e2d75b7508d2bcdeca5d32850a0b531f29c21afab9c7105eabf3b3b4392962eb7312fce4e7ba7e36584da3907e5ef296f7038a055108625f2c4dfc0f538b9559599a968615a794f74497b3d27defc87d71bed7afc55a1bbf28edc24dbf48d81dfbebda4d893831e5727f421b92dcff287f7ef7f8d54b301853c0d1c07e8c981799538286341546571d73951359988ea21abc15578df307514fc4523e5cd15c736079f652a7df9a57b8208a793dc8699e7e993fb15fb7f492dd7f763c6ecf7dc71ec8b490be011eb8e32bb66c2c29883f5cd492cfc3b51451690db76be1e4cff3dc8c0afeada78559d1fd8d5afb689ad0ba290e7e1598f521c0e079090955328bbbfdb8b3e4526b806ff7af71e47d5c6492005218591fe160842740d856b79688c4903047da39ee51bb70935a8a73a91d780e097444d0fd993bb580488e07cbfc24fa705bd7ab21554147a040fc5f8f2c4476e5950cd41af368b9274eea04d57149f5c3de4cacda7a841460a0230a4bb4c97855baddc0a06058fa4d5b1a8af64056910b62851d16b14fd6a98326e434ef7074e62ea301e1af5f7faf6dd5b6e100cbedefbb435bb39e41cf7894fd0740de3c5e67a94b827df00e47d83c522fe6b86f9ba22969ad974c0316ba5eefa2ad354ce6a66ffb3e5e955093570daf21ef0f7a778cd6f0365746405534b99374834ad7b9efaf1ec5173eecced9d594371a5746027b08e9a794a34cf3c80bc753905f495446dad60345545c7e14d9ac12a5e25b6810dd5949021bb41da961251d3bb6d8e5775105c73b646d2cc294e4530cb98f26a3d32a6b4a596987a31336a208b0105dbda625d8c1fb33fc58c195fb0ded6aedf0d40b04c068c348a625b8695f70326cbc6519ffa0570c6dfc10c3ebbbc774f865190ee32d2c75c829ae87c2ff9b87441c2ec21c28d95d3bfa4bbd57830755a3c81c5735b0c8328752e48cadabd2912b6c3b1a7700021e5a2fe5ef1b581814c01c58cb15267c8a7b7ebf66acb0a3458329889181ad9339d32699dabd8599255e030040c02dbf0fa3e55680551d331682d15fd90220969b0962f996f397776b753b85be03bb461f35462e13bd3ab4d2dcc828e5144b1465a31db1de0a9063799c24658e2eaa97200d9a3c9354f4073c58b812bddace8fa4f8084ff9b711c93888f8c09be6e80f477bf8806dc9a8a2604c865ed73c72b46a87f49875eb2e8a59517d5fe2e3ac22ff6dd71907b14241e8234dd85ffa403c719b2b475fc4c1ac1026cec67ede7da4da2f59208e168be5332d8ea6b8e5decbd1badfd7db125adab2c1144b25b3dea12072b4a620d2c0de18137aea2f47a3773530a787fae25e777c373707969c92a140ecf90e6c19464c392c6cde8460d9f51bcef88b2de509b49d6b1d4e77efac54ff445f97922a77ae5d02d7fd817ec9dc85df3f186f23aeafec1450bd1eb945f311fd48138beb0b567bcbee282cd01503132a28b4018defef2d35e1df021805fd044162072bc0d4c8ff025b7116948578de5400602edc5bbb42e3624237991589abb84fe82d3dd1ce16b1578e0804ca9af87496b0990c70fb8276cfd4fbcc1948a9d06f61aa56772a6320555beaea9d5b0d36049ae3f505aaa0075a816120655ba81349cca4e0ae011ebc2c2607050b780efdb2d79937f30ac9855dd9b198cd9fd7b5e12dd1d3caa3f00dda80f86a628046a20fb40dbaff538f87281c2df02d242a2687c2f0baafd8210a2559d476acbb2950270d456ff147ba38caa43f552ac5f790ff2dafaa08fc2021de3de38af3122f66f2c8cc086addd71aa461863dc574cf8a89bfc8d692d8f4ab27778311d5a018d27b1792cc26f5f3aafe4bfa8636349e872b9c9a349e07610fcc3d088e60078d17982bdb1ba398d372e5a4a3f1793663bbe9299a700e904da34696f9948695cebe78a6da0985e5d709d97c6adf6001effbb9d2a8a2ebf6591887afac2a9fcfc4a84ffcb2bf2c8b66dfe1f4177a02ffd6632765af503eb897ae4d12787885b41ef94277c1e2b7a8315a556257f3a74a4e109a078a429c306dc83e763156c9f2b5bf7d2ef848898a2f5a50c7b6a1dd476dc8a46c0e9f48ddf4d80b28c8194a174f58623f94e092f185763c3e7dcec7f2679ddbec964e9aea558c591748d1ec00540d09e269b85d6aa36d323e32c3a46f047589373b102d4cd78a01c0361799fe3e0ec051a58fa7ce386224da25037f38a9d99753840754e29b4975b38474c1570e7b2fed984fb3d6b81bef4e90a616aac20fddc746ba452dfd379e8c9cce141a9539e55ca6c47b4e995a13494716c0c2b817f258bf23626e18965645abab432148e305ca34406acfda7320cc5d192fefb5a547fa492c4b04e15789bd5e8432939ab3b1842f1b96681ea8a10b40b2ecf6353eb4f0a3b5835dfef670a73ad46a65ceaa6e8246e48814507b58bfc41fce6393e6ecd0fb5dfd2083376cc7bdcc4b5c45a0234690ffb5e71322cba6ebc8611ec15af7f3f7eb0dc9c5e180d379f107c31d05835ea1c4338c404a8c407bce2b526d015c3d2c0569f3b0ede542d49a74acecdf2ee05018f1b75f014993f0917a7b7577dd17cdcc83b98d4c99f03f4e1c4c848416935df0ee9046a74d7f2e95d2ae4f62754ece0651ea2925bc5ec008e124cd16eb21dfc061b9d15fd06e77163d18abddaafc2c7af154bbd4df10be6a6139c1b12349287ab66001cb39c9c502a41cc903d0164588b89fb53d88626f4d7ff748029a6c3f72b3af12ba5e05ad6c2ec3865eb42da966887ad2ca0c0e0cee63696159e6a6e99d84863932d48edf1960566959be0b14915c8cbef075bd427e7ce6ebbf9ee060081ac327f80ebb403039fd9ebfd2ba27d54a3047dfb5b83c4f54f3a3f330387ecb536e7db6f5e8cce9aa3af8e288fbb7cec215cbc05774f1597ea6b76ed08355f5676305904022f4d5ce4cc45f0390c413714757257baea325829b51041b349b101ecc9b615c8ce5cbe864df5fd4ec5c50ef10cac34f92eabbc1e227854e11486f19730f3d02dffbe1e7c6d1c0c8a4b6230e2efe03f6bd2aa8c925c6fb5595aff341960fce379b546882275c2210d53f2faac91e794f857f9489ca644c8227815c4d6194a1a4440740608d5b754dcbb82a82918e07d0243d8ff05e3c2020a1a4c03492f4b8f770bb8cd4311d7a0803f449391c1c608bd977ffec4d3dbe4012d7ec9d45b0e790d377c3d698da676137d206c5469dbf29f14cc3d22a0c9887f1383edc716c4c488f917ee8f5e02050f10c3fba63d961f7954bb47b3574cba08b93c471c9a8ba6c963410e341c8bd34b5689f38cadc174cbd51a78376848c3d4ac0e465f234172a9d607d8837c82b77c5248189b1d47a6e214c00a3d5a6a7432d14c05b4d2c9d6c27b8c8e32eb474660bdd3e54656ea08076409f52110105097075ade3e1a03dbf54b026947fceb9fa29c70931d3933b0c06cde335897e413644ebda78b85fb0d1d2b173c440198de487e05b6b1900d842b0dff04f878713556392da5db50757766f1179b3c52c3ec5f57c4620303871f48b258fd04796feb6979c2abd0b25ebb0854c24991b090fed836b0721da7eba9fb9eb47880eeca3e0de456eb180d7f645a8f826c50fbcff9b9af36556802ce11c24d49cd5bd0743d077147aa115d60b0f30a7396fd437fe85804b0b6292f89a0fb27e13b295007dc523dc052684983d99c836259533991fd44532cf5ce62e6e3fdb4094275ddde2fef0a3f332bba670022203af6307327b8ddd9f2058b0dee2638fa83d70b8189c7a2f4aae49b1d2af4697edf996f6b248b673c1d7dab6f31aabb2d455aac52fbe7589f62c1c7bd86258da1d59a774dc42396f0a1267e0e1e3add7221b22b9bbc5beae020da2634078a8dee030d98c79f04f0c36a2274f7bd551d05547bd54a4b5946eea1f8eb3a03c464e3a64b02f22ba83e3c49a196585da5b7a1e93cc5ef40a416ab5ac1511cbd1898819438346c92d9bf1acc49d8cf7c97cd72d7f3e00eba34459291c4151daea98a4bb7631c1d5cc5ef28283f788793cc14bc433c6789204c5dd7ccccd777682fa9573563fb2c60d49b45ed5e83bdd9598af15761bff4d2a68918cefeacdb02a023ff87502a524762152a50811f0f25a485d90eb13b967cc82315aeb855f33d71a6832d370c4f985c1ea30fc0a907caead3e85a4c5ec28e5df35e00b498fe8922f151540985da429f4c0f102bb838b08b44493b547a5dbe0a730a2836e675bf38c2d6ebe2105fcc2579fd8e58c9bd1e75d26af8c8678f39edc68733e23a81823df44c5a918f36aff7689a56984d0f93b45ab36b8505c7a6a0c0d606760dbc2490d1ff864877b09943c76d6472e9ac0e8c82dc32a46a208c040d9ad110062a051f3d65043103cae2d13dd813340cf768c7ac952400dcd88182905947cc64be1612c6d674bc6d012d86fc8ca891aa0e5007f378391fba8cde6b65573bc489f47db4389776027a8b413800fd9c4039671834cc5120c1277b9d16a4fe2c71aa0b100c1da7e665173d555803b390f9124bf63548503e83b47217152b8674d39edf46199a7b12425c249eac3a005c0bc9f94c9f3ca807fdb7991710f4f0a83362200bb978a174147e8f47d23a12d7fc0772f88a939141b49f31580a1026038ecb280c0f4973e04ab4eb4533820c4ee778c404f040a98b4cf77260f4df4f0c51179168a2366d411421066cc4e2838724afb8862b6d37ce9297a9975f725ee565e36fc2886ecee78e6b4397f7891d42f34769f87e015a960ba2916bb17f684685d6c88ffb65f509ab7098d5191c60158c574d7224a7e217a514e444cb5362393f8b080f3a7c468b752e688c008bbc37e68e3e010aade8ce8636ddbc91b91068cbe38f4422b9d751bcd4ddbe28aa3a45fb72b0f029e223c766504ac8bb825c14596d758f62aeb2b9389d8d8dc5e11844f07f330f338a1c0d596e03a84ed28fe1078defeb511ac680fd4fd08d4882d809b2045fcdfe7e4ddc03b50026edda6515a96c10dc5670b01f9b05b7ccde935277a9fcea4bdc8f8e76282e20e71fc444eadc24bafdc5e039970624e6dd6e077ca9ff5995ee17040718e017dff744d231b025a5723af1aecf2b1dcc2fb46282f9c213883a895ae508c733cb08f67f4737c9d02f1d14d53dc6211937d26ffca1b2bb5d5cf8b5cb9228afad203f746ee8576ac298059bee5d6707e8b3e9999101ea49dcd3e2a113d49d49125884f0c52b39d00ee2dd994fabed2a18562ccdde7474747f1beb66c975abe903d19306eb598caa7a262d66bd151a606b37aa708da5458e469c6f39643732e87ae2302087edbb751073819454ccbd05f186474d81542ff8c2395d90f1d4a440b08a1c2991a40d11fb7b983e045fb07997642f868b2cb7f6085b5fd18ff130bafd8e984afd1cdc67b29952a43dedae21873151ce98104fb386909cdbedc1413c103bef74db6dba90205f24877e9a6c2fce0ba84dd403b10d48662f887f080a06179aff63c1d46a2f79d9fbb759bd9701660cede427f772abebd749d02cc0cc2a78143f638551f7d98dd3698c8b0c3b072a3f88e1e4dfd4d5faa9969fe583cd40ab85df62781f53c7571535a1a66cc3148235615e7d786153c58124dbf62fee05a235974f660fdb4fade1e2ff356371ee7a78dd5fba508e7a6548d14081d86a9bb0f80914973655f430897e5ae28b076f13722e507d5ca4ddd5f41676f7fb897066ee10a655f3350e3cec1e9cd22f84ca4ea046e113527e42aeda1e68a6c02a2a12cc8e8b52142a6678f4a61d6109f29031a6a5e3451f8a8b0b27bbde4a46d308ed23c51d3b389ec7706d6044958d64366c69082dcd35114d79f6ffc495f0411e152b790a1a145964af4fa5e390aeba2c129185981d9b42f4ff10283cfbcce81feb8127ef5e1e7b83c6adce51fa15de670581fe038ae01d241386c15adfbf9a9e23a1ca8ba69735426761328ff3207974122eadeace310c355fc7baf994188035db1b6cc42e83c475506b6be892d73683957007ade48150c070f717191c9399756f8554055e698ee273f0859fdd971cd5c58bdb5e36b4aafeb589ac6dbab54cebfbb7a0d9b5de110e106ab8711e04ba8e12ea0f30f5860c0fc870fa0d75f8ba7480808c45e0d0ccbf769e503a48cb628510062de7b50c7a94e0b7049e0c3d2b96f78f2310a91302ed3346e02108db90b4822a5f7f0426f7fc41b6fef1eb8cfa3800090059bb987dc3bffdb05791503fe65f00b3b8d2d27d5bea88021698ace612313d3c0d5f8f4e48db89405838fbdc1e61d922f3bb62e01bf92aff38184b1687f15119b17c930854d0292097066edc168d270038f4b5015335c88a40cc3a3fbb1d6bf9671090789f3f838a450ad6474c70611face23555baa4f97563dc87594f1934a15718e98dda6978bf2b0dab28e24844e19a92426e1562e3214c0f3d265e645c5d289b065f0e3f4b0048f520fb7537abb8c6e7093299cf6186071ab966d5b5914437d921a91e3de28d648f535607d71d35972cc5f17b2e332a0eb54a1acd74d128cc110e777731d851ae8ed510334a8872237a11fd685873dd54c2d9c762204520ca07a82bcfc328d15ed52a33714bd49378fe68baf1c4f79b1e89fa7820c7e1c265dca61b051033149e29a578033bd5c6f27f266d640e4c488fa5ef9a21f894425091596e9a0afc0c36bed2c0af624404174d5151b39fb388e0ced719b0100e9aabd76a62e8231070993090f05448deeb84d80d75396d3b9265faf34c3404d91fab1e635108afb34c26da36ab78920228b7210db3d0bd0c055a76b78299e3f4ea0f1147d77318242146015feeb140c3f0beb2e7064e24eb823b19c0ec07cd16643e2c9848e837c3e33baf5483fe21e0631427a526c10ef097eeea51bf073416329797167f2a038ae0c7c56e505f02112af0e8dac1ae39ab5313a07ab221354c0837beec0a7bc0eda80cb1f738bc56eaa4a9085434ca9a1ac633fe3d197109f48c675ffc4bb67d33212e8ff8c462affdc3d99fc1d9662bbc24d28797473104fa43f8fb8538dbabb19bb34f186db8674fe8d5e22f3d294f86111d379a359c40d617556f65834160f29cfb40034c0dcd67e51f903c4ae9268271a4811c608010734aeb2e838b4f87c4b47fa3205cf42078ef44e7e04ce0d765a1382ebfb7ce70b7dbc180821493c702a8fb269661f389c5da3bde6baa388f54729629fa38bc1f34428dd602a8fb9ee0229d12e260648e3b2324db39634d63f166cfb0aab70c90da0a52efa04584fdf91514e7dfe02b3a3247ceafabb7fcee81dfa19747184f5906d7303ee1676dd510b86f2aaa2299c79a71a065be96e7f83ce47964279f7d6acbe6b75278e4a57f675e7047d4d963c6926012e903717bd96150d4249a05d4c53daa4b3b35aed190cd68e80fd175f89c2956bcbf5d9a3d0d48695d44f8f438257d499d2d9517fb3e114a50e0f1b1c263887e841db585afa5e54505fd047484606a88c29dfecb126d45070e9a7830e2e7f90197b3e6b699e976e84cef068f44a111c097e4d39b9f46f452f3d8a55c80745ec5a03bfdb920669b706b9045abcae5cfedfa6091a8a2ef943391aeedb7343d2edd6f06f11d7ed145f0cc8edd7846b746a4494219b44660301371a76aba6ad51e4ca4499f8153d0346c94ee00717a4e467c49ecf02ccb12db5aee73329ea5980d759906c64a5dd9f71f98859a34a42547914e0fd271954f69a87fd0f34396d69fd12190da93d4c7226f3cdb1f54713bdd76a729458857cc79d406c1aa37264b5908617ea451005203d844471cc8d616d66a01d5d15bb5bd2b34c3de855f2dd86764b2a50c863bb9e479a18b709b5c264fb0288b1fb75fce50750f908d245eb03687197612fefc44294b2808ea53e55856379d87d48342662db5a495127c0172d061f4d863647038d4247e8240d2ede8b76924639d505ab1effda2a6fcf35e79d2f17dc947e0c5b83dcec1a03a4d09ba41e6de9938dc03666c827c486b3cdf610718a503c3c69de30b6f904491565c4a153dfbd37416414032592bfb8cf72b03ab5c7fd6703beecfc71f8280e3f8c9e95e501cac31b44392021d1086a14a94c783ced48eeacf8ed4ce5b9dc059b6748169b6f44dbb042e4bfa31b384101fc460b30a83d8004dd8bac54902e27b101ad317b160ae080ac44b8159ba00c326335634f5dadede2b8a542e2ff455802a79001b083fdff75ce93481f4052eebf9de38bfa35390406312d8bae1059de42e86e811d70b9057039c863ddf722d7878115261ee19a7e724259064e25827602c9b60e48c831ee388eaf7b34a33b843309bca779a292454b1157c0414074ee8e0fe6b6b11deec05c0afe9c6c8e72f8c85c72061d7e9a5927c0c7e3e569e59ed3ae0a00a6887a9f321b03ec2baf15e0d33c8cf9766cb13c15151fc791e533a7414b2cdd36f7b340878a7f8139a49cdee5c1e87f626a10636aebba67dc8452207ed9a800136654f26b0417153cfcff0bc60e70cefb9b7e74c3917f01f9ed08efc26e1a1e65ac5cdf404f7428532121ae5923a9b3015d5c685810cf90fffd1c89e7a66820078ce0d036434e87df3436331cb431352c614a9b470d92873b27cf837f6e7404b1d7502566f59eac97098d125f06c7120b20b2a3321e06f8917b345cf9e93f2d943e5db40eac70ce08dab437df7fad2eacc99b8af4c1a4c6c17ded64f599d368f984637c54de441266d4ee799bbefc2c9abcc35c61e6f1ea60281768364a9d4775efc569cfc2ec840f6e688e5ad495811fb183f1b98c1fb8e8c49a6db6ed20e5c5a7bd9a8832e04b0233c924c92c9edc045adb7e9abd4d6e681dd4316ec234f2fd4a721d586e65d1ef5033a73197cc672493d60c71a335915b089c239c5b81f591cd1488dad9bca900bcdbc0ac80e55d08b618c0af0f99481d7865ed24d94f3b22e06438694c5dd82a8b6533bc7df41092702922c1cbb04db9f21b429ae98cf9b924410e0d39bf89052efaa3e703770fd3d179fdcc6884f576a15ee943512abefcbf110b01f86052e2c02c36b3d7d779df238199365fc8b94ed6eef4264817fc2dbd53c7364c17353572934b1966c3198d321ce308d8ef149b6abfc2c3c709df57900f4239d944c8fa1254aea0d916278099609c309eb640a8ddefb3435aea3cfe317b1d5c1da517f40974793017b2a3cf301a746e2344fe4db9941d83e191b05dcce3161b5b1775fa7fc5a78f50ac526243ea070bb4cfc1b4702188ff9c6fea30caf76a895be0a8de1c6eba260e98d5db451ab882ba4c2556259d7154b5410d7debea227040269ccd162c22b51682887f71ae6d6cdc6441b3cb1e89958a87489b3749bb540faecf753dbfb2c13460df39924deb439789747d5b9d2e1f46c35fdd92017751721f5e2fb2e0770ca5ebd0525d4722f9888329fa5d5cebda5e85953943063a1921fb05e0ee35fd1e735caae937045c801bbaba05a11a0de8f9903b72322567994427e59540b61c520117dd3012a06fe7b9212484136948527c2bbe587e4ba2e496687af85fbbd04dd63e513c0ef4677af26750f58d2b112bd1b7b8c05104313aea0d8dc01d87221473b9a97727777103af59607d2b162c6ea29d9759e7f60d0fb70cdaa787de324a0e46371fc583c7a5e9633c7688fd785cb2149d74b965170595f29bcf5db083fd6e504b4ee57ea0ba0bf336a82dd0c9f8c08ab446a9d1ea1377d35503206e657915b88e1ad3dfec0845a55c2faa520804f4477d6ffbe52bd37ad8499624e19076703ee6844cc9d09b16f2e3d68a5d584449a7d94ab2e232b397571cde83ca032f017d976692549c16366c99604c77904de8af4b0f20430bf31481f0d17216a090f0b5480f7e885e887a283ef8102692fa2a90a9916dc3b3a29e2c8e502b5b7d7c13799569d0c1a8194d5ff6e9e5864ad385915fe2da5ee46714006b34a22a9b833eb494793219e3f19371df2ca35c33ed77d4eefde1539c596340f6b3be7fb41c15ee4052a7b7d6b297a4a418cb0cce557b6f11de1cd578c7482e3241504e60adf1bd4c197188feffc64250c7577c58d760eb12caac0468646d0cbf0bd6a14e6ca47c839cb65d13987c419d5442609ad7fb076a00661cd291e863b669c76f05e9b96813af911c1936bbbeb79b69d0c121aaaadf6081883c1e6ec7df9fb7f66ff0b09e923f10c14a9ba3dda62927c718f3fa0162f66cf9469e9805135f2f2777dacfe331d852c4584e6227db841fecd19fa5f751260096659f2deeda435d961b8a24a90a0a809fdfe5e311ad277a0ff85a41cb6c30087088cab3651c0532fac3b976ec9cecf3ed2ba57c3bea23fe58873cc43396f3c348fa8f32b7070eba3697e0ec777ef0c5e20cb17361fc865d2746252f2cb1892ef90691d7a172c516b3c0b6e27b8729b03366b75dd818e50ed7105aff7f06ec13d9c17a72412cf7f06f32198f6522fc1bef69a66b6021df9a42a59253b8b13b3cfb9f0bd0edf438ba7ff280fd154790652ed64c0fbd29ccac1c21c76dcd5cefd11690c3c8f31fe3cff5ada3d48a51fb7d4d58b630941c92cee564b6e3135f23926a3f14bb0f85cda60f612a3939d1818c1fa28a279c7ca4cb5ebdfbb4ffe85b0940c70e71caefa5900f6fa2bcccfe1b95566f7ac8da85c9ced9abddcedf93667eb9d5d42a0bffab88d3e3c64d0e6c4523744083810f8fef2473a4e7a70784994a137c5851f667a2fe9f53e09d7b5740f8def97105ede83b662cd1623bc6cfdc2991e9bbeeef6a342adba27e9cc8eaf841035a10a3465a450696a48e02cad4c9192808f8852f5e99cfae23cfe0b87b948cce611f9ef57587e80c0c23a30ef5cc36271a4b12645a408cfaf9b1fca1c3c266e68fad27eed897b1bb33b0c5c1af441d53caf86e0f738c2d8cf019cb4527c614a68f9c9d91654711cae70d698ab00cbdc2a40f986f53504fbcf8608409b1c18d1ede25622b39dbe389515d67ea887c6eff91b1bfc3be7d9bbb356ee6ea311d78d04bfc8bdf52f0e9203c4844d67563fc50002d251bd7aacc38798c498a1338b1fe4b4ae747440f6c5a0f8c25380fee02a886dc938fa3a847db6364fa0ea4667d14d67af27e3d8de8671343f5d37c564c9acfe6c2fbe9de7f250c8a7b86139f4c7fdd064e71258af8947438755858cd1da4c9938846de12abf8073a21153f8fc6874b731dd9e3d67cbcddf622edc701ece5c6d80784ca94c2712938d1c369839a1f53f2f10a1a89b388b50a73c6b3006c28cc8a1284836581cdd4c482ffe0e41292a66b199d7aba0348e9398db5fd0f0fda528e234f8b644ba0f1770aeda6586af18f6f3c2dc829b3f092938b67f884bfc40d9e3941c26c235adf4e3db32a2a16a53b50003f27c88798e51c8a813bd56eb2bf53ef0ef4e2b3d6a6c980e72674f21d0bdf477f302d38cd22840e24c6e18d0c98cc6a710c8cc2a633a7b59a90bd893a073ea1b87bf15830e1e384749b25679f3ce10e0f9ab26010404a8df627f4db4205dbc7edd13620740e93e700f20975447a18b1454428311383ed5cd4eb6fdb520b08b842e2c419d786a2a92136eba3081763795cee17d4a5746406de0c599266c50455c71bbc3878b3399119a68f65c00f4a3f00b3c149c9c6111972e88123fa56d54ced86cdf807a5525e46f2178fda42d813b48161bfb40e499cd429fe502add5f80511aab5ff998e0a242a1a476014bab4805e337c3146397d366dc2a82206163d44061443f2e8b2cf125029e0454e58aa28b4c9018617409688acefdd067b7312c36a2755ab0b75828d9f692017c722c8d2cdc2bb313d68a4e4a9b03212d82f74f85169300749c63028f0f29bceeff7a542a90a272fbb5fe1f091f70b88aefbdfe8304a47c18b8fe40b25cb549f3c4b84871e521a2fa2cd5b2e026d70c474a1f3dd288c4ceda3022130e10c6c79f6f6f671ce6dd47e8c21c8d1d166919307bc63977a0c672f73f52df0527d9c079a9486cf295ad2a44527e321ecc03e9d4cd3f4ea1970e12fe18fed685358124512fdf5165bd3c5cc0e3e6373a49e299fb1a4e604c100e981a19428bd4e117966214b60d5ef0340de153e69c6b135e512f19c5565458a0dee4d1d4cebd55e8721c7020071c8c63d784f06abd5ee6ef12d0e9e89ce07cb732f954c41b53b8e829de4eff77ee45134103a2fd7a2dfb42c1a2e533e25c093e9abcc1d6c6204dc6e059614c0ea6cffe8e718acbe9c1e937751df9fcdb7b7865653c377a8bd604dbd336054636bef8e5af6e03a70e6c4d8577c084c8a6123fd88c75cc013301366e430e8892c5f86edea7b7a38da7a3dbe12b38952fd54652c940e5dc38d7eaabc4e6e621bf78bf72bf8eed930d0ba08bc613eef4a1bada0c4100fe64ea4b1197f0fefe4a10928b0ebbd1f846922d9da780383bb0dacb1c228d8fd722de3a63a51ad69a69c93f26832f4502981a3c53f58cb3bd77240d73c2324a3e6357b17c198cad87bab4c9879c8e8ff3774d706dc003c7df622522c60ebe14e077ee41c34027f2d907ab9899cf5451cf98e9a92a71aa649da2c0711b3a2117d77982e51284ed203358308ae2c0825e77916290eff356c84ba308dc48fd395e06d39f4382b70e3577f7330816e14a9966545ff4d8eb0e91373f0451c509a068e4070aeb92eeca09a7fb508d8ba284d396400e18f5d76719b3e455ba8c6b4d8880baff76b08e782df31e179ad65b283d89796bcec24aec2d4264f4092a85b3e3b990e8b6b807c9e66faf050f1d21d45908a6dd38ef9a6f0bf875404adf35d09429ef82bf7e118eaae455cdf97fa88870c4dbcfe8c8880fe0707e0a5b0283bd3e5071b6b0428dc854ac7c8f24432ba2e8aa6e8c2489b7428d7c682e44b858ed3ed53bf796c16a8c974e793eeb6f75441eb1a8b171d7bf97b09afa00e0d48a0fa76a9b9c63c287cb3ef58f0036f4922bbe019683ed3c610f013a5f6ef60533f422184053652ba13478373993ff65fa4aca60625267642ad658084a7aa2c6e0972aab8af7055e9681738c624666617f60db09cef296e390bd0ceb6bb10ce24ac1dc5a1728aa2a9dc9e77513618fdbddbe623d80356947d7f4c76c67ec0905e9aede5538014a8f704c23981b5562f6c9e80b1c5cef57c097a46039c19327bc77c93cc494e6eb936fbb21ec8f4c400368294ca5d5e01c25b7c62775b4943e5861c1539e1dfc4cce1e860f4f936023bee9eb77bc921bbef14dd92811dc93de775a4d2c46ce90667db3c758a296f754a8ef31e3a56496b0d53f3970ee25e482c1f53a1af8c1b45184d9f7dcaa70ead4afeae48a3fdac81192d6f927d4818d13a17ae8693af4c3640f082446b9e16d7f9287223ca562ab430b8533102f2479dab407524501f32cdba74c7c0f68c61a4db77c2dd87e52363bc66864ebefc5d6ab824d67bb68e2b0064e9e7c54405bc2eca8c3d1ef714691c17fddb493e6ed744d8dc69d9676566f22b087c9c5f5274435115357509f7cb6afe05f901491e15c5c2b829fa2281a6aed613f8778c94964632e15eeef1a37f2b704432946cdf3fde9521143ba9159f237d8471e644471a313ccbbfb12eae2d304ffcbe73d21976aef9b81869e8a2a4eccbfcb0495c08a40d4aa5800276252a1d4fc45555aa8f5d2c4fa7558d5aba3aab0629aa584b00d56c907dc1aae336b70d3f72175de393c5cfa92c431c30b01a1303186d23d359ce414c36eedb037a6409d778c29228b66eae808d10a797157611dab50af4b2dab47ab4b917436fce3d5eb408ba4b15469b39a74b108d6cd05a201713140a9d31b143df3836c3f07ceedd69b7b4023224f6fa327a15059ebdda5deeda5600254bbe9a5ad29365748e3b82ec2a35d1f43af408940a361907f0575b640b3e36a8215a12e71a692f31d191d8459e31679c6227a7ed2b66a00f58a100437d436661b280f137adc99ee5d57747627bd7e5630fcc4652525a57343ce543c077e08db61c931ff18e5f41f4c0acba9eaf119d1380324376fa2e71a121e75fe456004ee9580eab093d0d35550ffd9080929055654721f9f56a26eb8137549d6d8ad1ce97349be734f74bd342b3fa362554e5e874d83ee0b536ac989e267fa0f68716a962279c9edfa37778815763530601eadac2b7f06f4b7c0d2b681c39bbe4c91af0fea4f7a31a50927cc8c2fed0c24064e1b1db581b851c6c1461368738d032ea654e29ced460d4c2b831b15184be0b33351143ef0a5b6587f264fd44bf577ea4825a9fe8cbdfcf562b2c43135aa084329ea720d21b8da30dab28474d7b473418a60d5f0a5bb8690ca76d01ba6fd01e0d1f0e30ec298e76b0719f2a8d07d9498591f07d22dceb31f7f3190b03ef1182155cc8d83f8360bac32e9e1ae9f2dd52f75dfe41e8301804ddec2216a35578eed29c9c0b927f0edf5b647a63ab775e01a151e9edf14185a1d96fb74ac8d0c2d8031f6c13138087ed194159ecdac3cfe0a3aa8e4346dc4a396283f0401bd8017b5654d7c365bf701cfb03ec77691beb2b089b50afc2430d07ad18ab7516f415d24e00c61b5d99d4f83a9282afdb90caefc48f07d2908149eb31177dbd7b86bd99d0d7ff52e4ea8df5f3e0b1b34e2752787acc6edfb8d0c97b0ebe5f1ddd0733bb0d6056769901435ec1f1c0896201ad9c03094ac2f5335b814427764c05fd679595eee07bb6cc84fd732eadcf9ef091d28fafe1e26227ae22048b01dcd406c0235140237064e130b6d657ad083673801d81bd506a525069dfa59e6587183d92822821c0e5e2344808a53be34eb17dd31d05ef80750ca6ea47790033514874732fb76076eb799df4305d7914460e2993b082510ecb7ea4c1a08b2a2f5ac3bf6d0a226e0d203cfa1f4aa54d96d9920a972c6557fae0f3b0f5a25284d49db82a08b7082af9768b34283e899ae6c77ab6134bbcc9ed6de6f5029b03f2c2e4833cce8685812fa07d9417aaa7f8ca8dea12096827492d3786f60aa0c560110db958bed3a3227d8e5261201a7e21c3c757f376e9008851372108a2562f5e4e735546b58dfa7463d2492c32d1b025b56b9f16a85d96efe5a9adf5397d7ba3295020401fd79d17d97d4aee5a215cd56dd87f877b3ae4312be155dabdd2d885b5a03d4c83a23056398e1142796eb32bbfdf83f3907f6093ca4d916656a2bd6c30db1bbb68e525be582468c490aa05b0677340952fad1e32001f3c89f8ee0a559de7ec2d7ed84c1df48186f19ac6862797203f562cf05438802b4d6c039f1e68263c727e4b51ce7c581f0c6f39362e6f580e022568fd0d4604930bd2ba0110da9b63831c63b9ddb4ed4f103405d218f36e50e73dc42459143c0538fcd625907eae5a16c698c8ceaad09b49231c0242cd64f823b5b2a10f012b9106c3c2495b10315afe9ec54df939b2c9714372b3a13a8bbfe5384cc9d7c0f0d6977a408e290d1a00f3d1b25f723d2a7f5804477e959d03cd4ceaf536bcf4bd1be892ce7dd2c99cfe6d44bdd18aa09a5fd04898a89f58f14faaddf8e7c4a57d54998fea27f9fd34f51cf641ee226fc892795f1cf1c4b3705e91db17fb9282cf12a1c79401a50bd21a7afe3532be80119a6ffe9b3740f472972969836e811c8f62f8eb21884e12965528bd611b0437d02b614103e360b82d9cab46ccea08327b035c1c777ffc15c9a00d61eaea7831283742f907ac5364b6091c6e0f7f3b436f7c40d9c9444dbd82e4ffa0a62906cfe132acd30dec5fc59b2bf17474ee6e33ee3b3c8322b53c1aede611c98e458f24c0171fbc3677208e4230d81533ae6667be5c277e1a6224a64bfdae64ad9967baa81bc75ac8eab99c667547a288c727d5838ba85c1d70afc4f5370f59c009eacf6ddaa79981e57a97ecd98665a38019c065d54101ebdd3ac5983c3855863c37d962f29db13748b2ab83c61007af56881e784c397d21b04a4660ddaafe8aaa0ba5bbc924362efd94cd8f63b0e0660a2f3c121ee131efdf2d0535d809c1aa3820ab0a1273c68da6de38c346557d2cb11f8f73f1d5eb701f46e6518e0e9538c89f166f36eca21f827d044bde8c22ebd3c080ca6aeaa9dd57d07703f36e01dbac76897b0ef5049834a5abb7205f63a36e028f9a19500b5ad0ca1a35d80cbe873a9965615529053973e15e13ec56c172fe57ceb5da71ed459dd01a4ace6c40ae99a824b5d5098ce207d8c8893436fa3cfc0f712c2aa42a9eeabf373d3a7fa4e0773d41e6db1a3b1f0f98df490302a46c54988e8ea402aeaecd049a0021e01398e027620321acdd1cdc044d8e5551124fca4d71604e777d247d1d01d12980ca84993d689d0d554dbc4bdf26652c989c1c5da1af17dc70aa68b09b4648493c8eac4ace1f1fd6b5a51091c7dd265b5d118a8da19b86d5d55daf92f1c235736a9e5f8914b0ec6072a6e7e05027b220143c6968b1607fe7e9740d71a51e513f69c5254cb1f0044117705856e42c75423cd5e814b504b8ef46bd7d2a406081cf23a3b06918366aed92310210305670649693dbd3a9682ca2e40f26b77eb718673d92fea1093650b733f93da8ce4283af72d33ea56744bcbea60d11c311876417b0ef56e717f7445ab57166b9d8198865a8a459d58deacb26b35690da39b4aabd5d9441bccad4933833936d839c251301820ddcf02d44855779dd992c28a9514d1dea1a9946d3771fbfdf395623bcb4a6f4fe0ca8390dd1e9c91d6e7d6746c33ff7d59006b861511142d922f5f1c9abd9a106344e47e73bfe626d2da14c8741c1fac06d394eca297c72c48f21fb0cee319b1a0c4446790f9bb78a0ab59f0c22fc973e90b84bc9ea0c5095ef3387455511bb47c66f1d570ce12970e1b4849d04d7430dbe25c6d79dc59b2bd63e9cba0cb6c489e9af450cce5d0b432453a404fbcd6dc65483d58b4620c323020fe3a821ce2650927b5b6e20c2a2aafccc35bc9c70af75758fef44316fca58478a80d03ced20671e75bf7c8884b39d0edcf09060be19ba59dbabb8564aa685d216c39a05de58dac23cc28e4346f9e9e040701e8786d108cea77e06fcdb08b3ad798895404be66f115217e22c711bc6e7b27598da40c9c65e438fda1ef578af854ac61a8f8eefbbcc7591c821010298f9d87d9c7583bd8d3f8d994e0403bf688c1d65bded64059c60f91558d84e7e97d90749c9eb386eae605d8ff36952bb0ea883f712c9085c5567027f060311857640c74283e332c6e8d3ff85bb14f95e4bc12b3c84ffc1707599b4e24a646a30f3a4e7f38ef34abc7dedfd4f3def6df8bfd9078bca138c7119a82d29db078cebdf9bab3885b5931a09bde445b2953027db98e177ca336b1d9413a37cb44d7582490fee544040beb909ea39ab6b6e13b734975a901a6988fb3c80a27b5ced598584649aae9c889696f0db1293bbb4fa3bfd41bed2eb159a2e2baa103667439a7cf4e6ed0c2e6db5ff06379b8b526e99a771dbd6b42c7dba2e48c8f3b8738d7588e7d9dac2b3eb75b2687785fcf75558e738d46ddc6f1297933565bb067570b0c8671831b5acab52dc1150738681358a9e85c260fe9b78e130ac13f8c9564577fc81b9c66a35e53fa6dd1d8dff4b47327950d390af8a09c1cd6b89144ea67b4d21ac8f576d0c357c4d8383c5fdc32b5fbc1af03979012bc6806b4c01c84727c261dc0997d1ed825fc350d3d0121d9b29e06e5fb2e7c98f6dfa9d7af09c8884c088160e31cf1e8a2cf1372c75d3c8944e31370076292ba0186304a7cdcf8ebd1ce0db9a7fb0a9ca4f3991d7a7c949996e8cfa5260722fe2b2168e2b7839dd219deab839ffb24f97cff5fc3ce566862529376d784630243d4264be771f25b7c6f138e7e60e9d77e9d5ea6c6de3b56cccbf7a9540b366ade53dee86a7c03bc7ee41a35480d88ecddb5e2f29cbe173dec0214a34fed76157839b7b120f149be3c1e240b41818bc0ba9d82f153ed8fa0366d92848a7709fdd47bd02cac48ffcee3f790b4ea1b84edd73e85446748a03d9f0c9653ae5df24dab83c306699e401976c539b871a215b8e2be99ee252782f0cc406a9880d0ed2f7503412e15ec76439d997c61a7ca365613cfc9353635ee5f03f1ef83b48cf7bdaa8ebf3358c5f8a26f9f6d16471f7407ec88df5da0dc6d4dd0894dbdcab8e328ae6a35c63b89bd8c468439ea6a1729bf4d333a665b5798ec7a822b7ac5b477dc52320f163e819442813dfafd70b4c7a22995d8a157ccef7ee962a562b453f1cb51e8d023b81442959876ad6d693a127e84d32ed73cf6cd95981d5d65b00bde120a3de408bd16a00b187639c525bd607c57f54b2928583f86955a059116997730c83276624fc8940469b0d50631f0c8553dd8ef6ae4e7862ce250b7cfd29c5f2e77a7bdd922b1a95518ef38b5abbace6027b19651886147fdfa22edb16946456c7bc5f0ce972e978aa11d452f59c8d0a3797169b92c4dd2896d2f32ef62842b5a85f1ae442fab91218eb4d265caddd2bc4ec146a6a06b2ed811f27231ddb56d3a24a6d35692f6bd5d5cf7ad4d1282b1731b2574cb0bcbd161c8938ddd0ac69a16af0c7196a8d8c56fe4ff683afa87d6e749e326804f20030ac4e8573ea0d2704f7959d3e19f123965404c30593b1cc8505ff18063920a3b191c600eca286e85f1b5277067d20417b8a7213fc32243a6d934c8282e95dc20ee254d478ca11d0327589d60f84bd6c104b018d67be947a8aa6beabca0bac1ddb3a8d9f5f018798c93a2edf7e44ef8805107be6e72406540eb3b88efc2caa534a0a9ba8994ebf0daab5948583a0f17c1fb91c861031da061c799ab6fd9ffc5910bf9c48880f87982c284eab7f019b70e81b8be7a6fbe851a22a37ad7d679e36350cc1a26ca28fb3e5d5965c6e7464ef495f608fe3b3863150fab9f432c6c4f8f6c171ae161945e18f6bc4c0f6290263e87f5e7ba533e1788329ad786caabb2e2e94763da0bf61b6dcf11023476171cd4bc17d67aba0d43270aa79e2166289dd78943a242e351bfc5a663e1f629131f2f9d4b30cabaacfe7d3a745cbd85d7341d2cfe3efc370a4a3b0aa6a4a7ed4a0ecae9f5047d6d626cf6c091914dda3b05a408758c654161e0ac562c2bfcd35cdf127467e5913fc64d318425e098777fdbab400161fd2fb9b993432d6b1ec0622ee703694e5128ce0d17f8105e549c7c12ab00b0742fc289bcbb87dc3745a3cd02f7bc840b9bbff1618d5a5a341d57f1cd541ed9d092cdc7779a9ef6a8f76674110808b8fee5cf6ffcf6ff13a2bb700d8e43c10d872e020717ebb24179d1fad4a0e414344cbedd59a4ec38dbae1c1d3efe851695a818455f8819d53d250418ce94b6bee8c10444f586131a76f94018c8292d456aab5c8d03e90398de1a4e373c778d3599c3e083205881c2977b3c55807cbcf4aec2d9b556b63df0431b047af3e8250e41a4a38470dae56e304b408ec0b2f39a4c4bde4dba1b43bfee86000ea8160b04031e12c9ef871484b7264c47bff06bc0f8950b64b2f78885297133df0db98023d10a590c894359ba4d2a35ee6f6f4b52cd7722a2eb79661000b99e925bae7a530792be15a7d156dccbc8b61717fe387062676aeeac0186d14cdcc874d6784bb7bf35910e22b4c3e35e47c2c5e985cc0efe99bd68dffe515d8c287f1b79ba2c561ea37909b75e0a2c177fa31f730ccbf138f44b0f7c64f25812092300aaba5a136f26707c927b3f0e665f278b0d009146b7b08e2ac2a6f911b7d2b88bb74c5260faa55217f91eceeae6447519d57706e07bc47c1890ab9981709380362251214b6cb532f7919722b9e60e3890b7c0d8b05835c842b357b25a0b1ca55ee0cfacdc49b0dab64fafc792a693f3bb31eb8b889bf640593cd55902f2b0bc9dbe08f1e3a4b4d933dc9ed2565b5337b600b833b99b0c45d10bd3c6c9ebc4e5ae9d9ebc7aaab62a73d13cd8ebdd229d56aed99fc1879e5d5078eb7233eca87595f3135c68e7aaebfa26107ca4c1a2330b83a71e7d9b95b7843066829fe0bfaf4033c0d614f7777199831090a6071e8db4532f33fb3074c3ddbccdf2465345b449639ba43608818e3a1b4a46cfd2147b1c4da9327cdc563d70f6714d84b14bcdd119fb20c953feeca2716895901e502b6e1c3cd9b6178be049f08747857444495a53e790cfd8410d9cf690102024a03d2c784f5e5e517164c4a8848d0b2f4982770937ece3d0efc3a0bf7acbcac1cc70bc2ad0c03b03d2cb7c501a719584100aa9b24dc03da0145eb76dd4422d0212ef2ecd6bcc8770dda5954960b1276adf0d3a26c1d7bc3f7c1d8d6a05843ebcdf9b6f30e8eadeafa2d7280ab0970a4754fb09000b0c81c120fce087ec88a1dce137b769e4df5588e074212bb372e1a83af89e21caa72a1cfa3d61e7a2135d542aa8d4aacbe07dca3b0114eea270f2d3396c8ca7f91aba1499ab4dfa9577a09d79b9067dc8e089ba4b4312c37ab0eac6687889d8b94a70ef3ddf40d6a9e986b3cdf9688a5971bdeb8b08080ab87c7207ef46b44162d5cddc9f7f389b68dd5580a7c7fa75aee9bad66ab515a4bb6b58ba032f98c155b8cd1064e7e1215dedd29322966da2d90bf98be7a06c9ce6ad593a1cd2bc2829d0c5b2d469bdb2c63881c043a0c840f575c59ca1a64e0fcd6a77f798192471b03b101240628a1200550dcb148981ff337d871924d019215761a742e3cdcf204f8ca5e4654fe68f754ad9ed8eeeef9a81d971a7d8a87af00da91a64388bd3e4ff8e223b8bd445121e4d9da23439baa9ea4294e08b942eff676d6f2b31c91f67d3e6d9aca85055013f4b2b1341326840f2251396511322ffeac542b0f2db43a1e2740db5073dad65d2ffa649685cf93b4dc19d38080ba43d4a2ab75cad74cb469e0f61cc878c77f9400a119fb158eb298bb2876c560bbaab2195ef5a235fd5faf03091b75629193d16556c66e2c3a4241654ee39be5db4f87836558fe50242bb644768dcf8299d91eed188ff3ecf631fa0c28d68ac90c87b07532d4f7f75b2801b5fdea9f3204df3bdd98d5a62f543c5cf616168ac5d31e9404296f7f6c122a463619a19c1b20735bae2c78352b57ce17c7543b61f61a1c102603be3f7b2a6e82aefb9326288035b8ee075af3c7d588ed3ece43154bb70d1ab822d1a2013ed730be416a3ea03b71ce29b5fbf58688b7686c44ff78480b7694bd1b3308cf5f2e53856d8012bbcd08e217fb499fc0c0698be195b50ac71ccb313cc1d502743ed5fa37f51b1c790238eae19cedba209f466023ad179625b30cc59261d2bad05368361dab818c517f402d2f3c930b7af0b2f80a17d3aa3b425900765b82365a4f8aa22ae5206e9a788560a3fdcdcafafc3c06f4ebd99245999c79374d2731ce888f712ac933ed656a491bdf796524a29a54c01e305c005df0594527a29a593673e6d633a9f8af8c1991315b25cf486e441366cda8ec09db56cdc663f07266d6bb9a8d0d5d931361e6487709b7d3bc7ab1bb343d87bb42d9de950dae53ff6af6871f01ffb74e2113bf4e4daf673b2ed7f5604b9afce87a8a65051cdf096706dc89bd5d52e906d3fa7b3c7dcd5b1ad5dba3affb10fce6027a5d33ecf9c57778d8e66d0ceae9a42d6eb2645e56ed3b6d4426d3b7577dbfea4390c61064d273604a5c22be0ce7a813a2a64a9e8cd041e64b7beaf2f0c3cdad5cb07f68d79592e4b86ca7a5932f66b1ad599aeb21c5b65a95051edb46fdf4b5e543adfeecbcb6bc987ec0e435cc8968123b566091046bc422f9dafece565e6ebee9aa6508a1dd8d4579cabbb475729ec831994cb610862e00ed4d124478593b661560c970f795121efabadd57eed5a835e4c0778500c2fcb7811a37a598ed96790b7ad0466549633c37fea6391025bbed5428692f62d01ff9940995d3f27fa9870da3ad8faa96f65cce840ea52ec2c30a5c1c2c6a491d794b49e1c5edd47051480ffd4ffc940ca39e7bcc9299ed820b52d65dbcd7f84005065c800361b904dbecd2600a00e32c89ee55b5987b432a0fbe4be2d2ad464bed3360d1b94e10e1b9ce1144950a1d9200d65952860b65789e2b4a7c823c727c00eda3a8a09ac0d5ee997e678806efc35245d1bb4e17c13e65863521e98ed0fe41276186483399ce2881436e88552e4b1ef55a638e1b1ff8553fcb0b6fd19ba2cc74ef94ffd28b22d45af3285cbb6a254d589fd82899dcb41be6506239b1d3144e7f73ee7c41bf3a1f9d5bd6c960fd9af56cbfe70219b6507a831bb566b43d60cba4e5bad3fbb2c67192d4129fa16bb65c7462fc70a2269bb973563bf6c988d63b9a6f420573350df730ee6f5dbca4fbcea3ed2f26d7bdcfe22adeb6d35fd36fd1ffefbd86458600a556025000dacb26e9033e7425a4f0ebd7a0067100f8c6bdbdff61f9143fbbe8f3f33da6758ba61e780bced33a80ad896626d83d2684bd1c40667c8b4a568c106abcd2980ffd4cfdb8abe658617b6144f6410cc7fea9720a26d0fb40257a0691d0b88abbd2374c2abbe0675ccce39971510635b4e9bcf0c9243ecea64e704b06b5805037d04bb41985fdfd69f5fbffe08768f5328a56676ad31a55c536b9d21584011382f783db95a12e446172d2328c18d94a41e84d05a6284658b0844745e666e40d101ab086b8bfb90a60710465bbab06ed8a08106223ea0646141081f6c809470856a0a15246fa2986c29b13aad2d33097483c68b4b0e2276d8a1082d5ac0ec586fa4f6072a128cafee9edddddddddddddddd7fbebbbb4b6d55b5ab0fbf2aa373ee9a8ae24e7bf5e974ea506badb5567c3b10779a7e08b66da70f6b75eded09b35a9d529a75d538ca62ae50e9fdecfe757477ac6bf7de5bed64c240aa9829a8ae3e17d8f18af8b3055e051f05604fc8234391736256d326dcddddb350c6945212341a89122376744ce8daec440e0a194fd80352f055e059901f8b77b417541f9a62aa90ee186060cbf169f32f910348f1d7d8f6e2ecd9bed682a87d6028cb99699d11246825764cd448c89e320a9e1e0a2ab0e0c5f1029f142a30b0d90dde00100001d87e6c3995baa8b1ed6331675d8c40f49fbe7f1f9a140b81ef379a809d4517d2303450b69452a09cb86dbd72c45ead70a6decb9d536c9fede17c666797d8388f2e8212768329e8063108f3dce0cd66cba1a3535b89aa9d77cb36858cf217615d1d7d6fb7273ace86c106a98a0dd6141bb4e1bd608378dc601637e87d166c10ac608321051b94f56c3087678333141bd42736a853dbe008131b24b1b3415a58c5123b8b4534bcd1faadc48325681bdc21b14113233658d3d9e009bd4114b30df2e46cb047b6410a2a003768c1b7c1f73628e60d8e788317dc0dfad40da6a01b5431378841e881370ce8946eb3e5d4b1fa8f7d6c9f5aa2eb64f6257c82e2ae803aa50823d4fda4e3a980c4d997383b1e8538fb9adefc79e86cd39fed2712d831d39b6b7902a77d1f94936abe30659654c01c69982440161f92235de33ff7ef833694af3ffc4f72ddcf59eb5a8dea8b0ad3ac1163ce281b5fa633ea9166cdc670a459b5f8880f7dfe28a539d4b41068b499e7e1cfde1a4ed922a4945eec01683bc0e28c2983ca96a4339492295c02e5a4b55a5c29978c991a10c4a2b74119622c628cb1483798432cce5d450d56acc03767f6e5e011537b71a6de2863b492218396d9b3bd11ef037d6839d4b4106820187ab45a8f974399082de85b99b5b6d27baddc549c8165c8b025bb63ce096b538e7b855bb5e6e4c85ad8cf91855388058b695bb3c10bd92ccd6c0681ce22063d82c483b51a899016561fda3e4287462b11fad0fc6b0ef50e09590c1068b4113a25be844c8416f5b3d3a04fd3a74c6f4ebebf11b21694849485393217764726428beff19b086d9f3faf767f76ad483728438be516c50dc417d33659c4a0a59dd26bcdb1a3dc39321375066d7fb016fa0c92dd10944d815ef6f20f3e9b613c5e8cbf3ab0e99d7eb33f43b0e99d6eb33fefb624d8f4e67ca47af9881c5204d26baf3573bf28957cf995b3d62eeb97a66165307416c195c1c8b3c2a8db5a23749cd71a914cf80c76da530251ddce0e95a37b71dbf4a74e54e85a51240fba5bd3366f9a6b45852e17bd6dcda0fb356d5e3ce89e71dbfcc99546863d7fbedf607021bca3b78997acecb9db6177b9a810e5e261b8f6ac7ed6a65b337775be5f54885e4abf6610c55433c8f74de342f8be799957473bf1ce62963d39bd55e933c7c5cefc13a8b01326e39f346d57babb57ebb58e3997b252a4edd433a6fe95cef049e6e8ce6fade193bbd3dd763ad20f0c14e991952876f88fbf3f8be96267fb96d373aed4c4b6e3dc9a36ff6c8f80f6e9b495524a29a5f268a73ed524a594524a2ddd795d2925d3cea618534a919aeaa7a1d75e0e6ad43164a23876aca462b2d2ac6b606226761dadd7b45933f33693808c6c36ce8bd5c0f4ce6a2ad19d975c925f43f4ce9e24d449ef153760b1b3da294da1dbb414e42e81b486e7c475eb6e07bf278b4165302cc6584eab9b66e66d7e5797999dd3136ce7b48e334e2f143af0ddb97647842735b7e8910fd5973f6f9842df653303043647990b139b8e366cdaacd84d9265759556dc2420db9e54d68226c1618f109da34e364c47dff956e66267e7b11334785dce9c9dede2e22c4dcc4841a573d4893acda02453e846cddbbc50df0e6ad7f6b5d5936a01cc33c85d5b59e79cd3070a4a6863c2a6d5c6ed5857a880704081113b418dce2d9d4e67024e7ead07ec59c0cef978c940834d77ceb75c404bec9c1780d2a44da5926cda94be1fed8c26874d5fd7d09860d3ff34559bbe2d0d9c4d1f286ccba6bf82859aa64dff05934d5f060c9b7e071ee4b0e97fa086cda64f43caa62f822fba066c5336569b8a09d9542ccda63f652d28d4761fb255082123845c033b80c8c043520d43c0e4e031c4a69f01a5b456939452231c5a5d740cb16b6187a316c7a6417ad8f43358b1c114aa38fa3284e6671772dc9c5f4cc85af434c11ead49b8346b0d6993263fab239f55ae3ae9106ead9a564308bd86b331b39e28dd13a57361a87b8484a09c3e6d64b41c7d5642d602850e7a8e0dd0d9cfb9552da454948a5a51219e19393f3f679c5404af989b5e3b83b326e65eee4291e77e0d1ce532a259f496bdfcf58c769cec3ba0b7fc21fd6f246a32e37b59feef1b8956d091c836d22c9be3be689e77fc60daf2d3a0379a15a487117b8a5227f2ecd039b9e399417f3e1d2917bde5e799f13dfdef471d748e66ed6cc36e12e83e3d3b5d1f74f9410d1cf8f789bef7be062e57b3323c9167fec733efcff0fefefc3b123599e38cfbdf48f4f4469fc829b5a3f61f9ac73ccebfd4717e810d5dc79c74dad4ca92fead3131a0fb541946f8b3f438fe433f8b2ca89e364a3f8b95c506f45fd0209a423c1c706ddae5f47548699bfa535df5b0bb83e3411e4c9bbd3a0cb8cd5a19548806f560067d3031e04120d0f8008499cea4934e3aa907fcc74e3a0208260e6b41030790042a4016900cb50f7940c7854418c36a73cc491bdbd231e7426cab698860daec7b3a8bd6be87d1184104d249461101c924b9934b3289d401ad60319d6870759dc0b0f0851394527a29bd01634aa0464c385cd1828298314e8e854c9b4fcc0b3f7c74f1afb73a1ed973cff2c6ccc6ef5f7b00429284578414c1a16b9aa635aeab881827149a8031e130d0c636cb199d0f1cf40595a32936a98702c4d43cb169e2412a87992c202a7c595274496f7b9f7f2a595e121530de03dd2cbff342c7a56949a8681c6810a26b9aa6b50d269aa4b5fe22ecf551d43c848ede9cc5ac5892f096dbf5250917b163a67c78570d2303d2bf381ed1b7d61fbf62ac8566630c866be3efc1185f5b6b7d1f814c001249296064a68cc04993021a220a4313274a38123c8b8517640f615c6c4b2f3c6cc8c24d51c650020ea45dbcaec670535c800b38b0761103859ba202d0104d24aa1f7024c8326fd4c8100419299cebb0848d176e8acea506076622822c39e140af0b9026bbef06252a38100ca9e8696ea06117ee827c45e140594845e934a604859ba28765e1c09c908a928513dce8705394494470e04c3675c5b08423810145bcbebc082da08900f9a07693850a3745bfe2c2813a3e806de1a6e86ab61881a1f2859be210160e241152513e61caf2859ba2e4510407d298d28495b8fad2466a07aba80a146e8aa20f38d0840d287885e1a6b8002a1c580ba9e85686388dc9243680e87053e4c10107a208a938954c51dae1a6e85a9870204f48455966f7c216111cd8b3b56445aa051c48011329a70a422a7a97283aa8705314ca81032d08a948f43786ec7024781521ba4069000f1c09288ea8385b0813e44bac061c288654a441a42859e1a6e86ebc70e0f8b4e5eb82908ad3879a1d47b829ca5d130ef409a9487f8af3870a39763850850a0c841ca9a1c24d11031670244c89991ee0b9d65aabe73fee0159c4761f9df6e5e1b401ed586f761b0752d36e480a2aa0229fe7d2a5e73eb69b17d16a2780e8ee9b159c5e69fd4010fc467cadd504e3d10f87a6357b5982d38634235d6bf67ce8becd811f826118829f87af957080f4bf8fede643f6bf3157208ae3f17f3fe5d736f3bd9f6e75f4b4b9f74e6945a1622ae7fd377e3f65303c1530e839652dbe31e78db3ce771f7a8356fabdbfd72cc35cb4868bd6f7b7b216129c61252145e7f47eb076c287ec5714b9efc151c594fe3efff714a4e098fb3ccf7fe8a34993668dd8ec7bf0fb3e279af52ffe5a67ce4f5136caae69fbe6cfc4a2d9eb5a67ef7af673cec6d957d1ceeadffb33f933d1a56eda3ea99b1f1d89a6eddb60dabe5a3598b6efeff8bd98b6effb72d0e81cd0fee6d797543e543583bc51823d28d9e8f0afbdf6da6ba9a52fe728bffce7fb9c36dafbf9b97aa008f5fbaefd3d188a326adabeaf61f9be7e0f7e8f828dfe5e7ed9ff28100733e8fbef67cca0effb0e7c48eeef6592dcc9ef3398428efb9e6806f9fec6ef5fcca05c53b3bf31e72668f3b53fefbfcf697fdff7eedfe75cccfe3e69b4bf2abffe4b7eddffb2d6e3f79ffb7c230853234183864d1bd0e71515a7df74045160d1f741f9579cbd7f7fc59cf76fbca38c02e18904dfc235b1f7b558a45fe736e664e8771d91a8064eff157335b60c3deaa722ae62ae04ba5d9f059d80e3b47d2ddea881c3a28ccb4196c5a05fafa848ed0bb65f4db1d9341c7346d1a5f3e7a4d1b956d53973ee57d9910f25cda06f9455728d0e1f63f9b9591f4b1775df5152f94ffe1c31fdfdfdfc55d6e2826f4511e4ec2847ccc9a83dc28722d1b4e5af61d1740451a4d13929a9eab4ff395b7e5a8f8f0fc99ddf0233c8777e0f3ec8cfc10cf202ecfcb519946b79cc25c00d15fe9c5f6ce74c75db998a55fe44e95b52651936b372f75a6d3bdb3cc1dd6b1d73eeeeb5d6a8127d686aadadee4380c21443a81415e8d5829e9dfbe4e0c8f5482392ca3ab5bc8effccd07fa876ae07ab0d948e0f49b9b3be3a9a4ba00f7df72217f2c610a6e7ee8e2de54f6faef89cbd70e4801aaafbc07fe8e784e9dc0a0ea650a62fa8ad85894d1bb56336ecfb6ca3e0c8c1bc3a2af48dd4a3ff22cc86511be6747d5477e75163a5adf90c33139b8569f935bb534a297d7277a772c87faaa5694adb94c40681862ad4a933a64ebde6eeb3365a7ea884bad327da5e0f3927a6968db5d65a6b2dc8145291495aed9592ca0d40748cde3cdb241f46c2b463915215969574c4a48ac81b2cc0d975d1214477e589540a62395a58beb4cec0406baa9f7d9a449ae6cd8924c9fa7de2581d6f7b9fffaaf4e52a01618cb194d5bc79967521b565ad97654d51a09e484511abb24eb05250721e588e8c9a762401001493cb4385222c97124fa09e483da132400d24b0ab1a56e0e2c50528a929399c59e1799ee7756d9937bfa1cb0c98988bae3877c53e005496162e618ae001c6b610264ee5d23d3979126eb8b2064b8e9040e9ca61aa8aeaa4e82a3dbe364739bcfb63449795f58a38d2e562b39ce218f18adef63eff3e6e8fa2a41c5288602294d6587c31c61863ad1a741b63245dd334ad7ddcd0598c31c6ae75e5b431ceb6880f1b048ea08a28fdd811840b2c4861023311468bf3957ff8e0b9b266e70d71ba352d1b5e362b59a5209689889f967e603739bee049846bdedceb2b2c069131dd510ca69da661c98c744dd3b4c6a2e5471590a31c00c86087940957b6a2bcf1d2242a86a6ad1dbf4246e9de7b85e0901fd0c62a93b13233da22337adbfbfc2369e13759158916f1eea5030f1e55647c0023248a89d5911230a02c55cd74e89aa669d71863fc40158ad5bc79d6960b285cb5b7f1b6f7f9c7506ce48052b3d65a1c57f3e638b6641c37e81a97f66214668cd501858f060f40440909020b0727a61f5b0ff2f0bca61d61de629e2092e6cd959a92444d55e117415c617cc4cd67f47d41d2e4214bf3e64e160243a06a7188446525fd35859f5a5a4b25b8aadcc802431a2c489609122663430f5dd334ad3f1c3b9604d1b48b12a40a9215942cd961a8284b5d647a107372b3640b36c18b0998690c906f19cb5e0d4e474e7252961aa0bc2f49497caef42055770042f686c9dbdee73f0731a10c4859237c94f190b4ccf0198c31fe116563fc98d25f6ea8670162ea4b4a7a2283c011aeac43d7344d6b9aa4bfda00aab55a6bad8d529a376fca4974546dea5f4455d980260ecbc3f322c0444816141baeac6081da216d28fdbdf75ea4174854f3e6de67e918bb57e2e4302a41c4e4e6c997383c56c87a822a01894aa8ed59aa6ef0c104efca95af231ec4785765867c1fce7295f5e3862c584c3c174e5034efbd3b8cd060481ddd7baf8e1b812c2b1b665ae802e6c8aae08293aa05ca4910cf53e2b583c3f37ef8508483871f3eb471c12a79c20134e181ea9bda638cb115a7797328ebc28a540d63bc83078f0b045f85271409a18b3c6f099b22385c523a9d6e870557e238716d16335bb7c106295467a288140162938401a213064953c5218728426ac8bb1b330c8155250b1a1b73499634454c6e8c2352f8da7befbd5f9e57e3684c51952a306db40cb148b8423657c25c08acd1656103b653c4a4470f5654a6603172440b143360ba88ae06e788de3c67248c31f661410562ace810e60211657c30c2c215901e1d13beeacdfa6295030c3283a4f45c0d679ff6d65a5b24356f4e95ad74d6bcf98e51b4356ff5cdbd5e2f307594020e343eb674715a62d484e4de7b3bc02a226a86252e70caa4a0e5498913bef6de7befbdf7de1b6669df7baf0f329ee7795ed2bcb952539228173ca62ed02db22f907cc504302624402ab03871d92e2e37e9fb84bea0c1c14b13912629203d79524adf7763f7638c19145ab0a2b328d41d9856a0a0c694ad1a8cb470f9a1288626aca41d2892c4218143d7344deb163f880db1438fad2728262555592cc797a738cb179c242d48407c18630c638a8bde3c475953e4186bb9b06463121b177d6351bcb256942f1d0f648d54282f290b90e6b1c68512deec8081ca064b44c9148a4525c7dbdee7bf015345762b8831b911c60e1183af5c0dc1c3f36a349d99ba1186499a333a964ee0e16a51b2b51bc3d7de7befbd75df7bef8d81c48bde7c7b5ed69284892dfa228fe64ea7d3fd90aee55575b459cc6cb5ac948446f6e55896a625e99aa669adbf96a065457cd9071411a67352121297c517638c31c6185b0cdc9861e90a140f3ff0c06121523b3cef8521d600c1a5858620c0f81b2d558b0a5c74e8b79382990d4135440521883821081962a0e25eb26240c1c31262c9c8bd684d4129c95154f5a6c7152c562ad796229e25773a9d0e497a160b619ed523090bd7552642c9427901855edbb576b8c24a60e05825314794fc016fc2ac0d1d4ef28164e6e56adb7baf99e6799ee735cd9b27c9517aaa56f557b62d401b023d6e9837dfb915f712b12fcd42248b1579f7c5e97bc37defbd421577efbdf7e66a731f7e8062c5c8045858900177ed09fb6a5961e9291b994e3a9d0e4896bbed5a4dbb0f88e190d28586861e7078c6d9f882e978dbfbfcd7e09134e6684988274084cd1c642e182294309e5a47660862978c0185e9743a1f25c6b65fe1c235b659cc6c7b049778a864cc9a696ae810d5400008080043170000200c08060442499285699ad8f414800a569c4c664c3a9dc602a13092a32008a22086611884611004180380634a59a42600471c1dfb6c713b4833f5afd5912d46e3ecb860aa1994b4fe701c9d03d9fbf7d0f1aaf73a3721fc2c5a6dc720f353cadc10a689976f5ed4584f67f911df245bbb1c05828bf6b97e6bc5963b97596571e304aec4c9294da166ea9ee60bef8b7885ee827b15cbb93555268b85b72e1caa921e1edf676c29e119f616a3a1945edb7f5081f2ff974522aee0ab99a9d4d8c9f8ffa94b3c3f7e4c659042abdb51ca1d874290c96ab8c40ba6e2a6f0dd14dc06f2a5c50768f9cc9ffdf0c3c7bac5e93fe3725863a2eb7818d9274e005abd0b3ce1fea8afa200bfc00b31f07805e5f1e29a5c9fbe48a9ebb33cde5fbfd058007437b277aa99f06f481137cdecb6d262fdeb5ca9bbd0d67d76276825bf8b9ec1d81f0f70afb52233375b43f2186e50c74d044f4713b362cfd90700ef57e9298e5fcb0424b4bce990869c108893ea9a5159aa918ce4d0c091d2638fa6510c2de739e139f2b8cf70edeae09aecb9cf6cc3fea8a95de1877dd7c0ae4c59ac0628a53e811745dab7ce274101630fddeb4243adbb33ec0615c86396f65c293b94a2097613c4370161030c82534a4832c1285aa61a0f40317e0172ff07f5a117304965e4be0af44fad6e4174ec5878ceff6a91040acc32965da42149ebf0f5313bb7917ccd6b24d21070a83e993859a63226cde89d2c864a7f3046d2720c90a727133b04a8df2ac9855bd09d9af0b249645e7d4c44f1348df61b9976fc94098e2a60f98d70920c8c156ae7b7256b57b0d6be0e274557fdf9f4b6aa6005c05140e3f55f8a9f2fd34b13c4194cbbed49effa94a341c8ea83fa3835cb5e300a73530847414b118af9efeb1c7336990a035b7b752980012ccd1aaf4beefc9a9e66e51d7133005dabbc6ade25152400b98a547afc7bb23c905f095733484bd4c57fa2dcf5823984656bb92e3ab95489cd9ae7020e0aaab0213833eb1fcac026d2bde04bca15fe2ddd0e450020a6f4e7c515356504cf82adad16c46aa31bd4f2819b73749ec6c8c186189311fac0059878d5a883375820c044fabd2b1e8ad871927bf9009e956522b221115aee7128d3e4f271288e88851882528bc26689f4bfd973ee0ad85a415adc9f73eba60d7a32cf1177111bd8ac9a2cb12f4445f5aa6f0c131bb5259697b83ee38ec5f0587f430690d31b5577e036006882a0be1a8e2292d57a0f49a3ca8a4021ba4027d3b02d15645a8bf3d243fffd3aafa720ad4daef9edc0e21ddb0c746b687d2c27247c4b320eb311b2362c2efdd6a0a9086eeda9cbf093ac9a0586100213b39a8cd0b65c8abfd67c7c7bcc6011388f79955701dfc4431b7d1204947646795663e0ec270d2a094d2e19475d1b700b99323e1562f2c2b566f60ef85a605a99e32c26001925f3db3005801bc1ca13bfe31f67c725f9bf1eb25c3ac32660d77e1a51f2d5aea622c85def1cb7884876c33e2d1ebd98340c78b497af6b745c11741c6bf22cf1f73cf54f82985f10dc632b04b82d4d105ecf2590749c93beed9ae70473847c6c029c5969e2a2406c801091198ea62bc83fa7b3965980c1c6a0e11c76cd9922d3d9bd663012cd07847a6a89df6c71eeb3a06969eaa3844991878e208493693a522013a0db8284db2c5a3162025d6282275e4d071d33c92b4f482d0328f977c60c36090abcfa438d9556580e9e20a969688f49937ca30200487425996c1d22983e8923c8281649b92a306f7eac156a3e8db56be50d88706693103f07bbb90e84f3734fcb23406b13d4a9acc9110c2c584ba11f57e28f8d327916c537021f7430176235a7e9b6a56485c1609f0904b09744be1f67a15340a10f292f81fd156ca054f0a371cbc5d195f4121fb4aff0d867b9be71a0940b5e3c29155986869060fdaa46e163a52c8ec056d391d5624539b13d3a56beb599dd0be33e3abf164d0899cee373113b3ca03df7b7c0aef5c1976ad5175295db9d98ee1cc3343e6822d75f2d147279eeec2c9dfab351bb605d559a89e7bac37c541ce31ab1486bfdaf7607ba3cc7a74a54420319fa5e858b9c651b435dd7da2aa9ad273e6932fa9cb5c88c0335a6fdd432d45fa67bb875d152de836e3cd9fef83b0fcf5b9e3e080c25d0b6de72661d65ca3dcc8abf3597480bc84d4171f3c076dfe23abc3ae6bea51e9e2b5940a742de4b2e9122dc20df62b789a6ef5508c9353d5a82e33321c50bba337eb3a73188087e10ee0422a21c48ad2c7a623f3860131d0d82b75bb830e43bb37e9b8f33656600f510e4bf143cad6e5ed720467ec96b655358571a6bb800f6f7badc895193673270a93f00bbbcb5c5574eec031f3508bc6b7e03138ea2c061485b00f82e349c50eee18440538d0c534cc4ca6e9fc91582f22b8515d15b9994a0837c26459419411948273e4fb51a158db2ea04146a02f9c2c6ea54ae5897c0d25982f51ab6d00cb020fabe82d5b2f51ebcfbbbcf84822c21b6667d23023cad66afd36e830301dfb206b937b24823709d87af0fe19b9a2e1d34760a964bf8f30e474de9c5863b16ae96ac9f65cf589dd229d11daef39593458838e99a8c51fe6fd31b23ed088a02254ac8098bf12b3fca11d670598d854a6a5cd80d7bfccd057c4eca37df63c3cb463f56d3fb012f8be9c21097857d8fb525b7284545f5a7ad7c2119a9e34c2f008617ef482529816a16bafc1da9460cb4da84329846ba2bdf8f2c0d7ff55e78b5ba1a4f55b76b3a2feef87a9b62e1071be1e45358c3f66f9208b5ee537471fad28a9e72af61cb688ae5e86a95e7e6be2dcd7d00017c3c90527c1ded244b242ef72c173dfef41b115583369148cf9f467263408e41c8f505c2d9a0a034afabc25cecb577b4b727530f966e492106194608934af22e163a4509d2882f95db647115d1ee9d6ae62622d788861ee3389d8421afe16aba249cf323d53e26bdbfd87200e726fcdcc72848af3599c5c148f52639a3e07ca7ffb773be20225f873e5e5ab18652fbc04a8dc5ac6df0fbca395d3b89ed6c1f7ed42406d7b8d757768070aeb0708f81b6ee49006170b6893aa4a2e6838feea2ec529f319ec69068220902affc8e31179219acca0a4ce326881fd36dc71f36e34ed53915cf41e8045dba11feb100027e1b240ff972dce3bebeae0fec5ef2ff9cc686a26be631987fe4b1eb4c0bcda74dc532386b3758a9441b0e482a6b35f825f74e33c9bec95a55a0d6acfa32cff7690c1e5f566b8a21169c2dd7e59f7e1baf7c0ce5bf5204340a66402c880dc90d061c4c270088995a3017a0b17c815cfe6ff1a14039200260cdd8ff2afe40dbffbc5fe441235c826499862c074407e47b2ddf50743db9cbbf64bb6fa9f1f70eb420be43edc7d2bb884131db127ea1c510109d5253eff6ed53034733e79173ec89bf1a89a4884f4541e8a93b22c5351eb1f1ce6aacf46c8f4a22d7135e5169e035b092363e8f244fa552319b0c7493d919b8f691ec95c845030fe9de2bdb27225f293f85ee0a48294ec539648c0a907e3990f81ed1391f9d97753d7039c82901c5a8fc16d9203da4003d0f181210c0b23a2bbb71b588a804e9f3b37ec4969c7dcd7aafc4e88105ce08dfd9f0deab5cf27f9ba06685dda5144928e15c7e40b085bf72706f737db9f45b7335d325f3daea1196895a82bee3786e72d8f6ceaac63d2e7f734c352502293e897e245edcd49b6711eca07befa78cb3fb51c95b4f1bd184ed88b67d00a71a04f7f49a053402e408e6da6e0a47a39c8344d210e877fbb60ca7ae7da440c2e5c45dbf488f32a782499d42acab4f53778200952009ba20b3447793eca761277ed1b316a67d6a802a2096e0ff7d3cd9f2f07855485f4d79f42f55ab5a56547600dbf8ba237e10f003d0518e5c2ac37e08969bd5f68ba78d65c941dae5bf034c54637a9be30661204d1dfc7cc2305c6519cbd63b5ce23b97b865fb9f599f717262fe23534e17985bc19ee1e2cb9a447327a8af3a59eeab73a773e99d14445d89ebfd7d631873d38cb457e42e8581a864626c2d62028b0f8925b8124a9a36e9502228abd2255924cabb8e0b84c9d6500fef1a8009fe341560e11c6c89527f3d44aeb1a5ff3e9f53a2c67afaba56f597555146e28eca44cb6145ed1695efb7e65a4d410710b728ebb5099114a5ea2d41b5a870c745842e09ee9ba773c164a43faf39b6aafce8cb9a17ca262d60c98afbbf2c12990848a28a0f731ad86538f40468b96e33322945e33e4b14b86e7bdb902cc1eedc9f13d1d81fdaae501849a15d880bf2099f4923f768fca0bdcd498a53e49fa7fdf8eed6ab71f917af3e53d53f4c445baa1b2d2dc9d1b3e68646302b2743a619f62a4601d3ebf94830b3e8af599cef76709afdde78662d228e79005feda6d4533e5344c113db28c8b9097dc79f9dd0711209465952ba5d08167ca43a6825b19d002facd977455e918aacdba2c610554d44b11f7deef5d46405749dfa014b9991b162942b0476410c3d671fb73b597bcdd6a1d9ecf2e23e7db095a7761bab791656f24b78190f002500acf835e652ab38ee9636a6bbbb2b67970c524bebd167e3ba26bdb6aa6991c8d15e73e8ab78813fcec3d3feadbfbec6a9c76533f55a0b970147298061f896529e2bc39461af8b5035c5f55c37decd4715d6f8040ea3610cf6effbc9cb5fedc8e77f05c5c39ee0d7205d4ee382ce4154f1d6ed03eb9e0a4ba12469444ef299a066a5c33d50a750c88b4c89f16a20b179eb5d558baf1156dc875b3221d1f27d6d3efcf290b391e5763920a40afc78fc019dd0c701421479c6f9ece52288a0e64769ad6290bd25cdb1744f47beb7302b346dd71fc1732e45bd31333492ecb5619b0f0a3009409941fb6ee63280f90b26e217cd10c74e155d2a9c4d70bc6ee86e5888cb8daa1156189e3cd405198c76a8ea2c443cd056d7b25144509fa84d04ffd2664c1704708c78cb0220ed7893f49cb6e12c081646ef33caa0c42f9460837a97c3c71d783262e951c7fb9416ce875b08e0c4faac0188952dca0a87626deb84b104d3206e77cff636a980910dd7b6cd6ba3698837d6812e74a4d3333fe7c9f46f4528cbe6161cadbe90d528ebcd21f197f2bb4bf9cb380adb9ad6d030cdce4efc15413cbd4718c133118b6ab67e85ffa0ded70073cc340cbd88002cb1e37a9a9283ad829a9666ef0c08ec7644bd0b80e14b08d5e4ae40d3bc6ff43b0c425c647f3876ed15eed1cb8699c6fc40c1001fe296bd5c8390ac2556ce7da5213dabcf3be6e70ebb8ebc29b09bfef9c0e40388d397745af58f5cab4ff0b6337dacc6a9b2d7997395976349cca6c2e167a362977da96e13429595ddebb87e4ebda71a971714ef888eb75fa5c991840430438851440fb103da1236fe9f892da0be624e4be6ba09210c2afd29adc1221bc5ef78790e1f9f6cdae868792894029c5eb326f20b33f6c4ea14259e76156bdeb1238de0ff5d67d9606a14fdc73a4cf90240331b02164470f770990a2fd84f58c50bc1de0b5410ab1c16a50d4e9bf9b114dc86ee09483c27f60983cb9249e5e1821db420cfdf28622a934afd29cd768ab9b99432ce22ab5f508bed267014026009f1764f02413ac3b8c743bd4b61a6f5c0bee3386791c9eeb3a0de7554bea7ca1fe4f48623557c32adb297e8dd724d8a5f77a4d6f1d79093adefa4065702137d27fe780a44e6f788b76a84744827b516d13f3d3d4144e82ea4323c8ffd44ddcba278c4e940d5d089e1ab74cde3e090aa8168f4b171d40ba76543a70071cf27cdc2fb6a38928709ccd477f5efa7b745aa8c27862f881889f0dea65926695666e6eed4de0c032c31bcc3bc9c16556a69da796a421af91a0c89e18d7bb272d7feba646a367fd8dd26e27240a38bc2edfddc76d750b9f397304706fb54dd7753a327d56590b77e203061e412764f61f0bfa1ea056f9d8982ba8fd12962f4df05333bbcd94df9eff502b422a29eeeb7ef2657652d168860d19debdc75fb6834102eea0aad801a9a2d2242d1c84b7ead563eb271cb0cb17ff5131c117c1f779c23ebc257c7307350e30bb02c04219521e0a110b25f371bab309803dfcf651ca459ce5e0ed053631e26b3c2890857a594f6fd1813670032ce09d380d72b6a0bbcb923879f9886f360fbd1225fab8e74052974bfe5f72e32e4eee0bc1c2d128548b2f5e91698c87f082ae3d7c7ce462cdce8ede566db87bf3642efc4b3d1444bc9ab50ec9f0d8b7fe3cb04f16bce0eeb437f94fcc49279ec5d2edac2ca803eb2de7ac370982654afb83002302d6c17bdf31e3b46b9f951baa8671efab1eb5d7c4b5cbd788602444f9c8b92fed06794c301c155a2a558cfe875eb992e90091d63a253f1637ee9b01531da027eedb23ed237d9641d5fca6a827f06c30c9fa2f77880dfbd536722da87001ff8fd8198c625911af8639532acf93936d204bff3fb5d07bf1320f347a3a077ebf67efa4aa6e835fb03ada2967b33581f80a0b008f628b5fbfb3b08b18fa1c89fc4f54ca84f184130dac132f7a9243f6f68fd1b9891f4cff0c38bf7d49466d64785cb5e8271194be27a69dbbd06ca1224a8096b831257c66dddf05a9ab7618f993e4d7112f98d578d5e28e7f4d7c00438499c9c41dff52ef98f4c2439cb183532b762fcd3d930ab44e8c03e42868456b7bf6d27264f62f1c9ecd28821dfc8baf3803ef1c30748c1964a6cbaa5b20211052018500b0af182f3ebe2ff9e7ca3d0623ebd287bd07d8bc86ef4ec4d5a18c882e8875a3489e50b244f29829e926a2d628529e19e5fa212145e73d302c1664d180a4c5222ddf7e2ab6acd6f455056ccf0c6b62f106449c52cd099ae1ba4f889320e23380c637d9a1b955e1300d598d00db29f7531736c29c338314fcb1eb4f5651085c93b94c212e2addbc071b6386fd362008bae80e27fb32f22c9055d4472ab30d2f0c98f683420c5c1d3bd2b184557afe27b85ef0c4daf65c50b1efb7141b69bc6110c062893ceb7a512e10e3aade9752a4b5285577c1d70f7a1983ba5f31ea3b2e38ef47c0c3be1bfeec445a8c16b1a88ea66f7ca4ed98710b3783850e8541e1dbae144273e210c2da46201684a97288db1320c757c72d6d13d1d811c08f40bb5f12174cda92b9235c877c37b82c50f2ea75dba91a1c305f5f32b3b379c0a831c23d5d0cc0789b5674c2dae67130a0e554cf7c9c54c66688df795307c641d0cda102ea0b33a22d32301773c8a9d909852f563355cf27384e588e253413a102ee405418f681ac4ca363e38cb151f7380c2366fdb6b19435f597a05034be190011139c83aea17264b1a41f70063c6a04578194750ddc3421f7eb1548262ca314d936f1b90f8d5592d2e026d190d676cf50371be4c072c29bf6f24a5a106e881f6043623c3e8327f8e2c5d1d1071fb72e0c39ad3ee39714cd0dfac749e11d17f2749240b1d3ec8fbe279c85272d8f2241e34d753b0dd0a0d363d0b5616d0283ea49ea7f2ca8b62400b2f1bbe6567bd86dcc9c4b17c21241440c68a3caa501c724aba2be35f5939a7ab66eb7234a0449e6802d6e7993d1d7b306182834a8592a3c401c84c6a417c3483f9d74ca465b3fd79c14ebb0b9d78d3f4b80a9325676c6389e879d7e4ec789b99d4d7dc113c9168392af8653b197e0449f09b888e576252573f290d7653174c18ec16fe05a0b4832575b7af6d688b732d84cb74f19583705a8721991eccfe636be928f4dabfdb6ade4578cdbb44f5bdb37b1c493d2629c6678d85c0e7ea7be1dec89d43ba622bbfb12e613711e990d7928f2586175f8d9ad81032cc3d72e1b9b3a97b6328b4500cfbeb105a2a1f13610da26de3bb1c52481f8a85666ffff0efc7a2a40ecdd75ecba1bf409d7323c9ca9e4a5cf565f644166aa224fa7b79032842e4928c840710abbca10f04b21723c6f4f84583b55651f73ad29af38f252bc2bc148426d6988d05e4b8526d196e9db1b9d0ebcfcf175df45d35c6c0fbdd8c58c028faeeef6fd6921bba1b49705a7a2a3e86177dcfaa963f8750cc05798317e21915a9171814706f71b88d8f79c06be8a2a318eb9ac2520f8ba084b90b8646a81f972d0f9f343b7d9c5a1fcd1ecd0059b8abef65f7931b9101400386d970150382002621f1300b88061678613ba7b6ada24781ceb6fd1c52373adc14d184b8243b4568e50c1decb24c74adc328ee4e0bf7dbcad5bca7cca540d942248f19b07e0f19abbdd1ed7f265e62e6e370b1237df4d6dbf9fc08fd1e80ee232b9913857014e0af865b144c971e976c3935a2ab54a27431023e140bbc83f8b7af9c2c793c9c8a49d9a69b4d03e720773a982f3d5932c1535fafa08ffc3c8b600e13c7fd746c52af1c201bd4fc76843fca4e14a4fe632945c34c8ea93586e2c94401a36835ccf9228c6966dc10d7eba05a1fca8dc3c76e879ce872592dc1837427a712a897705d3fcd7712281b3de6474e0901b11eed200993179105274e16dd26e47408df914378966090540cdaa55011b1b325ecdad300ffdccc10133404cb6e6fa4ba775f637e770407bc605220466f29357004931bde4dbba14ccc19344da0ef9a6e41604d2ca250e7445ee9ab723ba9715554e814aa9bae8c7a8448eb971830c780638321358f44aaa47e43d13989c2703434380f65f195e33fd3971e60f75ebd3ee19eca82d964d2c314c3977ffff2f791008744da08cfac2404e189a9fdb9269f7e5ca3ffe09cf7cd6271924b5abe546e4b0e47a0c5b5783b2e022e742cb0c2ff15b0d7474659f65b83183e90e8b50e47d5a1051870ca683ac8180a69ded554e82640546c8c0d277ce3dbe3f9ec75d6fa9c81a7a351d4ad01729cba9188065ee802b434bd22620af82932cb9ce691f9e516a7469d5afb8aff9b18492c5307c366c227be8805ea79dfd6ed2bb36470393d7aa4d575e42a6ae51b335516c4fe49c9199b0cf5806b9bcf0a395260fe6058f174b4f5030b4a163ad93d37db4f66f025dc1ce87d4580623cb2d38cc44ecf51c52951e61a3f363806e9f64ae7530475e0682f0de2668df18d67b99b37b60f9189a2a8c32412ff818d579ff39d2a96226d27b7fb2f278502c6e37f5c953d25a8ca0152569f9d3980c9d2ed9cac4a45bb5d3ae7a09137c935cd019ade00ae40068b9e03dcc948c02d9dbb02ae4bc37635cfe2437bfcfca3ef19c2ce402f7c4bc0553121c10d3a64e1c931cce76c749590b6650e6f4f706271a336cac87b96ebf08956ff8c98a40d32dbf180eedd41d0ec2ab91acc2bf3a6f6f761ce8f3fa4f219a198331f37cb537c070c91039b90cf31dd5f1b6d6d726ea87745ee650396f76883391d9e137ee4c89a501f1fd6d169b66d9a4814ed181ca9aa0ce67d5faa749bcef29b26b8f615327813645d22b8391113d34d7e02246afea88d7f17662855fbdca68d15d747761792437cac5aad9b02681d996541f317320595a956fdaa94150dbf71a24e88b7d2a2d032fd85f48f62c60a9af59689cb7acbaea4ff51ea32d1330aaec27b832fa668a57314bfbfcc257f54d36c5feb55d0f516799c6b0629d519198e67e83b4546ccbb9409516d54d5d9cda8900a621862372a9bef74ff711b5c58728b472a0dba9876a8cc1360b485a0801fc2b6935b3f9574c47131124fc74a9c403676306506019b3b662db263fab7236291a74ea5ecae1fd74443544e01c3521bef682baf139dfeae781ca00fdc0a945b29109209ed8a2eb4c5a014dcc457f4144bb3eafd71c634b0b5bb985649871ab937df4a172ca8bfc4b6cc2306c96f9241273218cdb1d16266b124174cd7f455de18624ae6ecac7b731d3b099cbac5241ab9ab1caa528151679c5a65f921089ead80e797e2a93eda25875968bcbe3e61c95a2c62ab6b6ca01e026d8ded065411f7b18dee1a3ae23f414621e34dcec2464d239747ecdb6777e7c702e2d33609f29033668c11b290d9f1cf93dea21508304d437e2590428b9dafd2a5c823c4de1512113426268d4da7d7a10bdeb4bd78f442dd8801902d74e56da915464c9578a41bf2e2e84c9329cdc3d8e03e018f22d160a5fa71d4d12b8b20238d8cd8509324e0bf996b1cc1866eed5b82f1c1debade42a4137ffd240410dedf0c15b2327cd39b7780f345f27656ed99967bcd2512e6acae9bd88678d87bfdd821240fdec5ae8678ec86cfb1bfae72947fae7f9b9829a2e2de6e183c1fe253acdffec941f3a1507beab1426789e7d11f7ec08f0d2bf33fbeb1acd461d14901eebc06c161fbfb90c282d923e85c89d9a05d5b5e3802472d5d3239444c378d0ec22f49257906b5cacef6a5c0b3ef0890e0727986da17d0b21b955dc07c92367c30b62da2b345840fe3d9a63a1efc3e861d1a8483ba6ae2152f6fcf313d6310f7f8cc33e27bc62408abd3cdd7c4efa03f0e4c24f7c38e9a2478ec83e8a2e6d05c0b934ab160f6c45d03c59deac03f94d99666311ffc848fd0f9cabdb2c31c86997f7fa6abf7b8a3fce27aa72bf9a57e95988fbb4d19d0154baa372d54f6565ba5375fd001845cfedc5fee5bf68dd4ca9bcd6f2fc20e53a242b2437834e9ac64ed72c40dca23e62ad36a02f10b64497e3d9c5596d1bd4188d365a75e26e88bf07ddb7496e759a99b3c01d1aaedbbc4d0787e2aae2c0e6356e6616796f9b63319cc5c29d66242f9a4ff4a75861be0878e84e6f2303d8742ddf15be0828f4e03aa0a838c5c621c6efb1b88514b8b92c017e2dd6506648af637ebc2c3c23cff827bf1a4ef8fb02cd0852f3a12311134add6a01750c27592e6183c00826251bf7effaa800a1cdd54838f8ca0039fe09dd108fc9ef60f83f2d5caba096c425371e5765b0d0495b872f5ae0ba4edbfbe4bba3ae8b6fffb1dbcf222e0b17b06941ce99657c72c0dcc75fcfb459ec391211a9002e36fed0807ba160a9595d98c940432f50de4da88ad4abaf6af3bf2bbf530e761a5b52e618db167a0f06a16e76cf07a06b349e35391e9f8a179fe31e8b97b72b58b1cfa6dc7558440ea558b2f070e1a7928a4d852e389b9f2522c7be971393c2e4a8440a5d0c793f84ec048b496c23ac5527480208616626292058196f5cadc124491319965caef163012fdd9e4315dde12a0bfa7fc9969005bb635520efd5bb469e54d92d60b3f7eac6c98bcc98c9e7d47a9f69dcca8e2cd5bf067c4ed8e53b65ed813d13718c16100c6459c26eb4c9ef58a44729c27645a9e24417a162f69efec1b2dc85363fae22803c72bebeaaeff4f8fc8c2d2667c16108cd886ffd89d23d9f0b37ad9bd16eb63867a76f5f374ab37743abf043dd594341637029e59df7d452344df405cc66d64b0b8f70e7d956da00c73952419171d0ac2aa94c986cdb102fa910a9aa7a0a9ab8266f07b0cf0737879dea738e1a74caaf3c0e5a0020a3b6318924f4e0929846cd9e93a01a933bef3bfefa196e3db2bb2a14a8e5f5d2b672680e4d561637b00d6b3e154c3675df146fbb2238f05744bf11a3fd6a8ee134810538be47d1a6b2adc598c4e7b42cf5a723ba92c0522dac961a5e1065972c967bc4c338538297e648b468584f023220c5ae89b819432588b07ac6fb925397c8bc29a627ca3618363e63c09aed2de3b78c9739eeb10b4e1b65c8ab3b4822beee0831318742afc358254cab720f6db7301644a68951ed39fde288b4e333a7c5533c6d31b8496d4bc06bef6c823560f8436d88fe1a404d719939c30d8cf3f3d82bacad5cfe05e5777cf07b6e1655607e01d747693ea2e0b6fa8e954636c976d5a86d0d8199fc48ad6cbbb92b22ff4b0d7f9a4eac471d5500cedace429c7c8f445601eb6f3803c97de6bef0e005e78793a0a2f5b1bcf347cac45a7ff0f582c75a5ebb5bedf53bb7aa8f0b99f9788ce011192b66ad55fc7be25820d6e4201f306005bd15c1abad68abf5faba4d030ed77faea8946be0f9d2663537f634e224519d543038d8c4e1a19f5279ac5a855aa7900452726107567d37155810954cf89da0f15070517e4d5654b3121d73795383d5054d7111b0bf7b7c3c3657821924011c391225bb39e59f5399161fbbfa68abf1bb5e1a0f605fa759171ebecbfa9be20addf42be75c1ee4ebfc5091da8fcc5c3a9acad05538cd13dfb380b521b869a215b47d269cb70c88870d02e460ec43b84cce70d5da74157c892a26baa0cc93a7a16bc4369f915c9e5bf5d6cf5d5c8bca9b41e3235853428547d4431331f4e4726e88efd8aeec2cf1d29a01b2a4132ca9756c858c84ef7bbfb3b382a5f96b1b5666ba0d04b54ae1b643644feeb883c6d2cce846c7e5966f52e39821b4858e368185256bc9fa864a4b03bf69edfe671008487010343996a6172ab7452eb0fd0f7cd543b0ab90ffec8b3126702759192608a6cfde1b629a41805ef0324f59ff38197d2f062f3aca7a433debb979d3fc314d8870aa16b57cf0676b32b5842502cf7c85d76611a32feb0ada1f4bbdb8392c78865d00c9dae9dcd2840468b2a450059a18411982292ac907a5eeb7a2116b05cb0ac18c5cfac2b94f503b2e482dc3bb502fb4272260e2d6e56c6040f747efe7aa01b87c69784948d64edd178617e50d9383725c3c14b5ea01f7265e2e2c8c49bb4dac227ed2a13e3fe0402b511e5b0fd6e29283073f264179939f0dd0ae1fb0caa3c732b80e574fd29e4c0afb87902a959ef45a5bccde426698263a6414252595dfc42585cb484673515e0301b5669c1f5b2744a1808a26209463bd4a0f523f37c29dadc121e4c610f88d47df364332ef386a261da7ceed7e06371423b25a17ea97f3d181162edbdcc66524167f05d7f7eb802f56d64852e773e5ed39bff1b684e18f3763b076b8c51503890f2a535695921075032e87a02c6ca42d6b1784d8a3e11ad8373f3bf01dae7b6892874c757cd2ac5932e8578946032fe0215a4b0fc54b5bba54ee33038b9d26901771812088cc44c168103b69164dcdd3c112726259192846b07867ccb87a432f873c63b82b725071b5eaa1228c9e9652dd5a63eb8beab10f5b1f53d6ed52d89de3b7901fbf5077f992a0adbc17bf149a70ea4879c87e592293b46b02bf0724db3deea840938d62eef3dc696390688c114a83fa48188446fa7800e480560d1347fee5d654bd0f9265028fc8c4a8d3300f73ede70ef57b5f72b481479be1848f3808e50f3f68087c8cadf4660f1cb3326a23c1543e7f466b4fe4ef467667671b10567cf52d7c760ca904dc629eac28a3a4c8ce84e4a0e010696eeb5c1522e6413fabfca2634916ad2298c0146ece20e896a4cf6af137fd595bf8c40d9738c17cf05c85d41c6401930a895bdb7b1bf52327a4372be7274fb217916ed76b6ecd4e3e6acc9e1763de4a86663d09e428614aae7383fdef23e9e26cd22f05385e2546e5e4b57cb3149f46fda81e73c17017e297ddf96c6ec447402ab1e109bbe5f73fc4b88e11cb81c7a9f68f2267ad9417dc1d9be687d11268b91d939cceb2cc55266336ab17fd905664f90ee5057d437602035c721fbdab6ebc094384903ee4aa959ecd80b6525504d9420d8883f199f9c9b62bdba451219817634774ca2c284a1e29884cb5d8a237c8a680c8c6795ae7a9f10cb10281e68e54ed9892ecffd0788714ea12a5d2f546bb8fe3fe8a83a80b5a345c5a3ab6317820527f94750c94baa135ec458ab58c242a71b31a24eb13e5ab0ae2094d117204127cb8f8945bfb498d6a234b2c6344298fd3c72db72559158b1b6d963b248539ec0bf0bcbc3ed296239c1651335e70b33357a43e28c1bc54580e3c3f8a746c229c2c9a00b8ac574f52b6aef015d7eb8154b1daade382d3a8de8c4e8aa5d14224177122e80332d24a4c81f4bc382e10ff697a46a44d99b5b64360939ddbf2ca120c9b782fe030a24e54cc7335ef069e61b558b79e02f0891b72932d60dee2a171a08d1a2e45c1435c5a8ebf2121fea39265c0717e460d049e28c9aa992524c002242340d56fad69428afe3dfda5943bcb870225ea958600fddd3125f0faad281f81e0024169b53f6bfb955ab203fc14993cf274ea0a97afb5f4f7f6f5d4f5c7e8fd1dca09b81112d334625770e42a82c98a3e6bfea9624a14d740cf0797102b4c8800a94867b5211918fc54c3da4052049ba62d5fcfa484ca35e1723203073d4ecfc375e77a5eb42059e84f3b1235532888f83cb32c2102f8096e958c90b7495032fa349700ab65d1f6aa37701c5cb71a909bf9425f9a5c65d71b0ef82ac4c94dd42af1f7bac407ee4c98ee527838185e817dcb9485a6fa6faf5f1d60ff37998dda26ef6733b0129efee9ba0750067d3193ea38bf2168e437e7b0445fb6785ed3686196002273948e9ff380d806fc09f8047ae011a3d0a05b6019ce9617f28f2913de3956d5fb7f5823b463e9e6172323b38dcfaf7ba2816296adbba8fbca152b19ef5f9d354c24bc5a85a6813a371890619a224d65dfd849965d2a4056ba028644a021cd043502db2cb01876c1cdfd1873a110b2c51c03ec6470160cc390225f545e63252ad2393623646520593dcd54c0d4264430528e0052599e79a25d4b3a57f7c365992f7b55ac6cf65e94ff90d47caf8f6889db5ee643d466b6deef53f2db025151c9e11709cb55d847d762d1b38a573851ae2b799f72d99b9607724dfa74ec817b8324fd0da436c397758a27aa855304716dd524397d0047297e47118b86e371242a5200291b08f931b34d75587edee7b6c5589b1867e130eb11b1b5f9ed6b73691fd4a74c823a262dfc23d75087f65f1a9470409b742ddf52019617d19488de98a5c4e40e4e3a1b50f33a73f5eeb473e8e1c61c04deb7f1801e012ec1781ec2f475f49ca830956adfb2de0bed1d309a3ab1acdbbab0adf5baf5037390894dea3f9f115768da7ff461906f342e912c90843e8ddc64f9a560c06cc8a5cef53c96832bab865de6d88092f2b676d0f14979b586882b12a0837fbea286b507394ab28106340fc23ac4109dec96402954e708f64d015cc5af19cf334857e62a459e1e96a99aa3cff924efc56674fccc3fd7accbee5fd4ea7421b6b8f646b1824823131a9f9caffd39a97d4062828ee5cb20d042844310c4434664cbae92d9385f548d8277132ebc1d00d20c65b30e1bd4b47734a24d8ea2057298db0d65cb36153d7ccfd713b75e73f986097932426731a3bc89b24a8b1bcaff214e2f202e431f243cdab43a07eb69163ca3d8bfe77b583cb931034b456adabb7c48f21bf6875de86a250aa6bc6619c56b3887ce24dee72a5e4615174bd271fa1f8fa3f46d02f8e4479227712a48d16b3c9607345f1613f74bf8d68272c2d432bc554401755e36d354e5caa4de4baced335bee1e48523a2dd904d4327b69a00f8829379f741c3628d855c5c26880a7c9ac1625830cef88506dbf9f9ad696539740503e66868ad783326bdd4b00364639e160d2eb996af184d565c2f54bd767db5ef0b8ab229fea668f9eb816f1d016e5b4086ebc8e4617a49ed0a5eed57906394eef4657d47f78546f4f909187c2d09b6bd15ce89ef05c83f00bd4dd3e8743fecf1f04383809153bb9133c06db4d593fe43b143ffeb3d2941da839e7cfd8851ad7b929c2bda63d3807e6d40077b9fd914adce4a0e3537e7eb37287b32d20f4141f2f83cac54d3fe37a06f9b7d6601c79cdfe9f53d6dbd234b7f5a1f3f4dd50773acf7d4da674c195fab9b441db9fb99d8715c3481268dfb84128e0c5fde2bc51cf3cef82a0c45a95c72f848be81691ec7d028fe432acef4d4ea773ebf67c2553b83d787501da04c21b4222e9c4588e3ffbf572dfc7b178af2508fba62d32642a2468328058e55cd64106a6083da52cee1d56905d1f236c0ea1b040474b1391ea2ecc8d8f2fd813505e7e5a6d27b425f4c4cae0d5eeb073172a24bf10fa6e0b3fb3e19370bed5a4c4c3c75ff0cceb158795186cc52ca24b6c8dbe18687a043eef33bdb85dc8f1cc526b1e712da88e074a02dad9b871632200e8eb5477d851ff657ac6949cfd26fcc7737e739f1be159f81772a1ee4e86e749a72d772ebac49c1919362dbbb860ecdf5ffd57dc62f9e22cab3a26f197f60f4631c8f51168a717b6c243c637af2eefe56975b1cb4027587e9eca645e415c773e5fac0f05685e009b1f4fe88dcd8a5a7f846ff7484b2fef85e022f3cb8e630606e238d4947760f1e78cc9747d081144d0e9ec89f62843b1874ab6e8031f08f379d5b96db45373c0e920da382fdf06d1aee668336e17534104736584428b97532b16adfd63ecb34b2e8d55ba39a6438a0cf26a7e6f84f341b98dba88670ed1c1f8b1abc8b611150429c385300cdf6e5441c3684dc6697855aa1520a519cac596a198bb9b9229a3cbc5945c88f587f381b0ef93702e4d4c6421398ac655a703461527358f1714e2fff1410929683d89391bb12c664a310f20ac2d336935b5e9821f1a9c3d02a2b3dfed295207a78da4c7e4801806d2feefc2e468e58615c6accc039bf199f5bddafb48f36d85a1f191911681a67e935267dde6b440c431c15568aad2e42a2fe44ca1acbb3c39618e286824596ae1f48c99471472663df7b3ed4e2376adb46a510f1b134090d05b9c4bec0f9a2f2cfd0b75a8c014d1ccdc30970a03b0290a3527ca6e682df96c5c194dcc8a40024858ec67389be72fa58128d413493cf9823c18f8b6b943579da2dc30e0c82ba5ad830403a05720ca98afc9718caa0d737052af561488a85ac590f88c5890d15de7daa45f88b744ba4ccb540cd699c799ff3f7d18cc625bc699d76600d3cc1f69f73e6f7624434ef07246c42d6e93367f60fd0c87acc0fb62020ed31fab165854eb1df1261a6a91b9f68f4b358417401d390a0d4ca4e147427ad9d49f4d2486256ba45bb2ea9d2999951096c425202a49c64ad6c6aa5d3f0a5d1d3404ae942d8892bdb3628d56385fcbcb18e457d94ffafd26cfb0f3161a0341a46398d5e77e06d5acd30a44c74a431f822cf304cf2f9e2e51ce6b38292fa96fc82abfd223779772d9d9d13d3ccd7a883342d038fe3fcc7326d67894143b9a06d0760e9ede01eae9afa71a863b742229a01750cdb602108775009a0d29e31af6e337b7e06b983fec99caf0ef258e5246695c4a2acc98f0cea2cc4c9b6a701dbad47075cd8672d98081edb24be31123edf4b5ab40b9814c2351aef153bb49b742e7a764cc0235c545a1e0e40477bad985aee064e10a5b60a8c02c64972df61a1343826e4034ae24e810a8a944c6941fc5f98ca2b819c191aacc13dfecb8ebe3e9f096972191d3ce4f5acb422386e7839fbef79ad23642be4eb28dd15754171391d83e3c2ff15247de8418b17c910bedd399c5c0adf99261385d78818ac721ced57543791fcd466b2b99443d84f76cdbd8bcfc45ec1f82440aa4739eacd847a4f8faf4e3eed530dc1d86797fd31acc0eff874b76d5ac2aea0ce51b06c021881e36a9ac388dd76ff7132976fba26e57044817a2b42a0f917e5398dc0fb1c695ed729cd2b4b476401c83b2bf0b3feb774a01928cf74339f7c9ae65c08bfb679978b4f9b9c0be5d734c765f16992ebb2bcdae6fbae501c83a201e083d6fcd6400ffa180041fa216a6b34b7ac6e48fd93d77848caa88217e14d93bccbf0d736f7223c69927319fedae65f84276d722e845fdbdc8be1a749dee5f06b9b7b21fcb616855e323b4829c4f900af399e0c26a4ecddf14c7bb8150f49fcb6bfd7a91e8f3d51406e6e250e2834e5760b6267fb7d5175e988bedee3833c1a2083271337a1dd263b403aace7f2705b0b583ec00959c5b5e9b30170c7e64de8d3c01b5017fbd83e0cc61ca2e18687dbc96219d2fe3f451ca290f6631d7438caa5b407c244a256e0592173d99e840877218bb1b7534904b041df26e7a9095b970cf5b5380998c451eba1da8dcedd4f63f24e0fedd884a5357351009789200cdb52132b1118419424d8a4aa1657ac8b18b4650af6907fa6084ca92ddfe7e15a0074dc9d5d5b3272c14b8f42748c7704cd07fbc142438aac554c311764f1e4bce270892e31a9206a60318c358d2d28b800ad1418b8f9835df8a2e6cb181568e5e2227c08e42221b5f0da11cd771a47a79bf1e3fa0af163a2541f73cf1dc42a2d69db33b2657f37c0abfa5deed7c3339ea2aa6478a41340aa839c668ec0ff9385a33e97705b077dd847a4c681175531433ad6d2a8ce2d210514bb31e2fa510098611c2a25a733d2222a388833d8179ca81cadaf03958918c36471f7c9ab51de33c7fa9f89bac4deb64c0b5aa261b8623d5c9296fe1b517466aef4b8b600ea0d5df1b7d0648b89aeceeaedf015b679b0543d20288f794261d48c4801b7d2aef6ca49e654d46d65020205544e193b82d401c52fd4d74e974ba51aca57c6bee1d634a4f6f98971f55d39a2f147a3a1ae982aa931316ab40793f7f06fa49466738fff0ead92764fe9af5cb0c7df0a058a760a4fa364da08afd09326c31bb66f1a647fc497284eb5f9635e5fa1ecb3162e1f5d938623baffa77d709200204b2d1f09ab92a229324287cf541cfc7629989df02879574c002e6448caae00716803a765d564c4489c8968093962087bbce5d2effbcdb46ca81c0b29dab2e149dbb9b2aae4526bb0d8d08015ec85a70df005f88e6dfac1718190b2a7982649f4d290f1fe6e3f24aa1f982991499a61372538c62b354afa181e2401c3309ee4c74a7418b06a0f9cab433af2d78585b2d77a670c1359b83959de82c054ab8b7bcda019a49788667256b24ebe139b5031b3181ad996b4175b9ba9b173654025a117ebbfa10dae93bbef49b7678492c7307237c644f97638c3fb2c7cce41c5f11cc806c1bdfe033431226a2b02ec0f014aaccc2658808c65753ee887794ca694e8423f9cfbd4c4ece32e98109d849767c208f57f7a20b2a36a27aaabb7ddde7efbbf5bca2de516bbdd8cb2a6522314aaa3e295ceb100fca62b420ec5ff127c3b8a0c0f72a1bff79649eeedbf0351025b025a026f6268e5ffff9fd781e8ca396eadfb7bef4d14aab6945834be0622f2df6ddbb6d53d6d5176461884a907199617f6c3fcf803dfe083fb8f6fae32e7d9ecb4a21f56a8a6011b36ae54a4483ae3134edc6afec0b6b38b4ec20b50cb55d4193c61e2b922808cb297cbc11471385c3085294995584498120cd4562b51d4c4e25eebb4de75705d01c3fb69e50e442f0cb9df9e4935d132a6c90e273e58453eba7afae8db4b18c41d9f7119e7575ac877a9b5843becca06eb040703882bd1f31423a2c4b4b35fffff7fcb8e6a4b838e1a5a8edcff7f991412e66a4bdbb6d73aad772dc76ef1ff0753e27153c4d6f0fa8f10aeb6746f99cfb88cf393deae4247d5961e09317121a759e7320fa2a044386760a1e8888478bb98ff7f1bdc6a4bdb9b931b971658064b0f04efb34940c3071c9878c2460a1fc6148814b034a958b105335d7591e05992a8c0841cd630ab08a786cb9009e0566dfdc1546d6993eae99afc80baee2fa938731c5e103c59d213970719e1cbd26b9dd6fb8ff6bb6aa1a2d4b259fa709e67ee30b29e999e9b9e1d18b11753b688ae73907716bee530eabd68cac06112eab96e07284a515e721017940e5a62e318600070062526af99dbafffffffffffdfa486e7ba2f67e011cdf5b48551cfbf9deb3e957be1e7466ff8d9e19dd66b9dd63b8d98dca6bdd6697defd86b55fede9459ccb5700103059100ca6151435bc128bc69adb5a681546da992d9a432a1f1dc3709801cd11419a97cc410ab8eedb2ea88f7beadcc8a9b48acd73aadf79d372722fc8f5ba068b760e432cd675cc679ac87ab7c03a087d7691fb772fa729c0601084b52e59197306e83395a6bfd83516da99058346af821f78dcb6262219625bb45e1d034d3b0936a4b9d26b0d58ccbe27516ee0d423899558e730acc8ce45c9725967363bb5eebb4de87acebea1c52d5b92131df2c0c8b43e3103954b63a5e78bbbdb78f02e514d15496964d09adb474a4d911b31122c0a30a6aa5a39903e5548a18c6d81c60d4c2c0f3feff350002755d9479ff60e8a68eddbd2c807205a18e63f31cb45980e38172ea80e2702369dc61f0b0fcea529ac12569f5b071956630fda40618316505e3d4a1e10cf18bc90de335286629bf303969a4a59fa0560dbb65fcb5b296f78efb4d8750913ab12179681e9ad18dd4ec5e0f16f8ff1f958472b4f1f5933ad2cd2870486184ce0d0f0b1c9d19379528ecb48fbb5419c8ca393ee332cef76e010d76307dad7327d422de17e587cc2302dffc684664176ed148cc69bf7e4db5dad236e40554c71607282b142b4d2f58f2c27b5261c0e0bcaf91e40e93cb663ee332cef70ec26b715789dcf052b644dc2aa3e8fd7befbd37ce8ab473dd991e0be2098311f2977967595ee54baed73aadf7949c0d01cfff9f6abcb1501c0e8703538d25e9d208d869f3abd26b9dd67b8d950dd58a0e6937e4a255fc18418a6ddc5c2fa4a6f96b041a4b44950b6417120a9ed110d251cb4bef3d01a843f3c73414b6a4581a71926355b2c1a56778efbd7f38af0cc115756f7c7cc6659cb32c8ca4faff0f3210343397e0e2645e35873a2d645614574b4e6f5f8ffce81ffda37ffa6e2375f95c7867657a6a7213e5b2650e2e24e6332ee31cba6514b5d73e2999709284fce1a69a343ddb17215250848f19a2108589625e3e4098402c7e9a39421f36da6e59cb1f37bdba2097693ee3329ef2bdf78e4ff413ae092b2149e3622328086a14032a0bb04107af8210b64a00b942c842404f3d27c3744974a4aae1332ee39c050099f8847f280fe7f602219aa4891d3d3b4b1a06f149b224aea0f27efcbe5daafcbeac1c350231874ffc349491733a20083cec114154bd40f142e72bc7c615aeb153ec8cad621c5cec6d356a715f9594a815e2ed9ea61b904c71c3df1cacc94874478eb66e86b8c4f65cb709a861565bdaca328b02de7befdf7efdffff41764afe4150c10bc22b925163a85de8707aa94ba9898924415560ea9184d4cce05b0c875a846c5286e804d525732f452715882a96154bf9cc011085322fcb12ec9764787c3a179cbc9e1636408bffffbfff7f15e06a4b5b20b90f5690a0534f9ca593098720ff6339834df3d9beae0865772a4f5165ffff9137aa2ddd713520835cf7b142923940dc0631a3d39ff9c9be8c2b452a1c0ee782a158922a9dcca20757a78bc57bd6f33beb60aceac6c0d691d6608b897b50e00b8785881d2eae5e340b2517c5850dcb6a82a30964249dc155e07569467eccb573f658128452cf6a524ee96321b7c205ab434ce67d6560ad55d47a11d3e22135215844763ed72d89a67084b5e3464a57d30fc0fa1076a9b2ccfecbeb9b4bbf5e8c50d95281830905cf75776b34b9f5de7bc76ef1ff3f7e934a45aa2d35c4a1ff0627ba65e11b002cc41694f725f1efcbee6eba75f71a43a8da5262d1883489a1b48a12b66ddbb6ad08461c1bad1db20f9d5ef25d585ae510beebb3867059c307d62aecd7ffffff52b26a4be77686dfcc4ca4767a6d1606f386aeb6b4dda95573d54cd54dd54e55d0ff3fbc329766184c4275cb90f47c42b920953f3d29563415bc8135edabbbfe5a3511d52770fa044f9f00ea1330b51d9f7119e7efd3d97394c9c2a9c9332bd60bb1da5d500699d1bbbdf70e2a1015ea20236222a088242cbf9a101b24fde606613578b158a761474c9ac731b9d5f10a6cacf9ff1f6cb53ef28ec023cc5178247e4d456baddf57abb429ca8e47012f214a2947ebcb35f331fbfbf5a74e3a27d9fd7fde6e65f9e5782bfd786b0b6dfeffd61c7c8b423752d4474f0652d44d4a4b8a8ace1488109f184827514957caa99c9e3143078debea5cfe432f40b964ea581c5469c034531c5451f6878aef30c378ab2d6dd3a76156530b0e0b292e2d234b242ac34e589816942c337c24391706d86b9dd6bbafcf3a0c9ff83d3d071f789c163a58125cbc9073c1a66749509cfe0dffff8338555b6a8583cb2488976ad512198990444a2226519376d4d2b66ddbb63e239296e6d76f5ab8795c3c2f58a759f62be8ebf875f4c5f4e5844107d8371597c532ab8c225234c3d46e12e7c41c617657f7be28ef7efb6de3d016582006385da240275891a59c7064ac8e1f9c3c3e3271972ab3f2fbee1db33a56703a51e6d5512ac913fec7b7904ecbed7aa8306d8a52b5a5cc26150729cfdaabac41d6e8babaa7e26b6097ee16d78db7036956cfcb0a072e65b127cac87523dda004f2ff7f8524d76fba86e3ec5c77fb1eb8f7de39af92bd5b3dcf3f075fbc0ac0f4ff1f9e2919455f7aadd33abca2b957a8ae070b9bda990a9a3a4e1d4d314d3941591d459458f88ccb388759c050d0e9028cf2a8640d1fc7840018131a380411c37024ce03c59d00140005096c7c8468880c1d30201285432231200c120980a0301800048202819010000a8d87624a567700de405ed00989eb67c6d2ea2842ab21b96e279de6e208fbd681e1491b1283f0a0e78ab5d7a4c142d37e4db17b50a9c8cd5d317fe94d8173835d8cac71ebd41b70e04658a392565e7a102cc4446ea152db9990e95825fe96b29a6ad34bde4e88d8fa6d943759375627ab07da67dbf585ce37a1d7edb396aef40a9d2090df600f722dbd2b9d3c6106d3bd020956a7469e2284d7994a5e6f7754e97c825405a236fa176542470057311b4554a3c8bb54b502eaa7ec14e64fa651302abfa8d5f9c9806252e6bd7034ed440b78c6fa816ea728c64c6858c95b266973402da9790df6960a440a1267d30e0f0d69f3cb0eda3832ec8129586818e6fe9dbafb1028382f46b37867ad8439e1fc911f546fa111ad4ab2be679c2426bcf77ff43f2fd0ac0242659361b53584f84720796d46b3e78ad8391fca101eec8823f983651ec9cd39c3b5b046b31e1a53afe985ad46646521545f49af9e196a65bc02007b0eadf8942b6107e5184d64efee89f3316a3505d7c7a679bdc0ec180d9d5a239aa9feaa5c2c946c5f4095d797cef48d412942655cbc2fc0228c4e3e35ae699875e37dee2fb9149dc007c2eea6db15e2603f5de371622ab9c86a1dc24f4879f09e3da16eed8c73aea34d7e6e23e672d7ed34cd1b8d493412a9a2f5e35d8891372c8ef3c14bf81a19513daaf3b73e18af59268c9da82bdf4e57894c28805947ceb54bccc61ad638e1a51b1898db1886e603d95b9061e2fce17050dd53ad6b07d482e3f06f487ee343c3ce2792ade3f5420cf72e5e32c2812aaec9fad357021667fc2ba3f19abcff02b52dc6eff12a83594e9b225b42796f0d644c8c6164953b5bee5e68c6fdd635cb5ff862f088729455d66410db812eaf0c994a5d6e999369835e57570c849c291179b9169c1a011830e0a45e5bf4664ba8524706097ad018633550bb77c030f3f17db693af4ea69af7d2831c90b9fc3c10188f183f7e53eea659d84d2bd2583bca602d5c8a5e43605f4014ec0934bacaa3746746fe38ac3ea214719d123f5f01e73a1d95780a1405409fbca8fc57b7adb082f0e4cd89d0c165e2d2df49b8f1586a79a23ca051695fc4db68b4746aaaacbdc0d94aabcf512d2e677b552583290e05c0c31cfde3a0506164ba69e70da224966d66892a5aac313bbdd880c46773dbbf128a88d8d69cea105ec38ee965779d16cd0f14fcf6ce0a3e38d4f2934a95e86bb04b874a510cf4bd43ee227f1c4f42b17d2312ee0d15369c4f1ba9852ca6e1b2e1b77e39c0d70dce3d78daddd218d0217c302dfc7da58b159464dddc9065519dfcf15530205202b649201dc270d7119189c3f84dfe6199b8fea0b92b480887dbcd063f90a71ec460f84e173cc83f78283ba7c1a073ac7528f1363e822a2d508c07ca7be4971be715acc5517aa3408c72e3c8e8e64afe242537ac53a00edb96672e70c43a92321504557c2f1900bc3d8faad334b5c3611c2d473f0301b6910b95c1046f9444e02d02f4346ccfa8e480a5c5779c88d016696649f2ec4b059b8aa954b550208ead3381cedf55d7ddfb7dad484ab59885039df3dc19a9107f96f8a668fbcda7d5c15d8355462df6e8f49a3ecf45c5bec972e8d22864269fd82b2e8dea1a90c4c147c89f3452169ff150f43d2eca540bc19c3251266ce9ec86ed488cf6b072d52a4d84b576df849abf702476c373ac30bb0d5070677f76a410836ea9ee5586eedb817f4d3682909571163027fdeadff328fa0f760696841e2d3c84c01d84c2e45da7b341f4fa8f4505134b56355f93543535b48075310f07517854327058d7cd2a330670aeb0abeb9075ebb8c6df4043175ef473f25e6de0e17a2165d68694a950f37482b4bfe1d9f579dae009548e1e743e2a42ab96c790a8e5a01731c5a36bbc1a0f667639d90e724e0d08eab5d4dd1fc8c904295f49ebafc4a2d61471e7304166498bb072bad3d098e0f67cad1d0a11a8b465f7d89d4a86d9682214ea24c9d65cb6cf07b1d07a8a76e50adc6267099f7a99a82b25971f580c0f8d90fe6551b36dcc12b2f57b87586e6603dc2444e2ed99073de25df5683db10ac4cda2d04aa6a6fe91248df88e39a2208a6fa4ceac4c6243ea9fbfd004256a163b7d026d31d52846ae7a2e60a0a813bb214e909b3b6ce75279a6827f63e2830f64e92bf0d9663f947938058984dab5968baec7e58e0c2e724590892c92073aaf3eb135b2670e9aed3225da1847118957075f019587a5daa7eb40db8f09abd790c7e6f4aba1566aa97b167c4794232d4603bc0087a284a939adca2ebd56fb6fc0742d64950402b6907f031170ecfe8b4b086e4e5a65e17fe9a084dd29a809f247f654efba224772f4cd3f40efc0f2fd53ef2157189ae46d9b146168972e82218a5a373fc5e353c6102792ab690c597478b338a6da6c5a3142aba27304a8eee7a88fa327289596ff4bba9c2fd6dee19e6206880701e526ebc988b470daec9070907a1b59ac7d41831a82378cd3f120e339ac9e03c1819e46379f9158ef2cde50b9ed414ccae55ea11f1c5fd3950fd4fefd359c3f20013610d407e8974ec7d354d40cc446c28be25b7d7d63e380aec27014379316d6919869457fa88c5c21102d5939e9b02aa1c5d9aa07d61135d8184179e954b83b08121d23341e06202a78ec7e07528bafe260660a130f5f14d1cbf948865c91fc80b228ebc9a8899146dcd28efa643a2a19400707307f048a579158978d167a5b60490c29c96acbcd0e9393f6c82bc9a47ab906d7687836ec1a93c8bc89c03ad453bd7250fa1bc6428425286318a71468458e67302e7cadad0d6e75768f96d4653af59a5d685d65a28fa9f3ab7bed20786b1561527193d640f9026ed086525c480fa0d2948ea25250dd9963634df8a58a30d02de3284da6ecaf42c41fa29da42a3f9ff74430c10e5a5fd2d6543f17a6dc9cfa37f59769b6305c0d3e80a104796a57b44e30088da86bba2328cd44346d96aad6f5fb5f54f00240858cc354da859c5c60505c1baf465111b210db46f8160919d4729f1320bd4abb99a6166f02348fbc2765e9c7e5063c24d5ba0fa9b88e14a6610160ac5fd80031abf52f7d9dd2d050814d203be5dd22704b0c979610790518c95e2ff2e938d60f2dc24b9c007e8b61c150d24305735a5119ca8835540ca7b0b81d072aa9fa87a3e8e86232148c6d351e12874d5297d4aca2b86fc5e1622381c99d8d7c17b3269430f89137356dfa78dc4edf7e95bab567feca12b7c21818bb922dfa954e45e3351908b7267e28a176076666eacdb4111fff32b4b75bc39f8cc8f844175e2601b27840edde16d95c129a577af48bcbc5bbed7aba8813f49375e04329438a494d177d906e88c51fdec94bd1b411c53c75e68eec171f4c5dba501f7adb7e0d111ae7ca3da1309162cf3c8915a80050f889b6522035aa0d1faed71ca81bffad9d6509423520b49e7afc1851e9eba3e981f0c167f9f7465ec56fafe2c25bd985a8e033eaad744527f4598ad10a8362689ba2f385ac4d9e88057f5d3d75bfc69a39ef828415b8f1e835f66365da1d8fa64c9c6df9f5fa5ad8be17578315a4d45b7120d1550c9e62ab3f894e8e50640f07b526467042bf278707bdf39de444ab42934e81eb857edd12fc3b8868c9ef17a807992754909607a55718f9c0fcf793c9de6bb83cf905fa08af2004cdbced7e22f3296b17b397e75871feccea1b87d5095e3171f4b1f44fb568e285449cb3fbacb8b64b1699fd9534a6e9c0820083003c104fdd2c2d0022f49dc05a9a2f7ac447aca443265a9fd3f5559a5334e7d7b5d5eab4c2aaa3de249d57c281f5d9a3a5afaf24946e29571a648851ab34eca33ea4ccf49a790fa2ae7bcf9f88c2e1c674d8a9323541e9e68ed664c8fec4bcc673e85b52ddfb2df1d52eee651098bcab94a20c163a99ec71f67aae087503703f9c81ac857b353945dac4d1fa4f63d9a2450b939df0bfacb130941dad04ad328ebc68dd60afbb6ea85e125b1b761ceb4842891c0bfbf611bd5639709035e7c9565f671c45126cf36f74503d1033373dd70065c3cdc1c542c8d47f750fefa195dd0c471206224787518af8a2bcee6e7015b9b766cae3918627d1b40b040ca33fc360c08b3eb13b630f8d5a21501c1f04f437c43e6de73b7ac6d2044ffb94a2548a447dfe5fa96242bd1860f587b1c6dc02777c1c0885aff8851517fd8add58031c6b118d09f727f1538d7439988fa326854f18ad1b9a0411366c929d00ac22db05230b2d5baf3d7a3887ddad0746a500411889e8269255170915d84c6a8fa60314a038d36bcee7e20bb5e75554943b1b44588143cfeadea2a7dbb775284d28337bf2c0a02c1b2c863f4152eb405d308fe5d6bee1633379ac593bba0696b4e7c2e9a23cdf9caea5a5035d6d0192520ca0be81bcf8e4797b7e68bc78579419010838c79f83160eed060f5c38813c42f03f67df79ed1a3605db3431ddf50149fbbb808bb068f03b6d779f2aba9893d644b5dc1c1fd986a55c7387bdcb779d770742daf41a1083bbce9f93d6f2c13366b6997b16579f995dcdbe0cd7ecc5e90a7696bf1a163d237aafb239d7cab188b554ecd043ef7a3c1644fc5a384a85b620943430f3545b6c383dc35d836bf65b03c5fc1608aea4341be432ae44753c3dbc13c26ff2cfb434332477329fac79c74ccd6ec43f94a74fa76b2ec068728ecf80c2e856aad44397402289cf55fed0108c1bf2d41e574d9e762e2c681a409ffa38edb4bec7d4944463468b7aea323979953419e862791762cd89550b69b437fe55a4496708a56ea8abeb532dea2d7a53ef642f05b1ea2f05c879da9a36c64c100cf1103b3abba288c9cdb5fd30617c24775665980df9212312044f4002ebbb534ecdf2aaffb83ab2d054a2fa378f951702247a9b8e3fb177dcebd0f505a4556efe08e347ca64ac818fc802ffada1a416caf2b5954690a93069c75c67b1831d61589acbba8b61f445a3ac4490b683450508a14f1390ef7512d45d12415df945f54d75790add3e5166776281487447b4f8789e90744009e04e0dd983eb9bac1f32a0dfced3f31401765babe40b54c5fac0489f4167a99534d76034a32946c99f5c00372a5828c790ebe928003e866de2e6d15b2db6873f08d2bb32d2d2e33e88a52c2c8c304d7b2110a4647d711e66202a573242942f4e2eb741f9f5490f8b54c7b7a14a8b0ed57b4e8fa0c65b5f5b3c883df1caae254729cf72d27a0eb865750c8386a5a06569088d2343445b37f112e0b25cb7e0424282faedaa3240c9dca238fab23de9835879ceea31c208e967f4a37f301a78ef5a10ccb86d4288971d2fd39b7a6e5f14139a6817d327871955edc1156501b725ebb42a0f6d5f4644fa77335254c7408dffe03ab0bf7fb4e04518c04b626c112ff0cf0674b52892781ac9d8f827cb912bed12ce0d52c330f60f40cef7ac170b6c4f5a52e447c3a74662b05fc89d8e2f54f7e981d6abe54370ca33352a6118047200966b6864b45ed559e2bd720e9cbfb4a44eedd1e02540ef99d7d495f1ef46e66ef40a3b79c6a2ee433eb3aeb74463ab1c336806a2a2a497ee348417f83bc22d33b22e6b97978daae38d50e8c0fd65a0c35df22a58e60541d2444669f59ae7d9dcd2cb17c5e3bab78d68a8848ac1e462ab02697125687db058f756b64c54e9dee53feb45f8d0d27d09e5cb34de81c83f4b3477dc7bb8d2e05d1fab5ac7e12f917acd7abbc46465742a14c128a89295db240f335b4b5039bd28e9644ecbc58e848c310f84cd51bf98a1945b73e8f2d2b2b78b5e791886aeb968bd238bad4422f87c7e6ccdbf58ccaf715e134325c6791165d56b1b0e1323fa38438a44ed4274c4c783a2f844750ee81e88484e7e590f23c9c43824a390919d765ce929488d10692f1c98d88c88d088967f6c499d2947934f20bdb2316158054213949a07c1f1ff10b698193ec488f1e98312ff00ced33bbaba8c25c01d9574e03d94b4e04fa5b4f9ee67cba7abc0e484acc87422846ec0f2ed25acc0bf6f9157d874ce6e0039758bae889a6b8126eb0222eedffb2ac1c44d6815ce313ffe5c1b7df752927bebf167547e88fbbacc0b1de8dd7a38364e334a88285674f032ac23b9a1e1d518d8a234f50688d97fa4ed31856bcf0834c04f375103a0da95b00e607555a93b43561b4c98988c6adb5bff4ae8bf9e11862ee3705378afd36ae39817ac16e215ffd4d23d4af8f65e053d30abf6db3f55cb36c70364a3b1ee89a679fd9bd83bcaee02b256fcb0296f67f325d1127f104ff1756f45720a8fd3ce982961721503ce8cfa6c3cec9614d5ad1eea3fc0248e3ed972cf46786a2ba31cb12f4cc0c4c03d121b56c4ccd3480628093de0c251903331d7a2a51e4a37eb025a28e75a877aca661604a9ddf4daf63880938d56e40e1718ba216e1e509dd36255fd663081541d303ae64bb8af5b3fa55305fb6f82ae3f282b7b255db14fd4caffb2c6f8a73c368d9d8aa7de6f115abe4a72b4ec0641e0cc91cd22a5fc78556502bf34218e64c3658ca7041a2bebdc3d5de810d10d510f9d5b1214931668b05946098c63e8625b93683dc116a6131f2b8053792b565390db09c7dc4519402ea9ebc139c24205276d547e1982eee1a68a3718e20816c97025046500c2d373f68f2c4e40f8ee73f60cb6e995067f3611f436544e379e766b41ae90b283d1d182c8cb4bcede5d0f3005b6deea57fd58ba99d39e91b58ac83a1385a2877ff4498f1e7a66cd2dc753569c39d4e3adf4832efbb682bb52101a91787a2ad043b8f4d48412fabf75dc01908e892356f059dd887cd0c74d871958d69fb08730afb4f5742ea9eba479bee060a35ec23d1d329fcb50f220eb081d356700a83fd43a4e9a5098ebb7d931b77e27086cb5ef340a4030eacb9ca5b2cc82dc1a8fcf926c76cfe12937f4ee6561381499bf3e8cd9d82635bc80990975c027ab36a4934b76a31ea6f5bcb7c353aaa16f926722bc84508747718fc13be86ac70f08d8978d78d7bb5662e4815768fab6252b58f49333a28491d79caf2ba06c61b0648be6dfa8125cad15312b07cfcdc6119f55e86305024c4f402abf3a43202baef2442c947eda790638f7c384b4283ed70ec2d6849c71a57b9c25918b92d0b48770a3fb0a73fa4e8cc49620452010ca25ff21a29a09af4ad2b9c7cb1f9ccf40f12354d6d448714a304b0c4b09c5e4fe68a06d58d7d393fae229fb3226481922b4da99c065579711a844b948dcb160d002bf46dae8cdfa7d151916077f15878976dee762bc0638593b1dfc5644762e94fbb19a3ea240db4e94683e020b92591e8c3c09fbd9a253ac2823f539d98a342b43f184ea54896093693f326649d6cf48c07242186b16c27037f2df293f10304d0fcd626b726b8014485af3963df57f835ffa67c6e18d32412502cafb9dd55adc1e51807b349cd9bee8506dea226f43aeae6082241283478e47de6711080e640c6f217cf2dc7a1f96c9551c5e36bfd22da871a6d14dd3f0593b0b62ef40507f51b3791b193460eb2bf64bec73074f63c2dc1cbc37addf8394841e423fb8667cf9447b3638d1d41dba9483808249a398ee10c88a9461289c915c2846db8e6210d684ddb50509910a176e8e147687246cdaf0a53182c5a8094533b20a5abf0d5c62034ba84190b15a01f90c463258ee7870882776e3de26394ee7814ef3560668bca09063cfbfb7a2d57dd408a7395f084b0ab744ed4c9158edb31148839895f0a331b67d855c5bfe55ce27067c3fb1b3865764063faa2707b8e59fa5b26647ce9cc7674912218ed076541178e9296b2c5bc56b0e6b8ba41176d13de9b1e7718428e9819dc18a510c89346407615f7e0ec00b6430dda91decea95af344217229c123976b51f018bc5ae032f6f30fe0560167f4df234504880f834b6ec90712363b4496c0024b3ef66787f5883e9e84d89e70ffae75b72fc204dd6426e0d796b91ac5051e6bc6587c96fe88c3e342ab3c5d1ee0620cc3cd403b9eb5ccafe8e6829160565fd8bcc5f38c21a4c9df3bcd5786576332f6dca7250a428f61f7be908c270ed347dcc21f78769535c7acb8698c411eab7325d41046be9e3568601d2fb19ec6a0c4e120c4d4c0681b86e403d0cd8fc125a834c70bbe93b82a111dff334abe3803fe894108d82dfb96dab6992315dfb8327b8255af3c0234e0d0d8b52db062c9e1c588c8aacb4bf6d432bf07abda2f503b0174c82a7761b4c92694b2e52bb072193845a491152eee9ceced0c0970e097bd61e4c6e7a5b3c4dedce93950f5214b97aa471c9fc9c3f0b0f455aa2a35d033aa90503b2921c09358f21821a42edf7ac6909c7403c94a77c0c6d68fc2e168e4301e265ab1efdd1117c92b5f847645d7c5c5307ba178024140273e9a52cdd35cd4618693324775665cea6fc6023401213d484c409a2dc1d30044ccf34e5a98e5feac85c3b3471e33ea4f28830bdb5d358fabaca478d4163b13d40fe8933b8ca62b5cdfe6000e2bcb22d6df44151ad2c78e6523d7a45426cf5df0780649b5f1d669f4a5df830876d604d042b9b1707eb5904ac47045e82cbc27b2a18ed2554461f779df813e5010f616bee010b0db2f31f224ddc8fc0f14c8c0a015f6dd007a11a79be93ea5129da8910ac624451ad5875cf912d41c2ecd5d71e80c4f1f8174c4f01ead1d02e57608eb2eaca40188c6674dde8c189c9b5f649d9a064266acee0de5cc835f6fda7e3f10a733d70060b8f77e049e4e26f42330f5f729334b8cd992ac929d8285ad38ea68612c71794281feed3cdde22bbf79652cabde59601eb02e402ca022ac8f8629f404f0d878f263bbf2d67a0dcb62021757006f951bc01f5428f212c5944944fa6b23d93b7dbed289b4a17a3c084fc99daf3b49ffd0be9d29dd80f3ab4b564527140fc420fe79cf756adb53693946b7889a9e929859929de3aed1ea42d95854907f1a48a45d9cbeab4ad41b877ef03547279c44053909414a4765a882dfc8e5e45b6b8f78e43d1af77e2185a01d5a31e36c6c2589602e04e1530484dc21cbd5c6b71393f32ccd98c1176fe02d2a0cb525e7565a0b213765c87ea41fee01d1d6b5191325604ba513304a87b3beab813a43585ba1bfd3ced67ff1be81c825bee0c85b931c28f4f0f2f3a0b0b595e2c8039e25dd738099d864e44a7a293b10b69adb5d66580700e6827f9104e4319750fd75aeb1f64ae61ab649652c5cf92b71a059abe0c4540a3e69b73cef98e5c9aa23b44bed683132c0fd21e013dcc44946b3878824918db4ba200bb98bead15be2e5cccb7130609c491e1c713724b91d2d1ebd9495d3f703f783f80db4b968f5e772c8e47fe6cef04e5e8bc309b97adbce69b73ce399732a1c9e5fc07092ec7bf73540a894fce6cc242d1a45505b6c37bdacffe4f9e094f280a4e92452a589d2a4cf1d97befadcb35ec230aa6127409d3b4584375c3b76a34ca968a285d381ed0488594a48db37791910d4c7cbfdd0a3dca37e5d3e311a1dbe1eb80809b15949e959298ab28ac63084894984b1297259673ce77fc542e487e45e773ceb9ee2ad73056d76251e8b6628fc4777e80ec9fa51d22ba4632d7709672458de50a1bb7bdf7de3d810593ac326cdd7c7a1d1c20d55f678cce24b1bcfe84c7f3a911b2b0cdc65818cb9e3c25600ab574a581c270cdeb913373c45c3167cc217356b92492d7aef7dd7906e8e5731413a276a105dc538e2b30424e5882d8180b63d95e4e039f90004439496a1e4541f5f7b49fbdda0313c202ad1dd7121f19e2912b4cc82fab4c07427078e4a5e9042b41be08318d7eb2f41dc4be61ca6a0710ff847befcd4bae9d03550d7cdacffe81df6e23e6b27ce9deadc68c4292d4033ba18e6d3bdf5c9b4170b98679fa4410604c29f7b49ffd033ded670fd4818531543aac9c80a4844148969016940b71b81af9f494682cc8883133258b0b45a6313ba11aa92a9efea9563dbef5de7bef2bc35cc3c4a23155710599a63584b670a530c6084f8a182c530060a9b274fa20da3bc773bb6f3917a2bddb7bef0ddc38a8ce2b2182bcdd4ee619f210798a3cc65c0124088daf0d3e3698cb500814141c54ca00d9128188170f506694592186449cd350c1a2f2d05918cb64888db130963d795a6bfd03e61a36fa25b1bfa5d88fe97f4db4df538bb3dbec46815768191f52153488986bb868445aad084aa629be28d110807a0793a07941bc1b1336a6388db0b542074fc21c09614b46fe7828af434fd74de4de7b6fe130d730b168445a7114c224e729093aa8f7de7be73ded67df81661ed55a6b9e30d7f090476479c5d867c98caf953dca47563ffa50fed8180b63d9b507f532d4f0b49ffd7f617b9c73ee33409201037890c9e5b0611caad22f1c4b90d23a75c1de854ffbd97fe769ded37ef6efc339e73d2e245eb113ee1f06c426bb2d2a2aab0fc01051e2a1416908c55893ecb51ba0889a06a74f18ea5ec38e73ce777cebbdf7de7befbdf77188e8ee9d54820943316cd9c1f36ea5bb5122a12b2b9aa7a7a727fd6c8c85b12c50d14e4e25ab925749aca456720b7b1972e4c92a2325a58c0b3f001c1df5a23037dd7fd68397badecf765c1a47526badc72c730ddf743ea214300491543502aee2728985e848085f7049296e88d02eb3302e1bf9274d67f0b8c59023c9d0132c5e92f4a81ffb6715360e312101811c0e483213a5868f1a545c5a42920431953e1b8c670d779e8df34d32bd14c7c65818cb5ae5c8d9014468d74b724276391661ead2f7872646e30249567e600509ea7222c245ca0827d2521207a6a2820a6f8bd8180b63591db9e906640295e6f178ac085dadeefb95a4986bd888b43a9124b9693f7d07e4d4f9e69c735ea42ad7f015963e5144eb7f5415887d94c7efbd37d732d7f0adebd828cdefefab4ce9a5511c54d2fdfb7afd4275d7a9aca319f3ae15cf7edbe8e02a4570b75787c690fcfbcff588c5341364187664b9bdee676092c81c5cbc24a8e5ead37c01b48e9147e0e3328f505fc11979043e76b9b96671d9c3b5993e1d5c42996fc34b8d9e7fd692ac6294c5370b904fd7d4ea900e8f51bcac25d7868d68f118c5c4b5e1aa984922bb267a76e020460793c4f94bd786bbbe755d3cc58395b1dcd4355f0903475c48d0f32c55145cf3d97cbbf1a2ff03c12012127efbe9007a849aefeb084602a6f9acf9dfb6467e8ce02a058848d040d4a14fcf5854bbf4882cc36263fb631ee1b68f16aa3c8f482c2bb82f171f91471e1988693e7b8110a4293c7e49576ae8a52de66ed9ab35eadb9e55d3547d1433373fad021722da74ab3110da54dfb6b70cab5afdada4fb777d6cf6d94ed5dfaa0ccf3a7f2d45d9cf1a164c8c5e99966bd217a06b9bf6c1322dc5e56452929bcd0e458dccb897930d37bfbeca0e98356ef8a804e8e6ef3a3d935c6cc5cd47a72c377dbd5ff8acd140ff2bd3cc145a865f63045e277ee55a4e5f3fa64991be32adbb26cc354d94cb9a41098b33ad6635c5e40852ac0abeabf9f4b344bed906c4cebb662430ca78f5aee7561159e564caa7733a639777dd4d8c0c4d1e255a6e6fb6c189e15d3bb00a95652af441d113a67755b770a460f5aeeb91222f29a61b8d50d5cd8ab106d19b6d1c58beab06285b49272e27ba77ada1b29123da9045a9a8839508a9acc6b4c063a6e3a501fb01c33286e8335bf8f5d0bdd906078953962ec137db6015aae8a8514763885e1d1a24ac10e55efd3a34485c81d1f2a24f037ff911258accab4303b6c2ab7568c0b4bca88c06ecc77bca7a6ec0fc179043ba77f5f0012f97b4846fb60171e4a5819a6894ad416a4a51a7b8178e033570033550558f74be79aa39e71c8cf0ace11e46d97b74ef09bff229b0c3147bbedd50e8cfc8434d1e8aa28fa228aaa54a30ba285adcedd634218a3c1c82583d068d75d6da9cf26d381e97b1f65d746a98c13755756a33a3a8e6726d22509a9b0690737edda4cd4c24e6114d266e41e0d8f3b51ea2a8d6362fae39df4d1c6a94c7aca518f268d41a4af9b8368cc5b7d64d2f10336d935f37f5b48687a206a0a7251e4ed01004e5b49bdc9a8b8f479f8b31c678ef5775bbd5d7fbf1062f57033527d37c4e56caf039194dd3343d4f143d7bcf20c3b9d65599ed35cd89f3d7ef1df7c7af41874980a229e734329c65b698bb7bfa2bb751f1fe736f592cd3d8d6f6b6d6362593a15926eb4a58f3b39a53178ec79b6be3e2a3d4968ba2e8dea7aceb8ca25c86660950f33c914260a54a3a7e26c26471c14d8bd3e2058c1825724adcb43738376ddbde80621881e2d530eba9684c2ab7baa63432558943a6a64ec408e862513fe9a29ed2c51e74cb80f802468c3e6b5f9da5e19430018bc15ebd78c3af793312e26ebae090a989e7338d3266e87c98ddda57676938254cc062371cecd56f37deed85040edbd301f4c5491d1d8c1719dc7c40a318322e10b6a8c005b23d6b4955bf88819343ad15cf72d6b455d755cd2c5ae09448d39ade332ebac8236c3d031339285ec898a1c3a2457fb13091d33bac9b406122a72fc9a66f298fb0993722d0241a169fa64ca360aaeaf2089b6985337938d36828eedfde9478b1234ed377ee2419f4ff51cc9e96d3a2021722da58160b03d17b26b28f62f6b49c1615b810d1b230117bf6ec06c1ce68b3fc678a3818828cb1d28693eb6a12cd90c1417e93cc251d93f802460c8c83953414f76f6f4abc5819335e1bc53274b0d22c7ec1ed9abebaae6a5e53959645f44c5ba1fefaaa4c8015bb3028f67b64dc6d6b5d5bbfe0a6c569f102468c1239aa7e95e0379d77f506e7c6c6b6ed2d3d120267adaea9baae2a1e13340ad7548dda72d757f57f08e1a2c35e13ebaf81faebdbd47f695507b57e1a8a3b98efcdd6f068c47253550775528e22a8246dddf7cccd80008a069318000005000c8371200bb32c09553d1400072b8688bc8470a030168843c1b0400c0604836130280c00026050208a83180aa439ae92bd6ea36dfea7ca910ad69099128a9eea697280d3369cc7d579791f71ef55d0c6b560827f971dd2462687598d7298dea9323796e3e83c9814d46edc8ec3e4929af372adf00565c893047b1cc78596fea4576a8352b68c51da07d49b481bc0a7a78b99be6f301cd1c4f6d05bfb62718ac0fd4d9666091befe28d53e58708686b5c710508fcafb438b3dd3eb3da8646a8a4b0cbb14e2167680eaa6b92c6ee086c210ba929fc25b238a188b74aa3cd7d19353b09be55bd2146fa56a7e07e4de14ae54522265e6d27bbc1f28916c5ff4e2d911dabdea70697b8b14b75a9be4b465a37ce12532d0d64362d408338227cff42a274c17d24e205827426c2e94eb5319d411cd3c96db116ea00cbaafc56cddee200404266af65472fedecb59dbd981dbd0c3b7a69a7d776d8def494b0777ffe17806c62448f9f02e7289daab67c9273c4a19c8187600ea86cf0b53633a08506d30df202739bf169c70e4b23995116517cbf6b8933e1b2b4f3981bfd8be85ecd6961e7fbfdcd6cfc4db4a59e290d3b1c6e9ccecb684b572de819f8bef3bc6177b423d15d1c4fe8f07dc803d111419af74d097d2d39e82dbf2a6f5880fabd490844e38b53e89e6a0bdb0fbda8dfa315410012441ebbd25bd4c4d1c0541318382ac92c0f9f84ce51afe6bb69d556b619499acd6fe0d17a963ba4b79a616826caf4a2a1a8b64a1d505decfe8bcc0e44cd44e160f9021529161835052c84a5f4c50d750ae49ffd3dadd073284c1ce93816c968a22896b6a35dbc97d5886027f094d60563eb3633461fb2bdd44ffbd2b0ccf0f411848342de1675c124996ae65be0fe0e6d0a94ae10a02469b847de205f5c18a9cc665ea142e8cc01e78430241778d75faec6b8d2941655729e604a8169515c8855dac1c4f5e5832d0c3b1ad886ab3627429dffeaf5b2b2e36ffcfeaa955daa0b74357db697328e3950bbb577d986d693984f71d684d7a817af0968bf59ebc963dad1d2158460a0736798caad6c96fc93a75cf1b3b7527f3c4f8bb9ca02298ead92f848c350ffdc0344a30795d87dad3bd37aa52808c33fe24a391f73df55278e13c43f87d31054e8e69b705f62a57d488009e649a85f1f08e5034c1909fbcac256049d1000e9ecf7e31250e5bfa38eb4fe6f4e2cd204a1598736d2e2c084d4bd30a9538710ffb0f4a738e8fc687c63a144572803ead2b35725a1706112fc63287900199431f616941579dc3cbfc8cd8d9bce4522c4e741654cd11d89756ecee993009e840746541d2850c95eff7e3f417a3ea90ae79ad4eece06240256d40a108d3039fae799f06d9ce2d1852dc00f5312124e92b88b70ac125813e3e5b237c2d7880ae63431ad99338cdb3de9ce2a90fd02847238cb20d1d489a5b7a73f87425649b89b9e62cdea00fa47c4fe6fb1409b166a676b63926e8933959fde0f50f34b96ea0166dcab78faac84431960a24840a53e0f34a4e0e81534678924ae950fa8b0899eaea000b74364c79e95adc32a94da6c00206a663759f718189939bf2e15697ce4170783c03b58c413e7406f1d807d07dd3ddd2d0f8e027d0f01368d64f78122c9436ff86603bb0fb4a396d3f581dab3ef93883005e6e1ffc422049bad0da16e043a9ac6aad6d717afe8e991d05b0d21daeeca8f844b620747399d53127b1729a47ce41b7eb782755b15ab1b8032da95656093e11a0830dd3109e91b04f856b52f7b1e17d1bc4c67da521c3ad3f123c043a453e068d2e606273252a0cbd024074823e03e6bbab4ab43e343074061121c0e2cba1202b4e668b02d7fe4ff37335ed10592f9ae63e2ff51656a8f83a8350644ef8d19f1e465f05fd8ba6ff7fd4b2adf512f8d26af41034873bc4d91e2bfb17f29409508ef2f30b94268b67f87056bd54311f62286bd1695f12582e27ebf3dd7a7e40af98e830b0bb3f8febf834c6f4c84371f9b9cf323cb116ea539b96f34f6433ab8c3c18615eb303548cc521683f3df056c5a43ad2ce89e0caedd2c751193937ec4fb7501d3d7aa0b24da444dd76002240eb309d9166c36169fa042ab484b0810d88a283ac198838136eecf79e7ea0818568681d84925387f90cc25a0f3393aeddba0fa7909ed160fbd551f1fdbbd1dd2ab6cd7a8c0858c3747d171e812f770288d3e88b9a6da15eb0eb5cf73e702b4c923c885b8a5359d2ee0b256fc40d32e62ba02274262d88d0dd152269ab6fada873155aaade5e68b401cb8ad7284e7976bcaf2ad49d7f458847db902048b7cc20ef64c24b3d23cf829238685422231213752d263865aaea29c6359e219c11ef58bf34fefde3ee0c69ef6e6fd7bcc800bfba5d85ee3b4d5cafb8db7cb61aa7c3cc89e54db1b15df298e671c0b2289b3ecb0dfc8820c2936385247bfc543321f5ad5f588e1335fa9dafac46e618ee905108440ef77a344cd0518d991428761e8047a0174054b190a14595dbc660137a7eee5c20c786b6b8be85796f6e700020f5470641a6e9eb1040557a38d41053449696911e23c7a1bd5f6c689358f9141a5af0dd5879c0198950a8a7e8cf22c516e40a17ea6775966643d36558a6dd9b7d15b52195e09bd8a2e6db57c39dc01ed84963d478bf30a430132cb734fc617df2ff3c91f2edb705f124ce80beae5872043c2f112223cbc0b1fc82159fecfe51c1d09254e8bf6973d814e3ad05f00e274267c9d6cf404c46ca534c090c2a07720249523523beae83dd4f2e0a37e380d1a54fd37591a0f76267e638e9de983781b7e5a9994fe316f48cdd2d4170e053fa14b5642b2422ee997f1061b87028abaef048465a9b33dee6ce35e2af4495e568dbd72f253b6b9959983a7b0dfe3759f0a3fb8617e679ce3b2fefdebb901e7f8f3835182e8928245a83a5be341c4985c1ff21546b5315752fc036dcf2871cae8b483aa0efb1927796e3ac3a9fb95e5165401a554231db328bddc73c7a29e3ebac09e75e33e55bd1184a45f304ec842cf26c8855432656976e945f29fe52d7ea20638305be88ac2fb8a2d87e06e6cca74c32eeac50382a63ed5e844bb0cb08f6b15a76703abb0c6e3a06eaab7641596f0ce291e68069f01ef1cec81a19243c61f338c6a6e59759594464adfbdba88a27477a7f6c38b92c2c5fa149602280be7ce294aee9f95869af4b80ea09bedd5a51b5628dc9da948cdc56be659ada762513d5c6c5d1bcf67cd60ca9ea045a05f35a51cf1da3068e9612e40b7e6d1e9363b3a636bef7fea125caf4117705594a31321fc0f34df1732520028c3f6818abfc1633b37e35066d857dc1c526d5e98def9ed33422dda035f41d860882fd607c90985897f5013118ce6f3c491e127c195b7911aff3a35c78add701e3a809172939e15a544c35bdb51def1b1b0c6955a85e562560812193f015085607088145614a74a60e062613223c503e6cd70319e03578ca5cfc7eab195373441302f5462846b31d85049d7195c0119d07b165e7cf027df9c04a8fec6c656272d224620c539fd9b77152390ef09e3fd4b6bef748abe67583de884d5d54127ec0406a5b4b49caf4284b27eb721e62abf3e90898bf832e72142cd42cd0c97ca221f25e4a8278d48e00c3658d209954095f4ade710aac907ccf3d6490c7a58ab5a1a1554db744f6e245ff69e15a5c5a35982b69581cad2bbc59207e963d293ee52fe72792509780052f21724b717d51fda47bfb998ef5e87d60c9dd9a74a7b14388d6a0389f8570a017afb0e0e6866147077ebdbad190b52808ecbf1dd6830eaeabce7cdeaf8b66de7f403846f288f10e0dbb29d10cdf07941274e796c6ea6b4cd82d8e5f10b2b0cc084b79ad8c549ee5b8b1f2150784244d1b421f0d185295997ca64abd0c56eb092e6c14f446618e43a1cce6dcc02172dc34747a9a80602d1606b9e79594be42c19c82315673232467fa971093c27bbe8a8995e64efbb177b028c216daccdb5c6cad7bf77ce24c561bf5aaa92a1fb47fdce82a6d03940cbfab6b3739f6ae19542b0ce43622cc94e0da1c885fb9359d02686fbe4ff3f1a74c1472506855efbb623a854a9d23d255a634c381137cb37d2a454f3a07d490b20f0abfdb7437c0eb5a919c839c02330a099fe87d8e4b490ef595976a50ff7c4392e199804558393b401adfcbfa95c60198e43d45583de4624c5afb208d9dc4caa993644057a582faca0810228f5699d0817e0ed035212ae30e56df041c7501a939ba9e94ba9719886e4c53c910a4e31eeb0504495f304001e9c3130a280a824998545bd67dadf41d189a70bfbc92934c04a362892769374b2ec06cdb56808cc88bc8507efa1a5d80573052ee230ae820f67e2ed66884f0dda1fa52954439aab931d47aab90cc423090c2d0cd531ee105881a20fa5007e33490d31d70a0e41c78a70d810876682265602c98ac7fe45ca2c04376e455bca3a44f1b1530cea4d700987153a75a8ac183adebe9b04e3a645f7c684aedd2a8690f708efac70ac00ed16e70a2a2ee4d9397e5971aaff3c91a0f9d7cb8a2a5fb60a901b652b64e98d5351f75f58416d3b60b942650333ba221625de755064eea799772e0346e26c2612c66eecabd5100da898327305aee267e4099165d8fccd4d882967be7f3a2f44450b4a3f9af1a18c1651bb10d44ed7d3097b3fb47e8a0efadf4dc3bd97963a8c2e939259800220a08adeda6a0d2f59d474f6705c4461badebbace76bbbbb00669f136f42d3acfac5264251759381f4cfe1fc70373cc3396eefe11b596cb6874c4d5938d032425546c0226958dc299f978b5146e633da1f73456cb62ac3eb1c2eb95bbeaec5c898d072680851711896d1190496e3fcef6015902672ec665d10703df6cb190c62affc8d01be4893200cb52174a5372bd428102119dba72bf454b1282b6b5261daadaaaed2bbe902a003c537d87779ed255ac50019f78c3a708815ad2a22e522a57e00bd53d342ba7c1f8db7a091ae2962bc3e12027725dc64af9e0306c2809078f6c961c6b13e0022125c9f0edb1842ec020da06592ec229641b925cf3e183282f2a583e6466ead46047480d3121b4e556e907235ba0c08c14b32c87acb81f601de6b17ffd223c2e2167e0a8f5970443d4e63e384f9f919c2ed2d6ba640de1e11b6f8b1d7548ada3a541e87211197c2a4913665903c10fc191669341a3c74bb57cb74f6223828912b52c439ce175432170e1f25e5ad4c02fc27eb06b5f8860422d942f8927e70f348601d7b6565264cf2453d992c00911c10910f8ea424cf6025ec0ddfc6dd6129f7cccf667f625d7b103574742df314de24bbd176d88ab25097d6112ff2893123726b4f7d3705672f965335dc0a998d6de4687a4d2f9a15fab9b1152a238b15e8f7c91cb2b0a653cc503ac778394a461933efb3019413416b3ad57bf601872b9ea80a75b3b9e7d6cb683fea97c481b0a5c6856cf5d651296b1117e30a5b81f73804febf3ba50f147419f01493bc66745e1b0c13e0b933d2607c6fdf80cf546028144dc07bd8dffd495972b0d076d800116c5344cdf920033430785d038b1a936abee2c681635c7f1a3e8463beca640a60a4f57f96a4852fa2ad1e4df432598627674724fb36ebbbf6d798c3eeca8f711ba4fffd779ab5adb5b4c59e7a3663a67ffe4142c3faa071a6d161e20282203b7d9a285f70928b36df9e8f054a97e84ef0779431af3f2342adade2f59f78aa5ee4967515d96e7d1a8d54aadb6958b47f3bb7aeba2418f06541652ea3eeea60bf58cbba46197aa75b77599a3760104e5cf2d34703fe2b1e2b146622b1f3ed6f98e6416c9a16de0bf47f31c58fa9898393b69753bf2f33dcfdd52c2c0ae0f33562f6f8f4292b2f399248201fb9926dc4e25773a12d89e3157d3319efd10d2267cfc344f6bf2ca0b50c8b18939fb33ede20528245fd18bf9c0265d1d153e471e926355bfe4cc8e23c8f070d0e5da37800f71bb1ddbbe842f6816814c72968a07cf3eec1d037284bd766b94b7f85362c8b66004e5463253616526a8e24b5295218818a409c51d0b16c8436c3eb0a26201829ee3f82769088ecd249910220230d6e60042a81d18a7c4a361041d02badccf7c833868205e2d9ee7daaa00d33b3c66d10ee16370f83f946c60bc25ed75963e512670722843bdb3d82004c92823612e533fc882f015842ba8cdb0cb07fdbd1980642fff1a3dd19419fb0206d1ee3f3bf47bcb22706388499e00a17945cc6e4271615a61d5e130fcb446dcace0bc752c9ea64f28bded7f91e2729f9d0f89aba1c5371d5c0261f3ec6c62fde30c2ba20f27d68d4879337fa5cefdca18a0685e228a55e07ad153f82ca7e92b1a344df1c659951bcb88ae909cfa5e21ecf8e7980476ead867ee60b0547d45569bb68f2088c3adaeb66b44dbca690e90194c6cc988d01ad172f2188e753b40025677a504f0c83ad83010e4bc18c4cc15086f66bcb37d7eb96abe5e9fb785e8c855e41d1c2ec0874129efee5d65f475c63a75915287682ee3c9d5c11164f4a261f7a6b96218e3524fbf8c1aec9b88a71fc0a248f58986a7b9dcf7d563bb1dc1481e26a0bd4521e8005f7ed60a89b1ccc7e7381b87d6c761a09edbaa83bef7fba5765428cdd57ee67a4b0016f71eb9c063a905350b615fdbe913234142c72bbbd2e6eca704d9e88b7a49963d8deec571e2697a888d48b0e34740b7b7bc139dec2828f201952497cea9ec2944a9861ab523e9019cd0dd10c7f8d22142c9a6937a2e660327cccd627bbb7f5b0051f6877ab24e6d0f1f8e34814b2c2ee70b1c86865f6010da6f2331d72b2cda7ba515c5f67be1f01cb26da7ab21dbc10607571271df67f4be584e1629e13dfc1f790dc4414e06830f272314ffdb5f02f58b510702945ea858faeed48b60f547c2074a001be5864adee422bbb17a9c6c12beb80191e1f77b4cb002cbf3f81598b7eac088917e9e6fc74b9a019b35db889831e6d8bd08c5320626ee97d31b4be69a3b5d6bbed6582b0cca977b565408d02ac90322979e4904d8654924aeb91e3e56ab2e8e454eb77d26ec31479d78d5f906f415eae7278dd8acb5fd42c9aa8138ecf01ed168ab33c0e2abaeb37ae10b6226a2ed78c8ac0a6a85eaeccc0eb26e8c4212afcf5698a13798745f6b63f4ca3f015287fe88f813032244205582f327b86eea2def4fdca92d5a4b9893736413b4a82b72e61434eb8013c5af8098a66ab01fad2f5e285929c889d9480d9fff47aade0e8ff891e41480c08db4a95d0eb8a98a569582182009cd4415cd39bcbff694c71e990866142a655a443804a78f2bb66188ccc5f7ac6f397b5be82b125c0aa0a3fccb278bfb8619cf8067f5e1c8f72552f09026a3a0a995ba7b619afe16694479f2357ad5a3ee0f455b74be85244999b39ab9c89bbbc0f10af3b6b971d0bd29b053a5b413ca3a5d6408c39c628eafecb5068e71dc4bd7760157673797d467b00160714fe14dc98193affef14efef4391dfd256b9d608ccb2e7e5528746aaf9effdc9a0ff1f3fbc777ec313afdcfae993d7203cceb1f218479364b3ef76599c78bfbae2e0f4e9639988b11f09e44a9a90023848473015eda18f8b8bb3daee50bcce9f85042c4286dddc57450ede9a7d47045735f20ed5221e4e5b81ec10317e1133b91c6ce8c504a06e3eae32906021c4ed4372632c2382211c89cd1d2f8cf611e2ddb823d3be645a76ee0a26c3b3d9203572bf18fb14f8fbee520fae971a96fec166944e0625135e6ffb2841dc981258df4e1281f317e1342193505f4cb5239331841ddf3109920094197047602004abc9c68a3316893b0208be9cb99735761c3077aec546e54f1aa9702aa2bec89bdabad420472cae32e42210c2f63ee28089e6298b07414cda6cdb0c9d3f95349b7392cf41dc1574427b17efa6658f65f1a9a0d8a5dabae09a70c0f6cc174487c08262600b4935cebe5c38b8baf61453a6a0cdd4ae758bc7bb7b0d0aaff77cc7a297b7d11d2afa99dc8c3ee894e8ebe3b75d6579562668a4814a0a78e5c500137e2f6f2a9022db569d5afb669d5e5a6fb9916bf5a05322f74242e599e9b762bed43ca4eba8caf4b167614fb80553cc4dc4552a1c15c1e77daecff408bda84134f81b0282b83a422e7b3722c94d59f2a49fc81c1ce82f78ade449a06c81621d38c011fd6dcabe15b84bc997ad83807254c3b842b5ef23506c19c4170a3e0d271cd67ec76f661152e33c7544d22d09260c77f1d2e47804129bc7e81a93a0580370dbd86ae4a3ab1a11ee888b0075981185eeff98e9c109d563ec86ecb23400841b1b882a6241401e6bc57d205c296f76555311170da33d320f6108059174820b5cd40dfd7c0dcd9fbea16a1fe18f5e1a709af34bdba5153149351c0c4bc290f322ca868b053a71ce5824cb3587879db50adcf4bc3d54468f938a986fbebe598d7a286d989bac9eac87768f508888ab9a76a49fd0f742ac9f54b7502a058a436f68d6d31c7fac65b49b8b16c5680990e0698647bb3d3ef363de5a84a212af04b05d8a639d196a033ec2e3353e0c965bf3dd93254c78f8488138777cf337603ef60dd8acd5884f87426bbfa765fd7365bbc48f49014d073bae018722352545f561ad1b12b6b96c66aea0e13cdd5fa5f5ac0d47b10e52fb63e85f4788250d2c6a2215c8cf59f29d5391072b1dee6585c65e5bdcaa5aa86cc1202bc0b7d8f02ba7f505aed67c77cd079a7ede91ea5e5aeb45a69f6111285c3d8886e238ba03b509686ec44c71d2c035a3da9999acc6a34f8cac2cdea9a72e22ce0d5329bd2c545a1a52821730bfea50bd060d3ce5dcf607f9f222778b8600dd900af418678feb2c9d123b68ca810154c68ad56960c4ad44c4eea430eada43361952c82f1f399206a4754e512383190abc704f2955e19baab069ddc18859d73955014dd4fcb4bdbca35c04dea7a7baf092cc8ff9035db4bce7e40c1b05d336e319e661e95ef9a8d10ff408692d31a6bff0e22a14858f8d68fb13006028abc6e3e8cf4f7521d1a05546ca480347453c5115e121cf4649507bac901ea237ae8952c1469e5dd91a37f560f382e2f365fb3c8d8b886df7a13db768deec34938196da69bfc4c52c493478236766352b7af3677006a70e465d902139a42d23155c664de962c13a17bc3ed4195b60517a0c71763ac9750b386d71a8229f6e3b40f6831087db5605d78f2f829dfe3a140afbb5067bc035303a9f5e4ef139f36f097034e9895d1d964277070950533ddc265d1ac0fabafb6aa9542eee8602b9034e09f013343d0445786a146fe073be3f710de9ce819fd4a0c8b48d1fa147753b833cae663106563424bab2afe4d4260a689a059de14b3b39940a12868c136e1c1247c15b78700ac1159506932d4049d103dd1d7a9e832a61fca8f98bfc28b9a9471c85e6bb16f2dbb3ae01287e7be53146eb4854662a0e1a181259666318692353c212ae73c86168e05b81bc56ed86859c4f39b0fee0272dc9f5aa895250fd6c12bfe6244f57c33379e8868274a1d767d64ad456fcd5a1d860b637c6c5ecb484a596509ae8683740f45a9d630da16602d40e270dd5f09527b355c7659b10a11c0cb4130a17ebba69e1a9e561fb44a1348c559449e00bf9247e994a671dad93b4a4d4fb49f8af22ed463e34676796fae72827bfa2925f8bcbd855d0c35e17f1ceaa6ee693adabe523147c9785d7b3fd09b4015c7fdd4dff6f268e05226f4924c8205d69a4cae112c20253048cb34b04fccdbdd2a074a7542c304505b4e5937be5d596e595d81638aeda4ddc55af7be50184c6a473f49379581959e2ab16864bb75d7d3db01f7139293b9ce9053118b9b76355e4322358e10fcaf68617b025f949b587f656a314b05d19161b27ba7d59bea4cd9061eb796035e9f6e68dd5fe8d1a56d541f19ec6dabf015622202867eb10245eefe4d4dfbafad193566cff07dca12aaafec0531642904bcc03e5e0046bdf293b9db6132735f02ac515161c212790a9994dfba5669e4525859de2243486480915cf42cd353150274a4e5434f6a3840ecd1e53aa0dd7e949623d44ef49264b91ab510221c9f2f5733593944406e28c2bf322595e39af025454ba417291bd53d557750be8db5fc1559cc8b4a9524204b185707f0ef93013efa38d3c6e0a78a95c03838e4e93696ec2adc56eb97f1014470448d81a7f1a5c4b0d8b81e35f406290a8b59fc3ed093990dbad45ae638781d110ebf47b23de83a026bf4303c8283ee75b88478b66ee04a44cbde13cf06089cdc6983a7c1bf8b7922846cb03f03c94bcbaa427275b4097279abbc5434a96b2e24f0913964e95e323db6b872f471b736331f6e0e6d72502a8ffdbd45df689c8a4ff9e1ab47ce8a01115730a06a8296f7abe38eb34ed2e0282d9896a5d97098dffd7c8f6dccbfa4181ceb482a62252fd40c8e65155943d63d6a10701ee98a54dd76b076a5208b5c7f777078771b2d53cfa97da15c376146ea4d01c8f637b091e2f5c938188328a8ab7a0316a6a20318f292fe0f9c16d0f1ccf29f4d1e4acd23b2962ecc6f2cbf77264a356b64a13fd4945e58dc6d3662f628d5ce6aaa3846c7eb52c331b581e5e9736bb8b264471e7640839f95b411a772a3df226e4f6b4ff4a90f7d018df138b961305491574ae1ccdbd830bdce15d73366251e9d7aa3e1f49358adec034c16cee19e4ebad5716c234c233f5be7d6142ab2f87f74cf4e268299cba865dc60b682b2d51acaac2564e8c5701430b0d9966326ae61bb282829d9c25bf7332b0d432855b9abb9d65810c6f715932ebc91aeb51f76a540f6ef6891244a3fae460b7af2856d3cd832e6009a8738522782580df41acec7703aaacc28b5b20103a9b3689b8cf54ebc9c335d70e2eaa0d5511c8f7b67830ce31d5f6535f0997ab2e17f451cc542a63bd0e09fedc9480066a0184458517e2044214133295e56be268b5c4c0988f15572acfd743f118b1a559420811a195dddd3bd508aa088c08353d39ac3db24836a956ddfce788a28fb546f54bada2e27725f6f2f1d9416b54b5cd1dfab34645faddae5fa2eacc1a511c3a75d42dc65a55a72c942a55bba83697d6b1635a5bab55b5aa56d5aa5a05e61d34b5d6c4ac128bd6c8c1c517d360189824d8da0be38ae1c11503c3ff310d7f8580011160586cb91b9be11ac1da6b6d0f18c32c5672c1b4a6c130f662181fb0d2b518b09fbdf8fb5e7e5c62c182055f0b232187857980d1e4e8ac2e161a6c2fc68205df991a3b609ea9363c1bdd8d5cc2b4365b0d0e13f0c55213334176310f316c2f86e10f0617db8bf1adc262a3c9e1336f369bcd66b3f6add454d681592fa671b9762771661470758757a833d9f5305ec2d8e28e852c509a8b7fc5a1af71d0301afe0a21849b18b1d78d0830441061860cc7e119ae114600470881ee8b3136cf226a42a0aa038ec1603cb8605a733fdeb85eba5e2e3e6dd3b6642be2366d3c80532bd1d0336956995534a8bbcc28453b3d5b3e2573a787480c81562f7e34c01c0db2fe87d48179981903d3fa3e50cf2ae05759ab10fc88f07891122f3d445b2e8c841c16d6008c264767759d68727272747678ee4c4a4a8a2a9a3bf499f4f4d86c5051b6dbb95c3475cc2172129744848ceaf8b431e9993ae6107c9c5bb4c82a227a666aec80333b3c1f8f0dcf4677e3462ea1041cdccd66b3d5e030015fa79a9809b28b351083c966e0cde50405c2ae14418fa8f67b6a0e1f7a137331993bf42bc771401cb77bdea8584452a5730822a40e7d5965bb34397ce6cd66b3d96c361a1a188d140cb5b144ad25d1cae1abc116d3e808e90929e6e4d2c56a52093127386738e7d49cab3959734281c39c7a4e2f36a0eaad6fbd0e815afa592190163bebd659bf0a81587db50a9f6c40430fc359eb20f8bdfdb83cf52f8bfe7d9ee7754f4f40e95d9773e69e6ae043e7388cf185a2df6bad7d72a2865affa1ad09f290a542c0c560616247a7ef7542f45a3f8740f593e2062eea29ca08c768d9c10d9568220538c22668607b6a82034f33c0f244031078c104155239ca1502d1a456cc0e1a86042c9b5b29b06145a5b17420fc614512ab06482055d26a07337c532195e7043ec9808bee33ca3f7840e5692087199476b0c08f2e4e3e5069c28054d20ab03c6d898a22df2e9fa2a8d1f040bf643ca1b3cd264a77b71a1664b141bbba7c72c114312af44c974f2e00420e3ac6066c88d02b1f42a0c12a4fb71a0ce8b0cba7db125f831b979a1f6e56d4c840eb2e9f6e5f625b7486124546169ae583cd0cba25448d14dac2a046078d9190a185eea03e043a3bf14a682b45cd94b6389841735d3e59d1410d34d8e59315aa1568dce593952f41505441f1c31314542e8c2e9fae0cd16d974f504ce9a3ee5d3e79d3a42d93b6aba5c961f667983426d87d22fc34fa3f4e2e67613e7e22274c9613c9e7dec97cfc4e982a27e2b813cc67b6e89747c50b07424495e0881c9880026abe94ff16f8bebee7795ef5ba9f735a99f34b2ba50e19c057f2f6a70ecb83cc9dfbf7b9f4995c48eadce7bae851420529aab327b4fb46ea1124499430f92165998af7f30e2af3fefe4f0a3e3e29de5f6d49052d7c1bbe94f25e1624cefd7943a0908641a4ce7d2d033d2565a5b5d3aec6279d4b9f1f6edb4149a3610f195d2a39926423498011f9a55f0848f1fe85622275605398f360d672133fe247fc02fc02db164acd299e23b01ce831b4d1a6d08602cf91ac5f36e91376fbd35667e0f728292599ca72ca72504a3c073a1c42cec2a48a63933ef35f4ce82ca1956ec3a43acb394c6aeaf673d6faf57a9183ca7516660a6e1c74e641ead81fbda93477ec7f53c70372cf3f66e9935f7e0d79e818e8517291d18b2a2d730f0b5d9f86b63a53e2a0ea6cda2a8e7daa1e0e4aec799e97a839a84af3f884e1e0453167ad5fafffccc73fa2f52b64496184952b2fa815c78e7a3956d65e2b0c7fec1efcef0bbf0fffeb42a030fc3cafc525f56ea8387958ef25f73cafeb3afd79acd5117a3e058ba0f983fdf80e015a3e7961d4e5d3172f9d842e9fbe38f50c46f5eff2c90b5bff2c58c37be9e6de0e40f6fb7700b2d38e9b4070fa9db0cc9fd24eff010a189ff4fe43432058863fe8c380390cff7d8ec3300f12fb7db15f2eb57c4f5e50e9ad2e9fbc88d2394edad925cce20db32f1f6639fb536bc8837efeb9474b7fc23891c3b8173b7ecb25d705891d7379f94fb93ba0619822efbd38731c4841282a93faf2969dd2db95e9b4c5d55da1d27e1de7cdd5c779db50b4c551673bb4c82b9f162e776e815222a1860aaa366fd672f846ecf7ef9813eb1c06383e9da613152f9f372ac632b0c3d54110ef980e821bc6e10de3780c3508b5cee1ad3b18a3831edeaf0e62fc20f6fec378831dc41ba683e0867ddfe30dfbded53bbcbd0e82fb7bbcb9dd43c53b4349cb60b1549d4d259c45ca6613549dada6fabd38ea366d544481944d22a478ff2add6c94864904fe8bef8ae39ca8e7eee857deea8a86abfce0b797c2f7bacdf10efb0c816666a0650f7ff51fc428fcd5cfda9c809c4d2366ed8b18913ed3ca01e46c1a91e74d2a06fd4e24c279933af78ba8787f853fc4f876ff525d8c1f6f222ade79a3b22b542643f585ce6248538b54bf2fb95c2f50fd7ee6f0955ca6d18432a3e03d9fa8a8c8df8832597cf425156fa48ea414a094524a29a5391407dd712371e453ca5fc6133a6bd24355d500436a5a18564e289afed437d0f56dcd566bebc33096562606aaee000ad2f78a035f8b7474a1dc2857875ef7fc7b74a150d95402824e4aef124d1cb64452a577822e2efa28b7d468d0b3368f6ceca8eb96e9de5befadf72af5fbf48554bff5de4b61f4de692475eccf5bc3e06b869296c1f6f165a4e2d0206549f01146b2b60152b43f6228dd5a23964903a42c02566ad69844c0dadbc36a42eae1ac51d1ce4e7bfe0974399b14983744718089898159db316bb3866bb7a967d6a691cc2c82ce682694a6dbd4612a51a932917a76a4a0e5418d9b5823b8457752259cf64d89a65a2f9dafd84b2dda4e396728a262ec02393729d8409762673169235b747edb237d5ac8216704e801d40eb2482b4546681cddbe4815e75e1915758f7864545111996e7f1ebd9a12a083a31e2f55b386ef8863c7fcd8957a4a626acb402501057503277c4d0b426ef8929c7be4d4c484d8c47ee4f0255a88007ad8f0255ce7a3cdc7843c6b2fe1fa920b3543b811d3e863a4f133f8929c33a326068b9543838f393f832fd14368c4f80883fbf13184dcbc00f8922c24068c8f0989c5584262ac3762fac1c725213f63c88d908f3164c6cd1b31c1e063ec61b03ec6d7f0255c5f6a41c647ee4b4372f2901eae971962c34799afe14bc0ce2d60c46463c4e4838f4b43be470b397c5c3a7ada90a7c197e86e0123a61c1a6fc4e4e2a3cdbb64f858f332ff537900a510005aff01000a27ecd0e1b345f81c3b84efd933d93661d208f038b6085fc20ee16f6c1b3c7b87d208f035b608afb343f89c4d028d8dd3a4d208c08d98449ea5a85847e0990a2ad69fb145e04b2dfccd0ea1059ef986fdde34ff7be665ecd7db6cd8d76c9a8fed19d8879be6c13df3df7ebdb7613cdf280dcf57f60ccf4e3c6ba162fda23df444b6d00fd9432f640b3dd11ee239680bf11c5571eac76cafc7d8d8890920e1a5c3d8dcd3510d4ed06576bd61024e5774d7c619b04285831e53b987d93c9b8be17bef49ef6cd783f6cfbbb65bcbf52bbbc73d8981ebdc8e617e612d1734b9cf362acb372abb929db2960c4565e0d7cf5b721495adbe7ee64265acaf9fa5ea54af5febb20f21c5faddc62c90d88a14eb7f1b1f21c5fae0c659a4583fdc380929d65f6dcc8414ebb3369e8114ab4d7e5b766fcbde6d190a702190fd56d8f85671ea0f6063201b43559cfa413696aa38f559d8780a5751b1b2b06dad460bb22512b728906a34209bd206b0256d858d6b50a2c9102808b6b9aaf44b28157e68d096341710a248e9564603fa2d1fb153afd82613e3890f0ac4824fdeec265ddf042e9ffc4991cc2927940a37a9a507879883a3cf8f5171f2a07ba48f91bc6ea712c9ebb1a43ce876798fc8c11756875930326858f7abf77e76dfb765cf5bda9d73bea25bdfa3c5e3be12da5b998ff96908b47a4bc51687e54c45bd67ff747e90ce2c2df3f1fbf6fcd6962f31c65540e9f24b974f5448f59c41993f24afdec22150ce79cbcf2fe77361506ebdfca1dfb7f60d81705f7cf185173dff6d75cf91bc7aebbf1fb8e75f81fbe28b2fbe74c997623e871278d198bcef421edeb75e863f70f782bc6ffd0314f0a473610f98f7f92597ff75606bcbd71be67dc7a3c561dedf9e5f7adfc1bcd54f6e61b611cccff03418ce949fa6f52c1665b558ac47f2fa02a9cba72024f174f9e4c55467cd607e3cf282b57e056bf1a04bc3eff3666d58fed9f3c36c58e63f54c6082d66fd7118cb52b183b15adfb71b307bc8c5a8a48ff991805dff97392c3feb3587651e747bfecc412acadf30a8e82f833a7bf931c6e30b747d8f3f4665acff3e29d67f0f6aadbf87ec9ac35a0f83fefc3a7fabdb23d85b7a8fb977e1cb70f54359fb495fed0f8ab9f37df7537f28b8f1e7ed1955baee6dee32e7304857469440a4e3057586f4b9f3866ea70df6a907f4087e379dadb5a07d2b85764207f1418fb28a4913a5d7eb17a864afd0aed86b17a5b55a1b02d94aefbc925b8d31748c451ff38ca939f93cf2418fb6e7095231e7f778a6627e1c642675cfdaa4628af4c19f9ff6207d64cf2385d2338dd27306bd1f67979ebbcbbff43985caa3fb2f2eddd45204ad4281b86f952c27e59337a3de415e99ffeaf927d45b790dd173ce398725d0e34c9a20903326152753990ed267fe4b08cd943393599b47337a9ee9f9e7149bb3075ef6cf561a0af1f46761434165c922ade4c8218a7bcfccb45af9bb93d4c934d480ced2ea97be7519c29339b91ba0faa5d4de67c20bad32819cd1da8c7e9f365deac42b8bb6521ba2dfafdbd2a6c929150f80353aa5dfa753a86ca44a7d532ab4898a4ab52a2dce59eb170b3405a2e9bb69209a28ad512a773a61af0de71654e64f277e90330a0595258bb492238728ee3d33d3a2971ed15aa8015aeb41eadccfd2a707a9a31e2177eedf298552b90f04156fd3bd0fb3a4ef7d6929f580306b341dc73cbec1061a408cf1c37cd1ad2f5fb4d68f5b8f297e2a054a1abc97b27b2927973da34ea0432034b54d9bcc3ae4e3cbb56585d9b4d65f1f0b6e9a7291a232ac33c8f510889a60737d4a51244a6d98524ca929c53f4ef79ee9784a4da94937ed610d7a9c5c3ae6c29a85d4c1df0198a51945655ca62d67ad6d78d630aecd98982f32675d6162608c4849aa29298a3d30738ff4a1344aeae0cfd1c9191454962cd24a8e1ca2b8f7cc4cab953bae183fc5d84a5b9f80144649e61812da9b818d12373842460e6e300411371041ec49d374f974031a6474d17a8b9a240d5ab1c31432a44c2103124ce12483298288d140cb74f934050e2b9882862a22a6b6b08192144ca4c0ca91142690010412d4604b0e36567ad8417f5d3e4981c39314519ea4c88013525fc840aa4a8a4aaae60587e79c416ab59546ca5aabee81c3a27dd51816c695c1f5bab85aabb43119a6abb6a5bf7a2d0b5c85368570656bb51564596badbdf7de04d84f4a2965ad55ca4eca18970c8c18af191a2b613a2af06996b6b6d66aeb586badd3565a6dbd2f6a635418564a995b6fad55ca186ed65aabb5d6c26469efb5b5d65aeb4d4ccee05eedd55aad4642cfcf99c5e10f5c8136d44820818405b3adb56a243e962765d7aab556cbc5d85aab46c25e7b6b95b11546b560adb7d65a6dad55d35aef9db54a296ba5a94145776d772d87f306659db5248da25f132e51a89a1a1ec24d2b46de3accb6a1c2014aa5863a51a79b17d541043a852a51251166c4a051a8386304285414abed6e6769ad26ba70707042a0ee69c4dcda4da03ac5668a060935503112708e4e8d9dd6b7bafb6af15a55abea975a45655ea1229d37fd819b16274a6b9497ea05fcb6d42d4935a9ca4e5555945a65eed0aa2896060d1a39356c805f0f0d7a01bfca5a85750bf8d9ea96a42a494955a2d02a5a55a34c9b4da706abc6c66aecac749849539466deb171a3d6a8280ed7249badda2acea555b656d52a5bab495347dd22aa26d96c944ea1d8661424241e1be084326d586a6755091d121c47a69424c1918b9860c4041957ab554441a064337cb3a42023fcb9a9e0433acd7a2eee000e9b9027d65657104feaac4fd46b33369a9576b5a3d90b2a5caf1742781c06bdce1a1d4d8f725d5399e78541f93d8cc320fce3e4611fe38c45c73d7e3b4e1edf9297cf28558f46a9d3ff26ada087247db2cb49fa749555449fcf1a42caaaf59056532c9bf4b9ef59e9f4c7950efafc90e67b36c8d9459ade0d7d7a3bf4f9439f770252e64991fef4fa7ceeca2267accc8476a24b29ba64c91bcb8905c58a624531bbccf59cb36785e2efbb48f49341a76cdb22d5d9cc161287fe04926850aa36d0240571c49ae23ac767aaa40efd981eb8aa9c333b5075ca67945c543ab3ebe19337578cf84153cfd1a5113fb8f528166889eba692dbef532034175fa2a2032fe8d60b5d27de14387a1a4734b3d05f717ee4fef198d4c5c3165a4a29a19aaed0b2494a293d20a5acf726d97fa9239f7316db6b2d37a70c0cbc0a81a68ea17a7d56174dbf42f5f9b94b3dedd39f96523a47db44adf8a41c5937e972c2b5d63ab26c9eadc75ca1ebbc4795f390ba2b085dbfda7d6b52c74ad1d65a4b29f049798f5c41dc5a6bad7452b124f1b592d2b7750acba66b6c6e6dcecfeb3247b7d4a4cffdf9148a4b1004c1a736e9537fcc330ea91ed9a79ced0bd7e485674e69cc9d4b1a73a7d40a073ab5a15324a963fb422569ae6e5287be4c171e524de790ea0cec92047a51eaf44b68d4290da1d83a45ba75fad47290d659656521c9e072cba938f3a55472bce26987f8a4ecb3cf266aaaaaf84246942aac9001a50aa94b4301292c5fac076b2de5b1d64eb90423a234fa94467913271298d426715d958602d38baf7ed0cb8ac45a1890b83ac72da9f02d2930b8ba8e8f489074efed38bb37bbdda30b86fb5fa5e1e5a37d0c03f7b31f012177b4f75d0b4abfffa3e33d541cda429ec81ef26405972ebbecdfece9e1a1624ff7230f0e2a52fa437e8642f385fc1c7aa197319fefb7127af64eea7cffcd3028bff744bb09d10b7dd06e12f443ff854f3323055a49ed5e6cdbb4f03f14db3c251dbe77a96cf446c5ef1e4709f93f59d1a587cf9f8b2a7da6cb272ea0baf717bc51970b90aeafdedbf757fb7211fa27b4a10d231a7a17de05de64e89fd0be896d365da7d717be167917a8f876d02f742fe34665e115fef5ebacc9a4bdc061ab97b4173e53b1fe0b5bc6023564a9a2f9af3ec67f39048ac1df15aaf0a4f397e10a371ffe0f8db167c76150f89cc382c2e7cf390c37fc72a162fd6818c455d163547c096d1891d0bff02ff02642ff84e6824175b542185f3dde525d548471ce83761e1ad52e3ac8a72e3ae01b8eaab349eb3293514df80a207a7decc457213e82529798825467978a2a2c2a7a175e8643887c919fe11016de859fe190205f84dfaa8a53df858d6b15a74e79576391bd847632f445ef44e889fcad530a4a4751b17afb3e6c8810212f69451b3664c89097b48f0387c2cbc5c3b61b1a55c71bd5ebffd48f036f54fdcb2583340cc317a272d6550cb5affe72a1b290d7bf515466f3f5439b0df37e76ef5bd8308f631a8e4271eabbbad0f88647fc012d8377ab6da54eecbf70684b9ad096b4cd63df3719c00fe14d88bcd00bd9432fc321f39bacf043bcc9d0d70af04e867e0aed5a01de09111624ad002f4320dc5b16e81379217bfe904d7f288606cceebdfe300c3f535152d98a7f4e54ac5a3af7e0a7c556d161bccc635b9d09d5d4d4d4fc931545f4ee3152f8f5f51a75ffdcffdf86efedd92595ad3c6fb5e279255b714f48d284fc0c87509a10de440234a10fb7b72f14bd5b58b83421fe53712a6c0a4db14dfa7cbd7b0c84a340ba10080e8906f0b02134e8e94b1a0e895678d890f9422fc49d10fd7c49fba977684b1a91b1eba31c71fa58b3c74e668f38bf6598ddb5475d80dd64d280fcc72da9f05f0689b6a40575bce75bbb6595d0ee3d98bf21900be47eebed4ec2bdfd6c8ba13de6476ceb3d541099224445d014215c8ab8212e5436ea4065e396a8222c45246948e8edd72522292a1b97823e89cac6a5217f7986a2a212958d4b42def21c65e3f1f57111405f1fdf5208c0d7c7fcebe3fdf5f17f7d2ce3ebe3292a1bc5eff1f571172a1b47781f545436fa7c9ef24265638ecf5da85845fbfaf84baffd1801e0c883d221ddafcf45a1b2f1392a5436cebee7eb68c2cb9a705429c1864211277c7deeb6e3eb7357747c7dce69f5f5392de1d7e7a0c0afcf6da1b2f1c54d51d968e36f7cdd799eafcf51d5f8fa9c179dafcf5551d9e8fafadc172a1b738dca46185f3f1b51d9988fa86c84bdcdd7cf50a86ca4f99aaf9f91a86c9cf9d8d7cf51a86c7c3d152a1b49f89caf9f81a0b211e7697cfddc4465a3083fe3ebe72a543686f0374d2c8d005fdfc7eeb1fb27c3183e3ee931ff9b1fe0eff60cf0e05e15e063db662fe15cf8bd9fc8fe1c444422cf03888f97c86af311e89f488e8ff1259a88185b190189bc8c03ec226ec494c7d5fb8c90021fff7700648cc097685de4f3464ccfc71138203e06e07d88e4c844c411fe89fcf391bf0c222fe38d9802c0c72522ef539483c88b453e39de8869043efea7c0f928e303f0db48d12a17c540ef79d1e64bc022be978a3e666455f4a01171843762f2f8c89fc7e5e37ea0ef762754c218fb1ebe240badde888926d4f9d8fd15f2ae8d10ed574225f4d57b2f747cf4fe0abd354037c48d98c69e97f12579b4791c387e0060adec71f0257ae88d982e1f7bdef225dcb53f8f832fc9343ee2c84200e8f92b742f1fc1b742d7be11d30f1f97841e00433884be04be44e328e1fe8f7d007cf70228e363cff7219b3c248bad86bc21af7b23a61e3e2e0dbdec059ba18fbdd0237b23a6151fbda7817cecbe7fb8c3206ec434f61875fe065f92836cde88098513f818aec01b413a7cf4f136413a27d8fcaa48c8c7d58341ff152024e2464c230a234fbeb1838fe0873abeef061f6f7c0d9e376202f9a883ebf135f892ec838f3572d00e1d0f0681e017047e6fc4d4838f4b41bf83a846d0eb10d5d07923261d7c047fc7f73a3e2c82c29f407423139d40b4e263ec43a255f8464c28f07189e84f287283e8798adce07923261b3eae1e053e861f4de4d0c520875a6b51cc30e70c6e98d3bbc00d2608f1add3cc1b6f62cb70e34fe811a4a202bdd0b7e12619d682d0d42fd7ef05ecd316557ad8e5d3164b4f7491c30e6ed0e1061e50fa50f03d0cfca4413e958f9be84ceb053c9d795ff74c576f17a138b588fec8d32907730e7ae4e9b5b35d77bb6b654837dd6312ba8174fb45515195663ead9df2625f86db2d0943a756b7badd2303ba8ee9f5e7519404d42f5235a14c7951ea6c377c5f18638c2fbe574e4d7ac48684caf26c925f6ab30a68829c2fe6479ed0e011206e482273994a17df7be57c65f1e2a95473d63d2f45245e40263d932039d271a1c1a9a63995ec6cea330817d36c928d33082abb1f44d63a499228febf8e28c1b1a0aad0f393820f2acb16df231587ce7cc4a2441e52694eca9bbfebba2e772f770c32cfa3398f66eda85f2654a4f49b50f0fb3698430e3b2f8a761de9a949a54759b544ead07791408ff368945acca3b9a4dbb1d78d14f3fba02f16f5fbd130a876eee751c8419e8bf49bf744a222bd1c0a7d024e2f03534a2ba7e530596953be932b39ac7216ece327b24e2c9df6ef9c734e7b61d2eefb44973761929ce83ee64dee37794293586259ef4bbee8abe2c849b32f2d975095561a50f025b7716cf41c5dbe0f593da82a89f32371260f79eb31189974f6e0e93124199374fab35b6aed8cd1449536457da9515f7aad950aa41fbc76611c729164a45fdaa5f505b47ec9e8f2698ba3feeaf2a98a8a867995a65091d63ae794f35edcdd2899e69b7491eeede674b5cc8e5b5a39bd405cd8911294258194321d30c6b5be72cd1547dffcb552ea1ee87d580f6aad557c69dd43ad55722bd80faea84b2ae60ae4596bd53ed45aab94574a7b6bad1573f395c7273d37618e9382a5608e9382a5608e9382a5608cb96b6deebc8fcb1ccefdefb3f46ac115e47256e294c070c5e5acc429ad585cce4a9c124b63d64ab7ee2a94360461625c32b953ca1cf8859f4ce7e58eab326977c48814932454b4daeca3e2d09fa220f6e2efe1e1c11873d7525d67f8a5e4308ecd4cbaf047fe642d032f2caa6670420b2f3615d002072ad0820b951631f880164ac8e0420b2c1998fd792cd0d55aab0d5556600864271410b4e8741ef1d490a5d65aabb7fb08028106597ce9f4bb2b2a8c3ec6bc3461a5054659246591031b2cc001171dec6002287ae0850d515fab073d7e9de11a9468f3e707a9b3a7cf5a8d368a7d7c2f1ce8f42d8d396207bd7e7d9a3f206207a8664a29a5f2053f4851c1e40310566044a35f3aa5309d524a29a55b6237bdbe1c73b080a2d7267aa512a207343cf5200a219e7cb045af5fc32d5824f5faf5a9d3ff931faac0814b0e5774e8810b5aeda25729adefb25adafc41a76f5744bc68950d5039bd806a6a063360f10414aada155540c1022a8a3ceaced13c6533c595256caa74accba72b543528b72344f08315c040bfba7c3a62895815475451938123ba4802064850bc76974f5880609a33533a73753ad07494407ffc309ef41c42ce588f6d7ad26ccacf44168b5fac33169f4797cb8a7324f33ef4fee3f6e43c5bd2c02d69af8f83dcce38dcf3a8cea6d8bdcdf36d968cb1c57e37b88d264ddac00fce12f0676a027ecda0ef54d43cca1c1715a1b3b9da9f8c9138f8c10d9338f8e70ea1739b46af5b86d93da9833fcf1dfc9fb411823481fb1c8a833f9a4345cc7134497a3ec779268d153dcea328dca3e01d4da423d247569cfa1faceb8e261295cd747c85ce641254d88a2ae2389b3ac6f825ee3297452ac338ac50b70b74fd31eb26cf0acf7cc5d4f4c8faa80fc723f74bd7ca43651247776225c759fcd5bd94ff5adbf5f3b0a40f169befef260dd37aeff1f6fec31f66b1581eeb5b5e4b8af72ccdda7abb90d02f2adeef46497b8f7f9c50f07e918af7c30fe8d57b7f04c9d7c5d6f1b75aacff58fc07cd7a182ab2f808f6d6687b8b8f31afffa13304b295d0d2c7f5d3f534fcc13de62eee8710e87b560af45399d74d59571dc6a3cffe43c13dce0e7ee0ffd4ef827877add66be9237b8bebd67e8d40b77ed4bd25adf4091f7f320cc2df7ad686015b16ccb3382c88f5def770f13166c4ff80d959af6b6bb3feb5fafeebe49c751572706f4fc3f017e9dc86611ea4a8cb4fbbe80c09c5b97febccd2303f42c5cbd3af9e9e9197a41e42972ff801959e5b20a099a2614002d68781fbc961e0fe52259dfb8aedd8753ee60a83fdfbbcceb9333ae9f5e5dc19232083943af549af3f763d3fe6fee356af5930cd79d0aec3d73d3332857924158b92435f87acad821620289e3aa60ed9c11e86e1cbcd9574c8c3672dc1bcddb055ddb015e7d1bfc9b296c567af2ba017d75494303ad0e07f307cf5df86b17e76d67f1bc67275a1bfd0065d1ffc51f7186a0cf58b2fbafd3ecb3aeb4ab64ea0412e3b3bf2c02fb9fc5e127a941daff62737f153fcb01ff361964b9aed32515e4b853cf8e40dedb22e71f3e5bb6237a95ea429f42c7f85cdbf7d520965e98cd2e47f34e471bb05b1ad53fa987fbe9db7c9222541822e6f49f4d0c7aee33e1d20672e7944fad8f49c4e5f89f4997ccca9a1d31b0b743ac6be497da840c56f81407f9f414024496cd0f9b95d548406fc717ed1dc46ba9f1dff995f848ad48891d14851119e3a6652877ede78eecc9f61d8c4cd3a7dda43651864e7cefcccf1dce9f2a5dfe8f2a98b537e1f54a441a86cf22054443277e81f9105e124d09e7704c9d4213bfd514997bb4302f6ef3f6f2f85ffed5781cec20f77118a436df75167b68ba2919af076ce1f8d921677ac303f052a9b1c3481e67ecc3d3f3f3d3d477e7e7e7a7e6639cb6c2167ceac2b9060146857922463922347b86b593cb090eebd5f9fe64f8a765db121aa4b27babcb2011488935fa12db5b61e759d940a4d3cb0a18f720a890755fa28bd40d94ac516e8711ec9db7c2ace2bf4f8485a7cf266a418638ee3400eeb3cefcb9734f943b897efe40e999f3f7327f227875dce632aa145ccda3c6292848a98f3a21a2448c5a90f8343ab46a1c7dc6f95562bbe982ba8a575ce0f9f68a874ec986a7a5422443402000000c315000020100e88044271388e645108d17b14000f7c9040705419cac24912c4208a821432c6180200000818205343334312042af0a1262a12fb6497af725470e5d9185a1eb61a02e46a01e8cfa01fb0e90d0470ef7f7336fdcbcb7f24aa596e3950070100d3f5d70685865e660afe0109cbd5eb10885366611685226fbc3b1ca0224fb9a6ab2f850dbd3c8d1a57d786fe525b750118379c008f87f05499eccf1b40318da3a8410a2494836b708875e2c3e65a88a51c6c801f61f21f68a1d35dcbee2f0a80a1a7070e994bc5c128d776026d670609a29e13a37ffd1320f89c0706ff57c8431e53199490cc204d0b00eee3cabcf304c84ca0b32fd1f484db498c42b1454f5da3fbd0ffeb64337769f14eee1aa25940db0892ae49a9c63c0b47cca6964ba9c723b30153aad247e6103c400b702f58eedec60b3eb72f3b0bc96ed017d2cec7748fa043c376a477eb495349c5a6b256f2140f2a14ca447b48bdc2d73cb24a449ace270575d984d201b14e5bb45594e50b3fadc144ef42f4c9288c89de6393700a5355d49532dceedf92cb7203f515ce89ff321852e8e0ea7e91fad10f0a729d38f929ee1c0b8c160c112fec9bb9fd3ca8cf35d3a76880166731852f5313e406aed98b2b0f82c05afc585b5cdf408e8e2a1a87239e1714787fd4d6c7a42da8ac5bee122d6583d30e7f04bfaf8e8a023fe3c62a85bf8be9698ffee5946349ad90cc180ce43be28b7a6dd4604c2fcd2aa05b3d9a8cacc3a7d4a8310479f970c8a42c7d9581ce6a44129bbf5c9d9b520cb23e13ca346c61f470becf842b5f80304930d889fbabf8b139cb0be8c7c1913188d002c1223c75ea1c9cfacd6df3c035ab9cf1abdb8c15dcf79a9e546674314c0175a957f2571848754de8808d5f646946d38627b9750d08423f29e198ac6a56271d205db33d72c7fbd151b9c73260d4081b2850d89f853cda0bf22ce065f242d92dadd97adbe7dcba6e22b54b8f41f9f8326e2cdcc5a92fbef6df11bca25061d4cad102c704b43094d707f93c6c4f6c7391046e286e15870a19d1eb2c900d00e23e426024051f00af9a104a20e5d79f9760b728cd1d5385d0fb0c49146a2beb114b736b9e27b0996da5c090d91cfb7b37db361b424033757afda4df4788b801e33f512111aa3c7a3af1cac5daaef5dcc3857448a4b58c9e1ee502a7c17e4b8100fb427e2206603c9c391f8e6974358bb3301ce58b21046c3922b7233719afe85789a778a4610bae94476585320b15877192fb33db606b429600b0583f5215f53b4b4bc85919df3784964e2fe6f113fa5ca94068fc17b30a5d95c2d4a5764acc29c87a4a0847fdc59783a5fd08d0a06ff3b523143c733144d094ed92046f76a900db2ea04b3ed9d086648d6b1ab092232eac0cd6c8d3365c1127486cf73af4d2c645209dac4d37e377c26ec0a2f5ad16fc0e22aa92c87d519f96d59b905d17992f57533284367aa5b50d6439b9e91c4302beb447a4f529b1045b506478edd5afc9d3f0e63385348ed1a75a302c8a07bd8e6447eceadc5556b7b3bbb9cb4d6190d0ad4a913b3dcb1121dc12cca1efb41a2a1417af324f38d7ff78cd1855e4403af4f9641e004d2b285a3e4e38daeda64a1398e202c02a9ae4565a6ae2c37d7ab82b85aa80e9443a05c8f77147cf29fdf53020124d7071c0848cabe11a27340a2d57391f48b6c6d578a0092d1ab99b74530ea8901911dec678b7c4f850d02120dc5efbf18fde33fe8e7ecf1e90875ff365a70a6a332aafe9c252a761e169734f0cb94b14a4d57b7b70240aa212b8e91c51d0497b3e9d3ba0ca199267641d8a20412c9a8d63948e82a2c45d9eef02a2121a02be591f310fc9c5f2d5bc88251c9da0c175b7a25637fa26b29d1ec7375cc64c5167aeb05746fb54262be22db84fe2c17fd4ed58772ad7147cd763965b62253ec014484462f26b931f7513d1e0b5d14d0c1263e0c8093e96747a679b585dec64fdb72b8445c2fa40e9776ceca693c8e9e37bb6b38da6f775ff1d4d92643038ebb675c86578aa4d1c9006ff215a53fd5571ccd2a1792bf0c5a501546f4be6d325002c56780dbb0309ff47e09daefc730ef66cac1f36b1bbf6a86fa739a42d4f5c3cf344c0cf22631e0c8a020b76bdade0c2c92ab895a179e69f1920fb063105503a1f2f5dc1c705ae424127426201ca259ccc8d7fdc68b1807e0afe31a9b1806ec207b0d1887cb1e280e7a5a86f8a5b3cb42f9fc8bd925314c7b37c9f84f79b1adfc739fd5bdc09ab66c3ac1509c9a45f7778b8991935fb2a3501da98e63ea665e6df0fbe27f72339e63ed254a72602aeac252c9cbe5c03e5a69e43ce0d7f1e8af5a5ad7c4a374c9a7643fb1f97aa354a67cd199554a0e45b187668830ad869f3cf8e212ecab0c611dc91af265a256783593e0b7cfb0879de8e8ccb28e3c7c1d7ec90a00423a1ef1a5d6347e2d67cf5cc96cd34f805a2c7152ad168fe8bf316d0e05062cbd45592689e1da1b52b491fdf1a13d57a3e197990b5efe6dfcb560437e5b3542cab7e902f523f7857650cf4c6b0e16b60199f4384974541ce9daf1327ce8b088af9c16f53d9ecf3646b219c3c0cb17b8ebe00fadbdbb4e986fb28fa3fc599c8fddf4268f132aa4f7d64e11a3b41e98256f2039edb65374b4ecc0f7221307c6a853ea143d3ca020a610258b39bd3dfb92a631f80bd00ca6e663a26d771c35895f40b4659c4823aeba29377cfa763fbc1d7019daaf8b93f79486a87a6c59d9d3a5b80d8fff58e25618fdc36f00895e341e0f89fc7e839dafe60d739c7affa6014a085b3fffb04f4b8da0f5b20267f5142b5ae2f0c92ad584e006a101b60c146046f96ed79e961f0106a04514f00612597ee300bf2430e0e0868fa8687eb6bdc47ec9e2f3f5a8f518042b1f7e7bbba7f6c949cfa100e49355bccc868fe71c0f1e80307cd81dd56f157315fe6e8b9f52a9e41f833612221cbf8f17c6fadda3261e5d07cbc01fb312c771eef811bbea7048a518fee7bfde6f2cfbd1e5761ccf4faaf392cb4fbe0ed17319707d933a6e4966b3081493ade0c822ce9a1f22cd92b45572eafb74abde7d1c7d232a549e483599c75410b9afdc431a0e95b2741499c1df2f7e1a4cbabd07081f9e1706951b9eab497d37c8d4c52bb31e4e739bc5bac084cc4a0c242b917527dca4409f8799a51bf2d7528e237cfbd327bb12a8a73bcf10594c78b0f01a296668cc3b0a7b932e3bac945e91e524a0b65c6e7152c58d4ad9e7453e85728175bf6e3928adb79f4abf69e00d9f95f04ac3e999dcb961217e7e844b5b08cd4eb298d511811dd0f10378f80d8c17e72109744121fb5705370238c38dc918a60fa0c4b68bb8e680b4b867823b74b486ba3939ba663f00bbc2508be3dd2cb348205a2f38bdc28b5ab24917f7fc3095add02775d302dda9c1dfa15336d1c7f4cb4f7c9d8b88cb85ab9a4684787cede17d926da7b7ac16aa6b2e813fff233c6781570a49ff02b09af111dc97bf5a1bcc69f1ac7a522ed21a76448632f93ef1e27001062b77b800f765837ee371bfeb1cd70548777e9c283ad5d5b5c5bc28eb1bdd60681b79daceef3a5c3f5b6db8da49592ebee77b3a0891e2b0e5e3e66b53e73e3e88b6aa38a12deabc7393a24363cbe00e54e9afa90232614c2a2257312d2ecee41f140b4d66c3a72f91228c10efa08c3826fc6b9e873ff6ea9316cc00cbd808320fabc51215a4ca59e735abbaac028e3e3609c9a2262ff92b4997dcd103327c90d0f9309f3fac783dfbe3eee0dbca1c3886b088bca75694cdd597be69b98f9011237a8594914ca47a37387de65906f12335dbbcc322feb14163a3d7acbd0d58e7684b71557345f3ef8a03d3ef249e57b35ecb2edfb1070e956441bb3b01c4940d2a9aa5c3fca80471ce3dc50f9e2735cabeef1901038484b0c9281781406f81e65f7037962691ee7038568da1bb98f83ed7528119da91b0502c83b1393d1af165a00604720807b446f8062488e4b6f3c7abdb498d7fb9f9f3daf9a71afab09e4f9f25ea81de07fbb5a77f0ebb92cef59eb6e709a3af46738bc8637b78fc75cac372f3883425781b3e77cca1ff4d567a503c5c1c4b7a5e98a41fb92b8719892b403e4f9731fd67e3c83b34bf5f0858e8695b36caf243f09242a86d64662f738c4e0aa1cb5ea38734f12946df7e6ef2b86f3a2c8365c4c09aad3ed5e1d54f6f51c8a15d6fa342d723141d62a699f83d2729942b092ae7c18af13dba40ab382079f8c5c8f5fbc993550a8725abae055a8bd07e3b29f7f031d21dba2ba45e8e07411048ef11d64cb9ff7ade4d0cd5f630f689140ddb79267bd741dbd2f244d15cfb6b0afb476a830ead6d46a7957cc0c2f11c50f159ba9d147ee84d149af63133e8ea1ce84ce9e5661c6a3f7637a862db7125207662cd23721fcdf3e58677cb0c5120acfaac3012129fc404ed82b002370751ece72941dbee95e298e95e007a5bfacc2cac6e3dfe91880e22a7646b08c38bef8142763eb6719135c9b5d2ced50b83ef05a7d30c7744dc140cadb6a3b82c26717399498a061c60ff96580209b76a7ee1b4da6efe801f3800d31668c9ebb37a9ba76e63bdc17f632cefaf8263566e7dc98498fd1ee674df485c72647bf963d60322ec9bf34c994e608f8032b1176f3770c26696f772d56be28a74b1e30106832f5b3438477a1496ee1a7f066b35a2c03d6e63a4c475ca494412a8a95faffcfddc6084b7ddb16e820027c337a2dd3825c40deb43f2668cd69930eca1718dc92efcf9fda1791def389dd3029973351e92de808ba514d6a9094f349a6ac2a970514c676fb3084d491cc45bb95a6e1a0c15264142476883204f6d3b9456a30a386e2e9c68f321f50357ce54cfd9aa8a30689ed4296cbda1efc44b1237455af5b6f32c3a12094eb06d9e2cfb97a0518529e89b769570f7224add5194cf0cce3ab66a3f11692e9ba593ab7488c8c7a3710f746734202ebd86250f58a42192d2d30067451eaba90f7503801c784fe6e3f07a6d5e6eb58130cca2182e77a65a41be8681c2c9f09d17e9114e820c75b74b022bf536c30552b3cb79298da81e27b194398b46f4bd9aef3bcee77e7cca7d044f417216174bf42b0e47a5aa92b48902e6e9d5c8ae2a85919568e28e104f66aea1d3e0e0440222ca651432866e033ebea0e1af90bf128c97cf045ff57ae7f8192c0189978c561d452091a0f111480688c311ec2dfa2d790ec3ed0bd0b3fce2f2c93d96a963255ed54313d37f486f7bf6be3e84382f157981610e07e827048863d028788178baf150b4182593117d7986a7db0f222805aaa775932805c8cfbe685802776053ffcfad892a6f157a84a7a5d9d87da08f8a81d351e523c9acc996441c1d708dc218cd5d944e0b05c5d77ae0d790f66e51556c8ce2141ea6a1d11c0eeb29efe4b7ce1aa4f238ece789f473c7ea424f4428bc9f1946f6524485543cc4d645d3452374151c87513f21235c63bef1c3d6f3764bffac6c3ba045a5ad5824ff69a478d6067896d9ce8c2ccf096c376ba99f374f48a5c5016c85afbb239baed006c00b478b8afcf1a376e7bb1da20320577e9b5d94305ea10be9c124196c66c964aae95ce51d343d0e6c20036b03a939f161ff70d5561901acf81578a3bde143ce9223c0785b5046a3233647e2cc45fac8d7684ec9fe2cc4a3fab0dd5efce575c77e56c47bcc142f9e45bad683364dce83a110595f68d1605a4fd594e3764d85489c360603f1e91c2ab0222561671a8550620ff9504e8cfa813abbb1088260a4f83c55bdccb191431424b21c146f18e5e81672080474ae987adc2f485d16bf4de53d10ec00ad3a5f7c52d9370d6d8b23fbbc3c0f43505d42c470a27e37f6476e60687dac9a3f7eef53fa086f172d76734c79bc305fec14a55d09167a25873c676582d24c906d741e2f04c02557646b4f31dc54d8d9924d0991638d890800323d3ee5f2fb5dbf1aea94e340f07584e0f9ca50753f95312771f8a07a23c4ef26f807e42f90d78bf47bc2f30f4e49f17873deee999698f77ad48cbc2a5d313d12c6580cfdb71bd04573d9c0d6bced810e28024d7f60b3635194aee7a77f94a5805393039da139009c8c5e3b409bce5e4e94f86ee76b3eb557af54e5eb60507e01d78baecf2f902fc6a2b71e0bed3b064de28fb1efd30d6ae1f3b0fb84f18302d74ccb4f1855fa1667f501ed04d049cfbd4862850e082addd7ae2423418aafaee42ab30c07f089e70a332fea6a9ee94af330b079177c115f199cd6ad32c1c85c08c9607a00093def4f521d31ec69a9093be5a37291b1a4a98962c12042603a1a713c2475ab2202ca0009f69f3d16a1319f3e58c77fb98656de0fc9c2e18ff5d327e97212d756a2405944a87f93907048d18f15db062f1131a1a69fbcfa65ec23a909dadd0fecf619b0bb018f3318371da8f8e202366e8b67ec70faae942690f0119ddd241d670f57a7040b91ea1b86653d03106905d91e247d9673b60dc95d497c29ab74659880105891dec3fee5e9cbdbd209d5f81c89e7522a14d6a16c2b4e3a91f0d01999832b7924969d91ae46957d7eda25cb0da11644b45b0b09df5c0be7f3ddc474c2a7b120f7ddf94dec093fa87ce23ffa45c7b5a9391b511223f0573572828ee9d388559ddece78d7c6a96a991fe9f5be165b3df9942fd79936965b57fdbafa2ce65e16bec33d4781ea7a69ba7105b6f45eddc23778cc2567a167dddd1921027712835e74d09199be85a3a96cc0d7029a31f6fc6f7828a3e5d8496eea512c6fdec2da5b755d371ad76508e19165c36ac0b82c3d550acf9b4825b7c4bfd784964d4e1e2f3a908e087b4366accba11eb8baa916554b1e862c389da88a2f353645d0f99a7a149f2ee7b24af2137740fbc81a8a2dfb9f9b87a2a135f4bc6a9f74a9200b7a3a5350cfdd8f584b5b2a0c1c0243fbc45e9a092b0dc985876633374ac368a0137b50053d859c5fa5c6b7540e87056647d580c798bbbae7ea07821b453c694e82729d133bbb7b0da9da5a0b679950cbdf8431c4e162a55c7f3a45f7ea3b58a1d639b3826d11796ac41d7c83584f25fdcaa2af933c4cb00aab2e682b74c6fd8d5ce914416740442aecaf09184d1db1f10a1715c849a52ad08c1f4be9e0dda5529c66c01211c5f7bc1c9cc3776974f1b81a2901bd4f6bf94ed2ca17e0103d1ed5610185b40b5f49ceddc569e0af6736fa550f6b643b6be79a78e208a309aa34072a1056dbde9393c2ae3adda0899e78e983bb3ee7694b15c404ca77adfde709daa5f0e5dd0e003cfc9dca1229c72cbb11647d1f9c84abc77625a32951143a27e28a796f1489df776dbc8bf23dd8544d9a320f4d2dd62ac6e9df6568476371e85a7e55345ca0371e187172afd1c091a71325ae72ff9e0f2d87417b86f637d423cb1004be6016bf860babdf3990648c04914d0842ff7c6eac7c2b7338d30734225cda8852f20f58e03cae39b982e98255082e75ba3312341d41360ef56b92413f792b164d3a1770622934a26c8e1723e98045a5e0f0cc2ccc41d5c9d69693d126a823f54beea87db18273cc2819774522a4dc150852c6b855eb5212cae530d0999c18a5131925c2f54426f1bcf60baa99a4e93d020cc55921e2651ced4f7b3fb783492cd25f2ca47ab199288cfc17d7131367d64ba03023f13e978cec380ff087eaade0279538d91afc5a7acbc0eb57c08e135d4d66a9f32da24353c5443f7a5b2b3d32be6db96f053e34014cde144a71aa4d5b02b20bfcf14a409151fb89a21d953633582c1c617f09393f5a6b22a60c64b360b28fdd78997fdee6355e841ee518ec9245efb6715b8070596982ae0a6ac1650607a00b12a20af58027e2b993e4b12801b13aa6151d4251e2772cb3ba0445c1709282221ff01bcc93a47fff2fbd25c00b04e210bc2965f5864a146a757ed91bf28f21e06affc3a03c60e276ac3a79da44efb1ec94cf5e52bf6202707d46220dcebb816401366abe92ebdbb21048ccfcd210f4f073e3c6e189fe521a1fe4f5e13a3e0696df30ff6ab01324aa58a7072026d3662cb5615ed732b9225b18dbabe52fdd2a3ccd880ba97b51b53b1049eadbf93e186a5ec19f7a77dac125f31d6f6f975384c38159babd998ad81e183f3bf0e4327f0f670244e0534bed9cc049b93d5e9345e8389fa87b488c00ceee5b994007cf0374e5a1206a8ecc6aab0c677c69ce3d9420838e06ba74b536e90df4a6b4cf3a74c2844bb6707b1a46595e89d9913d1a8b1591777d5939a944c9d35a51e116e4dc652578ea5613a3d56c1b8b5ca58dfdadb64ac92bac91043004f73875668c111a69b7e13ba16ca79437cecc280ce663e23549b5a373dda7633a84afbc94483ac5b632a1fd7bbfad81a7a824ce57a2c5f930d28dd5d128a3eba35a510e0ba16cf3cf6681820e761a9eda484b4b0cd68b295a399b4b23ef593cd710ce363410915e8e1d908b8004221b2b941248bc4709ee0f9a84357ccb022906aa64e32b1b274ab53a61661ad0d706ea2b329b06641ff4d7c2a59075cb081e6bfd79b146039039f9166d981c8cd160cbb00ee0a9c6bc79be670dce06a4a90bfe2cc70b88a3ec73b44d30d5029a17755503c0ad221a1ba10d1803c89da4ee8a354b94dcd0cd0c7a80630a9dbd61e96bb7b0bee217cf62482b9384657f6f35de5bb848f5c985cdff94eaf285bf638e148c2991ee2b07af2305a580b15c816a7f6c42a85085243ba2ef503315581bdf4ac195957c09bd27a1b0a52abaf61ec4d77d4a2428056377927be78ad1ad9df219d340df2d6cdf8cea2f6bc210fbd108fe5a0eae6ae7e0c8014dc146faf5fc8acb102b96fe0a29386ff3030f5e4b747db4f70ac6f6da779e5142048ec227f67310e7cf661c5ce1800dc76d6f9d8274ba787fd6920f68bba706b24afd754ba5e784c759d4a2c8c90e85a5866901e4d050ae6c66e91004f635be94a7c650b7fcca6f47e2fa2726608f7c52398ab47a9cf06ace126a2475e81ebf9ce0f3d2a780878813d34b2f0494c33802a529871451ffa55ad4229cdab7b06b8655b73e2fe873b6026fd3310892e7583c4a3dbe5f9c61810234bca70fa6d0cb809b99eae5074d828a0726cce2675d47e081b6ed59effbac43abe735f224175ff9da91fae73dfe946cdfef4a459802b744d8a20dc3b04fa3fa98feed179cef58c83050ddd1bbfcbecf4e6aa28b2cb8a944592296b2dfd7d490d6d28a2c311eded8b64de57438c06abffaa9ae48f767e9dce942a9579e844bd51adae441788199934f887f9fd646233111f12d529f842f4540b9244ea4fb09559d845e05c3dcc8f9c69e81c792218ddda7acde6f47a549522175f8f5c444caa4a6cc68edb40fc362a34a508bd42c1671bba8f76a74a78e7a751a6d93239ad9c0a8db1be7f766d99c6e028e8cf9ec99bcb725a520649d7f87b32a125343af25502d1a2790b0379e78f624c9f2815a0af17f5cbc19043da0e0686140f116da8a74ae23ade2f5544a5ddcca893ba0c8fe7d94cb083128c68962fef707d609f158c5e5f0d1cba3442c5c31cdd489799d43f4507458d3e8d8d9d25044d229dae1648b7e24e133ee729aa2233bc13e53a17b4b416572c4a269f188fb0186cfb629446bdcd1bb4d5c25e92bb833c2f535e3eabc82bb54c3ffc8d7918f501b984004582df1594ce3a7d8944cfd320803d0ef5626110a06fa9d0c0d412ed24926f82a60025da1b2443682396253780f86a6efccb8c8d6260d3d0a937c6841f69d94e401ee68ca2f600b8e3833c86c0ec5407ccda19b8ec0080155a95d1680295ca47dd0f225ffa62c21cedf87c44222a2c387a03960f85e11e41d3ed5973757c5787c3f61f82ec9a79aae6d2d9b41911d569fd6ecc2caea79709585cf44b9a67263c067babcaa18e40bb3f909667ef3ad96e4e3a5bfc295ddba11eaf0e6cd7e067cf63c62a846fb12a95fe6e5521553f24ade8860bc1646358ccc345405c10a83f28420e7641694e752fb2924d9b4fcb16ef3447a740e462c39e17ede09d381b9accaf9ac192f3296da86a1db7fad4139dd41d8d97522c1ac328ef1f88e9e18555a8838adff08d89e8681c1e54fbc750c0e950a0a1724be114d7834c31047399ea5f2e5b306ce720ed6ba2be4eeb393e4fc7facb2558c317ed25c4d7f68e121927bdc2c723192875d416be4b373734fd463a122e3d32d1d4c3f6498bb2003f2a28c5ee4d9799ab1782c56bd81eed589c89a7f03d002613c8a9f50d7412434d08c200ebdaf5df1059786e493c281d0d9c9197949cd29d7d12d1daeec160ed3c171fed5b92acb586ea06955bb666ca7aa0f4178d7607b7a61f557e567efa5fbe04d933bb8fe7ea1e62b4a3f79a32faced7026b0477b818084af97ba082d94bd867f407efc0838887e5fbe78df84f440d79175187dd8eb6f759abdb633a4d0f4a91229914fc3c4ed16fac1cf2244f43e5e91aabfb78e69fc56c865c91cf2ac2e0a3dc6fdcd10b464b87b2d772aa5308df4b94d07bb50aadba8fe8b7fc78b2dcfc537334d1846eae0e6f6e39a987da076bc145012b7942812fadde6d509848b8387d0b0ccdbdb594c3f0ed122f6c61e86272679df6d6f7c25ffe4799f158e1cde0c862996235cc338f40ecf0be683373321705679c846bc4a2a875da499f8c9ca57f4afb89d5a89343aabb4805d24c72e510453f2f7ed3ffd64468d03b83ecafcbee0298fce55215ca0fc1eb01d3c754613c8b06541e49dbb18e6aa15fa98b215284278c5be07b753f266c1670bef8a47b464ad0a6e3cb14e12d96652213976802a852b0ec2ae7cbe3950a12e03907503e3d732e4834c5e13044dc4f309891a919f94ff825085b03f57358d423a6ff7325cdf1360aa60186baf2abb0c14e3bd57f875d16b50dad1adba30fd40a8293873ce29ecc7450d28e5634b935745ea59421389c0966ea21e8ad401cf03a48ce76c429faacb1b0f1b1b6a3edce9d9783951510d0037fec728f6d4a667cc8aa8db31bb6fc2e747f2d6e2a95b513c4fa86b0789eb830fb7ff30d54681e5cb04e694eace024e18d69e31b0d1872bcad68f8b41d387c3644a9dd8c5de433521bdf40739f591a895513d1e2c9447d1165e28c62afedc414b206bc12eb635fa7f0a8148d4fa1d06be64ab88f4b06b7ce0abec27738d30dfea8682002548584dcdad8869a4400f053249de6e844054e3c9f914b115bf2831bae4a44c09f87ed9d492e561f0aca33a15c4d629a0fa41e4616ee5724d1d0061e06b3ee3afd025574ffd11bd7b1307221d0c2409d12a3751b4abb41bd6bf8196b6e5c9f42c80055b7737b0b7de2a74b660af66fe592fcf01cb322ca6fefb0511d280e7e3d6347a44caa3c334a849b822bbfbb60ce6aa328fac02daba5a2dc0fb284095115549bf468e8769b570bdf0d43ddffc3ddbb05d2cf24b0a1d5b5765aa4525444e672e17c64d616bd9d8efae34bd407a405247ca2501492bc53b84058b875d9074b76870318ff936e4953764b738ed4554e16ec35aea1fe09880fd8100e2f824743ce8d86208142e06355a3fcc2e0f4479d7d936aecc8394aeac5864cb88650b2f5ba1041bef504182ea96594a792a8582e04248934bbd16082716ac9d039689e39574886e87f4744addfaf0ad3046a880828a46e42ebd7829568243b88ac75ae70c10d39815551aed74ef3350d62171deefc0d4367ca33173a9976cb7ef15647c8b023106847d8cbf8f645790bedeff1cbe6617b4ec0c0a2bbc736a35773a176193e306daf09e78603567a4ee5c319fe87123e8aee5346b31838677022437e50c48813968f416d52404aaad52b81e0e2afcdb811face271db0a2bb18d821a191e95d20b418c8a4221860947f399377ebe2c2f4e6c316fa2dc503b61c2f382421bd273356cc4523803a0c29e7976de3b0d5bc18a25f727b2f88a89de19494c11cb1dd241d343b64ca338816ee7fe8a8700077a8db4b6cdc4c01e6fd3353a3c470cb9865c11da74928b1a06184b4d2da8d8be651278e22a8b369eaf5e1d38bc9fef45d16dd324a8a58d52cb466d8c55802427bc003540478e727939ef0fce2e3a25bcdd06849919929385b50e558b41f5d2bc73d9f0ec3c692e4edb1bc5d382858d242f5258cd676ebbb36d801796dca8d36b75de0c0b661076adddc075f9c9468878daaa9574dbfc838a401ac7f6e618ddf03323745e68ec73343eef68e90bdfedfd2b06a2f105f0ed9d9306f1f2ab9389f9e29eacae056a7246a099a6f71b9e30bf4108727644bb44a8315e019dfd8570f92614157a6a03ebea0d649380d94678cdf2192089822b26c3d09446747d3f262b3d4e65892df2ce1d01525de31639d7db0ee221029c120b47e36d3d10ec4a99d5b17f3e543cce4acb54090740d0a8d5d79bc4cd5b49f853c4727dc4a734e466d4764d88e68a347b4b5695136505ae40b20124bfe8910b5394aa66b6482f86fd2ee3fe2f7b19aab8d5a58549a1c6162d8910729c3ab8a0f3018ba061831af82484709e0f932234718239842a085325fbdf14a0a80bdf749ee8a34ffb06696362b5ad1feeb378f2d84f3f6e1ffd8685e2073846c639d333c9fca1ff91ccf84ca0614b0afea1558d214e07296c2a2325ec91ae3b2e0fad4af9733900206104c022fc78d989404fac17c54fc2b5d36947e29038f6e511a4aaa7a2c0356e3a6008fec8a561f4297596f712fb54ac94b7a216bb956086cb9731f87d23efcbd40ca1e04fa6a637fc550a4910d1e2fb1fb218a332425812cb3c8a5515b3418770a04473cc1dc16c38df2bc94e7f2e78ba8017983db6db417142a48c7c27c0a5f82ac34a5190e69a76af47e0d54dfaf46ef7a71d144219e14255551b3552e0b83b310ae59877f2a8fc1348a9ddf165ddf2063b726a8b9a73299e400db166cf32e2d2e12a7a57973f2addef80b5d3225ea0a75b26e5b103353d54505a881a312a264d0cc36f4d8eb6d0c90ba8a91cc7e335748db4450d350a46f2544cad33078c45c73934b5a6973d6307b239e6fd36cb7d9c55846a5ff2051d79bf3922dfb26c44a43a4105b3aed619a6fcbad0c84d65c806d0be5ed7bb90d29d0458e026cf25d0e08b6205584fca0db106b50ea4015fa811240ef7d3778c7c5f3b3f7074405e258827554ae388558060b0f92590bcf6cacd693fab6a382dcf085f7b19d52694af26506a64d63e9bf815bb4a07198a9b8a3944d2a2307d4dc8e425699216d99355376982fdc1e316ebbaf422d8baa359d0bb775d56d5f520e8241dae140176972b28486e041f708ca0cdb0aaa3405125b8374b62e5d073152d63e73e82f014de5825cb2e618ac4051e662a0dbd5cc5bce702c3105593337bd4d13671a54f194349560552c85655adc34b9261b7ea3052b63100c0773b738fa42e8a9a61e9ac4db9b4e187c081fdd949ae1834f62d54cfadceac2df2aa70476bf095c907c4e7175f8d65d1513a96a428db70c4097dfa950941b9d3a338d423ee07fba031842b9cae426926b5562d72221ea0ec30da23f08ee83fe45a686b8ada3e0ced43dfe0901d2d16be082d0327226c8bc09febe9551f98ec7d091a06584484dcca2b8c44ad78b53d25d9c11d8f8e2e6c802942aee57c16de3e42c47ab24f4883a26ec214bc35f44fb32e570f4f706151303c3dc51b45433854d82db5a382c2dde05afab9db33182b0d84c342dc2ea9798427d34ef24f4228d79b2e1f3a2abbb81efe530bd934f562f118c842166d9bc9bb5acfa2998c30369f3a0ab07a29d40f3d059b6b863ab70b61cef7cd98dca1523d79ddc4ee6aaab302de008908ab0f42aad165ea81cfb43f7aba00850dbd8b2d1899ac310dbcd070d2181996f64a3b321183f95b2c862befe5e6aaf96f401f5b4835e30196a93522bc9dbb9aa084d4bf782d5f26648d6ffce53983599fa61394b24f06869a92777b08200a966f249243c23adc70104f26f6d54b3d364871185ab181a12e6fd43082ad0d89e24d790f7b417199d71695b310aaa7a13e67c7382415f93c17f5bc4c081f49dfb3a022fa52f233470105ef91aaae4007d2d65c42c79ccc79279c51411a8cc24934e9563bb6032a68401791299c9d8334581e5a1d1df0520e0301dd7bce819379249ee60b05604b9b20552af0271d4461c03ef46c6216b69ae9ef8aacaa009d2065ebc96397527947c1b9d8293ad4c35b22a78d552f8f7be200c04069e81761609cdcec4b03179795ab372d6aec0189887cf0100b17159e689dda9416ecaeb2d145fbeb125068c2024ce15ec9917a7ed88e4eaf89f021b4e42287e92d45d00ab3b166249396f8220e75c75473f1884ef818e6e9dba02303212e6daeacd5e459392aada4939d51345761e6827868b8ba1453c4c2e0cd593417ade4113195bb575dcae6363f2f1633086f64cfa498a86c21ef571a440322d319ab4805189cdb532071c601d9fbfc8e9ba86f4a1c9efad2f81a411f09e4a3e48548bc43c789d9e009c6b28998a1027ec5c4717843066c9afe18bebbe20b232af6a655eca1d984ead217d94f9884f03563eaaac2367ef80cbfe033b3a72eeb2737b2ea84d9f7560899c49e0ef18ac9c210c7851e08ffa175ee49d111d0c41670a32e169352989ee7f74c995db970206a09baf746bc3e495d5579da31bff7bc4fe27b61bd7ad08966cee6827b717a0a35f63efa3a7ea0e99053cc253d50d359160973ef869569eb8d68259539981242f401386c08908f997fe78e63ef0c69ef69d3c9a923a4886680d8d7947a7432d88ce4c6a6dc06012e9604d02853ee8670ccb4a84c901521693f5ac5db38c1b2843270862499e632b6f6b1cecdd5860a27d6cdb882522dcbc227d9510b27206b26fe12755077e58a102ef47a1e1d0aece2a0f5e6cb8c8042225aaa2435c84fe9800bbfb73bbcf15a412f5c36ad1d6d17a0206c7fdef589a4a0acb953f4b2d5eaf3a14fe03713ca4903f544cf0864a4dfb938b855511199769795a28a91038374938589bc13f575b4e41184dafc6ea97a817641a778017039485e8194410abb1d07bc4e61175d0628234520abcbd4389139653ce8bf12a91b674261915c62d504d1de5a2511d42cf7c17271ed6cc5e50854fbabdfba8739f3632eb6c0502081eb4634bc0930967c682472162139bdb324cda906ee086ecce1bd2cbfc6be0b424657b73df3c9c301c83ba747be15c46b2154faf06ec3c90f2dea7959266dc5adbb17942ea1131010178318d6dd53a6cacf531eb9139c5042178b289a3100c0e6f89a80e456ea0975fa4caa36d4ecfb353f9c7fe355a844001a253325f40f2dac2fd5715951a235d482f32673e913e4ac3dd4d34885bc8d87f1ca74d9f605aafbddfe1063826f04c72b6a36677844d6f3c9a8474e178bf5f79259d412690c0a602343ecff9408a1a6c9274042418d17960a81639db93bd6ecd7843d69029ac2091ce5510759e2ce83c7b1a665295979d77b1b4732eaef89c4a4d233cf61c7c554c533dca53acb0f8b7b478c95beffc8c93ebeadc708cb686fd807cfe1cc1b1ff0260bc38c0ac00b8fa80ef391881a6d50bf5e3c684bc754282a4bac192043930fc782461a6731e1baace6564b4bf0909b577bed704ae8453c1c64ac63d3e0f0de8a901b14743ec432961340c9d35139d070c422822ed48e776e4bf418a191fcb9cbc7043b6e5407cb9623ec3cd4a02e4562025917aca22f219662c7d78a40290495149e2435f02133a4f211117fd3ff554e60875f4866bca6471e38479d4117fec0eaca34c40b587d8b6c4937c468d2a10003aaa2db1bc86d87652725bf98260f6c2ab31c3ff6d7cf3ce6e60170ef6c2f30c313ca5155c3cdfaceb62833f65bf3a62fc251b0e69bb2d0e92ce127f57afe8195eaf185a46272667d37a78ca73bfa69992c2e06e83b15e5a528866fc35882f3f4e1a8f0e3b2e7e54c51e6f5a9d6b882965efb648c1ee9967f1c66067ab5348c6f0ec98d4a147b29df04583710ae1848b58bd56c260cc1303b5da88373c3835ad7182babf503b3be2c8a86e97d385d667e6d3e154eebb740e2d27c37d338f36bf09f305bbfca1d0fa7f07458d1ce2d8ed5063402cbeb6197c55b694fc6b41133720cd7f978f71602a15b426cc091e2cd38a8e22c594c4390e0e5cd3bc16a6d55ed3f0741b5e0e21f09232b8a4615386df5f8eb0f6c7fc731f30d030c12c8fcac26cbf65b30a5762a5456549a98104be0a8dc511c6f98bbd93e9e3b2a40463256c842ed6e513d274d9616b1ec080a8a75881bce47376f4545bc796bc96e725e4556879756f63c31ce887b4e7c76b5ef692c01621e84fbfa8dd95e4f7c5938eef0097db918e030a533752e49383e10c1d2a3591a7dbcdbbce0afbbf26ffaa31ed0b326712f241297b45167792c8dc980965733c70e83fa02c744d03c0a9eacb955bbe6cf33e850e3c952db95c2ba44bb439570513f583f72139816f4ff8d07e3023b1fd229143424bae8073501e44ab983f5c916d1307a77b11fba9415a1a6bf15580cea121fb56cfdcb8acdb357dddf8631f8d7ebda3201a25ebfc1a56d8301482107412d970abb769d3e8f2eadbbd071f0eff081fa1127facb4a54dc68a28abcc2288560d009e386f8447b4ad1089955d44f3e0ea70358cf008b2d2a5b343e180dc960bbe7e497f889724c5b82e10013d5726b42abd8cc2557acad595c1bdf690b87717d0359dd85665f04217c5139b8f71dfd69bec10cb9ac86d8e297231103f4c4f4e31f312a1e0de815113fbae4746a5b25c6c305eac3b4870b91b07c15a86acb15a30d2cac563a2c1c1f54dca6e884de1eed927de68268a779b58bc3cf18b8a66dba5c69ec384f9ea7a566b5623c30e812e6cd75a2c8cb0c37def8590420d90179650c1448b4f4067c9fd7406906ac49277f59fc88e02e2f28f184bdc0cf83fca18b451153bb8d0b0697999f6b7e180b301234dc16d9c2041310974bf44c0b81ff0881ce8d2aecf5ce0134230557ce5e69833e6ae261bdbeab32aa21fe7f61be62c19fd5f9df7ef41a2eb5f3c28201f8ed72ed22f1122565139d85d187993b2f478ce7b37cedf046aaa2fe84c9167801036ee541527663b3f1a44d711b62c99e9922e339798914f68e5e9266e78d5c6bf1dc0c73309e771fcc8b4ab0097d772ad99a2ac6d6fcd7903bee0c3a3ffaf95bf95bbb29bd49afcc95e2a4477e67835e25ee1866b4f012e93e7db4637c409b7399cc60a70a9be1408de5c79ef17c45d4d587fc3b8c397b1bf03d717e0e0dbc72bccd98f3201b891079abceb4cd0d37d27255b96eb4360b277213ae554cddd84ed855e826d5fa473d467ddfaf5921a951fb18f9b300aff7ee24f9c8bf561269b9c119406db8ee7a6e3d874aed39480e156d150c50651599b27af1a89807708df0cc55393606b5884936899c2f7354112c721bf5e664d6ed43c8568cc6baa0dacb9bbb1e8349e199c56f5b805fd363ca17c1f90b8f23dc4c11973f419f02569769cd73c36bbd8a1c709e6c92a36328cd869ec8aa3d7d17f4e0a5d75bf44fc55d72e85559a67af37734a7a03178aa662c61d19e061646d4f4af7a9537db0f45af900618e7511bafb8805d5eb6095757f27e0bc3d9b3889bb2f73ef3116eba3e6408a9866dcabcfd783147a4b64909ef7f4c9998c5eb7e127fadb0fa331cee479351addd90e74039aa0c2c7aa3f1380a0e0dbdb73f4044af13673602a50b8e76b6792f559178499d6062aa63f40cb053a5b0c377bb9752c8a6db3725086dc8492a316d70279a5ace0ca9ab8c972a252a2e3fabb4110a797a823107bd7ca284ac354379da4ebaf5fa85f74712f1a4aa1a4271deb4bd3ead701e3e5bb3fdabc469d25fcc6cfcacb85023979f490713d3e4d9b34e1bcf0e18fb493ac33dae0f626873e844387f39697d8274a06d51374a39cf9c7967fce7cbcee76f7b272cdfe6c53c976a2aa7568c5d9dfe726ede8a8ee0cd2254e89da1b45f00136a79ce7bcb5ef0c023688bfb442e37c52cbd9177630cee6dcd12a596209a2d3b38c11861fc18d142c57fe191fffc2b8b8f5def62951d4192ab1be53545389f98accf8c7732cdfe6e55b11326964b403932e8b90732afad8217650bcc352cfb1110db635a1f4d1c6adeabef6df71cdfe18b60dd4cbf8c0784fe58d9ba7e78344fef5eaad4a3f4f935018db5e047ee1feacc56ff9d50fcf041a847735c321986ef2113bfa4596ba5e566bdf283ee55e2da2a013818b39f43cd13cae068de677c559502f96274bcd889c0692624d027500b3e8633a978263d78d5fecff8119354ec82c2d84369c3ae8351764b90edcea004e7bd7867ca1fded9a6a9377b50e6fad89726982fc75e5834caf8818cd6e6c6493b3fd61b27e142b326e0ac54cbf0a472f20147da4e24f1485390db21a622b871f62f1903f030aaccc320543a7c0d0a5542376dcd771ede090c433de52a0856871c0de7d438d6b636dc83bc4ca7d100709bf7655b7d48b47594912ada7bf1f755940f22f1ef6f362a7eab8576493b66afd276ad53d84c09b0cde0826caaf1663fea860ef437dec0b996f35c138799d3ce8f63851b1a97c5f4f978036ec0ac3fd15d15beebab8d7b5dcb975071650dda8d4db24756730dc4f1f52a13f5ce343e679dd835669153a28f60ad6eac20acab01062f4d210b12ba2cfe668822410d2c7fda5db8bfd707861bd9c389cd0df047cece78f6564bf671d59e978469465dab77a54a7c76e2d3509cb012571992192c0dfcb8b001906e14f21bcc60c9be6356801e6a7933afea78e24b0efed309a14047a5c80d656359f5a00d6faa0f33b29244ead69da8f09f0b4f323b697262d545dad86d6029965e8a47a8b617e7341d253566f12d6c304edc28a06310dcb6a4e75c37f3f05d94937bf318c46bfd833d0029408d323f431c73535f6f54e1ec50d0d88c1e61ddd5ca4f4c751faf21122ab0d9c81fc0e4c095fa8763371ce40fef52bcda44a77cfde35c0fdc8a42dc600e918b425385872e76b5e28bb7ff09c41a50abbe3fd4ef858c908779998f144e0ccaebbcc6644df1dae4803ae099a072d1928a481713a7a21c639924311e4a551376c11fc956152f0645df25d0530507959c6702a6cdf912c2f75f22ff80954f22cba126169eb5e216bfcca861008bb6673af8e7288c7605602799e8d7b08f8aa6a0f9bebf9ed8243ae268b0a599500116b98855f52b197f6abf6b097c59deffca4b5c64f0b7b99f77da971977dd54b8e8c348c658300e448af9799107a59a85a62d4df27ee98cd9b3da879d9e9d52d9ac878d2c3f2d2029226be0e03668f73a64f970e73fbc4f82e8b4474084d53abc53896e4bae2c0d68a8301f2b2b708bfdbc2a2eb9b2505e94c32c7e156cf0beb36a3089d7a840914411defeeda756d992980e9ae22f4f78d1e293d55f693acbcc04401775bb18293c36bbdcfac32582c5f8f814c349dc9d0bfcc9d4b88778179ffabba0cbb78429da2622d5683c75789d283692d27e9665bf103d12013545534bf891802290d74830aa2c95bf01651ad0f38e9be58a8e242d33049c1349e8fbf182a4b9e421581f841b3186ee876519f793c54155bb76c953418f3232e5af5cd7494d1ad3f4cb15808962040d0c5abae4c1009771319cb9b4cb406f62ac5024d21d83d5892d3abc59f723597825e258786d780c68a17eb66a3321fa78c0a36cde90597ae16281bc9b48568b6b5bb80c050ef91a7037c473a10b80a4249571734e2bc001650531baed096ea2d77bf865da08966886e8274a8d80b0cc9f5053a96e5490d6801d5178c9ce6c78b325d78caefd81b4974c1b056425eaa9ef3f01101a4897c361273a6eb11510a705eb7022e8f802f252fb0592d61e160b524782f5889d06753a582514b5eb38235615d508fc3ad09f4cd00e0bc3524736c37de3e8da10c8433452ad5f075e798893a772910f16f09968f64a093f66af05bac0d48bb908103937e77285214ecb054e9a26346f508bb155a154179c7a9435b998b48fb5803862ea2408f9ee3a333ea480c2e048c81eca8fecc9a3fc5e4334b202aa47b92962fdb5d3dc6ca24e9762328625e03298c296094a5bf37532480899f4ef3f6a719a52b23fff63896e991bf9989a82d757e51d03f0f77e1703076266c20d23307a71e33a8b0c32ae9c92316696f23f1c4b645281c6292c3600f917090297200053b953d3eec61d3bfeb30be9839004bd2ae6aadaea1d0cd18496722a027569fbfd522f2907c67bf8f4178b8326786aa10b40b3c2bf5268eec1be1688866a5395b894c9c835dae04659df393d4325c1653a7a578da667a3215654e894b7f9759b887a9c2e6f3d76531a5864672a52db0db479420bb298f09253ef8306d3ecd30319a4d3c5d24d55de273f03db52989332b09e619725c24288f483b4777a1977cf0724142a30a13b75615459287ecf730a4f6c2623be90b8bb5e9f7581d036adcea44213a2280bc7f34e813cc7e72770330169a34d62fb8ea1c8801dab4becd4ceb9344ebbe58d251189978fba25f291d802721c36011daa43786fde60bec710858b648f581e8d7c198a74e8d5b500112fce097baebc3d47da770f2a9525654732648132989ffae00bba14ca680d80462322527582e8370d8102e3b935f2806f20d5bee2b01527f00a92715bd2e838107e82c39d0b1379f270bf2a8446e105568c057bb940ac40d3c51b8249b58dfc181af2904e1f86c9d6e40b56f09f9dd22398853b13c52924aeb90f4919c723b7f0afb603de6ae98114ce1bda8b7e77954c9e690b02245617040ccb6821bf7dd55d2039d417f20b4eedb62d738ecafb4db07e264b73760066aa4c3adc342db3dcaf669d5a8eabb58ef3c83cd7894124cd16d64a63971f8a405f4db88bc089eb747611d2d1ca03f5170a8b617b81f81050ea6a45a20c79f331c0f9006b240ff055ec03d748fe44ee3a81603e38defb130a9ab0d60306b7f5a8782b36d74356c4f1e42fdbcfb367664d42481602b1fd42eb5582e213495080306c1910c1df6d32168a6fe694ead6602c33121ed34defcf4cd94347dae995a52d5fbd031b02c7aec9245b09e458250558b1e240b060a3e4731f3f0850183557d96f5ced26ceb3797d82f836e40893d86dd55e90e375fe39e0943e6991b92377c276e83d36cb7eb9042992536f43302516e74602901bf0c16a5a501e5d90ea53eb3b21ee9f9e93c5d6ec6383ec174eaca569fe2b1d216d169035ed3ea0dc4bb59ef967fb1cb200fd09fff5cac9f45bcdd6c8d39099fb7b8111d4a4007854d1b45e3c41b3b4eca661c49e517dd13f392feb4512b29bc1df0dc250f07feef190ab8c0c3824a67ebb5a78c562962493e3c48a40be441a2a3ec17b7d4d8f08fea55627773be1da0b9e34c8712d5a35d1765a58cd259656c05befbf992346f623b66bb326f00be1b466fe148cf8f3a6978dea56ff6d6d7ff5ddd7c57be8389b3132ccc6cfe3f109b590316fda33b43fcba26c0658a892b5a92a813949d25e03b260527712e822912fac50aa42508edfd83a707724012db8f7013608c6030a10082aa61354a4134316c28aa36c2ab361ef33932a2d0e9540e5c1b600c3a1cd07acb9205dcd234a59fbeb95244ac2a9e1484b29f6ec6a73d0f633ad79c718fe19ef6f7a560c974077374dc7390a969eee2facb4b70570007d6a9d351adb56dc775a40d88713fe72590d596c9f4c9b72d7754d5151b872bde656c6a24142e75344fd7c7f1aea87284fa562a717f64ba04cd3fa5ea25388bac8f2df2bdd5cbd7506214ac3b2a56804f53e7d4103e0b3382cdf06cb2276f409b0533626d3488f09ab09029029b53a27c5e42a4de7dd1ff5e299fa1878fed0914458707b28cf264a05f0948a9d420f47f5b85a40dc0ff220e10681129362006483d06ff8b01477102e71458e90f59de2f05e284afd530f8b6a0f1af60e40b376d0b86267c100e3603acd14753fae27e23a6f49013aa891b46a3234598622a7b139d5f8b3f29c9c37412e60a5039b102c0e0c157e15b35c15a08dbfbd82b9a9fa60ed06b7307cc969e1acdb921cddbc3db63a222393f045cca2a84a8e36febf9b35a398b51d340f31611c888e9e765ecee18a1e853434b07653aeaed55e40fa456068ea8b49060c58e54e5543ecac54e8af74dcb33d3976215765d0df36839c31d8ad4d377d3c13e2ffad2329f4ef56103b0570004ad665a5f05063b6f321997d8366ee7a15357785a2c83d832cad27b02ef0554967144e87a21e7de1dad60015b2634335fdf05eaade6d54e4c1abcc61f822a200d912192bf313c08b0ade96881aa1ede7a0d9d8a2c211fada0f5047cd44a3a4f6299f38ff4a67065e814d0cd64ecdbf359811f313656fac8d099852da807912ecd0838fd880d1560e7472b08b1ac00e73f6b7bab80f3b77b03a39b0b1a23c25e1023241f74e8893341940c4d2eff73eab6e9c90b708ea9f5af34c3b21f7a01a7230c2e4bb7d52aed1bfae71b2a483a79c15927293e4749cf2df92fc093176016c34d195ea8c9cda6b48d5896dd0b1415b80be3ee74bf1760fe9da5dc7b5f3df394b2d46a2396660e64ca1564706fdf352212090c6440d6884c5bf82a0184378e564d997dd46ce6beca59e2ab58c7ff3536ed0d7ec980ecd034d5d608da6470ceb1f3210381214b853bd5ae82d6eec4693faf7f6ba543d5347f8d253fc05ed01c8a36347c8a4a429e5227af9ac29b5d174138aa3f549df6d481199cbe9d6d44271fddec54f0ba9212aaec097069b6c3a6e4242319dc2c0e68af46415ce59dd77820dda8d309d25229fdf8d621fc5c9a8855bf77dd9279e1393c9d0e91a0180ce14cb2ac55263d7be4a7ce25673a614d2af4216e820ad64245f18324296ce249b624f7a7b0135559044fcf11acaca15e8d22c216b76f1c25a57dc5e2155b8e3daf54bc9500a796e1b1d7874501526e18ac3379cefd73cb0042489b930e4abfad460006feb435e1c7a60e0ab2e31746d82a8625a4fbf3509370657c243757f0ccd75dea4ad70f0bedd9dc29a103fa04bdf98d8bde41734db5233f4158c0b13d19a6ee733430c1794f84d3ca494b3334350af3d0520d695a5b6b480fc91ebbcb772e3d8accbf95caee91fe43d9ce0ca5f2c6ad101bfa6b77e74e88442ba533f4091ac1ad3d90ade0f34f7f5f16a3d6a4f850a21f285d93e2a8a6291fb5be4fb3f5c81c5718e5ba1f0dd3410fcd5099658f972fd0507b0545a15b32fb424c8d80264971775916d0aa5bda2d1a5280fad9b136307ffe31a154f99fad9bbbb54826c18387f863fcc2fb0e750efa63b692d4c5b5d52d8042249cd1b58fdaecfa80bf9aca9ce369f0c3053a0c7e6421967cbaa9319260f5196d0e7de4a95773474dab2e34606ea33343732c4a2e1680a5092ed2d058b5a84049f4183c712f23b9c1314419496d5fcecf33acc208efae126661d1a6f9841543705d6c529cee12f241cb629edb46512cd1da837000b9a3eb2b85dd9a1210da140db2181331b46edb48ccdcebf2abafcf416a6ad367ad59c1dd77cfd79717d3bbcc76890b1d2c6c6dbc746a15a1b07cefd007df485b78a8379158153329824d2f46fb44e9f181e4f7eea45551f84d3f5646c368ab781441a760436c7b289cd66c9ad0c70514f1a83710ee6313abb3cea2243741b2dbb29e58ee2e76d97fa5a5d7569eff6566dc240e675a1aea46811ff8f6a8bfb1eff7a8d7db994f0007e3daeb97434dfabf2620453936e7724178d05f2cad4ef92207b31dbab866ddcd3698c067d19f1e19ae8cd62b8bf0583cea436b2f01b4889594ab56b8deb9e4941de64ed534c03621e9a5986a10c6cbb5b3ed2ee6958b50ca21d2a9d8ff34cb439cb8f044805f9af98bf9e3a585e970f3f63e05c45a81025ecb06173932ab8283ecf090ec0a7da51eab7fa34dc856dd4851c2b963ab94387da396fa569964448c7229132c403a5d1a472e6ea64b479bed90aee8a1f01ccd4e84444508133406c3c8133e59d80b2ed17872528a915b0f2f5d1d474504db6c3fada3c2dff9b4888e861e05433be1a596cff975bdda27e35feeb05c0d126ea17a2a817a89c7a6141f25cd6bc3e1a4d69d6e85d19fc680c81a38569f9b3b73bddd200796de29163b1410767030185d7220a9d983e600b6f9743902a0e82a12f3f3cd202639e6f4e1ca2ed1c6c911fa6375997ea1b7c3095c0344554ad1c1856ab05719ab31149ccf02903a4f307236533500fe4bcc6ed1f7d214e7dc55f5203cd797be0086ac0ec52897e130374e3e95f150932821397d9b33ffa88c14cba14a05f858bc720421ded0b9a9e2ea2b9a1651accd584927b3446d2597d9d8a15ff89f0aaf8d92940298510f67e23a554a90b4fc2557e54d40888a19191a5fc1842c83961519928ad0aa78544ce320be04c0d85381c4474079cb25ce8582db1e8641c5245468d8e9f5dc228c5505a733e62fe38c8d452a3c4a9170029ea1b330fe3a8a9f8ef1af63c337c824f350362a921b6506607acb2a1b5b26324ee902e64314caa45f3062895a0c6e20a3699da1f1a9445e9743bf4de04dbe060469ca413cfe6300c22117fc6308be7c142833a9fd0124585d6f694fbdaed1d6c7aef5219445e5d01c618a55d2b6286be5ff575faa1b3440fe895f08825d488d1e55722fff0324c95dced0742b9568a88d30abe72df6d88c90632f9019ee198355439eb924e3ccfce2b0097a763461d2940062259a21677ebe475778909d25b7b35617d574b3f56859bedc9d7f2a60e33db4830ef299f8e6344b45b1d9d5f1d6b01092b7c6ec0aa283e04a229f0c924c108f6af39cc06692b3d34998fd56084adc06c380a2f34e095c859380af8e3164a4c807d0706f6d9cb23b77ffc102aa0abbda9e444b65aae5f29fb376899039b26c33c9b1f2d0d1a633da13a84a880e90c89ecac063f74e51e613fac99059375a3b4eded1740b02b8bfa9ed6941be0cbadfca50c6e0d99ab1051dcf403a0598ccea88ae2794ef67b457fb48a6fa89660441e00aa1505c7efd2ff80df18aae74fdb2377b7cc87774549e5da6a0b7097fe9914a31786f7ecf649c859058b4f9a7863d7161afe3d3b6394ef26d4b0f6c42a9c77b7363ef5750f8ddb92e3f4fc41f05d0c7a81591a1761cb72ad728f23d474bf44bfe942c43c785860261dcd453c2f26048b13f42ef2e4dd6232336128b983e74f90227a85a8e652f8840bdfd4e792e477e78ea0aac726cb7a91bad58c8f9477884c4c7dec9396f1982930db3dc1e8f2d50338edb077eab93ca31616957af57a547e6a56c23f4ed4449fdfc6e901873bf33ed17f3136fd51a13ff953749d1884545afc8b7cb142565b9506e245184bae1ef0a07b147348baa8b02f54a04cc137475d19ac00049ce257aa973e4f4e32171641218f152e765890695a3e9f1f4a05edc8f7e44f3941182bef20259e963153852975c131a751d3af1cc0cadb8716521a1871d82509978b3a19dba4906af799b219fd08ad474fea0406e2b9a70eb99f5eb10eb9255d264c86a3a8406c9aa044c409bf3ba4f334094571b9328f28bdcfcb147982b99499a7acbbd687e63c2d5f98bc61bc948375bcc00761c775728266360533aa15a7e9cfb68638b7c67cc312546db731441f4143bb0acb57db8d84aa113813c0f9061e8d78b852658ecee864d8c84ed06604cdc54d322afb80c308982837edaa98e6af499702842d55b30d815c0e5a03b0d77574189e444a3b7795167062c9c507d586868b41daaf41ece6a5bb2fc1bf9a66d3a21911c2d3764413ef12b75fb1e16a5f057eaa097fa1f6dfeb87f9e1431ab1b011f1da09bd85d0dc544efa354172e42ddd193ef53e8581ccd59607808414a74993406f65619e73ad17e643ad89779cedb54ccea1e9be596b20ee73c1728bc86bb3610b60868ee311177d5237559544455cec268011de4729d331ca02d39c949ebbf98a916ee931dd50d855f56874592736d862be473ee746bd15073a205591a9a684fc3a6ee823d856905800f485690fa3db7268e3480afde1802203523b6f98e5ae3d701a8ef1d3695c357ba7e4486cb6ceafbd6f30090e19697a3a45faa5f4f6a749c8650d53a17aec20e0aeb99279a195de641db3add20fc269bc6704866d11e2c390f006267da30c68e92c3ad1167c8174a47e20b97dca223058a434068059486054f2d598e9b784a9199d749a007f0542377966e3a0aa2f15fd1aa55274bb557ffd5465d27353ab084c587bbf8380eb4305b464269c09729e3f183a55fb945ac2fc752ad3d3272e787871803f51077545c335eea836113c04237a8823dae686ad5dc00b25a893184ea182b4a34b25d1248ff39af3a7dc05f625d0f6f13c5ffd1b50d3ece36bb849abfe4b02660c24c113c85746c21fb572b8f35f5ec28400ab7263ad36946abd93463e63986c23b8e66954a32aabba2a9dc7cd64a66b6da270e2a6a4bbdf9c05fee0a5222e0460f3af0c2279d2d8e7ccca5592a2de4cee24e9f829e5663e7c8071c2b81ebe43119e3cc183e7706e4a34a89da94d188dab4f343c16966a136e135193561bd92cebc24cf842a341c26ed3d3f1c9096240013baf2eba5293703c30d8b8ae8a48fd5cd1a2e38bc079317590d9e7f91408ba4e4bc7fa246a4509746ceb92c17cd46a334878200ef59d002b060325980d72f50e4d8c5dd0cd7d20665b6a6621de1f860ad1da66945058fd4af003fef6460c3ad853592c9b4718aae85e1e37a6da1270be8422f4806cda9fd6cf952a6e3e0ed7729293a43ee2a5c0fae6979024a30aaf25264d63addf98a946acea00437148a3cdca987ea174e991e399bc44dccc42db96ca1250194409feb36d1e62ab073f90770fd12a4b404529b9c9c62e3ae93166efb58ef996a394e513b000c0b1e9e84573b7c6493127bba0901b7a0a4a1a0f22d69e5db30f649b5e71ed9a28a5d2bdf085ef966dd588d1f7448d5d3c14d0cf9514a4cd0abcc348f5e68391be931fde05ed2dd905d0c3b66ad8dc6449a8621bb8c30fc86abc242807036f92ad3f151a2041654650dbfed54d3f885ffc4457c670de4f601995d6d6c8294285199a9705474af554529a7cfa9880695f9e7b7ed28ee32999be085443f1f22a757e2228f1ebea1d889ad2f4c3f619d362ae34c7e138d73654ebbb0244b606ee0f033b29892e887486c5acf0b354fafd6d91942a58ab93a421ac2dd6a12af4786fe9551a34b86da1f22ac942e7ae6dcedcc109388d03b4b6f8124686c04a1b4ff8775ae87988e5ce156bedf88c8414648be2a29123a612eb0367ad4df40455110eac384d04057b0a27cfdf30e9bc6328e5624d185c2abbc018efd732b069f00779291a4f68963a8d0c048b697352cc01049d1b16be2e242987199ae9039de480a2f12939ea69afd8cc247b42ec63e42bc5c0d4acad54d34c842a11c972072f1317594e72d3434d0efe2357b0242d6d92e0e5abe5ff29a337e4d8141ce8c992424392f909fca97fae8d5fc92644ef2eb6d0f048502faceed615e300e429b0e29475929dc458bac5a66863f1f0237dfa460ed2c2b5d8f30e220b8b174993652acb67253b20623b8d1ac1d4b173a6680542008c2c050081cb4a186f24725968eaa82f1f61a9ab06948d07d97c14842dadeddf6de5bca94a40cc905db052306f9737e66055cf3015c2479103332c0cc5001469b007e6e4072b432fb1263f560c49e5e44b1a22442458b2916fdd752c5e40a2670cd4f29a5af718791d268330fec5bfb52704bd35d4f9317375c1ec949d816c9486020825f0c812f9017b6a10c5ce8e9f104c3e8018f543074588caa5902835778e097a6bb1d5090c02f78b0a8e98e474c0648b04bd31d4f12f907eb6c8335dde5b8418f9aee7ae8d03be3d82fffff5ff37ff59ecdfed5caffffff57ef7df5deffffffd7284b9cd08eebe1a8c0f112be97f0bdfcffff57effdffff57183be7131b312effffd5b443fcffff7f752fffff5ff37fb5f1f2e18fcbffffd7fc5f675efeffffab0ff1ffffffd5b9fcffff7ff5ef7d7dafffffff5f3d02f5bdbc2e7c9d0960e811e0a855985c3c7a2882c90b8f3cc20617785e3efc6d396d0c433ea717b0a7c81790e7c5c6da40e9745e6e52c0f0dcbcb0cceb6483cc4276a13100901d181aa8ff2e1fcf85261c3e5ee5017b57b7ca6303766dc0a03eddad5e6633eb33c287c25181244b121c287b4960bb7c603f80a24fa3151c70f4c9cb0c19a3292ccc80ba1502f96eb1716d61fd218622f4b579a1f9abc5791e61392470fdd9b5df5a5f56bb8c9fc72773ad1a18f30e69f80f929e6b59b39756665f3b14237d1d0eb4f050b35f9e31420aedcb3754703d918176b30a2d28b248d16cfe644afeca94b89529a9996615beae606c31b9a038169ad8670c7065cac1d39745405680d17578947c9869ae525427ac676d538a73a51462fcb39cc193d2a82ea03a945aa2efb33eb6487f198ce5ecb31a9f55f049f7584d7a5b9eef855f2d6dedddf6321760b679567545ca1e85aac91d917e39c7b1649fa6797c2bb1f6e783a4257df6fde9369bedc311b4e158cb1a2195616b29cac4bcffc4f92ffcce9a00317ff99ba0343e7ff99da01fc6c502860ef386a1f79ffacbae18486fc2cc8a3d9d85598c51ca41985b2c74f8b1308f2c328c3d065914adf85e7e0df37f08598b65085e5e097475047392d297984568f1810d63dade742086355717610d897de65e33ecb3b154ae6c57381d5a3d18f7297c2cfc1b9e53fbdff56631a5517309312fa12af11030a5030ae546caeaf3ee803ecdf80f438cad50406f3efb7c8e21fdb9ca52a0d1813d6f1ba47e1ad556b6615f915414569a0231e64a634d81188344cb0906c10122780efd6aca988d85317a4551146bb2de168706c18f92637d28630af86f5e27fc9bfa65fa378f06e860e3a0f400347f372280b5cb7608581914431f16b41d2ae41d37c0cc927db21d21d01b022fed57466907557ba6b45a46a06acf31179f6d4c05fa7424c50ea7a82c2f5042bb61f3cddff77ddff7e59e19b44fa6020e235a748c491204498e2bb47befbdf7e69e19b42bdb61654a5ce6e265119869cf2a8038dc4b216c2e7dfcb2baf74049bff9af18c48007bc61b3cf6f8365deec0bb8f9332c8394460d7a53f3786f094c961903cbb4e457adf518cdd6a97bbe4d29b8334bfcff0dfaffff9452ea84d4f6ffff711892345aa39021e6424c69a4b9d1eefe2fa33816c7d7fa63eab38cd2980dafe9771efdb54a6aaaaa07c573826a62525a62d2af43f2d9d3ec79cc6910dc3f8795c8679452d9b53424255dd81cca50b01ac401c62a91cffe5ee94d7343a208e8e0a6056bbc314d37ed853200e8728ecba88c4ff6590c41a032be56d3635fafdb25211439fd34273de327348de5a04abb975529a34b53285060d1b09ab18e80372ccf96b4e71a28434eda758551d1387587d6f8cd670a0b71fdecf7dd3a53019b9af7e0abafe3c4f1e003fb27d89861c5136c50de0c07880002ed39ed9d6063c607335c42d8dddddd4f16171790f1d6e8ffffff7fb5f9f0c256716b75556675d53ac9a8f37abcf8be9a9d81a7699e59e2ea6e7ccd0b5fff93583f406e409ee7ff9ff5a3e8ffdfa7e869acd5c6092570823cc50982c5466980fadaa88c19262a193e0092f03baf06065cf3b5bdd76f6d1c5757a118f262851febe40b0c24d83505420c11a39820ec6b0a84182c9ad414083152fcc7071d6f8a37d2a7b4529a6bf8fe9f6485ea89177c16a789d0dbe674dab75592d1db2eadc0ee90c2f2782bffa95ad53fe9ea5250f4fe23edfb75da061c4c9e78b0c1c013e5c911b4fd660ebd9fa77dbfd307fe052a21cff3404ea4dd6a7fcde4f5a4b657d57026348aae4efb22edd988561994032a322aef46d1bb3e5131e2ae49daeb927626ed3abf60b4e70dd004dc5d71da6b7ad72d4b3ebd2157454b9668f749df252778be80eb509abb9b25b8677c732544e5606514054918249a4fa13de378a0020bde5769c6865dcc80b75582b7b5d235c77235c784e06d79bae6991e785b9cae79c6076f7ba46b1e75e0fda7ae79cc81b7ade99a5f3cf0feded75c5ef91e787042ff517833aea2efcd36b37b73cde5414a11fd5990e82f6bdc1922302b4bcd312c78fb895bb89ab1185c76c1bb5ae97a73a10b08bcc4c30f4ffa66eb392df4bdb97b54b342cd3543b8c511b8851157697fa0efcdd8733ae8016889f1752ced5eaaf755d251daf41ca5d70dbf84f04b09af00835744f98ca9af4e6ea4369b0510f37caa2cb0c08d4dee03d7a4d3fd504d87c3d99148e6f699e165492949f1be2225291e0f642901c3610b302cb29ee0b058e48284e3e802aee975c823a526728512d764d50a5fc052f82459e4849aac7e5f9cc624b9058f2cc07bdced8034cdaf9ae76c162c903c67b3c8b130c2d57769cb42f71a33529b3df2f457b7d6d2d21d0cfe78aaecde7c4665f8bdb21b2ad0e8dff8c8f32123cd12bc432870cd940c857718e6077e5d2171385850f5029248612ec0e80b8ff63112888b0a7cb6d9fe48ddbdd6fa4febc7286d7451ea02236c371a9d42082872342a1a8d1928b903dea1866db939b1b14042f31755008f094a398569c19f8e926adc7103eb0ab803caeb4829143669f5f4007fc8ece80202b951224fd4c723468401dcb1c50ee9087b8a589e245c475a345b3af3ffffefee4ee9ffd731310024e1eb82eaeefe5f799274cdb3b0ea9d7610bcc9e81d8cae80d0ada5ff45fa2da551b30dffc7f0f08cae7b1bff1fc794bf7fad4e8c95f736366c8ab07d4100dd5d0feb43cf87393886d1d7fd587badb5bb1d25dafe8e5118c4486df667ea7aa4a91975a5a9f905a9a7a9191544db1ba551a51cf73752e06d6f2ec5d2ad56abd572d070be3aa5d769f970e5879bf93a6ed69a2eef756abe4f6fc2df3cd9deebb0aaa408b3937a9d7ad66ab42a6696bd2d57f62a856b36e175ece992b5649b9eba5652b66b4dd7517673b826abd493babd8e0cef47548d56b3c5555acd9fedcb7d4a36774f6b525962d23ed9be365def6ddb9bae17f7b27d6b2edbd6cabc58468a3f7b6db536e32adada232cdad640b3d6caf6e74072aab184134470112440420e2557ec802bd5b1b21906ae03de333eb8418093a602014e9acdbecb51a4e9aee6a40ba0e9ae96d3b0cfef544b97c06c031b23a114c173603f5fc7e8b57c80385af57a682f4768f1416d8cfe72049a90765033cce7cc01e5682f74fa2b4720ff4b4d83e508b8ea51243d666bb5267a68b11c0186aeb290caa899bc695639c258a76a7aac1946184d9623b87495d5b104bf5b5adbb223ab258a9fab6cb5beb54fa05d9aca7606f0f56ae963b055eb3731529b2d56023c1979522104fddccccfe7dedeb0814ee74267af9001e070b118ee5769a046ead1cced03b1dcf078230f30d432e319e1f508c500605c3918ab885487ea8c998eb1d9dfc09071c4ef3b0108ca58238933bbc9310df2cbf657d1fe5234eb346726cbcb48a98ed8c24694c19ee8589659a631c24c754a593dcdd14527bff13f455efddce7744b560ed1728189f58cf45a24e1c2620c8798fc1c359f0895f17242697cee739f7b2354a79ee1d3a0ff06ed673d574ce1970c76b5ca1739b25ee22b045fdf7dd9d7bfbceab42e0ed0552821d3227b9209211342b645b645f644294d61a4fec28229e9334a1ff00f0800a5b556233228ec25fd7affff9621546dcb10be52ffd6be61cb105e56a98ccff7af577a652e42b8d19f6f62147cd1f8acca348eaf5e34d25ceb7fad31190f531a96caf02355535bf34854b33bae44489447c23f8de528f0ff94470295f19656ff166d829f661753f8b30c08fc9986b4b766161550b8062890e0ca3676c2dfb8e732569b647478d3261d6a3a7a89e5abffe3501949f8c90b6880d2b059bb0015376c420951388791da904f25a4406530c198a65aac33cd1236c81399f33256594b692c043eb3945a7abfeffba815c1534a22c1b059e821a635d9719ea84310e0e9c9d4629e54c64cd0db1b36d0e95c2cb157c800703122333e3e10cb0dcf09ded803865a66bc303c5b2806c06c8109339baa14aae35667ad52b59c7efecd4c1138b39b1cd3e085fcc879667231413f6d40f385cc20cc4a75aa496f3c83995533f6d482577ba03a5ef5ae261097e7a958fc0a882f9af93906972d99d9335d6062bd303d130913046966fcc4ee04367330c1d34f0f4375aab97d0aedcf43bbb984a98286a60b17211b8600edf009c39027b25842442126086dada2180e11865a44d648822139b2c450e405b1d6da0fb439a01dc398d39b9b4596be1f735bf51bf98de7cf8927a621837c5a89308c851487300ca956b8500300e3053e2f7052221bf9584de99d71a046773abbdfb555edf5575043ad89ba8ff9afa0e262375bedd66eedd630a03fd7cfca5835d7b52499f4372c2128a678634c6520094127466ab3c7ea63a533ab025df3df27595b70cd7e5f34406a6bfd16683f3bda777badadb1d6d532c698e09a3f3f0d0db85afbffaf2209f55cb51197dd50e16d2fa864d0f4420a0613d996c10a30a6c85430d82383293a32f081599aee7658f978d184c5a8ccc3e0957cc32f28353cdc3a0283062cf3a3c90ba79a23484d773ebd9f5145979c19a203b860e241ac88016658284038ee08e00574335e99d999a8586c4966110123b6e58557ea5e16a1627401594334b6a062bd74a82b92c035bbcbbc66c8284cff414d773e5b6a171bce982098690fdc34c52234254b6f879d245f68429a666c358e1ca0fca8cdc043844412b42fd797fd3565572909bcaf92670f72e86532a3c36532f364c6680669262947ef99a5ab3473c369cf3339ed59e6c933dd323ded5b466acb9c54cc0bb51c74508810c3046dcb58f9d07ba6a63def19db0cd00d92c9728364b4c834c9f0b4cb3869bf2ddd98b467ba658eb4cbe8b4631925edf9754ed1fb65a57dcbd4b68c8dc5142754744f48edae685be6268393c9c9181941650413af9e94077abfa6a45e4e522f28ab9769f56ab2b279a6fb75d3be5fb8fdca25b96281d2d58e1c9a3ca1edd75190de2f9df6fd527a2de9f0daa1c38b87d5ab6600bd5f3ce69459c5c34f3cdce3e59ad01b1f69df188cde58a92908264ca298212a88b63190deb849fbc63cec645bc02fd816b0107cc3b802e88d8b2c0ef3581c063a3a8fac3cbb943cd3ed32b56f57d376f15600c40d5047dc74539aa06dd7d3cf76e9ed9aa27231a172dde03a72e9b46f57d2e92afad348bbcb666baedb76f13cd3ddea69257ab7a4b48381cad1c4bcb2048eb65b670fbd5b56daf376e9ede2a1d2a242a54acb6c35dde8dddaf1925a4bad23eda59467bacb53fb2ead76abd6b473950416a6225298a0ed96cd47efd60d57a43def56ae65e4a434c2099872a7bde4954f654ffb2ea396caa5255df32e99b4e7b23683dea54dfb2e77f9822251a07c9e8c1802036d97399bdea54e07a4ed32e905295e98828c9aaad24e5e7d8ee43df5b4671257b5c9a34dea4c98e2e4850627b6082d41dba4120fbd49537bde6413b97b1fb285f7217f5c913c2490f64d123dd038f54063d5a8a47d8f2692de639305a05822e68724228240a1ed91d7a3f7d8d361d1f61845352aa11a691889c6a2d148fb88748eb573b43545f9663d69cf74b37a364ad49468ea892248146db3a476e8cd3aa95857595851b2b0a4b0905849ac25ed9bc5e44f2c1c2b57c59e67ba4529ed5b3cb1e82d5a35c1638715232493882ab4cdaaf1e8cdb269dfac1b8b4807b18b0ee2102293b8139db48b50a6a8b445b326deb67824221579a24898102a8c9ad21e561d854d4721efe80977b443537bc8e44161900785425557218ff610c8c19e83527e3b63d01b54daa039c587eec70f51568ac6d036d8b4e37983504f80459e008d8040201158a41d34b2faacacc09a950f7a7fbc29bdbfa72f082938d49084071145d0f6d7d3b1bff3a5f75765c4a78311df93cfe843fa92b47f4b55aa77cfafe6fbbb7d44566e966ae56ab94b97e9eeb45727bff15d9d92f6b5b56845a86a11266c4f4abb9d3ab24e47164ac99a4d4abe03a3a6bb1698d0309fe5b2ac41cb5204b62c6b88a04ea1cbf20195d6b0321a1807489a1618266aba6b610c4959ac12bc988c31aaaa0c92fa6272fa4f9f68cfbf146619ab08ac8cd25c6b942495e1f9631bc074b78295de567b94ae347b2e414d772c74d1b0abe98e052434cc67e010638c263059b1062dc420024c0c2248c0a004172a304c00710194133190c0aee9eef644c6b73096da889aee6e51ec8c9a7ebb1e56bb1e411a10d9eb14540b836c3e2fa3f4d220c4e8b0790abfddcdca8de9ad3e77f2284da7aa9c8783e4494b384c383b4db3e76a95eb9870d5731e07ea3991179d9e2332b94ebddd7637a71bd42daa4e699aab59cd5a548d90cc249a35e0555715c804cff9325476a24c1f68ba41b681d5786ef45cabf3fccd5ea524265329090443d16429259199927924499224cbd69faeffa8977dbddeebeb8e4446b96d714418bf888cb46799dc0cce125d511463db3631c5a0c2db9e33344c2a2c6f4593f6cc82096661b0f70abb35f4d05e7bef7bbda30a52c58a7d79b467163ef33284aa6fac85adc2c06280c17bc560455983454b3b0fea29aae7525e61b1163af2cb102a5963fb1ad19a5b53f35e6bc2960b08682a73e95a6badb5c6ae18829f8d41e522779e589fe7379e67d5799ee779bf3083300cc33034dd64999f189e67d5799ee7699a4ca6699a66288e250c18e4c764b4288aa228428537b024ffac3acf93759ea2693299a6695eb274bd6666f469b2582c168b6542886121f967d5499e7f9ea7c96492e69ba639f67a51bd5eafd78b412543b30286410c1c28ed34fa4a0ccff33c4f93c9344df3b3377bbbdd7abda85eafd7ebe974493a9d4ea76341c6a0c2300c6a5e640d70707070706ece841eed9db477bbdd6e37f3669aa6699eb556190a568721c6ee39d273cef359ce3018c6ce7be51de2bcf1e7fc56c77add1cbe39d7cdb56eae64f54ecf11abf226f3a6a9b1d940343d277459b365cdd29abfc106a19bf66689cabcc9cf9ba6c6668307ebd9f39c2faaec7d8fd5137be147ea6a4ee73963924ea7d3659ee7f0b8ce254951b6696864bba6c6c66603bdef06fada44c1febd7821b64acddb00500c3d3e3ce0809aa8ed74ece0e3cbb36c93f3d137c46b4872753b3e59ac98a29f21b65ac3ced10f86611a8cc33ccb3639db320eebcb832008eee459b6c95906a1223cc2d80e7011c24d5124498ec9a783508f0c35e9d6e30a21f9c83986611a8c876cf7b6c4866f621b02002f8a143d2ef86043936fa932016689861e8c4071ca4108870e2152404bbc00b1c0080e3c6c80f4805887fe743b4856b019e73ee13c9c5a49d28412ef05636ac0c189cc288b12ab188a107345a92706223622df0d2bd4effb6c391b4adc30c4d5144142a67c4d7ea4e0916342f41d01f1c88179966d729e659b9cf32cdbe41d84a48608d04304470b2f60f181942b8244e94bfa8182c7901a64a4bf9d247f9d0841013911fb030f2a27461ca181059f0d403cb61c4f0cbfc4866f326408c4f964618103a44748ccd010af21c9d5ed0cdd98080dd9a9281c710aa104084989f7e97c7c2192a3bd16dc6e48a85ce9314403e8a3c7100e1b8240e1e1618673c7cf0cb9a00f7bf420c6071262aa7cc8a2e42709fbb3842d140a73f5234a101c6c481ed5a30267e783a2f8b83c42be8850949f6f0886611a4cf1ce47901d74542d4a7638979092434fee4ba2848f2161c66171b14877c728e8c88e960fc98fc923a4ee2046688c113e74099a72042817548f8080785c2041446e9e659b9cc19f230ff265e7082d59b0984a9f91a01a88ee67cbcd18f2a18a101a78d082830c40490f644568849a15241e588e5c1d2932c48194f0b06211c1304c8371d014a9dbf9efe60314a3fc902dcfb24d7e7a299f2142766018a6c118480fce0fc5d22076d031e413a9288633f817246407b56221e833faf1b15373399cb73351b4e1503266a9881f9618d97c10d9050dd9328ee5428b807a3f310c712236518c91e4cb55940b3f00d1f40d19c2f9f8dd09f180867c1e186ac14ed0962748a0bedc0f53ce46cbd1ee268390cdd3ad9ef3c05614c04c024b699021996d6860184cc1af77a81dd378d680e6808002984910ba8d76cf7b56834536600274e6d9495256ab602173154ac3cd586d1a6392758530a49424b3286656256bad51c95dcf9118093e9b00e560042ac3f327012ac3630fa01acca0acc515d3da806993d2709c19396813095278af3c9406a5203e81de80322aa5537eaefadea84e2b5f5d988aa8842c992d6aa988488010001401d316000020100a89c401491044811a95f20314800b5d844c625632980683c16018864114454118c230100208008600828c6312b50a02c3349451836eed19893a2c0be67858668b6b0c5f1594df6f88ff8c45d493c890f8557907fb9705a4ded3a59ff0b6f9977825033c90d419d3720d800860b8cc7a31a1484e1d1847ea5fb7a74a686e280d1b7ffba561248a6f1eb11a6bfc5551808b72dce55e18b63f55c089f4baaa3c78fa8859b44ef946f9bd34223524c05d6c0b72d5c1a11e5a06f848b9523823663d346c848581e9b4ddfb517a07937f3701cccc666dd260a06a8bddec4e19984620a3ebdf612df6a828020b3adc2f55694dda412843316c413c2ced5adcb7b1e068610b159b03a4911b8daf1cf41fe3fdc1369e972471b849ac1754da72800939dfdd6efce3bc9f2fb25082bd3fe916a8a3759ba9b4e9c972df5c3e08a2632315704be1db82b1bb6da7323e9976a0e9c946503f88aa46c1adf709c0ef76994b010b8f2194b835c9e25662076990d21db1cdca5d707f1990236c9f41a7f9c1e6a58258733dfe9b8126e998a376802683fa206142187278dc7d725eca7745042e4f7d216814ff0754dab25853aabccbe8d335e54ffe8cb8f1bbfacfd1ae8cd0c530724b4c70b56c0d67d7a47425915f428b6e3de9381b6305e0bff575a3d8988bad53a0810b3b437e85d736ba11c9ef2ec2932dd0313fa51aabc7dfa2e66d88159a1f93befe55df7202d8acbb974847220a211d6605a65f2121d6e47c2c2a58ad09f630e66f349e2e3586deadf86c5fe51fc06128fd888ca856672e7a4f22cb4cbdabca13757b3317bd7f3540cc526fda71cbf726ada207538749bded6bfddcdb48afeff3d173698dac96b2606ab5b03e3310d7067d35d2973f63e7e3ca1d208e0456718e089a876f2e9bd93cc98a69f8c10e0a07455ddb1fb39573cf853a074f08989b1ba3a824c8a63fbf7dd2b3a393ddaad47b10d9ebbc7e8613e169d515ac5bc7102e1dd80d3c959e13e782962a43980d1a0c6db6f12fb4cccf11dabf9252096f00b0e8fdfaf9fbb26e36f3a5f8bbb416e01b6044d2fbeb7993de2d998275ac5634e30646480223d8d3d234d66be97e1a4fdc2647070367cd1b7af9b8d42e66db4ef227648bfecc33ed8e97bf84109f6d2d951bd1a862e3d77b77d8487280240557e417b3cf7de75ebc3285b73916a81c59a09c645017f91c974601cadd72d0fd33ca0adaf1d0404b660f1e2299f7f8d83fccfa5e7279284e39a77ea93ed65bbf453137284f5039d425949f55372a8853d2344e6ac2368191411237360cd06fb5f82c82ae7ecfd0f30135c5daa5b046f18ee9a4e35dc4844622d179769e043b9564cb8cc86107fedcc269d50ce377869cf8692236606481b3e81dd37fe5cbea577dcc04478a4120db2796e6bda1cd71b47a84bdb677b1985e6695950d4ae0d8551afe1a6c782f93637d8f48c6b5faabf9837561f25a6d6453ecdf9b522cef3fbc309334861a74bbcac24406322903840a71b25e90f6f41d65f4c92db3f98e8cb26cc73efd29c1074dbaa26351396d8dd3ef6d15d511c18e2fa6acb64fb3464378989e7360baab0e05cb725c5a8aa884989f9b77c46bccb696d68f133930982f389db3e0a398af22a591867d57b2714d22366e362c2b480247b7573139e8a3b6f1de8b7d92379ea58f2df234468190b5fed1ebbd5903cccf8813687a45be4e77f8a8d718917aec3563d7ffacffad61e35a5b3d4a74806520eb74f944a86ad73bc99c14a21a816aec6cf32119c1223ca6b6695c1a8003d6140653d290b4a0a2745392309b3024d856f9e39b91d0aa7818daaa37ab0e0d0efddbefc93916ef8f0f56d760430e8908405a84018aabb47d9cb6b6314a52db766ddcc2ff868e554d4415f5a8eb81eed2693d650f677784eaaec5120297e7da09ec50180cfb0769fde758585a93a8d91b749316b75b0fe111fb07eb7dd11673fe20932b5fdad80f0323253c0d9b3949bb7f4b3854b9c90fc4a5299da5de610c7a5817d0658c703319134648a6e984d6602a22f9e85c67cc0f8dfd70efc32a32f166d9931d1904372c9b4e59b3e9539f216e1fcd9f046a1dc7bb756d5b1f48343018f3dbce7fe81816189b9075860110640dc1a7ed4b7d62b0201e75484069fe5103b1d5a1986ac0c4d0bfc2b912397428b195a40aeabc763ea9796075430f8472d54a94c6e914302b1970930a049599f353c3cc234142c4934721d4931638268d81d31e1b8e1c0222bf23bcfd31890731a3d6e552512c479d20f2bd758870ff4fd54360075d0876b00e23219ff1ce5a17eff26fe1ff1a31afadfdb1890720b321c5c9f721854861d85f2a9a0b04934341e2ae0ddd0c2c51b558e777768e77675938f69acf9f3985a426641e2e6104f6aeeec06b23a0d488029290c8dee37b2e990703898edfdd18b07aa2f4540f91823d0f24ce08761266af9bc8194dae972b1402c3e49aeb9390f8b2e7e2261bea498c68b9ad4ca6fe44c1930edbb5c571a3657e7171b9ca4c106197dc3129971c8f511ea43755de583cf940b00ad2cb1185804a0ae4f8154ea9bc0d413552da3498fb3667889e00039bbeeeaae183673bd7e80b246917063a863877b0f0edfbc31e6cb864177a0df5470a16924d00454bc2d6f9306ac2e74c3f992df552bafaf274852d24d64ee48299dffda6fecdfaa3d71ffb69eb2d52e3a58c4b19a95b3884ae520ecdd66cb0ffdf14d7c1f0954c714daacf8924e6e9b05e54cade511208b8787b474a3fb7bf55b3e15b65c808d14fbb6a6a16bbc340aa0cd9006ec3648cfb4292e1883f0a365a28b5eec649b94294159e15c54e82d32622556d01bbd5748496aa8a69da9284bc531a9d8351ce9c9d3e139e3f03b39727c8f91bf9db71e9dfbc6e41484f6c7ea175ccfe5866baca90fdb8dd79e133629fe07bbc87d685cc882dc73e76f40705abab07a0f991c39fbe938907fb691f367737c0571daafbb420fde3aa1e7037b34ba0e72b8662ed310582f42d273fe9934ec0200454fce6b877037d9d069400caa7616963d0926974742ec9b712e644af78b9cd85a7414b408db8a2d13319ec84a8beff9619d583f169ed3efc42fb30d0b8db6a6a5cab12af6c8a071490e6a5578dafc694b2f45e46bbd5ab0321b3cee45ded1a7031a1b4cdbc40e5e28abe5bde571c2cb6b7d04a28541d4f9855a9a0da51863ea72f79cf658f71bd93e1632bd4d15fa20c336255cc0a0fda5c2e82a5f942bee49637f47db4ece5ae91d05be1e85e32e4acf4e061015a382a59105264b0e307468b4c73b04fc1d2a6b9352b1318ab29b729d6d33bab916bba856afe115deefd268b875d1370999f816b58d576929da87b9cab937e00b93908bb9cee3e1ced4db407ed2951f2c7a0bd5f810beec2dded5d32d08c3b153290a24bcd491f18f4dae0de3dc96b182e91c8ef95c878fd69ba3383fe6620120ac97d50f1a51974df1da7c08855cf2d702568a68192b9e44db79394e96941ede9e9d5db0233d92e8998d8f86bda81c8a2f7513a87858c21e8f45ec4701ecbbbb8609d6887fef61b767b3f06cd6e5f60858b6024aca375c5be28b7d30b5cdd5a8946f7f57d0b9154a4d55f1a39a534546bfdb71e4bad7ef75337a637d5e8d5c8aa006c5da21c77526f07a21c8c4da21dd2856eaf6088b51a66eb35e47dcd7e736e1e5420d263637faf60758d5354b01f58b2beae672ab88b82d35135dc7f49a9330ad7d68d5cb7ee3b0eb41b7b5e3203fc8d7d93133fde2ecf50fddbfa3b90da05f3ea817bab36bc619dc1df2422ba775037c1c01162ec5e0a304b024f81afc636ee565e54ae6cf476c06c5b67958279fa883b7b3aaa191ff6c7e648efbee047a07da19987fc0218bfd4b8fc096b4f30f2831ff7b60e9cac3b3cb1eae726eb94607737ad6a22a503cddb492f37c5b4ce468450d54ba638d5d96b0bb876265604b9aa56db0bac9a5a016ec6a987d4e13799519c80696468a2b988464a3d2c1adf1a6cf0d355dd5594a85a5d0116a3aec6a130e79efd8880ea8b3d10dc0d3728ae61d5acac778103fc3f3faa7afc6704d83cb33bb46c1b99b2312c3512adb87a8673feb6bb73f275c99c90dd6676d0ab50ca50167f75fb10a33880e21a72f26e67eee3699f77bb3fe0c7c4e59ed97abc5d9e1b32f670bf59ae38a3eff3ca130cd387a4a590be7d08a437d6f1c55bfe244f5e3904cd30cd665e1609751bc828ae1d194c27e01977ff911034f891247892acbdd8c1646fd0f5c3b252a8e55197cd5d62d1234dd7bb07f128974132a035b7be232a057e37cdc328bb0b42678dffd06caf39ce6e3cbd4497c523409022a18419b76e198ec05c141ec1327aac3f8cd42b160fcc3c49cffead9bca4ac6b68cbb8690740696826fad374ebc151d03fc002178ada4b2e9bdc2d007e9e5c253f65e253f36ab40ac4d0e55d348bde6cdcd7d5d4e2d7be34a94a4087ea6833866a4b452fab2dc93313ea1e6528077249dbb6ca58677daf25cb4bf41ddaceaba23d00f34f62e9f723efe9dca1bc1f181e2c75a2677f555d787d4228a275b3ff2e1a675f42b6f51aa24e207b8bd2199b06c1b8ebd99b925caa3ee2e3a2414af979e338e3ab92f06ba2e2fe01133800a3a05fdd211fc6da35b1cfdf80224eac36b8053febb6e911faa31832e18f4566e8d09f79e1b0e1a7a105f5b82cf7b38eef9cc749a2d03cd2244fb8199433677cb26887985996872fa37e551a4b9cbc06f578e3bce64d858945d1b47b8b7837e652c251d96f77bd9a0a5d837d3783349d4457c34c98b150dfa0dc47ca5d478cf6e073a3c434a71169fcae4dc4ac3d8afa55956e4782908676da3611e10a821c99a52cef53c025a9b66c22661c837208e542178151cbc008d064402cbb960592d40424bc96d88bd55c4415cbc7599840d641afa1c187cf96c2a6fb1b0e34ae5536eb5137b0baee31aafe4025c381c57313b183c235ca0d3daae5d9bf1f4befc4944ec0023c22d9f95bbf46dc974449aec58ae0ccae9b44f25b201a1436cd0b9e8a85c602d812a77c31b88df89f1a71fd3a51d865271971180b785c846e68f7c63560981f6223bd9b8b9cd8831a018c2dec1b8400c21b47ea7de2d19a034cd3de485605a43ac28b2835016cec8daaf5214bad23e37e0d1080ac29519bf2f5d0e317f30ddce5b42340cd29dbd622c941ac4d83c4ad2be64ee121275039c4955a3eda6aa1070aceeae8cf46877f02daf1c4975d03d997bc1d8120cb62e24e1220558344a7a9b90637f3f85f60164eec076af1f1afec1f1130a604085b183494b360d8fff1a6e47629eb10c4128eb4a79c3bf65f414744132a2c76a77f1fb7771b5f3c2a4153ed3bb7efe656b30f16d96924e5a8809c01d6e7d08e6a0631b90f5b05db0396a3e692bf73724bd6618d0bbfe746bcdc93af2e99c096a5f0ec421d232c4bd9571740cf4d0cc162e6463f42e6d03483ce9be436c05ededa23a7513708c93b68b148bf3a1e635ce7115f29676821dc0631a16c8c8ce84d4d7f852520f2a6862e770c67479235ffee4fba5c354e214cf59579cd32433fc56fc389494ac9ad718d9ca0db83cbc05e28e17292bc4dde4f5cf9bfd42e9d055e2aefb8b0a4a1cf5d183c351b2459ca3a06e0bf3f25351bcc1bc1f5f442bc43f63bbb5d81bdac9c8caf0abb54368dabdd84b53ba85079a54ad7a0c98557de5373813366ab1750f2a378c5c4a99e567d983eebca9febd6db1f4295fa461d6a014787bdf00a6ca26e9209116d02e0b9a1f9407be395c99256e889baf53b16faab17714037aeffbb430fcf7303c764e74c8399b2dfb9dc34608b41b8b01bd17b4f82b271a5602c3b830795911e1967d95823f80443fd1ccbab2168e0282094231cf0f7694bcdc36cd6e00aca1058bacc5b84b349a7017ada20747e4e06874c95db3d845bb6bd25c60cd250e6f383c4a98f08fdb7d5ac5de0aa8543fe1cef0a407bd776ee77446117b182977912f71466184238d75c1a6d0817e5f3f94d53886f425f13167e8974a6b7177d53fb94b016e51d49bf4a3977ff86679fefaaea41b99d78d91039f011fc82e2893cb19e975ca1091bf0aec590ace7ebe1d6435c240fec16196ce27cd860122642d301ff71e1a1857e0d4f70238431800f20621a590bd371e67186f58085ac4e59e92d2de84613c92dc4a2fe0f1e42b7f009c0af7257cf473e89045f41b1748c7022e348623e9d601b233061d9525554497141fcc9284abf168a87473248f01f0d2ef9f0739cd6aafa1b10d507158a27e5336f48c74ff1e73a88b64309183f51804cbfd0bb0b823d0cb42cd9d8b1cc2bdec81d437d33816300f166c5876d7918021af476ed90e4f27be6cecf3e932daecb3398ed3c341a4aee0b708e6524434a183e9be830abf3f4564b8a3a1a4e1f7ffc46f6b2ec78379d8dceb0f60005fd16a6c3b6483a5d0d467ccedd9ee112c327d93f99a29481cbd0aa52d1a124553a674536d03aacb317f249310ce2f524b4a32a73d744bc924aca3d30eca62486647acb9e32cb87cd136033abc5783b1c8565de29138d9d31659b4d4ff04659253df11c02b35a29c777b92631f499047b682889695a5f4dad02f38410295e33277382cb81f33faac2a73511daff6ad10e48ba8ebc309040db14d26b68b9a3d917cae1fc9a7f859c244b85f080d38cd47f7212642cd3dba4ad0095ea732c6df95a9ecee978b5b0e922382da9879c13630b8d0982adef16a817a843ac495798a7c1061c5c84b6f367c443072ebab0927b106580b33cc9e6495baf19e9fef816d519677822c060a344a004f02de2c686ee489261a03a8c47a06508772eeb2ae81a1121c8b5322f998856214e9d929cfeb30ff274e5ba1bf898ee91064fb877cfc594655f024f2390cba73f2374bdc4012d602344e1faa4331364ee28a7940dc457807656ca857c54937faa295e3545a855952685e28622670d23fa16fc3f4706e813c9d6a06072b987747acecf7fe634f6a5ec0b1ec80d2f8838ed829e50f1ca2cc60fea0fd0611d4b114b349cf0ece5b13937babd0ca4650d3e40ed8cd30978c97e3942d2340708b4a55da0890f0fe13c0aff5f1a995e8f497d665642cdc9e85f34872037dabcb3448866f44730f22bc9537ede030168eac0ce04893a56976e21c0f6487c3d7a3816437ce8356a982274ef2375fd53e6320026bc25c31846af9607b7b80a270844052021f760c559f6a8e903f434ce3e91ff72eb1e61d7e88de92df508c7f31a23bb8eecb6cafc91e8428c7f756f6c63b39f3a9ce24c5055c5e16f50e3ef20c9c3a9b9eae6fea2de4277c87bdd67610dce0be9deb6241b86095a1320d9995788b42f11d94d5b5c81b2dbf10cd95e5e5c4d7b0dbf18698b32d9b91ba99165335b7385a2b690755fa9bb691d9d2aa359fabcb995226e49c1b51a2b9adfbb40ae12efd03ccfebaeebeb7eb9f9487cf2487ac07d382b0757d16d884140e4358fee3acf53255bb46ef06c46ca01ed9312e21d08fbeb3fe6cbc7d875fb0ebe05baed8f22ae1d5b5e9ce1d71bf0d244cdd296748412ebb2fa9150afa88a6267ae24419458c21bdc76f101002ac8e5bd61e65167c127e5e383e081258ab3f9a2cd8fe60d976f521834458286091977ece2c0fb0e9039f14373452b0ef9b0a6ae03a80f92cbab288638911c88ec2a4aef02c2bad8d93b536e057443c021ad89b54b83b208b54736c4590865de512ea07a0360648aff8f6a0c691185a7615aa424491edd2674e16d44f2d4154856050bcb158454b01edafa4f6a9aacb9ceb6d7fa15abb349040e6d3b653cd943ac2ea8146a3407d8079374769c07a0814a32aa731ce5051f7b53fc16a17a269c932f840d6b6803212a24d270dea4cb7e501fd6bf0adbc17d646166d81cb04840121b25f4d4f4b142eda5575e0131a4057a13e562f0938e832fd3d90fcc3c1b2a4c5f3812666ab85d6b751d011ad2aa9b8a3c19736b2fee7895bd28392c1e0dfea492238632f95dcaef865fa94fb6de90a6e28378dbd35fb6392b2f4b0c4c8f712f8d9fee6eb8e9e62803532b1e3657876216431085c1c770d8bcea4947c250e174b3c79a3d301581148ca0028a2d144030b02af612f8a948645af8f3f33fa84a839dbbc6e6952627a6acaf2cbcbd343830c798de567defb8cf3bcfe1687e25c6ab1dba06d02c135fb39707138e9082ea4a3bed42b619f431a3c312fdcef512308c80ed94046efa73dce90af5a30284e8f1a668c6bd1329e8fcbf61127802835301da9013aa17ea777626793455f7d528b10d83e892666960cfec3e52117ff6e892e1e325a45b0bf8b4c7e367131f1e72f6bb826d48158c97c7b8bdce70de0820df2908a9aeb66b71c2c6dd37e060806a4dadb02359df8d319c6dfe8a47cc8bf6b9702868508b038de50c3c881c495debb094b18d446975b5b7089fbe310b6df3bc1f099bc7ec360a97a5ebc089a33585dd333c6f86046252963cfc043c60aaa023d366ed5c26095f71c522cea1b646c8014638c4d443dd59f22c0859b0ca66b6af1a7e01086982b9156a709c5d4141d39bcef8c2baa08b5cd8fbb8bf3ea145166f5ebc0ed6c63b94d49ff12d4c2aa1c5a6861b185a91a9567a29c41efa848545d342ddd1311f7088d8517a101c3d678cb71424f295640682b8eb7fab4e265380ba978d1994506343d0c8df009555020d696a650ebaa3997259aec868bb467bc18ac68562cd3e1ba17effc0f99a2e38347b146717a8946b531f0ff7411df150eaccf319a882aa7ebc1b917224e1f885b7cf893c6aba45c5df2074463dc198361ba36ea0e1c0606dd8144cd3b239dac36a16e299397d65358468bcb1b8d190c20f31a3ca3d3aa87f07a7ea601c06026583a3dee90a23146b5aa7efcc64b0b93785622a2081be6ab7ad7518ab4dcfbfe7e772707ece05eef02ba2f748ec4cc25a60ab303b9c3699deae6112376bd445ab132c9338e77ddb3baf0ccdbd11266f6e3fa889460da0245f288b6208b1ced27d0ebe8f9ccb404b9e41f0ef85388af9661527ded158123eee172b1c034193637c9e0547e0c6e73bcfe211a152ac9a76f292e946478210357b5a0ebf3f588020b7e1ddaae81bc93986d05fd1520b226ff944a524a3cca7656e2b24467e30e3ead0f51c0c764810eef92ae9e8c3ed2b54e2bee7d1d10d2dfb70a4d365c13937b0cadfe8a5660fd1caaec8f1eacac84e3097a6946a70d8cb806cccf2cc02de675751c95d16559db5997067d38ddcf0f26ccdc94a0ce74bfc681851b0a58841cb3f423d4858012465181845174d6276e6fa0adf3daefc7d2e93ed65620601099950737781ede99cb9d6d3f6925c330899cdaed24a358c981888839ffd85075d85f438e3eb0bf77a942cc98600084d8f03fb2660806df93afedc70fc3e4bab353529b166bf06c349c64d8df73b07639145058f383b4e9af80e039fd98b45bfc10ca52d47cca199723d82e5ca213f67fabbd00e94a0e4023cdae5a604d41dfcb20419d6b8ede07cc78ad9b6ceffd6001c57687221c9ea0c2d6dbe574c050348ee2fa1b36c49cbee166725eddb28087b3a39a34c5eefc1ae2416b1f8a12c4c846edc68302f605a643ec9f379b502b74577ef678be40b64f37c61e31037a8196294301e296550d6210513ba3708081e58e7bd2f6d2c0a2599b587f4f3aed2bc7e1ec3f6efb2e9fdf1e4b79a7b8eff7078c6d5fcbd161d608424193ab81e7a9208c796253da1aa3acb23c958137de601f012d08b476f5d0cbbfda95dfcfb94afd0b0bff6d0ebcc851796794fa851f47b5a850ea2b4e0afca738de0d1acfa650a5a3807b49e5f986e18972b4a8fdada576c2b90089e770e5ab5e1d68a89ab2480f59439acff4465fc7a358eb69284cf242a78a381c329cb3abae41bd6afc49dbf7c91db1e2dbc05ea94fc020e900516c4da7c51186680b82171eda79d57a8f0a9a4bc0a59bf5d3671a6cdd5c3118cd16346aaf11ff11c61605ecbd09e77047f61f2322ed3b8d4993f0cd28d7d3cf56bb9603170b3494b9406da9fa72c444126941dfd9b7a6b0afbb9c59c1d78344414f303d1b431854f302e8036723a223e7c6088e42b08e20ea7da89d364d102546aaf73103d95d536496f1b0b1a89198c508df6e3d32cbec08fc18de0192a55bdf9bf25c2d24e5970caee1571bc5ab6154b2afc591516556aac0de50ab9b1a8f4720297b3703451f71460106b4b3da4924b34fc1d4cc40a2d24984c914656e98dffe3b7a0a13e6a8e1dcdc1ce65ef850dae700814bdb788a66d0ab7b4ce9a6be0092135811704c50dd13b03231a11e3cf1b1ebf042f04f1178c3bf5106b4f752e92c803003b76d70aa1e3ff24155ccd4e69cffbf60392ad0069ac24002328022297553ad70fc6d1e7174fe5b1692230fe9eb7bf3acc5e5197ee97ba871865871d2ad97f4e2283b1eee01d614fe4d0fad60e091d4472a55d7face026d5c84ece16e8a692af620a106211e90a1c28dc847b4409cdbb13eb199cf5a5a6b66c9537205f233dedf91ec6d39cbac1a34f4c2dbdad5b9231b61524eb77c48ccf3ab34fdf5b4356be85927cdde0fd21a1fe574165436accead951c5c3d67bfea7865a00dfd5c05205e3b2752a9ec1caca7a2fc7b0578017edddd99d0ae7fc93f0f574eadc280b03f947884960877f24a966835d158518aa1357cbd74c1b2c6a0a5835545389f9b02bc99d9a8cc9e512ac7fd9c670c92d0cb899420582780f78dd2733e61b438d5f264bd1acce4598886664dd798febec45e472be096c55a1e7bfa3b037ef08e5a0f4f99594ff67dca5f2718533f39c12259f2e97c45e511ce7ae502502ce1bf82daf5b46d9b8af8f9240b9b3fd7e603294cfc5d6e35837bd079bf76a2fef6f879d32747ab00bf81e43c3c674c06095a3aab91f4200d963f6526f04d578fed69b28613c51336bea5aea9595ea108dbff09733051be31951c2f401c2c048ddb8a910c785eec9f9bc57b230e820df2ec6d875c734a290155516490148fa1511af34ae78671239375f2b5603e1f1b0ab513449fc508fdf7927e109477b00c29be176c5750030c9c02d808798235089450b3ddbcd67ac10acd4f0aa9cd26a2fe6f6e4b046aeb4b16e16d4639b1149f76e8e731f7968b478ae316c052fa2e5b896d5edbbd379b8c19d5db874ddb9c36912568c2d8bf60df30b459837613f42e91ace8262f1c59488bfd002ec0abd92d5a14eda2ca33e23a7d333197108c8fecd12244a7a0c8dfe5369e6b4bdc302e33e14c14b4b7b9be4724ed1d234abd90d99850f118d2ca5deb162d294894ae1be46ee74792b05fd77583ad1a4630b29b8dbd20770deac3f634fe78b9b3ec28cdbec6bc1e82a0c911191514e4d84425516efe4ae3d1002454aa68b867874431ab5da682ab9071ec80c192af3dd4b3bf1747981c580ed8af72332ffbe8e2e9970bbdccee2863a70286d2e441511528fa90b2431d6f533e38d772d8bd1a71bdb9146f1f0fa75f607d5f145db23c2793fc83d54c14e99f757eaaa3297442ef8ce5b35934b73bfd2e0b84e5a161fa02778faa5de0db8ab5f530471c100dcd644682ecd81aeee818e571fc5871e27e26eef1c06617210c8eb2d6b760ae2d3dd5ac7ee40636644502e5674af9f498fd7abc512b866bed73951d696e27f135055ae184dabbfe0f9d46f407fb96a26123bfb88270e697dd4f5427a5e7de61841303faac5f8e008bec7c59120116695ebc775eb2fc3de294ca07d5c258076f4da7e03d42ff5da78cf9331728b15401803a755e3d3beeda1ba555687ff57f9303a9203bcabc6a4c0319b9b0707c23bfed342be61f6504a9baf18c684fcd320f651692ca8e5e82da2ac440c5360ea006c241764049bd6f940f120676fab899dab95d3db1ad4872bc5a8a88b0f4c08deca8e948ebe8428bd3f439d3991d862e6d15c5c0f2636a3a429afe8745b1e0ddb4a19bcf3b3df1bdf2c29bc65e95e39a4c4d50d13407f4434b269ec6ffbeff459632814004e9ac72ff18fe3b3ccbd28ce8e7dd3a9e7afa0ffb7690ac865b3a800311bb1689f6110465862546b6c77f1ac87a54a70bc3f9476947780c7220b34e0ae15a3aa9d708083a99537ffb813704b567f6be9004b6c6e9cec88284873c070f61995b15a4faece932332df44d59f752ad9dd794787e29cacc82fc4fa9227820c4dd2297033a9e65d905462afbc62fdb03dbd3cf455fd8a529e7c5e8d425e825326d2f33e292211f2aab073f30563c36110c220e8144cf3f257cb05e214ffb45d383ce157ffd234782ca849a02446060a3e76860a35f0f14aedf904ae766aaa17d1891e47e8f20a9d199e789ad29a2e3e8edc83fbbbd3ea0b3a55ecfbcf86000fcd69627dfeaaa6f17d718adb8ed3754945df32ba3d83abcaa3d400b01a3c5ee143d905b1700b90661e98486fcc1a8f1115336252274fe7ed262532028aaae0eb7f87519f0756606401a329bdf46b384dbd5f0b1425970e13743795d1bea76bb8ac7724cf86af8000515c2f335cd968a6f94ba7e5127de38b11cfc4851b02e66c38f284d7ef58ca91ab6cd9e3c86498cf812d721cc0c358eea004c698289687b8926f22e2c9f8f9c7f941a8be3b467508080e9cff1246e8c749bcb94cf5c70be4db212f0b6c1772bc6ce85ad852a4fff6a9fb6b87c04b6bb1eb614a6fa749fdc6a3731016a87adf4f0f75089be8e940e6ef65f01bd71d0a5cdd26ea15880d8abea6abdc29c8769d5b5d9bc021f650f2047c517ea7ad68cddd16500cbf70b03887f71e1eed2eb696b9d80b63456b03ceac39ca1d2a2211f5e10fa9c6f71ee249846c0ddf143a000d200969b7aac24704bbf8d1fe110651b36bf2be1fba00ef84a38fe7706fc37a551798501f66ef1d32e8075d26812097b10339935452c3d3caaa7e159a30ad6b331cc8dbe0f230aba7821d2f1a12c1883f0e180e464065bff9cb2b07ee1df3271c23b01be5347efaf8af1e1ad5821503e6862ec4f48c987ce73278cc7629a4639f5736a52a20c2816a0efe84a3787735b0021decb2e2eedb35d1c017f124c04f949cc8eedd75bce3155e5102beb9ff50ced2586da5c6caee0a25b138f9a76b98ecd9700c419269824874fe2e7661ed3c173bcfd5bc9fa70da3c190c059af66db5994e2d47bade08aae4ac0dfa84c72fdb9b61a3b2be1d648bf112e60fc53ab0c7086f5f97ae0ceec8516bcdfac4b39b35d9c3ec7b005f7c311f287177e568749124f8f47a1f504800799d8538fc36637d6f556a6680b4b52b2a4e744258e9e79f0c0a1d84c46a1c42589cccbc82dd155851553523f19ff497bca5b717b5647084055e6eb9b880cad296d02fea2a3b0e99ae3a6cd71314c0862e146f4172c4480c11cddf607e82347d61aa54503ffff41e5d5614173169a2e773c13a3068e43fc45479a26d7f73339283b884e0aca4553118e88242b647f1150d8733ca4f95648bab6b357387baa900c88ad9fae01244f5556192c46479ada8f994e21760baf47f9f6265e5c3d23439c894e49dc793104c0afcfc2977d12cf81fde6cc6358ff411180412d2a40cd4fe35142088234085cfc5feaace2569f5777d4b83b1eea210d9b1a27206f631b9852b01e6d7d1bf7f5ce3e321e62e8d7e12e67cc7a4f4fd26b5bd123a127d793c1170e0d93daafb31886563609ecbff42be8660c0fd079b7f60b3fe1a5162a1c5f95a16a7abbe3c2a396895d17f3e2e45548bef714eb22ffd071092b86b87c2b6cf21aafcfa87f7e28ab2907b0bf9eec7f7eaf928828f5e95ad01ba6e3ef0c87a72a0877d3853614b1688b3ddabcefcdf804fe36b6fc4fa2852b1617757b1e8cfdd7ddccc5f5986fda111cacc3e6a8099038fa5a1800274bafa6b358b8c7aa52f2804d01d57fa9f9bdaac7f257aee3ae6d7727d7ad0f23429e628793ad9bb241c56fba7d4de7c9a70cdc026aa4b0fabbdfd1f8f61a5c149b7ab84f47b6da0e22656310c794194b16ea2270bc29c43f6f6e8827cca280115d0e9ce58c9acb3fdba48131602e01a1e762ba84a93dd501bb8b94d49d23e9374199d1c9e5511c6a4b699ecb3bc2a33a2a461df05d1c7dd6134bb6e8586fde4f0c935ef3284fc09f8eb72e0cda9e6597b16f76fd79d8ff7fa8d8f5f9ac37dcd9d36f472f0e78a2092bdd4390cb4bc899759e771a14c3b64aeceefbddba821d3b946449a9bbc120b0e1281d01661088eda44275ddf72a352977fb2594f694d14f63ba0579dd0ea660a37055b605b8f572514c04c8b343c5f3555ff7becd765e0a97a7bd988aec35ae819d78d837a1dfd539bbd3464008d733241d790d4a1783418d1d9e30021f15cc458fa462216bc34357920a0b2bdbbfbf6399b1e6454456a54a38b7a358db0841726e9a4be5bd0f60e70b6fd54fc6c44f00e02bb129fd01e9f30915a87f673d898b620f82ecbe264291e1751dc4feb18d16570d75ce734fa6832972f4230650dd837f7932d5a525151d024fa69341f4449a833b34cc59859d813eaf9019fdd0c632700bc050fa0f32d18ead7fecb65419fa4d0aa5fd5fcb02ca3e03a8fb544424a47cfb387eb2ccd9ec4a1d1ddf752039159e8ba2521864720f839509534103d0107a02c9880f4408e74ae529a500f4b905e10d21192876703fa024a484fe2484bd77290eee15e9a9f76f5c6860073174d63bce2e1108cb697d462c9a0c522055dc97f47f5e4b7d533ac8cae6842376e51b4bf118200eecfec1f7eedeb372d47f8f9814db777128009c21df2a2b4a57654cf3471b47c764bf4b162ed192da70cd27fb9b9227002e8cbf93b47b23a2c73fa1e9c9ff4523a318ed0a5f76e6df6a4298b1f89c93a5ac87e4ec86b32dfbceac35ea8eb5e2654e10d45c17902c22265346b4a901a5154262e38849de34ef88b1dea14ba3adcbe8c3619c27f36e07803083633f962c49ce178f75aae8267c2b6c53d61f7ff6d924ef98e2b4b4538cbd01a04c29497e42ef47beae549cee07830eadd29a16dc715ae1657713651cf102036f85f958128f326c5dcc7ab05c3259e71a2450f256740cdb6a9ce46ed2042fb80af0cabd346d735503d964c334cb470b52de8c070be69b9cf6edad648d93ad80be56a4dba8ebc921a6de7bcf6995b88a9a981c34273a05a5cd36f2c64e5c1f1ec2261d206f963ed2bc66d41071ccfd926e72c2b19c22c6c46594799989450ad0669372e68b3f064c882e850ff648df8758fac28f4ea1ca47edb8a26ded8816e6d373ef03d40e788d5d021e5a23d88f997acde13553a57d49509db31783cf0ae1e42f74ce54dd47ed1d56cfa38a49192e10a04b1d4a3874ea0a491b3e1c6be3908ee70f8aff7cb0595451d13bf962c1953c8ab7633381696627aa7a92fa4d67abd95765a918f3145fca80b3b5b04b961cbd37efe7af2800d866d7d0d1930811cf3f1b8f26cd4dc56f420698346f4a1fb98a150cf9aa52aab16cb2c6ed240193a9ef7f0c571594034dcba31cf4c05655192a9ab4d44b89a14efc7edd8a870fbb4855e8ed2d1629910070267d61ec89193449fcf02dcceb4e912b40570d4d327b6008027938e9209875a68da2efe0237a6891b3ecd6419461b0bfa215685d4ccdf6fea3795ec0975c53d299ec402bbcb14adfdb5848152e8569529cddaf1cad0a1afd70939dc93de95e1a5d451131641e04994bb26a77385879a0d25b57e3b1f7d4183f8dc201a66de3b2d9635432c194d0f1d9b1012282699bfa3bc26b48abd93b943fdf04dbb7d031571544bc23b6c299de3b621f1394ed6559b9928bd4c71f487e54c498b292661db85e0fd6b4c344d5ab6a74ca19b220f8936100e7ded218e8d316c8b05b406d7c0f07520a98cb1bfec224a4a60415178af614f871dcbf970b733f5119da9f938c79b222325a807c50f0f5e510b956708b1e9fa6ae1fc1676565d4696934a5f2569186f7caeded7d0a365217ab151da9b051e741521af2f9267404ea1013e7885885c4d41b7e12369cbde1246e195cbbe4c539ab30baf81dd7ff841e161276c74f50079718ce31a5de22dea7cc469455b68ecf21fb61c8a64dedeeb3ce482158906c3aed038e5ac52d27732f87516ecb92fdba4ca50fe34bca92c3c7d2c37657142d8d769b56f4b96eb2b0ff4f5a55667d8d7ec4ee6c58f8f6a50aa1f0f902c97ee139b7ad89ca5ab9223d703f8bfc38624e960111c21790e8482eb80c2b5af83984d88ed4aa9b46dc543e7300ced0b7fa9273ab27f131f1ddcd5e5d8b6dd73f6bb6e86e1e02d489269d8a24d3ec8136c10e1066406492153ecb7d3502026076f7b975a78d7c2b42828e335c6cc4593dfdb4217dafd2da92cbeae1d5dc8e5008adb3e50c057409f6bc7167fd2519833456a9e1c09f63129aa942ea4b90150a39eccd7b2256cfc0e4de6bd5196c8f8a2ab1cf9a17728fb1f91549e8018cd8854581585c5c65da845c415136e421610ae191136ef0b15e083dc386e8e0bb11971c1fc4553efc74102a1c2670e6ac2203838e19d2c1a7d3f92ffc89892fa81194a863496a694a6997cd1ada9ba7434bfd5cba489d106a1f05a792cb62319052c35d371ad0df4242883cc63a0ee8b34c0e2b750ee1ab014876dffa13614e532cbc209f099000011e6e1291e643fef1675581c250929d27283d3db3b08f2d092d892127fe50b5c78e4c5b2a9d6d2b5bf98aedc2ca445e00384bc405a1636516f0caad3282ae6ab2512584c3716922b33171cfca4d311bae8ac541417c8543e763035fb025ea7f1e09017153c7281b76f7860d6c3098bf0e91c31ac6ae71e6cc6d8c0ae7761d70eb5300c44e824b8f53528ede13a0977afa46e3fbe9e266ca021848426038a88b0aef4063538d2c93148b2307f235fbdf3fefde9c2391ad011c9563762072310231a53370803c57db8b382c5669003d74b2d3f4253c9f4620806a605b240094601fa15eccab436de080509ac49a66b93934210fe8860d1aaae26f11996036b60dcac96cd81868835eccbc441cf1b39247be80caac0195bd88b33cedbb21faf470f2583a4ed6e2e599f3d41d99760511baf5eb6507c032bd4be82c73e8ea5011a03fba0638e20951837c720a894b6e38c9cab2ab11dfc96c1660172b9833a9171e5193fdc9698945add84aaa2b94b45a7ca0fc32d1b15eb2273f550abe842e8a682a82f1685dd3ad83796dd48569c60d21895bfdf25aea3d94b35647c8989946bff3b9c714de2a08c51ec614bc0e403b32287e4bd9723ef220797c65f89a72b80e81ff2f23b0fef9459d028491183d20b51019a284d0a2c5ca1c2e96662b9abdb53a694d96f21f40b64805620dd0a3c1d4ba28eac1dcf79e896eb13f005f21330306f432d119e19b28c23843fe6ae5d38448413509907e2ad7ffcefa83d7c59afd1b381d7cdc12033c3d3abc2b3b7588a914a2c600c7c670b81a0478f6c70780a9b24b2a440f190ec339229686d7723a332649df8445867a8c9c053b93427519c23b3ee59577d89fd3936741c9fe17b16c6966b6a9f8b7cd0bfa45a7456c133d3695fb44ffff849251533a074c3e18e617ac96434a1835c2d89a0c1657f4f101ac8292ab8d685b888725d74edd28317d2741481b99dc5c96014b69576f5076fdcd09f59e4a9cfc65c36f84a56d67b1c8c669b7778a65d3672c8a938440b419a6c80b8dcc28e3ab680b1449772f9053afa8a45915c11b448438404616a732c6decb44876f998e0a545ed2d3a230e380e9a9750a8467579e8134837080302a39e42adc4b22c1d7250b49a6cf3197c2dc27f784e89833e1a061109c2be853fec61a9ea214d6988f01ef3a9c3b061fa6079124d2088aa594f7f74fe96b52b16314c05df81340e255d5e21cc744ac3b524a0462539eb165d654b25856a70150a13cc352e8d06876c282a0fc95ebc540b768848d873a72e2a4702959f4e98e60e4ae4ba29f68fd1b0f5562024d7defa62363a021b7448eed2ef3ba910195724d94287660d1cd5485769a306858822ee2e65ee113892b94d0276fa14c78437bfd83f8e9d6b04b78f43072bbbf1ba580baab5d61ff1ef6eec34d965143db33e4e61799ae14ff62e31d112f1af049ea052d68929fe45d9fc5c946a0f312bc4de0944f320df28fa1da159b11927caa309f7fc56a57a08cc1389d8818ca0629f5af383520e5a238b0a08de933f63165fa391f54f4448686897a108fcd34ef1688abaa0c9d4f7887f04b3ae90ea0ba1a70bb6e655175a01c3c828dac93ec44370ed58ed9ee752a337369bcb8217eaf7868ef28c1ce2f415202073fe884e91df8a434273d7799fe918d2be2962c553b314ac76755f3276149af7183f22e77a8b0f15ae2bf6938ac3a2fa50e46bdb6e3f00328e9e350f236986f3f4c379384f3ede6ef0f5e3cf23f811e1ce61e1606b430bfc278446cab0cc019a44cc200ea7b6d825d76e5895bc03f0b8c1f404cf7b2d81d7c5b94fab15c9885438f1fbe8da1a014ca9436a35bf0b125341091344a073f95ab1d03f0ea5959f7042c6fdb8258e72ee6bea0718eea2d4b07f9125d63f538160a40e036c98bc3fce2e634d618a91c72a2e29fd396a68523072f9fba981084a56aa8ceff89ee9460ad91d3e5bbd1195531aa1d09911294bf6785a1a22163c1fe010eaaf698a7280b2fe3734d90d08aac92e3cc6736f23e13d7433329385779974126afd50d77332c57dd0e127724591fba8108fe1e2eb007d786c300c73c910aac5f3ee3b8ac5201f2cad85c170aa0563be0c7f5a7793fd585566f452bd0932d71cdd3c66647e9308f1bafb37e7596bf71f7693765a97142038c28f29ebb43f70c6a50385652ff077bb4b8b79248b0e5b979519a302b8f13578af2993106b0a3c2184e9b031fcd260fb9feeca493d0392e40ea43b9d7fab565b5f70c7f0265047afee665a6cf156efab6e4a617cee1ac4ca2861cfe0e429fd5bbca2ebd7f257562f4ffa4ea7fbf1a742d841c49ec69ede027d4491a9172dc9a35a45e02b29bf4db6f54a15be922526d58bad60b43823cdb2ca941743f3dceefd8efad45db2185a9607c7c8ec30fd0ff01674a07a33db9590b14279643a93275aec577f62f68b2623a1fed0c5b889b54642473edae81ce8c848dc646b5b640845e3020014c53af686a6927b2bcc7e64dce5eccf1bc26f328e8c7f011577a2716ddd15a573bcc30cd3fc811cf4ba67db42d65a49b8cfd5b1e55d5f4740c6f1b44e7f89ac2a9e372f4a90a70e2119ca58b4fd7847fd0a00f208cfa3e8e558070753ef994ccd66e4543929d84c2b576334931d67b46cdcf6bccb8f51a50e4f7378e2c5dcd9ba4f764de30e56dfe0ceb63d8854857a305d8970d15d7bb3c747ea8b5e6d5364a67e246d1b06104ab71902a8826be1923b69c0955ee7ab3318fbd27d79411a8024370bb5e47bc013103d82f041ff2fe7e62da841a0bccbf71cc6ab908f99fe522e43d490b75828b52171daadfad2dd2d47e56831bbc31e43daac0cf9f0869f82ec622ea88f8bc8b575a339eac1ba88af31d40291f490d5265de4066cbfd7ff08951d5d84e8052d2c05a029f4828955b2b94836ad2ba1c45c6412fad544f76b0d64882e1b0a23bdc760fb50e4b25a825c04422daf50ccefb8084f248cf7c2178169ade76a9f9cef60bf31e4b97b4e2653552480a62a104f310f88e67be202629de2720b01db14d8e2224206212f39f642cd0e82621ff6101127cff4e2f71004efb011495b636056538d9b31e5006f0f78adf7c09b1fbf73c7fbca016b8710894ddaaa03816ca2618a7079864c0b8e0b403cd9645cb4b3494c6a9ebd8656189fc02b839fc5aed3820a77646e004daf33bdd80d932beef0d7cba15da04ea6e1bba5021724ab02172f58810bfc5b810b9957e0828b97ad64b1742216e1b018859b567f16b8106388f844be62ed6603d70f62e6b6de263940393d4c12a7cdceec9bde0f036cb353502ea6ecd4f18eb5b617076a52caa203dae432166ad4934e75c551b558c01c8c6be6122a4a6786f15d8389266f9883c5b93e05ba9d371ada481e03524746dce5a273f7a613944afd8e00cc5aa9fb35dca909d2e69c893bbb7a3522f98cf264a3c0081cbf298a15429f7d755c7a05c5ec34cc2e60931643fbdf50690ba3f6893dbd64403d09ace2999eec30ecfdb8ab433f131e637d6561500421034b09a79ca9731b811405fa2852f293f2400a015e0ed6b53a203df6e6b4b9d396217b4e57f9d02b0846aa8e43f09c56e0cc2f0f5cdd05c29f5a24fead758c961015ade012d87c0f7707aa5887a7e5a89e7b563a22f8adfbbac353c61f95278a7bc0323e910114a3027aba795a5e4096594d07a163515486d4818ec1b4abb0d5ca6c2ba051306e1f9a7b446f0b9cedd3bde0123a620336ca0f6c9c0170e5f8f39d3dbb6c4780bd98009a413244175800dbfac60534ac10471b7be90c876bd1269a6c7befbdb79452ca24a50cea05910537053f4d5e7ea04075a7d3e98442fdc47439643e9f191d3441223d1031221253d1cdcccccca850416489b0654b18112516a81a542b38d024a1e9549e0c954ae5b1401a4f8613497a01c03b62657252a3192100a82d61384c21806ec7005e84d8a49410c08315a09ba3122a3d38d50e4b563bd3d59094d4853041c3243ac28df0e16606775a6b95104283b556488d6dadad752e11a282226e902203215078002064a9478b212d1a6892180264cb253d5818a1236ca2061008397e08126eac1822a50615366ccce4d02173038e181d7a6060d0e3059623f5d34487cb0f14b5d65a6b0fba6e09fb9122478e930f4b506ba5071d2a463974ac061391256e7c44947e7835f8e870e0c125d9b1bd7c6847d8909decc03818e1060e53f81877e41885041995f88c301b46257692a8778402363e0100210a30b4ff0528fa861e3792a8370cd9e2df282188941b72cc1b7a5021ebc486b5d6da2436ec20dbc0031aaaddf1830d417818d284ee81e68c5ea0611a0a1d64d409027483eca3c427a89b7352ea13f331aab55a0b640996618c73f689e203846ddb382e482bc84e1764d49e30d231d03e3c347c5bc27c7c70fc08c096df690f63d03e77ed7357523a69cb37c14edf016cf9ab3d3d164c475758dad09f75ce8ebaa8d9326646d5c0f6300b71dcbdb5863bd46548dbbf36d9fe1669d70ba4c4458130c618638c31c6f8047bbb5af3bd57944f58dab67555e3bada755df57c4ca6eec864ea6a57419595bab2e274ae9838ade2b156569458ba0a9e5686b63f77f25827aa643b05dafe9c8b8743815ee8cb4bf5f912454989ea2829cd8e8c90624646afe93381c6d9337b664b3e519ac9249984c209d3c39146db41f184ed6f4fd8f5fedcb78612c4dab679acd9799be7399d9ed771def799be4e6f269595159695eeb4b1a05a5a5c5abad4cbf6f2e274beb8bcc0c0c4c0743233dbcc8cd3391333a342c50a15dd831b083a9d6056d1d0b468d1551000abba5a399dabb07b0fa7822c3cd6dc5f4195c762d1d178acb9c5d9826bd1c2e96c91bf9819bbd2e42f26ef1b8a2fecfb379c2f253500120a8408734e4a8148f1c9517d786415c2c95808f4dd39260a3af512f600ca00ea4294494101285017f24fb02dcac8a8b698812cd9e2093a37293d8077a2feea556f95a8a103a9c6105cfdbaa2c90e4190868044521131ac0e1daa0ea2f155efe1576bad8e1a4d3210288030b1c5bfd16ce028cd3ba834c01140ac5031231303f3927269419d585654eed20d0e5fe268e8f974952a3a9a3f9a544d669a23a809cebdb2aca868d3e775dca6657c6d750a43f292306490ae4dd024afa931783a296532e42d38a2e95921f110119ae2871a45178663e7470f291d17920ea620a2093b9708d88f17c07ef8f88123c995c0bd116fe44dca6aaf3e5914d13ee89ea31bb8055a89c6a16709dde52003fd835ea27fd42062812ed23ad0e20ba17b0461867f558e628b7fe32ea83c58506badb5faa023db8006cc638707120e1e3db8c1e3c98d5a6b652144b3b881ee31ea2471b784f500daa27eed09e9d53d94b9eae0d0dceb0e1c19093d97d8b841d890d9bd1ed1a05396c788ed32bb9d0ee339c2dd4f29163ae898be3c886c1f337052e52fe491a7fc5db81082bcabfd9e7adda76602380d94db82727b15da49c5f0fe03b5d003a54400f6aecabe9905a035e2023675316be81f60e24c5904f640fe7ff166674fc60577de40fc1e68231503497eceba8753b706da29ad7f96cb9d90f7817a735b674d3b8355dacc6f67191c9572d2fad9ed446b64b667adf4ea87ff6d8c0ddcb683deeec0ec351a286b8c4cb36fbabd7ef7b7ed81da6be67720b72b3882bc4d19ffdc37747065c7b28b9394ea38cf8e9d429d72c837d82541f01c19c173844f94e429c2e6f1c109c5d384a669dac6e5c042702ca2cc9959603f1f2eb601ceb0cc21693dd829ba9a3c9e608c73e661846dceedb7ae7370bef4648fd68e77b3c77871702ca4a0f54e4f4ad9b303eb2105ee295202448b683e13ec39da29588fd2d640eee101ac87897c5a6badd6fad0e9898231c639f7247d3db3f301f3a32e078f071f42f898901feac423656f09eb29b235a025cc47918f156c7d4249183278c4231e4391c6f038f317741c9ab96645b3d7ec6836675172765aafd6fca31c6bcd26b725bbad27b3357d46d91c92b96445b227ae992b8a6be6b8e60e0af9a58f52b19c9616ed43ed6f9e20e7ba340e9c0821e90814484f72d04eb4f84568a019d01290127d8e9b1d65a2eb5b193502ae2242fb4ec1bd86754e202427fda78dc178a0be7d4fc544df5711a1ebd3e7a694726a29f81693993114c65f6aea94b3fdbefc84f9f9732661c4fc949130667eaaf8997fea8debbccf14b4b75701ba4c05587f063ce924c8ec19b0be0c887251318a59e9b1fd65c0fa31604b0f18efc51527db3f06acefb2e2e4e5fb1730f5302c4952a67fe146976c632d3be5d202ce99b4998f02e94bdacc3f81de42a9bc6865d862d2c6575e9c5b8eb45119a1456e64c1dcc8b2028a35890a286e4cc49a44944cc459644f23889406ceb3e76fa0e84f546b63dc688a4918991bdd9e642697039be3b2974b4e369b60cb766aa5acfe84cba4cd7c994dc1987d3b0ac2ec979dda2e9ba3a1d4725831b39fb7f5de03ab2b02f55b98533a8fd997e173499b2d2786daa61c550eb4783ac5240c0fb43033bbb2616630a3b4c9d9226adc38d022cc6cdc2b4618943ff7a71c09c33e6a77a0f4df40b126f1d74011a6c8f6cfa79858936c71cb41d915b3da92b171cee8ab25ba6c068a4efa892650716df54fa4b182dbb45c3ff104afa24d9f673f110560caa5057562b99f9882f057a898918981c19f08038b9053cdaf4755c6678ccc298cfb8bd7d4cc8e217585d285dab2ba6e8c9394934fa594524ad90d0a293440050bc09098a8ae93ee729d7352eaa4dea0904203548021c94e7aacb503d5da0c70e008859bbc012d5f3a5a5bfc0c7400031c48ca9dde3425a31ac0096f0414f3d64977192c41ea04057dc9a44e2009524f663229098e404608739491a008a0705c8db4a1cf79acce9b2f3b700514dba294a96a7e2561c080a2c080049a6d5f02a73aca7455250b26467420b5111eb8bbfb3092b48d44a18d4c4103a09d27403d408072580284a402c900a80787810e12ed456ddf7be9795ab57557b0db1ea8235f48b4e7a60e928a1f49cd21d875daf434cea774ff720e85bab0520f637e6bb266fe965760a6b63d10d440a12eccdbc1296de6ffddae9433f40e9c35be398dd364d63e1186bb3d1005afe164473f11867d9febee0d5dcdefe691fd44b06fc8edbf49b8945fe57ba514d3f8684c97d4a1f9321b610233128319d9c1c88fb611c6c8a7610b978e99315ffe0cc318e68c0144cf694b98911b5be29795c6072d6aed96d2b7f7d200a12cb8eb83809c6091620643b4c8565a2e5cea48fc53db5e43aefc459741ba704919521aa1f884159cd9b6876dc964b4f358a87ddff668512b9359d90e75e9c2b6ac8cd6902e300e99435de01dbada71ff9cb54ea51ec3703bd48595415a9f2e835c4d4a690be3fcadedef7fc10ee3f078e44ae9a1a492f4865b9b7f2a24ada26232994ca69cdfcafc854a12adf1672227692829a55f6d7faa22a32b7f16516823be978481df5ddc78eafb7749f485a45eabf3faa2d7b35dca2ba5a0d08dc670e8392ec356b540cfbf2ae8afb4740d36f851a40639cca0c7054556002bf2dada95a32a5215a98a5445aa225591aa4855a42a5215a98a544ab40cf764b45af6f55229512999cd5e2f95123aef0f17db273a8e2cb3bdf2da2ab3d5d25266baf9b67c950b62d48597f1cd2f6c1aa3abf91569b270328bca49dd9fb2249d7308479fa3200e67d22e013f9d6464b43eed6ac2a926157466a899219b64d4a97a813ed5e9e14899b5cf3e8e8715cab49d5377afe149a39bd220b40b75536ec3b450e25eddab7b75afaea27908457478111ed5b6285d6bedbdb9481147c2fee8714cc2919e5284861d384987e490387ce69c53c29c071eb62da58d140245c8d29096bb7bad4378e85896789f11dfd6846908107437c1214850192265e5da60f9729cb81b4e5bc27e7480fdc0c0e507960af75740955044ddf06d0a3f503481a27575a0689a89a851a411db2c58ec76948f9f3e0becf8b5d0b6640dfe16504a8c3fd3ecd02e5d86a35c27cad560057ffde9e9d851da1cd2b6e48beead4f02e6872e8440effbf6458b63cb37b564a6960b2dcc29ee1b989fe36ad76d2dd1478e7b1943cbdf7519dc32f49dce8787cae69c4e3b0cce6c2b6be8e7fcf7c5ad55eb579e4dbf574b2549452665d249279da6d6a66f6a997a4c3ed4457dfa92c63c67a5a5d11c8371577c447b499bfa34445adc7c44dbfaaa0dc56f885d7d76fd2369535f05d374f3318d3d9837d4e7f4ba5ad115b4ecfe4e59dbf66dfbe6b1a646bdea2b78c5bb3e95d65a6b27cd5f7d4af369ab98a0c56ecf1dc3969e0cbbe50c4b9f3e95362c336c583d968af6d97a790bb3f5587ac60c6738a5cdf5582614b246bd066c9c4757c0f7cbde0a54f3aaf4eacdcda6de04b4cf17d042c9c2b2d65aabc7aa1f818abd1758dbaecfdaf6145a2998a58d6762cd97af859ac7e2f686ef96e5c6b22116c1f670caf6629cac59cb67c92d437b63cf4f8ce167b430bdef7e06eeb79f5e8c04bcef7e052ff4d9f257e0c267dd5d9f75e7f55a9853fc394a433929ddd9dd77aeb5d69ddf5a6bedce7fefbd177bacbbf373b8b320c618638c777e151164b2d92c5411414f079c367e6ecbdfbd21fe1c7226edf30c2db439c49991f102ceccc63f7371789f8217c4b95376956f77eee6a454ca557066abe02f6c8eaca1d97362effea2cd2a3417f59c1d9c5489efbfe59680e9c2633debdbe7b0d61e832dc090a3813206bebf8135e69418db73f75efcb580b74f5980f915bc7b03b5771dea93a310ca91c2c5848901d25d166db1765e6a89932fe46ca1a43d5f4f39b433e423a57419ce29f2a58792536826242454c4e690253824646d702a1fd43bbdfaec98735277e1a15aabbd47afdc618c71d67490e9d084ebb66ddb384e071d66413b571302a77a82253b4fbecc19b42428c99c93521da204c1a413310c15a167c9ce1344600214316cd2b69ec3071cae53faa4b5b5d6d2d024c38288080171c2277d9913aa239cb851a98ec8d9d1e969e5ece8d89c698deccbba6c9155625d947baee5d30334b67c7a5a4746f775676e396badbd168fa07499c4135b6e419bca2c9fbea891bc2fbd47912d7f8eee84c9e69e6ad5ca2eb633cd266d75db9ccecdcaac87d3f2e9b940776cf9f4b464e5ee6c1c0252e21a878046dc755dd7755dd7dd9b2fd2a6afddd83dba467445595807e7dcd98d7295ee8c85f2c6212025ae710868ec388ee3382b62ccfddc1ccee306a464d3e75c1dd057bfcfe9fc46cdc3e9923a5917eb9a74485d8c9338266bad5ce52438374ec2321cc34d30129643405949768d434054d474726a0d3397679ecea6ffe5987294ac94672cd4d69e93da8bba16d38e36d58ce88a6aafae73d94638991b7726bff8d493519b0bb545dc7cb69eedb4b536d926db649b6c936db24e7b790dfdbab552fb7f6b6d2dbdb55e9b832fd4287567333c9b567b69afacbdb497f6e266cb6736ce8666ae59d12c168bc562139c3038d7d9cc5f60ad53a9ffd56acf6b5d961bb7fcd271a76d9ec73ce6318f63cc6c06ad4c166a678c33c6b8e23ce6d15f743a959a79f48bb5a7f0ccd75eeda1a8532bed95e79c8e63b1582c26b3e546cd0a4a79e79c33e358c6b1996fd8eb76bdde0edb2d7bbd99735bb98cfd5a4e2693c964a88e73c92464415b6b26417b51a3598f3cba93d4cb04e7e640c9cd3d77983d166a7badb5569f77eb625dac8b7d5dcceb625d287ee85d6cd3dff2e775e166da5a15d38cfff33a1c6e7e6fe7f986d8cb336cad775bb621cee3a63fb91bbee41e737fb9d0865ad8b198428b9ccbc5b9b40df11830242d9a1f9a1f9a1f9a1f9a1f9a1f9a1f9a1f9a1fd76abbb57588178a2556e7dfbfff85e2dcdfeb4d65dbb66dfbdef340b9849efbfbfa5da7a2efbba993fe8173bbcb169996e786d47e865ec76d2b9ea882e80fb86b7bc0fe7d0bded7a47d49fb92f6355f5c615f5315418b7a9b6d8ab4ede75f1935abc74d9f459bc27b6d53ce6e0ab55631a1e73659d004ce7d67b93b6d6dbef679afed4695e5b267fe3bab234d0f5adcc6210963daf7ec77d6fe665fdbc66dc5155bfb0ee440518b796ba16822123523d3c4407364a252f1a05695aa09c72191a0c025620256897c4d01fbe268c46aadef218c072540301e7a603ce8d088c598429c58cce572b954482829d188599775b1b47ecff35e7b1c8580b6b94f81fafc0db643d2dededf148a316c93f734623462d64523a682625dd625b2605d20988960a411a34b2a688275d125152cd957d29126ec30c30e3aee112486b4a9091fe80be5083a521c017284e8c80f47823832c491228884202a828809221f100d81480aa22947768ed870848748c8861321d9a13710bde0871a44406c513fd290520d1f52c20da29c1651cb46251a897ebc38571a5202510e516bce20a59425d8f245bd7d82323a3931f64f8461db0f80f55a984d60c2941d82fa73a740e7ac41ddebb77ef6af8a7d41793f7b3801d82b60f64a4a232d6abfd7b77dba6d285660d730c76ca9a216881d82f6d14349434345434843b56e296d94b8322d4407e921a11aaf1a3af259ca257690c413b11e7a10820c62d41fecfa3a881d42475bd42f44941fe81cc51029845a3628098e19044ba2ea93694b58500fb6a8df8d2c0c7410da220111d235d07828077a077a053a68e75f15c4435bb256ce3427a59e33a55402390c1757eb9b23d98b5dcecf7352ea5eab755cf3c6558e733a39abdd0d73b9ab98cb9de66d9fe66d9f9ecf993aeda97c2b2c9585c5e964e954563c9648812438a95214a599d1911152cce8c8e835a9d24c26d5271838814ef1041668a787377252a75eedbd4ee7edaabd177739e3ace170dce6e1705de7e178ddf7799fc9e4e1a8a8b4782c169794c742752f2d2b9d8a8703b278acb9358b8773ea50f51d942da89616a7b325dbe99fe801b05ace8262ad75bb751c974d631b7e0d638c378c2bc618632de3fa1ab5b586b97acb05129b4abbe3dae29f6fa9b6042bc37624dfc199fd45f19a6ff6cdbe99b31459c2a6f59b790d651967b34dbfc588b6bca88bdb0285b624d11626680b5228b6c0a46cfa2c2c2c51a80bbf16d7ad811b8cd6d0ed88aee8b6c1e88ac6a40dfd6f66caa12e44fbdfec53faa27c52622c977dc33cbb40b4bc7636ed618b72894ddfb644ae852738b3b796bff85edfabf57ddfebde17bfd735020d36fd5e7365c6f2ea69695117cef26279b1bc585e2c2f16162396a38c2be83bd266b66c0f698cae5aec0daebe5459fb72be9c2efd3bba12fbf46f0816accb34ee5049514da15a822a089c4986a2756debe1dc5db445ab82a2ef37406eefa747a9e7eeded75aab075a6badf5eebdf75eef39dc75a04a496f8fb5962cbfbc35db789cc5e628de1cdc852a257d9f72de6bcff2bacfbbe35ed33e6f2d7f0c7b46f7dab3bacd25a58d16d3bdf731dac7b0bdf0852ec499d95c286a38333b87629df46737b52038d5929eef4b4b4e97967c892e2df9d2125d5af2a5a525bab4e44b4b4b4b7469c99796969696e8d2d212751acad8d3c37959439c994d43cc0214576dcb5fb8fc7cd1e608e79c93db3697d7d0570de44af3b72e2957f75a9105ebda4a6817b005945e73f4744559dc90ab09a06ec81afa262564cd7dd37d930c15746668f919502ecfd949bd4a14eaa50b18e32d608c8731126879975fa1e55ddedf254401120f5770f954888297777914a4bee56388f102a25aa72368fbe216bbdb1f15b630a7a076240c67792957545e4a7bc3822d9468ef4d4d4c327fe1d2e997d285495c6d31bb730245948fb862079b0ea884ae3517a9543402000034007316000020140e898462491084619cc9d80714000a65763e665c349b0885b124ca61180652902186184310008600830c8d0d19100079122883d1cc168113c1f2811d0aba3ca64d0a05d64e12bc6b1ff05d48a12af4ac3770637c94ca5f409c4f7e1e0d0f4ddab2f34ab934ece8daaf123991633243107a24e8d5103283d70615934a050d19ef7f1456024007870fee05c32abf4c92b30efa65baa3f7d24d78d0c483914c5b05a20d9d6bea3034b3309a0a96635c228dc522f7d097db05aa2d41349c30efa2cf27306c4655fd445ab220278c44ac44f0960f3b1b8a0e4ab6047129b00a227897aea47b25ad63b0281c16039ecf01cc8e42be403be0b075b31b580486ac8e2c2b272458067f7e56dbd5bfa08b236cbaf8c0c978d085580085b7477884c1fb9979ed8496e092d0b5ab60076b8a3f80a0b4ec30203d9c104f1d83d4bc1491b5ab0d905f289600f4c0a108e8e4fd18514720a7ef2941175c31729ff5f3673205b62bff41b7fe66fbb040818ab0045415067c14a09497146ead9a9d7849389db470ebe777402946760ecfe34840d134b5ac10e90ac4a1cf20e14677afec6061ea76787220737f2d2a1881e10e8837292153746a354fca49b421b9580410c0fbff8acc5f1103c1be9040f0abfb0dc3efea84fe523c8c918081de5e384f2701cde25c2b8039e50b04ac2070800961bf8ad5e9af83193adb5f20187a08de04153316b2284e6c250df28542a35debb0ffa248fca4f3a4d66f563049679fc18f2fd590c6558bfcbffb9ad38fb41e08513515126931a87ed5e7b640fc9e87735efb0bed388ca7de9540ba26fd47fb58bf5cef9916e9bc5f83bb9950c555943db92feb1230901e542f96bbcafc452377d25cdc74181b5e283e447a432826794932db45f564bed49319c1b8701d7a4b951925c50cb180466353dd4b0e25cbcac861b8f1fa211cbb74bd4b6f81021dc1d783b5a1dd16a1da081e1d076c6bd38a547a35acd5d5d62d35ada5ab01b60a5f53c6824e4175f874c0184076b5394823f5316b0513a887785e3058c2bf02f570d836c779ef01fe4aa1c2c24f43db54dc66580b817a30c2c78737ae310a8496a66a5e9093a5257d6644531d2009212beda2a83d162f073805eaa16ddfe404b555bacf83f50f02f540c989912fce49da0497e9c63abd26414263e4e7ed15c615a281188f41e77622fbb342cf5ca509d4c3adb7714025118dd138e711a887c22b4cfe4e8f9a3e0cde7ee17e21c7eabfc065458f280aba249c0cf030f7aeb598941d8a2818a9b7b883e71512d317724c869f5b74bb54564b402313cdc8d1b509d4435f6f82d98ecdd5a422e5c479c2640880b294832e9138c9ffcf1ee532e2b0d86abbf77b6cbb632f7dead12f500ff355cbf49f99d6e1ac2c902b730509da54c13abcb605c335f5e0169cf273175bba17dc51421b52c3b8e10ceece527dd74ce6a4a0dfeef91e9db57bdf8a2aeeff2bafdc62f20533b51c9eb33c14b9f8695454413ef514cac1554e2602fa16f3abc4c74532a39327815db9370f0d513ec6c532ef994edb55a5e8d0c2c4acefc8dcb7d7271ba47d3c53dc3c7336a51cc1e93aef2997947826008920af80f375720f866621fc1a338365ef22b2bd8b57c132f6643ca2efa64caf4d228970f63f676ad533fb5575e475f35e41a5bd1402c4704a31b2acdd84b52b31a2d9b3209c656f5fac7724f5b53c4fb0a35bccf531be0907fe882df53c6ed401e2d5e9a9b0e6b2c58390f0a04b98a18b5891abb35aa9bd3a2f7ac60a71abd6013baf52505c200819855daad35b7ecdab02fb46b64e725395ef9d56eed1592e89620e314206036957a6476891aa004de53f4dae8ac4439c4dab7526057fc06fd594717d41d466426c7ae9282a8423c1dc4952ebc34acce79383c34476ad2ae8d632aa931d013cb5db4b8268850d834b629c5dfd508adc129374c33067c150efa1548eb94924d89178b2ab223ddee67d66ea12301e9fda1e8adbab9b7dc09aa50d9d1c95588bd464725d7e38c2e0d07e6459456b1f88973d6c04b416def7d58386547f77d45d2e6591ba34a27297ce5daefcd09db7f32e62eb30ad39ed0ff53bd4a0ee1fed6c6e6008263c0be1effd0eb8da475190a07c3fe7412ce87c5b8882fbc8678442e5e22d5fcadbe6804177c0c1a3744d230489bb69e194d10f8633c3a50ad2b9a728da893bf5b701442a877f8ad459bf82e0d8916d4d0b3f781739052362412be06a040bc00cd7991e815e5a96394cae9553a65df7986a1615a4b371d92b508419b2bc8ae4e0f61dd5d34e775dc7289ae5ddd781236423593df887a7dbf9c685bcbacd6ebca822dda49db834d5a3b2d902a58750c4eb4a87533bbe9c85ff01303782d44d8f1976bcdddd406f9c71bc6218b833828058a7dfeaf96bbffd0b986c8012107603c0d53e070c1885c98b6520d5df0ffbd707bd324a3f6ab01131aa856eeb80b14be36518d9f0736578c0d1ac2c6809370c14948f056f4a865fcd75947aaa0fdc4b7c6bcbd258bf081f3ace41898e1a36ac532385498315033b7a01e6a951ed754b7296192e4abfc1ba81f4aa669c20c419326ea19130798c46183e98178ec57e1240c2afcbab2024dfc60ab52bae68e3f131b4996b8928606ad8091045cbbcc3e059e135dc704c3daf1631014825476071aa5dbde1dd4c267857ae112e5a8888095c26b3736d242963e4e082ab91e3a9c387730b4df08cbebddb0ef9f8acb00f77348d2217dd0caba0fa5ab7acfd6fd39674e26fb526550e55616beef0df75c6902a98ea6c9541ef3f9857002492e8a5ec61b1a286bc2914329cb0b97c3f219ea885271378fca8217f4eac9ee0587092560e3dd30b4f8abab175671a6e5c52d22a5eba90b92f2f77d1f00107c15a7ca57a8c344268c76346c8c27388637eb5d4f5a7f28401b38c29d3dddafa5efe603b2e7fc1ad38c6e866f185c485546f1489b0b0b5036cb47777b37e9bf0a3f068b21bfa12c4c4a1a6905370241f7538afe58ba19f406160bfbb81709d66270158c9c11cbec9b85494dd155a911ce0438a3200b614f49162ffbe1f6233f97557a0e1ec6e2470ae2c668719aad0505971501327a4603a7a09d14c379f8f28eab0b9b83731390b6e8fce4ffb3370da806e03704635b4ce59dcb9a89da783547609773e8c404f783f8e6da54b7be422176e2cbbb6f9341eaa2374262dd5d9fdf7c7a771a81be0fa1fcf409edbcc0774e4a8b3d8a0cbd6e6549c58d772c7c759db953e6b00206c6484b2bea2b4a2a10b73a8dcc8f7527588d5ef6f8d62d823dc5cd69e42fedaee4f843d4329374dafe114ba4969959ba8dc6b9e4cba00d15dd071c8aff4cb451da339720e07d10dcb4630b355a8400d97bd95706486b763684829d6d79ca2acb8e50dd8b5eea4e58f08eaa33105d29df31ce0523b10b0ad7cf619f034e16460daf0a140c93cb9cb64edf7880786651b1edbd96f628eb9d9a482d3bd5b5fa010e2d8819e965b8a5b5cc0523cbdd8d8d8b0e04a5a2ae7efcc97c685df1610276c6ddc9e81407f4743dd0b3936206ba1fc9050f1afc2a28b2515aceba4059840c8a5496f615fe0a63d4a9a4ca4e92cf7e36552b61e43e6dce4d68fb4f5f840adaaa8c644d281fbabd42df6b76629d56ae6f6f0b773032dc63cbbc625dd47c8086afbd45d9c981cc9ce4da22570ecbd9354b973ead1177a8d5a391015aa3837c78285b0afd583250ff1c5922ed42996f90ec6b2cc7233a16596bcd65a6600c81cc3c88558eb3b3a4527f060593439408316a4ceeef0a8eaa7db6468f33f7bc9b4987b38fa6cd560fd200b4f68a1f2334130a888690e7ae32e663f7ee6277bf1922c65a1546a6d9d331b0501edd13f4ce7c1f751d500b91f69c10cc4f96cc89d5a549b4d2ac8e17bec99a622aa2e73db4f623627224d89b79b1f57fc55e81953c888d6789a9cd6d10ca4737f56bdecd1628ae6b381105878d125a5003e936efda6141376b4636df0694a97b03850bf31579d74d86c6662d8990eab7172da27da981af067533a4023d0e9c8e8b106cc7d7795aa8c8b3f0f1b018ce429cc23745439a62950ef55dce8c078b8ad1f074f0c346cd529abe44cf2a7630c8c4711c48ab8699a21266882cd5f88b6dcd1287e49d3966619235ddc084b9a0114d49148b31575eb76ba175b4cd37b78728d3aa501e182e202313aa96ea2399abc95bde7b5578a9bdc8b7db2309021b50dbd8ac099303849af2fae2df8b2e4f7d0a6f886ae88e8015042fa31221d4a5108b02bf6b565987234b608222907292c1bc241bf36265046e0f246406eb3e153eb0c732e0a0545ff58d11f14a5145470eeb8f89ae67127696308ebc183120a2670afa8e839a1bf6f2d24306038917acbc8fd26b720810121b11b84f66d921b26c44f6056afb02ffc2f0c732f52801627709f56269e1c7b574976bbcaaf026012d8e2217d2124a14a6640df3fec18d91b782dfff85d862cef10c1ec478a60f9fae3e1aed6537ad67014743342369f229ef68fef76e9360c8c08e7585f9cdcc0e90570b828f4023d66d7492667408052a06ade0ca2b732745004b3511406f22d57410a478ed89b92c1eb1ca388e0d514236328b1dcc75aa4aa0657805b413c21d31a10d4cf7e42d785dbe26112bcac0b6cc641257a5f89041ee6c705a62ad9cd122f859768b77574a31cc4b106aefba3993777af58e41f1af8d0787741b82f4d90f1c691a2970bb6c1c21c446fb4d5f741a769ce116ca4c0cba72dab87a70e2cb3dc8f21c064b3f06c5a059a0e4281aeef37ab607e351e3116e42bbbbc0ca783e860dc740e54b7b62c8db156f1b54d9f92d85a6d0b4e8c7d1669ef9bc0d8a0c82da2b969a113d6c2cc32394b9772b6bba986c43382a8244823a5dfac2dd2a5f0bba1f7273214b33c1f145120da2cac0677c395401bc4a4eb3ef225ce624d61f3cc679e497a55b96aef78ecaa1688632ed599e06b5845ae786c05bbaa61ec164c75d438a59538fd74d5ca5fd057b1b2cb00632584a569e14da59df4c2ded0e968e5b95bb4746916a359b99dcc815a981081fade4aa9729b97474bee941c18c34f153a4b889d5e3080900f02dc63d511e4cb1f4f16f74d7a5a61d03f8caf48903ff8fde1b21239f4f24c797ef421961f505c2ffa0fd6094cb9837dcc474fb568c68d118cc845303009229858702f5c9d2f88881d40f8e130c070ddf47a485195c1cfb786c115bfef6da4742d22267386cada57f0226904c184886ff7d6b0db32f4f4567ff4dc9d29e34e256bc8733236f473f391ff8e3d7b7adbbf24b07407dc7f88727f20719e0fd909faa08fe829f0e9c68a98943a3e3bc3f3a2310f0aa4ea08f9c92ac406d71a5cc57b35d7fc3c6894ed34f791fe0bdfe8d8dc4b67f92903aea9956548bae2ec8045262dfb1514ff59e9467a2d14d389b01491bedc1230f10adc801efc6f836122f501f30d7751c588ffe6c063358206436792d92e3b1cb478441a743cfc0d9aea891de00c341d0a8cf1efb47568e1b03688aa8b78c480169f27fafd940f655da01d0ceaf4436308209a2b6bff9f19686d5324e274c64ab8462916a0b26396b322d57e9b1853874ebafb85c5229bdad85a7d5efd92b8eb441a39cc8ea3c35341c501890cf349a93e8014aa5bbc4d130a7531cc5fdc922a175e6861b765bf77a59e5a638d97f0bdf1dffe14a5c68c18b1f01ab27dd66ae405acbf2d08b345ab58bdb23a61fc66120b0efe3156b297d04e9f4f940468db25a8d3f9dce088e6a22740e32050ea0598ac12ac074f0db0f7d34709fb85a58791b4dd682d9ab38e1e4cba52e60e9d2057892e0995f08bc3504281c5435daf794bfcf0102040de620c1ed220aea8272644f977080438bc8cf1fee272ec5d3c63fd19242cf5d3eaef2d921846a0518c870490b6802e6bdd8a665dc34b5e4601e54fb8d859084dd04b2a85db284aec0695847ddd321ebed3aa5017641b721c908653a138f6f82585b984faff85b04cea61c340ae632d0bb6bafdc5f3d9f5200fc0b10810e496c46c41b1f3e36331426b3727151a91ed440755d320b46aadd203aac70d82bb7853f0f9ce5d8cc04b93bc70e3b9683bffd35662f63ab6f9bf6be45c1852e68e5d14828d9b135ec3c293768e80f3dc0cd7b04cf91d00d0eb313436cc17dd84ab513fb84bb471da971dd8d725f93eb26a7188be1601f0b47348f2e55da74cfb87a4223d11e8ae3685faf39dfc82f318e4719e7a25ae9f1317fcceb6da6794daa5bcf58070349207c7a9b0dc09c06814a7fa1db962a00b344b8e287d0880c8a953b472bdb2f354126348c31674f20ed0d29a5c9a194c0c10f7eff17964132016fc23619ee2743736d029b72cd116241bf6fb16323e00e2c84a2ac8a5d4815de67040c587d3031183cb046ecb554341c65004889545184a1cbceedf3b00e9c312948b518e640658553a05e9afb3029de76aea2ff1104782c6d47667876a4a1fb053f8f4424b8960f7c332424eb52f724d849c00c88bc9350c817ac0298246ebc44511033e62b3e15b6a1d862dc7365bb4e08af6d7b82f0154b09e9f48326a7b880edce4c12937174fffcf3d880ea80ad6c8b51d25896831fc830e347bbebda15f1cb2195f24d2ae8487c7f65516228c0842f8774e43ebca5f8300e0d111c701e140198191eba88414b8fec58470180b1a3a06a7af406807657d2b0c8b4c0a86c58652e930d1015830688993fe6835d669475008961faeb1fdaa477f70a285d6c2d20dcc571c7823a240cd4bd0a0a0ad9c57c480be56bcc0e7304ec08d37c28a0723bd3d5dfec6b81105261f68d2ca7690867303b091a92cef1a9989109f5466e5dea227e5cc68b5787d31cf9708caf9f1f27793359ca58cd2de18a61c39b9224ebece540b7a91d1883308f5160f66031a6a9595c0176c5bc27a445584fc0f87015e1b8c43ec87aefc1f30e7dc9b4a64b63782e48b241c4bef5598bc21c448095ef6a95e6c921c3da4c3a28c1dd452d5feeb48d284dd8456b183de0ad9238bd55af2fa3424618bd2591a41dde6d1c7449fb215ceb41221d73fe423ecef61805692599d77624c98ffe1a9ac13b16f3251702995126ec46ef907817816e22a1d855cfca565acc3e6b42a925edab29328a34903fb36c26d5bbb8b509825dcad9fea6916993dca15bda8145c4cb94a9d98c7ca0e04ac4377ac31d86835a74b50465ded0835e8bd595128fc0571b0d88a562bb99e8f0cc6f5279d4aa205e54595c73050ddaf29000385b24e646e06e8b95828b3fb8ed201ff164295e32f0b71a66d0454964632fa324092c6120544a5817aaa194a2fbe9328a0287131d6d1d4a5e77f527865264f87067b8b9e7af83e69bdd078c321d21d262a22893e46f1d403f0e8f47c136d9c68ba2d24844e74443a65580e82634e5aa435012a9959488c06d08d6b7d2104ff6ae41d8b58513eff46c5dfad314389bd6b1db9132b48ce2dfb43001d9d227c236d541f567066d21b8b049b91660cb83691bf76f79845e5d092742e92a902a79136bc87419e505e2e0d167d26a05ef4f4f7081c0b68730774a6ba758ea0212ecfb90c597797ea63b5abefb489fe9f12289f44d7bd50872304485099b352ee1edf1d5016d7867fd4e4ef5116f41825404908706b8ba63e121ad3379e1c4ad648295d6eba316f16ed7689943f052436dbeea661af23768b2052ecd042a2d3dd52fc2279fbbb883905f580262f882e714785e3ab174e8bd0c03266ecad24fc8bf881f4717f77968041426b349ab739e2e18ea09e74c666d26257468bc931ea000160d35578539558e62745356ab6ed4b9d41157fc1cfcc07ea72542940b528e5c93cbd38722ebf1a2d1e797828b49f75b04da903a9fa0a6ac01c9a4dd699bd71a418e55d1de86baede78741928155fd52a0e69b4d9bc904419531e56ce2038d60f145da63a80cde2fd222bbe8ba80193316f5d06b976466408ee4c5c4bfb7cbbcd59f0e1a15088dc29453b16754369c558f55a84e30c159c1221d3a5aa79db93c74535ba301bd666177b3db92a62d148e7049499dec56279497e4f81d9f3e0ca72f84e3c6294ee258e86e6b6009eac09050148310af583b26e1a2a3f8b2a276bbb0448359e4df1c2eee57e8348fe09bc33780ee0003291c6c45a7b68a941763a1acf014d00523ef4c76fc9d970327782fab25c81e9e1db91922721da59f7c23bb841fff8b96684391f5c43cdef3675f359151ddc2b633a74fce09b58ab78ada28d0ff216d8a0a76ee9ea7d84e9b177612a52dc79a82131b5619e712b54bc6fa8f7018c8b232ad9194cf3799a8350ad5e6a9bfdc2fa4e17656dc2d697bc631de0021afb9626c764aee77eea14ccfc53669200dc1b1b478a0e5df9b62be09999635db2d464cd8a6c661e697cb3a920973ae7e146f295a27e31863021f3ff140d961a7d3d2c7c2e27808f70c85002c39bdbfe91b385d9abd43550fb221fa9b75f87dce7931c35dc2a9f49c66d42050a2bb3ed324644ab9dcb7f48420223c4197260e7c0dc1b8c1f3ca92d001dc0e3f41463a6ed1ed62ded950bd6a65b1dc72ddc46a7696378bc7572346bc7797b13970993982d27af32fd9bf613a9cb5ddb644990f678ac0d58ec6d7504f435deb96da8f75419681753baa5fc238f8a91f1dc402688ba687b2f890e890a00e1e8e9a2c02f30409ca3090b1e9ea2d0b46a1c07f4d6183b24b62b6edba7b6c0e93806f8e689782260eeb31d803864f566905a5a1d2c64b6ec6811a00ccafbc5aa328c8a367125a237dcff751b9dee74a7e50b2cf183e617d8c77525d3d9817abcda5a38ee8101f04406bc7a67ef792b8541ad5d7405d12398a697c25e34ae591a5924b6f19061bcde1adef1e4ac04d6ac0b3d509cdefed8c72bad082422ce72a78417be58a9bdebb28d77711bd9a1c652a9d3d98b5085b4afe8b20e0754aa85d6a4042a83fa1cd27e2547d2d337b28d8038856ef60c83395b2b96e0fc9a9d39265de270b2ce592259855e49e3fbde7d46f6c87963b836181079aa6d9a2e8c0e9ff36e691c71090fb6e3dbd9874ac00c1bf89c19b7c0d6c0143550452d9debf4c30dd9220cbfe41fdb15b1de5eabb9b5f43e4155dd9931020128d2bceffb47ad6d600e0771f858878266709c24cf858b346e5c33c3d59b27d5b66c9d2cda994ca408be239b9726a08208c177b24b49651325aeb87d279690d8abc4c79325a37c1d095216ecf9855f971e8e60a0b5097d2ce80300cfd8b5e08550f45434ad86c42a702e423b30a511508c14200df59fffd042f0cab565379af6d62a45ad8d754533c3aa82fb83d654aa17a653c34fe576cb1252ef4d9a2ba2facb5064d8adbebc8d53f60b33b52e25d89616d270d78b76b7ac7a3f58bd8e85b7d43286c13021199431cb613871d10381820511f59ef8b888ca3ef58ea4ae203daf52f2017caace1a57e2a7f358cee6ad03d96fe4641952db775a8216c4ac5d10abba8650ec0316c7a273ef73fb1fe0e069eef434a8f0d3b9163dd35d88437d48ac32b0e1c161c663c2e297ec1890f8ef1f863263ddf73db0c13b4a79d3bcf59fb7efc97b5a1d9cff03fd7dfb822f71b07095a20a8fb762c3108f0743e982dc01248b99de12dd2b31ff3b9d14f6c8e8b3ae5222ab3bb27b80c306a8eaf506c6c14cf11fd4e8a7ccb1e017f3a62802d543b0e379758559381feb7e1f4b6ad9c13350a958f2fa62115e1a1c50393c308bf8f7e1c32c7b28681dff98cd3f2941dffb802b2e6081265bc99cfa8664a1ec89e615725b00f5175f2d38b036c48be76ae4c4c2692d216b839b3e30c3a3236f337030c3146cfa9f6df2208f3bfd7c50f34d2d80e53a35e373bdf112df7565f1cd84097f167c00bce68faed596259d94acf968f6d8e117afb3257416d3cfb004f1dcccdd19300431c94e2ced40d52cd72a88ba71595690f8e38a64bcb919ddd7ef9e1eacd3663f4bbecc9a63b51fed30f61235556a24360917c72e05dea403347e15654d875eeb2cc3c188259efe51e302fe17b981a1a8f53f62bd386ef01ff3e190594289ef279b1b27926bb4eb6d87e001d39adb5899196e5b242354035cc69ef7b44234d767e354ce87c85e5763bc4eeff354848231221fa3b735b2f3422d1ce59dddc4cd007aa416964bcdd29c2360702d1f295d4bb4294d61b99650f0e551e7521ba4677e19e916ac1de401b7ce1af09b490428a5c0977fb406d06e0d05282aeb64b12f6ba94318e2c23eaebdb0c5547084b4a3f9c672d0b135904b08960c8594eefb3335d425545ebe24e5194108a97ba7d248cbc1ceea856a687e66c5ded328c201925a78343b1f1a2b6745772fc66c14e6b64dc309d1f5f49b394202c65e39e04e5cdd45655cf4c577090cb8fe7985233eebeda51e36807bfe44ad14dbe1f147eb5997bf7b61a74589916f76ab193ad689345b8f73922da6f9108249d4b096431f1adf635619f8107b30a5d40cbafa9e6770c24e3d08407b8867c5c063e58816bbbfbfff9dba50a8c6b4a44c7303dcd8836b3dc7ec020f4ec0b3f5b58d2e159be854f3c2c80e6a651e92b2b9dfb2b6ebeaa29270d6a0978e6aa20c4ebbc4cb62c05e36080718229c3ad4b8031edf41d20da4a29a6e94d8a4a1ce0817c4f1d153a003886400fb1f07ee2d486849399e0bfb707920d6138066d68ec85a25b9d6d4544e33d87ba223ce194b955061c2bf17d44b1e7523e9d12fa123d6643af406ad6446a6aae1fdb72b02d7cb9ec9f66ae23cdb36c7ed9a043080e2941336acf7a4fe0de267fd09ef80b26792e58702f4b28052cfc877e4b943634a169f7332efe433da934b10019f21a6a54f3420093fb80fc1620d87e75ef654e0285d8d6ceea5b032fde76b93a9e0e32afdc18e48669f7057241c40c3d625752a2f111a13ef67eac8d78bb8f6840dcd49d7950a24a9f78dc11ac5b514f0539cd72fd045ac6c0303aff8b2cdb74592a8440e457c6852cae90d3cef7e119523233b4f6d27a8d76522294b38536946b72c895752231125e2fedff8aa578e8fd10df527fb45e68db5f1983818621cc9c244b4514bb40d5ca2d40826766632b1874d6e83929109f6b3a10452ed9280b112121c667a840259cfa2755af1218b39acadd63a2d7cc8662e502668cbb3ed42a06b2a269ad0108c7cefd415299b944e94f1d403db3431e11a89b08324738c6043dec604e0af294a652264bf88905b4909c6d941afb17df8cb799f11573ca743beeda8963f2ebad8fde45f967dc61fa334f8c71faadb8346c9d2a5819c45e0c51a1edb4e1e1422e7262c138504afe0bdf27c9a3866989bd38858c933d8989c81585b78beee04ce064f8b1b82bfc0d930da5f93acf3ebed80195b7af40493827232f6216beaaa7e31090fc0988134308611198c317024bd5cb07a53180cb0612a2b020bcb656a198a2b3b6f8bd27d76baddc49a24177660f7ba899dc878a08351bb7bc114a397cc50b6e8468197193312655c82e20137720e8ece66a5a1ed48eea02f22e5814ed74b5ec3a9578cade144e309fc48be7f8a60270d5818e9b4f793952c3d89df0c3967741b8dd4ec2dad1c71a41dd16f097ac6bdece69e40471ccc2738c1866ca8eaba74d8fd46e9dd7141682dc382df739528f2e2c3386d9f25643731a206a07e91e3281682c41a77175e8788fb831fa7cc5bcc8fe258b36c91dc3b9ec85a376ae0a93a0d4181dce148b6b63a184f6046153e451b711718b1cdbd37c2b805ba97a463dc023cf8151a7f818e66e87ee515d7c3679662dc3ae37e781b6bee97a3befe2026bd1336d3460e36bd72f240874493c747929358bfd5baad29b43da9e75aa30453e286b2d31cc136f01e4b5cb68d525cd053a14f8ad7ed56848521fe9bb6f5a1af8035f5a1a1d21beefec1cf797352355620d6faf64fa3babe1fcc09251b6a4404e2791ed9f09501fc10b56dd6e61c78c42d165f81be4861296bf36d2379438ae3f7a6645ffd1013df0a9abab41011b26ead993527e31e352a1d3ae018d2dca24505b508f407f35f5eae3f58c9ca3b08d235dc4229863bc94d57e1ea0719cb293da61cabe3d71878b2a21425ee18ffeb9aaacb918951188a754cd5549f636886c449948931b7e9d66870244906460ebde6a9ba8ca8a029119a0a0bbd2d9ec6452345a75eb70be1de31d0b5377c7286254770883eb54c0b8e3f48c4574806c9cdd370564822206282995f51923b39ca6a62a553782fbf8284e07c6e582c4da358f5e9c07da6405b9946a6f294d537c3fd9b5889a18a48a44f3e173974c25184d5e5114944dbf64819f178cb644c2239acc17f5c4fbb15c28a0e3090016974da97eb6a87ad26d4a63c52b8ade782d667b15c8fa288274deea9b9f6287f3dee429ee5a45e2a694456727c2148b508add1b68639bb477428193c9672b290084354d305b91a5953af8c4b9a5e4f74907869505274db9bb80e4440d1a2d032a3c33b060d1016ed2264791a5bc8417d7ac2c36a6956b25934911c4c9a1d0576d8f841805a41478b1652d326501190479b3cdbecaf35d7f19a3620feecd378a6caff1dc7bef0383fd543afe095e857d6f097bacfad6d0e874048a1c448ab9e9c551e44281e70e5e02baa2a81f0bd4245c700eb04325c2769db68c6218ead59611c927deea7988a0c51adc8cd4543dfa08b107da372c13ebc17ad535d9c8942ef0f47515467c69a1bbc229bffbbfacfaa62ed61659374fcdb2949b7f791dc39ebbe45916885bdaffc4e4ac9984643e589ba06cf9e26d39aa5f457b9cc5bc1c081b6ce8ecbb4785311c59d2b17c80cf04d5ca93d0781810e79b24c2499951f309353b091b4c6478486654a1df52e83e6283f1fe0014c42c8bb12e8a517f82d71d7fe3e977135f5bb9010e0fd831be2036924a2894d8c8fd2f05d8008d2619a27fa1ee738cf7c999cdfb8e3d26ff4232fdced97d73023038ca9f711363048a19d7a73d4e279dbbc7fca890ffd3f38abf0ea827e2ecc158f552ef80f83ec116d74432975a2de61746e93fdf0ff6703128eaf10964aded6ce293320142b342da80c43e0ffc25505ebf6ec17562aa0ce83773649b4a0d0856dcc6742a8d5ca11db971b7a6f16ff0c56ddfb6b276bbca3974c7cd54b019ed3449c42cc0a894ac40a5ef01263c6d35cf92545664d6cc33c5479a42a62c1a67ce1819850cfcf10c7fcd049372f62c15274c1b5cb700d2756b1664541b40b839fab1725152165eab9de38dbf0a6421eb95f87f48f4034f8c7fe444d9c42536e1873b40d68dab43858e5cba2c02cf34db63a77a04b92415475af6e60822539e59a9308ad31b64b689b2c2465f1bdf1cdc49a25e9f09df815f436a5263f3b27cdfd43ecfb5daa1ce2d58a890fea8ecb0a03f4a4d75e003a8e29877f3e3d02a743d6d78241534da51c481f98355288517a1e35495753b0d59201431cf300a626467c3ebdc5e7eb657cbeffc7e7f38f7cbe921fc77f10f84e04c476a390de029854d186ea137fddb46e5e89f43bf80dede165a9be999463c78d7aa6a924956c9b18b46ae387177030eccfa5ca9fcc299ed834f180b92c246f03864b932f15cdfc54a27110e41baa7e092f82dea80d3d3e24f561bcc2bae3cc2538220c89489ce0e94783749d30c95276ec3d34620087ad15dae4211137ccef9d3b935a8f6ac3833c1b0564fd1481047813faf2d932389f2d54c0a8461c8a69457bc499aaff45657cd802f196ef8376d7a400655f341b81e92aa8b44316e4c1987721eefd259028c812de0146216012adfdecd697e260865445de6732cd147bfe372f9e313a1ae9bd938d28af7d1071a3d543fff29eac9a57d8bd999d6d6573fc765a03279692a2f00ee68cdb84d26a177c204325e5dff61f5c46615ca99e01dc6e406ce9be3ea1fb2dc11c758499edd4b8cd7324363c7e98a37c8a17408b9f01749f04d24424d658f2d96fe4de3f2ea91df980ea0642060b939002572aa0a5caff89198db8eee6567d5169f6aae46db10395fca5fef067345ae7028c64c8f515cd621f251bd6ca9c27228f82f42b7cff42495269cf20b426445a9005b1483ae66ce14bfe0e61528e905cfa15d1e4548701d96e1829aafa52f886864aaba6dd1311ad59a52192f2c73bbed903af12647f85173ce0829aec12d035617daa256df4b9217f62c1afc038560c53679a01516923c6e5e9e3d2de372a43186876f1460c937236c6e171b4775de3ad5397f73fdc42fbd2e08ccae92f0bcc7356baa1074a8e27e18c03cdfa8d79460397cc8bbbb1da93f21662a91d8f810f6a69877c88bf3fc6fd5a73d73687eb585463e970bb9ed25bf06e53cb2fc81b3a981a27db3b005216b77671ee702819d0d7c80bf2bcb4989868a80b746cb274b0213ce7d33955f13419aca05be1adfd05df12e84a792bcd5f6c38476030f9c2c0384ea5a49f17ace0d3c3a8b69a88f7a44f1aeaf72050957d3542379ed589c90eabf897820f60ae46c66ada9c79828f1be9dd25fc41c4db9a04a0a562fbbdecb2f15ba7bff9c0c80b1a4f9f7c8ddc2edc670a93065c45ad9b8a78a2dc2d9939e995c9a1a2165ac733c4605d55acf9eba2937ca2cb0722c3eb3cda3b5d3302db3fe015895112b273d95d98e01fc09f96b8be8e4de60e830a02d352baef6fd9b9262dbc6e63fa3e22fd376d1d9622b59e0615e6833454c417f4c4627bea9663544df69b8562e818052dc66742d8a93d18ed436cf86d46f6fe234e88fde268dcd86121add067a6cbb6f6d691442b329fa69e0cc4d4b6090a57dfca59f3437d9cea51824f38e3c628beb2634d69b8b13814ddc0b77f00a344cdda0c93492dce092dc1b7d5bd5859a37d13a9865bfd83506f9a1efbfe7813c4f036baab5a4942c124e6788855e70022556e13b71c5dec3ef2c3f7525b4ade310f952467ab18e8ae43a1769a3543c384f05846eafde047f411f4d70764d8c99c2b44ad8a83ed622a704932e18c1f862ac56e8b445e74547e545b6f8f041582a01ef1edd8b2fe708bd4250309d0c04a85745b91e15c6e6b388e759b84f252cdfe3224579d38b7013178a712ac1eb1c89f54757cc429477d93f59a5e2136b75900b0a5d2a327edadab4385985d7d7efa2fac34dd6d2236e501edd8c74a6f2d816adbadf48b2b2a94dc7d3072613dd8a4c379c834fb6f1f9fe9882e79440eb34c89967b238dd81f3bb2a9f57e6427e243498ad78191d9843e559a0e9e373b949f09ce10a43e99cea442099a853e4d4496133545cd596d527e580b74ab352f60301e330dd5150a1b74f6d5e2cc736d4544ce6a160dedbf18c2dcdaf54695f14ecb3edca5ff9f19929d6278b90d2c3471a3a5baa80e72bf8aea2022c53a578da60df5355b00989a52ecc18ce246c203561c561e5a969f7e35e809a403713deb9a9cd25b6cf6892eccc4d662645ef96d6c3935aa063afa3d11675bba0db21d82166dac8e62e38b3cd8c4ca45a9eab41e58c367f5a1c1568473db72a66071c2d30a82092802c509bc4cba46cb2b2b26f0ef23d6e043e0e61e5e2b33ddad4bdef5d12308fae346ac7013cf17ad5dcc5dd563959bb73b37e88019f2b9a260e1baa4611bd3d8af1616edbc2988697c6a94866da33af0fea44b2591b50c0b5df42fa3d9c2b603423b37e62572135c16b3011724003cb7ebf70e6abb7f7a654f71096754b7ddbf15e60e1c9df406808e9b652787466edf5270b5a7d67205040a89fb4abd567d8fe1f04c36b00c6dba9a9fc430f2051c177f0eaa558c732ce8f8b7b4a81bc6508ccc6ec324fa29356fe4cfed07ed2b2569c9bc7f98141013d1a89f01d0136065de8f1a6a6ceb53a102a4bc8841239c70d7de4fa1d1a6ed2395d821fbc0bd39db7e1f155c2fed0a364403728e2c46750c404cf019fb418f6944c7bbcab90c38c7227202fb13cf87bd1a78acf019c09ed7de0a2da3579f1f735073e938aed5164ee202656c191679a7f37d205f594869d84989b2ffeb0791143f56c3b358df2d904eef892fc10ba63204ac63e44ab241b3bdd4163a3ddd5e9f24e24c7ad0170a2e490bfe6a4f6a80adc00115ee0f0d7bd7156e5a2562e32c7ebdd40d77a6ac133464949076e74793c4b8e6d7f7f151e02746e87b12554cc032d1eb4025826db91044070a13fb2e2f33cad45581383e4bd2bc423126ab28ed9126958dc5fda4940d5541bf506f0328ae9a0991822d0d20148ddecb7ded3f165a858c94461975469760bb5db6f7ddae16dea70ccb5532cb64583d604a2511ecf193ca669f3d22dfb859a81b171ee78e80c24db39e3d7967b3d4ffe8d1dfb1e84feeac7fe43c86bd0498ae05210bb39dd3bcf5b9c821c2864ea6532849b5b2ad47c46b0d693d9fc0302fbb7795d02e61421cd8ebdead1004c7b2841ab9fcad826e3527f1cb505ef44a81547eda38a94f1e0e78d848130e1bcca3d7ef8742be65a06e80eeeb659903a209906e1c5ab3389a95eade620f34a516f799ebb0a717c6db0b118a55585f4a1eed1fbead2d5221b1f425619ec048570fe02131211a678d37eb674148ce566244dbc389698754e50970f78a41a000bacfc536e6b6877a132546675e6c38f61f5d74d346b39d709efe48da4f9ed32d9d4618e6332fab4914c90896caf1628a4fdbcbb8cd47e04d8be9eb157495f319e6df1903602edc0cdadaaede08af2a61c65a6a5174c32d56c3d193e7d4868bd3f3608804d22a928c0fcfa9e9d98bafd8e7255ea942f033426c2e1575cf36d8ce57e922cf14fc95fd57e6997ed6b682a81c22347b02ab131592944a075510a09dae20e53fb59d8592add93b656642bf1552219fb22af19e363dd8e4516d5bab8942bac7c3e1459ba8a170d674a4830a8cb2a04342e144b244680a1177e6f49cd61f84d2a44a3e61766cf09ceddcf94ffee655779096935f290ccde5727ab4dcc36f38aa09317231cb1b86be47aca77c2fd7a647236bb5875f2461eae9daab5513e646d9737d985d696becafc86bda5d7d4272b33eb0b209d0c00b36bbdb87aedd92bc230d8da6ecb29d438913542b950f4dd7c1a0b499e98309405e62ecd7ae02268f6a5ec5cdd9e6fe19a9a1fcd9ae7f2ec1095dfbaf86f15378392ff084758a7e50a6273ea6d9228c82309e9b76d2235fda40f58c32dd3c47f057880931ecdd0c4d202758ff1fc2413ca5cb78ce0f5cc06cec8872fa605dcb04e3eb46197f7965d0072e016ee1378f20f3e366da74e21550378d132b197a822fd8d49d40392540db25e9749969b20076072149f3b95d97811b708606e7d838ad8a3db3653465fc8ac672fdd5ca403f3c9add82ba6d6b017aec5538ddd464e744e06baa3b63a6538c532fc74247368ed59d14d904643019f6a58ffa22366d151e61e57619cd70325505adba968124c1f71a00e44d17eb529f4033f085415c6c9f344b2e15d7958aba575d69f00103edf68bd8ac7b9f402e007981a730f016a04f3fcea6a615d5047dd089554acb70e3a233303378f400d68da91373c0f52d33e70171192c00cc8a0eec790183db6405b75f5cf32d5ef079c902ab2cad3a507618e8233a8462dc5b87ae66083bb50e0e7f87d8d0c9e8d58325c3f6c3dbfb2f4b1251ed801cac0e1a434a5bffe345a01ca8a90b1f54a6d25eb0147373437749cca8d942bbd0f265d4f51fc44378b13e999304647e4decb4319e38ac6cfc9c36f7f7a5847c284e6ec556486ef3caec93ed1bef6654c1e4b187bce26a17e55ac136b709442660bcd52ae6ef767acd66610de1d5b2b151c24b6ebf4512b99abf49a2bf244eca89db10e80f89c1d5c9db04c153c25432bf99a09f4ac88509b729da83a4b58735360d5565440bd09c32b136333596cc09da36b0bb72618e4a04826647643da3eef72e8b8053c07880a2e8169a5091b2641681fc35a87c4d49f052f81bd6a42ab92878b5e142048415cc6f2fd05f257617736e4db4078978657ed3887c92c854276e0de80f897975fe467d9c7a812ab94f5b27aa06ccbdb375aa92fbb475509d7bb3779c6a2e35f7c1d671d56029e72cf5549dcdcbd92242d4041c5bf995c4bc8dec1ad80d6eb15cc6b4ec34a7efa2f78d8a73b1e27a52ff22af9cd7e6f919a67f2df63238e91664faba8be786fd9d9b4ead36163778d550b346235fdf296c63af2bf673aaa910ad0551ee4f374f596a2876e85684ab0d5bb3db47287a848c23d91fc0f34252878e9948e5a6d054149cfa0e96dc2c33fadc0f6f55604ba2591aa79cd4d61eb1f1cf58e559592e5454d41a80d3f0726a9eece4ae0dd4db6d6ec8926de7004ba1a85a449c348c55803eb5cc43f52af94fccde6cc069f56bbb7b4c26795c47f2c5091d96698ab9e2aaf1932c47f39f9398136fddb179c3926e55b8a44ca472b676c639c2bb4e6b2ac5d98ac40a3d840002acfb0b206387ca1ed10b3d40c87667870040296383aa6e8110932140cc727f29b2a2e9636aae781f88d8871e509f0552cf8babfccdb11f8220e067542509e4aa3c914982057f9f358d516b60ad442152469cf007b5b763c6df1eeb0bec24ba215dbee21179a9094a39433d7f524eaf89932772a9ad30eecee03283e63a2cf63da91ee409fb5b9a3a3f189d13ad8c4a892be08f979f20308d1071693ce1de8ce6c338cd91384bea30c80ce7d2fa8edac974e1059248b424482cb508ab36505643ee31bc102f2a7b625b8ec07c0d9c9230a1b2e006ab15b9c66c179eada7779384b56198428457227c74cad6e804e26b1ce193bc6ed68346025fd4ab99ef00584fd865f580c55b94304c4a34775e0b4573a0f0b60a03fbf7226bfc358d4e38db73ba58319bfa011e7525f2f821107ce8551f0f1d36f47f9000f30f49502f8df888c059eecebd80b7dc0be8867b006ec03e8f7ef3ee93a6a39e4b618f78cc4ec4993f4fc669c8d349775dc6746bdbef86a71f8b0eeb62350f2fc41a89a428ebae8baf010b506b2b432905d2ec1b7c9076f37674c565845bcee05cf208129228982ff26edd2804b41b5201132cf3943dacc1530d21df85de4b4bc6a1c95f83ad21e1a210505381a86c63252a70c5659b0116e70e46bfd9ec0d73986c5a3eb911283d8741823cc68e30387bd3510f931d52ce4208c7954075ec953ba09d772809a427d5e6415fd3a6df1a6814540918ac7ab8383bf9e1233f5ac00257c7817d548d53c041223e73dd24de9a5fa902508d7698cac321c1831adb3e5568caf5fe997dfb299c74804b9a3837150068cf232d6aa2393a8ccef88750f76d187fc8fb96bf9eb990b98949c50af03f8c030243afbd50052efc8f0c9a33fd5071c31345d268696561775224453d041e14793b5501cf18aa9d9eaa596e276dc5900d70b4e69a2e069f1797583f81a96238937e3187641602a50c29aed0d831dfa25838ca6551c32f5e00b379cd03296ab27c59c0568eb35ae596a09a5df82e8da7c019b3127aeb6585fb19d6599777072b6ed0870e22597b80be5fc8c6659b53821d120847a17a916e98ee4ada24067919d30f4675ccf1e20eb7ae7a7ce8bea5e8819b584ef504f87512163849391ea146c19f4c4a8f1a12cc865937d67608492d597e32b6d7422ff77a8d1a07c3ead294ae0f1eb13d4cd24e80163cb0cc914acd25c601260625981690ed07e46d01f130447fddaeebcf4a092c4ba8f051407d9f334fb9ceeecd52e2ef9cab00c868e2da5dcf855208f213f60a259909b83b29e1502b8874d0def0f42292465119e40d45dab18f86de583311f559631c299320bb85c8cad47357893d913aca57ca7782eac5da0d1e0019214ef1797205132b885211c94c94c324af325f66c2dd679d28a68358bbaa974fbb6726b54f019527089e71e15464e3a3f5fc23ba201a01fc0a03b725e5e0a12046db51af0e8d2acaa03fd07386e808f8ae7217ce332589d1ca7018d2f499a91fff4fd49740f4d2920998dd73468ed29826a0d628741960187ae858e56b5888d53480d7c8dd7f2ddff61602aeed014544c495863ea52b472cf80f29b1171e4df383a04ba8e3d3d531607738eb9f93adbebc380401b4a94cee025fb43babbab4e06271b0e87da621c21e186dfbead9372455586c2bba8c0f25742a5769ff95feb042f84fa82f2e27e4fec9c2a22467d72d2b348e6fc1f429dd3e5219a7ad6edd520b2db76a458bad5a6fb1f556dcaa675b6e5eb2ecaf0f373f101a250ca55ea54a7b432bfd6a17e47faa18b40b7552de0aa158ed9ea909b9ba3782f5838d456e5190ad2125bf8b88202bbf02c677ac84f7b7b73facb58a33511403bdc2bd60b3cb1c3a66770bdf598b8be592cea7aaf9793645cbadb56aade5165b6ba1e556ad68d95acbadb68efb56439f4267e7902c861584d4345d957fbd3c931d7f416757052c34a79f5ab9e1e2d6f1dbfe6da1f0f4b8b41781046d308c899f601664f4de887579cfeb2ce43737c73a16c85962c18644d16345d18aaf32a11dc0d2530a571a1f51ca4567f97fbf0c3b90587544293c938830b36832c86d3bc6f89bf2be71d524d3368a4ad006b0ce08766ec25851759dde5b21e9a69b70682fa9bb203ea9f2553aa77a60da9b2210b51136a3cecadef40ac066d0a4b8d9425b6f2e47bc495bb77ff0e3abb2f19e079853208e311fb7ed9004afdcd47bfc1b683a69bb4da6c03c56da75972c1f729a1ecb6cbc2ab25f1747d64c05386136c26e4d8bec2dcf76ada7f5ba385e4edce23c9dbcb39c11e46a6d4ab02c834b7a3d566ea9063e2995b0af924e67f8fcf2bcd5fa4eea6e8d27772a9d06771796bf7121ce932cc958852f5e74f90ee28baa08608be28b90f81bbc997a1c9906c3ea87750c6934d7faf58c0cec4120f4c1c664104e9dd496d41c4ff572f709eecb3a0101436e88ac8debc10483b83ac0ecfe35ca9776c0c83c5d8ba36bcf3daa61981ead577dd47d27b4dc3d94be8a700096c787587453e67429cbfdf79ae5c77fe888d61708f0a0a31411982263b30029732d071caa8b6f12dc8e8db264530acc9eb95a32341e15e7fb1ebf73165f2e33dad7545d2edce1f9344a0b1b4377b7d75cc908307384df542bf661f9ffa3428d556bce07cffe68290332314907d718f913d1cc250131b72842e943725e40a613a78fc8af63bb9867260136e12c5d0649f14dbaf618d9ca18107502f7cb41195960bd6d18854a50500970acb473131d3a5af04334978725e3fcc9e286a5d43de660870a21de8a2747ec65ad911b417c274a63376b535c51788bd77e38d1724b121304f8ec06ead6f86c64298970309f237b4682ff3bba507c0e8986821d5fa20111d75b2da69816043500a4ec94fa365bf55a33ddba2411c61fd15623c97fd3ffcf4c13e91b0ddee61ea1e208f66378e8ff2d0c85f7549caf3d1420bb388b884cb48bba61827306e52dfcb4887bcce15e08e7f863f8f332e9ed58295712fd2d8763f015a6270450f51d5576ba3226b4219aeb8cdc45fa56739a0c67cfed2dc194689e6839faf773bead594feab4d8edda0cd70493e7fca424dd6c5506e5c6a1e97e4b48390989cbeeb63d7122860b380c7b482e8f03e35905fff0feace9e22e7051451581afa4f31d094339ac6dd7aff5515fc8db3c970c0d070e861bc34cb60769034c72503a4d730c8103b0c7840582e926da86d23d71fb8b1c5d3b73432fbd5c3967ee673e8094f62ad854c691ed30ea9003198d6095b9430a363939b5ebd45b1533620de9d0a9f38e67d5b671921d24c5ef0f118eefefa5bf6735c8329b4f283f96f2b6067b32dadc64524da82eb080a166eaa8cb7439ea207b3ea6a01d98b44f179cd7c75f5d1cbd50903bc0f44e2c9d9f64dd78ea4c7cb0010de26bde9eae3e7b4d48812afcbc35760caeca3a4b348710113d2fa6cff90883ec82ad8a0a68c2f365593213fba0d9880e6e603ac9c93292f5342a6d2e8ae9dbd219ad916235b9c265fff424f88ed6a417361eac25153160e8db3647db285fe141344735f005eeba12050fb87ec0d39ac8c5b0cfa451db483d977319cb0c14a5f9d1a0b193d36f16dfad9704d225f23ae4f879d1a43817457811f7b09b4e9a25967c9b3bdec9a4af3450e1c7e40757a07b00eb46fd1ec38b3a710dc8b47dbfdbcfc150d57752dcf3b7051b1e5cbc2a69d096bfb7ae4a4c87aa656e854690b39f265c51d0f51249753b311810d00f59425a9a4c7dca77796ac713dc816df98368d4f259310a85bda719595baea8ab70a3f0d45adf6f7761a0ed19d9d614e51d473f12e998c95193faa035d9bbd62a101524c478b304b2ed8c92f0dd7d5dbb02733462a4c85fe73cecb269581c17947e996a9ca06a2859558a354a8d5ee7f5d0a2e0939eb6e0705e64c90301dc57040e24c4ff0d581050e76c739b4e17df07c77d7d078e9d16f17b98d61d28a2ca35a72beea0b734daed3ebdd9a9cf8e04ee9a688d1b3df4c3d41e762a6e2f1afaa599d383570d366e3fc40b7a99efc8224dbf1a9009b25e5a5ab2a594524a19540d6d0d6e0e4e7b8461e25bdce59bcea5a0bd854198ce7bfdfbf5f9c333b9e771ee3de726883dcf83e9f845f7c39c728bbf678a786fbc373f7764f486546aaa287b8f42f12b39fb36f70ecd9f09e57c9be5d3c704558739ad4a979717977205d3c19cc074b0273027d57ba6ea5c99604f60ba0b7b027382e9604fb2a7624d699cfd9835a55da294c6d9ff79ac29d81398cec64a15d614ec098c26961618399813eb0a16163096b0a6604f74e5f836bafdd74587bfecb940bdbc28a55d32516b59fe3a234ac964c9a1fbe5b4897273769476395719e5b9ceac64a2543251e896792d3767bf8b4efc2dbe77ae32f2b9ceace5c52e3a74b3a6a0d0fd354b285953e816a7bc12d0fde1ca9ab229628a1355a088020a74fda4a4604eb027505154acecb7b1525ef90f5c813e7aab30110515af984725a70f8d964c4258287fde7fdfdfc5c53b7f0879e76762fcdf87f1b7437b66f97de2cdad57ee5eb4ffab7f1dbddad5e2a5affe3b6d9828f70e158b10ea8f674ca17f05fa02bd5546700515f158d38232e97bce511a2bb9f53454726b3fcd95dcead9099a2ab905054d546ee1df364094e3db00b1ffaeca245c72ab5804ba8e4d33a45418fc35a68919b99bb3c35f657432a8689cfd2fe6d538a4f86b99448ca99bb3695c72e8fe1a2cca9546abcc754616743f69ae32aca0fb47731597a0fbc5281957d6193fa0fbc525f963223a41b7380494293b07ed174525e7f59898ae93dee59ef9195363133679962bf2572bf2572bf2572bf257258957ab8d921e093e69ae2f272bcc5fcaf2952bffc5a47c71294bf25f56ff5296bf23bb942fff42f25f5f5c5cbeb5ddfff2302fdfc2bc1ae7e55c5b92ef5c99a3c9e55e4d5e4d5e4d5e4db68785098a83406f95295aeefa9b2449d2db66520e9d2437f8de477670dda837924fbafc6aef15b97f875e91dfe624d5dfd5cb6a45becbea5dc8fda252fd6abbac5c48af3c5796e9cb15f93b7cef5526399aa30b221a9e6b7ff059f93fefe4bbce04adb55e71923ff96ab222c9578efc171372a522c9d5bf98ac9c14d12a72f5ab73a57279d5ea57ab736db7eb4a2e97cbd1e472af26af26af26af263a0fca08a8a029a0b7ca088640c5d59da1b5d649abf79bbf77729ef9e73d7adfd3d848f15e831f8e6388c7108f211ec3f177e8d15c6150c6579949e2df711cc3578daf0abfcd4534bae68026913fc41f7f47a6b161a21c4f5548ee98d6f149733ccfeff3077b8277c2f33b576f469f1d4aec1aa6833981418139810d017332bf1eeec4f03c5726cf833d99299130beee5ad45a9f5ed75d77ddf5f7edec79f95f394fffc639e7df5ac83b8b6814ef294f680f7aab84a08a2959bc2957a6ec3c6f6db7f7ddb461a2a4b191b28aaaefbf26e573c5603b8aa2289e6bab3ac1ef4f92a3b80acf76a3e0e9cd941f1329d4833971cd614e20cc69f55eef7661e881a0863d813d813d91da4ad0a1e2d6df66149bf8eaacb5d666521125fc8b68349f77a36d5ef1d9eaaf02f5416f15104085208a104841454de60f01141405f456010116a4507e4c8e66f9f95cbd07c573157fe5927239cf91e75095ea5cc7732d5736524aef6c37ea8de06bd3464a299e349e43bdfe2b535278d2d8305182a0deaf357f7d7a2e88bfb58ecc5d47fe7ebe9f4bf17e83bff7fdde61544498132a86abbf5feffc7be7dfe62418b9f78f17d1e2e3fcfcdb6cae1985f19debaebbeebdc38001c37e18bf23635314fa1ef39d0cccdf9792ef549beff6b93fc3743027fb330c0abab6593493c2cf01dd1746ce3011bfb8c28610619c49e28730604f6836eb078c9375ae1915614e68dfcf8958b9ccf31f58bfcf50fcf057ec42d84f56f8dff3177f7cd5cffc0b1ffb613a98130cdbdff7de6b9b579813fa0df5df67d27e181ff6cf45ccccb778f12efbf97f3d0ff54fda3f46b47fa97b31a31af7b97e1e155414391aee2d9adbdc3b84b1236f9813b4bff7dcdc2c937c1a537cfc9ef9fd0b7334759e7961ba7cc34317e7fac99ceb1773c29c6bd9e27c39d71997b3d4a238f21ce79d73bcfa4c0e75544abb4addfacd8452620e5d25ed73ffc0ab93e5bdf8fc75fabfaf1ccfb5d4a1ba84caf5e72f9a3b547cfee09326cfdd1cf193c61f232afea002214a28543fec0986890f3e4c975b17e6847ee6f5f6defbbd7ec29ee01dfec5d0601744904247f4e5a3401005bd81de2a4d4c5569220abd416f95269cd026aca0228affa543c515577d7fee945be1f7e743f0270e8547e1b91e9ae0878f3dfe8316f14966fcaf1cfefcfd0efdb5993493c6cf01c5e6aad16f955fe4f764cf2b9757130ccbdf93c81fbfc72f3f5cce35a3e3bf9c6b46c97fe9d0fe18e75f71c718e73389fcf1fb6b74ede7bab1c7a42407fcad38a0fdbf737d0d81aedeb739297f0e683799d06fdda8f721fef07c394157af8846578c923fd68ce69517e68ce2ffbe737de950efefa75dfabfffbeef57efd3dff7e5c72e308cfdc9c7663f59e387ef8541f01f7ce0ef3ff0f7f0bf7f31c1b0fe5fee3652ca36a37d6df34b87862539f4f2fd4c7a09fb4bf885e18bea3b574c7ebfae84f8be3e46a122c93b3a9a172549b36f94343bdaf3fa62827edfc921fce07b2ed87bd00c5f9b2d7e9b30af5cbef1568cf2f263f02c28e78f31f8abcb9505cd9804c95f5f4d30ccfbf25c5d575c52683fd776d3d83051aeed0e1f277d8861f8dea3e1c7a40f7cb2c01fdffb7e450894afa6b27cef6d14ffea57d7159446bef7e2c3982dccf15c9dabeb0a8a3f893c57ec913f461493af2273eb6a0ae5ff6a8261f85c5926f4fbf1c57fe572eba2e27752487ce9d0d0bc1c05cd8b76144c82f6150128df2828a2dff96a8277bcd3fc609e62b488351c30b1de576bfdf4b4f7d69c73ae7befbd6bec799e5793022da575396a6a0a3fe99a14945afcf83e316fba39fac4fa6a8df914a97752f9a98a9426a89aaa78a0021adf75f724a0190941d4ec9084140c1421a5082949f42b528040a538910281242ce18225ae486962da9d402a0328d8a1033a3c1d6189231c518a2e30c11551ca882598e0c01223582205bd054a00015562084a1c41092c96d86189dc120cf89fb1c4138d031e8c31c63a9c22ac22bca20cb1981197bcc8e486c74a283ae9d0ac45bc55f70909510709cff3d3381906a62cb9d6d8d34c2c19c2079fe87befc393bfb8d249fbf5633d14a2fc4530d47b46fbf7df3fd4b9d6fcf333f5be7f489fdd876d8a7cbce450efff799e287a9ee7f5ff6aa494eb875eb9354a94df63d58bbf3a6d76a5ea8442bd176fbe8cb599f34a6bad32f7de7bf59c73ce57bdf7defbea456f5499354d94e38356f2137f9a9a8a8af27ebd52ab96f254674d13e5f71ef96228ae5efca195ea3f5475ae2c4abe78b27c7ca8f81f2a9e2b8b86cf845e5225fe90eadc79cfa43a59aa5f3d4b7c267475b2aa93470c4a9ebd09f55e3cb39555eff6efd07d556205a569f149fe194e9cf7ce58c5a8bd77bec0bdd9da29bbba4ee3eca77ca3cb2e10898b737174fa29b77a947e81dbea1cd82ff22717077e7f8fb21f478fa25d3aa771f647c92d8dc4e6bf00ef5c35da4f0dc47581bf571eba791419677f14514c314515555cb1f73ef5c9afd4eccab547c9e56a42a0ffce58358a5fbc49a898c71a24caf523f9ebcd398f8ae23a5def5d9b3d2acaf33c4f9bdff77ddaacf1c05dbf1a0fdcf543a7a6a2a2b00eebae94d4ca835eb0c603a48f4f9bf859219b5d897f4837e5489647cc95dcda6f258af543bf6f9fd94a888598ff0eed20190606f7c71806467b68d737e7e67c687f1e5832881f3e911882e707f29a292528cd21fefb41d2071735278746b4bfb82acb12f8e8af3f7f1b7fd7f34ecc63a0b5d65a6badb5d62cadf15facb5d69ee7791ecbfba4fef9bdfef97bce5aefbd4dce39efbdf7ee799ee77956ac4c4d4545d52c316a1e31319ea7bd310871b766f2cda77f983f1a27f7c8acdb72e1de883fda35a371f073feeb6aeca1730fed7a8d93bfc78feb4261c5bbaa8887e295877a49572a05e1949ea922434103cdf8b71983ae5c3c93f4ffa3fa5c85f6f3a95b042b8c70c5119090042d4a580213b068c2169e45930e3b2460090f3df890fb41010b60c2809b9385f0d99bdc9cfc42f8ec40dc1c3e353535c59dc838f9ab7c50cc549169e42bb2967cc3013084cca7388fa90f07170b114e6e9073810a72a0c3150be01b83e0cab7c405960882123ebc311ffa9a26000325a040cf95099df14419365da0f173268736c92fa9bad138fb6f340e46552fe6d12383a854e4ab48d5efc89b3c874254f5a371f6dfee795fe736bb12e692e487c92079f5e4afc8df91fbeafc6ece470aedcf1b4172bf7773f6df9bf3bdf88d61280ec1e49bfb2339748373b8c773cc3743f7def07b73ff924364480eed7d73449024c11fc92038a148ca508e771cc771c4f953914f861787a16af5e5796fcefe15f91825cf1587aa4ef23f941ccf242d722b3c2b88deff5104eb933785a1111d98426f9523acd4406f9523a4c2150cca272f9ce18232e6e902795f8ad9333908963121cbc7daff47f6de8b4157566e4e085ef043cf95eb80661fb9095dfbb35cf0fefbef4cfadefb8bae2c17be071f3c933c960fd44373ce53bd09e2cd57ebab84134a3481aa59092594c0006f42cbff6074aea123d1e480174838c087992824228084110e408204489c00892790c80112424082093b1f7641ec22b0dbed50b0abc1ce073b2624914b0202493c452009100491440b929822092c94f84189274a04f1a2069d08d709225ca0a5092b1ce0440d22e00acf9ac134aca77922d6d3d03c0b06eb61641898bf2e7ed5684ccc11a58c8fc17222b7d62bc58262b1a272ebc56f2968a45ca06476b935fe7e192932503253722bfc7d450a746be9cf7aa6a0dc4f960c34cf7a229a67c9789a9388e659cf3a95c4789a9329a89fabeb8a7ef935e60a8dd4cdd92d7ebd5a647632524c19a87cb33f3465a6e49bad319025323ec68b364cca3d009a3fe107c07a18381c4114e3613c0e324e56ff139ee64ff88c438c139ee659fd243ae1a461113deb619c20089afd2f396aad7f95d9f1dc9a1f7c32bc68488243f2cd064f19167737673fcb14f502cab547117717084abb58bf3ddd9571f1ba8be2d61e2944e66a7050f65f7b941bb4803a8faf4d2634ab40f10a78b36abd01cd34e6ea5d98ab4665ce1e853fa131e6ca7f803165766bf962ae3452a0fb5dbe5cbe3157666fca374df9662fc937fb9bb838fb830842082188c8f9bc1728716fd2aebdc79c168e65762c285024557faf4bbca271f6e72857b4ea5cbf4f6719579ae8270b6a49b95e2d489434e5724b6726acfcfdb509630384cd0f25cdb3f6af48f9667fcb3259501a673f8d79350e2b0add4f23659380fdabcc0edd2f5ec9ad1c85546e65c9ad5c456e4d895a340cdfaff9fcce9bef87f156f5119565d70dda778ad59964f2e740bc33744e4f9726ff7d315d4cf1924361ff8db340ef5028b6782297f36a1cd513bd9c5ca77154e3487e2785c8b22c3f9b353828cdf5062ddb5ccdd55f12ff369950bc1a5f65ae2e2ae828fe6882b47dae375841f77b1ec9775194f9fbd0af054c0bf3858952af3b34cf8dbd3ca5402d4794e28fbf8a3926a52eb79cac769c0953107e51fc9129c8c3a2677eafcd8e3f3cd75512abddcdc9e0b98a3ab1495c22e64426f9e68657b4eb6a9ca7ce55e7effb7e15f1677226949faf26a358eec0ccabd8e8caf552592f95f552c158dbe46c7c28c7ecd954a0dc2ef0c327023f3c951ce1bd122225e0c9146443019b1d4a183b0c00a9c70b3d80dc28d0ac7972eb732b6774c6df5c1b68f632478298c129fea452c2bd404dda057e0699a02b38b572a7ff9e29e85472c47d259f4b8a280ccf1b74f111e5f7a04a8b76dda0fcab25aaa7702ab7ae00b5805828013f9dab6a2b41f574733238054e8157805a402cf24d06a7d00c964fdb02227f8c73d65aefbd37e79cf3de7bef5d4aca46025252566c24e0020ca87fa8f3d73fc435f7606023848d103642d8086123848d103642d8086123848d103642d844c0c6c96602364364189b1fcafe5ea6f1ba548b2c1768b7f8012f6205d59b5a64b9397a0585ea35b47273f4f39c8e422f007c805f54c088e60fa1565121540815428550df4e8b76f19cc6c1afd33939d1ec6a3650aa6a8a287ff0e229ea05113f8029a350fce2e9e6e0100ac53b34419aa7fd01299b2038b70902a683e9604e3aeb6e8a38ebdeb5ee5ae3ab55fbbbe9bd36b3eefa5ff83929a4ff72fe39f7ce79e77de45c777def3631d79a6bddb5dee7bdf876a81df889abd5f771fc63b532f3efd575e5d58404c1578de35786abd5eac599affc56ab57eed564c59fa0a84c1ea50927f37373755d71e29142a0598edf4b9749a1ef1b9b942f5d4fc107650a9407bd553e20050551708ffc21a21863f207ec09dfa157bedb917d88ebf763a87afc34d5b99227ec0939c44921cca39ca0a27f2df3c39c843ef3db186f069430274c5e2c9f78c4500c54144fa10dc5751ac743f57b2f662c86413c2acc7ac96a889f302721193727ffe5516c50ae32a4647c1dad89a2c4b7a37746c65388fa3b50864d48e5969725ac2284ba40f8330cd0bc0234f3a71d8fee99e11553d8ece815211651c2a928ca704abbc226aec880c1cc0a4aa0055dcb1b3ee5895f80ab6e108dcd11e57efd6153b824cc854cc0281728ff7aaf6c2d1b0bdec497e4d68783f2eb9cc35799e7da6eb19be087a6f8993c57b00830cacde13cf7997c49bee1ef81e6d6e29143380b5414c5f07bf099d0dcfbd612ea9a70f1770c054c077bf2f940bc33f0f8f7310e9641b97fbd41797efae7c791ff7bfe798320a8dbf7e61c3491434df456819a0215f178fbcdd1384acd0dca75856afd25cce51e0eca511ccd357cfe3db8238b2613dacf7c458641836de502c9809a12e584d3146511ba49a465980ee60473426154603a241df711883c97f3df954e9bfdb3b9b3de599379f71c7ae79a5bd17c6ffe9befad7f9bfa59f95fbaaccd570eefbd8b7868fe8dafceada9ddd45d5d57d0b5dde8c73fce75d7dfcf8b77f6cecf5fb99cbf3b2175022750318fd98583d0bcb3f9d2690fd54cf8732b7fbef1f05e4244effd3b23eec7f8e646e35ca2ce77ce7f4354ebd7e79500d65aebef3dcda33f4febad794d13b6d69aefd75bf3efbdefbd27fabc10e4dce324d177fe78e7cf9745c9f9de9d9ff7e6e0f776b0bbeb6e02c5fb3d4f67a71820959b9363682727cf09a704942b7ff2bc5d135bb66cd9b265cb962d5bb66cd9b265cb962d5bb66cd9b265cb962d5bb66cd9b265cb962d5bb66cd9421384104210d1317eaebb40b96ef2cfff8917efaceae6f8e2f3103ef89964c930bef84427d189f5cefb1bd29a975eefdcfb6de2a04bd630a1cc21d4cdb15952ae3c0a9b253973a69eb90bc49bc51a0e98c0043230410dd01922c49b6b76e052353328571868f6d0fc18669bbb07a57e7c5e9b0d943c4f3183030860315a6305b622632bb0d6d80aac37b6026b8eadc0ba632bb0d656606d05d656606d05b6a2c68aa9a91a2bbe2cb475fe1af04096f080b3f2303ed7691c91c7bd3737a20cf7e2904236bbb21c397966b24608e578b2787494ff28eef1bbd65af31f553fbe388ee3388a7f55e373b3a31c2ab7303a8abd8be7688acf773abcfac33b2d7e163f141f14ff13dfcb1d25c5f15cf78b2787ca37f8c53377813c73b7f21f780ebf3649dc9ba0f301162c58b060c182050b162c58b060c182050b162c58b060c182050b162c58b060c182050b162c58b0883f8e3c68ce959749e2ef95dff1ea707f8bfd2b5cee87a1c5efb3243fe6f7b9c2fc582589cf45d1eca2f82ef6b728c5219c05ba5ffc17c82f5f7373e52ff257a9f8bb98eb0d5954bf4f56f9fbc5e726abfcf139ab3c7988ff2e3f8a7b14c91727f2c53389fcf24c72f1fb4599b3d5688be72f9e6bccb94ffe2dced51e4755f99df4b16158553fbe78ee7cb3b63874d287866154ed9dcbd2a6017baf3764110a5fa471e1b1dfa52cbf3ccb972f4dfe62babc4aa57a1795d3e6e6dee2efe7e63ac34dd5f3d234825197f7f68be28b3764415ffee5344b73c5a8cbabf8f8dc54bd8b5996664655e6ba3a69dc6967a9a1a2dcfbb9a9121f7c95b9e64045293a85e61214ceca0d59b45600153e40b19356001554ec563588b2b2dbedac48e9dd6e777795124451e5bbdffd76c01ffe14347c992a253002d5ffa24ffd9cfcb1592c9e3552ab1a299b060c51822156ab526ab7db5ba846aadc7b550226bb5d7983959d959d951aa9ddae464ab5a5e60735596a82500304fee313891f3e0f78e7fc658ccb8df1efbbbfffd03e79eee4398cb3de9cf3ee8123376b7850e6d73c08f70f7d99e427cbc7f7fc873e7eb2be079fc59f09054ff63b799c3727eb1733292454230599372954238546f7de526c7473cea5e0e8eebdf7fe799ee779520c79e8ae91a2ccafa5a4a6a6a428b18f4f3cd7a4a113d07e02ba776b99831a1d443da12aa0b70a0996a0e2eaf2c77b7f7b7b68ff1cd0ccdfd3fdb7c9bf0b71eee1cc44cef8a2e38be369ae7c278ea6cbab9e87f257af49960c2eaf7a2297f0554f549e3a11ef9df9a77bb93db2c74547af3fffd1c7caffd6e0a00ccf15fcfc5a1c023ffc1d9ac677f977e87365aaf80fa8fe1c365398350fc43db4aad981de9b73ce7befddf33ccffbbeeffbbe9a1d8064cd0e4afc57dfe7bf33cea48fcef7d63b676f6ae78477dee1bf742fdd05f7f63e97cbe55e4c5eb9571328519e425de884f233772577259763a2fb767a87f7238139e51b0ff5604ef9c6f33013fc14f1ce6773853941f777d813bcb3a740f9e7300b1a4ae51b7eeef4a34c7a3fe64c97a90665be28bff9e6e342380df194d32e952ed702d18a0ea142a8a710ea29847a724108154e09a3422772ab875572ab8b57ae44f1cabd9abc7246440977518c087731788af204258a113b2950e1aea605207fe962f014e5094a14237652a05cf0f404258a113b2950af262f27215494a7904a9427283b2342a870ca5308f56af274828f69ce5c2ef7f46a128679f46187eafd5c8a87cf19f4c513a62cab8c2fe23193f81ccd9a274af1fb4a4d5e149b17ad794199043eff1d596ce972ebe6561f427cca2df17bfd6214318a68c42786fd7361a3e0f93b74c7678845bed1926fc4262d2116b829b7565c5305e4f8ec353028bdbfa1969a17949853e155b8150e05cf2dfc1a5d31bac29ee01d7c0e81f6afa1bc55a0a008b538e5d61a6ae154aaa0fa7995ecc241fa432c72eb0454df2a504c564e05d5ab98c33b2bcc49119d4516334c2bbbe2a7c11e952beec4299e378e991cc7f177e4574ea57afe959f7ebe5a9d364c741a57fd2802559d9ea99b684dbeca24cf95e9d5647ce5f44bc73dbdc751c348f1176b5a5026e132b7beffb7be747b84f93eee3404da7f47c6e8eb237d88a83e5dcc1594c6c13c62a0dfaba46e8e508d104afc6bb8ab4111280e7778b75a018a4f11cb6a05687f6de218e82587b0d405fabebf67da30515e2c287eb08bd80c3bdea19d3ba1df7328277fca3732d0ef65a0fdbfbe8a0a775ac9c9f9f3efbdf7ce4f1829328f4778b1a64a99e43d477b1e31e983a3de7758a2d079800c34855f8f18460b68e2bae4b8cbd0430eeb29e8081271200a5f8f1e7a2fc816c0193b06d0050c0c1b1a99143e155c0862a343f8e9783ea48fcb07106fdc1e0d07d22d70dc28fc0e0ff4c2bdc35dd83f3b30676993cbf0805f4cbf8460335f2803ba74602106d1c677b367ec197d034d29a8760b5c051e039cb161ac6ae8137668d2d9c19a88ced99a23e03c80034660ab21d1012b513b24360045014c3820619388a8b12107b0a18601cc200021405e08400fa01f9f1b9b1a00fc4e010ab22042155856d05173d2bcf09e5c5045031990416805a0136246b13b3105044744d100940b10f1340101dca0b0a32381081009c9028b2b8c6a20c2104850647369001033223a60a11725089d0bb4c19522a200bd5a2e60c4f0f360b03ec0840daaec470c01f0c14388024d7ef0e145ea860890e8c006986898c1d59a81d930b062841974e468815e4fa20710286181270e5800022a40010a00f123061d301446cc04bd84ac654ffd1072968d254bc1a5f00d720d320dc0198c506418e427f2949c84462227ed15cfc032be13380cf245769163308c4b0bfe925d70b95559e46106bf2f7b1ecf1b6b8cfb25579aa547d0b419d0b45fe8293cf68ea043998126ce47cfe6063a6c226a6c1723140e740534ed17409910c64c029a4002ec1a740cb1ede3c7e3fbe9a186cd84bc054d1f54138c0d3a802e3d012bc61d34f12c9af8484383a6b13d47d004cea8b207e33bdc492e467c01811d3693a695b932758ecbce37e392c50e5c85efc7c3a1571ac707a549c35063a787dd88262d769a4d43aca1f11e378d900757c12340a7e9615f479811069c01653c02e826ecd069f46e074ea5a966dd4a5f5473c2061a57dd65639da359d84046173d7b34782c2f061ea3c25dd47c8f5aebac3dfd693084b1b52700ef65052474f1ba87b5b76dec9d8dbf179aa503288373a04965840e2e7db3be1676d83bd8e12cfa9bc5162c36b802032b2ba0823b1212844e2868851c2e5b8d0843d841a15c7d5e016af81143162f8022063280e07182c08e08eca0064fa880041f4042033b3a364f702d10eced0845988a620650a0c0034c391cc0861a06f02386211e3262b89099120226243a90030e4527c0b0d564a876a083224a4d596c4902126c28800b19971aa820052520c10798480288263f141d800034fc88c1c7132f48810b44e0d40307377842052820c108320065013c24a008533598010946304589254daacc87900b3945c5143b58010a420002294504c100555674001b0a40830f2118108e0bf9c40b5630010904b144951d20003e7808c18054e4be544cf1c40b5890821080404a124534a0c9120304c0470e1890cb460d9c17977db3a0620a1db0600529404108409044110d08a2090396f0a0c80036acb0a38305135e7061a5ca010c1d93050c80061e0b30600149901c0166a1b7e4267026e4257c1cd81bc845640de09e5c43f3601d3147aba05f46468681591e8d7e8167b60b2c03b67879d1e55e8daa92a4195f88304217d0fbba977be75ce34b1396a0967b2f7bd3516946695b9bdd967654992d36abe14eb8177b60957e4b900225f8a4049770fee65f95cdd8daecd9d9b7b38725b9a5b71429f6e9ec411d205009d2b877c81fd5662ad291c7f91c38fe26078eafcd8e54a30f8bf22bc2bd439e88edd9d9b7498c0ca0a3a353001f3d3a0dcfbd1c767214eebd326ef9d12e7bd3a9ddd4b64d97766ead0ab7fc703e98daac6555d97f6bab3dabda648f73a3c66e381b5b7a48f0a4aeaca6b6e9b3a51725aba9a5d7c453f248ed514da53d11dbffcd46e496b6ecab47b5f486e4bdc0de746e4831255569478dcd68ec057788712f88807b559696125171b7f6de7d7e3b7d8a8b73efbdb9f7a270622aeeabc6ebf8db0d174be1f5fa79b95eadd78d978d97cfabe755e3c5f37afdfcfcb87e5a3f377e6cfcf8fcf4fcd4f8e1f979b97e5c2e57cb75c365c3e5e3ea71d570f1b85ead9f96abd56add68d968f9b47a5a355a3cadd78d9f1bae1bad1b376ed8b8e173a3e7468d1b3c375e367e6cb86cb46cdcb061c3868f8d1e1b356cf0d878f9fcf8b87c5a3e377c6cf8f8f8f4f8d4f0e1f179f5fcf4b87a5a3d377a6cf4f8f4f4f4d4e8e1e979d5f8a9e1aad1aa71a3868d1a3e357a6ad4a8c153e3c5f3c3e3e269f1dce0b1c1e3c3d3c35383878707098f7aef65dd9247b9ec4d470957db699588a8375bcc00050869dc8437383c0e8f807f1a218d1001ffff7fefb5b9f7d6dc7b678c5cf6407cc7104e53721516f7de17b7dc3d1832e481421a43863c504b44a5fd8743705aa4a3d9edbf25a2d26e863c904ca5cd52226c9a23470e935591626daa0480b3dc4718f1345040e1dbf4656a526c564b9796dc0bb4d372a3f7d9598b33fa67672a52dbe260b8d96e37a123b6dbcd16b461dcaba3b3f3bdd7bba56682958bf338707c8d9b576bb618867ccd366b977e3871ef3da1eead1971efede9ded6c9bd3c36dc7bf5fd7bef45ba17890da5eea189eebd3694bac7bda7117bef1572cb4c057bd3319ad1da9bcd28bdf7c6dcdbe2de3be3deedb2f5bd2a8e961af921b79bd090afa55fb3fd109c9ba5f476a48603026a6938560d32e4652a8d96c6eebde5bd7775ef55ddf1de2be221eebd02b825b652e22ab9e6defb40acac86b3193d11dbb738dbdbda22f6a6c3a36343a7460eabb6498c767c7a7e62eacb48f67af5b46231b5468e922da66384436a6747496e2d3b3b526f3bb6a35b1adbb9a932da0c67b4a32ad990763e078ebff786f7fb7e626e899b6e3ba32db13fa44d87c8dcf256b9f7bedc1213dd4b4b8dd894d4dbbd97bc9763a007f75e7ccbcb837baf8b5be216f75e985bde2ceecd814495d5663795adcdd89b8ecca6b413538de03c3bbbf99a4a7b1caf1abd8c86b321b18f73ef05efcdb27befcc2d2f14f6a6d32a11c9f766bbddd4afa52f536b375cabdebe669bb5695b539554da92d2ecdb597a6bffa8366bdb54e96623f269eef75e18b7bc33f75e975b5e1bf7de23361a919b6ac4f649da592c5d4a63ec4d27a61a293a92de766237762766a3d1d29d24b7f46634a3dd7bb9def7de3c566122dea8a626f4eaf10927722eb724b7cf1e6c9a48e2bf733eaf0be2cd6611cd69df921d74c89ff7bd401a7b60efbdf9638c93f02a84cf224a56fc0160eabf9518c9cf5bfdfcfbefd05c7325016042bff7ce6d26f1b38812143c933aff7dea353ff6cca44fc9de5aebadf7de5bebc7ff831721de35fffa3df8d92ca22f3f716b247f7fbecd223368523e5b8df6bf68781651d203a31f3e8b2841bd33697ffefefab57993ee5fa60f6711883650dfb5f9808d1238c43be392667e11e32012871cf27f6753fe3d8e0f7ae1fe9034f392fde3631307f07106c96df6b04290fed05c92c341fa7b582168ef279f488fa249921f8a9fc341e192f145f02f29c30d02c7271f9b38848fc113872388c2c78f83f8b93449160fef93bc530349f2f7530349f4af80969f7983447389f8b90f0aff339790dfc307858fc9b3e95c82430efac3b3492fc97d10f99aaca1d44f9a38e4a05f3c9bf493329044407238287cd0cce1207085a0f07338487c319be463737c4d0ab164d84f7e0dfac7ffcc25200d4c41e489c31144e2e3c7217c1307510c4db03f0eb241a22c72b340fb99a48124f9359064ff739225c3fefc441a080964ff2581e8ffde84f86997122539e4d79f03fefd4a8e20f2bebf9225b90fe2dfc3071d41043eff1cbe0fff9e4a7af8207e2ac9a17f78e63e483f78e6f09db90fc2cfdf3b73e8f8f9a9ff7e0e1dd7441b107cda1594cd1e700f7b49ee88222ba0f87bd8e711455640f5e72218d3207e9f077b72613ad813981377c2e45154f8141694f8f21d86bad2faf41429a2ae0558b08008e88a3f08829feff7ef5f4c9abc74a0c98dc026cfa4503f77bed97c976ff4d7aca0cc8f3594be7ff77fae2bda85ffe67bdee7cf99448937b7f42e7b6c1c60a3b301c206089b04e83eff8ece3dfe719023e19c932f92e68f8d037e702001af96ef6af1ae967eb53c2d79406944dfed96b8e88cc82d150edebfbec364df7db7ceecbe43b3d493eecb65169b0794ab8b4ec6a9873daee0af9ffc5c65a0ba7e05bbe844a953c669091299a8510130523f3b3c6fa5fd3829313a8423dfe47ebb138cf64ba53bb955f4d0aeeffbbeef07c76e5f25a019887cae6c9e94eb4a0a34c687d2f8beeffbaa5001142a3881155554206595751f08835bb87c7e202fbf5e2b65b9962dbe9798181ec363788c39eacab5d4953b3397badccab453564241751905d5bad4e99e722ba3facb1d1e195066291454b1b3470ac59ca36e77738bbfda55ea97dc7350bafcfa4d69170ee238e6e43c8649bc42d47239be5a254ee972cb1b6d9a942e2f6644117f6658962fe726c111bb982dccd25ccb75e6d17eae5c9c128ae13cf3cc31d7dfc91fdeafb47ca3dfe68717e6d538dc46bed1dc46877215a7a6722bbffeb1e98adcd2322e1973b9a5cb2d5687c1fb357fccd93fdfade23f34f32f64f0cc8b9f79a2173f33f32f5efc8b53c98b52873a5da0ac5d2fc6f1ef8a5c3d39f3e26762a038c50c11f3945b2ebe1301137581f2772da5ae74925b31afbf742a8540b516a750adf9bfe0274b86997ff14433ffe25472047f252f7e869f44343ff333a79299a739734cfe55140226eae668f15771aa8c314b27a5d310f9268f2e347e61b2f2cf982c19f249f4e2699e88f533cffa99f75e9833a614ef5d5c644cfe97748989898931d7fcdebf742634bf0c20df68cf3b6de02f66cc3b553a95ba28caf54ef12f677efdd0722dd172062d75f92cf3ceb8b8fc7aa75e62fee55b982b13dae23d52a8c58bba08ce62ed660d0e4afe2e9ef7313131264c0ccccb7b2fbf43b778717139bf95e756aa1f9e4c1d4c0950fde0a7b577ee9bf356f1df913b5f471d93652cb9f997f030feea79205f85c311dfe330feea3bf9aaef7f574f34feea24225f755e16c3d82ca0e451f4064551acc141197efeef2bf7832613fa79fbfb26c79d7fb3a4742d6dc9068828192b3b671a17ebef972c73cc3834435e8cb912c3471917286fbd5a5ca05ca05ca05ca05ca0c42b32315762aec4441123159325a68a98a9182dfa6ab954647651680f2526856e4317fddefb9b93733791767d888c133254d0fd1e29944fd9d849a1f245f15c459a2ce020a97befe7c0f10f34e4a836abcd5225f536e45b365d7a35564b8dc86a692ca6ca6c2c282001413d502aa80f57478f0f8ebda92cee26536f6aed88eda7a706aeade16c4ae90e17e4eef5a10795add996583588526c0032da0f590d6589da9a7abbe15848c2ded41991596b8b41a60a51d5a3b4157234834176b3c5d4202acd687654c3f178416d6d4a6a90db4d28e98710118bca541e4a6aeba39dd1c082dad67cace0dd24ed8c366b71371d506c7ae469b434f63a90866ea9632645faf46b3325b59dbd4a3baacdd847cb1c52f75e1db6c519f5dc328705eebdb69aacc70757e6507394578746538fb0e9d3a8a94666477f549b3d11dbbf4e121a0d47e4d556a52d29d962b299ec59f589d8827040b9b822b31a4e496d6754c0906ecf0c35b4c60c6d4d554a922e15a96caad2d42047d4184aa01d0882a14e1223100443f57176d81c968dd96439b559aab4639be9e8b0319b6c6707885d6a5ba41b03b435d5885a8057918840100cdb9a6a44480850d0bd37865bbeaeb8f7eab4ac7a748463bf453a9a29bdba94a44d55a496868ad4d65425dcd12d7d15a955656a4d5d5a4a919e4d91be669bb1accafe113586d70f2acd16842d8aa5291bebf1c1b14a6a3bfbf1e12ac9d09b7a83aade78dc541ccbfa5069b620ec527a44087b23e20144496d7dd05223b6dbd2124a5363b59405a2761624536f335c1135483b636d4b6a0b49684b4b38214852b5c519cd6e6d8bc220539362365a4d00ac92dafa50694629912a8405d5c84c8da941643555c86dd6821a9bdd8010a9b16aad9dd17053891405b5ead12d6d8b64ea111b8d96eac0a1aa448a5e98b5341c0c311bada8a5833d4a693cd25b8b4488464363ec0c0655e56154b3b1a91ae4a7a706ee06939bc4766b59b5859a7a83edc64335327b81559792a82c9b16b52acba23672d07feebd416e69a3bcf7aa375c9baa447e085bc31519491a22536946694d7dd5e85b16a7f4b25a3abbe5245193aeab868e0d1d9e9c231b0d77ef6d4b9f2cb71a7c94f4e8f8d4d0e1c9a91db1dd8c90d814c967003d6ee9f3828d48cd47cba62fccda59cdc70d335a5a9bf1104a425b5a4255364502420404f4820e99bab434bbb52c90129b1ec121a5c694dbc6d4dbcd16fbd65663ff89ccd42524eab3b35791881e36a4e8662b1a225391d8596d46e483d89b0edbce946a4a5755b209516bb618888e6ab35b2b2448d112ab2aa5351680808cf4f8e074dc54206cd14d5d42922add68696b6b8b90a44a3a702049956233b636bbb548d422954d8d74f4f8e070dc54204852a59a8d488907766bb6197b5367b8f7ce64aa919bcaaa4154f5c623c6ce948080d89a9a646b8180585c9bb642d4582da5a5413622351f496eb63695d5521868e94d5dfa414b956eb618d82429105625802d0636490a44558914b149d225964d692cb06c4a8be950218b21b129520da72acdda1aeea6a3d21973b4dc210f34e4dbf4d5366d9770b79b8d05f6a6434b7768295099737574745068551a8d36556f47dfda62781df669694ac3c56869ec91dad9913faacd64351c104d35a2d28a80d89b0e129b22ed20b1291210507bb32d2de1d8225529bd11a1f1c2bdedcd86c4becdd602a9ec4d35a2debee6e3de4b12dd52852cec4d4756535b24eacb016ea90292abb6ac5a2bbaa9edac48c9762352145397d89d986a44051b5208020eabde6cb1da2c4562618654f381c4a63d74e8b8f5c0414bdb9aed86d9cd86a4a348260408489ddddaa225d688ec961ea91db1b570c42ae9f8c1e188a9330001c554234040b2962d5a62d33695b569515b53d9b44d5b90a56a0f59aa22c5d0a3a8a8c70767c4b65484245542e18ba1870d23404045ad4d9626d16c4b699bb650b3cd6690a92d3b636f2a90195c584a6150d51b51108d86e3618be1681664b311dd54a4f6074b9ba5adda824c1592f4a36849298624491a536545b7a2225509c7aab725db4dd6e38393d16c48ac7a436a8b64357589c5b129ad550357d4b2b719119acd66d4b233246a114b4b6b4733b6c5fd686bea6c6916536f3a7078a84b4ab1013c8d189262365a50daa6b2b455958a54a59a8f9b2aaba9ec8cdd3daacd6846332140404a3124b5d9d1ecf603004af001004d1877ef1d724b0090b7b6a6ee2ca54aaa0e1c997a249bdd8cd0a721f391de5220373862ea0c6cac6886d3e3837b1a3d3eb81bf586195b244362d31e4040b254452a8a15512a2aa224bb4196d2cc72d9db03cc23bf837b89d86c3720aacc16535b686b2a6b8422c5da54899dc1d0da6cb2540d92e4d6d68468a92c5583b4369a1059aa22a1b6a19a6da6065165e9ed684900b254ed112b024311a516d41b6642549c8a930129d6a64becac859a8f25a55b0f96556f47424b4ab71ef7de17ae0b01b897a6d66a29120b4b4ab71e6c91daa62d5b9424a5a93720a0a2d686c42ea535216a9bb6428e6ab3a3946555188480811651cc60050bfc808a1544b1018e02c474a6cf03a18b079e08e2437043ae198109183cf400aa66c5c30b42983ab2e70113902225c78a9927332f9841112de410032205e4d041bff7f210a219a52d4e49e8e602c0c5bde92d6330b949d4a4225a1ab371e06620466943a7864e8d1c9502d8898baf9ce09627c0dc24b514894d55224466b24f5f35922221c56648479e46929b4d49557a5b5b9bc9521b4da6b631dcad656bb6183bd3499f880d89c57d12a397a9af1ad5d2d8bd2cdc7bf78e1772919bd4b6e9a76d2a7b36497a8496be4c656748edcb6ae923b129d2ab325b2d1da2e268e90c404032b576e4872a3baacd906269ecd55bfa2a0df7dea07b2fbae3ee9862099764b3d158bb7b1f4895d962afd28c52a458aade6ab897a90f549be14072ef855971af11eebd04b825ebaf4c9ddd5a578f0e4f8e912c479ddd5aa0974c35fa24b254bdd1d27bafcebdf7862213b8f712b9250dd2bdec4da74895d96ab35bbbc3aa4666ed4c69765bdaf171a9448c8c5c3b357a521e974fcc86ccc7c7c60f8feaca51dbf4d6b2463876876d71b6a4988a14db69674798878e4c6ddb9992922d86519da4586a64bbbd6af4b534f66d8ad47ecad6d25bfb3575c9f6b6189a80c22d5fec5a9bedf6ea517ab3b5ac12cc5bbe6897b4bcd871ef45a1c6aa36d9cb5215e9db9a7ae3f7b670cb992ad89bce4e91daa6b2192d35a2d3aaecac9dd1d2da4d6d91a84f4bdb9aaa54b3dd66466a424aca1926f72aa92d9b84cd59da6177eebd30dc72c6c6bdd764d35b0ae46baa7aa4558d5e6553f516abcd3e09ad8dcd6e2dfbb6f65999aad6fea61a29e16a9f14b3d18cbe364b91629f24a5a941eebd3faed60d1b3ef7de9bc3fcc2bdb7e8962e8adaf4686624d5463cd93374285a56f639723cce4d4da5e9c46eec027cb8f716e0963158eeadf13944682afb39723c928e7a2453d99b8e2ab3d57654f5d61aedb035dbac8da9349a2db6d332528918bd7a541b3c2d1f998db487a727c693a632998ba7461a4b7f5e3d2ed5c7868be7b5136b59dc6d8700443e584081a02c3c8e47229ba56dda827a940251712aae48d19292aca85569b474a988c5d1704ab858daa62d1ccd88d46c2c1ce0de9be396313b2e6bbba937fc516d968456b3bd2c55696f6b9f6d6746dfd6d4596c4684088e7d99da129921b117d370ef95dd12268abde9b4b17449c916f3c989a9325b2c27493bbba54bf7ded62d619aee95a9451ec6e373809eafa548edb3ac4a4b524b9f55956e298bb3cd8ca83720485459cdc78fb686b3c58a8edc542336594dbda96d0d6763413d4a81c8541657d4221dcd541a68294e36333293d566454852a512dc7b6bb8650b14d066b81b3a3d393622b5598e4a4b63ac4cad1dcd5816a94d81dc7b7368a8503b9ad152f58619dbe26c49d21756b877aa69c8f594bcbbefce81aff78465dc057b7785b90c56e1af67bbae76b26378f9fbf8d6185f1759e7e68b65d82be0ebc1f85d75ce6d649d7b60ece18cb1ee37649cb197658260dcb17733267574eefd267cbc73de3abc31ce213e42c6e0e57867ad62bc668cf32780ccca38f79d711032c63d6319b00b8c75fe4ec0b5a7c23218c77c1a4f0067dc33c65917e1bc43d0461eb18e0ff3193a07f672de35749833c659b53002760d91870692efbe190378eb9bcd33d6973f00f300df7ccbb2bcf8e28d331e02fe720a18679eb3c6d807efe00f8f39e7dc59c028e4bc33e6d13ae28bcf06932f34190298e71778638d6f06923fc4fbe659bf3f0433405ff304f8ee4841d6b8e3ed61126ba79c3f208d7f34ce797f1ec11bcc31f678760d193c3887fdcb1863ec610c8473c681ee18cc5b1bc167c661ac73302866fc7938efc831728c7c82befae215ee396372977ae7173967dcf12682c11958e41973ac72618c3166651f9e08666cf48d5ec632e00f639df1c63df009f9956164317b99638d33ce62f636d65c63ecc21adf60108f1863188e9143ede16fe38c3110c65d26c3e09d31dff89545ac730cac75ce3b6ffcca19d3e41083f8db19630c844fc8a1a7f1fe36c6d8f57826850f875681638d41bc67300802890560c5c000d838e79e3d1597313c620f8c33ce18e39cebdda55b03139e22c010ee1380882c5608604e51050d290f3a200029a42e9179832b840818a842d2276e1022a64c208869fb210311eb1490d4c68e17888861841e290c9e1711333baf54c5019714e40c9719e29614b809ad215950a0f55baece1026c4c8191225d390ca808dc6f3a4a0c2130d22c818f2a38c77f1729f5a18e00ee141dcdf09b857df7befbd37051df75e1acad514f6a693b6a9aca82f096b5ad0e4811eb8555fcb03f50923910e319cdf4d0fbf9f3daef01e7bd13ea249edb2af2420024d5d953d70d3204ff8e9405338c328db34543a641b2c2e808d3b0e1334e578a1059704e41dee177092d0e4831d52b0a203e6402e3276e890378dce59c2a60eaec20c9a7a97016449cc2a88073da4830f4d2f401a469811e6061e314b1f1b22a109470b0e0d6940e9819ae449fb07263a7c5bd54fc02b1f1db058004c81064b3e2d10506591ef70f15edb05b500bb1d5af09b904768d36dd82978af1d9a206268870d002153f22c21632f28c089261147440209207b567806181ae6bd68e9107aaf1e7e2aedf231a44f087920a129f47c9042be1e2c211e21d8631422c3d144938b4da31576a016640f03a1e6a8546db396454c3285d0c8884000010000b3140020301810090583a168384d1529a80f14800a98a04e62461888d224c97114424612600c1000000002202023230e089f67623713793497e2bbe53982023967b82d36261b1da63305dde7cd7072c48d67a1397d5568d4d74c70cc5ce9a5d25f2325e5d4a6908dffd9dd6c3df8fd09b9ed28e0dbbcf929787ad5070e1957204ab6783f41a70e507991959e26bb8a162df61deae7f3e00eccb943a36784177553a66e5eb54e2c9a00731c1cb4d8684e86eb5001c5f86662ed2ddce41addb4804ee3a098086d716b6e2b6f8fa0d69d63f12571ff36e5b13dfdb2236db4fece97fe5de3e7de00cb7c090568237012c2d34b005d766e1f07f806d858012e9799024b4a857535ca4a1deb8441207d1ee7a53e16862fec7a2c13af0a3f3cd8a7d84d45346efff1f14968a2ec73f7be0412acd27d9ca86455e07f5731012e0de3219e241eb53530cca6aa3872fcf476b4e589e1620a9634afd557c0a399464ac79efae1be9b8c4013299615e2f5bd5588e93c605d9b7e398cd8c32730dc608f0a44cf17b34b2aff17922d1b2e0980cac09af8eb807735a9e01d5231da3c9c9d2ade280e2b1ad6c524b50d4620e9afcba56e241f22b7ddf94a3f01e36c98f69cf3b983e108e3ad8e0c7c4cffe9c196e844d69974706b607f83f40acc0d750a69f2d10a6fc9252c7c15a25742c15101fc2c02b4cd7aef2a18ad5917fc4ac83ca33ef502dee79972fe1bf3f4945b4da44f63f9816b406092f5db67ae9d4da30e0d5acb0312a647cfcf2eed910149a383d0a76b2e0da2145f137714a7934ad100b6e3b97e948b7dc1cea6ce2e178b42df0f326f3b5c51bfca512e9079bdd2e863e850b993b651f76a42cf9f51e853a46ea7d3676ef326d349b49fcc2626330724afcd664d9434fcdcaf946bae21f308167561f88859043f0c46d94f4a50eda988fcb8eed1d2050a64934dfaca3d658b9f175f29fdaa42d89abfd80cb3349d37758d3d20a6ceb0878b9c361a2648906b65f9f1ed59f8e0d30e5f78a68a3d7da27274476d013c2c24612b82d9158b925a2fd05e83160984bb8813a6cb3dfbe9a65b01150d8b152aaaea4615de18167837be30aef95e0b6f19781a21cc3920f4e358b72a9bf8c0df07fe00e345bc89fc7f8f624569e5fd2b139dee0c34e72a511923d409639738ffcb62ede80a1bd9be8eed857d5c5441d2b114303334b154f2de15fc2408f22ee1e24ea1e89e33eba61705aedf0dbcb485fad42dd2d9bc055a27d38322bf4511fe3d1ca67344eddc0866328adb65b7efcc9a459f90a32f5fda84f6886c827c55a470d99508436f59d0474422b9e307a6286665be82506567b1f311d475d03da1e3dd504b5057d618f9fd44e15b0ea1aae5267a138b1ae7885b5a37680438c6621ee7eba949897c41a016a811f9f8c14557b4adca69f2f1b30ec6db1b9103e38dcc4cf64b2cd5526133ed57285565e1ad78d746fbcc12ad5577c21f039bf11f21e21505bece1ada2f1470dfdbb8682938064599f1a13c4cfaf6dff3b3cb228bd9b9b58f625d7912de7a4286443b155c5dabb6d898560f13fe335c6963bda8415940b03a1ab924cf55ac8c342a80e000272494e412a63664bcfc03812143751e4c54ccf8ee961baa8cf1ed364d721a480b9deea05cdfca84f59f1c32143df9db10ac288363f2e2f443d9c2305057b8d3281016ecaeedccdda433574ea104046543bf7775b568950d051bdb80d431c3e64277a0b5f9ae0e8066f12003a6881dbb51c58f6903714c9453e3aaa94deb4f8e348ab5e98f06549ace03e4c9051278fb51f5d09c6d494eca12d5817b7d7dfd7af376c7506243418f1f4d77b3481d959a9f6ef4c6acded51d4f816fe6b185c9ed280a0d6527ac1a589ee71c25fd014ff4f0ce5ecdbd83a17d08c90364095a8df7bc6184fe671fdc3c428486f4f86a1f4400c2cf2f9cd52b35357a8fbe758d1bc0416ce4d08baabaf29ec470d4be8c447f73500a61038014247ccb7c005889ab510afccb561144dc550003564e3753ea63f994cdb35b43540933563444bf436d1b13b7e7db599a60af4b9909cc6af76d819fefa31c3f939b58777b27d10d89d8ae6d9bee458da733dc1d8f52179a33fff9e67152e9b7c175d906bafde3887cc2bd3fcd5df140f0d598ac4896cef31d53cb4f59e6eb719b2df121dd5fea8ffe2d74f00b750c5badb6097e0f4bae37fe44e33771637a44c573580ee20058dbffd3fe30f810b34f0c591e71ede887f25d3712fd8f814b974df515857fdc2ed11e9799740b647d99c0fc2257607d00279104679c5d2940d74a7143740a7cf0013e27bc7c8848de6f9d30f359157ea4e553646fbbc9d96c5d3fd375d9907644b686b19b3dcffae4a59f930ec014dbca0f087e57d64fa615cc6ec1a34bb3b9c74a56f9be050c4e517762206496a7bddbe8ac1be9fc65552698a837f624506bfd33f35178fc0093b72ff0b87c24bfc5e7d077af4ea258acd31d525cd929b540af8de1f2bf8ad676e40db13f4a245de320ea43528afe59e77f0f1ba55e2ebb1165a51f6ef9872ace60fbcdf62ed02fa1efaff5c19f50eabd41e9afebdadc773086bd2f45fdf7c83ebbbcca792b79965fd3b17370dc3d9137996fa67e2148edf53981ebb1ea8caa27e69b7e62c2fa2bbaa5eb6c8a319fca6fd5a732788289c9b095e13f7ec7fcbdf48f1e380a9afcf2c49dd8ba51ae004e2c17b2bcc1376ae85d67aa755b7475d309f35832a6dfabacc5943ae4fab8714147f62aa608f4571ae3e600c7cf7d5f1bde63457cfcae04964b8717022766485b285c38c47fcc73ac5138b645e3f6cb1ce5a61a8ddf5b31b2f40f000d391a30bb0f523e45ec3a63a3434ed1b3759dd13743340bbdfc901d30a23f9473b43c0bc637c786ed01ab94d237e2be7d2dff39e39dc8987dae28788bd936003882041ee80e346f1a14ceebcc7185d51109ec9f5768792ecb87c3797942e0b0e9e7cbff891e325dfcf7061e61567342b4132439ccd8797e71c09dbdd1f3fbfd97f63975ee7ccc77eb993cc7de3101b37b329f0ff8fedf592fbfbe97618b3f38fda1317f650f3c18926f8a119e580d284e2027a2a32d7bc4be226e8b30df087cdf00ffa31d8cc35a8ffa9d03c5edbf9f90fb0f3a5f8cb7f465a4551f3e9c6e58086f7c4fd4f223e537d3dc75c510dca8732cfbd8eadd566bc2c4f98da58c984722fd9fe5ccb30a75010ed7c3c0fb12778030098c8c1c7bceec1f27bcc6fa124cee6b006917ad546464352cc3f23fbc1b8715b8640cb4f31adcfba1f6f55b96c35b851c4690d236e637c7ea35e26cbf63da7da5c4f8aec2e6fb2a5f0005667b0e90800462e2617c4a68bae2b08eefb94452795a24dcf779b863f7994b06bbbd931c85311e04c4190664413fb0b63ac01e4b4f3d395071d057ffac75f8d6044086477f760cc5f59f6e233d247cd4d1497316c71cf52b6de16f2857d6aa91999d4323c490eadd734a8bf7c089357ef5d905ff4bc9ab9004de340ca40f2787b1b7f4e61d73d861e033788c6197c2fad2419ddac0fce72bd4e4562a88010aed5c7a681f772159207d94910972e7fdbd7fcf09eafea49e0f25947eee94074488ad2a19703f58b4e5eff9eeb249130195cf7b84223ffebd207253bfa01ea975df8ac9579a7e98d939c1a274f7c816ccf56474eccf063a24c3e678ba80b8e47a42c6ea6033601de0963651668dd7e03a9c66634f97c165ff4b60e1ba32b875d9577a0328a515b179df36dae947ffa6c5e86ae54a9ba6d3cc86d0a46c44164a71df3a7b4fcf31832e5e5677a3a9bbd6d2128955635559ac92ef0432de07909d84152c5a334fa298a0a1fac95e29d66d80ca185dbe3a39336dceade3f0822f9765be4b05f8e7533af613fb99f99c486d6c577c417d6952c44ca8de6976d110ca72564ca71aeccc1e09037781edb3b32abb1044e7e399796f37ec5f8520b2da364b81bec6f42dd426824afa0b382156d91a7720e1b94d96aa7b19fc9c1604f7a6b84df7504483900227ef7d39f4b7fd93ab1f7e6c28cb719dcc0d1192e3ac662bcb5a8babd0f8e9373dd7fbe702f72355c6e22947c4d9873a235d76f08c593ee9571eecd1b693f7cffe00b9359151bb0100a73608d3451e798f81b0db652bfa866b8c2c00596971c1d12fd511cdab99ee50f3c8ba0f471d11e9aa3ab8fd430cf68e83fc6754cc748240227a55f657b359f91b4f759ebe05334e23d26148f42158bdefe3e5673fbbc71550b011c8c519cb611fc2799b4033bea2e65491bfa8a04191e9905adbc8eaa8ee70b3f1d310196627767bc942d05af95b35551c510a489cfcc3fa8a1360cc5da1107136dbf1013c7dfbf24c91d0763f92e05c21aa2101d4a9d8f141d0e9db2d1e0e4c88c6cad6a2c30b4ea00504341b89c5174dbebfa49b53a56913e5a90557fcdda4180cc8469c4c461198799e2c76ef00b4b7c8c3f4982644f92ffd18b97ab3db30e7cc2b3723602bf3b6b7b162170132c5e64011432725dbe9332f24ffad91c3bde7d874bfe00a752defcbe47d620c0c54dd2cd5407094ddc74a302923b57aa5112c084d1c750c45dfb3472f08cb1af00e8a23c70db48abfc037a25499fb482ee7ec1428e652628df4281d8ef171f248ea68b63a6334aae62ca1f7d97aa6350b65e98b8f84c517bfb042e96a235f3c5fab344821ca654eef1f368d6d66fded23ea8f4bc344911bf883fab6123b78a567b4dc6ccc79646c8e58e0b0b5130bb79632605869e9261726a9c860f99016d9d96fc680281676b9e3e2000892fb05882d91fd1526e2b41cee437be61929712a268459ee04bcba6e5f8906dcb92aeebb3c3799475dd81642305db095074c70dd3873007df8c27f28cf069216897111584c90522680d1fd006a484a920de2bd4ef97b4ea2e4f1c2d58cbcef1c5890318eccdeb2e501b544365fa31b3b79d0f7fa6c6f86648f3a31b6ec39a5dafb77f4bd49440496aa5a532384919feef8fe7213aa7e0aa20f65ad9aee3005cdb07e9ab268cf82b5b1ef07a0a412696fd69e6bbd313be5abb1dab9e2c05f8f001c43afb6b40146753ffbb1bf0d897d43214610e281a70773abf7c82e7dfb04e28891f1148734d68a727c712f23bb20d4467d8204d727343fc0bb1ad905baa862ced9cc859cdfa90f5bf867ea3c179e073829d274af32a924a694ad81db3d4a6d91bce6731a0ec21eabace1cf29da2cbce612b4d7bc65ed73ac17409a52be14c841770395928678496688a96a436cc885db3eaaefded83a2fe60730bdcf298240c87972ef8589128a66c36bae2db6220039372cb72ae4ba033a62745aa57cfa1093c4cacb244748fae23bfd16538c3583999d9b7dbbade3312a29d50709f5bb4c649c3493a63ae46a61fba9ad6e15cd6d42ea84fc034a16a166742a2f46b949ea0474601ae7fd1f828881680760060868e2e74f4bf50816e855091a1e2781fd3ff2dca576c33a0488c22698ac505f26e9044600b39123640cf6981315c442488cdb76adab1fa83961cffcb31bbd56b63ecfd2384d87dc2ed52fe602d1f96916c6ee2bb7ac4d589eec5f39ccec9596c31b99bc4709982887397cf23b5459721fc088db5b0a52363605984afbf681eca7de5d6673ef0e00b320bfcf422f2a79d84271bfe5e20e1f46518d90e39b872f0adc2377e24dd1648600368cd37c19d280e6925d9bf151b0160043d79214dfd2272c160dfd653028cfa6c9c54abf8e5aa5c6d2bbd1b6ee03c0cf3a12a3aa871c43a4dd291b83c8ead8de9045af787efcb58c16d7b6f89e6d468bfe8537655174a55540e589c5c00d2ca6b21134dce02687d3625b118d1e7680156eb33d19b1c756f147d050b54acbbf1a8996e3c3b8838c16e49aad2ac00d39dec46e9d8778fb4460d5832fc83b0fad8680330039d529fd6a7039d6d0b082b9b98ad55ae095de3b020677f2b9190e05b87920a9c94eaa5701c6dc2d3c00f23f73607d851164a39759efec9832516af27d30bb11a8deb0cef654ab0d9b9fb8434f8a20e69598d5a30aa0a41b2fc5abc68830673a498931c1fb2ed638a110e05b5e08218409dc71546c9129be81ada190f10dac09a75bff27657109209ad395add87501977d384b26322403efc9b7b472ba28ac8889a7123fdd670478007fda1b799eeb846f50a7bb07d953d33e9227a2fb411165c5beb98f0946b4b7f57c4f3eb23ae443c44e74eaaddc71ef7250b3e9319af58831491c164444b06b5aa0d5cc660a128d7caf66f68cf8f0f90de4c978c9769bf912df6a67d5ef61d806faa8109978af05edf62a6bac948f6293bcccce0ebdc6cd77a378334d15950765d5f8abf94529c16f72583b2f4bf4dc9535ecaa325e66c276a5f3a0d67e36d07eeac2b8b828ce20e8bc5d48cca36e1b0a96f69979881bdf166f9ff7ea6ea7583439a25d2ed368fa87336aec13f105afad55e49b94d0f5c66809edd92f5a6c97271008bdaff2a2c167711a8d336624f0ac24b49fd9b62f5a67cdae07849601dfcaee4d2f92183756f466e7cc3315ebd4fd43b7268a287a17c3f9e05e1f08a66b71b471b4880e24572a614337f6d72a991b6a27973f5c93a8407bf5f7e8d98329ccef83d31bdbefa3dcb0274ef713d9b4a8c4acd3cc0efb10b13a1b973d9b06b8208da3058b6bcee1ed9c9a301851ed89549fa9be6fd5369ef9ae5d0ad7dde4a0e52d858729ce27654b4a8e60897d1e2a36b819ad686359e87191f396fec83e6d484a26e293b5440422c92501c9b81102eb597455489739859288ac44b09f6b3f2f0d6d9b0fcc5a50eaf8d5945c9f44f8ba2dd5d1fd0d50c1c44c42a8b01482d9e2bec8d216b3a94a3c4a7dfa6b85c812257c6c249795c4e0ef4edd92e4579bdbac217bcd2c20791295c83e04de561cfbc0b9c6a6e637b0f66a9b11e5b684b3d19acda4998ae2603a93aeca1a1af599743a09f56fa472ccda8bfe663ef37fa27a8e38a8b89cea9b95a8c921b1533fd92cb2922c876f31eb4f53b51a3f3445e0b83111e804c477943fac87466450b4b2fa10b1ec6ca19f8f341cde15419bcc7c2372bd45ccbbd113a1c2de53af8da8293dd59bc8c40cbb73717fa287fc703675751c6ebe5e63e2c254a58259d49db489c678a5a00d60d4c0323e14067b0197f79972af604a0784407de0326e13a390b785b7e7d6cf38f8e2248613c4f099c46aecb6976f1376d0d75c5621a0866ac1f7ae0f0d93904dae64a120203434515c45b9b097f26e220a98cd056dff90c612a196d83dc8ebcd915c555a3d479fa9e3e1583891e6983b0b8b12bc42dc3df7e6c99db5cc13571e9d601252e960af11799f35b54c44995043408a55e894e5d926b6d0f46a5a60d03b7a94f91509e20d37575ca0a0e9f34e7e9c9caea5b93d493ed3c44c97b9d8fef67df1acc8e0a01c8baf1c48745aed1a22868d43d68385c5af250c39a9cbe0e903c60df2764205691da0019fff90cf04fc210bbe20371fcb5b93f1dbc6b4b59802192a32871ca766d904fa57aec1506e41495b4b9108c8f181d414d5cffd53066062ba32b56e6bd5913e20effe0a135d59d2f95f10d6defbc38fbf1ebe89ee6d0a4954f2a40d1761dc2f7b9dab75335427eb98cbf065d17130a32b5f2e05fbdf51a40075c98b19898970fd5087e4689ff1c000327d14a8d503ff6fdf08d70f6b71f7f28b1da49d611b45815d21e608bfb3fc23e28d5676ee36ef016637636077a17f243e6555d5be70eed64b116c9c9842b840217a4a13e9d31c93a6ad66cc7f524ce93ee0bb6d458e0c31ce8462fad25b3816f2e32df3c5b60c7c2fadeb13c061c170e90e5526a4e62d2729961074b905c4738b35eee92bd54381dd52ab3f152a49f81c02d83eda7a85ec9f0597735522dab9836c6b03eeb9c058f0fa3123718b7a57948429b00bad4caebd9d4c9a808e5f0ef69e35f6f73752986d75ca86920f687a71f28e943b7242f7deafdf269582ef9edacc2af96a4a980914a20653089962c95d4cbdad20263819601c27cef7f2f11b0187889640bd24a5a5e142c93d6f83b56ad39ae245444741f12550a692cc3c15f1ca29dfa87c92abfd9270ebc39849b652a0cfe6d21450406f231f93bd4ca3c280714525e567b0ec8ef94d2fc6e146165748ac93b7c670d4e4696ebf12f88dd5033034a45fead4fa90a809cf706aeb832deb13ea75e0e66e5f00cd68ffcd8615dc65f368dfa7cf6d033601facbe05998a0a0079c65427f8ed5310c614cb5cca792c2de153de21e42ee0a6f0d88865d703dcb9e1679b739288ae8cfd9db91a4e64d5d6af8426fb2d1651c01496821e8ee45008a30f8285c77ce46bc1b77679802471fc4eb08c5a04e09892b453ace9e313a60346ad9bd9d11f64ac4fc5911be7dd824ba15156074106f39721fc218b6aca5430168b96ef586b85dde09ffb8fc42c4d2ccac6358c5b74a03f62beb894bdf1a7413d836069b1ba7a3b089e140d2067a508a6059ed9123e9959f793f8a0ff5d3131348533897e1856930049c2de0f77adb1ef883aa94b16b20b83ca7407969a56a45e0c09cd84c18d083831f29b097b324d761fe0efb97390e56fe16919cb9d2403c77db0bfd288ebe980ab5ca87c0612138aa6b72d1b9f0b52228df4e7f13ffed56dc8b09a3956550405c4e93ab9967e76c10fd4a155fec028c678e7379597d793eddcdd4061e8cd9adb2e2281c304ca39b0ab9377f00756ef9f03bff354e3d2ef82b7481a749cdac486bad5f0e77a48c84a7b54fbe5102ca62b311667195a10216009f1587499c4b57e5f15b8d061d9e7aa8387b5b2ef87fb03184247231b03f946f1a0cfdf7020b52762ca540f63790cf1727b9d1bf33a2c4d78e2c6e550d6c250bd81c96a1c9c634d66836164cba4c1d6173608fb02a0a2540a65c77bf7969a850505bc8c5abf32be531e28b73d083393e2b3ab0ae7d660a202dfb84f034bd741830fc07a9deffae2de51a78b2990cfc6a63e9dbfc9d5c2799592d06e3fa8cb440c21ba7c6f7e852b47e4b6fa70734d2b694a50ef07e71d5b0b48b9a8e91cdc561f9268b6651cb8bd650363072e3c5e2448ada57d0a44f1702a94fcb2217477404512009f88e18de86d0471d92a264b9dab16bd4e55e83ce309d68c3f6054fc87927e44d71daadcf466728c3a43e964570028ac655c894c49083af87b0fd82ce2e2bd384627cc2977d4908799047f3b5a424308086db1a635edaa695554644645740f71b4f6cc492d1ea7f91793311d4de3ae32dea9d58b8f426c42ba25ebab5c20a097fa4fed09a548838c471f3a33ecf52790f6506ce11d7249c0ce0829ada4fe50abec9118d13e3c38d650456201f01997f788ab21782b9f4455b068157300039a31f05ab1ab44f0133a1686fbdeb0d051a22aa609baa06bc4ab0b18cc0a50fa3e2e7621d999cc87fb0feed2dad488f033aefc281e243faf6805d505f24c4d809830f2891041d49ad2c9859688ece82358a08b3ef534de6b3c42409d3774aaec4ca59d72cff007fc0a8960fd4202f5da8157905cda899608cd75cbeb2e19dd523509502f6a6df388b60b6db0ca90bb83c8ad4cdf75fed1556d9645f143689d16ccf186955a505e853f811e27f715bdf2019f2063fe0a2d4ccd98ef8e2626f27c70b4d661ff822721a545716807e5ca6fb333b699880b58a7373111d3116b7c3d80b349b85593129946bcbe7452766847ee56b6291eaec397895ad4f0444616bfa6e117cc83a615c420b4ecb3864240ce55e12d9c0bb6a59976a18a6f6f7140a26bcd9b2941b9e4555897bd2122b78705e9c45cd076cb29053e1aca9d477064f9dac7a1e28bbff3b620acea153f9f4bb976ed980978d121c0e7f0d710db18eb44e932efd262c5e5fc5fa668767adc8650a135e144b67aca90e4a04c64e2658cf424463d1631020c29682c87b59efdddb370f17fa83b121caabf8f0c597f94521878ec4623429c9a61fce1cbaacddd438220a611b893045885671191b5cccf2b677ad81ff618c1e5449d50d9cd8af9510aa4c7340660f38abf8a02704fc7560561a8a0fd76627003fd2b235dbde039f97f754ffb0134011b7272ec865fc6df38986a4db5b692e2e2ef0b1e70611860011e038e15978ec5e241dbe22dce3e93ee336b3ce1f4ec1a590244045c3e5381badd604908f30849168a8cf4008abce53488020c4141c85c46c6ac5b9c91bcebcf50ae215db5ef9f80de8d5aa04bd51fc3dd35f440f512afd2b50fa5131ef5e1edaa7a76f3b764395ef47e0c4330b7b4e899a9b381ca0110acdb0125ae8cde968b0e176e353877e0f142e8779bbdcab087f3e6eb27d9458c2f6d8d786fba402654761c2586c7e481c01d121a204c02e3cee234e416ccf8802100d69e2d820f86d46569a1c665ee238afb91ddb6a20cbb7b14ae9e3ec5c484e0aba9ecd86b7ec57ba4d09931f9090c895f14fdd3b2e086c003a028a68458a6d45740f7f698adbc04b15c91b319849aa99edef9f50a708b33814a9357b75989d73d878f5c9748f077b265cc9f01a1b2b21345b80d371ad6092e0b2e49d86ddc4a1e18c97e0e1c4f03a77b30caf4d69db0f4b1df8303cb91fc7154a790ce9b736e662b5219fc629a48daaa19bd8e90f5ab24dab83bf34879956489a8fbf0c2b363a1ed067a568568a7be63f4807cd3a0444085d3dd013fcaa009df2f999ac65d78932e6c3282458821bdb8b22f76311a558bcaf1a381d3eb2e3f32df8fd567c815e3738cffd39d00a3db30d204e2c6a44c1a3ac82554eaf00e8f76c90738fc180d43b9fc67d0d81cf9030227dc1aa68e1b31f03529d84223a6e2162a8d1230927227d1293cfe37bafaec6dabd8976a373a10fc5d6cf2f13499c7c1b84c0d062183e794efc89915ee5e4094139e6e6eed89e4d5a0e0f02238ac6d46bb82200e07058214896228ce347f80fbd28fd6769e978f0fcc5bff1e6de217044c71f3ee2c5a9e7b8f1b3c86df9ff7db776ce1e084a0de0a2e1cc85a2e3a4e871a9b402949929ad3252480dea5f7356f23d1e69d62376f505bf72ad0b9f128eeca04b4ff299c66a9805af2bf459e590f949ecf30c607eb0b56f5f8355b0ed2dd78a2b70b2eb645d386d912494593219fe041f0ab44164ef4b817d829ca3c2bd3c1e9d98626b6c7bbae80171a059a0cc5d27a5caa03ec92e4bdbb4016da37de6e0fe9412bb5c65a79cc4b9184719b398884f12236ffe684e0fb5529b4b28f1b3548bccbcd972512e1cbc994779280094b45630e99eeb16ebf6b41c252a6835e885a7085fae636c1e561b0c2df3a24f78faf72c41a9a704c818682aefc44bb32f8d76e09f4cc9d5ee8aaffdf038b229c448cbee708385c7eaf06f32b8946742c769ae101132859571dcb36124022432a7842d6408f1ce53171dc581a86489f1da53b7c91e85d8e9b1ef82ab73f7c39f43a2330ed8634a3b8a6c51045858bf44d42098c0284cd6079c0490ed0bdb5cb234a0b650e46ce65b3c001310c10b7d47128743658872aaa1d1b22224592d4f955019b4445d990dd7416932a294a1145db1b6d449955eec199285bd4fd390b8d176b74e70370b75d5019e2671c1e6ffcd4d76243f3ea77a30d274cb9dca8cbdc7cbd05b09830c2c2b1b4952f631031b96c923bf2703b9499cd0fdc53672ba4074b3ea05927175d8b32d3531af104a5c710be576bc45ba8e6b3fdee0af0ca4904a7983dc867ff44c7036e275b2a615fd6e6ba39de373099d39a6cc9dc11d3797e8668e8c19ecde16bc412ff8b587e31dcbb8c052fa9461e64b352161cc6a0a71b7293be6588f30f6ab5f897270e16b30a6e8c9e592b34fff5de04fdd189de205b79adf8bfa0de063402c1aca10c2a472a9b78c59cdc85b7c1cc1ac88184fcf7b54c3eeba8ad0fa59478d686923c8b90d9a9cdfa31abf3e3cd89e4a552a68a9bb01d5ce7a34e113fa9a2371415d6b0b7201ddc08ecb7f982f8eb401e6b161d1891ab0858c1c81e56304d0520a14749e9453578fd586ca6eb9d75b6f6809f632bd51674fe164c07e1864b3ab4ce0ab8af15e4901b7ae852594087ea80d47892a2edb69f0b6be67adadc6ba444b604ad2bc346b8cdfa08ba7dfb0040923d6378b36b4849b4a8e0fd6bdc619849c16b4d44c9aec4d48766229870f6deeead2a9cc20359df2bde5d78ca7e5d981febbf118e85cc2946294dee9b59110256dd6dc204a16c8effb850798a78328a514884e153f4253b322c319e791754a567fd0db297b0939808d0c66c1b23565a4611657590b31d9e44a65aac0837cec4d13393eb9961e8327dbe821d5a9a631321303cb71a4d1e3fba03c2294a0ca5d2e2dcc9675605a2ac9057651aa29500002af747943e13b91a05ae4ba886e32945e894e4b669d4b0be80d366c3b7a6c1a8515fb693fcf35ac8af1da31ac626062a71b4e4761f1abb1e1546fb0396a8ee298289ca16006dd7eb9e719c285284cf6447ca5936881ad8c4038a9b7b8162d7d1083d85265ed4b0bb907e9c9898f0f1200366407ab2833f4b0e7f1ad993119c3b11134b5549f0aa4437315c3e8f806c1334e8aec4fca2238de660d07582442f67f8f022f9c87edf94b66feffef9e140934c6c907471e73c2542e2d643bf0ac06e3e714884640dc2ab278a111ff26502d56026a32989bbbb463db82878d6e0be58a8a83f1144f86c00c1850db820b7f654c79e82417b3552884153d733e8811f9c86222c03cc6b9e3b8e08a950ee76595add7ecc83941b9b91e07b4e4d57e0c333bbec30cd1779878ccf4921fbfdd63adb25d0c516016b561f87e934bd63708c2e7d09c7bf01157a24f6d335d458e2ae61f250f62e4ae3917ea17606b67b470571393cd0364e875b8f04aaef5c0e63bdc7dae6195bc26dbbf9e5762c6fd684b572f4deed37b208eb856b266e66aa4a37dd6d4ef875fe14e2291776be110241778b41619a6b821b137ec3897bc5e5288c0836ebf6ff8370c394598d3e4745881780a475b9dbf6604b98cd9851dfde27ccb3c9cfc6e6b758f0f65e56ac345d5152d8245d80c798489bca97d0df00674e3375eef38871b1b61dfdbd2feeab3b863c7fb894a8b8a4290461a1e44f35337c6ef47680a7b48cb5cc925c6a7d25da3a17fbef69caa35b7b8e1f7b1a2e6dab1a32422ab401c9886f5f6db7b5bfe87aeb9c0a5da46a5599cf1e3daed0a047ce32babe31c84b1745c65e1f36689e31b4276b8fcbe6ded5d3d468d04e33a3b4dc3db9e19af1362f6ebe64251e16dff8d10d7dd4233f6ba6020bbfa2e44d267bf929162ed3ba75f4fc55bca820dfba054abca50c4b3f7a7118d24cf9b7b5a5559a153027cd4b0cfddf80c4b624055c7193d5c0035fae1311d458d181131f088d73fd5d9a096d91e08045914256fe0e015682ebed0cdc788edc01eda3498c03994db3f86b29ed51a6a854159209c52b413644e588a646d9976702dad852f65cd528ab97dafb0373e23efa5476f8b43f48330125adfbbeb57e200f0ad92d60b7fc0679d2484792cf3000422bb60ddc191ab3723f5efc1538a78f8594317faa5ff01332466f2f240df05c31f48f93928a42343f9c9d05b07b671e94a0e627a6989393f0f8538eb4210647480e53ab9dd30e9f4256feda5e8f1265cb5ce1fb87ed589f8f9a33bb48dc415de1efed63f5f6d9764c4294b7fc4ede543ca365cbec4216c000c1612d1a403d538de3a7d5b5ef50e6465355576b82300c9e5eee2228a0314711884246b31658fa9ccfbd6a97f9f673f4e07f36b6fcdb885fbe90f71fe06bb08e7e1f09b42fb730efad4a90830132b7336f2a811f76259b6bda2b8258e90b19a4b63876c176bb618bdc37228413563feed32d15f62e720ceb15e75cd780b83766e96b4726390b7fde561a6c82da78029dafc383c06b005743cdd5c8f47740b14454386d1b81c30d3f7363f75f81f3d704218e0546764b2a50f01882e91f517830cad89dd332dc90f78bc7ef1473d9778c88b7bfb8be7e14e473da6c489b2fd4562cb11182098bb2eab4f802bda198ab654e05a0c9d521a49274ebaadfa6f972a7bb63fcb0b8482e50e5e2b4f2e0206f0563556f9262db6db07dbb0bab38e53cbf32ad450a2d1b5497378e6266cd3282396504e8e109ad80e8230a4216a58bb08f394904011234ac00dbf2256f7ed4f8ac13a33f598d45527b31a35d4c1505b3db5d66054436d75ebd5c0a4965a75d5a9b31e06756aabb5a69e7aa873751db78b071ea21bba9d57bcfe3d7078a93309b450fbd50d77602cd9ac6349b34846deb26884eaeeda2d03b0080e1ed5be202aa40c27dee90f17abe2ec44e6b482a947a9f5a2ec7de69825c426ff65cd1968602465ee123199f833c07f581ace0647716297106adc4370f2a175f80ecd3541e3fdf2601bfe78805e678b5b389595e0f731d788ea8bcd37b9082d6acfc5822402c862ba82ea3eeeab71301711ca7901084604f23bee5d7d85f04c8989e2c13b56f83526a1d5f292e36d36260308a7bc0e6f60ce3e241830920532452ad4aaac28b4b57d4139113dfeec896bd5c9ff388ea7919ac8306ab1f16cbdd18b70ba66266d41b3b5e9215e151bc289ba58b26d7c314a89dee5b82ea54459f243f441f7c31333e95dccfab9433b9a79b5daf1c69e9ee0f3710686ffc23986245738f496295f13d242e741ae018655f737751d972452e5fff0b138315a67cad699ec1d4ede93ffca677ba94604681bff8c0dd9f619a50bc4c17b21095c2d065b55db596b85fc882418e7d45a6ecb8f62e79f32c0b52a19a7575e43906ea58f75ec09586cd9a0773ae3461d603df909cf68cc0ba00f8c80fc46306aee38c9aa172a9358a39e33908584622bd55d02e6cfa42c1da0ce858151e0459197c58120d035afb31ee261384e4762a509b2d2342751bc8c776b822e282417c1a197a017a990376dbe85b7fe39d54edf6ed4bfefdc88d1af486287a13a17573608df439c4e729d791b094121696e37705f84467ea0b8dddba294a9311789a8313957865474c58da724b54968a7748094e96575ea2aba9267a031882a44aa4e57127a5502e38f40d1fbb12d3632b5a10612c0bb64179b8a9bdd4fa2dcb35e8648cccfa1b571edbb49d211e229217e994ae0c1c1eb032f93bb40964dce7e4e8eac17bce16bde35d82d56771543079d998186f517903e3ff50ee09fbc8988c1964d06140c380f204e601b652cfdb23aac457dbb1821da4da23a943f554e2702f18efb254ed05833e042cf00b72cfcd84afb802d6faf4e7592f58b44f0507836874b8eb36fd390e1f44bb6edfd62c16e2feaa2df05381e5da4f20b16f989233e8545b7ea25f42bcc061bb516d18339d696e9fdb61a6cb8feef4a65abf61c040a19f7231bb687224f0fd4f710847bd6c482e31511f9dae75593fb039becb302107bc25242e4c365b6e03e6ebe766c6ee47cbaa52be56fab996b67a6ce1a26eaa9a38eb97aead65187995a35d6a9510b33b56ad552632d75ccd454b7562d75d4b9ae6b81d35ae50af32ac7ddcb1732d9c4a942e2db9b184e69f306de9f1958c048ccf7f4b797d2c339282b9249d05301c761365d2b1803c42240de45aa106791f609b4f754a497c95bed88804c62ab9dd6b5493d566acc6e1a17b63568001c0df5f9317ec06a13ff8b365d286b13c3492ff05636bde693bb17ae1d6eba50b0492ceb7e3489d44666dc3efd8ad5ab9b25d737d7f8f187e593eba3265285d02a8ee7c53ef1dd6ad31f1d3c3dbd7734f91087b30262aac017bc99e0ccf4fe66692427eeeb1d824a9739046f6932ea29a2eb33948faa8f602b4d1542cf853a9f94e7de090ffeed80e37ffc0b4887bde44c8419c818ca5df07dca2cf05d6cd15393a2371105cc08d41961da82fa0382d225b949265a937e25d639aa85ee31bda734b40601849e6fc924d24482f097740b594ba28be010572bad4254d5c0310d74a16fd3c0055c2087d75bfe9ab0c78babfe7cd2dc64cb5212f2445e05e17c1fdc3e2f1568bc5af6c73f075c423a70c66347c4ec12977e8c0422c93761d57e0bdce3a44fa270b54dae1062b904c2c200bcbd040402ea80ee49d8a855c8fad85ccfa55a85b88e132707028fe620860655c1ca07275fed18f749b89f0b7c7eec19ff83d126729bc246c64cc268257cb01e12a58870d16baea59208ec039e7ba18d574095d73a0e483033dfef4dab90a6b0a60c85aa17c81fabf50bcf7121b7b93746bd014164e78cc061b58f4bd602d0969b82df435d8307217136c737ab1f436454ec13ddfa09875811b854172e411706a3343a91ca506a6ba18bdefc5877ef0a3f6bf0d1f80ead8c053f3b521d2c69242642d955cb2d3dfe604f9538bbfc97c636d3ffd5152b2df9a3ec832841453a46874182d13858d820ad760a569d2450b1f58ecac9575ba3b3b6452ab8092bf47990aec48e09a5aa49651835419b1a620a5172e157605788921036b1cca5020d66bfbf4591aacbd9988af9d4203180ff1a84469e58f37f0a4d2422852841661e6b65a1a19e4abd5c95c05bfadf53fa8aeaf594d67fb2a51e9a73b697ee78fb90a791c5d183da584f45ce0121a6ea201545c7f7070e1e14a187bf1fc561dfec25a64fbcefe10a30a78c89d04f8de0f089871ab0a750a25da5346c2546c489a67d8d57cdea4787ce33cc53e05fb30381a48929dd2372776d3e4a4f1df14ae17800a310605bf446c89b14c5e0f99bc7767d22f42e39f2c1a5938b916bc36e8c10fb44062c05ad452c44ac754ca471917149c5e2e33ea17906cd197a3eff32357a1c0400b2701ed42722801fa26b3bb0c13928eb4bb58cf74b4a0b23025e95bada488f55f0660199e750f35abcc7967e3a4675978e1110fd44cbaefca081be00a1944196138add8daf76afb8de2c4ef7759f46be08430e3af3e9eac7d08ed1ca248c65a9d2f6fdd5ebf4cd21c30ab08d18bbee3aad5cb2898a3df3a2b2ff3a51d4305674f2078605afc2fba60b78d70d9e6d980c10e302292a081782f41651d693dc13e97fe4231016183d9cc1d84d1fea6fdc4801b29075d8295ac89e074ad94b5b7a753005fa4775315c9264a7d0815129fbc1927035e9c4e1fe29370e1faec226f7defe52fdb1d07a276c180e26be09f793c1e4d311a6a28e23c89faf72a1184e87e83b1b371f255d7a20ff913d1d13c078b55d59d0eda540c9fcd545852a6e508315f48e14bc4cc517164efc235cd23ee69fb08df006a6f794fd08c81d628d4f285d986a22a6eca459ebf0a8e6293a58c0fbadbf943efe7b6548621f2db20f0548876899efbea5e29ac807e611ed72e5f8170f80a80ac8cf3d2e9bb5616a887b4cb67663224bc2d532d3986c75a6273a5d0ec65e8345e89fdaee968cc21ca6f04e7d225e180ca4682869363f5a2f82039ed3828df5b09a82e3dd7060c3464179877c69cfce187d82b5b87ac04ac6de361dc64a5f619cbbe92799149ca251a42ad89a86172e26024ccacf948a83976823f65562cc237d6ccc51493e8c83a9f60ecce53be2dd3bdeea1e4a85efdddce0f30484f4c1772fd82a33a8911ac63de7f8a5310b8a0f96fdf84ed21078493beed8b38251bef3ead477053fbef8e1af3d2d46b6d0f0052723183d0d4721b1a5d76288b67a7cd077a29c052c554d788b8ca00cde60da3c31fc97b5a045b04d16ca2e6ee0b34191c7bcca79c2cbd477077ffe768eb6b2fb0e63682eb1cf8130d9a03d86adfb04ed894d57aa0eb207d3fe7404c86e20762a567aeba8ae8b8a9f78d07dd4e9e7e4384e88e230a85a4488038e67833d4c6de207635e10fdd3ea88f988e2cc84266bf94c85153578169366673aa56879361febca13dcadf36db372e0a711567c9c61d99bae0a39e4190cec2617e2e7e14065e9720a4191b212d15b8da02d0ea4d0cb966c0a0cf9fd11d787e44306c8ef6ad0b62a4f9a1f4992294cdf7e75c998699ad6745e1b9a84e0447374634937fa2b890ec80091998958dbcc2eca7583b0ce0c04017171d32189eb66caddd47f98ad4dfb423071ba74d6b77f3b64d7faad54fb2dda5afe80aeee092e38517cb9a326b5023f417b35a3de27873ff5cb1cd0b52c2dd60d493f1ac68f0f0b4384519891369bad00656aca293574fc178a60d1d182dabb7886aa20f96752b53fa4f69a3362c24f0091d864a8956128cf8b641373631b6008ab0cd648e858cbb390742f296d71b2089d462bc201a769f9f8d0531e9c9f21fafad055d317b671cbcb385914b8f975e8069376ccf233f047f631550007795bed80a866a0e3da34e719d056d499557339e981448a84d8a280d080b6355c09d8808c6a42c89096df446d38785e64013866c8c926ecfe6780bf800ce39605721a28132c29739d6fbc5108c221643a624a3f6740508127d0437ac20797393dff5274699d8320fc513846fb53417bd10b0424178998520fca4f7c7d59914f850e107d9d2324d4f0ec7066ba8e43af8ae63b339bbd0d3ccef8c89e9bd59f4cd7b12758c1dc1725f398540e994c8381ea0576813e084d4e7363b70f753dc9c544719d1d7a0916b4f27d248c89aa1ee828344c423f30df879c9569ee79972f6604085caced40aa23f9acc9189cedc2bb66011a30b602efd032525e75d4cbe5e2e3054c1499b3ec5cadad6c12ac846245a39bf112e9a3aa46e677b5e49aab45ef5c691295c98ef74285560ec1a28aa34cfc4ed0f6944beea6211bccc83118cc6be9d047e5d0bcc548e277942abaf4e5ed28c1079bf99cc13d049c544131f5b64a235c189badadfe3cf16f5e3e9d1ab3ea022c0af8f522500aa1057eacfb341d19700916e159360b4e8f9a1a234b0d529a3f2f5f229090cbaa6599ae7cd03c12f8a379d96b342ce46b3de455a0f9fcaf48be7515da41c63b9776c377129b858c2091ba44909a3775efad42fafadbe597a967c8faf161ac7942abd6cf1dc063abd6b99b39cf226f6363326904cf1d173d53ec866ce3c9dc6cfd416b5bf9043e10eaa3910bec3ce1391126bf60438654764946412541f0ca5c9c0ab9b0de6b38d2a494b0e86eefbecad423c56a65b2b2c4432adbdb8aaf0bfacc662d2717ed0df817cea22d01ea8a2ecf013ebf947cc9a5fcbf3af9ee4cd56e55c0bf7232cd13f3d38a1d096c4d2ee95cb078201fb99c51fc3a34c5648a442af7914c0495204f7f84a79196324354ceb20ef8a89c5c14431a51c2b92fb7c5e5d36e5c5904e6bf6349faf8e6de015251b1cbbfa15e804d2d4ccfdc7f3db71ebcbc372fde9f3a7404c8c85e4e452e1492f67de9ab8ad133518b0106dc9ea55b15dce75cb434521600cd4758b978e00c3fa52bed77c8ee558d02baa88f032ad19a03701becb37ba099fd1e5f0db2b025dd4977d24e58f19286a1b984913166e12266d74418b14ace04e45408d13b6603ba246721e3550b1f88996c1674659b67d77a70678e69d754a3a541412dbb43fcfa811f99e4962dfc348f34c6637c5cbc94d2921642d0512da29a68f45b1021b8763eb21c62e2a8de492b0cfb72d8740abfc1a820419235e70f7387a50893e5e82c955f343a9a6776e2c0c9d5279975debce474fbf3b6e7ec333cdb68f78cb2f82855e1ed03d6691aae6a869b84e5b3c47f4cc1d7852874a9c35003fa26e660c24e26a6be5bd88d06fcaf0cbf24bc9d0aaa2771c5956473e197e8a26825573982d28400485cd749ac13e87d6d7c7c150e16c0a51690f71bad11c37cbf4e744b28f143916521e2c2400d85ef6a3cd71a6680ecbdc5efc0144d4918b14cf562424273152134924bc39994b7b1e14a7eec91176233cba523b1854d64a86bcd3e8cd522d132b630a031212400e62b21e69f022b71b91cc50d1bc411c550fc5f6b6c877c246afffcdcb9f8a57f785186b2ef6f8451ff812cf40e1d682e389794e1f08f09aba9d65471c6e9881c42f4295cb030c58257865b5a61eec91f535831d11f53b2898aa3d0e58dea0fb8c1bc91c49c847020ba4e693a866ad28b2ecc66ad42370e37e0e3a2022916c9c948c54a60b9fbbf52956e35be0aabb155d2b4481763cc871dc79195818924fdcd0edfe5927a65968d327d8057d13c8ab3733309361e943a3dd7d21fb061655163886126a9c39a26e69cf231be6d5c713d51b5a56e191e65bcc5d0a8719c470b6b8298a4e4bd540c0d683811b1fd9a2a71b39509ff6ff14e0c89786e699c0011230c72e7aaee2dd550456b5d5db276ab391199c6b6c5eabf2fa5f0a8597290582ffc0cfb2a786dd001aba2bc200f2942f0dfbe7d0b371071f1f1afad2233bdae5c1023ddc5b59e4dc50cfb071b54ccdcdc27f7a2c557bfc67d82a3a128bcc0918fa265007ebaa17a13fdaeb903ed2c5ec23e164084da3a7ee30b7302c71b689c1f08ec622007d1ad486873648340910d8ddd83becd6f17aec1d411675ab60aaaa3b2ace0e216d088a42bc91af4ca92a419ceef6aa1b58e2fd4ef18c7c63b8c7c44bbdb60e02d0e85a58ce3686147fccb71560b69f4f875662eba1f992e753298c54f3267695e17e748c8e085dc6d4da6efe5b96e79251e22718af46b86c62533acfbdb7c7f1e084736f1a4e1e096aec034e741b612a51c971cbf8b251834daae753bd6d6e6c4f6015ec00b1ce4cdf6d7fb38160fcd53dfbcf8dd23834f4f6e95b5f84d4f02c9aa1ac6763bde1ffee2044d48182f163202f52de1070bc1154ab882b440dd2d8f4bd0e2eeecd029b185e0b574626563ae65a48b79814b9b1e3a80ba54ec70f7964dbe6a68f5b675735d622caace0dbb5e996b43478ddd36924f20516dc79f1b456b7a0e90e7ca906cae562fc551db637b8a16cfa7826d1548e2148281eaa3f2b97f1d423e1c585dfc8b143f1f9f9d6c2b3a60d37c986f42149946485114c2684435bdaaf1daa416a5002229c4b84f9a0aba95d44d0bbdd9dfd919ddf5093247a38b6c340d6ae5cb03080cf80c003a3e8a88ecb8c5c3262868f51043e4961098aba130298c5d38bf6aea977177fe3bc4879f171bb0a0205163577020a2f177921e912f5bb705227ca7796d2013695489514024e8e3a17385944e5552dad58cf41a87430f9d3e4bab595272610afaa9e53ecc8f52c13a64c77e66d29def0c15493bf269b7443554f6834479573e63df576ab22e1da7b50e14142151e05d083e357bdf808355d0fd2cca1fb69ab3b33ae34272f337c430134e305a0172e755dd08c0068ee629cb65ec1531312ce8ae7e66f6a8b0bd4ebd86f4c65ac8e2638eedb1d68a16835b572c77940a2378db4c13f667f079cc513c285ef4205d129e6f252afc705831a4851f2739265894a732cf7f4099c91a1e8bcdbe05a69a3c47d6251e6da7857ab71c625091f108f13f2cbdea78395358773084700c0b609267ef38d655cbb9c461c85b6ca4c4091b71b60ce8681493c0c059908ab49002c68cc138d4701c7eb1ee812e45b11499653cb08b85b5eb1fe26f0575114eb18a26c4feb8068053a184af94bee83adcc1e352f14f8d246435dc86e8448f6ec20205a7bfb1a9930f7fee86c8dddf09bb0ed911411cdb9cf6d6999dd4a2bf34420f60125315203cc8d886dc5bf591e544c7a40b59fe4e7f92dcf98cd35925a2c1599d8b1eb891f64f5736868d9cd9996ca4de46959231a5240c1c502bce94ef9983bd7f33efc2305e129e9a40dcbafc0b44f210abecf9f224688df093c2777a80b6c2e8d442affcb85f6df018648ba7e68d1ba662104d10895b9a5257d28f80598e2e6e2ad90f06915fa1937f8d77954655b0b7a213a259f061ee6a0c1b890d76d17c1df4a21129f435e83e08ec0c7d923f0d4babb6da391161f41c689882ae8abda020442e98cbaa64767afdec92c6b0d133be8729c4cb1285604d1dd65d25dc20e3ae0b331e00e11f0036cbd776c19aebe3db783d8abb06e3967fda441e5dd59e02557279b9f519ae30310d5161215c507392bb599e68b847ed19b67f62918b49f7b271d12c45cdb0302e80064709c49684e412756f86cdafeb48cc206fd8930720682d96671fd76e14f202294c5685a26a5832db7c93ee709407a089027541d5f545c2fe594cd4901ec66fc1f5597a2373c971d15637facc7d21b249c687be922ea5589ce2312581d41330243ea651e3314b1c269375858dce37de6db3c256a05371ba6044d12179f726d183973a1177deb1379d1a1131b53755d37bdf95c801d2c8e260807eddaae79832ac043335ad2025b0303a5fb3ae0a7fc99d1f00f2e7292dbb7d81f18c754919edf9d93a92f11f80825183d06186dc423729c2a8c9d029bfa8c0b2c3d4579ecc0eac43f207a34338aed0a60dc933aaabb172777f046ca7ac306be940106166f083c96b76f5e3b6711098f7378f1ba19217f90d90fba10031b6a9eb69ab12b98fc2a711dd0985328dbc5a9b813c688f128a365e8d661a7c7e8bd76503f21a606cb3355ccea6dd31ee78f7f763564968c50e4f52b4c094a58dceb5e66878e75e4fefac98fcabe191b4dadef6b55d966045b4bd90219ba83bf5d0a21a555a02ccb0ec77221152da1b8d72f5946ad74f8188cb49733910ad2692c24a67d1535d9926c18ce2c1b0ac03655f908123a64c309b9a8cd1539442772da3eb5ec54c6818f4b82a2028c19ffa5177aeb7bd9825369ba985d80b0e5674d062d32c25e7ea795cd298924d92c69bba40a4ef995df1541f1f9f33471e696fb14c82bf890accecb59c9962dfc83874e37b360cbf0f63aaa9db693bb3d02132c7180a45964b7f2600e55e649257c0d1e6ccc58d647840b6cd9ac8728faee957e756c35ab17b1caedef86fc0a096bf662d1d6317c0986fbbd120eeadff740a04a442ec795c29c4e3af9a713d4fe21d69b017eb9efc6cfec15b07f5b213163a5968087205fd3bd78d4fda38441aca9cf0d404ca8118f1fd5836a8185f2e1d2290c3635729093748b9f8678a27211c187c6f07a738840b6b781a97d35f98e08eb6c639c90d4b6cb614b1675e8cc07522a4235f26eefca5a26ad58f1c9b3320fa9fde3d738912244160f6eaab2735e8d80cbe5e831f4202eac4dca05a78327e0c2e9476d22e79525b9f4323f2455ff5c7a9fd44e917257397a168e0a1f125a1bf954daab5b2da8b5eeb2ddd1026f073d57874942766df53c565d4869f3bfec9a244d86346289d84f85cf57f28e46697fd39c765a36d197055b8100ba965aaaa2746dc1863feddaac34596e5aa150bcc66f3e1668daf689e21d9c5650f17917a07631fda4de5358966ed90168c4499e8ae8289b52407c4a4b0188381b9ac49b94d8ca0892689e6bc701cc7001de8d70d56c6d083bd81c681e1d4644072790abf79e39e14196bda893569f22bd5fdbac9fdfa5d0b7b9dbce91f5bac4f69d78466dc9261c94e427d1991864a0b5a5105bab1087ec50356c3dcc54a5af6d97e41f159bd468219f9e813e6c407f5ddc543db6792f2c8391d8dcffc6f2324621273f759d8b5e80420eb18f8380b497fd78f71e0fc8b93162ffc892d8ab404818dd2b20f1188a998c3e2eff2d8afd500313de03d90a08fa3fc4ff26f5c6fd1e5c4165ff32e56ba0ec2ac4740ea78ee69194303d2948543ce5182ed442ce9c7c5bf39c90313c326f719c117c2a9f394c2ac0aa20b08643321f599489b99f3ec7b9c862bcf2c99ab569b47fd4e2b2e730cdefd83d66d86a866407da707aa40e6bcd6d8862c59d9caf5053769cd6eb7ffa27d8c07dcb916ba871ea877f9d615d987dba7ba5bd17e7fbe7062b08a430872d2d378c8c18228aec1ef4f6708e0538a82dd2a2cbe6bc90a500412e2d6dedb8a825a26834355c6dd6e72ce7602b54412f8c16d3a2ad197bde9790300039e7084afc78653733b5c838b844ea2193fb79b1ebbcef9b5c9d879d3fd9b245385c4a5c20c15602883c04adcb06f911b2478b19e0dd3089190d61131dafdc1707cb00d49740d427a62dc00f9ef9bd6ef37af3e6077be80e2e8f2ba40e087a9d7cc6c4eacbac22374d58c9e7c081944b65f9813f6d30a4bc90305e676c0b8347a2d2f3ae1557ad13afac18c5c00572cce9e070cabeb5907291a37882209f701337f2992006991550bdc730e65641893bb29d606136cf431727a8f13abc84fed9c0d77331fb203fdb12cb437290c24c830cc28116662aeec98910d53e23f6213dc813a4470d96296a56a2a94e5ee5cdb916d133307375ff1a3ea28e3b243bd518e252b5fbbc76ed65cbeaade086ccfc2902f298e7d77ef6dcbd50c2cd20558481b16ff879e0e37fd896f7df6d0e132b433478635db323f166ac224e432a6846a785daa59cd8f5c6b42533c71338267ae1a107a6449e7e28984e132de773b2d569da5fd5b1158e331b34c8a5579d18c8bed0d664ec5108a002ff8fcba71301663fca6d81b98c4d015e3ba17c9cd80b54e6cfccb51286c581a4d86ad9c3651b532a5a3abd2e20a7a4553f0cc0a21ea866fa76363bc02e630dc06be8a8ca3e5886445f7933d2769790bac88db6a10983ce096314e48fb0204cde030adabcabccc9961637c93942c1aa62d4f4c1d27a6707e2f11dd88481aeaac29d055ed8b57ae9abba08bbc269b703a0b79910b085ae6bf6283d079bd013e2b283b4207d8b5bffb9118c2c93ff6c350b6e9e111b49cb7350cb07bd2fef4ad2c449c04a5734c73d3c370368c2b4ae0e89680eded5191877670333063260a040d2f2af3444b8082e2131343eba108f590164c19b7de9c11d4992419e6afb785851f49fa424f23a8ca98285b27bef961e2a8ccace4be7dc00ef279ce8ac8db28db8e889b9ef3d67131754f437b829b8068dda1be47efbf48ae0d3a2886fbd70af764a70d0f2923a0f1ae213b84ad29bc912f8a4639bb854e525e43550d3f85e7a112d547ed5339821d441f3573b1e56ce8777f27a37d0865cea01466cdcc582302dfcc280ec306b5d69970a8ed8cf46e44c1fc19caeebd0d070159237f8f53d328a851722f1d45b65900bea1b4e34e0b4182c1bc87634bbd8846d87ec9ff95183b5a0d49bc6c814e51cfc26b5257589bf422d9988362b88438c1e07fbeff3ea98d06ab04e1a776c4142668b106272e57d39cded24872335936d04cb708a10ef4b18a8b48c6c66c81309688e3d4345f6db685c7c17a127299e56c1ed1962e950c74569d73dceb47ac9d5c7ae18a3dc68429f7aa95a0333bd256ebe51da845a7ff444218f18ec9c5f7c8205b021ac1dfe67679d360490dd35a69634b6ae608ccd2cbe4ebe0a1d7c6787f62c2d3879eac59c8178a226fbac919232789746fc145c9a7bc3a65f33901c1620361afcbfd2a9c0b94266290fd9ad65387d07c53f55a611f9bfda39d7bf6ee57d7bfe8ff8d38a5063910c1bb8278b06f1d94f38d6f655fc38c9f190afd057c70a1fb1fb75730e61e64f2f98b01f4bd2bcfcfbd0644d40d8d92d454e0fcf3b928f5549bdf61ebdea0bcde831cd46943987fa4e0baba79f9bf57096d6d60f4410ee2403e228d3e45a4f02915e587a4e063f2a77cb873c1d6ad6a883ad05bb0d14b93af05677a8a57b4d2d27f84b98bceead841325233ebd7f19b21107141c403be7dc595d84e3e23dd7131d20e951c9072b2bdedb07a94850ce6f6bf6f4f7249a99ba9cef6d14f66c1b54666a028ebe57e8278309c9cdb8beba43ce16bac3f989413af5c899d1ccf434c40cfcc87df120a9367bf62dc9779dc82616114612180cbeecff2519d12371f19c24c21b0b3e6e74ef331e55b486f655f641bcf9b993951221cdbab01af9e95355252bb78dcd2b5d9e910add88a9e1cb183b3947230e7de8af4fabd39c97aa9e64c963b582129cd3c34ae04d07536581f567f3031a9f0189c039f604a6eaae398032ec2cad6bbbf97c9d77748ba8f6cc0e672c60d5492e44ac7e518c592d114bd51a240ef901c9ce595824ad0c2f0103a607078dc1a113e9b77c3ab9ad6dc4682344322cecf4e79b1e4642e4f4126247748a0a340f56e540d4a864a118a279002e3a1c30d7cfac23178a0cf4a23cdd748b7c7e4ac4e23bfc75c58fdb0b665847686638a7df4be1103d25423770817d387ba01aeb35123c606110df988fd09b203216ca430073d054a946bc9aeac1edf448d39b557ade501aa313d7bee8d21fb54add20f96494174b06bbb2048bb9e97797c8e2135ce89a713823423d500b9e4dc796bccd725a7a387c5c19ee585c8c9e1324f85e2c983041c7613a1d955c5e0b054dce21267c8d34078b2740ddc2d9100c92f2cb0479f42cc7ce6c12978ef63556ba7997f3e371abeb7f6b1e0d8fb53a5eae758baa250e4241fdf1b6365f7e1471e7a5c5fe70259152f7a209edacf58ce840ce7e7dcd09b36e6c4cd7cbc84c1980f5789643096b50c043f24a4df7fcd161ad0ce1714bc2240f066c0ec0ef556de623bb9b136615851c9ba33499c6efae755674d74e78b8d59715e74551ce45925db73d38916b116b3926a78a2e121fb44cdc5bd8423823c413b99e25bffbde78d9d06b5d19c9aa4c576b8bbdf73b41fb4c03c7467391b1cce7f014cc73dc4684f796176cb3f3160eaaa8c1917f42af057a42cd0e8831807b84e594d6745c226add56976d3b2bfdfdea5efbbccb8e266376b76be430fafde4d69844eceae99d7f4c97272f9e59061d23e7f67374fadfab38f847b76ecb23f335c4f8b67b728460798f5e1f9483d00e2c42410ca0c9bb9cbcac6262a1a84a8bdd123ab5c581d5f4ed509b57bec6fb8b6b8add5cd631cd016ba76dbd27b7fb4e5d2b6a5caeb213207f0fd5e6382fe5dfbb0ebefb39b195c9d76d38f9c47bfea43302e28b8d459c81ce05cff306d97938653671c506a90c19f2ab440965a8ff39ca30350f2a4a376d9477d1f3e662a9e14ec918d17e373286466a6e23a613ac5bce4ac62e4349d80356733b1e60ed5ffbab6e75172cd190a02029834a7e6b9e64e72b2928756985b73e26023e92da74c4e04e90a2ac774db3d22079f586b89ce9c8be07d3463a2754962c0d0f733111ac3ab1411cf0b30cb55a2637736d4a5a9a44b32139a358e00286ec22a69d5ec06843e9cb332903b20b9b52d8335548e5e0dd8d3f64674534db0ad6b466cc8788516cc631a4ae0245b3405662e63602e0b4bb24f7e8217e6c0d924f99f2fba905e78e128b7e3a3cb8993f93c411fdce0c765202d5a56cbc7ef1ea3b9e4b724c537f95ffcacbf44cf8926c0b18aed805d009b922e1dc7db956481a245a44fbbcd9cd3a678134dfafe677e3dde024ef90c34ae863dfc4ec56ceb560be330a6d068edb2f62fd926ea78d282774fc9ab190442c7b8cbbebf7eb85c39b68705c0261ee4b808620327ebfa854fea9537e1edfc989db2e4e8083610241083ea0ca0969e2843750c67ea9124b2ee9061a833fb9da81360685de870914a9e645f4bc4dc2c41303624b486d87ea24de4440efd4a9435a0f30434391b03cf20044826948a78bb25024560d25b00b98c0761d90a6f1046eefa03308e08040032accc778336248b2e752f440a8d291584f660e62b5aebcaf258db2e722663102c80998b167094ecb7ce0aed3e583a33752dd66a6d870262866a6a95fc99ecbda3268d2d11bf3ba255f9c94879e7d6a1a9f126f5192460802003af9d3a41b4778177e4052e8a6627debfc0e7a7d99108c1c0ef8481f5b866a7ce26e052085f4195dbb1ac9e425c31bb2e9983f9a6df8967844a44bcc8b3d5eac4e03ab89ab49638c97d103a6dc076eaefe0c9e8a49283676c83084f6172247b1755a6a43004585e2aaf627eb7700c2ec2cbbb63b0e4b366888389e391e398cba2106bd23071cc56d0835bed3f0ce0747d26584f9c1a652dedf594aa9b0e650a3d8638e186fd65ff16c534d539798e2d14a1ff75e3a0f20190401918c4c09e5df0d48a630c0d859768d26d4aaaaaf6668ca728295d980faf58da76cf27b30203a74df8cff3f1900a99c0b0ce516591d247a2639112f0c0557e0a1c455d95140aeda31c93bc79de9692c1ca409e8d931965701e56ae3c5416a9b224f57c39bcdd3fae71f6c7590c747448d61897557d2dc634acc249d95492f5292a5b02c57d83a807b4a46ca670faca8e9b29a4da116f4239245adaacc429a6541883b45d28f8578232933e8d885acf49b7b3cf228301594b998c3d7b7de19ddc5d0c6dcae961bc86ba75eae2d135fd53eeab36268644b60d293134a3420fed541bb63ac414bc24720848fb692fa80c5fff3f4a511959ecff92762a8f2582355bc82871f1211de99114bec99262cc2a7bfba298c2fa0a70291c2bb6f74bcec2c54fd55ffea77695c10177d7e3a4c2f8efa2707971cc67d8bd89c33c337c3633d9cdcecc957f705f4a27e8ccb3b0c824840280f4cea1d51a6f4e88c85be4b9e6452611ceeb930ef476f1bc5ef6dfe6b5a4c914f3f965ebd385d9a82791bc559c3a533f191fe7588c34b635554f57c814c3a1582622539bf82b3d055147caadceb6fe1fa1002a445944196fc72c08c7901803d72f91675e4720e631207767687cc114be9b2c1a9bf59a18afdc0971f3c659126f4bb31dda0f5a01494454bf912c352b24f2586e26ce767cd2019945edb161cf0e0f98206e258d3021b0fb7a8d3a2d1acacee835735efb57084d1f1fc8757f4b369dda12fe9ba900c10a08ae48c10a08a008018509a4882129244061022c244831022812582182c4e2d4d90db1a749c42511d3cee5e9206c074783e0a5523a9f0f62bbdd50386a6c7fddfc0955e9534a7461ce924f1377e3809bf2edb7fe13ca5953413e238ff3ae4eccdc5ddf9485184ccd7fed4c13e375ef423e259444f7bf8b2829c0d343b09dd863bc3f55ed70d2b2facf055cb682867c40afa178d0a06c886a3c0c2d6b2134b8f0fcfd7b420825c795adc8a4692e41fbdf62155a2e6ff95c7be14f14fc48c08c0bc50920226555c135b53454e5c32eafb1b80b0744db47bc0ea63c4bb3a3b907fbe6331bcc283ed12a5bcfd624a3ac1cd6dbd4ad686570febafb83e7956f40160f6dea1c1e67923c3f4f8c3d764d269a466295e2e57ab1bbbb06c05bf609d18a07c15567efa7a15daeb15d8b8af1f8d082287489a8e90486f4875be19a6574ca696a4714a95659ea847bfdf5a8cde4349c4d33d031a07c94968cc66d958992c3f298189ed6c05beb65622c2f2ec840c26b0931d20f545aa2ef3214c6de1456e3a58f8beb712358090834284e111731af09aaccb367008629eae4d1d8da697600884772704f72c436f05eda10759e58f1243e09d4e22c6b22d3ab3de2eb79a97a530aa4e489883bf580229effc6409987120c0f8887fb97b7b1558187a6d40204375e9623c6ff34b360d7cf92993c6ace11677a42b0959599bdac811c30b653e9802724bcd99204f40a2fbd7320ebd8d981a74ab5b551a3d65965fac80e72d543988f213d398e0ec3f7e11728ec806cbd806bca068cb2eb988f6d1508b5852d2921d2eadc4db6d310b75fc1a19779d6d9f444c79f0c8dba696b0a39baec5b26eb12e2209c371174864cf3a78b594caa93128081e94e578da6e52c41bbba03e37a8aa060009076bac55eb5b4951f2ff636f8b769f263fc3b75840ce1934ecf70500e108acd0225836f04768cbe24e974c19fa7d33590590e4e17b85816c8e6f892e95ad1e987a0e2d8198401d4120de8781b1d100636000e942667621ab26ca271290cdba7f71c743d04d9f44f291f24911230986164d3b929ba76d1f73bb2bf78687c8a9211091b5a3d2592b8457aa109b0d5749f9990041be0933021b0df2ee98ce9ce53b17aa05c8f42062fdccae108ea638132625a4d67f9e448a29672a4908f22400c4dfed8170a848c7372417ae1bc335417bdc701fecb348af464e70c38a190bf43d77c05f3d10473021a0a9418b192db8d879b4fb87b35769b9098184084a296ad7c82cbf6ff547a5db6b62ca47c6e9f4207dc2a05cbbf3f2d2c80f06aec4f4dd036b530af44a27be5b60f1d95587f37aad1f38945f5b61fbaaad87f6e54a3eb1b8bea561fba2a31d831dd123641ff37b62a370ae815c30711c721daff272673d0ab4be724453ba71fa404daf9dace0f931a1cf9045ad877c8531040cd381dac76f33cd5b18fccb3b6ac35244df293f11ee7f9ba9a6029098b41e2e8b965b35b2ac396724726d44892dd64e5d2162319007ff1ecc1af1e38b326ff81ffd876b3a98f0188f8e99166fccafb2405200c4c0c210c5d97f1fbe44f3aff522cb338e7d5529bf9a831d32a82a0f56a1c85696fc5da9f6c87f0645d2ba967315ed9a1f946d90e69891161f9fdc48842496be1fb14cb288e11fd678d624431220c7e063fb36244312231624c628070550d8e72558c083f96f45b7d12df4fa2f5e87229939893147cb80ae5aa18d10ab3babb8336492187498a2cba5bf02cdd31f8184b2dc280a28c29a208832fe7c4aba954d582a0f55ef83ebd57c24c1c80b5cff75a96b9deabb6be364071fdc4a44a5ff931fefcca8f67b5b0d7e8e5c758beee674c65b9d2d727c9ffdeab76c8fbb08529fefa3120795f7369c1f23d4e5d9be5f759ef87044431048a2c50882905297dc0061c1a98e18931cd796e878aa0e152adf737f46967f9411f61250bd01c29cda1812f5425149cd8c3e4c41add754ec36881852c2bd710110d2c23a22bee4529897777277434f40a8a61d56a0509bd808c565b8a8e5e5c56429d0f59d1c416f33f2a638e5d56a94ac5441335a5154a28dc4965d1bf959a8beb3e26fac3b4d8a218bd9fe436cfcf5e4ab39486fd69ddd357b59d57eaf573a6d7ffca4a5d3afea4fb2d0f2538fffb8b711197576e54bd6ca1ddfdb58909236ced2ea9d05d4aa1bb839450e82e05a0bb7442c984384c1798a3913c097bbd383cdf3afe3e296122063fb3c0cfac229a29f2e65ad04a9c5f8f96582048b71881072cc1e58423598ab49506e8411384c687013994fef8c18d369a7335e7cf5fb16806f53ce9ba6e86e595ce515de755ae389b27381e0dc75c43bdeb5c7e7646fbeab286fe30b7797eea419a8b6a5288c34f63a008d29a0463d6a6866be2393a1d92ccddb5d99356ae88c2a2f8c9d4db6c41ef3e1c5d9dc76cc6958a5fe8393af37ef85856e1bcb3a989d9bf81e2ee068a9dce796d0c536ef462164b98cdf848353efe2c4bd0e7f7b3fbb3fc329b1df530ff2b3f4bd97f3f1dcb5928859c7b9cedd07f30598f2878bfc80ecd7a34d6ec2d8a7ddfb243afd7eb3586dfb4607ea954b85a8a55aa218ca0311551c655fe0310455020862290e84e92c43ff9d8a591b8aae59493542dbf72ed53f28a32d7fe25af287f262fcd444417a1bc34b759fa570adecf3fe783547e3f5816e17f2bf5683492b9250395567e5cf891a4ebf1fdf9749cb21ee5faadd5106874f71059ba63960811224570971f2244881479cdafe2f802bd1a7e7d9f677d2c040bdc08bb1f7aae925e99847f5cdf277fc629df4856e9ac5f34de1765f83cffafc4ce8d557a8c3aea653263c74b32bd96ded025161dd3f9f5b14bcff27e97a32376feb38ccad2a35f28023947b31509897e4f732e661dfc59f6f94f735cdffbeb195bd0b174d475eca0bdb13affeff7ad9cc3572639cd5624ff2cd6c3764673fcb3eca818cbb99815fae1e236bb7336df2733f518751ccf58aca146de4cf183347bccfe2c3b77a90cdda6c39dcf47d53ce1c9f19c47921f6c7c2ccee1f031c64f65955a59b5e37dd106f78c3fcb8feb9274734a49c3553ee8e3f749118b9e6456d1c6dc0e1dfd153f2aadccfaadf9f7f347ff83c97af41f8ce6fb499595ed10d0984330e8da9957e51b615965ca2a76683e8c5e0fc3c416cd66c1489ecd5265f4ef27d956b643f985b21dc2ff566615ad956c87f05165815eed35eb633056c0c0e0285765fc1300afcfb602e1c555423f5c661ee8d59c8c219d61270de0e9b6d51570c41c5e2de504f46a4e7460373a39de74d2f339f2779e6742c989f5e8dceca4523f3cc0f44368fac1c90f35dd75ee394ef2ae4b5522e76c3e8bded7de9930a9710ce2bff2f37eba83c922064a143187cf83b2fa944fa25926d1fbf385e5f7c91796332f4bd8ebbd6b63b34c694e18c06a578d73ca113fadd91bfe15c3d7f751a45c1fccded7f077be57455bab7c8d357b431f884a301edce081890494a9d25d001f0c90809ad20b4a30e8520a4a5c94ba40c00e4152c080ee66b509012274d7710f01f470002e4d6408d00e70e0cb6511bcc10e33310c9043dba08424890a44e9ee239c74770f0a50c0f5c95448007fbf56ea5272fc05af52ec374e2589d7ff784c3a1cd1a5559756257497363da8d8408a0db050812a1468d2dd61bac17451b71601a060ca8184ff60300a039af2efce1311079c93f452a970887158c184c38643dbb8ca51fe8afdad71d1ec4f887b44efb5601aae300da5acd26b6759945f08d21b4a42dd30c8da34cee4df8cafa8f1d11d43fbd46ce39fb6be9ede4c33d3411a34d9f872f7338d597bc31776e56fa9a64a955481561531c40b6495fac043c986bff3a3332fc994f5683c5c107ec6489efdb0c76236b4aeca5a7d4864879ecb8fffcaf191fcda9857c518a6339773f971aed6739ca9a388102142a44895fed7c6be8a8f8fe463be74facfb2749bfd33bd345f8560c25e384ce505dae28aee29babbc80e6dba47e0cbe577825f36df3aa69eb1fc8814a9d6e230b47756eb19cbff3c4beaa3fcee2711795d7549853e6c6042824b6861430bdd611d9f28630fd4dd2fe0e00204c29e571f60871c70b04074cb8cc194190e78f0f1507a2fdad7124fc94acddd7dc7901b1c0e27e57dd2f74910bb0092d15dc369139843f77d4c6d777f69d3a786094cf5143faff657ce2af1e566de958f87d0be7670a443f5824251f1a1e236b5496b72ecb3a8547926a3469728c73e8bbb7bf58283d7267dbd6cfe0bc993373b0dc408a3832a78a0e213fbef353fdb4102a84cf1265eff7bbde667856edc2748094c544cffb4d5c71c824f7368400f3437ccc67478f4c96fdda229efab542a95bc413c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a3939393939393939393938383838383838383838373937393739373937393739373937393739373951a2448912254a942851a2f4f4f4f4f4f4f4f4f4f4408102050a142850a04081f2e4c993274f9e3c79f2e4090f0f0f0f0f0f0f0f0fcfcecececececececece8e8e8e8e8e8e8e8e8e8e4e4e4e4e4e4e4e4e4e4e0e0e0e0e0e0e0e0ececdcdcdcdcdcdcdcdcd0d4e942851a2448912254a94283d3d3d3d3d3d3d3d3d3d50a0408102050a142850a03c79f2e4c993274f9e3c79c2c3c3c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a393939393939393939393838383838383838383738313a507ca139e1d9d1c1ca14fe66b4190d5dda8ee1ada440032f2be138fdeaf2f1bbf4a048a20ad548866bfaf528fe667affcfc0793b37e580bca6d64219a2c302ab24378ac52c8dad1dd7574371d41d5d223a34c419ae5fe28b3d8a1ee9ea3bbe5e86e32124f2f28e8c7fa945540f71f7c411157ebe11c8af2a3324956d1ffca87e55945c714bf688357eaaefa37d81bcb22ccad8c30c7f3b333afd6effc2cff7afe307f3af14fc7f42b4d86d8eb3f0c8f55826eadebfe283fffe48a0622ba3b8eee564177c3d1261ac237de66afd47f96afad62a531cfb6fe549aad48f5c886663f774ec67d8de3ff983f68b18f32749b7dccf4a33059597fc52cc62af5efa3b4f959ce2c4a98cff2a530975ea56339036dc6a2c7519f2f1f263f6e62298ea963fc77ce2aa3f893a20defd95614b8d1dd6d7cb5430fda56779fa0bbd9e8ee354ce0fa124403abf5020a0222fa190a7a1911191905bd82b21c011109bdb8ac8454aafa48d6f59f38ad508f0ff89995f1cb2890cc7e93f595ca6ad08f16216c6ababbe5f87dc9892326f0e546d80b63197a3296a2628a5c65a520bd1ea6d341b13efe4993ec0d1dc49286ed1559a5f95ad8ca4539abbd1fa14a38635a0baa4077237184acd269bd077aa34f6669a5bb8d28a25be2e9fdad32da9aff6587e7c9ce1993f5e1e81a1f8bcde1783d952a4767b256935a2d7f71b61fcef66bed3c115b435e6128552a7945f9c222ae8fabf55e21f6e807138b54aa28383c3c39171bc1d0ab237b9f86b14a5d5dd1f32ccdeb8a2651a09e1c9ca3f9356a650812457874ab54af10cb300391eeb04d4540f87299c27a72705ef87ef8fdac270747246283e8a7bb251081eeefde20cf7edf8aa885a11eda0126a13c846ce396ac528ca9507743000c8b872c7c64b9d29f05087cb95996a193318701537f52cf5834729ceb83b2c73ecb32f4ef5ba954dd2ddc202824e980880e80e86ea23661b1028b0e8b8b2f393a2ea547ecc2b279c29373654890d7153157c474d76e1660ba72d3b9521bbee6d768cca3f9c159a6573ab962a4bb47987f087ece32062762e88901855c1fff2d1233b63b492a95cf0ccb2b323ccaef67a5d9f959ebbdc43b9b55be40b1ce2b69a15893604d82393a62d007ad230225a05b30fb559941a0c765beac9fa557edcf64a9542bc0daf4b3c30b3f2574370da175e5fa4941f872b9f6af0ff1d39b49286a575d3aa3390d926d0431bacbd6de6efac3ced83cf0d94698f259cc744bc5e16e0028a3db5595662b169afdbe9f9677ad159a9cb940f9aef97fe7fcd94408da4200c11602c8727d9fb43070c47ead3f282bf749dad73c56b95f24da9113ede8363b379f4ad0a318fcccc28f244121e7baae26859cbb7496b1a380584e6739e36ef4542a57d7728e3a873d1aa3de87d8abd31e1fd529abd8fb34542a24abcc7af41408dba17ac52af313ed96249ae59368966c87bccaca7608c993b0ef933fdb9127ddfde2674309aa541b0864ba7b7c24d567f9b5181b08f25a2745415b90309b911a74588f32f5e817d68296f466a427685259f47dd26709ad8b02db0722e87ed0b65e325ac35f9772e33e2e656e1f5ce96efcc190de8a8cd1f767732b62ea16e75fafd4a3329b6523a20668af15fc65ba558eb10cbb1b490bbd0db1621b72b721ae6d4808db1023aff768d27d8c2f87f126c48c10383621676c42aa6857bd6a125f54a6f3efcf2349d7260409e3f9ca9582f58ab04fa29f7c85ff482f9a9ce5184dce72486718949f5ff9f195ff799f9c334fb4b952f0951f6f41bc70104488090f26da84879836e101469bf040a34d789869d30b82b4e90545daf40215ba7b84108c14314d61469ba620d3a629f668131545da44850a6da282499ba850b5890aa0365181a54d547419e2c1c9c81e3b7ddae3a74f7b1cf5698f2e7dda43d6dd48908c00c2b6248fde9604d25bcd91de6a52e8ad86a7b71a2cbdd574bdd580201cd9909051a24d6436d026325cb4894c1a6d22a382369129d3263279b4890c1f6d92e38336c9e16d9223a74d72fcb4490e579be4f0bafbc807206c320ce94d86117a938185de64c0e94d8629bdc9d0ea4d86ae37196e6f3228a037198a28128210103a2872ba83883eddb1449fee80a24f775cd1a73bc6e8d31d29e8d31d66fa74471e7dbac38f3e9909d2273347fa6426007d3293a44f6694f4c9cc4d9fcca8fa6426863e99e16242228484378adaf4c60c6d7ae3d5a637be36bd91002444402812421121dd46b6235f6f476ee8ed48027a3b1281de8e54a0b7234df476444c6f473cd0db9110f4766404bd1d71a3b72374f476c406bd2109d21b9223bd2109a1372426f486c47b4382ea0d094f08a7cd84c4c807438ca8a14d46886d326287361901449b8c10a24d4628d126239c68931162badb4808276c518cf416a584dea22ce92d8aaab7285b7a8b6243779f4c38726488112444b60f361308221441b26d9e18bd796cf4e6d1d19b77a6b709426f3300bd4d25bd4d9ddee6aab73980de2697dea6acb7697b9b0ae86d42a0b799446f338adee616bdcd347a9b6ef436e7e86dd2a0b7e7a0b73fd2db9bd0db33e9eda3f4f6acde5ef6f6b8b777406fbf446f0f456fdf81defe8bdefe8cdebe8ddede05bdfd99de62427a8b997a8badd05b0cd55b8c4a777750c48891234540e8e0f4810d6d0293d426300868131820da042689368181a24d603ad0263060b4094c09da04864c9bc0b8a04d606ad0a630346d0a83a44d615268531816da1446d5a63059da14c6d5a630af29fdba010add2640ce0052c7f7498f0c40a2e8ffd8cbe6172061ba7bbefe6380e4d00d4877e5061014ba1b9020af337a74f71938bafbbd6aeb98334a74371e1fe9b30f7d1fe95adacb74e673357e9de9697cc64d6786f4f885a1fc038feec67fa8d159befe18f387137f84e9ee3fc2fec34be3a7cd81923f44e86e0a66fc343fce74f77c65fb61931f54f831816e6ecc21188b89307c3f3f68ab4a0c2bf529aba4310234ce1041c98c253e7081a90f115099e293825850a1e257f46a363548f6852967833d079b97a3708d921c1c856bc6477a8db0d78be3e04baedf9a577a9802adac702ad5b79ceb3a8f59bc72eeabbdef02b23f5ca8ec6795222dffe8d9d6efa319fb5f79e47f9f86258de6454246579c0b9d93550a5aaf529f9e633a3f49629f554afb4d98f55ba9cc30eb58d264c67626afc7acbb748b3f994709a26495a22ea6f763d2f3e39fd19947ef833f0bef2cdf4b41978e29fdfca5ebba3c685b1d9ed7d22acb39ec7856eb75e59cdb5c2dbdbfc5b9a73763eab3de0fc7af267de9ba2efe6edd568f519a7c8f594f7db9be688347f363779573a157eb3dc6ef7d1ffd120444f4a352bdbeef695dbe743fada7eaba7c1f457d3265658e3f89ca56cecda75f184a7fd8b4787e4f6328c72eb1f4bcb21ccb2a0dc3a2b1cafc4c6174666f91cc126895edd027ce9f79ad6c87ecd0ac4772745d77a98cee1219dda531ba4b21e82e81a0244677298cee1218dda531dda52fba4b5e7497ba286d51f24077298bee1216a50e94aae82e51d15d9aa2bb24a6bb2445778903dda50d7497a2e82e41d15d7aa2bba4815213dda50c749730b0c475410ecdc92a9d2e28ea1996af2e66da6833aeee4e22800ebff2cbbe4f7a34664ac0b9a30eafc6c31d52a44270dec1a53f09b35f0c40f80dd31d225c0e670996a940991df25fafda1f6b05b35a5ab084127ca954aff96ab4208f24e3fda4f74a3df487d13051c4b9578ba96867a68e92575cf9b5569c7b90e6fad82545ba9676bfbeccbd3eb633cfaf6867700e6331cb2bc350ba38ed0d9d09932b695fc5393c2dbd1f3a28004cf3cb9c0239a78103acb25421c68e0ff47bd5da11841d353b5c8d73fd6a43d01e491b0a3c20076903f3bf3a78e8d0a2268538ec995691b3998e72544d4dcc36f19dcff2bbd61f7bfdd0bae60f613b345629342f518c288b44f8e9b599d5ba5f6944d90ec9aabd5b1ea42e9acd62ad6b4b4d0acdbf3677219728b5c8c82c81569385e4d92c5a7a84e8a18f58417777c9007184e98ee33b288e23dcf8484dbceb1e49e2ee556d77e57f9ee3fc32cf2cf7f1ca4a617246a35d8b3d878fe47dd27c8fe2f191f092873dc6bee5da49670f7359a51e056db5def8517ff1ba9c730ec7acbd45e36399787c2c766848c91b41ba3bdb4b63240513e8f9f3671563f3fb2badf7eaaf4c25106281920daa20811311684c508308125de386125deac38d1d7c3918e85d9c02bddafd17e8d5da98d924da3872823c4e20c5099c50728229affcf865a788e52b7bb2fe176b98ba6dae35542aaf0cbd5a283a3a38148fd8c40650f784d91bb22182898d0fba1b851d5f0ee7584d52a9708ed535a8690d9557eb4edcab685454735d39fbb9c9c9d1a149a3c9aafdac128c0950e86e262e7003c5b01b28c61d16a5f730bf32744cbde895b2b14a5f24c0407e2ca318e7f592e64f045f58bee69825bc4a1620327e37a1cc18a4797ca4f1915e2fbb6238823c46f0c60872709202bd1aedda17e8d54c686c818614ae7a85b57f599b1d852fe7610a3af130054d6738815fc4983a99799f3dcbeba14b997f5f26a337ff153f89e2fab19f2ecf28ea2eddfec386b04bf628ac85abfcadfbdfacf2cab7b0f885576c198d62952b99550b02baf2bf1f59a5d87e3f397ccf5bd50f89683f1482f7cabb68f2ce59a65baaa5acef933949a0c74588195c329659262bd77e28cb7dfa85ff5672ed8780f087e0bdf293defb494421e871a99f05e3f0085ffbc124a640f7ff0be975c9a4ffde28f646393a47d635e5077a5c3e265bf8317ecfdea210f4b8205d09decf724b08de2b332cafd0ec10c6e36399e3fd24eb610a74ad95697ff2b72605aa967e325bd727b304c22ff3aa4ca2d99395f5d3feccbfe1a7fda1fdd06a94dfcfa34981022975e92ed1d05d9a81a64a0ebae0f0e7ff7eb2846460494ac8164e56eda579ae3fcb0fda96731826e29957e5ad22240c0d63f3b128a48b0a1c9c3a981d181184083d82d0c0a6e68569120e0209721424280894d296211e6419abf42cda99a95beca88cc5017497b474978e4aaeee52ab341485490c54378cf64349849147a55fa5fe30bf9ff45a98c220a244022fa5d504c4b823553f8ac55976299fa43757eb3050bc9e6331749b2b7dcdfa1efe595ae2f307a318dbd0c7af62f04aff240b93723eeba71daa02f4f4ba7c56a952c140f1fe34807e54c62cf62e8b60f7b14f5af2b4710c1036cbfb999664280d7d9143a9a8bb443434851745ed755107eb4a175e17417aa8bb278b0b1dba7b5631f6d70917266c9187ab4611a652dd9ff2ba32bed4ca234997f7dac28aee9601681c4c8b20b89755af3296bcf28a520b026831a5dbabe2cd8eca987eb9f695c70367bacbf66b756e3fec2128337e90ba304da2d3e52c0df1c051cb2b4a0f8020cb02882c4c2d2c98e89e7959824e74929c78170b8105162d5c714437075eae0002bd9a04c10e98e0fbe4cfb4b1caaab6cae80dd19ef64356bcc00a1bacb8d2fe49162c7da03400d62771a6b22b3ff79654e9b987127cc592e8f87d96743e08521f7f96bdeb7c8b73d21fb49ecdce7de96a5248a50a47bd41804c777ff1e228d4a4dec5cbcf32950e012bba2150838077739e0c913cf9b9c4a19cdd2f72ce867b8ed6f816c68da6b30ba344c39684a985a9e16c6e587b5a03a6cc186e0333e3aa9d98d27cb4f93e59e39566c1c07025185373b107b8c1e5077cb13d60d6ddf67accdaea1285256dfe7d400958a4983a60ccdb2c69a693ca706e0e987577eaefe600d3587fe6da8070a337207ce0e6e7bf61e702e2a6fb3e16e7bbb606b88083ad7c7cbe4ffef88c2dc7d25d53a6801e9729355bc507fbf864fcd3c7c7a6660ae871a9d92a3f68f9610887478984570cb881012b30a0880f56f830830f443d9ce9818ceee6983c683193ffc66f6249fb9149b69509a69f185b713f2568abf7b36c9f7676881bbf4f5eebe327c360b7e2e6a33e29f4c3a587560f29f47064015c3487bfce53dccf98bdb3fc7d36d7821620bb9ba8b7059c1640b329404c734ff3f1f36cf6ce539cd00f171feb95b2bf8ec5fbb26a3fcfd28807301b0f2e6c0920334a211fbdef6e197a43c00d106003042881802bcd3d9633ef8aa1ffc73c95e2b2a433996843d1510700e300f500ddb643203bb060db81871d380388c000073080ab0067badbd55b019c284002b602dc703eca5095e2fe6332ac9fe27274fc615dcd57aeea3a4f7135385cb2dbd8705c0daa26874be70ff3efa35b9cc3aa2e25ab1eb3a1f4b0fc3e899a0f7ea61feb3a4cfddf0a27f4c3455e8bfd939e0e596c3a24a14312dd9dea4d87dbdd5a7ad301856eae3a4ae8874bd7798acbd61aa9bace539c4df89ecc72561393b8f5590bfb17a63ef7e9057d6c7a4447f58835531e364af06629da07696e53979b367589b5a98b03dad4a589367519a34d5dc8b4c90b095e7adad4a583ee7e7282099d04004d9a30b1a959a2e405175a602189afd04d23044d1012e8a68940370d04c2e0e0020e2578e0c31030dd340fe8a67140370d10db0d6b6c379041f343370d037c3072bb24021fba5aca61fa85a0ecbaf958c23c0445988fb6e55c17d28cefd399149b262ec5997813ff8fbdaaa5b8c6a97848f32bff27c526a4b9c6fba4f7a2e29eff73e2c4ffda2aa3a88cc5db759d73d44798a36c429a6b3c357e577e14a4d8a557f1a672b5b7eb5ce26aa92ca4d9a5f4ff58d78d9d5ffa33cb4248b37f52cc9726b1e09f459bbb4f76997e22e8ff315f795219cfa24dfa40771fd1dd493c6c4961bbcace68495c927e50af2a3db10e49b5e0cbddcfa19cf285639ba84454416c5781f6c6e48c96cfe8ee1e23ffe416b2095fce894a95ef27d5913d2ca67374300f3d8457f872d57a2a15e8d5ae19574cb7ab66a5488e0ac5165e793494a880b0465766002410951ea745506aa153502a75ab154a5a2bd76f85d4956732d156013fb3dea32c22dc32cadf6ae1ef5b55246a0aa018dd1cc64b6216fbf7c99ffb45dcf749af2685383cdd43eaaa49216ed6c7f7b1e8d2714833fe2439c3dcf858b8f191ae751658a0efa82aaf8dd1b073dd6439f899e52be7ea879fbdee97be579fcecff23e28d6a7592923e7d6671e458d554e56b543d90ecd073fb3f0f749b065ad2b94b4d67fb27569368bb5ae49abccbf34ac05014d3bcb2f94eb675c5976c81acd1a942b15925902d5a450b6432fa10d360446cb95825784fdc5df47913cfb613a7e3509be5eeffd6b146138a4f935ca309434fc02a9fcbc5c29885f7fc317ce8f5733a4d1a61960d0261a9c5859410909514c5c4e6de262d3262eb64d5c9e68139732dac485066d9ac18436cdd0d3a61988da3443529b669022080a08e0870a4b9460259bd258ea9a4b6f0420d2dd3d38dc4d2a5b7bb72f3e740d5f5018425303921724f0e55a43442cd7cfd0d1005e5c5642f4dab07949a13bc9bc2e7c391e0e87bb49a5b62e0be802e3f05fffd07ad7e696f7492f24e9bd1624f95be6cf4a3dfc46b952a1af75b30cdfa81ecd8c6d968c45a0f9343b6b100d2fcc80811984704d0a499fd5a450ad49210ea419bfc7beda4c674d9231971efb1e03b9ea2bfd1cb439db2aced9fb3750bfa2a54972f6b05182f8bdbf2eda916b5920bfb60609baf450f4faa15521617abdd26c966eb1db793fe969f68acf7ffd15bdc795765ea587627299c4589e84c82888061f5a5d71b951906b1564148307557157eb48c839e7aa75500e3927ffc67c56946c7d3ed69573588e5fb55d87453c5e7973b5de4f8a65d57ed2bbcef1e358ccfe94f7c12ace75f893a29dc1bbae731ddc7d7ca6c82c811c35552a47f94c51a962dfb7a68074d60aa5d0cb241577771cdcffa321683d5bbbae0bf2169cfb4250d6deab24d1ecc99f32e3cc399681063238d1dddc9225ae9af485c2b1bf7e6d752c67f76f48d559a678cc7452d1068a2b8be6fa322db24ae503e12f0b17ddaad76b8888d5320ad2f20a8a616544e46abd5e2a5790515190ee8d2805ddf8bf3046747f7ef6ca7d11e35ca950ae542894b4d657fa4df940b952a1bf4593057e6685d4057e66e54a85402abf1feb9a8f8f26eb7ea54d96278766996e11f26296e5cfd7dff0e7ebfbe42bcb946e2c69a1bdf59164fe095225c894203d4e926c583a1a187a04d04d13250a0d952952545168a03ce9a6e169a2c444a549544b81682cbdb1e0e8f92b1b83a32a753bd4835b594987d20a343b5daa8175b35da901506abbd275572a65d27115bf4a71b98fd72478e5c9159b2b226c31b0e12a9baf8d01278610ace0d1ed2a57b9eaf57904dd43bd59e1a11b7f92fa4c196167581121080e0e56100426f11c72f0f94ec5c773c8c16d6aecfd1b5e9872ff37c64119ddfda537200b7c4b6ec049e2d3e4d44344f363f47d140bed87a6fd69fd7cff5069055591a38b7a785a2dae662409febcd62bf51a8d65d1c2a47363be1fda1b3a0b3d3c18b3e0c92ffceb3d3cad96cfcf935b1283738f64932ac536e34bc51b7ac63f3f5be3e13f92db2ce41c87677525eedc2cf49f757f6dac669b1a7ffc492bce75b4ceb98cede70fabd87a348fd1ce06a552b96a3afcf773cc82985639f31e7459a5f893fe4be5998c7e8e65f5a77b3463fa7dab2931fb492e6d5c7f6bdca3a1cd2e33a69f773eae291df8f26fb50f5a4c633625f36c6bcd5671ee6fa03855a94b2db608539ae7997533f899aa825c4443ce61e7eea342e7baaee59c8dcc72daec78090e2558e31c0ea5102a8be1276d2aadc9d63920d6959fe851f97d622c943f533d2887bc0f5b2cebc2f37f287bd69529ae76c8dea7c1caf7c99f0e2e14aebb25cdce0c94188b6ef3b443aeadc707a7c7490f0a5b0f929e211b9440a0988152064a0bba43eb362fd4b813b779c1512fb82ab48e7ac16bdc5528ec5fba6c50ae80724477bbcaa6a6fb090b90ae94c9f015ca9f4debbd68351afbfb42ba125452820d342fb8f07dd2cbd882af2c5f342d548af4ba368f57528fc6be3eec93b94ada2751190bf83f961fc9a31997d428a131294d121a5fe171a5af1aaef7a0acdd2511d0a850caf103f46add342974d3a050c5ee124ea60900ed95690ddd3427f41f26d0944043c208746c397974778e33999f0da54773b3e5d8a6018008219c36130d129b1a7cb9da0ba7c6551e0dad47433bad10feb792bff5489ecd92afbdf2bf9fa02b56d149dda03b95b3a548e89c24b779564baf8ddd0fc58c6596369674d39c604311a0e79c2b3c9b0831418349b831d169d4fc3bebb7706ab3f1c3260dd0ab39ea0be87151a95c5df0ed6c2af51a97a0c42216956ab290aea5e51f729f94e3c0c42b445c25e382bceb06e093c2a0734c9864d13a13267e6dcce7635c93429f2df84923eb2aa2114273448b6d8991ae8ff1bf4eb029b182735592f9559430e12a25a3928a6b52488f35b617d6e86ee9d77ae98ced05256658ceb0bd3039d896ed05176470a10e20960b39896e2ef4747373c88c138cd956d0a3bfdaa1f955465bd95a239a34da580862636187eeeeba6940a0f98086486f2c4cd958b8a1f18046080ba676951191915190274992644b228224662411b32571a2672f941357cd77e2a8d7968407a742854a77a90034bd25094a72d3aeea2ed5cda9d83c039b03d1dd957a3547da9ccbe6425e63f3faa4ab72ed51ae5a61896e49c37fcd29b1a49f7c8953ba5af04d851eece4451b7ebebc4f1a4333c68b0ebc604b14344f341edddc63fb795ecb18bfe789f8c55ce59532f7d0da2d448a70f8f3fc7a96568814e1b0cc72baa45d09cebcfab01af34f8ed2a6c441a968ca082f480109427497f4a041a51f95b1a0d20cba4b32e8ee2079246d279cb19d3044f7d39baf8d79fef4e69a149aad1a942578ad15d6cb6468071c4944adfc387febd682c6c782e767af608ce4652cb3bc572511c6f2c3489ecd1223c2f24a8ce8f3689ec55856a9a20a982a5ebab9ae8a32e9ab1519ddbd42c10c18c410e0048391eee630f77d02b07212408e4f0b82f059f98440a50f2a719ca888e96e0d0dd12a769e1abf599fded883b27e8efd37658f29664cb9537ea4b8408a17526652a41424523c5059d1376667959d8f307969aeff7f6d3ea94e51bc8872459457140f4e3d25e831408f0d141340e100141fa0003de9e3c919ddcdc56c2a635be452329d4832cfbf5984d1f1c3fe77fc669523cc75455bed937f7284470f9e0f788ce5aaae5e11f6a2d9efb319ab343af1c0c0edecb0c344e78f93ce057468d0f1b6c2fddf59e5fc9c03cb31410c9c2970bae0707974634c479b2ba5e37743c38d9193404e4ea03839718013282727cd7131c74b26cd7e1f0a771d77e246e088a4b44851917aa58aa0b0407928119a80716a324413dbc44a13cf9d93f9d586ff37d859a6d92b4bd9c7df5f0b14e455d678771f14c59b3b9b9de59966693f98cd9da71e87d413275ec2f28c4570ac327f2b26484c54367bd86471b2a9c1e60af7528a5c857f5a218ce577aa2973aa09c1a946889a2b35a6e6bafa37d86b674b92382df9d2dd47fab48448091f642821a2bb679e685d8f24a5ac387c52a2e4853e4e2fa8717a414cf7e985009c5c70c1077d72818c0b4bb8405d48d2c21939b35decca25b5127debd4020f2d44690185e626ed4e2c6c39b1b042123492fc9024764a729424c8c963e0489cfcca0a3538adf0447f9f84b3271d533ac25c3529486dd879a5d9076dbed643d133c6b1f08a61d29de56b69dee1bf22ae4155be30f59912542d960fcafac2147f12a4decf4fba5ed77ea84298eeee930a590501a830a4b93cc256a714c858a510a6b9f15ba530aba24da126579b51df7742a10cadd9160a447c969ff4f083288428388797c810b4a700a8c1e153009ee89acf1f9f9aadf27dd29b6179254a9f4ea0e174c20e9993096a98309af0c2c9040f4ab0e25442772a21a804212464d114d53d1633166b10ede87f978876ac47ced9740f4ad48ad6fe8b7d52c6f693ffa2cca5579a0c5bdddd419f484022a1841150308210a71180648ac300e80200e009003074b5d363d4678e1a61df27c21622d8e09ab1de10c20a4218e214c2d7dcc4ddad47270d9cbc53924d055b10a78dcac974a69bc3f7b1acd2751eb392fb6abb90ce46f971297c3205f1714202c5098944a2c48d233f9c8efc1124a7232710c49c40f0a1ebac749ee264e7a99baec8392c4329ca280c9498769eb27996af1876288e56ca840997b1fd983071550c43ac21a3d5166eac5d31f41308424e46ca3819d9c148cde98333ddcde5d1a606d32c6d6658f381014e1f2cf92000a72264cc2ac75aed5f5df77dd2eb4e45724e44627022c20511312722349c88f834d77df26f17b31efbaafde4eb25dd8538e99f9c61399bcdb0bca252b96658ce6cacb26cacd2d769889850fe5d9d86ecd0dded0de9ba1b853e0de9e9eeae44434c1eac4e1e10e1270fb8cc3c0802ca9310322721617e9e840079a93aa947c5d6a90333a70ea4e0babff6a70be27e7c2c3ee363191fc9e7b14cf15ecbce13f1a7f54e1d70a72065bafbc7ce539031a720439c82e490e24e418a9c38e0e2c441adbbafe87930115b6b74fdb73813264c9870d23f2c270e60c814fc479242270e4af8d689468d6e2e943f73bc04063ec62bd86f39d1c04e3447279a29ddddabcaaed5751eb3dd4fe72d6fe23f35449da7c64c6b3fde420bdc4feb39ed6bd063392bf2c118637983542ad7fdef7b6cf3fd57ae93be72ed2b686f8cf6f5da53abd17d6a3192864fdd9dba860b848fee40bc08a4c6e12c5781a0ba0fc4c80dc674372747587783909b37f01b00e2864c5640c63417d20c880fddcd0132833996344060709de1a387ce647126cc99a2ee96a3cd673cf5071fddfd871a7f38d12dbd87fdb1c3fda3eb6e4ed2feb8f9a3577e9021e44798f6236c3f7a84fc1842fba13ed6f0acccf1f7adfa80a20f2e5ce7bfeae3a65d7c94e1643436e56cc58798eee6f8f0a1393eb874371fdecd01b1f6e0e3fb567b8ce9ce4baadd838866ed71a5bbb9af362fd9a304ce06656ca0840d6acd511bc030c26c60ea1a94e16a9045770d66dd5c0d7aba399b1a9f8f690d4cdda3ad7a90e9d6238c1eb3fc343d4cdddc5f0b8e55521a64d1ddb53408d3dd1c8b066177d3e0a6bbdb69606a6efeac3f9b811bae1938d13d83d99533e8e1647506260a93411e2d1988b1433228eaaf264119d4743707c4e2a3b93cc8e8fba1e7c73f7910813d8f8e93558a252d0fef8fc6c08dee1880e9ee9eee23b39c3ea822289fc9c2de6bc13e36e74a856496f35a2be067ea333e92cf64b97c7c7ca6f8f4882d2295cae58343ea42f9b01ce59a12230a290e69f651a95c2f95cae5f3fa3ed319cdc7352566274ba57299c9c38c1a668830a3831925664ccd75b03b4228d3461930ca7c65885ad0470b9e68410d2d80a1dba6fec735f1f1916a62d625c5622c6929298f3b4f4919bf1aa3d3822955fe160b8e60c10e2c80b120880529b00000769461870f76c86007143b4cdd9df43a4f751c15073b4fa9feda486fe29e3771d0c3d5b25cd5cd1bce63b68e21ea20aa83053a6c40c7982a661156633f3ba3613a4a98638c39c239beccd1811c61c871013992e45042068f59ce4baaa571e357edc762d2bbceb1aa43c2644032362b58630547c4b1471c248803873852710851810a5440850abeeeae732cca686c94e0fd24eb18836faba3baae1e7170e49182a30738aac0819af91b42bc71f48691147491021ebaede7558b023a50c001140481022b28a0e2c61d6e44c08dae25cde6ab1d6b3c533ba3390aa3326d038e36aa68a3d6c66c633bc19913dcb0a102368060a3870d13e72fb799f455d339a6b95250f589d7bb6e5c238735b2ace126988109ae3001122660994004aea37d75da57f9d9a6c6667b4798ab3edba15925ea96608912e012542981cb68ac92800a12742450410d39d418d35c37d2beaac1a551260d2cd2a0401a4169843002322378c0088ab611981ecf3cd939777fc9cf5cd9fb1a60d6bb2b7e3457d151e0679afa1bb6d038001a5bd058e18c3dcef8e20c26ce103ac388d7d9fcca95d2beaec059bed66d76ebe54a854440452064c61e66a0c00c1eccc029838e32882863a80c131962907144a6f7bd5e31b3b8f772a678090afc59c6f26fecaf634b63706e4a195e7b6d2cf6a98c1fe6d2f15ff193499f7a23326ec8a019438de6ba6aeb156398028da1658c202178230461bae75cad407003105c018219089c80e0041004f9801b8d6d4a4af7810f1c11830b3192c4a012061e61d811d45b184a704ee493aeea42e7c67c65989a97cec63002b0815107185f808100306a603401631b338689be2bce064bafc6a5a454f76799fe2759a1313b638a7c41822f10f005973b5ff61f4da237aca2a7bc58c38b0978519ba38b26baa8a18b255c04b27101022e94e8eefcad5184b9aabb3f8a196359e4298e8bde228e2d80d862478b3db4e0428b0868a1450b0e3cf0010f844f04c35aee2b577912958ddf8ab3c98f3d466b1c779dcf0f250d77ddb531afb25654162fc882892c7668f95894c1620358cc8045962b6a70851b15ca155b07c674a0031df8a1032b2bfcb0228beee63a507ec64b1efc283865e7295511d775dcf8852e63d68a2755dca00a36aa20a20a5715a6f959ceba1587f1129b7dfca8004385162a50db1466a618639b0288295a537420268f6a65d2c78ff652c1f295e1084349c7a1a46131338811224518522421054c0a231cf8830319e0000fdd753ec25c66393b4f7163953657dad5ff3826dea453c94f04af8d79f2ca98f562d6fd6779e380120e6c1bf8a30e2edf2b5dd5e190e6ce531c0e69de40ddc09640a280220a22a280018a33449f745b7d52c754de2e46bbf06df2e31acfdf82e20505ea09173c81c413f5891834d0c7a60138341005d7e56f3dfea4aac328cae2521a00d2c0113a9cc8c2891b9c28eae6301e65f89a345749f354e7ff491c8a8e978ce2d6c4184d70a009209a906d4db890813532c044068ac8c04f0648e8eece06a786a87315d7cdef1c670a068d8f343e52e729cef11255a7baf23fc74bc0cfb4f314779760ea635d713fcb4430bfd6bb8ebbd67d260bfc4c7daa8f8da37c6aa678afe5abd667b21ce5289f1c1df14ed667eae333e5c331fbccfa1ef6f171d4fc7293336b3feb971c9dcd02372052a97c7a44d0e3829ff0e45c6159e08905842c9105e8d12c419b4559972852a2054a484089295fbc384a89139228d39d34ab4c62892428abb7243c7fa24dbd1656e39972b9d65505dea8c0022a70d35c16adcf6b1d355d66ea28293724c040c207247a8ef0a4a33e11bf6c8ab09ce727da237e38c21e7182111fe8fee205556d284a9b51571ae142776f45c0d1cd15d144113d1471632b92cc1d92cce394451421d35fbc649102435000060ecb19058c1031a6bf7841c9d0bb78e16c3ad0e332ebb7bacf315b5325feb742840288e0d2b4af44b830013dbabf784175f1d2da26f0c30476305d7199563b9b0d71660826be78f18cade7a82e5e388c45c7a1cdd887f819628a102c10420242fc04914710477cf1e2933aaa7e52bd9f6465fe49b14a21e7462cdafbb22aa7cd3ee5df1aae5209fa741b8282786d415cf12e5eb85c25e0870498904051739d8da326ab4b9233714ae9be4ffe74b38a2e812d023388c0115fbca08058a82e5eac9d72b6da12019eee0eab06a804f248f7a116328468460000000000031100204024180dc72362e178d26e011400015fb06cae529e0bc43cca29640c31c41000000000200020902106004b093cc84a4d5305c4eb7f5bf2761535e8d142836ed9241c418f1022738aeee60b399142a60806fef7d45aa6fa74880e72a4dfd5981eee60e1f679eb765b8811e367899f57c798c1265921fa3b00e369afe61a3b1a4e7bc49a1373601c8824a4a140c1a5a5e67e22a64f6240550f933d83796496623c29e71224986b68ede731d149e60b8706de8ade46f0cf418899d3312ab01d380ddc7baa431598ca46729aa93d3e421e6b9cdefa65bd9bf05e1b6f8535e96924f73050d8bc49d28551af05c87d2e7462a15f117b53d99aed6b469c54b7f30641d62deda01ff2078c2ca384af8a03eab29c71a981ef090dd627e14ee6ec633f8fd2c27e34329aef33fd4e20063169ec8b1d3e0e5a97b0d8b16eb5728972b33e8e67d45c7197232c9bde9b05011cc649651690249e6fdf2847a3af53de1d955aaff3103b1c6723995a99fc51f47cefb36f7fffa9f7bd67e82e55f5a459f2298f5f1f3455d17327f74d3895fe88ea24b47158d5e7feb918778f6f93353599499ebb038adb55069f7677642d9a24ca462df1a275209883fde0c87012b2a4603785ec1feebdd4de9f46e04c0dcb04ec90c97140ca89edb27f514d5c69b65dbc233a1780623848a81ed775b89d938bf0db2384d9f246c60243b06122518f2c3912674a1e29b9e0e5f671f5e4e5462ba476c0bdb09c93786d935178b44621969e07733ad6b8cd29d373dcc6c5be4b0157dd88ebf9ac22ff54e77ece4f05a9bfc7327db8b84f9d92fd1e40a01e72143d7f039c3c13ab4fe255ffa052295d4aabeb15247e23f0a4e318e95eb99d26cb4fb67d46aee1e0bf512f539840eace4b992bdc76e5c7dd1dc9ab1954d7e35e6e814200090d87c92928e46cdcc56f4cb16c010a276852714196dd9a31bea9b5c88a85a4fc9fd3637abe78b581261189d31c8c19ad650109dc804f0c4676867d2cae003edaf0d95e1eeed007b29d9c7d9e6d48d55515f31b2aa752ab1e8d518bb04cbf06a44ac3ba9ac7266d59b3286fed795dc3e303a3d84e1d2ec56ce2ea8a3ddadfcec68e670f6efd0fff715b8e9d4dcae6dcc2b5241b6728c50b2289ae59597b49090c1e72485a94249eed9a466ef121f06c24fdb5f60d258bc61d5d72b1eb208dce3f221d8fc421cff1511ec84c7cfe345f9b69f4d67c9669c4c583e289ac15ed56e7c7516f5cf9f9dad9e05d1895c3199bf3e37459aae5fc98a7868a363f8b2f185ca078b5b0b4106063cd7cb35ba6d8196802d95ba09ec91e542034d0eef765cbfc55aa178b1df31f7a67a0f4702a342f698b5ff7785cf438ce952542f20818389b2aa71562b3731ac5f22527887dca6962373239e9d14025959303e1cdecc79cf6bce7c9d02664df0259a30d7d7c6ed07c2f33bbaacc64eb654458c8082ffdab92a08c609025b5dc0f22e4df647e5a06027c396c8e3d9ccab65bc27a132998f0692fdadddffeb1df14e21e7161a49292545bc5fbdb9e6b19155ed9117a67710e9757ec07d703efeb3b1f57a541570a33e8a88b1a7550848d50a6384f0d22a910a9ed918fa50d9946df651a1befcef0daad831b66e560262e10bb33cebc3d2eb30ff746907231d6667f1252dc9e420c7a1ba16bb45556d9b01b4477614e3972064fb113aa4303693471d6faf5a0bea858fdcfe641419433670487a792c6e1d24da4be4703370c6eaa0109456c15c1f2fcc3a058a3147d6338b54ab48a0a60747f37f4577308dd83030466ddc48a66d45bea01ab06a70105f026448a64e7800dabde3ef0402ff3e9dae8032a851c2ef3f3c06ad4e3447196a6220bdeb0755841939b6002c4dbcab7091293247bd067a911d8a1947d4833b3dd1624e19d065227e9a11a3021aac8153839dd53ddb672307b9e989aaaf8c6cf59ce98f829464e9b64ffb41a4812570ba365d942fa5b0ae29b7a90d4419cf2360e50ef81b3bdeedaabe922578d42eaba2f5b8365d0fcea4ace17ee40ffe41ae4699707397ddda1800f6ae2e8a5b71b3a40516bedea0928c71d2dd8e588e92b7f665c7e0a52a903558275768d0065ac7ad781133d09e1309edd6102fa3b9d36a7bbca884fcaa36eef511de5120e48cce5de66dbf7269e99e94b24a1701f5bae359acedfe8e85b7dbaaa87e0308a138fe7c54a9e0cc9dd4f0b9e996b5b2f38277e72439f44bce86819897a26996858b1ae23ef34287a05c5aa838a8422d1f1ada4ac668d0c81dd2922857fc62637e3493c14f6e6ff638654d0f0e03b74128b392c7c699eb354670601bcae08cb52cb643481e38ebeab200e2c7c7d00c8f2df2f6773a5c84610777bb74e42f23fb6f4269593828a9d69516ada90cb1b84496ac305a42f40c3f5cf122ec9d87f8b64d4d903ac0cd2310bfe0ad392e68efbcf74cfcb4fa6ad5777ce7caf927bb2d5103f0629befaff56e9dc41641fdf3f254b73f8805bb907edc7608045930089da7e4fedbcb555fdffbb061434d1689d59fa9119fb373b30384d0c11dfba2c7e2691d1d6f8297c9e00b3027045447e4ec6cb3c512f29661c8be26af53053682a60d2104e3ffa631e8143e8462a3811ddd3934b432761c4ab04fc3cadf0f1f8ee0a437583f013065f0b107a7c77d07d1abd278335071e7eeb790e06fd239f5f8d357ec6f41a91214e07316767a1af8b1c7dafa15917f52330e1e0edb2c8cb75e5e611c7b84e367c9715114e53de4d6aa61bf7d7ef66bc7f83622fc9aea305e1f281646c8d0b4f452b20fcd85222ee7ea34ebf7a1912c78c0b701c2ee818363b59e94a351b2ab67840f735ab6db715dd5b7448bbc5e568be25195e6d05ae1060fc95b63e7aa3417ccd3e2edd6ef0a307f6d083861e986267ca2eec8a7750a530f91b01a0a3ea91bb87b0c6a10872b44a10b5d7b3100582e6d534a20aa14648850bb48a8e8c2ac299ea3f50c56f413ab266ca6a140e92339f0acb5bd77b0863c16bf065f7f3cf85f139b7322b0d9401b33fccddf50bc7193011c2b2e0b09a7cfd496b86ea9a6df066f6707e945d5811667f28cd8eedef2fcdd8fbb0e67aa0ff6190e019769fcd745c0dccd5b402dade0e0644e509076f49084034dbea0d1c7fb18600cc4d755b566ccc10f4e888b6c99b20e2e3ef0447b087c0b2894c2cb74953e8ea0798c11f50e0e22a2e325a7ac5e1776ee261384547f31665efd2306b638e847ec78499bca069d32e6aa633031efa6942f1425f6f5464080682a0051f374784525d5679aaf73c5260e1fbf2f8892e9d884c0530a0c3ebade73e4912d7066a4fa1269e05d3145218b8beece749f0a114a78d122d60ecad11922c62a01d7e73e9310f30c27b13472c217474870a43d5271bcfb89a8a279b665e643c3e781b415471337627fbe5206ac49c9b154e60bb7e1d50644d769216d8386d0fb55865bb127dc9b20bb7db9bfbdda6be7dc4d1f077e6e46fffe3cd62df49f2e38807c65bcf7b8d0798f2afb0edcc1bf9e98f17548f3f125ef329fb69e169ceb99ed6e60789846c8b6bd1a6a9e374fab3db8375a01c3f8a4b489fafeb615faf4e9ce918f71cac85481126d6f3a10ba773dd4d89231264fc3b093d317cbe1a2a929fbeab87c8d977088244e6e491e8a469a1f4d0e3a37a8dbf7e0711189552b264b3d771f518f896c1e1b256691b2989a654550a28131d7b2040fb5583ec939775252569ce152b625327a4d71934cf91fdb6c0e203725df98fae79a29c1933d3f5487cfdedb8ffa04de3168c220efe529f68421ebebd29ada2c1f6b0ae8d08e48fe60f1cf285aedbf3d0b2d0215a563af5ce19d16eb1d11e82d6802fc854d146bedb3d209bbeb81ee62fe53e51f7839d005504e6c8f5441a4d0eccdfbad86f46765d8003d863095c5ecebfd71c86cde0b996da39c071808141c6e00aedcb6569e01f64f6902808c2c216a594db3778a82e58e95ea9900bdd46da1920c2ed027ca536a2f101dec65870cdafa3ace67429716af6b9c9712306bd7b44d033846c96946c8dd138f7d6402f2466dc66c5868e432b28cfb291a1f75d4604dc89f40c3e12d8e2ce5e4b86b52334ef12fc03b076d015543371b37573142fa102a8e21e445d9a2eb1a7c03c3bd9e3dd739ed769d8aaf41b425aca53e1688874ecd474516fa801e0611170e3930389cb88ce60fd0a85f8094b7a10f943f87417d150a3ff123d8c18d0f5a0c08888183f5c286729e7b4e6c33d1fa617c2e3967b7698989c744a6e19ad78ce4c4d91041389809e90e82fd5d491c91965768b54f9fe5101e9f9c52addacb2473e072cd937cb07c9b7c7ca6ea1bdd187f0a031eab9b119db98ce09d6f873ac32f3fcd101f2360b67544ff7fa4e11b07f3be27f4891feb0af038a88bb9f75d47b2523587d48a3097439f5358fb40a33c57590fb7464a92216e40b8e287b94530b5b0cac06743590f9f6c0a3c841dbe747da2ce27498656a5cd1ac0d4cadc9a9a5296c66ff5b56c592bfcd4cbb2fe54792fc131380fab8398d1ad73415e65f8ffb826fe0b07b18c8e305c8500feac0ec3186c92faa3e12c3d36692b59fd1390f1f5522c2131d25770e9d736d1c359b906e7161f06d8cf25562ef4950508a278ab07298c410e86bb2a8855a645b26e7d938b0f3f008aab8a3a58ce5911a24a150209b527daa1f55c3456587399a3f8f4b3f46bd07883c1538c0c3e4c664bbe897b836563e13d7f0dd3f2a92cda270b56663b8370c30fec0adc98009ee5675d38b148a0123956ec007082c4e5f9f8d4325a9ab433bdc49e5d6db32ed30b6156e24001d8d3f7256b571f530386a1a83717dc6e08a571eea97d6ef0972ec477b0d5872b082724ddab3252230cb6f4f225ad63a62e78f8a15429aa4f46df0e8bbc8df04c9033b36d2e55c36e418ca75b65dcc3c5c534f6d4fcb0f52369e1eb265401915e509ca583c7b20873d1c3831c858d4e7d689f9cf357f5b4ddeb6008f4a973a23766d259677652a9c7b773c99b46f52a2ca2281baa0c3f87209f9c6ddbea8fdc20f76c4517dac3cf313fb66eaf5e59f74920ce840588d0cafd22a154955876071290b20e8d0beaac7e898563295204f8bed979a3b756b7749574328aab9068b057aad6878fb076b51ffb9675a7649e93b02552d32d1028517128923e367b6e50dbd9335904ce9a4756700b2f89bc8c6684f5083684446f4e8a16432165ee831f9aae129dc4506cc7dbb922ae552cde870b849fd2617298002a8968b7ac267b199e8d78a1db64defb581a3056fe7360ce32215bd3e8c5b908e79cf53cd06af2f0cc30be6bb147655922799d1135cfefb81accca01acf41e0fdb9436fd0597a7ee5e86d4d40a7c1dc914bac0da0b5c51e8cdb1b90287a2aec8e8ffcb7336aeae6443c818ba6558a2ef6c57acad5d5247cfd9edf23ffa54bc69f99a7093594e85a521305e28174e438716c6729e24328909f500d41f4988f60ec23d22f5da93ed2552dcd26893e6900c57377cd965b2276c62c132ec3b4999c2206c17a80805c1c9617bfdd98208abcd2b0e1ca5776cef532e87c66252dcd789950cd1f12d3ddc4cc51e57e3a96fb139b3d4fd9fb7385a94309182937bf2eca2b31dcfc0e6a1314678f1045bc6ffe050dbea06809ca84b04fa0f758702280fccdd165f44de9304edcdf8173253688ec695fa42f844223ed500580cb490243093729d75c5fa6ec330b7d966cd1a78f1df3ec1c4f481e72371b079021dff544f771a66df32b63cce95676139dda6a32f87a96832535663fd20566ad7eeecb646fd9d36ef75e0280e2defca5fd7d0fc79252646b4782642c18fff7d0935f96d39d2dd1283078cde019f4d996426c82d580edc62c21618380c8a2c0cd3138cfea3d869a1702dddc2cb6fc88465bd0c4a659bfb1589a1d8f0efd92e937aab34a9de170c5a36be78f22ee1fafd09a974ddd8c71475749bbd1b8a9a406a463c45b19a4cfdcd9c49663ae7ca5c113f0fe49359eed863dcd5afe10c0bdf7f8ef87637ea43815cbfa4272b7604a488827556c2bf1f644accd1c21d19269e088c1e41f0e3377ebec0ea3f0b8deab4bce9e8d174acce76f56ecd0f7c742b0a3fe8c134b2e38d4c98985ec2fc7404f83403e73fe1526b46be24f2dc28bc948779c4f61557b98e4dba195dc732906d04a3e43bb98c266f81d890fba3a7c13e692b1ec410e397c5a15b38b089ba49dcb6831370d00db2d8dd64cd10ae20b09ba017f07f19acbecea3a0faab5e8727c43d626a522c6651fdb546481968b7dd05c8b55b437a749f39e186edf5db98b2503e22e8fe25bc03d11ecb07061a51b652f41840821345e73cc4745c133e7e2647d7d43bf3da9d8dd0a50fb74df9353e573566b880625d515e1b54f8b6b52b588eb59638f2f3158431ba4d5a46b51dbbd8199f53a5946a902a17297af803d050be3512f273f6eeefcbc8f52228c52ad8c2dccc9835e3d8fcedcfba1292e05259cca1e7f7918fedbad2d178f7a470a26be5b429e024833917bf451a7f2f572c4bb8ddd7e9020ce16562563836e887486fa753cdb00f53843e534f38ebb55ffc36f4e7ecedf44de69deec85b265994ae9a687ed38273f79f919e880f007fa1ec01c2dcb29da457151f6d0c5873514202a49486455bce9727793a833a53fa8805dc18cbf8dbb57f9e7ad6971b82e593d7d42e7c074a67c170b14491e2c499c7539906f36d1c080ca2714b404c7e0725f578c0adff004885e76635b83686f1c9d1ac5388caef07ea917a4357a5ed832d009120611b7052002bb367f6350eefce6fe36ad9c99f75d4d485721c9206054d45a61080915d577b9d5d73f91a9f12af2a98106a9abe813cc714892abdcbc659197cc23f8fcb33b5ec18252d2d58850114dd39111e33f220ccb82cd0e4439ff41787e4e4ae31846a3a75ba76639330a0d688d7403ae995e3ed18914492ae654f1a0fb8b0cd25388a3a5c877e3b35ef8d52ace82f8463edaa202c054b3557da1c86324ccd35765a855bd50596351ac3d1ac50f332e4c7a144e4c402a31ad27cdcfa183489914b5c2b4dbdcb056a222ecc08d38765bc22619007e4b44332be469b0c39b5c1b88474e42d69cee538d35ebb1c55227553c5e1ef6dcc75bde097bfdb84051be94c1e01f33686e53a4cd43b7c03510cba28d86274316b023695e5b18428656f1ec9da82a0d2209b0210610426a83b30ab85cf79401e717ad99e80b051305f8cc8dbd3b2cf8350283b8c872fc2eafac94ef05809b2050a07e15036aca3fab318773859d0cbe0f5ecbcbf789494f33b4fbf59164f81a55aac9acda674df8b5e71c4f18c823acda94838cac02e387929165a70661e1bca068cdd52e98039b678b0031d581c4c73d94b2e869126b77c8b8603755df79f697c36b76978e7f9a8070a543fdafef17b7fdecabd5fec046447213e0a14d2ad8678c750ae3dcd30f3f96ea8cf7e46fcefef7f6e5dab75038326c01dd0dc792f56b2ffbf9a267796820de3c5817964e2b16c267c4b94ff5be2d8e93b0188a8058b7ed60d3cec0233d80ea20b76a12ae9f3feedd49e834ea0028ad477fe8e4829b6f9cdc802401aa26e6d591a5e4b199ba2e8f00cccac08836721afc722a7852d12752f784989d3f3c8265667f845a3a7f14ababab043949a13e0196a55c9f25876bdce66be91bcf4842a33fc084bb24f2d939dca004021652f6da963d7a125138ff6bfbc2608087e82b15891d3de6389a7b812e13573ee23327b3a56d3197483e4d5ab72cb6d5d3040bf34c68c1871923b596540460e58c41a869dc52dbdca252a4a44a295e5ad6a2996cdc41c0ba0b1e04c870767173c53a8033e49bc33239e4ffd9250ae43560b7118c1d7dc1a246e2a5b9bb72d6a8eef501e46d68153c7282f4a7e11d66505a8314de7d38418fefb9a3f432af722019c3fe5c6f20565100e287546ec0fb290f3da4d038bc179a58f74f233585d26305e79dbf0b912042c5be2f899dd171486e7095dc6f35e12520d36b3204889235bd1bb7f6f899fc554da76c80f7670b256270b6eed34b9e9163ccdd1e2c263e7a80d5612094e885f70cc67a16b2a4fbb63f15849ea79ddf48690d4117d27458136f4df68b647813cede0ffe00a303d4e68d94c2170baa1c6d0e1fb4ac204b3f2238e3cc7899902440a99bfe95ec0852918c4f21f94d1f5d40351c2195500ffdbf54717de0cde06ac26dbe84a3e037724e32f65d4820e7ccecbe830fa87885358704fd00a740007222f4876cdd4f8ce8999fc9e5ed5b3f5c8046f688ae127e3679d27eddfbae1fc1b9c191c17473bf94d75e23e091cfd89fc80ed1d9893548a2da97401021ed10d80f813a418140c905e82bd0f37fd364047741151b0b6d1443c8e6d145895e6b3d51ccbfe98f97e20a67ffadc44c9870e55fe9d50b6afce10dc52932c8a17345de65a312c5edafb15541c6bfeae19feaa62e6f074a7acf2d37c374f0b87cd7859a03e0f08cc57591c732acb3e5214853ccee98ac0d8f4ebb9eaef5d2cae862e2c5813a25e73f63567b1007b7b2d321901c91113b51d333ef692092d7c22748dc9f827788fda69d5751a5ebfa68e0588d940a7b447b5701c6c8a48f903e2f14fb1d4873ee1c02cdc27adb4ae913f27998e25c22e04d39210aba1e8d5694a5eff159c9d5348a62e4b10106fc0ed188bb4aaba01c5591a7dc15c6d98cec77b80e1bed1786747d60715627793f9e0a80de2e505dd0b000b2cd451c1594ae8f6182d82caa0234aa1f0c66737dfa25b2016b7df91057b045f69180ce393a670cf20897d5e5d9dc014b700dfa7bdf07a344e2cef9d6867818afd7aa636b413545c8339f31ca5d19fe6b2e8ecc0145b4a5444fd77dd564f963f4f1e0f45c088fa6473dfdeba653447b22c577cee686e5d4df8297754d65795b1fb14b5f4b1000a80bce936502aabb1ffb5ebdad814e028e8d8461668e0fb96aec5d3336d280f099da47dc7c29a4b9c371cc401da8c23d5462ffd1e3cdb224a6d45c9381dca8082b7751eba1c503eb1621baa739cbc779852d9d73b5b9be7aff4a11e95038231b1f29ef81a3499ff5160a1e49c23fb88e1f8f5a89184d9a087c0c5ebab5e252e6daee2a57bc660d739740cb7b755a80993cf3ba056e85f1e608b697a55a9fd0370f9ed6eb7759266a2dbef56148e9ae5f262797492a126ec613cb2a922cb20dc3e383539fbb140751aec327ebaed3a670f4344b5002371c3f108b0df75ac2cff5a1f3a84eb04f9d1ee7393eee23c9e7c2fd5914a657c19f0e55c87c81bca5f0e492328af84da73400aa2e331188509d75035ee8279d15ab5b3085241b8397151d8186e8d78a6a35c2068c5966cb14b2227fe7d3230382c77373067d8a7b9722b96daaaa8bdfec74086da764ed14e3cca927d49cea740740ce2be1d8272d9df328e54852b2fa739a8ea0043989fee207f9d137581422b9cc0dd94d0c6b42227e0919e6177e47720c9dffe6bb5f449daec1f1083d10e79efb2b76f97c558b3a2a949d7c988155bbd81c6e95d7ad0c0a58dabf18d551ddad942884b482f98c443b97ca976f236afd03a1bed1231daceafc9c4255ba20a0fcec057da803c8dce48f89d4e70c474786774c413bda8092c8659d45ff613e07417de6449076e5cbae3f7f17ddc4be5a64d7017592f915905da6d858addc4ea27660f5afeb33b57958a05384dcb52200c787caf2981f52d0ab2b5b014b9e73a0d635fe467c02d8b88af43d2636cb40c59de05be1026dfd97f8b180b4bd2cf7f3246daaf68fbf8725ca605c6b55e1b4ff4de7b08b34b3773fdbc40e36893d7dfc43dfa9033a3f26981365d1a2806f7365b4867d83208b16d2e8aecaf6251b9c988a31fc09e8f93970ce909c4dae8bc1bff691a7b9656e73d42fa270731d562c3189560ed8b3f5a07a47ca3c5421d60425960265b92514d530264bd9be60cf0df55a69aab6b7112c93b9885e8fca6f08b9806fe324c8e30615d4ed7908d0d369394ff0757f23f49742e6a8c80f8cbc12eac380eb7b539925ff830d0d59a384e0de84ba346f4f5bf6533f8bbd4a57a2124cd03d3a5e22cbf8f0943521d25f106f72ed826a1a21e6e7eda46cae5e3d0851e08bfdf92e50afb9b038a8577f87345211c1d9fb6c5b32760569489e1a09856263aad81f4e2ebd97394c8fc4c498c7eab877378e5438c02216e3c845e76617bf54df33d854265347ddb643766f6323e5a7bb38693c2d9ac0aa9aded9be4cbcf5e7637af211675236ff646528f3ff45df438f643a5d4fcea14a050807e92b3ef8188e0e282445b851eaf69cc4ab98ab0befb0252451b330792ab2fde9f726f2401e0fb9fd20fb4bec345b5abb5f0571830eed1b5f9cd8a9130d73e50550fc2d62e1f17067db0ca54eab3f957614989561b8b6f93f87476ee9f2b4bd70b0856f45f0f30c3e2bcb0dd9ef1ffdec2633c20a47e1c7ebdfbc8e07f74f8f7f44e9b050aee642ac2fc24ca85cb5f3aba58d3f8641226cbc2e65ae12ccb070644b6b9ca6be7d9ac6eb523ec37d4dc78191b146a76a51a7808138851a3189eb07baee639a8a817feee3651db51d4cc472963bf4db6a4eb4689c056f9e348a7612d2cfa1a8a04f33faa962849992e868874c65c07eed2ef4075820ea47bee9850c9f2c134aff4e22adfc0c62617f684ef9b2ac74fe8d61f8e75215f13ba7ab5d6a8d6014f255f259a048faec9408654a0c65dc9ab8fce48f5c9ddfbccc3713ec270e63f5a0079f0e9bd65a6ede7f9caf20e42ff65ffe2fc77e47b17f173492331f44302829f31e827db0aec189db8d2973c6eb532b4d3833595f0561a761641aba4f08ae477b71ca447e7cd7d7a58c1e4429760034f96fbc2199af32cb1a8cf9f8bba627c27844c74bddcc58366cb23fd906746172b2bfcf164f39c384593364d5455500ba1841550c05b7e922133f2e1c22ea80bb9b7a579337c74f6d08104b247bab82f74598f0551aff0b831f7f0c67cfc2c0e812428e92b5585d67db0ecb6166c79966d7fc1fdb780577f70cb127b4b2deccdc37e3246bcbf3d893caaa52c260b13c12428971b7dd452311ae10a5fc403814750759008a9d263244c9f5f403b09ca1e840596ace9c59206161f1eb65c0936b877ec56bee6c6d107aef566fdbd2d877cb07b2db269d9d0abc45332569b9a558c00fa18bfe8b8ca1ff4579c5c88ee5c929feb0b4e734fe3d7b93227f39584ef4733632d734687ec043281ed0fa4a96a895b7125e7a08f834f271e25e8fe4e824551dacfdd1a9785af97518d320ccbacb5fff125428bbda03a1f6a17d2729c1fab6939a490e458cda6202028e1dfdcfb827bc3da93506e59163aa09a4d675687852a14704f915e90b76dfbb7993e6ed0e4b391d624808b6c6e273d896e78155989c20dbc20e03e2255a58cb3995091009e02664e410f6ef4c85620018482881b1239416752d1c16e9572c701c7cbdafde0154b37d6edc268c875df382efeeba240f0ebf0b9a3d6990ed3a2efda2ee67d465b35fa7016117cdeb0eed18d73f466f3db6efcc2cdaa79c4f3c7c48099d563430e091c490d4c26827faa3496f5755cfaf54d546191753aa8baf91129e95eb36f555ffa285fe633a57845bcbcae9211a5a61308860a9a0d86a5a8e8cc209e84a546e79c8c163c47431aad5429e229c827393f99699e3eb74e36be71cc24e1b38cd65ed5a1a5d1e7f65979145cf766debb384fd2beeceb35af998d340ba4a6f70a6c60870b78c930be382830ec09866ed442a880c586d3b2926619db208fa07b30ab32622d8b2dd402952b8b58c15c9c568b938ecc65751d3cc947bfa97706c1742ac5a01d5f8af7237b2e143aea861a3ea4cfe042c6777b40eea3658556d241d4aa1bd5e1ebce6c11dedbf58691ab32abb330ab0cba88d322c6c7da346b4dba9ba9caade8cc4c02bd43f08ba89f65128841d8bb8ea19b04fd919e56706d0d746797cc9e78e035b0e0b98ed44f0fa92d979b0bd2944c76119cbf2b013d57513cf55917bdd6729ccce4bf899c36d4a8eb2dfd63f94dd00bd70c271b6d666a0b34fbcc901df0bbae4b45b5cb237d9ebcf370c2b30df3f1ac9f85ca78e1cfd9297b6bf889c5066c0c3d57e00149a97d742c7bac261bd32712cc66137889f963ec70a318c1f8a41c3a64797fe598cc0eec5ebd2246de12add8e20149324c030a14611ce8dfc395e60d8b8f978974d04d81e7e0b5d50d1bfdeae3c2fff0e650b0de3ebe4dfa912c26f5a326949abcd318e6a80d4060e5188c6abbe57f0d6752bcac0af44c03d255c57ec726ca0b1068d61bc2eb3f819c6bba78ed8adce5a99b4095fd263ac26dc727c2c2227c94c4ed3e3bd532eeb617fb747bf7d64356fbcd29ce1aec243a36a27f89f206fdda20e0932e5f2526c08fe50120a6741db5c266dd03e53a58157960368d2fb9584c4590aa8635e02a784b18e6c8cd6f4be3d0a809ba9577e213c6111e3f8550c7d569439fe4d8cd8ca2ffa3948ccca0c9b19e5311b78b4dc6a46a8a9c9edb3744c0f08ae23d4f5941753809e1c3d5b6fdc03cf4a50dac4f953fc55718ead7c59055234ce67014ea2cc5ba158d6c4e6ad451f2f4c5271c36011caf04bd030fde81a11ae53116a9a426ea0191677cb282a1834ca9dd22e32a28bf04ef2bc6b584aab1db55a039d2adecda25a718b10fde7f744ac856e56eaa3eaf465fd88e71a9f41c26f35ac32ece64fcb239653e01b945bc1e5b0fa6ca0c53cfe686b606a010f92a4ea65872bbaef2a47cf9b794895e31f91489bb3124ad69383b66491e60a33abe19a92d9378c7f3d688748e848bf728efdd18effd42ae41dfcffc59f98c66c759e208975658ba49137c238948576154d8968db1402e160331cc765576eb8f6dd5784833970e069fb452966dcdb7aad3b98e5f8c730be8a2311e807534ed0d508987addea7d8b51d6e1329bd7976896f2eee1c54722db8bfc635a9f07a637d40cc81185cd25fa695a08462ef55429e08ad21f033a638eacbab7b77edb2a177c0d746576c40e22f41cfe0cfe4fdcc9f4021b06c340767cd4abd882fc9d26d6e895e431ffd5987906a7342eed68359d4175cc6536b1f2864e3e131be49f50e8dd3107b9a65703960b5579bd1eff61671bbb0e72380dda9dfe18874f1d20d119d70ec570deb3879575f7bbdca6bda3d85534c964b95f94aa7e638778d1b9132883725921a1584790c8a621ea98dfd7a73f5fc21d1df52e07968f9a3db703264a3b778d537855cca1533738416478768b679403b2e80b9c6669c6a962392392ede68b7d49bef2bb2b9c8552ca55d5ef9f8f3482fc0e54746d0efef18f4ad7e05df6798e886e239bc657c16ecd848e11caa4eb799bd35d3dea8d68a3cdb8de17ff805765b366b9c67617978c6f9f9c53441c3643c48fd5f081524f04945587eb57c2ae6ae17aee1e1ef213ffc8349a383f421989b04e4bcffca87e62b18839cff6342b5df1c486aded58253130d9355851755e1059420db76c0d24c89ede19757b27d0e4abd1bb9902ad9d4605479029bd25e7fc55edd3280e8596cd8f3164e1376fdeaf6f4bb1766f87b502ff58856b13d2ae0a3a384c899183f611b82a81d3f48439ae02087d1eaed1657c6b595287419280ad87a4231e144709deb39b5b2bee1f163f6c7cc72202e076cee2dfd958b2c5f4b52a00161e3b802ff48064176a0cafa1e1cc247b01ec6d753635a45373314380e5689265b02653a2d0e131c209d6bb124a06e64fd0dc8aabba4292259c4bff87c2efd6c051e29cb963de012d449f563adfbdd7bdd534f5a64668e95455aabf7094a0646bfa27e6f4d8f28616dfc04a38166cdb302b74ac1331beb73a0340e4492577e638f5b883b1f1f5dd6b98a5ca56c1bb92ea93a5fab08ac82b8679d83aebc2e6e2136730a51bd3dddec7877be936637c74b182656e5879db81d96eddace4f06b47f01b416a2e7d8d813105fb1afcd8b595e312d67237c9ea874cafab63fb07ec4b226d2ca460be0ff2cf25cdf63b4fc1b66d6c16b7122305f51dea1d8324851a925127d0253c4ec89ff4c999d7533df9cbf4135e45ed3386ca73fcb95617dea49851e26ccf1a549d9f81b13209ba45a6e6f6f0b2deb6cd47e6499469bf47eeb7dc7d61dcddafc9e13e61aab0f20c8fd870f0f658809c5a4ee49b24b81c3d4d6120fde71449a80551b18fa6cfc5a3375b6420848a9f1f049912ef215f48afaa97944a9a4e2cbb84e41f58d406b783e6d4907f33f10528412d768d8885e89bca05adede6a422aa2e08291f4500ac5158044c5c57a1ea9d8bf323249a58523c17105accad3ebdde949b4b89c19f3f7df7733e333e94bc161793fa44aa2289b60d1a3ff98d9886f7d1536417bb528463cb55de19a23946ef0c7f8994d54a38c7509fc9a7695102c59e52ff1bd86ec4ac3b1702ee1f935b38d2dc7d0a1db53594be9b5372da8b5a137d8af4cd369babaf6a638b277c3dde0331cdb7934ce96b9f1ca1a8302a024826512c726bf4a8e73b926e1bc855460b7754c47387c3a2a467f896b47301e80b743d1bbb5beb38de7c823f45db3aeeb7e1c379973985464814b52ab4b3a8775b24f3596d7dee7085d586ce267cdf985410733bb7a356ce34b069e6535e08d04ca696e01cfcb3b86d33a7f70fc8d548592448fac239ea83ce4de6fea04fa23d8d38ba98cfe366a1ae3fe7f200322d719653a8e7d4dddd6938e1ce4a66402febcd03dcba10c48c423f82d4ec9a2c737962e5be2bef7a0899715005652fcdcf529aea8bbf5238b9ee4e1c53556507f0643a1a9f01c3d510ee215d3518c302eee940e5e1a0ef351efca9e9c4439836c954e3b723b36859062249e66d64e96ed8ed316b64d3aef3f4e2d934ad114e680ea1d1091815d5673ad3e23d0f3781b4f69a7cf56438c2e9f93a29d01844689884729021300b8883604a2d97860779c6f125443d11a55f1a6bff9ce73baef69481f08f66398dcf41cea1c2d2aec7402d490f71af427d15862738c95bd8c9edde36d3301d54f33419e44fdfa9301c54e23f846f5d5a94d03814e12d1c74d1a85b61844bddf28c60986a87e3eb0775e288025665e30d952e8b100e1c3b3e54ef1c6acf20955b861fa5baedb3582d80c488636e309e6dbe95f334da443d83bc8d42c610a733f91a3cf1784f090b44155c39656f1d15039f74cf8c1e257b6dd89884baffc563c7fce8ae7df4b315885f3adb92d5af4eacf336ba275420824e26462e6af57d8c55698e9f1ea0492e4f5852025fa69fbb9011bd652f2554fd0d5801188303b9c4919f98701e8d99342226f9f1993d78202719546810a2ca87e3a4e7f57f88ac4371bc22e270b6df3b99f57abfddcd028125548410e9260e5a3e33d12c21b0184a65953bf4de6befa0a039bdb1a078bc0036c547558af6420a79e57530643cf70638cfdc5c36f6f7a5ac33da473b6c8f66aea5f2fdde353b1e7a95104e07ecdf35d1a66b96ca53e7f050c3e187684a03d2f754b29c07f6ded5ad9451f81c41d32fde6c9e01e5c51363ced97157cd58ddf92578bdc8d43f214e6e7fbaa1bccd579730b39ab7db66b1baae902935ceae1e60940efaae2855caf0d446d70d75a58ae803019af90dc0839a1ba6f4cf75d0d8adb1e1fdca4121fd5789e75c2f4d1133374d3a24c202b11d6666b4d2f089c6a24fe9585a8db899691b68c7cafcfb7171c402840d548ac42d14ab9d26c6533266a17035ef9d41d356bd678d1174af5e66c9985fd895a3cdf89d52b063c95b2218bc50a8db47f557ce13f70188c7241e15eecabedeef31e500c217d51c0636d87909dd34e79afd171ed44d462592ba106510c2dd79d3cdba83878872418d77ae930da0ff42df47f5a1cce59d454b4129161de201d292d09181fd18be010de18bd09e97da1cef6480480d1d6343db36e86fea147fe6013c2b5d5ce2a2afb8c3c5316386785999cb51fb95797e7c4bd83b92ce9d42750dedb10b3eb2b07207578ff15aeb6fd1f6c51b15cf22a5409a221b2194ce103198ece25b8ccd2113b4f16fdbd9fd9a06c14920f89fc624b78c6348ba8f2ea5e50c5d106e56719ebb6061c4b8696bd94698c15e902f7520e7000d279acf2e8ed93dc4062217fd0e0e4f512114c7efea979bd35edd61613ccca0e205961fed996403cc319e0e294f8126fa61181093aa4c208f58493cc2a42f88eff72a880a62cb903600ac6744be7fae2b726e03ee8c5beac5397c4de1d9dec5fa4f22e25e8574029c56cbb2083f7937d458b9515fe2e39bae91acab8283e12ff09c6a42d4300ccdecd679c575f35e4fa35ee5b5765e8a2f6dc71af8ccdda356dfc6fb73fa558308f4452fb3f916f7dde063e119c76a30f492eeb3ffe81aa6a622e26bbff866443089cd4302ea76872a67779a1135eb1d91c206c5b51e987d1d528ea2a70cacbea4423edd65847c80c69bbabd3f61c8af1028bb518e2cdacdd4bca54817b86936be09d91e5fa4013387a77e308ad339c88936c6487caed3c5f830738386be792fca62586ea2cd7d65feaf67d5b787e19010840945fad40498bcdb6cd44f7180648ef7c521de73f87fde30a6a36a5659a982ead4dd91cfd70d419723cf9d2878e4f9e7b649dd97c15ccbc4895f3e49ceead1b52761d417ab73712bcdafeea2bbe1fa5c446a7d2599b003ca57d374b337771c9c5bb0f66e518b6b9c7cc673475d2b72626cf4cefc3b325bfc725d0df99345852b890e47840828b5dbdfd14aabeb1c52b3c15715b85eab49c92c8e07f96a4d2766916af152c3cc3d669cc4cacc09bc57dc189c1a19d12e381d9845fea123268cea73ab62bdf6ceabb51284ac84ef41a315b8797767f3dc588a640e0eb5527a75a11fda51fd32c38747e945203c873f8de57086cfc327bbe99bc4fa015c7ce3e3ab0530932b93238be7c75b1e77a3319cc85908e47172ed69148f52423346f10c4c2ccce90504b9c6927c3622ae608da329f51aa3ea540b464c74258945f6d6ccce628c77594f7e44ff07498e29d8fd54bd86f77af9093531f85462c6ea6f04027ea985ae1ae0db02327a1f0bf0e0f16d6e456d3bef5270f6ff0390253dc7fb4530764761c1751358c14f85fd322b6194aa67fd12ab2068d8efaa8d0ee458e5f7772f365c88f78e4926601318947096449250350e224c402af2cba5854c692dae3b93a7dc80f887e1e5169a3882c5e5daaa8e05d092c601627cc349bd10f6e4e617e221cf5e2026544b962d821b69c47d139f75a65e1af89f309d52a8a3b0ff2e39a25247bc6566dd2589d3b9669003c2aaae9041ea7f19a1bb92cd42c1e3d1c753806c0bc2e367bcf13108629a4412352f1fd2a28e5e6191de0d14f19f5729f8496ab67d9b066cb69b782818cbadebd60aca3a26440d17f35631bb581ff272053965e4148dfc6e15811bae26584272a755cca23bba1377a20bd35ae07de440c46351f55fe06818668a30059bed40d20ea2aec936bfea950a1de2fdbfbee5d1bd8ec833f97621abbb890761a65361da83de09efcf14bb07ea352ce18ffe4b50323d61e08758c7863ad6ef00bdc71c91d63c6bafd99f921538beac9662332d3cc7e766b3c470b1d31980027a115b8ee4ab0930fb73da71d75ad9d9918a83e2ccd35c0a9f7600878277af59c5e52ba80852c80f2d920fde46f412ea4ff3dfb0cc6e8a5008a7dd5dc4124e2731aa06030436f16d7fe7abf6c3f6fac57ebc7297f96c3251f9810fbd94ae7674d551ee5018afc32f53d7b4314d376eec62d6d49323c20c2a4391c8ffbd02d9879130402d70d51e0aca0a3a086f929a265a89e8b091929505057c8664cb755e4da926839cc2149c6dbb6d412bed24cabc72f9371f632b6eb26653442df3808db19811b2d60003121460ffa0412c878c03d40f21135ae46027e5bc889fe537de8d5b5332d8a40535e8ce16a8d59e839da2ddba1f888803141676b86a0c899702c087d7b5a7b28dd3e2a5d4f98da5eba1a64854ead33ef745aede5cbab6a45f7aced600f44d0a80044d949932d9af53432e54ba33e66db11aed9be41ff1d7dfd9db4dcb79531a845c6635666f1f740a837f2876f07d098f413420a87e6788fa97046e8507620bdafd534e0e345ddc95a92c0b2b01a717930292f3e1ba1eb9e2576f0157919fb1706d781db2205b3c059e9888e799c81224c5526e31cd2885d6c25866c76d0c5a3f9bfdc1e7f78169ea15f38928d37415c26970253676de8eb33ebb62dc44336680392d1078daeff8c85ff0537f9ed7f4edeb8e8ed8d70c447ef5477c643e79bae8407d1fe45585827888b67064c2a74dc40359a35c40c7c660d97def845cf0c122316c3aad26aa13f912066cbab369c865430d4db8ebb23af30ef8ba1287aa881899cbb13e040731b22f3ff3a27850680641e2d96bb9ccc4bc5dc5ec6e08255c1becb6025d81158d4c487df3827dd28a4c18033a5acc42ebecdc8c35c157edf9d8e040b932cf563fef43425f9816e7702deaff1703382d869e554f67d6a0eb6eba17dcd2d630ae60a7599a0e8f86855873753f0e9289a812098559fe08b69db10361f3704b8e3c5f2cf21a7042d98cb04fb71b56bd465382cd840cc72ea1752ce37d16773ae340a1ee8e5503eec633c5dfbe98521b84cd32a10af8ad0c486505fb5db3028e77eb5033e26ab1d35c6b3902055d5bd5908921464f37ff61d0431f97742cdf8e584e58fd9c6a0f05368d26eae4f5c21a638e5119ababac981c42ebb675e71914d591b150a4583a7f90595faa0f9ee1f1570c0b855cf008df821a3bfadc328a10e986ac68c9d7d99bac9b23f09c9338bfb042cbf503c6568128b7ceb4e1a41c7a6427830eed07787a58c5f9bd5031ce205d44c3fe0e0f3ed1551cc0bec648cf10a6cc25644817e98aec4a5f37a965ad58d40a35ebeb38119e4b08705b42160c00cc6e8ecb0051e8d81d2c8d8897aa09f3101a17553bd4637007b000d78947fe94043b2e81c994bef07a4d9d302325fcbd34fd1a9e64eba91b4a630dd6e4272f3a8f792d3b8a3953fee9b000b38565d7d5b33ef523c0d8f2dbdda9c368af819adf2601149d33d48c1690e754b85cea41ae8c33b410a769d7c5d98d75d2a7d75280f6ad2bbba5a5b8f8bc0335ef0c6da524feaeba26b18fc453b71e6e0d1851391d7cbcb0a4736dd55246ff8a52cc8fc0a4b022b4982c945d5133a461e600d82b98bb8565383335befd078cdcff6aac0f4ebbda71057c3a1dd43e05d134b5ea3f82dbc479d5a10d2bb2b37510d26cbd20f73b6484640ee14bce97ba92ea02e570195506d9b34604319d23cc1224dfb0964af155b03ff750cfe834b4aa84c164b6d36939c2f37f1b32d0f845ac420ce20e9869fbeb900c9f9fb65fd5d8b68f6d0a8fd9d6d4f675603a8be170a4d0ed12889597467f5e346aae25165d002b55b55b5c7ab0e44c9bdd1fd699556c10daab12b046ae74934193f1eddfd39257a1449a131108f8bf41e0a0c587795cb2c170a59d9618501f2168e395a7ef093138dc510c6ef7b73fd6e61a2bf70025b02dbc24b5ae857cc696d6469edaeb9f026e84bf73677053437240992bfc21d238c29178c10232f7e87352c553ee38e46a419cfcce0f5f2a9a08f0096f2c54678f6e4a074f6bc3ddb44e6e58f9bfa2e4ededcdce47b6be8d397dbc52886d73ddb17c67226f269d41e961929b2cca200afcd29ff3b0dba1233e931288513685e2ecf0bb71be333cd876a877ed2b65be0780a5d54117d1838a566a223adc7f76c53caa9877021e67d9be47f14824464ee76ff43e7082033abf0f81c917cd529df8c4c16863d64b194006aab7d1a0360771dd4b7d02a5ac177af6d2fadbea4d80cb6742bb5d7ccd887c0f41733b74fd2140fef0f8c563e238fb2cc01ce663a45764c83dfe658f4761c2ea535e714b2ef00c86a97227ca07a73bd5a802987ede6d9b96ab761ee4d71b312d87b0a8323ce8935bd9538424484b0c685cb254220f21a44705a969ae557bffd1b65ce5db52774c376acdebcd7e2f8e17884c1d63a55b74e39488d5b2a4321395c70dc514544b3834fd141b06793179f2d54834b52d4ee16a65f8dc5d9dba3fb4c83f9efed1320e5f44192bd6e74b261cb6f7c735c0e3ecf0be617b399196013e37e204d73a20721d768ea39004968b32cfbcc2448fad48595181767e7644d04629b61a16ca7026ff06e15b18279f83c5fd8ef5f49cbe75ea62d65331faac9c58cb9c615d3e3fc491bbecb9770371e4f61d19861efa900b73fd25883841724c0e9d6294bebfb8d94621b8f5db3cd36c2f3ece89cf7ca5063226756fb52c637fcbec0e63ca19a21821459dfe073096e6fec5ccf130243a67a7dc2226f8ccf5989c25f18f1216e06654db6648ef0719436a81864270a7ee3e3487ca03a5e0a1845c17af7831d6de86858931e870a470a7fecc48d3e456694dfd09466012c52c448d139b5d54a67c61cb8f84242e8362d921122862b0da21d0e0fe12f5e0f76976f57d52ef32a2b3806fe196aac320d5b2f3310be299f6d20b68a98dff84e4e1d70358b0031d5311520693fed02e7820c014fa39bd3a40a3b3f4d55d306114f7310293502fcf72020700d54f6aecfd23108016a4146268febc3ba88aef8ae2fc8cc7116cb0df1b29febe787c3acd1480f9d14e14409f37bdc1254d3c61044eb57d2c5be7a607bd561a07cda5bc21e06f358c9046c375ae85694a9249a45dc4dc4c82d7a3eee044f9c1c78b90e375b98a22a9ef20717f826b5f78183e92ed252c243306f8afb5fe72dc1db2f3b284e52cf54d496a4ff4f3f5297c95851896fcc3ff7b003cb0a3a449c036275c2b9cad78877655e8a8a600b15b30e0185c4c1897b51fadc0577d2ba0ea4aa976f50853062c226e614c747cb5da4b414b88e32488b6be38638305721050a0fe4ce5672952f65eb960b4b5f190e510609d6acfe3e6091b5db150edfdb2085392d6b152a83e4cf0e8e44a957deb483c7544b6c682dd6330e6ad03731c717406291776982d900dab8596a3738244ca5f84e54c06bc6dedaf02e2decd0fd74c5beac14d3c751e0e7c87558fb72a6ff3b7aa9bcc27f69670b0b623524119eb718c527b205cf59f7f32fb0e4fea969ee7a6dde04c7964bcce49ad23c74fc546d5216f2ba9ddcf483e9f734f09c27f3c4bf07b3042397420366fcef0981246c71c1edf6f7096db51fa8529e95a337cf4d3e1ef4e1cdfef0f8ebbeac37042a4a296cf2eecbf01c3ae85ad66481f98ada53c8fc8832be6329357665faeb2e0f5a45d28f8c4f93c3d548afb1b0b2d855fb3a2f3e6759b36c41c6382880117f00f7be7efe00c06bc6d15984f972ed8641c8e7414b8266626c476d92a6b22962a71f4d200279b21f37246dbcde03d3b4810853cc765dafa6b28356cf06e4a144210397e6a3ec1fee9bc6e8c187f577c7e9e0ad1fad93bfd53e31f75ceb8dc77c33a5f6e86401fc0e5adf636bf64c84430b3a13446e39d8440b451c75c9dfa007b12c6eecf0e1e3f2c084e9c6a009b073506684c7be61b1245b2e48fa00fe21889652076d6112043455f8cd3e4b6d953496ec3e256d91f737baf2aff3f936c7b20e4a22a4f0a01be6239b48a5d813257a705ba97a2cadeea8683803c0f2b7d36c015a702cba604c83ec89147ba4521a78395ffd98b48631af434626b3b53cd15c7fc382085549825e822f8c45b39dc08d438792725e0e05edc1d1c910b73ca5ed49d8ec0eb7cdc3ec6b8b2b36778548d4afb59d7036b8ebd0e4e753c918d4ccd24987cee44a0a4cd55433765d11aaecb409bb74f56542fb18181e3488073889740b875628608d45984a511935d849e2be742fbb2abec5c104fe3ebbbbfe650b2a02ba1cbe950a69da179fa32265c6a3d5d8181a3d719c4a56291573576660de44237a2ce71acd743d9a7115fa836edb65c8735244433d816cff91105b3c709dc283f28d36ef32030528666d6a023df1c21d696cd0b1eaa27ca62b12b0c58c00b156d859e83ff73c2fec03f6e86c28922a385432d8e43c1a90e8e213f7ec58a51f02b000edff34864931aca89b663c759830bc6925d4b4b97fec86cd88815b917caa727acbec2b27781ccd67ed02c40f2168f15d3c6920690e0800bd04222b0158a7751c133d659908ba588bb76ea7c891864be83581c941e0a48d03546fd0fa30ce4376057ac090a0ff922aa354df387847b4780db24229a3a5e2030de986ad2624ce2e12a2b10021e10e5111787d852bb9a0d869443c32e4f04534131df816be1f372040ebe3515c26ce6aaf5beaabd7eb754c2eb8e949b0c00633462db451699e365bc5f78d41e80becace37d89ea68c347c7ec7fb1c3462f6927259c569b16322f963b5713ab846df642f18bc09c327b4704debcfd770e7c8de20a972242d8c4723ea5ce8ba2a22d7c75fbeea8ddf922a602accaa02c1efc16ee26f7c697d3291d51e4044eca9e33c2dbe58b405c07eec0cc792a09ff70dd3e9be5c4a647267da50c11a062941a0313b48489a9e8c5a370124ca1f8809ccd020f30366f8c0c2fd34b61a76c9e73850edc236212280f50e2d83c8836180a5c5b1486d269e41cb6cd9994a029013d41d174c1e845b143daf516e19819951617d6c2158ac9f02c06330077371e11c105d6ac4d0d70986319407529c751562404d0a96329f54e40caf3828e835782a3d0abfdda192f4c034620703b0061da52547ee55c8d92805a4246e4414886824c5542c5ce4e4085ee4a63dd552207c0e08ed08c53dc011c8fd528968453c08f7764308bb3a4674d07c1fbfded93443e8f6f437d609842970e85870dc29dcef3d86535f97a807eb4061e3a9b04c0c58a27c002d87c00e0bffd42d5d1cbbae1de7f30382794d96df6cdfef0c7cf367e214be0ae9f91b4555d0033926c5d16c38021fbf78777440773251007f38e65f33eed6a4b989214c177f0f5a3753fcfb8271af3a26b8e3336c959101844ebf9e93e2cb64594767fd009820d6b4ad3137f8d345fa17f76952fc7cd2dc6345b9328132747bd8c22bdd9f94208f9e87bc7c8ba3b6070f871dda3fc1377f9cc73bdafa01ce36ddef82d5d8fd450048f85ea4d67b332c0817857c9a42a1452f50c4bfdaae5f28741f7dbc2a86afa45decd5fdddb00cb6eb8f2ba112eb8dad23905b3b7a6eb09cbca5bbdf5787af74c7eb8135f4b5f4ccdd1c1f16e65c2d8213fa41c23522b12fba916d245ede2485a6f663426f39d0bb05ce89effbecc1550aa03c634150bc8ccf467717518fa6d0895f04f1ada8e14eff58900ae76bf727b341f2e570ea9243f04688f9e542d52e51bfabb51eac7c1a3349bd0b133aa242e30d76fc075d04f38cc32210412adda87fa5777217d3c708cef973d034ddf406e4a1c87f36c60a1dbf97cfbfdac593865fdd76d550292b040d527872b40e609cf42d12e7b0d3305b8234a9a1c85848b81ff97e8bc0c365bdbcf98a5c451b6c5c7c7a9a6f2b5c2f92c0d4bb630ee50be669a04b3fa499d0dd15c798641639b17ba4ea90992415ee71381fcb3b0a0211d2125b244366d459d963af1b4125a3a3289a6484eb6088525943cc524cd75c50e47b1e0079dbc37c62ae18f868201d5393fe3cf5d2b80a17337a4b86a47ee14c3b7adfab9bfe3599d5c83c4125116737ae8447962410fbbf986290525589ede667cfce59d5c04003bbd378c01246fc41cb139649ad0b21f72f4483d6bbc0070c05a0b4c6e47d3fcef7f25e38b472c32d633ad16c48574960e1873ded5f56df13bddbfc0dfd94e8fdf1a37863028eae2c4716dd6a06ae7072cfabcf11ee94d0ab55a12ff1b451e93a3887ef8fb75f934a06ea15ef00531ba3d642576521836ef85f3a86762fe1e8eda103bc488145bba7e0efae376790a22cd4489391d546c06c3b44513aa97a390e1abb624860572e72f284676ee478cd52b0679063b8a0de77f84c32110e66ba915e227e80a99c6bd4e01f7f83c93aef7133087aaf86f6a7d9164e59502d836f4eefc62f9b7283d2e071cdedecf9087580f521682f17a7bce844c0daced391b92f33ba7508169fdf3db0ac8200c9d7be72c9925888260e2890bbdd3e6008501f5850f28020cf2f13ca7ba0b12512baa4bc400941aca070c90214a640e82d1256f3cc7abeb6aa87a1630cfebff9390f655a6aa408b376abe96fbf63e03f287b74620ccbdf6a0b0c164f790fc307c7af488949aad0d51095f27ecb85d96223d8c0885b16060dfa8aebd7e553f0a5da20b2d423d5833b277422b5b1b37d300e9bf43019d66d5a362b4e9a1ac8578089281a0634d52816f2807ece223dc4548d851c2ed251fe8ea93315b0e79b0b2f738a01bc8c112ec24dd5e82a78dee13e3de273571d361df70cbf9e9bae30a5e56164bee36a76225b235235a5357aaa840727c621f39f2bb9bdafa799ac707224e91cf8aa1344e335388038cf6436b856d74cbccba69f1e6e6e270e36156a051c9e5c5285fcf76ff0240b7eddba9dc993087579c363cf762902cd165800129687413c3412223e6ebf27f4ed9cce709cdc65e87680be5ca7db498a3d2735c5406dcc466f18e61dbd269949932203908985ab8b1923fa5652197c49e63ae7fa75ee23106bc479c23014466c7625b036d3621083661a6b0ebc6a9c4e0ff4939a534642ab03a4f45931057de1c8a0b7c80c1bcf4f5c60c1c5c4faf2c076717623dfa9e104d7365b202ad05e04570ed2d5c6011e5b617f723607830ae0e173f6a04f2ae3b063a6e22d3787632ca659a9de8494c57c533ad572a9faeb395ea0322c46c55e039807d4dccac349cee6f0042a793fa5ad5145541c3f2550e4793db2366108655a0707dd1bb4a95720acf28470f3a4e8b389f8206039f297e3583ea3cfadb3c4c8ece3c151fb60ca05236377682b7c12153849612adf9cee155899073083d1507d5f6f444b247e2718a5eb072d51ce4221bbd88aad13e2fc735ae85fc9d3b95ad2cadebea20a1be42767405a134b7bafed124eb912fdfa283359c2f292c7e20d9217f2543503ddf88188bb3f54144c4ebc1d4668cfb96e512af10cc8dbab11f075059435c0847732bec5c37ce4d3df5d91417892c34d4888d68257c247dc761e454223302a805fc16a6192059ed4a3741678a90d9de2cca785d6a94e7ef2fc0a5169439312c130f8737559453e5f9bb1ef8934240758d9a7d824d81aff770d00e80b75b4a6cc1d1349330ca8e8eeed591475cae7b528eea0eafca5a5d8142f4ff5b49e2f02cb5b97b1153b2e7e994f5699c7236ff7d74c87cc2ee990ff91cbc6ef6e5ff8601f640e9191634cf7cfc34900dd1f326b710e6fb05c80e81089718dc4672bf6db5a615e705247d2d14d2bff19711b28a2ca61359cba62911f8a75547fc95f2ef0596f0516b5425bd364c616ef5c022e845f5722cadd93160901c77c1066ea2f163aa537ad7d52aceaa89ce4610cf533aa0f0aa7becd9fdad6994f75a23d9dee921737da9a5ff6bf907b3d44f956cb015661ac3a3cb1b3d34d00772c434ceb7fa8e0ec64f67669f3fc0154b3d1bbcb513d696c03f05e7fa30fe1d5d8a98d0e18f269ccc43809dbd3d462bb16f5f63963ddd8ca970ddbc533908a2cdaef42795e97d8e3d949bc67971a022edd7a742c292791419de353da1dc0fb3a46ee0538126b4ea82730c9b858a36a81ab858bbecba76ea9bfcddc6fef940f5d8b37c1416c8b49fa84dc009d3c94c35a7a420fabe6da89bb0a1745af9fd3194ae65e394f3c4afd4754e6947603d134e3604b8fd693eeba6b727e46ae7f071fcb4a839164fa7bcdaff20166fdc9809ba870a40c0ac8a101cbd83b04b554de2149714d2ecf9d64a9c4dd7e0d6ce3be3875551548042c286edc9ef3eb6ba0a7bac6c08bf7cd52ebd9ebfa9e72a333026b081321846bd292ec471e56e8effd7773b553cc78423ccf543da2b19032c341a73091024d9e50964da077827f45cb17cd58b7079980f1abbd193eeb28e5f3920375730df98320b315c33326b0e68f4d8aa2bb0a1963a0f0d2aac42301a862fb0cbcc07749e698323b4ffec5b4cf688c3345ec74a81d4ef81fc8d7f26e762fd1fc73968636b50fe28c8e2ba22e042e921204ba9e4c26e7718f99fa55bcc37ea5459e09fbfc08e80f6435c63e743dd46a3f26747ef68c232efb00d71efa8a787aa42dc1ad21b08b445dee75e46472fd127d0fbeafa4c1f64a2b5c889e73b0b7ad43cb1d114c3841b2b44e60eebbb586f5552c4dd8ab043a7f9419379ef7a7dd4a4982dce79b38da5a9e9d64152ccb83e2391cd8e349106be97849a03e16587d84471fad128156bd9c27d1951e3acf14cb595cafe654236d97e6262374dae523f48c95a3c48c53f902fe616618c1c121273e49b9662d97afa23ca2d4caebcc5beef74f41d2bc9db5baad601248473b453bcd1cd3dd9f4c457b7a106aac8787a3beb80807574b4766faeda986502874469f9e669a40afe1764a77de64f0de5a4d6c9cab10c5965226a47f79a0aedff1f9a6c839664d85e3359833de2500b443c921e18143162a9af2d2079abfec5e6bd6db1ecd8ac49fae1e2749511f70b68e999797c5da87e63971e4219f319b8ddf96029c7f25caf6946d96b90b72f28be5711ce13228cda7fb1a8af486d3127ba1eaa9ad7be8026be0f76dc24d856f21388f73221e7565eb757a7be29db50273c0b0790fe015f41bf967f6f3e5784b6a161c96af1dc817fb31e7a875d8fba2e2bfc617bdb5c9ccf124c6a73bd87429de4de58f470f6a8789049f03abf0cc882524fcfcf6d497024c8cea894bda0f7106a4b16b26ea5f7b367c77e647aff23ee66e7e88f64e2dfe42072acfdb3e869932601bd7647f92e7d30b8338d38f9f8f8000dcf09de07eb60ff4f6446baed4d92edaa858c8cfd0f7bb18736c6fbce6742fdaffa04bfe816b38fa7b91cdd3fb4ee392f8ecb978f812e0908a7720296218322f87a193f7eb45f5adefe1d91648dea2cc0f41ba9be15bfed6730a6b0de39653bb3fdcc5a51559fdde955f5cbe4a5b410f22fd0b723f0abb604580b93fcff5a176bfa23b2354dae5433353f93df8899d1df2bdf8ead0dce347f01697dd7993a7620d5d99f681c1021f572aa6d201b28f51a5db9addbc3261b75157b11028a0a4eba2eb39ea1e4ee66beb202382fc273f08a9c036d213342ce9cc7e4cb6e45b180518abd7bb648da8136dcaa32cc385f01ab19738aefc2e650810fe85ccfb96718394c2ba6b477620d09481793a72d0a25cdfd7d7a221b445a3df37a16231fa28a53496d8199209173b704ac435a95cbea8fbd8af137ad44fe97cb2dcc64f53102e779aa55dc50c31c5e137ea1b143fc69668fdb0cff5f58df2898590a63255625c35a0087e77cafa4b985fa57cc1ecb31a28efc4cecec891887daef5b87030ca2b98bcde30f1033be0b742f0c5dbce5aed555f96f7f73e588ff99e0687e0011ad4e19ff03995e1de0fdf5d6a8125b72f3725e23b0d74d0791e6577f170e2b766fc0b1c21af2cef24496afdb6161693a0beec234c167e387b413039496bd6ca4eb639249b388b8dc1e9643b2528269a5a72dab96789183642b85021331f8ca2e5dbea8a0408403ff21867010f2adc5861dd3748d9ec6b0dc12c936637cb76467c88b3096320bdee1103127e62c174d2b3e278e64add0b224a57f282b710ad5cb3751b12d97a0fd63fd2e946c3b0283033f667ce58dbcba251dc09d852b779ec11982f65623ec2c292f94e1215467a0ec5a9ce43a47856f87f4687f7ece0fda833fe9675bbe0aaf6d29c27154cad730e65d143f04a094b1b8a49857ab476f47c21bec2665e97b6ae1b3705840358c561c5d957ffadb25178b7ca445bb8908fedb571bb7a68b3cba8bbaaf6dd296ab97a8d360f1e488c3370594dab8cf44bf140253479265da518c2fa3edb68226ff00ecc47e0b09f8f9a4f3c9114f81842dede81b35713b0e4017d5e25aa78f3877dbccca97e7dc782cf5461a6501af7d642dcd80e5a137bccab2cf856c2806dbf92843c9802a34787c40750c17e3c0ad566d762684208abeeae429ac1798bb7ddccc58625508410e32d52996d9077193dca99f971314557d83048080bd8985b8451f872efd90520d47dd51065457881e464b5e46aa38d66158d29d6330609fd809504800ae3a6e2758d37ee91bda0e5c4392b39c8df97b0103db71bb6736301a3f99cd7ca0fa80eee1ab45518096da39488b3d6effc8cde55e1c1283dd602397ca8c47c6363c1025f62439b48e528399101f3919114331a44cba1f2d28994d5e938bb33b1a0c7c7af51aee39db374fc813bf125c5eb28e449c3c1a7ba69e8791055151f067327ae98dc9ee619ecce4ee0e61937564061dd3d828ef233c31206ebfe627145d717a14040e46527de106bc0d1aba04f652b4bdcd0815b2813bd0a4bec65c37c0fe95877a140b5377c606fd1c01ed1944785879e4b52d4a0cbc0fc2f94ddef750636eb4d95c56db9723c2091196dd4c729777c15bf2cf3b210846972f351539dc58c79b69e0828b6c87a742824be34025ae6fc3991a5daf4b50c5faceb4685711cf1a837de51e290ed08ecf77bedf9926ae14b223b95958cb597dbf6b3952dff1c5bbfd1e38daf13cd28e28e049f241de18fd01dfb919a4e603109e0c1e50418e02ceea386d0976e946043432edda3d961433027a9f4e6fcf64e42c99bea8e6b8b8757f23652f2a28a74ddf0d38ae285a9eb9af738cc153397af6db375dbc5de685a81eceec17e63233d24f62c2f025ee421e7351076170eedeb199579597d652030980f1b63bca8fe3c7b6d4e04584e9789868cb0734e41ada04709c661f02e26fa07c3a6d18d4ac4a567faddc60cc2d036f31e4d8f78f18267cb472c48d1181adbb12c21e9da08e41090cdf1abe5c6f1aed92149e72bdf2a961f0af983506cf8796a48b44a3924b65904cc8a43394b0cbab42490eedebbd22675d04c4cdf028ef6ddbfddf2085a573b9e92d82c017fa2eb5f2961b647a41b25fb572a2eae99fd2c686465bfb2078e5f04ca7349623153df895962242981e5a4f63fa4311f4448b71be1f14f1a43dfe415a4c704228915d2c647a6556ca80fb0154e2e34ae1bef641fbe7d426e2ebb9543c5edab5234f9453fc92ed4a5cd058215095b0f8813e1d49c00d262e9e76defc4fca94891e165c3737b96b0db224891b3d4e859253885506084bc35b98a6bca59ea3b979ebd575c436df440f628e03df713c5034877f389c55963e9ae60edb2b0a887abb4462f65fc4db6820dbd681657b48d0d521731d95c212dbe32c2a6b8a2e9a0220dacd3c2a901b3375d55f729a78a3194d8e9fc5273975f86937043e14cc629e8b92aad274811d7daf87674627f8b6212186e564099c9b798b9c45bfe5a4190baa3c9fc5dfae9124fc6d55f79e17b47a8a5c256da4d40508683394174c9ef8b85571c92e4fcdf5efe38e8a4ce45cdfa2a39746e195a8073225d89a893bba3e86801d9b1b7d8eea626862081e3c868fa0e6a1001d6c9e58fc3669aeda4b3b6c289f6ef590d69fc29bdf7ffa3dd2688fc4b78caa091709c22fc487d8d98fec64df00e1207581f946398c5a020fa28b001056a050b247014811847ce93a201caecb3e96c803901106bb3fdd2a44b71b33e330f1bf82217b2fdb7fcbfc0700270e4aca008aaa3371801207ca0812b04f9a2b901b67d336355e8a5d65a46799daac8d3fe497e769a8d2c12f81efe3b29c30cb9cad73c85172f45f90f925aed04521483a62382bccc1f002ad2bfd407ebe08107bd661e86fa7e609b011c82878607b1d345f98b213d2898149442ad76a14923370f9b946a24c0ff67203b03def18f0b19069227d0a2eb31a173eb5fc5b2b868fd536d2648fc525e9fbe660d287ab3de9782ab7e3cd3a517735bdd99bed287a733724192eef29af00ad8e8382ec99f8e8bd03f364d71f9322525249bc810d646871315c1fe9522826749d77d3f220252394fd86eee78da263a74f368be03ceb2efd63bd38e40d137c4eb8b2ab7b161e494777fa6f5d38b79f244232722fa616aa1a57b6f6765a93d93d21e63219c3028b013e6958c5b2356d53992b62f0ef84d9e978142623b4df6b5d3960671e2ed05f564645b77c75bfca019c7debe6e918caab8e8d5c7ba622b8c6d659071199dd9dc553adeeb44294508514315a2448278ab20c48235469110153f3f18ebb5a57a5d655d452917ee4f0d66c0e154da88799361dc8f2ed0c2ec67f0166b8b2928214b946a7b109b375519f4b568c1309a3089aff17c13a46540eb38e77ab1b0c23496308744f1331560fe7de1030975b47f7841a94ee1e45fc5fd3a0ba6c6f4f5accbff57e11d7cfeff5cc0f66a37be75cf5888919233dbf8598f269801d99ec10669359a8500bf750def74387287855f58049b7cdf492fa772abff48041d21d04886ce1ec52d3629b10cd13d3835e85d5238ea16079b67990f1f9b45601f46a28b1bf4af6b6e816fa4bb34d254dc3f50efc0ca1a44a80815ba3613e91d7861f674d19e36cdea646e7eeed5a81b608ae5d9d00bb4a86519e218ba78914ab1d1264a8d8aa0732ec014c64c2b2e690d1ddeb1f84f0d99b6914165dfd5ed421367a588cf0d9b86a59b16cf39e63df9b081a1ceac0c4f0b2247b7483baf33e33a7afc802122219578b02041524cfcbae4b51b835d43f8c74ff1522f67aa5dded3a2b16e01987c4d13d40a7eb4fd893eca59b199b7d07f447e4a35b830a57afc50fd7070ae389b5ab17e5f85510d4250ac7c1fe9cf9cc717c47da41f33ff8319c5ea74d6a4995f05109f7d36972803ff052986a5758cf69626adfde776ee5b7cd408e148f198696f452fc84324f0d422c077f2d3a8270e384a6392b584dd331a679a00d826e84739ce1c23341bbd81deeb131182002856db86a713d56cc3d2dccda0b1681a9efa70e4c5ec8c4e6be8a3e2ca5c9b1758540c1461dbca7546d4571eca2ac1ca6bf9472ad376bc1028302d3ca8502592291b7e56471a46562f6313057ac5d88cb1c3143c373e89dabbe99297f0d5c83fc72c0226a97b0abec6f606b872b150d9c72d92e10cdd1c32e132c02d73c9e131d3999da88ef1b5b60b3a52a04ddee9ef624ba1114538a4b62d68f47099951033089a8444fcdc762a44851428bd4cb4ed0ed53378ded9965073f6cd3041263642e6352591b8240511d69f7b36912d25f97d8383f05881284ee7ec3f0ce56736800eab9ba1f7a6734b8bc9947eaee0279784be1cf9a0dbdab2f4a1bb3ae3317c74cf2983cdd1b317d4828ca93bd8e8a1e98a942f49c7910438df933720e9146f217022ccd238f7ac41e65779f6d4b943aee613b1c80f220194d622e03f201d71cb63b19de0c3ffdbd8ea5b32d24b80b88a93f871982fc4d793c35211d69191367f46184d48693b4be13eda1704a9232acddf70e1fd2847722b6f70358517d27d544a6ec6c59a8bc1ba2bad7d9b99287de6adfe51dc98171be2ce880efd5a2b6b98e36ff2d29c07b22bdafe4df303557a00f233f7e52170cba83a9dafb95f8438b6f66b3140fab3e4fef3c7ab7c49c9cfafca28dd675d7f6796042196981b0d3dad98a512c8d6e2c7b212dc98276389719c1ce6110917f82fe1d9bf9d8dbcc0b6984f1a19d64d4e7b6fe48fa04925335bbf093e97e046e77bbd3546584a336f99ff870246f77c1965b893fd166d55c874a9746e46f78f6f4b42f67b10fabaa4979962bef8f42936ace06687df3a69272ca14b528023b422960cf3a4eda3fc9c0ad0ab33fa99c06730f1a67dc59d8f27ef70c1dc7fe4eb8b1db08df75c639f767d592b83db4c74fdec1b5471d225ba76d1a304c8a89aec907a260ad77ca16b7675600eb4397b1e624fca070b8f4c9d314f0013be2349c27e9b6421e3982dbc5a3a616f1a5b492fd91193ffe421ba935044ff007a67c7bc37521c7c9ee6c5f1fd48aa08ae892ab8287c93429f2453587f22e99eea19df7663eee8bd8990a08f1b33c320e3988f0c7dc6424f7e44b68cc8a726312492bfd3e5e03b5bb2eb7961ea698dc04e2ade31e54ddf344e7c1708a79fe8ca087f261b30875b60ddc398187c18649a21c00f9d8be02c041068080272411fdbace58b7f7f147e05c62f52af80171be1177b8e5a23f685e5b9e60b63a51dfc7aa3e9568ac02ea959e5f437b8b7f1cb63c0b8b16bc93c6b599ae576a2db1e52c043fc59002e20d665de33f46cb87b76c526a9d1f2940480b747b681a7ff0038fb3f1ee75a41e868eaddfa3a7edb4a7ab0ed22afc714e156cb083620742d2639bc911ff6949943bf7b33334e82d89daedd1c4733ddd97bc527f8c435d451fa9161e5cdf0f6d0e2f55fc5a63e1fee85d17a42b3fc4601f0e333a3fd4e6644a9f080e103fd4c5cbbc7f98a366d3a37afe724fb051f0b4974e63ed46667e8d18756c566dcf059201e575a103119dbce2c0827e64df1c4af4d98d0e63627ec4afcb3c455555d93d499237ebd228ab37fd66afba9eaec7950b9f8e2aac030107edfc0dd58bb02151034002d611f5441beb63c856d00433358781cabdf0cf02820adcb3974c01da78a9abe558fba05d1bafa70ca80c9ddb9894636a48503ac4ba48f03f4902ba1a3d290d8233e8f0b2c1b0d0053cb8b6642d59d05abb41f291f67e5958c6698ab2a9b34247f3441be1b3af925d8e43cf8eb960c656f2151c5aeb30c0f0f8bb56b40c3d9d6987568600c7406a28bb5c33a27e463011ba7a121bea49b6f82658cd41f257cc5ba4f3a2868d577419e560c824c34f817f9dfcab906a2378b2f22824e349fb3d76efb369b92838afd315783621b339af912725e710ed73f8701c0f4fef18846a5bde34a883d87424e38f7d8173b2007bb3a3e05170e39306d70283cf1d1c4816e76a1b4739ab466dcdde55a3c162ef658024fc6de82aae6632b9113981f5f16d370c759ba3c258de418bb18dfb82a92d1592bf50c9e8f1c5c1e9b1a6ffd7493ec8c564c9730a364c1b95d2041646872d576a6c438cf9b3f16e3b6fcf9f9b535fa84549e5ad14b4f1727e5e728491d3a014142dd6e0bb5806f27616fc4a149348654debae9d66d1f929f8001c2e3ac1f2e4876ff39a27a6073eabd7ea3f12959cd1513aa655deb76e30349df57c04e0e31beb41e5db6bc5b9e2ddc1c7a88944555f4d1b2182ef6f01764d62deed553ce1daa2a2081e0e403c270b08e5ec2f751461301c6ef902dc287db06b5bfcfdcd11422fd28037f06f6abcb95eb57439264f90350d1a3c5bfc7ce087a5a463e9c7b4c06e76443806f6e4eea81764813a2575712a06d9400ab9cf9c40fcd38680c2e2708d3099dab4f38dc02ef92f3bafd7399e81cd37f312d0fea5e2cbb8c7b372c19ff832179f7e4e82cb1809315183557f6416bdd00c260446d0788e3a476e6c9f3fa0987ed7f422db88dd641129c7ab3d346bdd1e6578e3bd2c6919e3f8ea8ee9804fe7498ddb2c219604c6f84423bf44414845f6e3291e7ec07a9bce420b2c599b6581ee478781e05999ade154fba600b09ac56ac58f02bf557b031d96c80399e385fe4a20faf1097bdf11ddda2af2a03c05c79da881967638b86fa726db9d45c6de52641fb23af8f5e8e03835307569120aab6ec20dbc0502b4e888dfe82bb1603760d3abdac1b880c87401274b2ea3d07ba9d5d1090a6518190e6ff8268ea6544f6170268e712b76be086a1cac3781765b699a37609e7c38764af313678c6336a9b679c1f9e30b47d55464698901663e5dbc8c07e748d785c9eff94ce2f7ce41822af2b543637bb7b8852a09a10df03e72a6fe051fcf92328d2885fffe27158014b8f61a3b445a2ae11fa6af93afae05314cbf1c0bc8eb3bd417a7c5ed4ac6318ef9f6f6db0e0d2efff4cb1e6bc47b4a501eb2b6b589f264bd7a29c21fdb937a01d4631f00e6d26f83506df79101eea636a766fd2e689b3a86b53a6744cb6d3548f827a9096189a4e18a9a6603d4dcf4f5912f8681425f4952b15a44eb6465936a1aa539bd90039177f41b7b62a94f0c527a3a5663916a125aff21c812a29c5dddcce1baa05b52fb5d0b74127fd54887dc3c91eb99401a248f085bd2dcd226ab5f633835213cb880a092ca9011ad362d0c33e6fd3f62a138f3cc10ccd2bbec89113d52313c4135fcab39e7cfd805ae0cd8336cd0ee669cba85c71e9b2dcf4cedac9ae8464e4f79210d197be61c2b0f8596cf98e74ef57468fba320143937844f529f59f29530600ce8abaaee23c945ac486c22a72b2879609763ebc2fd183ac8b442c0c3416cb7d3727a87acc68581814abf771f1407e3e80a03bdea9731375d93bc0b08f00f0f248819b3e9003fb800ff09cf6e7644f6f70c026109d7e2b77cb0dcaf43b4c25f32284ba817a5b96d8e96368675ab3d37184bdc828036de67572f43ba3dac6be6234405ee27d5983ea5df0ced3f0b36bdd90de3202284bfe879ad04e3acab738a7c117af7a50ee130fe31238937f46277014c03f9352797850bc86147c91861b40e7b3d42050b45a1157445835273092b532b8edf49e14af2f6bb0e8b376cbdd4ba684c38dc7248334c1c028145f4068c2aafdf632ef2bf3c224fe06937cb40d7b472d221576636b707aa4ff33f718f7e7c4a1f8c26d685728274cdd483be3d3700df2f3150db086823d7206d153abd7ffd269a8ce7ddd397ec9894d64f98f9a6b287be30eabda0f0cd57dfba01690ddf63c4b77da309f9137611870c870c1105cb5be92b595e4987cc3c7201be160a38070e7fb6afba4484585916e6219a33b06d6adf4b2febbd1b9388e30c638f9531598b522d649b724182bd1eb27f49a3a8ab14cfe923a6ee2862180ca0b9366bb52b4bcd27b64b26410e117e07e60d8ecc9a3c4f9d614a3995a3cda077aa8e89dd00bbd85db9a116ae9cf94bfd769d5e99773c342780cfe6fbee3e51a02cf846406e5c7f60363b7716d3cae2549614a22429e2f1762d8111a913a2494b4bd677c1f102e731b9874d8599366d61f5c819e4a5e03063ab1f0b1fa092068e70d9a5317e6f6485dd15649af64f0d8ddd2f0f0a3db95819e2fe341d487885aea735d97b70070c30250399a4925b80cc090dcb3ee5b9e636a8ab27379f99be53efccaa30dc9164a34e154ed936c44ccb3611284a817dc49239c0d56a794e3d0165b374f274445235c388a479ffe75c722a8971e549e136152ab59174d8c3ecc6a95ddc6e23c5a2683f2a0f170831da296761f2845aa480dd0b23b7115c8eafe73f5d6d3e321f491214b20942d5a6288bc03f5d850948a6bf6b2b526c3dd792dc6594d2bbd8d7780aa55e18ff40f1dcd7ed92473b126bd38b91a44ad27bcfff583395cc402013467c0d168f04b5d8e54c94cd4532b432f56f61489a71c6e7220ff0a2bd588be016f26fecaa769dbdddd02ef666170a348b4fea3bccd29e06393e8dfa8b79abb881b0e59a4c7c99c8764b0fec8b1031146306257b81deeed0e1e04380631c7fba3532bd4a2c5d081156ae6d9e0e433e27f6b08284af9aec7b435fd5d28f661c7bdb52fc8ed1bf5b5af878272a0bcddd14344fea5a766a79268e5de0eabf7777491ab62503f9edd3d1d1a06b0df0dc3cc5ba2bc4a71ebb8ff8121961dd3288db10c418ac463f1cac558cee93d4ea8ed74a5de4d7a58850df6db02b08e1be68f9b6dfe0be2b8bb37a659c223a7d43097c2b79d959469c2951931dc036964fe797824f41878b98873e41af884171efcf9ee08b7fc7a201b4f8c2e836372b0b7de1ceef1eb19ed65c9d9b44e6a07ac4fd532971a1b0a9f2cf6adcc84f7ea3a61e3ff87daf6bf4818c1d080371a579e953dbd79246ed20d18f16b911c7b64764d77894dd7528cedbe0a7daf22fe57d001fe7faf93cec17c35370ecd51c026a822f6a9f70d3538b4e37225b3b2cc092db2b515a357d433f69f7998f967cd13152af1338028c6d6eddaf6b0f864894c50f8b734e7fade6710e37bcf16082a389fde6d4053ff1f32bca773f9e920f883bfd8c5eb9dcbcb6780fb1762ff699c2bee2094e2c12b349e2f796ec954e76eaee1de567b318c98146ffe4ee55361a80371448e16cdb355737cbc78786d97b2a6294e5070baf9f9610b2e739f02de75de943d34920072deec09ee430fe6dc8f388ab8fad05bcee1e97684c1a1ff9808b775e5b03fdef5c851ef0e4e397f81a7ae7cfb5b122e78a68f211fbf7ae3513f7bab37fb226cb4a66403932f2a72f874551f325a94625a495fa46bdcac13e0c3b96a616f6ea7dc33ba8a6acd5a3f9ba804c01a2155a88d30d104fafa35b6f5c520c5836e9b8d33bb6b90276ff734eb8bbc0b09cafe70b17f1c5aa6122c86ca5112139ca4280b49fe74e4ab47ca8b84cc6eb2d37172cd3a6a0ff40bdc34ad073e941d8f3e6a94688cc9d78cd6fc7a78e2056f5515bce46eeb1cf12d33b68285e304361156785f98175133b6fb61711d758504936157d07d96330e532e9efc2bf060de61a074b88f71c5a70ce3e9ba5d50f91d8de77ead800f95f99e0f5864fa73ff7b9b549652c01d4e4b9a618095d5043929be7c5c5e15629096e7f171fc69a1e25ad75b0077ade8a30092817ceae90b1270160b6bdaaddd45d263a020a7c3fb3035da5d757f5245788ab8f02776352e9d152e4beddf5e5915a2383326ee658d026cf55544cecee60ad87fbdf649a75d873948409d41e8720cf4d7dd8e7c5c508e86195c4635c867a8febc37c87558148f9af3d4a30d3367c617e0d67b0c8ae075829d086f9ef19eabf57ee27f5e7a7fc7a182953d851861cd933f19caaefb56d25f556c582bec3328d489b8e24b4cac0471d5e700bc49fc7dcc869d0df631f2415e4d45339c5530a6c3eb0385eaccf67c82c67c2c333a8b41f1a9c6839b4b1b9da52375971d7f1d6b3a6b518511b27e88e04ef208a3e58fc0d8bb9f6a414e6fb2bb83da90f55c3749f446f9781cfba4313a2e505d565cdf023f64e8ff285fb18cb4cbe4bc1d709671b5ad58d15380616d7fb02695d1992efdcb4f5c5a4ec7685e5ac20a994761b392bad8834450f6886dba6c97ada29ff627407812e8fa54496c559a69c259bd64bcf4da29420b643587b67c30bb15dc1f77dc95fcabbc01fea8b525b23954217710b8cc97ee6e09c4e4a542626ad31278fec81bfbdf6d7e18ceb2fd24bdddbcf92fdd6ce7b3b06480ef51c0b790f8f7bcca1baab27dbb630c0dcc407d328538dbfb4f752b5ecb642e67bd587d81b92df18dcdb0d20b927f134ee419fe5cff8e53a098d9ac17ab23a0de9b68b81ecab0f7dc40837d72411cdea15d8a7d0f97a239f19a3150ff5eb751b3d34804b29d71387f6a7d6233b32686187245b7c6286709956279d386807f39c0a3c792081957a4e1030d8a284c5c91860f3428a23071451a3ed0a088c2c41569f84083220a1357a4e1030d8a284c5c91860f3428a23071451a7ebcf10a4e2c71c41dcc18c78b3f5c5ca1c38d1e258668f1068d3c6cbc88c2041434f2b0f1220a1350d0c8c3c68b284c4041230f1b2fa23001058d3c6cbc88c2041434f2b0f1220a1350d0c8c3c68b284c4041230f1b2fa23001058d3c6cbc88c2041434f2b0f1220a1350d0c8c3c68b284c40c10e834b5d2d5fc2cdcd36c6b42dcca639b6a7aff9b7c651b555d414dd9184ea043b1411e2082fb491c61f6e6811c5135668238f375ca822892bdcd0471a2facd044146ff8a18d145738a18a34fe70438b289eb0421b79bce14215495ce1863ed278618526a278c30f6da4b8c20955a4f1871b5a44f18415dac8e30d17aa48e20a77c27a69d2083f68ac076b9c891afc32e32f581d86091f23406441e38d1ee0aad100697bf743d40c5aaa128535d29f4c54dfd7e8e56517a1911e29bb291342bd872b3a8a9280ce131df26410a6aa3c00fd23a0bd9686404fe455bf80b19fc189c27604f477531943ff4ef06b64a1da79b98c3d29b5afbd8cfba4a83fdf4e947c95e19ab40c751080adcf441b9eb28c0217eb4cc4fbe602f0c65fca92149cee96e75a58b95587f06b74165442f9ac4d315cfbdf6c61c7e598daa6dbe0eccb8999c45d5da912b33593b0892e984bc1fd3ed4d8e8c51bd7c985c25dd679fa0e5fa7e77aa2654b5f073c925aa35e5f65621148132b3d0a165dd9323a88d6bf9884289d634cb126d9a8b6d5f32f9877b146d49cf3f26b694b3a82b7d05d9bd3a882ba60cbb34cfa010900ee46a4be3f5bc074a816a092561b6e7b9cd3df0299e552c95b08cd9bfd4dff88190dbe4b839d709ab37ae3d985e0f32606dc3db2f507c73e77fa68640085ba4c0f01e4463183560abd9349419a4dadc8f80e6a7298231afa6ee07919f10a40293c97ddfbc382198270120cd1936e167c70771b12ade1312f546fe6ad2e65eac1455c77882fb82f186f92d09d3f583fe0349cd68d46203b8c267453c2faf5365b597a84f3cac759ce73af4b0d915eb8ec364f9357f72bf113cde8341ae1f78e689f39798e3293dae07ca993539c1b59a2b277fefce4e9a3aa7d6d50904764584c5387b06bca49e46a18a66319969698ddb94b7d40549388c38311c9d3a3e43b64cf23620ea9a31cc5841aeb3ba64df8a405861c9cbbd64a6d24a6d750b77a0188fadb81eb8deabd0713a7eb8d0844605ba07f817b9fd1cfbf0360a0aaac6129af08af6cc4e7942e8bcae849d7cb28f47f502652216fad42699feb53d5a8fb45ced9e21666afd154a3d7752aa46e0e27aaa36fcf69f44d9b4d256fba9289cd564b520fb966d55a18d8b0b25bf88d3c5c72b3fa52bfe8feb74cb82e7c2521230c66c3ef36291544aa758cfaa34953079fecfebfda3ffe77d4b941aed9a2b089101d83f80cdb7f71587965f21fe3784858abc72d8097f90226875aa657590b3eaa3c1539fa8a599ff0eeea66876c42fe315df53e31d13e36d4de56ff86c8901340ebbf171f0cb8922ce1d11750017e92f6efcedebe3a1be14aa70cca437994f50eacfd71de50dc21d1ed1775f8e3b638e09c4de4fa89c417659cc6f70fed7b2baa4464880ad19aa2d252eb517bad1879eeafc980d48d46d3dea00c1475659f7f31c0a41b6e12c15c7e7dc73d69231c742b89e14d96af67b1b2a79840c5fec05b7e4ae53e58946cf0a10381b542b3b3b1e2ccdb989132cd55683fd586f577e67d9b97bd53d0dbd0952ce69177d6f36649cd67e752acfc904f6be5ca2a73b87d97af61bd6c78a164d2c4231c3c59a26c2113b64b1d043969b3c555e786a2714f360fa163176261c34c8c05c2678f936565124ca583568913c8c3a1b37a4b6825be468b2e5798fe63ac337d20933140254d6002c2811e9123803c348e41607189bdf4758b0b2e22bc2a4f5fd09bcb34a502e3fcf193ac123f1b957badbfe88c171294037beeec91ecf489cc96a7d7a1c412e95adcd965a05da47d37ecb2bed66dffac99ede7b0a5a07003fb002e021c3804aeedc4b00e441098459336401606060606060aa13fcf61bc32e134f553e603b56d6d66a95964baade4caee7b7418cf42ca274ae15805307399dc5cd93b4b315d089e3df47241a7ffff77d512680d270d3ff28498ac1f79425b48d248e64ad287a62a9a5bb0aca66119d1c870967349c2d962878c9352efbf96f2ea156bc799e5a50a1648c312c4080bcb0b11160ec8e31d1acdb00f4db5b924e17c40878826c4e1446003ccd9800620908108b0408181085c80021680c0e6890ae8a0c005262081279e90804c0498f3a1cb094da3f9040438cff57b4223281ed01870007332d08096992ae7431c0e2b79ae67ea8b9c01359cc8ceb57d0e67d4e431b0006e6f02399a4c4c46145879ae370a68e6f42b991fca8901793aa2d9d924e0020880774433810330e73b1c4e1579649e304093295381023067fa7062409e9898e90301026c06700101b0e584680301e8d13401c0ce0fe9c0c14365f4311b4861111428cd5927cc014d9c5193dfa134a0050c40a112272f33412a188882139422804a031bd2e89182026506550645063506250615060506f505e505d505c505b505a5059505850575056505e586aa82a2829a8292828a826a4339413541b1a198a096a094a092a0d6504850475046506a106108692480454225a0d94f3983003932d00395030381c891812732b08049e46049420e1618e460194329a550625aa0943a192307060aa0948a018da608c4118524518a176d2895d2821c17f06208392cc0881416a4ac80a382142855d344bd6ca860c9510127e47802e6784225870484fcb04342528e08e488400fa544764837f8b8199d3e3ed89296961491ea99197ac9e1000554114ae96226470380a81c0ce081a806d4c3becc7469998a848b1f79409e0bfa78b6f87c7ad8f7a1548a8c7d2c3e9f1e4aa57c444aa52801d15ca5524e603d4946b58752291e90e7be15a2944af9d13a0ac63c562aa5c755aa59cd0c857a88944aa9541d79110dc9910045b41632b5891dc2b4f80f9d7534b980863c280b50169f0f4d900f9da3c9c57e26218f02cf11a552e62834cd4c215016329ef9995e44ff412617394d2f4aa5503133432f2ffacc201ffba199b668223ff3814c2e4aea446404ea3f748e682617224aea1c1d1149a55292f48001c251293e4ca0944a2981522aa54747343b292468f2a32c944a19410a1529493e1f1a17a552b810a520014d1ea552b6d022258b7f99ee4b8a8f2361b840054a2902a81c04e822070164945238540e0274548a1111289552040ba5525cae50475eea2f69668778da4b0941ca90d5356f45552a45c81c4dab544a15356f85fd783a3f52828c427346a914693d3523cf6786683e45944a01f2d5336921ba221464249a4f64d4a30868765e3e345d61877840592895f223f44a3ca0e90aa5527cc8cfac1e50a7fecb47e48979229fe9a91e4b1382075213a211cd0fbdd4f8502aa58752129a82d4f8b0a01f897e0b23539deab485522954804029191af9a1a9239a3f409ea98e3c3e441f7a218ca49130240d8943f290381288a4a5a5978844dd6c494b4bb3252d2dbd842d9962baa021a2e982864c017a229026344d017a222f335d9acc2be668da293c1c9067869e0e696999406f6526904824b21fcf165348515f0b10cdb525780c4bd046a159a4c64e3e46a1396387d4d8c9c714533493a25916231168fab1e10169a480cd3490a6e695b447633dcd68a6c8818302d633696124241a5d17998ae4c7a8899c219a1e3344d3a30768f2882aa0540a0526a054ca134aa54820254704944a8180130f502ac5010d502a85014a299594052825aa01f5c8a1dac0e28652ea24872203f57c60720420707008a5d4c9247ca0c4471a6edc600f4aa9142e668c2c410e0bd8042494522a6b102109457260a10cae943a29f208186ca10019e8810c94522720b8c108b028430640c88252ea640c445460470887144b504aa110912010c4181b1883124aa9140690d18645ec008d2cbc412905801ee4e08d9338b468820aa5144a202461860a30b10340b8524ac5073001e041070d27e08352ea240eb4c540821a74a10725aa792b54cc904359a552722865a70236a163850f532965084d2829a441a93ef86125133468d18452eaa4109810c4ccc4b4682d38a2903402fd91e9e3e39941a4d104bae2071249643793e8d36323896060ecd7cc4cd1775e364da64cf5c84c508dad53241a792693d13451298ae9f1c0e64a8e39f7984ca1988d4886668b8d0423a281014dd1d789c9683359d09d3a1db043423f43474342313173b339225391d8e9631298500aa5092a654a9129402671c50f245114b9e2079222285ca020c07e17a2c9c403298090841e941403f2cc7c3ca28d8482491492945253ad48e2899548bc11892e2834945291184542044aa99548b0ac4402012b90f8c113a8a0d45879441e4820b5984cf0c0c649017a00b12b8dc0b1c2083056180185525267feadd848452697d1df2ec800420d6a10274a0d4840610d4aa912a8153ea880a6fb403aa22984106f10a2116f509c1542c480109e4d0b21344008012835a2999c502b831083524a14925a5a04a18815410042106968598152aae6ad8809f5b04229b5a294a81541304014926a5e49072cf0a0491492020195529bd1dcd4bc9240a828a5763c4dcd2bd1a1529aa0d40a20c400c4095600b1031017500a3479381d58f1031b56fc50861f9aa89a57a292e20714c5647646d7250b511640642a9219998ae4c8911f1b1e47a4084d413e9ff9e3337fb4b40cc07a262d5a5a5a5a38a0b7d6d3e17866266746246a1e32c5147566a6c7f4d2d2c2b211cdcd0cd1f490628668e60834997ca64c4562a71fb4665e315dd090e9823c1c1ecda610fddfd0e411bd92ea19813c56fc124f8c4c6d62259029d812998aa4a5a59758191a10a70b19eb8634dcd044f5700394dca0949236239a3674a10d6228a55468a2338a9736e8982b6c68031bd2504aadb0c1056c5812882624f2d0e943376b68845292677259431a4aa209ad810a251aadc10925d957c3153e9e0e934d0f35443518600a6930c14a1a90a441493c8e782c688a36303f776e6060ec7b602634e8000d2e6888c019de38c31a671043492f33d5191adbe30c1b38c300cc100533d498610365788392be33ead49f01d13c0fd00e1d9b8f99329e8e6876ca8003253599321634d195321060850c4c20434729a96632097d8cfdcd68f69036224f8ca7230a8946a309a463f3d633863e8c01086310a2241890e70a989929049a36bf64fee7818881116258430c4894924430309b69049a7cfc80f9d01d54c72f999d1d53872486b5094324940a4d3a36d6c3b1a08986a10c150618a5800cd984810aa5442f617880da80c0f08652223080c10d30a00186264a94051838ea993ce80b8d50138f2fb080046ae50b17f0421d9492607e636ba6cc2481bc0086179e179c50eac78866ea00a84a458c74418d2e7456baa002e3919199a6cc688eb8a0038f5ae142145bc803f7d848a0c93312cd1b7a26d367d343ead43923da02932d944029f56a450b6f2805c3440b67ac6881052b5a78805230d368633fd3a8533d9d983993052c64210859200223cdd10402c3e441d36464661a89369e1016b680852a6081060b2c5750c315c670852faee072050a5821134a29098b4d0b8c24a281d944317d642a126966cac0c0b4c04c1fd29481811185a650e847738a21561043a21ed106d4c30a3392158a58c10a4e28a5944c6eb421b9b1e2869d3edc70a248151aa1a42ad461faa8c213ec8b68387688870a6f485470433499ac50a1a3946441930a0f5053e882524a7d56a640869a021647a4200929084149a01acae3ff6e60a6b043663ca39168aa9c8e680a91a9483aa2d991c292421476d80ffd12cfe7edcb44410c1505181b05a844d3f3991b26ef12051728f4c10714dc508ac9bbfc5fce14524c214593c907678a29ded27c3e34c399461cd11390b4d108a5148ca80d15c088be8dd746069c90871618980d1435af0466c786870e988d13de09185869421cfeef86039b4d88c6da1e2b6c4c820d44b02105a5a060c3b2c1030a181111db432925865a61c26785090258421b6076ec586bf10e1d3a1ac986477b6436a20d6c5aa050129a986c441ed1280a29667a96ccc02c218c25185902039840f14c5aa0906038164624648a9729b4d8f0b0420a2580a18417252c40d1242110d2153f908c402f335dd1d2f2c264022581034990c01a605883c91a3ed66824fc01096c200109127e4830a31771361b2573d4d2a26323e9d8d0c8f0268a1d303b9882b0d0b181818181a9a17f6d0cc81303f21ca1115d5047e8718422343147d82849b4e343938c8e4d0d681aa10c1b8fc86261843494149a36fd62842246d8484528c44a11d298403d8a00a3a4cf5750119ce8a1861b4ad1cd6803200f6cd468b2a206154a7d98fc5d51c30922486132e1b1995ca6480312692c218d1da401930601843005214c42d011044c7ca268922409169fcfdc7060c3c3a3040b2c36120cc36c6a3e211e59040106c10140a804108800842e80f08464375180a60d8f1890e707a21fc81f60c00780f0c1193ea04229f523c986039b243ab090fe33977868ba3032e311d1443137437ae0040fa6c08324f040090f1e0f02800615d08841a3a1c1811dec61074bd8c1921d5c400769d0c11374307550801c6c22076de4e073b08133dea014149b3bfdf57c36a291a7258a1f316f5ab0e8c0904d14d28e2331d3470716303a36a3d0df49039bcf9c666a689536473aa3896e78f00061b189428a02346571c4634173a363e31179866c463b746c44517842134c10110c0f984d8b04c381e14d14d763e79c420ad0779884a60d0fce193ad4194ee0c00d25ed004d1e100e6070f08452d28e69740338dcc08b999a91e86f204429c98c4658fa9687c78c36a41df6ea30a30c3360cc28831265aca1a6cf8866e2b129a389928a2855437b94a1431d21a30fd28e90c823b3094df7081965bc4787888c193284281f643cc0a3a40336a8832766c9a6870dd2504a82b1c18c82b101154a493030a21abc51833228c9d3a9818f1a8c60fe872e87c9836ae0040dda504a493c60447336912b34f828698c338c311ac3023340c30cd49881680675062b32c0820caa0c0c20461a144d68a463f34b3c9b69b4d9a163d60d0fd1ec6c3e1f0e68fe679a24a98e7ec383470d68d6d0990fcd78946c66886e78ec80d980fe958838ffa17323868b181788c11e6260460c48a0a4d0c443e4f90f058189014a185008a30461c4309e08438142349dfaf17c3cdf192d99bf858ecde2cf67fedfc9c48271061849c0f00183444818f8c88c3a92b469a1996a3210e3041acd128c60c2c00d18d040492f5860616453437f485f290cda178590449d116892d930194f93285e66f280f962f485902f0cf0023794827951161b0b00912f3d7e4823452410988dc833459e39442412c5cccffd0fdd17086132454bcb8b144d261f1cd1acaf850b4ae082014cc10b8f170b9060603677c8e679d887f9ce0834373c5e36924886a6863c75f3f94c2558c06c5ab0c54a0b10c082386c2c680ab160c4821d2b784329a9e63ba397563082156c54a00615b440053a528089149021056bd01f3b7eec8079d1f4d1d232676aa847e4e9bce8d8483534459e3b44daa1630303b3691981e668339a409f59e38111d1144c2998220505504adac13ab0d844217146d364e4f3594141cc0a0a84d444a2e6073556d43cd1840c4dca9060288d88637fc234435164fe4814e311c1ec108d26509d1e11cd34dad04c35348acf674e90f4a129d580264bf3a2998dfd234d609a40268550aa0c4cd060d2844991256fa8256948a2d99996ccac2c29a2d4121d4ad1bc41c306a594045aa171c0e810a3348c9a8c88280966878e9799441c91471a6dbe33a2d9c0c0ac88b620a2c18a480a991fbdac844c1082d7481771e8628d2e46d0058b9a20310d623a636253004065983262c8b47c06f1c9820e3ea08f90cf0494bc21f3a3d0743d4a9e3841203cb00323adc47821468c98224aa90a9af5c6e1fe60fa41fc10be7e0260ec083447a1694666c85318eb02db01c16ca26891d9706003aa57a8b762800682a6a19464ef047ac933dba0d448b432210c0b604c00c31e1071fe43975387c985b3860232c4852386c481a97925f63909504a12d5bc92cd3492f1bc15268082094860021d25f04309d650a2a7a317896a5ec94a092c40823790800d127041029411b0318215ac8cc00a25e910590fa7e69558d16323895e09c8b3d93112e9d8d47c36a169b3e333473a800cd93c1dd5bff7434c1e643fc411b991448c243049742895840bebe1c06cb848a3c70a1748242e7470a194928e20a98392824242032454483523d1648224015b9441a92dc4e8b105922d12a0b47063870e2dcad0a288524a6551892c8e908588c9df074d51dccf8ba2004d20d1069445162f6d7859432969e5c5e58868762a8d6823fa2c36927d110d68ae1c7143d54ca1d112cfe7081a4a3ad239b2568e38a18cd4611a414329c90812a5404696521268c588134a29490475504aa98d08bc58e219899684685a5a5eec90969617eb298286222a90608a14008b252b5840e102079734e6157990ae98572c267385c80e88fc20b2002bb460c55ba180101c220467483c42208010a80c69c2102e86e8504a59d0ec6c6024212f42282084031b9909d2b181a20a19841241d45809c24510968f58915b905dc88d92a2780f9599926c46a26966a381cd878a6c3c00337598f8b8626e3a95466623ba93911520330022c50f41acfc08c3e7c70224514bcb4f29c1c0d44ca30d10092606e411493b5a5a5e746c9a4c4f6fed7a7010d23fb64bbd4e8e70dee68e7367e461d29ce3670a391184a4cc369cf3ffb1cb56e48e73206464b690bd565eeecdf92020e433eaa637a597724378ff20a3b3d8a275eb5606dde32b4efc2067fce8d6b783975ae62f9ef4413ebb6b1f27337f773e88c9c4b173fa48d2e8632489e5a50a0ecb0fd642922489dae73e38e183e4e891d2e9ba31ee76eb876844d3a772607f26283443c351324d1f6f96972a5844c0f2838508cb0b1196b807d9bf784e46ab7d74dea807e938be775b2f771bd7a21d6716eb342c329e8e485ac2429d8665b524277990eeb9fb966d7cb3365090e6133cc8bafad1d775316bf5417607f9e885d39bf575cdffdd0e927a5c3c5fbbb5e875e69c9f787152075999417aa18dd33d76b3759c41ffe1d45f12d9417649c20972428718db6206bdb5061bbff6e7e082d4215b9741b7d6aadfa9f3c77b422f9f59272292c4f2a3e6ade888a6901f2ccff5c4e4e595cc8f0f49927c00b9c239962284933948d6757a9d3d217bdbe06c434ee420dd7a476ff3f3dfa6efc641ceea9effec576bf5081f1c24a396f9d64aeb648e55fa0649e98b7445eb1ebfe7d65a619cb8415e5ff4b5c7d031576b7b21d2832e49384b4eda20db8cad7f35dafed5cb5cc7f951e0610ebd9ddf617636c8c5d6edbbdcd042d7e87b4fe8a5e93859838c94def80ffadb4be3ad1f510cf568aea01a516b3951037cf0bd86afb1836fb116b9c576b77e65cc4c297bafa541b6bef3a98d6f5d7ff3d5b72e7131b5ee928423c5091a6475ededb69f718d6df90c72326c2dc2efbf7da3d38e33eb2e4ecc20a77363d4dfb3d6bac52e5bc14919e4b767f0bd377aaf6590c9201f745e77b6e7eeeaa5750c92bd729dd1bddf37793531489f4c6d74acd67ff6e3890b4ec22021e51a9d9b0fadd75b1b052760908f6f73bcb6459eb13a157d412e1a9d8b96455bd98bd17a41b67b08d9639d8f638b5d90cd5dd9b2ed192de39f1d67ae39e18274eb51cadc73d31febfa3ace2c05275b906bf2a3d3fa727addaab4e3bc5893132d48389bb79d956765f35edb71e69e4eb220fd369c73c1e56dc59eb6e38c0539e7db58df7bde1a593fd7a2932b48c79a75cb6b356abb8ea6c90ab29d2ba3931f5bb3d9ee8e33643724b38f6fb5e9f8759ca3cc4915a4af3819bfe59c6b73ef182ac876ddb6daac45b60e998ade094ea620af6d6a5f37e630dee8ea874453c759c98914a4a3cebedd5dd0b9fbb63bceddb8313397249cd04914e48cb132cf7e6f59f7ce4241d248fddd6823d34b6bab4f90ec6074fce2738d52365b1baf99f1748ef3ce3a9b3b01fabdadae05bd1775f19b209b9b73ef459eed7975c706730d3a6bab3973fc60847ddbf9b28cb649ddad183bfe5c0b912411cb0b114962ffe28409b25f5b48d76de8f0d7ea8eb33bb6fab14b12cee7640972bae8cf0bb2cb1d6778851325c80ae15a9351cbff1d7b769c471fc384f800d2114d2192442da7239a9d1f2c3f5884b0fc6039c2f2428445fa784fe84592a4299c244136ed5fede67c2c42381f4b11232c3e587eb0f460294284c52d7649c2a1395943c6e5b545e6d6b18670d28e73939119d2100e274890df569c31b66eb7f6f4d971fe0f8d38cc9f090a61f133352f8ddf891c4e8e20d97291b56b67cd55d7abe31c03f2884234f5431cfe182be713d182e5078b152c3f584634574852e89584a6ccdbc94592de4e204992a4c5fd760235c78cf2831323480aebacb76bbd6f75645d0469e7b3d6b1ca3fdbb17d6ac8c5ead79fab17ac11c64604692f9bd6396763ed8e9487201f8c94f2b3f3d26e783d0de97dbf4e67fb6e841cbbe30cf2707e3459088bc5d66d3d6ecdb5860d1b5b147665bff47ea416da20c8d9dc2e3f6bb13afbd6aec8831320c8d837de4b2bf438f936ef384f17244990c3ad5d92704e7470f203195b746fb953389d7d1a7122cb4b152c9254dfde372249a32a49955333f284e64c5b3438f1817cbb28b3df9a37ae6c6b0fe4e4d51ae4db68fcdbcff1a0f93d66e7d690db7bed71639f91f9f3cbd869eb38c30e8d66b6384143425e1d2db4b5c2fe8ed30834e407cb8bcc140a527fe68824bd307649c23961e3640712de4523b765ebb566d64f74205df57eeea6dd1a437b7310996baf2de7625c8b316cccd95b5cf36f37f6fb904fce90d6c5382347d8166def210ee4dfc8dcf87d7cbedeb5edb924e194e0e40694beebda9aff9cd5fb9a19b09b3ec65e5b6e5ba57c528684ec9b56e8fcadc6c8d60919b231767e7135c8bed17fed9dd840b2dfc8d8b5cb96b3ff8ee7490df841f606239b2ec27a3bceedf98c8c879dd04046c86cbd062d74beee84d0d3e485849ec944a44d703286a4cdfd0dfb19beb7d8cb42024d204992a1013d97241c7a3203c91ceb75b94156dd5b2eba76220339176dedce77d39fadc5f1440ce960abf5b50a9da73bbb1d67260f62a13f121a1d61f6d048c4e2490c64b75b477bd5c69a3665190ee83fce353353756c1d0dcb0b0b75342cb1ff240cc9aeb2f9bd96bb46bd46f7e3040c69bd2933fd660dd97f6c2a4e6020dfbebb9a32d8d3c5585d9e7c21295b18e973ff4d19a3cdc7c90ba453d66c17ff826e31d73acea310279eb8403a5fafffc1c6a87db71ff41f4e3b20275ec8b7a6fbaab3c54bef9d8d096981a4ced275ad7b3fdfabce678705921f7decae8deddacde81de7c7a1916805d2d98e8cd278bbc5c77c3bce9ee6a802d9cdba75f773b91a6fd38ef3e759ce0cc5386ef610a4404eea204fcbdef573573934122d8902f9e2ac5caf63ec5ffcca9f69a2426ae45d0d67e34927adeffa5d13e9ad46e72e6bfbb6d9638d89fccbeaf3e81efeb3ffcb9c5c221b6d6f3d686d6b0a63affecf0ccdc8491ac9fa41eef6cb99abddae6824ed2f372fe5065f8cf363c799c338af45b2d57eb131d7feba6baf779ced9c615248526eadbd5b1fd9ac5ebde3dc41ba901032ca9cc58ffc9e3fef388b26dbd88546a23723afebe64c9fc6b938b6cd629210c60a699df71fbc2fdeb50548366bef72ad42b6d4deb664e4fa66e357fee6fb66cf8f643fe37b71cd587db979b75022a38dd731daedf55cfc99a1183b679a433f8a2399cbe00922bdabbec3c758a3ff3aceb099863392b9b00a4fcf67b72a74ff33d6be1de7f668382f0acd99c6323420e6d259eef9653f5d7bd63eff8eb30b3d0834248b18e920730e3a4767bb33ebfe3343f3b2ae6cdcdaabebdba22e32b51d67fb79cf754798e148e632295eb23b1bd2b72ae5f6fc388d439d2af332cf452be9b4cd8db57eb16b5dac879d452449147af85c927062aaa4ebdfeb59db82fc7d1de3f7a1a93695b523d74ad9a5eb5e38a1283a97241c2493c5be4de1a3fdec7b65dd5c9270268c9cb77eadb42d7b5f8c8e46f600c75dfe3ae76cabddc1898509a48d7e6ff5b6566bd659d871e688209740da79ddb27cdd574be75b1de79af961ec4820ff466b7fc55bbdc2756fc7d9f11881ac8bbeb3b13eaff16d27610bfbce089bb79fae2b17b2df5666fa7f6d7bacf68c8c875124b2a363962e6eeb6cdbb52d646476e3f4fb60d7766f1ba2857cb33aa7d6b676273b5b35f33323f24c1734a47a40b38824b94b12cecd42365cebb6162d3357abd7189047495c2f92af7dfab13dbbff9429e3d74724f3f456abf31bfb1b6bf68c4806977bcedfdb366dff94a4cfe7eb7a2e49385c8840be7eefda6cf628658c7dc7d97a383f53337296972a586a403d24a906345f1e7349c2914564b345bd61ed76f860b38eb31bc95c0d60211f5ccf9c3dfa8c3e6a5fb3450217d97841c668bbcb556e7c9bd8211f039a4624e96340332449a29989c3ee928463af80bebac1f53a3a8e0d1946c6bca66bec4606d9ed6763c739f4a3213f24c9be6766aa922492a4d08f3c418cb0f4e2d068e6440d22b2596e949bfbbdddffdd71fe0a9ae2571016a0200f8a66a6162b64a4155ae6ec8b6bad05ffbe117f3134f4ad1bc95c361302f9ddde735b2f630b5ad61d670c0ca9bcda5db5adeb58b594798890b8fad9bbe6f77fe3ea38332baa80cc9f9bed0e79b5f5607308824c6fabb2fdd78dbef8338a40b6eb145ebf96d99bb3b4e36ca72340287b6cb2db9a36c3ca6ded64e4c7f5e7f9ea6d7f962b5b467cc8eaccfe2c8cb5196cdc1de7cf8c8c87633f343b22e83d7cbdca7d9fe37a6fa4b0fb850a19a77b4effba635fefad8ec8f98120b2d9627f84b5fd649527992934c37d640a79993bf7f8587b36461a3bce3353c8f2fa80a4ab32ebbd5a377bd9731d9f468ca590ecd8cdb8d8bd78a37bef383f68fa544ef58ce648d4cc1e9093dd55d85e37da3f9d3bce3253e83649aabf84e585088bcc140a22493e2429ba24e1a0a045c6c92e6d6ef3398cacb58e33f411bf3e66eb9ab1ade6fdace3922449cc85463333d02d77ab2d171b328efd166392cc92cf4792d65e87b6576c5adb6486028f882634dd181afa403e33329e763234203692b94c5c4ea7b1b647465bec4ad7bd5324f224f5efd8cf9d9bdf3be6a46333425f8daee8d68bb7e3cc61fc1ed75f32a449ea5eb3efea2f1b29dfd871e634bf91cc658fc94c132726c4e138d1d221d369e9a3ee689d2f769c47214805cbeb5673bbbec6bf6cd1da71e6385cf2418ecf9ba30c5e68d7ed3843f60ec858afe367bfcc3d99c78e33932914c32d496eb71c5ed7dc5757d9eb388746a22864bf7596c24899b3af671de791cc8d59f090adb2a3add616a3f3e9b7e36c419333596ee78203f23547d7d7eb342edacd759c3b229a194e68ba9cd783e507cb0bcb0b1196fa5a30994622bb7349c22102857cae32bbfd5a635ea3731de7d0ec544e6990143a766dbdfbd1c15ad120ab6dee083fb6e55d193c836cecedf4b6e5de3308971964c7c5b1ad48275d91322f03e48ecc9e2f66d65a7408dd7a8cdd466f7eedda8264906f5e5ae92f6ae3f36a2f49afd14819835c8dab5be8a8f5e7d8a1182474ec3e3a6153e668bc2c0ccddc7b31f81adec7d6e1a5bd268460906e2dd8fc1a64dfef5ef805799deb79edfcb726e3d8ab7941c60539dee791cdf76acf2ec8fbec9a8db35ae69ad5c605e9dd6afb823d79f9b4bf05f9288cdf17b2f876797b2d48db266c0efedf7ecc3666c17db16befc1d66df9e3d6b83e6799ed6bdf2fdbbc6241dad766bd6c6737ea7feb152473d3ed4ff7ef45e81ab382ac96d67a23b3492785cd53dc900fd2a71f3fbabb4b12ce94520549ffd9becadf11527ef63319a1825ccc4d6eb063ab70b275a09429c8cafac6ebaefbd9db738d06295290d4d1c9dc5c1552bed72394414a14a475facfb66b9bb7e9b13f2950900ec6c8209d0e4ec7263b7c829ccebc9832a53c579d5408296d48a7ec5ab66eb208dd8b35ba13645d6f2db4f55d9b347a8465a43441b286f4b1667e775eea8e0de98c1da3ee2e64963aa717e4b806294c90b636a45eaf6d06dbedb8041959bbd59dd95fb42b3f25c869d77b3db93bb68fd425417e6b375256a98df52dca6bf8f3d5cc3d326c112e87ab17acacf1746ccd67612341be777f1965c8f3d93193a4ee33528e205b8dcf9141dbb5e75c168c1423485ae372dc984f665f3f9b520449ef7f3bb3ba6e6d6d5e35a4c3ba68fd56db9aec412782e4ca1f5f9bf3f99df3dd21c8c7de471af93aa4f799a621dfbb3be163ce416e3fa710a4753aefbbf53acbe26b2f08ecde8bd11d5c8ff9b53bd89c7ea55e6f7ba79602417263a6d62163b4b2d9d69991f203f962cfe7265cedabbb6fb5f3817c343a56ed7a577b20e785de1ef36f0b36ebcd03e9627318ebf772ec359d68c809ed3bf64fbd3d3fb603e9a675d775de161d63d77420ef6c75c2e5f86ffd383907f12de871bdd70e32a47ddf7d91becb1e73ef0c495b64ed1abbe6a8abcc1c4bc1816c7432f6ac83b031fb6e536e20dd3fdb9eaf5f17df55e622c50cd9f8565edee037f8cbb19432628b8d9bafe69c63cc1a3a072b8dd751366bbb2dc6994286bcb59b63fe8bb9e618753fc3e1b824e1482936904ea7ffa2d4c5dbb3727fae0b414a0d64b48b36af2fc2d6718e993e2c850692d90779dad98badc7ed3acef66326224452c660ee0fb26b2b7ae4e80b3ee6ac27b3a3969f36ff989889c8cce4224931d3c7b1941948a6efb5763c5dab919d9d418a0ce4ba15ded6e8ff7a77f5730ed7a48821a18bb3d6e8f056daaadb8ef3874686bd1061f9d05c21499d9127342549f4f978a88813fa19497249c209a5c440def9a0b7db77adfad559c7f9b1bc5481c4a584213b3684175276dcbebe088674d15eebd4b9fffafe5fe8d54881819ccfddc8965dd39d6bb45f48caf04ebeefae1a27ff4b79819c8b2e6691f5830e69ab4d468a0be47bb0d6bf0c699cd636853748f14232c820b37b1fa32e49381d944dc836bbfdbdb33c9ff76590736203144d48ae8f1b755fed3bd866776226a437d8b3ade8e2fabf5f3bce50d4fc1a132c325f1e975bacc5151bb3c60fb2cbec8a773657173bce924892502e21a9bb914e5ad964d339fa5eb484e477df8dcfab2164f85f09c91ab43cddbaff766b636fc8c87d17f36759338dcf9490dfecaee6eaff24a47b963ecad67cdfaed1270969db9dfd73bfd970dd180979ed37d7d442c614d2cb90f0b63ac6e6ed203fefe61ab4d6467ffb1de36aeb1192c2d9de74d54268e3726ed02509a70b1447c8b8983f7bdad8ed499d3f9e02a51192ba9dd5e3e3172f6b334a92c4e12af2c8b4f718219d3268e35f7b2f6c8dd545487eac753b6f9659369d2b42bef6d5f6645ed5369e2f1192cd661f63fcf4bdb5672f445824e921425676567bb9edf5dc3a070f21dbbf7fc6188c17c26b6bd4018a21e4b313c6e7f15ed7b81d8a6a66114992240e4356039442c8e57045e7f65b5d94b6450809dddd4f9eb4d2679dd58ef3c803f270388f480f1f5810a9e2715e109441c878fb715dfdda51b7d1fe4ccd0b84038a2064b476bde6607c8eff286b6c4009849caee9ed6b9bfd45a77b2343244992a416f270984fd480028815ddf3e58cb9c8fdda7aebdd4869bb4febbbcbea6247f983f4fb7e56775e5dbcf1d98e7368e28c3ea631647e902f5eafff6abc91aee608631f64a51ca7b38ebea6fd6ef241d2d978ce7ef83e42eeba07d9d6537ef66aa43d29ed217a90b0be8bd559fbef8f17e6413e785fa4ee7d32b76e737890ddcdd8d1f6188dd6fabd83ec7facb1e69ab2c5b59b1de46aaef321eceb2a9cb4eb202d5c5cd9cf4be9c358e920b9d5f6ae515b1d4ec77c0e30b26bbcb841d7a28baedd6cf7fdd9e91e738f9543802207d9e873b5996bcfe7a4ef0c250ef24df8ea3f8e76d93bfbe1205bc78f71cdd56cd1bbea1be48bcecdd87cf78d5138dd20db7b4dab4feafcd6dab18181d20659997b0c6bdb3ba97b2f4e23d010fb45c84061838cf5e16a6adfe3d5ef9a19286b90b7c1e8f5758cd471acee676a5ed4202ba42cbae80d5e6a1b52c76990d6d2b7ea5b6f46fae0ba6c08286890f5fdbdd86cbee55ccfb0d0544492ce20db74cc39f7b6afbd8cc1c65afc403183acb49f2d8cefd9be313a5c6590cff1b3b7bd8f8ca1c3d123835cebc2b678f1635a595dee8d41726bb73cdf3bc6af7de32592d478096b4c401183e405ddcec573ad67f3ce114a1824f3b70d238ceb417b2d0383747e6f9bad78bb755cbcdc2509c7a27c41c2db5cece675416bdfaaf6f39e2092c461d1ab19351150bc20d94e58eb64fbcc51e898244952cd6831a766d44740e98274b6d9693fcf069d73970bf2fd75f63bce151f9b75a26c41b2d5cfb6f7e8f7f367a70509b927756f2e4ecaef19250b727dcf9fb77dc6c5902ba4020a1624fbfb9a747deb37fd5a9617222ccc1c6e0d05942b485ebe9e8bff957953487db9816205d960642cb2391f63ebaa243173d8b380e286a46d19c3da93cd65aded2a48763821658bb5adece3eb38337e549096c6bae8fdd7b12ff33905396b5b7eb99f4e9fedbd45285290ccaeeadeb4cbadbbae7317961f92d49648123387db928edc049428c8faf3bec38e6e41a72b42415a57ef7ab052b766bbf8160701e509b2d9652183b659182ff5da865cd42d06996bb6383a779d20dfad86df8eb57de7da04b9da4d08fb6d331b324e385d77b3491ba3fc4c90ac4e1b5d2fb816f49ebf84179bdba13be7cc357ecdb15d70317d97b266eb3e455182a4d6b29b1edf5f9eb739020125097255473d4ea7cdee5b3817af215f5cdeae75d02ba4f0b2a8060a12e45cacb265ff2dfafcabb30f508e205d63ee5217e7827cfdb124b91ea018415e372dadcdfadef5586345908bb15b71397d57392e5743badbc9ae279bd01b32874204b9acf5a7ccbe6a9d6dacbd4019826c0e276d35faaa73b28d6948ae0bfeaaf5cef6d5b185201b3fcbf5eb73f0d2493f08b2ddf53a7a741c19af07027cd01b8b8cc5d5cb19b218e98d35aee82cbdcc8bc21f48d87345487bbad616b32849920439fc646a139803141fc866ae2bdbfb5c7597b9ee382ff117222c222d9c81a0f440429f153ec7bb2863b641141e486f9eef5af4d5de51eab84041433238b9f99c0f5ac66ea53b90f1798df0e3ff85ddd65174d0627774081bbbe55c6cf6d8b37c6fb7f5b531e88d59819203193bbe561fb2e83f21db09c4f8ede4d220ca19d23dfb90b5d73e5fa36d4e23b7b8070a0e24e59fcc3a8fd331b618eb784d68049223d0ac530bd833538671506ec09219f22d8feb9b5a77ede9e58e3fa68c864592409367441dcd626c049432a4a3d3729b37ba8636b22a449220b39343a09021292fda4ed9bdc78cb226e448102b587eb008691c05141b48eab336effe7ead2d8f3590fccf3ec28ecc9adfe51d6796972a5866a61010499a9942b6c70b1196185d804203e98f7da40dc6e7fce9729431e4fcfaefbd9e5f5f6437a2cc40c6d93e3e56e18d96315765c01e14316475d59dff57cb7cf1accd3f283190b09d7bfb189bfefd36a7cba9098d20cb4b152c5210961f2c43e615923433657ac900250cb97e42a673bee89284c3021430e45d8eba8594199ded623b224992049b39ac071418c8fbcd9fb7d80ce1b7b3ccc040f942bee7d7d3b947eb3ff86ac799c3996238b05f80f202d96baeea1c5d76faeaca1f1108c505f2d9e4e52c7dd710c2b71de7d92c0c142f64336f93616df3b5a597759c39b1bf38d9845c76fe5dd6d108d937dbd59d134dc84979b1bbd66d65313a9809f9b5bd2edad7fddbd7dae2d598903ddd8470dab75e6dfd65874892489258821861892e349ab98464d7deda5a5badced91e5a42d65eb645ead739fbee7f6ba1d1cc4925642fb6e66568dbaab0fa7d435ef8dc3fb42f3eb86a5b4ac84a9bb6770abf4d7e5f27216be4b6d8e30899abcfab2464bf355d7c0b19840c5e1a0919995b0a693ba7966fa390908d7eb7ebb771a417fe23e4c7866e3517595dd07d3a42b2caf5525b619b8d90d74ef7d5baf8ea72ae272364b5d551eada83edb3ab2f423a363d3a3a2955848c94bd5dbb48dda18b9e08e9f0fb19af66eff5ca0e11925d0b2b65f79da5d5c64348c6cc62ec1badc7399b1942ba59ffd26adb7bcc9b7721e4e535d7857de7bccb5542487fcd3d2e3703765ddbd352cfbd9c214182485273d60e7054815e57a9d7d8ae2f2ac8fbecc7161bd2762fbf29480b637396f9d1eb9c3566714841d2e52d76a3f3fa9b90b28e732b82230a92f2bdf6f18c96cedbbafa101c50909752e8d69a73b9e63cc227c80997aff6eaa48edf7baedb90d6f9d177ebd72f6f8bdd091236377b35cbacb5cc51d77176dc04e9d5bd77abff75cfefb0e31c021c6cc8e6d5bdff4166fbf6d18e731482830932d3e4048e2558908709888603f2c43c55000e25f08bfefea6fdc6d45fc7f9459fe78426cecc94617623c09104872fb6c51ebddbe38e91df75bb1c3a86fe8b5f345f5c43c6d67adae9eabc6bb1879f8fc70a49428264b05147ab7f6bce2873c7395a81e308d27fd9461d8b6cb169efebb8c46104d9af3ee7cdbdae57e02882bc975db76ab4d6d235bf311638d490ce965977e3bc31be9bed514204f9987b554a19afea6cbda221486ff7ed75bb1f2773d869c849699cb5ce6717218b2d04b9a25bef5d27ffb76527e402471064ed6ebf8f1985acb5fb0b0e20c8d86c73afc966afc870be1fc8b61e6b9fd4353b1fb3db6c021c3e90b5d278a3bdf636e735f7e4e1cc885c0fe4b5d0c277ad6d961d6b93079223b4ecbd7b6edb37753464bdf4c2689ba3b55cab15e1d8816cd3cef8eeaf182fbba5387420e9f55a7fd55a6b6cd0298e1cc8badc6b73510a6f53eb0c04c719b25e4ae37a16dae51cf38b66440f070ea4abed39aeb0eb8a165207210d6c4670dc405e77b0216b103a7baf5333646dfc7ed5d6fa351abbcb90fc60a55f7b5efff9621538c870cd7964ddd8b5f3b81833efdb97bdc5efed391c3690f7b5a5af19cef64bf9fdcc54458c3f088e1a4857233ffae6659ef641e7b06b010e1a48c8ab72fb638f3aaccdc790d7defaea7293b266d9332f70cc40fa65dbd65f66d9bef9fe882449d2879cc3fe050e1948efe5a085af32f8b5290e3164656c5156e16cd04d475d0ca463eddcdf7dee2a6bb7b181230c399f8decbfb5e5fc5e6b38c090cb9d597b909f2d43f83090977e64d89752c89637fd42b6b3bfec9b5dcb3dd7e60be46d3ba17fd3e538b2d570b840d29ef5c21b9fb7afb361cd5be12ec0e185bcf632b7cb5d073f320825691acdd484eafbd0172a9b90f332640bb6fbd662ffea99b490a40f71f843539551d184b4eea29d93e15ab0276d9990efe05cedd9399973cc8d09795bf35db12973d61d7d97889063e3c6bc9fc3069dad35d76c66d53e66efb3e696908db54bfbd5ebe66ad3d6cf1149e2b068347d4410062a9590f6da181df515d9ff6cf10d597fb6f6ef356c46d96294906ec217dba4f0abad96b9492ce898c1e81e37477676feeed87de6c9ac4b42d2079d1f84b6cdc5ecb5922449921439ccecee04954848e86f457e0ed256e1b70609f96e84cbcd66b71d738e3e423a5c8fdd666dd7ff4659c511d05d6cdd1a3be366d618b76fd4d56861658c5f3742ce3563b3f1e983eeff5d4648b6feae6336ba46b9b65f84ecfeba228cffee397d56921421bda757d7f8b2d56ef9cd429224492511727265faeba16dd5bd7e1de7251ecb8044d81c1b22e4b3ee55babeeffcd5361e423a56ff31a6eedd3dd36808b9e2570a1d743b0b21fd1db7fa6e3f5bcd3512c21debc762c3c62ef6acb435e73cdb46689d0721db45e894bd8b6e19ed2908e97abdd8ce608b0cd97d13a80442be76d6badb5176f656db69a10208f91ca4d5ceb83a2edaac87260e28f4cea1872e2a7fe8181dfb8bcd6b797b1c5b83b3d91aab7d66af4c680a224932a1e97a68343303153f48c7edadba7bd19fc2d754fa20a38babc2051bdf45df6d153e48f7cbc1b6dcba70b26fbf07f9dc837d237cbf4e299c7a90ce3147da9cf955c983bcd1b2b62033b34f8faf8207c9f755b7f65dab0cdbaa77900ceb65cbadbbf781fc4ccd0b743bc8f7d83baf0bf2656c7e1d24bb565785f7eb65eefaa383ec053bbad66c74f445dae6e020b3b6627c0c32079d2d176d5dd475a3973264f6769c8b783e3347422391192a7290b59bae7effd132f4fb71906df5b7ca989baeab850e07e9b05258fbabbb6b395e8ee5a50a167f836ceb7a646eaecb9be9abb841bac9dead153a5c96b5d86d90adb9daa8ebe7d6779bb1331a4d1536785cabc6ea3c52da7176a8b206e9abda17db85b746b6d03ba359a4754673a6d58cbaa9a841fa64f555d766a5feb3276766cab8109534487bdd6cac7e7476ad42d871665c1382100d92edba0ee9a28d32b8e8ec3807314285ca19243b3b199caccd5727fb93a43154cc2023ebbf0edab97c39ff2803953248f7cb2cbed8da63f6602583ec8feebefd5e7736dbe018245bfcb4b9c9dddc7ccbb11421c2126754c4201942760e9da3f15506390cb2d6eb166b8bc66e90c50606c9d74ee69abec617b649bf205b333a5764daf4ad376e4a54bc20afbffb4be1bc31c2afec042a5d90d6d2091d5f3b79d558e1679aa8137d66643c1f152ec8c86a9d6eb139f9d2afb0e32cf240167d3c239a0fa86c41425b5b75ce4256d969752dc81b615b5c59f3d5d3dbcb82848e1b43a7ce5f73db1a0b9259871e978514ce55d715e49ad5b9bff5d1b9b5efad20bd79bbe86bcda6ef3dbb21df2f7e2ed2c9f3b18d4ba50a10727baebbe36bdc96af6e786f9bb4c1becbdba3825bcf9b5bfff8e2146443cafdec5ed8be61431529c8c790e35bcc5e7595f98540250a72367daf354ef8a0b5ed1c0af2173fdb1ccff62efceb4f90ae7283bd1a9dacef7aab0d692dafbeab5f575bbfa313daeae8966bf1b963ae35c8cebae5627dcb586bf6b99b2021f5d7de62dcdaa2743ae7300b54d8f0e51dbdc57e6b41b6185bed167dd63ab665ce3dea5498202fe4e78eb3dbed4f6b9720db62b77d3b757fe76d3d0279a6228f9919474509d25e766db5b7f576b95a559220295337eb5badaed8b6790d59ede3e9dcd7e2a6cf409e0ee751e01145d0f45141829cde2e842e3a4fd6f7baca11a4730c5f6b2c366861abd108b2b13fcbf8ffadd80e571164edff17d963e57be7e3e6031535e4edc78f3d84af5badbfe20f548820db62f3b145e9bbda189c2a4390304e7b69acaf3a3b19d63464a48b39f3db5a431b5dab0841ae8fb39d7bd54527ac3708f25e66e75f56a12fb6fe802027bb17464b1f7dfa685c3f90afa375cacd97bd6bc5f781bced8f218bef71b4f05b0f585baff97a8db93973915e6b9fc17bfda1733c90ef5ca34eed7b7c56bbd190eeb275cddfa5af59575782ca0e64ecc7ee5346efd7677f3d50d181bced23f4cb9c756eda6e8b611a2a3990efde9acccb327bd88e55ce90eed59e2c5e165f8becaf8203f9d8bf638e3ddaf13eb32a3790bfd8b3edcde8f64e7aa78a1992556723f3cab875b7c52a65c8e516b635a357e69eb577027154c8906ddd6c303a8eb4aee94ec50692aee5cfddfd9acebeca2a35903c273be78e369c767d54a181bcb7316fb6cbdad7ae479531a25bdc16739199c7c8e06ac88c36b75eacbc7e4216556620a79d0c6fe3052f636d592e5464b020838f35e8e28b11b2e8ab39beceae8373b2c65c6b2a6248eba89bd03a6b17bdfd680ca45776ae36e7eb9885d5a98421ebc7c85ae566b1fd7d300c153024848ebd185d5c96be4aada40203b95af407e1f2f74a995da399010d1185687eb824e1d4a87cd11ee4f55883d15773872c2e6ea2f20249ebff3bead6ddd81e8d2a2e9070dafad6e3691bfc67a98a17f22ee8ff58a3d03a086f370b523621a9bf055d77cff5968d6c149a455690a209592d63ebd86cb5d7a32e3a270529999097c1d9adae1999bd90be48248a9890b0de066f9df19f9db132ee21a45c42dad6fa5efeb76cbbbe299690b7d9756743c613365742daff65278cfc8db668f90d0969bf5b2774763573b7a55042d6dbdebd0f3e7abb2ecbacd92464752e7af509ef6cd4e9654c12d23d748b5f5dee0c1b3a9748c8b59ce13b37ad3376db8584f4ca9a7d961da4cdced1474876695ccdb5ca95c6f6a223645bef9eed5ef4f8bc7923e4adf0c57add7165e69332424eeace1a4e7a2fc3f8386511d2ddf75a758fae5ffb154190a208b9666535fefd189b459b9208b9a86358eb6daedb617b44c8e7677a2985af56f69a4d3944eb08d98bce7d3107df63d8f445bbb8d2ebb1b6eb264c3184e4d7ba3a56178bd43ef742c8c69aab7d1de3eadede284992d44b602f6999da841142d6e7d65babb2dbdcd2f6ee83904cd75d74adb598eda637668e8e80264f0b42c2c87639a78ef9e5e62cf2202510b269d3192d57f88fefb3e84793480c520021a7b50c59f5c56f9fceef38ffa886723a229a9938a29919d160e123e50fd21ba5f735f728eb7e97fa41ce38a1a5f521bf685ff33e48379fbbf5d6f5d12e1a5f0a1fe48495fff25b7f99ba56f7207bc2ca6ed3e59ec256a11ea46d3ae1b2fde88d1dafe741b6b390ab8d5e9b3ed68d07b9f851d6bc36abdcd5ae3bb4f8b8ade55a63d89c2fd6e8f3f61e7c94e1e26aed2027acddb6dfdedaff3da5d4ea2027b3ef6ef7620cd2c5970eb2ced833d2861ead75bbe6202fad8ffd753bab8bcf5a39c8ca62846fe17cbb3cc21907f9e87cee4dea6dbaeaa083838c0d328eeefaac8ed6d7de20b9d785cf6f9d0ebefa981b24d3e77eede0f50a2fd33648af5f178db6c1ca06d9f1c59e71fdfbeacd710d10aee7fe60e4e7eedaa3cbded5eebb5ce7a46a90f3f5cf48e784f4d9f51cc4414a9c28a8647206777910a32006010000c3648c08148311002028201c8d862322b178346cf300140003528c428c503c160924b2589283388a822806420c20c018638c21464106c70a02440da2a4f70b9e19cef7efb60051e3f3f1d3662c78862bb247f1cdf2e14d18f7dab2172ec655d1e0546dc723454d0d56b1014950080ff3fc6fe122ed4db9000adc2d28e33c176df1f800b3a663d4623dde81271dc1cf73986bd7dd0a5c88b507008728168277e40a49a8caba000b083a310e11f66faf48ebe15576ad18e68adcdf6fc521bd59eb16391659714c0b1e8ce20ec86315af63392550619f817636be187a460c22ac8898a8164f0b8693a65b7f083472d15137197239e7f91780ff8f6d1448d9aa45aa55605a4d0c238848a75321bac4ae2d8a70dc66c14d7a7ecc930af8b1d4d4f309a2caba4849f629dea1aae7c4722ffdc1ec2bba014855b50b2de54a44d45e4596c8cbb69cc2805c0db3e2b9ee974233789a557ca6dbb99dd08842948c15d09f53fd9f88d68af596d347ba730facead91e94304e0b51aac95309179c4fc470a963ed41798b677b0f217e3800959d8e830df4eb02b82ce4889ef4d4b4d856ee86056ea1ae9de56864d7b8678cfef6ff33038a8ad12be36ae0eef4cf0b717c68017aa0574e7a86b3d6db0f5b48f14fc177018684bdb501a6027b6c83727c9885fb51ec26972ea9d5070a6bc6d91a4770c78bbad0615fc6f20670dbbb857902296c4b54dcfb16cb04a3aa8615a7b76b53004e309cc641da72e9274d5737e428553005282e07c8b242bdfd32bc306a25b1871e3408965985f59eb1154e43e6e2d07a538117434d482d55cc8c698c5787059d3182dc70110b817ef06e1ed247cf576076fd2a0d41d9087ae36db1bb9ca81dc2141f4efab115285507ac492e84da308e0e56b044814a50739f5ada4df24693fb0b5c0e0967c142ce97f49c7bea535b004855a57ca86a0cf28ee230f3c299db1d582c816198a61a04b56b66c04ca1b4a40d54e13664bb54dc333282e84289758c06ef2730b1ae25713abd0d0dea044eabf5eeaa973b0eff034e289e38809c00e1d0e2967056fafd2e64df599fcc7efcb8411fa25b681951878c0f8244153dc0ab3b4c3ad24c3f06f728468c319c8dca50bbc5c1dbb792eb1362e73d9d92ef15167e9baf03145cccfc547d4e529b24fa8b7ce604f4119b6b75e70712a76835d7ce9b292b6a87ab2242955efda7e97b37fbef8c0a9e3e2d6865bfe3d4a7fbc18f5a18e24bfe85bb7fbfe23efd08981f58f85ce06985c90d2768cceb1c3b17a6ca583f9017322d3f47b06701d913d074a61b2273f7f49d9e8e2e037d986640212fa368b452e869a2226491f97c76ed03e934f47f19ec3a2c12b493bab067b95d530469e52eda4dfb47640dbbbe735349d5344da30e684e52008f9708ce852ae98732e632f4a71a88ce6ff5120d9b04ad59a286c05166277f7f651f5456a847c839d40c94980668e9b821e33f980d61104fee8e7a747b30b441fb543891f36a90dce8318e62572f7ad0e0e235e584f5142db2655e16871fd0fb4d9991a398be36cab405db739439ee7aa9812a7db9ddfae7a8651552ea346b89b0ca14216099f7fc92ff9252a16f9a0153183c3570b4eadcde1a1c63dbf85e34be3bffadf6be1d5249280be54d3213e972e04745cc5086ac348a52d08f558f50c3e3c16a71c4e04ca914cc6133833c62cce318b879cf248f67a69d799cea7e58283f205c492e05317172d2904273c4155d348f071409d1bf87155922156e47decd440e33a0f7e99761556739490838f971ead6c4c18ce5564f899eca59801a0b5b35f7fce0384bd55b18b9fe544163f67eca15594b5ea5f66e0da2fcacd3c0c5c8b2b9d87de31065bd760701f790dc0440086483455d40563046157bb339950a43acb842a080a556e526082f48d293219039be76af9f5eb9e62dcde8d2bafc08b64d7d60a724e9d4d4523e210e3d98aeaa56a6c5ede45b91bce1d180165965f02e4a7584442412f6edbc7c4a22c71a444427a5ca306e3566f6887b4bda23aefec0474f8fb727ecfa74bed2676e162b8743cb1be3f8e0fa42f73d8fb430fce5e84a669efba3cff2f5764b8125be20340f617a5435517c11daca4080a5ec79c6b5e65db0c53263e3ae9d1d8b88141f988b924ae121b4772069b872ff29f79d65eed8d55892e800895a28a1b06177129d999151b82a8e898a8dba7df3d107b835d27b554737c23e3e147ca1649de9da16a89be1bad97dfca96178e2a336d0367b49140a80d2d1eb6187e3881120f70926e0c84485d759fe1a070d22717ab1fa4d7a7363fadb9998e8753c50e46ada312394bdd61b28dc6947e66acb6dcbe48df974a0503e9503d77e0d6c7f23255c7c2b0a5ac99cdf63a24d571b5b4ddae718fa4733dea5902483e94b0ba0c6d9bc434c76dd5720858686a7ada734cd7e6c1ba1b0424c3c922af253784e6c5f447156f0c71d867aa83f6e21d27e016b365c9552955d814daa90949f0d797aa5a4f655b94d27e361e87cbc4998ebd2b58304034e8a4007306aa08fa086195d3eb558f0e7f2915ff4c27814bce4f510259f163be87eee91261416069c4954f82c2bd30e06508d112abac51ce20fe299a835a119fb99165d79c6b23ada15e9d3523dc50326143f82a3d753c659ca947cd939010ac28cf1c9d75e1b53876ae9ec5eeb30aaf33e19b1b46f0bc754ea08f1fe8709a7eb8db860215f5962d138c58ed30ab3133993cb0447ca32590eb4b822345abddaf4fed20b3b81b402c31476e32b1df7b9874833428d5ee220dcd1b060ef4d74ca3abf2c4440a82720ab41fce6121522bef622914245fc48721881eab526179a673d210d87e4da64955d6b0032a6b2c9a42e04d80d204aa8e267ac5a4187c977344357c6ac375b8d43dad2891a316a8e58f38e04d2a19ff12b3e03cf5ab0209dd331a4e9af084a512713618b7168d0c3385590bebb4f0a68ed3e9dce61f30cbbf902f1b1ca2278cf3c19105c8dd76e54b58a1e1ee2df7f659bc2fadb5fd4069b4260a19ced4bbcf55e4496f4e2403f77d159452d1b056e49c91a10d1b777b996dc9f565581a40ecc1c9a9225ed0cf2380e0acda1f345c24ca9fbf6442e547ad2d9bb7d603fcbdb91947ff674bac7080e703d38cc871632896db340235628395141e84e41539cd42e05dfe2615505fbde921873447199790fa524e13dae15b221e5d2e37b8cf74545993ace22f5844410547f5f226817850fdb99e87584202c608090308bc03af70a87bfbab9812e8108925d0920984db94585436bcf706c257acb8129e30dbd93ed37a841a161c1734608e77cb246d8e6ad6ff4325072a47653458197423f76f164de23cdc219103ad638dbbfc549807bb2675ee4149343563e4a26c2a5b9334678c966e4cbaa161e5594e0d754715471dd1b968a63e66ecd7d4934806b6adddb48e642b320a8c3ef74b39597cb67af5dde7cbdd7c2a1257afd32efb64208d5570984fe3c09fb0bc442f788a6b028070dbdf0a39196559f489d31caa33422a9d93983dd7c4d506a34b682e7600239fcb2ac71eb19ea314591e98f2f5492dcbf0e801b884a84039165fbd11e8c4538b4af077000dfddee4b4bbbead33892fa00c3d8294aca8b1d440c2ae8febf9acc16c66b16233e4ecdca51a9f6e68ebd2285cfd9cb7c99a1ce181622ae66356d361510e6e7c1a145cf70d4417df93e813274c49f22a92a4de887cb60be898ee033fc684111f772bb12535f5b2418833ac7712d6e13fe2c9b3adeb837e84dc1c947e4e53a398b21ec311e2850a1ad062e239ca8a3ae48c127963e5287aab37eecb504541764c75a87ad6fad3cb5f13a2798434b47ebcec7bd833acb0a39d0f420f1115ce15597ea7cbd033e41ff7b7b246fc54efe5108dfd51af856361119dc4d016a4fb2ea410d6bf9997895129ca081a958db024c87abf78d37ac69e1014c82aa2fd37e7ca7eff82251b5738bec2dcf9dc56ff01f19ce4521d2be3e9e522808ab7168eac51b8d2af44b33dc3433ca08ac274e1e27b8072c3cde2b4fb711676fc9c6388089c0be2f51407d4fa31bdf0a5604f0404eb5e911e41eaf0fa40f6a2f34f350d47993d6d2e8797ca3326556fb7937a7f042765940dc915997acb011268ef0ae2c111d28c17d31dadf4d9b46f9f0645d3365875e625b344930a64a03e16dd8e4b30094c6e4c3066a98c677a0f0838bc4832ac64c1c97221463fd4821825a0a1b5208a1423b1e6ea20220056c57894f15726d081616570e9325fbaf469438180b1a05d2777255872c2a10dd4f6a4dc7db2738d922174e89c342090e32f5e5f6e01371d5a5d401b5d2c2f7ea9cce8c305fc95acf1db2a715071822c1127ee1590d116a0b12fa9834d5401bedad8e9202bf37a91c5dea915576df161066d7da54352a9c98c15c5e4f582831006b4fdc1e1a053a9d4dbd4cb98a29e3aafd289cfaa3ad9246c937336cfc65eb874b9a990fdf368d5b6768573c0462b402f26182511ad5998178ba565803d955b5e86edaa9c52eda435359c8f40ec4ca89ac556b33e2f61557824e5d252f5501465b311822720b42103c8a06c2e20eb9887a13c610c954242777532577dffc023f10c5aaae0f5deb7c1388dae8661a53a4eb6562f684b1d6852c87feead9508fce5ebe158a5372542e18d92843be413bdea91de77e86b64b361da5b2998126ce81ddeced2d52ff23e834b09ac9392a6f3658531f096899b50759ea8da00c847551e7854313bdc54939decfbd45eebb26fdfa6fb9c2243cae5c4fcac2a9dc10b934d4e6f2895a57f83164a83f860268f09331c8ef89d1d3033add4219c3e519c623aaa27079924173217f7c143e82a030c496184fd416f6ef50738abf01b82b610a2f4748c67346c3fbe945fe07a48e0c4443314bfea4a8eb7ca63528f8b80bf33e8306e684aca529e6c1c12ae0895052bc908ca827ab2caf4a456f0ff3fb37250dd5576e761b13452da459dba44d00e3c9be4ae16eee81f45c4b45d8cde64fb66e6792d0501d2cad35bed328a9b1268791b5c2c1103586144821639fd03568b6b5a1559017a69c717362782b06e776a7805fd6b73ce8a91e78d76bd63e9f7d348ca795779ec32137a10070694f38def13ec0a55e8acdac266a8b3de044c1647d9869bc17746f12408a8441e096aa68993616be7591a793fbd9c62ce0b9289ed26120f3c112984192d7ad2364d8afef08ee9b415321b99ba0cf683bc873d7c00325bcfabbaec060f72435eda68570a4212b1f4005ec82f6df63511b7920d763dfc42d87bea017dcfa66e903a7c97fcb30d0833f9f98a5d3e38cb72899d685620c7e6c02784b661c10830fe5ba48ec9f49253d8cf694db93f14db4ea69ea92d66b1b7de714ef1959b6cd6d6246b73ab16202e386dfa94103e5f8356842b426b2dccd4062ada9f085d173a3f16b007211ba266d6eb0d1e605dd5e4a7e3256363abcb25967027083f0ea1cb4a3e20b59db4108ee760f44d8b84f16a74537f555453628ef977cf9f0362538421338e0d29eff1dcafd850d0364bb748b7a422397e826eddd386e45499f868072f305fad8969ac9cd2a446ab3e6312467c93b8c363625266485290cca1de49bb9c5ae61e19f5e295c253518b451a6c347337822624915f77fc21d08097f949281391bd1b3fef19b7119c15fdd0efc1ec132a4f76ec7a1618f6fa0f70efc62bd99261e450ee772e43049977dc1462d3065e1c6025ffd48a1d70eede0913faade1a253b789921026979f0340aa19c92517979c28a48d248705d683714396f7862e093f61291c4079da26985fab4310ed9c91d4c41d21410e976b86c5cafa74e050a1750ab76ef46d3098d810853d994c4dcef638acdd5e275cab405a30b73a1b80c841e9806f0aaa3cd9d0ee813e548ede3c946659f9f089c104dbde37a788d2a750622fcd4d0409d5e564494bc23f626e17699729d7c89180a22a0195d79253411b4b250241d2f60c36cee476f169acdae264a3d257ef2bb6d59557ec5ada9d62e4993aad6e0b9a6745838dc5e81640b1131e889603909ecb1f9908c42bcc44ae356325ffe6d34b2864ca60165565646e3bc804cb5225e139d718cbe08e819310b99ab331752bdd801ab1534e2565b060741d468efd75fb1d7422bbe3eb27192a6506090a54a20f3f72cd93024dbd29851e4168a5c5e1e0dff01086c634ed494159c5aa852cb74340723d43f206614a44108430aee7bbd0e6deb09ac913d2e8328c74a69c123f7fbbad8291297606cc6dac980dd7aa5f42349607ea62bd0d0d9eaa96d148cfb7ee6f2f5ba29d52d28d9d7674c7d4056f578a8962aff774330cfcd52269a6ce17756e610bc8b17b099666bb6e1affd0f2a1b5b2c15069e6d26b20c2dac326bec8b65e51d073090e0008cb991304b67dd879d5ede77716ac5b69b8df4bbbf3b939f51214218b36be3bd2ff396a706805f7091230367f6d4dfb1375baa69bc05efb580776e4aab243a4d8729ed3d688f96df24fb1e09329c6e42ed4811d8f9e474f1088833172e758627cb9990c05498e86a20067f389795d04c963a14badb5b55c3ff86c6b1536c18c79e6591c20ab37dd30454b9c6a4b94f79ec6343f4aa6a9dcb79aef52cece01e2155d1a1a34ff55e45f6c50c634c8d12d193c4301d1be4510d0268561edbb549712da9645d12600a620d32a392590602d8c9fec027a864665475d5c867d080d5e2afda3bbd33d542777c808390d476672545f2cd18005e75338b70925d9a0497f55ddae19471f97ce4bb68b0ab7dbfd66806150eb70d47e1f46453cf0024d9a6d54e74105fad01e1b45f0a02b441e3b9b10cfcad4b05178bd5ece31e5ee566211c17f287d9d072211e3699696dc36b3cd8655730f1cb43461c9ee7c9d7e66a52e2d71ea8e26e285bea9a19aa116f6c94a1267a8a7164d04123fb9344590e5b421746341eba46d142060f37d7a2b0d97f5ad3f202c866ee0813bf5dbc1e87c68ed88265f2f380f267ee5211b55309c6c8329279a6d9e6d8ceaba21bb4a7997c3151bad855bfafc0b0814f65b98569628acecde3c2c72d2aadb7c31acc8b51e6096ceb05fbebc446400c0b5710fe2d3d3acb916070f8e9c07eeca57177eea0a2d4cdc59f78c2ffb7c4781ba4275681c101bd44f72ad8f77757f50c31097880ae3baa78827654dd2d90b704d400a0f21593a5e2e7233237aca0f7a8554f935812ad8f791d3a998b1263a90bd605c783a05ac46925871d708e8a348cb438bb15330cd6acb17f62ba1aaa8a2dead927e3822a1218c55b8c34598ec4da94b27555f39aa60d3ca4c9a6016731a94304d078ad5ce2f3a5779f7d119eb64284b06eb3fe05de564244ee2176ed24e24e9af8a54d99d665fe56edfe3c86ddc281d012abbeadd32c1b233320c314aa418684b41e8a0f4d58dd4be22e8d40a9c6a2860a4158e30f0982fc981b43bee101973262c837bcf66e0a7913bcd6971eefaceb07f7ad39fe66310182a1a9d46639c801642b9c2e85ef7694baafc0572de63086d49754ba939aea5fc7a2be5368e59152505fe25ff262b41b7e314b7b9b618493e35b9b73da3b22e1af2f2700c8927d25c837ed818a71454fd19f7dcfb5b00db3bd53850f34125507926cf70076413ad12cae56766b4bbb4f6502bd430dbaf74421b56ea631807fea2c0b2911f353b03de506d32f10288b24f8dd888a0a2a9885dbdfc6533d7fa3471aa3e2424614792e01d01224153551c035c87cdb09ac3a8d4e14eaa83c264712f8cadb022d0770c62df7300a24957ffef3c044ba735d72e114f4d2bead037fa33d168371ebc38204c97c341334413244ab3f0616ec8a20eb74677c5d1ccac6ea9a2fe2bb6b1c504451a4ba127429b74eea73528708420bc9866f72aa8c816d83cc482694def5cfcdfc301659f4735a3197907e5bf554e3e470adf1f721c6474a94fad09b9915f326cc6bac5cc2ea9b9621d7250757c6cf6c738050c9e4c2bd26993db7ef61e4cfe9defec0826ae444a600f051b67eaeae0cdd399a9b6ad36f67ecb1a0c6f3475324b9ac4d3419ffa9a94d8b01db0ec465f06b23f64ea4fe90ed80025067767394690a61d39b9f6d7f21d15dec51f6af3fb7af387a0b0dbf7be627c89238a0dc6d626c918bcf544e4a8e3b473be16d99ed2cdb03688c141ba5c3831adb5164f21b83695b7cf01049417c0865da4ae2c6ada4dc2290525a7edf957b8ae74a7139b5986cf6b574c5bf6e707c1451d15e53d8b7b45d35064fc8f0dcd2680bc2ec407b411f66bbe2e798be7786dae3e42ad13bb71eaebb933aaf76aebabe8adce8f314ff14aeb9ecb935cffa36afd3368f8c3f39ae0614e782ba7e651701f00b1b52c9a1c3b4d648612ad9b9be67668c4a5c747b2e6042962c7ae64ff571df58af5bcbf067b7f60ad4b32c70dbd3b1a960c48d6e6fcd975e65083f236bb8797488220a0d05a73dce40612898f42b69312c3128a08874a1469ab29d86c3c7fc2e2440e30cd8df7293fc457e14a05a8065325a3d08b9531285e8270ba8f2de788060f9720c610eb70318801c643c9f30be040a5fe29cf85871fc8e6a799cafe47aeb6a9a79015cef1692d2014c3ca12938b1e68299aafba391fd35c0b9eb0454373c577fef572aded0077804dda415bf9d5e0c0b3c27c0ba790fc3f879f562292f6d1227a3bcf70b1a2150fbec3ba090cc721984869d14cb4c65e2438d5573313f9cd866e6870bbaeb8229f2c39603791a8ba29afe707930f35e584972d16a312ae56dc771f2bf6302451bcb798f6708aa599fd7302b19667e3b1e3fa35d5753a99c53d6791c1e361cd2968c61a2d22fc8dca0b72aa88b9dc43b1375bfb006bd663998f70315e00abb9db9c076549835d913de06b0b9abd8f17baaad842c4931b56acdde6c6bd3e1c2e0118c25805a1d08db4a4b4cbf31d5f6097351a86894ad2401faa0f67cafd4ff2a4c9fce4b741f0ff73890dffa5a9440c16ad8a505976f31c14413c39a970167c16be2fc7171c0997eebeecd7c3c51208af77f052620c6e6c1b1301071202434e3b1467e6f843fd3c6048b2ff69c4ed9891c7f03c3bab6b35ca79738f3738c10265a334d54f41f753708c782161591f0b7d2215bf368421e2499c584f9b4f957685a25d6a77fd4e796e06dacad9ae857b523eac25a6840cc6c85feb319fe05550c6a88ad6b62acb13577cf3951766395e9d41a2ba8b582298060644a4e7ff06ebf05bbbae67fc73e7aa47c501c72567637c034ebf1a96bbc52261b5c74f8e69558d0f7fa9d1dfcdc4f0790dd8d3238799993e0f83db8bca0403b4a99ee4d2c4b19170c236d3f7e75b61a15b2e8d4157c9a3b241e1e9d7e365c761b017d0a57a781e938eeeec2559e42a60032eaca1a815627316f50ab7a7a83d1831a590998fa6141621a539fa9905f43cdcfce23ed8e6b5bc7a50de95bf2336b45510f2278543a6f4a4fabd133d7c2004b56ca802eb8664f431896f743596995c6679afe967b180162ee2c9f708c43d6e71412a36dcba3efb3230100cd10a5c6e88049472fcc72c0ba10dd0e54b00b4e2e975244571f71c5e9f3d3e8405a8024da8003f360a7440049e3f32046260fb654e26e5d6ca0d83674216bc970b9440033342588fa0216b5806e3f89001b165a29c3a4403be20c4f34c4afdda8a633b38ba9fff54dcbd6ec40a24e6ae73b695c8b8563c200727a6221c0b9c79c22153618937204c0c0df438f77e34bb2802a28dc58e13d0891f5475e32a102394ff3137db84b451daf0e6b197bf59da9e9c32244808b60b350b04f7b2aacbc86c7d7aeddb60baf17014c72404b930eec8e36614432abde32b3080e7f5297e6145eb5507d413911cd8b705f03998550c4a1c8b0882421839efb634e70f17ab2988fee722c4806466a645026c6471d302e13809b1c0a989951ad0ccbc1e048870cca08f88e03c13332b0c60f4c39b42347ac542ee73d48237938ce0538592ff3b55ce6c59dbb46738b0885a3b32422839b7fd0ebebc1ef60ead57e2f271d8a37d461fce56996743823fe8fe2e59db0ce0714e1bec3fa1daa79e54a70b4ae30452d76f171034e81c75030e0fb72a3393b24925c6008f0fb31cf2e083d3c20497e2a9aba88649d0f3e77b04cb347029d69a2ccc039358e9c0ca709826e7c8863a3d8cf6a681c18e4ee332c37d2c17751fe4616acab0177d3f9adaebb07fc00531f88763a869f75fdc141bf5ffde925c3491735a69878e9dfb6bef3d8a709ecc0efa4ab4c2e859f206d21eb14ae895f15422ae67f8aaff64d303bcae891dd8f0287fdd024f1697719bec613726b25d351fb0107315c5bb2998fda07684184e2eea9b36d7e5f4ca0af643219cb0b413bd5603cf9b1819a902b9fb14b4adfb02ebf4f08f04cb91864243ccc8be22953ab36b9f549fada160fa19b14f6887da4a63bf6478d323746216d07e800f920e0157af8aa82d432df0226eefd4757fbe962175b2cb9ba1124a2a96f8a5c0835da510993dd5e18c48e6972447328435ec1d9f538c765168ad0b1b802c53a43c62015d19b4917513297686e9840b6244d4e6adce9da3dce657843076d13749eb0ba95dcf741d73808b481bc59f8188693ac282f3f51e09c47d998ec5eb3dee2deabc3905583739afaf381f1d8b001da100e6e784a9ac343ad0e31b7875428813040d67e6aaad1f9e94979c2a1e265a0687f26f62766dbd8b1c77911ae3541fba8344b179e13d7fb9b04667c553fe261fc15281df8f7e2eb18cfd1f8113763e1e317713f50719a09b913d14a02fd9317dbe3b1827e0eb452b008899434f40f88b285ad670c8869b710a839650e3800bcb8319c63345fb0858a343883c01ffd7fd734927cf0c1bacda046c09d0130edf7f3ce3d96a3aa77b0b9b5e494e9b9710f1f6e21ce68a44b28f141ecfa41135c7e329e224032f2c6beb239552e430ee5c7a827ac33675504767466a3d9e76d5930f8244fa235c350bc358b975bad63a4ed0792f5a6f7d2e2db29bd2a4de31317c9d77e460c8e478cae9b9ab9350c0ef61f31e4066eac43a3c6866c004c3cf8d6caeea5696e265a63296d5a43007a46403b0d35f55643674de797f1cd799572796c4c786a136423d576d23b049495965abd92cf85c0b46fda352a08cd90a92f9b045add70cc530b8e487ab8abc21e12f3a9d05eb3b35491e57599f7564bc91035cc05b27a7eb4edf7e3186ef6539783d70ec862e267b1a919199e0beb3e993a0576f827af38498ea3aaebef7cbfce376253274d5082c60daffa0c9187df960598712271e14c6f8e2058b34e8e1646e4ad020b969beba0637d3bce46c6f616182ae6469acbe652eabdee536baadef846afe7b8ad1bb4b342ce9b8e1128772987ae31fefaca05e176921fd1f59dd25d6ba5ee3dbc42b484fe46c82600f7057ef79a0a5ac3b7818ca2f1cb2e6789211c3473bf37e0b9f2a3d73ffd306d05fd815a2f336b2d7d1f26ba7e12dab9e42f5804c35a9623ee5f34fcf7f02c9eba6fe7687f861b1ef667d6f279d15c20217c28048f623418ec671d31725019e423ded0c65f00a46190d46a78b854b2c7e643c279ba103340baa1fd9ea7144736981cca418b0ce75d18fb9fdb2ed673b47fdba5f3046425c72dcc9566f3e4c1ebbc2efa31a6d0c7a53ef8dae60122040e9788386d92af3fb45e13587edfb6420afe61d731dab902bcbeb94b7dedfd3c6e750d74afaf3c28a083338cc802de384ee69662756ec9d84b41a48c9643db85d39650793c5f3334297b6a99be301d30e1c5b40cad2d37fa50c396936e9ce0498b9018a8968d91ab02e82a27d51b3e793b6a7080fcc3e115e21423478e85d073ceac69d03e20086685820d664672b5604dc8bdb89750b42b34acc1a2228bccd66f0951963dc6608da410eeaebe4cd284fb8571b80220f4edea0d802dcd7311acfc4642a5301c58f499aa63a2452cbb473e7cc45ed925d8548c8b209ca0a5118d882d1d2ff869219c328010d6d411dd6c2ceaabc5c52d921a182e6d88133553505c7323430f9360e67a88a8e2234a9dfd1485dacc11c93b6842b009edc8ba1a2118e7ceb90d0a58b2b895b3bb05a25ff10d75ab6e5cac9bac2ec3c25108fc4736afbf691753d89a5f07148758b63e0893ee44cec42c31d5573e5b6c59337b7156c177e097d5cefcfa2cd2f020ce2942f6915df1ae7b76f283ebc22380dacaca756cf912045971f9a61d1385029c21a8254fb144fdfbb55d531f6d7cfcd18fda9de87fde75daf8a7a60d9b1c68418747e5159d65498175f700a82f90c3eb4d62fa98e48f55404d0b48c623c14abd2cff6ef83283b8ba1a144b1abc67282c9c01c5b90eaabad75604db4259ffc9b232c78416b6a0d8f040753a9156e394ef2783b0bb72c3f7d94189bbc80523075a9e0361d216937759d9c84225886a799c6bc3a729da1adfee9f0417eac0b2e5ef51584d0d2effd3fbb67e0723df4e9df4e01f8ab6827b14d74ce3a18df160c82279ec4d1cf9c7c545b9e8d5d41974002a6613874a993d7154ab4fa20ba5f7417ed07f893997de7d68a535f31aecd3827d38d696b396b522e549e5cce3ef21571e6a577806c7716891ae67587e503fbd208618b74ea9ad4c02fd576ade122c0c5f5179c36f551be0a69d92dd7b871b0cea6b5833063714f3980a6c03c3b58566fc2b2a7e2c3ea6339fbb7f0e3a70424065deb08fbab6fa112abcfcbaeca2758b60a000279f1d9a32faeaa78b2305d697d3472794e6b5471aed1f3f142c960c35b8dbf2c175470a98601d3aaa039a4576f7cc3e687c3f747f181747027b7c7daaecad8e92b8571a1348a482a9d134c5d99b93d3f27cfb0e71af5fba6dec46a40089a6e62cf9c62ca60bf971afc5dbe16b0c1c55b34c4ba80e891ae4ecabfeec0b8b2ec9806aff9367ef2d1230a802d932de1b226cbfc2d9b223410242b11ac21c98281a9bfa7c6de9affca34cba2ece2627f16c6a25e8b2669a43112b3f616e5ed808683dcdb127b2618f59fac7098399c922566fff2090759c661cc6d2821f893b19050bfbc6e1268c7606ecf27d26b6ac360993ffe201f889d5abb7f7ce0b0c8a2068a2c6b20022d9850f2afb5a085a58905f54ed56e8ef080160c213afc1f876eb3fe062e9ca8ee3f8dd77fe5fae55cf4b7c6533d43fea6ca65039fdfa37e06986f7eaed27750973e8c1771323dfa4d3228bb6ef3bf40a1d759e15d2d97b5095a91f59b8fa6525bed74d351614e7b1e99b91baf96bb4bcc4f70d7ea2804b47d6f46e64c0f771c56291a933be2f20c39a748b6cf25de9cc40bbcb5e4bd3f6636d2f54e851ea683e0c9b3806840fb75508c824eaaf84ab92fbbbf10beb380bbd3444f069e9477e5806ec8173a4e335d5f39c7a685f1374da4639df4b730f5960eb8b75ec672fc655f4a83df7cd73efeb31cfbe4e3d14f31a0d01f2a131760650ad4c377b3d3793d1c2e58700fd586f20cfbfa79c55daaf226a74814623cbb89616f016783922018b4c84d46a0383188beb93c54bd5248115e2e20f9d0f4b246280913cadccb1ef1b407e5578a62dea353a1bede2f0d9f8d019f73d78f502e3c9fe17d60271b355592c6184d0ca2bbb2d542e1de9f1caba01b540953a265b30f8d50e240a6ff45a5924bcd124e073b3f3b640f9923dc32c45f3df20c90307c3616e1bc51783d792528b10a60eb63f0f4251b41086866e339322d4ad4318692442664d4aa83a9340030125ec929950fd9319da3200184d36af9ec09831363198a8f8cadd7c92a0d8310a4dc19914f93a96f494819fa7c01a742166b2acc09b3906ba856356359f11c0da74b51cb91d59b7d80aa12dbc01fc127c667d8bff39d066f3195f99249bfbef58a7b6b1f13b1c5bdf51e535521966b0140e73a366a865a2595dc2a9b14fd26da7b4e69d2cae2485dc66d35929093d03bf4e1518f2608801a6cdc8df96a07b06d7b4c8b6e8e88a38116ffda8018f1683c5c70d4386532d1688fdbd4d9775cad4bf2ec1b88d5b4b5e4dd498b17bdf432c1711456b019ba299c5a70a8655f34771f18fa8e432b5a311bb4abaec1ac1e8e2b4130a9ac14a0a99b755fb9115156fa6ae89e018a5f6d73472c16b5b734a2763ae4be6b897b0a688e91db047bb7e65dbfb91cdecf9bb4b0becdc66d3da65eb7beb4f3ec4fc2083a78e724b3f9640c96daf0c4625c2ecec763f40e8e41965fd883184ace9de3b38aaf95e0bd51f22b8621562f17bfd6d3582e92cb1badde3e04a29fff5e0a7d9b95771360d32b7fe465d6dee34c7ba7b8eaaa51737e0044ca231d2ad9989ffbd60a1b7c2c6e11a07e9b44f4bbf9fd10ffe54f26b877ba76b6e522d355e89bf85b4a40a2e5c786abbd67ea98d7a17ad5b0751408ce2fe9ac0d33f35820ef15eff1284fd49b0fbe7e4e0e15408ca6e29d099087b1a5894697297a6d16ae813d182705c4e404a44faf52a0e82ad55f126f5f00d295cc8ffe894687bd47ef9c56001ee52c195c9f1439c63028feb624630dd958ad807b73bfee03d50fe0c2f1579ec85d42793305bc0f1f9d5008af230145c58743b95cbd8a2dbed372111cf21a353ef1e753806c6510f7968402a36122b41a81b70a6b56c44d85f2f27b30e8f09953ba66c045ca6da07ef71b9b5c7de3f3f9a6ee712d14809c690b8191e52801fc343c2e3576671c203a87e9f57e88f61721b7edba8fe5a3da8b054095d964954339da9a8bfa16c8d5ccb6865c0e6d22b66827f02a591030a8cddd164f4cd67fba8680cae2f4675613327378eff0a984d01373ef0d692866be074edafe48dfaa72ef23007fa0bdff04659cef1ed6f373c1129c527e528bd352e60cde7961b09b5471b2ef91c8c593e49e63b3ba13fd5839f814f655845fca7de51dd1b87db8a8cc5ac526cab904a4436ec35e4c815113a575b2f008092844cbc93dc1a6e4fbc3a6de47151cac5605567280e4f8aa5f317b639cd165abb1418e68cd1d9879d4aefce65a4523c963c5727795f3a9332fb658ba2c6a9f273ce2733b24eccfdf8267944a8cf5d7d21567ae1735a0a78733f0e5f85f24fcb71849ecd0186f9a71e006b5d6d256610bd0bd0fc1c1e4714130e29c7e6b3b49a328cf511773b224edd436a6106df077799acfa4eab59db91eee4020609726b33b2010932b199b9b6fa3d25fd84642eb28acf1714f1b9df633e6e5d8fafb2407421832453eba4f02420eb563e1eb0352164217c699b3281b807e2cc1f6426fb07a41c537a73693f5c6e1ff34122a8728c5f362a14ea3d907a431191d6b4617ecef46ef183e0e18d67b1fb466051bb3d77175e0842bf5f2f2693b0b58c0aaf62868246380d984a54f03176912aa8f2f80d2188d7d7fbba4c2f0f18a749661ee84ce12c980d21de88b5d02d27320d542d662875f3ac5e18c01e3a2884a9db8d77d1d33d1eb00117aa898b6fc2b5f3091e4dbde06160be29e9b8e0f5ad70f216b35163da855d23879d60ddd8e670b2eddcb736662e6bdf94e54b914c69ff4928536e5ecb037fd65a43eb28516dc43fa3b819d2dfce58f422b74c983ed1abe969465c8f88b2961b97dd4b0f3d2244c518ed961ad43c62c3c44496faa1151f35d9c80961165b10b8cbd57c32a0a75b4816a244cb416be126151a5fd363b900791612f18396f53b0acad1588a5fee610cd8f390fe190ecac63d2a1c28c03d5c443405c9bb6eb1c303e637054e819eb605a44737dccd8951265b3a6f731aba5d4c47286fb99104d8334823034684d66d5c826393391b53748e70f761c8093e7721d59d398bf454da97d616e7921b384dabec6189b95980fec7b62c7c0234c0cc6f70bec41c5da955f9415112751d10aee7584dcef68218070c2dc5ee191f986842679dcdfee9cb2ef9f0eccf1be3ef6008d4bc1bf72b670a1605a39bd7c6a0adc8e922707d2f9478c67fec03998e567ac6151a9a617e165adb74c8214876ffa47316e7ebd059280c80aecd07f2bf754a8db7e71844d319d6a263849923b788618f4e116b19e169c6fc221b34714d0c7aa27048a555242e9a8afbfb05e68ed0f9215495468645cad197497d1001c41502e3a5c28995f2a47d06d0e28b3b28460ef01382e13f3b8ce5aeba01ffe56264a7d4d5e8bce3f41bf2da1fa66c10b9469f98648248a0842cd5f91451a94873511479de6b82ea95502c20ced1362a2f49fc3cde8b66f832f87ea52e63b9a5cdeb028537a4feae13f0c2b6a357ecb098883fe574651f49cb3f1ffdfa1ba82a6cfc5eb57828f5440eb55677d5ea28ccec6522fcbb27b819604855860303cadfe41dcb59148e73120a539f7daad6a261cdb4ed5e3626f49bdde102824dabda94ba184b226260941ad45981cd5140ebdbe0116bf16b1e0052020a28938c7063d3626407164b6b960f6a350e70ebd6efa0451e76046732c2527a674e1ba1763f3de8bbc2e0d04dfbf1253cc79c8f5d6dff0c8c51a6bbfd5f53010e7ef8f09034206dcd94663ed9bbd8685c33c6438af44948a325076a97f347d1323da01cf6bafdb3a171107eddc370e31799daf3e45421d66d385fed31665db2a4f6b9f5be4f12e7bdb8d5ec04e2b546bef45d177bfcf069db01162a3487af95a45b395988b107289b65232cf3713d7139dc2117a2a76e09dfd87de394cb501d4926b349721ea771436fcf532df287b02cb9ad7b2f5cc4d4420ff89e67bbba692c9e14ecceabbd6e36ed6f7ee60133af7422ce5583c85f933b09a1cb0f35cdc70966c2f7ad6137c00c68fe207173bfdc896193ad4f410528ee004f630192d664a658febbf3beec57b6d21e9ab31ba9149b76395397b3801e9d02a58774acb6a7ede35873181a220f2f071d6374811de26b52b50d5a2225c6ddca70fc78a1d40f97131488746f99a00c6cc21f599f30c699b7c051ef3d38a6d833f38e3179ba6c1626b7802cb8111c3385c2574c20d9937c53d18fc1956d693ace7c0aff8e2230f166b97f353c0a18b1c721bc01927f2e44a69f532db3255c3189defc4f059232cdd4ef73daf1a13e164c98dbd9b4457cfea8d605e881ed2c9b5b2e7495e711c41109f5a039a9e813d72b4dc519a2830d31c8817c0a695cccd42d513b5e7b94118bfc6f28683ff0f362d77507800c3cf89bf9e0aaf33f48b923ecc2502c30ef4275c17de9b43bf23c6c627ceadcc95bdfa583f999641cdf3e2f22d4eca9d6e591ea89bc1091961d2215468513bd9ae35349c8a71f6a6a562e202e87f21846c92c6b159a1341e34e79d15f3004f0a13a1d5cd3d603146c561ef1862088912ced3513ff5abac5ac360ed2b18472ee48935f881c9b32a158545326138219bdac2cbe442ce775b82b61aa484a350c707dd81215c99ca6977dd8e0de7238eade6122b556ef22cb352c5dbb3f7761a12ba303b50958ee058dbe4662192f2a1e51d7e21e22fbd76462587d675ff62127b47102867b5f6527cd0e1b06e7ac9dc6320e3f4f51c591e2e7b08197450b97ca55f18b4552dcfa29bba9e37f9ef79a113370f02de97258e90aee61d0b8b283401e483aa69b4bca20f3a6ae3d6d881de3cb628cfdab8f56eedae474c73286860a6444307ec41d9c91395b552970048d1108eb8829f2136c044e44419c0811abafb6cfad431825294ed3dd3123b9189627e3f5dab29bd85e49dc7089d9f6b1d8c1750df71b9a717720c83acde99de25422cb76b2c518edc3ed15e5bec8b5c7cc8a8060cc32967330fcd08bd9a7e2e086ab49d1a15b64fa93647903910fc4bcdc2821c5b0be9de26baf4996aa377cbcea4b140275ea8d2d63093ec4f224e8c85982cd66dc3b2624b67df5366fb535b1886d15b7e9e01f7f15029d41e19c8dab409b4dcc1bb7961775c3a15d8ff93bbea34ae36c70161208c440ec98483729597a62cf7ed48eb439012d06e1323542c1db53c5c39873d9402624390dfd82063b158e7b070e09629b8bd028e5f2d2cfbdf906524e1b11b22af095596b355f5e748c00a30193bd94555b6cfcfe1bfc1fb358babbd3e31a548d728901169bad75720ad94960eb7b331c17c000979a5886f3ff010052c5ed8c97af1f525251280ddf640ea55228cb43f69cc6590a4539e17e28705e13cdb8ae3dbaaccb1e27ed09aecbe95078acc45d451fac87359241d10a0445179acb2c3410a659c39cfbd6f64ae40dde9f1759da7e1f853c57add373ea33199b75c2436aa18685bc0b4b7367367bc85ca684757d258440b6a9d65df143cf2dc988fa97bb83f550f459f1bf36c4f897ebe579f50d200c3284c30643cd8a92c2e89accbcaf662e70f92dbd275416ba556ed664887973cf3c7ca5fff184b88005941c92a4b6bc6904ea63020e9f2e695a369284e2d0389fc30d7017f8b2b5def77507948d14e99a352cc6ba24e811344cea3153643e8e088db3524eb9659897c02597f3c32e1da0c5b4ce3e05d051b73bdb1123284e2755fd35ddd3df46888bda925aa55a2a3c63beca96474b1ead63a24fcf2cd836817c0e2eb2ba86e51f8159698272cd19144171c41f5da0bb486f32fc26eb27e8dcdff6686427b4f9aca9a318387c05bca2d27ab113a2068a3ddb04c50e8989b9610a2b8878a5100cfe29014480e2cbe7cec1dd63029095f02fdb8efb0a7bad5caa328ab3256b436505d47bdfa6e0a478ce115c7b7d71eddb4dbf1e7361a29395c45e1db3123481cc3598ae45f9786aac7406b5876a904e5e0df244c5640cc684cd141f90f86b4b7fd88c844061c98e19c6e6208cd273f831fd2b6e56c3e36fe09c32f26355c202fb403d99f7ed04e7e425c1d28321b72a78f90e3fdafad4c1d670ca678f4ad2f402cc1ad8b73e60dd6f39078aae4eb5be1a32d4747306afee451b702b3b3c0a85b9b62c3b59aa712b2353710073f733f664cc043539aea642dff09311d420371ff7f21474e9c185ebd6890288586c9f8b6c4bbabeb9b2172f1eeaa1a08c376581e91e4ba5502fddb51463982f4299eba7c4d2c1aa27cc3cfd77b9046b248f2cf8c9c6a400c67cafac30bb33fd965dd0e7dafa084bc28b4827edca10e3aa93fb09bb434e4afaf7b4e088b62c7cf27ffe96eb54905eed6a6b6b82ce7008e486f67fb1afe96e2a93253ca966345b2aac7ec06225a58dd55b725a0f137b499aadddb1ed5d477817ace0339c5105c890e7090c13bde6c8736c763755ee37aa3fdcd3f8bd9f4a12c847cf3cf64f642c0b1579ce65f916a0f492aef7147178514b1f51252d7fbe6ed27bd1556ebb80a6aff955469f8ae3b4bfe3727e45ac9d7515e641a654c82c06d64ad134674ef5c8753c75b0f6540f7ec45e57dbba8b31b7e170dd5e4070979197418568bf7f2d739f6266df30932a52281646f2bef538c7c8e005cc5eb5d6ac7ab0d2be3224d8e98f99ea412e167050824e0e4dbed100bee8939e30ded7110e3d83b1f46a51847cddfa0b40345563d1c1f981d6b73828d7ea50e914277307f04ca3cab7573a8981c6247d093dade60c70b7be5f381ce13dfc2274e8669d1011f3cc904166ede3a07b81b3d11d3b84d4fe264e3998b69a33cf628c1b1aa6b63960c5cbe78f0b5bbf780919b8f8959098e43b93e8e5f25f8e824fa1337190fcece7ab9dfe9528d4d386d81d02dd61fba81acaefbee496992bf42d23449fac7f473b2089f24b007ad0231790370cbf4310bfbb497f3c78326cf9e7ace0676b20dbd6023d67039f87406859bbfac0ada3b71c4e0300e6ce6158769a5e29844feb6e1b7c964baee78e6a04ff741cde18e1b1333cbc2280883d0ce76cc4cbf6b7be6786584ea276fba65637dd87bdecf52b28c2cdd094c3967dd058e735d83ea9cf88f57748fbb96b7596eff1fb6b2f83b22be7593afa62bb9fb9dfd1deae3043c04af2e25ec92dd67141d7d0ddebe5fa1facd9653293cd9f769df986f4fe2af37442aa6f042ddc79875818db7d36263507a66670e6eaf8a0a53387723dd105ffbb7a82e90cb9185e64628ee9e9f75b1eb12effd7c58a4687ea44fb93b6a2a27a0ea6dd9f266975fc7d0b061cbd33e256029aec33b91cfdc1d5b8fc29c4b2db5a5c936c307004fa21a97243a694334184e941c800b6ea17d1a84d550d0067ed81880475c58e331ee0b721c1fb22f26aae9727e805799efe98e672829fdf1e522e93ddcab80275de3dd3c95ae7b9cb8d70a003731b0dc0c0c08b64fdfde45e79cc71c17f35032cf9f4c30fc4189b7b2b2eeeeab61754001e66ea8fcfc96ad713e87bd5395fc615d3a543801625d02ef015a5d491d9717e8774ba094a80713b30711ab99f2c274bd584dd5a1958cde853c12f482e9c9a358e5bded3824203d4ab3bfcbbd42d986da56593c79608c4381f32f747d06e50baf0d8a576b5b54cb62eb5da00cacfc901b43443616cb745b3124b31689393c2ca8ef112abe5f4efc2fec92cf9d558ed467747c06a999f104a3c75279048e5da1678849cdefa3f3e07a0b2bc38c5016da47c06f4965e376019425558196ef4f103a4522613ce787be1aaa589c6909e26ba4b73a3f2648c6a8fefbd237bfc488c3a979e830d41f4ba892230639a1f57dee183e5b22f93cd5b48d81ac8d7a702918020efca0fce7953367b291f25e6e2001965f4b067d30a6824907f71ba86f51d8aa19571155a0f32a79449db9fd63398a7ff39522555e52555e014a0088e3be54858b534127c6f6ac415bf16b0aba31ae0eb66756451e63173a70d49b31fc4a7158d92da82cd3e1dabd5720a852da492a500371d4885bae4c38d1ebd26baa843f17395584c837b4111035283ebbd668b3cbeaebe64abc96e68b901a0e15866728b9df4ea8b27bd58462faaaa9bc3f50448ddf7fc9e00a5b88087e40c03a1cbe6c256e4bfb2c5d58451f3fcc14f25955b4f43dbd6bdeefe24b333b4c13fd5b1baaef3b644d4fe879dcd817c736d44cd118c384e3a2719ce9f3cf32eceaebf47ed2bad8ebe17401bb85c2e602c6cba5ff40aab8e821b0078f35649cc413de28dd8f9452d26c7318f08ab61df171374d4a376e8d011d846259d7dcdf63d285a52704d90022574364b02a3d202ab81ea58652fed4a890f8f440810208451ab45c260667c8de6c47c222378a7bd71addeeb38e7d6c58e6a5f2b387089d25f2c96b6d9c9df34dc514be6e02c6e7be6d30a18d92f16f92e30047beae18f4e9a50e81de1c5b6def6927dcdec0e82463e348ee4bf43ef1adc8bcd3cbc60622eda6bdec5b72874b169c78678ce710388aa7209f8e740b4541c7c686ba36f6f9dfee90ee4ff655c172b7e41ae8161b0a6d82ddc3a315d2620782a8d7ee0869656c3b77dd20c5a0e1b24bd97d4e8d5f3d5fedab3e101f2e8aa0180af33262a611ae149de1f689f4705c696cf7cf9ba55c08e350ddd084daa3b17b0bfb1892eb72a930dd5a9e2edd12bd1d15a53cd2877aed93c8f95db55e7a326f8d421750cf50535a4ecc9bb178b2903a4f432df0645faf1eecd771b025f1e994ddae1ccf3ddc29211d64c44620c8fad9a0326fb7e0f2bdd5bee20bab8fb8688e1e9f03a22c884a1eab9c91ac1c1bd30ae1d1fcdc9a273945aff44b800a659b9a5e2fa260aa494d1943a95b79bd787de3b43d88e8be51664baaafc6ccb626d70d1c1d8c662ebac8568dfe6fe7bea79f63bd3562e43aefcd46b2dff4bfd0fc561a7c2cd5faeb4130adc88f3ae14e2c2d2b2db868d73e07c83c0e37c8e3c96c4df06cb783ea0279a8838addab349ea567463917fc42442718a5114016a0f20374c7073ea601185774b2b91518e980097df2c2505c0535314a44097ef8da693148ccf291c7934068b515019b7f0a532503703c49913eb18de65c03af04f6f4362429bae49c4405cc44474c8a3e80cf2d5feca40eff94998a7dbbc7a61c11f8c9c99ce35dfb9b061351e8c368ffdc429abdcf42c0d0ff2d17f89f00d89533a4a937768ff5cf0597d45340906f3d035006d285c1e310dcb6c1c913288a1c480defb0797aa9e556ceee40c2064cd830d64ad9d43744d5889f17efea0db53c35afd17a45c1e9c667ec9a53f51218b3f3ae3d29d8a2865d98aedc6c98826099acfd4866c256d7408d8ec013acbdf798c4d5ecc168ec61bde6349d00f54ca9c38ea00e20e1e7562becba1f31e5daac8748480c21448ef49fb05487a0c069b06e6da7e3de571d58d8cbacf5843cae7487c8b3545a44128b90e1c39f1db1b890ac87f68e9d8509d9418a96741071ce39a751f207d6ccd52fc47ba05936a6d99c2da1ffa952427cd769d48d370aca701717351bc43f2cd6335b7ea16351411c3c3067f2b2b5cf55397ea79c2408b58809e1cc35f882f7181030e3e91e26197aba87dc1b3b44e2f180a91c4448be1e672ffee2382974e870ef2149a144fa30fdf57985c0f5c530183fde8b11275062f23c7096ed6acb1b1ae7800fe2facbb6dd2faef0038f85c07b06ce1ad53fcfcc8b3b9b02d1f623cd5d8fd59bfef11925b5a7d583764df5cfc309bbe9311a482ea9622707ddbc8947d5fcffec64f512ad425369e211dde0c185ec18d19974ddb9ca25522a491626304324d5f056d6ee95c248d4a886ff009b94d501cdc17848b05afcc2220f4ca7395f38e015a224482676d9d24e4665bcd138dede9320874016945f1d27f31b13e1a651d72625a47a6a9a1fcc7e4a995a1d92b51064f3920511ef9ed4f9bf3b90e9a9c4c23c48b59eb27b9f99ddbde7eff7a1b94ea782b05266eef06b9ac4962141b4f4f0b78c73fdba402e7e31df0063a02f0d42de87d0d601ffafac396a1a82a25732828084f43a12b16c18502f372c3652347028929c6afd9c2d186ff7383ee2fd60cf32db3c6dc7f35c46952636200075667a23e6ca78bf903c7fb9493e97aa07e14df9484fc369a72c9f148577e12d959c2a9e746b8aad966fcdb4f7a3317cb4ea13e4f4c9f5518b168e290bc7e19faa845463ecd44ace77f54db4168dd5423aa4f9a7465008454570c0601830e09b46d5a82e769e31cdd18a56189edfcefa87ef18c2985f17acf7379d11fafc497c0346ece405e068cbc726d606ff0c8c00cb030719871c1f3f35e8e4544833a949d63473132e71774b6dad1b5d91adfa56ae5f811bbb3355aec277c42bb7a1b800b17bf7f6c951b6f62b92db5c532b651674ee05f60a8179cb2dea37efad57b0b21d296882715d3847bf7411d65500d0e23b518a780c19494b9a0ebf71c3c1e6af998ccd32878f760ce5434e798e92179e28ea325bc1cc207ef8d42633eff953574df9885679c8c0ae31b52fece07617d159ac662ec3c715881f6706a520ec713fa93f7795ecf999ff5e63a8e1ccd9c5fb7e39f1a52cf5bb8da6b7d01b817e91c94681a63d7b38a660c6d69d8e8ef3f9f54afe3c60a2f1ba9e148aa5f168867a2dfbf1aca7ace9df57e470233595737d87277ca7d532d14624426455bebb241a2d755f46a79b0f2a2d81899177a2249ac2d7ea10313e27e72b91d47776381abbffc4c9590baa048152cba48d52b6155b621a305207e92e358938058e5d87aa517695d57402f0b937fcbac352a92e0473b55e0efbb57f707646240f1112695eca023cefe620bb76a1d70cb707731fb1b1e4eaccdf81a5eaa0959751e696bd3b37e324c01265fde017afad64c9bc42f7eb77e99407b535bbaf21c21cff8d038b43955c0b7fabed275ccbf33ac4738299449d9c1a881540110ab7d2e817f980cc4efdf22fb5d2281d864bfdff83014bf70c69b914ac39b5bf2affeef5c06f0cc2087a451383cc70883dcbc21f1188e901e87f6b7e6df232475c3e46110ca1b0c7d3175e7e8200d81c1dd507f1d97feaa2418e5570316a517266e9a10bccd0e32b7a83e998614ee2fa4bb477a90ee28587ddabc465c5372ec6addd79fd816bcfe533d035257046315d97a3debd0484cf0afe33b30e03fb1ab19c2c557301ec29992eb003319849b044f27952d5f9b4e7c3a9382d3b94e197f235fac6401cacba9f7cc6bb44a8ea533bba70318ee55cebee9a84ea67eb15117fd77e604bc1623e7b97246ac4433573bc14246ce9ea9b83d3eba7c142d6cfaa7e67a2cdcbd15e0b83c0d410d7e8bc8f369c071846128b5c4fcc7c7399b2e3101bfbde2f4b4f89de7585abde094f28291c80da70ba7fdf8de7611374b30dd714622f6fffb88bd11a163b9bbb924e4d5dd214239c43ee627939f762a22557a6d370b4428cfac06f86fecc0c91666a90bcba0bc6ed31e649790140bb3b69841e2f9752cae101d453e7ed5900d1eae47c473e8579b312da783ef40c621c6d5155abc04854c7d4be7fe450c01aedb1ddd27326f6ad4abfc0587f67e921004bf56d2b219a6ad06ff7ee80eb6234237fa6522726da7ef2df432ffe6989cdf663a25b09caea80482bf61674b81570237c9f4968d70607002990cdc3bc6225eb10d60d692998cc945196afff6322c81f9d8f6bc57cde676160e152c3bd8e603fcb7b2dc3d34224f04248fab139e99bd3065f76f795c25f245d8e484c7ae349eff55b270236c26277cf26db07700d0e00ddf03d8612c62f79462e40490fdb373e5684c96ad75753c2728916f4bf6b9e2de5a5b0a420a3a519c497f5bd0f6638ac0e0dd41ffac81bb5c61bdcafac49767695e87cb6c1e489d66ef64903827124ab30d41f0e36e5e04ec1bcfc0e7f538a4dd4696997afd3fc64e5cc7ee254bdcade7cf5f1f0909654678b18a027faf9f5268ffc1016f4353f12cae18b988c571e819bff3b0ffeff638a46a651765d12a1426dffdd97c07fac6cf286179e2297564159de4d619937c44ad2fb38fd80c5a58e882e8920413f4b02d07c2cb92ebe768b53a774991664fcec2c4a77b82edfeca914061ae2237dd23e8c67b5c2081dfd6659714fe8e8a322d5c1c0ccb7a290b6f10e15eeef431c54abe20289e9ea10715f55c9d2c2b14340ffda887f3c8020347ebc7c412b973b8dcbc439fdcb6accd3cf159690f79a5af551782454165b8359a015fc3e5703b430743157634b64e7be00a47a89178f265b26663c31857af0ae37f0197b61a0b3a707bbacfcf56c2aa038c8bfe9b676dc8a0c7f42fcc442adf1ac5b07fbd76579ae0424d96e6fe0242d470502be1a607b35b984e7aece20df618f4dc639eff295b5e50e57f186b288d67e8622f42935c44522e8535ff01b681ec61cc1d88dbf6d6a777acfe862e9fc433db47f0933afa88378a3ff940ee1afd9e82e4473c3a3824e1a0d59c7a71ef9109b32a4b9c5fa8dd81d6f6e150a940ed23e9f09c30010bcd3de813b845e1af6cbc368d5fbcf08aeee1185f781d94fa0bf017247e069177603e512f468770655af75ab52b37ca4572f969457099fbb0c99cb4055fc862547988889409a0946b1d27ef50c0b006985dfc431c419a93978c66a9bf60373983b9580506b79df86240f6d9c00d4e7af162d52845a09b1260206bde72314db6583b7bc0458697191f872e921c7def7a786d3013bd0df28321b2f69b0b103abf4280e5d50c3023c59fd823722090423b4e8e56759eb463dbd2fb22ef63ee72cbbdecac5d60774ca81b6e899606b793786286e56731499dfa77123bdb7cdcd1022e3bc06092b9d8aa9f75e601883efa8ffeb008cab7574fc67d5c801b31ee5702203e07b51b932fd94cc4dce77cca260dbd316565215233c5cb87ead4969a8a45b62b26e7472f63fcd43439e8ee5dc8a5877d6fb17900cc6377543b178c557a276ec704c90d2a423ccbf0c95759cd0d6d218ee2887f6b87cd6b82077a1bac195b9cfaa8b320edf045a91a62907fd97c8024b1a1846bf880074dff74108ba97b17878437aad6e0067eebe94a32bab9a12d0addac2bed6403abad03189c8e9d7c177c2b6a8eb8566772f78339edbc86e4fc14da607032cff78fccc4ae6b04a9623282308a6c8806f75c5053f0b8979d27ae1d0cf58efa6dcf4699410e8d9c9112ee45db32afddded1e1a6d914a73a649ae877351771535f55e1ee0a9d58eec55bae8044f07e96850f9b8eedcaecc936f55944c1acc6e33eb87961eee5df903c82ed92b5bb32bbb9b5a0e32cb76b729b716c5b84b9c516b64f1cbf7ddfee68dc7b2c28f8d7d19f0ef31081b92b92752eca9b44463d83b89fe7efe8eb6c76b8f07107f082a925f5123847e2fe9b5b8d88a3263dfbd8b34e0a55308ddc228e696acc68afdcfec64d790032ad8fea50a11eac48827c5528a89e82ca2d56087b03bbc1af6b785cb185f6ca60a39951c89509ab0157218a3f0991e8f1e1cc60ad0fa993091b476c8a4368de8369e58a5320a541a2828f7995eccf1cc492e5c65faef49f56d18b845a649b235c114bdf9e887974f59320a39a13540e8b5b0a0a572ce8f986125233e0dc81bc990a2396b4f56b32adbdfc530bb036cf3b013a643f89625d5a96436c731f13f74c9646a095271ce4c498a9452b865f0e46c7e92a1bd0641e190f7140ec27369c20f5f5cfa35f1a0ebc000ffc60443a5aa57caa8cac4bb4203ef1b4d33500f4086dd75e2083cc3ca2f75f5d879643c6340af8c0d2c02418205e87d5d0caadbb35e62208d900e360c943a6c2eef282a5b91f9b61fa1756f9463ec5d7df8642da513d9b046c37fdf16b1a685709215c8c13dec98170fdf851b782b7b44e61e968ebe1e8bffe77321a237d80826b6e28374b2764f78ef3b945bd21e6b9bdfb66fe508a3415800d2874115c817d0ef3ffe74991c7e9aa8ba0c879e27a0d01aea5f2bc59935de65b5bb9bd6b8bdf9c7c9d7adbc222945ade05d6fddff032546551a5f5b7f9cbf469566d0573dcf34557df86cec77d499f7b5a6b6a03963aeeb2742f07312da237d55d0f721c466e1fc4b4aa85877e2edeb528b24b61ae54fc13c54d60f5fb8bbc81de0eb0cbcc6b0f45ea4455fd5096478e7c8d8c288f12ec2bca1379baac813320dcc126a9eb9f115d40fc75c106cda56aff3cdfcfdc90668b66a1b8441c1529681ad4ce7487889a9bd3414fe4e4a138350c598fa47f480f473113472c1e6179c85c866ab8ebe4782bd379e8df9f2b975d423461efb0b17cff805ac84916ccd1ce87f34e4cae2ada5a057a82338d463078c891f64a5335f48698d526adea70c63ba454ebc6a279092e3333ed47c01af9e8d873399b80af16f2fde3b3e9bbb6863c12d0371dcd0d0c890e7da3fce8f111b227fae0bd095913a5ce3f25df18faf014cc9266be79865c8ff61f3cef47179a6d0ebbfd1e413f4842ba01079c24a9e89f22e33aa3581edb379e27f6137baa635ddd67ed61488f7ddd4daf6c011c5f62118c9afd46a2d3765b3adf0b8096a9e5d99168a4d18d036fb55227fae97069a9c691d24c641b4a63cd668b3f698ee0a4caccd46b6bc2a9de3a26d7da7d9ea3b9f12c1f9db3e7d755a6bb6f26c1608caf78b1176a05e7c1a63e55c431c507f141daddf14ae79141cd90c91e5139ce748d13f3fc35de6bff8e7ee4f7952854e865d32d688b5c7449b1e7daf056761db3ca96860ea19f7fcec534e78a39d1997535972de97e1812bd8c9472ef820f2216733f64f7e29be6e9c797727cc04a5f6371d92eb46901b62eb756d6be9bacc72ca540a309ff7b134c7c0be1e6c4af32cdac3632729dca5d07462d40d30ef5d70c97a2723c2493028ef484860c3560f0547230333333333333333333d36c6a6eb81c6d37c99545847b53f1a132536e4a2a55e2d3b9d386bf481bfe22fbb7ddec731a04e90ba60cc00c4984d1ec7144512ea743ffa83bc47a1a51700f21b98799b0791e461426b5e31c7c904514d3448d7daa5aa6e428a21c342c57773ed67d2711e5f4a0d2a3cc7e9cee20a21051cd55ebd63d6be71005eff0234f1d7c64751d4394cc3ffcdac47ed77821ca23493a46c9b6a23a21ca1e9864cb8d49fd7c10c5701f45b876928e1941143b7ef02073596ade8128a7bdb348e21fb6654094c3d29c9366f30f65f7e938c7c1478f6afaa1905f5a5422a6b90ce943c97f73fe40c2d5da840f654fe69ad371545991df4321e87fe60f227a28069bd0b8fed14b7b9f8742ccfa72eb1b5725f77828a5a4ab0ee26f78aefe0ec590657b7307f770d3dba13493a17b26b1e2a4af43714fbd537de9871c870e058919bf6eeb3994dafae358f26dade6e550f83856b266cf49cd8f4329bc7e7278b2e6928743e9ed4534dde630c9bfa12021fd9fb1774339cad4c455547ac6db507c0ff197f716d7391b0ae6c9d63269ec59660d85c96c665dfa31075b0dc5e49ff193aceb764e4351620e3ab80dfd60c38e8692fb5fdc6609922bfb190a16e593e1f383bfd6cd50daea204e3c2ecf5a2f4339daf0a95b09d5137532147220539af63b52437c0ce513897b92471743214f566a0e0fa929f43094ef443642f7a26c733014d5f374359af544cdbf500a8f63bfc8185c22732f9423f268895e9a644e174ad6f16154e4aab90f17cab1f5c7183c3de6fa6ca114be5e1e8c86d3f6182d1442c86778d7594d1d9385729c0357fbcc394afb0d164a9ef122bbe557287dc72719c6af73b431562875989931f5b7468e3455285fae4a7e8ede3b27860ae5db2bf338dafecb1033855247d2e01171ce4b34522868be8e7b3b62d0f4992894343f6fbc9dbb4ccf40a1183c4476fca48feccc13ca91fe2e25670f2397714231c78e39398cf3419c4c138aaaef2f1ef8760e25c38462860d3d9ac13743be84424c47bf74f8d5f9e35042a13e861c43bb459ef49184728449fe943112cad1c38a4919723b478f500e25ccbbc79063276a8472e7b8d2cb25dd4b528472d8abe181aa67448e08858e3ac8f598d47a6d08a5f5c06d752507f5a94308c524639a1ed5d5be7504a198838e63e6f0c3f3b83a805058d320271f678d9af383a2c585048dd391a5c70785d20d9f91ee41395a49b9ba0e2bcbe541b954b2078d0c9be3558b1d143bbbc715fed61da35ae8a0e86d113ba7aefd38548b1c1462c65fbfb7d597a816382846f0f03918d591345b8b1b14dcdc423f468d3196ad850dca39baba6efb241d86ad450dca151d556e5adffe6a2d68507c9f78e629f3eb573d8bb2c8c96a8fe5e4a85a160549f9e8c3bfdcb053c7a258d6eba1a45d94861a16e550621e721e668f3eaf28ffe6942cd53c5d1a57143cdcf06114cfe8f4684521ab07adaa7152869a1585f55d7309999e245e45218a7dcc6fcf70ebb22a8a72b6deb13b1d049753512875d75e8fcb3c321915250f226f089153ee977c8ac2bff984ed8e4d5178e9f8b6113984fcb814e5b6f7f03e68e4468f4951b69ee898f9d1c5453c8af2abb9484489dadb114559e7f43b0edc3fe50f45b1b3739c2b826dfb0e8a8286a0e93d08f39bfa27ca7947e3c6ccac270aa2e1e33c51d783c9b19d2854079a714a2cd4d3cb89420e956ddbb0efa1de4d14524d79b81e8b87215e4d144b52460c49de4c94254fa45f5bbbf3208b89835b7cdc74e025ca41ee309fd7fd5c9d254ad1816b44e4b8ef5925ca517aa4b1bba67b19250af22bf9d77eabfcda4994c7f63c73aad8c8d74aa26459fb219ebe2bae8d4439d88d78a692eb632d240abe11d2c38fdaa183f611059933effd5024f2ee88b2c781a49330e95cf5461437536aeea435b9378c28e86a687b1c5446dc2ca23ce1411e8fe2d73c0e4514ec36797e8ccc33e144948366bee5871073be882869c8d31d73daba871ea224aa2fba1d4b4ea31aa2949b23424d0ee2c664210a75f1615de29f7e2421ca29e6390892a34d6d0ea2601d4ce7c72107fa114194e338fbe4489a636318888287dfdf9373de9ca102a2b491fb1ee7a3c817ea1f0afbdad962dd618c9efaa190e2d3a9591e7964691f4a561de610a6a6ee26e543394c7273b8d57a7ef65018ffbcdd1f5bcee3e8a1e8d991ab4f43c78e9387827708fd295f44b3070f8510a96a543cc81d8a214e4bf89bf830ea6987927cb41a3e54f19836598782984d98707ff7fd9174288739487e9d4731b923e7504e9eac5dc4964321e968981a49377dc6a13c92b7c3daec2952040e258fcf52b2e647ceee0d25adfaa0a67cece3e686626e482a6dd35a6e651bcaadb29d3737fa934a3694fb43d209d72a9ef23594c3cc1faaca739b6fd450baacd30d59437c3fd250f4fcb9a349aff38e030dc597ae3799fc7b1f3e43c953c60871e632af6b86a2567fdc1cc7d1f34aca50de4d1d5f8fd1fe236428649188d4fe64a21d1943c1ab3fa7fd2d698f2262287a74f4f499e3fc38270c858f8ef020f36028bbc669e84f552dff0ba5fc3839ce1ce385b2baac24f7badf0fd385625b249fec12731e860ba508297164f6ffce630b45cbf99c9f9bf07116b550ca5cd65b2f711f52cc42399fb487fbe183cd12b1504c1d27fb103f885213bc42d1c343e89c09a371442b14d2e33ba4e679202356a11c87e704e97866a38854285a59f8c739eaf7693885f277b68efd8197840ca5508aec9511531885f24f0e39fadf1c470d2114ca7973f4b17dca84e8794279a4465ebe93eeee8462459b5fc7f9dad326947368f9aa5e1d7f920965f97615890e92de2fa17431efed9b99437b2514d35fa6cecee1666e9250fe507395d00fe6c320a194217b7dd81172708f23143cc63831b91aa174b9d1ca62223c368b502cf1dc7f298f08a5d07c61291dcb72724328a66ff6d81dbb5489370021943d45f8ab77f06184378020142cd5ac345fe6b5bb0100a1b09f29226f4e8ff719c00f8af996621f82a4c467003e2884e8a1c7bcf192dfd8007a50acf0a859b9edcd616c003c2847cf089247dfc46a1bc00e0a5599ebb3464c9bb20d4007e5706bd345f4eb07131b400eca692a33e72ecf66a20d0007e58df4b0537b42ecd0067083620e395ada6e75b09f0dc006e5aa8f3bf2edf87acf065083d25ada567a3c19d5b301d0a050e53937722a8fded92c4a96513fb9837efecd64518ec2235b787f7b98b25894279a4695fcef51cb60515c7df5c9da817749f68a82ec9aa8bca5df785c51ea3f0f45ef5e7cf76a45f9d5a3334dffd368172b8a99367c301fae551453bf27d4e67844df5245399799857d30bf6b2b15a53b4f1f457dfb785aa8289b4fa79cb03bde91758ac29fa708766b3b6952a628e6f8dbe3e31c79ccb14a51b498ef2026794eaa1529caa15855a40fdae3d46a146515d18ece218731ad124539f0283f316b6d8a5485a2b4de61e319246e46058a72b8fee94b4eaa24aa4f14b76f3dfccbafd5c713c58fd6103ce9cd69be13a5159309eb784e14724e57771d62b26fa2301d67329afe4588268a3b9d52ff7ef3789e89d207dd59653ca67e0d268a311ee6f19a0b0f3f2e51f2b7e9dc816f52dd1265310b1397541d845d89b275840d0ddbf0e552a2941fddc4bd6ac87712c515b38f5a73b8ee114914e36d865d9e969d9128d85766be76b2fe0e1245f938eec34f0edbf388721cc1d37b7c5a0d3ba2984ad3f6356b44396d7fdbd78c285ae4c03a9c0f1f8544025844b1ece3cb666a6d894711c5ff384d6ef45509994d4431c3a3c8ca0eabdc534414ffe3703e994ce86c92000e51ec510d6ae9c1cdff638882c74aff648e2f4439ffef47d16cb631c70980108c695b4a8c90626432db6a34fa4c3f96539d0ca2f869b37cd863d6317544a0035fb0400082286e7ca0fac977f5ce134020ca39f238fa98a30b55f3b01c080010856899e4a259baac7201fca1982d82da5d64e785afe58762c8f371bee3f41ab1838c3e943d7ef851c6d59475980fa58f0e8bac0f3bd66cc9f650888edacc39c89c6e4a3d1464d326d718593a72270f058bf52c6f8d9273bb7828648f1219d1bec376fc1d0a5665fa9a77ef5673ac1d8aff39a40dd57c4909d5a178abea7651d3a118f2111de9781c55633987d2977cb471f3c3944e90722845b6c4ec13d66f2b3cc87128deb4c71cd5f6519e3d1863948170288412fba8b5c483952880375c9b3bcea56fde36461928b80d08c00d7c5c5b842b8bd0d4da18f38a3ca32102684339e79c34eff5cc56522c2f2eb00013a0e0040d70c101c820a327e06220c0860d1b25003614253f4701f987c27af2709d2cefc5c30fa50c4dee31ddcf6fab3e9473189f99ec4c3dcbb4051f8a3998f7384fa567ef7f0fe51c65cc61cc933dbbc57a28fc64a7db6cb94d855be4a1187d32db8732d31d613e620b3c14f2cbadea7a2a73b7ef50700db13ac733fa786b8772a4a95a23c7ed5d626e5187d268d428c944bbe3965bd0a1e021848d0fbd3387424d8ee3b84ce57adecba1e0516aff9d6d42be3c0ee5d71ce6d3c912d6240387a24608393e0e1de24ff78662d6976eabdf88a3714341c395cc77dc59776b43e935ad65b5254245c5aad8820de590446462f489eb195f4321648fb5d5860851226a284d94cf25e7712431c53414eb36468dcdb6404339af94f586b2cf9afd19cae1296a34b684281b33943d9d5a555ac94c865b94a1743fa1f512fd83d6ab045b90a13caa395ed1ccbecef4184adfa7a93f7ee924411643d1839acf16d97379370c65bddde8d9de63a52179b6004339fa5af3a06c3e3cf52f14435f87f412eb3cc8f142a137c711536e4fcce9b145174aa393939947635f625b70a11ce7cb103e62d88f740b258ff5e34c269ecf63af8592e638656dbccc4ea7c94229f263a6f6523d2e1b0be5f6fa2b9dda9b8ceb150a2e36bd3b9f5229122b9473e326ed94940829ac42d937fc6cee4c591f795428f54832ef48f5f29c2994e3ccfac8d4b633ceb48514caa93639ba9973b54c060ac4f882b68842d9a303d94d3d7edac930da041378172302fb2e4618785b40a13415d7e1bcd3eede7cc34c182e2003b77842e123e62fb777f5255b388151b1c8c8329349933226848c7a9a42c1164d289566fee861def1386daa38b76042f9c38f3996d0d418428880093cc00b0554c00b5286d8620965978a4a8898d4325fe1417a87b173e0b659b35b28a1e8a6390e2dd443e658dc2209e51cac84ddd41ddce46a0b249456d26de4f90b0fa34dd07634d8e208c5a8f61fc97af2482118658cd128c00a630b2314ae4389cc1eaf35e4fc2d8a50ceb15b0f3cea77faeb8616196590d12628126c4184c27df4eb92e4e7ee7914dcb1c5100ae121de47c91fbef94334bf02365b08a1187eb222e43a67f54f60cec790800db60842a1b3667cdd53f0663e032928266830ca40c1094cd516402898dc8eb58d7c8a30193f28e6483a775278630b1f14bccd2fb35cea3baaa9c4d8a20745910e365a8ec6ee98048c2d7850d8e8c1e7bff32ad573636cb18352b876c7fe61f89e1c7243eb8b32b6d04161af63c6f49258eb4e0c0b44c0860dacda2207c5ff1c76982d254bfa67b0050eca759bac3fd3d548e76c71838298ca6b4b68c94cf1163628468cf410bd6f5183727785aded798e51e32d68904b494610f78eb950b9b28f24b3fd5317e9318b627b07752793ab738a298bd2cfb706cf3ecfd9cb860dfec18c5814d4b2c3245233b3160bc440800d1bb0a8e378ac32ef75d840c12b0ab719de83bdf8c8dd7445613c46644f7a462b0a2b6faa6fb62621115614ddc307abee711c27dbab287ce0d16373bb3d54ac8a729ab0954ab3f1ce4152b1c9a490b052261dc27d6ccc9bf6d3e35051b60e195a33b91b5aa7df04658821c618163841ff18659011810e7c818219a728ed77646e1dfda76607334c51d45ceb81e488f8724f186694a2b8be39f4a0b63149966790a22039d336e36a1c8f232d4751f438b732685c45510ef2965a1d2222263714250ff3d6c7955c196f81329e0c27cc0045397a583f659ff93a0a3f51bcc8f039a86a88fd314f14edb6dc73940c0db9ef44b935d6d5addaadc71927d88a999052b2c887c7b76887e2b1e68ff64d147eec3b3e08112e398e2654ad8a99d81eebf16df2f2fb61e59b199928777c51ad5982788ed30d2d177c09bef02713e384d100264c1442d393a6a45fb5c42e614ceeb0cc8ec91115c1325c7ef81cf7857e382d5188397efcc8c8a944c167e33e280fac3fce295152df48a23ae38c4914734e9e9ea371d755fa620c315012c56ad9089b324ee7a8138952aec741c8933aba6a98ca4230031285f8d197ad86e6a6b39e8c4714a277c895fd1ec457f18696234a12b2686ccb749b9c51cbcb27a2684a7c0623ca9aa4438d1f8b840dc94594c36f0eaa1ea6bc247b1551f418929f4924a6570d27a214b2fa81e57ebbde7943ebc9d09e8188524ab2cc9c1b3aa77ce48b198728ceaece6f764a8fbbded0aa324459c7f43fcc476197ff0dad2b10cc2844f1bdce63ab74ec3ffa8656162198b58822e125661e61be254adc3ff4204b841983285ed5f607cd4ed7cf7183198228e5799f878ba64788dbb061c3860d1b848319812866f08c1327a4b39304108516530de5d9e3df8337b4be18a3df04a610e04551bd61c61f36bd8f6e2f31ce45c40f4c673b9518a36dc31b5a4f861826305aa40fe5b79b1051cf7e4ae37c28e47029ddbed933e2227b20b94449089134a544dbc35abbe330630a0d9db11e8a1d1b32c99e8d69a91c98918742c450269a425d32473cfcd5918aebd67687b323ba8504eb4c09d559b99ebdd3d677d4c9d80c3b14ae63048f6d9b4256ab43d1c6d5afe3b9d2d8331d4aab1dda3d0e52e7fcec8c391455472752bddec676861c94d02a1d2e9f122376d9ae4dda34dfd05201195f5c1c0a9f6442f2e00389917f389463cae67b54232f9ddfd07a43314bdc9ca3b283d53cbba19024cfdff9ab6fc841da50f038e5f584381e72123114f4092e0333d8500abbb4dbd4bad61c6b28ade424d6efd9b1c3a386428e534de327526acf23cc4843b9ace38732e14143713e881223c3730755a9000228e813540961c6198ae937b89a9867d83c11c10c3394c4e57d474a5279fc366ccc284349e2677798591142386790a110e26d48ded6910ef10dad309a051fc6176388f145ddd518d088b7b2aaaef852b1b2ec58fec3c88fed380c8ba1a8d1a53f3ad17ebd1a6db5c25008a22317a9c3d758f1aa8c16fc21600618ca71387162b286dcf776c6170ae1b9eb73c6ffffca5e28d9c76a19c774dd33da85728ae0666a1d7eaf8798195ce0cc6423479ca984d421318f78f8f26da3d611e38c2d143d87491b23cec545e686960bc628a34a6b23d0812f0a30430b852bf78f9023edac91bea1e5617c190d28366c1820037733b250e89c83901c277e18db344167c01031030ba5f9587e743d07def9c2ca8b0b2ca06031e30ac5f00fc28497fcb36ba300052ab04261b3a493de6029e1c36e687de1618071ccc030a30ac5df88ab1da4888c69a6422927673f88f53fe54ea11c7744879447191fa9195228a8e9c6f01fc207727e14ca33fb1e35e581482b28c1e6236640a11c9dc7be7f47fbf39e2794ff25d54b66ae9c8a6e68ada0045f9c7e137c81e500628198e1849247edb989c49b507a5d9fb5f0f98afb6e68fd08fc8918339850ae37d58dd38a10575b4261b24faa95f459d57443eb8b2f7e0463fc09ca4a30430965cffa3ec9a21f9e7824a1983262fcb8f6c608c01823cf0c2414a773501da66c34a4f308e59af1a036a7c26313f49741c6172530810a0e525e5c60010818411860f0cf304239478a95cec1e75da608659d78af2991e38f93264239d94afef073f89d1f3c8462ca0fed8d5408c5e85b9a35c7991941287ee8a8d2377f9fc7f41795aa84194028dedc6a4eee1dfe4ae88696095a0529f82faa5a05292825307f0301f483724e3b1955b783d065faa0f02bf13de7f9d859562079460fcaf6bb9b355f27e3470086d30c1e14ee6cf2ec65eb8790e9318a0d1b3dc6dbb001c68c1d943a3b630e99e338fcd05e30430785e8712764af091e491883193928dda4e9fcd0ecedebcb0874e00b0fccc041c1834ab9d68c5fb50c0036987183b2872a9f273a9060ee8130986183f2bace768b87e620476530a306336850ba881cf9c6c7cea2e01ad92cd5fa43ba9445d15522ab4a0c1d5387d12216852c31dd22beb9631216c5cd3c1e27b9e99499e315c54c11e284ce397498d515a5aaf88fdaf71dcf865ab4a274aa13de93cb848e5618b4604521976ac4a01eaabd2357519e0e2746b9655dc7122d5451b2d89c83782669918a72ac39353d95993cc21148828a427ea8779def3ad4dc4e418e98646259a91221b15a643e824b6daa97a085290adf9d4e53c4d9adf55a94a2fc21b377ae64cec8f36b418a92c6f63053a665b41845f1efdf83dabde5e76f045a880251338fd8a21226458ee337e69498c0f2055a84a26c256a3e16390c76ef171f0618c709c4305661880146960bca00838c035a8082f808592389879774e4ebe547cecf91ab5a7ca2943d25d57592655422c3868d0874e00b1568e1893a264fbb939e5bde4e10434f6f0cd609b4e0c4f6299abbb792d28aa1c52632d59649a2222ed3e5f2314343ea7dcd6cb76ba1896287b3d71ee65c6694d8002d32510c9ab6f7db7446631e13e50ecd7ac52bd375ce9728bcd8479b35a325caa3f9c344fb20249d498b4a14acea53ea367b6c871285b049272547f1a8fb4994e3fe98ae3186c66824518c9b7153e68e73b88e138972bcafa13e869ea0f78144d12af23b68d1dc613e8f282695d1f20427bd97f51c6fb6fafc390b43021890801708f002015e5ce001e40f350e51fa1c6d62c43cf933f34da86188d2e6149b3f46ff89e4284431d87f10bfee39257610a2986722fa871d34aeef419453840fd1fe39a8970a05514e99a53dac6cbd570727a146200a6a9e1e6d13b2c7fc160935005154edbc3a714c53677fef0e35fe504ccd27137b77dbfe5938d4f0435123c6f3e06190b4a1461f8a9b32b2511fd799c47881800b3c400d35f85088f2e261f9f65e20c00b1380d165d41ecae6efb93468cc88eeeba1ac23a711dee11e3ecc43e1b575e39465aee49c146ae0a1f4398eba2672d9ef9466a8718782c4cc87a162837d60ded03a3fc6175587b8861a76287b98f5cbaac315f5ca0b047881002f10e0850d1b78861a7528af849ffc8ae438884b87d28a47f8758c8e5aea1c4a327fb17fe6cba11c9c759c4e79a651f338143ff0d8f3412af73a0c1ccae136ab9a66f2c8acdf504e2b33af1a9a66abfc410d37947b6a53e26d4a3b76db50928cbf3569747d36c286f249c40c9bf9484ab3867268d6c8fafd213e5d822e037b50430da510793d9199fcac7d1aca3f2b7b9f2c533e8e4443c9c34f6eb2fadf0f256728bfc4e0d1529ded223443a9dac169a7d77850a30ca5c8c99ab0b23976da6428fd884c0eaa3ed7c7c817ba841a6328061dd118315649cceb8e50430cc69be0b1c7d7a6c250eae99839c29fb79a0e06629f89dc95bd58ff8582c4fd31ab8d6f611d5e2825c7b09ad3ba7b74c138f521547cf8980b750e365d7abcd1322d50630ba52b8fe37c481d9bfa06468fe15a287e8792a2b9f3cc639c85c27de46215d1a31d9f19a106168a6d3e5a1792c3bced71854279474ec9412487ff850bc6288322a028e813848144a861855246bd0f726cf6331aaf42a9373df2de8f1f468e59a106150a7976f951e5a9dbeb53286a78daa8c9dd83688cdf163404bc4080176148c00b0478e16180e1002f10e00502bc70c13bc00b047831c6df20a319e00502bcd845419fe00bffaa2185927c77f021fb3ed9f551287fb49bbe43f397351d0aa59d9d90b1f2e309a7fa1cc77d993c9c50ce1cad3e733caed975138a6935d6e46b7eb45a33a1202f6316317fe6bcf312ca3985cdc57e9cf7e35809c5d412268a7cfee0f126a170331ef6f3a64828dddd86e98df15b62c8110a5975f5a1fe87d2b6118aaed9b1a6bd0d1f26b108658f422ad2798ea773234271367dd0a95f7ac71f42f125abc884a8f3a4114239c8c6ac0e37f5071ad508423970f73fcd71e0a259a7ab1a402879fcee7b9c659a63f0078556b9ef2499aae18372dc68ce133a65ef2428502f2ef0002f4e603640ecd4e84139af4c8be768a3071112851a3c288ec6c96934be837267cca669f65935291d94356fce1e72327486899d502307a5087bfa35672e119238289874dee4da7965fe4da1c60d4a5a1e27ced7741c44866da0860d4a571db39e7bdcc9892450a306e520711b52a75777de5a841a3428c688ee4156be7c1c7d16e578aea9e338f20d9eb33b021ab228be795a7fcadf4f537971810510a3118bd2d49cdc64576f6895a15e5c600165d8b042801754de042850c105bc98c003bc8045b966c7c56bd289fd1f46960d1b870d1bf68aa2fbda46ce797645c145638a3c29619b3406081aad284726d6414aee099eedb23ed0604551628ef9231d750eee59453173988e3a45a3b98e65b4e0bfe025d0504539b8baf11c65977e60af1e68a4a2a82e3a731e93eda64af3400315e540cce39a9e9d65cc9da29c269cea8725e181862930d9fc68377ae471f485185e30a0518ad2f544c941d8777f24b9a128b83046008605a24083147d487f1ee7c8346b5e37b46e98327a0c147c218693229b018d51942a5e3bc2c92b8a72dc69a5bb29dddb330230c8403bd00845f1f36d8ad84d5e1d6880a21c44f32c9fe44d4da29f28668fd34b2d637acc1a0d4f1442ceef1231443b519a90a37643d5474e9d1345cfb1738ee3a89e038d4d1473fe14961e3992ff38a2a18972a8a9224662ae3b7b3351da0c216669deac97ea0dad2fc428230c30ceb81213c5df989b3f4b76fc9dc3860d1b36acc8a04b94b46a6265ad7c43cb50d06368c0128518fce4d6aea3578738814625cadefdf21f798edd5037810625cae532b949cd56d43c93286ef4f587692389629ff467ea8d6603543ba011894208293cae3589891af2e2020ff0a205621cc0860dff1640a2bc9e9b3a6f5eb87ca812683ce23888f193f7a43aa2a01e3ef030b337a6480d8146230a1b666b37721c3945e757c088a2ef7856c6c5c67b0c7181c6220a39becfd829e7db2a29d050443965071f6ce7186ff16f68651108d04844e136746d4644fc720f12041a8828e674b11ac4cd32d57e88b2ebe7e42ea9ddd05a43c310a5192f0f22ba737ac46f6891f145ae8046210a31878ff058bec3c99b30548065d02044f1523ba4e35e35437d43cb05078c32c408e30bb331680ca2281242bfe45f0f6d911b5a5f88e15b5e5c6001240c1a8228f6e5768ee0d20dad4014a79345c7c9ca2003c7b02cd00044d93445fc94f30d2d23c3860dc3028d3f14a3467a73cea7922e1f4118606c1768f8a1ac1aba7122635668a7821ca30c14d0e843e9cb3f064f1a253e7af6f890b506cf8e1832931b5a648c32507034f650f64c97a2ed1e4bff74433540430fe51cd48efe869c32cc6420a19187628428b7f1d09531e71768e0a1105e3cfcb06c4de2d7641c34ee503a8fb3e7687535376188618762d6602f96b9547f03b41568d4a1f0916dd2faa8792a73171c5f988581018e030d3a94dbda03ff49ed1b5a43630ea5cc4164afb6520fb90181861c8a2ab91257836c544b8c130618c48b0b2cc00b03f880461c8ad5d3b29d03150ee5306fb2cdcb3cab8521c68621011b36c828811862dc40028d3710634ff2ce1ca223cf0426680424951668b8a1fce95327bd2f8f11761bcaf9273a7cde830d45cd9c340d496247fc6b28261f2da9907defb81acab18b076715da6b214943b13393741c860f0d859c6e68b8efb813be33101b1227ea1e2d29d5aa3ca6e3283e4c9abca134cc508e65ed364712d12843d1b443fb106ae6b62634c850109118c43b73dc1d3d34c6508e39a8f77b3fef8e430ca51a8b1c078b84a1d4d7e1be1b3d7d42d5031a6028c69ac858d3f29a3ffc42393f420ac99a77f3280734bc50fe55c938cd2e611a5d288a072bc17d5b3232cd8552865dc5c726e9d3b1db011a5b287a7c1e62cfbe67dd1c985a6025fc6cecb250eef7ec92f35774cee12c1528076860a1fc9d437bf8c8e369f25ca114b61e964689b5b6150aa2193bd098d77d6155a11ce9749abf1f0dfed11734a850d28e1eb91539ece4dc140abeefdb2529bd3677001a52287e78f4dfb599e7355a008d2814a379a70e3f25e60d8d188006140a7a1131c47b4cd5e63ea1bc5bf211390e6a1e3e27147c77734cfbf864c2d684720739e2f7b4dadc739850cee1586f079a3f7b7e9650fafd398dfc1b653e8e124a9ae3d44c9a3e4928bf69548d5b726ee7414239e99a6ce6e0b3473c4728e68f27e3641af3ea8d50cc41a73648520f91a308a54ae9e416b661354c8482a6b7f93c9137843c8462d27b3bcdd1be1d7c211452c7c8dc9833c7d33e08a5c80d5134aa6b4ed103a19c6e2b1ef9897dd47f500ee6a61fc609d1f6f04131fe53238790e379d77b50d08fa1033dd930893a0f0a9d4e5e3c254e75e83b287c1cbac4fd5487fd3a28985dcbc7d6b0e37d0e8ababa29592e5a913138287bb679fb30bd31c2e60645bf96a8a79b37e6cbd0b04139f86968e67876be73d0a841517672f37f3be7fc418306e58e2071facb743c3c8be2aea71db3b9655134ef1cfb89f6e8c78e45a923f1546afdeb2e362c0ec7cde5038f7945a93f360913219cd8ec8a72b835ef9ab8b135db8a9254568d8478e6a7322b4a3ffb9ae1a3cce6ce2aca79c722b5e4248bbc2a8a1e857df0336f2a0ab1b2c565d3454579c3a38f3727cfb1fd7a8a528c89d76f87ff9ad514e5388e1b23b27baee32845612667a7635576e8418a7294e0162185c494758ea27013f73ff648cbcb4c5194f32339e930c95094d7f34b87aa217310495094e389fc1d65bd8738e6270a39717243dc90931ec413a57fd14dd1e167cd59d289c43da44f848470a22c9a7dbb9a45f6d3b889f2c4788f6ddedddc464d94ef67325a46cffe2266a2e021b4e5a5b44a8821264a9a44e4367e9c3d115ea2741ecf844c85962869761c39aa68cdef57a21c64f4cf078b92fba044e1ae523b4ca923f224cad11173e049d55a424e12a5fbabcfe998931ab9489443e34e7210c3f47c40a29c23f9fe4b0e7b4431679f0631093b363147144ab5ec32781a510e6fd9718e3e37e930a29c437c0fb3461651d00e7aa2d9d88721528a28e698f65f27db686a958862c81ec4e48107220a79bbb44524de437d8862368f9aa9e30c51de98f6cb2348648d9102b62844d13f904cfffc4dfbd01684288668f370fdcf41e8390eb56a639ba611e8c01732d84210a5eacffcc993a671f90251fc38b23c373787aa0c106984b6046b9768cf1692b4cc3f8e83039b284c36491f874e2e1d74347104b13e134549a71d7bc88689525c66de6a26f7b08315842e2e514815631d355e47d9cdb5a10b4b9483fda4a93acb6e33ce862e2a51ba7eb7ea901f94285587f7d0e38469e862128573efb2f53fd3da90240a6ea6a9171d79790c3fd04524caf5a1a9e1496d2486c0d00524cac96de2778e3d22bdc40b5d3ca29c3b7af930379daf992ea313400600c8d085238ae5ff415d2ca53df81b510839043f59db30a2e43152aecc886ace8e451493a5676ef538e4fd684514e39f7cce9cc23bcf89285d646d330fb76223882885bdba871dfef9667c8882cc7ef4bb2fa131c5862859479b7c721c5988725c591f1fa6c4aa4d12a21cfa43dbda6f8328de7fd4301daf5ae788200af1b5b37baa8f942c06a29c436d9f91ef370f03a2ecbe39bb3ece5f9dbc3f14b7e5bd222684d6ed87427d6bd85849fa61b53e14a4eb56fd460a8010bae04321e549a6c9ebf469740f05499f1ff6471ff5e6d1851e0abd31ff98844cee5c322abac843f15fccad935f782844f0588b0eebf4d420c0860d1b366e056380d11340c0710205d8b041a5e8e20ee5e8fd52368796e9eb638792065789f1e3e88c76755187c2768e7ba3319b1b63baa04321a6a8f7e878bb9843217aaa482e1dbab5585dc8a1d4e1a19c989d5978ab5dc4a16c501770286eb987f68f8c2f58f061548da0c368082421ba7803795424adc498d76e286a871f62c760d11aff36143ecce1fc6d74ffbc391bca693ec8192261db53afa19cd3fd7c6a784779550d450d3b199348d250deebecc06ff3467e201a4a9f34e694072bb5d1339423a996f69de4b5493343413d244e4a99ca500e63ab4b18dd8cb10d19ca9a3347699fcb741e3e8672cca123eb1c5bf5c388a19c33db7aa4e8084339b3db36fa6b301436471abedfa2b3ed5f287b68d939bc93bd50c8214c6bfeec314ec65dd075ff62b2783821174aa99ab5e3badc822962a4c38fa331a95a28659a4ca5db7b4a0e9285f255d7f6f9d46478d60516ca13f7e315c6e8c20a85b5d08917f3a1a18b2a14f7c1c6b439248f0ae410e97d750a85cfede1adf53eee183743175228f427b90fb7a5b43ba250eef073b6ef687d5bf250e83a25781cebedff84e38fcc277e1ce6a64e386cb325a7d9040fcb26942c36c723a71126d3cc04f683659def48f41c59822bbd21dfc945ab84438fc97a2aa27b104ac2bfd5daf9038f3979474239a60f5fb374e47154720443b6588d1adf6346602a5bdc67638ea4638a50ac0cf2316796c72d1a118a5efaff9ad3c7949b1a42f725f91b33ea070ba16497ae913ece25375119ba0842517ab2947af05196dd40d83e73d0f6f96a3ae4f841f9e31cfa83920e2587491f7c1fe849a7499922377485d0450fca61a7eef6341375aed1a10b1e944a4259e7207e6a67cc0e8afe935b3de6101cbad0812a9d246afcfe103a9e83427547f591a448f5fbc101ee79f5fbb30721f4bf41a1fef40315c9eff83ebab0c17963744c88e4ce6e17aac711425febd95dd0a0a092a38ff7d78899b3b3586cd445726451560f1e6feaf4518488c6e29eebd6f4e83ed86e6051cc3aa221226aa476c92b0a1f43c7a33fb0ed30eb8a3f871c8dcea64dd5722bbc702b0f1ef2e7509515a5ccc8fa1c7b27f9385b85711f7393e5ee795451b68bd5ff707318a265dec0452a4c8d1c878dca0afd282a1ea9da9bcc9c22138b0f74d5d4a57343ebcb006301e68037c141082e4c717866b3c152a26fac52f8d2b91ebfb121e4b8a1c5a428caef9bc72182ce848f5194c3f051d3594896cfb628ca915e44bad75a99c9a1e032b64fd984ac11a2a0288711cddbce2b4e42f813e5aeeee439637dbe2c3dd1476af4fc1c3eb69a3b510ce1ab634db0ec60279c28a64a0776daa13eda4d14b284471f868ce123829a284f7f660df9f3de3dce44c1b535a64c97fa108389a2daa7c9b1ad511aac4b14c2b463bbceb12769c485250ed732ce548992c47bdefd538f03ef28a1bc07ede1878edb9f44614c738ba8e71cbe749228761ccf85688cd0fd238184a65b6c8c94c86b9a86986b16ee21879a63820e038cf3c51a43a29872f2e626b6efd16556955428e8137c416de0e21185a8d6f318bc3ea6268e28475c4ae7499146943c8ceefe0e255ca58611a4d0be4eeaff594421f787a8ce1d62ce2ba2bc1fc4ec419de71cf713512c515373d9f4f4104494639578521d3fa87ae61065d71c4fe43a0e325287218a2ac93c6895bf901c2e44c9e3dcd1be35a4ad9310a5df2e9d10b5bb1f64104ec8f3f87ad5d611b810c471549ae137211788527d04eb7febb439612b4094ec6387ff9f733b66ea0fc51c1ec34b65c881e64ce5062efc50cc13592dfea551aa5b4170d18782b706f510e5716add17f0451d1db8e04339e6cf71078f50d7667f512d2823f9c0c51ecaf1ffe394d936d691d643b13d9a66f97bca8e340f45d3f650114befaf030fc5bdd80bd1ed099e2a4e0217772886ea1c0766dbd933fa5cd8a1ec1d6c346fca915911c0451d0a66df7b95994387625c4feff16b8608d348818b3994477dd33fde905cf76427702187626aa6869043c9aa31270ee51c67c779fe9169580f877284999eb4d23561e26f28e4b8e2b93dee77ec0f3714463fae62bfd37f1cdc8662d8a879ba573f492b1b4cc9fd14b39f24c305ef803514acfc3f5ea37c7262d450fc385c86b8217af0aab848835962c4ff1c648c8672dc1fc72ab31eade4bc09438c08bc09438c306a0f5c9ca1d89e1225668d8f23586a86b2eb4cda99d89a31836528b947a9079a6b7316990cc5bfd0fc25b9d6abf9188af1f19757fb770eeb134339e618fcbdeda322d261284afb473f396623f904864278145ff3b8ffae922f14dc63e9b58bdbd891e6c20b65eda8c3b0f9e3a8f4f8093cc00b31cae82b3070d18582e76464bd88fa4ba45c28e750571f4287e7d14f82c0c5168aa799d424c7e221fb69a1b8795e63daf3e0220be5c82607e9ace3f8b7d5175f8618615421c00b03e010b8c042c922d56a88ecf17de4af50eeb03a6543cc29ceb35628871f69dce8791929b31d705185a28a7d640d0bc91d422a14f3c318d446bd6a6b9b4251b2c24d42dceaf6490ae53eef183c8e5fa7ae230a05f5bbf530ee840ca1864231ec841892ace2e209054fa969254f08ea93b61c70e184d2f594d668e647e7d001174d285c69a7cb5af3a831b342e08209c5ccb08f3f48fd786d0c042e965088292dd33449dc3bf147a055c62f80b950427147722469537f0e639c84727c125367fb206b914442517e73a8137396b03971718442ebea948788e13f3e4628f707f33af910a77753846207132a9ecc53fa3e88504c13db6d09de933bf3808b219472539348ca1c49b20b6fc085104a192d7d2e6972508fbb61c2e8310e1a701184f20739d2cdae3da541e5020885d78928d5e122c9a61f14e6a523c9904232e38ecb850f4adb2737124a22dbe6700cb8e8416144fd83ae0ec77f3e7850ce1ed33cb3f62ab18b51c6171ae06207c5dc5b699bc6e37fe675012e7450120f356a72eed9fc7095c0741916182301f9012e7250ee99d395fffca943a32a5ce0003b7509113c288c36010a9ac0c50dcab5966b191f4e75f488800b1b143acc614886bfd5ed1c37b44c6082be51060ade5bc0450d0a6962cc1b7122b47c9a0b1a14438ecb4432f665d0c82cca4147a36ba73d0cb1d59045596de36d9e1bf1400c4ca0462c0a1d3bc7be0ed12f393947500316e43e6df1483e53578d5714824d78a0db217fa4f78616d11aaea0d443ff4dbb8e1bba8c831aad287ec7883e93436d345107355851922ccff49c2bbbaec62a0a223944b4c4bf862a4ad21966efd7a537c335525108d26213322e19728af545a1a234b9a221dd8cda5ee7043dc60bc038011865a04002366c7c81891aa728a55f798e99bcbb29b3851aa6285adfdf7526fdd06e3b4b438d52145b3ace8f4bcd53749a9c45a0035f74a006298a59b4a25eab4362d88ca2d47136d69b77948fb7612316354451f07f4ffb7a7f539f138ae269fabab1fac8d23a40518ae4c1a564bc43f1b84f143c44bbee09517165bf021a60e589c279af84cee7965d45801a9d2807b39ef05976ade773a2202a92ed3fda9b2827ff124d95b98626b050af9f2c21234b32510ef13de3c3a3efc31f13c590830e7764643b8afd1285f70e6432a37e869cb64431e74da9b92341a7939528cd4ef4f0e769f1b39b000c151c616ca20625ca91d4c68efdb64e9173432bbdb8c002522b021df8428c1a9328ee4ff9f96584d1232076811a922884099f97daa621c7ed17653c193f8619056a44a2f0f944efc30ea2a9e0e8036a40a2e03ba332771e3d79d00dad2fbc7f8c74408d4794c3189183aa7a5851a9961a8e28aea68d1fcb90265f65216a34a2d471d2630e7360ff1979718107d46044c12652d6e4c8fcb299c0c1f8220c31d4868d0bbc09bc01596311e52023a60f377fdcd04a4514337958d9816ecefe9a88721c075125d95da406224a8b99a8b4ad050392401c0c0643a150280c08a52faf0063140000000c1610c782c1589ce96add011480033e3a2a3c32321e2018121818160e8843a1400010088801413018100685c2807030481107a1f601f86d3a39e541463532ba8bb6a3e72641b0b98152acd00c66a24ec6016502ffed1c54180fde1f235ab140a8038b2651468200682213de4fcbcb5f63641b6600d3ca95a3e77d333578f26375471941e2a1e678d5ba52b6b51f181e914b478459643fb716e4d4f115a9548ff0364186934461a84b1485184bb6b8ecadf5543f838ca1ab96b84a87237bf3d37cc603cf0715fd999d22eac809f6b6a9e8e43d91704fb4131de66650fd9cf6749493366c4424c1fb0e35892ba70666cfc1a0f7a0dc903259fb81c7528e8ef59feb0361bf613f1cff41fad998fd13aad74a39ea5b9af503c75bcc04d78fd157fd7e37bfbafd86fc072afd8ab4a86c46e3932a848604495123044047c87c5044e238dc756c3655d29a2a7dc7135f827a0380ca81a41dff3e205e9c2198353576184658db8c9128f831885ee14f027a20240d37dd36f74ccfa3a0af0782da824e308b740cbd2c3312327c8ee2b68ceccc343aeedfd751447d480a8a03c6de2eafcaa55561a00b5f60da87227a5128106e4509bafed3d43512f91566e0945465fa1659c5350fd6af3afdf966b42ae08b3c996427c02f0f93c8f3ea01a779f633d33ce39cca40d0f9206bc597a28d06e1da0231f2f3074ce8f7d705727cdf5a9e683756d9d4efb026129a2ce71ced86ef5dae1d2b0d6a6419451fca26f3b12cb318cbf359b91617c4aa1107236b2338a1d1b884d5dc7e309aef0845a43cc56867e5442fae65a467d2acc82b0341a429f4519a84b49b5a0762e846a3b9086f4bcb3c62f6483b547e6a3747a3a7258efc8616577e2b341e4d9559f78c02a33ef490d2b89da5119093fbf340aa257e815a2c8c6f04f3320997c53ac79c3873bed680d6ca8d26eac309b9bde90eb220dc287e5229946dc98cc734542220adf727605f7c2d84a3682037e9a61f95b592e70418aa383ea3abd233204810c0fca045a3a22394c05eb247871b8a4c2b893c606d001788db49ef13e41f693d2a76134bc6bf6401e1700852024a0bad0b157733c8aa48088cc7278ec6407fe9a30b463abdb2483dc19f0ea29f875b8889c7fe1387c918580dcb6ebac5dcbb7f0d182df61a8eaa628dbb860c224eb52a541ac70629ab9457b9b3794db823a7ca306f3760555023eaf1b49ce1e25419586dac85b21a35dc21b0119a7895b95ff08251c1315e6a50b1935f6f0d876ae2982ade5750357eb0926a2e795415f442f9b09c2047e8ea883381200906ef5a42f5497075af2b27e2c0b03c2ec6b75280779686753fa75fa56b217464d79303263a6254858141f37f82f512dbb4db1ecc22ee6aad3aed825a14e897299e44c31fc966a0033cfbfd6d6d2572a0125343a504c5218b0c1c50a7867ac0f402d9002665d4c0490185099501ace9f8483a4fe4a4fece067047341a120fd14911088026f725465e0d43ca46d42580337e61e01a0fe05614906d4b49910f99018e5815fc064c0fd94e377e7855a283f75ccda980368c1a2350c52170b4f806d4481aeddc29b4a4135acbca545356beb43bcfe29e997c7943a1adc30a771392393d1905784252e97399b954e003cb360e83da444406bac3a9ee7d6253dab3d84e78cfb67d436061af2682a0113416c4331024c84c706541ea0ef830d0b4be55a15cdbadf852b01d1222f16dbf38dee789da0121ce3586b0d3215891b35b6c40dd29d7322e2b1b66febc70763aa15191b01c9de0ce519218cef8be3e0fec0100a946d9f75d39a086cc063b1783002897bc63a04455c7d65ae990a8594628b0321e2d705285dd0e254c922bd5a4f171921d1dc0eec6c99013df6306954d7deed7e3d44f330ec50f66ea067bdf0ee02945797d520f4fb7f902d38346e4e1fc43eb082b80a0fbe44c86906902940a9c55c2af033cabd8bc9369e7315387c5548700e4392ed0acd57b1f02ed8abd0a39c1730d05295ebef3521397d6abe825dcfb1a805e03e7f43bd0df299783cb96a77f4c5fa3445604c26ed50f734993120e706776ebe7268c70388bd62d7bb45111f4ec2f6d9abea4bfa6cba17962490177c84099635744987ee7022dd9f27cd90a1ade456498fab077ac5955b843bd0e9a3c35ed9a261f824ebf2b3d1674ef2ca4a9fb6dde781593ee922390c06a8de314111e7c9941561b1c14f88b3a3f172cb20d1e216caa69f7d3069ca80d0e2e9f249ec1882e7488d26666b7b0a5b1bf367bd7719322d09ccc7e5197435f529881ccb1b95eeedac8caf717893aebcae798244f674af95fabd7acab288f826828ca4fdccfdf81dd06ab89d7c9d0a6aaa138edd415632929e9e572142daa6f5794465905707552dd8fcb3fca1d344219ea7f6567d19185b59cada36644c75ad2598a1faa04153b9528744bd8bcc101d520ad8d38362b1f2f9e85ecdb191c1790de4607794a21dd48192aef75374c4a479bd48a31d567361a8fae3e1181cd9a0b3a3c955b67054ed2546f38da3b493e477ba40fe04cc2efd1d90e034989ceda213f63ebe2308960a35a02630d62e40138f3edd04152b7c33538f711d357ef1795c5d8e1b0111c90c2ff8759670618ac1aa8857bb2b94c2e6bbdacbed4c74eac5d35c2f0110f0300d75cb9dc95b0ed7b44e3f122b0e182d99b547fd5c35bc9a36dfe6125fef1a76d11e0deb7198e4837d19af565b1e62e9d6a44ecb0d6f154b9fb21b2b728966901cbaa67f9981458e0e5ae33801b63cd77074e7c1d7f990fd23b576262cc81214baff5a2587d20502f196bddbcec3bdc5a46efdd273cc20fbb5cecdb52f20162d7cc0449ec8f1c181c7c2239cda307b3ed448f2e0613ef3b3f5ef33059c8289f6887c14e79124ce9f9c4a6c98180f72adbab6d94a6e18ba6c2a40d19dcd7a5ebf2f49d33d618faaf6157d58287820d31f2501cb2d7e54e514e7f765caaad17404f8d57a58d851adddb059a7cb62797c699ebd1875b1d3eb492b9bd4c9df74e3049d8bad4e23dc037114ef932295144639c041457a7fb9a65e040f4f60e3caed3ddccd5bfb2f9f6cbb8aa7ed25e7f2a2c450968906ff0e66a091bb5a64749ac41802683d0db0534ea3cba515b3aa930a198f41086b9b3ceb52bba5b8e8d561727a2392e4ed03556952d402a9ba0a13286145336936a7b346b31a14ac0c7ac128eb717e3a184269f5a624c16863b17578a8faadbf9753d8239f3f8c6d032e4ed35658872a76457f217ef04dfb0f20fd017eef35e144925bc643cb34a767fea10d0adfc881a76a75ca04a671330dfeeee46a52152479bad4cff43656e9b1c41691c156b5a0f04d0b28cd7561f3a12935beece4b27025d2fcb793b64f1919f32e3185188a16fdfb891d8461e1e204bf6d1a61c32c661de261ea1eab7451c96a16c062e30ec75a328a5ed5e67c07f1ad67bb3e85995b0f2660d9756f7ef379aa4b2cd9ac6330e3bd2d23b67afaccfd57aee00fc88c2a2482c6845825f622254c653896ccc95d1dd6be3f26608b5c102b12d10796814445b9bc04f5ac005b83c92a8db2c29495ce91b3e67bfd2f310db34fd2292a2b95d8b1373a9f66232228a7d444cb23cbb2040c1fb257a5bf902dc8f5ef2b8ebcd9479e5ab4cb3764da496800728a48f729ddbfc422278e956b9ef48dc8abbc80121289d17c855be4045b20e4570aac1af7ab69439e4992343514836b60b5704cb130027afc566b845f606cb2b01e075d85992958cfc847c4bf5f5009340f78a2d1bd9d7212320f5c697c81385a1c22ca2a8903abbd9a8e7698f2c5e95a794c85a8199ee2fb8bc928e20c22d4ff52159a82c1aa5807882771bae0e2f8eb74be11bcc91a28414ce751cfe9f74c7ac08e353d7a2d3c749eee2a3c50e092861948d79172f63379c91e0374f98ab0e1d8e9232bd6c59c67bc39638d19260f805470c1c2687aecb2f245b20d95282d6f7b7d7b5bcc6da001c068f0988e8cd3720becc2693913d24d03eaedea678fc289a8deea1732ad75c954a52f5020407f89dc97892de1bc759dbaf0788e064aaa9c2c881dec8811f1a91fc827a644f3328cf079afc75828e7ea7e49127c7f01829685504d1cd9b674e93986ab176fe3a7272875470f011c04d862e7832d0455082465c1a2557587b7794b256734c7445ed8db6d76d5fb27267a3a8b9bb15113fa7f872e9fe10f3231d7f6844b287c77ebefdfdfc912bcd10e424e048cf0c6cf629a9cc772427e86889819274bf9c69ceb5eb19d1f7468635fefbf6e186b464e0735fba197ff0c322d0461a34ed883e6e5a227c7e75c45581b919478619f293124535ceceddc23de2dab2fbf85ed1f14822c7cab98a3378743e83e3789e46f19e57699451c62eb59eb47d2c04250cc802608ac050e52ec11578f557c0fc53d348e10af0dd68b290f0cb64ede247f7c3dc21352f93a59c7f13fccd7f544f324120635292d8d14508b7f1acef5fdee63f0507b77f26527b82639f9282f03557006045526d625e182838683c36b4ac9ca88839638367bc9e90bb99061309772909faa4aee4b33183d8bbd23fe3feedb0f4b393384c0353fabdc66569ff90256186e51566ba66f0d58e47f70669bb987aea0d9fb19ded6ce64e962543304ddd647886d8706732ceee3d1deed0ef410b8cb5b209a892af292201eced2542b5da18edb83fe75422eb69ff7a3b6a92ed16c6762a4d894c171f383c9e397d53ba496cb2e260a7cbe8d838681af09079c28bf9d97c5bc01429e62d11727e249bd6a36ae57ea46cd1f1922c387103c66f0a7dd41112e8d04591be7915840991f6e56b68128aa286f0df50e256eedced61347adc552597b48314a9e17eb5fed4a36a20288d2e85a69f4c7ba6ec056025f8abaf0f43b1d2eced9fdc1535da63c428c43bb89b8932e85e37f4c2c27681286bf1e77cbf1a23f35c6a99278e15a76f4a628e0e52c4845a7084f9b532e7abba9300be8dc8c03da18ff48bd385102ec84995ba34f00505933433e84f47a42979e34fa052ce0fd2213f09c9c8070bc47c70d0ade0ba8f5f6646152514167a8e2dd9887e4ad8299879510c0d59904cb57861bb8281e54ee8a780fc429dfd1898db919e8cc1d35ac6233806bf993c9df8fc5f9aab4f677e21166786ada5772ccd9bce9a97e04f2c164a7c8d41e8b341bc75b6fcedcb973186cd2f17e1bf9a841f4e4d24281b655a89829b7c9f878ae7c00faf0698a12346bf5fd04bef85b30fc24084edc1981c9dc3e8b022cc6bd965309a9ebc9d54ebb4a7b8378dafcb732185a820990bb101cb077d11f6b5631b0f75917c3e4d9f3a54bab5286b7e4c30b922852d1147f3da02040dd914769786f732aef158ecf45c0a3973bfcc04441320bab5532232410925364811cf297357f530b3d65ba0e9c4aa246822741b371d9409100d998f02906b2ab5a6ff97d559886bd1beb22332009e0cccae749454b48dad3ea96bf80bf096cd13122b7a4268377bd4cfae8a88e395d2ce6226d883c99e93e69fe717fc2cbb7b8cf09213926055e2282f5191981e684fbb3c9ad896f99eb26e2b3b7d39352d8c453e485f57aedfff4fb506e5507c5571332c5c16e21ec3cc42cc8d23a5b80488e72c3207b5a52ee4eeb3eb92e6e2caa4ad3d52937341f4153ae7b22f643129501a204da658b90b87b88e948743cc3eb1ab39e1309b18b3a3744cc15731eb1d6cbc787b3c08b88d1ad38db1303e30a4b72d6dfbc1c035cfc0c65bc43c417e89c67efc18be5c311e535cfa45718e7d856a9799cbe03234d18cb7948916515f246a4139a0424ddf24112e6710e5ce73b9e0f7a85e6ae255e172b281b53397e8f02e51343fd4c4494ae956c55a8d1217c0dd99b4a57312000de1b78aabf8fdcd2cb92692e65b592c3fbb6bacf4cb3dac99b96cde6c669894f583b511367e756f1d243c640c74cb084b4bdf48873ef15d768b9ef967c17a0a1ab569b98fa7a64ae0b403de70fcded52c1709e95dd6a392186bbaed36372fb5cca17316a614b12364b2b40d3c5c1d6c66886dcde43648eb4dd00f5713603fdabb04a30412c0b45d986f4100843c024406229640b8005b25a4bb1ad3bc005e757fd231c25b7cdc792ebe9ec3a6847ed74aabdff365684e274d7489cbf527d5735e3e0766437a0c72806e9c5c2ed79e116c813b8bc8c534e4a2ae68518221ecec0169b8f7c811d0f5f85b97a507cd5a2acde2664dfc2e43569d8fce6b60a7bb96a74b57b36af6992f306bea15b9c4795940bc7a8cc7db436050c709d4bf5459748fa0bf93096bd03815dcce3ed350c410b975f2c9436d8230dc7b0ef1f4dc7270015e233d6bd843c0e6dd957712e1da53a1695569c2300f00b5490f89a3fc855910ddf25281b071488260f2af2be39d796c78f68b99149d142f75e39bf9a3fc2c26620e9521d7f21b6247bd613ae492c951b929d350e141424c59bf8fab7969bfd66c78b411cf17a381001ff894194a0e8262063d46be7726420c73bedd6ed41ab4b27264c1230bb45b8c46c0cfd897202cda8418598a1dd45543e518c439c5e034148a620eafeb34f3c6105429bc8ae1b077d5c7d72dab263004160add9a30be16a2d1e207cf5efe2753040c6b392fc77986f9fb556bd7c06a2e315cd47242268f5b285c9d1852da0666c5a5d1aafc5989384eba7ca39a66ca2d1a4186a486a44a019321703e4acaa4e00f655c11c92c8452bbaad432d93ad06de3dfc4e801486c673ae8f1b94102b51c3673e69c62e1bb71c9e86bf624bb0fc47c60c0109f87447436040ebaaf65357b0845074e0927f075a439bfb8e5f3d773260c7e150dd654f931a64b27d4808e571196d439903621cbdd9f0b2de9739a2d5b6db129f324f7b8209f3a52110d1b73fe3b41059b1c82548318ebdbbc2d30687f4268a1ae0cb5fc8b6119ad23050fb3d4f22b8b30d892947dd320a291e63016238d596f1c97aa3668a35f3037504a5e44ac7609f55376967158e6defc7ac5280a088414068c55cc990096550c8c8b8483a2938582712c4118e1cd5c87de7e0ca21d75108c2f34de51802eb32f3e29ab45c3d861b6bcb9ad48dba344038b172b92a36266efcabe7a4dce9b9ce9383e342b6c88d68e60f1fb320e0a9ae4a73255ce6f7606140937016a8d158aa8612127120f59db5e9c13ab7f539d813a69039a1f469acf3586cc957a59b822d227882cdeb2c664bc9d4c2e62be5126878b318bf6854669301a4521557c87400f9322092172e53984f06e9c92f5a024460f93a64b82ae0182d692c7f7c216f1d01e78a8e8f9ce7a6c474c5309ce44ffca052b672b535746ed3297cd5131f77f72c81b335afbe6042e78d65194d8a963bdcaee9306ea4feb470a5d5715782173955da3bd3d89248d558b8a85349425ea1a423d0a2c8855fe91e90d5679f1c7c2a1d59345491398277530d8c3023d5bd8c3023d5bb887857ab7400f2e400f0c23defb0a1045753b36304a24d2a6a176db7e361a756d749eb43a50c75be5b2151dd937c6a2461283eec830862dab501e4111b282f76b8186891f73f3b6601d900e9a0517c88cd0e6e7b5548df7591e19f256788cd4c15855fc81793b8aabc3112891061121d6be96e3787fe9d122b703838f414a6988aae75d1adf120326a3d1e5c1bac90419969fb395ad2012a4b89d2020174d908ed95ab72a5ed29cf4e56099ef59bbf31c5a89185cfdd7a5bff4044c8ac62356af448b88eee05538220e3b756734c2ccb3b05a1da6f889c14fe9d898c8e2faf35fc10259f156ab4935418a774ebe7b435aaf3bac8c2b12abaaad8d0cc8469e7af7196e2773e36656f034041af41ac00131cd3ab1aeb6dd7d5c8bebe9bc04b8ddc0ef65f2e64e14e350571867c78c00fae8b7de4d6e4c47bada5036a0e2f9891885da83e33dc7fcae03fff49ed5c0fe65dfadfc6c437f1bcd28a6f83ba85929847adb8833dcf1f26ab1cdcd3e37849481b9c43bceaff837a6f1ddf95aa4d98e29905f61bfc8e6863e5b1d936cdcaff4d1e4c8180258714116d28f54e304761a154f58231229a98171cd5836f169130161a2c31796fb4a22069986a4b3328d9ea17513aa23efecdc37d43847fb99fc1f08fe56c208739306835589fb88fbd8c109c5e7d88b15ae881b1c569ae6ca0215209ca228a161b20cec5dbaeaeb2e72a11ce530411f9f6e1fa69248d0bc9fe369ee7b27d471f367624a31e2255befd68c2b4bd4e437ee7dfd9a51a302df6ec9c5dd4da00ecda15a5f947074f24a4044158fecca5879f87e31b49bfc4630e808a0cb0926ffe518d35cca95e1b8e5f35f316d5fa286870788364de120e0b2862c72b0f226e77b8ed7d8dd4a7ab7fb89ee9bdc57306db6ba4ffa324ac40f6040c801ed7b2bafee2adadc02dd872bdc120c9284288260c8c60cd89d8e0d2ee4b5d07255dcbc4a0c024ffb010bc535f1f951bd8b5576aa54101dda999053a7cb14bb48f7d2783cd5dcea665e9a55f03dc3f4ea79974138cb399b8c9cbb72ce53aa1f2ae13c14bf2963925539abc4b763592fa2a066280e8710d74faedfdb4ac561a6dd4b16c0accd6c4c749860adc9eb2ac599779195bc7632074dfd2c69093b2d4419ae8fc92b388d0b19a8b0a6c6e007c5494f72019b0a8401d78ab384b2217ab0ff7da50a6b12e1fdd3ee5c52bc3237407bd115209a8915f3f2b9de48c5507121cfc29879302a2bf891dd83e678fda8004cecdc1eeb520bfd6a0e875094f90a2f4ed2c21e041448ee88b72a39b4bc00a92d3cbb38bcfddbbdabe03cf79affb1d4205655c2ff585d0487afdb68bbe254251380b153531c6d4651b66d792ec0cbeb42a3628e32a7529e190eaee817a82a218d2aa50a9952915d3a700b29302be2851096b0a7e14f86d46b83b241d45cef5f20fc310561bc63a63652606ed94e7e96452c403369a89170ee48cd6bb2ad9242e2b9c88f7483b4204a8f44962230df1a03bc4e74f9ce3458c244847ac2c15205be09240d96785d49398d065e1e418115b7e5ad7f4dc8bfc9b509e17ce760c08188180e777e0e8087d0152d6ea4078873da5f6810f1d848a65757d206294ea0118f0555503fba8b5775587214bfef929c30f2e5e5443603965c014954af90c350a5d7135f9dc659b83fd2f934a364d7b0d4317dac008267e659c47c28bd4b5365ad5ba21267e5feee4e415462927cf43c1f41b14b5a8b9023b6b6ab5669b7dfe8884cd1f7abdd36edcca9331482ebe249a5ccb72620713ec61105890174926fd172ac78e2101f5ba363806157c95dedeae13419d1d415e579b5b49f64e82304de60f5caf1d7407b001555630666874885d5fa73185b5550555c0bbb3fbb5c32253ac99535a4387ad76378180942780e83667484a4a0b154624b8675da9e147f7c6e209f14c7a6e7135326d11956e1cd2a133fc92543406be65063a595c8ab2dd6dab3dba99439d97b8e94f95f3491763519004dc977194aaa057bb7c8ca03004b4dba80fcee1f4603b8d085ad5368d69a3cdbed209dc47b18b34227e1a7a2daf72c2d501fd6f26156078c2e99edef7fd4d3a7bae88ddf86b6366760b8d09ab13f8255f0995ca54db47c45aecc435632574a4a6268132c7ed795880c789585768c2ef0b1c44598ad5fd276db1113719c392c3c733c54a03fc25c184352a14eb01739ce2bfa04e6176890f0755c5214352859a062a715b85025deba8e8fbce8acbee0eca2199b34897553cca3bb8e557d3eb1e7e19562d0482a06b16414c7ac1134f1561c39223a8baee0362580d381df23fc45d30b0cdcb22037ecc2c8dc48c01c72cd30c27f6501f215fc3ce212033d4b0fb9d19f0a7608592804a97d72a71024b49c307dbea98b7009930ced10dc0d6102904c570e5e268302facdf9da44ca4cb7f49e13fae80d0bb4efbe32eac21cf5420fba06b6b3908b04341bf3dd7a4671f16a6d5abc78a3157c5cf525f8c2493697818b1424d10d404e750720d55cbb5878bc914e98fd863f7f66626b868e550e4a8f153e34f6ff7e5f45530c0b24b773c2694592386c97d8cd7907885205061770f4b8132b10e028c2855772be81458242353a58dd9d123ba9dfaa44b594bc9389a8d6b2b4094f515add11e59ad06d1929e42ae2b6995f66b90c2866444d15de69c0a1b1de24afccc0a6b48277207d5c232999ea5d6e9bdeda802b6e8a3985929259e6acd30d3d22eb51d5c7a223be78d88a4279d5a47d4db54880ab680fb3c42d69a57c2d33e7dbd4424c51e9a6466520501e08dbd28add8e3a1dc941d7bef90c3ef51e971cd91c84666a8212ca6d9027855bdc31726447940bf65899a5d0be6394b5a0b06029b1236a00064c63587d8622d22482fc5b319758ae6ef73d9820152d320eb753cfe41442bcbaf0ca76d3e434bee90386a6f91ac733c460236c934eb91d2b716689815cd7b26e0bbcd3b52c2cfb7599565ab8961dcbde8cbdf20a6699d8f042afc5ac5bc0682c50ae998c68b533d43922198e7b694e05a58d9740fc68a8cd0b5e870647fc3ac62428c2d14e2c3ac0ea2a548ff1919da897189a25bd5570860b7acb57ebd86f6605f9ebae7ea767f9a6966d454f5e6e9db19b0ac9451dcdabb46f66145ec2f3c9be6ff777467c57cd4a7798ff42e7fc365ee6b18c82005a10871694bb0551bc7b4a931892200d1596d585bfc5aa094f805d2cd4982ed68a5909481be2d38aea844f0031d04f44756945627241d42d8c88f1d53fcf73b93ea75a8d4e29a9581e3818d63b200118bca383a648ecb4adfc30877220b31317ed54afb01fe0b1bccfd1ec4ac5842118473bc678a3ff504f2ee4a74aa772a5ee586796a5c4b336bad19b5c64372a2e4ea025740859f06f219abfd2237c5fe36996f244263605f09aa3739b2fead81fcfa3dcce91beb56b7718c7321a8a084c370a5ed047ef005e909919963caa899f0aba268b5a01559ecb41f4b11c98db3e6bcb11ae9adfc7ab1c9d237c58777f38fb53cd62c4450a6cab6570ad8c79bec9fa0bceeb3f0997319d1a344039511216417427fa42102f13eebf92da4a9bf1e1025acde293bafef770497c91392cbcc070486fd899097e0e04a1f219c65b426456feacd33ed4010ecdfc839afbe40f400cc4a791a8d4d9b2c453b1d0ed143e2f7a8895fa68120d1d29434cc2258e9604ae062f37190b43e36db16840e83afb9df06aec4c261f2bc39f9cb64930368db9968a43fe1ea7a729047e8fd8a8789ddfa586d3a8336cee6b69e314fa4e925a4159c0524612ad3d65c1822cc3fcf7b1cd3f9100aaa66b5d6523360d3e3ee7ddd4bad148ef5ee9f4cc9b322657982be58e5a5000d23bb64356edc1574558814349509a0e22cdf352c810255d4519e62b0d758362081695665eefee64228408e72b6312443312079ef5ba8432decd329553759bd07cc529a5eae6481b2c20f69fb3b665fdeaec2bc36a7400508ee6889ca9cb1726cad9f82594eb5913a78eb768e72aa5dfe998886bcc8c520d8bd8a88bf0d8d20c25dc5a733ace52d5505afdf676318716cd44459bb0ef3a70b061bc46cdeb79bd01e0d9866f0b39281b5cbcad795d94514f5ca228c948f70db02bfe9c67b81b95f240a8825c51b5df42a2d36d223ba3f9eafed274a536f49d024e54443d0b3640b0a6d0013c0c3c0c3c0c3c0c3c8cdfda7db3334b424a724b0ecf352e2371f45c4a49a62453ea01e7d8dfc65d23f6139dd87750d105120b4d0b3b0b979f58dc9432a23434714c62ea793f3739a491895390b376496e92826cee6860e2a0929ee79a762929c9d9e280c6258e7f2aaf9b99f989ad29cb6147c312c7682bf236c99c32b312072b2989419b98d24eec2971363fb1cbf4f5bdc8486312278b414933ee61f2844912e712c37cbe2889c4f146bffe49d950332f240e26ae4663f8082d571f713eb1945c89160d471cde04e9252f591adb984623dcd4e55e9aa667c431c68492bbaef4d87c110769d59b478813451cab7466840593449cc643a5709659ee2242c4415474eb89365e7ff621b858bf979ae4d81027cd948491637b5fa22bc4498610c2a452ca849bac123408718ef1ba744998123d4c833899586a6c5cb4823866af959b462daf7c204eda366ce92d1310c7f4d7ba5779e2768f79a0f187538ca14ac64942658a314fa0e187d389d1944cc2af82b07c1f8e6b26e575b58a679bc28763860aa129477b38596bfadde87de2f689c13ca0a187835e74cf19b50c17f23c1c4f0cf69fa5b615b6e3e12077835cb2cd1d8e7f59bf7362e869cded7036e9368d8a4993682ed5e12435b364f0f65f9d133a1cb4e952e2f958dee77012453643d706c9e16c7179c3791c879318ad2e6fd01bb4b586c349beed9fc9fd460fbde118449d92dacab42425bbe1f45bd29affbf88d56c1b4ee2bbeec93fa37f31ca86a39d98477e943a21d3b986b3d86a75a6b9a8e160a3fd44ec2a6938496ef516636349e6a3e168725eae5f49f90fcf7092f7cdd39643d85a678653eadca6d8e695e4caca70d4a0c56be42a329c4ed0fc975df6319ce44c4a2835cda2254562382949933ed5379d29bb1cd008c341ee9c64725a1e0c27e99f7382924a4c17fe2f1c6c83aefeeede0ba710427ec591ab0e3fe9c2612eef588ae11566e5c2e90479f94e3e4972db942d9ccdb73ef3c347f4bf32c8d0c2299560925ea8f1b752928553599718424923c46966031a58386df48faf245738e6d7ecdb125182f4da0ac760322b76255872135385e3570972b25e655e0b8ea851e1389671e4da9794dbf5b21b616820d30734a660383a40430a27d91d19639bbd9b5665018d289ca412dd922cbaef4db95038870ca5e468067172deecf1830c1a4f388e127facffa432193c16030d271cf62a64ee6c968a7ad28463ceb649b265ba0ff531e1fc1a3349c24bc7efaae440e211060e1c377019a481c6124ee276fa2f2a5352df2be1b8499d7fdfea08697b120e4ace2446a833932c9320e1a831e9aa135350ba24f511ce257d26d164c83eb1c44638e5e649b316939e49e1221c2dcc4932c42d98184e229cafe54491196ade34398463fe36cb266713c2e97236def2e95b511284b387f8d094a374a70a8483d0d46725c88911df0f0e3e9724953ba3e183d309ea3bfdc47c559a62e006183b98d18bbc34c59d686ef1e218b27ef3a2b1c410c1b801c60d66ece23442647c89b1f717e21066e8e2585a752fe6d5b8a736337271aa133bdd52b198470517e7b50a5a830c3d262d734498718b53d0173c33c5d509d1dbe2287ff94dce74e7dea3f7400ba3065d02bb1a748fc22158418e0fcca885f2b33a3a67333976f040333d98418b73bbd58976bf5a424fb338e5107929a7a5ae2596c5712b777f668d17e3f4589c64cc26dd246b499292e43a7a8481b0389c3ea9c49ce65fd6e82b4eea4ed7e5a9f8889a9d8f19ae388ca83357bde07341dd8ac3e912f584f293e52565c70c569cae42c90e0bf67f5959c549ce944a926933236aaa388c7cb14d82b852a6a6e2ac15b337bc7f8a9d507130c16393f8cbab1cfd290eeb15bd8412839292f403678a534a16c32e979284df121be8782ec57943739aeaa928f73d294ef1e545b7b5bae41c85b12fc98bf93413c5e14a5ccbbf74cbb47e284e42086b9337285d721e3974043340c1fa499374c95675750eee199f38c8f42deb660f1e3b74886086278ca752ac2495954cea44698390e1f7199c386df586d2e8a58493db91199bb82b8612c4590a8b6b2251b521c4a82d1307f950213575d3659838dab95e36cd277659b9c43188d7fd7dd11035269638698dfba2bf412e2e5f89c37d493fd394dc24497044cdc7ba6006254eb3bf974eb62b59d2a86c1247ad0d9e3198269d412389937cb92e9bbe93fc4a1889c3b5a674217a29ea0924cea15164967ec9be6df8889394344c9756e8d3e43be2fc268467be0d6ac4d92f337626691d23ce1a73c53b938b385ac6bd8d15fd5afd55c42543a9c5aa3549c46983cf6faed589e984117114bfb7112bf1d486cc214ee944bee532dd0a726488938ac9d4d2955988e3fa98ee55ddc6dfde83470edfa1438c1d3a9010873729175e94580671be132a2374f6eeb762411ccf94c50813be84f92810c74b62570c3ad46b6e538e1d3ef223c7cd00c4d9b469aeb4db8ea8d5a07b1433c18c3f1cbf922484f01f3dcbc0dd07fae198b29968fe1b9fe9ef0dcce8c3514ef49eaa1294d49234a296a306bee36ad025305782197c388aaa60b28c3ec1b265c61ece2952bc7545ce46fc33f47012524bc593dd16efe433f2701293be9d86ae2c1bf5197838a705ddfb3f1a4de5b747ce3830e30e871d1573995ff74e663e00831976389ae6de3d79a2da359f518773891ad7e46a5e9ea6eb6e84a1816fff40961f33e870329d79e49cb4952a26cd98c35183f44c616230d1c2b5a3cc90c329a824f2f64ed2d6a3b21e3c7eecf6f01f3a300333e270cdd8b7b670389912574388cd1a66bce1dcefdb59394ed2e3a53d61861bce176cdfb3726511178da855d98c369c2c7f946a57362defd970cebeb4f944680d8663861a4e326ab615eb509683e13a7a84813687196938c9a541c5cd5a29ab190d27e525e6fbc7a4f605679ce118e4555349f9554bccce30c349098bd14dad9fe612ce2843e95ab4284177f74f06e3ad578468d124fba1638c20cc188369b32a975b0c8b8a8173dfcd07560733c270aa929bfeede563c2196038891e4de65d172f5a72c617ce27a950a559e385e3574939494ed6d3c38c2e9cb2426c08f37ecf1b6770e1a0514e50497783aa351951cb513c31630be78c31a937ff6644ad52f70823ed28e3bd0437c0b801c68d1eee19b0195a38584ce2e51325c9b0d5ccc8c249b869851f1d9a195838254b622e9afc47d4c6e0d103a51c37e81186084a8003c75916665ce1784209e659629edc57ba47191d8219563878e5c93ef91b1d5667156654e16852927f62ab269a34e2c0910685195470a38c16214fe9499a423a546e2a8dffad91023af3af5850d69b3e2bcc8882617eb38896fbda2843a1df20fab5549c493561c613acd2703188a5149a5953a8de69b6962588194e3849929f8ca741dd2309339a70f82f25f8a85132e1a8ad279cb293da5ab7259cc5c4944eafd204a1d2cebfc47d5ff624092769f46dd2994ecdc68c84c325e937c54b594908a1231cf496983fb615ae1b34c2c1c49d5f0d2ff1e4ae453828b14c4fc3888970cc12afdfcdd4284d0fe1943a4ab42427936b2684a35dd8b13bd3a5ad0ec2498c3a352a5c09ff6006100e8d29fb6ab696e0a31fac1eb2f19a61860f2a4986e56ed40c05e8c549c985d7ea2b7b5126bc389d987459ecb4d899510176713a3156aaf04bf927bdba38c6ccc8865993a6f4a963a406397ef8d8a1c32e51010870d4a000b938f569baebad5efec87171f0be8c9e5d792c8bd22dce2347a9b3bc0b6dc26a8b830891fd55b2415fb702d4e228226b4e53d25af7cd0a0a400b3ced62524af212cd32dbe307195228c02c4edb6f29289da5a465e934060590c5262c66a8646eb39758a8ab23b533bb8776444d4d0f0a000bc3c1a300af3829e9b5620c931e1b972b0c0713a100ad28002b8e2987c566523ddb1eade2b4716d4d2e2e89ddb12a4ef1e226399ba3e4089531c61ea100a93866d70a977f458e3231a8389a50533954b632cdc929ce973bb64c9062ed5f39a100a638b7bb68c915e36a4aca48978408e2247f5bc5e6eb1d3a60d0a3471203c60e0edc00c33d033cc2b801c60d1f1670cf009610ac2087009410128853484d7296b4101773e41942007112b3dc9c85df60716232e40f87d731ddae3f77ba8446d472e428a50c217e38a694dc2ff76817f3b410d287832ab952e4e94deb22b6e3c3d9b5bd534b4596b43ac71e0e72662ddbfc2f88103d1c73ce4c729bab9246cfc36984f6bff892848793be7c714426794734bdf687903b1c94fdf6695c7ac68d1d4ed9a1beef8230a40e079992f0a75dfbf575a943081db6202c36aeb6b8a63715217338756508575306090f319085c8e16cf2fc6430b9c4768590389cf5cc524aa325e56317110287935ca34de593c3b2c738a266831ee3876bc0072242de703c61f51b5c93d01fb78709c01b42dc7092d56d2c653e493c31d68683166917fe64f7d831b94184b0e16ca3f6e29da0adb91d00cc10b286d3fa9c8ceecdcdf6550944881ace275c4c5a4d7e3d258f1f068c1b1cc081a3478b5146084400061837788491af0849c349badd11fbd39524137d313c4b081a0c070a39c3a95d2e2cf8260bd973f8701f3e72240ef80fef91fcf01c56a4106286938cf7a5c41c27ee3df621a40cc7ecfc8edd8b3bf94c0ee0c08103478e588490e118fc44ad8ec6bd30a5319c629de8a2f6f5ffee440c07b129fb82b818bf360ac3d98431ad1bd54f545843c070f6ffcd3b69c5f594d0178ea3e5449384fb5971f9bc70ce603a43cc25bb70ca50e29b98294e5bfd215c38a8db9482923516b285a3c5d25a954aaafc6f8521440b0721cb84fccf88e675b370caa0c4495a5a26aa5823f04208164e55569684d332f7357121e40ac7d42688d8dfd60ac768a94b8c6ab40a274ff72f59dde493926495e00618378a043746f0811b629031860e63215438a6e68cd9215f52844ce19cfd269a65b84011228583ca21644afea6c22a33a26696810b89c28540e1b0592658653ae924429e7090717467bf690b1b5b10e284732949df429c24138434e1b82a9a4a7a0b9dd9472b8430e1b849845566bc24f534860859c2495295f425e9f4c4a8b7e4184294707ecb4aaaefdd6a991292849324b37d5a3619241c568445519996239cb2ce850affde17428c70b6602364bad8b020a40827f9db4c924ada5421840827a1c4ca262d49c28413154286700a27871a1b6d4adbe9236abc234408214138d70927a364654d551523040807bd7137282b9919c2726f08f9c179e6c5c48a59c94e0a317910e283533e53e7e3276df9264dce01a417e7cf7f0d2adaab415b9217a72a251f328a7a2f2905db00b28b93ccacbc2ba784c9751c407471f612fd33a924a1de3a482e0e7a3f9a549e982cc606828bb36c9fc88fd8b09c1709406e7196d1385719d4d9670c882d8e6be5a9765a07a9c5296f854da29a7d93201d51cbc123951980d022310899e2040fe935cee21c4abeb86f72eb5b09fe304b1e20b2384932a878446f84a10131c468306e84f181911f3a5200128b93a8311b4cca098b639f893f66c13b1ff60690579c45ac64a94a21636d8c2b8e9ac34dd5494997c9122b80b4e2a084baa765bb9c60312b4ec2377f6564688c6f0f026415c7db978d76416cd42f2d7f0051c5b136e8deec88555271944d767625a395829211b56230e86181911f3a52b043070c74f41001af00041527156b64c6744d5aba8fe8097688e02ced28630c1d1c0390539c54e55035c944173b494c7158b5ab91b1f4864a651a404a712c49657ca816cdb6162505bff143e50b130d183c7e0707ba00328ae36b4e8b5352a8cfbb9c0144143a0009c5292cb8c5906695f48907028af389498bd698aaf7892499d51a26698962a91f3e76e828f703104f1477a2a2e5a12bb8236a3f748ca1430c3510dc00230537c2f8c08d528c8174e2e4256d595e1bad0184134755b91835172753af5e00d9c4295cca7a7a6397d476324d9cbd72fbe8c96413403271520d63e2090d260e3298c9aaa3bc33d606728973a6d09542cba8c918648973665e3a517493142aacc4955258b2d8943ae6cd434bfe36f165553e54cef86a004289f365cbdf716f2a9bc429cd5aecdd51aa9298d3c0f146181ad031068f1d371083c708dc33b0e7230320923849f2c49b58b934cf71a043bf001289e3ca5d12db6e47b4c5769cc07f74074020714a59369d25495b4619e5b07cc4d194fa2cd9d64c501a88234e9f392b9e9a3709208d38a5aeb10fa94950529a11c73e498bcc9976110771319d1a97d308208a389dbaaf919333efde8938bdba8b18134c0ad53f214010712cfbaccbf7df2a227488a37ea5de5ce63d635a431c4b54cbba71a62475152c810029c4492e799794521f21ce9a9a4fb8112a437316a369c0a30719641435800ce294562d2cc58908e5b7208e9ba133a2977ec7440371da6b8b333742fccdc6069e769cdd010410a760328556db3517d383c17a00f9c349b09c7117b2b44ae47600f1c3d173a4965d9e3c4a2999e006183870e0c07196438d0f01d287e3f965b4244fc6acc83870a08f1feec347973a80f0e19c21665183b8a0641161a0c3475601640f273f498834a924d92d623d1cde74c55872e96a13d3121c1d40f270de4dd7123345450c207838f67be8f82d490cd3271840ee70d6d9a4a2572e9b30b69501c40ee70d966d2da874fae44f05903a1c2e366a65e5cdfacb8da8e5c82780d0e1e8154cc9a4f5659210871340e67092044bb9d08d8da8d140c71140e470fe362549312396bb4f4b0089c3299e94cb73e9672b1240e0704a5a339d4a92fc1b4e61d46bd7bac363476e38cca971ff525b1356d486f34835414e5f98177db3e13c1bc65daec33493bd8673e5d5a5384956c3e1376bd2d0db27a846d370aa9355b7d35a56e4060d472f55756a41e68c3acf709271a28b491bcd7050139e65cafb329494e1e0a271ef44d1797f92e1a4bccda42acba7db311cbfe48cabf29db6b1a30188184e96e7d5ae73b35be6c370f8caa65b326949ff1d0c271d26b309c24ef469fc0ba7542dfa7aae57fde25e38fc96e8eac68b652675e194d794b4997da3ae252e1cb4c589f15a42c9f8c916ce9b525af432afee4ca285d3bfd9c8b552926c1c65e1f0756dcae384b0709e134aca994992e53b7485c325157ed3b292f8695be198b4e7e4cb262ec8acab70b4dcfe37df91e1aba6c249c8ec8e6bcafe25e9148e3e52d36a848a495251291cb36b53926bd3289c949e55f3711f179942e1682974896661a455de3ce1a06e9485a668bd76a6134e2a65f253e363134e95c45d7ff9ad561a99703c75269c6ecbc91bc4251c4459d0352555ab9d14251c93f80e55b97ca7c59284c3aaa6a0728957d738124ec9bafacced231ccd4b1052b3093ab3c508678d233c46f684915b114e3267cc8f2ab55c2923c2a95438d96eb3408670d2fb53fa4ba6a494aa8c01228493ec6c3bd19a830148104e3a476c63ec53265534a20d0284a38990ad416ec818def4838376b90deacfa4ec1381f8e0a46c55833c396f4e6869f4e2b866b71beb6450371e0d5e1c3ff4f66a7d75ee24dda0471829a0b18b2c9c5965aa7bacb6a9c566404317870f214d9d4ca67efc948ba3a6da2fd512c6c5f94b8cac5cf919748c346e71d0a3d48c8d88aa1e255b9c4bd693ac2451b97227b538d90922a3496baa2f515a9c82094a94bfb057fe178d591cbbed629dfa8a6b5a16c7e033aa4ee566aa6a62719e93fa2c73f86e8f098b93587b1aafe3bc55c5571c6dfeb269cc6ae9bb42c31567df686eb56efed79a561cc5ba439332bbfceab2e2941feab4c59fd9bc8aade234dac48af797bc638252a0a18aa3a9b1d798e4e465a9a4e2e4e773e25f0926dc8c8a837e91d14564e8f5499ee2acf9a21a2a8eb7756f8a9352e96a5a3d830aaa2fc531d3bbee9536bbe41529cea1e69a54adc995ee8ee29ce92d73a15772f5258a8312134753a8249b7472a138d6d8b949e99294428b40713eef2b99d64433d1449f3897feb656d9ef2b95d5010d4f1c6453fafea89243df6c041a9d38a5b025a951b17f42c938712e614aafc8743293a83771ccbeba09a9e6edabc87912e74aab623d324a29314be2f49ba792f5fba9d27124ce967262d21b935082742171da1d4be2eb9aba5bf611e77d6b97396d5ad3461d71d89a9197c433d327d4469ce43025a86bb6d6b01b461cc5e413ec4b83594a6216710a9644f88d65147150f1daf256626e93c9441cac2437f13443fbc524228ec1525f10b1ec8ad1fa18448d439ce4b2af5c552936c91067d1f551393119370b71ee102a9f4979d2ff9884385bc888cc2afddb3d0ee2202f575dac25b1510ae2e0e255a2ae64184d2981385cbecc68e13b56d400c429732c5e6cd396b9b1d6f8c3793ca4c5ad94b2cafb236a39f28cfdb0819715d4f0c3b943b78ffa7635fa70ee3929689162a142cfa3c7b5a0061f8e27c336ec98ead7ba20a8b187631284e54969c34e09170fd4d0c369db4e124d95ce64328b066ae4e178e9d428d962497a4e6944ade0e1a45d4e8abe1da63f7bb11a7738accdc6132f95cb34281135ec70d66c799358d91a753885ffbfb2ca1153d1840ee75f8b17b61a459fca8e329e6d50630ee7924bd8aa144c72388c9ed871b741edf72c83470d6ac4e118cdc26a1a5dc2e12493e472925552eadb1d3ac678c34197e9cd08559933ff6e38c5b8da9755cf67d335a2b6a3471928078fa434a8d18693c52e6562dc68a2b565439d39b332b2526b384951297d983e37d5ac1a8ec1b64b34316f92ad13428d349cdf2dad8932351acea5d93793b0164d9ee1a4ff37d4697eeb9d6e869398aa35dafa5acc8c1951cbc12371106a94e1945f6d94523bfdb241644057988de638078fa43fa83186f30925aafa92789eb9440c27fd5695e4a93a499b65442d078f1486ceaa3559dc7032a8c0908fc85841563458e30bc718da9d73e1bc440d79e160f1cd92e59f6c92d4236a5d38c86c93f5d4496a35642e9cbd92924a4d670955d5885a0e1e297b50630b67ddaf3c7a37a9b44d1951d3410d2d1ce3854bf1f963164e27a5afdc5c57b7760e6a60e1a02e9f50d22d778593768bc95e7194d49d8e647c50c30a07619b67c7ba1b51cb4186f720a37050a30a47d3b651132e448593852c4989269bc249f64a347529164a2e49e1b431059d9f73b7d6658ea14614ceaf6175bebaf4c68fa0705059d31a5434a9b2e8c450e30927fdd5e5be49cce65772c2c937f454aa7f089371130efa37428be8bb2499cb84c385fc56bc5439ceca259c74fba594f2a478a156c2d1c4726f893125e15c2c359270d02674c3f4e4cca2922ea88184939b7f2559724e4f291de164a743c6d126e5306a18e124be79dd8b10b727adab428d229c524d4c47e992d67f27c2d14444a970c1c4f593b11a4338d89c1e9b2bf100193d46a9218463a9adb2350d67b5e11a4148ec5ab12bcd2540386cc56ff9af669297fec1c1eb368414939fe1dc1a3e38650e9984cc6ad937687a71cc7015194446ba8cc68be3aca8c99c73f2c478ede25256a2bb7bd797ba38e90aea4dbf94942ff6b938c64bd16252169a6f755c9c749b50f25527b740d3c2523c0b6d9f235b9c2fdd8965a2788813bb166793e5b4fd69ada512438b73aa8f96bb70334d29b338c93521d79915277593450ebe88c541a85829546a3a2127068bc3d58f0aba9b4df6b45e918812ea62860ca57d7345257c6d2593fb3209ca56a4f67475c887669a15e8924ae782b2789372abe0b32dc8e8134f945561c5984c351ba6928ab734495d4a2a2b11159524a5ca1593a4ea1455dd9694265a668d294e426bf5dd52558a539a3f7fb564fff73a299075763d323a8a649e5071fe4b5414c54992c28fd8de84e2a0a1534f6a95676c0914e736252a68d77cd247f489a39b8951975eb24c903d718a5196a24b2cbd7fb29d380933b7ebdf52b696e5c4f182126e35bde4d7db4d1cc4e43596c628a91ad5c417974c9538eadfcac449f89c6c396f76f127260e6e7258d5e9f112a7ef0d1d1b2ffaf3522c71301551736a2cf867a51227a9da323a739bbe3c4a64da624ac7c618fb491c752ea39dd85dddd02471b098c72bb65b086b8b0b5f4402a573b3cd0789746cc59c4d23db233fe298ebf26405cd2d6a57471cf544153de1ecce47e50a5f34c214a7d154d7653c694664e26c64b42f16718c593746eee9aed35f11a78a37d24f584961e54fc449ae8bbc2c3e22dead2db5551de2282e27962ce233d47b439cf4a4d69cfebea54fbc1087f99c911bf93ee226c4f9c3f624715256690771aa3b3d57d3a197a28238daca58de132fe6aa1488c3893f5994204d554c9200e25cf5df3da296362bc91f6e3d2fa5de27ae2ce487f349b2963e312b9949faa0ae9598f6663e1ce498a07ab9234509427bd04ac98c17849be0ebe114fc04dd1a6f4d4911e5e1a0824a524f9ccd1356c1c3c9e337c8935bdfe178fa15542ead14b5b5039b2bd6af4e9694bdafc331578b899f4950b62b7f41878352e26d53933c879398b09bb9733bb6633914e6f409224b1451e37034d13d37b668e170aa72132c35ba6ff852bf84954cf25609b9e16849ca19279914dda1b6e1244c9d6cbd49f3051bce9b16f4db74ec7857c5b0f0c51a4e4affea884bf15ad7fe420d673f1d993339a59468d270c5d12498a445ec97341a4e82878f1825ac9b6cc9331c2eb9888b41bcc80fa1190c61e95edfbb416538cbaa785c2c0b194e6b314993b7b4f9a6319cb2626b8effba7dcd9bf085180c6d498edc8d26a5240cc70b2156dc525e4f76603888c67ca9499029c9da7de1f2dd93ab3754b495bd7032499338512fe5d1b375e1342a96b75ccd5c3809e15e32eea6b233d942627b267a2869e1207696ed628ae1fe962d193d46f9220b0791f1f22b98e82577341083870fefa169b1700ead69196d2dc9d9e80aa74c1be553d72f0595f263878e31ac70364b2273cda9fed574154a2563668450a1c231bc4628f95d392b86a670b0b496417ba752386ed21fa14ac62c6aad11b51c62f40f1e5b852fa2f0cfdf5b1295d79437f8020ac74c514c767d450ca4232923790ffdc1174f385838edfe118b79591951dbd1a30cc429f8c2095fae6dd3156dc27959c39e1c21365e3321794f869d91abe32ea1de1c26e6bf9f14957036e997b48b52a1996512ae2c25f7cdd98504334c5013db44b9bd18bb1186062e006df8e2086fbe4c934992ae67f7889a11d898efad2ca593f5f61e89a71c3b3af1c154f8a208274d6a829a8dbd6517044651f00511cef95f4aeeb194a16733a28666021c5f0ce1bc2af36bd2f6236a4987183c7cece0014617be10c271e35b123749503d2abd2f82704a82bebebb4d621e19b72b39be00c2f9945712fed54cbbf7236a37e8f1835ff0c50f4e258e12bff05b329e94e165cc80055ff8e05c61f44952e63451213482013284f4e278bd6b166a46d789bb0143082f4e31cdeefce98a6550efe214cfc47449c9b8112286e842d750cb4eb39439b3773dd4fd6fa8890e1f3b7424244272713a93afa1617c5c1cb34f0a172cc6c42bbd272bb738882e93e42825ca51d783c70f135ce0870d4ed9e224a35988e550a24df4b53809db9a36349992d6a4a010420b1e3ac08840c82c1c10228b584820041615f20a31c468305cd10a0884b0e20121abe0f13b72e820830121aa4840482a50718a901053f4304048297af88e1af8f8f1212840082972e820c3029eece0a18090512c2044143e6ee03268404828749061811e2e831e3c52d08010502487010c3a8c08847cc287277c3020a4130808e1448e04846cc2c70142349180904c848460c246c825be08b10401422ae1c375fca81142891c3ac8b040f728c3023642267192f3f449a5ae6652b6e4700d8448e29ce157b157424817f5b709227138c1eddc04d9909e494d08818499444b05a5df0c79c42928515a5b2cf44c923be2a439964f851d8534e2b4692d9a4f967872e58c38be661d37e9a4a994c4459c3ddb2f5664869bd11471cc75628497102156de449c2d66895e0b15f3531d2304111772880b3184e128049bc475d150b29de4885a08215239393d4a4ed54e46d43250821b7b2a0819c4416c5a5bbc38997e8c388c9da871599b378ecc0e361671cc64a249375af389ce28e21445da9daf8929cb96d0602311c7104b254533316d2ad1da40c479f48d12251adb38c4499cb678a2643ef1eb2bc586214ef2651121e245c58f4921ce3b96c4d45f4c09360871eccd67996166846c4503360671bcb0d911fda92283cc86208e962788106bed18360271ded4d061299f302966c4b0010846d8f8c3e1ae3685504b0736fc700c32a87f496ef7e1a0b5dd64c9371f0eef5ea53f5efd43a6d70636f6704aeac5046de9f461430f474bb2b75cc89e8763ec934606252831bd573d6ce0618b765afd95541eabcc1cc1c61d8e414fe69d89f2954c9a7318156cd8e114bd66ac4da62418e8f091c3f260a30ee751b24a92a6b2873e250e1c5fb04187737de974958da284ebce60630ea7aed392df24664ce5700ae17fe2a799ecfa31661c4eaa4992f642fe6a3ed9061c50392b6fdc282a2c031e3c46c4483aee0461e30da7bc76a61b0e4a7ec92445cb9a62fe236a660c36da700c71f9c345058c911f3a5220861816b801c68d1b60dcf86103ff71b02c6db0e1a05d94fefa4b82acc86cace1bc2bfaada4bee660430da75793c2d6e8be932ffe061b6938a96821faaa44532e97b1c1061a4e42e4d644681fa1b5bac1c6194e97e3677596d6a04b7062b06186c3c9ae6962a89db20c2ac341882ccdedffbab153186c90e1e4262c54f20afb4c63056c8ce1146477652879e33fbf180e62416bdca5d91b6987e1a0a5672efa520996c460387c965bd2a1c4ed49ef178e15e7d6dc4f975c4dbd70bedcb0be219ac208958d2e9c6c376f6e91f976e5c285e39ccc0b55252897932d9cfb4325ab4cc94d685e0bc7643ff657c2cfc2495daa2429f977f2081f0b07bb186cbd466c25bd5fe1242d84d66d3eb1c2413688d1d2ae4b219454e124b5da9aa8a1840ac716b92e76f2c3db640ae7ce946294116b92c2c952e3c7943ef597465138c8396d628d1a0ae78cfbef0ff5134eca4a5d52a26925df76c2498e0df72b41998e8e9b703afd264ebc8a19e363269c941493e34adf8ee6770907d5247d6788a897bd4a386fc9258637bf8aa2d9249c4ff8d47491e14767160907592293d0131ee1709adda3fd442b25b746385eb8f46df395b52ec2415c9297c4e2466511114e9954464bf2af7b1ac2595d7ded5d2fb9c74238ee5b3a559a47f4448370b0d8f5515fcae49c4038956e9d96d899b13ffdc1495025d637bba63caf0d1f9cccbcc226d1ecc539c734c689ba3711f2e294c4a44c459129cd5d767152c25a6a9697a42c4517870d8ba92fc86fd1cee4e29846d6dba48a312931838b83f9a5097397ebfd32b738697606edb72783db698bb37db8e5a573dd18cb5a9cff24b74ab2093d494b5a9c6b735b8ce9620997348b53ceaa46313954d819657138493a69779fb94e0c63710efdd9142d85b03805d1ec1d2a2353f45f7110cd62624f3583dab8e2b061b6a413d38ac399e5f9a6aab86461c5a92b7b9d7649a7eb2bab38f9f7655092d2a9e26cb1fb3ebf74a938ee28db1437e6f6660e1507934eb444469b6ded4e71d02a3a7231dbe44d628a63bca4497ba388cf6ba538fe6b98785dcd925a23c549e62f51d3368a63d6e952b1ffd27d2f8ad3853b15beb2f7c892509cec459d247d56a0386cb8dbe833ad749616f9c4b9c432e9ae37f8efd8229e386b4cff4d7e72892e428c8348274ee76f7ea94aef5728e5f0d1653e2c50da20c2895309a595b4a2a56f4639600232ca488e0132ca489e74132771cc4c543579a2a58906114d9cc63609215e34c693fd1944327152b13108a1e1ffeb3f260e5ef94a5692cf041d3233885ce22896c5b3e4e6d13c6f8f8343c412e72c157bfd130f4304229538267191a9f7e8e1a7042c4289499c543c292c2209c3110391488840e2441e71161fdfd465ca54d88afbc023e28863e5ba88a705cd273870300f441a7130b1d346d52875b308238e9574e38fcc380f327c780fdd80c8220efadce4d092414f7c8031802b8828e2246dae50620c26e5104a228938679fcb4932f4799738a2c6271041c4d94ecac6294934d49a50193e700422873888bf32f1aaaa1aad8fa85d08440c71d81857a9647dd7a8231d3c7e14e254fa6b2ce9498893f7d7ae8885d256ea0f880ce23866db5f493a11c4d137b4892d7fa1fbe2409c92d25a313aeb4ba60788538c3bf6793a664ea9f60f0731339b318da87e2b89f8e1984f972c1ec2ee83e1e0c389ece1647f297bb8864d440fc7b372cb241afa1a6f913c1cadeed384d91ce284d188daf3c831e2a337802a8287f3b98931954d9bec93bdc3492a5d72a53ca49f9df440c40ea7fbd7ce2cb1ea707ad7f495c424d992b0a1c349d95e36f537933775581099835136a3566a05d79896c238c6a0811862c8e168e9d2be9bbf011f37700fc4e1949d9552aed6cd90aba982081c0e6e29e54a3abbb69a7ec3593ec6b2896174c3513d44c7975e4bcb8a481bce99df62ced718af8a89b0e12408eb16d57b5ac3b162dedd3a29ac8663da4925941edd1b53af0591341c356979eca59c2af193418fc1131041c3b164daedb7653e419a233e7a037b64f4181d1039c349db645072e84ba5af34a26686f3895768b5e9de3291a1a30628528683b88ed025efaa81ef702b531021c331595752490c71b3de160a22633898e09afc3f93c60922623896bc66c1ac92c270d253825eee250b13180e266f5fe946bb19113741e40b278bc154126374f589122f1c947585fcd9ec9420d285f38dd21fbd244d64a8b8700ca7753299ac41fa7f754810d9c2317ea9be9a50f11144b470d8e82698f8b61c0b276288a143065938f6089949dc0a9555d458389a459dddab1246db39a2a603912b9ce2afea553875eac46d8563bc544a6534b19a665d238854e16ce2ea58ae8a51e1ac96dbe7478e7cec4f0491291c6f6b468653adce3c91c2495d9924ea496a49ce140a41240a470f296795d6dbc3741f50386869f6e656cd39568fa8390c9e702c31336553192689b28938e1e4966e94e46a526e4c36e1a4649b5b5c90266e4c62c2d94c7d36469df8891808224b385f9f94f9749d5896f4818812ce59b9f9cf828673209284c369ead167654ad46518092755ba466345b30df511ce7b996452a5a3ecb26c84a3eb9dac58e262dc707d14e1b027353d344f8d5022c241e993b444ce87ba4c1a511bc3b506224338a8e97b134e8a5db2288908e12826364be6b4a0747961f81009c2f92b2ca68d975485460111209c64ef8cff9cb064527644ad0c911f9caccee204add388dacd40c407a77c23bf24d37c2f4e266bf4b33b99c2cb548317c7fcd1a5279ce977d8bb388d8a8a7b69837e06378c92821aba38a8afb15dd924698f528d5c9c0c6ae0a2478d5bcca0862d4ef228f1532a0521d7e223d6e3071988a8518b53d8df684a30ab8ca225a1062dce662136b2e29aee9db11338ba4183a0ee878e1d3a769491c3d10d7a8cb3317c8c1ab338b66834a9aa6d2784892cce96abfaf554ea9b4c6371b638fe1d4a75c364ab018b538beb6831517fc53149566283ccb2385a75c541a80c7361764d6d50a9d18a831675e1d27465b62449561c83922565fe8be145a85ca8b18ab35acccb5ccc772a34559c7654ca55eedaaf50928a930575cabcb65276555071922df5242989e5250695531cc3dc49e16c839f0c15d730c5f9a4594679ddd83fb2b292851aa5386b8c7ca93f19a2f48651508314a73799776b6575dab4511c537be88fa6cdb6297e75a8218ad3269942653b31e692261d6a84e22c1aa6164bd2fea21b501c77d34952a9b6fc1aed11b51a9f38f87de55c8a3b32cb7b861a9e38e8adf8d6fb6c861a9d38eb9624eb9f60d51b4f9c38a5b497442ce9b6b6a04d9cbf7d4c8aa9356b6734711c9fd760e278795c3e13c7a094709dc124499fe531716e3fa99494943c3fd65fe2e0ab6be94556895a274b9c529998b86adf9f49528963ed8efd498bdb6e25943849d269038d6aa8849085325924120883a15020068370c4b90063130820182c2a0cc622e18038d3b73d1480035030284c38341c242012161889c4035120180a0402824028100605c2c02028440ec5909ed303fcb3fde05fb915d990e90c854c6d3639e84c3e53604bad21586865d61b0ac94842f207c8a5d85fcc8db819e4cc40c8c57574fb27442ae4f040f11ab540490cb1eb0e21691e7c24cddb1820dd678333a0012468fd1a9e45c9b217a1b5ab5da525d3f6f48b6b8d49ea58fc92244aaf973df999e661e09fb23942227075e8081cfebc502622ce6d6b5f034b8fa70e7b6a7a940b7fd2f96d3e77f4307c7a0ff4f7295fc2b129a9dac89b8533acbabff895e1d6b0fd085b2ca05568fce9a956524b2b9ee47db431fc6263c03cc733a4449463d9bd45c75bbdc441150df471896b59d672c6f980a03f38214cb679137c4e72e053937f0820dc9bbcede520d8d379e8b32a73d008321780877d01c9ba950acd41c6593b0c30271acb6a87723d982c42e7ab614d3532370f461776ceca258a90b43789a90680766a7fb93e99e64c636a3e57ca75e356979f118f0ecb96194ab49f840dca1bbffa4ae641aefe95bb2804a4242705844c6f5aaee6c54bdafe88627b162c42e31656066b80d2d10193f5a0636abb8aae096ac1fbea2404bd12efe1682b3e62aecb2e4c4ad157bf654c8b02d9622c9f6271df21c70edde6855f033d3131de0b08dde11b48ab6ca10ed03b51a601b4936ff0cae6f612fc0ac47f0362f8dc2fca7b97b8f5847abf66d996cc0feabc5f33a673dace3d89be5645cd2a119656f3dc9a366693c31b2d5a8dfc7e7d23a335b08121fbe5315f7304cc986887f3cba500c360a7201d71377559a084fe21f22de555a6b752944bc0c011f6318a977e445c4cc268bec3885d463e505525fbb2be1fc7b491487044a902882fb5dc6a9d42c4e7323924c33cf33b605b62a410534122390a268ffc6d3fe82aa79bab3d227798d74d5878bde0dba2bd651c095ef6231e6137b6758ab7b727eb99959c3c62bca8c7a1606049360dabf9e6209b1cf5455c8d29f004ae8a29f08458eecdc6a7936e731584305f5824c737bf9ed992453259cd5781b23eeaec98aa1a45a412cc66881c912fb2d50c91ad9147725599aeb7a03dda3ae054a028ba3db769203bdc3bcd3cb1024e5c1f93aaea0eaceefc7c25be8ad38d4770c59853afb0ee89973805d511b406cbfd007083da70a9e6d9fd2cb3980d9bf9d4ca7e41dbfd526de18989d3c0d3756b5418a681f985d46bd9051a3bfd4b649a7ddd213d9f1e41409f06cc2f80c246562345f5075058036603d63749fd1128f0a448497d16b0b2e4d8bcb778dd615abc72bcb040830b061658e5cc6c03f7618554bc2c65a4a5e0edc24ca69751c97a5014d63328e0b5fb9e67ab0da8faa5335585c80a49b0079ec6b1274b286e4eab0af77cd008b80d6e83e2b54794549dfca7222006de33b45f11ab79bf92fe5b5e04bf64174378261035e89f7355cf7b338e805371705c69efe89e4b1254528234663c315b34fa32a46e972c0625921299ad8c4acb1596d5e1b47eabfbd76432a0146dcb8b037c322528249476401255344d63459e22ea4153242cec96751ac7780491a5ebeb5e1fe194c898b5c4ca7a9a9736b858ba3775d51a58751be1ae19e379174514e622af2cdeb98865724280a897c48ba2ce50cf6e4a54930cc2d78ca8fb3ccdf09ebc1b025500be596860d0e124a5cab2235e7a5ce95a9762ffbc2da9265f0637efa11269625feb7a1948741949d4a15b6cf5523bede8e8616dcda18d25d1c72c37800dbc26420ad56fd23b3ec01f7052e21bad9d5361a158451d072aa5e7f8846aa631404ab4c92d65d051d1226690e3a3bc463d20ac21e80bcd2e4a5193ac513ea3f101f95e2296240fd362a79e0b7b9eb280358b913d59b04cf60d7990f2cbd04672d39308ec2cf4029637936cc2518c63c90d3f9801474416be20c38ca22416a205aa484d790e2af39ce392937a065ccbc74187384e55723a16f515be7a7fbd33fbc2d25c1d98da906f191291be8b48621571dad05fd5d00459e03c24c84762f10a4a4d882f5c3e1eaf735f1cfff786abebf8e436e1e6dfc77883db337006478756ad203dc1d0e2b95905eda40ed447237d2e08d30d44a014119dd24a804b2264fbc073749601905682280d636a0a9e753bf45cb3cefce43d52cc3fc6751317f897773588a24e7e573ccc573d4e44ca3188e617351ce3dcb4660c7504e88249cb87b25dfb22ee8f96d4e12653a8d329973ae0dc402d9d2600a7a40ed2b8bb1034aee8c3d96d6f3c622d462da7193c8716a3da915fe97b159f8d420701780ab0157d0bad98b605e472d649001a439932e1e3576fdd20b4c586a4b75e441d4285644344bd047c9a406b1e91a402bf6c624a2f99fea1eb94684071a07c887246496d08af41c6cbe299c43e94a4059cfc2fbec46e37ba68d773937d4bc6fb66c23b5eb1f73d65b93915d78f9c61352acb45d5a9475838bbd8c5b7f909fb3d7571bcc5844e7664791f4b6145c718be32b22cfd87efe01f6b217889f05e63e59072084cfddb7809a2e0dbf32afe4100cd071948e2c1637a40bcbffbdc3b1c5ca033dda5d600891ae6808f157b41efcf3bcbfc0d5c86008fcaf515ec118d52a31625837e51e0927591a584898580cf31c4f2e4f4a1ff710a2a14af5015582e8e34eb84e260598d3419a2aeae8335b854a2d516db15b03d88481087b2efe3ffe064df949875bf53a50662dbe693d49cf21b1038d3cca9e6b0a964a5abadb2be84a2d41ec2eba2dba8872ce69ef13e8eebca34f26aeb4099e6da891d1ec35e905f853973afe7b4d83c903a1e21f4db2c3e454a8f248bfa9aaaf917eb8a930c3838d33aaaf9a356e50d4e9c3a6650750791cfa646effc29a8ad6fb840e8170f6550ca38350aaf950d1e073f55cec2f8a05e5c6e6caeea1e0fd763b3c69f60f0296e61ec82deb0bcd87ed2e243fce21859eff9a32e99fc0b76e032a1771b3ad09297c73d6a05d3ca8b7e104bd6c201a7e9415221889a07b3d3f438ff9178e0acdc091e5c8fb63a170c657c260a1edf76651555416b0369451dd20c522b7ad2e065834dceaa39ef49d8c319ef3178c70fb0e0d8c3cb2422aea5a2037279c4526f568f809558068339402402904621140054286e246f7e6c7388b56ac8a1a957762915934586dcd80163c7c1079778cbb795bd84cf949cc0dace8ae12079955a8daba162f2c353df3c7be4fb80be1f9655b4ce977c761a79175c35b9f2a8774f353865b180584c9625eb3011c9f47249232f9071bee772047240862ddd8a153c049c3918dbeeafb275081ac1cae0d0f3ce1b3cce8e49b48c7cb4b218cf57be23dba4dcbbb944f0089517357dd114efe5635043a2a881a13199729ab000c9ab6add337571eba2aa5ba72edbba5c7521ea164e97aa2e99baf874abaa8b5317f7ea66dba3695d69d6c5a34b3674f1bc8349edff48395dea38ca31044ce530f1fe527b406dcf9dc3fb2d12ef4d5de2daa5cf5b5a77ebb3f5e2ab98bf84484cd082088ec599b30366a4c0b4b2a3bc7d585f81e52b0760c55bdb230c925aa33dc8c129fb5d73eaf6b7b6b887008c0af527f0984f408e0536e7b15e94250b98a179084fad1b8536b9c71fd1469403821860c7d93c3e309ef87e28013b1339caa526587916231756d87d3329db624bab9216c27648e1b748eeced10621ddd0df1a741345270f7e2c46f05f0239a2253297d2f95a450777f0dd543eeab1471f85c5b923ac048eb7bad835b3d8e4daa6d9b8d130ad0d51b58cc6ef34d3172d12d080abb87ee0f5333fadf43e0a64a591438326eff881b26bba8aa7ee76fd4123b271863e5921c2b7f44e443d2cecc54f88428b0dad1d6d36c50d7db2c2ebcde6c24e17b355c642753a934105a9c75cfb5ee12efb083a2643610b8b383f874a059fded699c7909b86880a82f631b6b3b1b6d9992c89c91bfc1d8c3567ac1c9d712116523ea1219f2534fe85b33aa523b30045ef0705d897fa3fa7e3adb0472e2155aa230ab8238b1afd59c08201e18ccc46760ab8d814f67b5820a617a2c2ca981220b07d8785bad44961963dcaa58eb38054c40634d7f806a88fb006dbef94cab85f15f7d0bd63b6193cec6f1d853a6af5889e10165c058565339580d1fd9c668d71e041a264b27a05ac2a92fff669a255a1837006691784a3214d1141311c92262c77ef7953145e57baf98bb378e0eacbd48f8bec435198b307e736c771d110e0573eebce11d6874272b4d19128267ad95bc40025952e33c46a844287771f99eae04c613d538a62037806bcb94089dbd74cad9d1a76dbfca9427467ab22def00990d798c6f566fe9f74f42276ec2a5d6ba3fc0b5719fd83aa97d13790a1153cb000dd003405da2690e15dd8440cafc27b500713be0e1249409404320f0d4cb66fa0826f53c6ba3e2904324947600876019707f86deba972459237dd92830432cbe4ffdcc3c9e6a029700dd160a0ab4050ac9208f74d0180f5de0904219a4fab8c30f438c5d1bd5757428f3e4e4f00339cdda29beebf458401503b6fdac5c00f928e3c9ba9d1c60f16db8032ff282d21df7fae488bfffdd893788a3570bc57a3549b1a4becac015523480d6a1acd6de06285cf1a6e5fa7ce11344a2940e092031a25d5d52883a686b6061701daebe3e0eb61626994cc3d14783b2d66cc191988a252cedfa66c67fabf2543508d03d121402480259aae947bccc33d1c7b8ddeac8fcc06d52b349aa75a0bb5a76505df16b4130f4089592a4f86a767032f6065be94cf86b85157753585447aa469f49cc1953674cb811d237a771885c113e0196aad3094990aee46a2830611358500e264ac3cf50c35247f68d26cc1d422336cd2f4f627a5f92b25374073f07140230da5aad1092ca917fae079cfd0d50091cc017733f9bb16ad7473847d1a6af5b7e765da30ebb27dda269236781854cd406f0364374f370c5ccbf8ba033d841e879a8114fea902924b07dacc4e960dcc6c941875a319be324032373fc9d1845d3ff618b6dc96060f7e3ab43a1f106450ad05dbc557c8504b9e21ee69fbf4852a15030044f87f6ef90e0f133b6838686e2a89625f7a0d3376b6ce8d9cff3e260b635d88650121623bfc732219fd253f7a7a92430726c9b47785f283e790eda8817f80e35d2e6b4d3104f33cdea8dc595e7952e39d65aca8d58a2adc84aae3d5353b3195cd508c70ab5866d0dd1a6abc931230a19ef526a9a2bcedd7cec070471ae96f6b9b637002fe442042d03f5fdaafbdb3051d4269e02e63b921d91721590e2ae0c33e4cb6c46d7a101aae9f580b6b26b5d5c89d2d9fb55353c6bafef38264b6c76533599d22598b997803af9f9ec8589673adf533d36cd8c1c0085df39def9f8a540f748cdbb39b8c2ae2fddb296e4b7b012c0119e3c82dddec1ce279c690d7a3b1ccd9af136896672e2c817df61f2783c841ffa73ff10767ea642f801e676e9b27b72389e63154933b87eaaa0ee35f17f74ebce7f1a3fbf978a6108478459fbaa4c0790e08ae791efacd77f91c169e2e3d0ce87cb3b360f012cef74adf32226df9d5b6257cd7c601df06912cd5bfaffd36746e2376d80e2a36c285c046435d77a70abdfdc2e934ba1040e098ee64debebb35941bee3a154e9ac23f48aa28aa48959d920a779d8c28cbeeda52228ea789becc28ecbe9971508d0f44a4214ba3f0ba255003603e70883ee40096044a8771be677c1f408f7fc34205ba3bbe9545c9597a8b67c1ac4cd84063a80fb165f28e5a706144b4ba5e762366c5731e27c6b3ddc49123340c7220ea6ac679e64d72d669888af729ec6394fa202855335b8966b7f391106924d800016ff9d1084a09b14c7323479959eed6acf5445b5824ce2b3e047b0b67bc5dbb6c722e700ac7e5ed2eeabd40c61b989068ddfbb87e499bb4c3699f5039b82c89466e49b58dd879d388bc92f41e231eee416a4df634dd05843606ccbabec96dc538293ae6f25539376ca64a4641acc93715bdc00717055f85824aba56aa551d50d2c54a9b7a2a61072f9a22189849f1d04543699ce782a294340ca6f87fb027d19fa48c8def2cbef297d8b4b44542b2ce433c28eeb8b9283d82aca9a30cb95c91d47b7762ea88a94b7377eb0639b8122350bc51f23fd6eb664ab68390f946c7a999479ea388cf1af6473da4ace638f240d35b192810c4c5a0e4e218a1129cd7380033a926672a7d674a9afb662b8f10db9fe42248ef4b09757671a1c6f089b4b3e7b7537ce440417419f939f57e429cd66312787f20404a4904dc3a73a22899c138375de426d97160eee8bea49608f69117a726d4ac5ecb712b83f119b110fdb3a66532f7a490849856e9c6746db4f61a7a71197a0b38ff8d78cb6162d0ebcbb4815e1d0f7e1313c0e1deef67de598db894563312c5c9dd616f29cd665df604eb3279596432455c03e61600795c82d8d9e5a00915384097dde15f20023ffebe9cb980bcb10bc72f207d85717afb04a238c480c0c9026291d2d43a3ccd6705f82d236ec327107ec2a87d06dc7e6f0e939f6955064dbbc53662a01b3ef286aba5a98152a4888c1fa2236c3a56aaa4ac314019c249c7e87024c39687e3759142a665d0e3701c1a15dc435049939f12652a204ed041459039d33b2c5841934b65acc451f6a0f6cb8c87e06b0804eb76952b10aca1d4dc8ce6990987645f386ae5ad683f00f64927d4103c712c6542435fb9512e2c918361e651e97aa2d6c108ed4aa25920a681c8b4dd149856c2496159316463962667045900bafa21039c184451d363e2a74a957d39b067ec42ce5406433edea60b3cc190e6046411ec7d9fcb5a92c9c8748ea87ab8ab92088946cebe03d1301bb8f8c0967a60d853669772ef2dc281998690f543fe4d800fc8c19593d06b9242157d41a424f964055dd15c391f871beaae121493b9a5888b61739daa6bc5ecba227123246d5f368172597cce72b526ca9769e0753e35da3ad8e48fb2eb274144d7df8d1cfb003a4fa7523262789f9aa88eba74668875d2d9b99487d28c6979e980b6a4d87c95471553f85501a8278de71f8192db85f00241246e34ed69939ff15310f98a5381c5e60b2061126218fe7d8ac08d593ef0545689cc97d17e4751bbd99cd2486d4a923bdfc70e019bc9740f41b51ed21e3a184e416b10d1160ab7f188e5ef59ef646a84e5b67dd87a89787cb0c7761410dccc08cccaaa7205cabaa07500c8c2ba9678d080089dfbfd360828012958a6001dd0e591ca44cc0698589532d1b1baf9d4f8918a48faa45a122705b75ab6a008cfac6a3e7da3c2cc2a56076d61a40eae50b8a22692f5fb69c25465ade76827611354c00977d0f941e40d7d985b6749aa402a5ac198c4037fcfe0e54f96ef2fdba1e8f14abac1f8e68349ad1d909a61d72c9bd1b9cc09ebd301201f952799d3a506e071c25f6f246b921180b172c0fd40f5129eef1ded59d4dd62fe7aae54960701fd33d72edd39f5ed604884cdd1386e190e1076352afcf5ae627c4f52a7b545f50ca404e47d82947f76717e4dfe6af60769ff1d09ac518e6247658403117c58f544ec8031fb0b3a08574b01f38982045154b802f2f81a495d1708d906cb53237c7700a1c4931d35046608a5fa80e532ca618478ab68a708ba8debc763f9ec69164d4a12b96d9b952f4be541c960bb1a874bf738cd8f433977785e9f758e1094894a5352ea32a0dc274291b1e56053290a51adc9db529242542baa62608a8d6d988b95a5bd8f0af7f3a7e983f572076b93c2d4c3b1707c9a2e14686b49fd1f2511038df269c585b8ea38d8b25fb435529bf775cd61c072507520dd03b6344675ce38ae69d3efb39216c43808bf6efaef6ae08e4b58f2bc2988217a837f2621b008accc145709788527ce055621d23e0fd9272d09953b3f4d488dbc86e9ae91c55c0ee01e2da904510ada9cdf00ea507893544fd2b93185653544e4ae38b5ba43a12d29b8c145c3149daff54abe87c25937bb384992222d4019a220a8338b80421240501e0a5f318fbbb728d2281745078ac3f56914c8a797d9b06c09e95dc63050bd4f52d5a61988cb45e76f73c5cb0c833f51a55c5b84897a65ccc1a667507df53b320472dfd3be17168b6599bbe7d1de90193a490811949d6060f13d666ed09adcb8bae4109e6e36b024002e77204105da85397a98560aa71c78619df08634ce982c23fdd6682ef97c20256c83e371a1ab6b3bb92f1181bfb65630e10808810bf4f064480f231aeac752aa87704686134699c6f22e19450a05687df431d970b35d36987d4e4622455f560747e2507d2fc3095c1c123c0125b8655206078a719e82d6143121b737eaf897c702f075e99ce7647fb01e4e1b413548b1c09bc923b09ed305faedec0f2e4c950d8be87900b0de453fb723ddc0427814ce7e5ff1ff60a1127154f01f252527fe1b21c3b47c23230b61861b1657d0484a09f57ee03d8e69246f98d587e02d99ea52b3e432eca13a9ff0b09ae1d66ba4d38a9e8736628a201bc4d9cec3250939fe4908353e01f283fed7ff6450abd24af4d500e1c606a9dceb95c0eb530ae79cc2322e0e9148f2a4204005903e66659a33cbd839d040fbb15babe49e5dc8e705372a29c6b49bbd2323c9ac258a6cdfed2c520e61577791788d71a2102cdac66296076625f41e9e13ed8831b4aec4aa6128a396ee55b951239952e0cc51f6eaf987575cc92f93b06b125a8afae0327cff19a8ccadcb7991e3bf0194b379723aa7bb4516026971e4fc840843c691905294a41360200a8428a9441c69d0c6b9232e4e8053dd40d0867635dc770fd922f6a82827b259f8b42b889765e786823e59f20e7477dca51c572e2703d4701a38d5a75c4cd40208e7e4e03fff3c2b609545141a84111c363d8c526a0765c9f7ebc11b4e2d0e4f822e667ed2353821db9739ebf3a978d9dbbbcfb0de9b8bb85cb44024351f08274338783f93b2833e575aa0b33c63f93dc6d2a30432e03d772c1915d89b567080994e28b3d3c24463a42344951a6bc320cf38c2ba080e6b69bb294ab0def48f4a7a69111099802922e82aa27896b667671625e6a11cefb73dbc2b4f7a5d31cc7ebe0da60eb4d00e568674d41c7be70b65b034eb9d4212830aef623ee48a91fb290ff44a195f9f573221c52de620282981205fa54acd1a1a0c393c0a96bedd57567017d94deb93152c19afccd645e33390a8a3714125c36ad49cb64439a1a2f601bbecc5d9f2f3df167ebc79c4d27615440953d05dd0e1a2a1924884924e18a6c98aa50f69596caf6fea21920145be58adcda58daff27d392f562fe1f04d58eba216385dce20aa1850f6412d27b1abeceab241d54236cc7b3ea3d1c40ec43b4a813bcc8c92812cf295028aead996958a959e311bcee8c425bb4fb2dcbce58c9b1c28184dbb0821b699e680adf1b0310618a162eee1b59275dc7188a7fd48f30047eaa05c625ce6ae2c6102b12dd71f68cc3f76d751d1705efdbd39a6e659298ddfaa1d6fdb1055a66a307119aaf1e321c75f80dd7d78b10a660275f73fc6040244f56b2547cdeaa22346a634adb7e3b3898903857e1143c78f3e543b8a35de70a4ef7ea473b25a2653ee9d5be2bfe628dcb5ce52f1078baf223d8dfb92780de9f516bf216259551459266fd9cfddce6eef0209d5f999bf34414045fb3b8a0c367adbff0595a46a19b5262b479d48605effff4cc4b391f2f0e53c39bdc837e3c396b136175ddaf5c441d88117c716f2c08e6cce5a94c9153a930badcc7074cce2e8db95011c4752a2438d99f9e3afe9ac8d97e9ff58120e7050fa2e19429fabe7a2eef4aae1d75110ea21a0c8657dd30a0cdc1d9e25e31bc5b35e5de6347409b8131cacd7e0e828b3089b650d222e59c3a1dd36a76827ca36450494888a0203abd104f5a62dd2028152fc857d6ff459a48ef84e2cf1a045da416955b4cb0c802130ca41210c62f0bd851e18280c6145b77a1184a8a8ca362b7fb8a9100547120d7b37cfc70a9c315d70a56cc6f859b28230814c2043844de83e5b38115edcb9c95ec2fea4cf0df88d4349bc1ea1b5b80f9581e3094a9cc214b6e9d1f9e8d32362ec796dd8aee1d66231bcf0e8204313624c27225095be05f0f55238b1255e63828474cfaede3c38fc4bce2a3978d5faa94adf8bb388fabb3ca4502a4057aa58993248e5fe1e2ab3b0985798f76208d41005392338cb4a5822475b60226187779125af8ff5ceea4162589a455824721c0520914802b1db03326747f12944c1c15e12e6669da067a9bb3631a46decc245c44823321176485a8428999c3fa1f45b71845b0425826ae45cc1fa8e3298e41d0b9a14a266f8e52c3fd8849d5a802bb014d00955092405720220a11e157abe0f9dc0aaa0843044e02e6023d42fa0a5856da6f6fc36840e023642f01f8e45e6627b0de193f051a09db0eb01cd29b6757eadddadf840912d41fc14f2694027d9c75c70e6b29f2d1cc899924e097ab422386a158000b01d9c36fcabc11aa489badcdea9625df3f27f999d50523f6a30457c64130de2b9b4c205341d78d83ffc0d5e862f11d2c8e2cf4abe0565dce793f1acf8e2979795af0b9e6688f70e5294751ab7a8135103180d03c436dd3d3748db6bd3a9b28ca41a2a9351c30847557f925c41894e1e30e308d6596dd07aad92e664b135c85b10a092517388174b18d93b5b321492f79c49ac4e5137a386de350ceb3451838d1006b26cd2d5c9f8b62ae3d1840cecddc9db6afe65f843df9025408247b7aad603a8c91280478b3dca920b110a33005019e2ca674f4f84d35a693f67aadb270e7ffaa49e4828cfc95ac35bef42d40ac3558c87aaee0039728e85e65a1cbeb362d7579ac0b309820323015d67798103471a301909d8166126f5d8b0c8b1c6b06d3b0004eb2620d3126bbbb288996d347573a94b3bb342d2e914b7cd25276a2e26a50b16bb583e230ec35ca4ba25cdae923c9dd0654248a92811926876ba1d885a87b5d297e9c1d7c878e7327a4f377a18b46ea4b4c32e025033a2c8487cc6dc0f097f642a465264f4ce77dcc1a13c43bfccf8d92e4cbdc858196430adcc8ad7d65a2549db022d5560f3559c227a15977625dc610eb1eaa984ede63fd13d045a59e75269580780c282325eeb5c15055810146456fe12292748118e0154e8779f1a0834e41e361130c2742bf1964323741acde202a0c06ff54e121e96f2f4bb15a1b6a8d3249212e0487d4816a0632bb7a570a9ae65d1ed0bb5e6896aca84445a0a20d15929404f50cc84ca9407c95932f098893a2a34143989534f81428977388819805ef017fa5e62ac48944981258ca915c49defc24c8768e5eae501d2d5782e8172995bb8d5653edcd0d6d1cf60ba4c006130feac5292e7236d69b61dd68de0776f6cfa0e7488138e270e66742e0549ef50388d38feab386ca96b0a265d62999f7a9e43ea7d1222dd162db9858207e1809f610774448060989563fad04a0b10b184b054a4a555c6c258203e96e53823bcca574da09bc56a11b5449751921fb1a521fc6b36a9421669976dc461b4bed1cc4423cb7613253c9e29aa5c7a64405c89e8c5762539d3174297b5324c2a831d6d1a358b02443ba3d8a275a26b3b3dd170d9616d3afdd68a8d2bf9ab32d8eafca099fe493204c32a83f9c44845c0100168912745286c0511027a940ee692df9473d76c450bc3de12bbf3c833e272289e7a0ab552b17a2d6c40a3a8368c7c5f8ca58242a7652b3f874a899a98d70cf1434982762c09c90e3ebe1de6056ea6e45a4353a4a434aecae2c9579c3c4803953ae4a5acc2a7085a5210232f0d3d511114366656c246b37a90c9b85dc6e7905272095d29769536ff99c7deaecb2adc4bd3122cdd2bb9d224221fc68d64350895cd0c5f0eea5d10a76c0e8257a077d656ed577cd64bc1e4b31b0bb831a647bad1c5be294f415c9fc56dc29594998235733e35d56e53a1b27572dfdfb7bb4270a32d89f75c721dd572f471414f047799e25eb3eb56a2cf5f112865cce57c7439ee6d4834b5fcd667dd8e5bd8871f9230cf876fe999845269696550d25ce5f9a53f5265088f01abb2967c33ae4736609673dc31f03835f7ebe83d1faf0a3d78400b7450214f846c7590e35712067b9a9479b05472b2b6dc63f3aa00225f9a772399d50c51254cf5dda54575b28306ce431d79ed70ac5addec386a70ca204a1b7c08b76b35167c716bf7da45bab520ea2fc684e4c8154482745b2c9ece650255763dc2d1d86db03a00236543ce9fc2964d26b8e9443f389c23405ea74e5a2156820f2f41d698d4bfdc39e82e2bb5075080b7cade99a75142b61f003f9bc02784faedd63042d51ff24b691c0d5206ed422f3a4918cc562b96b9f12b6a279d9c5141adf85d3cd941988c5aedc74d1c6786682ec7f8cb01fe36b5894c5ae03707267f20c0db9444ddabc297c0ac6a56c614d5005833254470a7869920108c49260ef314b95ec9ba1ced05d6335727398f85397e4007ba67e8c099f16e23d2634c1c7d2b5abc0e2bd16e932011d1fb5345147503ede590569e1c132c1e4c2b3d636897ed3ebb93ca54e8db08246603c31d273ed2bb90299bbbc3a4f081c60c8633456eab3e9b607abee5e4552b3d98b992674d39020cec3122fa3eab04df0e3217726e2d1ed075c893843824447202a28ffed809897f2f5eeb90429717a3584a76a00ec3697f43c1808fa547b2b8105b6135a82b562c819ca93b08c368fce0fa0c2ec54ee6174325d43df8e241dc48fe04a4e780303e27eb11553429110ca5556a6b4a3c7ca83b2a075f05d2fb45e1c10d7adbe0b9ce7671da5f29d473a3e160c01dcad54f71201cf1c7fc78a247722dea65d786d947472b9fe6b8587b7062de8f8857468549a1d7cf998013fc0f700c0e7c2cf0023a691a47f1eb501a27dc4060ee4f58da27cbf4115e01a408e0c0ffdc3fd08b9b767e81d376bf3cc9d26fd069aeab9a2da5775d85f7cd045d11e37b56c3b4f41b0f11a00f8d196997c2919b7d6ceee06a5c18e76994393180681e1a2db85d850afd6c1ef976431287a545afa2375d1cdf8d07fdb7aecf339c15086ff8691473983b125d2c84fd77e93a60be4f90a6577849f321467dd4275ab317924feec822d3268e7918ae58c410913ca64cb23bec3dc4dd277b231f7ef38689adf3d2c10016e09fbd0b68bff00733a4514e87c1ae154c1f6ae0fe35ba82f3301b7fb42aab967cdf6b2b524c1c90cf60e014906901342bec4e72f7660324fc305571c2a2fd23ba1f914adfcac8186b0817faca2aa5bc1f396e577841d08a5ca775d0aea640a77de9aa8e00b951747a45cf29edc3a09e32fae9ef5d8affe8046eba353b3332f48c20d8622ed18b6a4910d043ae1c33773436e75c6cba48322a8adcd29bc6a20a374f4159d9706ac133850a70beb434c4cf17a5ebd19665c2d40fda212d42b0d8bf6b15a6c4ce8bbe05f51ecf6689e1986e9a8970b4cda5660062025b01a581c0126c107220a78de642c55527429893dad01c31bef9aa2c1011439f235b7f410f6a84a0be0d3f071364dd5f6c818898cb1ce42e9a1718ac4bc895ec27a247b8c1d037b76c0742c126f45287184e71a8f4b45572f614c84efeee1bddc038d129182d770491b24e087e1f91bf933fac43996d8c5805a613dd451c220b87384dbe615abfb2c697bced58ce9a86fd9ba6a27701e2207addfaf49743280204da36b282ba6156980274d41a6230fcf800876f3db1e6e42ab06260d92b9983c58b2a6a3485483e00d8b6434b5a4faca3722a4deb5604d2c36040efcd19288bb80513e642218405da47d0f880e4813841804012603562ac64df3c45837d0ba7ec1832542d41f39aadcdf9034174ab27762638557e7905ff54e2b8c00b16a0b9c985b60a011b449ea25001a61fbe1d3d45fc2b4cdf4384a19dde61f721c481027ec7c4fa0b3160b25472ff000000000000000000046370df7e93105baf4813110e36252525d96e263ec8cc5e6ccdb6259d0e3e9d19bf20e90bff0b420ca5cbafde1d4f92982ba9602a8760daf1f3e4f02aa860f24b8516afdb6bab9c8289a75d9762554cc1599cbedce213eaa6520a362489793a4d8e762385147cccabcc1d7a1c0d7d14dce5d042ae9cfa971fa2e073149a1d4393a5a943c18d85eab837344ba6808213abae38216fe9a89fe07ee2e5903b5bf62e3dc1f644771072f8962ab89de0734d7d68ed793159cb09d6034b2fdad15e63ed2618fdfbd0153c695a5c134c0a3147c93d875b926782f7cc9d9327ec3a3c30c1486d894b7f48e19b4bb029452c6974dda03196e044226e5fd27a3f4d259890fa7274d89aa36e29c1e74eea41ca97122c3a09deaf42720965c9722709367d3fdec544828b993a079ef2a74f1b2498343169b65efdc8da2338936ca9233815bd48964356314b1ba1c7714a31542246f0bf95d1b5de52aa882e824b1e63baf193d0eb2b82eb387c435b697647223811f598e31fd19603117cf07812f3e3e475cd21b8fb0f1d274f8ed16308feee2387a4ec1e954270516a92c4a810820f731a0dae0f82abedb1cc7ead398e1404275125f87d8dc7a90c04235afb2963ce9fa20404535692e3ac1f9d5afc037fde9e35e2463bebfcc0adc715cb73ecb99d3eb0a963d3f29827bece075672e8e8d3b9072e73f81dd671eca1a4d403fb9f62ce13dc2d6b9479f83c7d2358f2e0816bfbe8771b3ea13beec07414711d4fedc044fdb3b6b0646a963af09af62cb96ebae8b174e0dad63f7a5ca92c45e7c0674844de7f88e9a71c58b74baf606b9b2b8871e0d6e2e670cb21644f100e8cc7514821f737f01944524eb7a71c7edcc059b4a8e6f6b481ffa8537a65dccbc961031f296977d68df6c95f03ebee295da6cba981ef298f34fb274e0ebf34306696fad3c4deca1f87064eaa2d7890c9638b39ee0c5ce6ccd0e1ebc731749c19b8d033ffc97721d9e6cac0e43c16c77defedd623031fab793869699289e4c6c0c6943c342b72626073cee8b6aeea38e8c3c054ea9e9c9e8e2ef460e02dd98414524537a7bfc0068fbaca33ec746b2fb0214786f2c8569b2c5de0eaf2e6c9b1c4b16db9c0e6b7f484f886ca6f0b4c9acc789d43aa18322d702185a97f6bb2ba290b7c5ce95e9b65e351322c7093bd230d39885d810b614937c72b46cb5b81bd16b3a9bc5781f73859a2e546c9be53818f2746bd983c1caf7c0adcfa9696a64cd2da2105c683444f1d34d2831c51602b6f640b968602d323551e68a60d19f3043ecca9c310d5d99346cb096cec71e9ec673581ff1c7b85609b12751613f8a06bfdd364b50b622d814b7b4bdd7a5eddb152029b3be4efcdded17227094c9494bcf63912d8641a52e4d0bee3cd8fc0a69cf08cca5ef51f46e04eb23424d3f00a1e45e0f3e728a6d289c07e943d86ac94afd36608bc84e42972d4fb1ca708810fd7bc12553343cf20b093f3fa6ff47a4b0884ffdf743ae5fc80eb8a7a59cc3b8eb6a301f880b71ca6ce2eeaffaa512ff81c1d776765d1f310e2051ff7e50b493cbb98a10b464d4342e6c8fdd4e782fdde1ad590596b37b860379996ba78a5d4985b3049630ead3c8a2db80b79371a254fceb5169c697a2c92435ae02c78efd6c9d17f103e9e2c78f70c6953a8cc7190c582cdff49eadda15dbe080e4c0b56033360c18620d963ab174b41ef57b03927f3bd9ca4ff1e734329a905c6cc156c55478de539da7bc86d61462bd860b6fbff515670a61efee48fad55f06925f65a14cbb020c1c20c55f09ddc3d456a4e2af86cd1abf42fe9c20c54b059bdd582479bdab11098710a36d35a8648c5362b1f30c3147ceb4f9ada95ca0b3f04334ac10739ec63d0efd2dbea033348c19e48508df62f5775146cca963b7a742c7ba744c15e9a46e6e42031a4f486d259604628b8af5e4ba79f4b2de46680821b4dc13d3bc7d1558813667c8217cd15628a2126695160862738df10a39b86dcdf4de90497a6bb2ab9449c6034d57b4e37f90da54dc18c4d18e57b21b222e7861219a789cb047b317e0e1dc5147c52850956a432e4477868a7df0d25f4c08c4b70abbfee361d4f08f5df5022c312dc7d7488fb95bafbe60da5a24af09342588f27f592d00a66508257c914d4bc364d7af424d894c8ba09b963f2cc4a828fb077498dda49a98d04af3ef991866f8eec15127cdcd53d82cdb1c2831c0fb225441dc15555879c36fcdc6f049bee3ccf2a454cc9638ce0cb7763688bd47d478be08269f41cb784e63854115b7b8b748798bb6b4a9cf26b8fa637aae306ef2fa8c11356136624820bd6eb714ed162c8d31500dc300311fcb75fccb14990900ebba13446192ff819cc80060739c38c43705f6f9ad2a3a3d01f6f08b603fd0cfa1ea5c398a3106cae4f1d8adf8b754c1382d58f982ffb843c557b107c47312415095a1ffb46105c87751d07c9624e936b20f88b94e3ad8fe300c1badec7815ec721fc627f6037e670b53d781cfe931f986ce9ad92c82de51b9dd107ce33ef87dcad7faa61ea60061ff828ad62679a9c57a27403dec18c3d30e6f127cdd13d6378f450ccd3360fa71fc28c3cf0b6933b8e7c73747b7130030f5c7a54c922645f91dc67dc81f7acefd0993d240633ecc0855ccf9c25adc3cc56995107bee394b4b1d355851ce5cca0036f2b3a39838aee757203628871cc600e468978b559df2dddf73490c13fe0f80f501515cc9003d7713c65eae9c3b2f3184055c693d1811971e0bb3d2b228d67fc28724309073328e305450cc000b11b33e0c0e69852634a12227e79dec05ac7fb1a6ad4add2ed0636071e46d210fbca1da50dec84f947d00fb2818d597c2f6392d6c048b290b73fc8c8219a1af8eef8b79a37c52e8f66a481519d5cf9236d0e62796e289512cc40839639476b3fee68396e30e30cfc7f143b9a8c3c29c79a19666024f5d5e3205207590c018e604619b88891e9c65a634c77cf20036b6e1a34baeba487df8c31b01f37847cd3ce10838981309811862ecc00c3169d93e6bf26e60d252235ccf8022f39faa58df956c7bdc0bee65069213a4e1e7aba5066ac7ecf53361778cfa194ae44d97bb4d90293db3f7f90f51f479ea28573bc989d267dc8e156b2c07a1cb5c4ab52bf0815b7726a677848f70abcb6b7e6ba8edc535eadc075f2d4da0fbad93e5641dffc4093c7c14fa402bb7e49345dce1418c9955d5277fe384e9f14f88e1d9bb44b150557439a140a664081b18fd22159129fc079ea3421a5a871f2e3042ef8975f0a510d9591194de052ba97d2958ace60021fd3c6f4bd694a5263dc208306312044465982e10c25cc4802b71963efbe5a688efd8f400b66208133b798bc25d847607310b929ff83724dee0da592c00c2310da43ee798eeb39c8985184fd2576e67cb9e0b801910b8e103c49c00c2230495b3fc4b052cbf50c814b9f0e5f2a7746c96184c08b7be59090bea36f4a10d8e8172564fab032c7a140e06adc7c3d57f207dcfdbea9f774ff87551e61860fd82469bab683fed8e3da0b26880721e7a05f2f39ce0bae2eb548468e79457c177c1cdcc47e4f3d3334ba60cfa3bc1ab96f3ce71427082017fca69cccfb8d229157120401e082b59d4aa97a39b7e0627d98e2648df51de56dc15b4a3efff8f18bc6bc167c547e4ae9f503cd69d3820ded1b3dabe628e70fce82fb4a2ac93b7b14da9f2cf8cf22d67a1d4da3b3fd810062c1fb78fe20b349070b2e42b64e25192e11be82bd9c5294b290f23b775cc14ab4a6fff1fb5ac1bddbe4c6cd9d156c2a73dd8b49dc8368ad8253f5eaf8b5c12644c44c00aae04d2a841c9a5aac07024805df493c9967fb1cf999a8603b2d734ec15b4a13bb26e42049b098821f0d6e21ba2b3be59482a9172db38f3cf47fac0f04400ac6ce3bc4f1e439b23e2d07021805db356d9ea38fcab3a645c18f6faafa8f261b0820146cc4d3241ee5b02e84d00a144c49e42887f0c0836c767d82cbff8165e9304a35280b08c013ace7cbdb88f9a3645b192a98010c543083188c51c6a7a0c82c5d009d6025c74a1529e528965538c167abc47cbd34a138086013eca847213dfe701080269894fb73070fb399e0268f68471d295d0e4c98002e416208c012ec7a90215da784d8966a83002ac15ece2e091d66da135b009460f3a94e90bed36c394e5e6004638c400093e0d346879b9ec70dfd32ae8c0024c1c6af08d9f26a7f8840009160234ed478392144d28f4282c934a96c9d293fec1fc1c741e26592b8bf2e410784200c077cc0012e28830c1aacc019f00f017f52af108023b894f5438ae5901ef1d208ce5a276fa5048db9bf0fbedf490a1500860fd84a39e4affeb6b6f4e905db9a43a954f3f082ad5d750f2c638c31f55df01e079a36dba40b3ecae1a78e75baa38e5c30ed1b2219dbb6266b70c159b88510dcce934ef6166c47cd39bcace9c34faa2d98cc71c7d051d73ca7b316ecc4aca91a9a2ed887b46027f5e430c6eff02a78b3e03287cc151d86081d8468c8826d8fc493073147afd8890537fea15b94ec1bcd745830112b273543fa0a36c7fa1bad7b2febd8345cc19ee565aef87b1596d756f0f541c8123f2c68facf0ace2a357ad86f5f1b7f5c05133ac61433d05005bb295e8e60a7b7a9752af8492675ede6916746a8e07284270b11346e69088d5370a939b8588a6f1e7352766898825bed09b93999a84bc8a0818fb1d5344ac1bae730a6e87b19830629f8fddeb78f8c9999e90fd01805e31ee5204aca2c7f0c698882cbfad4f57149c8aeed42c1849ad0538b1dc7131550f0314fb37ff4654a018015687c82cdedce9b1aeb402f58a0e1097e3f5cfb9f8a549ff41b4a63946162f03328df82363598c1d718687482c9fa28b4472e6a2327d8ead0937550d1b69b0a028d4df0414e1ba1e33d6f283d818626d89bfc912162a86482edeaf0d861c70ed3a22f80810a1c2081301c10c6053040031375ed491437c92fc184741c7894a886e6571ca061092e52f3f965f7a7b1a49914685482bfcf2429f7c7d714685082df0e4a55825bd6033426c1bdc71ce425296f28a5e0052aa0315a100317a8c008004aa02109563f4233a4e04682951ee9ec5972ec5ca132041a90e0d26e945d871a935afc114c8f65d647d1fb729d8ee0f55325cd39881ac1c77c7fa98e634f738f1b4a465724a0c1085e3cb7bc4573241adb19c0608c42008d4570a51e69ae8fedc30ab512682882b3dc610829da27826b0db30d1632c58f7a0ea3031508430c31b04003117cde8a9da1dc3c2f723b1a87e036969ec7bec91de6704370df52962b357ea5595e4054340ac149088b794c2f74749c109c564ea929ea791ccc63105cd0a950a6111a3a2e083ed3955587ec696f2d10ac65fa7bd4dc552e2940f0917b3dc596fec0faa7fc96a9631ed564d340c30f7c08e95eacf2e747fff4810971738a78eab9a3c6f0819b8ec53b078fcc32e5ae40630f4ce5b44cc9e1a1f3327ae04282f87be45144c87179e02fd43f07ab5481061e584939c4d4f83bba1f5981c61d58090997aceba142e8143b7011c98358879f42791849d0a8039763e4eba9a7d7ca381db86076f934accc2bca39707ae911b12a8fa02107bea3a4177375c4051a71602b85def3abf65cd963040d3830399214f3a3b3dfc047d6aa0e16573ca7981bb85037ad9e383a9ea34b048d36b0d191b6f35675ceefdb8003aa081a6c60534a0f558f9151081a6be043fff49d5163bba71c6a60ec638ca5810ff9c972e41c64c8c92a41d04003f71d7de8adebe4641242d038031369effd245be78e4a3a3370e9f34f4bc851206894810b0d5172903f27227a32709d3cfc9ee0711cbb8d63e0aaefd22efdf3030d31707f11c9ba3e870a2cf033c880fd814618f86c1fd77d7a8ddabbe9810618388f13b284cc41d6d6040f34bec0c6f78ebf3f1d739c1e59a0e105462562d0168d9efa4a078451c60b5460013ad0e802976b931b22753f083a7281d71c6bc71fc7b499b2a3e240630bbcd5ede5455bcbd9ad16b8a82f9d9bf2e7bc8fe040230baca5bbc8fb96cbee3d37d0c002ffddf519b463dae8ff0a57dc0ed1c2232d56e07a42355a5eb09b0c4b15f838565bd1e07160eff7b6810615f8d83de1252999027fd92dd4475aa4555d030d2930f173749dec1e561525a18146149868179d1cf14ef2268fe640030a4c7e9c5557dd5278ee9c81c613f8e49ebac3f441ced35771027b41828d64f6b1f3550c349ac05ae81ceb64d8c71c7a1708030d2630395965f1fe3eda8ef3028d25b03b9ab3c7d5fcacada34043097cf411f432789015638707ca302d80801862bce0cb28c3b4e0d04802e3d12bad86da8a791f3828430580a081043652c5d7cc99c390f912430c1c94a1023cd03802377e3972b38d516d3302eba1a318c273f67cc91781b5fa96accf715cb73a11f8b571f588cecf761d43e0a29f4e07a996257e84c07be0fdff1bd563fa08029ba277d22c16f37dd440603c96d0d3fd2ad19c1fb07597f5274325ed4ec3074c6994c70ca5e9051f6a9ac75e7e2e1e04c370800bbe8c09dce10b5e705143caceddb9eba2d9056b211d4535778f27d7051f63a835174cced1ad564278ce61c105b7259993dab3b760ed3ea8d8f9f9879cb505af3147c15a30fe7f79620af338cc2e2df8fbb4f3c8983c0e8dce82c929ada7477f360f4359f01f3d3a5b08dbfc10c682cb3313bff50f21f383c516d2e5c8b51cafe06e2a5a86ce925cdc7305bf97f63cdc9ed0742bb818ddc373f1d528bab18295e8d0e187d329665fabe026071e72de73b5181255301e858eafea3c52c1ae7706b5ac57314d1a157cce1e6f5aae7979879e820f3cc7518a5ae96f7b5a1c7c610a4e23a9599e4eb0f294820f838bd6646fa9a79c147c6c93424d779b46cb310a265ee5f45f3956144ce5a78fd3ce0d05173aee77f97ad6b8495030b5a956d15f73906dfb0413ffaa6d237f1ce7c87fe10936b6c58e18d3a4134c7ac4ea15bd39c1c450eeb6e2ef179be032628e6356ab9aa0fe7e3f7a1c72647c91896f27471d52321c10430c31c420e3085f60a2f4f8dfd973d091c2179760f34ff434e9225f5882ffcbe0ef6f13257c51094e274348bb8e2e2265a4055f5082b5fc3925cdc9241dd9f4c524b80ed6e1e4a02a92e032ddb23f47ea305850055f44821be98e427690a865794830b919835bf2d69ddc3f822b4b393b76ec1dc167e7dcd21cc999fa46b03173e5e8418598a3723082e91cafdd320fbe9163115cb21cea56f49c1b2cab0826e4f3942ff3da78a02682cd651d4c53e44afe8808b6ca4c9387123b04e3b17658aafa3885b40cc1c7cc0829b45ff0a013189041e80f15b4800c3cc01785e0a3f4ce347d1b73bc1382cb2d4fa331453da6ce20b80ea23f66573d8de60b82f5482594c594b60e1308f603ad8afe51fa4110104ce8a01e85d04cf6a1f2076ef7e3e8ee3c46f128e9072eef46f7c0ddc3cb8ffac0c5081a214a5aebb37ce0b234a4742947bed8c37da1872ff2c06aba6ffae8abcbe3e418442cf8020f8c4f5ecc71fcd47ea532686081174060bfb8036b1f47eed891cc33331340267c6107ae7224b1cb336491f0451df878c5e3a92431e5075fd0813defed583b42bffa44a48967458a8b2fe6c0e6ee642d31338b4a565545444444446535f14c85f0851cb8df5649a9b3a5836ff28b38b02f9dbd5fafd7d68703db5171624bc7bf21be810dc1dfa2ed468a6f6ee0a38e7437aa473d5d6de05daa35c7219a9a5a6ce026bb3d5748759da635709da30821ac420e6f3a6ae06f424a79bb59734f4e1a78bd8b9756220799f26860b7f5c3d114ad39e567604a2c640be2d152889b818be25ee31632b88729036771723ce9b5103d0a19383bcba1246d7a9bca18f830c6ebc8d1affb5831f01e591d74c68899cb30b06983f5b4f948f0110c6ccc1bcc3df6cadab9ff029b5cf42bf6876731e4bdc0d98ae618f32fbded029bc646344876aee55c6023c498fb2ced0712df0213b2e438f6e03a95365a60275d43538cf61e6a1698b452d915f3051d110b5c9e1cb24bfe10b1b42b306d3976cc1b722b7099b7aa77a3794bc4abc0ed6f8e57c7538151dd0ea2633947f353e02c789c7c6dc1bd7c29b09ff2d8c7f13da5cf11052e5f64c971fc010536c6e812d29656c71358cb243da9c427f576029f922a62214731e634810916b1ec3cf460021f5f42646aba0436bb4d8eaac7228712b8bde492979db6444c02771e4fb35fb6ad1443025f1ed55f8ebe6b5b4760ecd2f253b53c8e268711d8e8d8d378daab857c119810f5d13cce93d77222705b151d9a495dd487c07648a93df2fa384e0e21b0a1cd0e02bf1727b7c7162070aad3515beeca55fd80cdb8f53aed173ee053a6cb5cb1272fb2bde0437fba1c5fb556649b175cec3513dbac77c124f5fe280d213bae5a17ec7444dbbfd8f5489d0b6e4bfd3f050f51f76370c1bf67b8dae6dc82a98b24390e7a752fb7059f42dc831cfe69d3a3165cecae9354936f3a4d0bbe36777e94762b2f330bae634eaa21455295909105973c32d29e85d94f4c2c98dc9e1c4775f5907358b05b3eb1a7a30f7378bf82ddb5f4d8394ecb61bb829f8c5696bb3678c6b4828b90e37db81556b09edc935be6ad5cd155f015e37d94392daae066bdc404369ee0021b4e287448a89c1251a2755495b1dc31fecf185f060d62e002181c28061b4de072794cc9bdc3e2c79f1b4a6983094c88b9c394a3d42559646163097caa34d92d47da719d1b0b1b4a603c0eadfecb9724f0fb9a7d39aa70cbd2ca0612b824f5415c1f09a1c63e021f52d40e543c9cca5d0f17368cc0e5a012a4d5b42d864711f89bf441274446fa58379408ec7a640e18d82d6c0c81bfcc1ed279316d2f86182978810ac270401864d8c2861058ed0c217348aed5096c04818bc1de36fb6eb69ae80f3680c006fbd318123f9aaae7077c64aebeb776d98d6fc3075cbbbfe6f07d3dc58ae9059f2b86eb94a4bc602f772febffb80b268596ec28847c518f2ef89cbf4df4349248b072c1abe455665fcad131840b6e354b6a4e2e7e16f15bb07b51ee22e56187d9b605631f335c3a900e2b2db5e02a640d8b13a20aa8418b3ffe7966c18ea4e6405c6279c4b42c78f7a8edc3aa634ad3c5824d953ed9c69c64591158f06d9be249dba66ba8c62bf8c8b5b25727c6155c5caf3c29872934456d051bd3936af28929f99bace0377d9021620c39cc8ed558051fef7e5ff9e5fa49d40161100c84e18030c805c2704018c4026138200c5281301c1006d9430d5570ffeed1bd749a1e4bdf504a05379e2ea45e6d8f2b846e2879a2062ad8582a398eff9782e4e886528d53b07193f5c5e8b76717baa13430051f478c1af61ea68ce942458d5230b1a2c38fbc2a7fea6a908289103763a8a45b992432ca50816619458d51701beee9697fde715c51b0d933e9ee583089135c50d408051b8389774e53279a1a0656133540c17dce2c4b8d94ab43a599a8f109bea3cdafc896c5724c0794612e40060370605af01bb0440d4f70e751b49798fab24d8e6627d8ac1e43c813aaf3e300190cc840c143d4e00417fd3ba88f2ddd2823052ff80a941a9b28316a6882fdc8f2c5bff330b4d41a99e0a67537771ce858a881092e95d8e5b0b28698347943894c0a352ec1d46647e5a1e696e072e36d5c4be50da55209c624bec4f5f8410e2d093528c16fdecbdb10bc1b4a93e0cc7bd2e5ae9cf99704b7ea512c63f2b8e9b38d0497ae93fdf8f9c71a9290603aa456ba1191d8315948a8f108de62f0d698e358d555c10c62503ea89105dd18826578941b4a8de053f61cef886e9c4e0b5af0648c81031a3423785d4b265e21b4523d460c68008319a4a06a116cdf98a4103ac49ceda08622588fe021493bf286d21830704b0417f9fad15254886073734a8a915cb266c10183161c106a1c82b3ab0889f2ce285adf50ba32041ff73e56cd607bf79542304962aaa62eeb0f5d23d420043ff93fe559d08e320383198c8183ae16c060063808ce24e63052d2da9e985bc6b7e07d05c178f8e7babd1f44b21e0826c4747bf943ed30ffe3885003107ccc9c18f25c3c75d4831a7f60b3ae3ee438523fdcce0d2517a8c0d8a10217c0a00c437e603bd3fbbabe52ca1c7d43c9ae50a30f5ce794b2cf834d6ec9583ef0b1a3727091ceca7fb9a1944fa8b1076ed56ff2d2e38c1d74500f7c4eb79c53e40e72fbb262428d3cb01fa5497845cd7156aa6ae081fde8317310347743690c18f81db8ce51cfb81ee4dd98dd501a830c156c0d3bf095e6df41eff2febb1b4a64103a5b841a7560436bb4595c4dc990a203d3efa1c70f2bbd624bf6851a73e023f56439da29c50ed25ea821075622ffa7ee3257879e38f071276acbfc730a518503fb17530e25920e704018653420f406763d0e733bbacaf1476c0b35dcc09b5977dc1de4c89bd20da53670b6fd71de1c3347be1a12d46003fb51aaefca49b3811a6be06b243286e4ba8fc1afa1062e87ff41534388532108851a69609258e420f3a97b649e26d4400363c1c3cfe1497d64963f035f1d96a7df38a237793370d12c7328fa52997d978117eb4f97ca9182743419f8c820995b3a66ddd18c813731f53d49193170d92d4b26f14e2fc112066e7fb26bc851eb3dc782815defd58b4ab9adfefc0237eaa17a9e2647fbbdc0571e8fdb253ffac8a30b5c4eae261dc7c92da5b9c0455a8dfd51accc926a0b9c8798235bc22af6aa053e3a0f92c5ec965d22ca0217bcfd424decdba039a10616d8eda053e57852e520735c813b49b97356678ec8991b64180984d1810a8821c61849871a56605dd368d61c2cac51054e3a95e78f37256f4f161994a84105d62b3744e5541ed8f81108c3016140c0013b05be430f11a1366a45bba5c04e0e7b3aac8e7bf3e651a8c2f33e8e5ea1c045bc1cc7d173d41223fd04f623861c9ad27202df99632fb54c7a49cc267096ed4378909edcc3d87e0d480e3598c07db08eb672a5ded0713596c08203062de800d4500213d3a526c6addc50320aa3031100c1ff11438c1a49e02c5dce71e0afa139a6379460f08231aa4a0d245c96a3a6c761991994412e3082310c50e308bc86afdddb471bef9425d430021f471a2c6dbb799ace29029be1e739d64b9dca1f22f0b1a43c144987c09d69cc6331e5c98a8e10f82e8f24a706d38b3e08ac4e9654d9a15aca3610d8fed32c8da75993f91f30595ee22966bdac8cd7f00113725b99e7b8da3da7174c5b471eff5e36b3d0c10b26794509d1b4a76369175c65da8bae90fefc2b37b9e0c633e4586c93e4f471c1ee4607b99537784cf7166c56648ee320affea56a0b3ecc414efcbacfa2762db816f1dcb1b36fbc4ad2824dfe511c17cd6c5bc15930214da268a46e09a92cd8dcde7ce5d1a284d4c582d75ae90e1e794adf010bc634f98bf8792c9eda2bb8881052a3460a0c57f0414fe3dfc6cb6e293204305ac146d48d07fa153058c19e7734a162049fa89655f0b5163d22ab5405dfc1273c57d705462ab80a0faa7932dbfba79600062a18e938ed871bb92c76fc29b834f992fb96746df6300563397eaa55f12805eb81657aba0b91c1d2a460cf2ae45d4ef038d2370a3e3c891b37c71c42494c147c0e2749ec5cd17aa450f0d939479d1f42a06043d3d8d9d4762425d14ff039ac90d7d367d3247982f768d327adde0e3ef14e30b5717a4452cab62c9c60e3ef7fb43f7aba9ddd049fabb375eee86322a526f8f8f32f42b398092e73457f895a4132476182e9b053ea146a5d8289af1ba5c3cefbd4174bb09ede2f845c31727c6725384d8fcb538a581e3d5282d3c81343d7470b7dd924f89a1c684841752a3c24c16ad7dee46de4eb084582efd471d4dbae8f1d4182b51e0901e3112446e5018623981edbbe9cde413dcce50dc06804d7f6913c65fbde473e23d8d8a697a7438f45b062a79d62e6e8d773ad08467cdfc35add93ba35115cddf98687db8281083e72d9c79f3e128c43b01d47973779c2c3b4794330fd71e5383cdc8e3d8e2e04dbd16dce41a67775c91382fdbd98badd3774faf82098982ef6498eccc9122308cea64cfa329b7f90974070a31edd2a254d1e270404df1d9a29d93430fec0f5c616d71c727e8e3330fcc09d6e88f6e4ae5396f7810dbaa9af8310bd223df8c0b7e458d874925053b9a1f4021598327e0660ec814d0f5a39423a891600861e18dd4bbe7123d5aeff3c305ee29179877e34e9c6037b1e7b948387664e46c0b80313390a4f71246b0776d2c358299d11f2ae75e0dfb378468e3ad150d2810d0fa2e668de1cf8f3208867c8fc561901430eec45851c78f889031f7675cedc6ecd963e70604caa52ef428ed637e70dac6ffa1cadeafe4479dcc0c49822bf8f6f44efdd06d6fb6d3f160f36f0d1e7903eda6bd3cb7a0d6cec98eba1b55fe2460d4cecdfe9307d5093b14d031fada52153670f3d93686023c7d1e34535cb693c03f7f6bd9dad3ab2c8998189e641074619f8f81ea656cbeade412a043806186460fabdd57ea2671badc01803bfd1a7c4fc73dcd0316650460c080c66906088810d1edac7993b8af2fc5906c0088381a1f604607ca11bff4f2194a93400c30b5d60528588f8c71d648871e3c289813000630b8c6632b7e82e185a28168091859b0118587801185738a52da8984a749924538fccee26460ca1424e91008615b8c99f357d9c22b3728501605481c438021854e0abbbb752e8f81962ce14d85193be1c1ebf3bf4a5c0757ff42629ef47da61a2c0a4ecd4f17b103ce48f02054e3b0617cbf102e3097cc762dada9d72471fef0426e59f546d6bcf314d13f838d773664c60229ee7e40e5f2d6a57810dcc2600c61298ac4be3c9f3c4df1f0c25302e390e738e7673b23cb5004612b8241d7ae0c9a348605c2d791c5ced7fd53f02971ab2e598584afc14237051153442f4acf60e2f02abf67120a931c729912957008308dce68fe3de0d4dd7be72018c21f055bf51fbd155d20e4a40802104b6434549cb92536fccc10802df7953c6a09b0308f6eb66338f42f903de72b47a62836073b2febe781dd68582e05390c89763cef7ef02c105ed38c72871f5a31820b8dca2924f2aa528da1ff8dce9db41a71492333f9c2e0593a4397d603487ea460f63d09d0ffc5f568a740c1e52b5076e7d637b054b0f5cab699d455ef3c0fff779f01c925908513cf091458f1afdb903d37f9fd3d573fe1cc70e7c0ebf63c7f9af03eb9bdde7913c7df0e9c0a86436bb3c3ae9e373e0c49374981f7dafe572e02ba59a1c72448ec38f3870621bb2bf0e073e5ea49f776d0e62dec04b7a6787ccd107ede106c623fef9e6c4f8ead1066e837fdc7f9dee410e36b0dab591a5f6b57eaf818f228668c72e313f450d5cdeff4a8be419bb34309a21ee45c793975a34f0b14d7d4ebd1bb2da33f0317e0e53ca93e868cdc08ee61c6f927755a96560d3bf3b8a7b1fe5a564e092e78fc394628af6730c6cc7f9aa3785ea78a7189812fd28a5eb5092ca30b061932fa45fca1c0703bf962b72c5feca397e810f435f8dd8e805ce2d74554551a910bbc07810d10fbf24e87ec9056ea3c7ff2fb7052ecdfe73e8cffbb86981edfd0fa3721c7143cb029fa3ce919249d0945258e0c6d3b348ce61dc4a5d8197bc132f657a2c9dacc0a7eb3389163c658cb20a9cfbd4644f1c0f36492a30492ce57037e5503153e03c543bf82b052ee767d6bc5d6d46a14ee39b7de981029bf7d2fd47fe098c5fc7da69bdbe7177021fb3e3b8ae52a5b2de045e3c8e2ba60d13b8c81d47a631e97f97c0849468e9a343097cdeec99636692c05afe4c2d4bc1022081bf0c2ad1999b3e2c588023f0932d2a7987943c65b1004620dc8394325d5b04eec7f6a2f344e03dff9b7bf687971d021773a81963fd441285c0977560f69a726d14042ed86fb0dca14f0b044ec326c43a5847447fc0a7501fd9ea967a9402f880c9e157112cff52b4177c54fcde2cbb9558f282b12a158dff9382bbe03a3d481f724e175c84851c62f472c147bd16a4cc92e7c0c3053b693de4f1289aa2760b7eda3f4e53d59662660bb64ce266fae530f5aa051b8377b449bf532c8b166cbe7854c1727de669165c5aab0e21757a782159f09eaf34a94e98552816fcc70bda99c3942d42080bf66a6f638a7ff539045fc16b65ed38aafa784582aee0d65eaa2fc5cc20116c0577e7e679b27e381182ace0738e589fb4b30ace3bb22cf1fa62c8bd2af8380e3ab458c8d2da53c1c6d7ed28799cb6428e0a26e8e5ac11cddac4e3147c78971b661da6e03a94587b9183fc38a5603c6809f1f385144cc7ee5929a68c821d9d249973f471f3150593c1ef2e5f4377e886820be12d28d8a8e3e9715ca123aa9f60efd2443ada9c424e7a828f93aa7dfed8092643a8cbfc5873e59013ec48e4283fcc951fc34d3052bb316b7bc48f424d70ea15ed73f4d498bd4c70a19364cb90539cec30c1fb54bc1cc790af357609c65677bfcf35278d9d25b8f01c64d6e822b5da5582fdb8626ecd5795923a4a20c9d6fc833cdd24f8f20939a6525f4fd149828f4bc783cafeb50f7291e034a34e6615fd387320c1a9a7a81c2b7ae0d779041321fa974c2bed3d1cc1edd54799b9a3117c18a35d247fae440e46309d2f739c73d89b3b16c149e7ec211545b07ea51da40d21789b08f6453b47b446043b21636fc7610e414cde95bf1a43b039cc25a93efa95650ac1dd74a839b014bcf642087e5c5af36e0e4ba73208a62af3e5384ddb478a20f82a3d4d679625424a20b8d431ab532ae5ff0b08fe23654d195375d2fd0367a925a634bdb935ea072edde71c7e4c1e249a7d606af523a7d8b1a2997c603c468f791bfd72977be0ad5483af957a60bc34a57a550fc4cf03a379bd37b35cdd82786037686ebbb2123bf10e6ca9744d8bc59446b403d39d4ea5d5ab03b7d1227fb2774d0f3a702153eaea184952720e8c9dafea5dcce4c0c4f61c9264e3c0767c7a2167eae54038f01a29c450163569546f6072dc1d7f7db4e4c70d4cddd4469deec5b40deca5bbe74817a3e5880d5cd250125912633689ad81e9de6829efb857871a98ae9c69d35d2bba69e045356ddc5c6a2e7934309ed473fc62a926e9676075744434a47c361d6660d3b34d3ba79a98ee3270da1173bd84bee864602a99a98694bdd43206265245aa25a5889d18984ee983848e52c894090397463d59927c75e90206a6f256ddf8fb052e5864bdc0f48876debca1834c17f88f7cc3d3947b642617b8c91042e8cac99fe41698e81bcc2ed846fbd002d757517c5d5a43c52cb09bbf22425a2b4288055eaa4d7f2a7f32dbaec0447e0f6c93b9477959a1f0345215b8f7ce8eb4d73dde890a7ce09e1b9da39a021bd5fdbd77d19e839414d88f3f9a8958a58f211505dec30f69c173a0c078e86d133ffcbdfb3c8173d3880ef59f983e4e60d36a8acc21a7095c72ebe83d5aa3ea6702978246be91aef0f597c0df8478e8214bd4f595c06f57a53c9aa24cfc24302d314858cc0bd97b24703988695b9d276bce7104aec3a2aa648dc0c6cd0f215dc74cd751043eeeea38979d25d31081d35497d26fc463b487c087648b88f614ff324260f2583cd7ca418020b09d3fa19d3bf4c6420020b07124e7fb4731067d09f0033e969cc27a34be472e017cc05ffadb47befa385e7bc17b65a7fc0e7e1ea3bc606c6248a943e90d95ee82b7d01a4143db063b75c15ddcd0fdd9c2db82b9e0538b550e3dd2f788b8603fece89edd5a72fcdd828bd289a57d3992fcd982f1d8a343a7091ee5d58291fc122fed3ee575440b269b95e66e8d939da259f09a35a26f8fc6c88a64c17adf7ed6e8fff14a28165cf69f87155cdced030b3e3755c2b2e7159cb8557a7b4a65d371051772581dc7f4b9fe7f2bd8ea1c996bbd5e2e9f15dc7ff6ab607f7b93e9864696bc2ad8f48fc183ec11557b2ab81789d5573974aa382ad8aff6f455f7f13dc729d8abf330c433514d394cc127778f3d664bfb93a314dc7bea4bd2f1a4602565f6c6318bc1a347c196048f1f5958a44b8b82cffc496269219fd5a16023e78cca29d8861a14ec7887e3e1e96651f309d6a3c7d6287a828d1e639b4b0ebf9ce904939fedbe597a62c47082bf7bb3fcbfb516b309be3645f75234c1771cf94359a499b499e0d24b3afa1f13ac7d4cf9f1a997e0ae376e4f260f54534bb0e92a59094e738e7c2292e5b19460a723dbb68ec81b9d049363092d0ba224f8d441524b1f16093687b667453cd3fb20c1458f8fe0ef265e6cb7ac12cd116ccab86649ccf76a045b2ffa31248b1dd48ce03d43c899838e39de22b8eacd91df9dda56041f58584445bfd02c117ca56c77f51ecc24426c208255bfef899acccce2c5c621b871df4d6e1d593f87f5c08621b8d0d93bbeffb2c997d2818d42709df3a5f871b6ad98b1fb810d42b0ef597573070f3ca47b105cdc9073d0992fb7871a41f45e1dc3235d2f105c084931dbaed546bf00c18dc45cd9b1849c5a2a7fe0e39452e8bedacf76ae0d3ff0b9a232bc36c7f187ba0fec6877248f434a2b666df08153f7b0e3eb45dba54f07f6d0978e6f0e82e7fcea81ff8f2faae7bcafeef50636f2c08418bfcd73b87125ef36f0a0bbdea7a9abdedb8007d8b803636ad143b6903506a9edc09b4e760d76d571c89a3a309e13343c4dde182ba503d7593a7bb493755142a8d2c61cf89c935d78669ed871b41c98f81d33ff85b60b4fb9a1348629a305ef486cc4a1da061cf81c071e87ee59dec0c7ffb03daeccda7003173aaf3ceb6aa30d4cca5779a9f7d2fd48850736d88036d68036d4e0eec609c99ca543bba1fe8789010770f01450ba4dd848830d34d41958b18eb610f4e37de430035b27adde967b228bc446195837efb0fe754d447b316c90810f724ffa527f6c126c8c81f716354fbe1127d81003933fa5a47d1fe4f829250c7cc8902da92347fb1f30701b747bfa43626e56bb041b5f60a3e4e68e1f6f0ef3dc0bfc4f4b7ea9575536bac064ae9cdd827ab4de41aa6c70818d1e564a77e9a38d1d5265630b6c59aa7674316d6841c961be59e042a744defdcb96dec102d7e579ec2d5dda7ecf15b80a6e2a5a1a4b35e956301b55e082c468952b6d06cf49950d2a70dee953d33e07c9828d2970d6ebf1e307f95a1f471e6c4881b1507ef93e654e694f36d8880263693446a7e4aa95bb63030a7ce5cb6d8b8aa8b4adc5e2b1481c1087c30151200c06ba331fd3140800182c268d8502411427a2b03e14800351221c3a242620201a12141a0e0e140e8502815028140c04c1c0201810080683a1a08bac52c60f693771eb0f7c761f762dd2ba4e513bbb8529e25ae5eecb45fed7f3854cf14f117b026c4be28ca599a5dc1dc0c0013ad5d11cdb6701a58d773b5c5c9ea737e85837754f4c9cc607e8b00e73ef2ff53ccfd5b6f2190dfbad99da6277613d41820a29bd227d2f8e7a9fd7f5ac4c4b6fe9a519f9475677a107eea48a018d6968e3615f8f1279d529bab95c24a712b61bc39382ca81e23d4fe4995293df6f1af514e9ef453933c61350c08535841b479fea59538be9f88dfe42cd856bac8e943f75f5a18a35759d22f3caabc5eaa0e5889ed0d59cfede5ea2f33a97ddd22dd94895d89bccf4795744747ceeeb6c0e3d8be8f82cc56a6303a93c429a625e18f6944e6d8c9c961b8d61d38a6097bc392aff695d315f329f4e1673f4e8cff4066fe8b9cec668892d99aad6fb99f68b482404aaed9b7c9c6c58c9b0c302f62418d46044c7f34b7b64715279bd207f62f4947296f1c05cbb5a82117ebf7d0af7bd4e4775ab7373c8c77d4e7775d57c66b3b29c17adb5a7ec1dba871cf69c05dfca0d03656f2a350dfefbbe29207c3d5b4ac48fd4b768b4ce45eb467ec8c8f97b7de4f36c3c8883887c7b55ed147b9a4c940d27a82a3f587dcea736cc726eae8521e7fffc1cdd61b108ac6b31bea17de473756bd8ee3d0dc999ee69fd13d66a4321625ded250da36fbf3916c2dfc1851ff0bdcff2b16633d31990e56cb2a9343906c394da1bed23d2a60be2d0aa3b7db21338fc833eeb5b7c3de62136cdd2fb62e9fbf08353abdddfc19ddfa019bd9518cc63e6c3ecafcc895dc6e1efe955da6b9d209886eaa99ce73e6c3799ed7df86c347e78d6dd0313dc8c04b579d96179124ff98043b0a7c719b9580937ddb1fab0bcf097cdc836609996c7a40d2a454f3b81a95b24c602b2eacd72f41c38b1bac783799cb66a4d4d178904276a1154d81cdab24ec495c4d88fa1d89e018e89678946ab7e802514cee592a7d1c47c1840f8a223dadf85a1a5a65d511921bc10aa7b5342c4e9353c15f621013f21233cbcbfcd95931f2ef531855eeaed730965257c52b380d3e508df63daf70dde5e7e89b80ef1a857c8a9170e62b8feaaa3309e43bd6fc075377c0bb2fc40dcc3f1a8fcef7fa761e76098300c6a2c4a6bced9a087d0ee931d3791eb89edad1cff5f5a84de0eec40e78ed02412e6dd2cc1111aa84be12f80346e2414d264542e117da0400fe81c090157c30bf9b06674e97b619d6205143cc30fcbab63f0775f18ec8eb7a71508b313e352506dd041bf3de7de8bdbe8b2bbe9076589b021e3dadfd1955a2c2c84875a6173227724d2515e36183e61b793499a70e46044e4c29e76248166d80603e9c48ad0d5027f1135a128685351a2550d1670051400d24ec54b00dcf22e7d86fc904011a4fd2c0a20d41dca1c129edbc3948878bba4b95834d83d5ebb2e08afee5913f69ed843b1e568b4e12891241eca75e2538d8413021705383a13bd6bdb265a8d5b80b0c90dee0e7045722b08ae45b7b4c8edf2a0e914c3f9b434bad1f5a2e047c06f0a7608aab15e73a925abb7d70e36ecf8cc0af9f67a43b7163d7ff0d6cfce8318c61a0896df32dcac802808ff16711600c936ac483070fc0f2ba4cc241694520081a4b81102a0d02b301b3a04522ce02708f9e8cc4732a00f58db0dd94092fe0b49a20d443b860f9afedeba6cdbe4036bf296fa20990c509777a830b34b25ef19fb6f045ab4b5a482027d953fd7dacea712d40ce35d531ad051abf55504607a37672b0500bb2fd91c93d32d7b635538dc0e7f14765583c467463b937992cf1a9def480dbbe6b4a3815ea8398c9c10e42bbb34103db7748cc554a8ef470ad2be2631a73b345f2e7ae651c8ed519da1a2546edc3f130a57fca122b037e50a6442805a3bd7d6086ca64197ced77140e90076504cebaea691b06a960d0d30251329f5a226eb850500a27c5c2580652e6619de2d4093ea3fd4f9e8e27147244503a14131a27224440589fe40fb588c8cd0a05421070afcca09aa2e901cd139cb4bd06633e045d6956957d79d9233ba1201ef8a368e4d2c5966f3c8d5901a1a04d1caf751189aee788709a732c7648c9e50725fbff751e60dad3b752548b0d33bff5f33520c151bc11b8d5804448c768aab198c1fcf68c5f6743d05776433d23418edb2c56877d3902a4e530c56401dddc8837fe9f130b0157d92a1dfaade9bbfd29ba2b036455e78db6fcb45a41abea30f10f323b5241e75b0f34e6ff68b52624eedde8dd5be4fe18ca28e2944eb38c1c4ffe07d5e29c9bf018e8f22eebc7c5fb6374f85770c846a4d3c0f1616b0fd1decb28f0941ee9212be3d83d499c6106952ed67c8185b42558c1fcacb98fd7bd6d5986399dba68dedac2d4a71ecb70c3e8752b7ee14f71b7832766f86bdd42d7a7fbe6efa5444faf4042c9361ef512d8126924bf9322171b484ded19e5274cdda5807a7aae0fc51d364eb529711368734bd947558ec9418ea579be4184895d030936144fe93697e774b777ac81a701f75e6ba471777d31bff7e1b84f228784d3a9dce208b022f4f329359397ac93203a16b69376b1d988d11ce5091aeb66ada142141234fd93661b5a2178f6646dddde07a46af10649844b097e5865cd5dab2fc680b5df777fb486848b38b2670e242daf32420e91f6c1a6388159e4952a2dcc41f46a19c950e281124fb3952df1a4add9e09af345001a00b9c31c1725ff1f844964100d5c292e2f43d1966263276b9d20b0bcb15949739b4170b7a55aad1cefd38aad681f8bd50402247fcc88ad3ab52440872c236b87140ea3857e28be36dc85b9302168d219b4f45321aab5aeab7955b9fa812511e9925a74f291586a8d25ae0b44cd7765ec1e87848a06377b02f0116392f326604e8a95cba36391552c1658d02d4d9874da46e6093c71a0fc3a3f4d4098fbe167ead54b576253b08845d2483f0a824eac4a8a02a880249ab082708b51dc7927a0de308ad21500c355a12980f6c9c062e1c508320ab7038d5ff7a19619c61ed9946942c6b42419533682462b2f3decd3796039c960728c85aae134e65af3d61ed10839a4fe822366266b105d4a085adcac859ed144b8be8cb86b1ba230f34b0fcf7dd4a8c8391a7f5776891fcb2e2486f0fdc6bef6b48462aef285678700ad4598c9883cd7aa9c5ad8552750a39f39f502917b169b42430a5ace7431b952c9e410d941eaece4a17ee213848b9355ad57afe593abc9c4fea348ac2d10ea7452009a5343a13c184c6dc102f1ea8511dc4125a089066937b4d6cef512da9d512f417fd38cf422675110c519c04dd2d6adf3080cb87e3989f6ca5ce9ba80116064a404bbdc3b68007d2ab1c2d9f7ff25cf3da98bf4cdee10cf02ebba3de4482509bda1154c601a1fd1ed79da392d1c93bd0af2749dd0eab8ded4d7005dc739c4e344e17b2cbda28094d1d5f26963472d032a2d9c9a646bb03952105b70e133dbd996b5d48d3ba79443c87cc5321a3480c92a49a7103160b690a50383c5124f8aaa8306b7cca237446fe3d7fafc559bc611572b1c38d5c0b4d0b6874693610e8519129f9bec37afdd856cf7872cd194ff3c345062319c22012e637d636454fb58f2f04f63585d4990884a559808e2c0f5327e4962130343b0c2b7b7588af0c16a9a057830a350aa19605d52a10bfc5f8fd12c98cee5006f041588caf1e8acaa74f924fcd2fe85bafbf1cd2d912c4c0bffabdfbf0ffa004c8093803a0ccdf7e0058fe8ba6a96a36c9d2029f7050cdc54ce84fe8a90ca3fdbdecbd375eafd4639cfb59ffb9beef6334805e1a4800fe15208284a75f29284ffa60a872d69a3e9cc0c148b996588899c58a102498c5ee8f3ab1244ea954acf69fac11560d4deddf871b029641e65be06250b95c724740d090642f332b866295b4f530b34c9b9f210295cc63b76445246e6c2232b0494e86eacc71c7ba78880431320c706c799915c3672154c3bb8eb665e03271f942f669f798d126d70397303ab331d3348afb4ef945add708b5648af7057a34e79e9048dd9d7646c31888717b69c1c06120cdc5c2d893de72735811d06470bb213d45b471600b7c28f0deeb4d1c68fc0f6bfc7c84de4a847269a41a4108081b4f86e3bcf01f2c90a196cf9d097c4d256b120aab11252f5bb84291685c9a1882f5eb30cabbd39c052790ea557b9ece5a21a977d1754d9e2a75ca58a88caf638dbe44cd635694b1212bc845c8d4bd0a13b59812a1e2446dd31fd498a9c82a7aa40cd59d0b37fe93e30e48c177e0eecd984d57f24e8a6825000ec43a744f25a0dbf4c99d6cc28ec58f502cea64b45e1815699f012e48b6022326ee5e59a178a65c0f6fb7349f022f0267d64e4565189e701bf369328efa001de136981a9c7a8c9adc2a6b5a24f03955364c732ec8d4f96de735c91ae66e1d42d09bc69d994bc84fc84f3ab7a24a04af28c31b51ff0c245bbb693a552a404b504ba025b865a885b05d80f606aa490f892f371fc9ce5ef7aae04c42c531cc85607065ee93e1b6e04fbbe91af5e353f4137c71b6824c3e5d5bc4aaf2c42679a28c0a1127c49775268de8d06073f3462898606f53dd46bdda814ce06dff56d209d9babebbfa682902103ec403b7b3a67eda646593954db636b6c566a6f6b6d99b35193d3a7e7bd414578a0f9ebf6380ffc6c75134265d07d9eddf9f0715012ded40ef389323e21ba89409c5054188d963d9274eed6aa843aa89ab93c70b0fb5c1aafbcb9acdffd5c00624c0636449084677d14e9a4f9fe077ac608e377bdcd09ee77fb4695fef5f262f0f5a739d8b1186505968df2a709ac8b6fe6f9648ebb912d3148dce2df166fcbcff5fb8906b6e55e76c16440fa2dc46ba4b07dcbdf053671181b14b2867730ded89352f6762d2131897373e5c1d2d7bd17311540319179def5269eff093968da077ae2b3ed8ca8caae1e7cae7a7f2deaa2b4e71e61b6414d3a69fab1318c0e66e87b2f12a57ba9218f72be4682939e883f6eba3c084b8927078b24775ec1c439e8003c8f2f345592a2112d355234281e3f4f1102311f0f72a88dbfc3cfdec5049b074607b95bcf1567d1bb1d6d366dcc96ab88e4319e1cefcc0d01f2b4e3437a1c18a62b17e8417ef1750d1327d132a97f2f4906a535b9024e0cfa1ca426357ba51c2fe7e2b98a37f44ccd907720877eef03ae6d17464e110991647f5bb9a6fec32f3bb62625b6bf4055ffe483885ec0f27fdecc1c423b2c98104a42020be21e269c80b01bc1a42e7fe51cd9155ec1990923da1083112d481a5c15083853c152603bbca66fd55e490038d8431f95b27d32913c2b14b2b97091d4e121a224d4056954a8b4fd7ce060786c19d864bc5374de2b0d95518b3b636353b3e7a81c91efa6b3ab0ec149d7f63805ceae2aad12b7da114b59736fca562438d149c648548d7f152625388ee3231a8c2ba1e607efcf044f41813d5c35238ac945594250fed10506be73716612c949c3961355029f1925adf7b612e55e41f091b1d148560c96896e67adaa1a63abf2ea2ac871a4213b23680ed1242990214562a717b9a0db90af49430775dd16bf014d87e72d2164933db038a8e5fae4c038cb34e2bd2896624a1d5d14a93fb4b8584764579f9a855d8c619904aab0857e3b01d906eb6e4a3a1d9c4c384e162cb4eedc9af57205978ce37df4ae5ae90ab408c2c4b87dff06e99e1711baecc5cb3076237b71e4adcd93e7e59100328b45e873c3a8e6f55728f6d1d22e3c87837dcf5c0c7516361ad67492b9ca8a1a08a4e2e810755a849ffbdf9d46dda6c50ae75635490e1167b043c323ece0a7d8caa7fc0903178ef9eb52bb89bee3db2042d1f62807131cdc9841f78280d50f542b382378ba84fd4b9f5cc3891c6d64aa86dc0c953d65402a32858facb6645f2fb6a7ebfbed22063544059636fa1bf80896031a21a7f81598c22964620823af91f0b090b7312a0555f96441199481bdc5847c30d57991a8e12230923c0c89b9b0e7b1ed11fedfc66e435a98e921c4381de0dd271e98e6413feb35ad233651db414678078d3f895ef03bc76a8e9955a588cc3b0596befeaf8d07620f9335c122477eda59962649a91354d21069a1b657bdce1a05587550d0bcea2bda8b4c3a3460b0daa9b92793800606031cdb544a267292f1f2143a5b47f2039febd2f004b36a08c382f438c14f4891d475c768174785d0402c9380e2d4a25708aa57c0f859a040e6a56f8007daacada936749dda53ca450157d142d6934763b6d98a48375820cbd0ddabc250ac69923a8c9c32de27a2fc292db36a5fd46f0bcc3d5e81fbd1214b864a4e0625de6ea405f8c83e49060575cbbb98c71430691cd0317a6682a1dcab873bdb8c60dadfc3393b0702365b6f62ad2c68081870974b858d42c4a70d933682e154c02110253837f7ed03c6ecdb0f98ef169aa09cbb0fde0a6622013bbce2498ec9f57641406a16322c49a6e3488d42c6aa3380d44482353d1833ccc271ad9c5001c64cb186f4689fc49b648b63ba67c834811081088109827a7a3dcd6a963c3fdfa11f30c459ec1a368c0f022373af45e496a279d20e2708b2988ec00cd3feeaa7015376c553193e329a329beab58c5391797066842eb1cf7c66175c94840a22210bb04fe043d40bdb0cf98ac77428bd8ee27c1940ce19d0e95c7804bfdcc66dd845408cca814390920d50cc832aae55947429feda4863a740343275d70ff280a5b14fe2276eb0027763441275266fceac95c0a8d50204a992690fa476a817de7042f07a559465ce92370944fd4af0119f1f19c4facd912b97c04dc282f3163b66cb872cadcfb04b4d074a9268294e7f251911e8a52fec35dc6c214eeb28645b01a917965eb92ea72378c11570fc94b17d1c6cbf2f73a3a9343d96760cc3e6b1a51250512d84f6705a38366c852b7d745c3dc8d0f7724997d2c91b40a0103d6509501ad6d01527087dab45ddf12090e9db0b0e8848e9396601947a40bbf8422b2280a5babf82922655129eeb7fc370f432400e801a9e8f2bc172549b9423dee89cfa8610f4d230888a122a91b3ae9d0ac1994e761358ec4ff8e0dcde94c36ec9acf3328e580435c58c1bdd2e1021dc771a86ddabff4447c30f7e5b9c11f129f2a5b767302c4b50ede857929d40a6f2441ca29e37205b19293660411e6e6b6867e9221792fcafad3cc23a5e50c66f177e66e5f69d8dcf2cd006ba2b4b670366b9c70eb6372e3ad8e255fb017d18ce5a8bed9feaf199318daa919a41fcfbdd94350b29ecade1ec2d9b28158f5068bb513b692c0daf74a2c40d4f8ed8d913ec4e8e69723718ca38fb624851a9c516ab8bf26916f54efa9bb55b8dc77842985fa18f7a8c9b9ec1e92f1963a8e2bcc67cfa2fadc6b874d519981714c42383e87b7cb806c7cd8c1a94ac9a21d327822caabf8c3e281160e14b9e71e4cb82af363fda44241d806d7437a3235f6c6e5f8b4042ce9a7b34b7e4f344dbadaec390d9ffe5af9a9ca96709ac331c1a326ad5af87a12af109f4c73b278e99bcc4a823808fadbd8e0a67836f6fb02430327fd5ea3446e70812044aaae5546ffcf18fdf76067304016ef91f7dd871a1a08d2fed01cdae33b282149dc73ee21d34eeda0ade565985dc774ca80df218fb9c323dceaf4c2fb248e1973d06b115ba80b181465eedde309e8aae127b7820bfa082ac6fdf13de1bd44bf05e5a6a5f4a0772a781b375282f4612724f498795d7aa8dea15eedbe4b420f95f4018feee98aae3d3c7b07cde0fb0da1608511edf4ebc8aaf337ea5f9b4b1be6178814e3d471ec253b416bf5c71c155c1b391b26d4d8df35fea25bb7f300e926b1bdf1238e0a976faa3dc4234af696ac104e6980026220171a72994c05ed54bf1a38cf5d6befda0b9d0d750499877ec99ed607a52f89dcda1938a7da65eb506a2b31e5fe3adaa7c247ce11ac93c46fdaf464fc6a8e03db77594bf609fdd3266420da26402a00f1d89a42495c03c0592f1b5d1d61cdc76910287abf9e1f22d0db65adb5a922dab45972aa763df2b50907f39b94194021c9c0deb811bf99f91e48a0aeb9bc424dc22dc4ed1855d24931a936b04d8006554fa9971e957e1edddc8f90708e4684813756aaefa33575150db8761c2a2d9e76192f853eed6951afd4f11b9db4183fb7f402300926a3a6d6e8466496150805b3ccb7d6a9c0e908daa0bd9b0c0043382f8248e694a19461b8cbf9d59a773e193e8d8f32e470820ede8c5ae5b32e604d2bdd294e6d62f6f374740ee7e31abdc508255a46453f6358a90afe9a1221eef95d0c6c15d612837eea073748919fa67c271a04bd921306b93744cdfe2dfde40912e20a995ec1da3581d28085e6f4c7c3a9943741177812fca5ac282c3f624e481436d7950f5f86e709d6633c01c65f55f422ac533f179ba7bcbde3eeed755a7f9f9c97502d288f0591d57eadda9954434379a9138836e15bdae9f2df166b600521300209fa207501e38047763572d1deb63522ebf7eb98e2fb99050d0ff40a17428f77886481f2d32922e6c69023180ed1c173780155dceeaf14ce84f9001a2403ae8150c084ffa01557cbe9e3635834e08280001fd01bb042ece2114c7b8df6a9399f568a15de84ad603382a90c8cdf3bd1c292920116b1b8581428c4a8ed4b8b759b88f5c1a5e00904421ae8f7580323d03a760a70f5671198cebe093a12560e57e36e0315515cc3ad4db450fb2b90a13ea5c3eb8ec4f2f49d881f21ff7348b88460857483550813ece04aafeb2d04d930293c08ac2eb05753112a209832b6f7259bbc2eb7cc53097eee0a6b825ae3a1f8480f1d24055e8125a887a5202a77cc8dfc73181a6c82ae4024c007deb050284af037e2f9023eea874c0a2b20b548dd88d803f82ea83e8a10b7648208fc795ef066ec0b02405e306fe2aac418aa6f091885a7392a010776081d20879ea00440088426fe206102bc4a10e500fb9580115f4c0fe33ff044c1ce0462081adc04ae7004ccea14dd57e2c4bf016f8422f04fd241056fc292501583510e4f7a8f1d7d5fc409b9028e8823e426b67f76024f8bfc7f21980fd201384c0e7b824890087c012ef52bb8b6aa5cc4334b70da1a040d9306ef4169880c061dfe9aa0ee4d1f0e80c8e64fe5f157385eb7c82f72a57f2ac15452f463831c0204d7004ee0f412be070030768523613ec0375c7a545f85a3e20aab037304ebbd1b5b2d4c88508f0cb9e98fa4d016eba2cc82f840365889f1c2b9e00e42042e78834c52b14012d8075366a9c049b006cae6d21c5d5620c68b224b4d479423fdda86203db8146866f60049759ce9c0bc153c7d2e33e4a4fa688cbc4e286bc68eaf57b0d567896af7438c1b8aded1849a348e479c1689507c341cfe2511ca051a237b91279dec54c67dae6347ef1d679818cb39118060bc98d541826eb2a7e48aac198f09d3e2936fde877a9b15efe1da2e41748eb2b2684ae8579ced6395a899f05c3cc7ab048f103c6bc52747feb00d06e43a99a03f8bd47a3d7d3a5a9f610979429ff4715a9f5bd52cb8118a731301ba2ad4662a91dd485beecbbaeb9d44d29beb1c497837447a7d981032d6326ac9dd6cdc4450b31757f7b878b03ed2273464032bbf5e425dc5e6f8ca9f731f5bafee3ebf31dcc1ad4ae867a4b7eafbc39eb72f6638fdf9c3516dca752f42706d21076f75e561ed79e8f83cd9217a8bd36514b6e3f34bb35f9926ca2106c59092cdbdab7dd65e43be26c14984edb8501045df750f700902b872ae4f46949f15aab521d1d861e8d185912ed1ca720c84779d8c521fdee6c3de7c48bdf938907b6efb643d12141640e8c147370aa1171fada71f377e6b10e5e977ecd1dc9d3bb6d3a9cfa0744497de57ca5b043ab8da683634e1142cf5b6fed2e9c4066e277e129824181509532e1ed12ba48a51580d59db4107ca4f4cb2eea965f9ee38ffa3a8c6c039b0727feeb97c05d3618eab14b73ecba0f25053b7148483a880405912c0366af146fe2e642c2d8fac883d2570890c2213912b119a682188955778690e838820668898e87d89ea27314e8e1a770a508cea39f347c6abd5a2429685be5e0088d1605a8811d43ae663568011081f27f78dade4f31a4990292416ea18c83ee5276f58f243efc3dbafb33246ac316cbb3a6c0736e8098fed1ed4143d84cf192cf9e35d0c46417cc6713e998e176208777aa4814d6678209a1193e92f7b9683b5141f1b8462c2b1a3240880540ba94e4f845e0c601e3559371b370b1c382613171e3d6ff9e90b58c63e0bd98d21c8b493a9347c8446e9b588d849239f5b3116aa2054932ca153ef8bd07ef19e610c12ce8e4af538aee9dfa7fcfc5b527fc6fc199949a35f5ff818eb70b0bfda904eebbafc963ce0dc461f0392cd68e908ea1248861929c36b55e5cd7ddf08ae6975c132d15149deb13c95d5f2362b3bc6b3e4d89e132799bf7b5e588c6b45e64f2daf01d3e08bee2c72b54635ff122bfce32f2ee82b7d03e449a909f97c953b9eaf5ede9cf1cf7c811e010ff1f3a8b659c2ff47b8e14a1da72710fc6b9fee2ac918a88ed2760a254963f5f24881552f0ff4a93cef25ca312ee50db3b06d9e151e2887ab7f2adad5a4e0d24a2dcb9e7ed32ed8e52d361b0b3730151225deb29a049951171828a2880643684ceb96daf1d9553d1c286109c907b9d1b0bb9b9754a29f1495949cb41239b53018ecc168c7b1e2cf0db1b1b13e8fe6658a65d7dad03d52112bec9b0280ad7e624b9f6339e88599da5240d84bd0a74cdde824f74c8a8d8111a5f6a69b0699949016e88bc1ec2e5fa753ff3c44d7da185071c0e3e1ddb02cc410bb5952129fc390d844d9a631787a22bdd2f96d4858feae911e213a0241efa29070020d6f48813a74b671f6f16d17bb255a1d9ad0da7b539bedb28182d741d1e00eb03620c23d8a97f202e81941cd394c397c295eb4873caa16080ff160d3904e1a301a8bbbd6004c599bbc88b24ec33e24487b2493294196ba7c30a52d2590df5560a590d61d49dde8bd6d46daabcc9df6349cee79a0586b01cf6e124628dc570081d03dfa8ba543081d7fe8422208d56840c1504401caa2ecbf2552880e65158771ee3e632573526870a597c1c665865c11755cb61c08901f4b23383e42624152bd171e7ff41ed7fc7686dadf7f3e5581cc6343e939e409c9eec06b64229c9f566750ea0e84d5bc43273439c71b60f519585645ff2956d548c797388b4fce850d80eccb5872674a41d14b73b70d5304fffdf1d87e51906baf9c2099f09542bd9e7813af7f1d179f9ee78acf27437656814e9888ac7121f945245e0e01b32188364a2c4474649888b7f17424fda91410543c0830a0f7f8717b6f9d05ce493df29d96b2a0df4cd4fb078e8fe5144ee53796e23e67b61b4ad91c432e17347f30abd7b18ef2b82aff8d6e83d93ddffbddc5db210f4cad1a529bd1b58e9c2e942daac23fb0c9c543f4447d40ec939b2bcf6c6d81be864912a48a5adf7aab529a4bece41b419becc040bdecb6afbc6d9b49973ba9a74b17c76b018558027e2e84b10a3440b2a4d001ffffffffffffffbf4ebd655bdbb5cd3203da97526e69011492984b29c994524aea17e608218d106cfbc2f6b31bbc04330ad90a850a9aa913ce652d1955314d16214e7873295a75e89b0ba14df8a7a4249432419af004bb2073ac9d4c33ca84ffe225a2359a4a3984092fca499babc4d09c4a2ee187bbe79439c4949558c297912966372be17a77753e0be79f32947074ca65c9ac840af7241c214bfe4defc9362c09c7d2c42c25fb4878325909234b90f04449d1d6848b1b3d3fc20b3209224b562e96ec08d7cc34644c677f1d37c235b93dac8bd889f965845bc2781242447d70cb22bc777feda0a38a70dd3f663231f8bfa989f0433e965ad6f0d929229ccd5497b493877074884eb23ca6a8e519c253825ab414657ea715c2ab110d0b57c266aa1221bcd41de4c3c4ad579506e1e6d774316d1284f35e9224a609f5244c8170b4a6d0594b8070edef73498207994f7ff05b94d81827730cd50f7ed812ddcc9347114f1ffa94bb930c3a091a830f7e0a95e63d59cab424688c3df43147b7943d7a7036dd9470f62699d6e4c12f5f9393a8223a28f1e09760620e7626aedddfc13755bfb299243171dbc13599494e66413609aa835722f3952455be74990e9e24fcc6953057979d83b3496a7abf4d5e522b074f5d859edbfa33370edec9e1b249651bf47070fb64273bbde16f7c8327b8492699aab8c15f3fe1e4f9ab0d5ebe8de2b3f229a4c9064fba2e2999983b0829aec12b557fba04e52969a8064ff2183589258489280d9e798c6e4aa4978a1d1a3ca1447bd26b31a6ebcee055e5b4d983d9ac6d66f0a28938d9c4142a83dbdb236a7d36cc58880cdee953728c2234064f96fe9483f2123c460c5e4e954cd9785c304f189c4b972f9885b42f0f187ccd2948cd64d2c4a0e40b9e9cffb593d41c2ded057f8332150bd973ca2e78969596e73d973a9f0b9e4e299b50c1df827fc27470bfec6e522df8b32925995d67c151c265e7fc3316fc8cd9bc640cab70ba82df29b7d6bb67a58c159c8dd92adb08ad4a962a3832a81813b6ddb3880a5e4a8292846cc779760a7e0eeba4477aecb2540a7eca966e3165143c55e1777229a1e00715d7498ca67349e7139c515e962d78c868824e70ece3e234668bb162131c8fddbe9a66c3db638223469cdaaa35c9547796e046134d76910bd24d76d2f0f2e9ad12ad29c113a299c4aa609da19d24389fe173c5122b9a641d24f8a7c36bc57497e49c7404ffc772650fea2f83690c2338fa2a89366a4529256314c12d496888cc6818ae5d30b1a414240c57e6be748e3941a8c1f0c4543a3e338949c5030cdf4a2841a9365525fa85efb1eb4c83cacc24bef0356906ddafae79d30bb724f9cbc25829994c78e16b5f88c987b85777e1e8cf4e1f224d50daebc2ab4ed27a9998498572e15beacc953165499ec685a331b7874b2774e5dcc2f35c39297daf2dfcb48f31cdd6c24b19eaa364cbd27469e1db7b0e2a484bd2e667e128f9b88ea954d62eb2704da62409bfc4422d25366a4e85859f5cc62aa5f9156e6ae5d195dbec4eae706389316a92b173feb215be9885880b9e148315ae95da2093ec156307133156e1b6f76950ff9d5dba440c5560a5e45c2a5ccd15834c5d366264092a7c4dda7395a0738c6bc9291c25c518643e33dfde14ae9aec2393d8d9424da570acba52fa0b6e279848e1770e756a55e44b7a46e186becb1beb52949a88c293c436dd1674c52026a1f0733af97afc34df0714ce5a07ab14a3a689e6136e6629dbd0bc6d9af1841f54badc21ed4ef87177f2c65449cb0973c24f52948d6f36e17c5736b12f65b58c35e179a7a86e82b05333e1a8c77c90a1196407139e74de3978d6eca5da4bb89d338ad4a8a50425967035a5f9f63541e5be125e5be64ba984094a4a29671a25a8bb4cc299b9a492ee7c2927497876820ee2445a345b23e1fddf5dbb6d063541c23917df5a4b3aa6271fe1793ee5b33d2ab64347784a86ef0d67c1abd4084fb68c2f1965c43f6abed286b6084798694f7248119e1c93ba3f71b3a192084f6cd26ebfbe4184a72b47ad987308c7629aec6792aaa4254338769a19b2a4f9a510be7aba1eb751254d84f0d2834ca26c4a39e507e1f7a88e66c24810fe49e263a5cde3994281f094a48250da6349383980703d9558635f2a68fefcc1f512356b7312934c397ef073a59c3be59c66eca40f8e6be8ec2dd97262c2076f3685109e354927c9ecc1ebecd0fa908d59367a70448652795dd4dbcf831b16f3a551e3c1af784da9cc4464eee08789dbf69578f4de0e6eb6646e26a65f8c3a38a6d6265a2c0bb2ed17830e7ea5a98fd9ad366bfd62ccc1b34c2928c1945225c32f861caa7c29944ca21c0747e96ead36f10da18583a772eca4a4a437b8966bd6832ee1834e718363ca332bc6943638f25146c64d52ba2d6cf0748d9c201a624ad53538aa2e25fb3bd5e08aea92fb6419f39a064f6706a1bd349c8d8806bfd73cd7b87ccad2197c13623bb9e62ff5318323837bcae8af0c6e0a256325f92783df266decb7caf1e78dc1d3a3e77a63b68f1d138357952aaa2d56416c1f06b79208334bedc1e0da064b3176fbcc67ff05dfba36556352d288ebbde099f429a9bf183c675217bc9cda4a92432e78e3e127ca4b8991a52d78f9744e7d92ac900dd282db416a725192054fb4f0a144c9c1826b711a73ff27db3c57f024cbe53985cc0a9e2ca2a29bbc65094aaae0acaaa57747934b9750c119cfb3e9c65328a99982676d1acb45d7c592143c4912a1442dad29fa1f053ff797892e792878c1eedf92ecf594c34f705468cf615927f8397b4e1532de5ddb2678720639499bfc965398e0774ea2ac76094a46cb12dc4e5f7239a534a22a69f8196e54eebc2ac11f3567929804b153ab1849f094ec71e9528e4168550c2478399f8eb96b333d45c53882f329a59829ff099352318ce028b9356538955489528c22f8bed95e164d6a50e6303cd173d06292123a9d290c4f635013cddce3c50c86339a949c994db6c912186e275d2a7b3899dbe47ee16af24f51daf9c20b67d14ae5d43d5aeb85274c3061654e3da579e16b4a194ab4945b2ebbf0aac4dea8f5f619d5851b4e8e954e562d815cf8a7d2880ded69de4f2c015cf826a7923f73e8168ea65149c97da90f1f5bf8f29e3dfb492dfc7462284b62c96535a185b3a5f47a2c99851766729fbcb93d648c2c3c69724c5e426c508262e1561016e4bbe5f305165e78699d8f1093a2ff0a2f86ba9c63ada90eb9c2cfa08488d7dd0a47a70be9612bac70cbd4c5f6d32abcd7ac12930ea56aa3aa70947a7cf9e5646e662adc4a1b9d2425a8f0d4682a29fd770aeff3988d7b8550da99c24d42b6560595c259f70ea7f348f50b29dcba92c48c16e351f829f666a70a1334662c0a4f305162327571283c71153c27e1ccad4ca0f0b46c66bb3ce227973ed1673bcd9441e3093736945213a34e38b275aa624a4d7f8813fe8cc98775499f6268136e2865f2b78a34e19bbc59664b27a13e27139e785fcc667a4dde8309e78499fa311984303197f093d69ed395419ba8b184a7dc2d9a8b327993a6128e8b6c4c25bf554a794af81ec4af67e8684a924978490861d274cab1e19684ff96e96fe548f856b263569324412e0d0937f72541c767f7af1ff195981446cd8e7094ae249c5fbc126884574989a91a7412f36557028c70ebec83952acb4e979540f2c75451849fe9315c90616412a46604fac6201fc2f734aa4d8e495fd2e21ac2cd59548cb1f94ba64b0be128157d534b9884f02a36a3c5668ce12f3808ff3297a64fc2d5c74e108e0c4a4c499c52fe1ebb403827bdc807a9314038327f4a685bf6074fa7cd14bde9dafac40fde6813fcd4f57d70d5469c2449ca3e25990fce7b769d9694e46055cd00640f6eecb56462ac44b992a407c7635a134c45797073ca96941219840764ec641bcd4e74074ffd8ac574da9bb972ece09df8d59dac491d3c59aad478d02a6a64123af8612edbbb0425da5a660e7ea866f43ed1eccb7f39b826a594b22de5e3e0774e6b8f6ea1c9a20f072fc87f50c289312699fb0d8e8ab94b53c7da0d9ecce3a3949828ea326df0bcebfb533eefa0ff65839b6257578965ba73d7e0c9dded5183e31996efb22c69703df7860dd75e524c1534b8bd6a3f4a5bcc90969ec1519b4f0c972976cb976670c4c56eafd3b00c8eba583906cfe1e97b32782183c86c9faf82cc8ec1dfa86de25a46655c4e0c9e567509156a3b8c4a183c79fd4343068373f2f9cbc8d240bee05df4d818e35d97d602f1829b3d35abf29d1cdb49902e14acda6a6444d45be32d798f4aff188370c18b9916d4c4f419c4a57bc005902d70a5ce54bedf44455f00d102972984eeecb4ab0dcab1c3055700c982eb76628bfe12362de6981c6490f1c98c1c5501102c7852c78507798fdab071054f9d12e26ef36cd967a0b1031d3ac8b8b143c78d1efc0e2bfc9f438334bf2c8ea4074a666ae5032055f06a438651e2540e2364260042054f9237f668a68a6af5c1b17643478fb3a443c70e1578e048003205afc49483492728c97d0c714108508e1d2e501029788212bc4dfe2a49b8d3a60d902878d9b13d4851bf6216c2b166860e1d3dcc18410281829b041953986c213e53480390273897b4da289951ed31c5099e546e8134a18030a1a401b2844d9034ac24e98ca19992b8ce6c478f33ea00a20447b5dab3db647e024812dcac50eaec52121024b826a9d81cddba52c936470072044f69d5e0f2be22a6e4418ce078bce731d93363e98314c1cd1821deb4a4c3f04e0625c774b17d08c393821653626aed2cf1110cd773ce9d4cc78a10d91fe29d599515e203185ea813726616369d2c4ac2c72ffc933c49d945d7dbc5ce175ea9646e9793a4c2d332c2472fbc6c4174060f9daa9eed237cf0c22f51dd27766576e1dabce6243ba7e60a7a5db86292ccc154a658d6f823176e0cafbed9b7a9eac2b8f043fd05e57de92d9cf18b8f1a4aae1915e1c316cec88a3de6315b0b3f4d129364626d52f8a085dfd7f561b39b86cf69168eec51525292b6c79f46168e67534273ee8d353f167e67d216252aa77cb28685dfe2dd693e557ba9f02bfc998fa136a497ceb2ddade0c3159e9463b6f28f492d876d0b1fadf04d658931560a4ac57358e1fd28d9c46493abf03644e73575f2a71e57157e6bd87ccd784eb29b0abfb2cf656730e1b15250e1f927a154954cd3ca487c80a77044db4eb3c969aacf8438803f7c98c2db9cb1a2ad9af29847a5f0749dbd6cf9068d4187148e38ffec5e2a69666b1715f1310a3ffdb6758a5a27c9413902c4831c3d5e070805366c84b820042cb061c3860d162010e0e8912306643cfa400ee34314de56e774375525b557189161100b0e171fa1f0ab74c718ecc48e2dbd0d1ba0f0a492cbb0ec58be1efd847fd144f66635514ace74b0830325e0f122b833f340d50e8483c7abc0042188021adc10c0293e3ce1bd8675fa203b7c9a0ff6e1a3139e687ffac4ee5c7a5ce4c30727bcb4f0d14ffee826fc3731d9dd434d7852f986a8992a138e380bad3f9382096f6d7d4cb6eb745362838f4bb8fe553d9fb297927c04870f4b782a284d9b212c95acc14ab8692634a72ccb991df441093f6a34138eb54978d1ea447f366582cc8d6306e261a6840f49b8196a4e9d2445114a1254c7c3860d497c44c20f2f76bac23e37d00ebec75d0b121930e20312defa26f570317c4e613dc23bcda13e9979cd28318ef04f7d93b2bb4cd2e824eaf164dcd0d1e3060a3e1ae1678d26f5c5497d6a1d843c58d023c7413b3cc0821d243ac8e8800e17f0c02d7c30c2f1d0edaa2527a93cb4086fb68275584c49640c29c295d53e113b4f223ce1a430517b3ce33f46849f498caaa828d17b3bdba183073b9019668c20c4033b74e0e8810607cad0b1030466e400012c3e0ee18f4a4a7d3e7b4ee55a031f867046a792c3a9e814155121fcec1ead644ac933460f211c1b390b7eb21e84f79d2e092b23355a6705e19c64d278cefaad0fe34720dc3ceb318c7af558f50108d77492948a4952f5f39a3ff8d92f17e353f40925f583e39943589b0c1be5c33e7cf0c16d4b1dbd4e4a0debcac71edc92c4a87ce8c119e99abbb2736c868f3cb831aaf6670ee517333c860f3cf8b18493655af3f9a6e70e9e9429cf78a960191ff4860f3b38a3e92cf5ff7a096e0ac3471d3cf5d755dd314247190a1f747083922bd7683a392e5b7b898f39b8be5a659eb2a652bb85c4871cbca0954b69860e3e2756b6838f38f86141a7cbb1c2c834baa3a38cd279f880835f32879d503993428a3ef87843c1b2b3e6d25cb2697cb8c14f9b2fdf694f0de1a30ddeba9f98b7f213222638d6d8e0ca5892bf2fad6f92f226848f3538b2e131bb7d85ca68aac1cf50afb1de744a1afce0a5347cf6cef02dd1e02741893d3255ea2d6fcfe06a12d7577217aea46a06e7547ab7ab3229da7567f828831b8370ffcc61c1c10719fc7f91df4a49306144e7051f6370c62429a8bf9283e764f91083179aae9498b29669e1c247189cef2487f4d19a938efd07183c4f1e4b264bf132968371f8f8c287175cefd4ec6e0da6550eb8e1a30b9cb418272e38ea2dbcf2956492e67a0b9e6675bf242bac054fc99be24688daf759b2e0a59424f94bca549b073fb0e0092227fb7cd07f5cc14b3a093b7df7224de93eace0657977aabb4f4a663faae0a6e497bee76bf39d0e203ea8e0ba2813ae930a16de3f1e3ea6e067253179104a8dd0e1a40c1f52f05693eea0927b7638930090e1230a7e124a3aababd31a3ea0e08f8e4950a93d5449fb9fe089afd9f01ee44795886af8708297b46bcae4a2d7347c34c1199334df876d18e526267851a4e6924cda2cc1cfa153ce5a56417fe7d3f0ed735f76a694d2824509aec9b2aa129fe4f7124c82bfd173bc9c1494ecd943829b4a26d1d446578f123b829f5cfb4cce18c14d51353d6ab24b9fae3b317c14215957cfd1e4a0d23a7aec618c61f8d79acd544ef2765d1843189eab9b4962c8f8a44c29186eee93fc5db47d6f1a18aef6b88997d459a7a15f3822fc2e0977196ed3892fbc4afab6c6af62ded3bdf0ed64cd9e6154e62ef1c29199b34d8c7225279b77e169a99049ca319f42b3baf0ca8216916977d25632178e8cf6ff397887ab18c4859fc65c4b63da6ee1a75cc1bf3ea69613ab0cc402335210e201b38573339ad32afcdcdc7486316ae1473fe12da9fbf9ab092ddc14a1bcbf676dd326b1c218b3f0ccebb2879eef9853c3853164e1c9336fb6b10493c4fe6251a6b459f3c8060b37c9d5264db43a1edce84116c678853ff225c9418f27b1de76852383b074aa495a775863b4c2ef94426953a28415be6576d3a92fe7fbba57e1fcc69c3d46735b8caa2abcf2ba20c59494100f84e4302347421f98c318a970fc4b8cee2c17156e0af1bf13eab5bf621b366ed81ac63885d73998f06ec289a6968c610a3f8427a541c7602a87394629dc382997545e496390c26f194d16b49cf8f56514feff95e5bae06b92b8e007a8046388a28bdd3116c6b3b44eee33eed48f565b7f804a30863142e19f7f78c969adc600859b3a93a6c8de123ee9136ede3a9d2d494f954f9e702b5dd2ee2525a9d65fde20743079143208d7bc664eb6f12c2934c4830707b4478820bc241a52b3d2a970312924108e5853255b9b88af753a0801842bdb569a95e1b72af607573ce5ac4566d60f4108e903229b103e14930efe623a5b65101142f670217af046ed4bfcfde6380fae9f70632aa36892ee5e081e2ee40eb89adc659a8ecf983c7532b93c1c6b1962072f7410cd1a3dc70621757045093f29f58df84c2f840e6ee93225beaa846c97819039b841cc6387d614775b9283e7fdb1c47b13073fe8a4ae9d55a632cb70703c89362a991c0a7983db62f549b67f0a9d5337785292848e17150bcf671b3cf541491354de32f98f0df9fc474d529235683fade152f944c6104ac757480d85d7c9b9e4f24d2a2b869034b8d95b2667acaac8592168d8b292ba95a5d7ca6a6227badc8832adb5d96629849cc155add2544f9331055d88193ca1a5ccd365cbb9b7331e2165f072f41cc38bd081410819dccaaa26dea43c064fbb6913a36752ed139e1122064759a6de58f299a76f43c2e05da6ec260f42c0e088e8f0532ac542bee09cb0d52aaa15ab6ca4215e303c68eda0426751e2d81f8474217bcd639704c71a32810ffe048c06085810e286102e1cd755d6a97121227ee2ee2c14b285668327cd9a162d781617935f095a5469cddeb01d2159f0d4e4d8fc29b7b66f88633f040baec69769cfe104f353052157707390354a1eb1b9b7dc8e1e676020c40a6623072155f0c3eebc52da890a7e7609cb67494db40c9a827397fc93a9d50c6a355270e304f1d5274933db7714fcbff093c28d160afeb8e78b7556266b2a4f70940cba35492ad8a993e2042f877b8ef38e1f2b65131c33416de62b77fa2699e067a6cd256ce7aafa7109de565c2e95849786331f63d75cc792699b125c932f971cfbdfc4d61592043fa975d159b935cff748f0ca2e5cf4ab20af261572044f1294e8963f4c428ce0274b3f49f44aaac269438ae08928f5281aedf2a5576318ae299d820eedb030fce021a47889f5bed9e31ac170e4337f12d4f698ce39a506309c73f7d3fc15c524d4f88527c8e0294a8ad0da3d1e146af8221d835a12c497985783357ae16fe512343d798aa95583179e58963309176376e1c5562531a627d97febd5a0862e8e13934c1a935afcb9f042a3896ad255ceee142edc4ea33953a74a11678d5b38dff26a5faaac610bd4d252d53cc6523ebe374929949c6d3ded1ab570c3c8eed3b1e7844c0d4ea1062d3ca9d15a4349f206dd658d5938a624e92d9af4b11ab2704eec6cb484881aa51ab170649bb8943c5f98a49260e15772ed93b47798bba4573863154b9556932bbc24956ce752526c38532bfc7cd299c99a84f6a4c40ad734a891ebd09bdeb40ae7e2db7365af0d995785b31ee33dc4b69ab053e18ce7f6b97ca5aa2e0815cea893abfb4cc807119dc22ff93ef7669329fc1c7397f624052b53520a7f54ca1a27a6a706293c19a3ab9f761ed3f2d41885a7740ee5e15ab49fa786281c193c574a3108efced4088517a3fd491b99eddaad018af2e7203376ed27fc8d226318b5fa32a527fcb693a4d0e17527bcf2fc229360c209bf242d6ae9275594ba093705eb92c474a5de649af083d8f632a994269d3299703526d9efa231891613a9f956369d82bec6257c19a1b93c98df75f66b58c2f5eb1c224bf4876abf46253c95440bca934f094f95e7d3d698fb363a09c7be93e8fca731d32809379d4cf25dcc45a20e9dda3d4dea21e169cca8a8f03cc2111393a798fc7365ba1de19ca7cf5482fd67efba117ee73221c6ce92682746389f744aed5db2b7a88bf093d860490a4a9570a222dc54229b7e6249878c13e16dc93ab13144bb578c082fcfc77d921683aae8107e8e133c542d9ab8134378995e72d0496558490be1e852d26494bb95a0238457ab3177855857ea06e16679eb7bd904e16d50cb59b172095f6d205c53965aeb3f80c0e7939c248bf9836f7762cc24d39431fbc13b25b37cdeb4f421e57f62e1839772694fa3376992abb2072f43ac9f9f1c353c470f8e7b2ee93f4919c476340f8e1aede459d4a92e59c5835f6249a89693b47cab77702d78fea4c4143bf81b5e9b654cf28c8bd6c1ab7abff9381b659bd2c1eb542fca34837a14cdc153cf92e54b64cc3629074fd2317cd05faf99c4320e6e8cb4ac3f1d38b8296d3b3da98ff6e70d7e494274f5c9bbc1db204aac147c94acba0d8e18d3a933792a2b61367892ce29f8895057425d83b7224e9368a11a1c55e7a2d4ba3478f2894dea3e06254e0cd1e06fbfe958159ec113b495aaea246706cffdac6c947c19bc4ad34eaa6458fafec8e069b32498649e9450d21b83ffa56e179e93925c3b31f8395beaa06310174d1406573b780795b764d3ad81c115352526a95436576b5f70546b589149b5fb52cc0bfe59b68fcd5092d6a02e6c822e496df411173c314e35a75611a2926cc193d3adc499242995365af0c48ef92cf839b7fac9b294b1bfaa81052f5d4cc9546318c75a8e336c7301b6a8710547c64411954ffb75f975b3a861054f94a7f74b22c4b1d606031e3d921939cef81bbb8053d4a882bf1ed38926a54d810119093a860a80440d2a787974cb6c545b11969f829b71b24d366d1fbd53ac420d29382a664a26e524cd53d328f84135cba938eb8a4d0205df359c4a9935f304578430355127f59cdd56a8e104bf929c6d9427ab09bee934234657123e1e66420d2678c9724aef27d59ce85766a1c612bcb70f9d4f32318da5a620a1461a8fb8f67c2895378538d636063594e0cd6b8d099e3ac69c1a4970649204316e33e2d81a4870d475081d63eb1a47f054f455b86d92e43219c1134e565dd0a31a45f036b5d34a5e0f2b771a86a366ccb744cdd5410609c3cfdeb85ce2130cbf5492564b101f6078176335a7b9f42ffc5297d926dfa619d5f185efb5694e79280f0d26bdf03b668b6d397607f131bcf037e8fb8e4136bb7073a89cff4be7a03b47175e9c45f1de2498a532177edeb0a135658d0b3fc368ebe499bd85a745aaa5c5a5a5ad8b2d1cd5996d32f7c90aa3ac85a39efb2dafe834b19316aea651525a979d7432b370d409cb7565f2f5f6b2d867f409c2843b167e34613d97ca1883c51016bafc976726e92b9cd336a16e35bc24a132de8c0402882bbca03fa724e6f094a40ba202482bfc543a989884509926a78c0920ac784dd9cdc917c82a3cb198045f93257231d33a5880a8a2aa9439f9d779123ba9382bcf5dcca620372abc284a926278ebade0f7297c4ff2765e9a63b978a67074f0527a46e9ecac1a8eb552b8499019cb394eac7c6a258090c2919a934af2d9aea6d31f808c42cf529220bba445e19c50cb12735072d2aa79c0033f304307395af08394630782c1091460089050b8266809af29568fc98f63cd860db4338080c20d774a7fa853326f7caa00f289f5c47622198413209b607e2ec890a24fb461a36f478f3304800a104dfc692b9306158eb53274ec48366c9041c67b200b29403251b6e94c3741e32a83897ed6e6f35fb9c95fc274a59498ee4c92f2692d913693e6e4645d9f5209ad2fb99a0e3a96ec9c2fcbf13b5c0008104a9c4f48ef52f94b290f90495c96b3267d722294dc0a40247158caca97d96d8938b1a38538a951048984df9e1a640a52030209e7ab2ea9ac8658007984339629be42a6d15ab205c411ce89164e9ae9d4f0556a845f16d3090b1931c23769fdf55bf6156db308ef3af3a56c49d252a65684f76325e61c3e9d08c7f2a64a7acd92c8204684d77f3526671f1428b3809505f0eac698c01866a3141b1681a4070f34761420003f301638880100e8c1030d1610200048078f860ecc78321020001e3b12700200000000000040031c903c181080360080070f63830002f81e7f6c142000dfe38f8e1d360e0080014020013bd00e92efc1e3c60d070020000f3800e6e8b103070f3200d103f1f883a7b35cda54a1254941ea074f76b0f8d9e83d26667df08210dda333a5b78cf0c18bbb3ba9e424d9270fe3582b65a03274640f88073aca2803957180113d608e1e3cc871f2f08333f080397a1c1d65a071e3460246eee0ff5c29250899c3b17c86c6591968c70d1ee438374e8e049dc1a3ad871939908719397aec4064e438a30c1dc90a46ec80376e2060a40e6794919481ca30c0081d30470f3270f0f81b37123032077d4e58ae0e79e5e06ccc966eb3d6913878b29da04987abc820038d54e62370703b5b8926e7134db4a037781e266cae5c991137f82656bac7a49949187ba40dfea6888ea5a249c86e47d8802cb7bcc14bd2c81a8e5a23ae327047d4e02555f297bab524530c1a3c6c03236948d014ad53e3743682066fbb2f79d0a0ef64f8f5032367f0525ceebe4ed9cb548f636d2f306206ef55c3e23a2ea6fe348fc2c3032365f0b235437848cb4c2ae9841132944e7b3e694aaabf6a640c411811038e84c16ce81801038e7c0147bcc06c46ba80235c68d3e53b2cb5ddd59855a6ec4a82ac93c4890ec71adac1bbe08c912d782ff7266282aa05cf7a849674266ebd732359f0c4746ad0a7a247ac8605ff4657261393365c5d2357f05cfc820a3a4647acd0d5d869bda554d1b2adb24997be3ed748155cb1fe7a732f51c151d154614912b5644a6d8491297c9d3d44c9999d4fa4c09dd858c77c5a9c9dba944e6a240aae9647cd3146050a8e52a7f306dbeebf1c3ec193cb1c3dbd5a57ca718233eaacb53bc42fd5db044705cbd94c32d99b3f650242e5ec9f434339b2046fee248deea6bbc1481abe5d7d7609db297c14e130a204cf746534fd75d66b9f91243826c5e612ba0499c7d711243826dc9755b83872042fbf9aba4a52beec9f1b3182e7d6eaabb9c5926807478ae049721af5be1e2c839a458681c2c860f8ee276d4d8a689b8ac0f8857f99b67bbdf201324375b0039d006f07ea91c3178da5abaf9b8bfbac54963d6374b5a42f498af4c2ef7c16d761ae1e6de585ff9bc3ffc489fea9c3bb703c98ca77a5492c10d185f7a2f557a6745c524a72e1c6972c4f92d4ff1b1a2e1cb1a426e749a56eb1b6f03d861293d128b23a5b0b63ab57543b51a48523ccc6848e258df8ed66e19aece1572dd876c84c16d596b859ccd59d875ac86aac0f3317b3665c9f51468222b1705318a193dda5d461926c1081c589bcc273f1e0171e6acb6458c415d78a1361c5ad025521920abf3a8926e9d2a2c2f9326131575092880f9de2444c514ae1891ec5b49494e5d16344486136467122a2f0dca4f9594b9d434dec1222a170eeda63d558470414b956675d5da98b55dcc58ecd944294f040e4137e6837ad14cae4094f8e9afed7a429d209ef73526ac4278f08277c0daa4426b56a726e130144426413ce68b4f429d4ff29318926bcffa825894958d29e2a2299f0d53d26e5625a4c38dba33ecbbd0625d60ed423c7a52172093757ac7faf882de1af7528d9fb531d2295f04e1263ccb499bfb3654a38ea7fb3f5077712cea8c7a80da619b37f24e1c628316dd53466cdfd8e924422e1e5cf1af449efab588220a198669273346123f2087fd4eabc334511174d8ef0c263ee7c625552926b9146784273bf7f0a97968497117e1ea1c4093626a9fa908d165984a222fc79d14a7e29633a2713e19c1232b52631846dd2df21c24f231e9fe7447ab6cbdd219cada4ea4dbf7a6708ef4b8ed99238b710ce79ce575a25c3a8247f4708dfdc04bb3341733708dfe45ce1bf32a396ecde8908c279f3981593e8196d0a84a773c858c272a628a0c18d158800c26cc840e40f5f5d7a0aa33378227ef02a690e1dd3db81481fbcb0399f7c9d63dbd927c2074fe6a0739a5ce9e63e06f7e0c6e8a54b32ff51440f66e344f2e089a6e65cdaca3f2e8c638d470e3d3c38a3477a75bf8f2a4f9ec81d3cfdd7bd72ae881dfc55cd49a78b8cef31c113a983e396ec73e5e0be5d693c113a58b6766a39e334e6bd535082ce492c3207b75249f27627084f66de89c8c1df10dbe43e423488c4c1334925d1d2213e9f5b441138b8c13c2c7f524a30cbf937b832dae3b73fe90f1d22e38c3272f06017718327a894fa4dc8b5e38c2381481bf0d0b2682f372e1b1fe7c640840d9e49ba4663ae18f3d1640d9e8e661f43079d1abc12176c5465280dcea5b510ea27a6f2b9d1e0da98baa4677426a5b367f04db0f69c3d9beca44d3378a54ac7dc5c922acc2852063f9d8a91ae41e54e840cde6fd0d817f6a9fe9268888ca194e7e647dca6c5802261f0c3fa4f7f776a44c0e0da6b124df0e0f9821f84ff28ed1e2c27f18a78c1fffc314931e3f29c94d205376d28595c94bc696352840b6dbcc875857d67d85968225bf03e0923c63e5c98f24a440bce69c624e98f75b97354240bde5c677efb64b162c2388860c1cfa4d29625133c2ce65cc11b1d3de4ae7e2b78a9566387f6dc4949b10a5eda7a964a57279e3a112af89ac4565b26494e5a4e44a6e09fdc08db7cf29d8814fc1c63a6a376e9f858120547f9d69694363d1128b87e974b8e0b13b4fa2e66883cc1f1e097c5fab36b947837ec7870224e7054d2ca36ab8ccbcc399126785abe6f35fcf4faaa66e428c18930c1fbdc1fd7596d3c9125787139c7aaf0c15d3da682481a8e925d42f75889abcfa8031125f8e997bb44395bf90f169124f8a7cadd2e954604096ed7e6afd4ec27854a8a1cc133612a626db3c9304a96206204b7840a139476f12ba51529825faadd3d43936ca3510c1986274bde60dad714228c0c86d90086d9c01b84fcc26cec20c41766c38c905ef871c1b39a9a92bb537a8d7208e18517976427e194a0c2ac786408d9856361a207cd18ef563a3084e8c297f99c73c838132ade40482ecc062edcaecb8a3b215ea283ce8d63c697a16347e2b35bf855258ab79b106fd374b185b75d97aaefcdc4905a78fd665f9ece3708ad68e1e7ce96549b204d45ed67e1e5faa8d96252c951ab0e84c82224165e79b6a9d431f66c92c2c24b258931fea285f50dee2b9cedad5ca592f8b7fb425ce176e53bb53f93ec3b15d20a4e561373acb63c261c8bacf0c3924a259fc5ecb0cc2afcbf58e5eadaabc28f9e93f2183e93688553e1052d4265f42851e1a9bf7d095b5f1f4df7146a65134484923453f89e325c9f7621a5f04c5d5b7c168d145c2a8fafe066c20a198527a63abb8d4ee6561d46278488c2b55c41869728cd3a49a17054ac650e93724c1a43a0f063844713f2999459cc2736694dd46d5838c4139e86133227f1944e38d2653429b73984137e30952b99124c4999cb904d3817ec52eb5dd49d52856802f1b220a79666ecd3e6347a674ad7ee92d33a24138ede4a25d3c3c7e5394330e169cb49dcbc09254e8ca6117209bf4dfe31c1336b644b2010422ae155fee8aa6a62fae8f80619680725fc607723b5efa44d2dedc8914c032193f0bf62c9e6717d1e2b8f634d0737ccc881a3478e1e4842334b692cbbc96a5b5ab9a79989cf7539e57a98a16347ba71d046c2114ff2ffa84eeadec8811c0881846bd1a3bae6ec0b61bad39047f855ff224598609bfae4f164dce891838c33788438c26b8d4fdebea2be01010b42421ae146abadca781393a91fc2084f3b9a3ce22e54abe82cc291725f9b42e7ef4214e16f2879ab11a5e562851792086e4e74cf10b2c7b176e3073b7a98f18620c2ebfe98d76e2d0625ad9043783ac6128d696f88213ca56e93d41c5eb17336a4106e8bdc86932f2e688410c2038b41a824ae6582d25820188a43027128100a2cdc1e00031408001834268d0482e17014a6d2f6011480044d2212362e2420261416141a1818180c0642a16028140683826150180c0a0442a1f0759c237b013bd977dee17e4116ba8898ceee02846ec6cfb5912d1c5793b6e8b2e934af00b9d57830d9b6bc2eee70fa4906a4170f9321dc06fc002dee56d2c3c83b83c481b5fdc4ddb1bd978205f1357a0a6e3695266b7b4ae15595c79955ab534bd1f800b81046f8b726249377978efed9e71478c071abfedb9de803c542d383f7f74cee3c3de4d65506971860aff08484f3254eee1fa4c751ca2b4ba48dd8d02c0cade7905209e9546419c6193fa3a944bb6bc0a47e3871ed4f12229561893b939ce6eb22d141093556b3fe772625f249934a0e6cdc8300b84d96f86323305118c1c0b7f4a13145523dd1b9340ad03e9e593c0d8a3c9fc7c7900f9dbe79d689374de94fcb529678d5c7235b16c40920dbc075b5142448f630004e8806fc62b0e4a90764a13c9d4fdc60356c5ff0444957eea424332eb56faaf8518cba5f4e4eb06f13a0c88ac47ebcf7df3d2713d73721b92883205cc62545c4303ee26b5e89f74a5a664f1e98e390a9e92ddd04438ef4abb557aceeab9968fad88dc0484dcdec6ba6a0c0f71cf8db5424bfaff1c078211f88b812f5a5446cf68a2f01b6245a2141bb01b4b1fe1a15582527620c2d366cb3683bbc3bb4cd4a4b5c8a737eab65b9c26fc13183737aab25f7c04f4863b5c499262ae488aff9d7f2d21acdb27e23869fc5fa627068f293e81b5b1c022a163efe7d558c65fb727fadc93ece3024312239907f0790224cc51d3ccee47a463793cde24c9e111e1df5e4b25bc4cf0b73ebe08101da595e5d5454f8178c8fd92f6be94a19247484f39f3835c03009d7e6c98b97c64aeb3d8472d0b75d632f4b6ae6901943e89dba02879e7f732f33710412ffb045ff8342ba51c5492570e259fe60cb8b7a0ea3750eb8a89fb09e574a8a9b7ce4c762530e42c5ce9e1a25a774d4e681a9d413c4de1e8941f49706380a372c342849008e39ec05a517cac6ea61daa47b45c0e2e326b0eefeab565e263bb5f6b776e21eea2fc793eef39a7dd3d80ad4b3d5eb8573a852d0e129088de7c08809c9c04304a72394961a99474f08e525a211c748aa3608d3c0d965217a2536c25dac75d50e0d016fb150971b050246442579cedffd17dded06467ebabe5a4d8306fd21a82bc9f50a6f5a44c4dc7341d3f0cad7917ea9ad1f101feca68b96c27b4314118aa5c1c0bcbeb0aa24563a44d368af25d106bb06c91f917c477be39016d95e1be895fbc8c8e4bc636bd0bc58abad37c4d27384523b4f5b43c7c59277987567caf9c34d28bbe60f60772b23954ae29ca4ca57c8bf9ee29a9a752d1191bbcb297bbcf3becd7ffa320efa6b8b4ba1ecf196ec087bbc11b1d3d2c6ad78af8e69b5b356cac400f15aca9c35a06661e47aaed0e72c90141301602f45fe1144dea918863b057e1518dc0e15b2a06922c522f6af434d081662a09786eab66909a0c04449ccb3d08460a693de38a725fdc7c82ea3c6aa07fcd397fbc3c32301dd0d74897f462e97e8ae808e3ba888b6f1c6a3a1c8db5a6f4ec4de343bc08c24585a7da30597d70717adb705767e6abb507890d6aee044138275183941b6eab1373a9bc6323b3d428c95e6121318bf63e9c1c49c7b432cd623012f4ad77a867cb5cc4b20a5da4cde299dc93194dbaa0101e5c239e0f3265ac74c8395d641710be3fdc4dbae8e84ee64e34861aa52a0eb4e3cd6a6098c8b9ad61b41a15d799564ec809ad5d337fc522cf78a7e4feab2c6a102c1d11a029a60341a274984a0118b4e7278d3f2becc0e6faf3284e13a2c24e9522a19ac521532eec5f5c9aa2006555079764b874b50f1bfe5401ad542ada8d82d026bd3010054995b09b1f2d727a78b59e1351f23ae9e50d694f103c50bb1fd8bb59375a854e601d4b35b6720399c29b7cc4253d5cdd2aae9878376724cad20b7d886410e81665ac80bb22c82ef182358a4b066c4086bf30253fcba87526e0453d908b85a3302b12706bafce02a963f09701268c195bb70dd9afc2dbe0b4d17ff0ed1c1d6ee35413d26bd45b3ae3446e85ed8a7d55b16596433231491e6fbc8b83daf55599359c8cd76dbe5cdec4a0c20990c16df8112b9a813f310e21d525567e82a134693ac5ce24f3dda1e15cd59d3c0e0632d5b82df283cad60af80786f2a3b65ffe04b6a0818ef667524f444ca5b0b6b8916248708483a38c09ecbc322eda60d6326bdad9372e2b7d6aa2b9e0ebfdbb2b5ca90e340d201693ce3bb9a0507c7daa96821a655afeab3a0a00513acb424b3828bc20983a40fa2c84d96664c2f9fb8f94de1a2810f0e35a56b5b724a80e8df9b5c38b0f27c7bd6f0f5972e6f7b3a4179ca444422b6e25b8941bdddc951ccf51fa1d242d494e375204f77d50c65cc44d4e7a00c5dadc0dc428e561d7733e2f8737ecb1ab864aff07e55ca80c7c44cebfb1548b440bcc6004cdbb8308d9b17d41305c5a569f1ebab1d6dc96e2778a7b1e0562f88deeac8688fc66953f759526850853a2995d96b825739bd374bc3a36a1901462ebb9f4e5c417a3d2dbf410f8ecc8eb5aa2966aa2f09ea153b1a0675b5ec822813bcb3ce5a4abfe0c5262bc3eb3c481afa3efad45699651ba2614ec77fd7bef6dd0d0944f8599e9df295b5f9e27c2f10113c8b71213f78bb8e37e02ab1138580a4088f2a208f63de936a65f9ffdccc4c466acff12ee716a10690113ecfe226770effa95677ad257e9b6e6bb4e69cc961133a6d5f06b368998468b4b5e8f446801e8944210589c020a5b25899a078abe3d364f970a1bd868b416fe2d02ef91d58ba3b509265c39e2e23b68c539c34ba8aa5423ecb1424f68307036f9bd8bee3e6262400cec4f40c918013702a76fac06294ec0e0107dd41fdba4c36a87a2d18cdf196b914604bb694e94e486a65d673c34b75b9176da71047b3426c1bd9979ea9029a25360aff8e89208ab2e29ac5dd74e4d2f499eb5d0ca45e2c7400352877e6bc57cbf89a9a314716bdbd23543afff4f45faaa5d5f2c27595248d05d3feb8b10b6462a192886321074b49b67e58e0fa29972e4b225a95627f0bacf65014058c9d4a8e21711f975e2887da63da874df70ea8595262d96d8630908e2432f60d8c621d6af8cdbb28a91e282a422886d3e4a7085af0ede094131a85f59168a26828ea08d8d4be7e39d9ef84d1545d8810da19a9a26e06384432ed0c6c7c322f39a33e5fb49342c5ee679283d99bf41ec9de908d665d29f87c9a93a0f7a8fb90f10d19c9c44ed978f07b50f49154ca0c2a85ba6ce2f31cbe26f2454db300cbc99a18b3831c0c3e2506f62b3bad0afbba0de61591b38e1fd9dab642ab632c222a130ecb33b543ae9405a8f5e570da7338b9dff40a29321afff8ff74966115216104e80eae833cc9bb31173d13ac1108f9cf125af7929362685f39974017a90a3708bc19035c2bc657b779e06f33895035c2e211f3b77f15a88a0be7752105ef2415ec95ae429fb3ccd6c448fceea02f833cf6ed5084550a026bc2acfdb907fe20335fd2e23d1b771171a4e4ae723d4c10be06d8c45c0759a0f2f05f72669aa668d83f56fc380737629dfd47c76782230e9a130810a45397e34506bfafc59f7d1c9e153ad615aada186eb50198cc2f9ced546674c34f853228181db2f1f804c61db4ce547cabc9367b1e1fce6c940df630c2fbf7958deea405dfa1b1448b93c419b4c315043c183462f651034c68488b6300d46aee8a57902bccd09f124144712f1a3ea11714b70b9b0a8321b21349c00427d4f9be67279c05c6446a6016c05db2129012c6a319d1b87c82b597d2777a25d4ebaf546e2d883a3663b367e89a8ae3fe5435b4615a466438e7d126aeb268494d480093e7e05fd7bb71da26c7c51556dceed88cb8cbe150480bfb107351bf6b3e3e71b5222b2108c7b41c936032097d73ee4aee705cea528290e617b3c3af14d6fe647a264eac3c089bc8846d70199d455cb20dc57247861236a324f0323659e36e48587df280d4297c80ea9a04a26be252034c26b7a5de16e25aa9a2124b6b738c69822fd8953ebf84869810f9c4b2dcfd1e492c51d5ffc4928d51c273114013f840f43b3057f6bb6a7323f42a455e89299992a54676d980de6051d57b2b0f8ff00f05981488107e40aa43209ac5ae69a39571572098dfb1ff8a86b3372dc762350d75a1500a7023b29841deb2411b9db3a2a7c3c84d52f92bb2a5023b42b7e9b76e3f85fd2b18493ad4b8d8b1a8f78d09cd170c67b8b6ec88abe92ef0393883673d8e98b3fc3569fad858606bb930667d69dc99f07ae93fbb005403046f4c825713b9cbf047709d5167b8caa4a2703e22a8057202326c40955a5b9a6afded762814cd7a541a6dd7ac46a95ea0d2bc04d2dd68e3b774579c8d764745512b699b84e747f9c0a283675e38b4f52f100b6872729b2822ed3cca21d807dd8a2d6052a6ac10f3402a04f8014b57caf353214a7ff9c5c9444bb60b59acba61176d167a40e4eed5776b5064b85841bf0a4bec4c40765d457f284a770ba36b55f37e518937f3e23c048b333859000734dba9776698356a7ad5d65aa2747d04fd505ccdf0b26501389aa59b77adfc9b5cf41c22828df7c1dfb61d26f61a7721f973f10a5bfcabaa775f391bf82cfd1c2ec21aaa3f98d0a58e1e887f6f2378697c8387c9393d821e2b2d06cd7987a6830c88a052219f98cc0851cc27a02c827aa777045629d1e19babc7e1c9ec6ecbb401ccf41b7783560e7b5be467160dff8a2a2d2a9e55118a8e5c92d357e896d5df3e1991f6ab01e68292fca17680dd090cd544ac3e1777b56bb7422a534538897441fb5e3e65134e60c19dae28a98a12291a2dd0e91e0336c567f036bd321b677dc35677756c834f68504966646df6349871e4fdadefff4e1f059952cb80abde39c5482950c1652fcf0ad78f306d48740a78108f659c045274877e984375585ce4c95fe1c7a0a4141b9e4348ba88f162045f96ad7c8acf339bca55ad03dd517d569b14cb042d7b8ba7482acf6ac51f478d46b341df9ab0053ab78a74817956ef8a741feec35ae07c470776d57a4ab1e836f399450a53d549d1e65b298560793fea1443183501d75582d45c90a3181569c857194c42b2495a6e1fd611b0feba2e768b6d0ab1495ec8f39c4ca42904209e44a07d40dfee04ad448db4006c5b94faee13553d185df5221bf82e3fbd89013e9038ba7b2c960390a76d935e0fd46a87530f574e4ed01eb24f6b804adf3d541901ae74af3514a3de6422a0a848b8045655f6d33156a0aa7f0bff95eab9cf17ad56201290a5862fb86c6092a1a3cb4598d934575bcd7a0c11d82f7b9f3c18808ddd07a8bd29aae596cc74eafef37b198d087b6ed00113f50c006094d87661f6b6ff4194f9a365c21460dcfd1955471d0b64486274eee10964d6fadf11d104ff16db068ee5983e7110a042f23b2853814b7ef0b83b52cb6f997c9cd4374cf7474e5441d0c8f750a55b1b21107582a6ad0a2762309bebc882151882d367d85d17265e3038d9eeaba7776c4079028e3367b27ed7ebfd62bc35e62e040259ed89fdc3c087f963d6e29b3c57d80ee2a3e2722ac938fe8c6c77cdcd6bd116e17d728e87083963c5731ae91de7d128de4ec9b751d1466c02a867ac4e9c914ad04d0d01be9a1103ab9c00261c052356e81eb97f42301064502c50b48bf4ac32cc2861d18f13e09b00889a0e44f9258a072e950bd380b88782840a8b5234d5ccd4e74dfce72948ef0501fe347001154880cb7abb9ecffeac24a90c4da9f87eba5ddbbf3e7de77ad1e98f74fc9978fa4da072004a86d5d03440901e392dc235a6208ea12bfc559d1915a9cfa07c1a2e4d4815046a67144b9e6bf20fac496d9a44d6e981546d3614a956d686790d6d1c53e1dd4d12df2ee2638f6dc65b983b39f731136e949d43b0662fc9106e2b9e28e1c92461102aaab8a68c4e8dddfb83599803537636fb88830f8afe7ac070e72f6091e6ceff24958ed68d14baebe77d772b4e783f49212c67f36cfc8a139336b3b4c7476b8e70cfeca9ee8918c639d2052163a9229ce971711bf3356a9a840b689a9cfbeddf399298d16fd286594d0ea38fdfe8623382450aeb05fdc87604be970b9263527cd870e4f949c1be6513162095d4549b561e32b2a023d745f04530a6de46849d3cace3f69b724ca2100e317f8029fcebf088484a2804911317762d52f50365f0d5e61183133081ca0833e5ba8ed4602bb843bd494c30887e7823aa10730a6b877bf4b1b34bb9bf29daff39765befaa78443d45406a44775c87cedbd653c835b55852df95ea33d2f1317b730424680cb5b82bb4856fd92fb04535ef835d81a2237a28b6b1969e00129ae1bf76278ba58dce33e4a06ea7428d422154b09475764716ad5c402bcc75c049a437ad5d907b5c99dc0d74fa428acbadb1125cfa78368f115ca76bd7c278da14dac10c55baa4bd94ea7b610335c17d5cf2ed9559021ea9f13adb900ca43a5b9863fd330a8d2f74b1e0dbcd1c9b1c22ef9cdd585c8e6b5562a95d5927c009c7d25945189284f06ae58ef2ebc1728f5e718833b24621b65a26b9d3e0f415cd78ade3a558c40641539d592f9c7fb81de672fab05ae47311e68c70487ef352f654e43be87ca9a508440b5d29c64b299236b6b874c53b6db50792f5b150a65ec599e4365bed0143ae4def1d7ab94aad2392e4b3197c3b88b714cfc16fef25d356337dbfc68cceaf48e0d09b8c4286c88594aee5e26ed40f33dd9cba7142d60c422538bc279bd6ed595293a339742bc31fc02cf25a3e7cfe24b6d3ff5d8c061a81487508b8f8df7bae153ba355395a3b675d992eedfce65a48de3b931d655f4bb0c2d47d2565be9c83db159fcf9b595f5dc25d32129d35bc11b8e02735e8ee8f83281b9bb2b924d98203cec4ad496904a0e7722b0476b3d2380f99b7e9874934468fd023443842cf053fc1952218a4c7d187a29ff1828c3087cabb4db22ee549cffac7b9e715490e5b3f80a5acb32ee4439a8d4e027294134c1aeed4270f764b4295079e645ec1cca1845230a5091f360dceb35376de3788e2e95401342effbec69eb685cca9bbaf0694fdb13061c007edd14b9c8b9b88704a1b9afa41b7eca47e5ad7535437439e64890f4b32289abba684584800440c420271dd92111f89cb2dad53df522229e18b422455fcb6535f1de17d77b65b1bc5c2faea194595a9a702103cf0c2503dd967a47f20d557e98813fc5dfe8736dccf65bb032408a30cbe42958457b8039541c7f8ac0aa9648620ba29c8214b30552f3f70b695105b4f0031f0d8ace482118813f259213d212010dc99f7bf2cc0fe01cc9a244cdb7eb281032a045b74b1e2624a3396345a9422389a5ad30597e2a3bda8dc92eec34aa43d6b4d5b6482f7c6f0a39199eb5085dbd0878f6cb7048b682a5de6a72477b7d246cb1e65ed04d9c32c1616cf01b5b0b4901c5c896901aa5822f16e0d8e93c9168375e62ca529a1732ef82391a09d75c547180004fa200564b4a9e094dd976eace41e6f574a39c70530c8231c898b41ea8f721655529b162793a4f6e300262bceb9c43324bc80d814c8cad726ee023ebf282517f02f32c8289edc49511ae1858b7ef84abb214ba4c3e3ba79cc482228267324c9a052b2d503406737b58aa8724b4b90761c883b89591b31a83af023b580f75466cfc349e929a820e24229c599c829ded27302d5040a365e15985fbe35eca6a22204843b7ae9971eb3b431a715f4b8f13cf610dc5443258692277c082d5528917cd7c2225edc2958a442eb0ac94f803e53ecdec10595411ffd0939e066b66d34b2893efd03924eca4de8e1df2b586f7488588e7cde4f748033a2694fea20ed04d292a8b4e6b0edd3aabe03951a22104a0b0c2566b6b895ce710895983dd98949f890881f96dba48f1ed934e63ac7ea2296183913c9d911499df76cf218d731bdb530e16ed5fedc8fc7f3a1a75c47a39aac3a3bdcc5d14a2e2679fa63142f3da2aa2338817870ce50adfea935b9a455a7b5d94ca34165e2a72f58a3d42c62e3b46483ea64e2b9071aafb555f8800bc24cfc5e4b986a3b787e57287e4e59959c2fdd1811a8eb0413b60fe1d912d557125ce67a978eaa1ae2434923947f61310989f1f8bc94aae4448bbbf7272e3f256e7fd8fc65431bd70731c5a8a0368aa57332b2f879c7f227b2919f97492f999d2710464a2db5244cce58fb6ac499410178073052b7ba596a71afaaf0d1909a2c21150f7c274be256bab3ade5c3cb05eda2cd9f5072d0c0ee7948c9d17dfe8ad36b34d42435e081e03981fdc1fc4ce29d705e09725212cf2ad30b764dd982fd34d433cb622076bcbe52a5c133ff443867a3a2fe82e5bbb6b3a68777091b3376e8749228602abb599a230df5e0989c377981dbb1614292354322b0fe185178add5a62b84134ad0f009fab6d435b868128c5d0402e5e8e27dddf6e720e9b422005d962dd36951f29eb987b1314e0244cf6ecb48abfd6e690378768215adfa5414e10c99787c04daf0fb683f823009594148391b63f4c67f38d269f14dabd278bec22a024ca3964b6ef5d81b51eb029d8ffcf5f954f47e25eab680aac43e2d10f5db67706bcb6b056a95cf629d17306c4a866beae02d90d01214308ca8fd28e4d44f6e379893846a214f0fde2822896550da03fa1269073a0cfbba498c3f2983cebaab9e20d4b23d505078d306fc458eed3bacea0cf7024e49f0e892a37526076c0b3a1e348d8e05a3eb047c4b572a7d2e423382f09d7331f60ddfaf9292c564b9aaccc469ce3afa3d392382437e40f143a23803607498fcf4ee0d2ce630b19d4ebf15a7a594f45048f5274816cbeba0aba043429c155a9bc254f08bac7547a8c4cfa013b369301df73972649c68c2328bc4e72e77a326aef8e6a69ce74f8855a49f47571309220e73b81d5c44987671851745987ff08fc0ea4bbdb81047aa6a2da8cd07315b49f3699d35669546495f3b2cbdf79a7b28003cbc206121ffe29e92a8e6bb992602b586aaaafdae64fdd8c183b4759a58d4a4672c6268e2d72e4964c2ddc1432fe12f4895bba04e36806487e61edfb8a97d4a10edb253622fbf973ffe86917a181dac9d73a863e711cb98482b50f5996f758e4e308e9abc990d31bbe47eeef37814b2f22a062b0f0c1c05aeae7b93f271db21af9dae3877a1a7e1d45711d825fb8b913e7ecb3177b816ba701fdc8235f978fadd1424269ecb45b0f8bcb7eddcf6cc42ce9f61c687f86986ddc343271968cd317a39f4e6361cc10afbec1ae21cd50fd750133c28a9830389c5da090ce8dc9a00bc85c80080914ee0a1713186aeb3e2280a56c2d32d198919a546d972c2ff1d74fe60e04f852d10066bb63c1381f71784907de66fe37c40411c350e24fb2551c5a2e1dafc0fd81e360b50fcc74b1134e1f5f2a938daa8edb0a0bbd142bbdf9a1f56bba8162385333754d645c0df20fde083ac91ea5583126b8bb39811863b34a32ef2a8f14298a91beecba28d28c377f35b1164544685deac936f413ea2ff9af459102b6fe4dc82c4c39d78a9f45782e8983cf6ac7d0ba7590ad84bc744b05d60132a25a4908a1097d992505f0740a4ba25ee446759e59bbb5dc5aa4bd4d962dc39c3d6070969ea98954eade9ce6347babfdb2cc4ef5d6b865a21571bc95ec7a124ff6637adf5d972b2c387434cc8a30ed1d06266a63b00adb108c49e4f15769057246978bbec8ba3b0ffedb31ec4b6b10eea7997ead82044869e9daf4a49ee622c69e727d42b4f50a8d45d7585145a6555aef25f041b5ba902009495d5f1fd9ab73aace59cecb0f8aff17cec0f0ea0a661718db36a6ca9318dc6a36ea364d7c7694a211c2517fb77a56662c6d3509d12685892be99dbf4213d89051b16800ba2d77d9ca35ef2fd69083ec50fd67509e495b36c47fc290acb486b402ffe38ccbee1304bf41c4651aceff14ebd27945d217b08d122843221370aad45e813a11284c80c8103839d066a2502e7ddcfba03661df67fedb6ec95052bd6110658d838d00d1feef8c5e35fe90b29f64b095e921f90b735ca773009802b09a98385cf1f36d7a982f278ef2e2ac5632f66f8724425e35da70c8eaea5702ceaf8c5bea8f2d6a387d54b41e951bd3c195ab704ce8b32061dc74fe038f16f7b52c7e77534a50aa6e96b2a18a0f3bc7e5f58c9c1453633923b30949d9fb76426464399c16339431a34f6ba1a2e051be4b8a9c9a510a4500eb780a0fd53b832b852f212c5a4202ed285d58793c140854a701826a0946d360b8c4027ad1ea5f098d18a8158cf3238c95eb3fdd9fdaf268a833c99e4aa874df4637b71930715a5d907fc60550b2500f9d9130e4573ad0d74c03cb728c1127652ccc04f620388e5b4faef09620a8191264ae54ce118d7b55d8411f512952215c1834cc85be92078aa6af80ddeca1365cc4935ea8169707b0923c2fc3d7994b374059724336b68fceae3077d31177461b80f03f5bc859dc016033b8b8acd8fc9c8366b9d809cf5c027fcf55df4d55c480e0b0f4708c56ff81d61665c419c67e6f68e6cc009298a7c01163a75e2651f1bfc738f49a38d9d0cd2143166116234e6b95fc64d2763ec27dd9dcff4a7cf6ca7c69444b5eacaa8184d5b831cb51d66ceb4eb5487ff48c63b214773bc73910dc21778a655423b902a4e6a06a3cbbaebbf9cd5c64c56498bf0e7e4bb6d037c2a23d3aac0158d1e6b935e5e6d093964d2326229cbed1423d0c3895aa397185c0af1084ef20aafcb8a14156456873e95e9cd473bea75d7ead48bdc22737206de55c21cb810d56867a3c5613e6665997565b895b2b145011569e3bf54e83e467badd9495e8c76e2684a9a3550b80215ea17cd84af86bd9606c865aafc7ea78444c4fca11edb9317eebed2775f320d4425d494e59775c517e262563f53d7da8b4c4e72d79da700b763d64ccfd2c4e6596950465b0de69c794b2fbe49ec06cbfa96cbec4bb18c4485f0adf6e1a54262dac6e87591ebca7c53e058a16251fc2fdc20b3213783893599b4be1a8951a622f1555e6c9cfaeddcd5fddcf41ddedbdddb5a25768386a5e6117d93620a91285e8e98d4584d9e44e1feaaee09f022fa67b7063a717fd840d6047d71babbbf99317776825a63a562ebc0e0a222d2e19aa06b1ffc8f49541f872b5a42eaa3acbac9d7232e55aab8864d4bdacc0624b80b668a14d2a3035846ee2a8d4879b3c15a21351e935cfd739db6d9a43b5fc49be476c54b0833fba957e0c56ed93032edba7f91494989f6bc1da10889e8099268c7e9288ff2f03b336635a2f6bb6519d887d14f4530975fda8f5f5960008bcd9179115cfb5750ebf9e95ac76f786496178c87f01367e836950c9f2a53df2984c13d0cff610faf6345cebf43da94547984ef988cc0a7ccbbf1bcce22969525218a53cd08bf5cec0b74effa801752e303f5ee68cbff6a2bb1c4b98592fdf0af5e9d2012f84a96dde7505fe3c55dfe655959c6f5b0a050bba29e43e3442a76dd22640b2a0d88aa4b3873039b029fc1e1148411d2137878bf8604", - "0x3a65787472696e7369635f696e646578": "0x00000000", - "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x1002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d6318141ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9657ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", - "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x1002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d6318141ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9657ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0x5e8a19e3cd1b7c148b33880c479c02814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x682a59d51ab9e48a8c8cc418ff9708d24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0200", - "0x7c215a8bceb5e4dcd92f78e36a5fd0ff4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x000064a7b3b6e00d0000000000000000", - "0xcd5c1f6df63bc97f4a8ce37f14a50ca74e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb363fc5191ef4dec4f7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66": "0x7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3c21badc8f9053b1f1ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965": "0x1ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ca2c608870c60a0ccaa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814": "0xcaa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d823137badc90c9202d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534": "0x02d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534", - "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950039f7057bdcc4bc16175726180caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814": "0xcaa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19503b5fa0e23c4a4cae617572618002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534": "0x02d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19508d1b0ce8b7d45af961757261807ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66": "0x7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950e6240f6cb493659d61757261801ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965": "0x1ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965", - "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x1002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d6318141ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9657ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x1002d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce53402d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d6318141ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9651ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f9657ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b667ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66", - "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", - "0xe8d49389c2e23e152fdd6364daadd2cc4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" - }, - "childrenDefault": {} - } - } -} \ No newline at end of file diff --git a/cumulus/parachains/chain-specs/bridge-hub-kusama.json b/cumulus/parachains/chain-specs/bridge-hub-kusama.json index 5e45e1528a105412ca3862e13a883b30661a62d4..90b70b05016d52aeecaea4870f438bf980f7263b 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-kusama.json +++ b/cumulus/parachains/chain-specs/bridge-hub-kusama.json @@ -5,12 +5,8 @@ "bootNodes": [ "/dns/kusama-bridge-hub-connect-ew1-0.polkadot.io/tcp/30334/p2p/12D3KooWPQQPivrqQ51kRTDc2R1mtqwKT4GGtk2rapkY4FrwHrEp", "/dns/kusama-bridge-hub-connect-ew1-1.polkadot.io/tcp/30334/p2p/12D3KooWPcF9Yk4gYrMju9CyWCV69hAFXbYsnxCLogwLGu9QFTRn", - "/dns/kusama-bridge-hub-connect-ue4-0.polkadot.io/tcp/30334/p2p/12D3KooWMf1sVnJDTkKWtaThqvrgcSPLbfGXttSqbwhM2DJp9BUG", - "/dns/kusama-bridge-hub-connect-ue4-1.polkadot.io/tcp/30334/p2p/12D3KooWQaV7wMfNVKy2aMz4Lds3TTxgSDyZAUEnbAZMfD8rW3ow", "/dns/kusama-bridge-hub-connect-ew1-0.polkadot.io/tcp/443/wss/p2p/12D3KooWPQQPivrqQ51kRTDc2R1mtqwKT4GGtk2rapkY4FrwHrEp", "/dns/kusama-bridge-hub-connect-ew1-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPcF9Yk4gYrMju9CyWCV69hAFXbYsnxCLogwLGu9QFTRn", - "/dns/kusama-bridge-hub-connect-ue4-0.polkadot.io/tcp/443/wss/p2p/12D3KooWMf1sVnJDTkKWtaThqvrgcSPLbfGXttSqbwhM2DJp9BUG", - "/dns/kusama-bridge-hub-connect-ue4-1.polkadot.io/tcp/443/wss/p2p/12D3KooWQaV7wMfNVKy2aMz4Lds3TTxgSDyZAUEnbAZMfD8rW3ow", "/dns/boot.stake.plus/tcp/41333/p2p/12D3KooWBzbs2jsXjG5dipktGPKaUm9XWvkmeJFsEAGkVt946Aa7", "/dns/boot.stake.plus/tcp/41334/wss/p2p/12D3KooWBzbs2jsXjG5dipktGPKaUm9XWvkmeJFsEAGkVt946Aa7", "/dns/boot.metaspan.io/tcp/26032/p2p/12D3KooWKfuSaZrLNz43PDgM4inMALXRHTSh2WBuqQtZRq8zmT1Z", @@ -24,7 +20,14 @@ "/dns/bridge-hub-kusama.bootnode.amforc.com/tcp/30337/p2p/12D3KooWGNeQJ5rXnEJkVUuQqwHd8aV5GkTAheaRoCaK8ZwW94id", "/dns/bridge-hub-kusama.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWGNeQJ5rXnEJkVUuQqwHd8aV5GkTAheaRoCaK8ZwW94id", "/dns/kusama-bridge-hub-boot-ng.dwellir.com/tcp/30337/p2p/12D3KooWBFskNCQDVjuUeBh6vrszWrUvYMBBhtZRLnoTZDdLYbW5", - "/dns/kusama-bridge-hub-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBFskNCQDVjuUeBh6vrszWrUvYMBBhtZRLnoTZDdLYbW5" + "/dns/kusama-bridge-hub-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBFskNCQDVjuUeBh6vrszWrUvYMBBhtZRLnoTZDdLYbW5", + "/dns/bridgehub-kusama-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWQMWofXj8v3RroDNnrhv1iURqm8vnaG98AdGnCn2YoDcW", + "/dns/bridgehub-kusama-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWQMWofXj8v3RroDNnrhv1iURqm8vnaG98AdGnCn2YoDcW", + "/dns/kbr13.rotko.net/tcp/33553/p2p/12D3KooWAmBp54mUEYtvsk2kxNEsDbAvdUMcaghxKXgUQxmPEQ66", + "/dns/kbr13.rotko.net/tcp/34553/ws/p2p/12D3KooWAmBp54mUEYtvsk2kxNEsDbAvdUMcaghxKXgUQxmPEQ66", + "/dns/kbr13.rotko.net/tcp/35553/wss/p2p/12D3KooWAmBp54mUEYtvsk2kxNEsDbAvdUMcaghxKXgUQxmPEQ66", + "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30520/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV", + "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30522/wss/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json index fd56b61115d5f16a0c2798f73181a42f07774b9c..a9444b89e1e2cce9c79a367c4c561e910f1f28f7 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json @@ -5,12 +5,8 @@ "bootNodes": [ "/dns/polkadot-bridge-hub-connect-a-0.polkadot.io/tcp/30334/p2p/12D3KooWAVQMhkXmc5ueSYasdsRWQbKus2YGZ6HDZUB4ViJMCxXy", "/dns/polkadot-bridge-hub-connect-a-1.polkadot.io/tcp/30334/p2p/12D3KooWG4ypDHLKGCv4BZ6PuaGUwQHKAH6p2D6arR2uQ1eiR1T3", - "/dns/polkadot-bridge-hub-connect-b-0.polkadot.io/tcp/30334/p2p/12D3KooWCwGKxjpJXnx1mwXKvaxGQm769EM3b6Pg5vbU33wbhsNw", - "/dns/polkadot-bridge-hub-connect-b-1.polkadot.io/tcp/30334/p2p/12D3KooWLiSEdhriJUPdZKFtAjZrQncxN2ssEoDKVrt5mGM4Qu4J", "/dns/polkadot-bridge-hub-connect-a-0.polkadot.io/tcp/443/wss/p2p/12D3KooWAVQMhkXmc5ueSYasdsRWQbKus2YGZ6HDZUB4ViJMCxXy", "/dns/polkadot-bridge-hub-connect-a-1.polkadot.io/tcp/443/wss/p2p/12D3KooWG4ypDHLKGCv4BZ6PuaGUwQHKAH6p2D6arR2uQ1eiR1T3", - "/dns/polkadot-bridge-hub-connect-b-0.polkadot.io/tcp/443/wss/p2p/12D3KooWCwGKxjpJXnx1mwXKvaxGQm769EM3b6Pg5vbU33wbhsNw", - "/dns/polkadot-bridge-hub-connect-b-1.polkadot.io/tcp/443/wss/p2p/12D3KooWLiSEdhriJUPdZKFtAjZrQncxN2ssEoDKVrt5mGM4Qu4J", "/dns/polkadot-bridge-hub-boot-ng.dwellir.com/tcp/30339/p2p/12D3KooWPZ38PL3PhRVcUVYDNn7nRcZF8MykmWWLBKeDV2yna1vV", "/dns/polkadot-bridge-hub-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWPZ38PL3PhRVcUVYDNn7nRcZF8MykmWWLBKeDV2yna1vV", "/dns/boot-cr.gatotech.network/tcp/33130/p2p/12D3KooWCnFzfEdd7MwUNrrDv66FuS2DM5MGuiaB4y48XS7qNjF6", @@ -20,7 +16,14 @@ "/dns/boot.metaspan.io/tcp/16032/p2p/12D3KooWQTfRnrK3FfbrotpSP5RVJbjBHVBSu8VSzhj9qcvjaqnZ", "/dns/boot.metaspan.io/tcp/16036/wss/p2p/12D3KooWQTfRnrK3FfbrotpSP5RVJbjBHVBSu8VSzhj9qcvjaqnZ", "/dns/boot-node.helikon.io/tcp/8220/p2p/12D3KooWC38TZJA8ZBXZgAYVrceoJ56jNNLJPdpk3ojeFkTAwZVp", - "/dns/boot-node.helikon.io/tcp/8222/wss/p2p/12D3KooWC38TZJA8ZBXZgAYVrceoJ56jNNLJPdpk3ojeFkTAwZVp" + "/dns/boot-node.helikon.io/tcp/8222/wss/p2p/12D3KooWC38TZJA8ZBXZgAYVrceoJ56jNNLJPdpk3ojeFkTAwZVp", + "/dns/bridgehub-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWPNZm78tWUmKbta3SXdkqTPsquRc8ekEbJjZsGGi7YiRi", + "/dns/bridgehub-polkadot-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWPNZm78tWUmKbta3SXdkqTPsquRc8ekEbJjZsGGi7YiRi", + "/dns/pbr13.rotko.net/tcp/33543/p2p/12D3KooWMxZY7tDc2Rh454VaJJ7RexKAXVS6xSBEvTnXSGCnuGDw", + "/dns/pbr13.rotko.net/tcp/34543/ws/p2p/12D3KooWMxZY7tDc2Rh454VaJJ7RexKAXVS6xSBEvTnXSGCnuGDw", + "/dns/pbr13.rotko.net/tcp/35543/wss/p2p/12D3KooWMxZY7tDc2Rh454VaJJ7RexKAXVS6xSBEvTnXSGCnuGDw", + "/dns/bridge-hub-polkadot.bootnodes.polkadotters.com/tcp/30517/p2p/12D3KooWLUNE3LHPDa1WrrZaYT7ArK66CLM1bPv7kKz74UcLnQRB", + "/dns/bridge-hub-polkadot.bootnodes.polkadotters.com/tcp/30519/wss/p2p/12D3KooWLUNE3LHPDa1WrrZaYT7ArK66CLM1bPv7kKz74UcLnQRB" ], "telemetryEndpoints": null, "protocolId": null, @@ -87,4 +90,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/bridge-hub-rococo.json b/cumulus/parachains/chain-specs/bridge-hub-rococo.json index ff20d8fb4825ef964ff36439100d56715ca8c07b..6b430678a86c88d35894819d6433fcf605270d79 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-rococo.json +++ b/cumulus/parachains/chain-specs/bridge-hub-rococo.json @@ -4,9 +4,7 @@ "chainType": "Live", "bootNodes": [ "/dns/rococo-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJCFBJmFF65xz5xHeZQRSCf35BxfSEB3RHQFoLza28LWU", - "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ", - "/dns/rococo-bridge-hub-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPZLWbbDJzEXAHPuAcVssPrjQLyZK4nvvmV2ez6gy2FQ3", - "/dns/rococo-bridge-hub-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKWMENpCNH7wBVQoHLwQoWUs6acAEmfdV694v9jCuJwYc" + "/dns/rococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWJzLd8skcAgA24EcJey7aJAhYctfUxWGjSP5Usk9wbpPZ" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-westend.json b/cumulus/parachains/chain-specs/bridge-hub-westend.json index fbed89729c79b70e5631753feec73f71cebe401d..447207a58107a95bcd5fca173d9d5476a0ce9cab 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-westend.json +++ b/cumulus/parachains/chain-specs/bridge-hub-westend.json @@ -5,8 +5,6 @@ "bootNodes": [ "/dns/westend-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKyEuqkkWvFSrwZWKWBAsHgLV3HGfHj7yH3LNJLAVhmxY", "/dns/westend-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWBpvudthz61XC4oP2YYFFJdhWohBeQ1ffn1BMSGWhapjd", - "/dns/westend-bridge-hub-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPXqdRRthjKAMPFtaXUK7yBxsvh83QsmzXzALA3inoJfo", - "/dns/westend-bridge-hub-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWAp2YpVaiNBy7rozEHJGocDpaLFt3VFZsGMBEYh4BoEz7", "/dns/westend-bridge-hub-boot-ng.dwellir.com/tcp/30338/p2p/12D3KooWJWWRYTAwBLqYkh7iMBGDr5ouJ3MHj7M3fZ7zWS4zEk6F", "/dns/westend-bridge-hub-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWJWWRYTAwBLqYkh7iMBGDr5ouJ3MHj7M3fZ7zWS4zEk6F", "/dns/boot-cr.gatotech.network/tcp/33330/p2p/12D3KooWJHG6qznPzTSEbuujHNcvyzBZcR9zNRPFcXWUaoVWZBEw", @@ -16,7 +14,14 @@ "/dns/boot.metaspan.io/tcp/36032/p2p/12D3KooWPaLsu3buByBnGFQnp5UP4q1S652dGVft92TFeChizFir", "/dns/boot.metaspan.io/tcp/36036/wss/p2p/12D3KooWPaLsu3buByBnGFQnp5UP4q1S652dGVft92TFeChizFir", "/dns/boot-node.helikon.io/tcp/9220/p2p/12D3KooWK3K1Mu5Jjg96Lt9DUzg84KsWnZo44V4KB7mvhGqi6xnp", - "/dns/boot-node.helikon.io/tcp/9222/wss/p2p/12D3KooWK3K1Mu5Jjg96Lt9DUzg84KsWnZo44V4KB7mvhGqi6xnp" + "/dns/boot-node.helikon.io/tcp/9222/wss/p2p/12D3KooWK3K1Mu5Jjg96Lt9DUzg84KsWnZo44V4KB7mvhGqi6xnp", + "/dns/bridgehub-westend-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWBsBArCMxmQyo3feCEqMWuwyhb2LTRK8hmCCJxgrNeMke", + "/dns/bridgehub-westend-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWBsBArCMxmQyo3feCEqMWuwyhb2LTRK8hmCCJxgrNeMke", + "/dns/wbr13.rotko.net/tcp/33563/p2p/12D3KooWJyeRHpxZZbfBCNEgeUFzmRC5AMSAs2tJhjJS1k5hULkD", + "/dns/wbr13.rotko.net/tcp/34563/ws/p2p/12D3KooWJyeRHpxZZbfBCNEgeUFzmRC5AMSAs2tJhjJS1k5hULkD", + "/dns/wbr13.rotko.net/tcp/35563/wss/p2p/12D3KooWJyeRHpxZZbfBCNEgeUFzmRC5AMSAs2tJhjJS1k5hULkD", + "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30523/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj", + "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30525/wss/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj" ], "telemetryEndpoints": null, "protocolId": null, @@ -82,4 +87,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/bridge-hub-wococo.json b/cumulus/parachains/chain-specs/bridge-hub-wococo.json deleted file mode 100644 index 7024789b8ccacd22eb2f6171d911c3ea62876551..0000000000000000000000000000000000000000 --- a/cumulus/parachains/chain-specs/bridge-hub-wococo.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "Wococo BridgeHub", - "id": "bridge-hub-wococo", - "chainType": "Live", - "bootNodes": [ - "/dns/wococo-bridge-hub-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWCNomXYZWuhwHsWhZpmrFmswEG8W89UY9NjEGExM38yCr", - "/dns/wococo-bridge-hub-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKSq37RLqP3Ws3FtJDYB1xsjoBeJmehVYDZcCDRNLBXas", - "/dns/wococo-bridge-hub-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDkSQzQYC7VwpJKF8VJtJZMG8bcvWXm1UEJSKk8UE2iv5", - "/dns/wococo-bridge-hub-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQoUFxyPbpotTdUpfnsxQfQ4uyxz1beW5Z39LGM8JPhLi" - ], - "telemetryEndpoints": null, - "protocolId": null, - "properties": { - "ss58Format": 42, - "tokenDecimals": 12, - "tokenSymbol": "WOOK" - }, - "relay_chain": "wococo", - "para_id": 1014, - "codeSubstitutes": {}, - "genesis": { - "raw": { - "top": { - "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xf6030000", - "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", - "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x0a000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000000000", - "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9050f9ffb4503e7865bae8a399c89a5da52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649": "0x0000000000000000010000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b4dbfc3b7761206de75b3a8d70fc3d44a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b908aa810c364ce8c3bd964ff3d424cc926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f52c4b3c3fd1c798e3843e21a38f1421b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ff9bdc7d7afef8c14d5b253d4e25b33db0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x5191446272696467652d6875622d726f636f636f", - "0x2b46c0ae62c8114b3eda55630f11ff3a0f4cf0917788d791142ff6c1f216e7b3": "0x0000", - "0x2b46c0ae62c8114b3eda55630f11ff3a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x365c9cdbf82b9bda69e4bbdf1b38a7834e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x38653611363acac183fe5c86aa85f77b0f4cf0917788d791142ff6c1f216e7b3": "0x0000", - "0x38653611363acac183fe5c86aa85f77b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3a63": "0x", - "0x3a636f6465": "0x52bc537646db8e0528b52ffd00584402045e8e84760e521040ed361d686da611213512500714bb39cad5cf5641bd06bd0abd9638f539f67d2082d63fc6761fdaff8a1fa0ce30dde74873a8d1951f13478143baf1c7fc501ed2ffc5ee26424bdade7b6f29a59432c914d40f5b0d260deeca2b0c7fdbbaa337c730fdf8653b3fc62f4b4cffc7b7f86506e0f85b77f5661a70fdf8e55ec2f1e797db5d343c81010803387e6e77651670fc3c57c0f17fe2fc1ad70f4a8c13270e1c3c3f7e39052c7f835cfce4cfaf7177482a8ef48352f1bc19e278a1c03871e2c4c13986e3cbafdd05af7417fc176e116214ca295bf7a04ee0f5920d5fb075582fd9e004eee7dc070ee96f39ac978ce0e0fe1ddcdf3cfdb3b5013cf8fd9d6d5b3bac2186dfbf9dda612d1de6dfbfc14d72edb0c6d9e26ed40a6eb47157e3283fbe7fec88f86d885ac10d37ee6a0c3f7e2d2fec885877c5ef6e85c6b023e2ffe3d08cadb1fb43f7fbe331081db0c1871faa04de92d11a9c25f6b7f0adede8d3de933d0c3f7b5842d84284d161fc1f8731c618e1c708dddf62bf3a47fce53b3402df6ff68f0e219aad79f263ffad4ff0b636c1eb8fdf1d917ed8a940839191910db86fbb2b5becbf39d70e83efb07697ff8fc74fbacb1f8827bfe5b0c69ec3da5dfdfe2b87c5f76f390c62ffee8844f9cd10029612b318bec5f0ea1c812f1fbebc4410c7db1d117833c4f07ffcc79bc7afd0e3162116213c636bdc3cd9c3fe9b7558ced931e3c1cf1cf6cff1db5d3f3cf8f1fbbb43025f285eebae2c71ff8f6ff08b7806ba06cfc09936c8c14f62ae518802dd602815cf90ea1a8528d0084351009e81c3f0fd4d8d42146884a12800cf40317cff53a310051a61280ac0f0fdbb33228d8c30fc1fdf9ceb46017e8edfeeeab70ecb10c7efdfe267ddd55f002f7e7f7745a4bbfae3b5eeeafff1cd39f839e61a85287009cf40317cffae5188028d30140560f8feb043e21961f89cc31afb7767041a61f83ffee33ffee3fdb5bbfa9bc7af90875b8498439f636bdfe0b7c3723fbcedaefefe1884acad978cb8e0ae5bb3c2f0e0f7b7b6a3043c020773683b5576c4ec74d9c9610769e76847079d3874ded07942878d1c38726a908384ce520e169d2d397ae4c0d19922e74d0e0d726c90a32527881c1ce878d1b99223454e1c395ce4bc91932647889c31395ee41491a32687889c2372aac8a122078a9c2c3957e4a420874b4e1b3a5fe4dc20c78d9c2472b6e848a143458e113943e428a1d325870d1d2e74d4c84943670b1d2f7484d049810e179d2c72b4d0494207899c3572b2c809a303448e1a3968e434a1e3440e133a6374c4e860d1c9a2a3854e173a6de8ac9173860f2e7c64e123091f5a7c6cf131840f217c28f948f2b143eb8ed619ad33ad315a575a43b4886815d112c283382d1e5a46b4bab47a6821b572681db5acb4aab47c6829b56e68d9d09ad2326a2dd1e2d2628255048b4b6b8716102d245640b0ceb0cc6095c1228365863506ab042c3158655869b0d2b0ba608d6171c1da82a5054b0c8b0d6b05ac395872b0d6b0e260a9800507eb0d961bac14b0da60a96165c10ac3c28205867505cb0a161a161aac33582660b1c15a83a5068b0e9617d6142c28584eb0a2601dc1328285042b0b0b0b4b0b2b08d696d51eab1bace658ddb18a63058355192b33565aac985829b1c261556565a4aa4345878a8d6a052d2aaa34546da8d4a8d2a8e050a140a502951915192a32aa12a8965460a8c25029a97e50f1a0caa2baa20242d5838a08d5169515d50daa2a2b285438a46e908a418a8e1497541da932a92e526c52665263a44c90222355460a4e8a885411a92da9a55517a91e523ba4aaa47040d581a203c506b502d41ca814a0c64085818a831203f5065506c5060a0dea0c94095060a0be407981ea227584fa821a83e202b5054a0b5416282c50605057a0ac40516153d091e9baa0289042684dd0353a2f5039ac86a03fa47e48f950bf785ca4122b703c3820a2916dc1ed50814d0cd561ee21e730d9918dc9bcc8bae0c4c8b8f08ee40a3c2b340be5c1c8c94a648185c246e1ad31b36c65e60f560913189c0b4e649cde7049a73cb830ab32528e0e88037020fb20c2c7122a36542950c9a19a830271aa82334ad9b07a0206931ea637a624930ea61898dc98f030bd60278b1c2f2617703a7467a85ac0b540fb82b2e1a9c1e261cbc1760396145d182b30a6195d178a0487c56a4c0a063e7ee0ca9c84e07ce07ae0624071a049b49a50b5e1baa850d03d4c30e8c298eed870903385244382915b482da419e9854423d19067c832e419690269861c23976416b24c848104436221c39063c82ea418928be826de116310ed9062e417328c24234b10c988654433dc0df4065c95d416292d52625259a4c2a4b04881495d91b2225545ea4b8a8ad41429295251a4a04879493d917222d525d5448a89d412d40a45a247ad24e80e9b1b5b0ab63128961614ad28343ab4309a1cda15da1c9a149a155a15da190d0b6d4c7cc1c441eb08af8aea0eee87d40d3eb8acc2b06db82c03180165c6c68122c3aa20270ad50c58535253ac98ac053488eecad60595c36e5911416db09a816a881a05eb07153a2db638740eaac6b687d462dbe8beb06f745cd036ba2d681a140e548dc604aa8a75a31bb319d1899143d013d034e81b54087a443481c988728966c4335447aa1ce810f14c95629b82cb8252d9a8f0a66c52c835f4064e0b0f87f9c644639e99269867a4ee9868660db41fa21afbc589052735d68b531da736bc1a5829362eb30a8e8e4dcd1cc2844337c4e90a560e53ccd4428554a9a044d429e8163a03539b2e0b1947a605a724dde89672bad00943c708f946ca60e2a02ae35169c1a065470bcd0a07120ae9c55485e9cb76c336451a21a790544829e4171985b44256616ac2d4c5c484c909d312262fa627e411b4074e0c35826aa145705e5857621b561756132a213a21ac18272d34e944c7ca8b131b3487139b79c5a6c4f6e544c4690d5b666323a6a0dbc285c1ba617b43a5474b89235d0f2b1b7870565e72b0d88e905954656c557838886c7060e45c3155a15b54303965d831b50a8ecd14827b01ad62b2721a22e78ca6a55e31d558346c173a6298a6542c5462e488d15193842a715bd02bdc52ce163309eac3fc62a9c8c9e364850e13d58a1a4662c901c274838e1b394b262adc175b18394f14a1535ee8404e9b9a05e7c55687ce11dc981c27748858d58005848e12261b221aca854e191d2d1c9906705ca0d4acdef84022031aa0c04a8c150d567016b0d24388498aca8b84120880428312b141dc904b22f3182445c907923c5112044392241b8c2d34094a9244434d210129241005910c489c50e08582e2052552a404c5d04489035a682283930f3431128393282a9480f27281273e20c30a412fb4c024ca901e9413177854976e81484ad007923079824340a48293254e963891c0a39a5892c4890b4d8c0c0d395900d192a0a121270b18008a899632c4440951500b4982825c10006a8976e24213264942102548062849983cc1444d8262088112161ea544072d69e2a449122549983cc1414b9a3881005113199c2c59823f4025d1829325412d848042a2a58420482889142921083ad2c4091329528226f0a1b83408983cc1518284a238b900920f75442709922148860850a085a0175e50428101a08c6829424b82605852444b216a1214250886264c1eb5a5091317745044f41226529a30894244435092100c3d79a2244994a0168698c83044688993a1213ba8213a86244a8894b4a08428e801289410226032a484091193264c5828199604d110944205d1519c2c8043656929414c889844710203910c4a54282cddc40913222551646821099193212949983cc14aa2c880599c96165ae264e80349828894b4c06448c90ca68641431f5042d444061792102d098ac2a485264c9224f1018235aa6eccc06107eed8d64b464618c6a20f4ab7d02df009ac52e775a6ce9c3a73ce391f3620cae8ee31d2e88336a554c618a38fd8b4db2775eab46937a5317a8cde9446a7d4bda98cddd1146974d92e3dca0e423a21b410c23651e9d1caa6749e28b5d646774925758fdd1dad8d32524a29ede8dd4edb3bc6e9557ad562a4d57aa4399a4c596beaa47b4bf76a8a31336516b6a44d9d76b791e8923645511aa36d1929a5d4463982a49c779c515277b7564ab7ed544ada945211a2b74f2b9bdaa61d2195725249e18c905219a38c56f300d476f796b77b6c2aadec4c566a295b6b8dd1bad328b5f6485b4ed931ca6ea7b63dba5c817e965a08671115887c1f03e689523a29e559c090384f725a4abb1829ed6052dad46b94526a324a199b6e51f3683b464a1d77f4e15ab7f49316393a42bb7bd4dbfb27c7a9d3e850e5dd0e21ed1c053c37bb63c7d63414ca5a77872dbbe794d2d2ee283308a3f57992d1ba161b00b43d84107a7ab6cd7d76d65acd4ad9d15ae724c72500019ca7c0c52929a5ddddb6b5c8797bc71829a5d23d5a6a6d8c1da537d70ea5f40821f5d853ce9eddb337dad1bd63bb5c11721db5d8b1a91480bb006208d14485a01065cbd8b1a39c514a2935adbbdd5a6b6db4527ad7c9ae93d235cddd5a2badb4314a6b350f8075b74ebdbb4ed3bc45698c07a0b2a9bb76d2da6ab6a7b5d6cae8ae494d4a193d7a8c515208a1bb078992cae63877cedbbafb50d8cdba1bd2da5edbeb3640ccbadb3bc208238c1dd0e89436ede8edde1ddbdba97b778cd129a5d4673bed760a29841f50e800701f1edd23ddf16edfe1a2945abb6b10da181b15a375eb313a75db326aa713baa7d3e8de2d216ceb6dad49762d29adcb38dd6d942977f729a395537ae081071ea452ed31524a6137f47677299da7dd9b8bd1bddda3bbb70928947487314619a5bbc728829452caa64ddbbd3bbaecee3c46975d53f70622658cd15d528feed5e5a4d16943e83b4aec301155ed2188e22486a01868092d0445096a218a932430030009621285680401a87c749023b444c90786869c8cf0ca010104107cac2ad11225529a288942948488490c313871b1aea7a36a12f4e4891210ba84189810359121caf55c2092e18320189210356112e54992255298c4b07302ec031a9c80800804980180acba8420404a4802244848c9d00792bcd084090c5294c0f0260c31117afd1040011a089113272e12ba04254151584142498868086a4200d60b02b85d821422294c68081a81000d8406262e044579f25205bda0248a132229495a186202c39224444332287121a8859e4a14450991140100116100444aa20435097a218a14006c0da48520295282a23c89c1c9922451826670b224284a1225525a581294a404219801803c591224c52783610991920f2481410a93264c5c707d1002179844099a21c9014a30800180b41094c409d193104459224236143443d050121a94c410c2ce0b4e8692bcd084c90b2f3819f2016300a18382a2b890248a1322254b9c0c0d29d951110549f9199a0445af4b48d2c409132951826648124549d0d092243404b5d0844992264c94c0b0c405179c2421a221e8ced024288ed081e13158543ba0188cc158762a92b1c8813a5654818a8a6214a808c6602c56648162b0a8a828a635501715c58a62d1818a60aca8a8481645a0182c8ab92c2a8ac536a02e2aea585151512c36818a605151516c027551cc81baa8289601754c032a82310854048b8a1c28068b2050171559a02e2a8240311883b10ad44545b198042aea18058ac18e4da06ea0188cc52450c762b108d4b19803c5600c02750c021d9171c0ee86314851d201dd3e26d376a9e9cb161a451fb8147be49f3e07c9afecfa56f6b2cf16c325f941b89b5fdacd2e9ce15277f36f173a5cc2d98574fb6cfad3676f83eb764d9768629335f59f6ef63698be1b994c6f7a3732dddc5da2995bf2299d66a85ff85d507e05b2c1dbce3b2c7b8ffab0e8d775f4f14ebf456bad8d9f50d5ac8dd16a9a971df5dacd8eeba36e76ff3264f3f22f435cfd7afcfa6e791c66caa63fa50ffd297483f3f4a6344b7c14fdcbb00dce93422ffb496f866cf07c1a9f3e7c4a23fd1c25a6921ef1b7f0a3f1157d7a7bed01f542783dbebdd48b3ecffdc9fddde9c32328c49f5a36900dce4c5d11d4fb15f2b7dfa132ed39a7afa29fe228ea37fa92aa6eca1fa5754432bf82b7f835768f2e71fcdcadcfcffbb25f7dacc6dd473b9fab97d78b3eabfbdd73f7244dd07ef6b0bdb65e21ffeeed678b2de471987f47e7b0df3cc46dfe7215b1bd42fef4b3b7bf41fa995b9fd5dbee427ffb65a2881d5e9dfad9cfffe8f382485088fd99450c3f1345dcfd963dd57ebeec73573eebebeec94411dbdfee43fbda67d9cb8e4806d960ea5f63fb4d5ced4771fd1abbbbfddc25aef44224f863faeaebeef15ef25ca526c6325bbc62e4799577320f8696002f87d5e71c7efef1f3dfb22ccbe06f9fd0c966d9b6e93cccec15dafe64a18fb845cd6a5bbc029453d0ba3fcb13fe96fd26b9131cc6f270ac8619bf5f3372c1997efd84b208e1c7f88242e2d7a80546988fcff10a10fbf914af60fafedd6d6d7b2d78a3859f4fad335d26857c028559fc32f40bc469f6db6139f0b79fdf06dff428558c2f986559275ec980c01fd56a9fe2de9b11fe073a7787eb8ac417823abf7548381c1f083e77b3d52ebc1fd8bb1375b48e488d65f0b69db6ed94bde2ccf9baf7f1d5ff8911fac71ef9a66fdd6c511f7d7e628ffc8c75557f518fbd6bbad96676d37ecb367bfa4dbbf637e7809ce0b0f9f1e7cd27e099029e5e193c5fb6cecc394fd836cfb6ed6167e47481c01fd9cfdfbe6c6f9638bb3af627fa00c51ef96b96d62cad59c2f2814021d9c4db67a2894f6ffa7c02aedbf64227ed678784c3db039dde5a0f9f7e4d0b9f6e269a387bd3cd2760fa6bb0fceecb8040215bf6da05027fc88e488d5730ccb22c0362845b84580ef4e00a0887b3589a81b6bdfe56ff0bb78fef0f92c20f031ca6bdd7bf6dfefef0a38ffa6a071eb452315603c7bdbcc2c934f1d4715cfcece5954cf591eb4edd47d469d59d7e25cde0985d78e6c733b7f09c34cc79b92f437ccaf1f3898bb7fefcb287278dd5101f4e1a1fca3442dc17e1383378730c4a2579e50cce1466dca3eae9b33f84374b3398bef60f4f97c871063ffb25723cb30b43086dcec323f1b9f9c12f6b97c871a461eb56acef8679a7a8ba5cbc3aa7af4f55dfc1cdb683f7e066cf836fb95a9fbaf9e7c79b8d3cec90cc8f2f951c561fbebc0285c487dcffc41f8f5f63ee8b3925cde0faeef0e3158acfcd796516f8a3b2be75f3afde839b5bdd7770b757ddf8f42bac39755533f51d9c2fbf8573c399dfdd199ffe165f05e3a7383878cb263e7d8d293ec518638c27eeca2c515ec1f0695764be3ffde2ffc49ccf07fce8fb973dea3bbdf7997efb3c5ea9e4ca767ef6eae7567e1b7d7c48ff0a0eb312f0213ede157d58e01ebedffcad7a736bdeece1ecc2d18525b45f61e5be7e96b8fee69ccc42adf5bc562b3fb6f1b30bdbf876e885b3cf43af4c1f3ed70de332987fe5aecc52bf2cb59c9e3e0bd18786d823ffbfd5f2bc4683e5cb2ca76fc78c37f4c2dce9e96b9fc39dbefb2cb560ed7d7c1d7b20f74227ee69883e36f6c077fdb75a32de3cf4824658bee9eb7675374b2d98becc028570328b5492666416c885e539344883399cfa31406cc106c0bdf4031bbc350a055e2ffd0006bb702ffd9084b746d1c0eba51fcae00dc3dfda044f622e094b2e58fe068d3cdc6ffb570ef3fef4fdf08ac3baef875a1ca67d46bfb72dfbbaefbe7e343e0dd1c78d624ffcd46f11b56d99bb7bf609d9dedcb3acfa767fbc3b24dcc3ff710e090af1af799a9ebbd9b17dd3cd8eb3fff1f9652e09d739e7cdef5f86d8fae739ef9c7e85b2b73f7b8b5c774522cef36a0fbfec6148633514e1f9f5c29a498031fc9f08bfcc69c199bef609f917e1f9e588e17b6724e2f85b0cd7875f8659e2a9c1d76ebb6b664e096722ef88d40b256221ef9064c7be51cd1dc3aff5662e09d3b7f5a5c34c5fbf7eaeb542a8c4d56f87993ec3244ceb7cd7be0c6b4e8cee4343ec890fb0f5b39fd9fbcd12d718df8da24f3c5a7dca8d707c00fc79b311f8f3e7c30e09fcf9ed30edeb734750c8fceaff13b17f3b663c22c7331339ae4f3f734958fbeecb1077ef7545523fafd07cff53eacb10432dc6f851f5ab38592b53bda9ef6aacf5b2a296fab1de95eac6f9296e0cdeb2aff19c59f6cddf20373f893324134f6ff8237347b83eed8ac0ef9e7ef37fa2e93b3df779bf7dda43faddd7cddbeea2f1934f43f48947b0e1b41ce6b81f6e71d82ce2f8243f1e459fa825f6c897f01e61f9f0511fb4fed98fb0dfaeb3dd670e6fce7148504886181ae1f89366f3e16b0fc5f086389c5da1f9f5b5cf7e83f001f5b59b21fcb2cd3bfff9ddc3ce08fcf9dd97258657c7bef634bb12961fb5449f8e3d52462df52951c5f03351c5dd9b3ec323ac0d71187e1ee270f7dc6789b5b75d11f842ddcd10d7dfbe7648380c1b0ece16779fa31358bef7b5bbba3f7dd25dddcd4415d737dd0c8f30fdee5e76370f71b83e77b3c4f239242804de2c2586eb6faf7d6b4f3b221a9784b36f6e90ab1fc5f66b3cbf1c02e86eca23c0b37ef3bb3332f1fc1faf9fc4f683b83b231bae9743823fe8955c708b104b410fa8b830e59c3226441336c84116187ce14517a91ddc7c3cf840e50331268eac30429aa202373940edf0c60e2ea8f28115eea51eaaf080871e7304c92865c442ca6f2b65b6584af90696522a91a93222183c70b1c399243982bc18bf79c002c797ee038da2121ea0c01de05ee2010b99d7ab056b5f3c3c4ba4e47901f100bd8078fa045f90b1c38b249c80c10d1e0f49a634ca9cac58b166e8f04687a40dc829e594f2bd242a584a292fe0d91ddc4b4954608a7b29e9090f236988d70b03d6be3c661270dc6fa38f1425e850420b1bb8a0c2e814010bbc2c20010ea98c0e521c539034194725b0f2e50b0ad0e061426ac204c6e25ed2814a0e6b8ee048c0091c20cc88630b2b2fc8a343c29221197184a4288d3bc8a0e00d366cacc1e5e006090725241dee25241a20b1e0dfc5c2f3b8dec5c2bb3ccff53c2c3ccfbbdee5792d345e0420000108cc53f3e45ec255b6fb78b1a77e5b2c3fcfe95fe7afdc55e7ebd53df5e7cdf43d7755fad93b72c242b4e5307aeb77fb74f7648bbb7dbe7beac39be9b7dc551f56577d04781eaebfc1eec809cf5f790e830fe9d7d83b07c4f08c3e7048addfddbcf9b15febaefa3f2e3f7e7e6133ee6a95175e7ebd1c96ff84faf583386c7efd141c96e9cfafdf11b5821b62ea374fbd42bd14f348a38339e79c55bc60557982e5b599585e2def795c047877c58fa70a96957cd7f3fcac62c5245f6e31684591f42f3f97fcfc3cf27ffce5c7f75eaa91f3ffcfe3c10970fe17dd02057133c18c22e968290f7ad4c4105176f4f1e6d10eeeb562c32b8f6c48c29e4603530ba64b38681399f385f3bcde2afa0c79c38836b81421851d446c5d661449f10ebdf07c6f7ead55fdf81d7deaf5620f7cf9d96288a588347888820b135ec46878be37049ebf7258c513093cab0c9973ca2bc46b9c3d6c87c0ef4bb5383c363dbe81656e7e26c0520a5d06e27e09bfdf7582fbd821fd5ba3caf07a890a1c8cc2bd44a507dc9dff8f33c0fbf8f0771c9679708e778807c3975c2f5929018e2f8518127fe53e7248fc6f14208e31c6f8e34d32f0bde82387c087ef9fe72ef8f35b790ffcd51a6fc3bd64250c66e15eb282046e1e580463d25d0e2b3fbc5e09b0f6f5fd5bce8e18f16bc28385e273916b87651b7bfc3bd7b7eff4e5e7211e6c3ffbcd7ed65dfed9a700af7e63fff93f9e7d1277f49143fcadfd1ac30ec90c10c35b6f131e4caf90fcc961f8dda9206fbb2b5e0b1d696bf781464646464643f20b77903683a34f4c0b2b92f8d2069c3cf2886248bfe7b00c77bfebc77de490fecec0fcad3b220ff03cdc4b555a80fde1f75b1ff9396ef1b3dfdd918a85e24b77cdff71ebb078b3c4f3bb61f1b375d7fc76d7fc15164fe2ee76453864ce6f9e79853cd845ea8d191ca0c00b1a274e60c560e1b2c9cf96de0c71bb8b3ebdd962e83d8d42e3fcb0fa99cb10cfcf1ea6dfeed343720bd3a7f6e9af1c2631bd798807d3dfe867dd053f058f3efcee8ad06bed573fe9ae580f539091c70f698800c28621f4adc32aa6efe17084e9efb84f2f191919fd30843e8fc31ad377459f1e426f1e7ae179adbbe4b7bbe40bf1a28f1d22e5378fbc42de848e044f8c3146f83142777f19e14081473a36b223060f363c28cc97432f6ce74714b111961735a0c0f0e537f49218be9410ba0163fc2d7eb03bf83f3ee7c78f9b73f1abfeb083ef5d740867b0f54471ee6a77e51d0cff847b69ca1adc7217fcde998206678bdddfdd5d0c767777ffe6f127feef3097c3b26c392c3ecf94231c9f739f382412c071dec1f1e34b87f1c42bf48ae1856ac3eba51bb6e05eb2e10dce2eec6f02ee251b96707744bc1ff73cce61fd3e1ff76ff36b1c3f88b736c18398466e7ef4614724d2efce01314c37d819994f6f9e3f7ff3ce8887e77727dd257f935cfc20de2247bff9b0ebff1321073e0cb82b7efd8030c001208755e830eee313d9a7f3e61807821c448d0c1e0c9282c39e012b38c061dbc767c161d6088733c499c21fd6edc0f07d30830187e5200ecbad26c8128e9fc20a0e8bcf0187a9707c181cd64b3e2c81e30fb90f1cd241f00ae76e43e10f7a279289a1c438715ad12748cbd4ea5a78c26b7f0bf25a902c08aed7ba0b06c15109b708b10e6e74d4c0aab5d6974a9a34335f43623ffa52495e914a328b34c37aed63492579258b34e3306aa954aa55667987754f9fc761dcd377390c3e7d02386c7b6a29bd7966a9850599e564aa52693e0bd107ae549b177d42f82f38c2cfce37a49a98c6d1400cef194ce195664cd943d4c3cfa6bf0fff666ffbecafddfbcdcff66f7b081e7c6e7de0e3f38370db7b74be7f27a7d501f71d7fe7eadceccab93e6efed6cd2d0c3bb8d607c2b86ed7bcdbc36741de6c3dafa5baf9fb66577cae9f0ebd3097bdf59b539dbd465f62ee0a791c7debbd455193a705db7985bce73e7bef8e70d9f6de15a2d44289bb1f77c52e8363e672170182e0f9b076b08310baa028800e05c15485f9dad3af49102c34b5df36c881f085f0f6044c5f96d26bbbce7357d7754f3f280af0becb07bf4dce469f0f24e04342b836f6d0fff10fbe8e3d947eccb22c9b9c8d3e1ff890bb82bbbe2e213c343a456af4eea2df7d2d77d1e7be1d77d1df3e1e77d1ef2e02be85f081f0d9d8639f7e2b382cbe772a686fef0a12884340f81f0fe1fbe0e99739c05df141f8bcd813ff83cfc69ef83f31fb629bc1320bfc426339dea2c206890d92890d365d93e908898d0989cd11121ba40e29226994523894bea1d40da57650da86d20bd9983e335def3bcd27f579fb93473def7aba9b25ee6ee670173d1ae5f7b763c6d32ed77ffa22d6b297f54f57fb367dffe93351c4a63fdd0c93ba2b647a4dab37c74c57c87690fbee886cf6e9cf8feebc0bcf393f0fadb07d6add45b9bcc2747e34666fbf262b2c443ffb2d72da977daddeb65def4b7df7d5efe2fd8fcb4e02aa47d9d4c7794f4d7d5ef614f5b5f7d8f7aaf6f5b3eed25658e3b096bda679dfd2723bee82de0357dec3860d1b36486c903af7c2d8627372915ba216bf02313c921726b96bd2979ffdfa98dede6c3ffb97fcd8f56b6ffada7bb4cb7dfd1c87b52bedddb2faf26d36f1f4ee88bd102976856a9d56be8431c62ab9d9c273c6fa4939bfcc6159ffc7a1c4da84c342f2cecbb92bdad8acf4abfcccf4bd624fa417c92bfafc98a431f59b471529c961f5e71c03916a4dea3a1bcb8749f09b5f4bdc52fa4cee157d4c12ca3bef676278f1636cd580e7cbeffe44f92521c11ff2e5cf9f38e5e62636e8d7b806faf4c620a4104ed4dbf99e87ba48a018d8c67e504cec88d0d3a3be130c632d14032d180ac5388cc236d852086118995fd8e61360180ac36c5b63cf61b47e997dd5bfe67c3bcf4833b18c2f416cef184cc1d80bc578eebebecdfef6662826f5306f8fead7defbe893fa08dfde1cc447f5f0e617b6a7b75ee6bdfd8e3eaadbde937dbcd9950f87388c92d7f45aee2e916bf025465da1eda27e7bd3cd2fee127976ed663931457d7def8ea0ea29fbed0a41786198ccdad969763e7cfb410bad9d75da0967855c772ad0d77e8b5cea533dfd4cbf6d4fbfed539feaeb977549fd8f6f6f9f7e76d5a2147eeeaaeea21f1128104a7739fa21a9fff1d815b16ffad811c9ae74177dabfaacf7d0dfbeec619a7d9ebbe2abbeef8a684fffc74d5fbbcb7bfec737fb6d9f7dd25ddbd32f7b78fbed61a782f6dbf6f0b35ff6a0404ce947c46ba97d5ceba3cf8ee7307ba19830274f7d71a536f8712ff5b0056fbd39d79d4a057da854aaff71d81151a1fe0585b01ec5faf940a010d645b235273b22ac9b6398f5a8cfda47a1ac45bdfdd811b13f3176447c3cebf371730cab50a8871d11d4d60e53b5fd06f2f18ac32c9189fa545f8658f571ab9c4dd914b03d614a200f3a68a988cc8f3e3f1e74d09a1f6d837de05e0ae20cde22379d904b442ebe05e279b5603aff0585349e375e7117d53c78f8f33bf8f959fb393b1520fee07774726e46bdeadb61accfde77ff137d3eb83b3af7b63a039ab775739f69f54d01cf6bedb3be76576645cba57e655ff5ed98f15097686255eae613ba6fc78ce75d22c7aacf1ed82242f625565d21d45565a8cf3ab8dd67ed7693bb42a8573d7cef8ea820eb1239f7a82b647fe2d82199d87240e08f39bfba9dbe3b22a6ff71f8b02b5221ee4cdbbe7eed2efa75cf15e95e65b9875fdd320fa60f147da4f7d0e7ec0ad31def54e88a38e63e9e3ed3273fce6b790ffc17862e0ce3d780e9cfeffec49ae31520f0c7fcf9f427d28f612a6f9651c63c705516230c7dc0bd84c50eb9d5dc38fb7eafebbaae7a9f50a66fbf88769de7656fbbd597d1c97511f52bcf5b79deea7fe2aa7bd427645a9d3cef572bd4ea511dcaf3fec74fa837997ee5a1565d664955eac4aa1eea4d2bd47b52255312e5dd93d4bebea9e33eae3bb261ed66fad9ebf8db4be10f3ae1d73182188347c0bd148412de4e6efa84727df945b3a2503f6737bf43559b259e0f8094c21fda6bd1f4289486d2501a4a43fd4f44696ffa843614874299506fd24cddff38f7e5222c64faedbb43b23dea7fbc7b22c726a811cd6e0861caf41beaba77b3c57ebad9c36ebada7337fef651f843cbeccd1805eb94b8c5d8555921ee6a841271ed4e8518b6bfc9fa650fc76a80dfd50e7e57ff27d6f8da2734bf31acddcd31dc41a8d5d7dc8eee4289183e4076dfdf1d962b1808eea52b6bb0bcd16139836589aed3b8d73e772fbb2242f566b8755d966559d775dd675dd7ddccd1ace5d92c3697fdc5dfe0b7755704f6973de76c840d31fd84a0d480ed37eece48847842fa9d25fe0c6fc75f834dc0bd04441457d05c71026fb1ab7f450b1600ee2520dce0cd39da11a90f3bce5dfdf55bb9ab6bb6a6cf2d5c6fe63efbf67bbaa6dbb1a77ef431bd9fbed69b1f57efb78a7afba9afbbc7deec61fbdda3beee1eefebee397db6a7bbd9e2daeecabe1d331e778926ae377bd915c93121ed7e16af10fcfab45e0a7f1cd95e5e0a7fc4db33c63915c304c74098b4ede4faed94d2f9442ee41ca59372574876fd244cb2493246da5466f4b32dbcb254d636c56a707f78e45a8438b841682d54b26e9f3bf926adf45e9e3cd3cdd6badbe73ecb23dc854856722f4dca9792bac3a45a4dfe9ce9244f9be9b57882485996c13178b39cb596f30b91e4f665ff1337c941a41d99c98afa187d50f73b2985e4dbaf553e8448f047f71277dc8ddc6f96abdc476e8b34494a29a5ec25937b79442d9ab7d9b8ef7f5908219c270d426b2794fedc2731fd1a3b8542e84f2badf53c28fff4fec18ffb38dc1e3d7a845a849ff6fe69dfde495e2e56837c6b5fcab7fe86936eca124bee2514e2cfdd9f28bfec7670d67485b8afaa77fb9093ab97aad5cdee6dace79d6eb7bcdc5779e9fd710981a2cf4f6acba47c29e54329edcfd7baae087c7985e49f9e8314fea059447912f5de6bd0bd9b89a685f2bdac2342b5e04d72527e12739f94107b6764c32769bb22fedacf4f3efce69b1cfe2639fa82f4a1b556fb1ac3ee6dd4628cdfbddcb49b3d7bb395b25e4be18feea54e7dfa337e6bbbb99569375b4c35ee37ed85b84ce33e7291fbc8dd4c346d7c0a8568d9a5f007ed88506f83a194524a49e18f29a1d12a0bd7123b7e74bd4e089282c3b6d73ed3779f7dff4c0fbfba52a1221116583256830646934bc84f88d36e8e61eda1a42fe1cb2d52cbc240215a279f4b653725dd2f97297577d7ae907cee35e95ff670ac06ea95ca2570ae4fe442da1761eaefd225ae607e22fdb2dc827395da77557afd970f6f8d13c609e384127e866f1d963d8410d2d7b4f74f5aeb79dddc8561fc93afd1abc3bdfdba42dddcf2a7371bf1a74f1f7648be38cc3e7c08060aa10f35fa35d6e86769dff4795d11d4d32b445ffb13caaaeeeaa62efdfad6614e596bea8a78e773355533753314e3f42dd711d9363929e6bec692d24f62f951ca691482f137fdc4eee3deeb9b3eedb3eff4f16399e8855fdc55e7d3cfd673ab615e0f61991eb65a4e27ec60f6d05a6bbb2b44df64c34021193a4cbef71d7de27b57e28d7ed5beb6b7b75f21faf6a17d8d6e374331d65a4b4d500c8657277beee7b75af6eb739f94d8f4f5b7ee331483b7677da7cf12496cbfbe355dee2b50b4b6bb198ac1f5c370bf3dfcb23ded886c520bd668967d8d29a5946661e08f4aa191e73ebec009679a712ead77bd3ef81c64e7732fe97c6e33dbf773def6f1dbbe3f04104c1fecbc4ee6eede69ee59063dfbce2914721f7eb18b9a77af4d3370c9dbb81b87739af76e9ffde7fd79b3ef7e7e16e1b2e0b01c29a59c69aecb79c6effd3e4f33140af138cde0094d6d6ad3c39bebc4596c45230f8ebb8137d7a7f0faad7049e3fef4ddffd976089fbd06a1bbd4efcd1d4ac5eac08356101fd27d9a996670fd6dbb14fea8ddca5eb7087fe2785e9b4fe30300de1c2fd1c4fe9c6b3f5eea01b07a378af23bf2dcd38d37fb14e1423ae1e02debbaaf719665ddbd546e1f7cfbe5df3e04106e6efd0737bbf0cecd2f9d9b8360f93fd1071a512824cbf1717390eee620ad9b5ff6e6970737bb5c1ddcfcac9b5b4baa9bbaa89b699fedfbe7ee3d33994c266d7bd3677b290bdec6dd389c68947d363dcc2ed1c410c2cd5a9b3d6cf2cf2d6cfa954e4a29a5b4034c29a5d487971ae4c0e535a9a559ed3b6ee03d4d25e9ca968a63b044add9eb0439b881892a698ca0038c375878f060adb5d622808b1a3dbc80d3822890c8c4d1c8b8c921084de332441859b880066c30a1c3901a8328c8c4608b2462d0438040d364b600c3e5861af490858a0222183fe0e1c31ad2891e7a4a40e72b3ecf0968b821e68c13fc70050509f8a2bec14229a519d5ae54e967e228cbac0cf132adf1668d1bb041858da4f93972dcb0871352bc71811804241db5154ddb810a6e06aa2972072b1ee89004a5948201e33d3da64cf603e6cc534a2911714ace0c5455ec10565c76361a2323231c68adb5b21185f7349831652866e30ca5aee74169395da932e79cda9c9c1a3d4c9a161303e6cc164188d9c386255250d26176c1a2696dc8a1d3c60d7cf857c541a5c3136ac82c9b42060730e894aa06d73a258fc65f3891c54887232fd258c11c6608f9e28b46e68b2d785803255f3e240b1710c018820b0906103b24552cd424d912e07c3105ba400ccf0bd4682f98230710d2c820a90c2058000334be40218b35ee48434d1a6bd26843421a38b22519fc7065eeb186524a7b50e31b8f399096b43053850c3d0e30459824b8181919191929b911ee2526b41809a169796c44685a1a49043563ba000e8a8c1d3e5ca9b29179810d3c326d4ed08351abb502ba83d7f2569ea7431a9e0956def53cff409ec0527778d55a2b162aded3491363b9d28a4604a005061456ec0e5d6489811e53bc50c38a206f661b4869a295278e40010f44989182295990f1a9c28c172871f4721fb223dc4b561c1de10078d1c2488a3b54f0e608d811774587773dcf3f5784694a00e210038b25ae00d3c50b0c40a908f1aee7f9cf6323e25dcff31a921bba54f1018d227ab0e204306a88f857754197c7a6e466ac31a38d196e2a1044fcf0464a1f3b44ce39271aef69356bdab89143b4c091d2ab9862584cd9d28551162c4a5698b0451c7b58c9010ece70a20d901d58256841144234b1460a3a4a78c20c0d0b28a67c41d891c41674f84046122fa57f5122bc373232baa1d65ac15862821185d7840c5cd0050923f4b0e28a1f612c6185a70ea5a3b8078551888666709329e20c9c2328618238766061b4e6a800532c4d244dcbb952c5948419f7a1b71a234d1c2c50230c376c9004095ed8c1439691f1c20654bc8f4bb3d25a6badb5d65a6b9a3770ad72881df08064d070d380339618038e334865d610204d13199934544cc121b1c7111b61dac44187990178c1e20397d6cbc8c8680aa594aa31f29e3ecab01aa52c6b41a1b2061c3adc28c388140ca12360ba83894c1b53b09851323232ca41f6120ec698c1b407d31c2861fa84153746a0f4bd36984ef1022a5e2a9d34c394524ac56893650ec6107270c1c20734643084ba302553c69a24cc9833e032adec68d0464586067b1c35a00c30ce88e9f2c51e360800ce948c0c9ca42a31ce7681317030838e2ccc40410b02200796d7ec3546464655e69c6f5773ce39a938e33d9dc6bee1b169d9e13d950a17aef525b930726c61ec4002001664ec5218347e30c349102c28038e2fb808420b1e7aba0308315baad840658b0857a49713000146075382e88114270c1a7508a1c7f582b481f144172449040f1e8cbcc04153c798af0e248e8e785b6bad6f784fb3b176783f6fbcf19ec6b9f553c8518498247a48620c23fe8852b9591902b543ec072b6cac00872aa840410f21d02094f4b0628e90bcc1e20436bc80871a80010219206440660a31901acd192536be443144970fba8854689054e9a4b0f5d1470e518a3a4b584ca1e5c00453b63459d491650c1c1510b143c5b261e268027770e1522689277c8043c78b1dc24a1e8d066f27c8c5c8c80889524a698606f7d21d4798d24683b57861250f2bb2d481c699213998be970653374b573c7f63d65aebac3f3f9e338eae08b325a90d1940f800c3892e910d0cacd450868c25c250e30641f4d0029346aee0cd0eef7a9e2cbbaf471a2d6364a0061e6178b0042607948d0d3afcffeb4ddf600dca4d1694cd952a40c6c8228b29b05843055c7480038e0c0e7a30b2b8d65a6badbfe2813a78f52b66b3147b1acf69f18f679ffd56ee6a373150c1094ce0461a3e28c172c2b6f9e283a4523ed5ae47a9ac2b5cc9b031836b65411a5c6bad370fbd3c77c9cf84f01a7b2d6dd8fab34387fed0ef02e4cb2c7f932da55c05e1b57b77f37818dbe9d309a149e88fe1c6dd81f0c18e4e8e8f96071db056aa14ca3b993a6ed3325be994d19b84feee6093d025fe8f5bdbf176f714c198bbb3b935f87eefeeee7edc5d3ffef11883900431c6fe7e7873fffcd8b37ff60bcd18638cd25dfd664a895ae10d35ee6a2c5b4a29a5942fe99c7442d966c96f5e618932c6e81f1fd64133a594da34fafe36e1cff717729392e79f39188cdfb0f9197ebb4bc2772c045fba4bb60ab42f22703395b0fc8d76f140c0dd2464f9652a61f78caa02cb975fa612119e7c69e340c92f384f2e21e07933eecde422abc07972511a83055394b0d4be94406a1b673d544ab5629d4c5d16c4cbda95ea85f2ec13ef64eab84d6b363f99dbe7b1945e0955b02601ddc8b623f71b3cfb06787e0bfd46a7e933b9cba44ec09300789aba27904a10af2de562ddfce26e0e82bd27dc77803a996e77eb85548a6e80eb0342c0a8875f5837077961faab9b5ff4555ffabbd9a06e8e6df1be6fd7fcd919e925cc3d548a3ef0cbe99a6e7779f0e46ee6c0972f5f80707d592195bae6063d402a2b98c1218509d0582dab9c4b0bf7c280c5f55fafd76b02fdb42e3499cb34e89c70287e99d4271e148afaa5a20064848e9b33dfedf307a3f6734421fbf9b0853800fee8323bbca2cf130740213009fe88ef799e6d712fec38f719ac3dfd64bbe63f893e16264121dd3fecbf1b376e50e826213bce30095a0b95a28f92126e30625e3cd64cdbfd9a84f957bbd903583efcf04c82db3502dc1999b0dd94dd6e12a6a55de664250c396497c13266821605734e5a6bad75428974cee974cd6c69ddc19873c239e79c904e7a051cd3b3cc68835ab39f6f1b05faf3b346c1723c50a9f21a62db84f954b2aff24334ad88b609d31639b677360993e209e7ec36c0ac41d25969ad53721ef5a88775d3d25f27bcfcd5b109c0836526b797109b46dfe5fd5ce1c1cfbdd44bd2bedc4c392c9d73cee9940d8d8c680329d0fc363f59b7a9cd3929a5744a0e64d9b7c36a9854d2496b9d934e77c92b7d684a27849452af66997d3a9f7eadbf550e89fcf9c5da199197bb4df3901d11ffe9ad5a63a07c7fe8922b1ae3483c89b472a330bfaeb6bc8cf0b8cf2febae88a79423c053ca4a219c52d4392dcd68d71eb4cc023d9c614641e9500b533a29a555d096562cb58401cf97de500bcf8fb6f38ac392b68070cf0546e2ec6a8da0861735640bcb5803d928d0b8c411c7f4ca26814e4c63f5eb26226b9044314e9c38716688785034e484b235c4cdcce109a77f5022843ed66b496965a535674a39a5a453ba7c971c1d99a5da4b88ed73754f66e9bb68b0d5e594083f3ebcbd058c1d116bab0f5e2bf6c4df7aab9cfffc7c3e9e9948d28e08bc2defcab4020ffa6ff3017242a8855c22411b0c9fc2e165a8c5b56029653709b2634fee36585e1f722d30449ffaf2c225d792049d702796221087d41b95624f7c0d8b3b91dd09d712d3f080c6b5c82579c59cd3d239e72442bf9896f3ac98d4a7153aa5cfe145eee7dbe8b3bd10dce6c25a665f4fbc17cb8b1a5e5e585a8f2e786f35e184d07dfaf53cafd592d67d32197d88ad6fceb7f3eb7c3b6b4744ced9199997f3aec3c38354e61af09cdeaa2589249eefdd1105c4891347622971f344eaf3b583e94aba0ead56ad40ae4a6b903c3b62f058014f5443dd58eee6f8252661199570354231948ae1ed2621c7a32e9efcee5460613458deec4f7f287ef9f22a0d96ef68da0d8e9f87e21725dec0f3f30a473438c725371d607aa9929bb8e469708e4bd1a900f3838d5fa86403ddd04fb61b4ca1bbfbd06a367cfff94189eeb93569bb1982f3ba0dae9f5bd0880a180fbee5bc7693fdb00419b2ddb8a159a637efd3eff9591ebc69db45394cbb874a8b290db6d639ef1087a91a5ed4f08269a54d38cf0d47ddf0629c710f7a048c74465a65d233b65b9db98210d1ad7e5d84ea5dae17f75a76f3b3107d543775339bd9ecedb51f113dc5891307ce40b1bc338ead79b2e6e6ad73f29371d6cfd38989049653082ca70f58fecc611ec9973f78526636044cbfce239ce7128e9f83543caf66468d073fcf258d8cf9057a41c60b3bbf60e92ecd0d171b94327a97f6e0514a29a5b7a5c33210a63fb9d919794caf7744fceb4b7a25d13c12429c387130a5ef4fef0c13e20cc637fed23fcedb5bcece179e7f6329dffd8df8c71b8b106ae13e072901cb9ecf47fb78ad46817e90149a0812420e01670f6f5a86a63aa617862621befde2c1ef16a4803a2a93844c014b0dca2b3837214043bf78a15fe8172e948a1d3c2f5d9253e8125da25f2017344ed02f9c1b19dd153120b387ed37e4c2d2b39ee7aee8eff9b4a4f5ba789ce7ae18c45df15bd127f39a82bbe2d3a52096f31c4ad71af902ca5292216b4d7872485bf28c1ad29694bec0f4330affc0f36a689a84f8b40c0f7ed6d0686870fc293787dad29196238c2a565b62491881983e459e9191910d43a4f06ff95448d2dbf2e3d36fe70b4f7ec30e89bcf1db04af9b3fbb151acbdb1d8147e2851225846f54585b58fbf91d875cb8562d5eadffe3daf7dd33dffbcca32e3c69b0587e4b202cffe96cbc794784aed0b8de21179e0fbb233eced471059e167737e7fbcfdbc485e595afea51cdd62778f48bf8cbbbd36a08615ee1a6d9c2ee964edf7b7e919bdb6ce8519aba49f0bcc2ee9193d4d60c85f659c52d9efc22f25afa1eacdb954f987ebdf98463a7c2c4f3ba9a84f85d1c1efcecc2310ac4d985e35b0993d3bb151ad33bb4c2f2bd3b62821067eac0b23b79758cb8bc4d5658c2b9471ee260124c82470d91c40871050f00f7521070f0766a87e5a1f905c79f2d2d0d98befcac102dee92b508bb102d38cf9fdf9d4721f136fc41e997855cc1f25d8892bbe44f2159e00f7965ac42cfc23fe8380eb6bf75a702c4f6eaacd0d87e91c6f6ee94c07b9c6d0cd72f3faef55dfefcf11ac3f3239afe3f3136215c636b1efb0dbd69f036eeaadec65dd5dbc0eeb3cafde93375dfcbed3bfab4fae6c7f266a289a4e10f7ff94148e10f79fd9af97157bc96049e7ffcfc3886730df727f6a4f087944598e6189424f0fc9fc76153fe45835b84a218d39c9e606b9e0c9570b4edc25bab1b05487fdb9c839dbd2bcf23e25ed33ece3b0bbf19bc20d10786d813dfb6e95a064f88fed4607058f6f1879e1039ac068719394c931f1f1ec123982435fbf688b401a219a8646a598a4335ca9843333390000034005316003038180e080524b2aa6af20114001179b2525e4c948aa328c95114848c3184104208010400600818438463039b800a8633fff4469cb02112bba7b18c6889720983c7847f223b9fa0c8e05b82de07071d25ccf677f412f1965aa818d4491dbc287da19482b109b0a6ee1f9984e00db19ce74b3d9a168fa6ea485682252495d29dd0dbd223fcfe7e96f2d297f6c51639505a73da552aa6b995b76f5751949906b5ac454006d2f0a35901f92005c4a22a21b958db58153d758a38548447420459157a234b34bc3c86970a262a4a44a8fe835c3accc41504cde1f8769e5be53f14a268e726ba2fc37129f22cca15cc173a186afef1908ff09ee0262b6e6ef8cd027f270ab50cefffec365b712098ff0a89dd72516d13da3d44e0b19ce4d71b5f069b80a06ca562909fc8ecd3306976f35444ccc32ff084054f5e04ee5135c25d3580150d04de271e15270695536ed00df8a051bd03821b5ef8ec63b2d0f17496427d9799f8feecaf0ce2ab300f10df58e96203ebda71c902d61a2b61cc9433be973f0bf658f3bec0366c79fbaf34b140ed1e42b29eeb28353c537326a83b6db7216d17ca438291e60f1bdaa41a71a43a807ad708a4a96813cb910c0234324ea62702d044e8412fd27edb3c1347431fa23b71940b02128c79cd05968172ec84427229f2409574359c3468a7273a58fe88953bbbb53aecac0d75bb1d8fcd8ab60f730cbba170612e09b1c71d89571ebc64a7884c59c8161d34c41d20d99ff5523722e068722461bc8a4610988c2103c7708faa59b4d821e14efab164ea248a5c58ea3a92e110bd89c68df5bbdca69ea2a0480a84ea4eb3e2c28de6bb457e82818697e0618727696e59f781831dba51f572b63488b5930a725acb7086a47029f909463814b038a913756edefe8c955d3c857c7d755bac9ea6272be3080bf970bfa5efc42130ac8f8e4be28819617ed4686eeda6b67d4d505d014ba3775e383f0a5324e08df6d96c6d454c863d31a1901982c2ad2c3f02512bf94ffb81b02bc62084f6a007fce6a3a79e51f83639ede732bffbc70fbb95ec2d409e7ec36d5fb607c227353f873ca7660fd652b2c04b6975a806f4f526e28aafdcf92f91ebf23a3baa2ebbd54900b0f170c399abf58d814679d71d7b5d1ef9fbabef86fa4c56951a4df4f3d8feb9933c2341225c61e865bdc2f20d0dcbb3facb10ed0e5850ba175d0e061839214e01bd04a5383df04a775b2c3f3ff7749e35b84919db2c0baff729d72d0e1c36164865d75081d2eae90bbd05bd4636ea8dbdb72e01f635ff9e59dca97445abcdce6842103cb0b32083aa9866e33e50ba4d9921273a2d17613ddcb2f78bfaa9682e888510aa701967a509f30749b2ecf16608a4d9db59d1592c54c2d25f7e6d076a76953a1ef347e1b7dbf3f73b8ad8352842ddda9041f9b5e94a8802d553bc62568fc68ae7a3276f1279565b4f2f8767e8f4f37d00ca8e27c7735db6c6082024e73e7027903b65457d791f697ab6600c366f74511a4b85b0d8b5a7d21bd41c151057d11f9205702982159e2e513d19a076f054d53792d5d0b8b139315bc4afd1dac03c9731971a22abc93ab5a4d314b65c10530e27ca1ceefcf793be863d480d617b506b9e292debf6ec1be6d87f9ed8999ae77e0a8a821a10f88d22a4346324376942b484b10ba9de14d1e3d7468235b74e089e4be7cd4cac038ff60190558201e13cee9d765b18569dde3d5c9a68c70f2bfc3802ea584546d5ca781c99926a12e5a3bb8293726f0b7c365ae1ba511515ea2656d04c043529cbbff9ed72183fb7abdf6a4c43dfcec55cd4bdb0692b5cb5853cecb78fa384c78d61e4c461320fd53047e7997a5681b8d5ba9c49d2093a6a656a13a0f2c2acab52b0ed1c2678373231ec0369eb9af014883062dfbbfbbffa0627860309337088060b465e25593980f1f63245db4d2dbafab0b26ab0200e7f4b7d813185acb8688fc62782b90036bf6bfa83598f76ea6a812a039a5917496731e5105a0c8a4e27298743c02757f31b929baf6d5961ae7222251b62ccb4e20ad5f62a90a351e2cf877f34029e42ab0aabe7918178a8c8364f4292bfcef84111020a1b2ae040594dea6ef73d83d618cbe23adf4424accfcf206468cd7faee4000884af6a4f82cb7199d4b8732e937846a4ce0b7cf3890fa26e7df5ef61aa2ddd943c1eefd0bc64728d0d612855f115a773ac4f46e2db3ca201eed4a6ed6758e9686a54c665b24bf7d3d0aaba98c5a3a14df6885735abb64506ff81f657d34ac1897f31f658d20f643fcfa1f70f68a8ea511df4bf8ca7dcf89347ca81561694bbccf53dad64847253a68b1660bed6a141754bb6fea99c75e95665721c3502fdcd72ee05eb3b0d88aab0e1092e2b2e9e27481492e39ffb7d5c882c88b6bf3d4241f417ad9b7cf46e09c8e4383100c4f16e3e64b6d25b1096b4e6d19bc6c8c660e19feb36595cd08cfdea2b7ce1868bedccc720d954ed6189a3e13f8c9bf644c20f96e706e5ff68423cad062a3af527cb0c8bea85ecfa0c6b4d0e864c6b3246fbf537ac68802d6e6f7d120035a1851c61cc7bdbf4cd88edef952799ebd24a4d05c7cf82beae23c9933acc09d67fd851ae6553309d46a160576c1174ae9351b8798854f2712983b80f75b1fc971dd8d50701eb2ced7f31262dabc006d6284e0a640d4cdf9d2f2f14b81ef7dc2e430838da441be003525f17d0b326bb844877e457fa0ac7479d52822ba84d1eed93181468e5a5a671e8620459c36dc4e7f26df061f55e800282e43970be4772046fa0963c0c7a6d80ae040a02bb891e765dac1ed7641a5b45202cfca6039317c099b6e542b823b1d1711ba8796f724995e1ce3521f0584ba3b5b194234af17cd4561df5c52b29b12f8a0374b356e71d7dcb974b051146697a438f2f531f3b80907ebf298f3312a04f6633dae17bccd8b4902da9d624a70888230f2947c48c7ae139c4d9295770b05a6b3efb6366f7d20af279d9fb84df379b5e4b3dde917fd6514bfcb570f4b47e56af5ba3e9d27896cf3a23e9d89774f1b7e89b61de876a106fdf3c936ed9b1c520c696cba71f98934360420ea32bd33e7e217590471a5d65975891a0443b83e2f310b0edf9d0aabacda842bb82b7756460b4d7ef8f035a2b21868d0e3bd4a66cfc6cc7e553b3b616b5d7fb48d7b80cad7a6767bfb40676fc7ce62545203b42225e2f93d7bf41414b7f0bf051549a92f946ef1d2b184d650289cd0673093ebef15cc088830f99553b9481f649bccbdd06953ff311f0159b7c845fb4e9a4c97a0e5de19a62f62e43b81636032ef22e7adbebb64798d1a5e5a921aca0ed9189350a01e8dec10a70630197bdeb8288f256ee9394093cbc2fe5604240434e82c85adf0d31e882d64be51770e82708a48d465dc4f792f63aa385280e53571b5e0ec57f1f9d8bf153337e1652dd7a11e4ce9412206650cb185058fb6efb53dd1c4c3b7e33940b0cb00782d36eaa25a7d4ee3035640b2d4ee07be3f88e4b126d2f8c8eb17c486851da2c9bc045906dade52a85614a2bfebdb0e7a181d2d374622f65d5cd396f85e6bb252500234dc5d5f50e93ee12da74a9c849e88e15bb81de159a60d20bba68cb8c30d1806955505d1557c08e162ba002707263cce400a1a879b6237f33339c05b565251ae712ba49c4cd0a1fa82a582e3919722c04a402236b9fad914b846beda7475325f9a6f14a6e218b709db346fd56e35766bd5876829dfe06ca938d3fd9d48313906ed0b4d1d36fe5e6e10d0e550323b98504f8763bb4c7799671c626272895b9e6611d4657b36acb88549ee1b5e9ce29420f82638528ffe4c378158fbb67bcffee197f1e4d3cce5b2d57470fb2856ef3e683d5bc446df6ffb905041af320e8a9acdc06b0b045ffab110bd9140bc8e356508e606d7f52bfab673edf130631e2a1b2c84b0eabb8970b66b533bfff4b44483e442e90147f3778361bc6704c9f6c3e8ba2932c466de7da05c6ad8b62b354d575d1f28e2e3817d2ad8284a6a87386c0aa37f5aec395a7a0c6a2a4a4b085a9800c204efe084aad04ba95773c587915f8b3e1c8d855c8dec7e16a75df04d3e42424417fb1390892892f1a3adff7ae09e0c7bb8938c98ff077f74ec63c9dd409050e58033e34019be50a9a035c2ff8447ea304d318aa23a7deda022e6283dfcbed6431b7849bb96d7aa62293043ebddb1df1116720dd3361b17c419247cb652f1e915443fc6be8932d642ee94881899139e128f6ebc1614311ad4e10cb524c224ce0ac23dd81a3b476e9f840bfdcd8c30a2993701b2548f50b9c504a7a629a322bb22b6c469e807fae416d7a6ca486a78b89f6067eb45a01e235da8191b53a72325e63b6c06c58ada2cd3d1cd5a1f03feced9f56cc4f6a9dfc540dcb031bad8f42d94b1817f2b99e74dfb2e0ab98d1cb5aa92bbaf35fac643979475f33a2a44303edd108e9070d643291f9e487472034f87dfd605f841afe6152c33f63c92aacc9592ba1682ed3a3fb3a0a80892e1d618271e60411910201456847e935165f320ee402423ce913091b8e2f8f9e4f77f498759973a426d671df83cfcff8d078e3f38a67e2bc691c57f56599802af9dbc6e70a1365e2be85cc61045a346239f050fd3cf15c010e5a0de10655c9fff039f56a6c8b881b49c489e2e08f24fd80823301b3c4b76d362b2520e66b838f19cd360ac6334919e456ba4d2de24dedb140f3cf367eb4b4941f9a0770c2a10f4d408e7475210de2d4e8d9a80748afe78b3b5f5163622eb45a0cb3d31531b4a0fbcab933b278eac661d5046a6ee06296a3f77fadb5fdfd647b1be9cae0eff5fcfc6876cde50ade9655edb34e626e01f8dbec4a4300148f7139738314c9f78c61ddef08a415c4beec71487b279808d8d04f118d824f5844e03988cceaac091528395ab7e88fa5cf2414e2e95affb4dc6b21bd8ecd3682da314b11ff9d62fcb0cf97f309d9b84cb0dd36bfabd24fb3ac5eff10fefda1f833b9a25564747d0deb94aa7282ad0296efbb52ca5bea7bf23e1fb1e8170c4a3e1fd4f60719c6b5b44cb0c1bf6374d159e3528014170874cb5884864d80bdd7616354afa9f6a3b7d9bb4c82b83af079e780aa45a4e56bd23248c08531e629f86c64b94b0a1b1c7176e1eb689fc2073805b1f8c45a8b7ec3f562c3d5ddf422f939fafa85c70c6152de4663692d7e4e3d0a8802a695ed9926470982615ad63b43913611b51ced522895669479bda82a1314a6c66290d4e887764cae247782b2fa9a04c86216ac5aab45b5b95e5798210e1128772d2f5065ef6780302b4a1828ef2fe8fa14197bf8e57c784790fc2db41509497feff9fcd949f459a09560380f7e90eb1e3b57cc6dbad2890b08fc4a40c1f23ffcff494361854d66e5648b8a69f6af222987df781981205e5c289a70c549df74f76efdf82b22549c06e7bc6c1904a79dcf4f411caac79e5c93c451a82226aed8440fb8272abfe9190a7cc48ed99f738a67319ce9e3da0cab966372ac6c02826f1cc67acbbda5d76278f5f1571638f6ee172d493ac996b2bcc11b6591cb6948228d2a989bf606ee7e6ffe49209fb752b8603f4c2a64128adffb6608b5f09b94722420fbb1a98116d4f254c93784ad71ccf398ba669851ba948430af0456da252358419d5674ee9b61487042ccd6e58adb58beee8368ba55d5efefe7fac675233878d337cabe0986c78a84e2e71e10760090af95b5fb8160aced8f345a47d7b94ee300bf62e3bfe92769c0d8adb2a48d18e2b51c7c164127dffffe3df979d8a292ff60f095fdc157c420797c857ba7ab3c5ed766528cf354a3bb10684bd08566ebb00f2212da607d8a704a68d820c9083f9c0c3cb1bf4065ac91cfb19ed9c74da4eb90c7ca395d5f7dc5f811b2d15e0d177a40f86b8f7eb621c0fe2002ec000736d80e542759b32dc1ada6687a60f1a50164435fd9c7a30d94696e36859ef54aff538e22fee6de883213b92605a128c614ae533f831687b5e75bec927f63dfcbb7e95c3570920e5c3e47e2515a4cae43181248570f0f9d3da5220254e8ea3f457579485cbb7ae5ede7a09c44596ad4456ae04002e7688e2bd6b6514046476982ec80728c5a5de7b6159d9175a1309c77d618f7be11b8728a019501c571909a496a0df1e268b2d8788821f5b80a93febd912a71530d6832193e4e0a09dd65b1e521748efd83c5128ded6d21d5a1ff3bfea6ca3bf4685cee29ee0f5fc183a39f0df78f71c401bb039b38a21ffcbbd506ea311c118156488d5e00a9f711805f11415999b88dc3e731f7aa3cb6dde35171b95e93bbae1e1160080ded4e0b76f174b6ee6826447a440ba31b79774bc2700ac930068c76d5f3d0090e19ece53c76023cbb8c976e63b0e1407044ecd76a0f666132c6b2cae15867e8392bb633df67c805d72acfb530d21af5668fa70e0ceb025e3743cedd844339c2f7d649aa37edae9c0cae71cd35938f2cc468a9ef09338afdfaa377ce7f64ac7d2bb310031446f7d9497f13a3596844fcee309adb4ff0c8a6527769797ef52b769d4aadce3b1da8ec676be77e41149e19f173ca9fecd8d306fe797d5b6aa93d8daec63f077861e13427d039cc3aa1b1f3c82307292c1b1930b719fe35535b316f4194606a877d95a78862596f8ac5e06c62b0a7ba2ce8c42c37b203422c8d90eaf98bd7ed0a2e8ea1108c4f05b0c4bdf9cb40a1ec40aa26273c40550da1671e825224ff4897b27f7919e92ed11299eda8710837162751c1f96ee03908d8e1509a8cb53b6fd5fd56c2b77fcf7e00a546867c0338945ccbf3d5508648fa7cd18446820d283ec768f03f57dbcb7c26a0e0d7ab2881022c6263fdbdea9e021b152ae6ba4aeb757ef1b60caa95ff9af37b9661ccd370d20485ae3a61a94e0ceb3ee04e8592374b8c90e221b4fd83417e0d87d0b90dbacf99224324e19a5b637a61d89f83808300119069116ad2e19c78d26c83205c2e06c17795ad453ee6d47955c9fb5253f3b46a5b07ddf8d1641ed5da6912c1f85c3bb23436c16645637b6b79841d85247ef80cc317b0f40550ee25950868d38efc17df52a01913f59587c84fa089c2b63447067806c3da0b9090c5bc33d0d8d1421aad66ecd9c706a2b809ee4193c6b71a3a384fbaaaf5f47c2c4f0a41b148dfbd3805c86b1a73425da8524a81a7b9a1dc322142d7cc6b7da25fa106b6de887170d85cf5b04b915b78dce04d02dd1950dd68e61055bbf5db1b5af1fabca8c8e00cc6dfcc120cdb970c50e12c1d73701f00f4d0dcf2a8cd91bd2889af3d4879d8caf497e485b2a6c44d8454d2cb02bc97880bbac71b099124999a23444658ad58eb6248c97e9c598faa5f10ea52b9300abc4ca06f5d21a1a586a45c516a0c585256c3a410a4db066666c444cfbb89bb26ff5123555490a7e47cebaf49a117f5e81f1a9b8c57209be03a788f451f52536c446f2aba792417bd1d8b74a3ea6d009b602bac73c2f01458f0da65440628acd0af53bc29f0e721dd04abe2426d0ce470ed2a42312b78cc486a8a0992ad3f59aa4fa165f40fc1a7151967dfe3144795b05514447f024e5127f11afd990ca4b1ad1429174a91d685889042acddd1020edd07dab346643594ca8d717cc27bc82fa8d018a6d04061067294def003ddd5350b25e93130c91b35a482bffc792195706d13676f8082f134b34eeeceb78db8bf31f77b68580b667f2c8d86d2fa62c59dfe9f6136667a11caebdea53b84a21f88494a26103b393ed394c520ef25712ddf82aa57b470976fabe3e629df2d53a259b8b4630edb37a22160095197f51655ca14d5e6d64d48539391d2be9fdfbccfd01104bf37ba949288d8254377cb43172c2f6389dc8768f231a37e8f4165cafa67cd0a2f3f5c08a3b370e73f302d0c10ca063ea46cc3970582bd5d2ea781120c1d20014c20c332a321e4d5151e3db8903a14a7233c62635cf6c0e02c0434164ab8ee26b4e627d369eef0bc4b3eb3f2ba91767e6be0a84bb70fcef720f5366f63df229b6331db067753126473e078ac9b1cc848786b24b0c21bd8d1ed2e8cb1bcf8795cd58be6c19441c8a41161d730aa2cb65d15ab1f5c9d57a8dcceebbc0f22560c8d87055c2166385d3e33f88a18022cfced79da7a60d939ca36a3870f1ee9d4a9cbb5212ae29feeab8cc0197404440b38c6eac85519de69016b5f1ad0ef1f4dad51522d3486d983812765a42aade8f859c953152da41df9141bbc4f7ae59281b4db14f24acff0b39235f5e8b8a55e8f3ac90b0c237b7b97bf1a2c419918da08f69c91a5e6973339d434b3b4f3ece1d9dabd8931644a9277ced48658b04e1eb11b984e7b6d5f734932208561c9069e90ca0e615ef82d9dc12f7aa0c16fc7e9512d1a86a772a08454bee0066c7eca3e7c90ce619cce1be8038faacaf434b752532bb0ca45141ebe9fcdc72a48ca6485680467a4213ddb7f756d47953230291d678bb71f0c18ef01b71130d0a71e8161ab80f247d529371db418f3c1727f557d4e1269741a0e6a1f7317102eb5c9270bc20fc0cec4550006b1c13892b203e62557a361e198717f02541b35e532cb364c7d8940d20abbce6d03fce80e1c28bf0ade4dbc5763834a927bb6a424e002b2f9e014b401dec941a07afc43bf9cd25c4933f1572787eab436485c8a8da581f513262666a6354f5c2f3f044e419dd3ad031df63b8aeb154ee008b69d8f101bac28ef4181b7ff60a404d2bdc9be14446daf489d197eb6b341c2723c77c668f0864ed0fdc337b77761d8a7a5da6c5b3cf4af779379e704c08373f66b7188b381a28b4db9e96ea4d5dcecf0f8d4a64fa60eaf33445051d183f7d203326cb1eb01920dd68b1fc45a59c574d87b9f08b8c59a54467d9beee232027cb1b2dc34fb6648d47eb05552d26f7326e338c8d9a6ebd6eca51c9e1ea1c9c15c6e02a91d89dc7425975d20b5c592753efe1b9efb31cb6e840723e2f8ecad092353292bd57e107c3147da1e47810204ee1b9138b04112de9711a7310548000629dc0c58e68417c193359a0cf0f06fa0d17a317a52a4a5a4ec7b5945cecb8e337f8056140a5993928eca2efa063520be19888e7eea349218e33c05e71346894bb087d0595e2618120d63bc2002cc5954818e5d9632b9dc13345d15f336ba6ddb9d0b2de6f51a482b5c365d80223f957e7aa6420458f504ec77ca3a223223bea6d96420c374fe4d1312c636fb3e638b084661171c45b01d566c99579922ca319b9494aaacba9017160cb878d868b7a3f99d38ca5c1869c6c64347aa1b5a0a06e7c3bb4e932bdd0df529d61144f61f80667853c62cbadc6c4da073c7ebdd9b29733a2b6cd838d3ba4080be80126170253c5050e336ac9a8ad10d74d9d7f86321538c9f6a30bf5ae9500964c54612919f04e9979ae6520f961a4f6d8fde0e677f4998e9dee44f94441915c56d942c5037110a5678456b575b26aefd36975b5277a958cc2f425acafd650a663093e1077623d9ad3a4664f73c26191cb5def7edbbd4eb6701721a51f299088774e70a91301e70066de0f80983cb6ea865284e6bec2e65d68b271ef09fc7fa317962ba68266a9def9876b85865fbd4bdbcc75960b41fb091ccb8e98750b545de3786ea2bdd46dee9847df67272817a25760bad9ac75d08c816cc7e2110102a04edc67b2aa5d695cb616bfd31f9818775173b2f4ce3717dff84c486ba32d4ac8d7b370a86ee7d1207a661c5cc097897e7f71f2428d7bdfd5cf26ca57b4fb1ed52362c44c7cfaf57d643154f8cccb8124a4d974208dfafe70f0ec6cd1b3c003e361019a651de2180636b74c31004b700990841c3e72492c8d83e9786d763cf455ce6092272119ab98d7c7881253e68d77a1e6a3ce8aefa4652f5fb6de6e530b1a9b69e6f320805444e7c6885158828d2919e2fb14a5e99bf54501c548579129e5746eaf9f9f52e8c29fcc912f6e3482c004280e77eb5c4c04d99b4829fa8b3080772150f91556d815daf58068b78b4af1d78e6b9a8f19f55cb022eeef016d0c7df2038fda1b7c059ecc6b7d6514fa967e546218731481ebedd5c58d76a33018f16b8193fe6aabfc2634e3401fd85a5e3bbc2ac7b2c38bee2100749a13babb11ba999f4095c361caf4a0d7e9af829059e0c418e8865db30f957c674af04875664319f5479d5f2164cd73434dc44b6c4902c54a88d962b187c0a4026756307f6d644e2882b6afe25729ae3bbb23fd2d2e1830f22f34540409a20b4a11f344f9adc7bbb22fc2475536f5be2f640eca3d01132bf8930c856abeb40f323ddc8ad0081230908fae7718a3750c3f05be05be56aa8b05a4e05d8432c2d454dde0e82d267801371ab42b2d38f56c801cf580c60b2646cce9123e5d2cc558533aac8eeb3ba4a2d9a338a4c92314b52ff92e56a8e9459f004b20bc49aeffa612e1653b8366609167994c866644300f094718a4e92ecda647a34f1f274a0084b33f6a67eea0fba32bc6c6a1d0da56c85d45b0075bb6d9959b57df62a667f0e915909a086168baa9e8e7b221e70f2f70812461531d203929e2ef06a751357099ac460482ab07e052506ae67d7537c75433197c19a0cb4f685eeb1c67d9774fad1d532502610aee0aebdbbbaa5e6e5dcbc9ce0f65329d7ba51075e82804cc03b57aba04d7107a67373bef46e974f0e86eb771473238b2422997a9eda3184ac4e7d7772910e9cbb5c9661cdf20fe478786b492d86b8d18097dc236a70beb3cb0120c9b5b7425be8dd958861ff9083824a4ab0fd2ba1fd8bf464c7a61f4c254c9a6cbd9225f6b59c0362ebfee9878b2c1b89324004a57dd68ca0ea6ae6a33c8e5602c1182f5d602e203395ee60c186e38391c3be5459ac0322a083ab8f5f265021b6a0265060164aec1a484f8bf2d55f794aa0796386027b6433fe0b1c6b7b960be8ba4504ab0fe57e7a240c05f0e2fdc98f3038e7943e59484de333887290ed6daad0ff062dc261bb90a9dcd16450ae902715ac3f3379de529735bc20d903b7b85ad15eaeb74ff9c15b1f026a07b53d95f34238204a73493c9de1130e47b80a9ab1314fd13f701f1094cfee038eb9f9630f441aa0c9d55028a12bd8e2253e0634b510ac2031faa190e6aaa4af991ba40f942cc90bb938801d64f71e20a33e1f4a2f7fe899ccec1803e83f4489ae3bf3464ee1bd7ec37ef735eac1d07fce9bea11020e03643aaf8a4e13f7f7a307d577a44a5257ec1d12b337047285e7ce803b45d32840f8a571304bd31733ae74642af71e3bce1c7efcb42acad1c22b20727b8541a99b90f6e2b5a25485483e3261a08c1da1cf4913a4b5fbede2cc0ddbcdd2edbcecd667625cd6f14a539313defe3165ca6a088a7098cdb2ac8b0974489a0948b76973663ef85359ed65c8cc1015281fd7186c370b919a2be6351b19bc00766ddcdc87ec6db23aca7a3344640bf4ce44a2ee3c0478b30d3c6e0a59c71bc313598b31fda2d39e303f669ae57d9380367ea2b0d8664573b45c2455194598427d8c144d8dff15b40bb3d684401a8036867e0bc4a2a5e89a29736d1edc791f0083de1cfa03bf0a0e2cbfd84497cd40df1a64cabc840f00bd7e3f5f5a01a5d1d64a012544062f12f89f1a130325029a17e8dfb8c5a6451e97190f803d735765b1ab7072fce7cbfbc62c37a4dafb5a6e3e3844b2c2c186039b4c66a8e18858eb2a38f0a9a2f6f383a950e749a6da415a1eb10e83e0cc1af9efd5001daa3f859e05c32781def749982e18631858c1e845cf8de6c43f3f7128ea510d149e92d3088122cf9e897da8c46b6b1856278930558297b5e213d0510b731d25294457208ee3b98eaf224ff6fa242b3a338f9beb5031eb150d191d0c16fb7064e133c4125cbfa5fa9e10ed42018b4d49810579aad5a7d678613943b5c64dd54da1a9cc54016077159bb9cbcb81a2802aa1ef0eb5a05551c7360daf51f5bfc40919e2b7f2501b89a3a92f9b219ef53fc35b75040ab4ff6eb88a6b83baef6c11730438658d05318a81811a96ac324608bd23c07d92578281fe0938268f9f7c219423f4336883d528c511965e0e162f75045730d1507f0c4ed0624a4818e9943d53792dea0fdeae805957b4de45d511c0b630dad14d39e319389ae8fa807a450a2cfb0594a868236dff5c57d50394874ced3523327074fd105ba4d3d41fa1ca28989dfa73e4c42893503c37283aa7345fd3be29f35b5ddd2d5411a998684184650d2e976a4db40099ea53bad423fa5bfbd96e0459a57367cef5a3c26a70e6e00f2114e9944787f6e688ce46d5fac0b51b35c28eafa722a08fe618a7828663d430391c386e66ac92830418d8542e54e89d62cb5b9d38a33fd07225e233e4038266a4452559980a702a241e674e433770818940a77836eb2aa35df7447dee2e2a456ac82674065d7207eb50dffa23c549940da1b2bef74c196e5cd3d696a2fe6951d824e69386c0904b9755803ad3c0a9d71688b27edd12236e92d9f58291431600b5a96ec58a064811e76fc81c8bbcd71ad9b26fea607abd7cd44accb4cc9689a84216354df9ec9a255f3aef8f2047aa3d37df42bee02e043675056ccf5854f09697da6fba3d581cf4a10346fb4611a7a92b18a761ccd51bccc338b544de02dee76cd7a443c34f7d9bd7774172ad8e5271755cbe88d0fa6cf196de06d42a9f18fc086924856ae8c5072763001e4e21f6245bb9196fa23200e20ba106d544842a26c6c3ea9af7bd895cb611d8c92d3975d10c39bebd093089728bb0e48de6334132c37a83227e2e0cba7b2f6d2e2a5bdbe652942a11d181cea5c21b06d0208ab3e7aa356f625b498851588093e252b8884ace894e048be21870c9d24af21cea1155f26b27d60be8e5c12db8b6a59a2d8d4390691af42c2bec341d2afd081299b6d9fb73b2d3a976906124339ea950f4e9e41dff8e469263b27990ad9c247a6a70c720b07fc037a0a62ad642f562c48914ffbdb94ca6ad409cf795eba607a51156082d79267922c11881b9552d15ee30503d4c48673e1e8b98e1ad06adf6e28f1a4a70dbd44ab6f02d8e115fd1b442d725b8db08163f75b21eb4f103bcb96050eb5cedf7977bba3d35bdb3095f870c64985a6cd0f63df567610914b7b19ef625a6a7d919632092e23e2c64f6e4e2c9e24ef25b72124a47a6675b353762d34ebbc0c9381bf75628690250d3a4189b258a4efcb1750bb6f7a5c74626ea8b035e87a57fc757c4a90aee841606c2235972dfafec93f348a1f462bc8311f525b85639739f97d455739051a8769df433022016484cdc0445a2451017eebfdb2a8c197f485e72270755320b71cf10fa2277f7629e5f1b72db735168699890e210c558fb07a342fbae09b65f820d340a6472729679678d1e13982d3e603fa97bea183fe1339728fb7e6d9f21ee6a01a5c8b296dd8e6fe6d2bec3419a21df90196a8a9a5052ae594cfe156a06ae52bf0060569cb3380d4c71eb7c65bf83648d054d6c6cbbc6b50ca3e178aab633a4653746a868b1b9178008dcfb0344f18ab1d7e63205a03d525aef820c700641dbb3009e100eb386ade95834962d938901295785a4af3c95a2bf16840cca84f54642719b4b23463407922e3b05a302c3b5373e11e8d14a6c762a6a8d90c5d7a52d495f7fda1d6dcabafeef0f88cefc6fc51e8d96dc14d09549ca53728013ed058d88ba56e2dc9790cb4a256982b9a52d2a04dac22376e9422365a2bcf6b77f91244e92aecf58386ac4e823642fb31c91adbb79561fd6cee60a5c6a1130ceb62e6d5ad611ee6a169386da93458d00b64e22d4637308527af4ec82d7f1b4115c82ed32315e99ae409e39a0a16e8f3d9ee608f8bc7136eea94e86067e8f7cc49244948db8381e36b2c68c5898c18e6a5c3f529541c3963071deb19dc3aee04e213749e7a7145d770b66eabe54133302b502bf64f411c42c9c9f848bbb93b0f0849ade1b5c1d2cad3643d54328acc77bd0addf4c133d566ef829e58be06115e0efc18edd21bfcb0b12eb6390648ab7e4c1e08297a21fcb352c99405c89bd18d3b2f857237808c1128115af6ab7ec5e32af85e8162aa70a572f09a01003c121b6e96d4893dc1d5745dd3eff84b23a50a872ab51fcd48713676361c227ad4a31e41105e8579605a016526ed4f12f265d3653e5eb2027aa85124072cd8a0acd01401a48112aecb305c62c5be6fa97c535a78b95e46d44011b723b08fda34292b75286416a918c7163cc5c3bda8d4bf7370b8067f266e5569ca4b065b2c99851060e80b91f18762897748a17075816eff00de761d1c26f8e2d32fb0b294885a47164b2986c8f2a919de41d110987468f543e7052606b3280090d0736ae857facab96674eda3f13d2382ee279a792cc868253c0c90c84245ff8c4d468b72b6656066e5e2f2fd2f49ab32a0fba95b2197fa05a0070b686201fadbef8fb3ab8d4684498a32a5284cd752cef61c0f011969c451d36175fa420cb8e52dbce24d143fa6865e992970b63c0174b05d1460571105a22272898f0c089c52510c3891dfc40f68b8d881189f2b7613054444f3db1aa537b8b3a7b220eb7c750ebb19c19e08344fb8648c3a57accc9af689c9fa3878e1386119b81872c3ce9dd1e1dd22e618ab479c8768774d2c44bdd2eca43c7ea0046e6d3415632f51742a974c093f34a35d2046ccb170a6214c0c95e2328c120da8134488eca20c67f1ab28f5fde74852da2c2b8d0a247bee1356917bba66acedd6cf5a852045c87c96592044c86063f923c9cee41ae43696d8365f833bc17a0049803c2ac6d1e1533083377a21a2ffb81da5b356a4dcc8333297e3386fca65c90b365f503d484566d3e1d809be15d5e96cb0c38df8a863e5fcb541663090343561c4b3c977edf5a9fc579e6042ee75c2c60e0d495dd656d4c415c155fd6c4d868c448fca9dd3c9db4d64d4470ab9c54b1e53f8ceb9096ea04a8706fe94675911cb5e698a0b2df0f08cae791b53f7b6bdc78882dfdb28bc563aa9d690e245a54fe7cd6a7021b0795d22d67e3a7599d9d945950591318eaaed440cdac3a7e3850359358d6582d46fa4582c8ac6938e4ec0f6f21cb2e43be1179ad729163267c633203fdfd1e4200128cc8403ebc0b03a756f270548e88ea893dcdc23b25817b646a3ed0312ef497c37511b56dfc314fa8de95e90685a1e8ac2b2609694187d4664e711c2de6719c15b8ee64e53ee1fd366151dc6d4f728d12a9ca38325e5296dc828db4759610cf5d74abd0b24209abbc5be88f90ce450719ae2fe92c30e35e28d38c1f001405025cbfac6dfa44ccc7a1a8bd1c9714d8a748ec65e3ba60679c59051c7cc9e15e2e044aa5250a040faddcec45105a6ed0509c211fde6f746eafaafbf1f2db046b98160132d10af77fceeeea1282d32bd49ac3878e6023956ad4a9354e0104291a34a014ab919d708dcf24f18f715600a53294b2a271cf4d4f2c3969f30cc80562c859113d83638300bbc21b686e582501c158b482981bb148045729badc8179838f43b0dedc3e89e3839faf1d895215d240d0c4fb0371ec17d220ff7cf21f0e463042723e929a5b785987f51cf559445622eb59c40521f4b8d7d9fe0441f9cbb3228adfa6d5994be466d53e50a041283e204c1f900b782526b0edeb83c07f932659e4ff011b189c401d3e16a9de9809cbfc80f46cc60b08a22425976f003fcb2bf27581fb91f00e827d472104230e99630e1353220abeb3344b7659ca3882931a8cd715fc3b827d201af53596b8b0316e1243ee4e661067b5922706338ae231674880f7852bca167f473dc4df0bb7cffacb32cab4631cca535ab5c33fddf7d0eacd25690628074a63d8b13de6f95c378d00104304c27c95447c1ba68e51e1218be0125ca33b841cc0472cfd8c33118fe26e295814e65d3cf8fe284c7088ffda0568fe8507510f426e34439729eb5783827813229420104051bff2411e9973e3e383f92d6efc726ff16b3e2f859450994d361252439e3416c4856c8ef6d15b0cb652a02c05edd9993aa39fb7220f6c8534967e3f4abd3f803bc371b7cdaf4c9ed22ca5b67753bb6855a321e251b55e136afb5e8837453a738e25a5ae437bf19a25281c2b1dba55768e5d20ce96f6b9e4234d20a87969588156f61868e24e143b9326c199a73fb75629b29f93c0b9798f8a1160790a322778723aa24b83273ca8988141e2b26dad560ed59db88424366d9e8424fc04f8fca0caa44ce5d5e1841f30f1ce3840c454524fa48eab769da6d9e125aa7a5663e6a5cc7256e1f88ab09b321c202c52fd3cd6abf64e0151b3d2ce5095683c8867feedbe21c8462c419cf04982eb24ec379df46dfc6542067ab208e3c0d7926ef283d6437017d860cae421bb5841b114526f368dbbbfcdcd094189c670a105e56d52dc9c47e76ab697612ec2ad9082b1263d7dd0b56b206c00dee7aefc0a7610d091c06a34a93f323374467aac15526e0c6c007f47add3ca1c38c46b2eab23deeca15171e4fc2f73d2fa21f98ddd3659b5ea2a60453a48dcb4c497268bfd012dabd00330657bea37ac5e10770d26201e26c788146108c8ed6bc3b90f94266929f6f775f8c0fa6ab82d901332421a4274b81150a8b8728ad7c75ef421a1886bc8c5cfddf6440696b4a0442286fe6e1c7e84dcb107c5206b8614fa264e011bb6e572e4cdff6bd8544e7121d2484bb806124fc9543e5c8fd77f00084decf21a9d3bd13fa5a42455e74e6fe5c2bdc7d9ab61b4acea6dd885e31c51a6f9d0ad363d74c88d9f056570cd6f30ea70d7787551029ce1dde8fca9c1a8b1cc3c4e3a84d23f23103fc4201bedd68775b40ea522b4730b119ca786a571808ce60b69ccf5d89332247660ac9650d7dee483c1d13e5c40052c1d5a30c86b8b7939ca12c6d887627c6062e83640dcb4fd0593343502d26104691474b9caf8e00dd5e4544803ebc6d6bd01fdca27389483cf97b5b2ec2ea552657903a578144177687b3761a4a21e9fe89268160d0466a168058aca24d49e16997e5ff6edddd72851c8cc698004b739a130a42ddc0985c6513a1d4303f8e488f534086d223d2443b9c2861ecfe2543c3a1a059bf52180d53a5ab5bdacb9fcf8464f0bc38e6cdccf9c1f25a80de669ceffc260e2ffc22e225f4389a5de085ddbb888f7a1a17244b62c32474d04e545b05702f028d254f44817d0f47de0596baa6db4f995af179aa90d601dd571a1111f6afac42fca8bd93a303085f73d9335f90cd988796702e9c10f3ba4916eb80114041b45a0eaf6957c0efe2a2373c9bcb87d1f9935827299f85fc6843782c4999941ccaa08b70bd3bc939d49fa306d406acf753911ab3e1ae3057a984b19e807f8e261e0afd9f6e7b8527fe8d30c19d0dd85a4fb0f8537620805e2f3ee4c61dea3f6cd58f52f99b2e28e3d8cea610c90a019adb847bfad4f6e933bdc3769cfec627d8e60c8e415b22d1500daacbbc8b007c41600e6f5b2e648cbaad9073589185c42b76dd739cd420198380c856d00305700e30cb10db99c17afe5e452c03dfe27e7cab2fe0f0ae5cedf11a663c69b486325503d631f21a720f4f25ed7c00b0f28179cb8361fcaebd12680b32a29cc191b0b4274b5815a711deeba0e0697ec1074684d87edce403871ae5a0e9df1013711a1bbce6910917901cf2b3d674e8427f477a9f446028878d026da5d812d78ce851951455aea96bbc581e18b69524ea4ae391da385c22b74d8d4eaf16c569365482907fa9388e5b2550e46ab17e8b6794f9b0978c56d51d77f27885294ccdb7da0f0aae27463e38552c9226a3344d5a0d4dde860aadfd38e4a54423c8a467e7c90953e96ce7de969cb32fdba4b251aeb0fc39f5012d50076ac527db2a9a5a804308e9f94071a06652573d0930d08d5fba0fc597ab6f4133a0e92461c0601186b27e300d4bf4897deda915e85edae1035ff8127e210540e2d11d5d6e1a66d1cef6a95b17281ac71bbc3d45b227bd1ea5515dfdcf43c61d36cb42ec686177ebb61129230c9a49814d35e04530808850dc5be1f39501d7c31551c0c98f2db5affe2ce07b84634a99c5f00f33d4f6f47db05802276fefa8d80625523fce3be2ddc6eb2c207dc1e0580c54bc475c952dc7d4f45fd6255ec878e3825055464c5d5f542bb1bd46db9a99aecba7f4bfa289375f74e4e41e6167c4ee04b4ba6c9d3b0b50bb6abae4311044fe88a5514a7334e8f1b94bf18ce0abcf1eda78ba7669bdb9b1ea1690629c164cd0b3f6b1c9faf4726df5c9a7f8ab7e2b46ce1e264e4bee05a0d6c76c4979d328c97262d7df052e06eddc373f48eb3c94e3ea7d5f55cb4c6794ec5762acdfb6d3c981893335b147b18783f1fa2d0728a6cbac0a55b15eb189d50eab223e0ba3ffec60f0ea296483cdf8f03dad3eb1ef28d69bd14d939d02ce9d36e324f8d396be870b13b349644f1c05e9affebd40aa656a18d17b8359697dc4c79c432b2b862261b743701551ed88c5a37f91bb82e5494bc2d5aae14fb6186085dd206a0d1ed3ef6a0c7956dabd7006d293b5f2097b72611458d956022a163170c34196cb253985586332c91cb10933289db00269a9606b71d4f5cb2be3de048779b83a6d32c0c0b11d202d25066a285215d59d92c90e40d0f19448fa50a143a3f006a28c89931aeaa637bb43c6524b2b854e92d0fd51f3915c5dc41ff5c84b777615ceacec8a8d6bfd9416698714dc4824a3dce93db181637f34faccf3464e91275e045fdfc23f17d8ca845d3a605f33684afe2bdfe62cd0a8f98e84a3fe3e1b067695f6a042e19753af1a89c1b8e2866b87be775fb87c5b5a1191c3b04de32f4f23ec33730058e259eca350c3e45c5186f8a23641bf08d65c2106f3a2c2d926f99f7326cef97aa29d43dc70036a28d44ec7e74fb2c41c498f041acf52546ccb4b67d7403a430c9b07b399ff314c106ff4a90629fa4bc59612f1e2e837e0fe465f2ef61cb75751df5dbf812640c945f953e862f5c44a167f6c287e0b4f5e87c0b2482a57ab141ac9dc925bb96e1032a4559b96ea217a904c6f71c57c5867b77c65dc898db2d4980a3fb10bfc515aca7728bbde27900f819985fe34476c141da5e28781e5ca07da77e0fe40521fbb658b2dc0be222c59bac7cc6ab669845c01e3818979f1d5d58871f6bd9af6c0338bb6f256131549a7bdf0888047624ae948159f78604526cefe093b98a4db4de044f580511bebdb89579dbe49f88a55e1137a1ae680adfea2b7a5941743df7159f89f64c926bfc38912bd00494c4f9e9fbbb64af38dc260efa0529199dd0748774944f9a4cde52a61325e4a76565f11293aaeb0fcd78dd0a85fcc5bc649914183bb77d2d7812cc33c0444fd598a49cc7393d55b211934c1b775301d360a6177770932e2cce529a33490cb0874d83743830f899493a1add68edc654de0ca0326cdb0e92457f84471588b6ac07af09128aa300ddd02793007e0e1b2cfc5b041403a104e38ea327809811deb89a087a15ec8f28303ad6a837d7684df5ce9a0c99d88e5bcb0a4b076a92eaff44453e9821e2479c0d1ea44d1a4e6aca78991ce1db06276a86958c7d74875e082de81ec64212dca90df24794a2086b17353ef40f442ad1779d117a6c97c01e3542e32f0edc2864c6874f886afed1c19953e167b90a5f99bba7b5ef9817b641ea2f17e5726a6c4a588704fb48eafebe6590f2da24c5659bc9789df17bd99402316ed35b389cb64f26fb4b99c9ee6612aaa09d7d2c9a17795411076e35910707e0578dbe47ac086a803c123523ab0d61cb48a5c6f9219bf2496abf4f259662341ba72ce84eb955d670d55a6c583eae5ef947578112e23cf125e12b5e4d9ff62454faa27d8efdc93ac33bfbe8dc9a7a0da102e2fec79fdc53ee918dae56a9706506b5605f6e2a695bf6ef7989d3de94cd9fcb54c9597dfdbcbd652596bd9383146994f0dc685e1943ff89fbf2e84b36caaa42a56337ce3366aef26969e9c4d57859dc6f776af984feb503055532479a3c5965ea5f98a871a14f8b434155de025815fce907af4990ebf8a7947f012b6b5bf9f26ab8522bc4512e940a93ee405a21b3fe426f0ecddea96dabd2a24e5724c29eb5d8a6778f3e28008f08f9f30c995e2df14121599faec216b2ea8186a6e590046ee929519ff715a044dada1dcaa11e0d64cfdbde1ee7043f9ef0e31155e5f9943da319ece37ec4ea8f5a6aa257df035ae571ccb846ab7c3da5cc83db384df0db2c52c42872ad01cf8a341cde0a85fe84df1e2447940012b0aaaba045fba7f7b43f1ba6f991c47f2dbad4efc4aad980086871db16527d3527f999c40c03a4201c7501121917b19ce419f84e4464083eca618007626e07938ada6a54121cb5ce8e6a7c4495db2fa13636ae84ea0ffc66cd674c56a921a8157e9880e8a112b8e2463931b0b6fd8bcb7c4ce5c41630ea60661346a1a4f5b77ba7c5a78ba216f2658f366917a29938e3f912f6f19d52f83cac79d5c08c6e11781635ec2e10966d34bc091c6a28eb98a30aa1aeec99e7da40a98fc00d7dd23193c5e0d1b38437291563442ac43a4797b1be5e06493da831f59ff81cb643b88ae5c2a543f1f38a1e16be90130d67e81668456b5011f41070fc476c31cd02e1259c6617576e06cd9a4ca27a3331b885dbc6a60106694646583a1a872ab51b2d4b1c1e34b4e712c5420692651e1f523d34643867e4be56059b238bdcd20e21c0871d67af2554bb1eb50fa9805bb4e8c27abc2c48c6a4ba12204c45d394d0616abfd291c94682dcea560daa8347c1802b2c47e884b5fc6c1638f68b76a3e1dfb2b2ec5ba4ab319289d1ad92bbd2f48baf8ef609b8398659857c31128a4819c5aa66f448064e75ced79084f72f0e6cf1fedfc0d9b230e2130e6af7ea6e7f20aaa61091ea7bdc4d13ff9d07a94f0fa86e8b2c12af7b772dd182b25bb26f409d6d8ffcc34d3533e035c0ddcd649fe2e96d56c1db1a028de26e1aedc384833ddb7ce6e8e6acacf8b081d436a68cf6be761d31b11550e10d57cd9346299eba58c7393f82179d40619d9017c32749bc6169a6be2c96679c9df0a3dfe7b0b860ce749f80ba094a72824a84309e99fae29b11562114b13235ed32fb9f4ef189ec5406e26ae08a8be363eb464d3aee5b25428a887ce36f014230213cd8799dadc63b81dfc214e6edd830c425d427d393662c09976746b2981c265d0a16a84ddc28c4ba3b7b308ed440e39ccd29b92db94833fe4bacb3cf8f124fcb56defa4e8ec0689b255abe8685b424b11c57b52887ef8a75ad312bd399d640fbcf18a6b2b734cb30137a0497beada06c0620cda06729060ab15c90d600fa0243a33e418f5c77f1e9b73cbd73798463cdac7399b27bfeb1ea9347b979dde900e3a1d0bde76e6ff58bd0ee093b41bee6b9f3cbe478693d8568db14699622d9a52d395435b6da18d95bc520dd154d4f6cbc3c2934bbff7efa5152c41009f45962b88a9d6a93cb4271f94826ca03bc7fb5e11d6653cb20b3fed44de84606657356f0f89a8b2befbf4e20b34f2581006c07ce0a75bb794e07ca99638a390c897b33a82678feeb88af5eb7409ad1accc594d4f91b315456e217e9d9c54c8a385c6baa22df614da9de1c7c1535fc48a70c47263d5bc388fec29e4b51b3805dc2a09c031c51884be2afba06386ef4130fd9fcfa88bd8d86e5008bfb8ea2d4a26a2559041f988465dfe14d020e04a123223ba232f85760d2308eebf04346b411119b6e64124a64ddc0645cd8acd6b99d70c09587b2d445873256ac92ca3eb5434d8eb8fd0bfefff72919f9aea6b869b9ef9f16b8a991c5b60af64667df7976eeedf0fb57d1abf4cbed64cfbfe3c2f78c1549c909349fd83b9b06d19b46429040d2ffcfd260c559716adcdf7b9c4c2fa777e4e0b18484c417e611e482eca2b32349e7a4092e037a84ba1b2557f5d29515ecfe8036386aa4c0c5048c5c7b9d36d79598b98441a8cbcab320a3286cfbe7707f80fd6783a25294a43151054d387df22aa89a4a9c075b90fea908750e3503450dd123fc69a4fc1a5c4a880e9bface41267c88cff61a0e3dbace4126728e31f3b04747978b4f01f0cdd978b6bf489f1ec58fce26f497c97c9436b25c2108a2c4da12cfe4634c7d60a8ca1165b244de5f1aba902e8af5249a9086fcf4d1eca2f27d5300e12c9083ad7b62029d237090d0994e8dc1098726b80cb72a8fb114b8cfbac298380d63a41ef9e40356262f3df97cd0337bed56a2e71868cf7ca98f0bed4fe4aea065bf2734dfea85a9eba435c20465c638f1114bdd00bac93d543f929db09a24bb954a86fab7260451b69659f04025009f8b022d220ea07b09e24a37d14f616a9a4f12a1c5049c2fb5cfccf4f212cb6491c41c4859b010127c0380a165b0308df13421ac4f200230c7c04d7050ea830a1287597eee14ee8bd59d95a2f0f97efad560c6cd2a26661efcdf9a1e28f8a5894a07e92608821c7e7496f05ca14e06c7c843261b23c732314e20e79f98842ed3bded9b419ed381bf8c4b976f3842c48fe75b6142ba8c5c0fce33d3b428b18660b1ff828af4cc288605d7986e68d15134fe85eeabc4c3d09be579e0eed24beef8f8602168fe35f53d104f13558758cc17782425ccec7f8e7e9dec73210274ff4b7a888c348fdc5e107e4bf415b70f1e54d3e6d4e1c0dc8bb6f6dc6923ef3c40e2efd694e46bd75899a7e42873cb04b4c7cffffa9d090f3c9d7b118fdf3f61df569dd93ec4cd00238bed1855278ea154acfbd96f8776803399e4d1b1088c6a7d023500e58fceab1261fc34665effa240046bca4e77d9116445e161b2b8e007a9ed1455d5e51e7fc0c35496ccf1575d2587e837b3622f3a7c7f0ff674aa3e5c54d969bb3253a03f1c33a9b0b3b95f6a8c02559810ac394c4b0dc5f1dcdc5c9a7b2b5f3e57b432258e5b50d4f4c465676b3e7a151b8119fbfdf76b3068830d027b1b530bcb247b2f8f4a206e14bfb54a9c3e28b992a3c50c419f245723975c39f5011bf460965317bdacfa28966a3c4ace9e1f3820782114a7b5e9ea202e56fc55685cbb7acce3960081ac7c35414989a18b148bdadfca98f8b1f986a3c95675c549a17d5cc2252c0324444629334aa05e80f5c4ee641ebf95b814eb1460c500c34682d4ad279c1cec1f80fc5f94e91a45824729557519049ec21cf9c014fcfada74a843b637b38bfbd05a16b221af969f10724eea4844c6c246d33c089c3570905c99b9e7e93299ccaae4ccb9e53a94e238c813e20903acac47a6a097e36d67b18a5be85212c446d2edfe2662d80490c63c0f070cdaa752ea0b3285015fb245e04be38146f2720f506e3b0419f37a213490fd325b9b1fe81d2c7edb9e076ded7f6243415ecb10bd9d5fe5dfd3c956feb6b74b9e1f5e8802b0a08ac012a1ed241a72bec9b8ae2f6298b592adcc002c11436f2c56bc102985a07988859706b90ca5b6342511f8fcb296ec54e1907c8ff3b6b8c1981e39902ea520d9b9aa07edc428f13a3380ca7afa609fdae2b4a14dc760a49a68c2bfc965d156dd65727dad6826d72de8eeb486884296af63ec0925150fbe0cdc95e2162e5218c057a8fa5408c12313f66e292593f7cfff7866e7ead98bd2909eb2364de462be74b6c830bff4021c3f80aea93eb00438d033f969d5aba51432b13023e188a98fb2a519911c15f0e192ecad3882f8bc06217e3957d191b0e1b4eb578b9ae4453dbc7001d95c4244dc249e4ee5dde30ab2e1a0abf4492bf0868f1eaa8426aedc52902fdd9252d576a88a0fbed37efe23d3f23735ff61fcb9dbf3956efa443af262d8abafd5032780fed2fbfbd6c9eea8dd470d9ddf1e7b3bf6bbe0a646dbb7342d264a5332e05e56d61db969b12fed1431cb50afc68cad171485cec25c4cd84116c3cfc3ba7f9752e3f9850fb86d0333a16c54ce8a8ae2d4d08daac026787fedf473f431c2f3f476b83759b5c54141970416cb9728a9283387db216e390b3427c26e25351c13304f41adee1bf7d7f9a5b8d99ab27dacd113acc19d411000b88c50c61fd94283b80ed67cdf2df249366451d8afc7987c4b611c75a1c43ceca1dd49f3c6f37904a582d3c8cf03be63843df5297ba4c79cc38d0136da4bc427cec54f300838b841b8ead5fc07403276b34d1a31cc481558fd2a0a201d30572eac84c2916070bd8a85478fa86d4d08e8e2ec94b16d7c8023c013d2e576f3c3b1a685151528bb8d11080da02c0372d15cfbbc65459bcc20f59a093c96d823cfa93ec5d3320f61ddcd9fe8b9f3bb1ef2239caca74dc3c1bdd4e7655108e9b9a5ec6398ca81e80abeb354644068ea99a43004c73857236621f27e7000e4e545f0815103ffbf9654c7d90a69e1f437bfa4c6682493dde7a5ebf793c94a5a1ef896dc647e3d842c75f482f717abbd71fcb13b90f32569534d8f282f3a12d32d70bf6828c9b4f836e2158524e52c49ec3b6324b61a4db12c2dae8cab14fa73ce37997d07fbc28f55e23a675fc436882b92dd8e7226ed512d42fe8ad912e19f1004ff484d1209d3dc013dfc6b0466c35ef7df98e84f4d760292cf40525155e75eb6d72610afd30e618169c2671ec698d036d6a49d38aa3293854f638fb0af6f08aa2df5350fe14cf9396aa4917fce0207f38a840592d3ee8899527032ba1d147452f5879cfb0652601eccf8fa212f57bd8572e9cc04548e3e78f9feba71b259259302842a0fe28343cf927afb82fbaa1db7fad65d3e09b16c1ce8f62443dabb7b8fe8e5fbddf53e673a6e09eaa9460c97586bd1b4630fb42d3172624e3c0fe47019c8bc26cd03fdbb867c688b60e87b820b4eac6427a70ae8551cf2115451f886002a6877844cc101cb5867cfeb25787c1be75ef0da87f1109851de3d7749448f9d233b70b079a9d956693ba1539dd884907a52bbf6c18af0855d8cfa555a7df1eda1251932a31ef42595f6148db63000e5900c7cfc504dbdd95bbc280a286993b156ee66197b0aa2ddbb665188d3877b0be2b6c7dc391ec3962518da3c404b68e4e993a421288862564aaedb335a4e0d4de238b0d0d021d66a9fa516328d41618a4f41aa406148f59735dd7a42890f61ef5fbab91c1ae217dda01ad441510989cfc7adc6b4915453a74357123bc7c35b5d182253840192ced6c711c8c15e7b56a840e5d40c468ab4114473b38c1d1aadc9a4802b1f81837e3440e5672decabc19a17df22135317f9f8c09e3f6a4ff0a921028a643697cddba4d748211e8c96592c38916062379bd7b5322e495dc762400a8a8d818d7a2bbfb52aead4e3535a7760a55998aae4421cfd5497c62032ff545c5aa8d8aba612985f406ebdd8a31543e05ce8a8e630e4057a03186e5b2a89017f4eabd4f5e848aa83c312eae838c962a1e7e0434d9bc43e1708930f2a5686505a8ea0ff56ec3d87309782bf507cc57025a0bf66d5e30d0399b36fbc90598330a2813eda17b2cd61aedeb3f85387031d39af7b59f5d80732cd449536f24fbfb11f2389c7870904af58123461707880200203410e4604827b3e230b3d865be46f17ac0d028f2d4b977ab43ac9125462bbbbbbb779e07e1066906403748b202e6b886ba349473ce46d52673ce19a8d5ca25b888720a504495199884f0b090e080e5a6438f0eba70214aa18a0abb1f2596d0d50084d5c383188f196c489204103a254a14d1c1698ba089aa2b2a5d50a071c4eac895160e20a003054c7634e520c921861b1841e281e24ac81e81f4fadacffcdfe8995c88a21821830b3e5926247154431116d208227a00b19b1284d00e48b8b01b7a0f78eca9671d3f1246961bef041884aec0b024e4065725e9f8869dc739e71c0413b5c927a9a90b23881eb86327c82804211e947e24211464c7952147824c189a3a0c501ce71bf5eae64a81c40ec6187ff8c3b320164259b48f910f928f92cf924f13a8d47375c601cbe9e6c4e324e484a3a1b5d65ad3d859425103151d3e544574d06075c4902074c4f4d01fa0911232be58e3aa4dea9d1cbd63a726e79c791c519bbce191a479932a792469017ded67be7e8d1da236495474f43b46a6121a39b8f028a9728a322247dd41d11bb29aa0216946124ba87c48e2724281cf788c735b0b3ea67e100114c5c34d2ee2ee762acca81c989002d0f7e5686a01551f8acc9b1684f60f5e8cee47161d49b87a75a8728d9c1dac33cedfcd39e71894aa4d6aecc3673cc67905ba596dd9b163484806266e30114373891b4f2bc7508dc6f5c118635eb5496d430e893d9210494108a8188690210909fc44eeea972784b0d1a27aba375aad6d948b08b55a32871f285d74f8c03aa2a367a3e9efbdf7eec0519bcc69237eb30369f6540248b1f3b59ff91f40902e3abe0f4915a21c2d3da0f840e2260be1020ad9051bf4b08801a8060da52251928eac283159f5a04a461688c20061e9a173c2b2854a95254335f54ad3d557aebc2411ffdacf7c8fccd945e188ba5a5243851a5c7835828435900049d119e79b73ce50d526f5073e3defb45a39e7dcf338becf06c38ec49a56b7dbedc6944b2c2849ac09001c7f33ce39e79cab9e7ace97e39c735e8107861250b86c71f2c4d2122a4bfc008d94d0035df293fb98a7a5a7a627a83b93cdf8e1331ee31c07f5fcbcbdc105251e8355695dd1d9d139e79c7394a228475192a22489c2846b3ff33264e35e6b5bf2daa08131c6187fbd213476ec7c4dc642b728618cf18d0e7812b013f47d3f5f63b58810602203103f82340d614310447646176b8c31c6988810111c911c11232248449472cdccf5d15a6baded10143c84c4bdf7de9c546e2ad7cb55e5ae72597ae8c9074bcd71257b29c88255c291a826865801081b9830a247c617638c7196aa2fcbd5f7618cb18d5eadce10a23689cbcd309acd40fa5a8e50ee76bbe5d0425d2e19e5845268b5b0803c781c717908f1c0f1c8f130e28144021a563c1c783edc2069ea080e2f477ec86102e8410e273c61850769e787293a6868031ebe1a3b3b33fcd372408d1b4d9e7041a28cf8210987440e638cb1134cd4269fa4a63e184ef4be8fcb50c77807064518cd70840db8d4b0e506fbd003cbc118e30fd7087d1f0282e88a9f7cb176d292e46786291c4b64d91dc1a2e3fb14c063a733cedfcd3967aa6a931a5cc2e9169104908d5b0a999cda9cf7de7bbb0c519b242a3ab2bd4b92b54ce8fb32c5680ad214a5294b539ab48e7befbd3748ee04fdf0af28aa28ab282ca9dbfd1d3ee331cea54839a1621e0f5a28826aa714839193a01e9298d08465e2566d52efa31c1b2bc823a0824ce1312a62091e358878e8d85ae15600e2003b4534210429080a90149223aa72195f3c75f4a84deaa8d23aaef86c2be560a92f40559bd4435a723044647d5aad0a54936f909e9df37d2ff09cefcbc9dffdbeeffbbeeffbbecfc80e1be1612354d888153682855fb8615cc3232643079523965878c2470a5751476adc80ca2e280421c4d4a39b4149141884243185c87dd045880d4f3b90f0d8a107a585a7b9e4a7b5424ad44e091f4ea258411d55ad40afaca84df62b2ccdb172a8903c36885804fc1080c8f50100c6f8678510942055c448cf951c946e3094a31e4477fac0436d52e8c2f00177efa75320b18331c6180705d55c104aef42a9820185cf7428328431c6180785bd1e3a2e5e5f7cad6eb7dbcebdf7de7befbd513c514251b8a85c9451145294524dcfd9050e3a564ec0eaa18a0eba275156e10d514f04e1331ee39c85272d310861ca3144e5498a889754165c1056bcdbed5682b4eaf2869e9d55b7edb45a992a0917ae70e1e1049f1cb8e858219dab73ce5988276a9352533d184254fd68ad754bb7b44f2be71ca4e4de58bad17403eac6ee066fbb901383815c09619d0d82080af2ca2e00d1f9dacffccb84f47e58803288a4a8b440044815131d27dd901f2119638c31c63a45d4268f92927c373a4cdf877b723c4ca8831f3dec19070f1240010b152522a92641418cec0842e473f30307e486d60141010768435eb9f5d07060460e271e5c8448c20307255c9aa099052d4047434f922b2780e434f94c2c4520b54043951b98ecdc60271deb00a5d89139e8fb58cc9005dedd3c91c659ce39efba5c1a82eb39ef3ad852ea81e14410ba9da919a0704d8e5465242b20d5e49c3392aa8be48aafe0a187881454d8098a729399e8604a52365af86b3ff3af8444815eb05f83a5c87e244e09c9d6b1ca4076165433b758fdccd77ee6ff01365c209e562be79c6fe4661e2043cf52104c6c6c61bac14c2edce0a95b76ffba24204287179c78a2c7101a6ef212a03ce387cf788c7313acd0077456d728a35b97ebf5aa4fc54aab38ce3aa5e2b8ebc3a054e49be71d4e69e37e7981c1606062b1100794f8da4a63668c768d32fa8be3bc40beeb14075f0cd0e79c94ce0cb394d2b4a4998fb3fe5a20df3c74d9cae9e42e3d03c0032867460ad5e983548738f86d1883f6fa77cfc47aadda287e2975e812c4f3d6676869a6d66271eec429456bf46bd5664e511b7dba83fc803e9d559dfee8a2d3d9d35192867ea54d284953a3e8bce70d7e9682bf7439a1a48d7eeef26794b4c9c0fc7162995de14274fa52ce299dfef7b1d669cf80a4a10f83d6287c3e98779de27aeb6aa35fbec45dfeec13b9ae36fae19516650864566d577cd6b0b57a4bf94277f9e0b7adbda3ed35873855c58a262eb3fa0a6c1f271414082fddd22614add1bf527cfe38a1baa52d6ab5745a0af5f1a04c1461441320a434d93581fa195e1fbf26072813c0679f7d2ec941a5cbb70ee8b6a9db97da3ea72d00d0edd7d01672091226baedf65d4c2003f28594bb136ab7b485d8e94b143aa547af65c517e3185ef243c77726adb0f86c77bbd43189a14acd5c48268a2f9637909a3009b2e3315dea980cd11798087d8d098fa42d2653927c83906ebbd43129fde851823e38317d89d19a8c50af32970682931b59ddfe8cb6a09dfe8a24b9d30f6103d40605d2ed769b714375508e14d0e9e842c7e674d841c72574bbdd6e3743a40eca923ecaa74e6fae1802103d31841faec4107ec0a235cea53a9d353aed210041158a2076203c7f83692ba88d4edacc4adecc88b7c87ab3de8a33d369fee6c5a87ab59ae9f3c78bd5675ae3afb56a53ab30aa4d5daa9826ab8dde7c52f8dd5d5ca73f5f76e02e388c24bdfe0432c0d461960cf5fa16943214b0ddbeede30cbaa54949336f94eea958973c0663679d23d439a7a58dd6ced9edb6e27729a5fd499bdfeb4f1673f6fa521ce7ecb58212ffc839e3aff6ebbd3bb0e49c33fe6abf5e8c69f256ca02285f5c1b3342acdb4b6bad2fd2463f266d6aaa8d3e1d57f41a5aa33f7ba8b6734cd1e5377ea0d77fbca57c71b1ad745e6a55a8a999616e4998e95a3ef1faf847f82705065ebbd4e9a4faf842fc76a9d341a92c70da6b9208dd0d06e815003aa55eebcf6ac5b32e75ba5cdf5dea74434f6c4b70a9a16308914ecbcdd491bc49803ea7b479190069c33be813095b8c69e2cc0c13a8d01e8d4bc398f6347e106286b6fa7a50632a480c5a80e80194a344c4a774831b9fc2eee317f5b1d6278d3eba70d2421fe5d20cdd933eca9d9cea3574e2a3d78fcd6408f96124078a0d1d4d96b8a91f42b5915568d8e0e1874f4e0c4a6e6acd4e4ce8af2e754e74728f71f6c2f6399f4f3de78e1a28113d6188ab217afaac832fd9a2fee32217881c3234cb932732708b838eb7bad43db1413e3962adac5b6badad369e08a938f8ab4bdd131e98f6a484a018c5f1840655c2b7962bce89a0e232283a3a2a4f45a615bebbd43df998591cec52f784a34c4f3f7dea9e6edd5a6b676c466b5a382c89a90487994284a7d055225ccf40a1f0d88f1f294c273900f5c05174a973d24283f0982e754e55a8140fbbd43949f531d69d94f45a61d09cbbd439c5d0c12e754e387c308055ca179af9596bed6b1ab3d65a7b6fe8746b97605680ac0c5921b252f4f33ae39c73ce5aaaa84d5e5d185ab22e0c26747e1fc939add6117e216708c6187360020da0bab40588b733e531c6f887476df2874aff58f11facd9833d3c5e97371451c5a382badd6e4152ea76a0a8c09e1b357893a2a59aab6fe30c638cb18f2a1f573eb28a748a808a868a888a8aa82de7a50e837e3c6cadb5d61e10b981f669b586805008499d0a581ed0110109c9d1094961deac3ab6818e2f89e7938166f7de7b6900519ba431a46910f1d94eee927649764c1ff4960c1d11eb81a887a21e8e643f3d72c9d012a22118293d295552ae3e6729f1d8134c96cf788cf37bc3b6b3c294ed7a0cd1e307158e1fbabc80e4cab68050e4b09443530e503a35a210dd2b07638c3ffce12430e7bb1f0ca0929d03e46425cd63e23df1a47853bc1e04ae74eccc1c51603934df0d3f08ed5ca901882c2e4844386d2e069081e484548e23237c74663c8184d3015365e9581e9e1adcc4d324d48483d1949b7dce79c7c8e79cf3932b6a934fb2b40e3a5c07a0d9fb04a5a0ebe9aa787ec8f8628c31bea1065ec7584609af2a11a2875e21559bd44db81e03ec5478d25962d4a387129c50a2b365033c9484b050020d3a94d882c3b6b003e27373aa4e0ea8079b6412b404ee96038f9eae89197a605d2c4b3270408121ca094582f458016b07971582a414205c624713346e44546cc0040d296ce14036f57a8e0839f11375440b96203db5c850bd29426221a348871d26e8c822b4044b1622443b72f4421c4330c6d869ea73ea7d4e55338cf18e11b3044590b8da2129ca67882b1b94248829e829482a686abb90132364b1300e57eaee680b5811add167d16554a09a59758ad5e9a68a8935f78ab6f39c431c9dbf8f52985dc5db67e28692a5f826215a1b0af7c6e1c79aabb8d7110fdfc57ad4467f86382ddae5650db674d28b4629dd81348da479aecfd71fcccbae22d85d62ac2a6675859525045b3282e1aa4d092957043bd2b1adc5182fd68b55c5ac6257314338180e86043382e160447d84e17697312a98d9070260b7e616677f89b3871314658fed2a861ae9d6d4d46affb3d9a58d3aa906df30cfe55d2aab194fd2ecbd779524539dd6546059f17a555662ccbad6bd7cc156cfd22a8d06ba5aef0a71f6bbc218d5ca04352c065018490846eaad97214e6b05109807e98ed91f13e2749738fb8a5dc5579f899b576dbb5c91d1fd6abf1dc882058bcd79a7d1429c16c62fe5e31ffc3efe76155ffa1581a87850dc39153bcb85debbd6c76ff77d9aeadb7d3bcdd346fa4823e924ad7479ba8a4e72b1aed5e5adc0f6f1f254883290fe8738df7f18c37615cc2e1dbfb8ab15be228c31d33d9d9b6539dcd230da0b6da4bd9e8b16be6895b65bb46dc4a3d3c77b74739266fe67748b6e8feba3cfcfb451c642a72f23d4e7ab1065b581b61d8030068aae691a09576d3413b5dd1f5fffc8c1b7b40f87792e16be5d2af1f23eaaefe3a936b78a6aab6af35955db77a32d3e1fbe1cd9e29b92b57bd5e9d78fa7d36f67c5c5d9c30178c045d96d1dec0e429cfbb9c7001e6c2dee5cd111add1dfe246a2359a446df4a5eaad8ab3ce5485de2aa6257e3ba86a73a14088e959450efcdf4ec50e775b5abb429c664a9af957040243bb52d466566d1eec2aca74f0835d4515ad0b455bc468ad66573145bf221098b7345d6df3c103ec2aca3a98336eb5421c1811add1ef7b8c1f0be1f8fe2792809160b92fc4c14360d1165f4bbe909dfe072a605731d641172fc4b9bdd79669c2e5c3a0611843f78ff6ed240d7d992618aedabcbc60e91be799c184aa68d555a6559d3e7ebbaa03020a277c84e1f08f301c52c7b4512b55db7c185156db7897fa7cd006f6128ba9c2ab15598869b2dcfd6f275fec145757b21c8a1084988e6929ac2eeff6a80d0cfe70d5463f3765d443aa52ba5afa9a7a1f94ae92e56ed587ab36fb68277db99de4c36da60f693f7d38a9a54ea79a76ef83fa7ab96f476b29b691cedf948a1027d3be5dd6faeed6072260bbc4d9ad287badc265424c83e1b410b9f5f4bfdd7f53d406c6681f511b598eca72497cdc49669b69d3c6fdd4b4a52e6ddc53b2ddee7dbd4e1f94e5aa4dac8adae8c7ae8c62595c470948961b9235c51041f598a24e7f94ed98fa082b8201200441d6312d462573057fe0535987116144d4469f8310e7a3c1703bf0eff1e3bf1da4714933bfac19b0abe8ea570422f356e6af14b541f1f46544594e85a84151e7aa8d2ea236fa4b572e6a2e4f5fde2ca9d3239debe3d34f215eab6aa30f235eac6aa33f13b74cdc54d5e6f228d5869234f4b5b871b446df256e225aa39fc59da335faa1b88b9cf876148bb6d83bc87aab575f35eb63bdd57abb19f571d6e9a310bf5db5d18f113f5eb5d18f891f55b5d167118230ebbb2502e1f91b98d79baab7f6d3df5757be1ddd08d9800f373fa02f5f7c4f5f56446d609ebe2c07237eb410e7d2be1dee6d2ada423ffd5d455b804f7f5bd116f7e96b24cd932fead3d749d4661f511bfda9a95eafaa8a5e5dd12c9ad5e973108210d36d4e6ce5d7a2cdf5f9a0f85cd2cccfe2165b1fee05e6c36d028438fa0910c6b0fd7b018431623a48db46b4465f450e3e7ebb4e2f8eaad30709108a40fbf758ecdffd962825cdd8c2f579eb230cd7e97fe215b528ca72e3d7e753b04f59aed3071bb0abf839605751830fd855cc1d8b56efbea57cb182bf5cb07083ad2f85cef85a1a0f2250e97cf9a0e6000848800256b85a5dc527e385b2200782f415eef8fcf972f1f0f9f4a7fc1a23ec52d2485a6b13d789cf9fa135f9b546f1f955be94d2865b461e6e994d2b9db8758d327aad342969e4c3a0b40e593b27c5593bec90b5ce92b3d08b679d2f37ab56cd829c7376bdadcd7b089ec2a9fe5a1c9c2e586a2dfd682d8d31801ab1104e49c04896d6eee5b11027f770bead706a91248dd419efcfda7bf1bd17e3efcb3bcf40adb30ea7b45136f53975cee195f64d4941ed423b40daeda22e853166b792663ef8ed6fca39c73df7ae1bb4a0c64fdf8ad4524b69b4525a69a5b4660d6a3dc6ec1e49905616dc3b0c37155da074bd5e9c8a2ff28e327a18baf63ee2a394eaf3c11068ed8952dd026b3597122a64c24707f4a9f7ebb579af7ff951f74f04dc796881dca54b97acdea2018179575883d9cd10fd148714d0ff7af0e5f5126f7f79e997d1c110873fd5ff22ce1084dbf5bf6e07ad7e4b6d60afb97e7912669d87382fb4eff9e6a27ec1fbe52582a09f3fc8451026eac0dec6dfc06843f4abe0ef7a300c715cdbe5e2b413f4913f09b3ee7a189487a22e755159355deaa29e3a58371765bfe1188a805faf459728a37f76fd305146d3f60d8ec2377f71d7a74516cc1e3ef13eba9e84dd350eff57e812ad4bbabaa481e3df9c8638faf5c6af1f631d8fb3d80adae2f5bfc55146df210ee734991064e71f43764e9379f911682b9a701911f86bfefa5fbef532fa27bac4b105ddf5af17918ba3ee2f317c19ba5ea28cee1249d83da46d9ff901f89f38da0e6e51d2dae3972148b3adcfff7dfad3b48c6923d46cfa3aa2524c75cef90329bd2048c54d0bc57bf7f7fdfec0dcca9fcef5e9df5aeb1050ceccb0f091f7dfcf6b0dc59cf05d7b87ae300c7f8b2d5ab877185a9d979d3240903653c24a10c817b286d6ee7d70042035a8b919f25bccd91f3e9882115e503b1019ec971d7c1834146720e366ff278e32fa97814ddb6fc5164d04c59106b70eb65e861a90371b14f36f3147c66d2927c4a34363c6eda6868c9b9638ea9e3f033cad5d1cda0c715634e1af6e69b2a986135d8622bcfa47ab1115093e72f99c7efd0e08c004a82b2a5540f8f06143065c4b06167ae019b8880a555a6675390269939452b9a435e7b3d97fad56835dc8a58e23d047c0339d4629a5985e6c71c518637c29add4526a7390b4b106595deaf4faf5083ebf923075baaca26d91bf56ad6fcef8bbb4b471d6abb4c9af592fe330d2466a993e33d20a2e834f28030a332c9ff118e7980b2843cf0cce79eceb52e7e4471ff95f1f506ac9a5dd3222e0b7faaf2e0a5597ba28bbde4197ba28475def24fdbbd4451175d0ee1cde9d3716ed0c2f20bbbd17901dd3b00232f45bf942f63bc318f82da64fefeb797fa6cb88403bfd17986094d169c64f47c00f5a1104d0af38d3e54cb52580ba28a0fed2a52e0a56072568f70bfda37d2cedec327f8fa59ce95794561440cfb4da12c7ab1fb4e27d1b82407f62e0beb5bb4bf956c49fc5f15260a6e399d72f2d8d083ec28f9acf3029fd39e79cd58a24d4fa9c3ac4a1d64e7b5dd20a986df0491b6393d25aadbd17e39cb5feb49ca2fc96fc560b04f70e2595526e9c977cf0c55fe69c72665f603018f9b1584ccc8cb92e0ec2c4260e288bc9643218984cf6c26532d94be60a65b20db63e9d65f8da017c59548af6bafccaa3523ba949ebb517676db5ae746a89b3fcf4d702b7ddbbd2b95be00e5dd6e5aa74baacdc3294af5bb35daea42eff8554d4a514476ad4e5bfd0229885c12a9d309aa335f958575487deaeb0aeac9a98a4a0989a989666516e0e4d217ad5e557abdaa3eaf26f0f040aa0e2289914e0a286d6e44f4a6bb5f6628b71a5134f5aabb5f7e26c73ae74667b31ce797e2ddb6a553a5b597f5fab05823bb46158e90ce79ee19c2f6e39af74f2d0f57a71fe12c69813066361602a9d30b02d652c06058a2d65333b9b553a672f7b8aa385911f0b63ccce696d8a31618cd9e7833128a4ccca64954e9976c950a0f6f9b38f33e89396718c76c95060f7f9ba8f2ce893e6410b20285fd50a970fbea8b55fe79c73ce491bedd3f954565a5de3089d42d1da9df406ca99518504a7fe1ea4b8c7ad7df0f21be3a0c4398c61bb0e71c007b5c6693df87d21ced76ab540704e2e69429cfde0b67d8738af07c310277cd0e56a2d615788c31f7cb91ee4bb15ea075f2495d54ad885813374bdf84b0a97383b98c5d9412dce0e82e2ec604b9c1db4e2ece01667075fe2ec6028ce0e82776f7176d9c1bb5fe2ec7c5721f87cf02554016803af4b6f81fcd96548815997b4a21a223ca4b1c6fa66517dd5e5df2a6aa3b151b551f1f22f956caa4db52275f9d5a8da502aa5ba546d605ece5e7ecd551bd94b27648dcaaf38640b8a456bf425d0aceaf02cf9b48adad4db5721f9e27bf97447a7288ff628156db15f56239d21f76dfccd10fbb2dac46c2e54b54df9e2f5b2f52e51a21063aa08a3bf1c59fb6ef4c38d1a877b598ea7262925596a6dd706f96e89b283303b667fbb8ab090dfded1e5c17ed42f3f7297ae197553eb566ddff751812a24b8ab28556cfced5dc5f9cd8ec35d450a2382e13e193dfb156577ed2a5618110d971aa894cb9aec61d221743302000000c31700002810088724511ea969c87c14800c5078505e4a34508c486271300e83208642100461180641108481208002294792bb0150f8ea20842f70778afa1dfc6306e1226b6871b0b545448aff8f8bee024bba1249cc327f3c2d28f6cb4c368208f26d5bc7a3ac3e1669607f6ce913a3291fce8ebe4e087a9db5405a665132ae1c885cc826c5175bbd2249647682bf0e2b9998ce0c8a1dc63aad6d1bb4dcd39c28e8706b9f99812f6b2029738240d74bfeb815d68fd3b58db14ca91b193215951df2dc38e8aeccf88ae32eb50871736591fe1dc3f3c919be4aadcfafcd03b3b0bb6c0225fd9434481b2e345baa98405bedc09ebdcfe63cad8378cdbd0b6370698f1b698accd0bc50971c0f80346aa5a683b3ff0cebf0d2478d6db2cdfa9fa8b4ed68c5c8f78dfe3453e32773ed0b6318540a114f01e5d4349d8032f9cbe92f747b5465b454debc7ff49a65b90ef2f2fda2cb24b7f1768fd3c7a4396ec890d4afa0f444f877ec86c5e5ad172afbcc0fa3c4846931bbee2da38903e985726384ede914c08026166c4caf63980123373106155e3b71f38f5e6ca61a7e38347336a49eca9c000982702ee352659bd95beb3020938b5014070e9e291f5d51f1d8ab74ac5a0486d70388f2924e03f843c985e69502eafa99ec8fd7bd12a312b83a73024ff7cf77dca966ba179989c44e1f562784de86d60bc6cb78d6ba3afbc879bbd805f4953b6da158415c200545237ea84065b224141a2332cb40092d37220f20ee004f77b2610ab86649cc74628fa65304d6ed67179d39b6f74a54b37de36b5eec2cdde1704dfd4a604409b655eadf0625fb39f83edcee00a25ea5cb9fa3802c831bb478dc8561828fecc0f95acd69543c71be9dcb2be096e3559c1be195204ddce4be6e8780b36424d36ebfdd180e1a9739cc0f3d6d9f6b09173c69b9d2ccf4ce6c4fc41aa44c0480881149e3a2ef523f835d88b3ce54948c822329ac147ef49eed35e04478be88641b20f18a7bf9c4a4034a8a7985a8f3c0ec5a734ea423839ed389e79c4e2a40bebd62638da5737a53620bbcb2ecb22a750ecda138303e0fda3552b558879551dc65d3a25723459a678e30d7b22b754e510b378bc87b1a4715c0ce14f5c7587303669b6792335001e58bb5fb3f111a97c341c521b4699dc8102e28a650423f6d0d8d8fb0265e7509219192bd903019008d01c5ced7f468ab3c32e21d527d51109d0966d04cc31352ae097e93f6986ac09349b14fa474f4d458f581d099428a3aee32c51e43643e086df5dea5ff23cea41acd5cf92cc4917619e2e59ca25c76804b0de95ea127ca518be8d5c9d8b9d8317815d894a2397c827147ca5aa0f7d7f0a5001c2dc5e870ba4401b58095cb05106794aaa1b37dc0512a2326896930f6cc7d95725bb815cd25b727b0d2036ae5c135e2ef4389082bb77e481682ffc384613d34c123ea9a9e32aa26091b56829573b8cc68f1e7c8e042593befe42dc5a28b243c5236390b6d514936cae1f8a4255afb9f07d9e65927d7e516e57f0247666dfcea6dc703989e498b29e0509f7ec2e53e3eb5a043a919f5550a81b5793f785e2c7c93871dfe89f673d97d16f20293e60d00a1920a278e7dc9add61869234c3f38b555741d494229555d50fe73c9591fa4feb398f7066f349a5c686e3415b01d6de006e6621208f36de4a46cc3c0f780b980393efb792f5d9da957ffab0cba9001c35b8998344c74906298849311800559daedebb8e2f5be996779db89dd3b17a32f8d400ad7e27c43a3b79d4c100ac834f27e39d0a907bb891f618ac8f30856d95b3386e06de7ae442cacfe0b1dcc31b3d884d49f1bca167955c019a025c09b432fac04f6c77a0e0ba90c32590375fba06140359a68bcf33d79fdfac02de0445a5f4c491d7c001e3292334eaad03311eb4dc514023284437f32a1b85e11c7cee8fefa2ad85b6e09519ac719371b9c81def078e1ae4a26eed5ab0cdf24c2d2652c3672d2e7a2d789081ff9d386ad20ccb4737768c6049a2198eb913d7a6fce4ee583f06faa609f239de1746a5e7b6eaf38f231de498d5b12ff4f2a6066af4a7cc5dc7a1222ba221b7b90d1be79ec658ec616840ae164c3941865fbf41e54515b7cd289db3618ccdd3f9d4c987ced1c929f630ebb743e05011da61801ad561663c19d9fc9a9866b2f1d7f2acfa046dda5ddcefd1aaded7c83481620581be9decf72e79ba6e15516237ca9ece7075595f32dc6ec4c170dfeb6671f212d6d760cbb61f3448db11b99c232bb26df7ba2f6e9904ab8d5e4eb89874db3b246e51f89f76c9cf99f2c700ecd24fb183016c639d2287ed2b146244f6c47e5c601604be0b4e3210ccc90d7338f08eda9aa9baead251e8c598a65dc3a582d025b5451bc326316da97fda3b284db028f8bbec37a78e67ae947341fc963ec3f2228e43d2bf5a4dd77ceae29e0cc664a3dfb8acf04c2982622200eb07aa3c0affb1fd5584fd45a7206124be295b3fa10be883a87a96ac085497a5559c388ab6c4a9841213407d91a62bc5a8147b69a77b96c778b654f6f826c8c8c8548e683c16e991daf40df615cade8582ac73ec6393530a1932a5bac6a50d78a636ad9707f201d678d4815eb3f62c6c4107505328cb23bbbff5f4b0ba6c805f093780dd10b62b2449a45d99cc222eb0ab7245bd3561ed0ea56f1ef099897aa69c0f86dad6f639ed0d7a56240be5e1f60eb8d73ab1c8e2ce4a06c30dba9c3dfae37b1026364c51833addb02ee160acdcd05cb122ce7611f051624470694d91f1958e2c2e80fff37059da683888d5a21e29c058473ced5138b40fa8a3c20e1574b288e44adf685ea801c50f66fc9ecab049f84484015fd08614365551235793e600b61bf44a88d70aeed83d9948341a02fab3cbfec6bfb7780bb2a7941985e101929309f19008a1146a97bea0b90954a3c8d7234daec45712ed7ff1dc72e3f5eff65d8d2260994b5f6a7f48bf7d0dc4522604e7d623ce71f4b316a932e19291371967b7e01e4cff36126ea8a9f3e1999c5f24d5433f35c69b51be2dea26e68650950eb2b8d583ecf22e017659f287b8e7b53b6f4785aa3d56c03d80809c506c2684ff3d80a324f4ac3edfb7f14e47511aacc878be3a82962cd027b8e4058713204a0a1989ab3c4250fe91e645ef2819c9322a0087ad02d0b87db631f3c94ae6805102c495ea90298527b121688d1c58d50bd45791de8b4631ecc314ff162a8e6ee59bf1f4deb899f2f78a4c843bf8598ac002160ecf1b7ccb584159564268b8ee327dfa941c40b670e71c46e4e955a415508550dcec4da093aac9e2318c5d0f6a70809e383a51c8bf34ee8b9961b50c7325813f9e72c7cbd6c804f0e5c8e2ae9369216966492f0e2c793557848293f573a2fd1c008a2d8d7fc1193fbc6f49b74447586eb3c893b48f589caf9447e9382ac7ce7be0ee7e95f7c0d1f04c18e60662bf71db1cf9d39bf2c21c41987b207029ac93959c387fad74588efdd19ec577825004502768f487c88359a2cc2e19a46ae3c05de5da1e21ed9f4861406fdf0b5e9ee51b35dac7f3a5f9abe4d2f384365228df5b02e9b076fceacfe9ee1ef4729f9aa58b90f41bab64a60fdd16a17db699e3c3a7f88c2319738994a7ba73861af5423a48d197925a3c4b6709d9607f3fb2b1c853151b5defaf00b446588af5a1b15abb9c403ddc67efff8da71b872e47491afca0811e2812649536138db7390cd0a6e57ec79127ab7c0f5b4bae986d78153027b42eb4ba55260583a1ec6e7f02cfa548d488700b3a9598b72fa23215d3a95ecdc4dd712e30d9d4b65042b8eca4695853e82ba5336567df99e972e5cb6cebfb31010631d83c816b4d65f595baa2dc4a585db5ac8ce9d7ae0b668e79b50c8b50467f7df09ad3a72a63a1443a3549f3afd69f25794829b496a77a6611fa7a664e89f6812dcf17bb95e66b0546e454c07debe05a0495502e16e7e60b71dff36ab4ff7b5fd8ef1b1962fd5b7f0e6c42371e73a7693c7851c3c8301035b3c80856011813f434f9619f7f013658cfdcaddaa7070ef0b34654cdb63510eb2401437d23e49bd1766b533a8ac02d330419c847eb3a6d9610d1e77d84cbd16167663a3508e3fed8990f55f0a9873da59884d55c8ae5081ed411f93e4dbb000c257603389bce453907d5eb0631acd4fb0d297f538352e13396de6a4fcf8d1e393dccae2c32f27fc5df243bf386bf70cd8471efe8e7fd20aa5dbce51185ab8fe20924e548492275ece4c9e70265432f42dbd776a534a42fabded539ad629d079b43876fe5af177109609d50c78ea637217a90795bf6e03845ccf71325c154ac06032863130a50d7dc30262e25bc595eeb22bb5680a5308a891eb7bf06cbfc9289c130607be5ce967ff76830636f2ac3ca1352b7be7e177c311d009cda32cce9f51d4fb8ec33ed5ef2f0f7ce1d323074363eb35cf53f69973d4abaf00eca4d31e14134d86c0e97c8558e3bbe0b87699b7a68d87f20eb499abc9216ed4cea1170d267bc2112e84ace5465c6c47ad1f41025c6cd58200bba03b92902708a1101b116b6265567002bcb7bd7773435445545a7a0b1cf2b7fe1f332c78c08f876ba8a3f5e60499d21d43ab947a389c4bf44a04cd7c7f0bafe6e1edab371a3378a488030e4f4eec72b0aa40b9dfa192a0b0cfc88ec061ded11b0dc6b354f075875e9a79a99c501175f2a79ab2e15b4cdf0713c1e24d2149dea9e2f169cbefebce83b94e0f15fc4da34597f9f618ce038b9bd49273bbc43e861b91274c11139650465a427c5c6045d0dc85a46637880badda3e333b32affed1a9f276f7c4faba6d863b5a3de055e3b0a09a3023033d72e5e482886bba82a2b1484bd854a67a3fcf914e210f10b9824c820f6b27ed2911fc06f12e9449f6d6ccde4763f6e7c8da67db9e68fc2c6dd141edfcbc94a1e276ae68c830dac5d80d113965019b0a9a234cdd785ed6991c02931369d2adcc217f5190e57afe2c515a76db4b09f9906f4af046fcedde2cbaa338c3159239da2657962ee5ee3ef171a65dc62dc032b8b6cbc85475745f771e7a93e5a9bb00d48ccaa7c3b85fd6ec408f3a392634aac844d2c96eae6a1dc1adcab1314beba2f756462b26e794e8ba8d7638b1e636a781e6a006687c59c6b03f994ce68e6c64f5e9d1fd8bf5681601142e64215d0f684b253c1b350499ac6c63a38bdabb9635644d134151a609fb0dce3900a339ebc0c448d15ee3ad989369e630a7ebed34d4d1f5663ecf12a86f9cc74bbc44fb48050a848e09e0108cce411cdac9c04aee1f2442e68ae771a145a568126061ac968928ab014e77138017b4ce9a9c866bbd0328f0625e0923fc4cbd0e5d56cd90842c539610fdd286ecc4a01b073aaa39ee673ceea178fb19dcae2c6e29058b568891875418b8e2a8cce45ada98785022f9e1c994b83bc0a41893555b7eeb72e6b2d448700f1aa8e1ac3e1d87217087903bc4fb8b3f62f3855113eb6f21938c987e0330f603e53cae8311335af9712221e1fbf63b7362eda61b31927dc6e31913bd712ef5e27fdf18c57515c5aa153450a6cf0535da87f81951cf6c85d094065ae8bcb2f53e3f61983b6192abf07a80854ffd20c177dea386c64b04e637743a0ff31268d597fea63d051b579cdaed4ad1b1399d5d046d418041bdef92a99fe92becef8ae68e9aff7926d5c9d6fa9b4b8dada5613193dd71468645482b1ca9affae07a24af17f7f1bc69f6c2c189daa184686c34a5861c3b127063e00aef58a8f531388ea217465483b6c64c13f4fa2aa913ed3d0683b416f8c1274cb3620fab86ed3c771afc1ebc5b0e5472fed6b0b331101a4282fc904d6199b32e8461f084c8c619ecbdf0c00125c9a83e7e1de0de84e04ea1ed12dd56f84e95acc0c6dc56a80970de8131e40e840086e008c8a30479a0a5531428d240eb306619385984d2da7ce16e9b362dfb811efc1e800650c4f239339ace6685e4b99864c6ed6d1f0b9e439168c46ddaeadbc6f880721aefd806b5abfa06fc0e2a3fdac99cef0911b7fbba110059dda3d330d5338d0bdb52ede4ec85efbaec72976215526dafd420cc79c2f44302e33ac3a2e2bd1a13ea9c3e598a87d92b9d4245b6ec0bd6d74fba29292ca5bce83fd8556ffce1c48c324f88460e2a663de5938105c7470a27db0a5326acb13f2393893e6adc344169a7c7ba212d8eac1c9ad866bb0dbbbb1912791cb7512611770ddab0e7375c1834c0fab95e1a866fdaca018d65ebada3303663e3307e6da60f1b5f3aa79107e5f28ef3aaffb461c045e4edc4652ee546a122611b79921e5127986a5b0f1939c0d2569346de1a0637d7c2963271d739aa7c4806cb48739127dbae2dc04d885dbefddab5c4a156c16a76020e6825db92e6b3f8c6125fe8088b82b3049995d3ed5cd665a8b8d71aa42f2c096dd9319e006d129185fad6ef8a1b84f80e6d497a371013303a04bf19dbc705cdeed16953f876c5a6709416446ae42c5aa0063687135436930511c0c266bf363e74b9ac31090b54e83ab8a851f4488c19f0cf626fe767d5a8f71479b2304666f7f2afc1339d5f0f8a50c9d1bb55f991dc6510bae5760a670eb126c51e69e1b065a44631f1631288c1dbb6155e6c286f7fc4960929e4ad78a462ee8c63536a811ad8559c8ae3d223843dc009f7ea160ca9d944dc54685c580c20afbd9d7e950eb4310b69b99b5e05b92a847070138fc860d4266c9380079acb7f836e7475491992b6937ed1ba8944aaa2c3195fc75f4a3b05edc022c75ed41953b026045385c7e264920b657fadf37ae19395b20663921e5a01345f3ca00410fb8d2b8dc85974458a99160067cb0584863702aafd0bb8840101faf5e7ec430430cbae8ba2b0450db665db8e366930b577dfd7dd50dd96ef6317a18155bc2c9b9865bb14756fb3155ace4b26731d35cbd2c1d0c574751255645970c83e8b0f3fc55c53c37a39e32950130ab5b7236151f9cee457e9e49655d398b16070691b864215e87516d682cafbaf547dba0e7b4d4cc7caa053eb4b11ddc5b209059c85eb2640decabfc48f1c6a6529469597afbac30476815bb7c2f39d6acf8cc9df3a2c21f2cac0a4c68b00a66b42207edfcc050928374820a8b81b3683b811bf96cb84e7c98a13aeb548c8e19a0fb4097065c2ab613cf7b2e701397115b2c893d4770325ee0a32d43bc62a8481200fb18cbebc21d0f00f5cc5888e0cf5f8bd5e96ccaf3f376da62102435650e01653b6b380dc0fa042730acd00f2264cbe367cae190b35637aca81db547dd3478674a615de81c52e423347a158a21d0ad652ad4ff9a465ff42255989d2898e80d9dc6fb35526e0833f25b0642017c3d8e1c1c7cb1c9e07d7a9736e4adc83b612fd5de2d92d9c78c8ce4c4e8f0f4392561b2299920f313af1011d655906fba9f27e767668c69e1f0acd1d9936524f5c9a269a466b797b8701cfc63e9fe7e9c4bf4b74f7ebe544ec6f6defe1e04a10f68b87550d4b95e299f7a11e592e17262e448fbe48aa0aba3099e33c9996e1d973f594f368c267b8a524d12b221758076977c5815b9d5f780abd6e664f8c8c85b6201c42ac4e664fe5a92e49b85c3309deb6b04f1e6576598f2cfeee4e0cf1ce45127af4fe7df3f23cc92a1eaef047a994e3fc96bc5655e239779fb54a9f72e680174fb2824e90857d1a23e20f320b414986f30b1ca0d3db1b2dc47569fe6f321578ee91027c037c68d08f76d291c36bb01c3041029c72d51435a7504524a773d3bda7c2fda1990107416d08aec14f6189c700f882e77c44e7c3d55dd91167d4430e0ef05edccefa4e79aff988da656335d17c58adb9dbfa288a8a8f1a0e914a7a890c714057cfe0cd8289ea87dcbee9176ac40b974cff6695f06c7927a0d604b8b3db307ac26f1f24cb750f4dec0283d3ce15827eda7e8781bf7a4a248e7773ae025898d2083aa8919acf0481f3782411c482a7502c3a3d9aa102d8cef5ff3f7342329f944a43f8bb0790bf28c813f12fc4a8d7277e1a043b8082292c90673fb478558ec8c07f9df84ff72131b69306af87651c2ce08b1dd127f20cedc0f8fffacc45b57464ed6870cc230848be3e848ac05e7e66fe238a236dc6316fcc99096494956b09617a2d6f7a3486b5387b67a3155aa1a77612355e172d05fb19b7b3233a3eef7c6dba8308e2f2d190e9c6e50b7051d43478d94d89eccbc9324226988ce5023cddc1e98cc0a144196f5f414ea09256425aaf84726a0997e08cce1c3731435237b6f7328558714fdb6c34cae11594053797647e1071dfe409ac722e17748b67f6e4101d187f88202a023cf71ace27002dc19510d819926452db70f703a154a5ae322a493d1fbed43cc599c57126b88d9a7b89957739bbb63d788698b120e8583151688644b308f957f33740b31320a630b87495400753ef0f778c9e0a8913ce4107f73d49dece1c1ed8e28122b950f946871cb943afdc9cfcf9bb8022702c366553995a69bfbe5ed5a33fce6ce43df6351f64848d38994b668a965065835d19c45890ab7f15d62ef22f8bbc34b6ddff184b49bfe73fb829f8650a890935a4e8c85d4843022cc54017df00794839f1ce8d5b91a38262f02fccddd130b9290001f0426f756c6698ba5588dded127d9ba5296acce409eeae2e8402abe458f5404bef9f565dad4dfa77039cb6492bf6ea0936ee7d3360449c6a2dd5702ad3e9d69ed58b4d07f10aa8a3229a9b10722ea5347412f633ca46199965248d1e7a30ae053c255ea7f8b21c82962b3ca060b2bd28c412ad3b2eff727ee282d2332d7061bdbfa1ba36e18e3fc883b42158999bd1353783e03c337ed2638a0772d05e0d1f4c4a78704039145139097a22f9c046c0098ef80d16372c7f98ac5586096934bd3855a792a5ca97be72fc4ef4a966c8aaa5dcd62c6a06e17ed472431419ed8bae4a0c75e93978d32a7789e3cc42d8e0ac55bf09ff61ae685fec59bdbbb796e29a3db1a3f07bdd4d8dd3dbcc0de2657a97e5eb465b6e1f603405a0698e1045166bddc58ce1645196670c641099ac1806dd4ddaf28c31e14eef0a73e05c30672e2c45daa7c3b0f0d57da74b5ccecb96c3bb6c7b65be37e11417c108292599482429ee29f093762f700f3a1a9965b32a89a2cfa873d2a208d78437eded875d809d0708c2cab6f6bb018e085175b3ddfd000e5591228fbcbea9d3b5813448603ca9d5c66a1327cc3502ea10432cf09d8c06f3428e55decd4de6be2d0b17ae022abcde2858dc18d7ae6eb6c5d4a6c5659ddac128f891209eeceac101ed5b4fbfd563f03cfc67d49186a9dc36414a71f9d190acc21bf2faceb027dca918e987071aa485f2fa393d0de23b44980a32ad6d6cce2e34e92ec419a89485eafbb3b23bad433d2f0889d561397fb1e562d46fed8d095ee65295a79ac3319fd70f0a11ccfc3b7c2a5fb905c93df2e094acc6c61fb3097d17311e9ebce6c653748d8d64c0e3bf7fceec4124b09b59056e33febf27dc52883e72519df1d5fd817fa8d883a7d7c570e2d21368471082aa86fad72d847a61f0e639099e4136c92af29974a5674e561e040330ac32b8eeafa736476cd6f1be888dba909b804b920131f8bd34a2a4bd57d67c6f4deb09a60ef1c65bae80ba44943f8d98b00c2d842bfbea2d7cd1325200e61028164f4e4a8ca4866f712563caefc76cc705c042f4810d61a9e5a17e94813ef904859f42dc75ef017bcff1a79fe82715ed66ca00d56f4581014a5d8ebcd54cc9cb043cc8d80416cc3257d2aa6283d52e5f39c4a81b283b75eccf943d272c39b07e402c8f2d5281b1e7d73791cf545543edc9085485e9ad37775e2bfb10fc22f587442de1e664059f2a8610dad9517e31c730fdfac5d22ed77f309d8707b68f2050de9626c541fa48e58f3c928cdedc3ec67e9f947cd58fa170ca9809c97251265a4059592ff4c50ae07d58a7e8ccf0b75e428ae55a016d262c1948c7029643a25ace5604d09d174f0caf047dbdadb8a9de9c305e6c7efddb7518489c32243c0b092a743fc81e65f9ab1891b7e2752e4c1200b36a8fe8b9e7ce6673d69c4c95967d67deaf04a2b73e1997c16fc7d0cea2aa72ff2a5b3212ea1acfd1dabdfd2bc7af0e978c6481fa35fb2d0d4a46d6d65d77a8129de8f65403c3f6e347f04e5bdf46979828322eab7068642fe78deb72db93c4b306bf7b127d8d475aaadd84653f7416908424104781781470528474483f82ae05234511b58757dd5e409d6f8a6a02282b4c41cd5671531d3bec6bf09bb6539c6104b1138cc1e24e21b3e93d0b384d24db191448fe2d278e2ca9807d90a7a20636b982dd50f3e8de414e0a9074599551b680529441e6c8e2ca6d60e8f7251689f0744f3d038d9f504961243f4112602017bbbe62c72192538262f3afb0d893b43426a27f53ffd910388072e603ab51082d5f76552e886a5e36709273c60256cd1c63ce1001e0002dcf2c3693fba193aad6b6e179a4100c902c7c86632b0117c44deef804a02e367d2aabb57f108b7fa45772670e2432b9ba5cb9cbc79db8e8239a81b069c6c8d9a3995b720995f6012bbc410abb17e8be30ead0e9b2117d88ce95434022047f67c11e0275a3e68f8ff41f7dabbd163a2bb4121d2d376f5217d0a61c168bc60136fbe6ebc1b93ee023b668702b8910c11d6ba36f521e2ad3de591273b7e588e4b4cad9d3a4042d95fda0af74b2f3af5fe964a3805670cd8ab7e295378617eae22de52195f54481d7637e0cb3abea34cf0e3142d0da865972892f50d726cdf1690420b8e96426d8ecea1c335516f99ae09ed413389258615d74b40753c640c35048387820959f28a0635b7caf396af4c31fe6441eff48954b8629270754cfe91446f5b55c44509f6ab9746254813fee5fcfe869b1b119c7a6e1965147db07217c42103afc0625509fa3d8cc38516be3882884394d8d311cc3c6944407e55129d9bc959ddf67341d3f18b5651529ffd68649535b059a5d4e925c21140f0800ee628bd45cffc352239d9119c24127faa0d6383022de75924ee4f51428416d59877265c7f0e7497c487df269c7300b7ed74b5cb22f6f9200c7497e270a9814d10939b20fed85b41d02ec246af47eec8463a5d7002a8f2201d695221480ce59e0951054f07dff1566274a587aad0ac6e0aaa682c7b18f131c35019e269026dda3e1570dc502a6c79109981d27d28092512cfc91e9a07de5f0f3a9aed1bde9d7697dcf4252097d40b84d8b58630008d583df1c803b2fd222d5a8ab5d4014517415489791fb953048fc78bf69df412e9df1db46e8949092a443b132fb8df02499c1c963ba3be4c3591e14e31140dbfa00a59ba5703ee82f9750caedf0a9a96ddf68c3494b219d916ae2661f39017e3ad9985dacf958e6c1b3abc43c6a41aada35c4450606701d23c53a3a416f07309440063a7647a148b78e74967a5c44fade62ebe8c0f87a83bd69348fb330b01b11f78689025b0ad1b944085172121405c47e929a7a618a695439a38fb2ba040f30cbfa3e38a42334cf1a595327b4d7d2bf9c3482989e1f02aa44c0eeb05e54eb456001936528cec2a1d62580e967523ac02aaa015679e971b3e815ea7733ca2399236fbfb96c7d1e386487be13da649c7a9b6636b674858791ecdcd89b112ed064d7f27ac7ddc6b011ba68fec654a84145dbe369b187cd4481ce8c73cbfbf1dfe98f5803b3a6c8bcaf35fd6e0370dc0030b9b80d96aef8f2363dc14141aa3d7458dae32a617ce1a005fe0a9d7f5862f4e1b9a060a5e10a520b9a4cc2fd0c245c4d83c06f453a51d6d5518887e7a847c9b1b9702ffc4717990a708c6a0736b9bfa3a76398b3a5d5c720d03e89e96692466cb26596014fa0c55402276ae7c77f175d0f715efd033938acaa450570337af4a585031cb2b361ba7bce226f97445c6424887767d5dba11e90b46c47a09c49db2b6dc6df83f52df32b1afaedd94384a28f5d8552ea45c3f8d2c6aab3f9027b4843a111f5b7b2cb9d5287a32d2e64be9e1a84a9bc538ca6c186bc41512dc55213778a728cdb875d291e788412a128c3e361d2ca33457a3a376b0b0d43d90c1efe8ac2718ad45691bbe6a45a0b11384582878aba01f1a77f6d654face0c73a8c2c1a5e8bb25465fa2baa691869e52024981504cbf621a0397a527ef8d77c01c93eef2cc68d639a476fe462047958261ab7f516f1932996cd6c4b9aaa86ca71dffd162842e2486ca097f08a6f8ae9143595af047552ee44cda3fab8da37423aa88cca77332a86fe5b570cb3c9c0b214444a0738987801c6d006aa1a019c611726d6560c4a6c3225cbf89394197a28c10953ea08c789b772c6f24829644efadfe79f176ae550a6d788a60b391c12de55f1a874cc768649ed59d6348c026ab484e2a76b84e3ffa2b22df23deb15805e2887a56478546eafe4097512680471063bffa84a93fa84117fc3c771da2687d24a5c5c418c931a9ccc4584d1394f24107c3556f1cf6a6200c91435a8b7c64003795df4be374c3434746d2e83666eba3a4279182d761f6e2578326dc384cd50686ca05482988e22150f9c70696a982877974d4733eb2e6afeb381814f46a6e3362fb7d7f58758c755ccd3c449af1cfeeb491e626140d260786f2e409a9c25dc6549352d0afe5a03548aaad1750c3d4863f66affee4ba769ec7e5600e38d3df4d2a215c72e6fb542ffca1a09c6954b888cb7186da89fee09d4757413e4da3f16e5fdc172083c2df2408462033fd30f512f9860f594110eca2eef250318501d55a8ce3d2ed90df1d819d1a5f4276398ddb4ce42ea380d26dcf9cafb8010f428c974a619f815612a42379a846941ecc110cda2f2a6aafcb8d1207661717f836811402071cbf724297e05205117976f0aecdf873b716c1a929a02645a2a973a313613c4b5c9e18f8c06ed56e10a353a275400b2b99fad201c13ac189f0848c5132565103ae51a1a86485035a483dbfe7807d1d6a479a64e68cdfdf989486c56532e549840a17a9420a809dd98d31a9c0aafdc3445ed93ede8cd6a4a2bb7c0775d037ffadb4e6537943e4867dc0373a97c0f486284df999255405c792210a9c6165c760bbd8060f7d94881e436e4f4ff024e507b46916df220a764914bd235fc458d4fe28faa840d615df844a79c8612e95b5c78b31a00852afba02135361a7c50c49db998cc601091400aba870cb432af43f58a5f2ee46e65d1c6f19007909b8f321357c9dafe12b8d64d3785a8c3b5c0e129542450dd125d44ecfc1a3b177ac196141a262e9564a19d23bb1eb501b97174c99f7cc2acf8a273e6727da0421707ffcb05f7dfc2dd70769715921803f885a6023517b85bf368de4a2e2b521dd86b10203e4fa42715372870abecb6e2b09c342bde00ad006eb701ac2697422f44c51a0880ae5e0ba2401d15882050453438718dba559e8233144f5176f0d0d88c1e032afe2769930d82bdaa6185532fcfa8326f9f2afdab4b81b0cb5e44983a9e078f6c4b4a25e74751d51c0abe4d6068a55ea4205f776ced60884251b04c5e8e3cb8fd878c58998a3ea7f58bc62e5e33895d1045640edcd1c05fc7fc116355f9f964910f21041b19f6e0361722c54b3e775325a6a7f57ab7731f81cedefc961eacb560151ef75347a3730e0c7ce603888ec7b5797025850d96162706a32d9c6b7c3acc02f7f542d966ac5c0400bad07c349fcd7a5ce10074b6b7805dcc9001f80c3cbf5b1a140fb5fb24abd2e60beaca17d435c17b5add0264ff215cc4943ae244f0bfcff6e0d29837ad2fa216af18c8f0a138f235b512ee3083de0a0a4be0922e801c10395a401ebccddf687e072680e934bd1cebb8f3b9122cc918758848ce319d20a1581325c39d507ba053c9849173b8496944c8c63ab772df63cd6f3051d506de9dcdb33ee0dac02a8ea7ad1c7213093046608e1e1275d5c3315afd1e98385e94f5182d1374ca5fe4f7a8ac83c615c84468cc5c3717723a26263eb5d9979a2ab930c43fb60146787ab872220f9eb9c4a50198d3b63efa0d2c51903534b365bb950ad2c610afbf8e33f1d6c1f227845471a0e2eb50bb035ed6e85efe4f672381de324dba2492d674facf93c43ad47351fb143ea5efb2b9cdecf592652e048297f38387350401517e198d8114716468a37d08f794944e4b4724a59be76376eeab653295e89644279736e8b5137ceba261e2668230a18253583625b49dadcbfc3977057b0d9ca40f828fcf284fa8ecfe80d1c0378433ce0efe9bcc181b3e7ec503ea94cf42e83269804ddfe104b0c12ce3d989ddd356e8b0109dcde3cd182cb3dc0164bfcd3b423e2bdd0c8aeca54b68f95f592f56fac84d0179fd073524bc1dc0c84ae738c92c6a8f021cdb872d052dc9ef0910a7f60bc262c2e05596f3a2509b16da81063d81a6c03ec7954dafa63283b40d374b52dfe96dbb4d70bbd6b3b514d8b3d6c0fb5a2b4ae86c62103b81676bb3a22be92654e7c2e2ea0a7f60a1097c3c1d66ad1295250d200e2f2e991d01488c9561191d9a5be9b547e3179d8049f3b044f0220e1acf54fe4f22cc2ee7c9a9ef3c94008617210c502cde9d014722b530c6fadec11082530807a05f191cc2ec51475ce084192033acafdab90ad20ef5aa0dfdf82c1cea2d850aca8bc46bbb69e1d00608e6b15b03406a9a75c4eeb66d2213a23de790a5ac57e9778d22fc8f6d82d334dab5954fd1d88a4db7cf5a9273a6606b942dae9ea0609b28ad442e7c82ba088cbe7cc5813dbfa4d85246ef4b2f8184254b9644d04ce000fd0c4e00f92aa806c70279482402fc7494cfc50ac3c2524b6545a2065d5a5a59abdddbcf90cc842105c9ada87716db1d5fc80e6eac533bec6c2aafa058776fa92744de5f2673d8fc2bdde0f05893df16097afb80b9d045f37e18fe02d0708079102f9a4e9f09b0fa4f17c2350b1333b5bd17c77533e117faad4907be1f11a301d0b4502fc545e748584e7acfe9c5b1a5ecb06457d16da0b02878d8f446be04d880dcfe466eea4d7479d384fa0252480f0f2bda6a6543ac410dbd44bbf3fbda02e1a83ce69a3538cfc331f3f90fab0e18438a650e84eeca4bb20cf9652f9fcc92c995436826d03fac9899d119f9afb2cdc92b6db97534b8699c80375296b2a54c79f81b03508a8f4ddf5a354b0e2905ba05131a82fa0f447696b223b6d0fbb5316837e2ef3fe33001994a1ffc05d0b2317d9739118f443be334adfc7061ad83c4e4a253c98387957dd6b76c88bf7a35e43e804aa2a3794bb587a8248f3b8571e78386817b4d5520ab7f2815cece3c8a1b2d3214b9255c768646446f8ae595d006d739d853b840817d9bfb429f193d7f2d41d3161c4e9448889941fc043ed77ed48a3413be6eb516cc1d5da9b8a0f7d09f2dc41952337918895925257405498812d653cd60a17aa274702591fe9f9424499c16ed1b8489feeec063008791d18175aa1ff20cba01e8002860dd4fe23b4ebd31514f485142bf339c4206b16628c7d02e02fc9185c308669460970c96bae8f051617e2da07a2e4e00ece8092c042110ab343624aab027be788cd0cd694e3008a4797cf16026b3acc55a7fc9f209833dfdba97726d9bb0053f5e77390936321695334cbadac6889eca00fdf1015c6996bf4d0eef600359ee53dc8202b236623d787d426109f09970a828b1e926f843a3154cd1c36e61205f8c55b38caba4a78fee34071511ae360972867b7d003d82ce3831c180288e85641c6b8ec64447bbc6e2961399abfa073354de98761e205faca20ac91af5ebfc6aa6b51c07c5133b7bb4735075b7512222ea05c7910e4753db69c92f34f6a49a7950f462bd40cd2947221ac1d4620163a61688ab149f123bc9b0695756880c668356ee1059550807549948ee54e8c453c24419f35fd01cd2d803fd69be278dd5ba251665a493ef9bf10e0f928754987774e03b7f350eb33a91e4a9d08951cca4e04f9e90361942fc3912807b650857f1d78f9abfac1ac3b7f37d1d75fdc30a15554b45a9021b3d1e014177516adc022872f4c45cffdae9b4f3e582238202cf8255b421772ff3f60256113a0ab459587b3f901dd456737bc4d67b27c2e9db0ad6b0bebc925fd21d0d79195828cfb4ee0f84e17ec038ecd74d4651e285ef4b5ec2197f0387134470610c2cc373d9320a67778cfc65abc7b845fb1f6b00694c3063646adc52082b5b6303d24fc2e7739c7fd634b50fc648ba63d1cb2ee2bcb0841d6b8609001810dfd6ed85dfaf5aeb6e1164e70d306d1b23e78546432efa540341d0e9ca8bbbcd13381b355442a8174969afc2bd6aa086c32dffe2af9c6382484c76cfc124a17564499c5042745c508da35d222183eda7cda26fc0b803cfc140055f218a9654d75ba33c767e87d5070039ca2f8578fd13657c193c961bcfb1d9206fbdf3f5513ff4902b7d4f40437ca8bb6e8c28263bf5f8902db69a5b28b1c7150cb53d3cf51ba9bdacda6bdb19097e5f646b68b860ade8453da30daeff7300a8cfa179a76605fd5375ab8c90ea5bf2e1e6aabbdc86d2e28ea3649f81cce2ba7dc0d11b5a4a4bc800293decd4d28a1bcc27a85e8cfe43b5936b706452f2868a71831f416f02cc5e4aeb7804285d1ef9a846b79f4fe93783927021c7beef6e0fd30921d9288b47033a54d3dc425c8e0668285b5ba9cb8210fe77436133656dc5539d9660cc30725fffa7b571ea644de8145387e4ef064ad32e7dcc7d5de838fc90fc92a6c81f21c0809efecabd38d74b79ceabfe54b74e5d8097058e224ff750197f924a8b1164f1b334c512bd32549ddbe6773b2f0e0375dfe2748abd848b3b184667d90b834b7e6c99b460d040c77c8ad00c81bbc29c8f3b123ed4c2a1e780b8b1b3dd83575bcadc1117fbcb1182c7f70b01cf7d8083285902c9c54c301eaebeec38efb54b99f9aa38442ef4221ec5d44aa0ba33cb1489a8dd22c9d1ea5e9fafca3f3f33426211b22f289986ab9a7813ab430cf10f49d338365740c4f883068e84d3fe73833e134433dacd3b14b2935514200bd980bbcb10bcfdec9af3e5d3b4d8c0900252fff8268936711183a0499c0a882776626567bc04946c8e0d9823c5c5f86bb185a0fc5288118bb001e92cf51301accb036a766ba857309c11a539cd9a8c737e6e97cac1f8bbd715015cbc48c1b99c23cba7d67173dc720c0dc1d590dace5df8d8d5cf180680400804f2c45da40a0b404b778d8ddc9c173b8bdbf4eb1b9a0542d65adbad7523dd88f59295bd03f50a200ae609d43e6cdd1b293b5d0bbcb4956b816a0812040e7190810cc62023481038c461075ba7ced57090c5bab32c8b5e52beeacf66d41f75dd7d63b4c9ca2ed8a2977c8f980d1f57e3dda26e43d126ebd6e91ba35e6d92af7e12c9353ce8ddc6712dd62d322af5971ba7928f1e64917899f599d661e38c81ad4f9a459bac22d8bab44eefe3699375100d1e8fd5b5b61586f5c6e9534e9fac37147d7246aab083adb7f5d6e95375eb1da485f4093467add0b2ee56ac4bd33b202ec840066390b1923bc755d8750c526765d1c3ab2da9a053ed208b69cf6c3c9c1d64afbbecbe2ca82b06a9f3b47a8f8b17bb3388417f435c8d1ef429bb8f8bd30787aa5d76631b1183eef3712df3561c6a76d0e7f3f3d1ee6c5366ab3f8bc4c32c1231da2178feb2f96581e769f582b4693e5aa1e6c6c1f3f03e216d9aafeed369d33c48489d7f3e13d60a9f4f5b61d4fc7cf0fcfcf3795cf4c919e9f9c1f30f499ffaf3af8b3ecd3f2fdecfcabc349d0352820a5d28230c508c80085e9060e70b9d261c0e8ed00114479430822a70a290814fb8802eceb03332852ab2a0ceb1380fc0172a5e9e09d01580806ce3dc02dc02f28f7eefd4614054dcd732311f41f24cc42ba9792b647967c239269c63c93d7076ef83ee26af7308706fecc69391d6c93c0c8807c4c4fd43fe09920c13f1d47d62ca309184f3194206135a74818538f8408609797dd08fd739d7370969d32dc3e0ee208455555537cb43096704452ac6282384a00761105fc7186d8db5535d4be30084fbf1041f6b2822f8815c8d3550d7d4f57bb03b42186177144b8e106109d65c8f8c0fec2fed12f2d4d481738deb9177583e4679e59f8517b284e006f725bc8ec68178478a808377c0ee481074801f2604fcbbee7d9f756de201c4447dd712a3ba4318ecb815e75c942fa78160d838188e81e15be2c5cf922e9068e183240b1e2a76201188b409baf7e99302c321183af8749c83328eae2863bcbca368b41796f1be9c94abc2531f14b83ba70ab424457d1775d8450a81544ed41c8dc28ea709425ccbc3e9e1d48225dc04c1810ec0288243a23857494949ed3801195730210963a8c20c32d2092cb5208337217646a040049ceb6b00a1ab01a5947f125e31420863c326a08412c2e08b9e028c1da3c94463655e2a57cf27a68b8d457c500c312226c6732850a2448912254ade53222191c8c37371f10106a6e10afc699862ad2a6d224529dfd5a62fa4a328098b403de99c73ce41e81c26c66c0921a42ee79c032581f00b28a18430f802070f52a336814465a8ef0e3b233d44c08d3309374c7681f1810752cb74ec136882a0901294402767d6c04108a1a4c14b69d82357022205c337ce18c0110b9a9d730e7bc31bb273ced5ec9c7330a2384751f20444616f787087424275f8c12290250df53d8ab037480a140251d49049adeda2579442aab4d861cc1042986b865508218c43b221d8513a62502feabbc420bc2808af5c2b9dd939e7429568424c0c509c97fb8bd81b9e6ba1503e40c527282227c567082194422e512eaa86fa0ee35d4b8c3146d21549e09c73d2e66aa22933ba94f21c55b97ef44022189ea601129971772a4e25db81064e2861010b5e4a69872d898f167ab41fedc727894f8f4f4f95c3058f114a92f8e8a1e487f2a161d8626707469c1520300e315fec0481e30b1e3944470706efe5bab9d181a20b2d4419656c839349c299e49d847f91a2823cd722df7b2f084adc0e752bb9dee0fe1454efec54393b3b14a887b49016d2459a481b9ac88cd6e99cce494929a9901a8ac6699c1c4e1a48df00c04472d1cf89fac6784bde922a873a8288c8fba1ee4bc244122d47c9cece0d81dec3c3434a82c4e7a59476d854562a7557e5482e86b02189cf4ee5822aa75a817be91ec852c560a7e7dd1ee91345129f1e9f9e9c79750f1b768eec28d9d17292ececf8b0c1878a224a8af8eccc21497c0670817eed80ec13a263f2880c69c3109f2ae70b36d8a91e909b1b28701ececb79cffef0409bfa4b709f1640828ae0e16a8c5092c4470f253fa4d7837b07c812bbbbf7e29e7bbf17e9f32417decbb5f32c9ca54fe32b0bd732862174f4000131bccfb5c84b123613ab1c1f2df4482e70bfafe05e7aa777b6c0fd3a7af7da0141a1e5f44e1fd1c0d07eb49febc8b513c4b58383c700d8cc1494f673c54e15ee45fb71da4f147ce610ca06ed4703a35fa8db5a744f955345017f88d9a1407207644d6fc377e08bf4d9f1d989402a8c8b8b0b025a8544424002dea8d61aceb9972a0a57e55c550eee5f3b7d3ad22f920be91304e9a372137b071e9cc0398803c3509d06a85b91a7de32b3e4645a0603a3ba6e99fa96a0a51c113bec05c27b13fcf51b01a597ab970a7e5ae8e09ce9e1f70e23d6a14913bef0d03ea460785da40c4cee750a5639c197a85033093749a55f68b42905d5b93ee92061bfb8031b6a44709f4a9c364121713004030443a540b3e7d25c8d12894a490259a6784890e05ef233e34ab9267c9133b0bbb4cfe7f5a4a494544825941c4e720080491c01002ee76a8cb08b4ad43c7be41144441e0f646979df4ebc793b444e7e4833ae1b7a4320cb13f284e45c37a4339c1481533c228f484a69874d2587ab62e7248ed401435d995a983d1a769f57702f7d33b7c07dddf48d0e7d33802870963a59df0cc9c1997d134447e70cf6c29cfb7b51b429e7e5e490ce2080eba6c685a10e653dd0c3fd983c1c38c5bb1980e9824e3c4c6f6870e3d660ba9cab6155440ce153040f243502c08c484280025c3bdaa4e3023deb034cb43fc4ec04c182e170342ef81c1b212c75da2406171309b311312ca3be531a170c436db9a0e89e3d4170bf97702f12c7e19480d40de390f5481c8863006c666ad1a69e3d0770b99cb86eae25dccbec81ee2a01c6c149dad4a75fcc1ef8227574660f6e9d8b05f4001724a3ba48ea8e74dd4096aef3ba812f52ea489d8a53301b3a6009c7d0399c82e482804640025e025414206930a58e8c817bb96eae9b1be09b8b0537b84f1570a9e4504a99414239311989425bd5324c452302fbb2e0e1d87d899850f37167d28a09f6bc0a5f53e2f3fb6c538587a779b75eafb54d599e3a95b6ba1f183ef6e9f5e16542bedd354d1718ca3a29a5a4e2bcd90785df1d45494aca8b44c43e280c9740c518e19b079677363f9328e74eddfc0eba2ebe471d40178988e5f5219740679f5c84b54d3d1bd3b4cd4be2f5b342852cd53384cf411607af9450562c638c1144cdee17af6b5327f16c402d85502cbba8aa5956d5c2a0bec87e5d7f325776818e6517e894750abb40a08ab26e84096f5a8781717131997e12c97aae79e2ec9c802bd0a9492beb52d47556b01a1406daa7ef7a587c6333f1d008f8d23235e08aab61dddd08f872ddbd077cc1eeee01f842effe6e1e0e7cd1eef2cbc1ee3900d44503770fce1d80ba483c9c8f3bb353606ea84f0523b5a94fc22a809dde244a147832f44f865e9c4b063b4e940142dfc494790e01f3977d328ded8832d86d62094d86be655c9f3055391987cde8a0d73904cccfeaa5a8f054e7045c7d52540ee784d77de18030302e2e26d34f22d50a9d80a74d133095235ad7b996f998ae4c6dfacba91ac405284881a11004f7af3ea969bc8b3eb0275467f3083ff8787d3c3c135f8a77d7dd5b781b0944344ae9bb99d4f0ee1d3e683530dc92f6023fc70592a7858ec7e37a7c80a1e10283dfcadbf46cf49db3f9eece479dbbdad47d0a2ffca27d87afaf1c03921e8ba28c608104bb31b033724511f0cc46e05c5199c390a8d71ed3206ca63aa8aae66d13490c55fb9cd76cc8671fad693eb076b3f61a129e9fd88cf689cdcc536d3e6dfeea9728336fde4e37abdd7ce1aa3a5c9d5af45d9b757aebf4dbeb29365381a0db2b4c085019659441069eaf303b45ad4838a2d06758d72cb91ef4cd76e0e1d07569340d8b2ebab96684b53beddb65730dd66e7669af872e9bebad195d9af64bcb2e6dae51b1a24ea9fdd22e152beac4b966946b46d8b2fec31035d87d56478621bc600a3523dc81478621bc400af0a44338a0239b7be05128865eebcd3d70bc597bf61848e96b7de86f53bd66473458ac35c6cb1beb43b54d6e641dce309b6d5a3e9eba972138feda4207c79f9cce580fc74f987b601f10d39ce41910b81e511eb7931e6f36b91322de139a1ac6bbb6050ed68e72ea362daa33c28525d45179392c077dc484d02e6f3ea2e20e94f0066c3076f96a73c5b59646a7091dfbe8620fc58a9dda1c0a62fbe810bbf5d5462044ef81437f1804eae32d8d5e1a1dbbeb5b1a5d1c8e7e443f3abd23ecd86dee1173e8201c0f6ff2a21b85d88e5d9aeda34b8372cd47c4daf511f176518078d7feb01c28d7cdb83e20be2eafddec6e34b93e203eb9bc6e7ef8e4c4e62470d434fbe4558bc02188230432f6ec03e2939b4540e7756133d6a36dec3021208eda8d6daaa19be57d405c6ff601b1c79b7d4029922471392c8cbd5aeb1ac466acd1430f7df64bca43a7a120b687b25b452ddabec9af5c7b66afd0b3a7d807d8e052769af8d0b31b1f7a0cddd1c3d8a5b14e0fb5fa770a81aade61750be39d9eba4d8cea8c70c107d3d7d0cd363874cd4a9b7b949eddf54bcae5e825eb9a6574f3c49af6141b0343b6030f9f5cda6c72107669ea698e7afa98c681625ddad0b7938bb4d0e54397b72e76144b13bab14d237779b36b56f413bbd9c6229b4deeb4f4997e033638748c9646cf3e3a664597567bc8e68743af36f7d0e445d8334ba35d1ba16f21eb2a7df5cde624302d69a791afd7e4abf68add9b93c0f4ee3e22beee1eb6e8b5fe987560f769338fdc033b1948592ae27777440fece399a0b4a8060ebe9476c4cbcbfba3072908614726de4666254f8c659c9005c2977877576373351c8c6b71438d6de20210dca701238235a8220840b0865d1540d8021545ae788317b800d85d2146cf16b228d2180fb9061901cf123dc08e47092af061b0e3514209fcb0195292ea70742199a13ed2914a5dc50378deb2d43a0284e7a84d8f349f835975e9d3acfeaccaaaacfb8254f6e9d827042399a13a2c1da6aa14f4452eb6d003ec8c6c21887b0276467cb4c03e477c88809d111f29defb04d819c94214983a6c8614466d9218b5678cb15655552f0ad50aab5393f522bb84e82308457723d16b155dd1a10d5d91752e2412d9d03641950ae9aa37bbcf9a150bbeb2f015a9aaaaaab2f498cdd4081a0add0b428bddd91a92116d7ad5d2b02e968736bd63964711353d7c78e0479b6edaf4569e509f0d860ff7ed77691e91505b465e8c31c624505218fdde83955da2ba35677587ab4b59559f96ba950581a80abea2a07cf1c17e4d84929690dc509dd5401a08ae7f63d806d2267752186a3fbf31e4730275d306b30635d246822576815e14fd78a8590042df31db54f44e9be42ddb476c17e9534be04bebe4b9a4a990ad93d10b9bd94eb199581d04c2b0776c591476a3f1bc5dc4b5c81f83148220688222764bd3fab4fe6ebd7afc0b5d988dbfec461d75d784a61ed4ea9af23c90bdda7405a9daf3bcaac7d33ab2be563aaf07f3d6fa5c6793cecabe24cf8b36c936cd99d783f9987e5df4c9bd2eb0cd5ef911c1f2d522117116483cdc05ad288f1fe81fe8a66b6a93c440f7e5c4a0fe8736c9c750e8eece9d82b09909df14a1190a9b99f3ba669977d87ff4e9bd9b79f30332a93c18959d56a54d3ffac4a34f0e7449f499847950539b30d07d90fa7ee8d37b45da244df25d041403a1e3c1f254def4038f1f34fae5e55c389be27d39f0a58b4016f99c1c2a64f360f972f0cbe9d3c3f22f8a3ee9bc207d6a2cff2c88d484aaf3a26893fc65a7652fec30907d3951a8f15da48bc017539e5517298265147d023da64df0e5a5c0b1c8bf00cb1a60f97480e5a704dd97935951fb8d04eb511828e90da12e62cca2c2c7088f03d2b26b57c52ed03550051d848140200df301040a02479fb483fe114905cb2c098cdad7aa3927bd75bdda25ea374dab775bfd75d55baf59ec569b658f819ac56a66ad57eb9a745ad5044951fdaadfee3e72995d6953f5cdbe4dd5277c095d1364a95e5d2ad40c83ab78d24a4c9fb2e9873ee55110d571f4697b75d3ca4724953e51afaed96a496dd8427689d045db16128542a16fdb43db160aad5497c6f50cd9021731c668851a218410768443fc8b943cb54bd05fced13b4c2f25bdf4cebedbfd685beaf45979a3cf2bf28ac81e299f045d242595a74f0ed34a47474747eae8c097c7a3a3a3f37876a828425d5c8dc6f13442ecddb7f38ae8c017f730ea6fa74f11ca59ddb753a44d31bf2213042bf5768ac0972713ff5ca650e1f3e3b1fe78b2e8f141f292f4a97abc65a3b314bb7a5210d473cecaf378200b36d35b14d5e2c743a2b563a5ee13d2a6189d50e78d8d2bea4e9b9d0fce35ae8bf8c7d3a6785213ea9f4e9b3adebe3c5ccb0c20b51fdf8f86052c6821258c1d636c28a59452ca28e5a594524a299f6551e365942687c404d44d1ad4154a0faafeae20038c2be0b004bf0059422ba38c32ca90b9dec465aec7c3f4e91d24abea4e711db12c6c86dec266bab2d84c05aa287dd88c35b119ebd38216a4af6c1146d4b4293e623cda149f45a1becfbbec9060d61b736d82af2a58c10cc3e1b0754bf034efa0bf212c116d8ad7b0997977b4295636c2e8e893d301ab09133f274cc3e0186164506132e835245c1d6233a0431008545dd75537f5e9651a38b6293ef38089d057dbead4ce3fecdd790531f5c9d1227e30a98a6afde1781f6262daf4ee439be24136c66751c0d49577b635054a9242adf092a19f7d824a6acc7587c4866a1d9e94325fbd3e4a658c71526acd496d45e92deb3190da8afa659dcd93c657708c43c04214b7375c6377ab6b81cf786aff86c34068bff7deb37d698f78f13d2b50d72fb1022161f9a4bc9a05cba010ffa87cf1a7c643d32b23be273b6647628c110c38de0c4ac498e1e15ae2edd1020c086f063987ddacf14d47273521f1708699f085018e25de09150b1cab00311b8ddf85712df1194f35457869b4c9445a83c9217962feea5375eddbddf334b569be5a1a6d9a2e90653ebb34bba256a7ef550fc298958080b81ee2fc03d7eb5c4b95f554f78419f4e0ea315d8407fa94cfc3881e7da24e03cf673786cb8d0a9ecf2bf36ece39a763a99e64e70757f30e2ec1d5bc0b0d93ca572aab59d219ead5a679f8ae27b1a1d63ba9655957f6cd2eb13d5455db5d687b966d777b65b5bb55aff531b0b2da56edf52d4f7c65d663fa6db2e8cd2bd8a2956d152b2a6cf8724028a06ecea0c63695b4bbe7e2ee44d07a365610ae9858dc8834c305e658a73735bbe0e8925ddac567514aadca7577162a252a7c9c95ab61639b64846da06f7959575c97a0cb5799febad52f7a5d17bdb14d204a3e315ce65319d57fc5c462c532aceb029be8573f74f17233ae4df0107e240fe3a1bbef29d47375eb5a6f2c47f5b65e75633ff6dd0a6c03cf13526078eb27f7a2bbc4665e8cfd306b05f4f1a33ecd7035feaeab0e7ab64eef3c7d6339ac43d8d80c0df9616c52939a14858166409b0559cbce472c740a8878044d065c300df5eab23a6633b665ec1676299b5db00e1cfad5a7bbbb3e3d0a4481a8570abe80443763b7feb099d0053d06d6ebf2c4d975b14d57bc9cd7d150b7c605cbcb08b707ca29829b5ef0b683153ad78f81135ed4ddcd2ed8e16cc2f9b89d9b591ece79c1f88701d18f44e0e9eb1ee7836930b2c51652088694d6c7ed3183e027d3e2cd526eb24a79af2bbb306c54afd33ac2e2265f85c8aeede6c862b5f4ee10ca7a5ddae52b65d296b2ad94dd1c20f801c10f8edfe841772ea6b5abc253eba509d5c81c5c43378eeae8c444848944d7eb69155db566d7abe8aa597573ba19a5b496c491505c4dfb93d9cd42da103c7a66b757ed6699832b8e84a24da15b2d1d5c6fae9139979452ca2c7bb5d96b0eae525e3657f65a23732e795d19043fd925737070ae91391cf0e1001210fc40144c0147e640144cc10969f2aa097e6239cb7a24dd42b965726b64cda86698988c2ecd3cb1ac0ac5a4aa500e8f626272b3cd89cdb2939050286c66a2e4405229a188be519195a74de9a539b68b6e88463883b0a45d1ba2d367b556cbaa337ea3bac975c16bc6cd51dde495098511de61418cae6d303b842420aa6f971804b2d35b82a737f9664bf0d6b750a804af0d93876eaec1a1ebba6ab5b00480df48f90c912842938b007073a47cc6534c3ec3da40dc0e7fe01b2637d7606add5c83b7db600cc7ab1eb76a240f31214687976674edd29c1c3e64473007d2e3a14a09e56194c7340ab5145248512086cdcc4398d16955425894f42cc226df5c0fd73b40dfc4e4b14f2837b9c9af3e9ddce4d5d518dde4d735b1d8cd35183b6d4a6f89be9463bbf61bdb351b7aed37e835cdba90ddb094a31cda2ce5f496721cf1cac0a3c39be3e961bc35231b7cd9c6d7eb21be4d0e4d22d2cd8df385db24dacd1d40746845232cbae814c5c636897e622f5b5d8be831c6d3df889866c77e23bbd8b5c1d0529be1e15142779816d32856742d74739c1cfe4474288ab7946374f80dc0152cfae8d67af81b15de519b442f470ca4f37a9810f3669bea4054d8bab32ccd0debf51033c136939bc37abd55314c9bd65dd96360557dd2ede69a451d7478a6838d81a053685d9af9aa9aaf6c9894b0977288befd8668bbceb5d4db60df7e03fbb66155d49875ccbe32b0c342a1cdba39b0c76374c36e29473cf60d60b7ba96ede61a8a519b785db3f426767bb419b57974a932f0292d69bf11baceb5848e5de75a44b7c1da2de5d08efd8676ec36385a6a31ab59910dd91c31c4ea2956aa3707f678a97637a0ddea5ae8e3adae05bb0d7698766f64d8757312f8d68c6c66acee9cf6d50c1161cda16d16a8c8661f58b4d507b11d5e58bbd94396b59dd426ed99155d7fc7b457cd3a4df6ec6adfdc85b57b89acc3219b6bae4b7643780a6f52c186118300fdc921bc79d4b0b5d368e5c9254476cde64b2b894eb3fdbae85edf2ea83d06467bddb279135d57bbd42cf40101edf09644992d897e3ddbac5b125d1c0e97b0d358171dbb2a56886eddf9ebf1db2f9b7dc0edd415e38597bf2e4dbde8d29c1cda1c6fcd85eb8d2727903ec251a42626105e1bbc4ce0637508af1ba1161b42086f2545856fd1639f4c2efaecd3551289be91eacd57858f305eec249b3d74d578131b3a0dfcf610fc76b85d384765b9aab2ac0b678f36fbc071625966f268f3e8d45bb34eaa6a43d9f6f8edf1d5bd7e626936914dc49ed9d04d6cadb671c8e6d10559d6f66b131dbbe8970d3ddaec9bcd17d9eb98a5a972cd556d4e22d37eb90b4f9b89a06cf681dd61dc0116769005786baeb7032cec000bf8bd515a44443b25e54a829151461965943832f94cfbcb44230b65869cb24a92bc34dd4d4a29350d463b9172c6890909453bbd26e54de44fb24b4c080aa35d461965945146a93d4a6ab376a965f4d2661c5866b7147af6ebd49642df4ee7ac9544fab3094bedd905cb5b0addebdbb75fc781e9af18282595269730583e83c1f546931c263348379e8c4652cbe42533f928a59645994529a5945264330e2c2a997084c1dacee4a93d241f930b8e37946d39c4d01d24a5a8a8a4904eb2d056b32c7bbc19066737d7c0886cae81891607ceee6096f21928d648a472127cc9e1a519249c652a37bbe0eca6904e6efe4d2e2e3893e273b00eb2441c301126cb70e078cdaa585163966559ae818938d7c0c0c8247c727060f75957b050410e6056b050010e527ac60c88c0f4541b8d6aad2bd9cd2b389bb3de4c048e376738a28c32ca28459a9477a566a566c5c484141a1de5c4047b3df6788a65d94352bbc484d84e8f3ddabc826316b398c52c66dba5f7f231bbc8666c747a983d9eaa44c4cb5bd2aa2d611876ecf938c3b69b89c0f5f46622b0c4b29168f4ecd28c4e6b567076b98247a12c44ca504e4c4c4ceab3d3cca46619cd4c6a463322544651ae886e83c6e401471f703c6d3836ab3dabd1e4e61a6f5e11adb4a966a512b1729c1d4b152c557036ba9a3431f9c8ba36896eae99a6854426cfece82636c748448428746b5688a859a959b189c2a60811304ac4c0b302a3440c5798e8f4c0548b999499b434d67aadde614264988f88b938ddbc446ed65a7f851eb5fa4c087aec992c693707bdd843365351c542af3db057faaa95b69b835eec66b4da5c1faaa2639746f4edd25cd4fa88b8fa885854af8a723232198db25146b35146b7edc2a1c77e3964810fc51c5abc35bbd963e0ed7679d10c1b9b25c6add6dc036777998c8199cdd6e899157d647360d9b5cde61e18bbb4db4356d36ce3cd662bb3d866736037d324c6a3c749572b5cb732eab05a8385c8ba6e97f6b23287379b6b46d6ab910884cd542294110dfd44b45ddb2c755aadc49e5d6e87981036d67a1ae333f9cde6118e37b26fd9dde2319bb72042cffee2eb633c959766b3572b81b09e5db3d9a3cd3510d872dd2cf60cdb6ce6301bd7b7c774b6ddbc1d141f311c0f876e09117abc34a16797863e7eb335a3d08d11259e8846373f2c1289b66c1365d84cb565d5451f9663b35525aadae36dd49b6da48d527667a1574b5fe10ba94a291202bbc36eb455752b4765b9cb6ab334d7ad5bd6b76b368fb00cd94a2275bd39bb7be824abdd3d066ab66684b763b6030f5f879675cb6ad76633ed8de5d81eaa56bbb4a29b6b9cbda1bd3eb3f9e5d0eaad57d7cd6e6d8da5f5b939ec46ddaebbbcb4a2d6ad2afb7ea831e9d1109ad150dce208474dbbb906c79be5af07d0ec4f74074377d33e437bbc798435f79a51362f93511d913e4d1e6fc5504e3eeb28bbf6ecf234c34cb6c72bbb4e25bd6ef6f8ecf1f49bcd57fd76a8d99c9d5e29e5f678bafd0a85b64b7ffd3a7da69dda5c83e345aba6dd9a11a6f5f4d2d46f9706bb6ccd28bb35235c318c6482723232198db26ba7da28d3b48b3ebb36cae8a5cd5aaf592f212c7c5db3d64336d7e0d0e1c4a2bb7e119d8a6c1e69526553b1a26e36f4cc62332e7a76d1b73bcaaa4874f308cb9b6b4617d52eed0ed3b45c333a894e55645d9b4237579c6597331ed6f2712defd3d6206d1252a5681319d5c55b757890b527f0bb86e36af45fb67cf01b6181dfb34910f07bca88c7b5bc5bf5e406bf50121bd4ac25d1c1cfb9162d499ff2d3bce853b6c116cef7e96a48fcaec3d5b8f0fb0ed27c414d2142abe8cb1cf852bd2f756e758e051ef1c3c6c4477800471019478e8856b0c2c37bd2b21c6c18d35ddaa4c38ac6d95981e16dbc1efab027bb0cc1b3c385089e223c38a09125398097455871e101c6c5c58507d31ae68448be080327320192de26d8a436ddddabf880e17da06c6dc39c334e41e79a389348f0a59f4918f2b89623520b0872ef6a13fc73ceb9e7e9fe2e854c2785b83aaf5961051f943e9247eec81ed98321cc320b2dfa942b177dca328b2ca490474222933c9f48466d1aa0bf4f9065814034468bc648ab49279d745aa7a716ad267dd47ad57c0f142184b18706234904ab401aa636c18751b309d3e897918c8be9674f8243854f1249124537d5ddb161773f1f2d26cece478b1e2d30fc8e1d238aa8f129e23344083b234b94a8579d3f69d079407ac3c145ce14f43ac862a71b6acb0841efbe1bfb70406fd069fad4df21f5dedf71dc73f0efe6f90ac73e206d82b09aefa64f0f3704dd2170c4e8685317d103e44161048f1f15cc0478f3838304489f72863c3e7a55e9539f4a431a702a139ee003e4094ee0f9e1e1bdf7de7bfdf76aadafbe275f7cefc1b8146144cd73edfa21e1d127238ae8534dcc869d91307e2ef845cd13ff18a14fed4c71081842bcd0011dc1d10113c8a879de00006e1db0c3812ff333167135faf21247aa20224182e1092cdf5cf40b2807e2b81a3e7da24045201022630c012383847801cae92e3227a80008386329000396e35ae4295b648c213a00c07db31dd7226f8100804139f0e5eaa259e4bdd00143c835c6100c08112c31288a60994117121b54f96ce56cae2584df6506705f76565dcbcb1ac055c62ec5f89e08cbc3c8ffd02ff5477ed405964de4f202cb9bb0a444ae50a20c9c7dc0d25da1041538beb638b8b064064717b41c10163a2aaec6c36e08ee6e25745c0f4543c618a939e78c261bb344ed43087fe81384f03cd429cc2851d09287f8c3abca3a8aeabeeb6975dfb2acda17046fdd5cd909840eb86f3d768e135317c77589d9d10349c55de4dd21ae8e3689e250454ca87dca0e49764930cc5007ac14098ed0c0072700bb233360014f0c64e93705765789d88c6c4903c73b0804ca6e078370f409741b34a763a12c8e981f8af0809f8130e6071cf29a98d03038fa9479f821883ee58833cc47d855d1c40e7026e1fea93d23a0d28a3a038508a17272346945cd91087ecf9108122418e6c09cdee99d9f9f27044b2beabb931468660e3bccc63bbc25204c72c67d2311d7d28f92a20a21e26a0000bb3b2427e84cfd2c81e077a9843b3979e36ac4bf0af77b24e26ac0bf7b02217c4ccb246d9a8116091b33114288d9a03997479bfacdbc6761d45c75d250f39c8dddb8110f3118638c0e8b11da0a678a939dcf7b9ff0aebe1b34c7b5f4dfe5e15afad4bd1b54bc535097e2c012059e8cfcc339090cef14f20e0159fa5bd42c7fe48fabe17a09ee4337066e4b0ceca880b3a5831e827b09ee678a236524176abf678a83fb55888505752b940445cdaee7e9c09dbe8333c98bfe6edee545f53fe04ed499e4330d9c5d4fe5518411357db22edfa34ff4f23e3ce06a80aa6907d1cd34707e37373979bdb9a25c0759b27b72dd5cabc97590851a5d07595a5a966559d082cec54f37e785e78cddefbaa2e16572353abeb1bb8ed743bcef4602aec6bb6f04ae455edebc1e5a092c0fed93e2e950fb84bc202f8a97f37a80e5a50edc1be25ae4b7a0ee4df5c2fb21cb7b44b4ae98a054d39c6bedf201b5457d315eefbdf762bde8033d51ed45467dcf454949498148da73cf39e7faf57330d80c7487ddceb9ca39372d6ce6bdfbfabdd7efc57e8ca018f328b6fbc1efd0a2f4c29c735896695a7597e65c16afdff411e3acb0db5137a9d3dcb1acdf7bb1e17b365ea8819ca8efe6ab6e214de4466fe4b2ec974c42cd3442319baec55da3da8545a8f5ab19e8ed5003ea56f2dcc1f1f4bd39ab7b4284bc5bb750b3dbf18138f0257b3ccc812f35c31807f408999514e36a6038be7de04b0fc8123f67ad2452fbb81f1ff8f2864096f83a9f10c812c3a8b97d7ce41635bb1dec625c8d77d2166a15707458e087a710f882535e7c0ff8f2bee058e2cb30d750c3c0f1e79591d34e7cc6bee2192a7d3df4783dc0db3e48b81af336175c4beca10313cfe70a3bf7e96a4c1b97155547cbcc5b8354f87e5f702f31af0c38be35e0f8c27865e0e88410c8127f980359e235a8f3d23ed7cda66fb76e26cd7943b56e2452fd6f6e9f08b44c76210e6489af3eb97d70fc133267ad24d2cdc753481582233677aad68c80830714903c8b043c385e7fd1314660e5051f182b30a26c425250ec9c2205b272409723d588cf0ae8a19f1180ddff6849ae057e88d743df19ee0090bab3c4052c2f8fccacb8a0130912243c201213121e104e242624538c9a9764c78321fc10f0451e1e0784a0ed083850388104cfc0ae0a277eaa788310ac825d1562508121114c352ee9b86c64dc557ac5e46a3c5229fef43fb0cf4bc0c990200bf5797f60f7363277812cc7d4e7eb25e064eacd557b7691324e263bf55e295e0764a190908093c1717fc44016ea978dcc690fa6ae82457532d82dedb83e7fdd1becd7e7f54106644a7d4b1598a7df51d5fbc3077d77ccd3fbe3c9ccec7630f592fcc3980eec9674805eda711df4eba03f19180127739d464b9f730f664740fc44486aefdcdce01e430f106ef0659f0e64a12eb750f3c38143cd6e49e40159a86796b24e51b780abd181cae3f5d0778a7aea17702f409a30c960c721c9d453efc60138243460eacef9800323833dbb3560ead4b24fe6b22f073e9c3651d5e737dccf0f47071020d7b303a1d75e8aafb791c12c56ef0fedbe9dffc82e0fa67edd4686527b5922a578ec3632454af19622709d3ec6bb831ebb425e0e64a1de104cfd7a3a98ba1e0e64a1acf7cdb5c45717b2c4832e2482dd0e495836afab26281247bc32b0bb8f99b51edb7fb603131f012a230e37b8735075c09769723552b0bc0b7cb15eaa4b88e4ef3b6550af63d52d4148ddec92c067f7070fc8d27f8d29b525f7aca7e49e39fa1dd6e9dde9fde1037777d067f7870b6491ff21a909b2c8f71b0e2341722dfdcdb558a787d86529b5aeb4e33afd75ef2cedb8d695c09371d0edc0691b83ae0b64715acfb4e9963ea0dd327d83ab7b71ae5fdd05beccdf1d06be4cfb642c5bd9ac4875b8a40348ff7dc30f97e47f5cb73e3fef0ffaea3632109bd1010488f5eb02a94e6f8c655d4af2f436323025794b11b05e7d8755dd1f3e907747757a7f38c852dd0a5940ffe1208b751d64b191b1917132d5842ca0c7b4cc8c42adae457e732dd24196d9f31a96e1260bf575a9217ef7a14fce48178a9c4ef0bb113efaa5ba7946c0a023939f038622383b24f8c51e306c01f74faf87c68fd4271ef0fb3ba9bcf3f07a805fc12f0738d3984dd4e30c63829d912ea4c03c38c36057450a7e80dfcd0cae3ec9964656600228e6745f0421b998825d154d780193b033e28523b5ab60822dbac84113c468a20c49641e0ef0042e1d1e4a46f9b6a08626ec400d443035e420e6a0861d50178646d410030ce1492e8e67c3ce881a489086318e604fa75f5f0f0f3b200a2e96c8d077b575000970702353dad18787f7470380ecc8b48d0c75ddfdde83d05559b6c1ee005a3072239344bfac05d636f070e8b34ff3d6318c52ecb4af8d3af80923176070d105de013b235b14b1c113bb01a25ea854cb988a8b1a650cd188000000011315002030140e8904a3e18020a87aef011480108dac4c62449889b32087510a19630c31841002400446006664061b00c0497ae8d130665179b0dbc158114d3f880b3119da4a60013541de03a980b8448eeb53b3221a5cecd288ace50bc4426564df2241db17894c82231cae20f6ce9b98499589cc461726626d13613c960a7161fb1314c8a5984640648b3c3cd45cfcbbbc60ca865fc6f521360718b53b3a072495148d4f1ec6349ee1a9e557944c254bcd103e3b12d8004dd42d2ba37a614c0168e45a06a7bfcb3fee76e9bd98c51323fd3c296e675dc04d4c9f378f33010d4a4bb87346463c3ecc6673274632181df1dcd3d1445d1dc8fa7e894550b4d08cbcd6d98a579567906320914371a27ba96756c1da306e581e2fd8f7fd0636b673163f43bb5bee9b82b1c1eb893f474d4e5de7a2712d55a1d9f6f2476ef9dddac23d4d61a29403931e7f2e104d14f0c866254198ed9ce2b2ee0d86074a288b8b42eabe42fb7b07b11ea6de256a3f46d38793be08f241ee45ac353e0c733cecec1d7b70635de0ca7157f3dd63f007647bb51b243e87126ee61debc93b7cf64630b68894359601f076878f869772b3522f9b46bc23a0fb1c44d523ad8ac56b771aca25fce7b5d34096e53f03c1aba7bffd205201232c6ba193e0763a885cecb17f0553640a00b88a3492953af654fc2193fb940cf9d07f7ec64b4ef02909485b4020d56ec57862369a9fe4f4145c6f80da2df8daf49392435e674e16c33d11fe5a978cdc727dd4285df4d4689cd8352e1e8b70cd424eb6bda7bafac3e2b8cc651f6c9890e59a1eb792b4cb4b000283819793392f54932300bd241249f9b125045e53a1cc46852a71cb73c944a32fbbc0c5474ff16ea5bc34e75cc8a8dcddaff36591cfd2161bb7c017f4a9378c4aaf8fc707f2dc887df517cf8ea7e0d5971d5245f501e324b4808b5f070f4f650bfbb0c0b5690ba19486ec5057dc4dbfafc8f61661e536c18a68009d901622e640f31a15aaafcb9a0159d4385e4970c52885409f9ccd5119cf2bd6870289b2726ebb2ef334374caaa2c8dbc8bc0040ec50a27e098c83891d522de11123a45a3725f0fbf73ed89bfbe51e75acb9b0a5e5dd6ef65f0f557fc87da5bbbebf1404e9455d446e0ed7cd468fb575925f16d11e683e36265e2b98792d091348bba1f1069f68695d4a5282b2d0a9a0b80da8258ae8683e003d9834ae693570c10475b6b839cf9556b4ab6a1bff06bbc1505e40d397e06cabc9b5fabdffd7f970c1df37c64694be2335ee2308e13308b4d2eee6f2362fbce8fb078a2afe217d3b68b83a4724476696db93e0660804675ad03cee5ebc2b44278347b043ccf7b0e1cf3a6cc156759c00b8f143981d0c9867b266be8d44216ed33dabc147482711b1e81308efca5e3a35af2ec284fc348cf6f3df5df0d0df0b2573717f7b3d5463c37f27240d0d05a5b4cad07fb75f14139d5b207df7e56d2bc92acc0595bee1178672eb6ab5cc2f8c61edd5b02aba7525bcbd6c9b8c955e57d3e0f5ea0e2ddccef88dad8b23d0770fdb5d896c6aa731267855bdae10e5d6e03d8b6a38e8ccc5d87809cfef0eb63bc3261fa2fc9edcbbd65f97933bf55792d1bb9c1ecd6179b6bac937a620f7262337f35d7e079b813cae3e84bb6fcce2d56065efff64ba2df3fcc48f09ff91c7ab0b52a018fedecab16b6b0782c3cf6b8d42bd8111affcf9f2e482c9c6b814855a674b0e173d5b62806b33b8043522286fe0b2afb84032d890843fdeb5602d42f9d361e74514abab73124fe0eb8fb5ff65339c9ab94df8a370795016696dd2783e9254addaae7d3c2a79a47a4b934e4706e4e64cc6450004a65845383795017cfb1173dcde89710f3c7cd66935720954726fd02cfe991823e903e44b7a2bb7e08d383b17a5e2355c4d5726a584ad5457c730cbd893068844a45f89514b2597fc12a5940be4bb6188a1fda31ab3f3dc340e91b8c062865179817726893b2829ece032b45be6a8f39d3e4b7854abdac4ad533414a7c0c1126b7951135e94e5ceb0c34ee482b3635ceb2dbf1a697044650159965018b7ac603f8f61a0a392940b2328c876001f28a0f4bb0ef1fa4ba2cd69a9d313348e9b8dbbc29868c9b7a4f29d61a808e5caf27d80d128238f83d1cd48b7812c0f454988d61dd1f0e42508432fe2811112dd78e0c89eec1d3b1695d3ca2a4b2f026acef294a1aa5c728805a0432e1f7ee4575de49aa0df078b1cc52b10a510a26bb0eed096f396e0c32bf17e00f6800f17420effc6e2802a00e444e872c9b18c97b05883ed67b48f081075408c293b72a2129b11d0321c08a574043532e58985929c25ee04e5ab2e189225913112841e48c4cd172a3871d8721891dee9d4c7b97ab4517f57419d126f2818c5fa6105f4c3e006e912e2b0632dbbddbda20f6d0b5199f4736dc29232be202b3de04248df8526c78c5032dc2abc3dfc58da17c5b6dd03ad19890ad6fa3ecf5d4f6c7a16956dd004213ac62c185b8e1579d9ddf8b26021517f6883665125c117d347124c88f1d48abe935eb1244f9666951e3e7430b03a0068a2c3c2e14851029e48377310b310f3c0e54d68a8851ede94571c1e1dbd0c73f3d3f0e6c40f007db86d04872b0b10d892c5bcd3cd3fffaf26d7438e2a85fe63b25d70474fe56b6126f91025e07f844f76e5b8cb1c8c2afb88b677ee1286e1a03089cca87f766a05bf7a0bd43a062e33abd0cf5f3a57a55a5d9559dfb391ec7fba02d99cd80ee32371dc019113e7295ab130547024030280b9397a5e3505f91b7f82674b362094c59803adf15133f5ec38fbddca832a00dd148129b8f37d3e31a078a7a80ff63e52d68c9f67e048152330650031c3ce028ce9ee4663c4040f66b1e1ac25a6b867fe2f1b00bfb90ddb5ee18aba0b3e6b0c12cd0d66b30c7a35dcd0163c3a90ca4b26ea7c946179b17bc938e7194bfe9e4462c3ced0f871c4c1558f2a6d31f37378d144e068ce16e5b3ac15022b1aec41553bfb24cd78e7a21b4eea7006d0fbff2982d0a1f53ceb8577433cd807e03001fb20a3930329034947f77070ee6482356fab16894b4a1fe1c2049785b044419e6552083395d1aecb4f03d13ac6345f94abe08558081db56d3668cf05cf8262f4ae0fb9a03d357eeea31d74ba8bc949e1a57f5120201c797c80b6ef1b8e19faa70b48adf2fe1a24d673d490451eb0f5c76ced64d86558a81b345fe920e575440fcc9ce83ae26cc7cbb86fcb54939aa0b03f4aeeabe4fbcf29710119c5bf612458855a131017fed814f138047b9f962381e4153f783f6894210f4c47ad279888904f447708036d3d03046104d9bfefdf3d71c0fb9140e6bb8c2ff6f79ee91085d8240189cb0ee8e23a02cd18cbadc3ebf715f523e609317bd01fd20385450ab4ef8a4590314004b35fba937dbf5fda2219601520cca5ddc8e409a52b4cc58b37c6a2fb786b127a09b36f45189e6f93a802224127415a7feb0c5d13301bef9a6b9705a79391da7adc07964aa7babe043c54ed8b90d0f59735981564aa1400cd79bd6a0240888e45c83d1383fe1aef9a613bee70c973e191341575f8ff438e58310797a787c778e05a543e8d8f98f201b1ab7a8419f9b58d8c78a167237afcf7ef20faba0c7849a6818d4aff4a42395a061450804a444561d8fe8ecf924fa1ae14f4ecc632e4fc03658b0a7a4bf228ad5a3e67c2adb3bd1f90bdcefee815a05039ac7662ee338ec9c4454415ab78121617f63cf8f40c49cb675628c534a062e1da7875928fed1135fde6bb25d41a38105a6750ab6c12521a06c593d0e185d0bb4a9ed42ce69802661d2ab34fa633d2a7ac0336aba04de75333ac6c34d4629e034f3435e8221ba549ba5dafbee73dfb8a9e1367754d727da5d21816c5c0854c0b6a37d24021f5c251ad66bf65db493e06d6bc69436a2ea50efc6c3cd5d487dd06ca10aa2e995f56b7779f74fa89156c8603c20dd50d9e1545746f796b07a356c4b1bcf00d2962c9b22dedb0ca54b94ea744e3f9ff916dbc33e1ab3411adbf710afe1a599ae1606fcc59586934584d7fc1bcf56a8a0f5885540e32076c2f8ee237880571905c890274467523fa38d06413aa2f7ea077ae37f49e80b89bad7ec78d9d4f458621b6a2d1480f2d97ca2536155b2a25fee08aba43e3ccfb2a194e1206058472dd48f2cbfa3c8c14491a1a9a91df516b49e3aab01e50a7eb9517adac7e36cf6dae9461b22d8e42176a8da1605c25ac686b40fd382b8b08ed149a3a2d8860f0c1d447a6c7c6df7bdda7726a3c85c3b80ec7da1f7eb20d09cef796d2a611ff2fdd9f690967cd49759e02c69c18b50f930737c6060e71c7bfdca3be0514452271a81970003d85bacf4221267af2aa1f3694267769f20c201c4b062a1d428feaa2392dd65300c123fe47e776bd8c387a66c79725d5a5d0d99a06380a2e670574bf2c70ff0227169fba0242b843a180e237d4320d8e1030f8641858b84e082217581050c137c4dd1e14df9bee0431f1e7b68b48047e8d8eb56382680f49be5974a6ee293d06149ce1162fb5e7797df80115e0934b05f4154fd7e19f31d6d5530554488b2d32ec8b6835c4ea5e688151a04210d444c30d9067f5549761f961cfdb085478ff6278c6f3089c52d1661bc7ab30810a604cb676e2984826d6decadafc3050e17cee1ada3eeb82d362c0b1f2faf08718cfcf43dd3f31a94a6895cb4c91b268a6887a1b1b26cd309159312a184b2b59e9d8992a69dff37fff1317359bfbf66cb24453a8f067d677483da615c1f1911432df916d2165355100660d9fb96139b0504bd0eab6c6c06a4d38e34fdd31bb50bffa4a8f7c36b619edf594a7b56b95beb480aa994470eb6006f8499c36791e1f07a363fa903192457836a423fbd91d43bfc0e31388ce0ca9f864812d501a414aedf028300a0630c42d8f28453c43d443ec4be332cf3b722642d851b57753a2f5753ffe717420a4b4359bd87fb90533f5421075ecb38c584106b2f5e555410a766814d5d9794a55970ca8414196881795dd4099ce2c6d994a2247fd33b521eb1fbd7446ddc2bf8b7fbc2f6853918ff14ee21c99a2875f3c36e43e9b06e68c89180b705ec113c9c7390509c693aca7006a9d1384a5853f74ff90322c49a39053dd3f04d367392f31c7fda3132e1cc2a440ee62887f248c36adf8554dada05f4161318b42773a54da85b6649d296ecba8a29652444903dcba55870070e2c160d3ede1292d1ab00e96f55787f2ed346394a571435427e744454afc5ad52326f760ab7ad7b66d7ef05e355c6d7360e62c0cb0dd1e2137968da6bbdcee02d030216f4c0634d1890a5cfbfbf7df5c629b449c62e2897d7c8f1bac103d76ffda860dd724fa854ef27eea95610b9493aaacd0353b1b58eff09e6d1cb9815ed638d9d43dd5de846f9832426432bb45229cffb958e8b074ecb1d8da2ce4861c097c0b2e1b2c3b0f29f6e5ca9545c5e8d72ec4ed882f1226ce6b1053701ba13eaa9f015b77f8868d03bd8f5644873b896ed68a0ed8b79075b03f7c0b7f7170f890deb5957f3cdc8b58dd4787d86b74b50c3484bf87507dfacea4aa971fd05bd54a931afad858459c0a13aa83bea32de28283fb2fd2f0494b575764525d8c862da3f4cc0ec5289daddf86c40c5b0eed84924ca3019d5474e8dc436cbe73d3537bfe59dc59ae8d9256611864a41daf02b84bc85ccda5d1e17fbd58187cc50df0a7d1d89119969076f98f71d7a93b4e4fe9f6aebcb536567897c200f913a54bcae2ee1e853ef984547f303c6b9e52b116a75c4d49bf657ae04784bffeea3f62626085159ba3bf0a222503334f86ba4b8abbb7e422ac575cf16847637c30b248230ed84c44a15b0305cbdf97046ccd8252b88f21d56de3d69e725c6091aed8a99957d7b030d14385d123e17a9112be6b4c52a784f9e2d5923f6ebfd56fe84140e8cb2c17bfd32c88decf39f16ee519cbc3afe7daf4f2ecfcf5449022e1eeee8635abbbcf7e1e832eeefebb81ebba71ee46f5ca1add181be547fd4066d3320d21d6f10159c963c76699e009e8442c8a06a057884715a3aa1047aba357cec0280fde3ef03e7ac5629528da36ffa7c25698233b4d3336b3e49dc93e7c0da3cca12cb8f29f641a8c36123eb7047a0796ad44a7d1686d6b778b591ce30908d42a21dc05452b86bd4d9305c54cb18ebc5fd281dff02e30ce1b34f57333f257f121930cd95a9db867694690c8001d7f23b5703c93e89a1b402e932234458eb50a00e27bcc298a7c01f6007a59ef226e22faef83df382313f76330b636e68697522fa2d8f23ed1b63713631f917d17a2cf9cf728e514c450030bb3fc57251ad3ffb3cbb0a57c6b59d4230d3b7297d9dd473b157c1a5bfef957469b9e796a4a427e0f3f84f4bbec90dfa89186b787411e0cddf55b1455051b09366029157c198ac0ee36eb8085bd50fd43e18afe50cd42ee6cbf381d330f85e043b2eaf6409503fe85157bad870ccfa3008a7bb0d4a6a4581d40b1f7de3eede566252ac5c003bb466191215395d175d0ceffb6c9abd695963081e9028532a084414a707712f9b271f1c02c805c97374ff632a55a9d4a98463687cc6613b2bbbcb85197ce7f3b83ee067ea3d3fd38a9a7b7d074a55a89ef419a6af27e5027810b6b2f2563a2593b49c86758f3118dba894048feaf461b25a9f1de895dce1e40cb6527efbd2544ef0aa15bd04e3e686b96cccf6169724febb49842d8398ce15f0f4077cc03a5d6b5a9ac443d68bf7a40391edd2f225a70b35243da3d4e95b3940c3fa07390c341a5080d7edf153fe99a85aa400a94bf412e540eea40fe7bda3e472d698409bb5620dc4da54eddffffa95a2f46523755af9678e9fd6521f30c7d490a0293ac7a9799135da628ba09360ecf531796c1236c71b2694398251924720f00f2e07a358d6a8bdcc8beeed18c323cc4c5b19a1a3c325b8ed2680f780fbb0abcf0d33131d39e6d9635b2ad02d69106e0ba6605e8904f0fc0a57a990d80d4d1b902d60030529eaa42c25ae2567f8df1c7d284fb806afc01c816ef12a1b880af78a98a0b5894076c6daca15e00a33071010d8df93b0a74634f241d7a572224821b50135c9850fc2f5d20b8cd0fa798eb3c211b67fe46cb8609f5c90f10c060592bccfb0f89e0ff0f0907b49f3feaee7b1cac39e4d5d8cc6350e43988a447fb1d68c47d1c1186c3a2ba6e39e6b1248da2266a5f8bca81dcc1f43e6393fc8f6d6c6d2ced1f417321f57210190282b0a2ccf8d017f228cf15d52d60dc22224e040fb85580b7a80d34115ff4f2d9b984c20ae5b912f5aaf73a102cb14934703581d6d9721ac61d47b8473efc557ec877830a50f21c700bffef71c39d01d4dd452933cd4464f7fe2b5350009cf0781710ffd648121d5ba11dfe3f376fa0487878fd7b5f63df9865c0557be934303bfa39b2d4f40985470e9e0c2202e588cca83fe35d6114403cf4d1e5c9a71783c02fac03b37da7e44229b4d7b7c0bb07687dcd54d6213173dd205e2844a14491bee714a64f4d1d521f760deaf41782db6440ea5494167484f179aeb95a130d090d4f286522b7be7daa84b6f7c2426b6867c426051e38862047b0564ef2fcafd5dd49496315f2bb7765b1662b923b437e375a0a091f1e77f8c3fbab05e2df0f9efce7c523f70942e6955c939428d86ec838d80d2fc10b67b2bdb679689ff20e74a276c88ad01a8d3d480fbd377b7b97dfa0fc3f9bf752380e73a768525359a3e620ee8c85e7daf74b35bb2dec80dfb469dd939c7e9e385a1b89477fd3a064e9f8898b53c366bd5052f5de2025893ab770c4208ad99479953aa1cfc9374932d3840c1c589284304c3829359e31695b19fa8bfaed2d035de4dd86533a397dd995637351e4982d9066bb1222bce73f950e08c68bb974ad632ef75cbb106c9fae00372517e49521e6c367bcb9d4e0061606f39c11e117a226222891e3030b51139f55537bb2adb236b9acb58a690d8a13b3fe73b0e61a3eef9ad5fdc89e9d60bc529d68d08aa48f246e480e836e1b494a37969b57af45072bd044c6ad0e1d3f13bc532a47937a69a55635dafb31edcd371c6a29aca592b27dbfc83e059df98512667a9df9bb1da300da3589c25c12b6fef11ae31fb8a07c049154be6cc54368c041c2c3651c06206285a1bf672295d1501f7fa216aac29aad726a6c94a90f482b59bcf3559d22431378b374cad93c26247e2831024efd6e892d8610cecc48df3274766ef73696360cc046b3941c2d7ae6dd78b733c241d1b8526e5b3779824f3475820d37d12b50f50b1b49111dc14fa2afd6bd07618aba4657f85f6ec65b8eb6a723c75f246a813e8e6e526039c575ad31c41e5a39674d60864ddbe348628135360b7fed190e88190d6c94f8916e881115daa86f7eff89b3086bdbfb98e1930216351d0d02f1184d87f7514099c21bf66113fbf198b4cf18ab0ca8a93da2344619c73edee1d7b058182904086965e89a81761d67f8b7d4a88572872b018159055fb9be7e2b7783fa5014897afedef9ab9552bac0b89246f20f69523c0a54c291d304e67f1ff93ce519440b77f20af2990335e16ac9b59964bea1b693a4d6702946e1639ed933720acc9768579705ce628edb375e157608621b92a82527eac3852abcc92d162f40c7b1de7f7b09c48e336177004960fd3c8752a15e30ec21866178126f6e447b20665a792759ea097a8c153210a26468c2688469098b55660fb81ade35c258be700310055df1009a663d89feb1f222edb1b711adc4fefc9ff439980872c7a3731d06752ba6b8569feb47bc03c49a705b6dfc499ccc3da11210c49ffc6ad4c1fef5e156c3d4239359fbe9155710575084a6763f9cbd926498f508fb7f6e726b4b65a305ef20779e076b6bbc398c0c947cedef089919d6bc695f8f226a55cc88d3737a4a97631378fa15a570572b04a00268f1a5f40a99577cf45ad381671605d3e9d4832dbee7c30c5eb4d0230458f6f40aeb32300042c101b90d03a9f7867936d32c2a8e41dc9f759961ce0c3dbbf96f7760c9678f14a6af5667715f70d3ba998f011fc5ee7a66b24ccc0fe8a07d336235a97d0512c8c8880d1d0102d205d131cace11c0e1d80e14520bef477b4a2b0d14f8bc6763f9461c6945958971bd4ac3d1814a300e2896169d58d7fc90a1904e74230e18e138ebec78a4191d1a738f92fcb20be8a10a3e062f13f9a996a0c3fb8f71f128c9f00ea6d2c0ea243e73673baa12f78314b4cf6f1480929c52c0f53be4b20f76288f69e3dafcf796d844aeb699383e4f0896a9a34b3925a83025e52b599da432b5674eb8dd68d0ecb312eec47a5fba4cf841b47208ce2dc44aed4512e39eecd2ad2198c985501341e1a3f3f0b49342bcd2468e9222a297191a56e427858bbdbb388331990fd236101c52e3c8ee94ccfd7235ba83b08b0f0156243b8de1fd63950ffa8db714cf69128558b693d18cd754fcd46e539609e186c640d412c789b528f24310d1068c419ccf92838ddad3a776a351e6b8ef36a41e2dbf36611dd1b5414b2b2ab0408041c3d22aa87324c51594631f3ae99b8e9d31427c653f4606867539328c8aca2518255f332701f1ca07037636d765c8e20468c5f90b63fad6ae5c4026987f84eacb532a33c67d0fb5ec473e6c93ea07cf1e0923cee427ef3bb0c893c61c26179696921730920e691a34ad451da011944b88a0929bb2d356dcfc0600912bd9f2978a7028ddeca7359d3c03e6f51641f02013efb89906509618b1156f9202b725dad37b189e6d0929a422f68351967bf15c3eed8d628c4e42c33f90aa9436eb90a337f86807b52ddceb117f9792bbd1909cba949140ec476c356d1e9ec412b6712535d86841b33261ea270548e3f5488275357f290e7b73dce2a39d616986ce2ebcd2d1a3805ee1b58fd3cb7d096edca7a7b0f585a0896fa481b4e566d662d006cd0c33bb9a2e2533294d006609962e769211a70debbaa80d63518e331590dd100941a08726711019406479274b1f1e03a7300de04a4020d8355a55aa4b33cc9b76356b9e9c88728d83eea7a026c93a1474804bdf3af2b78786373209424418fd2d1129f8031f84727cb14d4d718383ae76b2bb39c9b4a43c23fb312957bf424622f5c39bc2bb2cc0e58bc2ddd4179ec1dd6a6e33a52fb69915d40c9f399ce134378040d172299aa0d1e69e1709ac6e273f94bba512907ef1a24f443bd7cb916f06ccaf0e845b32ca10350cb06a1dcf31a0c56125d33183334bc931700ae46a3813eab8d3f322d702b9216a135abfd1b86d1a3e2bc01241c6bfd732014940a0ad16b214c2bd39584be9cac0e0945c2661238bcbcf92b30c51ad565c394aac5fd474b0a2cf6dea34292578933bdd37d592227559e5252029b28b05d179faf187b315a1b5a403973910952be4071be5469989f1d84d8c3255adf09c46339cb415d69d38a059e2b997a097eff749ca34811129c4913074701cdc1e5adc047b3c9108cf911b503ecea378cc34217de5faea035ce00da395517a2aa92ba745a9558ef9269f46b0ee7cd12182f8c306ccd29e0c7385bb5c90bbef53213371391a969b0bd3c4d7955223809820d063bb1bde3e2a1b453cb816d550794776a4f7ed929bf8ab453d67431db54862791a728c9b598dbf14460cb215b20a3f3ba85a879a9d35591fea6a9532aa0e9949cb95ebc3ea55aaca54c8657578d1402b6e013dc3b5d46de350f9295506d9ddfae5af68abbf50a667dd3548195a070fc86047157637156bf0ea3edb9bd1effd3b6b61bb4d77d224e238553949bc0019ec26a049c87bbfb4906cc55d305e83815f08d4f1b13de82cb06fe7146313d799e607cd7dc61f3508b876aa5681d96f44557cb89038a00f6c12d0e4ca5902af993aa251aaec16046a1c34447c4f45c87fa473dc961485e0e2c53e42065b31d65a2047fa0dcbce23abfd1276becbc88402d0882e6d1eca20edbb00c6c517b536f7f0566f0d50542191b054abd3179010bbcdef0997154ea15d63aafe9917a3d64a04a166df94effa807e752c1bb84f4fe6b20802b2b6f982553a82b04e8d4c2907b42a608804d661b813c650aa6208330b7e7a6321e77cc7418aa5f518a6034fe930368290e2c613af4a054fb425b13fb81708ed969311b835bf84e36b8a11f8312e14d72df746206dc9508c2e9ada95a091b087d09decd2889f06bc052ba6a46f3d3494ee9686426b660d1c6f7879e331f8e4da5bfa826a4246f23241550c99df7e3b40f6e182d7a3fc759a0bac5af61ecc21b3266efe30d2d45a6725d1cf0694c35928ecb27a6a3e874c4b9fc5ab6056e85467edc49ee96aeec720b11f25ca0695ad11c880832e50601635558b527c0230496b704ded2fb225ba3125bf46da3127c1f0e2188737933cbde7f8b7986187fc3c969121253fe49be7b6c4b6e74a2de78789ae7eb32f9c91e9ff51c0dae1703a5e2660ce9564c90f816c63c84d35090c7b5384607859426990a6b8d5d523b6f828ebfabac25a09a37d25a0a379944bbf6783e31cebd410054adf287be71d4e4b31d67e07443644d43c7353104b1498b73f9981296cc1ea92433000755d1e0e060c9dcb9eb3e860761c557cabf613cc1530c02af32a832dfd81d2b04a909952bdbab4eedb9e7d20d3d94c0faa9a9165a587fd1fe3adfa4b6c9d231c92cf7d42b3a08a8297ef497563076c1d679aed511cf4f663793c53d88409df3744a9295b86a9008ec87371a4b64574a4393d293e17e7a23473c324a10c50ca9880e779e4d5c10b21a62b0fbd799c398762245c4542b736a3f2650f7c91032bc6e852afc097e79e479bd046d3a2ee1afe0781a33c61eb8820989458bcc5c0f30a6b5c3b13d5fd8e14ae40868b6a1867c3437ae75eb98d183a5815f71596722a41f9acc28c7f924abc0d74ba76a8a8cb8a81d270cd1bcb653a94bbc2d27846a61500c0ebb712831d4367370c5f601e26072a9ef33ac93c42d9beabaae38a11424acc19866eb056c19cee297a5628d7948592bcad59accfaabca67055da5879edcb659d317b246b69e071c066385254435ea0e996a14606e85c29b4e15d6f609c077134621f8dfe7e4616d6fc30e21ed79f482e28206394202fabe82a70abff9fea2f93ffa26d4f416336958f1f36ccc594e0cc16d4867f3d2bed57cc7f360931558bf9afe6a260ddcb341a7e34a872e9fbc0e378b86d0514ebca02bf1c760909825a07f888bf5828b4d1a8ef61085989907fc81d69067b284284ad2ba84fe3449a8275c36a006bddd14cafec9a6693909be0eac1e1e9307fe232895fcd4f11dda651e52f04606c42f10041e23e78b90f16ba9359697e898c178466bac2dc631bc1a42b61ab6720f77b76bacfb124135374e3ef9e711b4d023a5f4e351f2c574792a47bd74d8561d66f3bff15236bd15e07d28e769f46c21be3f2aaf9d8ddb0195732a80939266dd69c1d56eaef8137ad87f473ef6e4c9970334be96a6665ac3a11e050265588b09df816edb734abf11c83c19181a1091090f7a9ca628ecf3bfe304a1dd83841fac1e02b1165aeec6fd8a237e41bcd782ebe7201aae08f67d12b4f363e0204bc6b028ebfa6dfd3a34688be9168f03574a5cefc11f17eda18ac7c9795d5ac4a152947cb407bc02c54b97729b11634b56fc7d126accc8c2b7e405d2e3acb11d85d3605c62ad07e85790ba6e6b3611f8b204008871871858c598c0a9d8f96404c45d0971aa433507613102dc55f01c13775da35cb20c82b5dfb207c54922ceda2f266213949d3885272577a3428705698fc5311de87664a4350c82ddfc08621574c5a39f3b6ce8b8a3592d4da206d0698d7b6b653a845331a2bdc44843be90f5d301489db89166fe815c1e7068b81f3f57bc81e802323d787df5f2df906ccf7530e13496886b3f3100f132ee27af3cac7d262b03610731287d7e434e5658351229501d72821c41997e2ebf075552451e8ba04bfa6b020f927530ce630f46d3023ba087c7e5642a028d47f70e48284a33a8513c3dd6fb1696df05b67282ca925ce3ce4436a0c616af995d6e295d4e5b337208778fac05e70856821b8cac38ebde6ffb4c02029b83a9200d176a3b85571568484b8c0ad7dfe2324ab318f48640112d73dbb31d6a5c1a0b88d0314a51503f954dd0a2b113583aa060ca85196163388aaa57615763f5d82d33b6b51c9848cfabdb397c382a7369262e25fd5481b096c5c67ca6e3f8bf1aa097ce10df2b4d596673c09c2a07242acb91f769e40f1d5e8b0c3b70c2f5152d358f4ea61fd6a22379c9aec507f35d49c88a2f89aaf36d18f105ec8a18118bf6106a33fb09ff511e829c3867c91afa4775d9ad6e9a12139159449365bd94d42422a32853c4c5262af636a266378e1fa46ab8906889d6173b3cc184e8288514593038bd9e90e5841162e014ee85d247a6f0280886480688dc2908ae9307b006f6fe9d7feddb73d8e19c6c7cbcffb59e0bd0a6ed74c0c3fa0b1e60317fc42c5aa532b910e34331ec6b1450a2ab9f2a8694d69175765de2eec9303b20ba9305dd0d665ef4d773e1a2958b2971a45c1c762059685be6a91a3966c68f2fb4f1890842c3fc956a1d274d48edb3d8e4712996aa000ed2865ced4cc7d0d4f5979b1dd219ea74fd927b8e5c49b81fa9b13f53d075151e8b865b017810c51512c20a6ce53d15fbdffc411b6502051a880ac4c3701bd8f2e47858984ebcd519c900950d25e52e20dcd3a0207c3f1fdb30adfe70231f14cfe6daa05877cb2992959f4864cb0bc4eac32a4d5ddc1421f7b9c42f9da7e96a98cef79be54f39fd9513dfaf1432c16ee1e6e79acc5a3ec17545ee1445ca247c73462bb13c89bc6dbe1dee037c014a3f79c011646cce02e74a4a3612501c986a98f8a865418809dd5a461382b9e1832f637d0192931411b33001bc36ab9da3b156b05def7bbc04fb6297685b1b86b4b2db98aa962eaebc0045851c077d882ddcdc79e55d8887cb941d29436bfb458830d0e40d7fca3acd7eef4d3c82b9fdc2f7707c3a0272f0119f5f4afb94ae46903769ead6a06a238ea9495f2bcea20e5ed3f353a37c64b1192463f1d163a95e5bc53cb12b5327d9c602d2533baee979b38491886e626e4c58665129720f2f5cbf7b7feddb3deeff6dfb30092220bb632cb475dfe1e9f4250612fef277f0d5a36a99dece4879092f845f97775058c8af580528ab5cd8bf0a120c8275d70c9861f0a6fe02784cec84c1203745fc87f5e57e34650d0d1acbc4186c3e9f1dbf1ea1a171174d3cceac37d6023bbf5b75806709ce9ca4aa89f3074468b6e6b55c8a587ef9394395853cb003f4867fb7c1d107a0189209079b2e3465bf5ccf75c5cba13840cab4f8eeff9f6b646327e7e227bbfbc165519df4058eef162a78abd15ff874e0d392b7c7d35e078b83c04c878529584392e54c8bc9902bc312162d704c5bb2809174ebfab77cbd00a7f6ae59e9f00a7b794eee43defefb7a67b04a5b165e8c4eb406f651843b6e2c8cb97c18a2449a1a734c6613fc2104705111a98f9bfcc1d5ef52e2286dfe72563dce9ba0cccaf6ab4e35bd9c1a2031160b262fd609619766e5e00a462fcf67b77511504468699f80c3502e6e8540bbabea241b30293301f10f533f0dda50f347db910f5d66920457a057203fd84c7f583da1fd75205c3d24042417a17734c104518f4dfb3a2758c61e830b9c53c987a9cd782d40f64d53b128dc49772d559dd91ae6ee54fb43f4122b8c12118cc02062b8a880ccec7802dfa045fbe850ec04186809b86b07e928198ca3f84934c7db938cee12e79dd1bd65ac3efd4c0fff9ea69738f54cc915cb69f5dd222d70671df26cb59059b5bdceed523b38d366ff64d59ea01245c824f6a9517164967fde2ee433a2e2a9a864442c7a9cdffbc42f6b9e0fcbd267a39fa7e8bb991541aadbc3f5b8d85e90c7d453ad107e4d525dcd66c7cbf88eab323c9891e78eebf291d0016419a7e4e7449ffbca3c4e6e6e1bdd7c96226b3ec3726c4d46424c2ce72225e1d2d4aebcde48f327eab4a0df7ce7353d4a4b9903abc196dfe033c5b133b150c5f2174b005d2b60a304cba07e8762097309181b9ae3413882b85c4e7988beb3f9f4b9f7e947260fdc1e2b894d206d18b7d89926518b989f115268402f1da14ecd71c70d8748a5cce375a97c8be5b10adb784e460baf14f064f33153ac85982ab2abb14698083bef339abee8f3e251f4c53f0c23ee9a0a0c771756940802a0a367f4ea83ef000e924af6f241178adaced8d405071b01d5053da16db08382589375373e9a21d17086300cb118e3d359215c9d3a92ecee0280b24854d964288fae1e9894b6e7752ed9ba085e89a1934091b8d11b2f8121acbddb9e480a03e8abe62e8fd1c3ccecc3b3f40631e3e942351de038cbde8780036a5509ace3efa7b91dc8a4592e98ec9ac39386393f1d83facab83923516dfa605de82bef59a28dfa0fbb1a7c6cb1e36c017948720f6f655de99a14a81e47b0281936ea41428220056046abf0390c13bf8153bd963eaf750e4b874ae87173300c854e79f94d23370a60a527e8695458e0e714ce71bf1678470093af4d6b67f4955484474627e24c3f6cd1c3ac3876c28365684e5acdeae005a87d237835ee886cde76f8face187bf190de7eed02b396093560add81a5c9bd15d3efa1032b20731d296ce64d3e814fdc11051af6512792799d46fb605164b886ccdb3e242c652005fa74f05e5c879c00da0076ce6b39ae79a358e2dd30e890f10232f7af6878409069b3d1d19ea7c3b8ac59345df2c69120e67b3f650ebcfd49cf1da70283e51519a910578df54206e1a09d5a682b806404bacbeea0aea27abd1a3da9b46d799d59dc0c1b7a40eef932c141dc9c25e95abd8eba6cae46329a2dbc68eca6b088b08e3a4445b7f1c74ccf27c8bb0633a3bcef32211b1c0ec1bfbeb3c5a710071f37f334d5d1db3c6383318e1aea39acac88469ac18639bec385e61f1268262b969b0692e0c418011f0fe3f3e334d6da211b9079c207b08a71a8011a339e59f1ec6d704da11a1930d995b6a3be1e9fb79022fb67fcf274dcb4fbf1bd0b34d252b02aea7a5ea5e7eb3c134241635459e9ee26ef2fa4dde8043000bac960c2798d44b9b07415103ca903f11eb7283bbe17e71e2e270b9109ceebbc45a54f3a30714b57cd0c42ed089a5633860f8eca630488d8d5571e2708d8e6118240f5a96764e4ce5c8422a80d5e0cf43210b85bc5fc39d10fb077ba9d472679911220152ad9b6c20a94c5d6bd657318eee3fc886eb0c07e0cf066cfd8782700416dd03ccddb90a8fc186754e8056cc523bf758dcc3cbad14ab068a0ee5406cbce7598d55501a414826b879565865f481fd6a477c839b6185c92cf07b0941efe70e72fe07ea27f1f8992ec499d958a4e1b4c92d0d9a8da1c4e1815fdb38b72f8c99e9c7e0c8ae67301b0d7351afb68ffc6e0529f96adf20f8a6787dfd407f63644e8617cfa210ba55ea80c5c9fdd43b344af800af92a3a680e1a185c1da4328448e2b9d0bb39f251038e46220850a4b37e7b034bf11517818a3185893a6f6d509707baf57c4bc3b9ece6f2e57157a062cf8d1f7c23df248a4b14f9f94e33d4b0c631a0978f014e08ebfd9528bcefc4a2184c0eb716c1ebbea3ba6a0e1a0f2b3edeb34cdc4d79f33096168796c34be75e1d718a302212157ceb5f3754fe0022f8c58d32806018572242fa7efebd52fb3fb73bbcde4063108c3e1269a3274d842ea880f04e56192128e669c688deb307380179ec25a9802ea78ce53088706c40dce1228e98c8e6faa2ca2d679cba48f3807b22242aba8937f165511d6929d5858705961796e97c185032213f3957db4e0337ea72f9f9228e4f507ee667d6b7d6631f38d188535e19d925ccb8eb20ed2ec45b410c6f6d0399bab323bbf9588a968d6eb8e0bc03e135c63d564372f5217dad2aabb1230e5ad6c93fe90a9ecd1714afcc580da5ea29100ced67d1c55e0aeb33efb1599869ad86560abca3c0a9228612a4dc8dad2ae31dd079ac34bc1cc0d4a5986981b2e87ea3ad27edd6196ac72de8a022db0ac64c4b871c9c4123376f188fdf32ac0951a6b6407b59100ea6255d4020e8162fb5122fc2bd0fb4d5a95ed5e0482c6600c52376ca0680fadcf70358a332f643556dac3b31d048e934184407f3202a9ec7d5adb0a55e2d044de040b5575628f898361746c5368c1f18c3aaa901af6e3a2fa33ce87b3e980e699cc0991f1c12d1b0b0f204fad75ec2de54e4d32f209984aa068f25a0c3df95a004cdc7297cfd1fea1d6d02f985273ad2e8133ad8ad37bd1c817790259fff3e4ba8d7bed6f35adc18d4eefb96f2b4f716d869e1c01602f32276da30e680b1216290aece63a77d682680414967206437dca760d0a01d5144fcf5270fb95b20568d4ab034380540b9f8d160f8857b599125c2686246004fe25fd2617dbad3be5960752c9ff2d5dc280a00fc139fe23077cc6d6023f34d266406dd52f3f7a8718da2b6e424d4ffeee954249d4e56d414cd2ea334490031fd3b5f192eb13a78f149526ea7b04f31df046e062118a0838e73fbadfa15de560e350c1edd891a49cbef7e25b6430107c06c1780988981dffe0b9ab92d961c5683f560eaccdf819f80ea53c27d6afd6e82f78a06ed24b220f25c51123dcbafd27534732fe6ac87f01e2868049f873f38e4d4715510211ca2a96eda54fe446b97c07adad887a3c9c95b6a2288afca599f672dcf9ea03ad4f0bb40280bba768a0f694b806b20df86bd59058eb9ae62af5d4657de09a41918d8900c20e37d35eaa70867394635dc318cdace713a71651382ef128c0000369e74677e95c01cf60c0c3adcaf2bcc74eb100c4db0760e81ba00a0522e725f8079fc777ff280e6f70f3ace6c474477f593f3eab0502987e0e64c4dd2a5920c126ebf4338354c7c09275511d947127702492daadfbaaa71f5e9f0b55c297e8452a126fde37d501aef5d8c97125ebc8f7436c619501ab2ad50b4d188de6a83f611ac6faa37cb97a7c94883639d8c59ea58568c5f4db5252dc514d92066c9fa0783ba82f85d094d546cd999d92e737c963d6a8dec5c1d5a4b590afb7aaaf815aac8060ab346b827c1e48023e7518ed1328c9fedf6ba216cdce652b445e705f92f662a0cdca3dfcf69639ba0b37c7ee6ec6856b8791f111fba6a2523920d6e7f21c86d4b5287362c4e55775e43ffe77244f7a54d9214e3d7b7c225989e536c81e8b2edde3d08b2f8159b7e404468f73b5ffc634981026974b92c1a92d749b86b0edae1cca881c95c8542a786193f014b3bc870982efbf8dde1e10b7107f3bf53c8b070048e6492c566b9f78d38c0ae0e122346c5b264fef61da237c11dbfff89ae988da0245267d3ff2fbf80702f81126a1ed7439810c26c292000b7c68b81245e08fe0edcc597199e05dfbabe2e603488ac613d2ee93a1535d5e562ccc14da4b7c8452427c2f814311c59dd627b43d88821e429be44988b45cda01e6a6ba14288f01b6d657c7e6569d86404eb5c5f2bf479c91bf4b6c70cb11ed1d4940cde3a21923b955c647903a778620f50d1e1c6c753a8877a29406b6a0a76dfbdea6b192fa87dc314d50afd14d2488147a75b30374fd0bc41b100ada13cd01f645d0ec1564a8231527e9539e696ab2450d817586fcc12007d1d6277ef10ad5aa51621f6236e4a0e10206d5b934e3a26c26a7d7713215a369c355eb032f178617d98cb9c25cd011dc840e149650ace3704c377679f895580c376128a4d26b9a80bfaa69c3d49610a6bb05c3894160f550e5a6d33dcc12808052b521fee1d5cc774b5f0ca1e93d4bd0bb841dc317f77297c92e82b53c321aec9e0463ca35409e71deb668dd776e139246aa0de2ea46027593cc52276b4c0cc6abe70ad7f2c3bf0d6dd0dbda036a1e2cb777e16dff3651bd4076936912c2c1e54b5e4db0b88c025f6b0dd771ae9f25536f9d3b8e2c10fc34feb9b78740ee101c4117af9513f8b8e5f130d3f3de6944de645191d94af4a0caea3d88b972fb6318556dd2ebac5511564e00eb8911f06f14efbf4bf631683399bb51a6cde836c451e6314aa880546a55953ff66ac19c27c3a176df6b064cbb140348c97869be3c2893c04176644348dcc3b0e2d19e9bc667646dbe77aa84e27c300c915544baaa8108ec252c1189f1a5d0148d0b1847068d39650bc6351d131cbee20e0924521dfb92b710efc940b831e2b4dc459bd83dc011cee13f7577d016bc8eb7e3e445d91ce55c9dd586a7e129dae6f6449754bc3a1709249710aa13f21f1420e266761f3513e0bf99309654a569382f24f41ea64ee8a50fd7d8f30e79ef5d5514c9d13ec114e6eab688d2194c2de98bb885dead08a53067f94832dc6d82eaa36f9a0582c1c3ee18760da99a914c7d765286eb67f1ed3060b8a97fd3c890e31f60b51554db0385c7c10202e7d0a55326d917d34c119abf390cb7351c37f918be8b2addc15ce4c7c9aeeefd439e1677ea17d51d6a7bf42fbd3433123e3f760704f4e61b587e2315f58ad245b252c6b441bf441d1e337bfb1715aecebdf2868d6a5693176766c16a876ea4b388c7d2ec4677ebc3d99d1470ddcc3af141a2b695daadaf71ebc3de7b9730bb7eb79d49934160ac6350fcc70a466c2aa44e408e2cfb8d217380aca995548f62be452c4bef58284eb10d7183006cb52d514ceb9b2d93595e47e5d69cf750d61263251138d3be04a0d37359320fa1b6d93b5ca4dc68dc379df8cb28044c7cc3cde42f0f03678917e33b61b3981521e83c5721795e4bd1c8d87c2d9a87eb3335a4c7e434fb2d29f64dd6a90b349a4bcbedebd17b986944c936da8649fa640580d88dd08e99a3c95571120978a64354bcaa0e152330f3a40af99b54ed07d04e1835e6c13bdd26cbb7f18717a97573509a37b26f9fe0570050fa5c7703458960bc67cacf81deabbfcdf4f7c2dc2324f4e11422ced34d727d5e1bf5c60d43c86dce606f3c6970f2bf61ebd48fb8bd2d7ffc41b6ffbf3c0d1e6f32ba2436601e93df9ec02fc800bf54aa8b620a0dd0a40ab90e8cde2c1e7a333503590c79c9f1e5280390180908ee0a301739c044cb3ffe4412cd0103b605663fe29bc78a9c24f9409ef65eb7c188d28c288e6c5d0f17818b70fb73bd7fe57170b3923c85d4c022567435e697140c525a3133a28a6ef0775101765f4ce70b58e7716fa3477ba543f924475e8f2ab08d053754fbd78dec0eacc6739e64f9d5b10ab1653fb3cece3c4c420047ae4b8dba48ba42b50035baad037531e89de359b75e4637d743329437906d56b42f6331166dd6510a1818b51552b6b6b770089f301c2640e2cc937889b5683a9e01c5e53aa88a73a6cba95c44f9fefbf97da93355fa39bfc30064b8d5276b3cd5452cd7b6baa4ae258778261e786644ac31964edc1322195b38a9f74b74785a54759bf591618c3799abdd7d78d0754666fc4b2192d19bbf7ebd6321597269d8127efa4604800fcd3130a3ef69b8aa71dcf52d08587500c2fbc52142b047d24edd98dc68da2de097b15417c0dfde2bd27ee3542fcdaf48bf44e482f8bbf7f5cb10945f8a463f50ea84e18ce538adfbf4f6e1d372d61ee735b39b4b18fe83cb93a99af594068d142079c26ee03f95610aea51e854b5369b8ee22efb52de3993a85769d21f59e28bea9e14b0701fc7b9b0944bb1513dcce3bd5cbc561efd30406096a5ccf59372790b5decf085a16680dac16b6ec0192299b31eb5118a071724d0c9db93180feb0c3b3da0c6e3461cec5610aaad138887ef5d06ace81048ba0be9a5cf76002b86ceebc6d33be3a58b0cb01d7012eb5a561047c168464b4811c424d58861d047aff97baa7baf20109e3eafeab330c0d567e6586005105bbe4449dd45e3f7b207172403dfae469388945454fb897c1f76a7786733cc600b3f9052c5a73d8bdef08158e5466af39413df3145c776a9e3586b50105b960174c2cef114b1a762130cd0a1a15be621e8049c8ff448212b6a70ab6b3d6dd3d1da30c1a9e5a5a1e0bf166036be53846831434e4d7460177d5667536fbc8a536675a1040d101cf7e93a931fa182460f2da3811c99d1d24efe4e212aadf44d74c480b13634f2188c498d2dac0d2aea0e1180d279b21f35e15d1368986deff6ccf089608cebe704d78910a5ac478f442bac79a2bd6851c3ecdc5ca364b02281e44047b10ebb59878b08821427a65c1cf9b8e001fffe98b040f19ee9310d53328a50359bb0f7d497a9d548dda248bd047fcab1be8fce58a936c90e7e88fbb4f434459e47bba1e3e6347560407f5ed9cfc36915e003428040b249bec7f5ae5b5ab345a2a0e1d50e798e730a7872eee6e488c42e9241b526a3cce7a59d84fa6dac636bebc61a3ba425e4e68d8a8c23a6e0dca86b118945e37c49905802cb0aa4f1ca5c38a1610ef9c199ff94604c793e7fe4889e8b83f13b700d08f687433e48f2fd3609a858b7c8b096dfd3d9bac653e50f5da61013f3018303d62a4e470366e71c245b2951a42339238e141abc1c3b5f0be73cefc5f67501308623dc1308d83aa2dda762dac0cb50054344eeb36061fb3664bead35e8a193e86ecf62fdd6e9134eafc700da10edf7d8f1e2ae468d357a0a507a73d890efc81b93963ea8db641f680f8dfe63e25013c7b3a38ea171740a086a8d0ff392aca79175c20b826b331d484b72fc90ae9df044fe714177df48467aefb3be47baf99d6df926ea4629887d52cc27ad31a0146645471aab2da813168bac09a1d2aab234d1ea6dfedbee0aa6fa3ca15677d9b678ec559baeabbe38e37ff24b89f34d883f4b7b6fd8a87e39b89e6212caf331e70953f780f762c399583437c95ca571310ee7138615bbec210f0d7710304244b3cd920a74e537eca85d4d34640e1da77c0b57614de382d18249f4c445f1181fe9c2122310deb0f0b2d0391976e16aca4c1d3e3137d6ae88442d01e5422f2a164dec9be2a7bbc763b250740d98487c4613e5f16da09b9d8bedded209bde1de9d43c6edcc2e9f85be3e1683cc4c64188e2a4642f248139353685c26d1eb0b291779962e2d6d3d53a1adf1b745743835e1af588495586733a916d48cb880daa017266aad39045c31e27691f79b5bb6d6bda723d9fd60e04f095bcc83668cab06c0755ab3066e684c443720939d8e8522a025ede6ba613d6b05d5379d6a78759e9803edb29b32435947919d68b1c724149216e4a10e38571c4a825e590135a19f316fee463c6d361f554fb0cb352b83b8744dc6539fdfc73e31c65458b00fd47310d08c41f861748baed1afb3f1b1770aa6ef1d69b29bd854c7c6ca5f00309de64022d07d82a6751646ced3b744f4e2fe340db245cc736275bb8f6401655852c5861e5216a5ecdbcc3d306e66f8a54266c3742de60a984093e7855584b20bca97202e7ac7885c0e60a86b247ea3e78acac0041ad604e374ba0d718e3a861ae9679d271742826ad820a5c39de7e65f80acdb7100eb4a53a26575e30fe3d53aa5469cdfd3961972db4ca151ef905b4a55398888f0ccf094041fe3d5bb33121958f5efc3b6464a088cc01ee1ec70b3f3406dc72f3537c9d2fa174029815660af4159d17eb9702bf94f22f18d933f320b29afffca3d940317e6aed25bd99801feb588a7b244e2575644f7e29267476723facb3cf2d558145ec050ae81bb885b63fcbdadcc6d98fceea04e3bfee46b434065e0e8faf48d9d2e8ce322a718f8c9f18a9f19dcaebdf16857ec63a4f069d1ed0a251e9c2eb80a8b59c286d5469a42ec4cb9bd6864a42514ca2cbdecc0669e13ba23ff607e780725793b4fd165087dd4853e61c22237f42ae7801b95d082efd4967a16f247eb107926c56725e4e6a48b1c334208f8551d93020f4e13e43d257132a1523299ba810819872a2354a8b92358b3516291f7402c53ab74c2606c01676bb17a7671e30e23d55d58920f2e52308f6668014f18ffd6688441cea2a8f5e8b6f3910d5ef85968163b86b19a36523777cec55ad10750b5f42dea88b901f7faa18f0cb92470f379b0dd010f09c234121b2344f0b305822850d0ed96328c2fffae843755d432f287a44fc07409765910900039a1b673cf1944658ccbf3381f08181c209e9ef004dce543cf0db09810f317a374bfc20ab5d2c20f118095ce1678ce6f382dddc4898f711c7094e8a75c9cc8d41617eefe288053f57f4a07b70e882694e791c319cf705cca51b371a2cc370f935d33bce6da6a1ab2ffe807acdae95a424f06445012b52ea7a7523aba04a7d35e7900ed4b087a1af6b2c68746bef8cf21cd1136277b85cebe87329a83a8561980e7c1a3e2a7c380c811e7c6246cff78f5269f55a2ded67265680fb5e5400835929b49a651d5c9731be456bb5feda4929b57ce72e4d7ce956fff1eb47b80b1207b18130b93c1f74ce4cf5020e70e3fba597f80432a2741cd223ddf81b0219b6ec0f4c76744d52ada2a3798ad469f3c59f1a9a1b1861fa6762e2eb993fdbdc6c82c7525f89c6ddcc7ea5447b14eb762073dd44dfb1a8262c9724dcb26593e95ec742d1aa5e0904f05526cc8797c06b5a5d325b062483d77f4b208e7cbfc669efa20c66abd025c29617693061db0920f28c4d0448a69e649718f7515c9077a6c18b115794e7a931737e214b5a6a106f70021162f425c9810ff1f759109abf450639224e84c642ea38df3595a45cc5086e1576c91d5a022f90f22ed1e3c3074a648d7985147f13981e5bcd06fcbbcdf15b8ce574e0ffd5f0618466c672be487da6b6ff08cdd3135d17f50fd78547657e3e5e8bd819a465020d750018174efe37583d2276f0f21140fa1188619239e51e74c36e6851cc35cfb5d73f7d30b3cc80df11e8cccaf361751e991bf03b9e64f0c1d5e6a409bc7310d6f2612b0993e0964b59bbc1eb0dfa1bad2db7329a6966b54a4bc40da31cc78f5c4c763de0493231957aee3e314f69eb9120391558dac7df33878e98f6c65fbef0eb711bc4a64b8ec57279fba51aeed2386e741fd4973af8c57f5a3fa8a3c56c23003db6a30456283d7e15c8ddb74995d8fb6f5f3fda6b3859c54bdc11a38e79859f9bf23a8e12102633970550c31f0d2b1ffa4601bd4591dc198393fdedcfbfc4ab86737d47dac411270a6877b7f43fcced4059788a98419034efa0935545f0d3710bce0052618d8a3247a974df6205bf6a62219e5e1304296e6f78f61c981b33d1204cc765db3394f9b120300312fda7f7256822a5f22b2737333500371c4f0d398ccedd0a37e73779fd6168c179a375b75af6f3e87bd1a0810be5146754aa34770bcfb9a69d77fe729ee67c0bca84d4482954b199c286909ec0db935660a74005afe1cb339aea226d524bc2e3601ae86a4b8fc62da7bdb7fa8865bbab955832764d7957acfae9390b8a8efde92be5067850fbdd4803ecb81ac3eb0c02f0749ef0bf9a76073f8f4f74cb0a0081eea7e745064869052ad5ef162045f76fa57d807da839ee551cc90d1a31c5bc1fefc26a6f68efacb445d8c53e83656d0395192f2fae66ea3f7428569a43757e5965f0d0db0d5f3a464035bd68575aeb5e86ef2b5dfedaac28bcef4bee48a7c0e968136c50d1bee09c0cd00e3873e682816f62ff009cf0b4d5e775b2d3ca145000b6be6072379980340d21a57574fdb275dc02be0d68e2765c74a2933719fc3bb7acd6481f894c44694b15087c7cbfb38edb248c1430b1bc6c31bc9e0149387fbeab14fe04f58ffb08980cea3e8963ddfbfaa12c39a4531274c53519990f85c3c6ddb9bdded671fc8a2b3226d8e9b631e670660090ee8c1b7ee778808046735a3172bc4f84a909e8f99f8b20a638dddfe715cbecd63526ecab86b0be8e2315ffa04795967457ecb31f3cc79bc853db6f57b1b893e348097d048e6fcea3212f7939be3fd2bf729b6b9b4d03291d5440f6aa4b9f3800e3ae71a912c936e21045fabb3ac012daee51c287d862fe6e9bea068a8282e4298c5dd5d700f1d186d66d265083802e3833d22bc4c8bffaa7e5a13fdf0d4d8cb62f3bcc2d082d0d80686bad0b22bfecb6d372d9a23b3558cb21204bbb71b0391bff29eaefd7d3f9ab6c246ce90670f946ae04cc7818404670e479602162168a82f3b5633df361d60e08631efd97914eb0c6c4a3dab269be6cc1c254e706033ef55d074318aeeace4d0d05098914136e8b39db318688642d515a41f5a5e9926316b8e883301c49013692aa450bb725e5607385a4d296cd540ad7cfe8537d5bc407471e6247efa5571c79aa403ff88fd0c4871f579ada4a7fcf2b97c8902a93cc914e7b9a9278d53980effaf8dc8d352675b24059f16e887d9695f4b6dde500bf4e4c9ad42d9ab94e464f5f0233008d74dd500077669486db05246f0940938c7c7300384baff0b080131542f040481be06d70fea2cca91558cbd6d2b3518848d016bc9090b79a5db829c61b555c4f82828b3ee22345b7ba32a5aacb5d34e7b6d19d0ac09155cec81296498843fd988e3249721570b9d01d051f8e1eaed0822b9dd9e333fdd79e650abab159cc4c5ec9a5d2be567feffe728747480d616babc7a47f3a8ca3a532fe30e9864bfd7282e87644854999c1dbaf169f7e5db37b4495c2ee1e5ba3df90a170a1a7944d34c8e473c3e4903adb0da13327991b63b2a599b9a8d0ccf685c8ec10f77f694e7cad8255e91c389056ad3d459caf7669937cd6144bb273f004b95b6cf01a4da2dec2190248c272995bb7bb2b44c096be66a1da0facba20b676116d610d5dc44ea690cb29723b039b293cdda211f844ba9d350aff9db605abde98cee9e43c83f33e8b057559e813f1e5cb0b0b02a4c600c7b72e0bf3ccc687e702b2839fbac06127e74a0bea944ea69ec244856fb7757fb2ae4435f130144ba96ffa46e1439250c6641076c7a8ba27b4043bad1a8ae37a99499ed0ddeb7cc5574fbd85ef411ba2a3b461428439fd05f9d39308fc2e76f12d65dc01530912002f8094a9f5bae6af3cff7f485ad64221e6a514432ff02c95b9298fd89eb552d08ad34aec8e150a0b5b4804a92e95f7bce0724bca9c18c493baf9f176bbdd6b82e02d06cb14bc622ca5f1e298106d19966d7bd6854b12d3fc9f10ad91944754a3ebd6c1a3c8d4f2cc8d398257d09b866060b787abca334d11a78058df4392e8ceacade9673b403120314c2afe32628e4f3234c0157b8969fb4170f63c608c79a3256b8421a395911137d285022be812c00a571713333f53d52d45401d2d0e78d828d05c93733868ba43528643bf39021b8a1126de0646be2405c7118c5819e378c1ce121ea3fc8b07674d6d586596ebb3c350d1fad2ac466212c0d834798cf8e2487e3bc1d83dba2a090afc820e7c6c88f9bae35a06ddb85267349c797d672e321bd77797a3116a173d27741514bbb06dbeadc32f2b4599b0da5f07a4510e617c6324550a308aedfaa2d0c49631a8a6c54f7aec30c284815c40ca9886267c106ec449c1cf6ff622441023309e6b7b66a259a33a6afabe1c9de07eee3f5872be760d4b91c7dea23d92b283d54338393910ae5b2556aaf96ae80aafb1aa86a7feadba2470b29fb3217cf40b100350a4dc7a085fb712e4891206c59d3321bdd2e9e041f2313935fca051610473d816839878111be37988d9cfdde1ee4b93247a919ab3139c5861a675bd9ca3702ee14d9b762ed233890c116c26c8df3706eaa2fadc2502172ccc8df5b94851864426558dc859970852676132bd8abe2a695b35a8434a656dae4043c42a17eb307acba4f1d761d2362dda7f4d1ff21fba4b9c3dde04862225182dfba3046a687d49d423fb1f0941b22f0895fc07d26f9855e1345447082f3f5c9140fad1077e6c5be0359879bd0d972cbbcda537fe765f6f8dfb620b4cb99624dbefc1c302f68633c3b5c0351fd13ed3e3e84f910789dcf9c049efd5e9192e090fa57f54a8bb6a661a96372997625ed9d2093fba2de43eb694d4729c200e41035384371ace39df221a8cad89ad94fd145b0a01ec760a0cf6088b3f70d1ceaa1a5085c1ad6ae91a4a69a2c4a94983a9b6b33169b909ac54a4fb2626062bac4abac27d786db7016188e82dfd582d5bb68a9bd93c5eeefae1b8a9755193023e43a143e74dc938372dd6999a49a9fd08322d84f7a26f8dab819c4661520f3e4b08148e4a293ae20a78ed4a02c59936d5494f61ce825218a72428ae8302bf988a603f0b670c9ca58faa2705ccd5f07ce2464b9b1716ec8547ca0516bec2208fda681dde93505e8741665b9090a92f1ad52891a73a12608548f3cd9272a22a058b5f173eb8f803c3bac30f8fd7d31b119a3b4ca60a00027d1b5d977dc77f64508789b2ec3bd40594da4d219b06948960ca8ff3bbdbb178412c6048e50bce58f40e64109f72510d24d87b130cd2f3749959a10a116caa1491bea492e48ad8354d13446368650b3cf81bfb2a8a637d1437bf4f16cdf202abb081b112e04087a2e6824d75e9507092a3023c7416e12a3d01c298904370375f3b2b0b75c4b5b76abd75bdf1309447cd0e5ffcc3f399111eeda97089a9722be42bd461df015a7872975506291d15e483471605c24538fb3fff3bc63fd325089264d7b968f72f19daacd4b8256f9fb808da3c4a7e26b7685699afbd9c480a0b87afdf0f9085a54824dfb7dfe768c0112f20deb9dd803f98d37cc8c8754ff9016a5782c1e906088feef34d4488ac25a25886be8c03c60bf8147ea628ffdb4a0169e509df98f2e03312630205c82234c7520d8d6799c368936d47ff3814a5b84fc86b950b4dd007cce908dfb8034b3a81f8c44c063d75f34f604f39deaec7dd9d40de58df1d78a258afcf87b884225fa6b5ba5e12efe77cbbd005513c4453fde385067548b32c2cb635e2e2b68d96c19c54a4c33fbaf33ccb7f4eecbb0b770ab64b89e9d6239952d0928ff93de10c5151b9849886f97851e97bc10c5d941c693c478fcb95d61192142d408b58f0f4b4935d8d96ed108372c077e70b350ac0b1e931c251378224d80a4d81e1b4130fc151e4bb55b80bf35aa1916aa716b488fe0f0b046ed39d90abe74f30cc7aa816d38c1ac554f823c7e2a97c211ec7cd47251407534ded7d5a9befc03ec1593a66b3de56f9fc9276c4797df7b2a39484ac08a17e21e1a3ad27f9fb7ffa6c2c3185647fb5b0c8a21614e9be4f94e94555bc22d8682b82a9caac376e6dfec5ffe6f05c583651760252ddab743c9cb8debb0b44c8afab794936f7b32b400e30ae7e8f1bdf090751b533d9386ffd092c45ed003e42f1545c913f28773e4d9a768fa5fd253a38688cac7c5c5e9eb70261ae22266266a025ad990c7d338380ac780e2a9b4a61bfb8a8dd4ba046cd0a0cb8325a028e4f8e6655886fb66cf817fb04c8636df220e59096bcc965ba24c2971840385ae74acb2f7da5377fa19b69e8c8c13ed42d8985c7077988815b32438700eb792f698e9599a8813ebeb301cb1e57b82f7066044a8198e97fb9c8b17ba2db9d2050ed5ab3376b3f1cfe58775de5abaaa7e106e0fb005edd5b926ff0dfb80890a20a858822baa4c1983a04086be6afe55b40e6a349b2f885f48af3b1880ffaa3a55ae2bc796b41ec4b042e1684680d5920923252073580551cb8131241d84541088f2e0a3a53521923719a9bc3b8c6b07e59e32995be763e8fa5396bb145323e07838922c2469c35d984740ee89cdf158b4b899dd27fbf37bc831f32ce8a9446f6de7befbdb74c49ca570dd20cf70c9f459cdb9c19d451c2a54a38f1b4b1511ca791511c7f72cbcdcd589f6f8d071cc29f6c34ef20cdc946731acd538ee2f88be6cc8ce2f8098cf153299146669e7a954a9d431aa7c657cd5451648454b65b167f95169514152d258e34b21ccd0c0c9a2734517c467572e4c0a71cd521b1ff6892f8d46526770283ea34d5e9f3851346fd3760449dbdf2b3e461c0a790f491efa72f5bf234e05348eaeffcce498391af4f5f411c34294662d2170b267dcd981904c1cc208d19430dc1cc26eb94c3fe3ccc187a87994d1c66b649e4f0cdac7a02fbabccbcc30aecffc198315411cc6cf630b34d02c7cc3702664f7d2de0bf574e1a31e9eb48d881eec327fdf7a00c8dcb27bd2d87745ffa67950a0b92f7d9c162c4f4b1d22bb7c282ea4b112bddca5fba159e66955eb9100b2a1b12d55111d88e50b7f294236feb569e62e4a85b79ca0aff9d66cd84752b4fe1f9f034856644de5f1565c8fbab8084bcbf4a0a02de5f4533df5f45e5df5f5553bdbfaa8acdfbab6eddca017813de5f95a55b1900af43d4ad5cc29b372dddca3fdecca2c21de0fd555b5eefaf2ac2efafe212e4fd55395517f0fd55462a2f61ac5bb9f5ae9f6e651f4f826c842704981920ca0940059002843643055513abd8f7ff5bb772ce8bf0fe9fa55b7900dfe3fd9f08e7fd5f0b8ff77f5cb7f27dffdfd2ad6c7aff2fea568679ffe7d2ad8ce3fd3fd7ad9ce375bcff77e95696f99af77fa36ee5984fbdff7ba1797f33d6ad2c800fe1fdcd9f6ee5d5b3dedf94752bdf3c08ef6f3ee9563e7f478f7d39e0fdcd99ca345bf8cf1c27f0a23913814f993a4c25df03fecd47f2ff038909a7125045841c72e60fc9cc9f79c8ab9098f0a953c945f22335430111c9dbb4f02c9c3e4159c7eb9c4ac03cf3259420e4ccaa7f04d894702ab9978512de274875669d6fc89911f0259c4a4013ce5c0288c4049d572151a9ce6cbe0d1295cdfb0421e0cc5390bc092c9480e47fb050c28ff709d239b3ea859867b67904fc5380851990859438bec9c2f77f2a2159307f0a0b9fa2c00c0b2f52c0049df7091acf6c3e91efccff431e34c11d02e4d4bb4e25e0ceccfb041d60e77566f0bf9df1d3b173809fd921c06be6470980671effdbf970026091d32728bb9e8453099875fc08237c0f060943127e8453c92df23e41df995d1f9e4abe2fc43fc2a9043cc09947007782b8fedbf9be338b1fee7ce1fb04e1334fd9f9204546d879029c4aee0804f81e871fe4410988249cd9f5af223ac02224a4668a804546f07d825c679e52e44990808e229f92808b84f7099a39f3f80710cf0cfeeb5126cac8e9139451c83c5e8453096844c7fb0419e08433a36644118cf038730aafc3088f1374fc0c0450679e79d1c8db08a08e9c3e41d900b907284201ce2c3e0a88b5229c5984c7e9f13e41e299817c283ccea9044ce1cc38a091020079d188289e39f5d6c869834f90e81384c299a718f9021cc131f23c8ee0f0789f20206716bf00f6813c0a0206f8138e88001e39e1c8cc99538f3a32837a9f20039c79ca913f0102221cf91e1010a1c7fb04e938f3cc1be0cca83fe16b92f0ec389580200a21d4a47874a41210c2a9e4f2a880828e53c987cface3533c4f632af9f09407d4ac70fa04e51432eb413895802180508383862604d6994378104e25770556eacc305f021e07cf8e57e15402f2e080f9144f2a45c393a2799fa0049c790acf83b0c20e9ec7b102083bde2708e6cca987a1791c5f732af9f09424a614403c658510c01550b86f5a41c7994d5f732a21f169834f908e9af7094ae1cc53567814928470e629b17fadf0ac53c9c536f80485c07a9fa07b661d7f4d67ae21324408020ef0c2410c70420180986006e0754c007c09e6cdff30cf77992d124c1ff5e5801fc1bc790298e78b60e6f43007d02f073c8e79f33cccf3433005c032573dfe72c0e9131480d3b4f5ca01709a587ae50f8279734e49f23bccf39c12fb5792d3bc39cbdf3473fc9b32af3263dec63cbd0e33c7d798329f32639ec63cfd8c99e351a6cc8b66cc5bf3f4a399e334817ae50f9a32a729a557fe9f19739ab4d36952e9953f0b66914762eefc0a6691e73177fe8859e4346b3ba729e42cff9229621ca687051329bc6018f39b22968512d8643a1426b732f0353d1d9e30e201a6650ad4864ba739eb95ff204d18ec8f441a8bff9da1991f7f9fc9f07bfc7daef93efc20fec13003ee3e2fc11d06df474eb7421b9ae0d3f203bbfbceeecd59b74c2077edbcbf49735791f7376bee32f2fea690bb8ebcbf69cbe60dfbc330c5474c1a9a1193a6884933db3169645cdc4c235311f5cabf01a66ae62c7f06982a2067f92fc054d19ce5af0053252445b7e55af3abc1cdaa197612a64342349c5534ecaad9006296a4d04968a3753388ebdf24d8bedd99ddddbd3731edee52773748bd3a8e38dc553f08d626797ad5e009762008762058bbdad5ae56b05a13f47610fcceb305d0a38d2d3808daf7b301e07f592e4882eb7567925e9d3b9f15974559fd2294d04d7174b88f332d6a3368985e3163868730bd528616bcea16e87d1a975e29030bb6dd42afba855ef55be97eb8a00b9616edb4e0aefaea6e411738ffc8ab0690d83b4133bfe0df5bc11e160cd67f8162c11d78839b4118090523bdc98fe910135670bf87e9101334dc67beb88a980e3961c26404eab4b859a489455017118dead4dc13454e884974594383fb38868ab348c3eedfa51191c859fe37ecfd41da9a1e1ea9c98a4b87ba88e12cceba95c52da36c76d4ad3cc244a36e65f1688cd22d58b7e8101755b0ffe8d3e217b1a85b74880820ec2f7269577d8d30714bb7baf71974c0dd0942b91d6e22256edc67071412e1d92c0ad06d0626858c2e22ad8a902d0b4da48935918a586504f28c18e94dee8cb8b8ddd756e784aefc73d7a47b82fdadd013f69a680fa738fe79c494c430d437725c8ae3368ae35fcd4e46714a1dada37554ba5a879375559e74b30e4b77eb88ba2d9d503703ea562e75513a2954a7df28053730ecdd8b823646365eb8601cd85f05a4a2d22e5491cd1731b4cc08a97e54320b13630d9c5533a03182c059254505c3feb6f68c50b7f20cced6ad0cce5899c152aa70aa6d6163e42efa1ae2ed449cc96e3f4e76e6992223dd6b06c7459ec11561df6144715c9c9d6aa72a27a153d1a94bbb646c272b5da294804e594e4427dc89cbc956022ad1ba95c559494a89ca0e20aa53dfc519d5e9f7103bca0b5411c5f1534de6aeef297d2b24ceb0dbe49ce55fcb22ddcbb34ee03c83b391511cdc0cce596e636473067585ef6f6344752af859d5e4fbac6282bdcf366c60ff19217785a78d91b3fc4114eab3319a11f26c8c6684b238cb01ce3b8690612f01617f7aee00a2385bae794282e2f8cfe0b07faa9bc175abc3fe335da860ff99a26ec9607f9a58b79eb4ab03f29fe1d22d7a258d1b769b192f47d8bda8cb159c6960d8cb2e4538d3fc609fc961ffce561ba3da51b7f209091b2fddaa2cc06e04ce365f1660310a5b9c6ad8ff8404d5b9817993de4803b7461a4ef12dc57c03ce7a653b0ab6f5fb473429ae74ac4be0d1f481c70e4d7a458d30302d09803bd62b1047ad20683299683099faa79f604b021e3f3234c758af6c772199f566d62c95c859f649b36ee995adb82737f7d197549f35cb9534bae01bc5b1dfe4b651e77ab5806bb13f89a90d871bd4182247754a6fbf7feed730a8ab028122593ee969ee9957b79aa338350c4a3af3cb00db66229eb9b20154cf006770fb69e7b56bad5d9d7ae79d77de75d5ebbe69fffcdcdcb95be7faedcd82200aceb20f82f7f6bf5afd98627a232fbdb28f82bbea91b3ecd77e340d5b7acb3577cbb9e56e8fb3dd59bb8c2db835d72d297d42ec5fa6b36691d22b1c6ea59d368dfba9517320786fcdb92f291b47199ee7d3abba02d1466bf56a9dcf0fa61dac57d607975628c6f2884b3f773022b09771e9d7bca46b858da2ecf6f7090e81614f88e278252fbdf228156d25a35e79a5a35e791fa479f8708944cef2be8a44eea2215a7d77e6475591c8d5d528cb7b94941be509d6a1cb8186087b1f0373178d2dc7c4c092c9cd34361a2ced2a19659a2cd87bef881ff07dfa04f0bdbfb06ec5c06aa6ec77d85ad1864727b24daef717467546ec3d4d91eb8558322ebdc28508978cba65dffbd251b72af6bee4a55bd5fbfc037b9f738ec0de671c5dcc59de5b77816fdf444de8a8d41ededb07a94ecc0db6a20dd7ba98bbba9ab3a85413686cb587f728d4bdb9bdae86bda7b15513bad853aaf3d1d8868c40344531ec3d68ca7eb0f7344533ecbdf75d2ca609f6be868aa1dd0e284607ecbded6231343bb86ebd52eb02612ffad5fdad696b14c733484406c7c0288e949b4522ec7dbe30ec61ef4522aa1383bdb74960fbd926a192519dfa9e149c496c698ab0f73445345cda05bef75f7db05612e9f0c3ca303ccfeb9ede649a1aaeeff85b81686b211ff86e1b8f4e76a6c51fbe3fb171766354405ba5a28e018a914275bab403baaa3140b896ba509cfaa51dd016a5395c2aea56898cce750b2c19957245dc7c67b959b7c06e7d343cb83149784310e1fa370515e80e4175c4af7fa3d09678055d41a12d91a857f5633a356e4657722e2852021bd175a6876b3964c430d43788c46018ea1b43643aec47385fa23b8479ef8ce2d49b82caba82b66e1491a85b170aa5f5a6c6152317e49428c2b5b2f23582268c9b455cbf11be88a33add33c1f54f372462a8f8cea84efddc31c9de1048e0f08ca1e2ec0d81eb77015d0dd7c666e8e9cedc7dfd7e1af07385755f777ad503cb75865eb5574da825f882f8d54a85f54a347bf00c675cef51e855e7f1bd91c4a23985fc9e7691df3f43b76cfd69565bb9dfe3192aacc670f73b3b7062a0869b97d41ef5eb9357d8b8c286143c365993f4aa056ccf16407ce617c8cf6c210909eef799128940af5a063cd6af0fbe40e2ef6cc18bab42f8de995f00323ad9959e0bf52d083e117ff004f281ba04775f8574ffa457668dd22b201910b7cce1e61f3ffa4b2617656f4d6c1215782c5910db47c0009ecc60044070a922c3e2659308810a377239a04931dbe2556b46264ca9097604d86bffb0461a389c83a94d46c34d6838044c876251b07d7a93abf7bde77d0f82f7a250be72f0b39d9db9761688daa3bea556466bf5afefa590ea990a400103ee229d07eeef6e70bff79daeb35a0337d31ca6380c64b0669a4b42bf69adeea09dd1d6ed15bd6137813e2723bdc96e841dd411dfa6b2dca6d61f603faa2cd0ac63d3d3e2b14917cdc6a327d42bb7829d8a0903d3d676d32a4deb551bb5b64680fd69ba2937bbcd76732277e17857ccfb7b51b7b878ce853c4abbe8cb7f3409b144c01c1ff52443774f0f9a3018e045d3c3e069cae94d667dc59897b2c0c7618294053e4cad270c33e05a4f1fa429752c26759653f11aad5725a6b61bd8c06e7aecc79256343b3cc68004e64d4fc600be0cb22a045df9c7d096cfee89ea8c5f69a7c77ae56f327b70bce949f301d574d90fb7fe8fac57ee58288ebf0b517a7f9ff5fbe330837860def4b41422d623cff5a81e75eb4bb7c41864b8b87ef8f58b63f19b67e95514b4877f35c7ae2b85d42fbd72583dead5eb9abebf1e99dedf6bdd3a39269d3174a5997d8a98ff74ab7e9b20f698bbc097bfcbec8fb66d3b4ee155e7141c6f4d1083a591a07e1c676a07c7e3387bea8b06d3c338a6258fe961fe7e53e926d455893a46754aef60c9ec1891bbba0965f9bb19d887c0de50b0b713ec26c5e50c6ec55d575b6bb5b576b72debd9547eeeca1d7410bcbf02cfbccac939385ad64f7ae53f83bb9ae8c72de7c72ae76347df3fbd72b7d91c0b8eb36a6996bf1355c782c3f59bdb88f0a706ce6eaba1ac5aab8f0be576cc5d57063aabbec93c750b48afaa1b91da4668a437200882407a453f7fdff799a3936eedf7f6ab3dc1cf49b2ef86878f1a2fea3c6b3bdb759d0d8188b6f3c20acef07de0f9bdfd4eaf938286b5d652ef8430dda9b5d6026181d947f7e985fb35d4755d342d6581240665c0deafc01ffb52153efc819f9ff6878f5e754e941c106c7ab1b760d7c8e9ac6799107fe86823cbf83933e881417443d7755d37a44689a5061eb0f480038b0a9c98702ed7e82200985e594308ebc0f4ca1a42f8000a212bdda25008cdb0bfc7a53da36e51289af8c19e974f865345be0f3f253ef862ca9e48be25f6c5fae3d9d3b364865b120c90148742ee96a3386b7af1edc9d3533ac29eb2a199b267aa48f8e223a93f9e348c5fff4c591ab277f432fd0f52fc9e209ef24d670fca59ce437e79f6acc81f28b0b40257d843547d6a91883f9e3404f1947e7c1a4c2f3e92f1cb2f4f1a4a3f3e7de51f0f20bf2c99296b3253f6a4f74593f430bfe4d5550847ec74f654da4c881683b9a4eecf7516439379d42f79e578fa227dae42305f8f9ce5ff3d413ce39bbe27487ce7293dccd953794c5f3a7baae93c7b723df55671e24dc48db51e617f92d966aafb2f55e4fbfe547722f9be5348c0222da878510308067b911ee68378ee871f864f7a9ef0493187397b3a76366d167eee58d8339ac77247b15c1fa1cc1ca68c196352677962ae8fc6a7aff1b3c74edfdf13c413fefd9e201e1c1f7e8f7fcc002f98279d3d413ca43f9d3da727f1dc279d3da7b32faea785ce1bea0ba66fa6a72fd3e716329da9ee97bcbca35be9e9ab74a6ba73c7873acb2b7296ff9297971b87fd63985cfa4a21a9aff0eb2b0ce2217de94b674f50aa48e94dcf537ad3d91364bf873aab644f9ed2dfb3c767ee424e2b72d1ccd197f8d967e2494310cff8e2d357761a0de399e2f9f2c5338584fcf2c9b32728b523be11fa124f24de11106398c0043718ece53db17e912f82f1cb33c513be985d08fbd760b9f4857396bb5bd161892aaae00283bde88b3c6910c178c24aa3201cb8e0053218ec157ef8d971d8fff4c42d808f0dd32970f61caef9e086294c89b08d617fd0f423d38f2e7d89af8ab31f7d329825b64f68dc8ebabcb6edd62b65854a407cea16f5eeebda76e419ee7965032cee4edbb786fb75358c3a3315e53226882975755d77f2707c30a029bc62692491654992c69278431308f3e1f04e5d8c95f11c3d43d219140d09ec157d1fb9b8d759efeef6bc73ebdecdc5d82d7274b3d28c4eba89c239bc320570fc3eb3a3acd203b84c01044937718ce2c52339924878248d25d24d128f7834916e8a78c4417e267db919e211c7781a63c61c638e1135ce8c3533291a92870037a542bc07f7d7efdab178274c44182b64b53d6eb8dfdddd99a0b9bbbbdb19dc48e0fe8a806eed509d8a7fd0d91141324023056b0c61c4cb91c0de822e3cecee5e832b0d777b3780e1f77d679d41ccb3e5402a9e7432c87d5efcd827709e11193f5eeb8cd8c0dff79d50354bef0df8ab4fc8fc0c20f0477b856db38bf2724f68187d3da2e1755e1a5a8881d1c86d51051a339c3f478c22dc34c7c78f9cee078a2507b7cdf951695e37f9fd0796fe9874c575f0ddacf85ad0adbb5b775b12c51f45b1646d78c3ff36408db00be17ff75e4a73a02e98d3a3b49f544a603a69b3b24be98174c5754cff065ebcb3b5fb4692ac2591efc31fa3f4aa814eb7eed6ad7b674b77bcd7cca4b5b5bb4f30d4eeac513a2925917fcaab74ebd42b777721775bbfdfba95a5dfdd5f2debd9ad63fdd34ffa3b4a7fd9673411d5a958a47ddfb2ccd0dca439ab49575c0fc76071f619f6a7659d59fb6ee6d19b866929a42b7d9d8d7fadb8b96f7deb565e12962a94ce54c92a71af694b2625edbb1837835fb67fbf4893e3a6f59e64d230483651be586c21fea1e9f9d995441ae7509cfeaece6a942eb83fdc5f6f554bbb4c597f95d22d8a05072db8ab08e1f61a96181a38571b6e124b1312e05cb3e0eeac3b50b7728ed3ba959b08cfba45b1c4b8e07e8fd2ad6ac31138bb14dc41383b15dcdfe5f8f826ea16bd628610ee6f2dddaa3bac56f6cdd50ed745ece7eabbc1cd9fbaf804c57d5c650ace9586bba657fdde2fc6cd282fb2211ae94d1e800dbb91234fff05fc7da60b2f9cb048b2715d101ff5a3090affec67cf9c5f78e1c75ac3174e2e7c8f7a5a0e417daf9cc877a23e34f309873f36d98343d1ac988e1fea5cf5ea3f3672b8fb2e851189c9f32c982be4f0f8a81b1e1fd5060e534cf18f1fa8cfe9d50f14dc91888f21e0aff0b534529fe76b6984be7c76bec87747dc3a1a317b8c7c913f62f61cf99d6f1d9f92c1b18e332fc1366f73d652888eb3ff83b48e244f55339b368ef0f8363f7a576570c557cd5440bd127ff4a48c1e59cbb1b4829b5533aaa303e340c05d0f78aac2d2ad940aa857ae9272ea02f670b36a769e724e5c08bc2af5e3dbd8bcff29e7ae9efa82c099129fbe20f02204ccfb38ab664ab88171cc9adbb579b114629a4a052cc136a659b12d5518dfe64ca930becddb9c2edcd393f2148f95b4b1b1b1b9cbc765412d7ae5e8a8ce607f15c5b921644305fbd7530e8477103e9704c93fe06b894401ff8013d3cc7ae545cc144f91d92f79ed98299e9d7fc03fe0eca101fa2534326775485c1a769e85a7a1c823a9e590c66211d75f343da05ef98e495f45524878789ebe583053485658e1e96bec91969e945e8de3a9aa4d6e4daf50417c6cd203ea16bdb2031fecef49e9161d7fecba1c7fc7a4afb1b6946e8da7bf07d42d1def3fda3c2d85a8a2e83053e3573c7e1233359e34b217cd9366f97fb8abb23955b35e753e3775dacfaa99a53829a1268b98f4b5933a73cd8be73f2a09189ec1144b12328ca4bf8701bfc2d9c3c2ef7c2d91d4ef69c0f39c3d481e1681a701c9d7df316111781a5828425f11f8209d4259a49f85e731ebaf603e9255af3c50b1f8a449f13561dee633a82325a69e762b358ea9d409f66afc5aa6be5ba9332fc137b41b5ab752ef7f43a54ff0a757b0bfea7710fdab66eeda09c3302c8b742fd50c85ea685e0a11532991c4a9077b957a5a8a9ffad44eca9c528486c747fd581b856a638647f264a375341a9989864ab7f28c91c94423eb564733a3a9d108d9b0f8d364a1894203649a31ea5656cd668eba95bbdb8c97992f5eb900ab9aedb85db3c79ba6084fa9e99a3025dcad99aef9a34716316f68ce727aa542c1fe1d0d04efed68453a279868e6b348e4a76a36a392d9d8ca12ee948bf1e956beb553eed4e56414138b91c5cc62a2c448f1723a12c75bbb42ddca25dcad72ad4481fd71bc8d8dea88bfe3696454674ce9b0116b25dcad459145afe6ae93cd59fe62cd1477549f6f6d0711c5f99dd15401519cf0dde6d471a6ce0cd2c8529f73601e17c43c9e52d70c8fa791d138a13ae3fbcf18a5ce9cc3e6cc57c78c512a07ce25dcc9e6ae120e756d70ec0859c023c23662a67c4cc4264622ac131d4c3d301123cba27402060e9915af872eac932ba2cc0c981ea6383989f2f5c0c3c98a5076a5e4440b0e1918600f44e0a8d5dd6bedee76977db9b741cf2875b2eeeef6fab3fa3b24eba7419a09196ecff3aa15c25d8c07166ec1fbff51a81b005a93013d5101c0b9abddae06329cbb9ae9e1b149ea1685bad71a79de574a47c7fe6d3f9b70776f8d1c04fb658ddcd5c99cd50f82f71ae17e2fd6addc71e96cddca5ecc83752b77b5eea85bd983793f5dadbfabd29e4f7f97ebfaaae1ee72b8e5b6f313847231ad955ad2141089bfc0b34704af2f64612772d704d7ef64f54dd40cae3213ae9e6c06365c29143bd0308542872d459842f173c35646756aae67387070b395b50facc86030188cc9eb7e57eb84dc45a2810da490d9dcd536d8cd5df5e55668b10116d8ab7e87f3169c85b345ee12bdf8608d2c3977552318ec5541d17adeb9e668ee598d72991a155518ae63ed80aa0cd73377b43ac3355b19b64245f5bb9ccc5d3044417081bdeadb9991bb6acc13b2409d91b5b98bbe4ca04209dbcb3b7317ebcedcc950385b5937f33377b03eb33df26c9d96d1a365cd76d6764ec086c6d8f205183b58f1b256b0fddb05db2b2c304a62688717e011d3a11d6a385fdc658cf4867e4daf6c7033bda190a45b8d1d0505ec243933dd42967e6e8ababa9306c7096efe01c4ffc7d00e40980efd64c104c074e8678651a03a8efd53a03afdb4cb5640b7285eb92b4768760414422c61032f2f7f1e0ff8203da5eb1980d14b0403307a51ca5361dd3900a3572ba0578e42af604e701f531a7c21e556db5619e94df66a5aaca400e883c11111407c08a2efe8e86886198efe6835831fdd4e7402f404c8a3c56a2e4bad4e9034234a9104eeb2edcdf05892a41d404cba40d5843e4db4877ff652d061149eac5bd9f3682592dca27eada79fe052f76aa5da486ff2076483eeae95ec55f7f7703d9c1f531cdcdcb1ff665f141c700e9d78efdf32d0339d7a557bd530a0cec2eb6dd7a2b697dc926c576ed1795fc8a323100aef23e98aacb1dddc7810f9a0404a461fa84524222a6d017a7d0f95e0faeed525b8e6bf0131fd9b1b9c1fd7dc1348a250ffa31471fcba25c66e793d5bd17203353886669db90b8572103c2b10ea51a87bbb4ce13db2ecc62d4a7bf3b27b227129cab688e290645002dd153ea57d4da62df8298e094c303f6abe59ae9eb9c4cf2f82e09d959ab8f573c390f447138ca9bc2512399a4453089a3eaf33599377b500c96539aee86c40da6bc5bde28a2b4c2d6c706da45d31b5433b68712eb8dc70cee051ea2284db00ce1040a90bd6475e37cc91a60243b263c28604436624472e6296c8a8d112d488d213c92682651bd79d2c51506dc9895b9f9a485dc922aa0d7bfd6e2d6cf96ec9914c286c11eb15dd69efa626905981b0ff0cee2a4259fe58607f0f03fb9b3853222f7728ce96a35ee51cec0dc3dd095a9344a386e9df2eaed3efa4b8fed405bedc56db778391960ba00da027dd1961c879090ecf9c2db7737ad54f63b788c1e256fe3003c1392720393e8672f0d1416e7cf1ff7b5bbf2008c0e1be90d36fcb06e425b83ef8b65c40f8f53be53dc51fb880f74bf0dfac787ce1f8b414c7f0ad18be0d4fef2bf63e13c430b870da25d8cf4ecbd83f9686d41e72b27909267b5557b9c37488ed55cdc9b1b576b5ae9c557fe5ac223f7e74de29ff283fb29645bc7bff20ddbdbf100f1019714764c47e82432ac5a95f777c6cb83680cea23f9672fdca211587e59051a4b5497909b6562cfdc792903a9646a8b34a2215e391a4b549fe20c5a1751449f68774dd69cf940aa30c4b6c056932b467fd65682f38044c879adc30cd2fbc9073bae03de89d2ed8eea44464e86cb770b8fe2fe4600be29afbfb5fcb5013211d7cd9814f26bd14e2b68b31e2062cb067d70f5c20a542adbf04e3ba45774339ec1eb551d7dadd3ef34426e9c0cd555654655556651d7014d84217750ee5b07b041a758eba2ef56acdd342c4c52f09ba77d5bd562e9ff935a0b3ec0d076ea6b6ae77d5f66dfda1aedbac9a8313cdbc7ef4a4f6f44eafb35ee73ef3747ff4aac660dba9b5a8d20a82d4eb346aa55dfdc11eb5d9aeabb6eb6a0ef6bf9a5e9da85e81191c2b79921eb8f9c70f77d1936c16fd5a2991c5a2fb917e18ca6067f466dd82e099bdca04f85ead526b95093b4340909cdea9e7ca598e020c442db853234a77ba15d1dfaa10adb5ae9b61eb5d0b46f073aa0961f6279662c9404e9d3fa5ef11e0fb1caf0470f06b201bb89912d976baadcb36e903db4aa90f02b8796b56fce94dee225cbbefbb1f4cbf6ec1f565f8b0fd6c71ae44f4f44a15446ce9772511efbfb6247df2a714423f9346a940bdaa4ff0572937d71bae5f6f55c82b5869954aad75eb64ab8bbe9c967ede36512d84187766a43ddab794077c52d396b53e357171290d685c9a62a13a5487521ab8800418e5525877073df0039d65ddfef7cadddd3bb7d6bd3d140b6e7deaeef76b438b5b9f82a4940a00c0fe156707603f6d8357f4e311ac9fabf3ba6dd7edd6adc5eddddd6d564cbbc16ef0037b90c4182a09413054f2815e57b2b6ad974ad75d2165d9a7260ee0fe6a4b153eec5609eef70f45f0f32c15af32e37261301dbae1864ac62dad01bba62f6cb821a643366cd1565c11d3211baec0e15a4c876ca841c5723f4c876cb81dc006db90cf15387f8e1a15bc0fe6d823421821522551229f1a1a1c9570a3f4b24f9ab61fd401ee1a6242fa9e51ab90c1b906fb0eb0bff507af14ec8f1a410fb0bff8678c0b2f72a012458a279ca812349638ec00f4aab57e107751296c5dc860affa3b5e3f49b76a85c15e7985999c00f7dbeec1556385fd41700914990e6954c1031b3479436805a70f0adcef75773716475ccc60073f08caa186573781bb6170777777570ae94c9e720d169c6f70e681fdc5c0fed6aff8e0891ffc80fd076360bff2c613d887603660cfc1d65d089801151ea8a0600747bc9c09ecfffeab6eb17cc0d17d3ddd13e35613c4117c9182fb6d7fffd336dcd0a10a0f9610a20624961082c2e42efbaad88d3740a0453027f8dcb0f1c086901b35e8810d9c1b5edcd0c6cc0d307e8e8cdc40e3a70d58f8a4dde062cbecd6607a850b1f5c8182f3b709baa8f5d005140d45175b84ee09d32b5d78711714c5dc766130bd5264026c627ac50ba11c467a9363567099d056925e7910a02beff3e783bdf778405d15463d1f7869602f7b6460ef3d21aaa3c3bd9bbb4aefbd47e42ed27befe1dc55bef79ef79e171402d9132249b7bc195d79ef7942fe79ee5df39ea369ab09924822ec9de8cb9aa433e787b3c08a49a1539325d39e39267d914cfa02cdd20dc0ab347deaeb06f0e573c393a808de937504efc126c17b4fc85da76679efddb0e7adc2b8f4653fe6cddc95c48bb90b8518f684cc199ce5bd584531b40274c2666044ece2ec095daf46359cbd22710646c8400c2bd08dd6ad4c8fa274abb2a00ace24cede0c7b2dc0d9a39282ed6a542707f6bec2a88e8f07a33a157b3f83f749bc072f4871d87b1414d0aef1bd1f290ecaf2be345394e53d68eea0389e67cd1dafd1f4b93018ac88d70dab97cf0df7255211bc07eb085ff56996577d7ae57ddfbb99bda0ebffb937a338de07698fcaf56e1e96f1bf3f6114b27933aa439de5e1f7d913c2dea7c2107c4fc8b3756b067befe5bcb48b5251e4bd87a55bf44a1b32ec554f0bf69e66af087bef59c161ef69f6ba60cfbb61ef43f0f366ddf2b2577dba5595588097b0eb9fc198a2ae49438cb6ead5be26ea567173895e12dc130dd4f3ecb57ea24a54a7bead5b30d21b6b76415c5b8231bdf8a129a26a447334fd282434b3587a93f8a54e344be6354f585cd4771f665a149abff2b9a4173f3f26c727387c981f2b69329d590687f9b1144ab9a5a7bd2a9da4e9cc38500d38e2522cfec5a73f7d97297ca7ef5ba314323ec8e4bb2586953a291ce2383c53e1539c1a5271f88d2b0ed2a4399aa119c441109b4ca6bf54876293e94793898a724d5df7a6f02dd5298584e358325d08dff463085382299d29154a2f3e91d2892387ff818a4b7f5f2c7d787bd53daa57ddf863486b755269de1d93a7572fc5fdb65c7ad3dd0a6a310356c60efd5e4e67cae1bad07dfdfc02d99dd905fffaf574e1fbd0a4639319ec64f84cd299c117c8217d86b9d6da237bb8fb1828c5c921ee406fa4340ced0dca2f954ae43f0afdab20eea2affe1d9efe08b4abc678186120fd25929442db6a4205414c92987cf249d2a44e2e09924ffa205d9a53ee7b66aa7cef29ae66aa3c8754ea79a4b32cbfe97dc1b5b5548174bf7cd14c91be62d28fa4f3be4ecdeaff707775e65b7ffcfcd813df3c9928ead549b70ce2521cd3ab3dba2e9343ec9ba3b537b8987a4fcbcec3b6b3b5fef97961547a19bbd5185370caf7a76ee6acdac9a85cfb2ef09d55bfc3b55222da34debd25dbf5bdbf6776318ae30f0a259144267157fdc9486ff2ad614a032f68604a032f64e054f721f670c595ba50aecf264c4b2bb83f50178a5659b778e0fefa84fcac23467574e066f5997382ea594d80fb81f4aaed0aacb5160bee1fad8024bd8a40af4837ee0f25f1039ca48508f4aad69f5e59b901ab7ea31be66a60614e05a322f41371963c984ed29973c860c26aca18796912648ec05326b7c3cd30b81814e8e4a58d27799642a6c4fcb9634729c47f0779f10d79530a21ffc654530363eb158c09870117684b460b181a6944a24157fde451392b9b286b4457986eb79a1a58afbc2240717c98219bc94cc99ec8e46068a41f41203ddacd35b01e6ec2e8d0f129d327f5af7a9b30e7625763a68c523992992aa23829548502e71414dc9f4ddcff995cc0c09b32665011fa55a60c1974847e1b53260c4a423fcacc117316cc593232b90090a953264745f80973e4298ea78ed386268cdbe5303d9ab3eac904408daaf4a6dcb31ccff276438e6300483769400fcd29a713f4bcce8a625892652904664b075384233d15782df2ab270c1f1e71ccccfcc9f439bdcdebf81a199c8c163530130e095a392b85c8396eb531cb1a5011fa759825157484fe1ab38c82925012a5cc12872b8bbcc8f73a9de58d8ad04ff3862bcc39ab494663509d7e9930a809a65bed4175c6cf397e70e7f0c12df305e7284ebf8c165d6470dd4a854157fdb71a10c5e9af81b92b150565a5924082865343d06ea5a0d0951051b01535302c8a707f5803b3e19213b319532667ca18f5cacb91b372c064722a6ccfd42c05bbb9bc81f5c7fa612e0aaeee04d72a228c0aaeefa74b711f707dd3994d52e07e1ad351361286f4117c904082b71cebf4b0c1dfe9e69498a70923247354041820120dda8221823c2a676513261c79e426f2a864ba996ee447049009673203e60a98263e195ccbf5e347ea8dd41ba937489174205c7f1c8124a7a0500f9a31d4277d07eb5669ab81b6ca21130e86e6ac0e0aea828102c606d4ab2b680b8608baeac7f13947939f4bdf853877c9e040940923530a194f185c6cfc405bb76088ba1ca22d982be8ca84735798a3b98b3c7228a8cb54036a7397e9e62ca06e95b55ec110415b650b72dc1b9a70259cc9a312ce30b412164f181a0c0ee7ae726692c199a9544e0610c720c385b9820b0c51b7525e843977a59ea0acfe28707fce0a5cf846ca4671fa4b668a4671fa4b3335a338328ad34f1eb92b550465d500c87473570a0a65b520f5a4ac752be584aefa3b2584432a09dc7f04c339874c660c9930688f7e193347cc94e9d2abfe1853a69cb94be60856ce707f09e5108beb2993d3e1da68e16243d4adf2a3f8cf13f7f19f2fee836bae8175ad81d55c03cb365b6a70ad81e1fe1a588d4f4dac5b5d8c59e3d3ac7e9d528877d6c04ca831a11452e3635403238dac672dd5a11497273802e966e8c3b42484a08749b3529cfa3126ca59f547d386e694d3499b659a7f3dd4dfca1b004a216516104a2135b0f25603ab81c560700260e1e0f0c82171a510982dbdeaef510a296f14a73fa5c4cd3238dc2f73d4ad5cde70e7bac818c978e9567ec16bb8bc511d4a758c62689e3b6e0a1042575aa392954b605a0910e92975f9dc26c1fb1bfe459b84af59dff8f57d742bfc4a96e514d28b6696c192ced0cc3280d676d676967e24e9f3beef3b53e053ec6729043c7f58f0cce0e360357eef991feab10743077098c2c5200c178367ee000e4f17be074f7a7ed7f3ce947d8add330f4c36180327ba34510316144dc10419621c7155d1a1081c505053220c2332e8a34a0508444127a00d7d11a392bb38e0c2061a54a9a24311180638820a0f6ce002329e48f18591112481866d8d1c9400c8091e2c89a1a00539f4e0498e56c405ec958fe096cee8e18bd9961c7834d105b55c71c728ea1b563a7749980ecd8e50c18c88fac5ac043a98ed70c32dfda01bc3871d59dc530e90b8e414adc51d6d5ec312b1208a9012c409da4a103fe0206ae82145415bd940630808336a184340f0408bae610808222843403cd10d015105db13b6d65a5bd70082160410425429eec57408081d78b839bc94e09660f4172dbdc3d00f371c0cfd00834a34f4436de807150cfde0c3d00fb0a127604cf1a40b9c6f9dc193296231f8c1d093246030f444881c869edc50dd18f2810d3be48318433eecc08732ca26d2d04287378c9c206272dbc1117ae50532f8320217b0c007247470c4eb41133ce0404a1858047961642cf213c6951738f101076bf4507484ae808720218ecaf0c10db52e8eb4e0031054dc8084131bd0e0c2c816479230d2046602273e7c19ba4194d8161540e1018a0d3a88c16c8a16475865119d1bb040cb5015143421250b2325a85de1c61644ec10430116472a00c40329b92cac2041159c112b4a14d1440d293da80087235e340747c822a41116cbfbfa5d8cea78d4057ead1d0ed7ff5c45b87a8cea7caeae09653571e2040a9421bad84963a39f5d8ce2d433bfd0d53058afb8f906df78494485bd18b627798322c6b89f1af547dadf8ff5a496526a2975fbeeeeeed46d3df56a54e2661024ad3943af6a4f0b6eae76f9ce9e66d5197a5512e37e69e04c8f8c9418e94d077a55bdd833d390c3f6bd2d6eae7dd20fd26d76a057b58f6cd36e4a9bb6f5ac673d6bdbb6694f4b7d80393f7c802b10f5f9bbcbd251b40f43fcf4404415214c707180c183f6e28e4d54336e78442771bf28dee49a74a07245264f5c100c16dcd00a252a97510d2c40604e9843cba1e584b41a48abd1b6a079c92d89085794cbc9312327a79643abd1b8a0e5d47268351a0d68e489085794cbc9312327a79643abd1b8a08994d53d6dcaeaacf801ecd6e27272c01c5c43a250ffe314710c73c09c90560369355a151a4da8e6029f56a1b92b27879653e3515be5589b63736cce89bbaf13c0feb552ec0ac1cfecb063d06ccaeab288bbeeab5fecfde8d66cbaeadecb20c01efa68567f17746fbaf2dff30e37ee5c3e8eb83528544e2d8756a3c58006769f9373821c989c9c1c1fed6a5a9803e684b41a48abd184684039be18ad5befa10b67c11445f4c084eb704f980ef5c083ef7063dae82a2e98a576b9e21507e28e4d78096e78041b4ea2bce14487867261cca05d10d3211918ee23bb42c4f5301d9259b921c5c0f9736c94d1bbb8f51b878705a31840a90702b05fcc237e2c42d535614365bac72e6240a291f4845802ddfa4d29a54cdcfa4d29a55d22a55490c1fd1fce19c02d0e317e38df94bb7f980ef5b083ca850908d72f74b0420a325ef47118716b0d3018860b8c88e20656d8808817edc1184ca0418215106184e555dfc375ab56d185ea58bc811576c1bf3f7b395c3d9fbc5ff97b66eece1756b87bf7707e7a449e962dae57eb56956ee5c03970f5a23041f1a2304531fd98b26946744464829962b8ffdafa69be37dcf9e6ab03124cc1650926b498418257be54e07c73b8f335ba5e3c28d7090f8a15dc57c803ba543ca05ba5e6acda05aad1707fa9288ace25a31fe05c3ada52650b2953b8608a26af7cb3e07c63b83f5fd97d1246098b304a5b78e05cc21995b21895b478b09290072bd93cd80df79760fd349762b873299766626c3104ce0560cc9af8bc72690a9c4b34dc9f4bb5521527d870e20b6ed291ad8bcd0b110947544494c3fd24a01ee04ca29152509443174048e0c00667bc32490a9c4937dc9f4944242d5d131294ae0929ca007026cd3a19e947467ad2011d0191601dc9d6e50d772e73898b99a249edcb153088d0e195cb28702e73b83f9746279c4b2f58944e60515a1100cea5505149a5a8ac6254ce8c4aa09286fbc9a2353299c936bc98628d14bc21a50b28af5cde702e63b83f97b2189ccb27b608120b5b04b985853389b335328bad69b13652c8666d3736702663240762a0032e455f82c061f6ca2414389334dc9fc91a6e9cc92ad6c9c88675327e19bb8c5e56388f4776366eb1332e34a11beecf23118e571eb59c314239638c32fe8c4f4c9cc7d991787434c28e445b3fcde20d771689b2881bc1113e4c51c596d918af2cd200673187fbb368247ac14274020bd18a4845acf2388b42456214514a0e770e8d70875960c1124b630530c8f9e09545d80c701663b83f8b32f195c5274f84583c116e09b3845a70fbad59f5736805778805f787301de430873e9818c30a932f98784119af1c62c139a4e1fe1cd65ce32b8755866063882f5dbce0068f68208e5644cbe17e1048076790863b836ae00c2e11420a327ae8a283152cd1c52b83433883379008d4e24c4028ce048c02fe804f7083338f7d4720cc63b11b2e87fbf367f479f1e273c28bcfca47a50aee4fa8bffefaa3e58ebe1811fdf97bf28487c513de162f0b6e2db83ddccd13ba79b67e0fd6efc570674f0c9cbd067c1102075f48e141114bbcb27705678f86fbb3d743743d44d775e98e681d8e560464847347c39dbb1a1e03e74ee80b2a5284f1451559d0b8bc72677b02e7ee86fb734784bbd3d24c3a28cd244ad7ddac63471dccdafa69b637dc8b0a2ec0b0e9200a32a2bcb22db2e16c73b83f5b2f565827acb082fbad10ce52c1d92a393bcb5920dbd9a5c0d98f6c0cf7672b83c1d93e19c2b118620b6ec7e1a679169a162117725b158265d1d97b94a1040db254e1021918bdb203c9a03f7b95da848ddae44b77f182fb08b7ac28476b222d5974ee2d0e6828148b28b87bd682b37e9e74cca855025dd5cfd5095c8215dc5f6dfd34d71bae3f572fb85fb952c1b50a6e5a1067d577006d857455bfbd737d82fb6380738dd584213b238b1f8c4883c98bb6d0af8024dd7fbb062434c1043786ac8b202e5e1506b83fa7df876371b377e5660fe8899bbd5a6794bbd98375595c70e882b65b2ae3665b84eb97c2b8d91ae1faa52d252d6eb6365cffb671b31fe1fa9d12f5cb1f2e89874b7e7173637173d370edba2b68c470d7fdcdaa7b544d570208389fce58038fd003237a4069ab13a23e58457d908a4b82c12559b91e91ad1908d8fded98e802779d7d75bf434471f68870e582ddc3758b8824a33ed9e59642dcd212b754e5e6c7b54d40d833ca31c2abd65a6bedca9e9dac817e466bc5275442c1cdb6b4c31232886bb948d9598d0ca1575311cb01a874a9c08116dbab79a8e246d1cba50515dbabe7079627845e22b03e00bd68936069e78a81098e5e63e9a8541382bdda44750ae063d3696a4b5f6e578b25a359a8f44a95b7a922868ca1190104000000031500303018108ac56291481669c2e8071400108da44a704e9449b32887410a2163880100002100000003338341020091ba00e1af897c8868da0161a2a3721fbc1e43d59be171e5c48b796715a9e083234ef6ed7007d2021a96b30c75e21e15cafef8b6bd4868c7e1fd4c1be1f7793b083ca846b9ed8d54305cfb96c8710d494950d85a3a467ed944225eed0ff357bf14ff5fad032f0abddfa2eac0f8e76c2258c8c533435250b30a9999f246ae42edc1f44235b8a03d4e80524e910c520cad50c87fbf8ad44c52f72543b295d32fe0655c1de103732047f5c784fbcd3ba8df6894133316d8ccf34253b2b13cd9e57eb3505884cd63f9ce31d88c17d05e81e151c60645d39383fed87f341c131e9ff83fac3b84f1e05714636400101705d81a047fa79b8b893e644fe8c246ce288612504fa4ec0491cd82e179831f94c3fab115795e01a06501b7cc7cb5959c05bbdfeeb64c6ade83ed157e0fad3fb9fec16cad04ff531956bf7ec3a67e03f4f718adeaa322ef14d0f5eabd322c0628d200a0a02e5041f28425acf2f5420235f894c2beb18093a6f5b8f2ccd3448e0e93b593d08f4b0e8e0d2aac571db0c7ea549e56f741067096e38199c6d108e8ecef3c1df417898191121d659c53a977886ddb0c09980da71f30bb9fa6cc4699ca770fb3107353254a7e46d83a4da29d751e1c734744b12d5af3de8005e6125d0787e0aa3166f6be49f4f8ed11cd66caf6cfa0b04dad88f9ebeb01148087518c46090e2b8c60801112327ddb529f9f08978a9137681545d33c1c741851144ade48a6059f9fe057123769e46efc3b240e74241e9ecb3cbb99373c043ee2fbd6e416646bf54d34c4986ddb4b700e7aff514bfb6e28a5ac3a6d97b25dadd3fa09e27e05da86b0e5d7882c24b38d26a49781c7f08b7b870192f58bc3397c120bcb1b8585745b2490d79980ab129ecd9b0222facb5ae54b54f56366699ea61cd4d12bc9a99baae20444a884b14b8c2fba50a202fc9efc5ce6744714cb9459466cb74c0afbdcaa6436631dad13af992ec9ea894b7eb4d974725bd876d519e877c749e445dc40fbc2343caaad7de59cb1e7eb1d1ff2c10ff3f85d60df1d98f1602891330902f238ac32d5333a99f4d5bc39e9b27f28c0002a1b655cbd9db8ab1e6f685be0da4457490af6f1f065922fa1636d1723eb2790eb89c5672698bc35392042b640b3a12624a7d0482fb91712fbda1aabca374af90292f4631734ccc793d97928b6023786bc649f98e57ecdde0eb8d1c699480c3537e9d1e545737a5390f83ec4c9c32f8121b6c0c6ff0fc996554ca4676fb3acbb4753574788a7b8fd5cbafacbc9936fe8f41a786ced52f34d5daf8260d42c61d5de6b91ee7de9f5408444b6c6839e01193876960d935173c4ce4fc21190d1292076ff974081ede61f6184c803f963783eac4ac0927ee888c0f57fb53f256b59e2713cc0849fab62769cac7ce2e68561e69b7eda0527d5dac3c3d61c18dc27294714a9a40fa3ae9f403b5c287bdca2d1f84e218b87d6f5dc1c2361cb7ae48d333a1d47ac10254e419fa52ba6855f96ba3f83da77f215e6acbe87b542a679e3956d1afa59e53b0f81311b8fd8e34e7a47add97643ebdf9cab0749c34c608eb8c910d5df0b16a7ef0deb84f350e2adc9743c77962cd073e7047732c3a4766df1c1f464be895082215e228c58ae4d73cabbd0edf455cb1b2fadf5a0828f9b0e8ee889cdc9fc3626c4dc54b20ad76dcabf17a159e10946c47d6ab130973c80837576d758a6160f05476c3472eb9a24d5548650734c0a6deb5672c8a0a490ca97699cd790c88f8276529e1916fc9893398a01723e8b9ee707dea852f39c37273f07ac88909072d180c210ea2104f28642963725cddb7fdb98f88a18e5cb9f490fd69a38ea515d1eefa9e27f039356466dc19e0b79ca5cad7e8175406f76d14a571482197807037e0df4cdfe96809989ef8bee482e99c2ad7bf28b18ca2a8e485c2dda76ad538a9fdc744a8cd486e0c3760f3ee4ae1494aba143d30c2827006d5fbd8687c3eabcab468dc8e53f664658169900b66e70fb291825c8da7d1b71424044b898f0a552cfeac6fbaa645b3964c047fe1d0a5ab4ac7a5b8196aec6eb93ddc8416e5cfffd2ca85f0fcc38c4627f90f385c5c54133b74214419210342da896d5ff1568c39ba8006305c745c6ea385582bee6a6c1635db72d60e1eb71306a6d68c0370a50927aee4700bf48de2b1997d158b27d77329bcb5e43d0733ef644b11f2f2ce33e920702e41fb2d377f785a0e916abe58b00ff033bf63c734a433d61817e3057e409136a37eb2f2dfcf1048ec6a56ad2d738fd4355fa751b38b88e95f256cb7a0cc799cea6b62aac94c46b2c0755d88cddbd10e1b2751740045e10b3375e1c0186388babdf605780d207eab21620cbef960e01c925dc7d274b7aa1391d2c8936e605a27c55364540d47ed1f16d8cb3ddd7bc1c115efecc2c4bacc7367efccdf96cd3002b5539e6bc48fade49f994aeadde8a83b173b2131773b0a36d65f7ef5085f68c990b3f76aa09d24e5e4e078bd0782a0d4bede01b3131f4e38f8fd1877cc95dd86bc3ba30c7d3f177762be25f69aac00defcbba2aec2f0471cbf4ef446c56d46c8580fb28ffd6d9389e280ec8bc328e01c42c4510d64ab7cefc1e0649aef6793d98811c4074f13182b0389afee6c1167baa480707ac2435bac3e7753101ec2cb8fad64f132ce8d3b85a168a25415bd6462ed0f69bab46507d9b67298e92f6b237826d797e2aa5ac59b4510c8eae4335811f6f371dafe32c3396fd2e24665dcb4b73f7b0db0afa6ea36ac3222905c41e2672ac188ce68d8b6649af3cfab762abd971e894fbdb01ef31348f5ce251602834fae6e8615be6ee81ec47f7c0c85708c62605f004a55c9e2b0ee03d0aa966ff4f5d50ee1ebc369c693e1e07f970a82d84aef09aa68222fc784dda0f4a149e4a83148ba2298bf445c164eb6f9f12723c94290b3c2e1d0a43b9663fa80a2b2c7e9ad6ab4550229b9a5b41d10201701e72f7f15cc97911919a2b7ccd67104812231fa72dc8262f5fa3877e48afe26ac73958b6c2ed789aff8d1032fe60b52a641cb9489e68480ad7d421951845c743710b8fe78edbaef8534b825ed84cd096416ce1bb52f9d9860ea0e36eb648cc56fd3d92cca604255168f640905d183670703f02b7c74291a8f97230fe0b0e6fe4db9841352bdd399fe28bc4db8a759753f2ddf4f0856588d12e54f42314455f6ad8e7f14133890b3e8fd73f2b495057d66a655f458b767720be3fa8218c719d13322e094b1c9b2333a37cd9573109c23bd2bc2c94afe029062c779d3f95ab1a6e585ea3d6e3c96a514f8cfed4089ba25bb31d55b0f16f08303d5a671124b7855dca37312391e51c20c0c60e3edded1cb627a14f2aa5753d6e8b6838bf194978cb0942f6b7afb92f49d9fa975ecf8860a8974462053110641c1a9a5ef7f63f791a8011dc05fdcb21ff02a3e4ce2a55e64b3bb3e8ac21ff078bf067220c03068c5794a5e6921d33b5d2d554264ddd5e1c1b3dd1fec895abf418c7841ec4f88fe2c0710ccb275ae2c4bc0604cd1550a80dc0d0bfb012a9c3829aa8ee011c6214202a027941ec0292f56618a3610774da08ebe163504eec39288f1e46bef980db61e6e3fe4a60e8521cb78cb5122e1b53c578fa32de9d6e9e9f68c8da6e5d7f8f450537c7a3d478d20ebc0c20b30780e109ed30e703e85839e041337f00ac9deb0a549d08cf884aded21442129c341183ba9d441c0f23a720a0f08e38687a625b0c76bf72f83ac6943e3e7fe5a78293624d87dd9a51d574610085118816a53b280c09810026e3e011bdf1e5b0bf0996125ee137617688f8306cd250d1faa2cc87a06fd2d3b15a85cc9700b13f8d1caac6251eca9c4497be1643a9187ae0d3997defb69f60a873dc3af66c4f21e3982784398469102ec159140a8c859489834ea0dc497acb45214de60afa0e13852492e241257d89f3319a737535874f6a1a30c434434baf8b6a0343ce34d0fe1211d49b3910c85005ccffcea7a21878fcd27e33d199bb22be18933fbbf9d9ecafcd878ddfd2b92f046dc24dda1ccced6c293e125046dffe17823f89306e0085b87287d910bffaac58ef0b796b6c67f786418d6a3a163a35a507c985be103381c0cff0d6169e2fc7650674faa039c056cc0dd977c37a4e97c12f0076ec7335a4f3a154fb0e545a01ac3c8fa807156ed11434970c8181e070cefe5851030382ba9c2cef5b4632694010506f2c56aaf8873465ab2191e9d61f10686af9a8367bad0499c180a8a519072adff284134f348b8afb4fdef88316e0496c3af6ada93488a3def40336af431c0e7dd2029dedae6000424001d529b0c8f48646cbd926e902012520a220e249cc1f44f55a9388bfc0889c60210428b40acec04e086371c62968c31df6af70c06d6a519978ddf73c86ab986cc7e70368410e18e01687d4f74738d0f70a287b5cb060c3ea57bcd6164969185df07128874a1a506fa49657bbfc27ea3db184ca4f6cc9108e9575688f97334e382ca28169e3630708135d6544a0bfcd6e41fc6166207eb8c5886df1bb0ae9098db8cc738b113f143400396365820997a6c46fa715c1f12bfa306ebef2200bb76a37b63c9c3f83cfd1c0452c039362cf8131cfc54ef876357bc747bfbf1b5a5cd955ad770d3c5caac2f74af0cb1374b2ae5fd25c39a206efecfb06844c1d026a0784870c2d428e96f38af6527d6516970829fae12561c61ab33b3786d847534544cca4303d4aa7e00e2981eaabaa5e6047491716d3c8b60ef0ce83b6fc1f246c0fbc6234c220993db362c484ee75b5c7b18abc979a10ce1c0962cc16488b2fa0c2e38ff57e6f690cccf96fc27db208426de2de77989cd569e970bddd07cf81807ad7ab09af8f4493946999a68d69e19b3737fb8e7f5c042de40070cc5ad426409cb256b9a6385fbeef912ad049c9b4f1f27a04823095810708987503c4fb98b164062169c2321bf2a63bff16fa66a6c98386127989eb2250ad05319260ceae1758cf0bd3de6cd68f04987d1582ba630cb60ac994ea63dadd5d50c1202fe381262a8627ec7a34a9b2ea2ec7685db50d67f087436056122bb0a189a73719d04566be2dac12bcf27b64b94d13263583c7ef9a85d8b05cc8cd8e6a407d0f2865912f191c8abe4b8cee6d8c9579c212fb62a57cca81b90bd3aa7b93e4c4b00a6817bb43c7590431968e0a04e3e6629904364b663b10e1dc5fb6472a71f17b45239574687b431554ca84085b5984447d18bfa1be4a605c2a6057003e4642da633f1bef9b1aef9288440ff18be960b985ae507e5439a1505ffe7bcaa17678a64bac0699bff04bcc77fb63d1e051aae4d1962b98ffbdfc765cf362787f0ab18c9e602b233567475f21913df11112f511d0c777ad7d85218268b0180d15f516def5a3dc79d6a7d36bed62a2226dc006385ba6b15b23cc3d7dd8b4b34461d1cd93829a7610b60b8216f072b9f7dd39c8af3d65bd1acc3b991d5187f3667beebe0093222a0093bbd071638da7b661e40a88977ea9901dafe3bf32ba90439ebd36718c22a91bcd28704b952437da8461fa2a3117f1d5e5c6cbd784fa1cd1dd662be1c0dc3116fcf9f0bd23fd3847306a310c1530bc64017f84d3b19592ed5f83c13bb30c1d5ff3f3f66f1924a21fd3bfe152084e567ff537e449475abe5fe549f4fc5a17c9eb7bebdbdd5c8dd558e0d51f2eb0c0a009512b01aab87c9a34031c98b8b5eed81d3b4e316f7d19e0ecf5f2dc230f8db5d8f1caa4a9e9df1dccd1f9bb44685e3ee1f273fd0933a052a21aa20edc3e0b55dc3e5547c1d597d3355ed6246b7be89fe6721a73c716dec18a44452b6188800a1c3dfba0f3d57cdd32bd2b00168a726e6b60763c0a6d7d2e9da1be5bb5e79a31966a191f66ff7d7a132297c0e6f78b1181db901a6d98b797b1bea8a7980154ef45d7676525da317c95f25fdc6ce0634a02ad2a3d9f604816d2946b9418e3f6146ba3cb1285707c67288998e04b6e494540648e21d928b668f2f210534a1cb36092237ca661a46f70cd003457548cb9ea384421ce192da4e8e67d8c900893b48075d7ff1f126c74a7dac9b75046f3bee7d639583e72c8a39a4ca327dba3cffcc885463cb6bd6a66b07af472a1e6d98666fc490165cf630e94c52c96a64cdf58ba5a7e98557739aa12302e0395613824ddb81598f200e94214b52819c6b28c8a102225113ab23a9f4a5c9e248804044374f4cca3b23f6f754a3855da68cc6cddc42b9f13af09319c427293f56e35c90905f78ba2045833cf21be0216bddb83149b6e4a6fc7f656cda630bca98593312668c10e6318632c00e5f21a7082760c079108e1d24567f6cb1c4d2a4b8923347789b99a89b7e8d4894b83d1a7e029ce17a8659695c4dfd0f697cb06f3beba25177c71cc5897dc431691c36b78ab65bfd203f8cb0ad7392e367981dfc6134f5dabc8d87364643848eb87db282e323cb5b001b6f89cf1bbc26d8ec304491e3ecb854d929db5054797e3097091014afd66e7fad8ddb1321021c9d7aa32c3ccb5550bbc5d94e989f1830ebf4608121005c287dde0be90a18d55094b9a5b764f200f624494dbb406f0c601d2be25c023fdd53f457aff057008d90ad901a0e25e1989b8a51a3aab89a9d00c7151c79dcafd0267e34a2f0a6f49b3ff7444b369690146604c74dd2f5ec52f3aca132b24e5d174bee1e7263869f11de6b1da933c1621f06c99a15ff3d86973dcbba64c2f9f99076086485850f585603498693331548e9dcbf338ff2b115e3d98b57189deafd000aaa810ce1480ed464e0bfe6a5980c0fb83c0d5546f4a82d608ecaa24a822bac712f74e21dc122ab25f283320ff400801a4efb806c41dfdf83616227e69f5d939cb8f13364fcc319825ac19040f5c207d99225a3dca9c255544389550f10dbfc82e6450c7e0f7a9dd84e0c67e9f5b64f79307aada47de985a33c85ab12be9df433197df22618e6283717164283e8d9623c94c43478326628349a3d0319179e058d8486c646a1eb20ac3c99a85796e9e68e1ea1b8e4d51eb06bf877ba14c64a11d8fd6d69ea7a0de7b0dc5444451f2debd5e5a8b708d4f55e28ff1a6b6fde01fdf9619625b0eb06584b4c7f7596b8de0d0deef73d461c33b7b401f95f835f3cdba4514b27664160f49d874bd7aca20456ada553ce46cd24317fc03eaf4c668db312bad06ae0df229460d27efedc3384cb41fc88e9570d16f8cbafa8e745552888c94e3ce4aea0e8c1a0119310dd11644fa03d337f019640a43ba5e22442617ce4325e2e63834102a65cdb6370e14e3dbdf2ee0130c187f223f41182a71336e4484f6f488729bb63c38bad8bacf8e5da313e46de77e964eab814c29a44a519f9d269bb2b7b1c84a49bd367bddbe614d17d08c855b0ff9194bd6fc6f6cfd57bf00508505b61d7fdd8b05303c04df26f13980a5403031f8b43e128d4ff6c36fb34a5c2ccff8ab583b4a99a9a5589dd36b3a7940702756d493db4552937a1af6da0cc8586eda11843ca2455203c13ade753c21add945741097036629801535007d8e0f921b1648bd43c61378ef07c8030ef7ffe3833c653b23a48a0fc2d5765b7b589c673c080bdf7715db61f5e383482123c726116972653bce560943c1c5e921b0652d958ce22000786810d70951f102d6b397ed25ec7764a640b1ef5c8ad32178ee27c973219fff7f763cde0653894a85850bdd8e6ee1740c66788f63ef3d43b2957aa0a29ae1ed36e380ccdcc0f098b5bf43173611b58500ab7a5d5e4ffdba9a56ce6b6c73bd9114609e3d6f8180ff0379461001f06b918e724d66414fc01a2f0c072b33dab2f4ce669cb5ba786a2160f4f839048dd9f8fb7043fbbd4cbbc4fae30f9596ce18fc8579a6fb34a3ba1466a73f848171eb1e48213da25407a7aa42fb4bd310244d9a8c6769381e825c7399e8d9a8b16b9879db2ed09b03e523359cb5cb21f8f0c97e3e718c2039f8a950ff4f726dd15c8a2c24e0a6a9833bc9e403add9f709f00d3c3d6c7502e9336a358acd57c6967b8e6233f65dcf436357e9f10a604f56e4a813a332b8c5a85893942cbb657db7375117d8a621f75174ac278c38a77c2e72a8f324d53d19ca6f213a8103c5bba6220e38d2362367fee22b0c3be96b3f3bd4c52566d7877e45bb0144b0228b5d6c2a198c0379a1527c553582b31296901d2d85fe4ccec347049c263e276815d305df5ffce69051d4b31f0b2d36b2df68ac4cd841febea129c015d7f963be8cdf226ccfef3b75d29d0e1d7b15c067916032b858862e0db949de242381cf8c1929f79fdbe06231b0a8b02b5414b459ea21b31f831a5f3527d12f3903a84dfa3f79a4c4903ac7426af8f9da13fd9dc338b1f988f39f91d83efd6dfa33c78870e4c4669b4dc96c81a2032ab5e4390f53723d634eb05676729a06a61e0da70168e28eb3a560c11ca7afc383c287ecbcd2492864091e3cacc4558606fec8647099d22ffd26cf5a750f5d4adf7ca717c73e0cb44e8f277fbb50c0dea50960f614c17fb003c305f4bfbe43d1337c7b65ddb003d52309a177f0307ea8cc0163f122892fcc516f9a0426cea1735f49b51ba02539a6276ed27f320fb5821f070ccf78679f2bb05f97efd89b459e50d44f9881a7d2037f9014f8998deb70d9b814fa095e0e821a9cf2762820035169692a9c02ac036ea94ef06c11d555d87ebd9b77218115469ac3dc1b311bb42310110febad50c49493c120aaa1dded3b7dd46d820392b5f70ccc5a6da26e903b406837d9f54bdeafc2df3d2a331ba5571a85165541c37083ff0717a849201944c8b54acc0655fd177e2683ff0fda135d14e3fdb8b86710f7a5522f2bff24856ff3210b1bb76176181887cbc90fe29ef9d127f5865c55cedd49cebe501a3befda9ac6be77a2083cea79ff7b3767ae1911ea1d35428d7fa36902481896e728eab4deb2294a10dba28add573a0842432f2c49f258b67fe52eb666630200721f283fb08f4ca5eaa43e694ffc37be0c3404fcb5ddbb817dfe2c2519d4791c3c1d01e31b7f8c677c1c097f3e902949dcaa2b5f052d691aabdb57ba5fcda9dd27a0ff73ec7916c2a6aee42c55c9653219754ac3749e0d08e3967363e6d25bfa7d2bfb9ddc7316328b54179ec7e2d9c42286f4b6d9533630e75a60f699adacc9c74c321382eff0cbe22fa65b5b3363c9cb55390de574abdf7cc2e8f878268a86818d3c0009374088de2462b3d5a5f27c780dd5e68e78d28f218e93d4d44e860c5d7dc8ff7fa0efbde108395a7dbe8833f75d69e5a15641e5755cbc15c7aa8a856017bd05e79729a891466e2e0d7ba97b530895dc430282396bb3dad8a500c465dda7a3b20009e8ba2a259901cf31c6c58f758fb3807800a33fa2e3d30b88a4acb5793fa423f964252bf9a9b9e2f413bf86a5c3fdc65ea12cb4cb2448afe6d83f3a0fdb73b7caaf7c7b1c3145417391ff898699b25658f0b2c33ea0fd4f650a513087446794049dd2a68d8b8ee8cd5adc6dda0716eaa9f5e4af6a06f3319ce70574a33aefb3d3d0b86b942a9024ede561891e54af8237a93890fa87f920f675c23560b4130d5194be82c8a2710784a39137209a4e1765d7811b223399c07301fa687b16784af5be038bc8af47289925b83cc392796893ca31086c366fb02d624d9d4110a3b6bb608791ee05d31c1cf59e09835e0982bee0e71f5221bc392eb4e93940309637a9f7ca61ebe5edfcfd7409abd7febf4fadeb07b7dc84bf7a138e3118609bfc8f8118126b8687b0e37d2cefe2d6dc67c3b78c98145eafd42d88ad39f6aaa13ddddf9b28c3ec8ff56ac33624481065cf10a2c7964f31c86329e50aa793b59e783bb884e5f611eb1607181c2a6242309cc7db4accdd860ab6416cb83ac0e01cb160b711232ca1bbde897b9db60662e9fc54bd905e4cccf003374b0c15ac7a1f090c9f19f66112a09930596897e0ee6773842938a01cdac0834b5e244a6740ca7bd98bb5d6ff175b2bd8a357f8240a653064599f93655d82c3b99b55121fea523362a43ccc5d92c842419114d63dc31d441e0554c698ba74d475a8243fefa75b6b33fdd638b14a8cb09fafbc6c192d6c5441a4865eaca44127f1fa061ee8ed7a42cac0ce29e4221c5c7f9528f105c1d0b6f58daa11d3d996c7e7784df60642870157d9e3f5dda68157b8a7b7f233dcb1d10958bf49af0fc2f4043ec18e57b3a67cde79f75ee8cadb44230a8d9608edd8763abfcfdb617ca3f6919c4ffa3d65dd10fccdc793f61bd468a1e9d32d36d4ef185886fa16ed9cf27ba8e96d1b3761bff477515d75691c3e7a3f659a225391c6aff71ea93b38978986a4aa8866a4f1d7bc841e5cc3e6b815ff726e44d9316a3821850f31d385b6152306e6b4f631b804c97db65d04f2bca2a268f2b6f30f49ed08151cd59ed28c1e3b5658bc982326d3b58f2f94c1da6d1281a00075214ef824288c97837fc0e543605b1206df29c4eb88dd842504f9cc14acc12bf5c42878ca7df623bcb09dada2eb428d821cb4233bf371b3286c170d42471d465b01e1d38f24fd078356460298ac15a019082febcd9b06f2f9f7fa67526d39e1427d3576fcd7c3a91e3f1e229eefa62f5487edcfdb33f40f7a986bbe047b3155a9ad2fab240ccf32cb47c68161934a28bd2bf5a1ee1f241acb2032116c6f40546458db16937e01aa15e2114dc79f9e86f4fdc33915d13dd57354faeb44d4b5c51a482ab4e9385a174cc7c528a4ee862b77e1f08ca8fe2e71582198506f2206ef9873f4a3efb6c28ad198f4cfdaefbb397e83198ca03acaa418d5ac15b5c62e78d9fff60b2b6ec2fda258e71c1c20a9249a77176250fa6c78ee97a1ce7693ac42bb4b12cd9cea5bdb4e6a2e41b77ffd903a2ad87c52e4eee4f5b232079037b1c8809928ce8d84a4ea58c2b88c0fd8702173241bd9cfedd3f1276fcc67ae8264272ba8cec27b58f549349bbd3b2a3e8ad5dd3adf004dfd6a7b1b4137eaccaf59757f34fcba67dcc8ab68a24f3be135e6c9d0f825c68e219c79d4827f6ce529acddf6234f780d44eb3a4bf702e12c25581767878a58e3855f9975bd890189b03f3241cc010aed0b64411bd25563dbebef3687c092865ff7ce2368e2ec450b26fb341699557487a69e1fe17b29b3b3468fa463286b5c0566e0accf43b065fe86fb3b0ab5042931d2573b864ce5cb35942496380601897a1b37d7a6520cdf51739ba91b2e79240fab3fe8548f15b13708939b82a39ecf412aebcceadc0d90347bfb2758dbd0980e8ca0cfa2d898424f9b827db0abd09af8b2f004e3125d411e5a283454aade5d0ea59a90697140c1e22f82b0cbf2d0bce6eebe515ac967a39a56832806d09aa14101eac20324f79fa784cea82b7c1e08411c8ca469310742b18120ae5cb52882449ce9b7de7cb149f522c8eea1b35577e55744f8e4558de9fb4ec0848d22e5e23ee78dfb141b9a0c1aee6d5f141f8dc1ee38cf0d97da2a51d28fe045cdf7a6cec2530d317a600223c5fae21e4bd0cc89951f25011e3377c4638c5d073a5eb790cf1e1011f86244314064541b6f4cea38c324837b59d5900e32c57ff1af8a9a27edaa22b4c7c60e26ca83f063d4460f9c45e7861efe858aa8cceed35c0b8ba48866dc62ae64fab4c39f8f5e84aa31f9184aafe2a54fb752b9ef8023ef2162ea39993cccd2a06292b7929c97da8291b0bff31598989a8345266348b37c145d1ee2919f767485ab1423ce079d6daa0e536c34bb4d56d1fa002885190fc541f86a2415c4b330f9ead968a5f07ee9bf8c99795a9515a608f5cdb75609118ebac0deb910ab6f994c86c2d03ae271c13efb4deadbce6bc2a00b6af4029c2879201436338d842ffbcae7334a5b06a3d2bb4f2d21b70c3f1d2b0a178a49ff3c3878841a88500f17c5c9bc8e25a0471b2a38514b1f094b994fa096a7ca7c03322992e0d9334af11657baa55a6d68c6c0448015d4ece5707d50fde23105df68f7e285440020c6f3b7f12955b087874545dac456cf2187bc6e68ef9f5cb5e4caac49d379e9576463b4dc2c7c1f5d889dc0d3f70f61151d444986226362622989b3da664a70875cf06484afa6acf383fd38780f1ac3b0d0081f2b84a318cff72d3195448c0cc8cf93fa8444941ef8d7a6823eb8adfdb6e2feffe4ac956e82319704c0dda2347907583208bbe2cac85422a616a4c79a29901d971a7358950b80d8744b7dca42bf655ce4f159e5e9f45cd1af70d2f2c8fe62c071dcedf6a1a1e14a16ae92f7b8a09760aca1554abb6d6e6db5ea24d6d3e2b80baabc09a405fe2d125aefbc47aa92204d528f69cd6d3cf18d9590c21d0230d28e2976785605a4f83fa33682bd19ed4926a498b574b93bbfbeb359e22d451c65aa8aff4945775c1a2d28b4372bc038a827e464906565e02ccfa4e5adb2f578e26869e59137ccbcc6f88bf256e28c954da9b56172ab6154e43ec79b4631cf91911f557e691e28eedeb9bf2d9fd6dfa110dd0f239a6997a0bf9904defb7dbf6c62d518b5b8d6d9a60d73c96a3743e981ca8287d5d32f3f5c96faa476cd1b92bcf38a85f2d6c0084268e8b328d6ed75216d73e4ddd7cef1e4389b845419f11aa1520137978657bd6435e2d5a6f47af4dc874ce648c9d656ea4334b8469a13f9d8076c52e23139c627babfe4f68f7821cf1e4859f240a8de8c2b7e2793cbd45f526dc41c15628db3ee3500e719240f96fdbd69d4c43fdffc7c53709cea9698b8a130c8fd76f89f767c3424f7373d7ade3c2da15efd31780c9944ce0390b033c5a59b12eb43190e548ccc5fc5e18ff45aec5cf77fd3d5503d4d04c451ca82b464e1528eca51758daa79ee749a449b38485a14892e69d44930c6d08008533f5aba29ba15b9822787e15473093ddea58df33ca576047a9ccc201be09e286c251d47c6730482072c483e3f0f9f5671d40275881242082977d6ea32e8d812747c42a0a52bcb5efa236dcfba3c33f01f6f7dbc6d8962712b75ac12f21c6e55aa66d157150df19920d6db52350b049bff4c30d502a7ab3ebd466be3439d40ba64c282147cc56fced1d3cd5c4d66687630270223370f8b24379208a5ea89f7574e239f0243ece8f34c451c59e20e4bf46fde1a28d9bd0e36a9bcb86c4187b5633f5c468aab6cdf76b5f71218a90a80e66083bd5c3df3fc05e8d1324c2cb6b8695af2e531183e6b9058b6d33748c7910906aa54f72108cdc9b8c19dd5d411d42b28c974028be120cc76601bfdf9cef13ce648fdfdce3ee5bcff2634886d68481104d2d645157a98d4db9a067cd2110d04a641b1af5bd3c63c61a89fe71853aaf16c47f7ecee9a4f7a3bfcae974c73a0d00931c02533ec8aae8a8bd0553375a1aa747bdc9054238259df4ab86eee91c4dd89af28b184acdfec52d6dfe07fafffcbfcb700d8cfb31d983097c0f9441938d169ed9bc3f14622bb84dec143aac0bfa085526988eb961d53d1d54efd414fb71cd316334f518e8d08c94fe39b88f4cc26bc88a4b844579eae8b3e89efa58b5e06de47078265f49eaa3a5bbee28a889865d46c053470fdb0786d258270fbcb13edea2685080ad61c789663954581486270152c1a6776cc4157ad12d8584c4fe7268ef133fd994d900409be37118bd40ad46777f972f49ac4389aff5c13145cc251e5793b416e4e3edc0f8dff0af919a9cecda15a54626d301faa0ecd49d1d6494c311819ad535bea3c086fbacef24e976bb7631c2115b9bcc5ed8cb798e1aee24b1f47369cad9763ec5ba14192b6faa9be782a1bb8b1169325adba47d081de48ac40e8bf92888cfbbdccad3da9cc284434def24ac318c629f070cb7fb9e3d529981b9916f1067f527add497e76f978e773734b64c6249b6969a1a48ca3aa6d54c79864d5aaab05a1c61be466cc0a1063af4db274b6810a56e69a19c6d61aea9e37a9634dc4d3d1951d719d6df6a5e253323f13bd5edd7d267d22ba563c2ecfc0d72f50f359f951ef9018eea573a91492f59425d6934ce899bf10be318c39fe3bb7bd2a95b1a828b5f81e0fcab72c3be60acf7b23e81c1ddf436950b445e591bd0b8151c7c65ed59593eb38d499c007250fe89043154b9ecd2d006e6f71b46644e61c479a87d196dc3ab3dcadeecd4b3521f827e40e2a12d5d2bc2c7de02d155023b451eea9ba7bb0632486b59408f24c3a509c071b753b6ea9e177f260ddf0824dec6a2b0d79c396eff603e0ad6b6d30d023cda254434b7784b74d89584cb78985bab8fd5d1389e31c046e7d214bbb06cc79c439772c5a21aaada9a3fa621857260fa201220dd63ffa4c9e1693c534e16b869ac9b17a749735ec74a5f4e956f6d1875f86cf74d6c8d90748ae6fa87ebb724e5685e3c937b37e1f11d920faed22876ccbe050afbd64a5afb6fc0ed26b9cecd78d7ee36cbf40f410e7fb65a3cfa64fbdd2cd4bbc55afe07b70425aff1ecd5b8f1fb3329fbe57bec7a26386c622f4830ac747f9d3f922e57e67cdc0ce2fc905a60f35e6cbbfc5d542bf26bd8c5aaccf2004c47ae9a5b10e1f01e75b7648fee500682abfbcf6aa60d1f81743dbce42b2de0035dd50bdd5f79e5e8d6559b504e2e3f0514d3c5af255a9e9759c3a78fcaa648259f0d93a7374294bbddbf4f972fd5edcddf0a0e97d9d260835ac5406d7c48d0564164c165586e6234c3315476e45f27f8af3c7865cff7e4bda05276fa3524ae7cf8624093629eca8979d5f22485cb12216ea7528bd6953549c70659ad721d6a7ab8e6bfba8662d4b9340c8cbc753f19feb05acc50c4f0c8d641f4aa767724f1dbe188d5ddb04665d8e24b69a8ccba927c5354f377fc3cac94c28a19b383aa354f68d1033cbb292e08a17c97f4d781a3b84001a9e5c912028506dc7ab12dcbf1969672ff666e7545d94c982c5ca495cb816d4971dff6cd4091fe30fa31fcff367549d517ae31a3f8888836dfed5b493c87879a1d4f3fa765567451bc87fb7925de607a8462c3935dd17812ac4e9639e6bec4919f3e60f17a246dea1d8e72d2c7c4a3039fd0343cdcb77d8efa9744455411e8e35e1aca7adf9eb280f370d82db602fa5b2c24d7d3ffe1cdf17125adb8b6b3d2e4cadbc37c2bae0716ed1574b680b07c5927794e48f9945682a11876a504c10941e5144e5525d89868c65cc3a0b16350e4148337b63da12b1a2508e6128d6368c08b4f4923cc0799db0a5032b1aa7d28e34da0ff1ed6d4ed785a2abe6f7b3bacc86577bde57bd5a5d86b77488a0ff3b7ff1543f8cab1b40971df651dfcca50c354370d358f11da1b9573478902c072f596c373e0d66e10cd9178a1d3926bad22a77e16d2c034f83b330fa2b05469ed0ac8bafa6b9a8e6e4a10a7f7ea7a409ebd9f772a615475347f27a4a9f8f0d37ffd23e734a38684ad2ac353fcf7956b0d8d0f86ce1866320eec8bc6b0b7a52cefab7f3eb136ef723b48d320d6b159dd9d8bdb25a20b46c9da5368d5d0786212de1e9d50b9be0c41bf86518300474aa657c8cc45a9e66c7eb47f6acb28d967fec5fad0f190fbae7a46a124028ec49f3abc6a6733127661f1b73da5bfd74fe1161baf0cfff7b4a5092e963a3036d24499de29d5e7d862c1a9552bfdc8523bc0a3c7278d8a1397fbdb272f318659e1316e9f14dbd28fdc9f21a848985408c7e21195ebca01d9a93593eaa837054f45820d1683e8a82239f4aded32f27d08e67a8f318a00f8168a68f9d259affa9d9d043428174975b85934ab73a1062d27e94cbf414d32849ce775d9faa4a9ee4f18ce83830411dc28a89dff94079cd631fc265ad7db8fc8a21d39676b85fbb9ebf68c7ac0aef50afcc9c27628641f9c9cac710c2cae4cf09be2f69b1c336744d81f321f38c2e8f2294087b294877e0f74b88accd59e8fee5cae6fa85ab27d31824cbc83e4f7f2b0c35bcde5a85c14a7e578e1450ffbc4ed45f95cb4577fc3619339d29ed893d5519a87470d490242530d27112db17add0b511a6500a199c336ffacc67a64226844169946c0fffdeaad7d65e8b0cdd5baba0b5ba8ea8bb40b7f1a79575b5968d60d96c2daee2af6749513df9338e641b3a5c5150a1f10d7281742d8ba0894726daf355619bfb06eb1654ab8e9da9fba534b9c8d25218bc8021d18bb60c9761e0037b7cfd57f5cb2cf09489c1142c7315d63b961936ddc7a886743363c2e3ce14d83de2ea66d487983e1c760956153664efc66044d16e1478bb00fab4df9a3595ae76de5a08235a39dced20cad43256adddfab97ad7ab91bb0d0e3a4f4cb60667bee643e8616cd08047e929c085f69e66bcf6b00576d8caf942a919c3441c6318cb05c36e6cecf52bec474f04690eed5ce486463d8a12101ba2ed604df67d3dac48b94e3da759038fe09d4b2b5a82a5f28d8ecc09e82ba47c0e9b8bf21f7d77b4c2a495ae26929d836a34bcde31669dc10d4c7467f6db6dc2a837fbf982104fdd6226dee6c2db56dc0e5099a49135add5a6643a075d573f2ee155f238f5b2ec9041ecde098d1f2af148ffd8e2fdb5cfc22db34b823f172418082f6fbeb2b783512121a3747c10c2fc8236d5696de903632cfca4a0801a0b130d959185f4402e749c9dd74123c46790c86e3424f6b38d5635ffaf0cbfbcbf15219a71a600543329b9d993cb533347862ed13ebf73cbc3a2db9df604173c850168bda7c20fcc4a683ca7d1a4ab00d3e797d10e0785a206a1bd151fb68b76cc94753dd630ea9584a630a7908a04eb3a3602f4e3b78e1a75e5f99c233dfa6086d89636f8bd213031c0c02cf0074ec1ac5b984da85fd97aca5a6241066d299b9aaa77f7bf58248007ac0d095cf21ececea89f8c590c3d2b6774798e59f5f9a28665b9ba18628745b45179a0798a174303667169e8e24922d8a45ade8a71320a434620b72d5bc9dae2e70a692e4ea0cf7469dc680077395f608e2f12323eda107a427e303baea6c80c895ea436c1622eb4636e310ade5d2b123a8fd58e43fb2841b591049cc44743f1390eb148ac983830453952f67d9bdbda95bbdb8f48b58195552bd018acb3ae71514bfd5daa56848e6d9e4c68ecf6dac6e6a18a871403c08442fb788b22c2189771e5683aeeb325163ae11351112e27b4787be6b2674e347d9aaa70afe4f742df4491f2f9bd7c4c98d86338ec4da7e079c1e01b7bcb70f89f9aba08bcad03b16d92f474bc1b2361a8552aa671d0e14b62d9b35c6618c8c0e8b6900b9700be980094f18c9e53fbd5a99a4c534f692e665faebb05024edef1cd1413263715143b2a21d4e12b592530bd31ebf460a41262c113b4cd90999e290f355b412550aea887f18c109e53798b5778f26e3b88f8a3de89b4c628e2ecdd0f4f485af3a8c84c02a9370a4c92412855ab3e9753914bc3a459d1a5def39a941c1008fd4418d6eb396c17b6e268dda9cd6a3c52b51f2eb5f612269076c6b10062b227599b7419ca9a04fc4724e47424f70a00942fce4675881c18ab5a91333e498cc691780292e5686115694f72779019d74b0de2351ac90e75c97f5d9533dc8ec89411582d24c1b60804860b6eab643fb216f82bb9eb799f1b57d241e8f1b51e1d81b5c5d02660d19469c0a6a373f80e28880dddf07ef043cc5bec0602546c3ff2b153c58e038e265ba0a57a93cdab83e29ad7e733dcb4eca548788123d25d64cfd5a9ad9760cb1156da08a7377266ecb667fb05a782ac3163b154419d0bdb6dd12add44a20d2e19fd842b00650cf0c361063f72e113757097722eedb6926fca9983dbf882634635b76d8541520e5c38b25f7b61abe573f429ca164d82b00daacb4500d1849a4f696bead7f2c017685d75b3f0f5dbba4d7480d57ce09cbfa9f60acbe1ef3462e2cac905f06553b959bfd01d82e0aed05163932d3a27f73d2d68cab73f83b09a16b4ca2fe0ef462db4328b2d009004103de589b30731a8579106359231f670fdbe06d9c487a3813d2f33f6022a747ae9c25ae8c3295dc43a13c43637c4261ee8499071ac91818387acc5a624d98b8e5af5f7da00812371b553b5643bf340672b0006ef78a190557dd7316ad1c6354774883bc815c87915484071a6f2e474dedcb1cd680030d9809ddd0c58a96bd08dd80ef472c80b38f68885e856071940ddbee19a934c439a384b74838b70e6a1e9d672463ca9648aa9a8169f21337ce4e455fa0b550d6e57cca734619c1d73d0e92fff2e60b05b073d2860ae7f92c17b529a136fc6b31875554c02baa5c0a06eb28b7491ffd5c324c780ec3a93b407c27172ff40b365844e5a0ac5b9e48c139f72f8e0d2bad067453ae14174b3cc303c52dfe16573a8eff4e055073941ba863046767f27024cb2c2db0c3506d824f86d3cb0fbe1dfc94ac302093b5c201250f33d97233867c086be8a42a1476be19a1ca9c36ab9b2d05a5eeb1b6f26a04c62a5299730167f5d1f2246474125442b9777c8420934a8441e68aac1d167b0d2cedc46af49eee5e5a7deb8b95f8abd13879ba647c65d0e70428fb25388995ccfe497c3c18473164dbc3a64f35ff1f7a958c06eca3b4b71aa8c7b13c3d5ab21d12f47e554a3a2a8c5a39aee1f745a73ea18a452699bb09b5957352dd11a461947e39c1fef3ecdb1f555ca8f770e3b07639b7b49594ac16e59cd8f51963aa19fee57af39e2ef69791757ebe0dfc98fadd7f24e06abfae6c6ba745c9bdf5a0fa16d8566ac0b04502b3e60d83a01de5c8d5545a8c1fccacbf54c3cf449ce1a965687e09b8b6eca09ead113687b27894c9d845450e8c2d94998d9ee9b4e8cfbee3d5640eff915dcb1a951b3a19a9d209a2df5f86a069a9927f072cdad707443fb111bc6c84b848d85159d135d2708ebe9675012b79eb0ecce1f33be2f9016d3a1908f828939c365c6b3de39d1f4f2a9d9fc13dec475471067a4ddc57b4b8290a74ab4dcb7f911b1087c67274928e7ac4fb70925159da048208021426add74f3e3ee236af5be62a95d9cae2b8e618d4864ca7c8871086bc78464d5fa18e561fe4db8e8e4954b4949af877ff9ca62a4b44740179d7424ad674f89efbf2b843a25e36a691c76c82b1f64a717cda6bfe3c795cc2a3ae968a5343e901910e2d4275a748274ee876aee7bb00a585ac20371006da720474a34b90e080f0253c8dd40e0531455922c2bd509534ed870c31262ed07e0027b225091b2d01dfdfd5ebb8e33c7eb68adb783114250a26e0f7a47358503f06d3ac9c54d82a328195e038024f4e9f96ee4d86fbd497ea9fe175f9fc804cf02eb2b674f692daad64f8717a70c750b83bce77949893fa70bdf9688055ff558b2feb12e4c83cdc3199e3bcd4aca219ab509b14ebd5ebaa9c9c1f3fadc67f949619217e795b14b7e238aa17da3c026d511c7aa013c07b50ca9570cafafc8ddaa1c0950f428dfbf4e6b3fbb6c72cd3c4f97c9e0a315bbea275c2e426ba04aee08351ba8ce9843599039c665c686367670daebe6672351bb5fa609e940a2733cb745621643558c47a01dfb2fbca5c1d16836c0a1c0433fe978ba2488edb18ece5fabbfb808df610868cca18d73a94a524ea7d9fa2cbf0f949b72aa2c5b3f214134faed526249fb9e8ecca9a9d33056d368fb1d549dce0a3bc596094a66eb3a92d3d4bec1113e08be8c0ea30312a8b817d0d5727e3fb9e0a9146975ddb0d4f1602e2aa5393351a73d312714ec386b8a4d6af69fa35e2f7e451abfcbf63927f36e0ea18cf65f5d2f56f5ca637e3892882a3da11e8cc4ce867b9aaa6571de806038245a13981fa759b4a6d0b51481fca45eff2674983eb4b96c5d476e02fcce12cb57a221317048a651628c0f2c260b22706dc7cca6bc6b83d4996eb2505ec7142ecf2cf185594c636c15a8491a5bbe7d19b09b516831150198eaad5a83ee257756409d331fa8000b091da8dc4b11e0741c4cb8becf4e115dae06378413c2d7569b4a2b2bd14f47f5e2f829f1549b1343dcb79c22535d7aa7406b9bd5584b1fd8d8ef0c98bb31690b35639b1352691c8c941932cc797e36793367c34095c6d1b6c03e8ba0b589058a85ea55290a8c96ff31c3ca8f7c55c5222ed1081efb4477b4dae99c188af7752d982d758ef8baa0b9823239dccd86736e306aaa659984fa97d6ce045004d4622fa52b5448f19776fd3a1e292a7ef620c4a186b650252cd4c80719d08486bd1c50b06cb46fdcf9790599eb6014504c4dfea7f8d6b445bb3e0a633a45682e29aac6ecf2ed2d169ce3a1931fa77bfda7346df8e14b07a0847e701a4b37928b2d09a07f608dc24dc225a59e94d0f47ba3cbe4a407a03b90712906525b375b9f5b6a112b33d015a2985c63ea80ea07fc0687ca6307faef69da2e2e669cf9b0860552f2e678a2d89909d85178ffe78e82bd95db64dc19437d18dce5262227547d2700d1cdcb89967a0deb43f8619b7b5db9518c80716ea533c080e067ab512540464d67db2246752f8b5ec0469144baab9795d19d9f11922555d59fe595b68cfc188eef632bc4aa135b043083248c12aee51f776bf504481a817d80d36ffe6d9a25039071260e6666beb93d93ccb783ddfedfe58e2e3a7a8d68bd3dcd5ad329fd73ea94f7d27838f149d882b7a4e8cc41f54e89925737305372c483b42b10b604f4411cb5b318801e4bf72ae4e0bd3560b4b207ef1089d02d3ddf9f4a73b1e2443e5cc2d96ae33b7b18e182fa325916ddafd231242b7c3eceb311fc59000f01e5b63d92ed2024a4831e9f63e599ab5b9321a4a38ad650be39f4c804ff2abe90e123a68d6409c8b4bc082b7573f0efd4414925b6b4fea116cb0a2470ab0b023a07050eacaa643b2b55ccc954095555cce7086807f26a45379692936abe7b735ff12f0c5c805dcb05e849672a027b4eeffe5170abd1577be274567d152bdaa808f6d899697e63cc3f3892d0f23e5cb8c82277679f270abed587541a04811ffe8321c8688f2f46fcc2e02f463d31e00f43bf18e88d815f187cc5403f067c61e42b06bd31f209039f18e8c5885f187c7c84cede2874cfe48ce4a41c56321e4ae6602fe895cf6d4dce372313ac334eb524bd5c0e577d5fdb2659a7b468e82b769dde9fdfddf49cd42c7999f17f333a3c6041a499e77c0bc2421a42e2cd67ebd60e07774fb780477f9735100e8f9b0356dc8af756363759ded51c89d1f710719ac57cd40f94d27e1edddef41752cf55616b2d381dcca0d88d92f92c2bd9e1d56c9fc646cb10864b9c98393a0dc395155cdfbca3cfec44d7b66443b9facb35be8ff1336ffff35d30d097c4f04229ced104229f115f75c68ddfcc036a5589430c3e41f7dbff47df70f7b4356882f4dd776c706d7dd5ee2995848ef5a41c806fc08fbc9813385d8f7987888400591fc9bc9650d5479dd1fed10e613f5f6f876fff759f69c2e1159f8924050b40c6f224d951f421065871b9b117382916fda600195f607295bb36277d118670848827877f563bf2fe9fc2cc8680aad045ca40a8c8fc587a4746110d18136e7cb5541044302a0e299a43fe22b76c9288f8774f077bd9d56dd42a8af85ca731b3c1ed678741faa22002605f009bb417bb6aa1217ac604220df4652c438f6ba7895fd4196f0106e373e13383d5f8a84be589adf4d22b7a37bdf791ad046ab63cdacd664214b35c27daef2b9a985aa9cf41a23ec1bea61cff264de2792a6e4aed3ddd824433a0d009ac53725934dfd210b724a2220aa7c9707dd6ce35dcfc6cf198c2f2db2a4d426e8d87ea99e40145751f73cf86525b330f8c8db803499bcbd3c2f67fe21038ce970ad3a78982928813f7c48745e863a35cbc49dc189958989f484f6070156462f1accb3d85709a7b62b23d54ce01b7381f48236b0813cdcd7f66da5f28da8fa32120d0ded830f8b5db23948415c2edb40b8d401e1207b6cdb360b9004434b8d80b553bec0b58e2632261781601545d48688cc1b430d27a99e955180c289517fa2bc961640081f3a22eb85cde4adc8dc5af55da6e457eef5245426d8a73073e1a740cc00981c717cd085ce5a72398f25eb2c5cdc2376d1bf428ed2523a2e5f83320a35b48627d7d98fbaa618d3793b98cfb0b75a883ad5a8d37d766e5e9aaa13c5c444075b4b1fbe02eaa7349f7add57e9a0ba8569416f06801d8b45dae29867a2ef2a626365bf70502931b41b9b46b04de5b9cf3c50c22af971cc196799b6d0158a7a8e90954c7e1e0a861adea3a288239edcbbf081e55ba8b6838bf19c989cebf9624aa0a08146d699d6b5ec43737229e0d97836b37561d14766b33f55f65c4007997a9b427eac8da8600cba2282ffbbf6d998123923732449e398494aafcdb2d39507eff9323a4ec6492568a8ad15da68fc51352eacdbf23286b27a20f6c490c4c0a69f4103f953af7f080db1fa43004d31362cb751c5ea1d132fc97735eebfb17c29826cf0132ddc200fe7039489774f53bbc829ab88cd460bbc2f080586530921b6fee46b2ecf409be32d7ecf71e83e261d48540c33369f8144048e23a7c3a840931b15f5d4c6a7c81b5ee21a4299d1a67872ba8c683573ce8eef53199245eedd5dfffac6db0aa3e33044e77d235bea52329a429381c56d8d19db41948ce80e330c14fc43286a4ca041c5b501aa6e4a9e8c5546352baa5ba7d5110576e628cd119a82a37a720550ad1b4615713264470c4a18f2e83e51a4e32aad3f65584d9e584188cbd29b751514eb53a95e9ceeb312cff12a7419fe3d54799905c690037d2cb6b6f300ef0f8935ddc9c574ec46247cabda17582b9b750e92ff084da56a001b05b085b878da0ae3c0d0a6161077c73936bff7e5f36f829026a210aa771678713d8f3d679e5353b48d34b4962fd1c05721ba3f2302b752f3af24d63fc3b91803c5ccc67d6fd16372627ceb52ecbd52deae5cf0b9506b9fca4c3cca2a5f3ee8e1fc67e5f537d8835b517e6b78c1c272c2c40a6eb12870960b2e1a44e618142617ce9b338d9d3539b53aa48e517c2641997497d44b5b46e8ea4c116e023a923c5c6d274eb5d6201e90a5c00f96321e0a4a076edc88dde9de193c50de00dbd0e15a4b6e23e9a7f70312e75a744cec7e17ee414f0b52c37c5af1333e4a1858d3d9b99f9b440a3b5ed7f39424a8ee940fb320dd97accbed5ea28f997f5addcfc81cee1d1a45256df9ce0729d1239925f3c9e1e37a9651203457b76c2dcfd0db3de19d43f422a1cc6fa4ac64d9ca6665c4d41b3252b04f40771345fc5aaae65871b66b04cd7e3888e47aa261511b406b1d61afa5abb446802d45421538654cbbad77792436ef972b324dfc31c184c4093dafaaffbf00db71e5e39735e37452571c6a682f939af10272349abe6bac14e37509a1c18222ad78702161534753b292a7ab947c817ea8e668a960fbb484d64b0110f4817f6385a58a5ae29d3489a982c4c556d34629879268e85bbd64a9535feaa4dfee7a6401d7bfb82d1b6154a25194af8e1a010b0435bf322a515a68cf75a7cbb02f5d3264b47bf05118469ddb540a7c2c754e067ec410c01634ab02eb30634860a6fda269488f0d826d49d74da29c95b34c23eba455e27046546228af960bf1fb275865f9ff5661911f1592d8676f83d129f4ad8574efff5cf40e08274cc14afaf6ae8991e026960b942db0177804a9250f010d912db1aac8dde61b3c3191c07a4557fdc4e42be5ebb9127f1de733fc8c8aa27bfe0754dffac6bc4029d5c3c9bac743220b192dd338493528439843361ad91f355e54a5e7a0fd9f06759d88fd955b9aaa6647f408ea67e930a6bed848092c2b896c24bb5171fd8c80aeb340377a132df212b43f1b93dbb6f3e1404aee3581c6fde992806abf441ee5d6f43c32d000a20c1fd384f4f7dbc4bb49ce205e97588075fa0a35dc055b650d09f24e52bd74b5c439f23baeb01241070c58f5ca295d34653b79ec871145f8ca6981ad0f1c81440b80f62ed8c010eb7f1a77c5244809db8e47ab8090c50cdc1e7101e33dd937a253d269ce9d60cc9ca1fca6ce221d4d3d57f0f8f70cc453c7bcdf407f57d877e9d67be21847504b0a2e36d8cfc4e01d842df1c96815211b71ed102a2660110f1a14fe61820b1b6bd6975c7663ecd0e54f2bc69c830ff4a938ce89090ed987ae5d1157170cc074f8fbd5605f8775c4242adfb57a197ab637a427a0f3d8a6df7e0fb5f0e67df18643832a13cf197a6f587b8324b9855a424ca4e2db5d16e6e61aeae9e19d38500403b9e36690df0a9a59e33a4850c7ec5cc9f0143dcb945fd637518076b586cbc5dabc53aaa4ae147c731cf45411ef9bab8d825a6b9e632ab65f999199bb4ca49154ceab73a6761c6718fd89f2e7a1a8ae1211d7bc3ede6957d385934a7192968c44b3066c48cc7a1a357c6213aa064c6f02b90155b711e477736208886c9ecb4e8b0ef5abe21c98471b2058f256f0a3521bd31c5426959922ea5e2380a71ccfa54d2ed5f2111f0fe4f30dd4c582a5242e2309d6a3a9cdad816366dccab7a4379053e5fc1395892657a29478b2d501dc2961108f9dbba494d48bce0df7f14e0e9ff3036c5e31901b53dff87212a0187e43568d7fae7577c963050703628e33b2ab58378731d15e92f48f2e24440fbe22b518bb5f59ca743a0c8f856247c803e199eb1f184881aadc985afd8449f8431fbefd92b979b82a686c963becd15f6f6c1f07435d5ab60f5c3f195af6eb3e90dacf9fd791577a7faa9c753530e2f7bbdfcf0239a3f62dd4f91cd5377befe6cf949f56b9560b79005af45fd4ba2ad4cdac86370801d99b120d4e932d37d185bf31127255f3a8db31c4589e763c4093cdc6a1abb11690e52fc8f4715df831660f5f7a1cf21a4022881d7cd8fd8a93fcb61f483f9279a00b71ac0a3143dbbfd111afc1f967ea6b2227cd625121b03566bd90f0738909e9af5c1a3453c72101d38ad51198bf3381a5506263b89e9268ae8e1cd54f01acda2c2ae0e8a31055cadf3e41a8ee42012d08618ce2ebe329976f7170ac118ae1dae29d4c6a2ece6c99793b2ecc11c6322190fd220090c7771784e43a1e5c59e5199a8b3d2aa7e15937279cb5d931061c986f1cf43181b7bfb599c956afb7139a39745af32174a13b1f3621405042b9f8c764699b6833f8016a886141a136bdbdae32427df52d4acc8cd14d068c0dc11195581b06f9f2b0bd167e5d42df622045b8656971fc12402655c7faced0d9ac8d55f25e6cfbb8d9c91b13a83e91fe3ea06b9ef6111b5148797ddc41015158373a6325bf6fca08f39637c61fa5e092f4625d40a257ce6eb11c05f3dbdd15ddda1806e0a5ed2869e0105f4597b86eff75fa4db07ca60e1f63894cdb2816089223884af53b4337f60f9f5ba544674b8b9037acad6847d5b8c7214b0dcc1979b5d042c90d915dbee0cc9a0b0fc39aa6a9934a27990599a697dfe456d0d95797c8d22fa57d1783ca6cc2990b11d082bd564b8b09ce54024687569c2ae12711b2dfbe0c9aed8a0ae4870b02421db8a3a1eda489e34cfb9b07ed117860e52429bbc0eb0ad088dd9d39314a003c7fa02fef521db6eb1970d18b8d465e8cc4fde7a23db9a8a5d515b5ef922f9b9b01d540636645a7e35cc3402db6d7dd44d54b6212bdd96458585aa8eb468508871c272960391602dd2e4588531ece35cc56dfa4bd207db5664d06e65390824480ec56c18ab5e9a3c6d0c5dd37f605f1a2e915bf6766777f3fe17a8d2889ce0880c3afaab9a281543091fa338c366de34c51bdb9657ec1a9ae341ad83b316fabf427edbd316c7dc6085663c87a101976a37e718094087a6d8aaf3c1613b4c338a821c8427ba82f28a9668f8588cca463d56ced75649477e100ec5005149dcb7e4d5c9b805f0771d985274b7411fb1954b596321c4eee682b92863704af48b1acdba0591994c3198437127c05044290d2f4f3ffbaef0d0fb8692773584e1f1b59553e4270af83b0169a8d26e684033aaa832284785b54b744458e5a496c63bda1a5e986478a803c846122a629dd86ca5390cb37c58310a0410a611998c8df877fca1b5ee9d9630331928fba5071479852ba094e82c1b5cd756688463094a90ea685169c6a606d8530be195d01298375fcbf3ffd35f42fcfa96cd80fc894d59a815490039fbbc882d37336864c7ab2a658029f1a3821fc890d2d90ae8672cd118d67cd269ef20e5e4511b160e554dcf61b1b0fec58b0d08739eaf5cf9e46f0404db0373d0c2467835a12bc42691d69b9e63a708ddde6c45db9387d3cd472550a1a8685b699f1fca7037f57419b16aa6f5f1911da3e880d51026be9a0c4ae2c91f4846697a1e630b64c9f923a6f893f6b8d2d5814409032f9fc3f9cd2edf02ce6731fef91f1f9d1c74af0481e45327918f5126025f38a88abd316993bd16111c02576ca976f4ed0e9eaacc35ad338f9497910812e9c56054ed5f7523e059294d16174f919741a632a089359e7fb1fb6a4f545c3ac5e4d345a44579899f88d91a64344a2df8cdd9750d7940c4a9c6289510160a597a37304e4ae264e7c42280ae3c434439a593badb31c04c6164889e7e69e89bd301ef39919c8a48a49a7c2efb10b7041c4a8099ec915aaee5c3bad2f54bea98cc31b3e079663c11a016ce6629f1cb47a9f90f33da5b291891acb7876109bd95c970fcd850c8487cafe4197978a2fdd5d8070b22b190586c42c1ed4a6b205dc97b6d40fc1a1e3501e5b07a5ab45c4a907d74bb8a8ed415d3f32205ae5c90db1b2f8e8b924e940797fab1560ac7f4a1341565a6657d4976902abdff1f48cfe4cb3382f2d4733a1fb33f4b54c255488b5085eb1e101bc699ea18944e50f9ceb2830d0ec7c20f3629776213a7cbe76fe49c772f42f0442788e4c55efe2098ece66c10da7d69b999cf33c4fb6a9b001da108e1359052ff227b85236708ed4b2d71d48cc4a28161158cc8bcb8e3131b30b87f2fe360e4053a804b3982ce2034352d2902fd41811cf32578003b6121b327d0e491a5c851a59353e65a5a3d357b8e13c0228bfc669566cbe68860b915f27fde7dad6f73ac39f17ad0ceb0acf109a008526561fe4a9e3d3d84d98be8bf2c08ac670d8b9b7011f06bf5aecd6a5ca0fa7b342d9aca18982878b45f67b851b937125f1403a50b41956ed3e411b9e384a283508a0017b06ba933ad39b3786764bc6cd37e5ed088ce9b7fe1e57fbe1669675fead7fba5e6c94b75f485d3374f68a6632a1e1e3d034c6309b69294c063e7bc531b55532c0ae0ff6d0c12fda5071ca06559e0e1ea1fb1a3722c5e264447e0db418816582a720acc93ce8c3e70c9f4d911e46b9dc9054fa0548689f74d866ee4103a2754764ccc1695aece592f47a80c0b4dcae5c284197fdbb63716424f729a13169fe272c1ec70c3eeb5ec554d0d34c897c4fc501792282280d2edd9460363410e74716ad2991d6664e5c497eec7f3067e83e30f0fd3e9eef1546e521247fa0d23490021d06dcbcde5e0e6c6edc868eeaf5632a327717f643a1a534506b3948698a79ac0ad6fb5359031c3f42284ad106a01970730e0efed716296af748da3a0d50dde46831fef496af2f942598daecc14b8b905a37ac183ace0c5ae1085aceea4bb119042ce8a5e46bb996476b8ea7629e1ce07dbcc31f330bf669a95599bfb4fa9f357b93f01fe017e394f50cd7059d9745e9fca8fc35bde26948788baa2f18e1289ff1e12e27e37781ef4ec503d2c6c910ac514d83ec860c403eba82e8e6a8bb0e43f23d77e6fc1815c41b6b3c4e8b2427ebe678fe2b4243b1ea2fcbdf8904c7c72a5cde9802e1c927db06681dcd1ba8173d172963581fb78ba4c69cc9589110aa80eedd6ecf7e2baac1eb8277038963759042fc7cc64bde6967ff1143263c926660dd41568f7c805f993bf16b0c4332141c9d12db6af88a118e447e3b860f7420af1a5d10ab008b862b878f9b5adb5d9757e4accc09c384a860d52e4a431603bdc76e259f22a01b417ba5ffc126c544dbf89a3e56fdcc8e85f56d82cd162b7e761c08133f3094c8cbeb4fbb0014a24cb444f29360f78972a8c95cebe9ea1b2b4a2b15a4824c1f3aaf28d27922a3966d89ed1ddcb74235185d14b5e6c9749bcba747f329bf26fd6d8c0bde1b7148a8d83a821072eab17b9650dbc3a65fc3c8b96ffd278eab6c0428cc738193272fc00145b4eaface5922abcc326d71629a056913df85b454f2613332af046c68d6cb297e521c310c74719e1534b989accf3714a6815c1de53a61097f2166ef43da424844d9600d992cee97f1ecd48874c29bc7fee64d0c30b9c4138b5c8f40fb3fd0ad94ce20240a715124f744b3a915c64136ccf1c613ee53b842e8f2527f8b60d0941cc08deceff1ba411829fb20400e14f740f11765f0343d3c904980da5a2e935d48493c189f0c16f09d54f31789733e169746e3705d130fa7891a4730779e132539d57e151a6a678b47d49549ad6c6e5a0b1c3076b3355e3faf906cb4711c9dc052c62bb62f557ddbdc527b87819f5e0f7c7a34c5307cf9fa766e6b71280f0ce9f14a3197d66349812deaeaae8d7c55c3263fee22cf8bf0dc44837e69c33e4ee67f2c62e5199b1f5d1b291e2319a154619201683c0dce007b3366574ed3074b82a4c967a214c7873076167293b0be438635aa08c4e28a2cdfd6724a15b6e3158afbfdb7110b9542d1490229f39dbd70de0cc7115c0578d54fc28898d388828ed264234be07001aabfdd49b1109efcf6d145af1f13fb0c06d170842ceac02cfa58f874a289ab3a34616175f3997d1984e1de7a0dddd0cfa91951269cadd1e0fa1ec10aaf54bce51cba3cda5c495138c98337236433936e535f8015c9ebad54929dafc918843d811690d0353159ac37a9be7f6351af277e242a24bd7f364c25866e91541a2a6ec81f58550399d4848857914e7b16d35604461fd938bab0d6d5d58ca37f9df3cf85bafa0372e272afeda182d1edb546ebfdf074ee3fce1ee0c7d7f19158f025b5b0accdf9ec5f135561b5abcba40d6c264f852a9774084db29967e3ad0be81d9997fbeab384393f4a6ccf392052482f9b2eb02e5ff1dac09fa46c6d7304cd9cdba7dd237c8975d2bc678c35408353ba04dd73f229ee41afbbbc569a7283d0c46f2861177fe54a80fc47da0b5a5d522ed852c638eaa4f5b13fdb1141a16aa1370a1e81b3208ddc45b6d4114aaa8640f5d38cbc46c5c0e0843cca32739dcc36dde7f1f10d73f175b1207df9eb8b901c15a4ae5f67a41412200efae7039d74e1c6823a4076de35375e68eac81377309f9c72b32d5c2f8bdcb38115b187e605aca98c7059d2032bd3a2d7575177b9b7922894b50851988f78cd853384143e95dbdd2de15c6f8ef251c3485162ce8a38be706abab55d799b2ad7e41a139326fca26fdb7fcdd15bbbe26d71f912378ccd4706135d5f188411a9b29fda558a493f6fd0b12d54496f34280a9cfdaff7924d743af6a4199749b9cdc7114c532881e82db40031fb8d2e58ec1f2b38e05af7d81f0b4bc150024513eb74373f3f3d8c8412e7267b1921dd0aef55e2def1717336d360091864138a3264d2e23d9e3e755fc140e03316093c82590a6f5bb891985e50fd51f7082cc217760349650e87a2a7ba352fb0d808c0d469970b92b496a3d22ab7476c6c71498be97a21eff5c491625377b0798beaac618aa597344f298f177a5b1a60a4d958cc46b2587a8b0389f79f789746d53b6701aa087911beab2ce9751e17a8d67c02032434722f58346c6456d7226c0c7f59cf31d1c15cfd27cda0e2214b24cca28cfc68ab0cdf3fd2025fec065070c41b81382190a1c3e4a33f2c530e39e9fe550ce9c98301d613200f5d1a94563b2426f09092adfdc717d4169c92e9dd01f10ff42bde0cbf8ba9133711bec28ca869e4a623a7dcaa7f300329bb38a8658185c168567c0222001ca053881d640c2f40745b929163858ec03106243ca041ef1b6c19698c8fa0aa1c83f6f802561b098d007a93cec2c1dc11765f88dfaa9a4837063dbfc880164e48b5045d82b28fd52f5d787035ee3c833b0e33c14ba3bfd993b8b759ba3097617b97afb7efb85c307121086cf558418e94dd11a9fab2717727d130dcb6455c656f6a74ca296e70c973a7a86869373a17cf0a406d049c3ff364a04dacfac15c546d2dae56567e92846c8992fbff7cea21c6b3efbfb3f5f2ff3b1306797d4062f4a4acf2a84844b0b2ef46380bd866e1855e7f566693d2f201c9987f2e617b54acb34f12133585014babcd3f64b5b674396c0e996ab3e021ce801ba50ad456040d05d6d0e451dc31c668c7f22da9fc5fdc4e01246c8b93e5da52e1b5fcac9d8a6e0a0879e306b80a72718ae488b7d06f847dae7ebed0d704f0e1abbd5b5031ff0a1e18076de053a36e3cd1f812c8ffbd5d66ccdccd6eccaee955b4a29a54c013004340494042914089e3e10044d60c9034190869e0fd0d0f301ad35ad35ad5b584e26546b0586ae522764097ed5b3e8a65d7446dbc09ef0b1d8b3fa1bae4a2c1d8221086aadc75514f4454196d62bbdaa1cca345f21583994895572ad34188261088a3dabc7624fabb46a551157462c1d30ec4416267edd559369d50a5da813d895aa096b568bd62ca9ef964c166d793614eab46ab94c2d21b89550a754c7d248bd42ad5aae53edc41e17178ec5457409d196473ba1562dd7e9c5e5e55da21097f8ff302e5cc59aa5e56442890fb36ab94a465ec4c246bcb884a0d61a049704b1240813d69dd8c3f258ec69790e14858030fff22eaf126350a105e6c525748126ac2b872ab1585a5ef43571758592d8a42b876aad5a4e21c8994a26164bc963f1b892a769134bcb29043953c9c46229792c1e57f234bd89b3f5daaa657a9538c3ef58751caaa575c7a142d0642a9958ab92b7f24c254feb4eeb4eec0141a32e035fb9323aed95d169a764958665545f9495f2d9f4cbf5a5127d470906149d800f5d67fae6a4b6d6a9c14db4bdeef45c8bb3d51a56f2955fce3a2b09b112edd977993ec6e4f570edef645139854a9dbe542a954ab55241e09e3929add5da7b31d6b49cb76de3b8aed3daf34aa592c9f4dd9c3c1454a0299014954aa954e04ab55aad52a8d5eaf4ad562bd3aae4ad562bee89ce634a7f52b4e977164668d3a9af881f46c86d5c9b1be9d77523f7437c73731c2734356f222b6fe034916dfa37705c1b4608468813e70671d8dbcfd64175d09245904dbf0471d042be0860d906a27dc0b2cf6d5c9b763e5eaf476ccffc258b20f7f690d9738cfd382cc95772b19f4d3f8751598efd6cfadaa8ad3f29c242741e53c2e86caacde96eadbbbb8fe5fc6ee6397bbecf79eecaf96ccf2d4e527f8e079f0929c9f1704a2a8debfdb91db729bd8b431bc8a682edbf41f1991f9414721bedfdb720b739b94fe8964a0cb7069fb9384f38cdcd39dabe93b48fe8dc9d4ac37affcb53695adeef1751b47d8921a7b94a9ef88b7e6983b6bf6de234f7478a93d4df1ae133578892466ef3bdbf15729b1fb7f1defd12d56c4eaedeff2bcd46a47337f5ca68db5660e7f27adc9d39ed7937f6ab68f726f6b0587f63b8384da00c11f98440e7e61851722b95bc9b1e960712b90c15d017a5559c7e9a290b6a8da53ffd892fbb8a75c3944c8222aa345f7ea39548865d29ff1453f0bc1bdc852eefa627fc95aee2d5275074e5d01af43199334b6c29aa2fa2fa129a3224cbc1e141a7067f09d9419ca41fc46ab2e9e9448464d32f8d382b23239317d4227e412e63be742ad5dd5165187d738a5819d5974511e1b0c4ba535fb37b8d7b3e51f46ef28b2c9bc8c24469243f80bc4ececa88ca090706c76154ea8b65b7b16ce9616b6d2c593bb5e3ae17f411e59d6ea5dd9b93fbd93de8dd74dfa950a142663ecda5fca8df1e14852c993fc3ef003d994704ea0705e4eda078503e6c51d057376e4694a4cf551dddf8a3be649069fb9495a0a93982dad1d1450e1bda1971504c86cd8dd30c106ee0f868681ade8df63570cc99393367b6d74534a5c948a0396f799c5728a90d314393432684593497984c38cd349a4e68b299dafe27bf31939832f48d2acd9ccde66c26514ea2211cfa717837f771783decae6f53bfcd6f68b22943f197dda44236a538f38a6cde98493d184b6d88591efa72847044efece0a01000a0e70c670a1cae08003ab6039420d400148870780ed8323b08b043a3111371d4d48800004fc74f006444e868ce9ccdd9b432697451d19c45219230afe761783cecaee30edffc6e4700740f7dc32ee74c936d238a9b683a56b413da8892453737decde561abfb8c7dc74a1c66e5bb4271287953c72cc50d0fad8d349592dc353c6036562245cf9cbf6fce601e0fbce7ac045f07a3870fea638439828c00e61473e62ffab3074ea38bbc88c6a08b2829459100646232289d8dda2852a50a121a0387ab681f8bd9c36f1be2a1b20971e0ab95e3f9c193271e3ee93c263eff720c7798fb9c738e2fa67d77f1c2a4e6ee5e7d9268e193b3bb7bf54982854fad96dbb43d5560c52e6badb5facf29484ca762c4b5569755fdf698fadd2cf2ec606b756ab5b6de9f02f3b9bb7b459d5c52efae548bbb8a056cadbe6d4316cbbd15ae58906881b5b2a8dc5bb46765de1f9ffaf2d7417312a98cab7d7139c1bc7c0eb3e27b37fe5e6bad96a7c4b3fdd6e75c208ebabc3a5a4597dd576c2cb497f4cd16572375cd2efcc9cb5e179af3e19b5fafb5d6da17da63c2b3f2f3af7fe7eeb78cd56c180c1a03e6ddf84f9918323160305eb878912d58ac8851318a0ff3e2e292a136a05c6b91f868a574da91ce2122907a8e8a20c9a0e1cd660de3edbaa873567ddcdf4de4462721d72d8f9692d7bb41b2f8f3a89180ffbec519e318fb4835e8b2fd187c5573133525adbdc9aa6dcbf966fbaed1ce39371b7374648fc47c12b561fd6c4da23f0c411a8254eb0c4878de9b825a6bb5010933d14d9be98cf95953697cd05aaae3a4d5fa0e9f0adbefc755535fd4477d1d8032b179b4d8752c014a508b4d2fc6d35b72be1a1c9486003e80f87276ace152110ec2afcc9014c407215058a08928a4d8e490020d98408826acb060c78502882736e0b66e07caa1e86b41477374d4bd5a6a2979290af804e4f69fb5d6ea4df9e65f0a08e5246cadb5628d0a77af7cf3a324cd3e5f993f3a53e66f6e0efbac29cd47eba63cf6be5b6bbf7ea97dfe4e13b3e803a8fb3e16fdb5ca82cedf5a6b5557fffd7b6f7681b5d5da5a3fffac8f7f6ab77a23d4d15aa7b8e26bedb576d4f68822b9d36cdcbb0b74e6cd330c3286691562e28bc73bef9c2f6b3df0f79bf3f3d7f9f6eb7d6dcef71b6d4e6d4eaa7d25e1deb73fc21d7511b118131b84b036c686aed3327cda0ff1694f82864720770442e70d5d67f20e4c5aed5da22da1396f5ca7a9e7b0187cf34b1e8fd3d4e133d14f15baa0420baae9ba49a554aaa942a554a00a04c1a6efc70c1f37116bd239edbe39bde75e8c31c6d8628c31dec9796b8c31c63fb5fdbed3c9a232c67f350dbffd7dad8fa9259dcf25554c81560c8324419ebb4f1e987071f2e008cf11d997a1e8b0122c3ddf09079f07fd702508081db607fd7065973908881bb60729b9b24b1ba4e4689fb6072929daf3e64ca04a0fdf93567bb19637aed35e698a23c0c10e4fdf29c94e0ef5c99b739e504c26c784d529cf5a9b521da10255e090291d147005ae708e5782156bc5ba172809c10a5921cea1f3216c852d223c0ca192b7212d9616cb11447c367d4d440911241f8bd65a6b96162246d9f483b903142e6d32994c2e97994a0997179717530a627450f01d1395e5c93a37474779817981e921490f4c6cf703a353a9540ae67b30ea44f01a0441f0c5ac8160b1582c716cb55aad51c5dc21869815312b523c39277f482bb48b8b8bcb0a16d9ea80450b162d8cc044581bb4d073033020b5288a22f952a142858a970b27b2a626d363f202c60b18437278b8e23364665190b512183018304cc4ac8725b24704160316c34ef962c8c4e86434a24f2626132b2a72831ac8e0063119b1989dc2f34d913143868c2247e4191a0683c166cc20924d20232323030208448ec89e6c763fd498a95163cbd145925039ccd0cccc6c073292b353db81348043534343a391e86f3b90111cb28e910ab46e5063a3c6eb6ce829fabb771d66c409234d582341467c8c2cd9f435cd32b06163c30611a2ba1d88c8143be555516c6ed8d830d9a576a02239c044d813dcd0738649b72341021a76386fdcf06eaec31c362489b61de8c8066ab8ed40476e8819b962a40acb8989cf818ae8ec523b50919c9d6b8843dbb061c3060e1c2b22376edcb89123470eefe6861d523287b782dd393687d1cfe1f5f0fc670822cb29069f03f5302bede636bedd662c0015ee8789cf1604d97b8f8ea2dc28f6e85e1b746d1421295248b1831dece05e6be55a29808064506228c8de2656ae1db2b789956b87ec95e2da217b9b58b976c85e7bad5c3b14c4817b748f8ee63cba47f7e88806323070e26466af946b9d24d92be5da6b93ec9502058945b24848b52259248b14459369e7db39e79c4854ecb5d75e7bed104ed29c73ce59e79c4944494449444944b53a4112c30907cab516096905760517c9da0bc5de15b4606836ab52a58ab537c9ded91147e010498172ad93247ba55ceb24c95e29d7ceec9572ad93247ba55c7b6d92bd52a020b1481609a956248b6491907e0220234237675d0dec759264efccda9b64ef6ca88a09aacc66b39a295c50a10595f5efc694281d157c37d6de7b31c69aa67579e33aed954cdf099552812b56d8d22dda3f27849941a7f9535ff4ad0c2d2e1880e5b8f8772f302fb6eda7edd813b58861dec59ee38b2f712447152d54c414c14296737db6af6256acd06432ede743f2637bce70569a4c9369324d362b3559b7e74cc80aed6e23c312b3dabae89b82057964dcd979f18fd93e084a4e254a26134aca349f51324a0e6709a60f25e9bf5e2e7e8785a3cc5cbcd0e16cb6c14c4143f725c5403716915b1b51d2137a0103032f3939309e14183017580c578c16994e4f9d984c4ed79a3812f0990c1933c2caa2b1a251036c52c33bfdfe45a4642a045fdea24ddf84059ce0425fb1db5aec61c0833e2c2d4ef47a50c0613140cc2898f0752ff415370c435f3177307d3325f173a28ea13dd135aef002ce76bd16b950ec31e15bf7bbef48efa6fb191c371d86df04311465f415ef0ec5909274fcea8b62293eba00d15d01a2fbe5794a7a620f0a7f82a8b3f1b388ad133e147b4e7896d1c6bf0adaf841ebb3f1df9c2764158a12feb3f11bb1f1df9c9e1b64e39ca49ca31c5bf46489cf0e6dfc338c82f8d517fe135462ca00a2d7170244afaf04885e4db0bb8be993d88380ffc4ef1020a2c403884e5f25d94207ed6464cd726bf87c87d341a8a5d22843e3a4af5481a055356ae092377e94c46f0031cfd057a4579c34b4d863c277e227e2d907abc1f110b3fd5ca6c0fd50ef66be7b2dd43d82cf0088866d1fdf9402602881ce1c4610850ee6cf03bed6dbe125dfdcd6ded80f870571828549e3017446eb348cf18c41c05ff48324f951ec870ef29553c78d6aad5f4e9daed378c0f3f3e4cc3e24b0a70b7b8ee5fd07e29bdbfaa8343f7250fd3ace7dc5d8ae623975102196631f4333a5f1c1b37d549a1f46dff6a01b2899ebf843874d3f8830490f373f7c2e9f4c69a6f947e300ceee4c140625e948e35fabb96eb5abba7ab5545bc0fb7e1853f2cd58692c6b6231fd5ba65a8ccecc9c29e4b230378de9f45d1973b1cd95353a7076a63335f88bfe6c5293c73216824de9634ae9dbb1fc2a0e738ee5cf1c87d13fd1f0955348484888471df5d8637ad33869f951d99ee4fa6e6b6a623192fcb7b97aee309bc2be4fc237379e40ecd7b70f9483d02e730e4984a86a6cc464902f71cfd69cf33d1ba0e06006db9d74096c6ccb9c7f50ab7b9e34e0af9ef5e51752a7250cbbb43f637ca67b2e35b9544c49de683ffb52099a3c57e875548c5fd8620903b96fc61987372f6ceeafe841dec4868c7dbf7cedfbe4a7def4de77a9d6be5f9ef6fde93461f8f55d3596305bfbd458c2809fc732448de5771ac39135babfbe7135babf4ade088ee1c6da0a2fecbc892bcce9b59457a83b8faec128e706840ba128474309db727f630ec4b96dbae7b1bcaf5d7c471df5c62811dd8f6fe3fc3e96f8b5affe6dfcb36a594b21dc3e7dd08d25a93d9e9af637bfa6b9fb5f4a5622f35ad7c074f759cb9bb7cf394b4b6bb8f7de7bdddd4671f78944be9aad40ecdf7bef6de19674be5a6b75bb2d92bb5b77776badbbbb23d1d9aad55a6bad5673fcfedad75a35ed8e628dd2794c49f2b4b02c88a909d941bf1544862f02dc8f91afc43f6e23c3fdb860109cb81fd79e234be6b0a2af7429926d97a24d9ffe70f88725237990ac07b0cc06d5eb61bd5afa332db6bf3700ffd973cc30d4395110c5ecc0572ba5d8035f7dcc029ac792528c29a5948ee5fdfadb8a29543a038ec10bbeafe82b3f2d830ff7d88befc55a38e99c934e15b6d75e7badb5b86e9dfba40193e7a8046c5ed8d839190100083317000018100887854241160449a8ba0f14000f5a76465c5232170824b1401c464110c53010c44010c640084341ca40e50c53a802007c613884add2807b38610ff173cba2eeaab0beb1edf2e2ccb9c512f343ff8cf7561241b144afd16e1765e115ddeb9b439cee38284ce03f8c5b712632bdb616f1fa1ffefb53101126afc9a753d7684ab8e9d45ad26bfc0dee3463ff1592a4948eec27f6e4baa2b826d5952dc24cd541ca9eef6508be27c835dfb3b47fd220704fa64d1ae63410cc30809b7f33a1448b3a65d9336f569813d7112d7b272ee81c6579efad80e40d75246410bfe4402de463827973307d79fa3345054f0f81bd8263ccdb9deb33ee731f911db2a54a71fd6a86d1b7908848a4b260cd3c8c26a04014eafb03215d59d69046824b6bc696327a69d8c2842449495ac443966e7bdcd9c88cc77bfabfd26b205718b869e9ed12c4d595004dc3f2ec02b45d5c895ff1d1a8515e4522ffee05f32cbc93710bee3cf653ac4978b8883a8bff1193c202848e088e05257a1b46483193c0dd0457d15c5d737c1e9181424db2f64b039e52017862ee852cb97f271ccf0b29e0d91f0d11f973d284f92629dd479b963364b9a3a42a35a79eb86043387c02c143ebc9562804a8c33652bb800d0d7ed939eeaf6622cbf5afb058631c91c712151ce1f69b195e232ea2d925fbfc57f2c6a7106cbebe1fb20a1aa69e2dd457612531a21ff5e2cb50b150c6e87ab2be26e27d6d9547c486b3e0601f47ce6b35f193d59c03b72f4fc32688f1aab3f7f42efe1a0de0f581f003089e20bf01295a4e52882f8ba857ceffa5e8e0c531125515c704c7252415796162b3cc89fd86c4a79913c3fc65349a391dc7a66148ca96f98d0e07faa7269b4782b807e0a6634fe7699f2a825b4c3477c8680755c323c6eebbf4900066a90d4584a334c0e4b260eb708def9f0a4f81957bce9d71b5fa8ec64960c03adc21daf466ae11f895328d8e4f1410a4e6de3d1a83796e502314aa42c2957493246ee0cb9d5b2a93df6e4e22ec8be3a2668c89b69ed83393785c85f4ec233d82c74b945b5479acef8ad5afc850e3e6a386f8b42bc1e5c92b3f5e919a08b615227e79ecdd9b8ee9313d489203ed9a7630490e6b9814b50410068d5390b826af5d07e317ad4ff6f1129b0201956e9817c829a7e259140ad4679d02462706345696dd8df065d5632e7dd843fe8c05848a86bac6c34368d1f5f1c593b0286aec1b90d563660a827cb00c72713d736249f473fc58d91823f83b643973ac9f97811598e2a7dd8c231e672ca80c6b4cb2a5945f1eda3c8d957e1f5621a062f6cc4c058b8b354d4a0c63281c6aac216e928fe808d315e5ba73bd737b6ec56edcae19cd813c8fff5c097d12136626432a78d9e6b9d5d256140e7b29685208bd5ced0aab760b25ff7dba3e67fbfb25041e305456817383f97ccf236456e04d8303943945727e231aade5943b87f0569cc506e3c790fef6abe4670d350b12741f8872c1696941b7d9c5b8136b107d7fa77243bda1061a3153abd6f00c927ca7d5aa7501733a1c04de310e80b55e274feec0dd0cd1af642de5a17398df5e16e50766d7cff94c70379a8a1e358c91326c180d6ffcc676279a2ae7d1dd347fd110fa8119c25be313b7f749cd56da7d42115215c287fb68f2a11664e4ed69ed13ff2268fc5ffed8d451cde55f59049d6c379040d17ecd2e164c87cfdd08d8533b6755a250babfc194defa0a65eaaa23c468d332a0d82fe57494b4764b771ce0021187ff25ff280d161567591ece86deea96d291bdce95aa3e8b482c974b5b2930f6d5a060ffa28f511a8eac5aaadcdc5cc78483b2e86ca88fc162870f477d120033e20349d74fa11eefc1f0644b177ad2bb1f8c0b682b20b7c3933717e22bf7a165585af93c5da5180c983f4667e1ba1663447440ea09fc4480ba715358a4c8580b2a3c8305ba43392a2075ee0482f9108615c33bcf070249bcd606a04811a594f710affd78439070c72c023e5ca6d417a7b85dc64fd299344d355bebd542a240cbc848d4f5eb53adf72a0207041289bc72762c34bac91004a063ea544217e542b5afa80f625d558784a8caae1215eb526a89da7cc8301e55ff03ab4d9f5357c8f7953ce17bad5e7f387300fbbd7af3d891172e992a68165334c855988b9c43055177ccc153c4ca17aeb949b9498a033d57951f26d222412f533c59624bf70877720cd66f6b3a2e1cf162ce9333a3eebbb488c6fdc5089a2ce411c706c557153ce2b1d9f4f10742690b9a83983f84eea2a0631d88fec071831e80554ae7689657dd29c07e31046d164fd5af8446f5d72aa67e87b3cc21f999a54d10372bc649d84a67c0f316de509b990142d1c308df1ef2ab93852649c1ad309541346482a49c07fe34c4a8c089661911f950476fe4681aed4d7447ef5865f11aa8e859e42a951e652ed375ba36d12572ead9402205b8e10676e1e4485c93874197e0d1b408b87fbb854037c3c6fd5e45480b41bca44b5a080a9f2a087f7283873ea6f1615a68eb1df4ba33bb1a7427f306535fc3448f487cb33496e368028b65c8accb2012a6bafd85ca92a43f59a23f921aca2905c79dabbecbb3a410e1dc6a4567d2e353d3448f76ea1e67b9794ad8f3ef0de559747536b54452f2dbfe70403151e6a58531958160d138ace928117fe3644d9fe58a6eef8b0cfd57f4c20861bd62d1985349070ef2308d382e423e00b6811e8771262149a0ea87084acd42095c7539f26fa07b6efacda734cdce650563c3a6e352fc32d748b35a182d7347ebb241d4df5a469847839494f3efcd0faa1dd2d8853ab07f907d3d8ea30f260ffae2d42c1af8d7e89bbbfa294be274d40e801a24eb658e309adc6cde649c710a07d778796be1829e78def075ec665bcea621f8ba8918830dcb565dba8929c3fe91610a26f1a0a33b3f4d6cc6ea549d699a257751d0ba56f37abc38f59bcc56c85447ce104032350ae0d75cbffc2a2adfecc3073a9918134d489888ae2d991737a0a7adc2b9e767070169f4b4a0c96914955297298f70ded567d3c56c2c76d2bd60c147a0c7897dcc5b590cdf6241ff8446a8f625c56f6d75e34dd756506155d8be889c85de0639a5b07c2bbaa7ed132317ea8947731b1274e29ba516a2530b971d061165bcd93cb12c55f78263ec5b1f44e704b232d8ecf688cf2bd603e4eedaf1478594fca9b21899c8c321fdfb291c96ffe184f3d9f4b0bd7ee3e9309a2545bcd490636102c6a0412316e7c8638c7ef3b8d4926996ca312641e9fb14fcd84a3e17ec44d9a303b3bc5473e40b129307ff1e4015f26d662974654c85b6e062a209d3969488e3a4f7bd935847e7242d0d04bd662d65bfc7fcaac08ed1d94a158762cdd48a7a668b06f36394853da44eb83cb2983860808742582dec2d4f55645966916b92c231525b443ce254f608ec1ad71bdbc6abd571bd4ab4cc4f41c1c3b7f2b2577579ed67a3972c383afa4c1df281d2b9cfdafb965785d439cfd16bed7da1e82fbadfed116ae9f75d7ba72b532349c0898587e51773fe3d1c2862edfd6ed5a01453b0a7a8da393c4ce27fb5b757680ec8a47faabdd6e85abcf1bc53057ee10eaf0ee7b134818c1607f193d87e934ebf6306c5dcc6a0dea91beb2441f765b574dbb4d4db2d7fe489699cdd9ff864acb9503dad6ff3dabbb4f861535bff87045f8fde5fffe5e344ba9a384aa3d3b2c18591c46a9c2479266d5c1f01d7a40a0f118550e294b240ffe789ae9b5eafdb3bcaee80c9eb429aa21d53ae1a230f1f846abf55267e5aa92a05de4297db929719bdcfa46c989f66e107f13908c34a61c5f63d95b18aa41771aace37b22b5231d137c3fae76e7a4fa154dbe9379075cb993f646568362bc418b63045d047e2c5a76b1edeaa6395fe87fcc7f3c51f8864444a263483862274473345e05e0ffedfe3d76d85d89dd7d999be108da30c223b84eecccdd11a5183f2a82513a2b15ec00ac819d0db73e1bedf2a88e1fab20cc74e7dfe6e67107a235a10896c5c49b71750d9bd70b8d8d35b885cbc8a04c042c987b039b1b85a02c1dd924e2c8af9f4fab756510a1078f936cf16ada4f585f597804efa67a4f9f1c17c85fd1531a1f3216f7890407d6140c8d40ba7f882aae427979f4ea89d33938a0a874339526943f31483656685f0ef034806843cc054209171426748449c06e5960a7e00c063885367c2f7382067e20e933ed3b531b8393f8095f84e7fa5570447f0a071a2e93c81cde10d2a697086489c3474b9e5d00ca3fa839d3385c595bc66b54b018bbea8b3c39b9d0e47db1e1e5f5d94986bfa3a31b9a595908e69652957b86748ee03643add9e1b52c3dfe71db5a0d3f99e8e16849ace8eb018453c59a00e4d6020b12aa5e712403da615459f62baacd83c17b9148e1a19239b64694f5ec4cd9d18d0168c8a240b95be51d4bff68c32fd0019a62b5c82620e1bb386493ac9787d98191067b99391f65635081097b33ba7f122a92f6058fd2530dd67ba5effd8173889124672895ab1f8a1a66873892e0ab0e04e8a5e86860c42818c5fc9d7933e5de89f02356d356beeb75523a0bb1ee1ad9d4f70daee08dd4b2771903ff409bbc5954c7c672a22a55c520004dd3df2560a2ad59042fe563a1ff163229e8add3a81a939e0606465e0bf6cc3493609526581213810de48df73343f1f80daa037f6e94f9d6522372c34fd06a30be5efc0669072c28ab6c8d79d2cbfd0a325d3fc97931cfe2107bc4098515be68b3b6e8ea7746c8d796f4a7df63c09656ef2b17f339a21fa15b5d2551c9a7b3a68eed74d774f37fa7d4770de8e1f4d54ca614b3a9b7ebf7f5bfe074ba7f91dc8bf7076322c64c0f26d5eb4f37322fba75cc034a17fab1f26c14cb094667df32f00c67abbaf59f39080d7c8339819a1556902df448c0788ca0847c236658c77f9c4060b316cdd85958e1a0e988b11b129e5c074b913c4ad2a762e0b692bbdb35785966f89bed2bb5b75d40f9f6e9136a57aef5247e40deac29e43e0bcb042e92d054c752f8ec5bbf4133be08e400c5f443f9ffa0f58175f61071bf1b8b11f56c5b4f88746e680b9d2e88c611f6b7873f247e8e68d9180f1879fdb559b95d584583f054d07abf3c22c2d71da0a2a18f5dc044f52e326d4347d25810a19fab600877edf2f0d52a3490607dba25e143d325f762a1f1cc692afd47bb2615db940f999b913eeea4546a2bf6023ae0cd2d427ab7a7365f705eab99fd3255845338df5547ee69e275abf7a1ec8a3559ada64587042ffdc40961e44cad35e78df8b44402707dcc8aaeabac6680b8bee1c537fddd784345ec8e9323c252c3cfd8b7939797ab637e22a9867db495ab702c90659610f0252f181402edc85b164bb6a2365fb00eb3b17e99a704f3795f0a96e0bf9294bc37e73acb04c4303ea39b48a5d4480312d5f46a3defedf546bbb855113521df7eac27b9cfd88762de1d3fb17e9905346c45e9c9843ca0dcde4c4c65437a1a54898a142e169497ba1f88f88be11daef61764908540d1aaaf14f3058d4ad50085a67aa7f99eb9f61c00c14f55ee0ae0da5d7c9841003f9c93dfb7f65029faa7b39fa9776d8cf2edfc193d86b5fa62850163470898e3df29efb5c6a32ca6bde2367d6dc0999f4ec91913bc533641d0051b659f16e868b8dec11c3e06c827091512a7ac907f9579e1043b9efe70d059637e9d53767221f64d7f97b54175bb47a0c2345ee36fcd7830666e6e07158a7bbc00216343db8d713da29d616687a63fa8e2b67e7c6dab15e96064eb59b115526c8919d723720d66125837a6cf273e61e5a66a47180a48f2d025f736415c20d4581ca834281f0ccf822f619c9ceb8fd6cdce8a93b8c0d15937a27a11c6d290f3a4883004d7752a3a59ebb9ab360496c047618365c5f2178a3c85140c0b3271c10ef5711fa456e3329d146243981b38e62224e46c2375b8f96674ba6195f7653ec3d35e720ecc13243fc956f89a628aa7c808e0ea1c0b02cc34fca863e013c32b8dd8cf5182008aef9fb24941c50f8a929cbaef69e17771140607d74a64f84c6746e16ba6567cab56402ae0165f609c3276557b25ad499ef20d05eec2212838698a59285a7c2eb8eec7b5474ae606c3deae0a5e32472e599cf53a64f95c8751c9856c2f6573edf7e1d04d7e050bdccd0f0d012cad98565a1b27a03aca3f2c86d7c1810e89671ce6e2c1bf5f63b585e25cf01ee660c3b6f280d4e123f6c863d7f280d6e12372c82b9796a462756909f734d0c7675346ce6c739a6647b695399c8c73926d00a94742420e05ef0b3669892948f12f190181c9dd3810323c64b1c345ef183b79cb4548eeea641f50ff3013ab8cdbf7eb1f8c9669c795339b859fc62fdcdf583e640a8f865031c6022020b2e3afda2f69d27d17d9610d69795b7aa1df81bf5dd484ddd835d13ef60d05f68035dd76da254f276ba0d2df7c742b55e3e5590b29d681b585baa3c6ee35d8e4541421720f47818024e9f7d23a857204bda300b3b3df6e819a85cc83567e1b84e028199ce0da63e0098e2514330781ec15d0a7a901a6a3bf0e72dd0fa16607d0b05f07453aeb6859bdf51b56eebeba0b88e8b7a999db607963127172e2bd5ca1db00ee591dbf8dc9041b18439bb7059a99acf60794a9f03dc37671cf943293849dc6231aebca11c9c44dc62132ee78f86271dcaa025c13e1c636230aba3b1197fce3521b0494f27e25e8e310356a0f4313061f7823b356248aa7fb8b4c23330b73d46a22581c01231f0d38210b4f2e8a393d8f126080459df8ad34840d603233f28778988e54a28ed28660e9f6196b9728c9bf7dfb9a0ac7cccb24b0278e66cae40dc1bd57dfecee298e19052cf1b3a0145191fe4f7f2fee4d8e05d76a9aec225135b7ad20f6df866ab9ffa873768afba0ac851512604b91aefc1571951661018967041434b846bba9dd0ecf5541f0f1df7524833ef8fdc5ec2f2497df75a2f6ef3fac2bb8eb6fa08f9114252aff5fce602b3acb60ad4fae9da4bb522922303a7e85ee7c5cf2f4583bbbe40a2aa5db8ed3a5e243d1369cbe47e90a27f22784593516ca4fce7c8722a1337a1e0c5b1f059b41091ca6ef7fa4aeefe23551d36f825e900e6017b39b2ee2447aace2bdf21f43f1bc775a9d2d71d1411e4a4e126a8e17d86195fecce3113d192a4b32b3262052fb68c79eaed77617ae5afd2e4499609154cbf13eb97fb37388ec7999d97aabce60352091bf877d5059f1207f4501df2b49cc44ebf11b63505189a5c9c75ccdcbfc52f5ef1458ec5d05a0e4ca9f4d41754f1999c69e597a6b13a39cfd33a39c59337362e16a5218554aaada5bcbafc3c667d6123360aa0d2fc028bc1cee5f29ebe792a787afd6b8db2d44de721c6396847e85faa8d887897a58d16aae96e40873bb9836c921654478aae956f38b680288a0d14a4c331646c4dfc7e029986a243a7e73014169fe4b0504595aa8a1bce85a9ff9dac76e52ed1a1df7112a2dc48dc8957d12a28013d184f225868b3eaf92f32ea6b38cd2583c0abdc7ee4c2dd10580226ed32112d6fcb86bd10c32001088bbdb138e0ef03f98a18282e6f1130e8c19b672c1eb0c16f3094ca04a6a0cec14b6062d91884787d61eec43b088d2436e2c2a74da5842f5c9752c3d2ab90b553a876f776766552ddc8e67c121a07055f70871a0764aa160ae80d78c0ce5a2ad079a0754bf98c33c5045a924d1a410f843ae7e086dadaa1049c9c2be03962f701de91a6e0cb1062c9419b8fdae641ae7f52261cecf652fe32e327cc4198ede52750c9c47289c7879526e91e9fe0d8256bd708492df9ddb6de8a2109adb1cf83847c950121b49d84d10df32b87af8b5468ec0b81fbb9aa1202a6573a5a6870ecec520fa740602dedb6d157c02fe929104a5f5667225c4f4862424d1961582d8d2689850b6815d7c2909b1c40d633ee5ce4420db336e55db8376c46fd3862ba581e01a5201d91be33ddfde6ed9d3750f3be4bf90b99c0ea44ffc41fafcd454b925c6ebbeeb3d1c3b7866717486e6db56104f4a68bfadb5db07b4f4e82ad580e9957abcb5769f744f12b11cc5e12eb09dafa2e07f4b9f30f7db0fc7735076b2a704058d95ddb9aeb61f10482d101e5823188c78019b071c6679085099f5c2602b2f491c52dc31c71f4839b2d10b9efca791cbb8fbbab629b3382fd29b908c72dc407bf17b72a9570cde5cd2e76b644caa40181f800b8a3a00c9fa5d420ecea36325d6728cf9ac72d0479c22721c1e99e05a2b969ae59fdc1fa663c8a8191cdd08fb92e1cc4dadb0651ed0e19f375f7071e660379d313500b871ff50e934b8a94df1e8b2bd4384864b38141b741b2228c89aac85b484a074abd91d25f2bc4dc4766824f42d4252dd48e28be011d42916fa255f41b276049d734e002aa8deeb35e176c1c69568f5f0146e37a08824476ae1abce4c720bd5f19caa4733b9755a2b83a9077a8066b1e89bdac9e0ee20d7452710dbecb073d8270459d2a2eb4a7a693e045f57f50be79278fa2d1f073325264ba2989c233df229471ba57b13501488b55e3504b5b9443c41022fc3283582ce5d03278b9fab6f9e148e8400e16d88be4fb370eb6b88a51237233194ef63c91fe15c1710300f3247e48855711761104d7cd9dc1736c20faf150cf0a8e49030c1de082cae9b9120c75cf5476440ced2df3c22b965505f33708710d0870c348568b7bd8008f06a534b88c2b46dc28997d640d1111680b5a3362de099ba548496a66ed5bb780b1acea2bf00d2061e6a47f89ba0ecd588f20e64b8146a98ec799ddff6dc647a6afa8f1d066dc006d2b4865d80270404a39322bc8236943272d5e451dde4258edc942a2cb01f5fa237d8b55f77c894190fa96aaba5ec1de8f5a9451461eb1a8fd484afba1821718501914aba2e5f2d955f2233c489477da2601eba8d4899c85902278f8081aca926836cae881b023119fce2008dd9d5af5a4dc10029ca0d38df700a329e0259389be7d4da18780f779ad24ab3ac80c7aa54f30aa1a9330616daf41d0383730b3402cf4b2e8faeb9bd35f4d7aa0b1b29bed5aa64bbdbad93dbd1fb99ef353d3f6f7063be03193d6300fb1e7e425b25717c55386a507dcb3477b7cd858f022ecbcdc4330c528c84fb8ef512b9d8b58f430f572c50c150805d775f7df091a7b2860bbb5737ac4f20b5c0495c06a4a2d03849a51640f6b68f2b03d07361fc21233add22ba2e5c0ff9133b890367455b84d53d0650274efca89df068442a33010121140f90a524119e596b17e1a56a2e8f2d5454f2a49ecaa469936badace88de4b77a6bc98f0711ce411e4d144627a2b34e476fa4584b9b10c632f664df1cf45707617e5e16173c011266f2b0361d486914dccd58dae157ed311fed4582a4f7fe8c94442c8d0baa8e3ce3e49eb0619d2fd4b6bc4b0d6d59130be6d8e42932d59db8cb9c2d2ec8e6d7373b7f1d7e482440dcd0264649a42af2cf2b89d42a520b54d887ba6519bb04310c4b845bd27f040c69b50000df0d409ed821904a877800d117bec6a388f9f5bb86f60fc5a21360f0d5cd72c7661b108100b23b43d986786b0da81475a79252c977e343b0827b02a62823a6487432cb58e0ec4a1fae1c8726df41f9f80c2015cd0060b2a03fa3be04e1fa4e3634c96a1fca267645eb8d541831dde5199173ca1769928119d9ae703d8188cb1d23d1257e5b0dddc286c35fe85a10b836d39eba544a63b8c6bbc9c4a04a749ec9c6a564cc06bdc1e04d269a4b21251208b6a02d3fc1458319a1ac8933dd05e8980cea4910f4cdaf853a709b68ff95a22508df64f908ac0698652953aa5f0174d9fabe8ec79fcf81e8405f43ee27d78da1639720f33b09373baa84d42334494d6100830d924cc2b64144258699e2fc219ba79f42e0e427c9d5cf08d612cd16953f0538534fb8a39936f1dd2f0004592d9be638c485c08a26afa54f81af670114a517ef1743da55ebaed0a20e463451d38c660ef97d67d15f4bf296718fea62d966f6d07ca4b0cbcabfde44b496d4a9c5dc15ca8e13db4674a30707b0e462604d38a54ec20d8df670f21740f2b4bd0c070b9143d220800b82b6036be403e1c56cbf9e6491492fb0c8bd43f56412f803cc6c284ab0b075a3acf083143a41e40538702a0a30aa578e9360e496168a803b9fb08a48cd198666204aa9c0ad5d8e7f33958dca227ea6673f4c2703f52951aa4e350fd16b78f04d017e3b3277eed4856f5e201badc3b1d29c64fd86302e551c1da105486a13f57ee8a044840983a37e52b81a4faa9e17f9e40dcc3b992bf1f370960d984a0345f295f65521dd052755129390223f9b912950b93806724d47c109c3767fe5ddd9079cdbd52f20a664062590e46e0cd68b365ae3f04e204826dfae3833623517ec91b27182d26f35ab6532fa7962bc18ab10de18aadddf77d1ac64bdc3301ecd8ab0160ba5b3608f1e287c002a1928d1eaae1a74cebf91986f9788981bc8bcb72cb145888120f863494c7b460af2863f0eea072af6dc8ff2d3c5005b2c09772793113270b7b2d8abcc1bee3c743a6b8bc71b8ba512abb402a02c142ac0ef53455531c7d2046c6b67e581dea0d558c9554e0446c276178a1d6f07ed32419fb3cfbb39a8d10538bd69611808572c98d7f98580323b416b326539efc1a650341a1db25715c8ac7dd6b1a4b5d03b380d7f76f4a880d0f4753de9de4881aa0c2d0947239e0143847872d793c815ef9176c3508950e9b241649f7aabe641000e09a568158792ac27b5ff6aabcf597c0387ce79510a300af08e72ab985d3b240feee14fa182f57de5a97325b0cbde035da716784e6710b41e59bf039cffe161b8b507f2af1195873bb057954c0ea213f6e41e4cffff1736643dc934d78e05a22587d419b2fb08357290bac83be9f4cd313f9611f4988ab50ff45ef3103ebfd43b02bba0ee4282fef49ed54a78b8f0a11d238ca579dd00c5e8976d85760301fde070e82e46ce3480ee4edce85a8f1f057d20d432365bae69f8399eea1facb541daf99138be8af7f8d26f602bc617f73ec8e736540357b820bad69effe052c2df3110cec6b92f3418f4d2197e748b605022c5a5dc5739b0a0af05436f100ed1310a8d56a41c401a39e6b29a99e3def8b7e6b47bab423719e14b44bed6870c17a1553ca52b17dd32704a8cd1c9474abb6fd1b9c9e17d933f825d75628bba8086a00988819365d064176732d8a29fd7334d28f182cf030ba0347187bcc6ae52c688c7da074ff5f51b81bf78dbde34fdc02ad3964ef5084bd52d69e3f70cf25da7b741fcea2506284f84963691bcc46888aa6350e213219f00bba9878f159eddf05cb63e1d333bc340728c409b9116247f4e02d6b5fdc917086cc58f656b942eff4267f6e9b02046db02d7911e2c538d26dc7ce0cb33fabbebeb7044db3e7d0a8678a7d62004d8babe04d92685890bcb84ef8a9f49fe8f884daf3d3974104e54e5f478a67f29f1ff0b6dcdf66c4ce54224790da0382fb84e01e51ed068f647407aec2304125c348650e6810eac5c6cf606fa8843e4669875510b6d8bfe4e1563fa20475ce7a2daf54a1cb50b4d2421220220dbd6ef3556efc120a1a880d884d990e02e1e808f30873e16c1a2fb327af0e818d8d18938f7ec7cde01c9f171ee07cbdb2f83ac197c7f78e91397a2b1159f759df5bf8d7a79a6d3e513fe39f7c380d4abea036b4f277b8f9d6761f582150d5899d25df210326a573aeb7a33180b37276882240eb302c3ebe2af03277a3527a2f4e86f9dc1c925fe3bd4909566353938d32961dc738725dab6c1e8c2bee084e870eab0ae6bb9a351d9d27861f732f9caf8fba59da33e789bf290c4201b21e877fb46749b368e7273bc364aa2573cb47249fa9f41182846f2006644a5fd6f4fec95fbf8644c6c7eb161f59ab8e8fdbc4dbf596dc425e82f16acf3bfa8afca2477ee5780425abaaa04c7a594ecf7a9561c1a091faccc0dccb1e26cbf553a47312c194064018619df033e9745812843300a43569a59b0199be32d845a4f65c28d3a0ed3a8a62433a3b248ab9264070b7131b41a1e044d103dec353ecaaf17472e799beaca62ff812d99f43d80fcafa909ccba2e47824ea55217f4a777a0b131d2f7ecceb024118b5d00a71abc225de6faddf10af730fae80823ba6f7b5a5dd3436b8da645210d95cd8c1c5ced4ab321dca0e56775ba9a1e6eeecc2c9a05ac8292aab50d7ee6f4aaa54a441d233e04a3a386fdc907b6596427636b3a4481ef779f9addff72d08caa7e9202dc11d46cc81a8e8270617885cd7aabf027d21fe1b7e96a8ddc41b04f6e3d701d9c5399937ece21fc21f3655e0f4e9132aa3e2a33bfc8632a010dc097b947a8c8f7f0880b18c8c12ed87651246d11116e09a5e529750a93a546cb152d76bc593213ee80da0a484220cc229f1a99bdf0fa82556d4b7c95c1b63bd4df2964332e1432c5640a05afb6505178b84cb5ccf4a7aa0b8cdf34b6fc73cf0024581c6f1771250a5252444853ee86a027bae57c20dad1474013d2733945cd7dbb02be22b4f57b6d92cb8c2646765451650d4ec9990699e29a88070699a88e9920f80a273803a5cd220d98bb07122b5d8ad07a9211bdd9f8e0d08f84faedd2439b938ba793dcf4f7c33a37f9f28fc85fa13e56ea467b26ab6d08736937a365ac16da7f3d35964e0adf1146b172208de6170f07838be79f6f95d1efe2d12a888ef504cdea3e4b377e1ed2892487929547aac32d1b063338728d300637afd8e3e6143c90a0c6a864a38a7a1861815079fab1288f29869ababf8b43fdcb6062a60ec89a602007fd48b4b454e8ba701e8613a7178de66634f11ba4e3b3b8555ce272d926b6fc80777104f425cc0ceba725b1056a92b9bfde33192ef341098810f195829a1e762d47044e7c6112cb843bee698d3f186968ad80292874ac756038e4c28917e1036532d614337f3083ba4a7004deb98f241e7897a415857d42c837387ca2be22a4faf1b05381f5934c7d3b5edc4a7a28268b91c84b8a28df977e0534443796686c458ff713094323cb7467084567d2f5d94133d77e6f10242f38819ac3c13adbd197c1cedcedf40381e72db32093814691c12488e56aeab4abc03d407504df67d2c7ba8daaa376a9a7aad28ee525199579e18fc36853ed4c6956839598800bbdd29ebaf5d726a62ed20ca2265912d956a9a96b1e163bd4686c7bdae16619861fc57e7861aac2ed78a3353aa6d11c9f010ab848657037261c984b8301a10c52db4c8ad9106e00820f6c51112528ac19bb15c5c32216ecc8689f2165aa4d658037014a8d820b3d3b864ee9d23f698506f6d058f543f7c37cda4c1fbd6111960ac572b04bde41081beb9fbef8d75008f8ab477ae062be6d50951e5bafcf1ac10bf9cef43d8a8ca648093eb6dfdc06a710a887339f537d23c377bc16622f4ccc5bf066f3ab7446438f9c7f2bdf600a1fb4038fdc4a093c21c911149cd8800d627ed4e82523abf2d8d2d1f9ecacb0f0783c5eeeb276b67b885ca13dc0c5073d943b0f3024c520a7c8970e0f333f13dedc905a5351e7ff3f29bdf082ae1f75ddcb3a66b345287271896f3274309bf5aaec1277797b3d4656255cb04da0d6105003b66c98f9ab5dde2592f96f083a761a734c124f3c4541936de3e2d48702d436e8d5c11d3ee096c48932687c157123c885630e4680a50c723d20d7377092c55350bcb8ad35e1ca4806652d01f3cf613d8c3dfcd14998be3ea9bc6c6dc58724054289f6021e0c7686e0a8621847fb0fb7b0fde6d244a73e75b24dd5a88145d5ede993dc8c84c6006072b40619dd2306cb985207c9d28ba448d3743e67b7248c5d012469cb3a458a3ebd5cee0af66101e236b0fd6f0cbf167f390297ff0def7644e8ec0385f4978f80dcd58fd0caecff58636348cc1ad315556b349a08207d61b506b02a204ebfda62e621401ecf2abac3bc388e74d7c00b0c05b0a7672c34eaf0504763a75d20982adab81ca09fa044d5778512515a968f96405185a450f91fe25cd777e5de3758503ac50a2349568e9190fe5a95960e305bf8c9d5113f204d1a1fce77ceebda52a148116b4b2f3f0d2c5d265b926d3be283ae6b2530eac4e0fe29fd246b7ad81ba6baba0f731186dd346b3ff20fa364008b06c37ff51c99d7da61912707dcdfccdc96c4f3d73a86a429102c0ff129632b0e098b1ae5953334ee5b0115c797f5ba7eae38dfc7e136cebe29adb0603665f5f52abdacf9de2f4bde5d1c3df41819061931e2d5dba4fcba5b50ec1055e2eca5ff6ccbe985b1208a952c4813a6d35288016fbeb9251060a160da18b85cfe8a12213955f4658dd6a452297c11daf6110db57048da85e7f32ce7f821a0aa09d5b8fe84cfb40d40f7baaeb1f8ab02afb9e71b4597851498ceb7faaa61f94112151370d8e5550f84649004e60b80de46f00291c92bb97ed9a005a63d1ce1e410b2e50e15636be6c2970a0aaf2054646b1b9bbc2218a0045aa44f4ba42b10932e070bc46bdd72c4f3b7e7895c97c15470bf9cffb3a02f01f9226eb5f72865158070249479854a050dcf48b9fdcf8ae9dc28822f009f7315024d9a03966ee192d2c8d032f3e3bc141899570af0310b493fc628d92ad90371a590c27954ec47a19c84a27b2f30b81291ddc19fd397f8c145c5f6e59ae283605719105b490d4c8d99ccbbfeed4747bda4977824d6740b876e523fbf78f0ea8efafdaa3a3ce35f225580ab9cb09aebc76646c4f1370a87401faf4622d1a316c2397a682a02c37f5afc817831f6fb0a10869ab08d70e348a97c7abed699ca20d290633a53b3f184138f79a33ccbe2b084526168639c8d38047b97fcfb7d09c9f8f86ab702fe86a7916593508ddd6950f2b32f0f04b142dd4bc2421f27231ad103001a831721656fa609f96cbbf2b1b1c0baf62c34092c73d223e8719b73367e28df23b914d7ee443065723a9d40669aea9c8ca1ce167375c3632d999440a771c9aafd8571eedb48c337dd8b60c8c7b3a1b4d5104f346ccb6981bdeeea7bbb6b2976413919560358129e3a29f369487543c3279e5b5de44a859b8af3ffccdd62fbe13ce908a2d760a7049b5f5473d4940062a401a6bfe1a6a8e4112149a7deff7d94e067c0e37a99dcf39fb96dd18ac14e4dfac2afbb25b90c695fc19e53fd6dd5fa9830810bd91122b337b8e92fc0ce5eca00d54d64f432b6bc65ff80d19c13f28c54e87bea14fd56b20bc7b77147a1620879f20d1d07a262194e134fa08a53c8a02219d868e2ab8495b3efcc52ba18e8db138b987c78362fb17d07de995f9eb223c7f9c09116bdb376b87ede49848858b69fbbd61d09eb189f23baa391659f36cd58c8e190b6f96fb6ac559b7594594980886c85beab2f1a824368055560ff43fe357cdfd778358ec7bd11a96263f04c372757a6e91eeea93a6671e5a3ad2fad38312ed68e0839430522fae9b430aa49aa42db0a3abbcaa31f91d7cd751c50e1a7a9157d261bbde52d8900b1b5dad7e50890b33b22b122fca018ad3cd5b8ad95e03352e8b9f7e8b5859f95058534cc2e41de815477ad32ad06bacb04dcb339075b35a9e0d94c8c2717b12e2dddc85ff3aa527b41bf99692187201115b2306a42d94e348ddbbdd7c8d551d2958cdae5303de3e095fa615644bf2cdcf68bfc970955edeea75f8a408cca2039aaf1e8852106878940e6848968671ac85bb11849b17e89e4fb3b910a6c3007cea63eca3e4630e7013e1c79989369bad0d8224ed80cb20ad905004c700b4d4c70d5f16be2c961694d0e1206964257bef4de49652ca9452ee0822086f0836d5cf57d3b82fc636d5cfd71679f7bd6956ad6e8cd32f51ceb573fdadf55584119d04fbf7105c1062654e44c4ca9e182d5606351c4da0fc7cf9a80469dcee2324c8dceeae9d451925561635af8f082e40ac4c9e01852fac47860c235636ab90c114ceb032d30a945819a6797d430c21c4ca32cd2b961b33384290d65058828c11101ac4a0c5ca4e9ad72e23093e28c3ca6a8441626554f3ca3a98c2119258998d1b9ad767840cceb0321c9a57d782128a58198de6150e29a1c5ca72685ea10b90708695e9d0bcca15e4000644ac6c078f21850f8f21854f5791048533a89804c5119668b1b05c2df05912e2716405134c21c4c2d2592043894c8dd75411789971523bffc438434b8be5160b7f7106168268bcf6146600218895d5685e1f154818b13200685edf08b420c4ca5ed0bc624ee082102b83c1bf10210421af58fa082964a939904082112bb39a572c0f055b00f28a85bf50420a8a58ec09e8b400d8d8fa3636363630d8bc6063636313e1f3b6e961c92dd9eb7ea2c8b9aee185b9b5a64f3a5ad3eeefbd07618c170d2fdcfe6c729c8fdb92f9ba989939cade6c9cc3b086f099999999db8868a81adfb6dee2893ad759062156350d6ada73ad35d671d23422db840f3a84b02164ecd459b9cc96c7b332a0dc7ece50b5fb99a570934afb5cfeac35f7f15800d79f0e548aed27208c53e30cedcfe0468390db9f4516a3fdf4b622ef3eabc338fd409c6b0a4fec5cdb549178b99f89f82c72337333379452c668e110e7323e85acbb1d42d80ddbad88ff3a8ec97ddf1d9df52bbf1be9ff3ac2097ba0fb15af1f7b4769583b2117daf8ee0037bebbc1f15da5f98ec7edde5abb942608bd613dd68cd6beff56f4e953dbc19fdfa1226b3536e421450894d0c64f18271e845e98b63769b7bfc3be6db73d5366da889ce6a9dd9451a9a1619f61a6dab786965dec6959dd7e52d14e9fbde9b14a8587def9d86733db6ae8b9db33ce0f53ed3bebedee3dbc93a65b8625c434410acda9e514ddaf6273be8f8f610f6b4737225843bede55fd1f0f6c1ed8636a4f02f46cc895463a3ab75fa3ba89550a637cf83cb2613d425bad167c2c05181de6c8df5b068e6beeb3413d0a2f9b1abf69d3bfcf3293c984d2fe3df84cd779886182178e87182654717df2e69a3f263c9c9e63fa391f42211c1168fb9a2d68fcb993c3c1390560ce7f433dce8f2800e764b9fd44a78db3f93ddf5b11f8d7fb59e3439750c2d642073980efb13c73b8c4471c40d721d20edf1f8378d5c3f747215ef1f0fd9188573f7c7f2ce2950fffe4fba3d15b61e07b28fedc168a43fc747751d44540b8fdd00b5ec5a2260216bc8a493bbe1f2271101ddf0fbf70901cdfb1359f28361efdcd4152dfefbd00087cd3e0f87eeead947cbfeaad74f825dfb693f25eca0c47bdf14aeace3fa92d83a7a935be473dbd8e8ac3a3aa8de751e92fa9cde573d424bf43ed27be87da34781e6a13fd0fb587de87da33f858dfd5943c519969d0443d54996750996580434dc2a5eabc9b1d6474a8aa53a534474b88b9f0210f9621bbb41ef5868f1506279cf82c877a436d27361c15c92ba9cdc4eb501b064f536bf81d3502bfa4f60b3e47b5e177a81d83efa176138f19a08584e2520c9a88800d6398944e464215f56ee4d780840918bc40875552b52a2b0ec5b4587ddc2cd3622d002601ad0e49c032042af38fceafec2015669c29585a97e6334d8b35869b6d5b5cf0b1deb4608b0b5af043ed25de87da41af440e94bb4b04295183052bf85809a085052bd0f2436d15bc0fb553f0b10e2007149cb95d44ca4105294041ac026082aa433007d40d4dab43126092839d434c2caa0ee5606d6c6e5c4a2273630dc0cd68b4582dcda16d157b54e5f1b1d66be31c8f8fdfe3b31b1b911e3c783c6f4478d444ed331b1b118dc75683ed513b663ab61c72f86ba3c70e0d472df24a6a9be075a89de5692a0dafa31ef91dd5c82fa95d82d7ea5003fd56873a8947d5a13ec16340df2389e7d12738111599944c639c888a8a8a8e18391161455898c660a3531727a213d18908eb02a8ce24fa043414691374962e410efbc2cd505aac0088b526d6d4cd525aac3dae568798603d688d162b0f003cd48d75c7cd60d062d58146ac39d08895e66656fba13612ef43ed233e561cf7498d5fded093c7400f1f9ec70f8f6d75c88747d5a11f7e870e9f1d7e9efce0037d42481cf153990fd0452564e8e20a12d04514e020593b58e6033ca94dc463a0b6cfc77ae36a7508038faa434f1ec3c0f378f23c541f1edea788c9224177811505912fe25951c422aca849507db05dc4ff50db081eea6b8d87ea2c1e6ab3883471252473bb88644417d14418f1a0f9509b04cf4385acf843e412854c4a263430255a2dac08034f687028d161898d86a33ae095d49ed7a162799a3ae477d407fc0eb547f03dd41ee263b571e30c5803aa0ecb7a2b7589bdf20ca839ec7d25b55b330c71400f962b3d821e628607b06eb78638a007cb9506fc926ae57354060840c35157afa456791dea079ea6eafc8eba80dfa18ae07ba821784c08bd31a7eab041de03758915e21350735805bc920a02118440c802822800043aab2a1f1042042110b280200a0081ceaaca0784c0727e49f5c0e7a809780c8b03d070d41fafa44e791d6a104f5355bfa3caf03b549eefa176e063ad014407ec3b5a7558043c107589e5c0e3d41cf600afa452e1e90010191070002aaa1f5382e0004f0780c88080035051fd981204070cf04b2a109fa3e21040c35109f04a6af73a540d3c4de57e472dc0ef5037f03d54291febe96a75488747d521259f55ac08f3b2f9906850ec3bac082edd2e22f150a3d0bb1940d56163f80cd42516ca0ba0e6b037afa446e108d069004a0f9a0d48f161e3ca2f400c37513802741a8032805f5233f039aa007ad0d0ca69382a0cafa456e075a814789afabfa306e077a817f858b7bbd521258faa434ebe47e551b54a253ecfb4c15aefc631a177e3d8100c4a58ab158b5a4550e97611a907f93cd41f2a3ff93e3cf972e563407ef642d561ed37a94bec041e003587adafa45a002b6abd555c127aabf8830df12a26c137fe30c0804251baa385b5ba13d1f5cf2e6053e507c056ac6829891fec0758111a4b4b1670f2305480021380a7f8435c477de197d4269fa30200ab4aaa76a3c5aabd1e3cea56877a900146904066bb6e3bdad6af51125f7c41e4cf36ddd5bacdccb6d32ec720e77abe9bce7ab3d11f9be055447a371db1e038c56d241adc7ed9c539f9457e9160fa19f5cb30b248f64b23296584458d3427a548b7d9caa1eba5d05b5d567627816e9391593ce76f7bf972bb78e6762473fb8a41718be7c026dceee6f2773717f5eceff5c64f59cd937204ba5a46c4fb5e1b83baef75563a65f848088cfb6cee6b21a1346ea78a494d14713b21f74528f472bc081bbceb76fd3d887fd71f852251e412893ca7899cf348e41cf7a2d08b449ec3f7688a2ef39a01f5db1707f17ff0898cdfc674ce8773fdef25fc94b445ba48228d1b5f4748d1333aea42745b0506bdcd4a3eb065a49539eab22423ac468c70a69d885bdfe5cf98718af47dedec43d77f4dd13f0fcaa0b104b594b9f223120a92aee92f08a1e96166d29ebd2ef9d9dc8848db3733d52941dc877f6d36644eb623f1a5455a8113b753fa0ad1f7bdcf2e47728e0715b4f3251fe3395d94f9bee44ae020f2a7d4908ffdac59ed3b9150b0744d2fe59bf8ce1f21fa4ef93cb6dff7f07657eeb30e8673fe3cb4a0ec5c47732f264d38c823c08ddf1120c29dc7542295783b567a166e69d07e2f830145d125639b6bfa79bdf76c67aac17bd7bb30ec9bbb1ac36c62534e9e40973e6b92364d2e2430dccb46275e3522a3959ad190211d07e11c10a81bc5f4552acf46c30cd9358b5471eec278925cd77c29a87bfd109f0f67bde28c97859ca4e662b56333940e99c1c8bb9853471a08d48d027f2302e103b95e859ef3ee83f0c1f71ed4e85b6596d2eb4d292603d89a5a1533c99d2f471aa95243ec9782eaee8635b75989c9fb4951ff26d36b26386b0f91b5b7d8f0a5a06e438824deb635a82ae8ff5f46bc32a8bac309937bd94e3e977adf739f93d732ac696e648de437fe3438e0b7e97ab64913a26783beeb5f1256badd48627cd048a767e3d1d3e9f4ff5639fe44f4d9e99dde1342d4481bd25be9f88df3f8cc5293ed503a7fa37dded6065a1e96df0dca68eeb0ed3ef0410e1ee8031cae6d91345fa51dcf85efafbe4d83f6ce5141ffcea95dd76bd7958455d4bb792ad2780fb91b38381f2acfd9be6f547e37d049dbf8d6b991bb82c6e864da2548bfc633d56ab0d64040a70d687b57bdbe8ac4de460dbaf05ff7663be8543b1d9335994caff21c21e28d72d1b7aa32dfeac69bbe4df57d6ff0df0d2e5ea800c90d2e5ea4a0ca5b99beb6cd7bdbdbb66dfb57a738e09d3b0df18d485bf8dbbff7de7b5beda638b935feb4a91eb41d9cf277b39d935bc3764e6e14f8eaa97372b7bf3a743385a1efe435aef77a7d0359935f55c28fdc0d3b44c80fa87fa773230b3060c080815af610be776998f69e76cd0b4e613e8649d3cb961d3722b3e714a017b38939e7bce075cd26aa206517ba99d3b092a4c71b7982a6e2f7dcd411dbf596982af22e7c26ce5971ae63d1e5ad48c74add6fc473def7d710dddda69040b729ff9eebb6eb9b3ad2f55c6e2150b79d8d4dbf16060d570f41fb916492ac0103a19ffdaca8ceca46a1e868119aad6ad73f4debebb3eb3d0c5e4f427a339ab711c1a0e75ccfd85f6fc2ace986be4c52661e4b30df1ff267a64ae337ef256f3ca916f05e90c378220c28aefc773dff1412fff8d146e9f7e71fc2e4eb27aadb0ada17c221f9e0b5047b9364481b107f46ca36db880c691608f6d8fb33795b0ed89bb0cf78cb21b3d836a459207ee30fad342b9bc104d408cd82769c94e4adb2f767d203865a825993e58d324effd2e5570aced00204209bfe1a48ecb499e69f34ed7bd332db413617ab405a800014e7a4f43fdad47c99c212cea0ddbc50a840bb0622d3405ac8a0ee19cfe240415a8000b4f564814a0505dab1914f4b078aca890a0814321333454cca6f938cb67fa60f1bc1a7e3d75f17075d40bb6eb596076f16cf0192d15ef7d9fe699f1e74cb814c7de9e019ad386e2ba3f29ce97e3d574453f07b2e13e7381f4e726438de6dbcb7aa12f43a6c8eea4bd5c570ce2f0eeb555067dab5df361b7f82366e34511084df44d1c60d18df86edbc0cf6439e445d5a8982b6dab918d75feb1a890aac5460ed34ef742eeb3c9dbe6e3bfa3d670c613c3104540c613c11c63bb795a1433b2e1568fe045fe9c89adb3919277a2b27f17dc93997d6bf38e7605c0c5fedb05e83a2e9fccbab02ed9ccc9730d7cfb48f77ecd142dbd1380a45a973b134b7f3202fe34f48a2feec6e7432f29794648b1097eca7b3b44f7b61f2ef49dbf9279982b618e75e526c24319e23c53871eea5da274baa835a888b737dbdd25cde88f4fb1e6fc7638c1e5d7a8bb9afff9deba2944d4ae70a016f14d7714eb677b784efd25d46f1ae6edfcb08a58c32466755ff90f764082be8047d2fe6f6d32b409e3049504bd314547ebfb4a923f165b4edd3ad2a8c54430761df45edc573be349836ea308dd462ac68d64ab7b4cd34bb65dbc7b969bafa0bcdc52a2bbdd59c94a250ff1ca75219a9c18b641184fdee6d53fd3df7c8fbfe17055a20aa2cb38b7e2893264fa0480982c7870f950d596e3f74d2040cca4a3e7d7a925606a526945f523edb8e5641fbf68641be18754d35ca7bd835a5c9f9e9abec8d6e566dfcc6b74b055473973242e79e1532064287bcb975a819c59f6c87da344986ccdfab52e94be79bd364a9f4fd9697943685e44a9b92df73a5edfa42711bd1521ebc92e237dd01eef3f7dd0d9bb9efbb7a1fc53678880df802edbb7ed3179a9adf73a7109090d0cd5c9b357564fef69bedb60f027e7c20e0c78f1b919b75ddafbbe76fefb7cda68af0e529dd051af86e0fb7227e37fb68e01b450afc090491386978579b9fc9daf5cbd72a151e183fcceddb778b28bc5cbe7c6dea08f48d88e639effa67a7ebb1ae27c3aa5fcd733a2c43782b0b5d4f85350a7cffebf263f5bd8ee74ad1aee965757f4f1df1bfde2ddb14922b0a3b27f99a329bba9e59367ed3a9ebe1bdec0b81762ad585628d26615eb41041cb5f219e937adff7fb0c3a6b6c8082dbb6f34153432f3716322ad0df4411d18e938c763c47a3516adff11c2a4dc5b093273c220ca0db46612eb33333d75c7fa66e83f2f76850171568879aa85963d211d2e900b1d9885c45501b8ef361f3342877a7f19a9a47d1568da4db4f5b6f55a34c8b8ea11d2d738516b7abb15423a98611053275e982887f8811317ecca01dbd5f946ea77a6a86164237bd9304215c82323ef169870e638cd76503286ef7ee656c0163c718db2f29ed55c66d3bada0dddd0d217cb19b4a43f86243f8623784f1bdf9a65c010cc8d45c9a3242aeff0bc142a9e6b29d6528ff99ef0249427fc8b61372f96bf480762a0cc3302137fe157fc21c137c3a9ed36cbbd386695886613a2e1f5591058699e625ab469da341d538c7a79650384306aeaf7febb80a883f19de04136498bb3b0465b42bd058a544a01c978fb060c2edbe8c11848aa230c6fb8e99d9ea38d74f428176aafb638c408ba277e2f6ba2edbf947ab724ec7a547772ad06ec2f9942002c707c2c59faa195fc773a27bfc6955cee900b96c072333ac538bc17fc3d9855b5bbafbaf93fbd751284aa30e101d9e785955759cfb212ec4368b2f16a01032d325ab2199d5dff1c518e16316f38db79453e4400a51c8ba0af415b3f84c15cfb142e4c5878c9656905097db7f4109c5733a951351e5ada09039a349413b269301c6a901edf88c5213aa58c980e442494a4905101946170cb85cc1841959b22863832cca70c20c201b5d9819c2bbfcbae425fd3207bb1ec8330ccdf9a6e0c196197411f4450b1962a4fce70b40480aa416cb6d92eb4d0f5b2d9e9f9f2c5fc2686971f969a2223039edc36a96a9d26e2bf457b122050a1332cce2ed896a67bb7c540512b763231f2aaee36e1214c2739df8fddceb7eeef58b3e1ec7236e628a269334c95875b4f95650c8bc17827f402befcef74e453ecfbc4e9e3cebe4df7603bc52087fceef647a939369bb9e77d9662e3e22f83c08bff90784f2231509dfdf252f699971607c9b433b84d91d3b6443e9784ebf10cff9ebc58b3ba3e0f0a34befe747507fccc567124170e1f52e0c8ec0186ffcc73f640862d4dc5fbaa5e2cf33ef639cf8504acdddad334efc7725f2e5952f938b2593af79c253941d21bcbc618174cb2831460bd48aa6c0833180ccb905bea3960d8410ca187b4888dff8730c68e4fa9deb77ce47bf5bce47cd44d1c08804284518601c99e13338e74ddcdddddddd7fc7279d1d2319b197e580bccd125aab7ed96110ddddddddddddfd2f09a48646830df9a69f3b2bf73de7b904a42e35a6f4d3e0b7dfdf2f3333333333f30e338eff0ccecfcccccca8cb47566481b9b985e0115947d19b0dad45eb69bd1d81afd58230ca8f5398b9dd6b74966183ac684e1613e79a08635cf30dc9759b386782262eb44324a042e88c2bc8094b17019777b00432ee9c94a250ff1c333fbf8a935cdf598cc4f496d9a58bbe84991a9d28fa0dd40d74d906da58405be9fa76db3e970da265458ae7fc8de276ac6466e95222cc164961b2c8a2c4e4ddf4a42c7e5f03137756725672567a76062aaa20830b2131170197b9100acaaeac9934f9d1f22489cd3839d3adce92d3713b3def942ad7a758b93e8373fd2a959190397d7829733b149c56a042de62e67b5d97bc24ac7c55401e64e63a5a9ba169e95603db89ae24cef94e124e4ac2c47378ce0692d20e6141bc15f63a1c44bebf0278d543744be7aa789cf3e939a9fed3cfed4e3ff75509a25bfeb355836a416be39cf727813049da392945a1fee575556e097a35a1afb4dd3a6141e3cd78836f92b54b72e510bf2c274575a355a94c5f7cfc9dec929fba6c11bf1714291dd45c78da2788fe69a0de72fb2fe9c4733ad593a1e98469a5f84d43d142e3c72a84dbf0e33d9326feef4221fc4e795d48e388b7ee94d78ce358ae7f2379b6997bcfc4df3bc929ef46cba4490f13cf7976687e7f13cf715b8391b79a56e7dd20a98014c92fce304b218926d26b05d2bdbee3a5eb310ab49b971d9a3f2dc7653a1c64bbfe4288f44fb76c403b5e72b2c4a71bd0f9a66febf1afbf7c23e2c37574fcb7ab530695b7d34972a3ed925c55cb24578a4ac56d413b4e12656c8303fa3eb7fb2d7eb65c045cde6288a2cb9b4b817633045f74f141d1303472b4ccfc3624f1e5f551dad705ed26172a9e8dd3f7de7befbdf7de7bef9f8e11aa8576dc7d76e8faf73e3ce7b2f3d138f75a08d9cfe5a31f98b9978f7e50847b048599cb47508cb93cb87c0445d05b51ae79c20d89c6ecdb0dd70d9ed3c36d5d3c2e1ed1eb7628ab938347a5a0ef5c435f4d4ba5e577933d213acecdd805dd505d885bc07c4c64a4c0854d9664e25065080be1beefb0333d840bb4e333dd44f21cf8cfc7179d77d3ff52d6eb39153d33df8db3301280414649262d2129193da317c45bc97f3fe4aa50decd5382e7743ce6090769c6799784b2bacc90469009b4e33352e83e225ed26a506343023fca2845628494bc147d495a1af3043046446784b874112a2af2525404a648b66299eb11fb5416c1006159867d44e2d2d77518c5a4084614c3575c85a5d885cbcb4c9816cf728f6650e6668fcda26903353f0ae636faa74c359ee3f1b9b7f2f1a5212fd79f755cff5ff9683338014fa1cc9099eb4f4d582cc1f587507b5b0de3c89f991dd21ec31cd36c14925d86fc6abf793e82398d5f9ce34b733b96b9d208c297271a14dea60ff56ee81528bf1b28efa6dfc6b97678ea7a6edfae41960ad38307526c4599143dc4c42d70af525581049417786995410618ad312ea04d507ebefc88db3f7500a60b582832230a455bb2383203c5135820018cffe490441233609bedbed47b39494d88b95dd35f2218cf71236e7fdf9e1ee431c8df74dd460cf294733fecdbab1590f684bf0dc49b3073bb9ad79b0d7e03a688c1ec69bef549e31a0dabe07fcc143e0821843e02c833f25086e2208ee2202e73e6330d8a79b00268b8ef5bdb2c8a7122b49d81fb68988ba8f73452eed36810f74d9ec851c68ef28f0a4900c3c4edfec9008c09ae00d33a83839fdbfd4d41963d3127a5d0761042f810c27eefcdd41267f9fbedaedc21b7fc6e1c4b3becc1cfc852bc07e0a0c54737c812032d6e60c41347a0503e5b391981beb7c2834412462cff1f44d8418b9559a18c110b5e5d40a8ba10fe0e9fc1c58c11cb59b232cba808f01aa16174f403337ab0050aaed042fa0fadb9fe5690e5cf431cf532b882098c934ac085153a38f3458923a8a8e2f4848cc92891c6991f2f9060a208405408e160b9bb9dee41d99c8106505041128ca43084c47a4938e1c1282fd4bcece3aeabaeeb5c67a3242def67d8a6d0f27ccacbbab4dda490cd3033263eb11cdf4f5e50f89d14d4956fc673dec7f7249f14e54456221914cf71a307453a81a202fae4592862e8db3849a65ca47fc2ac8d17ff8909e3ab664537923aa03c7ce8fbce8d6ebcbef32425c6893f69fc530f2864a5db5f6e7c4fca7cc9c76c1fdfcb780ea655d3cbcad7a1731de6c625a87d41e7a414d5bd93b782acf850b2405f119605184561f84646c55b5755f9ced637eaade43ff6995f5aa03ea6f93eefafa0de4ada14fff56f056d8ae7ef986c8a5fbedd79368583fcf929b63bf2676a27fef5f1af1e16f6b44b0bf46d3934ebfae7fd4e297e9ebe29ed77de9b7efbbeb3a6f8af4491fd15d3bfbf12c56677e0cfbff2def4574db14d691607d3bfdf31b132bb331ffe1568536c51ce61ffacf9272b688a95caae5f62facb5e8962e7fdfc2bf04dbf64fef597bdf27e3eb3b0eb87388651d5eb22e3e7e2065d6eb7562b1b4aa051f8bb753b7e0773fbf6103d97ebcf64c98f75c75b2c2c9d10f0c64f1d69a11bedb31d96bb4359b28b9f3a82dd68a7d47826c255ddaea7d5ad2b6fb4687fdfdea6fc14ba9435ecf61e42ed10328ebccc38f2af4bc616e3c8d79cebb1f48410376b7ee65a347a2bd3cfef6dc764e7ec1866ed626841188c60307a395a8c737d8ed6bd1a3b75425ccc88712ecc68b65a465c9e39a973712e3d41e6f6cbea574c8fed981eb36c837a37f1676a4e239e136ffc1ac0dcf8493c67bbf1ab788e8fafe28ee7f011972d373e47462273a32771f1e2765ce6c6169724dc8ed3b891754955fc187322156ef413dc0ec88d26b8cd85dbd5bca93e1ad45bbd8f6f03b49901c3e77d056228be141e7387780849978fc01873972e1f8581843b842caedfa31f6cf941d1ed8d49f7e02d16db167cfeee022bda16b4a9252c68ad449103b3ae60e1312cdbe231acfe4ec263e81586004633f75ebb37433b8373fc52ba1d7379b3628c276c614543af20d0ee55dacf9641472157dab38e535f716dc5b99e1d34e7162430b878b9dd42fd53c6d63e73148ad2a09ff9cd5c9ca67fda9c0ff90734cd392d15ecfa6e4d13966576cabbd70ff16e75cb3993d5aae637fdf087be9ff115e0fadff00f1f3b977ccf33ef0de3c4b0e4ba61ce1fe7ba92ebbdf72cc7387e7377b8f099f36e3189f59bd8c0874241b7431ad4b6d7d1d188c8e6559955a39b691555b8f3a2c1c5996b9a7eabc9b20ccb30efad52e6e06b958639f85945710d73f0bd3e73f04d55e3c99039083b29f73d2b379e4e3694620b9e8e773b11bd3ac608af772a578c505e3142795dd76563845c85e7eed3dd83e69c33cea899349ecb84097199b60e5c73ce0f5c87b30a34fd7b1f2698447777777777774fe90b3308fdc8d9773c4763cedf9f6f7c8891f8cc9c3f73cc79174411f6242c89e37c30790204e9bea77a4f8cfb30314f890b2d9735413bcc0833c2c23c882569f1de0d433fcfba9ea72f10ef5e5588dfbc18df7b526809a5bc2e1510210efbbafd917fcc2721a4029f67dec838fd9cbf84363e09218416324e3f17217cf3dffb98308e8151c6cab761901fbcf7de7befbdf7de7bcfddfd6d98732f6273a758b9dd704e34fcbdf7de7b1df4de7b1921e783bb7cd50eaa0da4e5a7b340e85bf0f7aeb762bee9369dba2a774a156f8d0b86114209a310c7227bff8391f4e5f95cf085991e978fac38413b8124c6154920a39de8d17197d3b840bbd9aceb278764ea5809ab906ff826feac347c13ffaa1adfa06ec773e78517da29407c7044ac7c74b804ab98c5af06e7bcc9fbb37c13854869872e192d10d55b5dd688d339ef9213fcd1b99b17f042ffe85e405f46d113d04703cd02be7ff437ca99991b8c9ef73bef3db456acf42835d0922bbce40a3712638c9134274571567e7eb27c09a385cb0f1051111825a09f9f9f9fffc2fd645ad0ae5b3f2d1f9e20deeab24e749a30928e7ce208fa378deb4eb843f0c29b5b1962641a51149dd10afc6859aaf7fe2db450fb3322c529b6291ce2cb5ff21eda2b57a2d881ff6c8a9f59f2afbc8f36c576e87a66c567967b4555b6ccf079119860c2e48e240673c7904cef1786a4c29886ce2fb14cfcf19cbef05d401f920d5a97bb8ce4e5889b3deda2027fcfe5eb9988f14b1118ae890ce3f46346f18b0dbc841b55f2bd767d7b70f15af722d2038976aabbd1105bf7fab81579c144c30bd764e112e3f41779e14e0bb17f75087becb18a89f19baef34e0ce98a02ede217a488d4ff1c756b2415656c337dbabbbbbbbbbbfb7cdeee5af48783127051c5ef67f256d7f712ede6ed614d49440fabf6b05238c8bf7e47fe65ad44f1de4aca9f593bf0a3b562536e290bf52e2cd04e75051aaa25da71ae84c7dd4d1a54f3dc28fc52b40bff8a5de4624639f7fc06194b4229e39bf39a9a13325c3ad4f4fc5b2c1d61f1a5afebbab84b3281fef571bdcd50763dd7eb626677beaeefa4bcc33a3fab3eec9f3984d7e5261b85df947d766d4498afc953da47627a7ed904fad78a1ad03819f406d307a168a8c4cdf644390a159a110000000000b314000020100a05c462b16844a289c1ba0314000d80984a76589a88b324c861148590320410000030004004648666241b05e8867416985005c033236dabf73ca0a07a7bb54425016e1689120c3026fe0fa0437bd3d85637ac4d6e83b15ab798c154050517a230f2e98bc988aad370b0e202b1df70fadc7cc48e5bbdc465473b614cdfb3b5da0023bbabd424b42931e3e4aa4258aec408c1831b12606db331755ec562af72c7532e4da4b93a786b0994f627aa17cc493107d25c826ac31683d65b24aff119e4c8adf9c1a73afa2d0fcdf007d5435bf4a3a8dc78c26b6f614e9929fa574557476ab9c7c6325063a02898c8ad506fdc4d2f619671e7c4e179de0f38491a773ee09fcdae1de8fbf8d3e3cc070a4de3bf8269b4a7e17b7f45d5eb95c9c5c6a17c1004645feba845c95094daff16fda8127f8e5f715436144ae9072448f1f4680cb568dc785e49ac30ebe87300784959583331cff52b593e06c812bb85633e3e4ea8c576252bde7a335d38e6cb9b5c21f91ac8c8fb5ef4a22b1fb65dd131541f9a5daf0cd7c350c1f0d28569cd0408952ad08fd913fcf67ea82cb367d847e251e99b1181084cf3e3cfaf685ad56ab3afa97a43c87ffc8540f25c315194578491c18b9606e4e61c67034d89fd338096e5930a6b87cc95a38d319704904d122fb268e3c79b0487c8180e602e9d5c2dc9533943643eff54bab0e4de1bc563cf7df25f1c88c9aea7e33dc101bc42eca22e6b7537b80786ab08d127046979db23ec707a6954a5143f9460db75e8c4c37cc45aac805e99c7068e7317d689bd454fe80b36738a58b94fa11985dd86a3b4bfc36e894907d259ad1e88b5d1dc9e49c8425b21ac10ce2d9777ae12d86bebde8703cd0f612d1bf6acdca32414d423afe6a602390fc8ee2fd0284db09a70406eb32e62acc8c761ccf1542484822df0512987aa1118222dd6f40c878dd68b3626f2642f4324f1d0814c914d6d6e66a83911a396dbd5dc0275005b042e843d32589c7224134d904c54137f606fec202f5c41dca22403ba3a18aae405efdd1090aa4e6f50662e61460b2b5a4a18f6f8830d80dd53c44539cfd0d8b0b4dfa39eb06ffe3f0d1592b5ce2c3ca5039684a890a224077ab584f01ce1b70a4939adff9b0420d1adb17643c2488fd9e93022c12a9248a9e3b171ea2835a70eb28d046de133efd5881875799896111225cbb2c2c4118feb9bfd7551059d8cf7789a104e519fe259c7a1d422519516e1d0f2b87a64f1ecf523cb5dbb8c0f4eea1f85a1cc93a6db89663983df937852ff26746030f55a7f3d07d77c7fc6ddd8b1edb6d495f29b0351571fd71a580a1b95ede6d07aa4c109d2bf43669b6a7b8046158e840281cfd95ba959b77c06b1337421f975687d8ec30247d59d4e9e9024387d74d81f07ba8ec31542fc0c173fdefbe87d944ccc0de723e10d90febcb0e6b8100baad93d3cb8471d9d5c1f0274aa316b35a7ef12accd4b3027bdf38fce4ff5d7aa962e2032c5d0efcfd14f78b55e0b51cefba0071c35fcb0d41f04f2bfe801b8baa43d12da85af3cd08f4d117eeb5d03a77dd27a0cf742d95fcc6051db958b175a151bae3729421c0448a125a005e593a80c61f47c8eba74b46864ba06eedf384003cf188da071165a590c54499b1543ed72cd1d687c85086604b662bef71044307403e9f06db4499a063275d1928cc50b4c6f5b841c4b24a3a01285879a37bf4b367babd3bef680133778d5c77b8131bd020975ef8909e1d35381814db475a89703d725c936cc73523fbac45c62b1bc9508f8a005957021d0ba86861747f11e287b7d70b00c85e6442c2c717a46668f227ae6f95e643087e1f6e938d60ef1ef855ce4c09e01da5016f7819ecb57f347c8bc2cc1ce19a401c281d3b686d4d039905cace59d5fbe54284a6fd6a1de33bd455690e06056146cc2965a7d70b9ed25d43c191528a04032907d74313c031cc8834d2c9d27909ff1de55582395683cbfd315ddb02b0d9f1b716b2e62bf7ed479b30f5e8501f306c802108ddc042993d0d2e69913e2d35f636851c1dbabb2dcc466a0435df2855ab28674d1368661a728762c7ae79ffa2bf72f5784af456fff3d0310bfaf3ca19fc82645f8ffbe5201bf7b07cfad46b5143e0ea1b6e25a426d2800345a7ceb6fa60c77fcf46863665a120b8075c44eef53a2ccbf7b0040ae8390e365edc94d20cf2d7e5148ba834efc0685bca90f7262771bec30b22e63d066be2bf1b13b6d4e142896b453bc457140e70660366176dd0a0033ac160ad5018cb33483c79d39725c4758cce6f4b0a167bee77b97a7be7fb17d93c4344c0aafae3b01b54998b36b04e385e97a72054905a2112f0d880aeecc02a6e5b0ebf0b4ee2444c051d6174def3c9aca53b3e021e064535d363dc99b011d9086a19f36b2bba42ddaddd4215a1e0b0f3f3dfa6127719c3cac0e7d6f0b114b1f09682184ae9a9c9fe935fb984287fb6b00a59946ca56e0e0438ffa9ecc091601377113916b9c7e1926e011bbce5a9c23c0730a286b1001112f139dbe6f8f0c09624abd12202553b7e5fd79f4a126ca84b827e733d730a8adcc29e032b0ff00492f59764bcea85bc00b9006ecd9d6c8c47752b454f0b11b450fa47cff4d0084f3a87c8a94f55e8cb1d97e1713f56b879bd3aafe5d5e9cf02eed16e81aa439342a750c8dec957cca3798ed5dfc642fdf80289ecd41ab078b13dfd68132d0534e8b661ebfc7e8a9bd72b4899cdfeba45441e544b98cfff1884e04fef696b1eed24aa19118fd2ba2f715b1161f10a039dc8021c98327fe51501011fbb6e4de45d7a66fbad0127e8c6a5f412221628bc3da4947814b5946a427f17d00053bfb47f37d8efa1e8a8bfffc3bb0e6cb1e93fe02dd4f1b71869c9181efd3840f8469ec0e6ec367f19b487803beac13cd58ce9d9bcd7dec3e84bbd6a934771c632d740dbb80514e6a5e736c7f5cef532678c10a23548df4c6579780d548f049a11d6682b0cec442bf57ccc255f7502444e793135de809d4b8489e3a8b927fc0f52f6d18f05d9c7cab9102a8944c10ef44d32e21b8a9a1f90b93bb27ccb672e9ec46ef359750c0f43b72c9b630b902ac401357ae528b1830d91dc08a83c875e51c3352f274c8397718809022c8c862dba1978d0a84d3cffcf6740596a2bd63248fae800dcf316302b928dcd5eae7a25a7e945bbbc76cb90e0caee490219033be32c623f791b52dc8bf61f520100a3062bb9c49f8e31f1054ad8c5d11a7131a15973ca14a61de261e11cb21e59c23b236842741ab54eab5d283aa6a17da3e022710724ef5d8666ba2d38715ec135aa475299d70b3c5aa59fff4c2b4b9d6cbe7f5ffffe8f4320d83fbbdffaf47a18a452828b01f5e30551f08a72746c445a94e0a9dea689ec8bb7a1ff7be0829adf32784ae170b64e97951a58c00dd46227de0b52fc02128dd36978e959964c4072cc239c409d0b88a449c63d8eef0196a9440debf0a4b7278411783c0151941a1b6336eb055913b30ad11c615692cb65a00d504a93f0174d3a6081e1d1011d68f562c50f2ed784679ab27821b9f3e95bbee3bda84c2ff0cd9cf85e783b3964c688aa6f9c88731ff489489d078fd3051ab4d9f74bb005a3630929e69082892de9f280b15cd1352d9038e280372dc8bd0d4cf13588fc45c0f351cb4e1a0f7579b9e9beff91b507de83efebdc33238592cfe2996bc95134c20da309154e47d06eeee4706283835990e4dceaa434110a3995e213b22026092b240515fe1c1344b960600c0e92aa2deac1ea15172019a4e39c481bb6f98d2ba1d584bed44e914c7f2e8dc80bc50fe2dad03c33063ae6eee4545a90f1cc090cc49eeb129d7a15dd1703de8c704485abf139afc9cf7022974f476f1e80e22c519a182d343ec2f2becb97db6e8066c8a067a1d96e446931461c216717a81a17a256715375b2017ef998bba1b44e7ac7b7587b074e0751edc2b734d9b5f27a9744e0644f843cf34e20c2e92dccb637cd1d513dd43f8563814995e9efd824cda59bdf4b3a3860aa8d0dd212f9d3883b1c3291ac98fcfbb1ab4139cae062a0f5273c34d457e7f539f221226143c358c89984cd34443ba23aef2ea176db71174a0d7cf641e2692132d017415c310884dcd850cf248e042f6659cf964cac2cbcb3ddee85dbeeb6b4fd418a8feb6088a5382b90df466a181df2bf46a4040e685c793611da392e803f5fae7cf32e3b4b5a82d29ebe6c45ae57234d3f455bfc177e8e343421e68fdc06fcafb296683710936d932b47d2b77253890f06005504860874394104bd9448ec8e824adc51f92c20ee05721025ebc2e81e93bd7fb4702d94c5440a7fdb6830ca6d1634b6c2540a2a7c14ca548eb16910039508cab07d0752bb6edecd613565747b40bd5abdceae3e5d6d2377fbc016136109b39c6a5e78edd7c53dc2f242425d784c78206cf2ffedd0ed36f08f02381e15eef6fded6339e5fbd680d6d6a4864469edfcb16920e7ef81c3aa430d2aac8e35fd3d0fd6356cad56ba7e0032117ae536ac2b2eb010db3a100913615ff089cecb103a399116584396361e15360a5c836c177c837bdbe7dfa9e272fd627c0790c8708f6e90f82669a6c136d4186ac319aef60118a70522a45f4ed46d3c81208e521a5ad94ce5c451cf0a0b22a79edb63259470ec01a62ee41c9288953efa4438722cb39db32485e88f78b4f2467e5b843d2d0edf506d2c49209ce365f0ad27e62735e0fa09147f991f6570a8453d993f74b16285a17838a38baa58b26c076dfa121e2c0206dd99d755f80b63c775c0de2bfb0b873504fa9a6766ee773258d5ce5a0070b037511c4d713dda0696858eb6e6d0f2d88791040fa284f12bbfb0d213212ed1093bb96f294552e03de0bc73da9b5ebabca496db889f47c700891c18eba3468d9637782a9ac20944e4456055a745518bc1f71fa2283b12c950619c42d9bbe1c20304c08312c8f6269d3d9ece930d232e7c4a3811881a99ffc42bdcd6f1dd547c85bb7662cfc9c8e1ab907461a71c15e9ee5547c8bc52f917f9b60bd1d2976c71f1084c70fcef7509e2f8d35640380f08939e5d32c281f74ece1aa68ed3c293e4bf86bc0624195bb405ed14bed8cec3f9a1a04695f31ba86387343adc4494283868f54ae9aeffa9fd0225cc711cd19900e622cac7a80827ca8d60e7f45392eddd6f2040dcc39871830109f07c0c3a281f4840b8c4fc5e1a4f9392a208932f87f73b7dc2a863d96f32fe01450c0641ad6f4c991d0e4756e9da75c50cc1cbb784783766ca31d1fcc7463f407d9ccb4bdbfa4e5ebd8fa67fb45736bcfd799d33995ca9eac62a0701f2805edfa9e127da21ad30d59a530858f74e02661ecbb2cb4f1f41a50c4ba2dae80bb8990dff363938bead282acc07bcc5c5009908caa989fb32c8ecdc11d00dfbfb6e5fd49fcf5d251242af0ded9c69c99a6fabcfcee884f10b2dfcf9babbe82df42ec50256c6c7b44bbac19e909cea38c14cc7275f8d3270c3ad6f09b1721232e4a2a46664b0afb195f9cae4557914d842a620aeb282feda0cf2636ff60a89c9fada8b87f2aa7f2a1f840024ab4467977ec258f70416a4456a04a58d1e0b4492b8b36c6f9e4b9ba4942e0513e2b598cb41732bdb4224f88488987dd9dc1c8d8899319dd1ec358847631b3cf115d5604c80cb551393e19e4712b5d635774f946532f96625c114d504d55e42db373ed4a6f4a7a0fc220fd67888c91b064eef26b65b7cb8a4e0c0d0f8d642439f7f456171e568ee943765baf16b094bd8ae5142be3f1daa5c48ea75cad5daa585040160427151097baa84eec994859aa652c3d15a7e53fd74b4d243db6cdff35e58f53de8c1faa09ce4d750e78b46c6bb7b7076dc6b6819e17345bb5046ed052eacee65aef4b95b84110ab84d9bf92f4da05a4ba71bd7fa3c2424f3d486cf265ad8c4d124839abe5afe0a260ba6022f6653ff57489893dc8a64db22d6f190df8a910fd35087fb6ba6e36302e0ac1b616b3737f3e7b487fe16b8f8d2d97962dc741367f698530fdb7d427312b2052e80dfea9d69c00b8fbfb238b233e772019ed0790318ffe085956204c56153a341d80b61bdddbfb557ad9d0504927f90c591d9a5539041f3f65722d58b334c9c0925c848491d203b2b85136431733612d5260108319f0c2bdaa3af054398cf79c6b05bec515d5a67b145b865337cef85e7441bc59abe9bd000f4d8b3471eff0f44e9a5f9baaa9a3c8e3e233fef7c0ea621833f97100e9d33668df9754ec993d455f44e4f4aa2593db9dc27409ce7a606c082cba9904d5c2efe0471d436f2a9ca51b3c9cde55254bec0b9b02ddcf4431c12f7aa8280c0e0417fc6230d1d1c338efce28e11371c6c61f531bc8bd267b6addcbdab4126c2e6de06fddf2b43fb503676d0b55f9c2184b40a69b673f2b4cd3e974530c4f8d0078920a8c441cca7f474cd3dc6f81c29dffa776625331c30725861b64d7d21bfaf5fc480e54ca8da4eb12fdeefd0b2fd577d74c06876bc237a80e10359b264b5d11e420dee79871da6eba25d37d34154d8e1ba9d2536bfe5acd968a4a980e22cd6af1e545907c65c5b7ad5bcd2e7bd7cfb98b9d1cff5d6a8741f80f122ab0f960a959db23148b15906fc6e1a465c4fdc4bf3626e8e0448367721ab45e8fcbbebc0a8a248ce0906565688fd758a91b5fa984cc9f9f692a5b4a8afd5c4c25af7962583cfe795dea7e915de4d407505aae8a75280cc6ca64bfe7acc2d6eec3f55ed2e9314a06470d6716335332913d4af82772f2ebb5d1ca9c713573ae7bb9afc376ef7d638e5473843d34dbadbfb5be016cdbaa4eaaacb51ac0b04aa2cd16c0880710ec7a1922fde87dcffa284ec6c1c579f6ba629f8d36401eed63cda37c73809e9c8de47330a2f58bca93b91708c21090dbc3acbada6aed5d36a3d2190cda54ea93a2b91fe5d3c1c21187204f753e3c5604470362da365732fd86d8d99e629f72e081d27c7ba1c0d030dfb296b3cc69c3dbd3fcfdd3b5e69863eecfa878a5d01822b239c6920cfd512c278990ec20b944cfae0edb968b6608992ecfc062c5697b1aa97996b751c118fc9f2e9beda28f877d7e1030e3ff3f111c332328262abc4575e0063c6b18c6889ea456c916146eb5ed534b029b8234a5ddf4d2d60d8a9c7470bdc32d735182c250879d4389410be40fe2f5f06881d64c74dbcf57de9572f5765f4d3f0f02781588a6878f3f7efc110fee43cc88269afd8ccfb252d305dc80cdd45f08b435c77e4d5cb5ef07bf7f3a3a0a14d3d79801b16915e3da2d1f5da420c379accb963455744206e86a5aa3ea7899a10b76e4438b7d9da0f860237d57beeff03e48dfda004b86acbf75c6ce8899be6351dd2a2de9ba6b28160c7726c6c94a6e8920e63d22ae7c27c452d7e0f6c667dcc6681ebfbdbdfb8232cd17dd1b7c45a69027db0277184eaa1b979b9d86e755fc94edaa46d2106bb0cb0c918e83755fb7fd524e7813e56bebc2a890008519d0d2c08832a4c30d22e1a4f449dce30538102f991221aa0020d857ec1bea481f78db163c912697fe0948d2ea30bf413264269c1cadff14a06d5c42eb51d4e1c4c7f90c69a020ae9c26e7e0b5c4f172b206fa9af09164fa2d15746bcbdcceb358797a5893cda83ddfee2e05ff79732ae5308b92adedcc595f4704860e50a6cdeb51a23230499d633765f1cf8af87c5d941e7f2b4188f2c6203d15658e34fbd4d0f4d1809a76bbeb3647596246c27d43b47f36524ff079e51abc13e6eca46fd1a5bb7228e1812b97467ec1df479bfd9717707323b49ce7b30049f301e03a6a96a28cda54195fa369af3080553f3ad86020e642c8ca6ee360be0650da6c5d638e44979fc292b149b9319e9917b8ee0488998e4d29b5088832ae0e5111452d0b47174852ea76d50d9a7618ab411e8eb3b06f32f91bca72eaaf318335c5d4082bbbb3be3eb2d67c502d7e6693f6e53e6b58581f5145337080a529cb75c8a7d9cdbbb6813de505442c0d4c1ed6940c5cfbcabed7a172f825a6e9886cb6279709c60f4ee93ac49f8a65414afddc21498621ed0e0f904879528bad87cd0534aed430819ee74a4e42e4f22a889137b4d17546d14205881c6d4cf89e535f8e1969d5457a61ef49b1b1934e49e9a1405451207dec7169fcd672b183182027cd4e53d2022db34141bc8aac109ede0a3012f371984be9d6041f028a9712b834c8a6fab3628e8764b576774bd8cdbbe86c0d58f18f242c8afa2ae0c44076b4680202102d879006ffc88d7269271f28fc6d40cc4b1e6a55de0a5456d32b0a430d7451f8c4c2ab445c41330764b63675d4b1954ba7fee9347c2c8de8fad8fbdff87c9ca843615e1809a9a9067d0e51836b7b5e2935a3f68942aeb4f6ace2b9a63ce346424cca3a357dd7dc98b28a35453ee2abc0534cf878aae0daf491fe2a072fa2d5cb8ecc73c24e07bf48623e1523b292b8f8125a246bb42ed29d693802dd8268f63b9b7384735841521f00c81596a582aea9d2c3949710f948ac62b13323342e8b2049110e83c51a71d2adde8de951988b5e46da21a0051ce681da32a614344c6a8214585e872185dd9ce8aad29c156fdf18421c8295ecdc3143e426f8a9578f9da5804cfe38fa8c3fca86409dcefb45d0418bcd146582546aa91c30b0194902204eb87245a0028ec60da189c9ccbc2817b58d0267b60a523b66e6c8ac12ed445f3878537289903d355e49718bae48de76c3fe6d0797de22da34d0347f6a14f7b67a0deccb3bd225472292b8310990d09ceeb6aeb62640619a23a00146b8690df2bd1d081bb4eab32311bcec9d8811a668c32de122e9cdddd08e3c409ab0588d52bfc7f33190c15a70521b4e983c674a9f6d0dae66341022cde477f8712fc0a74ff0120384fa0ca5ae9cb0ef947f423e7e8cd0f0ac5590224d3794dc6705990e8e0c9336d7f8de1981b86c39918bd0da1222cbcc84f576b3511cf01c6a048f21349024c91966d94bc2cae99187a0f55611e7d0398ccffc6b23925a0b401860561fdbc3125fa854c0b6ba6bc830d86ae7dc593e5cabda3893e5d974efac2d3c73f44414a4a097f56d84ac1141eaa10825e97679242e90a8cdd347cb943efce8e57edfbb73c6903a11fab8843912e272707dfbd103628e15a0f8af8108202e430bcaab31618097ba80b11b5a7e33fd66c353f984af64d78d590d9dd3d57141ac5b499c2d68f6354aaad41b5f401218ab39a42911d035e388769c94d18b037945227c69845ed1b0e3650212d3718bbfc615625805afaf98694573b075521e70ac64bb77b8c45370c3d4d6e28b9f54f5d9ee94bc618ec795c0f54ca2af64e6a1acd745b8b0b3b426354fcbe72c755ec48d015f70e2c8ca1d4250369dab1845f8523f5cd1562569c7be8027d33ec87b83a88a586773079dc66a884dd210039cbe2c3adbbd814b20e175a5b40019c053f9bbf7d87ed084f4a962b630e2df43a64c3f2fc24234c22cf647b9381c3d5468c9b7da45601cd82da9b2308fbaf4817238691783286134c018652eebce203c501acb8c6d66b6e82a56a1ab163ff905f5e9c9e5baf3cea986064ee9eb94eba2e5922ac90f8f6f512cbd8577e3732dd2622da44767d31086150ed75ddbb5a9696f4b997c7ba2f22daf45d962032415918b9e353cf1b87f60ecd290c7a30c0416dd13931cccb485095059024b431778a4e4d1be0f6cc93e2d3b4c8350a57b85f799aeb70ef54acb30678655b1494a534b712064952a5eb10c738920f33f2c1b1b10218133c54167461dcb5d47d33e86352ee2c41c016c4e186a7855d9df102b52c3875fc0417c7865277b26474f48a9434494b84220e1b4c9d5de6989fd9c81f185e35517c451601aebc0380677fc1f0f2a600a9bffa39ef1e332136ae2f036ec45381cf3fcd4074f106b930be5e5e4ba8505494b30eddf8837b70fecdf8c9d9101862a8ce7c261054194c6dbe0d8a4430257fb1e94441f68dc03f1955b543b70eb8c106a78d60911488c662f2973c00512d271a0b4f7dfdc13f501efe586ae53da4ae841e9f25228dc46b5cca15a85157cc7a905b741ad11aa3153a06743006e42da52009fa707915b6834ca15522f289737ab984e0c20ecb6bf63688d4c9866f65f8fd75d53b7ed23e3be5b8a0bd66c2dec204f4735a25eee4a2a56245bd1d7659298e4d1611a9347824af786fc10fec74373a8278ba5d69c13ced920c82bd6fc8a61663261ef5725ab860d63638b6fd420fcf0a45632bf78b88c10d434eabbfc12d8b82eabda832ee8819165573e2c286652b4a1504cb1672de03b50ccfd09629ef88df08ce0d8fc83506dcb113be11c0f61d38a6955b55165f978e098ac145776332fb2f9fbe4155656c02465171a55bb09287226bce5beb2f731ffbd430c18dcf2327e30b4ccdba8730b012249aacbe01a10973febb2f68bc78845bc006e031cfffa0595f137fa5a515f3a1d57b5f71076ba0d3b02d6ba65fb0b3e9764a7a43984770245fcd74f086e70c0a0b97380f72b4cd05e4f7f6a43a080324b4f2720b61cd927b7c2bc98607f4a377453e5d13fdd9daa23bfd8cacd15ddbc0018abf5b539a5e1d325f3ee5f4e1adab3a684e88ca1a0f179be11d28ce2284b119425b12f93a84dc65e1b265ce3f96d644fd56ab466373724869813f6d4cf326242d5886842c07d1a4ba8291ea1bb5fd77cb72db7dc158dcec7e20c9724ac0118883eaaed96a42906ddebdf790a48a1ba0120c4199d6dd829c244cd57335cbbb3fc85c901a797c69bac7c220232d56fa77e02504686ebdae49a580340bfc27febbf125e19c4ea0a51a9fab1232ebd86c23e55d370de42244dc78c8af53624adc708724183a66041c1fd278b99726ba81e21ae403bb4957018246e5641b1435229e4fac5df9661f4c93aa435d28e9c7ab609de4ff856700e81b56adbd358a70214b2baa0d9b24f0a8588824afa992356fdaefe5c473dd347604d32cbb96a90acb717bc51059cb99b94578d89100826b4d130861eecf72f0beb5a6d4bb970dba4072d2240468854f266f11dce48c19485f8841f62a86575eed6cd92d4fad74d2fe024f501175ddc43a0071bd78dfe8d1b38734ae61b069d8da69335a7317037f7f403127054d7e9bd2758dc17fd5942149aa5c0692f01231dc1dc240aded34f1f8b39432fd3fa55effd1426eb9c4d8584973ccedf648fe4645a64890b9a2de285ce11e428d00321c9774765c059bc41e7ecad8522f4e34e8d91e9e06a857f7e10265ddb2003ced4f98b430e0565784623bcc719331d4a245b62260ca8045b0ec6601791ce9efa4f46a0fdf74024264ba809c8db343b8b5909911c2b68480102bbd88aadf80772b434bf5f81e963b7f7b555e8da08efaccd0f64861d544722edfc7e9172731372840c5915fc28bc9bcbe59a521d909a77549114aecce3f6bb7d57d27b1d508689929dd343bb1fb359f1aadf193d4cc11c376b650c61ebcd7833ed5865238381224d18068c655d0ea6a27077a3acb4ae030e5cbe3724131a9308bdc036c99dd4183375cafd29548db74f9f5075096255d381ad88879950f8c79a616612d216660d0b4f0d623081f6fa742bd543873d2282c80c6cc38875ac4989cad2b07ae09f7a9f139b67a565f69ba218ecc44a03c5fa2915e38a203a1152283fb2a6f19b31b3227922a363e2c2643434a6db43a9b86ef3aed04cdfa543ffd63862d9725cde6e80eab5a901fed002ce104bcb9ca6b3ec9394eec45f34790821c213e58d97017ea840b2a688046a3e35971f05d3171892990266189141eb0338ee5ac658815f8640382f51875c51b540a5299a181d0df71b7a524496f87ea0e2b9c08e5a0cba89894d7783c49f3dc2aee478f0f3d40944b8e670ce4e5d98579ebece0de47020de3d11fb7bf13ce8089cf378355378b9091d24a19547edc83c87b421fc00b6828a40f7ba22914e2d10fc12840bfa340a504a258aa57a48ee62d38f35b367234e0e60a6e83f76ecfd1a128cceeabccfc0bc6773c0a1e80b306142669eb1704d0bca753da2755ae737a1c7a41475396df63dc15848d965e74a48e362490b1e283f20560c949a4f0133cc9f48ba9ef6f1c827863911d616a3cb22a8dc11c5ab64ee6ac818cae732e43b12a75e8d206c23c73cdf0317b1d6ceede8c208af574fed8150b7ff0ecc1da48d88475aaaa3b188915f4e0ea5f3e6f1cabf8e11b175ab1560106649e5658472f90ef756acff5eb0bbf6c08994db3b15db6db28f460254c4d8801e01329a8641328cdbb8871e81319d4a3c106f9b5903bb3ab3bfaa2bbb980d06b78c25b21e4374d38cee6f49c3dc2999770d8b9c4499e9ba77010bbf2ec204c9622c9c7386299b3a73dca6056ed636b20aec4a5a9db13ce464c90e7099e3d11136710d78e6176153d416e465376fc4d7ab715a81bd43d910d9941c1fd6d4bba63023f699fe0ab5c42b6d0d61bbab61e9651d7dc3f7d17395891a7f25207ac4b7fda0684490195f110d2a82004166fb5106ad7e87b0835cdaf4a42d80ee68fcae3dc25a72e0d71e0a277a7937528edd29a2c1a4a6d7a3d088d36d1aea99e98f5f0e20e864d7b49b1681ad22d3ebb114e0808b00c2fdf8447c0980c1983023bcf18415d79c6ddf5e2cfa9238e5ecdbd92e976008be7d399e9aa65e0136a80052e0b3a77c9cba16b810c7a8fc0762d18a1c21382c18cfd83a1510aa04ee21a3887288425702d210a664b090a25d5fa3b4019e1d18c18ee6a62ab752a3c0c3b2ff9f6c0e096b44c7254e6525661d30192087973299a4936413a71e07c157082f154977bfab9dad1994121d6a01c769ef2f2dc94619619cded3746815222e168e8cd48ed8bb024a8d66e036a38399b03140536b4fffab11a1135ffc6fb9437b185b6472cdde623317638de28abcd96365301d3e35a27b54704f32b26d42e1e417ffe9207e7d6cab12f1d7de26d2d662fd51b2badcb10c32619381b18131fa3a4b6befa3615086b7a799d94a6c8b19c330cd073b1adcb7f3181cfca98adf5d6f47b83fd3815f9b0acd08772c97e169299c879c9b1ebfc4547c5c49e217716073af074cac9c9ac7ad8ee5dd9d8dcd99c5b4930b6d97b086095e89d4240bcc2dea70eb064fd83da0a65c1bcbe8debedea59ac3f23800d3c26323c1118d403e4d182da2e5760d9942bd881315383d8b3ccc8214be757882886149f0c3760f330c4868106fc796e55f6d21583bec1008792b3027648e9fd1e6c5d9450f2dce39adbea572c0a4af86bf2575432b808d629101a32e48829dce32550778295449c4823e4a6c522473320534c044aa00d455651187e068ab17eb088ac14288c6060827aa382ff1c98e1d1011914734bbc2b6feb62b0224d1717f8340973cbe1f50ebca49815ee2804ef71552a9e48c940f65519f8f79214c3877e96d5639f7619deb6d3c53c15db6dfe1fe5f8c282f3fe79ae93b16d9ce9f190e7c1ec36608d99a5457f0e8b4844ba96d9ac8152fa484c11e70107a9598ec1daf0c7e88bdc6d83f721a34aabdb92a9565089a03332ba19c46ee48a9ab7849685f3a086d6aa70ed01930f7ab759773884c20bac95ab2d6127807325a513d26d282b9d5530629f1e2d105a06f085df0d17a84521bd6c02280d9b68002e9e6a8f79bd72cd08f1cd1d441203461cbfd7a358e4defe8482da8d9b124a6ecb1b34947437286459a9252d881b3c6a3f36cbb997555407e505991e249cf1d77d1d88ef4a6dfa2baec72b3ce5bc28a0b9e0f8b6bc2da725f685545159404b7d73ca89ada28deb495b36de3ff13c34bd3747b99c24f9e39661c1d6514969df1220beec5bc5b74a1a1a010a8d685b6d30d0822721cb0adfe802adc15b96557360a57a5e4b58e60e6fb96e3a3c7624c89d569ca3c19ae808cce68753e7ad6e46a8f0b2bfb7a87368c7f86b6aba62d170c213af80a2792290942681431e8de6118b2b560a6ba5b667b46293dfcf6b28bc1ad5d09053a32944cde41918ffcc1653c3074cd33143d62def39ed03bf5917c7474d146e332d1e4f2a2b6b088cd0913251d26449e86785795bab348d469164f92768da6f4a7633c19eac2409e63f8e7f18131f9954bf6ce0cd87ba89f8abf8a675adc292e18287fa86000d5af219e4c2eb7909ce762d786e02f3ef2ee46937ac9ffc839f9c686c448cc310d438a4381d8442f2985df348e00cff531b9510a5d3d1856456c3a54d4e2124bd95b4dd89c554d377e61bd4eb688c5596d19983df035f03af821cda534fbe9eb8f950598e2b7df20192c358459ac4cc9e4d043e87b7806c8034712604b2261fbdc8b6ab8a028ab07f5d2e07fda59589e13cf6c00eeceb66c5858de1eed0a54605cada8cf745b814a30985c5d1fee7878c4fd9a03548f1bbef52a136a84ff5753693af590f5771dc582d1635ca5b44d4990ee2556e20ebd9e40b56301c62bb4c93f13fc41746897e3cd8c7df039742256274ba3bfd3a3b4c5510c906840a0596643b31bf9eea2fddeb9a3dc3d497638cb51855217fc430e20866d08036ca321ba044891aa4899031d327d063218bef21862e63135bc5c4ed421a060ad221db0905ba2f473cbd200fac2a40bd57e4950113275d48f734f923b10d1b281f2293802a5cb6100b9830b01b6a8bcda46910d4b7d7d068f29a9311e628e8a264161312e8d01cd2017646e4a4c77747baaf13f5209acba0d01f10526bb5f5b179f1713353082a5accf8c3cea7b1e2a4fc95b4990cea6fb20b1b58648dfad71bab72e8c8f1046110abfac7e520137dcc881f6d4d117862eca8c88eb43388266620a0732ac3f0e71c47f444f4d1edf579c9b1146807aee577362dcb36e00b6bf5d4de9c600efd13646e5bce707a63a2aaac507acc5baf6373ca2a0bc2a7d14cab67a3ac4df004d3ff565c7f81b69a60e9d132469a6440b4e2195ebc46d18a0d36f5f3e87204a349c44b0fa099c9045e9ba940b2adb09b8b3e4f9e2ac96e1b8b94f0e46739aad49f144af71ef365ad426db0e98aeeb507720ea2a06c9ea43e50a2a7d33ade6da15a04235a8452c2ce8f869beb9cf541e4958a9df854462aaf3049633025c69bd1c9e751faaa590008ba476dfb516f24602780130c11240e701377dfc0fcdb41a15aea75cd7e88720c844640365bab4b585f08a45bb2f9e99c862918a90cb0a2f2c9bcb16469f55c89f6c6b13410708f0ed175e2b0d22527bba5983e2b63f03c923a581ba75d08ba1a45a08d9242e1b39a6aefee23987edfa435b6bb915ad7906ff2fbbb1447c4b55031950442cc26b3842030687f1691a0fcf441b3a4e844c28d1d181907dd4f20de091ac5e45e2b2acdaaf07ce36ef71b1b0d01fdbc8b50df75a606a4724be20d669a696909a83da617f09e2140e605a4a8f750698043fad26eac354acb0e89afd6baf1ab5938fa3ab1b291c062d30691b183eb7633a1a1862e904668cf97b291672516cb6a2ff5a9d4f9e7d7547971573ae70b4676dc83eaa16f0eeb9368a2661461087fa9de8c35daae769bc0c9932c4e430f843d18a9e861b044c3e6eb6224be78de803da9558d6d86a45320a65fd64e9a96cfb0e44ca50b8ad729e35561bec4adc44054f605fed468587f97d403cf312de9095219797e2ef86b5e9de74bbf3d926ee6624b19fda24423a0b5182182b9bcb0a85b8ef7ae9d040b8518dceaf63ab02dc796bb0fa74093fdb61aa97282a9f55cd354cd18f51d17077585987c9148d3a7deb79342f3de5ea13aa55ecfb62052711ee0cd2fd0c5212ed52a5feec62c0c634805616dc0bfea15d91e87696ceb526e7fc89949f17fed3dcda95a1c22caa92a2d9156d6228abbade1b84bb063ae0269b8b9f6ec7b9c6d040074ed812a6b5c021f16d7564e8ad218431cf7f3e02e4aad384bed13bbb83d65eed12cd84139dfe9e19c487407460508d62fb343f596681818f227b3492620ba90adebbfb7e055a340e80e5b0ceee717db03c00a4bdb1551d0b53392f06e119d34771f867bd475c9e26d782ce3eac17a8fb999051e4591b956294f0fc5c02e9ec0830cce0736afa2e3806274ff0080f1a594e21dc03769c5df4ba51773232f76eb4fbdcb1d62cb23f4bae1c9ae56fc3b85e0cd416d07607daeb7f72301a4431c8bbe607d2eedbe0233052953b6d57f4c29b535b92f52ab8ff4e8fafd29349793f756780121e6f8f8f0ab9f4c969e23e0c5b37448abfc3a512345a5256ff041f2214f398b25ae08a8a5b4058b2803470054b204525e61b12b7f8586bffdb4326c75198784daa293db213527578086ad82c590be697305fca1fa44dfb65ce0af219714dd332517fe4f17ba759b5f0d770893151da5fe63fd7262d1d2bc15f2da873699ddf8262a06b580a214709adc9725f142a7ac358ec1deb590808495d3a22697e95eadd4a9b9b1a64ce269783af9c0139eaf6209c9c321c175d0de312a2c6ee9457c649890b5b5da8db8b2292ee90263f667058bb5544b5747d080605a973a3a095b5899c6ab96c503af4ffae60eba8dc37afa4d1b48f4e2ab7760b5facfc71dbfdf1fa83142de407f6dbf17ed02f761768fcbc9c5d643a1f1c47a0776698de76be5a59512ea32b631291dc4dd9e8047d1fd1701ef33010f9b79ebe05f5dbf750f0fbd89471c3a1c9de9a18238ae33aab2b1da7a01a1c7e41c798fc673a7f612a5a79add5da8d24d625f573cb08958c946cf38743b69a63281573fca6e119844c54ba3263e2ee6bdb6eedbe7f2897dcbda3e8af72d4d7750decd20bf2a6cc470e50e5bf1e6d14d6a7f0268313458dee8d9e2ad7d1c64bdf2cb0e2f816454877139c68d2bfce08a8e38de782aa432deae6f0166914edd1db2cf4bfa150cf2eec02cf4c3af254c1c28cdd7464b98aa7498f7cbed71682b7b8029b1755c6eb035495577d3d65184cb503592ecf9f91c053f909c49145b1aef95b1ca01f322562df8d68e793c0e7c19adc7a8c76398d953e9960bc2efcad21760619ba44315b3c3a61e16d01bd64919ed0ee110a66d2753b7b29b2177b9db16515592b444cd70205f6f858fe4db5ca238b4ab3776759a74638e60875288fbda843ed5d52ed43a7c8de94b4f20e0ad9dcd047ac9ac379c911df8a67d4566dc23e90182890dbbab6dc27d0e4eb0214c6e920165d2425d13ca86b901461d2b725add3e67f0e880ea55b231c808d9795d06c3a9052da02e2c82bc21ef2a4117b04c6ca98ffe07f53308a9b325f14fc821d0ec09da4d561a382711bb8c7dfa026e15e47693f930cd71b37fb1d6f3f838febdfa6dbc1e0657b38238806fd04b70dcc590c20ddf6ae76b0441d0c46c362db9cbca227c0067215605e6cc64a56bf11d3a9de9a2b801cb79b72aed9638405a1404b48ed0c4c682af7841c9ea3a421baf8fb0f3d955ef5e704d1a20429a4ffe019d345d932e7b04ea287cb38ba80100004a04338f395acc8b5d4c5e6f6e8f9ad8cac6c47b25568f6305ebfa3d36bfc75a28edb5cb4ebc577094fc96c9b46358061acd4323776dd0f40133e3ca1ad1d34fda716a4dcc0b518c8a9951ed1c9804766749d5c90ab818a507436468c3a6fd13e967811baa13758923944436ade3c27681483c6c1c8ddc9cd9c4da1a36599d43e60ac63a5ad5b3a9e57579951aad6f28ebf559f8770f8758ef5d1a7cb17e52e77b570287fe6580467ab19fb70e8d75a81621b876800b8e031fe81c84892f45c268c7e2ebb3155778dfceb2450ef7c41a4c302060bb38edb8c6472388fd2187493244c2536945bcd1d84b351f4afd8ea08a5def1d049b0b603a098b266bb9cc8f8d5b51c92d7cb98ab51c0f593d62d13a54d031eb2067886f8a0468bcc7a094236ccd917de6cee1508512d85045c0d03861ffe46aae66ea7c240b1ff2166d9f57b406b18026cc4916362cc5d3ccee61f6e8929e0d3810d6e23bb992051d3516197d13ab529aa2017e9a2eb80c7a30a0f0ee8db65608cf94a07df01755c0736a81866c8d40ee88dcbb63d30c72c26a498ae3e7628c118b3294b63b44ea6222d725639f76037fbd9ebf9481c9d26287c618249a1d0a8fda8e68b258d256250a9d15029d17ec2ea346043c49642b68d8732156c5f4124fe97599cb9482e043ac172d568d1a0009a675a32795a0ac171c2374ae066405d24a1cc27b5d431350ca8a3625c416c5972525f46112e4ba08da698640768ef4da4fb6bb3e13169a22162642f3040d8dc027a0f0f019e8a3696ef3a885ac20243be1d4ec12285dd746d677d712264d338c690226fec0dd57bf8e86af1546e7837a90dcffe7ea81c24e1b9810e1d9f880f4c48e3a9eb3b760833dc08be0d8f20074feeebab2af678ccdab4fa0c82b57dce32f9d5a40bce5e968600a57e7c2378a8f0b4d40974fd4124d27b24f449102a0d2728347d33ad3ba41db7d9b2a066fa00e99aa0b84cd42e73ffaa32a12029e05da7fe04cf286a2129fcd7c461a6bbd28f9a28c7a11ba1316a3d8a0246ccb1333426d0c6c71318fdd3d12f1c5fda933cf50dc81c0ca7df0c239a2e0b8664969e615a3c7b60b7ef45dfa0d9f1570f3b9f01be833552e770dce8261c357d63d49eebc1e6a9b6ada83761688c04b83e2e6d791dc4cd6341d3a4c4d24399783b1271570932097a4b891204319af8a383eb4ac4a435b7da783f87e83ebd838802806129587aa7e0caa00872c9752739f79ce74e0224c0d3353ac38d0101c852871a588bd7893a570ae137447658fc59ad059ed3a18e71bd364a2222ac9e90bb2285fa5a2463e66cc39f651519c47a2253c6ea772afcad7266dfa88a9c9eb33cf4e9597f0a9ee72ab9a8eef1644cf818023add36e95004026a9d74fffba9a0c23929ee531c7779da58a3fe0954828d55672504cf56551ac42b7e1cd4759aa29ca1bd9d9113b162000bd09949b3a1920f148d40d553a4483239b14d13f97f973cb195752a8fe556d4f55f22d864e8d163ad1688ab932b8890d27ea23489c69c32972f83077653c1b768963636c04dfc6691904894fa8c2993e665ac2e664faf9e9cdc930c3ae4c06f7391eb42d09dbbd9f7cea6fadc4e354d6197be8b92f6e5c96ebc5506ac223965e9ac2d3b11e7b63dcf762f6e952206eaab2bc51f856e28f6bc36e109c18b4fc531ba0e2b4a999c2d61ccfdf4df42f7abc8be4df96e27cd86ad8013cc596e3d23bfcb9458c942a3b399507f3cf8cbf75f2d532b52bc3328d07fcd405b2ce2f67205be3871da2c6cc38d009ea0ee626a4d0f65b8be84df5079a451c05925a9b8b23230a195ea0031bf776cd075642469d3ea7985826f4a8edb68979927b9388081cb9d8106b0d003998638d4099704d46543239130dd818a407e771dc0171158140882d7741681f063d3d1910e5803d6716e41a3465ccd317915fd8046babe80afd2b5bf041cd2602cdeaae17636472d855e103e0b0b4da807bcdc7dcf1f8f0a4acae13410a41c02a1cf81365c9bf19ed20e2cec7fc88f965cb4a137309c716a55266e835c534e2416d05e3d15f3a74874b5758a3f78fd351e8c0286b23e6859821c3ce52dc393cf26bdf2797176857866de93dfa565b693a8b9b22df70da3938afa05ca07a977769609b8e96ba8228dca8a85881593506edb1d23e5b2f8ee06686a9b3041367b26b76868c76e748a9049fd6ffe7fee8a17293d83a94cb903c1b08990d2f59c01a7834203d5c230ec8830aaada4cc0628735cfd649af645f3d7858d046b91b54685b23a7361e78e575709f0094f9e78f9073de018b04c35fb1bd7c4288e2135746ff1b1d10ee74ba531bb14989880883020cfd9c45634c1648992f412ba8fcd2486382c7fb4f1b3f4aafb1e83b00462bdad391b8fa112884ff4063593d486141d3d89e894ed2706afaa2dce31093e0e8a3011cfce346cc54a10963d9dc8924bcdd19cf16205c48b28f13ec394e2dd7db534af3c2cf03d18e1a753be5a3b581c4f1ed364f82752d3d6909297313add7542793a8ac743480c85b4e889d723912fa3b722919685d929e1c2a6f897c9db6391d4534c30b346e64f4c6cbc6590405eed8055fb7fffd5edd418c459cb52e01e7d4f2a14d9a2a6bc008421cdceea9c0836b4e0918aefd2a1542135c847d060fd5acdca3460aaadfb890288b079cab49c5194e4f95843a07337733536e60507858c8b33167884f0e2aa3ccc5b6433a9f61a1633f24f6b0b96a4899710a20992d6d1482e3a742523d650e5b7c5e355321890cf71e0338524695dfdc172cf30240cd2b452353325500d076c164319196f86e763eba9c57c4087b473a811f3b5e099856f24344e5547cc126a66d3a45a555b385a8be9de87afaa42ed76ff97653c1d45272ca9cfc44b28952648801a06e853c3f849347182084614e213a0100d18d24fe8c6805e877c23c1b7208b2f840260da6b52472ed2c0d46e8d42b3168437049dd95040beb401ed2c9858353a980523fea835418c9129ef87b40381ddc7c7a2ce4ac4068077b9121aa7967cc39d0e93b1dea247e166c20de15a2dc5fc0703c28c3a89d90e0364de47f4b5ddcfa3713d54555c79fc87f27da0ec3f4d55834aef2e0abc892334f61b4995df1cc5308b9321546af77f2a3d431a4359912a1e58bdf1b22e0974f57ef5d207ce10ddb57faaa16f46f5d09aca0609b41dd548123fa430a1f64bb8d683c8a9f5704d81f7542f40fa79f1531f5ffcfc9bd1c781f5ba1500b61b286baf56b8c16084e4d13e5324ac127379321cb987836416ce2a56405b4c9be98739c2aa4aec5af69c91be498c89e0584aad4f6f9b9570b8aa8df1710035648c6c7b4ae86ab6c9fb60bb2e7a3854a6547f636ba1c6d8f4fdd03f26833b9dfdcc9f6460198ee335d9b88c49958f1614db6befd361878f80224733e396634fb1f2d18e02905bbb37c8758a4ad6d8bade58c6e818130a2a73d96a696ee4859a9f1d3962bfa29f06774338e12d3df7d338e73c1994ce1a6e237e725e8989a09e46d0220161496d2b55f4048b44165786ef0bf101dd9c9ce43df4aa34644be25faba32c5dd8f710aefb000f811a03f910a82110e14b17ab35a2ed781d5d00569180b4222ef7c918e80918f610316a0d521252aeb6449bb134ceb2658ca28f6255de98e6b55f83e2f9e7490a17308c6608cbc70ffd9ca08cbf346f277a143e430412248cdbeb83f4a08ea020402a0f59e6229d08490be73127232b636a30f54d89254d8336b2191b0a49d177062be61f605d764f2e0ba2ecb5af2b57d48d7d06fadc838004e17b71532cbb382675b75095a2bf5a2ea8443678da86a279af6c32365613a8fbb54e17b6b2d9567a763fbab2d03cc240ec04292d7524edb4078e41e2aed91caaca614eb7758578a7e46f11fed13642160f2d682e081c59ada62b679666e14941c745c5d4fd0facea75bb724efc3914219b23a3ccc06a9a2712759ef9263c5d8b7868a0d8562e7cf04068ff579840964c12036b6594396d293c0cc01655f59585355c6010e023d5eb76c31f72d86cb6c140ad401689a86fc225ebfa18898025f29502a9bd41be3d0abd37a757075c090fe5908ca36b1839d8918fbb3d1e1af3eeba9522d84f0f02b31e07719f62514af6584272b63664cda6a4dd6e97ce7012ebb191a8a9bba122ab495f5992ef2175de6c5ebcd5ba10311d2890a96f40208ae0abbdbcdc6307c426d675bf830aea8454cf531f38be148032d897ab1621bf12f174d96d9748615eb805d43abfb1aa459f0f3a0a0e191fab80d76a2802f56eb4ba02b717330bbf9a03b4376cca4b6acf213619542199c692e427ac4b202171217aa15c777c27735210c68963ca9e8f625187c9d60b773df5b0c5cd8dc73dae3f660701c6ac3fc9a38c4aac8ea37f10ed7a6b73219e3da427a5ea2ad762bf51d202595f08ed00523789fb14305ee7fe1ec7904cb89c1ed07fa230988072d5ffb91e80982c0fe82da9dbf635212a3527a437b7fb70cc9b6dfaff624e61eca726506c5a8b7e0f73f9326eb56055ff71253c1fc6922ebb93986c0a6280f2e38929cb6d871d5d9428f187e32d3652ed807adb266baa2cff5d03d03e84715c790361cf50c8e14da2f33716efb9d9036b0b5883d981a2f313989620a9f915a5143a162b2d02009432c9b92392747b96c3d5dd9f01cb9c0d35f7ece90fe5fa975b6ba8abef42258da2782fb19cbd6c65d593c862b5acf5ca934f012192bd978f3e396ed7114792105fe5e9d07c10a1b66c8a2e2a6a14f31528067c5268f86b5a0aead28e4b8fe7b758a18b668c00fd078705e897900f0971ea1d7c96f9cc720feb7f1d23b71c2ecf3d6161ec91377697aeaf061951e80f51789883599d4e314781c0e94a73b39855f419984eacc569204742557bb4c7c9f82c6a59afca9903a76ddb6950c2cc96971011b73ba67c239bcbdc5eeaa37be0a418816415547486e4882e2440e50fa1844fec8de6cdb04a1e086363a4450e22d4735523a2681c4b2458c164918d6fe0cf3a3394e5f92f609a07423855f005cf401dcb393b71c8ee8561af9398b32066fcc852f075879488b008dd1ccae3b07a92a7c871776caf4f8601c2ba5f2505002ba9e5274c2701509c3b5ce5ab777cd389d735c2a1d977eaf4d3cdfaed65b8e0595bb040e4bc3af48e138ae2dac26ee5c3afee165b705c5e2ae3f82ea5c24dd9bb712c57bc0a9cbec7a905cc2be949f76bfb9d24ac80c7d23381f46f6c9b17419ce15231ced5341ba041d31405c46811c4e92edfa285a342e28b9f21c3d8e9ce11c90b5ecaf7e9b9066e0c89b0d451bb724f4690fe67322d19f1b0e9cda82b19267b719ab5d3def7e9419d16026bba65f7115da32057e6ea1023995169974ea617f15ea4261c6ed105e01caedf60d05c408d5bb7284b987f7331bfb43bdd12f84bc2561c778d5b3f0a3fa0611bf004eae58c349f39a19c007d8084a140cfdd395a24293b7512fb12122ae8fa2196345532aefc8f6e3161efd6d4b7b8034e2ffcda3a37ffda6c09c7a15dc5f4aeb9497918e5347ca29a3ecd04faf2056b6d6f737540f690bd051bb0e3aa29c192578aefeceaa58284d3a1904a300552c9702d8338d2640f15ed6900a3aa2e356bf14cd752044b5be41c06f88801fad3aa603e509e2a0dd35fcc4f45e782f09dd67c73bf904f2c20800e9433ec04607ef83318da50ec85be74a561d535bb0a6f08c319d798313e0087ae1e660bcf7b8fd74146c16e200022216350692e7725e7cd0783e5e8db4ac8ace04da5180f335d74d398aa7c21ab33be38e012113e71ab0bcc61c959cf1c7a9e6bce82606e2e33a816173e1badaef571148c2d1a30e4d9c36d1acfd5150996464eadd450e593d9bccccf31ddc2bf524bb596323620dfe7f4fbe903ce8fe5d9d1decb4dc1bf59e728add4ce2f85823ee1969de4c83abce0affbac4a4823e1a8e1933753a98a2d9c30fa8d95979148110ca4616dd8ef68fd56c6a0ea3a478e2f74a3df592cee7087d14a00c9e071d4710677cae044f5ec92b13046a4b0a2249fba300428e87ec1ae6abe8f0ab7aa459343cc8404031478859788db5377bdf652c7bff0d554606a09bd5fdf6fb60ba52f49c1de0eab3311c031f03f547cd5dafd6bf8815ed648ee68cf77aefa4ac9ddfd13dc8c569f8f3bbaaa656e4420c5a702df66da1cfe3f4bcfeab9c9a9a418fa636ce7058d5b70fa745d0b5f4c503960ca10431a75339d01ca23c0452e9fb2f37df32d5d16c56da9dcca1ea5af775099c42dd970ce4cbe3cc9bc4e818be2cee97ca3e2dcd0901dfc84091023f3b02d3ada64e7e099a6a3bef77d4656ab59974be56086c210147c65fc3d9a44200b69bb0da90c53499f5aaa1641e6a4ec38e78424b988ef134c57a627b8f537298a4068f9458c83657ef6952760790f6bdf7386387026b6fcd25474131baa84147a17fc2cb73394e6a462d9021a88b83685c91f9191b4bee341692acc5e827ee1130f31fdf90dd4ec293f953bcbe936415330b7c32ddf358bfbce14484870797bde1c99f24029240c18f18f929dc3b9160f984da0c713e1bad7e5cac5edc4a5d3735358729b85278b785befdbdfc5a208fc838c5519c01f6870594bea17dd59cf6947f1e026df9b2d4537efecc3edc1824a980133f30136ad3b1b6fef61f7050f395a13e1d112cadfeebfcedf2917f4f78747772a51333a305d1b78e0461c752b3ef1b2034f554840a67c39a34a7810bcfed22cb14520301e6d3364bb14e202710adec22826a61d2cc58785d70dfe80e2c87ed0f26b22dc74c57828358c2aa61f86ea85a0d11cbeb38fc8557ebe584089d3b5a9e68692f7c7ab9c89958dbf6a92adb18e9a5b443157ab3ee577ae5e9660490d1ae56af341146510e54f5a9efc646610ceef22bf161759bf11805906b460351b15e720dd9247b167ccea9ff44c52745867f2d1dec9e0cac36706d19d5ba07140aafc9fc99760cb5714e57eda3e71f910f0c5a814fdeb5d4e833abfac7e82dad32713f7941820bbd86e6b5b9de115dd44e78fbe126225ff0ee6fc37d6185ed304dd3f0e2e482db513de060f264f6ec9a23a6df63a0f51c8bda8cbf9ef4ed3c64d5afe1f6d40d6ee06753cbe09d892cdf66e29f76a47188d652ef6fd481c14540ee7c3f8137c2054d23e3994006127efc7f3a95614df42608322b0163cbb5a06e28bfd32aa302a2f451f84347960b96ce828d20671e759439a4e8b9dddc826672e8e86c96a3cfb97449b63396839606e61f116226f02fe4debd51a14891115dc8e9ae15dff5d13e54d7ed97aa46f911e5b58c3b358877aec5f2e7fa64ef05efc10e3568a9932d9f8b24783f55ee6fff46658c549efd4dfceb0cd0961e530a81f52c7d2a4cced13145a0956a4702245a05b1f9b7348c7cff48ff5e2699f3bfcf9878808cdeab75e2c95cd5ae793c690398a2d1b78245f6b4f14b62ab43637aa26fbf5deffb1779d8755f524c912607df6179ef9314f8f5cccc1623917213e6e2c4780c8f8882605802932716006c1e159d25bfbbc872c23918e08f2c8d98e8840431964e77d2b0ce23b465b4f939ab5a24807958ad2c8459fe04784577de5dabdf85ee05ae869f5f52ff67822a3a29dda914a4547b9e3bfdef366218628298c49d41cece9fefdfd87299b89510e4af2d6db94d610570bf7ac9d67dbf6f8cae4f3ab1468aa74448b7ab71be08c53b0607914d3c3e1faa6608e1a61d435700805d3f4b91a837e4a8f14b95fcfee6b0c1ceda9c256eb0d9ecb17ee83e4279c080f9b5b241c652fae0d98d8fab03e4816d7b8da1feb0f27b8702c595319b448b4dcb17e2814d84fe739efc6131d7764e01239dd9ca4bc4d1d787af64eef8ef9e86b486e1f8f1cdcfc28083143c6d74773edc8153760d18376873a3d897fee276261fdfe4d703673d93123d3f2dc6b3717bdcf9edd67e24b5234731b1850b4e980c7d4b13142669efecd3da8112e0cb9ae951f4026e0c21bbc76a6cec2383ca236614596db1d1f42f3f9109f631afe001575e6e1ed99054c8a145a3fe2c186469d9aaa9ed53cba04ac9f15920db2cc381f4fcdc6754ee5022c4149f9499a7828a0872823987d4979ae5fb09f8a1565f9ce6965c791fc926285b995638414e532df416918b73207312427f11c90f6b0c4ab689aff7887d7045579bf4b64add0d1d44f7b836276005dafd6d97bdc5b23a54e89ad3f9fd9f3a3d1645d317a546f595ceff6bd2a6933b9555ce08d8627cfa904d10f4c7ac00e6a329a66cd10aab4a0e3b7999811773da3b38f4f84a82c77af8856d5dac77d7dec0abf161f21d3258ee4fa2d05d16141274664794babedcb391752531bcad77f88274e34cc07d8bd0a8e01ff196bc8cb361f0088150d51e2e336491e9b3529bdd9176f013fdf104c1ce801d6fbd19031c9f94a28b81bce041d99eac1fed4481ccd6e900ab7d157e9e921c4f4f99c0b9cc9e547276f4879f91f073599cdbc2b431d8f1b7124f949ca888cd0c54c5fb7b42af5af74df5e2de0917d7d18e26d0f5af51dea6eb2e2464f295be92ae820c9a0e53586109be5337d0f3767c86f6cbc220c256b802aaaacc757ff4aa3314ed57ce73425707c1c0e3723d27884c0fb4a023478d9fff0bf0840c838aadd09d115a35da765575402b9d039a8abe0ba75931c775f659291bb893b562f670dc48bc8921727dbd4767ca55a262046765503694cac94c8c62bd432ad1d7d6aaea0d9e8b32d3931250968e782e7728bca045b6a81dbbd18a39ac4f2a747384f817c9a9ba975ae5817a76a028b79d9fe7453d74b883233906dd01fb67dfcea4c0215fa28fb14cfbfd49f365b144779c74aed037146f45289eff7211a6af05d0feb024c4ab31bc84bd347f9b05a65b5cc37c7a31421077e9997c97c7f7f3e5c4575d6b4a05c6a5cf54bd1f55012e91d1e7242e2d430a54670473547dad97ac5a1aba67806b1c42f869d605220730ad8518e2ac3809bee5bb80c104b3e6dc2ab8c780ea59f9cec4bd3169944e9ddbed0994de700c24198793ea21287faa5b097d28c563853f53163307119453b99e5ea8460679942f7b0ceafa6f2986ba1abc87ac1c94ef961ee8d883e47fce6408393ebe67ea4d57462b9e6de2688f23c0c113f5c1e610ad7bde17b4b694bdc12b3c4270013b2ac60d260ac1a32d50675d5d4c1ba3813b95c26ed8ad3cf0201882109901448eb1e7e6b20ad2783cbe53ab0f91783b146fb0bc5f7995ea5a51b7a2ee205ac027cad6246ef5be805075a759e039e45e31fef16266edcb9cc270edc7f941f8c5e3fb480129464ad7ad63ec97ae83d11177d7f77f1bc360ade296dce432e5dcc15be68244fcdd664e2d3fdab7f95569a1050fa11e218bd17171e25cfcb4635a66b6ebe8281f311f621271ecabed36f5515886a8bdcb63dd3c7a3d2490fab301bbe8c5ad886ebd88c1fa99cbd474cb02ff86583d4cdd355e712311370b3fa29b80869239b043e98e00e53c27de4969f9794b56250fa456575b2b3d73109f1ad6da02803aeead351c8a009c3d26aefd0d8ab9f72de95fb8afa2c329ce3b49e29e9b3116f1675055b60ac525a1988069e12a00644456755f39bc672d99bf8c860bc3a65dc94bd7a16fac1db1e25b6a0b480a6d0b535df9fd27da8054c23dfa66e1b6a14f9a0c3989db42c759554bc30d63d65172f02e67dd9cf1e73ebe6e8024a85cfa317489d0844a619339a95d4e964ac191c4664890bd85be143a879f471b93ef93b24dba954bafd4420e62c9d432227d8100805611bc2094faf1e866c8aa88a8bdf73f2fc90171a252c2115a015b2f5df1929ca217f4bab3ba5c5e6e3b580749c5211240a8b5a3e079e8247f5e51bb59d76ab7ce9600a00aea0e1525ae8f11293908a174a656d34eddf40740043a2394b1c7b60a9834dbec832238036ae826d174cfe523021304e6590651faa3540b4b8b5dda1016aab4ab8774cff498cb0399e3abd132f86b0028dcead61d7340b9071366d0623b12152cdefe1f50d1cf2a8bfd0ad81b24195c382f7025f8a73fc46b227b29887e4b81625bfc3f1907b7656ef662f8763e579da99438cc8c2edab6e47954072428370dafd190523fd74d444c3d85f87d7a1ccfd1934c618ab9f78bab29afc4312c8c490a3364ab055f846a17d28b68961adc68b32272d964e04ce89fc2ae57b0425fd9e07e21818fb78ce70c2023f2d1e8e2c6c92934cb4472d6ae775935dbded7804fd6a6045b3642745208c459dded34f1de1305dd50c4aea9b1d2bf52a9ed1a1580b8b01e1e2b2a945b935693157d0e2f31207f36ac19a17f2ae391149af16874a990b6d492d7a40832642c3a12992b520e61b8a071029d18d1523621eef447bfbba33a86b47f27742c0d6d1b0b588dd9e8832ad7d94cae37de1a6b4a6662fc9aa65e1482b2e6ac75c76c05c8083e0021f17e44f88a77021c60cd8dfae30a699f9d4b950b53461502186e5dfa340b4160dc4396770844436d396043e5bad90fa8f86cd44c8296094f55b0281d3331a8544f341062a81802eb5aea56e37720a38de6b29ce7f89517099b9291012d0067c8f4f8936e71469cfd282796d203649e3f4207000261aab48b6df1a4356e8545e3098824b39cf97460acf0adecdcbda82ddad0a5f1373fd118770c7a64f21f3232a8407ebb48abe7f728f4117340a1ac15b8db92b84441a2bac138a30a881c0a84d78675fe9e4408c16b8d8c3a3d177726fa06d409d2d2e92a170fb578d16d20361b0e010009b8616377570b674bb3fe7af98c38bfb0e9012960a89fd3c3cd4f031554d6b6521a4b99907db4ecc0f0801b89fcc6e22dd3a36c5e4de1717bd4009d9ff515f49617b7a9e14f80a7980c3860c5909186eafa90e2afef4aa035917eae81e10a6b35bd2e8eaed22d0a3ae328fc97b90977f6beadb030554f25a2be64d9e62d3fdd8ad9ccadea33f3d5a7878ff2385fcd60c41cfd100d091e3e05c34a2d8619c5614f49ccc91e94191b2082af6cf5c0afbbf7d73831e6f8a77b450b0b92e36c56c5650b09ecba289e566a432fd332cf60e2e6c8405b60e10ff8e69f878b0c21a66056b99a10e18c4282dea0dec6a3e0138f91d8138c9b1d8c76c094ac4f9051b1f48e132428087f94589dd8c3b058b49085d65b22e70aeae8ba92e54374cc68e25dc229b243fb4502feefd2788b833f8120fd5cc34ae85c6142941057f8f7a3307a5e0c29245733049f8d5495e62db3c16de3ef3a02e650bc8304a7a465cfff1c87da88fb035c755a1233c48763b62dbbb8f246dea673acd3009f0feaa39d1cdc630136319cb508370ae23a5cce045e011301f81b7712d7594c320994670804a6b51ea9a10c2291efabda6994bf96ca551efc84db8685f0990eb1a25c6f9c9b426cdfec465a8c3ef2cb32184268f62d8cbe11dd952df7c6a8b40c3219f4b4f7265b491425ebc2eeac0f3064b788adbdc9a50479acf0bcf2a034fb9bed3747133b6375f42cd50d065168bc00d0e8fd59e2d58937301ea52dbf8fd0f94c075b065dd5297d25b5a890d20970781b010267043210bccc05e22c51b1f4ff1c293a4d7adefa3325787253a4aaa68e13aafcfb7571ec426ad70f3231d0f728d07ea52ef6048fbdddc63afc2b1e67307fdb0c4c8ca8f23e25a764804940af9960fc9d70cd78f6d6716ade4660ff0eeba41cf14a3d29ff8ccb6ce62b2a14669af9395be229ec418132eaf93613f30853fa73831ca67f1d0bc3985e83a36032bfcb5d789c1ce334de9c4ac9f19269029d9134770e05683b9f46be21bc43601891d138fced33c9f18c58f4b9c58eab378505e66927e6ce86ed6078799b7f0c8a39ce284363afec0a9cd199f5336e8c0f125112bf1cad2a92937ebae467286fd3548effd4de1c928a801ed72eb2ed05d41afed2c386d051405bbf8d14331c861a7e550f0f787aca824a2816dfb02cd5ed129c022ee3b5827e1f3024351534cc4f9e248add0ef6b5a0a240b12d842aceedf19511b007747faafe556443f89504578090e3acfed6f68456bcb9940300e89859636477d7f341b929db802fbfb9a7611e3a3ccfef223d1efcef67bb0028ef4e39ab62a375cdc469a36c19120257798c30b28970049004d863700cd47422677268a649410d05d5aca290952a322474c158ab484d78d694be0369204cf6b46b9668d35405eaf5a1ab620afb4c64e603297b1c86c94f41a4ff501d0ccdfe2611a7e4f9df251d0695e965a62fbccbcf26a047fff86e87ca90bdb4eefd36140a1064bbc5ddffc9d169589bd353d3dcdced3c9ee881a5badb08709b468033a8afe7e5a301856a503753c781c664c75ff01cb20d1ba45e6af75bab148d8a3ec4a8a2ff2a615efcbb8b4ff264e82868e87b06ee4dc8fc8e9e1c6fc42ee780cdc7bd95c2c681f8e06235bd88b937568c44608919becbdc9965b4a99524a0109089107df0741fed324073eebbbbbee04dd3702db6ce24dc5b8bb72f74201a276f717a46e1b87b1d999bda9248850a0f8cab27118fe8975dfa43465d9546e1b7eacd404092a6b4054fe2f42d16cb86ce78b9c9b128f0c40b56c7c95456918fea19b29389599392b63a732c9c6e2f12e0b9201ddff4836bc0e77b3b1866c0ea177a73541b7c256353b11d4aa948c0fe195f154525ff4941eaafb1da53b284ac3f40fcd29f48b14958f65c8217288daffc9f81030784be52e06b9c815ec4cbf2c6273f0b6c011beef8c6ca916e7431aa6dd46e2d4fecf8d149139b2838ee918a1bf06140a45b0ef5202daffc1769e4ad319701b6c4d64d015829228f5faaf17890421694a7bcd9034a58b9a280cfab5511bc59db9f1d5caf0f5ba1ed37440926601213519ccfec216cb5645dac51efed474c0d674cc275d1756bab6ebbaaeeb9a521a5139320243c861fa49c3185139320223e338c7614e6ea30d994f9605cae38ba9cccc3a0ce1692c8e95f63a65ca822ed01353ea97d1d899ada99de127846a8aa980b9b8c0490c867079814f7797efefd79437a420f7ff362605b9e3e0cc4c3dd0b307a8cd309be71403b2acd76c31a2159558f9fe70ab41bcd919ebf9230e2fa02660bd860c90249f9d4aaa8244026a8f93442c4cc3f4f82af6380c50abe44d7b55ac4aa260929ab02429d4fed873fdb79182e6cd356f7cd58b79266f5ad591885d91bea50b6a3f495201b364c9eaae1f2c5931525b9101f2260055dedcc89be632b00151bb65b7d9908216c20d9a40d8dd857199c2ed392ddf87de64f03a934179b1d9aadd5df668b338f663985712c680d5b531683f8410328410465e8710420821c39501927a98996d9c3bc8c37501630c234689614481ddbabba3200c27d86c06383601630779b868d34d369e14069999a704ba7ae73576746260a196841647ff2402edb8393ab4918a3a9557a424a41dd81e6967a3736f3c5644e80e53eeeeee717677775f777737135254ac78a2abfbb3f3829dcb8a256aaa76107fca06d68022bcf0ed1301d46fa3ec1417eab754aad721d5fdf7e322d585fc3f3ee284a7fa11aaff042a41f5a74aa4808aa8fe0f34a5fa7b532851fd63a6a052fd65689a54ff1d45a8fe434517d5bf070e25a8fe39c0a0fa7ba055fb538516d5bf079b9b22d59f715ab53f4f8a5ab53f4388a2fafe58e1829dda3d063d7510e815f420dc100189138ad8e00761b420074b28b90c4ac8c1902934410c2b889a053908a37fae1bc19ac24fa27df9224e7c1925375caffd7e8f3da971eafcaf118bf919e7225f480d8973915de430f385d4d8402e94d8d46c8d12fef930f395dcd0419404a145314a6eb882f45f9d920fc9f515e028e8800a9bebfaabfb21fe28c1448d0f3fd874fff5f3270b681f39ccfc88c58c383009d8becc27143ec639388c4b632f3f08e9af17c2e3e2482fa426884bcbbfe1924fea82bca766153476a69aad99dddcdf997d9a43a402859db7ff60ccdcdb7d1ba046d8a8fbce334cc8ebedefc22b305a8411941f6a5530824b50fe2821f4ceb6e3e9ae09517e676fdeb8c54ab9eeee2bd9a173f0594a68c918533990b8e9816bbab4208e434beec0a5058fcc39a57487ded5b2a49412f27bb424ecc1bac718638c11faeadad12b6c955a18ebf7d7bb619b140e8b713435e5f35fd3ea61d2c000819e92338413e54911336bed7cc9c13dc03953524310e8550f3230c4548fc4d1703c64782023990757437b37344cf752c9f9223565479cc1ecb9d282ec3e60cd2b6766500da0dd19990e0396ecb86b7468f50644cc8f9324ba064e88f081139b1ff0643197a862db42e6a745a85caafce2c5c8484144addf36a9f05ba105faf649dda222a042205c3b00b99d7a80ba2a49c614415cd426aa8e44155180428a57ea522a423841006a7ffc620a016a2f81aefc3489c2293540ff0bdddd34b5d21f7d52bf959f292a344085f06918152ee9c126bbd795fabd20a07e376a4bed9f4a002106b53f0546ed9f20fcd4fe8101104a50bb2d2ae8a470ce981839a965594aa059dd1f288e78fa4b20d1688aaea488708872360ac59315645bf094a0d0e2084c09411a1447489074761a662b6dc11157163e180e8ccc241891aa286249b9a2d0e208c905d18ce2e80591c8322206904848e200091fc2894f8750c47b2f9d67035f4a114e82ff8fcf148afc4be1992c5ec4804159a8fbb30414474b6451bf37c21261d0808814473f5228b9111380ba3f48caa8b0ee8f144c3421dac1309e227e416dae00156b20d4e6b8ba5234c1456d2e01d505fefa0c21415d9f20a4509b73819d565115db9525171aea2704874be14491da5c4be54c0bba92fa56c50e08571dfddeefd21dc79106b4dd69903be9a448c1420a0d9a90228512b172ee34f8cbf10069d820ec7e6c75bfad523f776e6b2f6008d8be7c33702afcdddd088fb41724c60b9660138b76e66567c05895fb2a7e92f63806390c7c5413287c62881a6351905192a32540adea22157eec69d5fe3019aa2baf81df4add2f52a97005425ebf8f41be8a5d4988f214c187b06849abbc8b44d20942f108414f08090911050d4d8912d4da0efda24e4f9238d4e3ab6854e453a1b481d167e117390cbca191a83df8de45a1f620f501ed8f3cadf2aedf6b4161e558d4aa58e1c72fe047a356a52a7c99d3aa23bdda284636363640d4c08f54a0b8a97088bca990880995fa499c0a23cf0c76ea278d54f8dc9ea4fa0855f8d0880a7fbfd8a442a1566d93214451e1c7a156b96043dcd16498deb9b875846e85f0639256e9c0af3103471b82c9f8db7d701742f88b835fab025d2db0d9aaddf7e8ec871d842d847fe86e1460bb2be8bfdd36e4b8a903bacf8c045e5ed63a730c92e225a7654dd901bf32a0a4df658ed4dcdc40b043df19afd7b4e0bce637fff23199bbe6357dcc79d1581fe7b4ba7b798d4e29801042082184d0bd1b322fd3dd0714847143bf5fbd1093f104030929a594314218a594524a29a50c425a2fa594524a29a594525a5206017e319577b79bbb6c65677a4e4a5329b8db90959d99b12b0a2c0c7c08340c7c08030c0b58000e1c0318000000f065372c0ef834edc12f41a16c03a1c0f6e58b41bd1aa2c4e7ae8ceaf2d17e7d53f7378b290215ef7e79b1fe732337da197630b6555ba58f512504a24a284495df0d8b7b79f96fcd122222c121553ef64ed41d51953646befe2ed6c3f7ae8e8a9751e593b2ce8dda93bf033fe1da55dfbc6c9d2abbaf736427fde8b28134fc1560b9fcbef1951b398cfc54ca8d6eaa57fd8233f3979a0ed774ccabbbfe73a318f8d28d38c7f5f27d0a3be32fdfc358176357db6bff6d5f0b0b75fec782fc8db316663e89f3f5660bf34b2d130746ca2657e3afcbfdf29eef1ec3d7cb3d066938d8be14614aaa4d597bf3bfd40e1a2918162e5ce8f35d422697b507e1d2581373d6dec74f416805141d1c2bb02fc6e8233e8451beec7cc468452bc6ef193a426eeeee9d39abb9d8b23fb698fb699c3ff956bfe497dc26448552c686d6126c4f26d3b69d7edbb66ddbde74dab69369dbb66d336d26d366da28b7f21bc7d5643299b66ddbb66ddb4e9bc9743a6da793c9643a9d4ca69369336da68d9b6f42bd693b9db6a7ef6fc346e9e944b73f3d464f277a426da7cdb46dade9d860b6c27d5da9fb6ebfc2b13caae5b46262316d6599f1acb1cc303d663a6ddb76da36d3b6a1b8b6ac709d33638669db9ea64ddbb6994edb763299b66ddbb6b7341ddb763235caf4a895d3cac9d4f9d8def4a8eedbfea4cdb0bde9b16d333d4d9fb868dab687db531d915be9962a7894202857ae94503a0d2768f7b78fe92f1f7236b8bb5b7066346e4059a8632cf51262943e3ea9f1b5a1fa55fe4c6bbf34943e6a557bcce27898f587f7a8d737b74303614c7e2f7fcaf7313baecaee7d33d5f8a4f8754a29255cf9d52bd78d6b1dbcba6dd897b0cffe2bfd72d897b0f7512a71259c5267596f75d2fae52e19504dd3ba6d6612097273426f20489492a878a9fb53049d8a53b76e4502931e17a0c104f57730284dab5cfaabd7f8d6fc786192fb845409dbc3b2e69e6b0fe5ea5a0c5cd74d08baff2fa4aefb60e581bd4a15ccdad9a54f488519d78f715fb7293e480f84fb4a3e4a3f7fb3e29c5fe2e2fc1f5c4941fc91748432eaf73c9e4af57fe9989f19e8c48c26eb8f9c0ff940b8c6eba18febe3dfd899f9fe3876c67a7fcce27ec0bfb86983f531be65bd84b3bda9e988eb3d79630942a215237494ba55a855ec46acb8c8216f8ec6e116fafd978738d6747051e3700ede16fa9b368eb393daeeeece3e0bd3efce3fbb825915f587ca28bb8c138242ad82dd76f65f572c488dbf9b83a7700bfe58f785a007f679ea6f65cb39b66e93d666f0f1d5364f21bdb74f8d7d329e523db60b3ad25e6d4de32ccccecc1a57b05fc3a0fa631d17fdd0c6718edb25b9035f5cc4596d0f762cc418e4752f7d3f706f7c45b1189f9e545450457d4e54513c5451a8d3f3101ba138eee88f3da5bc276210994ea747991e55049b3ab399f97f2bf2a6ba8a65158d3c52c1b0d5744c2850d314a0d37b8a0635edbc50dd179f4800d5c5285d04a2c1437d00e14a8387fa383d10aea8a781033f722a190d1c645293213efc14ea5145280a25053742a1bac8a43d9f5a0c16aafb7460a38a7a2a646a3a505dca098d455d246acfffb388e214ab68674e278eabe585bf0d26d3e96411b5caf4fe25cc743a994ea7ae862850d47f91a8ba51dd191437494ee8ca9f229556a1de3f1eed8ce9fd3113c7358eb1333fd808f533dedf06d30ceea4cd807a5317350d9b61e26eb4e7a6199c7cd3c7a1f6fc511c0f5c4d5d0ded9db8ad510885e222188bc36871408112c1a86e453d9665344c15755514ea79575b837ad674a07ee3beaedba93d54d74f378ee521477f35d4d374145a79ffe813af8b248de8178dba3a0b2c548b76ce5a44ad6a797f6b4aab4eef2c276e6b0b072bcbb3d6c2d2e9e0d5d363278e56cc94c156a17ebf8d50a847f9a31e73e6768fa595953f715c53d8cfe03aa73d7fd650d148daf86a6ba6a9fb527fea3ecfaa2b9d4f69cf9f0614f4f42bef46ad9af14ee430fea41ada67535a6555b7886674d1a83d7f8ba8f46e3d819e3a1fa83ffd4af7a1def4a78f812bc619ea043ba845a36fa57ede3946353ea9feb373a2d10e71ff656768ca326a7182f9631e5d69b781697869cfc68ca954730f511bc231b027adb04fb55149b465f82abf6797755e7b730af47b1e7e6c951fc3b8cc6360d871030e3af43004c7c8ce129f2742256e8892b6587e775f57493f6d78696fc5a353a01e7f0719b6ce97812bec9a919e7f9090a4b60dfdd8b746830f95d4dd4b7b3c482418c1e0dab03dff60cf85c5e19ae53bc375dbdb3996e04bee85f67ca81076318b63bba1dfd717a30b09e6d2e282b07eee5abfd57a082deb97eb8ee37e9ab6e23707bb5f77a12e03ea3edb30511103b670018bba308bd303ce6120ec143828b130fed0d7a8ba3f7756d783eace1deb6c36b4ffd321abd810ba55479e20c8680a2836b4eecf155390a88040ef80dac38770a59630928665dc56d2757dff0bf74121db9ee5223bdade4ab148cc59f13901b62fcbbbd09b37923821b2c4870816f5db285f4c6d2743982c64cc21dcc99ce362ef12af65daf65c62d0ea041a02aa0b34a453a755039ab330fc3ddc4b2cd38ea1bf428d1dca0945d575328449a5699ac6894522518c82d99082c69f32c4a71d6c18a7bde21ad2ecd63f3557da6324d7c7ab830d136756630cfdf4b3a183ee60cd42500827fc8e12c299419733ebd93f5c9451b9ba3f5ca0a07631441540dd9f2d8a6abf106c865406d415828d4e17424149845af5357701d4358d0473f145f5a028ed44adda1f2e802a4fa9fc30a8f21605fdfceba020bef92890fffa354fd1624ed08ff2a8b643229f1f0b6a8fa3b4470225fbf25d68e6c0f4e2a83aacac14f1333333ffc73a3753e040a9cc58e854d6591c53a03b13b9e722bd92cff0a3340ce77490070539d13af4710faa7b543d08064d1d6c5f3c1e75dfff47fcaf87ba3cd078c896655996d5fd901fbb6fef0282fee0aedbee47fc8fe7c3f8fced4eee7ec80ec9fcd8c118dabbf308b49e856be1ad126b4d876ce1826061618131fe1504faad516dbaa261b2f1fd2bdfcf636750df5f4bc57e17c3fe7d95ea66f7d196da52bf67e15256e65afcd67420a161faf95cea22b075763fe67fa5544b8a1341cbcfe8be59677ce9a1a6834709b8a6c6d76869e18208f243959a962722a4a6a523e2f235ba5d98afd4fd98cf7f7a1a9ff11c929647b59c3ae690d87817ee8720b64acd0d8d1a1b9d10a9af91aab14452359e48ea6d3c1197aee589d8e8520eb3eff2342e5d8d69e26e6453a3dd6f89bb8f476df9ee4ba51e6be158525c102c1f44480d0b1744cb071152d3f22d2dcf1ca9fba624fd673e0ba5f6b3c897175763d9446b2c9bb2f6b883b0bd09db9b93a6776441347f7f8286d4a963da54e66e471644ed6d7db9d11e5b42563abb42e51cd2a6ee0ba9f995d4ae8af4dffc0b9b5c363f9b19336733fb2bcbb2ec4951d39149807faaf8bfebb18c8315bbb8ebadeb693a55b3cccabaefea30eb5ac11aebb2b419e6b32579f0a893089a4387437bf2e912b4ff5ba2b8026c07f6a161dffaae5e315efab3467a35e3734eabbe87afe6a43495fa174a59bca4bda8d35e3cd23011872493ce14fd94f731de0ac5a46a4cd5eac5d4e853d3c361e21341b9a7639ef6e2d32528ffc73cf108296880ba3f5374b045090aa0829a5501c21b757fa66851a9138e6871c1670a51dccc2929a84e570650f7a78ba24aaafb4344543ffa31c000dd5fe9cc5064a68ce71c23c5dddd9da116eeeeeedc5cfb99c8ddddddb9a5a49cd7076af1123f2ac1dddd9bc8dddd5b0b7777776e227777e777e796e2eeeececdae04cf8301061b2f5318b1840e3686803810cfc21b074b56e032329247278809c9230548b74d67017b485289c5ca0a0e3b69c29088731971840a23293133623c6f8887584465e0f09015c2a0f8204748ca101b41191421041b4949c5d450e4999167a55540e0fac02c483e913f0a9058231659ccb2d8267033c10f17dba47366141540a9fc52da9b1d461d5e869c3bdb8526601853fb617785bb10e60d60fd5873bd9a3710776682da40a061fa9f0c6d55680e329d8c8c8ccc94faedcd8eeee721fa65fa61e01cfa0757a948e077ebeeee5e3c827c8ce510f202bd926918f92b9550bf05aa511a46befc6f9bf8ca7af92048a24a7641954c44956c822ab70a69ce8bd2994a753bbc73b3434ae92c7d81a2c897e11ee9822a7f87164faafc1a606815aa55fba345922a9f93f48a6b24b6cd5dfa465099887b2a01e3b94750ac3959331aef6b90b2a4f2bfd02bae41adb8268bd5093374d820161696de75c185468d16ee13c22384e7f49f0e1b248447080f0b0b0bcbb3bcf5980d1b7e351e00789a66e158de46044057c3ba807a966e1d263e9401f5158b0d1bcfc26223f52c2e3452445a9e488d1a2d1f9fc657b8ef37e8c47d3a6c904b0d2e4524d52284878565069c33380ab3dda02a3ffe42ba7209a16d12c2f6653ae0e66b300760bf9f0d5132fd598bcf31a331b31105bd9eabd4cf7ad2f78eabfb313fab4382fda004be92869ccbacb04a2921f709a91db02a10aff085d41f5ca54a952a6f8445ec870fed43f28392f94a305ecdd274740cd63b841042e85f2291fe47d6f140a3525246ca1e3e89bacc68a9917a2234fa59fc377bd28ef52f7d3c6aa99b97f694fb7854aa3d3627a5a9d437aaebc7b84a353d46ea3e5849f033ce2bec0adb7fce2ecb485ffafeacfb484f7990381e75b518b84a25659d411cb0fbfab1c6fe87f55f0f55eb78a0513309ccfac3fa0ff820bdf63e48cfeddef8bafb61fd077fc6633c60f7c37af8d8d70f9b9b7577613ed263a4ee47d62111c2e55d66b87442a46a88b4fc8c1a349522c2727a1aa80f0839692f03dbd45d1ca6eed3ba7e0cfb1a1c0d2ee530fe298e484b17849484123a3882055154a153b31de53e1e15fb391ffbc5d65741866c9528b49082186458e10a556a667c105c7363d51011520331d27f6b3a48dd0cf37b3ae46cb5f21a97717485d4ad70930414be0b0da4ef2775fdb34e1b78e07a752e33cc6abd0e56e5c1ebec2ca12fa2d04d8d31b604a4ec7e6bda04e3875c10f023fcadf09921e7dd2e84990fe6f29304b457496a502a954aa552a964838dbe20131f3371597bd99f38da5e06dbcbb40fd6ad2b69d90909eb999f44a2c187ca2f3b48205c9df3c7be1fc219d34fb3ede6b39bcf6e3e3095a2b4bbf935f2fc1b7371c4273141ade79ac5e4c0819d6161ceebe3c797d13e6b4f7b1347dbd3607b5ae9276c595beec0759b57d5bacccaaa9401eb207ffcddcdeca39fdffa56b5c541c8cc1bfeb3c5dd3983ded16835e382ce198dec1439c4680111ff06a59a14f48bda44299a4afd7b5e4c8c8c4505fde24e44224229258e058316c4a5344553838d956df23720a510e2a0461c741bb1c6dda4d2d429a531f20821c6b8a9973963b828b91f1c353965ceee93ccbbcccc5146beaea0f1af2ba8114afaae59994aa5aeeb88e58322ae2b9ef48429764e8c9da30314e5888f919e273bbcbb393939399cc3394073523a314f88faef720ed7ccd8e518d9192576657deba898b0baced92ae41cb6591c40e020a942d47e1c1c86d36a3c02d1b6123a4c34c1c3399073f688856967258038a773765a1593b40ae6e4e44869d28eeceedcb98f957c2023d4aa9473ced91e94cf75e508a197646a5aa9eb8a385c2c92138de830e19155c418659447f108c8f3de2dcbb22c8f4632c628b985070a431d65a9a755328925301ef9eabf7864e47107059beab555a59e129356b58e515d35114a3d13ca21918c6b8ccd515ac22d1c5957a04b38c76684afd21208af9f2830a98ce1f4b4aa757680f5db74b69d281ca9fd5b92de76e490aa63d3d98e4c3944dac43238239c2346321647bf6f466a3315276c2a56467b2d6de211152780a8dc6155d031dc697686b198420b82b24fdaeb2ca8f65b41d01d60853433718cc5d1f20444a8fd2b494045ed5f5ec52a304cffb74188475c50d247a33ed22a77da4644bf524f8d47a51e2ff9f84ad37a4a3d380ed33f27a55438ad270afdb49eda40edf5507b5c6222042df9947a30a1da4f7be8970101f92a96118f32a07814817c85613c47d887f160516aff50abb6623c56603c4d24c1fd48a042e330f323f758f7e7094be4ce925ecdd89f2f96d4ce80726a7bd013aaa89fd4a9adf384a28e9c013d6955270161943262ebeede41663935f92f533ac2fab44603c4f1553c6a98fe39319c8e98131446a32a81f5997db4c234038a4737d81047ddc4a1316a41a5910bf5b0ac1c232f40603a73521908ac6430a45ee6b4a040617b1e638c91351d14093aa5d429da58407d244bc9ccccccccccd2a7539f9ec7c4380634e77f0f32822ae949f2594a29e5f498181fac87f67cd5355be3e3b2335b630e1b6915e9fbab979c3fcadfb8127759eccc92f417d7437bcda4c8364594e912da437998bea5a9e463bf9bc39f44395e18fec82d002abfa451e234ee9bd5fa52d546b5fee2be00d4097b10b2333c501f3687ec7808b233cef93367d52d3d4dfbe3b0335d8fd08f6f64e49b1bcb2a7151c7617cb22e8d61e2d9ee69d24586e89d66d239bd3b76cc09f49fd3cc3a3b47a4fab1ce0e1ba020dc848d0d51cd0fd9098c6aac4f7511db383be3be9adf4cd46e258e703ef2e123afa117d08f5d17f3a53137e7eccc7603ede7ed0c0d33c831c3b6d05f636749417b7a98b4eae3a37ad329aff39e35d4db85bb31e88bf5df35a080f2d8d14be8c742dcb338fab9070bd39f4a994c423c6b0fd15ef73c6bb28b813f461c17d0af6f6c70f11bed7996931dd4af735e883131323fa0dcd1f40c0bd3efd3b345edc7d1df37564eab8cb4cafba66f622c62e239a9406ed1e6e001203af04df72fcc01c25ead420e83907ffa8a03b3b9eb826e515d987e9f09a5359ac5d14f93a0df16c1325ae5454539c4286d24cfc927c6e8d11fbbaea01e638c5106ad57d7efe6e3abab9bddd6454cab42b12ad4e5aa59fde6ce68b50095bbcda73d18d0a8c4aa624f7bf107476a7f14b51f06ed0c7ca902257646bef544ed7f1293f67eb02a5214ebf517a3c08c50c19701e109eb25c74f62d2aa08c47a51ec10ff0f33c282f2535d89daff6146b8cee2d881ebf740f8c511e405fdb62a556e3eb57ff3e920cfbacde2f825ff7ff2211535f4627a6ce0851b40433290d7f376777727105f95806b94c8d095bb7d2a03fa79b18789e77945601576373ef15e88a994fb6e2aa0bb38fc8be4448662d06bafab47ab7a001758debd3020ecb32f719be37a1e0450f7b1151e66c5ba0bb80dd53499d6eb5f21affd1ad855e9b56f1a3d768686b76378515b6747c6f990957efb96d1baaecbbb4ad9f6a5acf45bf6da57207badab40e9b76ec7b4f1617bcdf4f4fb314b7657ac338b0c1f94dc40dff43e68bf7d7f0f5ff940dff42efc376caffd0da6a7cf9a0b7713d85efb0a6c5d054c1ded64b2a31e3f90c94adf8f65dcd6c8d094b8adc9385f6d9a0b8f204a6ec8bef441b6aede50ea8268dda698fb05c0c2aae27ff1c8242d89bd64fd0ad5a2565daf9abb3d5a45baa66544a73d263e36a4d64c36b5dfb2588889faa655bb9a445378888f7a082eb2fedf573d1ca67f4e4affa5a0f2b3848ac1ec7dd531319e979adc0959d66b9551d57a2c1ea9fcd65babf2b772ecb610bb2f1e515725a5b4acc9fc9ce3f28f3db866655147ec2663b1ab1fc0036abf500a0cea8500761e802aa0768edafbb33328a0bf03d78f79aceea3951641bf3deaa18707305e762fd99002670bcf8bda60d416a35a292fe897e216a61f867118b0f33409fab1902c925fb44ab2100bcd197162919cc56144c764b369f1844f504c8c0c0e3ac0f042904f8c89e1d72a33c7743b81c8094238adc56efbe7739759736a37044180518df5d38ba3fac5785184a69b609d7298b6a8e0757f7640e5e57a69da31b4c30323348c5dd0cfabdd95394d078d01fd627c9ac4c4c4c4a04bc9e6135331eea77ec70d2f3664e2c65475a9fdbf335dfb27b58ce818638caf73c35aecb6bf357a847e29984aa5a4c42992b3f9c82b567c36e6b4aa23503c127d624f7c1277e2128901b5949de9b4b7757fa3019d316767b48d468ad48e455a25634e7b3a54a014094ada212569558c3931c7b2224e9f5ca8142d7775450d11cd00000000c314002028100c07842291503c9ee8baf20314800c879846724e9949e324c9711432c618428c21c010024004406088860800b17ccc0f9967cba0b8be26a7e1c0c86b0fd9938b95c77bf289c01d1f623c61996d1ef4fea49764f0c2b049a02c78c15122aa34838ea6fb615429d635e75ed5753030ad8a59a4522b24a1fc86b48a399eaa3259231cf1db1c037c5c098829761b2745f8b61669be1b81f9ce0d544cc9e14f5d5b951e3b710f0377732bf956fd6d112bab45646c179e17ce52a5430a3a7de3841c2611ee1522d009c0392dc4a1455c9182403be85fdadb3feeb9984c549b26d5936a64a49d66fb547d5bac0cde18864044ab5dc0f2258cf8904ba549b2c13aaa2144bde86bb6ad78e66be379cea874e7a047bd9d8a43f0c8c9e519ac44135c0027dfea9142627dba5acf79b5e18b799febb6c8b00605e2a14a11e3f405dd2af6b6609de284bec7c70a72c4ee1dd0f7d860e59527b06900eb8ecd3f20f861088d0403c989203be1d83c85002544600e2282e2cf2bf4599e08e52dd7ace1e439b111b626ce1aa66ec44d4cb26b08aabc113b09709cf6fd771ba079477abca084b366c60e30d088cbd37519797e135874099aca06606bc1981535ce5f51fa48cc1e3f54012338cdb386b26b40f39258bf1144a2858f610aaf72ccc9bfa6554bca2085132280ea71dc89270da279cf75e80449a3df6acc2490a3aea297685ca49e02591bd55858a925ad400c494066d90aeace08eb5ee1340bed2466250de91e0de74e31e16b1f9467b96f546403bf022885fcf45489a23d9064264666db57839b06a8a8454567f251bcb9350915f729dda1ba650e64326fa9a3b300a53f98cba9c7fbf4b66d5bf35fc8ce6e19897e1f56fe21a04ff62cce5ae1e479c8a7fa9b414eec877caa37b39e8b9eff3b94babeea8bd5cf31d3777be35f059ba0dc24bf162ee526580046374471fe4598d4d27ab380795a483a2b8ba28ed21f96c2cc1a55e31a2566e9ff039519c85fd74e42cff227758992ea2666a51085dce7f1ca77eac16eb6380fde72f0d2f9e691420ddc45d044370c9ca8d340169e243fedd3b3acee9ebfbeffa4cefd9e3cb5990f222d0c39b5bbcf7f9ca87fe6f46c322097562792396e65acbdbc8e2242dcca334d8f898be7a46d20e507ef68c2929764d131420910f6c39a49b7863815f751eca0a4d0ac89112d0b17af00a13209abe222b52081e0a2ef6188a4dbd248ea4e709dadd62fa4c94409bb40ca18bcc4c4f0249566d533082ec88d350de8a0b911a28198cf0f20ba2e92251d43dcaee39509ee1564d3d3d7bd8bad806308b75caecab81ca765c31302d0c1a46b5120f834356bc20f39c566da9d60c952f5b46855fefce142063de20b3dcdc0e11809807e580c08a6089f413f49e37b1c1f7bcd2a13285bcc66899605e9387fb122c7da28d00daaf81a12397c3e8548f19a99bb688d2639b7bc4f041523529b67f51193dfd3cca7aa3cf4f691efa6035273a27eee200755a4f836e293297925f15a3bcce3c587bca7bb0d650d30f01a7b42285181ca1add2dca15a84613b725820b38e55b9deda862d133f3f21f8b4a9556d9d2d044b231a11c0975c950cb282205223eb9e5dff27ccf4092effd8939f3efc8d8fca201c0fbe737fea62f1b7508f9fb24ba5a35c10325d3a33e94ce1047055881d124e5346a9ec4f6401dd7d1d403dc263cf4216bd062ec6c884eccf37edc5dfc86146067c428c238abf8a95773a9a1eee70feb69c9ee0204efa4c530ea1b75f082c02d35de47378dc6d749eadfb2525b8924bff3f26b21b892a1914fd58a6a0b9ea50a176b99d4a8fe98b34659819fcf0717104a09b9471b79da8ff47cf62071f22fb9e0d48ee3ed3384c6706f3ab80a9ed9743bdeecb79c35dc3fcc3eb68cf30ebdf6b0f77fdf412ec373328faceab7c57372f979471a2b547f4d8dbd354285f19a107f861ac3f95cf34f5adf871dc007d7a0997d7003a16d089b1a13da9542da07e4bb60ee95949310c912408a61a6c7a04a86f83f8ba5fdc0e82cae08bc3aacb7003db90565b3f73dc7d0e4d3eb58c030434dbf779a495f76f11054e1a36ddfce5e47eb422530f6f42bf0220717305c463cf0996255f804c0ca8347c01ffde4ad09c390d9808388a013147b6d3602b010b9fa36fde7178d53d402ce629b5917fca351d5b31e7e781a4c93948bdd9e16c0967a760cbec54b493e592e9238d0516f36085f1c7a88c254034a9cf7690f392b806ad9d6fc2d1e128ebd841a6f561514aadafe45ef2bc2400b9b660deecbc9aa3d0878c35cb7fda47af1a45e1a0e2ec9c58445bb11caa2c3d4851ed6538ea0bc7cf7238ac087ea1b9a40cbac12f236e974a4dc206ce913d6e2cf9901a79342a923c6ae6d2e062a313f911005055d6a8ba66c445c2e481cc2375219a27af6204a57018ec54b261e8aa7f1fa2ae02a62c0fe02fac7cf4d52205e3dc2f07be122cd970129ee21d2e8077152355343494965df6f41d8036403afbb1967458427cbe18a1746508958886ef51f0ae89de02580e9099a0fb1e9fe407a8c843dcc9369f904a9fb6472e8ca3656094997161db81acc5e66b6927ba19151ed81248416bd0b5fd2fb599a8f14ca338205ee1f740e37605128ee92de9086b683d9fd58873188a7de7fdd3e383f45fb1a0aa8a33be72bad2dee9d35207d2ecd3e106efb5a5acf19e2f4297ca435e90c80eff09e5b7586b81ec1e522e6b99094ed5e1502d25cef9e7ba82cef57119b695f1f08fd5abb7146cd4b109d1aba77484478228af9f14675066c806adb3056c483b2453b1bf237b85abf5f2c3110acee2862bd587ca3af1b9a59c6e3cfec2b8d084c2bf2817b4e660ec9e1cbd3966352e6102bb307ca019bd57c41d772026a4ef90698eae68626419788ca2d2844858415ba6f299bd41128f3245b12b336607b8bd33c76bca2e42528096515df5d19508ad0fa08ba9e4b016539e8fb9c43a1905f3f7e12aac3c76b11814120181f910405a5c42830050aa69830854da9a78da4e017684ee3867a671d65a64b12d435ce64bae0438291fa2979396aa20e25403eaed0db19296cea109943a216623ad490120a1c15dfc5654d06236968dd61c249d5e5b4e0a873ab7e82f9f870cab78d1bb9d53efc1b5619209ad7b93067864c9364b40cf39befbf90cbbc00824d840a56d6a4aefcc0ada183a8e0ca05646f5c8eaf080c0f0da7f3c8a6af7124f7994b8556cb0850c10275aa985987c5ced5a8b74f96e6bfdf2450602de0666496885da3e680d5750a10f74bbee1693c7f716044c205f199a72443299f066114d7f62c830204b398f4c03da6046a822eca590588c31db72722364c570421bd24105b3ed499f9c0d8d69292c9452c1704479d4baa2876197baff7d21b64e4ad00487da538965519a52ca802e88176146d263a5438643ccff0a80a2f0cf615db68e4e21933189bcf55e11c83ed288bc251790471db734fe1f731de1c8a72fcf3c924a62b0b9fb2a23a53dfcabedaf75a3526ad81ab0ea922e5452462053a3450a61ac0823b838a1c4365edca803ec5f8b6dd649b8ef01fbb6d5656461ddcd5e2125686921a7659aef58650f775854bdb160c2dee40ba213cf9cffdf14df2291b6f0c2a975e5ad5cfd82f5d73a4cbc24f5f41c74a107a189e47d27a7d642e80f4c5ac95e132e18c4e1250468d62870538e4ac45407177c5a41623a5fa286e47406f39496e758557ecd947195d65df20e38e6733d3a3245dd2edda78c21189fb29011ce98a341be0cf96353c578f163e7cecb88ff410cd1ee203dd1501a878c8220ebcaa0f878ccf2d4cd3f4a9163a6aa1924bba8c159106e7551ea60b4f5e140ccfb0e3c9edd651fecc938b958e56593e75d5c51c0dc1a518e4260c8209d06d36cfec897aaba997c73e7591892d47f5e860ef2610d8db9e17a51203949a53665eaf41d0ddf7e9061ff6056826c2d0368164ce49e5d67f72740ee19f5a466ca92cccab9372b8c226d129d4e25dcc8dbec0d4f4d5880345858de2850c3a730ca38062e0a61c1bb460c9cbaaf911ee77c4cec6f2c53d5d8707ae8d91f187150bd53156f4050f83096f9c059edc76d00866a5c744224b325eda14414a984c93bc9d2c1613f42ff221b9e6b3860135afe8090bf0729b4ab13b098ba7158ce125d115fa3218a6052b032ef019dbec1453fc2b9dd043e068e3ede390c4d73bff797e7dcdecedd68a4729e0fe0e177348b058622f01a98a8cc83881c34e3d869185cb30fa2e92d35c94a2426209fe9a0a5b1a445094a47e9ec316dfe7cfdb79e72a29ac9263eb30b9d1d77bdf2b5cbd422a33149eca1a7c0a8202022ebd9b643916632e02eee5b1aa6327b9655f556742a7857ee0d7af5943fa182751e1302ac968a9e8724e1ce8a647fd6c15b343b3a1611705e608f98757bd3789e28a375a31aeb0b3b0ed8ad77e3962274928d6f024f53b8c5df2b86613f9fdbad45297b11dfdf446f74a9715bc0e4825162cad1a10460978ad8f10d967fa0bc252ce823b27159c6558495ca83b5a77de993d5ecc65dd4dbd9c148ce59928c77fadcddf20245e928dc18ad601f3acc2d09f3d09f44970784fa57fd384016c4c724cc2c11f5f17fc09fa9f5e3dc7ab5a7bff27c2133d0403f9a61285273a479b17cdaf777da764cc13d3449827292410a662b1e1d9b414021c9376ef9482b44ab7638c31f8570b1e7cabf5ffc333046030452bef14ee37b64208ecc74ba4caf225d1fad206b527622943aaf9a04f2def4ad4846ccc812cf63ca12a9f0270b25bd7d17d853004cf3a1081c3523432845071e391290a0394c4987c74c9111084f6bbdaf3726a21d6df44b08dda93c1fca8be79677284148a84b8a00999c6d0e90ec244f71e795f6d48458feb83b10fd4630cd5e35d24c9199868602ece493eee1d543e91f3d8c9d2eb9e758f744e644fce3c1a4de0d8a2109ff037460d1deb4145a519f2c208bd8098a5030626c2d81126e5fc2b723fcc7d3a76673992e67b59a13e987a48ee0c06180416b74110f4e6e5278315f105c5f333ddc77ca79b365903ca7cf37292da84ae82d293838b21f88a8292ee968142714f408d9aa322c5ad56a8132ecb70e0ddbe642a402e927934dc5f0613482e30b9d305ba8b7d8bab7800504d03a119469977ea098f7e7816592d1849ed7266be4f2b6142348bdef6769252f4d9019e7f506a6527d0ca013597b690b4002626169fed8b821207efe7b0a0c5103406ca89e0c278e8a6b82f349ee544bfe4cb63b261cf1d62ae02dab1c320d2eda3536a1491bb7ab824a2eb03ca83ae7272fb48c11240f47401febf019d9ebf072a2705ee336146bba59037c42e6949d36a513c826cf17e24379dc914696353b83066dcdd73e773e3b7b80aea870124ebf24282802d57aa72d621a7b5726510ceb4e5da00eecd41071e673fe67b97acf2441c3cd76da925565886864737221a835187150f9345ceda9ccab5b07b72527d3697866253556f5399f94eda646362c8aa73df4cf37db1ba5007eda2627ac7bc5b8befdf1009c4245094b58883f9f7536d2d2d3f705ec6eafb764aa685324a401be1e601ba42a419d5501fb08604eea6126aa8c163d794a783866362e4d11da94a10dd8927cf403443c64b44809fef3ca0bdd9405aa60bb30a24249d07ccfa472d7ba1f7b494cab8ae534e2c1a3b53feed70415243811b08898dcffb6874f4333f6e6a6a0b641221a130ad8df904496b1e428614041d33771d3781113125dc88ad01c2559c8c16d294364187dbed67776fcdcaed54053425e00bfffb081b209453bb53ed843034755fd54deccc71d548d6b95d751fbe1fa62de5a38b6701314a51c92aea30a687e972fb39fc33296235db329018e4b6042b98e4cedc574d6bd22da47cbdb765d694238eef90e8ad219afa0aa7054994b49bd7fb1986b22e781c6381c3faf094c99e657a807b097f9a49798b9f59613f9698cdfbe9202f960827971fc20c799e4ca5961ce9b4314f8014cfb1cb27bd7b9affcb9a63cf62dcf432599856ee02ccd17e3701b684f9bcbb586557598a98cc7e0893ac4020ea799850dc92ccf60fe477b1b0c1cc8be9e3ac04f49bc227e3dc89a39d2350911bbe41bf44c6fd814299f480ac425e24553db356e3eccaaf0fa10aa159d1e24abf090a443753ec2122dd3cd9d616ac70c719e961f821ebcd711c50eec740c721aa3f8d87d87d6db366e177ab35b510f1c5b306ebaa5dce93dd33cf95c084dac60463198a434bec8f4a3c4a23dea6737aed603b34a6e2bca036166fde038d7a5d3231dc7774fb81827c53a5dbdfda11487717797e05898614a36bb80eef56f659bbf879d56cae529b384f132170dcf1a0534cb4585364b3f1ef795690eaa1e911792b258a13990463e95d2b22a420723cfd29bc748d605cecff4457d67c522cf1d7f059a88148b61358624413a20008ab739aaf9572eb91d495497d26a1bfa583bbd278baf98cac2ac7535022998847274160131c6557817e96fa5e52fe2f35952fd9de1269625e67f9009e6f86950a76e4ed9d1a3b7d603d0496d05824d2527cbe0b69fb2077bd58d4021875711eb0a0a33dda7cfd53ec294ab282054cd8a1251a96298b2e0fc1b643c48d64022fe03c263f0c16b8094fd17ad6ae4abbbf4ff2693f21fe77df2b72bd8f2d7f30b5281574ef7b12e64fd7b6da2a258cc1c362075507407c5f21c1573b6a5c0a82503ac792b808f1b2d3adb0dde3fcc0b267de96a8d047ec87cbc9b7ed657c404706c0f6de1b57d8c33c69bdd663a4dc09d443bd98d0c7904f28301cfde4d6995c21815840aae1525afa77016501a952b0eb2f941012a1db281e70a63316237e10e97423da274395607e5cfa520c0ff04ae212fab54ffbd760b22896d32966a77b6d474732e9be3eef0189e590e4a78473ce594a8b4bcfaed672b86d32e7f8ed6fc963f388d6e6bdabc203af47651b93ccd50cc1c5802bea15948bd406eab1f8e94fac32c62befa6bad9bb012c8efd2f0f9f10fcc1960e59a275c28309c14a39cfe44a3a79f997b84269d773c5a0674ae5b51213e1a42dd877ecef1bed3bdd946001ead852c199b29da16928ba7a960576a5552d2099a141c65075ed3445ded0cb270b5c0feac302121b89d47b110077654fe3f2a2fd3cb92cc9f8629914d6af4b0c0251504097ac601fdc97aa1e4ac02f0788c70bf5f8af32c0c55e89383ad3e6e7f9d942c43275e0fb92e8ca2c4e180d981c66b02e15df431ea1f93b1113d7701e5463641e5a79281d1e22873107159539d52209020c1f7b24151685440513d60a9e1983a2e408c391832f6a44830471bc5927c1c55c587407235c0f0da71f269b96a7c6ba682b52383f510c8fe062f67caa8d5c7bd769785a829fc12baf50a4f4a9f821e5acbb6a1da07001d58af82a05961419f630f9b3c1a2dd5885f15253a4ec7d39187fc78d5f303a76aa266cd406295b07ea469ebab34ea2d0f07427f05250e6d1d1ec9d7b56756b4dc3228fb5f481e4cb6c4efe526c32f4b724ef5693211340aceda38b162b36fcaefe05ed459d9a167940ac846d7ae16f2a5460c6c774853361f15d8732295ce78f4e41fbd8a6f5847c06f2ed53de8d5c7251f341505dd29f5c029664eb3c166316573d3703f49dadaeac2d3a532e7f194b804201361c82f305760b532f1f9ef26811c9af5331fed375f3e4c8cfad9199841fff44c283185a92435bc605b8960f114c8351059d1bc83956b9e3c324d60b524d790a24ce6eb7f8a36bc6d9906038d0cbb418fa83edef296971b4c4dd46912e93c6517af20ee257b08fda16747cc7449a9a8bea6174172e97fcb7b4053551e4ac22a59d865b6e112f99f9caa9deb4332b6360a354b0ca5aa9985b567050bfc718bd3fe166308919ae5004d89cdc73e102c4635dc0681dfb8ff0c5c816a2eafb1a923fc127d837b681d81a4fc793dc6baeb940451111025b17b926acba316e62991ee8a12aafd8d225658243f30c33bb84ff9ff64c3805fe0da3dff9e3b79ecfe40b70ca0c2ae209ab21bdf7214c686c899f8e8cefa37a9c0235f2aa411c0048bec4562807a8916fa2be1b1196dc95fc74e0b261ad9b38f509e909694e5652b509daff710f6d6349961955a983eae79cec951042e36c555dfa4d73f6433d0df31c5ed09a2a9a141a9c5347abbf374be0936fb1f169873ef3ee6637052002a4938bf33a11fe0d91b7c8c7daf5c883c82c814e569d4aeb78a73975f62a74a7c1249b3d3daa282bee803177756a53cec1fcf9182a29fd34969e6331e0cf485771f22ec7139815c4194355f574da0e354e8bd9d6541e17831bb1e11dff88b3ff6ef8eab99633d17d24dde4865368947a558188875beb9988ff8a6af5dd05d6f520ffcfedd91424cc58d961f344342e8a48a20744938898a4072481af1ba524dbd5d679a3adc242d69177f766f5f02108ef1c3dac87e0dab76f329ad90b87e71c9933d716da22d6a339fce8eb6c1f16262160695ed2496f40ff7000e2f6ceb25596c86c3fee678e25e6daf2b7de055653490313172c3c0717f557046b018975bc7486c912a10c61dd304d69d53dc48da231e4b88e977d0e79fccff97faebb251ed103c6a92224ddf6899d6a2dc0e05295633981abced6825aed486f3097809c670582a161413d5866eb683472e5c5da90e75c91b9667b760285228588ca7abd24df50a6821039454a0ef2eab1af8747892a120363653a97c02819f65b5950b76beb962313d632ea365b7c0e7f65f25d9861584b5a291c1fe671a8d4d0d76ba0e2032dfeb3c0b38b7278e41d1478160529385f0176050d5de23b852a9cc1a3fbfb8d5380c6fc75f3c148ae38c472795124369527263c1c3e6df7e6dd8bb8527974a4fb312b1440c800a4eea9dd3d0c3672a987d8a3bd54dd82bbc5c74cffd2971a12892f69ee9cbe4698503fdca3e26733e6458422af15996d04462afde56f0f0af1f9841aec59e0d1f9d9b9e30cda1d5bc014b0deb27ade65ccdfd30269fbb4e721e19db4fc33c0a7b5469b7c7e339feba4d3f3c51a78e8323cf8d997243164df14d5625c75fc43645c59ca1127511930b880a4f293c2f832c6d177d62a926441bf1dbef7242bc0b34814acc12798da92b9aee9ce1da3505942683177e188deb43d89e65e9f587489b4dcac1b1b0c9185f4599a1b05a5f70fdfb335e58c4dd2a5765fae76c32851aefd9710d9e824e382341a8a9c430f8cc6a388ca9fb75ee9567388af2032d28600e104ff2665311ce50ebe244d59a3573c54c11dbd1c9de04883c4ae297eae32fd18ea73c9c59d4af1a0305eeb0fcc160d97162a76e4d4dc8092669a68b107858bfba5d36f90953b0d6b10ce8946c7ea1be7b3c2398b2c0b8bfb8f49bd51eb8afa5c75d97d1a8087da1c78d2dc6e30b421ecedab340b106a836d7d3c1067da3e2dc7b750388e5434c5132d56ee84c0b94a95d4375ce2e3a13381acfb6f8825e3cd23dc0d82759cfd178f4d843dae67856927a83fd0e2c407baef24c182d4647f02ed006911aa2bcdd6a321412a96407ee4a86336bae35f0373dc370a180b808f8c99cc02e5a496d1c2cd4365e39afcfd531837eb02951e990eeeaabda4c3c52a5e6e0c6c370c302ee0cd33a5955b43fcf77147066350a24d914c49f9fc9f872162986074d1d86ace84754bc0fc24840f91bf9d41df303024be5f4d32d6a9e02581f78ef44663ef1a6355d952ab85b6e1465c7b63f260859ae64cab8180b7915560870a0867ae8b96a2110c68ac81fa2439788b028361d29e66dfb419eda4131ac43fd593acb446141ebbbd47115f1c7f89e6784f4ac0de895ef1065a027730eae6345499df691883ded22f2734ec5c959bfd3b82071f63840b350e5a0462dc80a2bd0276ae6c068a11e89d8336e241424975b2768162f0eb780a5f94570efdf0cdc5a0988959fffcd91ac55156880790e49a39c842593116fd78d791a7c3af75bf8c01ad218afa532d4201a1a201cbdeb0befd71f694091ae2bf805179c3d8b22e36b683b17567037de6b7ad2add1164bd8298ae215a464c13ee298cf394f294583e6439b01eecdff897e2439662a52032ed17cc6af3877e1eb94aa2483c5e47723a1d318213c18cabe76bcbb0007042385105b7e3f10d9322710c1c39861c8a02a854ba734de937edb258dfd0034c6aadbe738b343b6a091b56a482bb2c74fd088bc80e0b724f4e1330d3936886d8c07d9c43660ece87e3c868c7dcbfbff0a454308e62aed81e584a65267d7440d1d1f739b10445612541c4ab5eefed763e7af49b7c2962caafe9154af66ff9315af944b24ba9ad515717c18497aef37774cd7c64bead74b71207990f0ac2c9d926e3946609f4a15fd9cfba2f379e015a07f353e0496976c74b2827bc30fcdbe2dade037ec4f62545e00e097a6da6c701bd929d348c479b89125e042a162b63e8b2c05adbcc4f1302a5675efcd096ded89541937dab2b2b9bdf231033ce128a546e53a7d6998fecd39aad764ed7657cd84e49286db8afbc200c2bb90c924ecbc9709a029b9ef49ae9c67f766db5f5541035640d9cdc35d859df802e9fce09d06bcd0d959ef13f85892d64b5de631515f5383c8f9269599eea585bc285eaf60a573ec8d4b46ea4dfdb05a2a90db3a29d3ec5352c9bb83853ccc3a4ad0b1ac07799ad28f44ed09b80007bdd12a5a2510c6f43a770db4c09a1d39bbcc454090e9aea45b380d0396592ac302ffa896fcd2b02e7dac5d1438bc6e3db333911f56ea470593cd544ade9f4ac498c05f794daf6ffa608d355a3e67bd99e14fddd306a4d137de15518e310d2fefa5e8539c1d855c2732e5263f5ea9ad3909e708513ce371c63942831b1d2f4dd7c7e16ac624b63cace52e80950a1019c35b546252d0a634fc0abd8e6d41a8a2a69f2ca64f0bc24528c06ae5fee21dea282210eb304a22f6de292dc02b877e76e4d3b34661dbd3473778cedc47c52433ba6de899fdeeb9a25a88b911cec169d6d39bad65438d18f758c614d1f088f42b841095a525dae3f3528ff183bbd03aacf7437621b5e10f2a926b7aa464c3295a6b2843094ab02055efd3a71659989efae1f7650dbf3503d1dcf6590eb764b3d5f02ea2e98acc180deb4410c697734f202e21740ef69d70b370bc016746039b81bdb92c6e06a5c81e52493b32aa58e56aa779a54134ef3eb17a3c899778006660d1737d43cb3d34764120fbe60104f04ec1195f8362902bea9c6151230cc8886a4ee46a2c62487d79e80b653a3115b465471d3b4f8440a1180907d13f4bd3886f8afa57612f94eab263e672580066a1c4fabdcb6aebe1ebd4524e8d970020c5339eac7f7efd15bfbf73ddfe450f8abf0a58aeceada566466bd33b818a8f53a5fd8752a431022a82b9072bd95271abecf484b92a915ce743df17932aa4e5fcdbff88d9f0638737fb1611be62094b449057c733c881d8a07da35c73a934eefbb253670a50965889266a63493c140880ad04a80b5c9302ee713a9d1b111ef08868d1d91ccf74170b9c8ce67fdd6f3f979850da128bc82702e207ea58dd95f868eff3739bb89cf042770184f0523d5eabd62569e79891e1f5bdf48ecb41abc8aa7448b79fa9cdb0905b0a9c3b11a75d0a53b90006939c0951d977d369e1f24b9fe167a32e3b3c995378dba8295597660b918eb7d2211ca144b52bbe707a377f642c865541f9087275604c984c5e51a9ec1588245a444f5cef04ea9783b4eae87f94b997618b798e66e1f4274cfdf001f92d67bb3e657597658794c8146604b5a344c66b8c9083510c407d9f176ddbe118e393b5aa030b7188e86dd670cd03fa281f46894aa7bf18e58d7764f66be224e620f805dfe60829d1aa40337961d71639e30a42c14dcb38e0ae0a8525b111d893a42cafa7635804f8f58ac18fec6c27805b57064c814d6049f131edcc53218f2d9c849021eba9937de3310a559d1f699d422f73f0c307a9e456eca1e6cc6b178689464973244980032746dfd3be597676a23e87959b9ca4bc63f1862bcad00cfedccd0303f905aeddff1a852867abd56a87bfd0098d24ffdc53f429576502579cfa64c02dfb462a3b4cac8d09e846d2247eb420868273d1db010789f2cfb8d5a60b45dc375e8263a743c01dd4ce07a89e2a932f62167e598caae4e7cc2a4af7fdf5a046cd862c370e0e45a34150eddf866e952242154049a157a236a81117e96ff1bb934fd844f476aea63fd489ac6368aac61386a32bf1b93b677747297de39d8a8a0064c37bd1d20a0e5b12e636b907200054f770f79c893928d87e8d8f5b0b5e638e3daeb4d32e154724c6bb6c0f51b011490304cb3605d36f09d99c687c8314d89425a9b49e0d4aa9bf9598608dc46836741d14288f3578635573dec86578e5b9c53cd51b6f6863efda32ffaa8e502cb50e53533fa52ecb7680ac8cf05197c5058011b1070ddfab89f6050b9d347fbb0d2ef3b8ffa5b15df77bbd28838fd58cc06a8eb48568c8a1ce4d36ae4a18a2b8903cf2b0539da51af7fcafe43ae24e51380eb23671c7f3da2c20bdff8c5cab43037670127b2276b9f5a40e20a436d2cf689fcbe101a446ea0758b5d34bc6c171d48c3461b93079dacf1d1b3239c31ac3a8c498e509ebe8292c46fcbe65f854173aefcf9f80bb396d003fab070f84ed8f77d961899d253bec0bf5b2417ad18b53650504f65caa22bcc8689d684e91776fca21e2c75bcae304163b122d7e1db28be63d3a1926f1adc9c33c4f9f2c6b0704ab392260597962eb4f7ed55cece468128eb7cf3986173cd3e26f2d73e90e86b54e2dba47f745d90e81cab13d9e618a3f6b971245624a65c5ff76a85a48863ae08f5ae1786625363820b64ee965f401f6283fdfe79171d4022c0ec79c9fc361b36d74203ac3eb1e8f29d06cfb1bcf0f7becdb1207948aa345d440813038a78aa542fcf2a732b2ff15f52ae0bdf86f9d076112f034295f6c8569e5aec8351d7a3241897486e2f8b508788f456acf97f11599b1b0c8d0447e62a511b9beecbb0257c3141d5c09ec03de390f91288d76facf4f070b717c410b80f2307bfa0da23bdf93bf0a9b130851f8821ad9f7441adb16201e091538ffd4bb513e588dd0161562e3b7db52973152632ef09589b583a0893cbd31e800376cea51e4af14304033b8ddfb7230f9444b97d783b36bfdcc0c4750098fe214081d7242617c1dad4c9acf92a5f1f3c36ebf8439dabd8aa20d58f17881f47c2be7e65c4fb47942315c41539a2a4006a9f8a3692f0c5db9a7a242f2e9afbfb45e8f608f910e4bbb8740182d439a9998f6d29bf88061a8d1aa7c943c327723b81a92c845eb99c44e839b59a42dc996bad9aaef3d57de0f99a34f456626cd2ba11a1113763cfcd83ffce717b3fee6b8f5a09e4c8351b7a0e4714065ea79599a4f061ee8ecd921e8efb10b26fc0d45c7cd6aba18986416cd1b2c2c2961a0ede91c36bc596da3641ceba8a29a45012632011fe0978934c42dbe978f7392ec0c6c3d919fc405fe3352e52d7622445a5b452c76d18e5c5e8b6906f103c6feb89b2045d3cdaf754654e3f70bda90cc998c5f0d9c752046603ab815a6f26348aae07f33b206ee133a036c62d7b58157b3ece579c90347c4098d47f8a47ae16f06a3c0048dd9a628e05bfb9fb8887478cf59f80b980c518011460de2f8d002a82014bbf0a039e1ffc290c2b40ea03f5ec70ae72109662b587c9061776b794ad294eca26e36231811aa3c2ff4b9f9f38aad3f085cce1fe6084987c969422c45f8b62aa9795de5868c0bb7387848b49ca687b47270880932d92eb5b4ab3b3b0dc885313f8888ecb84270b2281ef972582274c607223dbd36ded39077e8825a87964f1c03d748fe86568b20e6b541f279a4aaf120ffd63f4a6062ace4ecb2d53566d307e02d29544252a2260a59ed904a075019a3cb4f2dbb9d278bf0925625e02bbb5d6dfb9d2a6070893c1fe147285a12c577233215852e261124fcbecbee148e068cf72d119a928c199ec5cad3267b6e6cb7d75f383099f7134c32bb144618b0ca42d45232297a00670cad44a28eb54a2b03a8bcc545f7c606ffb867fcbbb88025cc40ea4194510e51490107aa370b880008e9e10379634e5383afd0f95aa6b39a669edbd9ced6de2ffd8028503a7bf655416f1dfd5a1991179a86c9ed0819769d9c8461407ff30cde9e8d355a72edd56472a99211a3b3e7fe5da6c47541c59fbe45061115b8b852a344cb67013ca01c77d3930c48ecd7f7d07d843407e3d848ba0bfa6fb90c024f5a174a3a997cd100c825116d48ea261c9fb5060c3869659939df3c38805dde198881907fc93ac04350a561f85f67899b6e678d7b9351e2939d0d583410bb137d4cc6cd8676aef4b571b610c3d5e694c2d3840911e4f4579952905af2d7343e35a971be2629f7b624fd7106431b1f435cad4ab243cb286f9f11fdd15c0a484c86782d8e7cc3bf124a9d091cb9d6ffcbdc469edf47adc628f9e5045ba98bd36eea77f37e57248afca48cf29944fb507f05ab4619ed015ee08a099ab673f295dd2c20784d2873318a400d94a01b90e8b3df2efb5b976e762099a7dc718508a6886f8dd0cdb11d877bed7e3bba9e5f6ee30f7b8834502165b77c7aabe3a1d816e507ebd5d1e0e6a59044d6d3f5448d1bbae19f589c7629382b557bb69a1ea3e33f61f90c0a3f4ed3009c29aca9c0f6f2fe9e2b4cb3cc49354b4731127b876015671861ffa7aa75c5c6a8a95822381f7595c44284c67d5f33008b56740ac2e9495440edde5a131ea943d11f0579bc7cade23134201731673a67e5c74bcf7b8bddeedcaf17a6d54b2c5ce689db3f6b422158c7bd1389fcc429c8296b7e50b11860b7fbaea0c62d963c46b2b1899868d6d1090cf2ac6fe695556283362ff6e089452f51aaea286dc5890d6182ab339263a137ef7e3e32db2c57ef1aea83b5d28207137245f8046b4871f79cf8df7c8e9be15567021d1db792513d6353fb23c57229b410254e4fe38a2dc6b701c77069530f4e1191c9788e69fb0a1df9e6817dc799db85e260c82468ae9da8489e46e98bc09a24063bc5c8b37c67ec1f335c4895b8ae8d7212952294a5ff48b0cf1ba51d8200dfb8896c1f64fbd27339a5f0f7a8660fc387f6cc05d5d762b7a2f7233d98593daf08e727dc6a4f516286d77b50010e166b6b0801cbe6e5b7457b0bdac1dc561f74b843aed6c5cde3e8dc94076a875e99466437205d7352ead681b214894f908709fed3c0e3ffb22f00c846e7420a7bda94ab89f1ea044df8e05ae36a1fb44513534660d4c031fbeedbbcff06c8126b641fa229d0d0271f000b3880515c33f60b6092f007127ac5d256fa630a40b0161a147b111ebcfba9b5bc3afc972b7d0cd9a5e6cabdb8a3357285a57ec1ab7a84de505bcc43f30a552ce5810231b7aded1705292434267eebe9e85b30da8ab801e97859a7261a0e472465e71212fddbeeba1c041e085827d508eb9cfb9fa429cbfcd84cb433bbe7810e4215836d8318447ecc7afe27ebc24fe8ee276358049c023db93dc601d1cba57069c64c067c0a28a42d776235254224e42b2a53128b2a8bf7313deb96bd69e739d1841d4968451e9f3dddae05f53dd3f9a0aa1deb7f87e8571e971c1e8e98e0410de1f8c9132d972d17cc0cf6ae08d682ce89a5832e9e9036191a94f1754c3c12d59b3c68439305849aba37ee9420bd97ccc11c23b1f18b83b6aab05697b160e4e21b847f59378e49bb1b7c1e4be265bb922f967d1288548a907754d8c89cf1064ad2338049fd001e19285a33a879a8d745e5497555fa4d1622af4aed22e688dff218903371b7810a0ba8bfa1f2d2717a400a6b757ca728a2851773a13797944aaf0031f8997ec42033bc4b780dd0c0581b27b52a659326ad720cdebe04242b27ce08334a43c94f7f4ff067205c8222fb3799ae27faba382ae9fe289d5775a4486ab00921ceb5a53e39b19d9631f5a7de5ac7f045f84c100b85ee3d5b0a7635d7941b26bcaa85509af3fa3886aa3dc69bb1eb88c970c1c94a28c07934aaa7557740907876f57151e48cab9c9b64b6b7512c38edc205e453940d33d25b3f62906007bee8cb36aa510de30be8783a8127e324b6f5e66d56cd63f73806cb374e7bb877c89739d0e5360a1e3ee8bb171d607c769d61b467c9f5536448927b41eccf2018dbd9c8ef86bb7b2333d94d47b7d6f8f9951128cc872d68d8c7e5c1a6a4b676b915991fedb9081d7485ac1126314ccf62f823e13b3a4c900d10a4dc201a39c946ff0e7132d70ea2736cecdf74b03591e7aa008520df9baf5d4120e74b1104955b269aeb4130c8343e4bed22ec0382fb131b6a37031ead37750743aaadd01c6d79dd7299b461f3e1a6b923e08ee07d64f719cc9b1b192a2167427041a5aa52c211ae43f4b45048b851bbfe18f03d9c4544803e32a7dd2769da07d8b0d90555be6c911632fd1ea6c6873a65099e964593fe6b00a371c6aeab5c94c9cdf609bf2289f03667aee9aabead8ff6e1fe8a66dfa840f01b8cb964e5676dfd95369edd327b2e805da6aceb5812772a029e25d4c7a290d32c6b5e8d8ef5651d826497ab3acc6cbde8cb78738d10821902007a505d98de595f0b55fe4cf485aa8ea2486b2e51c828a479a569ed4b12a20705f2962d3d099892cd114033c1fb915a1740f3361190a34ee0be065e64d3a784a3b15b9b59ed2383bdf8394bcc9d6eab2130d7a76d3152ca65dd90530b6c04afbbe558a88e923ee5fbb4f51e288021bb9cfe5c53af138e2bb27240e122c40405612212a070b2314ff742b76a8e0f647425230d3ac6cc51d8d82c52a323361df1053a3e80c068aeb00acea9a671123967fd0110a560dd2c5de5aed0d667096bd5faa37587f242704b174e7b662fda19cd56dbe88127c2e04c6a221223cca8942773245ae8f342d784625c9069c89e925a5829cfba4a4b557d2d756ef141f005a1da1e98be23fb1e391c6d960c43c5464ad33504dae59e79496a9a4039ac0b23dc9113e1d0b5ca089f5f06563da99a2f9e6d3bef8d75e08770ded20c286de10b2a1950dba9ec0e0ad41bd653da208cd2aa3b5f501a064d6a02c07183b31fd264886601f5a536bc82ee844ee548f926a139dbd7adac6860213b4b7bbbd6ba908f5b6bc6c4542d8eadafe05b54078a7079c289b9987ba89501b4fd5cfe96bbc2d5dec73672c95ac13ea47593695e6139451658817ecf44cdac817e7ce3a31889d3d1cc2b125f8b51c68c8125be1a691ac72ce2ab5d5699f99887e877af6a57f7a2fd5adc3f881a630d889d5b30fb89223fc23234f3477f7ccee0e3adcc4f773b433a607a8e8c5d003b3f72d6d1dc54681c19e2aa5c5081b4b804d75a9a1eac1e7db0b4567df0f167e5e31539f318ec8de689d5dc691621eb4ae5f9564713b12c0e24de8d70a40de0380e26295240d9fe22ae2d06a41df61a07e9e4367fe0ac55e41b79dbe9e8a63189fc4547b7426277ed2cfc75a46c07a299e110bdeb427301e204890a1b0897865e111c6c7e2ba1409044ebf7fee8a71fe45b77739e8cab61ad21090f9c15778d296bd45e98f56b923e3e675ac2570e2a2722a163232041105f4a78ccea93408b6a25cdb6aef06a9f88a4694ae8b64212e94bb94099268d4b8ad9e1e153b8946f766b899808f5bbc1f59abf7c5072707418262985e20f31cefb9ee014814c488344fec60b0c30fd2c4566939c329b5b6eeb8a82b1f4d5f114976967a332a0385cccac9991d3aa7fd6952e397696151f1a750cb6f57ef216f663873c8a2db156b9486638dd635600cd28d94afce246df662bd21fb5c25a01ed3e71bb1171e6b7114a4852c5fc25090b0c006ea9c085187937ca5f7abf6d3cee441db4407815ed05f7a916a2cdbb485e4d054919c5bc9a0988c629676d82ccfc4ee6722607f2270019f652fec3fa24e54c8f90869199473f0c1d5a2eb518ea85fdcefdb168da1a0849701907d0e0719b3de1144f93eb05973d307fec839f1027d8d84fb9bbfbe919231042a4407ab3025e3c61049398d3ec5a52b5bf2f01f2de47aadbf81291d3f4f4cde23d25036aeb7f83f501d4a8dee04ed3405c0da50ad6ddfafd2f89c9e601b860eeeb5f967eb135db03ac72385c65700141c15a35daadf7230c2a6afa1f95ca2d1b4c5b4a541d14f3913453c6440a268c0fc90b1a4fe8bbce02c511be5e6c7dbfbd131611d0c54b1527d22eca9a7055f1f2bcea27b0f95c2f48ebbb8a7796b4c40fafaab464e7859159d9f677860cff17fea32bfe0685357cebfb1dc841bd3b26797394b9ed3b6f0512a00c00f7b4f8352259cb1ada34d1727ce3d84db4f9b459d7cb601ed363929703d4dc3b7c5c78162e02db6523003e514d8d4784054a8bc78a5e0efe68f76bd48df97d20e93d44f05ff450355fa77093fd6c6f477e82df5817e16958317a57a62db32299042f1a97c77b455e5a0086f8888c0eb67713ce7e5db41dedc7eb57087bbb16539db5da0099d64ad426b0ff426b441f63bb896472b92a439d033da7a5122f73f8cb839d772c200bbdee6bd17540d1ac3dbc9e04c9e95e2747ce7eedea4011aa14b7bcf419d8a61895f519485ab5da3abc5019656df58a329e0bf12fbf7ee52fbf1b21c16c9dbd5e073ecb797986b7b8d7369b60a1b78e0216097995fcc487e9db10323f614a0b9333248af3bdf04e11a28a3ba536dd0c92f681947c5a1be2a8886d8905eab3f0fdf0763798e4da943e8b21e57ba9fb0f8100d5280dc0b5b060a9bf0a7c2565266729821b2b98f0c0be1b2ffb8d915ba0dce7534760d9160ddd741fc60dcb4a2db7da283a1b3847feabe321d81bd21d8696bb64f4d9d46b88ba9bc35a0f02e2fdaa36e5a4be00e82a46e14af8df92057a54d64b02244ab5cdd279847026c5a51fdaf879ba46124b8c88ae7b1cd61b098a0c1d60dae71dbf099a17baed38258d30f4ec699fcbfe2c884f274cbf7954a62629cf47f158b468d3b1c8e5a1741e580ab47977490141d184127354622e06874647256c3bc163c6ea784f26097c16c4137410872c04b864bb0e8a16d976f56b087a56261bbf71f077aace11fd99186ff899326eb55b627d856994986f1f9bcc6b0ecbdd8918bbe05a141725684d7325a14e00fdb711f5fb76898eff4944b100dc24c6a0e8146a16b7ce193a16da5ed9fb3a860ce019a754668364aff6c1d3ebe5b80a4f268e8020e560f991e7c127d9263da67335b42df3d30cf704f8993245efccfb5984f1487d62a6ca1ea7f7849ef021a598dcf0c332781377707dcb4c93613f01cad4dead88541302cea278624dee64ec7f9e62c7681ee94d944daf84a40e9ac4c101c22990507e91fd8c55cc213863bbdc194ec3d9faeddfd86c747d0e4c69dba52c916642031d2557ba2762c74c75d501c119fb6b6cdca601df9442f68057f3dff54bc7027f4bd8bdbfb8830af6f1a33777e172df7f2f3ef584e83b63052e48522d2f88f47139acbd861456f3dc35ff2116f9b68af3e2eafc42e7b40c6257f64fe88e8c0e2a58f88a1785d8809757eee2809f20fec9bd1b03772f1f83beaa41d6b71c21e858c309edcb0ae6090998ac54ba3201c223b08c3e8b486fe53fa048805ae0c3081dce8e54f14405c09af9e37987956c7ee213ca79f8119b7e40be65daf9eeb082780fbb8b2f88195d615d5cea189bfb185e7512a67d41994602ba82b26d7c68e977e58901b244fe328a0ce3c6819aad34ecff9b79e9fa4674a4a65889d0300f192b83cbf0c56f11a5519717210f3156b3c02848131f95eab089305393272a1f241b68fe2388b9157bd764c8ed02d850c196cd346f4136e5a0f48bed71f571a9a6be42b7cbed7f12f3b3321ef341d36cdd967e7f154afc492b9a28b5d5c62c92e7f62b08786153dae17e89f168828d2439b497ad429335b90b7e4a511aa097b48c4e04223e4d13f46945bf1ec999baade5819031d8bb5668462431a78ca0d984a3e2fbf8845246e660d0abd257ce781e41e607970e2a494e268c74bfd54ca2b624418886947c475682ef6bec919d888f736fa00d801c96cd02ed5ca958d641c6c65973563723b59499446e529109517a10b9e5fd8314e65a2655042c561e99bb7d648747c71bec9a203c2e4c6b2cf23a7874952009cf1c5bb2f2e28ee533ad538c93b3802b44e893999a04907fee0f92d2434e129ba3ade199471583584475016e16ddabff99e8fb060fee9d4d170b23dec43ba6dbeec87b9320d345d452ac2819da708172474ae3a155c680827e5b0790f9216deb0879ba987815b2fa71382a2a4dbf7beaeb75dba7c47c2a21c3a38e0362473e9f329a633bfcd323a137658c4f15fe0f12ca7522b508d723ef5ce2269b5a8befc4259560e598be33823c0892dac76de57ce98643c704d972b2db600297c01a977ce19282add97f5e1bc17a7342feddefdda36d77a20d04d04b03bfcee96929d42b608e559f84ce62c5e9331089765ae20807fe4b3d62ab0bf51afab9597e6a06c249a1ce0e41e50221ab1d4536400259ec2b4829f6568640d84726892ce659ba4c49ebd0f49be29fc35b278a58e1ac38081266e7359968704a46cbeae109f0d650b4ab84f45492bdf5ca2bb2e65e5f9b43095e27d660e5be88eb440710cd57ef3eb8197b9772b86e7cb784a1a5a68ebb4a51cc2093893321895c01105f045e3764a69b4696d60dac6882025d361e0a5e5ea30889dc4e0af4e5772b35a8790678e2e0794fea5664a944d3fa1286ca410d10b52cfa2e8db93e4882312194a9c163974addf1d6ed2e48b0f1efffe84e8bc4030f3c4e5e4c585c12ac4399b6d07234f1c5606fe7490160f92ca8b855ffa30764cd6998673507dde658db6413813592bdc8099b5ec770c16ad7ef74f4b00cce4f86b0c4b099bd572dda07a33b295e75f9f02de820d193112952cd1f4c62598b35473cdc78baaec8cc711a32676ec3221e6af9201d604268772173c0c625b0ee0a25f5e2632102400e0245057eabd4324c2f3971a273ce1843db922c2c85901c1de2fa58ebf946e52070278df1b3e42f02d44fa5fafef9aaabab05478cae829cd9fb7f54634a7d0ec770a813ee4a1bc28ad5e62532de7c4b83be82de8ce4ba46b26a54e47ad09afe9f1a06aa7c1f10a4d1cff0a6418e4eb13678ecc70c709b89f90bb2c6e27cd2dcf64c8e12ef45c4b04984740d2e80870af83e5f4376d77f571a440256c58ba6aecabac39b952736f6c85282376625e363de9cea4a533c0eec5947339876408ac4d8f690e431f9b5e8a580b5753b89e18d0d6b4ee8e3495d7a9e96a7f1ef1e3a6e9dac08ed0bd6705d819ef06a5dae0fee289e803c81e5703f4ca08cc924c22e5594a3a8bcd75abe18987a37b27fd97e27884342411e70ce484a616de42130060c6009fb76eb36d7b6d0a59d02d806bd92bd7a67372418aa3a917805a9317b92f2ded97af972cfc6a589b5f93d7c6571dbff36a2de5beba357730880c0ad6c6832e3573f092cdd88a4f6e2a226783fcd62b7332c6c9d2b0e8211b5671d9f62b96b11a997abba67d927ebf623f75b1042196e11db611a54a52b880d4b481ee4ee518a8c800b3b44412521bf1e49f3282495ecd07a46a078d9ac8b4a21bb362db64431ab05c08db50c958117d25e8437c0155fd5d8d3a14f32ac0499cdf6c33702c6474c992f423f9a087ba651cf6f98314a96ae9425fa09478215e8c5e24afbc90cbe243028e3604c07891561a3fd6c27e75098e7e35ec4804ff7b063e91b97a2392f627a35c5693142975b25049dffdc7e80a27a231376992c688e95863c78c3d01adafe1c69195b67c025b7e3c9abd5dbdc117f183be1aa34f6568e1552c9301871155105f91042c6fc2f0aa5e38862b2ab5fad8cc732d383cfae3829a7adb0ab24975502e41987777d446e911f7041747ec0a453a1040f23314fb688010cdebf511e9903630421121f62615829a3ec29a69c5d66bcd8d86aa6a834d6c3808912c2743004e72a0610148bcc376647178693a6c14470e9b25654d81601a8fcaef384720bb4cbfc704f009e3eab7560823d4f9506b003fd413cc154c71d645326e95f0402d1676ad3a9c645b8476e1c4e4b5d85c239cc31de41022f6ae103d46a6ad46506f997d7f0ff48f56cbb6bfec8cfe16ce8be2a60a5ee37b3b6e35df95f9129aeec1865241c13f82416d7b39c3339ffab11c94d9a242c6039dd518e426b5d913a190d07018e82618e44a6131fd8d63ad05e0adb51475e0bac8e5cbe291e20282a9f6e94b331868f938153017144fdef0e4195148f1543fb52c4a660a9a963b35200fa6c65fd4e8ffebc53ed7da8c5288bc300d1fe53c7a4405e1821272c4917ddefbc3162e982d0ebe2fd682cf3ad29be479e82a604e88d76c8f4ffd74706ac7a4a32700b6ede0d20e0e7e22f66c1ed362dbdb61b9aa9193faa8038c0c47318447f2ec51ca11fdf48b2fc9390b4c66b7eb3d06cdf0ae6bdbb820e14e6b84fa66626be7e0af283aa213aab32deef1805d3b2371a1f821364739714a31a5a32d9603818d3d48cf69da03fc466dd950a2cf1497cd733039c1e57315d4d699fde4012b56fbda27f7f9ea7dd23b7149f12df1d42ea0eaf4f877463809e9db0a066308f73a72e953321c076522569ad40e1d2f1af8be49b3dfc144d0dac1c51ae1c5bd8dc44311134f4953a95b63c013bc80658e98e254e460dec36b00f33c08cde55a43558939fce7eef859b65b21ea37a89c0e269ec5fbf2e87dbfb5b6c6ded68dca787761ace2fb26070c457d4bf6c9aa8963adbeb8c7b243c812cf4a7404b8b8d72457f664059ff84038f5e4dade6dfd267d9b405581098643f06e5dfb510f30f04a6309bf0f1891219b4b0ede52204e837fbc60c3ffc2abdeb5b38606a480630e598d0765363a26c6558948c1b38e9b8906b4e3d2cc248a596be25c8fd2f018eb38b2b27dc0cca1f700a62b2456d65520c6dafbebca29e0d14476f1f5b18149abd96b6635ac8bfca5e432078dffe29654822e5ffa96319bfb63b614a499e38c3178cbe1bccc2a4b18c3711addae90e25aba8f78117af000d92c0626c6b90aabe201e84c3ab74e91d2ead099c18b298264011c66942ad1a4ae3b3be93cbde472042d359c176d50ccae3d50c0b5f0bec2382496cb92de3d7865c75447d80e95b216456b6c3cc0422310c50370927b007d61a2b9e9b6aeee2dd15a87ba89970f2418e27fd5b311c94cc01b686c729206ed3c8a505df915ebc75d48a296a03a811ba80e3147d6bb4e9267da5deccbf3aa5f51d03bc594863361e8f9ce657f74bc614ee6b407cdd9cb5b2bc7b17353c95a8d678e47ec357005ac8e0bc69a9940de226d2ad3292059459558caeaee0ca6367574b594725a39c3f865317076b874c7e409d66a174b65f501b31a6cc76579ba4546b329710c0bda932e98f69709eb447ea8fe81bab1b3e426fff088c09a2df7f520255bd865852a6cc295f316bbd5c06493381bf74896b4456870b82edb706fe6e2c0b1aae7135d4e9caef83be5521020709138905dcded85e626acae32a55192bca69394a002bede0167784687afcc4368a4ba4a2d92b2ba80aff1b90ccd494a521a1c33fdc183a1ed5b5abf4195e5b080dcd01800995caba400751a70aac271121ab3b8f46b8efc8336b933c26fbc790c5431954295c44f3c8ef5d123929667f94aeb81150c7f5eb8c9a1245e37d951de5c5c5a4616009dcbb33c2da3b97268447f5a383b870b42db94b59e0212d5852c22a96bbced81d01e28e97ae4876d279c8cb0daf4a72c4c41ba0cdfe7a9f18483a4d156ffbd72e7436b3e0bd51c4fbc0962ee9c967ebd1349b581d5193bbd7e577847404ff62cceb2c2c97f88a7fa9b899c988778ea6f669e8bdeff3b94babeea8c559f63d357bbe22f2f2013f2b110f1d5687f4c72ad6a0e9018238ca5e1df69ffc1274a220cce87b56636485a0f04cb1183ff0063670b95a1329ad781670a8b79438fb2cf97f33ff87165f6e1b231caaa758c2a71f4896236ea40d46bb605eaa86d6a428641191a4364fdde0cdd92b220885e1ec60632f4d65f3e24a37cee3440e68443b5825b4ed775fc5d55a84cb489ed4f19ecb6bd38d5cde25c9cca6b70de027cf9a9807d6d7c3d3a2ebd81fe86c8ad2760a545b379d65c36a2b76407883bb96adca93217ea40ab7f14da70eed36b46aa8c5b093205f9eb2b4d4ace365cae28eb1f24962773cd9e930360d0dd7aece338a452b52073a24a77b5bd2db94531951201a66c7d8c77b565671461b36d9b1ae96d0a3c8ba46d7a8da49089718d0d3d7c874b371e2e0eefc301ed7ca444486cce42e56e23b81584a0907bc8a46c76462fe4ad96fc3ccd6a4b39304138894638e01e78c81800843b053aadb22ddd3c19570b409201a822fd2c100a8d350d41f51491a3b194d0c316f1b848d03a5c5e8198731092f1c82f186846506b4c39904083647ab5ccd6ea94b99ddc0b11ee987d9db126d16ee371146554ceefae8466a882b1f1de7fdda19c126e668ef2ad1336435b48197614dfc14eff163436b37c49af453befbeb44e9a7fd9468dd5aaf11536111d1b46e1926aae942884e1b35faa425d942ec92869b8222da44f91b3bb73381a49b4da501f7dec344e8d1eb597bffe1e72f8dde246a93783a41ede408ef08ab951bf13f05a52a990492f1efe883d19c89ef08aeafbd6d335fed1f5fc7edcc6a3a1fc2796f726ae40da0039d62898056cef920b93c74efc95d38863626da853a4cea66b08df7f8ed8b66e6b77053781ee03e256a5c29391e33343b8cfa7cc07b41dc3b8df5ceb7c82efb68e75b9c67c8f96d5093b0702f353b08559bd51be695ec67a071c77c6cf9235de9c39e5405a7edb97916b66860d9e0f9fbbb2ab2fc411f1b05bc2adf7593630eba25b5a2af414f164723902f501d2bea332c537f91680c6caeb795751d2f2d28163f45985b831133bcc7a6f2f92227d8853242e3985780977c527e70ef4df4030d5d268705e97d47a6eb0828990df05c829916939c0bd6ca354593396801ca419f4a83e53123d7c547143910b7fc80294a407345c58178bc0e91425dd0d44442712b7923e19b5f8b0aad4385b39e8175bcec0855b6ab26b99f46adf21007ae6ad8792385ff1c0b4cd72c2d04e243a40e61a2d38bc4ce3f924d42ed7c4cd108adbd8e3b8fb881a9de57ca7040e756c24d91a658c9503da92e93f63827810dad8121ab8ce0af46a9022d4ed4f14bb849e0ee85d260f444ab580777e3d7f444a5279f021bf4888dbdb160dea3541cbb721a18dc2466933dc614ed41a96c72fdca3ad1d143b3892bc034713c938b1e3061f9d75f7b646a32fe8eadc150000012bc6bf7897f25bc2c89c13ca12880214854807b7b0d12a8938ec5ab5d675c57a2ade33ea25c49646a48fa82ba6500c93d21b11675a1176d83a5574060fe89ecd4625340945d1187f3b09fc0ae827fcaa79bee429cc647858675cce4324cecb731a144a8ec411ee604cc8838620a94f2f9008bece558658b542064e0cdd5fd723f0ba45395fedcab11e6db556172de3c4e30242b4f8e0f7ffecdfcaf09fda1d6ec309a83680ef985c4a5800a1d2fc3f64ef662da80aa5127343c9512309e23d77add5018c4ae34bcce408d36d84b2e53e511f037abc46d2c0cafe651514cd097776912d6528e81a0dd982c004fdabec5bf00f1b2506309e46bf086e06275eaf77edbafe9569a1c38d7d78ab841a1eca1adae80d7f2f4f13e2d75af367a0b42fc389c3f3039654441e581942a10768a2a734fcbd21a3fdc7c3df0e6cc8dd9892329d3f474138576485dae14cad49f4b85b16e1cfaf95e13488a69673b1f822bb8d5d6132b184a6e105381d635c347661455f8dcb631869e7f65a06f1bd52327bd40942e15309f68d84befa937bf564278fc37572d32b326de17cdead88392d2764aeb44fdb3ace15129b23c0a74a7708dcf644cf55e6bd505f863b034008b8952a89b19be79e875b76e8b9f838fb9ba81e7a48904d321433bc7b1e689b9e0fa6f688d01808bb3f2d10dc450515e4ef4586c0813f5589ca22acebb42509953f05e6123c512446a59ac5a619065e277862a86d89f2fadf4bca34608271e1d45b16406207f9bd67556b4a682af0710df9bdfa98e7f872be15490a956ca465a142570073ee82c20def557b4c7b3e4838d2e41f36e30c382600e83f0c534da690dc4976150f620e8b615527c47b7776cd41183eb6c8310f55944cdf6ece7eaee5004dae560d948ef452b0608c93233b47e76b946716ca8896612d957e077a68d5d789e6ef9dc07cd5aaedb1696a6a7cfac6060f0d3ba1cd6db17772e5b5c25618c4c9639f668d50aa60f674d720b45b6b9e20eeb84d349c1c5f261e47d5c268f884f86d474d6f35a9a3b0f6207c368dc6252fbb65b3642bfae10789da99e4e853b2f65ff373ba0ed0ab872e5c5dc5486f41c4cdf85aba02ed744ff238ae360b57d09fdf02f4d4ee47866372644127f6e9c7c276764fd238ac164b57d4971f0a5a3b5f8d5a75496ce5d136d0561fb3441f08ba63c459756626a319c2ac769eb0e8e7d39eda085053d0436166c4cf278689704bf0218f244c03cc2443c563c6bed6638a890fc28b5787de0bd3ac2e437c367e2e70a82382cf7c67c5db7072f917183a5b8e38b6e71c29817fb30447022890973aa2a15f26d2570c0d424bf4cdbf3c4c1ce880a4dfe6ffd536de8499d4a0a8fc13676d321028099914ca51c2d03b05b712337aa885258361c01bd868b58c9394a8b9b7c0358ecc8bcea2c73870f38cd4a9ff37f04eb25a9d1505b7fc85fec78c3aef616521501804bf77813960f4db413ce2e391c4cb17632870017450ef6ad5b46e0d0213b1f3992d73a8a52e6d244334f434a38d6ea5b828a761c31b04591c5eb6f5b44dbc27a6e23fbb594d24ba515f1e3d68cabf72231e8c95a1f2458c5eb1d70f518576a3bab31538833b11288cb51e00d244cb4da4da151f684552759c36398da9fecd5933caad53e46fd0b2a33b406dbbbb39228a9d48ffb03852c2f964a69f65b816bf2cc29e2e04e7b26dae414f447849c21c2a5b5a18ef24e3958f4013e3b049af077ddd807e3a6a6599370258608b507b9467904c9665e291caf202cb491c68df26f44754a0d91d72350b6e3d7df039b7106f83bc82f026a022c8856700e0a3dd409171a47d472be6f85d521d0d27815b3d42201352dd23c60dea15b095dfd01392883ee388d6c6ad6f22e7d413d344131d41da6f599945be9103119d3342538c2da5480f53e00f2c46edaff94fac624b97b864d7c61280a0f6d5f3759cb763d28d2d9a4b64fc4fca0fc6d50097d308cfcfeaa4a5c9d8bc3ffb40686425f029174f0c5d3a433e5574a14393f6f1939d605352f4a93909821d67c69a78b129b1e6fbcdbfa355b65524a3971b51e08f4cf0dd259bd8a889401cefdc0aa7dee89a74cab788df677f36b50eb1f9d514998e42b5c2a652561f2bc023ecc0aae7be052d2a4ea550ebdbcf307691123ab9ac64399049a8de5c3d21839dccd6bc0a792e11b71174370236493f0df41bab0acc13991286e3c69c6284792c799d9b3258bf7189204c6b702fad6ce96d7543648f3870a3897cd586c52e4761731e2bdb8683019db49c0ec7af625414be599cf34425663a068b7fa7e227168c051a995502d3de8c70e4a3f944e01c369070e7e3e5d6b936d3c4dbfbfd411ec1e72b178c30b9e6c1829cf6c7a03ff316c4e12481e2d3d55a65d1c7b4ef5fd5da45c9840eab368db9a1d62c10e2d13f1cb9ee006a77889d925e80ca411953b0b2eb5da479fe9ea031af428ad7980fdc641e88a31b111d7f8788f76cf8045b623bf69bbf68da0ee4240bd771d9b8cc958e12bb5d37805b86d005a88661b500ac61402d00eb485ceb0901343a032a5bca765b30dc11a703190b57b53a0ce6fa53e5e5bee68c02ab3e7b2512719a52cfe280be93b59273545a72986599c080d4eb39c380705f9c739e77f8dea5117ec10c42bcba8a5375bd33fed81ba80295d603fa51486fb2f7965b4a29534a0197076e0729071703ecc30f7c0136c9af95b7c0b14b35f30149fd22c64823ad957e371741f4c7c67eaf426c582b9d5936461528179be816658388784431d18fa511c14471f46c6ff4fa76cf80417f2817dc794684fee4419e11a2b73e4aba1f07bd5b72bd833938411de8e70481402050cff5d929b9dec11c24b212a9ac576160bb1b91ca419137ea88d878ea6ca20b2aca1f09c1fe0d5d502e2897171da14f211feeb7afc2280af609edc384eb9f298927d0a75056f4134f22102c95ba20d813256a30b9fe3598b82974c1d9c2951f8eee0f045d685c6a4458b0ef829a33d0920bca4d21c8a7566b47a37f102c9544a9984f012e2789dd5c918b65e8f51719e166c1c8002e2789c52e8f8b7a54229428d8b063857c321f2de473fd41a21d4f9484543c8994d030fea231ae4fe1fa17d7455ab8f70e91cbc1276c18f209f98494380242d18ec8e5a0d3dcb0636551b022253847cce6bb9c040b4c4429142536ec5856e08cf1ea58abdbb1e249e472b1b0a0040b53c4e326d0bc9c13861697037922210e039a9f28656b903136e86089a2b02c33bafea2d70db27043114c8789859acb49c668b95c4e3286cf15b9e249948a302e7265de312452827380ae08250c2b72f9a3806143518a95640c55922bf4e45441c905c0e51c2aa42ee72091103ff14c153c3757e46272fd453b2e82b989ab68821151cff56f2109c28f06a23b5487b6a88b22b9feb426487777a31cf47a3baf67e20cc8880d413cae9debdd55ca087239c8afeba09c9b72d0855c0dd43da163c51388491021ddab3661bbf93c4c1f2e96074020b3c2be6af0841b8a6013a03d2c09c9c0b698f0badd4d3c8178bce7e6fa7736f1d4d9dc1c075de77a0ba4aa367685ba01e1a4ae3f4be4ba410b442e207c70d0d9c7b6c08622973f8d895c2257f7cf6d171b185c3029a594523e318d1e4f2486f1af60e7841d5161259d5956595180711557a41bd28bd4433a42725d7f9211528bc443c221e55cff2857562b2b5f59adac5c6e375c59cd8e42c618a38c1a16563eb7b262195fa51871d033c0da58516cac94554a13cbd2df97e5067afb5b317251eaca8a653a1b3fb0c1b291f38971fdbf1fef07d7df63d5f8410d1db0a9b26a20b94e038ceb4fc38765fa493ab8fe241c92147c9a29d28a89eb13057c4a594598c9829516d894550d29d8241ae235562cc3895e50c3c8154141e3c532dab7ac5866fb1327ae93562c33df3717682c96e9dfa4703d6595628465a610ce31059b660a18f44f59cdd44cb914a455465a8986b829c5c6c117b029058a94151429b12adc9595135efd141bf9fe2950c8552c5ce99a58ed862babeb95c61256b7458a9458cb6a65455a51eda3f1ca7ea65866ceaf5e44710e29515c709db990c296e21c42b61fcce0a62e758d2595d890b4bafe35706a2071d37cd28ab422adb24c3504dc5ac68a8b514697f2c54728bf516b2ca3ddcd5c2a954aa5582a81e068646d977c4aa20f47a5d2c997e14418cf5811c6a7beb8e05306639817cb7c2cd33783c2f5cfbae053e6ca5c61f88fcb4d61d6bafe61a6e3e3a630c3b96e0ab3d5f5f7277e6ee44ccaf5276e587b52ae1337ac4b629cb4e947e3679e100900a3f70ececaa3e3a0534a698b725dc332eef429bd71507429cd329a514a29a59f640648ee002e27a1a2757b037547a58ecf061d7abc28102db73f7339f8ca5cd777b2d72a7ea1ad2baede7c2c0e10fdc99f7c1d6089cf3306e0c2cf8ce879c68e5ef4e4e92bc2f8d39583f52605967e5857b71aa9373be8673a3e9cc38f60c35a5b3a2e4780cdbafaeaba162283e74f91cd554510255056154196b88983b003d5f5efeadc71e2b1cd5adc4b2fece112316fd7237eaa33fe15c9ca4db42b0b74f29c57e4e4b9a65346592b11079d7e585f4b622aa6a182c010e4fa579f28e9ca4d6125723dac2d236eaa47dce4426aab48ddb1617dd55a635f8797dcc43e88b8897ef2c18e013e633fae89a7af267e15e6a0eb8835d72b0f1636cc60d7bb27fb71535337c5506e7aedb8c9e5266f205aeecb154b10b9add37213cf1013d2c26637ec1a1f505ad8ec9565f55579a830e4fa57989b46d75fabd184f8e905567bc44865b0baa4c660543872430d75438523dc500be273fdb3b0ae6ecfaab403cb0ed2fa8a27caaaad97ebf5aa3118caa7e6facff5cf70e249f4fe192b5bc593e80bd9312c75d4ed584d3c098061fceb0f6aaea829d557e7d32971d3cc62f665446c585fd507477d5d7fb087eef8691e711887e9968e9b5ca6f9b3a7878767674747c77372261224376c1b9bfaaaaf5a6f1cec5f1919a1401e031659206149165974c1c510701046165984114626856c959436ad1d22ac21383aab66958a59c7037a60198fa592118d8412a640cd1ca1b1e03543db0966b972a4034b03296e019c7976cc212facdf9061bdf2664117d79b6019fcbd55397687d9363fe249f34f5569c44d51a552cdc92de12e7671bb5ea8203c1ddbf9a18129b9fe5c5dae964da90482239fb131220efa4ae56096c92fbb19fdfaedd5aeddcd953b7be6715d90ec766833ebdaddb53bab2f29a5557ee609116b880b2af241bc0ddb5ac9679bb061bb6e6ebcda051bbf6fa0df2e07bdba833b9ee32d6739f81feb4d91edb9d809d13565dbd552d832222b371971537c97abdb611ad4e2ae1512742be0eecc0afe5e90e0c6d85d7b576ba5747e911569c26ecffd48d3a66ae5a0919b2399103e32cad404f26eb19092a6ac40e5112bc448a594727e943be04020909be229fb92d7284deea7d20ee6e1010e96ec0d2cc8e0c4c183fc521c8dc007dcd83f224cc60000b76958226597b021019d526c91c4064f743183264accd0d51232a03f588206597c1f616a40109df0c50a6e1cfd4b0d2637c5e7865faa71d5e544dd3f343bcb961f597090a879e5e50e8746a12e0be42621587fce6e61fd434ec2fa2381c65533aeba1e9b9834a3d964c9214626199d4be6fca61858b4c32c73cc5541a4a185b40cfeb1e3113b06f88cf69cf68a33f3597290dfdfdf0ff23aacd5f5eb56abaaca8eda7a4cd3d0392ec4f319ae827c88a738c3ce6ab95e7746ef5c7739a8d3ca691afa0aee2154213d54647bee1cd033db67c41133dae737f10a2d0500b919185d22b8c98d1f0502a25908c11fd22f22de22b58d0032937df5369085f3f32dcbb24702c84cf621a1fdf67d42f0fbf471a3967d49648f44ad59f61589ac3e12ccfdf5b31186f91e2181cc1790cbe8201d80ccef5bf26f1362a38c35e8cc8f273953e486af84286e18c3841b324b83c10dd9e5451737e4d79ce10516377eca287e299e803ca14410929005124ca184d44cfc1ef1548117094c5084248a58c1c44cccc0cb49bc2872512e27f122053ce06e6e4a29a52198cd8360f7d05ea32cf3f372afbd3f6704ea87831c43fd883025b08dfa118da841ac3581a5f34796d950e7aae0146d4a9972ffa8d5fd6390717e1dc5130396f0b018eae76387f6db871cdbc08f6e41838039c0a6387fae4870354fc65903cb9c837ecf0086f1f711c411a03dbd309cc38608ad3860c518b3988d4623e67a90849060bdb9d8597767dd31fb58b9bb6528c6af66a010889981a6ea6604f25a332cc028860ded88657c87edc6166474341ad51abb1e4c9eb36fd49792d64e734c0cf32cd58059e11c0cfa976274d810b3e26e4ec13233f7f4c0572ac912186cba925cc103b33b1cfc763b643eb76374dc242915c12a12aab5a3d13f08964a42aa1dbd7d50f4335b73f6f0f18386071dd867728f4adb677ab0cc0b778e92e0f6c08bd1a41b728b5bdec7b7e1bfcf97ad1ea21f284437b3565114f68461ae7b61ed1aed8d5f77d73ea4d62aa25518d2df846429fba5d603d0b066f82d4b3f5fbfcfd29fcff40cff540da9b5dac898165a7c5a68a1058f1625e879d9c20848d8620b1e7c91451649086389192c66e6549c0dd7e2763822dc100e87d3e156dc91ebcfb44529a5b4a77866e69ae0d364a99ea3c1754e8aeb9c1377f3d97c6c16d7371fee33627b2033da17dafa85f5b71f6ee618da7c1cf499101876fbed9b2c9fadc55b2b6e2dd636597ddade00575c7f518b65e8778a53cd1c8719754dec3627eb091b72aac9a93895ec50568aa1712acba92ea7e2545936831552c1139ef0841ee0c00ad11150df5f8bed5ddddddddddddcd6ddab55f76fdcf7f700084c20b485dd425bd8d00d7b35819605f58ab342ab5557c70a384b64d9b66ddbe6f5cd7860fd7b05ead58a655e9a066cda9a154fdd87be7bd406daba1dd973bf7d7bdbc6711bb77942446edbb6eae0f6dc757f680b2bba61afb62af08038fc4b55d0f1462264f3012446cf9d3ef800e12627725d0c9eeba06e0709c65a432250680bcbddb0b34a433f9c6041df3dd7a5dcb47d3a38e80d60e9ef3e1f11c61b30453f4798ee3d161f3dd301d18778107d2cc4f6f5b90f7d35c28440946ef6dc4ce609513f0472c3fe84f861730129d2fd04dc549ffbd083be05b04c117607a6a1df5f0796d93ef4fde83e1f9dd01656bb61afb855afdac88d9b46af5301504796a4c0cee956bb9a676b9e62fa70d31044d4db2f4437b91bf7dcc70ef68f5511f56e1f4b731e4b7f2dc595b1e21c26b061a756ecf2c1839bb2e7981bb4148661fc3987ff8ae250246ed27ad520d09711b18d03fab05717f4855dad40209b9410d5901c075b3a2e075d9c8f4ec0d2ef8d0aab815fb2392686b963bbc4930118c6ff07ad1b5871436ec98e5e0d9ce3000ce3af738350854f7fb5c6b48ccf39a0b0f1fbc398989839fbc5edc3d6cc3c79e278f911e872944ac984ab5d2b57eeee089ab4d2ba39ad5ba51288005fce4277d997801f6f6aab6ae6484423a094d266e65a9dc1582ac556c7c4989898181da1efbefb8c38000d6b26f44066421b13a30ad1b801b8221c19b6aa6d3c9794524ae7f5d1aa19d82132edebe4531011c65f030c7a1ad8de3f88789a0ce3df6363a5347b970ed626d821e4f5ef1ee2a0eb52d81a140de86b220ee3350b3847132c838a7354256cb53ab00f88f1a1a273c8e69d169ae05a31f6e05139c8f7eb5a2ba54d388e638efb39778ee3388e7b8eb5b90486712ee60ddde5bef9eafb381f00be0731b049b624cb8e21fa7de70e9268f54d1937713819379c38ad71dbff8340de4c4d2153c53986d838888473b072600eb6f6c3444cb682b9b2d9e804a2149d1cf31b1566846a36ff60b62927283254be94910c10fcb736bbfc732915d913149452e90404ed176331fe30e285d5dbd08461a47d3614d79ff6efd811e7f8800d4121c74e34d5cdac183ffdb839d19b08e3fdb4890df96098f925fe31df6704bbd3b9c5ac1a93b1fca68c855dc3de9d080536e41a198bfda0acce455dc0a0d6ca959f65b8a585c420773cdcd9b57da11c580efe1b15b66fc86e186fe211d7de27111528129944a611ed9b11e7c369138f38e8d626c6f9f4c378b30aa714577b99ad588673d05eca901d94d912b8ad06dc7663fd39f7e852327fdc31c6edc67677b711bd018e5fc2108c356f7c99e32e9dd70e4cf2dca8f95c8f1f8b6bd56aede871f868546bfc19e7ffdd428f0a5817871b57be1cd49868b119a02746232dc6c3baadc586b0b69e1a232e6e890a09cb38cd4aa7fb09096119df7460c311d70f554b83c3afc55a11c6bfd56ab55a7de54b867078e412a944f6f0f8978dd2c68f654e07f9b26e38aaade9b0cc12ad3503f4021b6aad988cc91ee9e3a6506b5d3785201e9d0b6b8d6ae88166a5d33f42709c0745a41595a458e608cb7869869f5aad1d8d5c63e2a0bbb490001b4a004c83960596c19fdfb558cbca979b785886c64d7ce52b9eb45fd9585595af987cc99716932fd9e23e838f3efbc2e36facffa41ed8f9451047a2c2b214216ff642886ef6d10fb5d8f5cfbe22fab2e4d0777edff9fd107fca2fc684889b18247fdce4517af30c85602b3720b0b90c84cb391fe0b9dc7663fb55e58f9b96f8a94a2692090d4d9020b7854c21f7e7cec0ded41bbf6ae32015761be2a6a60faaae5584d1b961bfbcbf4824acaebae3a7ed26c2b84982e2cd76134f31debce2291e4172fde5176f6a18b63f6635f145cff671fbb841c1f667d996bd6759c671918bed5e4310c7f6d9735bf6dcb6719fc5df9ee3366edb70e8104936b0344302852eb877ee9d7b7fee0b5babb5dedac925ddd8f9f4c178aa957ee1e8725b37d7931707b58d7e8965fae9b3002efdd9ed3041b4c3c19ffb401cd8e1e0debccd7145f4e53e1cd2fd4936d8e24533c510cddefc32d4a55fd857b2e460af7c7b04ea1251a5c737eca13441dbcafeec76943847f65d1336cdf528959329a5a1f0882b6538c36d196ed7e4f0b901a3a172db4777fb5956d01702e1694ed8fec99190581e0d844aaf6b3a96fe6e6820b7dbab7193f4e1ea6f6f8e3ec4bc1e3fe61297b8e4d525378bb9d94b2f2c952aa8697cc4185def923a393b244b3653a734f4241ad86ce51271c4ed4ffe38987d5f1dce91a8b0fd38dc84c341f90efebba22b7a6169730e3a7378b9f3eb9e48c244e76eff57e37edb364dfbf0448b8930da77c7c48e73d0d6cfc5c4b08c5fed21e08301f85435ed354dd342eeb99af6ed951cd44007b5b086e0d53ec65fd3be46ed396a1a173b1e7fb7adbfb7edeb0f11acd0c11633b440b1cccae5f8912347fe81fdf4ed176e444d0f5ca6fc09d14d424983c8c5ed8f30ced1345aee8c7e9619373ee9c6d740ee8dee8c8ef3b95a433c51da104e4e9a55ad6a363fce8a2998bc91936635ab363fce8aa90893936634b3f971564c4d3aa9cd8fb3e4946df323a3bb8dbbb3cb03b6dddd99b942619bacc0b0695185666b8f72d2ac6a1b07ea42227b8222234565e5851169064b0b0a668392372c3348a3175668d470a1115336d3055b51499181325dd7ffc4e55b9c651353275614eade6349d19f0ec46dde3723f5b3d5cf06a99f7f46ff5753365e40b01b6a37b01b56ae767382d26e644bcbf28d15186a6c289524387e899379d28f1c63d4989f3a617677f7373f7bae524a50944110f1c38846f96923c2613c08e9447c4118696b848341fc20c2060d9b52e86134b2967d0797fbc324627a08e67c9fee737e3f383361fba3d349635f4166313a3333c8ccfca204cb1fd282e5ff19ff59267ba60ad48dcfc9e77eb96a1ae447552d430c58a63fba02ced115e56aa16c0caa54ab457df5e20dca531126fe7fea7aaf6ebcc2869e6ac59b78aa2f57371187cad58def1d037ca66aa9b9d252551561e26ba9f85a6a0879c3dabadacb5255d3a029c132ac80653414708ef85a8a7330c1c6afaaf885eca3baf1e3fb122ce01308056c9a1fdf9960934c81427bd9e7a6a12f4f4f32106ef872639c41693be0b2e797d0bb2eace8b92e0ad663e1a7575e1e5e9e6b502765d7850d4b5e77b78343f9139f9ddbb1cb49beb0b9dcecbab0a3efa2c05282a5c4eb721928f3fcce8e43273d78c0974bfa170f868f63d8f0afbcf1b2bf77dac527f05f3eac57e5994f31a8a8905e3cd27bc7c3cbc3f0f275e5253f4b7d50bd2facbcf02bcf3528b4f785e77e017c18bec6733cc5f0345a1efc183c183c66987f8e6b90e7b5ee7bff792190fb7dd6edf838ee6ee8cb71cf832ff7b5ca0bbffee6715ed801d6e5b7f1dc01b9d523625e1b1f6d19bc672ff4d6f55e47fc3c1d0e7a9f0defd577791da47cf771d0050b1e7838c2868d85a31cf532e51b0b96e12b9fe5bfc717887579fe7470f03dbe2e2eefe2f970b055f1241986f4a04bfa1a5f58532e89f42d9f0cd96bbdbeec20295eeeba21d8b05ddaab3c6b39f00ffcf539f6bce3d14d2eb77d2c3df86ecf83eff6b55461ff6a1fb92fe42fa2eff6321ec88ddf3497f41ae93b791df1c5e3991b1ebfe0857e57bcd055bc146f0829a3692e3b78e260089ae12398e2271d35ca94cb5fc83f42b0edeaaeb061e35cd9aa28e50ff9b57ef2b91a587e1db13edbd048fcc5e399ea79ffaecceb4bdbf52f107603d9871dbbf45132583c31674fe313e427a36f3b1d805cfb2899cd44f124c61e50beb06fb01f0e07b3ffe1ebb20f81687d4a43317e30a761ee3e7afd41c041fa7d45ef5da5a1c681350e0f123785fcc3ca490981c5531023213a62ca107d3be5261e314c7c0aa236f6401f022ebfe9bc86158de21a646d1d6ddaf76019bfdc03f1c34df1b9c7813302b884cb3df389e3985cae724bb8a0e4b292cb49b880ba5c77f5fb657b61894a6fcab4d64ffe70a07a92e2207d4ec244c90d632ef7f363dc14da1a38933ff73d38e6513c4950e54431ee9d7b3bfacad9f3f73d3a7bee53dcf4c2d11dd468159ac5d3ab8c5e5216109b543e74940fe9e5d06594cb2b33b8d8b17882e19fc6733cdd78d2734c79186e781a871435bb1d241594969f2e3f293f7fc6288ad2701ecbb28e3513076716737066a4277dc79a899b687ccb13ebe0bfbc783453c80fc1cfbc3c47c353798e515ad8e531a60666710ebbe20c25cd888ffcc06d6b9f1c54cdcd5ee5391068ce96ecc9aa8e74c91df9923c12267b6a2b87d57532cb5454a4fcc0ec7b699ff6c93ed92476551ee56916bb1d19334a8b8b8c14f9328548afc88d7ff16a7ee6868eee4c9a547efef4824d28dff2e104c3e5c329853b4d291fce2bdc49a26122fd7c1b545050b7bfc5a526c54bddfe1b9e90ecb3a4a7e1c52ee95b725bfe843c04e2a54545652e40e551be22fab67c381c0455be170727084492819d1b102c2769e273394913d8e53a504bd93b644739994ad91ff6759119b7949f757056503db1f19c83524d6b4384b58f0f3791a2cb4d2e2e1e5f1b5e8a83ff32f8dadcf934beb092be58caf7127561cd60f6289e64b0bfc5ab0cf6bb782006fb657896c1fe142f85c1fe176fc430fd5c37d31862ce91fd9d272007ffe33bcf13eba090222f8f9a2e51d68e46ff20d8ddcf51af6e42394d5884992f5b5e3ea8bb1d35fe3f96e99f6f834d2e3f57acccca33afbc8b0cf233ef86f7e2c916afa565a70acd6ef88ec51c376557b67861cbd36f1bff3cd7fbbf5c68c23a36ba615d2ef91d932e99aa91f23b4a103038a508244abec0539209af91277094fc80d3d4e4824f121561e6d7809112e34e55162eadb9930691a86c3472d0fec00e3e0da988fd2facb70b415064b85c94ba28a594ba6cb88e406dd878fa2fc3fb859f4a96502fdaf022cbc1e949eafdf03f445f172e30b9f233e9dd80e1c379f9252a9e60f822ec1bddf8502f1f8ca76560f0fb088ba78c613e6619be9fb1effb220f8fbf5eec2d3038bf09f3340d1d6b19e64f266227b873fadca905ce313f8c5adc2989b8e9467a805149d42871a7a79ab8473eef4369c4fbef698a8a444994a49135b1a5b40e4ecfbe7fc8beffd8411befb2432a6233216efa9f9fa9e2e9e5479fb95cdec617d6f92f5e44e230f33f2fb61c9cef795107457637d02715b1a1e5f1c3c8c1f944f44df981f638088ba7f69121effcd9333dca664affbd88e3e0247dc82c0fe2359ed338385de66b5fcc71704a548471949497f111a6f2f3994e1acf92cd7f1a1cf7e1844d189d347e3e0d7232c3ce98f1cc336a7892c693350e4edac1e2e9eb84d1f824cac1f9e18485120546bfcf7b7f6cd8ece9b07e4b8bc797c5e33bc31ba2495e389a3ff242d00b5e385af186c8ee1032c50b536e3861370545a2eeb45f5bd9d914f664bd3263b9388f2347ba9febd515657555573cf18ccb555dae39e4ca8eac8833e4469c78ea72aa0ba7ba62abbaa2eb0a914aaaab63551715366c588dbd03ebf153c3e284c176dc945ddfe1895f75b9ba411d23df4cc6f81cbbe55f19bb9fda85cca93a3b353033ca86ccbadf63c6c713d7ec1bc91d2d5558d9378b4fa9bc33b27e742468858d2f4b978709620ec3d74f80aa1eb091b3ef306657826cd61c4355d2b734bec724ba4702d481ea779fc4f648689ba67df749708fc4c66ddb779f44e891e042dd73a00e34c57c695d7e7086cb49b600bb610d9cb7797d625f7adc9f9473ce29a79492a9d72b38e79717909bb4831d17dbf902f1ee23703ec4d3fc7e57f1cf0de517a5f6b2471c8c3837de708e185511a65b80bcf2930ebaea86b2c5803833bf7913615260ddc64d44dcb472138559a16a24295b7637fc8ddd4f27e97eb5ddf165741bf43bb716985c7f8903cc75e5ffe5442d290138e1681922670c720c83e35d45068e97a41e58f9fdfe5d72a941a385650669f4c28a4a8a0c94132b0a75206ed332156474faa0da70043759071fac38c2128841a79a81b8b099672cc38e85f6b200c7ecd231b2923592a607c642128744556cc98eebc68807f11a9f9e1c56dbf41026b19077ee0ca5fe6efee6ef16c5980d4b3cfa3adfe026684a1965e49a72f46cf29ff8702c6de41026b1d7ce919b988a4194f8b47288b88da398f0bc708eb4904efd00c13a3db6176edb8195cd1d43fd99141cc72f0e52806b90e3b8eca16651941d0f6666e61a98bb9999a315dc88b95300902b9f3b098e5f628cd96539f252b86bc92507834814571cf1aca98d83f3a65faf3af9c125504afc5cede512a9ab916e9fdcd62e4b7c79e76d9b08139d87a8444d56563d0b991a110000202000b314000030140a874302a150302215d5cd0714800c848c4a74541ec9d320885114648c31c818431000c41000818119c11407043406eded9961672cdbc2f55f085e63545d2677c4b4e5a3e9a743afef616495b0219b15ab2d20c50063a02b6b4d831545b1a2a6fc6a6e3dbb26d570b39b8d022faed469e2c2f949a9dad15042f8dd539423b18a12f23833acf499fc3fac2878e82a5088823e37c95bcac7c5b012887a512660a03cef89558e98f16a03cf1f8d37302f174133088b509f30bef2e038610b28efcb369affb1716e4974ccb50ea980a8e740567dcde06691be5757ffa41f27af3117684896fcdee53295a2222e7fe593cd3dc06442f45ebe1ea1728c37b7a10716c65e0501984e4ab0769aa4b81840be9b07ab4aa8bafd426bde0e743c1121254e2992f4f510c2a856a04002dc936955081dec5dca0f5f50383ce438edb031749f3314662df49bd8250481334aebda532401861044b66149fb3c3357d802357e14bb057f1168cb8c748fd41331f46e4bf182034fee7b78c6fdecae67bcac568385551da1cfdbfa8f439b3c0c50b4e0ab9737144ccbf6a8d124ab50305f3a008dd99d36f06b75c9d93e96dc44306058a5a15411069095ea599836fa31f26625885ce3ae62add7e85a3987504c3269c60ec2915373a84003af09ba4e7082b097ed7677669fc43354af58408a18096ca91f35adfe8b77e0001004d20d9a4753a58a6409b0c5b8a62334a551347081c74ce7e8fec120157ee755046e2bbe916ab91df40bdf47a7c2b4726b82d8283d8dc238301ac2d2b2e507aae19cc283134484aba03415a0441ff010ec56fe89e07e955e26abdec507acd1d31e88c80d90d686ad62caff3f0bd49d5e5d19c18076bee4d98e7ee29201ffbdacf5c6e99d938267961d5c56739c064ce5babc8c10663f5502b2691cb9586001aab51d913df7cf0dc74646a7edf5d412deda11e65c05d6d806185ef54fc32ae358adeeccc6fff642f5056e1774c58e0251ba52a8c8d1c874daeda0135534d199817fa6422252884cc489e863b25e38c9a8d343abf613d462deaffdaad07ca4a2aea204c5009ef94fb6324a916bcf469d1318c88788b653088a9c030c581ea2397ca2fb67ef2cc6d2ece8fcea8a5a700332a14ef4311232b8c0f19c1ac111f157fb9f7b053880f52cbbdb7426929a8fbebd3f80a4550559db31343d6b1b89ccb95868aeea10df8360f02b69492c608a866ef491940c9d8bb5a1205893758d26059a2b54f46e43ec62277cf72d0e5ac143588424c76413d63ef2e2b37bd60052f1346b083ac067fe294d8877c42e4e589780febfa4385268d2d3ae0d6e5d53ef1ed009815a3f0687a282135c0216d9a0435840118683e38b9d46b058f6bdde4c746e4c433b9a1c4a78dc952648f24939773c8bb8afa0ba2e9346d987d2738fc8e6a5c5663e1a004916e338c52a27b0e075ede1d6c3d4361b6643b6c2326180a2810f845681381e08dd7f67d99ab92b8b2100739f3afd40c13185350fe2fcd73e2521b152ab805a98076e3ec99d096d86dfe698dbf306612af032778b98a48137c1ac85b87326033d6d659ed3f5a046515dbb40c92e8565989c88402a77e10c8005a5f9d47560dfb821b34620175089fbb9ba1f09db1161cd0b935571160badb421a67728050a1448b5c563aac4b95f9ad29622989724cc27f71388a07bc2c3ee717232298810fb88ab36dcca94d580b62ddbe2464af22938933c4ba49ffb7867fd4b82358a32b4237b47002389f72d50561e0edcb2aafae1e9bd0e835aa9fcdb513eef959b42f84646d882122ec944d1f75e2aadf38b32dfbe453ca48023902d901eec5f39ed6f5fea7d9578855597b68bd109f2112b011aac5a32556e9938eee1b4a9dc7e47b0fbafcad0fe077a9da08a0ef56f3c73aa5f0cb2bd8efafdfdd49846ebd9620e9cd02ab1248d033e8e7eb95632c45384f584633bfd56ff83be35aba0b2e5d40391881e4a64b5c67e98a07bd6a25f92e69c52584aa72c11f86b3fd698232c5269256f91c4c99990893bca5bbd6a698238027a8319e68817cca9b2bfdc974143118800206fc91055c4033f08398b4a8e0793d6476d8bc163ade125cd74ec40f4d4e1856eb47e1054373883e3055aeb714ef0226a904620fd2832148b01f33fe047065c42e96ff14214ee85036acba0aa2f1490d2f94e3da8d20b0b505236d7042dd64dbb3890c6ca832af6395d54aec14331cfa84ba5168151bdf40b42c94a38282b41ed2786868211e8ccd244be4fc65ac440ab8aaf2c983ad2f511fd23e0814f1a46d2c9021d5e450fa982547bf84f2e3417c46766ae816e8eb6f100a2e761670a50e976d8b81df84f6adf87bc817e8cfcbcb58cf514cec07b27f98dace801bb694c6ac53b3503a0150c21ada1091ee98b7f10325a241dd20942a070fa5a85133281f7a0dec5ba3fd1aa7fac323b9b1624783d42862ccfbdc8ee3c18e7506c4521f313ba1a1a154242d363a42d321724dc62dc661f284385ae7aa85b70660bba0449cb7f2cd2e0a0d21a27a598a95053e9a1bdf9cec38214503bc347026d5c515391f95d27ef4460da500df7858fe9db6a5c13f50ae618cd655d40fa6068ee21c6b71b5cb9505e3d384dfc1c9b333e5770a62ac6f43a0d7d31e6193b1bb316ab1d509dfa3493fbc92b389c38344a8670e0fb9d6140ee021a052fa4185925835b8fbfc9d9cd6b9b4488e6b3a648032e74a7ca34896d61bce8ebbbb21ccc322df7b659fe21867c0c796c46e4e2c91f08b7a85c017f139dd20515a5de51b27351e08eb1eaeb4d7d670c62afbc3c95ee24531e57fadc6871a01b77febf1444d66050ae3203a2471b296a91df344d37759e4140d0e526d1c9546943d54c79b248390e0de13d361c221e60e2b8acf4399caea85f820c5522f6809ed4e98c105e89a1f582809e16a600a2a3aa179ac9d51cc31daea19a50563a08fbd14b56cc9e1702ba813a01e3dd5cd596cd529a5b386220049b640894109695744359e8871b42dddd21d75d0159f56e2469d7294c00993b7ce212d944877f3d987077580e5788fdce91e82afe0e48a998f3b9e648efeb4f20b0a1e4a071a38791ae4ea620983c8627cfb11f40d87cf2ac421d04afca866fbed1dd302748c453af3f92ec062d2a7fe7496921285af26d931df04aa586704a49d43817a857e44ea091e9c20445e46b4c2c4608564082df79ea00e49b1acca3d5d792893c379db0d8a8cddefbf57a7ff4b8c915ec45be261c1859f02ed960ce815a24ca0e32cba3db626420c7e9edd4f9819241be94ce6385f8658c62874e2619bbf7efd8b782ef1e94ce3c2ef84f429ce524489d15b9efce82fb8772e08acef51098a4fe4917baba3fddece788146e6f4f6eceed19dd6fae615f33a5558667a51c426d5bf358e754b1dd690ab52ea43c10b497ca9c3dc2f22e66978070aaecc9e21c310710b200d152936565f4b3e383716957b391e76a27c0eeee8587b9a64c7b23a73c678aa51367e43082bcca6d444b2b66ef33d040477d1f517b1664818e1de3e3673e6c5ba63b947271f4ed8399ba43fa78cb6beb2b150afbcf5629afa4232cc09045ab236a21a89fc1ecc5a95ac94c28f0ce8d5158bb7b964b62a9ebc93947f47cb6a862c4718db8d827c432fe9b0f7e46c70fd96e5090aabb5fef6949c599c7f320a10719b4613cc4ac33872e1bade10145f235ab4c90dec183fe9c4e0246ea8b60d4fb0f82cfc0028071a6c95c4b619203dc8c2b7907ce34f3421c0131ce7ab81699a7eb20d1e5862da882df14442772a4318c375d5012f5a11e7b68b5a06081b359ebb0917789ba3146cddcc608edc183cfd10bd5804fdb1394205b0e7b2e0add3a9f089e6a2829a6ea736becfb4717e5bab3c3a27b93f713ac72356ea25aac3fda5e3a43d4e8da4755ab17d250f1abae9764c3e01a6b115eb14437a6153c2a2b6130d8ddd40f982a89d59eee7b171c885dbad877cd4c8766b98c0b1d17de41c51296869f2119520ac80c4f4a19613a8683f017bd578773f7c24064704a935cd2633e7edc2820890f14f087002f03c091e1dd925d8de8622823974b2af2694f8e2b4772220e76c2bb2ccfb651f9e7d0345e4466162cdf60072fd04e5567c8fd40f91faf4b1bd00f08b85507f7597aea321b66c93bd895bdc8e22edce8dc473eeefc049a7734037a5c2f532a81997a1db7a00e5c6cdc5d03139c72f6936bcdcf3030e4c88ac2f348a2f080268f2dda1d0c6b9e8e223acd64a4bd8c737f8ddc3c391932d2099999f59a6091ee76f5d0a2a807cd1b70e47970e8d018b4f135da877aa6490b86a70ebd0ed012e19beaea67898401367369222c918b528b24d004ea0898a5a8e210dc9ab76a6cafa3dd7481f484412fcedad294cfae11b972c8fcb6e5743311d4c3031e2486e9b99756ab432efa4c0aea99d5eaf88bc3b204f7c7aa43988f4d2903b85369091e0969d2c9c6b6d9566c83bc02b7c0257c5ffd857b50c411a38a09f90209c668ec6c129ec4eb09294f5986e158ef05897089e8210b55574744fc6d7988076862707431a2ba274f67332673f312d03e838a23063abfdb3c122c7a392eaae6cf4c52c6779b023b8aa1735807581c00c3771b1d1ba49390d465d4c2b74fcfcb3b30567aa8b7d47a42562181f329c1b04ad123cee07ee7bc54376ce94b5463279960f829f0c05656b38f5319bce7ea440c1494b1ddb674986eccff0350b759794cd86d2a1db87f50ef5c1128bd09d6ab804e8d471393f3afdfce61321685a130bc0d903e2137abb01e2216d34ecce53a5b5647b24b1282df09cfa1d914504f82cc744f4c5c0d26db347d038ad610eeaf0332a1114e3d36c98922eeda6f7672a31a7eb8f8edb04b5c1544f555ee55631b199fe049c76fe40266a24b2c9b7bab33bf1b34944ec51d1543ea49e0ee038c824580526dcc4b08ac500dfcce75d85431b26c4e36539bfe5d64865d704cc99cf1c3631dbd7993f59e104354846143d0326ba2ac0ce993f3b409438ac9017e588676bba9f982717a0e77e842c53f691255a2ac12a9671afcf38282b397d6a9e6aeed4454936b98bb9ee68c896a2d89cb1b6ccb29dea1d4870810eaefbed44f5e739ed6d69cb96761e15ce23264143c11a6d5a2ea702eff80c54545b02475069cd6016c9b49635748b700acdc44b4a2704b354c96af1902f7b3da2c4748e1d498a14a8c24e432cb02422c2c0c698819d24792c171ccd8baca2b70e3f452ab8abac2d900ec83fc7b2f056aa993bddbb408dd0c34cd765d0aae89c2eb51721d145c87a2eba1f45a285c0f45d74b01dbf9d6b3b82e289daaca912564f681a1bba91f722752770a853da66c20876af994349becc6e97a166fd11bf3144049d98870d1c8a45ea44e6dea4ab9807909a84d87a43512287220030c0d4393d255791c144f7b318aec65f466c545188dbf41d5c633715439209692a9ec4969682e0b95508b04215e51595ba664e04cd8e35fb2f7443542bfe7bac503150699c7a7637a61b6092f8cfca165beba7496d202a995151d563da8e0aad65525cb96b1425dacd7672084ffc2a72cb4a120474d601d9f6255f7c5247cc00a3eb0faf68509fa80153e602d1fb22afbc3647cc22a9fb12af6a7497c14580d3e19e7a3dbf585c8dc9a89d0951f0a0891e101b0a7c0e773c7a756187c2b99c7ed083368e01e727af7a3d341c43fa5f5f94cd651b3a069b87de1e98799eafbc88a3229bd4a938cf98c588e3aadb8cf20f4cd09fc4f42ead66544c1ea7c8419a66f266c4af26648212e47cb440c07465c07218e43e1139689780e82980e8218960f9f3948621c0031c8a1fce4a398654d2c5f66cf276db915d95c43085dc60142b07c67d75822c2038de3217fb9750c2d41dce4eea1549e641511433d33bc231fb72617758a82ec6b35053deb5abfac69b710261513e97f61820bb5a7517f4863180125e67bf32e135541e60739d5daf3e2693dca0ef4ab694328703845550a0b667d82c49fa056a00644bd9396edf3e313b3963bc391a22a1815ddbacb28e01e86af3b47291373e555a0209bb396d843bae22b37a1e90a7789f7729a513c2c9b4d3110b4f7a1bf92519e32cb44619d58dbb2f806938009ae3af9bed6e38402fae0035f63c467f1f36b5ce2b2b8d37aeac54335acbf1f954614c957fcaafa59077c58f7efd50a94cff75343188e490e49b151c15d9f9531b7f391982ac45d4190026230d6ced54c44afe2b7f276512bf969990be5f4495cc17ba465d50749a08266a24d69d6e3bb7ab5b9b9bdf4ac6c4947c7eac6b7a8b194a1a11ef92ee65c05906824f765e00a91d0ff46f6d0e56ef31d290dfc2405e8378fcdd901a20723060fe0b7dccbd93e4d982e42ea724b385aa83537f409b95a379ea4c8a6b9361e0882ddd6dd61aa844ee6049e8f2f1a3c78aab632890830671d8b628cc0f1bd532a395885f44ed570f8956fbdc90c391e2e4598281fd47c6b533770de563968ef12010f001d6bfea28fddc41f48781888cd17f94655b94dc1ad7191449275928350c82fd7f81988f9e9c9f689fe6125bdfe858b8ca264f743d9947ea93a5403cfb461625c07a74186463734c33990c17fc9606d3400c39874187cb67232a4bcfe33d177c3951d803fbecb6062b2381f7d50261691e4bf30973da2299ab8c8a113cf80f85c0e5d1a32687d5445b40891154b37ecab18d1a5df67a76e9b21d14e746140245b23274cc0d4916cfe76a49b296902f238101acaa8120c96e02fc8e9c7c61e3a37f103830c40082e6ac58c3406de8477c42394c10db95c731454cb8b834cfa27b6d9258c95bb5ad14cbafee3246fb7fa33e923ecfd31aaaacff9b6b57f3e33f94f77b2a251d17805d19eb52d3470a36097aaaf482650ed88888b1b580f77edbedbc177affe0e6264e2f918983e46c758adf55348f6e342eaacc0bf9f0110aafe80f1e4b752df267578d157f8aa06c820c381634c53966a08ac33f1c43a38827c88cc347c29aed4e140efc21d2834e8d53d49c97b34efaff39e0c7a0ff4bc833c5ea267af50ea6b638168e47fb6844c302bddca395cf43f9fa460f8f969be427c68889ca63d7f085cf0e49d8cc00a52c01ed7cb4a5d2c00462a0ca8bc616c5af01a60c9e20a2240cc01cb60b6ae1a2a59199f302298431de04076a49a687834f304cd4ba520eec844318fdb22a91aef4900a3b5e68ca9c9518d3a3d3581b2068a563c7b6f1cbe525f7c4f55a909dc1b865db0020dd1bfa0020dadb57fee08c215a1880a63b328344f643d3e7cee862af51a501a8ab64a14e292ef24f68670fdb790fc3ae020e63965c1dcd42cdbc7e9be2b687dfdd5b3d2abf8b8c0118d7262aa1dce064cde835dee2c4cfc28343df22af75c6cc27655cbe7c6e46ade0a5c6a567aaa60a72f68563676e63ad9ea47b8f10983ab55fe34468f48022ff2f1cee306320424e3e9db16402d218ad97eb55797dc880b74b67b1c3c7715c9309e4bd4b13a1dc3ce3f8b58056c45f7efddcd00090681b57a14f70c446eba84a8d6876e202bcbfe152b6caee48f2922977ad616a29e176cb0df4209af4b3492be23eaa258f72d7d4d9f300063e419622272565e24b7c71ca9718a92fdf419dfa554fb643da6ac730c24b327e671ea400f6ce1c04f6d25d4914cc559e476ca0285f368d7b4e9bcda0690ef67a0baba5e0931e8fa70ca6dc2535f3c23e6b373587bc5e6683e9e835218a7efac062a99ebe123d698d542d0865b031a3bce8d1cbb8beb1c6b67768a86183adb1429e610b16ae631b55dfb2752eb5edd3147accd3c1cf363fd9c0e2ef2c149f7a92c60a79b9a042dc85386837a2e132c33b76742652ef2a0806a597b6dd72878d025b0a8685482b01910d5ddf83d1c41dda9a5a224c585da56adf36f78827000400bd3b89051a2169f7904f8cc9657a9547a640270682550c14642c2f16ab0308a4b8ff002a0b0fdf47ac4b2fadde43f584935567878b845028885db97f5e687a57329972a989cc1f8003a31dd5f590aa283558e2ccd9c52e8a18379443044285a65d86befed087131d82a328c49b7e835f2b9b99c73249f0ff3012bf91c7cb84b269f775059be46c528edbf9a4abd99227e82deb63cca54b112b2280eb95dc3ccfca1a05cea561132713b707704dc48811e62d6d0445e21d86c2d1b0244e0f47ad330d8da19498ecf4666879808456cc6a9839be2f5879117041df885da9835d2e8a06a36b2b7349866e6ef28a5bc398083dbd441ca76fcf6488a34200969bd5a73ba0d5d5cfa1b61023388f31ae9993283ee39b99f764a3b807cb41eb4222d2e17a72173038ae4508bccde1674d2f3e1d8efce301c723375fe1d2de96005cb470c08713aa8b419fb6b4417ea0f117b317f90bd3b26a5ccec22f4e2c1c48a734124e345b8590a756262f8c8bd2c00b24abbf626869a6691948288295e5e068b4e22a310fa163fbc96101b5370c040a4d89ab44e69ffcad926463c62d9c386f4deef6c71ceb016e779faaad7c4b31c50fd02f10d2f2c2e36a164b6f7d169758ac02116b3ee22e75ad409afe745b5d769a19e64aefeab0ec60bc1742a55245e33ae056dea7ca7f723935c8c080b50ca11ef8ce9a63cd9e04932749125a19f7094efbd7807ee76d4c4a176da52f10ffb881e832983e2b8613ae4c330adf3928e10881f8fc3d024806d863f45251622a948b9a0369fe445d075c50dcfc73fb4cbc85255dead047b645fd0b7422d08a93f145ae6fea5ff0238792fd4f5f0af189788c46477f8532531929528b89ceb0d95c57a08dcf915d0566913ac61ba30a3193d8206e119ea838e496efb63908c1e714254117fc7a1295c6c22e58b3a7718db101d8bae13287acca5dc130b1fabe5ee198c144693320f108cc67c73031352037ee836c9e402f8a0b887d7d83ec0261050691c350998b2ba8eca2185f4efc784b105225c5f0294aaae322b5beadf26d2f62130c9f48db8aa4d007c1a9eeca79fda43d2228e33d9aa4cd351be9681d90053534fb14797e4bf6e83de58373c829ae0e25b77f100d44ca14aea6ec0cb30224ca9622def1244629f609e4f8693cf0129d6fa79e85d9b199121cf44452c87cb26c099ed107bcc79d3875b873dddc309e145480e36a88f2eb6ff256bf823b28cfcc7616679652d5bc5d8df627485618774b9c5787963c5282472ae2270185477af513e20056e390a0fd9087ade41a36268ecfacb8ebb0a42c0eca9e486483e66275f42c12cbf4ef64617c3be5c57b4ff2c34c4bbf767809c79b850b2906e28ac5c69b0ddef10ec974c6ce693f03c3455df8289b53119f5e25a25e1c60aa40c24e2fe8c3b2f258b8b5fc02b077b031053c015697ec239fe71b5e0c53527d6f49b30224f21b0cd30fc480f9d1eb332847c1eee69156ade0e71ccbe1845447012538b5114b01b890511f65b8b54ada51825620cd166c8d0d6a953764ffe2c3fc6f063b01feb700aab4ad629af7dd1da503580f403254b44d18cd13193fe446138666d2f3f2d56da2275983914e42a815b32d65cd339ecc7e991301ecc24edf821e2b34ce6d9059c6384380ec54ef388225507a82de2dca0f7b76872fdf144d08722f70cc40e3dce24a4baef1ec55bda29b054e68d7dec5888f2ecb69ed7c76c85244a2e812b8b5b577c09308ef84f6ddeaeae3de210054db6b9933d99961d2b0f8573292a92e771abba36209bc6623acf224062b8997ca75c7fd75d9232047ce2ef56cdcf30742bea482c98912324f3e11d84a653fa88514a7aa2c18ab29f4f5faa7b07336d273518f02601ace2fb2c4672b0d5db343d55ee31eed006b78ce746e72ff76b82fd4f2ede743af6cacef6fc0fe5b12f68e946567e8e069b015c34537eea9f83209527742da4318ad97dd067598c0ba72f0dbfef1cf8cec90a7c6768f9f314f1ee82d7705d3a0f11b41ec90b454029cc52b327023347f1e124007908ed1ae852395e0e86dbbc6f8752e1f6ebd087ac9f0b9ab65852a5907ed890bc62b928f308b53c424ecf1994427f4f149ae83195f0c034f0b86c4fdc9cc6491ed27bea761b7a128272b4f2e0d34913a24074becc6276ef5cde841488f682a2433a483012854a43a296453ef2cb9d1f0249eb88931228ee367a1af1854df937dc44d85a790f884da557fb247d0c7118d839ca250469ee66de5730abbac480e2b3d4f60d6ab3b450d050c702bd39df38c5b494e71db9f15e86e653cce548a79241d8a9d69eda6e02e4d83e8330cd208ee7dc20fd1618ae6ec76fa56c6d15e0c0314aae362ac1617ce6c890dbd5b0227ace4e5f0103875124b5ab002ee25823e9eeb307474403ebd42b60e01989a4565a33c0333b3c8ff62f96041caac0c567549099b5068854d251fc44d342e9da41c5047f742c3d5e315fc5e95ec502afbb811ea3318561d2074a6de5605089da17781460170afbe8a390dffe56855813f1587e8e7266b6d0269ce6f8ed4d490e143328d0a8f220a8861a415d2280622921946500316cdf8cbf35872dd37c1999dcf8ab634cfb396c3cae9783f79a6d1bcd013b700e980f785c3bb0eec51ce0ba0d48c21b5243d991005ca6bf4a48db60f1db299d03f780f0c0393b90b87f886117808d149a5b8d2f2788c890d328b503340706fbc04a53973c661bf9fe8df6f43d341f769b2efb01dc6c7442db6876c0241fa0442284c73693efefa31a1733f9199a094d7dfce5a5d14f9aff56d89922fc2d3fea72d6b9bf35ac8313f408885a80361ecffd20c7ef554efb796613d8265058e4f108b570e926f96dbf7d6d690e0d7460d037a2d88ca4dc4625a8e06c8eef49902b102d09fe1b3eb4a441811bb25b2094059719017c4c4027caa03e02ae3b6abb0205c611e72acc19b990d967b0a04d31109084bb8cc9ca9fb9d1992d04713bf0df81e7fad12f3f26091a642da78c86eb29d07f053811e07be51f139b3f88f0124d0a4ca846c449d59cff323aea622f2ef354743ad8a38f7305458e7f7181072c34f6720f33230a650d2d4beb29dc589208bef31322f3b07ebe12d22ea0107ea2bd78ccd0ecf4a9e5f8b8071dc3a3cd00527ef520491baffdbc0c868628d9692f59266ad189c40a488bb871801c5ac8a0ea341f4f0279df3bf03ffaa26e2ea516965d9827af798e1493340c86cc424d39893b970bc4fe822a6a1a5892e32018b1b6180c00bf7efe062361fcdde47a854ada3c9a90be19cbf605db185b8d33bfd0db47fbef971b936a8b2fd64fb526a2742381347e996a300bb8789fa6780d993297618937fa73a86a5d248f4a913357fbdeb2540aaa48e8958a8fe5473f01c90a99cedad3cb152117311d9f430b41d09d4df9854cb93b790eed3adeff3367c2d4cdfed6ec78ccb8027a09ff287ebeddfee34c6e2a5020ad58f4c317c95d2077d34b834defb3addd0c240ba2396001d126f51ada88b0c8d763cb2f3c3c2b4dbec4d5066ba8c788a028c772a48cc30fd76a8f6acea4fe684a8e38a05060cc1431e51fc9652972d6d6a0f0aca281a7d784ec81e95d3743a6f0bdbd068c11a9fad21205b4094bd541df29d3d0bdb75e1c51100cdc3180c5bcd0a6b5f42754f70298d3d7abe5e3e927e556a812ddab86941add5444ef3e98ec046349d391b690714f0c8b9888ad7095293236ab07f264be3845a68bc84c0cf52815ebdad7a74d4a6c3d000a6f8623f677bd724c1ed828a8994a106670bc8353bb45c71d053dfc8e521f554df76a1602d5065ec5401f19708afbd838015c6b037da48c8cc31139b5c689b26722210d29db79651bb3418d3ad4d0c5e23c8e8042313628ae15d2523b54740950424150db2722962f19f9cc8447d304fa9813a3a410024744f8e4a389c5d7548bd2af5b3ad045efaa59c1875bb13aef87d6e87a5c126c850e411d6e89e1521afc184df93c533998c5ddce1337c802a8824ef9dc2ff20b83c2f21bf350e0d6dd8b4124eb5c9ebb1c62cac843d04a21408cf97dcfb12d004bf63031e8c89363fa28a4981bba9fc3db2557d611df56085cebe6762a32364ec81ee1b1e9db234076b5c556e4f2666e2681f7848d0778bc8b90f9b9fab49354373ced78241a44ca3fe6247f88c3b19587434370d985bb91ecbc73f4eb0aa10052c945ae471e3a47d9a71cd43a58f250f781f0554a09f501779cc264d17a8c19bd829fd3e324bdaa23419a29c648f0c71185d0e5cdfa319e652922826269a18053feb514d061f60e8a43ffeee0923e79a080a2664f20b12e4341afabca931b1e90c0ca6a743c8b282a6afef2dda4429e9de3a833772a68398dbf26e1102ac611e5516084526c6292ab2aa7ede942bfeeaf336b1a0c6369d29d7fa274eeb15bf848c1993ab481e7c0ce3e7f814401286644dae84a65170a425de57713301b42374dedb63abadd3609c9f5b0004a58303175acd150a8f5a1e522e7646de2b8736cadee0ee86cb192ee558e6873ad0b8f30c05db6b05800caf9b126c914a74177350de2eaaa295362d9a6691f28c4438bff691c49df3c0d89ce0b321f23a280f7e9c6941e9903ad1c4649c362bbc9943bcacdb440f2a81393c6b1318196df5a20dcfe69d069165afa652074348352a2118b7a84ebc083fe5ca7113730036879cba6a92283f01b94c9af1831df12269e637a5391121f39d4ebd3de91acd1bc0fc6a870281c4fd4fe2174890b4c8cb4e6d4ab365f5512eeda3873e9005f95f959812d6c1c1a75dd5aa88c4144e3b0923b9ff29cd48e5895ea886d027f2fc3a11bc23f67fe6d32f61caa40530680c4e2e37f5256b58222595dca0e9be778dcead2feb1ca04dad5356fec78bd28b86c17c969d362858ead55920967542ebb1ddcf240ab43d26180b0e4efcd9bbf772a83df0cfb8499161f2128e978de819c1d91371605a6506a4e181e069e441397c699eecc93da51dd1ab6c9177d3961cd4107f07f651138e87e043a27240c87531976edb63452b81dd938a8acaecefe243e3f36e4085a554d53e57e173a56a22c9bcb7c0b9bb567b587a48cf2fe226a01e7e0e5ce891c5ad5a1ff523d993eb9cd3c95d6b830a91df04c68e9f4bcf52cf81f5b311199cecd79518eea2d06de4d8fe29e93830209e28dcaa98adaabcb190e226cf65b09e05de57be350ed4455df41773e479e01189bd19b378aef19ddf0eec47827b7a9795b853b905c83ba662840f2a16ac62e95b617ea6590cd3624da14858016d67527fb63bb4fa535aa6e075d1a41843fdf71528657a24ae98a917cea2659f1785067f933c2514856591298cc3ba3f48ff27509fc0ace38940fa7317e90c89eed9c3448f95813d45e6b4fab87d45ce92fa7772964ec411aa5282d24350d52db51054c9fdd5386262c646e09228db9c30f62229a6032f5f8d8d97bc0c787ba845fc8fe6157a47c1b3e205b969b3e57f517e4dcadba64d36e3f6be507eb70bd800055dcbab57b10f1386b41b43db52024961ad9cbdb684403db851872386a252078cd2e24573e360fd3baab65731eee0e581ae7351d9c5116ad5d7665cead4a93348c23efde181ece76827b0a39ea620f1481e7cf8d1dc0f6ac0597d20bc23b168e4b391413bb146a34cc49315cec649598f09533b257dfac60e5ecc236210b103471c6e4815baeb93f8bf2b8a0fb80cbc5eb1a362d215ff48baa1c9487c589bb501cd511929fbe1c9c0c6e2b73fe79956233ad3da3ca0627c56a9d92305644017f0d8fc1b31382aae58a57c016ba01a1e38cf2c4503a6b2e942705a8ea397e4991b400f45065bccda6485b1c9c43f15436fbfa12fde40c80fcae03369b935e3392a6a6eb99068f46b60cef8a253ac213f9c081ea8e1ed120710fc2a065471ad58a53c5cf63c80de0ce66e39af5ba3e3b8f41742e083ab5109e9032021cfc60a1d784f0f4960e02c4a0395dba75a4d45509cc30e93fb78e522340b8675de7cdf9e3283a79081696b9a9661219f0c6fa4d291084c60889bb0ce224fd59dbc2abf9ae926b5c2472284652116233ffa0b2a0d0f80caae9b957db145a1be45a4be9fd1b6d4421d4143a1bd262c809e3a6a484fce97d63cd3c968e9edfa3c49df2813e7a52002be118d2c53a08f2018522085d158d7f9c585631d5a6fd60d0249664b058e0e393a980adbd06a625e05188473ab32498423ba8b07777b61dae512be9465f6b635d863b968d115eebf45b2447399f401598ec555d2960b16feff0aab494805c980064e6f7c22eb3346c8d641738ba0f5f6b280ef38b1be910c59421d167584d952ebe5d4d4827663668386d6f2022ca7a6311e9028e3dfa0877380036e0ee76b29e265aca2c8197a0d2980c9fd2735f0a84cab17f320775fa35e06aeb066ac9534a7e75ec5ecbd0fec1b6ef22f0b39b851b7bd92bdc61259e67b39598e5b5a0edcefc4a90ae2d1391b574e5ffd26ccb6cd86926ad84c82e6cbf3a620467dd993149f06056ea2595c0e37bda73dfa9d7066f9164bd06f41bc1cdcf917d9b72bc4945a707d216204e70bcb75ebaa2064edee54c36a28bd61aec02fbe6444e11061da6062b9a94ceb24f6fc5adbb7748116ad86c732dbfe046a507394d80c82aefd037b92dc01706ede09e1f8524c217335798c2e11d6072aa8e21d1d5e8668c52c6cfc920f38cd98da6c3a8f1293e762170d6829daa05c30b624f0ddd3cbbfc08bbcaa54f9bcd88c51fded6e38f48460c9d311c7854a24cfbc96b718b5d92461f658a7cdf0e8d6da11a0e3bd9462a9b2a375736cbfb3aa55476428727c154d88705e3e07f3750e0a9a84d97ea61388ce6808c2179305c72657a285b206c77b15abe6174c9e52860c42efa94db13c6517e63bda2f245957d98aae8f55f75c0842ada47a9ac35005de343134472f1d9cf03cbe496db3ab23e9852e2b62ce13ebeeacd830428457cb6151eb58920a7e2f5b762d82fb17f842953ba26180015b005158f18dd4e5668da762f37a7c319a32dab24efbf1a4132a40edc5ec221548d0908332253d22410df5bb66f2fc5dc7638c18227e787f05e34063e90d1b6f5b652a522de042a98b2cfe776f9df05c8a6100ee4be76273df0d288c1375375960ddd9e74da6ddddea9acdffaa2dca88ac475b483f1df152510b9e5e8d282423d4c96a1fab17a75df0d80f45b616683a547f315d159867e4e956c2e54ed1f4213bfb135a6eccd6303947f12582a5e04ffe1552137d642e0cbc4026457aadb9796bae02153ac11c7d0894ce3a73bb3521c2b2e8e576e6418eb7b231656a2ae3f39b869e4ee1d625d3594607f875cc498e52c9997b712bcc5a24d08dc6b16472a3b04ee38c4225fcc9604d7824fca462c248d0cd4d8402c14c582c3604035f33c18ad40594ae98e08046ba0b5aff2c062854c3be0ea7cc7d922c49baecc4ff903dceeb0d244dd031b8bbd6784037fa4d4d031f636eaa07492389506cb916c419e4036704d792fbfa415774c6078cdaa71ba51eaa8b55060eeca590fe7895b333eebd1ce1362c643e0f1818b1f0b2517068f150cbf19829b9f703444c5483f1aec9d5ce108baca591510be3ab3f79b1f05eed842121eb6bd06d0b6051661b299e6a7837cd22788904c87d409e893dac5de06d23cc6b013a3740505a70d558925a11c2f59a39ee615637984b7c13e1f7ec31b098767391fd1158a1945206f7ca76da0132e31e5338bb318e8be548325a19aa17960d32ffc02e3575ce87a22a8d98f0a548e2ca9139925f945ca01029e16d7fa5e6bee4787400ea4059925a2920c428c18025cf879a93824c69409a425929619584d2fa0c65e44859fb6552b2caf8ff864f20ac9175a4bf06c6f3cc9efb2d4679f3a479de955990d726104c648605833e3048a203ada80ce96b144fc63b6890c833e077822b5f4b9da4f29f25a27b07745d9c4464390fb0a7f3e24a678e089bc20ff12005079879e8a412645b55a5b0e06581a0a1b194b4f9599960e4ac642f15ad43cd2f2ddf4edecbffbae0b0392f14e1eb4e4be5a1cf73ef88480413e15be62fabb22aad8ab5de45858d9b78b5dcda1bae2b9e6e02bc3d944b1ce4cbf800a083e24dbfdeb6893c6db7b0f43ead8e016506b0ff938420d3f025b3404631753ef8f7785aca97182d9e3c8120372c4e022b8b10388e59f8eed5bc4eb1ae660abd61bdba4c97e53a640f8d8468b60856105c9b12a75ab26244a24503a1b47c41d56d901428bede51dc3559eacc01a3a069bf905c233a15f4867308092c5b253d32b68f62b7da215fa0fb0cff44409bb002b79702da9a7940b79cdf817c73170a959bc286238a23850e0d53774e4945484b3abdad9c9f1390a8d548e393bff4d1e12fbae63bcec088b96888144a38f219540b03b30153d5ac9709ce08db48cb0528f1de879a9dcd0977acb6dffada844f8aedea104526957daafd0b4a15ab4e13b5a2dd51fd06cd87fb0c22709ec3f44993304bab543f364dc571cef177f2ecebd709bc1c725133e584c8f85514f70b2356eaab89782bfd81b8fd8285d0da63b197495c45ec637ada7aedb28eb1d98de6eebd5afdedac4eebb0cdc6c8bfedd34e95832d6c3d27a235a65411a7fec3b175e0e045d835559880968b3e19ce485b0163b9686129ed4ab1072beef84ed332eace744942112831dc214dd565e027312196921050b17f872a703101672b981680e6e96e865841c05b5c9e3f267da68c8b3a5e72f93423fc35d81807755c7ed76b4c18705b78e7bd36bef93e58132eca1ce0c99d3bfaf7790c170db63c077871e39f2a7a0afa20331927ca31f24511b3364682a5aa126e3498ba9049dfaf166ecc30447b5310e09023fcbf9efb3722e4e8b413117ef530513eaae00a1ba96d80fb5540b0e2515873ec7a51c2eb81b3d6c22eb32247082036ac905315bc2a12b81410e6ec4e8d1c7406d0b905fdc24f741f512029bc8dde3061575c395c0c4fcd739c47e0ca7aa8e604d422c5af2963145e46337075590cfda9b6c00a2b98abbf7a7faa504183fea80a0ba43da26c1b66bfe3bd96a889817f472421bd7db85ff8f0d3f44d01cd005178a73823a5163cc6c4e8e39d504859d493c9f418125671b25302c76635112a8a5fa9a46e80b63124374b2d30a0a0335a9927e12bdbd098815b7aee0dfd04d56f4ca9f85ae1e4b9643b02ebd5546377698e5974c4478d8bd900fb766a8247dbc859f304ab978942a4f36524924d243b2fa9493eb5ef7818a477a0a7646df242f7b6158056e8d100cebd42e4cc9622005dda609cc29dc94aca96867859bedfdb76514bacd5625738d5f9cf13f5301fe66229dc2ffa22a02b800590eb1c7671749615c96f2c28514f24b78081a11657c791345a160e2b87f0f32af7104a70279763b1dc5ad8390380c2a0f5b0cb5351ca4cf577c73611bc32ba0e1d7d16f8b1cd457964c5c02badbde8400423dd4464473519da425bf27154bf4054ba7331b7c4a790e2ea90e85540b9b24cc2c2d1a3873433170573d46ce4373de099550e5851d7610fbb2cd16be886f7c258529e005f48cca9c497cfde7a788a99fd19c612154ab9943e4a6c9a6a2cffc4a25528b9bd6c78add660c70c043bff1851bb6d96fe21784b5bcb536939cf2ed3936df74d896a5a932fb1983e6626851545720c4a947e6c4efec3904eeb871a7dd82e2e97e979a29ff0ca793c067f6fa3d1a2e9e4fb92082ce694cb02c429e463301b64584e631fa17012be34f991564acefb19abfb6e88ae5377a572e4cb8ff7db4b2d0d84c90173e183f846bf8a427e539d56c1753e184cdad5da72e75e30d37617bc80fad890322a534d6a57b5d97ebfb0d517f0a56a9be18ee32e1cf21329cb513040bd5f7ec04e554ef26106ac2fe0dcfffd8150212d55becc089d28b6fa7e5ed3575568aa4be8656fc91bf3d74d0c907011970d5a1b5bb7b0d98236fbcced3ae38edadce2c833e1edfa04a6b1a5486d49380e6fc1a86e44935a971e3f51ac63a86fffd1f90666a69dcf7eb5f630bb78513f32c8937c74d58f593f20b0cda9bd538eeb70c2f6ba14a8a805352c493c300eb0ad4d7acccac382fa0652fc77820f294e1d1bebd97fb79d45f34fb8ec9b0d4bfdddc36a08f7146fdf8425b9b543093718db31ee7635fc4220ac939cdf781621bbee2554ab7eb2a802679cfe22cdfd3ffd85a2a427422e42930ba9ddb1b588fb989efb182a3876df1661a408a84f848fd561efd0129aeddd17856e674a5a2642206e27658653b43b27000814484869ad7acc3d6bc33bcb42ca53d2cd9a528c52f5a10aea254d90aa4eaaa8ab2004322b3a9250365e6101d64855e9d736d887a3dbe9f4ddde0195f5d63557cfc78a2330b4cb4231bb56ce44e236a47f693dd054c2bf36c4631e192120c24ef37948938a64863fd4324699a7553fd765a5fd31e0212106ddf3f5475230bd21b842762fbdb96d0159477e42cf4a6cfd759a374ca7e93fdf74698bbe266f46f47974d3404380044355064b4e859564e634b5300d8b70cfb3b8150b41fed501a6d5773910edb59fdf19cbd0229682cdb9a71ebaffdee7cfdc3255d4b374c348bec202afdc0239e77af85726ea76475c2b1745a50289010f459af825b0e739df0ba241345c2827166226abf9ef2b6dad2f27c47e2a46839a55b9b73791538609949fe7c14166c93d416a126c1026e5249a5ddbb9089362e7f6d965a349f655a264bde3f1c79ef6946ca36cbd7ce3cd739b39c84374beb3d3c005cc491571d2289c93603437600b2e18a6a7cce67db8a24b7560a05244dfe3f44f450f2f897f776081e493bed5c188994944a800ae436f725450cc099e294924a489a0c61071759b3a3b2fe422e2cd74b45d3581dc7cd7fdeb5e229eb74579a234b896449593fb2c8dcd9160e498368d0b810183272d2980a610c6c8b758f983a2c01a3e5508010b39f02b4bbc5f14a8b70035cffd7978377f4145b20b85f262742d047fdf6adba13e62d56135d652aa764c595809f9c54411ff6f5355802893d5f0592a21497687a2720dd69468a81f2413d4de71547017226f99c403961b60c4b6054547fda8982e9027ff49a2d0312e875c2dcfa53d41fca604cea230c17308559e81cc8e99c294200b56789c4dd08130fb8489a391e047f66692231b1237aaae16101278f8f87ec4058bd5df7c8dd4c9556cb56f9185ed3a7492d138cc5b4b1a7d4477be304a8ac7c3045e9ab8c09d478a1108fcb953aa209f8212b6d33968422ea6ff03408a68e0520f0e59b8f86016c94df4c97e0d067213c2b90e8b15ffec62f6b0d49f53a588478aa82e3a670825f1614426d80f72b4507b2e5410a7a309787c39460b195f9a91a0c8bb9445d48f0c827ba0624681df8bc14dda459937bd4a2e373ff27840f09d84ad7b08a78af5ca66345ffc268e01fc951e29bd3aa9c21a9bb7fef23c8f0affaa467475ceaf2831082a267157137cffbb1f6806a391b05489fe4825eef04e1fbad24f213b83f3c531158745a714eae6c20e3369aad620aaa0a88ec96e88a50046b969d09277b923988fa4458a4d326dd628113ac818fc02ef581fb07440b57f898aae5ea8e7c04240e8b83ac3808fa38fafdc9107822d579167604e3e83154ac2ef9f1ab741dcd91e2d026ec74d4932b69f7daf4d9b84762cfcb5274769cc42785d829c4425834af50fa241eb3c530ccdbe13337e0001ce46ebbcc3e1fab620e1692bd917bf102ac553b9bee40a67db008ff8ab32a87beec0738a176d8012a90a3ca6ca019452c6f84aba30efad4c38eeef5b5a2dcc6e8020e371b8c4461575645d9d0088138b88e7636e6bded09d395ea6f01c67b766b9303607e76668fba11288edbb64464bba9f1b976e701cfcd42bdd648057f0dcaea07c5e5b7058c90556467d6f154302cc3debc190387eaf02f1c9d198f582d5bf7272232423c55b5844387c0ce44e6a14438204092bc8143f7ed477fe16349b54dec254c2337113177f3aff5a0a9330adaf4b1df4df7e063b1efcc293fdeab8d267f6e08afbe94a32dc5983592cc7263ecdc39ff9a772d5daf802bc3f6e6108cb244c5d7048eafa81944a84bf53ca5d80aa3c868dc5280914ed5d7cee13fe9b218aaac06deca61437516d16aafbc5960524e4306e3c63ebb2da76da381b1045ef48aa0f8ce85c9ecdf8dc5c762f53d03fce05bd4cd57c6a07fd76ab8a3c0cb222f4a37c41a43978f72c3e520404325c90e37d49d0010e0dafab3a2203a94ee56013d5d3e26e9c0bf10a72ed3e662b78e52d2b26e16f83316e3d4e3b7a8a246c0d65c1063a6e5455feaa9ed558c787fb877f9f8028875255f1b61faf1aa9a2368a1a30b265438c1fe4f94486173e8bdee9203f3a1145a6e8918b2ca75f73bf94db4fb7044adf74b354ca0cffc2dd0400f71b2ca687e4777245304ab80ee2eeb073b8ee100a69fcd0c0e14b107c1180bba3fdb6972365c058fd9d4d6ccd69623368407d4278b6102866bce8ab1dc91dcb885629349c1c3c69ddb1742ba15d77ad2b51a1de5f564c8b84a1a4d12b1f5725f05e010aa222d792258c92a4cf9c4fa077804b61ee7cb82824d2de91f8cd7fa35f3f3073a0f165967002fec21fe72370a271f6854634b220610fb17c4808258ae24c438216aa6dc3a9a87cdb07f29edc443cfac95d220749363b8c7d3cae7e86a1b622fc7ece8f546d1e9fa30095b48de9b9ffd09bd8913e76d7233def6a859de0982ef28b487a68e3d62b2ad05ec336962d90847b612f94cfb938495b3ca8d89b575d5b4a35a7349bd6d1ab26a162281f1480cdfbdefe07d636b7c6ac6f6f4fa8cc3f5ce111680637ed5f3d16338012d183cab5ef85437f40d1ff5eb7be8433587e1a710940b86dd19a5808840870460bed53266adaf48d112967eefb72c0640bf74f61baca8e0ff5c70539d52ef29e40e15ecf9793314103080bf18b7a6f6fbdc22689ed58d52b4226fdfcdc222f9efad3ceb381c24cce8b292b5e7811974dafc2c2beb05c81202d2252b7b70bd6ad061d3149dc9d7327e318aa2a8cbae8a63613293d16d5ab62965975a500683174ff33115739f0b135797906d8384b71c07a041eee92894dc21f2b8230412112d263411bf5f1aaaa1f47bb875df650c6bea08af4fba42ec97945d2dc118b896cd2d3ff6cd881c392d31adb4e873e6d988b2772d7f446bc9ad3f7dcb79d5eb21f4a034e11566dd1ae39838890bf786f32d8c518182a8d353e1d014f1149def4fafff4733850df5dcd87020beae4f8b5c92504259f6ce552516dd10fe60b137308ecc8d44680a04ec9b01848129302322d9cd23e7e656cb31eaa3742dcca25c9d71250636e589ff9b278ea22792da2ec92c449a035f389c909e21beb2c4c835ad656b3bbd339eff6601485fceba7a44e1ab3acb29029cf9d5e901db656265af9c83240a1d4b03bc18a43d565bf1b672c807929a599e26759ed806ecd28310afcf01633d35668cf7f300fcf6494d220e9200fd95fc0a8487405e59b80fcad19ec37fc98997cefcfe9a295b0f8453d46cbb7dfb4e725e663ade9927838179a9e517f46f5ff52cc45108063430c8742da13175ae10dcc48309433441d6724ce66392e98640a7d4334ebc0a34df7232679c5458be06904c59d4f4c455194000ae36f428d18f2fe740f0ae65117312190b7ec7de98feff874e5aeb741baf59fd4e03749137234ad993d5abb39030ce4289bedb5f0e575a1b185efd56a170e54d88d3d4528aabfdee628995e2c98d6e890d1303c4f70245cdbd4058bd341675aeddbd376424e3b562b62af62750945a0a51a74ec7a39e5c4af0e126f304a93c166c4803f04bfa858091589f817453dc496d84294346bee7b8f20f0fe6052dd401b157db1cfb8031d3e6053197fa98e77a0c7a83a7282e8da149fa002e43d6f735e62b70f925ffe56894bc27e813915ca1e9f5fffa7a6f001874c0e2731d50868b42dbc544e0834015cb4db968a428b047de33947bd7456978a5d0eb5ee72c35c31c1e7d834098b4a0d299351332a09a3abb5e9a332726772e01b2a157f4c990e664e13168a235df7957215d278d18d4aae9626d032e065d9b69945279e89583b3fc4a7aeee4c515c16c6f33bcfc27e541890e60e7e4a07864ee4459c153e5e0ee521b8922429b6ee318659a6822d6c15ab93647ef6528b2d39426e69192809ed4990596f4543d683d060ff6bafc5943566b58e588b49e9f997180b4f34d98fab8157cc56b6b5d602466d03a1d20174df8652c18dde6e23f8b5f85eaa6232e1456770d822f48a154b9245e46af4c135877f0a308806b92ce8318d43d846fb112dace76a4fce1af98082478ee2f37fa7e8fd5d2979323307fab7f73e362d6bbc38079313f61aa29f95b136d290619b2f838264d1a9c7f2a37252466031a36f9564dc353ebd6ad0e02cf2904bf649ba4458e0b8c7665e318a40e37f6bfaa4c2ba4e97a3dec6c6a63f9e2dea3e2fe6ffef885899fa48d5ded5880316b5c680011a19f0905e848d3c1a2f7cb580e53e0ef76ca998c392474f9b884cfe06a575a5b0b82b41fb23e05aa6bf7ecfa90ddfb1e386da4516a2c492558a1e500e7d3273c2f89cd9c580bae340168fc3d2bb0db0421cdf430fd747b7af7a28a896519e96f83009d664191987122205e226f4f01e8fb4791d02826ebbaf8d7d52034e5c06f2b44636bcfa2ce1ebca0ecb0958b1f873a7ab4fb16b03315e5409303e02c0159f09b9992e2e9eb49f92cfb9bd69624f4d7242ca9a55445236704507d2488056d0baffb48e02251460f8f3fe9310eb1ee449a0fb88f0acbea44957e20984f0db5b3196a14753188972884932dbd0d9ca818d0d5cd97c49589a3866bebb1922cc3432b2e0ef38700a55ca270c6a7faa568a62cb3a86fbc24c435368cc7fe03ce09f246c6c3404fdff5b63ff1e684ff8fa25da28ea6492dd0f40361aef671df2b1cbfab9a5c88ee81d864b9da0536cb54796d57cfa19c3a2c97f87bc1548874137fe17f02f4ed82eec2aa30be330157ca52494ae9f842fcc0b8bdac94899071593ed2ef63790805465c285a80a371439a4d1cf5ae5f7b6ffd3f195dc7c1320f0838a501f9d24f0d3b6eeed28b28b49b50597290f6f9becff35d0815882a98bcfedb688ec02029a60bc56f1fbedc0e53df529a85f85b96a8a511f1f0fd58fa9b5ca771056b7608dec0b73866169cb07ae1961b325fbda6ec6c53a0a611f7adf3fc77f0d6664b1c346919437f59c59186b6be84ec9880c66e4425f8edead9e10a2cc61f10fe042b2ecae62de33e2d9169cd74700e3664a8c09e3e73a841de9cc40853f8b4a63c5c76035708c508ab1a472188f1fe9b0eca79763290e081af4d9183fd10825f80a3bb64f264fbef105ad356edc13107ca3338302e33de192e514420102bea1f98a23014783e6cca524409065a104cf1a7c1b4c356322aea69263fe751967273968758815821c9f00da61abd3afd100a44a147cd976a4f75eeb16b0b18ffe230424d92c00151babca5807f1b5db812e049bcbeb99b9f9e6d1ac44655b2aab55b26b059e68aefa4854e75e74e6c2144199aca45243824a4e79e570c1d96ae6385c5bcf99e17d8b8767b1dbb47d100651d282cd8433e6f4c318496ceebbd43da27ad4df30ecb619b08341a579637a003a4f8a375eec4fa00dba6356365306356c2bc2bcf3187be9c2fa9bfb3d1971d4ee0f06d234e9855150623a868268cd25f1ce12321aba050388818891faacccc47d338222e87c34180adf0ae3f870b54fff0f07d3159540d800b119ea2d35ddfbf22c1f4cad28cf3a361cfa035e108248cfaea9bbe387be5ce7f5f6d371b47491300a1adabdb3666e613e8a9938238d61fbfd3f18b13b8e9904c50cde4f2db8e2a811a214026acb34b28596377a811b79f48f610b77410f341e654dae3da2865d0bb46a56967e309a30573f9bc0ce3fa727fabf04f407cf4cb11baf5d6fb1d230ab40e1536d3583257d9c0234db6d75d0fbd1576d6d3e0ef8e190346b0d8951a91fd444a60cd5c02ee090c98a9632d08120cf443a62ef7147d4023cababfc69c28d9cbda0d4925eadffb28c2a066c1832919c1189fd72ee8192387a855e5b72eca4e1b190403b1c87893204a7d302d54bc81e66cf7171dd116ecb181d540c2eec0a0fc6c2c222dd804c0a70c4b0656db3d5d114c499081158d28aa52c9eae7cc560c22796dcff491afaa0b80a501e928b6cdb19e0886db36f682bcc2e6135710732a9ad8138c9975fa70d84e743a6be3fe2bbd46f9cd550c11257444db2e2aeb8910e0a9be913a909681cb6c54e7e9fe67eb12143678c98ea158ff10d81f0e639e2a93a03449392e0d466df8729f82eba816f72b1759723c633f6cac141b963a88b0683623326f85dfc7baacccf70e179f5465f8625c68d85b1cba54621a39d1af33908e2435553d5b1254f82a7bd6db3061b74c59e8a10d705e5a4dffbd125a837e3f9b5fc488c683f0800a3120f8f219ebc1097b524baf58523bf33c7813dae5186df7c25211b35440e238cf4ef69227a36995251d83cb955eb5370393ea05716dcf41f2f0b6824c240a6f7ed9deade66142276e352ce12cab40cf289ea50e0b40cab5db7aaf289da799b5e9c8ef0f852709e5c73cf1369dfe6ee7d96b4a0f97ed85c6fae88d5f5976ea1f936415818abeab1f1e2ba84370e2af95d29d97c52934a488d0c9c27b58773afa4bac7a9238ae52303a37f68d6ed3286b34700cf6a49610cd6a176ecf1fcfd2a9379dcad741fc898b3f30b8a33ec9db6f1654ab4aac8b40b75c1714b98c64d0ea7189f965d654be73c97e0bab99bf8e95a6664e92c9f93c07fb602d4ada04cc1d4c768a597b3c3d3e0262fb514b6b69ec3fd78f1fe44a98d26c6c21d531da2681e79f0c7d8b2b397f4af3ecd74d7e8b1b809c819aef7d47ffe37033c5be6fb4f767e8f6b1ce4672af0969982eaa3902cf3c2387ba236a2bb9cbaca95371be3c2aac1d527060dcb00427f644b66e55425dcb023f7051f49205c3cbacafd422b1e28fc483586a6ad10a7540149d63aea3baa3dd13996ec707b0c176f0f18a01efee82ab51d5a92691ad4e88412c30af400cdd2a8771b58d91f18423d518e4828ba11d2f133b60bb97a9515ef285516554e140cb250108f7c8410d36106fa6f054f3808cc0c2936ebfd4a54d232573a2090b8f7c1f24c0f6d129fb0f856c628878aaeb1f428395fc14e3afe67f2f52edcfbdcef4d11f261a1f63c9c1070edea5d4c19aaa3fce6824172aedc428b0e4f37d7d4ed8c289b75da8bf6e36e6a3e8a25a9f1cf0f1c363e7851f463d88810ae819b0252632c229f8d3698c1873bdb5d8ab4469e5c724e8057c4696d5ca46b1236ec87013959e2c25255a8ae32995187e4cb9a855048d036fcca157ec3aa4228b244f06ad68a109268dbad6cf590e6631d77f313de72d51dea2877f8ef7e6e389fed2839223a69c332bbe548be0b39668bf1d08dcdd891532ba185f5570b5b97376354e61a5f57468a8ba1e27e5aab5f567e77677b8c370a4218f2d22b364af7e1c74a1597aff31088a51f6b11b65562db8d4e6a9a307680aab503541499c8ff3a3df41352c126709675fcf6705cc9d69351b738602b64ae957235182c637b02036a63cb2b68fe702a3a45d0f922819983cdb657b6d93bd76f97160bf5fcc7171895ed07b47e05a352729d8104f282e85cb980682eeaef1a45205361646a78c4f638adc230939417f4b88998187ea3bd6331fbe2694a606c63c4cb46d748c51d372b81b81a7272e39e41f223b4f29c993875967711d96ac91be8eb57a83e6f3e9af28e13f5c94387a2ac79244cb54123d9293dc695f4c1729cb811932512c8de440103f6ee575b4ceda7c3934b9452c2a827585627452f4e951351e7c393c55345dc7c3b329af5e393b273dce71ca37703d8a3d9919534520935a4210f989ed89703ee5e2cf13f95c33752270f55904d0b586ab18e28f910006c1cbbbe9f71cd9e62355f86076257225822925d1265247d894814f0d6c6fdd4df4976a36b4d71414b2192327a63e6e8121d98eb3886ab85ce948c629f803e3df9fa0085346c623289121ad29704a8c1c63b9bc207100aa25df6825282a95993c9da0d6a8b6c6d1fde90409b79c5d4e71f47d4803d60315aa6ec6addccea5ab6d26c87a66cf012096ca51419d09258aee663907248f206acdb80bbcfe6052e41ffc580f7a8bb8b4be3319886a79c98061d128974cac21321e7e8e96822fbdc262823bc044aec29d60420c1a68f865a6bb02f81414f7bd145ca8ae5c13f0a799b690af9bd36470a45217f8b2234e967c5a3d1f1b3144f41668774717fc915dd18e107c4fec67a066483719a806fa3ee04c67c8d578294d70a50d76a02fa283d3e28d83141ec2354ec5ff8805cb5235290a10e6dbd6637729aa52d234ce33536fed6f873eb07b54ed55e191b8833088c20ad0c1d7d1867f082bdb486b9ab9c2be406b777addd93ce208143ee872aad4d539299c0ed7a259888ba4229997987308228010381f37262b8f71938a7134d865ff5e16cd9e218922e461d8b16bb6d1da2df672a2a237fab6de253f31d34f2edc2c7b4f5c2ed86e9d66466c13a056af2db12888029f7c61ea0c012c1b6d25aa9ee4ce02ba485a715b501c55a503583340223e78272d28e7f34ef624a4202331006e0be003dd3281ae505e08028536f0ef4e41701cb54087d203290b94497fa6a045e4f16b3613f58dec2951717effa1ba97533d3bb5a3932f09915bee2db794322529030c074807d10628ee73c441c51f2820d8aebcfd888217e34566f81c5580edc71e0f4686b71f65fa145371261551a95853a44d2b94783111e786cf0e86a107a342489f628780cf3ea4132282edbb4f9b52bc7d0ff262224f8ccfae046cdf7b1eccbf7dcfe9535ce1cfaee32aab6283171385607cf61ba41e0c8ab7ef3cfa245107f8ec3d7ca070b07d47b5e9e4edfb075e8ca431c0670701b6ef340f2665d527992ac0e71e9249f98c509b50a8f06224ce8bcf6d85a0076382a74fb28bf9dc3d9d10fbad6a93c9db6f245e8ce481f9dc41c0f67be7c194de7edff449da6073ec89b7df2b783152e8e573b700db6f9b07437afbbda34f1375fadc34a8e661bf65da340ac38b9934a6cf7089220fa604a84f3355c36718945aa58684b48964092f66e2b8b0f3edec08f019ee743c5d0fb67ec48b993c03f80c7180edc31f0f66e4edc39a3ecd95003e439b1b5bdf3e0cc18b99422e9fe108b07d683f883e59a8169f5f1114dc61ff87365d6fdf022fc6a2812de407e0f3cb02b6efc383b96f7f873e592d9f9f0f108bcf8f08d87e8d1763e1b0e8930580cf2e5ed8d7a14dd8dba7f160e2dbe7da247a1a3ecf80ed3b0bcbe78bedf6d9b1fd152fc6e2597930fdd67e6c137dfbf0c55842b0857c6b7f8595617570a45a4cc590c2813195e7069cce4f3585802ed5a53a1e18537162f060592b8e35d61885604cad30a21096d8ea00a8156a85a2813198900168b0ec0a90ea525d0a07c6603c2f70b04cc574a92ed5f1c0180c0766cb8d6136484c4a211883d1bc48217cad4ea8156a85a2813197908926e5a7abab21d5a5ba140e8cb9785ce0747eba5204e8525daae3813117ce0078b0fcb9f2d38512c05ca1e64afe148231178dcb1442f989ae5aa056a8158a06c650a100d0a4fc44bb9654aa4be1c018cac302a7f3134d01a04b7529f9160f8ca13890061e2cdf5af989a29c453ecb96e9b556b54708c6501ad802e545364b08af44d416515c44cdb079112cbf6518ad80aacab312c3b66dda06557545852845ad2a6a8a5a71b0fc18deaa5454a551b1a1526ca84a83e56f28b29395107682590cd3522a0a86a1c04e30dbf160f96f023349e17c2e5d0c07cb57295d273014466302439960288ba1301a2c5f05898e5642a4955d8d5642587e8a128ba4e32175f6ea3a1e2c3f654e4fe1a452291c2c1f6544e21c652ed44553572579a12e1a2c1fc5112e5d09ad562b212cbf5e282eb43d58f634273b9ecebb8e07cbafd8499456d59ca4299c52eada70b0fcab35aea2525b20144569600cb6822de4974a2814a634d802a93d3ed80ac6d423bc16f2e5aad4d59c9a84967f02eb6ef49345e1d7a66c5371a6b5c61102d95995a4da64fd49c6cbb740ac8edd71771da7a938bfd6ae39f923adf6549f949faa9066912f8465aa0ec13255bbd722b1eede8aca155579d41aa87a2aec6daa0de0ae533b67915f2add6b43e9bb0e965f479a48b31ed7dda2bf765d9b80657794663bb8d4a6da643723663e868c977f72c4cd46c878ebad871b10d1fe68d3932c999c3615db6dca59a41465dbd71fd8d2a04a8606799aa5a735e8038540ab93f213544115b429df32e79bf3e041ed7afae43ace52759c0754f9c9fe6816f9120896726ed68e50301a76804877ade8eaa6f42b9ab2226d9b5c6dda0c48ffb648229148d7f5b1360ef659ae563c6afa86d2be691cfa51eb1bfaf4baae97df385a78a71f9ca4f50f7eead48d24c27d7f7a828f85f4f2913ecb22a4cf254dfa3c69e4fc975ad05eb64d9bfaa954e21281a5f6797a00fb98353db0e4f11cc4017e9c017f5a1d86c017d4e8b6d21c095aa4cf484372258914e1623240d092b627a18bfebffde5ef70319148442f6c45daa1010e78a66b3389c1849a98fc4563d84818bdb7b46a27d2260baf2c2d5b71471667a24c94993313356b64914933794c99b903d3a06155aea631ba23daa6dd153541e3eafaa7711ba7395296c2f20633e2a634275fe551cc3fc5f0f2638d4d9b505e7ebca16993ed81e5e7e8034b14963ce64854a1f461ac28a51b8d1baeede51009847db4497bf9d68d9fb697bfa24486fafa1834af2395a743e386c6e91b3f750d8c4486529a53d1340f6a4ea8594a9fdf0a484eff684ef68dfcef1a227d43e525b14ba66819e214b95279b9e251d3e3e4e4e4df3bf9a8a534a7b2959a93f2e95da1f82c57284a58224322d1aa4d96c890287b0b85969dc8d56a6561abd50e7204c36a1a9557e66097a2b92fd4b7923b85e7573af2a38f0f816aef6893e7e079ef0673ee43adadfb36c3fffedddc7ad7624e9b2c20100af34b54782d4025f0fc67c5ccb5872deb47da1bd1e27c4aefadf5fd905513ff12124b83401e6984f4b96f4a7f4ba41117356e4d6bcec7465c300cc77af71e6aad10e57fbf5a1626e310154fec8942868c543b3a2211bc378328540ff7e14fa7352369104873f35de67fa97427cc696e3e46846bbda49f7ba6712e0cf2f9243fbf79748ff6f1a387406dba3f1f4e149eef736eb16ff07c126d072067995fb71a6c9b235b07198d92cfcfe7afbff160e693c027adc0f047eee8b1c0177d49c3cceb936879062c7a9296bf84457fb57cb1080828c36ea514cd8717fcb677e1e64fece6663893e3f2aa737bd79499f1b60c61387d7ff17b7f0fe6fcc032e3a8ee0fb6fbfc0177b4e901cd49eebf5492c2cdfef067dae4d0ebfff39cd7427e6eb9e12cf2a2b4f4299f3304cc915a8ec31967914f6974aa510d0849370eceb4ec99e922c3a5089eef13f2c898d3ce683ce79cd3e7a4f4de52a9e36b89315ec98e067ee0cce1c7dd90d747be0dcadc9236b9ce852427c8000637c618638c52c61a39e79c724a29a57c2a295f4a29a59c539ea0c36f870815aea3b7f2fa716c538c1af081df8e067ae0b6f0bd53c630246188c2c559ae18bfb7a7c26270637631651adfe56347af59c2cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf129c3a848f05aeb650df6887081ef8ed1041836b6725213a3aa74bc75976e20bfce8166282e8ca06fc60e2a5e3274a5730e6aad1b974746ae88ad698dc135c44563cb8a464cbf106c71d788844dab20361d514807c3a20f8de2d7b0d268245a22d77876f82a09393c2c1d90cf6f1c22a086808c630d8035b404b0cad8228cd5142a7d299b2c605bb74ca094ae455035bb89faea01a68e1f8b2e2f85750bcb8807de268751802cf8f53c67f38c71dec510705eff4e280cfd1050df81c67c080cff1881d9af4fdaec43be1b0f13912b180cfd1030af81c43a043930479273893c3676f42023ebb146a7cf62bd0f8ec447a1eccb5c13bc1140e9f3d07333ebb0f647c7621dcf023dfef1f7827e84382189f1d052e80d1222abc53cbb4160cf0b99570225ef4088977ea9b22c07cee204d78c9be3b18983eb70d3a072eda86e19d7aa88b18c0e7fe4093c0a5b1770885007c86546861f195fb0c7b601168f80c91803bc0fdf088777295b3f8ca8330c070053004efe42bfa7dc5008b7861c0fdb880fb2df03ce5f30b4ad15de39da28e0f0f66bef5289fdf4e0170d7d0823bffc9c88915ef14571ecc3b5d41fde2d4015ba60dd818f072e9d8d8325dc096af0274d8b20abe6a72d8324dc0966f8d99ab06fbd3d8b28ad3150e5ba633b67c65a4fc6fd8b20aa62a046c99c6d8f2c5d007c696559cda1c60cbd4005bbe0590f17fb165156b2866cb1466cbd7869b21ecffd2f9c9ea4e5ba6a62ddf1abaf377b16515ff1ef2937543802dd3016cf90aa0876eb0bfcb9655dc6ab1651a802ddf161b7f165b56993e00d832a561cb9745e583fd3957d119b64caf0c5bbe2b56b6ac126bfc34679c45c7fd63d8b24a69db72e96a5bbe5f4355b64c6f8a2d97fc53b6ace232c7af203f49942dab94506cb9744fb67c73b07f29d2135ba6d7c496afc9e65fb265954a9ae14b37f6883d482e9deb0997cfd55d2a1ff22112ad31e95e35170eaed47573d9788ff78cac2e105c3397cc502af55a6063d514807c3aa7719ad702a94d0f7252374141af053616698d6b36444b40517486caececbc962e0b41403e6dd336af0536aed84d10747252458a544bc652815563a1ac191f1fad31a635aed4c70bab20a01f3fae9411543a3910f55ab49e73661a316d66cd4409f9f05a2ca025865641345e0b6c3c7394d0a974f20a5e792d50bb825e8b53019ba90146c48d12d85f0bd8319fdbdf38be9dd7f392a5748ddd6d6dc4ad1b2ae1f65f412f380b00c37f1d26e33a2bec04c0f0f3f50397d742b3924ff7f2e2fac1c9afa094190c1573e9c016feb906fcde4590b3f8e9c51ce0b1e8c01837c11e040c33681330c4c32739410c803886d0938424924822892492482289249248a2a7a7a7a727489020418204091224489020417a7a7a7a7a80e0e08bdbb3e69cd2f67facbbaf39dadc0edeee5b7479fd4218c1860dbbdb4b34e08e1c6c11fd71d6e3e0472933d7011fd83348298425b810461610421c5c941912eee0a08e3c04ee8de2cc02d2c01923c487abb88a6e5b4efff6f6e601dba39c16bdb09a59d1e8461d5c49242526264e944e50a0a4a450a1f275cb2c901a2bb06d13fc95ecc591e8df977c6e47ef2db98db3bc15e4811676b5a7552a55112258de2a04f3c1cd35956ad38f3ecda8dd9c73c5cf883369f839e764f9c933ffe79c53e5a7d0dc7ece39b59f9266a6fc9c73a2fcc4992a7ece3953fce499a59f73ce133f85268a9f73ce939f93669af839e72cf989334d7e4e28846792fc9c735e3f1f1e311ffe9873f64f8b2688392d30477efe0f3e3c18ece79cf36bcc39a90b3c75a0f160eecfe7da447fce49ef149af6e714fda434b3fe7c122b63662ac3e7cba57c15c3f3744242945ed4aae2a056a815aad2e0a4ba549712e2e9525daae311c270502bd40a85d1e0a4ba549712e2e9525daae311ba702c1ad40ab5425d3438a92ed5a584788456509c88533bd47f090d4860597133c8b0625bd99af42af5ab4aad239bd56a7d959b8204941494918a9aa213ea523a944e543ac1934aa52c8a139cd289cc04124ae66ab5aa2656ec844ab60e2399578aa7ae521744c285bafa1a01025aad562b12c530ac07eb84ae6e76b1eb919175533ca9d44c453a2d90e624ce08355151d6fe6181fcc899217d703b3f79778a013ab8dfbfcac0c5dc40b9817203c9b8f3c49bf7c9ae42f90c8b9512eff16cdb36baadac68a08a23db566c8d195862ac5f6d9b78cb872249be26b61e62b2358bddb24d867ea658b5f65bf4d8c5304c84fd08135d1856c230ec5f300cc330158661d80a6b03cb300c1b619f6536db36ec57ac0dcc6e99bec665aec5af9a74b9dc82171011286b99190f68dfc2229b9aef34673da964bda7721c05af85f552731f6ed35c8fd762fda5794d0fe92015604b88070d1120f7eec138befc725739be9ca54df5af872fc655b085f5d7257a1d238ceb76d917f8651bb0b523fbd78e46cfa7b30fa3edbf88c1f66d78f65f1e4cf69ccb73a5b7d65a3bfacddab0236bed6da03659a3cff747234dc5d6b87894c17bf568eba11b37eeeeeeeeee14220d4773d68e14a601bf9d1d3b706dc9a0d6d5dd5bd27e6bc3bb5b3e20da323dc42573c9a00c974f371c2ab606dc7a78411d0528642e734b5700596c53b7d64380825c1577747dabd4e3f28834d87a0f03f6d67b910793fd09764bb4a98788c0959065ddcfded36f9f5a0f7bee96290e15fd76460e020011212482adef7f449ab3dedb0880690f765d775df52fecaa29fe22824d19d49cd2928a7b15150b88e276dfdeabb036e2b7a7b0394420efba96b9b7e42d033bd85d5b7dc97c4a805282b0f5796585adcf2c86b0f5d9854be7f1eaebba2836e3c2e8735c749159efc2d65881614f733ab82eb2a9c19986321a8ed7627d9440047131055e8bf513782daca7253ff9d0400d94527ab841d4095bc33758037974479f188ce1a14df11ba8814a1d84ad066a16eb9d62b481eaf67a4890095b636e60083e65d87c92f662b8e638d22cbb5e1ecc8b7938db707277ef5cd5f7453ee9b30b49c9e73739f1fe36bc98873d06fb9fd05cd52cd69bd05eb39868af4bb4d724da6b967b9d581bd70bd5e87aff2ccaaeb23eb3ef9b095b43b40901f168cba2cffefa92b571bddd8480d8ca0dd49dbc806ed796657fd9172262fa3a4698feccb62dfbe0da5582eb3fe48558369ccb73a5bf256abdebb8676f626dbc601d3a2fb4151a3ea07ef8c022363b1d4493eaf1a1d8836465cc2fb136e8666b6ca08d7f9eef323b50cd593f626b4cd18b8b5bcf592aac0a373710ac5fa1f530c0246b837edb401ff06644e4f65e204e833c604de62a958a7ef6b93fdb20ca6e225ba33efd5a3558a3411e1a8684f5385e8b8cd53b66b217d65b169411f2febd7f3971c1cd1015a3f3bc16eb5d7315156e4301e8c1d0b7fe4f4e464630eda71ad66d656aa36e596e686b60dfcfe2806d39aab5411e0d51f4ad9a4a9b52d883c34f2faacee781e80e1d6c6bd8ef4d083883bee8dbf6534da40501b1dd2a606d9ab35cbc96feaa615aae01f70b6b63b50c61e043cb2f3630959203adac657a47cfc86073a0df3f73bddea2b7f40de45a0f1a862171dd7a5751eba9c521c7cd9ab338f496a36e3d34677d1010635b059ab39ee26813f6d657a08736c1ae39eb5d50b8046cc11e68e3271774e7f2942ceb61e7a7d856a79456defa6aca30f9aec527693388df9a105407c4f45dfcd4439ac57acb22a263fd73eb69b065f5f710cbfaab866f77cb9ab3626a0e513b889c278481094c880113567244e1f8f01f7eff0fec8fe1e0c6187d6896f8cfce90985ef49ff4d15c3f0ce8f63b90ee1a21a59ecbe3f11d070f26057ec48be91eb045a43f7aa8843390814747fa730f79ce83f18f5f3dbeb50ab0a2e7a9ccd6803fa7c43e37c8a239df62ebf0dec9538f257ece155d8563f7e0e6e87ffc706e9bad6f5e6f140c77c86ddae4a34fa236cd7fa6d6e91cec6345e2c1643a4676cb360cd70cbf836c0df8edbe6aa11eea22cdd243d60a366c070252e2e61ec2407f12bd03f99066f1d47391dfeb25ffe71efdc2cdd140cd4521cdb54fdcd1a6dc3e38c69907f3e2eb08a988c0e0e6f669216d7a40fdfa6d9d133ba7c473771c0806d2dc0adc5f61cc71078edf714773d1815e4b7c2ddc1cfe41c0e8369d83e3bb4c9b72f4c38f45dab4e3c1581fdf8b7830d8d7c7a696eb9661b5727f07d91add58136eee148e36387e074520ed3ad973d09bff682efa909eff2ba594cc7f747417f22ba594d83ee2b74d740b95dab13488baa2ff782dd18174f780317ec46be142bea7c48363dcc131e2e0b5c4cf9d021c1d08b729fbcf8fef400f060a0fa63f7a50ff7c9f5601fd107e77f398f08a150211718d1de18b52045142521e3f72ffa5d2bd310ee98e55d4dd16ca4420627cb738484c9b8b31fa77e0765140bfbf9874cc8781c4b572e8c8e111f54bd42d36d78197977effbfd868683a6834248adc66c8194360199d469bdec97b97054f0144abc6af1076d6d15dca2d12c131ca2c6e2e566c683debbdb8b381d4fcf93466493ea5f7cea759d765bc975da0816a25c6faa86b367b15c7b8b9d4803ebc60bfc00bce01ff6db9befd5a3d4f250299cbe630f271e4e343ab80918f4ffa112d3617738ed10701314a0e2bd8be7decddc64fd481c41b2cff3d18205bf66105dfa76f227a97f994de2bdfcc33e0fa511b799296e3d3217864cb2b303a5e6d8461ecf15ae88ff0d54443227c35db63f1d5b25486af569da6e2ab614118bedab573e1abd1b6a1f86a56110b5f6dfa5c4dfe90f86a115fcd7d782df461340dc7f7da565ecbd5e0c32f36873b476c03eebf2c2cee9725ee8e706b10c67af2664c7d4b2028094ae86eef97ed36600b51227d8ba0768a90c10f085fd0c119ce7c21855dd2173443697c23f4af87330f0666cdd1871e78384a14297df8a1b9eeee255cf93cf0e00ca421b826701a0ce10cf4ad873892d29c8c0f67e2d4749823a5e6766008652e9f1e2285e77baf037ec6009eff1e4cb6e9784b6023489f678eecb9e69eb5d65abbc9b7810bf17b59a6e9c8b44dea3004ceb6f758e4b965f266737ea6e5f9d9c3f079b76cd3613dbc4071cb14da06bc18a2a79848241a899c625f479a68341a8946196d2b63e44764ec70b70ddccfc01031eed76967e8804be022e0ec7ff2e0ece3f5a2c72ed18f445a965834dabeb92c3fceb22d870ef8d9fe0e77bb5fc41031ee5604c98f6c9b0ef8d94bcc88fb24a46de624c6dd3c70fffe94f56a1b804bc49881ffbd5a0fced21fb7f75aee86436b02ee8f2cb8f901fdc8bd3f52c41df9228ab85fc41db9a216fd58c10a58b8f75a924ea5c37114e32eaf9ec55a3fab6f5fe49efd8884c54c52f955be95f233ecdd62b5d60a6fc4a8578ee3388eb302a594529ad9ab6299bd2a7661d876c3d68851af1cc77196cb382b9ca01849898909ec04356192955812ecaf2cb3b5d65a6badb5565aebbf5affa56badb5d65aabadb6d66a6d96bddd6eac581b75e361cbd8e3c0208e304257752ec4711cc7715784115ae932be4b7f9b437cf71cf529a594d2cf1c0f385c467bd5af97afb0f52e0417b224f7428c7ae53a8ee3382b504a29a5f6298655d90bfcb20d38cb46d993b2d1c827fbd7d9c3e8d3eb176dba3e8bc1d9dbf032fb5996655996655946cab22c23dd8c748374335a29a515c70dacc364948e452cfa38e632fcfbabc6e9a83ab9dda011070f8e09d15a2f4b29b502bd28a5d55652f61846cab0ea9a73f4a2f47a8cce8bbae698e3388ee3aeab52ad717c3856214a297d95d28dc64a7dac06653080693778c011010854eefaa9029d4a87e37caecf9db950342de3ffdcbed8f0425a79f9e7fad3d64d08783df6f5945dfead39e656a8b5d282274c9894904029258e3641d8e379787930d8c37f41024ba00934014fc0d24308bf74c284490909941794524a91fcfa39fb6aebf632517d297bc01a200e7c8c194497998dca2d623d281522e2fa3a46b8bedb796113c3300c7bc1300cc330cc5ed8b418865deeeeeeeeeeeeeeee5e67b00dd264a81b3e3e3e3e3eb36233d87cc938ee390eda642251ada24afabe312289ea065e0b11afc58c8db3dc3467717f697d29373833cabe7ecd348882343e375e8bd5320f06eb1b6c7d13f1609e8fd672cfbc1b3e4132744608c3b08afda3d7b545ac521ab30da220cd4c9b301a148661377c7c7c7c28966518866938b28d87cc5e5aa5194633eca29816ff9a3687c79246ecda78d84e8cb8d6dfe00107ad18ad188d5a7c9296e91aaca75a1010e7f0586a9c9f7a48c7e9bc3cb63d68a1d4d0b0fcac312004e612fcf693154b383e8e57b8f1dfbf9f76802dfca95e8c3b3ce0c13ceaa5d79221ce436029b7f75662741d6c7da534e59fca53aa421fa32a3df409ae9cc57ae86f35832d08bf519c944e9830297912d2c377b90fe1bf8071030d1d7668d394f1f40edbda8094d217cdc1959f6280f0073fc5b041d4b65167a11b44a97814158f12bf033c7009bf1d0e14c13566293495778dfe6bda47cd887fa9c59f9aacdaf559f622cdfe7d9236f2241ae94b349237d14ade8466f22734135fd24efc89567a14dac9b786e251b4fe941913abf809a6527c2e614d83449ac5527998f27febe1509bfc6fca962f45d1541e62ec0a17a6f8dc40b0f5b97780533e370ac5035b5f536810abd074fc80f2291fad0d158ff2aee251be76962207158ff2303a058a2604c4291e12696b07b68268501e46aba00011e3f760f5a7682f36d75c0a4de2144d067d140d12b11e85068b682dd39cf525eb4f683da3354d73d69b68ad3589d635dd43ebabb58fbe691cad53cd599fe53490e6ac9f5aebf44e73aad79aa7bb6783abf79ceb83adf8348b56d4717d86356a8f62d785c58b4a29bf876e7c307d4bb27073fb5c202860eb5b88f4f611d2a69783842b8743de33ae73d22545ea5bf94ac37a15e0a14fa47f1d8b95b62c210e805fc078373c937429c08be9c78d53638555bfd639bbc3d85bec33ec4594da1fdd7e87f645337ec7ef48bf075f6faf17ed60faee9ffd0cc18a7ea3029b3da804ec980a831eaa9911000000001316000030100e868442b144cdc248ef3914001078884a7058194964711044214c19631421840003000000406040a638009f001e7779b6919068760b89c4dd60303ee562dbc6093aa7dd54965ee0ec2dad11e4b572a4634f12e481fd32b06c42d0110aa56923aa49ce425b1a360f1f748f03cdc1d5635171ed21831fab1b219117c1a23348af743e5fdb67096c026b9244030d3b0c428b4afb3a95f6dace7268a7907af10fdbd3616386b0cb44c45606012d58f356fffee4ca1b5ebd9e7fb18a8a4fc19f1fb135fb22ed424955f4b009078d1c5b226978322162abe3978c96d69cc9aaa896d399c9e13134f293b7f82684ac46cf1c7155ebf051f6501a8de34f1eeba8696beb81bc929f69d106813a62af72800a4cf45ee4fd195126196d68f97f6674e74f98bfe787877a5798b8bb5761188b591e99c90571b43fb9572569f770c74bc9080747bbd4e1bdac42d8fd2939e217f5446651faf5d6fbbd617543dd70562cdb99b88a1b3fe9b1c791c0f80779ccd8f47a3c900b2424c2dfc3a929704e082419e80a595ad4015d494c486b64fa090cb57ee66d2179e5da0e45ad412c7016ac1ad7ed12d0d71e828d46f2cc87c66a4e6a523fc93fdcb1b5183a2f58581ae28d20eaae7e0c129848c6479c099c3e6b79a13f34862bef1866bc7a6d996bf2ecbeee32d7eeb15109fc49e4a13223503e2f9657921d00fc57d0dc7e2af0dd14c816fee2bceb7e708a26802ca8405707ce54698bb5e20285621835091d20465b005f3983e076da3b22a09943940069ecf39a3bc8ed5bbe0158768c6650e10e625a30e52626fe1494fd0eab6c12ad0a6f25ba6d86c63dbfc80a01d7c229a62d140f9404b0fa183fc22ae30410ea0d3c94f33a6d6b315c8982ea06eba377b1b6aa18a549c08bdc77cba1398551364287dd691af1e217412d65dce38425a41f6296f8e6473dfb4d313b5198adec8dc4a126a24251dc6a86b7b3784cc019614d3b1104dffa5ecf23e18c1a3c12b2d16b9c6e82ee04bf0b804da0b58653423dd304bdd3641ee027e7a4a11d387eb1f2cabd87de910c06bb0398f984837924432e2251b99fd739339f4acd7b27c9a53e66bc7724d38ea74481f03e3be3e6432a7bc6d003310baca8b7be7c500d22a19b0ecd6830db88c76d00aea0f3991a32201af16893a4571f1cd54b324e1175322263a230b471263a119432ae3018776b4b6673f7324286a65caba10d895a42a27a85b6422c0dab7cb67bbdcb055425095aaebc495d80a4975f6a615da9cbb150691230619884d57040385766fc0043952559e6855b18d465f369a669062885ce38ec0cbb131dc9d8ec02fc6dc0be11315e5427aecea4788925b3eb5615144914577912d9272a25ad64af6421620fda91e0b0f3efb6d58c7fb62e90df3814cc7db95d0850e977a90128910d372b71733d0d4895957053ab06eefb2df8017a21ce2186dcd22c02539caaf6bc4c03290e4a509aaef7a615b1b8b1015b1d4a3d2a4c6a47ada6d2c5eacc612a4124f3f40862c22731e84a2275603566249be8108d3e2fac9eb77a7daceb6d213c94bf25addf8b333555e234ce73b3244bfdc9e3ef421233d6757f11465c1dcbd64c920726618c4f4465b88ea7464eb8e8a110c9fa524ae510bde5c5cd24dc6d3f5e9b9924abbafbc1dff74ce29ec3879de1ee0060c8efca8eae33492859697d33d34134502a0c52289ed1312a70c4b38655acb24566a3e1a96297bab78862de26116bfb15988115671b9b8e7631964c4216c911d316075d17a5b050700350338511022304d0665a02ab45ce8c024d4a89de896dbdc41f6f8b2f7aa2c24c1213024df6473852afdf8fa4e3f0ac6ceb4e144711e7a828f10e43ff3dcdb6ca6b93ac526308d4265ca04c0495b8f7a316b9f2b9cc4b28b08c4334297a47e334ddb498c0b443884c0ee8af02259ed9a32cdb241f21a99a0eef999a62cca044e9c02659dec0c5d6a1c7be1b902b8507592e4938a4e9697385aecb09a434f9cd3aee8a09a028992df8b1df882523ea3a93748f841274c6be916e2654b48f5192442fd5f040e602814901ff8b2687629c7248b32311497697b76306e6916a047b77ad766b28fa7a1904e18043c0b118f0dec409da122c90b9847142900f15aa5ccfd9c421740a6625c8608820f638cc42c788d4f57acb729ebc40e27852e9b0447a2811a0fd05428f9075073f4221203ce8c46e1520c31413019d1ad1d3ada807298c7f47fecc0e6878dff6a48da3205c9d665eb106b1fe753474e00b7f18e9a2527790e4265a94bc74e081c32814388905dd81a28eb8cafe9c8b5becf05d4e44a26dbe21254fa99d4549e9b7ec2846dafc9b2e106761d45b8851ce0582858ae5f087e7a8681809008a8aeca15540814e981aeca796a1d8b575cf31028ba9a1069e5cb71311694de42d268ac06107be198781e013cb10c4918a9573e211c07d31f5faefed6c534a8547c375a4220a990029194829e7daf7e00e49d4903ccb1763312396ea09d939f5ae5612cf17e806a507acdbb129d324d63d363cab063290556aa175b3b669a9f0af493519885b9873d7836a5e9bb0e3a68f3821e9cdeaf3848f773edc4748fba495f222ff7f5ad39e7686e710141f48efa8a2ffa2ef21340dabfffa8dc69a8203dc7c7b141ecbbe6b6d20840826285488568f8986d263e5e7ab9ad9d53e18d22a03d1dd389b339078ac071ba2845e12421d6c58d33eab8669a2e2d24cf366516f69ab78f074268593ee09aedb984a83710b00f1f8b0cc9573e09abae54493c69482b6e37d64f737c89d3b83e695788a60d757c176c4a7daf01477697350b24bc9133af198ab23ea4e50366f0de01a58d140ea78174d87bf49c02195d2325265251b9b0375072e1640ed4f3ebab1d3a7d1c16f1fd0a1e977aaca32e20443ede465d50278c7f43611c57eb29d67ae03e3104e26a06a7220353790fc6c88ee00a2b484ac8e07df0f08c36d8e5427619933815217287b5f31ce5be36a430e9837babb540f0e46b8380a8d0f24cd82d229237d91e94d95a9977411a937abce30da41dfab9d4da3c9bc1b0b86a06db1dafcdf9bfeaf4b071fc7f0b892dda0d4f8e98f44ad8d0f8a50e10ed0d2aeac6c536cd32743b0aab1b617482d03567b4a24730dbe0d787cdf030137c786418b3714f85952806e11dd581328afce996b84de489f4be3765aee380aca3cef0f08653537715bb439a5ce67cfe7b3409ab3fab374d6f5a2b20b5c883a7478ea6424f37beff476a53684bc7276ae11915060c2d4529ff5552cc23ca3e9663d4a212958f0ae277b28ec6b755e42b532f57e19b949c5fbd671e9e9a959637187cc14455310c93eba01276f54b6162d76ca14899701cf67168af327bc80cdee2a1bc8d1bc271ecd6592021db940845289748ba8f589ec958ad22adfc6220c31d8ca1a410d4a16251915966b15b5c9b92c9169d1c0471bd7bc28bc4f229eb647d72091979e16d8959884f9611161a5ca7aa45337952359410617e6d86b58da14e6d9c8388d1c87d3d89ffc8a3475b52a6f5598cc93576983c6325004d9042c43147782daedc5456d2a9ef211e234827afd69fa21847d3b6b7e075977cb4039eddb5ae62036decbc025e5e631f964478e3cead867f232a2e0802b7f8b28d7b2f2e3e811767397c3e183973732506bd3cf5f80c478c839bcbb27cf94b75f44a9c5663107a752133c1cfcc786b3d6cdc37bd36f914433e1471952e9085b1a59c10242be80aa69ee47f7388e1fd6ec62382855d91ccc6dd70a937dc76261c1219efdf46b19f17963978eb57351cb248b70b0d8464513e43e8ebaa22f12889c9140e0cf45886ca82ccd07c356c426309a36f3d86f101708f7ef3c0a61d82bcc4983caf65ef982cb8ea58c49e3b63295eadd753fde333c609ed2b2769602b15fad3c8dbed15b088a09d1af10d5a14ad5ec52e007fc79fde4bef2cf3ca5669d92ba6db267a7aade21ab7e8114bd1408b03770e34cc1cea04988b579928b86f927f1802243a3fe4a634f2d1fe9947c33367cac2dd7e879d490e7fdb5f37d4df58bbf45cfcd11fb674ae31ceea07276f29178cc50aed8445c89227a839c173d6287c2b58d9d08cb7b4c6992305679bb4af83fd82a2eaf2a908ee010403d72fb7e44f31f6ad651f45a420db449f5dea8460392164d4e09020a24aa3ffc0841a745b905e8d723838849378c4b9842fc225950731fd090941007fc42a1bc84ac0527c5a1fea24ec8b786d004ea687335c7aaefe590a78df6dca954616e699c0b2277d2eab72aa65b4163bd5c420034f32fde5c1e3691ecc48e78e5d9134aa1230266d6c026174d8811918ea1deb82c7b128ebb8e85339238ecbb05d45963ac2cce94f964746c1f2361cf9dab184d5576fce728a34b74406540b9fd272e49536f0f7e35d119830afd00b618a2f2a8b1fb6b5c78d616c0bee5b6cb3c10c81b68c83aad9f6293cc2a57f479c24d5ce41e6d99ff08a558ebe344877238cb106353ff5cf5f88f8f0afee216edfe296170fdc7614bb1abcc28c9fd9e24334ab3ec159a203d1651bd695f76749e2b2a9e8039e21b6e2d87680b02dc6ea545c17400fd6f08a68dd3e1edbd283197d9aa12d264499cf4e9447f5018529a41a7d0b49dc1aeed5830ba5c4a9a701351aebac2436657ebea5e0c43bf58dd43a1072455df10fb43162b1d3084891c521fb02862536aa0b6901370ffdee1cf0c870b29837dd99bc9ae555b960a42c8817e280f8a3c7a0509cb3cffca51761f152fbc95c97a1218551620a2f346c0c8ed7d213220da75c935ec3e2e767ba8611ee685878ebfcdffdb37731c5ec3e991c7f9d5f84e2222926433e53c3d6ccd7148fd389999651c1764c58d66a82fd48928626ac1c28b22a58a22ef15e08495cefd6f84aebf4b18eecb81bcf92c05954ed4208d8e4ab950995d82daf41876d864db25a8959386ae1a21ba7e9c55bd27f6126c874bbba0e95516c261b70cc0a9d990c5265dce956972d396531a39934669fbf25ab2f99c3948330b100f9e87daa33b4dfa373982211083c8f98df4e59ff9513af110fc8098c7d76ed7b9f8e09da7be93b64258b7e3f4b3e757e2117853b17c10f06b14d73fa0acbfd8f5a0d31dd8b523b1410f1c3b16f9273f80ef071dc0fe9024bb0124a606b656d9e78fc87d22a20a3e5236fd951f591cb2b45c9935557b94ff7ad5e004a8c3dc88906ad5b6b8a9db48515a330e114cd8b8ccf5123d81956f0b2e6d65f93c20d24fbcad5a07dafeae9fab102ce62a8d91ab60132acfb52a00bca64b4d1637cb6d82ad962eaa5fe2b52055f04aa95dbf381f219886d4f484db23f35876d07fb4d39f8d54dbc0ce18986f6a3e87baac3b4a6aa4f55b81407f613a9876e791d0aae5d1b5ffda3b011d48d25e26744ef7d1ee2cba70f77b1d1241e407371de76c18a844661a66119658b46fdc65c1af15f5fcc93a80d1da5a210601353e9b77d67871082b094a971dfdbebf6a533cca4c184bf4f2a8613dfb56ed8bf2523c43af3f0e327615e0f085dd01134d2203967699919511621203302979f39458e8842459a95f85fe784a9fdc124d9573ebe38132d3d1784c90d9f4624cf7b95136661fb96befbbae33f8957e8cdd3798cb6e426101d50f5a41d95a7716d1e8238a00a7d85aad9de8a861b109973a09939e27025c13e5b68a8914a52691e56c404a3d895921aa18a1c7ca69826bb03d84547735513c05c2a33eaa0ada3fa117042a103d14cb5324fa2cb724ba442057305c81e823df9940dcd764bdd056b90a569c487074c7415ca9c2b946fb06b70302fc1c0452c8f2d3257e06dd0c1ca0e19413c3e828d341d02c9e641fc61cd0f096fb5431847ed286e5b42188349b53c3f1f191def77a0fa2fafec2dbdebed89303558466d92ec0a9c810f07186b507e0c7659c1d3ddbe95363c4db0237db98357483bd955f26b3b212083cfca87d59692f8663ecabba17e392ad0175af8fcf17e3113b5f1e06d0698fe0030b6eb360d58bcfe669a9d91f966825d3b9415801cfb5034a4dcb0e67409f08e0e039d22cbfe400a1bf912c8a84f2ca03590d7f45ff9371e1a0b564839936891a5bc3d7c8ae810e5123eb556e2c9e4f2745b801363901982cb412952ff2abd0b8e0efa52e7251a2d0a438efe0ce798cc37d267d63f1ca541e456a3cec4bea4524e234e36155615c2393cd80ed8500f29750c5e5ca1c716eff16e2db4f300512c013ec3b67d494133c02edf0f16467313d9671a7467c7380ce87ceb0c0cf0a27c8b2108fb0322a08328b6f90e2f33b5afb50fbce78f76f1a130065eb70b598988a3cf0bd25594ed8938411c9741eda477ef5b4df0d2f9c6338d58c070775a65f6ea4f6d686e3a0df4d83915e437e9f9f1cd3710e8d00f81d55880f6d2d0ed703dff28b601dc48e5b47c94a3085794666cb41dd1f4ef036516d188af4df0614c91edd2014e290a0624b1849660520631b2c0d42b38c33a9497bc473b56bcac1efc98d2cc5bd198513e9bf75b6df4fec5955376801b7f1b6da1eceb2492ab0f593ada5eafa58c753d807dde5ab6b23aa0892eb0f46e5a6a04ace2b9a37088a123dfc0f287a2fd26f48574c64dc020d24aa72cdc35d3bb84ed270527b6e3355d5408df9b99d195a30e985000e786814fab750005cc3e6437ea45b8f4602191db702374a201d02b3b13036a0a6e274c0b1ef0ac8224ec644649cd36ace598cd72c3710909105cbc0622c5be05be57c023376ebac087dd4f7647ec70b7b9e0580590baf160366263c35a7ea638bf6479f9729da9c2f485a00aa7add3d3d18372469a27116f1c0cd3d17c8b5a3d9387dafc47b30f4d88db68a9e238b6981182cc5f87c7bebfae586d93e529476ad76fe0a297ab080659bc2c04acbdbf1c5a7594f0703645e472082c8f95380701c8211c08006c0989ec32f5e00cf022b7fb55c5b7bf399161760986843a161dc70bf51d50f65bee384c652bdf5a16c9a168c16746aeb5fc952913f827bbd5d441d044e466219a82be6508426472753128d961b0f8fccbce9421d1bea7cfa5f9c0dd496c06905fc7027e040354a7143df8340a658c151a7704ef97559aff683c3d4a30c5ec395722c119a7a66b594ee1f696aa97436b1c4e16e614e1e197a0aad08564b740b2d36887a82d0fc353dcccd0504fad718c8e4bc8581d66d48cb5bcd2cd09a0544b1028a8cc9ec4e59f5091897301651b10d95d6cc164f9f45d683507217baae842ece7d82e1a1a00d1b16187340937e81ae4a008b992ee9a7b1b84a2b9a6bf7431e367c0464094ee91b2cec116ba91bbd6cf00030e9055f5e70a70b4ef882dfcefae3de15d1ee63ff2371822643284b1944832080196437f03aa35b466b14bc6b51a80784d3c2c4cfc0b92a03539e12ba8566278d0bc20ee786328fa90544c7056e19114087114056ce1e7b0aa87c6056cf3736944ad9f545d5932e199443d42bc3aee1480ab438f00a936a4c3e90a1fdd42e0e182162c44b69e423c0facc86906c81234183503ca541ab3d7b7a6238ae29f1ac0116e80967ee51ba79b89c516c5638ec307f3cf110e004ef4d2f6913e8045e78e9bb23154a80cedf74f8382f1fab8496c227af1d3f4b788eb056e4385cc9a9c326df106420d5daefc1d28468439fc1cb5329f38a49691726f0b4e45f582116444ca580a154b3f921fe91fa06f062b5b947a3b8656e9715f8e6583207fa98e3b2f52684b36e78fc55610619b7b5ca008ebc2dc3169141c26b50d369117077c667d46e421c21554515e2a3ce7ce34435d847dcad15c01682eb4010d2ac13a43e9436f8ce18a8a53910e705decd952caffa74a6fa91c367f45a7c7512df4494fcaa87c9e2a66dd496962b19447d4b32ead53393a2a499cba3e1dcc3ec8d31cf4758e82df5107447ab7670fa284b62335150a9146608344daaeb9ac8c41c0817e823059c87cd43356cdf515da544db467506a39f951ce9dd44842183c4c143e99c1df01cbc67f1731e207b661fbb0f46facc8559b68836ff4125f338174b2846fcd99fd55b09248d57a968ddb8a6f6058fd4da0f0547075521ef0798649d78c7ac400ec341f48fbd8db9b13ff6d73dc712f23f0754b148d91bb18122776e7da549e0b82c92fee4c3e67bdae0310029d9d539a3f2204d35519c13171cd5359eca0ce8e00b15a3e650816914b1003bd0d07d228bbe3c26eb2694000259872734306b707ef4128cb6355bf339b43e630ca3f7bb5da5b6b9e296ddc59961744b521b90f4bc3aea6c91d004182955fe8de4928e81d51572649f4754866592837429bc7173034f24329861723c15704e0a6ad47f69762fdcc1d214889b29e540dd0b1d457448e56c16d2781feb88c78e60528a803b7fb34dc13cc158cd51afcead52f254fa4dd32965d7dc25fef135234a908e91d03fa17ed28e6c56ed2da44d3ef247ac7cf4a38369e18a39c4f23c5fcaadc780d6ba81d8739c13556a910288bf991f6abfe51c0616a7880fc4701f00c40efe5193e5412e10dd474a0635d7f0f3b4a7b207b77e1258d1592af8a28c25588325ff88ad082091a7dc1555afe2035046c956781aa00157fcfe5bfbe3c5f01e88f7ee923877b23ae2215d8c51f1960a13553f2508705d9680f73a52c975b35284ddb1732efc4e9e211737fce91b9a5e010cba0685390292d5979f7f0618e62da8a0b280b00cbb611f1a167c2f18afe28aa8de8c48872fd6e5ce4ef426587bdf54445742075627728864142c796904b27940dd11f8380732ee52a1c9a91bc4c38ab6b94c748d6d661571f657b25e7c854b73a43f2407f376ab492e9966a066ed30f3c54e0fddcd3ee0b5f17f07c972f9111dcdbb063e6b14c2b474473622042bd9b1499d75b6fe4d65ab4a89c2c404dc824731e44afa32b22e8ec3aee9fc2339140e4b5657979920aaa98b0c4b04b37d0d80dc415d282fc4339471e24c5414038d840db3d23f70a5f60047af83689ed369b00cb2c5ce05e28f6adaf1c353af53ef338e3e2b5a68acef55803106df961aa649dd7b3b41d8a4dd058da12f6a38c43df69117ac06ee54bdf23afba8fe2e357f25b16f8c4d5791fd895db7b1ff98d9092aa1575d68b5be753c24a10174befecde8189b786c776467373a9712a73149b4f4eecb9a35e7cad75c5ddc7a495c930406780d83cada9015c659c58659bf0590440d614fe15aa91008ad9111bce8757c050060e1b289ff6a4f95774987cb2f82266da31d1bead31d1fc3e800346aaf6b35adbac33d07c535dc9cce6c061e5d63a9ed8ff6a08bce73c79ef4a99a41c87cce1fe2ef48ee3dd5b35d583e7c559c625af5bea820ceea5bc27dd98bb33a4e351f727521f23a23f2e2112a5406743c5ddd147f397aa06054a932baf618eb9e772aa22d461f4a4b6c6ccfe0d37bb19e8ebbaef6228029e79ca45c7d5ba43dd95ef69c1e443d09e9117aa3b7a3af9e8c108e5464be19cf04a7a582149a035a55b5b078a1ba9fbacde9e495491432bf22d8cbe4f444a86f3a3d51b954ae9223c853ca55f403d241b98a6e204444a27d042774b2ff8c65018df64ba2d1c4988b87cb09ce13e8de3e85dc97a807c6aa955e8a69b124f860a5cbb9611e49676ab5a4d2cbd04dc9df5cbb8e8ee9a9287802d78e374d644ac49330f7891248c342708e7f557b30cead8147844349c83d18d2b417d313e23f83aee135787120349003987a19d6309bd6400afb4c11710d305c3c00341cf9eaac371018a911412a39cc113b221b3f8735fb3e72526748a2eb41b10def097c3ca6f86b4d20d2b4518983020284dfa781da6eac4405a664a638455dc2186127be19e726192b7d111180f10b8a34507b81789b934a302c3394dbd1638c362c4540a238cd86289583ad48febda3ab87c2cd4c004110cd4bc5baeb6d418dbdfa4958507a1f90dbf0aac7dd30c415fa6ac52823abe3a6c5fdbc2c91dba2f9a8b18799cfb12e11262c86b2bace48ded5c6ba43348ebca23aa0ebf07c56c6a8652a366bcee4831ab154b8bde9a0a7575c193dfa8748c8f07b22c73479c57eae956b148f5b8d73a23f6dd5c86e713e3eb5e4771b50fb3083c5053a939d5fe2e19f095bdbe17b476e91ac0145870b2b56d1ae256fc9115c8bda8a900822438452ab5a17f8d508a4804655651fcb653dd8f76ad0284541ef95c7cb4809b976870834bce643683c0a60936ed975a53a53389b1dfb51ff1c22909ffccd7b4684416d550c5311f1b2c32fce0daf36dab0c058b4e4b08418df82704bd3862a5fb1af5b102883e8310b5e4d34749bdf939fd5db1e29a2d3d1d5a2e370e6654da3d2f64f272c96429299c0879415b2d4093de1849cfdd4511db0401d7e26856fec38125dabf21327393fd00570611b454324a6e8316a29297b889079cb313122e4acc2578a94d33f906c93afc367a3eac94ea8bf6a87f61ea4a7d345fc3ddde6cd9fd619b60c3ca303919c2a261474c6e1b5b20833a94c2b97bec2a684e6c7cd4420eb773ab5e666368981086d3f62c82bdc8b0092c0001860d7d7adca50f2d985e95e8ddfcc1b657c810160dd8df55f7171b26778fe5b77e712898e065834d6901708246cbedfa9ea0c89cc201b42ffc14e0f18604039da3a557f6384fd7532299902d399625a691e862d51da3d5c1be6487dfc5811fd83da53b80047e0dedc5d9f5dcb8f34205b56f01d4262ad49cc2eb5e318cdad8b36cb2e9bfe196f677c1df75cd224d2453086e47b09ebe736430bc8387d11552865fbc1bf16c2f02d7d3b175890b64bbf09421f3f842ea0dd0f1138671b60d6254dfb35e16d763fb4a44f6fd13437b4dec0b32ec0f340b847f10585091c87a63ef2a5f3bf083cc6fdf97bbe60773838c136060c1e2ba81507775c2d95436433c630aeee3b8936c3219ff2163db6ad9499969357ef54300c1bbce780a40790cf890cdc6508ee046cee004f810c7db82487f59b3d3fa99b4ea7a036bea1f453fa80f9fff1a5b59910e8fb72d0182b6fe4b69e36ab4c43f681a3af5a3017a881aa70550d01532c9f1febbe87febd0969891772667cec26ff8ae2bb7b68fa28c5a23033e434ee808b8410a1ed8f9b9f81e3b4c712395e69f33afc2691e5d13511513715dbfbf85aaf52182fc57ee6f37a05b08baf4c17ca56de90d566468275d048d8e5bd7d417b70316598f09be841bdca4876e17ec00485191def3230cf13b76896af4b863105559fd262db46034f3db35746906319324ba91f4930de76c570559fe73d28f6c371358466bb248bd8da057140acb8e1811e8b2f8b7f82c805f3171cca692fedb330a4ca7a9167b1613f08028d09f04d1086492a1a99a0aaa2b2ec13112a645cb9096b823e208b81dc3ecf2251e4ed15800a66c7477951c4e3043607a3e90aca6f57f225cdd93cf626867a47a812ef4873133cf340d39b2534f93459bca290df3a4687b2677acc07667b16691ef25ede4d7aabd137dab36fe99d02b675aea3440691d68e4251ee1b152c5bc4c63aac182a6b5e1b59faf834c009b50df64ddf58563c8c5e18642cd1cf154d5e6b2c732b15ca4504b65dfb34dfd8a836aa6c3c04cc1a7141bbd62068de00287b9b6569e5a42a03fcb7a0a7823dad3f712fbe6e721363f3d97f384605fef15158585bafe8412b085f9e0e26c282a696e25ad211825c9395c46c4434176ea264698a03fb1adc32836834a7eba82acda8a28d25c61536d85056b3e0c8626ed2eb3f5f2bf5ae1f7bec4076c7ce1bb326f0b23f3d129976a8adff28ef2e94b477373dd25946a8fc78014fb045e45e71830f372502121cd27c487c7e26c4124d45329a832a059847d58661e79e273a4c504ebbc8a51aa481da5d26eb79be0fdba71457a049a63671faced14adf3edd05bfadbde67792cb6cd609e5d2f6e9e6294c6dcbabb11f21852abce48e665baa8618fd3a3b1a051dbe04373ae72a27012285aa6efaef9f98c8231b20de203ecaa0c114a34a2703e0bff59f2d9d35b55a1cc2f3cd077acf269b17917431c26f4cc2edf8700e5ab8950f9d812f161a1cd7dd1bd5b81cff03ffd46bc298b68993f5e8e3e9142fdffd081e844f65847b0b0ee4f714b52e9b6578b13629e243c53f047e4fa583ad13637e1bb76d0e6cbfa72e3648d30ee2fe932c9ded005fd401d26c2a0e7c09b5654830356e73ec674c376d70b3c5c2a7c895d7e3aa2f3a5f404c02c5a1688a94d9948956c1b32fd23d9e374c8b3f5a1761341785ad90aae0528d666724e70f000670da5389c647d2208957fd4c4e712e9f633f921d6ac04bce603ef27b2a3ae3908a7d2aaaee696cd0a3700f738262170f9a84aad426cea2f8d27c6f53776393bdfc4115d2013601d0d34614a51c67f9c49f8ad48872b6b029da3f4d83ed97094684bee123a13ae20b92c589302692378c137308986734a7ae1ca23275aa48ab8297fe99cc6ef4e8482dc05886df39efc7f5cdd24d1bc2f6a96b1985e6ec1c6275a06baa237da6d9ad2ab63009fd8e792c6c480ff3b8bd4e4198a8107a53a33816c37e2a4a3f2bdef42ff7342890a3f88a85f266a87ce429e4f50c3327559db4a6256d59fc598572980a46545fbf0abafe820abf1ff2a36e4468c33ef94386bfcca5eba1b1a0093fa5e0e919f6a0f184c4763a8d2da8679b79e8be05a7701f3a24cb1b5a69a0134eb03cd1b3762e8fffce8681ea5d30e308862245219def0b077c23d53a8acaee56a5f69bdb3bd74fb82aa7702731e591198b6d2232c13ff929e48d5d88343d9c2f1142c5e526de647a9119d3ce63eaf9b107cd7a6ee2b8b3d913620efe2aa409569c683f5d6ee85a43573af4d5d8949a5e89351112a88678df99118f25dd9ed4c417d952bc0a7d0316efd2fdc05e734e1e1e1de02de606203d5e0228068e0ee036ce1d900e571a3006430fe806ee268a93bfd17c6837b41fed0ded477fa1fdd06ff43fb41bfd436f43f3d16ae8bea7de758663a35fc4e17ccb55b440d76d04e9424e7114ecf621b753ba172136ee5cb0df94c99928442e6844fbc8225416a24e6bdd8001b7db59ef544cfed2c2c75f03d6ef3a3c17f47d84bed0d27f4ec0db563e3a770c6bd29aa4c61d9b7b1bb08fe611196ad49d8e2fe2e9398057eefa6db2fc5d0b3c21a8b8800032c561536b43b7b42b3fa809260e66d22287878352ce0f37891a4f395b5c786b9ddaea5a81c5d9dc66abd2744578aa79b7d5d3b3951214a12d33d7d60181ebb0526c5bd134b6db2e1257abc8d7ef4aad6a116dee078ad6482dc48dfd7880442d9c0443ca00034e2b620edf89303f6bff50b2a655b8f9328034a3b4961bfe57c992561a9ab301438fd604893d7e9b7cdafe53baa2b5e450185885d82b8c8b2bb4b073f99e84b959ff49a9052d149a0d0891f428f7f8ace2c6fe3e54c02e52b3e924798f0700b54cb9cd3a3c733f4fdbde3f2604532feeb4c19cb8602466e1f063a29e9d7565a5960981b6c95d7b8ca8d828a25a03aaf1dac558834239446db87f6049b86f6375b7941b5ebcbe860da63a338a629f07b9aa8a02c407429804b5dd52eb19dcc9392e5b426a95b4e762aca6f1ba260987228485b4af606a455f2b17a8c74f2cbefda54651c0df1e6e4669864d895ac87113cda3eb2c2ccedf1448c2c5162357f89b3d386d8898ca842b672db23920c83b74dc8b6265b7e690e0585cd33e0b2db534bff9366d3f0ba0b0c52b01ec6d93fc458ced37d3f7a754ff0929edd71a4a17dc8464850e027d63d1614fa474517fbc809885b7598b6cbfafac2e747d708e172ce98bb4c95fd317094def3b0cbd1f8ce330b05f60685f23bd5a9e3c064ab624cb8cfb1f220b8c4b0bc3d97ca92434c49570dd6a74dc136961d4f989011c8f1a31a11a668ed96884b1217af3abed13d0d9712e8e31061d907a201dc97ee5d7927084bef1d4b532bcb2b9dce691508e9469daef28e6b12bacdbf296243c311876e1621b5185b0c378822b4c12f6e3fa21257e892dd00122fc92509b87c1724e69e46aba997276ca540bf45401b875bbd0e7e1af87127edca9cc00e443e6f79dce13039e842be49e06b36e61c4dca41e4247e62df3fb0d0d4cb6840b86296fcd104389224881862ec123093186e3d80de0bcc6a92fd670be10b5ef9d41978576406b7a7c34ebd052a8f0158b69258a7afadc41f520d629d8e73e9ee19af1e0a5abe499bcef2f9a03506e8ebafca8e626b30aab9b5e31ccc486e88aec9bed75f8f64dd2d9d1153814617588677eedb1721e2ef5d8a1ef5fba49a11bcf58dbcaeb078db2e723ba8bcb06c9a5f7b75c2b6da09e5d202117f4431a23b0f1c41730ec49a6b745178cd7347c47bab7179dbb3847468da9e375cb789e3de86cb0e9e19eaf4443fb38a433bf21b889346e127585acf594c0310eaf90614fd2be3d7ed75a9bbc5420613146057141f7d72de41ca0d456f44acf55e73f15d387666a58d763434e4f61edab621d3ba79b6d8334ea13417dbff1ee86116b2d90b66cce9f90658272cbe596348b494d8ea95310879c97883654cb44b264ea2ad50d8e5a873c9589a4a7c43d0248f130165e1ae71a9893d307c7dcea74cbc6b60459c4a607d232e3b80dee182338ecdae8698dd4029cac73a7d9ff327ef4d88faaf730a88d647083dea37a8cdcc4ec54f73f864cde87574192ea8dfbc924c1f27fc87ab843d9d08274f36770958fd4bb3c366fd326fd5892af52a676e46d3b64401b3acac5f8435be72d15619eedb9ec1f377b522ce93d1762e2a0c6a032af1bc834f73817763699999fb7957d67d53066c1e54dc3d0711e57f8792497fbe70078f0d8cbd7703c4f9fcc0f1bec06ac203a4e37da4491c3c7561fcba6f757509e12657a9f0fe353d0172db7f221cc192afe11aa2baea6fff3101ee73771e7e87c8c1054cf27bddb1a68f4e4f214287bc60506bb6f25796171d7c40b6afc5f8167910ffc362faab91c68bd729227ce7ec4be3d536c4aa7ebe0bb14c531b2893a80ca59de840770abe97562da09fd0b60cc9d419b2dd9cb137afcec605da9c7dc0e32cc6ab595ca18c543f8cdb11c5647f83c5264a738232ab26d70251f856d2747585e156aa2986ffba9a30177c2460eeac447bd493f4251a042752d76e160944dd7886498e773787dc760dd53e6bcce37cf5fd11c14bddd4935ff4d4e7931fbd0e07b2a30cfe452452af6f28faf2f1373ef729c149978445414ce3270f9bb27daa7804debadf19af0c822cfc6f94bda5906894de42ea9bf81259769a17a2812b688b913197709b1338dcb3c104a2ced452b4904540c4db5282582f49f6f1f943981c220c38c4367e1c4ef4280272abb89ced7502de2f2f441979846db045ad9255143d2606b10fb04bd2649abbe2000559e961ae726e9bef62b1b85d8463768d67fdebea7f0b23c7530480dc2bd7749098244e75a988f042e258c7e3ee0e64834afcf625610fb28bdb9a6ff69899fe9a6906ea4d8853723e80ac58ebc51883e32f6c40d0af56edb9bb35ee0f6b2d48be59377f3e6e7fe92ee2bbd14fb4777e9f6777f41f72bb98c95dfcdbfbd12ea1559af400e7d5a3151ce41f5c709d5f71531a3a79e2b416d9e325708fa22096725df84c244c527f9be2a466cdd2e43980afcaf944d46c02e819d21ac3ff0c89c08047f46658cce3c2ec578411ba32a74352029fa3bbdb894c4cc6ddb852e16f1d2803eaf239d781fa13a979bdaf38ea165d3fcc419489252cf4e0712db4ca744071f912b1ea7ae1654f112364f1721ca007fd377d605a3b6b58cf0ceb1dccc245a19452c7efe0ae5d85837d9fb0e18d41458cb4ca004e110e7c82a83546a743b01a9e1daf1f1a8a9131dc7b9f88a97ff00797807cdc238120f9376460a53cabc56d3b284f66d6459c09662ed0361de5d5a4c66efcbbc44a19f4f86d512549df93e61ca36606bab9e9fd6f2eef15366e6b4e26a7fea145264cf7e0166ef3832c151f32bfae30c49c11f5dff0a92e818842cec4b64b90b3b892554962c9beedeb39d55fa5a7953b482ad9f2406ccc7a26f81cc873f5eaa5964d66f86eddc3514ada30f1bbfc84618b996ce048b3cf049e048d5bbd755978531fc2f097fa2309f2865526e52248266f0f1d2c53140c0684445af8fa9ab39516d0f9efe43329840e9e9b92ac470b2b0fa20f1d967df3ef3e9cb77be7df2d997cfe6a3c6fdd925b21cc6d8f8f6d557dfbef2e9c3573e7df3917d98f521079f99171920a9180ed195918e3b6ce60399442b899bd10a058d9006d06f091b149ee184599bbd8f866ccce77d630123de2855b6bcf6420609bc3e4ea0815a94532fcdc0f25e1d74ce7cc63e533a0c2910347c63a728c4c36e6c60fb6bebe5d14334202c3aa49e06a80ffb95089c060ce06ec006fb37fccc52fbad88669c196244023c1d2aba4d5ae20b0d45e80208c1776eec78e8b6250ab4c282263b1462c991dbc5aab1ebcc32c82ab1df232b4d58bc4c16797580b074c33cf44afffde9b698b8faaa5b847ed67f16d59015b7807ee17f92e8b50a9017137d4e323f2630c321a5794ec63feadaf828d19c21c6ce86f6fbca72dfc651ce6ef99f39397f4933765c6f4eda3f5e942593f23527891fcbc8de2c9af1e1d0ac517fa849a6251ac99cac3f1e1fc3d76866c30ab346fda100991634ee72b2fc786e0c5fd0100843cb1af587ca3119d1d0cac9fae3ca1db1973562cc6acaacf9f7604ca23cf9649afc44276699d1382886c986fefb42713bc111c96ef59f7d38cf8b2287896630a3e287069cb9f160d88a5c88bf3f88673ff64176f6fff8c27d85babe998fa952673a33590ca3451debe76a33aee3c2daae98cc44a121881b6b861b9862e61cfb9bc56edad38c4145ab486d86f52ae98ac65a0de0864c522970792e73d9fbb708ea6c64e790014417839bc5572ee70c3b69b083018d330e6b8987eef3b4ddf15c39f113043aecde811d651aeb29698e4ffb504c0fe3d13d8c7da6b044d5818d73bdc45fe3a94547f4faae7b7ee13641dd7c85720dbd2fa98fd11a015b5b8e3a2dafc8d9000f05f1423fc4cb7f2915050178bc23b788d72a446dbd502c1fdbc6b2410b55f96fcdc36c88ddd3e9b716b3d54ee221e37cba3972cc2fdc4f923f4dc3262ee6bfef93bbcc71780f1f80b72645b1ce733eeedc677ec1fd3809184d52c5d148572c9eb16434cdb2778ffb82b76ace90b298c8f3be368faf590eee114038aae12e9619ced791bbcd2fdc8fdf05a3c9c2b8ce9f3e37c737c7e11e0e1f7e4d2e636b7c675ca6712eb3dd8f268c5287340e6c6bf13ddbe71c737b1096b9b5b08de12d2262cf5ee727db4fde4aa486711c8d39c762dfb3dcb2a4268522dd01a2b88e6bec7e83f94174c72f922d29f2f6fae9c6cee71934b9640347c04672b3272d741c8b3cd08b8ad913e5b5637656ef16428444d39238778b8f3f40aca36a8f6f1ad999bdb5305c7e12610be62704cdcfbd5db2ac992ef6401aac1ffeaef0818fde1fbf2f34201edbd3bd45ab10b20f5fe5184abf2f43e9d085d2c69b24fc9a9e593d6120fe0178eced4984508c6830257cf47c27c8637470902cc77e468e09c6fb816ff53afcfe300178ec4ff7d2c8f8c3974506bbb847837285e8a87a5c0ac981c320c3361e2fd9e9a0b56be214a2540355718fb4cff6a95ebc4e8ca8387e32090193b4a31a56b7f107b040c6ab9c05c03443afff1a2f5bdc59b731c4de379653cb79e3fe4e3cfded7866c5c45fccb7e45659c5f3f4121883a7c8b03759a45e396087bd58cce182c693006ea9a8481a7bf498ed12f1f58c1ad95b3fb16363aa662bd799ade697d669edaedee348a1c8fd60dd83392be97789fb9e3fc2200cbbdbb7f22487fe98a2b30a1e4cddba769153a515dd45f190c3db9d4ab30be6e436ad5c78e287f616e3df225081aeedd1bd38fcd9bd78d501caa2550f1fce6b6ddc50ca612d7302ad1714f003b189c20a4cddc3d702b910a8d361f56a9891a2dcf8c1086aa41795857be95a90c1b83f27216a7826fd0078107f52893e9a1cb334f0c8ed2228c4a86aac9698d1bfe203353daaf487fe6aa02d4498973ecf8acbc4c6351f8ce5716362c0a2f0f4937668ffa672a5af257f80d99194e7c1f578bfb398c995a906cceebbc1c707b838d994995b66503616193d0d560f6d8d45169e9398b8dde22d50c98ce897243ae4f4b99c6586242f76cb484a29b54f4cc797d703035fe1801bcc40a676f1fb21996edc1dfd2946f122ed9dacf6b88a7778e2551237fd378fbbe848a4dc3b414cd09d7df2f2ef83067d32617f66683b4d1c002ffae112a1eb4d5ada90cea364bed8a2f4a0d0312476c07b0e7fa5fb4b37acd6fee77398c8ccb10273f41d6b53b489f785c046a3ef33106bcb0ebdc8419feb4aec7d9fb96102a614284667011854bc971f98ffc1bd42809afec8f7d738ca562c9be6f7c702b9850d178c3d972cc080e6ee02d4f5dff71ba3505d3720f43c521953d4742a38ee3fdea7180572d441d27127074c5cfa479731b0a75b56e31518a68172991ff30e5da09b92ff4e0e5010227f241bc1f24637b15abb3fee6135125ee133dfd1ac2e58ef667d296482bd76608807b21b0c8c9e8b2ca28c92be0f9fe5f129385ed21fee2986da8b1e2260da6ae5269c13208423ec3507472842464766c844c521bd95e9f7c1f5e77db5f395a0f1b289bde39a785e5e967deef61f35bddcabc0df4a28a10fd43a153590bf88e12fef76a882aba99b76091478d6b2c2028e1fa00ffa389f9a85577d9da592d41c956812d729ef482496a6bd24f5bd1b8826e2ee109dd1ffd367beb2e7a4cfdbe9647b57defb5dceea91d2ed9b7c7fd0786c24486c29450794e9e75d5f4490d3d6d52052e81034d509e2c1bd0cc95ad88c879918af0bc632966449a16c33b2113fb426ba09bb5f393cd406052041bb68476943cec2b0498aad8ef4e36518b59d59b2e63d716dc6cfb0a6c52479d8dcba39fad75be86196c3f987fc2082ca3e96a4ad63f65a0950fe6cc03dc6d0bd5fb12946ccb9e3d1a432df3999acc9cbac6b04ff421ed920af19db8e1aa940701d8740a69303af968f16a23cffe6680cf48b5e3ce2038fc1a25a8e748b4b542a87ce41ba932a247b2ad87aa343a05820fec59f00fdd1dbdd525108045aae03630f5120442d2fc8621bbe20162ba8da4a0836d644bab88b2bc0f213e57e21d1afb45752fe601a89ed744e04060c9945a3be2c53d66679f46f3cba8a9963ee234da598f3ac8e0294639545efa629330f9dba68a6fae01ac373fee03c83975e6530c2a7baa42375a34716583da9ddf556b89050eb3284e6d033136bedc51d809027d059c6c057ae89598d608a36d7c5653e8b14fc078cc4c23caae4ff6378b392613024d50056d77c817a7d8c5a080f8d7002ba60634c4573028012b6f70af1459051d1f34d698f3650285863e38801776cb3890e8d34013792265fa1ccb14f7f42d1f2ef1825563d2348bb85c682aba6539f59c3fe50350eb8edd5eacca5d2ebf2cc8e09ddd6b4742b329fde4f1b2a1fdd0ace22d5c6f3de88c335cad0bb726b75af123870576eee0b354331229e2fcdc41ee9833545a3c959df5a20ae48c947b576b3bf95b6563e00c834103cdc535ed89060e38409976f34a28271329a9f3ecb9c836a8c542bd71b578064777fab8fb49d81ec33066ff5edcad1a76ddcbbe1311bc591b28cdce4c5a7b32c85f3f9a3ed8b080557faa28798eb60cb88a72b8c6a96a7f226e52479852d22288cbfcf5c6825bfc30ed3865dc82bbd990a20d57ab689039c243dae8cd834450560ac703593e41c8711f810ca9f3d2a92853878386b3bba7c32bdc72ef3bd01312dcb794b388fa0a8afcab83c49cd9079fa3ca15bd9661bb3977841cf97b0d2680591a1f6faa6c0776c36a23a2a36a40385c2535751c9d2801d469432fbe565956ec044519152d6b29e7e42e319c335cf0a5c04d120ebcc99f3f465e4e5b5fe885195759c3a626307482b7da5c9fb40c3593f60e20da1c750dc1265c63c4507b72195e8eb45d498067644eb1f015520ddbb9fa46ce8e8cfa08eac0657aba4ecf244d535f2ac748c838652e95b5b91c2facbb172e22a365dbc4e2889b2a8b1cb550eb7c9253d6c2b6aa36f750a3ab8ba2861f1cd28b58edb7c0b0f5d8ba3e9de887537c7f09b6be83ea03cc3775a7634957076b43c1aea6cc62bc7961ba2c1c032559976f401f365492a3dfcec1011cfbc73aaa77a935dc334d89386a22ff3d15967c6325c68711469cc061c798a4064a78d7cd7cea28231d499ee6934a86615826d07a391d3d51f1a508e7823e411bbd9d420d87d09e79bb15f830980600152cadc78d509eae715454e32c045b1984171b51c5db95acd27d44613cc4412fa57ff5c140414296b29e449a20134885c382e9289cccbd3ee92e247dcc45747d80c733cefcf5d4a7615e86ce7566bcde6b6e794b35fe00f350c1c32a3174474e9072239374b077864a8cf879f436b5a282748af15e83b2263535a9d20adb349c878507f299510673ad3dc43e0c201cc66e4203171d0383b32ada5f98e7c88759aa0bd8d5bf503d5265d421bfbff41615f435134da99c4b2a254af0cb1df276e98f7c494dbcfcacaf888508ecf18cd5e717da850b2695ed679b6ee56fbf2ac55d5039c4c3297797a31a32373d2562e77c0d7e616179ddc2bf378d84b152ff5bb3310c877ec2310b4b2328364df4fed3788c443071acd527601159926d79329f9e37166446537719b9f4ab7363bda3f1a57afb09e6204586aa0dc8f581dc0457bba53df2f11a13735d7b198603b95ec0cb9d578ca0350ac209e2b551fc06f8415fa87d65a77145ccdd63864bd8697f22a4c89e096ebc43e092b2af51dfadf8d08783d7b5cda812a6ac7f7cce0aa6af3993d7520e2275ab52b4c201a1146425c64b349da2ed54462f0234f2d8482be59fa9448e82d9294d444518f0397dadf3a34a3261deb93deb6645f758e709b6333cbdfbf318c45120fdce8802132fbcfa65dda8390f09e93ed4c49927c950a1e0cb16be16657c1cbed9e0ed74cc144126d5f81189556edd95d4a21476dd9da9f6ee944b0f548d5ce1d6cfb25e31821738ee8e08344e7d3b3eb7780d28e39685665d144666b586a915733789fb305b2895c870cc8633004a08fd319a663800e0d4e177608f35f989e246395b3cdacba121883f1d81102a82b0657ffea965f2c0aa80a9354d6a2f162b05d31217d6ecd6796e06d123eebca7a1858672f126c2931c16331edf12f6b49c12d17c1dea1d884ff2ade1d48991735f82f85898021b8f157c26c0f03820e5cc06362030404bccf752d189b1192b21eb8ed730377e01a18eae0a129812126febcd8c60de9ac4c2d03d6d8185e332583701aaa7182415d091d53201670e42ef011ad3872bb9c329a2bb296f0925ccf771b0e2273c5d11ce79e9752354b949a17e53bdde5f2b21e8a4709c0b7cc8f4dbe39e6a6a815a7bf8d04b7142c1866f10fa7deed2b9aeb24f5280060adde25a481016cc395ece64a0b7ccd342dbf1d83ee7425e4058146d0fdbbe7449abc745b7eb3e4b3084d3f9ed4573e574a5745a04fbfaa9268ad3d7ae34988b96ab9ce81e2d27a1262f4089391f99a320ba669068facc48d120da6498d25459c742e745afa6eb6ced413f47c84b4c045ca98e523f5295d4b933d40a463970f03f7e8cd3c60df57c55d17c2cec4c1d65de4e224d68478945a77f42e2ac3ee16877ca3cce6061a1ddc4cc8a033a43e2f0546990d9ff1c1106b618fdf8b717f5d2b5f1b0073b3643bfa2e63e4857e59ce9b096f976594b898eb3b677c14d6572942dcf7d56849870f95666af433a23798f29d2612b17d730ee86006ad0c70f41b7ec5443a1449c444233322a13dfbc6396012438f036ada636f9b3df36bb66a05bf9904f0baa2483e1677ba07ca6e405f618d45baed874a512fad775199596329140b82814fb31595622bee5841667362727217c2e243414fbe5e5166b524558131d973b52257551dbd9b496c7db00bdceee61063874289fb052bec43450f629d2657d707352a0c1b1c932682d7151035caf50c4bb0ce679b411fbe46393cf5b495351f176f1a71e2c3034e8981acaac8eff9e9d1882dcc7db16c490b584d1984c90c8ba30b3f959fcbc774366382993185caf5f39ffb0ecd00daf98564a6f3108ebbc8ee90d63dcc40d40783245d3053157f6dc63ade921c5a74ba03e40d034a47a2735c4016d6a8033927a77fd91b8f09160a88508166d7b40a61891e23a042050f0b2bfa7959312c91d5218f9137770b1582d6266df1869b3b7427d01d198b89e07df400497cf8ce815dbfd55b72d3bf9ebaef5b229becbd9bc8bda54c49ca030704073307b90b7b897c91714ea8ef57c80fc86312c6f32ab957a3366509fc0ae67c610ef85d983376083c2028019ac1df01663094ff619ae987c327a45e03f8387cb060e8a03b08b691b9b2acdb12fd0b06b6c4ff70671077cb887ca9b0860206eb9bb662072224b9be0c7c7099df841e6b53d726f06bb71cf3f8bc835d4b916b1b993018ac63d74f8e852cd5efdbba5a58265c2f727d97428f20c4fc46bed4af1f8eef5588dcfa85dfccf3c9cc093dd6a2f452f78e7980efdf81becfb3c8f513fa4d8b75ba82b819fc6e5523ad164d905cdfa712f2458794c5aa7c1203926b4afb9429d5d379d648182ec4d109839879b37866a72f4fd7e09ddd392f82b5906e4de72ad7110d675999a122921123258402fa5cd0f3791dd7b13f7bf66823678bbb8389fc73b8f31de658f0ca3b66791ad8bd0f87965beccc4cd374a9b67cf779d4bac53b80d8def3efdca9bb7b34700bc78454c58276ab1a999906de0144f7e140e3e973349e6a349e6ab50ec9b548ae2d4d69da64c79634ac67b67bb6d76aa6b8330d3c8bb4c882e70a9eae7964be4478d6c888816792693395e0196bf1a645d9c573094e93399b4e66ce7cd228dcd6f3164e9693eb6f3b95d34e74b4d8434e8bdd5edbcdda4deb6c5bc76e5a9d4026ab552dab4d2d09236db0b653d57ef7dbb69bb59bd6d9b68edd3447ad740c1174b145175b8073d6b0dfb483fe485df3a1fe8ce2f677f34610647f85a80a59a728803cd9f86af0dcf511ea614a24d71f6d4898ede57788b8c92e64594304106b7b889016eb8d18505e865ece78b9f292f492e5a569e44d8ac2cdc44c51144551140a7fda54e34f2edd6a23edea23fdea9a09a4456e76671eefad6d02615e0dfd3895ccd8ca8ff386c4328fe4f0557eb42af8f361ab6cf8b3e5ff2e9b97c7f871dea4fc38b190f1a21759215d047f6a7c7d0aa44f2e346dd284d0fbb589fb176eb409f42a6f1562b1b468834296a79485be88b2e4a028fed408559e3ecb93e8b3d0a6fbf46dbc404dded347409b509e3e0d6d0a3dfd1bda3463e5e9e7d026d2d3d7a14d349efe0eada34dff9482445f3f1c2888522a4a3dcbbb608af21209b3fc0a26fd0cbcf2213ce35170e83d8cf21de630f81fe6fedeef60cf6fdbab60ed43acf23570f81f5cc362ec82698f2ed56f954c06aa99a2462d5f3fa6434cc4e55530111efc34532959a761cfc1da77f0f635b0ca7b70e72ff6fc87ef73f87b1073df61d062d0a360ef43180563fb2b78c693f0cabb609f7909ff289aa91adf82ff3f58e547b8e569e0d163fc79164ce34998e56b6077c1eea44b15d443d708c31087a3883e084baca345fa2d988716e98ff00e2dd2a7c182736891feca0c7c438bf4439806148c8098185aa40fe21b2fd86041243dcb07abdc2ed1a79f7602f397d77c28885290d540208d826c8f56895ec6579894af3f919821984948d367b260129924c8f5e787822805590d04d228c86e22bc027aed412f03af801ee4b05cdf7a1290bfec7b8d84017d7d47224f14d526fb9504b9aa1c0bb722fb0e727dcf833c0f02bd8d816790b9f35daab608fa4843d14e9cc0aa52f00e20b2fdfa298047fd6a00695f0da0f74c219e07fdfc70b0d6d30977cc6c3daa5642c2d8af2f413ff66a07983ddfc1d73e0864ed8364ee19ee2f40613b9100cd3be62c7754d2a4f54a521ca60d1ff49488265966213211493eaf7d9efa58655296353e5ab9b35e0c2c33c808099c509d20d71741ae1286c70e30777e071099fb11f4f7bb6b412f6500812ae8fb6bbfb7f72365d7e2825efb18786610963905074173e7bd06f4db670308047ada898010b967f683e88cf569e8281ab693ed7b8100b94f33c8f57b562b01e469a60c2091803e9baa7e0b49f90c670a147e56e1c3d17d1e37bb328f3deb5945224f53ce9a49935aa282c5b422d7a7b5ce219f9756f1a01dcff6a047516ead5354a7833d1dfc75b0d7c15de63ab8d3c1b683b70ed662b58369eefcd0b14f67ee402fdcbc15a21d482459be9406c872dbb68d0052f6e7c3d1a17828289d94a9e6ecd1e2c75ed6bb215967eea40301018db4081aa254a4b8a3cfc6de620b0973ede9d65c92915cbf87b09a89aa6a1328c4fb5a6bcf66b32b12a17c3cc96296353e94e44eb694937a38265c8a42a92ecce605ab3fb616b9829e4a275daa3f9fd81e17c8a4e9d24cc47999e80ab1436e11d1ec48ae3162bc0ef9f2f90f9e34b024335b9de4fa9a4de692793395bc44f15fd4b3fc09678a8bfbf97a86db4913eeac3d3c5f9f162161fcebf7122408225513063fe2ae285a2cc8d559443e36520ad0fea37db6cf67eba06ca10f316c614caf7c7e5bf93c7dd077dd07e3d4e751330e2e19f4910f902715be3e0f3a24cce76b1d69e4943c4ad99747299b34f2a53e08cf940ae1fc802cd507400aab57e34c4591ab8441f911f49eefa80bae87843bf30cfe3ceea37ce738f409c729048443ffc1e34cf1a8799ca95038532d92320ade014406bd6b713fbffd3853342f9f993f5866d7a1e4064b48220c19fb0ed8be135ec0891cec6fe18a7480b58009689aa0646cb822c31c9db71d3c67bc94c2d6031788b08716ef09ee6823cf39e78c2ccd9ffebd62438b0f72a586cffe9f3dd4028ad80a9004a982083632a30c7568717e8739720e39da244dda118e200213b29c5a6f51e8a4ed3c8fd25e71fd7da43ddd93a195ee4210a84075dd89a2286b8c0092e7dbd041479fb656cd8f26b6a604799436ddaac6082379943795fa040000a6470ab1885198ba3c3d0c3f15e1bd8fceeb3aaff33e6cc7769ed779dce73dd779dd73f45ff07087398c377777af2f9d6216d8752280b427f556d2cd4f80cc5e8b93a3b9eedfa1c275f01310fe0290a7eefddf4fa06fef4ebd002e1e777fea2e04f7dcec3c58039eb78010329eef30053c6327a4de7b1e0ff6bc0f21643c2111cf6f9e9f5a2502fe07864574efc3f3baeebdce47f7e0fbf83cef03fc3cfeb9e772be6d0d4827a9b8397610803c7d4ffffdf479fa220b36e858001759a24fe9d319ba898536cdf7bed364f805ccac693f7a2f01cf44be0f53e07aef9ef75c0cbe27d47efe46e47ee7e6c85bf879f01316f1bde7f3f8f8a4e77d7cff791fe07d1f1ff0df4f3794526a6fa345f99a27e924c9e7ecee04482bbce89ace1ebc44a764326589be0e3350651e3453a924d3af39686e43bed09653877ca13c38e181f093fcc107fa109030b04c17da431b12c633fdaf92854e4a4775a6340e3421f1a039d4a29673e70b2d5ef9a23deda6276a52bbb93a786072c79bb54fa97cd0fe0708481876982e4818fb9a48d3aea66978c6c87698305c91e7e7c47a6a43c8f3474984ac6d73aac8e0d6a7dacfd5105ca716b1bd0f6dd3b4e7ecfbd8ecd679cbbded74fc393ba5f5523b95f343dbddfde5d7dedd7313827685816c0a456e8a14d9de9f7bedb9cdb76eabdb7b5b751d6d9b64cdabd770fbeec3c1c36df3cd6de46813d5c35cb5d84a24172829cef9a760142c25578439b3de95732665920b7310896438de2be773610ef6436a91fb1a762c6c9b16fda96c868e6fe8a4e7c89727d755bd366d38f4d5d548980f8984f180efe9f0087a42cafa64a62ce9d15611fcd9f32f66e6b83087cbb9a2173d27cb3cc74fd4e5e288705d6d82f92914ce09f3e9036db5388bb448599e4359394f664eba499eaf82824b59b4086d110925e953287c922ff11b9f4a92b4e8dfb93f19a9903cbf3d47be4c1fb476b5e9087db5c9f3817b6f93a44d281324cce7f90f8f9ef00b3f057023f7eee270e7cded09f6d791cf9547e7d173be903033cf7730e689cabc849301fa832291274ae6272a852ccd9f62cef2ac82e650970aaebba86c7eedd1a6d15d5946970859e599c97d4c73ae361dd15c89c75a9c5e459e9ee4499fe67b4e9bb67f92882527274fd90e73f224114ba83d25f3dbc6c75eb991398f4c2078be44d9c764ab1e92a44dd2c96faf719cf63bc0ac85d539ee67e6421ffcb9700299b3e944e2cdab6f3e7da25aa49c02b8d79cd35cf3ea79e118830ef2c5bfb9ef6601d316e70c2353c7f239dc5916e6ece76c3a993913861738cffb98e6f0f61fce816f6851aa90b7cdeb7efefce99953040a477b45cf85e3cb09c3fd8eec8108bbc812f757ee20732f03731fc30099fb140270df2b3fc5f8094e2e06f6f0b7c87d0ae62a97c32b49be70e128442bf432bb06426f012164429f2314869ec35a18c28ec70aa0b27f8c7358c3282f3f064819143cce16e4f99bd56490f1a7f8a5ac295007000c4a679e3479d3e44f940572e06ec8a384c99a18b27c1b5206f48056bc9ecffbb89f0b7a1e9cef09455c5c26927e5f484339e99cd46b71865a1c6ff6df6ebaf0a28b2ebce8a28b2ebc78c213b678e5fa5d7b64f7205f2a8e99e9cba6b23df2635a8654cef68c17c8f274a5489a402f7f07794279617c71a191e2317cb2769e84f9f8cbef3e3c2d9ef983bb255fea7fd833a5d4dd41f01d047d89df4c58cba64dfdfc38abc8dc8703f8143693b4c9e66947bb4e426b2161b8dea34f28f7c38dc256520f08e441c9913092a6dae4f9fe781feeec5802367b5e067236f51441c248e03a161206bf12c673c1effed37dc0e762703dded8b22fb8f0781110227b9e8855d1adb15b63b768cafc667098cb6cbcf879e91d9e4fc4aac0c11452cc3a514ab772b73cbbc7037e3878de6df0783c9e3d3c7b00b10eaab907d67df73fa18af6200db4390ce6de851fd80dc799845cbfdd434d821eb6fcd48397eabb5216cbeec334c7f0dc1db6a229d7b744acbbcab7895dd26119baf7a75307f20507d20528c0a76df250f0c441220359fe3c48f3d4010ea40b497a8908890cfa480d2e1de7bae384f9963373726eb754527063577498c3e44bfd199ee7d48574a9fe9022b91a7125a40bf53dd855f2c57b094e90cc4496ea7bca87f8a23d1f8e9abfb06572c23cd6269a6b8cf6f8272227dd6ad29c121aa7823f36930c86a00f07f05d4697481bc0903e68631eb67a74234f478b1602b3882172fdfa75aae44bfd98f660cfddd6b63d2306021c1883b93fbfaae409e5ab95309f9f29a8dc6978ca74a523b5fec0bd4aafcdbedbbf4bdcb1e4caf64bae2396a341e9863b52ba22cf522ccf9ad24c9ea30a57e4a9422c4f2aae689d46385e620d41d2e4199b71c40cd78c579edff292305f6e81e5efc79624dc8fa4d88cd40cd58c559e2945e4f9dc4bc270797e0a4ac2a0c07c7e4c99c9738b128e2cd99f2f4b4b902792aae4846c4baa15507984e3a7124e29d62692aa3404692205a9a3128e9f546024559b54624d0a224d2a4390a22de1681fd32b449454f97bfb28b8468e97ac0acc4fa4192fc5da54830ba9320458ae1b187df0687e38b67cc31a33f9627f45033704bb6f2b2b8f251c92ca85991848d17e0927975c2d70916d0b39d956554752f969e5ea224a31b0ac15a8aa72bd619d912f355cb1a1fef7328f3fd456aeff7d8f0b621e3b661036134119c1296cfe40335606f41d08f77002fc464dba34c3ce1be17c328c9ab468b358694129a74d384dda04befdd2acf4fd7cfb2a5950eb1d691a70355d1279f4a8c8f63bcf73779ffe9d7bb8dacd2ea41cf6beb63af6b927a4e9aab8d64b69ba41648fda1a7d08db9230dcf398b9fbabeab77f83a846952cba1694c8f6c7102c5b169864fb2349c5028e7cb125952cb2fd894b38f2c57e7b610b33985cee79cc91f542daea274ad9da4e5843255fec8f239c6c4b38d97e01525cb768abecbf49a90179ea843f74c94da366c49c2f65e74922f71d2f84802cf93bca51201877bc71439fac68e6a445af39dd71f679d8418716fd453f75e10dcd85379474421a5af428dc3a9b692b0acebcfc79b257279574f38059855c33704709cb34f8a9c1262e0d120694ebdf90c34f2ca8ee78e3460c7dbad2faa95ffbeb1a90d1de869ffcb591b6e51b369c838616eb2c896eda169d0129a84cbf9bb3bb9b36716f00c9f4eb17f25491c82b648d499818992ac91449a694522aca022625a53366c670cc8d16db33c3151a13dee81bc5155d17d1eedf6a8d698ddeb4486fb448e9e7823b6f8cb386312dd2bfd1226d19d94d28eacd8a0b6e7d4b6fccd0c1b5b11afbc4a6ec8ab25aceedaf610acaeba2d6efeeeeeedaed92a5d6ae9b477aabfb64086202372412b9bc5adc2ef59e066ad41a721c2ae4da55cf5abdd32bfd62f3a89673479a679d52f318e1fdf07e783fbc1fde0fef87f7c3fbe1fdf07e783fbc1fe04bd2f882cd8e1946a7baba2e75471b1bcd1d6db8ec493ddb9c5c4d8b352d72b0914bc2d5b4a871af16b56e6eb6d32571c71112b7ded7dee5e50273b1f19394d11ec452e6835d5ad503f10a11f0efd3ef34528e97b477418184e9522061b8073129a709a03d7541650d0c49b30a853bbab046d22c6b2db1d0ac6271471796cbcf01f46fe8c2f292f66368766deefc512c34fbe4512c8a3b8e5823d6a8357a62e482656dc4ca1a67f31ad9b47deee525ed391796bfc5e090ebe2f2534bcc85e5d2b2b92eb096d811b7f3c01d555679c4c468c8871bcc428ae012f0882f0b8b41997ce96fae25e6e127932f5aea8e9c0c4cc917ed8330802fb2f603c87101f6c152a673d1dea5489bfabdc585459f703d8f2ef7754717960b6bc2d043b4b236cadec24cc28cb21d6529dfdddd2790fd66fb12ff2066f73e19bcabbdf7c9e0b5a8d99769ef682bbcac07aefc5165355a6591b51f695491b5a761f344d67e6461226bcfb29230dbabacc026ee0a2e3fcdd79ec7cc2e2c09735b8a90b5ef3a5e104631daa2a11988e3c2fa3a0fdc11c4c95a45c99796a13fc4449a48da8770248c35919c2045edc750933c8a2991b50fa9248c7f68c6246b0fc29130df8fa07dd2aca52039e148878995c243c346c2f86b3f5aa9ac326d693e7af1eeb43796aec8da8fa358d6441464edc7d04c64c99751658563943d2cc5e48bf633985c54fe7e6c8965cddf5a97e7c3d1923dc4c95a87b5e7b0c6c9b2f6dccb4ffeda1ec7cc5b28ca17ed7178b62111f0c15024d2dca5488bdac7b4edc07569b9b05ad4feb6eefcd185f5820d8d994ecaf8ffc0fbc1f43c2b244e9b3a779336d17799a5d316c126aebf56843c552b24135971244c977dc9a81d91ddfda5ac5cc4e44934562b72a845779cb9e26d37b835cc41438b1ec51d2b0e6cde10a7498bfec13813dcb17a0deb0c67acf22b4e9d856ef01522447ea2c14b6e330b0cee58712a4e9b9ab449dbdee7a6bdaf6c2f33b542da5457da10ad880c3c6a68436d9229a930a66cce9e5886db9c586e9bd4b46fd165f345c39c2e6d704729e3a16532dab425dfbb36a148515ad36d91caaf4da21651a46985245fe8af882e52a44fe9f44f0bbf453abb767f1096569f2ffd696def9794c8cccb6c2f3e0ba4189b1aeb7e90baee47ad5da26fbbb81e9a654a4999be683a49ea6274a022363bb298b981f186b6bdd3771e383dc2a437602aedae6a377032d3bc582ca6830eb1584c87582cc60d318bf1936f5608b693736fdce8dc1c9f1889e2838efb3a4ab85e6b273b76d32ae7497587a4d89edc51e2cc740f1ca40026337fa6be0709d34b7e45c3914171c73923eb61866a37b846886425dc1ee6f7cc94b1df8388037ce7a5cccc945991e18a03fc26f47e9615f42d5fa67ca19acbdddd3dd47949ba778c6f8daadc53b24b81942e6305d5ece4d1dd1d0cfb1dd3eed4f6a605e1cef75a434a4ff673c2b796525e17dcf9deb7823bdf4271e753771eac6aadb55677afdae649cef3649b64a59d5aabd72bea63036fa5b5d65e51296a5ab51f90dd90704354a300dd42295e5112a6bef760f65d5dcdc6409712a3d65a434a81a8fd5c0a3a104a8a8a772710cda8b47f26e1cedae944934e20e68c09c44a051609b753595dd60c9b884e92bafb3891bd16ec783ceffdd411751da5b2012d6343adfbcc3ab9fbb1d41de4a17859ab00953394563a1d85a8a81ed42e34bf1e548e098a9bb8e3d3f9543a45c10724c3516484bcee0437a5b3d33aad1677524abf8be1f547679d32bc09a8cc8f4ec04fb54865cdaf48bf7be8ff7d32782766e44e9127bb0ed71a25f156909815d7dab8ae28cb9a202e87e2c6c8b2264809dc06413ad0515c4f963541504e8325bce0aaf8604ae186b2ac596206dec412447a043725cb9a25906062091f34f7cb99b12b230b27eead028c0ba28113b92845da07f70a69275c9b658d12345307f7cbb246890da0545e9440c5854b713b59d6a87054b210cc4a30e7ab4649eea7362f584d8bfd72af74455333cc4c693ab4d3f19e1d9ad234995dd2997d33b48ce35a9e919adcffbd3cae1966831e88faf483785094508f149a92e23d531cd57259eae5c51d698a0ca7d6d3d2ab6675ab5dfd9abd60363199b7506d8342a150a88ec9706639b97f6bb55aad560b8542a1502895cebba0506896d3a8994ebd5eafd7ebf54aa552a954ca65ad5eb55aad568bd52dd7ab2b756fb5bce52d6f79eb138a2142794dd8b49931d94cbd5eafd72b954aa5522997cd721ad5339df2d7ebf57abd52a9542a957299cb62c800e59455cd158b07fa7abd5ea9f94aa552a9d4844d9b1993517fbd5eafd72b954aa552a9192b343cfc59e1c899bd715f22588be2bf4874ef54b166abe7ebf57af54b1a23efdab037562b1e44d1e2dc99e851d746a3b33aa374e674d6e2755e5c30b3c06eafbce4a72a6eb7bb91fb6dfc546daa4d157d15b387a3f8e1f872b579c2a764e9b1f65e91487edd13eefcb669d397e577b623bba900267492348656b9692ea7f21227a44fd239559baa11ae0837c44f154897fa698468b98675463b92a59fea8c28f7ad40faf4b19971e115f2b1a9342df913eb71678f72f52c3b30dd27a81a3d38c8fd1e9884a9332572bfd7a44f9f954b186f13fdc0685e405c5f8fd693dc1f96e7e463d34e31ac898b3547f9b3f253dfb096b49ab872bf723ff9d0b06d60eccb6f0943e753594e9f3375ebd36f8691caf3d9e03c82b0b9e58bf6da942f1b063ab3db6367ad4f5d3da8dbd7d91312c6bf7e6c72addf4afa04ae60b95670e5c4d5a4952bc8ca355475521461936736981317cbb5812b3f6d375dea5fc26ad272927b7bd223779dc9dd5ab8c5ea4cfb055ad29e1e5a81b4b0d7de1b435a11892a7ca0b8542948000108480a57080923fcf844f14241a24912178444184040197c9230e287272472cdaefd00906f06319089233c18cc785ad081eea5fa5e46c4a0151303d7d5c18fce9199ce0a7ba4c5198103db14389d2d3a20ab49420a3e5a1950c5cc606609246260f02a321312d160c64bb21a31624a3b5f7e33d029864c51e466800646d8c45011eb92bca210e0d42c6b40b0c58f66e61140106b1bdc962c6b40208513f7b3ac01811260dc5114905c1523f4e07eaa0072b71ccc255c192e68215c30cb9a0f0c89c1a559d67c6085800fa86a6878f024509635469420d32c6b687290c7b731826ea5062da84c7f82ab16291872dec8fcd43d7d10076c02ce4027601337c4449ab81f3284c3814c297744a6b41fbc429e40952cc5244c4aa62408224f200c66821cbbd172ee08c2626012f0d5227d70e525fa1c068b7430683168047481475aa43504592dd25781425aa4609216a9bb833620ac45fa200c8481b04cb5154d03e9833735997e99fe9db9347d9a4cd8804bda246b903092a92ad31f412739994e0e1c91c78bcaf402c9740471327dad9a469026d3559b4610060369daf47476912914f2080e510077c4a46fc1587f378305c9fd817b65ee955b765a70678c6011327d59c381551e350d5b501d1494beb02ca3592bcd636934575dfbcb39e71452552d82b9736f7695f5dfd6f51f6bac43b9bfde743d525db09964ce9c14940be924695491a55ef2a5672d3271edfce5ae769db2d2ba792aabc57e3077d4bbaf5b3b9079741e39d7846105b97f56ce4531f7029bb82e0983929b7ed7245deaef0a6bb17b065dc9a1bd165a60cad4b0be10329c1451c834dc3277847b71aed9f53c722e1559f5412749630152d93ef89d7b5deaf6fb0ca3dc79f7eebbd16b95ef7d4c733305955740e5cebd0ecb4c3b5f18d772ac164b455ab4cf6a955425212dda9752cad2ab45cb795f1877fbc2b85a09a7c4449e463fae3093ed0a4364fb297191ed8fb6b050b8a330b20d5b3690ed7b985cedb58fb964c9bebd313f955825562c5b708a6cbf059584e9bcbd2a0d8986af65d92fb1da54c47e4b966bad25ae8683e5fac9b55443a150280402815754963ca9c0bc1e215d5e5f1c179bb8a8c4cd1476410c95a21900000000b314000028140c8844a391581c8f035d991f14800d7ea23a745a9d8ac31cca611443c8106308000000006000464646a2008e230e90ce6020521e1d441c93c903feaad4344104290cef06c190a646f319881fd14c2dc2e9caa4b93db06fae30edab91a898486329efdec880476fe1125c0087021f9dc874e9e6f8ab7ae148bcdd9c8c6b18dc06c4b898744767b21ab7def8dc6e6bf3618889e137e17dcf78fd55e8179e05ab6fdb249a4a5cfa3eadb4e130697f00e3c4473339f8fc7f9312a3da34539a40a112c4375360dff72369a28b69db7e4bf814423b5479409f066dfb9752506029770c63270d95336882337a18bff87cca26838d4a841a4fadb7cf76b1f748aaf220e060ceea770735d64cc904850d1481f4f93765786a993125ca9d5b1776a6a31f4a1f31a6370ee79a50269377a6a5d8fbc89d2e08a3e5f238accca0d4cf074741c58a433f05082c90f2f2e50b32b9b511799f650549acc4f710de5c247c904d6ef30e6f47575b20476ba5240ff82c1445a83ba451a47931a94978a757107bb3a56addcdf61d7e0182010dd010d2b3f4f463850cd24a5c4a66284e3843d17808109cac53a9efb6333528f499fde171936494727be68cb41539f76b5b1b70108daa7b8d9d93a3b9c4a04a12393b4ededdd73a898aaf5b264067a8710a36f33e33806a5f385fc4464c97d02440b60ce6091dd05bbaed214cc364665351e4ceeda7efa411e5aeab06661826c1fa1c205e428518580b60527d451ec8427692f330eaaf03fce2099824d559caadb8f3e03895f27a30441f869b970880dc6ef3afa37a7fce7417a1132f866602b06327079b91f84fd9a7bdd6e26f382acbe7ca588917a986047d7ae81494d358e39c4ab84eabbbd27f8cfd516dc7aeba0322c409d1192dd59d54b1d2a70ea22ca2fbfc40eb853d38c91d12604a66d5ab0e527efdf9462ab44a1f74c9fa3f4ebee12ee8be985e4dc1beaec038c93a8098e8855d64b76d194a81189d4ab90a4ecf97ec1584b9765c677b4f940cdcd79416801f509680f1ad0112c050331567f6d49128b7c1295f8bca227946a0916b9453d0f14071a323fca47b936deb7db13418b902e804f2aadc9c63b4a02ca3107d738daab30586d21e7238d929c8f1cf2db205d2d4d30ffd60658262223c35046679887f49a0ed565b708a100c1936d75ef27940e097981fe5a51a037b8cbb2c4a89713f0685f583ac79fa824d70a00eb604e448cf8cffdda095c4c65f237648162809504cc43212068617975a88fc4a449be0f44898acce7f9580fa472fb7152b7b3318005965c87c4098009c2c57f08dfd02dd6ed7aec128a425f040b00a0615f5f9328989017de7675b99e07977fc12c4240971f90af7c54ae228cb0f0f7eb516442ad8fed156d5fda77164b5e7cfb55d1058602f4e111f365a72d7abc78b19c1489f1992e8bd2d87aa739cc5b0a573f9aaa3849fdaf465ee455e7c6247c1270dff67c57295958f4ec227b822de5ffa531c8a8d336cd93f6e745a23274bc6e00f20823fd192e334c4930da59ca17d3ab3b93a772d59c1a36a700f568854d83378c3d4b24336ac446eb3fe43a7575ae5a8e5e53b8abf3181548245d5240949c9559f36afd0c8c0b6e9e706e23fd99833453b6222a98b1d40d3a79a02d52830d804d11963cc148f20b46419506ff4b80c963c2d2fcb490734b2eb0b5a8d632245d02d11a033855593b4c2112c3302b5b53ef9d610ebd5b4d1792f096cb3e29c4ec5b9d447a0d225b793e88e6f54533843dff004cc0a2e7a0f280bffd0e4fd1688c419e632c64a87f636bddef489aac92439d3abf3a72d60c5d384817ea119341441f6ab11915af458a693a8a5cd0ff08302e786f7700283de518bee06c819faadf450d93839f2b3bbd45202263414d795757195afe0d82d66497f2f3ae801d90922722fa17534b6789424a31278e89e6a566c44cc3ebf8a7a1b70b9117606449f416b1dbcc6a1231a347ec8667b7630352160f5657aec34028a22b54eb20d3aed0efa515c24003b6f47509d123c42423f6a28cff1c3672ec83429fdc6b00bf6355558f1bc48c482456f69f22a29c88e26b9f3f11aa722b4283b5c06f605b076ff80b937f6c7190c460a6c4403101f552eac0e310f251b1e49bb0b70f1492093c739e0bc7163112440422f70eed93aa429e7b32b0b334c244cf3dc791d3df4c170bcbc029144b9222f38db9a1edaa9508713de03b4ad9a72701125722656a290a57260cbc19a4caf3e7321d8fa078061ff5cbc32ddae6cbbf69a01f224d6daf02e504ed9c5622572a547a39791a72142805e68ad2af350c2a8288681af8e7e5e74276ba6f344db09b99dd0eb8b046f938d3ddf9da64b3770cdabeaa8d4c3c6434047eefe5323711ca184a14e29e26a82a8957d6dfe0292fe0c0cb65a9d815e1f729c1aee13a6e7d019641073b3a62531f923cd31d456fe33fd8a8ae7a1829222a624006af15e837873abc8fb4186f85de4d48533ae0fa508012814448d0ec819fcc734d6240d03739a43df2082e81c22226a462f007c89092127c0c4163084e654330d4a500830042a0bf827d1edf30c3a95e32d0d8ee9e46a84a143472f509cbcd8028a41ee44951c8a61a40e82a5f746eb43c529388f71b869a4fa55825228814adb09b87d82388a87b06944ea1abdc91e75c441bc9041ffa6c43f6898401a595a64f662b67960f8e12b29a606ecd695875f19124eb691aff3cd7a1a98925116813185a5341944e1054af1302607731fbff6dab30c4a491a5b377557d09ce368d2c04d3a2451eb4c55158630f42d241b11e7b06112905b3a98c6067da399630fbcc7f779019dc5d027f111456e53c17660ece88e8d48a8cd7e42f4839141638f88812ad00a94099467ed7bb5683898886b8b1dd06f68abfd2ee7e4ae6b7204a08ab116703c7792737d6805050081c5f64ac125624d63c1695eb98a9c0c524dad006349830998bc3592151c852555528b26b53e02cc5fc48248c875dc9687317f9347104521d02100fc24c524fbdfe889354ef01aa51f4aa544a5289546f143ed7433c05da5e885715ba953ac10f18693a71877993c26ce9d7000ce4e99e87c8dbed825d694286269c62194c373db7e88460af1555d6071cbb9a90813f76131a622097ac9800c070b6f0808424ec0c5bc8864477ce162c4c8d5389d9597f8739542d00e7ab30777813c96bbb3f093bc4dc3f1745d1b81a421c7a5c55d407872f8ca9d4f8d902e6c2ddc25269973e38dd40fb5d3cc829b8da41fa5511225aa9cdbcbed1983e9c4b6bfd9dfcb2086178ab038a5432877a8c9211942a112f99d41b85b066cc39f7cf9344b4fe83c6c0d8fbd7d000b9b34fc78c27c4e4674dee4ef4d2bd20460a9707dc7f92963880024b1022f1fe965ee6bc7b816156a45b769e1bcfa182f3e80c66db3b92463da73eeb1252e882da7813f715d19a9eeb6a8cc4eafb0a38ab40313c3f62330275f8e4e1f945229982045769f417a12a5661bc4814c00d655f88a5aa5fa9c5778fbf7ce0e863b98f53500bb925238af8be885f809e71d7c0c1d9872d02d3c7072394dedc4adac939f957e8327f2b9531ef230c1c261e43f45926390c8fe5818321ee30bfc1827006744eafcd46f929f6e6385709a4956934737030036e51988884c0ffe1e0d0a8a502bc109d01444c0967da791c5649aa9baaec639c2994d0e0dbda74ea8c64264415057458b0cc9c5a8b51ee5c134172f17a1fe56b24e1895f12122c0bf6661c864f7c0d84d985a6c280f38b7efb6d3b87acb4e513449ed4434344c67b236d184480bdd122481a2c81915389d6314282eb2a59a0e24d0b11d52a05e1ac45eb5d83b551467a51774137a430a46e4bfd4266832f9961218e02a2d120a11b040928126e82826bfe69eaa02b61fef94270e8524aa66a39738e5108c92c327ae188250fe1f1cad5a49c60b0ba61d465c100cf44eacc187a740ce7ed9481753b1a1a653e6cc6de1bdcb5502e48db947479b7dcc85c17f02bbbca4c69b59bf4e3b9341e0f0752880951c1827cd66195e9fb72c978eb5f2e24b05eb9c6efb313551f23d5b4d382c396e33514e023973380fe00ddfa0c91510038a1e0321e0c3efbe9a59b72099d0890397b27f1dce72416586e1171f697b6a8b9aaaa0f0c3cfe0a7acd95b75feb4a81b74fde2fef17b0acd7c92a5d7ac6fd4626bd70d01677cee8c2258e408dd038225ea477c163d2dbf5abbd9264b7e0a51b1570de4e376d2b26c2167cf2321494b9b4da83a65b9facf032283fefe8085df5a19f686ef8af4ac04d08d667515f10cee084bd7a9ae9fa44a581318f18de98bf123eebecef3935bac1bf57eb3b3407ebae625b392f798bfbe361511f671c735c1355b0b690e65744e98d7455954f7c976bc2a998b25c426b4c7f6751bff1fadd42545452e60d5af31d961e6ec9195c429cf7557cceaec3baf028ccf36778d97f06ae447649bfa3087631e4ead15ae67b4e322619135caa87b84fe37014852952f98818c2e0779091dea0dc8646dc249fdc36adc66931b05ed02254743e8015c7972f30fbf2ed4ab972561b67938f8b2d8a170497169d3baf8fa18bbae56b105522c207a7720f0dd38bf3f05e6e8bde71b40bbb66a0e704a7e81925dec0b2f3f84a34e1110b3cdd0199c63788e229315651bb419ce7b47b95612cb3b2e3b4507873aefeeb4e880a16111c12e69b4d0b45aeb415fdba13c30bfc937550ac00ba59ded28d8ddbda3e66dc60667706ef7af59c3987d9d28ce174d7bf694baa8a85306c69f908694d6a17ccb25ba56065a46f161658dbacaa43cc40b2888cb840b1c1a9a4c8177b6ac57ba6e1a58dd589828e9e99a344ca7037586abb98e4e3259fe612d8ed4beae3846c589fee0414b6461b7d332f193dc8860bf9962ec2d7f7431ab7a031d6dd649602a516f5da061c71ea15a89e23aa1b8465b10d575863a8ab22cd23564e07c5c41b846a3e1bc898272e134528fa491053fe1d538bc7c1c250f79c45a11e30a42b8b6ad88754d438e88b59451ddf227c62454ca9addf1fb27c6e7054351729423626572267d49285d2f6b115ce38839aad3b35255be4b197c99fe30908bdab84815ec980fa8cabd786fe9c00f9dcc5dbcacb49195aad6fecdeaa2ea16bfda62f20e6a0a9aea463adee5f3ac49d874c3d9effdfe5c44b56f0f3bfb1586730f99ec661baeb7d8d330bca494e396ad5bb982357d0ffd2e38bae872d13844230dad4057eecbe9dead92a6538ef391ad8359a3d457852521e8f71bf1b8a8f1e311ae5b75a871db94f3c9731e08d18a37323e8208e0cc5558e0c31cb11256a021666f728daf56e30a4e6b8404ff6656806f39a1f0cfb175d031d61f9945490e3e6305b2a649a551789e2602251e11935bb6f2079ce1afbc8bd36dc58982d4a6a403bd5c6fe37f89ce19a220c98e83c0795c6a2fa1abb386250935c0c9e9f7059e274f14dc88426ed0ceee526fd2c001cf225b341b5470e4add53c10b64710dd36154293034693d582a26a5bef609ab43c73249126e98253e9d68d3c0259946dca76d06722582a8950bb2b45daddacf44862482c05ce45050eed91172038488aa9089a3a0d7f72c3e8e02ecab4ad6a50ec9a48a8269ffa90e187f2cfd600e8baf272d8a49a27b211f8ca31168901aedf9af40f7defc54a5f4aeab6e9508071e3624050d016ad6528c1e36d38ab2bd3dc4a0c81d9bb16411c62625fb78bc25d0398c7aa9bd83582fd2846adea736077a31844d283c25ba703d94c95d9ad08a8b5873540dbd1597aae301c846cc4f48087e3d6923a60e8f2a68e129a3dd4e23318fc58104f260176b1c970d8d62a4932d8de76c7dbf09f146ef382a8572e0df2d586869a29d39ef8538a0d529b4b6856c59e80f64c0f8d6ffe981e950927313fa2f799052d8ee7c8485f4683d00e8dac7cc93a0b020ac1cf3cfef3b04ace0ae434bbaa5023d8c2718dc7b54c25060325dc2664d07b9f4ac23e06d234a4e99db4be8b420dc1cadf969a7b9910befd36e957a1be84fc30858500fd9d933584a82a0f91a2284b78001cb8c0f7364fe1775766029ed08b6e5c49389b5663e79a96479f3b2ccb53f3242850202e3ff0bbd7b7e02dbf672167bbbf62ffbc3f9b6acdc75abd7084167cb2ee581fc9553b5cadd91e0ffed827e9ad0b8755d1caa0e11b7f1d069b6c9a96af168cd7d2051c03e4df00444f96151b1eae20450d3e9cae26add55672ce0ba12e6d2714787d951cab24422210148daa88e491bb6abb1bc04d728404e26c790f24a9e137193c975d4328f81abe3718a2f17da21b108195c3e3906c28cba8e4bb3674639074746f0bcd7d65342eaa0db1bce5c6ed6e24a75b4ef4971b7d857e02f30b6cef97468b86887489fc21290a5f4a998435a6ade4c7a19c77d058c3d4cbbb9d0edcff524f783617291acd6d516fb5def11f5c62d62508d3614dbcd3d7fe4eba3b78041b0142198a1c1231f67139477a23feae85468a60255ee1d618dcc5c6999094b5d44c3521d86bd9ac5070e09ee24be58c7ade5fa667902382dc19dc401fdbcb2b05f3aade384838f5b962226af4c4524a886cf6b4ba04475e5de13685ca35cf8287f98145d5acc4ff290ab64d7295fabef27843a91e2868abe8c1d02d9053f14c42442319fcc7395a082b8b5f58b950b1483b2e221e1a39956fe1d87757ae5bdd65cd4ca5963b36770e78ce001f2f00842767f562d158d00fc7e240a649c4e134c389d609884c97a7b74493cdb272ac9d6c2abf464d479ab8caa1f487d27f686d1de0d55de6751b0ec59cf47df6e07ab0d8fb6bc958360b80e885e523c6a4a4c11993b8456c67e41e2180f6e7cb3bda9bfe1aa2d3baed49edd1e00c146ec8e43eb266cd0148f65d01fa833d8c40b5dc3ee751e722aea563fa9a7b852708b4a0e6be0ecdef6d82dc71dfee344a19b892d2cda8038d3861b29259ed6d7aa03b3c05902c3f10de60439c2142c80615fc5593b0134a06a0d3cdcdf2fed9b2885579aa12760ce58f24243c1825062600685a262e48b5c19156ec82378cd2eba9c8bca64397b5fac263ae24ca60cddf5c7b4521c353ac0d69efc9835b4b47296fb592a0b94449ac6c3f495a560a1b830c4b9fad6690b856d859e613b22a708359d58b5703ea2a2cf58997e86bdf703ef434c3ea71ad111a41600b339f8a4751f1a16e7d5085e80a448dddeb3b62fbcd9d00bf1671c7a408c7a3aadbb1bf169b54bc0f6008b088c92ce58f346992c00827d9436fae1f61906f2c8e54cb076a1ce1766f5382a8127a621a6acb888972d316ecc3dde466f64fe93fe1aa9fafe35eab6d8ecf47720124627f37788e31e858b3f173224e720a6daee8f98a70bac8ba30f734bc7b1a444af0ab5f513ee14395535cb5215338e8999d68068cfac4fbd55496048c8eeffdfe19d687eddf53aa1ebab027971ee86b5c018ac5058449d90178c0a05722497321b2cc23d5bd238fb5dd0187813218ebf5cc5671d5f90f6318a61996f456246741a9f9096995c562835bd24a8da832bce38b7807ca0fbcce10cc481114714b28a228152587dc77011abf1289f4513e92428a4c74ef8c33b0ffd86f42016f9ec21eeb56c0ce6d6682c16ab57422fb92e9f08e5b687160d95002b790983c59ce506b9109505c7addb8e66c6f98f8b3cd3021bb12a6a33a03236d18dee2294c79497ee576dc1d1809b3c0229590309c33c59c2157511744a015918e9bac307fcd09f66e298fdca410f6987a3813fed3ee3b0e12886e86f647e06530a737a4dd40ef3014cd80ed6024a55bd54ebf4682ace04d87c3bbc1c86495744431bfe85da491b40f7aa63f6923e5c398f085a2661d533b520549639c35a9744428972e57b822c3c7b05c734b7dbf078d6971121a91b4fbfabf5d99ebcc7ba455526b61874c4a58e0a750dbe57a05366d0a0dd0980c9727bb475b020bb39224e66f58560b7fb2b79bfce3afcd9a358c93e31f01dbbc8844c8a0c32ae97a879ecef5b8d62b3bff266b2d2fe2c44384e0edbc1fc57a3aded5d1de12ed7363157af34b332b531e9c22a9bedc3ebc52f870af45e999f588e207db08f3bb12fdabb210fc6162658649845d8b7173dab6b41c4d050514c8443d1a239284ca53c8be57a1f23e6ae66c20a2c262a0fa38e5edece3c8b4d1918920b40f39d02615752c8051a378437c12be4257018fc9c1a6edc63fc6cc73f00ebdc416f8c2b9d4eb552412b658923286f37c422153fc121de577d20789225b6a1689ef2b677d6284213ae1d0ecff991552a68c484e6c9ba7cb80193f8ce8ae6b9c7dd5cc8195287a311e2931c17f535ff247cdf3a012ff9919003148454af80186e5aa791917053d363c83f83db6d73ed23047daff3b01d6532c38c26919aa7df85c40bc6bf03c0e9c769e6ba9c4c5d4196e2697181653861a204f076b5e2a465b52c41d131c8d5d48183bc61dd0ade2d51d272f8536146d1a381b5ca665622a4be173793d3d716162489b507bd5a200bbb8182e2937bf1517d4d3c5e8056db3e40585e71d19a6ee0d5b5d40649c0f0ac107559d5f0a75ffff2fa410738e86a8f24118f0a81cd73e95f84989854bd8c1bbf090ad77946885423352ce235c6254ef03a6865d6587f45c6ca746da201a7a5394fdc35c13bedc33bad1c1adc20af3b2dc49f24d848ca47599f332ee470fc87ca261f3c065a669b77c9db2a6147ece1ea2c7fd45d6eecea5a238711bf907b1d4c59ad480fda2080b91f85950a88b6c8cc3fd78e4a4aadf1e8047c40f4c60e12ee71ef040299e7c1238c185a23cb4d33d36dbf769da539225bff3406d52f63c28efec221f2a24f68950aa046f88b27795c52f68a5b7617d4c922f9ba467be435caad5dac023742d8090003e5b34dc13949d31f1224e95978a412757cd8c62d2acb78181cdad58a69a8395f18a30d80337d259bbbd3cbca6b35233360b7af55acbba99afbb1e422a8ab277d78f99cd1238804506b27811eab3f54dabc40d120bc934278c76ea5dbb9f857ea473c897413d8d06031c1792c10f80965898198d42019b8ba9363c1709e6e46c2c971f99bf37f890381c2dda2a2a3084bc1548dfdb10f19434896fb4687e7823eb78e73a7b0dbd064bd86ed6001f7fc4b592026460dc54ec27a262d581668e1e6bc4f61ea0d633fa7d5b18388645ef179078fcf5fc626058295c7389754d0330cde02af825d604420f5ac8ca70cc9ee37d129f72cac7a6de9928e281aefd075a3b05fb24d34d4daaae49d49b5b0907f4c989f1f9f35d547e7fb80108bd272c84e1cc4ba05a4f36bd5c8e761457cfc9919df30f07fc33e3e97a67080f17c3ed7791f72ad96dc27dbbab85d8645794e5cbcedaf66fe57045d2c11ae768939f1db9c93f6c762a69e9fa445a86da198a1504bd555bf9e929b88b33079dd4d1f78189eca1cf9895f84d4384af44b088ae6b30bc1229227918846ea86f6faf06aa55d5c1c0bf4972f9152b256f039c64046a9802f4fa6791453cade72c5f04fb960ccff958955c9a2b212b41027462998efb9cd58c1db31aa2b35c801d89a871f43a5ca64a182b168a19870586f650c97d947e62024d9692df0b0d08278e87b6a2e56608f7e5f241b27ef0ff7f29b3e620fe8b7432abfef3ab7d39e90eacfb8340a483da27a772daff7e802cf7eb8e836b49b7cef93fa4ad19d7d6a80cd2a955d5059926c4b6ca7c9a8a40d0dfa3f3f6f96cfefd9445941ad75184aa15f0c83b441a916bbf10725d5ff8f7d7a1793787bea983c4bdd87f6975b0d29453e71c581a3fbe367f47cd3ef1dadea01117b5cffd0409abf01bdb50b320eaab51e812367fa7ad4c9d36eb3d081c613f115cdaf9161b63b6ad745b23365db5d7db4f351ee948991ab88fe7b5be007f014d3887d24f26360cd2efb526adc5f93e39f2b38430e71badf8d03ba4bbf455e780300fe59b48b300124ddcda6af5607baa5c41d305c5d10a8311ed83790a21d77a04422fa4506408c9c39dd5df6cd20fbeed885b995c05e37d720c013a0c045bdab2367d18749d876575218a56bd4f25a0344f367a9ab3a2f6bfce068486be507da59448be9ad1d96c84656062f0c10865ab5c8963d67d485251142448003bdb1ad945b0522b4a94325087e26e2ac2fc9b040b5fdf088938d7cb7a10e8cb218a66a1a60a2ceb0229f26147e97fd5260ddbc14a408665f19f9204a1e2cf1391726ecd19a9d201e623a371dad33a8a8cf4708b4965a6858dd115c37d9742caf80df68dab0a696024967a0f87e6f0450f54cdaa43327453b0404ac6ec0f37e35fb9ed195e3f56da36aab131b1c5208325816f8a80056f33b4be158030f897b2d58915f152fd651eac9683cd8f009724df13237ef13b8e82691bb7adfd30107cbc610a9413e59f985020cd6e5da27768d17a9d2e1b43a9a8fae41cfee410541a6d05caa5c2974e246978a6d686019906c1827cb404bc5605ef1c6b5cf3a231424d1c0154e1f51eed506608dc4e2d889b912a06abd97c07e0c1de5185b26b389e18af081d137dc2387f933e52c23ed933e51dcbd0cc0e4ebe0ab1b47a051f5838fab25d122077f2697f77991b0d60eaa0cd6cd3ae0b04d9989ee89a1bb1d98b6c803224fc21a01e9e582ebc509fccd1299b131938623311965f3daaa159b188bc1537497b1468bd9ce74eddc7c94d286473781a43deca05ec5c7b0e7453aec7d114d2e01746870a3616c56d3968b676b5ebb01aac684e30c7815d726aa0e5cbbafc3de7e40ee6d27c6493e0bf5b7936ab606cb6869fc307b74aa950eb5664cfbad2f66f7fcbc79844b85f360ef66a10bd21526cdef17e2d172cf4d1d1afb3e984b95b63db7e9b44f52f8822a15ee3a693d4e5ef304855f04e1731bf11bd1a30b7c106e11af65524e8b246c9a0e7cc680b97b8fd2618ea63e218bc597d114c041d244c1d753ed8f35204f5668a35f7b48da16ceb5423ec72138c6d421a1cd2837d62a08a0950017198111e7b5d29d1d4cca4f325b719453c9e0d545c5d7eb1f87a0ce303b883922722bb75125f714570b0377bd11c2db27df21182cd04151fbf7597b9f373c17b2b745fe3b0013d1c73c2ebc139ff6d8f1c09a8798e1c57687395454fffa46cbc3509e5d874b7b2cae3dbe5f7426510c60c1cb09ecf1fcb438baa535d0c29521d80d2265ece2bbc61b1bba0d46a95ce6a16bd0ecc17626d7416f531ecbae816311257b18c83877da3a774d29fca11eaad8733b9925145b0fcf44f489d2c4d7e18347972884fe1ad6bdca69205490dc9474d77fb287a4feafd1c5562e3679c2a8f8df537557188a13b55ef2739558aaf66a4921206a355bb5a7a484c1cccce531426db314bfd0f6f76df1328d46af63af546cca640554d0cd888b2c686e15333df5b9b34ff69f0bd8377ab64deb26e39ba62b4ccba07b29c295adb4ff1c09f534886f994541ea0564b48aa4abdf142ea5cf2301f231cc55bcab621bb3454bd9bb54f7fadb5049ba678de3a17f5dd7bf575c6cd2f1b83bba569f289d678a20fbd3963b7bf9ec7f8840cffcedc136c9272075027d0aeffcdce4ad52707680e31d594aedd1dd8f215075879289ba2a5c5b11a07062a65531ac05f9598f3248bc81b9214380cb2c1a9fde6f8e1454dd73e06c2fb4fdc6f6b9c3db26192bb794d3bc9446f26ae01702ce8fd518b2bce03c0fa2ace70a659cd12f8c76e7eeb8bf13c14cb39cad95e0053b06dd06e6aaf44185a307294f5f29e6de5dd3dfcd6f8020a4ec637f1f5661102bb9d58925e01e3b819b6fcff59ec242c35b28d777f21643a2be1d0f6a7e9b6a3253fd2d12d86c4b1c8c672d981629bf3f994aaaa9e8162014ad1fa486efcc599c199a4c32385f37cd008abaf06e08548141f84fd34d9fc746b36da67e5fe7cfb6748eccd60413fe72cffcd313252cafbe9e152f5e06fe15edff3150a09ebfd41f7d6ed53091a5b8daed35a477b4b9fdaf9f8e33810849e9840a7356a434c8714b3e5e9eaaa5c862e16b8a3020a01dcb005531e6545042e55347fa7c6ba89e650c4540057c76b44b52ceaa5e9b8f9c01110ac5143a967936e43bc31dbea1992147d47bc14ead730422778c327667408d88dfe716270b297a65fa0d20289d851657bf20f0e950293e26e63592d65040ed5d25c4a6419c1c1e047c192e470d0f5374dc7a466c53ce2108e35cc2d4a76dfa0f58934be86e6ac1cadac65428d7e00820bec1580f747955cd4128a461f63c101636a5fb90ffcd2e84b7fa8b6db2ed847ab07f38b69c79042db78a9fb748a1f468c421f3767dc6dc2e24761eb54c465d6f61b03153584adda01a4410b4271d195ba95ca308490747a8af3f0b65a140ee4c680b55313c4da4614142c3972774e3e9a2ee7a7454346283c99a9bdc462830f089d24a06fb0ec2bb682673b790503ad55c1f8567af4644a90ab8c7c01c2331886dc8e855f447846dc2a42dc0af90b4ce32e34dc58b46548738143196c9ea1989bd41eb871a6a3f6f0acf5c20e5429d4a21d78148e4ed797b4087b5ae5aa95cf984a441849db6a202c5c44b4d867d4a34ae786a04cceb87eedf57fecf008ae9b7e2a6cf0711a21dcb7dceca51f825b41d357d53e77a9e82cf00b1f8f09adc5a7a1fcda6ab059a36e5016f2f2c0838f7d5feaeafa0070e1370edc97dacf6d37388e5db6352d6a674c6f986c2a2776c921ef7f0cf0e361c467c99343491d2987289ae3aac6592d357ba3960863b2b8e4214dc83d4a90708a5b522e4bf62c32ea51f58c0779151059281a736c223c82bf14034c96b06b186cbb7c0774d74aab4e701fe5ed3738a7566013ad07f2ee2b71a80c7ce611629120b52534137817c816f8c862e11dcbf2a636732b972db591eed4d48e65f9cb80627cdbce9a74996db5c0d1fca86cb514f04392a2d06f239a131fcb7289e894e0f63e9f2a35935f66356711a4af9a62b7cd6dae2341e2906454c944ea6c94eadd16c1b38e8c0b81278661163896908792b4b8e4732a7c375c67c92769c2704f9c3106f4a31b16f3a49045057bf4f9c6d7cbc313c0449f13df05d0ffc33a62d7544df4b085859570cdc488803bd6cbe4ab705025fb15a3cfabd1d3317c18ecd7bb30819b4949bd498a688ce29fa0c554ece8772f1f0fcc62fa4e50df6b698f141b64ddfe42f3c700a50a7689ded93111f299456ddf00c0e09f8df1a038c9172821b9c8acc3bad85e609e1e1049205592fc4f349e3440925a005be4ae1383d870ab09dc360973908d895f1c17ea394088a2c8631ac73493a93d891792f6bf8dec7396ff8a2acfac5090249d1641208212f2790a4d7637d9f410d0246c2cfa798181a32d145883ba888737e70dd12000c2eec936ddc4d2162742e2f3fe0ed32bab9a4a2201c631f39c116845d366344496db8f3486ddfb61d5264e9cb15142460b52db613ba538d0c642eef8f6677438dcf6d20f5d72eb7d203ad406160fa5c1eb899a9a02c135f7c58caf8a13516dab27e3217c15a334acea9672e986fd04c1170283e03f9c4b0ecd4e574cafc02b1514b2cd979988b71e9e7d19e11f226ce9ca5d0f982feb57d980948f37feb05be71f800d87a6fc69d950a18de7caef42455ad4bd0d8de0117bf5b1d0a8db06b942324a845236b478577b47d9ddd5a6c11e7e43f1ab825d9287d36e0de1a99d8f1f5a5346067a942e9b961fcee3dded8277ac71a80dfa58216f80cfd5d04a50cf878c6cbafc1af8f954a1d304c6d5d06ecda07de0bb7c8b58d8d1a65f162a043eaa0a61ff595a225420fa04dc328375151ab45d54eae6f7ababb56723e4e272c2eae3332c4cfbe86e11ee02adce34aa903a418aa2e23c78366f283ee6c85579efb9e170c6fcbcdb81fe8fb86cfaa6900cd2145fbb77523e8f32d47a23809c701ac1eb32354c98ed48e53b2c80109cda3a35a9f0a6d249d84ff9e8a6d2aac6a77c57f2538e529da9971e895da8b6fb268065ff3b3e0fc92cafc2b886f7d9ed0f05179910fcb603aa0274df2cce2b492c1f4a1c0aa41d44ffb74c100197a9af77073f0d59cbd3d4f0805f65c5749302273a7940036d50b023debcc316f8d2d800df7402763610fc1381b618c4c8de65d8583d4ea9ea590e9d0153d44b4404a3f0ae96c14389d0d48cc428bc921a0223763e858537baabfade6a1b38c5e351043aef2cb7f9f751423b6e224d77ffd78bdd20c2b458a4f5c849fe59e4515cb530ac96b461f327ca247d5deee7987f9077d9bf9bcc0ecdafb15e7c073506e13c721e69c4b3207f4545e67e608a867acc31bc2b9c938a69bde9c9c158a343c5e30222f8def07a8d33a4e0be742c5486d315f702f2347ec200af763949c6089f87b4baaa9ea83332e44d7dc6b599625d3a652fa62d4d180e64652948be4205f4a281ff487936a8246a11699b2ae68e6ed1d253f85933240f8a7053a5d4051bbe2138d68d0eabb7338a2e0abfde78c64d75e67470825bb27551c655d87d722c342b332de28f00a37094c78b17bb1a8f0fdc7f78d9b5e7d582419a242245a974e26153cff53fde3424ff6e1056343abcde689f3b53b34536a525a2da5a76477fd9b919cfe330e561c6d92d66e66dac5231c959921d460f96dab3ffdf0a5145af066124976f5628a91fb049c7bfdadb85eb5b6422f5f146470127f1991c56b10391ff69f29edc5a861110bae08f635205ce835dbe1ce711ee01332dafe9e63f9f493b93cc910412b7b611365a00000788ce2ec126ec4d9bb7232fc00252ef25f0b9a3686fb22171c00c38f2aa7c907abc8a9a77794fa0d756499923a25ab38f90148401369f4935a8b17d96578b310db4294674d12f02facaeb053a3dcbf4eed7d0478bff6ad9dff0fe545cbbfe8e88d622a1f9ddab81f76e39bcc449d58f3aa6951e49a98b289319eb19131de88dac9b3cd1ed9ce744129e071a32b2642cce090226a3d9650fd4b815511687f7511b3156d9ca20d118f534248aa64eb7c645032685f1339deef5d7fe7463247b576caf4e4d8f6021849545b643308ba10262cc78e5b393fada3ab3fd0e5a4986f79b362c28b9e8374150ef87ff05c3a6932da5660f06670bd79aa219880a181b02b29628a59d1037d5d206036f3573efe682407ff66dac20f7e1b4c9f2981b1ded0564ca58f78bdcd8b1ea42c189b281e98dcb35517829bd034493c2d06ffa59c38ea5d7364f04bc398d31ae6ff21c3da31d47ab643168b814209e6e9f94c9c14cc5136d1c621777e4c21acf51c78c205e80d34f53d22f63790c8ce9b952cf9ae617a01502bd31a275fcfcd35dc65692b0fcc67b450b6190e5f7bf05d7551c5a2ecef26e32e73a401a2f8673cb33084d379f450f554ec58ed5415fd699ab73425095234684d46e0646280089194a896e5c3a70923c231cca511f3b58316c98f490674af4029eb82f07f57cbc85e889a363b8794e3941c172a4e1e37b494ece6c6854364fc89ab96cf38d6481ef0cea14dcf115800e48dbb755ce3af2a128f87c1b7cb9435191ee6b8809604b88b026747c362f1552e6b0f070f3184e2e7ae34326f07ca72826b2ac22a719da5058c88692b8522069c5c51e3a4e9fe54c0b0c3c18edd6b7f39b204936dd9358bf36e5d74bffd27bdcb487732695cedcc340a2edb57c17c9af639b217e9857afc2e0abf7d49efd1665c4c5f0ca7b24d730d521e81f490edd71d8731d100b4bb1b8b7b3ba25703cfd1e31fb7992fc9e792cebe7292b9731273812ab35746f65b9080fe46426b744d648700568a5df7cfb056ef323e042f3cbf60b9718cf7f1895ecac3bcf902eeadce5674c1ae72c991a1874af63d7a781a2b11c9f452b1eb2868c8bae02a54a66fc439ab74888adf1e1c6fbdbed2498b84530cd020be646c0f5c61807f1305ce3d3c143340148cb992062720f14a6882e9cb9823ca44b673818069dbf2d318cab1860c71c853a374b723fce54413cb4fcb6ed07a292550c5eba930631453bbe87c499cbd2ef9419e75a92b1b71b31eb1ba742ca7a03934e187feb064b40657b839aa5a24a38e34d82c9012a9578a5c91d4fcb3821d2582ad63e00b2cd7fa5c1ae00971984d7e5db2491df72532fd7686950010fa219ee429b9451ad89d14524751873585697753d552d3673476305584ca4e3f9124b42db1fdc561926d24b21f8b42f06be15d338e73bcde73743ee7dae52e5e8342bd710dfcfc4112937c4abb9868330836b20a8118eb10542a7c67e8f1cbe7cdf9ead8619d110bb12a1482bc82afc28bd5622c3cc7b2b9a8a0410d7b0b7d6cd9870b9e0e834e69d4d68871c1d0bd203cef68d60402f9d74db945889da956a8e774cab9fc077bb93a2f3a35f01d4694c862145f401bb6fe3d67da2531cb1b92ae7009a69873c13d8bb48b4394539ab35976f0e75c00adc2e926174640c8de713760de911442f55ec88019152e55fefd49ef26a3acb099906c945a7a6dadd85e95be029a886c8c20138f663bb1eccb458022e9866e87bc1e20b0cc4a420e8fda6f45049a77ca5be0794c635d89c191da40ca1bbab6cfb1e2e81a8dbb235def862f5bdb86d2da249f9f687668fdf7066a6c59b4f2279cbebb58579e199324f92cb5bef3eb69b33008b6d00d776431a924551451e34bbb3d5a61c64937d7b9342d2cf612f2ac39b207e3728a655a05eec22880d9f7c135ed214cc7813d763435764e0d53549f9a831122088b64fc240c94988e61911dd6c84866fd94bde36e0ab6b4811ef5b432a7e28e9ab018ae9b51564ba0451403ce1a5bccccd9d27c84ae3f539f2577a9b5d376a2ca1a5528d9a76fba0edfc2b52462161c825788ffe4a5cb0e9993acfeae5abb43319be9ac2cc04ae0d2b409512503b1011e8c59eefb775937ba3176e09dc48441160e49a0f125a303cccbf237f0b3a6715a72cad3e207d9092ea15256146fc8bf05203b1c2ec27a39731a549069c4570eee188cebc60e9ac62caa52ab1040698d6e96ae1595c3ae6c8262d7540ad7cbe180a3e71cbf655a0c6dc924bf6ecf70dea7bb9c1f03a6de2ba53a34f0cd1bfbbfba19fa5985cd09db1d5eddddfcc8900919f419086944e99485cbd97223c4553c1159c560509002e273b7506c6dccc8369a313e5cdbf97917d317acb913915db2c5707406d44c9e8f0dee39e07bc544a9524d2e853bc8daf51272b59de194079a92d9bac510626c5d6090e42478c32c11f84c26cfaa74ad2db88649ff76c4f7dcb3debc544442694a59c54247c3c231aa1254a5a213ea50b32349de4c79810e84e86cecdccccb215f2962363a4f056cdb5cc42cbbded5fb24468c6b5fddedb89eb7fb489855976283420a16b5d48a6d5999d56e593970b8c44a7b1e10348967116297a1d1f13e5a42f13056d4a1c769cf882262ae872f8beab98f9f3d90ba5e8ab46fa4af5aa32f4838bf96041df86e873efe4c3a1976ca2574cea67a38c7f7224c079018ff2c68494587abf71a64909fc52824fe630874b699a9db4590431cc70a3e6537a3a9853059e46cc48712998b65f56a01b28b58c944668403952bf1d0183792820f96e08745a95d0455544514e74e17742ae0afabe767d690d3f878f55f436857dbd24f62d85b14ffaec047d779e427de71e1f5109a17fa9c26a4d17bcbc6ad08f786e9c46673293032feccd282ed1c4df4004e57983d495e8c870426185c368483810b4483901852089f3f49c6efba0de5716c99c6b52bbac87c449f100c0a6ad99fcca34b5c517c67f8be356cc92698b53b0f21729c4c0f56e9eef7c762315b83a940b6ffa0404729b4d5eea3c1632b4e44b945b6682ee733d60a676879770384905d409efad35958ad5ff4471d1b0a8707d57975b3a405ad6d3ffcfc96f6264b018d9857413de0594ea87f4ebd66daf22365184fe0912fd81f7073638c143a9fcb4797c8eb55cdd471ddda561725135bbde313415d93f692501427b178755eba6dda8580c9838f51b61c95a92d08f28275f4b5777890af53ad99330c43094f7348695391be43c32bf5a931231a5f59ccee8aa9c3a71758bda41996f119e968d2188b077ede61a8426ca0ba182ddee62d1c1e46317550598e626e2d3b6dea31bb9a4ed744101ec2b876ed12190b126762d8097668e9d0fda490d00a0120b417966d1a4f33bfc33a87ebf7b47ac1509614aa8221101ff50b41db0c5d27b9f5360fee143f97b0317f9912083512e919b5f186581a2a49c2ab06cb40b45d9f3f5c15a18b8545be7b6722aa2b3d06a2fd0d3264389f86e178dbe99af929b8a2bd7e9bb130220e5dc557439b42435b0cf3c7adb635581a154db92050ceb981239d5669da3cb211de265a5ae41fac769f7fb337328e17b6bff0b2fb7b69131c97fce76ab745b3da7581464800c8b696b036865fb90325c77acca8f1424bbf58f3c444ba9e5095d222be25e634b5ad81025fd061d9d5280308f2b112cf68fd160fa8dc1fd25d619967ec8c608902703ceb52c72266f4072c4715389409e63ce9652dcffd0acb625c290edf817e2debd0c9280d9b28297501656b2a149a5485c17320a85c077b9e1102c0730bb4587b0599b252ea9851ce5e22dcdb83e33241062110c8db5e5aa902b4a883f56ef316d67fee4d43e17562ba806843b18a4db8d00f1b6576b3e42988e5c6946b8e237b433a7a1676b9882dec4682459bba5e651283bbf5126e0d3b7cab5f3660efbe9e9e04e7f875f54849c4386976e51e481370ffdd1c65489ae1f91caac187b728ae638e29d792b807825d618ae06dc3115c05e479a1c56242186597635f634e20afc3a52965bba2eb1ef713c4a710ce29eedbd1112ba14ff3a5761673a6ec5ac3830126dfe3330a0e55b41171f38fdc7f7b676eb3446b77ad6cd5bd318c184ca40eba1c912a3a5a0ba7dc3053c54773f3d12bbe771f955ff039211fa089621c928e97d4255dffd8d41b067f72cc8131e1e29510cff514c5a804904cff5d38fc97054726ebb3d4db9cbf786a4af3508a1293348cd88dc07e60b8ca4a6b84bf797e749d765271535b0314a9fcb24c675afebfe0b780a7d21c289e780614cc5b27a6ee2cbfc458dd2656a89710f239bebff8640a803c5ed8d1d4cd593cbc42560d307762f29fa8b73e8b9d9399cf225dd89b8b12002c8ca74b91f854893b5895310a64db4a329c42ee7dd5a8511d270f53bd6cf609def4417a458854c4ef4be94015f1d3a6833fae48486d2199cffa30dac850700cdaa8562094c105205bb12682509e4032f86bbfb28517e4189f592f69b1159c8b7626236e18204de631b5f972003a26935ea96d5c1846ae26ac7be0ffc1e1ae5da913fc5ed21c087dd1ec4627ea060a014f37e945dd14758007e7c80574c2709cfa6a680b05eeb483737fb9bd22e205da26d981b0315204eab4ac6d778065ab6b6b51c9db9ae18edfce56ac9a49e75f87dd68b6aa5662142df3fb40b5a0f694ec09063fe33f0bad444f839af9b8bf40c64d77fc8eed11f3fdb1dc4d5a62bdd4a739da9da60267485baee1f7955721d3f22e04c75c6da9fd5057150cb3c302c382d0d5c3225c723fd06f1c6a0520fe65af175cc105b7d3a04a926c001e4c514d7a645c08e17ba81271cd11293501470870cdac8b99c61248fb4bab51a3aec6bf146a8ad9446b4bf8133afb55ec4c94636cf003abbbe068c0b48d7f978be878574d980db794d81b63c4b9b8f51aca96aa083c681fc3310e88556d0c9cd92707df9919a952ad664703f519b355065c289b567efa774443674da995ec9227525ce341f8eb2ab450c0d573298372ce82ffdade06650db26bb6c19850f2c4a3d92eef09a24b2a2a35225082890613582b2efb6508e69018ffec0451468d88e0ec62833369477f6046bb7ff7d961f746264a7896559412165122fea73ba2a004adba7da7a15ee622c0062fee00a2cd3964146ecd9a936a5f4c1a7d0d417958ff3c1e718e8d89fbd585a725121315dfa451fc6c47ad322851684c38282d1e3411c01acc95aa6373c8e9625480049952cecaea5614d1c9fb5ee125d1801f39f1c56b0336b0ce33d124811eff14b4b8446b58729bcab57b4c04bc5d87b7b5d4ddd150886f498f88348ec235c91c6f0a54860cee2fbdcb7c9563bae00ebe4c817560423ba57ed5a9885ef349a4870f98cbf609788f55d342c6db085422d9d6e1ad0a3e40834e1d9d1b108d20108eadfa93367d0c656510fb6c45b063d43622de85acefac3871f08cb1b78d230c307689bc0f8de5141082312ae82492f59d1dd346085b3cead51c62aadf4f8fe6173be0e997f4e7558b5ae6ed17bff30eed57362c4452916bda7075251ca29f682abd8d6ea471063776ca24817abb741f4a5f08b3e1aaaa851a1dcbd52cae896c3d772fd7d92815ce28b54d9c424e0a4ac731db9fed2f9cb46301cdc84bdb624827b8ac59142428f3807e9c1d3460c2a58bc6114f1cd066c451068386e2c1eaac181877e28dbc167a761a1c622f1e41a7d99df94651c518e19c53eb47960dbf292eafcba9914701b3f93e2b0a2493ff81a2091c75b2bf04f7273c94b701287495f71f1c4f00265345309fb9dc4b05023da5ca5828760c44b6e30e850f0ccbf0db252d68a950258acb18c01605ed79cb43ab05214a484c4d39de770c7fbf3f1fff186ffbf9bfe7f2ef39c32e539a3833c1258aa9186686fd00050a829b2c18cc2518e2db6b6ea8808054f00aa83d6ab882d614c8e38f1c4087c6e89881b7935ff49e45aa63bae9759eb2c2e64dccfb6f272627e025f21d10a2e014d4dcafe8657bcca45b0f9cb1145968c8b574f0a38cf1b8f5310c0a134ea499281d04ac849a8b02fa727e710439c84518b7b3294748c248717169cb5d30f81a02857e6a6c959f1deb18a72d8d5500d766cca7c2e19a149ead1a15a82e0066490df6209aad8418f09212d614e855c8565c545b61d10d3b5f47f8cc000432a7bc07208460619c7060407540c4fdcbf7f7d39770b1e07da21e64730e4a30cf91d98974f059990250dabf90bd86f39fc8813ebc9ca774c089a74ff8513aed3f790a347c0c6da6d5c5048d30aef05dcf9fa47ccc8ee88564c7ae8d3f9e96b6f26a4377f9794f2fdc922de9b9ea1af8f551a89351550dfee11cef17de002a1a574d76b49bba1f4940ae5063a552bf47935f9ca8086347045a783a63c6e6e8d2261faf6c22545e269de44ca522456692c4f9875c3de7cc607764871e141238ce5ed82c4b0c5419893e4ab99488c59a8a80ee644a994cd767d3c6499e0b7b58acc244cf59b5b1d7ee4e0730b35609e725a8902619731c0d14b03a75a899dedbd0a99eed306a40237b7398444abf976a568f3df8acb2bec31fe2ea52d90f5b2f23c45329f80e35c21a0340ce340b8c2a9faa798161753a826c6285083399d5aef2a097e1c20e7c80d6fd0f987eb734040f99995e00bcc94e234fdbceaf2e179eb37c144c51006374535a5de9ff15b6daf162b345242b972029d38de8dc5c4ea3f4ca50671db2cbad34337e9b4debe9836d1f37b3a617c336f17151270ec9f1a5d754c009f0f7a59041eeaa17352d8bc123316ac448c34157f0634a5b21a3a5ee8a4da4541cca2b0860fb14f630d792b26fe64c0375a5a7844b0ebc6685fe9e6c8026a9e67e990cbe980cddc73c0312edf4997b610c02f872c02fcb92e01dac7accc78a5242e09144db70fc8811d83eb9aa416d6ff63b323969c6c136c64a63576aeb557169d76478c72536393fabd7436811bcf8011b25bfc74924c2acd324625a4248319b8fafc10deea41e0d08c1c5ea3afd13c23816587c04995d3f12827637712f6db675ef613fc90c2fc9e48fa94841b3ddc00e83159c104b1fa7b587609fd92ada2ea8711bcd71037ac81caefd7bce0ea8300a0602023117c669e16c1af67196cb0d66037bdfeef1d67f077f4a0235d3d79f89f64e530fbc9d90bb49571df5fce58535819405bc91ad8147310c053fb92f1dbcec6e409afb14c2d0140f5e6ef0094f9ad5f9ae4a96fd6d4d103033706715c5d7791992ee78020a6f99d6bc0e483e8ea7b8b6822ee59e9b995c838de362b43fd3070e560266ec9e588eee1c58627647b1fd1c3031ab8700adb70cfcddf693c84ab22b35acb0071f80cd60455dd500b8d5226ec822ba3fe2a4ae08193eb250bfd91ca9b88a3832a8b73c653547ae5c09d0989ab23f57c00562f83ebd1055d7aec008733ad2dddc342d5ec4c345f6b45b9277852777b98e7a8088854f6d4c6e78012d9b77042b2884dd25c6cdb2e8ba36f3352ce9cdec6dbd307c18c7b3cad973f086a883e6b2d498b4d08cff18615d70cea070f42d56a33a15db2029348dcd558f1c790262bdd65e1536c07e07731a99c4ccb9af0655c2421964b7ba7963d6ef4305d4a2240161128d9ae9a1bee44284534f95bedfd16c6daeec4e14156b25a338a345a4026458379c46e8f1443ffb61ea579d3c3bc33e03a69218ba91eca20bcc0058f79f92569697ab89121ece12bd5cbf4cebd25dd019765773a73c5fdcca0a83c402c2a20833843efc9371b2ca11be868e1120a86f848816fa5ccaf1291a4afa5de8fe98ec79a07e090382eac1fce011c518def1bd2b84b719966105db38c36e5159174b0ad2b183f133df8de4174c764915ca409ede38f73a3329fc301cb60dc3dead68224bfa3136ee13cb8a700c9c9bb558f7f1fa92a19ec019a2d9d363f4d684954a4a1b862953b4220304cd0b81db9e5d4458ee6cbe914b9730b54164a0caa0c20d55c9f5b67a999b27b711ed60610fadd74bd0e2bd20fd32d5e30a0708cf3861e96eb3e2e415aec7eaaa7fe973066cd59f32da6769b2ad9e7905f9236d74bc8686aa37906a19385ec7087ffbac65ff70976ba2fc28b425c3b22c864b8a97b1c715fc92fb22cb6b24dde4654e750d373e05f71b4ad4071bd09f9f4eea704ba5c4cb8a8e907838b004bf44145248383041678794d129c70243cce5310f9df0e3dc8672b6f46e7b9c0669d9a6ef1f352d79c965a681fc18c55c44689d5e70879e065c9e19888b57404c7621847a016a0b1fe112b5386b1cd5ab807d1d129c5bb2d21b5e83ecff03f648dd00ed2063b47b53a29380c096d983c0c46e4ca8c2ef4799fdf3b2f451255712c35f255eaee43dbd2668e3ec860e05983490f881d33b001506656614946b18f5a2e21800e309b5ae3654bc521951209c64a7be81d8c38b97dfd9a96f7ab3b77a429a5ed873b0bf768ed9f9df8385ec0ccc8b3315e3057cc5659350fb3f991640c6f3b932299bcf553005c69d40fbd89080176414beca79521231e51c61221d4c9d0de5797e1fccdc34403cd308d4451716d8bb500ac626bd4e51723212cc53ca34832935d2a84c38fc808803127f29b298c10621160c25324b0f71dc01b4f40da0c390ef80ae8b86be10b9d3d9185b5d35cc39b0d6ffcf75d540490de466c25841a149a27c3eb351e3615cb87f6d145c62fbc9d4963282bedc73c6d99179b7c19299c558ac12f86749fffb38725500a35990492a138ee781763ff0294775af3ca3d6db4486110e6c03e85ee266475861b5505a215506a11176fa955d5bcbaac060e6451cfc0d97c6850ec3869ebf473b116410726670f95227ce8d199f421a4f8fca0e675dffbc3d570a804de4781042c49205877004134e81a8378b25108213128300fdb661d5918e2002566777708ca30780bd07412a1d67c14bb3d95687d183586e4ed9df979d0035d9530d21172b87933f4d95120319fb0feb19060d48009c79b2ff9ad6a091ce4a8672099ac445b0d704cd94ec9aa1c87b90b14b9233615641f450d25831647a9bcc63adb3bd9c2ea21b895ba59b4ce466b25097b4aff3616bd741aef7179b463610db8ce36fb13aa44e1a784ee845ca957c1aa22ff1432d9d26ea80e324a15416e1fe340fe46513938e70e4aa588db3366dfa99fba3fbe0160b1b1ab4d629dd284852434cc1ffd012f6686f359f996fe62c80b4ce903d359f6ec2e2350e3d068591fe3f1247c293497321a5158b98f24588c6ae35c9f59491c0f0e1f671d6ff5482f1d1fca1e1cfe8821827f5c06a67f64f1ae88c210b1bee1230bf6c8338b32431d3b5fe2a2677db0002281ebbdde8817323fa0a5e121ce6c0776f15b0e9fa52106b0a117aa8a3bb97ef5110ed63b612ba2b341700aaca8ccda5cf5825e12f4e33a2fd7405bf858eb36f9259e6eafcb5920a0e296fb5a453be8ad4ac32bd12c86a0478bf62926802b86bc6de03d3c2291abdebec840c6be41b211ab4cc33e520489a6056e1f74103d17dcaa077a321b5670e2f03f3e9423803c61b2cd4614c1f17da4100c3cbf047b573eda641f395e09ff3d3d76d880d628e95dde4d8f6a83059beed8205435ac8200700ab3e9389b841567bae15ad88c41ee5e3c815a3d49498d57a44a5d0d7ef8eceeb94ef4db5264ec5a6a86978331739a935b20b036cf35768f03ed32e216e90191eb409c92340eeaff77b4d2518dbe447621d5f4efdff99171be145a5c22968583868f6b69f96461eeef29dae47c62be7f6f4e7093eeffb8e4306831bbc35b4d05628cca458d041fad7d7499cb4de37632e10556d55f397342e07a7b1f3dfd06c323eca9d134be5cce55ddeb2530c2456eff3885de0d4161ca9422f0545a6247dbc55b01e59c44195fd1ecf6fce7ef120365dacd70a5b2b876594b9d14e028294d947c729614e469d8fc20c9ca74afe5332487e556a268898dfdd30260d2bcdbbcd9c8c3a92e7ddc58c26c0ff6fa676417035cd9b6a5d2d031a67bbe14ab599e44909b83b40a464b728fb7f93d2a1bf6bd26c887317cc77b27ff4ba5ed563c4fcf152a649a3b3261da9cfb464fd1507fff897d2acb5f097be5d10a66decd554e9436fcb43065cac339fe35a378cdc2e1a99b07642703da721280562058524d07c9096756d83c8a4239e266992a28648caf17263f8179061c09819eb659003201a9eab328f0a358e3664f94e6feffffd05836cb54fb8a3ad390bfb1892ed0e2564c7b4c70088a5ddfeeddd6f6de52a62453080831089607402e8edf1fd65cccc0f787422e16f1fde1908b18f8feb0c8c50b7cffccca450b7cff4c8f8b15f8fe19968b14f8fe191f1727f0fd332d1725f0fd332e1789f8fe99978b11f8fe19988b1070f101df3f33737188ef9fa1b9e880ef9f01725188ef9fa9cd08cd0ccd149d56a79ed3c9e7d43ab94e2f177bf8fe13ec143bcd4e27a053ed24741a3a15d1ac687a68687c685a6178e47b273a55f8fd342f1a184d8c664643a301a2a9d108d10cd114b9287e7fcdaaa6a78655e353d3aa71d5bc6a6035b19a590dad06c8451b762afbfd3535175df8fe1a211701f0fd35432eda7c7f4d918ba8efffb172f1c7f7ffe871b1e6fb7fb05ca4f9fe1f3e2e9ebeff47cbc599efffe172d1c7b7f778779977cfe1c5c2f8f7f822aa169f97d90f2fdf2364c2736328a67a794fbef7592d90f80dd8d015be4258180b67212d040a6ba150381416cdac667a6658333e33ad19d7cc6b0636139b99cdd08498a9cd08cd0ccd14ad7a583e2dd70b169bd1806a424345342b9a1e1a168d0f4dcb6d685e34309a18cd8c8646034453a311a219a229aa59d5f4d4b06a7c6a5a35ae9a570dac265633aba1d5d46a846a866a8a7eac7ef4fc60fdf0f9d1b2dd33c730fa1a24f00cfd3eec1121be0911ac543e3eb43d9d0a8990c6550344699446815c7ce1fbe61bc87790c7f986e103f0fd1bcbc518be65f8feade5e20cdfbfb95c4c7d8b42be69f8cef9aee1db86efdf6a2e0ae0fb37211707f04d80efdf8a5c1cf2fddccac5fbfd5c8f8baaef027cdff0fd5ccb459d6f1cbe73f89d988b3a7c3f3773d1002eeee0220f35170fe02202be7b7091888b09f8f6e1fb3b968b0af8fecec7c51fbebf6bb9b880efef5c2e02f1fdddcb45067c7f07733188efef622e36e0fbbb998b34343e342c9a1e9ad5a9e83474123ad54e4027da69768a9d60a7d7c9756a9d7c4eac53cf6935533433342334539b01729b19dacc6c263633f39a71cdb4667c6658333d33ab301c0a85c25a0814d2c259180bc357c8a30c21acc98761d8806d79db72846dd1806de1b12d46d896d1b664c0b614615b30605b2e605b2c605b2a605b28605d26605d24605d88b02e11b02e10b02eae075897d710d605e600eb1213c2bacc1a605d68415817200658971a10d6456801d665e807eb52a400bb63e583ddd19300bb8345c4eef0e9c1ee6821c0ee701dc0ee78f16077c076b03b6206b03b663ad81d3b76470e76070e76878edd7183dd51006b5259d3b5a6cf9a7caca945006b720dc09a5e02b026980dd614abc19a6639d644833509b126d19a52d6348335c9605f62b02f01b02f30d8171cfb12c4be00b12f37f6e505fbe2827d01807db1b12f35b7e947d9971ff6a5c6be14b94d3f8de571b23c662c0f1e21e99963f0de5405cfd06fa7181cc338c1b82f3ee019faf965c637fd3d2c95b134369aae184d588c138c3b86ae2946e8fac1d9a08e322e99260d93692203ab42f53bcab8a895b2c5f5728af917fb626b08c217b111c7c6c6c6684aedf1e3e53674b25179b61257a3fc5fe6c7ce7bbc4cd7d52aaf9296969f4609c1e35f42981f5ebec7f778f9222a6fc651fde2505a8d870faae3dcb63f5efe6259d5e00f24154c0d10df7035a8638b4fe8e3a2ccfbf8b1ebd1e2d3e273fb2bcb459930f4d1753dc2d0f512b26eff6ca3f21d431749b2abf8c7d8ce590563d96d66e85a9154210c09750c5dfe625965b2ace28c0003048ab8f4025c1ebbc8958b428505536c4c361a9080524a2901a0b821d4441b48e830a880a950e1248aed091544d890e0846ca5d14ac2cd7159d69a715d4c93cb8e63ef9e7bd975bfb1cbae3399b8706ee1f4e98151e5773e83fe4ca48d1b6d9c74d2a8e3b62fa4d7a47694d9398e63661e72d1dbb66fd9f10de98734736fe29cee2bc1d17b9649a3705ce2724b803a74970ba7128dea264036261274e83250ec324cbee4aaa60450092fabe0f169e41abb4837aeebf89f77dca687044144b62c32cd4646803c232c43d8ce3052baee58c06ebd2cc3c2558239e79c2c7cf320163a2c74328ce9c9f7da7a1cc49f411d5cfaebe83057ab11a35c823a7ce7ebd096aed2df5347de194e26b1ec04c5ee103c3e3569d4f63120c11ab5915e8d22411d49afbb35e9457f9bdbf3889955df7cf3cd49b12be813e419b54733173d2745d1295aeb54d7559f2f4408ca4455a321d451084d0aa174e8c382575550f99973c38d933fab3a17e7835ab0548144b50273ce90e39ffdcc1d27654d883929b770c6b2eb36d9b68da00e11a1390e0e0e8eb731e79c4fc453322653691222c2a269c62c84bbe6c66111a776dc0513a1d1da87bcbbfbb773ad2c5733f8ce8ea5dbbdd7a514b4e9e734c9a0763ca52b555840352122cd5dd7ddddc5702478858dce0d0b1dce95729374523adb0ad52fcb7e842085da97653f4f380d2030144aa612136a196e0201e0c8000a12444105294d4ca0dac0a86d5070826edbb66d67acbc4848e85318d4b0821320149c7850b001926ef25334bf1f2b6c2a4fb4c182328ef0a095c69f602ecb7e8e70b7cbb29f33eef8381d1c422e4b2356947c904145151c15452598d554b7bab9cf1377fc2db270b9c7f5976161c6757f93976457105df0b26c8b2ad0adbbe56f3fae1eb49f9629081546877b92e30b56823a1c16eaf8d7e9f53fa971fd519da2b92eca262b2452aebba05cdfa88cb94b3ca653d251a87fc3b4e9c063cea9a13975cf84f6657cf48881e1f162dae1c2ecded25dd29183a5aea8809f471a75dc46a7b76466fea1a2230eafb96817e78fdddddd75774b7627b44bcb16efd274968e4a272b470583aafc4999a9cf486ab9b36c9503c1f0b85001dd3d0aeaf07fde07d280446177da11e7f20375bcebd40c3d77d2080ca2ddcae4aa07ddd18dfe142f9430545568b2d8922d96a4a0d6cbb22d7ee846e956a48a234150ecc0c80ae268828adac0d110420773254780367c9ca8261ae8a0967a5c96d1e00597745946039f3bd67f524d2c973b1f624c01aae66f9506c40586cc5582db4f4d473e58182c7ce72c75fd758bdb5a20a96e4ce1daebdf5d1117bc208b214c110512de70a20661cc489e1975acdd18320d1c9881c5097290c6cfaa61347881b28adb3f3b35737b3445d77d4ae050c12c8b2856d0ca420915435988918496051090a8a688657104964f165180428a57166c94408582a22c8a5852c5f2711bee62d023854a8a21b7e1eeeb4ad6ea7759f633c50957f4b471c515fdbaa28b367245196e03329a00440613b6f0fa4b8489886b442869c1955f44a748578a5e8c47ae624a5c77943d2f254fee28930c4d69a28ed275a50965227ac5c182d665245c76628513497bc00318ac5426598374a34e79e5cb30bbf285746a74e513c1d3e268c53218c4aea4bdceb83020ba234be1919f5cf9d3dbe43623a89d13b554ab6361073938799f952a8e8ceafe09f1bd27610417c2e0b03df7498c927b1088efd993e09efe68f4feaad19cbfcd0d0402040286f4de832153064921077446a14e172661049d8ed3b56fb1832de48036b803f723520fa41f718ecaf3c69151d96366e6da4f7afe2e1cbfff3a9544f7db7ba3cfd6466d25591dbbeb3dd7b9c88454756e437ad2875aa82ea4533f412038b652c100317fe557c21054153b57d77b151fe633215530e0affce681210774be50c70b93308214761d17abf299366a9a8295952b6a4fe59773520a521ac2788883ca1a52324bc9524ac9cc2cc3dec16496cccccccccccc2c99437e29a5dc014ab2e4154c6f834928aa3f75771ab694393eddd91d8c52fdc1245407c1e8c24eb2f39d51e0b9fe6c47fed2ed920c6a7fbe02451dbffa7d3521228d239f70594a139a0997bb5bce12ccda0fda8f1a9ad3ccf5b6cf6d68edf5bacfed5f99eb913cef73dbe37adfe736e67ae0e716e67a2a9f5b1ed75ba99f5bd3f55872945a4a2d2e3b4c2f3030313d7cfc7fe1e776a3d73b7d6ee7f5686a6c6a503fd0283d4ef7382a21cb20643a89e3e7be5c76b2855608723ccbb3242047b87215134586747d724b97652f805d09ea106179ae05553edfd029be2a2afde104c3ce298bd5f11e0f8a83ca37c709d5a9fa2ddf2fc4459767d9dec5a2de6d4a61732f70dd98cbb2174091e3658e90891ccf6e23c311fc117bfc8dacca8f9e64c7f955abf20cfacacacae7c0772584dd952f72a51d5fc58e72943fb2de7f2ab6c8052d12572a41af17ca306490d10ff1d137f703cbeb0899c8f12c447284ef365d67fd59feca7716cb2ada4a7351be98ddd18ffca54c064297653200bab4cb321990e006e1e2c73d0e4309c245264420b108fa4248c5647b11481d14c18a958a8911dc6fdf4fc40fa3ffe8e58f4ad0911582c3a4fa8f7ec451826af1fdb45a804380b6e7551fa52b3fe78a65b6b1016de78d5ecad0a911e9fd19943e7adf41268e38e2b8231c221ad541a471b95088dddca8230315411b19caed97ce8333a451fd3f744aa5e776d795ea5877e4275dd2e97600433217f8dcc62162c9ae20925d41f45786e4042d9f4e4fc198e46d31fa05d91a89e1f1698481769b4ba386b5532bdfef499dfa2070ef9e70574223bce7ebf5b00c87154f58c9ebcd1f56d43174fd7071c1a87ef0995332aefa35409cf3b5f8d070ef96f432ae3622c34573d6473474f5f7adae7f0edad1ef168ed4081d8d2018ba3c9e35c25f5ed3f7b03309758c7175ef992ced2e835d486921ec52200aa551ed44fd1edf1ed4237ce1fbd18fdeef94ba72afac52cc7dc8bc8ae7c31ef1b1e33d1f3e7cecb0324b7c5815958c3d22f3929171f9b02b2a9cdb03c7e5dba5e565663ee6b749b9128395db593a823a863d7c8463748a9483dff93c7c38e7e30103bfdc33a778b456fe65c639cee30a261fced9ded552fa91c7153ca685070c9c470bd4f1238f2d74b024cb8f3cb6b8397eb3342ebe89711c19d7adaccfea60f14d97627c430222d52c49c892d46854d348ab95211a6d686885b6a206e7c8db3fab50473faed05cec2833bbfd2d76acb76447d701561607640e96d0357a8b0f3572c7169fb193711997cc129537aa4c93462961ffa51553c218d22b31a863485add91d2c2259df2bb813f42033c468c1928020317b0400528300109101101083c60080708d180201800c4027e50800f0920d203020ec0c30e06d06127071c746e2880ea864308300001d850430e0d42c4d40c32c4100018708200b979c1ca20e102006c7c5aae17ec47ccc50e7cff8f998b1ef8fed0c539fe3df2b8c26d1e57e0203476e64fd6c7ffb0345f634f6f637f3ccad6bc0bd6e60160518f8405c0cb5817deadcaed803d22e3c9581faa4fe5f52763415595b12a2a0fd823d5a767e8f74065ada8bc982531af185716ea18e37a7b4489e7c06fc026f147d80d3c8f3de23560ff47cbf346580d7c1176fc0c5823fe02b688c780cdc057c05ee02d6031f013b015780a580b3c1176022f014b81878025e2236025f0435808fc036c045e083bc43bc03ee083b0427c03ac031e081bc433c036e07bd899ffc102f10bb00c781fec0faf00bb8027627df80458053c022c91efc126e079b008f803d81ede009687dfc11ee077ac01be03d6c7eb6077781ceccee76075f81b2c0eaf63737895bde10b60753eb4aabfb6004f001bfe107b5f0096003f003be46bb002781bec009e065bc3e7581b5eb434bc109bf33358f15356c8c7606778196cea61b0317c00ac0c1fc4c2f03836007f63833c108bf3d6defc0b16c82361edcbd8172616ea18e38a71b9d8e3fb635e31b04e7985f50cfd49d8fa729b7ec5b8625c314b42978b1bf8fef015c25c3ce2fbc3988b1af8fe70e622cff78734178df8fe305d12db21e98ef474ac46f9c05ea655b72a2da9a3f77a3501572f3402a2f0f8348235afb277b22b49b4018c24289c74e60673a02fe5b6f546aaa6da712412c916517992eb48a4f7b6cf1651c9e724d77deddb4ea37e43fdb66d6347db98773a2587d016e7702f651552f64cbecb2331744b69e1a5b4e87995f793c4513a7b4257b4412dbcf7b6cfb345540c6eafeae813ea2b4a39aefbb4207d678ba83cf0235998f9ac2aa2f22c8cf49ef8c02aefa525e23dc9c2f893be884a5a9c9ea1dfed0fde9384f830ad83f273bddb3f5a21697154fb49721b045263e8b61677ec8a6ed79cfcd8e08ea3233e2508d4a91184020675aa9bb83d41289d8a92ea02b0943b8409574a2923cdae2cd5ae64669e49714bac2bc71c4cb83247ed4ac915805355703eaed71f68e40c8fd6812be99b9dcdf7e86c56464837ba6dde795e371a798ecfb3303f78fffd1732e1859ddbc82fa2f2be6f7377777003c139e7749fd3dde7f469bfcfedecda664a3048766556bf19a8439f346ad6d166970625a8679cc31d5047f38ee6eef66b99ded2d2d2d2925b4824b016840973ce39e21b9eb3e31b0e856e14ad2640dda97ba468c30c2fd8f0f83d8e0195edde6d4c28c731e9ba6e34a294524a3f8f524a2908aaa8544a29a5254a698c49878e5249967474b2a5fb799a14bb4674112a4116e8d08315b03194852b8a15391823084734a9c2118af022128202073945e0020a0f9a60218692243c09a316049bd10381278c14cc645f14ad618331808a68d083042f07befd2225900b01d49152d62e354af685901bc199516f727f7f3703e76b9b86f9c261526743a37ae84d51fd3f97350ccf9399fef5c64db7f249f249a3f97da4cffbc0397e3a2b2aa0bbfbec6a8e39e79c639d3a3eb72550c7ed686ae17671912e54a5e6d0e1f29540b9a3861423c5482cf05cca9eddcdcfdddf3d6479cba4db06eafcb80fc7aa946e73ebe6d7baf239ae54aaa65aedbaeedb5c3131a6171a71f75e7f0dea74f2495ef749e9ee2e59524a77777777e9d2634929dddddddda54b29dddddddda5cb5a59587248777777f79fd265c83fc4db8e3cdbebbecc1bde1452cab652498b34c49448228f5c675d7f9e1f2a89e8d40daebc3b51845cecde9f871d3847bebf0e5cc3f6fe3b9cd3ee2fc4c52e44e20659c3166680834c23f826092e827360ae3f129c339f301216c137b22a7fbbcdd31689327c87b9f1a544c21d2ec245efbdbbb00b67655d9ee1a4c031dd46cda210125d65fa2acb11700f0bdf34c7f16c2bdfa871c7189917417734c5f0c7d255cee16b622b3c1e3880e1415d19e22c67244a299d2fda4265e8c3c7ab7559e6c513182ecbba605d0e48da1992a4a41a6969714ebf735fa2c9df6c8338b8dc91646485c637de8fd4c8ca8a6f483c6a95b3251aad45a2ad9c71bdb6ae7fd7d2ba2e8d5c971746871e617668b9f3bb3b8e45ee6a93e138bdb8f2473b449d9852d41354f9b363e6be0dccc21d9999b9fb3999d9755c237cbafb646666eefb974952e5b304afc0ee6117854927654d8a444a23bc3129a593521ace39c5a493ba31e91493524a279d534c15d2a80a8f9959b664fb9cf3a373a2208a2adf03bbae0b87786c8817a173b17b1f4da3c9393af449dec7dd574b3ca5cee9bcc1ee6c96878ffec7a8af1b7d226e68d4a4027490692330d6a8928f784bcb8a73fc9be6ab2273d48d3a20f0ca0ac76d1cb76d5bd7bd3143d9978a60d4f99c6934e1f169e45ab7719ce5e2f632dc66446014bfb75cdc62fe04a736da1a0cc7eddf622e6e406ed39b50a33622706a9b02506c0dd6c16d87b1df8045ae391f1edc666d4260dd0672d1cd38c26d2087b914ea6ae5d7e1af9c5d67bd530af526d43db73558a41b1ca016266fb4712a1eb6ecac16dcd2613d0ee35adc14888888888888888888888888888888888888888888888888888888888888888888888888e65bc1eb46dd88c335c98a0d758be6c18133724db26243ddf2e0c019b92659b1210f0e0f0e0f0e9cd8a4936b92359d6b707477370e1cddddddc1ecf0ddfeee8e1db8fde3eea12369d426c37777ca73d6ba32fd89cba23fc1365dc7f9138cea2c702af49986ee3f7dba74779f2418b5dec337fc9e53ca734e76fa1c52ca048d0a5532abb0ba1dd7b9a909ac68c3dddddde51a2e4bb677777777a75b28a7505dae739cbb4bd1c61ad2c5c5e9aaa5f4586b1d2b0dbbe6c2722b464deb540fe740912dbfd5b8520a91e18e3b435a4bb221c1d98ea0a6bc1ab7db9334aa4767b55c5a0375e8ebbef9745e1a55b274b9bee386b68131a3599ea4c5ed67f3f162f319b58086d029a750ead8ac6675924fae58ae3f77ab09189b4bb5705dfa25195c4affd429bea6196861064377acbfc5136c7c9f154f58d1460ec9f24477446c649228ea281c54bf5e2c7963cadf12ff9874a3d1649e22aaa85fd7c6ab6bb08cdac508eac842b28b2290e8827366a9f2340a091f2128212fc42424844e7665481e5a44a5461b86746af4fc37eccc8df4425adc897941b63c94cca312f3763f1a3dd73b1a3ddb24f85bd21f171e8a8daec1910a6a49b6641975942d2c8cd4918b24163c9258700ec734ca746254f73b2e2e81ba5d105e833bf8e57e091e7de1c845435cda51269678a216fd27472990cccc93528962979452bee1a6e0e54c917bff8ec5ed3dacee9b6df7144b672a2553db4d77774abdf09a3223e195e526a1a85fa3022698a529d7f4a7ed8d3b3e8e0ebe2b793700fc7c6635d552ad4b682648e14eb66061e1fc0d5abc1107157c88a0021e389164065c12304a6085ada8045310630953e288020b1f38093a49eb04a5ebeee4064e5870bdcb322741f848204e55c90955fe28c3e5cee5103a56741105106600451a2b3fd24fc0dc90022382a9319348648f8442229982e71b95e0be9e0f8a0f4918a3087479680f0d2c1a90d0d043c3096860d1c0f24625dabb0e7647298334b9ed61c8204d65f7352d226587138ceef3a2aa14d539594e5b6a147f5ed4d1c4301c5e5761993ae5612c0aefa52d7259f2e479e41ba6df935c975d96419afd79037598d26e4a7f486f019041d8caf7248d92b48827613ae5b3bc736ac193d496e03975008c5047067241ad0a3e313333bb337d670e479e4f864619a18e1dca24637e4aa742c2aeb73269a951fd813ac6dcbe25194608bbd3ce225414d4f1a53cf594cd85c792467d660961a4f724cf76dd245aab7537f539e766e553ba49e948c625da0430403021a5f052794f7a27a48a8910df935eaa421820432a26800ca93aee8dface3ac363961f880113c375592686cfbdb92c0b43e85616ee5a8e46a32f7297b89d1d658c932a0ba376b922b7ad127eb9504a3b4a24e3127732d7ae02291b24a34ac9b5a7c6ccc548efc713ea8e56a42907fa75a9c97576ee807367ccac8e9ef4dc8f401d8e0379287247ef791f773beffb1adc01673e377a1eddd3397f8775e19bf9d2966490f99be5f992c32f87914e174ea7e1178ee444e2832a654f6629e7c6ffb951e9cae4e74aa55aedbaaf73c5c4f09bdce7f2e74695eff5d7dff3af7c6eac803534b951c76feeb827aa0c2b4b8ed0db9b25cb8fa1d26536f4b9e06bb5bed6f67c1a597a9f0beab66ddb36b76fcb43a3dcf37e5aef6bdfc9440d2719c47f86e30736961cffb52a372a071bb55b6ff75eb76c57c9f13247c87cc3729235dc80eaee4ad881271007e7c14db5763c5b6514c14d826ffc49b33a7fe4d9c7b38de704b343bddcd7cbf9f644a9d4f249eb02eab07c199248d0e291ed9d78d51632a1cf743aa5d4bbdb89253fd785cbb2357ed29ecb4ddb1541d2a8d95df7fc908c4a7093fef8f55c6f9f3d7c50c820fef47300e783de811cf8ce3007be1e72d26fc0bd13afe242269bcb22ee489fc7d41bddbe9f2a7f88d7d5386ff395ee9cbb8be04e6959f7199c52064172e54f97cf600f7d024b879eb42855fc2de145588d924970b79f9fe7141e9f2cc783735d722fbf7bb747e05c2e1c712ef76cf983f93eef512ffc0e912086d8624e9020b9e1f324f8de370ad529ee7bf4a0ad6dc3bd643be2426a3ba652f4e067cae51efcb0725cee010ba67051147359c6c508ee929fcb322e5a3a2ecb96f45cd992594cc6647547efc5b22545d78b7932273050e3b2131848b9a274d282315230e5fe65274986aeb77284097d275e05e4f93482abdbff43a37a76f5070e9ade9c3b9e0d406cd7d333f4975c75f5ea1f29a594524aea79d39bdef4e6ec1f6f25b0466d636caeadc9e66a5407cdcca021250cc7b0f6c9bf9256a7646496f8f011d4a3c74f4c0c141818190f1e519abcbc2831999eecd821c5c5658c961624a5d28f0e1d5072e448c2c2b2a4d6262b2b414077274876411df087f4b7a48e1c1127c4a9714ba69f9d1273769513626ee8965151efb91ad54b622419e9456ad24d6ac2a48ede6bc4f9d4b8ede352c324a50675e741b93d7a4f6e8f9eeb76d79eecb6e7ead4e8f92c19492c25a426a4216f8c298dea55a3fe84d37d3ca6cbdd67e2d114493a3285e6688c3982098d6052eaf7601a75bee7f17c4a5c96df6528b7a350697eda71e4536b547b526ebf47f3a000b14d00f920409e8300010204083310205ed06d8fe641a1b16317bbfd2a4bea2c09532e2949a7466e8cdb2e543872413e9d621913daed5fdd71ebf15e36c81a522ccee11e56758a2fabf8927a3887893eda48c99dd2e2541d19fd78e1d83d91dd5192d17b3189dd9bcb3226acebe0f750eee947efd51de914ede91445d229173af5c237b7e7d13cb7486275349009f5e689348abfe625a873136e5ba83fe4c72da16013eacd0be15c3a37e10b2e58120b00e168b209598efacfdad8489b90396442823e340f28dbffc38e1cabb163f7e4f6d3d8b16b72fb4f763596ac4fa3ba05d4a92ec2e53b6377d0000a0a42a3d6a98672f98676078d1a1a52d09002eb5413e1f2b576270a6c8c31669dea215cbe6f77a2cc9e3c6975aacfb87c65ec4e94961225ae4e35edf2f56177a2b8962c01ea549b71f9f6b03b518082827c3ad542b87c63ec4e149f9f1f5aa73a08972f8cdd8942830225d6a97e72f9f2b03b516232599457a71a0897ef8bdd89f28ad2244a9356a7fa0797afc9ee04b594289975aa7d70f9eeb03b41b3274f6a9dead9e5eb6277826a52a4c03ad53db87c5bec4e106c8c317a3ad54e2edf92dd09ea4182c4a7535dc6e5abc3ee04f9fcfcd03ad5b2cb3787dd09a24181c2ea54f3e0f265b13b41ac24495c9d6a322edf6a77825c4b96bc3ad53bb87c57ec4ed0ab4913a04e75ecf255b13b414041413b41b14eb50e2e5fd0ee04c582649c03ee04c9f886ef902ec3044deec8f56c435b11b7bad4e54d661ecf7b69c7ef7adecfc96a5423a95197924e0a958663cf7a5c5fd4958b5e54d46aefe58523cff57e5acf8349c92f7be0b0eba1b65bcd1f3b0f66794ab26b424340d264a2af693dd8fc26b51fd6ad3a357a0fecfbe62ff4602f93c7ee12d6299e3d2e7e4b9a67cfedafe85bd26237a5bf968b9d3c5274254bb286bce78eddea08a9352aa22bc9069c40a884ac37763dc914a2111900080008007315000020100a0704628140208e5435d73e14000b789a4a7048198b634110e43008a218c620040c008018600c02c8304353260022f6bc1cbff12752177aa43623b73d4270595ecae83cd1726bf84757828de109fae3cd6b64b4f95bdac172afeef25a32829c0c5660bdad01bb2a82753351f29433d2196e0acc97285067e04d56d79d358452ca357e921c1bf52701c33d250b7ebbee6ba84760dd9ccefdd799c053be78f8108124fd1246e8d9ebac0463dd9914edf63a4cfb6d0868f5a99c8ffe6fe20b153b23b1c72c772edafc68af040c644fe9ab5682d6d71dda2bea4f1b378e9786910c6957070fa51a896abf0edef4bb4a6b5d5b8d236631177dbdb255d394fbce2d424420d5d50849aa5afbccd911219b6aa5fcc899790834e96f1099ac025558cfe7c12a50b54e1f0dc37ae6437783549f0925231ba7105b8ddf061f07e68f3ca88e1617bbd78af13f8afae3d21f5420727cf96d302d8dbd8af00ca2928bd82816dfa18d1b306e3a1ac421622ef673c8a2e20b5e80181d6ade18633d58c8be2edcd01583b1aea96d4bc317e933cddab1ecf03b0404227488ed2f7711980e43c3505cf212c86e17f95c3ba12c285f4fe4ea56bfe44eb8e00567cba913d7a83f2309de4b948f0e0bfdf30775f7665131d9c4c0d521a4512c7c4010d11532d9185325c7a4ae6aabb3c20f7b62614a833014a9c29e93b6b20ad27e8370e85441e74082f7fb48a2d0f044cebf86497b22121702f4cd10157d87452eb4cad97f7d3405643c0a36ea7250e23582f41dee93a82272a0dabe473cec2378c2a143f9ad40afdeba9fcb91ec29c81923acc3c701b06059e4f107e333e61a42fd45fc33ef9d16bcffe5fdff2ecc0b78d3e31f6d5a4e47e286cbcde953fde50099a1d2ccfa0b2be6c8fd5124fe5c43c8e70596c68dc9fdc9b284654e8a30eb7694863881246342d20c1ef8e4f1ee8c5d202f6d4c8f009e936f6eb1e2c73491225e68d4b091710448103109c6a1f12d0b9fafdd84e51f5040052134f4b1e9ab0836cb58e83a14fe193f963a55169b646f94ec20cf7f004cbe874ee8dfc3cf7894da5c1b69995c8234f98899b907a357266c510341570ab910f07441672fe20a6247bc7f93ec3d5581fde8d14a8d005c419962fbd0c42a6b6f3e7c46746dc4cd55c2553fd5b2d472e596981d58be2120f82320e3087d1cf6fe5cdcb4b13d7adce06784cc29aebfceb933945a304ed5f3fd2a16f727e90763bcfe9b6e002acc370743892c5929a8f7a4b07a26326d09a7cdde92dd66deeb099259d98c6fd2aa3974fe43d1263ddf080c0240442acf8a8c68b77fbed294769f09219dd1291f1be5a2f9c56d6ad2ad5650cae1631fdde452949c79faa5235abde114381d71bc85be059a4420ed1e92957045f284ec1f8dbc61aeb26125b8326756ed2f5619a9551144db7bd5d19ba3103af16e6658ab42153963de07d1ec18ff3300d465fe025c013465f04ff084379f07a16bc04ca1d3881d3590954b0a3b4dea638fb185dbae30c8f0f0d839e71a263f989154c8a162754675a670c650c485d9cb0a3de376b77e2639b07be46cd4976d9530bed94b7818362522a9bc8d4425eba791214a121e83e24c82874bdc1a6c4904806225434ca8dae69046e2d617c94ef4515511cc9af2b8d4fe6fe06364c1eb7c38a04f7a2432354cb99433842d4bfbbb333d8788bd5bfbf3779154529b99171dd3dc0d9b3c72ea00d1925dae2d857b5e826451fb18a83a458c60f3b5205717d0a013d40bb996257dad2ab0ea1ee5b8c8ca35f0452d390ea7fff8af1ee2cdb7a5fbd7a370babe98189b36bc5df8bb5a73a42ec0c58ce1cb0c07cceb3a7d6208315558a569731e076ea261e333d5f3799ab3311337f1539abdec72f47d0a748030c78359a00c8da1536d1ea099a9ab54017faaa8292820c97306ec63bdd6c470c9b632639d039d2c9e02900fc476e02e854046d466b4f36f62ded599afbe74161a64c261467364f93ea8582ce45b9383a82b8670c0bcbd1c4b91d3efb22ef7f46a088a4a4b77101f51a1a01917306bbaafb8060002e50323a041964e97e415cbbbfb7312405ed281ceb15678d23e8f964f0a92be1caf2428dcf9421e526fa7b07ee8d1bbcb4141e7d16cfed5c79975a737dd16306929af90a8043f49d4f3f21858964eb0b1696b36ee96019b11ac7900c956533c2a3aed143c8886e2f44512b640f70bdc27715c827e939d9d10d3c1c15e7c290c14f8301e2c3d50641dfc5312f9ac0e7d3d4f713c1f0695cd67c11f00eb07c6ad06702fc34d78199daff51c5f13716f6e715d640bd2d460c0f7f46a7b8ca6393428e0aa6abd1c6293ec5fadd9fcdf32a9a0d0fed8c3c927671ab0b51bdfed4dccd7ce263a01c02a6b3ab167771eec099081fdb2b133f40e3bf33f7f8bb7efbd788e5e59ac7f61894a54edca4afb0aee95aa0031c73139534bc132e151c0e47a862f661d6297accf4eba38f82bf554e5839d9e84fc9b9f3235d5ca1e7c6291e8c2ee2bf1c88122aef5471b6df94672d865ec7a609c514b9ad4520653aabdea8e929e689a6f5d0a31f2f92ea0e69bf17e03da7c1cef5782ca17e3fc36287c6bdc2f839a8fc6f84bd0f0cdd8bf01353ee4b1e0ef05b508a32c5a40bfbea82844e4358aa03022d72b12e5885e5b112923c27545a70851e91aaf2814235e88da2fd6817df3974c9f6ab74499ba40d4ef4b7b3d7a32451d8dcacd34d6c7075c184a77c5eebacad4d4a5e461a13d1a06e0ec079e3db4be8ac235fccc5871baf8051cbae41d3d1df388dc8239c3d83233c7841b1111b1f5f65734c3110e66b0c85f9d557f1b761def57347488f227d8cccdbe4d26b256e711ecd3b4a89008ffa7244cd49c9e9213c056fb46d4d156c49bca491e793660d626856c76ec98bb3b28a96fc5cd8b8ca4dbc2a75ca03f1189bee5c221da6331cf05b6cfdefcfc1a69063a081192f0d2a837f442a5490ffd944ad65b453a8925435259be9b19903e8a6b9f8873672389a02508fa16f62c57a4354ec22c64d7dcc072e5aaebff9623c9bf2b60ff2790f21ecd2eb08f92c1937678052d0e9803bcef244a893696aadeb0b10105db9b47e63e9cee2a1fec2d81461009f77703d99ce3166dc4cb92d5b0fed068bc873ec12cf7e428aa7760f6536e154244b2ea2d1376aaf65fcefcdf51f897453dbff65fe868a675288a19e1ccbc7181821036a49ad0b1e573cb04a75bde6dc11a2e15eb5df3c191c0a16c544d2081b81e3d19d4264b9bfeff91691e1ce5a85d9da9d514e7a0892b75d3942a74c8605c763f4d5e62b006d851b3d33d3ad55608ab97f013adbec43ae1a3f44df0a2afabf2f735ef7f15a484d79d67b7e118ce1100077fcab3be464bed6bfd2f04216c3f0099a3e5bed805aae64937f4f08e47d05037d134cfb1c33d7cc50659f698a7bef4efa61b5ad33a8b89996b215216ee666deeae40db14f61b439bfddb9db4100f12685a18d8848ad09f76f87eecb55727bd800dd9b6c9cf5b402e8744843ddcd3a68e96ed6c66844803a50e0193c526afceabcc14c040d5934e5cb3553b878aab114c0416be450fa3b7f17c72a98f226909c06af8eb1393b34385be6568ebce082cf34ddeb5158c5e1a56c2128bf04d870da76e7d0280816f08b9d2427ec6174b6bc3d2b24fe391a548c67fea7964b521757f15a3005e0ab2ff7aacf477b91ae9205300468d061bc1f137970855faae974a9f301edcfd12ec0d093cc046715a3af5df6fb37f215d52477170d954e801f1ec63ee612c80749ab45b55c11e60186b04b9810402417db14481e552111992be1b8716f6adbd170dfe53c5f18fe91714b78a680c742d5fb517cb7247a71f9282cefcf75cc8fb5a29de817b0f5d0efe2cf95061a980c29f4ebdcb3d024e4df0b6b08d5b28019e289634a99bf85d87fbe6a252371d320a6d7d6889a529b4c9d2240c6daed9876a02c1d4e1b19b15afd0c680677950889d4d074c001536188229a5ffb868b2bc5f3836521270db06a507dbc9c31e78bdf1daeb3a6f847b9dfe81106bd632315ad30e0ce089f22e088d2c91ab3fdb0def6941fa1f87f87b87f77b57a9c41d873b0f03af7c7d3039d56c64a037bc91241e5b5422dd4279e2b19e6ccb7dccbe7a60ab361a871b97879fcde4dcf0c6b4131613c5b5930c5e982d47a8e2320168cad7cec052964b270bf756f90ef6785febebe584d4e0fb840c6d1e58fb6cd879128213332c2ce5b532acf08ab7d6204155089ec740bb4b6bdda4b614e24ecd6a4ff05e320d3adbd650d6e25d1a1276282e8923207d928528c89b8c611cbcf0b2fd9bbe8a1660e4968b4479cffbf594e7cf44c4bc29a06513f62a3d0396057586ea011ccd4294277536139528aca53f18ad770b5553697000a4cfe2f944c15fbc16dffd181ee3f21f5f50176be663c045385a13c07a7e8e06806e9d0b011de229286072eb477b01b9fc191b60323caf07f00e8e2302ddc68925003a3d4f0ae06ececb02adcd1930603a3fef0c606fce73038dc1893a30b2f78c826a5352f5e203cac93cfb19c442f75c667bc6c49aa004e381338c7eb01ec08590b0483aa2c5361a9de330e685412a3f31391d0ae843d44680cb1ff5033649ae07f3e92972fc00bd1d6de4e0ad4eaccc530b4b3249e7bf142af67e1dc361ecddc54886ca196e514e00219b11e6822ab5044e4f538883330147fa5d061796ed66338df7093e62755ea2b400a04897b14868e0b3e250d63845e4a58e9ae7ee6e7cf3e955630932209295f0f1c75f18c20beeafd307fa623a397d90c457e47302164d32cbbceff81ddb93111095ba7a93da9b2874ff725ddfaf44350fdf4c962c88eca40919aef636cc17fc756e291ccdc067005a0140d4148715345d56fb62fe3f6d1f5f26d4396999bc5301e90f38cdd4c983b8c58dcf95fc0e54930325f69a2d5f3201010d370c5226cbd8f1ffa8fd862d1765f2ac53ae6160452a8579b4173732106572efd027f0ac6d86c5c04874a3b92d8549a35285e2399192b424440f2db7564f9d4d8eebc6ce7880cd22a4fd81d22aaf5bfcdfc6bb2a806d76ea8803ef1407d125ee082c81faa1d32dd9f5935b03e55cfe5e1eb136b7c0bf40f45aa96e1ceacd6b658ccc1e9b3ba7d70f3fe28ef168f722d84835b75f4edb38039d1f9fa00bbc4e8b4885b2b26f9c3a65110f5243a7b5add2cb1edc2769f7e170605ee9d0599d50966030121da76c77e31d81e5ba18c41a8b8d74ab85a5b41195018efab65a43af449e0e4398e3988a50894128d5f07781fc1297b84cae009631236cb3dbf95fcf5379028671ba7f283ea3df8cb2745b8d0929865a9e91c5e0ce2816e40a468637ecca686cb91b0005e60cdb58c5be6040ebe497b90a9a8b56b506140f3ab8bc7d0b3ada7d508eb40b6853c365db80dc2d4e40d3a847e48e811ec2533d20d264f0bc4aa40e4515d22b6bc57041cb9a5b089ba9789f7887792f825653dd97b9a6413273314927421230cf0c8cfa56c7fa138e618d3b25fa34778d44247133f30db95f4bf933620d67869d322306f88c78c39920e7ccc4003f33d2f0cc7fa8ea07eeee4ca916334c60cf881d3b73d6c28c33c033e2c7ce94b4317306f4ccc8b13327adc44ce5f4210669324298556e44486dbf7e411439c0f38db806da858a4ef0d6f060b35e66674ee4dd99edfa403842b3b4a134cce22b5cf4531165024beaed6806a6ce850d83e010c476d04ebf0b530f00adafef4d7f7fec591da811ff1807c4953590233882da7a73cdc386dd34e2fc5b9092fdabd860ace2800c169a73130a9457b184b9f905f98f06a20112822f745900032d959a1d14bed30d6608712a3cf87be6542b2e8a5684da70800ac2c18a09c21159e80b7a5c5579b2985c2f6f0d759b436cada8856d6ee76299b3215b7f9e3c11988e06192a62364c46e4ba314e81afd7372167748f95a2905a8f0bb100209e0725935dcc9f9ebd7e532c32f9048b00dd6c63a906352ba1b77c9c42c1852c1304fb7513e83e2d4c7348a019f3854b80f4b8df30c58aa7f4a0bce59ceebbbc5f423b240973edaeca54781f6c9620a58cd3042fb8acc5c2e2dddfe5a009b0e8a5a6218302e37e841a5cd036621d6f1128e024e0123aab3ccbe0ced9c74a0ff3db9258f34c77e4b8885a206294321c3de8b70f646a95c15204df203f3263f2ecddd7a0ecd29ed746e503f67fadff19a7c0d5746259f4c83070bb0bc85fad63b9c09b95d3acbea17f01d7ac416f0936a1d5a18f9e8a7d6e4047780ef047b708b3a472087a1dfda407e8237f0a9de28a6bcfa18e9b28c1dc29b27108defc47beb024b84ba02cffcf7ed0c4091abe657d30edeb378d42369dd1c40ea48ce45250d06cd93eb5ffd64467f0843cc76573137f6ceff412d4ce8116dae0e926b02e42c58caf3ee6b9f0c9dca32ed1d768ffe5a450f3668f4f55fd24995294d887af7a21a8b153c8bc66d59040105c3a78cf1db2c15cb87a1635ae865c8d4efc85ab3cd7c7c402bf3fef49e76a5cdeaaff1e019dc739d4b4571d26bb2712437622cc9442fbc74341c12d48f6eaf5be831ab3c810ac13ba5bef1b3a2eaf6017f456906857f747cac2683752c493bf0ebad19116a0c736fd475c039b513f043865369c6d38d42229341cc19356a0fd8638825ee6eba4ebf183127f1be82bf8e757f1af293f3595330940401f79c7880819b6d819f61ec23df8d5a1a5af5a784b1c388ec9f8e1a98e5e2268b33fe4f7f6f7eddb155c246bd5bb0bcd82705014ecc762570624861a742d1f5c06dd0b47457fa69da826804bad756ebe54459c453c965c365fea70d75366920b912b191263c5b0014dfa22d6ea03e8ec26f8a522af4f9832825a3124c9435a4d57ff4a41d168ae0492fff52a3ba497c0cf861fad8c2df24e93a4bd0d46038c0ac9ce595eccaa8367f1f8270a7d2a2fe410b427859e2a33d544f7f0b6a2f7f5cf94beefb8aa93d5f386792ab2ee71965c27df5f9a7b6e5a1580ee4c146611fa9f37127c7e610cada7b5139435e787d7d4cfbcd0f1d65f15babf5f7fde82e4b6e78a0ba37eb501941942efba8261fd075a67d74fd8617704d34699ae88b28070743634c7d50194f838e3c691ef51644b16d61c7a27fcfc279436e409e9894f7fee65192cad5e6109d1f1ab34b686ba826758171b5403273f5a6d358885c056ee418b65c8f82a3d1d3293d061c05a6f386b51bc2c0c2f32f4b976e26c316e6f4a3d6f03a9f03da7633a93deaa906259ed45bc5541bfcec4e77e9f41cb2333386403e8f0a08690e77072d5b33f52212db7239c7a98e0cfbec652f20f76ba1a2b0caf5b6568f85edb7c1a18a4ed6597b15e2308e32012654eda6006b280521d581869695f4185376e3af3fc19af75d36bd28ffef27a716f6c7a3e69842821ea4bb8c94ae1f5761186536339cecddb87d55ebbd7eede410cf3276ad19fb319ee24c664438ec53ac5ff7ea535d2ec042eafe783a4a0327a6524de5abfa5b21d42791a7de65d7c7d853b61e935771bf3903f679f295b98aafc598ecde32bb330877c308e2215176d9d6fca5f9a8b6c0041f540cc7ec925ff761854511cebda42178e07c8477fcccadd3234aa72397bf468ba78c3991c93e248dfff503e018297e5ab9a81e3722a4afa6c5897484364c87e85d29b00189bf4e9980b80bcc7e866060e5debb5f4d414da75c75470d9784d272431ed008d0d19441087ac4de0408360f546df71c59b7c14ebae2b47bea32d87099a9a9c3d84d876eb8a4bcf330a593b966008df0305f91af33918812c5b47d09f8b0c4b2cf5ac897c7723f394642d108e48e4641c3c7cdc12fb2762d8b361fc0ccc5c8690a77b52c0ca804820c9fee06305d8dbb7aa34f55a3423ef02a27f3c5a8021ce3c29e26c6bd0bf7930e861a93d3504f5f828661e3b20b51ae5be06853bab2f1f77d4b668e88fd6dac7f17f5492b309fac76b6de658ebd394dd862fc7384371c606d9747e655cd4db1775a6626c0d27d9dea0fa68e46edcbdde1474f381ee77aedd899bd657430ce2af23146dddcc3fdca4b0c11fda79bea81543cf219fa635bd305fe9f3d9038e3b96ad39f07b0883ba7e0a632c34bd846ae9ff8c576944956d76dc8af4e948d3f20379a3950fc8e16a1aa9e686c12035bd765167b29a07d2b9b03bd45799140425f5fcba401d34ab7078a8363db655aa4d12a455dd2691307bb2f5b8492746554dbe73eb2feb6ece1b1bb06439992beaf2b6bca8c2dd3b58b0921e847d2cf3f953c7874588c0c16fd97b061dc83ce40c93064bb64fbe3346fdd1f73838d2367ba0e928c4d4a44ee09959bf200d86f98c61cd85326011bf4fabf69409d568eb59acf05c7c9294f9588b18568813624e9c98f19ba15af6f8fa9c7be3ef9cbb6e57d14745c68a4d731e772b161cd8163fa97dc2dc0aec15ced96b06670a701d0ef7e129807ac0058e792def87b23582a58bdbc77749d97d2c3eb9714f957be0904dbab6d632146c26e1a1b519d5861956e5b4d1f08d5eba89967bcf72623855f1c9be1c29ac01176804899359d5b608000518c5556ab74e6f8ce8a1c93e9ee781f05632914fd0be2ed4797176f7f66b41b0eac32cd5550d34609544b36255ee015353daca301e4d70e859aaaed7c0a4c6da3c1ca8f290baa17a15730f33705098727e834d27ccb40bd7b8a0e1ac7ee9596672fb7321fe16b4cc0fe71db39ad7b27aa3a049eba7e0974bca1f3476e5950a155624c4049b788310333b4b52ecaa00043af655f7c4af1c08eea3fd785c04a95acba2c9975dd4d0a56eb29a6cbb18d88117232cdab75f7e6dabc7d2140bb352e51cde421dc9200228af321555748ddd9b03f0facd297e3abeff79c7a5f0c651ee99c2d762a3a40b219a8c77518c5070879c17444736eb824d55b8a3b06bacde020d062f2a6021fa2909c566806beda3492394723b258151a907aad0b5f1990f7313918a7867b299ddeca9d21d9558788cf9e5ca9e390661fea57813417067ce44b18d9cca36274ed859e27596fa43f89cc72ea0de0b2c13d8d4c41e9c355476519b556d27c27d1e4b772b3b03c06083b3202efa8eca58c81e53a3060631a6a21a132de43c8a5c044d44fd6aa47e7cab2a4892e2ab7ae66dd12962d1e70976763ac6af0063cd6a5508c39395d2f21907be538e5bb5af2a1a4d20a0ddf0a6376b189f0f51e81f590d52f553017cebbe1ea379a225fb912380753e63d4f3bec1f82226bd90ad6a02a1def25617f40dc07062eb0b0b567228374ac5078c3587a92d83f247497a83e437b77b28087756cce2ceadeda5e9b8bf52c5c93d8f524be5ab71c0ffba40cd19d9925acc8d6d45ccf5ac34b0ac19487eb14c465bf8bca7b1b501d47b445a2360e786eb465da47775b19bbe9009135e03f7bae64ee31ab215435dc2546c52a2885e480a998a3553425441778d20f11d8a223dfb40c72730a14f6f3760c5dce231e08a0fd4139730763ff16e91fd24f72e5361a31f4d43487c0f27086124288bbe2fd00a0323002062acf4db0036a5d7c0974c5f75d340abc0dbeab28cf4547551c5a0f12fcda0243977eb49b540ea4b3558775844bb52c973a3f98aa790c58880c6dfd46e1831e7679280aee66f79205826851bbcef33c049bd1df762415eb60b00b290615a6a0be09eb78f1d46817fc316ad261ed8029dd4241750ddf2b18aca20d6802e4470cd30a26a4845db23dbaed97531174ea124db070c71aa9bb86d9545353e79041c19bc699b14d2e6e3abaf534e7ed2fd75efd0687791b8f118dd730d88219fc46cd278472df487ac3dd09a39615f23ea996fc0175963f33637f8a491480be639653c86322106da8dc79a62847425d2978cfd6b73bda90395a067adfa28cb039d34a6ba1b6dcdac30046a9d57582e5c2ac238e42bee69237b09666c64f980e69106f582163beefdeb481fa604209a032d24add941f08113872975e507a1c2afe7765662b036da193261527ec65f228955c23ea2ab0a59c5b579d063c8c3e96499267d04c27e06874ef8b9cdd17e6a48766ea1f9d98a3dbeee6fbea4d71f54747763016a6980d0b544b0adf660435646e502a5ca13e886c20a3eceaf540b29a2bd592150f5100ffcc93d0c0f209c2d7fa703e28db03940148318124327b56a7d1a1afeb9f9243badf66e94ce035acfc7f0cd5acecac50baea030ab869cf0f11860a972ff0635731b6f5489f82467f9ec114acdad506b4cab2d3a2955f305b5c94d6a15ecaffeb4911f5ad18e811018931146972480c008648e233c69a779e2be12950bdfe1444de150475fa8f1a7805c791fdd9f61ed605fe2e99fe07fa3a176fc2ce8333f1f2002f274f560d948199085a738903b753846c86856e23fa9981f3302246d0a0598ad82b589b9e45d08cd5284cceb3028eb290aceb8077a019262f1d99611cb553fd93e358e6f5f06fbe8630245694a8515478349e0223a27ae6d096148dc86d98ce9a4ba8b3fa34188124fdac55858e98fa186a07c23c9301317cbdaa261249e631e2ba572e6de1ae5a700dc21b6b7e90422ac37e04318ac9b616d9dd42548482f6e770f940faa2145126236b8a2ad690299ed6257f5f894fce70389432f15b1a60c68676772d496b7238e16d875d505a3685805ca2db5c4471613b25361dcc74426bdc73f5279fa8f23a7fa94c04724f0d95653991e04de23ee3ffd217e0f1b86f60b06aa1420a67349647834ab3fa6fafe676d50aabd13fa7e3904e82855d8894e235c850f95987c89643e297e7136145d4cd741d3e936073e77dc80abfe54b059ff994479cbbf07aabf3aa6f147a62a61f2191a63242d40734c7000ccc8cbb952c46c5c3343dd0a17f579673bd669aebd77ddf1293617a4bd743f884f6c3578d9fa805c745a7555c4a38988229689d140705946c5788854d9b70166cd0bca1403ce17000b5adb70ef97c486711dca4ae77e868c9ba7eb30b847c34d08e4f938cb2f50f2619a5b78fd1042e0351353616b1c71213a035e3dafe768057399ce7705e1f6ac57f1e1a8d90084283a2c23c1b977c199b07787e458871b79a589fd8d450547dbe17f4b6dc6a1259b6aa9bfea798042211c6793a77571f1432037c4bc061a07a8e42b7fcd8dc517bef25ce2ac005c6f0960fb9653b2ae3edc92846975f9d3c01b6788067411647925e92fbdff7a9a845e3037ff85e34e1f0e483601813ee91aeeef8ebc1dc1a33282e12ee1503107b74966720d18a9adb7573f4aaa1c11534bd8e375b243ec8b6aebafd89f76bc1258c576a2f747bd31dd9ee0863a4839d55b045412915584a330c4c6ac0a2808b48308c8ce88c181d98ced1a863af89b88f38a18d1be86af29e72b29d012794d66ba27c83304d7d5c30739d636d77fadeea3fb2471d39267885dc90f996e9e67885db8ca4013bb2ceb2b6227c4e709789191b08061b344a95be2a13004870b97fced707686b86159003884beeaea168da566c3cf5411eab9326ac56e4f7571b5f1e7dd688a51bf07ebe26e2c4873e2bebaf8927a2491aa3a745a039a57b18686ae78ef47addc24eeb8e9847bca685dabd89aa6328b8175cf9d25daba005fd415d7a045ac5dbf548bc94f3b46c8b2a8e473755f4b0a942e005c02e313b69fd132468ed59d11ba8812f3eec0eb4015fcf22f72a1e9f6bb5cfbc9d205b110caed0d6635eb55eef15dbf636e982503be9851fa965b81a12ef037ddabf2d4ef1e0207bdce909c9d6ffd36e2a5420a318a412ccc02f4038992cc3689c53e5fc3d28270ba99f75ccc7c052f704d74c3136a87f017bad377767851f7b2c7f0b4161b839aba7ae4d689bc1a9f8e5c505275c41af6d045ff7bcd2b3bcfdd1914b3eec3aa4f14e1d90e9aeaf244f1c0511ba8d7456616fc1db84753758343d02c1d124287b7283ff82ae8cc2ba0dc4fa5a06fa712b06e842def1c0d94ee6c05a4db5ccac50ddfe4574d37ad02e53e95667d43a9f9337420e9e7d52adcf8d653f5cd548aeac69a7aefab405dd762a9a41be4c6d8b3cc46fa6afe25b46fad8b886729266875a869c68053afb2d107e6044709d6e74b866e0fb98930a55b223eeb5041ad9f38f22898aa2bdda2309822b97c042ee0072ce99c2ddd42082a0c89c49c5fdea096e4c5cc6e906f41bd0f6bf5e253d82598d8cc26b437cab08d4c1601f4dcf3e8b28f28b73014ebc95c98694d5e8e8a603a09a72a7d6c662b722911c9084a82580243fa0188827f9e6569baa54731c7fe370d2eca0808a2d04690b29694438d86ef8497624897fa28101ea6be960d05712a23bc489f275ddcd9a46e3b417656b6c1f44a9a9dbdf243ae82426069038b23e65192ffd9d3b5cb1d4629a4cba912060898db5b1c02bd7bd20c28b8c37c368621c0327095df2dd67b459a564c84bd83a0f898406d4770c9182b4d4aa17e4396c14682b1b552f449fa474515a50b4b164030caff0a4d67324fe8528cf802b0101c526e8596641d1389e409c002dbd3153eb905c9198a05e1a5fceedbaaa27375a93f78b58c0983217ae2533208e865badecb3f3f6290ac5ad11ea9676084ae1cdc6e7be26c6b43f0d2df602ec880218c769f5954f4093f215dab94d16627a0e20e682e9c9a572090eb8e09330bc2e01651e521c06f4910602dcf04070593dc45812d14581e0e0394c819efe01096ab6d80fbf2eb05e44731e60763b07b6dbea07c0e026fef353879c1696adc8e251779c87501747d17c9f228f19ffbe791c42956025ddb8d83491439f633debf1084ac0515510008e8d9fecb00c0ea6981286b890d43bc9b020c585641ca58542c210a3267e04149e6bc13e3e0046a79f679613823d6220d6ba69ca3855ca78998098943318444c25f53801dbed9f8a343bd59c5b55924f2a6fdf75e9855f4d40763e50a2b29669417734a52a03d1fe5bd80bb721d79bc290fa2b52d227e4aac9c0576e905fc9c428cccf6965f6a15ac2ff4810e274f30a6423241e4e31b5f6d88f06d135a909189fd3a45ea22b3a614a15345d6b552842e26c2e3f2d14d649f2a83e5f8b729c33c9580b05bff20340b84dc4dfea311db926089a4a15e279435889f117f112d21022be8053095c95e3380b72119fdb5571c2216e9d8142a7c212b78fe35adeb724959de4e2821a9dd4baea9015784837f68828a1e65635f2305d042fc3fb0014071a501b91f497e232a9035888a4b1c7fab4113a8fdb57855af676255caa0f0e220c26ec84bbbdea5495399d4ebbfcb1bf9b0f846eb3d255bd29310a3c63c31222b70f15514f1f1f7bfc08259d5542270017a0dce17496658d9245d5fa19227f0c04872467e5e1d6c4ae4203f134a1044036569222772640175318369c12ad0a5c8dc5700b42cc16a45fe0c6b9c2d4fa00a5de2451cd88a1f881451951f85f4820abfa21d252a8661a234038891dc3401a454efe40c3a475c8824a7ba93f19d9c2bcd36b1ee27142024f71dfd107fa42c9ffedcb869bcf4126a3fc9ae02019ec288a07e43b23904fb19e481fab5dcc818daae2566d31b28a876958c411111a58fdd256695631223a77fea95f45f216a431e3915603fccf8fd7700e1a233dcfd10dbfa60b0b65d20a08c075fc9f5eb8f90a3e3fc4990a92cb7633fb15f8c1a11b683f7b346ef877cbdf0172c31571650af0d6cf204b2bf142ce0fc3518c3f172e364209d6ff49d5b4b699540114480040d631f9d2e8930f89b26ae1b83223d9adf8930fc06136d1434e358d0c0f239ff77012ce3d035b46c0c7ffcc06dd08e130a61e72240150c6fc8bc9dd28b1b1fa9837b3072af7f3304fa4f0ad3bdfd622efdb5b505f583ef48bb8fdac04febf59539e43ac2398d7f615109e57581b877b5a4ea802ba87d5bd58412a4a8b7c0c7256b57079412ffeb240d2859a795f70681d6dc56f610203be5eba6faa867262e1a9b61df613a5b4bb031d3b7a4758fca17375a8e7918a4a9f127a00801ed78df98f82492ac9cfe80509517fa1c72cfda252b18264f1a63084ed062deaccb83ef5a6faf2be4bcb75b6695dbf86fb3ead978c035cf1db25dab44cd2b58a8780e501235897f00292b65f7d49f3c9ac7c63a9d4ee5c006daf9d9f97e080f25f011612f9f03637edd50e7e1b26e441f6ac4fd3360d94caae4eb540029f1561bd88ace2187a00e862ab50461cba9c8a11d3d8ea4d09883ddcc708c7720da403ed8f38826a515a4a7dfaf2bd3c413f63e7a56d9aa4e2543b57cba29761329054c9aefe0890178ab19d284df2dfc7b021aa4ace363cbfb0024d0144c160875e8d2492464f49b419558d144d51be529f3d6a3146dc1ae9ece67d914e5f3998d8f447d0462d56a7c7a75b858397fdc1124fb675ae48d910aa453692d0ae15d686dc53cd842dfb8a1858011e23039a9d2a6913bcfc562f69d877d63cd432af40d1a0eef1b5cd9929cfe94d1c12d2f019d6c3d20bb6e40de4c28ba40e8bdce77964036a38c5b4f2f50de82b59484b30577d9865904b884d640af5f40163c05d65f071c4334cc13ccd925f00322f9c268eca046760f3c32c2ccd7a64679b8ffb028675690ab0ed05a72d907a1db81e752ee76a4082611761aa6a3a490700659588d96ecb9e6bd7d5a6838c01afc8e42c19635707df8080bb36c9c3eab64225bd954e5eba9f97d6deeec58810dcc3db3b364bdec25df1e0837bcdcf3445a31961b676783e82706797375cb12b4477fad0be564cca013da8b9f5d0edc3f9a65658070af85e23b82e0efe03ffc04414f53ddaec5caad7484024cb1d620a2bce3132ec44d0dca77e39e81f727e6b7ab3943bc6bb1a6b51b1ecd9359348934b9d67c16b428f0e126fb24bd6854d7da093ca62b1d0636309710a93ad5376046a8c16c1dd1417e10b8e9d0d5fc72171985fe7af915eede448582a97e8368d574907b022c1eccc582e54496d669b3324556ed2935181bd5cc52b2a243724a533d04293f7da46fe367d1fe03b2a0750e5b01a7d4700eecc3df0f1f05d0afda7c8b1cc29a16adc87c94653d6aad7f084bf1145bf1324cf937b2abd997793ea891fc41a588d6d219f2261d3b0991f6954709a6c5dae97ca4c3a28cdeb8ef2d18b4c553b8249bcc1a08fde0f2b0ec5e0e30967b6656ba549c0bd37db0ddd6defaf3c539d6eb838b788732eefb00e9eee9275095619a4de9786f3ef80e9718aa0bde583b5aff4e2b537e5533360365a895403907116049bc6e8d76ef146d728073f232ae402e905f616300d8c80eb60593f7513cd478a062619f6296da3f03623b5907e2624841f3d6f0b8ea5f9efed2f04ef480fcc3a1e306de81b08c3d8d5c07bf195020011c75d00354edb02d24062b823c3a9bc7fe689625b65ca456c3c7b69854d0e585bcd6c25a2d2774dd0a82d0ec0bd5bcac4cbefcb5cd67d96a8081d4f4c07f35abb61b83b08483aba54551578f7a077da027151608c24429063f82054c512314ba2bfa210074c1693cb5e114f1e35ba9a593b78a2377ac4f01f9070e1fe5b5e69c122bfc95208756e415b7579ec04cf82919f2abe8b6f02931476897224415abc8d0b98da5f53bcf6f2f834a27099fd03780185a84c7bfab8611706f75bf69af1144f35b8d86d7d4e248fe9431fdad51ea65e35e57c7b3949f323ebabe8e80c2b3125b20c74e42956ef7d856cb089a64013a019bb41bdada94bcb77cdb702a7158a91d81ed090fb0e0b23ace38778a8b856500598c23918ef83b98ef5c15e0e08e8e687e1c1c30bcbfe8867caff21fc25701e1287d3faef7a13d0071b014ff5a2472b02b2c022ecc9386c09b87a5a1640ac261906cf93e36ed13bab56398a913287553c62d375f97083cde3b87394e2d915e7d99c7e5239b9974075da2905689595965bb04cc8561074d506cfeaa2d90ad112497bccc332488bf9a07f42c3709a0daa838a61002ed92a56014b2bee5b0c6c1a64c9cab73ad661927a77c87bcd6733960377a7995d68c4152657b75029c912dd57971c74a1b5a66d41ddb9a034076fd840878aa113d06eaad84cc0782afe477ea2b0b64a3c07a72e7acbd89b1ac3a3272c013b318a7f72bc1385f4c000b1c419a8e4300493ddaba8c55b372338d1423368227f6145b516bbd7cdfac218977c620197b98c47a0f39e116056678ac5741cb13d342be114c34cdeab99d4ee74fcc7b7c3de6765b93e93c2ad99688eedfa4abe232d954fe340cfdd8c116b8a9470ca327e53f1a77a2dd2dc74da7ecf47654752d2a2a77018c4759606ebbdf574cc8db3f98ae6bb7c45b88337408c91ca6246cfacd3aecd625a98886f39f1a06c7851c6ee6af881ba7b7fae98c220cfae2934c45de8a5d216675d3ca17a88eb81eae19585cd5e46c8fc358060d322bf028b40c8baf6bdca14e4ac2f6cca0afe52ec262083705e28d2ff4844ab5ff5e8f0d8249ad9ec58f22f1f55cb44fd938e424c31e470e0096265aeede9da89677c4d30267e0c3a7da45e0a6b5dc5d927a1acb462ab576e9bc2ec6e5c1eca7c73c70861b279ba0f648f25ac65358de0cb4baa139e68e93a4e2998a056041a7a2d3c572b4dbcd0e4b900734f2046c6de7be9a912ebfccd5591f77cb46022d10908c3308c4b810e8c0e11abc0c8dcc93637d657e3ce21dc75e3bf1e89b8fc187686d001a2519f571f67c958065411ad4c713043fabd26eda780803d2764c1c4e629573f95aeea8ac8dcf67d9c9c3ad0fd7af529c6bb9b09f7f9aa5962bfd8768b97f91baa5754d4b1a6d3310ad6070a75d14492a2dc5f599578eeda03eac1f76b437f4168e508cebcdd7a7e295bb7c634e1372b21e976bcc77c468226b65e2731ca96de4e50128ed7233439093886ba5c02ad2a53dc96b9752ad5e8f191cb63d28c644e5809e008db48360b93bb687d79502ff9278b36943a1b713de376419a9a7132099f420587b4653c984c22a4388a66f7578fd070836c235360c8a04da04d2b3f3402707a5681de38f7362580e03c1635f4de17a7ff4ade1bec9af650672110b0fc6507787ab5a3614499fbd80b4b8021dca110f649c01e792a6f2f18cfcfffa8b2b343fc70ac4ad1320e51adbd8feb442f061ae11e2ec7caa5b9da296cbfc91fba1c306b280e0c7826975aeecc4d6eb401564dfe5ee7bf2967f530bc597736421603019ecd29616d711bb78b6463dd9c5439bfcc1a6c65362b9aedd623c65a3e45b5e3c25acd7ff76a9584b53ff99be1fd7d9cfc3978aaa761f475ae969bda21fe811fc9c5d308da4625ba6b5b711ab980abb665fbca0dc665210bccfafe3664bf97408779c2fd399cbe4ebe5d7f9154958380b417dd18c1bb659b1f1e4cc2f6df9527a6ddb60f9fbe49b8cb30896647a66c1edf731bfe822f445b0f2bea267b39df70a466fa8378dcc30a69209bbe481e24e83d0a6b30178e20bb9a4785024480635d0237a393075b7cf890b60526e20d9fb29e9b56c5ba7c89ccb1506ac5ab072dec806c1325a700c0c3d1db1c70ecd1eb7786a8c3ecaf92107a48e148b01e0c68fd1dee68a839ae73846363db00fe058f68d73412e4a996e6b03d91efe58e156ae75df195cb752c5e8005e66ae0de5c0a5eb955eba1555f0086e81604dc2516c6be45541df938c44b0f9075ca35899edd226a10edf2ba08c2f32ccfc67884c447bd77b9caa9cf6f37997328401138ad0f990e887ba8d485deda71a88e1bd378ed82bba32758b593de82c36d5931363cb6162b03c950ae76da9facbd236146c3664d54f00dbf80373b6e17a36a9956dc79f8d4b8366395948e00f21c70450b77e5a5ea2bc22b1d45583eca444eeccc78934c8f582604047fe7666aa9b836a47e11812e0b19e874afd900dfd6011665f7271033a03fb58ec03f73c80433f508dc2b9d2d9834314d5a40891f1a0cf063b2fc6d4ac82ef7527335de033e61b516341775279850e7214c6084ff70e9268140a19e9f71a13dd98f804726149e716592078ab6256e2a8eb1e1cf4dfca9116983e0c708f16e83f5053be8a9c847789ec16c66ffdd01a918930b8cd59f5a0a2f822e194d017dbd6d8235933e4854716a28e9e7a1c16d9074a801017748c90fe289ebf9fa31c70327074a4a569909e4e8ea9c706f39d789efb2bdcbcb2dde2c0e774e64113fe4d4e8e8d69207ce790a3556ba90d4e1afb71a4cc105c35d38412453f34de3df659284fe497d3e4dfadb6c52517e01fbb3d99f685470495f85e904d6bf3aa11c86b2cdb7b4da9f26a00ad64d1f31ee3e7e64372d53ce7fe5610b2880a3b93f1ff52862baea3faedac5810f7d567a0602c56fc3d0c21ad5ef673a711ec6339bee4af4da2cfa5b817a090e24c3076318f48fe6f13207f7b5a9639944aba054e14afa44117cf390c54ce367154748c0cd94eae6c809b34f86b6c49645fda49a3b0f4e96c020693b21f21f907159cb0d505e4bb5a2cd607a967269c61458959b1a39e7979805af402c47cd7e441b03fcac68d22e19dfb15d9c379710df255204d33b173d3c5ce40b303510920976f57431d826ec21efcdcb71d1075dbff8b1ee5f0215dae3fe7484a47e221a62b73a5e40fc4c98cf793bca4bbe615f5230379e018df8d6b4815fc3928126d6372a86c62518caf39c7dd40fd656a5a91f359da96a4bc72d7c506879ea3b008618ac08fdf7f700165727a2350e69644e8da265413e698e4d81ec1db4cb8a3feeb4d823b0bcc51a4b372b5899308596220a659b0d5c484671d7f525ddadf94e3504ae042bf207ad8e1f099f46a7f222b65a67b81c35cca4207738e1bb3b23dcc14c253d47f723e7b176f86400663fd096ca41e57f531e77671e1e0f42b34d308b1d24e5775b821712a28f16eadfc711f300635fc088da54ae24d3c31c6873a5589507a7876d159ed6eb356e8d41f0f624c3fdd12515d0409ecd00f2f2187a64a8f63d1becc232ab15df552c8a4870e26e8fcc7a8594a7ea71cfc8b147f5164973436f3e03153ffb6266b32a98e45833403550e0bd181dc18bae03326d876c36a899e329621ba5d293b26795764d9e92a200c64388c1647aea48d452de1f37b7c9809c2eda58244e7d73a2123d8971461be1ee220b6869082c6f1889b44dcb07fc1bf9d4605b68dec33fbc4f48f1389058e00a7219e67ce1efa51bf61922cd050f10bdb7f1cda062ac405b3f0d877fd39d1b57884c5078ddb0ef998e60e3ef91a1fad65d1936fb4e4ebecc96802707f68da8251c678cf7d50dcafff368163e1b3e5ae0d23b1606077d639a5464878a873aa5b30bd32b9b5ecff6e8bcf2908e7c68a57dd9bf9878b8b9466cfe8373e20ddff15353cdb1e593dbcbf5a74f546e67a415404b36affbfc74e3949016e2ba12adbb47c09f83ab0a7131572f2956e29a5a4c24a427d062059aa74fb5852a41dbef34682bf2020525f2eac5118d4201305f83e7c86ef186c7608659d3c93c560b2a16bee6aceb98e1b4a2f868a77384b3a32bc734c498dd72fc05464717b47885e9e15fe20997860b9ee589d055c8ff5172c219f79ce2342a54f6a0f702eb632e1fbf2845144c3526e980708a62771eb8eee376defd2a0941744de8b09b8567e7a53e5af77a426f791f100e419e5d482d1364cd7bcaf72880904d2bb01be311de5dfcd238630daf633efa0931ae6f8bce5153888656b7cc5967e8af45e5aa00bc61d4ae26e1c90c149456f01e18e20a0a5223e1abffac9b54ad0d25da47baab132075df84e09bae5a6a09eae8f810cad515564692a774539d2f30b6a4ad792b794fc978eb2a9c809a1e16fe827493d2eb5fb16e524d78d4faa7d4833d12bc64fbd7e574b8656bca2d447eb245789c12439860aef7ae42ca89cebf9a41c9155c3bb627e538bc4a570a6924fb342e428834755fc5a11c2d5c04d095f5d81340570bffa2a95cf4375586af19b5825110c58b469e838dff17f0498126456ee2ee682ab58151aadb818c5a2a9f866b97d2144d7d0f997f9180c56f1a6b528ffc0da309797647a1f60ca355d29d7412057a174909da9a0d43281112d315f7e41f382c5edb367117f54e38d21d93170234225d57d32de69441fb927aaf23fde2b79e519f197a4a2ff0e8b20bf9ce760c6a678df30dbc9ed7347d44c8e553940888663268726783a8485fde2809c7d466f0d5e0ba72b5491ef8ae9a5ebb1aec6758df9ffc582d4bb2c74d2f61111683114ac172dbcd5a7487ce7cfeec49dbd4e6c9360d93871fddcc21656534ef4f7058026663d71f47bcab0492a8fdc0f15b95fe281aefc83f649ab7ac53e3ed54470a6360fd6cc371ac1674e61356ab34a62998a05552ca6395de3484180036fc9981a70bd506478c3afd6e766bed4fc94bd747b9f1e34c28178b72a5a0a462cfaffed7c4951760c31fb7be72e9fa2061b871ff356e809a786367fb8bb1fcf9f90cc9914c58ced2f39b58c8453637f753e557c943db6eea7a7fa18d484d24351f375e1799c669368efecce0c1af8d8dd02f0b353c46768db5e09b11400c690d9c3fd702df34fec4841d407230744d7326d0bc25bf52654e63306aee07cb7412b04b939f01180619afee5ec1d41a7037faa3fb8c76491c9c2cf5f244b66df68f7e934ea7d505117e8bfcd03c80229abd2e982136bf54d72d3619276c368981ee8561d3347be836b059cdab9f89ac48155f9123b4f9def89e713345442ca802271cd86417c36d3a9dbe765b6fc2f2f233a329df0c0289385419394fa589b8bd01b41216161267d802f92618a30173dff0aea501af8d3e891106b1538282a3c27cf4f5bfe4bdf3691e5c39fc5443825cadf90616d169adcfa93d7cd9b291d9519598741b7e8a311f1fae38251a74c06363c97d8d2aadd13ac59cf407cb5bb6f551e43fffe82fdf82c19e3ffb765558f1a636aac8ff6e66521c6dc19dd7773527a5e1fd453a2693a0dd4b49123760b118489293ad1c76a5805c8ac9698fad26ba315d4e418fa122205e2e80cda748541a6c1959eeee559b4edc41abbb061821a207d2205be825b516fba9925c6e8af9eb21e8be21452f7d00d425acd07eddeb569c6574a51a08cf8198855cc48cb86a0120fb4ac5009cbd0883ca897e8e5adc2dca01a4bd0d83a8d3f828a0118b5fb266ebaa5462189dd9e965a54001bfc63be31a110813cd436c19676d8738bf0a6a19d449471fa94e01ca4632283e136961928443e37ab3c2369ca77b9c9287d017af21b10bc6c1f2b3779aec62434ef47423ffd4b98496a1138032968ccb54cbc384179b35a29ed1ebce6c9cf17c84f6bd8f77d1c3f7e291bf42e91535508e1b922403e827ed403f775ae45d3907c54f22a879dc9f5818318cd610c6643d594a46f4d5b6fc800a2a97d543198b298cedc97286c2453e74b574f6b338f745ac1c678ded403f176a9eff35213d430531b1fb24bc23b5bb4d50d5e88f12767f973cb067931d5726c5c282db75f2579802e5d8c0abbeb8d6d2def38efe3d62af2133f8fdf570275f60e4bba187b29864f89cd20267dcceca8c68a28f84672133ffda9c0c37382c194c64829a66e0edb4882377bee1beb28d56406acdfe001f70b1d9f33c6cb61b496d2685f9a60a053b6fbeeb1f4140d64d0e17a8c74c3b33b05541274f4638fc556b427f09a7eaab85fed5690e17c001155a9b70498226fd2462346d825f34794239b50718268de66427d7f2121a18843af46dbe0def3992c3d6af00052f7bb5e1ee662eff90bff25fe02ad5f14e883e8344bd6ef399718f61d434c53a1a7d9a3c8838347b5d3c1530c4cc29534a66a20cb018ae186622219bef3ad6c48af250f7dc2eab581089623680681328ff2f1146522e4d597946bcfcca8f045ec678d43d0fb66434f64989efd49fb0a79dc207f7f1d39ca6b100b03b5e633210a8059588bc8964840bb3b4a060f4faa198f12b2d18db4433f07d0c5145c6009eef34d0bbff963ca1c5a79fe995188f778f19ddb0230247a73234cba5e4d282bf9291cfe7a5004c42d8d0762ffb42160c286e49f0b19575fafdfff825e7d2c48a57e391e8803d8ce1292dd69c5450820a61170950553e04dacbe037f27d7b877ee4c066e6fae5e0991f4c3d4e010a60a2c1594d8544337c910d3452d441b0d691f86585fc17d78e92d3d07147a1e7a2ef4d4e9b52252fb5e5be1e222be71edca01067b95888047cc96a3e9bb35500735d8faf3509d5ed14c0c041c3edb1578c063c68af88e368822fa24380304a79ecdab978c7c637c54921927ebf5e6a8338dc26ddbf5352ef46620b72a0c2710c62b072d76fa6e518f5ff1d3b12cd53b20a298984197050f8b55e88026de10c0c3603454d2c265809f1144ae101115901126958179fcf16eaa41769831071eb39d3b36c8cbca5015a30ec1486e0ee40898e6b6254aec556dd1099930a7c0d85b5e12bf976fa176151acb1ca0d328ac659b685012702f29c690eee9c9ac0d92150b1176a613910999e2ea2dc7cc1d5d8f127c47a012a033e83f0da5e8a925a63b53ed5e49692dee7bf2d2307581f7b5c2922a5b69cb1726f5cb5204f491f2fc4bbb8990acb6b4430419f967adaad2f4ffaea7030c1123c6a817002dd11321452c1d66c5e4c4b0803901fbb7924cfb00fea1cabbe7454b98527a9d98b0f5bd8a8ebf3f36e52685a7431b8120887b7b671aa243a5920caa146304294a9ace20ed806b6da78b4e6d3fc2225cce2d134d3e3d436f2b52dbd1d305954e9064f0e886ecb7c9a88e6d8797ce2efb83f9516ee3c0ca207bcb1e0c0bd887f39aa4c44ede104c22c132ffa3a3607ebd1f40156f08e83f0054e9c46ec5c807ab812c5a7708de2ae115a1a2c20ce018a9442a045d3205a94ad1d9039d2014024c468fbb63873dea36329966a0f2f4e082b5d8e7e6b80ab9fee0a76c935af1fc7ee3d68d00863d81842ee247aad77105b6cc27f7cfc03c7169dcc6354bb8e33cce5279054d03ab2f83c6d94cd351224a00b1d9f3663234f46a78a7407eb11ec5521124be3d8513ce4214ada4a85b8017c72d4f16d9b94d541e277e1afbde4e93c3e7246bf324d1b7525037c8e70c7bc26f759ddc97449f15b5936c408c62e6c801048e232f7c4ce7277019c199c3e8c69b2c99d3fd0871782307cddb19664912e702e650d7d9414b56e70ef539fa665b654178d84c59bc80dfa4fb4f9828333bf3328707c1656b3c32c09c5b8bc9e88498efb448dc1b38d0b3f903be35dbe00cdc761061bbd13861bf5e70e9dbc2c3d243f80fdc204527cbbb0b257890b3dcc268e1675082a7c0da8b18245fe617887debf0628d309caec5dd753001a0ee5446af74f595692768afd13f59fc74d44ca2a0c48baf9ba3733ff2e4b4bd016abc24fda0835242cc69c884a0a4c91442540e53050e8a8357388a994383fb42a84a052e8e781eb2f88f07c4822dac9f08f2d5aafee072f93803255475f23c35f55149c141ee032cadced7065bc41260e500ac8a743c006d5c2fc83f1296e05866ca9cbff4c1cf06d8822cbf3f7056101f278f0241b5d088f487d8fb598a348376eca00b1d90bc688382ac2916548587c477009d9a59fc322d92c48e96d0d346679d7fc819233323fbc0e56b4a0a7a229c9f931d48e2c0cc131aa8212bb9823913914f8a5b4a0b719128404a99ec156ba6bcb5ae878ea100761b889c2541edafa0412b81f92cae542b1931cab071a4b1614a6a8e70358cc8b06bc03178cbb7d9009204a939cc8ebdb11c1e0cfa3ad463fef1483899f69f2d67de2cf7716b14ab837cade5a129720dc950b9c2af668b44677229d615209d0c5cd4b1c839574406b0c6597db63890a3cc553d547c6cee8fe1a5ad1f8093eb6aa92c78e85edd37bbf1e12a3d96c852e2db06b0c3fdcdc9c01bd372570059f61c61dde7779b3d70fff0585b20d299d3b1f87eab2bd55636dc28dbe45daee80cfa18f6308d966c81301f3991987cd84ff4012131e138b7d3f6fb8fe87008365b54d66980da82a8deb08a9934c4ad3e916358c188da5ecf1fc0544b91c75e20f89839464048ad29fb438830fadd7cdccffbf6ddbf5c6e7549fd4f3e5eacb2f2ee93300fb8e979b6e8801e9dc16165c9389bef693169746e1531865af949a4a1b5db889fc789cd40f28224c54f5c2e8a15cc2e7633df1e2b5cb32cc2bd26efb45c5008b2fc280a6a31fabbd4a427f67ef9931a819e3e4bde048ee049254a7af6356a0158388826d3a3348ba83f12ac49e83b1595ec3ad5e3da4257b43129fd0c9731c64fe9fccd9e82bf52e078dc171eec8d2ef13b3ee5f3f9cae7fb94cef72bbdef29bbbe87728d0c0042ee912f4156b5fa616ef4c4eff82abdcf513ebfaf7cfea7e47c5ff9fb9f92eb7be5c77fcadfe79eb23f107f8852dec169f3110b53d7fee06c7b6a270d27a0bdabdebf6f98ad8108319fb95da3483a9532572f6b231a3bdaa4a86012dfaeb41326ccb74152a657f1197828dbee470df8e69db07cd1206dc5ddd65a4f9611f0ba50704c7caef9510d01810c95a4226a0f86c06970170a0afa3533b1ca475df3257260d995edf12053c0aa3bab12155d09dd98283330517100ddbc4097ff18a05bbd03bf8376ad359b1135d1edd40a55272cf7d94988e050b2e03fe66dec32a5d66b02de2420e9566ed23447b5333891e2f4c668f96de1059a12411377244fa94125011bd7ea2c33d43f555fe6ae7c6d351d87a9d7b871724649314d14dae506681a6061c9aedaf7fe02280a4e2cc5bf210d3d0b06246895e3d1363f097c336a1c4b22807599858637985700e8e7c117224cf235d5d478fe217b9e295b42ef090b0e2b77f07b2c31dec11c84de0d796612c4c07c42f69cb1c90bf2e8b02c545ae8ee128fd0d62b2c6b5f6ceba1459131dfd03c00bc1a8215e4d69e658d360e7f0941f81d6d761fab6224cc3865d2d8ada475bfe19df20c454d57ea796da622b87ec9433b5a29eb1a873130e486033a6286795a30ceba4a079a17ed98da12247a787b85b70dd10724d0a4d1a462a380992b0d76e048b60cab0f81caaae3e8222b8859a9c0700800846110447d77fe3806502ddd51b013b8a67aa8aaf62d031daee07af9c9de7b6fb9b79429c914a7078a072d075ff4764e977e7dd2078b920916824c9104526b4afbf4f66e98ca98941155ba28a1c34bd932cc195bfcb4d80449dfd20a0f443f2dc9ad23b7e0c2e537894bd24f04812533777377ea4bfd9054672984594a7971e8cfd9179ef048d82fccc313fefa7a64589be5d7231ff34ea1bf137a8aa83645d8a5b34dfbb2f7fbc4124cdf180c6ab8503d3a29a8c0ac900b69ef9c10bfb0d6d5ca1d047574168ae7439d1472e881a250ab13866ab850504bf8c04950f1fc45ad292a052931f9e8b2aa5e497cf6c81f56458d4418a0d74c93cac320bb8edf7c5c20d3b7d863e2a6a9514708e105355c262d0ce147080b84c83e46010627c0a59ae050d490dbe3189c0066c61022dc51da7fda0f53d41294d21392d30f12901211922b814b3a45daa374b57221dd4f354f090797f75c7f7dabaa828a55d1bd09cc9a579ab60aa72b5f9e7d29c0acead129e98a98a2a42a4df8c9b3e49865b35f58b10ff78414d073f5a7abe2b486f1310b308a26bc482124851829b8f2bd5300a816f3764322cb4085acc86c98e08a2cc69c28849e37345a4a29a56cc92c1e4a62e450473101d4ea4b071b0c3dd1a260d8a0cc1605c39c73cea1213a0445c2e00409b678511d1e29e3755d2e7bd64dcef951c902338f11e79c733a0771190c50d21ac605df2ec7782205dfc1d39f1758041048001484124d2ba062a59d2c410433f315467255d47cc8230b1adf2e97207262e83bc909167c273d914483208f3123e8620c2b9868ead26aa0259ae8ee6e9e0f3924329e5dd22c569efdf2d9418834d6c8810c2c579ab091a5933e230e15cf4e9192b0c4e0d9650d41784e092f5c77b764d60658989e9ddd4340020dad77642b9e8083678e08ec4a1a4ede06202c2e3479c2e244861732c292830e58a6047d0d47aba3f99ba44568486785f135af6bf696aafd262d81c60f4baca105e8e84a12950f5d87d7a853d4e824f446a99002fbb38338bb338207cfbefbc1731733490736877d84def1400d5d3c892d3ca7b0392c3fba3b91fbcab63c7c4b1d51e7af4fa00a512f8fad662e43923ee49f9fcc85c18e518d4343bdbef8991077e24e64f1883bd917c970fc82be7de30efdda5347d4fe25f24dd89c7612afd4e96c346777f7f48c7ea8181db50319191d31cb84cde91b28e24f7f4be21b6d4e7b1429450c56236a187fe20fb3da2310b3fcdb3906ae54ea9025f208a512a3da673ba5a15db68731eadbe32e0d2f693080f94012d513305e7ca04b4440a9d0ad243cdf60b2a8e13e41a950676c923321d188e968b548d18899993d53a3ae886211e581c6220612d27dcd581445d064546e89d26aa4f3ab138d7aa752da46341353c368840df14e0c621ca01e82f27d91e19d1ec6515a62727afa714549242e0bad2ba4d58840f0d1430dca7316c5b3674ab387b9c312adea864ddfedd38a3a06999f666572c8cfaa2db21283d0ac0f96455ad13fbf94d3fd67fc342524a4242422dce5b3da98448dc32eebf48b7e21ad983356975e7af1a98caf3188085c54b03ef645e0d2c5e6ec9452f4f2faa427b525ca05cf92f8fd404444e1464528cc8acf1e7fd8e38ddf2782288aad097077110ce2a565344c84e3a28633ee4e193521a128b424c6e8d1e78724c6c8f243cd885d33625d2c70d2e505ff9c2caa21129a403c81e60c319c428420e610a41d88253f11b8342db841fa13818bd3fad8ff1c7dd1ac04ea9a6b9f0a9a53f769f9b09712a386b4d5be32e0010c3006895332187d84c5898e41f144899fdf56f931eb08e3c54fabb94720e148ca473f12697e629215281f7d63b742282b563e5ca8135cf1e19259c06c30e9a3c785e2a347ef115d8418a4fdc783f04d94849cf8992844b36cf952c4d7fc6ef922f4dacfffd2badd2ad21856ef1bfbb722a2ca6f089b1266ca942953a6353d5c792bb430fd721530bf3e9b085c4e9d7e2b605fed29362dee8ecc214716db02767397d9e4b2a22fb540b44019cf7e81ddd152f0e1aaf1ec210d071687fdf27a36879958fa85e2378909a0df6217bf139caa12176631f1d33650393aaa72148f3e3ec5a30f823e00fa40e83f40fa00e803210e6221167a4662a11e820c05110a12244810a20f12442808910f11e8490413c13c0f4530114c0f5d5027d4cdd0097542df2175429d500fd9970c8b8c29637a0d2863ea410e49212525998392d2cb2829a4a414a9442349248924d14ba5684424897cd82465268b397b927b63c730ce322cc3bad9f38ed14ac7d9dd53ca9976e9492ee8392bcfcc0ee8eee6ab876d7950211a8814c16ca9868b0422459ecde9ef83f5d1de1fd838ed21839b93755143708703c362d0e8956c22c982c998b22f19161953c634b4719daf6237a49394eb76c1cd3b8df3329a6d5ab669d9365dcb2826b5d8666cf2fac2242a55ca57c66e48cad97b30283bfec0a21a7aaa4bf9163b22b163ee88c4e794a779e76dde0b686eebcc85708ccf69fc65dce52551c9263629a5d44dc52c6e15995bb1c2d84df8c6206154ec9748a415efc89f9f9f9fa29604235b2b7949fc96dd10b940b699bae63b8c4a7f8904c105526473dadb7176870111a75769b505cd76f9067f84cde985ea8592bddd51a6429f966330df9cf531255d6c772986794a9a52cc993233e674550c3a0f06416e4b5d5fef30ab2384903214032665c448a2356170c11617309cdf242b2cf8d46fd2095ef8b0fa0f4569dcd058fa0947058fa8b4baa2c50940e0dceaa7af1a3f5df24efc99ae0a4954cf39c5f145813a39fcf272f80da21a6e540a3333333774a8cb6075956783c3557ec33bcf0687772ea7951a6ed496c26683a3e99d6d4de76abc65b0c67778a1cfeff0d80dd9e15b1bd1e19a13d1e19a0a4f7df342ffcdbb6d7c3bda3dfc363535be79427a87c7c3e0741d2030888109306be5d32950011d3a7ce5853ebfbae1d443425d876fcd514f87afb4229a6f4e44fb3e9fcf6a7c3b22493ea48e25f9f67ae6d77c06883e74bc0e17d22b6f0406757c2430387378a16ac990ce8da8fcc627db861715aeba196f89fc14f54b8cd2ef97dfa43068fcc65c479d04c6baa8f24ba59f0e88d5006634a346fbbc4fe9b79a937a9619d11c73221a0a8eb9cfbbe6b1d38edd10ea0cce8d4a4105a0181485f6336f1fbb965421d85ac3302965c4a254b218b62d8d0c7593c280f9f07564632354252c85ded13cf3f607ccefaea1d8d71836b7c6506d967e7e5a36d8776473cddb8a2f5d927e5ae12ad51148385282a7c3832dd570957a46882ae5eabb9579dbc2a2a861e5992fb6502ec5c9766700e00ac8b70136a77d6969a19ed832df3cbcb1a1beff92e55f36d7f6e04d67a3a23a0f76a6eacf831df3d25bf4172e928e1cc1b63c5261d4f91b73f4ad549fb1893a3fae87d08863cc14b53d7ef9334665b0dd1863ec346ad49921d82db7bb7957763783717773b031a494524ab9524a2925ed28a5e48c6a6a4c8dbe4c63438d21cf4c15354a29a59c317072c6755d9793339c384a94dc0b7a5543b3768ee2581c5eed4e54f2e7e2431ccfcea4e4459db339ca149595c4218c961fc24cd1210c943020e8c1417f1e6014a0c61932b98c163c790b96885c0826075ba0e43cc660fad075e69815ad2bb992ab95ebf0e8441e1941a9a3e3c40c33335319f3bc7a66660e554bdf4f3c7c3b0056dd125b68f62b801822f8768f9a6005358e45185cbe9d834af35c6586e71dcfccccbc41013ee4c20801185bbe5d83ba049377d30d91208011039824bedda61e31e110c382307e07e52553c58894181410a066c5eceb459923199c10030325a316df7034c4cccccc021b707454e9d2e500bf68c1b7e3e886f44a091d54dd1019c400df5f1ce1851adf7ec306062ee8020594266364b02109a1ca1d51bb19bc20a28b8d216a74e71139d58d1a0978765a9d9878f69517373c3b8db3b0454a14348a7a3003a906249868aa1702ac145d28f1ec3360f4208513615c2082c916424a34916353365e7543002dba00e2d9ebca419d15283d7bc783435217647878010620a6a0e1680633ddddcd71e16509f0610f0f9acc5081142bf0e1d9b7e9cab36f3a64b4b00230a06c21850c5a3d43953456dddd1a93cd0d17ab0f7fa8400b325b7cd9c20c2e52f0ec3a4d4268f1c3b3673ccccc5fd4303333dd2d4cdf8e3133b3ea9999992f1e26caec6384166518910652143d74d1e296e199999919480a3ee4be553fb25062ba218ba1efd5561c1324753154df408bed0a143cbb64a7363c6361f4ec9e859367671067893138a0c26c962e48cf5d78f9e1d957343cbb835fd22840944aa9a604f6c35386a5860b0827ba1434a1302b9a7c90ad22a4cb8a313c744b0446b3cb0d4d58b502424a0b0823c6d90f69edb2c3502b88a52e37b47e301a434a556714c160c2e90926a27a4081132b2800c28a151044a86a64196c1a75220eecb2a5c438582ea0821a2ac98aa1242b86428921c13c760d900c7ed1483be6d243d2547e3645a24b27125dd22f6c47c1e7314f7a7bdb0c720e5289c82d77f51bea5c47c02360af560b729291267712a00fb1586c4104831b96420003171d4485c9169b3e54dd80f0210f2a90602ab078ae2289e7a56d8a122f5cf5ca40981dc4703203142f5ad0ba6248e2b26549cac28a5361f43d74aa8ac76f12154153a4f1617549c6360514ab07a5d4318a61d431ea48302aa9378d74a32f8314c324b738dc5ce4dc952ba594f3baae29a1dcb071c718a58cbb4b7b803cfc866319f905b556963eac7c70f0c1cb0c45148da004472d95184b528c2fd7755d57d21446244d01c307e03789cb174d644b4d66bb6951ce6e488c9e7d51521a23a591b2804d4abd18a5f4cb9b31d218e3754d193fc6de29e594bdc5469915a30e8f8398d3dd997526d6ebab110c2beecae5dd9d5f7b4eb7676eec8dbb1b7dc60ffc9c92a76cbbbbbbbb71f7851a77bb9b36c61877e76ff801ef393f2e4b32e8fbd9fcfc267637eeee6edcdd7da269127597ceec16661f29834eeceec4664fd991e01366925de0aba38c335e916758866d65e571f4d520666420cc1cd78ca55f987df4db39a992377271d6657bfb4ed9e204296a1da38ca1b237c7d8fd3127d5f59622f1e3c39ef9589669ee9365cec2afd3eb9a8e7d1b7d649701a28fcbb199c2efcc7c7a88c1a904cbbca77e797345b9da3c5e7ef3862d82bee265e61f0c367fab0383242a25956a83ac87d2a3947e451f41e4e49f46e2239932fad8287b7ad9e62cdd1c2f7a38321cd74e3d62f2e9271dfbb6a737ca97524ac961a40c933c974695517292ce9944f558c47aa4055199bd9d32fbfc912b25c73278c5cbdc2ddb65f7b52c76297b0269414c6666665ece82030e98c5c5189d97be05398ee36c9047947730a10662266649c78ec87c216644312962e81762458c71133333b3cf23325288524822109ab2c510a151d40961489c261499851921ac8a2fecfd4230b69471621c608fa0ba4969fc7cb882fa90ae6ec01e36472edf2f817f5a99d7dec1fc72ea3169c2d3ba1c88b6b2eca37ef5090710e3f71bc246ba0a8bd4eac0e51dc0244b36bb4b9736fb1dc15c7edb38b30a4789f9283034d44d1ab39434e6e7d9f7c728edc4960c969efda604fe69613d04f65d2ea42f6fee22e9e1971f0afdeb377e3b231dfc7e437440b264735fbcbb65cab4ae214268b5cba6cd977779097f97291376778f912d53e6777bf8f7578b11d1f7f40ae09ab2fb8b29d4376e8bdce64dd9b8dc376934c0516fc9e2ccd91e8bd097ad8b9b7239f59614913fdb8571d4b37940e79c9bd07d9c9bb0375254251b95f93ebd4fcba7c5be794c36af4cb49b2b55c9467d4ffff46ded7a707958e3027cbe5b9bb72dcd0b57cf535e9d91036c584bbfb8d949a79b370505be7a76b7ec52d987023f4629365510059b8e794ba6cb12b22f2e8e2c42bd2952ca28a5c43c1380a628e179243dfdd4c1f76965be4d26993799a284e77bda83e94cc4e759b2f0d2c88dbf7cc3b8d839a063d7535060acbbe94cd10f056621fa602c49c02b9f48f9e94df9a28cb98fc831d89d03549073859746a62b3d21fa769fef8e88cfcbae018c51cc378a518f31c6103dc65864cef82db97c4c2ed2df2e250a7cc54e613e5d62599fdbc64cafbee4d579d7cdcb27cbb6ac2553e7cffcea86b06f9b4bce85f0b6b1b07da1cf6bceac6531f3d96210839237cf3ccdf7af2bc964e6c974a98b9c185d98bb58e08a2b78f264e6e6e666962cb999254b6e66c9929b59b264ee3002d1299ec8752f73ce39e79c73ce39e79c73ce39e79c73ce39e79c73ce39a95c27622afd8c9a4abb249bb68d73ce39efee6eda4dd9d37cbda65a6354d3b6204debac334a31acbbb56e8db6532fe33009a77a29bf30022d985de262d7c4fc854956425c768a08c4befcf14e31cb78f172c5153116c156ac7e9993acc4fc7a64301460ffc234c2fed0d9c00534fd650e7bb06fe7fd98ad28e2a76f22f974f1133f79b4a23d4975f40b9370f189b6bdf6755a374413c25a8a26f5e4c9f684e3aa4b4d4b79044aa568b7d14ca7dd179f601d912ee5f1490cb29fc2ba21a92d729d57196c1583ab19aff3946f5737242584535efc2e05cedbdfb2f904e84990952f6a0c0a0aea99786d4197c8054d18b755ef24d1bb30629d03b68f5facb246d568ea863aa17c214cb9ed8a741ff57cc6354fd2788c34a9ea9cdbb0517da3f9c2787546b4af5facd211a98ef9b6b1caec86c42d729587075e7ead51a5b3a0f49a0be92ed409e5cf68e52856893e58b2a8c420474f8f79d18b1a8fbed02776462b59dbc91973c6e8e40c39844ee496bb37abe5a8aa554a19394a65635c3b0f97bb7465f3eec64c6691b4b1c476ca66d6819999a5d08298524a2977836e0e9e3b76939e7377775bd8a44f1923effc760a29e5eeeeb619bcccccdcccccacc52665738c5d3672749ab3a5f00805c7d1059b7415e3b06ffcea1645070c66896b851432456810891162fe61244060c5cfe64811430424bfcc887b85262030426230323ff3c39638e0f6e69fcb8526116c3dbde3ee789391598811708cd31db10b8c1aaec0e9ac80892d9021744e219897bba45c975e92cb282e2bed98572a3be7ec5ced18246d3647f2dd40516d8a68deeef3d10bb99ea79b47334e722b198735662cb244c752c3aa39e6600f9e1b902b3fcd3929a5d4fb72e9f3d2eb997f7d57f4d172bbdec8916333b1c5dfd5e6f21732d3f317b2d3f374a276ebf40ee7f2552becd2bc9b5d90bd71a4cb0df354abe9f3ec71e6f9fce5f5cca79f90c6245d90a5945386cd03d3a8e3ee76fb6e77f7eea7c445e90c7b9acedd5dba12cc6297a45e1881e293e863fae552fa3c989dbb2be76c597dd8284fb7941e992b067db4727dd7855dd775b1cf39e575496f4e223a2738187897b4e212b708f1257eac9d030d548dbe34bb1dbbc3e8ded9ed2f94dedef63aaf0759ecec48b9debed95cee3101a22dea99631e106d61bed433af094f0b9b36d77704f3fd126a8b5ed7fc86a02ea4298d13ce6479e5178a26803efa364333469dbf503411b4a018ca60099c233302bc44a40d8a8cfc4d3a52e32362d39068c688e1a36f6ad38298712ba3976988dbdcd404068c98ee5e6a2762af1131c6488233628c118933aa8811887562d0d9f9c13a35e87c69a2ae7299e1a48b326078322e32c63cf79b440619ec658b930d8aeeee8dded77657237c90e10518621ce28c187138e32746252ac7bdaa2a190c59f8175620449b8a9a8c9a58908d2180674fc92691b1e587802184964d0430fad5d6b6c4c5d26ef8e300cfde269131f4db2da0fefcfcfceec536d7b5ba69baf1d347288177544f68c1e527e5f979196961c6878b24f4c416229891270e3f873e5415e95d2729c5b63c361bcf9c7ad98d534a5d082f7779c6cd289bcea5ab7ce238a27265aabf128e11c860f238bf4962983e859ede47bf111d47dc1c137a876fdfc091e3068e1c33335e5353934acdccf826a46b3c1a9ff1aaebf036ea8833333a6e7821cd513d1078a206de8d57a7541beadb383b3efa1d31a171a653a071a6cd1706344fdf1e7a45443a78fe36252b35ba57a4460d699e689e9825c5d040312b8a5992868c8d1f1b406b238859546888595098456d14d98862a30866d9c8cf337a8f0a83ed15e145f1a478450cb6cf7444b017c00d951a5f4a4813d01dcd130f35cc01a514d23c7d6f1a54e328318bb5a88f4adfdbe615056049011b7c0c41c8e7e89153001f3a0418000ed88107063880007e2080b5c313001e1f000048101012f0292004217f4d9935c7d536ae4be9c8c19e03ea3b07540e34c87c7b8e2866adbe5d47918e2278872594830cb3564b1334be3d1d4142df397ca883e81b49cb0b22f85047946f1d3fdf7e4de915795198d53e7c0b60e9064723acf0322773dddd577477fdc21b249899c36e2634a7e70e76dc24b149f9e81a5293605937160462593bae67d78a34a3dec15ce9c68ea3e72a38c7b3efe889bf2131185af9e8461f1d939f8d0430bf3c0214f3cbe9a73d7df44f73fae81a53efac807d3625f8e5d8a731354ef4b4a5c69973cea21965fbddb9f901e28b36ad4ca4a934bf4cd91a13b3426de99b98d550b425a1deb1290153c12fdf16e63eade8db99807d9bd0b73929b12d8f9b5f6c1a2d101cbf3e8fae9f05c2c4c9eed059c3efdc0188d7f9ecd3903647cbb2382da503203e9c473b5cb3869f4ce6d1918d13955545aaa879340375798d940d4915757d610d09c6f870ab91b239ed335115a926a98a16c82a7bda9c76a3233903c5ac700b67e6110e2066853a3368cca369e5872a4a45867762eba87d43daf133c3d89647d801d4f7f56944dc75d8c530cbf2e10c94eff6f0464acbb74b4f47d4e614f14be487379466a0dc5062b0ddc689ca1ede50baf9ed13da9c769b2c35fc9ebe574c1d102d90586334bf27a6ef0e88767851d9430dbe270d9e36305a20dcb76f40e5138a3546cc0a575fbe2866853546cfc4acf07bd2008a59e18aa986caf7f489593569e0a40198ef4903a76f5f7d997f436983ef00aa8c6d79d834a9458dd318510d69ec0102b322576f1cbca28b40c2d29b22176a38b9e78b2f7addfc50c35954847483430d67d154e272396bbaa4328f988594c42c5e2213160887e3dbeb0456bf432562563883269110b3426a44b91bdf3e8ba2349d41f39790e0ba0502816d4da41aeabce6f435d7d134cd7f30a8f1e8302879b8aa569ba649d7b4ef4797aaf399c772d35c6e13cca49bcf6b5e485f0b356c4ea72dd9bb27b430014a42280929fb8100d4a45b2e60ad291bf80172a165841617a044bf9cfe5eecb1bbfaf2ab277f48f892d9513f92fe1863e434e42f33333737c3103d885f97b515f04208bc30e124071310d1ea1e90c0b2310d400b3e3cabf1ec71a505045f3c6f99917288ab3bfa45549b6e3b43d517110bea616035b03a0b44821e6c109e1eae0c966210fe3c581fde1e5e5d7c73341a7e9870941b28aa212e6881c4cd895d46cb1828818c139592acf050d129e28ff44c8114137ae7f276cc318fa7c12b0b9d621753ec9a58bd3e6f1c3235dca7f05aba7e2ea08be85aba98c00e7d9c26b350b81e495d01ec8e23f9c418427d1b77e3c6dd2bce3955577bf494741929d74fe09dda727f7e886214ba225064c260139b65b37c96678fb2bb63645ea46f10d4d1e1e18948989979126dcdedc68db55d994d6666c97c5dd8dca572b7bb05979b99b9b5dd652fa4bb5c489fdbb4ac4b652ea0204a149542892ee0018718aa8d5f2d3cb840c69830671c51b55f2d3b88e1d405af51e9af961db2602715fbd5b2c3d20e4a444cd28a186a7cf1a2060bb8a861c61435f59bb4460b57eaf69bb48650d31a3d3ca843cb5f2d39cc400cd531a2e12886238aa4d0a1c80b273f2db0e19390a22588cc0daa959153e32ca5018747c2498b0c5166f82396861069d1e1474b0e6574ca88d1a20313967dc08601817dc0b5c27101948c521abaa2b06447605a506901a2cc4a0b1062fc8d5f2d407cf1a24cd36fd21a509f82dfa43582f82a8da05efa6a4102e939b0fa30859f9f9fef597dc8fd76bfedb193987cccb16fbfab3f24edfc5b847f1ef187a4bd1f8994acc568e937505f09071f3d74293eaf9e2d9b07120c615c0fd62981c64e1aada2a89b65e60b4177f979b03bd2a93d10188c4e5de7d439751eae36a773212c8d7a87417766bf564a4cba9c73767727659cccbe507ed77599fb7429df244d492a697f8ace7cb6c5ce48ea3b2f32f39d8a3f5c81bf487fcafbe36ffdd1ebba6f199c538300ac3c13621cf62cfb4c665e16829a39fd56d34267faae738e776e78e7aad90de9a07ecba86cb323225f765fe8f39d77d7f9d50de9d488f2d187dfa419a0f1b3ebbe95cb51a6420f7c9675528e8eaacb94cba3974e33ef6490f28ce83397633297693c7d7de99b065cb7e9e06a8c2a8e1b3974dca0a99587f47aacb81a23ea3ee65b6a46c94cca3b1adf979e793399a7ba22326b0e2911b39050afcece420bc4860391ae64867a36bcf8335d03a4d7ea524aaf2e846d78fb1f8d573d24d4488a7ef2db7670639ef350c5503c1e621c46a34666079339bb1ca36bc00ee95d369f53d715e93cf34d7a2bef26bbbc03210ab34229a5300b89f4ccb9eb8cc41048f95403529e79273bcfa451e3a01a74e6bdb32d5a57bc399911553ae6947ae7586764e6af1980f90df39bc4d518d5303e929ef8483aef1cf3cde36a8ea8988735aef67d33a562b0e3e654b8c2d34f7a1736550c76be711c9793a9ee5b0633a690858e7c48bf49c35e85ce18a7bf09a14ba824ce562539090dcd0800009000e314000018100a09840281382c1ed465c17c14800c778c3e7a583498c7b2188761100541c8186388318618600891191a9a220400ec21f0b2476929381f6babed1c109f0adbffc4101366501c27f194756892c6691ff69d606ca20d2dc2389aca3542b0338b61420e859fdd42a4f08609ff460341b4c6c38e324d3f0d428912ecef54fff7c852aaef7fe841a7031d31296af7feabcd23411ccad62e334128e3dfeee18cba02318469c8875e75e57371cf7bc16eb83fbd1be0f8633306758c087667b1101546fa0c4a87e50de3670f71f06a8f6fc9e13f9185b5c8625732e4afd823af0355135f79868588fd7c5671b3f79b4a28e932b42ab1de71e15582addd077f845f6317a023bbbe22d3bccf95387e48d52fdb10fb133275011ea85e684486267c3d319649e48b8880285e27e788b271255c58b362f0252e55059fd429217963abf9ab01287b338634668d3eec5e613eb3795f99b7b859629cd99dc66057b1dd8512af01c68e41dd2e4692ee23e88a109e7bdc04f208574a5efa0c0b9bf80094145c2108f6c1eedac45d8546f18c31b0c13c0f3effb364c7753eb007e60e63d9a6b87557500949537050cbe3e3140087f7d5e03143ea899278d76529b1f01a4f26564a101f82518e83d0f233826ef96bc61488c4ad93e81d25f7f7432866d337366b6e38f3aac4bd1ab7c3d2702e946ee4765e56169f6596baae0dec988025a1f7ffea9313002df29ac6c6896f53922722bb686c9c0b7e67091e07593d9a06280b7d411999e4892c6a4d00b2058801cf2d6f3117cd80453052c8b264711940ec088e5dc562cbebd00ba68e57139db50f309b6f08fc2507395e093e382c298aa41a0eb6930b1e3b55fff87486b92877088834334d3e26229e85d0018da9dafbe17872c44881ad970d302204f433c29757ecf4d8813229910018ef4223c573e62ebc28b4d01d2cea843df87ae07c1f82f34e73c2fbf0b4bea72d1c30784f041c6e06cf5ed61a8bbf6e81a79dea865be74b717be95fd9833e23245da1ed4c8b02d1087381d4a510d8f79799aeae9e51f2405d8db61ea70faaac4dcf287c7677e95146442590ee9f9acfa8432a0f71042ff0bbbdb3382f8a8e99e726d6023c4e6c398d7381d78f1a25e82672517e49eef4a95979d372de5cf9572554f56c1b2fdebd6bc28e4698022db3258a120035eabc728041a396406070031192c063404fb03c88b32dc8a14a672f63cca8bb0f1ed4c68570b6e587bd3c0c08ca081166336c85163c09826d1faac111b587ec5d184cf81c364775a915a7a5da96559750fd0bd71770c28184faa26b4789b84d0082cc16be2a0e81173b8702b5e0f739eb2f35cd26ae838438dbc3c4f55a1293aab378cb590c5bb7b6402ddd3dd096039d0fd47cbfd6bfd7b12936b5835d9f6606c3752f0c0e9e47d71cc8f15cc60588957b709e8981944f27368471ae56a796ce07173c008eb50788ec43e9c3dcc8da83bdb025c7adb2b714276069a2468be34f01b49988adbaefde025f392c8136338e2490bb8dd2300a5c1899357285ed4243b0bd6381086e4e1241cd833a74fdd36eedbbf056c1ef75c38e0cbd3ac0a3501e1e64f82f316d1ca8bf1f145c6fce7b44c14f7f100ff5338edcc0c6bb765cf35e75e34202b0f15e6aa943efa000ada30b4e45576f22e5dba58fbda7f9e94491fd8b71a36c8e76cd23e8a15c8b3f716c8a3aba6d1af6c8bc08250389e07ba6aac10162e888f1b994b57fb87f97a250fcce0d8f2dc303fbda70c54f509450f767aaed0d44703355aaba20d94cdbf06ba347528578a37b26d5a4b4a4d8e6844c19491ffd9837662198963746fd68407a467aab76b36cfe1fb1a88dbe9d85cd07c90df5004d12ac6d3b354430e62d2a575f8409235b60b857d7ab25ef653057aa5bc3a9b9a642978993da99d37109d44e114a20800f0b9d8ff2f9374f32a1e0f85391cce6d03bc46fb822c30758c33dae4b95f007d99c6baccbc7a4668c9d3b20d838489e6056682ac01c23db3c585b0ad4fe8c1af76bcb8edb74684a25f16a4298a96a9b8a2f4ca37f31a17ba5c322a9c992cc802d85bc719d526c45638c22b98c08260f9dbd31c4623d0688216795a34b4351db03c7b8754ec0683454251d830fa70ac23ed0adf17979b37ca85a13069ea3f6495d11cd20f2a72d47a2c4a6aaa7926aa31db48cc862a1c7403d77f5f16332aafad1a1e6a37f1cf456c3fd26f23b29755681b7a7d4e24f1124a1445284da7f88047ce46852fc06e8b4da8d1201c194c80e23d5d0a7add95a626840059343a8b156f035c44858c1f0ec1b5ca5915b9165e9ec0add8e456579627e2fa4807369807e8dc4b00a46c522e222e40a6d6d01e3e324d598e2d0068a5ca38e0435ba33a7fd767719d41b623a516e17ffcf558b7f051513d6370c498fc70b2bd22017af2b50ee27f42d16b0e3f1a8db201522804365f49643233af101903f573b927ed207cd4295fcb7f9f85a2972c6fa9cb97f870880efcfb9b9e02fd40e3a000b03d914d9ab77f9118a26e8db18aabe98f3e5f2a3d72241892e76f49f110c1627e71a637c401150848fc832f6216b0fa84a3f96291029829a03e95f95a4090f23a8fa3154acd8ef0b074521fe2cdf72a317e9411be09d358f662da682d0e16a6e58919d560b8471ff27061528ff74a47dc3f9000890d5f46e928d2afeb59587a3280cf52be721ab5e0aafd3e27b7abfb9034878c07c166d06f049a1c1e0498c0be8681675e0502b25f4619af9887e32805ac721d950228eff9507beda6a4b8c783bb96471ff251aaf74992a4d8f9bd2bc048206a542cde9b1d502c3f7cb0713b7086e6fa4337a4dbe25dcc79b5e8e77d683f75ecc1e893aa91e091818a9f714ecf5d1a3dc168395fad560cc8822edb39f420363b644b027e94d90f771cb96c520cba45c8f71453038260f6260637b0809ce69674cb45cd9632f7ccb3b9b0128907b41d375c7cf74114859984668caf12e2eb67563de1c8ac28b201966c29d171e96167407bbc2175705aafacac2e05f5cb87c5b0aa0ea99928d9918c99862a45a01575bba30731b7c9fb2d1b1de650623225f722d5ae543794c47310c20f77164864b3dad82192ef3fccbb5f59e955c78cc3016d2c5e242a172a91909995938b70e79021410cab93497833aaabfed25e03f91c91fb3c8c12daf8877c464d1679ca5478bcd5315d481b05ea31fdab32f58d19c711b6cb17e7a1e2922f5010d2a43c4844a463c1839a6deead13a8826877ec4f0507a2003c53ea0194eb2a4a9d8ac46c459e4756ce1d3b23f064ca2871263540310136d641dd8ca4520891c1713104f896a111b85ded1539c2b65a98d93cd8fd119f44a8b71946cd8944d4a1052e2404ebf6fc4009148a371fbdd007453f0503ad6daf6cade826cdf8f3b2dbb75f8068614818dc949f97c85a3ae3ffb0c2ca3197350805cb552ce6ff6e81db1602a5d056c81bd215881fd5a6bcd7c59b978ee8133929d27438d3f5c38f5494ab6106a71276d5f3c5262b015e7e796c65877f2a3a18646e9bf8908f774b03af5711c27f9272e9cfa14cb282c0ce8682b7bea3e027e464c25700270d5b7561efbd594b9eba7a4bc6b3004fd99ed94dc56d0d36f9c56cba8f458f4d63f54268a793d9e82a900e083a4f95effdc6b78a8255c384f54c35d92286b2bd3ecbd8ae2c2e8956a4627a00c0c2060049837a3e8816805e68d2aedc01fac0154e5373c9a83225b21be5452a69efd06889614a427f1c27f5ecb70db79aabdc2efc885214005fdfb413a6db8a90c72ae1c126c4ca8f91ee00e9430acb97b400709c21e0f1636ba032bf09db720fdabf4ca4d3195c54a301e961cb3975d8cd4708cfca570a97d30f2858a59160e23bf8c255fee8b3a4b29b70a16f29a2c01fe0b97b0804b061cd3a655211f9de06f328699d72796e5bf940f813339ae2c7eaea859c47e920c7d7f5e5f4ce644d56f2a5c86a7d3e73772921b422f91c9c64ce8543f6e85d09c9c98ddf8297ed892ccd5c9847ac26398f0e0317b6c39b94f37ea2a347f987226d29fdc3ea0f76f87f095994773fae0261e55af56207d346dd8c3ab3a0dfe90459558cbbff8f86b5ceb076dd6a8ff69b31948aa8122e2d8daeab2fbf8f4ff3ecdb1e70e8b2243dde82d1a5210a3809d799eba23d5637bdb793b4550f53c905221e8d442e68e34281419b5dbfe246d21487cfd971b3fd0b33936d575d5ed3d7b1787dacabe7d9bd91801009e96542687a5a3a31f95f664f795a9126bd42b1630d1487608433f7aeedb26e84418929bad2a75bb6a0d0af4204ffb5f671d0dfb48a1c959a71371f433bb2c79977b65557a70f17450fc82e0e9cc21e5d99c35b1c53ed200208c7770468e52c1a6aeb4ca3063e270a8dd2d161a404a873f201e46d4900c60d37a9cff1aca9ec91e98708ea5fd6d950f611d7e486c9df10cd20176459511efb90fabadcb3cf481920e704589ab4887df46d2811afb0bce78c24200b8ae02009a21ada81dc3400b27655ae3cffab904b45d78f93bbabd14a9f13ff741a0aa26ed4549d6fce55319b5bece9fcac7a91e37fc722262a1c065086f053d207af47b87cb79e068b000e8d9515ed13c32af4ee46485686a48bfdc1f5405b131c42d70628bb46259aa8faec3629fe3df969cc16801d635e82fd15c6f9a764765449755144a16e974013cda765f2592326dffa2387e19765a41f9d3e52233c9886c5b2f3bca933b2ab3af151450a3946a5524d586850ce838b743f8c3a245e78d37e44f71f901dcc79e300ec3d07c34b0f5c9930d7105d5d49282f03333ae5da1efc77ffd118a56cb1ded80b2555a2f3b08b67cd5abd7e83dda1c6c026c7bff21ee2c0f9807e7ede8ff3f910a18e920cca0c0c651f7142f904e260d3b321d41d74c0a781a5dd0400da40350c3c04bdb8b32305ddc23eab5a46b42432c2f025bd1739e1f2aa9cc9e2dcd700ec3a72ec469b6ce4d58e8a46cf60a4dad66576e288d3f9c38a0fc7a31c1143c64664a8efc98e22dcb2c2640e89defbd3c3c3b9e08b88828d9aa5fa6529bfa72a1b30fcca40294c96f0b75a4651a043a67ef429b302658c9f35739a4b817896a22dc50a9247f25525ba0651eaff47dbcc53ed8d4c22654e79d2879739d544a9de42f7a2243922b26b25cefe15038bc630619901925140461209655da6cf7cbe30e5926be3ea9af7ade7398abb00e613b61792547dba9a1999030f25601f1a51517fd3bba0e71ef1d90fc0380a428ec71f7a35305ff014992f8a1506427553bc1c8a4b567465bf22f9f6f7aa3228ef0f634ef9a4df963863390cd840ee38f9c078dbdaf7e3be5b83d0b793e527ed2e5dbc5b01a1f6a81cb1b8709cc17bd7cf9014100437894fdabb1e1d5aa297f50ddf10325548f9e1f5b55d0359bce92ffd2bd43e77089f82ce49c8b6603c713338b73fb7de0e65c58b0e2dc17b7ca40e8ff217cc00b9fa3997e282f954d431566a24246cc5e1b50bddd0661d27f129de6987c37701ba3aecf0c3845b95fbda99c11a499c117114b18a0efc899fb6e3b733cea608d39a9254499988f65964819af13fc9483778f9c78f365924d26d8f28597cd25bb36cb802a581aa54299e82e96c449a9f7984bfa25131db290d13a2b4cad8377c14d2aadb5915d37e49f8d3630d3242676deee09326b8828170c6641206840eb045e142e5fccdfd016498c19307b1452a4b9f6d01a4905b97e780dab9bf62af452da64187caa7dfe498c7088d37f4a40fbbf617e16125136f0c8bc4cd897b1c5470d5addfc6aa04a21883f0f62d66c71220614d08f463fd7e4e5329e628aad2e420e9fc0021a20b1721b14d4ae714f825a17d4d16250e5fe052dd4bd57156b23569b0977cb5d243936a6e6bb6678d7af8a42a8641704c8529c457a700135b2f31794e29753c3e5059ae5586d6c9744c9948a28bd92d51cdc3634131e41ff0392d62b1e55cb91f2ad7b56f0ce2e697060e92a2b9041a6bb983b37ef12c725baabc5a1579d1939f7d9be97a723f12b5713faf77cd05d480fa7d1312ff9ee14ec8d9dbf0a6291dfb33dd6b2ecd44dfc740597c1348e351e9533f2c4116a2a8390c384b83c9eaf22ab3de56e5dc6df606f570458d8698acd12418e4c03d4e5825d58cf0f73380b19873c011e62e9ace8b26155ac1e30d95d546232f1a6f3f2d5c6497003390f7701f220d2c031940f6565df8c65004b99c5fad399acd974e2fe58723a014121ea5106d2c49eea1ceca7df369b1fcd7951cb8fb2c509462d8a6f092e20dc6b767809c2b384972acbd866c020fe598380722a5c261210ccdd6611651726afe65ad7d45519da1251d0dc5fd3cf3b84e02e8b7ea5a81be3782c8244530a14bc4e884bf8a83f64251d036f6729fd3d04508a0d2bafc9002d724dc8e7d01b9992edf295db999e1f54bf08dc443228319032ace733db909db508a001db8670bd3062d4ed133250a373f6df40193f3ce1d95a1177b384dd6e34a610654a82fe549b428eb28ca1d3f9e2a4e039cd1c3e011347f4248cfaa4a19e60b9809c6a462f027c8c86aa00c64796cef8e8d105c7b7d228ad8bb97792c8d9d98902f6b4b3530218585cfbc4b0b618b7fe6857b29bb3706af40ba7b773c411374f00e92d33635c9eada1c54c08b1b8f683add6ea7214dacf51865bbf6f749263fb8afa7630631a0f853a864710accb27401411d0993a16a77c2d8a96577446656fd1b81c4131dbf62092b080471b9d999df5fb16e8add1c79044400adc1f37245d84811acd3c3e01803e388d81c3d5f05b532596d6fdb63c2683161ae320cb768a69d896ce499b460caa694e38b3e7c0950f8c3d748ffc2004aa581195641c987a082b47815b2411d3dc8865d4adc4f20d10716e2087124afdf963b62428294adabcf1e42a6c5617c2bc614785060f6dd14e7c430d105085830c4ab71a129fe022b85c39a222e3a023037b422e2621ff19a018f8288c72f4c29b93f1b464f5e62bb2ce2ad68ecdd7159a9150474324c5da871e99c3502f03456052ff7fe643e56997f45b8aa82e74c1b8b997dbbdf8348ccff40a57b658c2e970709b1c3202786b58f9fdb8d4fd12c4396941740c21c0d55e59d5be047ebb4db8a75ba843a77ef7bcc686ca000fe9eda4fbd30e20aefb4b4640b2e723d43e717059883dc575704d2506fb2aef71b8434783254176881b9ba837ca1ccef25a4623e1eae716fb7fb408d8fab4a172352281298d659818c804fd523cec3a2460149cae9e827883dec6ca0bc85d419f6281a0261b3fa3c9562871c719543ec6ade090a0f00e51526f871508dfcc7cc9b2ff61fcd934c77a811b936914a51b4b51be90b33007c9efbe9b137899428180cfde8543a60f395b80797bbca7bfcc341ed48023a8881aa5ef2474a3c0257b7eb18a544318bbb2563530c466301fe6797251d8ec893b4eb8adb7ed3eb2a27d5ff2344567035e0998204bc03747c5c131ce0428822fb16e85fd311ce8b223522449e57cc2762def0af2a01f24ad24b1f551212e2a3ea79efb1dbf06fda61d3438ed9a854b00dd1e568395452aecc8e50bbde08bc93c720e634a69e1782470196fb6560517873675f17fcb9a0e31e028005bfa5404e9ce20c79dae709ab138d62443e03f2a5f9c4bd4c4858b4ecee1ab953789ca671ba4fc263823ba1a237a6356f8bf5d9f13a71840061f7b0c34fac39bff2ac380666551297e03461b812615ac03c911bc8ca24ccac0f0acb3b50e62b7c242092b7fbf1ec29c9694e40517ea7fce63a1d70009b3fff2c303192e570010c1031baf2b0184118554a45e944b1db01bd9c78671b0a1c4e0135d4025d2a92b036689eecc268ffa2b64946499937f85250da23af8291e39a3ba152a117147f8930657d263667d4f5b8db82a1fc5245f259ed35ecfd5b31225c37e16ffa1decac10e9419cdf16f6636f167609510f78af48cb2416cda2a9f01778139fba4b4745b677650030bb473d9da5e74e8d31f849a07ccad9f92fc31831035ad931bea88198c827d046c67adbe0f9d3b8c39806c57e4b093e015008e54d072e13b9a7acd8da340b2fdb0b56445010b49e68a40fd77b338ab03999e8e354b3545225a19ed358277b8b79f04ea1cfd66fee64a0509897abc2a74875f2013b5c652e44cf9eed9d0e2bc3082392ddb6b77b49b67dd4c03ad226b27eb95742478479245b6b3057ef5371ce1a00e7aa720a8a09b41cf6d372782737092d2c9f9e26aa1c49979d46acbbe1c04f5f238d9874622664ef717fe335ce385c650c69d83d3f281bfa37ee070db5ac0530f9cb6b1703b3535d4b670880ac93ba34513c836eca40a8624cf214117497f6f6df02db7fcb0892e0fe4d83181f87dcc2e28e5b4a9334209683fbce6b0b2d30cce0bbc8ba5e2283f090c4f29de7caf8a7f764bb0ad6c9025d44023066449f31418ee532d28733377b49cf3409700cfe77c196283b1d346dc6c5ba641d3a0b23985d051c987094176b5b9c5b77e1cc064ca4ebae0e45ff1ba8d7487a35f0949de9bbbcca33a19a962f67c208c10b93ce7c980db7a9956826e3763c8e24140d19e90d6775158065bb3ead3d6f7cd4e37eca9b3d86544bc9149a711fef6af791162d338afa1f6ac8dc9de2d7931b976cb777c5c7f388a5acb242fd14e59fb446dec2bcfc1bdc4940d92b572d5fd626c77047a67796a6f3a38c56676efff87691f9eb269c9dc2f4e4a7ebf6eb76d2e5ec5bfd76955cc997b20930f59ea5911b9832a60e97b2760d1fb462c39382b9dd5d6a1394f525bcb5a90722599b279c92b8d4a672da581c033f54094c913210ed2a316606e549bc732a4f20afda991ae7b3b47b62d34d76cceb1db42fe5274752de9be8984a0292971af54fa64ef93db0b3892336d947b5a0e2b1f42208e32f238bd18a5c7996f91265680f4d32546eb6937477ad5090a0e000353c1326935feb0972c3b9fe077218bd77269ab35a90fee35a2be321674c3b3b9e05e28794980349716120f36b3712b05047b427b80edede5a6b52029e43fb8286cb51afe579b58b6d198a5861a3e92d6040455e96eec49ab796b3a6836db28eb08097b7600a503cae61bf7adc948b1f10df2088826ca3531a812265c740d69afc7ed3348bd748b4a0905ce7d324d0d54fe4e28670f220a011b3a879875398a34fe5ed1507c903b96902d963da06937573ba913717ac0ea1a208ed42c0cefe9bf21bc9b6845ed6e9b33498190fbedd482ac4c54cd969c29b83e02234b322916fcaaa9455b6dab5bfe66717385940015afa7946c81e017672ea0c5278fba6acce38eadd6c148f92cf1b88597d15cc6e8fff57ffebb3367e260392982487b0b33c28e94cf43d19bea46d17767f1df6534dfb7cbd538fc60eee00f72dc5559e0b794378a913f232861252cb2e0d8f709a378cb4fe68cf49e8e33cac290594e94980f7dd88f617966080bba0e512d6efe51529fa5d3478622378a7dce5ee191cdc98248bd5c3c8677f01b1184c60d6068f6a55e100e5905390b7f60d7da798703702485d73d35136050831378b139c4dca2907532cab4799f0bd28b0e574619783fd4d79b81491c82b30d11cfd1eb7003a5d74a2679683fd3f7e34b3eaf0461d0029fe78a2f1e7e68a291fd46cc3959cae55cfe6d5e20008deeadef5f62e0e0611fb0dd92a5e56db19e6d23a52c43c78313e7705add1301e6e7ec9120e1280fc55eaea3d06b30c8feb8f309554aa7d879035592d7a0b4e0337bec76fc103e562bb376cab3b0abf677107e4edbbf3e021be21f739ead44800169a9d27bc5303be1fe104d9f6672db502c830ef329b13fa193990efdb339142a2f0038ff487a98c4f21f8bcb83a42b60c616d58751006d2e8a82a2813f3a7c25555d5005fb0310267b2506b34476a37a50748f8663ddfb981f92af6561b132932f1113daf35e117b5855985936af2bf54d5b05a57b27d9c26791fce8d3aa7a5270822dbeedadf028aa5d8842058b32f95e44ad2dc24500684c756f6e48575a7e1890d8914c34ee4e61bd540cedc60257b4d41967cfe0084ad09aba546b33a206f4502a06a40d0e949bcef9a8bc809ad026ba18d73f729ef6adb16685b35822d5f60e29760f067285bae908aac48ddc811c2050106d866f57890d03dd5e68c69860a99579e8b7393f1a14bc6a192892ea2c2e176e8faa12f47fa9c91931eb9ab19555f7b480b75a6579150a3e5425dd2e7974d3b2b6a0020a2ddeba36e31789c5ae88cc2821816e4b4679d33fa21b24f7634efc7fab4b21728af7b595df86a53082b06d5cf2ab89317bf68a289f9f225aec93eb55eea38c3311642bae68957b59b572218205a12ee61aeecdaebcf41b3d5bf3ee4a9307d68e19f6c9c2fc92dce38a1ada4b98e7fd1a77aee9000f3360820660cfeb08cb2d581facd0ccc10285e47d0968ffd8cd6847abfcf11e9cede882b47dc1aa759bf2d4222aa5b5967a5e2a27f5401d963f722a09f887e41ca51d21525bbda2b7cb8e522b6499b462c3af1cdb8a4d96e005a6750b071e8e5436cc74db2d30606d40dff8a6192e029bebcbe0ff90eeb86e72a6396515f986d60556c9bb94692f1dd24a3d3cfb4524ecaa5b84c9ddbb4cd289bc88add8359ec81850736a21014d0820349b1d858d71038adc9ceaeaefa5806f912336323ee023407cc4d89568c7893ce36afe89a2f29feccaa7c73bd97d036450aa27ea3784d225b765ad957ca917af0ad4fa533ddc08e0a4821ff1a7740d5af256b58fe8c051b01d8d24bdae5eab315d3d8670201d161f3c4fe66443b70e9a76be198b8982b23000a84206306721675ac2f60915aaf51658c5faa209a5ae847931b274a420bc9443b00c84e816cd89cc50ba6e67a338bc49812820b4dc05ca762e76653429421b2c127f37c2909d43c0a6eaf6b593ea4d29015ce3e1ebe4fceac4933253df4d1f7c3954bce87c17750f72fbd8613fc8aa6778b3450aa4164a508478cce1caeb27c51a6db74d15ad11db0052379297524d860cffa3d040404e29efb49c6d2f404b9447dd4d39ced8e3d870b7c3db072d00ee29a4bca3b44bd2fa27b524a3ae9324af75bc630c7f5f7210cf751dee1070084850eec80c9410d72c7b2bf8a50c7cb360dfd05bcad3c704a5882c3338f401c3c800e96f7a2feea74df1d50e497ecb9e6c66734e81b4146674e8f87239711f1b2d7076a65a745ccd92420a30cfb5f3bb7d3d9a63cc10041167b7fb9c5fff72924ba576671f77ccb1815b74d9562c38d7142e02534aaad67a0f4b7eb61dea84b3c23bd0028d89dea2a58065fd847a9b306cb6c00b161de2ad5d71daaf625192e8f80ee696a6d115b23b2c092e6bb602b7119a137434035e3b61cb09403227fb180f22f650f3f8211c96cd148053d36787b484f0b21c570725d149efae5756f8572c2d7069631c2d3d49d77c1f3526f0f47f032f73abffbe335548cb058c1db281f630258cdd2adf61eedaee4ca0ca6a119abd832200da0740e27a2c44c368a04f7051afa8ea2f0cb1e868e0a3cecf892b849af378d01b0d202696070ee967aa2527184920a5568214fa47806ac02dc1d943da1c754044cf105b3d9d193836554a957e9765a66f553e49b435e12042ce81a7839966e330b30bdbad2a4836e3356ca827b0c06b727a6fdb9e1395dd8bfb667f737237e45b9d7480a49c147e2396572ea5686219a7388b6e9871c5c955c1c22d1e56659007132e839cc4b3ed7aa196e921e0a176af5de89612dd4a36f62d9e474112d85bce87383a1ea91eaf0ff82bbc85faa982e859855b296ca6038add18456ac6d3a624fc7fcfab9a6bd62395876ef115142ed7a9161b86e739d1eef112d3e5fe4ad3f04bf5f563f6d52063e15517460635442e45ee84bb24afcfab64622c369f1839a907c9fa406af080394a783e915cb620eb21cb7b2492f61dce168ba2d57d2df5a131b92d3d24fb99748dc522a4714ff52725594f867e37b01ba3cef04ac390b0e0a4b39b96efa9d5c59843fb1fff0f3c6234800d017342bfb24d08b121214d8666a15306e5d4842ae6b3bbe25b4459858f275e2cf8ce6deea34414a389f2323a34f19be20f66dbc59f16d4d6b68ea18080b210967d5acdca485c49d0822aef1117de0b82896f763f9b31a86a1a9d0bf4d1eddc3081d58bb16f2dd57451e94e3eb8985b348d384c664fc7930b258553643cb8ce370dae9f758c7cf58161e36226f3b1eadf33f91897a2182eb0dfd61fb7a436ea55ef6e3900fe31e8f58bafd055bcf08358d6e54e57bde926856479dd388ab01d16881529cd3abd9e09499b7db1e2ad361110d67f6e96083c5605927b993295d93cc9ead0462e51a4ee4ec0bb0c7edd1da7aca1e7afb285a7652c9b9c56604c1927d7cbf85f84f2ea499d8bc5d772be54b7a6ac871469efa12a44663ecc0ac0b00e006e29dec4b09964f387443a9f11c768ed70c50689a4ebc08c89f0f9d2077d0f387df7308c0826ba340c1691993118d9aa5a48c328158ccc860ab9464006eb1a0471692744bf88defef05dc0a56dcb68f4ed368a8d099726a41827fa8e2568377aeba5137e4b7705615f0555fee39d4738ad7157d291f1374f23649725f2de60543814b71daf479f9c9c0d83b4fb0ba6f63efcb578d740a936e5f18f95d7b1b9d9a131d2c8278e1d3a90fe1cbc153b810a62c1b7f4b96e94eccafe79fd341d1e95c3b7a7f2f0ad050ba5d6d7eb97f9dfbd3c30cf4b3b8aa8ab1ef22ddcfcb13342db098dd2e1ccf1981f2a20a79c843bcc3cefe586079e7a85e7f0f7033f8fb74f82e847cfbb23421457ed9bdd0526d96051ac6ea3162530d4e9f37055af6cc87837e7ba8a68ca8dd051f4ccdc5fc186c749c02a80f04e8bec6a3d2d82699f1650e4e15544addc7abec12ce72aa5746225f2ce3f82db138ad5001040d05ada8fd3dec07e1aa77ee539a8d483a13d96f7ba41699bf45bec83cface1c8cb9c725025f2b7482ec695e2dedcb2b2db6a45822e3062c4b4d14aaec70d411f6ccb385894f8dab248997bf8fbbdba2561272c463e7a1d41999ba3cc2f6b1977812187d918cc2efd03850016e87d2ccf372bcc9027b3fc29383103c907f028c4f5423e714579f27384e6f1c02feb0051f8314c868a59165452f6f2b502b93dccb095adcbdff38cc847e82adbb220c51b0c23aff2e0e55211d60181efbfcc65a92549df27a4acf6dfe37f907de452cdbdfbaaca4a64beed3885f96b9c89f7d0a443629b8d6f29857ab0c8cc897d26df277b05f92970f09bdc4078900856585d3727fab35d086cb74fc433cf9a4a895a40880cf012f09d93b7cf43ffb1cba11894efec58c2746a972af997bd00c45722c88fc88d411721e2b514667a923dd1c93617c4220117b43485ac8564302f4fb91e5ebbc1c7769b22944e9777afaff645603f192c6e886de19a94816ca03dea7d407bcccd66acb94e8ef36c5210ecd6ca0add0d9993a1a06775f3a775b2f2f1c329b30c5c13e1472759888059a5c84a6e0f8870ad42d1038abfa21de3ade6c678d84c9a32f9c314e5d7e1854fc2bbb2bd49888326060c163933cbf581bbba580bd2efc302d11df317ece32a1325b08c3b34ab9aca95ff1d01b8f0c6a2a4685c666c78238de7ee145660f3d49cb27b5f7283396c149d05f499cc9cb39df6ff7cc57f0659a99ad430e6e9e17a01b10cd64b8369abda67f0420363a0919140ad652b296b9359c597861cf14f5d0155f9d1467ce882de98ad6937e93d9da2526ddb2f53d8ea05ceaeb4b0564e7441d1e87ef783fe4a72e15d5d72e6a04dcee4fab6e53fa3fcc760c21661864d9f23ca5104456b58867ec8dee4cf3937a9db72da6d9eab045f81f2fd60b8f8589cfeb1be2da881236ab5d591f884c17956fab558a7c4ae83fb7b4bc65d68699a0dd25e2e7d3f21e81bef388ba46fe3130a37b1ab9d77f2c06c3f7599b56cef387ce60afac1ca7f0e14dcca2c886d0db01ba4339b0de6ba4a857706a0563a097d85c4780857903c9f7d16e79dbdd09254c467071de5c3af89ccfa69a2ba2a2c51fd5801f3ca673934dc6961f8f4ec0ee37b00e4a044f20483ff8767aee4e999cb9541949c224bb212f126699624007457206652d12e65bf8e56ec3f7a72fcd69a6112b6df9d0532263c03c3c4ef78ec02572031c4b1032799f13563d9d1bd819c3bfc7385d4da84301ac468dad06ebe32aa1d98a6ec63c5bf9dd03393cc4f621e6bad62f2081a27bdcd8a074f8b279c1a5b634e1b24695e7a902c2afc55bce9bf000baea7f37d2830f54d97d736cd6d8356fda8cc0f1981e983220e9c42f189de72033a01e901a1923d4528ae2570055ef1dc0f091d32377c9180445e001dc92f16ed611a3ed83f7de04b8fa3518bcd343deb26060cf5ce08f3bea502283a0b192fa336696a6fe259c993a3d1c88a4c3f02743ccaed1e2ec28b67c9350728ee0196c49d4540e230ffe10365e7bf45c8dbe19d936f1ef286be16cce0d17b9f66c4ab627a1cafa82f32f0d70d57e11926704e6825d88cf7f361dacaf230a71891cbdfdeab77e6d4b7a15d266de110bf034259f573af4385e4066f4255927d8b8045323ec706fc59019b843cb1eb174a968d4449f4819e46e15ef1af8a4142eebab895f94909aeffecb037c0b77f983138e4ecfe27992010603aa10e3b41de5c536b848021ac3565dd163d2baf70c1754dcecb90c8a2359b49db8267ec6b843da597cf499a3f66e5dcfa39e62d75efb0b977659be75495200d830220cd6c44a9218c49730c9b296942f83ed001346c8d314544eeea351d108cf889685a5918d3714c0de2eed519927d2a72067069191ba934e83cc928393a40f0460e958868f5bf4c296e85b8f2e7d6454ac4db9508da2856228fa7d265866711e0a543498546baa78ed122542b07b621d7a35f5634908f48b35d211b2599b08f20de7139d90a8d18aea23ded5c2b25b197ff1b4428bc0321eec11e55b59254ea13a5b91ba276748c93c2b6849e88eba9dbddadf301b3b5bb21f0d36d60ce3eac059a334168753bd7b801588958efc7bc9034dff0f650010e76f75dc9ee6d1f41b253ad7226166fb99dac8fa1ab996d9098007013c0d9c577a1903d98afa593311d2189c279246336882807f2c6d4c1dcd8382de0063c7cad2b5c094aff7a1eb6a8f725aee48a075a060e50069e2a29190da7a4bda6cb2a0d005f860437a044cc284951768faec00df84dc6f1433d758434d80ae4a45a108c2b4eb8be9d528297ff40a2e388ec01493a941d5b08f4a9c0707271585914bc3b500ab05c307cc65756f4ff1492a1c90960ef068814bea8bb1ac8eb9e95091b76cc9757258ca4465c430a31bb65fe6d8ee2ed5f613fee0d08a20930f88430b3c82011f59c2fdb9bb255f6f77ba4e6ece883733301826beb4d438116201f1bac1485f18333f303f3488f819af8ed5630b007729346ef0bde247076872c46983ab22a95256affaa458f40949728ecf7429185ff0cb3f90fcbf019cdfbb78285e9a4cbfd796dfe258c6df20602aa17b16f5e7edb4122a263b96b09265915fb809f5fb505f4d506130435255b73f34e6ba05c84b9aba9b7c0e43bd6bfe5b63fb80d710815b979da999035dcc738adaa964a8b809e715bde6246c18b376c1347f84eaa5079d93d62f023d3776c8a44415c4940aefa5a28f30971691554bf90bc89870862c7fe492b02f6fd6fa32ddc00e95d1d3c968c0f295cb8102656f172dc364a9cd395e8885fac88da1a88109bca204bed92850b51107580e4eaa4018f50bb695d5d09062ca320619d2a5dbd082557520013e69af6751da65299fa41b47061ec07fef14b0b1534e4835367d9c809f28d8455567f543da829f8cd1aaff6eb91e30c1445bf209ee99d865783df3bba7feb7ea634974ab14abc0c85861b7dbf90a283a3d00143292094f2f1bf612f94c4932b735e80e8b1cb2105f829c7a98a0c7dbadd83bf4fdd23f045df27c3cbf9e7e97671f06a172043c0aadc1ac846a56ba8e8c89ba6de71417a30de0c8d7aa1d6fa13fb878b8dd3f5ec1672749d88dc563586bb6275d253b54fab30b810b68698d1cdbbe8fb54789261091f4484ee9e3663972c00c8b24a6b6949071f8c45a2ba7b7e29120545c60529ce31a4156ca1b50ba654f0e1801af43ec1295f0817087a5588945a609e0d0552d29aa521718fc5ae77b163c64016e8ab4d2760a6dfa22316b4844a9b3e6e93fa8c3d8d8c2fcf1cd3c11c5bcecf820429b42c1ded516756ab4fbc8b2ba200cfdd873cdce1f9d8eebe1cd53e1995446ab413b96e1bdbcd828110c15105e0e7c3970aa76f93a8b78c2ebb00c0eb05227508e109f0ff47ae50d6c5f6815c1084786a3c18f4bb359bb54d9b8556a7cb5acc8a7b5af70f77c885e6d97a160229db4b505aac19fc65d5218766720881c54a23067027472fbc4d75f187d9d996533acabd3caada6e52c3701bf1ca76c0ab3989f826214a4dd861592910986a84ddbc3fd85465fa924bbe4cecd2948b4277861b0e111bc9c5ebb1c0efc925a01d7a4d8cc00c4fb3ffb871552e52a158556a63900261d842e960c595073876066026cca1aad024a060f43a10141457e295a834d02e058fc303548545b4c4620836f00ad50fc9fba6c4d170fb15f890108db2249e08d2338a9fb15099584909eddb11626b9b5bb97bf741cf3311c65a97630d2e1a7fc8dcff0be32893dcbc56ca71db575ff69923ba6a6a8140ed513e1b73475e2480b02b110e33cbfa483d9a62c835103c0f60cb12c1872bcb06d04a1501a232360600c2ded961faae7e4ed30a5e24b5c3d6e228150ed4f8d38c4c1b498ff27def4f88be0452c5d1fe1ce51c1288f063a46df73f06b323684534fba3d1901e74666a1dd863bb6335dfc0237c3aaca7544029c9436bf141da3c27d59dc57ade87973bfc9885658b9d96fe2ff169877f74068089e52bb6180f81835ca7b170fab64a8517a5a02090064a294f15bd5c085ff016ae74cff7395705e9d5f67b36f108df3fd1f6bd3d7bc57e78edff12adfac36a19bcfa33e7aa794711fa5038c6f43104ec78135714bed4d834c4990ec4b453a0f6082a2cbca26adcdd5df684cd4859cbba7ef986c0eef4d2898213f2b70fb01bfae00a7d0c0051b4dfe25766702e9d2e82c7f02b1f4f3b4a857572536654988a9b481881512216c8d029337834d1109e0e61e0dc2382dc067865f9761b48251c268de02d0f0a70441fc9af0d2f197275b2b2147d172143d210fb3abfe0e961c0023854c0df0b005bd17a949f9e8ff8b5b64ab9bc9f84c7856cdb39d1aab681e5f729c30bcd19257308ac0e4bbdac212fa8a7677f6465e44538d43c19b63e046fdd9952cb75fd5c6f46462d19791f4074b0d0cf16ce1a59ba9b6ae56916b8130bee1f17b4a2b3d7558c4cd4c369f5359da9ff42a6f64d9d41cf85b4be90bdd76363f9ac0a9c4e9a363236dbf697444fff02e5cc04b46cbdf3b351914601e0ca5e6d2895bc812311cc3b7a973b153a358e1c91ab03dcf5ef3a7a8c0ef1e9515ffd7b9dfe19cbaa671442599aa944df55dc06220d3d2b86b8f45e83968092a4053d1b5af07b7a81db3db7556f27f8fb4989348803a06759294098d820550a47e2ed41028ed1a493e08cd6e09fd6428a5a82857ef2acc9c13d50c1ae05504e48f8420df5f86c1d246c59a07f86d3e3054c85e257e329e73a6524568be55c8d5cec0f4a78c60a90a969939ba79adb916337c309e9bce857c9a7f39c2425ce0ee57ca91e1bc9c69862660190e2c908c4090d30d03a0c685626d269526b52f5c7a6b1db1f738e78f1fd5f0383a70bf0e5ff1fff1ff2b33c00ba93766059394aee6e71af43ec11ee3273d43d080396ef8746d1ba0a1d212a5268078bd9cd5a6fdee0a98e52a6d62593edaec6dddb6e6d721396db07ccd52c55a418790aae673d6c072fb6eb2fe6358a5150f70c69ec0e9c51741e9dc275e47453f8c8e045d7a0ac072285bb59fcb17ba39a6d83e0772958e41ad52c6a990c21057e56ed2e9e2a0fd8286b01d5e87e6271f92002136b592f4e9b163e74c805096bd35add227729a9b8237c27c35f8099af1325936433235c23546b2943cf1acc2ae9ea155cead3abfddcb18dd3db1e19418ba7ed437a4a2315707bf3f1f1e6a040cd5990cf14a8091dcf4c9fe43474b3c5f4c9f316ac76d8aa013881648106fd67f4a2999fe9d92cfad5f394d4043da6a4fd58ff88ac52f82a750eefd6345989db7f368eaecc53caea4c416e19f873c8417dd4d4919dfd118d6895e8cc95b291336d902bfd59a54f72126e3478fe5374e43991a8d2f5fa27ed3082732d4bd9a751252cffe04df2776092052dbff51a49c7d6176495deb1787e1c4157022f6d1ca03c3f3d83b643039c3373b88fa207bae798e1a07e0422c9d8877207c6606178f13606eeb9485c28741d54b1f5b9a6ff41f7502345fb04856d9004865c2f6b7408c96375a08519b729bbb9083696e924fb382ce5015f8812c473cfc3319af42979ba55403479e32ceefa02269fa199979a6bfd083ba07fcf3e2c6cc5ee022e6cb91d81fe1a9e88097bf23c79e180330f0b4c0ad1d0ae329de5df12824e87a5f04475b337a649b8c95c949faf38e6f0bad45f4511ccbd27e6762b6a8b73cfef3d11b6f4b2b9a82e9d24fdc91afd000afc93f4fc95b8f8453186c289736283274cebfbc26de7923952d9ffe805b4214404b5df2cb64006f78f5d6a7bd0af8bd70094c08e7a133d00fe1e9365e08093d50ab91079edcdb23b55000240cbd3101791a75d69c00e7387807c7d4e5f05e0ccff15a59fd4dca69959a28071f6d45c4d688f47eb8f9a10d983475ced98134f055701f2276852e6731ae7a2fa7e5168b0169ea6c9e70c98401ffd62146c845fed7ac49582fcfb2a0ea0dea159c543baa0f7cf39b73e734baa3ad4d79b9f50f73952dd16555e2f6d87d60f5b1ac28be7bcbe8694e80c4995e1d0042c70faa97d51a64566cddc77e03c655042dd91dbb33039cedae4f85087c11516732e0253439d14cdcd63e88ac681533a3f62ca8cb82afd623735e9eadc6a2c168fee647f40691c860619b2be566e8f1b2a61d80dfcb86904ff5161912e7823019826c7d1b81cb2359ccb679cea5ecb426310058ea61653e6e9bb251951c0faf7d11364c37252849e52f6ca13ad6d3941ff90facb7303923feaf662dd6c57e2dd15b676966e15ea83af431fc6ddf0c54e735ed66ac50ae01d14ab611515c842bbf8c8b853050356d81cea4aaaac94a8eac16025b14cfb29e7ccdfab645429e84459d24ddd68a668d96fc9ac5dfb83738c4a0700e0691386c9e943bd2964f38738cd0e86823d4de8436ce3ba055dca1d8b42e6e52120a2686fe327c6aa93ae0e01cdef43db18c329691da937ebfcefb0fa263c56354b29f36c5f54641434908cf58a94d63c08c662a079fbaf0a12ac844475246de067b9d10c663dbb86950a004f7052647a7ad67a8bb5c5773f139e6bd3e14067ac167ab6f932788d9a59af64c41499db18bd5db69738da839e02805597ddd8770e26f9fb7688125d41a16800887092bbb9e0786b1e181943df4bfd77692d34c368f383c2da9f91529eebfc94c5473ab72a51b2ac95b0bb7f7167492a2f0b255e7b3258f0517c96036998ea64c581340c55470e27d3ae4f0c866aafbc39af5443f695532effb53a2ed7d0ac78009635f5e87f9047ebfd042e6fc219352a23676b4833f0a686f2f2cce8ce7758cfaf9161e48aac5ada33d1216332b1bd82ab44ccc5790155393c547de04eb803becc29a326c75940f3b0aa4bff71a11357fa7fc0fb0be8e3b73564eb9584ae6b7ef798c01028180312b47a75d31825262937530d9c4b4acce3280aafd52a7e98dd9c0752f9461b63220ecbaa37bca367fad478ffb88d6d15457e6dce2abca04bf35bac2aa771d797f82c412701f5b129fd799ad9d865e834ef42de4de31632b76e74379106f016b94115324edee0fa165a9fd0cc097d5fd424b5c87d5b7ad364d514e8a7acfcaa49cc9587374fbc121d61c04da311aa772db5bc19e94612e23446a4dc2203604ebbf23082cc7d0543772324231ba83c5e50f0bf2030c13a5172b15cfebcf6eef8ae4024b8089abc9a04e91aba2e1b4e34799d03f17cd0e7cf90ed866e7963ad743171ac0c2471c300d7561fc8b3270b5adf36e2377c04258ad8881bfa6d5d5791dccdf076e687879ea9f04c2fa963207e3fd14a607439b5e7af463269f8fabad5e0f28b61054a3ac63e7c9f181193fb3d38ef8f0d8482ed3378312e72115b9e7c145fa5b2d11fbf7d141d8c2c4fbd78cdbdfb8e82f8c5f8d76845c5a83a534bcc91e10a4e545d4486e0bab549a9a27d5058ba8e8553e1dfcdd7c81a65a50c111162a61203318340eede410d2f5aa933722b4672c10b04350ff29ac4be74b87a58b385a87cd04f7b70b50d1635f159832f17d0724788d59f1a77c32ddacef4d699c9e36194fb2112a0cb2cd61afeb48649647cbe32d8fb69741e123f36df36fba2dcc509d13ece972e8eb16b9b8f821db753c70a45e65b21336a41237a78d1da819cb8ad6028765c6f796604f96ee1067582ffc0cea82ddb9531d317550f19fe717a573056d69ca17166122af24b8a6e1e74923b0437a1c75cc62970356bd8c707a8a71c6711d4e6f9e6ef90106da7ce49542d7aac0f29f52ad26a3eba0f24514eacea5b23f566acbe8a2fd6b3e22b23e9bf474a93c25be3bca995a74980210210397338c78a186d473de7813f246ab4848747c2bf3dcb393a9e87bb68fae9e13b8caf974464aaf0b2f337691cca9742824508ef82dba73af45ab4ba1c1c5e8e9c489f661978d2e99fcf7386dcc7afcf638bda3c44fcfb5e29193e73fe841548e326261e7289e593edd014764edbc254e76e01c056f1453b93fdf5d980d7d1cad2033ee08c4639d923eca4adee51855b433317e2e5d3a883b9811079d58005b67727a4dfdbdbe7315dc6f65a64b5f2150d868240edb1ba9062f40e950fa9437664593816c093b69bf3039ef944aca30433730ccd50e2f10f90c2078a3ec29e2c5b1d205709ad158f9b33a407f711790878d9a1102e1eadc52812522254e1593c20e893463132b7205d19f70596b0b9482fa8b67278ad57f08b89999bf5f5c96ee5090999bd40919242b06cb4cb82128387926f971e96b7ddc85ed031ec6eafc9ba2b27ce21e4f5d052500f3a6ea743e4eb6ecefa92335938a46bcb970fd1a96a400c99083b8c86c1c6939cf03284e37ecdcaeb753a11d021f80c1a10a5a80660b3b20522a5807fcccf3a9ca8cdb0493ebc2b5360f5254a23b92daa32142506c8aaa575e901528f8e06f05ae60facdce33f30aaa676debdbe667530bebcdaa2f2a594bc90042071bc9317549d782fa5685de18ddc013462dae8054704724fef2233c417c61f2b1f174cf93678f4992d4cc8f508688d25280a5b320770006dae896237b4d700a1e691bb2a90527f8918c812205f32e93363a2d5c7b9a17b4fd8df6f37b5c2d6e2ba10812300b9809b52d5d3b26adbbf69ea366b0fc116c421dc49a79bacfd0c462f0d460e1fd96e3103d6c6fb263fd57611d85cef32bbe213775b87fe2d8006e111a767cb46fb19f483bbf4cf5c6cccfaa862f342e86ba206d5ce9deea7ed0c3a9bcadca03018b7a1e776cfa676e65cb19f9ba70320ee3615aff2f1f6bfd07456536af90cec5303b9be891ed4659edf57ee4b29a5de47edc00591038e16e074f50d789e1b29970a821d8544cf0c12b0772267ca9e36ae897845ffc9623eaff516ed3df96b98ed6d6968dfc14e6856a9089c5c04168962ad171ea74ca7028f6ce851286e1cd582e35c9720a5ecf76ba3f75c8a4b07a500bee84930959e5016fce40e45ad1e97672bf7966685fe233138c749c0b2559f1de9a6842252e65dbb1bf44780f80c6fd02bc3546f2215cbde21364a8b6f7a2d65102269a8c11727f06f1200b8fe099bb2e7c4936fa3199251f71a4f087fdf75da25de1d5627428182066f4a626e511d689fdcce891fb7609edaa83191085d56a7242bf9418dce6b8005f15f96e80a767b92d2801d16df6a5bbc9c2d89d3527817176d82877efa4888236b7e16c8a3c24d98b9aeed84c036b79c0717511cc4b1ca476d2d28a67bdfb2dce9a651bcd6240b7deedc752bf5fa60c8c08ae8af9802fb1d8445f5b98ae0e09223dc8bfa9802c0544fc269647707a42d48158aa6a6e749ad94b47a83da2b63f2ed7e986452d2fbefa97b7b3a73f7fe600816fa1044f170362e901b06a16bf784c38d967b4402c48358a7d2a3c666fee936c7183e12b15eb100fd669e7c003e1fb77d3280060019caa7753b5dbd2d5d60ef3e5e1e93c24bebf4d4fba41424483a714bc0c6d9a22aff504a5cdc93050e8d830e482d167aaa6a6345e8d22aae921444f602cfbf4831eea2d2d81a762337ad64b95ad8c82c1cc37802f51f68c4bb4e112ebad9fa7b0b103b432ad10bbf5f0a7128f22457888287892d557fd984817fa2aa95d7280066aaa22a9ae18e68b7bfe1a6b3bb7e3e5b42bc46fa1b58a9eb54cbfce52357bf89544346d01636afdc6c4ec22386b106ba4d0c85daa710bcdfe57f25fd36f616d6dab806a2175b1b4485d6b073d54171d5189aa6c09678fa251a520399af07996cf2236f2ede5e166ab24b7a0893c4bef807f6c8f14cfceb860b51e8bd8a5b7c9890854b2c54e0319c7cce3971c9465d649bc674b14ce39c8ebbbaf73da6b2e78ffb9fc86ae5a8a1305bb345668d4fc94a699a66c0665035b74b3f00eceaa22ee90c7cd44a820a04ecad16bc8eeb9c486015a00035a0ce2055333d00665c4d85f97efcaa7cf5fa5e62295d3483c36abba2f9a9cb0c819877ba5b297af86c16657c8f7b97986a3808b1a2e28ec9fc63620aeedafb03c234a8c72963e77c380e69db5364e489e682895bfd0b9d8fcf45affc2a114bd54f67b96ea192c2577ffa4adfb1055229cce8072ea1cbb970c5740438a28b753ce2d288fad14edd4cee31d30a1b15588cbc202af50081f7d1ebeb0e575647d4326f6975f97eb3205dd2ca3c5da14ff0ac759a5e625697edfa0c908384bdf029c85df1d901f6dd38516d6947b71cf569b75c7a3805462db900928b2e1dc98c524261758017e4ed9ee006f2edd755de270165a937e01a9ecbc3b9c1d89a8cbff945bdbee95503748b409949d11ada2f0816901ae547abf1f17916f7f8603400993d3e30fc9fc8cb25687e6a4b83a46b4bf0a79b1a38996a3c0e40578ea0a96da134cc7c592c11786fa4da4f00e48f6f4a3d4ca57e34f464e49265c45d445d1fb5aec1b6e6f78e1619c3ebfaf0601c34d884b044ea7e9435056ccf4142a5f37cdd057234d3e2cf73e38865785dfc364945b64045630724e158208e51bbc72caf8978a6c7f04da387a1f1bd551ef83e54ed61bbb93424ed8e444ea8846ccefc41721fda74cabb9415460b181a101659fdefee4a773345a4fd13365d4dd63c0930adc38b103fa604981b350ef40e002027b719581abee3304a9502e7aab62287c5911d89acd0386ffa9735f86748eb23ebcb86df39b58055ba004d8be543de11c8ce3f34bd33552db7de0aa59aa6d3b694c4e66701ef9991bfade5038d5045209ac6f1c8f32f8cec4a78a064b931ec5162e4b6c258981bf4e46d4f8c6b980f7bc38102f02e51a514daf8224124cdf57cb487298510c7c5af7ba65669a6f6ed73362d74943289d028638a0f59f06c9cd99066ea2f8373c8211d72eb18910cc9882ce5b164a73fa4a23d802245a1302b04219a4905c1942f247ed384e38f805969220c579a80c095d7bdf4a829f8226ca7769d3b764c2d326c90cd0cf4d1b3192be0169727acbd1428f0639a60492f3ff9ae22a1a4f973971a3f5624495efebffb4a96e037b7774fddd76d5f177235ebdd561d3df5dc6c21b23053cb02a61ec17a09f7d6b13bcd1a2c0b168fbba6b8dfa71178f2538ff93f7a6ee503213f6777c795e36e3dbdaa238a7a8795a6415966e408d3bae7edbff9055fccf5b8eb9eb8e3fd8273d5b67d5ee60788b9767e387798d3bc6c3bd08af64a9e9ddad38a3a978f19d5cd927882624c9555fbd166c012ee76fd92832256a6ea6db69b0da2d1c4716022a07773703b2148ba67abb6497ef3cc46aec542e45d86bb296cbb3039072fdc31baaa9fe25c91f66d15b3932f5c6e8b8c1cc8e84f07e89e06771f7b42df4efb1c096e267604fb0718cdccca62069eb6d5adef69a6962d3661a77d578eb7cc3c6dd1193f711b40abbf58a9b397cadb335720ec1dae7697a575a8ef7f469ade21b8ac433e77164e1686f9eb91a217aedbcfa4f21cb8859af6f7f5068df5469062b0043fabc635c387ba4ac609086ef75b7dac1e9e5c4be4c71f6e1b850ebcfb9a11b446ceb9287d101131dd8efd05e5dd8950831aff93dad92d7b07f2ac2f76bbb72375c1316877200623c3c1964b468ddc63e66a4fa39c1510e5330b73bb5037e0553bf10f1d9858fa8861533e2b34101e08f9adca771f489ed371ef0af122d21d258276b1b71e7fc97a891035aa8bd778c3e0cd0e22d18830487a82a89c42f576c50d21443319a9f6c2c17f81aa6964f0976e3b0be1cc0afe11767a24ba89fcee71ce698196140e405431536734dd7df0cc209ba294bb0735b200563db69d82541aadaaea28a8350f117007d5f268430d323d6400155583175f95223311f5e8db6817e2e6cbacfb7783a92a02013688a75e97a1895226140fee3ef8ed4cd795261f4be37a0127d41cbf1881e5cda05c27e3d1b89bb158df7a3a149275d7f78566dd7daddd11073f43f05ba8d1345e6b642e3ff16752efa2c747d057dcdc4041f2c1c7711c05a45be4beb95ac56d55c57a1105531308a29f9a1c2b2f47571d70f41d4ab8192e70070961874576ec020a6b25e66cc1ea05bb8ae95f1a81abd1433815c7894b67a56ede139083ce94fe71924c7bf321a3397140eabbe72e5d369f94dd0c697df96d9a7ef60175f321cc9d4bedf692d5eb83a7f54f04a8d53248842f54e1c7a18926eb1deab56ed106e55ad7c529f0bdf845669a9508f7d755aa0ffc821449626036de353b2d6e2b046dc05678e82bca6c1e33b5d4e1a45edb9a339fc2ce3042d3ebe13a05ebc334c11355447058b13bb04a454064e096b448ac24aa7a1d532fc4e303fceac31e556a037d686d977d2769ce6409542b0508575c4da93e14caabcc6f4aa94b552e9bd0c2a05218ea6021d098bc10c68a85888652b48fad5bdb88acb3f4b225a4524ac0d9c529f51c62e53cbecaeeecfed85693d53a5e6dc207e6cc1b4af74635db04c8f99e456dfb4d55b9466ef6bc54731f9e6c9ed2970ea33dbac294c9d599842f0d0384f8475466b249dcce37abeda6e7bdde9dd3074802de22541f4ae9c349418f63c8ce6c86d2585b698f8ffb7020711b2e354028a5144d58e20f155e7ce0c2e78ba13d109e208087fcee19c9259c81df01c6ed802751b9c484bb7344b629d714a489f5b68dbb6a4f35008fc53448c4a98657b21bb5cb579e1918d1f2a34dcc0b4d158be1e946cbc16079e6cfcb949e710fd604e7fc2264563940723a7e95aa356a2c107cb6612784e25e039f4dcd07155cdc55be07b519c3087c368e275d591eafcdbea7e6404a636f4200e612ee651e0bb20d78d6fe30f2fa6f08b048eb67708dfb778e4f41ecbb3a89a45f94c4ec6d812c50bec87652e5420a74c35142ebba381d440b2d3efbf72b4ca1c8d3699a0018efb19aca8c7859537c755cfb8c9fb8c2c9cd6392e8fee601c0a90ef5a9b56bb4f07af83413e1ebb917662b9313fbf86822ac301b47184bae68db7b51df352853231e73960071bddab7330a4926b9eb1ae95dbe46924d60a2b6cd95f8ce413123b2ad7488890f1a07e1b31f368ca8f1428eb87640f45caa70042fba128a88ade7abec1613603b99c8f1055a18554e4b942aed94cbf141079595fed78a40fd577e55d87203b9cd12900601d17abea6de10370d73bf62e8e48a23f28a3dc5641013964b1fdeda941131f93eff0044642401f02413a79104a6598cc1289a786d1ebdb908aa19f1af25830b769da773cfe952177119dedbaacb211c27ab3df9e803d1215602c5506bdd5b34f97c05bd8b72ea8c6f3ba1788a401d37f1aef544a743592f28a497342225ce5af1ab117e2e3acddf7aad690c1d63fdb5b62dd1ce30607e15e3a1a1d44f14524363f21918de3d151afa8027c5daca7e002dfc6c843e60c2816249033824dd9e086138477544cfc6225cb63ff56c2a18656e73db57bf6703567c25780166ffda172e25d39a49f01dbc275db192b75685eb3f6ba540f9c2ba1dc198cdc3e670583aea161120ef11c9321df689b721047bd61e6fd32adf4424b231343c76715f392849ddfbcc913b1e19b2c828afe00cb15de46e3d4265b29ae4e5550976b2b4be07111acbc684ba7a065ce847941f29f69aeb42ebaa1aa0552ec06102b57695b3e3258c415cc9276c5fc1ae8b7061cf4e211b29f19ace5d9073e1c517258e8f64542117a24af92ae66b2359b525851f19c9cb272493dd9076664d250b52815dae419e7e0ff4cf3a9e36523f809115cdaa7ef4f5d92c4bbb0dca5c99d5bc84d2240e6cf94cca12c51cbf10a554b10732708a4e569e1ae3141af89292d2ce455e70c5c6012de25ea58dba6937ba5e2516140664bce3c20991863bec90165c14d3280d345ccc5b433dcc374898b1df85688d5283f69487a78e3bcc6fa1f203d3adadd882838974fc524b25e3b308b63d4c67822795064d867798580d44e2d9c37b0ac3f1c215fabe6a303946716131a5513556b4c23d4cedc99ed5ea153b09c269a5c4d0964d598ad614b752b8aafd3459a90d35f214d725d27a15335800e28381a78f1a1e6bd79cc1af6253ab9acca0358fc8c61b675ea6a38b1fbbddb4f7a8bfd5c684d8bcdbd61f155c41bffd355b7ed90085f61e9d8fd0d9197cc1ed667cd5315cc967b91d5b08f6e53ed2e69dc391f782d20c870ec957e9c87ee3ace30211fa762aa3d67529b78fd0d29d0760f861ceb4971e011f6dfd41320aa005cfb6f6b6d74c89a01ac0b8ef76e95142cb55049ee0be3cc9476a45824fecc478d1e6ba584d167483dd4d35e858953362e2b7cb85b99fcd57ddc2aa005ed28bc729eba6911517ed913551aa96eae75174f1d97480ece9a217e1b9ef342bb5d533385cc7833157628fbf35b12f72ca0c76fd0523bbc3233b9b613fa8b88577583d1d2e9686611fb9fcc4bf2064be4ea8dedee40c8143585e825093d7f7cac56b1e303e254d639b1e7bb0f2a12462a4a74d0bfcd4eb44e22e70700f4a63cc948e059f529cb868b473f37c63f2c45015407d2c370e6a57ad6a97f2c5657e8d72202f2a4fb3c2f8f57f42104414331300a6139c8884bcacc66b349c02ba35d4642e9d77235815e732d04e479491ad0b9a0b929a3f225785dc9ee81102b3929de4beea2f20deee81848e49de69c76e25d8a558d794448ac5f2724fb59f5d9013ddf7964115a8571760c8d0a9349a6d4a20f901c64639e82ba0469938d7a637db42e8b5daeaebe149be176c44bab9044699c729c44727c86571c1668cba05b7b5d6550be05af5f162ff20c982384a3b20dd34ee7de3761413fa26642cf41d778f395e8fedc70c5464243619c87589cb8a3a216877b5b24245796aff18707522b0273451238b9af08a891e02ad610fcd9c071f534097171c3775c2aab62fc6b28fcbde31e3e94462a71aa106951e12cd447783dea45fee7ef38f99179121258ecb04d1b2aaceec1692ba5ba87f214832fbbbe2a8267a583129d7c488950ef4d1f0a911e6d73cb5025082e32e22a9965bf5865b7c7b7b40a20029c70c79ff18b07422b79ca3b4fcf5285800c890eee9900c2284c87c05c8e5ee8ddba754d419dde2ee769016c51e73ec90f68f7f4f4fe439b16f3af05159f62c420631212c5f097239f7c6f5290ff5463770573bb814a571ef1bef964e24d64d730d709a182bd18de725e829f24adebb6b466622d631dda25beee1a26cc57dfadeb81cea89db742052beec56a42c6251fff947b331fe6b880ca17fee41a6e9449eb621f78e57af8ea86102df9d682258c85aa297c437ac530e512342f1212fdd5bd2036a98780d4064d380f45bc7db8e7a4f37ebd883eb5411fed55a4d53eb6df5ed9020463bb86f6c8b2eb1dbe35b5eabc316728ca7acc65519860dc627dc4a0a6ff966b8fd188e0215c20673b71450c2fb9506f3a2f2b4e6e1ef92fbb52e66938863f5f5b6a429569f590ff3400bb3682ec021edf3571818e5929200ef607c5fd91126504eab632cf04e7463afb87412183dbac82a642b83e5181043b739bedcc390781319d201b0bc9597e8e3b2f0a1964ffdd0e50cfdf73899ac4dda1568a959ab320661a219b550b36a2b85311696fc10eaf526e8519e95c2b1141e51274c0770598904506520ab70a73649188097b153a063745cf5066db5023ac39924b84af06473cf04181f2ef16f29daa66a24c2591665e5ea1943690a007cbae20a6c35c5690835845ef29c91834f1179de2bdc882513794e5011d9888267bffdfe50d283c8f42eea1ab393a1c99ff712a0307d9caef453162d91e4b74f53121f689f7e8257948283bcede76e3607f700b0c8451e73d410eb04040f8ea9a677a9c984d753e038464fc59b2e837aef60addff7a0bc9cd93fa84ad19e6b20a87ed73a26776c37a6e5845f5fde00c0f817cd3edce63cd4117cd6c0be7ec4a64547affe743ec13e7510026c366b944da0d260302e49f03b2157c8381bb218931bfaf55de3a21844f750ce39282c5e41e854c3bc8b0332e33cc69f01bb0a8cb32086b8a54250e78f386a98f108ce9331d1ccc57a062cb83041f966efb179f4b41b37ff73c7b35438622aadc311c695109520e1441225ff8b8995798deaad30ffa4a59a8f4ddc1c99458dd9a7bc92646d37f43a9535b0c0a6d1e7b1de3c0273554e0ccea95b84450d4228dce3854e2835ab7c06857b8a329d61a230d4f7699b23474340202a35e3b99d6f478856dc3dbff0819b77479c53d1bb0707ba0208c80d4f59334e8f041e2e3d84b8a14e3e680d350a3d3955a50f4611ecba079290635957ec7b52656071c6cfbaf209975cac1965ee47c89c0e0c0043f2e4a2ddec3a2c0ed831868d488977b15623a55716d61bd6d93c937d1d975b43595c3d08dd0d80372ef2cffa1f93e9a171c1264a4f973c6b90f3c76f11ecfd67747cacb20552258f0ac40768f83059b968bfe0c4e5e8bb122099beef125060c7c844880747d8fbb4c3c946f9052781984ce1ceb62a9a47c6c64e4d33a1e028e08b693195203f95a68001de2cfa5c8a0a79e0d0b90489d1fae7b95faf3f63b31af1e0de80d10c218af5e6ad0ab8198786a8125296a88fa00a358a96de25e84256268d20c7c51dfd66b7d713699ee15e429862354e014042256d369f527aeed79122cabf8b0529d7ce8a389bdf4b7d9a9f66cd62957b256c88a23fe46f5bee3350fdd094232a3910481e00a55c43b22cc1c0747ed73920d78f3659fd8a071fb82ed39a7f8f2ed0d86b174b0a775a89aed990627114e629b2ab95fb170cd5a97aa6c8f53e22867fe78dca147319adb960773bcaa5b7236f5180c1de1882ebaf43fc963aafc3dc71fb00576f27ea934db83382fe3bf9a62e9bac917020fd7786cdf5bb32b93c6289d947544ea6924da1638c8e9960f5ecb48c282e44a9f14c4333893b888860e099f49c9d838edc359ed252f73edaa2c84ee518bbe6cc4289c801c40704dae916935a309ad9a8036d017d95b81b7534825bf2d2eb59e7172b99af1215d352945a7b55d4f985a604e02d693b8187001706eb32008b85bfee0d409597f7ea7b5b741153d7f101e64cc00f7bc9101e6b3e07a234b27709915b4a995292015309ff0801094ee9f6a4c1e9ee350dce50c7aca9b973de50b145972cbed0e5ba68f1c3165d4202c4e8eece3e41f1f3f93e2d3fd5188f9452de78dd4012f27844375a104ff4862be4051bb6ecb4ecb45469943ffd192b4f7d9efe0cd68c239dda9efe8c9946cd9869903efdcd9b71a44ff43fde8c959fe86bde0cd70c240dd28e8df219ab06a9ec6f06ab41dab2d352a541fae534f84d6990ba7f6a3ed997d32095f275682fe7a5d2066925cb4ff23fd2468be2e052fa9bb672050f0e403597b63cf522463facf4378da4852b5c5a82f222563f2469e1d28883b6ec406931f9c04a01d4441cf44d3db01febc6723c5ff87205ebd2f07b89451c547e78c31577b05cda4fdf468e8d2902a8893b4a97bec7ab2db0393237608d0abf9c8fd5a8f086cbc6ab51e1c7faa6dc70d1bf81a4bf23365af46dcc748a55375c97da685dfa9f8ca77a46f06071477ddab27369a57109f002c140869beb4fc5865ca5ab820d1987715ab264ae9dec24576e4e93a0eb41ea0b69946b9e902035b0f25bce8f46360d31ebac5ba89ba1d9cde9df6d32efd5d5ae76ef5f3d914e87be50540589be5ecd132fe641d13c9836bd9a395f44454465a6b5c07a349a269279ca7b45ca491af72e06f4f9783697f78a32bc62b8893b463de2275a7953cc3959bc9877e345f1a4740a064db451c2149e6a559050b49a8005e268265a4d11cc26c1e2783827d41ee340b1fc954a20884bc73821d0a7a9482681205e0a7d58ea55cb34abf3787ad532b5e31a4923e12a8cf38cf395ab5c5f49c9612a8d9af14e5dc9f49146d5994642776815aa43673211cc26c1e2c040709b6d3adbce36db749c3bc21dd9622f5bec65a3b2cdb69b0d6793b2e56cb28dca36db74b62adc8a93e18e805eee4e677447ca1ef2324ec4e14f659bcbe394914b3627a9459a21b91a4591344a6a24181352934699a4dca2d496974846a9b74571ba49b9fe2d48d8502413edacb0568eb45666a69cf2b6b741f17fd228f9d2ab2db030d968c61380b5d8c0868c335f9203b1c0f27475ecc913282e1d309432b7e7bd3c184a861bbf6e1ab7f176b3dd3488339a79ca7b39e94b5ffa8a6c241f0d3a10979d32e7dca66cc661a971e167c7596efcb094ed29f194489efcfd91244362792f98a7a4478a71e2c877921053323a01f49434e8a30b850dbd9707bbfedb36e292b03d4cb388c37faee20f3d68dcfe2a67a31be530fa078c3efaa987fc968cbe8b69a9a2ca92d1cb6fe5b3679c734e6e01851cb8f2670db28d1f7ad8b83d6c5cd985381b6bd04211cd2026ba81b5d7dfe248712344e14a9a52db927d988ec540841808091bf35ef28be14605d6f472350a49a7b8d1e81be9f4cd75ea042b5a89563dead117db98e5e7bd746c68493252e53fda71ef158b512072117281651c2a9d6a4d08eb83bce168d694e50a89567ac40e94c48e6671070ba926eee04ab50536243d11c558b6416914b39417295689a40c11b751e9dfc216066e8870ab0215e20edea81077c446fd0814031b8e66235289542ac55045f7198e69670556ca10bb1ef3530d13ff50062664a0b93238d9e8145ad61f4b242379238f93d1c5d09f1ecfa0c1d1b85c77413774a5a723920d61c3d18c04f314d71a1b2bdde06481e53d8ffabc16ba42ac4184c303883bfa5db087955e7cec62603c4c8b565584c3358894f000fc59beb02f0c6f058ab762713cb55263bfb0c64432d08773fd3fde0a92065da4239a354865e529914809d7208a421c80ffea3fd16a757d0a11878b946cb73f916c93c286229ceb0f13e14811f916639cb8a3df29e3344af3d4e612c136570906b3b11d1b338964225983fd8d5c53b436b5176bb6980b2c6d2ef0869b89337531fc5bdc5ab619dcb602cbdfdded99e40c1658fed1935e7e4cc24a7ed2681571cc9f3438ad468d2ec6f2c77d5dcce76b373d49c416c413442a3b1e9ac70b6b5cfa9b17962efd90c7c970831199390de2b80a2cbfe51adecb53d2366e7fdecb654b5f473adda73e354be914e91bedf4943b9ad92876f4e107e77a30cffb6378de2b06db3c2eb634b191716a136c389ad53bf2fa412f1b461c5783fe5f17b32169d01f0000e8623c1ff6cb1b430c5d8ce765d86cd8e862badf22e304c023802ee6c68d2ec6d3830b79ec1a605282edaa46a77755a3b36a7486b8de6edc9841f4337431768256b2c8826a539b9352a1543c9e4ffd7c3c559bb47360e716e23a39b710d7897e0b71dd0ac80b8988dea5c8c670aadc183302cecd71630cc78aeba9116915cac1e5e862badf401c773db6efafa506769b82260404cb32c06a5c598a5b8c877643df64c53c72fdb55fe3bcb04658d2bcd06eb1500626b4ff8c669f7ed32cee78c51da3ebffe97ad0c9da620d76251df1dea275595a2497a764fc4609259d92e9565bf840b4da62e5a956f9d7f698348a75e49d4f658d0a45b22da6042b9f6adcd3e7ba184e24932024ac01646c25b0fc24130f1aa39fd1fd0bade9157198a48e52c5698fbbd153a38f343ff23c98d744c45149bd9757e3bd3c1aefe529f1601e8dd7c473e229f19878308fc66be2c47b6dbf7da88b916fbb6679ea8bd476b3e1d8d8622024768bc51d9cf79ab23315b1b918c7236343c6097d7f1659cc494325c9590aa9b5cd48ab485ac934aa7b7f12abf3463aa32adf68a741d2aa411912ab1bcda4b0fd9366737ddb4bc230c9bfedf22dc5818eb01bcb53261d621431ed50fad18fbe204c4b485ffad1973eaa628cbcd18e9f666d00df7236d9e59fefb56792cf2b93fc7060216e7f1eac4107b16ce8bdbc977bb0464591482492bd48068a64e188c48160609b9aa46c3997590ac913cdda00fe23ef01ad12c9b4b0a2558b4ca3e4164b89607ef2274d92ccf588b3c3e84b5bb061c4e16e2892b5ac5a44304f89642d3f14c9463b394c15e91bcd4c60e70799800cce029df25e1da9e8dc70341be9f4c9fb1bcdb8b02195b1a965d7db74d0d828130f1ad7f48a38fce5b571e36f3b5c5f26f9f1b8db01a81544b26d07570317638af4f1871053a44f3413ed18e1fa6f36d8d0c57c98b7e70e0a16f4dd136c48657183d222996927ee58798af441444d44d045d375c3d18c45f2465e741809406243efe5bde28e4e046b11ec3a7dc28a609fdfde2362d2232e5cffdcd16c948598da5cf70bd73fd4c53468c6868ce33f9a798aca46333a922f3bc8ab09583fec76e495e53e5c69b91a15eb87e69ca1919556832b332fb87ea666b2c2a4c18f87df95232259a3bc3f5b031b8a641d725eb87264a5759d88f9bae168d6b2e5b7a4249790e86cdab3fb5b32fa78ab5ca73a944a835efabc17e91b792fd9f5efac273d2f1b402fb0a148d681c186015e1d46d2f0223f8c3957018d8a3ec042e6bacaa35cbfed7d0c19db1ffe9319119689f5889db92123f952c9da7f89373f23d881d650f0e94f7b5210599312ca34c9d7666cc84eb6296a368e714a57d8edb9e3747638ee0b59093f61d7e7dbf3946236e4e49c56844176ad08434ec31a359bc99df2e327ad96483a15553031a7268c393535b55a5b2afd83edea9bd9852bc428d46a6da9f40fca2f04db75270c9cdc1a6eb4412273650d4e2a77be676571e4738ea7402738383737311d3a5e5e9c9dbc90b8c313127138f55c9ed877719981aed8a24bd8ad3be387910995d9ddb54dd378db1ec699dba8b36ab5f55539bbfdfc96df3ede8f8cca6c87ab747bec6a1009bf2effa6c1748b892d6ed3679db3d6dfeaacb36a9a36a5bb00c7c605311b17d4508d0adbaf39c17a3e7611f0bc9663611aa6e76fdea685594d086c5871b0ddf5983caa56bf70ce7e2258ade713ddddedddcdb5bbbbdbbbdb6fc441e59cddddeddd4d5b879fa2edfe423adddd3f7777777bbbbb7d748cdd35babd1b65bb7be56ecf7cfee89457760dee6eef5293183f1b9ad2bddd654b1963d4d17dc1b3b0fc119c54892aabac52a3b26a746251659573ca8a4595556a747241568d4e2ce8d4aaa453e3c2a4746a58c84a27165556c9052ec88a4595556a747241568d4e2cf845086eb74beaf98f17e3297a78b81e1cecf5ac5c4a39239d9519042c25cd9c936af2b5e2417601ee67d7a32f7dea40224eb5212211c0eac6cb29d6c048c0b883fecac503f082744b80c8f942f27a315db9388afc5c5a77b186e6619d0772eff12c790f46878e5abb987afd3d1fdee15afd8f1abf3cd6c02b1e407f5cd26da186a768909f34afb806c6411c403fc36ee7e00bfbfae784fa7bd75ed1a9925b770bc58f345694b835c6a05c7e215136bb619c451ded4396615695cb2b70e26a54549aba21771170e7d72adca9021a445401ae7c37c04c154fb99429ccd8d63d42cdb52d2eaa94f2a5ac5283538529c44640052c54d25f5742f1842be50a40b041fe6f901fc64c143566a2dc7eca85cdcc912bad605df88a1b722950c195d22605545c69bd4ab63366125576e08426b2e842042a47a28a24aa689d1136aaf823c3b8d2e5060f71431a6c884faebc618c22e5ca304e8954ae8c3a376ec832574a29955cc94caee4263712e74a1f5cf9d5961e7cd9e2cad701e3038809a470e50711e2822bff023c28b9f23b50021aac5a336039e14a1b1340e1ca67d7ab0505f6baf2045fb8d206053657d6748bc60db8d0d5c88d92c1881a7421d840dd875cd5fef3fcd9366d033dc7759ced4adc57c488aafa4982752e11126402f26384dbab7810fddbbb1784133af46f5f10fe3faf94dbb64d5ae78ca7094ecfc3f4ac73ce5a1fa6ab67f6e64e581999fadacb7c806c2d0dca2042fc24dfe3f1e027b973df2a61a3aaa3f71c424a66081daafaacaa8a37b0aaaa228005aaaa0e55fd307443cb76e7be251e4adb536bd55eb4af881115e8271817987571f194f7cb9904e536ad7aa8901e224a3ea594522a29a5dc531ae44337fa7998e6bc8d7ea807a37a3e34fa2126f4c5a54f57ae104a29fd172b7187e4964c5a8d9a29ff25203f01bf9425263a09aea97ea2dc77ff7ea2dc51aeb3496ce8ed87961041e41622a2e6db974f631b71e987fd624aabf538e4d16fa6a1f47900e229966db3faf9c26a59e6b57ac83296691f10d012f60a71876b5267197d9fedb1414b422c3fd1d057c4888a577ea22e3fd19f5e9106e9771ebffc449ff34232b021cb2efda6cf541a45c31aacd3a8305661599128dfbd46dc319fbea845db53821bfa4318d626b1feb5b6770e1e4f143e7eeaaa3965a25b1671e4258382f541b245970d1485edce94037dff1de69bbe3d530efe9bff66daa17ffbf638130f36f570fbc66b52b96762ce33a936af1a8c9fb4f7782f7ed29e3d20d54f5a3581ad9f900635ab84e5d7be2171d6dd336895db2496bef61a882918f1c42fa48788627d0562caf3120c573eb7623cc51ca2aa56e085785ec6b841e28e18a518058fb7c58e0d5ae2d981559eaf881155c94ff245be7b21256c28614c00888c7fbfbfb42b8f6db5d35e7bd01784133a749f89bffba8d23e08137f2620a07f405481be7bd01744112776e8be20b4073de833f17bde882a08530ea0ef5e07d077cfdf7d459ce04f87ee3d5f91edbb2fc23de88da8a22aaaeac7f8d0c303a2caf33ec4d007e279fed04755f54caae955072f882907d06bbf03e8b5ee833031e877e83ef405a17da61c3e5fdfc4df0e9fafa61d3cafbde7b5ef7e5ac0dbbc7ff160dc559d175551e5d1fc02426a8ec7886c990396b69dbda659e2e048ab26ca68d537312436353128363956b0ac5c75ecba7b943ef9bfd010b98f399e23b7ef1b4f715fec93e7fdfb7cd64f427bc3bee16a9d2db484041b7a8ecb5b316f4cd0739d3ecdb2b09286f9f272bd6fa4dbf03a9a603d54d8b06f62cdf462dff40d6bc53da188d09c52b26466240dfa0ce8091b7d90a19422054aa77cc58a72bdfde67aada5d23ff8e26c274f66fa52d2c91486a3602e59462352f264f9e486f2b603348f1155b040e6c62a9898a982099924b31b6d92b86e8d1b6d62b073aba79886c64649ce0d998606a7869f58c1862d73cf91b4c8df040526a3cd94ac12c039323509eca65668da460924ed0e6b54e82d67d2283f62497d249daadfef2d3f49fa7dec849bb4ac651f107c63a8f4ac5155588d32f9d7526ee7340e15d96ba36495801bb27497d069a42c715c8311a641fad50b417d8f7bfd78651123aa0aa47e3588d59d757a37c01995063b641a19f7cda16954934639695477aca534d83d651ac186356c9c96795381718b90a9b2abe422101d91a01cf52227a9276bec5a9d9d466463b384b564d5b2d16689d8ad29818d364b1c991fef1232b75f5a227488982273a34d13b01bde68d3c40a6e113a5ba847bcf2e534523bfa85305a2e578a462a1702d1577acb4b82991b6d9ab8b9d1a60927973b1c5ec2a06f0488beb2148d4491c624dab0a4f477ec28fb5f7e4858bdbc8c52ca180861dde50d5b74f95861e5546dd185ffa594374958fee8ff430f1a3d685cff9c75002501250125012501250125012501250125012501250125e9ee6e8e99bb298d6c69b3604159b00fc2428d2e402f2b92b939b618638c6f84f987e9eeeeee36783c221d771e209ea68e886356ff524ba73eef729aeff1ac7b37e80cccc55ab9247cdbb60e14e1d5c7c371bd6a14cf667d17d3df8a79c52deeb7f7e799bb5166cfe779ed79560f73778c92888c32ca2861b0b42dc3eaae570dba911dd8b05b9f1f1d0082b906dd79c52c9eed3428032ac2869107203ad028fed7a6841b38b1fdfc71868d99993bd0df2897521af1f81cc5f6b4a7ff90f69c8e8273f96794db3e39768ee5a41ad5aae7233f1f6ffe54cf6763fe6c5c0792209037839a378e39ee744234cc176eeb40ec85dde4f2876838f62c33efe8b44caf74767466b127516e9ec49ed4b09217cfb08b5bad13a7cc29426868a641c7e56e6ee6666eee66eee6e6666ee6e66ee66e6e6ee6666eee66eee6e6666ee6e66ee66e6e6ee6666eee66eee6e6666ee6e66ee66e66666666666e66eee6e6666ee6e66e668e5f9ce109e865638cee39e79c2e7da367b468c5b460dd188bb2736f8c45a172632c8a943b6f8c45d1e27637c6a2c42ed712076c6620538a013b07c1a68999e812e6985dfa5b9cb173abdc193a0dcaa8c86633a4349833434a8374c6944b3f8b2ca23408e5de5c4ae34983f4f4a27de9cf8835a89d5a336a4e33979e5c973e89a29d28a29d258668c24c76dcbf192cf9335a3746fb4d7a9b11a670c525bba7d528f6820b4b3fde6833c4eace1bd2a809575c37a755a35a8990249372e9d378d229a9a23094dcf074e4b49a51737d8872e98cd8a53bbc0862c68b8f2a9bc569e5a996999f58395476c315178805365c719d565ceb86530624480c377e8c1a292b2ea90a576039643b74e5499a6802c594690f4487209b9d79caa44391f9d9d91b51494fb4f2938846d3887c6a444c1aa42c62b52772229aa2a641faf5db13356954143111d1889a50cbadb838d7c74e8976fc44bf6b6f65b6c2b2331e36ae6867734d86e9054a3c06dcca4a4bb4e3a91c37a21d5ea29be52ee59c946a5aad4e786650123303cc33c3ebf3d9368eeb64ba2ee6c492397272c556c4013a12736a753de43db1421ce865b70f753c44f7d40a4000661aa4ad2ee6b384d51ec64fae285a59717f4dd334ed3443049c71219969909e5a0dbe780103064b177362354845be22ca91e32b2bce6ab10c342cd68ac873e4342acc7193634aa398090b894482f182445a119148244b0a8148a48e2403e8b45a61cd6fc5884afefc8a9c587ea2dd813eac31368b40a107ee691571d0d78ab0462efd8d94e3c6533972e4b8b9f44b1d0f1b37474d0b6ccda5e16925f2fc69f5e1c10df96a4fc3090d270dd21fc0751a348dd2be7e4aa349a3f883d160d22827362eada29526b58e60c63959a981e29233829b4b7f05e629a9a22fe24aa3e6e38a38e2f524588db2475a8dfacc9ce89f90506f0f880e0dd29968b5f2414ab9344883f4b46245894e5cc4a4535545dfce7c6bd91a5193cf17da9d4bdfe6c8726e3865b04b7a790a74118cbee849a362855d0ad2dc90e4443473a9e87569dc9cd417d81761cc0112c4dd6d64aac820cd6c5e20e3f6bad6b29cd9cd4e139772130ac50d671577cf577b5d6ef7435ea60f4487203e3c359f3ee8d1a3fb8b2fecd734afb60cd7f34dd9a4d220fdeacd5983f42375d9fe15178d9a06e98aebd215d70a12ba5dfa2bb195289df2e4ac2899b9f4b46a72e98a934b576a2e5d8172e90aecd2af5a9f568d0a69d49c64a846a3a651e194695346a72cc7aceb02e0606b6fe9395b74a9344ccc64366e692f1b18eb32cbcc9cd795a12d98106e4883b708ec8d4c099c9d1bb85826b381cb062c3660dd1feaed21140358143951e8d0908d8a727eff2ef85845009c24f1240a58144f6ed0763935273649c854a1647563154a88b8f1230fcfd5aed7835780e375f38d7a69c2e4b22c1471f40c52ca77fe86f0fb8d77fa674bfefc0de19777083f8c52830d850d43a12e821452803efe06f2a2aa763fe70cfdfca9ea2d7641fc39ad87faa1ec1eb07dbfec80f4578455fd204faaa206f25c15bb086c527e219197cbef7998ee8ec7479326edb77e0ee4b52af499b4e73eaa603a7611f0ef4cda0724fa69ce49697b131ae88006511544f7ced476dc05f17c7d1db82f227f2bc2aafa9d9dc2522a83f097ff89dd0e9ecfc4f533f107a4bee75915bb1de4734c876095ffe659f7d0efad1456fbc27ff110d0800734a0e209273752f104ec2ae1462a9cb0e26a1f6d2cbe49d5b1ed15b633e097e3176195f65e3db14bc9ad9fa20724b22cf8560aeb5f8c600f4e36af199750e45c6fc2ba2e00f9dcb94b77e92ebf21d2dda5bb7c7f59efd6dadd5ccebb4d7937ea77d39aafe7030a8d444fb16c020d11074bc9d2dddd67d54c931ffc960659c62867c85dcbc5386b832597e52fa1c07694097c032b29ff1248e0400183e2090c9a19176984af6e6e7fdc116f8ca1f4d65dfad43a6e7de957ac5cf41457e9936c0dc9ed76a9f26a257df27ccda45f35f2d592a6514d66c9a7afae9c4f6cf2a39473ca66e11003100dba03b9f3397604a2087f7e2b5d6196b260b7f744d7a234aabb188d6a5b0c3f69d0b9662bcdb4adca1a37da5cc90a36e41a4d3648a7e0528c9f0693d830ce580431f5e2a0cd6304a25342fc2413773c0f2f9e6211c49387be90995c23745fc84a5cbfd9be9061703d765905d7bf7e6c84b34cc4e13cd42d58f95ab06c4dfdf109176e4cfd552ec2901b18b6e812c618638c316ea01b1b6d8e98a243a3a2a6bd0e4d7b4da3da0d4dc8478f9af69aa669df1f71680faac26a9a276109263eee8861c629ed9476beb06a7387b5e329a6413ccdc7c18a7b7045c431614bec84cde79d5aad2d9558b3d209634dfad1d73cde8938e67fbc606bbdc1165d6e3438e701eebce2345fbe900b10b100af80e8700356d053158802780550fb847057b7b0fcd463cddddcdddd9371c2433e72e99ceeeeeeaef12a89e5cebfda85f587791f14c6470cac2ba041ea6386edf9807162e7c71bc61a145f1f0ad0ba18ad6380012ec77079064b4cc13c8cb8a39f1986be0cf785918bcbab9813f22ae25c1af2aa7ecc3311077d5e8188d0d2f629a5db94cb1e7d496d8eb8b9b173aed199ddddddccfdb93438a90cfadd1030a2fafcc61c7fdfa49f0b909a09e8e0baf37998308d62fa35efa739690ef134ff0897093f5ffb3e2037bccac2165dbe566b4bb19b48839286693c858178922fa594af84122b630e8da79899b9c52fab16f878c132bd428ae4e9ca7892b546c1faf7bfd8d0a9a8923647481c294056d81c5173e5ffdd420db2c23233337f34784471761f106c01850b1f3517f061736060c86ed9924a2aa99c31eb5384a7b360eae4fa47f762f7e3f978fc0836e416cc5bad96a7e5f13ead176e79cab65aadcf57fde4ade28f5aad5e28659c756075a178b4b021b72a939a16d31b13d830586e005f1662db3e443017ba65cffe823b1329e968d093d0b28b39051bc6588eec6ed4d3041bd3364f10b8d618f5413dcd07e8c353b1d8c7a208ac29095b7377f3e76eceb5e7f2e88f38e24f7a378da3f36e95d33c5cdd3e9c67e33ebe753c3650bd120794524a76699152527727d59b8a1657b46c77a38d1653f8115ad4c052aac5924b2975b9d4857ea04c8d97c0866eb4d1c2b585b537da68c1baa0162b18364e9420a9f0c251b103ae268a2642d051f18318382042088e0a27bcc0010b449003264da298c12c0433a0a2092a6cc0fa5a503f27075d8e14a597249774a34dce111c22148104144588000c76866c89103a6661dc58051152341756c68d5510f1e241844b070c5924e16fb4d1810deebcd1260a8e0c162fb07881f42209e945921749727832ed546b8a99c81f694f7ffeba1be58b0d9d42dd7039d0a969799086c9a811743f5a3a9dfe52da9eae6e8fbbcf1a7fd63ed6d2572f81da175dfcc38299d3b96e0dd788835ffe6c35caf919e4ca91fb413973a412482f92a0c00639d8810c162fb078812d8cd9255d7f1806b4e4dc788d200727f09e0cf3777fe27a77614ca4a4a16910f409a301931bbe8d711d069b3739d74fc04537a8146de9a594923bfe92a79cabb5a5ab2367eb8f2f62ae88df90a00a1355910fc42db6e8812ab6013eb0d2050994b43077943d44124dac54edf1f5400ead9adf3e039c1b6d688e7c37dacc40765ffc5d4620f3b9ebe1a014d8eaa1cd0166f9c30f7431128991ebbafd41c8aefcba856be058d356d8a24b1867cc3c97443f3161a31424ad925f48a45dd7bfea3962e3b5118711ec6c76cf37c20337f9fb0073e71b69ff78874c771a4fe3f6d3488004dd8e35ee16b738675ff7b80b217ac591a3834c1ebf5f7a26ef587363177793dc04552cb6f650ad7a3e5c4888b04a90ec1c06f5eba1c6ad3ceced8167b7f6c039b7720d3ce5268446a5fb4b5929d7f1f01bfa7fded3fd98efb16b40a55fe8bf753cb6afdd8ff9b57570376f88e7e3fd78433c9f8976026efd7a48c0ad2cdb25e887be1ba0b7be46bf1ff3f31441cb4203620a8c3e7088a839803b5f0031a523cc71e77747ac0f4f959a60c3d2ed7ee2403fe736c979c77d9670b6c1eee676cd96950eac3bc129cc34364228f1d982cd0d6395c854878ef9371ad535801af1ea1cd03c280dff7e9e3faf3d77311ed0f32d61bd8afb6ef4697efd6c83933e5f2c6c186377469c3baf56021b6d9e904d7afb9932bd629ba297347cd00725fa100d77e5431d77c5fbe4bfd858abdcec7ef8dd3ed935a06f8ddd038cbc8046882caca00a27b458822ac6d3c6d5ffbcf6c2e30be3d58b44b81bfa4a0d7e3eab9100b46d9d2723b8fd16c1edf371abcfeff14a0dc2b02b3ca5d0313eb3a6c5d88de2da4ba9ea5fe839c2768f4ed3e6ac1faff649b355fba2155bff94314a7f193dcf5187d5de1b55bfb9f7bce6d5777f29995f6808e472d7d39efa7288fcfec2974be4e56a2febfdc1c8d5be5f56d93f67ec1a07ff7e30d23580bee40b04df21b141ff213e41febde7bfc62b3904022bbfa50a5b5b5b7409690ee378aa5955b4326928f42ca167193d0b0caea1499cfff49ff5e7fc5017e6877ea3c1919e8544fa18a3d1932a3f817323116eb47162cadd6c7cc703f440805c045d469fe7bdf8182c2c1faad96a743f3814a2d1e1c0f2a18f9d0bcbfc9100388e3bba07be21d297483f92517a920c8f6f8dd1e8597e9b818b30edfdd3983143869df33f73cee9c528753c406f7f0b00d72c4f954a94d22fc9968e654a9ff4a16c1d61fe4da39a1de03f6f68148a43ea7ef8cf7ff11be0fb22c6bf78fa840e09fd7cdafde0da80fa1746874368f42ca497a718a7be3c1555d55251c7a3ef286cb92111ee967e54fa70be8c1f329f1477989ebe3c41dcc1f2f43716193f59dec4f224d2b3fcd6d3636179d2c3b4697ea5ef21744b1fd6cb7259be547a8e3b98633c6143f7f3316a6043d98a01b3a15c5d96df5e622c593de1e4fa97bef4dcf1b01fbb1e432669caf8c218a46f488c273dfd10e9411d0e31481f0bcb778703cb8f5e86172fe959bc0df02581be30c6832ecbcfaec710d203c177f431e4ea52c96a14e95b35aaf485a4df9e8ebe21a41f3d7dd0e8bb0e07d2a8001cc71d3c86801e08befe34c658e2e9787017319cb021ef5cfad2f5b2b53d1dc2ef79fa1dd7e1c0b1c1ad9de33641b1c1d1e7cdee6384f17d18c38c5b596eed76843ee46ead47d8fa9a0cdaf2f361c0f80b2f5e9017ef8ac701c173d5a81dae425fb3a325fac142dedc8938e4d397a76617e249fe09ae7cd0179688b8920a415f57b2bdf25b3c39d327b97d12896c35285bbe6449f92c05677e52caade5b2fd7ca10572eb4f4a298d5e3dee32cd29b55725e43f3f2044e861b8f3bbce1ff487876175c30f0871671777fbc0e560baaf773f8c78175d5c186f23eec78b43b48fb77a43b4cff483fe7cfac9e7eb61dff9d50643fce13681e869a743848706e77bccb6ddae76beec62427f8088f27ff1a08f3105b217f431de603f2c5dd0f3eb27f7aa331e1ac5f7028d72224034c8c3ca5331c655fc42fa9fd760bc39900e1fd803625ef9f140ef02f746770b909f19cb37cc750b2b7f93948dc059c49f367e373bcf4ba3a6d53eb0c11713d81b2fd5635874d2a0565b6bb0f9c6ad7f37f3c268ba98d95285255dffd8e140bacca4c1c94adc5f2cb9eeda8525dd68e304922b7ffbd0862b0d70258e2b63b872c6e5970c0970ba5114bd4e3e219ee64f8ed2a7f973fb42b6c200f346a33d25e298ef2022d850878a51d1c609d69d1f04f419fb86950f5deeb4eff37578ea455df992e8bbb19f8e1b2a767eb7b666d982e5e4f88b7ce376ff0e62cfaff34e193f6a709f929b53d6e0430fdcc56d8f13db570e911bbf04827de037c5db6d84fd177efa376868540f1f7817f08423eef07cf7c0b7bec7f3f96064752b0f98ec5cfaa546511b26472e7d977e41bdb8347629c8826e3f6873eee3993e1fef10cfd7df3cd3e7fb71c3adef03ccf57c261c6eb8fd375c8f9fd61d6cdd1c638cf1e327d22eeed831b2c6cc517e6b1f4fa5f2e3f10d4e83fc7944845cc0029c93334586840f059474c45b024b0fdeb85ea76cf10183fa11a4e8a8b44d1566450c198d00000008008314002028100c888462c160301ea99ae23b14000e859e4678589b49c32087710a1963002100100300006000044606db00717a79d06bc7bd4a04f2404a6cf04f531b43d1829a4cac527b03fe8c9c34712b7ece8f9860f8a20ea117f96dab79d61aff747f86413a8c25862cc5283099a6ef92b67ae92b03c81bf22b1d795ac2c3439188306b89df4c6258eb0edf37373e8c667a4598735f0c606c5c390dabc33bfa0fe9d63ed948b52b313e54be141298b682660d57f0f0644bd58de6ecf5175ec57badc26c8a18519b086069215125de3959be84388f37b5397050dca6094b692d9ba4b4d076fcd7004f5799132de5e17eb29d9e8ac60c7910a1148be09e4579b84cca06e421390ea83ad44c82c91441e6acfa0b17ab47ee74d65f498ba1840702ecc065e20281224e7bda082791bf3e2a724acacd089ff6854667330ff69655fb5c6472f402839c321e2b21ce18687a7131e8af0b891c09795c1857b862c351d436af2c1d1750ea565745c778311099ccd1007cec36837462f9ef6f69712719fa5926546157c7bcf1c3010fc4c0e0c37891b6e706a9700b7f6c00c8fba607daba9181708e39a093396eb5b397f16a2f9ad1bac475cf509aea3f1a54bc4828bff147becb7be31626d636e398a359a569c3737fd8169dfc4479dbb9c732ec4aa37bb78f17ed6454af29005ac19fb6bf819186c3ec0f76d6e47c0abc36b33472f13a792fd1ee0a258d5f7a48ed4a6fa8d7cfcd2d149157bffdd218a0af1ad94fa4195974542ac58177f8b2ad76d7a9861acd6a802e1814819a30dbac8dac55ae5429ad71c5970ce930e2a817e10fc99155dd9cd8c5a6890315ae7743eacadd217ad7460cf785d536749ab8af2e2284ae6e88699d6e756c456d26a7f916012972a9c01a7701a5a0e9d552475bc8c345c2af969061a995d71a3df8aeadb5e45c3e4bcaf4790d30dc49488d144093fbdea23bff2d3e0aaf01b34b5ac109c4086d543983e93d4780ab57afdd84b5d39daf006fbeb07f69aff4aa2eb887276fdd10f66f25b68a72941da9ce263879d0fd2ef899462e310b4e3807d211672125520a2efb60117448778ec4be842253991abeb21b50804a1dfbca446ce2f094da3a7b2ff70b7e6f98cf35b8da3c5a07b803d97d092686205bee286f8326d9e96f3cd701f929eaa897a4fc084a0bac77ac9647cffe567162884b3d9a1c87879b0af455f1a66446eda17bbeaaa4e95b80aa688ef5acd04e53c3d9266c310543fce054b8a3387a67fab079467614b9c63ffcdc293b8a607740b2b889cf3a066c098b9bbfd093c29d2bf6ba1fc700d00308099e24ae45a6d509c8ca3591a11d3fd88ceb3af0deec0687bd1ab8131dbfebeaccca7a480544afa01c4c29fdc8ee4e171c3899b95084d377c19e3c626eda5bedb6ed327d0230b58904e15f4d0d7485dce74cc094d595659eedec314f4019f20eb891e09617ac897502cbb4de6be35457e9b348d73e61fcba93e5c931eeac4b2d6b498a989c9a2b4daef092e2e552b77a8a0508882fc67f2f0a2ca15b90ee488cb199e8dff828eb787797590fc6b50cc041eb8b78466210000c366b458ab9c45554b4ffa73e0515bd4f1b99a423e7ea8ed144feea3a8256fdef43b34c2f7a7b743181897822a2dee10643aa55076bd52724e481b9a16c900786492d9603b80bfc86be41e068c458d18d209ef4411e38907ed03f041fec746b28ced82119863037624fde21fbdd440f8ff7a5a5c779d7f18d9ec5f12021d473a3babc307c67d20008c67d868f39229f14acf52617d0d06dde2d0b84404192269db5c509c3d7c1e869dc2d97148b073c2bd6a928276225d174f1b0ef6840062f708ae964e8599f80e19e15ee86540348e06158291920eb6f6a2d3e019f72141c0f0db84b6a2978fa2f99d9bdb922481a551a7e1a4aefd3f4fb3cf14274f9dc71cd2e140a3f30c52661915d6fed62e5c2d8999d308687054817feb9cf8521ee55c01cfb5c5d51178ab4fb76c5bf40d2cfd699e09a49d90a059d0c7172cd98ad18f4f727c0752b144c43b016617f5e7b34e42797c432da50750f79171b1ab6d9561b6863fd884c942746ec8758954d70323b5315d01aa1b65920964884f63f38ee7122b26ec295fc5a51d3fbcad708c714556899c8de9e69a14533a73bf4e21cc952f48a72054273ca0c330b949f7ef444a882f2187e4d391af6fd74dd7073219ee71381807d227ccb46287c9feda297abe5c50e04a6ed546ac7a7f37feabd384920a749866f56d6fc2147f94dd0b20f63be10a6ea697497b07563683f07f75adbf01d8a5c42ead8589ea9d7fb9869249d05e5a3b20e70d3281068065a1b00cc0c909c97df169ccbc8dc1d75828c4f680ec4502a3232e45236754aaccb7fae09300b73e598dd699570c3b555b206cc25f35514dbdd8c45e0f5ca71a687f78508a1fe81c15f4ec0744e231d0fba12f834803fa97a5eb86bdeb6eaae85ec41169c1499f2b43b15956d3ff40e30969aad407e280c46d1f4863fca7ffbce5e50fd187c8c7435f66dd428ecbaf9bb4254c63fba47596e0821929ec9d29f47776c9274572d50508fa94192a50c1be6f452d986f29b30d664f597394809a55b6ae2bd39f79f97b5a192fecd0b185a43f778fb9be3a98028db575061905170600891161782784ea975fd48fbfc69d2140f2573371f7d348f7086b567e6409ab2356f45d5b9c396ed848362a32846d286b19344ce5fec4743cbe08219d3448e89ef1ac651058225b0ad4e00b8e8ca81592da51a415fd825e47dd16636fdbd35e651f8220c9fc17d965ac744d932e70b733ca8c12022cb56d4b1940f69d728a24b50ef14f335a91803e63c7a4d3a73f02d17c11f63dcef84d37526b9c12fbab01019255bca36d791e94c64ba1209e01e34a8e3153cce519e20487f0359388ce572626ca9971aace5b38559c718a400e8a5e88115a8f8583ae32fa8266377de5f8f8f70fbbf81d8100bc14fb1b33c4f64eaa46d5b97ec05eec50011d74b740d9bfedce02271e9f594d2a82de0631cd89b4cb6c2d44f1a39fbcaffa91311ecf11b6d5268bce86660410668ba7cc1ab419106ce8ac2874e390716f981754888b1d3890a14ef9957bda34f80ecc563272d98adb900337e5e7fadfbc11093b1817252c87d9866dc0cc86f390422f185debe15447c6427626216ea940347058b15ab17ab9290580362d754148da5fa8c47f5abb5aa5451f4394cec4e502781b4d0436144d9876b426b4de3fb0df1802bc9e357526911d1415af911d29a7b83fb09ca4b7c157d4c0b17229ebf3ef02944c45708102d3004032b8750676b12539f164289314782d4fa8d44185b3f892065af8d68bb87e01549b7d6784d8bdb444d2280e02bf193678f2aa598779c8793fb996a64f16cb57f45b6c50dd08eb97f0a70e90a38fab2fc6957ef43b7d41bebc0a51962fe2be6450fc7586e08b267c5e9e843fc7d4f36125b5d3dda6b9f178780817a02b6f798e6f3c5b6c91608fc275589eeafb1e5e75e8e051c43f966406ee6542452993dd28ef93edadb95a013fb4f46e33949a2efa563db2267aa89859b2b764da60148e04de5c5b302f3a2fa4e743075f597f762ca12b8e67fab321c9eca9db282359a700bbde14593feb5507c5c2e6a245cbefd2168e3571a19af75ad3c33c1d7eecbb68f477e93d4b83875acff96d37226d505108f5429510ad9835faaf7e9e824cf49c218a317e3900e0a110b34b4e7b5759165fb1be5dcb7fcfe33ed7386283f81fbfc53a92cb2a5c5e13bdbadd2392abae3f2fd142c0a5c8fa199b201dfa1cac58bdb8383ddd1958fd9708ad486719d19ab3d662c62e98af1d1410e40c88fd655819fd43c5f0dfdf2d12835b6d0475d9948684d28f17446508a91c03640ed1d5a19c68f373cf4142efd7df082fb95f6511c307c56ebd2a1c9301c8fb8401bb5bc62acf5c27513a035ae37224a6d497ae27bab044c6590985e474d4b31d72cb5b8ac0c725d3e0af5a3dc834c582f7ca27e5c9c73b68702effb3d9ea96c6ba794815c7391a62d722c990f1d7407c0bc5ab7684252713ab24c15ee81e3fe26aed8942826286a50347e351f3fb09f18addd0a914d7c5872ca1270c9bc1b67f52b18dd571f785de22ae1a81e90b8c8b60f4866754109df4c5ab8ae44da64bc50624446a3532e4fe6708386d000c2e55295e8ec93c6e67a38c35e4d042028d89e3911fa70576adc797a1cc50a2eaabae78a1a643f26c44585cbec67b2109fcb8eb4b0037fcf40a891924e3bb1d7a08687ce635dba31e1354b503a3704ef98026b464370b54b7ac5e2181fb13e5fc626c4b5493caa960e3eb2c58a6d1aee0e4f1476a9bcbfbd048264d80c370e5788985606632d29e235f84de69315a2f5666832b9492730c542ba548c5c2c6a11a82f73f724d93e49b2fe14eaf984889f36e8a550dc8d623e1483aef260023f5df3214e2a2572ac59e8c4976ec6e1c5ed10579d9931fa1e18e1e63af31bc4f4a2cfbe67d4cb8b5d268f6d55e81ff8cd4e36da9908810aab7647cc001237fbe47de0ecedc7818f74d44c45d55f1c54b71f27febfc2196c04d3b168d58ccdfe9721af1197ce46c6aaa74b401741c2904c2e8a4057b297656b9137caecefcc590b8356f8225f24f1fc726e96e2ee985d5af83eb4f1ae73c13261c0323888a1856cf3192e9bb47979eb49b9a11cee7881a31854d4bbb4611f071ba65978427e12b1d0a94920002b960c0992a18d2a43f25b240510bf459b4b00f9487615860bfcb0eb79cab63631213c1559d943f8a8d4b0ecdddbfef462543283054205b1a3ab4b8c623e4688a71b65d9b3747cac40fa7381f2fa6c090a016e329eb7ed961fa21ebc2d2259f2b277bbe62613faad7d270b10491da7723082f62799dee038aa08c929b0681578d43b1f897b8786bdf58f3b01cd63c264fff68f6c9dd4882c10259f938ecea0537df56a36e0297d2af854564c3ac7350d9c77d6d1998959142b5f42a919ae21ea65f84430a02acd71936bc260332af1acca57f5740a147976f0ba9f674e0d92b8c480cd912835976aa667d394c575c8f6013a6eff3bfe9fca59fcb102661df7f23a547744dbf0e018b896e827f5d765e6554c1a4273c149fa7922dbd60f0cd24adb18d2cff70dd58e2365acb85cbfa4a33fdab14029b167edbbb1576bccbee83d10183549811ec3d30ce497d910745914166a34c37fcf604450d46c9b3815101c2477016afe80c2e838b0c6463f25a281ea3f0e5ef66bbe0206ecce6caa8a7e83a6bcf2d034f0720630359c99bec9f516da37cb886741f3642f9df2751637887a534bedfcd8d8ff59c2683f0eda017b6f202deafb1b9fff3c3e2bbfb3be3858cc9958fa75da5533f4de294be216a677d1e76459f869a2e639712655f36528c96ca291c01999f3615af0f9b069b151a530a02323d99da449ca21fced214387995060c011d5f8db6b2860476f1c278195f803f7fe4f67d9d3a89ba333197550341c1bad8e03671a5019524ca8223616af2e9c009846edb61ce9b74aa811db0d614c8815e2befae489db987a98b300e3d20446487e1996368ec5c32adb5e8b452ac827fa2434c3a49c440b5f42ca349a745a8ed3cc2c17bd06d5085e016b997d91ef411d24b8f95d606601d9809a60c08471f13b7a9d8ad70ba8094888e44eabf5556235421acf7840e6cbf5d437ca7a717d1d5ebf8b9c15b9cfd6f6512649df4be711545b2d1c738dc3e2481478b2fa8a69d1dcd9976dacf87ac29b65ff1edd26b7ab0362d5f24c05d1be6db7a7bb32ad84082b8dfabf86d952cfe7824ae25d4607b4f688b5c5fb1e3e8cfd7d2ea8226c17a041bc2ac866e1604be24c9e94fffdef592e87dc5a4963ba0ef3dac178ff03e1db447f5b0b85affcf9896fa9b87644bf2f600e4dd8d63e9cd4131d0f79f07bcc2e6de6c9cd63c22cb8c35d11e623c48da295bce9eeedaa32b45b3f8300bfc66c737e4b428d0df5340af21e68a82c27319d6c09cd737da886aa991b68f6c2f9cf26cb4b51eb12b27abb498ac35fd0e4ada4cadf87587bbc4ecdd44f8a86f85f576834fa9de67219940bb581bb03fc9a221597bf44aedbe01927a5012debf71c0eda99bf09593d6ce6ad131a7dc3d36b6a2f4ba286ef33416fd7eb2c9d51ed63d353c3db281055d66a6f6aac030bac403d90e1b7de1715f41d74da738849dfaad29b0d487ce74a452bd8130f493367219bdceff00d4862013b8646d2474a3510204fa5ffb3f90d2017a1610070ce968f5e320d189beef8f4d1426e9f6e93a5d863ede0daf5cc27c6470b81cb310f2dce6172c6d7d75daeeb8b34d6743db72e1f73b65cf2bdd8608f2d7eb7d8b8b3151dffe484562527b283c8ed49f9acc8c0fa681628be06fae93c7921bfdb9fee462438fca5ead64142e636104ee679e3733d5adad2d268b04e4afa302328ef518c4ec68194824651070b814f76bc9e8b322f2fcf6a59333725f35804f4cbcbd89564566a6bacd8ab680cb9339a79350c5984ac088a1a67d41931cec7fa84bec8bb5a95ec66e083862271c62299a9da4fb7cc9abe6faa38f66577799f9615eeb608c1d218f569e4b205d355cb338afd3e8ef2f731c6f9287b8efa3e3320ff1c449c040c5570f67e0444cdc3a0c11430c744f105c3d1ae38d2f045117c19ee3928eeadb3c7ab8445a0bd084cc5e4c03200284fd2f2c9ea6c598b4e6cf7368424a4a1e7ef3f856ca267da9a0e7d1ed02d0d4c3fafc43db1449f7d67bd11dccc5f2c76ab12bc4e95bdf1e4fde6e95c4a8f40ebf777904100ded8140c9e6e1271df2cb85c77dedb8e3bdfc98aec7501b0bcf52d807500f49c2a6bd0b1b503eac085d88ce6c47801070f4d442ec821c80dd42f141bd6b4d358c47b9b31c659297591af1832fe8596cda850e533fdc09d2906001f79cd1a362269be1b1c36ce32d0c0282e8984cdfa6a7982f11c6e4d20f7f0c9e3dfffa864324ef544f5b59cf8c2a6b30ed216bf6807a03bfb79e38a2c2081cfdbcb9225c7bf29109a992b7e5f4d8ea4c6c45cb1c7abe186c42e6b158d80c91c9b542ba9d1f78a93c8f3588379be208dcc16d9c82341b476bec0c2e8ca4819622f9480b70cccd5e26c60e8d3b53eca8ccee42c7e903bbe1c05bacf3df0d6d939e08cca405f66a4ea399fb5dcac265b06298fcdfc20273976ef74714de4ce8e9a1ee7feef484866367c7b09f22a49da19d55c602190cf5491b6acc570dd30c3bc037a7eb9e938101b2413f25e5162a7bce5b3dbb3d22d10a19a2274f0f1e763f3587089a58b044002bb818b5912c8c00be049d960ff0d6235c955592284a333fb5b85550b9d3b126fdd64b5ad6a35f63b4e65d15702cbaef129d1e6e7845235c589d6bd79165b51060c5c2425ee881f1335832154c3282b113a148283fb6c90193d3480a2343d8455a64d343d58a54ef069c8b751487a10582ecd1abeb91426223dfca4bce26fb2c03de9e10750432d0038a2d1bdb74e8881931ef625afdb58f5f49339f688325e3198f4070e484b8786009a59a9386d8df28210520856c52a4213aa4c9c1ce09554a8e373961af925ce750d30951c9f253ef3fb6d838581849a87531ac3ecad81677993539c4019d17e810618765191ef73566e08fb6560916f77fb1176db7eea0ad19815d1a293ae6469a8c2445f1821e97ad0c61bed248c802fc06e854069746d4b61597e5ebe520af9f4acd6aaa0ae0cb8085344d082762fb9b696165cba19162de218e9a6c2a008a21b9a456081a5c1e1baffd36f08e4891db57d4fff53a645e05a6a9be91d993a489a478a8f321df4252f09d59475fe09863bd15be5138f01083e2f6c1851c044f69a447a624bc20b37c7998b13e30fc94fe1e45ee7d6f5fe8320279954a0ab24a20319ebe4a5efa7ca97c6c07539a0beafa7cb426ba08ef806a4ceb438b4e5d0245b6e655cc982d073db9c4b3ff1c948107744bb4996073211f530e59d93b167241e3d8e9b3311745c2b1b63432dee642699b56eb77be8af5c24fba21063b21db520b94a0635d2e49d4ec701d5f2ac2c5f71e8520d99ff5619220f6ac3f75abda189a8854a99f6d80caee362beb6fba3da2c33d1ba9af915ca4d1ca62a152d76e3b1026f6d6566baa98cf4e1b8e1d724c9ed23768a3cac25e7d408ad8af2ac6e7382908f405fecf3aff245689d09605570e221428cffaa8553b00d709a2b91db9e7aa382b7483d886a880da7609cc40f89b65d623649e322ec76b358872a57a122676c503d414ded9bc345ab9dcbb7429d685a3bf47575acc5f2e84a285024eba222ce0d41515022a3b1ac9e18f79838c9c846be214165bd991289c923668604d5d73746d7a0bee0005886973a60b059c75945b2c9f2e541d4827c48a58a7980429dae03eb45f23b906af977e9e37d5daf25cc9a06a80115529c4222421c46d3450ec549bb4fa725deb166efef6b60c580faa16a0a1fd712dbff27853429a4d340da477ee71122705eb8e2a98ed717aef8f8d53bdf04e77160767bfe4d87403f59ab6d8e24a486df9a4a0eecf6cd7762abf95a300a70ffba88c5ea22e23f6c7fd0fffae73f2eb30c424a13202a386b01268fb8a7d148a2f32719e3174510a7361b53af4f29d5902e186e643cd31898f5c52f9a9e7532b2a452b3c7b390f7ffcc335bfdaa6e4d1e2362289c55dd69444aaf31e733fa42b108761b04ae29700a29202ef487bf2a9bce12b6a55a27fdc6fedb5b5cb869c19fcadd4be8dcdb5188e97c18eb2eadf45aa943d898fd06573db382bf6a3e74a054fc7d517a497a6e1d785b30769d2b64475410f488f32a220618b40f09b60488e092e1bc6bb544e8d46b7f977f69f71afad68db8f042723bed1e39dddcaac54e4c4625fb4d7cee7ef13512d7687db0509fb3c0bbf5d1ce7857211eda327123af1a0d5f742b41f6fb2984069af717c361366b902ae3549f6a42804a7cf552d5551b9514f9e1f800dbf5c1fe8564eeac924c4bdc485b5da7a10d210b1c09d8cad079434b43a4342960e0a3478de1f6a700bbe44a82544b12ed1b5a5491cacc59f49b1a43f6e634a4ab66135dfa3e4aefd050c842e3d9014c1a5018bf12633a5af55c56d17ff2583776f9da4f542e4946e665b5f07946a854e49a96a21a0c889118432dc4b28ba4612c79714d6b0bcc0783ec6f4981a686ddd9fe657f5671e5ea1f6a7016777dcc64350f2d6714094470a50bc906caa4bda2be4a482c34fdb2aacc55214a7493c489ff5a41497a171f83c76e9598809b0a369412ff00453def12e50b1d6ec113fca37aa6c3c02aad4950de4a2e68dace5f3e9e9a17debae1d5884ba32e83e0be41be828e4cc04d1b373034eb7dd115fd9935a03817c862fa2db93c3927136f015665d5ad448861e1eaa86564f222ee00ccb5cef8e530bb64ecd74033b91b20d20c2dedefa466f2c9acbcc3b2adc703db39ca90a9da25b868486c3f086dcccbdad120ea583551c2c51b287e40b083915bf9003a401e38cad3e41ae037f2d00ab0b4dcb2ea3a57ddb508ca463eaa7326ccc668775c19a9de3c2a080138d06282b16ee380388703d01dfb1a9ac97cbf73a8445ba45beca401cc9a0aab4e2674f7cdc5faca1399c48b444156821bc1dbeeff41caca868a9307667b3efebfbe5e3627eb6c5b0c2ab0f56b8f41c30abc1049e0b02536e15b38b64245132bc38a23470b6c742e4b1a12a9e6846960915c56461c61ee006610297d98808efb85b281ac141e6d8ebffc655b7eed2cb172e02418958c59015657b42a08ff14740402196ae2183fbc2ced132d9aa832319c81fc55694ff408400b7ce01d7f1666ef8820c67da8d53bab0de1370930d66dc4579700fc1754a5c771f8162f28eb9430dd0d2c6f99c906019fde483955f3c012599ed034a3edf2c28becc77a78742c924986d73fc6fab1f6e1d406b1d37d51116091a688406a684b06fa178e3ef9209c4c6a0918dbdff5c33f0f0bc1cd0173b73f274be41c30ae3dd8bf3d36d004b9f820e6e84f2f97d746143201b5446f86468f6b4dc2aa383bf0b1fe6c5c1f05c0d8ff90ae923e8dbc73603efc18701bfaec888ca13bb15f591e92e5875432c2221833b69d0ebff2e6c2413873b0fd343c774d3e2bc06ab6c784c530274caa792543771135318e622af41141d5c74dd989a40331ef7545d1b3f9e2355dcdad7c40d6ffefa414bdd05ef32f4fa9c7d9ec6264f45469495c77340ba77b628b58edc3b82e84a9191f1545abf99d5e58a4c17ffca2799c6f9130bd5d09a1a5030581fea6a42fd1f72db33ed5a1ab01620d8e53e776fc03e767bf430282ccc103ddb2f362e2dd9f394cc3a958f6d0a6f2754ec421b009b6c12458e41a53599942896ef2b8901741ce62347be4369b8485d3173c13812b354d7ca1bcf23faa629fe6bea7be97d8c71404685d3cf671526a2fe88da00f3c50248bd6269ac4f4a59088506d4506383173281efbe0a1aa862e815a4e1a8f2f21f6cd40d7f060f11fa0eda8e60691e17aa108f1991e43e97aa3f7b1a431ffca8f91bd6eb7bb46770357a9e10b5a97fe3ea5462df32fc643a8c11887aa117118ea32938b1949316383225ed464c0c23f40737c804f5c2a380361a10fd8673a68314c8a06240e29b3135dd6c3e198a99af8188410e9c67ddadb2351beeb991ae405ce3bb2e698daabac24c20557b68b8244eccd4c39ddaa13d8ed2d1c3293ba338b06fb8e68ebfd12f42c2b11f0d9494488195c539bb4d58a4381f4d9e752ad56f2508a034e63c381beae8a7816970902f8ecb4a15745064169f18c8b40353f2da2e970d6aeb2d57da45ef7fee6ca048ae96185604603a437f5977c4efd726d7ce73d62da63a58d650f3045d7f879c95f40221115837e4ac14740fba1de54b3f89a22b5fa32fb39fad7d5f20a15d78fa0afcc2acc7417e6b8fbfa9586324c4e402d135bb05ac3e263405b48fb766dc88461fa3e7201548b6040856336eb53a1e757798dabcf3f544ac6bb2c7b1cf42e7dd30ab70c12e90646a332885eb6f313b23d14db54166a3c6167a0a091fd9569ed2370de6f51ba336def382e4679377fe95517d0f0327a189696dbc1614f30405fcce56d3491b30e5d740e7a07816abdb2794204943e54069b49b987cba8c429475409fabfa64da29c2ca1f3badc67f7ce6e90a701b06f2338b4372c1dcc5582123580ee56941963c34f7663d3f3b64ce940fd4a9d5dd04583bf144617d3db87fd227c0703e160f89514a985d3ecc14983b16320ed64f068d6c9ec19b77f9bac901748ee955d2c73e86feefb976af81d11079325b7913ee7da5ce8dc7324d45d52aa400f00e13590206ed80fbd0348d01ed745f8c261ff578537003a6c54a130c9068cb134c6326e772da34c66f7c2de869ef516824d46f2081a1750d0c6dedbffe6b8bce53c74031fac560dcaea95cf467dac9559d17710cbaabfc5a45be60d374577605821141eae0080e97f880d100647039f9d63727fbca2385a15f478792e255ef083079fe3d0129b30647129fcecf92cc9eb429e297c146f94cd60a439a6308b20756305dc93f0f24fabbb0fa2c1c407b2165422e8b0ab3ff6b16e1d2a023fdcbb7ece9547d0e0dfea2b98004e7492983957ae289f27ec6907068a1b9172d421f5a40de6c8f9593d431e7bb50f4a03780dbedb0bf1a51cdce0dafe107e9fc2d8f75499164db19bcf3b2be1e9949b0e2b5c60709a3075449bad36f2d728b8a5d7a3654b9c6203cd868c7f5fa69ab0adbcb239377ed71d5449f8fa5cfdedf98c034ad45204b878df44c5912ce360758bac6921dda1c06e217095f9049eb9580e2cc3960c461f37484d782863002dab7fb4584006a962b81b17e9da712d56cb958e50f7a71164a544e46479750f6b5be73687d22ec2d8866d97ae91e242b7aba75abb2ff0ea15e404325296d8714e2c018227a2153e26ae85ea83e46d0528151de8b550f5f4dcbf0b4ffb0e489a1d81c3883985753fa3f465e56c9ad0309987ce6b905e689ce85963a33ec0712f9ec0821c8f747065f8f84979373e51bf363343d07d658a6e4f38c45d3647fd3d21f4d77f6fd41e4236b46c77c27d44d8861f02bfe9d9e92de1853a98761b901d2fa2e13f86938c61734ca9a5c1189b6588e9dcb501fd38746e55de2d3423e48301deaf845fb835a79abb69b9c2f6e1272a44671e9e929d8a364c409d41e7691c894619e0da27372f0092843df9610641876c67f394f1017c4534280012a65a14d8113b1ed9fe5eee98427b8afe7cd97bbda5fff77034729b26d9e320019101c8f6c9a0e0ce9335aa503d3d31b4c8f66b83022269cbaf18d75e9121bdd4bc9b485bec9c71b62b135cdebeab595608b49f24866bab91c61b99c0014dd63ed13d0320061e0416fd59da0ac954d6165c10918ad53c969abb68570d85183fe5b1704d81a36a8fc55e1d638144383c5034892e1f1fd7fbfa952c72745b1b8c52636433defd9d87767de94a5684109ebf25cfebbd56bb1a3a03e030dcd43a5960600ffee23ce9437ecc9b755d34c5f492402509898dedf98d6ea5b4ff03d94f38f4401fa0ce38b6c715b9f7ac118212c57ac5a0eb6f5b64ab2645045def01e3897eb3c8d066f3eec3e0b81e38c8a0f5c1152915fd6ae329c2001035d627e886085216a26a406f71ca6d640f7a45bfed7f44f21e8d585eafe57177c8bd0cc440b867c80c25c83b87d53bc25a75bdb8be8e44d8035b5e3130f180e08a737545a108eda75c1ba49b9131f4951b9bbd2529bba4c866d432a5425540470c263004f18272dd65fcd0fedc6e54c3f4963e9e89eeda09762c532f571d71080e969e1db75c79b685aa65f52defa56a12fe9b680ec5457dab5e9b0f8a4e384215554ec0534426ad733313fa91a7b3d9c1398eca62fb712559105f81e8a13354f4e402fa4c595fa55d07c45cf555487603bd41b38e95a8da7e54f2a9d91d1c6f941e377f0b6da04ec1d85221600b065b22073224e6825cc56f3ebac4c9f403c2dc24556879692d2e3602446110ca9c1d421a3adc17db2c38a43aa67cb920d84c5bba83b317628fd70a7df25435c48740015bd2ffd89678b9e318b11aa0e3089f4820212019905cd91f4bd3e88d48111545b52d95cc4e22baf2e2aad111ca2a6cfc9961f63d85f514cf6e7e1ddb7d31e86232f12af51757c1b80cf0495222c3fee069413b052b61ad4a90c92ef102dd7603a22f7de0602751b20893bd112fa87ec2ccda9f1e7167460d4ebbadba4689d29d2da2b45be8eb118bc7a44d7f9bf47eb4e6ab7d9a41cf68ec3843516ecb821b7260a9ce122e031ded2dcf3b95eba7e150223d8ac562b1c2dc98e7e099a1940abbd2e0188b84d9c719e38b5b9bdda173fa8f735c8036045b1b0a29ce138cc81fe35b195c831f24354f651b9128b0703042f89a04bd718ad1ff865622ec443814ebfd4c62a1c30732696bcbf7dff77ff563380ab09178fa1eef205a92f3e890eb44d7b2124fb40c0442dfd0a5751589dddbef2e2925e4fe44a6f9d84beb9262166cc744a43b410c8acf9041c0c925cb61aebf980edda2c8a908b3ead2850fb9fec8df187af33bf08ea22578fa8e6ff2ff5f7d23cbc6e643a19c6b540555066b54466471f1241a507241db97084a96850277210a0028b0137d09536765352dfb2db79222ccfa6e855b8a62975749bd008e0d6400d164519519fa281584ccda302d08165d498564df46a4daa7d87ea33b78386a90d666b20f67134a8de9959a700c023908a0bf62175aee16c308941fb542170eca38e82b97c7865f51dd1e1861b36dde92601a4723bbe1c5ff5a64221710f709304968eb4e060ab1650f6926fe26104445f3d4199227bd35bd4f007984cdc2f00c8959c8ba8c9b5e39e08a12c1c9c7c39f92cc388f22e49b6606ac2a44ca0c8f7a0dde3b92abaf666660cf85396e2636286a16ec7d37991bd360f6c2d1edb77d64754f34ac00747076ecce7d4ce3a1ef2d9f9e316602f21c9a4a6b949628dcb58787f463c8b209c33041a807cc0f28d3b9e9a3b6ffe1d7b41463773f28690a01079a0bb878a9b115ffd280820d016842792acf475400cbe098711ecec890b5a7529b39221d29418095fc466d19f9391d38ab9f9f0921cff4f0cc34397b948c8d18534fc845028b6c22a82ec24d12193280b4b87fb31826a60fe2cfee57bdcf6888e33e11aab03a0514ca83da230820b077201a0e6a6874578980c5a68a282816388606cc384f149042fd198941cc4e0d3ae7faff2cc3eceb237a925c6aac709a1a7294ee8dac80e2464aa6c99df2e2ecb066383e1a96ef80d596bdb00646505a6b24fe9a4b9db0743e48a0de3e5766a26c631cb2b0f2b576e61dc6490e6e33f65153174bdf00439fec8676997a04ecefd392e1de382930f6b7043ad12a9966c5463e16ac9754c320506cd98c963b7192d0ea5f969066d88b92691a7593a24c8e769ecc6cade9d105b538f88801ad9b5852a788b39ad11f812135db34256bae283071a1e0cb12f555344016f93c682cbab9879b020e92449561cdfae3a76b220022905f15f46c5611d47dadb75a7ebdb69e4e81c27e437d499ad45103952f93aea4ad9e2394ee79ec10753cafe11a4242b303029e0846402074e1cbca2d4432b3eb23b05464ba0be3b0951b2848d556184082c9f81200eb318252934a57a231ca8278710fcb8f5ab7ecbbd28a60074a86f7cb4420673bfb15c812a9545513e5aa317d3c8550da6e30d8a7ab6258fc44da820a83e8fff7cba1d412d250ab700842b29c2f6d85c047ec05b560099ed2122c877179389d528109ec5d558b40564c0c36f6b668dce120b0ce762ec1816df091e749cb89c078a1945bac9af5601725d931fff75bd8b23223bb540151b41362b8a876571c0aace4b03291bcedbfb4699ffeece424b6c319844351151a29b43caa9274ec4f291f9cc2155fa489b7da82ce245fe47b6978dcea995902ca5ad85dc2cf733503a61b1401f6de6ceaf63104e1f7b44306903757e6daa0a1339a5d533fe83154505e8587f0fc38470cd2d2a2d0971e3b61c296e260d67b58eac32c7751a8e535fa6b4e8de8329256859da5daf6063a5805b8299cd48101b1b143c4cec36ab2911dae5468f5e07f6a4969568fc2ea57b8c9920c3bb828a01372f75b2d7443128831f89d46cb49367a019b1e260f548f39d14f5190b7095391a35e2f98e532cfe4b1ed787860008f5f1b92e9e980292cfbf228473469ee5510be367badadb335c24eba59b1f5c804e93a9f7dabd62a03f7c0c40bedd64c5eadf4e5a6f777b132aae8969ef9f4bc3c6ad13843beaf74384938ad5ebba29f60ca14596a56e53d32652660ffab63b5137b77c469ae1048267a4c49bafba6e65c86f4d807ea54a7fff61bb1c25dfee462faeedd39b7ace01ed7a6ca361d974ed9002d7ecaee5f6b65e4b3c89714eec845ac33fa58e4c8175ec9b54a014ab7a960fd2e372e32f4bf2014ee78db32d2e5e7541f9612ead550986ca7aa398594f3f1b136b13d614c73ab8a9b010cc39b9120028b8c197ac92fac7834668770c4ac2b6fe22d01d4ffe6756628a8cb7e6d50cef41cd32e2075e3b2e320d9fdcdb3024643248c01eff8907bff0357eafd00664faa6ccb3043c382c61e896ed1c4a034a3cf9936ec98adce8288749f6f06d82768b5d3b365774f74645b991a91ec4b7f16546ab837f326dd786fa8b9dd67378833d8b6fd8e44cc45ab274e3f805e6da2a1c4ba88708944ebf390acf7588fd1847451b2f6afe7542ba0ac4f650bfa34812029c5998b3ebca56337f2c30e19d4c07481db2248c8d4f88009d2ceeeca02537d43a3c6dccd8476b72dec091480eec7fb4c52d9aaa8fd71974c8581e4a10ea19210955f7e21b1d55827c83a6a6f51cc01780d0bf9f30b5ad2a7307f17b408f1791606b389c6f04187258ae331c47834f1bd5cb862f7d4ffa57472961fbc1dc8f53312201dad4220d0a0696b7397e5d525b616ba838a49cee8950250d42016d182318fe3b135e14461e23fcf4c687ec46049c6dc9401c8c9335aafeae0abc609bdf7666ce1a41e781e464d627bcfd8c83f7440135c33b11d799bd2c10711a30758b7ccd8931eb4f8c59bc04c2dba16960eff9571849d76cbdde44e3fffbe2217f7f48e21391e25fc3bd10874e731b65d3c0e95d430f2c4257175579547fa338e75499e76aac42194c801a05948044d43f713b807d8837451453c900b28d4329a076a9d635b73f43cf71f661b99626c2acc5465f7ebc5eee506c05d19904371fd9f8874937935b3e7e86f75bdf62a692ba644fc50dbfd0bef6309b2d83adf85030850bd55b45d773b9b0103fc3bec055c424b16bb9a5339fa0be8c3de1bcee6d18915420bae3ebc1686d9a7376a35f90609bf9238ed244a91885e337086c0efb7c4de0c4badfbc94c1cac74c3aedce64103e2e6da744a18dbd51627d9a4d676fda110cd94d7e1e3d3b67b36eb44cde41ce0bc42f793737b042ec78264bdf06a0227eb58391dad42903a4506471072b7a719ba802b3dede46a8a2f90c7a10bcd2921bafb34796d4c4c115f7402ab00abe324e13c0beec57e8ce7cdf9093404b78a0b6c0681ab328933c5c1ba8cf20ef3e122f1f9f83722de9fa5de6b1d9d64bbb11cd7188490e5166811ab86ab7ec353cc8b4db255dce84cef5482334305d3626c767e60a3a3c73035486249cb723474e313fa73572a0425cd909a40813832ad589b2a1b49c4623717368c532944ad042929327e013e68ad7c3d4ae82b0c032a34968fcdab1ddc6d6dd50999254ee15427b2f7ef4df2dcd90e48b7bfc314c251992169f7cc43646c4c6878d9f2d93cbfe45a12a75be4444f778b1ef6c90c98047871426982779a1521c98add64f050cbe3890958ed2d53f4136c8accbc3d7e9fddb206df917b423bee71c2c239fd5eca4c09e9f3157527792b97f321034c9c30c52781850b88887d72d7667c061eeb7db07979b7aaee72019a4487e504eed229fe677792fc4783bc977a405d98912dba119682947af4e6fcdfd85a546438bfbc27fec0dee7b4a5a385ebf07547cd73552161b6a0dfea60d9d8983a88ef1a22d5ed08f64f96024c410bf32295fbf1a59f073a71655e033801acb2251f6db25cd85c9d2fd796a249907bc7270eda4454c31316fb3975339ee5d91f34375177e071725272f2425cb11560785be6b54f400ffd2af96804a06a468f70f596e39d89c7abeb7c0f2e8b2c99085c91238e163681acc1b9dc71b0cf1c93f9312ccb333099a917ef2fdbb6be6d877da3884cebee924317b042e30b785f2d218dc8934dc26fc88aea25eb650162f67b0ff56168d9c9350b4293719618ca229b07c03144e5e29756ce168f65d00348697773a44c2f503c72070ea5d96546b1cb8079c47b59a00c12297dcc3758e4981cc2d9135f4d83308caedc48a1713deb08beadc0e86c829cf32ba773630eec70c89690726099ca380d6c572fbb372a14c3c82f7e169e4dcb1523badf97492996b834acb9fd234166185c1a16e1a12fbb4eb4a1c75b28174c35f384970a5b593c4919564a533fea0d2539298e1ec74c771c19aecadd7301d9aecaec5ceef0ae6b3ecdfacd2886bd7fd2c1ff66f06b65b137c31c0c16a4f1f4b0c85cdfc20696a5bf1cd9149e5294bb72e0cc2539ff15f8f55b6c8a4d8e8e30a63bf791d6a41f68632d6b63a0db8385519e0b2e26f46a52513bdabcc78f5d6feb538ae4e592c1878c5e09dbff69ccbfe202a6bbd3dc918c266395c3f4d6b2dc09f89d4c67a6abcc0bfefec666c7d6dd502d0bb7b81702db0ec0f4159c286859e0a24889cf089c13f5d63c2661ae51ad23e31ea4a19c69162c3186de6e0c7dcc9e2cba4d57cece27176662bc320340a2dbe9d0761b7f19b652990cf44adf64ff2ac3d32ef7802d2159f1cfc021bf3e0aa46dd9823403f0f65249563627fd9f6ef5760a10a916506802d5c2c6ecf1731ec8fd16c18a7585b0d6f41c061ccc54977968b7ea923de7f5641eb5a330d33356911156a586c736d08b0329b4928dbd1877f530b33e1e7097c05f66c6e7592e12c6450e9d03cf56e272826c5d3d5f9bc1153e5bfa392f708a2bccf45e469c48c49c57671f1338f02f0c9804046d076df9c256656112f9bd101603d5fc3fa3a1c0022f2be34e4f22df6d79c13fac30fa167e4de7b7eda2b59b12adfccc99dad401b807a8dbd1b550f1c4911027d0780d7b40e71cbf48296fe4008e9038b4ea1887e0bdd7a27c463dbedda5690ace959dd839299f9c56f7cf41781372e24425aab3ddbd9d06ed4a4af96fdc948eb7d0be1e6d7afd124d82bfdaf61445195253c67fe22cd3e8edd5ef33434ca30ffc61d3dd13ae8ffb2a8e97c6a41ccbc0f253191e420bf3982f267d1f70f347bf4c2cf50462a7d303f071f34f07aeb3ab01968466af8c1697539569a413fca4652dbb5da2fcec288f6cfec7a6c8e8d95632c25d73c074d861749ef3e727d3062680108e22efcc2890631f92c40032f9375c9a66b749be65131d45eb5c1cfd1a21aebdf3d8b10605288ec4e04c1e7ecbd52671f0d1006be3195d3fbf02e8c0854dca80b716a6c1f665c26ec848fa535ee174dc386cdef40f18ab1910e4ae03184836fef8f65111e4d15ce0aa04a3ba05f24013ad4067b4ced54732f49ce7008b325aaf5b13280a9818dfc8366cc9620edecd1bfe957e41fb8c2a2e76b5d086a41818f0ded9e265274806da0fd51a46b1ad0e40c0ff88524c9e6c1f935c82f01e02a466065680b5d14b148ca2fc18edf4c5b5a40517ea2dab2bd77e2d8360e1ea8003f864aed89127c046906f6edd6a24a46a3611f101edfc2992ab3309eb2e851c715e7d1acaa3f7cc79dd1ff558bb67e2eb37bdc75af16d3e0156734aee0f1d85866f5508d42bab6d0f124544691f1047cce69501014d4fe388e944cc72e8f6897b88bd8f46af81747ead7506c3c02576a18973d53a66c03997750fd69e1cdbe0083b33ca7c11b87ea40f5b60ee895dc41b4bc68022048d9939a93564f1f196b4f63b324f7b3e5835b1fdf875208d4786e23f4cfc051edc4ee4c9e951d697105726083f1bb89b78916723bf046f50a5786198003215c79abb8f895425b4bca8b4bb63121614d108bf89788d9df7fdc2202cb4a82228edad0cfeb837611dd91936c663b4e5218689803a925fdd5d1df0929e088bd90edd0bc0ac492cc58fef28639a5e17083db853a593ddb4befc03d6184b19822fd0afeeb7c9251862760dfd84fbd8c7dfb072718898278ca5c64a3ecd72643640971eec6ba5a11b11b481bc9de74e1065621dc6d34f102f3c64af0221aa7371ad5a9e574e8557a2439f8710d2ccfa048ceb2b19f5c234f992e0029ab13d75764f30ed0ed7b509333857a0c08ffe301db350ccb28c1a3b5b39fa1203684b412cea27dec5ea81901f3493c100be0a0ea39e97dc737f27de91aefa42daa3062126b766c0a75416a9b8f18c4bd9ea726f2c8c4d70f97eeceffeda01a3a7599c25b322e810a12d585a20a402518c0542567d568e27ed6f4c95106e7bbcd7566947fb82051a1d00830230bd7866c3b6a3b39b0b6640a420b56cf3dfbfa7637c337ef8953ff783a0eb1bbab3179dfd4fdc12231e76c98145f8ab7638e6ccd68be8eb598dea1a6d15f4f1963bc3415140a7e1845da564068daa1435cdfade09b72ae532ef7d425019aba0816a5ed7c503bcda72f806663d8d0a84de4fb3e9afa61a45beb99e67546e41f90fb4551a0a663f61622d4fa986f2972026928589b73f3f806986db5084efa750c0c30932735675f4a083cb37f2fe4bb43155c9cfb49591a93b5978e4c9b75dc3722ebf0de462a54785b9b65843b5b3ea77f75563a5f08291b2a5e447461cef7c0bd8097785becbaea515c1aed86427c92dd8686a2595bbe77155b4b194773c01e9a6475d17f262b35b5c0520e140b2d60a25eb0c46146e8279da817431a0ecc6aa4963b62c8fae9d494ce4a20584278b98757b1230ac0d45a52cd90d4a1045e7817042dd7fc6cd1dcc922c91011e7cdb638bb0cf3432ee1a5dca7a6e5338017af3ee386e26f8b5c3f5df35af5a9ce400df75065c736880035cccd8740e3b34dfda2f153e70ea80c9a12827f6f92802726439ee23ca32212960a30cac17d31b50a765cc30c1e9a3ae95c41115a6dad9d164b7ef7b7fc61e2838c0bff50bbef4ca88263b814cbae1757f425fec41f16cfe92e6f8f2e12bae6841e7f52095b2b4b60d22d91734b686d95dc2f216339bf5381957be3c06abc2f1bdc25fabc4553c4a0b5df00860477cd7066d82adce46b64d268c9804174d812286a1ad94534f08f449a1cb7a039097e3764093e022af0d61bc7819379364fb94227201a2f2576efaab731650b8f3ea593f72fd4cec7195d3584f5c9922585dae0ff0fd6fc480cf45889251848c95901f71d1bf2e701f3eaeda90aee77d475c247fdaa49f24528b2a222e4a80bbdbe960436a1f4420b0774917755d0e8c6d199da27b760d2e19cee6c47b157ffb934c76b52762347cd0173aa748d7242cc2eff80778f990d343dd3a2bd28f9e8cedfff80e51751bb68b194b66c5e4d5700cdc54781af06bf14f25a6a7e47cf1c42337f397aecfecc586201c8f9f01ee529e06ee3909ae9b040fe41557b24840f51d19adca84e95931c8b20aa537ada1980116562ea7f4b40d72398c93e540ee5d187018214444764e820c9c5c0ce5673e5071b263c963b0480a2d2652559e429dc810f272d8982e745f1c54aaa311b28fa3f676381e6c11db00871a3f498dcffc8b829b0bd06f15a03f047ed90103ef30edb90e851eca672fe338ac2b513a88586ba4b708a12da1132836e916a85370299d62397717e4f84882e5324ab33c52abb999ff31281733d0063feb38fbd31b6991d50897c4349b723b9494243e01c69aca7f323a4efacad0fd5407fa682b17ddeaee73f2f010c63c582571a2ca33111cb249548ad6cab3b2a21dab69f9f860f69dcd954b9224d94c980199f706b6c09a2da476843b79830c2dbd3ffedd05ac323695daa8e35af652e698903cd5f15468beec7867150f63c44e83a6595ebb45029bee364b00eb38a926e402b602a89d200cb93bd11b5992b90fdd2cb30a8f73fe0c839a8f0f4eb0a7edb075440bf79c6ea579d96cd491ddb5a31fb707cec1fee7b4e59adf8650ee615eb7602188cb189ca2605faeee594af7c069e99419b96c7909eacdbe6917b8b64b6e38d71b9cb6c43d153b5cd900a4744496ca6a156029a345794db44fd2ee7d7ab084d7de17f002a71eabb2d297ba9c179f4178831fd0b6a4fb4c1d448608974c389bac7d5dff69a8089fc68b3bb9daee14738ceee55425795e758d6bbd461f15315ee05278fe22509773f8739d6cfab32b0bcf3a6422973ec2b5fcd61e2213469c4fdcabbaf4c60ae92718368df39b4db2c65b692fbd891207a69ea33a99ef2a69a04e1dffac2f04fbec66aa5f2c100358b1a1604d90589ca973b3233bdb19cf492dd150236f6162eac056b056ae628b965d8db91aa343396ffae39fdc8cc72f2ab5a588e99798b8e16260b1e0095790174123ac79759bdd035abb1e28cbb235de17fd3101393bab876aeb436d79e41c29a4fda47289ffa24c60fdb6da8b1727bc7b1ed941eca38f6d1b23a548b62a35baa693a528f8074a7c903c890c458ef3a1c1a58c25a0cf8a76c7b5e78b613fbd9cac24bb7f6e32513f611c7fa60e5e042cd4537fe45ea18f741127883ce9c0f805d86655e8d354f0a9f79561e0f80e261403558d371dfaafbd7ac5e6774b9274abac68f0ce4fde1ddab9406511679141b5eea020093aa7023d65aa8588b2ffc035ca5dc38039f75026de894a0106d5744f48d265720b9065e0a758f2fe7b528f6b350af1043b41c9b6a824d84a7b5e6809d093fc8093e920ce6381e901973d24d658cdf0b2d30616e5ba0a6892a0a11919ed6be8244c76c65501ab5d4ce3a8a79b0de3a991bb7fbf5f2bb444bae84a493b902931879b182e12dbf7c0da04287877aa4f878ee43c8125f15954292b8eccf3dc7abd66d0ea6dbc684442b9551c0642c090b89192f7045b329f91fb1b347d0f8d93337ad4339d67093e652441ab92589f81062bf7e0a5aeea70895b7f299628427518f80c78b9879f3fc866e79e37ed5a297d5f115a293d7aadd1fd4f057f52ac102456aad3a86b3ec294ce6b1f54e7ce7c059fe7f0d16a005ec66157936a1db00323616eb2a1767fa2bd9d378639d0ad45ec5a0cebc9835bb6aae412912da2e4a997f2b689785af7bea73aa217b62dfe664042302a1fbcc9023527cead1b78614f419b1392f7371b9cdea014a67dc6541bca4f379ba850c38d9c1d89a67b865f998c29362a052bf4928f917e4c01ac7695c02f2b38fd39e741f6656477760bb4bc61b3e803a231011c426206680fdf28ef6fa1f6ccfd260de1ebdf515c64d82081e8242f3d1b7135fa8d3230e8153d9cb7a0799f0005a6fbec28f503b4d087b8794f5c32d39b378736e6720e8a4cef4ba16940fe80e75326d43b345e8f13265ab1e6530e0530a726cbe30f1b721e7537e72296d594caf5271bcc98f084111f3b49680a0bc80074726ff8cea36ca15278d8157e072174422a06cb78710fa72f9139d75b2c39e71b3fb0dec835f91d30357f98c201eaf34f2f9e623a0bbacd195d5b7138d909de3250e11d945bab45e37c396e4c4877cce81a1470cb21ab200d7ae655144582c2051cd180032bd8d69bedddd60eb7b6703874a3e4ce8675645820153041c57811d4b49024eca3f84fc7e63d11ddd9e10a5023b0d99139981daeabfcdb3a6da028c14357cc60e69699e3cc4546b7d274cfcf412366b6cc4721c6ed91805fdbe53442ef887ee4808d7601349df42a618221995df9ce55880cffaf50d47634c631902221cf9773f61080d480415c9e5421154f439b262688b9a8694d856fe26530c446bc5896e4e3c7a21e6690a141b0171f898d1ca1a82db84f028cd343c2d3a2dac6aef92ef9de93e4949aa4264b6196765370af0e2c288c07df84bc3729c0137a5d7f544c2271522585c88a094ab8fbe32947d4f7c3244bc2c5731049934f3223fa51d6d6c5b27a422292c833efd9cc68bb5d3fdaca317e9eb158665d9c8369d8732652e530a28f19e748567788292d19ead0c2e220e50203400413463b230a61bdb264224dfdde28f62beecc96e3a8d4a47e63a16862ad80d3f6f02ab62260027bb61f083e69293cb048c5cc9a2eaba72fab560e290842a73ecdc333fa28c1d997bcab6266a47df08997ed8de17f1bd80ca59573db63bfc05dd84202c7949340e13cd23c2ea19a2f7cd291d5c06f633ab9925e45a46b758a36e1d2fa68527967499a2b7c91f9832c6ed0085610e8baa7413f744d7c400c086286f479e745daff03f738226424fd60d61d260ef57ce9af90d9d379d5ad499bfd7572a9aca104df3a19193fe0549e476847dcbc7da7d9ea4075648a69649d4086bfe2779b38f44d9d5ba7ad5b1ae95e69a62d79144ea8d9be07a5e2b374096b9159988707222e63902d9b58d42c1805073c3aa7ce551e2bd7dc50d559b85886494decad104c56b2bc5550b02a92bc7c057f6f8ff0192d2d2dbb386758bab22f2863260d78a641ddfbbc62cb04898bbc62e244d1f3028cff34b7d52f5824e69d857702cc09e7e97b1b9f15a2a395b86374b5c8c92a2c6250b8f9bc394d98a310459fe898a8175b8bb9575b7983aa63f37b2b7d55822e191fbc495a743bab411a46a735b91ce163d3abf3eacf65132baf5905b8d7825e38e6dd112dcb3458608e3faefd4479566d3916617a62cb7ce7e5f8ef522f4264d44e91a333acb574f06e6c7d4e3e2be4241f28f28a9659374dd041c661d77217c4349bf9dfb6c5068a17c51db16f5b1407ac6e406d36d09b7e33d1a7134ac75105c9c404346006da32cf21441789462dd69dd390b5c4931489cfa0f77b439c72a00a685f83b5f08ace2bcd82b426fc659735f67f4709e74eda7ade558bcbc1adc3f252f7dc0e6437b93f813995350c8f2c93b169756825c8cdf416e6cd341bd4c711bc8186ddc4e55630f04c3f365e4708607e6b615600e96b2fe0e61c1c51df2121dfd5ccdbec359e344de90d9d51fe5e13b8273ac5c2c4ab6a1a34d587141154ebe4e54cfb20e64d7f29229a8b2f21d7c48f3bf55aeae7f96a3984df20d5e25cbd7a045e18c7608920b10c1237ed147a7be297ce6ed869c050709d1065050713a68bca86883f71275f66079a78c6863aedef9deb0bfd17b43235e14437a255aba60288ab63b3805a44e812181890734b3c606844075238b5afdecd20101281a8a4b42317cb782901e7b26bcad1d7bb0bf93fd2de52c18555ace66053175bd19aacf81c422092302b837b084698f4387c213b0f2cdadbeaa9b91733b917561cdc750c8cd18c548fba55078eac53e12ba426bf0b7c173ac379472082c3df4f9a695ac683c943487c951936da05c30bc92dd0f4e5062d40cabd3778da5d26394236347d5ea4e3b6d74138eca0ca160a1b8378eb9fbda3a698c391107d4a8024c756bc15c821bd8ab0a895f0d18019db60f96c4310d62daef2c40ad47b62727e25d4149717a3d077605d5c1aa09395418e7ca512efbd8d37518b81c5e17f77a607b487aa0233370d16402cfe47f1f617d0ca71608b836c15544c992d5eee3841877242d1005069046d7055980fd4d949f58b200e0be8ab0ee8750c61f7a1a6707b12f49ecb233678ad562fdfeea7376b870e88094be2daf3ed2bd1db9bd6d08bfd8c55e587d303e62e68d8bf5702000e88bedee846b0dada7a66c5a037766a9ab5b5224da740a951c5c522ea82a8c9c64786b9c07c0f342e228942fdbfd4176c6598f2da1e20ad73f11f8af7016593243098a14f5bdc0276ec65f306286b7fd8078f960bfeb33db6f3ed95eec922bcd8f65caefd7ed509ab84e27b70714dc0a837672a35e79f110ccd2e22aaa6f0d18c506875134c92502ea0e3e4298d0b37c2307613b56f7579f1e19168fd25a637d288510f351c3ceaa05171f404b1279619b2b626e051172f303b6f123395d67f9dc6fe66314ab1892fe5c80437a8b04dac440ed247a8415d2aab4381bc458a482877f73b41bfdd1270d6f5605641a49ac40936269bf8d8a580d4de6230456ff1e67df66404bbb440470c787385c4ee3ab6bda230299160104017944c5adcd73fad0701ca01fb97a0c2d4b82ace9920ce1fc63c159856255e02f98f1b28589359053056381f8591ca04f011107ba25b863f65b92d34da6e5a1b4d8eb207b513efd461558b19d7c88a0a77066cdbb3e050d9bd6ee4d2b9d52ba55549fb3d2fdee92108596033413a7e648e986c3d9b5a289d3740cbe875090a1ba765ca3b024f3be5ac70cf9375c2210a908cac4b0b24dc3fa1fb76b0438bd070608b459403eaf02de375213c57e89b375ebca39e35b59b828e65a2680ad704f968836c54d2ea60f00f9fec1f097700a45f1b4532de0a8645a093edbd57f50251811f42176a7f1fb5457b52b01bc21ae3f4ea39a1ede9c7155d577b7a3f9bf2f7aa3f3e96c378fed78224f82ef98db36752c4939e524aa3eddb7cffe2bee1adcfd5db144adc06ab3f2b054ce4f87862274074a2d8149ea8602e90007f1ee56ec359629a3f7faa49adf5181df050256c8e0ccb93cd9a6cde18edde0d83de96012e9fdd651e0f699d7a159f450cb4a6ef00cd1500110847ab9c3a0bad55f30bb212e28866186e88a71f9e79ec8612de750a3033d99e3993a177a678680c385c79109a9ca98d5cc4c7206d234b4214a4f193c7385b887da3f66e105c62f429c9194f9be75e48b2e8e17d3acf847b0aa4bee22def200a3c558c18659cb60bcdb29b190a89a34254183e52e77b865117c0860deb9103841d43bc5c00e9a53ae8186cb337744752f5e019a45b446f8e8e6c7ab00ef05948f676f1d2846766534892e7473c059d3f18b243dcdb3a5d280135f3831fad9cd69b090443fe7a39e7018f03d1da1f0bf57a02c8631d72ea1d5e8439f1de1a562903d40976a51bb4a63bf4a68dec2f3ac53a155c99aa4638ec692787003129e7bc7c10d867897b3b37c928f3d722330c5e51e7485ba73e1849ee2e48a853d248121be6848a4784208a48a27466a531226165aca4505f94f70f1ad3fbbf5714890451ca2998a2e40c8e2dc7a8798af2bf787a109175da9a02271a119128ec70ecc9ad0ea790e4de4be84ff64e419183bb1b66e31ea20307115acad93473920a22ee95038d83f55148a42adb2f498d4c66bf54be8ea8cc4a5330737806a57ddef2914a24176f01321e988470d8d34cdeafb3c7376045f7aeb66e3484ddc342113cc866a8966d4f339af2a8b25358f05625790fbe130ec19526a262de232aec68155b446dfb4238e723a6f0cc1fff3810bf68aa4b033b068d902387479b49f1a27276e8e866ebbb08d9a5fca2ae7fd94fdd66d1d443f5f98986153d2dbd3b78e1c8d797daa84e3d70192c585ec3069f59ec638322f94ae302621e4243be50fb965289b1bd052b140c0319304a1826fe2d4c533c0697507af35bcfa8772d939d10f4e35899c8b61846265ba23174185c6ae81828e95c3bfbb219497225a1ab25fdd62c530cd10b0cebdabd5f5a8c00063243d401b17a00224c347f404bde756a83e6e0ce1810777ce40bd0469663ffe0ff31e3e5d807bd4fd77c279fedd9b89d1e593ded161891254b8c5e4c3149708b711f51723c0c7c0e79bae8cc6b6341b55797c404c0797cfa8f1f05ea504783ea0972abe39f45f45f8df0acc73001154f2b65917d4adce9bf021b3f4930c787c57355ce0de628043fa0bf171ff02c7a0a823db91cb38dc8bdae6fe2fc12057f3196a90c13437fedf2c1a08b5fe1477c176b6e9520f72d0fe3e4d4fbe4cf2b0e316ed9c6cbd345d8f007af27617839f843e5d4b5360d3e8b4258b15cb4ad12acbf3f644d60438081d914e171bfa334014f71e2a50d82f7f71035e6719741c13b5287033adf63ce262c826a345e0472a686cc841c43e40ad09eb522faa686c9c24cdb6da1a73f7a7e8e355cf0c31c33477131c0cc8c60222a0397ef401ca4fbe5b285aacfb7ff516657b655377d9bf4b2d8b694c11a0284e8ffc6b66a379fbe4269619a50c36a62133676a66fdef4cdb30886d06fcd3322c5ac8a15f8d85a4461f1b3bee4e2eb2b864d9674f6a1f93264bcf7452dff26f2232cede3d012ab43396bbeec4d51c6747497d647ec99349ae14fd282dc9b67b2fe57baea2c50156bff1ab11712911e99a1a8790374d4f2791e0152356c52fa8f68aaad2179b806ae771b1a495469a95a74b8b7a723cdef03971cbd0e947de8aea1554e17e6700d03e81ad0780d3cb945cc63d4cbfdab145ce062b762dbe15f08191a804b4f85f4d55183d48b007dd6dd8b536cbceb3b73cec55136d0598ecefa19c1e1af65841b4a171107e8e11aa10df5a23a59622874d8d0008796a03c9722df3ca5b8acb421468031453239854bc2066bd4400232d47d3f3970bcdf81734f9234416e030459f3cd31f4bdd9766687c0ba727307d8e22ff3af6a504c440dd0018e83180a4c7a39aa03d55b104ebf4ed94cfb310cf8c3cbe90f89b7b171a20eb4837fe52e74c0d53f703939d0bbde39d58d12a48da49b77b7f86096cc6f122f1ca284c45f537c4877491ca6a0f9565033a8ef067264e59136bea4327205dcbb82e6f1d27344849ecbd92fdd9635424086d7a5c7ff868b2e0682e393f7d6ccc136cc008fc6b93fd7132c0f39ff30e78be3c4e1166075cbc1a91d00b69183728d0f00ea35ec4e7f8ae3daf352eef300b0fcb8ec432fd3c250401ce0b14ec36aec4347599bc657cae1f4f75b3075c84513f2bfbfe7b55f6739d8d625980d339631ea3fc015e12466f675913184d3c1ccf5b4d43ff88aaf73ab6240d3ff6f67423e7e5274eda312df50cd59c3f34b2ce1dfbcdd13554afc84b8a92970d2560875c8a2c7d815e055745f958db82be0ad95bc64787ff987bd0c4157907e5ea1fe039b834f4281730b8626ab2bbca3f00140ca7fce2d82d0dfe26a88b9451c6a4cd1a687c1cf6c775cc78aec1316e8489b0e6db4071b92a45e13a99b6cfa2c9f772a6823765f44a989f287eb1b9cad1f4183c00b1385f7ddd906e6b59267e819c0217e1d962354a70c519f7c2f49a35329fda1e6300024f31695a87918c69dcec413b8eae0ebdcccfaecf2deddd4f933a0b5c6f13dc6883fef215438f5b151b0e82bb250dd5854a20cdb6f73c13bc8064b46fe4e9c32de992dfdf7e9daa49ba5da4cda95135832b75d0e02a4b73de07bcc86e011aa407e11f2dbb82e7b0a017e98a83ddd45c8d00bd4f44d0ae22a5ec6f8db2d33749b487b6710fe14e095b005a9ff39957b69d8ce99c2b2a49cdd76b437e280160a2ded7e6080c143eb84ef6a95787363ae6e37400d57d2dc5aa8faec9aac1713b242db62c1037a0d525902ed873a6038a0f6381fb305bbcb5be6af9364507e115a2428d119720a13e4fa5df98526e7aff00de3dc1a92c6dd90b6c4a211410f7197b0a29b2b815433912ba33a37b206102831fd1a6d7893ee97e084e61193bedace83a4ed34d608ba8c0141d3a2526f0686a3871b13d3402138e2add203aacc89f3d520d9393e224d142cd45c1d983937409190ed5dafdb22934455a1f22c16ba57c4690a9885faedf39de1d140225f43baecf5752a05fabedafb937f11b2eb544949925ca33142ec7792f5b09c1570196392eea0c449c1099059e7fd3979feeced783b4bc1e8ad745767b0f9b312733e0fe239f7de2b78d060ce6ca5db3d1027b5d64dda2e070ab2a47b9a9fd73e0e7a049846d80d3fb1e772f57e88034771d38524906937a88dae47b68ef9f01f388a4e2c09a2c6bc4c99d6e737e74b382d5ef588e167f94b58c45596ba0438f98a807d8d39be17b86380f1d062380faedd5a624731f91e87a3f51f2aa17435a3bf49c8d904ce79e8f2017ebcf21d1de479fd83631cbeec57c9c3586097fada92cbb0beddcacc13cc33d42d6a451053cdcc6bb70280b56130bc522d7f9f3137de266c91708641da84287780aec62ec15221635428e4690bb36de8c40629c0d9888bcfc3b0d022365692f958322775fb7bd444c6d656e2ff4479910f246b3ae0302416e8c2b72479172a0013bf734858014a12cf4834a340b80450f2f441763310a53edcb0664633c4dffe78716332cc3aad05e0023a088c32ea6191908b9b0d228471cbca33d8057b3048d5737fd1f56cfde77ac713e70c38200dda81cebd02d29b0524360f401bc0099ddea7e31c0630f449b9f476cb8e9312042a6295a1605da6539865e551d60f73a1936029c42b4b2fb4fcd2e0aae636fbf84554482814e072d5fb06c9ad041aada3e51e52249b38761c22e8e39e642b8e5430e19d9ac4868a3f65e34eb9d2b21c55c045a7244c40c5dd75bb1db459654ab3db910e5f764efebb577e4f2f37bd66b567b3fe601fbf3b25ca6a7e759be5346e0a218b4c6b5c31e31eb4efacbb5a0810bdd41860fa00803439a830e8956820af80ba71e346c78ec03db03a9c69146013444c64d76142b72de49f328bc30357c829796b15d33f1251e14a10a00f416dcce6fb171286ecb093ad6421d548b71c4a2ca5abce2f51a1ed74ed0d4a888750736c28c6aa21adcff9b2398711383a225bf88c960522afa45f2e0581dfee7642670f12d2250fd80291c3899aa052c0294af82a742a82b2bc60a9e2eed6cf2c998b41487e36f6be74476510d33a2e875055b20c86cf10c48729054da3c1236a70c65b1556269fe1431ea9a7d390f596c71044f4decf1ad7647a74bbc5e78784461684a02be1bcc10b55c9d47f396b3fbd4cf644c24d62ca61337591b7811db209810349819b68dffe605a90c81b999c0c7337022d70a88138e0ac549b1c3178499452c29d2bd8662eed0f4afc497cf5dda3a7c4f30e076fad0dfb42381fc7fd4a59aeafb3414bbe23e05ef35689b3f73da7e63d0274e7c362ddee81373cd2c73cb147cbb25aa54b1572a3603ac0691c4dbe7735124906782690add6321f7837fe321461b6b9cd7475d60ec3a8b1d710dd39db0f94533112659813a3b41a84b9829b9c21255878eaa29602652e0a4e11f650d03abc08da98379f093bb3830c4ab06df69bd00ae8ee76c72bf28146848d96508008576b10a29694166f26580aaeb39867cff1f5e6eec2efc714f05793aebfff40ebf8b0672c7b7aed28792dabe4581799ee1b9a271990962f052e37baa16df0a2abc37759282957ba4e3d012922541db8c3048fae818e7c41096da27b32d6ab5e939d5548ac791671af1ca5a1cc1a1016243f91d39544e2c59ea97c62ec3fb3c0f064e6204d6e394c9e563fa05411e2d6e739b94a470051dd22acc3ad054398eb9da2675b2358fb80fe5815a6f755b284628d935306d0b81a9e17b49b06189c0a96ed6e609c67590d83a55bd4d4da88fe6a835b811284e2494225aa36a329869189e060b2521753fd744cdbd792e18a9ab9858e17980876ba75baf94af8c53266821c00a7522bbcf0bf20ddb9d852d131a14d3d52a906f839b23f92f875ad298261ea18de2067db76bf3841c1021242141039811d4e33403133a2df5eb52c5dcbd608703f52107fae5140fdf658c05932347dd2ce1efe4b74af4d018e5f66ee4cf5fe4fc0874e5be2f5aa273e4457ecb547458df356d8d77b9b864b054f3323c2937789a758b4775940c87b4c146ecccd79393f0a616b7f63c8492f6f9ecbe4c66d43842a31666b10f74734e71e8b28d5ade03c02dc2f575bd562002199c2e3485d780ecf0d458abeb24d88d004b4c46b0dd77ed5a34747041d9c045b2260a7f710fe17e7124b8b30cb67e0921c36289474cce15a633883aecc6b770f9a7133caf08025211fa655d52a5b384e846e27f4434d52481d0a89b0656d456e681a018ae40ee5fd0ef50c520a68c9db2d37ba5b9aec66c55c080bf58c633863cf3f8714c82d5c25078f1eb4c12adcba7fbc50e9d3f4a2aff2cb53a820ab050e9d63b67783447b24a69330bd7dac487a330cbf617efe64c178ff84f388292078c8331fb3ded30d43042686bce03fbb5c99d73ef9feec66fda3a81239b04b64243021e7d091a6db1741f0d77202282573b7ccc726fd6c5b4683a31b889d15a6e428b119134fba8afb0701bfed124a90b340ee7ba7a3e640ef90c81e0994131afc627cf591de8ebf483ae060b0690264df494793054c0b17ff8ba59a46c1ea21cc62440ddd1d505c00146743ca0e527abaf3060b9c441bf0d0bd16980a5bbae4b17db0910ed52f2999fa59d5598022f3b9bfc9944fff932fcb2cec4cd9460ee6e6748ec422d08635b1662b83a27725faed47c7b04b74ea67b455075a14dad2d5719c7dddbec8941b88aa846f32d382df4f578dd4e9fb41cdefe220e787a1ec19f238bfc7702470c56d63e30247016930035cc4984a501c94481e60cfb273a83ab9534033843203d063fca7904530d72c29148755912a39a683478bcd0151060963f5c2428ab201a03df98db8a55483faa4dc97ec89107aeb53037e53f2763195c1ea31f9e3f211730ebb1709f322f6c7142c5c0924608340dbfd45a977bb8239844a889f246ee269107d79fe40bc734ad0f2a39cf103ac5d979b511f6a1b850afbbe2318e6b667fa3d39a1b5332a1556f2cad3aaefa3039736cd883ca36988d1de17894410411acb44219a1cf63b1c357a1187162ef53102740d6d6843eea24069691b6a3ae091e105c1fd82602f4b2b19dd3bf4f7aefbe5b6fcd4c50756f76aa94868690c8751cd8d313107f79e3aeb45db2c49b5f279c2332c43a743c64c45065d2231921ab47c5d8d07c74bace70667fb9653a8b180f9d9da2d0b76fe49e60225a74aa07b368ff9e254c1c9d461c51564dc44818e192e4e06b015e21cbaca9267f6603d1622b8c1fdd941677873461bb1667250a9b666165e87596e00877b4eb82f64966a0a2dc09227b8d712ed993dd817fa67381c7048ffef4e6dec964650470a09af0c693104936f66b9c6c6c57edf46c0b71a82b516209c23af5ce5ff191b0da94383783de2c50b315ebf058adf6e42c169826ad72788ed73be709ac99fff5632e6906389ca219272f10c9ef4c8c034a35b82d0bffbecd5867c0a283ce992ced5f0c217ac4b7070bec2c09c45caa6782a485109cac09a3556f3968838941fb23ec318d78be8ed4e643f7f068b67dcb558e2da3a57a36e96bd0406545c576288c167c5eb9a359ebe094a380a5de7b4850874d051ab371ac85e7cd0c3b46698595b783402c87d176e1fc7b8c6d8b908150f95ffe976250e92bd05857c06a0b3b4b6a48d872450e0a1c8b5d8d71378b463d7294bce0a836b298ac410e496888486733a29ed06c57d483137ad96c992164ec0b835e9f348dba61e4ebaaa8d44cf0e6b1ae093e0ec5695b94003468144546133bc356c591c7fe6eef48d5c0b61f83892a4127d851f60b5a9f6618be4c4be4c4c882fa4e9e225e2411dea6d38007bfaaa6aad23e2d20beef1434d528e8b32eb3f61d3983a5a18a2492448a7bce4e01ed88f4615bb2c7c23089fa8ec806b252b16a7ba80ff0038a320be16444e1697fa0c3f187ccffb4ee7e3c10d7c6868b35642ec5b12108923a03432bb8d1a39bd65ad99402341219c1782f595b018990e5ac46d87e29c7bca286f002b19b0f0845e7f2d41a7eab83f233ed6ed98a74ddba55e700cc53ab46f18f17bb93425041a2ed8b6bfaa467fe2ccfacd93d9ff634ab9c1620b81cfc9bc04dae204170b16d128b0fe778f15249b37f94814846be2176435e37d03d2b97423ef20e56b7783dd02dc31f84dec2752e0339e7a392b34e2a2636d5a92f8b10fe3f53e0ae8f8e8235ce73a9b52fe0703c6bea30a064830511fc2658dfe45e295d2f6087d1aa01a1c26031b84157042de8248b66b8988506e6eaa3b26471848b561d60a43bf87a0048263f31470813677831c09751e8b5a89d0ca679baea44171a3e0a21123904d998fa46cfd145848f44683e4885fc7672fb042c42c9b649eaa8bd2df34dc5ed231d16abe11936fd3b29d0c61cc51c8c2cc56a1fccba2ccdcb44c9f405215b9b1121e02fef2108b0833b5a463f3ab2b0616ff934312e51206d8e9b205265b11514ea36b2d57c9137eb539c7134bd27319cecf9727662ada18af056bcf1f6653e465e53bce7ce328468323404e36e71bea81c9c42dacb795c63d41c3f69233672909f2ca1c95f1b59288951b91874a12b63e2aabf95de87893c8a56561f2775c78034aa05a875a20d2324e07a91b1a16d63ed926ac4fc13b87f3d8b1ac2b8e88f58ca60bde7b8019ddcd84ed8a65dbc54c1ec0e4ba333b3c00882acc3d80f381e6ce3cb9d47f7f9557b3959acba248ec433fe8626fd8480891dd6dcbbda54c29c935086d0852082fef16d616bf1a64067356802bee2590b7ea16d286bf67645c7e2d40de92afd9655a35aa4696d262ac5187fc6d5cac6fa490134a2ebfd7d337a7cb51bac54d8ebc237d23856cf043f3bd604c137202883b7a40b22034eeb8e335dacbb5113a331f4f7ef40d730d2e3f3331721466e3f2f338ad5cfe9122f1e99b91cb72f9b90a71f9f9ab1012a79f6920af48a22c72cb8e3b4aa2d8112547cceec84d4a45a56a54892ad0e5e7b28c5c8dab5d7e8a643bda90288dfe4c2b7d33d990387507bbaa5d7e22afa75b24b90dd3ee1b76641a1312a79b5b6d58ee06aa5a6c51550389434920c1b824f0255474b8e25e86c0815ff34b489c8d04d38df8eb159e00f71bec7a4882edfd55e02e778624d8c2950a7c2596bb3df75b480289e572cfbd875824964b4189e54e3087d8a2b862ed088add9ef5cdca6582bd52a8892ceee8babcca39a1c3d156a309f8f58a62b878524ad9fdccccccddcfb265bb4fba9d4e6ad8b17e9d34d58825130f29a52783902d594ac9cfdd31049743c949296fb03c4a2f375837a92089acdced9b66c8a2aea462031877d5fd659aea9aa06a0516fceaac1eb9123ce79c9973da1759aeab024b3f037a76453b704553e08ac680a79c5681f26553f375e6ecc161ce9c73ce39a7cf39e79c734ba19e522ae37da7266aa27ea652a9ef4f79def3c87c69bac012b8eaf09b5b71dc36b7c4eee4ee9c386823e81bf9041a5cee7cd9375c66357c3f5b82e0b6964fa4c1e5cedf497de977525f0a799cf4a7c215ed70a7be17f2a0be56273af665c0214e86c478ef3d0c380415ae5648bdf7ab0e7752e18aa2bef4ab1dd4977e45c318d88210905f40f6f1e69b4e75d28ebaeb5a05026a260553f24000635e057e3f03ca3c0ac4f12998926751dcf4058a7537ec9cd47d73c959ea643ee66338fe2bcd3c9117ea63defb9d182fa6d6eb5faa5592414fe0215b18f00285972736e6bf975207f7a587f98ae353cfd3bd0c8e97a6958ee7cdfc6cb1799c6ce1cef733210f8e2f1c02f3a9e7910963c252ed4a30a91015c662a7f90308c358b6f54b0d4c6821490808443ab410927024a99266928078161402587a10c0ef55a0e97180303ff3727a19fb31a81040f9c231a3e32da61119e91809c971b9529094a48214084992f14c5ffa18cc7f329e1793fa30e4711213f2c0fc57fa4a257e8162a743b19433ad74bcaf807c79a1573f2e954c5ffa60aa29880a7ce957938ec779a0101f5ca853d47d4ea760ba3a5287cba2d27bcfc2f7312f4d3a32323120ffb450df86ae7456fd9ecc7bbf23f35e873b38de7b2fe4b16ec33d8e27f2aade92796e48cc7f5ff2bed4ad569899a9cfe3fdcc8ecce30879625e26946ec385de977ec72b79315f58823181a93016437decf431d37b1fb36ffa7e409eb9cd090c1292c4f5498b276b46147399b19e36eaa2a67550cf6cd85b185aeb79263046be4a2dfbc96926f2561bf53891b7e4aba87b66d8304e72c38edd1333fdcbe774f778eb855fba6e494b9aab48f7744f1be92e32c3eee99e067ca37d37b9b0eacf0b42df051aaedafbf926bcbf4ba7851174ab04f3fe42b6dc1c6f79ff305781a78f953ea6c7bd439d2f5cf57f4fe4e57dbfd3fd17f238e9e739853121fb84dcd33de16b03ad14dba63f05768fb762a44db7cb974ef739ed41dee3edce4546d74b44201fb94d0c6424b7298289eed64f2d283b1f6f79a6f7e7206f9ddef98767fc6299cf0f5053fba77f79d3f75ee93b6f59fff4cc6dfc4f60d3dcc6df8244d7768ff8ddf3a8d99523c0f011d93a45449fd036cddf32386050a9179fed65c0d3c75414587a3bb91477733e836c1fab7f9adc73f6f4727a1bf2d43f3d4ff7db665f38ce9eb002f7dbef70bf853c4e68b8b3fd29e47909b7d78999fef3bed47de57edb6ae9e3927a7c7c187931f038caf5e728fcc2317301a964e4918c7d7eb8c701762f03963e06b40f9342995e6875798b81663417edc791f81e383afa80077e1c49af56e5a8dacf7dcc7e877a79bdc25414f7f5fb6bf730218ffdee794acf3d9117f7d6f35ad5c3fd8e2e1797d03dd761de82a97f8162fb8eaed50adc97de47a7631bfc5191581f3a3ab1faa8efc021a8ef9e038738d97e488581a996fb9d8a0a4370354be0d782901e9f9aece787ad474cfbc4d8a9520322c45b323f8218c63d2c631f1ce0cbcb80dcc7ace609dcbeafa0e9a56927a771c4c06c3fbcc54134a41f48407ce8d43848ade642102035766145bf02f205f35d107e79fb31ee51dfe18a765db8ea4f39d139bd3f6b7bff9cfa5c77bff3f2dd77873bdca7421e5428431e1b4ab7912fee14c662b5fb975088dbb88d3310c3d834db80d4b8c8bb06e06706566ab1e3c98876995cfe0030e99be63fcdc08eaa95bc4994716578a2f5cdaabf2fff89e8f4c43d9d80aefcbab063bd7c320ac08f8db518843d72fded111f3fbc8563e41f2bb27e74f10c581f07c87d0c587a19b07b18f0fb14e8a140d34bd30c285f3840f98a01e54b06942f18f9922f2fc7fd81ae7f87b6c7c260aa13dae6058a9dd78509b3fc1ee7861d5f472571fa312075f8980aa17447697433c036fe63c9e5c280d4e1c31a61f943b05558aed55a954a14592c972bc86a0b32bfe7f7f74d900ac8572c56fa3ac3d516eac0d8f7a759cd473d91979fc2d50c572bd4b7bf536dc8e364863bdca3429eeeb91f45d795790e8c7918d0fe0944fd2908eb01d992f287c4e1ae3ff7780bf5fe5e5d92617f64d96a85fa32bf535f26e471b23d8ffdfa5bb8c37d4cc8837aee731ac6047c69a803f35e0ae49769a5635fde1d144c188bbd7cecfb58e9ebc74c1ff33ed6fd6b4ef0071036812c731b97492fece3a34a4711996d4ae2ad58ca977fa54c82008d4e0d960e026a11775a5822ab0052eaa8c18eb20889d66812ac481609f1e136feb6da9a0e163bb2ae8b5d98d79fe686a9ac4559ade2014f7dfa869f3269f714e45df9cff79f482d3291953bafcc232c3ca69116769c46957b266df8895c66182079cf3c1c1b080805f636cb126af71df6cf4c5293b0815a6417ea078101764bbf712ebc60bd69e2d1d42401a9838491af538fb9fd4367578e008347a4d40f4883df7b9a163becaebbf3baeebabbaf95ab5ce56a0df9082b89f2848b242e6aa3aabb9fee02080aa1a4db85236b54cd80bac07247eee9e2cae9093b72cf898a75e1057bbd882124095c8395425b44b9a30335b946461e85db82cb1d1d480a6db1c5833c8a941e73b74da1f39e66c87aef4d530a7e3defa5d451df0b5544d8fe12f5265f09a4e91b2e9cd9917b6487ea71e534c5726fbdb07f2b7f274d29f0ed7cc80db52341fb5c1174bfbdd775e13804e976df2fd781a40efe93146b42ea59c15fa6b005efc03a728f3f07bec0a17ae440d6a2bc32bb2a1f16ba0274995fecfc5ef7d2e4d5fa1e33b9df7fe1d8cf976f7d14bafb1ccf690e247247eaa16c51ba112afe24bf0406ccf29903b5c8a3039d68f6eb983646186104228608823109599edddd2d6bb195c05aac5272b5c2bc043b0439ae613aec58d8b006f9568eab5eadd5e33cfa32f41892c401254e5fb79bfdb69eb7a0fb00683f680224857ea0e54aa11f3cb9fe6e44dfacb877c065c0f5ef9a2301598fd1bca503e404ec05f344de8a797f0f06641a0cb86201e6633e26e421f253c405f6820999466d0a55645329eb0590fd00e62fff14ca1619f51ccdac3496bee99f09ccdbdcb5bf70df5921ece8488e747a6e4b52dfd8f7afa0c75ab4a15ff9d167462d228d3ee3b877cfe2b5d93365de8a4d5813494c26dd8ad1284caeff6cd22df1c7b7f48d147a23a9b9592ca86f467726aebf47e91bafc1041c48e2b8155287bf876ec58ba40ecf5283f59a23b568013b2ff7f44a9a912f2ebd928a2cc6e1e8c21e0e6b18c311ceb691d2ddf42770a4f990c669c338829a2bdf144adaf77c2471e8cbf742b66427511dbb3b4dd21c49ea7032a48dbf1220a07c8e7847ece8b3a3eb336fc999d7ea3f1b5dd78158d71d49bcfed7c53b3ad25b47f22c27d422b64798333ad2f5b749d0fcc85d1ec1c86cafb478b2a6d63ba5eb27f71bf882c4a852499c0580205b3856b0a4b09932b9cd153491d865424e0b2f746b8676820e9054568c8e6416a2d1c6d0d4d8184a9b4fccf764ac7bd1f02f27fcb2632e33cc656e6e70e6b2ea7237a72eafe88a7eccdd4a974bdd274d2f3b52c7fc0b481df31b345da6463433cbf29b1c6a448fe603e8158ac4eaf10079a382146958757a741115e40d77953b0c2be78665b9e6cbad6f25ce0a52c7fcf928d9aa00dbcc9fe217777271e78f20481df33f2e6c075219b3c4814dbef377646047ba05c2cf0664324b8c36dbbe99df9d3477f606ebded8a84b97527af3c3c24d3cb0e0b86eebb69d16e9735f2af3fea7c9a5b496c6af7ed7c6a838a4956532598b4cf0a531d786ebf107830b230370c52fa19822e84a28a690e16ee08a4312ee0d686f30a3831bc42ff754a8713dfa6db4bb0de17074b7f085be41b86b8279b9f73629e087c5f5eab781a9dba084e2063bb85ef74171032d2bff986f034b7705d7f33690666629906d89bc61d8467ffbd9826e780629d21f3726976e404028f734cf206fb80629d2a745b886ede9f6260ec31a4a4484644d2f6d7a0972ba497c24145520b9cc32a9e37e6f625d9d158a7e44dd5116c9a21c2c89c349a40efaf40f205b7c03dbd0a7a14cc5e1c83fa0d8660b47dee152d665d9c785156554b66d7493511a7db95596b1122a73e933adb5fae11dd0ca6d634d8e4b9f6ea12f478985379a958157b6ee94db24fdb60d8aeaca9f261ef2a5d339a97be5da29c8f33ab37bf7e550448bb34b2fa28589161fb414d152440b17d1c21c4ee9ce0497aba79d3c3d76b4d8df26568bfdac49b926368728d26dfbb135bf719446c5d51963bfafba90da9de33934e94d152e4f9e29677348524a19b6bbbb7bbbfb74d9dd3d96dfa59ca3ca8b62f95bd61f74ffdc29b1d49a93b0a0e28e0dbbddb1eee923b795dc367a1214012ab234310415405c644bdc9189c4b82317b1952206ea6e33babbbb5b47321c11030c189151d01dd9c78a26b849d7884445484104962656a421f46a25eee82ab2a159ee400746909421850624aabc3a892ab4b8dd3fd65861c6bc42873baf08e3ceff23ae942514e0969e33f1285d95b7fa48c80aa1dbf1e86826d44c1cd851f5002455da465ab922626165716d691bf900245cda465e5558e4c6ad5fbf6fc6f9aaa7f9517555453ef3615bd39a06c62d00cc0eecd845475384c160324cb07aab8faa00823d1bb0c466795ffd73bbf6c2436559e49a5c7b0a19952e3410a1e50c27af867284142f1284b08a18aebfaaa690a83e3b93eac10d4c3cdca6941842064786b1a042e8fac7a8a40a056c48f1c61049a630a2c393275174779b8a54f18731f170ebeeced6741d0232c8628d1e18d9e10d365e8ec47524f7cfba0b80c68a3bd62ce08e397698d02f475985c889db3ffb6b142cb8fd56158511b7ffa580dd7e510a2b6e3fcb65e4b6d01439b82d546576fbad2c4aa28c12df9c2691ecfabf5422a0d8e10030c41af80cd5705b68e88adbff220d42505871fb4d261eedbad636142aaa1aec285e9eaf16c284ebef0d215403cded2fd927426cfcd96eca135194e103cec4a36d1a54ac0ce0f66f314ba9c4aeffac3198f24568bf20335eec677bca935bb36365fe1c527c8073bb8640a4aa342b476045b4232431d4683dc021061f2932f010a39221a96d24ac6de49da1a76de41dadca871d603d17261392b204929c2eadb252342bfd60a3db467b0d8bba524889a03bbe50140e64bf2b8594b0c2444889232b4af0804a900c0e847db95228c808cd6e570a05c510d483152015165adcf15946684f5c29447bc29fe34a215a8f57742b90e45e25da97ae6a6465ab8158b2558d74648bd22460e44e7bb4c857a7452ebab51e85d5a8083b76a0cbe55e86cba1faa507ebd1854f0eb603d2395fe2373256e221317df064cda358c63ac89618aa66d0fd528a3edcce913aa45896fee3c3b24de6326f19fb33c7f99c1eb2333b3bb3f4979be424273929597a385b9cbff90ee2ac5d36cd2ab2ac4525dc93f26c581786cbc59ab4cf8c2f2ed76a2666d6e284659865ecb3858c98cb958db8dc31075deebaaee676396eb7e3765aa6dcaeebdea2b182dbbd0a8d2e6ef79fc611b77b718b91db3d6bcb95db7d35f1e86a65d5deb8dc6f9b0d3f94d25c71c79a1d97527ac610979ec1c6a53d20baf445faacda0e669821e54d857d5e33e6b8358b10d75fe559c6b8fea2194cf0408cebdf2c1aed765cbb483206908a5519a672ddddff3ccb5883073f77fea712e1499037e79cde0c921464f44046ad0c2224246a104248b2cec4a32dd2183a40c0edafdf932e4c3c7c0c99a37ad3d00f3fbcc4fc69973b7fced96fdbd463afdc39c29d4dec5869c428a23f2849be39e79c7e65042eee98a30077dcd1002c4061c4c210420c30cab8fdd2563b4455b1aac771dc15f6bb52e80bdab5555a505ffc1a5108a8f405173a705218756fbfd072439da105340a240e8c243bcc17bf46248a111d2152b2a9ec1751c4adcac6a88892e8bf05e3099217bf461c9a4dfb6050c181a598b9101859288922488dda290c16d752d8d4954251f80cdb5d2914a50bd6220a961f2b73a550943344b852a889254f60c1e2f93daf97ce3927ade6c513dd37e3c73f2ce626fe31f3fff8f8fc70e5c6b063c324037a9e05d2841ed60bd50054d8535f56084b9dc8e73f5d61ffc5fbaf63dd73a467903839dc1860dd44f5b862f4776cd985dc754aaec73e7e1a6b38aac251e5c3a5bb5e6d58adb08ef1cce5ef969cf273e40ba04e62d24fba6552df3497bef19ed8f523b52d7d534d9c17c27cfa0883cd5cb8848e41923083d4c177642e9763b5d6dad5ae76b5d61a76acf9b7d00076b7d09bc29fa510a89ffc28d0c10a4a95d421654e9280a20a14dc4c14322d88263916376fff8e1907c05a62e19cd63723ffdc27faa67ffcc7bdde205c97bb75f7db94d2d5e214cbbab37f6319ecdfeead5555ece56e8f9bb4d80be828585247f75b29ac874fbab8a36515753224c14cd422bf0db263edc2af6df839946dd361fdd911b2f8a7c879984e29a5343539e44fcaccfc514a671593b3a51a9a98b975a96bd3ee40497ba33133f04c38b0fc638cfdea3c92a9d1103c2444eeab39553432d75ba9a31a2ed7dd7dcaf98d31d7251629c1a751c5d8af52777729e7fca68d51c3fe690db08bf99e289f8fe6cfe72f240e0d5b0622eb3f7960295dc37a38ca233be7d1fc5aad85a966296c8a74e79cb4a483ed772b1b6fa017b538bf9b6f8bb0db53fa35a421a5f4ce4be5579c17f58d17b4e77393869369379d3d7b4af9f45b5ae91ab25ed76d552d8eb69ba34d7ba34dbba9bc65b1b62eb64be2c93f4db1abca3cf97d86ef56d26897dff20a866a6022014f1a61815d39a4c488dbe10b7d4fa0ba2fa88830bb6044edf273615867d236fcb98facb94f4f59e3a73858e93edfa1ac510a036b535d60bf9a95ff69b1f23d2ff22a5ec4ef42bce5d55a15ffd4faab1daf7bcf7dee38593b9dcb6d587ea8150a72a806262b16bc6ec50237a4ca17fafabb498599dbdffeaa1504666effcced17c1f6fdf4b77005810e51d04335cc5c07e59011da6d10c84ce9391686745f7dac76ba1e352cfd58ed54e64286b9cd1676943549f35add81276b4698a3dbefcdd9ec99796b1691366d83db33c9ed375d86756ccefac6c39ea1dbd3605fa6b749b76bb727114ccc28b6b2a617b75f8e539630812409fd63c70de87e9c5fdc7ec90c9862947e82b2825f0bd1535fa537813df6557a6f3699b3c96d3f67f349ff0cea9bd39d59bac54ecc28b1db0feb89e5f67c6ea30deb7136e99b55cc65b0fef5fb4b478279191287c39641eae8ef9e267d333faefb0de4a7a057f04b415e17a4d4b19aefc99d52e989bc3a30d6fdb7c128d0ed39bb5e80fe052ba0bf0369f857b7e3852bfeef4da57ff9d2bf84d26d60b7571caed0afea35b7e92237e2048c2e468d4bf0ef9f44781ab2d535481b2fb1580630c00823ac5630307774249c177e22248e87466e7f73d83d4c427f05606e43867558d11d59740d3be97c3283c2396bb1ad1376a5ba342d7acb9174bc555556bc638bd791faab0dfd55c3fe6926768431f299893ac058269edc619d98fd34f176f7769f5d2abd572a954ade61ff7c45a8ec888c1ee99b9e7907796b1a55b7aa69f4fd17f27ce16ae77bd313799d9ec82bf5445e2fbfda81f1c2d56ae7e589bc504fe40513127975388a3deb20b7a9df4a85fef9335c4dc91ef6ec5542b67f54cd2bd388bbc2362d06f52fe16ae7e54f7f0a794eff62026152f66dc863df14ae764c2ff332210f036105d0cbf4445e68d85174b1a8d8fe9195d3e30cabe34389fd01b68086ed3f828cdb4f5ae4f7598bdca2db52e8eedf1b3f2d5279efd934f270ae66974aa54e8a12d3142947889822a5895237857604e40532ec285b088f18769449a8d40d49dc0084373b12820b33163b32ec64dfab45497c2043e4e5812bff9727f22acd52a92b152df911aa41a99bbcf3bde9c56743fcc0c18618ea39e2854d8128906545ac09e431fdf72f20cfcb7bb3c87f70c27f30fa698264f94f2003ade005641ac84158ecc841b4565b0287395b82064455d0b0231371b335685064a50776e4221d34277e92a0c1e84a187664a31a8e74331eb2d0278eb4e08922b5232c67d8918f8250b499ec065ed1941c8c31250731a42c5aecc84840231c51f9e1e76787ef880a50142a30d852db32861db9b6061355d11a4abad625cb8c8d2235c8e024e67264474eeadad112536cb425bce0c27482fd38ec89202bb427a0f8acfce5134aa4a104d622bfcced1818f603db03fbdbc28eddb3be794274f93ba86f7064e9968a1644bb6c54a5a8c7366a2cdd6ed437fcf38acbfa86b7cb3e2d3255628fec539924b2f2e2f6ef7c2f47df4c8e565a7b7047c8b02ce39e16e723b1f673bcc5729b69439657697d814e06b9a760108902e78980fbad7ad273b95a34f66938be306feae97b29d0c54a62533fb26e0aa9d0d5e214826605f2f06bb5a2114d9e2d09cbb016a76d815d3d4f9a9f00f819809fef2d117e3ef7489cf9f2bdfcf7acbe898199f3653ef5144cd114a55446e653a9d45370b43725937a1c60102913ba5221abc519d3435e1abe302fcc57930a0ee8d2a5cb044e31e0bc30e04a854e04a7ef589879115e9a5850fd8740dc66823c217c0f1e105ec76d668eb5c1a60210ae587010de5fbe0010ae58e817e145088784c082f1b123a3a484d1c1a668c2da368fe2e47da92327b12bf05bf4583b5c93025c972e5db4dcbab1fa468a2dcecf0182005614cc5b431170e176ed11578116e4b6b044767c3bbb7204184a44601ad583e782855df95c172bbf666e7212a156b384c98ed83c42a26868c916cc9f7771fdfb7e5b05ea90fadc0fd99ebe735fc3badaa135ac00bf36509ada4a1df25d081cccb834e3211fd9656905367447793469433e62dc5166b9e112f8f2d3eca8c9e1a292a44a57b90cf31e0670e5d0122472e8c18778f099245c39c4c3929bba7228872377b4b7979cbebe41c1bc2278206ec39ff4034947d2e60fc9f2c60f49fc404312b1d70c596ec34637a71b1c595cdc51b5c51d45a33bbab4b8a324cae28ed2080756ee28b1f841c914337a3022c41a2fc6e2721b6d0039d183315c60c49317bbe00a173c448143111fbc5a90f2c30f48a038e30ba1979c92c50e8ab8e244075704f1e2d7f11691266cf83083284ddc40450c5efc3fbc45c44796450e50a8449182cb8b1f88b788e0008410d2164b72b0850d5e0cbb39ad86959f225d9c809245183d5c214692082e48425ffc9affd1064810f9708413468cf1daac08e204e5053478d1145f6367777cd65644c5dbb66d49361a92148e8644546449b5ddd00b8ebc2b875e40bba82b875e40c65125626405555c5144066d34c1c40e3a9c9a4605df1123ebcfa1dc416697b0760887d9954338fc70b972e88626b787b76892dbf07c164b14ff6baa19d29adbb04b9a7bce09fc3d80f0742b351bba81b925f52997b17cb769cd5bb4459f456b5f592c4e14c37053d94993bc259dd6c64d6655cc4f93faa693688d26f5d32de3bc340956eb9bfeefc52fc1a9841d290c269f7360475aa3491b6c239238dfe5dfaa9480248eeaf29792240ecd2237a39bc37f425fa140fc5c7e9ac567df54180ce69582a08136a573968e92d4e86c5312596527fc155d2fc0ad675c0f80cff5179598a08e8b6407d7c37b641b92c9e0fa7ba58f36a593bb16d0bae9ec511eb9ee524a261edccbfc920905ee4eca8239dad93075c8062ed72dad094167bf4bb9cede2d31a3200c1c0cac1c9214bd9cacf8895c0ed72003090612b80025bd642b60fca47f1494666471c2e7c57d74fbca85306cbf852a9f208f93ed7960a03f9fdb421886c010c2e6cf906ea100fe120362b32734643e01ad440b625629fb2547b9bf7ef79ef85d48636338fc8ab0fdd47be98131866ddfb3932f252d9e8056a20551a2519f163d0545e346849d9fe3b5fe04a912caa4452a6bd19ffe18b12395cd77daa4451f6f51a09f168db0e3936e79fff44ea62d8ff64496684184935dca29bf512f2911b62765a44b417a3dfed2b85eb3cb9e2f361394435bb85c0a76974d3c4c3e7664b176b898094bc35aa52cc293356337bbfedeecfc259b7667fb489c25b2d5b486c1ec2380e60c3b0abb74d2dc559cfbb8c568cf75ff4bc35983b9c08eb376dd9f7bd6e696a4bee16e7685e78d9e4481c84581aae4cbd0b7f7cd3b5a294729dd28a5b46edbe6a19c379c88fc884ef739db6e74cae935de74fa07a055edeededededeed277477cb539239a7cf39a7cf397dceed4729a5f449ff45564d70da83e51f41cdf50e3f362cbf3b0d5628f00dc25ddcf97da3dca4936b92c6e46260adadd9009d6d78ecb2c5103e65adb5d6b7e756b52aa9f2af7247dbf0ecdfe1f21bb4b400460d5c67296ea18ca2bb67f069afbb9b065b4a40e352840f96a001547bd9206ec75ceee548a3008e33e1722f81b81cc70dcd7074b9981297fb6fc1e5b81550b9dc100d4bb8ed270ea89052d61892618aa118a05c2a69deb25f4c8d50171aaebf55d9707d08e6c56529969f2f3f570508aa317f4063a03180b4bbbd372b839fabc5ed27cfd90da809b981349bede7f2dee536db6f2edadef6837d1e48a92b751d2704bb62d471251f3f3626b60964ab3374c0edf7b834259523212e466cf54b933f6a03d86fd3c79b33fda95f7afb6e20a090167dde86633dbd842fb4e8817c55de6218fc10773be26ecf425adcbe43202dce366c15bb97a6526982d20401de4283186e0d4720462c90be61d187071f893382c4f1bbf1d718b0138672b7f75a47b2497b60cc1f3393ed9917f0c3432ce0226becc355b697371099e56ebf6dd2e885ed9989cd0677e418cda60402b42930e5087ca8685cbed16d73b931c2ddfeeb1ba6cd20a90104834986e5608165892df664c38ef247f1d61c6dd3bfd336fd34aa16ab2c6a1daf462cf730cc12e10351c5ac246454d20d1c6a68f0400b1facf8bcda89294931a46212c77d50c49d3fe9106d68b162f08bb04ac01d6b92d4b8fdb3dba0e1761b40dc6ec3095114592c9628b29ab348f2a1bb356acdc18f0f4d830b9a30b791958d299068f961830b1cd060c30c26b5a4a42239a071b4e50daf884f62c65e740b6cd7850c16c795424dccf049cc78a085165a489c1472c08c2185d8885dd79148eeca72fd4df86e13b28813c38eef2fb248b8fe3a64093344c1f87116965e1f2d7a16b65ef9dc3454d1c44861c79f3b3a1cee44617908082ed7df65440b2d6416b6fb250beb2e136e66ee0bdd2aa1e884357eac64b9d46713e0d2fef128049038f3ef0c5fc0bdcb07fd8d7e9538399d6c5d69439f521d2e7479447614615048769351f395532c3fcbdcc443be9361f9e7f4a05839e70c6873ce195eca0196304c18dc279592caa794524aa9a42e039ed4a8e48c16863d0d15a2190100802000d314003028100c0704428148281c9466c97614000c7c984c80549609e324887118062162100100180300010032223434a3006a49d24a09e5875e96b9a9420a4d1bf61d2b220c8854e6651b430124cb0e3a7c5dcf6cae9cbb239d0e1af20c1f0efe7080cb55eba54558738288d4f77de865b66aa3b4d77ab1ca41a0d1ec71bdb329c8c4355016a1e71d6309418398721f5d80b6b7adec8ed27a8aeac522a901636c59ca4374ecb5d9065b595221cd40925be9b8492d53c70e390ce3546b85d8bf77baf66900eab53b46105a26db4c0ee06d4c1c0293cb83a3a5a355d50e5933b6c66ac8ac7cc663e869d11a863b7caff5f2e4820237642dbb5e3edc8cba2a9faf37325cfe00d95753b48aabf3049c19b6a0714b21c4f6922bec122213c1186a785789856df21294cebc86faddd84d1fcbc4a2ccb22929bc0b413145a4f03c111b0fc4694b41e91cb96262628ffb59469fe56d0109003eb5809ba141816fc66992a6082b425ed2dadd5f2e5a39e5425945d49e49dcef3addcca2ee8fcd7786f66841ab889b90c855909637aab729796b84f20ff35ac88689b4f4951320c5fb25d506c94a0c91b4be59b62f5148db256944edbbb02c025d405956082389f51a0f412d3b817dc51df1b868795e91b80703afb80ea2ce1c6927d77cc5d91087fbc2e485a6ba8b19a1e351458749b71c8201ba8e1b0edce7d99f7a2a791be5c11464374df03a7b1aa53d5588315b3047b033a72175737a0c308be2fab2111ddd48d8a91879166c8dfb13291c0032161657004d5e9b8883f4db015a2319a117cb68d6595abe54ac2799515c19b2f5aad084e5824b5c1190e1c0e2774186ee93b418436875abc8f6aaeba2315087a0a7fff1f058abdac2234eb1862d70c8255d431c27665936c734f10b50791a86c330032e2530a1f0c2b4cbf09aef303b514928ab314ee0fa6c331f78f7b5d3ced8169808646d4f592c9b2ddd441f6eba692c9dc7eea9e0f148a95c2b498afb8aee713d5e4c7dcbf4d79793913e850695f2e30f6ace7a4e296d5bb26c1e5eb974c9a95ff66adb885bf5ada242d37c6168df53628647dc1e5948dc7010df002bcecffc16110b9fb52fdbcc35f0538f315080a61b75e87160bfee3183d00e22c18e49adc0b6e83b57f4c680059572dce6c6cfd1ab42635233421bdffe9237c3eec9b5e112f433fff60d44b2407c3febf2e53aedf6dda709409abba0db222a337d10784de393b629bc1ef0043f852f20845b771e0219c67f9041053008e55ba7b98f3bf27be6513f61c50753c32f020fc89f877a3c0929e4931e89f23890430e6fafc71a7d76d39e4b4b6fc9bbea84bc55be361be112a5158c84b5c3b18f0865d9911152049e557d13966e76a4153ba33dc2bc58ea8a68f48824a16107120ceba6072b6939a104996529f7f42d424499fad1f90b1fa7cbb0b5f1458987c3a655fdbd97b00d8931c54e4c9d2aef409d82aee804be4d789b65b37005e52140a1d8aafca1075c31101ad7eb5bab909eb8750a7a2d497935dccea7c74f4ac70defa75ebebc1cc70c1f928e98c009687831ab41ee611a394b8cbc45c04290ad17b5863e1502ffa6e35c8ff6e9078d643986d1d1075e5fc650a5f14b1f7c32bea6b240e50a7b661a9a1ac8322c24d08310016c26d8fdeed0984bf7b2d2bc8849344b10d9bd54f9d5a6109d93ce7cafcd0e8b160900cc65da47d2f1458ca7e83b344057d5c89276aec1d150dce459c3d389e1fb8721ea327e6ac02b32506bfadc4b7cf72e600c8325d6ff338456b8f3408b301c1408e8fb8b26a463c709f00c73ecc1f2f3b17d1b2f631868d8d78c2fb4a4e6b056df8f601a0919e9bd0f462069405693c7a34127d8ee3425440cf2a9190aba13a1bb82048201ab41775e16e88234541caa70d5409dd6baf619b416706d10108fd94cefdfda0d598f25d50f1c2800acc96e1295ec54a374407d36e9771524c25de09fdaa7b9ba7848789f317d15b706fd2abc5d847983a90aab741cf23e7dbca5e41019411848a737105a9ec31748ca7dd01dcd26e0ec2d200fd6892f12fbbbfb6231a1bd9d380feed28014ff05856003b8adc18d6c341e8ced8ca2ba98bb08395a4991f4625e7244282649f19ebf9c7cca4d8f645e9f065f683ce83d4feaf19f01e8e439167e1afadc497179fcba9782051059aad07c1ea0eb8e1436e931dc92b5988c49f9c39362f276d2fda8decc1a7ee97e1b5fb8526120ebbaca18bd8f22a00c7a950a4025788af509ec792478204be7d4a10417e99928923f8ddc17c78dcdc9a5de85d367af293273693a08364037d907bbcfb93dd7502b061082a5c1a67a4294c0c18995052da8613e056c9fc10afe5c359cdd8358b1f8aaaf59cce79d50e4306a88ee6ce235f59c0bc44ce8be234dc318c6e98f7f37bb21f5718c2f0c0ae805b4afcb77f0b97efe6799bb3258435ee48a0cc2bd955bcefd70803d79e6dc51c3d4074f45d8d8bbb83a9973b7f6e2342ce50e83427aafac33fe90ba60eb949abd1a0f39301d6ab9d3070792ff9c1da3c01f6e11879aa95315d241f4998264b11c41a03b5f16150eb93c90f3755aaae826786d4132c605d3820c88474aa79b8cb245a4bce1cea202e177c5024556af3b780b79452ab904345ad36f1f3534b04772d53d8368df7b782045ff72ede5178848e78b3764028a087936c22e6b813602b59128b75cb7b16277e76e7ef6da3ee0ca628d52ea7c3ca6e561103151b599c504dbbd05110319645c08f441c1a8b3b2354bdc99231983c908cd3c9d74896f05d77bd58b85126eb6fb470c6d42776f3b8f35ef9867b15664d880689bca3e329b479a6bb3e55637100b72dfc6b0dfeb6d941ec9c411e0476a1a8df0ce29f419023603d2751cf6c1bb3e37d3fcd4c0b80ca319a7d34b00a8255f67672c04dcd1ad06140faa76ef130c8b16f6a1349a746b94864510ee2dec79c6b47a2eee2d3ac7c780a24dc107c006f07fb35c63f9b099f9fb18ea5751a041b2c079df904538060ee0138e2bc487f8a44916df6fb140da3985f458429c9dde5f9978c3b39f6095d5a98ee918479882bd130285ea6f893b59f79e08b420226a12fef81970bda7eb464b104bc45b40a9768f6288b592073f25b4473362a9c22e219f43bfa6a191fc91eb0a4951ac75d5cb1328928ae74314c7d2ea4b895e6f195ccc41ec6838f11c4b2e517da1857f4587901e190ddd8d333703f2578a32a62923b9064503b6e062d38da5b948464a01d1e471ea34206e3881d633883414241f8758c5a0c10b6b5b8a67bcd0d8a164d1883e72369d01a6906446f69b378a02b46ed701fbcf20ba6511b29a49e845d01c49a51269ecf0ca202ace3a32b70802194834b06e3b7205d508165e33bec099a19dc927c11c1f00b1f4877f5547a0074380aa659f584e3e97246b3d5a489de0764d4b5d5b384954c978835366d7991acea2b7bcc32396dcb2ae8e270b9a80408909fee81e72732fab0e42897e68e5402013adb132add98e58a49a8cf5d6a294c28a9610575d7dc7b3a180d878c4f5567cdb8d6e24dc4fe19445d5fd94072c48917a1b888850135fac234585babc3366a159854b8061759a7a26e3b8eff3597e5e256af521c12c56f9ad879ffaa4772aa46f8e22ec30b1a17978791aeacb409b3873776e8f13fa281482f3a4c483f597b6edaf2ff2375fc871ca7f42445b15f739e3b31bd07846a59f1f8d35526ffac8c6721d5519248d284f1fbceff992cd8e7a9c4b8d0bb121b38835af8c6b5f182acc3be40754e5334f1fb0a2d5912eeb9bf1b4e28d90ab008aee414b4d84e7cab695b8f3d03d1ec878ed0945145611d5496682dfe9365410ca865c0262addaffd808d0db3e011532b7a747a7afda5ed03751d621c2ec5666f10a6cf06d2fb8cbf0b24d58b01777db7db1c898a9f88537d9f1c7ec572fa7bf9b0fe34489912a64cf17dda0c728609c1b2cecd85308741e90806fd0cab439bd0ca53963d269fc080579072d7a123faaa53e7c34f79bc66219b06bef21aac319452a1ef113306a379f89d020d0a0ec1900f6d02bbf15057cc05f047b5ec10428ad84e97f8dc1f777ce6495a71fad09d1bb029eae16e16e861cdbbf0c37c5729e48244c529bdd8ed66111fc71076ab73e843587b204e747befff8f6be0f9ce720c07abc9f221a1760a5676a49459ac15c11ad63a30448318920bc8ff58cb1769635613329389e0815327eebc7ff1e6f40aec012d12b49ec67734328f6c1dfa36d7dc86e6247d01d88a4c2ce021243a7b868e4ef6dfabab5b722018beeadd463cb26303ff0a9cacf72954a7983894f0b13ba58cecb8ffc4671ef83b3ebec87d6cd80959a9564cd9b91d5d775a3384717e6b2d3f8271702435b7a1b189bb3244d412d345516fb121f730b38a71f0ef0be470b6db57e1b1a13d748dc55de9cff683c001b98012c7cd2314be96cf48774ee2ca133d7bb56026301e978f3b68fd704148cd6cfda16b5d490311d6a913bf9e394f64428a8e282a18c0f59bcea263408613f68c28561664e0913499cc0254a1a33bba5c485bdd5a40e55b599f2c7b0c81a1b5fcb6bb81db82cc71882bb74df427cb5934332b9667f6c12adc1287c47fbfbdcb373ae020789a5224d8d820bee93b1497c2fc1cd2b1ef875515a392795ebaacdd42413e966e7616d32a2d9f9db9609c8273d188388dce1f7765051b05f32be0b77451669422580d672421c8cf17ee42f27823edeff874918f02cd0dc6773bc15198a88480cf2c2615c4f884b4375f0053917f3d266fea95be76881d35e4c67b3027e8c98d6548bf6871ad9c25c68b915ed508f6955ee97d39aef24c403a4db1a75ccb57effa21564da594c850922f5a7488a37342b58c162de4e2bf305c42e93d1ce449f6e96c354a2c936fcae0de9f253333331a0682f98a9a3f4fcdd7d9ad441f16c8a83785bde342c431d6e982c131f87ca7802ce1b0039036e5157fa26aec699f7655819c57d59a931910c5818900d9b5d34f55a61184dd55034d33366e8e9da35815919985bf86b9d97c9fc25d5cb125fd1d017eb5d4e65f956c618d4913134da136220d279564e0686f5d21705b128b86f898dc6b84f9f1b1fa167b8f51eb87d74c0e7498391f007f1521eca3c6f4018a70596842ece684c0d3ea98c0dd6ad920217020bed64cb4ea5b85cd8a8322588785dfcb1c45d1276fab4afb5ce7441a521b5731631121e43d9e3925e3d12ad355e14a073ec2b572012cada0a4e9c76e1190ab593c09e5b8a437cdc030e234b1148dc260db7ef44d16f278d3233cff7377738b6b93ffd601a98d61d798ee39489a7667e0d1de58084e040bd61e06e46d2c66469caf330a07f3f487196fc6e13577e60d8dbfbcce95b25ec25f6a828bdd9e88a2b253fb909440fa37b78c50aff8d69ad45eb20d2f42c4d5d961d43d482a82fb58248caad9f90e5054d95b9041c59b6b52ab819cbfe628bf34fe7308f1c60ed282fd7012f2c256906d23ac0c6ecc1ca7d057b267715c0a9c7145bdf954ffbd54159af9ca081c81c4b100f17d98512b6e5b857918793bc32ec58b4a52e1eb97c6808343808dfc5140fa7701c8ba64d1e6e62568932212aa1a9b4ecbb9bee148e4882d4a5a8d1c610a0d595d42a2dd3dde5dd4af1401dd506ece7a3157ac2c10a59461e1b506afeecb4877415d7a3a15df7bf39c51dd92ef0b4b248837c2d8a9bf7d611b5752e7bb740473d5a628d64afac6fdb2518b51c374a6700c4349bc22b3b53f2a84d187ca646c47908f6ef81d04da82acabb82557b1e35d830b8a1c37e8a61c90357d3979cb02882ee1c3bd4850851a22c42895ae912fd09bee1bd632174c256d9355f37b9fd94ac44757eff351ea521e48343222af386578c4c521cfd4ce716b6ff11b932a66d84602a594b619f7e3755097faa2a808b3797490a135c7740856ce3f2fbe8b9fda3bf8e4453e263e3a21cecbd674afaed246d22e723290493633a78a6f0d5890c5d3a7de7103da6c51f358c17d00f53da9b619030153b68dd4e1a75454497597c553c56b8c774f783d6686c6e78ef32a4b52b9cecf76275831dfb3e31e6ccf498e114e04a7f73c6e3ba369a3ce948d9f28e92deaee53081a7c9cc77623286dc7402a218f5bd4cd39a5f7eb31165505de3a5e7f8bdc9b65980e6c80f514007487353812fca0bcf7643c725b1aeaff121f7f6586504e0f89133625d99027817e811db7af59600b85003520d8134994fa80f415355923e47a420ec75a14eb98fc09c7904b41d3294d0583765659f36000bd1e4aa4c9b43461ad8d74a4d8392f92a42712a2519382f91a06c2a71f18f8cb3fb497c1f30f90a95d2155250c55f22428a6da50ead3e8328d7a7c106c7b997d68335cf559e0e316615890416742f20574f68972915772681eff28a185c574b5a1499e11ebfc911c9e5e44608cb0ee89d80f84881164cf3ac55b721de9e59e6a6f793cfdd6ff91515a1453f607d9b58d56053fb115a894047b8052b29daab9a2de5ddebe9ef31ed2a7fb62cf68c7ea9f93bdd154be0e832d81c7c845e74068a51412f0cf81357365cb18952c4558a713fe16a718c9ae7f2a9fb08e41fa26132bafdafd77e43410340fae188fe8bafa95b02104f8f3a859b31d4aaed17f926bef966da9f7be067efa70174d3576848bf463f9b847c3279bc2bc79828464333843b417642a3bdd8e50a8894418fc2bb84d505d2c771a4038104b0b35f3f219e8934a00f13b27e07941c45c4f4b72b5b3a8b37c7fdcee0da29c93dbe976e7509075cf90d28d3d9be8191520ae7c49b56419b34014a37f8ccbaffe010a823a1c44085f181b27fc5c36ce6d8fbcf84291cbd3f5753abfdfac1605ad11418836a5db74cb1f64dc774645aa2e80110bd591336cd5d3fa9088af76a8beea86c7beecf1bbef853128d92f29f67b28d690ee0f18c1c4bc931b6744b8c53e244914f5acc95d35165148fc4489488c710ce7db58511b2262ccd38846dac657fde39b3a5ca310c7fd57044f4551b42d52d4d090930360bc61a337053e7afae74974013f7a3fd3500a027f52246a45052b25e479f44cdc42e89eb06e23875012dcca4d517f0d7dd2023c8b9666e47c643cf569aa475ebd115a19e990171e93ef85782f906486c402251a2f7aaead80faf3f59e510026ca64403ee6017701260dcd173cc6fe05114cbb39ee85218a23486ae24a3b71ccc22fffb14a7d8b73ceaf8a7742d122472143405685379c52ba949aff9e4a3276183a600b4fbe36ee3b32b8d9bfdd636b894fc3a40d98f09ae14be288a302532910bde5a6556c70ffacd8e9d08cce3174f20655676cccd4c595207a6228c751635507087ec0020fd312e1298a257974636dca914e1697aaee7201aca8cdb605e793b1431ea302fb038c24dfdf980c33b794416fc57789517cb20b642dd1b1fdd4de3cc581fe093102d4dfddaf3cd08eb710e08814aaa078bf0e490a8c315d6768419a3ffb73ca0dd0c8d795c70940e77adc725180cdb351293bde6f91d7f7e328e9fec608e54c2c4694f151624040abe13317b89429f27553a2293d1a10eff20093302e965ac749ff0d6a08c1ef4c94eaab378d77087cbe0bbd67197add8142f937b1937b7d0e61a784129276854590734ba1d8ee139f767d7fd038a5a9b7bb98b733fdb4adff519f6a843fa1c8439cc1d66e2a558b9a0dbf1104bb5dcf195db1806d66a109c085fa6b0e79627401d25458db0f7ae9179ebf6e05abe00a51242069411c33c19f129b6172a0d0184726e7297cfb8ece9e7912f0188a763830ebe3e9860e4a567a39ea118b10c89ebe282efbad49c58538b01e8a339c18e3e3db4829ee5a0c3f955846052e82927490140f0312894168c7213bac4cee6dc8d0b6478ccdca4086aba53bc0d579ed6b998d62f6df0c730c30fd8fc13aba0d606b48ad1d55e5046e6ba09252379b14a96bab3a28699e2892fdd12d8b890b7289ea8200d31f18d2f898181ef1b86a0cb2876bfea134f616663bf5325a213b69b3c7f7e71d9f8650b9e1ab2e351004821567fd878bd3bd184aaa4c15293f72683649e8866496488b8531be7c6f0810563a7285a744404352a94bbaf1044796c8faf53dcba1bc007751cde97f81838fef67905aa302b5e79a8961175595616d0292f3a451775314977f6c986cfdb160f4da559066c2746fe5b3b88feddceeba21fe0b0b3cae484746410006bbd485120c08b2b636f2595c909a50745d860cd2edfba7dddfcd6048baf459feafda4365780df1b4682378aee995385f1dcac79c5277b691dbb3d76058e1a5b237b1f8f5ca0876a061b6f7c86a8ebf32bead4a74eacb9de75a9ae11ea5dbfcbf3db725550916e2c845ea43d490dcc9baeb23ec9d0db0656dc6701f4b71739c1fd88d70000221b09023a8f00f6a5e975e4f3547ad132056c7d8a1d5672a8afbde412ef3a510755d04c7df16ef8be4989eca9271837af6fae164751efe70c50de7d43e270304500e3152af6db807abf7b2c8150e9d5e0baca3a55688dfe658ac8533e09d0d58d85ef5c5b850c3e6d60d28ab27243bc1e15520d25a9da6760f13a3ef1544a866fce43aaf32c4cadc1923c11a765d091804265ea12463d30da55956b11f0db2318e8fe05e2952a540637c04352888f55aec8f893ebc9dd306efff2c7dec4464503983886beb8a572f51e8fc66ab9b586b25db53baf71408fe99332e0deec31e3644b3758ec90808f0861d8034a4584ba8c5e67f76ff686b8c203c55c242c2369e9671741d76d663fa61af2be2ed046f40db2e4aa9dd958d8e8cb2f7afd2c9cebd241330815a7268e4254d676d3c5db5575fd0fbfdd2744736fc3436df7e1d1e9eaa399a4891632824fc957bfd9c10669c3bfd676182f519d919fe507bfc428d8fe60dde7466a0f587560c8dc4140cc61ab9c9937555c8cb301863018c3978d17055268b29da1eb20b4dc1450ae972cec300bdbaebddc3278d650855e1d8dd0b6d8610310434c0419ec46358b06d4d5cc08328d63d6ac84db707a5bd86ea269e6f94d132da82b58f859e8225783834561209a8709b6aa5a27018a0a3d34dbcb1bdce30d6977e2789d971ff2542a73c6c760362db0c81d1ea95e8ee7315598d542f6971c3cf416cf5474a1e27c8425ed0f62de301d4e9ede8028f5258fae8d0117e8e4668a9e8721f70a28d648a898adb442cbb72e054745a1b3e556f8f7e81ddab1d5a5183b4da94678eff234a2880164ee9d431ac2bde463483bc8e4f23cb49daef111300b2fc5ec3034d9e14c86adcd107e1bc0de7fcc5d4b93c820ebd15b475df795c89bd226db911a7a8af47c39c3fc74a19cd90afecfb3599b7c467081c35be855faabf57ab325176859cc549d994ca015826df03b65fcfe329fb345fcc546564430dd26690104db4e18f5af0f27c2c12ec9e00e84743a4c6035c5eb92294ebc1f6c23acedcf0d5ebc63d60c2b1fcb06652ed0174d1d04b690f06e54307451a2bcebfd380c7d7c067971ce82f6fb6dcf83bd8b75b56b28cf75d9d366a7e3180498fb3d8e0a19549915fdacb49b2d9668080c66a39938ec03b216287e6257264b5cbcd3b06d239922715ef4f27854d50267c6917941595475356390aea8a599d12335eb0748786cc185fe1517e8b3b2ed7d3c21f1c29b04fe6ea517529f24ad21640778f7a9eab3f0fea616cf42840a4afed4cf206e282bd20669b34bbb3ffb734b8cd15b99e7f2156514c10cdd68393bda353656aeccd4cc25ecbfa7846d93544c706100d99e608c893446a539f0e6f67af09ab86516877add7c2c6c30c71bac4b350e15aa223706179a00512a03894d0971d5e9aeeca87cb2ff352c69b419f4b6089fe2d52e03b9bcca29155acac9ecd608b874bb5c9e1cdf0929859b093aa4973a13a854f42d13591de83466cb14b8ce78610481e36a127350f229aa1dcc6c51c69cae2608403839db39dca81beaf2ef539b04fe4fdaa6d51b032cfed26d54d1ad41a0a90a96d8953a8a6d2db73eca24d58a6ad0d3a2c8031a5417b948fce5e9a62e7744ebfd21406fb1312c9372a2ca91d37f708862fd165d74bd07c7c9cae4cb0426ef0beb4192ed1d6219a463dc892a0071dfe6c288ad01954148857972529d3c90579e656c796a34d11e8c3c0e14849060a1006cb591d63f5f892dea79795462dc7b9dd2b6f1932e075cf067ea587e2a258e2a783bd35962578c676dca10c898ca9845e25ca59379dc51947e52d33782edfa0a965b88259afa3371b84e6850b3ba2a0dd91c09dd8c008c147d16786a3ccc5560831aaa5a0a323ea6edd9c7cc7948365665802daa0c4b5c3c32d49c78f7ff21a873d23fd540cb76c6c39f03c0fb053321a59b06f068d5e36031612f562e1864ac1147f81ec9d17182c63f59808b9880bbedd0726289f5242c6cae2831364e7d06dc0670a83cfb3b169104b5a895647c24b5ae35f568c05c70e862080135834daf55a70b0cce98f9e86b1ece88aa107cd6cbbe19454c328013bae1907a917138253abab253a960f325375aa1b7ba0148f25b2f028ebeacdac7346d41794dde2bb9681fcd158519714818a40516a0c25bff78133f49409e647df1a0299d635c222608a51e01e63307d57ac446adbd19ea799c8433f87f643db3656d9b8c341500242c2151c5824d86002bfb1e27c81c3d59b4d5007516ff7efe404a4b0e372055c1c4963c5025e2920a790cd75cfc11032d8a8390c03f995e6c0616086c7d609dd5b3cf82082958984f113567d2ebe441a3683d5b6dc051b8659c355ed81dcaca1ca33bb0519e079ebeb3f148e7798f7452d299916aabb973dcd10ff05bcaf0406a06c941460110dbf1ff20bc2a5f11014cf3a7a542b11297cbb5a24d7065ffae2776e7c4f46f30e3cce31a4288a1bdb9542eec807dcd457e7384d24684d88a321270a8dd86c2e824eba0f3ba65c0901322821f0a86b3c7ac7acfaebbbcd22d415cf114bab55b0e63649710b9d79e72ce40e3d2d9a523111fd1bbab04ce0287c6fb0990ffdbef20909bf613b55cf50055bd88c506219b85ee33ddcac408376fa9a06ab1419e35481131adcab03fd74b9781ba6b2deba5fd8e256de8db69bc3cdd6ba759e6777a421790a7cf20e3b6756acb47dc18a15606801428840f21d9add54fdf5f11dde5bde80836ffedf4bb3e07d4690394d8ef25cb2c845270b0362e8eef27c11c045ef48a343452163f202ba66c8b17047ad2d5b0a5e0d639fc2a591251057719180aa7b2301f79e4078ca93f4bef6d643e5c2335ce6ea3e073f7b81f6e9edbe5f60c97b1bd9030d6e618387bc9edf2768ac8dfa92878ccfd3e91b9d3342dbb80862d194187daae658d75967baf56f1692373eb058d73116c0204524c46832a3a51d300c01fec3b6fa87be570a0e7ed31126e24d65b2dd247170ffedd7f482389a4600b9ef59ba68f26a277df03ef4d91e0954d22e5a8f7d6a97d34c029ab2eb64504f52d577d9c5032e0a08199f513fe14ee108a5abdae2e70f2ef43d658b98df723bf46a8e98fbbd7f31eefda40c634f914a3134204c0016128da1faf0581edbd66b3580ca6e9d8b517b5b778614edc8db84636cc4a975601aafd69dc5ba6621750c9d6a9cdbb00a3198c77a527737d32c63a36160ce53aaf19e05f40b41274f3934f0d1a6740419fc2f118570e8138e31238052d9fc21a7ebc3e5a37ee8598c813af7a8bb7f349360886c1fe53640235381b5ad1342044242fdc103f1ca505ce1f9c5c2062a183c8c40b1debf95736cd05165a5ca58a44fd8a5bfcca4b920c5d71cb8de5eb0070527f0ba69bb5c7724e9cb84aa7a8dde15e671f459e8e4472442120a1644889407d1005b4a302cce5821895423c6aa782ee7b96dfcb6cf568ba0b5956ce70850b7276d7340a83be54a29d7b6c60e016009fdbe3d9ad14c3eb9cb0230e2f0c18b74bfd91f3791aa65f88fddf492ec835e001a4a68f7882351c4058f76e9bd14e67ed0bb64da3ab2f91053840413b322e42b604af8435dc59d4e413547ec7ad091184e7e0c45672bc6d7ee08bc665575101d5cd92cbbaee30cab7a6e34598089d2cee829adccb1ff1fbe4a8929d23c237e61ed34c90398bc9ff8f42a5588962b9e61198d1a288df7901e2e6f37aa6979bf5de7efd239a21c58a1c7d8c6653f245738643107799d10095c8e6ff2f47d6e2db1bc87b498c52de49aac05868a6f87d7136cfd499e2df012997f3955464197219c39748f21068df950ff58756cdf3eb3aaf8ec612adbc2ac1c9f418ab27555c5a7663212cf9dea09caf728f71ab430cc2c556ac81d3dc4e649af93429e5c092aaed48315e6320c2874c250b5317835e51ff94d169dddc86728902f3deaa4822753556b4e1c9515052765f29317b0d659c82017fac9c8c3d6da0efb78187641b8b0e111e5112781822d1c2254d02d347c9d5754f7a53c00498806a998cc819381226a18b383932c778186fa22b8335d25e24ab8a862b93ac340ea42cfe8fa2a23628037e83ac484594c36841fe7aca72bdea8430efcecb8a983a3a2d99ba348f764a57c6ef2c24c876ec25e39c552f217a93aaadfd869512a9c47c06b8c371ab88f8d2ba72841cc6afb77d68f97138ac87cb4b19bf6b6a5edc10bbd1a99e9b866e11256d6fd4311ded2646946452d24fb14e48ce65a56286053a6e66f0d34ce6429a30b4066926bbcd8c75a7a02b9740f517eb7c0d11a1bb4a2595c0fcc598e96deebd1ca340f52902f5d9f02d2965b9a8691932e238a158b885d8244631421cd6e5d942924c646f15bf1c91d7b4d132336d717657b37a4f4969bbcf4101a7ce734f99ce25e3ceecbdae66964fead001abdfd241045e45c6c5b7859ae4701029f329014300b69f5a35a814a9fae448df2cda6aa4345e22167f012f017ef2b4840118a19b24dded57ab0db3a3561f9b62995ddef211a4da568ef532de2a79e666369c446d67081ef22df077ce14a123ccb37149a1569cbe9a756ac9dec917122681d79aa467a42f5cf273c74208c9c45a8119fd425ced7669ff1e3b1c633013d7775984fe7392a2dbb85c32e018f601cc97ebfcb29c98e07f3a257ad4dee3f80e993a2dcc8ebef62ebc6ccd8d9aaea892ced84ff5c97ecaa6595710a7b34fdf8a072322ccc4cba74187f302a0313ac4eeb6e1022269d67fdef04c441161e33bfb32cc20fd03eadf1f4f336e71438546446ec05b48d53990e7e535c4600206234ba9bb96d5610d05ab0018c68fef7bf22fcc62470a38b8d223f251ff3d9445cbc79507ccae53e84ba125d22474a9782c298f5ed73e42da99c5d33998e3d42146a443f185a0a83c64d459005d2d1a702fbe5df4550ad701b409c53d7f1382435c78a658f5d9a3ccb2170aa13da506d9af4b4c1c72ce5f7c6d02a5c043aa6746b2ac8658b81a12dfe6b1a49eae6772ebcf2a22064ee83882151f6a948d76c5836a27c64ab76a27d42b863213b6eb51bc6ab38bf6ca370bd4ecc82bc9edfe1b0ef70cc7cb778f8d3676f60fcc0ff60346fe4e541dbc5756f814b691215ac49204cbc3869b728887ba54754dc38371bddb90cba9c5e632cc2620103753ed7ac1788cac51a1c5cc55483001fb026ad868b6f3104ccb61bc95095453584b85c061abc97402451c16464df3d663af556b335b5f77d453431a236278eccbea0a49c0b9f64608001d4b501157ab43033bb90945484fa18d78abaeba36200b398da070fc1406a78709c30275e84d1deb33a3a104adc73200cbd694385a6497108a35641bc4b2d8055e497ca2dfd1e3778c9b70e8ae246507e8c62298042b7b03ce86940f618231763a589d149c1dff05fd96ce21da2116256b8f33e2de0dd6a49fc265d6a9d207499016ca6bd07683331685405d3077fc2e6c3420015f94fe3260f8b1b67ece083fd4de11b07b882f4d2946b7d994a53b8ecd2ba91333e106808c2f4fe5c88c59db8c223e6876c6d1878221e49ca6e2ba04d8b67549811b7620f4fa2fff5e5c852c1992feb88a3abb891af35988042bfdd86c6c17b3d18082252ccd8b0a2624aa1888081c5a09fea17f896e8caf2e13dd11300ac23b0312e845fd20b292811d2458e9e56b528b1c2f4917f41d86253d7158c6aca3a2bc4b5de3d5c9c80386a507195761ff0c636a01d7ffb38ab3ca69b5a05710fa20007843f11b054cc6c2ff9ff956812ab02f76945326cdfd470f978360998b221ac5e2736a7cf45e6dba774f20543e131dd1317df1d49de047b952122f12ba567dece9b705d1de1c6746ea92d18dc303c42c92358b2abe44429424a997a2e80f1e719de2f033da8ffc6aac96d2fa81a8ec5f78b2bf34d746cb3d727a656dd124c28bac8b1ba7124889efd562cf00d9a608a8d0ab04ec46fe83196d0ace26f554d86ef45726da394f099c0c8563bfc0c37519e6c79d25dfe578c4e35369ac71031e6e881b0c8eac4a8a2ff2b279e75700642f6e7c0963731495a002f540f469688d36e96ba58b70ef0ef4be43a28bb2de62c6669e59bd2acf02177d8cd9bf48b2e7ff33a92d6dc57a935a356d652281a26efc372e517b742d2deb07d55ff5d65a6c14124d092dd063b4bbc934bba4727d205232a1793d5aa1686ed48dcb45da0d31b28ed5d6266167f71f18de2bad2f443f4cdc9431ca4c30802e8b29afd80c6061d99ef2855b26b23ade968ff2c1538476273cd34adf5a98922f78b8bcb6f74cc9139903b13206fe566a3bc25420df18b0653ddee6a124b2e3bda30c2e210d522c14fd6d07f996345358edaef4e3b50845d57109151e118d99e275fa0fafb1f04d7ca243606ba1373a9912756d3edb205a236d426565a2ac2c1bea64c9e412685a1090b503f58b1dd946d54daaac76ec74c9c9b871e58e2044a04f75e97663d64aeb714d5365b563a74b4ec68d2b77043694ea21f25d8ec8320daa2d75aa403319ea12752356dc10968db531df33372847fe6d2a5cb12995abab2f699aac3d246e20a8fab1908547827e9c2e992204a607c6c307c375599ba18323bffa26ecf067b5267a414eda7c7fd7ce51e1e9e3a6959ecf112a47612776712f866309115a90600cd3cfeb581bcefd08372870c69939d1e49dfcf5f35647023247b226fa292d7c95be19903c72c23e90ee3cbd5917fdf0c6e03249cbfba3b09330252965ec034698ccf88e906665bf64480d8b0953069d16cb8581431579cf898db6ff1568c5da5e3cdb31636db757d1322f4627dd309135a88ed1d2eacc7a5047c51ca8578b9b50c3836e25448b8166af80506270c4217690c2cfbcecc025bd8f5d93d3862af0b43d0f3602e41b4905e6a187a6ebcfc3984943ac788a65d4a9605ba8e765a8693f2865499ad009616774607ffe31a86152cb77b4393c221754111fda029d097252bccf982c99533964a7c939c1e3c4d30afcf96efbb1c477fe4017c5c77fe7018f391bc5bebb484f29bc1873d869bfa1f143ea0a5a93783f1a63582781de66ae557b1a242687505cd582c7cf4696f0850782a44954f142e863747d0b5139c26cec8a342e6e63ac49cb52550515eba54ae65a228d89afec983ef4805d280b75a9be0a2a9aa47fef8558defb907e6700b799a2c7a5d9c3e044ea35df5464f0759160b2a79f47be0486647cf4fa6739c50894a998b2de6cc0c740a92c0d6fb63190cfb37b1642bfa38a82bb5eecc8c12a8e71809734b09bd173f6ae05e5dd66eee0e7914317769c095e1913c78f4d94b2e0372da27220088d92d6f12b2fcf561dda22d366b608a9168ba32d16f0370a494c3156a29b0ea0fa2dd6966ede432164b78b65a51412859caf0165c2f2f474fac3baec774033d67f5b8b103d05906934507930f8080b7ace89258e47118ad684a5406ab0315154f38b8f634f609fafcce7a4b6d304415b02539d51c25001b59bdacd439a116cf8a4ff3e28e036d6ed25f1f5f4ccf3b6f3976d670df5b9ab3b99a7a837e1d950a0a79701d871fcdf4960849bed9363ff9d009726703d18d22ad705086ee5503b85cf5ed469ae6ca5be275ea1a381bf7a193e853631732abfa0f049bae3b1d06303427cc7ee0599b2070ad5ed66c8a5b0c46c7d560f7e6b2291a4f39f832be131ea599ed8c34195f129658be1e5ab6fe75d201f522d91a73fd590dfc4718bb503fe50e2e322c219bfdc72b4a21223e9a233241d582a126968ee1125bb938868866f1af422f80214e882c48d3cb019cbe515264a4848c8bd2a5d5e46e207d72e1ff083d4ece33f6ac887e28d8be79fbdb06035c95b826c200034c191ae248e94f36b1f5253e9ae9433040d570818df4602d461a3186813fc6058ba4c8f3401b394dd1eb84e3bcee1b090f790e5c913c92e8f9b45d18a3224e6f6989330466c9a6bc58aa739cdc19e57b09c4a4a09a360e8e34f1c77671b71a382a1ff726e5303cc2865952d8851724a0da20bdef5c874e26d32740533775951fe42bb36efa0872e52d659548871d67f834af8451c6aa5fb1c6b791c702d036490dffb12a85b91fb5e14f5c237cbdeb0ef62a8c0ef9f469bc45450e2ab5a70f328b1b3afd694a824ea87ee9bfc3066747ca5a228536b8c3245c90c167d12815dd2d1a8b2bfb9d8d5e1f3ed606693d2affeebc37f770900227b8cb32ebcffbc9145db05200592c194ba40171b220f335b8765708e5e2c93389de47f836ad23642273bd405bf715fa37ba27d3a5708423ebce103c53d3fa2a1ccb89c8695b701a1ad096e6abcfeadf989fd964a2bbf4657aeafcf32084474057af9cb18a2f769e07cde2aca037985d9a202024128921917d9f7278b861699cad0a8e3d24df620c35c0885ac11b9b4900f745ab2057f592b24389869288bdd4ed72219579c51ac8f792fd97a9ee7f4ac3d3e06cc67eafa14744704b975236583673b43406fc39fd77b47e2cb5bda79b0fa3056de9c5c448b7fdc79f3d1f3cca7eca9e7e9c62f045a02f68550d3d861b703878bad0cacb528d9d82fb111dbd1960dfbc45b0dcf6401460aa55301356963d95270b2c8fc25b4c5be8162ed08c003f44a6434801b421cc4060e8d7276656dc670cef7347e6f3c5ac498cda2735a850275c8cec97a5a854cc94a1b765ef3bcf55f0cfa9d5bd4a921f86cf56210bde967de01362df7d7f4472e0635e243069e4ed5f447186ebc9ef98df37c081875f262d00ecb1389904988fee9810521195d25434f5cbf9e43df8eb04abe3a2869517eadae40141ff391e0af0826b04499cc6549c5f30ec45862baddc4559a9408b45b349567b503fddb48a1e8fe40f88e721b6718538ec0b4393084bcb895eff59fc8387e1c4baebc0e70204db0b2fc3b6280b5612f0626d554ec39cf036a10059583bd82768db71a8c4a93861e6c89e4a897af24790ee0434bcccd9c566b45dcb53eed95e6bea707d19b10655a7419440c368a8f96dbb0e702cd39a0ad63b70f01de51caf1a8ff11b324eaee9a9f4412b85c2bf24ba430f501ac93a0b923e4c2c2e73a141d23aa0e346ae4e4d040c35a0faadaa9b0ae79cf914e1a6e6abc5ab7fcb54a8fe55276a555ede50232d0b12d942d926e8acaa82990f5f961718772430878723cb6e5028e607d5fbace6684b5da4aa386520f56f4a54d05e035c988742e4dda8928490bbf78bfa50078b277e02d78ce01dff0cb6ba860b09471d776a7f18fa6f80469a76e30f336a3eff424354bfe04a3d5406ae952298d69885a0a224f4a9c0d80c289c748aab83259c23dd1e8df7de10c168011691d60fb16e2f16da096a2c2899f064b70fa732bce340589c53ce83e23fc7c109988b28e34f44346b6879d44ae196504ba27e41ff6440ce1939f4860e8aa9fd79c65f2665e22cbf4100c2bd3932fc1d8a2093a4019312027535fafbdeabb4c3749ac60a596389dcd9d960175fe130289ad89389c845bff40b85031177087da19bc01081b1a370543167a2b86776ea11772d16b6dcd51ec240588507eaee5db18ff1b72b88b232a764498dbbeffa33466002b49fe1448d564d0fa16023e5c43da3fe8f6adb57cd3d74c682922a886ce749ff59d1c2725a3dcaea658f140ab7aaf124ba52b2b5dac93af12a3312a5e4055b2d9ea6a5c2b3617ea5111bd7521624d66c3ddc56896a20092666dce2baeb0a3511e1e19013b275dea7bdf38f7f380a5808776e16a258146977fc880145dbc3f25e830bf292d80d4636b80a97a98d5efee894061e83053dbfc68c23556f56c7a28988af566a7abee9ce0ce5f59c3e05666fa9fa0856f5b0d90f9a005d295b4b307e840b05aff764481f2562a8056408944aae1ced81a2a608245188f100690ae231d9594481c4c1fd69c5d3631af392017af1b717dadc6997910b8516dac8813f9c1adbc68d8ebdf33de8f235091732f8e5bc857d87c37977d55c0e06888e4d2db52fa06960405b1790251a43f31339cddb084b1d3a8fd6f9db384132694a1d595386a756f0032c4666b9ad32715709215aaa390f927e6d2feee77d3c3a5c599dde4aa6ed4984fa0b6fce7ac74c01766717ba15ff2da874f4e9211e8520ab3cb758d0214b3039eb7f27d9f00439086e14878882c2049cc67106e04006e136208020165329c1378a5be4b53a0494ea254c5920407af25811d6ca08c852eba0979dd81a89c49fbdf508bef806df9bb01697a3a54147b530e6495b18f4c420c06d11658c740c931c71be018fe81731b446c30af6a86b2ebab25cc5b1b720ce910eefc6bb97585c60b80215c4ecf1b42237161563ff5f3e085e53c9b158a87e69d47a867193aea926a8cc41f35443412c74aaaadff00aceb09231e591676c8d8f2538d340b3b43d547832ed0e94a46bfbeaa125bea63ba3fc4a52c13def9abc50cc3f930f612c2737292633a9a11421dc04f23ce5cb721a4f1917c0ae80214ebc054c4d82c2868ae3d85b7b2884c238431ab7735d00f2aa0718cfcf83ae94b2555ef0d61eab89eabb8bac91b11ad349430101bf088f854810291ae27897b7067823e39fc439efa9a29d64ae9122e1d8d5db5a8a373903de01ec59f5a41cb2fc7e42037dac1ce3a14edd29cbad51e14c3f3fac2d98af37968dbec0dd89e267843920234a192a6f34350a271db09f54b6eceed6a46a6f6af24c02e29dff0740666a948ca8a81255308efb487e346a890506cb7cc342a2747e28178efc6147079b84cb763ca9636591e82197ac34b84f41b9f22446b4cad725a61f8141979696da6726cc7d4b5586ff68b75bd00500a3a076779c13e04f204d3991e4a38a3170a07cf3aad80cb276f7c1c4ab1c075c353c60fd803559324e8d7cf6d4dc05a574a71191b2437950813303c11315559a36eccd431518f3b8990540109047e4234fa08aa78c2339b2b430f05b16af818b1b7d290b92b6a6bbd984c8fa3fccdef4272e2476c12bcf2b75d7c6712ff6ea12b143f5f7342272107a827da768c5e0e56d13b1dd0d0b28b049421018683203fe07991d1f3a7f6f1d727fc17a34aeeee4bc02a276c6be9c243381a59d3472552e8e4a37794825f63ef2895341e1b21c8ca382750fc4e66f1ca2b40649a99d1b85a4c3c8a82dace15f0a2d3882b5258f99cf6fcc4260f19775c935d02d6e5cc5cea4021029b11b63cbc2bb605d3e17f9d13d5497efcdc449d368bdcf007dcfb164537f5a9c99f66a9f2d09266eac6c54e0f82b64c424896add2d32ef98fdf26b7a2bb934b69fd3001b43d0e435aa736dcba86d765719d56cbac09c5759a3d1337c11c9429e461426b218aeeda954bd21097b41e7edb17dc136b901ff5a54a0134dda4f6715b7b52b8cbc2a7fac4f4c433c6681b8535838ccdc92606b609acd3154e45c949d7e3d8ba592a59a12038939409a6a3b2dd868281f50fa28c6c353864d1592398f6375412dcada4c21cde38c0addb6fe7b7019d2189400640c8ff9677e319e99e643bbe7e77aeaa169982ddf41456d62bcca7003697f4cec0dde8a785e44805d4610ca42ae3814314a5965cd553fb21424d5ed06cdfb3a36ff278349d1740ab2d49c7a762d4899cb09af04a5da9afa15d2a1bed42cd22162aae34b1e08a2f124f9ca9fbd4134b7e4c9260e9597f781df921e3904d870c0c1488d77e9f313a7bcba54b19938d1dd275661a1f55a03e65706217aaab838868340cbde41a268da5cfe20172b9c1bcb06453d357b220632f6d44bd3a54e16f3b2036a824feddf80ceaffcf62f710b50f368d9fceb643b2004fc0b7422d7e8d6564ab303ee2d2af0902df0523f39818f5a8b345f1e0d7cc7f42be003c4afc57299cec29d591d38e5c0ccd57fc9b79916da9cf13814df1e8264d3b0a35edd6011a7a90f147894768dbaaba2e1da94a0e0bf7e3f59ae36d0686d3d7f599f1de590b6ec96c3b8ff9d34550e388fa7088757ff684df7a8758f7db2bb1c4654f9c4a7686a2ae7aeb4ab89aaadbcf7ed5763ab784b41028dadbe573e9d57e4917d713a43218ad96cbeecd41e8d424811eeb147bd42001fb7a55203ea1577011535701ff87d2d5dcda9e78c1c9354337b3307c3d74f68f4238e4cb809200166c3db6ba803de3fe64b0a5333c94b79d311d53bf4e5dcf877d12d3668ce81c15cae7fdd01d8708c91329d3497758d35b0f5ce578651f3d19824c8d67bd11a54e6ca31502f7194f0186dd18b38e78856cfbf86efbcfeef847bcd31301241c37124f97cf90435a0c05af30f3145239bfcfe4c7cecf6eb39faa7d303e716cfc5e80452dba5f4824ff2b63272029c5c2d9ec134a67c1220051296273b378d998a8371a5d72c9b858a31725011adae86459d1c11c5e827f1efbd352dfd77fbb6d34cf7993cc59d4ee40090c278ff5b3e987226d838283729496acfba464fe5d28aba4bdec4febe86c4672d86ac9efacd87fbc1654e6fdbaeff61e0a5d74f33ec8d173b06c75dec5ee5ce09c97f8192b659446089a8946d1b57d5a5fac464630576629915e8ed3dcb1152532da4f31d2f6cb2fd644ed10debb6fd809ea210a506f06f7d659f7ec03c248d5d9473bc140b9c4f767e8031268b6246d175469ca96b8c67a1be249abd846c1e3d7d1609231ba1012055de18669e88db1311b359e3f74f7a3cf3bc13bf11edbf6d1400c33283bb42c1c128434b3a2270c4d26ab20fcf0befe7b9686184b4a2b9e9c704af32f1742afd9ec8ca9b6624f8988a64af78c3bb46cf6fa6cd41ba2caff6fb05383d9d264400275ce99c79cd93e641c9846b4b2e670e48dc6baf225c3412784f0339bd940dac66a4db841abb38dcf635dba30f72de6b61a807962a03dab5c4425a897cdc651908248e75bfa5d11508b04590911cb27132ae9cb538e06a9c3979fcaf6fa96ec108ad98a0001412926080581bdc69ebf2d0b530e2484e3bf9616a8a98abfc3348b8147cbfd8f889200fb4d5132993e2f9df7acbb43700130df30b4a0f1e2dd9b6a1811bb22b085f0f7e173ba0cd9b30d513cd23e17d65df6dce706199ebf8411ac7be201b4f2944fceda7177c736dd7e21608236ffa7b1d74b229d1010d2e2f655ee120437e0c2ca4c0142a8209869e8fd8aa21ddcad63023766575d702c94e5609d843688dd07bffc884e263bfe74fa9a14c84c35bf45b24374e85a0168aa37ccf85dae61a3e64496c903d140cfedd1a0672de0c1d85a11f85bdc7c6bc7321030774c47ed708baadb0dcbf90b8d5925a1c480f2b7e4bbd8d2907b6167911b66985d6d7b38cefe77656072ab2556775853937527038453ec07470daf80e338b6857cd326c77f69e447f85b8aa3d5cc637f64965b615ae605f4c2843e162e9cbf73ef337f6c639a5ce1b542e2aae465f14e92e635b8d8d2b344fbc076cfdbb142f489d8da562c1ef7d6ebb03ceeaed07dff826e4a861cf29642fef5f105e48e0bf0f8a19b624c1052b8983a13f315f9e581a679f10fa1dfe952eb3a874d367f2ddd716f000e85913673d23175e24da7710b1ac4e94d1b1f72b426d84061d74b4cd70adf252c54b5c27c2856bed277b931322b439bb0296abc5e80af31017e3dfa8c1175e73fec872bee8de3fa513dbe2e4565b1635ecb232bd5c38804fe0d74f72e9e85c42a50bd54e3ee2f460d12a438b4e12c303abfda8f9ff29f2c207342fa2bb49721d652d71f9fd7e52c0fc25bdf2b9fb50d99a46e68c99845dc404d27cf0e6830d93a7acd9f11eb0689622b8adbb27fcb084f4156c7f4adc55570fb04214550c1b480118f5ec9aaed049f95f8e722a70e1fee0c6a5c8dd21024499539a11a32a870ce35f8e81f7681b035d6647bbb6565818de5943fabfa42e1652738a84e33001d4ed90efe0112d9ec2cd6f02db21c938eac60f81ecc840a974e21305c2b070646c220a439558af0f9217c59c3fe738c45459e694155ed8917b3ed2d7ad578798c39736c7481ccbdf3f8f910782394605e14f4bdd65e0398a462ec5c4587469f088f1683106f32c331635f91946c730d181d752d1f96621a2e83ae1803abb7d3393b2c7a0cc81d65bafaea84ff00540355cfe30c7bbe7cab3d3fa00b65f221b9a6adfaaff49406c679947d323e68649ae2847426953a7501357ffeeeb41b3277c56dda3fff92f6e7875e14d206fe52bab583435d5205b3fcd50c0afc16e0d5b7153ca5ecf6ad4857c54bc8f7b9cfc596ddcefe3df6dd3cc52e5bd4b9a6173fc245e9d23ac685752a18c87928204cfb6d4123aef3ff68c8487526948faf1b3978b67f10714e56c1888fa646991f4256932d9c9ad7daf41e94984266531f5092505c0474d1dc887c4582c39b27fef06674bd3d3abc59f46978f3dc256f86379702b856ebe6f8767bba25bd8762f92b4fe1a9c9950833b2042ac60003ada96917b40b5aed7c985d1bb4eaf77801bc7265ff5fd49b68cd4f07ffce3d231dc57ec1faa340d87d722a00db55bc6428e9fa5b5cbb5a9db391ac4aa5de941ccc994f1b0952f75eb16214d5e888c43a1dd7d6aec764605a1b0163f99ffa11f49be5bfd9fe38887f19ef75079eade0b65a7d447de5d63769d26cb4e24c440f4e9db4b1481c8bc71e75d55e46bfc8fcd1e48819cc01c4de18fc01fbb0bfc19636faf480c9063bfaad5d8cfcdc79bf423ecb41f4c37e6ba0e2941209166ddb1db304eb810a70714d66524fe49bb4ffcd048c3813cb2fc07034ebbe600e464902a1fc1351e3de7a2864b01f8b9c76f8f485267b7bc9ad842a05eb74e6757842c2f71b043936994d6827cdb5830e848995bf11180d28f4000e93bcbd2995d66748003fe8b1a084e85f20e482bc36cebcc24f8877e50fc4c68a2d039aa79c61c6912db31b506bb22142e1e0207add6d202dc91268d6c4532da305959c3d14e2eb6c423f36b468ca85c1e2d92d1079c10564b563da922a511bf0ec0a454d554134ef33091d115436e42856999fb9c28e43c68db38f5f0410c2d912cbd89309a5108915c4ed54304ddacaab05add9835f0535368d1927f452fb6772286945b2458ff1896fddcbeaa5d5c9ae220078cf4436cd69cfb545d236675fe25188d82e44552766c046997ef5f05a22d7d15fd3279fb10c635743a06789205ddef8b2b8a37a257959a4bb17bf2caf9baa3ccfc47926376909b809de9814b5eae3d13565f3cedc05358d648a5198a35ac2c081e53c44d1a7142d4dc154969dea4de9fecbcc67bac245b5753b6ca22dcea18d2568ad8075eec211c4511d8374f21ec121f5d9292a747f127ee456cbffdea020c168fd3bcfca563bfb8f0d409f098982503f3f3074db2c9f41249f8c4159d8f935af1a14f25d6d3f800c13d00f74b3be48e080610fe982067ca5ce914465ab670bdc779517db48320b9b09017f6960536091a8000b94026e4e9155f3e4d588025e2af9dfaa6a2254c560806a356988272fe449e67b7cc2616bd5d10324fe496560589b4d38856c9887d531b8ac14c48e0a53f54e586f1906a10aa764bf694ff585ca5687a2341c9690c2c516f64f84a7db1cbca590ddd7a0694bc634d2ecaa6756a716da873150c591cd33aa8d45780aff78b50e5000dceaa2ace8e9113c7a9b5d72fec63c3c7c2b4605010eb076428a209d612645de3529309210cde6d142c4949252930b216dd83bbc16b7e4640fd079f9858314ab96fbbd3588cdcb8b76799a2466010a85c3d0a4bf6c45e72d8bace70ee9d1a610cb88a1a1b88ae474faa1f70117f1039ad2fbc3420a7b50d0eaa4cd840976e92761f6bfba7f2e60008474492ae124fc359aadee1c31ce27759d55201f83822f87b8c323747e245b498ac47477dfb769a607bc0e46be0ef22c267da154e65465dbc07a37838c0c63a0f024995c633a399edc221b930835c67db20058a24d1679806ceffed8c83898e11dcfef8571c2bd60d3c27a6925056f56b06a805fd933e10ccc71341ba911878d03fe782475483c3cfb926c8fa8cf236a48711cac4a4c873103d7a0c9967a36c469ca44c12f6d0990b7241430256cb1b1b377845653ed8579ee64a00672df88c63220a04181b357795085c391dcef0a4ba75f1c3cf9bb22800459022e12744f4b61e42367e52ce0207ec5d6ca87b35279ef963effa3ed8e436dc2a0f90a690aeeaaa30ee44ff90d230709553ac4b31606cf1198ae21516ba84c460a68e370b892d6abe3d21f27d61454dd232bdc8bf14166e4b558bd1fe76dba97b3afe8cab119e43dfc601b8c03abe9f93d4269483a51e9e206ed0b76fa2a1e118c0b5225475cf05f5c246e5c8e44a1c7a0444f71c536ec1f5deb09fa0bdcbb6f7069a8ffae25a0a98c29972a1935fde518baebe353a670694b03ff6520c8b9103bdf1a781c0d3d1bcbcc79b9820bec64d02adf2ebb559a74229d95e3b995ce1f922086f0025dcb7e92ea9033176246537de8947e8a185340c355f98c10b0a363f076476e471675fd4d3d38e037fe4088101d7a387b0dea526f2979d45d8e0e67d467f52f2ab19cf0792daaa5bb7726de5d63821e6d041841566354b7142301f0869a084f406c8e622b51d6f3499185e87ed61f18eae44506a09d1ec35dd514393c6e163b857acb7119bd0519bd02eb4023ab319c47011e2ebedcb8176538ee48816b13928d65e6de3db676e6ea4359690df6b335b0ee7ac08232b98d3e5aa3cf576fc1b8feda8ad8c831d716b210683459e8049e1a1567cf31baa5aa0cd8e5be4e3c8e4df3dfc0425cf16496dba0b1046237cf7dba49102c4bbaa7cebc295bdd60f5f2784f1897e34dd4e333cf674b1cd55b9d3e313f19ed551a4bcc090d12e28dde0ed9030d7f250863c4fdd49fe3585fd417a7074622f8f24e0b84eb9ec89ea33bff012ee1b640c2fda3dda317b22f09b5e166c4ee2a6109642178bbace073867dd7946eaa6c2fb2730bd8f1e5b0919a17e4f6cb3a7c88d2a2715e0e3854a248378f6d0d01ac3f192936bdacbbfaf86ab91c03874bfdfcda69becc714cc4e8e02d790495b5f6f53a1ebee9e9f81126f4747c616d701f8df17d2a61c95e309f17b100204713d52484e262c2a1512ff8d7fb3a51381ea68695f4a5d1ac34047d2f5348425e6cd18a023fab3d9a9bf429c702674c508bdf1f797933abae37898cdb5f02107054f32d17679fd360c57ae4789432e05ade29db1a63bd6af8086df0f25254d4d2b3260f32db1ea26247a97e5f1a73042c00c2af929791bc980454a6631ad5241e0aec882896aad9f924cbfd1ef63621f8e07cd03726b0c1af08253266d844002d0d71263d6dce5b88cc098fd3c61eb16647d0663334f0ccd925482d63492f22794c2a23d23da0dfdf14676bd98274d538ee87b05ffbf1191bd8b37f1b502ca349d8a2aeab8dbf6c588b7df9ba6ee5147acddc68330c222b50626cf03b73a1936e93f0182ef57cf91ebc7270f84f9b8e4d2781e8c4d880c112ca6b02e51a11ca138a7bd702d268d8e42bd2549cc80965c6aa8e5987c649f91b546ece294cbdb5466b03a9aaa572bb189474a306190c431df962285e0e98d8da1263871c830786d30d66014cb17e5f9c410b9d50e19abc922126df64b44fbfafa9a55fda455f1558b6db06d5cc0a4f3c63f97f6198f30902b9ee72564f79c006c5de941ace858000b00f788212eecf59589e7a9a622717af157b291f9cc7a3efc23840214659403a9d428c69a7ce455fdc277f245ea391e368b51a98cf0d0e268c67d382da81837d3f4799b9cf5be02e1afcae6518983452a28ca34858c0bca67933a5f2fc127d756b0dd2e865c40395ec85c35cba80aee102edb1830a735b6f41ab1662e4dd5b03d6b8e1ecf1b655227a6ffa2b44b1c63ed8383ef149f1f4ef58b58a205761ab6a25763345e45274fee93f33951b28011b1f234af12892c921af9bb93f40fb38b535537df30daea8b3b7954e091fa41fc6cceb2c52501f303eed5c29f5bfa0609e69d24cb859086b662275c825085d7810c5f6624c9ba06c16a7c0a17ff1c0d5059628c43218b098bac392807c4fcb76ab20daa08a9335af518570ca2fdf00876034a2a81648d9b79f9371227345780f3d0ba465bb48f8a50bba1c7a63db8e156f90b37edb0e2c3085fe4825c6925f9a962b085960ee242bd7acfde1751334dc8a877e9f115d2a1ba2483c20c48087e4d20902375ba623b983a1c19b774cf6fd5ffd280624dc37c83f360bcc37d7a4ca1892d348f54a3322088ff67cec29f5f950c00ba7c794fd43d12f73d00ac38f306ba750d32577f74b960a41383d45f89675a684fbefe9a7ecad387410d76ae9e6f17bc4427c0bf862e94b5f91ec76412e74d280ff3c022b0da797868c00d08e8f069339b8351314570100670445b979a0dfbd92657e76c618b1f3baa8dcfed9608c74d903333c3b0bc8bbee996db50e9f2a22c389eb0dcfa0814b3f3babf40ec3e7a088a3d043c5ee3f5ff721386730445ebe4747c9e88286f5ff9a5e941ef2bb000a455501bba19fe735d3373e5cb970688021b84ca746ebcce4267e727542e6492ea764a4284e7c21c7a1154bb547c86dd58ea873f891a34e51b56f0a992a3ccbfa5acb3b5ad7f2df9ff6b9f2e5fee62e01b39f4d47e4ad73d2bdba1223def75bb9155e5cf0354a0e7d0cb3d242a37ba1c640b73ffdb620ec95696ee7cfc99928d8994fa2f8017175fa09e8c57334a7b631e1ea3f026eb457d25d922780279d07da0be597f891c9c7a9175c0a0c4965f314f0f5586f0199b1afb8606006afbd5e5af973bd7eb4b6492691599fb3a0ebe090c2c84f1195cab5964af7df13339fc558fe03f42ac0b7c2ac65c6b3775df838b68fadf41d757b71c82a0d0afb85792ed6e5e8d4e352a1b849f8a0579686af14fda476fb246501bd15c21bbed9b561c9d847ead42f18136b21f02b11fa4e01c9bbd4238ba9c88747804dde265a458929511fad9bdaa870a2006ee737240d5ecf08dcb4f53952922bd888417130643c03a3fc8aba17ff6eb7397cf9e4763a1de7139032a602f4f6deefd5a8a1c9ef1cab93d05a634589094389c930d341508a2df02a1db0aff298abf2348e53a2f7376598d45d3b52e6ce5085eef21429813533eb3a68f6c2800ad4b638d7f2fb4ed259149c038486a2616e1b905bb83347e266bc0a2bcdaeb465c793c7eb0174c8fa4db3cba4b203be3b88005504852ebb484e6de72339344af3d0e1341968711619ede4a55965a6b63ed59727b89ee34f36c8127a354f989465a4ef33e87ca5b7f849f6f95b746714cae28c5909610f1fe50df887899b06f45ae15278bce8ebb53d75ae4d567037313e69a18c5ef0c6ac5d5ebecb1a6ca9d644e00cd3d205f4071975c5a75ae7a7d49c82e4330432755165003f6940f1dc4769e652e3cc763619b8a9f9d0af08971ac4c584d1732ac535147f6de3b01b967380e92a4affb5f407d83ea32c3d9b9e66d1280f8142e24d857bc43fe8e4b9cc1bc237a6007fbc1f0ff48ee95fbddf748922bc4b4a0f392aa5d4dfe2f8893bd217c53412e6bd24cc8c1ace79a917de9024e0031abf428a5a3092b24a0774aa617e1b91213d28cad17924336a9311c1c8f70c4cd22b47045e65a70271c2fc5018567e3cf33e0a1359721d8e18c40e21ee26ff9bec0143b92dc90e8421a6848edc3e61396109a31149c8de9b6cb9a59429c9148c08a008af084c3f67337589ae281491c4a113605c3bf6a82ef59092be016a4d6fa2eea2b9ade91d95dc9882b25e4bcd39e7a494d22b9ed8620aad92447be89342af100ca5d3439756a644b1215ee194d4e82cba28827ae8bcdd2e855e374a81b01ef3a8972551e14b2f7a607c7bdfea04479ebdcf5f286a0a0d87e3d0d6b52dc6b8716fdf1e3711cb444cc4444c54d459ac4d34b22ca1be3979ad84d18ca68b384ab75ab5d54ae823968acd168227605e2b350ebb637aed851785aa0fb59dd25a572bf72eb10b121212121212121c1a1a1a824370e8058ac95dd241b34a57984fa983d695fbea88d2125397e88a42bba3c8a86f805ad1fba86f622b7a2345ef24f85b0f9822d215ad9d72da6058270fb2e009ede9ec8e01b62b5567ab7876cc6bb95dd41bd47d8c190d2d88fbd05f1fd237dcd2b1424dcf4cf711c1a8d117872a65b42a7d7a78027b3901d81dd073c0e6985eb39b764aa5ba55dfa0bc9c9e99ce51bad5aaad56d46379437a26077b8210fa98736beb0f756ed947c761cccb9a583507816d515f6e85cce722d5302a3caadf22e5b00d4324509604dacaac572bd579b08db3faa676ad7089e8312d9df64d0532e753e6ececfdb0671ea5dec2afa3e199e845f8d40a2ba0e4f21d63b72ec0c036fc51032fc00053bf158e24eac74222959676c7fce8d1736227a516801f3eb2784a164df49a15fc57c24724d80db01f54e85a503bfb76a99042af672043131831e39c724a0ca379950119c25a94a3c500bbfb06b6a0d1aaeb315dc33af82922ea0b30ec8e7e66eec17390455a496196115245c25e68d0a194d57ab54a67b9cc8470357d6b75e9ccf2a0333b4b764da8c354f697ebf9951a3d5a6550d78bd056662194e242d1d22fa7600f50760a7432e5173af4ef080b255ec085949bee197a43b18d3dc3321fad4cf1b5e92397cbc5a4b52ef3317e4e021bba7ba4d71e6afb07a4e6d7b1f5d9c3b311e83bdeb00e97611434ce3aa2aed333aad05626c69652ae1797392889da0d8181895dddec4526aa13d589fac9a1117e374775a27e957d865416c3c84008218410d2fcc0f13d274f0c5599c5109ea8d413d14909b3b8a8ae0f3df453d1d2facad9a7878359bca2592d51bf55d7309329455a8ef881094ac83798a87901480ec56116e7308b1d859cf839363407a762511286ebb5e40c2e3980f1c40e929ef8c28b126aad15935336520b38be554a3f072e925c192209d31e59b9a24675b435be37bbbd11ee921cea56a9189d4589d1103a94a194444329a535ac231d2924e950410eb0c856b522e4f40cd4302c070a27451f24012df5686372a2f43828e434a5b57609355cd41e9428c038e2c86b890d922061586c39651c8228d265aa5108b6608d186de286450b0b96388210c26ed8b1bb1bb61af5b56489ffbc9508829bb449bfbfb467588859ccd083aee9b1cf393d28c42c57d763467ec18520b6f9ea11261a4589553958909cc0c113b85f4f1151774708bb237e4bf15d05cce2834e40230db00de63ab816443ae6da9c3306b9942089b0c796fae6c237f00625fb0a22e9af1c01614592464a22a5b919326a52eff3d7c00b4098d59967ddaa67da574a56b0e0218986524a4b1822e8bf158d34903146116d8509841076c38eddddb08647b6654b0e1a7466b11015732618382d7b6173a4c888dbbccd14faf00d8cb3c3a0f217d92d0ccc82f17c501eb7791f0c3a791f3cc233eb7d9087c7a1625ea54c9106bd2dd5f614729885024e5ff1a012a6ef9ced733aac153684b23d1dcce21520dc97106b3c166dbbbbe1228959770f45c87ea134fa287b23db8baa905238f17032d3dde66c67d6bb2211881e2e8c59b01f54762d08faaeb3b74460548c1b514da4f743a4d5481b9822c491286ce8a0c3962d4cf0f36791b92589dc32c40c33892d425b74e0867e055a284184b444121bb43a8b1a900749d404c114cde092c39c73ce2b341d084929a534a32933830b217964860d6c88dee74068d1b890dbef0b082b804092e96e1684524a092504646bd2cd81a4e3285a901f1be407ffc2ddeef677b71b6e4435891fbd1f22adcdb102285c826c32df48600dea1791963d22f54d44ea5eadbe2378d41ecb30ea4721c7c2a92e12520b3cb3a27e1387a8f7d160de2723bd2b98237b38bced994f8693b61ab4202c4f090bf14acfb01bf50c47a266610594e3f0c6c54820e51c508bf37e384f79949340ca37ef67f394b73d138d863e02b0a5f97210802d9487d3659a9fbadd4ec9278fe252cf78675d1ca2b4d638d44b431d87881e63a3c2a3e83046473a74e94524a4e7d8dd64c298dba2ad0c16f33717e558587d7029fae79f45fd5652469de93cbd18fa40bfaa328c2ae3510ba1adccd6a1aae599238b46cb94a62c96e7d81007d7f0002619f06ebe2d84f04a951e236c69da7215a08e793f9d9400e659cf347b43c8ab427d22c070a890c28e5d05199ba119bb5b639d42075ac0947124d65585a9358629ca88c10896ca20628cd6c4a20335280d44a4aae042a36c43b767ba7d841be64f87dccef4393dc3a1f244afd3a52784031fd0cb989d08c33718d6c1f08c0f5374ffe86ffeee583fc6ecc0077ab9fa9f908be8f9f4b7f3c48e75071fb962d6c666231a659b15c518323364ee41a33793621b158a49323be73226312c4297589c5146e8b1dcbf1a1a14d8e6a391bfaf2563fc5753dd945276ea7d98d3cca8faefebb8f0d16747a17fbcc144fd366905d6f151e8fc5a1205550243e9a76bad9d2dd97559da1c7369ddd604d6c87c0db3a6438fd533d0bf392464844509299d4ca69377c9e943faa6f74ac7999302df680e63c4a4c4628c724e27a61fed8efd9945ad624af1733ea91273780406314b7a10df4c293c337d1a316b722ca8df1c9a43f387e99c6b9318e529d7686bcc8bc39aca07755f4c907ce69b6b736b52b96df3894fe752cdf17074f0c83cd37ca3b4c7467dcb9cba77facd53dd4fca69cf44c845605b5587caa28e79f4181618e50c5de52a4f869443df56749cf6a8c7a731d4dbd60e15f3a47f7ba4c2f4b6134b1c147d14c197bac4a4a48e51aa9215499c642e05201f2e3a4e6f2aa8df1ed9601293580ce6c529b72d6a2fd34f34a0abbc88355ee5a8203d30df5cd350beb17ff89722c62802cb6fce1971e46f3867db723ff6e8531eaa5339aac985fee80e75fd867f40601ee5eb2ef48bb7035de51aa773c355de87f21b5eca672a3a876aa36ade8ea3a13af828f8d3ceff867f34854239e57aa0b0d5633818d703dbc738dbbc1f9bd747a57cf86ff804f2aa1b9c639c0a2f1ccad7551d0fca0302f398d7d40e74cc6f7428c7e9da018189c13c1f9b7373ce39274cc7c3ce79733b9b73ae719b083bd4e93cec37fc637ff1f6f5e9f4a55b1df61baace3f3553a99437d72385eafc51aef2e9afa15428ca36289408421585e23a76981d76ce511d8dd1e15ce59cab5c53752fde07f42fa8eeeb71c355dd77c3f3b19b23e5bb3950aed2fca5abaeeaf653dd324b55bd189d946f8ef20de5619b77ea3e208e751fcc32a5e0a6820d4a7737d35537abd9611af3a8d6a575ebf6511e6cb350eab76a1c4ed374ca2da600f4537a5b022e5cb468b9e28a2953a0405161f1c597be2f1630794a1bae8cec403bd4a40acbe9acb3c7b33ebfe9995c20cb799ffbf60ef7dbbcc8b3be0f6117a3037d73e8db0ff8a76c6ad1e3598f1fabb0db06e4633f65de0764fb9353c7e9930e3fa45eedb8cdfb36f6930aeb1bcab554ac5eddb66e761f109f5307647b76ca33d4e35957817d3d9eeeeb3e94c3ad8b9d4d744edb3c7a86ea806c1d8fe6a76da3bf798ae3bc75aea3b183fe387dea787a7e3eba07c4e7f903ea6920fd9cf631d1d3615f5f6757a24d51a5d1d123cfb42f26e56504959fd37b19e104f2320a07e519f6866c682bf34188d337d25b01097895b6eaac1bc6183d86defeceb4677b845391a8dd903992400b809300b460d78db48c623395e2ee6ed6cc047e678486ec3025a594187876d811024101e109664c1b8b53ca19b1ee1333330b76fb535aeb6ab5cc98195ab61bb91ba646b9cd28753e8c52e5c328353e8c527be3d2a87046a9fcf0895f9f5166a4ddd8ee94bb72eb9e3015e6868e2865948d6d5aefeeeeeec6651d6cb3bb3bc2aa68a8d0a1ffd03ea6b1313bdab7c34fc90032cd53d215252424e8332fbb09fca9e3ec6308b87bd961e09bd3f1294fb1e3635b7a0bb6a5b7605b7acb9678dab2269c1a42084f9a7642631d6e292456274206030486ca19b19326671ca2719133f622edc629e1102aa7f78da0a4952841ead5aad69dde1e4fd33739bd330e8561534eef9336b5cfbab7300540076d653e86dc5102203275795e9a734ecc0df0944f425ec77bda5de03bc6e8410a02cc4dd093e945684b6b180db8a0f44277fd116be14aec50c0492187870e1d0dbb22abd6131f1898b48ed704e9530498469999e132c6365162372808d94bb6b134f793b6c1ddb1be9ab6a5321d7bc339c6751b06e429cc538c528a514a378ed6ac0e9508d9d6ad9ad49ef1018661189665aea15c877abc39a46319e51a36d531a4593dc419ebc26ddd42cc211c629f1e1f0d351c3aea1bb844eb6a846f043e499f06f8ee5878adeb9d91be71da83e075e863de777781e7381d87cba84c85ec3f229f79fcf54bc828b6a1190a84d4465320681995d13316c10ced1413816d01b54ede4f4d1023106afd10010105d3dbbb1d73acbd186f72f288b4ab7572a00722025f2045124aad939fbc2751fa6c5dd3df9e7e355dcf0fc3e6a4d8571c5615864184a46da82f6df01635c5032cb5febe9a70c1d4a4063c54eef7d5e4a8891111486d68abd85287c45831c6183fd6a7432b82e346d96677777777b7fb84a451b6a1bd4420106488146ba8500d6e11c5a9391be615223cbb74df5ae8411714fc8046941ca48cb1640c09a3d4cf1fa29054978950b003ac48d9d2860a5c261012030a5356bfa8d091787ea6b436f9b0c51736e80088334ed0ea3090c0a1bb9bbe20a91b01941c81e46a4167f54d74b95a4c943cd33d0dd1496de4c43a7619814b23dd81127d31317243dd811011af1520fd573d7221b79da82fbf2f14f0f09ffb58c1d16be8f5f1f7c5840c160c71611e6481195d7469615e4c04b685438abaad05249921a9b52dcc3b729432c68ea74f8e9159465e8c7da02d9118205a4f6276244f8c0ef4e97ae8c5fc80de936a6ac23ce9fbd1fb814994e0a0a84564a3c20852eb09bbfbfcece86f5f4d47e33ad2a503fd5ce35590f91ef81fd85e7a9fcf03d97e42a008590dc802d886d041156933f32085183df801102e2e39a061e386088a31c6486536332c8b3176370c1c6ed472066aa5a466a9e663091638609283273518b18325519c8ad4954dc418230a445dc808b2d274448c565cfa88c56da93290186162ec668a54aa00be00e30545d460a305556cd0410fe4532206e8f0248729a21c9d40098c2a3d6c8143105d6cb0450d72b0044bed618c16dc50832c3cc021888a1863b442250908299608b1c40f5a518b0da2c7d50ab6be15b3b8ae927e68a992c49325429441462b7ef1d15d004b50c9c33653c48551bf5a5732090b005b2ac30831daa872021c96064b5440250f9c095054a1a2c90a2a6db0e1c5921c52d3638c3156017cec5254d44690133182a0a18b25357ca41f638cb18ccaec2b3e025612769e9c208b264aa8a00a1fb4a2d2c72dd9d2487730f30eed7a28a5acd2576c03f372065cda28030823485f44d1925cbc94524a295df22e0e37925869a66559b6b03765f43055f4d0a51add9b72f270775344cc82f1068d62bdbb1bb320f30cb4edfe7c626c19658cdc2ea8f1e1f3f6e030ebb1bb796477c75fff81a7b1eaeef689dfc415c3026c49872de9096d2de931edf0637452df9efa2622e86623c2ea28df343f39855107ce94d6ba5a19618d04000fc6bbe1bd781f5579d5fb683c94d7488dc494791f126ca49ee2b1ea436d26368227b0d74c1c83dd01bd3683099ef0e27a5e622116da1c049fde4eac4d4cc1b2a64f10bc665af9900894ae6a0d61b532dabc78b444ebca63580801040fc1b7154227030d21f8ca7f565e0c0b20f8ca579e0c2b07c1b7f51382afbccaad10021008dd9339271324824444908808120de9d2a54b972e5d90b627b6483da4f5d0a987568ecd0fa1ebd1d03efafad67cc67372b83e75a853084f1ae47c6429f8dac933d7bec6e1ac8b272199c34e08fc2c8bdd28e8f5f4c7f8e8870e3ddf1cebf06997793cb4876e56dbbd0c6ab4fba8c3d34f6c864b03fd73005013bf4b037fbed93315baa7bf7550dbb93dc743eca737c5e5fa03fc34426b551795473a09ffc9fd231bd81d416abec93ad082e1d077bde90515c9d19a2e74a305817c4d0e56089a2a2eea078fe4e0a3ffacd79ef1c10f30080206454ad0355734620443d0b064895650911c2de842375aeb4145b885b910038418d0fa792201e83f2bf8cf139c9e8912980ebd1ff76a66f68c1ba46842861645747183d6b616a8072a4b2449e2c495176c69d5b8cbd58247d886048844086d77c7579372787a192311f9c5474f7e138b8e791f98de371d48bb80e9a5f781a50289f7fd7cf4ee960a12fcf49a508dc87fd05b3a10a01f7e7e600141471f582a44f0717a1f582d2fbd2422261060e589e84961f2387e5f528a7c939f7522a9a6a6a61f5a591317085bb8a0861db85c2dd8d34d4ddfc425001d56c08392203e30ac966fe2428095275c4216107434c4b5542069ad7f2278f9ed2b064cbc0ffc2634ac9656b6decf3a910f2411228b19d670b95ad473057dbc1ebb76e658408015279a585075b105116dd4d0d4d45ac7e16626b48e11428c4a59a59c3ca50ebf629684dc945976747b8db7b3a6b767d3e96cef5508bb838d704c4ccc019bb19374732c93a1adaf4c94bb037e09cc92def435fd519516f5e452328bba741dbbc3370775e95a577986ced010be6f563c435dfa6a9542d81dd4fbf715650dd8f4fb8a42e571308bc6603198b7af18d4f0fb8ac10cbfea899fc9cdb1bedc941ecf1e2121bbe89b23fa2f71c5b777e47a8604d13083eb7bbe10fc808da69f1e03d04f2f06e934888619be07367d43be77a8f70df9f6e83ea6e3740f6c7ac8e3f5fc3c141ae61ae47a68beb974da71bebbc3c7e9bfcd355fce07751efad1df57942e2f391f1d3bd7513f75f0352dcb9ceba0f765ecdab6fd38bd6bdd8683bab9e6271d9a2db7c39eb9dc60a7207a8d8c46a224bd741c7cc32dc983e50cda2e2fc30d683b551bb3605c283ac6184f6c457330cc7960519c3ce616e09bd8c2f60a8603760467f018ff4a8ac73019ac8bf62565e93ffabda8a0f20d24c2000bd778201a3132b2695fd51c6d01e044dd881e3660512030e44f6b677a761a1e46de3728f45c962071e58c2476288118ad0541d5aa57fd58cf3a584643cf4ec3a3b20ddf00bdedf76524f4d15bb9b4555951792b80b62a1a54f60de583f6fb8201122130183222c60d58ca38d2da4d83de4c99dba9f2e93f611a9665de97f9c933ac2eb320f72316f993633efd996346bcb53cc3c2f1d0fba637840df0911bb753153977f83edd322f8368574551a1ea553ffa301052c2e8d971ac3a551415fa7e0cdab2a6ed06d401d897124457bc92284ac5448750464f077a7b4fbc92500205e9f7c5098c1e0860ec61d42864c0c90f4db7e559af87884371f2fb8272e40ff0fb4242e95b9b33667a3ee04f9f1e5c9998024d6554dbbff628f6c7f65aaa699493bddd210f0e3ba1adeaa84291f2ebebd0a35a4aa5446d67ef21f25103da7277ecd81d7b3b24da2b1244df0ed38dc67f25380f7be465d6c61bf47dbdc08917997d80db74e1bba220ba94f1d7ab3dac9cb5757167a0f730e6a0053d816e027bf5a2fa9682faf9c4df15a633909af71acc8f68427d769f3f4b6f639650ddb7404ed1d1b3a740bdeb55fadd8db0a6678ba8305bb7db65ed42f71468968ae9fa582c9a12a447169b50c45b352cf69a086553ad82d6d414113d7b094199535f19a8847645bc0589d80476ae8d4ad40d95b00d1326cc824ca093d75e503fa8843d9554bf1a244d34cc7938cc8a465a370e01a27e955b52bf652a42b2316d4a2449211798569080962526289552a9700ca6a54ea24195439b23074b24423da3833b499ee192429b245dcc4a5aa4a567cf4941090f91899138894d60a74a4c51a1d720d5442566b1d7c4259c236a4a98d2ad2e3ab16822d30c2e5c69e188a367b700dd7068d923081de63ca9df5e6961db6582a3d905613e2b8cfbc4bab588b7566c023b8c41fdea3b0a490daa954de0f582c8f789dfb07a9f4ffcac04ab0a441cae71d61144cf4e53c2dc66dced086bc2751c718445c32308eb6ad80436817d865159417a481cd0e9f41ca408a30b1c39599a60ae35d763195b0c426f023359abf9adb085fcc1158e0a23d178b5600c1db22847070f2a911d070e29b3bb553231ab4b0a881f2a0bc2d40cea278b92aa2cd291c434059cf43113ec2ba934ea57c3f4ec3525c8246ac4aaa80546cf364026506098a8ae60188989d80812b11154c2529e8f20938e8b986806f563a2e7222276c22c864f8a8a889c1071511b1509f30f4c98c8c90fea47e936a920ad742879761e39292c96e9b1db6716e621b942920a3d8af342c7cf35cc4a6185169a2687fab158d3a361b842ad51bffa304c720bd7ab0541cffe13e42dea4866c3ee863de59c73cee96d11baa8a81f4d45513f9a861654c45bf19b496fc4261cb109ec5a52fd5849ca89da37d1a1111321b1cdc74a58c918cfce4ca2919448859d3c1b3d339192cdc1117ad1281a454f2a39f550bf6af4ec156626c519d4ad3032dbaafa50bf45cab951bf4a030db67808b338fac41f66b1c3e033e427002ba55a9f4c797618187c60108e67df18638c314a28e184134e082374c9d2fba0eb8759437ca0d40f2a2969e11b67a71b354283ad95493551e1100f7d50e808dd70401e506c547676fd0787bc5f49a4fc3a5b81a343c7131e9e9dc74e89be322c19d826b0a3aa54766e2d3a287d12f4ecdb0a070dcc93f622de92f11444f462dab7b5714c3b76838f257950d99738597a76af4e7e7836628de7163719ffc9b467516a71d097d8a608e97df86388df039bdec52c2d278b96d6e0c65c987e3f478b2e6cb33388e28adfcfc9a2856dda7b0662ba6cad250ed6c719a7cc50d841470db31a43f2c3365ff588f930d6b1bc65e6ec7a523f7db5dabc97d4d8421a6314d8867a7bce0a19cd4e5e4c4665c7ca82a8fbb125c336b1318c7760535353d357f7619f3fbbc6ed1f909a9f1e338fe0a5898d9362f41eae547b77d460139bd8cb17f5f3c7b04a23837985594d39d5f081032a2cb9c22c960ab38a31c62cde4594527a1370ce1e2f58d46ff57385c365586c237dcec520c4e04ca2588c27c7783067ff302f46078b1e0cb328a594524a29cdb21a25b428a5d71bd4cf5d86153b1c48754ef9c5164cde005dac2c6591f270b5f0a28e10970b16b4baba808ccc92f14afdea4b293bcaa0bd5851bfd41603d07eecc31ffcf0258b0a7d13d01bbda2b24399a680b632cbbd64a9317a94892a6471f232bf2f2c4753623a9f8cccda972a2a92166b542318a3d949731b5f969481eec2d6ad706840eb60366c57839d84b6329f4402a930513948f476168d37bb4ae09b9337b71337f827efc3d1dc4a8b351aa34d527e4a79ec7a88b81eeb242abd91fc4f1e9d4f5eccc9a5b73177f228758eeb388f5d2761596a2eae23434c8aa4205da13406dbb00c621bee829e6c5ec45b4f62e476a647eff35e730a9f71f0c3f796713b3dc5fb60bc4662d6675c9552bf3e624e8783fcd83f39aae3e7b41dde91e1b5cd7bf322b733bd9ee27d42e07f90761007479e1de31e001ba98acdd18d24172e941215fb0a66c910bd17283ac906896df691d0245e827809e285e9b5059417265e98883ea4008508ca10528e1841e4c3f6cbd0b2e6943d30b833a317d9891d7c384a1fce52b3038fc8232e8f57094d59f290a86655695c464ba2464182e8618d3da12c56c41c3616a374ea75e84f5696bc942ec3f23e2035ee2ea3050eff794d0ab495d96ee6c7e3776f686bfd8505172c8ebc1c8931768c5e833e3acccb8a093ebee0305f5998f1a7df57164b49af2b5dae1c791941dd5716575e5908bdb2487a5de1a1ca1afc92c38b119fee7e318205dbd699b933aa30fdd8871042086143f6cdc155945e55b4782aa83c57c1e2d779febeaa24fd0aa631c60beb3c82d7e1bfed049a2a8a7d5181e5c515b39e03807e5d0627ebdb7a3282162309ff4920f9c52fdb2f5e6adb51975dec64bb80a9453dd7528124fa9791f01a5cae02243c5bd1a9a2a0d17ae0331443bfb974ee628039fb99c7033d0be243bf679b5e73e67c6c7ecad9d73c10acbc3d7ddda2117d8342e837cf5cd36496ed449dcc37cd61a779d66ddedc0e75083ff632d7ba8f1ffa38d0845c06dce807f8e8f3f544d24fc75668e1688d3662a4e6a77419960b2f3c71441ec941cb182fa7b376977ae76c758593c2cb09a397122688cfc6a900c363ceeb3e53866d7a605310769fb98381c79c62ce8b79bb34ebf9c8e6697e1073eea887751f3bd04f9e0ff3f56fea605ecc0e3be6eccbdcb56b90dbd957385e4757fcece4cef01ff57d2961543598e9d43f199a9e6778eadab28b7a5887d3738687dc948bc6e326e59cafc3acd69dfa02fd0948d3b418ea99734d70f9e85c1a411f9d07a9694030ebf9e9d75c8bd9c15c460c85ed60288cba969d661773f27c4cf9e82787f291477ae66d59733e4ea7d3c9e391fe458f0148059ee9d1bfe99b97394e673fbff9ecbed85c643701b01594f222b085d35bd7b1a777328f87f3e81ce73d39d7bc18cd31ce8bd1bc1deab33b39c64329b64331bade375ddb3c269b5d4ce6f9907bc4e4a36b3c27d7a66f9bc69c0f4d47d3bc3df35621fa740d3e7c88aad171e2ad8726a4f4186a18756162e80c8e1a77d297797631d2e163be53159d846fc77c3be9bc73e74e0cfa4eb8581e76ec586746853e436f326ed2650f550f2a744d723e16cbc4582858f31cbdca2c1220d0847d036d556cd4a86aa3f211bfaf170a9e83f87d41d1e559bf2f2888747c6a60da0f042578fd3a54b551fb89dfd7145e3c9cf2fb9222c9d3fcbe905e90fb71aa81d2f7b8f04c49be8708ea3f1f2fb65805f19f0f51109a17fff9e4e085fc2f862c9238400c92fe8bc128e997ba5cdc36fd3af7a0f5c097d2cac307bc6cb0e41076313bd2ca479756fe8b01e8a3cb9e6d6a6a924b0fab1c7df7dd6bbc7137c2cb4e5a79c8fd100200e8f9f0b377c7c31e5d934d1fa303bdfd07f4f67cb4a742cbfbf04bef631702bf1de8bbefae7bf6d1be0d260fbd6f874dc4a9da52f7b5040f4f7f609e39edbecc794e4e5dfae6ed1aaa8b41a162744e3fbd9ed40b61ae8b4139fc1475d6892ed76b1ebf5f73ee9899fb8172ae4bf9d6a17c76bd715bb73e78b96dddfee0b98e5fc3e244f5c0a64f391bcfad841c51313b0b45ff072465b1fcecb89b9eea38b93cecb409eacbfd804e29655f1f103ab30e3b750dd569dd9661c66bbda1bae5a239aa8b017b21fc1c08025f7339c36bae4d4e6546d57c39550faae63e168b6c082343380780d27c8a8b86477535af6527678df3dd1c19e7dbf4fe936fa72d3bb9d605d9386efa76f2ec4473f2325fa7ddc2a992deb7b7621673109301c76d1bc7711cc7ad0cc786a69d4e9aa6699a764696519a650e3b55962ccb32a7bb23ab9007da3233c6cc9159d8e6c01c76aa2caa2c357ab63b7ab0633b96b45555a91fc552b37b7677cf6d73604ea90a4bdd97942e2f1d9b8e3111da8f7d196dbb2df22b66ddf0bb3952dbe64839772a2c15e59c6faeb90a4b3d79a6c292f26d77b0a79cdb1dfb29e754175a4a07730cebcc9da977e6d467379d9910f8edededf9538f873a96c1ac697753c7e99e1fa79c3165ca0ed9b78b69878ef3bb341fe610c37a603488cf06830b4f9dee4e71b9a8d3eef382f42c93cbf53e2f7ff26e9fe55480e1b3203d0d78de1ced3e93667307039ff9083d0d78b839a8b74bd38ec3d2fb0372c80e7da38751d1a46bb2a7ca31d7e00fb6bbeb58cfcfc39e7ee8a9a514767b8cd19e7eea09a90f24ba66f88fbd075b6386a61742bf8c277ad7ed63f67a8208c13e06a0875e8f9dceb307aef1311e752784277fd3bdafe7e723c47e9e3a77cccc3d3f0e6300fa756ef8ededf5fcbc0998a58136349516955910ee4b0a1408a167a06ab1dbee80bfed8e20f0886b6efbd4ee08025ddfd300577505a338a8240e1655392a15cd88000000005314002020140a878402a158341e52855dfb14800b8b9e44745698c9b32489611442c618430801040000008088cc8cc64100e84a32aaa98103b553366244dfb51e7ef1e67970766edebd30313ed43fa9fe8b7e96998759ea9a1e1db82bfcce61e5e2f3ace0e05753781e71a774045d99db22068412b4d29664c394c0b2b5e7517e6518d747f3754dc2a584585e9504056abeb17a5df4da963e364691bdfaeab9198d3b3e0c1c0d3b93b5adb5d1b3205b73a3cdd9cec23e62f940ae9676ab424b2007e4b172428cca0a1937516ba765fab19fe9592d29f4cadf8d92d3b537ab6a7d5af520e9e00b6f2a2c55eb9f997ed72573008295eb7ef22a41472e39421883223f6ba4ac792438706c494433e46882b6ef28e128d483246639f88921febab60ce586f97d7b2e8bf880937511bca7de728ef913928f4a958319395600dddb9e0cc92b0b43785671a0d550ffbdc6cfcdce48dbf4c0550c227f2914ea6d74fb29fe3f0890e43b67bafc0a416e8d71ae27b6a6faa2ca77e4f9b1efd60842daa0e51c3bb10a159ddd24e8221d3041473d7d6f3aa604b771d5e9c6bc35b6e56113da961989c2ccfcd98d3ac3091896b10f1d7b05575cef3728520511ea01f4662a266ee47457251b3b434aa426396315f22046cc17645081e3afc761d088a9be63109f9008cb9168923d536797e71882310d942149f0b9a6f421645effd689dc968086652f4160a984207d4de502dc81abdd3042edaa2c1b2902f1e45717c950e445e3d006834fb8933e597b0a6d75697308f104a9e1a2658900943cf16f5897476dbb6fcd5c630e413e6181fb0996559f6a7bc6d402c81d2c623862d8e85d45a470f155b6acacee4dd0925aa0dd2d3db9a0be34cd12a73826bf2e1bbd8ad166a4fe5bdbc00cd33089db341a6cda6c939946525186064e94497473be053353b8dc4294a19e417a765191bcc689ba2be9d7f95f8b7292e5fd7fe5023348e988a49998389c33a98a184408b6a3c98409a45496f62f8c0bdb5072e177ef84d896c97dc44784e852b38ec4578cca56b3a2c7527b269dbcdc1883120858ae2254d6a3abad43ee3bf707502da508ac18db6e9b00f1b4e63c10a6912c41123d4f67b586ef4d5cda1807a0117c5c7feca5c3de4bcc93569d1386908bb34d96b855003ae82000943f708ea7c177ba371df99787c28af8760b0cc9f06e15a49ed9471fb965e4e3298c0bb2b43a75e1d1d280f96314bbd31b1c2c6b90a47372aba592d01f65795768a02f6b45bd2feced2d24bc10c38b8e02a1cb2c6010d5aa2149c31a3bd2f4f9843cc0a15580d2d7669638fd059a2d051c6347a146200865f69a0ba0c0035128216380cd8447868b31e0874cd2189343fd53eb8d32b2d008f01ba632693c7f04d912343f884771d12ba76b0556ddb77ac2a2868d9fa4d5fb03788625a1ba18c7a078684635dee4163f2bd737d5dda06990c6017b48edb139481b738f7cec05322a39681c8a6316c9407517e42cd8650227a96fa286c9ff7421708ff9c2974fefda63121ad12052bb1db4e0be07b76907c6846ccb2c729b984c68e6c4e1a53d0a77a66252c923710c571d80941ae39f57c7367846385b7b878122b280ddf7939899b38ee848fd4d9154206ca6b684a593736a512c5aee8ccbf7a44e7212a82b1071dcf2c6ddfff100b572ea6bc21b241b31c49b2742b582badde494a8b09495c5fff73ef2308ea8a15224167f060d84a07dcf4e8f67c73a61c4d83395665d4df8c4ee7f6f71a6a267c9537a2625850de4ff5e827c6a525f7609d04a053dd60c3211165b13e9f7193beae530a0f0ea6f3a5c8550072ac7a822406740bc95db8b9548c2cfdbff6718d6b67913ecb364217363653e3e89d33dd450152b601f3c6d74e8a29f2bc88f6f786e7da9c6aa9b5ffc0f84d6957fd814bb1008cb2504c2c7b5696a43e1877d4b0097c9811b7c1b0bfe968808ac401105ba40ddd6cd183de2dcae957a62887cf71b391a4506c82a47b8ecc0a76b92d413697f6dfaf1cfb1ee4044442a202d58ec0b2f8b257d9cec387dec74c4791c405832f009346b6edd7a74a2b66eb3cda418f90b1fd55925213691922316fe489253262cf0093122dcf5fb916a68c8cfbdaec98a5dfe74ab9a2277b4e38cb55ad255c2894554aa8e2ecdafb67cb93b6f206862fa03fa4f6d9c9ccdf3ed999377d750a863b022532807c7b65afc82065efbee77e037a13fecb1170dfe989a1151a9d38292ec03d67656ccee3686785a920826930c98d4e642f4cadea3371fdd1b538213f89fbdbcc94d56dddf615d6cef72b651697a50bac3c52b9e2af585c776c849f54a77106bd244b8039bc42f2a49eb0faf7b4199637c4209005e86040010134217616e2e29a6dd5cbda4545cb0284df183656028552e9450f7502a80fc421eb870a1e96a2ec1fe9d449b369a2358a56069042e7736d5c9da03151d18125a33602bfe148fef1f5d593deb9e81655f2229431844b07b358889ad7b14e63d63e1c846767a3e529cdf264830a0bb623a993196fe3098053a2bee5d59c13646080bebac285ac8b0e329ea3613beeba6d7152be31a8ec8404fd4cd64cbb4437c496e7afc6a95f5e963295dadf1bf510e65a8e59e8a7a00b748613a93a71210a7d4c3066925f86945e20b74e5191f5f281b531c7a1504526a7446fe501fa1efd17aab89c02d6fc62152b7a84eba91f61b8ee9447e20fa06382c6f41d08b8df9776c2283a13348171f01283db2cbf1856779dc118ac34b3d190c735010aded84e96475ac4c420e0d6571ab0abec9ce733c37f48d9e1e95e25521043dc00aa18d838c23234645b18f2c28740bc92016ca47476be466a62a4328d70baa00866469ffbb60761108724944e4859091479d938088c8f8ab8dcbe9d4aa7797553be93a367c51c1f6ddf8ca2a6c9a8cfb98f1e813329b7b405c95b1aa45d2ba1e9b4e97c041582c48c73b16eadce47d422758a830095082ae124b410866d4fa9f66ccc747dbfc3e78682afb561c59845faf18c9cdf2f7d94961557a26e8afbb5b5eaef7681943d2483afa19cc60ddb74992c9076612dd3ad686a8f68ef1619df56f8adcde239ff9df512c993df6ae86ac3cdabdeb07cfeb5c23d773d198255ff24a219883cdb7b422d6fc94e866d61bbf3f5fe2ea1fb38e71ac8b058266b42f998c6dca58a8a74ccdcfc84030cca99e961a6b9d72d0ec30746c9ed96acde0cd121a318892e33cb914475b533b045578808a53b3c4d0f5590d8f324d08a6184a8b5601cce30515e66a6690fd7d6d3c3c5de4a10d35fd1e3ca4d0809426c120e65468315b31a29dc148e56535049e5b29862d8b0cff1fe6f16f459ac145e684bef3c1210b2c786f7592e0a5a753b8294a8d2748683973131f527a63514f205cf3e254c169d76f7351e1b6b8c03238317c817c6935296bee7dce0881d8a86a0cd478c5554dab5d83c1bc78c56ede16b2b737f4b99195193e8e5717f85cb54327423c811db07da6d84304d65442f323136f1a8e1d2d448c369c6d9a3fd0c40d655e9c15699e1728bb96d48378dc112749627744a18a0992f9bcabf14ad6891adbe148f201e97fb7ba1fc3e25218ae64632d71e2307de24c66638216afe765b8a3118e9391884241500a565544b2b8d8454c60244c825ecf052bca8b85b4541ec6e27b2382a3c1b15989f7e10d0dab9ccd881085099ee6f1f548f8f3c87de6a8e7496d88666195fb920692679add3760d9cad5c64817bb1e5d61e9a953e750d8336b5d3980357bf8e4f02f257568111528402515fc0dcb46cacf9f26509d3de374b97a28e1e6db1c6c69664ecf7572c68e95ea292d0824be983c48c7971ce4be7414f5a3a589df8f2f19eccb2a8e2bbd4e52a1f1d43905ef0280a060ee56e8037c3417d375a08c1a6491203ce78e0e1172a003167cc97de4ce5f13069a7f70b33be724dc6c4c10c70e08c26d2050e8e6a696f6d28714b47e921bc0df085cd807f233274e9913b7f221d7f3150b934d37597024832a1bb160b2fd6e1f3e26220e3986aa0596f1cb9d849e27d954d999e8631d61fed118620256191378c9d58e538ab6331ffac70c27d2df1f0d2cb5e9f3bc36a5b04479052ccd7f71bf159959efc3bc805edf14ac8a3545d86cc5ce5443e8cf3d68203fce9885ccc1cef0043d771d9be3233b14332bc88198d0c43d3678eee2aa5b1ffb0ca02087d2d3b8fdd831e6265b19dac1ebe28f540ffb22309ed91187b8abb95613aee45e30aa91bea11ea6174ac0e4c307dc020f5fa14e110eec0b1e915f2e10714e172451cf3d24887db21fcb3af9669099f23bb9cb2ea589fc391006ef0c0cb6f5648230f3728144da018d02c7de52da8c0b7872c7629a1427cd4f25cbd33377bc6f3bc6caafe91072e2d584293390cbc15685aa5e8476414589dbead67dfd34807ff43d7ca2d71a9cf3eba3188672bf1e31abf5490ec231dfae22c3749d0f023620f490c90d33a0818f2f96253797ee62c2e33c0efc9c8862030c608b4ad9f3934a792b150b4f79caa09257f88613fb9b85eb78149a33e016316975fd6dba1f3254df7853568bbad1c016ac599db8ed7648ed7558bdbcccec1d7171deaba578c201a5c28e10dcbe09c8bea096c63bffee0d7c491c9ee0e3aa32144632ceae91e7cc099bacb5813f11df6ab97032afea1b9ec030d04c60e4c69155c781e1c957c68d510740ec9131f1fc4346ef8ac4bef0799bfec202e5b3abed3b0bf96557334268e658a0e06a69864d6143a378477217a80a3d54b0d0dc952c773563670ad87d41bed7cc74cbec9d6a015693818433844eb0f900d4abe7b0ce38573c3a3eee5270ada3365600aa2515de6b8123efb3c49fdd0f91af6bcd87a40f4b03002d59e937d93fe0bb84cc1faadb137ade0adb1832046a29b57f4ecf50c3308e2520786d3e45c987dd3f961077051dc23e0cf179bd1ad604a811c30fc01ebc6f1a77f0e05423971c1fccf82fe74eb7c6359a3c14e046cb87ab394b354bca8db2de64d382ea0acd64078633d98e3a0d78358100626711a554750423f42c63f0f2c8192ee1d4522f8428eb95ec4b2456f3ec7c501747cfa4fd8ecc61266763ba3854721475f47e9817cc19a9fdafa4fe7298a03d484b25ac17f0b558bada259f23d8e2ca8bf34057bf5d7d332a8dd33059475950ea0b7e0760e8ff4febd9dee487ea8cbb0171c26fe1c3b37668984bba9a890508feb2828cabe370c2bc6d0c5bbe4578c62fade498118cac7a91c8367bf7cdc65fd714267e19c6c62d3e10062c8c254f8cf42bb729519e943fc894e3d499ff7a373f134dd4f88f314d10a1cb3d371aca34ec894ee984b1d7705f7a61b57b480b7ca0bb4bda2bc8035b62fccd755fcb9eafbcaafef262ce4b504ae7c43c119ee1e24f4c1b84c2a0853624134d503f4b4cefd4f0a0e716401aab50f4c9e05381c848718b1fcccc516ee684216cbfc293653d9eb17ce053e633f55e81f94d79ec4f6608607cdfd6f46d2a3b28a7325f2e4255a5cdf11c77eddc6e94abc9988aa45e89fae0de9325f013398e6eb74fd60207ae14ac96d2264b40f2269100e52a13f87cf91ebd19ca0fb5dbf7123f09b004292dbf182a563e2bf6d9eb11087008f787db8fd35cb9b60d63cc98d5c31982a7e4b4c1cded99eba0fde05a05094ba99320f3e9ffb7a03950882dd82820f7438bbbb079bdb1d149e0bc284f8374df8f56fed35c02cfeccde9925447cf1c5a88744f708531e4a90a72bce7f1c0f7c06e4eeece09008b7049a4daab46e25f50aa84fe007aafa75019ef2e4a289ac5a42ff6120ce96dc365c2987ae85447a2118da7ce7dcaa2e00f6e244c7b0ab7b12f17649d674dc4244b45ef930e0381dde337d2932e02a21a90dfe03a890be684bdd50dd8f435f48320c0b990a5861364b3ff218e16443bfb523fb6120d3278dc4ad9175815103efcf4839b0b323972b0b7f82a2a0304d3a7ffad6c37f04aaa7241c112fd0fa9da03e95b050aa266ba9512854dc015c0cc53f876ba7dbd005ad5ab04350c20ce57a673516ec8107ee3d86ec1fe58d812f933007e6d37a52cc0ad225ecb0d88d3cc1bdef4e16b5a70dffc9da5626b9aee728ec35f6dc5c3d684ae5a685207757e23f11f9a643847fd6cc9851293bfb5b0ea4d2472caf7e1ba65fdd301f67faa9487f99cfc18ec00e5a6ca6bcd7a6749166c5d086f848c32572644338ee0b2f8aec371b7c2ee2f538f04364c02e5b8e16b35e4b9bc77a8dbd68a0c02b35822c41734cf6c8c93c53dac2aad54b760c7bf63c6a83f5a7fd4b6ba04cb1567b88571d19ba4de08615286cd4402685f06234248bc8ad0d48001b3a7e19af70a6c5e3cd33b1b97d1062c1e1299a0033253959f2dea34aabec1eeb10c706afc05913e79ccbdf62d5215f5bd564ec010d97cf989c24993e2a97ddc66a3a50158c88d58f76c1359050244e91b7d08ba77487a40ea2fcc5e0f0cfcb29543bb95924352a059ad5b02a407d4be8b204d1273798d057b09f753df10951e2199678d2d659e36dc733c6084dcf8158bf1e71fc2b18fe86138021ada15a0a65ad37e2c57570b9f08a80b1bcbe58e373553bee94fff44a8af9f7ad860e2a825c847f7530fb7b2e7ec2bdf642b0596263ff0e52da967070759583ace26e6e5a7c36a182ee356fe7e25f0ba2ca90731224f3f5762118cad2fad8a56f8a459fd3a71dffb419082a188ef0821594294025493fc44c9088d77f8be9e0e931f69ddea1e87a5e3c2435e4304e754338f37ba87fb6af4bc4c346f5c5ce26e798fcf3c1b023e8e52be11c8aaa18e74d1b79aa215feb33559251fadcb910559fd10875f7151f8875dea4ed6a3fd723b07f0e9abea6411e61482f8317000b0ae5fbb78e2fc8cff9c0dd56c00ec3b087d70537cd6d502ec003531bdb17c7f28e5b71584edc2be6183bc3aa08d936999412aa2f755114d2e7fc6a698c6e59e9f150bedf24bdc4126f80078491cda1727e399331cf5a2412fdb5fd970e8ccc3a2c309c959f1115613cfd0d0a5aff00ffff33d5d18e720e98a68b597602d24b2feb4a71c78869795a524a3f65def70b6c2708fa571a0220c2177ac564564543267f37af202e7a10a98283686e44e05264a12648f937afb3e60794c8dcec37e299d6008af65465aac504ee68ada743b91da955c6c00a42b03e88ceeafd548a754d95b5977f0d47c1bc14cca4940936594767b75325eb285b48e8b2062d028ea2f1ceb70595a6896264f9c29a6bd758534ee631e6fbd658e4f3f2aaa0b7a99dc546cfbca58e952d1a46f1385d8807ea04a4ef8690aaec5a2e2dc3b42395f193106c0b03c7760e352a9ba85cd0ff902f963859d41814b44718e70d52c15233c492cda5f19ca8eab51f309625d31c07c27479692f05b46f897bfa5bc2275e3ca464ee2b704f979e97066c08f495ca7205f7d7245f0594098c67fec512d20a2e12f08ea4e5766871e8bb9015399fdc7292ea4910daf9aa5b7fed99441bcdee9365c52cd395a0801a8cefae81c67603cb7dbf25b08723d2309c6f1ff50803f169208324d4c2dc9fab1df4481df9882d979368e71a02e7d591322bc905b7288b2b1fca2e70b42cc1351fcbe131b7c6e566759af42309c74051cbd2097d3cb9de5a25ad580114791a01ae480e7c75365fde43b7ffb5a7d230a0fe6f30b966bc327bee9a0d7a55c94ee7e54d6c5928385ee9787f910ddfbc1cf08e8ad54b6dac3a58408ed923b542a338015b46f4e54808a82ea127ccc1a883a8d209d8ddc544d19d366277ae249d20eac6499fffa589168e6edf7e2310f482676ef9d5e41e4702a25b7c0086f60b5e627bd5a9149f57ddc9f475254ad408f67db0cbe8634fd080a57fc23ef055bba50db972792546e638b971a04246a3315c3774e2d8880b0e5a3e32a66786f64b567ee2e7fd3b1d008c5b89844460c468153b8fc56ab53048191a6aab8e30c162b28123274d2ed89e9a853a9164403e6265330db701a3a6e92f0060babee7aee8a4ec0683ccdeb0bdba9c4749a042d435de84ef4d71d3194c647c64c72cc9fef2949165c087721fc5367e89f07fefe38deec6b7194c3abacf793619b344ee92238c4a1bafebd188fb3acdc9df1d9642f24791fc10cbbb7246ff4fd28c46b586a1203e07ae9ad747967ec455baa0fd4e2dcd561ea76a1128f3ff575380a9aa1e6f757feb58b8e0fcd387d00f0b3c3b56beb1473f6737414c27c2cf6b8fb326119a5db381d0b730ec570bf2a514fd2fa3588236391166452236da5fa54a244773ef56a040ecb32431831b29e8327fb274126c72a97e9616f216305a507e26557f42fe1b8457961495b8100f45d40775779015ead4bc36512eef7b42e81528405378f30906bdd85a6c311569e6f32a96f6384c9ef297f1881cec15fe27f66d348bec919a0302944fd78af6295fe58f12129bae11891230a4afe01e7d5b75be621a2a00915394877b65b8f8934b2f49b8d7d05c1f4a693f8a53a1e8f6cfea45c81c796dd1ad6acfc4a8179a8505a31dafbdd8d4d9690f4494610af3e69bb3ef765cb9542cd290ae0229d0bdce7f703dc546c2661a3c9218d7040c6a159b6523eefb0a80b8fc16075b4325f84bfd8ce41219c5602849c700b722a865d3ee4b87f2d784ba43819d856dd1b3e41309ab0adb574453ec8fbfc7afe471b75b1324f600c1f54929fe4248d3ff25eb7894aa8542f09fdbf6d56a46bfa905bac5c3102f6a7575a31dd98d9f027a1c12f02e26ce76a6e0ae9341d4572231e3444e5b84e74b95b43af4657ffbdb6da43134980b309044220257c18f61b13497f5c107a07b56f275c46d618226259bb5d50d03154b44cea8d14f8979b0d33e512d4390d4414169bfdcdc75751006e6bec5ff3dd7818b3e47c8593117df83cbb73d9deb970280f4bd19940a417550cb631d17e3b8298a401165e64dfdc418300b8861e4c504712ef6ef24fa7ebb3b3513771f226eb4bf82bd371addf1be8e8094f94e1e93532242c9badca9b9b497cdba93a0581a3268a22318fc912731e765a64171de6b2be6acba8f9b0dc431089f6e18f9f0421098ba64febd2400a40add11728dcf003f3a7cc0013b942cb58ba10521294324762d2659b8247a33d680c261db398ba2fb8caa3828c2c59eedff94a7059679ff8ae28906cce642c5754c028bea041166f202d36090d59c42a16be6ba72439434a78c8384c4a5fa94da144843baefe4f9993830d737084a373463c4513285c92cde0039ce26389443a77fdb59755893bd6266bf2e0b928c0f697703bd1f47a2969469725612169313fdc081025418720201eb72a5ed4291bd9586ced4f950844b8087646019f6774c5d97862069e65243cf37f5476550fbd6cd263fdebc4e268d9b0673a42c5d97f96ac910e19871c44d725049e465bdb78b0092aa4a010c4bd99a1731f00f0108c4c5168c4a7913809828bda95c5c62956a368782e4166fe75a0cfbf18ae63268cb1f9012ef3f62b345af47a064df6545f917202b597b1b53f5c9a7ef8b330dc6fe6aadc8c4937566ea6b7fc7aaa86b106bc175f7ece8764fc0032193943f8ad9c840b0c9419526a80261688c1dd7a1b93f39b7b250f9c3810c27a964c9cd081e3bed16089359bec1a5583c5f2f61c9b026cd599ed8021cd2730320da8e97db4a75f61641bd3fce7bfad13fdc0a1e7175a6761397660440a6dfda4dee21fdd67593585f6e86918a9dd555ddc98069b6758549eae24311493375931892d07e8ab05f429cbd7b2c5ea4051af20b32fcd6642805b5cc9b318431b9d447c7af32b2c131f442a3b69affe3d3a651a7449af8ede6b0e60616d7eb2c31913d9cee7f22192149360ac77c0bbc8c6abb0ee96ef8cbaaac06befd9c57daf5061ee04111a78ab1d36d525046a3805100e51ab1429528a6b95ade04929b4219cd1d5d51e5d5c6a6e025444730c55c6664ca24ea08139eb667942b506c4e6cdb6003c2bd9779443a69bf1d6a5dde0b3e16cf6d8594859ec1930ee1aea5df535f38d4c6fca0029008e5212bb2d5f0e893c3a6511cd91172b2ea1faa07858e96a0f3bcb167e7fc3bc7b2f4b89944b2818c674a3e8eac176039cea1d0239ea4af7b9578844149df8c66898539e46bb12d98aefce63c39301e31a3ccc985e346d4baaa5d06553801266156bf45f2c5bbc970286a0c7df453611a359eaeac1926abd1f8c416ec08c63a4402b06dd5150ef5006d3634c912b7841ea92e0d6eeea03a0f4da5cc7c2a504ab8bb43ad942b7f6d147dba47165418a2353859073d6a2de9ca997f34edfae92358b644abe1d3775a0cdf47495c64aa113be9f6872ebc9211da3170282123441228a17519e6cd878a6f082dc0216fe42183287d6291e1f95ca6ec34a694f5693d5ab2d8b5549c6106c153f5736407ff5c64c39eb6f74ec5558f67b692b75893ccd6c5d25376d55e34095a7e166a75428f85338668acaf6fd73d85850d3a696e832f6da94436470efee2c3cb7ac14b97dc0abca12eb6e2182a889120ebcb03e1a24645125af601a4f7f50621a2a5fae08b02c4a008f1f82a4d8ec95fc23afc0e0b16c2f789094f0bd34ff960c0c722007dcfcc3ec7204e47ab2a689d620e3a06801ec993a8e5a8ab80d66e9abaf50dbc119d760915310ff7261c6112b3723e02d4b8e19fef229f03543ef057841f13cc46e50dc4ccf0bfdeb6bc9066593e59da9b0e532d7381d32946df200945c96686ad113cb92a4c5a3ba0e2a67792599e2e9e54e83e4d57210a8508dbdd007f874b2a4064310b5d38b3e625c93fdf20b56f7d6b514338439f6a59396f499af4c0384b41a19858812b9cdae03b744afc485b33d690998d14e6767a299c8619be31ee8139413d716a17887c5b5a217888b201aa4fdb9148eaed5f1f9949c2fb45185eca89e54efaff1035f038134390ed5e7a1f50ad2aa4beb21f8882b25f8be735c29fe73c3196e223ea684138c86b724f685827c3f3dd1b6e836a7481bee9b8038ce3d80099042f8a2254743678d2cb1d604728c372dd8beb5e44a4282b98cf44475d83dd04a9652036ac0da6656c8ab069eb74f9c9ba24ffd4773df6efd00bee7a29307700f0a4e8e257c75b095a5d823e3a33cad64c64fdf2fc6b4988cd6746f5c7d31b24ffdca4aa1d303e33a24236c345d50cda72aee7764fbf0ef8582f662e3e301c64269051b7388f8c5332637062a2bce4e06a1bb3bc911458bbb30961c279b4710264d7cdbc8a5deecc58f0311d6d67a3c7857253ebf245eacb75223ed3bd90bfef05bcb15f468afec1181476093f084b9f1cf057bdbd704bf5904807a89df121de9b14f6d568dfb822caff3df1b041f92f58c78b9c10242181a1c72eba9a2f3900c36ff8c078af84b81fcc331cc1238b4ed6253c6fc0b591ddb3d5f70b0ce02450521d1ddb0a5695a8bd33074a19e5b7585a16622fd600016514e12c200705d720149d30eb73f06a0fa9cfbf9433bfcc784bda04574a3e1af65006ccf82236141f70841e7df2d56a7ffc3bf410e986c5ca01bd31dce9cd823d6bbb5e12001b7938c7b9d6debd2bc4d11a1949e68c13e1ae37a71680529f13f642eeec084b0b4149b58a661aba9b9843a97715fd17575ad744f522b6bacb2d726437f0e2785fd8b2db11e7f2ed3383e61a0f2de68c6c56eea5d66151f14f8481972ce2c0b4f65550e49160251726075516d8916ac51016d1995009db07b00243f35daccbfc9a37dc38b4cc0bb970f5fadb10a09d4ef1200fabb60eb5bed97ef1c8a3b994941e36bdbc8e51799385935fa804efb7c35474ec7292f76268fc314f7e997e63c612ad9575d16fd02050228fd2598ee48c4cc46bcf85d867da29f84b9fde48b4e71329addf9292e76ea87a6cef0ef4cf9fa31cbfb80f06b838caaa84e959fa215b8261d71b5ee516b34fd38cc183d1af50501d66397afd1c353895ae89f92b64886d539658616e5f2516f00a3352dadc23244c3b36f5eb4197a98c2a5d4ff9b1adfc89b3f1cd7c64ae2747f81b3c5efc19d275af7b04da3d9176254d16fdfde1643349b91062134b56fff24b88fd9d21a1837edb9380d767f02481414e4245ae317f96cd5ffd08fbdd5bebc3442eada0057c6e6c5389fd20cf1fd11a029737e7778f18fe82671831eaed2fb65791ce69215dcbd788c72bb5cb78d9b8cc2530eba8bd7e2f49da57c000de463e61a0368a8cbe87f5718e23b0bb010d89884e400cef8232b751c2b90d4532d77a1c744fba931393ff73d04eca99cb6b217a8bdc3c7bd5a9733ae2762ef9237ddf8caba16a22ed23c9f207b8158511865a9b38b6b7400f1b87cd05fc7066c120a0c9ef247a10e107fe70ff830ff6539a8c007f7a4d4fc5c31e12395f3583db79cac676f2674791efb38559ae5233b1cf3cfe99ddd78306de9b312a02087636fd079543120c342429099eb1f734318f04bbbd449dcfbb2501021e95375f9214c33d59ba9f62b4a545b9d2d2392872a4c17d3322e27d8054c743cbb1e835742e1fd6f5536ce1304a184e449032e1419717d60764fb921572e62391c94530d722e5d9c4e763677b91cac152455b7b9ff24356befaee274fe5fc077d848f8ce1a98ee9e001805cf15dfbe92ea7118d4d33fa1a830d4595970d4214505e3eaab723ec2ce7a9c9947219465aa8065df2322ca0df2c8055a2dd536accd589b56a80fd61e7ff17dd46017656e61ee49fe3b5a5b9f9492205084a8f3f9b9897768dbdbf91a5a7a0c53f5fd5b29407bf0d3b9da7af0deeaee6d35a8eee6227871513f59610529d2c798965d90e334c7dac8edfab2381ec37ca0f490901c0a3e4eb8cc42803e4ddc3e7c6e5d211a73011fbb0068a9c6eb4ccafd3a8888bfa2c797c2a51cd776d2e7f464a8da628b054b92021782f451dfd20b4e0b3b6db05e5c46dde74cb30abd80c94bb430a4a2b3fd9343314c95291af9da77fe8b58db83b5acb169929ea414623902009286e15a7375e6db9824de69c3015e181e6a8759c0c448475727431cd8300f7f07ec730d7691284e94fda3524e6049fe91c0b8ba72bffd389ce0f48d4945b189cbd37fe656c75b9aaba25bce3bbdd7c3d3a84cf7ad036bc2b303f1b3bc1b38bd63ce892124a38bb06c72d971548c077262ccf36f500e39edfe21c348ed669f9dda4095d26a22f8dd36ba2f9f17dca958c52dcfb3bf6cb50caec643bcaab7b6ba5768c811179a2fea9f8bba1a982ed97b816e711ead67d728741b35b8bcce3b97713321167a93befb8b78f2c7be5c466337ed17b5d2a5c43d3fe5e451e3caad5f608a4f5c39d428cbd6ec4774dbc33f04391ec40002453eeb360ccbbd5127a89f215576606e363545e0051b1424f0672542837745edc4fcfdeaaac37f9ee275f255ba838a5aeedf377f153563fe7a4c79f14bead33b3b3239819cf0484896d47a5e1706d9eb6e707922ad38f59fc655ab506e5652b347e5db224a1f98b43ff7b150ded829616135862a9fdcf705665cc5f9105a090963d9a107700c807c42fb4980927b8386838e4465f10ad6cef943c4ad1b90066212d002aa952d23f6a4a49b8a34ac2562f09bc01cd59a666828cc801e68864dc42b92b093127568b6d64fba285bbc8dc413267b78aa83654daa8cf3507380886a0ed290b365d11bb9490505dc8127b0dabc26e2e283056c5f65bc733b3e02328387f9e8e8ee8b90d33c5394ffa9dd90c6687a29dc55d950ee8cebd95165d5132f3e5a9d948d3e58e046b99df408c0e999efc4f261f2848bedd4be391b96a0b791a9843a3b99ca228abba3c1d163b2c4384b519c8251a12e4ba85c7cb62918e44e64d6fa3b17489f67a2eee2bb241e60426adea5fb8b3bcd44d9558cf169d53d190b3f7b5e22b00582551260a304cfaab732999bac52b5452df427d01785087aac6ebcbb9c7fb87e4c9464bb0829893e6fc8f8b32c05b72a72031ac72d5ade803620a0773629dfad9f6bd1d5272161dbbe8231e27f20327fdeb2c038459e803fe0babc141bb8738737a4ca717a8ab5be8f28ce5022d8e7f8cc6f119d61a75dbf0cb0d701107fa35e16bd3aaa5328241a250c977f610cc3a33c719998c8423ba59803e653c1a80700b0859248090107b1de1008e698d37737df1b0bd6e2d5fa228c245c6128d38e7613d388c7b40478e93aa497fe57dad02a144d6568cf7600193700badb8d3985edb0aea22ca40adb109e89c03caa2be8c3b56642f4471bb3d6b444fb407ffb82817bd9cc37e859613234acdcd71cee8a12f1d8f85e72780c27e64b2bf017bb908b12711518a869e3a58cbfd77bcc30c38700ece387d647fc2ad6c44ab88712ef4ba34afcd9a79e2e4e8e1e7b461d9865ca2a40ddda4b9ad340d6075ca82c1d5b58ef7610a1c644349d0f78941e3b5549ea400e5ca43bc587dcfd108d4d97f2ae4da386d0247134f8573c8ae631ac3fb1c1e0e444ace36a202838fa7afdef3d8a90bee85017364fc1d4c64e3a5a83e73333cd7b348e02059b4f57770fd4609c08db70ed7f27ef6e377470e7b5825c05a94befd8a6cf8bc14b340b42937cb7b431c13ab590a16dce31e16f4b1282509bd2eebb5514a0c4f8af00ac2e65f8709323b9ecc889bfb6a4014108c8a5244054f9a40a032b409f0e1edb4a54d18e5c16a74f4073e576421a05029a9523ba41cc90c6f92b486efc29735fde8a50056763a66e8a9e994b03992e4d3e824aa368a538d1cf58934a184253122a60cdd1afc101a8a432a642770b0efd5c60133c19a88f0a8570fb0cd8516a90516b091e4837d5c45896ece703a0cdb3f7b51e420ed7fa15abcb887e192c957dcc87ebd0d1281689df7d9438930e851aa2a2472c6e03f388082df1e0c15364235ac699470822c1d99c4dbc11ed74a25c950eca2a916c32e860f5d602ac668867831ecf35bd97050f980c9f422e908a2dd84cc1779fc834be665094a1f182ce1468837b02913334d7b09aa8efa44a3943c72260680bd4646533f436628262b800bc1a0b6060bd63a7424921bedfdaba3b0b3da12e094e15d75744df07827de2d84ee50065f925162c03218fae078bae378b5d2f8309f5fff6b52205621aca6ea69766045b19b6496d5b2aa91c7cd2ff5405e7da8b84cd0e59ed97a572409c7b06b4fc6b3c242607988c79354a178fd620f5a68b5846c2e4d50eaca5ab963b6d9c637f761909e492a5bfdd3ae1922e0578adb55ec1c6292930aeb6300c28dd70e84b29d7c5fc977fcc26cc287c1aa7b1f3791ef026fa10fc656fbd39d36e4af27e036f1db9312cb779086bc3fd7d19a348e8e76f97f4143aad87f40554e94b0187012ab6ed78499e3cf09544927f00454e823067649166d586c69983cb7fde266e8f8f558920c8455c4bcb699283bde5009271069137df2fa69f8c11b681ac9458bc56d4771548297d510a75990e69bde250ec8ee25f158d6e1a23122e9458ab4e5e3a973fffd678829dfd01c5cf0dac94bcae27859b4790bf492993d164ea484676a2be121f011c44ca465233625ecf06e25cdcad5de049c9974079524e148fe52b350b4a7c47da50814c2ed32199aeb5314c230e94b5715dda226880b28d7dc5675fc106289720067dda862354c72b87c624de468f5cc873607b04c3ef62aa922d20ecc545b892b38adb1d2c8dd507d87dd8680c28fbc978314d8c2d0b07abd6a28c4a64c63055d240180686cbf4bda93bd5b326c038bf76c4807fce3c00e4fe49560ca7bcb4f1553f12f08f4d6c0bf90fee389cd47fe01b3183462811e2e330a3af1032b349f34161a9d4c6daceeca0ba391cf4d703385f44d6c3cea235fc5d64fecc17b0c1ca8647206f3631cfc3ef2940b60f81ff8efd8a464832186d91928d9821c966a3b1e553ae794bc6c3a0c5cbedf763ebc18f2037f2c221918d7176b17088a30c3f02f70945f09df9b1a6379b89d5219c21292216904c5b43bd28ac8e0c4a0814f885fc4151bc08bf1f1533c61e7f6f56b6e8a2ee4472c712e8af374e71820b1c21d0e3f915d75baef9f1d8c02227471160ac828b5561a5386ee69c3159f8ad93da1810d44b9c9b88f75ec1f83cca7ecd40a2714fa1d9a9e71f76d89c77eef6ae9224c2f256b9a2d1213088a0806423eda9eda3f7deb07ca5d4cbe13bb9065ff7c85703a7e2168cfb062d2c117f7fd9af8edbf8f92ec5af25ddc76f83b45b29a67bdd0118910b5289231b31bdd7010b42f982d17c4182b66d07f4de3685a83277ac851fb92fba8aadcc9294a49914f1219b3ceca24c38943c8b39c210d31b5d3822048f70ce3dc1b37bdab692053b94cea2ca27d0df783f5b10e17e01791a2f580a8c60b621d07e2bc8338c8db6a07c86a1ed569cc78cdcbae00d751eee846e13d948fcde9fe314ea84e92231f5417e2c959cc704eb23c42a0ca13e0c2e942aa266744762dcf3cbe1478ac54382ee85598e9a17e71366e2799611043d7d61442b2118d5b8b3b6f76db13e70730c51e2f86c0d08f59d77439f3fa9d85298632a4de8b6361c9bf0aaf9181e38c3639d86d113a3fb1b9a08e35d39776befcacaed55b18e549e3e03fba66e706ea6bd53b203d767380efd758a486e3f04050112ac8de3afe4cff2efcb0b936790a680dafedfc9724b9e1ccd22e50b2d76670085822807e238fd0a4ed5697fa48b5cef2274eb32e800684068ca49a2f114550868ebf387d25613675da6e192092aff13ea281af0f76a7272f8a19904249739a09c190891555fc51345a8104a161ee67090ccd214eee8e22af2db1ab00e24e2590a41b743e359cd6106b75cc1a86f3625048285c2093d2d1383e29309d7c4f734544d7318f840c5a0001ac5248e5290c19363b59c66b31790280bfe28df302113db93ac40ff08f92f93e0b3232911133bccfde853f653875374a4455161786642a242f322a9e3cac72fd0ca637ba947a9ec380af5d7cd5abfd8440133b88fd505a33ec15d11d60a0a81dae1622a7d18b766e89acc951940bbe808d2989bb28d0414c30c38a7515e764b833655a52b62251ee6eb695ab2b263f7bfc7edea80f07160a09464b8a43095a1312080467353c14f06847de07e8ac071699fafdb1737cf7c886c0afa365a69e124dfef95a76d7342421bf5dbd36f7673d25626dcd77c04e0756ac749d3c2b4aaad9070c4d16305b898cf6312872183f21f57b08013a99ba637d3e7ee9d8503957726d6c019b8170852503d8f716dbbdf341fa6478b44c44c58895de006de04820c0674b6d44beeda9fc4f971cf196cd0a45a3ced5cfa49908a2e968714025a3210bf91ac9505358e9234465842420115203ef140f6dfa97ab5eb72d7ee7f4dd8043108d27ff18f33a332cb5f0f81deb657deaaf7245ccff48fc31fffe439969503706f6b4b2fca8597dcd012995bc0ab7191cd46cddfd8ef6324576b6c1a7ec523d4029ff0329c53dadee0b9f080bf369b764e47d2fa5416ccc0ffc66ecdc5681c412008b341aa9903c891122464c3d928fb7e0c3dd9a8d8ce59f039fd6d47cb09bddff778c5e578f0c1f8520f4e4654e6fd8270cd9ffa7b8f5e6631501effb5e4a9520a5e1d4b2df26395bf155353f37bb7a13ff50632cc0712d1ac81620170540faeafa8506300998e895f912518ca08636a2083ebf4d6aa9a5b5aa30b7ee9e092c4801331aca1a08611c68040e351de08bd918b042a9de3d3a989780a2464b0b25506dd497524173efc1aca65a65fdf87712d99f4e5b9fdb1ff375a2d3ba2bfa8133517a14c866f00444b2a173c9db11821ed08194d24a80c0b8981f1d54d54e1b100ca8d6cdf1884850a13063f4f2465b2082d953cc01f979d4c0d1316484c263c646c68430aae0f784282db8043e7591b685f6d5ae11b28fa3c06bef2602513b4c85acf09bbd622be510b85d147b572e33e0ae1da2c760dd7c9630fdd0a0093b27675e98f9c9ab70db23dfa4f322f41cba9d26550ad0b4a3487a961feca722fade93344a34aec1552d7e13e997ba8eb041c93807522559a4385a676e08c338322572952d2c1168878f53044ee770a75878e6f648f3e458c34174f708da94baf3bf6c6b4fd6700e113b5119aeb64429afd1be91172fd80ef2486815107fc9dbeeb6bedaa3f02d155d544882602efff75b11590409153b493eaae71fe31765972bd9f890313a938656876ffc0334e17016c2808cc5444344fffd8cc973ee41ccce001af018f4d80393be931167b32c62452057ce0f9e3aa4b8f0b56019e272315656d81398e905bc5b7fea5dc5791a013a5b55e4aef098a6575de89634dc38e4c8833303cc3240f103baca85609b64073c34459c00256fc74cd3bfeb11a2e6a0915907381fbdadc9e1dd26867636997abae62e4292d25615d0f7553af77f6f0e148aef5eae9b95ce7ebddbae3501d9b8b1846fe427026e82c47de45cf81c93582720115c6ea46305253fd4ba0eed15d57d59d67c93745083bc116d9befaa4e4f7674b4256b21fa65741f14d506d04a4eaf12379a411d8c3cf6428ca712d4c19962cf27496400d94848febc4ae6661fb403a6d9071c95c35b0ff9b8ab3256e0d606df935905ecf00de1f401451eecea27e60c68cbf8d456cc7fe4691c642c09a483444ab843334a78882d86d01851284ec3d9c2b19355a19ade34c6c9a2cf064b21f752730cb31aa19a3db96c626be18ed05d68c437bcd3c12cf013295af1d86df778af51b66659d6da9e51855f442dc5549926da0dceb0f1794e3c668d4b14ffc622cc53c9db3be53a7937a750c56eb61826354e553753988f6f10cf0d12f5304c9e0a55b2d0a704b01c8fcb558aab3cfdf742fb07c739ae1ea91efd9e9a345388f94d70e61d75f902c851bd9e56e725cd3161d98203e0f02d27b2b45fd489870ea3ac4a4d06bf462fa6eed403f30231854c0177790c148653ae8c051bd6fb5d31c6270cb220344cecb897e6aeca8d16374223225bc3224ddc938abdd9222164c7d53eb1f52ff04ba9d74f5f5afb3b4735d497674624602651b9f7a218d015cf919d814ac8e2a55dffedf82cb76cc5563f4b51eac39442de03e28f9c4b50034d60ce19a87a4aebd43fe65b6822ec8caaf8bebd404c7a3df90572ab4f8c6e5028473c6545eeaf2856a1e5478188254a4553d9eafa4cffed7042368d83f9a974748f3443462102d920c041b1f589c0140da4449c371bdb3e83c42e9cf0cae98e6b6601d021090bd174cad9d230cf71b1b626068a293be94ddd21786fa53d6c41325976537188e50d83beea70056a49645a213a89b117e91a8b947805236d41fe8398c2dd775e70fa0c185826443b942bad5a6be3c9e203f4c79843556820c3a759b0bda4923dd7aa60bce3d94e59f35cbcd5b3bdff8a1adc898b5c7920a5cf4bee91f891dc321fdf5a001708d9468684dd8251ed1e3b29ad11fa5ab15aa73b144e8c4cbb69966b35ab27bba5b9dd2f7ac7d7629f7282c3f8e7f453068945d47d25516a21432d6a522482eb65db818cf57dee158b788fd7fa3de6b1fefa39be87fb3a3f71013963a093c5cf474ef0f936412b2438efbd08aecc9bfb2e4e9632b5ac79f750e6a843927489b82d4949c02fc8e7ed749a5cad6158540f98abb4107589bfd6798d626e2fb69f1bea0fe3d4cb61d89444ea0a5a93bafc184ba616bafd9917a5fc0bdb427988dd73c66f62d7b784af2b62fba4076ddec14f472ed8d563540a4753fed5adf53905d79eb00bb53b30fcccf29027825288244334f1ecfaccfbe9bc8137f53725d794d0a51fbab8f37edda8b9298a689491847a09a08658d4defa3fc6d1c95a79f19dbfa81e980b8b7610873bb0c8a98f6765248cafe36b419703a78a1496380c5100d79149125b5c5651454194ece08586fa010526c57b7149d361d20c0877af6ba4ae0864016ba85c037d453408fd9e8052ded94271645af574365570d3d0cf2a6e08a494b74919232c94a40eabdfb87e662b3e52d1419f7a02c6f7010e23c2795eceffffa8e211b883ddccf9e2d2ac59db7a6f55925349f724bd989dfdf5c385dfbe85afb7b93228a2277b90431177e94484f575764516ae385767adb0ec02e931de478e2fab29156486281f5ff54e23721376294d8ba5a17409d9c5fbe341ab6e47aa511505d6e9621a1c3ba50e47b05e311b21cdcec16348d08b8583cc5fcd0e5122f240328918803515888daa9275b0c90dbb3d51311cbd272cfc3381c3c9324cd6f85b6c436fc120814dab78269d9c6534137a129393d72e4b971d26d57dd39a4645134810668c1365704b5f6d0e46b5d639d61f27b01ad551cdea81f1663ff6603ae2be0372dd33b71cc2c43a314341d44595ee639d1b7ab8b7b7eec8d5d87639a4c086bf70106e9ad1d34c7928d49715fdc02dd7575c5f070491becb88898cab08e19c1d87b12e2f4b4223223a67b67d2374741b87f20f078ec16a4be1d5c0587816b6477a81163a51980bcd516f169369c370ef8ca100f60b5e75599af2f73a154447a23ceb14f5fecfaff4e5f5e1e5e1deb6b45c3332f9f3ce6a34e9f57a44712cc4612e9d3022453bf5fd4b1f46d4eb03792afbf766184ff78a4e35cd5e5cd862dbb29e5150f8f93563df92c499bb013cd5c4a4da2f2a263694bd471cc2b94a2deb31fbd491c1f60bda5e8c9471361c401a2d2ce15bd6615d381d6e732b0b7341015bf93ca70f46ea09c3c2e20c08ea7e7cef8a730684d23a26aa44db034a51b4582f81c246d37a4655b3f32047e76925053f8b2520847dadd7cef1937a0b89f8e24945b1612034688c4d1496a1d9249567fe98ba71c87a8cce1886ce557b462f327e45865815b5981c630a8563a83e069bf176aea990f66091934b142b2811030bab6b174cf7e4ab90bb8763742c9e49e16491f518f93681dbb5240e860ad69c6431303ff3725ca857a345e871c4753bbc547090a41a42e30a0e2241c76491bb32fca113ef92bfd8704ea7b6b509f9f2e9a5e29bbd95af57f560289e9b92839ac07b8983ca6551d0847667789c5ceaa48483817cc49afef9a2897135583f08ee8347068f1977e6c5bf1d8cfa6e7743bb865686af90040280aa6bb29488a222be216438d1a78bcd1b7d17012d3da2549968695a9b34e1a4cde39abb43965ef6f589a29676ca4a3434d051280e5e47b38c34fdf3a521ea46265210d5d301c11f8df3579e6d056690b211fd813d34c6e377758f281f681bf59fa60a0358936de579b711ac6085b826608abe087d46bfbfb6d6f8a75fe41447304dfa460ace157cdc4df706e6f4f39cd1c38a464c0ad4a9db69448ef1ac9c064ebd4773aa40c343f042923627e5a19ce39051282cebb844337013b742e30f6a133372449f4b6ead9435210cb0d8796c194d8832329eac26fe3d32b72f422ccc5c268ffc1ef4519a30e7cff88ef3d8fa6c173ef8dd93b9a7066441fe014e211a0d8f13dd39b01e3012d99961722132438c487a5f888246f1551687d68be18160f8f5cfd95b8f86b298048511060c6e66cd5f2b29641dc0b76afb0a88abc2d02691d955e24706293323b53034ccaa768422d27c26167fc21080125644e21c7857f0a4be2f76e314c5c58da5d416b0089d05952625ddbba528a704d4b827d2f7665446afede1fb6080cf8cec7df70e01a4617c0c5039405b8978a74fe087756b9da0430fbdfb1a99916312972203a8b5311a04f3ed7d9bd8c0310d4f38176625f2d3fdc250690c689c291f67f44ff6b794aa319a75a4d1f7d8cf589d70da014bfd789120edd1180ce8ef1eab87c7a0930c4de5feb08fdd81d2d57a91557a2cd979ef94ac8b0b88cb08e7345785605de9aab8d37c9e84bda0fe5b3bf82b2858f4d10feed4341b53405c045ca53247c6c64f67201001e29ede270246e2ead7f9aa593e2eceb5eef68cd77cadb0b773ab430def44395edd4350b9b80ac2e0f998045ca25fb6bdd53f8d752a9511bb1b76184d70786b0270868a9558426c8b35b0a883b789082bc9cd5f4a2c4557a75643bf37a0261df110d85a9ac6db82d1e48f9aacfd09cbc38847e35d67ad18d657ba9122ffd134bd1a4f437c1fb2dae8a07992b85e03a6bdb39f07b6971922663f560f4d96cdfbfd66730e409815e0c09765a3a1a6bf4445ba46a0556ea99c9c82e7b87308dec95e8983c837647e3e17bfb6c7452224cd4b7d5d4e7addfd9b6dcd1f62c2836394d4b204141c986867671281cfa0eab24c28ef5043f91b1995a363267dc7b5eb5c35cd32599378beca4fe125b08a1c6f8d09dfcc8a14d41f60a9513089d3af11a4384c8eb4460994f86dccd9c9ab1ea7b7f6e6bf032d3bf5933e2a8ecc3117f5c5af91b9212d76088846b18ecbb153dc909336d3cc915065f73b7170667637b243ec8bc028d0767c4d85617ca0127ad802c560f7057770ac39185a13dc33186bdd000befa42c52be2e39dd9aacbcadc002de88ff6c8293374098906a2e3e8205263eda433753f3cae97411bd4b4ebe88e7847662d2f6008f7225d6b82dcb69659b8f7747e4cf2efcabf403ec9d03329c1a3f2e4c06c1c1179057e24da1f95713515d2c6d4e521af280ee9e424bac00e8c83c735a6d73337c081f2d54f1a898d76a5a37851b738e08894179b3a5fbb62b5d834893a1b71b0803762fd88f042e5dd3416bc0efe71d5dba614ca90526828770eeb8f8d8801b2ac2891ee61c94cc6cdede7cc3389bd32d9ac078ef71f892909a50d1a24b290fb9a122acb981f0a7e654c50e52bbc0d9c214e67667b91688f97a595fa2b4025b1bbf485e08e222bef4c5a2b5a72e2f722226a7c0a4064261ee2b6be08ca9d377ad760869225b789bb10bbc60ced413cd442ec15146c58cb0fd80fc97a5e2c06e69c6a7f5b4b0d4c93a04c377545552886dee783c534bd71f97eee1d561f10565fabdc75960bca4cf8a390c25908d5ae9e3b2a90457b0449f3bc6c23fab2ca5b6b6aabec0ebde1e0db076095893f6fa6cb889a1b3d35fc449e749fb700d138418cff0de2632aa636cb21df0f49ccef5039a01f79cf3433154edf38a136258274c704b7cc6d747092dd779185898b6815c1b1434697ba220427efb2cd0b0c4308beeff11107d14c916c0427bb8d903cc5a7bba21bf28ec44865a8b915c88e284624788626452f7a382f93fd359b677cd7ae2292970889ec7882cf5c50dccfd0a78438e0ef498b657319de5bfb8807813121144d6519aba464621a9df54803b74c56de342d4df2318d0771962b349952d237a17dab7f01c6b6c0f528fec15a80d232c10514f84bb76ab94e321313ab54bebccfa56f0b6a2de5267b7962d45787cb43301c540644f263cf8cc52dfe0268682b88f02a0bbae9613331c68c68b02ebca5db5a0deadb9488c13dbf7b0ce82173ebaf83c16e3160c2506a5bb2b25b0da453043e065c1c9807daea04d1853970c1b02b335c6c8cf875e71068529d3b09c8bcd62a147d6ab7f8e19b09662f67f2a0cccdf56b2e22ab643753279d2d42575baf09c7a7216bde428303e0339fc2995a7689901f13f1c0dd6c20b920c8815c718ffeac314906e464b33ff91dd706304eef29dcb385afabe328e22177ffa8bb5732b0593c0c0ed1050349fa1ade4824b47800a9eb63863d631704cec78b0907e52b88e67a8e04f2f1896bef14a72133a7cbf21e4c91874b6cf6108c5dec291daf826ba9ea4e8cc99abbdb36b1e423bda3e9a2b827396248cd00016797f168850b48ccba87a8345e72c85ac3951fe9216236039c259c51d81c9aa0a534c4fad90d238799a396216ee8fd54c35c441956f3d93e108a339855eb8e7662b2a604beb236ba2eb4a44b9173fb4f613c440e2847afd1a5a1ee3ed8f817f31ab18f5e6554593d12bf93eda059cb31aab6852eebbd6c22ff67f615a04d30f7db6d64e087d52216fbc51559c7acf0e58b06e7faf09a822a9e7809f25871622908b7b9e3279792c3845f83701a4109bf5f04be27fa1fd98803346332127d7cba2137e4fa932cc29acaf9658e0825f27486c34496f45e0619958e0d1a67388090993dd2925b57ac0c8080e90cf9fe1205df624011938e9a1a6ba50e390c3a3a6c3793c7019678ab5bbf4ef8d24322264fa5e34a1888d32a3b56e68025beb7be8025920e9a0bdf7224d5132169e6fb63a02fa66ee10c703d0a259c8da12c4bc60020f9420fd347e0422d0b48501106fcc03d1968c8478c35e45a0b2c30441776032830a1a0dd00316539222cc303dd1f9171ce498a302c6f02c4f58950584650eb7e4831a099a24af1e4a9ac78487c538d8602f22bd11583d6bde90217efda965b13af417096844a6e16b6aea9465a092fb29f8a6355e869f48c27fb570388330b0579b1ab8cb9d397b5d258db3105ec3747072a4f7af7655812bd268d89964d423c599bc3e03228339bc0952cfa0772bdeda70ad8c42096bd6dbb226de27252a46d0692557893bfa032be8d17e8809bcc4a39f2265ac5d5acaf810ff047533e13f7b80c1abb3217dabebafd3b2241a6cf745cdc6f9dd73e1aeebd2f726cf74163d9e34c2dfbf37d2392b25b8e0740d21788efaabc0bde9ac980b76f980217a7b10539a79de1255b88848e64a20c05f500a9df3a31a9e9864210705bc036a41b40199068b51a206831326810567a3d88bc0c5325e31929e27063a602c48545fe790a6b837236b67ccb34976a58425eff5aa0e4d723f42f3ac56b8d23c45e68aebe8d489df7df6ee140934dcfe66bde34426694474acb9219786b110f74bf6d33cd19dab38794872b5a24dcf332deb8dac523a72f7b21d66063b508fbfaaefda9ff1a816fe21b2dac93549252dd0635eb1afdd885659880e0dcee17ed39bbae8be15d4f016a0315c8f45fdce6af8ad0b378eed3507da0cceb1e184c360440e16338cfa83dc3429ef57821ad5fdccce7d4237694b149fc4cf62a79845f8f286e4370ff2300f97746a93f0fb8f44d8ddc0022796394b19999856754dcf2c1f3470c6fa94ec45f592ff03b7fe48e93b54e1f39b15de38d4310c1dc81eb495e47aea0270f4f71c484a7e54279b256a42afac3f71dfd11ab73e456e6b18385bdadd0613bc0caae448b523f0db90d094a854dff3cf52874bab2ebde982e542abfb1896a7a95f4708f905257c21fcfd41ed1684dd8dc9a98692ac7a9c83622bb93c7773d743ab87db694c7158242ca37d2c1ca3701c2fdc51148e067b38fa2233818028335cd673adc23a0c2140e1a087d3a294d9bd1d7b87a251a10b4d845c5ed3489549a26af58a320dabb55bf8acd4c063c28a19a11a0d825985b2dfcaa49367434a66c07e0b678ce25ec4a9a44b103d074a38943dc5916ac14665fd69e7ff784809385d1ca0ca280ef78f2d71f00381007950dbe4914168af9b150ffdc795a64188764144249e0aba7ac4b1e459fc083cfb50c7cbd397054cc52e08513c4504aee3b82ab482bb6caf69463b3b0733396d873e8b92311e160e80e4ae8c9ae30c1fb3bd161831027e9ebe374844c34b58935cda24ba85b7a30f58432722d01e8852527f3c5552b7c620f47cff4e4fca9c58359f28d40670bdf059afe5f8c52158452211c792ae245208b0fc04328ea2eef4e24588ba020dbb9c627ea32606241758727f7fdd38cbb4ce085b4ebe9b8d98fc7a8f2ee2d622e07f423535e2e61dc85f2d810f67c698838949a6064ee4cdd64435b85488092d9a49c22ae33b122b789812f318d259aa41fdb47372e847211069cfaa962716497f6f7ff6cd290410e1b73f9594255919adf4e8dcc21d985752065f4b1543a55bc111c594667e1022ca447d62fae40851e1a24b10949c3b88f7dcc5706797c0befe29519945b39dfaad2e81c47ba4d662f8840dafeb3c22566968982ef8c53b86f18d31a19f988e8e34385fd89c96462aa11ab3d1440349aa47845953a02c708059a9cd905eba874ca48456df7643fc176387234a40ed1990405320bb7d35e3458cae9db30b41aa2953489b0e160a580b124824bcd8faa940e236ef4c224290eb1b9e309fb5c27162380e2545d372e58bba7e0e650d9534a71507ba59a986f54dc2d57b1bdc7e42c679a4cdd120eac6eccb81a8a741c991bb70a2322fa8731f3ac2f2a6586141c07d4e60bce44b075127826606b681edca2b4908639c29ff56535523be09a1aa2096134ad5e402d0a9443c572a3d31b9993dee02db7e015269f6ffc49426f7a28dcb9a0cbac83f957a0d9e1fe624626954185c1b66460a5cd73c62d61fb4bb7aa4607c912524c19d6797d2965a165ce652b6f56f5724111b2ec4a6126ddb993143dcd0eb14aae94f19907994899588e59637588ea5d24931ca5e18e092f65ee45e7a80c5eaf9fb8710fcf3bdbb9408755d45dc713c4cb7432d5b1eb9636c223492118ae5fece426f59bc08e50ed53a649043497d56c0fea470f6ff5f4872e27f9a8256488462620f49f6ce2515fd32820171461212e15e438f216f6a8f9e8788b8b058c53748e4fa861562be935768515c3601d9faaefc145066152ae5de70624782a9aed3a763dbb81ebba776230fbfb3e547f96fe020ea913c0e1080848335cf9d5972c89fa4d6c7109185e49757aa32b51028a28b71e21e50f0b2114d0afa4605af1f6f04f61664143b6e06be64717eadef223f326413ac86c94d2690255dfaf0a760beb3d4ebfbd82f5d3945f2cdf6f265f32fc3e7c3402964bb2d0030384d2d34a46e7e48a398c5ed783aa91fa254b291fdc8a879240710d03c5d7eb5466cc629b1e4e55a147cecd3e4c5b4af15d73aa34b412f43b59980db70538f5ba6246107b893957c41054411532d117c05bdaf0138f0f37d37aa0e6cdf02b4277da9f2e976b67acb82ed19b9021edc0c57944717c030179c63732e6e08bb5eb01a45c8e6fcac47cee7fa7c3fadfe971ff84d182d2066ac183d02cc0c7442f97892e4a1b1c987ddf779a2b39140e51a608c30b70fff4f9cc48d594ff037a1337cb249560081b103fadd503352b88cdf19e4388726b9f7668b5fed8f8d6fc4000dec8e5409e86119c0d616f6fda196ca16b66267e6c39ac17b6aaebc0355a8c28c54928ec919437bf5450860cdd96751aa6d9e8ccddda9ee9a3d793ea2ace9b14d0049d735442b892c8fd150c6d992c45d2fce892a9aca397decfc89da90dfddb99a824571246100fdee499ef0a9a2d60e8c675aa833c9c5bb9f586d1b1a6e0ade6d0efeaa9a33fad55cc270eba5bdc8db75dc3f1cce50457f63e573a70bc1ade44f2a369717404d8091f7626b4808a91f18469c51ade5fa2120ee89226ddd5728a289f1b6c3a47117a288bbc22268416128e305997a799d99b320ff242a0250203c9a15726d64160156734eaf37e0b1b8535404b6659bf17741b96cc002b2c4466aaa427ba6715db562b51564a0a007cc878eea1b02b9cc8fe26ca26d1a30c2304aeacac89658a3064882e2cea9f8487a861b1ada8a6d0bed196ae35401ddfacc5ac9b141bb9fc99a0aa0e6849413ec3c60c87f7561c43010d2823515c935cd0a15ea1b55834b1b8e1043b8f4b9d533d6c0d1775f6ae39b0b89c77404d20680608c64a4c8f96d5af1997ecb9f85bd00c533c4bea611b71c7ba946aa4724746bec269d6be6b2825a6d49b3bbca4ff16ab12e543d593dbb021a46a3fcb086483eede6a1d4384122f5fd94ce4de2c883d4513ce8212ecdd0db88e166b59946a1d4198959e872d6d1f029d8e4685477146a957a7c4e3c84413774dec4b58246163b227a1844c8f9ef0372c995eef42806ce21c89640cce7d5a48c3c76e608dcedc327646af69ffa61409b77cef7bb5552deef4325a20e89da192ae0d9a5005c90ca4ef79852086de7f1f2ea0f481b3887783d32985b60c59e105d11889da50619490a6689322308a52286ec9b2009452f63b281616537c2dfc6da774884212126639304088a50623b6c8cf337b41b8518aa844b2831fbfe2e7ec313e0d509f93475b3f10f51bf8e871d6deb63794c86e95803c9f2d3694f65b76d6abf8fb45003138dccba5554168763e635176ad8f999725bd3463a416d432d210accd6bd625b8d81fbbec52fb9a7ecd6563d71db5ee2db769c07fb9ff769463f528f464abaa25f3f8c9b6ab5bc4923dabd9d916017796978014027a7f57db9da70dc39fa633b0647a3a4ceaf86348f519fd5cb1609ea55b63e6fb6cecce131b371ea822a4debbfbd13024305ae0602aaaeb2a091914652c1d1b97fac7174fe2b16b3fc4ce4ccb7216ff499db59e04c90a0f4103935c2dacce139f0592074aaf632a829810640178e15073fad3c6827959665f68f8e4d323b5a23e316921419425aafe13fbd790e362c1635d6aa0dc75b103702a59d2916fc00f4a7b2796f6c2e63de1b2bec17d7d23b2f27a60c70efcb0070c24b9ef0f9ae2ac07c5c1e71d83fc62ad0af8dde74897b71ddec5125f3f72168b9c441d2a4a3de432f321a34f4d3c63e917f078df41d5bbc3f56719538555be69b3fc2c25404aee4856a0ab3c936a108ae71dd89861109f9dcba57926aa05e446a320905469fed05122a78ff9a15e44497f4b4e84827eeb7a2e4aee2468fd6711b1cfa66d6cd1670e37a4ddd94ccb2857681c3bc4ac28e6d94418eccea172777e552455df607f4359d03bc341577b7775dd92d0c4e5a88b005c23b9061e0987ae995d2afbf22b8a2cc5c8a13df0c142d6cc8ba8e01773d2f5d020f022d7fa85e33b8542f039fdec2898276b2db25d4493d17d390dc1115791ce2fb21fde3f9c1140b9a5b59185d6a63a6fb72a7f1c711bb28236c0af1da14167ae6085c243686b8277e207d677c6fa836c254c6cd27ce0b28f83ee090b76e76ead0ef984f6756286f8884cfdbeb4c2e5279f64eec8cb6fcc15119c29bbf37b40e7d368cde535bf59f1f0b377d501a9d72870a75c4d944deb01e2ffb71f8448a686f2c86fd707e754ebaaeba6235581f67a42165ef5dc5e87e26b1939df33bd9e3aa4a5fc8226141500bd2f35f3866e8413430158c0a89931924279458652aa389f7b18cb01ce4d1428afea4f261162b8a724180f7c72935e51294c5d53485a33e378656ec08b596eba1d568e74418ebd6b7bc6aae9b993a0849ff156cd172a6816b010c2dfe2167467c52446d81c58ab28ac74610a5b16cb82576370b8a30fdcc90be837cba17217163c44e55a8aaac26a625fac3064625f2a881e964b720f278d31c58851b2fe21123a09022905f470b6d328d3ffbd8178fd73d120bde3213c2b6a62644e602a5181569b8e9cbb1e468a72e924ec811d23410819ac497e12d0cb7a958831cfc373a82c0acd5375cd0cc92962c4114b188c59c04357ea31838a1cfbf9a3725237423c9b30f64e45a29a670a90f33294c64a35780f137f0fb52fd4a8b56d65b4a2cc1da8373470aedc20e160610beed19cc5e9b1bf604929966016050d5440704b7b24373eba894cce81223c360799e8e87aae12474d7b5623a7a19b45565acb1d671c15fd148ef4038fdf699d2b3b9ea9c08e3981e416e851f9468ced9c96564046c94a74921f0dfc2aa66890fcbe7919f32542b303b6215d865b45ce01c76ad4e0c4e1719c40318e0ebbd39d0aa8100f59230b7141805590878cd793616b8dfb35023a51d1d8f40c8edd3469c9caebc78c70521c75c21c8279cc83eba2f7ae443146dbcfb1c1cba900b532258430c1e84683717d00f6c3d1828e4da4184f657a5ce9a9c95230e86917e445794890e49e659d213144502b9aeb64749c4c1df1161e8eac281a9afe739e7203d25b4a032fee3a7fbee0104a602cc41191e89c55230747a6d099b2e9a96c62765c600c520f8a84b52c09d11e4dcf670ad31f560aa5d85a66ce70a4e2b99ee960ba941a50edb5247412e2a2c9bad5d61442a5e147ac53042be9a27a7630c188948368c455b91dafa0ccf0f315cc4ecad8e0b0d0701c7c5cb7c85e224c6505fac991c24087817da3eb748e4d1792b1c1a764517600c8e9c9f7876aecfa1cb2f06925e4f9f9dec433b8c006944ee3bbe0de67a640587dd6724fa1de1bd91b468468048b692b91ce081978ab0277f922fe3cc61fad7679d5533183efe330547c6690d5488e085c08f9451d987b4b3f6623271e350e0d4bf7dd2d1e60c3f1cf976233aa34f6665f44a89cd09c7b73d4e881a2a300644d663eee16aec47b5ff1e5b062cc692a8baeb661b15c1a3cd1440df9f4277cb6c5f9413ed911d9e31bfdc017e2388c27a223dd8323f51cc14d8fd1732684241eb654f6192d1789fdcf5f130b311e3c6ff1081184b1a472f158bfd7713386224ed3d964624ea7697c1d783b8abd2c048e741268711ff1198f31f93aaf2784bc79d348038b0fec2e442b7a54b2316b49adcbf41db8cdd0f76c0b2ba4268747137cea38ddf4c237ccf88ef11a3ec5a6e39e6fea59b13cb30b2681a18cab596541365ac8469237cab06c97f811d4290cb0705c1730fa66e21c5c57884a687026454d65c6205fcfb0d1dc934b59023fdf7453e0ffb5b094e8344e1a89781fa3c82850cbc312610b8d83d3a18d57941608ce9d01749332545f3761dab11418fa2b784dd99c6ede23bc306e6147cebca4fc15317c45e29cb8abc3f04d9981694f916406a2b9330986831a625cbeba1e092f619a11a872336395d23fa35b819953e21ddfc54f092de004851b78118a8e3c9628c7e00a94acbb34830455b87aaab222dba61cbea9e4d44a08762c6aba767aab1b86d54ac03cd52d52205e9a700769b7ad01b3f2c84eb79278e047a83a9d5d00ce2a1c253fedea7c7e901c504aa0ca97c4bd094efa09e6a16299652841c9484bfc120b037273e9a54a468af374c89150cd8c5784d2306f38c0dbf91160a597582d8f761e423ca7222033113f57c192c4d38d5fa690e8244f5132ed89dfa1519f39b4a6631690005417a054074cb0801881ebf4bd18a2f6e31ed3bb40e39612f5ae02d3d0900a875ec0b6f4b5ff3b1cc17a2947425c8b40a16517f6f8d59c9a382eaf64569f4cf1ef06ca2889a4484e59a1b33eb43f7a87d6f66cd04b2cb11031635af7f86656ac839c752a33f497563c5595ad8b6c2e95f93c38d58ca46d60386854ff8f8753bc8bb206a08d694ecd7acc6cb4dfd525d406bd1c88050a8aa25afc0f501cd71c66b5730fa527f1ce1243035ce18127f8e63cf0cf1ee095cae27605f20c75853c9ec9a800ee67917544ecf025115f3fbb4082c0a8dd0bfa10fea3ec7b52ec132f87d93e3a60af7ed747694f8c3314d2d26e1325567a2fee29196c21f3ac72ffb44b7ecf8bc445af11469c762a027a76c362b650e02e473833e2ec2bc3568112142b0fcf92bbef2cf73fe3b405f4d28a13a21acb0667e07b094a3133f18cba5b68ce6cc297e98d5d79595f365b354662d5eea1f109bade71fcac712106f8f8d7c7409b57b72d57346c4629f971664eb13fa3aff941afdb1cf67fe93b029b44d863c8d89e55bba5802673d1ad811b53323864780474396e5e8a2012eea5496390e8f8228e210f2e9bc4099c926cabfdc64a561147aa0a80ae4ae450c66fef64d086a7525dc975d95010ca0fa9d99559a7c663a54cdf1c057b43ecfbd399fc40e28337dfbb02c963a1641aaa9d4dc0b05570848b41dfe0ab77a333584bc8890105a3812c14b5647f7a0596064a10ad89e6026c534858d4938aa7f55cfca97da885ecc8469cefe55d0c4ed8df093e6a91610b0545346009c637dae105af59f68363b617fb013eca5968d7377632284fb560e813f8c38ec2eaca970f98d6db67c05d9d6c0c5b89a2012969fe76f91e57e07e0d608f7c6c087e5ef7ac38d5e9e67eb0b574955c8365223b232b7de64ab424708f5a42ea0f9d7dd07057cdec11c442396a10bb89312a16429718424619f3bcd2a7886bf580e22a0b7480a9d291fd618b33a99e1e427e050b36787cee51c111f5bee5e5a2b039ef4b0fbbcce7cf303b53cb87811d1537c7d679fed7b8619cf60f482b377b8de12b56f30c7fcc762b97538003cff7e1898242a65a453e56a3fc0d89fd4d449ed92b4848893aee45ed0b71eb90481fd33c2ad8932356cdd10a4e30d1ebf8c5d8927a6522f1edf48345485b8724fbad25aa9bc16a566a59a138d4c61cb2868c027fba9043c23fc844ef31913c945318ceb38154e6a0441559443ce2af8d107f7ac430870024f4f322f512e4e641999471fa1ca0986516a43008fae7e7b828b343a1f1a85c8cf97354ae6eb253cfc45cf501a9d033fd2bfd988100fe5c05019a5aa8f175a6ec902b988544289c327198382137962016e555b7e7a3804d7b56edc8047a33e7b0f2c65441fad1c80c4b412d4140b1bbea1245004523a966882eb1912dc0a9ef41741405dec67a09d0ad0dda336e1b4df2c8bb3956036602d2ab025f8eb442f1cee7b7d6031429f6a85e3ddf75aac855d701f484025aac51595d400cd24a07d7d6aa264511b9b597ae4057e1176d0482e3006f62c4ec1fa591612001b54aa8c992a4da0e58e6eef6e61e596527f60878c99f6ca2d9b9dd37558aeb4f7ab9a8d380992351fb7af9398ffede841291bd37217b6f996492293205e7056b0551c0ef6e8c4f061583769d9325cea5e50c5264159665943a15afdbef764dd6b80192edc1124bb5ade0717b27d88ab8f1d2a5658c018f1b86a5ebbe2430ee3db5d4eeb9adaeba6dabb5b719dcddb9544e05ad1ea6a94e4fa26917dab5c86d75e38e6dc7b6c27aab8165b46cf59eb7da9ccbc1c5bb6eeb4497d7ba6d75eb28ac7dd6e91b95e6d2b5b65c2dab9fbb3b753a3798ed60b0d94cb9eb105494525ae9b67175b5ac3a57aed4290ab8d0ca71f40575297dd1f79342bb749974636206266544e706e6a5cdd3c52d6b597753da4d9b9c4d7677336566e6d97101367657052a07bef8ea9cb35a3ae7b6ad3ae7aa37b91e5c60f0ad1e1d574d5d21bcb8711b6bb4dbc64c79c5d4683e0aacb5f673ba74b4312ff71cd364c072bdc97523d85d71efbc63db7157e794503ae52adadd29a5eeb452a74ac89c7271efe25ddc4bdfb69655af75dbb676f759c51b5de75d57dbab7be1c2c5a8762ee709d67a6bfda424b94cebf2744b5897a52e4697695dccdc1246eb22352fad5d7caac7024ff2240ad44a6e8502c9508a8af2a4284fe269a530458a14efbd9ed8b295528c53c2560ac3306ca5d05ba93bcadd7d157efd868181ec5d7d38ba9496b992d3adcf25d9247f3d71d50840937dc2a5b095c22ffba995b60fc5370cb0af3dd53ffd5d0df6caad0ffbb27bb875555b0a451886b0307c17ed713cb970866da595ba4a092ceb8b42a35b513c0ab21da98ed074c69aad9fce9a3f7ffed763c1cb980005f88795e2cbeebfb4f7f5cc401cfbcc7fcdf95d8a3bff75eefc14a3f79d331c7b9cbaf1236279b56df030ba9f2b292ca5a4a4566aa786eaa85eeaa7c6726bab36dd1ac3f36648d9923c894b8a41046cdd70295a3dc3ef25a6a6567a8f7eacefc6ae65acdfc61928dec6bcb3831410ddd64f9296717524a496d16cac65f5757ee74f3cfda424a0673a0155a569e74b7fb2b274e3499ee4525c49c759b02f5ba97b2739303788d1911db4aca2203937f2222e547cf7221464fd1f5d88fad4f7c0988e9323c5e8c0932051a06e427deab7ba4a5b89e9d01435ecc66e6aa6962db5ac95a84f45c122cbaebe0bca3396f3f2fcb646053bbfe7cb56ca799d6f256e81a5a3faa7e7ebc4b1170786f30cbed079176dc1786353a2405d542b45f59023b4acfece97ad74e24baeaa0102add64fa52a6da5f5fd6ab533c2c628295ad6329cd1c6bc652b85a3920deeca888a8a4a413a596badb5d6a74eb400748042530191d7d3cb074b4b01141c685366685345b422ccbc9ea880e81ab1e6729322882ebff3379dba5da9925326aad46de00bee5f6a2c14156c57cea83ba32859c26814b3bbbe96d59f533305b77f4acda82935a79c4e6d2b782fa715ad082e3f20390015114338894193664a903389c0eb8abf9ab093c5b744d41dc0659a981faee79f6fe197e0652010a88ffbd7d6d7a05e73bef46fbdac7f66d47fbd5eafd78b74d1af31f67acf35e62539406f140f509f560717944eee7cab7eeb7d2c3bf71d72088750d0eb2908a18ece2d790afcbfa065feb1eb0f989792fe4ebfacdbef8c433b3b0f1b3db0e3727d155fdcd9d9791d388276481d382f56b2c6bfc828b0777d949dbf69913863095388a9c951119109398286b84d500e59b413042379c05e8728c2602fc2c4d701fb9cd781b3f33a72704817d4a71fa7eb44b29dc2a8ff39f802ac4fbf1fe9f774f49f171ce7f52ff9fbd99de0ea2748bf7b91b1dd9796affb53ba7dadb40677fd9b45f92b988d7a5b46eb37c963e775c0766062ceebc0c9c1c1f91d1e3aaf2347270796f33b275e87ce899dd781edc0eaef3cec775e07ea96b1cbb42d4e3b309d1c1c9169491855776ff26a9c219e7ebe013426b4e480030bb433416e98817bd8e189922321128c5922021a1349b4333acc508858068520539cd6c08025c969088d7c0620677ed45892e58b101a4e00b304ed8c8e5bdaa7f28303274547cce8c0b464a98a0b2c413891ca42d5040b324420f411fa986939b183abae11e02ced7076959162cb2ced4862ddbd0c90d6501b35ae323e866e4eb03a818a1566a8d65a7b9031c2231366a8cd8b8c0940325b88dcddfd8909a9214e38386142490c649a7e90b96195b303d72524f2922c9961284b932e41602093838c9010d45a6b65336a5e402a8a8aa1c913a5315475cc19a336750c976dcc971d6d702ed3cc5059025ea69951e1bb3a790c162a30e19053773348d7cd18b91ebb2bf09dcbb4326cee77995666cc2dedf3f0026781d2c3d29212506569018530472c160ab9c7700801591285c70f2f3df8a02281199b181b2936b1865571814625f54a037ba1b1250c067ba5a96ad9ab8cedaaaa9719209eb7f462e2f5e475e675e6f5e4f524c9652246445c9c3211b9650745a32e986bc3299776daf3a5260acbecad82542fbc98789da1513dbdccd0c034d194488a227e8b1d8e07b2c096d7f2a87d10b4ad96ed3a16ab6f97f363b1baf7be9fd53df82ea8d7faae63b1480eec74ad9665756f7f46d712bf25b6ba6f91acc11245511445513c61bf652dd9df02c756abd56ab544b1e52d0f0767249a3263fbafffdb9ee5b11efcefc5f1fb09fa37f8bd7f64e9912c328af80c96300b9ad2df2d763f8e5ba3694a54da972ab77a01d373ebd6e1b8e9b0481255b42f4597695ed6d0bc807901d1de6cdf3ad6d7b230984e5718fb219104892077beede69c4d7396eae7ee5e379d20388eeb563949b0582cef635da621d1a6f5812008b66e361eaecf5a6b5daf5710f10bc33014715a2ae87c393939393a307fe1c4b7b3b3b37382c72583055f0ff862f67c4f0f08635a007a2818641a1252685e9a3ac537c7ef07efebb089304b60b0f090c38d932335c50e42f0603384871d4749628c6c1227ee354a982476c498d0e30818e6e90b1325e448069769470c71732ed38e68da76a830f86ee41c400c4984a0cc10060d19fef2e58a9227477620420491e7dc82fe772bcf063d6f1d37e84beb9cd503cb911506ade4023af0601643c60acb40348b0f5a84109aa2821f82fa610c848308d416a72134c860431041fdcf40385258926605587698d0c292a07e190349a00710528cb61061839aa04981e876b1809675cd9e6f640370cf0f8058cfb52a32b81a74ec0160065f78e173c081abc705070be0c0d5b36a56ade99f9e15e5fb85a3087d815e452cdf9bcff5ab1720cbc117b641b1b50a377091a55fd7bf7ee74b169712e0529f4bc74ba94f5703f47cb95dfa5d142d9c5b884fc992860697feac652c8c580a83768055f4f78c1e9818f4f38c374614e38ce1bf43962960ad43e69038e4346215fde2380212e2c4228a566c0244e004ea0535305ca1e100426584903205052b5a82c277fd178e3f6eff6b5cf1c2f50caedee53f57feb3ae2e576115ed4137eb1fbf3eab4c2badd56a07334627604209282d637eca0710a3a71f3d66e8a9e4c57a8c96cde6d16c89c71155b2741142e10baf816129082117804059e13146a4d01024898b1825ec24c504426b5c0042488707e0ea999d65d4c6067e6bdcb8ab6780df150ad2a9a96593a9653262fc30cda9a9e46207193448a8525873e50c0e100cf121071242504d943c087206cc1227453c518380205aca242121a2cc95596532fd943a4c55553c99b4dc9e555ac6dc651451507cc78d9331a0bf8d9455b00a5a7bb4e05c4a5f0845f3b432954411d892a13cb0808cb20b61e64c0c84e3484c102a2d34c920460541fdd38981be3037a05902491a286582fa271403e15062c4972b46517058c10f41fd338a812eb0e44996da910b26d021a89ff6744f198f63eeb376735f2b4752fa2e68cfe9d3371fe2087d10188829c9f3a5c3086cc953970ab90d9bb78f3b51f74ebfe9d88d1f13a03e37eee882c202809c41fd170ab6847110f7baa16723bb9fdd67c415301501b0e0320e03f1ff70ab1fd64f364073a37763e7dfa3b38fff8a142a6001b8ee5f52760a24fbaa576b8a5b3f356ad8884004be860d193c1fb0fee66f4812bc67f5db90c1f3c1cd73cf912480af6fd2f5dc7bae675006f8dc6b9cb9469e7e1c41dd7b7ef381f7ac6f5006f837ef323e1996bc2179e8b7a0beb1f3c8182b32aa71cc2fc1e2a8dd09e1717f351e4eb5a22d2111a1096a3eb408f14310351f4ff3eb6d1312fee05ef3513444864f966f903b89853ecc62a10f33fe19c0162e253124357505892a20e86104643a354421205146aa882f42476282ea95a9dae58715c7521b1a22b4f25dae150561c3666162510f3cdac244765a94838f4a5ad9b115b94044910f138a7e0cd58a885cbf5c2b3a9299a9ed0043543b018ae740db9bb536d4543361081c6228aa1151210a450080cb35221fb5a11514416b3992d4724829e371536f62c0eff52cc009588b96da0ea7fe698e5b701bb7d82ab7a8945bd0e6163db90503388eeb2639637bba6d3326b76db5ba53da3debd6fdd65d83bb3336fa1bc0719bf41484926a398cda30b5b9a10eb6fc8f59b1bbc1b29e21c0e29ed5bd4f06703fc97206c78d33b655d95de69c52c56236b8c76a30412ca4614643cc3d66e6f9c5e358b8c2569b9a6ee882edbee4291068b3940ccb3f714db76a00b4a27dea735d672d0cf63f6b1911528cb27a17dd4c7426b3b7254ad907bb16f83d73ef711cd9b58c2bbb6ec1e318c7711cc7711c0d2e031cc551fc17cc56700bef634039fcfefc9fc5797e7efe679163149c0f459bf3dd6f61af70cc19bf7db8678db29671235fd73f05c279d7f733c8728d769cb58cfb9b71c5025ac63d38723076d032eebd3106abe09eb9e77e72ddecb02b0bde56c97276c1ff98c912fc4963763330bb33be677d6439bbdf73e2f33f6b20924d72cbeceda73e3731f02d057ad9ef7f95dfdb0bda1216e3cee0bedb6c7b0a737653ea5ebf58f8c4321942b14cf2164ab1fcb505f6473d67d5d5f6d1afb556b61d7339c2a57c66e6715b7fbe536f500f99f89c33644229ede12a1053c17f9ba19318a55d0bc62af835105271f1eb388ef3cd2332bfee614b0e2ebf8f258beb1f26d9f9cc9d0e21930b5ab03d4ea9cb9642166870d3f5f0053b3f64c262049eb7c205b7905def65d4a77fbe6d9cb7bb9f210cb6b49676ac011ec812822c4797c60c4b072d6b59ea3357f382a32865e9bbb8e9680cb694b13c269ad2fd8e0fba79fa0be89fd2453feb7b5894068b06dce8610814a54b83a3ee8ab5226fb86932d42682ae3e47c9600dc482fa8863acbf48c68a5f6c39912652734d38320a4f93b66eb552cb54a294adcf818c50060b82054210d240dfe89feec892b1802f3652049f555845fd1ee9c89d01922865cb8954c32a550322f44f12fdfa134aff94ae010b17240d6eba1f5821082f5397064fddb98d7ebd0baaf65e70eb331317c4cb2f64f25de58eaca5083ea198525a39aeb5ce64b19e90491db965b57254bcdcad6015bccd7a8699991f267e4ce81017ac823560516b18c5ce771ab15b57c8624c389c32bff236c6f3d901d4a4738a403ba0316ba251773a4dd289054ff1944f5a43d7aeabbc824511eab68d3512206bf7c9b5d52cc86e562d1754e0ce1873cfa511bbcc2b58f4ccdadccd4df48777d3f929d370061b320967b0f38bf0bc56aecf33b8bed71e175b3cab60104227b57ad8c46912112c343547684d34d19a18425bc1118d0d5177d358a804ea072966686b96686b8c684d70a1ad495267e07f3822d0ee590ebe586dddddddb3c7c4a092cdecd36f3b8c8a81ce10444d0ed877ec3714b911112b5a8ba649141f9d7b5f1a26d26cb9afcbb4344f4dee55553cac98e6061e422106f0210c0a599658e9b2c586aaa5b00a88162a00a0943182882c637e18c26ba83aaa52210577a741d07c71a169a2885b41d37483468823b43446d2e4e0c19966d775676164d975ddb33fb1c771e0a7342ce2d5eb1133982d420931684eb0e2c353b01540fc2b20d6f373958bb27a8f670a6eab95e5ab516c1f510571ca8b99629425432728017344494d09162451892bd9c17d0cf5151e6a4b6a4b7a882c085163427482b804c5e59a92274a52b8a57d1a9decc432597e77bafbb3cf16a1383333f3a424152336119950f1301ef7d6dddd546cd3e9b59bce2db39d928e141393cc6662928aa6960298bad1dfb6c771bd0a3a60b6b993fc2606fd0e8a466cdff2bb1e573fd188f5ca2dbaefedb90634e00377bb3e637bdab70319e0b79bd4276dd54abae8a7cf130629035792fdc799db00cd6c635ee656536b2948dd1bafde6489e3ba830da80cf819eddc829dce362212e91fee9a8e3c9fc19e3fc17e15f8a42df6e8c9cccc350833736566a6cc3dba364ff7e279ddb86ed5d1233ae54a62915824165135133d9af2ba719bd7ad566f42e5d48bec7c168b264e793b07cfebc675ab8ea7e61113553781f2ba711dc744d54dbc6edcc644e575ab4c5e7d1e399d953cd51db61162151dd40c89d22d61174acc61b98604aa8684a986b474044a34c1720d49931a12540d0952ed48140fb1870dd9846c4236219b908dd82364239e10b209d98846a20f9187c843ecc15fce49760af01cc4207d2302b1fc747425561183876b2ccf2906b1fd5db774a4a866a44a14221e31333333d31ffc6da2917b157d783fb338c4ce6777177b74ab15108f63e2907d21352505c68b94d49aaa27a827a9a827a827a7d94da97b9ddd94bad710a87baddbc6d56de35694725db75ab158de8ac5f240ea7d1f08b65a37dfd76a2d59ab74bbd7ac69a32e5ab3a60aea492aea09eac9694a99493349923b917a4d1583011342a941971adc762616b39b52f73a9b52f75a37afdbc6715db7e2bad58ac5f2bc8fe57d1f08b65a3760ebe6c65a97eb157a18d29ee12b14451c9c1c10468e0e1dbd57d863c369d35b6ee05214d7ad88449c739beb1106341ca0893468d018f152a48d9a226c4ca82a628647d589cbb5224314090204454398c0a6fd32662ceb72ad88d32dff65308a30511fbe34e7728d480e446ab8e5cf946e607cb76d1b63b7ed68058308d28a4893bb11996244d55cc82c115b22b6c46cc8512c06c63df625f6251646c8901d428c7674976b4288b621424290c208c931a4c4111b22258ed40c29513b52e272976b475c8eb05cce72b916644d77224fa08edb87bfecb1edc3d5b64ffbb40f85a23dc578dc97dde3fe8e9d9220042b19905092c6cc8a9cf5da838fcfc71110589b73b906c4885bdaa755de24823aa58e05888551fb395d4a5fec0fa16ceb720d880d3520546ebd5c03d202125e19c52959c214fe293368d0e06f3203bf81d7f54f7773cece7f1e31837128f56595a3beece963ff45789d7775dbde46dd8dc7966dabfe934f90c312164260049b294882a81390a40b0322e4dacb3520396ef93331b35909ba6e068494171e6c90ca618448131e7a6c59f5404285a72c2b243b7c60fd38936406b3523b74406c5801798a726669051886f0c18685a61e4cc01e52803cf5f0d1ea81e4e96347d07e5809a5c8232f39865648226929c87728436b3c9828123ed57e0471bdcbb51fb5304bd21405dabc4caa6d42511f1236a1a8ad6aaa681e4da411928a661488fb201f9ffa6cef23cf074e42f71eb47dfd1b256cefcf91256ceffe374a20854ad89eabdf0fcb800a55755212cf07503c1f8ca443714a3ce616f366de7ceb6dc860fdeafbf59f8c1bdddb088024bc1e7c2fc8f5947ce1afcee5653e474571de73de5ddcebc1ee3df03dd02379fa57e484a23ef35f2338f274551fd5d1467dd07bfd8b24c105be3efc902481fbd77390f74dbe68f143d78b1ffefc46e2d91e4790f85e1d6df4cbb8f9d5d3b72183f5ada75fe3831bde7f7ff31fdc00df82325c32b87f7d0ca2ee5fdf77ff2279fc73924610723206770638a438ce0f479e8d9c56aab5b181790398a8a8346e3755a9636668000020080a4316000018100a8843e280388e24a22af90e14800b6b80426c503298064391480ec3200a8218884218844114640c4206198520a2ab00384cd46e0c59d7bafd9125bbc32a7a6caed40e18c6ad04599bda52527f09ca900bc53fe979a97f7671da1580f0a693c1d93b59201e84846789568bd6f38226ea3012680842569287cda5e2e0aacd80135f3fc88d9aa0d8bbb57d2da69cab0cdc362c14477331654405195e657767bfd46681f11d8da18c5811e6804ccfc5791710270de845c8e80e47777d841963158d4a05aebb8dd2e797b93b6419e49a94965e55941e62a3bd63da2c81183766590e2ceb1063b690323e666849a6f0b639d259b9a78dcfbe355e4612694d1612aa6090cb56589656ed1c5d16f196e7d71386296e126f5e7bf93d406aec85eda9ae53cc689a6cb1a9b2803fb50c0927331f2d9770c2bb04855879da0d3df7cecde6e7e24866f3951d7f377b17c7a6792d4d8fd247f2a328382c12d0698ea4cbea978357e2a799c9cbe56dbf481c47622979d1e27f319fce37ccd1a5d15ce6ff70e50074ed95b0b67a166829d1effd4c1156775bdf55e399ad99a5e1fc0aa9218f5893269fe6494c655425724ee15e6bbd2721d8e616af1da33227aa892dce973859e22c9f2cdfd55ed1f11019e9bdc8be1da05f74e8758386b6673bfbb34ee1c0f6602b59f789014b5b66f2fc6673d166920c0eb3c4ff83c877794e2f4f17c89e162fdde31ae53d74a8fe9e40e03bbab4367c0d6d89091aaea3d59fa06ea5408292f7a2066cea393a38846953026035d10fd087c8b2e1acfa512f58a65081b2a020179a394407bbfe366a88fd84f683f28942e51cae0a04544e96a2f142c5c91165d5e5c157a938bf4fc8d6c8193abe1c36cc2c678a49bf87e5a9d870fa2a88e88d8728c7e728f30182df2b3ac7175f3eaad8936e95c00a2e283b37fd1802b91c7d6ffa1d7623df858d3f48dd5e93fa00e143ac5c92f8db7f9f07773719e86778005eff4085aebdfc2d06e20de52e27348f72cf46f434348b250db126c94feaf35451755d61d9882bdc38801058f67aaba3400308622628a6ab0d08941e405029b2576719534f349166353c2e2aa575fd879c9e8f2328b211620ce8811243e4be959d899e4aef82cc0f9bc4ccb7874403e10f340729a8f1c0c2fc32f80865d1b39736cfbc4b5e84ac7aa039a3944564c4f3dae647697e59d7ed8674d67863fdee6e13547ab62f6d79a81e83fb0c80f454020023f02d5ab6233ded7d17805073f014775ae2232954c03ab625018c81b24e9c2e75438bb992a60afab28947d817a97c58bc98d45e4d7f1f4d0ba76b4f9a4eaebcf8204f2ff7a04cd0b79e721bf8bdd83693cf95b75c557e4a1c5e1783b45450206cbabf09327dc08c1c268995474c855cf3a91e51b213a7974a42e5eda45770b0647298d30f997390c4181f957d49b3ceb55984720a2277186f34f8f9b8713bdb1644338c919784c10d83d716ebc41987dd03a963d18297e20048c4cd1e4ec49532e83e2019d71dcf77a41e04ebeef7372c7068cee399433a721204a71f9d40589325840b856e445e2921cdab084a8e68031d154d4a7a36f8a76dae7c3f59776aa39f9caa431bd481d7b0303a998978c6cf3d0352da421be8c3be62234588eb31a5466803ba56eb7b044b09a3296803f5d76c07db9a2f425c01135f406b15fee5fd178027f0222901262775ff6ca0cd72473ec302184dfe154023f5b0aec169317f619e2cdb6d44cc5f76af5476649bcda171459ab5ecc74857b2d9260d1b18185767228ec6e76b6460ad5db18a48fa824906d8b321e9e867675c1ba6b492bedaacb94b4326f7f52cf60c707587344cfac2fcecd593601b57a84b3be9cb473101a373d8da6c8b924789eae87423c4370df9d3452bea7352faea303ad632813142f9651a5ae90bbd7a1ca4fa83cfb06a2153137460365c0df62a6a625c0cad79b4ffc35d51308b9806e0cc571c44b89a347b5c6b233e25b89f830bf802a9f6535093c6c6788fb407ba49e01b7ae2cbaecee6dc436ab543d41c7ec2ae9acae3f0c33b3dd59b3b6872dacb74a0fc8f43e2bf7a4b632e1df69ae91b0b3e7d7fd1650aecaa174553393dd54b6c46da1402076c9997196a244ec03128f6611f5111fea90ce9821155aed6a830cd8dcd8527db212ee19b8fddd63c51302452e0ebb1fab3ddbc5881fca00baa9db225885014d441d0736f98aa9002da76cf17d9815a56d482600ad6f298ed780d70d593f276033b4487683f1574e74b4d7e3d0624240baf86553391c1989d35c53ea5597a4624bd8e76f1850d9480423c377dba64a9ba63d62df2d86b8e0b0175721c630ad853c498cd261a694fa02e697074681b238e2900c4cc5ff66adec02ddac12af4b448fe04151f22239e5afec43f7c7647fa35914726b838ebf501edd254e93d17363e3bd45e5d7bca10981aa2ee5eaaecd3fb1401cd5d739d6236b3a1c2d25f8d08d3f4e31ad262f957851f4752bea7b3819cc0f392a95064d29e5bcb2ac8bad001aaaf3338aefe0d220f3cb322a553dec8207e327fe5520b071a2818b940eba6fbac12645d7f3373743a6305e31530de558cc906733e10b290373c9873c9759a5ccb93a8ae72d7692cb72bef58dae9fece3660d8e4bf62c2ba322ec9c43c1ca1e1ab616de9fec8823aadfad67a2ea2691e1d9b54c6fa1a12c0e632f903ec7b0252a7b3a0cb98bf6602d0ca2ec0dc28b396b641ffb2492a66eda1fbf80b60e7dd64c49e8a94497d81ecc81502300d55a9550e6c707bc92fef1d709e899b5eef00d09934b3ca299cf8eed004b8f99325dc9692c05eade0b207454ab69fa1605937eb66b919c617067da7bd25b03c6c206de15292a2e4492652d602817da3589079dd6d3fea01b51882b91c8cbc583b91fd8618519fd17d8e28742743356c9c82979db1409ae2d403466feecab779855de7769a07aeae260b7a007bcd38eb286346c552666b019666e09294889403bf65929a42736c83237b9c48a56415e23f47d5bf47a33a0f632fd3a76062fc68930159bc8d3de4ac2c4d6327bec50d19a56b7a4719771d3f677c984220e45518e2e38fb9275d0dd27a666d8fbd1d90df982096cd8bd7b122c751339d3cc09fbb28af31964e0f6029ed5af9be8007220ab40ede6854a2177f4c9c0ad5249e3758aa240a206f44b5b37ab413ca9094dd7285d2f1dd2c79c31c3b8245f6b30029f6e50e1981600001dc6e0214b5f9b7a43702620480c87e161045be572447704c18845ecec0a240127cac0888850da1cc310588ed4de3184c4c925efa9b861209fa25d7c7c474ab192e348afe4756cda4e72f79e9fcf47ab19a2907d1563b83c1d4d4fd8b428eb674019d2ef8284bcc72447606c7ce8ffa81fcd67975a4d944104c9f0e5a175093be2f9b1935298ab01927389fdaf42df140ecfba2915937a62ecc55bf5faea518c756c9544c2142500f7b2d3ec86cbe8eb3e7e5ee167c315266b49d4c87ebd35c8e7f3f81c9f1ba5e15b251cdb0a653b6d73ede674ea123bcc92b351615591cc0e131ae5fb518177f19b2f658925b084c2ba7ed31752c4d1ee7e79465da1e077ad691c917f919d9c683ab1d59797250fc19cfdb9d820f20beb597ee75b682fb9f37907d8c14e382697189998e903a605cc084103fa35f6dfee18336541bc49b7f283daabcb41f0ce56d6ac6bfabb9dc8564ccb66f5398d50a95e79c26d73779147969c66a61f38f54ee07724f8af5ced7b9c6fc9afe444f9b60e5e12b204340c149448115a7ed28fd2f7e622484074db35d8de113506560b95176f782df248d0e1f9f321adabb20b68d5180185635794352b8cc81a77220591b5a0ec82f7aa9748729dae54100ea9583517b5d28039d1f295e1153fce85c54ce198459791015977a0c5af36e34c207e2605b1393c4808de961f723e3237e980713e2960b089295ce8f4ee9b289fb37ac8dbb4eafc4cb06c87103246f354f3cc1468fa899ae6e16799fdb178467840a50bce51a618b76e312db602b6e096611160681641f76393bbf0644075c6507c55b8f8d890f07911d789d9b3c1c23148fb1c55e219b37f83ce3d1ee250d18ceb561741162c8890168515e77b5e768b70c03d0f383367852b292d4718c72f930ac3756564325e5f31d5582998815bd4a531f31aab136bace80b8a54e9272a953ae394fec1e36bf8e57f78a50a7477c139dee685cb012f11b5b6af744e2236f9905337e0c53f16e8224d66332359b1b330bde6ec67081ca7565125314e2a090dc90c44070b3ad736ea23931b4a3578c14e7f06ec9f615e26dd64eea1addd1c8bc3541cbafe1e8bedf0e1482f87e45579fe9a8041e356dfc35ce88f0572aa271c6dec498afdf52ba0febd25cf44505abbe1b2573cf08be97262914624a76b7bca7c4b43ecad43381b53911fce40285a8a95c707453a509e67b70ae1ca41e559ea6402fce09ee1705c6b4f932c9b43aaf3c04138c54e22418cf8c42f1101454b0396b556ac254c5554c56abe82b089b30631ce375bb0082c1bf96d65af40a02050f5c18176d19dbc858a59f89def2ea4abb3233901b0e7c2543e4005112d8b6f4ac8e9bf7faae29b0b46a2e138f1350b281d94bb52af7bb93fbd3f3935e51563c8c7f32141e7b27314b5e96a4b3294b30e9b7d56c81df2ecbc4fe9726194b73a0d583086c7f446f58a07e04aa256c27a044a3e9e79d2b2209664995250524dcf664a600e2b6daa74accf1f812cc773e65454cf8ae9537d07f1bed501da43519c1445f99d88b42dc9a9859fd91b86c92282b5e9e98ac44a221814a2c657742fd26329b005dbbbc2774bcb4b8445b9a71a8a586653d151a3995d1427f5cf3541463a87d267154a87d8d52032478692eded41d09ae9a0594a19afdcbc8432920034512630e67cf0817f6664e48e4743052abd3d31488f36fbaa31130cb78d488f3b26ffda4e950e79f5bd685c48421b3b00bccd11b49896401eef7bae27c7aec4e35aae5c144681f2e1a9efaa10ca291eb7ee617b481e10277bafe2621be681f5ce1e63e544750f9dd7ba5bdfbbc4422a6935019ee29b158d1af9a62134a9d01e8712c635127e4c5e18e4834d4ca6534eb614b7ef85d6bc097c27bf1582b73d44b2a9e4418b85dd9522eae4b04f744654be477835b10b873b25dcfe81ce23f469668992eda444a64d3b969c5e6b5ac601c1d2fb72e1e733673fd0d5e9242de9fe2c1bb84fb7abb04a07fd2e16e79d204166c453b84f2447c6a5942e3972b120f74f1a2c8460954541b1e29d8ee993925f31a0c6b995ea37ced7844147181ebe8833cad846ae8203a2254a8cafdfa375ae2f215d4b6ab0c6c5d533e8d1a7ede3025422849c3a329a48bbb2ae95be3c16de4466b45a95d6c674e6fd7cd40c3d274e517d73695feca5062b461e11cc0e75c45f52cf9514f36dfe2c15d6deb6182a2ea28c9e8d1ace29dfe6570c4e997a756532a01b93a8ede801219aa64562a4c7290bde2dfb0f0ba1fa811a14c1feb850a1c6ab0fe0a290f388a5b8599ad59012269658c1f3fedec4d8142d87106aafe849ccdbaba701c0746de0aac3e3278bdabc96c3449dbf66eef739d1636e0278cad77503a8db302a0fafa30456952889135b93079ea40f80281e1de2f9d493e7a0550af511a1504e5e6e6946c219d04f5f68fedbd3b399bc06119d38fc444dad1cd6f3030fcecedc30ee50f9d4d2d2756ec6b50727b535a009e4820834de501d20379c0bd3f3c11fc1e69320fe15ffb9e26c85e8bfa856d54fb6a1b7f5b19515f2e19ea6a7a069dbd61717b688ea2fd1b672460af140153ded6b670187edc021d99045c635358829012cdb9c999584d4f5798e74d625a26bc7f13a9c8adcc4621e94305a3f210a47f26cf4a64208e9208a133046ca412cce925c2f549fe26e716650d90f42c09dd056de0027e9c98e96fca30338c5209cb917a1801eb41df1c4911524702d027e7a545ebe45eae212ea5a74f64faae64b98167cae94113c5117bfaf2802bb32665bfda563ddf96fe98b2387fb14c592ba7024f3bde72b39d15e6efcd907b471765e2c237c035b8f162c7f465023fda43448423e06b9e4d7831b8a6ef4fda44e75418a13ee7ceff2c296493f0a888204b31da1020e8b32128ec0b1fa496c77702daa13801715a84cec2dde30941fd822ca37d87217ea4bd037ff8d5dfebc2e5ecfaca07e0d583158947f2f89419a4f2a953c51baae37cee8c99331d543fd7914da9e98fe244d86c2620a6656de56ada7443556221049e6f4771496fade1b2c7795176d088c8c4926212bf302a4f49baa8ca0562db91ea16fea6bcc6ff4e49080123fb034cbbc486b7d70dfa3201b6da3f8836c5af0856a5ff64615da1193d2897776ae7b8932778e26b7e5e8dea00ac31e659aaf2d94eb08ced06bfcb40f4f782dd06e0023742cd8ebcdf8b447ebdfd18122427b336feaaadd5f96af41f9e0fa876fb67aa15b397c196a2031a1ef36fc44e3da119c21a4fa5799361de3a4616b3ce1d0184e4209ab323c2680a87507fd54407897bbf3ee30171b5304da2d85afb61190561a8091535fab645d378a822a564146b96b04743ba1bc0b33a7ba04cafe044bf8f8cfe741468456bec8bb22542f26ddebe4d64abde541c8219c696385c6c64bc953864897f1017f4a994a71550c5e78ff98edf6a36da50751b1e97d8de941680372666f879fb303c26785aa5fedeed8cd202faad7231aa01b9a220096836551a27df3ac72dfab935132bec72f16912fa8c887c4a20890931085da64a17e2ff1d3ff55d53fa90d10018c648a29d963ff0ca107cfd69b93fc303783c1fe8c634468a27bde048bbfdfc50167211adf356d2909c0924adbc27ea4b8c36a54247ac9a1ee9b2ec1d7bc3794aa2d751f3f1155c633f37fc14c42cfbc5b2f3a9737f8deade13775bb6ac7ee2f5fc6fc2a40b8726b0f3e2977ff9180dddda0f25c1355ba33ef80fadd86663caf6c4f407074cfae27a048f85fb8e8d52c8e6e88e49cda5255c62f8450e2250b43244f27c4ab8e7441b7e4caecc801481e03e8de27519a1d24209f2c29188c273138ca6b3e49a040da8ba49c2f8bfc1540e3a6072c6224bb5ee5cb5ecfe30d52ff2a538103166f6e4252aef6a534da051489911ce182ace808366a53795503f94aa7d764433a92da219548f823f82f4473b8138fdd162293d6c7409bbbaef94bcdf25bdc398fb3f4b20313106ee0885f6f0f072a7f4aaeb2ae5cfafc72866f345e4643d7b27d007f624ac74d816f528d3235c7b5fc8a033fddc85872812474312e9d2bff1dc1a5aaf27ecbeb3e4caf6da736ab75ff46911f574bcdb5909c1560e135e922e52d16b35085fbe4813c11a8c3d07511c0e0fc86786d3fcb0262e8864c1e5ca0f91dab1d03faa8cb59fd7f1508c12804ea27f78d4f483687639aa4c6306ace295b9a4d3cf843af515dbcd8b77d18dddc58538ea811f7c12804944ca2914fccf8ff8de265104c341c8669b02d6137f63cc0441659a3ba08f0f970aa8341247aad80d88a99342be185f6c7924c62180bc01909b82b8050df128217832ba6d1b43774ef749c626594269c85950f35cfa7afb0586e5a12ea57f71018d2d2bfc9867938b6a39ca61f889f40838877d99b2e7f26ca3c564e250d083393446ed37517d1c6e204633900bb93a03cd69ec5a60b9984f425b35f95a24212eb963ffda87c81ffb51ae17578fd398639cfe85809d13bb7293edb6ee4b3804b0e015f443d0c0a490a9c99e2c9d5c33103ed070a616c3b88208b6e652289345a0c0b50fe48d95b0d3e0918feb7c7ebd1d8172fda8914631593a82702419a7a592ceeb26bdce9a1dbc21e63954845a617b3502cebd833d26989c230378a0c4e6ad29600f8228e05d53fbc414ec66636a36b3649fc131920ee5ad930cd0866e93a829c8d3ac7476a3dcab44f40e9cf1df38f252fb32f9bfbe78b789f2104d99190f28de5bd9129f322996f823becf90cf9e0f023cfc352198e44862b05ca9d8f7238c4234dcd8dc414c4d21a404c83d60204fb5601235e9d584c87fa4c68cbfd2041b172365a42989337402be68215064f85b0bd0071eda2ab6dac3753d8ea06a72ea7ad8a54fe982e413bd0201e87fefabe461fbedbeb6432ab755338f3cfd37d28750055ea640725fda3bae980a834604ed145d6b4e5b63d99c1c6b34de0880034c227ce464b05a1c44151ea97617e3bb6f1153b210c96a598fff8fba1fe2aaab0c97163c266b86e2afd14ced11e9d1afc6716e490a80cdec085bda03394acec554626e189c08761903561b021455a813083bd75ec18c368f0942548a8eaed97948841ee123e20d130c44196be749880219597f843d31682b6f89414405acb4abd385dffa251c413035e713051ee62174844273cac31aac4981a3b76fed0c99c661c7f33fb89d1daea387288bb75f4125c243ca03f5a7a0a9e70e912072f0448e97e5f4daac63e984a690f79f4245a16bf0a7a5fbe16236e3568ee6afb3637679451083d3da4cd5dc690d272ac9a0501e1df2aa787eb9643d052d81c658c9a3196bebd7177f549d5b9dc2213f0d2f6748119f945b49ca7f97c5c9a4fbbb5e818c5738e34430ef68cc1bb47436448d9519e1aed3523ce99e1a2120fde61a0063ecb1681c6ef2f6a816d4dc28f54a85a408cb87ce3b93b0989b078a00d5aff7c4845375545377399f22f95b3ea5511300e3a44deaa17aad751267c7f5b96ec661869d4cb0011d71133cb47074bde6803bfbf85f379d6634b4a4cea318ae11d19af9e918df5d7d3cbc0d899c4f1f905d1f00fe697d26ee71d9934d0c482f488634c12bb5834432d4bdf17d3526360244eca1e75c4681dbf405c8669a209424712b2af4f059344f717ada4f9582fa8c3b935c0ad4aeb70956d3d9480f35f6ffc2425f5ef0fe8c2dc324a658761883d74664dff514410450de6a06b34dfb8abb4844e86ddda99cc4854c35b0780774f9ef17451d1103754355c1ebb3966187b509b71767e8ebb33ea006cb4d3514487ba89b9c22633503a4db57df9895209d3db25f4208f50ca724911658a21d4568d4fdfff0223e0b5504272b9e6bfe2f23343ee8af2a74a43c0b6b93d862b8023106a3e2e523cc5979d5c0aba3b40d0605439a7c5a4d3ed39c3e54568fb3d31430e51a430ef41c20c1bf4555bb4090dfd1aa62561155fde2fe927eb72450ece3c61375912c3af25d11f751347df2125d5565ef036cb1ef9a744169b74cde2e4359ad3ab47434954a7c30b0cf25e6dfc866baaa4cb455f8e231dcf484d44ed821e3b372d33e9f864396a52c3601301d7d8f149235cb8e824702186b27a1e532a950637d6e01e583fc0b17637e4e4b38987c5dba2be4e75dc9aea5ac39781c244ee63de800414559450df592f209eddf32cf1dde85c503704bbc071b084bd214c7baf5826441fa254bd3619d6b4e6bb56c384a5fb70d4921038614fd33191640731728a74b6c40de020eac0e7d0fbf4501216b27b5fcdef6ec6a0a663d79799011bf59e82ff982a10e1675e48fc26228037c6ee469dde8dc2b32523047678c7bb0e1bb61470ca488c64048814b951460bff50463678bf929fe9770614c50ecd4f8a92d9fec968026ec24d8edd2e5bf74b416c67f249e6aa00f843b5ed008068cf14e5d1c8bf54cded3fbb805767e900eadbeda1da23611c81b5b7d90fa7bffd329e8165221778bc33297f6033ea41d3adbbd9fef942b1aae4702b0a1a0f76061a6ced92d8f25df1c15c89f67ac1601e72a38857fb20c37646956ac6677013c4136e5b128a354fb8a8d1fe4868d3f5d6062a9dc5df9d40fa39c00bc8c0930e1010e29e116d61b9d5a723ba0563660e00584de65095f18eaa394ad829c63fa240e1fea4798c336502772b8b99895bf0b097b11f88751ec5985224c576a27506ec6a96e49ba61ee21c625dc7ce33deecf177c0d67a726ca4188492a7e98ad1978e0e0f5ad9f7d6c08863955c40c3e93618f0654334de8109ae7ba23498c87e8829df2bbd59a2481895c0f5831b4d298ad4d2829831386c38d89579d408ce71aa8b838676c4875815d3b78a42e4c65ef915e72e8e291cbd5ef65d1b814ad8f574e3546dc9ab90a4030b585aeb8c436876b966e82c9ba0d73e1ccb45c8d875235cbeeedf4a6c7153404419ed83130fd9dc50946fc90f07418be0aee1b182ed57cd0eca3b4f473f0338df5c35e0be80bc49a9856ab206a38c6e221e329d98329a6cb0f4e64a23b0f904a72b24c9410bbdac7ae58c7956ef0951ed66600842c93d3648b880168f6a392ad33f2e830934bfa00e94172ad1f58621ece710937b4a83a131d4625cdd1976ca8f784664d0ec0b27f79fdb7000086b3227b14744fa449445a5c4f39942331311b74785e621c859f8109e31b518ef5260ea265d38422f0645126365a66f47f167c497c4b515b51e212bd44111ba2c332b0a01ce9e7ad18a72a3c73a1c3edd30c481a9dc330e9876e30b0ed419f6dd8214c600ef4522b6019cfb2f7a7d0a2ad673d3034f2114691140cf8cdb36c1037d454d2eaf562efef18fb25904ab91f7ba24ac3c9dcf115a410f62a4f17e834a0d3475bb098c642f49faba930f846bdcae3281528ea3f9c7d6a38edc7833835a0c3584276099bc811706ae6f26eb00b65f296294395723feee7e3d7ef5555cd4e2192e537da8f438d9e1b8d73904c904331da1871f04b995ed57746c0227e564a9233707b3650bbc232b831ff14acb515f4a8344cd48abd87ed2a740633dd474cf1aa26777c587ec813e490f4142fee26b2368186334e331de3b82c35085b24d31e5c51f970623b9ae91ca8541a928d62fcde38903bf5bfea6e7bf8fa4a8ec2f184f699a4b413081a4f8ec214fe69133fe265e643d6d07f1b684805d1d49210d742788848d96b4e3da0a8a5b0d55f96017180f96617af54df2a9c0e30bea63fe0bef953483175993d578ef6f0c54687742b249e25631ad67a307e1cd425a22cb5408ea72d286a705b2b9fa08b36aa409a51555e031791acbc6134f3c08e06ea74e70a5cb5d47f64a07073407771e182792a17eeac25d7de4ba0916b536d41fc9d888a596d617320b2368318a3d69037864d4d1c3867b8da5c9b435da1dda1b0aac539618b8d81105784a11cc69bb4cc34e0e553cb381313ee0189e97411cf182999ed041e0fdcf35a9606c77a4345c72fd500706bc45467c9c10911ab0beb063fb020ea632d2039c638bf180f7a3788c4011299d7f6bb12f02fb850045b20ad8e60a9f0d5ca5a48d95b358598b506a49125bd07d969414a5a0aa52f675994d48688588ac498e7f04534c96032aaa541503aa5d47a980844e269433987dfd031804b9ef5c223cefab1ff4017b82b64b42164e9928a6af8e1aa2535c51b876cbb6840dba2d6f8c22e01e821ad3b8c4e3d5b2982482e485d131b26df2ee67e2004c06134e6687acdfd4041bd181301333f5dfec4f55ccfcdc3cbf4af05d60ab564805130e5efba0c5c8451971b31d39f66030c1c7962a8f9b07494e8476b61e9054b73484057e1a6ea535830bf54429a5055b46ed8b5745285aa4fc400bc17d626a46e7d33f1035104b8865b9c5cdedd5f3c2642b476a0eb491a946e2035650492a9ff02fc871bceb620886daa1222aa948e7a8de66e28563feae31863166e0c33e6d19d5e8747f2080d6482b61a3cb0377c8ef1ba1b0692dbb6d9c9102ccb3017647f126fead8755d6afd00183fa6e55b392a819a8f6a9c831651ec12988a043be46707a84b78f92072a82240f9dfc8eda434910c035109e05cc27a093e80c48108af236617e48e0c9ee2125874d43d36651cdbcb97c1a4666f04cd8469cbc9fc7a0d31c8bcaf03e2c8f38c603ae58beb18d8a7003834c5fed31e76c70de8141158ece895968e0b5aa3f8055c412fca42087b0eac8d17182bd8e714beb71632385e8050f4d18b31df9f1339d962f3c51653fb82e81cffb29561da3ea1a848724b75ccbb52ea03eeca5e3c166bd04aeef155d8f663722fb079586c75836b9df7dc79757f82dcd557caeb9ef8d1cbd904ca58c6248cedcef3bbbbf29e1d4dfa5146d4c9bdf95b6e546d3043b66507839cb50b07ae66df97027740ab070fcda3015ee91beab8464a85564f2addeba0d5a6cdfb44a1eec46eff3c1b562c44b6ee30183925fce5aa5f0bf69fc372f63e5584d895d7fef3e9af14b2882d940d90fe35f193a201f3c601d8ff73d533bb155948cbbde86554e389d857f345c9d56ba1827a8af9a560a977640fff78fc8d4681d222302d0a40a3406911983605a255a0b4084c9b02d12a505a04a6cd1418a06dd58c48d5103b60fb07eae5ad069fdabfd6839eeabfd5c14ef7b7d5e053fbd77ad053fd9756ef60a7f603debdfe541a92cbe7a4bc9c76196e524710b292b63a9c830efe4cf60453ddf3c6146019a7d1ffab8537481316d7c13c4dd854644e6747d7d4ed39afe0ba353f1148c234b86e051bb17bf2f09bb770f5eb97e05485483a3b61f3374be87c507759461f0fd3dd9ea24eac4f18295862bad948ccb6aa7b5203bac9ec5775c35a73e3d65d9a5249f46aafa3756c115904a296f924c084a56ee8f78728c6ff5af71d55ab56ebcf277c0505d41cf670519a40426ebc5c0219c158a2ed8aba533c976371e534efc417b55d646532acb50e1e45a6beea232b5c8d467cb229c082839f41cbcca5ae872c052d9cd170f793f5bcacf97d49222dd8e5d0eeb3a5714091bf94d866af2f1766dc40de803c66aa198d6161893d691345ba9b8e06b86eab80ffe6198005a720b35416416a46c3c125cea8f87641ff0c4df8f085b3a9e3bec34d3834cc82bb66347cb765a8184f8439c010cec3f80b19425e66eac25cc1fa5e239c9fa1d992a7a1fa8fe65ee1611a00de10dab228839c746db558d6984066349a0f013e5e0a076843f7f0ee5f4899d12888869a3cd8a352337fc51e8956339aa5d24e412fbe1c0961cdbbc6b69cae8a2c2ec134e7d255c33abd01df53c83ee18d0c3a26d38c4302522dde4afd3db825464633c25449b864e2aea0d117e528cfc1ebfe7c8aeaab57f6d0a0762a773e8ba5aa255c4dd44343b95f4424144573354fcc4904b7ace4310b385ad7f229cfd9943282916dfae8c848784680cec5f085409dac8285c12027061cb289833890831c9ec31cec409303ee5594ac1850d500308a9554586e6485a7b6196afd4365e223abc83236156f892f1855cc2ead6282379543791f6793d291036028830cc1abdf851b1a7285791b4714a8b4f18eb6a51661dcf4130115ed8a3b7ca884ac02dfcf0d18d6721d42a0ad8d3fb689b2ae02e0b6bd3bdbce727e51b0dee61eda75998e2060d5ee6fd3be8497db6f55bf3a939122c619fe16cc94f2a14767d61e6bc2cdfb79dd5aba1581ea62c352b4bae8de90c36be90a16e33198caa0d0024bf482ac5252d39d3aa33bcdf7ea2e7a58042c6dd8acac3c1671914440a33db5309f053114f06bcc8692aea802397be7cf79043e346ff36917fcc5d1676dc1440b320ab2823a7fdb8af7ad993c7316d2149c7900b4377f09e8734e2ca04d30a5518eb9f6f219a56b421e3202a683d41be48510ad104797341683b420745cd4dfb1d59c8f3a995a872f6418a6a742cf1f0ee7897de661a19394c4c5836c8e07e4cc8af387bb14818389129f9aa63c2078456f219c732e34f23ec458d939f39f5fcf641d28dc5d25a9a78b42ccf1277ddda99f04d574c352d5ddd0391e6320293ba963bfc8f39aa0098e2011bca75542e34e733b98e282116c88a031aa58d480ea2a4a797e1869b6ec59c37d82ba33a0928bce2ce5d616b8c85fc11ea3cb18c45a6906b42f66c39b3a4de65564e312d990f7ddea484ea29ffdc1a727ac94f4b613f60773725a0a301372cf44678ef79c990a4b892a773d0f91ae33f9848f756fbc786ca48ea409b9b39370fccacf02392baf108171810ea6f29af1593c577470bd06eac13959efbcd58a746385a2b9a4a028894aff89107d36c84e473b15f0499068456debc4e84c3addb7a20e5cfdc31947a37d9981c3d2542560eb26f31144f6a28909cec6908e1fde488900fd5444585ab7461c235a015c864c4a033f274992202cca4292bb0e90929b73b824175dd007ea611104b74b7aaebeb93848c6c3f82a1ebf8cbe1abd040789b43570f88b34a5cc66697601a088931fcbcc38718c043b6dc99f9dc5c436776f5a59c480710fcc18e38f1ec39a539adcdd4526f4a4bd7baf52b02fa7cc1f49411e92621226271d19e6c1403126f2d80778f211b0fdb25cd6b8106153b2cf6d385ee51b66f7ceb13656c77acadcd343ba1b251c0724a4d052d2d200cb0f02365511eaf4c26fab21f0366fa939d7a70484b2782d77a3e6182630be3c82dd3f3404a2bce723f7456332b7dd77d89313d5b7939f0756a279a26394d1e0c8882cc482f8ba84cd48641af32deeb1325c3c93693558807075b9155819df173136c8c6db57a867a0130969da7bafde5bf0bab3493588ebda927f7b3d0b3fe490bdecc5f7b30a45408d0cb678c86e0a7daf04bd1c0cec0629b98ae1f5205f5a1f4c84e9078eec32bf8d95e083d03a4c1c93c2acb61425cb887fe6a14d952fb0de2d0fa1f14cb250d188da26a5fc668d8a72f4de9d236152f253dfeb6882b4b1618d628522b22b83efc35edbe7460a917117e4d4579384e24b0d4e93721429cecc27f3379b56f43485d8966f8abd9e04bacfe060543b6da6f4bab1cbc922e02f203bf7ec4ae43f0965ed1813a840832210bae96f686e6421fa83c16b218f2189da145266b36d84b58dea1769a7146bf7b33e84cf0278feb53743e0d6e9a688f27f0b8ab5ffe96269d8635affe2e4a2cbae9336b0fc515e7fbf1919a09fbc0ed0ddc9a0347c78d0a315da8ec8ebd089cdd6192f2b2ed8f54eb9dd0823fb0aab64891e0762a0cf48f59001e3fa75a3d250485bab5cbcb645aef14de32bb287123e87287834c5dd787bb1ab4111c2b1257df53bbdbc41798a0591f93a3caddf544d4f82dd92e7e4eccf468a0d7a245c26f180286d9663af7d30e4300aaaff050114628e5466a045da3cc2af3b836775507e35588371e788d296fcf2a1086996408e2c88bc4dfe2d37fbfb69ee2231ebaae55638eb6ddcedc3d24d869f2e6929dc6c85f56a225143c60d1a85185324cec5073c9690bc8f7bb7b46424a202a798207278a3d7b05ba8d18961d213689d7836e837a1dba42208abb70530bbb795f22765a3f43c2a480f5e7457b9002592b0595270abd73e059a288852f0a5203483306d13ddc2c1827381e9559f1d28a806228ff8f0773403fa6d521460d7aa9d85e5b0dbd1e3246377bb763a874fb455fa2ff1ba9894974f0276b162a5dee1c926f6e0866919fd7ee89bc318dd61b24584f0d1119cd3d6ae6d37e468a50d791b5717e5a229b7643b9c2b9dc98cef6321aaa5043158f52909376ca689f040d8ab81d8f3a0d6fe3849bd3c28bb86c3dcee38316fe6fbe9cead90afc74c1ae9a130fc636d5970e30a4d4b660e1d735132b18b7e8a7c95bde3aec210ab0c6312c4515499b2fa089c9f0ca2d85c2d77e95ef7d9e7c16a25846f02809977641b0fcf12f6d86d2ec28a7c018b4bbe03afcee1e2a80e3de1791a01c3080ee90760482654e23d9ffed0033d6a220d7880b5005c90faeb62c973469f1f7ef461f747acf3cae992b754921bf6e546effeadca0aafb22d1cdefeb01eeead0740e474bee26939326a408935719f9063690c03c63f3a12853487b86c419c9031f358217e13e0af01859eac93f882b71fbc893fdbe147269a9699b2f655c1a541475aea68cd6828771ca141f8bcea92893638f5c650d30c2b06e5379dde510839a715a558783509b2c029f90bd077198adc33080ef03a577ea6ee426736d0b08c09819b5c043e40d2305ce5c9c6368a90b08bbb660d5f25081695c31ea4883cff5bfd892a68225b0dd583b95d92fb8a54c2e0d6c35a57cb0f9338b8667e9014e9d069ee1c58a5b5b2b8471c583348ff985d69ab19b8d374eec02c5c08e64e651a3e819adbab98f004fedadaabdc3a06b8726e6f339d281fdc1dc68e4248b9aaf278320c3e6d6b9893cc1273a1af87485723c627624338e4052269d29432702f98e1c11ef19681158bf296ae6b5d163d2919ce6590961063b36d4b04ca238181154b767c91943f2a3893511a2244d5f3b7083928c9ac995ce22c21d6fd89059525d5a98771541d9d5825d389c38ec759f611f46940417d77af4c97cb5a7555d9ca9069e4a29e8899d31911838cfef603c6a42e6655188c404025343ed17e097dcf63b0ec9eb068cb2bebebafe9abc14efcdc385e0e540a13539ac4637041b044a035f26c12c093060f143eea032c52b3921e0937893810feb7522d757475007fe05baae2fcceb29cd436af3159ac1e8d579848680b2c2b6384a207083bfef6d1f4eab510f98b6b204d93c66e224ffb7328b48ba19dd94d93f7403ac7e16145e2181851b0532ae901f3d7c5cec19c27d804c35140713968dd4a31f7086e0a4f626bf7346bd01b608e7a942b7303f4d22006e456e37eb24720525cee70fbc3251e9eeb62dcdc15f16c65b683f03cdfa1f78c274cca73069808578724e34e063dc1e6a29bbd0b38a7658e46e9cb74e01ec836aef121a624c36fdef4eafda4c6444ac2d327b57481f38d5feff5066edf69e3891e85a6e57a8879e50db8ba88e80a6f43b62c08da564696ba30e4045683bd78642b29129b7e01bf24143f13d9728848f6947af7220c1d59c7665d8f5c754bdf2c687702da3a4893e8aa43fca3a490a6a85bb124599df75e992b636a57a67eda11e9fb4def6c1a293be3a9b048d6367c0c80fc264404ccb7fd8484870fb823305a5d4ecca0ac5b7d4c227aeb5d74765dff6a93c9775e43064d623958ca9b9443e77787e06284e2b3f699ff71aac7ef9875ac06e7a2552c0cbda2a54d415948824b04afc23cd267951a69925a6e372cad3f714e25d9b3e7025991ef24cdc8c1af9941ebf9ea2e626414607c796141498c3a41ba764fc99d23435557044c03c014606cfba55abd2c3080ef95ff5d05d12fa505b015a287f7b0b5ef04677ccad90532137a533b880a313fe058ea93036b0211224a0feb23d73d35244b72da9b212a219139b9a6bff776b9107be96b1f914906273e7b07f07abb7185028dd1329358d781acef92994ba220878bad9a58e00acb404a5d7edd12b3c6088a305dba42c9b00f6f2b27bfced10184c1268a9b8a00231cc2058ab3b74cef76ee63112f9b48a8f00e9a09a9744fe3866a2de774bb80afd90b23a9d2b216fe7e73880fd915a1b36fa7af6417794995c3068fc8deec741c617a5038e240634461e40d3ba410af7eda16750373b7c38e16d7255ed383467d0fd5fc6dcd0c3a15c869a5a5d96c5e9df405c19b6bcdc963f94c5d7132f6f70aa923e1463cf9c65610cedce8040c57f329a46eb5e91b1b1818ec241afdd6f9945619ec764e583e3998420ca05b74425d4ee639c1ce696e9b1f6f3a85fbdf92a2bcd6911bfc6d3951e13c31a67946331e42811ae4d0d3c4100f819782864d1e07444be5cb1652aa4c7a50ec56d19723cf96eeecb13ecaeae3e30fafe98c9f221b71cb56d48bd62fc67421090cafcb8176980bc8812f8bad887a66ddfe0fb33316090cc2353bb0f9382e5c225107606462ff54d213d7633b222f8e1a8eda2d9bceac899a2cde00c09f515cc83e7f0d9d9b3373a693e845a0eef5eb9cace0037b59a05d92623b30c7d0ad05bcd04e96f15c667b3b2601d6a7524810bf6f5ddb7ae5a786c38954ee6321cc745ee5d0129e62419beaa82b3ea429770688ba071950d765c1cbca0a433682c0b9f0eb68ea6bf4dc66e6711fe84c4d55aae4c1afcdf3a87f9440e915e93f5f11248b0b65a96859fa6dcc338e40e56ddbce3f2afa8c9a405500f62c225d0896b33b5f65a11693e72110821d55542fd85db6601ec26ec188e1ef34e66edf0b62c948ea6cd62177fcf3d324e414d4f04a13ce2caf1b1ab98d82ca565ff5bf81403ea01565c214cb7ac78a902544dde490066e8d358b4f19d8929c1c29b6aae46e4167bac2f0df3d94c9569eac52ec1a037c6fa21744e8eb524652a8606ed2fc6f3f412c518a51437e3c99cd23e2cdab2cbb712ff6b4b7edb32b56d7186fdc512ce991f1e62052063eafc7c357b61efc4b1eeb66e399f1c6050568b7818a2976b4ed3b72cb028134c64b72d727ba658bc2a06234ef0ec467c75ce860aaa403a4a6c3dec5459b9fdb98bc7285e0adbc9ca1a78ee4353504f489ca8e8e10d64cbda324313976c0a722c68875621c6c6bc564ff641148afa5124697bd79e4903181717605f64c7494c0b3b0b09425c5ad7429a2d619f13e126e6871075f38e4d9e1a304c1f2e85615f1d95e45595dc92ae21151f277acd633ab6b7e26d45b62d565f1db29851b9ba125453ade0355112b404dae7592c8e06a3481fb120c898031942eea6806273941425048a6998391eacc7f855273ebcd7c7004e1bc1ba5f62600e424ca477223fc5615f1145753f87efe3e79f1c44e492692e90b865c1fdf895ae26bf9da36bcb58a6d55114f7295fa70ba18455839f6c74ea96ac8d1eb76c2ffa2d952fffdc1e9268d4189f39b1470becc349853302fdda49728a22a622bb94a37be3b362cc3dd0d3f6e9c454b30a5a112849768567460609734a62fc4f00756ea905d76d1c00a80e5b1bb7a85f492381b4d6d3fc7158e250a09432d2c74e60cbd0c597ada5eb783c35bec1ea47d4606133d51833281ff542dd555d6bedbdbc43c4a307c1f0b1fc43dfd3926a2ae2d0bfba00c2edf857f1289836679268c7d17fc80ee8cc8a1173502c823788255e956a1c440bd0b8fa38abd3bc3841fa940ddc67b3936a348f16153207b1617a1cf2b768a1a276002d1bb960690c4aed914a7de260c9101201aa2cf32a8c702d8863f7f9797e469b3e70bcb925d72a10e6aa66102a064bf873011a57e35ecef96641131bbbd72f7de0ca80481338b2351780426aa1e80935147d85c4e347649e7b6aecc1d4d21aa62adb4eaeaac8879b432445109656b4d846212235d6bb8a39881bada511ae2b223228570207bb6cd7105c16dcb3bc5dbf3952ddc73501f67756abcb320fdbad11112c543d81adc36903ae4f6c9220ed53c78e4d149c265069cf68854a2314a60c06bd9f6846fa0277d77587120525e885f2979393f315e9a4cae402904fc93816016f14c276509a9494b43c6862d9083c2b45c5d8b1629464280527e0e2777ae09af368cc7320bb44847e07c6063eb612fc32226ec87d511c950a0adbd3e53bac7088e561f1a8f0ccabea2a261115b70afea027c3bbaeb9e23ad143b1a02b343c69d4d7217dee1bc35bca6edd3ca19cc828ff895ba68aee5c60e8d4b46cd8873f0a941c8671ecef72e70244df6cab5d534a8f2b0e3f2a85ad0326c552b2f69d27833c07ac05eeb7d18fc1ff20098522b45b21327e45f1081aa28eba6b062a9984252dd38e024e3528ba65088c6bf63c8fcd4b9ce9d7331a5cf881cb36722b189792196b16bc18f0905de4fcadfa356d8965b587385b9a95ef18e9584139261ed8246505e249e275e83964d6bb47b559e83566157cc261b71baf1626d4592c44c023eceb504287604428a3b4f2ed0b43abf27bf95ba909d28210772a263dabeaf135ba1d888af4cd70160853ec0fed236e9bd3d1aa0f5e9439a957a6c2326a2cc045afd40dde0bb275f3e72c11f04436ebf0a3d3c52e02018772243239b4dcec346ee3c21b685f7e7627130190bbee0883f111786b18f9457aeb5a3726ffe5c5108fcacea81e6f7862f7371bc07da83960222075c470a13d8a2a7674824ac8e80c8f541e266cdc691a32fc90519123ff3c49d5c66f97313331d47a5328fddfa09602509863444ccc897cfb9ce3b4e90aea7545475eff3d2b11e9665fbbac6a1dd68b94b9dc5fc59cfbe8210d4cc9f7d8040bc29b69c21963927e01c0756217e5a1a4eb313a612e34d7d5a69f281eb1047babea5d13a6e092c082791cdd39565e875c37cf95751e91b879b6be7b036a9291595b4d34726075f8f60edb7ba00eeb9c12da8620bbd3134dcec23b75c4407b28674258e3e0c9d4122eb62f38f692566d1698cfe26045c4c1f203ff008d47eb7618a155a04684e428e091b346be101120de4a7415b3478d32e6509da11a7b4865e741ae2bb17a68923d48658558be86c3069659f19dd89ca9f07e8b10ff38611d84a21d896056d4f7b803ebefe8e1acf8ab1c265bf95ffda69b2cdb2bc0f79570e3568cb8e689f10331dfc65f80b7698dd4b53534b65bf90beb56a61c2db6b949b7a8d15f5596cd859029507372af86a2e1e47891eca92040f0b09b66e392ba40d0225120139de519c9c3a67d20cdf37578848da37ff11217fd09235d3924898febb9b19088d6a92ec9dd91193624a229e84d0deea31f785d11328216674950eb20d3abb85a93d03a3fe31378984a93542ea0e6193f2ce61c9f47ffbc7e920642b644aa8e895120133082b6001420894e31fbb7e7b4df4e2ef716d5a1c6cc7d76cabd7bdc41528105553d5cd0030cce23b8ec48413d137fe4194f0456d173477dc52c04b67cbbe883077a941f45481dc3e8bfd1b46096a374810e662c1f653a82ce7307ff6dd403bad546e9f8dcbb395a83cc730293c47e9344b57cd0d883903859b084189bd9730c30c2ea099f92088f1d970e6e76029cff2b90a29c72e7a62fdb637a53d56928797a026d4d457b47a56d1ca5f10b2781d312f4ae423c3c049d5045202f94c869b8bcb856aa552a27300e7620270ed7b4a33e986f5c197e2972c3a99805ae8dd2b8c4ee8f0be833504f5bfb4e1713c93c68b33c198090da93d26868b1d8ab0ec2bd06b6551ded6625a441f735492ddd30142a58aaee55f8f750ffd150795afc7626fdc7a2006a28321be4dde644ac3c650ca6867debed146d6618c65872ddbbd49edfa1aa3439920451f28e26da147013dbcf4018a5ef1465c404cca4d1bbaba2fc55db04ecd56cdd64ea2a31d81b4352d2b4bd52dd5dbf6a572d5079168fdec08eb9742bfe2d331e4a31c03f61d99279a41e3c2fb3ae2b558780fe0b5b45e6e364b1d24b906cc8d280234b196483689eceedeb40335041e045f04f962fdc272bef09bb677e95dd65ed05f2f68fad52ffca69f745ea7f3ab574f04e91efda0f8923fc29e9d47fffa5f6752ad7ecdb9f1f0a217397e2ffb5ab5e74515eba0b8b10e8a3bd3b556afd73d9f9ac7f4f7a90effe93ffd88df985ff5f4527f7af99d299d6f9ddad681e7f8fa306d39328ce936d56ece29edd39cdfe8a3baeac3dd74ed5fbf26fdb2dcce3ab81d493d512f28badc58bfe675aa03bb172479ec6f4fdf91f88dfbd48349ec4df780c94172fb7b4172e3a79f7d4e90dcf74d14fb289a73ce39e7083e748ab986a4ad6d7f7f23178eec0c368cb5a0b4632d18c5a6bc89b57065e3adb5d9ce82365291d3c0743275f1e3fbe079b1ca12632dcbb2ec658237cb34cdc5fbcbfdd2de74dd0b8a3caed65e94ee4134584a29a35743fb679e7ca965a6e57b41516e4f6a4beb90d0081bd8d43d11e884b3bdfa41f1a5f3236cd97972d38f4b7e83be9d6eb65781481ed9534fea6c9d36bb9241938df02419663b040382cbab8e7def87f5d7f99fe8d92410248dcf18b5e9ce73a1336d7afbd507cf0bede5d58ed6c158077dc9632ee94c4aefc557fb3a8fda7858171aa537bda3bd2c57ba48aefcbd171a45ff94e9ed37a7f96afa2e34d5f2573b52502e7fd3b61cd668638d564dca9752c79b4b1d50997aaeb34f4fef5dfde8ed482f823b7bcec982366eea6b481a20b4a9b1611b6fc7a568b448eae99e6b67996759bfe86b9fbd37b5c9c66f78be6d8ef65c352818179a192f7a74a83ae817d5ec67da677ab2c93a2f286ed73cf7edd707418aa4317772ce59ceeceb8f195b7befc70ccfb56d50dcda96ea565086ffdd3ad0fe95edc643dbda7b92a4e102c2f09f4e06469e3f66ecec51dabbd9d79f6cfc86a6bd206db689d67d3bb829a5d7eac9c60f50e9ca9f0a866e3401970bccbdae1d5c3bb8a4308942254c11528e8ec4a0d41045cac4e0823da318b9a2f67cc2d99188f8d837286ea06859754e444a296fd500063be2a8f46bfc2a9fca0c437e10fff82a12729452ce0f3265340245caf6320b59b6e398ff5cf69c176a2a41863625920f1822ca2a12f29d1007d28b7e10aa3580c17614d0ab4da6fbd794dd87f0830c41fd9c8c34fe94523e9c4186e25f7f68d2f79afe9afe9afe5ed35fd35f9389be35cdf970cb26fd204399571cfe52879c0c724a29a564e1db525ec9d930c6829088c3d011269ad0c046891a2bcae0a0638ea6dc232c4a2c3d2a81e9c884eca8487ef2e6f464496bb161ec488aac0817c39855124a29a54faaf03c0993e48912ad8615c40e1715141d26392b18d956201a82e5ca7a64c376b161ec688ca6c6370e4b1186d127e2d8f1902b2cbbd7ccf3e9269fba7ec55f49ad43fed096b1e3d1f1435b763c22962ff972c697dcf4988513374e927032705b9c187132847257e6e6a4034670b819b2f7315a714a6de6eb38e411a3a56df481941dc7705eb207d15c40fefce460c8d501b15543f6fee584c0105783af58ac2c73b25cae17ee7559b162c58a9519d7deb7f7decf4cf8b3b75d662d0b6734cb3217ac2e6a4df3efd32e03acfdb25f7138ddf535ae4c86b1457545b25a11b1cab12a6eb6dd30868548ec8a1b2c4a62d11527b6b7da7792d9327665cc9672d28f41727251646f4584e44c62578e368c59d1b213b061cc4a914de94522fbdbab5f7b41920691e74f7db9359942a50f7af8f01bbea915a23dffca101846d45fa880ed3d0b9ed58ae19ae1e54ded3d4d144a4ad9c258eb6cdd19dd9b8b2debbfb0197eb85d1cb39bcd6da5d6c5ad75fe84a6652e4d96cf4191e79bea8d2fa58d319b3d06f69ddbaf76fd7bba1d47b3dbfd58ed1aadd541b34e96c431f280e57346640f82f140d2c8d2d68772bea3a01d451f29d1077fccb19d30fc542a954aa572ce5ace39eb1b5ba9d752a9eef4a8d49f724efdc94fa7134cfd4ff4a71bd0dddeaa1deb53dd67edf564ed71fea8cee16b2fa772e7cdd8f971d4b40ec696f65be70dededd4f9a734d4ff440837aa0b8afba4fd8fc45a8da21d7f29c3f71c85ec81b63a726672148a30e27b8e7f63cb2f94a14d91a1c4493085b12a6fbf6218370f7e36d4417c4b20f3ed7710820e530840fccca6ff23e396efb966bdec177ba8049b3e103cb3a9b69e7f90a17dffdad71eef74f4753afad837d9b560b2edf3ec906fdfc5db1caba71d7c6e668f7adf78dc2cdbc6908e72ac5e76aaa7dde931dc80646c3c2c7ea9bd21bc7560bfc5dfaec5a7ba17fed4a33a0f3fea733a9ed5c3adf333ffec7856bff3709b5cbca9e3d981dfc5e377a179561a6868e73ccf8e9dcfbff3f9f3c3ed83218ee3c8d77fb175707fa7cb799d2ee7559db7fd7dae7ba51e6edf78d816dac37faf64bdee678deabcfbf5d4bd8636d6f6afe6019a943ece3a99b5c9ee07d59d7d08190f47b3a506c2c186f701435eb6d410c6b86fb708ccb77303e21b686e10caf04e335c7072dc3056a5cdf687ff801814beeca4969e100f10fcfd1323842d38a529e6d8c7bdd2faf051743b56cee66c713e647fcfe54a02fa7024b2e3e0156af66f189342654a99edda303625caaea2b40bb0610c2987ed464b2ea594527bdcd49e6bb713ee36b783c0dd31a08cf84116933110a31a1f92c68c30e04f78d1b4cabc528c35a6ec321218b2a287b5c7daf863441fd45356f449ddcff47334ae88ec41a50fa28f174437f73adcd3ef91b3f354fb9ccf79fa1ee8742bae7b21b2f6706f3cb457e9e81b5bd447a5b5d8e280c8706bdce77430b6763aed539d86d2fed469b7f3f0cbbf9af6f0d76cb58626274e22d9a3c961f6192074ff9126d36337c90e08c4dad465c0b5bd0ff6cbf42ffbd7a43dd746bdd3c83452ad54f71d97444675598338fe35bdf5561b636df22dce2a2b0210d142112bdf805c4a29a5942ecffca8917314a6841f357e08a23388ce68e68718638c510649332f448890208a419c38716af61e3e753d1b4aaeb318bbaed3ffd6f7e17f4d3fc2f6ce93bbe2bfd77dbeac2f03954e19bd8cf9f9f129b40141d0e533934215375c4260c21327d67cd9115dc4d8d0040b538448d3040d669e4822a5892e492c79b2fab22b9723d50a2ed50aae2627a48941cc902f0a11402c41e6c4c2ce6088d920021b94d850a4490fdf1a2744c22c817284e60c172cf286312e52708162ce3955297c8410f6009b13b8012651a0c9192523592cc645cb1625551325b88051156dd1b20569c992d816a2580d2aa83e30b73019a24a92b3616c8b0a5b4ed041091224b0d290a48508d511a844864d155ebaa4817d6668505aa22a12a3c1cdae1bc66860b3841115111511974b25c4758118222d463bdb30a6454c0c5cb28c5111c5a034244e143374c0fe5de1f75a5b9a30447dec84907a6818c7c79fe91ea7975ac83fea9c250cfbf67b62f34d5a3e8e3bafa3fa9d57bd10520fcefde7f7f09ff3fd33dd63b5d21f6821a41e3aef5ac8b5a375ecbb8612864a0b69a19730ec73dab4759a1642c2580b216599ee81fa9316f22209c33eeafd5d6b5a080409c37e7c8c75d4422e44c2b08f75517cd742510b091142eaa1bd6ba10b4818f6b517fafc108743342d1ee2b0390f71f8ffc8169d10520ffc395ac871e0477daa13417bd3e74e04a41e9a16c1a8274caedaaf4818f67b62598b2084240188839ab408f4b5f82e43920c4a9bbe04208efa2cab5f8e1a92d1b690451659b2d40d6359a8dc0d63598ef286b12c39449c6304ea3869a65b89688021bd81bbc9b2dceae3ad13d6885d907f941d100e6274089bf8f022eca316514a67d19c45d48be89c718e669b4d8db814e4d986c26254b2c45c4061c65c9a6c625998c4603832dbcc361c175beedbd33a4e2967ef695b732fb823b2163788cfada8e65bdb5e02f1373dd479beb3a7f6efd03675b0ccce3a48e514d6faf8fceea7fe95577f08c9044d787dc60db2ff2cebae84c13931df1d4acb159183eecc5e7e0cfff952fe4a88ec0fe97b575fe9fa66a0648528b5519337e0096df9208cc9be835cdbb5e3628bbe101a39ce29bb9e959aecbd522c83e18cac79e0eeeefe3344d6fe47ca1a5b834af7c61f0760edad56ff2daa3dd7a6d33ffea8b123e728cc375d3b132532e76db2b75ac5d56a75a592b15fe5cf8e7ead376eefecc3ad5aafbe5f535fad45232fbcd9f36b7dfb3aa00ddf5edfca7007fc9ca00d8736dcb32feb337fb61c403cceeca15f1fb7e8eeb3fe7670f7449fbab4e9f3ecb85f9f7b9af3b5f3ee7bf54ff676f755efddaf2f28d9f42d16381bffcdf9dbe5ef893e55bbbcf42fde34d6b6010dedd4f7449ff973fbd9f1e47c8b87dbc4bdaaf3ea4beef1739a27e77976b4d87ebb2f5f27f5391dd0d04e699dee762f94d69aaef5356823c8bdf75e7b41d8d7de0bec7bedfcfa38ab2f37a0baa3fefc0965d01c3aa00c7fb847f0212ede21775ecd94fd9c530b9a306972803dea70a297a3739aa63e9d5efbc9423ddc4e287aa2f444a10c7f8d4b833b3837dd80ee5bf939ea9cfa076b478f961334214f2ea178414d7e947d6f6e586acff680c9a8cf7ecb911f6ea8ec01dc281d2477c0add33a53bed6522967bc4328974adc7bef0dac1d2a3858c5129f0b26c40b4cac143b20718411951fe20c218109489c70302a5f0a1f54d2600e878701c06012ae0526f91229e8e15bc167016463c2978295f95a58a1c18a6fcbb0244ec4c467441361a019d81043d67c18c0aaa8014c083e2f015601ab32a508163d54f9818e2049122762be0ef6c577f8a00f6e94d8f00eac19c19700d8149f9aaf85cf00302fea81b9142b3e00c0e07c10cc093ea46f07a6037331f08a34377c26f8bc042c180c2207147c2a1cf151d887e48bf3050096826fcb9723c3b6602898c7de48017130c11c862c20141f38810f4a3817a883d11468c27c4b1f0d1730193e173e1f05080076f4f1a8606dca7c453cf15df1043a41840314cc7c59602cb80d513e349f9ff0a5f96e6c006301b033df99158c518106d5ca77e44c60f14125ba7c1a6602ffa10858950f0a11e753e34478f94e3011c0215c8a183e203e2f4200d80c2a3e27c10ff794430a2028c104244c8688c06889d10b58ecab71009891af003027de04de50e1130ee6c81b32396c71a288112adcf0396183c3b7822d7d12f686872f068c8a249660f9943e103ccc8f7cf1270a282e9ce952c607129ae43044911112300706c07c06272ecc498046f894232011eea6890f896fcc77418561984301e553c1e7818f0c105348141306b1f87a78ebd4566badb5d65a8b421a2373ce5c6d0adbda31dbda67d93ce743fd5f629d28e090c6c8131d3e04c050f091f9b2c800e62b04f1c12f9e9415418608082078d832460c3da1c8e7c4a3806daeec5891ed54f1a9d6a8d6a4a88871c7083d6a67a2c9d2f65c316012f4a909b12647a8090ebd5cf5eaa6b0e415fda73ae6d314b2bf4bf9f429109a1fa6abaea84c90a34f36c2dd1d42c881ed7bd61dbab8cae16974b56ae5f83c555fbd92aa20f21a3c800c2716405e45f7a803907d019895e1acb36a426400640a01ad4da826776f3090efc720c3798105b8271321b9063825464a29654b0545cfb35a3cd9e557e0172e62ec8b980867cfd5a2c8b552f7250c59348860cc406ad0697d8f524a554b59ca28bffaab962aadd47f683bca1a56ac9d18e16aa5d303abf20c5a9f4a9a6717828c71cbabb94c8494d9f936b344481e6dd53c32b6c8444893bec6306e40c3a7cec9d21b80216bfe7d95103995ed132151d6cd90b5da94b2628b7e4f6ca9a0c82bf72e04dff3e7947246942b12c140be4f3c966a2a2bd9bb9bcad885e09b3ed573ca2d1321650f95a04ffd4ccb4c1ddd1f81c09aea4b8e1f7fc85dd31bc6924ad8d0020ce14d7980c49bd882494ab66f55d2abbc64ef6bcc37db7dc6f62452469fecdd3da7841b160ca38fd4f1612c8968c33dbb21e915a8c2daa73ff4d5dda9b096d2b74ebf83b887aac71430ce709a60968990a8563df1454ae907f0fe0b970d635fa81860c3d80d7062c496fc566ca9b064af1583c2a8a3c4be28edd8c1d80d70a2dc242818c8b7cdd06220df354b55ec86a50d63494b7bc686b1a41836109ae10f9e990849c6bc38b1636019337c58b16722a4287f85612642827f82a92ae6c7a18d51a86ec37777d73e776c40f1e30e0becf9b2f3b48e2e6e3ab61c33c25039fa54181f46a923524a5f53678f61c8f21e7e972cefbfba60b157b1e55d6a297b31f2b264fb67a8638c72c6bc5c8979498a52a8a6c8974ba60d9168a8144db7753d0a15d10800400100c314000020100806c5228150281ccf15c63d14000a868c3c744c954723711cc6400cc3300c822000800000022008820008006114638a1f539420858f7061137e724068bd07c5b86e9365c4a555ba9ab9ae67da5e431047e159ec6631e7a173f8106088720f89e7256462a23adef307266d16531e5c7317b69eb49d4f6e8ff8cde30f2a983d6e6b6688d478306bc743a786e701ec87721e1440a815e2629a4746a7a2a967b6e1f6605910c77af778d32cb87b16eea420f650b60588f810a980288b8647d56efbc1e2cfd3e91c3f243dc4142f1e648712420f51c307712024e3593c3a870d42441066d1787cda2cd03def9d09c2164473212eb278f234b14330a8c422583ef5c7fe5adc70808c932287676eb3f0e01170c51f708eb888eb81a04181e042643f78f9c10781911052883d1ed02c883c4bed2e163d62ef6663f62cb65321e7a9bea5d0e5e1360e10ae3da860e151b6792584593a8f67370b78cfeaba8b1d8fcb964e88b2d27a44ea7ad63d6adb2c9e3c98dd7ce63c7668ba1e84426006b102b147a4ce67bdc7dd26e70f2a683debb41a88f043ac88787c9d4441f844f44ca259a879dcb834435cdcf048e554d4f62cdb2cbe7a047317461ea772762a7ade36f383cd87e63e5864f3946b2ab479786d20208810ad1fe536a53ca8d8f7c4dae6426cf1f006c1830805218648db834b0fd221f410b6219a037191dd53def5d4f6f06c0079b050ecc9c45da4f190cd41d1dc23d3701f92f560f1edc1968be2db33a641f460e1c4e3eed20462c5c653dddd16c2f1bf9e8f737c00bbe21e4c21e21f2c43a816420adc1e9dbb1e02130af180bbe33ffc0962459ea7136ef1681e6b9b6fe34e57e1530750569ec916f581fb60ad0f16df9e6039e4b87b543df050e25010b820b621be3f84efc3a81e2c19f1037df04b57c6fb7a9c7090b1990fb122c833de8611844fe61eb1370b674fb693e3211b4437047e20a680787cd72e02a1e702cfa9a3f90171105254f608a659c8f7ec9d5e0f413ea41cc48a82a7272e8a741ef79ac9436a47205a808c46611074b5784a6a49f980d1e09c3cf8c69cadc59ce1a0be6b0f4365360b8e475d8ef14f0fe006e2070b674fee9d4f268f60350b79cf8ed3f310fc87f2071442ddc0965a8a07e83d5a23cc94c1203f4c0ac34c971c04b286792ade2cc43d4a753e153d75df2cc034ba071f305d8043fcd3ea5972b358e961744c20b410c9057181e5599ef3d9f6f47617795051ec31b5497d105f4b00d956c251a3cfb119a3751f0521d941f9a86ca9f9a0c2dfd35d734224cd83142d1ea5358a0ff57fb081d0ed03bb077b85b8f8f784db6d438747830e0a318f6abb82070b394f129a0a688f159b37102bf8793cce9f432aa26295dd53d33cd6ee26c4ff2120502a8b39a3cf0a37c2830a791e481ba086272d84a6f3b00f0012cb5906987ec4c45e6a26d78b131436e0a6afbf1882b17a0dd48ad7eee1ca33e41942f11823dd507112a7aad26320ea708013946265e0edeef8ba9f1e30b5cf5344d6e17bbc66a2b5a598f985c7104015e4c9d58240beae743e8e227dc856fc09d6e4548e4ef7855f00aa1248b8ab1212d50a182bf8cb48b688b1963edc7c935dea6c8d39fec6be83a024e7538a8754cd82866759a727178f62ce27718faa73e1830a388f841b0a22b943d1dbc06323ff83d30f4e592f9fce4131c9b46c1c9ddcdf767c0b004ab7e93f84fbb9d0bde379721830a59da7bbce184efd17749e4f297fed4e0a2198ecdd1084faab4292ee33f123a1873ec23be2625b130d41c638d866019091ef7bcce487dcf7f901ced961cee82fef0351de98e27a9599258c42b3a8777c854acd267899fc039709107046bd8aab7262be74f4d1d42be38cd3d59ba8f61540914744c91c58030f407ae15875646df56291ff3c57ce6cbd9dfb01977403cb099291438736c61d3543a66a88a3507b9634be8fffe0ef701aae3b942ba6ccffbb0bdcdd71228d753e71188446c673e64299b8c624a9cad405e7c731ae49bdbca6ca058750eaad3317949432d51a41c98506d1f91a3baff9fcce47bcf010b1eb5e1cbce219b3f3da170e8639ed4e5b48c2bb0ec0e35b0035c6b0cb58d75e9281d246ce746d9504dcf028cbbe0383b24c0acdd6469b4ded7b3dac8d30f56f6f73503d56041958e210ca43defaf16366fb54bee507fae841ce1bdc3092a1b56bacebd2eff9740f4dc732a055f094ef1197f2c23a66d12ae89c2c00651d2a388b4bd183500704f0d11a19e284ae7905f558b1fe0fe5eae5441a8c4c557139c6e03014b74e2b252c92ad5d73a03b18f38f73a92517b6aa87742efd76699646703153fbdf844cac2022101bbacad63bbcfa0bc5a46c6b0850a53ac6b6aa4b9180c46f7d0951317c8fa21667c16a808c9b0e746b9c779bef8db548579c3892a44c60a0ac1de7f321dc6bf24c27d77cc775d5873812a0ccb101312a41bdd3c3d02e1f5acb29a10f3469edeb7ac332d6935c2a676e96ef2091360c8252245755ed54204b5b6775d149bb56f9f45cff5bdc1395cf8271682a79f60129014b994aa3a1e536c206f90075b4ea239414912069476fbe8e7a2eaea89940f44b600e5c14468225991ff7021c0d3762977ff37c11e506b09dc4b3e8cf31d324934b9c2f9ca8beb00d6f4b8b44f25f99e29bb7e5f580a3ee70e8a31663d4a110ff389e079d64f9f0b5862a14c2e6e497c42bebdfe8ff2c7330c3af61fd240910808ea07bf3023bd604b84ecd9d87f8902555c64c5f8af971a42b72d3503cb1e399a24cdb34cb2a1f5872986c2c936cd62d92efce2e082e111df371ed5199b076dbf9d02032f1fe4e86720b0ae31bc6866bfa8a571c21455c13731b8b4740c432cf55a2b17391986f09cebc5a5cb08298e6b9a4ed3eada4e562331b69dd05922c1aad0ccea1d50be17431409277b4de47f56468acd7b0ef0e17da6ec827ce9c2c14b6dfa5430a89391d9a050683cb64abea2d2ed898563c1cb72478c258f575768577fc4f868c6db18237b7c13baca926f5a5694c4aa272367eb5ee11ec99ca7a6ed0de312ba4fc21ddc941b07e90de2cfaf8566bd55a1e3715a2f921cebc2efc229a730850f858e79b424bc12ad968fe4d08d2601104cfd6f9828ca65fadd374af163aefe6aa8762751f60672e32296c2e149410fb258969c5a0798b71bc36915418c496fec044c188e7486924d9a0312a2bd66a331d48d8306ae8ee72c215364be625185089393a434dcdd23fd3ca613cbdc645dd32d7409aabce0d011c7191aa8b3c4803717dbd8d681085e4e481cb61025487957eb4361ffee98bce05a381f1adb022906764318c49099a78870a72c536be07677ddc6bd0a09d5f2fc0c38db46b16a0e221de584f3d71264d037310a7ea09425ac918299fc3a7b22cc0091d70539b62afa8b5e9bcd5e8fc50b6bc1821b443d1946b69c85d542aeb6dcb0fc22593ea5019c4ec0d098f9215967ddc6992e17f680bae536ec2d4d633a3f4c9a00d13a971b965533b74209172f4c8e8d5f53f167d5ec222500818f9a82792a9feb51de26a7a05200a842653151f4179349d9b59aa618b6aa50805114dfbf3efcc362e3ed95f785dd6bc2125ed51bd9f8275e5a7219de9df88733326fee47c1fac8e6b314d13e968da6182e27daf0d8ea0295c837a1b035ba3c92fd9d0b1c8a95f330ff5e760b8280daf5e2b83a6cf9eb6cc7e99b53e1c9ebffc0361e1035a0310f6f5d67e14f401e50ae971cfd48233260b5bddf56b00e86ac34151e0cf3be457bdb07a3d04fd5200437fb360f70c225ff7d3de43945d925cdb60dd8ed70cb266c3416c175c9c4267cde735ec11dc648e279b664cb8672cd7abdf6f5070a39a7994f37655fda583c660b8a1fde2928c7b054ff9335852721d63df21794318e86d02561bd091185438c0f832abc4534d07376dabb4f301031c9f2dd52ec92c79bf47e30158a8f17c516ef4235290e611d3abce3447eb73780d8a8c11c427e8c2c5bda466d6b65361cd2ea22c348f4037b84614becf76f42c1bf278c623bd765de486f0604adef822a13d5471ee900ea62c7b27d515a68af281612d8534941da195b65fe86641a8c7924cd3f657b87684183deb8ef364f7659d02d746d78605b5df358fe8ef68279e17fe50acb549fa273682d7af54d062bcf563ccc8484f4a49e912280c3c7fb47f43931541cc1acaa8fb92269a11f9958c22a119da71e3fb1d78d2564765fd124848e70bb06a5174c1939a23a349545bb9a0a0581e70d9227b173d14eec742fd9324442d431815d266a45b51c0ec39d1d48c38139abda9825ed6cf807c2ef789cbb04175163a91c951aefc86232048b3f9e4de45dd2b39b3ebd92b15be3598ff2ee7fa3ccf6cb75174ed171cc62139940318cfc49ba6fef319deee2e433b6b925c5f496bf638a418f3476490a9017161156156ca563ec83213ebf94e922908791241d6e84d42934a0cae598429304394c0307e731763f67e9321766b74dcffd573d599de541860772e65b42c16e84e2bd87682badf8ed18700a91876911dd0c88918815b689a16572e1d16c4034bc029f91985bd7f4dd0a3a3f6a14c3d9742b9116e14259e084064d83644af22d084e51a27d816f6a3dc32877ca655946a9edd4b14bdb876fafb93191f0abbba2f9e71a3ed86fb6000d9f17e95860929bdd3ded459307aedddb02619debcf1fbe8f3168b059b8d98b6020f1921fa31f5386380e5f6b0686b29c57c187e32adcded4cf5ee33f7e71290c1a6e7fe656f877977c3655560435a6d9f144853628dcef3edb1865d44fdb7f6fe430371bb225062d63f3adce005a6f407383f20661ca29f56b89838d56aa33b976c204539c6b9df205935e3a0b71aaec490da19bf855e796bdd4000857e0eec9b31e142e7f44eb0e91510e0dbb4925ad22dd9cafcf29c1253fc13ce36d38635b6a74b06684dbb6b70c3c971410c05fbb0467afa68ac797eef47e4d646fe447583cc7e1ae9a44505caa59300517146ad5461c874f6540e8882077e7d83c7f45d8f4da7b7c06f778b969233e62c96d7bb71e315e7963c0db710c30ebd515840f91419778c69528731cf65dc78f81d44ec4b2e38fcee968833123a1265f6684037b1524ea3247c68ed581d2a9ac165f8ea1bf31356c1c05356be8428acc948ddeef82a2f64f2fc17db7e16c34db0456aea01ac322313a22cf9807dd2d9655ce310691718e533e6a303b8eb9b60e3eac0c41bfaa8f24832a51f7da20a14b79f0b98b7a1a8a77765228031b8e71549e7ca6c17a9243707cfb43368ca9ec344d554b47010ed0b8aed3be1c66e09f0cd218207500265f9315c2bae8a62e2a0d08b5fc373df484e8b1ad6c5b8ac261040525dd750aa22be3e02934328beb4bb1cd8cb1e18efc24c139279b5ea942248e145d999e121afff6186e4aea4e1a67ce60ea18b879430a19401b2ccf21bf312a28b77f1fc17c5b040338f165205b786862411ecbb855f466ae5269d95ae2d998d4ea95371f30179f820b9f8d9e84a67979ae4837b140b2f5dda920b6727d2f31004eb662d5026fd265ee34d28b34062f11e4fcc87775815358ca26b28b5c8df561f0a9cfff89019bbf0d4d255e7612710e4c690b634f22d08328f6aaeaa56ae42f47dc674a8696cf01b747ecbc0ec672e0ae2d414607abb77967ee4904d7f6b9c8b3a7ed16cd8ff37f916c99a9a9d08994157d5548f9e9b261f93f499d316f661ec908989c05348499ebcf06184288430a0c3761dc6950cf3a2e1cb8f0db2fdab246699a597e837d5587acf6dc84a85ef6612157bcc12c3573a6d2253511314400f88856ddce7aa217104a0fbc5ef83333d1b46060bc53f5fe28120ff3b51f9efa400ad0253c67604671c52929212a088efb7f0359eb9de0e01ce3402244335c9d87886d964032529de57e50268c4f406aa39ec6ebf7f1ea165408ad4b6c33fc608d345221ad36655b723ec3581d4110fbb45312bcefbfcdeceb97515d2e3218f32630e8fd975d0f7d8650310405bef0da332cb93cc1b3cd9de99b491479662e54746c1bfede5a7d88ae809bca21397d6c825222d1e44c21c8ec0aec650c634bc2f0d8c1429c861e1230b9d704835f203584702c122611776ad0390f40b2d1458276c621242e785528930a508a3c2b91d2545d582d474f9be5ae060b7f855855935587f94390e1222968d05174a411178a25b5b2ab20ec01a922781a823f2bd0638bb8175edde472dbad2c076d94f2344c728d1afdf721c503cd45be07c7d4ee66992ce77b68cf19d11c0dfe8066a30571a51f054b31916f5bea6b0c5116105efc6e860371f55c3248d6c8293221e9394663d2b18afe0538767d9658f040bcdaec862e876a4da830d0e604f062952c53f8a05a751853c87f1a62f4415c10d2aab08a48cebd883abd6c543fa5b94298c38e19d059a261d7a1e47fa69b9d1760a5742e8799fe9f13e106eace4debb59424310066864eaaf495b97ef51003ca05c43f725b87522efe9626650e1816e773f0fbc43cab1c4916f6986322929203388069699af807dfe16a1dfda9babd7481a48477e9bb8699811d3a884455900bf18617625f39745b08870c0a5173a6d88099174f58c44f4a3d18b0a6b0722ed2f45ec1267d16ee7711da9e4af04ca66221c729ffd4ed3d7abcaba25c7a6bf4cd277789bb02a0c74ea83e1cef614ecce4a7820904512f10816ac2c27a178457b4b3f10b3e36406747039cb07765fd020ae495bf5504b0371588c5c7470805e107df25cecfac05f9cea71ebb8b454ccfa5b960ac8de3868b3a4b28b23f3476771c4c74b08f86c8d404dae01325470a13e4589d3875cc0d4b863076de4c88081b7a9c1ef812eb108cddf55056df1cdf43f99c93d52bd2497752a1af49ba939895dfff8f8f4267b69e26c51191bc4d66c651e53517610529ae257c4136feb531ee19d3db0daad5fb8b80d91905f177ce2025cc9c497cf36de36e7603e6ac8a882eda0bee283f802f971acdb0580f3bcb955b41c63945c7d7d099445acf1f02b3994c72a9dfac106d845c9ff1516878e7b761e92e73bac2975cecf9b93500d0e08db8c88f2089dea11cf131529e5aaab05ba5199551ecdbd407134f7a5315066ea26c9731d2e5b98493e400eb5218ecbaa453d29cfef8c730810fdb85600949779da0d7b812866bfc8110e1941c8163dbe54da7b70e5c144e4b61b1c3df0f5af35a42a776a7e5c56489b23d2656cd0b1b87bb61c6f057994d2fcc047aaa61afc9702d7213bc57a013cb3cc9476d026456425a17cbc7ba031b0d16463ae162842496949503b9c1aaee0df95af4f57566f22a8f4568b910b11b516fa042c88a26ed94437b3ea09750f6334c821380ae0368cd743113a0f7311e6278d7828f201b8a5fb772e565ba2aad6681bc1fc026d47562a6f1002cd3099e957c4356ccbc49a014859974b6de0c61a75522a6df747d9a96ec96d27b0f2c1028ed56e9a332ab513a447de48773c849906e145686558fa22958a6669c7b8831b574d486a1da2bbe7d40c484d3fd4052fdf33bd100f07b130530c3485c74f2a9c0c45e6bff2bab44affc795866d34090db58475464bd446afc5daa873c1220e56be438d30e8466ef5ea8e62a01505d847a22f7a386d1306348b6cc8c6ac5594107795ec80a2ff22f961a0db016b6cd6cc7a7ec2ec56172a16383eb78e52ba96a2956e4438d888fdccae1dede98280b0fbc8504dc11c05db38751195904f53ec4ab5748d5cc7a53be06ec1f6352011270a568d28d8708f03b2f913ebb1804407160ce6efb0ff1ed250c55f4dc64aa76dab4433c7de9c4f62df2ee6d8c7edea01129a4246666a2433250b60ec78ace3cfe4d7ce6b7e2fc7e4c6ab82f4bfe0da1369d4458b39ee3bfe47e655b86e9501c64e5219ee4bf037a0c6058c2c016dda66a41a4f16bdcc3fac0d02b77a43b373acd9b6c4989209ffe5ca38e49a8b521c7b6e67de6126c2a85e3d2fd2cc347e322e339460e8e26f7ffe6d0b960a4bee7748fac33a2f07f2afc72d2ead2bdb7a52866a09639fdd4d6faeb52fa7edb0d705f5a5a6135d367a3dab441f25062912629c4c6fd6ff076da99ca1962e086f264da024a5fa8f6db05712b314bc3546367fbcee3372b9671f3fba6791a71fbc9bb5ed33db147ab99157129753381814dde845eddc70e3349830c43dba8af5969a3520e644d3a002ae6a3ad4d2c46bcc18794d6710014216c498f0904856053197840970c3eae32dd9abbf4570c5589084599caf737b353938746d39534bcc3ae0ecd5abd868237e7e6bcc598c99f4606ce5141b5ce9b0a1326609142613ed424dfb6577b53ac84f0cdd04391b809574d118a1af532c02dc676797783f627297fb945d1e1dba56172aac196f8eab1533de350856060de9024b0dc14847c33c388b6034b8c11673c3e83058e3ff24f88b33ecdf759648d804576bdbcbe798c51d3f328efe21f982e08df1a1841e528b30de2c6c1f03d76615d7803ca3773420ef24f8af4432a4682567c14f33ec95076163be662b2d1cdac93e6f921ad250031e1164257d9bc3581b3661ff1a3cdd80c59ab9de9995876ed0e56d749cc8c2f4aa1b35db1579c6e66cd8b865fcfd1103f06e7a768fa0bc5f3688974a4cc0583f0ac7d655ccb6c5dbc0a7f5500c0e24ea2257378ae0164d243c9e95957b49652e25d0a91a576c89789ef2849788e0eb8671d34b3ee5e0daf0ea368dfee1f1ebe259553b19c2b5aca8de1a79d97212cf9c9c727071e0bdc551a2a28695a7b020f99e60d63407ea20e6a4d4d2c6b3e6e0603ffe630403bf680ac7709342a6e7bd887314214afd0d49476e6ee77ea5ff432d73392379fc0b9899b3e4f87ae54d41870e4638c5f0405da89964074f54a18d53417e9fcd77e29488f02ef21af4c97a3a498eb49b374ce70debbc16025353b4ab847f133fc3caec293daf0b8ccf270648303927de43e8fb522f6137d06db75b1a65061e14ebaebe9228b58ccac3efeee03e67f87f20560479e12b9de2938b2f0c2f11915559439057815ebabcfc7461b78c7dd01738512b0a3fb91ed6963cc32aa0e72c57642f9233b12ea760c843bd8c1450c47c2db0a6673d02ac0dd60ee08aadf510145287db4564f8c31bab73822a4e9613adfe17a0c6f9c585a1444367066771f2bfb06508bbc6fb039b2bbc5aae55782f466d70e2b574feddd91f43b0f811330b2062b9db98fa17398018ec721cd5ac009ad9eee5421fabdcfa9ac2d42f0a9f3cac14e41f8360fc84b51e97c126b239e22ef3b0e9bca7292e78a7f56513f5b79827c4978e338d53113b02c2486b953e2041fd781bc2f1a6956237076f45056248c868ce2df2bfc6fdfe4513e940bcd62ff73047f334570325088c4521d642adc5ede8a7239a51d2a30ba379b11fc858afb81a7889eb46b2bf4a918e264c639a9841269025d3efb80424ba764b41e1405b8588c64d1824fbff30a3b2ffad50800f8c2f69c8cb5823bbc6a5de65ff8133fd7c428b91e46bbe5f24bc1dd2b9ae1791aaf487f346fd41390ec323fd59222b65107d0674f0165f015df9c03fa2e57c85d7b8d55f90e5379b8c7010529ffae747fbfa082eb761baf97a2e2268aaf90c3424017c224837106b10e49110f2a5d2f99048994b64336bb2846af60e5c877f52a8d6e5ec95dc107ee2ba1a9296fefe95e8acf0b152d5a0b75c65d5564d0416b50741f0ec8051e508189983042c192bb4f6b50a7290787c09946cd91d538c3c0aa72b8dc37bf39e61cd2d002d4a831ac10b1d450016a7e61ecba16ad0d49afa71661cb9c726a85443eebb5026d951aa056193fe8ca0c27ad14c7dee50b55c14fe08984872157680875b318f2d33030290e3429d5950658242170651337d16e94b11fd348332ac2bc1d5a156dcdf9c091f86c1f483a34a47596f676f92b7a330df8f262380185e661e15bf6c255d46327ece19b54888b127ab71a837804a5b3deb3c6c7683790b565e5f7e33c5703b06904bcfef336dd49a6d8f663e9dc3bd6ebb689816d7187d6ad0df2a76df86bdc4e08dea2fa22607c16d1ad42ff9c1a8e54cb43e925c287c4b75c0c857fbf619f33318158ae0ec8c72680afc043eac86769304d1ce2227e9a7e093f3bc0b1fd12e7c3ab6c90d6473b37d2f87d62a10480754915bef7ebc1466f1031a2024a54f2dacabb34fb17e25ea5be741ec515afb1f1680b257aac75665765c58e04d9ebaedec83fc73c0c37f94899d3863b99d2e0d17a931d5e30d9117be7a943d711795208625b871013a9b3bfd098825dc08270626b03863e62f8afbd07715aa5e3661336d8afcb5a5d76df7746abe7564667885bcd9d406cd16b5a9d4959ada4d66a58bb03cf72acbdaa6be6ba6ebd3a67ec6758e5071d9e82c243a52a01eadc27ef616e0fb14b5a2ca9967e888c12a9649b4fbad378e1c34a6b2f224621beaa4e22dd28596ab2951dc69fac1fc7b1bf78117cce218265e202471e5eac66bcc9a4e179e9468d14eafc9476889298a6567e7ca9c5d5fc25113316a39f1e8bfcb8d7a7759a34e1d76294b12df3a2b53e567e0c6074756a595475ea5a0aa0370498096f8cdc1fa62c178990bfceea4f34b0f77a5c9d0b66ecbc551251d26980c23107d743ca11a1d0335dd1837e9aef858ff4fbf7b6cb11f3ed7ae502d264361639320cf24377002cb90de0d24235777572bae9682f5e554b8501234dd49308bad5dc8b844cb964dc016c9abee43bb41e9391d5a6982d44bbc18514f91d0fbbeb5a7fea52673e67611e3def88c4bda27752b27aee9c033dadc6404b2d3914bae4cf442ddb8c0ae35bb3206f49d48228b12f237505cb8119d3ee9f35f4d6005c636d953f7c203cdf2d033c36643db01e87c573a7c8e81a10e7a8766e738453907bf5f826c3f355a49884ce68102b3e6bab6a36c6bd47310e45300cf1689385cb4a46025ca25bff20c93c54cf36103935ac20335fbb5937cbc3073344078d38e5ac743a7e23cc44b36a9d5e3858935e088ddd52a3c7d7459f6d4562fb541bb3b7fe1d9c4be2a07e26cf505b011921c5cca74519ff033e9decf3df85d2ec518a654549776251c528db56f204125c76cb12c30f1e6046652b58b37f826495e7bed21d57504cccb43ef90fb2fe6c2c1823ae2ab06641107122fc3c60e11f8c90e43210c748bb2c1d5aae455e48607a2d72f10bf8be0bc1efad2c9ce63362ff1c948fa37d1d0212cb25bb1d14616317ecc4bc93a04bac6fb4c72b71a67949fda8804ca17363cc9f6bfbd033c4e86ff5a2526867e22d17a55a26090ee1e1352fd8cadb3fc8fcbada8b016e02813fc665c30dcc3f1a8d588ff49e0c50a1a07706a5bd92110531ddc00ec0438433498188a77e40352cb8a70961178bc404de808eb6b33cc6739190cf61eec6c9e92e168b6ef65e769e57193056d607f88d7f93375a718cf73943af6aaccfd88159c128dd3cac2ba6039c8e407156bf9f0a9a19e16500e20f5c6e47b16c0b0979d4b850c4d3920d8a7d3cb39083bef23f2d76ce871b28106174eed3b693906a66c016eec3d3e6f4019d334ca160a2842f65a03d0d9bdcab94d1bfe003bc4686d3c7e054756c1f56d56db8bd95e90a2798736e1ef3d7ff9b275e45b686a1d81e0b8067b55dc3315513a7a9bf18e514f99136b4ccd45c6b9cc16f4032b1f5a2621e937dbc8757e6b2c4e6d057acd9363808ee792d210939434c333332c1307df9f3e7f3cb187957565a214021618b7f5926ee1a21227cc3772bd6b1f5c612d7b62c9c0d3c0fe8963a3a18a3423c629fe338d1d5632e0576769955e5e02997782a11f703b1860f9f9a3f1ea5d81cde3ea1e0b8807ed7469acc4e59498884bdd52056751949810342c656e9cf113b76a966ccd79f9193f0c0d5db3e1d186fff07dc2b64dd8ed464b41157ed25dc6bb0250cec30a6be76c9cfc769e2eaa524a53efa7f5702cadc9067a67d26f00cc70cff120554d50b34327d58493f6a276cbf498ae496ee862e4adf4e874058a65dc94a3ad8116833fa0f85086752d80344f564f8d0f0ee64f129a692a4e0b3f145a4841d374ff386e79245a5b06e629035900e979ef46c5f5a926d7832bf3fae2d2982603639633d248f57e74a1fa8987c1ba5b3c50754f204ede12bcad6674898638a94221b98682a86372c56d623d4b58137f9afc3892b0445117d36be10ac8a25762fd4dc0b4834f2a06535336ba6a9378bca7a70401fe83b769bc77a77ef57c7abf3e2f8ffe23aea0ec044d304a41061e285f9314e915518699cfad3918dc1d92b844bcbda992a7837c6e7da881c99658926fe490da7c421f3b013dfe9654d682d3f3e12a8d5270d1939d40b17c62a650ceb02ae5245111683a30269a8eb8c11de965134242af48a728a72c41963f4e4d661255b8b64a2aea124a57e9ed8d4a31ec5170ab138d1e0cec2c035a7ad48b98c475f5cc6091d49fdc8687a80a8f36312b8e6e241738bbc36ab902cf2b9f0c1067523b83bbc81f60aecceb0a7612f16cea55a79deb0fba8079660981de1e32ac2af310b21de663f6165137a7670403a40354886a20a0c3fd32678019274017ca2143adf3aefaa53071ca1ece46d7ad649207f4a3c98b6c60e0180bd31200edcd427a462aee8f51bed337058c70329bfdf55c4b037fbe1088dd9091c8dc27644fd6720148d57a5bd4a4dd60de045717b7a36dd14ca46326a4667392e5626b2dad4f05a352a3cb6b1ec987def50987484b03259bad1acdc8f997bd8dcdce7bbafde0863bdcceb9c02b5758d4ebf71eb19a764de3e30eff37112e8e22d512a23735fa0f647715a43a7d2fec08bf0a954c84ef776648ae2aa8083a2fb885cfd1c8ccba8edf181cc006e8deee4a25f0862f35cde9bdbbc7f988390bdb07b3d71f94af5194f892d8e65396205493cbb55fe729258f633453fbb1b231d368c9e94285636a06f3ed1fbbd4c96af2d4b69880f50487c5d44850396c7e5185bb6bcf7a36ea838b64788055c59f83a9545122e1af4ad8bb1bdcde92ec16e71d16089ea580b3f26701e2d61875b746f4346e554410dd13f410a075b1600a0fb3e16a952074c549002997f0f814fb6b85e32816b4a3cb0779ded4f57af2795bebe0ed081e7d87936fe2e91d68451652f326c43d9a7654f14ccd100af2aa7280e46a0cffd56ad0f3c1a9b5999dcc3bef8835c068c6dca9ecaf73228dc216a6d05a68a9fa6afca01cc0bc9b31d05204a49f3daac94391a0cf11914c8c6a958d859337e1a9abb03f2fec580ae65d3fbe15a957addb42d336b8075950cd0818416b51f3ff0418683699623964ece94c81a083d77ab91664d77525afcd65e28f594d0797f1bbb4c3555b172cd11cdb6d7c72ad2de5d07a353ab2cc1a12a0c8d9170bed44862bfbe59d287d83f1719076b4a3f7f631479f21eed053f85dc27ba559464973319ce831713ef34eb5fcca60430c7d2bc2909eee50f5526edc5917355c030eb6de5dbe23b399eadc629a5a859e1e29db9ee1b6cefbebc859aa15191900ad89b696b5ffcfee17f9bce79f80255461966b50b36c98607f1af1ecc45bbeb51a863fc1410398c3edfd7d174865126afed7ea04a8038b64c59bb8112a6e9a82dc20163e4e89998ed7b807af4b8cf5a3120a5b65c22815d77846ce9ffd4005b3510108979399b58d5354168647646d341360174d0d93cabea8ea2775d46add3ee46a380269b4ce6951b8369f3f7733486c35ea19a115f024621d63e2cfe59c5e95ae8937f43a3e8e9dd1af50a3bdd5578412c8d98525e96813b7f95a3199f1a892939c3699c644c8938147eaa0ec96069f7fb84a18d01ef49a3961ec85a4cbf9e24075eb99027b7ab13c4abc728d99020120070651e6238a357231472367aedba6206323c5f55d81fc7bc57e48e3420541644d1feda6e6c1116748415e9d5ad7c04a1e60e1f765afce0d6523501dbbb86ffdcf2d39be6efb2c7539783f1922ef30f84b6491e0863fdc5f1c04117f18c4ce9b80236fa9b53db32c0554d9f17491b0594ef7f34c2a92f414209122450c94667e2a28b2efc03e59ed92b9083bf570eba65368b239c123ee01530662006d4f0c5ce44b907a7da50df630223f498b10b122204e510e5117e0b4013c83fd7f5b79ddb343c3df216f58f7488d205e249e033cd6016a518a831cd8b00ae4c866125d23d30cf2e0f29feb918381c65c582a5e87046690f4762a5aec1d996d0b189e9f420233cc6ccbad94c3ba8178979295b7769092ed905223e1fb6b7fdab611165a78050e7a7c702abbb3f1d115e09dfa1095b9bea1713d7595f1c1a113e0f6c8f8603946aa4e16418d2d9a98b1d8a81a45a562a2853f80ed586eee46f5aedf7f83036e13636ccb5bd97165ad9376361d629aaecaa05f8569355de509bfb52c4bc9c10a88020e8efe2de7da90c6c1e6aa632c4849150e69942e6d5bce299700f2ab9715a6a06972807aca14f93768b5920bea2067f008d1b2e19407aef6162e6a60d16867a649b6267e5041e48b5b6cfd12eac03883f4bb3be8a5c40a15a2d57640d351177b18a96c91e573f048fa5af325a21c830688fdd00e0c7423852c56c48e81c46557fd6707e409cf9ebbdbfbc54fe8fbfc10e9b2a517a7e2aa738006ef5c0cfa516901fc56a76bf31f00328047f6c2815da0dbb329267c069727191812f4bf3d00947c01d16833e860589a3046b98bb900486a02da0aa2f6d02db7dac08e2b177ea337a176a3e7abcb90792a4474f4cab9534bc9637b505b9d64d1de08856fc21f7d11d6ba263cbe48f0d50cf5e4eed6f66ed758551594ec95ec7edd9bcfeb5eb00bf29d0dde73591a419861ee039c4f649c245aefe8b39c269fdaa1dedb04dedd1bbb94fac7d0a972ebc5647eb8786b3c605a46aadb082ecbb2fe7a6f4ce63345853250b71171304ca8be2129829773cd76fa31997ae782f8fb9e70fe8d804e42fe169dfff0be823574d96d1c907b92e1da04349609848e014c6aa3c5fd8fcd4c37949dba21963d4446d19806720c3ce44ad7280390cc6fe20441310af1181bf2744793cb185545a52d80c768375559274b2769e731a8ecd78e97dfa699a3c9a5c03e7e639ef205c1581555a917a4a9f7dd07f0abf9a1e7e0be0e159f6d26c193c10512e19662360543834e911d3a1c98c6620089817a5d72959cf5d5989c5f178ef694179517f98b09fe0bde6caeb1b2c0345c1e52617f1158fc179476efd61817393e573642da67836cbd3e4b426322d0a4e9cb2925cb65317964dc8983e62862334491d523b236293a8278919022b3cb32e6b3ae6a3021d2754f842e33c4cf223eb336937ba8a331986d258476a79ddc89349f489a9ad54409ab9292222009de2093998cec36fc83400364484a3f462e2e839d45bf6537850ef604853e50386075679128423c39ade3111a10bef4256cd4cfd5a8fef6249e98c2fdfab7a21a2474a0736690ea3908c18661f3c3da72e1204f398d61bcbabbd35cc703d2e0c3e80d70902d076d259d15c405cfe0571b7ddae4072fb64c2af7ee75bdfad32a6d0f85319434c409cf6d14fc3474031b039009c9ea367d975b79fa621c1642cbfa2ab18b4a590b425204e47a2e0c1b4941e48e0e7194a85c0b2f29a0a75871369ca2cd98eea318e2834807744173eb96521d0b2bca7635238d9da0a0fe8c414839889f874a1a336a38611ce60242ab2c0ca4df69336adbca262b9edfe16a5767b6e03b65efadc7831c1cedcb9073ef36a86ec2e6686e3c67b442f2b46bf604bad5961bc67978ff3319a88a62e748509a89815201869a7163062c1d0a328b51c7d6e5244107e9410cbf71141ddfe7a02481ebed1c4851484346a13e1d133a0780e1ce4d3748cbceba4915a28620a3c70730d4e0c8985cc860fc3c8b61ae2fe37e34855d52d082b8f25a43c6902971637820727540710c9a9cbf0518e47030451d56ece01f1c9a9e0090eb8d2c73c019030e77916b25166beb3b929484a80c156fd99b06d000e62f87f3823168630e7561b03ea7a53c95dd3e48955f69ae9603d9cae1359735967cf810946d8d594fdd8643632903e536a2cbcfe77ca36b1b1974faa77891a2d89277e20b5af00b4f6d960fb24a1208c39aa3bb6ca242150cdb7e0ef1c0025e58d6dc45d5b0ff4f05835ef6c54751c74df577ee6fef71c3b4812aef1ea3025cb1032a6ce67d5c400ec29236ffcf6a2bb2b3ca515edfdde2defca42bb84bf1bb1ffb33979f2636cffc05bd19de778f62de66c67b770707079fd03c069a0ee4315072ecb09dbd8e54bea81158e09b1d1e2d2045f41091b445f677b7944422bbbb37b70c640848075f07302bfd5ccfb08c046f36ca473e508c236d08a1eb3c5f9783de82abe1e2cc30786f3e82f96857564d368207a831c66ff1375ca76fde66b66c560ee36b568beb1ca6a367a35a6b7eda5a9665d9e5b211f4ec2809ee4c5f8232e93a2cc272d6dbddb66ce4b00ea8babb3bd499de2e978d6a6ca949ebebb05db7added88afecb371b410d6036aab5e6a39ddd7b6f367a6a0171a0561036d1719ffc75bff75ed28f3ffc8e28e67ee483b0fbd3c222a58fcadbe9d8769defada49490c893eceb07434aed6316a41a849ac575bef16bae73aeb3d63efe4e6249b91cfcf733082124a2ee37e71aab971c5e5d167dd6ea33fa91b5ba99c6d52cc6afb7adb51816590d16e9588cf1ad346f69b8cf56ab5c078be0ed814537ebc1977aa981b80986f51881cd0691dc27677f75f6cb9fbfbe28ec5143a369d7ddfdbe7eecd3dcebdf5bfd625c0435f0aff7ed732bae730cdb75ceeafedd9cdc9ccefdf8e4b2f915cfaf386977d45f843d3dd63a67f698086a003fc347b346ee67bec3ece96b5f2bd761d8a66f6bfd5ae151a5b57a8db13e9caf77ef116144229bafe1e29c3930914e4c635887613bd3b9460dab268b31b6789b7963b3aa9189eb802a248a5f399b165cacc19946a6ddd7db735eb9ef6b4e9f9868d72c23ae747e5d4904a5122b262559683998b3cddfe15d047b00d53debbedf01d5ec21bc3a6ece5cae1c2ecaafeca9941f932ea875e75f653a70ae4fccee2213eb259d27731d50ddf557dcc3b8086a007151966518b66fadb5e222b9e41a62b2f166bd3f91d90bd82f4a0aa82d0552145ca438b2b59dbd1f6d8d85ff3d8f8510c21f0be184b673fc927b35e94f09b3f816eeb9b3df3efcfec2ff58af69d9bb94d2df61d23bcf5e3ff545d8d257eed93b0c6ae09f3952f670c5fd645f69de5f864359f3345ce7bb7e0dd7b91f698e7b59957ea3c20d3988307ab4ebeef01d06aded72aec17ac96116fd1e2d1dbed6720dff1fc87530cb39f0f6e720ea4d3acb9fb696d52ccb6c68dbfae39f2fb97ad197111d763a3668df76fa86eb892ee27770679fff4518361cf697dbfe919f7d956ff3e7d49ed8b633fcda7772d3ec7fb0fd9b3d0778dbd2086a905dce8d280dbaa194524ae946f000b3c64a8c514b145a96087a01490ba6d041aacc992733e492275cb67cd2450b55144d42082184104208250771a439daead75aeef1c0872fd7a836fb456981da5a76738d1839ea6563d2125a4ca541ee599f513e8ccf3ec8cd282d49568831c6317a9ee0868c96455a23685921055a84eca82c5fb248c1c2db52e8c9202aa0019bee514016293bee179505ca76a90044182752984891a4354511570a56700591e5e804597e18314305a645854b4bc815149270b910c54542ac8a1ecedc906486960f47c8b4e01ac28422b09470d3022c3da2ae20a2828a2c2d26aaa430258a10537c7061716a99510112277009b9f00452abc815202d16ae8ce06a010b97942e5d9eb8b962891618d102828816ec898b053ab8bcd070e3850906185a4229b458e04410d790134327187363451451b4ca54451de1322ae2e9879b2c9abcb0a4850214b87c20e2329274534513427e68c2474baa4cebca9711b620391143c28584944b4b940a3c606143eb49494b8b1157092cb8aca81ad25aaa22ba99e24c12615a424ce192010b154a8891c22408139e5c2ac0c18914fc88018bd2095c9828baf9c284095a348871f56801112d3a58719a7a3284d20a9a72704dd1d4820249abea85255000620958154b08b97ec862852a8680c24503132d22545a2fb0c00754931d5c46d4e04a9281842525159658316af1608584961464a6bcb88890c2a5431348542d134069c58020727263868992243114a15ac115a6ca51902a3f7630d30498960f2e2e2757ae2421260a0a623754f470c30d2e2666801d6931b5e0cac184224a94d0eaa1440f970d57fca062862cae23fa56696dd1774acb6808d794272e303b34b1a49544925614232e282db89e38214812405a4392103a618cab8a97a12d445c3952e28829ad150cd10ae2890b861d5c5c96b47a48d2cac1080f2db89e9ce00412405a312021e4921a93c44beb862dae1eae984089d60a535c4086b8b1e2890a767065591245122b46925a681971c21254405c3b50092931c6062fad315baeb872034689569329ad3043e0f0e4068b1dbe2c693149e25a62c465420b474ea8e108202d274708b5aa8c711d7981624b0bcc951494702d4d996108284f56b0830b4b8448d20262c455d582abe9042f5480e04045a895c598960c5e5c4c5b905c6959a1440ba62831c4094facece0a2b2444892169411d7112d6c39a10420ae226df78b3242a808322880194245abe809170b5537679068219992b2bb9e2c43ecee5d354c41edaec77b7caa498c3dce15a04c18a32943536025b0f68b9a828214262a65898f2925079894f2032c4a8902b34294e547b65f94142ed60995514aa9208060a9a9224d6a8912b29b2ebae5a45d5db4d311e5a2c4e6942192dba0f49b51c894208504989421246421b3d2eeb446f4a8cdb420340c5f5253524bc418a39c525fa2a49ea094d26a8fa2a20c65f8de7b332d0a5394251dc5294a2cca14168bc5d23a0a972858a290d91b097b731e1b9bb7b1b9919c94242943b42a52477e4825816521a570d85498503e26d4152353cffbe4cc841a1a8173616171c062c2a82750512ba8b2a1205b2d68282d25b1d694aefb4541f1e177a0e0004586dd7d8f0cab305386a2a6c03426ba24871f70142956a464814e689b529414128ccceeec97fda2a0608982c20494a9bbadd49128a9241b10207a7acaf46c1b196bb79d1d971929776209969040c3932c59b2289af192c468a3c2139a2efa7138401049196113da8b71f5f957ff76b68edf1a0fbe8d2eb2aab3b3f3baaff99d0eec1aa3b91f1aa3b8f6dcf5053c3bdb37b8b3c1233d68d084006c407bdbfd63c411f543d2fe8823a4e1c7f86cd09eff0f4c9aa27e807247b2d3e3ba4207de9133fae96b8ef5d21eafb6addd4ab54d4f4c42736c29844118844969d8eab88f5f2a5168ee5c13a5d25f4a4923a5d48daa04c569a17741ee4735bae46895dfa94b850386dda0c1f7de7befbdf79e8f94d1e39933f03df8de63bd0de41b6ef85e4613d3648c1230fa458951daf53331b0fa569b33d3d06f6b97bdcd352af793fdd8ec6bb759fa9a7bc3793ef76bdc7cbbf136fbb8d6d7a0ff646f69c6e81785c513164d5aec2ccc7edb1723eef8f7c96ddd0704dfd7be18ef3dfa7f41668302f4bc410daeef986fbc996b646fb91c1bf0fad6b20deb5bb31bc09dbdf6824ffbbe9659986dc01e3db67d6bc3bee56452e6447b8ee5e39eb4711b42773d3d11c6ff0645fbcfc73d1731c61dcb68cfc110b15fd4972aadfda2be406df71fd7e1798ba2fd9fd0b66ddbc604133bda7bcd8f6fbe362bbab31be2d7db167b8c0efd09ed39fd2ae5fde9a56abf282f52db00fb457939b22dfec2e40bd2d6229eaf559ca7dc31cf1bd4561cb48cfd472dae34e80b49c6898067cce04ff306fef486cfbf786e2d62faf2af367cfbf3f04017b465f8bb36b71390868d789311b3fbba34eee5eae3fb38fb9773d09d3dd8d90eeffbaa56e6fbe65f86bc74d99ae3ad7bb1ddc7408185d4d622ce68de60aba2b1ac785b0b82dcdd96a3bee867cfd20d29670384861c7ef82c162b9eac10da1aebdda94be589a66d32aeb36fdfbbf9a02d0cec9fbddeeef45b986d689cf549ebb6021d9fbeb42f7d5fdab10f3ada9755205501eb40021dbc3ddf4e6dc2e7ab19dd57b54b6c7736aacbd3267580d0d6be561dfa2ac25dbd873aa1c5183f564a6b377f2617a7d09e575c040fb0dff77b9739f77f2ed0ad419bbea4c55b72b8e8452370d1a5b2f7befc2b83f6dc3568d7970ebc2977a9cc200a21161e2433de2b6df5aaf1f6eaf1bf3c01297fc5c95fadbec6db2b2e27fb9fd5d3cf381d20748e8dd5d35f711fb4b5bf32a20c0fdaf7a563dbd6fdc95790061d8b26d811122c4e0c9b3842a227717c4dc344b888c98f384f7c98a483c64518a6431375414b82f261cd47ce536b7c92f169cf3aa30be9537b6598cb88d239981477500c81331261d3bc83f668e513ad4ab2cf61bbe4092a4684d414d586837ed24ffa49dbf0041523426a8aca7db44815a9621527ad6d369ddfe1d996dce9a4b5cda6f33b3c3668b8b5cda6f33b3cd928233d69296df34f50fa291b65987e4ada9d3f697fc29af6b4659bce465539c020e4ee6b9a63982167a3750df835f8b8420e3bcf0d717b1b13fd737f6aefd4043e41b90f25024ab98f563f7e8cc55824a26a725f195142254c4d90c97d94c0a6ad863bb71cb4fdb7bb8d4973bc6d7bc77de484dfc1dde1227800c81d4545c1b43be80463eed341a6bdb4e3ebf090d9b1c58e51510c6ddbe97233c7dc9c60bb73a7089d76748a30205374daf1072dcb38e8742f17b9ce56a628855138aac1443535f868d6d4d43c26aac1493535353535180689740d24d29846d71a1a8c8b70112e9a62a8a8da70c87e515310f1996b915fe7d735bf56fa11616748e5ce35dfcff5817c5b4d6f86b36ed142b768a15f46d4f981fc67a5695ab478ad59fa59b9458b97e134abd72d58ac4c59b6c5731da4a161cd1afdcf3557535bacf4d7d4e8973472cbcfe2b2afbf7a652378807c43db19d7d1bf39fe5667695d58d2f37536d25a7f8669ad334c6b9d8f1ca67f63659c595ac7b2f2968db25136ca4615891628ed02ec174545974b7fbafaf245d87066ae9bacc7ac6cff029d58fd9bc5cc6265162bbf8c98b37ffd6879c5627dce383fce58dc8eb8599d08fb07bef62fe7d03ebf0c67e1cf8f3916877fb5e230b75a752208eaf48e3032c759fce2a4e5ba7d549934655886655846da369a2687b9a6c1ab8c31c6eeee34347fe3345b8d679c8d367d9dc418e3b591c6f7f8b3230419e15f7c71d2cffcb761e62efd658438e4a55cbe6c04ec17c5e5698a26770a25530cdd0b54ef773a30ebfa63fd2caff1e35c0733ec5dceb8c5e71a9a5fe9c03b7bd62a638d03aa3be3ae6f9ac66d97a67c9461f9281f35e95553d2d3553725b5f8fb3636fbda6affe6f5dcc5dd746c5b5fd86adb1cb66dd9281b65a3a61a9776a5f4bd3af9e253281d5b691fffec0861db7f7bfea630faaf931bc83bc8bdf85b9e7602f68bda02e3d2c485686b31db2d623601f68bda22b535c714dbb03effed1161c0a3c71cde175ce0abf75af3d57247f6f50b6ffdcf691ebf38177ac57136e334ad0bfad1e46719771f46cbfdc0af95bba1edc975f0658c4bda691619d1a58a23709f9cfd4645814dc77bef8df8de186f5c5d2a50838ba3cdca66d639b93a679df15e2cd7efd4ad52dd9fca7c1cf3af5faa4d9592605574e1f088deac669702999476a95cf71baeb32db81aee4edd7ba92ed5bd54e001327f0ec3697cd66f55afef55ad1bcee1a592655946b4b51863bc542a7caf0e6ff72a05a57a34f9d6e4fa052197ed97cac3a2e361b9f32f15df36891d0605bc4aea78581e169b9dbff32eda79f253c03e2c5e75a95c2a59965daa7d6badf5528939593c3fe5a3a7a5caa4599fcdfcf9e111d10e5376efbd56bb376b166723cf3ccb471bce39393b27179b97fbc9ae43e8015f6700f6ac0f816dffed9ffaf3ef2bc772371eb0677d67f6cb34098ed103620a272c50f922454b940e4ab218d10d43623f5ed878bfd80f1362444ab11f4c361ca399c39e73e29410dfc67e0cd95bec4709677080c47c50c57c74511203a28453e44efec3260ace8f2a38100a0e09d1fda573b15e9e8bb73b16072460792edea65c379fe2f8d0aefd3eb0e13f695d1bbe1d4f8233c2c643005b0441fb08f99b165de9847e75ad6fadbd3077007bdcb5fa0e7b9dc4365bcee946724e871dbdfada716e47fad38ee4994177ef69fb1e37f2d78e4ed55401efa84067c0036d849e8476a71eac4d949bf946b6e1fb3b05e5a572a9dc07fe8baf10e0739e1e34e80e3231b9cbf8da96cc3cbb99d1cffd3d7f21007a31e0c7877a71818701fcf7daf1ed07dd41a919efb5b73d0efb732a300c5681445218e3224c848b300c37515a428afd911438312969721f1b0c8920112ed29b5729c54898da06d82f3614b5638f5d7f3ef94fdb747853ae83506cf86e64fe6436ca47cf977667ab765393eea0d30cf83006fdc1874eb0097c729f771eeb366fc79723a7b3137b486d1c1368191b7ada4f8b9eff76f7305e171b9ff950ba3f3d9ee7c3c1d093c7f37617f7ab6769e868bfd8508fedc37e31a126fe5f6013d824e6c58062f6d39bbcc08b012ff030c0ce13a38c32461921d28b0191f6fb23cd5f899052062fd3c06f9ba2a196f1ed2d2340692c7ff81acc1b14fafd56a521ccc5c02e5f9c07569844e53c559cc7feb6843ea2d25088712eee33b7283484493c3dce03313542cf39a1d21685863106f8246c029d6093769e5a391bf9f4ed6bee899d87621de7d9a664f0a2a70132e8dbcf79e0d7973af07e39159fbea947c47e2f85c57c6d5268fad5c638e79c336ad9b5f6da2bb4b86dd1f2e76bda66739ded3ad97b764ae9b362cc791573ad974a8cf7dae870b3a2c78bf676b6277a74ec91f5b595b1d9ac9c58ffb675bad7711f8c3fe74c639fc6eab7411b685f9ad75bb1f05ee1af38bfb48935fcaaf65a58f3f5714dac9952dcdd8336dc9452c9e5c87f3bfe94d2a394324af727adf78b0dc13048570c6d4f68dcfd66857edfd9383f83d7e33c0fce785cc7b3dfffcf2864ce39a73b76f7e73c21e8c73a07ffdb8e1d3b7eec1863c7733ee9cf7ebedece4f5f86af56abd5b3de026d8c85b5ca38bf8236d678368549af52f83af605cb97a83058f68b02c3654b305536b521d4e7baf7c1fe299f74dc53b4e7ba8a588f2aca92af49295b163db55ab55a6bb5afbdeed72a69bd19b8cfbbf75eac15107fd21c2a89b459e68bb67975e03cf0be071cf800e65be3edecb50b2187eb6b32d3b8f1fabdd65ecb592c45cfcb6d52740733831739fef0bf2059e95100941276e03cce011327254c5a6909e98806128530021b020e7870010c74c0818c0824a1a3ac91abf9ab10f60acbd5ea31c678e54faf8ac69964507952c08a35575246c9751f6c89a406cf1a29a364c98852466471da5de06d1557f2e7ab931b471925123abe8c08028df834e66b71da7b67a6a141c8e5d4781b7239acff81acaf407b3ec6acd78bf95f9ef3a503efcce1f8c22f191149cdcb57cc346ab808b80bb732a83cd5ac409bff76623da4363462bf2833488e99d09d8d31c6bbe9701effc079a69c8788186ef2c4a4493bc2a49454617008f6f8eca98e0f746c1c5042776f4e7b33d63169cc9ff74ece613bc629a584704228b357126ca2e11dd0c031fb0d8bb63f907b21c95bc706e4dbfe1052cc316a1e44176ead8dd1460fb4bb80d9431b71c0b81da1a10734e0d3b0167640757fe0c1c60307f0572bcedd05d769ffeac1948e312772d366cb3828a0c9d7f6ba4015cd992d8bfeee35fb1201dc1b12babec97a5b5fb2d6f9f9b591d1f3b527ed7d12efe93e2f5bfdfc2dcb5ef31cfd3facafcfe2eaebbf1fb4f3ab03da59bfde5b9de9c4acb53d18ca30176e57f47bf0bd07ef85f741682d7cf642c8c9f7af7b1fc26ae73f776b39eb6eb56addedbbf0df6b3b4203edcebeed6ca63d6d371bcb6d5386f34c05609f6fedfdee69700082dc1b0fbaebe95e6ccf9ee862becdbea001b66619f4b88f85776b558eb0de7645d3c775ef753c8aa7c59b101aeee7f43577cd39e8a613530ae907bb83af63db6e660866c8d1d72a86afb775a4931e6d4db8bb95ad898d422c5b4c771069c7df96d094a3f452d5163a1d9b1ff95135017cfe684028b56deb80efaffaef059fbf18e78c73c639e39cd1edcd345eadb656bbe1fbf9b262587df4c597f78aa6f94b8b31461aa3c797479aa10cf75adfdd29b5b87a8df3b9d328ed7b5af584985894163251503845414134866aabf294045f48b4a72fa29fa17dd2cc322e1f4d0138b9127711ff1e711e2598d4447702d8f1bbc72e00cb83e69e222c8a2afbb55f54144e5b63e5fcd220f69c390ccb34ecc320be5582412227ba63ed783756bdf7e69cbfc6db39ffcc3f3fcf5f3d05dada2a630dbf82b6f6aa9693be244fd8cee80b36335a62b1d9741e3ba7fdeab5f94856cf7a516b6d8db7319793ff077f7dcc056d7f75783b507ebd9776fd2523aea4bbf0cf3c12d917e0f4d09d8531c37c615f86437cef6759a65f7f7e751bdcac872ffdaaf1b67ecf1c44cd45adf5d7785b7339abffd14f7f15408a441a5518f5408118292d4daa44452188a211248c6070257a985042589ea02105467a28828930491811813f8e6431c488fc9e09068671424288981052424d481439633483c081c8842b42e420868a8c0f505431542525c80b08f1638a1cb2c8a082148e289244042795215b902233f828421b80c214145b86a678d1c18813285181cc952d45aca061ca07158f420b112b544a2c711465069df203121148b444996c05669082e587092838a1420a113372042216746062e28910172824518888f1016f684249280c112846a69841af9820849011d884c88f291ca6ca10c18354112641600850411c7962041915846c31001952e1d2924f20481246b420440e4e9ea01089a11e49618811b445132aa4a04cac8a99a122a8966250126204d67a04b66b25d2b51ee9b16b159a1a5ad29bb6d19ac8074a9d362d82029e3046300153855094120ba6822cc91b22504230f454c943f43cbff34f247fe7f53b2087129e96f8c147165033ea83dab512fd7fcfe5a1035a8aa8614c192233ec30833e01e2b48890135164e9c7120c2c0c51451555676af831c491268c88f8d31412c2889348a48890113b340822381005e9c0c4981f66ca14c10405028a2009e23232830f18a2701283911dac0c298acd6004c916285ea8724491a8084491213fa6868c48292314c0080bce70b1a14715d10f44a68696a6449d81fd500609144e50456251080f106668f0c134441527cca84f762562c3aebbfaf02352c9000a494cd9618528534a984157602406a2a50ba8d0013395d4c40a23aa78e28a15f902e4022e3068529340307c8101cb140c50fb7d45daf0dfd2d086dfc1fdbacb6347dd3a0f83f7e0c3d14177bfdf7dba4de7f5769df2a55f5dfd17c551ffe9c7d94420b9e5cfbbe54f8d661c9c949c8df338dc4fcbf970ee9c73c08feef37aa0322cb2bf57647fab138452706abfbfa0c3f6da3a6fb47139997139e73edec0b7761ee8dbf30d1bb65a2c2c86156ad7ba2ba713d3f5e194f5ad8dcad99c4aa3fa8e4c362989098bd8ae6fe3cff9b405bdf4dab4b82d6a3aa96f3024c27de27edfca1bc0a75dad843534f6c7fe7c1a9d6d58cedab7336e03df75be7c630d6f3b3d0de0e0021ee818c1d0911213d7cc98095fbb61ffb15f7fe27c03be4065b30dabb76d83729273095f9c2bf8c44ab24c5c38b25f4c29a6a4f4feb2d4b67f3d73e6cc97ed1cbc6d0397eddf056da80423b3fdb597fdc67bf83beaf607da51370ef8ddddf5e3fc0d9fbe3bf7fc3d845974d96fbf167a6896661e5ff6a7ded9f195fd7cddf852defd7ee24b1022d7d19f74bee2b49193f645163caa17303290af1b21a41f33b00fef6b67539c8781bfad701b4184e141fcd7c36032d1f133d8de00397b9cc7795ebcddf578c0810f62087adce7c5787bca298f788056da980a55fbc55220134b414a62d9bf5f2c096a6b12d7495f527e9dc08bc91783e2095d5ab8b33588e90f7df903d8d96f274a236863940f83fb7467cfece5abe20ddc56f9f4abb4f4ebdbd1417b70b330b402962ddf5798daf2e70a5f26862fb835c7e1f20502d08e1a9fc78ebaf1c360661cfe72661cf35f11ee69730ef96e63becb87ffa43e65c1973829d0f17fe6d7a0a57df950a3afecef6b076b7fc67ddd128a69daf1e3d2d1864f7becb78398ec00ec174b42da4070c78790420861b5f5c20b2437a654cb3428a5ff30c85f5f19ddd9557bfab236dbbc01fcf8eae6e521b909a1fd8e7e7d9971d0a7b4fec06e661b90521abfbe80dc8bfd76b0367c18ecb72c486dedbba07d9ff6db5a55d3d27efb1da1d065f5ddd534b6d1b217feac6918bf9dbd7a75da67d9ed286bf3901b3f90dc73c33adb9c2cccddd5e797b51947f61a0737cb520ac28e10ecfc3ce453fb2b08e9afa8cc38b2f65973ed5734344d7b0f03bc7ad99d9dbddeaf72ce5e661cf7b5d7b297d5b847279ddc7be13c2f83496df8484ffb6deaaf6e6a2cedc50bda90cb360640df8fff7b6963003fb01341d0f6d74dceb9175bb4f831a4a6cded1743826df8dac1f281174e68ffc2a72ffbd7eeec17ee43a7267f42187d9c4066c7d82f7642920a5d628000bfab5fe97b318068c5b9213e95f83df9e26110a5467d095ff0cca6be03da780fbfc6e37278c8ec211783b6cc39e63f8e478ee56cd41ffa137d044aafbbcc39ab2c5b79cc37e8e31a2fb818660320b8ebe320d127342d6df9f2dec8a0dc20273f0f69fffd0ed67edc0e8823e37840cf39e43f8e078c9cb5f3c68ea5dc0be7e19ef08f0ee18efbb97d31e612f951be9cf467471d4774f9e231215481169f36fd3ab5b4e34b3047dbff0ee9b163eca88bc6b2cfd9afb9c6cd3e6707dcef79c80d7f072be7a05fb92ee6980f391ef1fdbbf920d8d05b04febbe6d8887f3f723fdccf1561bffb1dece0b679076b8be03d0cba1b738ec8c92b7940b8736cd48f0f0437e5e48fef80db665026d1e45729f9367654b5ed531c634fcfecd903d89053c005383de89e1d2a0044d141cc1de364c08e2f76dcd1be385f0e081d4fe9369c939dde4f27a6e100ecfbaba32fdfbefd982bd7390f488bd09efbecb0fe9207dc91b31b98616a1bada70c52480d9bb55f4c489330306831214331a316708cc0900959808aa525a822320cb1d28310241b13a22484f3c2cd7e31231f9bee1733eaf11c6993c0051754e001850e8cb0450a0b445780798287205171bf5810283331d891189026382a047121a3608439e70c52d402043d3c9962ea6949114b33a693333288d00f2ff8093c85068b907c294410263706731204078558101df66abf5810251a0bb4e7d6f16cb28206a008e99224aa8916f4b0434f0f4e0a3d4838483edd7f1c532074887e2f718468f96fe21763ee1061fbdbfb62dc979f5f8c1aa4b1a804ce564631643424010009a3150000280c0a868342c158308c9274b61d14800c7fa044744e974da420874118639031c618420831c400030440886caa0058956e60dc687ccef5e20280ab452f25787b747e97d7e6a747c374e0b440ffed039c2161819fad1bdc34bf5008b524040c6b078a1b826bb73164c6b104e25a5de1a1182a8559e0dddfaf4635b0da881b6c1aa897558ed8be3d22a7067dd110bdd451755cae55d36d890827c00c6ea2242af0b7b735c123c28e02c7d4b5ddf56edd702fd1ece0093063a91724ee3759ce6ba3e0a0fe1bfcc67812f4550eafad8d9bd4061dee4f034047be4a4c161e736547869f287bbefd7f5337a706a10fa0b529665954a8554ad07bd63e5a75afe02a32bfb65ea32983894160d59e4ba7af69906bcdb40462adae7041c9815502d7cd7c191f5499dda49a9537ef246d5f94925b52a0748a59429e1c0d6ad5ce4db1aa403c8299c12f217e631ae28412748b44bc896817d15c087369e9980aed45fd57479cb76ab99d79bb1dd0029bf4be2040c315221117c01b97c5fa765bb4b870f7ba2cd2b75bcd227e37a11612a08b80d0699fbef3bb66035afba84113f58dd2105a62c5c1ee03faf456cbada54b28b4d72c790204ca154c7ffa4d800d3e1b71ebbe443a42b2de73236b557cec969f099bf4cf711c82d9a630d899e46d9d518457539145ab1d1c839246a7e2dc15e4957eadd0c38132a1b0c0fb2b7e85d39a3bfd410ab937f819b6d9803e3829dfc53289ab67789f9adb51a5db31361d14f87a6fbfcf1fcb5c70d8058441027fb67163ec88ee6916c3af972b27178205f7edc3339a6e98adb6f8329b92c30bd6593c6cb3ed79a21a60e48af20d89bbf8a0bf7486ad6e600179953c55e67d0d3e603aadcdf20112e93400de01234c903aadc1d99e8640c07a5e338b6823137ce4aad0e156acca640e50582ac699740041226b0031f4de8144047e31258fd58b840c81b8ac964c58d2eb3e7be3424b9c585187424d5704ee2a0589355af1637630af50a872db3fda6e0efcce5e2df7167dcc62da5cd15aeb46c30d9a9e5c3e82e78b24d286eabdcb7494cef81ce62a14a2d549750cd5aa61b7e06713ae430fa77418d89f26336f01c1529f007a26ac139e8cce3e50237bb00c6fc14c9cece42d644bf62924d25072eeb11699a5eb237f3d5503b1ad955ceab1354a1bd5304ab46ab57463775509fadd77ef49bf9db1edb3230eb39f7353a73803ad855838951ecf4da26fd83941779ef3126c7d3a0005fc945979da91d92874c346110887285b428eb1a124372085b14657560318281452908c9b7a7735ba6393df2365916504ec6b5c49e86fb9aa06828e36cf1647aba68b9cba398cac3c63cec3ecb950292f2b163402b709550f882c3caf490b7674fd093e135cf596ca8c9d5cbd8c2607d49e73dc94e8eeaa8133a20319cb0b71f36c78cfa407a9ec64e4e4444a2eacc009b1f6a07f93e5396a07485c38c36447752b852aee692c9ebbe35e9c751e48fd2be5718b5d07999a04060d02cf0dc624b42f40b18527e653844de88cc60bef14d43d532a08fe05503afbb664a716ce0863e46cf888c01a8684b4915c3956e408bbd1d36baa98ba1ba0c04344f259b944b27aab6493d2b767044911b9e857669ade255632bb7d23d0db64bc2db77e3dc7a6f2e657008fbbc16da395a6cf2150e332a1608a6ce1d8848ada39b3d1289611c90a690e57de879b1e7e379899991a857afbdba1b80790bbeaa3198114d0ecaed30d2890bc705f096a23deeede2e709fd0925f858a9dc395295b54b15378c14e05c1b4145d53d47725d07c61ae8b028d00eb97edf118dd020a24d9135799dd4a99d418c9c28f0d19b3c52aa7672d671a7d23133a9c27a187e66550ad73850656b60fdbb3d1d82c766ee14ac1f2bbfbe371695202f49dfe72bf79be1143e5f6e3fe30b4c997029872021e79dd61e3c002667125ed1f4c23e74a18322d4eca52499a4e144d81f8a0a59f79345d58a5833ef06bc5e4755e6ec68c7e38b328a2c27c934d46c0257b473225995c6318df4883aeb062e82ab2e1950238f25ee93fa0958d65c17ddb51d2949034919fd191bf0a2f9a973225fc231b4b4292fe396098672fbb9a28ade439c007ad6387ba682663e63bc802e6384b8774d8b6243a885be794abd5908993efd73b6fa6f0804459df118d6023b4e40015b185a1a7a840ee26aebfa0825bffcb807db2eb76d83bf66f6868dc229602b9b9d3f3f33eddd365653b5fb3db27c4b34934d31f8a048c2d4d36c4da5cbb464e9928ad4f902718f298f2a8e1b15d6fbea358fdbc46e51c7bca6629ef44eb35fd58c634d34a89df296d723988bbdda5a1ab24827f02aa5788dd994b249b4e12dfe4852814ebfb3e859406f594cd0d89c98c4a06b911a76a82ac155133bdd97d29ab73f27433263811e660d9b4b457af8f54214b12692896ac49158c520d560f13d41a696224398430ba0a92b34234a80a5190cb88037d5d60a7b44ead44325c3e1125c8dce2902cc1780ef560f05c179095d1896db3cd0c88ce25dca3a0f70b2dae5ba6df399c38ecd43955dd6213a75cab25f152cc5ae8f8a71249196b068e7e4af7290690020faa38774130905b3449a066ebbdd03d5495b0679d8b8c94b1a364e9cc789988101c4469cbf84a114c38f7208acc2b4a41c9ea76e14dd47a862c7331f4d12350a70b188e4a1733e48266b1462323bc3cf1e601135a46d29dab548d9036bfeb8a51d2b89654a31c14d150237c7556b9c18906e4be54a13675c47239bd6c3ca7e6e52d808c5ec53648ee6d3a210174fa4518d09ca4f9872b2b7d222d0544809fb41cbd1f4fee37a27990d41df48fb91b0f735a09a465091571a04c0a2ec530ec93338e851898dad3510c4d12d0500c0f4a40a5999d3f2ccd03080e08667d220421d5600363b0aeb85482313ceaad93a35df8e0c2ce4245728307c08d370433207858a1c502394631545fcb8ba83e55c58b20459b6e61a543461a9b16022d32c3351103b282b11c889896ab23d7b5f3ded6f7b897d3c1d0fe53f100bace5ae87c6fc18c6b79fb0d0fa0d6d490178fafeafe0fa08428ce4478205ec50aa7cebb8f53476f2f01af821d4a12b5a3e794cd772792cefa3aa6ba88ebbf030a8d452a53db41e3bc5b9a6ccbd2454243f7889fd82625be31a904a101080c449f14d13a6fa3b1bb05263cd3d5378035a598598ed0d525e25424c8090a53154f2869d01967aada06d19c3441c31f4e42c16c2cccfbd1232ab50b04cca1767712ff67de96be262a9e85c7c8fcc26e536082c8c7e6c6331aa261b7595767105e4d0554edbfb196f3b75133b73c0058a3da77ab52ff994cb0013f9cb4dc343c06d6fc84ee0cf7aa88c836c6f1d65164080a8b5259ed7f9900901212e588b0b50efb852c852ad98678149a901d0ed498f46743ffb3813d9c7eb62084b91bb18c218bb0d7566f8e5fe82a7672f0c277be1549adedd74d4c0fd90683643872c55bc7caa8b063d2db8d0384581897c153de1de9908093e8b9a52a589fc2da8569c22e2b0fbd422705e181e4f182e03a2c347fbc5544a4566e4fc5bde6af7b6dffa661d5f196e9127394e9bce629837f26d32b18e244ceacdbced37fca6dd043eff31fa9c6c43150b9dc3397a927588c0acbc410d15a4ab6673dc583f9eefa14ccb7e06085c47cd2388920b44c84e118d936f22c998aa02a2eda228a01beaee6c045834465d3abdaf60a586aacb2ec8ea2522068cc8f254abedda11ae283d2a3265cf07292eb3433e508d040199686bc7bfaa85f6ce7829a0f5cac53f8e42a3ea0d49436a170d6683dc325318b78590be8a6e8f334b715b05ced5c838a9379287ad38d485a27babe17052a5c5d632b913c44471ee7a0f842cfc4c3c1267c1bc65978a02857056237cf739fc956900bfcd81d257c48cf5b4577157a0d284854680c0b4980b89d117088fccc477b765d422d386c3ba87960d9e4b73849e131b358dc537ba6a9ebefb91be58e67a6f43110a7808e07996c19067ced2d5c552667f687c33438fe37c5b7a4741d535ace6609f0ea466711929d32fb2c9351733ece0210b0402bac10d554cac085e57094f44b7d70a9561f52f21658d394570ecd56d5ba6bcde8aa6741ef2f28a88a40178ff17f9f79ce3ca963804a3714b4a16ce751e8d8fffd975ed01dfef1eeff0121384c7a3df04935774828b7517123c75fd573337aeaf1b7fb70a6e282dd06a33cf058a0d9afac0998dba55084fc5488920cb0730fba12dc8512952e9117ab0c0948c97e550c46e48c3ebd69450c4944c296eff9cb3a6b310d2c2538ac7c02a087b59799b2ced59719ed903777a8600342cda457c4a8f5a937b14299d0d8ca0308db154a6290a4bbb30db8d1ce56fcfb24701602a93756c8f11ec744475d92d4b4632991033e80841907cb0d2dff3e25d34ed62a40abdbc1048183f78e7dfbf9ae64e463984570e6036a488b071bb0e64c762c010877eb820afe0f3d9b46405fd15c99acb400a4c55be9734e46dd4963459bae8ed79a1289741555b8d9533a13a919072b150421dae7259aa2ca5fa4e83375b121299ec39e25059cfc938310b7cb282debf72b65f723761850f9f6157e494ff1a35b6bdf1c8760587172cd5f44b597eccf610688741eb1c61b5b06f50c29c55040fda745275418d5db0141bf461f3c544633a401f568ccc38d671efe99cda168a629a076069a8a72522da245554862c01750304f15d8b909fc1900570a8d11405cea55e149b30bd8b00fcf1fbab76d4bcf63ee02af1b00cf2aa0710b07c8ee0503f121a9ff9abb3aa662b1a3f0c00cd13da776338960077998dc33237cfde6b999930d418b6960ec5eab18f638546014e5c8ed3e69e3f47467002e6c20f3c81b322619f0e6d7091a125a588c384822e127a6f992cd0d77ad8dc04ab49889b3c4df6004ea0f7bc5295b0d0875968822c791c6f107d25a8e2ef98a0ffc4b91793d07921f7c4be8f8d7e5577f6dae34ec76f39a48dd458c77d6de70185093eed081f63bd3c59f065e2c2526d154b23e2f2456d8304a70e6bf6f716b1a3e32602c9026f9df2f956f073d3f1e5ea070c126af09e25af1387b84a861747475b4a6c3f8624c8033f92570d8b0b2c81393ec3d3df809a19daa8c12be6271c220c60a1d568a73a7ec074082f1109e3738d268607e3e12f070efb72f408032e446944cdbc1cbae6dc6d974bc25c25f717eb261c410bf4e160fc0219d3ec911c4741b719710a90bd70bf894cb3ba8c9c27d1710ba7be864bed4a6904d80fc3792e6170a8a1f8a44064a7ab223834f05c70e6827f82f150c11247613e4b2bc651d4c608053e2451d18008e3114dd45ca0b81110b36cf7d0fff1d4fd60cef8110bde7d8b11cd4e9426b62c431e18d09f3012abc615e7583c18186bba0c16c0065a642d2d85130bea98ee28b0f23b8785805f670195ec0d8a374303fc15a10666d90acc4b035bb8dc3b26dd79dab4224afa8f81366d020fe379eea71a9977946538072c1241ba36d7e310157d93735fab7be7c4307b9591e1ef400ed496e75d82c231a411ffcf1bd4a5159b76d2551eb768199324d18c0d52c428ac7534e0a6877140f86566b5f0c377d7cabc2ed4f3777e9b1387be23230535dd1d1024042bd71b73a08feaaa1295b17300f9ee0ca9eb1658c04235cd2f27189802ab64044571eadd00bdd03b00f0fb3b51449b3c0790a73932c6f450926ceddff3293fb5df662fec7bc90623438b9871ba550c709b53cda4c55560e12d27e85347c04fc497d3ac2b0e914590fbe41fd85b09d4bf30a7b2800c13cb0d1ff503b6b884514221baed6283889351d3b802cd7c929fa4b2d9fd619f24286d4039335a65b384a93ed31a92d4cf53c5a8bf5387c795d33ebc151b93b957e7609006e40a299430dadb01442b59f6c0e41afa9150b63980da41761f5dc426128d7f7f83a4daa8d1947fe194e292e906de95cecea4a369b3f580f5ab3721b157a65aa679193319612ee44f4f977263acab35ce30c16016ed491b62e185e55190ba992a8290e51ee1b99cd5361d8465762d9b62abb2ca7e45e5d24b29eb1ca0137a5f31bee7befe642828f7727d6391c8410b1b9e97c6b5c2294ff7f878bcb7120232ff54a0b6d60bfd857c5e9f7cffecec29b209647d87b5650a6a5454dda3254f724aa044bdd7d790031bbe95272130c21373b84334569a273916a0f30ec3bdcae228519214fba8120b421dbaf4d19f3e1d5cf29308dab0899454c85919fef530e400ec95bd5836137d3b3cd7071b66fafac11ddc850b1a5e3ec737f74fad0529125f00c7cf2f3c9ecc7e7b0418b08d1906670be587d59a84b7e589b4856ba4a32f29d2a6ece3832be0fc4a80adc0f02f3e397f05454d020a098051fa8992959865839f3662e3c00c5aee55dc3c10f777c2b1dbc63a5c57d151bbfb3fef89eb80df24f9f56f10d0009ca73eafe4a359f1954802040e321df43c0a9e50fc77d7b0b2be025b814bf3086292748ca53f3b190dee6c74332af3e832583c72b685bf6a3a54f1c7c73da534ac18c7abe2c35d66c708c71ebca3049754bb06c9a4124f60d42a8718606a21d45d5e18b115aaac492d6ac4724676feab6d69bfa07ac1af847536e52361c7586e7a7b843f5dd1de15ce588c137aee4dc34bc023653d98f1d581df8aae5d40907260a8daa6ef79c27cd1fc8cb291d6b436d4d15a60e0d1e6890fcb3449297724d154cbb035235ed451111340afdd8d1e5afbc942d4bd922009fe29cd85c789b5859eeadf3afade76d90ebf4328749890a393802adc0d4dd2790fecf01dffa7588a60edbb78e8336572aa0f5933c85cdbe154b220105a34c1679485c04ad4a53935eb0151f2154f4c30b0fd6939d48ba816c146295ae851509e6d5b02328687fbbf339791ababfa5c8e22f0d70d9942e12ef481400493f7493eb39a41b819c2734414b06eef342a6cfa5134ba14747e41a0e6c3d4989d7bf7a4787f916fcad84b45e8fc1c4b62ec8ad9f014cf3b867308f0e32233871e1eb06d7aa1be5c5aacc6e2bfda515c7de6216d1e66e53ac84ad05dcc18a4ddd1e841462718b6dd4700853f54d1bfc4c12b84ccfcbdb0ae36eeb85c26c5f867b53afdbaec5575b4c4b76b71d1c480f6969f4a642ee03b41542f4c65e93bfd53851d2f062577d50eea821cbdced9598903e2e4db4618e74e2376d11780f131781939199e09bfee11c4dea1a50fccd383fc1ae95df5a89bc84508686348c1ccf26e046118c925a6a98d813c4ec7c58695908fb1c1e61b943af5806543698ff2b0cdd5dd8d8e0f41db4e9d8de50d55361079d7c2a0789b8d6b559b78dab5a975baf406cd404435a76b15169428e44aaec9c10b27c3183a0c2f99fb3d416b183b066fad0c7fbbff4c6258fb6e9656ddff6d96c8e89c2e74ff29b8b55e8166e2146ccea18be68ab9404508074a56e9f0bbef7c3ff6692a9c5a686be3f4ec7c3aed88c8ce9f5c7b956699ea2d47e3197e4a09e0bfae95839960ef7fbcdff10de65d9181113db54139e02417e5f476669bac8b776197db6a13862db9ceeed26114d37133ae6f5ccb74cb71fbf9ea777355f5f1c28a059084895018519f6b85e66902d02ddd2fdbb0c4c8f99ec186735c17a0f83da4d9a6e03081a2202c5a7a4b47ec8b0690e5d3d1eeb8d5d56ec8c7f533fa576ef04b0ae9bd5ca308fcba943f915599bc753679dd907559abdc570c59431df14941e7e3233771df532850b85a33b4838434c1546881d54f0b8fc65ecc513c00157e7f225d207d0c0582cc8a8c460fc0daa3e911bcebbfcdfa3143c3a76f53b5c57449d123ac55a4e42bc87964143de8f5204c7803c1f6b848ac8f10e104b8f1765b4f857281dc2e9c887360695663f179403657dc5e7514a8192a4bfdbacef125e9850911424410c89595d492ecda3ba3486fcd1359ec47b4c486dff23f3593b68e916ff00c9b6a26fddb1eb4697966eb4e0328a1242dd9803adbc69cbae1be3f701bb6e8533ce7e85b2fe4bcb2202d6c87af1e40a2905cf86f53e2c7f8a19c5983a6517598341c9631bb0833175a47124a3fdcfa9a3e13a0514ea0d661238e817a2b1efc297ddf40e1acf7c8d9640f2188401f8ad13273fd27b158ab55cfa083203d4099036d41b9b8b55a88ac6b2147f972d689590e72a600e9e1390b06348af2a2578509f4cdb6ac081af4c30b32cbca56d2c277256fa784d339fbca5007385db6445af62b53e38733aa62185ef48c56802ac3a5eb7512406a47a26269c05756c23347c656671d19d685f13161a20470003bb3dc64d05e0b96a79da1a3cfbba0b6a4b7da518ad992a20d7c7d3808f9a4a973d89249d4c7f8f4f0b56008c7031d9d66dd03a20e147d39c7d81f8cc33daf7b0d8a99f07cadd35e58fa735f64b7655500978216b8aafbeb4043591ed24c2b835dd69b36027e1f170bdfd53670284d1c1ac8e563969017292e58aab825ee0d5d1d8c6ddae5e7b4e632c70e11e0a234a67f555fac13ba99e021bb7a204c9c64a7b72f01389273967b46e6a00b8be9879947ebd918b434ace168e8abf4413c7c05bf0974265f148499dfb0df3b5d344c443f10f66770cbe7cd649bd458cfad983be20a8e06db665548c355bcd572cf136ed371c5d64e5584aaf75e138a233658f62f3f9973b722e267021d9b114ef53ae264ff53df21131499d07a5353fc6b4e08a3a8432083008341db94c40349a844035d26883e8c05c45b5355ec84590d88e2c80160857d8539dc2aca3579e306b71c74a74b0dd50b9659f0a0669cd9d7b91ea5139070a3f71d185090b2951329799d61fbe93d1f717ef4b97134df6b8340315e9e53efef62379eeaa68e5e7b5efa463c65caf5c5e65a0c381406c8a2096c90d95eabc61ad8fbdaa3ef52c331c2c3c5a6367f51ffd7f82d3dfbae828d5ec9997f2baebf8a384ace8cc08b209402edc913e546b7dea618196d6aafc11b94cde2ad877a9723f90215e01bf6b3523e103741baeb6443e1f5887c5ef96b4928f81410a83064b85fad32338ef29504a6ce5081614f19d03c6aac212f6a4dca2a3a24bafe7c9110b2d1d302b30fb2a52a6971acaefaa07536fb3e314afb5dcba0c160f51866dba919515a3b782684c9bb266c5665604f578d40314701d1a4233cc98853e2a18f5f15eceb167e61210b9fe83a176ac2f0022a39b31f370ace4543a3718ad10c0b2deb6ee09cfe0c22268adf21f11a995639cc2031f4014467a597686f6d2526d0200fab7868a7d1cfbc4425cb4bd0511380d13136fe24248603205a707ee9ce6de3b54a6c01c671fe9485d0168cdb111d9a00ddc0bcbc0daab4c2300afe7430b4b305024a2132c7b6da32d1fbba2e07818e1d87897ab9e0a829da0c03d1a1d8f4dfb5a4cac983ed038d4652d151c950db4bd1fe7f8b1212685f96fce4ba27ca077938e95a69b669f830c04767ecc810b9a2ea15f74eb2f2afa8de932225833349915a4df85b8383f009d56dc5200fff05b64d19c15593a4c2dc91c0cd898254dbe2b3b178862d7564daf513f0fde0568f12e05a2dc5ad65a607523abfbd131bb583f246d9eb1a5613e919bcf092f3e588396c9cc9ffee38a44e882f1d97d61191a9b68c768ade5645bd03f1044a789dca43909643a2a0ac6f775021de277feafa94440b1301fd29986b91d4a91c0e8e79154d2071e8213218054a950f7522fa2b0542b755b94b7746b7a2ae1574b306a79603f2b0bec8dbde3d611b4ed27e415ca335c11d4c2885c7f3b9d721c83e8bf7c53f9b75615b958a3df93682c5b1c1ad8e918f42539b92172389742f008faf2b9a8244a099a17642ba7b3e5f6b42d8bed1ce88d878a4c7937d98be22ee1e2cc6d7990bd40ae33cce6f3270450ab68bbf020546562433100ecc17692e674e900ec57e8978ea2b1d8dd89efeb8f1b1319780ea35ed7223ec6b5d0b53e1396428517cd115599997800da3d46e83e197a54bd8eea404ee41fd617d89fc868f025747b6cba84f5a7a41ac50f2ead04fd3449e05c92d1daf0fdaa97274b54c5eb01ed4a5747c2e8f989bc3f81d4c7a3682829b4a633d8a0357d4452cdcdea2028986f391df27587aacaea9760e536fbcbac239da7d92e9b133bb68344c658d92bef58e56072cdd4ec2c1631df5a3113753e101756e9202ec98fbf06f0faf5249a038093ac8310c80230050a32528dd2fea21dd1f43426c6643f1f5cb1b7d8de3a032e078462ecec15bf84392dd290b07c45b52c44f9c0b86174e8cefb2819c9f8233fe8324a3045e0c3b8535684107db229bfd3a0130fe227eb48aa6c4b2eb17d52188f5e1140c7b222578ab779d4ad0695086f5db9341beccf93ce4a25cc9882adc19550c958a60920e24b5ab2c3921bbda5ee01afc9562f35eab8a8e87f59e9f9052b4ab098bc770ea1dd4ce91bfa376169d50cb4862fdbb00a7285ad2fc6ba025669a9a3e26862c17b3b775a4bd7899e3a673fc0722f34d43f7d83ae3bf460b7f469f61a4b764974dde522382d90b1d41d5823cf04ed62fe640c91e354b132525c9e8e8977d1a8a875bada7a02ca03b8872260e64b2ed45cccef0d43df3f314647c21d2ae0f0b5b732b95ac79b263fd6962f65074280db9a60ed92fc12b1bb1ba6f4a3f8e24ef6304f43ef4170225bb7d4712d7202162aeecbd6566a7a214ec734375ce36023de13644a25c96abe53ad804beaade4522acadaf67ee40437e4595c5de10760f865c07652774679946fa78a8292753f5801fa3e0e05caf140f2346d72b4cd078af9f828bcf58c7ec76abb90ecb566136b6aafa20e98d08355879e0b27d88726e82998ae223ae28828c5e887e83ebf666a792bccfafa246b97c93deca6229d0f3ffe55ebdc2c9166c5a5208e4fb415229d40f59be519ee28dd3f30afd1f32b4e2257249b804f05f0c07c79e2db8ee17a07dc5224e4d866728e696770804b958f577ec75ff9158649e4ae3d89044a23fae4050f06a912fe37146cc7f2aa3124ef4ac13d9fd17037bcf96312e625a71bfb054c4669b0c37a68b748565c73ad02395d8de420c67a4340dea1a399057844ea36437dbd8c6e1cb3303054a3c23b3043e35bd11b02f06c9b7187605e7b4f82fd408359053461557ff71dbe34c7ab05fe836e32c28b5055eb47180399dc06cb0c12154d27d6a4c9b47c6a8205743e613a36b8d4e3807a1ee4aef9d4b9e8229efab9e914ec37715165299ea48bd29accfaa585ccb8b2564c1167ed833f9667b1a72fd1d48f0443b62f53f039c27bd6f54be3e25e2140280b3d7dfb025cb7ba608f934184365f84688a78e90c59515059de9c65f8ef7be8c87515c3961e8a7b734d1d69502c78c3347fbab1a34131050cbf5f047fb5b202357d9a8d13d8871ca60b6f9358c164030c804bf3bdb8e11746706723c9cf705960e7f2fdefae1f00eacf95c0963f70d6f427c5c77817d234f9610e0be0eca2316293ba85ad9b16daaa7cccebdfe63a9aee710abd835dfc23795b1b5d31b5499a0686414c5ba122b9f2590a88ae0cacb9bf46d4eb6cc125c00235191b579781b51086d62f1bda4e1305ae53d0cf0245dc8899dac4c880c24626b211648675dee3cfe1bd3c844630594113df491bd510c4bc63c230abd718980265d5a2abdab1f624954d6709d3054c95d35d8278bace5a070a9b0d09108ae2a01dcc3a15d635be66dab78f29738eb42f4f8a26d9fe739d8802b41204ec1f9a6be72cf85683f4fa0e6ebad003a764ac5dd70583e458eba2146d0554a01e0527d01422186590539389198f327a08f6143b36323fbabb4d7d8d2792e4161679cc2779bdfeb6b06417fd5173687bfa02ff802b72ae049605579ad7987fce8b2b1bfbb5e14636b8367e2b51d65976e99218405058c74ce7f299a07cf12c9e213ef333c94a5de311f72b73af4c285d3ab383f76ae3d1b2cc7f43b5c5679e77abfe8f9a1f3eddbcd858af4766995b3db9809782fac44b5da880c0c2e2d00056b81f61172d2338ae4a33bcc1c305f2b59f6312ec237c48733eb72135360c8725216be5e6468fd65dd18cecebb12690f000cb6b6dd6d33f273b01ae434d808d2cb22fa922ebe16b026ea119b8d758100773a8897b95a701c6bd28599003f59676d775734ed5c04f67a40d418a30124c8484fdd7614dd2612f2c946ce3e22233987554bb746d4249e4e926ee64380db68ac38fef31972530ae0970e7e2604d83d036be6b597e3767839aecf42ed007fbd2c91201f3cb3233aa4133f253488856c54c21542fee5611e353d26c261cb9456da002a0c5b0e7b593050e6427cca3c3a0359f70307bbbf481151aeb981fa983ca364dc5a7e58401c55251b2debb464931e5af9b05256f6741fba55fb2472230ac177aa1157d7a9b40b60da8d06b80af78ce324142344122acc4a8ad8d5948cf32b1a062c97d14bda015fa7caeea08021663180d025c702eacb37cd5a42943460e2d3449daaf9e512a1019e86dda4e3da95e9dcf5ad0c5f0d0d331e2d39dcac57a10f284ea0da4d1e4b987bc9b6a609c287f4f738422df9f188f5a9022395496f2a220a79f3406a8b2382871475dbcb63df5e39e162430a5f0a452b37632732b21a8ccbe2a972ebdc3d0e84c7aea5600d98307dcb76536750dbd2431a1398251da4966f1a73469e9cb1907d2d52152b669344f3b65e0eb3091f0b219f4fc861545cbbee44dc32d839e1e330f7017880adf4bf2958756b048c05ea0fa5a841a94531cb37df5eb2388ce5f084cfbb76c0d49d62b6fd0b49384f468e7c7be1849ce1f4d67ec02e6ea3f9e7e22c8255e839c276ea84b2b1a55716e261c41570d2215b61524c078486d5bde254d88fab74a87d533ee2c711952c317543c8438e4d5feb3b8dd331728c84f7dc43a9449aeb55737b40946a591d143be4a0267f6a0cbdab5db8288cec4d20f88842cfca51bb10c6b6b154914710ae3d864e860414e297e18193f73eec2508a89fa149ec48b6af1b3c8e27a58c794efb82911f460971b58ec97119ca0aef28c4e392f6c96ec9cd7384d1943d3166228ceab996ca18512a1c859fd7d4cdc81cf8f5dc0604971ea42d250a374a068dbe06d106a871b78e426c4140e3424c8b6505d4a54cff694ef9f9a90cf8b94787cb661b760fa1262967e06e8b1a01e2b0c8994fcdaa45097d505f16551a8b52560c2a642108a575c854140121a747d5ffcaad61c11aa3d85a54ddcfaacd0f45cdc32aee64f3c1410e8761458f75bfb7e6346ce5e6c73bf360481ffa7f243300373e75dec154698921d6d4e69d172eca2457c027a1c7754b68b27dbf6590df20af3961a419cbb5c56d6911c1969246cd726a5dd8a6d4e2d782c94ac5e0e522ac27db67af68bc6ba0acc3f3370067282d4ca635e2af39965035600f98f2f85bc932d628e4d31fd5fd9252bd008b3590d56706010992218dbb84e430001ca35481ae92b6a653899fe8ce03356a8eb4581c8e3fef1c5223df7e8b294517d40bb92dc09c69449c033b1fff064a2bdd2c1ee943b7ece5ca748b7750ea0b3d507e24944cc6785c83c50290aca49e399261e3bf4eddbbe9feed74ec9a88690b4e093eef5d276061ea022fcf07a0508a9ba53d6a19cbc9512690a082d2d765430f70c8f3984c782db718f199636d0a0fb4492c4632eb6792f20fa529694cd0b2ce9c3f91467922962151f19e165142dc076fe41ed42c84201735734b6c4a05e5a8c1a735a25328cb5d50a47bfdd82e5c3be89921e4568a6decf75e615af3c47977acc8ce543bbecea56dcc50cefc296a1628d01221b624d766f0b8f9b50fabf42a05748757a7d75bafa0a2c4bafaeabf9d2c4e2531c9b6c6d7a7bea89d5a1b7140d91051e4eb5db9f22cbee01bca8f77252c4419cc7a1bdd1f784d7ac77bf14a9d44b73cd14c3f71acc93ea0339baa94a897dae5d174ed07b415d36bda80f5a4027e20022db71d3c119ac666f484dda0d77ccadf4c1f620fe5361df7eafae2cc45408fb690da5ba6a86b016fb1249f7d1b3c6c585bd7382d0b9487270054fbed0f046e3e5cc438e91fc1aca4217164207b8fe207e0bdf0b59ae38928db967bcb6e4c2a445ab65e598a64cf47c42d8c3c69511028518916ba753210194cf6a9ca89bdb64247e77a3c6d0ba8283c002b000c0d8bbd6df76542a20cf62d78612c0141d977eaa7c5cb9bc723c3fa52b42fd99537dae2ded6fe4b40e455064ae8d12d1168f90e62d4434e9606213a2e193bf488fe8e65a53ebd23bb0cb4f2c7ead2075979361b26061801dc6b97bb2a011604af9856705f072b05b664a4f75043c9c931cb0284c4b7d1792aac7389615d129aed11e479655f1207667cb6f88032bd5ce14604819e51e4d2815c837aa85c8bb45f177a6835ee2d9660eb35e82e6d29da22673e0d943b10f1c4a5fb6e24762077c03d4fe09ea60d56b538aa9f0a4fb601200278ac8ebd9bdcd46a9c88fcd7189a2fd38df6d8b1ef4947ecff7e1dc6a1f1f8ffb3c6d6683ac40005c473b8b265e4a84e98615b1cb9c9c612adcee42a0f36c7bc7af54d8aef7ad59a65b41fea9a30576b9ded1e037be58be0a64126251c3ed87af261214be9a4261cdda81fb5805734f49fec7b78c4abd0eb2682e02b7de03ddfc9bebadd1bd75a79f39398c52e4c1d6357ccb02fc0974310dd952773ddfbb474eff14d9197946f7c7a631aed6e6812052f74b5dda2c674ec9f82fb60ca0be357904b7e4cede59c3fc375b200782dd782c9b93cd373fa563b3446194914007998f0bac245022ce422613265c63dc07ba289f867c1d0225c86d804493a26abbf2e9658094eae697b24a3073ca28487f9bd7e0713cd142319dfaa581ee237a0cea5b1ba548791e06b1f6728d31de1a0cb6a3511970f5480ee3e90f12bf20b12554e2168d1d7421cc11a5d01f88f46f6c278d28bfe99b28968254247c7212de088bc15d2e5c0d1c1f3fef7b40a4c225a042a19f9f7be014efc227b38c1f90e8da99ca393b1a421212edec3990adfc863602cf7bdae2414c3a7f244cfae60888232d08ea08d4a8b9696aac14da4edfbb468f852d4ec46ec27ff9a9b27ccb085908eeb961dae9cba3ea75b23d3dbfdb5c1cdd9c38c78e6bae5e2dde31a12762f809ffdd0dd4b2932575d66cda7d68b41f8c6764c4863b617eaa324834ca8599a34eb0e8abe7f55f5c787fb74ef42f57c0681dbb39aca260bdd23bb156d0dbd2d16a28bd3facecba2eea2465445113762b4ebdb786775c6c872eb013a45f609b23468210eac6d8a92d58db6bb9d8127acc7245f09c4c288de4476dca56109175aa6a9c06d2a8a56c2f877c7651f033fe6f38ecccc3e51f2c979752ca7a905c9d3bde1f3bcc92642f4561bc958c3ecf38152aa3231f9ce7d3efbcb5524a3edcf75da4e0b7efa0a5c5842956357967460f77976a5d4c651868369b44827d6b98b8f048afdab7ff18c07753a49defd422d9d14a80dc86a14d10779b1d91ad33e8b31d6fc20ceb85e6ac70f38a007729850e47e6658fa31893e296a8b85148e3241b4b92b1d4a169e3536d9ea1741d6b8e7769c281be26d0f1c49e011c9db7f7dadfd8cca85f3492034ce458bcd3ac18d4019b3b7101ca9765c79f14b901868c2020b26ceb95bebd30b922711072b6b7bcb3c508fa0dfb7e91c85ed6343fc1371cad72263cf2ea9c43b0e07b3cc32b7947f696d5a973de5d964db3702c2549ded7e84b715031bc4293c5ba4013891c75f5413256ebbd1621840ebae00ce1e197c269e7df3c941dd27b33cfcaaf10316ca27e7c9f33f73cb1a94c02846583cdbd06c17b1a3553c6e47ba2b2936761c3d9587059358907f738ba5d14d71345784189b2b178340eaeee4e5b20c25c23eb75e42547c599835ba8fddd54358ba73c12658da85936fc052f4c9f86c6e0139f896ac5e46538443d32904dec99093512f7650f1149205af00bc05abb6b17d23e076d10cb013799c0ad3a6eec6746b50137c3a01f44985be1158fb573daaa5eac9d58436ff3d48341ea3c585137923d81bc4d9ae770c46570776703997a772de630a7009112b4c8d7159b3b0e0b9fed47a7e937a219c5119b879509a7a7980c71c1a21b3dcaf4a8289a2546bfb64417ae25e4b8d23c9d45d6117a4602d54403570d4a1f5ce524f05da730182490c4a4f8eaa55414b25b8f8ddfd9074b6ba23598793d8f67c70040412e3bd295a3a98b500b8a682a3565e188bcc6fe20a1003ed3635c083c528877904f3dcc72b448b61deaac5de552d57368b56862cb39cd0a0192995b2b599a0d36a7065c16af8f072a5f8fb429e0081ad12dfd1a19b4002dbe70688c2e2fd7b6b3d145464ae94f729a491783bc1c158c9562d2d09548b1b34a6dd7b71ffa035841ee85dc5ae2ab1a94ef22d14136b0a4e1567481cf1cce39ef128838906d25a2801d6b74016353fb25194b9dc002be8f2c05f8574cd59161f468fd92cfd17e3a6675bfebb03ea8ec4f8d727ddf0658c2a096d0899789bccac1c2d13d93078947bf2aef40b1af1480242bfb98cd7219de66b30e3124140c467ea989cdad6a1517db37f208801842d98f9ff6c6faf2110da7b9c8db5e3c62ee3f205a5b4ed1b3db07bbc79fd669d85e65e85bd45231735dcde3aca8bfa99815bec953e06fa6448f27c2ad29e9ad43039a0ea6fb21ecddb0ac6a9384345fef28998b7b2c34e2c2dcf07580086fa91751893b825e1bb7df32c4fa83a962d69fd374486561ceb9192c5fa61c1a9b6e759248720cf0d3af2e295a1d58ba465531b5831d8a1b914a8c1ed0e25cda2d4bc2066b8f350ecacd463329f3e8c0714b7a2f330e82a0dbc9a7b4f2bc87dba1ccc0a73f92599135af1d76750479508ceb74be7fec7fe9ba4dd8e1772b4893afbef0b71829bc39cb4c5bb8a76c4ec6346f026b80ab4791d9c56a95d607982ee5837c481218ab86b0b6238c34eff0522f4487b6d1bc403bd1c37c0acc42cbf98fa361464c7207c45858a0f74064a580ed9aa8613ae488a8bc1f401130a531c85be80acdfa0e190d068cd7202eaa148ef1c9dbfec336cb8ae3575c1a063e1b56c3a1ef1fc0d29cfe267bc7a54eb642188676766c2568087cf429d3be54c459a49c9e20a2fc5e1c0dee52635715b696e65a7a3b62a93c1cda05b73cbdbd7a0918fc3cdd21334656818ca1fb5f3341b80bcedc3a16f77f66f0ee4aa30c4aff149fa3197a43d4b5c30e3d1d11bde10956feae925bb5283a5ecaf7b0506830bcdcd1b5eeee50f61d28542cd4614abc20a7962dfb42cb6bfaf11b26e4ee5103eb7080e9eccaa4244239436c6824498f4b99702b160e5fff77ada3d68f4fb41a7470e312131f72ce901070563ba1fe9f282e5303601e5623155f2481da5a1a17d91c70f02571eb659d1713abd45923b041ceed76ad0fc6285888f99c6e34a90666230684863bb18634a72d2d6d2d2bcf4109774dcc4977fa0c2bc66ca51acb16abce184b2c666a0c4a492d6eb5d350b574541c0644970d75772d077ccda11bbfe6dcb9878b4f8757a6ca13d69d1901c8d9329490134e9d40fb23d749ab3538411196292730fcc110655cbeeaa3ded9851a3d10d3d9b1ffe0d2e092110b4c4d548a9c19fdb245b41ead35b46b41a9edefb40191482830c92e94e348a07f218d14a5f1d5824bf42875114bd9265756003375a3ef953b7631e80652c7a9b266950996bb8e4c8ac66991ad40752e3ee247b54f7edcf565e12d221fdaafe5e152a041060b93c9d4c384936c0ed39d078afc4b5e268cbcc34d18c0de0c59afa8bffb9fe1c4fb962bcd9fa2ce80f68604f5f4da2cd7d1bba56c55a53fbbd7420084362e957a5d70f697ecc88d114f9d04c86d26c65ba121fb33b1abe5807eca71965093c04d9e576080029cfec09e47f95a3df29d30725fce274a9c5493250e2832a2b77d9b620671e5f7e5e90dea87544f888d689d620202accee9324a1c8f68088570d0dcf743a5f8dffaabe6d18bfb07da6ad4a6a60a9e1db60319257fa198c8a1875d41b169ee8af9c0140d1c98712991ba356a92046ead585b87dab195fa681490227ce9c028ee3b4b840514c038b0bfb4e078c98288afd862095ceac265cc2f4a8ffe7437d50dd6b80d24adc286ce7a84a2960106e56ec64dfb4bda0ebacf46f2f4bcf7bd925607599d2003f86fc1001ef862ff1d1df947682c35a0af309395d8c61799ed4708ae99ed6866745ee3e0d598646b311819385145cef1361585ae41fe08faa5cca47ca9f3ed03cbd04dabae0ffdb5ce00e938c5cccaadc4dec13a52756c9a0ffa657c60dc5c5bc14626bea2ade2f981b0fdda1aaf1442586911b7248340575fdb25b0ab32047b003f6841c704fb7229d7c24082ede377dc021e8e5d81c497c6401fa423bea20eb6be231819e4b374466f80caa3af5bfe55613dc1d83aec5fcb6bbeb50e6ba41f03c0c60485d5b749611e0d2e4ff64ff10e0863a19da84be9a70e5e72bdadb5ac931f62b1b5f02c40cd0eefa39794d656b851aaac32c233a1ed84ac31eec33b97a8f3b17374df3ca94b52af0604008fd05708b9292027965cdb7dfd711a14ed4df15834d940a44f27a69302013ea10a47babbb0162c2493e6a21092bc41bc4725823dc16c69da7f6d93077ff6b1337a36969e6386dddba2d0f5b335ab6ef2e9107db995260411e0bf329c544f0ceaf304eede12892337c8607ed5773c9df97dae307698c8fe9976edb6899e799d1491866e4a9132a9c1b8cae9325b64a65355a2aa9c4a01f8494402c7af4b6f3327f58e23ca78d712b3c06b92c5f8e29ad57da4498137360ac6e2ee6b20bc81f400c45b3ba2e816b4b51f420d3a043876b88299cbc54b5ecd2003dcbf4e95d7932c89a0dbdf445a59c9de5ddf571901c566997fcb6f98f3f8ed05b7024bf944d566d9d2e54d78ee53dcd9e0c6ede698d9073d4411c7c262b5a57ba8e06f7d638dd7ad07a532a1958640a715c480c24b481a10f7b0347b7c33b6d0128b05303a41dde49b7da8aa25e6faaec261d9fa77826790d1a532570c085ac04142a14410c9a57e500622a1cde80686100ddcd48c581f769a1b1f4fc29f123cc2d37ab48cd00c0a7fa16521a6bce57610852438a5a41b5c74c0bb46c31134ca53143dbc8560ccf814b402b2c03298b8b4eb0cf97769878e13aea57f64f33f5ae4be83c9343df6309c642fdd18b0ec5f64fb3ba9f8cd71113fa415b917b9d3521b85372a60eb5c443a9498400d83780e8a21e0160aab1b603e3e8167f6d52648e48398843804df3ffbff7399d81cfb2fdfc0edb81d6b536670affb83b38bec67e088076e6dcaf5551e7a4489ba4ef976b95f39bd45dae8eb2007fd6bf1d67573add61ed93626755342d4c1a44055056d021d6e239e9f3dc54f83be10403a70b6b8753b45bfabc71cdb5389e525f68cefe0e014f250b6b93c271fa4918b45721a491f3e1c6f3c5c0c3f4a951a6a22734475fd3486326d73d9571339a117349d433c49af19157d1247751e471e5595a24d91bc437bb8cd34c00ad737fc1dbf3fa5fbc7e0740dce264654be5013a265e4b49081b832a9de77643fcfe3af5358a3a99d7924b6233111d490632b71125828360bcc49c140ccc5dfa4ca4a0e6b90018092b58631af0c1c08a4e302e36661ad5c933555572c6efbf23237177d69af8cbcf445682f05bf6ee248523a99d785ea4044c8c85eb04c3c7da68a0751005fd40b46a16d4fe82f5d97aa57b7052b6ce60519242984d7aee7bf5f86c04fd7e409d65fb9d001d7d99aa036d31c239d15053a83d9b4614800e1d7456811c54530e0fa7c31a549b89694fe5c93818bb4e3e0014b73f0bbb23ec711a941f2187fc2c38e6fa032699d1a03e30ed35a3f90d89be464947c2229c39480334b229f6470007ef50f88e3e954337406c4cc9710642dd079f699fa89e03fce75349c27d87a77592d8c8046b8701349d9d102c3d79cd9bc7201051b042228089e7c58ce5e8fe1e90deedf6ea5c94299b4c4c71bf938c785c6bfe2514fc366b7fe082c230d6fcc66cb18617d552c8ce9257a415cb42211fad918c7e526312efdce8c3b7e9dda8fa9c6d6883d139a9064c857b6f72005f6a5c8f78ce1ac0a83d749628175aab1c50260fa232db9538e695a47c2b15268de29bcd233587214c1c5e96ef1b1d809812ca4bcaf02cb05d0d0cc5838db9f3ec3b558be6264b68e92787f1d762ac150db1d0f7791cac942664084673a77a427aa94333302eac0ed11b248fd6874e988f9f5f5691c1337de4358fad0dbead55607a6c33e332d51e9e23595180047ed7d8ae52bab5aa12992ecfeea93d90ad07d17dc3839117f79c498e1f44c99e3e225f4b341170aac0c633a45ebd07259ed25382a070ca47c744da457988fc1153643aedd081af0a34df731e1df4e92f075f661d0a49a12eb77bc8ffb2100f4a9f531ee130e23567f85ca52983901840b1957c405b2ef82dce2e208d76e35743418be58416ed084ce21e68b2aeffae948c5a71713c0a688e1c0d4a9d34e25488248a24868cdeaaa8333d0c048434eb6565cfab7369faa0d92de2a83684ac3023b39b18053742e479410284088ccf83ad016cc2e49b7207dc90331850cf754915f4343061884a585c9b0182f5cd390b16ac098714d3afd90378b7f59d1451c252130f6f80eb51ae5b8c7eea1174a34c5323d1a6a7d056a75c73a3954f9c4ca3db5505385f6bbfbcf040b6260a3f135ee62fabb62f1bc0b05041aa536e9d7123d5f24d8530e538b0e8f925a02c54000d66b34ef7f94b7b8983682ad135fc33076adcb3e7cd612272b14618b7ea3b5550418de4ec9e3cada5ebbf9b23ab0c32363754814ba99b8e5eb8b57d1ff423a382a4d3c76f27fed77f7b2a84ccad3de1bb5922044e1a207433040f5102ae247e1a7d6b52b0b261da426df64a2b4b75e6f1ccc35c74983a54280c87b3b1914920103c404e32a06a2ca79315b1e22ccde9ea64bfb64f01f7243894e5d5194186d3ccb091a8ce0128a584332c28d5ad1bff2952d10a974e134c8c758f491f00d979768e6fcaf01cfc04a8feace6325f1e2ca716e165d10426676a2af3cc6ed67e83fe6a88628378b7ffdbab527db6ff5b88fa0cdf6a82e62e44677cd6068f14f9e7551103e02da444b84f172efa15f18b4871cf06ec0c4c9a1c9db18307ab0d2b0a56c71cb9475c1eb308235699308ee7e0abeb017db96663a55b602bc1635d80afcdb9de3d8f568a3a037e44ed9466c6d331ae3469a842bdf36819f80f4ffbad664592898b5dd1fb0cbfea8017080410c9575e0c25fb3c4c06029e526fbb6bbeb989d9d2ee761d6011e6936241f82246ada35ba4e8c1cfee829632a947be7d1a06188f16db35f45266480f2f6efe6eaaf90e2c50e40ef607d447031f1c4823c96fb01e7a52585afdbd6db16e756609bf3a8d33e08f497b5a780087aa6338d09f43ef37a6284d3bf28c5360a5c9b72280a039fc95d75aa2cc6ad31ae8ec4156e7a8e45a1b9c2c8bd006cf9b144a6dc873af687c5fd9d9c3d2665b2cb2a01e1da87379d538231c62048a95c0253ea30884b939b0802a815998b973fec783120b4fc6d359e08511ff5bfc8ea456b0068e0e4b7388924c8b59a247a6f5ab60baba0568f3c4cfd029b434af48ab3d9822836da8600e3c3856d1a4df27a7e84f4d49ffaae15567c30287adef92d518f3a8d72d231c51a607c812d79526fbea80236443e81295bf3eb2ae737c99898ffac4e9e90228b4b64351695c73682816f368b16d36c74761688f19f05e4f374c71d517eede16555b2a33dcaed7096d81c5f6f05c1985908ac4271588b6bb091478e843240ab77890b8ef887a3462c4b4f63941141536408f1ae0aa749d732173c9edee1b17665bf51b606a5171dce2d1115784998c1e430ddb86bd4f33a29d550ff3daf7e04cc4261e13fe676951fef2f2725eba5feb9e4e7d2e17f32bca7d436f47f4589faf0ead5cf16071e384c3f4757733308794e749d668d01283967416866edf1bcc501fd2a89ed6a8866ee43461393f0a5a579232185594749b415c6718557c7ada3d4f39d7e803e212e3efa1306581d12411bf633b092c1d8a902304b6b1c6c555f8e48219e68cd39b69c6d7232714938e7297288afd38c588024dc37feba27ae070b566fa58e2436b4cecc01bd3e05d6b91012d8c64901b4e433e852b68e77e06633dae4d981c59150001cdbeb91b94c403887a30939b769ec7cbc266ce1bac41a86f353ef4d964625eed3d15682c72050fea57d0bfb1e7226510a18d4ebfacdf90395e5b78dc05809766df5793b157405fc066397969ee40fcf8b4d08fdb8b8f89087b7b5f81c07d6778430df7e63c8c1d9b1a27cd9d1315c141b017c2dfc800c0c4ed194f9f46e10bea5188d25415ab2c2ce02a29662579a30e53dd908ae8dae45938f1cf1a257809dfb8426f5fd6b4e83c6da0d70a60943492424120ce6baa86fb511de84e978a43044267703ffdba5f99889b9ab254358342a305c656e574740d93833a49b79ded793b720d623104e9fcb3f2696d6dbe2d981e2f3dbb1df784f1cb98e4e3100b490d2b413c7c69cf6851ffca3f28134a9babbc509fef9738f8bfed3e535dbedc607ea0294aa8c207c12b0f3d1e72bea6d385f10be94cc8a8b96bc0c8cebc69cd16ee24314daf4079678944bbb12867e1b03f921d4b88ffc28c5e3e7b631fd23289e050ae6b50279b90790a5b830142388a72659e8953882020ff3dc6d46e65c16f0b397c55c5a035da1d08115f63a55466a1493e317e88be2f4c2189db688c37895aed10bc75045596299e65c92c4a8bbd22b5d74cdbd17e1ebb345a2f91ec29428f115ef9d244c45610a067477117b60e4410d7907bbfd8fdf145c3ae83429c00df2e62c9aca7d282c21cb424cbc86431804a29457b63b0d223e8c3884ee05861ac5ce23d84cb102a48914696a115999ac9b3de3dbf8b56a72c881ddb8f790a3cfbc0d7afce47d7d38b6dbfece5dd70ed12cd81845bfb3a7329001ac4584ab5788c22c833a0f5d7ae3a31eeb0cc246fa76911cd2fc8990584f6635cfad8c4ef831c1b04d89c181939fd5128fd627be407ef1cc8fc7d4f97b845b8042e0a7f8b4cef2c87373c8689bf8f74570b94509e60c1dfea64d69aaff3aa90fbb72aa6ab203283f72626d6c9114718d4e297c96767591611128d12c9281047522e50f498c209303b19640da673aea6082c6df8d9a8fdd5246a00d2a15c8dafdeb1b067ef31bb3f084beb02286b75b70e1131f7d570142241ae28c7cfa1ba7977c4208d02b8cf97b8eed1ae452635d92253f81e37f8097f698141a0645ebf7a526bf452ee84c07b575c4c51c7e748579ed3d478a9fefdabef598b6222dfc7debd8589e30681cd5ac9930687e7199ecedbe72d8ffb2b41d1405fb5b61262567ee0edb5a1b2bb69246d2d22e89f4051d1cf29d6a237c456749e40307e78a8ca9f65732d77168f59133436ecccf1d8350b55beb1c97d09b4c9b80c0ead0d384b0506526f8c40ff3456364dd1ee10bd59bddc51a89676002b3e90730dc6c54e6b1648869bd569676a15c03754439e7cfec75c1a7674c1ee014c766cf8ecc3368024e17a56a03b56923edbb8f8afe18be705d8321b0830915466cd0df8d7cdc288a98437f51294896746d4f1e08a27b831155e5345143d4df5b5e03f37ed514d7ea5080461790429a587811494217c26729c1d53d4a22b7c3693c439b6efbcb42171a77a80b639438da0800545c4b518b5cd1c7ee1ab77e08be63b61cac396cc6c3088d2f164148a8a78f91bf2de3dae84d4985fc7b4ddd706115d77fa188adda61827e9e0974cc0a24529b2a032b1c72b60580b6ae7cfa709a91a644cdc057d437061532402c601028e9faa5c44da5f2878983691fc02937130826d4569cdf5b405dce0470846f52323c9a3bea78dad4b0a1b5ae1e05371f81250f963afbfb08eb41e16e4981f609406cd8bd578e0fe128351c51956db83f4466a085a88a99f596d5eb0d8d4c57cde1aa3f489c22572d9261b024ea7be5df6e42fd595c0ff146c865dbf888c72ac9bfe8503e6c4f47c08a87db980ed1554c703602464ac8a303e198b6f95e3c23d17a718237e78db4347b918eefcd4b2a55c4da365064695120951020d7a6b0b5c5449202c2ee3d91ac035774e57096e2b56e2831fb77b390a16dafd51daec6c40c0dd210f14309edd1a10cd83f15cb5fa4e213baadf4848967ff6a9ce97d503883d4a303b591f2b149a2d539ee5cc69c783549757be7be9a962f4864caa6d8295c311800931c53e85c222e3279055e0a22ef732cce0f44dfd0e40eeabd1d859e3b351f0075e92f269f3425d1beabc9adfa57910b687eea16ad20e3b32d4eaa368d3c01a5fb408108322d21e053e80f25acf3c4cf301f20868503eaad73805ee9b607a6d709e6759f984a83226bb0c15720832e3eb36ad088c1f8840a0d1083181bc0130a906f32714bed0167d53e452f870f716bb8213cabec020803a2cbc32a690616c5428202a78c8985843dcdd97214745fcf8f0226c42b8d5129bb68ae0095d2e2f70e7c025d67948f9f84438e0f8bfb76b057b83fac774468c804d811e1016e65dc2ee9a3faedaa362f20b42344ae1f581f4724517d19a53c0b1f109534473ab9e79f3f24925b5895034bda6f8e7477326a3afdc974e80ebce78f87f70e475f037e3b7b8cc3864a48254923553b65d64c051582058171ca5a7d35f412b6be872c0b8a4acae8386b740711412ea86cc2e7d399606d20fdf0544cbeae6d8dfeceb7a0b87f0335f7925aaeec9f5813393c9b138c666b0cdf54b9b7f4349c0dd23bd46b02132b6ee7acbac61fd44f0fd669e3890f7b811178db9286b769dc4de16bba63c21fbd6e0a4e8790133fe42c22fbf449a2c30f5ee348a62665f5595a3c8fe5d9408f73503dbcd22da63a801b5b8b6f8b798bad81bd290fe1665fa1fa386f0f41d83ed7e453c1df6e4b0724fa163ee5d1a2c2b0041930bed8a89a997300445520f38e9bca0d2dc9a1e4f77d871fc3f57d67374a62cda7c4d0b75477452118a9b240085dcc3e0f0ca2a241989b3b8dd9957423d160badf9d162c7a6def77bcbd0242d15e2392f8532335534d9fbebf7fd4dd7c6a24d8d9f2c35a96f9258e1be8f8e8b2741206ee1562144610e39534f58893bea1af153b4987494efc9afe39ebf73ff5a6902636f43ed9b5c6e03f46aa1875fc43f1d17e43ecc0d4389a9722750623b6c90dd834509f7fd1a58e42c47fc37c848863d1581ba815593fbeac3244c73242c06e810b55e9a733d47a1a7e6a07f467a14341e2e9008950cc14e22d1a73cff5e597e8bc5925384a841cbee470a0fc773836167ac99ac876371122eb23909ba4caed2519fcc762e2f227e39e90c2169477ce435f3bc453947981b61e87f69cf80f3cfd074b46033571b8e7484512d4f6fe33599c8206c6ea49396572689d81833d0cd5f8d85ea1fd334485c3301c7fadc09f44776cafe8d66083ed6d54afb0c77f261831e04858769632ada1678c271228ed89f036859c2af0c6efef13a01201f2b5d2dc12763e47a9e8d2c7b990caf5f9356a05a1002958042b7a5cdb7b7107fecb483337b1d84681a4a12838c4f68702cbf7a5249cb13885271c926c3c41945260af0e4908dfa2e387e0d8e23e03a767a2bbe6160ec1fa6c9cda0529457e921c4289b21d451acfa7af0a8599510f652e377291c94a7464d5340d982b0e85ddcd5ebcecf33ecdc12f24731af75a795a6862843215f58d37c6c80e02c99a24ce0d023524aaa0f07b8ab211e9a198408392f6740aad7e07253de860e2082a3628252d1e7f4cf9d5de6149095d0bcf9d829b53895749fc3eaf5f7f0f5a162841f43e520a5af114b458533dc52e04d647f9861a45482eff6a577f9f7376d1a98d64e572be1091ce145928ee548cf2ab4d752c726460ff0daade17b97c2e91ef9d718986d02677b45b50ee6bf95ac6123d97383395c1fbf20bfad7887f25a80fbefa7d289da6c9c056cdeca6e24b763cce894cee0bda816f6df989f9b173bfff706694346b830ebe15c0d1a910c553041929e9819d82e01f271b4ca8c481eb3305c70e10d6a02188be4bc0b99e6d18b2d0f70c0f8930dd4d43fff0365d0400b4b280e5e938abff4661efba48725c3e571a514edb92a74b3a1b0b4b44a808fe802870d0d5fcf84e27f6fc8c7e6bc1d742100ac893a17fd17dbde95e2c82152842709fade47c47da89f8be8e2c6a4b5a47c6b10c07b86d14362fa7128c33be0bc9b339f45c22baec05d1e925ef37c428c54369bb797c718ba0de36a0657ae56edf92dca98d04e16b24f9aac0caca438308ae23bd569ced72c424a0316d0f8b1509eff2334cf899b31aea64d0200e2066333a9b8a80edf8eeab22fd0bdbf30cbe98f84024047e96a8fe3105ac4a640b8ceb648ad7398050f348975c5fe951d0bc2db834df01a00b458f3de0ba4940a993d8f52b54bb76cd9169babeb959d9f26e8e197a0297d98b35f5bb04d40632ff213e271f6069212b97f8618ce5f215c112127688a5abb34d4aa09d452f805b0d4bffe4d598a187b526588698969670f7d43fc60a79bdf2cbd8276edbcb528018bfb4c89bd19426015ae0ab45d1b52b585a2a06cdb6368b652b60e938c2ce79344ff2cef1c6f646cea1b223ab4be40c3b3e99171d55ff3f1e11a4ecbf04a40099302b853f3505ce79f63c9e4d659ba2c9712147e995c908db5899f44a6ac6b3968ec1d1e66ac8113737f1fab807b79a95db646cddb4ff908bcd90e155f53fbbe32c98ffa6003c6bb6342dcd4604c3dff9e195d40f041e51643b7eef36f443e6e4c867da88e450762bdfbd98b3fb4c86db8c73b7a386c7ba7c6171d1bdc3b12e3aa39f82922b434802ba951037b195695b4d49e1b31c479f1679b44da3d8476122a8729dfdbc771b7720ff95ff2bc2d3255c7ed0b6124b935212868b1c80d43f4898102de3ec12227ad36b039a0bc73886dbff0b4e1dd5fbd2a4850b2b5401b20d0845ad7d4602cd4b0fb8b033834d2695a2fcda1d848fe40285a2085218ca92c2d9d09a0ba3b7c0a47b0bc3019e25d37bd865e35fbdb4ae7f1dcb3bf5958e6ea3490ca1d5ea21e0a0e579bb2462a606187bc6bd6b0f9d8c833308c945f8fe37000fd898c567e9ce82520d8b670e312b04d52f0126fa782684964128bf8e25d3539ab92e824e762c8ea014d0c5f50284278bbabf685d831b4a25836bdaa19aa28f42bf4312fad31b3fe9a5432454bfad8464d79a220eb7201c71704e1dbbf37ddc90cc4467f6de2743c02f68dbcdedc80780f090fce404bf70cc4f3075c4da5779f112b650348df88054a881c6ac61d4c58879a81b63b930a92888943f8f09ebc7ac41e82275916dbff987e4cfcf9e0a207ea4adf433b9747924c7acd8818b4db2ce8adeedf91f745333be9c9022ffbafd81c22a271d0884c5c8406a44b1ca801c721d0c37b2a040d2698aa0b11f51435291f3ee8355ca0a3ce17951f14f97ee1420fc0987f10f80baf1279c1913f08049cddb3dfc0946778a5ee55f4532c35b4e59810e5c15bc92919f302689923af3a530aefc0495e0d8414cf0c3027051323c8b693ef861b9171a21c60ad7219b0de2b777031d2d1909e779aee709b62bfd4f930f0ebde7e269cb10f15293861a517c7634eaf095e8e4a010d0a559866bb9a115aeb5d1b23df5b80dadc07ff78bbe16a6de4a8bb9c96dd45222f9691dad32089a3baa5fd2e3cd02f708f5dfbf16ad704485893e0401cfe41d9f5b8fde1708b764b9dde8937c10218bfe7a7ce26d55a197fec0b8cc122056011fce12f494a0e580f12bba8bc4b383621566f277472378cb703f261682568b769a30d127a5647f4424abe1fd6e95bd1b0c8152b66e050a87c23d36d3a757087a93fc4dd14b58b8c3f5a3bb8ee8f6eed93ec8355068f2d8edd51c429a055041de5d1e9ccaee8e9e69e51911d3cacb21f1c8978045e6e91147f8b1680e00123efa6d43833ebf308307a3ea80a157228c885ff219535ef270b9585c21018603c31e061582cf235a159213e8a74c27fbba6d1b0d15be0097b01c727acc3590e96a125aef25614cffa125211b2c953fe4ed2e8948544084fa2d82083f05d15cbd9875538eb5c21abb7123ff3e6b1fd7bc6ff468cc3fbafb29a8d9000ca8021571bed6ae66ac35813dc1b4ebf78fbed67b573ed5d66d141ae4d50a5177467a224345a5687e31a41e2b179a7bbf372d6eb5e83ebee9263bf75fa2bbd35aa22156713578bebcab2632057a949bcee59d23d9e469d13180b3f28fc2700d99a5300aeb7226fa041963e1cf61eb2d6a20921fe53b3e8065b45f5d2c16d52036b5413c54b9615716aebf07ee577565a3064a65019d69d7cce451cfbebe8317d403f1c3349f09234ab4cdaff2b9c3205c82f0a4240c715e313c89f93bc1670838915b2f97b73e744029521bfd9a318adb9576d25d290a3ffb804c8b4eec73a1a32dc140adc3b8352f5323200fa604496f6815cf19ddc60315e95cf131416445e09e71838d105e5e6467bf374ca3151acc10bdf28c1f4aaa996794bdce4094ef6666a65f62649e80b3408bceb856092f1e7724915e8137af0ab4116b0d55b044f28939a3be786b660470cdc43037455eff53e1810a1e20cb47476660b1b87b0649545573ada15512ddb6d4e9922b1533a05dc5e87c9384fd9434806a5b1912c1d3314b5279d9e55e506253f3795e9bf31b0694418acc33e9ea731071b9045c1c404568afdb9e9634b22ed85a2160e9321eee1a8d6cabef6cd2dd4f154030c97c9637f79e0792271f68e972d02b239ad549e1a0d8695b058088788793e61ae15ae4b67861c8f1bb9ec398a22008d241143042375e896993773f3c0b60dc2398f9ed09ada86a401f495a5df9ceb5ffd26fea8dab0b79d57be335e951c64c749f5d59b9c8d7be17bf418dc90dde6f6d8637aaae3167f696d026f538219c18a0afb0e7f666e9afd46d7db0bfd8dee2114f98a49fb14dff5afabd7fed441b63ce64eb8e8befc1d7e68df20288bac52db24efb5b38d62f9255d7bd94ecbfa9f1cd0ce67c3d412b46b0d3d2148df98185207529ad7b6f6a2c332faf786f7a90af049f768e320ee7dac4b61b3595d1ace1fb1ade0653879251e12997646bb215f5d6731419d48b264ec5afa4fbb6f46d5aa7d7516eca527419983ee55acd27ab79da35e97acbb86596587dded598bd4a57ac1a278dae8cd7f5f7d246b3f657cef3fe6a3463a6d9db79e66e257e1454aeb3c51e35a929d050742819159e3255ed2ffb93ed6495523f8dbdd24dadf71c458edb262fd5bb795dbc6d8f9717cb6b30e6096a84dc4ce57a91b14f6810d8ae5ae7b549dae4b297f9041acd38d572cbdf6c1e3293b5efc9d354465db3acd3cd64b195fb465a1e839fb020a85ca6f61b953c424381e6ea70daed25bea16637ce351e9fc1633479283114de261e23a1c0586824585d1ce7db5ed05cc6b24657eef3f669fa1cb1a5f9e2e5a8e2bbd647f01af78a4720f3d313d82b79f5c1d1ea26684769d8521992d041e4e8f245e371dcc0fa6fc08a50807677f977c3cd8e1905ededdbe28e7454b9b4387529ecc312b9ed96b2293bdd9c6bf48bde18ed74532c34b74f6ca0c225dc09ec6895d342d8eaaa0c056a7403945cd5f61fac42f84036cf2b6617da5fb01255be1961a2612a55c7799b428a77e2766366e1bbd6da563dff9a0d2fd08d7b722fe07e97080828f60c75a337879aabc444a0bec4271e93304c16b0eb894f201a4bb22af72d36c480fbd21c152456ed966069c8b91a236c54ea7a8f5db18038484286390fe962b3e27138c86e28809fc72ff320fbec63849bcdc3a3d87307968e1d31f60c590649618849588bd6a24330ac3fabc6e83f435ccfe4c92fb79764d5e17f7114f46fae82a17e5e782a8e26c8f96b390b9f75f68b63f5d2daeeb497362dc3f16a79485bdd99e3fadf7e20ee1d6f9ca2d51f80a4214d9e7aad6aeffe0649144bad7fe9dab6c09be4ec97ed29c6eabd724925fdb75a63400142b238f67c558210cbb77e6958a07053270c3d90ab57179d3229a64d9e48e7fa3c6121de0e45c6b8ed1dbc84f67b6dc5725d5254daaf5d4df0058a9b40adef361d4ca70be3d068a6f7afeb2bdfb0b1d632e563b5865a90d3efc739dc31bb5ccbd91c0f26a7ca3b0e4b316ebcba9acc4c001cb44b6a050e7f972f9a46288b6571f3c76fa41b4171b864e22c2ff39d10f8d8aeb03d21ce4ba8c487d43f32340000fb26a42075abfddf009d002a510a30619948ee431122394f68fea79475f7f0f103dc9f7dc760b902e61298c6734e9885150a17faf0eed7fc1cb81caba74e43fe29c9e9aed9af569ff3fe3fd40ef48eb5626938b34b36b56bf79cb53058e101fdc65789c0a3d374127e9677dd6bca82a249f8e00020b9684e6ce94e417a74183b9c6a3f07238a6a24e74835ba28f07c3ede57e05c19735b9e7ffc6997745544f197d1bc8a9c03777d5d629fa91c23c15b42a49084c47380243e227d1985a35a7fc510968ca31dae5904915c720f292f7019b34d5f060e7f22a3ae1794f12de2eed64e4fe4b47fbddc9e11528c539298fc97819560b2c4f68f39cfc3d66dcd9457afa15d9343205eca96559e99c8b3671abb2e4c6c1bdec6ccec4bbf014b4eab71cac3d90c569a0a66ff8870d054d741a3126620ffc4e187db762fc92a8469cbe81853e5a69acf44c2ccc56c84caaaea426b544b1417e2986395cec5accfcb0e3410211a34e482c84faad8d29a341e9e28d4eedf57cd569d50116b499eb0e8f59696f0b9a706c9b2208b9fb14be8d33d844ee5f2204c8532e0c1cb754b50dc18ec8a5365507e30d4da5361164fc3ba61f8be71ac122b0713354dae71070bf56116346dc6303a1dfdabf38cbabcc7f8381bbe7a5b34fedeb2a6cd0e1be4d350605fc4d620815f570a0b35e06359bea2385fcd67221e2321a2f80147c44b6d8c435bd94b469c1d6995a14c51a31622854b97d0648cb745baa1b7914d7c29fa07a7097d9f06115a1521a545ac934416cc6c7a5df178de0dcae655ff83e63b4c4ce4118de308cb2efd5f554e60399d22dbc7201a837eb48956b0f8e85d5bf299e4bc01848cc213046755421b31255b82278821c03a2329611f6726421f42ef5b8d6f8c1c440103dc08360cc6869a1664f93e19532291fa6d0eb0c3b83645694c3b878477ecc97833a2fb17fd45a5352f6e387be7ae27b25bf767d132d0ab86ebbaf2803553fd88960578f9f31cee0131ba8a877911079884819423f52e5350abf12332ae67f1fd266293064c2a93eb29ba26d226a8fa6a5009f4b3026aceb95e6050901d710e47d6ba854efec14995ae1ee7fa3cbca4c31e5b99f60143190bfeb2838bec5efa5f08f384db0760118f3f55af72551c6da0a39edb430ce0181a2508a5b100b43f7c44678d5ca2f8e93021a0c07fabdffe777f6f91bd456e29a5943206060c06b105f7de8b6ac1877b5235be1e353549dc1e363e3a7e66703aaaccd864d4d022b09179c4a001c144605c8b7b88616a1bdb01b3a9d6a29a7c34be1bea33c6e3865e43e923ff9471a15a6e5f52bf608efd1acc61735ab19f83b3e69064800c6a9f7fdf4b73fb77efbd74850e750de6d89f418dbfbe8157675aaf9777e74012c8f1ad63d169daecaea7698fb5f05ba1c1dd2de8750ed47cc5b4b7daeb690f7e1c073390419a16de51d755838966a0219d3aad9d39f41eae1e45f4b4d649acf3ae87927eeab1828c839de281521e3bf020f28310fd74efc598474fce596f3ca2703d6a708e9d7a69ba47941e34d87cd810f381d37dd87451f798a2b9988f1ed6da7b392154d3b49c99701fb66ddb38cefa88e2c389f723c7ba623ea6807efce0ede88fce0953dfc67cbcf831feb0f9b1a49fea0a04027d1f0f273f860809d9a91ff54747950b92814522d0341e19c80e223dc476c0b69db4346148136647d9434ebe19db31db3104a9b48389ae32e26aee9812e341a3c84ef588e9f0d1e1a4cf980e26bdcf980eb2cf988e57176faf3a7474dcd0a94e9f3fbf62400044e410841830c6181391430e740e3db8b8073244d13a339e157ca89eefe7bbc1477e3a3d8f42f719d369d2c58fb1319d034a134a69ad35b11c52eebd17631cb37c9373d65aa7474c87049ae3b8bd7560311d241da7604ca7a79fea6ab3c1d6aa237460e04086941b3e9aacb84ffacd41418d8fe48362c1e7832f470e8c24aec783c60caf2ff535ada3a1aeab596564e80cc65496a8d231609f76fa52ea16009d76ac54b77fc18e05cf13a63ea5dc82df4c89128061cfba593347ac2b12843d653bf8b42eea188e1702c80963c22b8b090820b1b0a0e8338663d6c5125e9870696151a550a71553893412853e90d7f1cd6d59c3b90473565a5f3012857cb0e9cf580e1a28a5b5c67094b11c26c8e9a7baa2f596a0a200e4757c739bfeae0c3120720440bac0477286f0d84c7272c0c018e328b11c323ad7672c472bc7d6543c52316a80aa810d88f004558423504bdca08bffcdc84117bfd4a8039492aff619d361073ca8f97f145b9f311d7c5083030d574e9f5d7d13aaaac725801020288211362f1834588189c8e146e7102307193ac6a9285a8725bdeb33a683912e7e7f6dec9e3a339df844f999d2e7575995c9d13dbe9d13920a8b61d2e757d74f9d61814ee9b3d6cc2a8d7a339f96145667e838522623ed19a993d18746e9f329496fea0c256d20690e497b903b24923e7594efe734a5a6d360a1ca68efe47b4e4c26acd7013c8a13cb47aa757f95d16ac78b79766024cf4ece7a07d6eb6f24b737e71c142d141fce3bcf96200bc527744318d31c9778716ebe2f7473737a7dd1758d70ee0dc65b0c96a2e113ef68442a9962bdfe0a8ceaaa2fbe18db4af1de255eda268a6b7afd15dc3a61d4fd49dd54ca569a32993a167bc5eb55569bd459ad52e8a9eef4faa8575dd98e86aa7258300b8bad9405e3afb22a53ea1604754a29a5b4ba3e0d6f3a6bd75e958a5757759525f8f5b2d47659fe9465599658d32d2d5a6bad35a93fcb699b2ecb9fb22ccb922461244992a4de7667c204b7a0a86fdbb66d9b4faf63eeb82d7fcab2e4ca72234918499224e61d283402416ec1512f498ee3388e6341735bfe94bcb465599230929396244970cf663eb3d96c360b8948a653eac4098e5d314e7ac5b2fed32b9eb2e9b22ccb92849124496ae3388eb399cf6c369bcd5eaf9dd7ebf57aadf0140ddf29c5e2f2e1083c262626465cb150ced9388ee3488e244992242d29a525a56549694943176e17edacd4fafb4ea7175775b5fa3f9dbeef643fdbfbc23921ecfab00b845d1e7675dcacac32db4f599625deec485e18e892de25bb4bda4b72ba9277bc37dd1ded1db93b6e77d477ec5dcf2196d5d5acca683eddccceb8d936d31a7f51d7abcaec1dd1beb6d7ebb5432cb32d5b536730e7241616142806505b345dc721960da1b94c5aedc55a76b95d044a4ab7189a4d95c183244200c30b6e68cc6e78b08b19abb1048610ecc7dc26d506326010e16245071e354a4ad8c4a00811b32a2f70113b3268229c2cb12ab0b12e88f001c35ce08fc9336ce0f810810d1e6e7650a9818d9f19aeaabd3041ea090c4be8f8f84182270139047991738ec1072d2da0268c25a2f333a464ed70ea985283222061a768f47b32e407633ca4ec18e36b87f4401ba28322500c6a223c448480413ac5880c41c4e706112110598207064f0d3c3af004e1f18127089e2178a0f014a1488d2237140952842cd282224d4650e40734187161c406233b469418618191205041f0122d60d8800532b8e0494f1044d8991a1ff9c5af055aa573fb6b07b9c7a13e670579df20bd3eaa167ea4a9001bbd82fb9cadb5964e9bbd1c5c8877e615dcfa0635308339b850770cd2cd72da9c34c4b96b608abacaa0ed7bdbdbd49c277ea06b9ddabef7e4d5133f50c1cada3686db2c0e3b6f63a0df70776d4e8ce7540159d547b2eb5780fefc1a86b3b7a07445165374550f1c6cf81d7471d5ad1024ba8d09fb8c09890931d2c5ef99a07020244a17a2040d8450196233e406a5c490130061c88f2843900c5932a4c91019082a90aa990109c7d7fc53f5824e167cb58a17e339356dcea97a7d73e2a01855c40e0c5dfcca1d183b35ddd4674cc858c39e2844d1cc4fad8d4c75316ba60cb5166b13dfc0239ecd6aa96b8f6f8c15cff00ccff00cdbd4d594c178c433175326cf982bfb36dbd495cda37db1aff5f79d4e9f33b96daa0c7e61e21978d66d4da7f685b9a25a7f5f1ef398b5a764ec1d4eb70ff2d98ceea6db54998e86b9faf928279f8fcb2377438f9c30f6533af842211008040281c6f0beb4c5333b13f2a92ff69d689f2fcae9a7acba8867226824bbfd1a9ad9f844d0788178e45491df5a1b9b8e069f3a336b374e15f8ed736bfb7db1bbe976caff9ca27c3e95cba1064e98da23da177a4ac9475fab945a4f6cd1eb8bc64f466a496a496a496a496a496a496a496a496a496a496a496a490a084a9fc51d474c92a014102c52407c62ad6549922920b4d640b065a4ea728d442419a2e3f8cd3903797d7eea878f565995e9eaeda19546b3a60dbbfeae20cdc7397eeb004b22b1b07cf7de171ac4b2214fc13e91443b9ad9efd49b77bfa3092f147a2d6799b4da8bc33a532bf8445d37a73b1696d2a9e26a166abf99395faefef4d5b4b576bb978584c3cf9e7e45c3bb3b1698b334f4eafcc39e73ce396b4f6dd223ab785eac652deb6dea8ddbdce61d9d74f2d97920eecdf9a009cef9a1595d7535ff47ca4f098bc17a64b0188ca43774ac3fe5f481c24209d5ce12fac43193d66aedbd98566befc558bb58d372d67acb7adb386e6fce6dcebbcef3409482e8474322d12495a669858e48255389d2b0ca08a0c3c6523e2104d101940f08b9cf5890257a6c4a35cd66cfda537dba7d3bb3d73ebe55a7e654d7c59a5642b7dabd9aee589adf6dfad0cbe1b036bfbcf3401704b29582f80581be8f5f3004da5fa86389ec8f46fcda2789f80537a96391f8b55fcae9f6f7ca89a32a0a752d4549f9a935b5f5f353c6603d32188ca43834c7456fe8f8534e1f1f2dc5a5d1ae58028b6a1d8b4e0de68de336c739d7f1dd791ee8e34021916824e2246e543299564cfc84e250285b290ac55750a9942ac5595ab896165b694b0b57b5b8b89870e10f7220682b05358a11462081047e4100acee6a652b5d85fc3b9a0b9ee858b4ff0551ec137c848e453b2561934082ad94044d47d0b4a729752dacb27e4391fe7c6a01fb8a3962cf5a73aab55a3b22b9f7826e8727a7bada41081fd70a693244de38da3790483a4aa32426d9cacbc9e9351a8d4624d20eafd48b8aca93c1f2e1b4783a2e7c08901f5a380b0b0b4b4b0b102620a7600c089410089513a21b201d50f013274e9c40810248931d12ba2000c029c8430c569cf2708397529f311e920800772100548d019c74360138fd4f899d4af213fbe113fb71c4ac23d8a1e606093b8400c60842ec700379e3c40b66089fc80077d8018ddf4108314cb464b8c888a1659c81e586192a1d37a9243750321aa7979319565e485c53bff7de3bc477fb8cbd88f0a2429231c3086714e9cc101a42e303f2830c101026313c205040d001a15203bf6163eb5083dbd978084206cd430c6ae41bc488f190440d415cd0b8416a88c006d1c1a9414819689015d8c482d0a0e33e63416040c29204d86b696a86cf884ffcf7d1234656c1c70941c9c007d827fe0b1f958f886ff6e11f44dc1d2ff852ad8fc907836f052ffe1f851ea28b1f637776b0d188ccc03fc488f110e2de2afb216b77d6ee95ba60cf4aedb457279b803cc2d34476a43c0285e3b8bd91b43a24385b0f766a8969ed4c12ebe23d55d94774349524639218eeab883d91187b929f4eff43cd1e707ad099f667579ab487561258929d24407ae041f74004cbd8a920f03a519432d94a28a5b522b141e244ab76465bc45083ec87924711391c5981b556c7f60442ccb0dfe9237d1fc655098c287ad4670cc98f24396eacb5d65a5a7f1663adb596beb514451129246628aac5af225248d4a0d56bcba6e6666cd9d4e016e5d9c1304ce217e6c13bf855311e716e725c23cecd584af929a74b2767e7e5d2c97159aeed94c1933e391c34f4a97f522594f4992bcc077d6af6060e7d3e9d295c3cd3322e3586827deaaa629a11e746cbd15cdaa8e16837dab877e69c73ce39e75723619b26d3621aacaeaa46e65c935b5aa949d17eb452772c53b79fc59e9c73ce39e75c6b1797b3d3ebef17d7c9a9abeada38e79c73ceb9acaeeabc2e9d1c1797653cfbf4fa5d9e79b949eea9ab9a7572f48e7e6997d6d139bc63997a4dabaee69ca1e65b975d4dafefb5405a8afed125cbd4b7dfb36e3dbdbedd645b6c83d555dd48cef9c91a7169e6a2954cd2399cabaeaa0bb78bdc287238dc4d5d558ec48df6a53ee566dc8c9b71336ec6cdb81937722337a2f0466edf773afd7323377e276ea49c05531576d2cab22c698837d2ce68d7466ee446726dcbe88853ba4a9df255f29432994c2693510baa409465ea59bb5ed3a52eed4cfebed3e95f5c75aa618df7e5f4cfcc29a5ae6b419692e2106d6aea3ae79c73b6376b9776d9192ee3daa5b5eb6a9776715aa69c86b552fb74287e99beb86d4a29d537cb643299acb4604bcf2b34674b29dd3acbb22ccbb2ace4b2be9ca629dfc88c2da735bd71a835ccf5b96998cbb86cb64b7cbfa60c6638b8912ab1911556dee855bb8ce815c53107ce8ee213a41d8313d31eba98baf53a6ea9c6711997512ea35cf6d5b5ea9cd6d6b39de53ab4206ebc39ebfc5efe8ee7d06e9aa6f19fd686b9d365c61ab7a1385be032f51df2bdb51d62cbb3acd72de4ad09b3c35bbd4e6bf74bd71ceed77eadbab85fbd7e76d97ab3eb66d7cdae9b5d3714ab2cbb4a3b535d3ae5abe429c932465afa548a6e7d36a7f171463b43635af63539516dd4c65e1fcfe8e3b8665467565ff4a1e8f29b524eadb769d999184d7eb1538b9a4cf786a6deda404c83e173c10f8ef8415509420f3e185f0cbe1c7c475ab123321ca99932172d2b398a76a642c1745b2aad9ea801170d5feb891b30b1b94d5f4fe4c0873e90d7614fec00785a31954823cd133d10be09971616552a7be2074e84e17da1a04fe9ed41b1ac02f7550b2875fcba8b14898e1f77a91c7c5a0a07734eba699c6ca21049a8a74f5d5345eae9d372aa503dfd4a4e152c4fdf8e5345cbd3b7b3a9c2e5f5d38fdbbcf3409bc6d1b97701ed6cc2b880f75b40d2b380a5954f89961e8275fb2de07d16b01482b1f0174537ba7df0be0a34dd50752f8a8ce8f6efaf888c48798f024f9f1a0141811eb55da7ac6ba5b2957e02539830f45740ea02693961e897c04a92403b4e18fad314fa0b72b20963452f52ae356152477ce2768df2768d445826722e00813874ea03b12ab13474fb64bb40b2a942efedc2a4160bdd2868a12cb3aab2a98b2ac5cd061365b7f83b4abfebc2a7f51bc05fe8f8f38bf805ef35612cd792815a13c6884f2491645345f798c6d4477d7b3155a65c13a6e44acd3e3155727513aea902a74a4c6aa54c1bc4a54a318543b7af4932ae255e202613e595d1425966b6aaac4dd98bb2a7f03555acf419ce176edd17933deb9ebfeb9c112863529870420a0f60d2e3e4be481fd797525aeb0f6e4c0a134e488149cf8cf4b9eea5c0c5380213884d32450c12295dfc0850808509fc681e93c28413500ca084871999d991f471bdf5e0440a4225594e242e89939492e5b479c042844eff5960094599d2f7cbe61cc5045746665d9cb397a962f6d5541133428585232d2baab15345541c1d94523a448af561f3da41bb195bc7791fc9f10de25f17f24473ce39e79c7386287d111d4d12ad3f527e4a580cd62383c560241dabc5738a2554b1844e31add5e28b359df54643f7e636e71d0df5bcaea3f16868481412896ca52253c71aad9c50a6504733ea58b47f9bc44b1d8bee692a994cb652d3e9afc6985a4facc0bdd7ee5073f7de6eb1b51dcd4acf1d0bcd6d39e7cce57c73ce396f3a6b0c8a1608b51d65689475aeeacfc73645fafccba322d39ff3f73604fc0217ab2e9d8bd9194f0ac806bfe8955e6947386c43f04afb32729565af6f82551319e57d4e4eaaa927144d4c4653ca2a837faacc955265ac86f3e542ae7258e6952059e9fd7852bc29563655ccbed2356d2a9398489b3ba48b9306bd8f7bdc95822e9d1bed8c47ce97fa5a7ffa458fd4b4173db205b57aa4a81c911ed9eb9b6e4c6395b12372448ec81139224764af3f1ac146b12aa3591b131963dfb71ba03f69acf5ac66e72c46adad345e101dbf40ae1afbdec9e22952d817eaa5a8a05eedd767d9fae1d7f98586436d5f42bd7eadb86389543468aa1a9f783ba957d50c5fa9d7af5577618acab7d1ba4dcd79f65527dcb7cfbab5b6e8b6da2a7b83f0abdb8e46eb3cddaa5ef8f6d35dabb576df7beffe8d390731c6186f4dd3344ddbaa181ff7791b679ab463995da58cba44ada58ab175dd6fee0455172ea0d4b7d6dfa2a3c07f7b167ff509b3a9f877afdabe45efc20bf090c6850bc58dc625876228da3a35a79b10950d950d950d950d950d950d950d950d950d950d950d950dfa3434da933d5188a51795a934c43a2b4f718c29f322a6d1e963574829a5e206649dfb977da9645d550cc45cd58de2d79c2b4dc3a205f0ab6b1e7cf7ef0a680237e598ac32ff18881873b5aaaf4229c67ca90f9ac17cd11e843b1629d4bc30fd8b95df98567b27f64ca8a59f2ba0cafee95bac30812a1baa1c60fa955fc1f42b6f7f25ec800f1baeb0f2a7b003a85ff90e9c4a2790762c54303eccd24a610bb44a2fd94c1576445f14023501cdecccca57fe73ca9c98abfadfcf2903f27e25ebb5db10db58122896707afd11289a0885622986eda05004cda0d7f740110445c4a0b2e4123720eb16c02ffcb2aef992718c7922639d9e7f0b45ddf3af80789c2f3967158ecfbe3d61f64b7b50d243e945aa1b08d1886dba5eb9b9aa4fbfd70fbd9f8f694c3cce19fe13c79807a01fba80814fc433fa7cd06867bc10cf46175aa055acfdbd2fcfc1b9d1ce4cebdafbe78a4d3fe7fd86d6868f389d1640b4316674fa9886d08c526a77e51974e9784e6ebcf7af4782c6904f9da93db9d24a2badb02e72d008ba01e15499fbf5735865a271abfac8abab7ed8c809735fa5e413391c3c8a9b179b2a665e1017a7df8f4d98fba9275f15399c0b72d960e3986a29ed4eb8ab957d61ddaff5decaea50e0b5db36dba9b082ed58f4d6f957357e226f7151a8f76b7827cc84992c52bf8fef65897a7eed5f6cdf0a5ba835ee58a1908674c2ccef58f759b3df3085de6e77029735900add0a394c11a6b02f5b77e77dda3160f6bba9905f83400ea77dd162fa7c3a69f5ee37e05e80b57bfdeb8158f4ef6f5bc7e2fd3ece1aa727e7d9afd267c7001cd6dbe2d6ed62cfabbff9acd967c89282187d634f6cd15bc8fd3e0a2dd02a2015e68a9a17ddf37fb19f7bdaadd0d1e7e1fc15f6d8e7b3b47e9fa5f59fb54a0d5952f051d64acf5f4f3d5b8bbaf7a67a7e8c31566150d3344dcb1d8ba5e79c73ceb9a567150c9f785fb35959aa60d8ba05947afecd6927e8f0b5b06b14b610b742dcba008d4bcf4fe3923590dac7345aadd26f886d26cc7c6ca3f50fdc16ba6e19e016905ee7055d7a0a7606b7e64b7dadbfefe4c357012bda17b10d35ddb8166e71ad1a969e6cb5fab90350d0854cf1ce1ffd6dfbad850fb4baf61ce8825659c1fdd6342d7bd9becc02d89aad3a61766bf3aba0c3957db12ff35b74dab12a0af7ebb3eed7bd7b7d6b43157eed039cc8a894ce76f62464330300001d3317400020140c0a44922cc9812c4d73ed14000c5e74426454381aca84a15896e3300a83308818620c410020428c31c81461011a0dbae69e79f17659752ec7cbbef4c72eca575b70c437faa021233300faaa6b7f52e26b4ebca99421522625938182ba4aa00cd2bf81d20fb0102d98bc9a735b57c1db113660cab6072ed6ed89ed836dd7f22e8a511a0065d91527dacad612db98b3522fd06a21e35df8b550f09f2fb2e2ea4d7dd04941b3e8b49b50d72a2f2e0a3bc4305cb85e26b18f84128bfc0606052389661e32304e7cf44acf930e56665cea1833fe98e6335934b241b9ded17e4fa7b021f43b6fc7c1ca4a452f562d930dd9d6bb657def1e6c304c8ad0ddbb65c7700989ac4ea28ee2ba56d345f9b7c76e50d77479c227277cd48e1b3b47a8b26145d5851b6aa5f92971caf2228265595ba9ff512a476ef35125a31844815cd2cffb48fe38fb951f9eaae5c48518a8452d149b8a68709149eba610db86c7c096060c0bd4d8c2988a77c4eee4ec8a6f9f67547d4976c67975eeef214dff2bed7a47218eb150e5f1abd474641613eb7216006f63f15d0090420c61b7228f59b4ef09ef3fafda0470cd9ab3e490e3d4eec2ad2795cf1eb2c8185aac1755992af582469cd22d959efccdeb83e61eb819b182ea6b0d9f8d392d019e451c37ae1aa32588644bae0ee0329425e2113292c82c680356600a9c5cc5b3aec12eaa2c9abc80b380a862fece822af268cdc27bdfdb7554c3a01d19705eadc875b3a9830c49a7f02ba701a2bb6562116a00c6c05905520c7ee02002a820f21d1424b525bc9714198590b9538df67143c99c0398e208828616dd0757568755e0d6a10144956b6710edd23bc27df7e4360bd04fc892f44f146a67b6f10a85d99f7eb45c0753b78c7a70369a382385b1c358c3a8b27a20fcd404513f4b1fa50855a0bcd26ffa5960ba4b2952ce9971eb318a3a4dae57b91ac2b32e5f926fb7ce2a48c01b1025ae0f0f32922a8f553a3f8a57071e900803f8a78ee0d5f383544d4cad282d8e2fcaaea7f108dd238d74e2bf5621707c8b991331b3963d6328786861835f3f3a4ca491494e12528f115ec10534e690a41b5d9f1e93daecd423896ebd46f9c44d360b6df87f9a375e19d8995e72f87cdc2fd1da178399599d01f41b79aa831039337c2b4c18947f3d089e4e35120f2a10afb10e3bd8021166e11b00efaa86317b2232529c518376b54bd423b3d0e29e6b498ef217c10824080a9dfbaf361f59dc2a64c4f191a2e1b9039a48f105b95a9fd735becda31175abe956f87bf06f4f8e60c70ae6c2900d275b2f4ffd52cd802991bfa75fd8125c876950e4c4016bfb4fbc52b97ac98e06d417457612fab0bbc9e57b2cae25dd16a3ee3a2e8c1ba1d701db8960ac537e84132a03d613427ffcac5f76ab55c71c748145ec1851856557b9232fec0edb5c4d53e1e9152022b4830b3101d1b262612f2f33ae9ed11610ed447666530a79f9cc8ddca2e10e6e242fa80c17bbf53d807a59a36c1389e031d0d7de34af9d582280d30a14827e5b440c79cbf2a83f9a25031bf8c0a220c4c73233eec5a043c6931d9aa45a2536ca36021192dc042257845e3f112b84fd2d194616be80890a1854d4197771d0ab1c008fc0bf7b4e262f870dc3de3ce8bfb652277a77c2981d8cd7041abac6fee1d5e3b631b151fbcab687ad60a25dca8d7b0e32a5fb6e1e1245f58463198723a3c0bdc271e80933e3b353fdce93aecd20e9b873b5d4ec444c4b0f41465b05a53f2418f38a9fdceec2f6630230ae743b6ae486c40147907a275c4f7367db99010d9971943c282e17754e0174926cbb6bc2add4401136485df48ace8f3e0db15cdab00216192d122e70e9f6e1606a721d73d3c4a537417f81fdc54c4cd911976e2815cca2f9894b5b464206824b990a329640a964413d72a9b22fc1d4cc379ab8944c69bc3ae15215e474604a4a14d37a73cd819921580d5a23b810ea3f444152557d328f8b47f7bc90df67cfeed284fce02c3c14ab3adfc9ea7f41346ebbaf4a3db14580823ac15ded7e2d270f6c908a9e47835179bc8aef33190ba27a7100e43d808402e97c49c0703430ab60dec1aaaf56e8ee0772898ee82f2a2c0e16115a43140742da93838b1aad3f046f179aea094b2b356b1f420dd46f5558ec0678a67c37729c4e4425331bf19d51c4986b9e7643be7cd3cab1a33980d09a5c566f9b54d10cbcce3def8aec993a00c1740401833fb5424cdc45e4916c5ddbb1ef1147b48c0f7b94d68218a0c4e8c2ee51a0bf573f7d28dc8fbfb952448232999c3f0cc8adccd6da1283f920436e8ea28ee9b80d35c86e05eb7e592cbafda465dd242e7528295946d559e78cec0f10d39065360bf1098ccd543c779e9346ce7633ba5232e9cb3fefefe7d94b9c6e33e7dd6c7bffd2d7ee7edc3c1965463b84fdf2b673eeb8b4d434470e966cbcfe9edc0c38d64f38ce44c7529a5700f812d7aec9e667aa00416638885809beee962d408b8f6dbb7bd8b7e8668b425deb60c1994ca6e54d0ddecab02138395f5b66dba3345722342a3f8739fc01d781ae1ce2ad38cd7e3c9d8c3848f11c5607af42e4a8868d43f130343a3a8eeb9dd2934482082965960f65b3d4cbeb8467f4a3eb2557c7173d1c430ec83d9f7ffeaf223c059d15cdcb2c487a634fca4e8e1f706b6a7371883d51f731d3c58b2db7c5e00a46bf980d3f324862e8b2c505108ecf90c02a5ab994d03c6e67ab0fab40c6ee682511c81311540874387764700b5329ce713460d7addf12bf2a2f93ec73b4d5a19ec17465774117f4aa163d1fe27767b7a4b7c0c63584e7278d8a9fe6c32948588d2f35aec7d4385f5981befcbaaaf74022d211b60f5f933d52d45b9ede86cf881b4bc4501f5dd73f48595694421f22d564cf5a61dba0fedeca329550dc2e40ceebebb5cecc575a6bcb364e765c9fe5719b5b846e8212e1a8f0c9064442888fcaf596062503d714784cbcf225efedac0f5a2b2292658f159e5e3fc76aa6947b74a36283398505eaca6a63bfcf201c007449fd6480a4149205728cc29a7ced86aef03bac486be23ceb425122b9ff138e2a376e71b8fab1a2adcab8035c35c9fe836dc9c5d85849f2139278875a8b71a10ff8324bccfac05c509b1a303d5337fccc8d93f307fc16fa4c2c05fa6f5c91157d251f4d4a190f4d701a76e518d010497cef93fc287ed88c7a452b9ec4706a11b7417ce83a2251713f07d71800af7e8b16400ea1bc39d974a80e65415900d7a07a2fbfb43c5dff49a971e37e10174c3736aa5675bf639682c6645c9a6d8c10b5150e8d2cd0657e492e148d71ee4914fb444be878f9a6da89963002ab1768a281d4c9203462a2bda41d8b74ce40b544937c1836a6058c04abd5ed74200050bb337877b8ab65af950460312818401f57cc23fe13ee1e5ab920e5a87e6c4ed3db5248b4cb490a85a2870fdb7360200e1dd38df6225b0a8cadaf82251b4fd1f40441406e298877edab0e40f51fc2d3efd25f75fc957e968eb954b33769c304d695c8629ccee482df5964215d27a4f8c2c122015eab7f9778e222a7363b08cd13d8abf61c81308ff3d9e18afa9f3c3e08f7219998c2488cf85ec0e2446a7ecbd13b2d9d292619a1d0dc1bc7c91288dfd5788be62b75fc62ad1a616668af3a1099c87b6fde597a4f2605fd24af64f8ab190e13d918e49f819964ec6a792d32c460c55a6aa0886c3fb04d5b20cd122b50f100e4aba3d19ee8f1128c52cdbdd51326f2be693e81b05f870724f3db9258a5fb478a394cd2232620c05ea204f7d7811e69ece95410d637bb72732a7fa1f8850c0f110a51206f07921c8a4d9575e6f280c37d001b67e8e48160aca07b1267e1a88058dccbb58cebae1e5e03ee31d2df3f012a777127d64eb73d7c14c983ac287819d84f493cbf9a459cfc7bc21c18613bde8bc1b64584225d4c7711c5d85d89e9a4b9192c5c27af24f01f32f5beac27afe0b8fa178439aa84092cd4dac53aacf795a6a45ca8a42e6155354b697acff3f6e3bbab524107c5b6c3eccd66024a3f0556b59c19f372d8a9a8737a99e63a4948c60189f9c1cc31850147f088fd1c789d512ecf0dac36ceeb5c9606f76904d042feba387cf65011e00044381e60281a27e6c433914c61149f6ff0a71ad5675ccb7ffa7f2a56ce67d0eb057217f95fab2fb1868335e55542a4f0a11792de9fb3eeeb990a42b74488b0f7048185c3d5bc2625c7acf2ee61807a5f22b4c95ea70577d5895036b68b46a3c97168015cc38a994cd9f1f322168dbde31f2673e39e30f1a648edefd8198f0a242085e91a0e907c9977d5ffc1604c02e997dfa47c8ee45f12788739c45d9323e1e10c5aa99e483a9f256b45aa0b32cdf4b0bea58cdc64e6926371c9861ba8f5e03f8c42b2b6f121bac12b76332e6034ccb3034ad119180c7e87233a04acac4c316f363eab40e2fda9a04b12e98992cd5dd101efef6c6c29064c242e8e2dc61b18cb62cd100ca98bf418e47da956856da8f09cb55269c508776f2c2c7d7d62b794ab7c2399e279033129b9040e4dc437d27352871e645b0753fa8961f9322a6be3c2c5c6687e4588e4eb803046f28114b683fd241ea0ae15e7e5c2c5fe17feb36f8cd27bf1a2d8facfb559bc3158737806698970c8e9ca794ead76593db244571d3a01624737af9811c5de9bc9c07229ed08fa6053d0e8bf2ceed265eaf4d0067ae37202b861bc0214da145737b234e18c5e8dd04f26144a30549f7117d0346fb39dcc7611d0ae9c70fa54bbb81b8483cac0fdc8f412d6627c4c5073c68e03edaf66dbe7c63595480b3ff9cbdf9aa2f53aae0c31dc6a15e3bca8105d7e8738a74610fb2c91974c3234967dde2cfbf7ef93042d6452e701f4280ee5d8412fd7ef1c3b59211f0ba0ccff95e0d9032e07cacad072f13c357b08f371c674d8224ae59fb20930169724c00b6887ec52a86c2a08ceb9a9d7e0aade88382adb61227dd5d64658060bb08c50cf97256263d979b1f23531fc235460c40bf2a2de78324cfdaea05d35d926233d5f39e19de063221af990b62e4a648a4c5769810fb12493e61b38097d1895b52f1b16809dfd3be67c18bbc61bc8fcb6d1408e8406f2d088859bd9e184f735e3869cb1840091233aa7a4d398fd944a5d1208a17f96033a7274c1a137eaa865666739a47b63a78c39581654829d7068ac241e1e1d3c534a8565c709dab1de78822d46c52cd0b63a030f710be8488280350db081cd44f1bc5cf7191a04a0f7e87402d351cfa14f95195ca19bf870be6f8d0cb0f102f67ed07fd4d4d617eae65bd1274b93ac40c2e61d0ec4a3b6483c3cabfc49dc314f46030a59362cec515243c903aaa0db89d34ce12b6284afc923be15cbd50b2018ce57c02a840953cff4f0025f5611e9337c6d19364569b1f0e0f5d332048a807a34ca37f921afd7031902335c670b0449c2739e511d5da37ba3dad2d00739eadd90454d02f1c73eedbbd7e537d7fcde8db1b693a6d0a9c95858eafef0770a972040233e65431b6440a40dc45a87c5315cefe3da1df4914ba20af44c6009b09b4e5807e22af4a203d1de716d252ae34688ddbc4658298c1e0bada913f060037415d87777439b9c8f0ed097ab1c30ffbc8045c0de9b27f11d76c510d4e848f4c33a29c3ad51859d4846abee7eb134931efd02c5381edc815a35aec21bf60446b90ff890c182262767edc32722ec3704a936ebd173f063a0111f8258c5dda7d4869a3966e59ab90e3f3c8d89921c7bde724219cea2844bbc6e84c3c940b000dfb5799a92c42387acddd361ee0e4f62fd2eed26e1832548a9f94bf729c7610581b63e89e7a3907f30cc66698876dd3321e5c0abfe363db16dbc20663e2fe186e1206fd5d32141fc9f2856b34d61982bff58febcb14357dc0420e218a0072788cbe7ee3598ba18d3307ef42fbec79cd16ee838988ccc9f636032497a7e6bc1353a6f158ac285142cc3b49d9bb224580c28f83f81e0ff0a8e91c23e65dc17d128f7b951de106d1cf391f18a88c33dfca9a4b752016f5632aa1f826b0c210ade6fc654fb74ee2f33a73ec7810e81cf5b98b41fd099fb419740145b1ec187b00f45d1f012d0775ec76a01e353ecc20fc381deb5ab1fe4770dcf448e809b99f9c73402b884931e63bbc86bf447371eaaac3f5ae3e512dbc01b227e9e796702146b4b3f433be046fbc31cdc31b368b3ac7d7e7eee527ca7fbbfb2075cc5e143f83e16e659c2328470717bae1c2f4e7bfade4b4e429ecd8438e21f04d32c85d2dffccca2bcf1742ca29eec2961182e9d1b0e3a2906cbb8174afc9eca1f737bc600abbc221ea425c10651478628ec5bc3210cd2a44242dbb43abb746a2bc4376bca964e0530b9063bd0e0293591b764181e5a5fce998cbb07a06403ea070ee32405d16f729ef3b2e0c34fccda1323f50fe2fe2d9bc19186d51023da40eb72b054bbc59efada3ea724682e10aee5e6d23c0fdfb4f77cdc0274c6d59935951b6cdbc68d0e3cd8d75e35611ea02adebe5c9c71688726b7eda7cc3b8e6ae83604035d8e6cf2b55dd9c449c4be5254df3f81d12832466ddf9e1e338be7002aa6bfb67d26b020bd9929c3227344ceec1b44f099a4f75a6fb79b852c28c34cb9cd671276a03beb6023bab825139859af07e1bf11f90724e4ef610939e9331fdbd161273e4a0a14915c05d4e143a1b89c163e17352ff1fb04572bad38b0556078adfbfaf54a7675b7e42783afcae549c8b588181462a078270cd7119a60aa828884133ce832070d281fe09cfb4056c4fd83ed9621b758d4c32e1a50f22d768239f9403badcd6a225b71c1840bdf002000c0778b0e4d35c28fca82f090eb82b8417aa67bd099dda7ba07e08a92640492e79d599c27de90a76f1cef47b2895beeb9a1b247615415b9783e3971c9bf17b7ea676975f90dd0ce24f6ff0e79ba8161d2eae9cbb03736c1b395be5d1d013f2e54b9d69ab1fc2865f27d30e72202131ff84025a1212c815e8f9411dbaf0eddaf50424ead474e39ed7f4c8c55a862f48f173b3c68e8ab9903ee59c49f3e78cc7a828c16a3ec83887209a31f06865282351a12617362d3de7cc3278a2a0442013a68fc01ee18dbf8c385a2118bccc03cb08d924f075c8c75ed1e353733ef149fce4e0d258f717d195410746931311afd48a6c30819f79d933ff176ea646356a59b17de0f6212706f33ca73c27e34328bd9b667e8b96de9a745753270bf459755a7f8ce814c728d055b827d0de80578a0278bbe6db8a46472239b791cc930ad3d341985092bfffdd5d1bcb497525335daf81a0af42605c4db6da0e7c58a6507b355fd74c54e1bd1152fce2368518e0023761e5c5f16b3f8c5c8c3d0edbdd1f6977618d2c529f6f400bac0d8ff46ac7ad380a271a24dcbb3655ef640e3d65581b942d85b39d822479a0dbe3072a2a61369e4facb2e382a3fba49370ce991c39405a15fb9f620a355580aafb95ce91584b1ec2da49eeb68e6ad7d451d97069f0a67eff0791fada3d1ca0273ad47214f9e900aebce0512fa52e6ab516db086b2fcf5fc54f963eb89b46c3c11623caef5841a7a366c4848cd3e854bd977716a067c71ba68a8d2c625dc7b35a7b167599ca9b7e89d900c06371b4580b4fe5fd2e235935754ff5ee2401576629b0686ba032e8ff22ad88425d65b9ce9cac2295cd1473dd63b4e1c6cc4fe659e0f9c12654b1b33e66532b66e572aaac9059c39e958f623fb942f2576cef77c62290d23aaee67cde8ca7cca5034f9a9e3a24647623862abe4c8743969fdf7280cb54968166f57e310b49c917bf7795a88250d84c6b605dc730e4c60411843596198e2182a8c7fa86446c70bd9baf37d2aa7502fb8c3e656a0e88e148cfcba88a047f0d985d6c3349dbfe54b27814ba1979f615760e9e50fc174a3f41a0df5b318f27119d7910e0c71b7912731b311c2b56b390c9310dc9583eb9985fef88ecde85c812f5fbb29164373ef9c9b4731b6bfd2012cff7d987c63cc2489c4c643404b37962df6e67eeca5dbfbbf2202a6c95e2482228917fa7526b8f6a64f238c9a30c0ee9f74cad9f5108bff3887f5e36907d503112942fc25341e9cb9531c8461b2df82308e8372cad97d86642580efc95f793a9b155f720123f801f90d30665196a46dce95212d999fccc82c6791743786afe19af472988a21b419a891f4a2e71e9e7de86136d091eab27743b2067f2d3df9bc1cb25a21f3569b7dd90c9fa1a4f1902e396b7230bf37d2358526c93d0313be479e52dd054e6afa7272c8034c6d9516bdb29fed6b94e2db9e0a6756dd6346f2c7401adac4bcaefe11af9c3383614e9065e5534b8f90a5ecad1381fa212d8101ec12dee331e97536cc6526bc004a091cb49230f31f09184c3802d05ef82b816fbe7e53e03c13646eb329204eba985052b7d10bf4e676b29a5017fa6327cc3313b2b44905f484c302b6d607df016e58d0c5f8f766d4a2e7a028d050b50d6dcc7912a274a4cb54325dd1229eeb41956542e97fd15d6914634ae3b29e04468e7ea2302a28419eef458e5125c2266319e0ceccc4ec34719f7b861a845c205fc668eee06d84029c67d19b2e9a1d8c362d2e09cef7993b67d8530eb2c21d77916b7f528caf91f0035ea340191bbf3f178a46319a11d65265b61e9d5fa7657a55dcd29c2291920dfff1ab1a8509590da597ab61e5a180bb329336182ae74935dc198e4abe20519f7f3855dd6c6a63c5e362d97d724e1df406e92a26ef82a5ceb688e3462342d4b09b2198b67fd0e0ba0e46599c37224dde9fa2a0ec2f36d68525f5d3d8471183a55e013dd85cb44f9e5595223d851d682e9a8e16387a52a8930351a34208280ae78ce290f4d441e071467326265a6275fd89ca6ab7b9a1e596f5695774138391c550e3bbd54d8d43a6bf381189cdb6cd051f83a41127c295553f6ffb972a9bbefec3b502b3d905ee14552a8ef4d383d4f26ce50067bb6b49dfa07077826e4a3335908d961b8837026aab2c0643dc607f26c6797d33d8a0dc8d6b67105ce2250f62cf33ed30a6f6b23df08132d1d0c42f5d5c3958b103a6ded41e4e58421ff98aef1f93e4ceb546753e0113582f1abaa84b577b305074b849c2a8d4105e0834653514e7e868eaa8d3bbb270bf0a079674a2e50f2c3fc2cdc8bf9e96159794aee662ab8d5e894a39ed6d15a86d9e1710477e7e11c894773946beb4aefc9c08f9c75d9c9d08c62d6a1982577420a24d75f6255d08c5ce56d0f9d6d8d99c7fc07428c925d009f5b14ffce237355e1cf35ad726385ff71980582196fcef11c1056f2928d0b9381eaf1b9647456246f804e7fc95c91670a4f7477cb08e1fb74b7658ef1bccaec04b4e1cac3ce1c238b4cb2416143afa50e87a8942f77f143affa4d0558a355690266a58c1abde240ce418ec54f455b60d2b79da417da1dc87f7e3394ab82c5041d7f3c951f48ba0d34777bec011f45c28e517d9e165cc2c0b9c8dfdaf96eff8171ef67ea9065832dd3caa691333461f7fdf71a87874c61bd3b7f03b5222d55b650fd6e76f3ac597b3a108c702f427081ef7f3b70c3dc5929eac1c97f0a74b4eae80bf95ca9748e690f80b14f5b362a90352ec4e0f782a49d2968779657cf228d5e13af10f79a142b1e7118be0667067c35a801c50602230e1a2af4c2f631fa98acee89933747321c5e18978a450eb3b396e6e94c4172816f5658a5d5affc62612b7d9a211a199a0a67134dc7789e74d7dbba781781152d5e04a22dca1873d83e9ba14ff3f6059ff3a77e0cc760b1bfdfe0f4c3fbd1f6410eb071460cd29601f063164abb2808ea13aa7ac81cc13da58769093d9f21777b432b7d9452b79b03e3adaec28ca5753a558dd98c320097e156abf19b5c1ea9e8d0dfae2c48a771fc925045ee101ea203b23fdf86c5f3c3606b543cc6ac35a4d6884fce3cd5aed6610382365a3136e10aac12cd9209cc6f9183b132b964123595d32bdca46ec27f8bc253b307b988898be5c09dc465f82bffa3d9f67cfeb63544db0ead7d8b3d333aa9f99f47ffef356188ebf9fcaf78279d9f3f01f19175698f49f4ed1fde02b4d02725ebe7419df37a37c008b235985c10c9b20dec48bfe45e9daf888a3e2bb11f92de6a1e4984d6d46be78dfc5a17f9bc9dc4291ed1477ae0ad7842f0c6ff65bb81e39002afa6d7d92b01e2eaad713d38e9001eff708505901c398803e9394f127b4a3741917e2e0aa3d89620ae53b2f4790138f1edba7ddc725cfd36f20128866dc0cc4bf516854f3b25eeb72db808e321353da753b772bfb1b6d28361c2100d14e2019a442faef1240c5dbeeb5cab7f2b9e9d859413355ee36f819814737d2c62b85e6e6011ef667a144c93c581c79694417b32192a00061b3c782aeda620b32f6ab9f7dc472886d5fc3f9eb934d82b9d65bb7364f166fedbc785c87dbd8976b6a44aca66623e7573cb44c22e1d7a5f24b259b576aaaa6dafcaaea1348b43e7e431f34259f3a2d026c8e0d15313eee9bd5f55caef64196d4b3584837a574c6cb45aa07776b0ed1875468c9ac72cbf2116c237ec02515216f734a0bda1b8a56c70e939b33c9949e07ae1dcdd6cb011e09cb6d61b16da1470013fb4564a894b0c9913d493dd84acf0d0f19e04f9a202c8abd2f4f4e267b4cdedd8588dc646bbf89052353577a01a98f4e085abc18d2e15fd5f236758595416fa558d93c44f794df224b8b1c29e2b006ba1bcfa4b1c884586ff01fd9a554baab2122afc1ff0231ccb71b9e6e34b2d5017636b47eefe7cd6ef3fc429bf16c31d3679de4fc2c7806ab08c50bfe10a439bd591822fdd6a12614fa6f076f249f36b242b73074da4f17db0b7b9b28657ac028551fd57183f6a93e3b0cd16217811bd7f936b2c79dad926e8155295786061781235d8c267ca7c660e9eb8f7478d2f2078050cb99fed4f0f85655103ec48fbc2ecf379325e28264fca779b2ed834e84887e203580e92b5ac973330926ae27da7718bfeb0d83022e1ffca5aca645218b5610c17be9e4c9092299eb42aa37130fe0b635b825d330cfeaaeb19408d4161bd75fe10c0ed9e9a545c05615f460aa017347c84b2f7c3eca142846f2aabfb2302dccbfdf08a11e78d98d09fb6242176f017a8ca3080b610529978ae906e8d52d2c015fb09877dad2288798074ab2ed2d7d024cc0f223a2198d2fcd25d8bd3d3b1b52f853de801cc0740273b95e11ed0d3d2e627a75f40d4b3082dab9d6b0cc5e50f6fdc004baa3f5474dc02db8be5a343d149bb0f83c826317fab687fee6f09cd45d42596bba713d8918cd012ed633ce508e3188d8c6dc31fde0dedfa1170b08162db6df9c6bab88d6f82cf26e5cae9f3071e3e2fd881afda67048f5982e0fe78f9f7643e778c4b68fdb2113242fcffe951bc73261cc603fcf68338e16b9b728591fa2117b658291371a718650c331a127a1ac7cfb5da8ca7d6baad8fddf2a17bfd2ea755f0cd5579fcbbac46f652deac154a89aa0fb7faadc18e8564bf8da256ff1b629db58e3ef1d25067397c85e034618b4bdc5a92f421d64796ee01300d019f401fbdd8b927f39a936b77af225f3ff194fc84e3ffc1c9394d3849080319073b6fda40da2bc1392c1c5bb5e69678bcd5007322b16f4a8cd9bf1224412b3e45cb5fa6991c3239a999d68987c6245baeccab33129b63072fd630f243a6752dd2a1eff245bee4f26c6ffbd52bd8ee2d815e4a65aafb7e3731523c6a97ecb437d8af15e24533e34c2d4ea43055df3fc00a6a4beb46829c1ca4c784fa9427a8d592ff8006cb19ef95ad94167ddb0257739346285de4929ce21d7e66a17be567fbfd346b15d4fce5f0338adb5fe52c6a97df852e84371cb19c505949cc9a6e137771cd4b5d086eefc114ffafa31162abc4178fe6332ea39b1bab2602736bc68fc813305926c806c20d8d5779a42b17bfa9d0a10860df9f2c541d0711957b462f67e2fc34646343a103c548290b9b9ba145f0ac3bfb2f3ac2535682b2e836e720aa2ff62e3fc9194cec2127f92e1c894e3e037d9ff4c2e25549d81045316dc6ef8db6cfff6829aaf3be6b9b653b18c64fbaac676a6211c85483a4f347e856c39fbbfefdca1dd652c3468ab653f93c58f29fc1ac4ca6c8ca4ca0f20ebe56739542ca47910ef27e642d3b70b8a63cf83e44bd89a89632f4b46c60b127b90027d2242ff314574a1a6d21c778ee72846e09ae369df6b2535ad741fdd0bfd85a631b1957e44ef0f20749e0315a1a35f90ed3374327a319763e8b0ee60cede64cac13196e84af4b972fde5b143b662ded540b70ebd2fcd5f4075df76f66a22f61272ec8f5764374809bdc7ac4781d2243e37cd536097728a7d20e6b91243e0de242b43e14770dc9ca6b82279da21b24cc135c551a0983f39fdbb079b275f22bf242c09ef1f52cfa7e06cc305dad8968a8a0943241fc4b97eb6209048e9cd866ccf8d8a7f148a130956e96fb8279e1ead2cd5754d57e90068366cd5156af6ce2077b41b9d50de9c627cbaf59d50214975a279d6d2d50c659f2444ad1dfafe8b85f22e2326f1f936795d94317433a67da5de7b19e41e37672a6e29bbf65d6e4e2995a71889cc2654e602a02d4019d08d3c9acaddc33da6714162648a0af68f67342e267c89ea37e41355194907073e6f5b674f11e292886a8fae67f14a9187a290b1b72569f8923129f8c3d288dba2a91785e14c5d15784d5e4ae14b2f8fe8dc1c36615c68a951d9c2e4b94ec182fc4a5efed5eccbd1747a7408272b77fcc172387d26cac98b00cdfbb08e2acf80cc91153d3f72f308174009af66ff676d6ecb6761e6fd9984915228ef455fe01340627a15417a6ae704600222db61d1fc0790408c964c43674794b66ecdaecdc257c818ad6c25267fd9052b64a21f37c135c111f4adfa2fa766c5a3878217422bf3ea6e9e15f3671f891a1f08ef6cd04400e1a4090c1ce1bda68c0ba71d9cb358f38939996d226810245239650523807c19c40d804e539f8f1f2e20d682285d8a052b77ec788d9026ce3b995b931fff8f58866d8dce7ec432b35ede1ad1ce254e68b2c85e0ab7d5c0a0f379821dd878b7b09e826ccc02b2c2100aaed94374667ce6f7588be9b2937b91cbdb2cc9f636e3327d36c959926dde4c1d82648498104fa228de436463a47e9ea415a53bdbe7c885a654456611440362df69dcd32bb465111c449f6fd482d680f8706b3a112078a6b2d16f0bf9a15c6b241d47ca24061921d44df47cf8f96278fcb9378d73740052322863f6c2d41ccdfc8d6e50fba6244a9fd011d2d2f25ad44c982d5deafd312958c94ce4a98f6a2c0385aa0fbc249ca014655b67d761c6d7ac3961e17bfce9065ea5b3a16c60cb8eea681fc9da89b9c6d1d4307b40c6c4194ff4cd21b9feee56b823b9268b17d316e3a3c66955eff83b1c545d26a0d4efd6599d41711f7ca9a95865630544e63033338f0ecb83a7259e54e2d30c5184e2669cbbf466b16e1d37dfcefd1962d6fd59151761c1bfd71f79fe0ba9f0df7289d6cbfb3e7c13ab184b0adc018e426f674ca454173d2bbe2e70262a7891204fa0f4b14f129d1e9319ec5eaed50dadaa4648fac596f12ba07903ac18bf11e3db8e720b20e4701c4c7bb17913eff9ee036d0dccddaa00d7e18e22c587b993e258050fd585a3708276bd07bde4a00ea3ecf1da1277ebe072807a84f42e0199ffc1e51e2b40ee866cb207f35420afa11b89fad9ef806212a86fa22c224606998716d3c8e442c25e05d67759630733196d83d665b9d3e1faa43a130399196260d047d36474861e8c04415536c34ecf1092ba02ab4f8d969b79727859c100d2e7bffca0d9648d290ee01ff2bf8a440fcd0a8cf80ce6977a1bab36bb5a982886a59d10ea8eb2dfe6e0b959cc83ef35b744a9ba1406f22d3fd96b2a38f3420d0b80c19a38da06fba5db64f4d61654b10f809b37ff90b7155a649d679c7e1764284a70b4a0d85ac13cd9527c0b01a8ff3eaf294828c4e70398f480ca6a6e3d07b57106a6dd2e5446f5b807acc4bd3ce6ce6e128efe90528ad1516ac457b69f2b2e0e52a10392048fc2fe02c79f2bba9bd16f2dbb5b33ae09efc273b541db74a70b34ca4db844a5e959a2258c7d52ff7ca807e3048a4a3cbe92a073d4ba55895d643a64265d4925fc9bf4d808ceaf51dd4b0725fe0b8ff1e6e1bec914ae14ecbb99ab538d775520772a4fab287716c6cde0edab76e32c46ae410aba69d1e4098bbb83883a60338209cc8cd61b0b293bc0eb5884facadfdf70c1432e41bc0d0507c26e090f1d36b7b4d5c19d7f1427102ebe89c612627ef0e965ce8f4e16609edbae4ec5d4fe2bebab944ee265b5201f346dc5f4bf3ec1e747365a0f6f15492a66cccfeb0acb3850453e01b10bc038206b31b313f529658aea1b77f3365a107ab0ab9f4de8b1f90ea203fa919c3137ac8b25fa2870468db1af80d946803fe17fdac6d490c366f83a952956f6d5b538ed3b364d02e5d81496d8811a69e6b2644ff9b73e8c74159ba581708405237fd3123c88d3bc84d85805e32470072dd5c0e5ef5cf603ae7bd3ca91b80467b5c8862f67eb351d1afbe3949a948fcf808ea53b1aba846bd422a40d4e0f28b2e93f387f723257f62cd0607da296b48f8d8e0281b37eb9dfeae466a08dde99f2c0e8660ca0642403fdc1161732ff2b7200dd8b39a763f8839c96212f119b9222632573325af1ef3250c590458416d79a3be361f1b8f60df0c6366f8fcd505f714d2cc69f6b8fbfecc3d4a6edc8c09407eaa9a9cb1d2dfc03d0090e18c49249393f75a7c3817e5ad52586ed79d7ee468655515bc964a6d27dd08e7ea379aa5bede7e372bba94fb7e9a5d4568dbb5d7e70e489e6722911b2ef4ff21afccf23514bc2e9eca970ab04c0ed8b33db1f42875d27c664df46dfcb0b077fd8dcbf890d48df1424e9dca6bce83426242656ff94ae11ee394b59df48ba747911cd3dbf88e5357c01536a00486491692b07482fd179da6933302c8950e61eb67e83286fc7f68a9acad7d222309d0111aaca31388c8d6487b05c00f05a1cedc7f8af399d65f3c99e1bb4d8fa8e9c710210a081c5d401a95377aa6130fe0d683509ee04245c38e38e0687e8577150060e40c44bcfe21fa0012d826cdef82ed8e6a5cd7497b0e4569a3b7f2b0716fe81be286777232634aa0c48a3b6895cff1ad089b8b8d00a8d69aa0811d7b2f73911752435762d86dd698671b2a000b87001b5c60fceab5123451c4085f0b69a6be16fc918808d40bc10ea5989f5940ac5d776b4bcbba9c46b31054958610924fcb0891a921fad900785edfb2fd67b2b87a3e15dc3615907628af5ee707ab130039511c8032eff1522d3c5a6e43046eb5ca16874068a55e25dc848232e670374e0e31ee83816e41238a6d920bb3737683b3a09b975259e00e0a4003ddd3608882f7c11258e285a717d00ea9f921907773727c259d80ea26161a615acf9f7b807aa3f416e19709488b67c34c97ea6c2da3f2897cc48e1d27105fe7768009c83f4ff31242b654ab4658491711548281993ae3b16d4b8c4d7ab3f9ae9472e106a25b59030d6d0f672cafdb90a0e25a062d937d531d31ad43307a04513b1ebcd41264c1704a173749a357379719fe414cf16c9a1e827d219113f5ba24f8521b9771aec2629ecbdc8b86da87656df56cc45b53a606a82dd89c654dc19b9620e8e6145712bb1fb7f7c5652b0161b3a2dda86ef34d8174839560ec030a93f0ba2e76dc9b9f779b2b570a69cd12b4889de27837d28b72d38aafeedc7cbddc5969016a7677cec7508651183a6d85d0db25bf088f282a07cf3cd932fea8ec8831e8fb98ab828e3f5f753ebcb072a87b7e8f9e24a7c88a10405b9c39cf676b6d46e171a407e182c7e55dc2d882247052f1f2adae0d409d44cb327403bcf425f265e34650129795a81fe4a007a7a638307673454dcf7c62ba65bdb050ae3cf909bcb07a5cbd5642a7cb3370b7d467ab38ed1e462c93748a2188c557b31f84a80f06995e90f1cf48ae7adc40704e975faafd89eb61467b940ea010110da6f00e7b5a64180a58bbea514ea924068a52191ac73dc8778e85ceeff5e158aaa3d41889eab6f9d73b123a89190b92b1d0f96b024e9021bd561a244e172d43a452dc26c59eee86eb7438409d3dfe2fa3e2e97b918dcf5ef2e089acea27b0dcfca9649a5c413572ce9871d1fe549d8185fef092336204dad872215ef0d81b676d3d0f2dca370a990bb055943fca14490289d221ebe400baf331be1aa061efa48d044278dc1aab38cb5f513028e2a853a33065f6546de0af9f44666caea35fbfe19608c30ca14fc0efd9eb1473c61481beb2f33b302fd49d423038c19ea85c4f933265e602f89f164ce5ca05e1ae3c99cf8a05e2ade97519ba1b949651d4064aa0cf54b6c9d339b832703515e999cca9d81aea1b2e5f4ab9f44cd8154552cab44875bedc1dd5b6f06cf5c644ddecd4d96bbcc1a6edbdb56ce2d83d2db76d3ceb9642bbcbb1b5cf6266bf8f63759a6aa55ff356e76ceea9df3ce8ce58ddc489f9d53ed3424ccb28cff35843a26e133eca4352bef2aabd2dbd89b00fe44b7745c4258edfd43124a597533543ef9741f5e10f64a365bfa245c4926c23bbd8d2d6dd22b7f5ad02cffd59535da1a4a3039c3f6e1ffa1eac0834e7ae1152c94aea0b798c15c165ce621e4f7f7db8cf7be5453fe5bba0af87aaa085aec7fb585bc446dee919d5275c76def0af691267ade97c527ed73d148c8fe3f3c57c063fe7fab173ae26edb069f7acc46fbaac53a84f34128319ca489b10b96e16c383d6b458197c20fde18d3fef87d7c79b29ceaf7f3905d09d0900b75c4fbdc6727860ab2acb21e6df4d5aa1fa148cb36393d546a276da1ac8590899280fc94a0b3f4b784ec0d6d57589faa2b86d4d43b88fc10e2c8fc20f334ffdb233d5056d742b9a20b1f51062ba3c9c3bd5bfe9280986fc005736efd9d799b35ddbb1b3c7729637a763758e62d866f336e0396ec8ca5b4655fbd0090693e3b1f8a0c84e430330a50c845c88659292afadcf156122b2d0e4b98a6e921bf50a5e24188fad95418ddfd76b0977f2617910e7a9486189094744234691356e7c4579cdf0006ead460350a4e83f403a60e467958d20500543c5478dd2e93aa9542bf4d0bf0c64ad61f588404fb1ef818edd9f5d020a5c4ac10830131894df952a339d236068e28dba4a2936fe418782065c8519f5ecfcfe9743f07d2101246266f8591a71217cbea7b0ba807220931a6496fc8d94f914297f15509cbbc79b45a423d7f120430d44cb0d43112a73909b53d1f993b16ee114e9a5dadd0b7cbd7d77a0621441e32c8a51cdd780c0492af02d36a392197b34da3f98c790000e83b4ce820dc07902df5fe401b47bda0a40a50dacd87d6d1379e273ceef5ea61c55e4ae7d50f9f9de8d647984d6a308b4a7f740dec529abba0a5bc94c687ff33ad6433da905588a3c48a8d0d04de3b9658dc42e6f0573d3b8f559582518389beac34b6df1c18164828d64f8db3e5025c01cbb411fc3e6a922717890220be8286974b36fc3d2e185be6d4e0c7ba28d18350ebaca62393587a155780abd6be87c32f6e1e17ba6b61641cb61e6760512865f1a2c32d5dd993c1415fa38839a6d7fc28edbc98b9522cfc528931a1f79df63625bad143dacc77b6d8043989c590ada2b14adc8aacd693a1d061deec02483c50a7249c50ab47005031a3d7077991c014ef20bd717cb94e67a3cb5c3f4629c446059523445661bd67b2406d06ba1a28c8d3994dd760d5cfb03c3fad6cae453ce4da1bc9f9895d06dd95039f6bbb4c7634fe8f9cdfe238071b892dac6b5c23381c8f20c59854d20536b2aaeefa249f564fabf9a602a9d41dc2fcef31ac5ab934cfd8c6ee674a1bf32c6e32130fd70ec54779799e8a16eb44ba2299f0710e5475682dc97001f0ea9984e5561902dff8a49bfc3a41b16a7b6d7f2c51b3f96d2c8b56ed5f219478982ab12824d4677ed5d807ebb140d22dfc7e0ba7ae1cdc17c6c7caf0a19fcd94cab439bfdd49791ae4c1a33c9c1c576cf43aa93f84ea1d0aae4a8a523c6128af737f076a8ae1b9767b5471310f7d457ef50b79c2533894dec063988af309cb96118cecfc333c0e51db0e46913b9859bbd0928fabdb5515ebea1935f6f68000d21d6620bf32f557d9038af9ec038bb6549fb1b3edb2f60642778d3d038add1f1babbffff69c1c5a69efcdf4690948765561c2d346c77111f0054266324422d1a5be518cbb59613230f09f4f75b79cfdf7cb727e443cc40d8903dc9153703cc3d5d58b169b40d1257d0cb915f908624761c539bac4ad944018665ca66e0e7f28093d0d540a0e31480531fd8c533349f621e6e1d410d1578a5a336a134d8d538bc866f2680c744581d7a05acedd93284a85ab85547ca1fa53fec0a4d85e94b93f455a37e83e2b18376549c9ee1a81ba169c98bd52ab9717c4e610d62f7ace0610d20450e57a42e1de7c770e6f54b6994cb8f8cd258de56cb3155cc2795e8818f162d2c56a12150c39fe0dd7dae44ac174ecace1f6146251b90bd861051d38dd2935704a8218688848b7835942950c39993dde21216ac6e25bf31bf2910bcc3e79aaee751ef4e7cfa3d46707b2f96814e84721dbb3849c735e11a9b1347a9b011a403fcabc67954603433ca96ab412dd63474d1f2ed97f6d576388c8e7129a8c1b40c1d92a031dee22c6cd5685a15d6efdffc31663ff11bd641a6ee499a5c609f15e0740c106c82b580b3adb616564de2f9642f39b7caa92da00dc183c63cc2be5ece095b26dc31454ca6016c26028641d3ddff373dc253c833285f49f089c780bb15e992c9079340757b5035a6ee3609a475dd0b02c1c1f9b61a09b2d2445799fa21b28436f5268c89ed4bc8096ed25cc1988af914758d7dd52e25fc63486d3e868706589418c4572f15e50dec6f6535e58bf79b049cee705beea23e5dbdd96322baf91262c595578bf0cc2b05b530731ecd721bb867327b112ef91f3c33785cec30ee8ef7b1eac3bfa811c7a73dfacc87b195a5d12af43a989dcab10ea917a179aba645e6d9ea464457e5e43d1157948bdc55055fd4dc98b00b136f97e6ee851398c7c5a0f4f58ca18e2d04a1d7cc7b2f4cd54895c1ca7be85dfde32a1aa4a1bdb7ad3b913b8987addf434466762433eb8c564040197f8199ddd70dc61f5dced430d827e73d2c3dfad6370baac4f28a48d4dde6bf882bcab069d40cca0d1546cab50670a76332743b09b26e26c2b96e0aebff22a5eb87fa368befd2af57185d9af12f4918da4b750d422ed2d60408cd6da374c47dc6ee615aeed45631cd9e81d9bcae435ecf2f18264e2f0f72063666a66ad9733916c4bb4b9ecb1af9ac83e668e52e38f1b544f165340545595b64a571002e82703431f7fdcf5e6ef366bc6b9070e4d5fc1173fb72cf457baec27f440f4f17cd9e473420504636f8367fa1c02ecf40ccc7f922f2272b4dd4a966ab7f4df1eed2c60025edc0c55fd293be1edb31065c80912ee955731d2709b481396904f2bc291fa7c5a69d799b200d45b2810ff0eca974fba153779cbce85b67885dd171472b65348095908707b2d730ec2ea390839df0705a4713a3112edc73fe105888326d56ab37123b119bda02350bdc7a4640953734ba79789d7ae0508ff17f51f9a95cd398fe909f4b8393eed1d25de995ebaf30607084ab1955ded2114d466c77c5005bde0bfb629c863f160c15ed7fd462efef558fee13ec6910f704dea32a666365bca16abde86d2f10885ad227bf6403a207e495c948d6b3ed9975e0a8b04dfa5f00247fceb91f2cd261190303bca2ada4de10804158d4f16fa1de261c85a8bce0e8c85c8d4ed6c5f848670d9d9c053000bd56efd10a9f719315b7d312caba6ab1aa4d2d18584399ff5cd110f38f3416e053bc9f652025626e40ced80bf4255637ec793260b5cd778025cda4cf00d29bf2cbb501fab5a3c1093008c46a73f5d03d9de46f3e011bf762061d5d2ad00354f018c852401ea69f6945f6f92045dd7baa1ddc31cf1437f113dab424cc6acbb4340792dd232cf674cc202820417117091415d61634e9e48b2d8c537b3b65204c85ba29bfb95d19933b4b285917899c1de5b6afbd032c8d1d73c8ef0e61e6e29be4efbc2048814eee165fb532c9696e65bcc6fc78c7ceb51a555cca41eea888727050b56612cf6e3bdf37f76c2b36b746e0cfacdd6eb0844a9e402426aa17a646da5d4da49feb7fce2c37292141682c9ff5863d41bfe68da3b00cf5011e89a9378c542c7c32472c501229ffb7d718a3a6ff70670003086685e63ac8a70bb66e0464434901cbbad964d18b8cf0345a0ea88b49734ab14308642d4f934817917216f4a87769f97a3a043450e66c551d342b22d10696b9160cff41dc8f2f4604350249033a66a44bcc7883964a0766cd99c1b8e47a1366b150caf197760a91206c3d7677c447a71accaebbf385543e0e28bdfb96414a8e1efdf0de37c44ac2d6079d0da014cd99baa34a6420aaedfc48732f9f76f2f33c9f009d8db30afff7a01a2951970a316c2e83c29820da6dc02fb0f34973e13c74780ca3f378e702ebdc5e81c11c943a66673d0ec19b5a2fba2e7014b99b706a7a78cc7c4ea08214acafb03586fe0e53a2d66838b9a2f3ad51e6110d1850ab051a6a8fe8cb5ad6c74b508675659e26be192b3ae5ef65c0cd1fe8bc962ca3b1c8f34a6e490dcfcb1088f9890f4b16662258d61264060f51a393ab1a9176c51ab17dadbf481b76bb59e014e6d21c3a980910c6d6ad3a180ce1eaf2d5c42ff7406756e4fce2cca8778332ed8f36a1880d3e2ac6ae6c856a8c3a09ab36fcede2ae07c134aad8eef599b99594dc6e028067e4f16bac3cb77b1fac5d6118988c0b728046e655edb8ab98f97060fdb665e624459dc9858c52252ae7faff50914a94c03b1754ba11a6ab1c171f8b164fe2c7df11e488bb65e46b36369318d8e10d7ec32c52d5a7845d3e7fceff99450f9d5a937adec82878c2a26da36825458a0e9ffde70e8feb2b2c68081685b4ee6a0bfa6c25dfe8d776511824b8a5b5f000e2aa1b4ac7221de7a7e37f70330d2c600f1e5fef51e99fe7c962fe7d52ce7e2f31ff9d1cc08a186890a3f7b794d6bb3c135daf939e5bdf5efcdb67631ce570028692a05ab576169b4705dfa773d472d87ae8c01e1fa8e31d43998898dcce76ed3bb77987142eeae1963fda672c27a71fd5ac6926d84844d15a2d0e6a0050cfd31ccbd610310e55a066c826270b736032e7051dbb678a531c6c910d6612307c31e461decbe91809d163c61c7fb02099cc04ab33798d1d62e6adb649397d7b46a26dcdc1b927271cec2ccf890ffc5ecf3ab9a66d2bb7fa7926de006db4c796f9eeb23017b031078248707d33f7f86414594cc9bf023016a0ecc0fe8cc76b82d66962b6a93b7d02a6b19d12ea568e006df93e19489dcd198d3994cea630014ed3799ad66ac348caa94daf762c83f9437e4b642d0fef222562226278d05cc0d46d3295049b1abf2bdda5ede566c2fa1e678e046091da62ee011accb6e1830b859e0980cec4c9bc076e9a908fd89619103022a90900814061b2c555b591e95541a2662413055db6dd094213c51c1ed6698891498da4acdb60d13fc9a466f5cf9e1afb9913d9a4f300db102863298f90bf8add3c400b29994241c6a338b04a812bad24ff3a8d7aa84d6880eb8e841570bef98965b8573f137a8fcd9660579766ab1d7ee6067ddb956d72d3731827efaf7b7fd989c1be7cc12344cf99609e7f34a0a2e85740bec526d54c730a0de28037dc0821ffa3b146a6dee23878bea4d525e1500f512f0e1d6cedbf5a4ce5cad9410c6f1fb778a23737a3314f19db8b8e90e5ea2311e34bbc8d9f683657586e8b678414574084e654341cecc2c03e3ddfae21549341bf8b181cfc10b80c64c95984b4bbecc3bcb40bf922d25ebc22824a490d60b92cc7a094865956853694adc2f1e34fea8eeda2c31f4aaad98178fd46f03ba1f5001948ce1825dcc8d22563f7ef0194d11cb3965547a7da8dfaa1a9f96ddd69b1d5c22ed2f9afcb0db0df47160f45c56b57127ba5130bd97586dd08b5614ccede5b6362b34abdeb8f8d18dd7aef1278a8f3762d4c1448a6019a8900c857ae91992b6c6aa59426395e2c5f835bd50d5386c9dc359dd85b201742f77f92e1e69fca1b10d7825ae5c3e76689bec6ba2ed47fc4197c0ddd34c8aa3169930111a003889fd14c086c5fa197925c16fa322b385feb6a8cfc39713560b6a0b9f7788d95b1a2516c24df7c5b60e2b01720554171be4876d807781e2f85cfc4194e0919f3c906dff895a91ae911cb17dd66b6fb5fe8f5da2787e92dea6a83cd22bcec383f8d759e5b3adb75e08c40637970dc3e9348deea42cffc583c61f22b915ab89156697cb8d862b35ab1eba8c64239344103f63826b71b5319db8806e99c0016aefa01e06a8e7a58a46374d5b8f5764b6641d527114a207ca3430b7975b6d568a5614ccede5b6362b34abdeb8c89368bcc084fdba8f3df79e8c9a2c2b806903b1ea05bd1cbe4f327b1394124a0ea23029bd05beada2f91763a124a500a50bbdfa106ed4c0e9c202ac44032a5006755d5094554d1ba0dd506fc6a7ba58b18ce2fba28a8f733a8254adc0b0af56ec932491f628ee9a9234e261fd0ebb980908e84886c2258410022036fa5879e0bfa2b15374953601eb11f87fa3642519b0681661f5f1768925929248647777ef580711073007262653cacb6bfd095930fcd9e3c0fe3ecdb08dcd95978cd7cf1c965e27049d97f9f5b32dc6acb3eeed45e3f56bd57aa4a33be5d8f652798d188cf50773ce39e79c73ced9cd69619c73ce7befbd570a0109e1f89d4234a54242b5562124d65a2ba4e4de7baf10131d1db992ab9e9e564b881643934193614e914824ba775e0d880ce777b024df84c242065c912b9884d165175ce090e360cc1608210c1852066c1100c73b74551b79e4aeb530f03583e0898284243265208bf9f447d7a288f76ae7bf0bfd597b22576209268e50a10a4df83861852b3c01c54f8d42a6860ed8700387076e40e5f8000866420059a44bdc9606b2c87789560490457eadb5fe902bf287604450909004e03db27444967c64a9f6f470d973043805203d1f604109a28022091d95c61e008922881024c870c5f4b42c3940081c4418710322827044006a76e4a094520a84c5871835416319c822bf844f59d4daad01a5b9422bdd1ae0c25ca19fedbdb6928268132c4ad06b0fb2a0fd9279d5eef612b1cd85d2c7ae966d1867135982af6d2f2588a9b6b5eda5041f52edf1f6422964c95bb6699f350d37803ed61ec3db83407bd3b4911334cc0562143c3efda185eb828c8e11ae1f473874f58088ab0746eebd2326885690c57d131dc822d394756be07420cbac35d48a75b822f87c0cc4ea7531a2e051762f48c8b98c7a1c7ee9ae310631bdc23abbd71ab884528677fbdd0bf7b3fb9063bd7b217bbcabccdedd0b227ea594525e990c259c1302590151f1501117101d406e54199e55c4404db0d7323e8cf0638c31c618638c31c618638c31c618638c31c618638c31c6187f4609ea305c7e8c8c0548a207352493a7b6453ffe9cd95ec9977c9f2008d1983570d277f9848aeff248f75d22218da87c44f914df2987bf3b86a9ec8e691676d79d0a4d4af1547cca6fef460fa4ef5898ad396766c123fdc8b33cc5a5495ff22a3e679b8b0a8ca482b4bdbae291c5cdc8a39c0578803e55dde4242f45c586b2d479904baec28390a5247216bc17a0dec5ee48fb057bdf0b4ae9bc941f8dbccbb79792c233f9134fc447249736f992474131313129f15ac0562b7b146fb6bcf9c39b2ecf763d74c9e3f74ce4eac42be15d0fed4f3c92d689178a29cdbb138fe35d0fadffc403f1ae8789c99f781b1745930d65a964dbcd85e4e3d6629278f3698749b4005fa0d9679f7df6d96a4d1a9365e39c737b41fad06304986da9c8900a325cd153c870597fe4435360b2384cfcce06ba57518bdbc752d274c2e5635ca6339ce52ccb40bf25a12307e58d358777b6239f39f6aa0373016e2e9ba885615a05fb1612eb992d10eb89df5540ab4cd5b797cbb15d72d3ddd0bd4467c5e597e860a9c96a7159c223854cc6c4362df75a730ffa0c1a2da123e7382f724904d34c88f02c7e06eaf3075d15e1d84bba3ae97ca03bb6379045acbfd5d7eae7fab87e86575105057b9de3c1e58fbb43dcc1157f54561032a5b27a14b2c4d8834689cf5a2710464aac5831eaa174450d76609304156c006208337021013cd0418e9c235778810d2e20037aa0a0e3430e3cb44260673ee7d947bbd30c83394ae9cfbd3bcc8fbd38610b0e9f4f72935f88bc5b80d7981c06f4a2873090fcdc273fb537f9b97bc9630f839f3ef6eaf340c5f19ff0001543ffdc9dc6fcfa12a3d8f29e1bfe0568cffdc47bfbcd9b2f849ebef6fa0c3a306c3f77c7fd7cfcd4ebb8cf4f3b6ebfc09f3f3f7efd19cffc1646e4dad30f6d924bd38d378cf6dc8639794fdbf4e485f8271b8a768dc9499e3ede7dd3925d6372908ce8751af8c45ef6e21663722c01cebddd626c30f4edf3e34d65a9d7981cb43b07f89c3cef6d73bbd3af31f1e4f3e59c51d073faf8c0b9c9c0d953afcf30cadaebfae3f682e39edb1cdc5e74e8398ffee6cd90fe6ddb5e6f3b8301c7c8367e01866cc6c741f070fcf329eddaa31a00bde5f931fe197478e843dbe3369a9f72df41fb62fc32e4e9f51974ba1af4fc0d3f0d79771a387ef85d8b3cf320c75e56819e0c40175442d1811f1ea7062287a20350f0f8a296287244c0a18340e5084207efdf824fcba7a54410a81758d81ae2039ad371425b251871e388d6075a3b5a3db46ad0ca6905e183083e92e0e3081f4bf8b8424ba675c3c70d3c307dec00258bf80022c4061f3ee05dff10ac8f1b364c1f37481fa9948f9507a6407db07cb8a00691e78dec061f291f2b1c104208a1a651e210baa8cc967baf54b11e95db8b00b847aa7bd62923ac14443b8e448672095d05ad397401710408205c314629813002441108598e80d240d04268206e6083052207ab9c79d7313fdce009403a20644812a2606b58011128b582118dfd1ca177b0ca09020431a708a5263949f0ae7f02a94ad04074e6d095634465a5a05510057d7182680fe81ee81c558eff15393ac8cfdc8b284db4520b2dd0186b8ca5b9f22f99bbe042b59b8ca1ce7fc9bcbe643e37065962cc248c12b2b8cccf9ccbcaf61279d6e08c1983e8d74a27764339f932318cee2e867cfa2f7253cae7c314a43473317252bec41871631bdb91cfbd7790927631289d3464fa99a37476b6c61ceca575e259a3bd32ce4bb19a599c69ce53c65c338b6fc6b49b312ddb30e80328ede2d425c6af5be3cc159ae9960ae75e241a9d672456c369dec169de81d3cc03a739bc63ae6dbfafd6c92bacab296765551c4ef308aa8eb1c171ff719f8ef643e9fff457c3c54aa2f587d2ffcde80f06c485386e7b12ee49b690267992ad7e27093d365b4c3eb4af7e923721f91292d056b3bf14a5512314eaa3f9683e9a8fe61ee1d2e171018188a6dbb66d97da6da334fb8daba1e728e5fe94a2fdb403f7210f06f49023e1b8ed43dc872e3049381ae242dba85f0ae29e7bcdd13fd99c6ceeae2c28e49c737275abbf7d34dcb67da8edbf998d036d1bf7df0c57b36ddf428236eeb9dd3950e8411cf7daab415b76502889428d50a88fe6a3f9683e9ad46d41113bf00170e8d2f1d9ec9c73826eadf5a576529d54279b6800fc58d370d470d470d4fe94922da6861fa4691a7e90f620a94990b641784aed374f937fb239d99c6c4e36956571a0d9f9a1be996fe6a25020540d2de79c77aff1231290bd6999f376c7bb06bddf87a5f2b4f3ce396f8c8d7b4a9d6aee69077aaa39edb8f75ad70f5c40f87181d00308339b66826519efdd63ee3dd9a0e897b5428ae72f854dec4be12fa55fee7e3fcb1b959f7b0cdbaf7d686b2090e6b21a945f2dcb9b4645f18be967d9a43fe347f3d17c34a94dda1afca2f2a48a5ac3e2c329ab9427557652f1f92e8e000e5d3ea8f901ce0f523ccfcdf503192e000e5d3ed8e1596a71d34c7e5616c6fdde6cf778b239d940b13dfdcaaa3d2a4ecdb1f6e4ca2715cf18b3b692a4b598cd1fe5574c72d2db7a83a203b7d33e84d65a1269c3409ff42d6474b96fbdca3a31d1727b515976774d2b2bc6c62975aaa1a71db207832149ca532affa9269f766412e96433ea48ddee92e7938a5b4d49a8093da1b0a2208beee9c6f1056c779735d0a37cca29254dd8db3fd558294faa932a931e63294425291fb232db5eeccbd0b87d9d4a7938511e4e9046efc945492de4a986dbaf9db7fdc8cb1fff7ad8a778289e973aa564a9e2d75c999f6277ec64774cb44d764741096d153e7772d62aaab66ab5b5722fb717cbf16bb6e814d651546646712eb3380cfdcae277531991dbddfdfc3535bfa4514a5c85e3a0ea67bf7915b5bde487d1e82643e3f94bb3a5e61082cfa971f8fc934d3ce5934d3440fdec4fa9795271cc5a6b4f36a21687008819c5144ce4708d937ccb0737780238740d19c25d4376b88838e21ac2c397e299aba0b75fedd71dd2b4aa3b48537efbf5a6a2668bc997bcc55ef6f8e3ad39e6166383860ff549ecdbb17930dad3c023c645fb1bf69be43e1a98edb5a83dc9ee926fffa5b8fd18b726bf471b23ccf6da5b6b6f8c0d0cc3be1dbc5fbb517e1ff7af865fec4b7d1b86edfea5f8fd1a92cf96f9d863bbd7a0d8f78b4d0cc342dafeb68f5e7fbbbbaf3960b0f63f9434e1c7fe9b8927cbe597fa521c7f96496cb127c1b9c4839304db3d6ed8770e070cb3f6b1d6e279ab76b3946f9eb51a96853cd0739ef6f17ee661ea651f4a966e11eaf7d8e14ebd9126fd2a1c0e5847fd18b3ef2d2d3c5cc62ddbbe7ff3b56d7971bb6faf41f14720db86b1f82f0a6e30608fafb41fe3ee320b7770e0b5fe776b50be3df7bd65876bbba22aea4d3c3857b41ab465875f8dfec037d086ea5c0fafffd1c493b6bffc1f2ae72fc5b1cdacb53672f861262b90143d913b13cc19e75cad28a5b3d65aa7b5d6da79efbd377a1d122612e64ccd946cf5f4c4555c75082312f47c88d12d035964f46037276908975c3e8c19b2650a91c3e54329e5ca089b4b882bcc486d0d9724e0dd04be80743d4899821035e80881438810581e048942909e204c04e111a4479022d00722c8610948b021a2d584110429626483ce3c80f4c8394a0419c110417208b2023b0a0190201c8810204500d90192042046801cf1bf628922f3468c110559d0898d6024f011fc44be9167720d9752e2972c75426029f9d48b314ea87dc8b5d206481313ade9df174ab3c77e46f65886dd8d61746e5290bd29110c9b955639271de9444abb97baad0d19aea8f6b17b73bef7de69312274c7f8c910bae31e06f4f9b94d0aa2413b4359dae226e5f673ceed29a574fb5c3590576bad75fb6c359067adb5d66e9faf06f23a1dad7da6235775d5d3d36ac5d4fd0e5ff7efb63b1d8dfdfd6ce33ee3fc1db75fd0f8b67dde187f0c8f01fafc2fa0cdbaf9e106da1de8b9eff2c7706ecb00da2f447cb332fc7ede2f441c6fa9d327abdbd0e13466d6114745e80de18a9e5b94d627b9b4555916c0127df873255b6c6aaee48026bb92a5946cc93ceb035c49ada4c9de48cb0268b2318025fa94453fd7550ca08942a8007b43ff469a26ca077bc32908eeee73a2f6140177799544644b2d2257e8172962c40812481ca1b4cf1ba8b9eb4e2784eef666d4ed4dee8290d2627c9d2e88eed8567fd25a6bab5553296bad6db5eebdf76ad3c3300c9b9e8ecee6d20dd15dae7a7a5aad988aa90e5fbd85212ea420fa65caa0208b0c2f4472479ae8eb14e91886611816e9963a58892c2b9e81cd15696212b5b6012bd68bebe123b8077ef42cafabd98225eee996d0bdb2700fc6812cf22bc6015c913b2a45a00c8183e0b2e6ac642a0ba216dfc6289c52271b27642868fabd4480166494af801630302c7b98b964d1621209990823dce3d045c4877af8d97ad3f54ceebb1d135a7bf9d8eb158531bb2515aa0e490a5407b2789c9c96eac715b40eaacecc912b4ad278715a3fd487fa96c8342a12d6f27052b5843033c445841cd710565f8a240211e65245a1125c4225648f3b32d78f71e615749163f5348e8b0ce394389c0e64916fc283a35add1f3835bad7d5ea5e9d2b9052a4142985d13bb1b982daa979f7c5b0ac678b6808b8225f0786c3dc5d77dd42e9ba85164f551df0be0e1c70d041071ebda6200bec7eb636a7a00b0a2395a04dd573caf21c2795149b555ffbd656eb616d7b31ebf642d3b65a5bd105bc9b14024db50ab2885cf305591112e85e738acb2c7e26eab9321bed60c61965b8134c20438c9174439c9174439c9174439c9174439c9174439cf7de4bba21926ee8e921dda0e3434c4639ef8f67c00d1c1a0eed6b6aaee4d2dc628070a5e6802b546b144639b76d6bbb7b611fce39e7cffa5aae1ee8b5cf9aa6695a7e08d2be7a96571c69ea95c5359ebb0ab7b6b2e6ca8ed5bcfc953531765933bfcc8ff367f9b1fcf77bddb17ccb5f71a449db3dfedc5ed0cfbbe2c852fcbcaf576bcecb42a1aaaf0e097d7924c2e517a99d154838905f5f524841f1f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3935fd35ef4ba7bd560f2d75a9feeddb3e6410e52654c9f33714f7ff793c76028a1db8b9eee6ef2982c4d2e7ffdecd99c3f05fd92d0539d5f6214200acabfbdfe9937e82d25f9fa21afcfc00303e8e9ee74f53afddad74eef17f9b57f2dd3aefdcddb936c924bdbcf1b667bbd6152fcde291ebe8c5b8c141b9eec1a939774d1a626bbc6e4dcd72ff13a0d9c6a9f3f674fcb346b0a7aed3347bfc2a06d2a4b35bec6cc959256a0e90bcd8ff157fc79d4ab8a8742311cf4f4435abfde1a85c5495e6f245ee8413fea28a00fed5e551cb4b1b661f067bc7ba634e7a7d5eb28d5037d556920f2d04baee53c030f277992dd69c0c8430faada570ff4214f6b4f7290d7b9ad7939bfc6d33da129dd5ed407e1dc6978f184d6b28a6f1bf7b0057038cc80ce0c3c7ace0f18e1041e55f303461851594698448810214284cb32c4bcbb26b8ae09d20a8420048e7bbdb7bd51dd35a129e5842084ae099726ba2664483251903a40aae1458c8277affd8cfcf871442ab1a963acb5da7fa1bba27645f118e5a4d55eac7342cb9f4e640ca33efd97ba6bdd178851c470897dfd174c45dd1df6d977f56378b665c0f60b1575431639e326e7a494d65aabb5d6da7befbdb7f3d1f2e7ebd5d3e3a3337d51c1ad0a4ebdae7da042109e28e2c53307e786d100f654c34fa91be4c975559894d65aadb5f6de7b6f15b2b9bb2a6c2f30ea9631ecae329b4a39af4ece1a2b6b5ee8418f437731841ef43342f8413f43e3e73e474a65c5a6d52f906b7f3718906bbbbe1663db4c68bc7bf6127f8b49437e8b692b9c33079f4f038dc17232a187d5e3a462b158ac53cdc9c608cf4b966a12ac2458ac1e383860962a5249f1d3efa39f792d2f4c77457ddf8777b8e178855358c56b37858afabeeffbbeeffbbe6f0a5a7e9da9298c5595c82c560f1c1613b21374afae8cca285746b932ca65249b52b9469a6cde9157d294f34dbec93c9c52279b277cb28ccf135986c7e5e31aa24364a7c82b774cc82a8fcbc7354487c84e919711976b880e919d22af93cd694746f9b8b2cac73564e7898cca33ae8c3ad99c522e168be53ad964560622c315b4eee031827e7b14ad9b8031c658f7043d1fc356b8ae3a23344cf635631c9c835bd2f4439a2c1e8275a429633f7f67a70886fa68eac63cc8d28d2cb1f00d6649d38d190eab6b42fcecfbba232abe3142476c879bcac3c4f968be1a6edf45045e619734757c5357377c3ec62e2266f87c5c57f8a3a91f6af5d1ec8f0ff2b61ee35d7d34f8a5e5d82c08a46d191fea9b49d9ed356f8b315a5b6be5360988a62cfed1fcf0795c6baeccac7daf3ff069513f5f93b0696d5dc9d2dc814fcc8b2f37120edd31151e9fe274fa43c470200b15100f6481fd7ced093c7ef698fde8d95edf6e1e280e6499cfb9301c3ea38d7d6bfd7a81c9edb79056931a0d6cee98a061ee576e2bbf7f5251cda4d2cc92255b7b701b6316e32975efcf2de3de7bb39f5e16efbd11a390d33fa9a4a9fee71ce9a8d5c36da49462d845baa1addd24979e36367dfab5ae302898f03ad9f4f0f89c54469038f23ad9e4ac74cf2acb35cf2b9e54324ea953cd2975b239a94ea9930d76aa39a54e362715764a9d6c4229d6c926bf42a9532a7ec633a37b7dbd727e0c215284c8eb75529d6c705a2e9d9d9316f6a1c369113a45f4e0396a5d13340cf695d39734c52f5587485324e222a940f73cc3e99354a03be6994274f873e81ae206bf31a6c754978bc9c764d0cda5d6da624acf6258861f0af24af3c5eb558f72f8d1e4abc1d9922bae3b7e34355096e2cbcd8564d38e755d8291e4974b1589aaa88aaac8d35ce2d5482bddb58c5e7992570f7208ec19ba864f159f0f90a5a9656456d28402ed8a5a0af4bb88b626c32ad124d9111dfaf939bf66e6a9358a76ada26824f3c8d2fcfcd264a469669d01b2341f0239a4a0ec151acaf36080e2cd45f4f8d5234dd5088f341d91261f6992bb7a9a0e1d0169923fdf021298408d0d4419ca611045484119e5534e224acaa3fc8c944741f994944fd93652b40c47411672b6a4701bf71b4acaa3886aa4498412cd8852d294c2ea30614116f22d8f9691a6d0cfd737a449f4f3354acfe41c3af8cc2f1e3eeba7d4ddc580f2293f03e553b68d1eeadb48799419231b283faaa28fdb8b4a117298b020cbccdff34bcb78fa8646cdc892d452cce875f251bc2e06b967a4fce867748ff2dda3fc4df1505c37e4f5181eaa0fb790482412795dfe7d121bc3492e8cb917204bf32b0f64999fe2d51ef8d2282db3842ec9ad6529bec6b8ee9a6b14aea7fece1b7928a1d093889ee44bbcbbb9947c9e1bd531a1eb87ee7d9148e499884c48fe927c8b594212fa1632b4b15e5139563387a6654c7ee0f33339efa6504a5be902e68e48aa1802a1fc88437bee716c0f7a0ef4803d07b4e7de6e0fea62d09efb19da737bc6f6a00de78a95d8d3083dd20edd356cf78e098d5f3e86bda59f79311cbb2dae2a5da87cda44862b7a475ad5a0610159a2ad131d7ac29226c933942da396eee1ebced3e4ca08c36016ed88aa7612c2b91dfae49d104e08278413c209e1e49d13d18e68475444f412f1889010f5887c267475bd6bbdb0d1f14a2c42164c5a6f022cbb2370f2e3c425b704ac8bcebbe7d1772871d4fde867743f1a7d47ea9e54a589498aef938b1e9ebc88aabc8aee873475f8ea70baae254d294f497ef40a9db04e7a9ce09ce448137e2ada5902ef211cfbddc7b8907677318cbefb19a3ef547eb4678cbefb6edb50f1a30d27c977d1cee8055968c977e873c23ae9e19de0c8127dec9de4c8129d910137545ec567d2091e307a16fe01dd9338d0c30c154f7a0ea8ecce3e0b3f7a169e032a7637770c2cfce83bbb67b0b047dd8cef9eb4b34cf332d5503cfb70d3e69cdf4f5815653ffbed710cdeb2084c09c4ccf23269870e7d664116590a9ea4f86973a6f3c26dbd5e511d13da7eb7377c061f6d3deda717c32528d33228417d062e471ef729bc3ef9c9ee35c715b17a5d95785d93787db4044e3f847116c57956464696e80d59a2dfe100aed0d7a103071c749072431ae86865a84569a4558d27ac0e07b2b09906da992bf4a5bdc93bf341bb63d8b6bbe632ef6495bc83034effdadde1904077e8b3011b3228f9d44a6f44ca418fbefb3e8112fd6ed48d6a74cf3b273bd2248bbca489070969eac93ed877b931593291a50925a4588c1464edd6b804e0ece95e27f2919052ade0cc24f5435e867273d93e574d6e2ff821c4b964c3398364d71488dacd65d35abff43a26b4fcfad1eb33f850eee1169f7a313c72da83bcdee2e25a7ecdcbea0c3abdb22a4e652da125a6513836b137f160c5bcfd02c9cc2747e7d7346a46a7a4a9866365199434cdc455d63eff0ced737e2dc6c59d9e8d8f77e77ee0589025db3da7b28c976f64549e91a52ca393f14e943d51febc14c3b0aff17a31bcceac59433269f58009c0e8d0a5a12b46928a64021d35797dd0f43efe1817d8dd5d0cd9e39f913dde367ab86f23db312eaa0c4823d01dba4823d0fd1f3001180b80c10069eab0086d419a263595624fad82cc7aea0ae4048c9ccc6cc95ede3cf6312e3ab8b78d1ee0dbc03edb70aec4dd5b5e3330ded0058cbb57528ec63efb9ef5cc16e8423e7703b4822ce2673dd27424f3c99e70a2eac0bb3b4807ea0468055964d6931dc97cb2276449667a65255d41ae1f630a397b7a68b5af170b9c9262408ac10b22183386c7592f10120f120f120f120f120f120f120f120f120f120f120f120f1e788001e9a6071e52509a9072d076742d7c4196c9f1e03c9005fd69654a8ee073ce121ec832391c3ee38c5407b2cceb552b5ba3158f9f47241c342eb916c6c1381807fb149fd9525173257e2aa552ad5612d405a1b923525690452b45071ebf7347c494156489d8a487c71fc5ec0559e2ef395ad11ba880cc80091549875a493a9c5227153fb94ea9934a4eeb7da869739d1f4ade5a6fadb5d62f95035686711c8655ec6b94917bcbce47b365d983344c638ee33eff08d318b739aeae76f0ea4d0a6acb0e958fb1bf9bcb47130d203ff3b4867d7273c1308d9402fda5ac900c576499166f81439790142f024ebd9b8ccc638c5edc64d05a238fbdb22476b2c959c35e3fa5409d06c89e6cb697cd25d656fd214bd5655ffedd5c6e8c2414e8932a6eaf53a3c0bd58be4f2af931d3ab4ae8aea2a94016707a96774b68cb7b8c98a0ad8717159c33e6bc9a9a2bf77e943163e8a2b6b09c38ea0479d0c588948365f00d8c92a699ec06b290af3d5165ea0d1770b64889f1ded9632fbfdc76cf78643790a5aeb8b67b45d51bb254ff7a30db4e7df8bfc42878cef93bf5f05b2ca58d9dfae0542627145966071ae300beb011cc8391c07287cb22e0103088528e34d11a9f4ea8544eb47639ebd4cc000000004315002030140e07442281402c8d2541d41d14000b8d8e4864509b08035a9203290821070c32c410000000022033421a8100f24870c83a800ce3eae86d3d0f7e390d2ba27ca400b8423a61000c8de64282ffbb893e3c2e61384c521daa958ad762ef023a760035ee6e39162cbdbe5e07e3830192e56b9228429631375c2928c2f5aec630675f3eb28457664c49972935545dc4f73ca61f6e2d2333caaa7c9cee6880d1ef7d0b88423b35555004fb30243118aaef5406eae46b7a7c6a805c54cf8b2336074397cbfb201bb70946777f8b5d418f128d27837e0ad2232fc41d7137a5d7e2f0e1574c6de0f9548f3e9a67616c3cfd6ebd5c6b82f4dda89c9295ada9bb354e186451fce7e8018882bc3d8e129b52f8acc03d36de6aeeb783e8b51da540418174223181182135704c3d8a416cdd7ae95d74bb486ae45bd027f49c46694294a01d3dc3570458ee48a112d9e166876cd677ab10523ea293884a22e35b51ab9a2c38b33d25ea6819d3611e512ae38d7e31cf37f0b5a2b8c46212455f501071332e66a61bcfd85c03f94fe2817adcdc607d1388873e67eb71f3712c12dbf20c8a8570219e3b1d21bbcab5b31b16591c6b41bff53aee49c2df445b0c1cb7b33b0d111331534f108e935c07cb8c4087e0fe6113123043b8d575f58015a120a29a8dad9842e3d7d50c248db05cb884f10ac07441f7849877d2e850804359b43478c2f9721381b81fdba86620673f88e2e924d8d363c8a551f34666b08eeeb219d929a175d43cacfc4b7436dff678cbe3e5826f77a24826e1536fd751bd99b7d309744be385b6403a8f1bfcfa4264c14597bd5832ae3872c19ee0cd6423e441765ee69ce84fe5c81ecdee23e2355363e61ed13e42bf890127b4b4504d792a93b623f443e46d9f9233b5e2c8fb20228dc10634fe8689f170add37b479b1042b35e8cc596cbda64a5a215a86ac4aac89d883d86ee0ee72d095ae5d44d071384b43dfb095d4660b9aa2eba053565c727126ac5b33888651881aca877ed593b6bef7c4e93648308953e22556d0d33d51867fcf94bf6c8109b0a063fe9d984d929b07cfe32fdfcf10169c057c1164da5b3ef0c86368904ab48fb82f6d47f83390ee4db0a2c9507aa01a321bde00658d61150df1862e3a0a017ced37bbc442529290640034c341a2f0fe93a6fa431413150faac9e5a625c2ebd53d38e9adbc83757e7b35c10a45327e308d0b69094b7e86f97398c7111ccd5325d93b88f3dce36583e94e8601a0b7eef98906c60ef1c6a683649de971a3fff4f80c42574e4477009b206b43dc50cd71f0aa1d9c8e41b8645479213f74d4631ee1561216b1da8628f5cdaccd041a6b7753c1b7446f423201273ca12fa21e9191060403f47585c157d1ea07d3612856411d26cfa4e3806d4509a03d39e488c0763ea84f6c3d000203e0c8a9f2c239801515f84976d78424cba8251294bc322345c0339706c643c37c9d6ec4f708d98316a21e2680c1430491f4cd17097f2e535989f392f41573f2b5cc6140b6e4be674bd452cb99087ec87ec4341ae264f8002353cb2ba4b202ef904baca3d4bb7c10a3b57df78c368ba28b890380805b2a93668a931ec6f3312deca0aece055e5242a763085c4809fa1ef6f63d4823ea2ecce0a8874aebb52702598074bd2f446840ccdec9bf23b6cb455d1866113caa03129d28641a880062903acf1e3f0cc81a9df509ae138374b896fde35ff4d788db138ca5364874ceb285c75ac54ef4dfe24104a3be822729c3e2b13b407471ea50a8373dbb039c47241f17720dbd16c4f6f2b095a5fb8852e17b035ad69ebea0e9062e841ad311884b1f81a01b6b59d1661eeefe1abc83037a7d22e1009c6c5d52941f0f19eea1f02f36f1437e45f0905f6d42adb5e18d08b1397a7217e252d6e399dc02cab38f047d4b7e21915c9b9ae530e196cf567cbf62b8fe6511e01399e76ea8050f268285c30b674abec4a5d59fa3bde556cd9f950435c920a9bb5801afb382b3016fab87cf94db842c3b86d963cdca97ea50818fd9f461e3693274594ac2b8c8ee317dd1fe4124619f19b15e9409839f7f0257bdcb87abc9c3ffd380578ea43d9366936ea4f2828c91f87211dea219bbc57e2a9a9815c74e7d1104fc22f8869a2959d86c773fd7442e2c206c433ea7c55add4bcf309e202a0f8330809f4261ae00aaeca4e5fa101d2fa6844093cd682dfb368f64d7d00f0be59615965b49091c553ce8f91bc1143825470f973e600f1a95abd2793898d1949c2530fc42d91804e63113813b4c43a02b148809cb34a1b87b3008d90e8edb9c1aa317104fed0311a13fb4dcc8d5ac1ac062a573782227839503295a711511325533462ee68f423a9f45bfdfd333438d7ea2b68ddb6db82859a6afffba9e6b36e29a9be63203faca6c4d312d999bc9bf0204addbf7c45cb6c7cf5e35364ceda9231016be3fee620ae10e4bae2889469a37d0648d0ed44e3dcd69d5bc1ac42300766972cd11b46fe04f665d5f99186da0f10d6c1a8ea21b2b5b07e71a6bf3df408e253de73659207340bb5893e63caa4ab020c11ba27ab59715583d3e977520766de65c70e8d83f39c167cd44fc50bed8fce38feb7c84ec0a840af48ae22959392bb1a3f872274e55fc84a9750c271049d8bfb3cf80365da4f2445c93b702f5ea42cd44a455ebbe098d64ca9ad0ed6ed2d7cd87f0bf70c4ec049495e5a29f5449a299b0658bc3b49fe6ee41e75f44f63670b61c71f20aaaab8a710a1ff46baa4b4d8b601c860d8075350f244c7e02d09e4fed013a5d093e3495d1b9da0e13840fb6653ebf99eec24f4570bebc7136e17f33105c1574c08d4b3755f7fd997a3db24f4d3115b034095e8b3dc88ac781b382aeae234efa9913da93a5babf1b44c7d58c93f7634314cc2995401fb996d8d60e8833fb692b295a77b3269224e052e7b02da755afcaa738be2f301331c677ed22f7f5ba21cf60d7d7d107cea5410082f43c60cf358efe78f4cd09bdc2b82812e44af024db2d0b4f17fb4e46edfab60363203583cc640616ccce6edf9d75df8a6dad824ee0d9b66130b691a10168ef34154e8558c4bf98b9a0b6cda58cf3c4d8e1411b441d13d40589bb236b40ba714116e50d33d2719d3637ddad84c8b2c061f3ebb40c8c549a06ed9f0702a72e2b6c41913180b29b1631109fd080851b54872e157ec01224ebf765a5bcd37aca0cff4dd50b7f9afe164b3679a7130dc61d0e4c35a33a46b7604c939679d58b664da91cce0f8d7185b8d30d0812a8343b7751db2b78bca6108a6ebe9b6929bb580d969d492848eb92327a1b953ca9e7f25899830bf9d5f0921205ebbcf0a4d9ef83d0b1d9c647d7977ce94fe976357f9b21c811971d2ad1b916815321841ff58e110f42fd51262c27b98a8aaf6906172543e61fe423e118869054190ed5efda12b7996c69bd735850150dbbe9fd88ded80e55eac5cc3bb6a1afeaf86e3aee8153dc551525c7451b2751e5ab9c3d85d8ee01f93caa6e33cb63146719b3a77cf216e1d1782012eecb8595c5cf8930eec710d163e760c4d4909cfd1b13c9707f813c73435ebb0b4565514351f48eda35e603af62ecd45df959a2454bbd5152b6de39592426db67e04ee0a4d238ddb077040257dab12748fe5177ca428117cd0a3f2d921a817c12c352b42a79a3e18ac7c200aaab9751fb72f83dbdf49db92a910fb829d91fa085f2f2b812a04674eda97632b36404d1c227f62f0abc10f8e4c885d39f000051488eea7188610c0b44a51e86002f3a7679abc0fd1538cde2c7b3bc3c92fac772bf1f9d99e1f45c77bd1c450b4eeed7b83f2ef8b307180a8fc45905b2fb83b59f1a28f2ced05ad6365a5097f7fa2bc82b109639f21567e17d8e39899b9fcf26775f07d52481d46b12d2f519729dd0ee0076b191f0b044e5d3ea6b29324cba9cc6127cece6eaa50675bb767785bf88952a1a75c8c6123872ffea352203efc93a3960f733b589e8854333c0acd993ae2e3c93b72bba0e8d3976c7d716d5cd93f90ca54ecfa095020061ff600af0d3841301502f0033f876d9482f80120721a4d38c3ff4359c7306738851e83a9ce37c1f3fd1bfcfc49b420d38169fb617c6bd5be7c4d593deb014f734ee20929c7ab4a8221cffed113a41a6ef870ea84f621d031f4caa287f35b572105e9884106ecc8c8af4a873442c8339ebf39732a5f536e796d183afcc65a3d10d6700a088b23396b8b92b53129ec0841f9ea69cb13789d21cfef98a8069df3f65cc59a671a3c17d3177380c775f8da6a5576532af04b7d5c89fdf5a4405f1ba525069a86bbd6d6d0d50fe077726dfc4045a5098693950e899e5805a6815a2f5b025c1560a127e6b8033540c5e027b18ea225d113b161e8e09bbdc15b118f48acab48c9f4446c743af0e66feec899ea37e28801ab6b8c9e408ec488db4f767bc1d3acd5a44580b8c56e6082053d03dcc7139699c899aec9dcfa069749695ed20e03f2bf447539556039363029559f4fc50d33a85554b8721a14648f04395389bf2b9a0322f5a2a2b13ffdd0e0c74064c207cb78832e8814c0f5f4ce188f3af689734591a12ab82a156ebd8006d21b504128f3c728b332890c2082073cbe9153bb82460f08bcb9818dd462bf5cda3f6dee01c4be6fc7ed3ec84e8fc228b5fdda3de1a6587af42a5da2f325b2e00d7417d5cbbcbb815209bb65672718f1ded6533e76ad53205005901011627b612b025d21cd526dc7b8332f2ae1a06b1a992fb9eb1ac977cb968ccc3d91d90acf995f34e83372a751cf46a6aa3426ddb637627116cdfe8bfb2f19918dfe6a423536ce4b27bdeef9ce6a9b5e5c7c4f4a6ad9617f701dd911f92841c74d25fb20d6013f56cf28665bdfdc859defef172667ebccf0bac099081ed5b408f1b0da070591a8433693935dab7e0206e42c6ce02ecd6e65ac5b8897aeb3e76f73f1358594a09012b0dd19ef22bd41f8675933cf4835ea5bae942facdbf2c061e61c9b9f6420e6f9af2514a30ed9b5cbb43f6dcd813bc9ec7c0336ab3a76a330c05e560c4d2984c216d779940480a0412be82955504802fb02d9ec6d3428add1fe533c219f2471ba88213c0e122bd645fd4e9b9ae0995d68cf42d4e43079fa74f364eba71e88f053469c4cda7eb017c478226aa5f91ea89406fb5ab109f92817e3905b020243556df1f8c5494231391f4d160edaaa1e2dd3780a8cd75a65bea95edc71536cb6692c61c1506fe99ee99678230d82aabcb9350fac6f20b5b05bf994ea7706aeddb48cb869367a13aa390ee4bb8f8185e43f2dd4252202c77d15ad38e05683cf597ad0914519d895c8363fb28b6449bac0c85191df6a0ed7da7116388b0eb138ebea52a6c59f95e37fb76298214478f65bc4f99cc9c1478a607931e799595e08c6e7fb424393b06409aa90a225407a1d6d7e3ab4f332da04a26be6112172ef3e33ed18d662f24a9003306bd7b4fcb0a8c3a866964bd2d2b0f8572f9e0c81b2f83849c547d8577cb7539b2b74d560fe294f87ce818d989f5db96e4a6206e3e12a2ccc3ceb24d3adfb095c7d4e418de8e6ea0c0e234ef8d98646774c6b314410231113353bb473c03539465e4e689f818e485acee9758f13e960a204269908a7197d4d88081af678a42ebeb424354c6426318a0e850f042a49788b1aed07550fbbec05f6721e3ff7169c12e1fa6652a8e31c9079b09dff8b9cfd74cab9ab8f56957fb6db283954c42e51aa730ff493baee4ddc40ac34ca160c488c92f8cc3bc2434947544b5f74dc27d43d705bafc0ab5dbabb2711befbdd7051cf6012c1d9a6feb6fa8750fbf484893292de7e3ed34eac260d79b247db4d09ee28ecc502f02d0fcfbfa32f1d83850d736596db528a2861516ac44efaf31304c147863096875c7d6e9139e23e1beed013e8c942e4f2c8938ec65e964a85918eec632765833cfbe4e18947dc03c7a529c821c7970b70a87b9d3fc5b0be66b8d1b0ff6f2381a8714c91cf68d4f3b120203aa46d2736613a48f8a2a71bf1b8ff4e60f87e12b6e98e4be4ed62db28927d8449216ebc5f8a47441188a9f3be74fcf4a37b3b121e19efd2e3736c36bbc3d4bcb079bc213d8e14f4629101878819b4d013434a1e7f2a3cb863109dc3f9cb649e504b399819075afef27975a68c926ef972bfce2f448c2fdad88fc0a8700dd995b4e62d86ef6bb80c6b1cb0dd1c2a5186b6632eeb920afb3a797cf8cac9e759147926ca6513b2a04c9418fa89f2fc2ce205c3722c336119166fa279d10ac5f59529d4725a91f4ed0b102745c55de0ca88663a8a42316a2131795a9b00006fed79125fbdb1105458f5ff6020b753f886e705bc356e5f2cf3acd33c4e8523dbcfd0a7114f450b1c19defabd038ce47187ff9724f7dea9f7905815bdfa489abe786b334d7b533eead5ee2982867d39a088dadc913a226aebf2420cbcf94a460cbb9673dc7ee4254c1b8ffd7136ed113450d0f03ce4e96ba5e5c14c525d82e73508ed07c8ad2e122130dca15cf2302d4d021bfb66ad43f99cf72cff69d69d5f32cb62ab4df271534f53270b49d57d9614808e60b0e6338512779a4282aa9cae392888d421f748960d71df8ea546b9872f7530e8403fe722d69f258a9322ff13801372ddda838e845f88970d76871c5f1e9b38c4263582b657c978732d58fff0856ccfebdaa331c195075783fe1e3ec4042b43158c599342f23e87eb60f483748f3b20b95d18732fedeb8b29ca0cb7bf1ad61e637119021439516aeaf0383d8537e621b758417299b424233f2131939bff467b37e109954e36e8e1f9a6203e9c48dc41a46311fce3d4307434cab8f5220d4e413fe3275cb10dd5fdb9bca2279ffadc36d72b6727811f78b1d5a4e2b6a4ca0f83d713c49b621f8770447b4c40eaa4019f564bedcb164bd250556f3f2db40bd60657966490a0e346231e4b970ec12f0927f436453096006eb32f5a588b3f34c89dc110900113d3216e27ae658916290aa23a74c86bd9edacc79e8ae93e2ecb403e9cd7e63932488b944c701f7b7f90236693fdfd7d62c8cb49f2a8080a8886f93455b7c9837cba29aef606f8e3f2baaeab0909372272bd52ebec7ea5bd5102d3a6f6c69e37b90249f04ace36ae69115da976a3032b41b35123a989f42e159689c96540b752376153e0d7d2f02528da091e5280122d837121a32b054e5d59bb3667a95a01b8d37f73d47aa997d31b08f51047f90ea9bfba9e8a28ce5c4c3b308e062a0b92120aebe8b443d6382f83421a2613bfd8db48fecf9060c313c3619bf7ddb4dff44db0cfeda870031e5b50707e01e21cf8bf3bfefbfcdb0218b4f96ec9e8def78ba8e5ca5a94eb8a1ea64a6c45bf42944f7bfe7c8b8e3a99f8627f23fb3f3b825835082536aeb1390d135c355ce3c4b299fe4f601697cb6efe680a38b8b7b81ba8c096a9c7ca44ecddea909b68a56d7a018e1aed574eef09ed44f4ddfec9404c19e362cbb6ec6f479148cd880eb81946f1b50a0b7e7bbd154f20895d85bfbdde8e49289c7a803fc74b7cfc650845323e2a40871d228aac00f0043eedb2496b331981153a218349572d14bac6cba0328604e0d04301734559ee9036712ba7e11a96e85b41c4b4b8c346ba4d04a1b0d02502136b5902a04803b950fa313de0d026b6a5539d0206aa3014dbbd12473bd5af89dbb74251bac3adef63baa08a39d4bd5b897aa70ac58ed11580bea2208a0d985ce82a5e0df99dfdfa3ee06054136ad2bbbbc10f1b065695ad263959c9f51d29fe019e6ff6ee021b936a42585d25ee71c0e3c9babe6126ad0bd714b46bb74691eff21abeeefd0caf6a59f995f10a9e04ee2166c0ea84c6b360eb50197abb33c843a6af6d90aa83f932fd06810b1cb35c3b2cf791e16e8bb301c348cfbf052c218c891139b92e20d4704a085085f0074f9adc99220f79d36f1e2066dcc7dfafa00f25b117584bd38028a35a3b589c28f38ceb6c430351c35d138e63609510d15d7f20945b556697a0a333f660eff6c41e9252483f6793a6b45d8dbf8d5f47ceb225552702dd99e18179381d5695854d485b11337f1e09f492f946c60d846ebbf54a5e2c80f976424acfe5bb978682f4563b260c7e242e0c9c1950b9e95b3eccfc689f9e985cf53b95b7c959838d779baecda6ea7f51b7f404d6b8332ca425dfad1ba7f4bec83198d24572127fd59953363d05617d26bd2fc37f08607db82f9800ce3db769b7437c9ed51efcdc880801b97bb98a005595d0c29e70672c761c5fa31cec122dd8408d3862994072229dd0e5cc6869052e81ae0997c462af0d5ea93017f56fa5c4689626bd075fa54fa4cbc0d4dc2d863289708624173c114a68dc6835928fa2ebbc024a9c775d0cabbcbc24e9d6be1ba3460eb1183ca5375a71129d54f8c0d89f82f508d3b5c9cd9b1623f04907d76e0deae22327b38e1f390059f542a6cd4650fc3518ebe053e7b3f6a71cb20c80dde4df45d51868ff281f91a69e463dad4aa79ef48fe475ead6481f97f5d8c3785058a781c00edd1af9807f73c72f3509962691e49426dd15c5ac91d64a9b57445c412819f882fde81968f0846defef61f4473669bc41f07154a3416d4d04eed4e3cd93353e3448e5025c8acbd6d45c80b2e01ce1e1b52f8086aa41e2cd31cb148edb8205457cb89fef6d4a50a6b5cec237fbbd8ccd9483c174feca8af6f80b22e3b3c6828e049114aeeee01b7c2c109a811259b9c95976cb43c9f1ec13d36a04e1385d2253fb8ce0983fa6dd54933dfc19070ba48b33e44afc47e904ed14bb0d666d510551faad28462455377df3ba0b68d4c132dbbe20c2d62ae1e14ba1e75c3ea2742b32a902e2d1145b25cb4a0005f66021834680da4450c50700e9019f93c023bf2d609f4a723234811bd3125c3114422c497d3247a4f2b605f6cc85dcea86de4dce8d1f4a3b4138341af070a4727857054572c99cef8700188d37f2088072e1fa4b8029c7175b50319f729208d8df47785b67aa00db3a00a7dbd331ac87ea51e1b8e50540c2a9407826fc3c51d9bfcfaf1c4001a356eed516604ba15caaf47a5770cb587ab7a446b3d475b06876ea8a12386e75cd8202df8733946b0f302a52148cac9744c9da62b8a2af351ae6927c72e69bcdf442eb65ff269f8b869525a6e79f129ac5bfd38cedadb6af9d1c94beca6d4c17c3d3304727eb344ca06131cd262ce848d364166998a091751a26b0a4d1641a16d230c9721a26c482934ba38facd4dcbf2b18de6dd20d4a764e8343cd53baa8311f9a66798670113279d567fe14f46ba1ca55b034fa3303f5d30e8e0a8282be5d99dc8ec4c50b1486d15e2601c84579a25a721a0bccb70656f7e9182378bd203f6b2fb382c82522b417abbd4c7fc53507335a26c6b1e36b24d866dacb14d2644f006b6ad8cb8c0c94a17c50020df243692fb3732a49398f9a1c848dc3feadecfd1d6985ee7a3da999650971d109087b997a12492f1e3e104077c0874d622f63b8c1b1a574c141e0ca4c1eb7a680031bf29543947792a17fff94e05d6671a640d469abdcd5873463493b882bfe3642881707ba4524ae5e32dd549cc970196704c49ef056ce12fe9a9f52cef4103d870c53f81425c697435ae01e2e7c35a9ef356c05b497c157c05fcb8fb37910440e4b5153d12309f0f00eda6d2f73c79c337f43e52b39d4e939620ebf10ec8261a69501d071dabacc368df877985fb36974eb75971d2d6086adf3c2f332c6bec24e89b40495ae83eeb0838cf2aa5ce89c7fc95cc1bb36238cb9d498975954443a5458137c12ad109c64f96f96ce088d38eedb4ec300e59470213e2fa32fa1194b76301bbb6296da7f8f791953f10a5f0a69593b5dcef1e665b030e924a8841f278caa2acf705c22129df180abfc5a6194365f6434c530fb26c3a69335cccb5cc330c76b9e3a5907119b6fa76b788ef72ec0af51afa9afbc11116da9220e1410df33a7676150ca7a720936b6fef6219e612e508536397f617b0e84cc3aaf042d92782058bccb072ca615feb692b1141618c69f9749b1c2ff04785a28ea89b79c25c648019f92b12e93f573f5d5df77daee4de6f2c4e749391024abf9c2c92a1b2b03e2416e736465bc0f675556ee89820fc6dd945f22b89691967db79895d76456309afa4d56e7e559e106acdbcfa66070e39c6805989a6f9956fc0e4435e95b2c540e183255a45afebd595c34bb9a5a99a337842541970b6cbfe5a05b0e40106bad646c476c4594d8cdb495e6468c5b516f79b25b5ce00aa349f38d65c5ff8adcafa29cd3732840e04031237a685911715bc4d4e93d1193cfa23076a3a7b0132c4492c63f56e11c8eb6626784cd9dc81114d1beb902d6863bcc6f3628a25b1545e88eb8c5a8f137d81f19e15ca38c55c8bddaa5c3c2c5fdaa3f7d7be5176711ee61db2e1941fdf84258459bd2f1f9b7c3e1abf0a295bd78b0fd5e7010d51c15e1feb06d921b295edf1455e9c94c664ac8eb8b9f348df4de51bf46c157e16a05fc5acb35e3ce1120f286087db21777ecac39419ad264fa50dfb666c8d0993e4a7e7d24104aea75c66b43783b0855a8f1443ab08da9b36130e299307736e429400a085f805734e448e4a80b8b73202102ca6568177f887b653b317992319ee090e914557c2383b530aee5a1bd6c75f0d6c6b4465a247c880cb626a037da905c75bf7a0a266258c8a75a26075210fce8407635193459c561ce5ac401fc608b65f15c306065218866295e5135818406adc84e78ea59f163ed25a1e566a2482348414ccf88b962f0c521b21dedfcf65d210a6da3ef6fddbee3e67957df08539dd19b13dc3f7837f97ff507c6943ffb051fa6883e437218bbb9d81f4e4e37cefdd4673b7be29e458ece7038dcbb22ec0f98f758bd07e5582f30fb9d8e01b6485e2465c5c85f92f71c586c42f71d25ef1113a2def41aec95bc843728bad23291dfb9e4b9193c214133ecd269b762f21cef28281bc86cefb189a6012994e8793bab503479a349d0044da7f0953b72d20ca397463988a6bac9eb0a9331e65ca872317db9e308fa558322d46a1463808a657133939b54e8f3ed3c290e5fbbca0ad7f58b4294b7352b4419b27ba2269d707a24da8707ace94608aa961fed202d3f2e9ef4b965995a057ca8f59e4be345c995e2d11de7583b7b76fd805eb20a540ec087cb42d0a5a18b4d90e8e2eb7490bcb230d3084173ce5a53985810ac7e023ba5392edba452fa89d602b14569e206f32f0bc017c8de6524d7b306cfb87cffaa10bdc2ad63a45df6f1c2930c604fc06c59d410ed5256d17c97996f201b6ef6651f4f999bc60632fad1e9db0db11f0d6c3b204f31d61cc843f48520c5f28b1cf0273089cb55e18490004c81a741687567bf450ba780a8535ce26f3ee56da039e58f9a5bee20f40c439e842ddbdc5fd67bf54f0bbbb75a70dfd3ea58a5c95ff7bb87fcae14fd2f31b2cf5b424870a20317666cf57632c657318a71423c91f727a635de52151cf7772f12fe9d62dc2d2f88ef6ef98e6bc9a41080421f832967f189e07b0781924208f2ba3e194c7525b1f861f9b0fb0672a6cc1e7a6d252efff31574bcef1ae08bc6374da29090ab7607e3cf8c17e1aec9b68b4a259e2fbe128560805003a6db79291dba057dd22a80c3313cfb196e83c05da7937906847211bba8e54e741acb88332e7654829c2672a158a72f2e13f1475b87f8658020ce40a31410f3bb665ea4e8d4847d27a6da53ef8ef98dcdd30fa4269b72c438e5fc6587d7603e356792a856366fc0b20e6375c46cdf3d38a6c4980f0b30245e9075448f2864a61f40ea9ae240bee4818a60ec37e21a23f4b11930607ec9b085393f312e2be860ff1fab67f201e8714a00ece9e89d4de27784dd7169c720a21e178bb6ff5738344c35c032c89b2c1c728f033837675bb994d91df1e1b4574847ca57c165e1b4e459182c2f72f6c9983865bb4228993b8652d48555abc1b368e6e80239e7f55d0d86a376c815127337f347ecb7bfecea3f0d68bf4092df5b78577c8207d7542d667c9a09931b1d86a83e3ecc1ff62c30dee643b46c8500687bd2cad45a21839f63c38df0d946f2f9c1971c06cb172f7106907805125566894b44557823ae9b011bdbab19fee5e3c6e11ce737a27da640b6028e3f8232bafb3654714b945813d4916f4d755d3e25e03bc8d9f36d01f8de8e01cab9045e632e686687281512865faad6351c10a557cc6eabcfd02cc9ba4467e3391a05c0920d94be484aa04685b4dd5d59544844b2e2ca0ea1ad5b4d2a0c8fc11f1df93661f615f444622141cd9b2170467a5cbe912a3e75bb8fab0a20064b45f7756a6f034234187563b6bb2d577b29db808ad40a9502ec1bac79c593b649792ed2b940e89810b791233839e0ff1a5b7ab21c56f8e4048e5304cde8470e1eeb0308aefc213f8c4d0ec43612b8ca3d0949d51f57cd58df209b85049961637c3b18bc6d0471e3431908bdae7e298e4047fcc8b963bb0df8ba5b78697236655fcc4d1258c205bb676570e94f0ed0c4c6973df7ff7d40c452026ae9009d6aed236da9cf10c0da9e37ba9f67cf705b7d3c7134d9e65ffee261a69212d702e6ebfc32579134bb10ad63b5705bba50873f4a4bb519d253f1e849d3f8e74bf96f25ace1bac47aa0afccc85fddc879502c1396e30f5306168e85bd5d16b094fff41e338685b2b46df447c2d2baba22bcd3bd93f1a758283cd5c5b6948ac2e499baa56814a406c3daa5430a864133e14d7781fda4bf2bc5ab03ed965a54ed2481943fc52a0346e5043cc170a53bdfeea92ac95940badc4dcc5c5bd906cd9f4e46a4919eed01d2151c50072a02d51bb787fe933ca5cdbeff6c647db5a0f6f470b35a2e6fe258778c450c92c5bb303af1ecfefb80bffe894dba2287804adece5706f3bd8da6a67de8992dd404683ffe61715b70e696bc2e0be19c2c06af88569932fe70ec687e382009503882b5c68f8dc3ab3fda7619e69aa37daae006066adc7e763cc5f0498461df1eaf1da5fa14a4c2a5e42cff5fdfc3beef50e3828f7d62710e2811b2b583c122451ab92c07ab2f341eb5494b68451afc823b213e4c9462e97aa7cf2f10a75af5de44b3ba2bccc18803d675e2570be0ebce1292aa800a89839cf67582267998ea57f73e09b70228bf8f3cc9a2db9ad1f520c4734f6b6e901e259e4fce4cc64c0f321b5dd86e4c28a1709f0306583812cf0c07fc137d7515e95a95f32051771641a8f8b8e946335b992668dc5fd02a7a06d9cfc44f9696e702a7d014646fb7a7b3f1eb35e49339bea7443902a36f066690bc8c352c949c3c54f21f1730cbe2e904228cc7d66d067ae7be4cf1d928f00eb9cc1e6d4d334bdae4345c25735491b20a6b8694491038b1d9815ebb90e00851115b987fc4823d2761281ceaac42b15232a513cecd56c450b05c7ec0c746bcca005eeb4d05b164a3e3c639188657058ba535445fa4e84d9c759f73764d2e47ead9860f6c91c2e2bc741ad61b0b366d22c547b1a6ce230dc6e4754af9009de310c611084baacd0e66989fca25e9a9b2714895d19209c0488300dd6d2e21a79437523e1cfcd5dd421df05e95fae3953fe3222fa11ee3a38320909fe44b072dd525161a8488e6ce59819c65361899b058fd11e0038eba548aa1206e0d10ed77aca49c3facad615bc108871ae13dae2e45d8a31eb47ba8bafc3748f2dab22c9965d656d9cb36e56cf40821d9e23923e89d968a48ea17354e21085e3de4a8460c8828c2a5632a20049f4272da22e636ac4abf5975f5eafb4b344b4f59ea9070b3cdd01e4f42857306d8929f046b3654064554c5dbe928d2e4a63e6dcc0d7d3ccb267df36c8553163fa3981852f6f5804bc22c581a97ddde395f864f386f91ec8608bd82ba979d5bc2106c7d8be35bf06ccce21bf1bb31b5df0fc5c462f896f0d453b184a2ed86794ee4ae338b9f0a3a572310066a40210204a96643255b03b1a29fd0904224ed2140bdabed7456de88b95fdbd4a06da65875a11bff4a068474d2ed6f304efb23645b98e71ee16260fc0e7b570c3b0d6e39148d812da645055751fbbecc57d82f5fe6e5b7284692c9318cadab108495a01f0f07faba2dc9f64b713b51a1b2512c92951bd9af78f4808b152bb9e89a826c5e056c43e4e03bc684028d763c6c60d2598ffb35371b08384cc9615245616febe8d1f0e7011d260352dafe16b5b837a02376afdf411f207023f7d19a23c02b8717d0c1ad2ee1242d165244bb181631e4e0551f0837e7f4c860a89d2fc0fd5f41a0b715a0ad34830ba2e51014420718215ae3d9df6156b441ddcfec5030d14b192dbb6facb02558a63846b692ee5e91c171c25e927fd644aff5a3725355fd0d2381251c864612f19b56b73e5d08f306537f0d3b652c717dc001848b9da20f38c223ecde262d8274f077c2f61ce03fb9a9df1c00c0ece437e028b9b77836e3a782334ebb23ee9413d23c1c938f42c542db34e018ad5eeb510ca7729013f81d0ba305f88ce26e6f007bd07a5f21efc484b32a8ba76b98e40264e0bb6c3c314ca457d4e137c61bedeeb5aea062051c191eef31027c85aaf5fbf7a268b67412ae70bed65e1c918e6a9a03b8ebdcc2c157600465831605cd608d8be9f6249407480de003294a070dcb7a9f43a7e9230d46552025b0997d3f88eab21ad070dcc65998e540cce0719eff415f6264d8440bc66797579dd9bb7ef96dfa446160ee4f69f4046aa859bfbbe9c27a37fbcce37caa3f2b8e825885c5cff8effae9dbf4d12a2b1c19652da3181b821d1efa3c75f05491d7b82ce038e04505f0f8f33fa58c86d269420753b843ff6ee59c259cbea10dd0124b7594473e0edd4e86c79f8723c4558dc9d9382a02c781cfd4dff31d8e52569545d6440128d47cda336ec29c4a3d76796ba20d5df761c9443ad2a0d1b10dee0f1d7a1f9bdc67e1e20816461bd8558d7b80d88d7d367fab10cfbca25ce2855e27b380dd0f297a29e2142c1d0e97bd0d8c34cce68dad20c79dd5b1a9cc5b28c8ecc708bbd9def7e0f4a1c636229a106e8d66a6e1d2e210972f7c63c16d0348090b02f191f771ee755c916f7c77c8f0777e48dc78f552671e45eea24ae762fa3cff8cf9f06700f954a1912e03bb557039502f00b4bf432ae8ec78b78a2514acb5935805f0a29b12c1422d03cb20f9a26047ed12cb95391c14645c7bbc6aec54842eb6e6071403e316c1d308587e6c86121625c7544285df1406c2f29e631543e4073c41c68aaf74cc0d395c0fd21db9d2a3b1861c77be3227d333f10cf6a7bf62814549c7015eb5818bf04b8bd8903e7e5772428e50a4294c92eca612728c1ee64a0ddeec6b54b2d2724a0c7f6ee2003277aa7c841c6686347aa42a53c16cf1d416e2aa55506361469e3bb0903043727f8109fefa31beedd767e0192ee412606ebd1996fac58d10460ef70bc48a26ea89c72b0460865e04dcdef4ab763bdd7070b182464e98f19cc58b92958675e0d2fb6324ee26aebcc6847866465870976c249116f53c98d1f8ef19c12e3499ca08f7f873789932256e1096ad9ae94f75f266a9bafec1a3a41b4a0a949978289befd67f25683a3ed65935fed7b823ad88272abdd793f5a592d0e5caad0e8603223edf4e1c7284145d742a920107d8893835b10021df40025b8afccc79ce3f210b274145ea3c279ee3b2c418fbd16a3b3c47b330bf1b65cbbea95d2a3c47fd0aadb3bcb2a7a06556c70e0a6c1cc0e515e56ba0a9fdef1cd87fb492bc0c8010d8d144d497cc1c1bed103a110d3bb273be956a22266e98f88d90739637e0c4a1866a0024f5da04c6162ed1fc137e72de860405e81ef99d708a6b3edaf1281dba010647953e8a0b9d6b78e94514c2f2f94b07ce782e4a85c9604aef884684f77d9f5e1e8fef8fb017962e90d25096f41a222dd1a3b14c496debe099b437b05f1e0eee060eb78f1211d04aa51c611b81af462959dc36aef1d5de0481e9ed424d0409491da0e052f8cb7ce9591c47c22ad69523e602ced89bfa48d96539fd45581438dd115d07aa42bdc05e2d0fe03e5dda21ab37cde094755db6f5852f452c3e054d7c038823c3a68f10c09aff05d4ef6b145407a03247ca1562c64b42928a25f91c7406249105307962b5c49ec181f4da456bd61af165aa5d10e5944933a9768088302ed22ab9d1ec174d96c9c3d7a743560dc6acb167b160ab3cf82b17ef72613a8823c10f966145c71d72845006e0ca50542f4315203d97e93812cbecb4fd89e38889101abe8cd21619b256bda7ec84b81c2ae784e9bf151357c30e85074e2ccb003a2d5b99368e3ce4195fe470bfd10164fffd85830dae84244aa3b50e5bb2b307261a74b0d159708a250947fcb5ebeed1cf3d7f2deec37bc1e64480b8b79743885e9e38faea15aa061cf1a1ebcb907d2bf51f913051b67bab6d2007181ca2e09ffde0adfab4e56e6fa5b916326459ca963f5f793fc3d220dd3ce1cfda6a984ee8719792c9486d21421ddd260b7cdf3c5b031cc2090c595c18c84441b75263710a84fd42a05b0b56449ffa1bf7381427f69a75a1f2cea5ac1129464778421f98cc9240d1425edeea2d7d0d013dfaef6e9a6fd98868dcbadbdf450b9aedab8f916b66d57d3b41ad8de75b23ff938ed84d0eaa665787b800450603a8b5a6e59c4d8ccf3c0657dee1612a584f702c80861f67a57a67ffb7cab12a1520ec2d41b4b74eada2ac881a235fc2a5ea2b1f9422d945cf459a5a3cb8d119df56c5d02910010a0bfa13ab9e4bae9a75253bb7af30c22b96497908957746104982edd0137ae413910e39788cd839910e31427a4116e123ddc7c8166d10e3975a32dc8fb6cc29b2332aaaa889c3b7f14271b0fbcb1c04dd53023b717e97e494f2a9e5f08e096edf993347f2ff0f9152ce6c43a4d459554ec72761b38f7a7372dd2d80d2d8a9d73bb3c69f217bd6c61d949b6b35bffb9dc58b75068b0707dc6f37ee7207ac491bf057cea94ead82f7c167aec719463e2e18f978f5a892c60e91e951da2d9eaa7900a7b13501d1cf27b2d1c466ea967ab04a2f257d16ac43ea130f2c560fd54cd084370d7e88fcf204d66a20c7424ad5f2ba007ceb6826c21235adb51f85c4faefc0173151689aff883f9fc31ebd2fc5efeaf5db4267ec83ab80804cce1933a13013228f6e217e8add79db589b570001998ce85673a1998780be7ae8642db5330d4db58999ab142a6bc91200b09f6533bbc9b41bd3a230a5e1821809eb77613847347d7fe52446c639a37c5ca44d5cd8f18bcc24db7249dda7d488655f41cb688fb541e4599ed1878f93dbe858c9e017594eabedab8989e85c30b106e40cff524b1f43c6373c261e5587f7cc7370a8aa8245e19b173bb8be31c12d0a65adf5517c968484e347ab10e0eb51a0ac5e9a2e082d37f874daaf40fedd4833ca334cea0eb1868763eadd0eae8aa2c4a34b0ae6e871252291585d7e2ef11d3e0aef58447b2bf3117981db2292e1df34a159e631575f4942585d8c0df4bae8f90de0725e07dfbee3ce33b7ea5a81d93d64afc5e9276a846bda1ee44e176871aea1fc3ccdfdf9a0902f469330c3fda8bc3e1835464127c5f6c3ea597b8a9d4a86cee83a3f6741117893a3a9742e9b54b72f7f017979134640735672ad5cf68e05ab3935fcd7f4a68478531d53684857e80c2738a5e9a91cdc725252a30f04b03bedc69eae2da6887dfd38eab537ce2f01b1ed68a28ec61365df5de976970983e55c4a9bbaf0a06f371f691a8f0e76c7ba4f8ac82889cd262a7baf6a753598b15999466957799af6b86d6c6976662c3648623aa3ea4061baf5ae3e6b3022367405f8a773c5789f3b38e459bec2b8d4c78dfa11d9dbb642c824905e3f88817adc48c4468b18a67671550bf9adb1c4cfc55c282c12ed9b09e4af2083e81e91a4959eb746c5019b087690eb7bd6b05ee9cb3c928da52121245de083cc8fe66823ed3d89d9fa11d7923d7fa6ea4a182a79367bf2fe279370d7453c2a0d24663d5f8eba74afa994656e50909cba6a11bde747773d4541043fa0c7c159702d5d65c16b1fd0eb5b73fa717676958971b102610d779d4ecea07eadd23cb8f11e00ae78bfb323c565cdf14640931e30ee67c4368c1ab7eaef3a3865d8c303066d9882b3241554fffd70a3553023b39638bdd881fc55cca09ea55736c518d4dfd519bc9f018d290348835f06eea85984fba211a6a4b11134f4117d0e848f44afbc675e6af1cdda3dac026adb973a49d379380744767f1d4369001e5d68e39c962b4d4d67025c5eaca270d7879f0b4245f5029f28523e0dc628c7f03da68ba20fab0468a1d2c386206cf8b9724bfffffa11274e9d5daf0589c42ae187f46861241e43b83f524a051bd94c585cff5716ab637291e910c5a28735643a52b56b6cb9431bd104a360f164aa9f4434f54b444df935a7fe3af1e13b22e42d801b48167a3ca76c205901eb86927d51ca1212e0a648b074c9682fbf74a9d169d565071d66423aead2a339176875f98c5bd85abb4b9773ced3419ab11ec3e2cfb40b97f6e35b76a29bcdda27099a8df880b1150b0e361e82a2669379033fbff953bfd6963243308b788dd010a0b132b54c93e424d1dad11a3645a86fe3ff1beb5c197a23416bf173ad14ed848712f78782ea43e63ed89ee05e6b110497c6415bba43afda65d8ebcdb309af509ede742ec0d3330a068eaf2023192f02dc3f44b7d3e290156d3910481f112a3f9b6ca4017881633d34d6a13ca35d812f1700bfcccf2f2e21a6fc31d2e88c26a6d9b9bf44207197fca3c34fae97f1a662d0d7f0c4efd298c330ce2c77c248904178df04694c3a930b91a8367fab83f27f9028a87772417b4c6259c4a09534793b12cb1f71fd9c35b9c606e1e6971c997fe6515a7d37ac75c3847fc8f88d8f50ac6ce228283264c9273455f420f201ddb6e408f2d475f710e7cddfa84088430e0c83ba3ad4bbaf3db17a0b02c4f6f91dcdc9f67a4f40cb7e64ba646dc6de0297bcd01b8512301046fe2258d882618ff39c8ef3a73f5cb8ffb002322d528b7c48a563391c5a91fbfa9aeb881f953673cab3eb69bd66496fc194ac6963d5e40715c07813aeba28080aae1989076424e028984d07eb70ba2bcd1b3ac71a07176ec03f2097c4f11833af49e87ac39419ab4db4aa1bd74095901b3bf22f20f914bef8230769eb0c2be2870b0a0a4625dfc7623e246aa5a821586eb8d390a2c2a99f7147456b3729a468ef435e6a405a948e41889022f9ad7dd1a0f498183d825d3348d9150f9a0ee3eee5831909522cef64c26eba4db4cef52e2290b03b65a6f0d685732d482aea397e5a58121fbebfea84ae7551f932b9d95f0cd18f28b8714047a20870de5dafef6aefe265d31bc7119456aa1cf76bdfd6b2da47da0847554ceecc3030a6c33b5997528e4fe7c9036780cc754cdb3b52e5a86fa9135d61f0940ee36db9c9d665e0893ddcfe71d03d3f0b7d1109ef9ddeee35c7cd58f83fc1f882acc38a99aa90a8e163c779f1cb938eb8eff2a7075df85825d28d768f66eb87cf6937a86a1babb5ea021d269775edc4fd3a5f8fd928d2decf81de734bff9ce5e5ed2a7dace1b698e2038a9b182ab947a7f147032bcf6fbb4f378be307a235167d9d46054a944c4f550a13b689b2ea3c60aaad88559f682c336fd11c70764609624da5abea892c1954a07d3a4c5121f2c8e95131eda5726af65e83410f544dc550ad2b2d70226c56b1e31af684533ce63267a067b03a5fa9a883d524ede9f098c2c5fea28ced50b81349c8b1b775cb7baea4f2880f6a2597ec912b3b9a8956ee96713866a23183f33ec0373049d4afbb78ead371e14f13640f285c837dd68b3c59b370031ac462dacf62c8c697ce1b426ddd2e7189018bfd68f8fc2ca91817906249abc98e745a41638e10ca4b0336767465c56932b4f6e401c80f3bb90f3c87b6b6cf0be098c19591eb09ae2dd5a27080b4b8471d4f7d7414767d5f87de5285061c8603990f85cdce0f51b08c1ed412eddc06a5280d039bae723e730a5d2a305be9661224d3073d7fd081cee19a4299979cb8b0e0e628788fd865d1141438349929dff5f1012a2384907b32180e20e5c4d4d08dbf4b53c7db27f7823b92f66fae137c38093ef536db7caf7a693bfa85e70cd0eca1ed54583e92c7dfeabeb24cb86a8a9b15e755897bbf4f4ab2a5ea728c759d7bc981f32ea74c343583f9f033190758343afd1d42cb03a29728ebf210faa338463232298361d9fa35c7d13357de3de897f241376a9fd90efca06201298930f577b1c2197db9444e83b9ade5f202183de8a9bbdfa75e5b8777a682e2b3d4cb1f141aadc6562fdc1da148567a5615be6950b19cf8cefa69b5f53a3cf5b38e5c83b537a81211919366f4cac28d94412d1fcd81592d72196ff8e460adc6a2681a46f9e208b256c7fad159ac9943e74fd2b27a6a46c8ac6d316cb82079b8f9a4ca5ab4151da9539b332d423bb25d8e08eff1b37250f0f498adbd572619b78299a253b088f3e5a88b77050062c10ad1decae70ed0285e09f4308a01ad4aced9f61a53d17be70f792b6ad475d535487a5be8284ff5c39951687d0411ab96bfb8e073c875ea45d904673894b7602de62109dc33b7960471cf6da48e100613b515011222f8e598d01c21d2d7191dee29a7011627d378cdab2a94e7fdb4e71324f498cb0bbf3f0370949f0ed09a465a62f4f64f767d88543cca617adc8a4e874c586afbe9348b5951e0a8cf84bda5d5e920dedd6658b2f2fb3a2d776cd070e740f58dc94e8b196853ec21edd113d8b19d966ae0b49bb0a1a95a6f2067773a40dd3656f183f31681567326804ac809ad797c57c8d9bcef327aa297c9d72a308a7ff608a1dc35e6c2938df38d8d3db4b5fb16ea773a601b86c8eb425a87a0342ae111defa84a0a0fde144355eecfa7323b7e71cc353004eae3f48ff01bd90419c04c99e2217a1803d1933a6a71114d451aea169a08b8fd2c5f462a2a8512e5513b28f348381fa1f0fa731e6227221d24853d7d0431af2550f228902cac41237f5195bf50ec6a063d6bc5c8476b302a042d74b812d4b316d95f582dba8697b009e4f12ec231f35948856d609cd21c2923e7c6c41b8eccda152f5936119cc956669746a714fbe1d0261b73cb6ec27470d19e62993f3cb90479430d41b6c48da15a4976981ea4c6ac9a4ee990e74457f837f182140d127be12bab170f4e48204df8c36798acadb4b0dbc841a3f1c0923ab831479da02b9ba70bc6e83b45a785e6e4352858fb54c462d5c2f5a27a11a466fed93822a2af09bb7e0246b6fff73179354f231b039a224853d22cc520189364c1e2b67b149149fb94d28b66b5e6e04513271532bc9be8ce92a6446b89dfcc71c9ed328d188af2a531e18efd9aa28e8c92001639d53792163c96a0a125ca822a7d563100a302cdbf6551b1070aff09b91dcadf57c02f1f50ce88517412d4e06c4b4470d1cda4ad539ca7c0cefb7fb178df9056464285b2cf75bbf3894068fca9d17e2cbbb47941efad78832a31fa6476afddbcfd5dce1587d60b1297e49068304baebbc60fe8a8dc6f33af8c6ee5e764b3a13e3ce71715c1a23452e82346ce0219263fd1f43103f8b34cfe29debc2bdcd31330cdafdebb852a31e4c0d0019e6e05e36ef255ca65eeef9f0878090019727db1e9570304c109870d9b18e92c79ac16d1defb87edd95789167ae10522d2bdce3accb115ded55c96a0be802ead0492a7bf8346a4fa116e56725a1b9d216f8ed3db0e6fac942894b6cf13bbc493e157def8770f197e3eda5de62afbaac6b9c605fa188aed34240f6c3b9fee357c384aa0d00ac4ed4ba3865f608eaf283d5ddfe500e28f04085f748d669a64abd8f0ba0b2295f17d7f7e0d2ad8a894e45322b636f283d8c1f0c7f21be6905e5124ca5b5b91abaff7a485e533eef37c47eaddbbfad1c1de7a2fdfcb3bd47d538f6cd9707999c9a129be54a9ca062453f403ce1893671408ac4747ed5767f7c2ad3c476482732d927e65a8f959d2a3a8f7dd26111ac212af1f7e0d7b3165504ef6320310e6f822d10d72252d5676e9f75a439c8017c4a78b394d85f1ddbf15fcf8cc7433162806c2ee48812123f090a8d1d831db465c69e1a4cfd15417c65df6668004f0e892d685406d9187be9ccad45ab218c5c5320f0153a36a25e463d55ce82055e2ca70208d40643a0475621c1215388b4f2d29d88a93d7a6a1128e0c98223a8161e1f4a936ce9fdf13131694d33fb0b86f7ecd224e0ab9c7fb8b450102bb0d8dec9f9317c4afd1607cb4fa4162e72ccd868303a6a6778548d2217a4a43dca935a45d310d05b863cdd475a148821b568a76bf2861b7d9cf92b6a6f1d23ac125993dccbbe119c7fc821ad84cbf9bd57386cf7ae49ff97e6a741bcb5564409f8a89540d5451068e3e2c93997dab8c150b78e80b3e5f48ba4810a02c6d661342a70b1e53dd56caba6523b515e2c3193a9f72d2843d2cc26226385a6cfdd65f0768bbf0dd1bfc7db16b56a83761979bb852dd6a1b14cde6d51426b682e23ef5b30ab84991741702b80d579dadb648d53d72b328f4d228ed19649da107be1ad17d3fdf181af095f7d41f07005ed01423efe0379c088d493824680e09f1364f66dd08e89706f6cf1460dae0bd863025de417a48872d16786e8014f0516c6c9ef0093e101e60d1aee21bb16de53ba5ebfcfca9e450df4cf1657ed50d9262a2e875e0e8138ae0b6a969febfe487810d6a758ede6b4ddf262828f57908e230795e7055157b34d408ef1af9b1881450005b7f12799bc487cc0408201e408e57516425d3fe057a2d650190e9e964e7b03d31c8c706904ed297a79c08c46659165305b9e1f3effc9fa171fb0a7313652630c06cb9a775166c067ccc79370b627452bd4f669b90786eee4ebc0c1072b87acb20a14bab70d2a6dfa3ddcfa2e9dc9f016c746133b06e8f7a04c64d807961490e51f4de8a1aa228a31196f27f4ad3392495fc18538d00021c412749fd724d61a6f1c63332ea8651ce8e085bed6850dedff8643bea03864d376565856b6b815ceffee3e1e90ffaa81aad988922e8b5b5fcef4ec39b4148592126cae194b66c27edd421946a614ac9482a08eddf2399ac610430c902192002741cc33f5491770fef460aa971815d5e4ea706213aec1591ca89e9f694729ed600d300fccba63dee126b69543ae57f9962e3645502cb6808ebf36d495001a4c7824b8db67115aa3647c4ffa2a57753f443a0d604437bca7def36849c85b5a1b2deac569dd83a3b0e2c66e32fcfd0453f19ddfef417b1a298fe82293dbf8f1923e6007e9a02f113cd11cc7a2218493dbe509c8c7a715a16f012822bb82429f3061b98d57a07e3268f413ea21e5d99b4fe812fed56d89b041d92e420efec1bbef51cdc1321a1de1b4ce29aa1ed64822d52320e0a4318079ddf74230f929db67e39b29287131ef256dc4d600ddf9e6bb5907ed4bac1478374bef38907970b9a8a57f85daa3a7c9e6d330613405a5bf87608ef744c8a7c0ca83035607c6fd1c50df41d35c1d3c40ab01bb14c3012065e9feb86b8c74873fecaffbd7092f85f5e5c57e1cde2b72a60dede0227da67f62ab9d0045b3a3d01281e6f09856066a59a3353d371ce52a45a1a4890244cde40096d26f933d38004c5df5b7e25fa11b8dddc4a30187b18ea2a6ca84d34cf61ff5ec60668111581534c633f08b8f37fa16faf56428eb6259556fb582b456c5be7506ce5a3d19ec43f6cc5697e419304da8bdfa372daf8fcc0089f421d5ffc0681c1fc37718de9081ff5f3204b34bc3e84aad09f7fa759bd9c0d29037c99560b9a307710a8a29b251a53e62a417157c9c71c0c1d468cca51d03b9f51c5763bcb05e2e52f19372814a33f43da085d85cd7ea4abde2357e5d5a614e32484a1d497353bd2b8dfcb3653a9633d87648660bd10409d33eb27b6e51adb2b8d459805dc74daff73431062f4b056efcde442bfc159ba713e5e6ec848112bd8453d1f57b6422e7844dfb9eb9ee4032317795eb0e9251a13dabc55c80d370c48bea8088d31f68136bc7e1ddbc21f5e2dccea4898267ecae494da99762c3deb384824294b4f8d9ba7bdf17f5d619bd3add91879e60aececdd2da236491cd84e55ddea3b01d3ecce9e06ca793c3383cf163bd533fb890f8abed9a51d3bad1cc9699fd23487bb84caf4c888510e5f1232d790568839a976cce9695d29e86480e60e9f98ca2e7eac26855a179978fbb5cf9d35c09d89f5a6597c1535b801f1dd83667784a1a5b271743419a3ea8fe7a89bb6e1d8693ca1750d3474c77f1756d3baa819560204783b10c951b0a0d2ad6db23bb5809897879bd2e1798d806971d3d1c61a94effb7736ad7bf1fe1168d32a08216d1be6d9e184b51079b1a5ac70c68dc42104869bdc0e416137da1e88eb8055893955aab9fa0d364b855ab6352d6a2b56a54369c303100ebd30c5e0a028abeed97dc501498b34e5b24ce0249995a32c9dacda26a2d225d06bf62a10cf67763ce3767213e59fe39ddc168b22bb046ccf2de7aa668868aa81fd484c546097e12a4c4c70907dbec9599b08e1d9062a53898883aeedd7a98b8f248f055f9ffe45a43de8d0d236c89c19a054211121c260e47b32bbf085a5fe7afc5fef6146f8e71d22f376993604824baf867ddbe2c1dd491fa5f41b49736227031f9bad0caf6f15c3613a0e938eb4a15b9469ecc02c549c2b7908a95959af747a6d725bfbc605788184358c4243cd16245205586359970e9175e5179c3d077d52a7b8581729e0f7d17b7cf193591ae27ef3bbb1517fde9df1ce5543431123a7a45fdf2273b6ab44ce076ffd4ced7f8661a886d45f7afa32be6401b1e9c0e68d8d5174191126cc89d415fb7ccb6a8764e7ad21b52e09511afdbfd064750a86de6f16899dbe80a630d2c2b21749204726c68dd1d1a264a8ff9887f42a0cbf6e798b3f831b2520bafab3e514452fc99a12883addf3f06a30f574a8204016da12fd00fb524ec79ab247f9ddd857fd41bb62a52e15a85a552303f237c065af18d9299de9d34f8ccef424e667a86cb5d26e54068ce60057935041d37ef90279d8e77db9df238923d465ec9a451d4ad49c94d34b5d1af52132cd2879c86b6e0e09d88fdd4c79b983a7e3f1dc0c1bde04551a3a0431aa401ef23dc6923902f87880dad4aa23dc399b735c5a2863f92d8e69d9b4a883b27931221111cb0f8e28f1c9cc08baa1042d406010aa894399a829327f94bb9d98d1a420ea7f812453cb1086a0dde1e60349ecfea9b8ce464125a4c5a2f2b3f5fae54ea2def977477ae7243a5a3c1afb73608bb4b64607d14ff50a059048660d53ec71d55f072aba0b830999048b057f8954a76e46eb4a1fb01629185eca4bd7ea4c4da27cda8a786c6a7745d28c732164981e8fc8b49d9fc41d244ae594ddc6e828bddbd6ec452ebfa72ea25def11e02dd39ab30614a72954b298e1143bf409f9b3952253b51d242a786fbe655fafee859a783c1491f22eb4798579129390d7aa5e51b861c467c19a62bb1086b44ed4798618aceace778bac4921b7d81041bb386aebd5c8bdf2bf163aaf4566278f0f17a6b276c0ccf355ff130e0ffd58da2adada8b65c36bf529d2cdaca694b798dbddb2b42e8fd6f8eb4179747b55db8e841e78f3fa309beb4cf5bc21d23d35ff2c3ce498b46854c40fe60ed3e2526e7642659e33f8db99c91b027f46d76bd14a327efabd85ca9b8c230f0f381000ae12c7bd02b6a0437d85cc7285ef0b49f17a4cf0eb735ac8f32b846944e3c91f947528d6e8651be710c6743587c25ef141092798b32cb5425730cb944c2cbcfb98bca1b1133486e205cc5d53a20d27fc59ddf9de35ceb405a8485508c624650e059e3bf40bae7e85e570ef5da31bdac6cd3a9fa64f117fbce10abd343ef777f58b2c7cadfc673f5ca79d597e2afe3d222b2ccf31e0b34fe67e6ddf8f517ef22870fed5a915874a342d498d548cbee214c7a037d90c3de8300e49c62b3500b049354f0f3c3bfe561dbb68fb0d8d548495a28b41fae62cc153aa194046dbec7806e1156dd7893aae5aef4a5c9be3baa29da92fffea35869d5c7ed8665a1748bc2721ed9860e65e32d63df71482eab890b4ea8e5950011395829faf2449e66d66c2bf166501544aa8659c317d6b9ab137e202ba0f5088554a4f36bd576282eab261ece086ca3221459138e492e42412fdd72417af58a65ba479bc5acc5bd1a929254ed9164b43b80259037c4e2f9182e3aadc959428f6e2ca0081da2f4054b5d44ba784c06190e81170367788166092957c4e647ff4f981638e5ae11410498b11d2ac0e0c2206112b81dfebe057f79e0b80d9544e6285492b3640d80d1b533a71aa0e11c6c2922cf59005bfcb0c0e667b255d9aa31a3d8c8f9ef612d13213c9616e0c67f5f7d25aa13efe8f97a652c86129978d1e3ce6d7b0b9ea2087098d1dc8dc0e02f3a7c5cfff8578eaea64f754e61df41bf774e44721580858a74b45cdc6c262d31078396401efd00d2d66608e59d245db467c8fcfc8761ebe238cf998f312319e2ff9f3d575c7e22100ecff7304a1ab5979232d06d015d5906b1e82dad6fcc7dcbe66e4d0fce011d81a766b700605e014fc38140f5d4060f9458fe618503509006ff174073a43c0e853cfd24f99c2b8a5e5a7a8dd26e481088790e135235d029b952c62c040b23b7ff709d4112bc52c94f9652a38b51515852bb24b386a8277b05915a90bf1e0370e873c4a2a02d218e8e0b94b9879ef06905a3a34d0c6dc4ccd549446ba11acf9c1008087ddcee77926d8c048a146fd12ed2b770b494d4f02d384928caca3569725ba305965adde2b4b011f74ffd4248c1ad411ff53ade707ea707b70861a94717d13e7863e6533ff67deb462c91340f6390f29c1db16edd386183064cd9ee5100252252a7d300dca921181b2b7cb059efee3a128e21706847c496155cc2aa7e16ef3d621825dd1e6e37bba2518d846dc18d01f8996565f622519bf2074c0c388e08628e81e8b33f81a4a17d5a3cea374dda3b4707f15c4115734f38adac72d9af245d8af7081faeda8bb0b3086b8487243941b9741c00366bf85203b027b1063ac55946642c9209f1149602ef2b815d044c90050e6a85eb11561058aa4c3a74016796193ab7fe616ff8f5f757ff0ccbf5c765a54221db0636feade2fa9212b4a564dd526e514b4248da9bc84725d8b208e308b55fc72d0922c24314828611fb1c61d45627c05ae4de6e018408d93b051f11bb10a611b5b446b17fc2b93e05692c56e27991bc5e51362cf1d0eb15e585a20dda8269d5fb57eb31eae15311d350b4446a39fc1998290d437972df862576f2c47f161564a5bb9d3cf16ecadaec19975188829a74fbcff2fdb03e14edeb509450ac892ca7ac55b2335a952d12c509023d0048aabb39850ca1250408429ce8aef653362cf1749c8ff6f5bbf19f65212904d941772be956102dddfd4a633a5d2d46c1276d5842d1ac28665c698e284eda14f83310539a6e05290ae202d206101a0079019030802400c80dddbd23e430743c3c3c393c3d4ec3278a13fcfca9689e54f47ee831ebaed3e1dcf8677a695e74e45f9b5ca7c3e1fc1bbeeb74383d374dae3324e430741e9437621af5656b9f3e17c5094ec7b59f721b9638d79fe59268284cba2f24f4a5943f029f09beff758a4338592c6f84c06782bd2121f09978d956efda9837858f327e190d314871a537573d994b72862752b6f67a9f67c650787c42283e3f4e24a87452f561f539c050d3ebf57a8d6055502924880f52983482523d560475aba74cea564f4e164b9cafd7c26e3cd38ca3ba4a5443b77878449ce9f8b45a5a295197a9877f6db5331a9eb39c6ef1a4b0e3811db1a13cf13c777c7428e8a874974bebe1b59e239265e89366ea39f7a9beca2a9db3aba1f3a10386ad987ede4fcdfcf92cd3a9996be124ea964e4c4749ce1072b43494279e9313d26ac9f26136e778ce9df86d9803e628e5e4e4fa38ba9533030d3f5a30979316b34868480287f5a15a340069284f727eb8f1430a284f3c630b7a8e93271dc625266dc61e7ec65f4af9fb4d7ec4ece816ce173851e0283594277e9444fbeab7b4323a5aeb78d2a6877396a45b373118d2ad1b296ea27cb8d1509e78d51c3d479c6506a7e764b1c4e1e79785a5dfcff3afcc31edbae5e38aeece47141f26743511cfd90a6660c00c524e9e74a34fbfd6419ae76873392b0bdd9a210619c668284f6495d626e94e9e7438df084a85f5a13e97558acbfa39666feabc4f73fc9fa4bb57e8960c52c890bba13cc999e13973274fc879b3d739d2298250323cf9744b069f31a020062a31dc74f7e13c7ef29c30c9736ebebf659ed3659552cd99d732568633191d897ca2388d3e9fb5f41af4e8160c1d8041cc79519ca4e784d49d3ce94471823ea930adf3061860d87a3ca087548f189b0a74f7926ed97cdd0de58987d4739c3ce9c20fdf5a99d744d884d94b3aa6f5a9eee73aeb7d2a2b737196752e79173e49da1bd64418c5d892feb38c4b7b5f566788934bfb23cd36fbb5f492e1fc3bbd52bfa965548769a573fcb026e2d252aabfa4d7608ec12992f7e9e712e68fbd76bf94e2e167f0c9976ed9d4e06a7477cc7a6dd21cd31c3c69e15fdad32dbfa2bbbbfa0ff3a79a33c7515ee5dedddd8fc54a183849ec31eb53e0147d6272ceee3b7957a9e3d8db7d476ffc4b8ca9c593e67f77baf5c2190fd6bf1ffbbbd4ad17b2747787a7087ee836cfc03a75fe1a75b78f6ebdf075773f8b348683ba175ab93e16ea960b5d74be4f55e9f7f174cb852c1d9e1adddaec78ce94bae542d0b5311aa3b97e48bdbad02d175add9dacd26b634ddd8dd4ad16b6e8c2cf7fc92faa05265a40ea70c29f65fc97ac7a7f58a69dacfb9bfa5454d642eb2b1defe70ffd61d847148f2dbabb3b5c4ec773e6b570fe15a767ea0f568a9d3aa65ea45b3caa6a6963feb09967582d89ff5e0bfa1375b753b7787857ad0d31a5790d4714e78d579af56cebfd4a63a18bee6e0ab422be8f4b17459af1c5e5041fe6e1675beb04a7402bd6d27f16c5025597ff47cfb65e9b27cdb4867492e18d12aab2454343a2388d9e5eaf21198d556b846b508ea74709098da0141da9d2d010ce8dd0084a0d8da09450931194f2f929878886946816cba5f0990855d9a28c3f5441062a64e970a66eb38775d29f659ba730d10e2bbabba95b3b7468756b07530a6e74f797e20f31acc45e13f39dfe349b1dbfacce19b86424e5b53aef94fd755c7aedbeac5ad16dfd4b4b29ff944200ba5b48b752b84147b75228eaeea76ea1a04677385f4b9225f6cf75baf061f482f8439755fa30c73466ef2ce709d25a8ea305fd61778a25e853146dfee9160a3014604ef8c1094e9c4034d3ad13804c300348b74cd8d2dd5da61eb3fee3478b7f10a996da5c13f1a4d57e645b7f387e1b3aa633b18c61fc20586227f21792a826a6eec339566b6bf83f35f3387d2c475a35c79f658c5fa4b35ced6772fc9ca7e34cbdd2877d5573b44c5d4d1427cd779235b15abfd66be2e717c9d1821e82fec9f0f8f9fd59fe19cd6535acd4c3ffb03ed57b25ea3cc773c459a73f92eebea15b259c20a65b2584a004b2841d78cec05b924b465239b53a3d463d47e7b31dffea7453985ecff3615e9ba328529a576b7f963da7ce59a6d78e1e4bf2cefbfcdad84c56dafa6eb393a5e7e4e816094274f7ec16093732748b84956b4b7f588a60a598d218cdb6faf4f04edab5b19895d1bf4f65b1ff2cdf8fe13a6f5477c374cbd5d3dd50dd6ac9a0bbbd5bad0d74f754b75a50ad5777c3baa5630c1d3f7437d82d1d2f74b70dddcaa1821c5474b754b77238757754b770b871d42d1c58745e9be40ca4339a759b2b7de974feb0da5cf2ee3f56b3d9c3c7a5d7c0faf796a0ff2c8f9fa9677c69ac3ecc6d769dcee7274c02893eafd363d689fcf5d5eea4208d7d7dd8e73add4d8f94cdee61fd0f07a7eb6e9c3c27d7bfd3e9c647eaf387794d142748c95a7651a44dbc9be5aacfd51ef9ebcbf8c3cf75ba2523a912fcaaf7b04edabc61cc9213fcaf8ea7d79f9a5914674ef8b52c67d9c39052d94b62eaf83fe6d7c63cd3918ade7de367ea79ffb12f66fdda0c3e957ff8a3fee11c1d53ff90e26a3dd71f6b93f4ff245da57366b3fb686fcc717e8ca7ff68b1e76bc9173faabb99ba85e34877abd02d1c3e5a387c74b74eb76adae86e26ddaa91a2bb7b1136310ddde66b3d4756a958ced887feb3287f7dde0856790fabcdf1739d2e5bfbe40ff38ccb257f7d95cae8b59feb7438a563ea997c109c5e275869c61ffaccf5c1bf9eb11dfd67d963d46799d23cfcd082f5f39ce59fe51baf96e2fb54be6424e51f9bd8a7e787e26638a5eef6ba550343e729a354f34b7152f9f7d5327d3ebd5aff706ec87969b57cbdaaa5996cfa3c7c519ce0068ba17d755952a53ec9c9293254c471aa427a9363aff47ae5998c3edda61c7b2577f7fa6407af0aa9f7b2f0bd2970deec40e0c2032e14b8526428f6a20b1045843cd7d2824e83d717bd3c93d1eb85a25d55a144b398bc889171e8458a38ce4d7db1032f5ee2f1a117ba592828a15b3155f487b67a2d93e377f78f6ec55475fbeb430bfa8bb03903e7df5a7af8f7737d7f89b3decf951211801ed789898fbd48119f752f0b7f4a773be9164c168d6ff7a1ad3bff37c37670b534832f8a16dcf989853e4133d603e4f33383c4603d41c02140427a80fc00f9c07ac009c680262c28c6c33377c08ccbbb8343549211911391910b795548fd15be4867ec3ec6b3fbf512e7782d53f8599c483fd25ccbeb91b06bef45f26219daa59785f416855f694da2b5304f56e92d432fa493f4fe635ed824cf64f4695eae4fe25abedcee8e597b9d70381fe9f5fa6b412fdb704e8996f448583824043e93a1a052087c263e3fa50b026cac26cd3b9d70a57306ce3b498f84e1703e92cf4f592dcd145bd2fb7c2d39522f577d9de18914cecff88bbc8c1f36c31369dee934c373e685f3674c53a0158beec3c6b27a53a015615e9e2c21669e475fa7d2e799a18e7ebc34562d889f96d4ea5cb2362aa4e2a4aa49e18bf3a9f64ac08ff24f24ac26e1af530987b5b4b05c36e579bfbe8c8a3ef8374fb19697aca517fe7d2a2a7ee8653c454f9cd9e65ac61f8ad6abd4c3237e7a737def7e6d8a7fbdf0c33b697f6b19d6af54f6b02c96f89f66609e24a6474c2f9b18ffd283d5d630e9eb540aa9f7758a1fda2559a579de279b408a6751779bd0dd1de8ee26dde2bcf1ed429196309eeea7f3d1b1b14a755e9c20def9f109027f082c1c32c44708087e8fce68c10e56ce3295d56ec654f677c7caee0477c4095a71e7e27227fc2aa3184fcfb6fa2c4fa7fbe17ca44ab375aa2e9981798e982e656b9f5e36afcd5043e19324bd19bf463c6942dd8da33b2668334aa15b9bd7dd27746bd3d2dd998a24f62fcfafbbb76e6d45dd8d42b7b69cee26a15b1b55d58795ba38a9ecf8b708be5d68c71df0a9701cd31baf2fba903fcc43b1b433ec38dd2dd3cd85ddbda35b1b0bdf2e8f31aeafbdf7bd4e0fbfde32fff56a699eb9fef8223952cf74deecb24a739ee3b5319fdc4b77f3e8565775b7ab5b9d4477d3ac18bb4f65bd962938c909e2db814fe5d1e6dd012fde01ad28ce9dda9d1746c5ee7659b1ba9b3372450515a8400d32296c5105ce05bca0e214830d44c5be0092831a11c98dcd0d82c02881d6b5c1041a4ca21052c804f5c079a2078c125418a25b91f828062351b000400131298871c11834bd85152b44bd8a9101169ade1183c386739a6d2bc2518507ce637a5829aaa97119981996cfe0b0e1aeb0bcc6379b2a32be713238bc072b364118295af98ccd152c5bd1ca833052c462390e8f5af9b615d1d8e0c0e133348e652b9ab952734317c95ca9711cbed95499f14da647152ba2667cf32a34ce15cdd0d8e06021098cbf4cc1f498a2c6f94b8f296a2b371283c3a68b686caed0788ccd151c1e8417cdf886a38795d5952a3cac7ca68795d5152c5e84c3573872c06153858795d3f4b0b272ee061c512be7b8191b2c5bd18ccbd860d98a649cc6e64a10468a661ccb5644e3343537b01c87e7b0ba22d3a487221c3daa7895991a9b2a34453336415821c5088f0db4a4f41b442f3da6a6a8713e458de52c5fb17003cb398f5a39711e63e3c4f2c6e1e42bee0503459b0d162cac2b58583d8288590151a1c203e72be761739653b182ca152c5ee4e2417891155132eee23cc4f88c770f2a4178914b0f2a3cbcf84c0f2c5eb472991e3cc4f47071981e3cbc6ceed263e5ed3cbc6c9586c71631cd830b1e1d68ad705c9c07095a2c77a12917671d5929a16916c739c7ead75601168bc5f26ddbb6a92d4a8c6f3da4d05089b2b90d3dbc388b0a15e72e79e52e7e64e5b272a162430f4431de63ca65caa587142a44323da6629c731767f55845e17c83b1998a61b156ab156bc562b158ab55fb8643890ece9b1a40d32bce8686c6a55d7adcccf2971e549cbfb80bcb46ca0ea229ae4713cdf490420569238a91b22292e91185e55b0f295488605e7ce52b9ba9f656a283888bd9382d6878788086660b620be20a162ba418d93c08560f2ba418215a796fd14407912c449268972692858812449870f9808dcb16ab2cadb37917dccde62ccca0b5f90a2e60ac805cb66c588cd136ed2bcec6250bae3dcb4a082ee8a2055910b9f8102c323e44c6dbb96e98971887e9215886bcc86c1ccc8ac5c2a2755828a3c539162e58ac805cb07038eded1d0334c60a68f3e6baddc5379724b81b9722b830a18c13ca78a2070c8c14ad8d68f55246bf24b125892438212e2b226c65b0ca681c10614b194f74d14347190c68456975d105e73a6630d345129c239d80861adca6650b0556442e3da250178fe9118524e266449caf10654a19445c8f282b24ee66731e0a6845d99ce06eaab89b242bee66732a4a339bb7cc8b96558c6fbe51605b697191a122251585068c1930886468bc20da1cc666052425a74d4a017dc3b98b102d64d3c2dd60a971ce8589178e63712c560f169410713d58e0bc0722e768ba00434b8bf318b81b1a3056389bd3d86c2eb502a229638543e3456f0aa02901d1e6db1944db16345db4cec6c51003d1c6e389d68c1aadb3398f245a9b4779d982686bee460d196e86091dcd7571e4c505c625c645c6a5cac54547100f05040955c9b4609a8d9766a35faa78d9b2e5654b172f55341a31dd458c90cb4bcccb964dcbf6c2da02f3b2650b11c769e15a672ca015c59585e31c05265e62666ab6319aeb112566d58ae9f1d223ca0b515e7065198388f323abcd9585a68c1e2830d13a1c0f0270393131313131354374d1822aba6841132d28e2841bd70e2bdc4d538c8c095b092ee7fc08091ea5b7206ab55e8eac5aadf626c9f4290352b4d2a44ca03240764375f7d82d0c08a1bb65646466b0131f6dc6a5918c8ccc0c0610d07d345bbfafc76970fce9d07c0a9c30c79836c19e1f0abf08d6309351246ba3c20f8942b129fc9fb824dde2f09fa4420319e84b234b0feb7bf6515eb2372a909266782279e3e7a4dacd158905bc689f9f10dfeebe3782559e0536264c846fb773adce0856ed8c60d54e50104c08181bb203030261130603d203ce9f9e190ec1afd7b5155052019ef65758e994e7f8ebb5c4c847f59b88020bbafbe5794c4d49504f489878486a207a6a8a82f2bc1709ba1b4ab720800309cc98b6b4a0f1f7f99da40e3982553b78076f29c016da16215b76babf8fcabfafc93b9c1f3f7c06523f028b34b41c80069e3782559e53b5f46577ce8c26040c2076c7baa71541687cbbfb99ee7c29e5430f7caafcb8889087746419b18a9a25b4a39b15d4ac9d66d95851035211578146ffb55596e4795a34a78506262045d31a3045f7d88aa057e59f953227ad159bc8cc747f67f822e89e671299992e533c61d3ffcec0fa3117e7a4ea4d8719b6ee6d691b92a3846e1d74b3c001355df5dd1d051195d2197b9a691592d595212727e8d1be6e417a4bc28034ba1b8a7b917c5e9f48407723408b13ddddc4d7dd07e86e03743713ddbd44772bd1dd05e8161555b4a8d042809a01743712dd9da5bb8fe86e01748b8a272d2a9c8ce8ee2258dd44743705ba5b4ab7a6e0628a1ba6c802df6ee75a708a25ba3b866e4d81bbbbc37ffd490b62af147c2acf9b9a4f7bbdc22731f84cc29f9adf84a7a7ebd1019f4a14e758974091239da2511549941f0ae9a1702992b7840a6b19fba75a5924ab34142dd2e72a244f617d10ffd17cd1485629b6a21111395ea4a7fa649311ed99c8f1226570861f3e51131faa1194da4a90428d0ed7272d9df7480239ba5b031668057477956e49d1d38d6f977f077be17fa533708e3a3bf8a968a623bda42833536db54d5e89a09890b6d6577b84896c584285b55712d2b084eaee59b7a2b04177cbc8c8cc7432323233e1679c1d7cedbdaf83773068439d4ae7b86369d761d0863b3b2f4ce63cbb81e2c47f1625232333134516dd8d67eda7bcfcb896362c711445babb3967b94e7a27957fb54651a2388dbaf0a53811afdda74262c312aafb072d5a40e1c4f83969b4b9ce9993e74141797c7c7e68f3294caafab04e9ac5727cbdf0ed6054b4618977763e8cfdc48082767ea610203c3c3306fbfff1010224a6f379de8c6125dec1b9b43b99ca74b494dd6eab03c0091a0cfd38ab900d4ba8a7a7270b5ad036391a41297204a5a0a08c483ac39e0325746d12519c46e2347abd665e2f0b7ad8ab1f8674e651727a247ebd62e2e3211b96d8c76766a8276906878f3dcfc3a5588e2538f47a0d815484954e244964a3c25adaa2d72b06c7d3e72d8984e62de7108ee7f8fc9437deb5516698c434752d2d7fb5e1fe084a651a25d749bbb6d2d1fb3c2fe8553afe38eb8fb37aa38dd5a4b0d2a9fc5320cdf83f86abe688e44558698fc24a953c59cb9e5735c7aa395e4befe79a94b11dbd3cbdaa3986d41bc1aa244f456543d5d29a24043e131f2aa111947abd7ea8c297328695ce999147001749720ea08c6e004c74e10f20684ec773002d2224c6804d1d24aa1a679c298cceec259108eaee30b54c59cec88245775ba62c06c882d4b5bc6e33a561ea4e302bc2c823d4e8aee5c57646fd082cba0b7fca87163c628a94552ad219cd8996ccf09c791f0a8005023842004e0430c4881c18e14538af8c2c7342a24cd26c44018c30d27d7646fb269933966138499abf9cef3fe67dfe1f230aab8555cdb108308ab0d29d08c381dd7858c3a79a339c791355440a446c414415226ebabbef739b3f6c54e9d2e7e18f23f530acb6a8061a7d5db5fe54f345effe6808371ae63550d6ad217618828710637477fe345baf2c7ccf93a8fb5bca66323a93555a6b79318d257538f8c663569c1f5221b47487afcd53142de838374e452fac16e685d413e2060b1bdd58c6c0824537a658b47c89ef3b61a96a2c4e5dc592aa26799d780916cf5f3b4a722cdd798634ac1374dac7dcdaa8009cd1dd0100220048dd1d762b08336ea6efb539e6393305cbf0ef9741f410c477f7772b88eefe3cd2def0fe2cdf3246da1be6d80440140070ea50b433fc7796417b49276dc6ee318baf3ce9d6951fae0475775ed51c3d7204ab3205a94856bd87a395f95359d8f4eec712cfb1562710460021f4830d7e88e287f107a5ee9a7caed3794d1c4129728edff7fac259f4b9ce92911436ea960f32a0ddf2a1f42186eeeebeda043ff4bc9a383547eff1987522efaa95d94bfafd8c67ec3ecd8a4ed4fd0c9c229d5493c456b0e8c63e72a0ac2cb11264a54777c7bad54316fd79ddccf3e8cb2f6ff84a711ef9cb753afa242eb1cd1e8a36c7acf730eb41051e76d0dd4658fc9a4bbb5369e7b46424857f2cf13f0dcdf0449ae1390b1f97d7defb797ab93ee661d6dd9d38cb90523d0f47a6ac5739a3ca960e46b14fa7a52724af4455aababb4b965e89aa7c5d95235dc5715925a6bba99441658beeeec25944a5aafbb3139527b3e8f5029feaf50a33f6486c73387fb6c3cd148a1d4f71c3698a1853ac7447c7d9146f4a4c7777352a3ebf6831cc7a6dd69dac16d4e1d26df6199eb35a4c775745c5b5310be555566aa8f218cd78284cc238333c913ce7867644e5032a045039a1cae98ec8e98889e8a9861aecfb726c5e30fc9a15c90f3de7c13a33aef4c35847e7f3efc3168bf33e957559a5e1e3d2f3c224ea95587079249fd1dd7df9a5143c7396eeeefc61116c8177c04e380973b78bcbc4cddd5d6df2d717e6fa54483a5cdea78a597f7d9febbc70579b9feb745fd37f8ef1245f94d50729d57da8a1212121f0994851a3fbfa53512c858aee4c27cd31953293c233a348229058745fed73ec21f935e9fd758a3603764696985eeb98ce281b6463ba73f74a6b1754b6cebac311679eb11bff6651754727abf4f19c8d63d4603e3dfca5b14a07e73f56a9cd37db1f2d1e837ac4d14d453338bd3cbd90fe7bb1a69ab8a3362546f7ced493a99728178862447777e21c69a5a33f4c3645c7b24ac3520469ace2dcbff1fb39d31156e23c67b9c8bbaf5374eb5856e985554b71cc56d922cfa37d9d791e25bd5ef7c912cf3c432fd34a8786923c274a48c8861ae8166d44add09b190a663fcc8268a51e4beaa8dc5eeb9586759616366760fe98db7c2d084e4c7d7a2d46ef3391794036ca5288b511a322e6f46c74932c6d2dcf99e4b33cdecf5e694d04e7256d2e6ff6896947b3758a7a7e5c5a1cbc76e7cd1476a7d7ea0c61612dc15a9bf3669cad2dfa29fe87eeeef31aac36973afc6399c589f4415d7e4c4335ba3b31dca216155ae97019252ae4117ac60f0b45eb987a14980538c309c33bc33a33d6a9b6e2e92118d4853f858ab0b0521a6ccbf7398c00feb23b45b7366a083fad9a4bafd7d0509250f8d76628fcd4c3f53dfc60b5f7ef8730da7cf2a650ec5159d2ab9ae3ccf3289c8f14ce9fb95b32929a40ba5b32929aaeaf8cb695668f3e2ce8c704c55f5ff6914bbea087f9a89f77d853a3bbddeb4204875eaf2821f09978b4af9e10f845b0961eb3d3698627d250ae3fdaa5d72b8a66ab0e0fd0a1091d8ca00cc103502600050645e7c9184fb8e8befcd8bb2f8675cefcbd6a3fffa9995faf25213dfa3c666fbc67c479d8971f2aaa367dd332ddf897ebdf9fd5a4ef0b2dbdef349623ad74e9abc1fc61e438337e58f651f19c4d7190d64926f97dd21facf63dfc294b55e98f340f0d85499e03e5394c482fbb330f31790e9290d35f324c82c2659967c69ef77f63a3bd319b9fb4dac9164ea884363bf9c9a18ceeee9eac6571569aa4ab167cec42ba958396ee0e07ffe730ebee8e742b07ef70aea55473f6304c47bf3686c3183836df780885c3143848757787b1156f2e2b7559a53947487209a3613ba3f9a411e1c063edab5b5267b414152d4e4c6fce546ad62f96e1cf404ce54452479a866e49c5e0aab9e4e1e3f0c759999a8cd1ddb49d265474d3199326b4d6f94b16fddf9847fb2a5a0bf3685f5faf28266a20800901981ce9ae36c5fb54762c319e9e4b18784b8bb3b577d6746b8903963c59f2438c281fa268885a41a5008a0ba80bc5c213174fe3534e370cee5dc697621ff77139ff4e51d1332e6d709c2f6fbe71f27fca3fd779679993f17cd1f3acfa5cbb4ff58ef3589cb19b1c3c675e1f7bf3456b75ba18252908d575607ecfc139f87eadce1a8cfc4b7e2d6d2d27fce936cabbfbb01c9cef4bfadd38fe3c67197b2fc2a6e735b1da09e6fa3770e0062b3710714ac3890aa72bdd7dd9dafb39a6389ff7e3bcb1efc6738e5f4bfb4a2f692f494ec7f992c4996dd4e73fcb168b531469177e1ed6793f8bd32986a6359a70d0944417da59fe110671ccb3cea9cb607933f65797eba479696bfe23cd320c1902c5c984620a82e9bb1ba85b4c1d0b96ac2c2d2dad94b6282540298bd291d20b4a4480a45b4a7e50c2440909368411a45b3638d1dd8d45476e6beddaf147afd475ba29cfb6babb38c3707acdf9f0fc714e5d4bf3efa9a8383df27fcafbc25a8a33e3f2e8be4c466fc6612d63fee4d1be7ad5be5eb467aa4dee2e8a34f630a721761df7ae9669954f5c9b34f1f3ea04bdbf25aeffb98efbeba92bbdaa8a38fc99935ca77b2897792ec94aef9336479c20cdf4829e6dc5b9fe58caee3c729deeda0cceef1327cd9566451fddfdfe6f92cffffb1bf3909652a868c3cc06a1eea29a9a9ebc0683d569d43d089bb0254fb85a3cff4e8156e9ffc6c2fb1fab3fc5de9807623a3e58a7388beed358d253ddc7ff14d22124a12a5b34cb982a654c456f8649f9a1c20fede8d9b096f60e85499e0355cbf4da592e8966723cc7e8f59a6458e79579b54cc559692cd371b4e09010f84cf083757af8b3a542daa2bbc3b59ff2913af691a95bec394f45c5c75e7b25485a3a27e90c239196092908530a5a8c2766426ad57eea2966ed256b7052832719429227924c493203922f904c4182d439f8c95af6da4f55ea38333c33f61baf8919fc4b71ec9d75fe5813c559e2f2c6c7cfb886f1df309c8e7d1c2539b5e0c31e0a3f1d5d71944477df74eb483ceae9ee19ba754463240223021881463c8a6ad0ddf72bada9f64a3e7f85f43ee99fe96d023b729d0fd75e49ae7aa6cf73be585398c424e77be170fe13eca88ac66e7048bdda4fbd5eb8f64a5eafa8cf89bcfb707a8c5ca7cb555f69aea567f063b1cf3eaecd73b4e0c370769b2751f7e1da4f7df8a6f653f83194386b54edda3cc33a476c6335c9c62abdf6def7667822bd5e51333c674548fd243967b5a9e82624cbd757d4ddf32f48445406396931748b680b5196f64ad4dd45dd22fa21d2ad236c7477c7e4da6b8d4471821409386fae4a72448bec96a4bd3f45a7da6aa45b47a4ba1b2da8536d753c67340f430ad212eac88e86d53782c5e3f917fc906645f167512d23491871ea9e961fe63fceea3f8b0222a414450abbef547b25435f8a93c987161caabd92da4f0d396068090cdd1af2298286774636f45cda221928a2a5fbc3e9c1e95611b1bb331d7feacef1afeb54fb5373492806424308591122bbdba65b424d4245447c406401448ae84e14cb9863fa377f13ef6af7a972c2fa64f7e1801f5ab0c7753a9ca7a2b83aa6af1788e9f87a59ea91f8732c8a13bcf10f07eaa6e9f397cd3daee3e5c75e9de0e7b1aa0feafb3eb4e02ccbea8bfe197dfef76734ff70a09c0637ba69fa5ca7fbd0824e7b222bbc4af0c2d2dddde72996a038c1794b10bc1fd6d271eda760252edde66cedd3f56fe23f7efcf8d155d1d85f12e73b33f63c67798ae27c0575b7ff2c36d4ad216a7477e4fc99631fb9fe58eda554f5bd26c2c6c79808f64d5e36498f596fa15b43ba6851a44a8648d1dd8d9fe94321198a022969f3e339737a3c6724be3f96b85a70e67934f30cf1cd014e5c5eeb7083f617cecdcb734171c305658cee86f2048adbfce4a65b1469cc231f632f97f6b1278a74caab31e18602edbae140bb6ec068d70d0cda75438476e194d02e1c15da85a3834209353dd3dd2281d62e128c68170952b48b840bb48b0412b48b0416b48b041cb48b041fb48b0436da55424c0c0c09355a6cd12d2dbee8961633e8961638e8961640e8ee9a1250d8414bbb76c0a25d3b90a05d3bb8a05d3bf0a05d54647ca8a0c20a2e9840860add2243866e91a1d42d32bc6e9151d52d3294e8161911e8161924e8161964748b0c36ba550609dd2a63a75b652c75ab0c1dba55c6edee9a1472c8b8a41c699714a57649c9a15d52a4b44b0a10ed928244bba43ca05d52ae6897142eda25650526d4b4b040d22d2c96ba85c5ec161663b7b0a8750b8b20ba858512ddad2305124e7025e169571223ed4a6243bb92cc762589d2ae2454da95e488762591e2258517a6b4eb0520daf58210ed7a418976bd604577d794b082eb8b6ad7f7b5ebb3edfa7e68d7f744bb3e0c74b7cb4c4d8e191c2e24d0dcd02e1926ed9281b54b466c974c0fed9221a25d320568970c14ed929940bb64b668970c09da25a38276c9d0a05d3269b44bc608ed9ae1da35b342bb6672da3503a45d334672e0a8a19991e92e8104574b478c1752313238d882c5121a6023033dc80c5d99693ac3bfda28ce065713026c56746f556c0e18c006ccd011e9bc4ec43a6c2c2fe8b8243d9b2bf542aaa56ea47e79a14867e0ebc3d11db68b8909f876ae25653cbdb982e3fc9bb13fbdb97b636153810a9c1b7cbb1f2a57141bdd6ddb15d54337be9d0fd5eb45b60b8a89137cb40b4acae582e281e38cee9e81798e4e5b0d9eaa7822c013d99fdf5fc2f564e4f57abd02700315546cab1b82bafb862dc993e29444b7d3c7d138c5506b3aa3fb05aea6aaeedea8d81ab0c3c5d4457d8cdff39ede0c446b179391ef612758aaa2f3974db5f48b969cf0d7125e9a4d28ae251abadb8a502254f879d29b27f94ff9a1b055cab83c0a9f66c38a44098a921874b7121faae65237d28cdfabf527adcd3c1d4fe74305c5e4ddd4df982c674261ef6a62b5f8fb70f9dd9965d58af3731c8bd90fe7fdd182b92cf2eeabe23e34e439d77384a084463a8322e79217f1276b3bb8bbaddff7217118dc664c8eb3ea3b9c692da3704d7a2a2a1291ff4a644c6495ce3f7201e70349053a0d1711306e60b0712131a11b5f7182b65bec1680104745f4743d3ae2741d21b98e84a8a8521ab9d1fe7a6a7a7a42e23636363bda657401230b1839e1324aa2ef7b77079722794bece5782e23d28b1429d2bde560d463c4a3fd65e966435114454ab88aa874671a7e919322a4a222aea21d38de8dfb2bc75f33afd47574370eed22aaa2bb63888ababbabcd5bce5b4ed7911d5c477068b096377b8e577b7f0aa654f7c323b88820691e2356ba69a6782c8ad3db969022035b888cd4f1a4d1180ded4884de6c69d56f0268ec35b1dee94f6561491f5aa6a2a3a737cac33a5f2fd8585ea3de7c701569e17b3fd32180d041084bdbfb53446ff2c2e212a281e747079f5a9d8e7fbcd39d5c449ce8f6d7bce59cb79caf23803d9d4ff7a3037e68419d215756606273810843c839bae00c5750113f38c06680250a90812424179006687600c48373f3c3c48749f7758a5ac2f95ea488df125c4d9c9b29eb61dae160a782e3790ebe99818ae7783598e775b8d224e19d20a647444980c400c8d7fdb5e1ebf539f93e8f59d089bcfb6a8ffced1468452a7b5a97e78cb6c4ffd23aef30e8986a620feb9c560c61763e9eb479ed0c93b362c7b029ce8c3d27e7c230cdf9319d653afe8cfc8e8e1e3e94ef73325aa8cf31c5e1b535c93b1153eb95c82fcd503e4cf17da7cf498ccec0f91eb30fab16747fb905f18350901c3519bd5e9e283ecd0914273953c7caf05351199177a1f834369396c82a25c95a51f84e325a646d1451b64c50d93259a6b02e71dd2875b78d910f7be2d7fcb2af63629d9e8ad6ff7acd99de3ba9cfc00952dd87f224a89ad0d729e6fa778b19583697c26a1daed61f366f49e4d726711146717dec93864b5cbf3eb633d02f2ef3bc9324a787f692fee3c7a47d91779834428fbce3810987820d160ac030460bd98e504198233f344d815609cf3c8f68c01105e989ab47015c3d8268f02f4c7ab216557b2576e2cfabf6bbf345d0717ed90cbcf3b1d7eeac1426c226b6187b267f0a7c2aff984df20efb788c6b3fe5e197f85a3a7b98cb2a7d0ad2d1560b7e14f60e5f1babbd92f008d75e89656272bd70a4bbb3bd4e2e1e136829601b02cc09dc0a2cbc24a047cf85155a70a3050db47085e7853f8582b37e122e1e2ae8ae1403fdfcf4505cc3b9eabd79cb99af5d1a2dd457cb14c544093997fe6f54918b474ce7c73628c20a4c6c4b74370f39331e69c6b59ff2320cbe0101b142a7e052e10b0cd5dd13f861f3c18a6b871b3d741b1b0fdd1dd39b946b074f774fb952e8c0ce0fd50e7831145353529411d3121b3c29a2256fa3d2bdedd0bd4de9de6a1bad7ba3da72f786bbb7dbbd49e9dec8eecd766fb57b1bb7a9ee2d4af746bbb759f726ebde625bd8bd81dbdcbc0dcae6a47bcb81b54935d5e7e5c75f37920b05a9ee62d6a8dbc885424ecff0140ae707b94ed8d23b9fe75623d51d86444cb4a73d93cb0436da3f6fb1356122ce4cc5129373f4ba2970c26a3fcbfe7d8e6b12ef3208e573f2b09ad8e1a9e45d4e2dd35a8ea3056f7230ed93581b952d130e93882c53f8d9325ddb85af97cb45e536fb14af8ddd2761dd4bae1615dd983a0dd556ef7eb654427a3a1e1d1d9776b508d0dd38ed6a4581c1f374da5759a51349035c5431829e80eb25470cbac0788a46efa48108d8e0010b70c2e57245bb72acc0cb8fc36a615e0dcc8fa797ebe74abd199e4848f722996a4242ce4b73e1b081bfc879696eb3119e4ef83fc9ad4b9ea06e10e94c4292f346e19f9a518fef87b5f0692d9c4b727d2822178d105c34b9cb9af739ac8f5d334d4bfe96f5a1c21761b32e49ca96a996c911c93fe170164159a6906695f0146895b265cabf942d13fe4f922d135e529346b0ca0beb6397cc0cddfdd22e19cff8431446b0caf33edbea7a21d2fed271b94cb95c965c2e352e17177c3b10d37107c474ac6ad70a0dbc33033fd732bd93dcf13caf483a91f06f19fbd0a76b05c60a8c6e7fbdd1ddcd556eaeb4e8f0e7173fac492f0cf0a1f3d2a493557b69fea385f20ec34aec33b0ce5bf492859225085a8f858fcb17272eac486897d0a5896631110420e0dc789876b59ff26c566fb096340b09d04ff7e61303a354ab33b4dd5b4ff7c6b3ed746f1d4e15ae2ea37bbed39ea99f70b514958a4c32e06aa5ee49fb746f449b4e376bc3d9d2db0a74c045b7dee011da5aa7ad56acc1fcaf0e1c105339cf0f6dfd5eaf0f656093ee8d86eeed8716478c1004b13b006a24d1dd6d842b4670eac6095f0737c6687c3b7204abe658999055ba15204bb5b44a119868225b83e758a6ad6aba956db53b0c9cc6a10d345a6d4060ce3c8f7476701b3bba2b9db2e9b896d78954cda5d72bca87bc6a8e4430a3fd25042244800855fe753a8eacd23a6f3630886084084de5feda5cc00613ddfe029f8a0db1db5fdd1b114340c110ae1882136ce8b0e1021b280cc108dd3bfe0a1f975d919016790d21d61a82549197876184358ab0061afd370eb50605d670a2bb49a3ee8e8110ce1002098470c51a6b0875db845e13a1d620a1fdfecd35e808816c7fe1fbf9c77927298a36e614861062ba7129e5f190500df645109cd8094268c316a215841df8763ae14b212245b4f47a011152facfb228fa781a116ca96296487ea8fbd932f198adc13cd471aca4443496f8f52261f3e62a247fcb2c8ad3e8f502e9fdfab225e4fcd90b7c1257e5a8d70b2be1030bf8c0097ff91045f4017c80e4839ceeb6b1a1317caba2c027a1a6c09f1975cbd20842396eab6e238d2b3c0d265a280da1c6b71bc1aa3476b4bf42eacd3b49a61e64d18446e3c118dd84073b74b7e7e5e96159f5f0edd0e0020d2aedafaf53f41c1d0f538732cbb409a6b3a8d70b562239e9ee9b6e1cba8546ab7f3a9f9e8e47a7d2ec85cdf09ced000da48db5d1a8a07b8a9446000cced8b283afeb33c1d47490bb33497ef876d8bb4f8234ff68c5b683629a27aed35e708e4ee4ab9a630c2ee43855738c6147ca8d481ff41d97f2f5962eb6a52d07afc17c5b825bf5c682eecea15b3928a1bb3bf0435be9cd69f4a01b06dd313863072c7c7a33109f8e470716ea3cbd796ba17be3b1b1d0b91cc3dfcec04113bedd2cd33b773069433a9dfe93544b3b6c3349c1498e5424c7f9b42f7c3c614e8e254c9663a1bcc3f7e9cf68dff739111c1a9c88ff701afc3fe6d11b2fe224cd5e7e91080e49f30d284ed02be29e5fbc3689efecd09c8ccbfb7d9f773598e7e09034dff8148a756ae29d221dabbdb14f9ce3ad96cafe7aad96a4d9279e1966dd66ffa8becf2fb5b60749b38bb3cc9752f5f0cfa5cfa62f53b11c89bc065dd2140ae5d485d38ab5498a73a4495d48bd06ef4251240a69cc3a3997bcb3d5478b432babb4739c0f572bced977ad6c8a31ff3cc3b10c5faf1a382f68c70ff18e5fea3f66d1a777f72b6d7a4db44c5fa752d2cbee54b2360a27dda75fa752ad28fc4f328baa4949ff50f971c8960927d5398bc2d72e8dd74645754b0667b4bf64800515edaf289c1732c861b70c5a32382283d74fa1b8b3d93d064674770c70447ded3e9505313dfad8845a5a03064374b7ce1114395ea4fa4a484491e888f64cd8e7670992f8547b2534cb14264ddd39decfb309395e248c3fa4971c41a90fad91d37d27b096b65622225c7b25617834f33c924d27ebf422496f948d0a97e0a7f7a9487e925cf52f9bb24a27d58bff849d329e4a1f5a232647a194f128e3a9941faa064a599a4ae4084a4d81562c32cad822652b421925c9f8c5eec136c34603c185052fc0b70beb4f811326b43d114348a906aee869b615ba7b0ac544b038a926c6b79be139abe5ebb50213daf334ea15d10a685361dbd18d69f65e0bbb254824fc70fe9d659debe3693e6150a182234ee253bd1269a3c438428ff626d1d119bd189da56004ed79fa97b5eaebebe5b55200450aacb4bf3c4f9c2f6b3f75d3ab03a420c7fd4af3bcfc18df0edfa7f74ed8eb358255289809c38d30d0082305ed2fd0cbf1fc8573fc45c3bf463f35a376701854fe0a43060c31c2078302feb2d91d6330a0c018d2b9cd0ee5f245eb8b2d7a636dab8ddb447182dd5be73a694f45656fe0ff18eeee2374b711dce8ee22fc386b77b741043686308255ddbd8610ba3b081908dd3d811ff8a0bbd5e8ee347ac003234a6065099e5424022969414adaa8fc501fda25fc9f243f147e11363f5ba62553a055ba569cf83e15ad4bee7cd1a8deb2e8a77df0a28aaad799b79c5e5cc1e27cecf3a924f217c924dd1d96504f4ff765b3cea51ca333a8119432ca5548e62d89e62d896cac2639ada0bb55204677a7a0bb51d078dee9b4852008314475f716821a7477579180136ca0f1440846c0b4c4173b62a50217495ea4104194143574e8eead250507d0d866e06e46a0421b02f088a8ddbdd5800924a6c400830d18ba7b83820634b07812811636e8ee2d059956c680521b61743707a345ac0f6a40001d74f74645140d702a41035d0075f76664004e9ab20881802cba7b2362072fe05c17a1077437a784c4880d88f041c410ba7b75c21160f4600926b2404177bf1ca0006c6c62bc8e70a4bb3733848004153796a8015077b39af8600c3023411432ba7b1303e603822746488901aebd92150142f0a4bbc300a35b0c670b40e00010000102b25b6707ef8c60d5eb855fafebc1ca99072b673a2030a16dfefeccab686c48c7433ac95a3a1ed2f17c2d59f5fe32f257d35153d4193135f0f11fa168c9097631f19574b70edd92c095ad6c7da086c6b7abdd5c7f879c23185d83715f7051d5ade3e1fb140a47d562345ba68a24d787caf5a19284b3289c45229d493c20d5655b978ca42abd16876eb148e4dd175efc7d3e2995875f136be41c696d668c7f16e5e183749cb5168295c83becf39657b41656cb5467da4aa913f96a3d4f77ef70909f204184d8ea347b658ebfcfbfa9a66fde3aab384b29ffa0a5650f6bccea54bb1326dd271f86f394fd95c9a64379581fca468da054388bb4c0d2cd45a0d5bd227b15a557517c74afa0ac8854e85e1dc9828deef6d79291948ed392919453735278129960af6490c5ab93552a52ec5da53e85e2570d4c494c4f444dc811949ae19934c313e9f5aa761c2de88563a5d3eb25832090e47cbdbc8943f0c3ff0b054810214066603359bba0cf4f88b31571b622141021650c723c929caf9747e2213d4182f85c0c83587707f9f42c8941c00b8ae87ec11ab34c69af9747e22617a400df2e5358904f8f87ef93e2cf7c7eca209f9ed205df64d4cd3d71d22dde8b04b4a248d4020ab85ac0638c261aea8a6b0c202cc8fd2ce8a2bb27b953cb3948e1fb19974f2e7becb34cf47a757338c070593c5690868d0b132e45ace048055c88a194022d8404f1414a8113cd002b34e8186ee52908eaee2a9e20509045a596f4506000271faad76b68862792900cd7a6f821cd86a2b5a07767de5856309c0063c717547c21009f9ff20b16a0609f2758ad5198f47ab17002a7e4f46a9ebadf280a1350697c3bf043194cb0a3db045b09cae86e2f22e4fefa3ecf789254e11c0288124891600c1230d1cdddc0393591a0e5c3a412f530a13dd308a2bcb881cb0b26babbcbf865b2123f59cb8fe78cc90b9eeea2013c1e8fe7c384c7e3f17a98e07f12452210d3a5ff242f412e1154d1dd1ff30fd7d2a39aaa22bd3ab59f02eae980743c3a322b724b2e115c1141958dd524359124891ecf9953d4eb750508d0a0443eef5aef9b615a69cc8bf0e56ae092b4acd29ba13238499f9f7208e3297af7c9aa390a35793c61433e3fa550d51cc519f501db5d9b4ef871b04c2e2ea4b823467a479c2f0f3475d7759ef38b587caa97f8542f7aafeebf4970fd299b716d3afdc73c5c9b24c653043b30d58d6f37c37336c373b64516ddf9da19766bf3ebf17daabfce151102da2208df2e24c2812ebaf1edaa773f536f7c12ef804fe5e24015dd3d010e0ce9b07e59e9e8431ed69fcd9b6125766d20025a00753fd2d09050b543deebf5f4668fc49e0c8f33cf10c9e7a77465000c512c9358a6fbb3dc34f33c3a9a6af2f92985f494a2b5b08c69d57b581cc2f11c2caa9c718133f0bd40d50fe110e1f8fc94a094d193328616f80aa4c0550121570576a4e1ba424b4e0e5138ff299cff94ad08f55328544d22c78b6484841c2f52374703f703a7dbf3a078b0801260a0022351e4c0065e50d102212727bcf4105381195705be40c2353085ab40b0000f00d4000b19740040c900365970f8140001575ca1f3831d4aec38810a1a70c4030aa286282283176e8609e448e00ad7162c3819bab918381b0e866eae878d2b0245b8222015be0bdd5c0b1c8f22acd14d81d35ecb340304c22e3f46528300cb8a2abadb06bbacd8d133cfa32ab2546142773f408cb0fecba693eb012d74732ba8d0dc8eae322ace9f42712e6d6d2a199922866e35ac7c0d33c234ab5945b686d1b2852eae0da6756c2e286c39b6181f1650af9260b900c3c5c4b8b056dbd629705d73bdddc032b2350c1197d6f1b2f5b66d2e2e40381aaeb76d5b7148db06b371dbb67a99c26ddc06c3a1f0b46d2f1b4c731b8e6ec5bdbcdcccf8c6e23856968db56ddccc26046e9369978dc5ad7edba270dbc6c52ce158e036ee85c56d3be0b6ed85db6ed852d8b61517c385cb0a26c786c2b66d1b37843b2166b52160e3b6176edbb895b771ac1919205ccd3614b3b9c4b02ab2c1701cab6735c3711b8e0e21db0eac1e9a242b27ae59cd2d6063ad6e582edcb66a97026c34d89aeb56abd55b6fac8ddb7ab0c5703fb68d73e1b8d5b6fd6c3b5bcc56c3711cf762b4f9e03816b7f1ac3a1a156266d87090b002b7c4e6c2a9b0b1b6d5d69c13e7331bab39fad2fe6d46acda868bad8f84805b6d2f1b0b66c3b1ad98e0b898d76a0b5a6d1cc772ae46e66673d936181e56cf0b3c1b37f312c36ddbb6c16cdb6be3b817ac5e36198eb5a2da7a70e5b6ad7436191a6e8b81d9b8239c0b9c0b5c0bab5ef5a6637be1b80d07abb562712a701cb7bd6cacd82613c346e3c26d2e5b0e2016b824181a196e83c5d4c070db0d5bccb6adb88db5216d2d7043381e1c0d07c3b96cab8ddbb8edb5b5c0d17030ac6de5b2da36a06db5dd6c325bcdb66d443617b89915cc16c3dab86d7b6ddb0b0a9c091b8bdb5c58db108e665b712e6cab15c7b138d63684e3b615b8994d668b6171dbb6bdb616b81998d5c68a616ddb06e4db8e1f31422b1a5cb6d526b3b1766c32322f1a272cb8b0c1c0da38ee8583f9ad75020f0dd2b6711bb76d9b4ec334d939f4b0a981430fda7180e20c3070b031038d26930610583283323acac6050d0545180f35bfe82728b240c0938d6dba018a286a384d204794a0700108489307cf102876ec0c99391e0034031c3100d9cc6c4266b01941c8f7460284f811bafb8ae082ce4705975306036c8ef3fca0414b4e1a2f7c5ef382a740426b29014a7f2e48b4b380682e057cbb972b333290c00a8c0c8c0c2b478c101899550b35513e30f588b97999890962d5e8d858352f1e8bb6c2b172b1c600e200565e72703032ac1c1c2d0441c0ca0c939a909593c3070e080b2e4b58db8b9007ac14513a7201c1b166584a3843b072831f7e80e1c3e6f272bd70c4078eb5c24a87dc6c865c685081959797179cae9ca0a30800ab1a7c08801515649ad4985063c2942427903178660d2b4259a6866f002d1899150e9729961045f810c3caf1d2c2a6e3c7071c4421f363055762d858220747e372c4053384a5e49d6cf9a104979b99243333bc3c61fd8019f20306a0a8e107071c81428be70a0e5069880f58a129634d0081c347c7c684c78a088c0a427c988119f232139363059454c3aa8599246b58998149c2011583c40292243332483550a11006aca4c0ca1132f3f22a01c7cb0c0d921e3a30c0ca4c0c920b3022acb8ac5a2edbcb0d2f332e445e7e0c15d194323e62905e666054c0712413038b4926051378ccb0020a3f626850f234830f33311dcc0f8e1fa02427981a16cc0a2eaf9510991a960f3f02230333c3da7151c20a3ab2b9b0b0b27141814726065c0d41426447cc0c0a32403e1831d10089b961c5c08ae1c501567ee4601db9d0e0e2824c0c2c1e3a7a562dfc60a5dbd956b1950e6be5b2430f1d08b0015b23000130b6d81201074831440f55aaf054ecc14f87273948315902e5a46403d22be8e76606196070560840f0461a68e0c0c8900c3d565001468b094a3005036e4062e4d5c20935342f1fc8220356503185140a80420b0296dcf8d8e99a384014d41b6e040196431a3de080675d033882c68688ccc0508144162099168cd1002a5e4384a46002cd4c0c0c0f162e6081271f3631d99094044902042044004828238816118080037a68c20448c80e13586180010426434584c8c0bc4820880c4c200a250840c40e4f2420014734310d11f151b31561d506c7064b0def0177060b07dc0c5ccad85cc0b1805b818c0a6ac4e0c2e0bee0b2e024b08a0037c58ab2c5b0bd10d3820b0f1c2a702970276c26b84a702181736d2d560e8ec66586938989e160605c38d6b6dab697c6a163c5c26a03565846586129bdfcf00d46083eb41860c5c5a506660617f8c082a28795428d140f7819c20a4b490685191e3b80b02283032b87950b34f9a52626494cd0cbcc8ac88e1917125889c1c28a094f7c90015a21e0073564587179c38a4bcd0a2bb052937b6cc08acc8e1c1c0c11971d9724b09c40a3c2123fb08ab0a2c34687cd4ac7b513b3c3f5c60f2e34c404c108ad74ac8462a4b0b25aead9799979a9c2ca8ae66505560e4dcf6a63d5b0726692b8d00083c3cb0a2f332f516a4ca8314166870c0a3038ac8cf0c3cb0aab2d3fb87461458628acd13d195a60bd6a74bcb858db4a6765c472aa49e185836902c3020c93cb0939b6179a950bab66b55a712b9855cc4a6686076b05b30486041d4e332e9817986d05c302c2da616d312aac58f041e605970758c951850fae17160b31473fb0c8f8a187e7fdc61170d08231c200c38b2e4a191412599014192902646b1a3dd8f1d1d21103e3248726a6374e20060a5e38828789258e1c800c167c21020e5c200253ec74332851e623f015820fd480810ac40041057ae041872739483531d590e405175240210b0df4100108f050c56b8187ad2fe420430652462b6f1c61086b3c7192020a2e168820041de0c0052c208123042084a7030e4d9a9892be3841084ca00513482f70c1172200010736c00029880000103e5083052ae0c006b298c0152b1f262171e150c3066690e105083490812b1a8084111f7a3a3c71d224090991178e0bc71727f002014d2071e5431d6c484a8284c82b070e56dbc08c2f4e3082106820035748a0010310c0152836241521f20202d28343828bd56fd8c00c198cc08b1080400319904003068084008cb842030fca13233b9d1b455052a223470c7864298208a9263c51184144adca0a99375647e08ce05204ae8d9807b01cc035809b62f3e17a563c5b47a3b3a261e528bcc0f1d858805961a5c2b68395c286824c092490b06ab174d4e468e158a146051a1e332e1998981718eee5c5c565b5add033507c5de925773eb420ad250c1440a078d172042d637c7ee3446a77ce19e8530b03b444a12589ee8f8a18c78b08f94d1121d7126ac1a13bd76050baae739284f4b2024fb70b911e2e3e9e10e37ece93dcb9f909249ea082f3840e4f20b5e799a7e3273e016b24c00cbf36766709b802dfeecec0f1657c9099d2fde5cddddc0ca7a34b801287026702a763b3410fe9f121bfb7047f0c191234046888902141860019f233c46748cf109e21438282828082840405090212f413e413d413c413340428080808480850102020403f403e403d403c4043840409011222444810214084fc08f111d2238447c8902041418082080912240890203f417c82f404e10932044810102020428004010204c80f101f203d4078800cf909fa01fa11f213e407c8cfcf8fcf4fcf0fcfcf109f201f201f213e417c80f8fcf8f8f8f4f8f0f80ce909ea01ea11d213a40748cf4f8f4f4f4f0f4fcf109e201e201e213c417880f0fcf0f8f0f4f0f0f054f13c025a65ddc9630c8a93999c9b999b2a5ec57770cf99c999d9c1ddddbbb912ba3912ba3917a7c38912f88b8aef0727a89c089d50f27dfe9ac9f93e7fe5d88bee331f8ee7d88b375f9122456cf0732d1d4e9ce084099ee3c387e749eb24d1e87e35e145130d68c7f4cba56bef4552d4c495eeae6b224a13603711d4382e24e43d37ee00d7016070802d77aad23bc3b4afeb004bdd7d2d6c76732edd1cab9b5bad8480038ee3b6373623746f6e746f4570c26500210304194000ddfec2a5759b3d7cbb1e1d2f22e4dd5b1bdd1b11ba37361a533f5a756f2c267898d8d11d2ec146b7b7040cba1b8b74e60fbdffd8125d74db585d624b7727b144ee6e5cfba9277d7fea5a9a178bd9258e2c71d378899ac64a04a15b0918b4a74409669ee1cc3f2a718513ddad4411ad4455e3a7472811831235dd1d7ad98ab8004328000c6cf6d0859c26b61a4c6892a4b7190ac0ea6ea09e8e47275b7bdb38c2b6861082d06da8d0032034047ee00335b6347ad03d67554808b0340013f400a454bdcc054a989e9248712111019b106372dea8a2f0a792885f369dea549a791ee1584dc2384b159d254b16b1bd56c25e2fbc4424fb88315c4714a0bb7f160aa008dd7dcb9066c5184865560949782df84fb93e1592fbf7a778d5d611ac32a20d3bf33c2a22462b62887016155143440f88b082080674378d089ceebe7f3f871f51da354496114f1a3863d53221326306edb241b7cb0644dd9d423791fed12fd46047bb6ab0d3ae1af8d0ae1a64d1ae1abca05d66b8b4cb0c1ada65860ded3263b6cb0c2ced32a303edba0152bb6e30db7503266e808176d90007edba418e76dde006e8049925ed0a8249f7f64577c76c5cc0ac58249613495629cdf567ce0f95ce0856e9504dd98e8eebbc301662060b1bfeaad6fd052be70c8b969ec2a294f3226c2241f2542d93386156ac4156ed6df27a05202700ac6e57100ff097ac520f66c5227c3b990b005274b791aeeb8cf84bc7af1d4129ef876ac948caa1c0ac583482524eb613746f2628c1f60112746f22184108bcd844d0bd85a07bfb0008bab70f74673c959a667822d5a4703ed252f875c9959c06820d207a00c40a80c042c8dd6d06820820720082c88b0cf9fd9c1fe679d2a0aae65287c599b18fbfd4da19cec199e6e0bf3e4a5bf3183805ceabc4bb70fe2cff38999cba4fe7bb641ebd5227ef3e9c1b9bcfc519523c65ad92ab871d3c88dd4c8ea9f3d0060f3d70f1b045b78b072d3fc466513c0875f3d0eaaeb245952caa24a0bbfbdad8fc0fffebf8cbc62af50e8f3fcbd9daa70d0b1d0c008574ed10b583936b079ef6d7dfe9b467729b1d5898c2c6141e927e0a85f2a7399e589c454adce7e729101f9e1e3a6316a422ac243d84e1bf13d441a2446404f55328eeaae69293ff4a6c14eeea9c91d7bacdb6b4349713c433b01c1d4face3f8ef675a69ec437b65977e376773d634893caaa3fe1fde26eff03b9177b09f9ae30433cc6916c59c0f7f8e339df9f7e1506c0a1faa26b9a43429469309e8eea176915f7552c85fa2756242f2a17abdec19dd6d45603960b5e4f8cb5675b7fdac52fbcbce689e637f747bf876f73339c3e9d540572deb97e78ce6d50986600245b8c62074f774dfae8c5be019c588a55bc7a8d485148663ac81114b70f486f851d11d9718437717e21a8cb1e4c848109efe42071e3340d9f143912c45ae2733a801288ae886e971e8f09f5e14a992251f5aa312ca15054a9420501406dded1414040aeaf1b49966eb947762ee91e7786d92a02018f700a748fe0d0a0205e5e1fba8a109eca88bd9199e48e1df38f93f35bad21468c50ecfe0decdc8d9f7d7c66ac6b9f1c7e24ce2dde7f3537edf8fb37e195bd156bd05414ad618fd70725eafa89b6f863f6631ad7306e297d597893a7926a3a26390664c414a1209fdc4f97be33e33a6a27f435142e3fc1bb33ab9b4b656d922ef742af5c9c43a614abbb944e25d0e9277df07e51df681c939deb877f865367b2e4971e2e0cf3598577ae336cabba3a41f2713f82454928dc2e13f1368a38c8eaa65b2f773a8964914a70b7d72bbb71a2e6678ce7268999c707941d4685ee8f29a784e2eaf06cfc7e3f176ba49eb3832dcf8cb771c4706f71c19fc455acf91c16fdc5f390e25a900a8c4f028d7c1a205194334032000000000e310003038281a0d872362d9a0c24db50114000266c46a9a501bcab3308a31858c31c41000000000000000303310004a1258a65ae1fed656b92aa8fa7122590a3c8212b538e3c31596bb447c8664475bc7ed782249f5a68e05e76d66774d9cccb486ee38ff5359e6e36035c9f30919d01b2c4e0fa3de2438222b1b7e828ddafd2ba4ba0a81ca1112e797fc76118335a2575d9cdf0af84799e148751d368dc35f349e26e2ac7015aee966c83fba18828f9edf0104aefaa901023ba4cbaaa8718689eade6759c374e4eb597e04589bcf17667eecd586eafb8e4adadc92fc008990125a09072b2626d56d69efad456207deea495f5e3d5a8ede3d31fa74d80d23f6d4cf8516c40fedebde3decac7d149e23c293bfd56a36421f60e743d93272e53a4567fa7601c8dd45cb51b68b439923871a362e55d0bcd496f021b44eff2390a67ac10c34b9d397b5436e3b6a2557f0def6c14a078cd9decdfc330d84bf0583760fe8fbb44bd8ce495206085cdcc9b860dcbd2f99c045a8377e146b452179c9e08deacd5e3c9107625165aa07d1f24b025e77bd908345623fecde3f4139dce2c66423c225742b25ccbbd3dcf8a1f45e2cd16575a0ce2779da716d8ace5976b2e4cba11c4c8daacb7aaed68ed799852be7dac30c25e716e681d831ae6f9c2e98e07e6701134d2b9034f988e62a49edc5e342833bc53da718121f24244c57a417fea14333491133cda33534676baa9b9b63ca3e408e97eb71d93a807b378fd5352bfdf79bfa0e3f63fcb949f96dc94cb19da940b34d7a11e5d2fa516d0460f9b0bfb4d979b9939e2550b000e26021c77b059d667fdd1977e73704757046fe0dc5f795b78b5107691e6de409f60f95f77c565606f0a677d136e1c064c05a68fd3f83cc80f00fcd327ccfe0f6b790a7d876bb9c7fd08f479ca113aeb87f0cb07ddc85fb3dfe316d3b5570c858fea0bfb4ee496ff6f877c9b4ef0ce29db76ac6634334926f0ddbef4539b15010aea6158312df37adf287f73455ced4ace65580982dcc4c083447d90c33a0a73ee8f87002854c46a8be12a319f0756e98cf8a6e1b66522d243dc6bb780645ae3861a1e4a2f0fd2d577207c4ca2841e31b3d44b6ebaf55a7dbc2b77d52ccf3eb75595cf58e938d967f3c816c2aff4c503f76a48dd0f1e412be1d3bbc51a14f2b95c6213bf8fc30fe2bc7bab310017f8ec6558e054892fa3f4e8e76c11423d155d351511b1e09fba87c0f1af03218dad1e938e48b46dca6240702986aaf87f1e6ed677dfbfd6f02596d13d7b08cca6414f0fadb9996715529aa3ee41f0b8022df34bc90ced572de973a3a93169e2af9c00a7bdea762b3527ac60e1bbf6e151d9fb222282f3efab2e26217f1992bd565085ce91a2dec2179045bf06159c213314d4d3918d7bbffef312207804c18adc2bf75c951b5043d043a48c15ab972363bd82a52b5af6ba57e56227374f5dfde9b243990f8451b6e9ae17b1db5cdc4abaac8c5c5b26de5a0a37b60486c97d6106b546f14264f63ccfa8fe59ad2bb4a7d8dc5d507a10f45b5f5611210ab671725bdd12c384a92e0f7ead3937324a860586cf61410b93ccdb0598d5ad92313807e41685ed7b8fef46aa6c4a3353a1e05fc2f2ed547e2a5918c6e620463fa334cc16b0ef4d83f1bc4e2c54727404f10b62fc9cb10b4d6947bf6d6bf8adc9bb2971f0b2f8bab7437127656b0d3910346eeb2e8d1e89f07fd532e5747e91aff87aab643054219cdb7c44a6fe21eaee98f86eb2c1a59d25979a4868dd9867d396fd39675650a782cbcd5763cbd2ff26db5beef635cc6afb6b7141fea7cc94a91adbf42da21ff3a1b8ac1818233c9ab973a62d913f1b2c8c7e632e136518b9e52ee60762b68d91b926b5c02884d44a566ed714f6e31d3a0defa549fbe09c55ec3c242715b8016031e325ba164b38e2ab3c39086f29e2fdcb17e61f779f88439faf111d5aaa5598c27cdca7beb35a57159a53ba32db3b8ec3154bc50ec7ddc3b921f87e7695ca2fd4957e41033aa76d24e41ca126f72620152073bc4bd5b8b7d52bba935fd2dca770aeb7f90028754cbbadce48d9e902f184e984233a1b02ec1d548a5ca2c2c2a6a5724551f80411426b37e346ea35e0f20e1d1d825be9a4e055f0624dad4a042f51d075a978b279ca72a519ee7c9cb06f69e0e37008dd5fdf497dfb82af085f3f9df588fbf81cb5b0e02d55ea7546a1431a6ee7ecae2c70c2504f49b84380f8b887811fae7a4e262888768642d056b599a8dc4c9984d4505da0d1f3af4312e5d5eb36230413ffc2f49f51ef39101a2ca2b622f320b7a9e8ef7ced64ef2b41470e7c91c4ba1a55ab216035f417d6d18004d66b89e0a0198583cc9cbb1acbda48972c675043266900a0660db6e3f1fde9e747250328aed91360ce921984b78e81f9e8754da1d1abfd6a48d40b8400b10ccf6690facac4f3af023e0343b6eaf18bd84000fa779abde680550b00b5698292415aeb4b1ef2b1a39e4848ef9ce9c55be7af319d379e91f7e881600bec124f408bb827cf6347ca09384bbb51843edc530f8ce9b3151ff93ebf353d6b320e3a6f9fecc71907ad28d01e7eeefb4b8224a2a57ff9d7f9afafc717c0726cb03284121fb2f04513806742f6e82706fb6d643bcc5e8319d61898d5b151260b6b52ade976a3f62456f96ac9c6480809a8d681d7bc0fea10870cabd18d54b24889eede32dca9f1e143fb5c4dcd1d85a73920e743e08cb80d0ac6bc4ce834b168d1369bf5b717e81853aa5d77b05f6d5b108048c09104b17e0bab907f856788c4f1fe5a8f9d866652aeb1b9ce72ce5eb2ffa19f8955e6088a43a5125d6f00f969a5e44e09938c906f18c898b211edafbf8d8773edc637219d5cdfb7d0e0e737f58e1f56b05cb4f58fb5b37b4f45d86cff5e762a016fd2ca918c13bf2adcd191be108e61a4e791c44d437b990b3fc429002f237c1e20fda79021d05a01687a5a34986c75aba78371e6f37435cc713c7b43e9ea3a8764958bf5fb3493c1864a46f5708037131502ccd5767cdb0f5983749c8c4e74e6299c758508306b65c2bdb622d276903f1fd44736cecbcb82f2e3c6922daa41765612e3c8083123c63fec79813586ed9071736623805c65d467d066ce55e4a469ebe7ad3480e16e0f5fb37fc9dbc7dafbe5ed6db9a38e600edfe6f73301d27de01f0b6d320b2a4a5051d45a02e5204eec6b70aec865e1b97860ee9889d578da46fafb431f7115c9db85e3b1ea6558cc5cb00204f028b93929a38d6bccb6ef3a3a37dfef28318d79d63bffeda9950c4f721babb6f38f44adf96e3019a33db8146aeb3a589499e5432e0d2e2670add37ecbe32f71e740ebc4e360fafd0797cfbfbf36860f5237ff5faa0f21ff46d2e22fb30ee000f798bcd04fd6717fe46e1c06d5ad0bd8d12e6e2901e93998cac6c386de8bf650dfb93b5e3a32d8f3a1e36f4d5b11be7a57c06301a0f3dba88513efb4b4dfcfb77a8fe31acfaa0fa08d14364be9b0a1ecf5aa9df76010e2f6d8aa1cfe3a400fa05a530f657880f7bcefa4380b4b770d9945858c0cbbcb0555daf54df8e79c170f4b31a86e10b92fa48f6cb72c389aab71dbf92c20e33677e40b8ebb590c170f355956d9e16721553db3d0263ec6d29ca297df2e4a69abbaf1cba25a51992ab06315468ba32a8687f81adb1794d18f600e6ae567d619e56cff603a22de8548cd732c4eaa49485562f69ba7754703624ad0445aee788b83f07afd5769d1e4a06eb296061cdba35073616df1e9e0bcbd3fe8f66797d034b8e79f7b36eda3f382e34b0709182d63f8a321f3a7aa62e2db69e48f0073bd7607effeba7c4f13422881c60fd6993b478721f9ad26488dde4e308e5e7a1acd826b4f14473566dd203b1dd93b5cc71d1c7f6d9a5e7b32fa5a317e27519fa569485fe8d4f7f432759f7d98302a3eb696ff278e653db13cb1675909b5794a703ecfed0f839bcff3a1175869b777f5d90bcd33ca7482a1f7481d5302229249ae6fc8cd12fdeaffad9ef18090f33bfb0771faa7fd657ce7de87ebc481fcb52c5cfe7315c03212634e78de87952a8bb4d84da82071e9072440dba4b3c701886c98e4bf24a6bfb7c91fa18721eeecab10dfcb6316088afcda1c50b769b17b16399d0e9a71641e780511f381a71954fd4841190b05156780e415a78c5eb381fcd34128fb6183f6e6332e25ecb21a77af403a8c0381bbe9249df3439932d449b5c9bc418351f0f0d9e150d07e31d28d01e8981e2afe390e4f1f1a446a268b062f0ae4093837e9f1f4cf45d4fa3fa0158f0e8d4a41b1bbec2f1153f79ea7df68d41548fc083373e3ce38c302b4effbc457ebb0b2da8d580f7ce3ff872d59b51970583342dd5cb2aeca9e46aac96b4132f2d4b41f3d73dad96ae484a129f7d738f3c3b7c13234147cec35566ddfb51e28fbbee35a99079adf0193444c65a3f3829901c1d9089119f6b2b4f9b0bd837b326eda45826fe576f50f41960aab72cdae75231030cb82e7113685734f64cd7430df605c93aa07b76139c35509e6c2526eddb9be6f74e56d3884902c71fe28b86a73f92825964a786683f90eeee947b7cdb52df56ef88b2ef26a1b81fda42046a845e8da533b18dc5d1066976feaa51e93bf1dfdf2d0c903722ed2887c26f4fc5333ce25c5dde53549223976941478cf84e18fd41397f5b08f163d07ddb09db07e32ed9af13b4fff35b8fc2a0a40c41d8bf0006b7b9d5e83958dc4ee762467ad71ae47131cafe6715f44252aa95b63304c2d27524a9339d085769547ddddef7d1acd1bed0b37e64a448d70c2ae4dd8cccb928202eabf96306b2aea929c82eccc55978af5f95d4684d21e292a92ca5945e791ccc6a782c56f711384d64e57a7c72acf24291e7570de231b755aded5184a89b99186a0f1dfba225ee0a5a3114557aad479a056dee4113aa28b12db33248e233c7a40e1da4f0146aa9a76882c2307bd41dee7a85fe9c52c1892b817e8e7a059a4c00d21eb45e3b9a854aadd54e4672e09c4453ff7c6a5293d7c2cb98702d8e5b77c4aa499f83569215482c838f9f6773bb6dfbfb1fe6795d7453632a67d1cc5e0da4ef73793ebc7e0024be421782c713381cf9cacc82bb65214833784a8bbcc9e013509ca426e96ec1957d3bb86920a570d92d3451d0ae1372bf6cba1a5d7d37d927f0935f9a609842af5e4df726eaf7eaf5b400f96a30a0a9285b7005a7006303c43b34cb36ed78f3df647693f712328b30b603e4f1b7c853e60e666b73637179aed678d2aad01c0dffcbf978321709d711f4d76c35f1cab61ca2a7435193857219e4ddebadb8a47882ec11752da14f3a1cfc0687ea18566725e68a25b45a50e75cc270799aa9c14965fa3da6bea0e540f47ccfe27c062b72b88b3df8a7e45cc9244f382163e62b354930d389dad06980bfdb9d6fb2c2bc9c486abfd3c84047d8e09ec0f5f2a921cf9d0f7c76ccba18db66fc5d745557a309ceb50543882af0ca783a10a93171bbd64fef5f420cb1f4ef9248fc30ca0abb9ac184831920e7102fd843c2f01e5fdefed39a1704a1ea08b5bd723dc1bd5fc0bc1b835d17bbf4876012e32e6f5b28c5429de2fb7cdc72f3e2532d917ccdee16223f4dfcbe85e51eec10a8075e04dd4b48d90607afb54c7c41807c194d3e541022ef104e16ada1db3eaeab3f65f01d7f5520e106fc344c9a95a7f44bb79aa3cddf6ce36ecaa3878c5a02d42b6ae0cd8613284c2760caa97221d55e07a5307f0e27c483f30462c20389a4dc2827df90fb9ef9f09afd2f8f18d1f8bf0575968ce2001e3d919237974009d8c9128f91408de8f9b401c547e0708aa1a6ac9446106f7934424c3fdcc84f156eccd8433752145a96b72fb63cbc2479678891ecb6fb47e61c739e4fe62311b2c69f053ffdf86b4c82ddcb11203580276c1eb4504016139d59905d27b5acc5ea17299cf7c87ecc0f91606abf8aa2211b2e3a0a0a3e857fd479751ea95b31f0ee6f34fd8eca0956a7aef6668cd660227d603c6d09172eb5c3c20e3202e880fe3aa04d582d275306c8add9bb95ac3cf50bc5d61df88f8f581f648d23e00950b25a26e1e644f539005d2287b3e1634fc67e54cbc6c180239db7e79bab9baf16afbfe438650e5ae29696cf3fab9275a04bcc3d1002f3a3cc9426c378d1d017db34336391215b8439735d4500ccdc21a23fe6dff469bd6c311c0768ce9dea2869b20e6cffe4f7017e3d29176c60e0654a4a3b001b70888e8e1be3aa579030c2119f813f7dc1bf1e3d707fc269ce9eff2f2c9ad1cdba6ad35ab72974a2a7b1340285458005c68daf33e0cba1c6a1aad06c376e20621b5d2ded619b4b278ae49a82b88ccd2a760f0c404ca0f307ca5a8052da0ec88b757b80c8c74fcae636cdd86df2889a78426d138a937f19a2fba1740fcb7cf143f1a8580807ac3fead17ef433b15c6bdf5ee54691fcd2164ddb4da264b50c05181bece3ed7fd472e64f8a081703a6eaa4f633c24e64c9931f4a0384f97fc48ad40d7aec7d2e55f57cf0806b4651a5623adf15ebfa447c382ec181ed654390a9dcd893de46f410f397da6bb1be7f45056be80066e96a520da4f0c8dd79c74661288df174ef82c0b1fcc9843fa63c1139572617e8bf756c8d24497e3910fc1e9c40f41e3c7ea0549d86e28bbc0d5e0a76dbe0fc9f05e382623aad788beada56ce601b8c1abd689f662ae95560862a9e424b7f2447c0c39ceca047fa31701f8c0ce50cf73d750c4a50b0d42a3abc300c6eb47681074a14207927d04cf8d7f499c4c577ee9d88c158ca87d45d45d2ef4017ef84c68f2dda0c74d211f496f33df24e1f2d89994673ba252dec361f55c15f5b99a74890c0b5ed3d2f1d586f7acf21bb1ca746e73d16b94f3a227298cb309db343ef5733d44bd8adcc7aeabbb089312fd76c28f124ef80e6590f9f8824f87575c265f614e3590934c349d2838c58bf5b3dd1f5c214a31283c798a1088ae8330e506a98535f19220049879799ab2f8e507c48f9920b356d8aa700e75639ae2ba0b326ce06fbc1ecc4afc9bc94a13fb832d45426c8b909c7bc08afbc1bbb26fdf78f031e06f52bf335ebb35ceb6db57214db6ca8bfa028077e935c048bb17a41f2bf5fefec156a7a32ebdea15bfab21fa2d6038269d37c19a6abe6245b946ca51203f2bb7cde40cc174ab44b279f142363e0ecbc88205814fb9361ea09fb1961c4e3c207e8ca85e4a1f74595b26f1b6521d6389fcff05f4318983167dfb8b8fd47197c3bb81a93de2074123332951346dffb9632630610bc109f390a83e3c3343954a73829239b5a1a2148bc7975044dc27945a4781cf0022a2438fa7beffef1ba0578c11fc6311e3e3f15b8754351a3475a6db93a2fe40791d2ae9fb8d5f0cd9c6c895365caa72f6b8e3f3f84588dc225408a0116a01c32760693d54d81832a1f821d1ff9470920ba879ea126ebf29e8e950c6c24fbcf373b6fc9d6c9305f32aaf14743734dc32e9c927c49fc5c2467288f6c0b0e03540a084f5bdcf0d7a9b702d327f9b6f23f6f91645c6b4d2d10622a0756c6e1191ec6981b653db063dcac0f0d85558b40a6ab732a01082430974516ef704a8ba86f88c9114f080c3944424f676b00a1ea8c933ecdeb7eaeca67c751275993bfeb6ed55d08f8b5124be225075ceab64781417c7d1c4974a54e019914a1bc0e2cb7b411928cc469e2c76b0505d53ad43149d58e58dbac06ab342c803e484d4c7d5876e91f1f1ac6a04387c44c48f1480238b9c67e7b8c4824e6350dc9cde09e59d12e583a5d460b4af37968537049490a55ffdb21de4b7cd16b1875a3c380731abcf08dc6f77b41906c918a91e9cdc7b6b5cee5df835c9aa5e01c3a8cc687b74bced277916a53de521d051bd9eee7979bedc92dcd02f5f37c3f1d9f1750f9a0609719d105bba4fef047e3e608c9f248cc63f0c015790772c5d826842963557593d6f190627a7a0ae3e7115659e67d3645b47677804ce52f5f35e6a5181ef5dd2fc293d07da4a05b34d19e9282c96e2edd203d3badac7ef0f9e47b77ba243f92c121257af9296452c67b54b69830ce31bc0e0185242818563c21445064140a7570c64c7e4f0378515ce828e6584a6909ff3320c6596f47a4249672bdb443f341a8098c5fe7a8bc59cf964ae59db3c97fca25a9226f4cb81a3e863319ef0dbf09f12fdefabf0ba3d72e15812ff8c03d0e83007c40171f083b96a00917fb132690b533ae2f463e5634a68b726457c7983fc8c45e946cb7c93e71c0846c410d0ae389dc345e683777f7011a984502fe309ddd8b20f92e38b74f6aa1bf1125bb5fc493eeb9163e5275060224ab267003bc89ce8717e6e46760df35d97e6450a6da7e8d4baee93f4c40f5d9484b50fdc57a75ffc6f3c79dfaa8f45bc02f1789b562547805a49e8bf29196d44f93589a26d8e4e7fa3f003160aed360b9d5229f5bc81b3f388d4327f45454a889072d9527026b38d4a8e4e0b81e873b80367c476c9f0459a319b9443ef5121ea77552cc98172044fe97d8e3853b81823d1b570171bc5f26f81ef28e7c36760a12fc3756dde0ddc98554cba682889784dbb495b5e8fe4dbb292c3840275e052b8df1a134d464e8aba11c95463a3408988c0687dae6c3872ee67eff3fdcbedba7920c2564a6f988017145cb542d5e8dff767f51798d6f9a2ca41e51ccb45806e069172624564cdde45fc2fa5b0217918cccd863982ed6dc6a999a8fa0140dcb0d409205a1afbe034b568a520e49a838cecaa87bcae147c3c8f728ed440b730af11dab346c8c9759c3e9d21f416bbb2216f66a8f66a988b293cf747f0effb54b50de0219435a3e5da1dfe4a6279810fae1f06f12f97bd2e413a20fdb039fe88e784aca7268ed7d60cb00fb5cc745139e29cd6c9df3a7deec9470239a9caefb43666c40e087755ab2f3ae02c273a0f32329da955cb81318b73ed598abd7ed51e797cdef33c033b37fffc14ba7c1873c75c8317412c251911bf0389e105c473e66f4de34711c340bb9e30ca7bb735225425f2a5d1cf183e80bd222bb1aa346eb07da10d82327e4850c691df2a7559e0bc8740f23ea0dcbd286f756d052af27e8d88b272e1828a264a332e6d798a768d222e82a212a581a6e98ccf930c41914d2b49db1baf2cfc68b6926b0d3662bba857696128ecf6b4c8dbe3b6ba19eb177f9b016b9954dcc536d20b36ada01481658956574eb5ac4f45c00a671dbfd00b7b30a12d83ddbea27468f964ab9eaba902193eda9203164c3e6e12d6b6216467c1e92e63d361ee31e75b68ae9b75295ecc0581e81889cadb782058d4ec2ee83f1466fb3313fc240a010039045093e8b09a8ab20547397f47cf04c39b9c11f269be59c7b911dddcbe9d5714f54d8e6c48ebce88696587330c540bcde665f538930b2a1c1090991a072170cda2ce0995d2f643435643181036fa8f3d86690f4452e56f096543d48ac1db1bf09316e48d6d4ef614ce6da39ce9415b0eb95731b88cd9e6c50d75a151971fd787a7a0ab667dbae18745cdf2505c4ce7dd71de432f614c7f5af75c1ed48b67c062ed4bdf7808bbf6c5bb4f6fbbc4667efcd2b7795c5b98b5d50e4fbf2598e9fb03dd99ee9f0443a1bd00243be667a9316d3fadf79eaa3f0e885288b89aacc71d0cfc4c7c83e4fa6cdd715fa1f36c0f55062eaedde3583fc8b452acc2c31283139112ae19ed9c524cbbf582360ff405b9fe2f36a38ce47873a039611b29871fe87b2f95c9c828dc937bb5d355925a2509c24e537e10799d8bfe1f0486bd7d04173f6902bf53086dbe0cbb6672c22aeff3bd986feb064ff887cdf297efc868eba833d67bbf9d15ee97a53d99201a18005f6b788abdb631ce43971ef6d9a83e77a1ae07d5a4ab2cdb3a2154ad0044c2dcde562c1f53caa66cd5c8bfc9b5a1aec2bc7b52517f463c508bafc5f14f784b9d7d4deececd76f63244cbb499f84b3dc31b585298839d1ad84b9fae73ce8f7b32be1f2bdd97c5b0d42eb9924ae807d198ccfb94986d682da4cf159e367ff38f6e5c896cb5f6bedb6227f041a48700a654d1c7a71091f15c239a030461f355fe9b9fa2a43ed704322898646d1dbc58b1676a5738ed952d072dbfc19473c5955b9edbae66e621c9af2715516d908504b448b358c9060cd0b527c13074278275f4ef06817e413a726e333705f94c060dd7c5b60e833ca5dfd53c92c5d9626ee93d0ca556a0c51b7b42864ecbf2621e43c20d419d07d91fb08179e1d92d487a7ec0acfe1177fab2d89d62c719927bdf69c8297c83d46b902932b9b3b08092f211cc8e48555d505dcb8fb40279da7af25d36ec0362a9a2bf5758b840d7b75ad31da4c9ce700c79f652ef3fc11691f1cb85cd5cc862f276fddd6cddda9c2acb1fac4db6d9f91cef800c3cb66674b6c574298c86517e9dda37a708b5626d7cb3f0b902d5b79659598d08e991f474a2f408bc71ec2efa9c8d353a61963438ba57e01819f861e102cc04def7824ce28fcc9bb362118d23e7da605b1a7142f68e9ef9e6f07e39ec6f7cd408ff77a5379fe85edecc851a7fe4e5437c8762928fbace3feb7dedf961c5c1858dc91635025cec7521803e3529b8474fa910b2fa2163d635442d00d20f08da5a4eb716d89b67e348ce0ad62c299b3a3e7cd05fbc153b887decfd2c278dd3a3474dc25537d79ea2dd2d89a5a0fe79dd6bd4760a5cff3882f8ccb4a265cf850e4e04e0a9ba86998e1fbe74e6c76139968baa21a4405e85e30e8082574b156b8e129dfc40a11c42f57978f4a6cecc52b7d0d36203d5fbf3e0d341f2be0b0a16aad93f882f7491044518e251c2f3e11936b6cd04f98de91b8af3e4b85c2105c028f45bc7289ba193d9063a91f01c6a51b4094235c2730983cf749476e6cde6ec2c589f960aa3f8ee24f18eb3919e6390ffac207ea3e5c5a6b17ae2b840c265481af94d72221f95f1400bc0b1b962607fec34c43e8cf582dc2a4a82de61f8f2de2cf872bf1d27d09647d67dfcf7666dd758449b22a20cf00671c1c7e5a5a678688cf9d10bbf071c573253f91b8594a6cf840424b85445f193c82c94e6f47f78d91552b3e824c5de53cce4f807f36411f3b3b1d195216f9148825151690f386cf75bad108dddcbf86f47cab83ab5652bc35d0cbf3214d8c45616ec8c6474360addfe01b33fb4eb97f085d95fd006be1a0987686e0b1fcd6c961b5f233e033ebe5d6d3ade8a5ed4df5aa18dfff697b3c0d441413449894f7751f54bf7a0623923f6aa9129523c73b99699697b1347fc09c50d72e1ad006cb796ceade633fdf0b3d7fad50240f1ef15396c369f259cd74c9675ef83cee0fbfab9bf56fb5ceec56fa43789ed9f24f45a37caaa398de349f7500fcbcd353913795234299374f16c1fed17feafcd0ba717af25fb93af79036856f170ee18e13771cfcb019c7b1b91263fc6ede3e0b7203dffb62192135d1c0074ac9ba4ab1126b8271a880444771a4d1c9243030e029a7c636e327df4249a1b37b98c43c06269fc720bfde292617f315ef78b6ec67f32f034b9fdb3a12fc5f6d3977e4662e8af6941c71eb0d474321a7663ac6c3f37fe2ceb183383ab0c5701771b99678fd7d6ba7da593b783990f95af84d1ca6cb95d0f9d764a9be731d518fcdeaca1dd1a5f49f352f13e7172f0d273b40f77d2ca8b47eacfdd9d1c27125f661239ff1e254804a95ae757ef9c85dc4dd3928ebec8e9824f565a41d14d783d61184f199230d64ef95d27b15dd5a14c0b4db15b8a6df7352c6b782c5dc57147538ee7fb2af9096e553fca00ae5c33734d9a36e3e2c0ac7bbbd5600b5762aac6ae395ef9139fc2d3890a02c66b85658087388f3b2399299a7cda76bec22742ab01055ed1e8388f632f39fdfbaf80eebf8ac1b136fec7aa02eebb05ebb331f0e01e34259d293a7104724c8c7828b12635d1cb3ac0585ac0ecb4a6aa69152cb2feae0a43f79b731e73a79bf6daf6772989873db3083794faff695a140a1daf7fd09ad41c5f0a3dcd689b802ffcdb4db0227745180c5f6c6d9418092ab70d9f875a51757e3a6edae7c6cd56f75909750fae1ef8c771e02370e5173f6c1f019175908769b76853c703a9635c26c8885da3aa49eb02b99327d8627eb8e2e4729095375cd52fd4360f5c5ca9fc5ae1da3864439a76c9ef433a51e854221a7aa746bcef38912b41872815d610e5ba10e918777a84b6703c4860e512167a55d5355e2ab84a0d41b312927c31df6cf048069a96552a72611db810911b2321bd20d11c0d1f8f25c8e899c582c1dc8a9291c3f055010676264e46c27b24833ab8a6b8afb297c2d9111379c8ac34757aa912e0c13319c1f5d53a9fd5704db5a05ed18a90cced579fbc87c3e04c125e5ea79638486aadfc6baf71cd5fb8ac0a21245843b95a753a5c6c8b2cd1c0bd489cf0e42340d19bbc997ec9d38deee799e3e7bda8dba8751587b67deec40f6f361bfe290c841107368bff267f57a0e6ec87119b4de23f622e855f923b7298be61f9632cde9143252d7f46441aae9e41869f784bcb78a50c909a0a2dd498264462f7846d37cd1c6fb0ff3810175a8331aa0003730f2309bbd18b6b51e4f458a641c68dd39abba7a1abec334b48e3109bf424401aa850dff99c37db87ca6e995384ec0a1707d1db82d91dd7e7f3481fabc50b45032bad4e84a27e26f5331febb74a71454615823215dfed37e99bc301540468d8057b82e0eec7d919775770f4eee54cb9c140fa0e6e52e0d020e1b9a8565a041f32d519ef34f41b7ce9dac62153235b3f040bd2a5f2269b2295ea8e9c435bdeb3420c56a81a1f5eb4b2caadad246ba70dae5c9ebc0f6b5e3d52a49cfa012831fb8bc013b912bc372d9b2dda211dd7e7cddba0b93dce3252097378b623267eb22eea9c492b5195fa6c4b2b24a03b7618a9a44b1ee017bbd64c747ecc672289141f46fbcbccb7132b89eca23add6375857936b7e8d1b8bb27ac63b24cea7be5be61bb802a28a982a7f7e8975829d39313bbbcd60915ddc5364c72eac7274c9471f869b40be292d903d0b1c0f573a087b11f281baeffb89eeed3107b86f05eba940bf256a3b6d97937a3e1d0135a5ecced4b1eca5a5c650ac5867efea45a565f51150a3ba256dab0c35713435ba74634d0642f4463cf00ae09978a37d9ca77384fe56c8fa9fbda312b8e47393c11800f79e9e67ec01fbe2fdfda3b3f79edc13bf7b7215481bc17883f26ff0c2c3832a36c348a4b0d82e82d5fcd1692e6c0a47a6acbb5cb95e6d796a8efda32b5bc43c250d28f3777335fb78f4c0bf4b4ef82b0cf76fc951f4b039906bf8a4ffffae0ca805c55b31b1f6927e7ce88903c323ba5771e261d519c09a0cc3285927ed814ba62d0d85455603445995100fa27ffc8cd17b73a82329c322351e0459c460ad8a890c5b7343c89bb3a286b715b205eb7d131cd2852e13db7531d258b87ccc72e823bd1f42faeeff35840230ee7173eb30c61fe812dcf6429aa1df1e63d7825ce60e47586043d0db7ff2bf198d92b017123744c9ee5243d3593addcd07e81fcd2a4fcf9a50cc1483f00c87fc4c401ff3dd77ff61179d383374e28abb6afe392b8989dd27870fd6fcdf3d4ff6ef48c18919dd539ab00782a6141980740e381636d9f7fb65cf7b5c0941efbbf0cbe3018b415bca5f9aec59d187f92137b3e88482d8560fc1229637402b1285b9f543945f9933f16041e926a6a7d5bdbb16b2c3368f9ffd82d10d728631c0c39e9c8c723f732af660c5eb1234889230a1bfe00a272966b10c744d81af07230df97d6cfb7eb7f520326385e6f3106a1a58e4cf72d7c4199c8ebbd2dd844420722ba73526bc23acdde2cde7fde4e3d74580b00bc320aefb7cb248599aa8f0028d19b1ef8ca811132b0625167e4568dd4e2725c2bcb8a80633d4b06b9cacd68576bec2f8ff83093f49a153e9862832bdbeb5e19bc6049309b7f65e37f14eac75ef52e627d93b02950c7b66e61ecd47fcdeca6780f6764ac54f12560c77f5d8f44f325f7cf84baff2fe71b8df3e8727e430368790eebc8350f7838aac0ce88a66f719ad31a4d38710d4d73d39df2e08bbafa8dd17f1c6da7fc54eb6151b2b288a08621d7b3bfdcb9b34fa29c5fca6446f3f11219a5868793e7cb95f333c355ffceec65826f970241b1a243674872c814558d36ab1e88a2497543bc3097a4787f815d53c644db2bdd666e398d8fb60837808c83cd4b21bd7047a777dc4572f713d571b532fbc157582304a16cfb182c218960d9bb0ab61915a6948858b269de959123d86245310c9d026888fa471f8f1a7920c593ee5154171cfcec67cd311d3a969f31c44c76ce78f3c492487e0014c4b9205c65bb44e3df5e74af32fa3dee324eb2a50849c831baf085246dde094df04e16b248c3b42e53f9b56553e1d939579fe889d7c81b9fb19920352ecc4f72c43bfb917ab98598f5ffcb2186827269e9a6250d3c1db83d4329367a80901244440e565b5d367c6aff9813251e8f21be50c8c78033c044f4a58fc9411c2d468d4641ba41916944a0a0967c63a542b7a9141b131e78f49afd28e2b3f5c4c9cb681a199f9e45c69da8530cb66fa9c611a2cf6374009b1238c51faa8978bebb3412c0120325d7a6d94cbe44e1932e79390b5a8c27cc766d5ec86a78fc034a2a9d6e43c31f5f0d7bfc3a777967f5071923d6f3c6e6ca0f774f753b8acfecda3d54e15108b74fac5b28e13b10b49b52ab29b6e45db67073160c4c04b87bbe9a92d2b1d612d870a53e8c1fef9bbc38868f2dc7f3b30e663003a90b9c9d5871169904934f394af82ef3b3ad22813f97975d8162a6e9beef15d52436f694164eeed05836ba3b86684ac203598a67b0a9c081be8a530b89da8b077ad6e760713f5228a12b62cc6c8efa95965d236e30a9412f97525795635a7ad9b2d5ca157f907cbbb0d3f0339dd75b79f3bd9272ca0c1ae4180ea83eddd490d8a248c9dcbcbd84328284abfe162ec49a903af9e61ddef46f416c0769f319b762c2976ff426448dc4a08360ea7f63f393c8c54996c63af9a2557b61bed7e243475f49d6b020daf72cd596f158ce4fae517b821e2810c61a6561e0c67c58a48c1e8a2d65f3a42be4b4884c8ad35a5298215d9c78c37399908f914228283f4de9ff03a47ef7c7d66a162d85ae853920d20abbfbb0e7649166aeb51ffc17798e7b8f5cf75a94756a30b5532275d5768d8dc3700847844e707acf2163b18d161d77d90929e2ac1643425a3da39fea0d211ee4f52ff74e0b602783a8559cbe16e73b65c900cbf688a4d452fc87a1e1b36811636e5586a37e3ace6f32714970e4cc67d86a50fd5a20c39e22b19346d520461a466d6b4fa52b2fba29bb24fe6cc4a2de31c8dfbd6f79988d34736553dc2ff14339ec63858cbfa7f693ab5001e40846172fcfb6059c8fdd3dea8f080804b27ca2849dfd63c9781572cd82c09b40f067adcbb41755a8f6599d419a844059cea8e6f8fda0d1e597a2c52942db7c6c59efc33ef3287cccf2fb8746b4905f7f40c964f656119dee42d168e7505edaf543eec29f1a07927085bd231cb20b6f45f3bb6f4d702e8db76e7478edfef609a7e7bc6fb36556effb5dd0ea68232782ed5d006707fa11e6bbfdd7729bfa8406e7335b693e3a4e50ffcaef90b3858384cd85b0e03697a254ecc4bda7ade8afb2b2605d7bd99f4f1e9165745d6af945d7891d3f9fbf26ce664e13082c7bf7975c01b17ce3d29fd889e2b2cad1ffc6319e825c8a020fb11bb63f5a6d38f5eba0f30853ac0769339f56b04d38ce676a926e0dfefead2cf7c68e6831d5f93cb9ad12f6c6af902dc8b469c16ce59e8d0be835efbe4da22f0fadeeb0f63e2a763f70f09554fe4275d69faa030ae4674dff352d8455007438e58e6749bb712d6728091107d87423ddd6d2a6002ae14dd4247b9e53a6c763b02609c026687de250d051c63e28227e2702da225a32a9e7e0db499f51a367792e6d3603c2db04ebcbe4bec7ed197869ce4deca40bf5d232b54f6763d88053ffd30d85723dec5a3bbd7e6fe3accb283b267141dce8fc5c01f6c66ac5b49001c3c7a5623ae8774402e2ae8c25860b9c3e8fc957f0b876ce8fbb415d6fbde3c296e37bcd124fc73ab907a02e62ee00c10c59c4dfd50dde373372c1050dc93fc502be87ee0a4816ca209fbe7f72ff94b875ce7bc6f1cf23742fe9b58b0abccec3d1f033a3704b820d1657eae2e88db7badd8b7d3e3aa0a8ea294ddd5fd84b49c19628bd74156365d47964bc6e6688ff78b4798049bb425ea6d1867adc613eb3d95778a57fb6b31bbc842fa238596cef0393fd28818e20660698407dfb1bfe2ef5b9c63bdf51b0caab9aac0ba0f0ef4ac6bef12ec63dab7d70f15ed9f22fe27ee31facd064900a2f3344826dd7a17463dc0792addde71725ecae8b373745ab61f48ce160017a64710434695c4178795fdd6a836dd1bd7eaa3bd479a941d08a2a4b02ec1ccf75b49804087fd8cd17e3286098e4b3ab4281e7e4a4c2e0747e95b3d7b19d9e4acf5aaa40c9a2e96122e3683fae7f6716b7cdfc8fdc87662d023b8a847fff44d02d437eaa9da79c2f34c25fe4b8c3fcc454601190fbe8aff704e71492bc22f1c8a46d38bf079d4d7d9115da029598006533adaaa4d5012b44559cdd23ad52b5280ea763ace5bec179897f26e4bbe487a15384d6e597d5e2af5bbc5340895b2a9da29f5bb34bb148d38ce5adce1a64ecc1e175c7e711aa1bb2616b2a052edfb267e95ac5b78751e9dac6b54497d739d44ae546b58306326e5c1cbb4ad03443fdbe01b9c2a47a63ed38a5ba7faa248b75596034a1fe14b541b3c10491e3c7c2fac09b3f87f4bf73524fdeb815e2afe5fbaebda600d808f669b3c3d48da24bbf20bc4b4cabba180df386185462d937904fabb724df40424f1bf5dc0f73ea1a83ba2825127908b40d41b699a40011423011ad3fdff80e82b178bf78d68892762482af564bab8694c0f11dfcb3b56d8527fca1bee0fc5fbe4f84ecbeec2f0728f76fc32acc1ee8ce4e28d0be2cd118ff92565b48c7e1efe1e2c7c2e2777b3c8c26379e47cf0533cfe8cd1a9762070b996b31515b5cef7c7883e306a82d24f15d8d158e425465647633d2d56df429d0fe4bbe021becf627ede6173c37b3c599ffc5ff91a1c898805d5e85c3615ab3d1fde84703c37b9622b53c9d661626de44a7f307387be77a61c8744dd466217c45e1e44f24f438e53ae73b5707de9feef0d546780c38c389369993d70021c58c01e2b8cc09d97cd3d7b47cb26687c0cbee2c98bfee08d49283021af06752023f4f523dfe901a1d3509916f3fe31ae2ae21767e0d11a4631702f67c25361afcbeb01368da6b02ea98bcb2433706cf9bcc5f0b4d50300a5d343da6f4276e37e4ca57d71ef41cbefa9940b34e5435192a9b4ddce937ff9125511b7ed760b81cc256cbeb2443709307ff55e03127921cc1692a85eb4d0e32a727cd7e4c762c2701bdca85ac3840b3a19139f997dfc5c5570e5fbcb6299d64c537addf23121772e8f46e18de100569f268978f5704c7032465adff57af5d35db80644a465670416c586a65e696de798406aa3ac1999ab0bd814d41917c3cb9363ad738787ce6425a8245882ce430f5e3a3d97c6a065423eec70f644364dc53fac7747b027af5bf860860ad3f49ec37f4b5e166abb2a6648db6a52bbf0d7812b85f56ede61373e6123631692235c96af8e3f6a1568c7e4bb5b2a27f7c7d63e1f91078b85bbc235b156fa637aba45c4e8b7bd1f78fba9a00f98f37732428e0d88626558eb23e628da1f5cf766625daa75602ce83e6d5d53951f6191863e704c6bc26a5d31ac759cb4875d6818df37c48611f0e03c8d90907092bcbc8e3d6a49282dee176808614e099e589ef788c82f53a14ac55ee8cd60b38df2296395467b4b11cc4d4365504b71e9f4018b5bfe44018b3f403953356d01d1eaaa720a89a2f0a8e866e6a402369586b00fa8aed914153d87700e1c9269d6e40e701888c4e3611d92f6f407c5651e60438e1e20eea0f09442cdda2721025ac47135e35ac25aea5508e1e6d408d8704416992d3fff1c6cf2c75d1c34120910b5214bfaec8e310d3606855225ac6de33ae44ab80d301b7650a4e57950dab7fab2c04cf29323ceaa55aa1d1b71b15c2161ebfa5bce88bbb62a8e8f0a2008d95cbafd2f58b13f6b53ee0f5f8774226406fde8b9f2035a9260398059ec2a4afc67ad42a43a03939bf0d533afe4537fb3982063bbd117b1869a537541b6146faa08c5b4acd8f42d364ede60fec7ecf381c54cdf20d941a8f7d3dce9af9b6fc655b4788b191f18456af233ef58683a49fd26880af91b5a71d5ac4b5a58a57941101e8bbb6782d2ce1756463f1161f38accde6ced09505c191d3192ea96959ba1d0cad1b791b07436f7c4a7edd7d41eb94a4d1bc88cdd5a005b6e34f2977944a31b2d166352996544fd5500a10bfb71ad848f29d7becb2e264e7d5c8d69b407604fda8f62c976745788a9590241ac7e7b6a78990c3061978adc607554338bcc9fa96ca7ad94b8b38556e1ac0c7b0b374612940d6be83e71bb44099bb9f473c17489ec19dffa2bd79c97be167037ca18b96e7cb82496cba90652b57e9f89a45091269746590cb054f406fc8fdf6a1681baa54e6e113e4e10ceb49d8b4addad784ca4f3d303a89e7ea8e4bbe0d544d6d9c4896980818e0350d3433287b2408a67356825f8e31f864be8df246643e351b1849b2423b9ab5f1a29ce692a4c2d5041770e81f2c69ece00eec55d599f8ca1f05e284d7cc2b905049836ad71a02ee020680025e4091f689623bbbc06a26d87afba09dc54ab46cbfccd44dc26258741da5c524a1c4c35f32254a82923ef500b14471c844da25b15968b5a327d8d0524b0860dd55163c0b168020216cf1658025e8e36a2821e67431f95ea9531ce7ac7d219b5a9905a628a209e693c28b511ab98d790b835867206e565d2475fad2886006654611f6fa2950a4e22c98f311290b9b357993774ec1e999473c3ca9674f34d7194733fbca5c9a91b3ec5dcb1c1f310ec48c74ee8975074cf7f34ab49feda228e10e8cfb927a2c20b70843618b71c3f39b265da821546908daf1357c680051364f6fb59b1ab8aa6f51bfc909114fb119e3c3e9f7bfa056051b651c2e4eec061e9f48ead5f7edf62bb4da28d489e477ae9a964454929d2b9b9a98c356e2411af3bee2ccc0cce9e3def257bd98dc942a61d5cf20fee5ebf6a9518ca230bbaa48818b098e211d2732383f6386feee8fd193b8fa1eeab98ceb218fd02f9ade5ccfdab6d294356f8762a08d2e8935e2aa124fa4d66f3f47165f35f69b030918313cc0fb5327f28d3551b9e4ef341adbc064a241c33642b071499cff9380b5a53d098cd453360985a84d51f8720a04901bce62a73cc97eea468c95681cd9f09c75f107b97787d0a21f9b076021ae7b0f4a8dfc29b8db2ed7cbf45410d0a2438e789fef09a4b707f80f523ea689d13c4ee01a72208ea974c811bebaf3951e928ce22cdd1dc42933630067771df34dcd52b58037a8ce7b6d151252951df35a66e89feb1ba988e9f7b71f0fa2b3899242509a5e01dc4f01942589614b7dee21b1a42d5d78faff21fc785cf785648ed9ce752a1f7096db6c7da2facf57f4b25ecb257174d34f74dbd82b84e334b8c05878e5427fc6835f550e61869423ea253cf233b4385f228dea20fb93c0cd370dfaac5df3036797d21af38e2514d90ef0d7eeceee4df4c2432506c7e1c730978a079b1f57c8563f13460a802337aa37ec0f38a2ffd72a36e1251e99febfb2d6ba6392d8398dfd3bd974776f731e4e064f5c79cb2246a38b97b17fa7fddeb8b042f8bc9e7d24a1ab807cc32f51062afafb560caa39f7cfb22d10d58d0a6a6b48eabfce3fc4792a5ac6c53993a07931a1517d4fcaee1a8c326671853493728f336175432f74a5e7d7bf285abdbd1075863f8a969bd6ae388a59185a17a0b726c87a828cde6859cf19a610b1f37ca1fbdc08b00350a4ef49a8e1507d4e749c29c1b580abce411a5a80d663901598777223f2d0069a3104ab6edbe6db6f127a72cecb7102a759ce2ffa3d042ebfbc287015eafff100095d586a4fe9a57164c9d0404c240ec5b7fd2170a0553fcf8cbda0ea4bbeabf0fffbb9d608f8f6035ba32da635991ea3233709b764937425a34893bb89cbea9940fdddea0c006e0692613b8934cc7951c7d08f4d83c9b9a72f05767c788076432f653df342832690cb98ec03d532754710d792bb03fc41f48b48e0f38efa0c2cc33343ea13dbb40e05d44cb7e777fdea8ec6977c3939ee07cf32a389f7f01b4b8d391b0d54712e032bb2abea96152b3e0900ecf977c0532266096360ab566956bbca81492914319582fe7d893d72470c9a31e6093728143ae30392366815f9c43747da68a5f781dabcddceb1594dd636726dd9bf7dfc7353fbcf6039106bcacc930503c598c3e198eeaca833b99cfa000b0e5f70e924f31a4b9b06fd21a4579a5579e599b17eabac0ed9490e6a21511724c39a7e668d062d2742f041e32572d491b719a2b0a0f3f50e7b79bfa4b566941fd1f5efead09283bce3d7a696e2748a4a6a148b8616dd26227a238496a1eb8d5da8116e7a3a9deebcbb3f91ce7e69cb6fe39497ddbd1e73e51e7a3de9cdf5aca1f64b1925ca7b5af52de85bbc8c83cba41fd9bd358488467313b5af30abd9a7a102ebc69ff007c1343fe0b92f0ed219520f942b18101e22280f4a2cb4be20ab801e683b07566b28947461ea255685468d6be8da8bd749ef31e8f72f5d482ab35b0e3e2ed85df5da3c06e21ed149295054cdd983efbdcae5aeb5dd8cd29082eb76e5ace2160cb2ec66af26cf8f57be14f2db909fffa2857f33eb4f49c485d5bcfe8794efcae8d5287542077043597a7c2ac88baf77916b64058364d1883a10b35081ebd75bf589f56df211bbaed5238a52a31a81bf97a70a02241162b3f188c9cc46fd8812bfb38fa212ddc2e7e0f0f3236c3340ebd7f3d22e3c59381c7f283bfc22fa185db96dd0b2a8548560a4463556d8d4347e42e264e03d1dad082416af612f782742340a053443879453449bbc22500c100e07c53e1010c96b7bc310c5407b827c6fd9306c401e8a340c12a816937ad8ded72ce0257aa2123e4cacd16998df3798d3804ab7bff5a8f4c4dacc76fb794cf8a71105833ef35805c7913a50dd78372a61f79d163b7e98e511dbcb5aa347c80b04773be5d1ac2b0fb9b46c496bd01feab63f44c9731b34655a26e81dbb857a323db801872330dd39b064c7a6187f0d6f1a804de549e6336bbc137efa75d29946a678a447ea8f69bd4c0371ab392b77c45114616b5c8d5132ecba0d2b3bd6debb9c60c60dd72cfcdcabc3bd52df44380c692def3003b041224be775e2f25954a8531b8e02f9c6d7a23df1370dfaae590555f7452258ccecbd471ebfd322b0c27e07605573424fc0b3d857e2d4463cc2c398e3549bc89a99f6358ca97f2e9258fd6e8cd8e66dd3c9132220b1dd214dd74b74c6e3299c6547486c8bf75eae3013c60975f547ade016bf628eaa6625736f1816284bca08dbf8deba2e5aa7973bcdaa599510b2afa7a4a09069686a75cfc97490dd159ba6bec4ba574fbcd45a533103c99a632f6baf0d84613bd31fa3d542bd6aafe273d07808721ec4c5a938b5f3b30697e3f979fbcb76cfae5181d66824a83d560cd5c98d4584725bcb2ac53cf01e407f7f646a3f267b0bbb94aeaf791f357854965e3a574b16807ffbc9ee17936e7576dd025cfbc125759864657e6c4d9ac330efd23f308b7112167c3c899ba9d169ba2cf84e395006a872d7b5a628c49529ac1028255b3a7ed3cd2f697a3afa92676182ef59b2222618554f16432116068b7e7dde2a6e1cfa291dbdf8989647868b9b14419049700d8dcdaf867c794387e4dcb1e272f9f49b8251d3e7552417ef0fd5807f7ef38720c5161ba1dd3a187c903f6ea0ae50c7a700d4ab9b3834c9c7aaa7ea78d758df7d8e562d5ac16b0695ae5563a3b6329d41dbb81af17587f00333f6cb27faf3554c300c5a5914930d934b3b5b79341d246f4cf1a65f67405b3df413a7f5bc475473214fc5eeceae7a918a3dc6a53e7b55315d5598d9b0a9479d56aa143516223ee3a052a7c711618af67547680fea3c278432973a37a8fc56e12b695aa1828ef8bdb6be00f89d787558b4e9597546212ecb2a6d72239a9463c93ec5eee27759e1a19be9f2cfaec8e54c625a9777a7f9589a9175293c05cbbcc6ad416412d6586e89d4e9c89e74e28103620a2b087ea3af1cf515077727cfd44ff9fbba9b50672a3e4ad96dc749a6ec8ac7be298f94b90e9942feff41fee050af15ca978a4e7ca7c21b5af31f7da83014af797d4d303b99641783ad43166a2b2446ac234ee4aa30d54fff9f9471862bbdbcf87323492da19a71bb3e82093341be508b92f2924329a33a34910a034c9cc05ab53e55b33dc7425793d8e6631917fecc28d34e1dee20f233b03c69f518f6d27f4f614a661ac04e2e0ef83ca521b17e12a5aca8dca0e0dc3409a5b6c993f037a460443717762d9ab06cbaa0fe07283c0c0b4863226a8f520fce244be3c6a3297d10b9c3fcf696ed231d79f232e10a7183d5cd804c153b68b4189f9de665f3b9c9467647631fe7e3a97ef31e6769a6324056393420bb3c6c13217bf4bd34187238019b6897e3c81a6b47ef7c031a7b7d884717f18d0dda1b99510f5b7586d8562b40cf1a671acdf209469fc303957b8f16a5ab8d95ec968feb227226060fb504bd75254d60a027d23c5f50b11691351b6476c1bb2636a8982ae17ffd594bf1f2bcb7065ae6d5c0ae89268971d84fa5f7dcadd865b813a2528a88a70bdaf347cab3ccd25123c221b5b195c5fba1c53b2cdf57f8c7aae4ce3c313f5563fdf7e42e6ab7c3dcf894614c9aa7fa3cb3addd1636c8aa49a082a7d2033d0b1781ebea0f386899f592a36dff1a1ca453d041f94f64d58ec403d0921a3ffff117a727dfb9f124a67f06aa82575c3acdbf619a409461367fdda167fdf9b753dcf2d567242bc743f7fcdf5ace48065972302fd9b8eb36d9b00f804ce5b8b994b65d72ce2ca8e77de8a174fbbc15be42856d2b3c3c1b0858c71e1f616f0d565e86ba28c3adfb108dd52546ac5a6ab90942daf4000247d8eec984d99bae444c488c2341a1a84e05a9f82cfd6d3d706eaa314d4cc675255c1f873d10b2f1999f3071f3dd384e7003dc12e429f027be1b3eb574edf60f18d8d93f0536071b86d3fc29cfb1c0ed6064a63ca4f76cc5c93a1b7a6817d25ca82dac442bdd0ab4a497e6289968d76c18d3a9da7c909eb8244eaa7bcd3a9c8ad4876ff292007c76f1975721600343ba831f1175ad02929a390eed0fe3ddf3c40b28a3f485d3a2c7d452629542518d9b270f000252d9428f1c593fe22cc984936ab67f36fbb0a775d51f59d01e8b604fb7d582ba41cd7125ac875f14fc8f2384c33f196f8f0a6a4c5b35f1e12aba801021a1052539fa40ca0e8a378c8d5aa9f5ba22628179ecc701d44430e751b0f47890c5295d2913e0dd8f9134aef04b44da6603b4758d1e4da7b87db5d1680756669fd0c4238d73f3ee62a9e2649a5bf68b0b10c468bf968e7700fbb558f5607956ff34161b22898b03e57b6488c75b1f047f5c87752e3a3c8d36cfd2d46940292c1d47953f257c321e912ef897c730cfffcf7dbbdc5711335c8386f280330c7ce23013ef3bb48220cf97d6253e87626ff8e07b74943571d6a973436a47a8a0cd47826779419d8f1fdd4507871fb00d596026c508b3dfd0f8618c63308fca03b76d0378972b127df97e2c892837186822239c6f719036d9e761dc6d99b01cf86446a7d2d474ef085e74254e589065a59805c2e8042c17568a75f7ec3d949ca3369ad779248d45a9fab63c59330b7c6524b734f6354756583c378741ea65c6f661e4f76d5ff8acb47546e6d80af278f2b8ac20dac53ce8fc22588aab7aa7cd6610ca6edec0c5e4f41ff75cb1c58864f5f593c591806a2e98a1376f1ce68fa028e163e4fa13dc4c53977f01a7c2bd10b2601ac6498fe699bc31dfefc3af7aa4f0b145362b95b718bf0b06e57db346899a53097c4f90f8539b20d0483ed48ec2297183787f30c5e08a78ad1eb31ecabc4dd54f289a97ef806249a3e5c0a4ccbaefffde76cd69135309e42bc310ceb1a703a0df8b8776f5bef09d32cc58c4be8e5462234fe51aa2921685b4a8a35cde03a37ea865770a075c79a1175091748e5247b8005aa179f4f4c282c46208f754e32a989603f6cf0c2112143ff5d9c98cd7b8438df162206040dc7fa236a4d7fcee86dd9f2d91adbab205f9f56c028dbff65ddc45464b9aecb1315917fd07aa402cb46ad9260f8a65e2284c6c5cc28d5f0b791a70cc053ed3d288af8377a15d681a5b3d4fdab8f81932b1b2aed73ec4aac5b16b57fd4cb603038f3b1bb1529fa9f303fe71714d74d559a60941f8bdeb5a135449e0ce8ebf17128ce72367f9cbb048245c49b82c696d96eb984072976608961efe098cbd092c465d8ec12da1cc87dc002e7d7109b47a7ae25953ce78ea0fe630371da2154d643487cd183856fcc6f4efcc13e2e3a91ab84491cd7890f465653c61f09f88f91da25dfa443bb7d0d47e326122fa4b2ec5d013fb80e3b4872f9ccf137b8fc6c58c1a023591065f24e4617dcf7b5f3ddfa40b24297c71149473766cc7d30fc04a4bfabf4d4a33ed232cc2e7bc65e16116e9f3cd760c15ee0d571954c3449c5af7dd0dae273e4bd50a7f8e1d3b381f2baeddd3ac8f7e89e9d5fe75d538205cdc08fd3c194c6276c2f488ff739e4878f72445ccb5091a3142f60a59e38665a7a1ceb3f1c7bff0cf047b6f7bdcc66aace8f3bc47e25b0d4f3fc3b8f995fd1aff93079019b7dbd6d03f69620d420c9f541f9e93de3db5d6123e1591dd6d0e4b7c2fe729f1cdf3940e31b8465d2212b419d4efd3c476a3793d1d7eab55bc6a43f7b513f83046f9ab9efad3a403de765b29c99d3e66bcab5924451f023e3e4e0de3850ec04c3f7da647186a72950b9a7c4b84122a9dfd358c64cd06a813988b5be5d71454b8abb8d415d73796bfe873f6f8c65c7bbe9af21b60a72452716d14b67fcbff2fb26dedf31eea88fd09c9b48ce5788e2372249b298a22f39052f19c1f4390de2b49b4dca4df450d84da966fdb39aaafd2fe630555a4e216b4d9000becb8499da87db867665a01564dddbca5d7913668672ca526cd3fcbdf7d76c72e93d7c5ed03d08e3abea7a06f15e83de08244dc4f52ffd39592f631e599612ef83e9f48fe3601e3f5a63d897ebeb92f711ab311bee64a9cb9eb1154f078f580c96331679eba5253eb7180b8dd2d59958952a7163c3ba42c12400593dee7f192b9c27aa559866a070a6af2b3375af3eb08caddcf23351c4c6c9b987bac5e88656fa8c868b1b3b80e8f43174e3809109dd880abed5b63cffdab5927d417f93a3c9095268d72412f662c1c00c9a357e21ede9e2ad2889cbcb6b2a19f8ced00c9cd34a841d5db0d1b9398b5a055f34e22fc9138390782ad01ab2bfd208511bf8a4c6bcded921d00f28db6687a131730720b3865d9215ebdbce37a4bd3c4f0d44b3ab7341dbda301072dac7293d54aa59827a30b719ed3aaba611b31ec4601c3f7bc55ad042bd2ff863eb1374930fdb0120c6f4c7dd722af182493d3ce87968ce58e5cc68abfb691d343b6b4bf926043060b5c16ab458213521b2a88586be3670c7a82ed617782cca56682793be9cc02b48c1a24e6183c0e21f58b3c1a44312231d12ae9ec3ad3c6020728f92818c79e206e7583012053a7650ce57c836bd52cf1c19c2d0882ef102e676c091df51f8f8d8b5aaf680f06b3135236b8bd2ddc77645af084037c5caa4c4c1cc9bd1a9af192964810b4b3cb78df6621b29798737df05d27876452b69e82785b5999ed098ae6dc58ce30c66606ae7e5caea7f65022e7767865f1d27cf816d7fd7b623ff40fdfab4913e614362cda63135fb9124243854ef65281264f23e390e9ff1de2e22448a53362ec4e983c1729c0538ab83373804879e8a3a479ec40e4ee939729bff5ad83730a5773a84a83407d79c403082a69ec826e502585835a442c51737eae9084e17473aab8c52d346b4c6bc5a155fe978d8b2641e6a694782e44aa68fc5b3e94965e41c6673b3f7a97018549b7b018011a136e8ddbd99af677c5798c4f0f8fdaf92c896991b2ef76e36b75a20dfa8236e79b7073fe6343000394822ecd0f39e9e86e4f6902d3508761a283f7c486100f6b065537099e38ff8e8c3bc67cac1fb0279a6b4ed0b31df53c081f79e01d1d874a51c23fe626cf5fa6ca09c6f314aa223393cd842e4a17e64aa0b58a675433c02da79aa18c573e3ae9fb226d81d4958f1ae931537df44227939f017718c0864b8dbfceb76e03b847754cab646ee8dc76985adf5bcc259657c1b27f8b93fdd6dd834a1d877c81d308743a9e080ddc2759201bdc4a4b04626f45ba465a255c10510c954c3768c4bca894a5ed8815e602afac554a4fc859c9d1706b010113188eda3cd9e11d133280746e9dd87d7f845c7fdc96bfbef7852324ea16f472da5affab71943ea61ffe9c05561852b23fafaa1e4febaf206f7fc4f04e27a32e5d6156a73ff423c72949c65963035bfc702b6e2b7ddbce9116cfa79d08f216c5b6901e92a9b8bc138d81f78d7698f2398b426cacc89a3e11a636a0d2e09ba67f4f15d15bdebee2dbda501ec70d37ca5db2dcb11e073ff480e9d50171488995e46e65a014e7ef223925518eca9b4af239deefa98ffcfcf55b6551700c8007c371b23f81bce8f700bf26ef12bde59ef5768fbd66e3acd6bf8646465e5d1fc94fbf81a7fa6a4a8aa128d901f8620231ff89c174289abaa51466f30e4065a6be8c6e15e7d1583e01e78e48683de11416199ee6021e0961d91709a37c9ac4b8d3940b3ff676f83030f2182b135820e3cd3a40c06b89c10a8d622347961cce941b91893a1327308f5b6ca2760012a3957901336e6a4a853eabc3498c17b09f4e09e8bd98e297cd9279162da51608552f2d2162d1fcc022daaa4f4adda9ed8bc8f47e2edfd6e2fca90bc9ab5d23377f3d0ac40e4846cf9d889d5289644cd94e6ae23216cf59f29a8e2a2e07fd24de6b6514cd97750539a960b712e93cfb33cccf7f45860a9308e7659a90578960d690f6a5256c01567f6e7e31961f6ce79837e35fa23101544b9d40a7ea2e88bca180c33add2fdda685f89ad7fc18f558768ebd97192064b9d54b5e7da1b256064b9df8304356c10d62251ac95cf5ee30118d7dc694009409472b1e7598a59cea89395513e97032c2062bfa38979464a51f18ed961ce5b52f307455f75c0b5ae5c8a0d813c5ca7adbbca9bc5bc13ae573b9bcf5eedccb32703c56b0e889742cd06114987a37fad8de4f6782c14a7976e23e36410af7d84c4c42e156da96c5f53eab8782e4f5a87f45de36d579c27ef434028ceb9ab23036023744abbe1fc2108c1eb7f0e08d32e1b0b5e9b99e2b94874d7c213e883ef5e67daa17a821af25e564fefd75f9003d390492193b734548edb4841ed27221129da0dfd343e9d3b525ee37768da966d74549908f6fb78851a26fe594839f1224ba87ab3ca7d500b7278fffa2aa18dba8972d4878385d57f480d6dcd4fc5ecee98fab82c48eb0fdabbd1ca8175f5e118500403b5ac7b3c3ff1ae04cb81eb196286194321243a4063b25ac96d3990060a8582309a389c8f8272ca13ecb6d7358f4e1ac96501cad925261342e86b5464ed9e9d7b817f3b8005e382603e59648a83aea694e22a6122a0ede7441a0ceaeebf0d86785119debacba6bf634cb2bee174c8f85f45bfc5b35fe7d961d8ad4eb007037d4b4c83bb8cd1aa9fc19e718c075f74d0622d9c4c9e0bb5d0c2a459c4ebdf787d40b81e0a1e216e71db8de8707cbebee401d8f9d722f45bdc8c081b1ae91929a54ad99a88bbc4d2cdb31d38bfff7aee2b9f396ce16b8721cf95ddd73aedd64e0db0fb29c5427659fbe29aa1fb05bc41044f33dd369a9bbb7a4b134f1e8876096371f8031c089e02f788d74bab043517e921a8b123f8cad3d20d0ee575f38eccb05ddee368c46b87cc6a0eec07be4244078338d308efa1ea34bd9de0341efe9ea4e4ff5bcc7edfb37dfef540389ad557d3e03318d716d8f7ed9fb7074e1c601c21a78b48df32b941f6dd000eeeacf88488da287980380e9b61df9d3b6f483874156ac90d2601ef3b8d0cb6a877970918c185ade6eda0b984dc74600290f10af74a1619f891bf40c0a7abdd04c279b0223a34caee25fe0de0229e5ef37150c2e877a468b11deb816f001b849ad021a635947c48f761d320c090ca8aa72e938f028043b4d32de615f1472040e87cd29e428376f2316831d1be6f862b2598e0cde0f1298906248325b661177208240df3bc911387cd495ed4c1f0b881f929f65775f6e3c83b0000815be116ea553c60ce9029a9278a1047090c5ad12c9cfd5722278ea8bf969de478bcb5a0095bf39a477fe55e3f7b34cec6d72653f5ec7080cd1d023dc883a701149df4ab1f1307ea889eef64a654f696258795a55420a7e43e16c0171aea9a117706de0c960cb061760843bc8ca372aefa490513d15a0c7c7deef70fadb405ecba1b2c3c1d1d3c5ca0e7e0a92fbab6fdd537d87d368f4087146022fce61fd263e61f8779b281fd25c5dc55da20990c6b3137f57156fa372c83e3c0aafcddd17d4a8e3cfc233bcd3b2db43cc3970102e6805ee0652355105275ac3c0eba26f259a683b3283b805b9b8cb3b1c2d8b3a42bb9d5465b8dbee180badd26c515e77053a36d21440c011ba0931fa89bd3052698ebbe4bee39feb707d4ccd1c88367e7b6a122bc3da71b8902d29989d6deecdd6ab1cddc32f661f3e831c5fdeb30f49241c4af2c0dc46205469eccbef67557500cdb4efd0e097e914d7ecab567b5b3ef409d87b88b6dc9881225c0cfa3069bf7e620e24e099e1eb2fcff3acaae477af9affef4f499cf5eb7cf7f7ab24c663863303e8e8504b4e11292602c7a4dd8340d93c3cf9b7ce9dc753e9d8a1ed92fc38a56a2bd45340baf701e38c2c5fdfdec30f5071103a77efaf0faac08cbd471c0db334bdd134fd90932dc59f3afd4bfa4ebf4d6bfed32fd767ea97e132f4e4baa87dc2df5692442e07b52cbef9a73d11c7ce8226fa8992784591c431be580c83ed240c214662265fc28c4baaa11ada9944da37b68e13a760bd6f33d6a3d0cea7e1061c66490f37c4a26d8b6c391716dd847952a42a4e6ec2b68eeeeda913080f29c18f703aa378f1f824afeb95059fbec00d785bd9737e5f08ea06cd593cfe8b434dcd1b7dce08cf8aca495002f0404450ea1a8779d177766802058f5543bd0865bb43f47d936eac89d41f605b364b17607852cc9a83f2cc12aa2ab7a92725dc776637eac5992c6eb9c42579da095d7b2dbb750747ef417851ca5133bb72c80bfb878e2a3e39af9e8d62c6be202d00592b18c44806c13b944cd7cb14fe1d450d1be0bb309e271e4446ad37a6e03d35616cb7bbfcea08347c10233b47abdd23066fa0dff50d8cc34ba55c71a7ac4b45d62443a82b68f7b10161e9b52bfb1f7b1298cae63158ba8d817f1e105418f04f4a4d833a39b911ac5292fd4a09f502e7fa596397a6b395750ed46658f2272e3be9dc4467cc74fe6937c8dbaa6c5e1d714ed0ee4e209b99c0d93177ff80c016f0ea2be70d4011139764187267de6b973fad135865c07f41741dbae9a097870bc8bb2b3e718de3831dc803d68c5f7797cc3213c406a6a2bb1d673ebb1ac02f25146920e266edf6365d6362fb1d11b7ad280cab3135d945f125341754caa05544350c89674058c229ceef75eab7978fa786710cb44fc127736179008e03705254e80acc01986490732437593607dadaf25a93a661da2b2158ce68e07279c677eeedb68bdfb7789c416c113eb9581c0220f1706d4532bea0e0151a327ef352cd573930a85af2635f4ffac82f8af9cba07bf27d60e5e9634db266237c82333231f1657f2153a8a7c6e3b417ea8c758f8eaf05b96588d1d3f4551aee0a6ec9e95151109e7029d3be7ce0ca31345564cb3d369f5ffc140af23f9e7b9a70b13d619f9e9ca98e8521d2a2128a06bfe8e11195291eb0a01ee42adf94f98674cdf46299ca86885d89c652be99092c72fc6de1d746f1e459d2e1e1176c6f7d9e8e7099edafe562a215b9e17aa7dcd34963ce20b1091b0ec5b5a41e7c2dae3c03a1ae3447f52c2439222ae764b26b42007c9f22d80b8772ffa437523147a2dd14d249a396e28ecd35023a53c8fe08db0b1a29b126b3b52b9f584385992666bb12129054a2a2a4cc62f55a13bc6f248337659f7d8c2dc8ae603453e851ecfc29c57bad68ab0d22b16dc22f28291d67c5c70cd1f1c398b3fd5e591b4dba953b631d8d8c3fcb43d201f6953919d55eb7904b4fa506410824921c43e23ae9b564c7934220cd6220523e5ba1f624518cf0289d8c6892587b1eea9d314c8f861f4d549fe35fb7988cb6d8a63c3e16b9bfe7fb807fa9456075a2bf37d351f73f4f4de6d9e67baa79bbe24982517e3fe936b3e607de35ffe98f827209421e31aad2eb5b2f9f5a5efaa97300906f1610b2ab2a18544443d4a965d9e69eeafdc5db36b1c59b1b2c2b7d5aafa7126d6a7cd8367b9bdc23d6299cff4c26ad0ff504f0175844687c0a71620d5bae28ca63382cd631e3be0087f1bc7bdf3f610b9e9dd375f1ec204c4b845710d854b58e66b455ecca7544afb86cdd1cd492e5784bd4102f24596196397e683f7d21ab018066f5e2c4d0ce654bba84eb7729b6c29076e9b5a900892220fba6b722de7ae6ffa3f4c1e0751f70fac51dd58f60b21f4e188bb25cd9e027951ef2d796e015579f444b89384644b2383ebcc5a418f7700fa751d5806a833d4d641c0f0075e9c4bc828d31923e26cb36103a2afbbe967743b880e8f88eb4536e9fd9882b63f6835469ed0656ab86fc335f86298ffce793f429fe92df6c3d10b8cf595c15aa78d050bbf685268fc1b33a939ce3289bd0bac499530c3382b742482be7544881e8069e8b9301621e4ea33bb1c25fffda97bb516b9985d4129a6ba7d3a377a326ada8dd09ca6a55f88ef188cfc5ce652d3d831f741c4397cce1d9f76c00b43db59de68e53031bc088b2ff34fef2facb1575736cbfb74eb832dcd03118768e4dc0a5e0ffe2fc6d55421598b2840d79fd304a638c531371dd2d8ad8e674528c0eaeae678561881924e6a4b9cbc74374a5b6e5fa4dee4c3c4af5b8bad8acdb263696a91ce5d534bea8c523697cc8923c885d1bec45c69c87eac855097848792f327e95164ff4fde3f13097d0bf018aa382dcc2c344ae5a537155d2ee1f9e15507af3700e281159f16c785571f5be05b99c47451803de32a942799547a071435de616b7779e2ed7bdbd8209dd91c0779c002ab24061610795290cf18a390f839239e3f6dd8291d621e3c356b554e3fff36048ce7780a1eb5ac2dbefa6af625b80accc3e45b4ae370f50aaea458dee878c01b13316091f4d51daaeb99f50f3206d09fa98d82b91ed44e2f6c3edd13dbd29f600584887ae71a0479eb0d5b3f8af98b4a907ad742dff3386a9ef162e72bf3cee2f00e71fb6634ce7df1c3001d3f72eee0368621d23c7b858dbbf9847efcd71c7d18b970e3b4f2d47a7985914e523480974baf4301c47bdf19d8de8151dc4a85e26fe024c07fce863478a02c61eed7ffff865b89c3ce478194ee90a77cd01ec201334e9f81c77bb2cac8394ff8870dd5296d8999f94155176bff8a5b965eb34ae40cc4a5c1819c48fa612f3640f7fcff5fa1e6ea30a49ed5dbb8502e50d5c504788b2b60b2398bd23b3759d922e4e98bc9edb304116bcc10ab7a3fef6828ade783c3601efce59bd3311945bdfb502a31ffb8fd932d1bafa0f5de377048d86e29eeec4391e1aa6ce2e9db037bc41ffd8d1e897078c8f9f182a9021429a36fe62a9c9ba18d4226b536ae49f301489ba75fa3f58e1a205c39160a4464a315067b0a52e721e0aedaffc4b6c4a8228aaf1a539d98173d164fe142d3813b875db9e70e03c4f2181ce66481972d1414cdfa09c82bb43efc536ab6fdd21b80501a08c4bbed74584ac41de5f2d6067d35ef959f995eaecd3e5ac69ee77eeae20ce5dd3cfe0853592947c440605c9ef0b9acd7973878baba569de3582d83a33136e8e8e1fd9cb24f2e5bfd802e4cfd23ad807fcf7c63a51d7526f95e91463ffb18e005a387adc60a13ef5778ba34ec8925e8ed3ffbe310d1b4461b351b4c141d14d1222db2c253b03b190cb23a04217f156c4e647432009a28e3775679fd74ced55bec35723ff04c50f6d692c170e54f76e716dc1fe4aad5789894277a1df2636b1bbebad92dc23b61362155167a381de70de8707d1b50fbdb4aced8406f44d4033c520199157346ed71326d76b30188f143819947488d738cceccc487a6d1e038a7d86565bccc8edf7b6b3938ed15f80d735b5e8007e6c44d966ee237240dc21a5197eaa44d92ff72b03eba0b6389d6539ff9499a0ccfafc2551e29b0fdf4705c4078f1e268d5966d75f95fe2e0456f1fba49aac440a681ccc883a55e5c19ef34871784db70a5560a54298e76e7b06745cda8863b3f676c15bb6613a7c915c2034af3fb09296624f774c04ef722e02f3ec8a2364d0d9fb20567a4f61d9335282821110be8936b829f53d2e6e3508300fe28a3644caa3224f47339b9e7f31f36fbfbd4c3a7be00e3d32f9ee6fc32836da0041918add57bdcf05e4e8080610d70e2a1e8a20a802c03ad910f099678e745e525ee7bf1540dba755454b6597c9d6a75a90a02c2bb327ee7ed4ff3b7aaefbe50e9157259e7bd77d7df8b4be0b171f7e60fcc9d81cbd6072d22c592c988749a2daea53f98a3dd8f7dde95e4c755d7a99f5c84a05fc923511e5393c06ff2dac0bbf237bc4e4328c029e0d93efe6565df99ed23e2f082c4586539c2ec5c5ec4d4f0492744dbd3714d30bd45b6b3334917ece6a30c80b545ef35931d1e7fc3b4fc26a27eed764bbb65b2c62dce814e3f2614d2a875c9affb8ff76397ed06720e2accdb3baf244928eb1a589db95d5ea94b24e6f1999e19ae6377dc9199af089e69c0e9489a59efaf617c20505c6e8fc9ee03a6ed90c9e4e8628cf7be9811411afaef14a58f02a262b550e492a45b1823ee4e8cb1df808caf822fc97ab434070dd1d0b12f344dd01789394745eb40302107efa54886c39998cdc599429dff7491a2a993f2707cf24bc4cb81a1220fa83eb75cfff8cc0673db93141c58cfc986877a913b7f988f17f9563ae9913b4ce33f4c74eca3135f417bf9f436e84962e36cc0c509fd5ef7fde12e62b35e66dc4dac93ce56a1488e21a0b89886f87b8a778f6065a94067eed84e65719f9567614bc13da7b5f5b9eeb386f90d214092500167ee0ec7e8b7d719cf29ec547a130ce3ff94202eb77e11866699b0d7d9399b1d571938528b182b160cf85d798b9df06270bc24f691ed4738c94b68fc3b1fafa5790e5358397bf086ae51aee074905607ee78ccb53835edb97037cdb4a9f7241de0eb293d1610e1d1b55b00782b5b7befef625e7f10ef7cf2ae87681e80472df3295ab0675253a3c8f36b1bb22bfd8d7b04acaf76bb20e38ec6dc779cf5491ee0c2c1263ecc247ee16c69fc1908a646e369c40b4ebf12b258006b6156e564a2279d11f08f704ea837d944b7d42350bb35109f0fd681698697682733dbc8e205e5e49fb12f2bf7de4c6dc238a7c45d76a9c9c517b07c4c8e20b4ab6b1567a55e10525b69c36dcde8fca80c734cfcb8e8eef67d13ab4885d5b53c89584effcd40d3f4a20fb5c2c42dca73d60a56f2f278b87d8cf0eca31df28a0414728f38df8039cab41d2cd2471de220d8815a4cae114d759621ca99b7877c354ec44e1fa87f81defeb2f5002a684a8957369dcb7ed84d0e6e8d81a79f5969f7974998212cec03dd6b6b2bfe508d6a860e8740ed7f5dfb51fa4d0ea63bab8c2d12ca465a1885e8212225f6125c65cf826c6033c052ef9240db05cc9fd7266902412e1a456d948117611e43761b880c1be5e54186ced75a863a01ca7c0983d082e1bf4010f00ed952923b8d3a07b0112b3fb27afbd520bf53c78bc664b86bdba554390bf3a29683cd3f316869d8e0812b78c0dead748e92f43e2ed1127d4dcfefc9c3eb3539dfa4d18340bcf6268423c2bc2757cbf30ba37a8db62bca57866a6354e810a6fb850d65d14ef2c9c9ef3e45d36c79d31872338dc56b0febaf48e083c3fe7ebac7786db2500640acf0546c4a4b022e2e7f53705324d2d7ddd2ab03ea4133507c192938deac4b9278199a404e3e34ac35609f9338833e934dc31896799447d88032f43871a2ff4f040292bb874004a8ac36278b1eecdfde8f0f7fb0dea7fe8637e9470bbad00384baa9f87ef02ce1355e89546107598d78bf60b23fa9bd5a1335c2ac7abf39a8309fffebc52dbc77fe3ac0be30aca9cc4c063c4d1ce27d4d32be430c4c922db60d6a8c7641458229f58613fb109d803e0c2560b6828d00aea5fc3de827725607a14f1d055dd251131914b23fca8826cfccc64929ac81658f8e0570aff2335d6c43f77264e0820e8053516d17343a26173f8a14e3fe87a26a1a18d395aeee80b9971828022d97773a500e3c0c53b4734a47dddec72b6f180d4967bc473a3dd37e0396b8ffb1a53f8606b195bae7661401a5a881a743cf8ef59a075637327c97891a1a17d3774c6b75f616ff3c6f803e63c3fd5ccf4573361a3bf9df9d1bbecc730b90f82048fd96079131f5ddea17c21db6e7f3b7b436f815aa2dcfcc8012e99f656681ce33fead2dfd914ec390429940ddc71c0326385d0f955f9826eaf72de18f2a3f9a8a6493df9d868f437c5fd39141b6e9398bbe0890fcd799319c8de0c5b2a377cb1dc860526ffb60469624c0dad187e3affd2b43fc06ab59fd58b511d1d55b97dc212c7ae59b5a1562310b372992a2fbd9581afaf7c915fe5da55ec136698f5e395466512deca25ac1fafeb37792e5e2defc5b975ef7bf370fba64661f4b43dd260b5573cd62e1854d587106157858cf90423c529e997309da2285540d2cb01d7b5b883b9d7c6a73c14c39d78647f957e33fbacdd5a8edfcc7d3f36e3269c8fce1f0aa2c702506c21c059e80207f40f99abb320ccdb75ffc509f34763fbc1d6a04e524e68dbd0471fcc488ee8d2216ef1feaf4321f0cdd08c705df2bc6beb16cdc1d2571ff45f17b143c859c832076a4bb5705042a54eebbc5d3eac00c5b063c8c97c4f7871edd7512756806a59761fadf09d0dc2325abfb147719ab31e942c8e6115ca0ec4ccbfced57c82f08103e4130b5198722743c11b5a658a21a768a21e6508e4b9d1627a3de02315ad3364dfe8f0a0ea3b5d2ae1a5d87d6dd0d9101b1c553ab2888826c0bccde7535efa7d7756411da3a12ff0ed1e5aebc8b2261b3b470d6c5e900a2c084865b4c8bd7aed517f80e76bf08b975c41b124fcfc6e86c21ebcfd270da0103616874a8a9d406f5e036c939eedcf817ef31eb361863c5f8de0e42be05e4585b563787055bd299bf31a193701c18d1a0eb9301f14dcda9ed33e2613a98b31be60da8b9ad9042b59ddc1c1c41418724eb5ac9ddde86d9ef2534aaef0f5a1ddb77a3bea87d949cacc971c542f5698ae1b57f3127e784227ad879273a99aae8399ba50950f579227b2ec2f5c23197645955eaa3cbb104fa80cc09c38314c6f783c8c22417551407bafb60ae35d4abdbdbe6b32ee48695ed8ac46e9aee54d17a507ad83df2c53ac0f7d4163445b4819e40fb923540000516bd211e5ca55ff262fa51d635dcc23c27fdb4c7014e4e06dfd5d3d9218dd2c3d7e91e00dd1e5204a203055c279835905eaa87aceb3966a0f8e65227e70a77368afffa441e7e5fe50e9d2da2b5f010e6cee3f52c40c9d16b0dcc09cf2941fb71b084f588c84d58a4b19ac42bce31223e3ac8f395ecdc6ec821f727fec50118820f3e860c1dc5c783139589e8cc3e00d7f74043c3327308bf2e1afdafdcfdd0ca34a76eff71583c6994e6ff82d504b914a6a69f09ef6b94112f142c6afb6375ab6dbb8e36589e5f0a779cd72d5df7bb12d15165e6e88fd9f1f6efa0abacd8df27ae021561accac2ab45f624a248386c4cccf6d951d802ff96d0835421a36b05407ea9fea1de994d4550078ba3d0c044190c87056160a98b14f87b298b71e30bf9d378f0e9b6581afe3dba208b9da52f1525b83b8cbe8d532e6e5af41d6bfc471e8b7ea413ae676b7da23af550b9d07504342fbc5e6b639e7cc97587ed920647b222a5b82a16c90134b4e51bea6c8691df182afbc613f3e269fc053e30c350469f9586e72a976deb817bd605df989bd28195ecfe243a2433cb85aff7bceed38284a47a8d153e5d2eccef5f101524aae564f8609dd9c9333416cb377632473814398d54c48802de72f5b74c9aab19e8bc09fe89bbf17a2566361c7b4c6112767697bacb084fd7b2437fd0309e5ead51fba5e833de354e72e0e1226841c000ffea771160335ca52412012ecdb608a4b124503b8a037843bd05f71398fb965e1e9ed0dcf030b8dd67abfbff3c622162c97d36d93e2898f49f8dbbd37673317367bd1e32f7b20c55e51596b2929f6c1338511f5e149c93db7bfba9a6c8bc0c0fabe08582a7474a74e52eb7f2d2eb93b22358e4797b40c27400b354efe4fba63db6d531f298aba6f9b6e55a8405933d2d18f08edffa25e83e28b08bef93002c94fc28172002ca6fbcea451a864a243bfe5d940591e24b5754a8759620ca3aad6d4afd88c87721e2cc8a97d1289fe38a89a44d0062c03b3b15eaffb5d09d869cb8d19a60ffab6024195ee84caca0686373c32345dc1a04fc23318133f55284d5d95299231b450daa6a950d4c9543b4298279624ae51aecc3428ff8fbcd4e3983af02d1806d7a9ab2c4dca3e4b0db55926da73b4533f4145ffbeb04cfb1f9f8ee5cc24370eeb04fdda5a29393b2300c3e1dbb7a6917729f4eddf10f7674e4ef040114b19ead03ee1f124d19c3da030979a2d7aca08c526df30719feddf121b573dd23d2880db8b9b0d409e8c32ec89e564e19aba9bbd5553f6d9152679e4b4e3956c6b588561527d45a35391a7868d4edc2c6fd546e214347a896da2ae1dd16487e40789abe3ad2861f4b37c98e7275820756da386b0bb23abea32283faf3da7ffe06a71b365c0abf781f814e0dfdedcadbc5951a000f3b151c01af04ea12d4377351761df7759a03c46bf19023598e62696900a29cd6b26dc51a575f3fac6430a24f96de4cfe48416612df77dde00020bc485dd38892339c87628eddc5bad472ff3fd25da31fa5414d12710f72db9afacc448ca3cba05f89cf9015b14d3351719cb6db51063a22b49c9aa5244e8f209af9bff2884ac508605d0c7e3b112d7192ffba5b073b4568b97d731522683306701eaa0be0eab7fe313769d4a5cc6c40a0a62206af1e8b2c7de4d2e997f8fbf3487431f0bdeab302a9df0721d0784a2b9c905010c80409c3517d785ba1eed50217fb21b65b6b911b40c7846c2dd23072aea7c2bf13c2cc955d5f91c0ccc0d3cbce56c688d4223ee7ec9800de00474f110b4e38c617bdbae4431e6700331c25089cdd4b34c3cae8e424ddb87588f31a27ca36ff913d809b317e642ee5ca2fc3122777003d3bb11a851171bc1a4e123df2f813b1c84912ae89bfc5cd3796827b805e5644c9ed323dee8aa123b6c415f71b40775d175744ff57ab4ba300c45c54b45e960f6c900ab5572e4751803e395242acbabb8de239f58bb82d13fa04272b0e80fc84d8a3ea6e4fdd6177d1b1f27828472673dd2fda6183bd59c2f4c60674348e5d840a0b36cc6097ee3b4fbc237948b6edb4d0cae735741e50fad031266ba30e68d4c2661feb8744c2827ab297302ac709cf0c28fbe6b07550fbdaf9f70ebb2c7eab754f9b16ef86588bee8e53cdd6fe3154f720dc34240cc68f017da6af0af99bd51c1b9b6977c681a71ef47fdfe0208c52340b9e400387708e570cba31ad368001edfbd3c8f817cd67b1d158115bbb9800f0d450352ba0c21df5819d9f2bdf67ec8bbd9f300e3729d7e11005fa9e4bc0c6df2123f4695da1bb2f84cc28d861439b9d74d24551b1142e029560a9ffec296305e9c8b4d0ae9527c4bb02bfe8d63c1d29539a28dbecd283ee27f4c1830e7712b09e29e92402a29891057e10e7d824e733f849006d191086b071bda629a6835133c363e51087ad6262a22057de8c2403ae22c5270e3410b8a44907fcffffffffffffff5f3e10bb37f8660fad4dcb94a47c02f587fe533dbaf5c330a524a54c49c21257b8637d33b388042b10ed0cef0de7a4f58518328649e5d0ac6ba79f6b1ff48d2c0e8d627732a9b3aeab0987a6515a5da78e9b7e6f68789731cdcbfff5b3dcd0b46d8f16f9ae4f7fdc8666b3b713f639efd15d6c688e17229ee77f4e676b68b21be7b7677f54436e3534da525d75ab22a6f46968d0fd4129b53dcd9a8ed1d0ac1f237ae4bfc4c56768f8284776d4aaf6a8ba6668bc4fb5f6ed67b1b696a171961821568ab5d915191a3f9f4793fe1d2746636814a9b4a8f152ca9b1f1543e349f5cf3acbba216a6168ba35ef86e90de92a303448a1f31d6debf4507ea1c1c68cd1f35254ac5079a1c1fed4989ff61666eb42f39ceeb07de16a55ce85266926cbb63a2d5fcb690bcd2b9456bac6527975d342b3faf4a87b6fbe1a4a280b0d2a4e6d5743a5c8dbb0d0a0456655ccd921a37585469536e6ed126ffbb542d38abe7142c5985061151a96f4507a65aad8d9935468122b578dad86ccf4fc141a3ddc0ccfbb426fee526852fac6e37b8e324b2b0a8d42ab699d55efab5a43a1e9948dce41e85061524f683a35f59d74d4add5894e68d02995fb8d13526c75131ae4f94b35b448f33c33a16988e997f3d3d3265d42b39cbdcfb75247f4a984a67375aa37dac693f092d074f3a5ef8749e52324346d7d5bbaeb8becd08fd0b8f7ead23d2c31646c84e6b7192bbc7e45be2d42a3fafcda72543e97784ef4543ec584fd1c267cc9c464af071c266784418486e162a74e1fb45a3afe101a66e66871a3f4bcd2a7ca0bfaf1230d855b893084d01c5eb7166addd62fe43bd1fb677131b97b20417ca0bb44109acdd44d6d3bcdb871eb13bd39e458d5092034a8499b335c452855fa13bd39e43069fd2926e8dc7041c29047fca0b955dbc952ee62ebda7cd0b86a85baab19feb6ef1e342afdb4839db859779707cdc9db3e4cb7fda83a1aa4e16baa2b7f118f37ab945679c1a930705147c3ce3a4d3fe838f72c3ed1fb38fcd30a1dcda3e25dcf0e52ed7cfa895e1c2a2872b03858fec7a11c73348edcefb85d1953e7d6899e6a2f0d4a299d47bfac192b5b277a29fd2e2d2672a4bc2c1f6a94c4a182c2efd2a095ada1a764a99af9b73469bd4a7e86a99dc5ed2c0d1e3edafcf4ba1af31d34e76053eecfac3ab55c401a3fe4868829efb62979a5c13f46d610d1d9e4532acd9f6d4b7da9d6e76df9a3e1cb94eaa447b848179ee8c5a162a2eee3f0a65f0d374a7894b8a0844789cbcb1a990ccbcbc1d23f5a8f4c269361c1397c5f5141e92ec40b29cd31c4aacd6eb1e34a884d6eb5204773147a95941bcb4fb516ab1e70982c1d34ec10be42c6d8527ed03922a034efbbbe3b151e75cc53550f384c7270d2ac4c7aab09193b7acb4c0609713497a7b1e653aabefa596b14638b353badde522df5cda1857850ab6d6afd2d6fd4ba56a921aec34c9759b37bf2ac4bbf648d1d546335b8fcccf3b0272be76b3ede83499357a944872a39f64a67cccf05396810375c2de5b2df41c489c960d2a474c40e626bf162e8bf9f05ec051c348dddba162a3cd538d739c5851b34c9113b5fb3be45ebaca791c9647ac061d26cd09c57cde8d4f54a86d41b6ad0dcba54eb7b97fe4a9ee068fe1fe531edc496394a3468be61638eab31365d95de6896e61d5fcc12a35d3c8383c8981062b42a116eca6cdd78215ed5f6122a72a3694d214da98eca4b8f0e083268944fb55cec2b353e0a65322db8269748f0d1ac5aec572b6b87d4728d41f3c708d77b3bcda3f2da68d23974be1df59cf4e8c3a059be536ebea6b2d7616c34edb0234fea0f29d3532f4008f1494cbe5b74a9993a427ebf183adcdca349baffaa5b4b4b9dbdd63f0e49b046c3922646b752f2b49cd721821acd294a4ab31d66c99a638b2ec1050d2a52281b35ec764cd76d115ad0a84b8b147762bde69f5685904683cce7305e7f0b175b0bf22ef0010b1a64c6ee7ba58b5d3a27ad4912d068d2372166878cd399e2cc690f384cfc8c86d1a976aa354c8fcab369b657d72663c4e5f93ed05871514923c3c272067b2031c8644a7ca091c9b09f2393d1bc1e709884f1861534298f2fa4eaab7dd6f1440f4810172ef181464989a797921e252b5f4a7c94acf452d2569a4a269302e4074a6a4b0a901f2840e0d1bc36d6eb38516adb963ad10bf272dcb5047939f02e0566347ecc91f3428c7d6bfb6534673184be51395377524ff4e6d0121f68949c51e2438d921637f492c9b91e7098ac4005cd72e6e9bd787e7bef6990824635e26b42edba31c49c062868146aabe4464839edd113340899b96a3ecda9d55d0d1334ce487136f3d13eacad12b02274d025aea653874a6de6c71c292999668f72bedd5f88e954b98004cd61aa7b7dba23feb65ac6f1b27e05994c467bc061120737cec8fae0f274682de4d656588d6d702554c7944a98e998e42975ece748439b6eed5222ea46da8b77a26782a75c7eb454aab9568a8c1953aec74b4ff45c585605a951a7197fd9d23c69bd3ad133d1450d6adddba971267688b113bd3dcde6aca4946e3baa9d6c9e277a74acb0c471880d424b1d645785da7aa5277a2c2e2dd72c6cda586b5bd91a259fe8b9fc682c83d3286632bf624d193ada133d576993153f541a8ca0f17e74b835f1ec33d64ef44e5a5e80a8324a7894f828f1a146097b37e8e836f834f580c3448d4cb3dc7c3ad3fbef3686277a2c7dc24ccebba0b0cc209371f9d13299675961337079f96172520f384c66208246ad27e594582dd5523b27ba2f2567b45152e2438d1213151445161720f92c2b0cf580c3e4042168d2f95ea9d7d97c95923ad1f357014173b2b1a69c9fbcdb3674a2d7e2ff2d262e1f47ba121f68b474c9c19792672c2f2b31682393f9b1e22acd92e9121f6a949820ee018749184af88049cb0b8b8949043ce06fa2127ea0032cedc2010e40600311d0801cac574acaca074a2290810a6000021720a30316c04005244081091c0624d05c5e2270267f6262c25a507e4c00024851e00175acd0e1520107a884643460f14cf0e5e89465d227643020cf0238a00074595919c3fb593a90801020200e0e1ce05a3e608053000f10800303c00c08c0440301c01f2bcce4594c4c4e0e951008fb1605c00973e965d201314c4c32b0a38efe61019233594931894305258e3856522030f2a3022267f22ccd8465c5a52710424618676262e252c763003301474000118108e37fb4ca080410710b020c00a3884f88a20ccc15ac50052a4c410a5180c21398b0042524010947304211883004210401083ff0410f7810848e395e5c5a5876006445e547484a881c3a58980424420a980424c217221051c200308ae08426cac0842015128000068309a3092050050613878a4bb798a4740b4ba6452402180c060022101803043618c3181f6884c803441c60d2000f78a1640c0f0c600c0d58610c0de4608c1233f060c9988c9101398c91812f605a3c0d44ab6eada4982182803130c08003881820e4162205b8000bc620c30bfe03f06f09a25206b27ccb0bcb037989c10ff646082b4404908232839494323cc414188c4800fc5f9092520606230280160c46440c959786c188ec7094345af12ecfc2cac06044483e65a5872d648146302b22a22e48b3bc42240483c10cd22c2c65b4603021c100060683c159fca215184cc82b7c11031f2d28c060302fc08c118143377eb03af25ddee553529a8a0c52525ecef894d52b3d3c658505cf039418d4d16bb4342bc06042780164854545063f503a85c7d7d16bb4fc9fb1d2a35959f101a4597cb0075247f33fba471d4d4aff0a567ac8c156d458e9e1af22834f59bdd2a314bfd2236417981053e80283e969a020958b202f070613828b8341de0521b7b045b3bc0b52d0f01494131e4152545e80c184d4c285a581603021b4701496971436309890593c435971a385e5d78f5f4365e50c97967e355cca5801b2c2c2864a9ff8789695353c0d1459b0bc1c282a2b6b603021a6c03429cd50544ed8fbf83950e27835521a85a1f84b0b7410c40c0c26e415184c48296281c1c8c1b272461033fa47b7bcb8c055dea5e567d0c60c18ca8f97e659564e5a9a870aca0a734131038309810506830979852b78fc606fa084b42225e5a5070613c20a0c266415184c882a5211820a7f1f798a10537c4003648ca100068ca180048ca1000260424a410a0ca6e5599e05f706be916fa037d21bea0d7d63dfc0394840821fac8e16cc394840029cc35778ac20bd067b3750b0d254d230017b37585654507efc8f5641c18f1595f71f2b2a2d2d2d9e823203149880bd1b2871a8bc9ca1f2d25cddbab0341b417cc50c179606e26904f115335080024c1360cac0a5456585071921d817132cdecbbe047939907a7114cc97311090301b300303042514001384039cbc220c5884c18930fc10c61f0c6088114e2c0183c1a48119c3006f84840cc2022948c2180590420000b082310ab080304276817e410097428c31002708c20061008317bce084246092b004231ce1082c287029000683b2460a5e05858c1220191a2043e5530e1927b800192a9f9202c0461961941146026c71025cc4c103180ce60d180083195b90c20730184c14306c60dc484104c610a311984f590bc5fb59907a19438c10cc61420213202d63ec48c5183b3631c60e4578800c1284800c12fc0f141793f62c1df041c70a8f967d61ef460b0a90325a5080741c2a29407ea0903c620c92c5a081823146ece0873146d068795f511943e4102d3e5c3e06638808608c10160c17b060863854509460843076f02c738c817104a60c7900f22e2d72b4904186fb78202f58e911c6ef8065a58520000108400004a0d26604716141f9973212100c04860400c305f30b1f8cc117bdc06034d0035fb00083c194a429f4c20c2642f0410f78e10b97766909c14322789172070ca62501bb600383c18cc10e25ba30030653872edec0605672e1065d040043860a0a1975b84a0b4b026ce18404d8e28d04d4c20b188c4ab842878f97a353cc48e98fc1b3b0d12cdf2c4d4a29a594104208218410cacccccccc4444444444bcbbbbbbbb3b78f0e0c183070f1e3c789099999999797777777757555555555529a594524aa994524a29a594104208218410cacccccccc4444444444bcbbbbbb3be79c73ce39e798999999997777777777555555555595524a29a5944a29a594524a092184104208a1cccccccc4c44444444c4bbbbbbbb3bc7ab2aa1c45be3471a181a24601566c06030cdf223602832586179388298b1d2e35156e0292f2b33a06325e5cd78977e161ecf8315963342cac0f2272e6d060613428736567ab484cc01830991030613120795151419b411a4d58043c81b30188c1bbac559d3a0b8a4bcacc1a38565a5474bcbb7a15206cb0b632b2a65a0cc81b282567921e359fa517cb974cbcb22e30c7f153a1a480b1929fd2d2a2f64a8b407e85869f96f413a880b4a0aca6a95349ee5c5e5a5b1bcb82c324e5c585016194d864a46a5fb57a790d1d2749491697997159512c481d24246081bd680c1a4f4cb0e48210b0c26440d184c481a309810346012400a459ce155567a60302165f81f2b31c06042c880c1848c0183091143187cdca081238d1898aca059c10b78f4684cda78411b6d34268d1a3258c10bd63079a38c35ca507939567cec0b0a7ebc4b1a71aca4a0605fd278159438541a1ea3f881f140073061102085250c3588c2074196494b4b03097142135c545a05e5070a0b6b4171327ebca8fc8f96a6838c1516c4a182e2c2b2b2ca2023f364b088622404e5048369c16042e208692bf0675961616952daa5cf6028292b7aea7857e15152e2438d924ca605cfe4f0f620228986d9c2f3ebc591681af2f5eae7aaba95a7a751c2a32406253c4a7a6432ea9cc98940a26167eae0797514e7f5b3d14619256ca85182d48bc8231a67b4d27752c908b1b5239af5ff67fd4e713b7f97d2253ed428c964708e4ce607ab834d0ee7c01fac8e448248239a554ad92b5b2d75f6c21317143a18d12464f68baaf32cd2c6269765105944a396b75764b5b90d979de8398a499065729065c525064da645e54d80b49851c2a384054d26e352c76732af8272c2fa87099016114534ca3d2d74f2945aeb78221ac765ac1da4ac8920a241942badfa4bd6147113394493db5225e59799ccdb440cd11c55bf7ca93ee920b44da4100d2a6faf4b7335b76a132144e3abb5bc57da5072d2263288a61f513bbe87b59592361141342a9b615ba8c9d6a536914034e77ad0c2bb6387596a22806818e725fbe176a8106a227f681aa37407bf314d07fd8bf8a139ad3f1d74902ea410bf481f9adca6a919b11b43a82fc28786b12394c919d9ba4f2fb287e6702b6d0cdd674b782fa28746a1bef3a9d787d59d17c94393f637217dd785cdda45f0d02463b6da79e3c518b78bdca1e1b54e3abd44b8adea227668d84aa7354f2b694aa55ca40ecdfbd9c37ad61d1f4b2e4287c68fe26bc7095d2d45179943e37dad4bb1b6cb57e82272685ab6f38628f1f612ba481c9a4566d4ca7a74659f8bc0a1b95b8c3efff8ece271913734e99a4aa550a6f3f5c644dcd0f85e6b4cfbb493a7c644dad0a4bf86365762979d1a136143c3dab9b36eb9c294f099c81a9ac53eadb2b61e2152c544d4d07862e322968dd8e1642269689252e88e5d373d499389a0a1416ca9a37cb4a94e25133943c3780b9b8fa162f86d8998a171d5ed199ea7f35e6d8994a1592c7b1513bb644cb5254286e6a8a52e554327adfb5f22636898a59f7699edba8d2d1131349e89dbaecc4b89a95e22616814cb961843fdff077b8980a161a81c153abfca0879897ca169e7e8a4fee784bc778978a139ac879ee9606e7a5d225d68be57b6e453dca5489708179a4f9454753f4a8eaf96c8169a45cccee985e8fc795a225a68d461eb6dc9edd662698964a141958ea357f6dc6be512c142a3b6e51ecf45ab7e72895ca161db504bb8ab1ef9c9256285861152dd14674ada8d4ba40a0dde23c6da11b1fbe012a142738e42881a429e0ba15b225368d03384bfd43d2f42b744a4d03496da59cbcf3763ec2c91283427bb1d3fc751322e6f1128344cb795f2d6b62c256f912734ca8ea6f647353e8a598b38a1417d16d2637d52da632dd284a6173b84ceb95696ba5b8409cdfa9d362647dad46b8b2ca1698ca5c4cbd6e142d7165142a37f9e9bea697dcd528b24a1f13beda064ddb221862d8284e64fb5830edb94e9075be4088bb926f6099d1631427390393badbe6dddcf2245685c651f5fa7afb5e767112234d7f02ca7bd5cbbfc2c3284260f37b796acca1b3a8b08a151a86bfd619712525d1c8c467d3353ab9d4b99ba19188d3adceeecab43c9dcfc8b8625869421b6a4946db32f1ad6f658229534e5dae65e34fbecdcaa55fc9fb29917cd41cb91f269284f9f79174d331e3667e7f89833eba2698a176aa5c75a7d53ce4593aca15fdb0d1db3a58c8be6f4b5745a35bd4593cbe8a4e6b79ced52b668923b524a8f6a66e6a916cdadd2dd3cb95ab79568d1ec62e5d4ed3429d6d02c1aa47f2ef977624e45b268f29e25857b786f21e25834cbd5a536a62b5a448645b316ed66e21e5e878e5fd1e06a9ce8bef3a4e7b12b9adc6d681bf3a3cadbe15634ea96b9a385f215f2c3ac68fc98972dc412faf9e15534b792fe3af47e9cf9aa68d63742ee4c8f8e9aa7a241db961fb1d584788e8ac6fb39cfafd546b6d8299aa6fa30cd7594afb596291ac4a3703d537e53bc4ad19cb60e437b2ca953b848d1a8f27bd3e386eb648fa251e82c5fedc5705fb1289a83ce62aded97526c1b8a86315a0c135a0ce169bba068105a76c5ab0ba544693fd1206e96526173c4aea1f544d316c354844c29bd46db89e6ee56273baae8a86839d1b047ea96dfd7699e6837d1a43e8b560fa7f6a9b99a6874fb513bfb660dd967a261db9efa107263c58f89a6976ee3eaba5b88fa124d6a5fab6d9de5cfb22dd1fc61fbbd955ef51a57a2d186ed29ddb9b4ed4b89a6d5ee42d5d4612c914ea2f196daf6afa318e3a324d13063febb2e33df1945a269aa4c9ba975f6d22148347eae5c111e274799fa88e615a3c46ca9734a9d544734ad907f15fda5bb446d44d35072be1e474c6ea78c68de8f727f7ce5aaf5e9221afc86994d9d3faafea8221adf758469d94ae8d8d1443499ed383b6d212adc8688060faaab749ab35dfe8768ce427898f09b4fb3374483ddd241aa4cb5f4fc4234cd384f156b4d55f984681a2aa69a3543a74ffe201a46aba92e7affab664134b9cdbb3b75912dc58168b22f61aaa49ecf9001d1b8fbb6564409a1bffd4393e9ce26a243abda523f347d7e0e63b244e54afbd0a463b5f05a9d2376ca87e6747abb46d4f6cc92da43838db5f7f9e56fde49e9a1d97552766b0bf774239587c6d3a63c6ab5a6471d5278686e25773fc384d4375477681a4f1e2b5ae9ac944ed9a1c17c3e89eb3067e81c5587e6f0674a74c40df9353a3487d569d8687526543e8726253ddefcf1974acce4d060fa498fa89952e73bc5a16185ff978dd939993ac1a169499dc59672296cb8e90d0d9f76965ea1d669992637340b25452bd149a90dcdf1630a51f5a9bf636243b38cbaceb0394bcc4e5a4393d8aad9faae748d9a1a9ab5cd2a9da5bc16ca9686a6e54988d9f718657b3434feb7309dd1f791ff0c0829740d17a73743b394356dc48b78b1fb3234b75c9d5d8d2d233e8c0c0d3b5608353c5fb6878da14955ba9479fb93972b86c6d96fa56f0b9dd92d0a43a3bcb629f2cd4c291381a1593dd7945971ee6a445f68fa5cbeb344ebf030222f34898fe6762bdae689a80b0d5be8affdb0dca37c880bcd29c53e19d1a12d3487d8c9ec74b2f5d6b4d024434717c35467f75b161ad55e2d3c896957aa85852635d60d51d79b25b4aed020b5de071fd90fa26585266563db94e99ee2ad2a349d30752b7ca642d3502ad2c67fe99cf3141ad5ae795d420b93429542b3db8a91a5b4161ed48c42f3bc8bb999b6fe7534a1d09c7674d43db1f3ad2a439ed09c6407a52fe6ddaa94214e68d0b526638aaf511bca902634fd4c7d0fef37a59f19c28466179da1968f907fca0c594283fa8cd62d2f2a42cc102534481342e950ebee3bcc90243477afad59a3b68efd3204090dae6ea52a5d7abc7419728466e5d2c610e2c6baa1cb102334dc2ead679f86dde832a4088d224c2a3d3f716378194284a61375e6bafddce31f328466b5f5afe7dab3a6b3102134ee284f6a6fca0f190b46d3b0912f2164e78a17309ac67cebac4bc713e6fa45c332ada6d3bcd1170d4aa88edcb5d9f669ec4573d82ef50a5d22b633f2a2393f7becee199e45c65d34ccf0f0d2618ba96d51174dba73d376d6c7912be6a251e40891596ba4582ae2a2396c663edcbd12b3c45b348de770bb53fd8b37d1160d72ba1fe6e81c6699588b063156875b2e3f564ca445d310e2d656aba6424c9c45f39a7011fdf94d691165d1a8e7937a9db366481163d19cd6ce41bbe7202c9adc65dd74ef204b79f015cd2795abbd266abbf0a02b1a55c93c175bf78ca5d68a66cfb2e3f6761c1d1b2b9ac6ea5a996265a7aeada259cab3b9a1e769c95f15cd263bdcd94dfda6f4a9685affdf3aa91d6c073d2a1ab69c756263c95a433f4573e835ed375a743d6e8a06939d6e9b9242f98d95a2f1d312ddf1835a4b67a468b421fb4c557c84c746d1bc9e7f840ebbb4ce2d51340defaca74429f57b87a261ac8d1ed7595bed09140d7ab6aab731dd279a3c98da417dac3155749e68d8d16f7a4ecff539e83ad1ac5384506bb62e8fe571a2e16eaa27f5c9db44c3beb6754288b821e369a2e9952b615b8e5e2d759789c63b71628fd8eaad1b261a6cb9ccbd1d53e8ed976854db514cd9627dd6334b340b7997bdc26775d8956892b1c6162d1e443f4c8906a1f48bf6bf5bd3b69368fc2c579fd8e96fcf54120d5ac8541e23f75ea79168127accdca35629be8244937c0bdb9ffbb7c6ea110dba46b88c37b5b43439a2518f8feeda37e384a9118deb4a958b506244c35c35d656a78d5bd1221ad6f7b9f7aba13fab88c61bb2fcc3ddbcb14c44739f0d1f1d64e58b8988e6db59ec0eaff6c4c54334c7f6587a63e6eafc19a2e9d6d431f4c8d5723c278c42342ccfb13d95b2f1b774277a2b2e4a7bc061b21206219a86d86aebf2f1305bd7133d35833006d1f8f9678cd4f714216d41349edab93a9d7cbdaea94034d79fc8898aa9ff960988467331869cc8cf3556cc64329937520f384c4cc2f843b350725b6dbca7574ae487e6ba1d47dcea64cb84f2442f4fa111461f9a6bdeaa0f2a56f6c5e7439367f1d5bb42ef97d4f7d0ec26e5a94ee24b295dd24383122d95886ed7d1525c5131c943a3c75d73b546d44b2d3c34abe7ebb0e5ed36d569776856ef6af6c71a35e6b243b318fa86cdf41cc5e97c1d9ab5b72ce9598b3e37391d9a656c5c8b991ff75e7368569e2ebda1be44989cd039133790cca159a8fa903f6bcc280f311b48e4d0a4c5acd75ab5e7442f0e8dea65ea3095c9bb39d1895ecb1d81040ecd52ec4b9d3a4c35734b07595a32994b4890bca1c1ce3bbd7aabad422b4ff45e8e76414c5404891b1a54e44c39a3e2aa552c0d246de084d90ef6272df18146a281840dcd713e755019db6e26446b6432990c620f384c42a040b28626af512177eeedb41ea9a1612ae9b5a693c78b71b9a8346b371683b0b8a87c02d23fd284240d4d6f2a736c75772803123434cb19afe63d4a216b745c417286c655db399d548b47d5cdd028e3a3abd4aec4575eab202943b3b787f9f8528b2574cd64d0999cda0b0509191a67beee987cf2a8933493c964de28e151b246098f12377eb8b4bce8c0cbf83fc3dfe5e360691f6716246368dccf9f7c4dea4efb3e71aca8c16420114393dc4b255fa931bfc6380c0d5bcd15eac39eab8c080c246068d63ab6b7128fc286f95f6812634e687db2c5dc9761e085462db56bddb164f5ea5c179ae64f4a1df4add805122e34671be2b6ea9cc610717281640b0d2b964eb7d4bfaa399f13bd964f612a2f2626aed208151148b4d0243bed6e442d65ab55277a7b253ed0500949b2e0781afb57b94b2b418285a6b923c285ceb8fcf731ff407285e6dbc9f4fd9cf4442f4f354162854633252f4a6795bff19fe8dd5982a40a8d9e6c8cdd5bca46cc469050a141dca4eb92bf96f850a344b5132453680e62d78650ae1ff35e1e990c2948a4d0207677b6d51ab64ecb277ae85e50e2438d12974c06251e8f92d37c0a90171f256ca85182ca112451685ee539e647b49fba0b85e6f417196243474da573a2f73c40f9169606c27897ee090d3a8f18624ae764428b9dd030b59a6aabd2d9156b3c51a59ca409cd9f3fc7d4d83b6bbd4c0109131a4495dc29b13ea8ed1a2dc9129ae7c41652e7d7254b0dd91b990c0a0b1b253c4a9c4789e28144094d6edbf677850e6ba83909cdda5fdbe84e42dc4cd3895eae9514339e750a1b2c2f2f28e151c2f22e2f2b6dac951433322d6ff292c9e48104092aada66b79da860ad321394293d02364edc573b61f19a1c9cbd4b9966152449f2e244568d09ef549cfba456dcc89c0ac14b55e5babaed2428164084dfa323d98fcded7518b44080da6773fbfcdaab94783d1b0d53e5ce694cffd0a1829713a47f51d1e949c9d241617202c23bf6892d7b66fc4ff69ad947ca1dcec4d2f55156b5f2f1a6cdf8e2b4a0cbd7ba11d4678a1123b2bb55deea2c955aec88e129fcc96a35d902ed8a99fb572b942ec72c18b1a75724bfcc81bc385b2a4bc592b3b3cfdb7d84efb7a5073aaa39a2d9ab5dad9dabed56ad1d873b795f451f918a145837c09757bfc832c91611dbd46098f12951794f0287115141794f028f99794c60bf22c5fe2438d9273a90f23b368a84fb174e89935a2238b57ebaf7c4aa1c3541d144662a158a14c65e55d857a0a61faf9f973529b8e1e3c34d005253c4aee531690fe61728e65c5a5390c498cc0a269d489eadc1a9de8ede0b1c4071a253b68d8c86480fc602d994c2693477bc061b2465ed1a82e26a610b92e4a8b277a26790ddeaa188cb8a251e9fc5cda9550e7e279c3482b9a85d62eb50a3d437dd4133d4e3718614583cb39efd0ae2a5ba9595cea684a78f86732990c43d964c3c82a9a8554d75178bccea94408e130a28a462dbc85aed7ca83bcd089deeaa5a2f9555fd7706dafeb93277a2d2b40120c23a868d86afbf37b4a15ea4d277acf0204bf30728aa67953bff2e3d344279de8a9ac6c19464cd1b45c46e8475142d53e65327726a7b0071c2661d061a414cdb7839e71535f799b5c0b23a46812ea5ae47d7abb95d251348ab919ff75db96898fca6144144d439898cc556a33466c24144d76dee333a40e5b2c9f0b23a0687cdf57dd337614a1fb271ad452ffd5e2a6546fa6ac34e4d27cce138dbea76665a41663a45bc2a38447890f354a32192028272d67a4130d628aa8f0f8e2f5ef8d134dcba5db3ced9ff6add8269a7e947017275cd78d5713cd49f5eb67bd895143958986ad2f4b747225539b12130d43acb93aa52cddf15ea25187b025a4ce22ea9396680ef9f0612ab1b3e91c56a2d96698bb490f4b541796959673cf50525ad2084628d130f7b3b9d83ae7da3b262e1f875e890f345c1e8d4c66124d6b8ad57a4e8e68b1f1899e9244731a3dba8412b7259480a0d081828c44a2f984a7eadc713ad1a3e3559e2521d1243bf68f8f8710634b859147348c547afb4d67644d4961c4110d3366ea9f6db5df62e844af110da3f34cf1266bc694f2133d4634a7791cdf19cfb97c1795c6288c2ca2b9839ec893bfafc5aa262edff22c8a689672c6f2a88349e562894e184944a3c98a7b1dea4c9777be0511cd299ee58b15f59ed6d641268c1ca251958a1551fe2de4aebc841143349efabc31cb6e6fa710cd2e4aed526a5a84bb16211a667e7892a673766d4b07d11c96d85108216ffe6b2c88e6f03ab1e47b5aa73cdd4034fab95063eecde57bfe440f104d77b66c671d62e3c650452161e40fcd2e63a82542f968b552277e683c9dcf3c6afbace6dac808237d680efea75a7576d5b7b52d7c6890e141db7a1d557a5a0b4acabba09cd061e2f271eca1e9a36db166c516d95d47f4d02047e9ec0f9b2beb75f2d0fc65befac56ba8d82b3c34ea3bdb497498327de2133d24c2c81d9ad599d8695596f2ecae13bd2384113b34a7b186eb51adfff5d60b2525a59b0aca09c1481d1a5fecb3bd79f377eb3cd17379f96182c218c2081d9a5db418e69d75d8fcd989de7293963ed752c6ee1c9a6deaf8a0b3ed7bcc288726e9fd694a79392f557da2f7e3958e913834dc58ad9f555dad31ba133d3834d91ab5d4d8f7dc3fe989de1c236f68f6d131dbd59e1b9a3faba126c4d4cdd8fe446fdbd02c4cea3729fdb698149ee8b97c1cc9a3840566ac60390b4a789498b1821e7098b48cb0a1c1fea558b1d6b2a9ed5f4373bd7095d79d75b299a9a169688f4f21ae9f639cd2d0a8d47598ceb2b510a273a846d0d0e4b57e236e2c756aad9da1697cf6a9e52de23c8c4ef4da25e53ddda3b0b89c113334ad1652bb776badfbfb9dc9a953ed018749187c18294383485dd5f175e9b96af509ea0187090e46c8d0e46d33b41c1d22d4f247c6d02837d55d7aedb6d5592a28325871514923f580c364062362d8a5bb8a9a1a312d6d7fc29ac7a3b0f84869b6a24626c3a331c39f3590180479171c1201c5481856d16126e54c4a0f296a5e4aa4c8f59b6b9ee620ef82939666010f3356b0862a538c80a14188971b97f5e9d61add179aa64e73dbc98656db1d37329997353299ccf23b1617205718f142b3def4fc1c725bce4c65c52425a519339517a89c91c964322dbb2d4056c268c248171a7c4da48dd462ad8e1b171a86160fb76a2cf52b565b689a324fdf5bbe2969a733da28e90187091a235a685c5d57353f683f39337d309285268ffdbab245074f17169a4b9d124a47b6cc98d4151af6abe8ec99ba745c35208c58a169d42dadead65f85262d7574107d6b57a731151a6cc51af2e1c59ecd7c0a4d3aa850e169ead83a9b149ae66a25c5149d8314b68d42834ede22a65b2a9df3ab072350689a33b5cf9a714a858a4f68f2eca9f566db6c931e6b1d234e68166a5a775c4e9cd2594d68d6616dcfcee57a769ac784a64fea6527adf339a5c90a234b68b41962bf2a9762d7554a6892b35a997cd562bddc49689ca1e6dee8542bd5474283ee2b134b756bd91f8fd0f425f666c51269a7e61123348b101f75566eeb883c5284663f354674e5e4c8db23446850bab6fe2845fce8ac73a298dc181942d316f559db478e7e302284268fba662344c7f6dbe7fa402418cde16c448b13b35e0afd0ff646528208301a3ea7fef652626de537fc459388ab1bfddbb2be6b3c10f14593945da9e294e8dc14447ad128efa5b808751dc4ca8be612f36dbfdc8e1da713d945c374f9af57eac99df111d145b31cef15d3e695e9acb96856fbc595b0bb53ae4c5c347deab86dbe739e62768bc60f3ad45d0ca1ffab75da28e151726050c2a3e4b051c2861a259b02e4078ac9a70079713151b66852e62ee4e82065cab6b9a0a8a0348ba3b403915a348856a9d6a73526e7b9121f6a9424ca21428be670eaf75f52a821427ea2174465323c4a80f41a778ef5c2b883c82c1ac696dfe1e47a6c99d7109145a3cb1873a6986c33a90b4462d134c7b3f21c7e7a7afd444304160d9fb4ea346d3d48d762277a7945107945a38bcf5fed518ce99b277a77798d882b1a64cd6c9d2a751823446e45838e6a9c8c1de35b6e87e320c28ae659d3bb773ad4c39ec9989c5a45b3889e4f1bf2afd474a682882a9a7667113bceaf2e315c277a9fe228262a2b9a86ca0b3299163956564ed4214452d19c738b4ff5aa6aacec4ff482b0b8dce96d0f384c5420828aa6f5781d64b71059b79de8b9faf12e696432253ed02829a3c4871a5e464a4aff884126938906915334adaf7f29a6fbb984365920628a46a12a74eb0a1f2d9e2d61438d12ad8148299a54483d42d8da496b5b93a2e1c37416c3eda6f6584f74930d4446d1e4e9ad6c5faba14c4c4ff482b0b86cfe58613923088b8f4c06cd20228a86d57a757f0ce5715c8bd90b22a16870f55e9dc627f57d51174440d1e4fae1a30e367e3dbe9f684e3a6a6debae526d519fe86532253ed428a963c58c4cc6bf8e9585760e114f349e4d5ff11c6b224ab8dc89e677f1e243a888f7ac7551f93938d12c46ca970bf13ad153977a886ca26967a48a8dd16e7a57eaa220a289a67fd5f961685b6a4da1828248269ac4923564d98d59fbd530d1bcee614e2df5bd6a8b41098f4c2693c964140b229768d441ba183a4fbe4afd9de805592698b2da5f4506994c2693c9643299cc8abf9c18442cd1e42666d4d452ccd5732342a4124db34a844df5524a34dfabadf5c55653aaf59ce82994851099447318d3d57579d452148868d22ed438b1a5091dfa9982188768eea866c7955aa8473db6cbcb0f93202c2e8668d65929a14edb8ca83dfa979476232521885188c6537bd7fe4fbd0e3b219ae4549c69756288f5323888318846a1a6f2f570dfe2769a209ad47e165d1d6a46ca59023102d1bcd2a3897439f5aa76e94ce6553ac504250d4e3688018806711bf55fc25569fdffd0345e4adf88def21ca5128ae18746d5b7e663086562a9d9895e8b494b9006723048b3a15246903588d18726a1a37ed95f5b8ce12b3e34de07a58514366b2919fab813630f4d63d7a79eada76f88dc0531f4d0e4a52e568a29ea85d8f3d0ac4bdf9673aa4e8ba9f1d0b4a3d466ee7a943011dfa1e9834aefbc72d5562ad9a1c9744c9f35865a2236ac43b3aeeba4d7ed44fda743a3ccdb2aff6ebcfcd61c1a94129d65f477b2a5741e253c3688187268585b940bf55a76f6bae2d0a4c3f4dbee13c3a1618a5aeb5f87d8351f552288f186a6a9745a5a7cfadcbafa8b186e680eaab6b6d8f5b62f4caa458c3634adc8471b3aaabd101b1b1ae6497b254ec7d4d8d1891eab408c3568ffd2c5f5ac997a931a1ac5f92b17abfc6b4a511abab143d5abfbd52ac5b262c2f22a741c6c62a0a151ad9d9e530815dedad36b811867b8634e851417a542c4ea6c428c3164e88c4e5a2b0331ccd0b4671f32d5d2ae765ad528f1a146907781192b284373d459be3a35c7f4ea2e06197ef71d21eed63e86e6da27d276b8095f958ba1d984c897fe69af545a2b0ccda94eb7f4153ae70e6b3034d7dc57b66e6e5b8cfd85e6527b4d0c55f2ffb4eb85a6a9b7a3eb20dd2e34e9dbbf51ea7667d9592e34d90e2de4fd736c7bba85e67ccbf4aa55ea3ce793161ac57fa6981fe655cc9485a6132adf27a50e524b11161ac68787f9e249a452a12b34dbee94f7b4cfd55d6985a635a657c67d56159adf7510623e9c7efe910a4d273d28d151ac9aaf368506fdeb69afb6e8514327850619b3d377b2f936fb281c3fdedd63aece891ef311c48042c35023c4f47cf95abac9623ca1414e8a7aed31d46ba739a159558bf1a8d5d38faa6234a151c438359bd50c5c1e0d952206139a4fb8f87da9ed55e56d098de3a6b58e735a4a68ee34f29ec3dbd865da2434cbbfdd793563e9741509cd3a2e953a91a3a2644768da2326b6f872e51ea58cd0b41ee5ad4e357b554c318ad0ac85ea09a5c4edd62f13a15989f4acd389adb5a11a42a39c21d5d077911842683c19fa53087dbade96c1689a51a985d251e90ab10446a3b8a957ffec2eb1f6fda2598ae778a2be45d58bbe68903e6fde6a62ea45c329a5a66fcfe76ea99378d134cd66b91ab7e4e9dbd94583ccd662b7ab4b112fa4ba68d241290f63ca6e78efb968d2ba6caa89cdb775ea295c347cdcf87e6173bf5ced168dfb1e85ab2df7fd94ca160d4b78eb7c0fabd334af164d62f46a39ef67dbf4db2ecfa248b08316cd5294275dbba2b679d489ea193b66d1ac3b94b85242dcc9519de8dda960872c1aeff354dbf6b0c5ab3d16cdb9a3d8ce6928312e068b26bd667a3baa584a7a68c270ec7845f32a1b5ab54d4f2bd5ee8a261d53747fabfb646bab158d3ae72c2e6e2a74cbd00e5634a94d3ba93e3a799fda2a9a45a84ee15985bead5baa6892624665e7ac747ac8a968124a8b52ba86dedf1a77a0a249c6558a2da63c1b32768ac67fe9ebc2b392abbf3245b32b37a5c456cb9cc18e5234275ba9955a2d746d7391a2f173989a2f5bcac70b768ca269a592a16445cb11fb124573d8b9b5964ddbedf63ad16b3945d9118adc018a4fecf044d3da5ffa94eca43ac9ef3b3ad1ec2aaa73ee60a3b57fce496107279af4bfc8923b52f69d7c134d37e375548a5b2d4bd744f3a793fe5187a931de9189a62d66b7f3a48950ea8589a6e1724ae7d9fe599f2ed1ace6213f8d696ff9594b34ad7a7167a25c8968a94a34db1abb547ccf76e924251ae4a7729b2f3bb6d0c11d9368124a8ae1eb7d5b695b9344c3f86b5beab73cdded23d1b46c68dd23d5103711128d1fa6f8dfd977eb86fd88cbb46087231a54ea91993655d456423de03051d9d188e6da1536c5cf9c88f932a259c5b61b6abcab2a9d17d1a46e8955733f53aae84e117628a2f1c37d92265e9988e6e0a926d3b5981dd648b1137620a261b498adc6792b21c5ec108daa46c728213a4dbd19a259bcf99fed12dafff38568b429b5c856e2a3db9013a249766d17abe265424dadc9e510760ca2495e5f08fdcf265e8709a2698cccb7890f72ddc4dc1188e6f3371fef1a29bf8600d120474e5ceca4ea5bec3f34abd7b52bdd463cd8e187e60eebc4d61bca6bd9541f1a4666e8d4fa9ea3c4898e1d7c6818f15a9dd2b94b906572d2f202845d5e80b8bcbcc08c1d7b68bc8fc267cd5d76c998b9430f0dffe2566a35112a962b0f8de7fa74b58e69071e9a63ecf93c52678d1b3b30ecb843f39f8b993a6e2825c36587e6d33a4c6b7b7d1355d7a1c1a46e71627c9a52ca76071d9a3feb0e7a960a1f39af0b3be6d028942c79fbf6b4f8abc9a149a64e53662a1e6c763be2d0a8374c0d19a5cdd4773e5ce50e383822d373986e1576bca1e1f473ecb257bb476c3734eccbb6f79d3162e8dbf0ec3ceb2f527d454fc88626addea58912d38e355c6671b0430d3bd28018a53dcae954e59dee4043a38aafc973537f2a43699c3371c38e33348a5ba3c487cea767eb30ed3043f38fb78e2bc66bdde1e2512283121e2569b874ca19998ccabbb8744a9bc28e32346c532dd7db560ca16b6468f47493d96a4d2c1bf33134fba9165bf4eeb77689a1f9b6bf88abd6f39b79189afb85d242bd7ffc6e0743c35ab1a6c465ee97e85f68107a4b78d48f5329655e68bc3921eed9e40d25b5bad0e0b56245de4eaf5b65066107179ab57a1bf57262e67ed6169ad3b2b5f4dbb0531f590bcdad7b969c88193184360b0d9ebfa6e778a55a6c2c34bfaa4ff71d66959aafd034648735c492f52a27b542b3d6695ed5847499af2a34ccda4aa1d3e4874885062155cbb1bd36a5a7a7d034940e51b6f5ecf62c8526d5a7f3d6b510325e158566ff10e23bacf123e504854653b2f694fe5be7624f6850cbf587adfedf3c8a9cd0f8e75a97db966aa7994d681e9d2f5d479b9ea68d09cdb7a69491fda144ec71090dabcde3499debfdf4a78466b1f4ca1a5daf336f496834715d1d75c9fd131e243487546bf8ad9b8cd39d23342d25e58ff22826950e1ba169868f58bbaf3dbd6d2a841d45687c21cd4dd44c2bf77407119a4ccad62ef674ada572a9e355501aa5770ca161ceed5c22b7d656e7ce600674ec1042b39a9edf71b427bd21142065047139a30d14209d61f90549301acdf3bf47b5dd7ed92b37cc306159692629ec1d48cb1ee4061260342b31c6d5983ade164afc45d3ce3e1e74955453b1fba2e1eec4c8e8306ee7d2f5a2c94baa1ced6ebb7b563590f0a251d5d4a773ee6eb154e90a24bb685073528c3ee925d6551e24ba681a62b5ce426fcbced97e40928be6f4af85abe9a4d4fc5a144870d1a8b67da9cf396bb5ed160dfea152ecb076c850d9a2596788b7b49ddf5baa45a396d3a6d5faa7b1859e16cd69eb101eae6bbd52d92c9afbb45a1d3d9fac375b160d7b7adf216b47553fc7a241dff7a9f697f594824583d71653a79571a993f6158de21f6d4ed7da75bb2b9aa3ad569f642cb13ad68a6653774f3a77cea16c56349cb8a87ba1d7a316e92a1a4fea2e295a3e6bdb49aa68ceeb4ae8d3e2d43d2d15cdaaa6ddd0596cc7bba86816a2e7334dedc732d1291ad5bef69558d96248a5291a45fabe4ca1b62cbf95a2498c534bdd94cd37252445b354536ca85ca1b5fe378a26b59e5db92a316a4945d1a4c5f6abe99a2e43e947aba0121f68dc310249281ad65c29961697ba735a110f140d62b716faddf3a9eb6b0a249f68ae1a2ab47a8fb594cff2d2b292c24c165352143dd1fc2daebd3efa88d0f1133d4447b7010c924e34ac1aa1c4848e997ae7133d96202c2ee9509ac587154838d17c62ad1163bc4bc3e4d160924d344a2f35f4fd9ea9af3693d92d9068a259d4a79d86e8d14a9bee99ca8a4ac685470b8f202c6eac81f9921940194832d1a4d3d44a6bd73fe6e362a2d945cedcd55833f5d4279ad289e4128d52ad3973942751f942339058022bc12f24946834d367429a10d9751fb1b80069836412ac5929cd5327357432992693f9c10a0b1249206aeb9c6cbd105adc1b89bfb388e8e061a4f44ef44a5c5f4a7ea09cb464e62859fa5272100b09245ee5a1224fb6cb6ed7e5650d9533121b248f60a4e820428af32465454cd58e52c9f3936a2c9de8a5a434432a67601a248e68d2422d5719bb1bd1f03752a738f93774589e33394634dea9d269ab4ce9b376248b6852b1a2745cbb21e6121f6894005961594126936901b2e2031245348eaa1442f45ff795545652786432afb292c2039244349894b1d139bfb8946a3de030c901092296236be94f7fbd9d4c8760988b903362febd18211d9018a259dece4b37a9a01cf20d480ad16caa93986e9b177e734234eb273fa9226eab55ae4134b9e8907aba86bd0e6b4134a779ba32314baa99a240346a2185daa9e7b76e2901d160a29eefc48d57299e3f9c956b53f345dce9288390f8a1e1432a9d46a4d82ec4b2720e7140d287cbd441c20774ce5a3b77c8fe5142997640b28726756a4a1bb65dc9c793440f0d3b7dfc0ebb323a2ecf3d90e4a1e943acfdf9c66ba594961e48f0d09cc6a81f21cfe40b9ddea1e165eee9503fbb03891d1ac68a54f3d5de3a34ce2d1d2db7335ccd6c03123a3401915fa8844e0a15711c0c858120880110844cad1605b31400203020180f4683d134d1b4e90314800340545ca432281e14c822814818108401613020100cc3200803210c03411005ab24748f0018c71e4670c790eed24b1fb48aaa1a8e1f3cd090877c1a100c3dfbf503705f9e6dc341a64406d43a71909887b6f5c682ca2941074a15a5ce31d139bc22973002545540e6dd5163512b1ed9786adb4301d8fcfbc12396f764ded133d31af749400fad43080f4a8b573dd02bb3e3030f42c8c23dcf373609fb14b6fe876a81f2d81e74686841223dc84374112845e848ed67a78481cf359745eff5d54f81ebb57cbb048205fa283723f5d1efe08bc051611da2e5be72ea98e23930c73456c0d074c4e3209112483a02076f7e506c2c19cebd05e483559af7dd6d40d51cbf8c5919ce0f0fb0bd48663d31200fcecd25da915dd549f0a5fc7e7c31de9eb445d4e0208cc55b22e5c56c458f46ccbe4ce01a595fbc15ddc51190a63d123b4a633f6a7e80721f01125a230303f107233cb002a0c6a7ec04bcbdd00bdc63dac32775c92da95ed25ac32a8206f09cb17114030fdceda98170467ed2b72009c32345011773793328843812b5bdfa448ec210116f804d842c5187b5e97e737df537ae6691931ffb4a456b73c8da48c2bd5e901fd53dcfba553cdfcea696e6c460c106805d8d92bf1f2f40d762689c2980d18703a877681f2a0a4a70d4451478d025ae5e4cb2f7287774549430d405286e9426143a5058448bc3cd5c68a1531654fca5dcac0cb1ed76a01561197884818c500ebf9ba642caeac5364e6ccc4d47d99fc1089eae784cf8761b2a7c1c9a5f668bb6318de360bd90d5a0bafab6e251cb509a8fc28e13a1f13e5b0495b60bb85db828c62706b6ea2fa6960872c9740bef32d92451b1d65002d803331a4047b7df2b8df91415347d0784f900bed44c5c4cd5e57e9c80acea22be1f5ff5231ab8c83ef07a34daffd1b85cf0f39484b1e4e5a185e4410582e5ed58118cc889ec2280d7cf8807ce743b509667b469f46c661a439b94c2d5fe8a780c98b012592298e29131d7bbf66bd8debffe0b88c8a6e573bbd8ec0e4b114e003c147b57127c1118f2ea0bf29fc2a0abcec126679de0b420e8075a9700951f8001021b30c7aea3ba6a58667aa6e0e7c037db9d230302b742e7282692154e77d753c9c19b9c45b3f65b2a9a61e4f585019870c7f608627a7f0bb2b728c3c2880d7f88ab091a4a9a4d24ee9aa12bbdc351d30b3065da20b39e9ee63e984808f81776f5d6458c9bd82e6525abf2ec58291a83d12e4e14c5894ebdecb6f9ad0667afd8fa40551b7c25811cd4c9a408a78e2b24ad145c14ef1c8fd2f99a5f6d484c23d2ad60480d2f0eb046b714b11326325775881b91cab5c7a433b808d1118d42cf272f5fb9e75d6aaf0d7f169388552586bce7cb70918a949cf1f2a412693889a78280df1373de2cd1a7f6e43f8ff05fb99201c9d31bc4690d5b0c0614cc4a16d0ca5153dc09161288d8e103c8fd350dfce83961097db2f9d4082f6d5082d41216a83f5d0e527b1b2eedc304816fab3309a32c60271180023fa020925c737eba0f9bdd005e52202d947e9c36d1eeb204ed5082c6f8876222692fc3d83e3184a708cf2fbe32b4b0625688f80bd3dade7d699bb3c87a98a614848d27e83343cee06fc0c752540c14a2dd9a00fa20f034cb0889433337ddfc2b9543c28e07aac0dccf6ab776dfd8688258f1467f16779100c31ccf9032e20206384726edf817acd7845ac1a865e03129b81afa9a02c6bc92640c3bb09863a102eeaca0057cd0dd4dbd63e06caf08c3be10ed1c1ed8cb9314053788d197148989d9eb9ad63d6eb0d510a5563ea370d80e6e4388e3981c5b7a2c234541fe337aa4d4eed36907a373f3240e98619d2ab840db461573e7481090d4b4d856556fa56a7a7654c9265dda0c714658f1b0548eeb861996a0d401c3197af0fe64f7dbbf0767275a7fe51dee1e747a4e9a7e83234af723c8f53860e8c880523f819746d2a547a21d7bbc48674f74b37baa63d08bd387d5b88cee79ca7bd97a1d3dcbe14509add1e3a8597b2d6f57005de6db820bec436dbd367817ffb611be02c9fcb12008a7518b302f1044cf201fab41bc1e4dbf9c40113d213ebc183c249f377893778ec6ff28554b7faa214538929bf6a52502f917322dec470ccd94460fa9a3515c2b31fc7742d63ad44e9847d34f5993aba4f2191721a60eedc236d83e2dee9cf4332798f8d503e7c4198135e24082e6861b7cf314883e2c606649431f3ec95010fd0210a41f1ad37307aca193e9e1936046e81f72bbeedfa5ddf12c4a6a1d7b3772b385800d32d9f7af8065739c5f98d90bb5feb6531fe091864aea7905d1636a506b99a0632727c559f9b16cdabe4c44f7fb8a0a1a21fc7c86d17d94d67e7ddff97ed9885fa270b8c2958fdd268f46fbacd4a1eccfa5360fe0e218c1484a80564ebfa93f647b15526bf515763b10305d61c5ad05965e649599f7a3fadd2ad1bbe88cbf7b3f098b6e002da739eeff3799bb4c4d1b89a74d172f3d874e64d2c0d46d4273120c65f109cf546b692e1404d3504e56a6b8a1349af99ca246059aac3981f77441aa5df07e6b260141fa11efd493ad87e2560882f9294467ce2d1283227f8f5ac12d87ef4008089c5cbe257683ee87770bd7ed7aeacc61ac9dd87d7d8aacf607ff5ac6929d5f215aeae5a4c64112160c3de2d3dee43a5c750437c9c1b3873411faa85441f527eb2f7a4ca871d2089457769d5380c14e3175365813bd6743bbb6e117a9a2c16596a4c2617337b5474caa260cccb7f985b0d03b6970dfa743e942267bbffd8be83ba746b1a964a766c7c1de36241d390f2696541f2551ea070d4c18193f0b85e9864061940883712387f6d50d1a01f1b54e47fa71dc5482bf15a1e15a8414fb7953156d7a2c8155250d03d2aca6a8e9ead0f7bc251647846ae92c8799b96db1a797aade3bbe4c54d641fc464aa92e7574f5b560bdea20a782332aea674833d8b8525990853d5d1b03a21f78ad9634838df2c44a80ac0e1d9d7ae790fb6df0b29a43cd9eb4cb41718f4f39a566240e0e9940737249d65d224de27b4c6d27ce051c6095f47ff694f086cec30642819f15238693c7e29f6f65f6389e7e2216c67d305157922e1e431f392580d203402df241aa04b0f5b88811fc8403d3abffdad202db7ec59c8382fd0198a51960083c93bcdf07c13944a1d785c86bd178001c1a2a4198a377fe73b8a1b1fdbcad2c130424b22edad3c40724cbb4c9a56d4923c89a6523eb2eabb1c70a62a10210c5b3b8871b113369ecf7e9bbac61b5abe4a38b65a87d83492e55898825aa06bc8703df1bd38535cfbafa7c895d2adaa8efbf480eb69231a92a32c363bc14a2a64884be8b9fa4ecfa5407979336b99661a238e14e676d5e2277ebca3ec0bcbb55da751cd489fe539e703675f4a7edc7ffae6cbaff4c74feda80053d3c7ec0edb491cfe4aa57d349c45442c955fe39cc9904e06d89014f2551285c525aa1ac58ac5302707744164ea189084883d748e6f5df8c2188c33df501df67468dcf5005ea5535659ec3ece5c257fd773bc2807e8de748355cbfc5b19d2f12d376c4bf561351b81663226595df2224a786cf3226e448a726938fef6628a6eea4c6a1c6996627bad48e3ba9ec7cf8c7496966d40af2e70645165a0af2cf8ce886622377cf1a2e4bdeec9007a831a7a52e5fc6baf3488c4c01b27740ea63e223abd6663feb02602a1e18ce1a244afaa56962a6959278f94e86d436fb763f2acb73717ad938e52f9de2d3cccbe553e5867093d1972e31add23d455a651e89ecafbd3554af60fc2766015e437de15fa1973dd102187a310f2bce2171e699de3831795d75cd6200b2f7d8b99a0be0870bae49cc21628a1c8f03a4ec6afcbe73d9cda0c8828f27cddaa158e1b7b2e6809e2e741f388bbdf0ddcf64fbd203e440b9b96a0677b1c53373e3eada6e1512c28680d1b61bdf6f05d8d15ad9332e73b5cde53cbb6135b5945828ea5a999c0ccf1713f1f52191b35bee472e6e4b676c172f4e2c467ec27427e87d424fc7496cdf4d8a3537e0bd9f33ffee1e486b58e79d3ee91231e9986f8074566080fd510c3106291bc85931ae3d34412cfbb6ac6f7ae49ee646ba463e9956335621dce5e93f05cb734956eb91ada35e5b63f086c3a27233b6d700e4b1b3a1cf96352c1c7564178239297d92d82c5d1384390cd58e3c54ad042273a941e9b08dc12edf5605814e0f014d7372e2c22dfb546b99eb4a3b90f97680eb83dee27dc585e3fdefa340e14b5839b00ac75dccaa299b5965a4b4b080845a6c6483bdc14b83df437b828649b3c4de650c43975bacc262c17d8724bf44a61ee4f37764bd82cbbcd48964d71bf2153776157b70241a25763a93ab63cdec06c34d9342c4375cd8a50f94c1981816888cab43794246b16cae129d3080f4f9ba6205d51232006199fd038d2514c257811dff569d51f85c96dca32d87932b6130a0899b3ec6dbe0dfff95068d443f33fc0c8230ba22fbe5ba0ab0437a3444c527368ca444171c6a055b8c23e56ffb29f2b4d347bdbd880997f3103d8c0470ee00421588f938140876f0571780a47a51852d8f9ddc989fc50266fb6334082066845573de490231fac8dc5eb44907b73de5528f147e386eaadd63c2321d0eab51d4bfa1aad91a94ae6364a41d223418b1bc9d4ae078b7280915e202c3889e933e5a479ca5e376da80dd0d1b5e83611d78284598518e632eb8b1d5e8f7207a1301e0e650833d65fa64b87daa72ebcfd1dd45a93af6d3266b021f199843d241221fcd17d46ddecc9d458b00248dcea895bf3d7e769135bbfca201626323b166b433d42314bfd6aaa1e1b2ea6ddc7a81420d9a5228d29af619d7f50e03d8184ede7e41710f418b8bc24fe644375da5c4d7d99f5608fdc327f8af3ac518a7d1360c1b93a4080ea5190e6edcde1019b4cbd266539784dce06cc4b91ca42411aac9870bf0f47af134095855a950cb61fe85811b32029cde562b9541ce0962c05fafb074fa06608c8623df6f0a8617ad0c097d4d0d3025b15153b1b11ef0bad389c70d10be48d133da692c233de24ae16698ac57a589e08f4f49273da52f47548556e81a496715dc68aa2778d6c7979880c53445094d42dbe11f4b08724520e7e50bb4da220a461eed50bdf038a9c10e1e7ec290844c4d89819c23c928ce4d56feaa0c70afcea2c4255977aeff8897e3eecb9730e4b40054a0f75eed020932b1c6b03988cef707e65fef0192899cb0d7fa736daf5cdb1e12b7dd675c85f5be0acfdb03c4568cfa4698fd515255d67f0105646b4a7643bcaac8d801d0b92660b3228c925b791eb351e7b5c17956e4fe9bb42b41d64bc8ae8ddaea1f2b457670a9e0c7084459baaab6f473565a9754d0112021359df84ea85cc94f4e08f03aeaa157608a1bffb613617d639395f3d8ea9e1729c0e86b3ef0f98285a303e4474784c496ca8851c1e4500813449dd589d2578c4a49f3329d82af02a54be2b736a68c6646ba31e98e8848205135885fc8581179f1009e1e29136a1d69a2c2d377782e4aa48336fb098341066b3702b9b7d8c4064a68d87de482e1a63f6fceeee1325693c25d15125474c59ea6d19b417a021c698bb486e16235cd780e9a6f40f4eb769d543e49d08aa01e04ba1cce65d0793624172414164c0488bc2aebc22068f54f5c58223d6b7e5f01868e98b6f548d20ebf9a799fdd0364cd86b7f19225abdd3407798af49ba8c55f2bce9b70ad40a87816b588702101a6f7e4406f187d28cb68ad94cc50e172deb040b489026431e09727d89179d5820c3acbed2f139b9dfec96d266a656b00ebf2d95527b79bc30baafcc516fb1dbeb577b1e609de1f0d23c58b0e54834aac5b8251ab4285b24b298e3c322fca3be65579983fc142569347680cce7877312848ada376597a6ffecf4b7892eb537a7eedb8f64b8b672dbfa14a83f2fb380e11e8c5dc29ed555daee9b6d63190b38c345b87251d4d49d59fe8507af408bac9a4b84f085069d20a7da68afb56a5fcdcd7be654d16d1c557598e8c4321b3b5b9824d58d2a6b52852ed76370831c1f91dcf8ac8359dece5278849eeba42df21ce5b20dfcb96d7ec14578948f37be46bac93500ec9b78b82f455928f7f349360a557c0e89d058578e85d37226354d21a0ec2b10936aa210479a48d1a7a011f2fce7857deca58e98eb80adb9a7f0def8d61654a83241a422ec1c00d521e61fe42c64e2463f9dbc46217d2123a491264f4cd2d4ea1901c39114581509c026e74e4018b34297bb75289f80ea4d637fe7191a05f32eac527706f7c630186d6f8f156485433c9c08cf21db67d51aa04e14b8874096118c5d08dd5c8ad1f23db160c5d9f8b722a1701100c905080c2f4451e7e5a891eb83cfe382987fc98875c599611ba1ceeae04ffe05de95de61b57adf2e893f04c7959911d0c24cb803358d3bb189a6a2a5c9947f1cd8a8a1c6b0b7131b869dea1e4b905aa5c92d612e54384ca3d021021e6c6a3fa8cfcd709a2d0dec52fff6d5e3917e92154cb59ce7c5691e53c0e48fc3501fd93615743b9cc99abcb0daf4c070aed760a093c04c8440bcf463cd8e0c2722c10a8f95bc8cb6dd7e0b729bf4e5aa2ada456a5044d89092a66b029c9c30941177c98a3ef1ad7b08de27f5370bcd7c9b9b192579b21596b5bbf63a54a37ece23f4d2d2c477bde18fa3974b9dda3e520bfc121d4cced09dd34a5ef06b56d3d3b96bc6ed60d1582554f6fec15d5757fedee1fc0ef4d5c57d4c82f52128bbd778d661946556a3b982ea568acbfc165b20313b9463d9e0a97bf205a6fd99dd9ab44547ed486a2dd2c1e0e1e16de73125619130b2908e3ec4d298d801b96e23a9017e5839b4454bcb011cc0cf01bb8b8a70a3260b02d9f412305cece6f2e2c9907e37976e1f811e9641fdf5eac2389a51602f87dc4a21f31a21d0d0fd0517584a5b002323eeeb68c47aa9656c6e03c0c4cad37d8c06850af8d2948e6b3a929d172ad8989725222699d1ddb5d1a380dc078b7052a97d2d263d9b3167fe8e96a7bbea99455d57563771e989d56458a321a344cea97a2d4ce09b2c728c2be3f1f968e95a367edde6a0fdb9d4c570356d89fdf001f27d69817804819e4a6c587e69c16370469cbb4af546853d5dd044894543a6329f3164c06371791477260b9914c66248149a892a8e485438c32f0745a06453da338f438493a94b460880fde51b8f4e7829239ec8c727a90ab41dd4ee84bb53c007c41f1d534880b3335ddc04fa0d9dcd4131cf998c44474a88fe69ded30cdce3760cc2e8cc25b02bc576b1103c1145145d41a16657bb826456cf5785b29a2c1f5bc82f645aab96aa098c7091f6fc826488e4e984ad564bb876ae7d28da1f7110836926e80035c8a06bd4468b0047709466131a59aa16144d5c3d9b4b2e8ea25256e514074495a900201ed32dc5f94e59db4c5f40cd07c3faa96dbdb10f237b3734232df47629c86a214ee87299412d6a15d83568d3ce9edbce6a2d8b14684d10696fd7d575a96c33bb3231d825bb5f07c8006e347a7ed4e510c6bc94cc65825112f70aba5c35dd0ecb66d77b440286d49f685e758eba6169a0f28618a0a1b2bf439685f19c4985c5ce890e158f30f6c7d81886cdc9951a46be0b0192058bc8c19d580588272bb3122da4395357a3304c168669a1787a877258a47e60dcd12f3d48aaf0611839e4ddcd5e2ca52c85359173eae81edfa4f73db927414c6f3f925906f86f2669b88e66517a418462438b29bd06fe0d629e6192e45a99de236bb2b4f0c84c02fd2f843298265588d7cdad21e303256388acc09df6f364b2a651a39cacba0421f4686c132bbf894e574e208b917bde9d0381a8b1f0567ec5872e53f36e1ed9faf1bf4f127863f42a5872c1b6e25e0208928b4554e4b08a30f0675bb6bb82004027cfa887937abb6835f7fa1e3b734aa56c94038d1279065bf7a3da7db53c9cb32803c6d4e3b12381a9a246e649fae618e624aa59fdc042a3cfafad44ca0f4a73b2f4ab66b186c36d40b7f20d46d3135ad7b80928cdf6cc406f0a9cc9620b2ba654a0fae8507bba6e73f41642e49a0cb0f5c9cbc073b05acff1347c4f7e61c6d05eb012c71e601d3cdabb61ba8e550e24ab154a57abf51dd3f3a1612221c061177ee8c37be0ddfc543d0ff81ab0744fe00ef119e25f3b73feb6e64691f2c424e550a60fb2b24a446a7dac88268c80d1b2840c2d0573ed7f9edcc671978cf8069511b45e509b27d55ea903124aa28fbacb5b37bc10c7e6957ae4c77d85377060b8e38b1e8f2c0a4c495f01c0004a17ca6c7d01e1f8e2e3cc6f99d123d8583a0337662e6ff204ea6316ed0c369200ceea19b98217d2407c8d80dd54937160678a00687413db82933d047e0b08cba413b7523c0a007d7a030d023373103f8380e95a137b4136c240cf7a01a08437a701364988fc541337a0374e24687a11ea82161a0076c5a86f2886e949da61936064379528d05022638c243a81392ca103c124ab0a51269b73b933ca35e94f3cec13d0e439d62c91c21106062220a14ddab78003b9987dba37af05e4927e34019235122f4484d810802e95e681cd623e5e23961cff02876681eb50764518403a925389240d01448397b4a0fbd47e98038ac97cba1e7e41e8987ec80bc666691e88822012548e2a2f415cc33ed41191031a0aa64baa2e7fef02871e29c60158787d2c538cb0ee2734d0c9d72bb9edda3e8d2984d022438c243a891b216bd85b150e85d4c8f336773d4bd94ce1a5c2bc79cef28dde5656609498e200eecf01ca4613fc5066a3c1cc8e11af4265a4c1102017e2c8eab76e55fa7c5f029cd920bc1a79ca48af0883441110064a66a848644bda54b03a905919e88fd42f6229ec80ef3787b480fbb27e8621c574fcae1e7e81e277e1f99b2fe76e990ba521e2b16bac3e15b4ec613767170919eae47cdc139f21ea40371ad1e25a79e83ee465ff0d789e4ee593698404a7a500fd34cf1e7905cfe72f85ea42f119e8d110837aa0dfa4eaf189d9688580b4a2ff8bad4e0578aa4b0f0aae6ebf016eac08758a88b269035d3db60d9bd44001794626fa7af96bb9800a71953ecee17c8d1daa8d6a4dc857e4dbd266bcd88ca24f605a55091a74cda9959622a273952a4c4af6949199531e12e80cb58d566fa944e0b50f892eba2d768d294224a60968152b4a9b4a20466252c4e97ea42d39253e682b64c97324519353b680b75859b5506ba3e0a7257dbdc4c04290f14abce565a9984b4f25774ded69aa901c21204732c2f6818971977536ffedc66a0ccc2f7d2041d00cd69ac3bb5e747c7843b4eaf2b2f306ccb7daae670936a9e6180338177950e88a2df59cef3b86325696aa9233e1c53a78fb65ff4c1e90238c672d3147d1a3e323d91b9859cfd21f3598d934948e61b70ebd5ebb8b4b04226551e7155cc5e5215beab8a281feec5bb86f39f580abf0cc3dd5526be93943e1d2343d7025d1d7145e7cc6adf58f8268715e4c2519160a809d36e17d7dab71f1929fc2f2bf47f16f82f0afe070afc3705f93fa820f2bfc23d2b65769f5f32ec4e1a2a496a6ed244e104c3fef0847f592707238efc34bac0bd062ff552cdad642a27fc25659d44587c7fc445178472255a48ac0bf85b691b0a5db65fd9aff0a28fcaf0ac878efa4d1779aa279778845bbe69cbb9dd614e20bac9db813adf58ac7a16a37b5ab0c5e3f43fe71bc51267338b3e369b40dd181488ca8edaea2ed4fdc08e8e510a20585ac6a45dce827cf0c1d52aba30f978d1a284fda380e41ba61d03c2f68ee9fe2a3cc7f5bed70e73daff39d49b6a1cc84e69419d90f837719c6459535f53dd19180af34d81267ea6cc0687cd436256ffefd211d06a3605471a4aa748fca03168b90748e1574083c3c2e8360f83e3add23fa205dabef7c688d4cefd88979fcd200631a0dbe4794c160c4ba0878056e35dd114f5ea4a0d891b1819a998bbe9374f3b5de1b14bab51432747e55aed408472ee11347f5857fcfea23fe46c6a850b9e792f16f14b128d2e13fabab45a42fc640d15717974a4168f56ae70a11ffc3f9a6f472561c492d18e5386c2e887c91e157164b68b8031439010c14e1285aeb9938e47138d0c2277ba3d81492bcc445977b39e8013c65940afe7966af6f4b12770fef1cc66e0a15e0ca737d85e47f1e6f03c7aa893aa57d134fb33b719d16be5bfd35e59542ae463333622816f66929ad5ab07de0e9aef6b75ac604046839760be1093a0242edd12d05c675ec38c4f00876499f0ecb232340722e989f8880d078c258718e8ee2582f26b36c0a360075f9d09da7bb03e4f640350385ab3c6164aee53ba608a193c18cc77117c0b5064e538ff8d1436114c305a417857560e1f1d0d499b2dc223d49a731380859b5420b8f2326ec7ffd2776b14520a1f6720c399a5a5f9775c639f0b1648c21664b7968da606d0835ae904eb7f2cb8e2e39c5cc61506c2067a477dc60db8338d35f8a5a2d64d5b81891e97de1916141d7850dbcb0a7a33d0bd161e47eac9762adf72a1a432e2473c8d63f1e6947918663add0f6090ef5147eef1b0c326377a649e01bd14bfe620b6b1e33b76a8a02a6ebe83de695943308b8a788acd338cbe7a521671f82fa2419b8283a4c28bce24d4743bdebc18f144f7fbf0afa12d1ee549697f1b18629ce0c4d3d43738a3cf49781e0050af012ab111d8c141c19da5db646003645fe7e7b57ecb660ad38d81a1100bfc09c0c09b40221c2e32b6ca8c7432f6e09a17aab0349b97ff64b3a13aaa48af34877a5d7956baa7132d5ce201e635695b7dad9cb56a7a42cb510eba4bfe60e8fb6116b3402b7bcf4d65afaae9bc605587f19865b9d567c331533ddaa97ac9a0ced0d8aa07d850b2b1abd1328ad871ca0b3e66c088a3b9889bf72daf84f06d9a1a99c49b03f371d0ccc4a6f6570c7a5b40c85b6b63452171b0febe55be6b256aaebc8a9476a705013accce50e08e117177bbbb480ca6388912d05d3a9f43f45db3018a3de9c2181d0fdc6d6cf11c050a194a091b0905198a440d89051985448d89820c8584a684c20c4582a6c44286029998c66692196e033d7ca4fb5eed594d76a7b4c5538bed7fd0a6a65c743138edc3a2b04fc1f69ac2cd1084c92c06076c709cad6acaf5e3668616ba6393004b056427a5754737f4561de49b8686218de9621d38de4ee2c3b642c14a2435b63ef1746b9072a11639f5607849b84583f0afff0466ff3e7ccb2fcfd65516a3c0c5390024c2b0ac470560f99aee24df361bce2bc0bf2eef1c50ac91ac27e4eb1c80e963be36c3a74b19564344dfa4773b1c148ef7b4b7e04d1fb1e5f4a27909a37fccd8add1b4aee0d239925fe4333138d3f7f0b16a4a7921e29e131af3b86d235f03f3b5e122aec8983407e59068a0ed2ed98310b595ffcb40c99a9bd72e13d05c30aef074e24c3ffb269000c03c0eeaf17f58c03479a024f0c0f6f56d3db5dda6b3d6096c8cf12fa98b65a9f1aaec130455df908377090bec771a6c66e09bcabe37960e627f9531d57b8d2b05a51712604481913d3852cb5430932a5f978863c25958e13f6d206c722eebe148ac973d721c39ffcbf0ad5e9e9230c1a3a05b10c1c0e1bfe5c3ccee13f3fe4fa9545106bb23d36e3557f8f4f742500416adce2386b1a05dde61d14df9e9cfc7ef1e14fe29839161073cb152bea52b88409cebe7c2ddd27cc771c7ccfbe49ab7897c254f0a8bccd1c8b14a9cd4fd336daf1765d73e7eca7bc737d4fefea406f6c48e4ef3f3980dcea7360a24bfe07931fd7c315b926fb2bfe0be755d03edfab00989acca377596b2119338c0b5cd00de52abb9f51ea8ca5d9d7400cad5a790eb1eb4e51e0296d109b5716e10df02a2cc5185a3460baf44f7863f251c0a7e49e26a7f11613739852a7650bfbcd5cf44b09883137a736030e181511e5ab7d92849237ef9ec32853f97d17cdc3212e92f782c9018b7d7b033cbef6a5a752ed77385da624d1d95719c09f307fd12f2c370ad6a531eb5b12fb18881f329c0d532878a966495c0beb33dd426cd6c706673d9597d66b81e69926fa13e3684a40786fb819993fdfa9511a95b288e406e0ba99ddf75814ff89f25b4bf64a3c10b198e7f91e21449d62c747479260a2355dcf7ad89d800e2ee3ac4d4b54ac3e5c2030382f55b9705aa3c6943b5eef963052a5af766ed6c179abc29416e03212eaa1e0e24853a541619d86c42418dfeded0229b1c3af70fb29043f1df693d557caf8546fefadd93b7b22f345cdc3e7008efc9518686db36ee6c5bda34cd101dd5d0ce1684e2c8e04019cc0cb603473a4c70b33683e74c7e3811bf983c340e1c02b4ab4e431fddffaeeaf333981c7aa48df50ec73bd5487f9222945d1749e24e33fca03c25e69350876f06dfe78295e3dce8d2e24d523de41e82f55060a37140bef6c2dc1225c4fccff95969d7d72459d5fc9908dbd68f2bd0a774e6c6e87881a8c8db930f03912e54fcc93ee45a4cfd85f4df93eb6f6fd99cdf8322cdd0cba4692f1485b3fc08f70f1e0e4971fae35bc60e6c76d5d6c5541e038b1748feff354e2d38290ca6b2d3dec8e4b76d2c326c75bb1dad489bd02d5286600e47d08be9a951360b2e776850c79c75e68ec6f3c6aaca696ef67df33b7490d496f983a255756315144f2aff5660741ae7e68ed772a461f6ac6a6f9dae57ad1f05c6d6ccd8594f120ffe31287426340add1fdf31f351496a9551413e034593b8f71e5768a00b494d527c30f734c36243ee03436eeb290e47e0f7584396c9b94abb1e9e8b16045f86017a0e224bdf84e2d1ec80b0e14b27e44ba29199e281d24b5edffd011ecc77be7eb7cabcb2b24ff6ca6a7ecc34ca93b5bb8c13f4e4241f2062afda83eb045bcf59767d1371d6a65ee2dea83d31c0a8adbb1fb9de464adc6bf1706121f139243bff61c0a1e34202a61777f048b734131c880ebac12fbfd06d99c6726305deaf3c30a5501949dc56eaff9a1efdd95d7b8aeca63f04fdab772e80f6e6e72080dc383ae65718b21e299780918e41fe497c31fa8f7b3c52d080cb04a617db6e0b9fa482b3a2f175ee6bce59152baa730be0b6d3066205d14aa4087927cbea80f4bf1fd9f53f0c4b47be469f21b1cf2c60cfff68c794c924bdbd18826f0051c19448b4d3e4678372adb90dfd25295967e419ff89ab18e79c0e56c639f48a4e49e169f3bab387d0e3c083c5e0feaa6dc9d187f3af0f0f1451f2f721d32c1abdd576520a65ec0e6b1d09e048d27d7bec47027a3dc47d761bdc5c67e55d976f661096310a8cbca70e4bd0a60f4e399eee73f1dc929af64d5ee495dd85b057dc42e06ebc409846e15ac56262d7b85d6d6587a2807cdefcd9680c47604bd6091f850987bdb6b00dd8fda31d996e7efd7c700b0901c4557d7eeebc1f3821f9cce264a398e35f5ac211dc62a0f2efa120debe8d2ae604b78705e139e0cc20fefb4b3703ef3750be089ec2fbd585de8f988140ea8dc039742a0be4906454aebbda54f2e4b5a7397ce3d0fd9a7076f50a21fdbc426d60fd064d2faf5fdaeeac72c85c26234e7181299685f1ef65d87b710b4a231d5ba2c778b1948ae9c159441ebdabe6ba63c9e6bd614b335b6d9aa192ccde364d71405b7c8905dd1dae891b2e05a828d3d5146e161f485d23810b8af72e601fcd6aaf4cce9d04185abd04d1f4f1cc7d2fa3dc9ad6789db8b06adc8a6a0b78f66f2d9720659a9c2e65fc05e133057afe4c1732920e3a083ae6b7fc8649f575d15b275503038f542770f3f496a4b1bef55781b4ec4f937a6d242e7f0c23fdc425dbba2bb72dca60311a71629a7a9a7d072b50264588e1c20a517d684ead354421d03b5485d7f75dd36ee7b9cf4aeb3dfc02495cdda71d3ba20f9311c624068003c38fee488bb833575a41fc44abf144793b12f09beb84f0465a38ced1e1ec8fd8c6a61b1f3bea169d67a4386b3ff0a21613f31f8c45cec1a385da41a153ad732a3f6edef4bce747fb5634ea1b0cf4ed2797742b5bd8b303ce95e338c60d1fc879f36ffa89e23748cd9d90a398701300b3c427732f05afb142fb6b7f7b9db80ea239cf5aa48f35fbc460bd9737bd30177d27aff771dc6b7a7cdc31fcb307857f1b42c03cfec849d2994c6aab3a8d65f47ac35776514c07f061fc297ebf5f7affd7d837d93c15fc4e47b151f829f37df9735192d28baea173a80670c394cafa56a6fa474eeb2b989ce885f40d8c9221245abf32ff5eaa4917d8bfaf5bf2932c9ea464001f32da26adfbe14078dea6b5056bfa882da78be6a558fd63c66c8dd3be829bce91c6a2978dc199be861f3f8dafa3129efa3aaf68c71a8fd711af02e0779542dcf0f5443e71ca39c4103d52e42683a7b2108eb98b399e0f3bbb0094ce7a8c41fc9fd034bcb18b70c577e821a29186a4f4b49036a012a774f4a094143013636464888d1a91114364a02a5a16a868a3aea98e8a4e0270f324778cc898311a314446a6c8a8211a31454786c888111b31464686d8a8111931446386e888111933462386c8c81419354423a6e8c81019316223c6c8c8101b3522238668cc101d31221fddf92e6fb764969b20db8c69d336db2dd8185d13d79a31d534a5f6c06675b986aa52834a89464d49a34a49434d6943a54443a5ac41b544a3a2ac5155a2a1a2d4a452aaa152d2a45ad25051d2502bd158516a502bd5a82869509569a82a69a89469ac9434a89434559436a04ea3945c13e90ab9f2d18de36aa34bc6358bae060fe251dc46e09c84f288ec122fc1e7bb7cb491b5886ce70f72d8bead922c324ad14d651447a92db89778140e2f0d513bcfbe8a55d6e172b1b8f9b961f61045ebab554bd637b8cbf7f17bef62d88cf21235936f2a3ed0134eb5cb7057502321cd988f5d148de285cf8d9ee092e13e33e03be026d362a3aefd132b4f9c540c5af3da01376e542a9633635588fbfbf1e0daaef87ccc6fe6c6abe1c49cce7dd9f912d21f081bf9aa7a14a9358d364efa8f76173ef218b21aeead37849768c62fd304c4ce512408171749b07118512ae336c452728787ddc46d1594e511b47b6d41bf181abde65a78b045b40c637cafb07bfadbf5669ad1331a7a77972e571b93104416a682f1238df1730abf98c03c107eab5e392252ad0b94d2d4d5309ff01443ccd5b8219cfc3898c594249c0173a7b2d32dbbcf079ec36f2d4cac210f2a538b0bc0d2bba2ba1831aa24aa01c3c62c138469f682da08abb10d7a18736244943ded0256b080d8e7baca4302380281b687b558efb619368c709f23cc39cd8f82e8503b1f653d202dcd9dbe295a558da37679dd0ab6f3383850e55dc4b33986642324ab31a3a99e193dc83167833e9f16fda0040fd42cdca1cdb4f4b2ddd8835a922a1b4205d0a689f43fca527ee736021865ba87cf8b69e9cbf14b424b1b4dbee25c4d2f70b0f444844c822a4624c87026c7ea7b28167afdd38a411c045ad0f4abe6b5c0fc944c665dd82c78fd201f3d97abc9a9aa99cd5bbec1d7f1d68977ddcf7d157c4901f85b3d26c0c67972aaafb5a0be8610759f64d3d9d30138a64f025ce3ec9ad375eeda65bd2029b40ada5ea9e3947479ab996fd004672bf696c709d913a46f62a79e860c0a7f0ef4d335de7de54739314a558cfab52dc43202e47be598d714bde4107f89f72e90e248e0b296b053253cfce7c330667e606f66b590deecbdd163bca6a7d8fb5e32be996599253167a0dd26ec054c6f6995fea8655992b20db313b53b38b9940ee61ab4e202feb9042a0c3550b4960efcffffffffffffff8fd244686bd9ef2f5392b2a61591126b4929c994528a743820001000000000000000202222c41f0c02090c170c0b0c5becc1643a247921c66c26fe39baa0408efed163d8420fe6b33cd6bea78336bdf2d0bad211812e6c910783ca1d4f3e288df0eb6fc04e8c568105b6c083a3f3a2fbeddc3cb45085e18009e4e0220704acaab0c51d4c9dee3792c8ee748f71095bd8c11c45fe7c10c9a3ad4550d8a20ec6945157977a3cfc29eda8e2872de86090379feeb52eb7c8f4ecb0c51c4ca3c387f233df8f0feea825c416723027e923e455c89f4c7a8caa7c94c1618b3898d4cb4f8aa4f0252f0c07e36d7ef018699fe42819c0001d3a7c6cf106a3ba57049d3836a74e6e3089a9acff13a73618b4b8084922cef8ca890de66032bb635932f517b406939fbcd98c4b72923b6a3047086f274d64f9c86d1a4c5e72757dbd4727ada2c1a0d69f75ea84c8b9e8198c29c24276fe94ce4b3583d9545f9fd2749f13296530c91519c9e693ad9f4706435262ab4f8e1853790c066f0f992357e6455b6230cce9842bffc26056ed6022bff7e3ab028361ee529affd42b6f7ec1a44aa8fb18ca62e44740b085170cb27b274982124bc2d405d39d4e62f76d417cff5c3079dccfd051f62d182cdaddfce9a4823c212d98b458ca956eb162b959305afcc8a7d4f9f2a45830482d1d8b2d75732a573046b44b0d592167e78e150c3f5f61173fc41bbf0aa6ec174c352f8cce592a98ec43f8766d9d54750aa68c8f93bd74be2aafa5601e9d2cc42fcea99da2601a4b193b4925fdf6612818f44ecfe811a523c7fa04c366ce7f89c41e9d2f4e30a5f7795ed3fd4967130ca77c26e999f48a21134cd52ade79bcebae5b82b16fff848a97afd74a2598931c555a240879f161120c3ac1c37eb44a4ce59060ee8fe5e1737f2cad1fc1fcf12323b26d4630859df095345611ccf2f7b91ee5228241ce442a91629cd0f12198226c5ad07fe679ae104c95f54c07d9c92358648b2018ad557362f9bef53f50bbe06f0b2098b2cbac499cf907062176467ddcff4f9dfa1f5d0fd8c2075aa6cac60493ec25615b7b235bf4c014d1b4bbf8967a522169d88207c64bbafb84503276cddf80077a5c40033a708b1d18b584fcee68a6683f3918efc37274f1001d6334043c1046173a749cc08b044618a8942d74609e1d61a7ad7492a8a61c98a3790ede262388f0c1c00b1fdf630716186044c02ab0050ecca73dc48c08b57949f43d90025bdcc05c2905bb53297ce5fee2eb74172d680d94b5a07b1c5b20043b26b0850d4a2d61b19273747e98a38b07e4f0a24717950419b53069be29ed901523da4e0b73e75c91be7448b1f0ccc29cbae5d963254b77280bc39b1a9daaff218eae7868f90ef6818c58987330dd7bae5fb2ed630e64c0c27cff932cdfc2f456765e610e11a3e342f6ae3058eddd8909b9897e91d10a930afa63679f9583387968a5e0dfc70e309ec78f1d160219ac30859988b4bc5b0b1e0cab62ab307b9aaac975478f3076b4a07ba8c294266be50fe1fb61d74f5559a5013252619e207e62c8656c299df7a24717580019a8300425825bd6085fe15d9e818c5318438654f5eed71041640ae38c58c926a4b5677f61fcd8510a938ea4cbd7e3c5e90b18450ac3950c13dd4b93729830172940fd63033d1a5046610a425ba8505de915c51d68a454148614654384f959ec80d15f885195a38b07bc8761011d32426190617a3fc4962de98e307e1806acae24200314e6cee946f9470e717579686d8e2e1ef05ef4e8422d10821d2a90f10983a98c7c590c997561199e30e9a9d4baedb9d89d4e984ee81cf15349f0893127cc1e5288ae3a69e949f681dc8439d6ca3b2975f71e154d98d39d70b32b55b284ac8bf3808c4c98e7c6534a389f15a5420cc840960eaaa23b549bfe12e8021996e0e4e536dd735b39ce436b8c82289051094ee54fc152a88788d80f1f391ca0811c6b1f904189a2e9e753b773c9a72a750619933056fe9a7c3aad5ebc5e126689ee153d89cce89423612e61bda3f387f7ad17122699a9264b4d93f108c367899267f1ad217747184fadfbb9bbd5a7da5246230c263e64dbc6d404198c30858e9e2348ca49e79c8b30292529c7a57f8a30fde8e01d614f2da85322cc363aa493747f7a4a2c031126b12a351d26238730ea594a3db2645dd25f1c6418c29c32747a0b7f0939c70a196414c238a223899276d967db4208b3c58b8ea273490fe19641c81004b636f1476b1e5a2de81eff630c31541006ca2dc80884c9c4e6cdc44a223c90010853cea94787ad10e2fdfc83417b749892b9b6162e6c90e10783960769eaa3ed3a731f4c4aadc95342ef259955a541061f0caaf5463e598ab463d983215df987eca972d6577a30ac964cbe8f97112a98077387dcd7fe99f8f8919664e0c130f641896897be834146465facdfaaa4199340861dccff412d6dd6aedcf475307be82c4aa9ca39f92930c8a083d1737bc8cd074f72467330a88e7ad63f23b276922107f35e884e592f280ec65b137baf24f408b9dac1c1fca7da63e992fb89d21b8c9e3c68f7ce311e44fe61e0850f93e106435ef95037e2c243ab0b0ae4e0c2cc64b4c1a0477a3ef59f6383e1cc247f7213eba2a20c780758ed283ec85883d994c9ff6ea848dfabc1944c049d2dba9d46c564a4c11425e5522288cf1a55cb4083e9ce3d45fc988d561315649cc120e48775ca3e9e935da220c30c8668b9e37e9809292459190ce6397dc6e54206b3c7307d9f5354f590c33b051963306955aaecb19ae35b8bc1a4f32b5d9d8e7c275d45c80883c944cc43c9133d9d476030c5f794204cff05930a7bcd13394a38e1f182296fcc688b746b9f2e18462d5cbea7f7e439174c7ad4c5ddea6c0b26a1ae2b37cb3fc84d1a21430ba6dc5f4224e50a1ed4aa2508195930c976b0962c21a7bb601732b0603c4bf791d7cc52c298011957305c5e24f54f63f254ce18492920c30a86f49f391aaa5288e92932aa60ce0a72ec83fcfc924c55195430fa276dfb930b9ba1a71d0e46ef3803e3021cb8400e2e722c800b4b195330e7aff07229c8b7a519cb18a98231c0b81f2a4832a4609eb82563469d0e72291e5a581bd0a1e3bf18230c1e3e4a46148c22aa948ebc5ca253100fad2ffaea07188f7a3c069454046440c16c35e66b1d6f2ea4d2130c2e974e6ac8fea03ae40483fa129654a99b60501ff25509152c4ccc4ca8ae547abf1751ba0a838c25987765bb834a752b9f0c2598642f7704a9ff016424c1d47775e91dde420e1b09e608218af2cf1d65613a82aa1f4bc45a66873820c308e6ac60a5528ba93eb38b60904fcbcdd311a16c2582d9aaa2882482aaed55866010d253f420528460b6f41062e95c42fc9a413057901f7dbce7fc43060453f2349fdcf574e7ab1f18aeece3a4c976a32d3e309db20a69b154357444460f4c95425d479a24db4d3b7efc09b01e208307c64e79547ed83ae51db40383af2757b1f8b468b30e0c1224b8a80bca64f3cd81f952bf62a987ab4999063270609c1417bc7dc526dde906c6fe136b79ab61417c193630e56d4eb2f83f51d76b6150a72b5f6fd2c27c1d45ed47521647df5998fd36547bb794cc8564618e7f22273d6a7eed736261ecb6ce312d419cb40e2ccc75bf9eb5cfc944f015e632151ea9c7930a49ec0af3842442389d52d80f6d2b1c934f9fd4575cad9215e68c34723ce54a3a865661f2ac1a953f4aca3b125518b75754bddcaa481aa5a274100015069d2bf97b9e3193f9398529b7e4c85fedfee1318f1810c014c64f7ee13572ea9c4a5e0aa457d049b7e7fc713b29cc973c9bbfc6a57eedef6314e6a8efdfa2c5aefb1485f973bef1a4d4edc8974261d2493e88c9e5e4f97f5018fff4ea88542ea7843e61bccb6f59f96641c9c713c68b783aaf2b27136d3b6152b9634e54e4701da29c302813b35f56694caeb9098367d56c7ca74779356136e121cffb526cb364c2a4a183049d4ac44b121b430c4c387ea7e2fb7e09e345089b273c5e626e09739d8a31e955af84c133e227b71c4409e36ebec4512158d88a266138b97212f383a6762c097386bef029f7ec5d682361dec8394d547d8f1f5b4898726f47cb50217dbaf411c6f99422c7a9926b5f728449a55f9d2d936b32d1469863559820bc7ad75f46183f26f78bf96f8bb42cc260dea696ce53184b4a453c22bfc42a7122d07e233a840e1b2622ccd9c4248beaac58d23f847dc1a298e5a5d60dd1ac7eecb1be1004af969093162546680961d6339542458aa79db2419833b5624cd7273fcf04618cf8c12f7ad2ee1d2b10cebd7aaa0bb59f0b200c29e699befd3197b880007f209c72895939f5a6290c04f083692f9d105912d4a71cf6a1eedd915ad5f3d00ae7fb94ee1fcf83414727ddab3ba9e55478307dcfaf65adbf305bb983d192dafb77aead17e10e2bd0b083216fae7b1a1547be8666a051077366c8fbd22b67fa4c63a0410793c58f26ab9fb48d4acec178f94125f5fbf1de4459a0210773d2d193645fa42a21e30bd0888339f5c7740fd9534759c51a40030e26d349af7bd63911840e8d0c34de603629e6f55ba64507dfcb0d0675f6e7f3f5222eb13618a47b4774f31bf523d1a1839740830d4611e94423e6285d527c038d3598ccea3ebff64e1ae9abc1a075fe6c24e8ec8db4b181461a0c36334186ee130de61095a56584f4ab129a1c5ce838832988f853928288194ca2436a4921226f86476a4715a55106e3b56675527244830ce61eedfb97e2a564cadf917ca0310683f89bff5127c162e479685989d13d3ca043c7ee8086184c1b629d69aa84cef1941fd00883797d2c7adcd32977950b048106184c6abc76278ed24281c6174c4a3cfa9892f792a1f28221fce86cdee7b4ab9c2e940e24d0e042e948018d2d18f269778972910934b4609668a92b6ab759305e585491b5e6a9fb724fa08105b36fc44a5b1ff28bfa2de0051a57308748b1b81c618296489221d0b08279e2e5096d42984a9b106854c1fc793e63d4e477bd8d0615cc5a91429e143f29ed09a2310573db7bf67435e1fbe2625230b99e4c492bc931f0078d289854a5f0b127c46728090f2db31e34a0602a11969eca5efd12866f8241e309c68cb979b724eba28d5ca08386134c27e2f4464499a4e5eb1f3e920b349a603635fdd0d2aa94c4cb434b8c307ef8c01eb6051a4c30d59bfa16911e2ba8022ffa0b64a6051a4b30e9fd206ded92107a9ec30ad05082390451919024bb88706240035ce4c0c2458e1d2307062890c38a0968248106128c138258380d1d635455681c818611b408067b1b4d1799a03ea778685d40870e30c2200279db5464eac3c78ff7e1a3abb0406308468d5cfa3dc9503a4751173484608895fe6df3771dcf2e3b018d20143d724a96686152a4981c0e6800c1fc225e6f26ee851c45a4d64640e307c66a0b16497e3ba4c555a0e103b3bf675f2eed78feff08038ce2a20b0a1481460fccc1e3db6d97ed9a52a2c103a307115424ef1153a61268ecc02474105a27f9f98a2d78308e1c5b20043b0c0d1d9883299dd9a9f47869e5726034697593ceb332a08103b3e8c82167b4898d0f8d1b188436a5e742083222a86240c306a624e6b293f2ab8a6cb760462d0c3aa6df6fd797b7a96861f0cead6d1621b3307916895bc28434b9932ccc695b945aa9a9cfdf53e0bde8d1c58f19b130dfd8d97d52cadac73166c0c2a442f81c32f711fffb579864ef5dbdb8573221628110ec0063862bcc27cbd6c37d8e3265f24893c08c5698c7839948f62356982ce229190f397fca41ab309fae5afb1a4b156651513752448dc89c53617ae8c8395d825061ee0839e8c8c9729b883cb45e10468f1f3ede9502760a43b2b822b4bd4c8a1e738117894d61d27e2a16ee156f455f8a8256f36f719314e6586f9dae735bf58236c0c3474300edc7fb188591adcf724893ec3d4414067d51df57f663bbf7a130bba54ffa2397f604ed1761ec500ccc0085552ba3b6ce3de9cd9f30e8d415319268452d390fad1e6180e1c5056678e288bd9b9743524b4add892e881f4969b42d3f831306716a6172def85988f098b189f4551023a2dcea46af89f674c8ed91b5d2bd3261f0f2dbea0ec145b28b896b62cbe5fb582f61fc206f6a415a4a114e96305aa8dc31f7c278520b0fad84c172a7f7f653f75333254c27679d54864a934fc27059d9b5fb926b972909a3e7ac96f3cc29494ab3c248983ce89549a99d839b1812e68e574a4848595e4f7f844188b4a5befcf7cc5288596086230c162a05b9b9a712ede4a1958d309b0eb9b27c4c903f59469893564818613d42646711a6760bf2b47f99297915617cb7ece53a39276d61224cf27a743c214c4418f746c74fa974f2adcb671cc210fb92fe1a8d1375bb47183b1e0c2fbe00c30b84c00c4318e4edbb87e4bad5396046218c9e2ec7c90b12b7e56206210c2fe1fc6737f27778076138657943b28bebc4490a660802d57fe766999577185f94198130c8c58ff9091f2fe469158119803075a568a1e3681f25fe3f9877c4ab625c2cdf8ab2482d60861f4ce9fbe7fa374a379215c8c1458e2e6030a30fe6efb8ca081e29cbad1000c50c3e987445bcdfdaa5bc153ac18c3d18c5940ae55d421e5a5f7cd1cae38718ef7a305c888fbc48931979309aa90ff9435ed05f8cc1750230bc48011b60061e4c2a2307f57b97f52346abe0b2ca1d4cfa2b7d9712d926b3d9c114ac735410fbd3495533ea60ea7f4b5751e7629bc6436b7bfc0f2f5067d02167cc61861ccc5a396b8997944672639811076377d0cb155256795167c0c1ec92f38752ca6f25a56e0b33de60fcd71b51325d726c871c61861b8ced975208422e7495dec3b0c08c3698554689b8a69692aa6583d143a5ce77b9b5c32a6b306aea08ffdb486663a9c134124345cfef21240fd3609226bdfe6e25968aea0e31c6d8918230c2f8422d0339b8e0c2821968304baa7dedd2f39cdb9dc1204fc7529683853495c4436b86198ce1c1a25b4bea524ac943df8b1e3f4a1a04700d33ca60502e622968e54942725a10c68e307e5cbd8761017c0f030cdc1964305dd5d57f05bbf41909c18c313c9773c510ebbd62a118103977234208dfdabf0c6684218f936c5e2154a947c180bc2c324fa8b1f81e643998f185d37cdaabd4d11e39c5c73e0fd488d4d68f3154c033bc60942d934942757e112a35a30b66ff90ef2a82cb05c3683b3d4f49a58821ced882e1ecd63cec5f08c9453b5c8bcd0c2d18d343b25ec9bfda129330230b86116d975f647cd384b06088974b37e2e5b4a0b43e7cf01961c615cc292b2fa9d74fbf6d6240a9da516733ac602c79212fda45b7b95530fc8539935dfaf99ecfa082514db2e412c2623d4c664cc1942d64cb5e6af647da3cb428c087195230f7e4784a29f9f2d0225681195130c86abb30115bc3f4050ac63319212dc79f139223b787194f3048aab3902bf68356cbfb77d4cd70827172a4d42251d4ac844d305af6ae27ed9644268909e62b5f8dd75695f87dce58c2936cde2ff22535f3f0d11058eb194a30e648ca4155ccd57d20c04930e9882d774f194ad96501370309866439e7e09d9d83f4a42398edd22cc9c75fad3b19c1f0be9ea6efa7e39fa908a66c89a7ba5ad192e88960fe0ff1c2c4c5d0b1fb2198f65ffc2f485e0826b1b35f7b661ed22908060f739f74ee87650f0473c82d7651ff4377e8072639213f7d5a1493a90f0c923ea759e4f2adb007868fe149c64ede08f1c01c9e7368115e6196f31d982adf5e9b3cb5934f3a3047d07341acfff3b51c1844efabbf4ac8f3e2c0ac6e71cf2c04cdb881f184a4b90a6ded16cfb08179d443103a478514dd530bf35b4e6a42829f577968618a71ba15cbc74d8a6716c689dd72e9e15c5c3bb23089603e4a9dd0896f762ccc25b224b5f3d3f21d16a6ca9d64e991941fe4af30f78afe882234cf3cae30486a17253f4d7908732b0c566917fe4a2d3dcbac30e7fc6d1131cbb398bc0a738fd23631f4b643c2aa30c75516a13454bce5371526931472a689cffadea2c21823a382b29238c9db5318ad3a462a13f9d34ea630e9a06d94865b0a5345acf83029e49e28294cc2af634bc6e910c15118b2670d993f15563c1585a94549ac4f217e7969284c3df9949a0b6a494da030643d0f2a8e48aba5435ed03d7a74aa608b4f186342cc9724d573fac513464b2de6b9d5b4b22f9d30e9787dd6a75b4ae40b27cca3da72c871f73dd86513a60bb1f5a62dae989e9a3079ef5e76d0127cb49909537acb73212795d4041313e65035fa921ad1f9585ec22043435fcaa152ef474b988212a3c3695de97fb112268bd19d827213d94e4709732e91954724ad68f74dc27442a86a8705a1937c9230a79b4ef210dd9fe38b84f1d2847ac92374763d48982babbf87ee24fbeb1eb18523cc964ec77d92f47256234ca795f95d935b2fe41861485aa9ace65d7bd54598a4e99db419426a8798228c621756a7c487514f220c5924fb44ef9cd3418439a71cb592b4ea10a6b83db6fdbf2130b768d1cab4652ead4dfc628b4298c462686df7e4217f4298b2f36ba6a7ec41e88330d9554ace88be118404610c11eb1eefa9f306c260ed9f5288cb90e30184e1c64daa8f4af1a0fe832929b77822abe513d90f86393b216ce343f0601f4c25d977d24e080fadad2ffa2a035bf0c19cd7626fc9c512b73a021b10630cff06f88b3186bf7b21c96e11aabeea409892a4cf95c27d99970c08a387fdfacfa9ec808d3f18e7e3db999e117152d80fc6512b41d59fd28e90b5d1075398de53e236964a5e1b7c30885cf75c2a555ea98f8d3da80d3d707da2e9357b5b29f721860fcc43edb08107a39c8af569215a529f7407f36b956c086d32b2e67630dbe7957cb724d359540793ae11a363a6c7310bd1c170d967d474ce44987a0ec61017d6f4cfe4dc2339985c453da6a9e4af4fc6c12cda3b3fd22d870a271c4c6ea2d3be43aa4f267c83d1724b529c921b8c274cfd2aa90efae2d706b3be658a8696ef7431361844947a902a59e6ada4031b6b30a74bd515b265ddb4bc810d3598478d7a88182e3968cf38b09106f3564bca3fd34b7a2a34983de49b901e623b8c08d7c0c619ccbfb9f771eece54fccd600ad94dbb29191dd6eadac04619cc95fa5d5216b38e9892c118a632b2bb9ece578c0836c6600ecb96a6c23d6c5b2e068375ce4f579ff27ff684c16c27534de4b7486c0b180c29d94ad00a53fb4bd9f88269440a4a9bae3c29d2c80b0693d9cb49cf249124dbe88221e8d3b92a42481b03c0680ae05920043b04c0031b5c30965f690d35ef0b36b660b6f84bd331a284d88ebb2ed8d08239a88e3bf92b5c5229f3d0ba828d2c184bcb46dcc7a460030b86b73d1521238e7e4f47c1c6158c35ff93ecb5c27bb06105c35b5aadf5effa3979158c112e5f571cd111ff52c1746194704bb2d61d6c4cc154ef9244a52c6a44dca460ca9d63c288643a5de48e8d2898927da55f4890cbbe8b1b6c40c16ca962a9758ffb2109b5c1c613cca2a39998e71017d725c286130cafa124774932f1b77274f1801a848d2618f47a7256e4b4f37c79c106130c2aebf396241f021b4b3067b5701be25ffa4d65430926ab2fb125f2f9ec5406071b4930874bb4f291905227892a6c20c16cc983becaa72adcb54730ca0819da1f4f6eb6564e61c30866cd097a2c28d3e16ec74ed82882e932237da64f4eca430483d0718b223957c94a1a82314fc623850be2eb7e21187d7de4e71fa525ea41308fe7ff5c8b1772e10682f1f497f7f787e07dffc0944e9947b20c6556a90f0cf7ffa5f4b989aad90393940ffb51279ef55578600e3ae8abc85de7ced60e4c6a29b21ba29eb6441260430786fc25324d2d89f853ca812189c5f270e791431c074613263c999459b7cf0dcc95533f48089e8288bd0d1b989388e9d74125131ded5a98821c1711b2292dcc173e3e632605152a9d85b9d3e64908cafc52d80e0d356461cac92d59529d42895cc6c238f23f4b4c5de888f70c356061128daff18c0d91ecbdc2747f21f8e5b8bb5d21355c617ebf14b176737bd96e8539da5c9c52e22ee5cbd56005aec22025fd8e92da6fad630d5518fd629e50322b5ce42017c8c1450e62811c5ce42015c8c1450e42811c5ce420355261ec94e63ea79c15cc54cc891aa8309dc4cea7972fcd5d763151e314862d5117d47a3e1d3f4a440d53940e52a31446519e8485cb5022b2355d440d52983e94480c4b4945cee5086a8cc220ff743edd51ac4c0737504314060f2af9fc64a51437b3136a84c2d8ad233c9a8e6fb7c80f35406152a94285e8f727cc3d238450db1242f6747ac2202905c95e4955feba1306533febfad71c696e066a70c21427c95219999750631326375b5f0f8f1644eea0260c417cb0f3a81afa3acd436b47187e2ba89109a307f1a02fa50e8dab7968ed08c3338c1a983076e970952d992e97a44b98822993913d755dcc6f0983851a2f8dd851d4d74a98e3252d1d77534d878d12a69d302a6dc4348dbc9d1a6a4cc238e9e255565216c77c2cd4908429779afc9744867512f9682fbe58c00ef6448d489827e424928ddfa42c491e5ae97becf8d1e359b09030799824ea4da4ec11e6c83dee2647bda5be3bb40b351c61d0ca15614689daf945cb0a351a71253f4f0b2a5c8f8c38c40b35f1c36a267a8be8dd57d5b47fce3f114594725415bb2f11a990aa4a7b88502cd353d8ff1085899ff99fc2a8f01f43e47d25b372514d5d0874c364f6bba82c5912c20fbbbb11e635dc710230bc7881935a409a8f1a83308857909c277a9fe993200c9f71a376b6235523100659b14d29e521b4e41220cc2329d9087d3b0fadabf2c3c79fc00b87408d3f183dc9e7acd7c9a427bf861f0cf2438e5fbe776e8a3c748c1d7f35fa606a893f496da808419438f0450235f8604e7d2912846759630f268db8a5a4a907d3bd5dea1c467f7b97f2605041c542a98e24d9e478307d0e4aa98f91d3bba23b186c6459b724353a2b6c07936d6c7dc841ef8509a983c1d25fba588ce5ad4f07738585dcf13f6ebdef3918c4645d0b2a6639982ec75badd23befd6d5888329cd27a145298b58a10207539c754991b7fe428d371875b647b4fc93a8bde8d0e1e328a1861b4c6adcf256e6a98a9e1ca3461bcca3848b6aa76442ae285550830d869ca5a56657164d6a54a8b10653ba0dedea295ff6fdd460d02952c652a7a4c1204987cfc983d6aacba141df1271ce822829aa710653fa17dbcee5318371eb23c89972cb60b015d5d1a1cffcfa440653e56c5a3a764ce8f0180c2baaa2f5f54e344737a82106d3a5093a554c8eee12660c5020c78f0e83d9c24248ebbd81c19c845ed39e17e3a179841a5f30e41c63464c701731a04387116a7821e5797499d04a6bc1645564a8d105b56387d2bee173c1102496659bf42476f9168c9e62d9996fe4104cad854376a73bfb4bf9beb42c98d305616274f47fb1140b55e30aa5c30a26d5b1eb7c474948f252053e5356b256cacb7c4549b2278960badc22d4a082d92bf2fce4b97851b329983fa5f797381a5f9efa1380d11a10420d29983abaffd789b41e4e1205539c9febbf514a95447dbce027b041a80105e347f30f2afc4fdacd0ed0a1438c56810e1d359e900214c617e7460d2718429edcfaa726c9a90c6b34c1543a4e99fd67cb113213cc299cfe976012215a5ff658564309e648e24ee229ab245efe512309a6dd52254109a1a3e85e3590604e3f9652c3741aeba41a47308cf0fce822549660790d2398c3943cbd78b18b2554a308261549a9ce69259e85d42082c9c5c35625e9fd4ec21a4330a7fa5fb7e8f1bed52404c3c66a4c90e272322c542308a5e3d40082d1ce7b4cc5532a7f8a357e60aac959412ca74f9e92357c70ecee94535918199f7bfc10430a357a605017df279f9ef9d4411e6a8a821a3cb843e57c715311cbdbc13bd6313ba88935511ad4d04171418d1c88510307a66c8f1c793fb40bf8418d1b18f4798610f149c408196bd8c032959044899f6a61526d219cd887f34e39b4307d8b94749f2f7928ed2ccc5f29a9fb791031f2938539477db43e6eac592a16e693eb109f4c0e0ba397c66caca577af30dd785df0d3ce0fba55dd4580862b921eccf572fe051aad3056b2ccbdf83b2b0c7f15aa553b82aae8e1a145631566bff0b7e6f91e3e3f63c1aac298a1efd2451116fd3ea9307bd5e9ba1211a97562870a9375a8a99e9eec5fb1198d5318eb924ec943b6b7a417331aa630871a954f6eecc24a4a611ecb6184ec6adddae4a4408314e630757fc93d66466314e68ed9d117f7840add9ad1108529e51443e8493e312fc78c46284c99dbe152bc6edbf3663440610e796742dfaad2e5cc687cc2a41dcb72ca17b2165c331a9e309eb7c7f95f4ea576e7a1e56596011a9d20a94a965482a809343861b8966d5b99d196ed4d98fd45ab4f142d9684ac09f36d89139f92b6952866c2f02974592abd38359f0e1d582a402ec0020d4c98e4cb78773e8d147fbf84d1840e8993c2a3e87747f9d0b38461de45e6d4e95c258c992a736b55126f4b37d0a0443257497b27cf6ba0310983eeb4086bb92a95bc2561d0b1f3ed2676eb7838122649eae2fa76c8e1d4dec08e2a489824dabe48fbd3262ff608b3e7e748c8a5bc52cec411a6cf351f7354bc202906038d46988457b23c22e5f8acce0853dab6dc8e307eec782d15305a80c622cc239e72f1d1445ed02ac2f46a3997fc9c589c9817341261bafad04988b06c723444182e49af546d9e7f6ff1d0caea8202398859398d4318d645296dfb383be91bc23c9e6657475dcc948846218ca7e38d105751b4854d8310860b57415b9d059b5cd31884b183c91e51713ae48a82307f325713f39a8796d1088469b44b082b4ac7497e03c29423929022973a4eb834fe404a26c3744e896dfdf811060b780234fc600e793de269554c9b8f1d8d3e18b74e88d46a1f7fdfe5a1d58207a30a8c30bcf0e1e30d41830f9ddf965c0e41630fc64a9782cabda4603aab071a7a309dddce019bc011086375be09c2e2e5f4541c8030a8a94f1ad9911582b0e0f8030e3f18b4e55c7d129f2462185e10cb3e98736c52b2244c9f0e3e0f2d3e1894f47ccff9606501c71e4c4125996bf9afb7a8911760b4189738f460ca214b563b21c3d7f338f2f0fa876fd3bb923bc2432b056184c163c77fe15e98e901071e8cfb5a5ae5fd29089df2d04a1c773004131fcff9f6a2ec45d9020e3b18d7945e7b0af2e27b9c0b1e3fbcf0808f2fbe58400e2e727420071739b8c881a4120b38ea608a1d3b9f78132a8fd938e860ee94d7741c952dd5e870ccc19ce358debc32eb969d87d68502871c0c23cfe47dd0b4d57d75e8f0cfc1450e1ee847e188836962a54913bf4211e080834159c95b093e417b2adf609ec915ab457b4605cf0d06194ad693de65eaa8b80d06d36954591cd3fa79ac020e3698f5ee5324693bd1641fc0b106c37d2eb7eff7c997fa0570a8c1b8ee6f1d3dc56930c44f7fdd71394ea8af1801071aee0cacba8c59f96b031c6630f676a54e2eb2e33dc943abf4f821068e3298e33f47672df19083c943cb8a0ce6f289ee92f2eaeee9c760baec6841f69e5998470c260f9f45857db174931406f3ed78fbc689892c81c17479b9c3f75daea4e62f983bf642afb576b78d5e308c8e5e9eec635de04526679aa8b985e0f23d29f7eebd09511318cf824f22e0e08231e5b774db27b568150f2d1f5a3b1c2d0066c0b10543dcb0ff8d943c3f88f5f0f1c38b2fe0d082d97f634e46ee20546a5930eea458d9977416cbd233e0c0824168a410af48974cda573004892d419f4d2e11210e2b986452874a37429b028e2a182eeed4f87cf01cdc5275051c5430bcc90b1df9c29d88f0053fbcc0038e2998420aa3bd7b443a79f30e6270c0210573b797b650ea1d4c7ba2606cd912dbf6fafa161d01071412c96582889254c602f3c1f504d3bf5e9e7e13fa20030e2798cfcb249bf768fb7013cc6dc244b057bd70cb61825975e24dcc30fb9415c7128c6db2344ee71a49a2027028c1acaa9fa14b9cb0a0454930bc6aa8acb997bc9308120c49e9b14b713f8271c4e81479e2295cba4630cb9792f536d1502a5804a378d4906e7af31b311c4430afc52a4b5bf3ecb09c00c7104cee979428bda767f4a1031c42307da914fa93427648bebac0e8d001801de0088279532d8a8b100f7000c154fe963db4ee9e109f17e0f881e972ee0b69ebf2b1190e1f18278ea8c41c3da1e41c5d50e0878f3030c0e387171ec0d103538d4afa3016ac1ae0e00141b887705d591203c70ecc7f21e9ec974efae752a1000e1d18dcfd3474597ea7c8171c39e0628498267227f49c827f1f555fe0c0816926b589ad6f15bb51318e1b1827c8d5a95a87ac52e1a15506c06103e38fde7aaff4ff29be3cb4c6b8510ba3a8a9f825935289d87868ed00c38b13fc51417f8f1f1b58c10d5a98b4bba588e59cd5646916e6903fddbe62fa4f88b230ef68675519c98d58982e46a77c57e19d5f040be3c958efa09a57223daf30c7fb1ecf904917ae30ff5a5fb0bb54bb176a40870ed4e3462b4c3a459193aad4b845f0062b0cfa2ce877de5925f1f801c621e0c62accfd27c2786c0bb2df21b50eb8a18af77cdcd253773752b1375071e314374c81be197dd97451252e32cf0a374a611e11bf20ba43a43098cc52ea82a6a4d1d95198d383daf7cbe58628ac043742613825a9c4a2f7e7704982c2a0ce4479c8122e2995a9007d80dc27c80d4f984c6427513d4f3137e4a1652cb00adce88421cdbe28f9d7f710373861d4dcaa34b9384b92db8471e77264a96fd3679b268ca3d5745bab4a9f4a4c0e3732613a1129287f7933a14a4c9837d46289ec2de3a69df5e1c625cc5521858ef99de52b5c9630e68e7a8f5479c4ea540953c9b7dbfb5c5a41d3cbc30d4a18e762b5c40791f393d024cc7d997ad2f38787960a7a9413dc9084313c848e5c2657940445c220728cbf5c32ebb02a0f2d30c6e8210642c29cf2e5cc8a1c7c84b163ef5ac4d80aa9adb207a3bf3874c311e6bcd43941eefc241d6a8459b4edeacab47fcce70e371861083ae4ef590b51bd2a371661d2a1bf1ee5fdc24f754311063b9ba0e9e32a62d1449874e74e9f438520ea53fde206228c225b9f63554d7ecfaaed8d4394d2c68e9df9824fc18e33dc308469bca4694fd2f4c23b7cbce053b0238c1f3bf805370a61aa105dbdd408c9a2478430eea7393913c7362c83307eb8b11326e5c2adb225083b85651b6d3a57e97718146e04c2bca2729e902bfd95e7e306200c93c4a4d17ad192befcc1b093b446bc89a62ecf3d6ef8210737fad0c849c94f6afe85173c10a93c62850fa6d8e9bddd395cd27979282afb1b7b30ffc4ce4f4179486bb78f5223b8a107538ced7753177256a5f3603869123ce8cfa36adec30d3c98f2a5a82c3eb31224ae37ee60b438bd3fc9c24292af1d0c7abb15f4e55cb64e7530c9c4d8f2cff9e154420793e7abba9823962f7d73307b788b3fe24675e30edc9083d9bba429a5c765a4e8821b71308652b1ed52ab3cb48270030e867dd36e413b84879606da7e70e30d66d7731777316bcd0d26715742769df2d1561537da609cbfef77cfcb7f9905000737d8604c59cbd31e4c4810b3d81acc579644540b22e7932d0036b8a10653d851f93de64e32578ba5c194da479965734fde17c70b34867f0fad2ebe385e9c008c31920e1d358b1b6830fbe8d8eb9dac5e46050e37ce60c8a5239ea7860a752f1766308f38e9ed1523bfbe2e8369c6ee4d8f4f0653a820da4f88aca4463e06b324cbed693fe6e56a31984f54323d3971295f895a7c613007efd87e4a7a300d37c060aabfcd76d3cf7dc1e839beef4c8a1fd64230dcf082d19384bc2a32d205839eb0e517be6b74ccb860aade4e4975fecefab50563687a78c96551826df26e68c16e64c1d815d664ef3b744be80616cac78d2b9815ec46150c228b5cd21da2a794743cb448f9e006158cf3d5b3b2150fad2a37a6600cf5a3e48ea74e88190f2d07e37da0140cdeb5aa113ca98e75230c5020c70e1f5c704172004fb81105e3aae7453df370490f85c00d281852aaed98321397d3e3a1c51cb8f10483eca518da1d5fdfdf33860f2718d4e80b3adf4216251f637c21468f317c20066e34c134e2a183a9982d413b5ed0e38731c1f469e184e7f2d991a0173d7e8c15ec08e3c7120c492cb36394cd432bed86120c5fdd6982e90d3ba51e430c2666379260323dc923d77a07ed96c3700309e65a13526cc27878b8be70e308c68e9ddda35a2f5392cc831b4630efaf6fccbbfa995cbbc7186d811b45308ffc9490ddad93badfc1f02255750f30bc70800e1ddd638c2682e9eff358be7ae43cb6176e0cc1a09282d9ee86321381e1058f1d0f032f7c70136e08c114247e07f5c9a3e4f575e840c28d2020227d10c93b8909714725226e00c19c4f5c072b1dc45b528270e307e64e963ec792c70701c20d1f709d346488cd7c490f1695744ae282bef5899c1a6ef0400bf974678b33cd93d7706307a70ae592522e8d5a07565232f2eb599b974a0ebe3c72f1a24725a5180e0829a74a172265b871033cb5522ad78895e55d417dca5b6e687ab8610373a4583b2ea9e47afc550b731ec9c9a398704d1be5e02202b4281db330e8cd7ff4b17c604316a6ce12f9f631e3b3cc21d888854124f1756d6d9e4c070b634c981341a508512222041baf305e945339f121c9cb88d870854185bc973284522b8cf676b9e73de928e6af95f5c0062b4cf127f5fb4af4e52aadc224362de24d50a6ed3e5518f743fced47482a95a7c2906eb46a2bc4ed4841a8308df0604a4ddb4e544a878e1fa8c550010f6c9cc29cf19e233c8e1ad52553986eb55228d3a14bdd5f0ae3e5b22dd3b1233d7d4861d0ee921627a79476e9284c71ede53da8246ae41485496ffefd778d7b743114a6bc91f1b95c038579e62d7b50af1b11ff274c6b61912b76ef0953ca9552af9f8ace8877c2f4134b858b1693556c4e18dde267dda7f0a5abdd8451e2297d2a5a134e4f4d98726765d5259689ed2bbc9710db0eab63e213797625749ecff91dc7362e61d2eebc9ec4c52d61c85d722768f70f298575e898818d4a182de4ec5a22ff54f6a7842966f6f9aec8995e7e12a6a4245c6775bee62f09f3e724928a3f972174381286142b3bc456b37e0943c2103d565ece9c6727f711e64f89591e62e37bb28e30fde6d66f87ca766336c21463dd744af91861f42b19a62cc4fb092dc2203e2bc644cdb128170b6c28c29c53decd5f8aa5bce38930643353f1aa15c29b106150e9fd73def574933d8730dc8a4a5a627886786c0873685fe859901f9ec4b3510863e609d5c86b9f62458430f99b49902d6e1a4acd432b8c1f604420d9188439966a9e9cfbe42f7a4198637e1e8f53a8147126d348240c850281400c0231403cae00131308001834220cc68201896034941f1480034e3832483e36222820160a8622e138180e05428190200808848161188ea15892079a14f501c6a2f519398c78796186fd2b2c3e7f007a467d0b940f2b021ad7558c038d5427f5243e62f8995900774ee2ca7050d677bf8c9c906668c6a1714616f173a00d2b8f40a95785f23bb8941e696a4aba5558ba746aa1d5a979722babdc14861332299aab9a2f7c494beadba88fedabcefc6d8836338b8a8ac668c92cb21648aba8692ccca993d8961b6c8e911f0179e031b390ae523d2e379e87b6119fbf7d561b01fa10ded6f3feb170ab79afd10d9ef11a6ed8ce89e2574b971712efa61c3ab6cff3c52bafe198e349cc1b8918d5dde356dc023a695ba06ad9c1d811bae004b58b39d41cdd585bc8bd086ea0739340c7bf7c9c4175464534134a00f18756293610741a96f0ecbb93f282e294a0153b854e2cd1aa973b470fcf9533dcc432901be6026a556e5b571817cdee946812a1ffe7b19089523a3af971b8e4e9009f8299f6e8592f2fc841a655b3e1dce6f1f75d6712b1270012639129a85867adb5e7dbda1e800a7f70bea32b2afc82f09de67555a47d6bcb09058a63dd245e63a9c483dde4e2a1e7166d4891d183351c3dc6b62dc07a107e67bbe57099571ea32375d818a93fa065aa52011d07f194e0d827ebc100998a706140bbf03a1ff48a8f7a9d1fef5520f4321fdacbf171934d808ad120e023194d94bebdede1b4bbf31ab898d069fd55ed0c2077b6f9ee35c3b029c9618406bacda6e3c94837af848a3faf6434008aeac9b96799e5997a64aebca3c880cb808f4361854c2e2f8efdc1e0f9b649b6c253fd0dc7a4a4e53724eede8067958c9d3e25227248f2b4c9a8fac49bb6ab103c36778d402a2e08205c1d5413b88fdaa2d28ca74ead38b212be1050db2c7fe15c0b684ae142216a05a1a7ae86e657116abd3b4d283acb782a5f279da9bbbcda85877eb057a772e97ed9510e2fd2b93ab74cf4a26072e355f5851417df145c3c2a21075034b7606f3710da2c22a3636aaf8e35091bde86e4a2b1417d8062f22a0dc13f7bbf0cd20079d7e137348cb74e03d9618cee4bb7b8df0923d36dc415d1889380a59bd3009c17d5001aece633dd64ddcbdbce1aa646305b483d9c0d1b77a835e30aa6cb81e50318b3c4d873d031a690f970232c79530b05ddbd521eca2e83866508f2faface52a927892ed8d8095e4c11c98747203dcb19d122981e404ff967f5ff064bf9611c4811690758e9c65862806612d9208248a61e7f875875b41c26213adf60d26c876f4659b381d209640215890e1b0e4c497606167da13e093233d6a7c082f284ff4dcd8bac671465167c1ed2180d1b4372241ec9ec763a901863cb72c6e3c8c0b1df05b6c968b45e8b1ec64208fe4867e09997377c90cfa248f105537183a71060783e2d165a8f2df0a07627deb687a3f8ad7acc3be1a05346e55e842cd32f04ecfe5836cc26a9ecf10c320f77dd2bedb86e80d50c1d1302934064f219f4d363f94d46911c3bba80199b99322e7846808a599d3a232a0347025e6c5220f0445f45d291c9ffa092049e01bb1d82060eb88ec741a57103388679dfe6ded55ea7fab8fe64d2eed799526153fa7423cbb3360bd853c1132f620ce410e36301685cbc6510a5e744c05a5111c990a68f51f8e1c94db7b54998d9b2c56cbd39e3135e87145e3df3f8d312c1d472c07dad9752b94280a785c80a0799b1f314166f54f760e15dd34f49036a04c2fe1371c52ebb294013aaaa033e69686b9f33927d1c3b2fab922236e055d3fff42eb2aa9531ddd2e4ca8fe0e142397195e977593f154b7050d7e3201087e0e85abd68934267ae0888928a1908c8815caa2d2b97610f900a64e4445ef524306dffb982ed475cc9844c8160660ccf114a179feb8e8e151a506a73b706cf7406a53d8030a3f82b0a49c272885091adb1e9e72b8a9dc964093d105d19077c4d6a033f297954408ddb2981862dc409bb0dc80fadbbaeed1ff563620081abccad6ec715efe4eb1dfb64a23b3667859d0b98f90489e84d769d5395d49bcbe4e2f2bbeb12ead259c6d99c69cd4576614ba4e9bfa40118b20c05ede01996f101d8ef3979de0612368521b4bc5e5654c8afd73dcbc09de96573d6272c85a97ca2b2215a1486693579f7e8d3c1c83be880a4bf7008e0ced6bb4aeec2eb0c16ccc0d2b5633ff2b90ee9c59329fbfd7ffa6f5f8e7f52a86c300782f9d9cdaf96a043ae6101cf5c2a15632c54ae923aeb76552ad4ae9e9ce784a85766a799d8b11b443aada2a1da736a3d1c464157f60ba7c8eab410c190a9db7a8ce92b68808217647b9827dc3d6eba98f472eed73307c7d062f649768fcbdb1e0dce21ee2dfa4d5e2066b67d9a6d0fb64261eaf59298a2079c0152dd0088e766cf807550eb286b53c6282a95ea650ab5477ff809d8cb06bdef8080e0074a3c8441bdf74ce87f482100c8b57fe367ceeae49abc1f55a300f65c189e201f75c5fe642102d46fc83d81a82132c532f1d82d8d33635f944906d1f1947e969ca4c4d3721440ae373617c2750376611c1152699c4042c2f46d641d1985c7088c8c69408299d2d2d8b50c498d3edf31c27b3ee66cebfe4330222d9f9f339f8eb243a98bdfb868cd0525e272cb64efa92e4b565d9471db5523e82e4e144a75ecc7cccd9833ea54172c024437f1177dfb0ac20098c4362283155b7faf23c3b2000e7217b2da8078e5bdf311a160b611b94614d652e62ee0c0ed54c7a7eeed36c1bb95e08a59f2f74c449fb26310e3167fa861ff6a14421f87dd6b59f23955f603c4074a9db9cc07220af0cdb56013be214839105b8314040268a4e00ba412af21b23dc8b9b485688486e6885dff4762bbc6a120c04c1e4d8350be5807b420290eda2bd83d19624091676f758109261684e5bb3e5d0039c4cdb2d3bc074b3604983f9b43b8714ec081a80ea54cd1384010044eaa8a233f344b0700025981705cb7763a5584606e146a2fd01eb9a6d1e5d1dc27a02fbfe02f24208bc72ee84d599008bace6e9aa1b95352def3da04981a77c8956aac9939a78282ab601f4b40a42a7db3183440ffd3703001964a33df5b59ff1df0ea30f1b1f03a590557a4691d067d0c7665bec0895511c5fad4194ad5c594688b05129c6be2d872877ff26b1ebc7be0bcd69c9a06c867b2f63dafddf2d6b911b0d32d2d917551873332493351910839f7bcedeb9f63a41e299de4e275801e1a070a1d3613498e34d72312a3612f77233a4a7fb589a5adaee36fdb7be0b80d17b09fa595d564302518b68c6d62f57e7e318de3d5441d12c5c0ea02eb67b0cd09c2d13fd3b81c1ea41121cea34990b227ca065e88c8e12e9f223eaa731235797cce0a882b563d4bd194cd34dd87b5cb5a50c04ab6ebe381bf116180e505f93e31dbcce7d9eef2a7b9dd8dd0255a29c3ea1cad640d1387b8c63a923b2020744a7d00d21c8b0dcc1a1550328f285f81f976a3a35b479ce37087442f438b760d8d52bfb1ba5df3767b7c367ea07076cc81ed9c0ac3f5c708463733c800929be84f0d4194f9776ec27014a560a6907e79089a2a996a8e005d02b54ad34d87587f9dce654a7e744fd39439d526bcabd19a609bade44262e1ec029b56ec39277882bd714565b1ce843869bbc13c1b9332d7632688a5ee085d0a909d96f306ff9349210fb9aa52fa5d3bc7d21e3da23af29b2c34fdbe3b695cf942b763d49527f576af05d81ea2eaa28ae66937c442c62c83d6f6d0476eccdcec164a25e6ccc31b48e006ef92075615d2e5718cdd3df4244533920e3e2122827cb6098771ed8f8b8c7d51c6ec4766b3a964203ef1cc90f0a8e8a57d68e463990acad455221ac690ee8589403ff2c2153868a178c89439857c19b62cc99091f4b9f766759ca4dd9eacf4466bf22a8e8b4340e7d044d54971d1cb0c1db4e00219e83f50ccb63f1171870bbd694c3ca837a0fcf12dc7139597b0b082f94046ce53fe1b7f82a265fb5a43e91f1aa64f4368a17d27c3be27503bcd5cec03a67991367a3458ffc8f71b13fb5e290d85686c001ca3e0fa56a99217ec00858f0c35703657404400db5f3b5206749d4072394c0b884e32828ddd358272b7ea161eae03af361e2bf786cf4a459888d4af907544e046e3aa973c80d9ca5a434fba5035008c4c958f21c825b68cd057954538ba4d20d67f2c17d9bd7cb9efc8e5793942418e45efd937cc7a556119854dac110e2ddecbf3c3d1201ef5e6c8ba4cbc10730fb3521726ac57018301031d58aa82e648766b4049be4f5bab40868e4199b560ceb3340d5b85037f49e07dc939f320052e6d6cd283d2089b0b3d7d66eebb15e3bd50413f302f45ca704359c02ffc92e6d441404d61656e406dce4e0072be89558d38d4dd9455ee9ab5666fc2971ca9a0ee72905ed607cb81a791c263cd9bfa451958e446a9776aa3e42c1b5f2cb15ab44f26e8688069c860591adb8089b01a831451703f37c311c76357e868f0ef9ccaf236004d3f9c43ab6a07b9895e51b9ec2c7248b85a9cc63a80b189d1f2a3807b350846a85c958764aa67541bf24a7a8069cadaee7e571b292dcc80c7b4ec12276c91ea37a635808325821f04a903f50ea2d33cc868e74a0cabb201f5a16503c86a0039619f105dbe608941ec405fc15345881026ed054040d41a9ac97e6f796c0eb990117bb03326f39c5d6f95f85cbab4471b0dfcdeb5ccf5fb8233e4d71a2413c76fca2694063c9629c149690642416a8432729bd6087527bb2824211e40aa40ba422e11485df6505bebf8c41c149ee91b96c6da85186a256e2e50ae906b0ffa21613952a552e294f1b5f076274485f4772e0ebe5b2ca381c69ccdce54d46829b407ab7d3485ac26617a08ad59a28ec44c27c1f214843434b98338761d2073d2b50cc9a0864b2860f49d56a92f1064f9f3bbdc6fe62a3397172899d10a74c2873cde3f259230d57a4be8cada11e48e8ab66cf5f94fa33409c5fc1f8f6a252d4083d0ff66b1c692e9f794e6a617d316586df3ef6863a7d41aa040407dd8ce20a43287af36cf7625ed400b8d1db14f0e7bda32418aa03c8c6b714cb4c2665ea00d3ee17ad218f5a64fc1aebd835b3944f43294c760ec59a971d6b5e9956269d470d73692b137797401f4eda22552bc11d83f5641a2de431637da6b7a09f83332c2cfd55d4f8748bc39931fc6bb7a40b746605bbe7439c2e82589135d54e4f387e212f39432a856f24ee8881200673e124dccb10eccc28f3622f257cb70eb8d6c28e45c1e4425ab09ae90c7c5bdb6b99d2050a8eb16b72fe096f52ba86019e7c0e041fee1738907c0549dab4e352f598d1f747223e45f0e7b133a0ff8d5c33b946e1c3b503a4401cf3e39defc84c02add5f908698d729d173a98854d630480ef451581907eb5833a549ceaf4ab5f662ce4ff6d581cf3bb200651993af6fc125d44b415e4b159c3c24797a13aa8264aa96e7eea360486a8f904503c2b413aafb5051971ceca39bd6a9dc25f00662e7d39520e12e33e029f2b936fcdeb76e79f211be94076c687ea3dcd4a91790bd556d8159117de70ada5f96f54b4b133fad902aa136a8574584b9d6b62346018d85a460e6823850af809adf4a3aece520612c4cf0bff3666bb92aab50515b74e03ead83ea6e1733e5e51365346ca80295886a75bc921cbc901165a2b0bc8581cc02b1c12503d902a698fc37fe3542c6f5db9b052d79b5baaa9910bd0dd3b93045c0ee6634d041be23a2e4ba5e62aaceec833d3b0399afeda153c888ea2404fa1f9f5a08152c1582386b503d87c57560bc5929a424daefd06d3d51f984b9dbb8a3305ef50a890c582e6a013d91e171cf283f0cb7e3c3e4c95ca688a00ce755cb8e15a39a63f8518df1748cfb58329a74b48a9f813441517cddfdd8586113656c94363ae4474b9b36c5af7b77c00df0e1d9a8940df9aa007fa063af51d68a472afd31d6fb15d574fc675bdf2ad4cb34814bbf36ef99070369a8513a470cf507fa88d85c330740bf2868560439aabc8d8b13a4efcc9fd0e1a85e8a064c961c8cf1f5880c1c65fa581f14191d87dc43af7eaf79f3723e6030e6ac9af9b4be0881592c0692c3cb17cabf0eae3e2ad15bef69688f4c717be1d70602857c1639ed4740ac7cfb0febcf90b7989556e93e1778c82a0f189a92237d7f5bb00d7aa803339424740c619d185ba3d2d6cfe72d3fcdbb2473d3214dfc18a32f6237a6172a2ba329030a93e6338d8bcd949933f1ae8a81f340af9b06d5a193a8abd0e2d42d2fc1e12d39b7d4414aa90b6dc467c02835e0320eef37feb9ae39e53003c08445b2295742c64a924042a1ef1f326628e9bd6150b92e96997bfe356495f9931c72edd79df8abf8eb55e91e22862de1058541936a0ad4c4f3365fdae44a9b59ddd9e467ac0c3293858712fbb58013d129230234e1b226afdc3d176fd56039ec97ebbac14a44fae56c90dad8f93b1fe318c10e23f38c2d3dc2badd5e1d8c5be4ae6c3dbd6c3023c7559e4b61b58ea5241bfc96fd111d6ed237b8b4c82d4d662a25707f9bfa1b6050ada6d4f7de28ec53063c520072fb28615ac1d1abb600724213eb012e2a07805922ba6f49c6216b0d507ac230aeab3eea6fad66885edfc15f4ea4a67edd55d3923fcbbd4cb0ad8a3c661b5909d462edb7c827b3db005347987606f6edf86d1cd6f7a0657846cec2cad9ec923c920596fdacb9973909e75c9554084f1666b48f6c70a115a4d8ccf8b1079d51683c890510c349012f0cd04ef7ef3500987bc5d2ea1c23d7dfe6a7f9db770629550f5fc9089064c52cc72e70bbeeec9cda1428b4d5a7ee5afa2d8a28add8e2552269ddfc634ddb685363aaad163d669d69eb0f71c4861c435849c5c899d840c21e3369fba5dadfaf7688678f6c7145124290f436ec8977049213b5d46c7b7fe9b4de7565600b1ffa452b3c581d3f8965f52b6d7a51b39d30fa089ea8c1f442950cb50ded094d059451e21725d1c7a4bf37e5876360780b475768197f0226066ac608e60da49d3018e190d729b12069001bd5cc21b3780bf481bb6b4ab2dc3d461c996d02c942692d9b6dcc4bf707dc55c7967aee310a4248af4904b3922f9c72d6af2c070f2bbc304b81cea7ce26902905285e6b242b92cdae67b2a7de1e856d40363248abcd83f3401e713717c68a7cc59b1e18b6e4e87971b58096fccb2a0cf3be02225633f353f177e339595ac25913ccb8f4d36b0714ab4379273521d00079cdad895ebbc9028e5a0af09bf1270f0b107dc67033db1f383848679e7f909138bcd068dcca8ff1186939817a556144e85f6611325f17152f13ee1602bd530017523227ff21d8cdc1b22f561d79524b1683de58fe7995641b713369c98826fbcbb5dcf926b0ff514378f5bc910d5fe092c708b85813c5b0b580d7a494ab269f8550ed780fd1ff4e2b40051da6020906b1dca60c49aa6dc00584f7e38f600860856173e1e102f76a9dad8f9204bf318ecc8f73324a61864de13e16e1916c91240632fc941cf721bb06a74450f0399d82f036bd6c17a1b4d5b5551d0a306286ad6d08928b7e04d076ecf82bc946c7e4e68447f6d83380d11b1a70c7036e47992aef952411b9d918c46a8f6ea2baec4a7126ddbaede4daa5512fe28c945ff874f96e419dda5ff8cf9121f64034629012a17377f072bc8e6703b6ee4cf7839d77cff5cc1324ad1d51f907388d409d491c7c790874ece362c909cf5a09eb643b047a459bc523d03752edb2ee8b6afc64698e660d7f95d8b667c9e8e8ec90d456c6641eac26f4957fc6f4d08dd0a9f9dcfa25d205b1b62a308b682cfbbbaadc0616a63b380194db67221ffaf194da3d19094971a7d5f7512dfb99c3c169d728cc073f00a5548070f9607d4194c8e9c1e3faa87b710ca97c62906004e9fcf70b668d311d7076e312c89291e49f54320f5c855d6ff88ae78d3a6c3100031817236c3b8fa20fb96ae91bfaa96488e087bdda62b2cdadd4a805c41e77be0b0d0cf4545d0208ba1979f0e3570ccbd8562dcadebfd7848687115613b553a28652d85f5d58974d0d569bff5d3da4608107e15b344abcacfe9308149833a955f4e691fac9225e06271af96af3d0972514a0dc50b4d6ed3ecaf65e9556399b62ef29a3ec459ee686efc4d4ddf82abf38c481b4f40389206c0be26e56a0fb8e9607fa70c39f468919334c465a40af198d1921aff582c3e9066add5fd57f7966bc0ef934c310334589d5b5c0b6161684a21ed08408e80a363d614f89c3eda2dcb7ad95809ca0f3cb085eded98718da407797b9f8bd530f7d680639e1e7d7c8b20319fea5fba1707e3d9d52bff6795104343aceb442c7a3c449ec20127a8ba4ccc1847d36221683bbc5e3e2a5ba6d87c93b506c4f6bd87d048576ad89b1ea8b241f82387ee7bd612f970acc85a1e9e309288c223580306aaf8a886095cf5b014b4bb833ac896acfb55f5962f378786cd2f878f86a1aa761f4e18dce6d7b08d2436107a6228b2d899156669f8526afbb4424275e33dc0e5ab79945cc1ac63595185e6ce0f213a42877d21e77430cc600b99fef2ae1af29ede59350104ca85049763698f9d2caadc5c2d29bc9b4a60ba7455238c1915c89d99b73a7c5514a3b2401d4aa426b603abfc2003db129fb15a7e422f436d16e3698e8b934ff45cff62b38f8244395765811144b9ee45eb878b1d92fa3a3de4249b022bac18bda8e1beca7a35fff2b8ca18985446b70c72784a822b2a28c4446921f259b4b77286419066c01f3c1fc5a820c059c1911855c5188f8610268b644e0e23c508545ccc3f0ec027221d2d157f21109280ebe52fa04f8fb7bd0515cccb03331cbd530f4fe1ab46d4db8be9bff99959b806841e6cce217d3abc6dc509685a025e6596a89a68e61910609120bbb94401f82223ebf72de42dc8264d09f793e7e4a564ea7da27091a83126811195b7991da117ff524e46abbf33811be055af00cc1cc873df9ba7c57a818304c5d624b67fc15d788f26049de2a010084d62a9b1e45b09bf3468c558c74181a55919139b33c73a26d073ec37be0d7893da7285a174340d5188167d5d3ea02d0f86f19664cf4e9f6fdb9acb79672605e597e8bf485d1b8a4bcb1a4012ace813133a4cb8a79f73055e36e623aee1050638489ae582268bddbdb1d09d692fc03c80ba4ebe1d7bbf43a1645708723f875d236833b2bb0091e9ce19c3f5c1ddc284699937a8fc52246081cd3280271796d7b8131b8f22b45e03cb059b26b65ad861ffd5b0e8dd73429edd04a3d7cf900471e44bafe85752717900e6fb21e2aa409d215d0cb9e601f429480708783d08e682d8d6a4472ec9e22eddc55321845c79e5f85b182ac869905ae3612032485c5207222bc410a937e831b9d139b128020ca00ef10dbfaa188fb9b2ba4ca4d29a344cf7f501b8766e0ffd9ea9608faa2014d50b3f90f2679d322d28e4dae827c9038d593a6fb8fdbdadff89625f9b08e6dd83b77e50619255ca22892ee13b30fa452dc36703c0672c0f03b42554a3a4b24cb693872c2b3a191c64eb802c270f39a24e0cbbb52760c34c5f760a25ada119485d05e0143025dbf6ae72cda802d6586965fa14811b1ff9dfe7aa6099dbed3d52e30b4d0a45f89cc85e37e14b29a3451b0d322a255f3ee42e8ab42ae1f0f7e4c4b75ec38073e7e9f40fb45b100e7ec1344c52f4094fd58b934764e7184fd6454d2cf11d2cb427c3097726a9b8ff1534ca5b864e0f1094b9fe3a5a4d474a197807c828571b7efdb2471351b52464828c81259e6441cfe0081868fbd2be03a5b68ee970e0d04c3c066660997cb3dbc5f4d9df1bf9d8f92267c05fbe77e428cb21fd4a496a16f5db1dfd4419f27b16768c5ace075bb1b1ccd0ef3129def9413308a47bffcb9e803a06095f3ec1e8180f3a2205f70633501295ae5edb84835f637ed5e27fb1a409fae135ecf230855e9ebd44d41b2dd1c7d9dcca4041a8ab1debfa670389a68f12042e7bd24b2c987ee51f6542bdc2503865e7862c48520d8422f24790e57432b27a48e801d41c63769967141a164b0f2e14ba6b185f0584a9e9bc35c289443a0019d4a20e543211699cba0809bb2e121d83efad3c75cdc6466f6d7d7df8b58d0e400fb392712cdff2f2345683ab8a196f6ec3c6268c954767857c30a5a022a4add22a3e86deae3a3334b4cfed0203362c16112f26532e656b64a06a054ab5f66ad95291dd992146d2ad17a4d54bd2e7911de2d9d5872c2e1ce51e2d27bd5efbbda96a1f35a06c98d4572ef7e0ee2148ec5d34e9fd30cecbefd8bae04feb20759e8b371b997184d705d16b021a837fef13f886446a77500f43d30499040271006facf55a69807fbcf42f5db934aa8c5cda5e3d9f54e4ba1934f014fdc7b97639f39e69dbaf5eded8c9707755492dad4c7e4fb093d9276bc60d448ae4b496d5ca1fbaecfd9b34d5487400fb19a943653be394d9863a99bd0624938490331cdeedc69d02422f50b78775b12812cfd1e5b0190ce0b05489aa5ed55a24238d5fc195b8ab69988b7665b0561e9ee1e221a428c13e41ae9bd2a2a816b3c48f92f54bce8a696ef0c614d635720e2b04f6e194dc304587d3924de5257b248b276fdc75c94f65fb6cde572537c4cc6ae22dc0935699981c82c0313c9f9b38f7fa1d06aeaca90a35c16501313940b0ec26b086ae78ceaa622da0bbcf2acd53bc47742eec94c127181f7d7c2525681f6003bd17e3d94282a48b03631784cf5e286b46a04d5ddf98b771a36311a809f8e61c634932f543ba94c13351b4c255e31ded150fec8570c03c387fce3aa88af3c6d1614205babfcb3e202d24e7bab24b1c61c142245bd291b3cb91fb265c8db76b02e72410e9918d9552252ae44c1325cfd75af25516d1451a7979a0107877be6d86a4c0cbb477570b385f26a11ad30c0ec25d593075229f71c01eb6b1fb05a9d9fa95abf55bc201050f21505d3373c092c856182b3b2683859637edc2a37612d6802b1dbdbe954a9867f1f7ca853f224ec5271af32641641804879f12969b47ca5018314bacbe5c7c766012d6b381267f8a99e540e1dfae35859b2f929bb57fae2a0b33c8fe559a801bce717163979e6893e119c595b393d9a7af9912c7344c16578013e4153757cc5bd7961ae97c393ed461b87a8df6529624dfa178424341baad881cf3a538c359e092bc3238cf1c09aa1e6b2618b49fe188e45bccad7904d6ac71fcbedbb7b5e30a2831fffe5486a51b14bf234af6853e6fb7676fe28410308687dfd688567b31e8ebaf058b41eb50bbb41ade014336179446b4a64c825e092be164c64bb83192286a0154d7be8b031a994db818a4a2289930a47fc138e603f5c1c3d6ccbaf45a50c67a8927466e12ac9db5aa9070a87bb64764c5da533d44b0485a14a7fb34ada2929ae8b248ab5ca8b3b5f4eabac061b825506375619965a0c6649e5658cc5194101e65cc73a303919f7dc8952ba717479310c2b0223ef2d1ac5b904d81505a85bc43849b0b2749882cf99f1f35472dec36902ce2720d74b1496907486e85c399908b70a1ee2063eeea2efab3cc25033e3c602104b20905af43321e2ad6e0f3988728d1021cee45985da080daf4685280eb64f01938209ba9b0ce41fedb06cc7c38afc1f678b18ab03b40fb1e3f6a0bcc6e9aec80857b9ccb7a0e44cc2eed5687304c9f6701f17690e6799fd3e24ede9368427b35673a4e4f2af439eba8efb17347c7b8890ba80ac1c1f0e75ee6217565490512ecdb46b495168c06a375f53a1c19c3f06969e20abb1d0dd1cb05b2b8cf582b064c779630e7ce86a761d0bd8c5581dc646b3161f3a67ad22134ed3f28a8352a58c1499d58661ae6e437c1db441ee6f2fb47fd1ceb92294ec198dc9cbf1dc39cf27faca9ff02596b610069dd2548d7b5ea26a49c8f06e161c02100cf9771d24aa5c2b9678ca16d93ae9d4b1991ad3b7e51d47c5a6204f7f4ec0f82d1631d770f1fc8eeae7875ffb725fbac188d2594ab98c2e07830d01131d9c30f64de216507cd1c7e9391a9bf05b059728d181cbc081363a573ae9cc039ec3200cf49f42ea739d4138bcba7359f85f234690e2882addcb5f17f0c4c9af923ee792a3e1a6f8b9d63fdf93304664f8de0a8511264590ebe0d6002f21567f9702190195e0b89e5b305d3493e5197223fc008e2bb7e77f1a012b8250e042ce8d6bc84fe12cb8bafdc93ddd11a6287a914a3e0a750cf980c3f2951c7ae9061b0c0ba4704f708938b4aca37591fd67113356e817e5fb01f01cdc7d7994dea21f4dafe834e78d63dc4de1adf234fd89cee5d5d051cd0462361408e3b72b281bdf5be084898b62a6267226fdca86ac44800a48bcef7743b327c06a8e089e644bfca0fb44a3c8c01c033201391a6f10f037885d38aa102e2e5ce86368e3c4620888e9690017fc31de6db63af1ad7f987aa3703ee240ba57fa54ab3cd014c3301b357d8f2f502e87234d7b0e0424b4cbafd5bce856d41ed1b654b115916d826f2f337596d5afd2ac59fe1e1fa2217eaad65e181c3ade97cd99799612dcee7d8406e97f505d7861c468075d4c40ba8cae028a5c80f50fa722e586bfcc7fc369c735a7d128041ef69adf3410c075342848f09a8193176330cf79d0aee0c7f8e82653ee31a7fe159fd566cfd4c31f74c60308b16c378e2877720d8702802dc651a1869f48bafa41f4f00377d91fff74b8be07403b73cda08f788f7579feb2c2b7115a7597853ab2ab6adeb3cea58adea3affbf58619ca8b782411d5d3ea6594bd6a2f027b438d951b69c1609540effc53544f9d0c066f3805ec2b7647f13cd8aa87814b94a7b8de0081aa860c342a547a1c9a1bd3ce29af8fe3ee2303b33477261eb19c7c691f4e875a0b177c4d08992d86762dfcbd007766e970d329c1f7a64033ec6b9b5b9d6bd7add45972fe72e438a5dc391c0d9e9ee1c11c7761214a5e09b6f7232ff80996850655dd2a522c75f9c992138183c84394d15da0dc1c997d2dd3712c716ad9932a6e4c43740a62c152f77ba28e3bd3189d8e58bb54a7263ddb81c44ba6921811384d1404afac4e8b82a986bb8a35c9c0980cfaf6629be05354c3049632240c98a4dea67e6da2ed6d3a3cec35f3e13c2866c429c3438b22e26743019e41c3f9e2bf018c5f657774a38b7e10ad415c71c6b68b00b015dbc23e9079fc396b93363df87f1f21255c518f2de74e46a6cbe506edff1ea4e9bf83adf86ea5f81cd926af73dfefdf926c4b1f4fe93d2e2bdc81840fb4753ceeb2c825da988b208524e805632a4ae09eb91867496a7910a4fc0f3e5ab0a807d137a07d518744d59bd08f9c64e941a8d2741a6b801017c5bf9508aeed2e7f7e74712773c192e71759a5058daf0a20e0f4d5105c615d9345f721fc371885e710fff258859bf3c46e103f0c8a2e9a8ca6c83020747985200b1ce1c9ca9822a18b4122a5d49d1005b4bd9111b66e5231ffe8c055b0d8aeae083d5437d00fe4b6d24d656042d86afb8a29b709a9c39236c532237731ab5b7c6b33f0092c6116d262cfadde1d592ef684527efa3d3c9660559c56f1b08975d9b6ccb3686370d3901b357b9ed50b3fa562602a6415ed91b61c1757b677be2c7ec40c36513218879d100a979be29a535883e93bdfb358f6afe3cd3fa6595afb8b8e865f58028e4cf7761ad289d6a96974e0461099c5265ad8b38cf866068459a9d856b3bdae102a3d200e35e50d09c64a000bf55e39e0d3d873549fd75166fe6d278494bf19070dbc26ce7d04ffc1719a3f000a14a732c33ad5fdb74748bb183b15f43f9b562996eaec0dbda70facef0fe0676274ec9121d913742d47807838bbcafdc981602b7c858970e454fefd33e3f889058a36cf6cd9ac60e91adfdd731e8dec1d1642c49ea28c682ee7b6c5dc26f7bfbce3e53f6d9ef185f97de64ee004c4546140f3955a6710f9dfb2324c09eadae806da625dd3e077e8061bbf1852e434527ecf84d9ed6a91200ed468265448cba43776e39ce05b144bfc8b9aafabaa650157c26b00a8bc52add26e360618072a6ea8d70d4c9a42403b84f4d9c02b48e4d4d75e44f76fb4572b5a8924fcae9e449311c16238133925eb1dc3484057209a606753f5ad0909f963d36712f19b4c2ff2ca74ab425dca4149fee51d52638bd179d594690bec195d248df118bd0e5722808af1eaa82b1eb7dcad864c3affb88d82b876438450286ffc60dae416e1530d4c66755e205cb8018acd40b98d03c28130c904fa7c064585b640133c85d812a185336ad03365aff4dc51d04bca15ff7e5ec6f1e7a3a09e9587bd32da6db5aecfa58c4867f12b679a68c3ee9463a596a2c1a26d00b7d3d42123135389c834fc397748e22eb1cae4d4b967d086a1370121afd121c11947bfd73557a31d9d0b0665be04562b1baff7d84cec6ebc38662915d88a446c8481c5ed6161645977d741dbe6ba2ff5d731b2226e6216ac956e18d8abde4285e35744d3179458a021b9e5d63e3aef0f699fd900034869f83b8f2043e89481dcbf6a06c0363525700494a5ecef1d13103b58401a8c151ede63cf5cba4b1d12a394e1e5eea6cc8570d58ec34ce52aae51406957dcc3442d1e848ed3b4623277569a4a3c425b0c3dacd3c2eacb1b71ea01657c965295ed274d247100a7cef25acf460e35e98074190fad49eda7fd0a66008bb09debf6588f6ce330cff38d6b188087c2bd2021588838cec44756699cc45bbaaf49daf36e0a6144b5e46b75a1e77b4a9fb2afa55b9ef2544c485500b6487b927681f4ab81fb79bd1eaacfee7b017ddc9d8449fe2c4df4e0d6fd74b8253ac216c7d5c98848ad4c6da53b8c1d4914d2a6d287c6b7aaa37871701d592be3ef88a45578f4fa87009054337a5f7aba69a9add5cbd0ca9ebfa9bfd0e54dc199354cd591d3c6cd7528882fa9696ff20cc63f26f13fc3b197f0e7aeea78b64885272f1b402beed550e800418294f71b4779bf5554c874eddc5b86c25f20bd163b97bd983bd7c0fa788b3dfddd02fb60715d1b98d67c46599a934bfbb05482ea93bd31f69fc3884260bd35e89c750f59314ffd687015bba4c8f9be740e0bcbdff595e6d39a088dcb483c7132e3b3a2c41b1060889686afb58174cd00dab557508fbba2fd7326e75b3d7ebf055e56833970fe303988f099508efc28302842a13e052101e0b80a00435f392a7101cc280638029dd0d1111a3e9233041813608942b20b09e3784ff4c0cf087b15abc0e2fbf050b8e141d3664b6aaa975e0ec6616f065a47901684d3cc2163507a7031762ba3eda4e5bb2f911c93102b231981df4f20960e752ea1bb42ca18e5a32950d5d3ac0694b2486b0042aae2a4ea4625d6268ec5e5ebea5da4c0114b59ad46a11e69eda202b1468de880c0339ab0e8ed73148bd3cfc24f01bd09980972fd1780ac86b215d1bf7e139304cd827a9241b2ce9831ba6b8ce730c7d8c6dcc49071e3becfa9767c8e678d410b9af97355c4fcd8f27a5a640318d504f8f85cac08e70ec0d0e19b14341f0642268d0c5dde98541847cec8be859ee8de34ee3959c7f8ff66965d5a045f60303264b86d391d6825d608dee0001bf8c987949876a5cdea68cdfa0948bdb027056bdb54738ac457e68ae9e12d00ae15659a9c5dc3055afebccf6f2042255627583237ce64ae15bb87967a310797a8bd1d8b0ea2af40fe129f3c013c1d031e9045480e3540b0a6d0013c0c3c0c3c0c3c0c5c7536dffa5afbf85afb32c94d060328570e91524a29a5943c5dcfffef2bac5922fdb89d60df01b8d70e220e120eaa7dfa5e075b2efb2063077a400408652df9c3437ba61326ae821e3ffe0565f418418e1c39729061461921bf28a91d599ed6e194b43b437c51ec13df84ff7079a344ef4541c6dfcf744d6249b2f4e351f023080accf8471020c88bc2dff6781c212263a48ce7c1e6a347a216460a38701fb28b92fef6091e5ddb3d85548c105d144b26699b774509a2a71f8f7ef4b80b84e4a29c4cc99f43eb68dc871917650b9927b7c43a25590695f19981905b1447f9a888f53be2b6289f52627a1cf1e4c1c2329ec7c8a906426a51b4fdec31f5d42a95fd303a125a14e77478f5d14107ed263ad8d67e0401328bd266350d6f9fb36a92e8e011203bce5c168531912509c26c7d67d4c1a88c27e375f8303fc6503be39058144f0c5fd3237b93a9820081454147f6dc3cbcfa8a8230c1eac3678792724b57943fb47d8ea7f4181dc4561464d8cff41373e97dfb9415e5ce3fefbe1172a49a405651f2f6befe7c2f41358223236398711eeda9a2a07a714a5232a6830d191f98232415e5249ffb8bf87ac90d1e2aca299ae2b5aa1b3beb3ad8808cf44069fa2308103c4541ac44c6262d1de3634c5152f3eb9d3edfb9c51e528a629dacf0cfee9b2d7421a4286c12cd4fb9ae8c1a8ea25ca7848d90a5cf84d1340819094551da1cfa32b4da63cdfa31c623e3e3ecc7070962c8f8d16303de21a1f864588d887bd867668c042123f50642409129993bcc2cccef1dc935232de4139527fbf168824c075b88278ab3a159372819d484589d28c9e8239424c937e531a4834d5160d641c8484881104e1493e416faf24267f670246d13a54c1bbac9947479d2e9c89420470e203f7e8c104d143eec37779c641966a065a2f499c6946a5cd97e8c1963fcc080eb085e30d282104c9c5fda29337facd2d120e412e524c9a8485d0d27bb4d20c4120671dbf9d6677e1e414825ca194f3b76d0d4655b8e0069a34479cb939275e44994be63fd68d4bc31ffa6834d21102289b27ab869f54e4522512332960d89529eeca4fdf45eed731f64ecc0904714b3e9bebdfcdb112541aa8e96eb2487995448238adea722931c93e825cbc98e324c0972e4e891cad83112c288d29796937e639af025c9224a9fc39d86a83a791f3a78544439dec4cd5d33c4c62582188424a22c4289a33b98b618f172e4e0c1e39311bc60c405218828f5b8565acb9c9cafe732309023c71965989043943ea8d53521aef1b91d3f72a00c0831445648214a56aa9320e5cf444fa703e4811f2184c04b63d6c636d4adeacae498d94e4a9fdaf977f8f81d3e7a241b384118f623880f31840ca2f0aba97dfb3e89568d04420451f03539b67f90a3f2203bd2488f20231e080944395a674e3b52c3067d1eb380104094444d62706dddd3b1d23f943cd7b86e585ee6fc506cb913ccf47dd0a793fa50a93ce93bfa766d0721d4c34d10c2873c3b45d326c1e4a01e7b40f95b491f7ed53e08f5d0c335fab47a34eb4e10928782ea38aa041d7d90b16304fd0fa3034873081efe127feaec4fb44a772908b943c174897651c2e73023cd410f1e3c8c0e206c1b6207e6c4b05755b6996e0f327afcf0618782903a1446632cef9c33c99d355621742896e7581946891e7df23818e911046d0e25cf7e9a27747808598f243b82982040ce2c440e85d71393febdaf83ad3300e4839c7174042f18b99038b49539b9f989591bd7c1962347081cd8d6b54c9d7b2f07216f38811b4ed0860a61c30e1f18b09035b0400d277040481a121082869104849ca1472ac3033c78f410438c078498a101216518d9e10303090821c32765f4286364870f0c7c52468f03848c010121620072c6db40012161d8e103033fde063f7ab080040a0801c3c89bf16698a08c1e3bd08f318649c10342be00c40b0908e9420142b8306280902d848468c106490f1e3742b22012828500845ca14688154676f8c080f96106066e8454a160ff3934489bf524b33ad87a943172e7215428a8bfff2033f6ee29ad0e2ef311bc60240221532846b7fb12fcdd7c452152287f89d29d674fde54734814ca25c9aeee5df7ffa5040ac564428fc649f3d965f384c2e6b251ab394e288870f3b10dcae3c5d88462ad8c8fb9ca8472978f88273f3107cf9650cc647667adbf0e1ef911c447f5f8b1214ae0a412f49d1a5faf2c0c31c2383463032708e3f0109284c2c6a4c2834c9adb84db4b084142b9d49ea9d2a349a89584902394d4a6ca5232fe7d5ec8084513e799d9731b5284f248bb93e4d493f3ae2342c96a9318d38cb4d7d72e6408e570b28cc79c124e65fca2102284e2b907f5eb99369a3b288404a1b0664aaa0cb109014239e5a5de096fda2d55441abf28ea8f3049aa9a81f0a0e18b92506277b2eaa917e5984faa8c3a5a2d76bd3bd0e04561b3c77df17eed40631745318f1557a5a7dd73ba28967abafccad1bff97a1a68e4a21c231aea63f43393a33470512e133e64dea8656738d20884c62d0a52739bcef15593c9a911207e4174502c880e7e0488d9a2f4354a9fb2d1754a5f8b72ba170df7cff1b9a145f1e48534bd1326a6a75994459ae99fd3d8eb709245318b66508d2757dde76351cc66235fe53decc48745c1e784d9bed20df9a15794bb7f477d6c13579494fc9fd8a9ac3d666f4549f2f38b4d67b3a298e55d4eab7e8ad9781505dd3b193d72d3ed5d55143ef73f5e73fc9837a9287aa5989cc9dab9754445b104fb34266c788a72589aace979c2b97ea628beea499fc4fa33d973a3518a729e3a41c896983c428806290a3a8490494ebda97a0d8d519474d04992a284f909cd588086284aa579e4899fd7953385a22449d273ab869fce290245713c773825d3bac712fd4449129f3c9d09f1b77ba2a0434dc9661ed74f4350d0e844f947abe95327d6bf361d6c3b6870a2984dc9d5249320e222db44f1d53c77d6a07a9d952a6868a224ea097b1fda34058d4c94b4fb79d0d4d472f3395ea581896290a3aa63a8d635d5250aa3fde336ea8312ef6149d0b04425cad7aa495c9fd2a995fd4801236850a26025893249e718861861fc6fa0470a120e34265116fd246e08a1497a3691444166936f3f3d8d3e31fb3064208d48946a738ee341d490286e9e5ece8ce611a5b036396d7a53b2d9ec88921276d6c24abcd0db1a519a3bdbdd64a145c830a260ba43986cf7ffa44c8bc853c9922335bfc9194514e7e3fa6adba6e69c24a29c314d6b493f393e5f4454e268c9703a8ceab90ef1e892fa3fe97adc1f0d51eada20afe5468b30994294248f25e4974c72de3809515211e557e2e98f3af9411493cc993ff66a2a258782e8a4118892786ab6cf7793ff0b8882f85ccd74d61f8c9b9478dac3d74b26f143fa33b7c91334bd8c923e6c82109eb4492735a4880ff99a6ef8de18d53de41d547dd4207fd703324ff6f02c4f1ecec3a984f3132c9326133c54727286fa77d82ded644e5ae2d88cec7075f579272ba5c23a1c575a6ed69a63b3478792f6cfd983e59c92433a87c7e4a0be04d34caf961c52925a569b85b5e75c1c92e4bee764577127dd703079c9fae1bdf469d3bfc19149892f7292ce70a51bca6ea63a32747c028d361475e3f7743e17b91d67366cf206cf915ab206affd8365c6d5500e6f93ae269f864cbd9594af36254e101a8aa272f4276d910eb63ae34110861861fcb0f661c8403d434989571d773766889598a16c93b379b669cf67cb50d0e973bd234c12255528a04186720abd6d629f5607aba00c129c21193bbc0634c6503ad14eaad03822f23a0ad0104331c8ddf598af3386ea0a136884a19c4f895d52cc35ed361d3cda020d3014ec2441c67fd198748849e30b05d3b9af33951423dde485c2270df723af6e626acbb0a3d18584dd6899f111b53dd21fd0e042a9e4a04f6cfadb123637d20399a5e0d0d842d974af75de2eb19a4b8c2fd0d04279354c909a6fb7dadd2c6832886ff64a071b1b0bc2484108c240dc010d2c9447bdbbaface84f2756051a5728f58ce9d64d9d95020d2b946468ff8df953b3499f2a147feea3c6e6e4a184860a2531a5e47b7e279d4a3685626fb0d3257c091a64ea051a5228ff28d750d1dd156844a19c35c99fa36cccb9dda20185f2dec9f29b5f76477c46683c01bbd2d8ce8c99f3b0de99c49ee7ce55d17042d935cf84f97c55ddd1193f82f80c6834a124eeb6fed4989e0906349850d2e92b367fc814ab5b1829e08006dc0c349650de9ca1f3263d269c749786122ae962ef36d826061a4928a767d2f8ab3dc9f33c62677da0818492d0ec229b4b64b24f9d25681ca11844f796101d0e842146181b08430c0d88d1041a46286afb7712eff7cabb9350a05184c28b0e9df33cd98ecf3ffe0cc3c36910a124e9e997ac26dc7e85348650d03bed31b27f6ed38910caa9fc3f9996ab6c111f176804a12073f0123e8f9538a6a6018462583b39ece90bf3c7f330c374e00d60fca29cfdd2539f6de94f932f4ac2bb4ce79cab4e8d75b0a5200c318e191d684080d18b72525a54c9871b13d3880960f0a220838f9293593bbcd43ad83a0818bb28af96ced334d1a4d2c9d34541a9ca49933ccd9acc80918b52069904f5a03d682a1d1e407e60e02c29e30c2029013070514cca4d5d346eb078138c5b3818b638306a513cd9d489f7142aee01e95182325ad82c0aba57832675a19545316d89acd2eefeeea95814949fbe10ea049d1f755894ac4b694cb9b19bca1cb90318af28e7572bdd6d25d33cab030c571443d34b79cc79a21db7a254828b792ce1d921305851dc68eb1adb2a3c3d6715a53e49f7efe44927ea070c55946284d23741f5a928493a0751ab38796ca42360a0a29459e4cec987093da55394529334355a3be76d638af2e5a893bd93982f6f2b45f184e6b8499c4f2ea35e1bc0204541cc56bb283d3289761a45b9dadaee9360cad333828c9cf123c89a000c512c4aa3499ab1444351ec93e7c49a0f3f9a732000031465f7caae8c615927e7e800c6270a27a63aef7cf67f4aec89b29d0e2274909b533ed589f22849989bc991effb79303851cabcda326b93c963b4897297ec9b2e596487de780cc0d044b15a3608f3f0373a9899287fe60db2b3c7ed9c8e063030517e13e68395ec5ea2583d2e1a931271994b33806189827c9f11677a9d94772a515aadb52e71d5372ad7c1c61d008312e5ed93b4fd6c5a0d623289e2be8d36934f575989e960d40018922869ca8bd36aa34894ac3b577c29d122e5a3830d12a51026b98d90a9a233bf07301e511c351142737c3ea78707301c51f2dfdc18ce7b3580d1886296fca074775f3d857ab829c0604469acda44bbcd227b4d74b0f507ba9ccb67adab5d77d0c618caf2fa2786ccb93bdc440ca5396df2e77d71fd20b51186d229d98498cf26c55564030c655fffae38a165fa5a1b5f28962969eafe3a86d7a00d2f144f676c4631a99d7b75a1eaf6ea5a3bd3d777f36c3519a427cdbd1a1b5c2896b4cd541d5457e36d630be56b5bf5d9eb7427c936b450923cfd8e1e1d93b0496c64a15c57922821e63a84d06d60a1d42708bddefc31a7aaae5034d5abf137a73655cd3052c001337c94e18122d8b04229c694da69f3fcd3b58d2adcb97a57e9dd5a726f993d87930d2af069eeb1f76e9e3b1ab69d8d2914ec6c839254e6932429fac086140afe717233224dd235f7c046140a9e84926b44941ceb046d40a118546bfcdd68eaf3af53b0f18462f013cf3ba6be1d061b4e28092dd2aab38d8c60a309c5cf9c93248e14a13bfe4db0c184d2273313757479106ab3847266f393e35ac8041b4a28a589f641693bb143466d24a118ad3549a735886803092559b2e9ce66e308c5adcea2f44b6484526d5475df9c63a308c50fa7466af97c8dfa2c608308e59e133ee893261a4a6d6308e593523ef58369923f2b84622731998950796b82ca4610f8ced6f39ccfd6add7b0d4517b5f61bab5b70184a2e99bf693f1464cfe77357e512a69841ef3bdd0bb9f20403e8809410d5f143d89ba116969f1e1ce408d5e14d4c92a6d552ee273c90bde72ed3bc6526b57d53c73f896899fa7214fbb28993e49899327ba286719ef20ecae5c145f6d838d4cea2f931617a65d59acdabdd6ff7f0e4b137b84b8b728c99c34a9f7d059735f58a8618b92f8566fe71ca73afe558b725a4ddd7a52498b7267979d24e3cae745b328e7b0784d82f664510eb552923a194394d8c4a264e23cef53932c9f5a58f0a1966e7a3a73258ddc479b5f517ef3bf8daf9de4f48f183c0c116ab8a21893ce9e744b3f58a7ad2846bb784d9e54cd9a9415a5cd51337afed64966b20abcd5533cdc524dd47d8fabfa66d9aa28be271bcf9c4cd852515453a22b4d7083500315c5921337c9dcb6b1733db2c38719791f23232378c1488e1aa72898acbc933d49acd2243c927ea086290a23f33b425c4fbc453ad8c6e8c1033b50a31465cd8e254d3c792ae63052c08130c6e811025270e762b6a66de59ae26dfde9e4add2129b34c6183d42e0a3c70872e418a3070ffc11c4478d519476d3e8f7b9e98ba8841aa2289776cf41e7dc77259f80181d25c8918347ec8c1aa1289d55674fb939a5f230830ce461860872e4d01164c70f9323478e1c3f6a80a29c4ace41b7484f4ddaeb60db1a9f285b9ee0267912ae1a4b9e286f14552354f6e3675ea313c5134a0e9d4f9e70a2984bc97662f2cad31ab38982c9f93953be9588ff3551d0399f204fc59ca4eba4a5502313853b414f5be68eac7c5a0313e5986433e1e33a895fe21a9728a60dafef195ff5edd4e167895267e7c692e3677b95b85e332f6f533ce698667abda9e490b183478e1c35285192c3e836e9f762afd21a932827496e27a983be8ff149a2a4c43331e8e4d176bd8f44594698d6300fb2447f1aa80189e2c6d028eabf47947396fdb928fd95dd1c5154fbdd5a3bd1333c6c444179384f4ad4a4bba3c508e5e53d6bce3af34b4c74eacd9e1c4335165152615d2b1af4d566aaab504311854f5b6de7f107627888e156a8918892789327ef7d7ebb3f39c39420478e26d44044593d86aa3d0fe27d926a1c8213d9acd5135bd37dcdac33393658be27916b18a23c622ce3b7c504c80e32b210651531a1a3efb1f24e6e508310a54ddf1bdc3db4566310e53969fd41c482484b12c46f499e06a2246892348f58b1104dd5004439e99b4e72d021c4270935fe5052d95c62b0d9d4af6e0d3f947489a6f7e54cbe51a73e94d4c9b0cf1c7352bb960f25b3da3c4ad548371dda43b97dcc2ead753d949350b5a99468cc1a637928099ff583a9342d4a8cf050d87e3ba533d5eb6f77872e3b6e67eef42cab4b9f888f27c345678792c8f1b0dfafa635b735ea507011715ad776db734c87d27b7e4a3bc9f7eb338782edbefba5a818519a1c4ae9eab13f68a546ab8a50230ee59834cb427bb693dffe8f123ca1061c4af2ee7af814ed7d9c918228d47843e19458769bba947423fb3823056704393794f33b08fd0aef31afdb505a1b69926f920e430d3614641833759ad3668d0ffcd1a30442a8b186626b07bd1a3da9114295b602316aa8a19c395b54fed788c8a9461a8a71cb24e16e234e526eb2a1061aca4947467591ee19ab8a7186921cd38b6caad2709023471a0e30cd508cf3ebac79728e49496b94a1243d7a124b289dd7f417d72043d14e95e7969d98535e8d31944f507be7e124d94eb4108fdfb1811c3980ec19414e0c35c450da2fbbf494e272529d23478e1c67043923c899a146180a329759a7114a867e150c652b0bf9ec536b7ca1a05378bcff36d5f042a636470fd1cad4e842e1654b0921636f871a5c28072da332aea9eef8921c6a6ca170f399e4244c1c93458c871a5a2849b132134266dae7910f35b2508c322fa784d2e7c14e5828fa8693ab1bd1536258e30ac52023b654c9e24918d90d35ac50def0d4a132a8f6b5be46154af96579a1f1438572ea2c1ae4dc48fd0f03a9318562101bab394bd0ca0daa2185929fa7075d4d1285b257c921f4492d75e2040a25258b87be9dd3573d3fa12c9feda4d3ac3aa1a446e7f42841fcafef4d28f6be08f73cd9cdf56742e1f96215eef55d192a5b25bb79f3a4359650d27a57ef5c2efb5f76a186124aeab6a66f99fb246552861a49289d896b7afaca78e23e861a48287fc9b9eafc3b4c3afd166a1ca1a4faf2cf32a9129ea6166a18a17499b3b64975ea72826a14a124cba9c91b4463c5794428e58c1425f3c86a0ca11cb4c9204bd23d6a6d53430825b1171b9d63be8d3d0a42d9d3a62769aeac9390d70042d93edd4ab7067f51cc9ef2a21e76733fe68b5b4fc4e3eccb73357436bbc4124f92ede3ec45c13a3e7abfd7c9f8c88600031920410838b02b10e145f1459e072dd9a533b722bb286f8a358d266af64516d145c9bb4bdce041915c9454643a41346a27a51a115c143b28712f9a31b710b1454988ca3ef7d3afaab1482d0a278618d5e826c8911f115a94d744d3ffa0bcd6244f641605df1c6fef9a41dcc322b22849e599a4cde56910b6482c4a92b0994c899b4404169beca7aed8655896b0179bebb49a38b1c82b4a3a8a6e3465a2f8cfc90722ae487baebc1c3946ac1322ad288956680751e21a1aff6158041156943a06b5394e9f876d9019e931460b4164158517fdd22ee6e1212023237eaa289edcb79d5aa743241525c965cd74fe112a0a42959df4a6e323a8ee14652be13597f86eea93ea603345a9d466379363b092252e4579e36374cf27b36f57f923880f142145399ef2f313fd348a527ebd27934c1a0d22248a92c996d93e4567b68642518ec1f7b37dfe8f513fa028bc0927e8e85ca638cd278ab29a7da45ba9c7917ba2941a6c94951ee5bde14e14f64f7edb0a9313a55e5355f725353dba9b28868e874d5a35dcc3d5443928a536eaf5a69b8966a298eddc94f43998288586be26396c68e67989a27ea6bb93632c511c1de232b331bc6b5889d2cb69dafd690e271f258a9d04313ea7425f956c1225eddc57c2e5e9204e24514e92541e4d9f3ea9eb4814362693d4d656b9a74a0412e5de70f2ee8312b46d26f28892fc983bed696f8f1e114794d7e74e2651cdb74623d28892709b84df4e4222106144d1d4f8bfae2bb288c2ef69acf44f4af073e408f263e4c89123c7f216441451d2f5f50ea594c7946222ca9efac4854e7244105112e466adf36822872887ba073b9d476f3b6c228628659f3a49eebdd8cc26852875fa7832932094ca0d3110440851f0cf582aff64450651f4b7ccfb6492cce132558808a2f0f72a4ae8f196d1ad4820fc90165af3af49f9990820942d7d775517b17dbfd7249edd5d913f94ac9369294964123ec9e620e287c268f850a2271d7742d68754c738dfec2912e143b9d5c74caa07757a9f1144f6503ef9564ae37c7ecaa88772d8d2f8493e8d7703913c9464bf07d3fbe99e8411f640040fc5509f565976927bb57728c5ef95564f236f366e87c2e84d63fb5937a9491d4a4ac9b3a38489ce76ba0e361e3c76d8c03108227428069325f36492bd4de7db81c81c4a726cd11bc79368750910391493b0d97c4e9949f6771c8a9e641a319f4c506d62702857cfacb69c896d7dec0c913794547886ccaf1b7920e28692186972095be9e7412ed481481b4adaf309f7d023eab5eb21c286a2e8f4aee29e25b7d91acaa794b89576fcef4df58088a8a1f41fffd48c06d350d09a3127559f2756d63ad87e98e791341a0adb419618c694d2a7752743e40cc5cc1a4e89d87fc9138f6186f28a6dbdee5599d4b9ad814819ca799e756172d08f11203d74b0a5a5d940840c25dd21c2323c243286828bfda6f8e630cf256228c63a31a677d4ce1e5f2fa38048184ada2553dca4d1cc260806ec562be5cc62b5432cb62cf4846cfb75b00d41e40b85ab6f0fe349690d9317f1424969dfa0f4d68348178a7a92984f3e4f5207112e14c427a5acf6ba34a46b86c8160a32cd97eeec1d996346440bc52d3169d45d08a5633e0bc593d74ca82d9d3dc9712c9464aa321d6ed36ec47d85629049a9de2ae13a976b8552c8776a129d44e89645aa50f8fef64dc2c7dbb050840aca95ebab756e8cd68f9fd3438395660a65cfea7af71ecf4c9459109142699418338dbfc9be795b41240ac5d238f95866fbe5252250286e36d933eba2a39854e409c5f02432070da2d96452c4098bbde75a8ba568767ef024767cae838d05224d28dfc9d0f4983e3eab25c28482eef876a7ad4d2e2528b204d3285d4a9be791881216997bcd2c570dbdbd73cd5d6ef2d9273e20104942b1646e977529133a65224828fb65feb8b979cfaa9581c811dccaf332798d70a792b7d7bdc79334102942a932eb59a92fcd77a1138810a16cb28e105332bfc39a224328dbdec751cfa293b9688788100a73a2744bd21977bad4664482e0be28251e8fde52527b1213058f65e61fbd4431abc6bc47c465969628fa89901e2ad3086d254a629424aba84889b227eda6e1af9d445946569a9cd74aa214a7d47fea18c3c452240acab48508dfcf49fe9028e8a4c6a43449ee3ba14794e3a94bcf49e8fc337244b177add395a0f7d449234a61ea644db731321f4614335b3fa68f413b7716b19d8c8d228a9dab27e9b058cf9a4494f54fd04d62bc28d511510ca64b752961d5fe4394776be4fec6666d3644b1c4d4497b2cbd2772210a2a4b47477487f84a8892923c87d239c6adcb4114b38a9e31f313538c8228ac491936931888629e8e507bb127eb01a2187e629b66ec0f653969edc62df5434ac58850a9497d28df9d59e8aa66c9890fc5bc59b7f5419352fa3d946b43efe4ca2bd15e0fa5fe34da6f72de1244792867265972c7123c943e937492cb9de67eee50f8183de67327c94b76289c7a7528093a6e87bd9bd21c1d4a82ee9998323987e28ab9b7e9590ee50d9f31fb6d4dd9c6a1a04accf6591a4a87150ea51ea55e3a9db849d0e51b4a1d6cc4970e1b4c66e986b2dc6c99a9d998e5b7a158d26a791e9f0dc59426ea8c97d41aa13514939c95902656434992739ef4a374e80fa7a1e0b56a56ea4543d9ffd53fbd9e3cb9e2194a5e16a349bff7cb8a6628ac5bf99d32259e3eb10ca50a1b37318a9c0d1992a1bcb33b26958663280926a2a4289d97114331060fffe8dff7610a4359c3ac08f799bf180c2513cc3e6e8efa4dfe17ca7b15f23f73bc50148f93fbbde942e184a651928ce142e9cb4527311b84d0e92d94d6930ae5776ba1aca726e9f5344a3ee12c94fceacc7c64fa13652c14635de6a9d05ea12cf233697a92df3c5628c8f4aab249aced74150a32c91a4c56a5664fa9503e592f67a69d42b9b74d34496d470ac59ce25b69265128effc89d9e40c2ae7048592ec7f799b24bf13ff13cac9e4a0d2c3774271f5b4e6d8ef617350138a5aef59e999b73188092549c9d9bdc64b891a2da11833a9dcc6cb3d494709850de2da3509a5e43d4928c7fc959a1009052d934e8a1495df1da124d4a4fbe4266b2e638482061119ea2776eb4528ac6b78bd7053de39114a32e769f21f5af4432808fb354929f9df0409a124c3dd7732f14c49128452f95b670679257e2d002094b736fb4539853cf132d442269df9a2d872c2e53de83a99592f0a5b612137336fe8245e143d56d77be7ffbc41bb28ef29fdf9544f99215d1493aa2731a8925c9474b66eec33ae830e2e8a9bb398c96563759b5b947a84de289371d7765b94f39e70cd2e3167b3b52879385fdfd8de9ea745318fde064db11e6a9d4569749ff027d44fab2a8bb29bcc19254db32435160569c2df9b1cc2a25c3a356692b145ebf78a927fcc1853488d39ed8a925257b2c684119fb31bad6893741dd3fa646fb0a2e827e3c858fe9db8f08a70631545934f860e2a4e7834f91baa2859c898da16912789592a8a6e5f1a5b63f3262ba1a2185b72c24ee7f4fcec8d539463fcc9ace898b3be6d811ba628c7369dc44fbfa519bc1d3f8264298aaf1e73cca7e4bfa8cb468ad2e7c93efb5892ec1502328a92a4a3e8b7159d288aef1e5f3fa39a8927a72e14a50dbff52553846992048ab28a2ea94978dd5027fe445944741ab5275388b73d511ca59359099e84a9d98982d89339c99daae644d1e48cb1303957687313e512f9a267f34a125113057dbde2ef9ec944c1443706594df54cf906264ac2f59ddca55abd44499c9c5fafeb74fdb40e364b149392b6e45bc9df900e5ec74825fa5ed7b87f62debf7e8312799bd7c89ee759b77799dc984431b675fa32e1e4830c0f9c910120404c09529023c719c70b372451fcb5534a0ab976a434e2810d6c0088c9418e1c23772312e593242df3179e0eb65cf6c00d4814842a41644e9f27f59574b0f523ca226a7d3fb77a8e2886ddf71a4d75232ee3461463369d37689eb5f50d234a331bc7568468857e802ca270a12419364bfe290f15518c49db699bc79afa97888212d95c9726556c2e35228abf3147d5c4cc2e9d3f4439c6124c8ce96eeff73544d14b2ae94d46934294cb4ccdc4b9124e6ed98e1064f4e83132c68f911100dc1844b92a53c6f5fb587dbb0e36327af44005dc10042a63470eca3023e4462074f7183d2a101e3ff2a3c7880f4386190c6e0002d3dcacb0ccb355f5f4ec93a6f73aa68198fd4329349c641dde417bcc79f0d801821c39fc502a25ef8b528b11e1b60eb64ec18d3e14e3bb9992abf7b0b9c387a25ae5fc897d6298bb87b2c62c1f235be57d4e74b025a21b7a28dec5c9e9749864b9e7a19ca4b01f6166d2c19656821b78286a3a7311ad2b49d64a079bdfa120caea636b4dae8913b9dcb043a9e6f575344875b091b11fb85187b28b7abb92a26412b74ef9a34710e31db8418782095111b7b9a39e500437e6502ce147094a85f7891305321204480f1d2365fcb8b41e3b467c00b92187921e0deb414bf81c5abe118782124ef808cd682b7a73030ec53eb58fdf4986b9f974b00121a3c78f641ab8f1062df754656b7366cc4db764feaf78c30d2599fbb37fdf080ff7abe0461b0a22332833d119376eab83addb8c0d9c208c13dc60434134febf9b2e25a9cfaca124772aa19310f1fc9d5443d1b47b7eb20eed92848c1b6928c858aeb3b525a8d2e90d34143d4921e623db3ee94b851b6728894934b5c2b6549f243e7c181f3e0cde30434916a19352f239e7ce77190a1aa389dd194f6428c791f1e327a54dc6f68c616ff1d05611cf36edd97553d7365b622868e6a8d56d174a93347ab40ddc08437154d53535980c1094bcfee006180a4ae99cdf386b31aa7fa124ffd5485155923c4bbd504c5751e2dfe69efc71170a3a423b4cba8d339f03e106174ae3b9f5eef7744e4a76630ba538cfd18afb9ca2365a28867e87b2ebdc8d2c14639364daa49a49f74958288f89b6b0188d665772857218d91c83daf9183369855227bd103a8e0c42ba78a30a25ff9c9aa4bbcb7f0972106e50a12c5a4d8e416fc9dc69fb3f7a2443b83185929ce039e6605ddeafdd904241fb55bb4567df52a251287c3a9d3adde45048f646cbbb4ce55a5b3c4df24e7aff130a5abb25faf9271d5379a020478e203d78006923c20d27144fbcc4f5a079138a9a653f36af857cae985052dead5ea7369fa04b4b687674ae5d5b64d75ece549a2c7a3e4a28bcc979d408f3907d6d8e1c3972e4c861e6a67a3f80b0f96186e11b49282637a99ebced6b3d4642696b77eb745849a2ec4728ccb996ec1b5e256776c3087d887d55c69b8a89a66713ef1b45286586939f537f76d56bc30d229433345756863139cd98851b4328c9def018e3c909f59a2adc1042e94e4c93c46f1d11e16007420d789f3d8f63460fe439821b41282941a91813bc648dab6f00a1307ea2fc09b2de3ae62faa52abf4b65acf50c2c5456b7e744fe28be2c6951bd9bca525cb62d8e8454938c947a8dd79519239864fede31d73ebbb28876aecfe8f7126e4ed3e6ce8a264625b9acb895b576f9f99918bf2c546cdfb9e848b74b6dba4114aee1645117a7b4a64ce924a124b1bd8b04541959419748d6b9d7758031bb5289cf465720c25aed6c11e3d0e0f1bb428a8bbba07a1ab55c19f338c08ea82ec28634719668cfc39c3907146c693b1021bb32869b3316955d5f193b42c4a2766708f311b3b09828d5814e39db6d792ac4cefb4018b92658a698d3e7ff41829e3c77b80edc7efe811d87845d9c438f357f3911f625714f6a3fd0893a48ea2cc56946263f9f68c859b88e935f03ece48012b0aa6393d598aa938edaca26cfa64cee9ea94e841aa28ae78e7899b246eb63b15b99eba6557b9d5556c7dd092dca369b612ca12ece0e103053f8200715414bdc4bca643cd9b1cff5394bbf4e829bda17394db1425d30cba772c53ef88364a513049959c99924c38d9b5418aaf2a7c6b74ca4651369d44a6556b703de908624a9023478e3382181ba228cee6d85aed233378fc0f1fdc364251b891f9246d59bffa988e20a604a92388f1916704b9b4018a92d906a194d850075bd9f84469dd4f9b6a135e820d4f9432fcddf64b29257f3fe2c390c11db0d1896250824913d63153e6c7a39da09b13259d04f139e898be57a8830dc88e36203c760431a30236365138c94eaf28f9741aed68a27872c998ac336c23139fb0a161d54a305192c13e7410be9d45852e513a21c3ad7cd49628a83f19a2f9a41205ad59937c569bd7e450a2f8357a4549523c8962a912a73c7e85086949a2e81642e6a4e7c453491289a2ae5b5a8fe820512e0f26c7c599127465361ed1be698ed858cdeebd57965646b92fb5238ad971b36c652731fd37a29c94741d5c7e3c67c9da60447946ac2419ee5579af8d459404ad6fd53fc14d68491125d9bbfcfccd67ab631251729393e7959c464449b3492a6b36eb246964e3109ceab6da8b9e8d6b8589f66872ba7338d98e0d51ac13f4c8cfa5e111532b4459732e3176c7698310e5d6da117a948a2f31d483482cbdd4dc6c4356cf5c3b66d5d0b9eac08fcf418f1008a2f45ea64cde32abdc0e44b9e24ab587532a44c7793b7a9460cdcc06208a39f234dec91e4f96c9c61f0a613aa64b1f53fa166cf8a128aa5f746430e9ddbac3c139176cf4a19cb9f3c65f494a8cc91a830d3e143ecf6e9de4f61973d9d84349aabd31e175194c7b65f4e0a187d297f09a5de6c943b9ccb4868a0ec2437994aa66d0313556a76cdca1982646696f923a953aed505e334912bc4b5687a2d8c9a35465b834bda143616feb57545677553d87526a38dd417e954341c83f5342638aaeaae350dab4bec2019b93d7dc6a11b5306f3599b653f69d2e49d2df903466fd26c7f7dc5038f95373834cb6a1a0b94fe94cf7a649920321c3fc3025b039d86043c164d331a64129f5bbd460630de5283a961e5e549d12b3a186f295321d72d28c2757cb6ca461afd0af716e2b1b68288819ed1533d9632a491b67287d9f8e7a273663fed8061b662897243a5f6bb50799dd46194a1964e8a0839824a3f636c860955a590139cae8b103c88fc7c1164af59d63744e42002d94c4df9612ba3c367790005928f76cecd9ba888572d05bf2caf457289d9a29498c155ba1285a4d1ead331696e92a98ae65efa9d76a27b3accfe654333b158a3f6692e09f1f1d6cc93c0ff341802914839b2a175b3731c9e12910400a65cd789fc49c272310200aa50d37d6be19d642e45a2000144a3a7aba047842490ee9a145f5228013caa961c4c9f1a975aa2240138a22e49592846e72550d080198507a2bf1c326f5d0213e4b28298bcd5695a5839c93004a28e76beaa8e9f312552640124a55823439476acc512912d4aedb5bf7135bf3e2e3356f8e5010b712630444af4dad2bdf44636792732a3d32ccdb042842417d8b1619a1c35cfe91ba1ea90c0f201e3b7488e1250601885012c5e42449d031bc7d9c0043b8db5637d62d67c4abeabefd327b9600422846394964522e7b26bbbc2640100a9f5409df1b94080084828aea53d17a92ba93fe4551e54c63f343387c81be12296a73cc467ef4c85e94e237ec7f7b6ed0e7f1a29c447df2e0f1ee9390cd00c72ecaf562aad499b02e8a57527e925c378effe38394f1b9288ac618a366f60f22322e4a72f36ed0b8db27681be1b845f924dbae6a8bd338992d0a6a6b5f3e99fae0a845e9ce4a6a9e5c213c4d5a94c3f8c98ef92143c87716650fe3ad77f2d1111cb228c6efa75262d29a7fda58945db64c921baf0c70c0a2204fabab9f92ede9321d6caf7066dfc6b35d67f32d6b43a606b58e41cc84a280c315a53953b1ef5d9f59351cad488cbfebd8fc8f51468e1c45041cac28d98fd6a6f1d8e5225a4539ad4dde3f11ff70fd781d3930db030e55f4d69fb7639c8aa491fd12ea327ed81415059d214ccc1d99496cce294a3ab5849e485dd1ce1587298c7b7d95b3b91c4dd5bcd798376da3a414c50b79c2efe6838314259395e326e99a9d18846314c5b34f92a0e4524c795814b57e7a977bdbc9a955699abed1d7d7ffcce10845b17f93be12ddf94f058af2a99226e636fa8992c7a7dd18ca4d535e1c9e289b7fd8eeb53f41c486a3138cb55ade6ba8bc685a6da6af8aabdd4e4d130d3838515ecd3ba51f4a5b897c1325df4c620ed14e937b6aa274c28e97307a7a467b3251b2f8f1aabd4d279828e7b8f02a41b8021c97289b0ad9b761642c51d8e41b4f3c8df4dfb212c55423ef4b5a2f49afb653c04189c247cfb7b0d23bb6e5248a1d548b681ab59d4eeac707f1000e4994b7f64bc66c8dde6d3252c6f3b0eb008e4814bdc4357aa62cf16489063820510e7652dec893e151323ec88e9133ca3039f832467ef41831447b84d5b5b1a697ebb9b1b39b1d229e65745e8b7138a260995b2676f0924a72510c1c8d304354354fedde45c746bff3480ffff1931007234a1d949ff892741c8b289d303d2d25e38a2886acefa9e7ec0c424e44f94d8365ce4ed2e98e21a25442a8d995f02e310879a200c7210a9b39675b6c8b6bcae0304441a7fce849289d73d4288e4294bd4e4c92d45163d049c6c3ace028818310a552f95cc266cf2769aa83edc728c3fc305ee01844c93a6a9242c49356d7870f330451ca2e219360d95dba84efdec7192960038e4014845c91969aff613509204aa2fcf8895e727f68d732643e110e3f14fe53ea9a899d6476501f8a6573d965aed94de0e043b1a3680d23437ecce2d94339a85d97ed111b81430fe52a6155a3949b879220aa4dce2e27e72b0d071e4aca6544c91e993b94bb432771e4afd6c56687826b6efa18ab318973eb50d22245fd7e1cb9f9d5261c7428e6bbf28c414f83c93a87d2aaf688f468624de670c8a124bb8db9aee98e319ae250d816a91e47753814f3a6bce79c6fcb32f78692dfc953b2ef3987c670b8a124dc4916da3d9824fbc2d186d2ed49ca37ea95fecc1951020e3614a355dfabc7ccbfd55801c71a4a21fc641ca17e336ef02336485e47623c8c19372838d450ba8c3b213e447e92e49e8662f67811a3cf33556627703494cb94a039ee99b89ebbc27186921c17a162ee53f5a87f941c39cc8fdf81c30c05d5d94bdce99ce2f383812e4339092d4f9adc838e6fb8031c6428dda634f19c4c5daba80738c650f6204aaabc0f3d51e9085e3002031c62280953edbe6dd2c618533c0c8e30143629419a9453214b4fc9031c6028851e755295f97ef00be7fa6f90ef24cc8912fe08e20387174a2bba3a8f509bcf634e178a69a36abc4c4a8d6f0907178a99420831da4fa5c7ec16ce552526d37ba25a402f3dacdc4e742c353683072d6ed2290d8e2c14d427297d7c93a49f7c2c94aaf39e2c62629a6c325728c6f5cfd74a4fd2366e85922925b697858ca30a25e17772dc1e9f0ac5fd919ee4547392961c1c53288926493286d689965e38a4508c21333ccae7d9d4a6f4008e2894379d38b2bfcf0437792814fdf2e40f1d6412d4c99f50924b8f677c4eab3c2d0e27946b4b1a15132253368fa3092521dc4c4d83fa68d77c193bceba0007134a629e549d543e89db0cc7129c0b5713d7ee345a19942ed9c2a184f299cc67b2758ee2c1f33892500eea639295e8bb259be04042b154c61cfdd7e45cb763e03842956e753532377799263f3ae4c7549dc719388c50927be4546decfc004711caed59367d94d21c5414003103071110abe93fe7e5a46e087cfedb9231239349f7c021844e8b6b36495e93e4cd0d7004017db56aab105715dbaa7abd5c311945642ac88e1f2cc89103b10d7000e1de705a9dd44b06f08b82d076777192205f944563d0a57ceb45b9b3eb0799ee74a650f2a2249654254aec87f81ced5c30805d14943651f2e37e12d446ba28afc6911ef5e5e742938ba2673e112afac6c5b29fda7676765d1fefd9791e93243be7926400b72827cdf0e99d648bc25a9aa93ecdf764aa45d9940a2db1dad3fe6951d67e3d2b49dc244cd02c4a264c120d9f74de389145594cea8927cebd4a271605e141cdbb7b92593db028099e35c624d8f89ecc2bca353a1afc7357144c3c49fc98e35733de8a624e9f847ccd243adcac288bc992f57583f610a755144b9e26556206111d4daa282799a7f7ba4abb9529150553afd9849b2d5db2848a9209ad35a5b1748aa29bfe384aaab98c5a3245f9325e568e28318a954a515e9b0d3ffe69e20991a2e41993b8f7b9268e925114e375de64a26a429a88a27cb27fda8a3331772714054f4deaa70414e538a65affe95e73f389d29d5493f4aee7774f1437c79f4d999386dd899258790de336270acaa4d2609d6ea2649b94f7d57da74613c564a2cac878dfcf9928c9aba6ef151f1345d3db374af83cfee15f42ed1eef2d51703b497ce835f5f0be1285edfd36a143d693d853a22498127372737faef69328eee8bc4d5286d46c9244599390395afc7350279128e529e162c30e89c26752eb41c5b9fbfc8852db2875adb7a249eb8862504aecbb96ceff69234aa324a963caa0c3c91231a2b84926b1775a1fb7448b28dd0731763d2aa224b45d88889a1173224adbfedae271e4680f11e57ce2a7eebad5b20f51fef66cfd98d14365c81045d1fc181db5d4bb10052fb9676fdc7dd42744614e36f9314906514c927c7efdbb208aeea7bf438938b303510c2703a2d47a5f2fd62342c87f288edc52ba42f6434912bfbc24f93dfea40f852d6dcf6099be36c987f26bbc7433f750be0c15b277b6e418d44341acda4d305d928762f61525849636d72dc143d13a57c91d8ab1754b57c865a8c90e250d1b8f569f7468ea50cef85994c89e0e05a576d54d08cda19842658e696f749d20391474f3b7c589414925270ea5f0b2f62475e050526e7e7d2665936bde50ac534ad6ae6f5d3714c4ce79d6d84f1bca39fa6af2c86c286d87df17359ad4b86b28dae76b12b583284d8f1a8aeb3a26532931e84b4f1aca2b27c82446068f221e349443ba8f97a43e299d3b6728b78bf847abaa91a9638682dd9af0546f612b2a43499630daa762b3930c19f658951ee43c866295cceeb94b12e4d76228091a7ec408f3246c1d8692ff5629a964fdd03018ca4918cfd93e1b6412fd4249466d936fa25e28fa69f60cd32b4249174a27c5c670a198352669f4c22d14559448357552cdaf85922937412de53e4db2502af14451935f6d355828dfcceddacfc9abef0ac532b9844c5aee7356c60a85d7a4c49c9c64af4d158aee56bf9f4a5428e924c769ead0144a199bba94870f3e1b29146cbe4bccf3d6c9a4a350924d909bbf1e37970c85823e93bd4fbd4f28e83826991ca64e72d509c5f528daa48ea726a64d28061db7493861a99932a1fc5be641b5c90d652ea1282a83e793cb834ea3128af7b94af949288bde6849a1cbfb3b2494eae4a968d5b3123d4261373727ed79fc47334241e7d6104a30b1f0cb8b500c258e66927a22a084934dce984e86504edd8f498ceb26a78c100a1bcece72a3abba1e84d29ff02567da030042d9f34b38b1a75c2cfd45d177c3d97ed0bbf9f44541dec9d7fc1bb327d98ba27e7cf4b061377b9017ef8d3e310932d8bb28e5d9fac78ad26262ad8bf2076dcd11ef9796752e4a924713e593122eca22efd357589aa6e91625a18465c9e9d8c17d5b94478785e887a9aaa816c55c27a98f661e4ac9a145d94c30f7dc2aeaf43e8b6210428829e9c34dd46551ec187563a963515ccff5e92e4eb2b56151b22cc176e7571454f8c8cf86de24b32b8aa2be593ac656949349728b79ebef8bac28c74d73daa33509177915059973e8a0a1e38be75815e51332c7d6c4fba0394e4549850ceaef3c4645c933a52625da65b68e4f51f2d8d2490e2564ccc5a628c9b959ee73923dfe2f456947e820ee4446358814654f9df7e396dca2a351945a64248ac2cccab7a8d8fa982414c592e76b545c448a09288a57f2df4bd0270ade5a59da931c6d1e4f147e7d845092b727eb74a2289f499294dee0f1349c28e79ecdeacf31e8d437514c3751a77afbf1744d9453275592fb9c89627637058e83a8444ec5e2c1682c148604c2703014081e5f47003313000000101c108682d150382616c67d148003593c22463230221e1c1018160b8502613810060483a130100c0685c2a13020188cdb5295d603cb054c60548c0784f6397345e10bf91864179e38dbef2d58da42856dcd9ea57a73d4e80c2aa86d9d328280fbdd0b7b708d041d5df71eec76da3fab5abe9e45613bd4dd24a75127a7b8710dcf8fe7744892e6289db296f18b342ba9a637b3b1703bd23fc4ede7092cfc05c6e54ee63193261835cc49728bcdd448e3a5653a4b1d3088ec98e22fda517ad7f80b9c7b960d1a3750dadd9d3df8d503be34d5c5d2dcc74f63f434f29c0f46e353849df0017656ec65e9d30df0ccdea32cc178e77fb3a333266f23c9b994907c4889390fe6e48001859f89c0df5757c2a15a652573e9b8985ffd5c61a76f6d0d900f8e743232c176301f992d92489c788707940f79bb8481876965cc59f015c9fc2a947568e3be9f058baf0026df1e108be8c0faa6124042342fa51cd46c2654a0d319b6612fe175ed1eb503837c4d70570b256786337bba13f2a07eaadce9710ea1ccbe3a775c976ada286a9006199c7be6fc04d7767bbaefc726a1c9be22f7d4932f4ba423bb57b3e77b3c0ce3ac808f40d8d3de3da0e541ce80ce8a1b40cd2414f119dc587e9f9ba3cc0061620e9a96d92ede4f925b9a35a66f7b2c89b02df4af868a044f2818a8bb5c77b0a83dee9a3627c8fe68a462738bda98c65d3dc5d07200de6fbe26448f37f167959a25187029e197aa84988b31c3e6a30c04ca743cff0c58d7151e0b62f562a352d63271761dc5dae7725586bd9a7c8ee74ad5000641c547e032b7332bc9054acd42bbadd3c05d130a1a69659cf8d75f60784f3b88445377127bbda39ff9195eca8041a224325026209eb57d1bf251ea08e0654849bf4e8b3d47006b1b1517623c22505b1747c33418254420fbb1aed461891a267281e16fab5d720d0097309b934b31a8e801c4402087069983f52767331ac1958e93e3c267d19eaf0be92c434c5005b409c44260bf049aa01b0ecc126e09a3a054062258260f52d051afbc3cbcaa7dde100647046526b4b88ff836567e9195b1efa6b64333b3c12dbac33514521be0170b38ac5ea4aa86d1b8448dc868c80049c56b9bc07fcbed8aaf725927d0bd163bb8185b16dd4003259c428d1d32dcbebe03c2d2f1df069fc501f522985be8ad2c680213c56aa2d55b326529d7bbfb9f525739a1af8c9f6966d23fa9eabcec7e9e893c56a2bd07229574550ef4987d6179014d2844677de29e254a529f096aeedf460511efab751a0e142f6453cccf8112a9aa247f10122db2e6a8aa60b90d5e86600fe4a9c14d7ebd6d8276940b2c55a8e4654295122b6729cc1e2196aa9016481c9070a3a8ca5d82f17f709811fff44af5d02b6e56cdf3090ff16c5bf1764d9eca328254a37c32993b14f1686de8c8fa3a2672532eb7e31a10b9e680afee4127fe4455375925b1eccf17fcf8fd94af1dc95da2a778a8443647b725ea6874ca7be3429a368320fa59d7900ef85eaa4574fc71ffc8b8ac8852e1527ce95b4615b12b3ca46a6c9f33ddd8ac95b5d44685d2c885212c26aa3131166caf2c8cd09b0ae85505f150eecde26e9ebeaa96656f6be158a5503f1c23d1e8bbada6007a8e5596a696be729ed99cc67f3f2b6b5385df1af9d18966a8f642d0e1c84a8aeb725d2a14a1b00cbadf04313d796a5c9831088fe121552d7315d02585a78797c290f7eacb32204c64e1944de58bda0ed86d2dd1dd8b4026f52c937f081c7fe40e73b429e7a3d718e1ae4ece50f06af56f3208aa8e22bd0207545a74e9811e193494adaecde40824e3be7064762dbff8cbb39577e84bcc6a7961e3798ed3246c57a829883f20e9cde2f3540341078035a33be392278bf9ce6ca5b70bc72546204e9610d89117dc09709c87b66b2ef02d935776edc44076c48597feeed42426d0141ddbf88ab2994df92ca05b16d56456036d1377623267f43840f512052d630a23a1e758c76a83f936d169b23c638517413871530e6dfbed776e859b013deafd00167cefe00992c3530466710e21cfac2b111e8e169786636a0630e3374b2caba268b3580aba762eea38ae21dc1ab40664c5876ba656e1aee31ada063bc47131e5448ef2466a0458441be0bc34faad5970aa8ddf389edbc3ded4aa5d207e6c636512b07a4ca0abcba09fa71286cb5bd6fb305733f78594c6863776c291c3e0e3fb4e6d59c0839e9ebde149e2da2fb7571121eb5967b7ea5cdf3fef29a44ba52adda0e65aa6e8814314cce489691af69c2e59c7577be0094d531aae83d1ec12019ae686ced959da7074c903f0a985b6476984be61c283389ad39dd6cac2b209469c18c4a9829eb10425c1b8bcf78d2dbadb19e8d8dceba490fbda756185e9868edbdd15306f7d2f920d93b491081511307a25b1d2f5ea84e5c8462fcf4e555cdb9736fb2f8b5378001cb6b3eba07d0069f4dcbd436aafc4f5b02ac44e4d68ef17fd14c032f6e44fed56490ec8d579cbf03ee6e3c49339ed33ca70c15311f3392bfc1ed699abf4c6b6e7506063e4854815e2f4706abd832dbe1fd69684a4910a896fb5e276d85a27c63476795a5fe047cfcad28e11fd7092e60f674ef28bad8270e941eedb535a17090440ba08000081aa98643102cf865f7c327d89d07378c3126fa1f463fdb299bd88e57dfb757a0f7e9198db22c69be641581a527362a74e665c0f8d02a4d25ab514a16b863528367fc6efe83544c3daff50dff9f68b85a52a2a766cd32e12042a4d8442ef6693200702407449dac2e6009a1997c5ba128f1cf428bd19b70317c15bedc745cd535001ac2263f71c1dfd4cf4e6bfaeafd9f0e16c88f12433df04a551f123b35ff4b48f28190bd07b6abc79f733d71a3098c5006fd3eb7a545c09b7b3339b7f692403a7e346758eb1772505d4beca73f0b03613e90fd037eabec8144498cfd6241d8ea4b7e41e1a96dc680ac3c98b5a65a2afad8207ebd1f3d61ca087e36438985d4463defbcf9f2abc88577a4b35ba053bb3eabb01539007ccf0922441ad6a3d1ea8c116ba40a2952587c94172d8dfe68a1575e0a0c1397e436bad36f4c6a3cba4b7c4d970ce0e542ca2e16c6dee9e691c5600f02b3dcca96d3153912ebe48a899a20fa88b8274ed68ba56795717aa3be4d07b4c4fe92171d03041f328e54c67d69bd913711b6c6d89eac6be598e967be58b10a80861e9687fca82c7eec58d29b7f9d5cb2832039f11ed0c97c74938c80e5086bdd58460f0dbdfa21c23ec758c634403039811a8c49f23bdd7b25a1d8f824cc377cb51b241da53cb6811bc430699f1bcc345c8b342c3ee947266394d1191e8264b8e848fa07432631267190ca3792e4c5b901b7f4a7d5342132c53e286cc47022dd2088d13651f051ef6387f2b20e1cede43ed239cbfcb79db0e987da29c530047bd686b53e46a20f02dac2b5a664ef796138ab5f2186d03cd99756e9f9743c901f3099acfa76a28c649a506446260ca1028c43bada59408dbe4fa0710022f880b15295e5723b75a13a40858c0da3e07b7fc71bfab6d9b49e6bede56342cde64e79d47b799782a81d5202247025fe020c594cfd8bb6b0ae44d652e7604034dc85b23b8817fd3b7aad82071a056ba745affb326ca04847c864e185a3343b5a9af082ff5093738a2ec21a1d4c211ba64f6c1ce60764d93cfea225839998baa2919d027fe50051ed5e790fb1fd5dd840183dd00ff5923a23cd4802a50d429213fdba14b633fbfc1a4a804d5676a9d7957dc80186d5c6ebf1d753afe59ff985f31fe4ec2e728ad0d3c9383513244ce0eef74ce7f86c5b9e7bbfd811500d809aa6c5631f4af84dc6e89c339ab95727065bee6588eea276f86df69b0686390f690231c53eeb650e6df1158d355bb5bca6b1a26754d82b4a3d50a9190bc87e8196de44ab6dde5bbae0625212ee4d272c203bc8a0801ef7050b30e275cdd84df5dcbd66a2ba818e2d9ec818a31bfdf92c28083dfadab95963d4944262b795e8dd7a8855c2fa71767ae9996b03d854985a23adba0d9399231cdc1fc6fbd8a1a3a45ac13c3f11bc3fc0cca3f239bf5c72096840665c6b7590ec5c3192e46339460daf2ebe6e792c10cdb5bb5cf698c9c8194e6233a456a0d9016be74ee70ddf745bb6caf820bbcb3c14df9008a87fde04e77611e5af3ea1c5dfe7b0ab280af2b0da0ae4afa78861a05c36c02e8cc42aa826bac64aa4ea8464177d09f56faf311bece6ffee3c013140fa7ad8ba888e23af960ce34cf1d9e0fcdcd0a8b5bdf0390b57c9531843e261ca60fe4e9df97a27b069a6354d816ab5a4a8d95659593c3053a8b85c65c8f5fc6ac40fca13638b815175728905eb74a49575ef8c1392e81085e0974c230c6587c48d108b3530422d7056ca17fe9be29325be06450023c2081b950ff02c681bcba5584238267b17543f8d8b6c0be7cea95f7995e5528d67bf545cb0ab83692ad4dbbb84a0aaf4854022e64b07a3dda75b8e541020c5dd9ee09e64d017c578b1cf84253d60cc0292d5e6fe79a50613033a9f870e7db394249065548edb6de5b1fe2fb2605c4c3815e88d786d339c128808e18b5d9d2ed752aac942118122127bb5153a13a2301b080c72c03f5c92953d1bd8ae1da9359a1d162665bdcdf7cfa600172d0219a2fcb43711e71348e125a12167400ea1b631c213201c473b487fdea08dab0db5ebe4ded91ce8a6e8d0cc923c052d29f702219186008973bb2d487116d3b2086cd04b685cfc5e0303927ee26ba3580521cd312a2b73ae7bcb0a06458d2ed5cc4608cdab2a6010d95aa1b20143c1a2cf6494174412fc711ced275785ad3b64f1dd6548c0e31026cbbb0c156a4c9f20930ca7455a47078aa503fa58a59480d8254a9432c79201be7d433a0949953de30520b738f6c91aec6cc03644418a98662ab0e01212def4f64128a598ac16e08428e8532464660bc7a92cd908170543617062c3b476620692f12b627860c58bc984c8248b8e1bb893407a81564773a1ae494e1f11190450aea1e44814aa36e21e016a735733a9ad466ee4bfd2cac5c87c47ed2140006bed8ef47e15a100f344004b5cb23fe9d73dc59a1df17aa88f2c41aa732ba86427600d54304911359c9220e25302695cde96049b68e84ce4f9a1055cb75535a2961ec33d8a7728b1b3516129593f32f31c51d58bbdea7380b09490ca011949373afb67b15acaaeb3b6ffd701d769fb754e270e75c6b4d317ba1dc7774064482670aa671b295e260c1dd98ebd343be1916d7809791fe3778e23ab1f86bd22ca69552014c49ada4c9e4c00de074b260b4eff485dc48b02d7573ba486bda40b03502e441566a017a57a81976b7abcc53de6665831e5f5860701041b6a0e5fe6edd5ce47423e60af0ab05cb1553480c66a7e34aad39c1fbc1dcd82129e5ebb861b76898de449336ba6bfdbbb45a97de2687b9492e5cf3673de9d7c340c47907434c5fb1caa8fed036637abf78eac6e720b59185e4f6b0480908e6852a5ab6c5ae39d3872638f1af4b0c9a65809eec978e5a2e91b5971950ac72aaa5a8bc54fb61e5182d56adcb3163b869ba6210710b0116bcd4db5cb2071b5ce7684a168274b118a4c4d9b90e5d4544803501d8e871bb12b8a25dfdc1e920f7e014588c834d4386a0ff0e8a8f3806ccc8836a7194fd774d13c50475dffaf3a06b6fa0c47e9127f7c2ed15ab5773cc6403a0fd2e51a90163a73c6325db2458508acdc091c017d916fc29e3d500392f4e94cac927867007b3f3bee4070d7e78257e22d5be9282ba2f22b9aa6281240d56b0507b0b6480741b8e538da61249dde4726300809979a945b41146b730b2bdadd00c8d9e37b7e4882d55aad762348bbe76c58bb1211c5b648099c805bab360d8f81dc22ac09d57e8400b0333179185664f3ed22e84fa2c673a2b5b732948cfd2ba67d3f96d28a11da54f9282ee2a264fc55c4e4be603926e40cea5c6b853d5d5316c8986fa7c4ab571b00fbf8fa6c557fbcb9539d3e59a963eb07acf74279934d6514ae29300c61d395c25d6f9abe3cda2fb941f58d30b97882cef5a099c6bdd919970f0962a8441ab6425a44cd9c969d982e8d8626d5cb801cae00fc1c6a62e46c74ec7299352194620747dac03d50c70364570eb86e202a6dfd3ad683aa145bd3a27d143a7a9019cb7530cd1a6a4f5b3c5cbce1f0a985078ba130c23026012b01682cdd8ab350eeff95bb5f11cdae6311c840d4eb285e03ea79b0b099af3cb34edd89c2a246826e040d7c19997fe8e002424811b0b63026bc0ecc5abf22bad8e257d7c7baf7564b4856f899ccb8995283d19fa6162b8918c3410734e8df454b18d944548f410136136842f228abe2fa7a924167460d84963c2663cd66f525d92caa7c159a426d309fa71c454bcc99b05ae00ee33c0062d196caaf390a825aae77837f21d4441e8c39af007bc0ecfb8531ac56f537341028fb582cd2cde7f8942cdb48cf468b263a6beeebcdee6626bdbc423a12c24ebbdb50cc401b3b58e5eb52b63fd0f1629a8aa6aa87081c3e62b758b2249aa24a3bac632ad37ff9e7acf4315ad9a179c0d8b35573e77a07137eb401f7a13156e344834324ef3a6699ce28d9d9710d42fb158809f572aea3920a18e283697378fb04c9d6765a17a34bc1a420330269586a4b079a28100b4a9c9ed98882e987e1b55ff098b101159e2663a778c0af8fede0d33ad13ec78d52eb3801816f3afc7132358e7b7fa09c82e59ba89f997453c1c3b67e3b77f792f22e37e0a8de8b55f7056df7033343dca32fc95f7a62bd450b0393f2851dadc105fb1fc5c67da77ef6cfb3a056543933b6fd41c4c9d7eef1f821c3c88a03a9024faf3731db97c88ccb8b516cffe1357704139092a124055776435940ff9e1a04f2b24c0bad9edf1149d2964848b9baad0ee1c9026858f137227702b9023547a0180949c1d0f0dc7618617d12c383803a7a104d731be751cf50ea89a19b801886d4d88916b73c85ad16785b9959091f437de398426731992b523a4cd49ea274a3554296fbc909d004b6915c9506a80fb9a3e5080127a2abecd2a8454ffed3ad1543b48738fa08cbd73618d83b5a82e695c43c5b075ef9434d732de4392ad52b5488126fa473254ee765d61f39ae4da2d0165cf7b1c13cb0a15310b404f2aa383ad87ca319fd0c088b344eda9089d0e903e28d42abb6e943efa6446f3e700d24bea07954e8cd135e462dde134effd7c3951666469217831228d891001b738b095eb5ad25795114dde1a8a708a3c81a468ca88a41beb520895181589bd8b8e5411890d3ac5d7d933159fe9babb0c220c3e03c750cdb3819a87818fdf44e250274e456d691215b9294f002c6b84b2bdbee69d4ef9e228cc13192027716872134c32277d257260c90fe8a462f59eb9a09595bc9b23038dc0ea50cd589ec99385139e96b44c93a1c9963a7c4a3a92142300c91aa158c80e00238c34a71f27d08a6f4437b0a8fb119d4ce156fea6d62a62eea7634624ae14ff20f1e02850b16108a794cf17ed032c1f61b99ca1866349623ff3465c64641e845a513ddb27baa106c2683948eaa1446ff2211e45bd336fcbdcaad27ec92a099805512cc2b59f7d9f62be97f80cc92253185b43c65e1212118cc8c46e742d6ee50b29ed987b04b3408c0dd690be384fb99be7be6aba37c43f9290aac28650d0e6a4f508dcbede6cc92a75e79127126aa32c75242b4c97d67692db4d2f56ee202829501cbb1b9d6a82ca668684e2c231db61819998ab6c765fc6708e8231f76e5e9fa75b7279c5c2c4b3b52cf7b482faf2c711157dbbe358d0a5007714d5ba2954f44b40fade8df1d9b575e5c84b6f1c0d0998a8a79c547fdf17de0899cca7cc518cf139188c40d26f25b2905d683428168db889d1e01ee6a20db8e713a56594bb1cbeed42d37d9543786ba324f0e70cbe6cc6a191b8382ba785dd2777904097d87dc571ffd76d91d421c01e4d9cc4541a1ee54072d8ebf384ddf51d026a4d917aac523bca0065f2d9a94a7dbb6ee35180e58f44d82bd7c023eec65789ad7df23f81b84477b0826283398f7f164f7613bd187c877feddd9ba6d4a520a20990a02ba224bdb235e8382672bec92d713edb88c65532dcc52b794edf49d8d8cb0d6586d84be627caa05801fd1a7cf50e0fb16793606102a2e07d49314506e294f31facc52c31ae4aafe7e864e1f67c7836b0720a430edd61faab9b1403a9cfebdfff10aac1e10fcf122f5e867f195d098d6599af3186347d7b83e935e1f0301b7363a2fec3a5ad47260861df5f4da2c87f489d27cb658dd4d6798d85adac7a10a0301238204c966e185ad6c7547264df8c830e6062ea45049bd736eaeaac294d3daf9182457a6e4a8b9709d0a24259dc035a31a0810346bd3cae9f5f79649f70abcd6dca2907799c0ff13fd1085ba38dfbff7f1501f644b34c2e20b7ea27e5384eadfe3fc4f6b9a8425118eb982a329e1eea3138dccc583d9eb8877e4fb43b248d5fcbacab1b7b7558678bff0a099981aa74b521d488e02ab79a4bb80b5f0d92ad85a9e01d458ff21ca064faa182875ea5753196bd3388e1153cf0dc51f80dd903e8521b9a670bb4783b7813551b2699910adce52dde67c4e0d4d5ceda80197d6ae04f936f8fad80fd41e473f0d1ee48220c05d736d8d8880538e0469177a8807a0d937fc253192fbc2e518f289a35d1d276f136b66b126289e65017936a596b01187507abac4ad6b8d77ca992e5753db05fa1742d6448123a17976c6898702b2448f723c7267a66ab0c205e53d021fcd91b13f606b616833f2082d57990d70e11c4026239e2dc23af80750b1217fe2faac2a1657e987fc88d42db3da429735fdb8a11ce8927ef0821894ef4cef893730c49a214c03c4eca8766aa6db06df03d7af98c87386503e1c2461ebaf0caa814168b09e267a77008a239088d4c41e3c0958e168cd8b0bd97e2d7dfa9e14cc1ec60963e1734248dee4db0d0572515266c50420ed52b3107b362a58223229e33bb8e6f47e60a3d20f2e8757afe4adbe8299ac58f3ec3ca7155f93305d06c608f08304ee50e53013f23f1ce717e0f9c698d307801ccea2a40f502eda7e178770891f08b91fbd97c2398500c56856059995068ddb129642c4c8fb90084bc829e8080de5a96d3ba148b33e5f1f383573053b0fd25905d8251a6cc4ae3dddecd7d077164ba38285f05587b84a01090a45cd97db5f11b622f03721ae7e536ee8d77791551e13b2f170b4cf63c7a84f8e1c9e6a4679f9cd8116f52f688041ae4fc98068060a807b9deb995070897dcaf134f3e971dd98c71311636a9fd923472d99e1d7919e325675c9cfd003c4718170ba0e3477163bc9731ea15bf01776a2d72c31d9050772c14e706a49e58d225730306b6610b16bf17018d37107231e5f55c3229c81ad4c402eae4981547596bc7b6ed925c1e4383bfe9a47a754d31a4639f7c8c8ce2c12c645a95421c03cacccbd6b0c918c76e258e03fe30ebb0d8f43331fa16d4c55874a3a7d300a9189f0354c90f54d271b016e87dc360a7068bdbe4469850753908021b747f94c7f2d481a7f4ff5640f14c8701ab62e6697a7a24d199762b9f91931052edf014d4c6a44237cfe790810df13f124a7a905743621d2c8b7bb35b17a9b72b25e38a91f10255a49420a7c5164758bbde64138e5f5036ec9314fe4160744df16060c1a7043b3ca52f8dcbe6b59184a760a2da591d8410a3ec2d75349b255a200bd0abc33d34fa4a4ef5b2621293b5262c94e26d4c1aef7d6ac248c0217a5671017cf7160224f1d761c34b9f0c4cd4ecf64f0dd624d43ae31533f318451072620cb660f0ed7ad5467c8e23bc7bc2e7073177542cfd64d81d28b6aab35020efa3268a63217a239dc091f9455ae47a487cde2d9df56d1d4170b28e2ec89af2cc9b2655a75e0285165cdad94c35e3155309ce6036b3a4c7297b7f5f38b662310c4d033edabfa8b3087a39015dc25df6d508fbf619e17c2444c56fdb7ef20ded043ce60d4af863c49660cf123838ece9e74cf70084280864cf3878122ec6a6607ed6055fa13a54af3219979ec318c44e9496da351243400ffc07ebb89e0d27587328aa49fad8891cd4ecedbca5e17d8c25b63c2ba0104430c1fddec594bed3d1126b68f34836b96ac01426c6e53ba731587ccc53c8f2e01c6524bb44a15c9be20ac6fb8292fe63a0f46c2ccd2c64067d3e72e4395e43bb743e230e797491cff49bcd6b4fd90443305874705238e626448862f2d18da370b468d99094423afa6e443301ef7ab80bc9dce2e65fa1e410a5be8dd9d8fa96eb1f922485ad6987f30308cbd1b73a6940cfe861f6097eb8b71e77b20df91aed65cca2c941cf75b079ce1a08e3f0348229925f6c447abf2baf358dd0cfe8b7bc2c521ff9b593e141ab796834855d7f51daccd392a1f148e5ab0287cca014ef1e4b7872b2bd683ab31d2a93e3f311556235fe623a942234a94cb936364cc35fd2b582b8026450c5b8c2f86a75214b3a38b1010eeb9b05125e484a5038c7c0fea9444d98d0cdac0e8ae27cd85701f552627b75f0a966c63d39106b583c832e4c2058186214c45a17b8f3084270b382a4ac2626f73a7033b7e1927f51776d1cae845c1aadfc628cb6a02a70a1b38dde8ef69c1747f301ccc08a17c37315ec8b845d8d7c62703f1ae6f22da59cc659f30940b05e61d97b8905c2e394d314c6b2ac69b44ba1587e29a67ccca84cd582c348cf4ff7ed95d57a5f26376a8d869973038346e09921fd111532f4162e88d2d7afe389390b29a3293d2dd0da9f92610bb050aa5790967a053ac676c537ff86b2d01c3da489a1d917f4c2716c9276e5e095b40d9ea88ecf12ce4e0e797fa31c851aa5a436a32cffe85c1294ee0da8ab9c0d72b927b06a13c009f4c9e9d0c48b89b8bd7e1c34b1daebd214aaf20cb3c6ccb494d7c75a6e8c12850c7c3fe35073c3a22d1664e66c51183dfa1a506242091d188bc4f7f8a954542102e779f57c924669818179616f212763293ca7ad133c467ef5b443aaacd2a2e2139bfa2d2a683401873ec4356da588a6f976ed07e2cf4ae18df0b23e583f9cc67cab8dc4887c670c0698f6957c22929e1e1126580d804e0e9e6bd41d7460db7c130df0b4f82410a8b06bf07869fe6e89ac2c376d489f488fe5730c603454508e4a22484790b568eeed2e2865ffcb9e20b3195cd1f961c49b2f558c9870292bd978f5a75a77ea1d18a5b7f7170ea7d8aa5fad2ab4bf3c2c01329469508402624649e0c2f5b599a1612b8936b3c44efa48128cb0da2ab508c4d66ed129997c88efa1226b00fcb384ba1e2c91635f49337e04e339521b57a63a2df39704e3e516b0936a69453de0d4fcf2cbf401390caca759b283ce9546b3955a31f9bd14236d7872377fdad8ecce65d1b32275bf0d4c3ef3f47d90717b59f59c39cd8a25872634c96460c9edbd02cc0c6ab7e417dceaad3a0c9e340a1b6da0614cabb418aa5700839576fa625fe919f00395ca4fef86c7c7b055de446e7c860f2e563c76e9917ccd8933053b20f00e0796309853cddc82d341f776fc6cde1e44436d9f00b8cbc614ebbdc48500e9b9842567fe5adf2801f6e64f97e3c0e64dcadf2f325d613e617850d3591f2874245fa39422643c228565badc098c640a2047993c8f76113d883bccf2e34c58fa537c9843dc3fb3d6aec58aad70b3093316c416d8a967c0eb2eb10699b2f87b9b32c4e7458a94e962d1508a568f4ebece01cfbeb7b28acbd9463155961be79de601f1738cb1de129d10e6a21ab35841d555ad259a24226d7461c26b97e3535441ba34d19c499d703986880f28682fb99cb7bfb884bb826030566a11b60671f35a257fbc71aa5717f34a008c2e853db4c7c7944f585da294679852d517397af33ca36b0a2ff9f01b69ca701525e19be7eccf49247e0853c810ac762cbeddc7d3cc51b208b7a04d97b3e561ece9f44469747d7722cb5a17138211c535995f18ad252f818f82fa5e2d416de1f6fde3268e39b4fb89a28ad7b8af9990b1cb49677147592d490b9cf8a00a8d74510cb8ed09ada98b02bfbaf114edf376669a871bff3d8d628a6dcb0ed463d9005dcb36af96cc1b3d475188ef65111b906febdbbc7b65896d808b1448a7290f34deeccbe3cb6af8defa8c667089c65b62320123daf89ab940d3156d5fdd5c8fb8031c7b4acca012aa4a95eced7744848882e0cbfef5a39ca3c878de43e06942693e1d40ab73da48f683ddf5f34354547a85a882d0520cca4c6c4e5afe0921a2066089c24b7443e9199b72fa906849cc54f55ed22f865c622e61c375c0c5dc9470084f6eba62d5401e2ffd4f5e22b66999f61d00632493950906f7d4b0aae5df82567ce622fb2bd383f2e0bb691c3dbdfbac835e79f19eeb0dcb1b6f303587a3816e914f58be64025d81d1278837ff033329c0701833ab9d57bdda5f720792a107fff6a2f80902f263544254dcbdc3eb7351e76d2f762655c7ae8b30c8780be2ac2ff7dce6464f44f7c1f3606907229234e177b39bf6f6c53b9abafee94a4b179fba40cfdd73fef0b758d135b7dced05a37022b7ae3bb1ba3bde3d0bef2ebb7b6e4015b930e9d6f7bb7511f053bc4f784966097ccf79963b87d261880ccdc14b32cc54d0b9eea14ff2ebb82a0dcf4c345942ed9644daecaee4f780d53d8884654b5d4da2ae0623094eedee9189aa04202907f17c57979840f1c1aefc28f3464811a5e2c5c5086b060510ab4ff7124d9e41cb14e31b2bd011db226cfb14034cf2abd1134cc80ef1f1fc54673f054c7a9d9ee6fb0ac3b68108ac8cb59c6d63f9f21306b3c56529dc460c6a620ef72e8838ef8dc45aa1cf16ce323f230585ed8a9c7090a63a4fa0943ec4c4aaf76769f05806b4bd255640061f8068507050261547e2561a20cd680ba18d8e169b51f799c940be7a6dea6d2278d6bfd525f70397f4955c8c19ad34d905036042904042d6406368412a224feb310492e0dc1e2d60686f6afc2aa29b9ba14f0a3ba18de19709eed9f15c60f2193e855f724319298ed82b0832249300d32bc9bc045c670b478058bb23b0c861f763984aafa14d4df9f8273026951ea5a2193810b743badf459c94663a1dbf601b882de136264430014a90a1e3c7ea2eae8f20c36c40efa7b3b75a6a5218bcf5fb7e075a6d173e226cf6766ef39b920dfcd1cef0e53be293b63b1d9d00c37ccc76bf92c1b97bb7d3b5e3360458efc00873f78250071d5df87e351497de8f93aaed1c62358264eeda8f2360e19e6524c5dcc8970b4f23a65a6809e675f308d98aaaf2a82e330a2905cc15e5fde17a27d404b5c104dc32e1ce98e7f86738a9212331d79dec3dbb567e002f300b8503877c69258034849df17397ded4bce1edb7b6f51bc6e6aad1b03a33c954411d34bee200565f0c45a5be2b6c971a94316f28cbf90a6d6014c783133ed5c1516fc0c4638762caa51420ab1b435803e123adb0a7d179927b4fe401298dcfe468000874138290ff4366512262007e48002d848173e0a8adb00d0d0ccff1a0ce864429bdac1e0779eb0b1bfdb704ac5e2dd01f26bf6eb03c89567880152ed36224572f9b85f23e298465c7158e8a1a888f5a6ba831716ee662c9c0ab0a1d30a1455922f90db1ce86542b860bf3ae5078a84445b4441de5e47e55dbf632161cdd52605ebdf9b765b2e62d4b30a5b442e3660ec00e7089bb516a6d3efe91239f779d123112796a36f6844742a6a0709a075832b380049faa2e0904af1feb6ff50d482bcd449e44a4a190de608f44c081ad415421aea3b44c622dc05bc3da4dcb3ab257e63ac2169cf13624d2460fdda13bc7256e3287fb4a4b0c9ac8fdd0d1c7e141cbe6240d0698592c9c2a25a8a1510c0f87c10423f6e6bbb66e5875cf41dea3f88c81de1173bf7e9f8231879f6a9afa424e721ca4f17dcbac68124646a18e0c11a7247acfe9171e07fa5ed89d7db4c907ac22b127ccda14fb8945f787f4ea6a4c69f580a8a93796e04ecd14d7ae9516f9600ba81cd1e858c57c971064bcad9134cb2b94eaa438b8c6e8d1c5e20617f5f72154a5f44e515cd4c61bce684e38d2d58d9938a9d1a2e03c6741250282d19645adf095bc0f4fa8aa9591c51611dc3e53c02529e9f494c8a0b860dc42f24b85d7134b9ea50a72a68a0ae0bac20a8fb88192ee59c6998ce2b959bbec44805f35a419115458cc1ced294a17ba30392c61842716d16e027ba6700617b7ef8c11c23c7141d278b717d53bd922205ac3842ff070536ae0e6483611c9524d6a1239a23b84b9c198e4804dc8003f816feb6f5078ef10952aeabe5dfcba45d41f155a8cea52145458c5aa0ea4c0e05f39ff2cccb56897478ad6e5df441110490e7448cb231d9b5d5be9afb4739441b5010773b8a8578dca4483408a5f0862648c3dc2dc3e6ce97d398aeaadcab60c769ca58ccb13348cf9c3dcc9e401a408d017558ff4495c3d88d7f25aabd51205edbb4c7cd2837a2505024de3ce145426fe0d7fb45e64f402c79dbc0c4a0e0faf20009379cd4a64edcec883cbb90f3459a116f288f24b8fef6a15e14520eb9c388cf7612c0032dadc55b942837bdc080b65512daa7efa1da6d128c6ab3f0cf3475a4d2f985d25a059c591582bec0414df49b29db5e55a1f2a747483cd03fa0b2e0f5f44ee07357f413799789261bb617f2e18493cb0418c18866ebce99360b8a6375905bde93de807210c29860ae2ccb1e44a2123930f5f7ea46367a486af04d291a3e423b63481d7a87ff0393864f32c82215a0eff4054491e1171d2133b22904b0b3cd996013f0055f125491258fc96462c9a7fcf4ba01b5e8b4c5d46535807c3812ce9e9d4767b9e198f53f6efd3531cdaa6d85343b1dbead95dbadc18e517e626870bcac0f2a6d4ceec704417842ce79e4b841bb1742a10ba52aec00a80c741e3757502e182d0f7c31b8164b6b804e96a06dd5cf52a2ee3aae7563ca8eeea6c7e82f82156c29e43d692f90d159b7374f9550a48439f80565af1dbe565718fea436d4a2801112cd6ab1b27092a5b1885c959f5545abea5f07b699c2df23714e038a4acd721a2ffbb9ce7d5bfed0442ab39f6daac7c9040cace6e888e1753885591a4c48986a96682cb3917eaa75c8276cfc9d08815de897842945491cad19fd9e77157d2ec6d254f80da46c24f55e5959ca4f7d84d5c99049882115b479aa42276d13e256d03f1ec3615c87190336646c8cde1dfd7753a864e98ca3d104d8abe3a1c2735b2f35a18fd236aa5a3d5e518017ae6d94e3750b024e9f3ffffffffffffffffb73f03f57fcbaa6f2d4a59fb65da8944d5ef7a807af9a5945292292535adc8eed48146180210c7d90f0fcd04b804bb043930d8886a0511d51e5604c18129a88ee8ce2e29778a6e61cc0e3fa9593aa505912d4c26e8a8242f7ce7b4502d8ca32fcce65f88160613775626e6e9704966611a5992248bec9a51b92c4c52de0b4ab224562733164651f9203b5ea1920a2ccca9f925ff69e715263f694ef45eb35caae30a73b410bf22fc6e85717ff75450974fa974b3c294727e3f69292f5db657610cdd15a1c397aca6b62a0c9f3c4fa9b6b0cc512a0ca3b5bb247d720e9d46854157504ac8c896942a9fc2a0964d343997f2944d3685e14f3b09af914b618a562373e123c3362685419d65cd77d372e11f85a94fcb522ec93a25b4a230883613dc7cb4a130459f5b8bdba2fdbf0585c1aebbdeae4f78f9fa0993b8f112bd4b3c61b4973f5953564f693b61529742af8e494bc29c308d2cd5b7e6269c979b309ca4e414e646fda8144d186b9409da62c7bdd93261743ba5c4f48612e5524c18d743a7a0b7d2bde41206bd7d92fa5b3313479630c9c9d36739a92cfa5a09930795d6b994a026949430492a98a4353fa5982d2761cee1525a923f5b9f2449184c3ca9663929132a14099329492c415d7ee6af903028252c589e571e61bab85b26dd2d8e30ff49b29518a37fd408f305b153d9a45c7135238c5a1743fdf6d8d58b30a97cdecf29ddc3a98a30c55f117b233b7b4926c2e4f9979d4cf65c628588307adcedca265db6241dc25c42a992666135e4378449ae0acafc94a082c70b61de92a414d51f26d784307cf6a4b3a7a4648b77109f248ae59c94f43a04611272c2883ea9b33e5e47208ced96fd2cd72a3e781d803098bcdf2ddaf1f9afebf88349ca9e35397fadd25b871f8c9d46277f2fa5fae2d6d107a38a907695264ab4a475f0612d49f2a86aa3edc194dce7f4da45857cf46014f99fa7427930d9bf7fc9322b9e82d58107737a3225f907ad97628da30b1b30ca021d7730d58e651b79d28daa708d3383049fd810017f62a346c10e3b18d4cb829026d71828b83a987265b760e22bcf440a74d0a1b4faacb41d9f765dd17ecccfb5d4da310793fcd78a77d5d6bbfa8b37c1c7c7177f430ea63c3d6d82aebb78d566ad1d7130a95ffa24c90b71428dc8011280e0011e30c103d214d1018752aaa0f69682e5aab16e8d35134b8e8fddd8e90d06e9c94cb419d31adaec7083e1c28485d94ebdd0d106e3e757faeb3a134c996a78a0c6c7078ef71b5ed8c0c10683eea7afdb9e3a99710ed7603c659e35777fa4c85a6a74a8c15cea3b093725cc4aeb1d6930760e1ff9e5b26e227466c4a0030da6fc567dda2a7781a8e30c0671d2f66a5aac8b52fcbf683398925e7ed51eb598178db1d6050dc7e4c60874d05106831a71d55be9c6b4cc186b33f87b1588a1830ca6d015c5ce4c9e54aac73106e3abfa7dca0e93dae4c458cb917e4689d12106936be8242cc878d1ca6c7484c1143e946b5d8b095d79cae80083a99312daaa748fe58fe18d8e2f983aa83bc94e4ba9138cb41c1d5e306da7f4b5143bc274ca468e7619334e20828f8f62f633109251a3a30bc6ff9827ed725ce6ce1b7470c194265eb08f224abcd4b760f49c534990935544c41d5a3007937faae64d7ab2307764c1582664c74959d34fec3bb0603af973acd3a1730573561115de4411679d596173efaa32b3f4626a1645e856ee58a9c4f3245605b35a9838974fa869eb686083031f1f34b071e3860dc43be8a082f1a49ba59c7f8a9243018d16952585314fbd7fb41247a17d7b86cbd576b5c77b66d50553a1eea41721a2308d3a0b1e772c47694f284c232e94459384e7eb2008283e615263e22f25d9669e4697104f98fa724eff29492645afccf81b9d30eba7ff8a9ee54af42cb231830b183564c4c82183069c3068c895346aa93142c93000d9845a39b32d879798dc9593e4a6098367f595cb7f501bba7172c090811a8064c2303aeeb39a9c3e3d39709c19850993cef1aad6a22b077209b349258bc7ebea5cca4518104b184b12de74f2201f3f59953057b9df875c0e29614e523ac164359dbe5e9a8471cdc38d9849a1c47c499892641ba271712470f3e81e2a42050983f6399da6db7e84a9da040ff2a2234c694eca252559f600d20893b5e5b973b5a4b3fe3b4698835aa9d58f16bd362ec2ecf21fc75384c1bde4ee1d5dd2c929ba904498b4e7fbf5511fda468761314c90dc18419af127486a7861630310442c725e2f676f2b2ab3e5df5bcad28dccf77511904398a454fd418c969c32ad3b4398c2851df74e3ba410265952760a5b32e79e12c2e8e676691ea247251f3208c39a24b78d888230aa77b40ad7c1c273c80524101040e0073282fcc17aad39fd491a75e5dd00e207fca4916daf2fa40f06f90e9fda4609840f66ed183a27a5ef41f6602e13c2920a262607a207ce5c35e336cebd66ecc6e2f6bea46f06903c182f77ec4acc0bf9240f0382875665e4364d3353e5d2e99c961c2c873e3d668c85dce102103b986f3e7512aee259142da40e667f936221ceb6e3c5e960981d3b2988fb925abd39984c89ff9cf23b440ea6950bdb2a5a2a9f4a82c4c1a86b96f2587d14a15c3898df5e3e4ffdc59b4e7907903718566f4edeefa9684a47216e307fa8ad272b35f9d678006983394bf612f7923fa5b18f081036581f749e135eca5d83d9c2eac77559bb92e58d43023560ba4bd7a794dd7a1a0ca26f46e4e9a89ed92068c8cdd4d52e5cb7659a9b7b8f2ae1562f8f2067307cb4a484d99f8a0b2066305af2e416fc833cf9446530bcfeefe574e2daaa044206f3e5205b4cdef2929fc76052a37d3d1e34e3348688c1a86fd144fa6e8e12475b0348188c174dd2274c4c99bcbd0b08180caea17e57b25649dda7085a00e304902f98bf4fd07a32aff369442f205e30b9057513457b8a5a62e50b48178c557fbb26e7564f2504e142f24dac928fc5da93186bb702c81698b50ed5f373fbe1a40573ea3fa58352313c9664c1e415734c1ce963c1a8ad5d7a74ea573078aeb617136b2b98c6241d5feca29b7cb80a7ad6b8ac254d8fedb62a69491ee11e5f5d1840a8608aef31599d84a995ca14ccfb27f7eba9acea5a918251bde453a1ee954b08a3603cf1f54e8b1a14cc39ce86e5d7f9fc0483b61a0d4bca8238e190259bf006a40986535fb2bd6f564ff01c01c2049310a74b7f76ef7cae41966092847e3dcb7de97fa64194600a276246f498902498f357f07827c9a6764a4830dc8efb7c1c1dc1a46f525c6f7c50af18c1681e522d8c2839e73015c1ac323a071b5de28d4430c6a8efba8e52fddd0fc1a4334fceddf5d5878560d6b41f5df766bf571204733c11da843a6942f44030dce5ccadccca6ee90f4cabfe73153bf829a1fbc0a0fae4fc7b1c417a6032e5f317e6043de141101e98f483099526277377116407e6135bffdbc4835b164174b085322d2457588a2f240c7a544a3f25a5f826fadd230c32dba6041d3d098f9f3b47183bd48e59ec85b7cbdf35c29c66a14a08b1aa2bea648471d6d3c7b8694982c7298216c028416411f6918a30e60915c6fadff4f2788930e8d2654ab55cbc31f1106110f5112754ecc509fd0e611ccf155bdf4bcf8c8d6708c3de98dcc9e3df8a2ac12b847d1c218ca95eb93e6f26e860d4b05103060e19798330a7e55714154d9b247f2708535ebe6a575341977681305c05f7dd5ddf8ff7f10061f450529592e28df707ada2c9d85a18eb92d7f2d1f220c3cea43cbd88f8c124f65ef9e4545e0921d207939c75a73b07cf41590731c207f3872bb9b2f3af88e8ecc1ec3958aa2486a6589c64b089e8c1f871645ec9e1e46f74711f88e46105113c984b09233c58524a8d6877309d9af1f8bab61d0c16f43e3c7e3ccb35eb60d47093dc47ae5abaa509113a98c2fdd44627c152d49f83c1b35b44e97c729768cbc13ca7bd7979b471307cbffd9d56090783d27fae960455e40dba99c6c9aaccaadd89697625bfb05a8288ac98081137183eccd6e4bade30e9b7c13ed4236c30fb5cb2de9392246889770d26a992f8ff257f35184f4a4196ca2db5a1b3481a70537331ad1173bbcdd4226830a75cd62d9e44cf60de123cc4a2b7d696680693e5186fa6bd8a94c1f0e5a9e488d08efd6c840c863d1d4a9d9cd25fc25286c818cc77d2ec86b84a5391188c716a6a746cf912ec240ca66f533a844a3a184cda757445498b279e141c44be601262d55277f75c561bf182f1938a1e23c7bede928b23d20573565f532709251be18249f0cd107b674ae7186dc164e1d2d55aceab33fb225a30c9916ac95b4fad11c982e943c4e849b288b9e41f0cbb1944b060d0b94a6c4e52929a14bec815cceba574112b186458f83119179b1ec58b54a1a895c62abaa6855669c5c837f5aba7741ca182f14a6b9e78934fafda8c84c8144c616e372ec7ab52521129984e38c9e4b55c6d498723513025db4cd1fc52d2348582293b33a4f607234f30c75fe7ac56da7dd50f224e30d8dde97dd2a5ebecdc4813cceff1536787fa2b3339c204f3979da57d2e3df1cc8c2ca1a08812cc27c529a1a624c9e45125c1543bcae37dd43d331d120ca2845162a5ab8cc8110c777bf9835b122fa5ce8c88114ce2478449561f2d0731094cc0813343a408c68b9e2ce90f675174878508118c15547928c1b38b0cc1b42588144fdb7b4acc63ac71062242309678267e4a90be732119448260d2bb24a876b2731c9731830f87a5200204b355763dab4a4fe407262578ac94fde492ff5368d8b8e101ec42c407873d3d33f28332d20363a509b1bd4a4a4e1131c20393a835153f5fae0d4476601c15948ae835233a30fa9a5842d456bcf8d9480e4c9b66629fa0dd54504f0407a67613d7e4246af49a30e4162679af976e517c456683d8a24fa9e40ba416661deb4fdbb67f75265a98b4099e539263eac50e4166611eebedd1c93b76d09185c184d3e7c1d2f4e5edb1309f1c42092c4cd2df099e2fe7786a9757982de6e514eee49b38b97585e59e844af1f25e32af1587adb7dc578a6bb1d41631bbf7c9533065b1c27c15c6c6528696d3294280acc25469ea24f171559893ecdb7925e8a4c27c79a93ea26254dc953a94f6fd8b0590539853de2e29a72bbfff92290c4a3b49295ee3545fb414e6edec766d12489b80d904eeea1c90f6204e5527020ea8c18c1c31b890516302f5b1fa6110f01c38bcb8518000d4004d400100c881c30b13102000cfc5f1828b1a6706020480e3460252d081057800878c2e8e0202d00900e0c0813e08208093e3241f0508c0c971121b373e0e008001342001375e067e72e080016301000800030e60ac38f3ae0e33c6f299176734fe068c2f64243092b7818c19a72d470d1925f01804cac87123068e19306024c04310080306023c0251832e008132727c2123f9431734dc0f07f0e803cac891d8a0e1050c1809f0e083c98288f07e17f5b1d53d98564e70b7d01e3d1894f7869d7dec2a59a3f134563df26070d191db26d8a8b9ba701a4f43693c0dc45143468e1b3f434617346c787ae021c7e3a0f1340ce07107949163460c1c07068c0478d8c1246fdea64927874cd73dea6014b1944fba5eedac241e7430b8575f5abf246b29f939986329f1e24b28d93eb57230bf9a5d3ce250b2f1b42b79391e70306ae824cfda492de9471e6f309594462b08932495f2a181ebe10653e78fbba35715450563ac212f7058063cda80ce1f3f28398c5b540627078c19e7ac051e6c3088180ffb1baa3af529cd630d2639fba34b928331d6b6021e6a30a5b4bd26a898ee114a7028073cd2601245674d13c4ffa90e3f3e7008c1030dc597b53c6562a7dce30c36f030c395c13ec870ad6338317018ce030c7785ca655dba979d6dede85c5249269e7eabc4587b191c14d8f0f88251dd4efd72b78c32255e308a5b8fb78f55504914048f2e983e69f66abff8bb29f1e0824909a567743479fc6b6381c7168c27d2b2456f71cb51d5c2a2f55931bd6b564ff9e88d7713c4ee910583fcc99eaaf5ddb88e0716ccf771b2f33dbf9a24ee1c785ce1f0496a1d932b556e8542456bd3d2c557c5d57ca69ee5ad932f7854c1709d836ba79e50c19c644b313b94b833e314cc6fd2aeabaff86875a460ae5c9e4b7c1ad1ec2b0a663df973c8311f0a264bbf21272ea5028f2798e4b6ff441de17db99fe539c160ba72580a7fbf2709c9e0d104f37712af24ef4f15ecf66082f1c45db00ab3aa173d5d82c7128c1e6c54c7657dbe42237828c1e44149426e554efc94f548027a2001cf051e4730ddb8c7f44fb2af24b111ec051e45307eb59ddca3abe7e2c687e0e3e3091e44c8537fceccd7368d57e03104cf3aad644ba8fa3709a1ac8e97042541e8eaea2e6d78a5d02d3deba673cea13ce50184f5f881f94c4527a54e849b8b1e3ee8bcded2b344343f8e89631d4f4f25c6dad7b81998ee82867bf4c0245a177e540e83c18307e6d6d172ddf7bb73238f1dd8470d0f1d182d2e659b8c8d12d6f7c8817d78e0c03ef416678bfba8458416c6f22c263dcb9da4adcec2a07562da63c57afa5316f64123120b8585e1444f1b162fa556669157d8872becc3059156184e4c344c0e79841595588a5517ab2dd69cd2b3e079b7fe22b20a93877b497aeb8ca8c2d4df5ff949d653bb4b85f1c47ff5e92b820ab327134b6d07a15318b44e304b734fc414e6d1b72555e543f4f752182c9a3e53fa7d3f6c8c90c270f22de5a024c1844ba25198c24d4c2a21e74fb89f43c6f920220a83054b424789d4930453241486ad115baac2fddda423a0309e1aa5199653d9c51df984e9b3f7e32c684f26ab114f184f734476344148ff3fd20963bc55b9ea251d3bfa08274c2572a71df224d32a463661ce525efaa944e3eb8a68c2a04cd6df7811213f7432616e9354b4362915c18449f420972f27a14b20b48c1425689f6ec83824f8f8d044c4128995306585eb2476f696f88f12264177f6abe724b953bb9b84e104a5255c5787f5b8ee24613853274c3bf64e30ed2e1266ff589df428854aa853b75196c32808821808422086e56b920e8313503070401a8dc5e271891ee9fa1300418ae2e07030128d03e260481c08410cc5300c82200cc5200cc2300c637005d979bc62f63130b36589ba7ce7d9e5d1ffea47b4f9655e35ec945bab29af3e23582b974ffd70e05fedb2f3ab5a225fb162270aa868e2bd37c31c2663ce99616128361c10c6189d2c9370d89adec83512183e68126746eb68fb7ab4fc3dd619fc36c34b9af84f34e5c6cd4490ef0b0d8d962b530608cc16214b72459ae77cb9690d418b3d870919ac409a28199d9720a235191a58dba83f9f2814a7c9d460f19783e83e2b47e7ee75481c3a29ae359d27f4b108ead259242f3342155c8f4482da07897c9acf2540d73f748c109a440bb18b4f717de41812994799adfdc8571e7a9805ccab9d0c9a604511c0fdade1c95c583460d4fdf1b3d1f5cbd96a79686106babbaa2bd0f9cf706cf1db7a5165a52454a1d4447e8b82c6f816636e9328dba0cdee2f3e8722b7e62201b611b10e85a597eaf3a9d1cd36558b5fb84c70f68b38ba942b49f3549c6d58a16987a247bd7894ad1b29c3e12ac290baaf36dafc3fa79992f4421ab7c4cbee943ca64cc3af2e11c46aab63a5ee6988ace3f7eef0faa488c7c54d1debfa0f67776d11c8612f93b296d9cdf1132d9c1a0d733dfdabbd34ec0389553de28ed226bf53312ec50da6c6ae8823fa6ae1077f8bf3004658bae8a612404f33b1ca3f4c6d09d316252c3647101c3ee0f340a30945dd1a1fc8210ce8f7bbbebd23e01b60dd28f9e0eaccb0f072fb86561074ae4d73012a1cf81309b85d963591bbf0a274f40032fc270debd1d4eacfcd79528074251a0cd7173a53353b2bcd9e2a2128ffc4881001ed63ca6efb01b90ea5553e0031de199944c3492af3719a43026b5e368fa41d96daa1b22b493f722fb5247f09c930862f32d68e16c56136352b88da9196e46642207117e23ae64b48cbe1155c8b8ade35785ffae8b5707524cc19ec4c915f4ca13499fd526c93d2d342f832563717446e8979742c6b9911d6367a8eedd650d746167f409f2ec120945b1dad143da00af9b0437c00cba6140347c088cac92549894713fbd4ec03447e7dade80ab7a366242a71f4c27b826c3bf6412cd4d62f44fe1a9f142cb3df33358ad016ff86f3730885f12c108f6f0ffb769a2ea424e68ea41ecc8093fa76d880e81f0c240f551762285318bfd3b7836ecafdea967cbfe0bfd07a83293610602a056e0325df26732b78e44071415cb607a1109d3f336317c911e77cb475cc5712c75277430b6261843acb318c4150a8bb49c3e4b8018b791175ccc7d3d03595aa33028bd7af6af67e3b1d2713d1b3a56217e0e287ccec3f24031591178dc550848a1163e17781dc7186303b7654f184e4aa8de1aeec7f4b2f84c7b006149ca47ced69b453aee3c35d91d493f792e84277f872811c000e61df99834b9e0f6759468b2417e4ac1d52cccb9f596ea12b7cb9c0b7bb872a023a21434e2896f80a3b33f4fe1e8b40f03b0e7c20fb8cf64e32b32fab86b4cfd18c7426e65f3c139ea0a15191ab53df5ff07912530e052635f6b0d0308a1bd2f824c775096dd5905f506ca115a155807829cd8b7f5f66fc44c44b221308b1c8613534615acafad3d6631dc34cc9fc4a2086cfa2ba232e40732a4ec93fce6d1e0254078f22f2fa74a62cfaa69b13aefdc2fd0a4e9e2d2077d03ed255644f2ec34fbba5f0efc96502d2b6bf464833479a9fdefbb8c4ece33e20352e8710b667ef796e1fff5b8177278eb97c487c26ec80084a0381f3ba2c4a505ca52c56622b4230c2f54381107046f82993d725428c70d5799c44f5167b3b319a5a5f5e45be28fd574540e46878e433a6ca1021481e7e03275bacf10d0ae107a8058c408aa2f4ab58caafb1961880f794a7feb5bcb59931fdd16c4b6b612093b3993f0b41c1d4a2b175d9cb4aa568534065fe604fa673b61bea5622ab76ce0334e77c870a026a7d74eff068fff6b7d3ca28c462d69c665114f1c78abf040df6e4cef31bcee4e4bb9c62961e4b2a70b592acb5d152a26c9bab4e1168cb27fde9c174e9853fbc11a92bfdcfedd69b004b6fab98f70b397438080bcc61db579a24ff529af59c64735ec483cdf9cb304a5562910303b3226a71df9ff171369c1c6b0e110a7c9fe52a7c4909340aa065e1b96e21fa661c51c6e4464044a1b202756f2adc2bb366612925ca81d857a2e7a72ef57f72a7e5144ef4019d17eee8d98c0b5df9e75cef1db150720f755ecf95ea614b4bd2fa6262406ec2283314730081c43bd3a982b4c5fe03b5a205d41e8e0520127cb650df57e75725a9f12af4645f1f3f2def1571153997f8858cc67890a5fa3d0be27f15444984a1a6090e5c81f89083ccefce6356253eb3c675157faa8ccc1696cf0b2d52816d3568f0dfd8f5edc39f4afe85259e10129d3c86edacab29cf52a5c40f9c417a80c7c45782737bcc8b2e4524deb942c328035b52a22840cc104fa33fc2a0e9d4f1104df37d483a30a7dae084d405f424f7ac98b2dfbe00fba44bd0496e05f88ffc4519b28a3b7c7531c928d7a104357098b80f249f454b6426c2b291084a7fe1d2898f7edfc988684b098b8cdf473467a8adcf4ca7f279946c302238ddb39585d64aee16ccf01d34d52cfa30b3ea19c82295dd17b790d64ecd54b88300e7380b756d1c1dddec34175f91cce3c861a6a804941f2b4c8e1e123211dec5c105ad05cae072402ce47debb0969b7338a9593995990bdf2c56438241cb4d5719074096461cf2c02b6e9eb71502ea9378becd5f11b8fa4bf0b31306784665f13f69391be27e12062f1a0ab4e621791ebc9bb5f95763d1dfb940053910a9c51e792dc1bc71190ca7286ab62045d90449a4601286ff5391c076e8290bd93631e52618b641e0599ba6e9507cc5777b72fbe0c4889a1495758a33d0ce825294a36aca2c4493b2d1e510132480f4b2c21221e1a545f87e3f2aad3608d59f554c9713bf74e9cd2857bf4f0ef5121ffe7d8e34f5c0765139745447d23822236329d9dcbc3e313d522e612946a4ab0d65b5c81a3ea1188d6b61dde1da56d69203eb7d95dca060d02dd6ab89e336a984bef70176c5302f4b007f50982a7a37ff800d753b25674c14fe514f116847504991189561bc39425d2de0a353a43381211bcd401182508d64d5e99aa8cdb1960788ce4e73ea04e0d34cae4f904278436926015f1efda4fb6a4a6b7f24c6ccddecec9cb53eddf8adc55fb51f38a84fedfbfd402d241ed383e097338465cc006f9b8c2c54b83b5fb11e2ebc0fdbb6bca99dc3514a489bfea47b95a96dccae4532096855db5cfa9ba136cae012a03c74205446b79eb7bad806452a9a317fdd9e1c49ae80f7412241c6d2dfe5b05983808e511455ec09de62dbd40a957cf8062935b30013698af69884e1d6aea6b6b40325bdaaba115bd538301c7c67f19f2c79b05e1f265399aaf61e3dc2dcc106df34032dd935c2d3a44603779f8973bf1b08a0e1172420c532ec1e8e2a4997ec2ef58559c8391d837eedc5e1833e96be19c061cfa7975f27976df4dc4adc315e2c8455910e8752a6b0df326b0e51d4be038d0f808f53ba94fd3b5d7fb31d7fe5c1fa4cd08e68451d436497b12a34e2f08bb7191c7607a64871e03b70c11f4f95d6b2ae163d36d2edf20bfa14f19a3dc97e7d7f421a41b7083da1ed9ed973019202d6a3df08062e41100202578d21dbd233641079513bb2c1114ddb88eecccf278965965f11bf4292ca1bd89b68cb1096b11ea23d828c5226c54b2ac7b1dcad418c6e8e5dc0f8a66caaaa3020617f6cb970be5ad79723acb3e94c29a9e083745843c0c433374a610837bfb287c7e81f71092f85de62da004e7df15187f3dfe9170ba10f1d6368936e025758672b32f6e6f63aceb840bea38c14224b12b98ade25d6ce5a2a7dc4d7a9cdf15059682ea533112449b1ddf639b7c7ad38798e3029d1f85e8246b82f117fe920160938909ab37221eb4f221cc5d61dc85065dacb58970948602ee62d8d67b1568e90a882d8f302bb492d9e8d6f356255e938b9ee3b3b2690cb4a2fc684a85908520b6380459343d4bb13bd515d13713dc6d8620e230369694e03b945a01258811292adc93093fd5c9ce59e7bc2340d1850f46998ed94fa687d75dfb717e4b615ec8768e1ac8567dd566d87a3a60f401cc5dbd906ea176ce7663c050d0504d87ec4351f051414d51485b7a784dd251fc54ec61065b76a129ddbd725a465a4670b40b4bdae02b6207a49950094812e368cec46b1007e34ab8d25a957cda15e9a01c9740174c2252ab0c9fee3cc8ef5a63ab75e66f4c92582d6e045d3cd05b40084cab04435ba291a1798b49d53ca7bcff080ef3b72e0d0482795af2d5fcd75190d9f2a95f39e05592188011008bd8627b6fa2437d943024da2629f674a1453dbe14020fbf2c7776136dd083178a070459c198d58b3294c3312138d375657fc22e9e1f0ce3840f64d402c0e694381eb95901d33cb95a2670323761d7d94472360752625fb59167ef1236b07115464bc86cdc625e165b1c42665386302019c3d333d8ee701b3fbb382902ef6194c3629919599efa64016aeb24ff80be1cba9450e7cbd5ed909a2d50e83db096a94ce681a3954073e5a0649b7ad3e04eed4be2995516f199008ebe36ba6e45444061fb10084521211aeee0de2647a839c64739802987538a9e7c8c454a85d3b67c0a648f36700a81b5de56254b35e44bf95c19ca62017b2355c029e1d4eaa6524139e32ac8f6d02987d3331ce73d4401bc672686e31ad570f36c5c8bf611042c503690e31d14c4bc1193115f6d003daa0bc29ba08099bf21548124adc09d5ab4b7d8c7a0010155eb6ca7f75ef2950154da89680404e50084d806c45d63891aa6ce3412ff96eeb9336ca17d0d6f350f925f91bb262730b3255a91f3ab6235b124a42ab81e3ecd1e2efefd3163cf2798874b3dbe0fc7f898bd9c9a3ab9494bf8214c0cc6f90a339a43fa8c8cbf8310b0ccabc66323a5e8d9b3838663282b27df5ac041f764323d4b5fa8a5cf9e6027fe1f85d11b20c4a8bfda294453132a311026c49861a20c9d31abeb2b2a8538580f522820e86195d7aa7acc39213c42a1d64acbab3146e22b9f4f476898b18647dd9529243c95edbb07e1a695f73a9df83ef16772109a26a38fadd619d4a6a0c0f0932500e9d868a481c061f0765dc3919ab40b6d274b8ed680d93261225397dbfccfa33ab1aa60bc1fec8acb6b0d54af50632da13523e378cdc0838d282e6a2fbb091ccf009a8ff16136de1945afcba4b97455f8c3b28b1eac3ec6558d0ba13fa978598b01241ab0df3a92741c56e16b495080fba5ab33b2eea3d18fcab3cf8eaf7284a6fa7e005afceb79bd5f6edca8c1600c3687d6e2f9b801007b6be708055e1a941c3edc5c97755b8bf97e46489da9033c9217e20395d3c788ff57308cea45a11ed8ce4222a7641c1f5da012d892fb6009020606bc3a4b89552b6bd6263a724e398b49e31389445f4aeb814", - "0x3a65787472696e7369635f696e646578": "0x00000000", - "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", - "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0x52bc71c1eca5353749542dfdf0af97bf764f9c2f44e860cd485f1cd86400f649", - "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0200", - "0x88fbb13c02428a6ba0e3c362f503d78c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x9ba1b78972885c5d3fc221d6771e8ba20f4cf0917788d791142ff6c1f216e7b3": "0x01", - "0x9ba1b78972885c5d3fc221d6771e8ba24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", - "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000100000000000000000", - "0xcd5c1f6df63bc97f4a8ce37f14a50ca74e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3401bcd1e9f3885b9b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0xb0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb381d03c816fe51e89926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ae9e7a6969af6726a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0xa8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d6668b8260aeead3b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0xb8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575", - "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500952b0337fcbf1d46175726180926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227": "0x926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195017c489719c28aa986175726180a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20": "0xa8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195026ed82a0e5bfb6c76175726180b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34": "0xb0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34", - "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19502ac2136394fc85866175726180b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575": "0xb8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575", - "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x10b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b8d9d88d2b1318a098588380c0bfaf43fa93881fd212f9c70069665c3f6b7575b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34b0b0bb7e1c48209d1c515c54dc6b106e9dc8764697c67063a3df2f1cb9abbd34926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227926bc7fcc360e0e37ed3d4527e4c55d448318bd2fcc17bac149cdcc34b37c227a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20a8d37e9f0fb7f832fe8ce4b130004e19dc201f6741d816b9dc4108b0805c2d20", - "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", - "0xe81713b6b40972bbcd298d67597a495f0f4cf0917788d791142ff6c1f216e7b3": "0x01", - "0xe81713b6b40972bbcd298d67597a495f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xf7327be699d4ca1e710c5cb7cfa19d3c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" - }, - "childrenDefault": {} - } - } -} diff --git a/cumulus/parachains/chain-specs/collectives-polkadot.json b/cumulus/parachains/chain-specs/collectives-polkadot.json index e17958f1f68324ce33b16491e037e7035baf787a..259669cf37a0643534eaa5d1776daf79ec402d75 100644 --- a/cumulus/parachains/chain-specs/collectives-polkadot.json +++ b/cumulus/parachains/chain-specs/collectives-polkadot.json @@ -5,12 +5,8 @@ "bootNodes": [ "/dns/polkadot-collectives-connect-ew6-0.polkadot.io/tcp/30334/p2p/12D3KooWLDZT5gAjMtC8fojiCwiz17SC61oeX2C7GWBCqqf9TwVD", "/dns/polkadot-collectives-connect-ew6-1.polkadot.io/tcp/30334/p2p/12D3KooWC9BwKMDyRUTXsE7teSmoKMgbyxqAp3zi2MTGRJR5nhCL", - "/dns/polkadot-collectives-connect-uw1-0.polkadot.io/tcp/30334/p2p/12D3KooWPrJ9VTn3GEs2e7GQs4zoEFiTFcjXFNbQ2iDxFDQAbstQ", - "/dns/polkadot-collectives-connect-uw1-1.polkadot.io/tcp/30334/p2p/12D3KooWGFYW6hJYn3pkpJawyMk6souXh7sznK8yvPmVQ7ktfHbV", "/dns/polkadot-collectives-connect-ew6-0.polkadot.io/tcp/443/wss/p2p/12D3KooWLDZT5gAjMtC8fojiCwiz17SC61oeX2C7GWBCqqf9TwVD", "/dns/polkadot-collectives-connect-ew6-1.polkadot.io/tcp/443/wss/p2p/12D3KooWC9BwKMDyRUTXsE7teSmoKMgbyxqAp3zi2MTGRJR5nhCL", - "/dns/polkadot-collectives-connect-uw1-0.polkadot.io/tcp/443/wss/p2p/12D3KooWPrJ9VTn3GEs2e7GQs4zoEFiTFcjXFNbQ2iDxFDQAbstQ", - "/dns/polkadot-collectives-connect-uw1-1.polkadot.io/tcp/443/wss/p2p/12D3KooWGFYW6hJYn3pkpJawyMk6souXh7sznK8yvPmVQ7ktfHbV", "/dns/boot.stake.plus/tcp/37333/p2p/12D3KooWRgFfEtwPo3xorKGYALRHRteKNgF37iN9q8xTLPYc34LA", "/dns/boot.stake.plus/tcp/37334/wss/p2p/12D3KooWRgFfEtwPo3xorKGYALRHRteKNgF37iN9q8xTLPYc34LA", "/dns/boot.metaspan.io/tcp/16072/p2p/12D3KooWJWTTu2t2yg5bFRH6tjEpfzKwZir5R9JRRjQpgFPXdDfp", @@ -24,7 +20,14 @@ "/dns/collectives-polkadot.bootnode.amforc.com/tcp/30335/p2p/12D3KooWQeAjDnGkrPe5vtpfnB6ydZfWyMxyrXLkBFmA6o4k9aiU", "/dns/collectives-polkadot.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWQeAjDnGkrPe5vtpfnB6ydZfWyMxyrXLkBFmA6o4k9aiU", "/dns/polkadot-collectives-boot-ng.dwellir.com/tcp/30341/p2p/12D3KooWDMFYCNRAQcSRNV7xu2xv8319goSEbSHW4TnXRz6EpPKc", - "/dns/polkadot-collectives-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWDMFYCNRAQcSRNV7xu2xv8319goSEbSHW4TnXRz6EpPKc" + "/dns/polkadot-collectives-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWDMFYCNRAQcSRNV7xu2xv8319goSEbSHW4TnXRz6EpPKc", + "/dns/collectives-polkadot-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWDumvnNwPbBg5inBEapgjKU7ECdMHHgwfYeGWUkzYUE1c", + "/dns/collectives-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWDumvnNwPbBg5inBEapgjKU7ECdMHHgwfYeGWUkzYUE1c", + "/dns/pch13.rotko.net/tcp/33573/p2p/12D3KooWRXudHoazPZ9osMfdY38e8CBxQLD4RhrVeHpRSNNpcDtH", + "/dns/pch13.rotko.net/tcp/34573/ws/p2p/12D3KooWRXudHoazPZ9osMfdY38e8CBxQLD4RhrVeHpRSNNpcDtH", + "/dns/pch13.rotko.net/tcp/35573/wss/p2p/12D3KooWRXudHoazPZ9osMfdY38e8CBxQLD4RhrVeHpRSNNpcDtH", + "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30526/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff", + "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30528/wss/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/collectives-westend.json b/cumulus/parachains/chain-specs/collectives-westend.json index a40512997daa546f7abc76e45cf8d6a3f09e735e..e459c631f8be9df7c5c52993c116f11ef619fefe 100644 --- a/cumulus/parachains/chain-specs/collectives-westend.json +++ b/cumulus/parachains/chain-specs/collectives-westend.json @@ -5,12 +5,8 @@ "bootNodes": [ "/dns/westend-collectives-collator-node-0.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWBMAuyzQu3yAf8YXyoyxsSzSsgoaqAepgnNyQcPaPjPXe", "/dns/westend-collectives-collator-node-1.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWAujYtHbCs4MiDD57JNTntTJnYnikfnaPa7JdnMyAUrHB", - "/dns/westend-collectives-collator-node-2.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWP4pJg6pZUpxETd8Rs6GmS9FeRCeNtrBerqZhUyEPCiPp", - "/dns/westend-collectives-collator-node-3.parity-testnet.parity.io/tcp/30334/p2p/12D3KooWBbrBYhXxFXhdio3AiuaqMG26pn91SUnd12gJiVn2Wh8Q", "/dns/westend-collectives-collator-0.polkadot.io/tcp/443/wss/p2p/12D3KooWBMAuyzQu3yAf8YXyoyxsSzSsgoaqAepgnNyQcPaPjPXe", "/dns/westend-collectives-collator-1.polkadot.io/tcp/443/wss/p2p/12D3KooWAujYtHbCs4MiDD57JNTntTJnYnikfnaPa7JdnMyAUrHB", - "/dns/westend-collectives-collator-2.polkadot.io/tcp/443/wss/p2p/12D3KooWP4pJg6pZUpxETd8Rs6GmS9FeRCeNtrBerqZhUyEPCiPp", - "/dns/westend-collectives-collator-3.polkadot.io/tcp/443/wss/p2p/12D3KooWBbrBYhXxFXhdio3AiuaqMG26pn91SUnd12gJiVn2Wh8Q", "/dns/boot.stake.plus/tcp/38333/p2p/12D3KooWQoVsFCfgu21iu6kdtQsU9T6dPn1wsyLn1U34yPerR6zQ", "/dns/boot.stake.plus/tcp/38334/wss/p2p/12D3KooWQoVsFCfgu21iu6kdtQsU9T6dPn1wsyLn1U34yPerR6zQ", "/dns/boot.metaspan.io/tcp/36072/p2p/12D3KooWEf2QXWq5pAbFJLfbnexA7KYtRRDSPkqTP64n1KtdsdV2", @@ -23,8 +19,15 @@ "/dns/boot-node.helikon.io/tcp/10262/wss/p2p/12D3KooWMzfnt29VAmrJHQcJU6Vfn4RsMbqPqgyWHqt9VTTAbSrL", "/dns/collectives-westend.bootnode.amforc.com/tcp/30340/p2p/12D3KooWERPzUhHau6o2XZRUi3tn7544rYiaHL418Nw5t8fYWP1F", "/dns/collectives-westend.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWERPzUhHau6o2XZRUi3tn7544rYiaHL418Nw5t8fYWP1F", + "/dns/collectives-westend-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWMAgVm1PnsLVfxoDLCbYv1DgnN6tjcRQbrq8xhbwo4whE", + "/dns/collectives-westend-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWMAgVm1PnsLVfxoDLCbYv1DgnN6tjcRQbrq8xhbwo4whE", "/dns/westend-collectives-boot-ng.dwellir.com/tcp/30340/p2p/12D3KooWPFM93jgm4pgxx8PM8WJKAJF49qia8jRB95uciUQwYh7m", - "/dns/westend-collectives-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWPFM93jgm4pgxx8PM8WJKAJF49qia8jRB95uciUQwYh7m" + "/dns/westend-collectives-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWPFM93jgm4pgxx8PM8WJKAJF49qia8jRB95uciUQwYh7m", + "/dns/wch13.rotko.net/tcp/33593/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", + "/dns/wch13.rotko.net/tcp/34593/ws/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", + "/dns/wch13.rotko.net/tcp/35593/wss/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", + "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30529/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn", + "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30531/wss/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn" ], "telemetryEndpoints": null, "protocolId": null, @@ -107,4 +110,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} diff --git a/cumulus/parachains/chain-specs/contracts-rococo.json b/cumulus/parachains/chain-specs/contracts-rococo.json index 09108e9c0995aa2abd52277eca6572c62fb2ec79..422268a5efdb2f8efc96da27dde92a92b11ac9ab 100644 --- a/cumulus/parachains/chain-specs/contracts-rococo.json +++ b/cumulus/parachains/chain-specs/contracts-rococo.json @@ -5,12 +5,8 @@ "bootNodes": [ "/dns/rococo-contracts-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj", "/dns/rococo-contracts-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh", - "/dns/rococo-contracts-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWEVU8AFNary4nP4qEnEcwJaRuy59Wefekzdu9pKbnVEhk", - "/dns/rococo-contracts-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP6pV3ZmcXzGDjv8ZMgA6nZxfAKDxSz4VNiLx6vVCQgJX", "/dns/rococo-contracts-collator-node-0.polkadot.io/tcp/443/wss/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj", - "/dns/rococo-contracts-collator-node-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh", - "/dns/rococo-contracts-collator-node-2.polkadot.io/tcp/443/wss/p2p/12D3KooWEVU8AFNary4nP4qEnEcwJaRuy59Wefekzdu9pKbnVEhk", - "/dns/rococo-contracts-collator-node-3.polkadot.io/tcp/443/wss/p2p/12D3KooWP6pV3ZmcXzGDjv8ZMgA6nZxfAKDxSz4VNiLx6vVCQgJX" + "/dns/rococo-contracts-collator-node-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh" ], "telemetryEndpoints": null, 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/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json new file mode 100644 index 0000000000000000000000000000000000000000..c79fd582348b0223cd4c1de71b94075751cfdeb2 --- /dev/null +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -0,0 +1,71 @@ +{ + "name": "Westend Coretime", + "id": "coretime-westend", + "chainType": "Live", + "bootNodes": [ + "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "WND" + }, + "relay_chain": "westend", + "para_id": 1005, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xed030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000008277f47279c4", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da998af9d0b9c163cc7caff71526e1e20bf4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d834c58cefa69bb125bbc8bef14e90eabc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x66763d0040636f726574696d652d77657374656e64", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058544104aeba85d4125110284d291ddb4a3c50cb1bd0b174da063483a55a47260ab964ae54d7660813796247c0bec7d0f1b338b9c2280c6160568412ae63c3a7815a544782b1d1ea96f47ff926f8d0da2664efbdf7de5b4a99640afb1340115a114f7dbbfcdd2d91be3a3843eef26f979f4522f5a9bd54f4a9d7cb0d9bcee28fe9e3738e1937a9166f6707e9b5f46edf77febca5077e3ac8b3bb558280dee92c0ab13febf7e59ff5341265ce93916b5180787e58439561514395b06f6fd71165cdb70f35547f3b778f192b9116a00609dec11fd56143b1b7f3986840761ad9a7b3d7ed94523adba39a76fbec6eeaddce22ed7e7af7937c0d995e7d7a25abe653b2368cc9727e93533ee5800cc2c835da9e1f1baa6b28aed1f03c7bd850b307c7caa167672626a7fc39c76c4e0d40be609ccece1ec80d35793e3be8e286e267e7866a2fbb67af0d359d1d9c470b50238777f083339c773e8bed6087d48f26a03256a73e9d8a3f26c933680169fc348cbf9d5e6e18759086f3ce6fb19dc54a72c3caeea947358b3f6ac3a84775544775544f9101b36f67f1c7f4a88eead99c330c90994a7fea20076004d982911b568e4f7d3a8b3fa64f110809341a2dca4f073be4869ace5e1baa9d9dc5ae61d4a37adeb061d4798ce5d0536faf5e764f1d1c6a286e183bf5a2869a4f1da9a1a653076758d450fcd4599c1ed5515def93a57732fb265f43ba6fefbec9d790f6eaed95ac9a3fc9da307616854c6fb26c9f1ed551cd4c949cf2e71cb39e330910640e5a401a3e0de3e786b1b3f8a3368c3daa19090499831690864fc3f8b961ec2cfea80d638f6a4602b9416eb01ca7738d86f5ec2c823cd38c5c3be3e727eda9833cd38c5c3be3e727eda9832e76662a27eddb41be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723df51ecfe34bcefa92bd3d8af2e5a79779073b3843be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723d75bef33cbee4ac2fd9dba3285f7e7a9977b0831df29d6fc6e9f3992d609f22953f7516a9c6a72452c3a647755447755447355f6e183b3335393569cf396674f61d70ce19a773c3d839041983713a3b7b4f564c2cd4c8f33d9df1f4e7c9cf139ca73bb0219ea27872f3d4c4539d273a4f443ce1f0b4c3530e4f433c31f1f4e66989271d9ea078eaf234e609cb0909272d6055386501b3024685531930269cc880d581110183038be33405ac0898113029606d6039c06e80e1009b02e6c6490998164e79602cc0528089719a03530186020c4d530db025605f602bc0c2c080683ac1a9052726602748e00706c6c90c9816a7349c02358dd1a48593144e6a38ade1c486531b4e4fc0cc38dd716ac26909a73a4e4738e971f2e324c7c909273a4e28387d71c2d3c4a7890b98104e6838d9718ac2e90427289ca23851717ac1698c130e4e679cc838617162c12907273330139cca38c9e034c5e906a738b019607a9ac86832a3098ca62c9ad468c2d31482a63e4d5d3479d1f4a7e98ba6329ab068b2a1e94b93094d5d9cb434a1d02443530a4d58b01a9cae9ac0346535a191a00b09c468b22241194d5b48f0c7298b0461345d493006cc8b045f34016a4aa3098d26319afc4840869394a62a4d3134e5699aa2e98e930d4d503449d1a4a7694b1315303c4e4338c17112a2c90aa73730289aae34618145e1c4a7898a939ba6354d55c0bc70a23915e164048c0b5809b02d4d7b605d38d560779c8870fac1a98fd316b03c303f4d6a9ace38e9c00488290d263098ca601a83890ca62f98b660f282a90b262e98ee30e5619282c908a6384c3a30e5c0e4860907261b98da303dc174039317262c26354c6498ba3095616a81a904261898d630b9c0f4021316262e4c26305d3189b95f7c0f530a4c3130c970694c3530cdc0748509858bc245e2cab94c5c3a77893be7d6ee1057881bc405e2fe707db83d5c1eee0e57879bc33de21a718bb871ae9d0bc5dd73adb84ddc3a97880be7beb94adc2a2e1557cf9de24a71f35c3cf7ce8de2267167b8692e0bf7858be6ba70cfdc3297cc6de18eb95a2e1707744fb85b6e949be576b9265c2cf7ca03f9184e866739155e854fe17a5c0aaf791e27c2e3f81b5fc2e1b811fe83fbe03d4c1d669c69c66370357ec665f02b7ee5519cb614c6929ea526969c587a620988a51f96725862b394c29218df614985a51296b82c6d59d2a294a5b486d2194a642899a154c65214253f4a5f2881a1d485d21e253d4a6094c228b1a02446898c920b4a65945a505a41898bd2094a584a5e944c58f242c98a5215a5ab243594aebcac78a9f14ae3c5c64b8cd711af35906078055a1ae365c66b8cd717af3290c8bceebca27849f182e285e745c64bce0b89579c5713af33c0aca23cad34dc9ea324849e38a223d4c411124b593c09f184c7688a9215444d10915164c5119a2335de134a583c3d71b405c626e47274550446384611202317261146568caa14f131ca62a4c5088bd116a32ba3128cb81c19717404d195a23ddc15474514e1196243488c100c457d8ab2f02a447f96b03a1a96bc049109a2419c23d221aa82e807222188862082822808223b444010d588b8200a811702a52e2d322d168c6430c2c1ebc304c6d3e2ca706330aac1dab168381a426588b08868445e8e6a445b88ae86b284c21071212ac18333d4c6119c232288a61061116241e805a230b388a01c826a1042230485d00f426b5a68847c10c20aba21888720218286084a130443109a201b82ce04ed10e4264886221d8a70081293b446919b2124b818382a5c6f966018925314c6e6d9a6e8d67c736e6cb991c5c5e8d204e9b004a66313c4c2b7449118b72248ca4867cc1a9918db18e78c4a8c498c7ec6268eb25eb451cf9865d47264e568ca1195a32a631ea32b4654462ce3d558c518c518659432d2c629aeacb1ca48659ce29a19a93052234425c887a21b9ebc28c2228c4224056c83cb82284b51152d354266845c408078159211aa110212af846d885dc412442e2216518bb845cc12061269e119e294300d314a58862845b412b2215209d708a1089f08ed844e84688459a1192296582544214c21fce242c29584501dd71ca41590c020a9809402120a485f904e403201c90b12165217a41290b8206d41d2829405090bd215d215242b485590a8204d11b212d406d1164c663a1b9a0271575a7f5a675a6b5a34b46a50a2c20886a440621df72332114407c795d315477598d2f819e2124251969208ca0aa708d9790aa26b012683530f620c4e622c4dc1c404531da624c23c4c48846088b48454c22a445688aa10f92024035197d04a2885a88790361448088c500b476f846268cd20b442911e2132477184c6bc9e08af6041845796c068d9b0e4e70617ee4c510f37b418d960a31877187910aac2c9f049b1f40248c35219904d50202129a30edd0ca21ba1294d6138ed6064c6bf30ca6189cca7c607e83b23a807a23e45360495099a41c8093050eb05212e2e2236295a654638de0c482d787196f87844e0c0b224c6cb09a336dc9dcd088fcc9398a22994c6006b00db80362cb910c4068705d319a53698c460f2b334e5d3a3848612a0a0183822884c98703a3845528851885088764427c427b819bc2fba1b96c61051e1b4604283e373a914515174055194a52f96bae0d0883d0889c16486d0198e0db7a669ceb5b259e132420805a21d8878f0deb46410fa227442d4b6c5b667fbb389b1f1692db15de1416c58089920a482cbd9b4104a61cb626271218cd62cb9712c82d6b4ae08d1827e0852639406a94c100e4c5a2899095ac18f083242298ca0165c6d2c140e84eb082616967600cf00e5b4c00455b164825318dd1c5896560b5bcd2886a6283a3a5e98ed8d111aa333e0114b347862c612bc3162134a3034d9615a2108081007223a4c6896cec0b06c15d78b1804131bbf41fc42498cae8d77c5e362890c3b05ac07ae886d08d00ca72eac1437be386161a443b744501bd70f412ec0e6785bb87c087aa1b5c56be382e35d8d3d34b5e134466b05cf8a088493174d337834387d0173628985221496ca80fd3004674909d18f52154d59b63b4e3e38f1e03934d1b958ad314c2a0c1161e4c66d68fa21c88c111babc76806a52bdc4aa7c67130ba012ca3c8078fcf520bcec3a7048c872636416cbe2444219ab8040561f120a971fa033be304042c86262d5e1738b27435048df1fc783534a5697202b6a6e88b110d4c67c0d4785e582aec9d5bc57570374a250451318f984a4c246612730b14d9942381a9094c1b924c34217cc003554a38108139c3dce1010ee02001441ab0010316a08020407e680000d8a4338dd031810f02ec78a260039d18e07c9e4170bca6524e9068c0880848a4423062049cf4832552468c7c3b76bcc6bc5e621884241b58c244aa0a8954130f9c247122b5442a08d32b8c0746986c608910254a9820a0ca0329254a98208082d70a3c5e60942841e28112254810a0420855202c915292024b496dc008932a235521809044aa034f2f14d889922448aaa43e302225b501a7d71766b28125498c78a089d408541e40c281bf4e60290f963059620489912422bc94074b9834a06ac9084c3cf0e0935e267cc0c403a90f965e5ed88907a4a68c3871e201a9214b982471e2440a02f785c51d4822c237919a6ac2640244eeab0b1b911a416a040714f9400a04109014a1e055023b99f2402a040fb8b093aa25524da4425892c45f5b9624d9c0eba5853d48e26449922655244819f1801211444062a489d4074a928c109bf280899218d22b0b3b397a61612446905e57cc012348aa907c80a44a6a83a0d7950f245182244955922549885e5646f0408a0429a157156ec20401e18b0a2f6122c208463e509264040d185982c403234944f8aa25521c00410a021f487d3082120f9624591201244d4678a7d714762295a42a49132621548d80e4c64b0a2f6192a40a4993113e3052c544891323494478244d4678a2560f3ce50113251a30225585e483244a903c69f19452a20124554b46d880912a0fa49a24f960491223468c6e9787db93841a3bcf33906b34dacf1935daba31ba31a60893ab2f8ee338ce5faf66aeb6d9b6d74d6cd9b6e759ef76b3b5b5d26a69b7672ba594d6ba44b9569e9e67adc7dc2d669e3cd9329dddade6f6badbf6b4cc936d579eec596bbb5a4aab6d6bd9f6646bd9b265ca5c2957a9b55dab999976dcb6d932b36506bbeea363d7792dcab69d687bb57ad54eae75a3bd7db5daadebdaccd672edd8b2b595bbbba910b6ddad00b66c39cbb6369d6d2dc8cd69ad05396b9939e656c7d6522b766deb51d7b5abd8cc4df986ad5cd74aada5dc9452fa715ba6b4d66eca93a765b66c6dad96d9d619a42e55ae4d992d65e6ae636bed9cb36933536edacd6c9932dba6b59b76b4e575f41bbb6ddb6aedba6da51fed5a2bb3e568a5cc5c7976f735c9dd3d5ae6aee35a29e5aab5b67274d25927b5b5da5a69edbeb0295769d7516e6bbbaf56dbd5d64a9bda6eb6d572b4a394d63a9b562bd6a6b569d30a93a0bdee6e8ea3d4923870e0c0318e5ddbd66a2dedacadd6327365ae73f6e4b6d572d759bb76edb6d6325b6666ae67dbb6b6d666daddd556e6e6e65abbcad56abbc56e55cbcd952bb7654be9f731775dd775b5a3b4d5a2ad16a5fd7ddd5dadb5d2ae56da75dd59f2e356ab7e638bdd5cedf6686d6eea6adaf1b4d64eaf6366facd8e765cd71cc7d1da51ae9572edbaeb2aad1d534adb7237c4d3560f88e7791c672db7c8956b57b641ccd66b668ea3b59963a6cc1cd390b6ba9b2b8b2df2edda97d9dace72edee4a9927a5fcb99832739dddd336cf8e67d755dab5ba47db2d57fd51bd24e6cacdec2580f6a4cc744eee9abbcea3ad27da35ad5cd7ee2a1ddb7250100e4aabb547a2c8f3c68d1b372c775dc7dce2d608a81d830d35edae5dbbae2cd4ccfcddedd47577d35a9b6a9db36bcfd99f2b8afbbaaeeb68eddad559c5da1f336dd767958e9879daca2c8aac8104b567655aab57bd56aab5bdbb52ca89dd4dc55a99593c129794447a80393434bb6eb679ab957bd6aeb3fb6b6b2973b7ede6b176f7e4dab5b97eb3526b5d963f97ebfbe6ecb6dd5fb7adb5dad9b9babfb69c672767bb9bc5b6cc3c6480dadd3dbbbb999916a0766f6cbbd2e68d85b8999bc10c6a57aefd357794bbb9bb6b37a78f6a6464645439cbd9e69edd5d6ba55dbb9b52da5df775b4eba825802deaba5a6bb596eb38dbae0130f52872d12228a6223ae7b41cc7b96cb5185c600590c3e6985c9ddc7459aeb3b655abb575ceca6c6d73d6b6a5ad00749576d552ae56cb6c375b29fd68a594ed275adbb5abe57a6b5a9999adb5fd64c5d315f406b7079a30e180140736902d907a22a5c408094838b034c41680c0448911109624010104264a8c04a0444992291e1638f18055008b4047044e009860078fa00f942409c10323554a4640b201a90f9e9c04a90f962431b2240992103cd8c006989001c8a1a30a4913a9255220349120e40048397122d544040e30f1c04813a9274c3c906a620489930f3c903262c11414060110c10329273d923690a489d4132304b000030c02f081941126552278a089073928f840aa89d4074d981881c2200052499a544d3039c06c8154930d1869c2a40a89074c9428418274015412094c3a50a5048541009c280016180980d41412251a3002c2922421384112824b712049d592119a904203d840d508494224488560a46a4992262218f1c049120e344152e5c429c889d10d0e0013264d8ea63c40a201254a983499600224524d8e3c203565a48a04a9254a48555220181139a97292840429a48d2d58c224499507489c2c41d2a4ca4855120e7080092c020e0009493620d5440452a8091229251ebc9e2c91a2416c8191254c92386922f5c4c8074ba44410018911291090346152e5c448150952484f96485158a8453367653d6a359b714766f3b3b29acdba236c6565357956a97864365d4766d3caaa522bab7a6436adacda8a1e994dab59f711ab39b3b2a247d8b39af511b69acda86c568f3038b3d5caca6a668ff06c6665359b6d4778666535adacfa08cf66df91d9b49af48895d53cc256563deb8ecce68c3bc25656b3593d6235f908cfb623b339b34766938fcce66cd6f408cf66b3593dc2b3591f99cdd93cc2b3796401b5cf64e6c901274838e5ec67e79b48e4f372fed4f4affbda67f9797b15876ce20faa2912a13ec94dfc41f5633ae820174e11c8dc1c9ca27b2e7ad7b1d3b36dadb54e3daf80b885e09d0fb218e4732317bdb879ea9fbf442f669e7a17861e76de1110516c857326b95c2f251732be9492c4d946149344f126b5ac98e45e52cb75e3f54a6a3baf97507d390e7fb9c670cea1250a68e9f5ba62107d891e818b0e8af7b5a6a15e6e1d1c6f4c1df9e73cfe8608e4c83fb7e210ea9f834261779884248a4d84844a1e8e2ec4e59d874aa187235952b9c49b445259fdcbe72dc3f0ce477abd3caa93e6b7f8e3e55424d2bf39d2e50fdf822fb72fd18b5c74b0fe9c2290e99d47d16974cbf947a21f5d23a3cb5f14c1b5601a661dc7a56534ccba77c57b4bd907dd52f642b794b56e29a38028a0b76ec13454d7693b6f9d96d1509e5b07b7f0bbfca0f8912f21d4a7bc3ae72c2a605bd350a25b074551f416ddefd5a5c0ebb93604192f71bcd8c1e28365072bce78e5310fdd3f27651ebe15458f6a500cc521f33bcf5bee79cb37201aaae5d6c1d6b7becfe563183611f27da077fe7d9f77640986d755da31dfe51f5badeff2bb5a2d4f1c02137dba48ce350db3ee7e3f12a058b9934e9234f77befe5d8ecbeeb97cde6ddadea9f2e12b1e20fae69d9f255fddfd34f7203a261d65bdcc0fb8f2c3bd00798932c65778a3f42b722e1a7fb9d6ec56332772b190c5ffcd062d769548f175f683127a958f1987b479615f48d6abe775eda3193ceebf977a983b724e13d677103d87b42bea71ed5dfa5a51df3d63720ba4ec3acdb327c3bdbbcc580f5ae33dbd8e9e6edd05b1fddf9adcf356e3dec03b8db2a16b9ee4ec522ee32d29dbc6ec9a9adf69cc38ace5eedd4455954849465cd53af6190d97355d52959560929654d9e74be5555dfbc4522b02fadde9624502d7de72c12b15d8121a43cad800d9dde7a5457f107259f742477ad53d0c5b67a54db5bb657f5bf86cc6f17e2f5522dfd74d0fa764bd96fd6b7926e96fd5271088cfd89fde92c12f9683fb949fb64e0e8b926449f07990266be325ffdc8d9bb9901f7e5a91fddb2ffe5cc3e78bccb8f9eb2c697f3f872fe78795433fbb0e0fb296bdc9cc797fd9d4f71c8cbcb7e717e3bc82210023cf38e6ed6bedd12c8ece776673fefcb5db73cf2e60a206de02e47424242a2484a480ed21009c9a37a1c9594941c9ce1789594bcc51f4a2e57abd5727086aedbea4285022d079902498e2324ea3cc84676e4ece7ed6e397dbae82eea87dbec96f9b59ba0f8a3e5aecb3f5e7e3082708ad38bc02936c0fae62c4e5201d6c12e904df2a4a4a4249bb4e44b499e94b4949484e42d476ab5c069e6e896f35f4e95bce54a258f47f2f2e5ed9b0884fa6b3e92b78c8e40b045fdb45a60ab7593ba45be8644e0491e8127959d74f4ba01b6c0248bd54a4a0293922e18267912f91a52e4a01739e820d26d521fc95f48439b1b97b3b8c1fced6391c89391bbdcc85d0eba2ee848b7fb5926246a799293d9b7383cf3935a975ffc010e41bafcd3bdd73df2287a74f98d2e67a761d58beed6a661d523b876cb2d65ffdd52f6e5e646c638dce538c80e50c3aabbc859a66195e86260a45f7278383c5fbd0334743130f6979b9bcdcd57e7ec3494dd72e362609c5f5aacafbeb56928975707699f860a0afa3e178738fdd7a7a13eaf6077cbf9bcc51f9f779c869a5e1d1cc7717416a37af4280dcb69e647a7e28fd1e7e50fbafc23d9711a561df4bce979f5ae83b3dfe68d6a70c24a28e3773cd74a88d3c5cff778ae757183e5e6a19e6b5d0295e0a60430255c7dd2738d4b9fe782e7919e6b5c7e785028e41c64100c59ecc852f69dcf5b8e3ffdde2b8fdd705e5276ddea8b7163d0750e82e1bd1c9bb7fad64dcfc130f43c055870bbafe9fcb37f0de97cf3201d59523f5f7d96932c653f7df653fc313d8adeaafee99c48e409749783ee22ed9686815e1de4c4214e0f8a0e76384521d33b9fa290cda7e87323b96193acea0fbdec02893f5a0eb2e8224bab77f976cbee379fb7ec7eba789b0811bd7321422e926513219d07b990d13b17c9727321921b26bac80d15449654f345efc8921bb6f9e8ba214965b55df1f2b72ed57c1074f1f28720e8e210d883e46bc8e79e7f24edd3b0ea5ed905faeadf9886f2c859e6a88371f30e3ae8609c3fc5397f75ee0803e354e7fc25f5d3728e30304eaf1d39b93bc2f855e36fe4992b8fc9da4d99255ca0c5ac6a9f3761e0c4a07840368b31f9151e312b3c626ca6db2d0d45fd34d4fc31cf5fd23fdf72be7acfc941fbe468397d801ce534f3d5271ab2e390d34c1f80f4ea474f59e304f3fc65f7d56b43bd363f73e5b1ebfcdac822e3f5fef9d5b90f70bdfabc45b382e9f61eb18ee956dc402c42fa752a16f1eb2d3a806339c8913e4d9fa2033846921d7d9acea203387649a64fd3bf3a4bea0aaecc320de5392527a032ce325f2b4029603f07c1197ed786adcfb6875f6f9b779d7f5f4704f65f77a77f37f4ed72dbe6dfb5e4f639187a674520a27f4e6fe7e1053d8a76203b78f945ebecad5bceaafeae4037ddf3497acee28fcfe7ed3e2e8a527e90410ee715ddbbaf21d43da79e83f472b33a1589c09e92a575f0cce8514daf7590c5902c651f7ee3bdf2d8e8dc0718499995cbb931f85cc68d01f5d1a9579108eca7fbbca087b77dbc1c735d36a183f38a64597f7a78bbef7a2290cda7d3fb39783b8fa25fe7d5a3a8c7dfb7aa7fbedd6e59d55324f2f4d62d39cb3c658db3ccf3539f659e7358f52ceced0ad650317413aa86cfd37e4a5decafe7da9632ada5cf338d7bae6999f3ccb50a841f5960ece79a1637cf3e82cd79dfb272f7ca635cc871a48c73ab0aeec0a1c5388e9faaab00ac4c59d54f87383df5339d47755f2b049e873d957df5beddbdf2581776571edb7c23cb7a808e94756ec53937065d037d75b0c3beecdde5188b44e8b757928a666565653d77b93128b94666ce57f5976928a6449e9e92b30c8b3f9aa4e2ac7a667b0303e406b94190c3193d02390323d7b28c79fb5cc302c47310489d994aaef3ed20bd577c2a59cebe3abd200de99719368f71000d558ecf8e34b1b8a1e28f79c2384b1e3c06d03d9c46a3d162ed51ddc363ed0a68a8eedb19c03ea80b09cb7c1d0df5622bca23cf2210d6f3ec200d6b991a5a2c5736dcc41f81b8702b40f7e0d89fcd5aee1c2d7617fb60927907f5e63a4f184b1e513d003bdfce03cfb75fd050737e01be9deb583e3ccc4a764c32ed0038646c980b9c4546161730fb5ac93913d003530418a16898db3bf4161930fb2926a9ac9ebd6c9f4c0a71c5c373ed0a4cbde2e76905c1c9220366cf0d2bebcf7e8254fc613bdcacdf1a64760df502327ff38dec7ebabdf399a99253639b0187cca9792798322703610bc6f0eb83aef626a957bbb537d40b483b75bab9f529ba79d750fd5bf8511dd5b35964045d579501b349760dab5e1d7409a1b2facaf32ba901909b524abdbd564a2b7529901b8a6b57e43c75f6f082dce034616c3dd7ace8f9e9dc02e2ab6f3d380e8d1663a73de8eca08b7dcab383cc3e5a0e82b3d05b9779474896e00faafe202059f6833e3dbca5d587f35ba513ab07bdfae7cc3e3c6ff18e1659824e5d74ef32ef10c93274ca3f2f15979595d5c6570f6f390332bf7a548397ffbb53957c327f7321fd9b537148f526cbd9b7f3833e6f397f3a15816cde72f0be84fc148384cebc637a148deaf052cdacefbc755f6210ce3db29ceff977cbf99f83db2de76f2e7a1589540f3daa41a6c0c8352b73befae620831db2184474902ceb83b49f74dff216394521f5370741165b6439fb9687f749f7a183b79c0f3ab38f902c4357e2e39977800e7a14052f757086e0a5ce628ba4a23fc51f9b8bb7dc8654dfe2d0a43cf839fb1483ccac0f49e61d254852cdac07dd3a8b0ae00749661d9ed3eff27b773a8d7758e76ed93ea37ae6cd4d0ae8b4471597bb33e75570f8e63bdaab1879cb0140bd0a96e9455e8133ed51c5889409393df22a4b0e3a7b477b54e15125023772f622daa30a0e5286c35d2e370a72d7751c2ec341ca8298dcc865383c749757a1a4aceb7c2465a373d4659494752496e9b2ce953cc845671f698f2a1f29fbbcdb412352463a97c39dbc39377259bb1129b3d64752f6f9e8322307bd4af523523634e442a48cba47ca3cbfa0cbae83a48c529781fef21be3e82129b3d63f52464959927f3e521f5d16fa48caec0d97597f72095c36ba04a46c68c889481991739c2b91b2212527e2885c0297712e012953f2a892cb3c26120bec0629bbe14444feba4144447a8eef73a5a4ef033fef1219e108a223d7de8783762091755d1b766714e4eaee72231d3f8e08b4210e231cdd7505d9cff32838724441e188c30b8d82b8eefa735d4b442d9111ed9c2618c1765dd0290892e07372ba610452efe9c965ed4f4f1e0e1ca4c3c6ce61e10413b8ecf30926b81ce772d29fc2cf9f2c0ce6b2eb302758e7221a9df4098c827c02a7c065389c82a7279781fe24c19353e032cf3a3981d74987592287b59393d3042ee37c0252e6e414449d020924487259e8499e93fea4e449397050e04f2e33f22718acc925206530cf21813fb9ecf31c2e1b4926bf71a38d9c74a7a424cfa1a4e42f4fcae1492e0bf2a4ce9d5c87cb5cae63f240720a5ce6720a2693e7a0c075b80c87e77059e74f302424d2919e1cc965d791a6cb404772095e2fcfa144ca92485992e7f09792bf5c16fa6bbaccba040e73d9e8b0d9e4e432ce9d4859939393a4acc995941ce632eaa4cb3cd741ca2698c09948d98b94bdfcc60d4722654747fe2265487ee386bf5c46e42f5276f4f223978564143f4d67720a7284429e44ca8e48d9912779183ae9b2cf49522694c3499781a408fc345dc82970d94792c04fd3292065139058e613e81290b2eff3215236e4a0040ebacc233b0049d9073a1229fb3cf43c74d92539e0a7e9482e232215c04fd361a4cc89c89b481949ca4897e03a1329cbe14e9232ee824a1a73b25820c384abd86de2ca1726d408234c95266255ae4719911d3f4dbfa4cc49c741323f4d5f22993eb12b25193912297b15f911299bfd80831775f0c89086ce0f31a399182952788193270d3c6462462e0b228bf869ba11292b22993e554096b06797b9c8919fa6474076f4891d87cb2819023f4dc741ca88482c335625c8874899cb6f905878c4aa50172265412d7791b2590c67d8381303a032be102346798a3f38c0e0450c203869c4aa5077b96c2407c04fd35da4ac45327d0200590ee0d9651cc9839fa68b64117d62973579849fa687a40c24b1c0625536ff4859017600028c1aca78e28f9a984762f15895a53767e430841456d4f02256a59d735947c2f8693a47ca3692e9d30eb2e4f1ec324b3a3f4dafe4489fd8d967d4eaa977648153871f6ad82c6ad8f47eae9dd9e187a8ea23f18e4972c3a847358bf3ce20fc5433ebd959147947df9077b4d3dbe6cbfa3dc35b8d5cab52e7d9ed73ad8a992f3beaed22159f6b556a6080d52b9fa9f3430d63b236ac9d45aa99f5f676bca37df6d4f996f3d9a794b17e7b1845a3ba631fd6db3df6c1df6e2f734161c3b86165d14f9fcf353354fc50c3a6d73b9f2f378cbdce59abf22510086353a7d429a5739e524a29136d42058d37547b1d1baa74262a58df1e3614fdf632fcd23b5686cf234c29f35c9352c697aea74ec1734d8a9c075dd46b43cdbe6c9f3e874c4df235a4fef4fad349a0d168b4ea4d96edd559145292f0d52b4935359dbfe47e3a9d54407e660267197ecf09cedb35d147399fba00c62094ecc00ee7ad1fb28f26f3cf5371ff44c805d2705efae2932ddcf34fc216fb6832694fa5c14f6f679148a5d1a62864a4fdf450c8a4fdf49e8d07e4ef1aaa3c9a80cad83ebda4ce0dab606ca7cece2291f6294a760d638fead99c0998aeaeef0454c6e9ed94cee9e00ce924bb9ef5a40299a91cb96165d74f6cc1b8f97c900330820c82916b527878ae49c1fad2f5ed0278ae45d1f32dfe28c7e95ca3614175de5e1b6af3f60e6cf147b74d9101b3df1c9ce17c708a9b83dbad9a543f6ac3aa0b197ff3292a807f73167f70c3caf0a725cbd9dbaef328daddb27e59bd8a3ee72cfeb01ed520c822254bd953ef6ee9dff9764b6e1809df79c90d2b45f8cecbcdb9616508df7914a59723a96401734eef6a7b37e7f4aea968374be0eaeafbf2beafeaf52a208af3097638390e08d5cc6a52158a20e4c8771d731c1aad73b03af5c82654fc8746fb2aa1ef9cd2aaf03b22227cb7099506dfbca37ad5f69f974d3eafda861879cf97a834f8c93b6af548661dd54520d637b2de8ebd49253bae0c6a1db477c68981d44170f6dd2cdb3b7a69734e9ba492b1d3ae54f4e7e49c0a8973b06b58e739774bae738fe3486e58479655fd54d3737a4bea4fc657526951beec9c8a0b1425eb9f74df4d556f079d23bb1b725e761dc979d9395547454a946ff264fbe94fb877327bcea3287737ef2e3fdf928afbf3d6378feaaeaabfbaf712326bdfbeddc9f9e67d9f74e7fdd63b671108554927779d639f776a92dca5dd91e57476ce85d02f9dcc1e087f25a72839c5deecd3e7f605c8dd903fbb3314588ed3856cbef9e6b1b3e7ecf9e7ccdf1dbd27f3b76d92ce3b189c21177ef3c1cf79bce7736e0e7e1e72dc90cd41d2e5f9146797ce99f4c8a186f1e69e7f6439fb6fbae7517482e0e775dd4ff72eb7b18e765b594703a93f492ad953f72e6dd6d15e2e6036c44fd6d1be80d910cfaca3bd8fbd3b4b18a90b19c3a6aaa5f5e9dd7d4d926a3e4752f0e00cbbbbe3d9b7e99b10585ac629eb4e5f5a7e7e29f6d184c79f619ff1163e9d99dc482a5676c436bfba11dbd250435f5dc95be7f1e5d053d97c5e8de5dca2c55797b18ff4c98c313b90d9a1cd1e1a2db6f904375a5553020c525e082246c5ca8ed8f4ab1b31762a563ac6f9d50162d3b77b7580988c4d91e9320ee853f55947c6a608e71b298ba24fd53952c6a6c8e693946d240fa64fb5daf492ebf019579038f3851469ae7022c6319ea1302788165a7883e70632621c632f7914711a2d36b73494d357aceeea0238b3d0271c33f099c188f7510af9aad76af5d4279fafa42db7ea1cb96423cbcd95501bfebc25977849c952b639bd74b07eb35e5a7d399d5a57321bf3dc12a683f5d6cb0ffcc6914b98cfdb6d8901a8d4de9275d00851c11d386449c2539f5e2b7046c66ee5cacaca1a13f3ced092b8c4f0a54c1309cad42087562502f3f933b40aeec0a13d3100951a0934a68315632f3ff0560af38979248df9c4d84bd95bb29c4eb982926b67daf8aaf9676838aab0610a17345a8c4919bbd592165cc81325108d16eb485a597d96223c2d1274451532b44185468b6d70e39b8649e33a1c1469a8768ef57c757032c174f0f8ea30ee96ee4cdce5ea1dcc0aaa176958750e38a03d9ceb5c80ebf89131a8f4a74d5be5ace33e702404114898344b0668edd8509db33bfbe08454d03a8b41ac57b7b37f4cf77cf3167f0c5d5bef93f9e57c32bf56079b27776dd9cfa7a653a75ef515dcee4b08f379d0e937f8025914c2f57990e45941d96fb7d94c5b3c2bb04eeb55127ebdaf21746a73eafcfc6343f17876eff278ff36b7777a19a717358c7d68f3aff32952758fc43bd8bb3b368c9dbbce3b9864b256b20c6b20d03a3395dd1f2b63d9d56a0dc579f52e4e43b557efe43454e7d5cb6ece57075f30f3123cd75c08f4205dc3dafbc158a977dec5a935ac7ad931c07a4776722600ba1aedd161f553f5dad58c8cd6bb5a87d5b0ea1a182df912d234da53b7a2d17e8a2c89fe49ad834584d9a8bd2595587f2980df766ebfeac5183333e0af9df8837a3f48434035e46ed9617dedb06606fdd5bb2f667cf50e4c43d1af0e4eda93b6fabedacf5c6910f37cbebdf3b9fb6a7f59e75a96352f4b4280639e73cd85329e6b2e90f142e85ba76f49ab2ed887f5ea5d1dda03f4ea1d1eda63c6aa777b688f9657effacc0c3aaf0e6efeb2de8139df0125be755fd667ac63e07d59d2019d5b9931d07dc6ece520f4ee961d58e237af0fd29e4c6bab9cfd8b83cf5f9cd7a7ff7280e82f8ee44024a7701620cfebf7cf8f6afae19d326320f9da482a57d69fb9ba20e6f91457561b312b33e63995ee23a770595959fd2c22993e8398dd9fe71c56dbcc366705381d9cf367736200b24f29b4d66b7d4ecedacda973d65a70bb483388da3558af3fa866d657df48a45aeffcdafd0438bd631f938441cd0f358c7ac73ea83310b3f28e26b9611d9dd306ca0e5a3a69e77473ba39f501741b75ba39a5d42d52c3b6f6a1dacb76f3b9f9abfafcfe57ed6173cb91480ddbc8daf10e9e5f6f3b01b657f6d1aee9957d4c926a66b5e51d5c79c79c02b2f5ca3e3667b7ec635ed030ebe67934cc3a8bb40c656188d43d3eb75e00f6e1bca373eb3a1a6a6c58e7d607f0d43953f7e8faa92b5f61f718eaa7cead33d69717bcf517fb986f41ef2235ac736a43547be7d46508f4dd143b7f712f8e73ae9d59f35c3b53c30f35acf3edcef748a48675dcb5bc8377f07c9e33db940dc836683a90efa73f8b3f9a32ed9e44c0cdc16d8a3f3eee8a6e1d042f90d997a17f9c33e7ece12db9e7da19345f798708e4b68b9773efce07c1ce411af2ed5ce49f1d5976dedd66b274b0c3f07eef1f4841d1992c3b2a12a8488942fb266148523999bde76507d66023eb9d701d345fb2bc478664c9208336ec6e835ec12dec6e3b8b4186886449c283ce21c96e45205fe81fffd4e7cc2495ec4bfa9fb328a47b909ceabccb6e487be779148dea1938eb0f208330562f8bde7a4359bfa0a1fcad179da1bd75247b79bc2d7aebe390b5ce352a603932df00d90eb90238d053493bd5ad73d69fb30ec2b8f90038a8b21b8f3414e70568287fce3b6828aec980e639bf4043d1e718cf737700cf3937d4fd92e770c3ac473512d897fb33c178aebfc84f4fa6adb1a1a8733ed450d539ce3967268e9ce23f144d9a38df43d8a194723b7524f6c14f3d897d00a9d5867140a50a942aaad4c1aa7244157c624c9faa7c13379c30a74f1a68bc89317d6ae70e0a1b5649186c7af74c7b703f95e37b3fd762a0f2b52b47ebb59dc71ccdecf9f621580168ac0cff86cc056d64d8304b86cf353380be92430d9b5c7bce31b3b30181136be4860595e3d3ae84e918ed3cf521ea5d680170b619bb86e7da0f5ebec93cd77818e361cf351e5e105235e5cf973320b62f47f062c4b3bb669b91c279aeb9d9f333ce730d8731afe3b976439c2906f996d8f354479e3dc8f7a785a7b26a7d598545154564beac424306c4e2cb2a2d58d82f9de4c981021fea7ce984873acfded16895467bce7a76f973a300e8ed1daaf9d5c17a6d25cb39c521d49b2cdf8cf95a7dbebdf3be86d83b5fdddef9d2c9ecabdb4bc5595959a01df341f4f9fbf7c10eedb577beefd24f31887f7bbd4f882659d5feed43c0bee50c32b3a8a5e44bc874ea41a653b20afc1a98afeab764d9fe643ef5d9d3fbf4deef2af0d96776361aa08f497bb0c38f7df8f89e9d39039e155477b10f66a3c7369fa657b2ecbedca28430c33baa8bf5398328215ee00af82b5936595afd937e109acc9fee0d7d2318865c83c1cf53b9fe49b7cd0cf869d95f6e4f7d8a4dc6e9e014a9c6094e3a1ce70755a5d1784fe5c1ead9f8f0cc60f6d1f3ec3d7b708cfd9b1954da3310d6c3755dd76ddd7574765d6761ade99b9da539208cec4c0d555630d45025d34f57fae9a119343f7d6ca8f99399e606ade79a992be0730d86403f840432ed71a4478a33ce1083258d9a184761478d12f62891060630b1e9946bc366756f58ddc8d27ff38e6d506d185956d19feef95043811cc87d1f5972dce7d441eece07bfcb0d9b6290a4af0df39caba2ff39389b276df183b086d25122fd9c64b9b975ea209d73bbfcdeed34a8b24fc26fceb7ec9e3bb64155646a1875cebd611cd95e6f399f6a6eb78a4ef10793961cc3a186e2a7ee8fd450fed475b00ffad499fe9bd60c730f4a9956e7fba43e7bbd42c667a7949def3c8f2f39ebcb49a3e349c6e9652dc7b18381cfb7874349a29a3613902f0e6318c3d5b70f1521cd0ce67c334e9fcf8d013bb740662a79cf5767f6c13e6677b48747adb51664b0651fa5937df4b3835de710989d7b0e5a07ed15f27177bc1d7daa6eefd7fd68ef728cbb42c0a73e7e5367431dfcd16439ab218bd6f6d6dd6d6de85d286cac5b75ee6d937dd0670741ea9e832f21e58ccc5bdf66784b36f1df7cf61be95da74fd5bf5b449feae604029f7b2414cf3f8f00c73c928df52a3ee08a06b18f84d1a7ea3ce853f54e2442dd92250713d0468e757e458398e79e97b01f1deb5bce860809fa910d9ddc93b6ca087cc3f0e54bae7df50e1aaa2c80021aaa643c17682828aebdf0e7ab2780e77c651ffcec53ccf1a555011a8a7a75de53bb6bb3e7c9e79a0b40bc0b6b5cb8fa3280acdffadc50d54bd85b6742f27e16eb543bf572f472f3f6269b92536315e32ddfa9f6ea54dca0e9bbf0e7cbf1ed5089f4969ca2de5efa5beb61d19cd46706fd74031ecfbc63523620adce6ebe3768a765cc7c3b2d13e7dba9ab86f496ece373cfabc800ebd5ab48a49d7abd95c77fd4bfe9168036b42211fa390d3da4fe7d4ec13bfd2ba94fdf4409740ea523a174be39e7e0165271483b75b00ba72881ae025666ac732856666cdb1ccae69c4775079d43e93ae89cab93ae007e79eab24d2c43e5a953eb9f6f413ec920dfee94ce37b7d7ea8b5967f6c1f9465a7db1cd4b91888b39b8f2dcda3b63de6543a581cb83f1732b9cb3019df6c5ac5777393f9932ee92ef410faf90afe55504325d28f4f24e29013d74d1697897802e7a149d975fe896b30f9aee72f97843679101937491238c5f561a6d7acbc1fb02453240fc7daeb5b086cc9a677aae91b96a01900dc1fb6a89a2287221785f60ebbe4051f4a8b622270e119d8c9cafea21df0338b691533a52368b7da415ce67cc9eb9e24212f4a8b7a46cb3ce791b6965ad6f2eb3ea7c23af3cb6f995c73cd2aa23af3c66492b571ac4aacc62d6ad731b05c9a908ccacbfc06a3576b291044bbe40b7d6e7245fa0256765bec51fd4a7b524d3a7ce6aebce0a904918b99625d0772d063dcfdf851980cc54f268a8da50ed72ab0e62a30444b71aea22d18b8a6eb8b8398b1d885e7443073bacd373f60e64506264918e8d35e486e22f02e5c8438732fa91b358e4c84352d6f2d13a8b1d8c2c047aea2cf8b162911b1ee4e115bd75a1dcf0965bb102462efa768bdcf02052d6722267f6e1a00d7dc9652d231749d90d0f5b976342174ae846be74a118f90d27ba5064a1df70ee96b3e7e18d9e6b2cd47990c51f9cdbbbe47bebdc15f2bd752207c12956a0e544eeba505aeef2a02b6b79908f57d6f2d159acc00d37f2a826ba32972f7978670ffae720175a162b200b72d1652e1772168bdc70235236bab874a1c864a28ffef28e095d283259c831d795c9dc953ce842b90ec51d8a92cbaca020b99013b9acc865385c36e4a3158bb43cc8add8c1e83748d92ce6f2a32b9b3117696f9196f52012cae8410e7221152510ba110945f49673e24616b9e150427779d19589de22ada044e04b1d84e414230fdd455a7db12017afd5171b997d58b2e4f167ae5a1e92565f2cf4cd83aecbc72bce5890e99db328819194852e92565ace6623a79458e77cf3208f6a902c793ce71fd92290e9d4b9bbc4fae651744e31f2205236632409e545ca8e5c16e44beeddcec33bc5c8973c822b2bf2081cdcb89479a683b57129f39bdbaf3f06cd73133f918c6551cde227cb1a16d08c0134a64f0b5b5a183306cf1839637808437b5f1d39c4fbcde77fcff579e6c3f5f9cde9f7b32884f9fc66002ab58a051bda6fcefdf6602bb4f765bdbb2f4b0af9cdbdef9e452115dc81430b452106a052fb8d0e56cb9748d98cc92eb934bacc979446d20ac9454f22ad90d8703e3a9147f5bc63fcf4a07be5b1207fdd2b8f85eeba562e27f223d22a74a220d2cae52e17dde8ca66b11ba49516c9864a839f2a79565072cdcb9f9f6e9d7fc68aee95c7649dcc65571eb30a5df41b1ec195e120adc2d0895c6625e444e495c75a3e445e79ec066925445e792c24ad04915334880579e82239c595959595152bb9e6a5cf4fb24a24c24dfc243930d2f9d29e69f34ab8cefbaccc7b61e82dafceb48757ef4409b4dce523ed31c5e52df22a8864b3f92c569d232d39c5911198c5c68eb5bc5a8d9d6cd26823095c235fd65b6ec5632ddfbc14e137f265c922e3f4f2c84f9fceb4878b84227ae8d5bb99c1247d132bd0128b84de22652d0f5b62c8e339875504b3aec71ebc7c79eadf567bfb5179a6c0a4e1e6a1539108f50fab8ca73e1db4e1141b30c2f873deaaf13907bf16814c1e6fc1b3f8f91643afc3951065ba3f74cab498585aba13c8082a2bf0a006ab01539899d3268a33a09001ce1a7fdad89400238276682c50384fd82e896d03c36b17d3ca5519ef4650123d5c49d3a78a324acc62a002062d59e09961882c98c00a9918c3c60b0a2b9248a1052d65e4b0700496181ea880c184156a9851bf4079a603faf0d0054b0e3ac490c59675c21156c8c040e58b10b10d882e7e2b630d5f120616795280620d27aecc7a90f2849a2fc4f041e20a0d2d35ba2ee401ab33418b28070c47ad1726466fa8088e1e387636f1c7365a137af8b8ab5d1266ba24462f7d42ef82226ab50a3c21065680a1f44d126b420c92b0dd1a31e88225e49810e59bd3e7823bd616b50281522e6c81e50a22d270a266f4c21938a410c68f963d21204a63c80a3956d4792ac17e7f62376011b1c5eca1cf0f5788e0fc308688e3380ecc05377431451b50b06046192b00ea689f0a30e8f0051e38410c01688fbde39605037001a286374e4ca14495a42f64b4f451a28e0e637868586b6d2ccb9fb0b3d67aa8a586b7564800dbb66ddb9f0068dbb2e0388ee338306a360a0c3923c7e9e1c0e82e09b734c41989729033bbda308ee3ba3b35ac5d68e70f65d31b18672678c41cf9600124860f7368501344cf1a785c90c306960f6748e0b5b9e10934c260830f123eb4e499028a3f61d4c892a13363dbb66dc3439fb8a378a0881c9006167a5e2053c2137c92b0b2020e538a4083821577bc32267863bfc811a4c4d2eccb9d3743f8b132069e35e42c11484c135eeab061c61a3266d8b66dbb02a89ff8bd2b5a376e9b1795853214e6058e0a6db031e20c375dc4caa22b8e38c14f175df42c5184123cc6d869f8bba0e130ac3831049eab2e725ee8e285082804a1062a6a609c19e3470531b62f018b0a15eba1b5d65a2e4e6d7462806033840b28a8f083151ca49006971fb6b879a18dcdfafe30953e436357c60327b0060c52c874e902893eb1b2080b0c5957a408d1064f6c83b3049509c2289282a986258ac8418940808888e2824415668099d3264dcc6e6aecd9c0a8c551c2b3839487ead9b6087edbb66dfb03a8ab5c948f0fcc5afb0015b6c840840f6bf4194301265889e1cd1653c6c8e3820564bff8c268c18f116c60e9b2e5ea846ddb74e80963420b6ab2e02286d8e6a7e560f5114e5c90854d1a36c8d0020b59a021254f9b2ec290f2a6081e62b6931326c80b1b4bd3c6f3c20faa68b2a1d5ca3f048dd21faec6a271a8284f9c3c73f200513462d90931782bea787a7a90636d51f7c34473840a615e80020713cc50b2620b186fb2f85153a68b2e46c35514319b1a4360a094c20e2778c1c41a55b0a8d1c3842f6eb408e28a3163e07001830b220c7c79214d1a5c64e9c10427a81cf1021524beb8e973860e73cee820461c3b1d7803c55518294af4e027049c9c91092fd80a1e2b7ba8952cec983f514bd466342966aa6041c5851b84e821884e1242b33045d056b861072ceaf4d922041d861c8ee3b82fdd6aac50c5586b6dd8d903704438c503da50e6891a117f8e2801b6edfb6ddbb6edca5557b72965fc36858d2a26111454b9a0a58d142e74a1020687406ae810854d1c1fda90e2849c20aa1637d02b3552a0d65a8b4219cf13734268adb5dd1d1004601031054d1648bcf9130131d8102184195a00c1c6c847866edb3c3836749bfd9e06301d00f260a140a50440324ca982068a1e4a1883c60c2ec2b0e9c2e4d065c1449b34ec0579007180c0d8580f57b3ab26aa54d1a5872a687001ce181e42383e0072f50a60a86922501436c260c1dd098918b7f96b805913468e0c6c8c61e7852e88b0e28b2c68fa88b1478b2e2a9f2296aea042b864e1e2093a67b00122043e50c9428f1d30ba24f1c795c5b487e39c03d9f09c57ce09381e1f38540f17c5588318175a46f040c59fad0a3096e6c9f19ad8289c2c33140e177dc29f0ae78b1c110d77010a344ab53cf1d56cd7c5e9c46c2bc809a2badd98469ff8c7d6cd24b238a2091fbab85025c8183f9499a10b397142d08585b302ed86155ca3d16854c6a271a8e8aac31ac114fd1686d63eeae0ae7658a3edc1075a973c6ab858230b156848d3c74f192cbcc4de688144e3a95590450dabb12f65ac2ddad8d8b66ddbe205236a3384e1c4963739ccdcb04697345d8081868a16845228f3c38a094de0f963070519c2c0f0bdf1628527064dce1531d858d3c31d3d4a415ea800c6891cccb439e1823953e0f1628b174250268a15a229b62d841378c8e20207159e80b3e445072e5edaa0b973441c29c49082c6aa8041e359c1eae250305d08c57a643801d00a73e09c2943c90b6118c1a70e9f10600552c27b13c80ed2e7698eb332c3735c0e6d050c115c14127236af5b0f2d84cec0dab66debea96c1986eb3b54d1f6b6763b88013a60c2a9a089347853469fc00e884187688e244adf5c6093a43789eb8b21d07049ae7c4e7eed4a6b8fe0b9a98192d5af890040c6780618217464c37f3810c0d4ba4b96244183d77bacd06b529632dc78518bc1ddbd1f190b8caa26a6a4576131f07dab6891963df6079e15db0f1a0d16853ac77f68ed6cb2eb4e468a65b33f6f828c6f413ff56f461ae6a982ddb057f041e243b667cc33181021f2c4e18e326893159572c21658925c228a289285e18a57867faf413f7889df9d34ffc6537f2b09161adb563b409bbcdbef9a10eed82072a091af6a440a676831e3a333437c470041559646419014506ba2853840b7cdcacb9f345873066108186952b63443b1c57bf9855f45183884f8d25383a7ab834e4805d121b0fb40c976d23c2f5d65a6bc3ce663dc1d5ac6460f305203d377861c64c0c0eb52c940033840c7b6841d4f1893c2d1ddbb66ddbb6c5d0ece9ea0d2a26c4200399335854819306ce6f1b1728fc4684f5849b22de0871f5841338d88926dee0c0a7ca992a72c8c3c294ac1e86f0e28a36028d4d2c71006b8a1d2d7cb4c0e28a930a56a451a38a0f3aa04007853a550d96d0123443d001038829e860d92a52ac49830e17658cd1c69db74c042961abfc8980892a69be706941cf10575e7edbde5ca1f01b4d0514b8a401031617c888d1d962dbb66ddb7e05345d1553c36fdb057cea126a709689335d8d891ac425b41c599bc5e6ed8db75b06d65a02b8912b269c91a58420a840e3bba863e68b3f56e44401478e10bf1dfd36c7dafbd65a6bc34e0c2d4260c4943c6c5c1db16789366b30c1c213213013a58db75e8394b09623428a6db3bf6ddbb6bd0a603c8fb9f8cdebb66dad4510d7861dba0616ce004db4a0c5092df4f1220f57faf1c282942a45b051458cab4f7449fca007071fb8f8c0420e6fb0e0236689326ac033854c8ce3b83753b83cc72131563fa0b5d65a6badbd801846a000841028a4a181c403c61425f4c1010d326216dc22be1c232c2d75a8c883460e6b2435a186cb19698461a68798058f18a27738047bca1eb288d3460a25d0b04620cb45940f96a508148e58c10e1a2362d688b7d6766317b212333891860a38cc59e30a6b54c2132918a1821337ec10b3d6eb9b3e7ddeda22b6502db2c0c842678d305dee0cc101ce165a80e903464c203dbf6d71c8a1b16152c6b8c62840806a40244c78c106cc161dbcf0504495315f90e1a54c9b1048d1a1b2118523a245e0428d24a40c91458c1bac4cc0e28d1a448831538585c3b9306f1823ce0972be3c91658bd915304ce893250b062b6cc041224e056f66f0df7ef3a2eec131a93c5966a8c1fa220b3d48d42143041453d8d046132b3cf85b4dcddc346021079ff1650b3654c819838b2b0ac872c20830a4642903cc1b295a820e502f50210826989022059c2b2b6248e30a303a4cb1e70436d61832c019a32dabf61b01388ee3b8ca0dab1b9cc0c2115b8cb0d2c61004d8e3041e16b2ec6051e54d1434383570180b40ff4a063354ece0c294134050060c35bc69618d26b8e8f2adb06540419d31b2dce142cc1458cca8b0010405202e69b858418e095d1ab5ce470f80fcd04962cc165dd4c04cf819034c9f2b96a87395c6145d1a597004f08dc6f5731cc7715149b4b1d6bbb5bbbc849c5614f4025a79ce1a2eb26eb8420525b050c08b1a5e80a344145f44a14366a313c3d000c6a2f187985a90bbcab4c7f882162b27b071c613719a48c0092b40c1031d3464044147cbd7e7bce3f31c8ee77a3cc771e3386e6eacb5d65a4f7c14e4785ed8352c015e7ef8c1cd1a686c5144ac2cfa25f28062683718808605336154f8c2892f31a88de435d6a803031442e0b9c28505c1183a26a4a1d3650b2c6216092a6fed9b30657cf81683b7d67990433cf4d5b3ccdaa38b591fad75b7d6da1678a04f3c81600bc6eeeb3bc7715ce7cef9c8f95011c7711c7742adbd1b40d09045adce15953e53a8c0c49b2e4a2071e70d2d0d9be58c77413776e108a6cd1cda9ecb1413ba206382164c10c11d00862e42bc30850a3429c4b8da11543cf79c96209ef3b0a1849e736f28013ce7b086b2ceb98eee31d268b428316ef3a186216d5e0e7d51c3aa6f2452c36a8f58181ee813bfb5dd0f213153dd66cd02a178f3caf9e6614375becda14672f68183e93923c6cd677799f698e1a0c61914ac4cb9d26242ac2cfacd47daa3481b25fef010c5175da410db9ce1bc71e1373fbfb93754007e7318ed51639bf3204b2503784b860dabcec519f9cbdaa38bd5b1d11cf1e5082a4694f5d98834b6e79a1164d4aed81822ceb383cfb521c6f09e6b437051c1684a29a574de491bb976a626c40f716d081fd80c33a594569252a674cacc8ab1614a2ff38e24234869656e582bd1ade3db8bf8ea68a810b8e9db79cdf74b50c4038521f8b422308bb543a15e8592573488b53769d5d3c12900766eb105b53a7d39bd4b1e33db593d67713bb7570215dc79931563afd7521bfec43a92c674b0aa97de532776162be0f4f3891e3d4a7055aff5be36672a575dacbbea629cf32dad8a342dd6b977ad0c20c6bd84cc326fed4672c0397f9131d66d1b49e57a4b9ee9664f3d8a7ec75da64f65f7b61bab470bf55ed558757a8b8cb1eacc949254ae3f5399bb271931946e308605be332b68610c487b4a9693365900e79591ab8cb361932905ec7da9e6cbe66cb6063c98f6e0bc1c378ee953e57c73eafdaa80e79b17e99cebdc92453cdf48191beb1ee94d8a6cde91b2913ed1ea0fe058117da24eede6a53fe538b65d8ed55bc2fef2a04d5f00ad8c4d75ab2f66c15a81cd2d2963539d72aca34f318e59a75648d0371bb0964abea75e9d9265754b9ed982f09fb9f2181bea541ce27df5bee76ce6a6dd5f40a9f1730a402a27f33d726abae73798f9b29b4cde61bdaa7b2ace0a3fd0faec7273ebdebde106371f825edf72d62c76e90da91efa148784ee89413807bd1b7a1587743e7d92cc3b66d9a4e7e02ddb7bd23d156d835656cdd936bb8b3a3330d61696781bfcfce6b40b3b3802a6be9dce410736f0405d0f85f178eb1c58b7b02321345459c47218d006d0dbb74c2c27c797ac878f7fc97e7ad85869e79c739b3c37e7e955dbf37ca686b24dd8abb62146be7d6be7d924b38ef9638a3a039ca9cf0982df5c06058a5567e5b1477664499dbddc806c4e876c24146fa8ee8d1c32bddd3ad23854abe1cf6fd7fed850bcf964a38d369ec52cd6c1bedd99f52cd258077b546fb769cfa2908ef2d39bfef1d2802e179d7da8a8a140676a28ced9610db5f9ecbf2b3a776720f839f5f0be38f75cdcf3d5bf3a5fdd73902930d58c5caba1cd3305ca7c75b10607bf70bbd5417036a97bdeb5e00c2d157d17e853af219d5bef3a8f1437f83aafd5710e7a9ff37b1f098473cfbdd0b93b7f9b403eebdef711a9fe9153d6cbea4e66bf39271299dabc7a47969b87a4e7dbfccd75695b61b660c6a8b8dc0ab3059bcf58140def4752c9a8bb2ec74030745d8e7d97bf9c3d778778e494e8f5c7045246d1a92804f48d2c4507c917e89d5b6f518848be407208e7dbe71dd74d711b50aef3c812bc65e82fcff9e59140ac830e921f82ee6d539f836439bfdc888024f30e926a66794ec16dfee7ddf9179405788eb177e1cca0a33c7b8bc7001a8a5acdd9d7211610a44128d944660f2a9f53614341d0bb5ceb063ba4a177798fc8802a12a11c6beea70b9875f98e080ffed380a8ef9c8a43dad52aadb01410f59db71864c9535909097fe9b916173023f31d905999a73af29d83ad566955e637661d65e77301b321369fce75e1d7915ef85d8e8942da37f22bad7ebc4e494a96542452a9e84f518e1ca1e8ab8a7ea33ee9d4f4e9f5be86941e107ff4d616d09a3e6bc0c4b141ec5823fb74906f7df189904bfc907d3073ab452275b2f38f261d1aa7afcdbc63e37c63ffd807d71de7d33972faf7d33ab7cf5b1bd65b3ffb4704243fded11c7727eddb9977744779b7e4e8d46fc089354e07419e358c754cf3e7a73313582ff97682b3450684de777a78a7c420edd3a7f39461e49a1ada7f42a0f9ef4307411a4e3148e79e4f35d8f725b2e87dfb452eaccc3b5c6ebd6fcb5db76c2f81b4b7c872b608a47df4283aded790f696b7b7c8fb1af27dfbf74d1ef10eca3688f3a63d3846bdafcb5be548e4fbd08fd807752b1279f10eea2179d4f2a89eb46731c8a49d09f2a81e698f20b2f61375ea22cbee379f6affbc6c6706cc86f8bea8066bb728a4fd739e6af10eea2ede41695f79071561acb4282f3ae88122e972e61d9ee8f25658d207bd057ad5f62df13669b9428f6a57e82d3174ef96f3411d20e9f9e77df9996d10478e0ddb48f22d39e4ade6c480132b0d178f35dae75a1a3ecc3b24e8a694f29c96c99284e7be9346bdac7c27d94514453174d0455114456726b1a980200882200882ce4c201b9ee7799d73ee799ee739337967701cc7711cc78d338cdbede014fbce6f679177d8ae61d6a775be9336d6b7d645f6f1a3d9471f8f7dfca0ece302903b30729c832eea94524a29e52d08cfade36b35722d8d9db70eabdb76836c5e3daa39ef185c2e57cb450f1d7497cbe572e90141f073cf3be71c0441107402646666e6c9cd85bc83f379adc6cd6dd93dc779c83eda3917d9073fe77d9938ca3cc027f6a9bc3ed5f8a49faa3ed20d3602ad831b5986bf3948b93f2b2065f7d7822ec2f720b9e42badbe7a97ed4ae6196fc9255f7b25cbcfbbefd3b879fb9f8335eced3f921b56d6df2cfdeeb496b4ed79df9e156c0ed6591bb6753bc8b56fb28cfa76d00bedb5ceb5e7da0c653cbff9266bc3b6cdebb8a7cedd5249f8d637f247f5a6cd020ae19cabce79ee798b3fb6eec97cae5ecec16ee71d3cd56dd0f9e61e59bb5a2fd8cd39ab77339c0ea8c4e955ea4381182471d0213353339a049000c31540303024140c87e3812ceb3a14800d8ab24c4e3e1507234994a32086611886611086010000000c200431c52cd20240f98d80fa372520bea03b32db02dfc3c5c0646f1b737b5671ab412a4bd3d0b46d8338fa132f40f8f97f0e6842a284a96cfdd1076c01395441522d3f82a6e5d29d49a69e6cfab01634d157934db4c0396e954670f0205056eb49d63bfc6974c9b3a92ab1d84b2bb7ec8f3336c46497d4e722abcfb4874887f6508536fc821b4fa23df3138578013124db10b427a358fcf9a2cac1172f72e458467633bd79682c41ed91b536a90b4b31d5ddb59f787cabb1f864e8fa938014d16c4299fcfb881cd59999483f8900b9ca283b8bbbb9b90451068fa0516172a669c0b9ff57636a0fe534bed582442000c630a942173385851b3a1f23115bd1187bd05031642f476c7ba424795252bab50139b366b1a405306c6f4ccc9719f1b67f155d434e3b09921dcfa2e435087f9f8b7118c2b44f239196a50a51e8c719c31a10b57a80a4a83dc42045824254da68bf187a0a7ddc0006a67c81f8d7b79a5171320507f60b9825d30ea3ad38a751c169c4e5be36f60cb1d66c30c0eb092870fa0951e872814f3abc3225b81f51ea6c868c0cfe5c27dc12181a59f5f1b78dca9fc2bf341c594c3c0e3b01aba9e9ab10319139370dc491baedb28c27ca48d09791302eed9c8014a33b9d86b6ee4ac165a5058ae2fded93bec92352d0696b16a8a5bf502c842c3d6f7241e152d5142029d8dc388879a6c5dca08eab615a483e87147525290c16f38434eba1662fd488b6243da065b71fa4f00c76499a503193fc16a7d8005de30c26316d0a833392e38913ebfd7084331252c47ce24864338b0960094bd64caa963c24b9c1beeabd95ecbb6121697eaf734b3391c6ac9af1f4d1731ec476f3131b16f25c8198a9af8489b787cc6cfe572ab340d2382d2e508241d27518ed748f06eef1af2d909abafae0c5b92601d650e0ea8204838611a26f1c6169273853980c39177cc37c8bc09e1dcc129303f7c9817ba7fb910736e3ac80937278ff91180543eab0068180e8dd7a3d5ca04fced799bc12b078702614291a96f1da2323c3660de1f3bbd6263617a1980d2fa29d96a4250f52632363131e6a0ea090a415967ad27738a334ccde5a94338abfbd2382a7c33d9c3d6c10f0c38b43037fbd3c18d1374ffa8645cef65febb6c2fb11069ee80c7b970a7e8ff569295292749093c1263a56f1b16e0a72eaf490e80584701f5eca3ac1ac0b356c3ae44249a42314bb8c0d91cb4cb20c86bd8e0eebd90b1da7ddf774a632f289451149c6b2dae220b2c8a4ece63fca9bfc56ae532a9ab4f1e2d5eed41abbbb3a8ddf0c0f33830ac31982157e4fb8d9d7361b2584b6c1e8a8b1c1d677cd7869aca31f2e2a72f8c39ffac0d638db85105cf4e368f14b511a7508900b6ce666c46f84846a5229e83b3205c87b759de873182ba204ebebef8e0b4a298737ba3d1af644a9b36cfd1edef8500891e781fdc9bafca08592d73de6d2e2c5573fe5764b929d817b5394b756da5276aae8a0f62e647d259123f98c6418c900d0175afccd67c6d64bd222a487440fc5a6c6ed63f3fffa346859bdbb17079ca724ee535f0a1d1af0200df269c898621df1815527a4274ec8dc3230e863158f6b9eb96525348b2e3444a74644eaa9cabcad96b0e12b7b138115bdc82ece7d4c62224a569448681384e8738495c60946fe9caed286a639a21aaed28e5ab463c9453f47b1b304e359abb66e324edfaba71ec50900ff2d4f9df249147b65bc8b12942720b846abbcfdede10186b14c2f46951a6fd37ee65d0bbd0820ddba418813d07d62cf156bf30610e0a7d6d842f3f76a35064c44b32a06227df918c00c7d08d7097baeeab8c624d80670ae83d966f7bb11a88491b95f3e87e3ef13a51e8fc352da9ef9ed75ba4446cffff645d5bbee462381938c6c8ce9dc25257b7c33860a3f79a160867317139858ea3472b5a8960fa635d10e6c0284907c2204a9d3b974e105a12dbc6d741ed924e107eb2908a1655f1e998451eb12a8b0eaf12716d96ccfb70fcdfab0f3253cf5f9e5ba446facc66777b63514acb870425899806ea596101d5340c813baf06eb94a7f42985cf290a2036c12bb4b744292fcc8d896c93420a8dbb990b04101904d8c52f909439a3f6b1bf1a397c75ce4ff4a10bd321e32f50c896e4cb5f9fe148fcd9700c7e3f38a0871935d2d3c9e048ecfffafaf2587ae45bff8a2d289dd8d47f7b06fdd5268d4a3ff289ffd04fecd8f6e8c5c9faf11d6f6fd4330fc59920fdefc30d27b85f5d73a876193a657247b1116a4d88dd0f0bcd59392629480356ab2cf17a63a8ca37ed22a32751000335a3d2dcf69cea7e0384e53e0e8c6b4c79366e36504d2ff29d7a91744bbb37aa5c9d12f9c640496a7b6cb1cfd0451cda9917d0e0b97fabcb8683f607c9d1b604eb4fecb4aa48d52b0360a22c97aada4c7096c19099171f4c07a186d381f51b4cbcba01bb996b9554e059b9e0478645793aa30a3336925a3eb1c7705e637f72ed2c613f5f8072b87e48eea9cb45e2453a168bbf559b39059b0b18098494b0515cb35c830a7cccf3c6c0e832c6b61788e0722392361a643dcaf9bc185e774cd28dacd887daa4c327592c72822724bf16ca34680c22a181f9b470231055ba0661036fea389b93a9664a76953000e59ac28b1333357f4735b530d2d7412f224b67834c5832090394841e306f96038493e147d6a24c6611e268bb8b838088c51c0c7c42f282de337ffcbca009c4be2a3af655b4db8d0898c0396a8846aa9aea682b107034fd50b32ba44fb083a12bf0cf76bae99f4d95c07ef9a60a147d43421ee19081971ae9fcbafa604b49bbe7e14e997149cb875bd5f637052c804cd1b43a4e83efa7a40ed5eb2d26436fb374a685b5c2a6e87b44a7678d66e410ca8224db1df70ce8890106a802807a6c01a86744db1ce270f35865543cc13e0f6a2ec7f2b829377b660ad860382f4711f0042802541809ef5ef222bc0ce444b424b658279930de78ca84e7b9106fdf4e6210f6f294485d570c7a1d70ad9ef721eab3410e4c801ebdb406ff20e262a4647624f95c4c651e5dc463c434d93181cbd8f0d212ff7a179344b0e62015964adb30a67cfe6cd92186507ba3057a7a71c4bb711b15c3ba44426462bb43d3b9e893af6b2a8bcfc4489d0b0054aeddb146486575b6c1b4b2a970fe4363934c0f15ecb6d9fd80211f87c575fa023b685c0a32e2cae30f679ab7ac5a2a4d938b117282a896feac5c7898bf11c3e9c2b0f6dd224d4c9df4b84395c4f16a6ab5d8900f834499202c9f8c7ef4665882f801be5c7c24f138f0233a75f8702b105a15d9a4e94bdf56973c654393939f0bcc99a588841a9f98175bf69992932d57828c18daf7ecab2ce2131f858a69fa85e9a08b7f29b2376d6b159df51798bf7c5f139214d59a03f304cc360dfd6305b810b12a5e98da0432cf78fb7170a3765400dbff9c7400560453eff4c900e827baeb3f5af08ac51024bae4a812fa35dc8e4a79541a323dff7c4be8030a69a668bafa66e961fa1f15e6c825cb13105224e745582f280bc154a7be6d0b98205848678e9a11906fe4ae9e5086021a75314836b1930ff10eec9d89aa9be8cc143d083b2292279758786ac9dfd5d3988283d82aae45fb6dd02c613ea0697c35ec5f5846db8eecb84600a20a86d947e3b57d992f00d6de082449e12fca1e6848d8c74668c266bd9ffd673baab27c7749ddd23034904642bef4590edca2d370920a194335b7767a6522b0b16f27aa1a5d8889d0a18cdfc359fd39d7153bce269b9b37c90af5f08525114706fae6e692740a344090492472cec7eae86768cd8e2406efcecdf4280e3a03349056c2ca247cb74fd07730f5f02988f4bf4a9a3562a409b7e15690c46a07e717bbc90c7631f9f0a7da046d7c4442d2e9ffcf4c6becb6977f81700bc277717ad9944768494f2e30183f4cbfad1d317f9088051facbcc162fb2bbf9b6f90f6e7b055b7801018070376b1fd09701098d03052f37dbb647badafa142e5eab0e19ea516e033274c089e05b7b1503a10e53275bd4727b8f947d7b9f6c85a18babf01375f81bf474afe58fe2fd7e15ac01e641b32b85b246bf593dbfc18436f6dc97c11ea030ed77f13d17536840a9d077a29216079042caef39c25dc35dbc5f28daaa8d31586111d9c2d642463aa2f17eb562d44600eb18632e76e35e55d40900c18bad152879a2222d28141289879a94d216e60330d39d51c6fa955699e949335d4249566ab436265e40b7863b439e1a13f6d99e4e0ad8dc77d2f29d0ad3c884ca8332ccc2873ad907064054b704227c8f424b8d237c6e049861ec6fd514d973b879a83ac4e21475d3bccb1cc567e32a0c92a50b14012a487a4504f79d1995b9a1e90ba534b34fe4586711a81fef40a4277d201ac6655b20f9c4c588bc89133f11dc2f0aa0ce71e2739b302f2ad54778134453ad8e001841306f36455dfe1a0d165fcb08502e6719f0352a9e6f9c34f10a2a84404b7b56704507cce052864c9b7dab7023c4f3d25ea03c4dc434563653857e147de5417dda63ebfc3f349eebc2e76410285b475c4e389ce740a4c69748e66a3b098158e91734ee7b2b355ad363857250ea1ce18733feeb2f35cc4a7eb091d9163d22e84b622df62bc56ae3b228d1de11c20871a16d4cd7b92d4f749cf46b9bd1ee0e15c50e370a75d40e86b86c7dce10bcb5ca261a917e68c4361b9be4c7c0b57bf9a8b88d7c9b15816ad1e99339c6ffce45489152020deda8343eac8da6d311679d90630a0e19e5a552e708050afb20cb55c18fb3c3c52fc1e5cf776dec2aa5d3677c828b07213c9aabc43774aed044abc02c3e00dd97d01177484bbc0a1c97b80b02d6cb5aaffa69a15cd5128c3f6bc958b20e2626b42a3004790085beff8a2da2439082460f41982f6dc9b3303e3e017c8e00bcaa3002d80d6f41c5b99ac32d3013e25295ac25d02e3b37b40cc95f3b9c28717020d1b8635dfe40823927e64455aff9d51e501cb995ee1132dbfd7da1a96dc17d4e19cc66a57e42ccd4de2ae266a10b9d9987b5dc281128c1e053ed6623314c81aeaf6ae72059007e68229102fbe986526d62a058faef918a58a98b37ea616dd2869151fcac13f9dce68974d25a9d1086298397b839c5e08b30ceb36a4866a278ff817b60644c4a11c77f92572e3886e553a387447fa019c066c061494f33a104882db20c5f2b141c1a73229675b2dfcfec9adc9b98e5dfb4884b72f5b0a4c1059c47e6951518b9b85c51b06c62f30899c9cf1d8f80e7009188651d6440866bbc4c1bc7ee8c2143200eb712e8344b06740459b9fa26a8a033dbcc618a1d906a6a31749efc012ebcf63ab44e2e6789b25ce39a98701c1f3b935127ffac6f3b284a92f3c045e86191415ba0eeec784f5d4791cdf00592b775c387b0bf40136d88bd503c525e524f204a62f80d947ec2e1680363952d676c4916064cfcc2511b83270242c465447176770007b5cd7aa11a5c3e60a86e9eb1b0bd852832e84d45b5dacdb4db25b26f24b0ff059f486947c3173495aac70212d857fe5b6185d432be8902b51ad684749a43a51eee3ef8b51e94c27b8270928e3e0b19441e07644e0745139450f5337c8456b285d6493a0c12449554b83bc2407741f3e4719ad1b90104fbc59a24ed7259602e2cb1ca2848007ff295f48b803cbc68cfa36f86993c0a612360f2d7400c9ba93a62c8510e7bd0d28e8271c28a397c8f9f19206430e01b4ae4d088809a4991f560517e29c1a6c511f9e6e66f5e0a2bd566bfccb218f3a10a6d506afa99e5b1d077f427f8e6eb6c13e1af4e69836b66c4db9707227386f3a20076562385eda75969a717893b62b83a17f4067bdd7b9b14cca728f0d4683a3ce85cc03c48613c97e2ecb7486f35d179567fc65778b7030b4083d906c3aad875f7e91b5ecb475373e4362a5399248dcfd4002e41ccdc881210b7adc8e149475f0266ba799902298f32739301b76368ad94398783a0d444019799e7d214300e0833b0bdacc0b4acae510f36ee89c06a5c000b3a1b495daa3ecfd8ad1bc3b4dfe8355f54b942b5b935076ca49e405511142812e54f0162522ff2c8e86a8ad6a28326442fe2651a457075d77bfae2c1d9eb382cab346499bc929bb27da6767d19a4f93ec279b339298f87cbc31a4ee256d9f99a97a35ad9be6e2da34e5ae26493f68a12cd96351c995cb20ab06ea6e8acf14f76234f86bcc5eb5d244775f31686533986d7a34280735c72646cf2139831505c392a1155d7ad63861226978241714cf875d98c1b71af188cc5a5679f164522d2d4b0dfdc7cbb573709b0deccd0cea01ea4e6d63a2e0cbb5143037f089b28306334ca18e9995e9ee94574a477ff245a918f6c1846a676dfb80f3b800f334b9edbe5cd63be1482c15894bd109c7195602b7ccd45de9c58efc97ff7be17726f049afec455b888ee2189fd641a801a90925f14484ccc4d28202515de24edf177c6c1de88b0e8aea25c749a95700ae098c0313427214a0816d2b661a68248555a41dbe69602b5a9dd43135bd018f74ff162590bf075c5ece05841fa7a5a84d709663833a7152403c88e9723e70692fe9a1f0175ab1f232148bbc88e766b82e22c6747e793ddd19d5f72dd1414480d3a7202f1106c7358fcc17efec17380c62964847c951987d64703e14f9f48271ed47f1457a30f0d53ed01be5ca322b33253672ac47dad415d028748cc3013e2699c928f4f664e17ddd40c2293929ffa786268b81e223cb07921c7ae9549bb1e53e8aedcab387b32c9a7f11889d4383ff4d9ce40989d960a1d20e749f41e178b133e483bd80ed4478aa0be033d55d61826a09bbeb1f10e045e8380c4c6400c04bcd870b8a8f39145376721c824b333dc3527e3610b7a8c481344b21d6b8f0ac8b735b69df22e3a7e9455689c304221570c6d133705f9368e7d01353033464420ba862323521942cc23f1a68cd322c6c9f544ea4cd189af0dc719285367e7ab8359d55959336b914df7ca26c7e676cef524586b5a01ae9baf34589e7c2b581ad42cc622588e996d0e809b299171c9452c91a69fc65d611c56baecfad5b239e18b5935b7fd9f575a2371c72bf1916533f01203148d22d8a08a5fe1a7fc186143e4a5424799858353231a9826a395079b28e75e7ccab9e9b8bdaf4263e7b2dfc89cdd827c7cf3efec50f86b5eb76094ff4268d9faf49e1db5075b10727148f024ac9af5326525b2d8f26de68c22c448ccee7db2faafba9653ecf7c65862650df08a6ed1ad2e231caec1562006463b72a60543b10942e6a8176f24ba6815748ef0812c8482b50a54410011c88e52f9c60eb05a1ea9444ee5747038fb13f2eff53d27ebbbe8077b1b18736b00d43e90c26efccc0e7e538350ca3851f210f3cb8086cc68cd9038f9695e1e4b3f1c4d3d1e884e47d41b77034125352f118470e1e7d53a9748a934d03e8b4477198195c53328ac33f0c210621991222115b9696a2c81b10f55f313856a0386b86a003acdd17b41ec8523fa3614536124b5acf819bd75eb5213704dda829bc85d74a65b172bca009da1b5ec54f514a9d30d0c0ece781a483fe752f8fd2ade5a42c84e4c0a1300e69441a3c0850b5223dad0211e14132f9d2da5e16b46e55c8ecf46054060fd40725d16b145d37c0f2cd5a1765217c5ff1023241cb30ca03302c9ca45082f098815dc044bf1f516cecd0a07379554562ad950cf26f799840377d603e6f3482eb83d13d21061a4bb419a0b22a2ad52124aeb7b1f7e42ab326163eb697a93509a820e83eb144c9be8c735138020195047101221bc7bac483b5c99a6f196f34aeb91c809d0f1360cac89f850fd73bcb23d3e9c2634bb4b8f7aa836b29215f08f601f4a834822726a47498e6459a431b2b31ba8e204da5137be156c666c94fab8720329256292c7d93ef4365a7f258aeecd2116ad226b962a9e8edccbeafebfd07f9782e80e85d0141c5e8b2d3fbd20d2516519fd53126ef042003ecbf7b8394bc9f43117c911154abd5faacd33b3ed31d47c5d8bb8dfd3d323e8b8be55901c9dabcb29a2d2b3e7787136ca22b8c3253b9d885543adacc0a9181c9d421d90756fb5e8afde4bc27741a71425a51a79bf5207664690feafac9dc066f990a8055760e0aaa0559120b0a053e050a016f69dc4c28ed0a3d55f5078d96b136d4f823c1c0f74093c742800cbfac1c614f82cb9f9e04eadeb3feaccca46000cd5291e24918411a0c9d8231d4f61c5e7beeca0674b722c934d53236f480fae3f3415ada4677e5b6e97df42f412d25fd2c1748c8c487c9509a6243ca1ce2a5b2622891a930f06ff221462e7209e6c7069504d4107695fdccb1801627219ed3ee23c3dbb42770543e6df2bccb2ee25c56ea4e65c632642f6ed0a729f4eb618e6160a7968c760b77f41050505cf29739ae30941e25667dd1bc7f28ac5201414cbd55948f1a38b6193cb491e6c2cef62c226026803d417e7bc5b20311d653ab18d0e98260c88f7df7aa10ea3f9a1e4a0761acfb6c96f24ac1c04f935c0f28b2457232c958dc766eb16e0b2b09d9e230fa769d49997071e4de0362f9bf9caeb58497cfbce2d8ff694e6f6f665aee96a30c13f882016f0e4e07dcb78d3d00423230972b3d21f1c2c55c682bba906ffea62f6657eda4dc3a28e542b091333b24dad6c0ff63a9b994d663bb7d4d28b29b3053e47a182e12e9a443d73839966d924ef3e8966d7d3ea4621f361e517b26aa8f110d6465724fa1b89955525950c2e668f1a4b30ea053578381ca8a19c5ce62c2da349f0aa900a335380a96c215e43a636c2600f58b89538887fa6f57f4647dc4dcbff5caa8fd6471ff492cfd41376b1821a9d468a5a2d40143b428e0f2821448c8e18d601e3abf3c3f3a722a8eac8cbc3ab9d88000b8efc250de3c7d3e785637230269cd67aaa95d9a0cf3ba7e8743cc89cd630b59e6d8b5bc88714e6c6f656d016dc1beaf8ce5434028002398148bb403faba14017507c71361cdd39517a16693e9c8cde19216c4ec87e46477328d4f52365e8abd882715dc7f960dbb9d241c61298b0782521c9de6c8d66d6c8fe6adb79dc04729e8b14cc6178e05ad90bc9f879952664fd1ca2a1adabbe51e856c7d1be40cb6a1cde605237a188ac6699ffb09686dc82a8991148e1688e99f8dbd38d85b40dc86b333039b80792c0a6001edb188c156e7f7b0d2c13d8447fe5bd97786100ebd1b24f87992014dbfdcc78875d745560b043cb096da79f33d3bd40ce29a6eda0c39464c2b823118aaca132f9d9100ef9c991119c2e4d1636948f005f25d878478f0f317387f37bdfa3b1c7b74e51951cfcc1896c112786ee4f965d2b2491bd611c49a36044cafa5666bab4d7c4ac4b0cdc3f835877437ca65f552fea781c567d4c9d2c0a1afb827f50d3a9bd6620e1dc285a9d5a2417ce7adb52669cec27a421d6f4361c4709427208a4cb0a634f7a08cd61ae4de26f5457966fb48ed84d3d938eb025c5e3bb8d1d445f0c75a9aee019a4189576ca408873edf34a301f053162764e17151d10b0e54659094a3bea2a93502da86f0a17d8181e35b4b9fec02942e47eb17b74a5428b3fd8eca8645524a8a48c497048d3dfd679d9a99e2267f10a2868ef000e7f85bd3869a72280040c237bbdacd3c488c6c038a93ad9f0583c60b68e6b4446670b2284231c11842d0aadb929d1431a0febb051ec33a4cafe22840c826295c0f542eac265a8689daca47329a983d0de1f669ff9787fdc099937937233a8412be838f77807b26bfa2b949ef29343c0c7b5afa9be4149221764fcc1a52b201293934c8e69b787385a3a98c364e7afd06c72c594889763a0f84d3e83a9ea1cb9fb06220ef1b2ab4c16ae5c7909663f39ff0c470a404db927152c05889543cc7005bb6f8ff70d4bed81b5147f1fbb4f2700bc6fbadc80148f4aee289ee8df240203b25c7cccbe317c238ce6e8f77a4e44b4d7f2b625ac823ceeb2d553d9c2323a28531fa7b70cd526c0b1504c0145ea0efee2a70bf8ce46aa1584f7003824a7adbeb746f544a1298b2302e1bba4e07a483de1714a32ad81fbf05500a70c6aefcd662b6791b18d0734ddbdb64f283a4ff409fdfa47374aaafafb3465289332e20f74c948e707290159d40588722ce4fe61a988062734e47a4f1e086920c2c4bdf157cc206a670f1cbae26a578801da111322a64468708973e3780d668dd90588add099c4840ac5d54afb40e047f3bb9bd52e795327a7a0a7e98583df8d80f50240d15a76aba88e7ee830c30014aaabd2b0981a8c50de13c4583d0e39451d516be6310ba87741789434c189e32b724924e669a69b2cac9a228a385470bdf25abcf379cf5a94fa84fa0bba2efa3a384c0bc304800e8636cb8b6d24abd890af55b8fb4d4499b89544b22336bcb04c4820fdc5dbc8a428770bee3936b1478e36cf55607d44f59688fba01b995e54a24d8bd7f875f0fd8a98df01795e115c991005dc8e2261113687d610b445a596da35745c68b7dc150548ec81a9921950590538602dd85a5dddb4001d1c2e37ea4baf625868f8e6d8ac9119e5484a313cdb8eeb6530557775e00c09272135b993930f412ed0635ee4e65924f293d8e7f99017ec37cb41e5438d2122d2d4e43c7b4e289aba261e82d73e6a807dc5ea6e01b14ed9f7a3cf83c4abbb9e53dd12b1a9ee11642da7747cb71caccea03c4820235cf99498540e25e5f12610799d31e9b0f17d01d65eb10b24aea2d8e71d9809a54140d98041b4b8f1bcbcd082141f53541902e7ea5a1d32e967a8e711caaf61c1cd2cb5d4604532e2c69c7d40047afd6c5c69df593595a3236eaec2b481f86e0dcbd99e857a9dc24c99262597e76363947493d941403b2cefeb117c6d79fe6c663fa93bcf154335c28f6e65cb7c802c502fb69640f9a580aa01d31c446d9581d09d2cec45f596596c333bb24999e266833338bbbd621bb28bd5378ac4742e2e8634d2f99269c5012e3420deeab6c98fd6a725c3d44320bd46b3d84b11e234a41f1f6f4bd5d38b2cafc89c1e4710080a89a6e14d93b1809d9d216dfc4a83b29512fcf20a8c11b1197c49b42c4e3040baa8d5faca87dec1ecea146542fa79b08d976c6359d2e530253ce3442367f18e12e2d7b00ce1581528b29f87ca85b006c675192ed1d5d7785f723a939ef911a1c4c2fb2c2aa09abaaa8fb52fd3880616566e85842153da86ed48f0a36259a7d2cd4315a05dbc2217438ddd8c069f42c3e6356a2b8b0718bbfce63067b534baf27250e4f7cef252430d02adaf35c1962edb8116aab0c918e36cefe05374982753ffb80d0f867fe2ddae359062d234836b5332156049fe0e2ce210d65c7d0679d3455febf877908058d75a33bc34d1155e5614b002b16035be28fd492e284ff9b2543a95252fbfbf3405263dcca5495f3f310d92bab89023f95705c100587c35b222992ae7002d8d68e1e33e9fd1ad7ad0fa7fad9b0004c76e7d0f08311138b1b319b385c9693c9697042e82764946185b668cb5122acc0d826e9f6f8b4685becc55c531a38222114199f4f77946855b98181976685a4cd82627ceb4110366edc9343d1094972f09582a6766a05010abcb193d2deb2d37af155d28eb8032e8b086193136b0804621581e681371ca80db7165710bc4a032a1841b52ac852454a9d9b1609066daa851f34c766cf1c5a0c15fd844d9b8e2944fa64e4b1b0ce07785c4eddc14880e5bbecaa66a0fe1da81cf8894dc0166fb6ddc14125a34d9306d808829391871ac4919142ee48c40e8f3bccd5a06d746c0140ae26291d9ee330557f7c5c5421cdbcb497bf27a5006fd147f63eff544707d1e599a6d3cdccb52b13e7b3d654153b0a69c04831ba602224785f67f61153dc60b9220712f59f97a386e378010ac69d032baa2894957c9916c63c9aaeaa24175fb76a67a830c442b864e1991730f114241641eb297f133cf9e290421556db3e97fd04757f88904e27f4c3215523fbdc04a2c49be1545d3ee52eee72c6d2f4a3110938c5b4d0d277cb8bb564159100ea1f1c9a7ee5463cfcf3f3ce963ace93f6fa662b9017f00e4a868f8352a99681e38b04115bd01def38c7f6e07e1fdffbd6a8d22a065371b395f00ba98f954f74443441d049dbd9dea1083100ec143e63ab637a5339c23ad43f820653f567d981afb92f4fb0c3c3ebc3181fcf16eedaa7ff8bcbbc1480acda5520dac104d03dd4b1af0c97210e31168286a591aa889f09966329718f0d36963c71f9f95173281d69f94df3462cc6b75b42b9fcf85aee96164016e5d14dd4c2f300700b7ac4bdcde6a65f989faaf80087d6d355bf466b2db139001aecfd1e333006cbb47066fb1b65d2ed54026f71d0aadeeff602d89a0ec7200dc0658be932de10f78b719366b3d4ea514dfad1b145fc28fae03df2e919b4c311c04b1aa5fa8df46979bdc13f8ef5944b5f0841410a603e00c04a570e0787b9534037232078765c2aaff246d8e00d879e90f44bd790675ca399f0be2d076c27e607dfde8b5c1036f9b5eb4d475566ec6a06772988f9c7cdb294ccafab8b68d1029ef4b4f943e632b9ca6cacb1d78de13ec3f016fba9a400d507ad084ebe518a9ccf70768417e9f08a295f419bc3326c75f86fd34b357caa3a23423e01bf5cf55093051e58aa3f17d7f48606e0c77d0da972b0245467065ed236edd11d70c20d84e357db1cc50fe37484826580929a869738a6e612e4318230741450c96c2182ea25c14fdd01ba03f14335dbe9688001ad334bc9d97fb2d56971d7a9e14f8ff2fe453933d0f67f34ee0da2e39e39df3cb35bbe24ed372c9d9faf2b7f6f8b7cee4be22e3a2c8d91a144b0bd60cee21712bc92792a02f315bae3202def9804f6c406701d7605c004a3bc1eca316367e4983f8d5149f93357985dbea27ead810ea4e95e95a63b65e22001fbdd713ef207dbcafceb5603c6ac59026f414a5db7150036b52067c4a865a372d325e2883e4187886d793e68345bb250360eb3944b38e17caa60871f72b4e8e88803f7f4dfa8c0267bf15ad50c47e1810b57488ed5a5fb12cc28870f4ef9267efbd4157be6b14be162632c6987933554ed791c86426b0cd8eb5b9b6d368ed715f13ad858d40ea35cd5262d3901a4d83f2584bd5601f1e77b2f8996d17eb36061f7bcdb810dc7245d5673d1b56078ca7c2238f6bc5f7ce097b7c481a87be064e5ac57144dbf9c21c580340ddff2894de13aaefe32d9cf396cf0634bf382d20fa6695d154c7197c9b8ec7506e247d98d3df1e99fe02ab492b34d32a26bac2ce32c90c234ad18221e587a05ae23e5359ac0284039a1bc0cb1d8f645a75b464cb545c2d748940a6885b9c672b35a4641e340d4488201dfd4e127993de473e4dcc83c159fe57fee01b80f18c36bb4db41c4797f5abc90273d967800de7cb769e535edb4e7946fe5a41a483a51baea65d3c3b70e24d5c3940a809f239c88097dac2997a6e87dfab5f27d7f24901876aa2472ba3d69e480489f91f9f34bad28985cd01fe617d6c7827a13dc488adc3e89bac0df29abb54389fd666868b784e5cc25079c0e20f6548aaede97e0125f512a6ea302a581571664aa91fd86ff9de97f27bd78730deee2af36d4666c07268490cc49c62b6e3ce5eaa256f08b39aa10060f86e8204d9287480e4b621732055d762bfd7f6b227a658a76b1374564be16a35a70ee2b2a7a087b241e4cc1b2de8fe301a902befe769b6b85ad03fa1437a7127f2eb15668648315f18d6edf703d65d94b5de4709ee70c54c30f5d64fb692324427fa37e2884b40457c3d9a9ea070dd32d642ef98a95bd2d7103c361ddaac2366768c2a4588885ca84ef49ed30a620bd5b0fec9e50a8d9e98223425604a6173a597a0e0c3520c6695981c0b3830224d92fea529e93106ff9f5fc3852cfc2ed6a41a1a8fb5da023e05ade2af9b9bc498ec31638be2b226e3aa893785508dd3304f9974b92c31da26a78604ab35472c9056f437ed387748c23196e9ff1193b0979efe9130d33344160ed5621f20509c359ad0517ad96883f8faebbfc795d2d062783819a3496411bc5ef86b059accc2eb5e034da126a17aebd366d28938cedf084795b3372e3c9bae5ae0a1164f8494a409b81741026647f8c37a54ff96c342183a89a31dcb4fb9951154735d3d0a3b3611192160bf5ce69ad7b09f4eac59573baded954a3d5582dcc52346d30036c5d930a8f0a09b68cca937e181e44df5bda0e10d9908948b365b970ade749336c0c0f4f1c269c5f93b61b27e5cf22de3310095f1c190a2084e1471b60a22ae41e80c5c2ba892a076deab3bd75a45d04593865604cb6a64ac15635e35de8bd048edb6f0d38554996ca2146f663d42afe548c2393070b04c5e8e0009fda196379f0ddce8e392026f5d116dd889cb9058313a532d82d117a85a86fed278de34ad703c5d633010c2a8e18f2474c7d4eee97994096c1e0108520110d0e06a208bb224f83e88d46185096b3e0bbfd44b74526acddb4fd824b4a4d49cfe5b73817b1ef9e33200eb09cc3dcc61028528b63451c01e078167ab04f50dbf70e8f68e1629460e4edab7b7e037f0691cbadc5b9c36655bf0328f992cc0cddced284c1faa3e1dfef43a9d91dbae9dc3a64486aff7434c39ca5e6b8051d454a9806c6c23790748a0f1eea5e7f37ac3044ae3c21ece803ae3ebdcb964fbd27437f463469659b2e8c2aeb8b1c70e3a7faff68c7956abaace1f3bb6e81184afd2fe354327d20d818ddf78f4c7c81747ce2343afe07535e65ba9747b6b223bdc7d362c06aa75f962595041d408f5e5a65474db4cb7a755d349a1917989dd1949fb80630b14236bbd60a313ac405f09ad227936d8be98be57be68d73fdef061faa7848795151594f0d040571ff44776bc9d79dda81a939185e67195f67f984c50258b8a70add15d6e8ae310b1ce0407db687fd739c9e5fc90115044195f23f8cab77ef195fe3a49b6862b4c590de31d034526481795c73f02c3568002ea85d445fb6b1e4dcb7321087113348c84f384b34df16a7f237e17cf8c543f463e9239f3bab68d04590bf17f0f58ee9434c1c29ae782ee3e5dc6f0798773ce79b82ff15124356fedc7f96edb9801eb4e4041a2021e312073b5318744c1a1c6b14e86e2cf99ae5de89a291e5b5293b484068ef72cae436abb4c0b6d03615690d43b08581bffc74019fa8e3444fde35f91045daea9fc478468583b6d571ad30ddfc2304cb15bc04c13a46803bd805e660316d5300e3498e7c1f4196761aa731011d24d9895dc2927e34adc7237ee42463585274bbd5dd711e3a968798d2ca294f7b48c81ef16878c0854ad9bc449d81fcbe961110a57e99eef1858a5e8d9f62d5fe5d2284d9da4aaa637daf150215b06736ab2c5f0723d0f68e79ec41521a588d670c2ad0dd0349c9b64c5496c466517e077f52a8c8afa0a59e551efb253fcd3e554fa1284166098b06529aa97ec918ec4e479388e5e18dc864850e5bc05842252bad4ad93edca857ae6ac6ec5e1c658a488c1b06d692062ec9a63ce8f87e2ed2d1d493ecb3fe8993c42284f5311674219235972ecdaeb1efc74feed50d3086f38719aab3991c3c6cf5211741519ab863f6b4d5f4299e7e832a52b30962c677baeccd79cc823cd155146d08eb699d7e6bf7a089cd5a1eae84708144c64578d20102b1010d7f4a42ac0a21404b02243a01965efe8371569cf7f59c1bc894a2b59f7a30240dfa8157f02e3af0effee7b2b03097f7e0a3689abc8b5a3414c921155d29ebeb873693092e0de1f15ea5f123ea24dedd0d159041f65db5867bee85d56f9248ff4b0f57a5caab5461fbe5ecc1b9b524413685db363589780e7daf644d28ee37f906f38fc4336b4ec0a1b7ac4030f6ab9ae35ba7cda6c672dc764e3edbb60e9d1be58bc870f02bc4c159dd16cb5eb713bd132de1f23095553e66f028e23b0ab0d6fd0193fd3490b0e402287e32419e7cb668d689d25d1bc3c6d0dd9bdcd841c087d8338a7f4b12aaf06c61e799808b5a80f79777b65e7b93b0ef3b66d2a6aef856777ab3c020cc8dae28b6aa6020ed4de4cefad58646e4ac0039b6f0343469ddcd1681462cdc593000ed0867d67a46cc718c773a4f652bc4cfe09d6759737ea995235285b5e7723ba9dc52ca15faa63cd342999dc060f6323001778535acab5afa40562dfa4894a71bd5ffd982136e9844c7955ce9792926896b4344941556a26edc827ec14519845e723ab0a3717efd5c7df22915a050f4959bd4923116b6f13c9ce68f87028926d5fe03a4376b690e63d00634b9b380ee79b2c7ac66a063a4f7fc086be5e874c61e0ba692cf0b1e16302ba1ae2bee104630a9c43c4207b7e99484cb4dec0915783306f9a08049d41cb2eb2ba61ece541598f02df88062c75bfc9064c4da8f3c87cbe707324349625404a352275daecfc314927fa5cede5d018b18e75dd5cabe6041c6874a8c48cffd838d4302ae0fd29be986bffc7b9f450bfbcc1e64134a13dd75001d42062870067017fb05469696e7071e45d1628fbd024e867f53c1a5910fd0bd8c95ea23ae11c2f81626bfce1fcb3070024762417b450aab1f82764f8ac01ca74e552882862a3f897b0b1d3c7a44a00a7b48e73c4e9aa35a9f5b39ed7600b76b5a7f8c57c742ef7712d3bfae7c0377ae182ecd6e8d4e5de5672fde51d42413d1bfbd573f1344a213bdc896f70c8c84709b70e291a7c38b4bb23c49751d7b429d33b0e164982313aa64d236344af68476629c4d127b071a44844e1ec6dd6a0a1bf91ae437db1ed61deeba9922ea642827d0e32321d3163ca075d782bc311028620cdb5de10b8e4d45e1b62abdbe853ad875c157f34c8b67bf48e2e651ef3284a7e6bb356b72849a744d7869033106c2e735d3d3508403bd52c010c16a41b579d0b822e808e0c583f4f4ae0452a78686eccafd5664b524ca77548f28ea33cde7006ff27ef0db2840ecb521fd2198668e6885f4c5719846a7acd9803ae99246e15e4e79cd8db39a1b11b5322c92ea6cc49dfa2f2b1674a0f35ac9f5e63b8a0452cbf80041406cc969dbdd3985daa9cf92ecb216ce5a81d9ef0cba09e673134d1e33e8c5267b35e1358ce2e49696f40af33f4b5346096bdd3c50c9123431dd1339df1771465ce71ab145e6cd0d4486549e53330d74a4651c0869382df9951359a6be34672d310d29c88a6508f11e073980935199953a71160af7e2a5c751848ebf8124e3f623f59dade4f1b947b06965c7f4d6b79b61813fcde5e8a27569f8397c3fbdf70ed4b576486b44e9e4a1a385f4e0593888c4b1afe3a03d778e2a7b4bdf70857dc55c6f03504aa56569ac66f9a9f8049eef5db0354c167423ec69359ebcc80f2df28420a17a6690be3cb9fc58a99501b4d043746a29a8eecfd1c9276d2a53eea1a8468715697b081f2c8eb51c66193aa7f81739e89be6be28c45f092000c912bc22918440a6ef3e798b74075d18ccf2b9dbc055a4498012801edfbc09ffbe69baf4d0416f247685d0a3f94766dd48cf45653559e7c7e5a261af00146e4bfc7143735e534fcb52d0ad1bec84206643fa5f3fe6028cade8f5c8cfabcc46faf22fa574ee86737144a5cbe668cf585375438e6c32d0b1ea19643c12da7423e895fe7bae83c5d10ef0b21c06493eb8931850a4343a9e504b709e2472b39ae4f9e371d20b44b38b073fe8dad5c9d310da7424d7e8be2eab5db1d6ab85cc8dfdf1741200e54c67179fea4075d9a9c7a4f8455069f059bb39d020c22432da124a9b823852165e79cacb30f6473e8afcf8be954e5c1a2894a2c68cb9ba8f5458cae4d17bc89502296aade703cb8493029f15c27ca849215155e7e5921ecd46ac42052507e99af3e0e2f8e4747b98f3339cbbff1f0c457b151c3103456357ecf43c97a0ad8883387ad6d550fe7be8588096dbc7482d49df3c71a68d4feb604ba1038e7be6d37efa4cb9736f2c7b2e84acf9c90bae2f6b490ccd7f682ad6cbd19586205235d03f7545d2b36a04e84c9d61c835ac5a58aea009809b46481f1c79c79a4583be8c0df45da1900dc60d5686bc0f1e55d053ab1ed27b5f036933eb20e24bbb6e9d580eb3f1920e62d26bfba1b8141788b6606e5232ed1f1d356b10965e5714a1840894ecd0748eea628c33608b7a611769dd776143aaa8641f6a5196a7c031eb1fa88fd71f81fe2c79393cef967c5d391d6bb4e0bbaa4574b1b0803ab045256bd37a1d55a82401560b221eca45fc1b32d073cc941c13af82170bb64210d45c1d824a9b10b42304a1bf411e113d9631537283fa843acdec8d9fe1b4947e9681ef5e8af04551824a544eb097c5727b11e0a88e3466aac3df86ce73d65b65dba8689b9f9082b343dc7290b96806a059477fc88c7ac81ba5fa155b91a14a81b7c177812810d1bc075e79b38cd0de2b3ed5508811effd043ac73479e1c7bb43d47270de84d0fefe0b6ee706a219b588404901cde4aa35280180834a662292ea4e635147568b88260c98ee610c5d54797b9375d4c8b5677e0d0b5e4bc4517a1d43b242b8c87352b78cef161aaba0c8622b08323398db9eb18c5357d969321ea04cb6e90636b94b1bab3e76f5b77370708b8748cb291f30408c91ef13424894e1f2e76c3fe4c3a152ef732f082e17d1c1f7ff118299d491038e8ea66f87e00f87eb443050eb8dd41b05adba3ce23757a8a9216fe03598b3d14320b7ee49a978334ccaaa0b46501b5d53c7a30a305243ab6d4c5da81e41ff554dc8620ca0e1de522c4b8895e272effde43973649b097b1cdb206eb57a7bb976344d13a9c5413cb83fc99299770265c988d5787b93603652d303723477363853bee3ebf3cb69a5b9b2ccbd466b5c8207d0b072d75ec6291a8b4fe59e3c138bd1f18614d46f1d73a0055c5b914c643ba73bcbde0a89e324b989050e3c20102cfae9b0172f9cfc3d159847ee34ae7e19f69200ce3ce0fb376c172e49a5cd4000a06ee4ce868c84a76d538946a0812671f518e6b32c49a05b1a38518c63b1ec69a82cad14298e39d0c11ad41ee6c428871cf86b1c66072b011e471c786496b90b99a08631cc6a15c3b98397046961a0bc344ba893c2ab63adad5e0a180d401a1139d6b3dcd81ea8b7a0e977360ab17879d735560476806e28f05d776fd9a53c8050ba91a64d865f5fa41c90a41f903d9b4027cef16d21e414674b0f08510c8697a2f69d20cc18c5c438c5587e1dcacbdbc952242d0021cdfb35b29a0e9ce97a18d1cd927d3f8ffbc406141e802c5196f06d1b10c07be45448a90e79d2ec7d0b2960687133bc97be6938ea56217946b898ab16964a99bdf69f06faf636551ba557f654bfa1192222124cc7b66922b609bd44b3380c72c983a55508ae853a82c5da6a6aa389f771e1752abef4a7e83e9292d19af471e99bd6fb1dadceeeb1e374acf57737821423c60e9f9bf881f7063178d353848c7a8ba2f6ae096ddc17e50cbccf9c8308f8e0f967627b2c013a4d687d4ae7dc192f4f2b0245121067c67b43f41d2f21dbaeeb4c74fb11f52534c5295493ee17e58f64595b66b4e1ad3ed464b540333ec33559f721682d1080dca7729ccecbf3c6c089c161ad5b793057f1863b28f407308f433b210c57740f3e430c63442a1a4fad210b29f9bfc83d9174c84649041227cd644f2652e1c1236e88573567fba269700edac9a452c4babd3b494099f23f18413de25ab1f468d06e75ca2eed4bf0236be6c6e4d6f04b1e714b34adda427dad5d642d1ae5c48edfbf4888fa8bc0f87210892d8adca010b5e0aa1e1750dd34806303ac394f1430a9f4d143ddcf346a7b13bfa5f52361c82fe1a7ed52dc77849b5ad860bc547c1fa07424dba848608fe9f147f1ecaefd759cc1abebd424cee6b98330ed10ccb87166384cd03ff060e654ef4ea7fcf6df6a6566e13642e5939b8aa1a8c4e8c53fec2231feffa0c2e7c6a6ba9084acfdffbe862b982ef25365d1517b03e9a468409128e4aedddc6a51790137b793df1b6fbcbd27208f82dd4690be57e6062b1772a61f502ccd572e0eaa7bca992c31f758de65acd63e78b79619dcd8f5a840723a019958b7828fdad9c84b33a12ba7fd7ad2ba861fab98cdf07f20d8104acb9bc75191d895b34c9b4c1f5cf9c56bfb0d7816fde5e33135aa89e445e5ff9c2b30eaf015fa0e1419886580db51e490a0f9b50423f826b1e032d3d83d2fd0f8ffeedd50650f8bb71ebee0521b6a8259c60681e2f6c32e1c92be19dfe8e8779d538e7dc11560fd8fccc2056400ea343c412b61c592cd9641c521d1016d208b7bf421af809066905c057f5de0762720e601acb3f8373376f17a329c78f029c682cf14dd75d580824d06bd765082509a02456b2cf47a289bb4add18aa608171e69fa1da29eee2c42d046bceb592fb9ea002f4bd4ca6d5c618ab1c502939d42eb9f3ef618be2f69858adf17b080e9907e129f461904a3c9eb2494d47d9e90938194420710b2ddb1b48be0181ed5b2d099a5bba744609ceed9939ff39eff6a14c486c4ccc951a7154071d80792d24b9ae4a9da9cfb9759a4daaa064b6919308ce224937e375f9eef544e9796a621604ec487042c7197088cdd3e7f5b7f971140709c3b7e3f60114a16cbf3688d617992b688b9fd5d87e795d843f875d2f567b454f27b212473d8ef218808b57dca7c6a01d9286cd7ad54f52888b411bb5809505e4c3443dc573d35ad2c23c9f783ad6fa2ceea787a9d5c187bff7b6b590ac56ede50dcdc93ed982fdc516cf7c03d84d7d199fc366810a9bce0ae67ca8c9c81a4f8d4d3a65a5a946adda1a1aab0e10fe5f5b03612d3018a2a25c406cbf8c2465de332d816dcff18003c93a668fcb714c02591986f7b33d8838a7181469702d456cf5891403cefe0df4876c0dfe86bbd866f8028e9a2a094cdbaee704fc7547f0823299ccc5258c1771c02fe9a0e6d7ff4ba58f0d9b8d851b2441d0e3c089115e3c0b65c7a367a3e9afa9611d17714481ab5a56260318447672e5ce06ba58afc44c628c99d747cc21a590794de69b69268448ab9d38d5125d95db75af62286c922e014c22280daf6ae7f34b9976539a74e0fca294501703f21dd5d3cd18382d8a30cff5953d99b80ad050a5d2f1daf61b9c178e374d1940fd2e9f3ac0714156b970dae85d295a5eaff81aa80f5fa35f14f875d79f43bd9ef3f1ab389688800070997f830ebe04240a055c17207bad9564f587a814909fbd7d80c5f31ba5ca154f55b2913ae05d74a5e3d6a2444004d183d5af56f6b7b25d41a713ffb4971d21284481f492410a60c9f39969b8f93e6090c189700c4a1a50dc84fd1a08e81027214ade42750e7ed32771667ba3ac9ff1ae8458dd1a9296eb21f78cbe6ce3cac8c10e32173f0c0251b9484c8e89668e24976d6364c182b54aa6c16809ad6b4c9e33b6873802453a39c617e4da1a71e698a1906b26c3d1016b20f3655c6bab5f1c80414a327ffd5712facb0f3c75fc5085f66225536038307f8c2c4252fb01870fc78d2200ed2e3a3dfe9e9b041625c0b71adc9b20126951099399e1a18969138f064fe2564a483c5ef2b4a1208b71ee0e46f01d4789438a4fcce813fc04ec596341ef64cb9d2e4071c0e66ff2a2bde1132ed62021b51d94fb6f241bd35b065f133c5f83b7f2cbaaa612cbffcb60511e685ef3783292f3ce5a1c11518929532631132b0d1a29e7e49a8fe1eb30c493f25ef05c096959d54d4a73a63827f8ad7ff1ba5e0764d28ca4ab003645fa5b07a53e76227bb517a8002cbf8e50dc353ee25ed126617336115cb723baba81e9242006e598ddc957504a35f6f0a948ad9c3b64e9c0b8380dae0f82d51f73c8af822f4e4031590addf40a6cd8017a82d95359508a2cd51cd4c6c9278e218716747258403bdf9f9f64a31ed52b00b33eaa07f6c2fa89d453050f63e21c8d2eea320395064100f0180d0000000008861c74686bd4bd3f6fa5042a51ac5bc98d33de31763711112152e4962277b405af05aa04371ec6536739cbb29605aec753b50cc920a5594b085317b141d4f50105db1423375e105d89112d71a2612e8b16109d0bb1caafe4945a6badb7b24f2473f6debb8630fb44921caf6e00d13ce7546698932a4bb24a6badf91a06380c908a52024b95ae29503008c285cb891b5bbeaa22c0c853843a8b37042b914b31a32893b160752c5d6a071354e4988c15099a25e15c62b40885c05a52050b0b39250df7451eb7109a31012cba34e90032212d9100929525258ab48cb6709923af0ba5cd44ef0a23aac6a6c690c6a608d140595c8c1447aa2c21eb02e1e89a0535c8429c736e42987d22e73bfa4899a37b927912923c75ab3d95d7eb11c49ccbe82871cb5ebfbb9286acb278a51d718b92b1820bd92f68068249e80cbd1898170c778e5a8b536baca277d86e71561de044baa57463489d19e1e2a16b868727aca7235b9eb850445d2318e29113dfc18d95e5dea605175a6bfd43c937331f4c6f603d9e8469fe63e6cb8d450961d817b2db426badb54631502d25b1c83103ce838c9029ab1e53183c359122b3773cac69adb54649924b6b7d1271d39199db3ad1ac9810b931cc608acbd775e505090d9e4220cddc206613f11445ac4491ab0e2794846d46968c70e11882702fb8d1522a7594645ac203905c4b9c738e06d468418d26d41ac8d332a7925c9c73ced73827b9c436309f70403be7a12fae93814dfa8ee93bfb9cbe5adcf89203041855082556535e4765545959516c97c5b622be05ade6dc508ecdd066320c97b4952d39482eadb5e6224449adb5d65a6b0d93e46ef1aba7aff4ec4155a8ccc842897b8b9af224a40d4dc611dc111d62716857458e02b0829c22976684ccca392380c906505c5a59b478405a97c4bcb8906dadad5d15640feaac43ad611525bb96386a814ceac84f4887436afcfada393de444decde8456bada5b24fd45aeb1db02b5619f418220a82d35a29909c4abc695145d1a0bdccba3640d72966da96585870912d4038b5947a58ca18f689c3328314b41e4a6aadb5d6bb6986294d5bca90e6a46a6d9246ba51e462e30eb1db46d0180a3242085c1107a240c93115250a598bd14a4d1157eafc4704f098c4b851e5cdcb4990589b87163da24e6451fa9d4029cc4ba4b270f32addd2536badd5230f780c7391311d6aa14b88afce500b429c46275b128cbce042c4891230af200fd0c83281e2060bac1a1fa6fabe40ce391f6acd39d781c98c9a4d872a8c84931811312f1500ba2176e40356dbd552df4337cb57d9b38ca943677c6ca18a318951b64eb91e47b8c895a1f87116ecfbcc8584b3ac3934a196a8267157489f5558c4168abbe5426badbfb24f24679818f448bd9aceb44860998bf2c2818f2745866d504d5c377c17c5de7b9323d788364c3361f28674406af25643cc040a2d1d13a844f16224c90c0f1bdc1303e0ea994a44081664022e0a921e105a303410b14528d4e21ebcca6b02cd0049483576dce842925663e4c2c3706e0712b0847066c812066fc02a2c613f62b4bea0d831b643480cae221f891a293721d2d19b198f195d8658741971c5c59817b22ad38a8c0b009dff9e04bd900c6744722493485d3c967d895f19484ad4984b8c4d4034ac58bce032923225891557901188a6205d57ee88db25c5af0c284a4490d12811266ac9b25584c30aea448e2e1e57c4d2de7bb305b34f24c9710b90beb7606d09f68c025a54116d80d564c690046001124246902126b8aa9a3608b186e58dc9cdea8b88ab1bb01b545340265f55479820dc20b3c7a1696de69d632cef1c5e0fc9182e737a902dab2f23dac6f0475388ae214a529819fd18a95203aec95c9a8c0b42a34965adb5d6239c4648a9284964430a09b02fc0ababab9f94142a5d6d349a049d48d2f0416b4d6291d05aeb095d6badf590a74950935b171d5f8ab07030da8005d88e2b4d6cd74384240dc6809482441a640d21914989f10ba115b5e6bb404c3bfea16c94e2356e2be3cc5600c7115c22554149d6932d59597a4009d281b62c28403956600dd142aaf205c6abd425590f3ca80ce1b042b2c6c5c477c513338968404042b8e38497a823271b8a20e150e1042b0c88cb8795124fd25a6b2a4e65a9d3b6bad65a670d09eaa25a02c407908e28b01f28a8b01c7999d29054a6e1a8e23c9b3e9a84f838d20445da4f0d6a48b464ea45972c0d9c73f25291731c01202ef03bb3a2b286e5618371abc599d0cad987221f5f825c262773009c0a9fc34d4c455a8aa8485ba1826cc7579c980c10e50552e5ea80b164092e05d7972c1b398678fd483204cafd01838c0a0201929952957d22c953492edf9614711f9c6819425192011652721eb24e68192952e663096f146e0cfbc4211c9ada1bc5bc252ae724b9c2b65b95db96f85b7252c92af88bc4f2030b16962e9eaa4506ccfe11d3e49679ea1c952371426048433d2860e42b4890998c2767409c462b20505341821b4cee4bedfdf6e29c733527ec13d5a448150237e0588890e1e2aa484db735006a05d04510105da634f01e7c8d2cbbd0c8ac90384b2104c611d16d0a2ccee8298ad25097d18dd4de7b93239212f3e4a4d020b7b17443854c708c6c37b9994122317e4d5ede664c41c2e636222908902d54416e1453d90b0724ef1af26e78bd9baa9b2f77849cd49aa8010717ec14a4f22ef4cae984c939e75cd54dbe0d586d890a538a9a3285f4854990880fa7b42525245c1e5a6badb5862aa5ec9992f32267ce0524a22918406ca400ebc0e34993d80a28b032332d38740b48ddc18742b1382d7241293090226c069b93164b34b460c8683d3d80e24d0b5077d07602caf9b9d8bf19397f69ad032467b9f2871073cec925546e46a5dbf0c72b4d4e84a04823e2a1a109181298d1d987282155daae0e0a7a5fe1de572b7ea39421101090882df24b1deb0d1f0d47c305559bc21994d3a4da23d368449036058332234ca6ca62acd8b06409657975195932f364138506649cda1aab9ccc1e175d4f408890646d6100945cd205422530b610c17273286528c21c4090d2d0a86c65192271f242475a1290b2ebc45b227b812aed256613226a5290825f9cd03acc28b205b8620957d50f29122e0e9c6dba691b4099a60d480cd35a82db62535203469a74c5c55397361249ee0d02909a6d6c5ccad1b58f1e5d975d05a5f60e1ee231c1cc99d9a7b5661be3dcb04f8c9323559473cef9da6b1c5843d298a973fb50a821c7988828566647538a0c31811a9ff48d01545993b9a0141b2a57c138e75cce29fb44724fe9fcd6e09c935ce35826d45d50c55e59c1d5508abae00ac293242baa0cba8a5435ddadb4997ee618622b8adc50178da1383518bf24fd6b4a834c2b84aa30a1c5dd0c4a33333333b3b33371c417e79caf01d7826bc29e1964d4c03476041535d818d31b8c9d9c73ceb994d61c65915de41927b7c6d70d1839a7b7c5e0af2c9b7c2849b192624b6898648fc1741091b436d6a405c70e104ca3909c8e0865b8a4b590058439356585a5a022b230e48406dbdc5395131af438b4c197b4d61a051a4a6aadf51b08664075d9a1f5c16ce8c6891145ba700cd980413a60517a954a157221e88262ca1a0f284d563acb0059544a634428b27cdc24ad35b964efbdbdccc019a071ce493327c04d3e41d35bfbd81ac7225ff98814f9f553ad96747cb8896991003e90c5cbd3970624c2989aa00d15e1028503c88bc6d2cdb682ddb24f24bd4c952f9158994ce562dc6dc416a2b5d6426328a9254c6bbd4de87e06fcb1256840b60032b2d2398a9b988f2daa2b222a5d566c3d9921a7e3458caaa6b3b3920d691c8aa0f264d12c2e460f293b9e0851f1d4c012c4a5048750b835f6358373ceb7b24fe49c73cec7b42bde100d63caad840c9dcf469ef834369639da0a286bc839aa83b65b1bcbb036e4a4d9022474c8800b4494539915165059aa3c510ad1b61435e343cd95c1246a25e9a0b2838d089136116c8bc9b02d48d9063562947b97d194596288ecbdf7c6b0f7dedb0d080868c4de7bbf8d619f3824d154b7de9bdc03e1747de5877da2c6d033e035ce39e79b0bed774d4e18259108490d875af00114d8bcf9529065be7d986f717a2b0ed3b5e67b34332ff330796dd50e1564f3335ace727df716f42bdedc8bf14d8a6f32ff5d8e90b22deebb8fc0b6b8f3b58eef5f76d75e7c4dd687ffee2fc6acd93b54803fdf9cb39ccf7d0b3ace7c7f9a3fcd6ed8bcc96a7727fdcddf34d936af61f34d33ebacb3e6ac0ffe9b654c996ff1d92649f345f182699a6b7a7bdf9234dfaa33d9a777f833e78f591b08bfdf7c0af9020512fec1bf83f57536cfc389b98fd9f601f7f130a3dd1dfc1967c0588220b5cd6d5eebe6d7acbbb467f798f5c15ff3ba9df91444f896ec362fe20b36bfb2ff6cabbe04b625717e09664ef3ee510a19a312d8169fe91c64fcfb1f9ce906743caa593184d50218473f5f3180bf158797cd6a38a3f5fb3ceda9c17fb3a27fd77d7ecd2a769bb5fdde8ac31acce221c6d91728649ffe076c4bf6d89674c0b62a0ddbaa3ab6edcdb06d8fc7b62fc3b6df63d81607866d71544ea6eabdf77235aba59fbfc5672d5eeb986d39a0e7eef1a38f75dfb13ee9f337d16b9aa689a62c49aa6a4fbc208a59ed4777f229e40beceba7401c13c0b7cf7edb03c0b76a005e00ec05669ffef603409200505556ccbeb697d55a7cd6392b4190faa3fbfdf647f7f4f7a3bb0be49fd5b2da9db01f7dce9ef57d81bd5eab762dfa7ef410fdac86a2798d020b88437198d5ee8d15a7fe5dbcfa3a4b22ec14ba7885c4ad7f85f50274f1ea48968a8e76dd6314d8c730b26ea18b576148fd0a0caee374f10aacab9fe20454f12a2ca89ffd9ec3ecd4b5fa62f9e78b5de4fc1caab91d7b665b0e6c7b75e904f7d3b407bd62375fbcf7cf21d631ff3e9a6eb8791dd30d2c19cc2bfa679a73fe49571f1dfe1dc629fed4cc3b5e7b6726e0b12deeba9f615bdcd373d8cd3ff3b7d7bc37b33ee9ebfeeadeee0e1558d0ef9b2c36db11f48edb73adb7fb571ffc16f4bbb6b99b3fa60fc83de57c15cdf3acb73f7803b9b777821677acb3cb3fb8a7cf39db9ec3be9f7396739eb23ef7f1f3f4cf8bc67c7b7ffbf94db5ce7cccde359bac8dcc65db07e0bf7faff927d9ddb96f660982d41fdc6ffb83db73d8f9a3eb2eedd1fd657deea3afe33059c8bb9806ff4c9af6e0f73159dda398260b4d7ccdfb332c2f0b4f1488ae77c54199151f6121c6d1afa3e255bc13da93ace3e7690ffaf9f77a7fb7f2c7ebfe0c398b3d66457b8b8328a9aa3af2af7aaf8ef521df7cdd9f183dcbee8e389a2fe66fef6fff5923010854f7b84602102a07b4a3ac8ff9fc53dda37fde5402fc75e92a0153fdf492af63dbb1abac4df9fbf9af4571305f20b34fbfc638e66f914bbf9ffeeedbfbeb9c0307b4a78feeb4e7be4fcab6b79b13a0fd8a5c7acb41e4d2cddfb1edfdf5fded0576fd64fafab26d073a74f375cf53b6e58076f351b61dcbee8ed8f98fe9067e0efb6aae38788471f493ab8edae2e003ceced9b3ec4ed86c768ab9345b6395fae666925c403a189b060fc3c939a7b98043a34a15545b0db9a11e2b19312e78cba2c523aeef3b046f69bd7574ceb9ca4b5487ad40eec30826434044d828a9861a0c9991015d79210900d6b375cd01811e5e7051d2b1446e2dca1a932a38d82b84298dcb2ddc4aad758ede9c735e632b0b915373b4d61a6b8dec58675b6d02044384a436411bcb1cb25faf4984b2b71d4131610c2842e690043da6fdd1e8eb2e6b7e8a2fbffb634af1798cee0e3651076cdbbb976d7fb48e6d498ea61b509e2e1e23fe33fcf3aa7e611cfe3aeae6b8fccd13ec89f9f3cb157f86cc5b772fb3a61f13c3bfd5529dbff9952fa8bfece3df92ad6a9a5f30ff37ed21fbeef96abaa9bf7c415dca3efd3d936d7baa8ad91fccaae33c46473def937cfcedbd240cc963c4d3d674ebfad5a57c414b659ffe5e4f5597ba7e1e237dd4eff3d7a59ce43ad8dbb1f31d077dd44d2dc56394b23c465a4a4b712dd5358fd14e37a03c47ea96da8571b4baa56ef118dd097b66487321b8f130aec2c05167a67b6ffde9ea83f7b9b7dee98955cd5f6360bf67fa699a9efbcfc798436f974e705f97f6ec3efd11eb887dbf1006629dfdf7d374c34d37deebee49990e15a427527cd47d78584dd3c7f8874fdc1dfc1cabe83b459fc7b1908a310de73d0d8a817dcfb02d49c2b0ad4ab2b7f3d0e76d1c01de666d605e7ffa986d2f867ce1636430fe619df4f7e3139b73fd2d06f694f7974579e9d38c3329e6cf59de9b180bf1d29832dd70e2aadd8a8530c0ac3ce89a0163217ca2e327d39edd7d753d1fafdafc11c5429aa729dbeaf4ee58fccb17d2e724dbeb3ee46ff275e8eb5e4c23a0636dcad77f1ffd7c0143f68dcfd9564d592c7477d65eaf3f893ec730e216431f5fecf1c742e89f8f7f5868a71b4ea089818763c08085ee04cd8aa3cc95593b6fc6ca2acc47d3b4cd2a4852679d71ce69dae3e0f3599455a0e986cc4fbc3a78714606e6cf736dd5dea1023ccc407eb659d8db1f8c571fdd5bd0af48de9b6b3e7312e7a2bbc33f3f38cf1eee65153dcfc8c0b0fcdb9cefb33ee4eb32cbf39ff94f613ecf35677c3e0e667ebe88751e6c6cf3ad2eabe8fbc9ac79d63c6b9e9e36ec0fee29eb93ff3eff56c7fe8822e73cb3692fff59c3336b5393c1e6e62164b6cdfccfcfc0bb93ffcc39b7115474fe0ef0f999f5c9af3b39848fb9791e4dfefdfc8cc0729e9eeca3e7ebfcfc7c08ec0d7bb2366c9a662cdd9a8bf29aa1666500b93a207f2b561565a083fc0fd6f47bebeeefbad177b03ac89f7dfa695852ed31ac4a96afaa97647dca3f3f1791e425337beabefdd1e9eb589f53774f129fea7396b5313f5fc8907dfadbcf4577e7fcfd774d2f99fe6e15310ec9595d06febd1eff56edfcfcfde4e7a2f3756c2eba13f6a78f87e9e7a2f4c774439a558c6b068ca3df5c19505555d4a1e63a30c35b3ccbd13b9d599665a9ff07fbec922c4b8c35c9fa5c71ebd7e69fdfa29fa66c9bd3f59f24fb83bbcf6d5196fc936575f9e5df11e38cdd7c0a77a734734ab6bd6a3ac10ee3dfe5532877851cac536eb8eb986e284554ed1d2ac01b3f05bc5bb1aa974fae3ee756f72e9f7c0a77673f89a1b7325f6295f521ff547fe32f778ccac6b02dcee9e5b732ecf9e49fe43ecbdf64c9dac4bc7e73effde553b83bf74b36a7b732afbfcde9e6a3bab4079f9f9a5f41b7322cc668dab3d309d0bebfc2ddd9d84c3798e2598c8e9aff52e841bf7cbd9a7bd5b7c2ddd1ed569bbeffbe4ec7da90afdfac7077f6ebaf49d78d9e15de34bf8299338e9bad702768f5ee54ecee7894d7ae10351f7d11eba0f7d73550f669b59ba63d356ffe8875b2d8876b68ba21920fa8b40c590b62ce18c51100000001b31700002800088683b234d10335f614000a45ba90a498501a8b47426138180682c100502000410000401080611086623196ee706b24bb86af8cb951f749e57dbd23f830dc0547252728c144e4aff9753cbeb4de4f851d4f0661944eba0797013593b8d0641e5cfef5f89cbda4b1d905624a450bb6e927a63fad4b85ad3dcc4544420e2864eea051610dc2d551afb24cb17e03b471e0f2662ad6c2c26d41e922297d75f1456e6963d95e63572a676ad6f8f60c8d9dbb205ba78fd6cf2e8a6c2737141d1c80b00c9d77eb1a94988cb8926dc1555ce6304ebe95069bb3431c29e66b6f7591b9948ecc42599464e86bc15de05b9109530399d9d9e1dad18c24b3261ed66133f9e78accec9d025c3788cb18b357c8f5b9f8792a1e2196bbfa8afa4eb2667ee4b050b01530194590eb029296fd6f553d09aa49e1e54f525e2d05c7f79ad572edc19a7c89701756138ce2e021da0c2f2ee11432e0356edb228dd6ae3fe2b322977529723b786be1fd4542129eb4cef15cc3d5b175a643a5c80a2611fb2cb3dfd6cde077c4b8317836e520b330c84e606fc1d354e21e90abbe792bd43ea6a3eebbfe50221cd287ebb8461ea524a5755a0920e9a2a0c5997a4b3f301af1410aba3a6987972599fe894ad74872bd5cff0c7997027a184b3ecd0f5ced8ae403823d001975000b7c84e4224c38bf5839095941d5ab20c146fe1067fcac790035bd52c8738d6bfe3ac7cacac2a624fde8ce203756c66b0badeca1f04a04f40ed619cb8b4920fd16447d203e7982bc0ce05c881063e6eb78bd88e05793e5f74835726607bf24264617fc0e46f3770c2f8215370e9eea7a0038fe09a21c7d2f362f24142b0ebea63a8ecf686e9ccadb95e7a7e4ec6f54a3b40e2851dd1af908e0664d5e69bc1e3dd0c3e83f29cba773a13c4c24a3804d2af3364af28bfaf17b30c8daade85fd6644b20587500abf369d7cd4c6b87d4592225c411f5f9ca14a72304b53a5758e9c07449319db1af7c0b5a9065df98b42acb2a0f7a3a3db607fddd1bf4d6f343c6d46be22112a9feb21588858a6f492c46a98c315b6a4b75bef4329955a22c9ce4da732e6cc4e8df314b9f8c919a96228c988ce5f4eb8d7832c4dbefffc0e9e7e7a4a0528b825da28a7cfc6427bbf5b9136e0c49520da85996387fdc6c0e1969ac24ff12e0af8863206a47b665cf7f88ac15e8827ad3104ed7d58fd660d864770e72c281c7aac92ce4881cc0276989a66bcbe9a499c081ffa81e7aa809a4940622e132dc346a86ffbbff6b1c08908527dec3ecd8a03256c277e2c4e14e4c586d484419d45788d48470acbd97a346d1877912bf1ca06e2d3c7f11548212a5232b45f46eee012219e04c2256b98f54db11aae2783d40246f86a8866a7105c5787d7e789f3a32c79463a21c7e96138a2b456d4736bd84d09c1675ce6b5e46b8633efe26a73c2d7a2d8a6ecd7fcb11869af7e948da6a3c7eae4247d3430dd5c961a2abfda960f43792da51f71c720ceade781ef5cd3281eb4cacbb14530989f3b5efa227e636a6175b59da01c7a8f399caee74b87fa8e485fbd3c6dc2ed3417745a588b7c3c8c86e0d1d9a21a83816d8608b0a2bb82c0a583d462185f32a40ca9659ac42a860a2b70cb44fba35c09a38b3ad8e6cf4ecfc041876a1429bbcc5e510c3060476150aafbafd01e4c32956896bf2b30a21228c7bc8bc74124739dc59860e06e96aeb3b2b89993cbf213f123a33eb486b073087683758b01b17f209610703fd70b8330939ee00c9157929c63d60944d14596ee0d7d9acf28a5699c7d8605fded0898c236213c19cc656fbc9d46ca2074f55dfeaf83f74e5162a69d5f54e639574a89100b3782850dbc47ad8e98d6f28d2814900a74680358b763b8a7f6bf8400eca16769ee5da0d0cbc6d2edbcd6cf13edc07df0c6e90606f21c609f034cbdf022ad220fa1518a87b48c16659edb8c68b19470c00cfae6c075275059944211fbbbd7bed3ea5ad8795d178263748ecf521723780dccfe47a3f3532b6eeb8c2fdd4a8413ff868a41418fc8f998a4a886172be22896040875832ccf48b2a0ce46a95cdbb921f9d20c1caab5eca025510e60d5afc3619c445db4eca630c8a1d737bdafaed6c0544a13ee2b1ef6f9082b7ab30be0d0e6edc0e5c66c7ce27ed7cdc894ca1a4a0f73e79005ee09545ab976673d369bc4a7b817da3901929c01a2f3676d33d3558a59da5467121ffc89dfa53e20c7458be408ff96923baffa33641b84f0555e484f0420db5a20b7be5e6a928a0d3e68998e9230f96a9abdd800bc19f6ad7765bfe9558196ce5e103c3081a0f856539561e7b51a4254e3030686c5d3c5492c04cd0e0c007fd5191a8b12dd8bee11bf838aaccd54637821741eaf090cc39b14acc52ca6c46d1451d17c1139d1ea665c254572e4e33ce513bd9e389d072a7f9bb0ee4966a7292c4813fe67b57fa8b99fc49681a36b0001703003c3f3405de0e2c776da18b033484ae5d10ac8d9125082e40a1073bbf0c28bf7e4a146979a913f54e53e6e2de007e57a516bf264ccdc5106dd488bb20be7e54a6ba47c898858f26d347fa98a1d581955c5d8cb518adeb093d24167915624649d41c6b25e3bcced7e62ec93ca7340366731beba6e9a3bcf59dddf09cbb2cfeaa29850253b8a7a57953ba7e536664a67c63726f78fff27c7a69c10b0584799b96eb1f91fa5c811090be48c1ef4ab231e60805b882affaa2532824075ccecec9d1602ff4c8a5cf819a47440f6b575b2aa7244bc1d4fcc74d7e14ebc26332ef9ca035feda29d5903f1ebd2dbc233d29a97c86beceea16cb26c5253bd9486c79926d570291e73ab7e64f22ad594239751f4f18f32818612681399ed0a147b4c393db0de22d912cbd9ba4600fadd5c4f3761b46115358f5289549b76611cd40b9227bf236cd7b2b5f909dd5df7fbed39830ea4087ecfb6570f9d76aba35f6479cc3cc060924d857d67090c5a1d1d60ecae6b2643766dd6cc2bcb96a8e5985c8149edff7114f0c4dd02027ccb6f2eec561124abede3ce73b9c44c7d74a00dd59e1606ade37b7d1ebd976da49eb022a9cd8028712a2d20ae14fef6744f47f457dd2de16b4d249960a8a6ca914646de9d41f1e9e651e0a766727c6dbad1c7dfd8f8865a3b539f14c857de4f12309606f73e05c333eebc75debf17b23fa3fdbb4e8f733bf3ac2ac17925c95ac7d7a4c18024e8f8dbbe23cb78a9e3d30dee3eac87ef915ff1ce40040e2bf218cd009c204a2ebc9963066b40034c43fb457a81cbd467472e502a71777890b619f6ccf189952ae552f859083e6214e6a69c891fa9ffce49d6fd9b8771d186e70a2536433ee9758fc6fd5194b8eda1f7d650e36cfb823bcfa7a0653c5799e55da2224ae1fd97050bfeceba57748e9e04c496b9f09854cd1e8c8fdf2ea306c2446648a2a8f9922ce97a3b570eac6b0078f1ea9163c01bedd094b041a2de827a54d8271ba6827597983bd9be5f2d7bbe8604ccc5f716fb12910ca2ff4bea038c6b2be5637543c3e91e7c854620d4d970bbc4e473fcd91bc547535fe54526a4783c00243add16b1e990a2732d1e63aaa7a08b9609868f9c6aa5fa2a32c46b3e67fa27601e226a2fb9e915321423ea267773c74b2e9ae9b35bae0d75db2f9d5f2e9644b88aef0b4808dcb2a7e5edf1d6d3bbdbbca613126dbf6ea46fd44c63b0fc093ce1897049d3c379161a7a14fa63d91c0be9b5ed818844c8ed9dc81c4b49a6aef670601ec19e2411278e98270d19cb9de188132cd4cd7e9ee6c4e44ed693417ad89c56785e6eeef745497c638bbf91b470b4aefa750263d1141d87aa9f135e1aecc542a712a1f3ce9bff76f0ccb684b78889dc3fa7c46e77369ab97363f03c713b12b95cc3955c50640a47d4e67c07f429f66573fc03ec58cee88eeab2d0451121e37a656905761f40c9d4886fc1348d438667a6532ec2584c9953f3028e406444174c45c1003e1ad82345e2b560b09826cdd19fe7ba56bb8b6c2e2901eb7646cd051ad7c7ea3fcddec2db90128194e204ea2423613c9699c949eccddaf77bb2d4d0660c2c37d1d79e8927c46fb2f2b0a7399a83dd224d9b369a09d866c27d17ddad392cee25aab8389a4f1667477fdc382e95195dcd77268f5461e7839a0cd052018aed1d5d394b524a88d252767129ec6b553232d96fd88cd123b5bfabceb7b930abd63a6d14bda2ebafbda6f6814e7a6cc182c7e37396b5eb374f97c208905ec936bb89195814f9346e1b9bb56b1dd69093b68639db412ad17520b259dcd8c1a5323e97842cd68d271bfb18e46c9ebd88459d7c83a951dbcd962b46c7060da7d894e87ca215a37a57551c082c005b7e0e01b670b3eb9c2f5785a625def435a8c25cff04ae4e86a17dd4a77979abfa80f19314065a545c405db15cfe0c90fd5a840f11c2266a0b4c7cc6ebb67475fa55eb6110fb5222539a05b5f79c60cc5a94d7f2e686ab53d73fbc8314bda99afef07cb2ac97c53290d4562bb45cb6e4736f1198d5a8e8f94a0d325a3c589cd54a4c715c2a6276c730a135005faa99f130e6aff54c022d01b2db940e0ef6e21df2c48e0793c5e74a6b63f2e83b1732766aae9db2f0a7b8aedd9d9a8916113673d71ae92a5713dfad49a97c34510191af44361c8d528185f67893ac8d55f9c436efdf2453c47485941d7b5666ded89fa1cc0f9225cab8659d4df833c087e807f17fa1219c1ccaeb8716e9612446a0fa356df19c88843d5d5acc1e7f6abe4568e4cdbaa73c8055e1dbad21f9168b5f35b3577adb3307fbbda4de561c9365252d883baefd6c668271ac024a5a9de2f5bb90a791cd0069582998c8717ceb40a6c391f5a9b7a71aec4dfde39f103ff5d686404a2bf7905f9d4ae820893df0ceb554dab736e3d3d773bcb3bd427d328c38d37d0e770b72afae10209a27367ea7aef37400161e90ccca759b67f573c8b284049391ee4d4d8d1a0640d6a7e966464d4d33a51996c61c3e74d2acda62a9f5dfda4d13982ee48294030fcf2de9563753ffa7dcd80faf464779a99ceb4d0d1d777eb29f0dac749aee6d7b19c2fcbdeabc84e91184505746d18d03cad9b6160e867cc99abcc2c57f93f1b9b1e95692a45a3fc75a7cf580addac1882cf131ab10ee379b7c0e22234ba745a9406bfaae0d1a1e4ee4ce28c072ff25c5a6ed3d87e21157307c82f2ff48361c54a8fe9f9909d50aae91dbacf152baac26314fa0f64e180b880e2b6285b717552cf22b1f56533c1048a6b046fe230d80786e68a70022d0b6427399d9c6c7a3259022aea4a4f6fdc7de3b34afc19d85b98f00407ee57618c6db2a7ba9a00ad52a7d60c171018f7168c47cbfa5e511afc895456f806792aa69fb5d7f857a2ef80283d37e88288184b96b2ae00d3ed4704835c2d0e992b6a24c0c279284f9d480c3f8085e1eddae8f066e414c8df3ddec5ae795972ee11a473a45a1679d5273dd93d20553d06786e2325c25cfd6fd4ab373d448123cdb1521e9c26f3aab260192d9ee1c93c8e021fce4abef1ed762ccee92e71c5e92d77960fb856c479c32c1b0571d9c0560ad0e2324a444bef6eafe3713c0b852ad0ff5bb60dda7de28a6b5f48a5e80bcb3b2a45e9f596084971aaa86ccead4151eb4b7d8cd7005942dd2a4555ad0f17e77b49d0c27403515223918de91a4a268d0707ddea0432eed2e2a9c78d6d6c378dfa9442d904f14d6fb811db28b57da5c09a169a9f7a2316cdded16c2bf674165f021bef0766e5110d6f38315a4254e7ca3026d6865a270f91575107aa606d20805478d2e5dc741f96890d181ed8459210b262eccebf6bb0f2b9b4cc6224637d89ac84157ef0ad74affda6c47ee731655328f3618955a18f1940afac49dcb94d9b746a0c5ad4f86d142dfa9b5906cb21daad96113f3873dd06825443d1624d098e305d687c48bfb6e4e8eda47555a00921dc0218d77b3ac8533afba1123358535697965b6d20eb55665ae29f47b6fbb93893d1e74d3686ee3f0a2de05d11ff27497e7c8c54c8236961d774328ba8ebd09a7c3862ce5495ac1cbb5dbc529481845609eca2cc9784e3da52117d9b6946c925d068fbc92f68ae9de5c3e810d7be50e14d3bfa902b2d771524f44b108c2884602a925983a8791fb28a1077e2cd2626c6d805b53560a26d4d8d28e9f03326fe204c45129ea7bee6bdcaa15717acc744f530f2a0ed3a2248e72d38e4575cb4f8ec33f3467ee6c15104febe44472f940bbf7a3038217c0f35abc0c6fe4d0e64ddc577e4e35887ab8cd97d3a1410c88791da5b8afe0a3bd8570e2b5264401d0e484480ebf15af1d30c5af309bac83f3634a42665f219f543f95225e825d70d7b1fb37a545630a083feeab24b2fcd416516d33888e401c18eb2e058c8e8586ec7450a6fdfcfda03df3046f50ee8874065d32bee7ab04ef527494d46c6da403183f9db292df2e97d95075027eadfd52ea5ce8dfc8e82876097d83434d8de267c5dda69aab532d545a2122bd145eba3e5bb9b331b6c67a1c1975b1bf8682e5dacc282510116a32dd9e226ff23600f34910c7c174c90f591acc9d169c96c4da3f3b7591f2b40c584c7dc429d33455eb34e1d38139335ad408ff8c505767762488b27af431daaf44a08cbfad30820c3c767e402bc4a60be177b0529683a5cc8db34640f6694e8ab01be1d17289544eaf6b7492b2f71454bc38693109b80e8c25fff3fe17a2c76edb557401cc00fcb6c12ddb17504f94d519020b9234c9e02678766c9ef0a25c8151583950a076a957957f66e374349409b49516352a0eaa47fba79c44a2d515922db3dcfcaff86cfa6736f8f0ec2326eb5a0a8bb6dd1796b03ac074a8ed1eb6e1719344e85e8b6c1688532752eb4e5340cb6e1d62002573e4146c3517438ac79155ed6024642f66d47913df842e2765b24b81b5fbf77fd017427d8178408686c34836b93371fbf18a2ac6fad7821b8b1f9dcee8afd090c67274400fd8b482dac61ca6b65e5e76904c9812f3059f9f790bf278e359f2f41338cba4e829617c79d09c862e4c3d720b69b85e50416fd16b377044b8be08bc69766e4b15a60096cca5c88cf046055c84003db853eed7e153c59f5dbcc26e7aaa9b02b6e40475e0c5c05dbe84d025d2c92ed2f0b2b2606565dec9493adea72fbd3eb0d753921e9df8274532cef9307b37b3d51f97128e8440095986a378f3057c7594241fa44183962dbb648bef6d3810470aa897ac3bad3ebed8fb63914550be05b11e9611a8fb3950c8211d3937e96c1432817d802172cf0ca268df4beeaf1b8ecf8d1e906dc41654d8a3e616136079bfa7721d74b81e8fcaba8917716a1956b8239aed39966b668ed378a88014d95e02a268a503b25cb1ea845f159dbddb1e7266ee2158ed48ae019932c83ad258194d8ec953ea4c50d834ad10329cced6789b53fbcc5bd65f29f892c745558e08dcd47a98cb3050aa28c02ff04039904940bf1b032aa76282a571a5d104172d0de2c69b87bd2ee7ce5635dfa892c5c15090f177c8c7138a839d4b5c22c6a3045e06630f6695507904a2f4f58261c0633218b50d409bfa92d85ac726c99311f83e83f7164f238f1e547ac401bd03d39fc214430ba89ae8aff05326e8e4efae50e05dffa8f8229ca26048b83a2d5621c6d80c86722dd4cfea591e2ac1be8f921b4dd0ae8a2d28024cec64a7c38a9a2de045c54226381717eb905b1a42d38ac4c9fd23e2aaa56731f331937c74113a67d367bb37a5dfb95a15f6152b6f81d3e59a19d8de4a1323d4ea21624c1b76c63b5cb3da7b9d1b612135fba3a63cf980d12315639e98508800319b852f805322237a352a00f2ee9cc830bbe01566de5b1bc9fa257bfe4ea993a0066db65c138d3d2d809fe8f68d861be9d5a94751db4799f832be1e58d6c9d64c985fe5776923810adeabb3ec1c005fccc9e64db891a58505cc72924033b92281a41b1b856496e83f7d0fa2d671d36412fb4c8d1d5ac84d27d09b7a477da7f86f6d1085f2604d5a2686adf949697d04f0a3312cbd041438d2b4c58721a3f800bb9261143bdd7807bad5b5387e69da934a4ea8174f98b27305060200b7a669fec51b91639cf2ea55fd216c4666e5c42f6da13dcf01625064b152b2b769eeb48c238fcfa939d2fb9741abd0d88b3db52051ae345e0f814c4b9c8ef3bf9b7429ab6078895d4f2634fdddd4cf7450501cb5689fcd1e266fe5130314b2b652397bbc10d6d9aefd8aeac87ccdb7ebc7eaa1da49356ecf2f4633bc27090bbd76a0838c5bbaf1b4ccec73ae64500000215c2046337250981cc3a3eb91df95a49ef4680cbffc2bb5cab591e6221882a9cca21456bccf97ac3090917c9d7a7902ad12a9ea4e37cf17297c66803e770e5ce82472413226481d0408e8cdf79682cb9a03f2da36f5ae80d17548ea01ca922ae461546c6131689b4f6595ea0493baac51104a9e2b4a52aafd0203e18381d2aa804b99bb45484637f507fb475348c23cca44ad5e5404986aeb4e6dbcdace85bc36112d5c80a2a6180db4ab809b60e47a0a2d47baac2422e156d67293d276eccba21e09fa3f94cac1ae41c5502a18db51e8c5f8ba76b62b799e6d0a2aa406d92b2147acb1cdb366d9425ca3b08384d56147c09d3793677cb0454776ae3e74d85a65eceb4ee4597351e7a694829826daa8fd4cc11cdb3e23d3850f07314637c8c3cec0a66e6ca88e48723d0703e93de7cb3584e97918e23ae2adcaf03dcd26a60ddaa930cba20c96d8dfde951686774ab9145218fcc7376f4d11790468eb0f51b318dd2b5a21219bbb9d6d18cf031886c54fb0bfb63f2274314fb067fa4447a11920c6b9412833071ebe6376e96a7d5fd769df53421702f19e178abe2f9af5b3542c5cfe3d140116010d26844a668b0b9526b124421bc5a4e0ba88d2a5f84aeebcfdd199f7611d253d56e5e5a26c6ceddfac5d0c5d8119999f23307028248c2058389c8c7f36582d5ff4fd07c6e6acc39cd10053dd0c05b250147b648c64f5adb8de375268effd72e756bb744bb2ac6bca5ea496e0dd804289f895776d135cb862ea6b116b8e846e890869bf81793428627d80d36214c324afda99317d43f2b75c9a11cfd85e408d8cabae6a94b74cdf8194aab94a35e79e1f029400a231dfe9c6e267a7ee61ac7c5e14c3a213d2251c42412f1d1261743454a64ce510c8ff45f5d1e5d2e2459631bfbfa60224ba9ce3c2e260be16e7b0f6e802e7a5314f1f1e6b11da08525b05fdd5326cbf2f2d7660d39deef7e5e1638cd72ff548522c56c20d3328259d7599f95de98d0e99ae0ef10693a5884bbfd9d8ddeb1c09755b5864412fbc83344ac4402aeaddc8674bbfa6113a41232805fe92ee1ee5559758d6e0f49dcc04c0679006a5cf709238fd203cea8651756a122e07e005cfe26a2f1431ec0405480080928028f546f48322054a851ce42a4eaf718c80c53dfcc5025745b9ecdd14783b44b5e1697c94b18e79527ec856023e0ab6c858b4ce8127c3e88c6839af407ea4ab51d808a558b8bfb16c02c4e051f3f255e200c177c33514cff357d9146f289eca57de0b977fd115210e05a13ad48f28cb2721704b505167035e672426fa0c003b64e879a2d52685cbd95f38ecf81c53ce560155415d8caf918df78c0e1b8f9488a3ed6344edd1380ca27a278232116d213d10c75bb56df460b6461800acc14a800a3c78d64f4245b7ad9dd03c8666b0ce77d6fe3cce961d5949fb962a2dfbfcf3f8232c8f14700c53e82f6cd2272570f0f6728180cc53fcb21b3baa3f993a8628e26ab20580762f96a38c31b1cb9ae863c9d0fb0df5d876e3a3a719babb37cf731f63bfcb01653e418c7120e9e917cbe6e7f899987af5b568459299e44b312f948c2262f3c66b06be2e8f936ac63bc179d794658c7c24a2e05f71b71c049423947244008ffba41dec5960901f2430bc4554baa6225dcc5de100beda54ddc09848d50bd3b1904bda579e5a301dbee8107c98c655945dd7d126aa09576ea6dc38922988838faa9911197018726195142128201ca0d21af23cc7b11dbe5904b00dba4b5ebf32ce250bb5b10aded79501d497cd6112d57e5b98ba8941c839c74b5309414450c9062b5053f283a78348a7b9701ea1dce0971ed0a56ac9d0c95eb1e85a0344681fa4a54a33c49849d4b5c48720387d27526f11449da415c90d15c6041bfda48fa5c998667077ef05c353a8d108ed6c3505fcc99d36046246d594d60162fafb8230e72e7ebf3cf2545df23b348a42a738af67cca3bfa3059ba960eb1619b688136af979fc3df7c8804d46ea8287618287ae5b3c4564e0ba6140ea8d1090f7848e3cd801ec9d42131a1a884277b66909b0862300b9db1f2f7c593522a401b5908290d66baaf9bbcb305e245dbc33bf1eac0fa4735181f919630742c68787d9986322a5e026d93d21309055b17ad2214a5004991d62cdaa4eb5209d0f41b5cc99af318633ecc92d54cfd577499f6c4c1aa557e3662964def6969d2224014046d4343d33723d6b2fc4e262b99fbaa5beb0e62132f842c9cd8783a871173a842ba23959f9d5906c64a56ea0a05945a38ede7bcb492118ceda2e574a7b5c8aca9e5f04df010c5caeef6e0a37462d716025adb71f7194e107d0948681cdd972af1b6668de39959a39bc80967804e314bb8f6792c62d3d272d0cad9037753329a6356068f8ec64cc19bd14aa9991f6d914fe0ee53eea9521d373f2a2195139af6b283073329d235301e8641349b5c789e1477a0c72690d3c48e9c80d0516fdaa9a3b792146fb78ab48be2326684c9d98ec890b8e50776711a9de88a13d57bf3bad540cd2bf86220d7645ab9118dc1ab44c39838bf86647418746d9ccce36b622204e50eaa76a82edf346951503cd5d5e8b3c66614bc6f62ff3df8862215e9e2c264d1ed7bd160c55190b9b3332053241ea8a3dc0d38cee41ffbd39c6517a9a2391767eee87b34dfa6223a6662451ab28ee7c32d6926328adf37e3697f41ce28e9b315fcd10847f6571405be820133698da0436572c40dbba0c25060f50da124d38209d9e00a8b3fa0a7cab9a58ef546936e0861fe49bc411ff0fcbc9885c15ba7448ac23f2980eb808ed78b1bff52a289c1ab93d9fb799430da8f94020888fed6f334f715ac8f0ce47427126fc20aea4db35e392c319707d19a5164cdbded34ab91a4a522dd0d8ae1baed6deee3d130605b6ba2bb73bf3bc0adc72daf63b7444ca5af52ab84372b5578581d116fd021efbb2bfe54c37bf1be2c764b1d2ae9820655ba84b405d93496b8cec558120b8c6170013ca81fe1021f777cad2ee99743e579ce8773d54738a57dc40e5ae1fd4a4e9077c013df0c4578c551505255798725cd66e32d16aee8c543ce750d6cc8f947927b177b1ff955e910e512a14a81611c83e8e0969fd74726914f632ea655f962c6294ad4259a50519fb1c558adb4bb5b9295864924ce76b9b13a24442d514d0e7622e88b18c94150b354167fa30095a562df5aa9a63baaf3c90972738c11b58d14c7c30dc1b3e9d86bfb7e8dc71ced9d17ddddfdd5dec4c41576c93e7eb43780c50acd5fca9758da7bd648c6eb3a899112939584ee8d0e600dbf0b5e4bc659f6c0ba42d14c9d5341582d112e3e825d929abfcb9050bbe7e2415d4ecd43b24e8a478a76d09062414d29898f2411e7d79812e4498acc76691ea62055d57ececbbb8a1078615b0c111b8d161ca558ce93c9a60bcccb8b8a0608168958f0a204653623e6d81baf923e5e0cf72770d9442e3f9552b3036a103f157003e69faeacd747f260bd24e2174a5b432be054803d3e0c7d0c07034a6c549d9362b6e4ff5dc00c5e810d40af7c01de7c1c47ed9da20405da64312002caa3799a1feabf18933879e0c394f5cf3c3af956b077b6c8b52fdf526478df2d35e802c0ea5068c1b3115657628c6b04cb1a3e46fe036ce0a7ff55c4b962fbd007a9a87578b7ea6ff9eca39149bb19c93a064e38bc7832c93e198282d2ecfcd649e434feee4cf18d15f4407ff1a1694984c1d16964168a5b548a5c9d789b93d61e5074d013420908e20ca0453a219834806aac6f650caae38f59249567f9343c2a5589dd8886decf49d7e0202887b27ba77481aeb06bc5389f8f603f7fb36690309845303d719666e12ef56d99b3343871d46a1e3bfbf735cb3896b09ffbaea233df64067df709d355dc665027a8bc7e70d820543a1b2996c61d24c0be2cc72031351fc80e6f36691c0cd93571feb4afae752ca80d28a90cb30c6430fe0b67123a91545e48f11e52969c774794bda8981f1e70a5674643e9784561891023dbf66e0cdd4234a343c7bd7052ee8c345568a483697b9e24a94f4af85d8007dba5238c704461ef9212906d2da2d3a8fb782a7a5b6cc5f95a8e789493d0a7802db34a8f64d67ebad1b9a8091bcb9440b8ea7e7dc8726e2c59104dcae96cd7431af28c90219b510c4b6a5319aba2252cc3e535c300a782575231cbdc30a1dfde6644e2fc02a4d1721ca8af22f5e5fad04d3247a1dc69ad702a57d8a1dfb04f16eb1045d610177baae8f041eb6e72ee24a3dcd4c35b1a7bd11a36beee76a27f8bafabeb0bd8c0fe45278b596fb06fe7ba6b2d929ea40812522d5a1245820dc247adde667f60bc37c91abfeae88dc5e5e1f8cc6786a2c57fec4570326d8c3be78d57162db8471076bea45b2882fc653d6df72aa658497535ab5f94df0794b2ee5c5a4db87acb2b4457e768ad80e316f4994b313e421c5682a110826e9c9a22ebdf46977dba86c1b85aec2a150fc99423e8f79caf30bd68b02c18d61d3eb5870ca47b0be348f4fd1964e503b7037031f179aa5fb9a9b8fc6803fcaf20afa9fab8522ace75e2b5952a74fe9c7d6ca5e9d54b293291441fcf4879e86951906d6a18ba68ccd4538bb4aa2c11d5fea27e32bc6006330e5c408a6ff6ee53dee40c76c88b500df529012c326763088af42f4f5ac57398b3915082cf34c4d0284993cea49495c7ad07728108d9c70aaf52d62a11a699a2ec38652213e2c2b3fd4d8dc4a2cf536adffa9cfd10e69ec6ffd369d1da17460b45d0a61c43f9bdacc8b161beb4c09b5b032d2bf68394946850edb19444b3d6b8ef132983810e8053cb967adc0b285b8a95adb610c8fde24e47d67c909e80085bac1000611bae24e7fe67b3e8f524a8e53a2117ac5642a066c1250b6af190d2a0e12889dd6f364afc49247b02fb18da54050ee450cb40207dd8c6cb11980fe82583820877a2800cf70b36928267f8b1124055ac3c00c994f3af9e163922780c60a584ee948dcd25c0a46948a38b780c31f03aa56333ed2465bc498ffe3ff534af74f8057ebe59c3b85cfce68b94ca21af9d51616144655f247d181a94720003145ab6cebd891a0592d4e7895884d18959c3c1f833024bbcd6189e29b1857b0b59b1072efa08548ba671923f6f6b235656f6f739abcd7795a77eb258943dd2c2379991285f1818c46517a816a9eb10717eb5484afada0711bfc02c7229f6eec6d8387dcbbc2f6d29b32d56255c71a38c4d54d1b409a3903991228fdd7815d917c9726060eb066f46e25e3af89ea9215cb1778bdfb0ec716ee0c9818fa884cbba2d8c4c7f5dc7171212b3ee8da1161c91080c014e67bf417fbb379fe4fb9ad2458c2e6533054c48d8eac89484187100289f402f93513a18c3167b310963aacedb83efc0dde0700b4a36f6815e23c05a32c42d047a8f33ab54305e19a503f808a2d062e0f54f717eb72298b102d7294de80620ba28c3fe4e40834b918e06329687682eefeea600c3f0a3ba04b4864b6712db3b9a525a9c9e357231f83294b5cf66c9267b4bb9b794522699920c6a0a750ab40a34b08dc91447ec1449537fb3f1e54529a53897fa4751caec7f5d1c9fb33ba3eda354bc6e477f6558f95786ed183118067d2abaaeeba22fca70ac6fd72512d1abfa7096e56d1acb2e111687dffa17fd5d79ac5e19968eaa5bf9171b6157fdd196a55e10e4dc150cab5744dfdfca62300c5a1bd74bd5180c4356128cc130487de25763300caf14636885d9edd039e75c0e900863ec980324c648a974532006a2e9508c910a758cb15482d547d1e59a7a17f34acbe99c3fb169cda773524ae7e8e7b77a31966ed28b26cb8b9e246ad1f7b3ac88eabd6a8f92a51e76adb2b6aca286d020121535f5d3ed0ebea6280a3a98b5acd98ed175ebd6addb2184cecdb0b75d5314d6cbf22f9df547bb84240a95304511e7a673530cd982d016a0d818b631d9420f4541ca51f0ddf970967ad68d1c2b7ffac7ec62360748142a3e45b9f70ce7391bf963f4f83ea9eac35deadb378aa228f9d4d329b29eb6850327562227faeb125dd785bd88e53b5ed48ebef5cab3ac8c30516d995567ad530a898e5a66d163db275076b11c206d46f169c5049a13287eb5e364fafa2ba9ef1d57c33a4ea65ffe55772ea5b48a7a6c3febb89a956f1ddbf2316ab1d1e8a299ac559c3ad47c5aa959e9502a654a9952a6941eaca34ef44b85ec750c7f3253dfa46ceee553207ed99ca5e437dbb24ba958d72c948f0afd68eb54c83a29a50baba6799c4c7c497fbefbe671f1256d1e1bbfdba7fbf6dff6b151f471f454bd18f6d48f9aab597959472f7fa55e49fd28938f65b24219aa56591bfdfe2b13401198557bb42c6cec21b3de1e258753557feaf610ebfebd7b1ac440c8f83042af34886138e7637584620b3b520cd929aacd65484185f5c1362652f848d1630d601b132856d8d9a6b70a7380789b69efcec20cc70710ce6efb81f5f2fbd6fe72ad5786ed87d60dbc81df2c38eb75011481c1faa32dbc3938ae5255fef5ef8fc140c4fa9ed6cbef6a0c86d193bd499326cee33b4e6408e8876358a924a51fd1bee347384f13df7122ed45fc081edbfc5c1b087a12bee344bc88f3f811cef3c3815880fc08df7122cee33ced1aa8891fe1473811df8912c88fe81fb27e38901fe14730901fe147fc7020dff1239cc78fb042e4da8a6ea251f1a10d8a7d41b40c7313b6aab79d0bb34f2fe926aca87e4a33662e6f399703a4629f56527593e85a0be7985bc416a26872d9ed262c3b572f73c5b95ef45595030476131ce7b4aad84749ec9f8ab6e825b4bb09cb4fff555dfa2b4b5fd116c7dfd56555bd8cc47235b9786bc5399fa6a9bebf957413b79bb0d5bb4491cb5fd5dbb89a215ebef58be961967ce5fefe700ad355d48e7fb80543695be192f512be3206f49256e2f2b14546df6570be0bb5ef5fb24b5dd8c7ea255c2ff958bc7a49576e457f7f5c0f2b4ef55144e95531ad90f41246420d1144d5a0d8c8993b6524963e6cad7b492ff9e1487a897713360be825544a5c8ac215d1fd0a913fc13f16b6582377f77ae3cacb7a7fd418e57bd3441ecee489a6e19b98b975c373f69bb2d1c7ac9f662ff25bb8d00126fe0edfd60d51bd98855c6fc9eb7d1bebf5c142f71c20a2e82ff227913e11d6493f73acc8bdff3817912a8e7b6a8ad7ffba8907cfae3f61e30e15a6de9f830ef596da30e3df9abbd7dbe307917b7417551ce79e5dcc7ae700218204d6dbfaf527f6f6ff681c7f226c641efca2eb4da43f7fd7eb4fd8fe53d66264e5b37cf7d2559c6f5370114fe6d0a40dedc7c2bfed0c56ceb062a8997132f06fabc2ca6f8d86e9f6f841ca17790b170ea03f9c09e6fbe5f3cedce1a90bcbbe94c33f7e8ccfa369e4f3fb0f03115ff4364e747ff4f527acfc17918523befc254b9fd5eb03f65f7980d4efa043751da4f74ae63ffc71441edfeb05418e33895f8a8fc5bfe253b1ea507fce2b5e3286c99808f3f4093e0d16bda3ffd848bf337f1b67ca46ef2263f916d9ca9332d2bf60d9955199571e7d9a812f457f4bfdd7077b676cfbf632662dcf160e37fae66a8668b93efc9744f596a04b669a4fbfd9dbe387cb2dcc14ff6d61c2404491429815a52bee67612f1761b9c61fac553dbfa8562f5fc42211bb882a7fcb8b6a4b35cd8f4ffa211cf4c187ff5209ca8f3f58d1df1e3f58ea3192e859b2951a7f84435da2e71d57c34da6d1b476ba2725643e2d8c8c7a2ca47eb0f23167c24088e45f6f3d0f6702fd09dec998489fe053f5328f85505667c23022ac380cc33bcca1998a421d793221f3f5cc511eb1b12b1471375e823ad2fde47a287a9ac5f780ab69f146cb0102ab469d933d9411ca07d28a5a02d2caa57a17c8cc95f51072f5fcbaa9a694d84b35744bbaa78c503ed0fa764fc1e76f4d1db1d4b756338226f31e02adc6034e884683f4b1b2b87e8a3f46e866530e52c5fee9289accfbc7dc431969e7ee8fbed4111b5f9459385af4575c713e2e917a69eaa2e8874093e9aef0299f8e75d617ea5daaf39954146a897ed2509a8a3ebd7e423d650487e8aba732be9e66160b071165adc703aea67f46d06a9a14c4beee1f57e37e3a8a56e3a28882510c63961f9f5aa20159f7b74703a2a88a232996235925632f9251eced273fae8683b8285a0dbba0c9bccb40b49fb81a0f4420cb2f7af893fe954908d15e58a27ab10b5aa38283b81a083c6940363e907def016a89f857a596983189f7b2a7699698933a52c5ac9f34947ed24fa82518066cd18408bb2612855e268d4f238b23b916277ad75d1b0d97bf6e65f49873e19a0707b150d3b475d3eeca5c7c2bfd9cd27d741f79340d5094cee41ccb218e26f90c00e74c3ba449c8b22bb97ad9b173ce39e75c741c7380346ca682e3dc026220fa250fb9fecb42b259c8c2f8d1aa381d638ccfb22a8c3146f7c3c98c86e96f84f90bad0e7fdf0e3c7c371affeeb9d1c868d477fcc39962fe03e05c3f03b5942c2459c8c68b6300be85eb213a179f3a177b084013e120860163de5bcf550e6220eb2e0064f46bb6f25936facfb0e72173f91d32d1eb90b53c4c467a53c6f2eecae6bb70c1e3b5a82fccf54552cf2fdf5a8d0fae5f38a86513b272be94ced28ad31f5f5a2cad96596d353e74e52086f1e22cf52fdf0fd9392b5d5a9ee52741cbcf92b5f662256bed05ff28e3f6821fcbbabde01765de5ef4c7c791d5baea6d513888818856e5201e1c44290bcdce3e463e0ef2216b31af768972133c6446347380bc79cdf9e6c54358c83567bd3cc4e7cf36b38b7578c8756a290bc13e46c84278486cecdb1523eb44e6b9321c3aeabe9e752edeb9e2a8e25c4f57580803111f76935648432cacb2c5ebe9d5b4b290cbb4f2ae0663794b8787f0103b1fa3d853cac2cdf2d72dfdeabd99c5e226b08c857ccc959d9e85b00e36c258c89c95e78565cdf295224959f497a89a28ccd5bc8b2c24bebbde2fc84258080be1211ff3cf4246bcb2b3d2359cd428d20264df4ffa357830513f2c9524d71a38b0764cdb168e15d87050a348d35837b0b26eb8a92765a28719f5335b7996ac7a081f8f93810f27848de190f11fcfac3c5d6fc9de39dd9c3578660d232ec3b11acb9ccf5c83a767b2d4c081eb6ea951c4c2efaed0ea86ddddeead6f5851394028f85cc388bdd4c3ac6776dd728d9e0a36cb7cca718d2296f2aaaaaaca551cf87d7bc892aaaaaa1eb294dd5a4df5c4c944075453cb535f61462e1717ff02e1c3e7ef1a3c0c047cae356e8d22b6fa95ccc2b162bd68a58a317e74516fa9458cf0a9fef0dffb67e9b07484c056d67f46ea5949076571bc62925482793d483a0c80849329cdf26acaf15e39406a2298e86ffdafbfddf8b773cc3cf0ddfb74f35eedeaffde7bef357ccf7c1061df63fb685ac67da394dae7fe322a07ecfd751cce49ec0bd84d3baf3f5c8702aa54a9f2effb5f15815b1cb742e802cf9549d8db83f678cc2ca9237df2964e1d89ef5d6d0c03be6794112b7b9c5a8261f84ffffcfc2c51c23e4b7aa7775aa765e0549c8a53712b7cc8d1e0c46c0f23358d1b1279888cdd2cb7738e2dafcd3316e23c7233e99d9681483a9955effaa7cc6e641575d9bc321ccd5e3f1d950f70ffab5eef7fd53b6dba59573531a0d9eb71347b5553f538d7c3afaa00548099725c0fffaab24f39d6a6d9caaab771fc3996ca7a94acac2d677247f9d119b2a333812610f5f353bd833e4e2673eb52ca8cd2a6941e234958477f7ca4cf18a69429650c3fb1a1109888b7371250a13e452ae40efdf97bddd3dd975c0d7d5841a9c4c9b877199f9dc17f19c3e87f19ac97f93b63dee1dec7bad213eb9e95b89a52cbb82f29b1ee7d0ad6180da24153b0fee6d23da11a7261b69340469f38da27a34fec23bef7b1ee68311806d7981f9c0f61fda5ae01e28120418204b1d182b711830dc0ef9e04a43afe1b48c4cde1acf5e90d61186c72e609ad51be05a65ec0ee6701c3a1fed9fb967ed3c469d1c79915bf5974523506a904432ac1b42df89b410506ff92603c0b18db8f39fd3023a369bcfa180c63d6f9353ee687180c8363dc57a3743b591ed1a3c3cc3a5c9b4bc133f28b825de6612891a2f3bdf8919f8d7b3d4918e949c248f391c2df9b428f0b187cee9e248c1cf131028587a74812468ef84c2913e83d9e9e223c459298526694d898e7d228a58b18f348ca3d534a0f18668bb9eec7ba672297796c2c626365f97336652bd6e653c4f93e56f62a306735ab050c47fe9b8fa2936346dfbf6fd68c14bff86e65a5418e63ed9fac9fe826fdd34f34691ad83c4ed1ac473a64f97db82a55bc0b187cee9fe96837699acbae058cf9df47244d23957013fcc3f6d2208691c3440b3ad735c1469aa631d14210eb9e8f3012da2c4981d8773cebc355a942c45e36e2e227bad37141c5fab77f4ab7c446a28c551aa1410cc3dd0fe586268634011fc2772d0a0b81cf63088ff71e8fa611c2431c7c3cdc674a8cfe1afda4f1a708bef7de73cf83752a0fa99eff5dfaaf5e1ee257befbf651f4d67cbed7bf77ce5ab112d5dba2b8776b551c59ad874e424b561cf7cf5a95855c55baf7fcc69458367a08a91b652b2f33d8ac1bd6db34cdd8ae64de32147b2b9bd44a19f993e9d30ccb64cb60a4120c112ec76a0b0ed3bd26fe84b3b0d6b06e867555469f1c6d973eac578675f323fc86fdf0636beffe6e67c56933fea535d6ebcfba8f417fd28ae3ffecac31bafe687b5d460c8601e1ab6c1acec8808eba97c16f32a0bff7fc41e71c7ce7e0730e66b25e6f3d2b7d9b86f1a5cca88c2fe7d36cc29b59513202a9ee3277732cf4e7ee722a9e5a509264a424c968341a2161252b2bf5f6a0499228e1a1992bc3c224489228191a25513242c24958090f7192c6496243e2c4c66f489ad8f80d09921f1bbf2169c2c66f4894f4295e5662efcaf3a5c449af7a517bebcdb0f03da9de190be1fbd1a85eb7573e7c160114815575c6ba2befbd57d1e856cfc6e7240c045671aa17e57856543909c3e0ea554ea2c4424ee25e5d24aafcd945e016879f7aeaa3122c31c2c73a6631c6883d24c2c2c882e3bde8a757ef132b41fa973552952d83510b7b9b9ea20ccbbc4fd5b364b265aaeafbb164a4e7ac12bd27b1485209e6297ca8248992249d83f5480c3dfe8c7812aea78b381eeb884cebdf43ea8894fee5afc44ad4e85a81a5fa52d52be535c4b764adc98cbe610f474f1a3dcbe8faf72bf1c7b3f2bd77b544efa938923fa2f547df91d05571462f471527feb3f2d53a1a413a1a8d462347e4492c2b4f1079c206468ece08b7e38c70449e18392346ce0847e40967c468d4ac11352deffe5d4d4b1dc2c9b86ff951bd259787f5a592ea952eb53919f82cb5a5aefc7c916359796af4d5bf7a63ec1011e2b8d8b6eaaf21dcb3d42b4b31f679cbbca792641f8c7d6ffe133d965dcf41dc951773a21765d7e98797cdc87035b265f831e9883823569ab464ba8f2f0bc7c25dd154acb715d6bdf690a4e450ab59f92b5dfe962cac97c22749d2cb6fc15a5ebe4bd69a8cac0d7e4b46caa85fc95a938155004560b4fe78d6fffe78d123f64f561cecfd29cca9e94f451f9ae2de433de478da9555c9ce7032ee63bd3f5e4f1162205a4fe9a19ed2432dd4403da5871c4f4f69a0290cc39f044ec6bdaceac5ee0ceb7e62a51286f1308f93918d06dfb7d1d5fb96c25e7b8d8a4f0277094005d85beae3a8de5249545fbdd855af949622817c39b1b00e3a086f54cf56a4242525f5b162ed05cc46d65de62995fe4fd6cd58fe3b635d0ff514fe7939a25e19120a120a120a120a120ae20124d4a2dc06c4127900f10812e21124c40388479050108fa039ca58de9aa20c56d925c187855fe1a8b4156ce1908dcf83812a0bf529be876fd57a59e806db0ad62bbf7d6b35a3bfd747f85413b290053e7cf7c3599959ef0f8765ad65669df3af14401198ac3da895f3392bf9ddca8ad3f19b3f0751cfc6277aecf5c18a926cfc8a7b580f34185b91ecedc124eb15a7aa28f557a6c4cacacf7a49363e96ad3cccac87580446cff2ceba8931be8d9b908a32f8518995b7e919a995c1af20307a96c7b2951765d6cf2a8bcf16aca3487db370505f55d925d9ea59322b937daa7e25a32d533d5b37a8af7e3265c946bf9215c1041332be12d8c7ec669479cbf05b192dc17c8c123a6f03ab58cbb0509050905090509050d08f964ca6333276788c99183262ccc49011c3fdf42c60cf624d23c3d2f8c7f7695d4f65f2e78cd55938e87543c66fd60d5ad95a19955d195b37e4533fb971bbcb7ada24c5487d621931285347729c0b466c0bdb987061889d8ebe1e2fe2789a8875b7b1ee58af3ce4cdfbea8f7ed60db6d6c7f6323e15a997a9eaf548d197adc977db2aceac4dca7afd59a72acefb96e3551cf8cfb6ae9e851de337ac4fced56c62feccb0235b583940ad571cbec1300d6b625ac6b1901d35d45f6a634ccbb81bae598c9187340dfc266c9c5b37cc42e090e94dc33a4278c88e69ad1bb769696f4ccc080b9873ce954ad601a936ee0d11c2303ce745587d38fb5efba9faa20195ef512078e50091d56575f5d75320eb9292ba4bbe6549f8de494b5279c96faead2ed1201a4483681014cde57bef399c1bdf9f08cbefdbe5f7324247555f5129a59452caea7f34cefc7649497f46a964f5e1ae39bf256d5a45f8bead7692fef55646ab83e0ad6fb569f5b4befb9951b25edb62dae2b804f5a6bb7b3b7cf74440f7e93f6dfa7a2d299a0324ce9f693f3855b462a43f4361443cfd2b7aaade1fd76aac5e10e4fc95b53e59f5d2faa3a9efafb2180c8392b15e2be23707510e9047a93c3a4485a8100da241a31c20ad43812895918f9e028de8d0685e2ad443443d64f4d7e988326c527f1f66e768cec74a348806d1201a4483240f4625f573ce1c20f1452b706ef5b055ee2fca8f0f7a453d29f1e714bdcfd9667c5186737db3ec237791bf288e2ed1579463ac73bb082b1a89aa63a3d19b8f61d57bd0e8f3df5f994567bd1525618cd99402eb94927371cc220b2b00db9850f1c452f16339d64fa65635e5a02feda5f4af7a85b056651c79e1533915c338f83be79c7bce39e79e0ffeb1fcce55f2537eef4c8fdb8148f76d92fca6c86f82dc6d5fe69cbf7b771eb4bffbbbfc6692cd4efd8d7fe1dfcc44f59ba209f69bbc5972cbd8b28fac71bff75e95efda0e586545b517eedd3f21acbffd95ef2f330e6febeccd5d7702f6bd6f4dc37fa56cf95d258d4603beac57f64f297380b5953ad8e2c80fd85811c00fe40471e884c8c56f2f0c18bf6c62b01f78ab79bb75e33893ff046b7fe563e76eed78ef2fdf2b25849572c7634b007b5580c5674980283431d858618d695732ab9fb469e6578f150023596f0294c5748ace39216d4df36cf5cebaa948d6fa69b22c0562adbff7fa363593d71b95e5f6c2bde9aa37dc5fb7f05db336ceca7c0081f227cbf9cfd2117fd62b7a6fed85f5adbdb87e66a27af9ce7f0f65d3bc7f7b2bbbf3ef7cea29ebc6fc7e5ae1c86ad211df3f567f7f985dceb1f809c13560577e13db4eb4008aed8ec28bd846c18d4fdbdd12001f086b8fb91af8fd5202187c2a600d8a2243f6f27080e7dc00f86773cdcdaf06c239ce3594d9d2861ea0b44c01024b102a2f9c94e8210bb7fddcd64486bd27cbff6e5d7b2166b81ab7d11e6b1a0358fec62ff94bfcd2368b9f980d0458fbc9c2891dd9f623072a6cfbb19207cbdc9e5b4da9c9f0b737b51a9b96e1a7fed6684d6c7362db0be16aa46bed17d034ee5b9f2e006ee08fb280dd92e512ccc7344ded31c33e2380891e211d3c618a2bdc50829d335039b204305049821620172043a14414059512cb504541a594be0463e3621f8147804cfa6dfac4801f1c0be8133f3f5feefef605dbd37614b6bbfb674a7de226a365f86136f33025378282ddf663f96d16d0346df9191044d3dcf6e3f6c6d89e61fbdb605b86ed2ae460fb65ff589bb6e952d7481bfcdf35ed87bff50b016bb631a9e28835d9c6a48a2e58681b932aa8606fc9b627b22b28cc2705d6bec53927c3dcb46f4eba75e3ef4118a3941445393b725319a9e6a4d46a587bd138f34b7a17b1ed5b8f6ddf78da8b8b7246e486c7b6175938dcbe9df6a2fd1451ce30eba68d1af3af28c15cb5e9365ab16e562ce798857246b26e943461dbb7583748ac8b0bbbbccb94035afeb65c2f059e8f6d2c24524b8b8b75d3faf4d2f2f2f2426259797919612f2f2fa297cb7a79a1b3a2647c7981cf5d73f3e1e2bf3163b95671983525cb9524038cf6a925148c9deee036a254c2c03f764b509cc01dc37048247bb12323905204edaeb4adde0e58f72f076671aeb46c833fc7998d6036b56fd674836d5bb658e36776156b9be35804aee635991158f7aed97819db68d60ee99a5f703a817b05dc1b03c140308cebbe5b070a6c5bcb3bdae2b87681f3e6da7b6bad55ef16b8b77bbfe3f8f3e71ee5bd0b76ce9d3be79c73ce3d18deb3bb51039640d2a7662fff7b5833ec3ded9393fae4589ffc52eba5126d8c04afc7a1fb73ec1435763737bc6236c7390464c02cbc1042c8af0bb6f5a0897f1f95b5f75e7edb1b33afccf1a38ddf0db3fce6abecfb7eef7d6a99f520d9c75da01ebe6c35f263cc1a26b2389cc96c4e86cf0f6b4b12b0ebd6bde62a7e1ffffb7e9c57659f9afa426d34ef2f7feb130f52301c7e6f3fdc3219a4c4b0190590c95a946a587f890d9114239148561c48926235463a4ff8a08613b06143c9844718e1eb9682be9b61082c60a0f40814232d29839bc3132023744b119e40d96850ff9e6a34aa7fdf604c38614422da5630424b45f8e27b104688cda0074e2282ef50ecbd962ad2d217d603605d07eb584bab1182d468c02741114290c0d5f0c083946d61fda1bd700f4b2f2846725f83bf57399c7105d5c3b651e4c3c2776ace3bed7b0f818537d47f55dbbb082c7ccf51397ccd9b9004a6ea81922aaaf0c2a4e4ccc5acbf272c1365cdf598895760f2af5b27ad8cb658611eb0f119b3f1298a87999625a45842ce58af557b0f60aa2490f620b214a10c10c658a593f207dd469630f3e20425a4222871862a1ae1794565474a2b96fe347367c029a10c824949fa1356ef694684cdb8ea9d315a9955bd2f84ab2155965269a5d621a4a4a1e2108fca9ebc955004ad57dfb9fac1c18350ec3dc6a209596f43d66d0382407313ca54acf310033115d69f9fb826aea67aafad84b110d23b8ec25cec1dd83bdcc4de522b8180b35155067232ee454828f110c5488c85ef30d1612c3cc863c113c2638333bb940723739352ba8184549c8f7577db5ddd076bb1ee884bc2157144dc10ebae08ebdf59748dfb692caac3c2c536b89fe97efe74b2d7fdccd8eb7e6c63612f86858b85d5e9b02ce186389f2e0212e703a94023107209831042f8de7a940efb6115c1b3dea7b7862ebc1dea8a35b83869542ce145a8c4b3035f59027b4163e11e62d7dffd61bdef3d1caf181446701b3cd9683c1d6d636ded05e7c808dbb16086b27583f5bf9365e607212451acd1785f5a41c9d5c43424f529c60698f729c6f6c9d957bdbd70eb27b0afb5537c0d32bf210c416708987fb324b9c14f692486d20a5e637f6666e6667e8f04f3de37f93ecaf7cdbabfe7ef555abda578afab934a30dced48bcf79e6c01f6228c5404e260842fbe577f380b2174d69d0c5cc6b52d815435219cd55343587ff6870fbe079f6322adc8c7ba9f9492484fa7d1c0f7eb46b0feee737b50920b28468a7f5b58e73e8d8a75efa875b363f9e78a0fb642048cebd580fb587ece2889c508c90bd87d3ddc970fae624d399cd580a59eb4176e87ebd8fb7a32fbea45054b0fc67f2b23d619c1deca08c548bd13c51856b0635de4a306521440a712122d8b49229560bff760c348654cb8fc17dfbbe8ee50a475d3ef482598eef604140e85f8902da9c42fdd87fb60120c9348303014098654c29a06def7fe54c684bfb4ac1c2f5a37f2a115448e7599c32bceab9894d414a4120c77c37e3c78026484dbd400f4822fa8c1b630015449b1841440fc24073f2b4bb88d0c8d0ac982364523c30b4fa3626261c90d1a153f8f87b0b095302af358058bb159d87bf5beaf7e62d1d2e12f2bb47460c07e4549eac7e3a14a952a56be6d96aca6fe5845f02cac38af6255ceb82a151577f7fb1e0cde8b2c3e3a9e0f3b010a4e783e15959e35c4c09a36c9f695cddcfc6aed857f0f927db04acca5c430ac51dc24c55a1389ec3d3d0b2ba9bd70dfe203d207d84a8a7177185a76aa1febbfa204f3ef5b61a1fa896ea89e506161868484eaa781c18916543f2d3b2e0bd1c54a55d3fa781dc4441fbf1b8deb639f68fc6f278865a2eccababd786f65de5ebca7f37df59e8a39dc3a29a3f5870fc6f8a8eb740feb58ff91d065245827ce815dc048ccb8048175a21360a0c23ad6bbe706940af4b1fe2b4930bf100ad6a18f4c012c82857f211460ecab947c80edc524853edcf3c408f4a1549c0e804e2f06c67a35e3f48619947cfb6690a220e9a9859329483839c91e855c2102e66fe3faad97accbf95786f5ffa16b18c063c6130ce0e16adc6c22ab7a2556ba8d0aeb2cac3348516acc5849420a46c8199683b814fefefca13f7ff8fcfde5f05aa50a23f1e6246a1d9e1c0bccfde46475a7d110c93a07c8e75e1280144b4851b2de2725d15ffc9751ef37107bcf41da64decf2a6b3bb3d94eef453eace77856b29d8f5bbbabaa1555ef8cf586c0facbca8ac0c166e5a8ac3b8cf13de8ef30f8d7b8df73e68308eb2df3c1b2625262587c979e615960ee486fc0fcf92fc9b627d6abb7454981b1a4186d522c21c512de6e03d2c37f4d7cd0f348a42bbf6bdedf93605680f95f9275afc2bc93d4bae12cac520c18cf384bebf506d64debd395360d238d19ccb38ffbf1a1fead01b3a546e372b36e1e83c9da4e1b3d7cb33e70b3f220607ad1d6340e731455992a49cdf9524a2ac2222a2bfe2512322d242c8ab981629919afb86192660bfd7f9ba678e5dbba51a287af6e5858be5937d95fd90de96306d93047b4878cb17032f0f989cb439707009599123dfcece149cbc087f0e1bf29d930e513cb549402cbdb3465ddacfc9c2bb639118320d84b0900b060d8dbb485848bb769919016900d18b4cd891814611bdbf93466d6bf5838dac25859c064730a98b7e9f912c494b1fdcb9a2d68c5ac1b12c502b32800dc3627863821bd4d4f1734cbe2f3b062ddb0c038acc444294c171a33d157d64ae52bfa045f94fda00560649b133150123375a000c8d84600321e7232f063b2e6645ebe590cd4271960598b7583ad1200f8a903fd2cfe0ed98b75835506ea79993ed098413760d23626572861af8c2dd5038dd96807eb868132b6a38c857880c50780c9bac1d6c5cf1d7cd02263eb830e960fb62d7ef24063167b80c58f1f4b19db9315eb5303c631960fb6a59fadba9a05ed05fc6f79468e18b1f0e70e34c222da0bf82d6bc078a84ff0e1ef60dd68cb402cd427f88f05ace5f9e508f624d1c3d68d6ae7cf2aa3b266a5751379d8e1a9df81871daa29c77cea67a52aea7aeaf99d0ecfd6e930afbfae1a001a65b46ee2941917e913fc0cdaeb679c3f7ba055467d55796bcecd0a93b53565cdbac89a6dd1ec0b7c0064cd864bf60187df925d4849cdae64cd625fc92a635b6a342e11b5e65bd6ad5e9aaa69ca8155d5e84d2391754d1f40ac893ef5d4acb0aa9a72544fd9748561b4cae877114d439f9fc5ba29351aa5f6e2a5df7472521425fa6bfdbdfe563f45d65545a65a502b7b69c09a1342aeb0b76760e18d26075b7941324b279f7a4a66d4cf2679ce7a4760abf854f5da3b3af48d3578fbd8fed9f2f2e55fbebdbc54fe71d969c95cfee5a7b36e5e9c89d4604d73f9c752d134cd56b0fdfca4691a133010b1fd0ca56932b61cc5f633943edd583f62717979a497ccc5a5c5c55d5cfea5e55f5c5c5cded4e2e22ffe2dff92b1e5d1270bf4a93f0821624e367d5a409fbaa50c1809a3d2a66948dfbf80a661f97e0634cdcaf707d134a3ef17a269b0ef9740d388be9f47d35cdf6f81a6b1be7f085753fafe1fba6603f4fb4bae86833899fe2bc3f2f4a98b6cc0d5b011c73f5e2f3664bbe5bbe5fbe5d9b6a5e3c5bed43b0297b7712e190fe9534be59dca444859ebf4a99f25eb217dea5fc97e9435917e51ffd56f659d449ffa4b591b7132fd34632a195b9131948ca9e028465c0d5fd1fce36a18a88be02c6c1be987fc63fb557e5205ebefe7214db3c344ba06ebe7241a0d0ed2efede3d2d4889de6240806a29fffe46a82684e4eeed49c9c5c75b2621b145420d98161f0372741748d0f1218a2070980864e1cc47bbe8083e6043ef94213f636212a57d8dbb2e0224e4d737fa669ee0e969fa94062572c57e1e49c733b3010cd4a4088209a13176648831165584111234fb08115a1640a54e818018a1776e27b4d4ae73c4ef99e94523ef94fbef7de7befbdf72084104208638cf067b618987cf6c650046ef94d39b8c7f21476267abbbb4ddffe1cb85b6e2d7a89badffbbd35f58efad7b73ff0a8e783ab0421a5bcfd3fd846c6e0bf9f6da00244158ef063e365cd467f0d06822d7fc9d5d46832fc7c6bf45facd4625b142c3862bb5ecc470d1002147b6b8cb03e99746cffad619bb4a0c7baafd1355810fe050861810de8582e51692a5ec8420d5bc4eb61a231e98211f64213f6fe6d5658b671db150d188293ac014a60913540892c9a068a29ac60a5e587691aae82e52ff0587662b98a4987b3fd389cedda80213ce953b339d6a66fbc9f05a0020cb30be813dbd41a7d621f356009227d6ab686e52ad0008425082188064357d8e0390c91010a2c3ca148902136da08c307530065666666e641695edb10f6ed60be5601779052dffe87a6815f1ac280ac10ad460827d35f6a32a361edc6d8f633ad6686b61acc5119095aa67f2801e37e219eb88f0d2d1d4f0706acfb917246cec838e39ca4a6c0f8b1fee6baddb763e7ff70dcfde087858a5acd8c6c332fd4f2538c549a99218308314c814194294534a18402e89cc1064338021a7e88e854465c549899312363c6d5d0e61e1804c852a0f41443c2689b9131a3c68db13c05288210032054c1840c98b071319b5304291409c28113514882848d9c2254a444718624a8c041156cdc192ab014b0d042052a5c01064c104214a64049d48511ec3b42d9f096adf7770feb4872c02eb55d6a4ffc96a2a47d6af938b26254845edd7f0183e98a44df367ba96dd928577fb0fcb6db0a22c7bafb1e24fbaab42e33e5700ff263bdfef0510ab3b91d33574d39e9dd1fed6defcdbfabcf07e18b2e1dd515bbf7e6b8a2649c97eb6ed983f9d73976ef4eed4563777f9f1e639d8b8fd25de9fd2ec3fa24ffbabfd2653c48c1a475cf38ae4a8f42bb3c7eafb5d7321325236ccdbdb9736fddcdb52a6c7c475d0dec0ff86c9da5e3c5c27a4760a3bf7bcec555cec908dd5154455155d8f753626eddb4736ef2e7dc6584fdde73fe2e73f7eedc73d73025b4b48b9b83757730e6ba23b05e231c2053ddd745f85c4b49496965d38898d97d73decef96377e7dc39e7deb9b716e3ba23d8e19ae29785e7f720f40761f5a68a92d11d3abbd6ae88bf0cacb157117e840d61eccc545192bdd9f67bf8cd568eeaa753cf4eaf66fe3bb54c0c16b0bf31a42760fd336e67d08035dba0a721b615325af7d2f2fac08039db9878810c7652937af6a860ed9f0a338d4665ddcff822a4d7bd404b27a57c9c5d370708f04524b07b8bc4ad3d39193e0206d32777bf3fa64f0eb36ca74be7f88665d61ccef8574ac991b317845d927539dc6bd65e38ebdb0bf74f08bb24189817d61fb3e1fc5521487b37b57bdd6ca26484ee5c3be7bcfa8ec9fed839d1e87bad392923747ecdb17bded1fa9df26ece91ec86e0b798c9923e2bb0f6d3c51c5557cf62bd99b36ef8bbc64100dbfdaddd3784ef26d71a02b1ce1f6592ed4db1bde9b566eb321f406cb70f67217c6776af8372d0dd59392a6bedc03163bbfeabced089f9c43045ca3138e8e037f82efc97f39891ccf9ed67a42c68c292192bd06cd08a82f9cd864d4fb62527e39e720728b90138ec95019b8f63bee408344b02f3e118e27c8a728bc20d0258ea6d5a561f5c859ad915c2ca9f15f5183dc2e8103ab5c34a8ad1083376931a03c5c48035fb166d4d43b167ddc49c679347c8593f57f14d547c938c6feae8ceafc3a23333f5e7046936a67bd7d60d02d8f86ee5a822fce8168e99589fcbaed630df1830f71b906334c164c09d261030c01f7e031a0842c083851567bebfb769f9f1a7e89d017b2f048eeae5cfec52dfa3b2f3275b3aa2a5a62001092741b4294840820a765695a5ac1df21dacaa87d59d4f559969c28a81f8957fa3c94dcf24e19b22ac66e603888d55e4f830be4dcb8fcec6afded558d5cb94bc6c23c724668b3980a91f07f4e9b78799293ab7699a2a13e56f92ce11b633846fd36e6920870a12b4fac0c22eeb4fdf15309afdc06a404e7303b8adc91374ec0f67a18e4682e407396070c2061698a149cf02e8b7d5801ceb2fad0cb405d04e820501ac21e06189fb16f380744e6cb78244297dac69aea7367de1bf2b33417abdec5a4f992cfaa649df54d13751f46af6f90062e195f970163ecd207c43180e7dc7917ffd63b32e05b202ae628389f744543674bc082061c37db378b47032050948389982041e1bee8d01a3efde148c3e0e7d6f7f3dbddead9b8bca5e6662ea5dd7f514f534bbf36fc54fff65a649bda9a2de04a93731f5aecc07106bb17d56a859a8e4cb174c9bd829443310000000027315002030100c8784c30189a80a26f51400118caa5060461588e32887614a19638831060c011001019011daa4019cfec2b25db599508915818aebe07b478a4d07421aabe3004a299cadfecab867b531b92ec46e6e358cd3b07b68ca4f4f0fca2426b53a863107765629a9fac1e0e324b22e991696c8ae1977498a9b3e615ce2baf94cfffc9c4db5e12c98cf70f6a7a51d78fd492191e7dca543d02860a989e455c503209a40ce3623b415000c2c74a7b8ca2728392a301218a97bde0151a973a41509a0c262e624e5a32644fcb4f2b89e794357558dcc0fd346a2048325bd6102a916bf653c30bd1fbad41535e879e0bea4af6ead2d5950a52bac6894fcc87d7388535d39840fbe7584eebabcaabaebf78b051038e07ae204998cb3f73fb8c44d484eb494bca76233627113390bf27f9e82086d404422c957dbadcca081e2d66e2b2a2df4c55234584cc93e72ea922f4bd481f6ff165e5d7d10b90b5632eb71895e8ff7210b602958ee97d0523d5d542f2633f9c0ac240f7cf84582ca7f092209f83ac3a86d0914c3be4c485263edc8347348a3d81115eef44e6c7a354e3a2807214ccf1f9bf6d53c01f2f982952bb869c6d019a73739c98e5e36b9d6db87319ea4b5ede49804f2aaa391fa8dd9f53fe98fa4bb5bb5b5fd3bd90cfc10ced3a7d8a9e719876bc1f9dc1f6aa6dacdc36c596ed4f138500a6ec9967f8f54c35d490989c799471aa158acf157492119024d2287bb3474eb8c4cd47e7d6e421d0c58206b6281386c1a52b121bb6089b3781e483922de78f71d51e9a7c880e978a550fdbf71e3ea60eadd0f2d3b7408d9d27ef6680fb6748626552d054cfc784f50bb720ba69821d7e631e1aac50099fa404708dbd35047d695dd8b716e07c280f3392a0ccc6a0bfee0e7bb3cc216857898a31ac056ec370d2cfd3490f4870e42fa11bff079070f0b639d0852aa032395dbcab695d36c83345540560be43aa2a6a9965cfd01602ccbd4bd3838b152fa238207b078dbed55e7704865628592a5c40369f2c8fa359036ac0e03ebfdd311c627e9a9bb8ea265269aa1be32091b5b9428932a81ca4fcad6b0e43770b85042fdbb14a385849fe947ba12db5c689c21254efe86c1b0d0e1032ef8886d917512bf5328e08f3f6d0003ce784ab43f095753663fd77854aaa4f0a78d1c6a00922181e065cf004392799434bf0d97b3c9aa1b91c3945c20cb3d2ca116741470ac19a25cb5bdf21ac54cea988f92417efbd36bf41c559cf17e2c15f96eca61e9e504befadc4c7c71a333e0dd5da2ee40ff4529a569685e669c9051ac3d50811645f105c1749e7fc9e3abdaf82c93fdb8d80dd83975316f00464d521a7b4da487baf6b274c16b156dbc01d7276b2e2de521b3f05cc899845d5046a75e2d18cf60aa438f1e16013e19770bcf5a943c667216c8c13378afdda36cd58f23d775e4e3c0e5c307e63b625713785e3bdf53655cb8ada1f92d5411f78fd3d85964cae332a5ae88a765c625a2fd649d5d602f93fa7e71c3d3a81183a03e3a6add5049191bb0cc6a991aeef01e2ef8022249aeb9d4268477f7a001223473ea2651d2bbfb79371304f8c061974c7168b3634494741ede3fc9c9849234b52c11d8fe2e5f590bbbac0c343279fb3e0e15482ffccc6980dcd1906c00ea8bdf051ff84798cedb4ac8950499f51ff908901774204603dff911877ba1177f57fc73748ddf23df045a70a0c2e7252e92eea42b8963e52cba41e7db85a33c9ff33ae3c0fef0ab5d9e4102d3d9cd8bb8346bb51beda3c7eaeca540fe0dbc533bd0ffa7cd65d4aaa4a74a99a94a95eb1b24692afb945fa9b8ca25ade7d984e480d3291cad2405bbc28dca050d47a34a921c2956cc36bc0647e954f22352bb1d4799b25ee1e547242e560b4781a3f4d8e169043b8ab2ce5d0b38ef122f34e64997b7ac484fc922507a82ff42c156d44ab7bffc71183821192fd7a2ff79a5180ee65bf5d18ea33bd5141c5a7f8064ce2f6e8f5a14b560dbd711873d2010170945bf2bfe9fe1561ae4d9e6e53df1c472539e65317f72a84358328d846826644241dc4b46aa68b42f00910d905f7ae0ef195f34ec13636258b264e618b8d0e0b58f00f686af4ea8af0e1248159035592625dd1a3805f429788b32f412aa35b90c2caae9a2687a4652da9f3a4e4217b93db098a919c28f9f658b8595f210e32b7383fe7ed0b13e5e4e4e8c3ec2c14165c2be524695244bfef1fab58304115972093d2ef13191c60fe00a200172c5c50b3ec6585efdaa8552dff6094582ad1983e1c3f1dde885cf1dc8a87c5c86c4dfc60219e2372c7528832d48d728223645a97336a0429215754cea391603f04205d3e3074914864c1758ead8eedf2c76f4fb7e8b913ac7d1451e9523f5abda93246e3f6edea7460249313869536061ac32c6362e0fc0205ab5900f1fa58f91b7cc9a190d3f770e33d0d3dc212c33cbd6709d539d10578f39e9ae609a8043a9c041bbf82c7635502c03432d393c318beefc423639f53d977f275c519a0e55b714923202d2f047d048b0ab803fc22704eeffbe21ae396d6d5ca1de9cbcb135df728338ab2b35303e8aafb5e26a1a8ea99c00e3d3bd22c10c8600907c39bd9c4d443809c13a875dff54f859999aeceb0c753769e285062d876d124390a34af2b86e782e0d7d8175916c91a87d3948d68d6dbaf7544242aa29447b0d0aeb138cb78c8ff28ac4bc9b420d01e8104ac5775474b7da3b4a6fb3d064ec4569f6b09f8bd0cfd65b41f682420044551e5d13a8b1a35798f71a3c41455528852065ae06f882ee2b5024c759475ad31943e96902bc5374d16c0434e32b4a574b38ab0af9cf694065f5fb3508f127006c9edb84ab63e42a8884a0a5c21f853cd07d9fd32b0d1d9ab4851a00f2bde3a60bc2217c0cecde13c4a53c5cb5145b4a5cebd68a7dcd66e8c174dcfb35f60538b11adec6e3c81202480c211239ecbd1083a1c7e514a6efab10d3421afcf0aeb6650be907ef6a2486fc483d491e3e8f40dd07341b0f1e7f1724f1a366b22f4d9592ad45d7fab18376caa45d8b86278da15e1b2adc1a1fa64f6b9b3dcb3c3df966c7effb211f89a648c49874576e284795de1d98c93f5ee8d2510b5ee8f4a66b1cdf4d2dfd8d17b97c0525bc242fd2b004628ba2da234a74ee8712ce5be4aaf23e88b2bc502cdbee38e4212ac67e35ce930deb89f982b695f8fb7cbcef0a757a2cdfeef87cb41a1100d5872e28ae08626825ad8f642b8a6277f13db1551f3595e732f86fa23784a49de2be1eed30d995855b9a864f53cbf5c0753b81a1b09f0d2202a5d0212d1b83028dd42c91dc9c62232ee732ca674406087250d3015d97e96a9d1698b168cdb20a7189f55d0233dc7156baabca96917ec63a85bf42f7fe6fb3bd640e2944e9dfa04a99c66e24e0420ac680af9cf53576cf54346d75bbe8878bff972be5798486c9e4a5f7833b8e39eab41d690f33f8feb78bd632ee3332acd7e89bf0e2ebdbd06702529caff9b356623ab22671e49025e99a48836bd08d66e4399f7d87f4893ca938fe9251d547d2b3479898d19fb27bd0372dd89f0b5c028f5423af90b79cf4b62405795c3b9448b57ecacac0199b32573f90ec9ba9484c0716fa5901cbe99de1802db34971a8305ab54cd51632ef5c416c19a6616021c53692a43c9f5f6f52dfda726ab7ffdd244a8e6ded14fd786bf99b2ee06e80580afd46ae176a46dbd0428a2da46442c266a4643d24a66abb077f36432a3796df963605e05e7592ab6945b0f852f9fe19f3dad306e3a51d42f0baee11eaa33286687e069845e770c7b14fddefc9e782593eff6e6775e1c89e31df15e512fab3c741cd1c421df33f7c657d889425981bcbf98e93715c0495b6eae1f62c10374ddb1bd9467b42feb3f3bdcca2b7b42c6dbca6a7702ed7f27c237ee986f25c5e3b92dbd01f75895e6cb0facbde69f32dcb2695847c00a0115a268cb55b19b0b8d09c8f8d01e13ea3b377cdf6add5637d112e235dc84399bf84ba514b9fde676398eaf471b8c7dd4ea421104daf1024fce76b8ad3f243a6de82d3deef58c7be466d3a15f7a54c83ad257ae9ed2c7a6b5935170a278b5115da6d27dca7fb3e2811a5c908607784f2ac8c8b0180a4096b89d1bf497b02f60d60fe06271135e9418612b848891cb8753e31152993f816e83f8134573bc0cc2bbcea35643ca6b9bc4e05f4c433062c47043317401d8e1bcaf90d7919e60c4059dc5ec45f876e734ebf6ce7f6a4e28e7463952e31c44cf91d941c328dfe03cb47c2663e3c2095a5af784e66db8bb3fd1030ecffcc64613e37d8370d5266ff996d169d0b771f14a2f1fc51426be19b40baa14c7960d5af6b3a6542b5d1ff27678196f04f40cfb883621953b684b3ecce5941d25983f34c40b66723fe200186cc8cc6c2311a3bcb331947272fc477a0e52f7e0476c7081b99d17e4884149e29ab01c234c7607936e9d44d462af8d17241bf6b7c6eeed8b8f183b54273324d7bd8127df400ea5fd1cbf57f43887daa85859a43eb5517d58dc3564a5abefd2a7512ace33067d983a2ea816179320049703c02c807f265a5f26e6d93e879d992d2e82aa93990f8ffcb2fdd7501e98ccbe100b93c25ff5fd4d15d4174ec931550161c43db0e19e4b5ec3984c18a465e0078980cee75e96cb40027fa9df2f3da6f44febbc3b70f1ab889f332d0ccb37546678a3bc977abfcbc430bc1f4ab76d565feb1be416b19462e14f428bc946b514bc417fd8266cf1857654a593b3bbc9ed37f4ea8d6a430392ad63819a1b903d8f9fe75922b8d7d90bec25149f9907283a11db2b9b46523820ed28584308f3758f823f6c1b1b11e74d90463e176d762d4747bf2ea68291e1f5a59c389ef472277a00f29402bb30f1aa6450a4c12d09401e112bddcb715081c6fc1cd105ce61f40cda0359a1fec7ce3518d5a575dae0888e9433bb0c1380f6a6fffc47d8f268e899bf12847bc803c19e754173476048dc2c146ef9c6dd3f7b4346f118dcc42400183c55df5fdd0bd2d80a3bdd3334e0bdcec952080ce6bb6ba89332bf25f0bba7d97b23916e76b1b4fffa03f97f2540e8aeb7946345bb21469988257d196ca1b67cbdb5a90d235e83ed32407975be891ea3b464205d63d9f6d60828ae85a4add0619fdac9b56f54b2116027b4b44324094cd35cf9b12b4aa22bccae6e27c0b83aad84354644062df69b36125330fbe32585f9020d2ca6ad3ab05c7a9742b3428d86dd9db671e0ae87110585601736363d4294c94420535795be1b2c9aa8c2d4aa24ba6761fe8da75a4ec56a17c70da70d5898a318edad957cb4e1351de914220d627af329db40afd430a403cf68892de9835729930fff6cb7ca32f20f38948d9fa61f8546d175baca56b8286cf89f260d58d26a20dac04b786782d98df4bfa7034a6556979fed283062b23d5f25acd46aff33e9ef03f5c5a1febbad08b1ceffb70048605bd578bfb2b78ca47d7505035198550013fba3df5fe725447f18e598565ea76dcbbca802b9825c484edbd0f31ad23337c2aa62cb852e082d337f959e172ee6ea58d96d34bb49631b815af38de81910815861a0dbc9a184945290c7565863698b8a5d9bd5a0b3c85cacdcc20c22f287ea9aef6834679e548d7cf33a244b22fce7a8cd24b98149325fa9cc935f196984b098f0e9a9f891d404bf90bb8d5adaeb7e75cfa706d33a19b08ed93f7c48722919eff3329aadc9c05d08703f366b241d84545fdd631f3bdf74a70429944829bda222a9b5b621ae80ab650fdc3c6b6b386fed6d99cbe5d341fc910ab35d075b93b5fd17d0572ac454e9424a36ca9f6cfc6232ae9b7948cade26081212435139bec4b9fe918a4a2b7bd918d15865ff7c5db8e8a57c9b509533cc6f9dbc369c33594205964ef047ed3032480f57a51daf0f50898532cce9f632b026f03ada662cbf2cb674d429a4b01057da37ac63ad80cca8656d30c98b0b30862ce6fd5e6d9b6c8ea013a1d3a002e2361bf80f831f9afe2d19416130425c4653c9c0bd15601955d98e9a683e396f35861d06943780cf4acf14d0f60bfd74d7b38e6a2f9910d264ab490e397c4225a7778ecc558adbf1643892aeed80ad5ff58321ea645da1b7983cea071faa7aa0229c9301c97806cdb3a0f1816914797e2a40a0cdd60cf39724154e8d7b019be4f8ed3aabfb2cf2e57bb45fd91ba504954643d926b6cb445e72f6fca556ebbf829fdb261276ca380ba918784a3ecb57a538f6221992f8dbf1deb492d38d684394e1967e88c9edddf75fa0260c05ec90fb85d18edf66124efa0c7b9be74316ee259c59c29e4504ce9210493254f3bdf19935239e383373e78daba2ebae1b34be290ab10cad114f2d5feb5cc91a21a5554483a399f0a7e3c65aba272033735920c0505ac2368071a91ad0362813f1f9d85acb68e322525f9e2e283d6306caf04331f4a8c4cc269f48176cf84f20b009f1f04b44ea3ef16ff9bc5f6bb32c718c465f152d09058d23faee9623309fd4780ed1085b307933f544ed547c536cd75fb0be641817479708c43a2eb89762eb1b9badb2b1c1951f9d04238908f342fae6a64b15097d621b7676d41c390b8d7095bd9bed61ece6c61a94f1c8d397be96f1e4b3cb45066ba476aeb49c242b5acad77635f29b624107725a87f625a56c2b82b885591e4317f2e5c6609eda1dc12c53aba8484a20ff98e164aad449bf3c2c120a184a4da60a1ce36a33e42a1e205b3e9717a014982065ef7651f87e40a41006103cee135290fe95affe5ab92143362180a32895485cbea8fe01de534516c6f0089418d52522c8702ca9cccb131c652db6139159169eb4c648d2809e2238822de9bc7b24dca62515c14a48659ebc5391ac13e56b93452f6118be74710847bd141f37e015c17d2c02cc0251cc3ac37175939add8107d648d107fc5144455026b6b7029187841ae63613486b7d9ae5469d1496e6a8684b3adcd404c094ab9e4010124d3dca930c8d05d12daf9b4ef219500a66a48512aae667e27ac4845bfff9a67b22cea4b13d9a412139221c42d82e4c60a8b211a2b847f7bc436b544ad30a6af65ae6b90c9545d59911c0ef867021934f5da7443a33c33b0156eb1941640a1b378d37869c616426570922458a90e610e468fd35d66c067fcde095cdc243e26b417fb15c6ca3260b08c184efd1c3c8cc9653712ed35eef44d34179e892eab5b45a8d3bc53855ed42e4bffef31caa3f3a8d17c70df9cd20292712aaa4e73921954a36cfb468fce2c9c4e8beda9042bca173fce9be524f9f2fc094bad23d3de37ed096776d9138a23a20b6c0789916d970f42ec862512a959dac4690e93cefef7b952f0fe2058c44f150c570d28f8a5d0c07bb7981ca264a988a2cccca98c449ce7aa59d3e5b28f337f46502d7d78fea8102b68065f16871130e8731f171090cb40f2eec39408a89c75418543752e87bd5ab0a2081e803a0ca141b47064c620c15654c7d4be212fb582d1563a995c587ed54f3a5ef12922c65df2b853859451c9381220626a7e912a18549a4242ee95a26915ed88253e5bc1af85e617e15aa965de3287c9a968d833ce6502129b7224a643d9bb1a01794f86a5b802b5cdea433c8fbc68f600b613df2bd41f4615907e308871254b23ef85e4ef9abc4d9dfe99798dc0aeb97d107fd542123a11b9aa592c84e2037e5ec29186971a8c6dce4e78d95922da1431f7c0ff28683fb5442bc64c6a267c07b216647693c3cbcba1279fad01fdf32d695534e49eaa4e86cbb52aa993854fa6e44ca54d3ed305e2a084b0718f6a0178d00e6ad8ea17050af510daca3afd2d714b6d2c553962d01d2ce5f168697dae36f44b47ca46f4c64de0fa6b8dfae0b2091b979aef9b116bb0cfaadaca5773fca38e8dc290a82de2002fa7cc6c02b23181c58431a6a99dc541727ee077926de7707fdf1e0b62a2978b3cdc463aac40d311f840e42aeaac2cb040171c24bd2a30f886792cde82c55240a2d0b5155ea02f92a47b324920f7b472f62cfb46f332c12c7c2e672f4a41511d9d11eeb020d95bdd494eb1b10afb4e65cb8cd3a18b1ab40066c03321172c42f4140353866a788852caf0006c957af148522e768279d766bb4a119e0e07134b3b17b04483f7feb45239c19982e0573d9ccd422cb00f52f20ca38ce3f7b41448c6ba2ce114626f31588f9b65124cb1b5800a0af29b8cf335d091059ead14ac5838ca1af54a79fc66c069e4d580d02d09443f4271f097c2aa332716f93ec3f53445ece18c16dfd03292c5575fd19f010ef398d8883b0c48809e9710a2b381f11c05cce06463a866460624a0e2d1709197620df4f6c2f602f643f023ed728df5b3510ca1e5d8edf365572cad7d2027a699fe8fc2fe74e008339a39af39087303c463846f981121ad7f05b26d9d90f06213a43eec74d473fc05f1aff4e217626ac68c8164a426934e04af43332f456ae0dc4a68d91d46fe5d8566c6fef467bf6608ee472808f070960e69fde29c409f142f233eb405258db8a0ab222b3e4cf6972645f59fe54c38f58c3b623eae44382978d1d5f1fafb6713f1e2eb520642022215ac9db28113a070c6bab209826f78da6c7bc806a4584507aacb8c7810afbb6729d80c161b370f0d89db7d04393cafbbbd8bfeec07bf8ed649cca3798a6d3ff53c2c169965b237629b2c5f15250fa6b0b34dba153cf8d2092201ca65fc21c4d10cdaaa46fc260f229b803dcb84db241403e1246ae5c8683f2df23158841e0864c574066cdde8e87aa4faf1906c522be92aaab1d44ac78f339a4c70056c8b6b622d22c615661d118a4d8507e90cfc5fd9fba9db592fdb7f28cc277d46cde02ca40a26be1b4938d77769256d9354fdfb6792397ec2a8c5b1818e5576ee83cabf445ad4148590890a3f1e5c43a7e51abbca0fca7a35239a67e7fb0f5190848ca9f610251d238027aa9168bbb0c88592bd10d99c1cafcc5d1ac3c2c9761bca28624fe63d17e2df1618a4d70bf73828b09ffedd886ec562e8861333f5ed01564996705df23f8f1adbd53a50d6f2cc6b5b6cf4af521395c060a3988e9ff9c313b027656dd6204e8e13c21a4df0ada3a9510477637095104a833c351f2b5f557c0c5905030dd6628222ab1d365900375ede0f8a72dc4191a7abb3c391aa951cd7ba34b40c90ae5d172ba63de98dd269bd9eb469564e4bb3632aa460893c553e7ae0acda13e96958f617aeb8e12318c8ae6b23501a4490ec5f68de024113bb729b9ba0a2352036f22ed279c4d88c0e9b04b3484c5583943977d161f6171c5e13d7f849a9d534d72d013a42d5818d6abceac463d06691cdf8da77a09232b2c56657d985cc49981b173301f3ba4c077c486a2ac5c3c9f1f3b1c7855b5c5ec2d9e5764a374b3c37ce0ef5d8108f08ce4cd9683037fcee0cefabba7f240021c2567013ce973851b294d156555dadef26711f46ac1e128a5299786ad49fe6a2288fa6985423ec1914b1a918beefacac8eb0d0531e753856d51b20899d1508753670a2335b8698498666bd07aa27149af5eb610779286d62ee85d207fa239dcd1669e61833a443333b28a0d6db6f1df8071895b16852022dd4e3d78a33137749f6cc0436bf71e2d806488e206faf1a8af7315222aa40f6113895ca4511361f1d1f91b8336b2a1ab9a1adc40dae0889b154a6653ec221020cceca2cbdb752fe8f194e78e668aca00bf0ea3409045bc51ec442f8dab980eb7191b0169e492e2c56e65c929a7ccaa5ea94f7739c66c1c0fe4550a3f0746a3987b43461741ff7b42e484f40fe7e6b424c413ba644a715259262f1b71fa80277129bb760a62c5f98991b2c4d6cec3ace91d5be7831b1b47c22ac29d21600485be28d4327cbfae15dd433b2de582717fbd1b0f0538ee668f88080704ba635b3b8f76639e9981a72fe70f8d2fdb59201f9cd69a36d18b8c7a29cd1810470fd9ab19f74621101a0ad822c69c5cf79324b80eb65f120c1133cb23511c7dda91259881e6cc56bee5005d4670e60c11d8470006d2d1f530fb2d7dde4cdbb910414cbe9c842c0e7ca4deb0c78864daf616c4500583932643d85644d76d4adc7870d0db3a51f31e127a23feb3ba42a6c2e9269f5cc219fbd3b3bf63cd9f192a783259a4cad6a91929e925e04946f6185ba87b1a3282158529f7c8bc113a219d1b5c963c2e0a2171aec49f9fe5294e56e00b77844a3f64c22ad1267e90761a89e11b9358e6c8cc286337e4c962df46bb5b5a39996c9347460e82569ad42ac872e3aa620f4900b1faf4dcf256c1f8d05b81aa68a728370a02c365a705604661bcee782f75100625dc6c711c1fc99b3a567299d9c19a5d346ef637284b07ddbab10da19456b84c44c60118937078c0f484aa4d348e971a90652d5a69b576f7a55818899ca1112c1dc30ec3d84e2a537445be77781479f85d43b58a87f464bc761a2fe0a575001cc13324125eafa786e14c1257a45da4c11fcc1e449f9fd8ecd1297d7d7254135e174e06f2c371f6bb5ee1118180d9a5b8b0f74517d10e1c073ec66aaa143091f196be9b0af03f115d2b812f508765fdb62c4b6b5adc71d30ba4bbcced166790505e502559145ed681806d3586084d94fb2351b28f11b078f45279afb3c53d8e292733e283e23124549ae84ffe85cba7703b15d161585bda2affb634c699c1935ebfaf2f5244e008fd03e0c637545890440708d0a8143f46a707a0271236fda05562e6143e26524d251c45b44bdb907d001e4b3e652dd0eadab133e5a3b3efa6fc45a4d09a9a08f81092e0739ac48b4c1e5b7c2dbe9ed7493e845a176ebf5a216f051e61e9357ecdaf4351cca4d1ca773b1ee3d3e5dc49a35ff7584f2c6422bb6305088ff8c02ea4c57803429c00de49dbec6b333789149eef1fee484a34528e1181ecaec326311f070b93171fd7dd1b9cb9e56ced4024c7973818358ba22d3a26f71036784a12cb6e313d10736a932f386420e1a0af80f34ad8827083805acb6108c6f3b95f631edacb1e65680e3e7cf84a8083440a04824a85b82870a2791ce3aca38911100a769449c77c9ede91172c403cb4bdf6420bc199833b2378cdca0ff0341e8e868448e1b2a033732b1da6ca60d9c7e64dfa541e40c239d9444684e52e81426534efa9ed624d929e8b842904b37f399c1cb8a4a0d9ce766221637d4db48105855d15b20644012d32c85cd4be2a1ae14f41c4d14d6796b27c6e8c1c0ee72e0797cb6f76625fb5d1ee03a29c4805d981a7d13c226774541562d8ffe522734eeed1763abd24099fc532fe0a507fa98322a56f39a87aa6dc7ce9d7c01e005b1cfd27956a41e7aec392ed358e3b1c2bdfe98e6a990434bd17f8f12590f08cde1ed16d75ef3f8a16fdf230d1424b36c67e4c6b5aed49ce7f959be4b4ecbaf4a1a0492a7e343e4f1d988ec0b5f987ddf18a0bd491b6b4a53c02d83bcb43b4e2c29b0d2f11bc72c7619d7493206934f3367f6f3aceb3f93a9deaa8181adf7eb98983120563dfb8e190fabfb13e0600ebacc9df51b4c7db53124774de234276988bc3a7aecf2ee022d40c8ecac6994ff325176c58a169b44daf1bf1c817de23fade073608f2020ad500c48fb575f9bcfdf48e9cd2d349d3e0dceb0245b114934b2d8c04de5661d27769ca3f2ed1a118750108cff90079bb90976fab8cbe58e0f9f00fc234fd22844a42e215db8648dd55da30f4bb34a59413c384d7a8f6f31499d72a3acd7f1fbd99dcf44986e547c9b2022204c3a51ecf4fd937e4884b42dc3b83fb62f12e19f4be4319c881c150427b7e29beb552f48af83059262856623a714de22bb4ddf94bfdcb08fbd647bce37e7404cd00f92613ac329549bd15b6d66edde3d315aa933a61ab9614a7d8e450887dfc985217dc4940397f33cf0e018237807496bb64ad5018a42ea0055da5049f730175bd1b941c0a5bfc8fdb12d6ec6bcdfab46f50581e5c5d1d18abb400d2ecab95a0a3a8bd32a88cb496fe383a5042b47a68334930639b6e37a39cc2426172750828f3f742e0f16483a70772e987099877d10572fd344703a4cc9254b9b3d3c9a6748e388417333638ed93f5600044354d4ca964891bb6b0f49dde6a865745db0eb1f347bbefe6e4197d63b15794cb1f2f472714382f9d87180ec0876127fff3b637481c00120f1007998a3f6958eea2bf69ff41550a10c40ce1761c3a2f27f4862f1758280e6e10b1034b1d1db90ccc90ea558e00229282d3e4e9d90415e8198d8534fc62fd5b737a8e0d19d890640c7bea423924f06695cbf2f788e5dbe55e8eb53420107dc34fb7cd8dde4f3bfec3fd2e87614ebaddb1d41646369138acdd9685d64be14b829daa360533ee65157113d048a642cb4a9a03c4e6f18f08f17e1ff52233fcc965205362a8412f609d685120f3144d6ff88d4b72187837cc18038c55214e258099fb80a0b160c4ed2abcf9b6eea524df3f79e8fa4391f7440f3712834002d5e13918f900bfc39c16f97a6d559461e430f7cf92cbaeee7111cac18029f7799ceacd579b0d42a7c6635aaca1306713921c6916474c4f524bbbbfb686957646ec918a06e3ec08591b01c17f1d8e625c36fb929924ec4a88f95d2fea6960da11b844bbaad7c03ae5860791dee533c245d503a46b16866dc31b825b893748e14b4720224a60ff4c788a6a087e6c6cce317af9cdd1a9ce6fcd4021acc60b20c5f464573d17071ce9ad3ea542bc9534a58ebd3ea68d3dde93942adff0c97e9fb6ce7702ef360586b33d2649bf0c4ed774af614dbcd3aa09b3993ef52d81d4ccab155416159ca6110280c97eda38f7ebe57fc70b81990f4091673b70c6e22befc5da54da0f7e2225a0fba18ec7a9827d594c641018cb12342f5bea5cdd2bb2d95418f974203dccc9e8af94a7c447af6d57b935dbaa976ddfab64c962b526cc7a59f06cf84a90159ef6ce0eb3b92833d2c9491187ce4530ec99cfb4cd038ccbe0c1803b5c06c251e8a7a8656a91dded5828ec55d802a0033cfb9747edae1f3a0d72608b85921e40d9de34f7fc1907e2c1610104a7840ecdf2eab0ddc3dd64f5dc6e17eade384721e0a62ca5eb0003d54b018eeba49e23479cd76af894e3d29d6f00e3367676f0505a616a75d05f03f1a1322addbeec039dff1c1502dc133ce545177e018670890a35d64e4bcf103dca5a596397cc792f578c66cdf06062c32085ac3738b6bc9d6691dbee16eb164a85b8e7f2962aecbb9ae17a820626d07ed2cc93ce4dad485d31655a23de02740627725e3a2d5041ed484ad10cadd37a25c36d86db58cebb8a5d571651d17ec314d02a9230933531ab94289a6fbf2822c4cf062f624fbb6f1ff8a23e831e430a4b78f44a0a19c4dcfed2eddaf08fe8e00f24f4082d883a46b4a81da8934fcfa9b3d5551c75fafdcf68b5905350d033e55906591a09917c1396219bc4413e30675ec4fa6a49f8189572d40381f1d53fb9c443328687e403ba931bb9b7dc72ac89802383a68eaba70290ff2c1be6fc88ab32d62d8232db3d85290d4949699b3778ea04c41134403ba5ad8936f33413826a9b38954cf8af73305635e1f7ffcf282d2371c2117b0be0a8ce4b1facaf8d76a8a967be7770c3de309c111a233af287dd8155f10985c033c703e41c04700f07deecd3f5ba6f5af2e20624ff2c73b04da8caabcddb10022c8842c21e9d06065cb20e7f3df9c0a7e78be50ed35b56afb2c582837538970467853189e05377f77cd139b0819b8605d93bb5689f510b42e25e36b17a0721bdd13d15b129da3b82e03c9ec9935d625c2e36e1095872f129b25b2f2097d9bef24c5b2b7b26808f8315e82d0157a9c951b7542addaddfee165adf59a87cf66d84c962a5302d8aa3e0c4b79b8eaf8fe2af92ef9bac3c7c89825b6d416895e2203f88a9e025c906eaa9e33f9874c11d0c0ed3f908bb3a4af9221c87709c3318a9d250e4210bc7b01d4c2e61de4b7ddd302d95794442345b7b01ec1d6e5a6a71422d30707948273b9787d2bc16c7cf32ad2494bfc2b1ced293157830ba77c965f5f54141171553e5fa0fa10482766a523d061e1c5f46e1d20d70428461b7b3e5463c5d0f6d46d64f7ceb88186580c6c56884b05b5f0744bb2165f6ac97b93ca650ebe70a341e6fd07032f1aea03581b25b2ab3c0a02f91edfe1e7d2f82087f079393a3efa0010709934df362eaa15e20be81354d776134da862f7d3a607dc93c2bc0a1a88d45737523b8d99c4b24ed27a8e0756cdfced42d24d11d9e50f153c9914e7376d61116630add58d10787dab5295170dd1781154f80580b2296c7016f0dff2fa0a7dbba351dc2b848ade144cce2504be810d4540b80380281c60dffd1cca6f886a47a9f1ef3801e90d151d1154180892d25a063d2a56b96ac783c12dfc62413179d9fdf33d1818124e927c4ae1d4f9bf8f25aa276a462671ef4a11cf0b4222e1721ec5f64d17d41de39fd885a68939453b159509d8cc334d8ab00ab360048824d28b79da9dba7da8559b3eb2cd090ab32f9689a2e0895909b40ea807f3d59bd98169e723ffbb6c63a3c93722138e23ae30f86900d918ac5179a7db8504920002a4605612fd8585700d4d62c836eee76831f0b888db59c58ffb77a1180fc505388b343ab353926dec99f7fea957b04a1c32f10c2674ef0d206440ee1e6fc6d2dcbf7a112043b43414d267c6631037324cc16e465b520dc24379f3aa35506ae67c8a92674f8aadc5f132b52c8b358b2aa2a6c4ca2ed6fbed9df484d339a067f278c972ddceb603affdebadcbe7ebc0f719a46e4dba8ef5f5495c89a8aecf94cbc7317f71f0742d3cf276089c11b8304c8c70981b13f11108f5c60046a268072edfb7d1ea94216570d854f5484961ddb377bc17f8ae0059f6173421ddfa9c5431869b0d1cb63ec5576db45c986d59cadd423a211b924568b97f99ed748bd4009f758ce23599bc7a81fa6694ec28941aa647675d28fdec6a33daa33fe158d6e1b7d5e1330d2df796470e1478b15be05eded45b03dce11d681e5d2865f3d3d070c9d711a22bbf702eca0359f6c399e36503a45437e2b3b2aba8b0145145e0a04587b01b88a742e7d64433054679bdaa28b1d31a65557b09052df66c4165cd424f03fbc9f89ef59bb010b97d974c2416367ca3cee55266ba0e9b11dd4c8ff7b4c338b8f6439bd99862a07dee9fa1319421247838cb70cb2f97474031e082aff9be88831fce1a8656fdfbe944b4935dcec3eed5ed0309769f67aacb29c7adfda679040d8589c4ebe56cb33ef7141e7db7ff3d4bb6941415662cd6616a7e7d0b9f066475a764e58a17f1310bb9dd653336cfc9d2f9cbf2b6aa03485516574c1e988d8b509695278aac86e37061b132b546b9826839b33305ccd196cc9a7a4e55a3e4dde815a3037217750eab75d69963b5a85f06f29e2e3aa135494137e47474d32a0174bcb8b909775e84490dced7b60b44f11c5dd2c8f2e9fddc03674782744a128584e9f921f0a5d35209f164bee8cbe4a1ea9934116d605e53d536aa730a96911a6ded5b9931c5d9446c0e17ab04ef6814a6f2781f159e5b24535993060b06964f19e4d89efdcd7f0dd3177cf0525dffc45435417a00edf01ac8051d211258e28f61a76c82e3eb751d524ae292be8746ddbaed3a0f62c2c2760ae4e2bc8ef97d6c089ac09709010c72cecf29a720bf718bf59fe2709729165115c0f9fa1d4daebaf439326d1e1e142e88088f48146e8d707c8ee4aae1da9cd0e47229feff049d0601861bac4b6ea3ce095a9ce823e7ee05da1b5c9d6f7005bb4b305dbcd10014f241f3e4e5cce03946f5ee11482844f992820fa64344464c8d20148fc7dcc0a4c75fc465e910d96d8a822440840e1a8365ec8e63319c534236eafd7387fa04edce1d053d51893b9eb95ba4a40454b4ae7f468bb16a2cde7df4ce72f1721ed57859770afef63f504784e95f7da17b67bed0752750f50a7641032d40badca62f008278ebd4ee2e7b86944fd62385a1c77f6617fca5014fcc95155a6b284da8f640a3c278bf7444f5430b8731fe42f628193cbf543c32fedaea7d6bffed24e65e947f67bb08ffe586fc25d78b45236bf5be19953b926764922f2113df087d642fd3e201d78ab3abb6e922a83794dd0a939744d00b359bc2d5515a80013c00f1d36bfc00c5e2e13f78ab060b6edfc8150b253bb18e8fdeeb33aaf06898d29ef6a7ae37ededc8e31b613752dcc8a2f9fb9a4227786e2aa631714610514e142f50c6ca161c42d201f005426f9fa7e83e7d8854943e337bd156aad34db8a6b6d2062580b14238e7a029c01b31aea228a0ed832cf65aa0ab9e8abfa81a3627a0abc5d9850c73b5c0ea8602b307a20aec08467b0f8207cf2ea52f96ae1b179f4d0ab387f57405ddad42b84307a1b47cc801adf4b9918300c3bf6c1584001503a624aeb07942f1fc42e47d5e003d1c1b0f658da4f501e9d9919c7d6941fc903beb062c18c7ba0899b8316e10907ca69936288fda1aebf399edc3ee9e315a75297b5c4e10cba3c2e468a60c079f66928d038b300612ea624818da5f301eefd79369c0c4373f196428ee8205d1a5766b7c84f418464c52157a62453c40f2403e2cc290da24effd180f31f8f571a63a0c5e70c3b9c5bc0a04f1cd67d1e5c620dd82b8bfc15e8b90fb0e4585457ebfaff61762092170a1cdc6462b3aade4de42744f99f8201b5f3d5b3ab571f55db7a3ff392c84793afc6a388406ba84aebce604a75972d2613bdffd77edb7ae89fbc717cdc7c4c70c84d4347f472fb34c96abe2bc9d0601270592d8377408d2c76d3440c04e1f5f416131538f896369c3a80dac3afaf127f6fff543ef27e81cd075554adf27a67d7ccea699a6d4cc627c1c7a101d08c455367d9eb8f6294bbd579b253834211dc040cc8e34ac72b7b2ff45516948962c93d74f89ca24e37f0c548fa8911aaddc2c40991129d7bef38a065d814b014cbbf9f41e232085355241faefd65e140fda53cd3befe86058760ec2b8dbee9729620e333239228965c7e2994a5cda298824119fff613a8757714c5347dc3d29f76e4558ee505ef4bc034c298f44ff8453398c0b29a797c3b1d8bfb34b8dcfb0a3af8041f77f35f0eb709f0d850aa9fa53cd16d9bbe45dd75ed7572bd505f36d62deef78171c2690a5a6aa2d882e86ff5135b9424bba1f511f73d931d9e8d224943d2e4f9c7dfc78ba1939dd522601bd80f254595d0a8ad27b6735cd8537ed3246abdce5ea074ff196e66058b2a1eb2b14ed06d75f3474df8ac7f4fa8786ed7dfb75e3c08854cb73f7933e30852db0a2060628579664687323bec6a4d795a6dd072dd7a281aceaaeef6b996e25563287e016e21f2eac476a0e89a37da5b24b1758619ca9d4fd9654a215cc8efd61dc49314650118028c90468dcb7dfc70f609bd6cb17e80ef929c8931ed5bae485d3f250b4c8043c8118a50a7366d0c150cf35b10f3206217cb9397f80406ae932e79031221436ba58290b89de280f7c0aa141da824bd7e1330bf7d5198575d0dd9857c09432ad97dc30db5ba7d8f719c8ba39c00614f10e9e445161f849748a51e908bdbba90a81f0434b2bdb8a20f5f8a22262ce20e4e888fabb229cd62b9770930ee4aa44dc07f650da0a538a85134be03ff4f9c5723a31c2984f53d6a2555f2b9880bc28a5707ac2a88022613fc1e454cc60c08726036dcd16a694e806ad4e7773bb0538fd816a997ad295dd4be28dc39d07768e1ebdc827844c51634af9b7fc8f05b82026849b2267b604cda4bfa840a843f66769aecf08f482951f3a21b882d4db971aaa5036eb5de65ca0e4ed3620b9cb0c6914913e2ae6cb59628106e9a4dbd8de4bccedbb12d9f540123a4bf9c349450e0e10272f2381f262819d12c406da1563c0db61448db497552f74d61c18461761a0976d6f7ab6fe20753dfb5c47cef784e0ad5bfa62020cfefdc6468458560e114484ef22a6464075ca0b2591545292ed3e30238543f3b08e30a848082cd4100858898b14dc246d236d97d1050c520fa7627c768d8d682fdc63db9ef4608b2b0d1008074a800298b5318724820c84d9c1e90eb9f0f8bc574e1691f0ac33457bbc9bcb7149195fb3dce492dfd6a61a31ce3e3a069d77d818a45312c94306b23dae1acfcee74a258b701d2476d09c564390c8b926c46d99999c0423fe0f669154e1a139756be4bc353e73b0d0a7703a47035c4a80f8bd8839739e22e2b830c7c1cf9cfc7442fc01383f479e20f4e8b4809f3f11fc7aa3932436ae92b004676c74a8a03fe395de86bc2a30179db5bb4c2c8e526edbfa83a7e63e428768dc2a1195380a8ea4b90e3e77576930fa008433da3b1d1d044fa7babdfd3020c2773bd2d93619a7b58878a5c1d8d54e9081a654c0e920bcce90a233d474121d3b5ed22901d5834128a392e3e30918cfbd8f756a223f5303f61685a0fe058cdd27c4efde1dd77f85ba1074c796df37b3ab3462c24c9012e08fd3ea1b37ec981426c87f965f11879f0cd9076f2c26afbad9b0bc96d8badf2561ba6b014a632453388ff3148d3170b60e0a7f620e030fce2df18598981510c4b05a122dfd01cccfa0d1eb3132a7ce5812869e7c1cd613fcb78d779c551cbecb4288ce059d4e4e8720da1da87c69d366b373014e05b69a028b249a183fdac9dc71afa336fc1aa0d855174e09b06318edf085ea832e9ba52380aef1b5021c2bf910fdbc7653401b45b1b1ffd256ba8522f2a80711dde4daa7c66963926865fa6f699026e83c37d55c17d734b3f06dbd3e53ddc4013d318bc76dc19b4077b10f1370a548bc3e3d6be3b458b9ce39a1277ce8c825c64b1d8797c117c2b777e9dd1a3e1652606bbb9f07dbbfdc0645d6dba36b204a40938e7a83e33ea3b5f98304796c29ecf0ac273d8a3fc61894ea38ea21da248d4172ea10ada39d9bb751fe1cee289b0804a5cb8506888b3f92202faf492b17697cf299528bc72e2a08088df1b3a800f83e5684ef2a3d90851a935a2c9eb0f0c970ad6f085db0485e6129396624d89f63704e17f56529219366ce7ffda18c4f8d17b14bfee5707911a72b307204fd9763c022f6898669d6b14a98e12f08c7ba28c3bc18c5a1d53d14904679f1e46d0ed2d8db733cad26d1d351e5244efa7068c687143ebe9561b8fbb12b7aa68af39d124b537b677ea76bc60d3cfb474811b453501ba26a9f4be5935cd9b3795f5fbf7575caad2feaec345a7d0e1831ef7207e93714aedcf17d125dd3f695376a9a7301e08984ed2ee38e76c2802b90381ac03591fc175d4f8572353bb60f217787c7dccc5576efffcf5f63248596ad3ee818d6faa7e53af7bce91f31d630f9217cd46a9597d4ea87a5ddabf17a0114d6cfde0df2b059987d36efaa4c5db8db0fdb8a3bdf6bb7ff40ba688e4dc08d7c45892a523281829ee882130ea5d4e39e602926fe8742beeaed2a0a1d2c90c5ed0f5716d526943e0e1d98ddc1b8f5b60b3d9a0c5542025646a6d123dc19f3440741dd292a092b6646469477ba2826307c0e01f2aefa2220c6eabb7452f65377401a89a1d7f4a55cb3ff7e20eb92d790250e5f468ad37ae0c6a33b79942455f7a39a8ce01b22cd31b0a15c4e49c4d70f78af92b3adbce15b7122c36fc50054c9121cb95fb703588da26a4a1621e1d5643f7d90f63b1266725f29e92452f7d627e02b6a9788265e83c2a9faa3e6bdbe69c38716c07ede97ccc2417682403373228af5f9723d5e0f6f45e8d2acbbb178a07927d0ab90f5ac5df6b5a67b410822e74256f8a44c27452a327d86cc86ac928ff8e44854892c35d515222787bbd2fa87c56738ea7f5f8ec95f63c109709fe309e9d21b344701f18825abe943b2b72a77fe2f33730baff222452109b50807822d642b8fcf64dca9df9aae7ae2914501b65fedb8ed13b9a166cd7788076d9b3229933ee7e064da6cb38e012d6908472da0611c6dacb1b734ff7feeea0cdf4d9bef31b7e4e03bc770d5eef722254a174aa5f9d53831eac5917900996e63eda06eae2a7cca6448cf7edecd0856658c608c0f3da4a1358d334d4f9d07d81266e6d0cb4865a44bfe7dc4392218ea4e07e1b62ee2de7595cba898c5de5ce1dba76c3a3f0a20db2698baf0687a08ceb3b9485dc60035c6160c2411d9ce7f0940d86c8a391b1d932546705b68bb418c30d422b8a68ffd8323a839a6683979f31d95f19929fe4a133ae262217dda6f11504c7f22011de72fafb2e5df5a0cc1785c3378c9ebcb563a61c56149607c7be1558cfdd968e1b340f6f5a3c0be4b3477ad9a09d09f025feda475dde6d22f382acb2afabd97b097bb860a46041bb892bbc3e1add8d662b011824f366dd5148f42e447bfa213478c41dd6f2b0d247fc104f4adaecab44ddc0ec035145d9ee3dc4404d75063923cc746a8d04c9f1a48d989df3e3c475e4ee2e68b72c22f0828e56c99c14f53939b96f178b17f8866f619c4d9053ddc10c6833ff0e6fcd1495c31bdedb438b1e81222e88481799d41b5c9b0eff3824a7502a94e692d9ee6139482aa7fa508e48e6aa20eed4c952fc0ece58efe032c12921bf59d1f6cf40d84d8c5ddcf52ec8644217df88431b212b015c450ee622c64cdac37c9ab1b548087848754cbb7c936796571307c48b1535e89fd2e58f8249f93c1c6d931e1b0e8c2e822498a49e0adc3722fa23a249fc0933fbbb881609390deacfec077753c8fab01e021763d945c6f62730dc043a58ea85ab8608bf9652096ca7d3a5c7d00421f0e71a61d61241c2657451436193d273797667cf17dcf255733f88d2e27a6ec8dca5c88a860a5eaf6d5801ecaaf9933a559459fbd8fe685e3b356b4b7599909d13ffcc442410cc4381610cd49150b02346d79b97d0a4f34f7dccd1d377b27f7b7c64a159be28129cd5a279b9226ee4854b079188ca737ba461395f6f243aa80b992c7c56024a1a017d99cb8e8772bf3bdcd07861274b1411705bb0dbc3ab369086f11bdaa1118929603200010a5f76d2bbeccc526321777d251b9f9b7a513f2362596b93b25a4ea2723419e8256f706bc047241087f030a3cd8299a95bbfb7ec5adb4037687aca5c14e88e3a047a8ec3742b367399830bb1c8951bce310139b831d52de51bf215672f53e47cbe208243d49ac13455993b9a420872e8a0a8a1680a44f825b61f759608852b9024abd0345c9d03289ca4b9907b09573422067fc2917b4413443f40b5e90aae5488ccc0febe58836a015e59511bc0e23bb6d44375f83471ba8f146872ef3adc27096f8be34c37939099326dcca450c6eed6cef048f33e064310a946e02cc1a8685fbc9a01292bd9626c2e59846a48859e4e05683daabc65627e2e3c2bd5de471c12e04e945ce3b9e7623a49af816a8d3e622d98b91a3aff59b9a836b490d40c0f95d1a1f30cd16b0a203c9db5806b94437bbb02fc74de9828ee6aff134a6a647ffab5a965795c0846dd98325025bde53ddaf2038e65899da0439e293cfed5cd88b56e779ace5b403e85c5a07371f837606daa6adcf8e73e5698cbb694cd86a941d1a3df83aebfd0746498e7724011305deb256c081610d1ed5d071de90c1937931c673875036d4248b8aec2c1abb09b4a5a321c7256fd7d8570036debb9f1359887204abda9d853927ca300bce3262038d976a4a33f486e95bc1f82992e31fe5dd700605d626262d4ab013028b12a3954db650891184deea9eb0a28bd4f5be3ad88a0d211c37ab5d29aefca456e2758159fff9dc193d36a9fadd7519620c3bf3c7e0ab9d589daabd80741153ab415d323386a0aa4b481af08b302034731b545fef51607630367155a2e7cfb4fb7d096808c98b526c3e0cd7f27125cd38bc24ef48d0be532f4058acf2e9c2d2e4cb4439aa08300203aa2ef48b9c4ffc7e5475d03fe21a68684fdc650aa916de7d851babd9999310409e0f15cedbaec864bf8cc4f2afd25785dbf7aca062f02e8433311786d75be35346f19cb1c1c41627f6286252dd16cffcd44b3f05b0b6f7aca3fd7ef3b8371861383b7efd05821484c0cce6f4e7be6a8b497fbd65c9eb485b1a3bcad7092505292a9f9c38f4a6f0e7a88f6edfd38916c00d52a2fedaa931c01a4fd3f7f86d958cd8301c1fb093d65322778799eb28108c4903a6b4273942348630205ba474640a949749cecf3b24835c41fd0a93da229bddf7d0bab8873c35558dbe7e642743f63512489615c9f72ba825bbcb9d7ea586cdc5dbf1a6e072c90080a7a820509af1f72e0dedb949c37de849caa18238e9e2967f57d971b5fe2b4be2452c6fc89b2339fd10f6c8d21ce49002598e9cdfaabe475f9347292187c8460e41841f4cae873abfbc85c02d6b3085a34d9f7286fe7ee243773eb6cbbb6ded2d0cc84c12b473f8afdb50c2df47b53f0795ec282fbc0eb9c785a4863ed7f23cc1ae18742447cb2b71ef3cac9fff1ef6b48918fabcff4e434a6220cac3f2031846a05895a97ce3c4c63672e3f00453ad5a8b467dc8f7eff1b1757155e414b960e87979e300fe4780ab04b86ba42d07840c32e0a520409870507b4f4fa93da0f5236ee49c2952838520b6933fbe5501a59e07761d0717e6e00f0457f7bb0efac00e3c7980f61371ca5ceb0136d207ffc289bffde1994b3f673676a2a991fafffdfbbb2e864a7c4870ca70780bf7a6b3f755516aa0f0adc291f955bd38a6d88a49b6c6a42ed3dc390c31d5d9804708b02dc6e35f0c1cb57da48088dd8e91596e49344efd4969604ac59a4312245668d9ac503d4d521423b56972c710c7b88232e5525418f37c2719d943cd7bfc69cc81bf7c2dcba165dcf83388c33ef15f5d08a048780391627878c714211b71b53176404f2cff43b04ea6c3723240b5697dbfe0cc9a3fac6f67bc8dc97af0d35eefb68f6b93c0fd5e25e1843c609c1a9c7ca78b5c161ce4df6d49edba7797094b489e9bd80d37eb20a461bdc09da8594109cd5d5460aaf5bc8f61a08565f535f0bca958c002a57ded9534d71d10f2f49980fc2728a51312b0008927c581a687f6cb7d0e3cb3ccb033032ff33199d8bf0a20de616e851034ccfd51f8405bc603fb5c43bcd7d68834d3843577e55d07773d4583440b019f37cae21d2561224bd92f7a35cf481daf3527a0ce4379c45530a8208212fee940ab9c95d6032f5a8c0d2867f4320d48a2833fbbf3476730c96c989c0128100c01776d9dc7a4c47a37ab04c1c9c36042abdf9238de87089135fc7c3e609896cc036a556cad9f74454548b87461ae584772b7b57138f07a22d15c19758d9d2909dced1bd4bd58cfba01b2d7374d4c1104dca17d1ae16fa6f871d5dcfefe7707e05be8bfbb278567becf1b544f73b56c44d2f542342c408f32fd7c609455e3c998e8cef10efe13acfbab899d3b8b1b99ac88913eb50e3b60372bb26eb2c040e175abbc904971ede858f5aeba798365028ce74c3a7a22e552428055db2dbeb883ee37f4a04314c37c6d4c0741c82aa28694cc0849c0adf0eb8cdd9a322ec53a92a265f73db1dfab00f66f87bd5d82858a02e7b232ef6c33000eee45780985c958de9e4979ac253b60d7963927a8def81c92e5d47cdc459ea596c6141c821ba8a5e22996f12b3299ab799dc72083b4f78ef8e0cca79d8826c1a781028585f0b155a2a7ea8cb8329ea259c5737f7593b1fe4d9a96dfe07f59654f5d130c343ea53a38a9a9301f3cc596f853796915a0af109e4287b7080e8b37f085ac39a512d4f494507adf258bb9f1f7a3a5d4befecc96f8fdca736b0bcdfe406b4ae19c43de01c097be8f4cbfc5f04992452c06272ad1e500bc63d593ba02e32fa4cdfff4a830dd6e88fe326487c3704c51f42417961f6b411585177ae0f9e7be13c7121e3cffdb973c3f1d3c6dc559dfbe16b2dabbc9c34b5c3361d098c6daad4cd1154ef0225e2743df24f09aa8ac4f2f5a55c95955f64d40951d72163d96b200374a4c75fc8a41c4a71bc20a1751259d95313d1693858192cbc6f9c26de0e544376a45e2c64b5bdf88b16ba374b8025aff1bb2c7572ba030bc2f771ab3bfb226d8537b88365074663ffdfc5157b83cdc3f27978d02a2a16200d26e8c83151fff465845425fe8dd86b8c7773d600ef2ce50f6c75f0badb28f4912a21769b619df0d460f8bd3cb9e536a6569705d838e6da6921b0506c0aa65d5982613ccc04b104cd388d06af82932414a7081be45b070f18c2055d80ed69f5151494eb72a7e66db3f1660791bf5da4fd4938466a26d83aea3535a43d6ab30410d0cc8f0992441b10306f0bafcf4599ee9b7804bdb1e4f6ba2404810484226600775f4bc9d8370e46876ffd20078f1b242231367a04d3404d92ecdecc2eb643190237bab89fe533f293bc62c7c685f4dad40101cb784a8df534068b3da274458cb0bb1e38d678b4061e369a50d4e47629c404240064105d96882c000578d60a003c76fb2b6f63a86e9ee405650c0f4b833db812154914550015689d0988300edf7f75add8ff6b59bfcd087ff10f9a72d0046b6abcf300ed2795e7e9f727987aa65f343b9be49ff7c13d752fbacbb84a1386acd1ba032e4c2e620f6524a95235b0e84a40cf68f2e1a4acb04cb7c1124b67bcc470e344089099e377074af8bce7a6b7b3c68a5761df7c1a91003996013d1be219ae26bdcbf79836a44136e376e5313d7195f8d78f50eaec287b5e84dbc3065920d55c2387f9f7beeccef202b3eb8980c22feb61ab3ecd09360afaa1e26441f50617c5750b1226ffb140f4bd090bc1bde405d54886540993af696fe80583de1be557a64cd2838575a99a6ceeddb559bd7b932354717830c821d9484d510f3e2f9ae9ab8abda4a468c94f37072235ec12b118274928ba03358d97ca80bc238fc3530afc7e1af805362722dab86b23ed5ca022c27e44aed81d31ef1bcbb400c7a99b8300d395f442b3f77f16377e58c23a250eb22d2fe26af86f5e092857cff21438f3df3617c870e81742a20e031fb21eb438d8270b8e7852e87511d52b40a7d54c8e31ffebcff6a90ea261297f13f240e6fe885cae1ffa29810638f99b668ce9a685e13c814800dc7357e97399c546813c04cae2ffcc315e531e597013634426c7344d2dd748868d617e6153c3999408c46792e3f12781642c09c3cf2a59a0cde911fed8540c4e97d58de79c3d243925591e738ace66803e0bdb1fcaf6b00501af1449601587e37d12e5df09a33f7b8aaa79e948ef0f148bb8661fd7b1a6b5ebbe03600c5c63933ffb85fb11c11a99daff88dee9f151cbf1284c9197ef55068011c5cbfd2af3585da0410aac4f21d41ce2d0e3ffdc86f4881fee241757f067919faffa27d1c6e146a973c35368798c9fdd64ecc506cdfb96066fe80efc0307f30156845354474b89f41aaefa3e03a4631ffad5358ea5d7f1f8e52b76f4947b2f803a7742acd202ac7c140a9bda15e2a8d565cfa74cd57857ec090b9ffd415abe6943abf93e117a63f126b99bbf0b421dfde6bf316bce2beee03ff9e03d0492b19ce8de7f4f9608673eabc7e580b10de4df6c7198d4fa9d7d1730388cfc149527233347890e039d961f888959e7794eba56e106022a6f4095ff2f9cc8233cef8a51dd81637bebdc83945e70992f1ac1b2a46705f0fa22652a8e69b4a65276ec41416584602aa0ac41cc8423a5f19038ad67463c8c9bb1471aac683f062e92817b24c569b42187a8d06e34fddb527f1ca085f204ac7df615756f3af52b1019ffe330f44aaa80532198e4f91bc43bb3c3f530844ec5c42388c899c410c7e08823c74944da36320d9b56b924da3cc139c02d944e91189b7d71c0365becb9ede664cc0ce49d5062aa7576f0360d962444747317ef980db89f77b66d6742bc80083a0acc3601979d21f8b67406b099350147536908c7c797db8972a6c75f1a17399200a4a8d2d089301292f46fa983a2523b17fc71a23d90b38f9059ed90b8c1fa54a71cd59001e77ddbe27639434167b5a66166ce11308a9291583758ce1beb877a652848ba291563521ac72348ca29064644faf4212c7e6d0aa718efe2012717e62c8b54671c74dfb8add90320123bdb50901f0eaba445cc7c95df300ac6aa64cdcfb5bd21b2bd1e6dfef48161d9b039e0971aa61e2883a92e83323c18e2923a7128ffd4763caa2fc0beb1bf64272d1fef248eef8d6aca165e1150b1b3da23e4b83de2492b0141420b4a26f1cd1958dfcfc0f8d7315f569e9ee8651f6e6277592bee0adbb7cd1c3dad7f468ff745952b368e17832c22405f0b0d4473a505d0cfb62f6b5f376fe994de8d8eb64299787d0ca80be8c6ddded1f0d76df598065e1b8cbe371928ca3e551a60d5478d5caece54f834440e11b121ae07f6106bc32bf948bf1a2516b0ccb837b7f6405eb8e597ee31a0248b9230da68b6a57f92b303eb857ae41f6c06d6c176d931630b305f068b975017647ba6306706d6c1f4aa98d32679f09a661acdc1ec60609c8caadeeab00dd8c9197ce879e48ba6c05ca3e9c7c8717ce99af107312316e8e8fea6ce61e6c10e0de0dd38e5dabfdf10ffde7a9494dd03effac68d9e40fa9eeb0cd410c3186d126fcda4f77c95a26c55182a365494051a0c7a46461c93331905cf1c851c51ac541997092554fd93e4883ca29111c775a63323293488e1703b15e3a7721b758555769b108ee204f64a94d95672c930b4f22a8299113cb23501b8a27b72c0f9ef2053d88c3c6e418c1eb4378f200efae682dba25827eb2521a01e9b412de35524a1fb1b5393421a19cc26b72515521d2e7ac72e9a42fa477163385703a64a380284a5eae7ac0333c978df5cbf23c07b0aa21ac035db763224a5d5a1b6d8f392287f82b71667f49b6b948b4d80139088f1b96a8595f1dda5baf13612a7f4f58fdd31506781468e9a7256491d08e08f67e2480d19b99e92520c5081df67eb53d7cb4afd4e1011af3420eabc1efdf1e6ef6ffb1161fe2b523887729840149d27dec84711c5542cf530c9c792331353bf046145ee41591adb4cebfc197b55cad207ee123bf01f928e31d2f063ecac733dc84341b0a5d104edbee9b20de985067d098a74cd02202c7ef0b37f66a61f029ac182f94f3c69f0687028c43a9704c216d82822b4de17af5a19e9937417c98497635c840002b0471a0a32b56910183c1168ce5ca30f58ae335c9384ad07238e41ee70824cbc8dccf1dbb7389b86fa720bedb35c874de6b55b7486e798653d3dc394d3ab2a6546b1e0a03256cc9b80aaa79b63f13e7add14310f777232c442c57c442ce546d449f627855bfdf2b9998e15a5b28257c04c616fa8238caa610027c5d889d2ce3d55644d06f12d9f799c7d4246e4f5e10ff41d5c7235106e993ca0140da254b036ca422a3b2494364643b0b3141fa9caa30f77068b3e4899d3b90876fa5bbafdaab07968b36ddcadc961017e1a37031031c0e35ce1e26630c54a9dbeab97dfe0cea9b334cd834d5be1eb0e2606244cace2ffea67bbc3b86f8c8894553b52a86b234155deb24ed2d82ff296878f26e0e35103e1b9ad0b072f39a97ba425118aa5ba5a9fa0775cd96de29323a5baeca4dcd320fba952f84a5fd0417385ca28f8a01edfdaf250a07cfdb9508cea29bbedf26512cdf41a12392a8e461ee36fe05a3485c635cd0d40c4955242a23c7ee02b40b4d7fa14fcbcfe2f566f5981c29c458b94adcb7d513c66e2e4be5e124f04e263d6dd6aa7ddc7b61cecbb9eafadba8aff0e8be3efa82f5063ecf2b861de814941237db554f5545979900cdb2633cdeb08bc4358e04e8494d11bd4a7ef3fa6317529923e991c4bc80cace4fbb1d376e170da9f46930062986ed050adafc2ead92c96b281bd7a834e3f827e7f521c4ba88f12c5d26bf368c748bd00a31527416ff02929ba3b1468215813e5c56e67565ce8f5c792b5aa09d1446fa66f58238b740a30e26c701d91e056c08283cce61db4381a0b1e60469ed7d00b65ea22dcc027afebffe8746f9076ec959e38b1f2818a3b1e3c5ea9fb4bc370d15b5976f002c88232c20f3adbd272b604da91722e668d5636d57e5964f4792b12378b0d670b06fec4b72c83b99d3159f3062d38e6d834cd3fa09b8b688dddbfc24d926cd08aaace802859631c43118972e16b708b02f4397b8501333b8374d07b6175622f3798e32e3b70583f51930e3498e7529292026a419acc60a8d8e17bd6eb1e90a6089580e6f4d4371f796749193112e40a10b8df3027e233dd01a4b66b0c572e7be9b797f8d65e7f877d8a688d157de3f36d85a29e19cc64986ebdbcb55008eb379d176d757bf528270c0f6b41e9edaeca341b7e68a0f74ae0e05ce502af5c275a56d5cc5fb8b77cdc87aa4b61c586a985fddb29e0455453143e934363d9fab03a74a80bd31f09f04b37960fa05ee8bc00df1cdf2c40fffcd5812d438dabb2956b4b53851298f333d9ce99aed2ea2f262405b6473b0510d5e4fce875d01a074501daa94c57b91828ea22b4589734654ec46097f053f2bceddee82e278ef26b28a86391ffb9bd5750842a00d51b6d00408a8c850ace1357e42727ef345a66a65198db9fc25382192387caa0912c097ae923e4642c4e6af56459f017ba28708587585c7786cd2e504d7e1a8f925e28032992426c8d47514d0bc07334b64ac929d19fda5aa3617f1fa55422e772b47b234525767a0d26187033e38f96187b87b7446700f3ab69fb70197c2c36b65d86e87e491c951ad777968685c155194bac29cd67d01725d63bfda30f9c69ee2b05432d54aab1f0ed0c61b2676ccd117035ac19c99ad028aabc5920fb6f3d223a1dc1ee6edf1bfd99745c4291ceaf028ab56e183c04064c56a3edbd2dc607c6fc70bed93c4963c1374762847892dc3999434e4b7049dabcacf09beb670c36d09076e6b66496237b9caf248e72556007cbaadc408bac478814e799ed7cb285f849f9d2e7c7e75415df2e5f42465d2b294f7d32671872a2c21c1f4e502057e73d56319c41fa6eb7f6905b38853e3458282897e15e989461722c9ef35f7182be46d04f8f8a9b72513e71d684412bc591e673c08ebd5346228a140641453348d3182278e7cc65392f65405b96364c2b83cf998e25d1512e239e1a7e23abea869217ec0560f1869f1ae2a27b1786fdd511f1bbe633882542f2959b17a1e031bc00bbfc4500035324f62987b05ddccab55dce6ca37e2342a6c97a3d2bffb3f58d70011bc731480110f9ac379eefa500609f59ccbbc195d917a20a48c83ee2878fb8c2cf7a3c769d6495e030841538777972f57b90831b3c33bcfc2b6ab22038618d90e318d7c01b673188aa5320a8a7719635f5609f7b0b626426d88ee2d49f326850094a875a199ef345d78239ce12aadcc5079dd8a2a0d7a1f1bbdfbb0d84828e2904be96a396375de4a58170a03a5fa6bf4c11b4fc6bc99e8208ae96273b5b58bdf5c977e6d833f1aeb6866e3b1c923831339f76e673eb9a1dd8a36b2089201ad8c4cbcae92232474673165efd71f534250f3474c980d93ee909393b8513bb17eca849fe41a0f153cd62856f5de9a2d98c49423481809b464f6681d39925e9372ab55afb11acc981055f272f8c0eb96e3d89b1fb302c0616970e0abd58bfc974b98692f5ca65bf64be4c082b3ac19f8bf9f87e3f08983d38af6c7597cba8865b21d68ff30d97c34c9c8e957a7e9df6b8bbb24eb764b9b452033aa330ae20dce07882c5ab53f0e5e8a0807fa97162e9965b25460c734f2c9f89fea5df8a84e53fd80d1f8328add271227328f8c5a63f6882b2e1ed17113f3a6513dcceededc2e9a11df1c4170ff9fd4be0cf0e99e18f08cb86421d2927c7646d8fe5d2fd396a8c070556288d669b1e1b51e428c56f5820579f0e8826210422035239a080a1a7fe893e9165ac07fcadb8fe6f96bf6064243061f0a8a8822d33cbee804f313915f4903874032815a2d49d8d2a6e35a01d72687cda576c9b8ed5b190d8be83c66e2dc187bc523f64041e93313a5c76e16fb4d8f4291e33a15c626a2cc719c6cc8f407ec9dcb9de7f7603f84f9414a3af2d481cb130ca41d3810b530eec52dc5220e2631c231330903b987a58333cab9fd48186efe058fbac2e327a3b092308e9878d6778cfa8d315834729a3d4fc4f93579c7a80b80234c5b57d16ff568f2ce3eaa1f6bf8aa53a3912984f9339d77002a3af52ff2c8e16ca0b7609fb5f8d566b8aca477c51866630f0a66e4226e6730d1a5b530638abdf3398513a219edbc19ccb5d08cc3f9addb9a37927e9d6120c1ff34d97b8da4985923405b5706dabe87080dd421cace11508e9f85be451ac5a07663c18197ff25d46400cfaea22a3cfaa61dca7bd48e50071cebc8df346f94eeb2941e7c4a99eaf1413160ab49de76c8f41685ec34b2b85694e03d6893f4ab0bf8952306041d7414ba52288220855b1a17eea810fbcc9cf9e32fa51231661b798926c2746d4c6a6941d8164df386601b3a615fe30ab6384859412afef5c3b319f8f3731e6551c7f5a371396f6038b4b554881cfb3eb8cc3b4c32e72aaeb6f1f1cffda861df90417612933c1b6a1fb8072d83b8cf64d6ab61395805392ba4111cdbee60b591dab0a0fe637af4017fe005331c861343c9b03ea749e15bcab7a79d2fd800a73726fca4d0042020bd35e01b097199b0810f8ea5ef28fc7c15a6b6daf521e612bb8f52963dc240ac60f94cfcfa269d887e2dfae7b1697b2e594b18a6cc31c8ef75f3f3361fb2eaee8e2df6af8fb0e67fcf4c867e51a47d72d6043c38dbd28f9ab520986d93fa8a16af7cb548309c55e237195f1221cfbb70229c87534112d98f060651b09b5c70bab5dbc1621a1cf49904590081715412cac94363db6a0e19c0d67269ee5c3580fb7f0016548d2c5ef7309cf28e7b3fdcd7a54cf448d0070a4010f590815c846c50b02a07e006fa40fed32d680ef295b40898d899f4534a9afc1889a87cd94a59ec6c48defef385eda0cd850a6398c13d8a8a3cff1d4f414a5afaa31efdaafd27072f9f456f9f1102830932522f5f992c8925cf4763ad00a042905a377a21fe01503f30a87aad66c55a542eeac275eb23a355acadab3ee02b022ec0937a3d062854a26b57fd65b6b9a7935d4910865b7e09c9a81b729614555a1f97d8fe82981646563a39e6f9f479a898938411bd861bc5ccdd19a60ec8c6859235da5528e5361c20818446a3c4f2176c219669721641c22db50a0f6883b5d82d33f0ef4c73d96d7562e6e022af81a15aea512fb442c5140903b7e24864c8f6554bcca8df495b04accce70cdbe3dc6b025e2152071f03795876d1032040aa3a9da202f92f18af9ecd7986f0bac82027ada1f36483a9dc6fbb52dc2e4c4334af01d247e28b136bbf2b07e19ad315ca9d7eec4c07443335b8b10a60ff58b70f8de156e8464eef41244fabc00bd296a8f8f0745df93a7f3370bb407c1a9bae4d2e32b3c75e1afb5be2ab6d3e7c6c3a0dba0ec0dbe1f7706139d308563c641eac5b7db902692292def26fe1380c63e566322fbf65ae22906223a0c390b03518043e943c46e209b836dcb7edd50292a398251976b96414f731c584624043d116a7f8075c9e4791df164266e9b6492e58564803b215b207e7f3a374c103fd867ec654941ab4136f886672906a0c61fdf7f576858d20303dd7e014ad60d576565c41c7ac9b7335bb42c2e3d4fbcbcaf349c053ddae0367570625e4839fba2174095b0c968b6ffa1e84f32f0fdac4d788115fc2e1b228ffb4fc676521f09d2a810d8366e97aeb04c02779e94c0bb920849a3d9bb06d47615885084eade4677bf7e033e4f0be5f5c14e3d3c28d236071cc3ce8d343826c38305432455eb2129d98b3e6044678d3b5c3f40a5e0a03c52ff2b206b3365dc9b8196b18660c71936c606cb99efa3da9363027867ba5738f7afedea2ec4b0c9f060102bc965a0fbd31efe643954629ed127d9c2f56e014718b9599e718fead709a3f458723064797d16ae9442f4706d83b7ca4443690b2ab2b9fcf6b047812e32803db2b157b4fb6f6106483b8229689d48624d179df3890aa1d5c8d6483d3ffae285e4358e03c8d70160f2f0b4aa255ae68f2b82c1c61746c6fb83af6a1b5e440e2e8588c89e3fd54b5e245339256b5d4f35623319491cb386e9d16993c062cc221898c890fc8b6d454805fff18ecb26eb17c0d0bf0ead937da0867b125781a299ec8ccb258027b38f72bf8ceeb678c3e99040642c28e9658e77bf668a06de491c2a4319281d881bbd944faf1ecca7fd9591a18ee05c4a1b86d2e0dba8da107387bee066275a64bc15ebd20b4db1aa9710cd4cd108c318a71ff313527594f26638f9c6b631ab5eedfa9da00d267f34040c1e1d8b26599f26235d7d126c73316d0107beea2313041bf4b3f588ccbf91bf8fadb67cfd6e6f3017c8bed19aa0df40f24613d825d4a481e4e6dbd6f48ba9257bb6f3f215978506115c7213602a89be197a3608c680b8cd9114e0f1eb25433dcd4a1f4daec3cd39a83f62edaee450cbe1284b003172e9e2e1d7ad854be60b6865f4a8335d70381196739ad15105f2106467c1212237a77b49a9805d1b6ddb0f6d2fe9ce621a985085c71df3b4357dba226daa480402d23d4028c0b48581c7c1da45807d205067d3eb100bff2dd26e10fee1b04966411174f0f6e0d8d3be376d27b913228c2b6aec48e7cc210e4b0ae35f9a429de563de2a371fb93a4f36824a437045cafc52532728971ec5bb461c281e87cb8f56dc3e2079129624d69ae525ab9c3953ee7c56422d6645df32a4b562bf1c84da68d61ef3f772c7ac71860af2d15f13b160c39069c0164f403ca58bfaa59200ebb4a070d854c341707bf3a0476b5796faf4c957e46bd2c81b55a7348281486808004fb94e489a36ac8ea073317737060407001e4c75a64ce15b2643d8560ad8cc6d7c1d3ba7d7f8a0917ae6d92ecbdf7de72cb2d654a29f40a7f0ac00a2015f087cb9fb242e5f25beb71387a96cb6f73e566dedc48ef1b8f4aa3262a8d37712228252f5f94bc302d2135947ed251a0f493a6d246ee74e6f2cb27a7345045f87dec71f9facc91bdd049a9f4a6aee7efdaa59c33cba836354d7a6b33a354d36addb8c971d29bd3eab6715cd779dffc3ee9fd719de77d1f08a6ecb4567adb0f4ca5acb5b1b9c1993838d21bc77a6de3f5cd8dd738385ee7e8dc98376e48ef1b3a1e73385656a93cc681c3e30c5673b592deab71de9837bc0e31f880f4ad18781daa3e202aaf431c1f90be3683994106d23b830aae846c2daa2ab812526fbf07ae2f398bdf86e715eee1f73e40131db7cb99a5681256fb80b4284ed66a057d098ab3b8dbbddb5dca39b38ca5797487caba1cd7759e67c1f03eefb35eed3ceff3e814ebff7d3f3611f4c0efe7c60bebe29893ca49a97c80983b3936366de3efd7c591021e9d62476b7fde6cb1ac29964eb1f23b1b9b023fafb399d2c6c6c6c69b6f80e3d055a5ae8ba305eefc6ecf91f2fbc9e8a4d2296dce001c872e3707f2b9675e7fff9204c08cabe41bd25d173fedba3802b95344ba19a7859d53497675d2b0d2c515dfbe610cc1658730a0d8e88188024e62928a5480820a48806b74d658777216eb185df247fe4cc7ece12fe6cfc419faf838ab7538ab878fb35a8a0be01e8eb253c7ad97e7f5d15f87b37a38ab75dcf67156ff95610f7f4d1d3efef267e90be01ef6f1bb2e1ef1ae55715541c73cfccc8c81aeeb666662ffb6b06861e207326882f8828408081f52e045882dd488f55bd5e46f76efb68bec2c74df6ffb55fddc5f3980e38965e9d275d670771108265d762872820c199270c01145325c41c512359c11ebe7da51fe61769977f0ab9f99d9ddfd9bba39e79cd9f7cda6db40d92b52f07084093f7881e202505030e60a28b0b800c40a9888410a1a4660e840868816581441b4469a24317d458e6b8575588e58e7b83edc63a208ca042e4e986082bb9cf054a4a4e54c132c5c49b2e79cb22b7bfd9eb31c88dfeccfb0e3bc4164bdd79cdebce3bc9e4468beea9c72877b5aaef866e3bce211ef0edda1ebc35d82f1d96d7ecd9ffee26ac8cdae0f7f410c931ccad860440ace380ad3c40a5a90c1840f42c4867a70e10ba32d36ac31861633361821260b2753d2f0428cdbf7f6edaeafdfe61dfcce6de3efd91523a0cc602106349e865290429636722843ca1a137e8cb37347040f4bbab8029acf80dbba50782882892f653091c5286a0a1dc6b0c265053e1c81011563c498018a2e3448f1812515144da9c2044669486f2e64b3381ef158e4b9f1c4f6f77c57cd1c99f8b77fea44d9912747f338cba794f31ef1ee5013261d6848c114470c710293c1e54b1334184d41c529d6cf551e75ccd3fc95c753c2fe2500171b7a78e2046c6c299259a28e30a3ccd30aca88f51366cafc40f4a4030b530c4921a20541b46102349cd6b81acaf59c55a3d8e7e7b7ab7e8eb9aa6c15f112a60127317090018c333ea010630e54e1850d297a688309333166772539ce923b3b3b3b3b3b3a5c4546002222e4afd155640420222ebbe30f00867db0b4b37377549d976559662793e1eebc23abf117da31bae38ed1d5806a48c48b52c12c1da151810b2840c164e8072e556ce9a10a26c258112b0265863154ce6001130f212843430c4b616c31521303025e1781b8a705bbc9dbf137ba3f7d977559d52c32021091cd4866f5b6d0318f55a9542a22413749e49092994408f2978a7be45315ef002f1167c917410c52c18e2ea65aad6dd7e59efbcd05cb3df72e56eefa11d7a53dfe3bceca4256b97c0471f964485d96b97c7cf3dbb9c746b297afc9cf5ebecc6ec636d8be3f0427bb1d7f43b22eab95523a660f821ebaf23329ab6ca227662d3d16ace4ff0d57aeebf5f029b0fc81d274fdbb2ed500cb496b7cb9f3adacd2325b9674b9ac7cc25048fce57da595fa4a6ba962f3465625556f55b55ad6ed9c6f177cb99ce48222035c4e5a43cced2b1276e33de77daca3f56f2309bb21ead48a63add18f6fc8f7f5fb7bf99d5c795f7f3524f5fd3d849deeacd55a07571fd8fccd8760f33722cb10703e4764d9dafcedcf1f5d362f5bfd40d265b77d29012b66f6ed77e10afcbedd77a970e589ab0f6edee62b7023da88df5780656b7b96adf1db7bfadf77d2b32a30565e12a6f6d21bebddc223ae3b5f86556cc4bee22d7f9ff5b284d5987fed2bcef2f77f7f96bf557553cabf2d9dff0d971ccfdafefd257db80a8cd5eee8d5a7a111edeb775a3836905e732fc73d10be2f8eda8360e866af65997596ffe0f2e11fc4dfe5ef8345d7b6efc8606ebf07bca36f7f777b27e9eb1961fb880e4e3441a191018d18b13640165b9068a38931a06c89757b0d1df3743e495811fc256d22d04395ed3751255bf23b1ede01def75726e517912df9dcfb11d735a28dae1e96bef6fdfd707533d8be54ca1d67491e062f0ff7c8ef54602ce79f04d8e9665f447eadf2816e75ad2ab0bd2756807b962d1f81ae87e35f1edee12f7f03865d805bf2b71fe075b1480bb684a3a0eeb92fc1fbed4be89efbcd055bc251fd12b6f7be04eebbefb7b923cf95bf12a2bdbf8be3caf2cdf1a2d80c9861060ca71bc2e8c2892464f8d0822acc302d114505c6da957f08db7b1f02f7ddcac50fb6f73e687befab18c47df7fea240d788ffe8f242962d1f59ab943e2a3076de91c79fba1f716de10cab6caf92b04df4a957256135267d9c25a5b07f479f2b1f89fc25f28bc81f413e11756f86b6f62bbff8f592dfed815735856dde015ee76f889db307fb77fceb7f5d129643cc1047c860c50e658089f9fb48581024d4b060ca17596a7012f37f091bc2b266892a68b688716223e663a0c0e4841067ac60068a983ff3380d5b144a98c9628d14b4e00aae05556650a20b169e28c302cf0a1e96ce4c69e2a40b00a0008616393889a1864919287e60618736d04065e113830926a0619283172a8491c60b60b4f8e084082c47b1dec2883164c658628c1250b019629c008523c0c0e26486a1262e1091260a2598a26078ea98877ac837c9103944def92e92903d091cf42b515d1edcd343e49a350c6056cf24728d1a09f41a2992c000eee99720f02b9b279844de4cf4e19efeeccacfaee46c22e0431467f1e59bddd1e7f6b74f8f97301e1386090f6984185383090358c1531037a031c40a44c4faad84752b50b106982d699c9888e150912445114614356d8921258101e572d2175f2e079793be88728bf8cbbf896ef703054120c88eb3da593e3d46289201671571560674ceb047fc357253910c38ac89624448b016c15f3e1256631fd4111647acc0888828d6cc4caa7641c9263ae619dd480b3328db2aeefe406ed36aed544dd9a46a4adcf1d7cdf33f8fcf73009ef983f82b7cfe0efca5c1f303f92be7f983fce53d3f117f89cf2f824753cfcf92b00d9ec3adb590e3d0749d7b7cb9d0af0e1b3d314ad9017c5f85005f85f55d7d2ed4fec32a03f81a5611c07b6115d6876115027c0004f022eb3520806c5107c7125c6dbb1f1bf57ec13df4e31efa3e9cae61854f53bbfce57c614e8e1a04089bbc8fe2ac61cf2cfda080a652a91450d052e63e3d7cb22c48074a3e7d40006983b0493ff90236789eb049d1c73610fb8cb7e8a3f031f6058c3937a5a552a954910c34655e6ade8ecaef27c5cac1aaf4fb49111121ccb66da9d4a73efb7e522e1dae0184ede42d2b4ad169244aa0a8ec543baaec1be1c8d3b681cff3365f37759e4e1335a1e057bf9be695765db64488a3346ddbc4310417ac5ae77dd3e6584debbaf013b51dd0b09c94a6e87217276a6edfae6ab3edf7d9f7f37511edc05abbad829be7d5ba6d75dbb6ad82bf816037659639a87da236e57a11efe0e8a7756f5b1269d39dfa40ae6dba33ef0b37ea7d9e5f1fdef9f06d7b0eb53970b7ccac82fe6e7cb0fdfc364eb6eb9a43d2d24eaf1471b38596b12aba8375d1dbaa46bd4e7ab6724f96514dab35b56dfc1cc762eea647330d1c876ed3ef07070d7664a6cbdf2129996f7370dc603baebc794feed16a6a9333939f4d97618a73eabc0fb4a954aa39701cf27efc56aedab6d12d9b72dbbc3f1014c710dcd651d3fdf4395dfacde6dfd031cfb85db9f5b78cbf0d880fefb8fbf17dabcf3bbee7e781f9196f5577f25625c33df54701f0f02f9803eacf63ebf2955b3f64d89cc2ad9afa8e16a19424a6c915ad89a3d4fd82790d3ee3d669e66e516961a978c2eb32d622b3af3fad4818fd3a97a4d29dd4085b7f36f9ab6ffd398679f0ad3fbdb8b5d61647952afb7eb2ad05ab8942475414da8088231788629a58b52525a218c74e906bd6c48efcc7ba794b3ff569c774c255107d9dd711851680438c8a9ffd28c3dcfa43312d5c05694f9faee48f52e9d6cfc4512e09b509842062232654026f866d0b19b123ee87621eeb187317169eb022265482ae85d891cd97808aa12576c4fd68ef0270888daa5badd8ac89bfbcafa03771abbeaff9b9e5d67162e9e1d69f4ef6d6a9464e2f7532b9c490e19c1f7d8c9b1f1dcdadce0619de64a7f44c1c55ef503829374516e7475f923937a23789dedf0f10cbd60ae7478954add8ecebd730bc83bddcfa338cbfc0af3f99e61486c9a86da630b3f854e3302039c5b88fd9935bc7ccc9ad63d6e4d699e6d67966642f3e9170f6f9e5d6efb250ca2809935f30d4ad3fce14a4995b65148ef881c7a82891b210bc3a64ec28a374c4d85122dd5a5f22f10e2e0c7389a344b2e268a9b855d4e25697225bf5a1b4401c2b2de2bbfd11d17e5a91adfafc85407f4a912d1d30ac3779930a09eb1d2dc28e538a07ddcff79bc7097338abbe874980f06539abbe904ea444d8fe29d6f725d605c51ac559f5eb07c4131d4ad8dd6ef3ba90bb53e91639cb9ddc63c2d6e7cad8d1752bd04d4842d747c23bb6af0f24eb575bbfdbae43c97e936263e5d62b24f494c23dd59bbc55bf566b55f5bf88fee4acfa9de84dceaabfe90cf1e42feebebe3b75d9f7d36d73eb365147089b89cc3dd994c2adfa5ba731d92430acdcfaeee4afefebfb99e6e15f1ffc2f742767d5ff446fe29efa43d8714a19bde9d69f526a6f534ae67455bc438584edad8a46b62b9f6b7042d7bd03bc4c7f7ef77cfa73663658a942c2f6f38f16c7bfad4da06f159dfbfa4f5b497627fd2addb38475227babc76a650d1df3b49c1ef36bcea7f5ce9ffcd2c4d5f6dabbee14579b9824c98fec6affc3ffceecfb99dc4b5693e5629128f197fcea2c96b3b2f9fd645f07435d85cbe12c774e646f217196bf13abe23aa904090d05090d31b7ce1aebaa1ebb010058e7fae7f08eee905bfcad8a80cb0cb8cccc2fdf23c2720008219c86d0410a13acd0019834a288a2018da215c498e77c17bf0ffff3b37829882e8316e4bb7c9ee5ecae3d6114d5c4dd356666ae4aae11fe239ec71f106e0e467aeb53edc7a714c64e40b174f8e8a032bd8f17a525ea5af5ac1a4e6ebf4a8da5baa3699a3656165fb23767f5799666a6892861aef633c7daed7dfbeeb5ef38864292f9e682f51e7c9bf74c1c6b4a053ed28f2c5b6bcada4fa51adf63b1474e3ce232325feba40cb30fbc07bf02de836205beafe2c89f1d715d4dd34495670e44c447902ff4eae59c7631a7b8ea1f8a69efdf50242c13572d0235994f7f28963d0d8b844864cb7f8651d7ffcb78a1a3a0f9f4858e82b29f2f7414543f7b4ddc4421966cf9ab64cb9f8a2c5b33f3af4dcf31edc7fa1cd344a5f923eb7a26aea4b8aa40f6da07cdafa2d051502694fd508c63545cb518c459ee2161dd1f89ff12ff22236421cb9694e23eaf482121e1ac7e6666e690382d482a95bfbe2ada6b91304dacb2c85f7d47f79a02ddc1aef8872e8f59d1a53bd83193f223ece7ac90b95295195dfe01506147151fc98caef4b08af69c16c969a9b7725a9cd59994db3f725a6e7f7625d39249c9aca490948238cb87b3fa419519ac46c85f4df4c174be3f4807404a96f8abfbfe2087a964abe96b7235a4bb5cb81ac23df8a068a43a8b4150ac5e379dc5f6e63cfb6b2524f5e0a7721e14c7e92cfba038e6dc9c21e0a7fe26ec409cf71b9cbf1157436edee681dc88ecac1c1c71041f047d6fbebecd0f797f137a0ffa9096bf1f6b82ede12f10fc54d8371506e90b7e90bfc06ff0035aa2a48320a10f6f0579ab5febc167ae4b8aa38bde15fb77ecd1df50fa9ba88ba4e4b8a606d267affd50acd237c1506c0ec5561fd0d73e88ca17a241f33351688a2b176bcc7a39d85792f28235da4041164659c486887848811921b224c184536cc8042f94d9e2c415252992621c00f103104d5411c415505c4bc79e0a96bb9cf0e58e6da5caed1f9b876b45ca652e97bb9c204667cd3bab7f70565f11db8ab3fa690d76fc3b4295544a747e1f92430f3be0738d70473f20dc774fbf1f557807f7fd3bbcc36f7ff3c03b3a7147ec197672b0da2be11de0f71791b020b2d5db8f1a70bd1dd7171473b88739c8d673f9eb0697ff131f0097991395b670d4e08e7f55771c198d3ad6233eccc57e7b69628b0d39643105991362fd4b244cc88421cad042c309515ec4fa8b8c801446e70da5a388fd445bc212751192251df3f72161d55a95ea5721cc12e44fb1869095303f13637fddfbfd3b96301fb2e5efe33efcc4aa56f5d7592070cc47b6fc93ec7b0c4434f1c50b2cb0c812731aa6f80206335c30e386a898bbbfb4dcd352daa71a1d4be9cf4e04e846894662e425207f7155498c264a65ce5cf948fcc54a66a25cf94a263bc06e5d985cb91bd492ef7b207f7d747bf92ba0d4af40e058ea5721dca4fe266ca2b0e807a809544bfe976139063e41c3d10bb05021228addbccd1f05d94f7d4a143a5a7d90fa9b0f4afd8d2874e42fc4b2c5b2957a1b51a8ca96fc94c8922df95654712cf5634d892b7f8ead5cf4215bd243026a0053c4d0850f4cccc01245d10d5ec4a861c6099517beafa1f7fd5eff11ef722010bd711c5b84ebfefe2c85eef61e8e39eed75bfd8dfb8e535d77e77e86494ad071ffd1efe7cb66c646fa73ef0e866e67f5b9a7b5bb5962842ca8a0200a12555061fa20ea09852f6b9ce1c428261fc9922020ffee95ccf75013a269e2c83f24648aabf9435da7e4b6904fc8f6b79558099b281472966424ce923fafffb6f2f7ae406dc08a35a420838b0b3eb080d872020e2d7469810531f940446818e42c2253ecf338edd005140a498459212687ae88b182c60b0c51b82813938f44fe12f959c88ee4b9cd109d13e4f63395828ff8642209f31ef1b93dbfe8d2e4ccdd71b94b932bb74b31ad3a6b2ca53cd8197a5f6fd4d8f188cfbfcb5f46b497af7d36eafc30f78c39b78a23fdf997fad41c777f2b84fdebf2f7f17f7f5697410b96fa7c2cd68eb32c9bef72183771858888c8849808683c39492335c47021a1a1e58eaca446989bdbfc74fbb34939298d283051502e274531f198e3f69aa14bc51590bef43535567adfe228443dfe79c4aba27d95304d54cd5b256cc6faad4c42c240a043992bac68c38631bc88f50791300f629042862d9ec0d2248d58bfcba789a853376baddcc3c373831d5d5787bfa6e7d3aea3f6c840af4e88a185cc9a32606ce8e1c109263544d5e082342ee8ba145387b9fccee4622ebf0ce34c4e4d33a35a9db54aef4ab5bad58debbce979d2dbab1e33d779df123841507a83489cc7ecf107a6bc30cccce28459fe71de76b08b1f1cb83219f344ca1427f0e1288a98db4d517e58ea98a7bb87b3fc5b87b35c477f8ebff8b67875601c34d8295556ecbc2efb25ef88a12bc2aa7edadd5d4a972e59d765ed6e76253a6686751df30e7b97266a722e27a14165877bb8dfd61d156ce39e137776444a67e634ebec6624c8329a4d1e874180bdb097072c89d0052888c803267353189df4727887f6fc0be01df279a9c76e70017059876139f2bbd58fbefd7e9b7bb297dc0e5ef6913022b2e55fabb5619e8854a9ace5f04e0b5cf91d5e39998a3b0ef3e1c9f1196ac366c05f23af21224291a13a99355b8a53868a050e5d544183491042483124c39a2554f0840937b4b0020d2095adc5d47ab90b13a43baab4321db775b1f6f22bc3e66736f5b2c54fc35a67c65ea6933f76bff7339fa71453612c3f773929cd1177acb77938111b779c38f41593067b73b98b0962f0889793bcb861cdbc3eb38adb2dc597fa6b7fc7d4803bf28c3996a4944ccf239f41c01d394d13a63bb6932ceed84fae7c1e3bca954dc50562d810828d24aefc5703c395cf5233c595ef7ac1942bdfc7c70bceb8f283105df9406d185df9448a0c71e58fd086d2958fc4c995bf24094e5ac3c56806ee4274e6caef6282972b5b7646c5de5c864d2f52b444714516221ad2888db60bd4931bd82033861660625c6401f6acd7f3972482260a13d0f090851461c4180d355d9e9e0ca1431719625b8cd14410f91dcc98f434303ced161df38cb2e9caf9d9d3e729ae3ef0ef0f2113d9c5ecfd331a7648bf9fbe4b5e915724928ee6d12f9f25e5952bdfc3ac5640da83c0314dd4be8a26ba644bca7731cda3af7c69c59596240d5d8b235df9b2c95d22d131cfa819ddfadf3fc7aa84ad28f507477b6f9effdae7d10586f67bf0bf1fb3a8261c1c0a459b6e7d5a84f320700c470ca10a8e289ba893e611c65f4d8940ffe8cb7074a25b6fc297ad6ac5e657f48762b2295c511148b638308e88621c3bea1fffda2f225bf5e5832f7414f4bd7ca1a3a0d47fefa2d05190bfad424741e0bb28e40314a528249412e5d2734cfee88a4971459f63489fb8f278c945f9df87003e159138abfe12d9aa552e3194841971610a1cd074a146cc12b1faf249c24430630c2b6552f0031932b1fa72a93e1876912c025fcaa5a286225bf5bfb0b5855d057defffbdd7fab2298bcaa26891746a1e57fcd5b00634418ad59761780aa318c7388bbaf55b8bbf562070ac49954bb2557fb5024a3d081c0b21f55552a26c92adfa52cc184f886efd46a279f4adef45feb2d257248c63f5003f24858114e3d8d00a64949a94c048f9c2ec49b6ea268e19d4adaf03c68e72897e4c33d28632ef54d0d4df8de0ac2e525f7b0f41f0582bc9563fad61fd3043b1ae863464a495fff6433121a5958bab0fe8d70fa25fdf5d0caabf89422f5bf285e4506c28d6b5f703a4922df956d829dac438266bada1e51e4941d001c37293bbd4b274fd253373376b9ae4dadddd4d3d244a96083591c3367b25473979ab53b0939dba2905dde474c73e13d54efdd44d59946d5f9a5f7e9cd399db61f09991e066ef61f60999d74826afe6cd9a04adbc43b4db0c77293f27f3cc85861db3ed3331c7b91f354df4e7b699b9f3dcd6b215cf9869afcd6ce8be125df59757667172add3fb6f10d99ece92fea31c9aed5a7c136378803082034a6d44e1841429baa8428b3394a0550431c41254c0a892451a7e33c6eeec6c5ed871c73fc7ceabccb8a2b47065cc142640a920893066727628d1e48b35463de420048e2776545de7f1a9b386e561bd2375432e6031c30950a470c59a206a165060bc2ac61817b8b8e00c1064d8b8c08e39b029868e2f551ca1050b261aca600105e566c8072f30a421c411485bb86881f252460a231b8ed81883af28638d4d182bbca026892eca90c1640b194b50d1040e53ba582385d8a83a018c262cd8b22685a32315130268262c278049586078160021c2e2a4cb18589a60c8524316224cd8020d259ec034beb6c2f53baacad3a3ea7a17266c28e07217264a17bcdc85899a2b664e3592c052830bc2a862c4155dc5192b727c8a0c4c3c5101c2c9c9f3b225ec9d97256cc6dcdd0b7771a609c76082a0128399145660030b331568a44030634a0e3d8839a18ca3293bb8f1c48e7f5dcaf937470f155f1d3f19a0c0f2f86c89a53a8b4160e274051565b068028d589120971f44c26a2f5d993486962baf648a820a1a92b8d822871762521c8ff0d395520363a594b04dd454db268e55cca1e2583371b4538c4999e3b08ef94b1f7a8db5b99c244697c9458c21be8881025781ed2e278901450ca3a79aaa1da735ceedb756d8b1be8be7fa38ab7538cbc98516be368c526b5ef09a6801cfc8d059f9ba70813b61c9b38285cd84a62e8aa21a86eb52a4dd17346d5764a0562a191c66c898446964548861de3544344ac6889ab2ea2a038357db366682d1fc7eaa4bd6302f344f133e6d28c916d70a8612acfd3d87714ce5ffbc83e7b2b336916fec8428d9e25b6bbdd561b349d3b890c75bd6abd5aacc68a918aca05e8d25c35cdeeaa7edb76dacb0af3ec27373883c2c2b367539890ba32bb9e082c855d822ea4a186ccee5a42d9caa992bbd853bdeeaf750d54f2cf0e15c31b89cc44515775e4e7a02e38eaa4b44d74d79e305dbcf9476925e33278bb9b3c79c734ee9934d4a754c4ae99c3367ba171e0fd9fde7289f4e7166c059d23bc5cc2ca59472642e32ec5a552a7f76e7e9578a6392ebb46dbe7463872b21248097ff0097c5ee39a7bbbf9340c701dc164958b992ecb2b37c74f755773334020279ab8820fbedf217348988827a0631522c1359b684f4cb1d7a495092adfe6e2b21fd3a42588f84799564b7de1ece926c74b265cb9e73ce29e98f926546bfe953d621a564b94a25db480665d87ecdc7a505f1ed8bed8b0cca701e9da339e06f297586b0fd45feca39e94f0dae115a65d8e3d09552ece19f0bdabf9bd997f22c4ead37268ea1db0160a581a45c9dbcc4630e2a2c97018e1d1c9f4a85c10d15007e6addf799dc68e53c1fdcf277a48c0adbd11b18508a73a3f3fda0322727e7b3d6a66c3490329433a39b56b917e44f77647f64df9afa545a2cb783dabf2af7fd5411fc7e480878decc9e0e913f5ffb869470b3ebd788177e3f529c99a4731ed99f19422048d6c44e91fe643fa668a14caa55ee9983506de3bcef2703e79c93665ea7cdae4baec63c853c6d529effcd4b7ddba45ae53acfc8b79915f9bb6d5cd576542a9af252b4083b81708cb2ce386fdb7e6edfddf6b3cbbe736edbc2203a644c5e823bdf6f673d39676773f3fdd0f74f0b62fad0c17a481b934d0fa57ae8b8dba54baa75b5f33e6ef2ac55e51879729867f5ac8eec328bc8cd26b3ea5637715cc255e8d28fe7fbfebd94f7ac202e7fcdccfb36cdc572ee3b1189e3fcd5fdedbedb6edd3ceffbb84ef43a6e87fb54aacda6080b3eff4e8ee6215f5e7e9ba3e261a5c22021cbd60f900fd9c27181fe54edfba161c6acba8d21b8cdacfadb27645e2e9b24d8bee3b81dac9506f97ea838214073e8676d3307b284fb09c1f170be1ffa3dfc452f7f97437bf8e01e2c76721304421fcef2cfa8582ff31efea2b31d5461de56dd795d6de5d6dba598cced05dc1cae7e1f27b75f0bb8445c04873911111191512c7b244c6017f593feb1a3d8da3c7254133dd97079fc68f5c54ed39bd9d7ec6d992e377b2ee32753cd69a5fc92e3ca9ff22b0df255465c19861a57b2a0cb954e4857ba18e65f19c6316f2b77486bcac4f4d78ae5ffb49793c0d052dd8b62bf9fd56ffb553da5eb523e3dea9ce04ac8bc4da75899bac1923dd9d1cb022ea401c3a94932dd5103eb2ca5b4f9c27a51a8e0e58eecc5f539b51d7fb158ff2a95b539ceda496335202a71c759ae627d2078cc7bfe3ee89827be6cb16047af5aca2a6d7e3f5db78629d5348d366d5fd8e9b02647f351f8187d7ffeb15d16ab11cc0818e7cb21b6c99cc1523ef1c4134f3cf1c419fce59061fbd9fe30439d0184a73a07caf673b7f79037081a4afa4301050e282c7f430d99fe72470af91b8a77709fc1527a0ad08f4c058e282c83b95324a17fdc681c5158fef6261d27c0ce577f544affbecdb429eda694d226774c5dff7ee0607946479f551a779f7d27c1d46ca7dadd9a1c55fba07d143e067ed75e461f0c3966438e75d3ab927a302cfa18f8366c82c2c740b1e86329b1997eda9cfd75afb64ab3399b9bdcc7715fbfede3ef27497d207c6596651909db6ba1f69fec4f9361d6029bae1e45a3bd9452ce4ca3b56d4ef5b23087b392d278b953279be1f37ce735f7292f0b691adff7c335fa1d8ecdb229bb7e43705cca79ed79ddd3fb3cc8424a43ed046b80cb4a667eb8a9d0003130e418e82c172bc70b59f6158089fd6698dd4c74e5e0eef6fdf0cba2c2d66fcdcee9c3c16f088e3ba5f77990515a6f789a9d2f2595947e3f5f0b571fb755a9d1ec33cda314030cbaaefbaac1f7d3519ae58eaca6f4334d6a9a465fa35a6b54f47c356674f13361f9fb01bfd8ea3efe1a59294817547a090b304bd55c5645332d6b4dd3fa332b612ed9f2afd5950939cbbf899ce5fd852e5756e50bc1639fb884be8088848da05bfe4caeafd031503444b084978082323198eed83ad4ad3bd71943c43bda033fcfab5de2a5ef477999f200790794e5660afcb8eee66aad75e3c24efd16fed4e742205da83d90842d898ae2a85a6b98b560732f057e32ab9cc769d9e7814d6bc1cd05f7d0bfb1af7afbf61bcc62fbab16de2c91b086c20c258aa144759c4d779ec79f90ae6d3cbb75f5e2789ca6d5dad5aedbbcfaab54fd15587ff5556eebbaaeebb6ee6fd8685da55dcd40fbd1bada69936f7c38f81b82430332c6497f3fbc86da65316a2eeb721217686e77c3637fd9fcd675dbd66de1ca03bf6ddb58ec529b4d286f7f40988dab697ebb54f7a9c709fb723a9ff339fed211c7a1aba393921f10e622f3329cefa6970abbeff8fbc16c5c4db2ac5d6bede6496daf13e6b09094e7ac1eaadf0ff79cbaf1fafbd11ae7d3c46c0cdbdfe9785c57bfa31e4ec8979de56915075b5f7bef93f44dbd114dcc7156d32bac36cdc0e1d5ef47c3c0ebba59b31982e3500a343a6937a5dd1b80f55321dfda81d65f5e576d1a9acdf707e40753976e0a23c60b63e3cd9cecc67b2f057e36bfbdcd8d4d2a95e36d9d8e8d77b7e79efb9efbbcd4d71008ff68df75e1f640927ea87d7fdd6bdd8971561266d1effa2269285209efd89eaab4f416cd01fd2f1473e96fa197db5a700f942f2e7dda6ba8ceb8b4cd5cfa9a18867ba8133b72d4a5f49708f18ebea90ffc6cbc8ee98ebd46121dc065753800dcf88f195ce696ffe84ee4718f142b7ff4dc55a5f3974f7f72dd48b7da9413574232ca3b9ac7f623275df6323297cb0cb88c80cbadf0320e1bd676f93b1b41b694ea92e5302c1f17278e2e16eb5f25c58c3579b497e6c07ff49e734e8d6f966559bf16fe643f8284b5926cf92bf5936c79691e4ea539f0f771b958ac7f95ca5a2aed85fe0c333456f693844923b7aa7f16cbe5734769247b60982fc996bf8a8bddc24b58aebf349230bf225b2a2a8e2aeb578c240ccc817f0da50cccf2772acdc3bfb4520df7cb1d9dca751984aa75b8ae54e53a9411f7aca0fd288d74a4cda959289f38cbbfdbb4ac7a35d39ee739eec77fe01eff4e7e3f788c67d9abdf4fa67296bbf61dcdda3da09a26ba1567f9c7d02dff1bc2ec70bd5ba3f4e1a967b8fe7ec506ffd23c640ccd813f16a51eaefb142d4faeffe8535034240cdce3d2887bfc3515ece856acf80f15fc7e54bc4313c721693d9a8406893b4a30d75fda78a669e21882db371ef38e6e1e7cbd73b18d4ee7049667ac73ceee7d782b49988c49b9f39d15c53bba5b83612ec591646bfe7418ee7415ee7413c23471d39beeec445a245b921ac9967ca8f19f9a3acccd6022b9144991aea4e22c69c5593293c23d92165df97fb39925ea8e540bd207325689c699e5ca1fa9166ff1e7cc32b52051af644d1df3705bddb6add65a2b277f634deca6336738aa9dfaa99bce5cf9479028f1a56c4b6b5bfd5e5e137f75d93b94b3e4f793b3e4df8862475ff225287f752faf338511e34edfcbf733fef25e6a1cc7711cd1173a53f53677f70f5cebb88ee3388ee37e96f86b1ca263d8b19ffac95f7dc65f2ce42cb9840ba7b3b89f889c497ed7dfb5d65aab8f477234d1975660a574314f4c1ec697b235ecc869ae7c254b849aba7e28acb52858b9dcc5045fae3fec7217133ca962c79d162c7769a2a6dd7d1ed045852c97bb14295d035cee5214e5ee3c8fbf3458fdece507c29fa0011411111151ac8afffdec781f594d5489e34e8e673761d8692f4f86d4b54a635e55cbac852bafdbaaa6f1a77d2b21b46a5f438ddf56ed059df46a7deb2feeeb6bdbb6852bfb791de536f0eb835fbf72a9edeb6f627d6da3692c5ff6c24474557eadb3b20782c387cf6f080ef0f9f3a03ef8d5c14d1c876e15333496ab6a76bf859c27aabc5cdeb6a7f539cadadc9e7b0e579fd76d9459ab5cfdae32b76962a59968e4c11e7af28e9ea2463465e73527c65dced93de777f79c995579e14b551361b56f7154b5ecf9c939e79c73ced933f3e4f7237b6a1e75df3409d4cf83ec7dcc714d9c9ec33a0666b3cbb6aa6593e33a8edb3c3ba7cca494cd6d5593946619cd6696655fe5dc001c439089ae1ebed236ac44c1ebf7f7d3a13901d7efce19d142ff24507f7bf69e6af5db5e0bb7ef5a506bd7811ee86d42b410fcae867efbfb799e1350ff7e34277280ba5dcbb8dcea87dfefbfe7db71a779ddd3fa7d69ddf8f3a0f3eacf075e4e42f3e5d6d0efd7d19417324f6e95e0652f644c70b5af6d63b55abfe3c2cdda54d7759e57bbaec3f16ae8b776b57e97533bbf36e1f83d354dd3b82ce3c4aaad90c5bd5e55e14db1aaa9524a9ed212e1305180c344810c4f7efc90c188597c9959b3890b2873eabb29bd54ea7db8166668bc90e5ac90e7b3e227a521ba5c41eff3b80d044170d37cf8c679a9507bff52afa542ed6dc83765a5067aa067434e85148df7d9efbaf6b68ff5fd58f179a258ed4b794969a05cefb3afd355d0fb3efb5a48d3d8bedc040b3fdceefb290013db5d6e82052957bb9dc8e2be1f7ec194077ef6bbea7d077a1c0ee881decd6f7f8373e379def7799e27de78369ffdedfbd1bafadd5c3b7a37e53de7dd78a94fa552a0077aa9df3ec5e1e0d430f5a954bfeafb49712a2fc502953b72d0ecac811ee8f1e6ce6dae0c5ab82f95b36894cdd171dec1b73fcb5cd3681279352dcbb8ad769644e8ae863c7d3bdf9361953f5639c324f2ae84684f65c6f5f7e5be2118dc16c710745dd7691fe7f9f733b9a7df7329a5a6494dd33419d2efa4d675f742911377d5957674e950a5f074e5ef48cf1675d2c747c9ed9d63e69c33851f5ece3c738ccde16fd35cff1c58c7aa06f8c387c28e5306a31ea8744cb7b4454d2ac5684400000000c314002028140e080543a1603cd8d5b50714000e8d9e4466489b8903254a611832c6104280218018202000303244c206941b9faa46376312ff1a63a5c71b4fbacf63e9a9071df7b380c69b34fdd16da3b7909ede06979d6c0d6281ae85a7314e100c70c7edd1c427ae349df306b1cf8aca32ce9dba0849656973dcb9cc03e8ce604e1ba7dc5df41049232033b25b32d714dd718af9d4c2ac61a45fd4b2d23f2244047c96285ad220c6ccff90adb41309107061c5f317243a37115906b460931e54e710c50e60880b357865f246da1a901344a80ad7d0b0dab501432a9978ddc6d9162e52e09365f1810dbfd4e7704d333401fc7b7a94c2099353226ec48024d3881ceb4eeb8ad073be49281aafa0ce44f9929dc5f316fc73eb775edba78c19dbb2db8f2fc49611e316426fb0001700f684422949cf306f190c84bbc8566142827af98ea999c8c1f479a23d4f45b0147929290d4b4df0aa65fe94a531b941d0dce236274c6628e3e6d4151b9b1c1a2b1b86b020eb338df9a0be7289ce5de752fb840416b836549e33de0a036c8e58725ff7bed54d7ee02a37d7bc807f5fe36637cafab576efb40d46e6d7f9e9a7844b58edf948b0d173838bfef5d7b4b2b556fa034e70afe251bf16c5d444913aaf59f81b9ad064fccc99f70ac213294fdbd8847bde78c19a0c9d0142d53d041022a0374ad9160534cc753b33e029c14ab0499041317b0dc0c8d8990649a588ffebe3cf3011b4099ba42f04a5264958f2f082fb1ffaab260586d0d88c9e359b3d43bd2ba4cd1d142f85c757354e23e128282ccbed6cb1f47451f30b02aadef5b35a24798615558b99cdccd61534db2d00f714237ba294eb80f90677006c8741ff8a3e96f80056e61432c11877f15b0819482d145a98003fedc2acf134ad9962a5fbb77d899f845c48f8710a2b6035152370abfc199ff3c43e6bc581f5f14d8499b7de720b84056b7e6a6fca9e7ab01532ebfb033895353f3c692a263ca2e6f24baf10e6a68090654c5a044b9e4149ceeb7b436e79bb04630fc408767efea4225facd7624e0850ea779b279b52efbc0722c5fa347ad3f8dba07fb862af8612a39b7f94b34f96cd42f8beef4d6edde5a9ae163865fc81edf7bf7151530f96470f1960b6d58b02c13df9fa81cc1233050161b8896c51e2aae7c8e2bae7a8e72f1ca6ef89e8cfa8169a9b7414d5f40ea8784ef2606e4c16cbca59ab6929741e5b96b27e70f06701ab66ba40abf38fd8e47045a5fafebc80dbb461b805627274869d20c2c378428c8acacb9fb57ae4dda63c985aeeda0cd8a7da4a5b7c3e0670bbbf11c73e16d6a2631650d407edcc3547a9d6da4185631d26c937735a7532a0d9415286cf904e2c3963d25dccdc12a13a461b451a7931ab8c2ece1e4f5a5bd3258607854b969eefa3d4cd638a9aa380d3773890ca1861c419c103840d3e057cd9cb9ddf0c644ce90e170c7c3516dc73609c21290d30704ec75caf3d238a9b4f200b757274e111914d6ffbdd9e5045050baec1de7f06f5d8877e0c561ba7cd5ea379e1a04db2368b6e123977cefcc9bbcaafce1783b3c230f8131899c535ea63644cc37ffe734874d6849ff8fccacb30e5dab7c186078ef88277e0190c7e1bb0fd7c71420a3027c2f51257d14a4ee2f112b2cf61ce057fe35319557e40844d9741b9deb38bebe9c0949419581620f65eb05e2d026b6b51f8a12d01099febd25afb38b15d2c4a5792f729db721ed5a755e106671a03cadc0a2f4cc23e5686948fc58bfb44ef8720bdf95c8e3ceba67b1853f750d4db7dac1e3393daa3d51fe9659a2406ec8c3bdae3861b9dfd3c28acd484230093e2cf7eacd3ed6920e69609d11341e9eb9fb445344f114da2e70d77a40797743e96e6db611e2093bf8f7adf8b677c41d441d255ac01377146e6c52e3935b468a9ef3596154c9fc2e781164a87170010450a68a0851f3b953c49f8fa62ddcf9fb2ddeac306c7b6f3978382ef3f83b61fef9494b6befea32660e113d5d838899c51959ec64d79355aa0e1f6464d2d960536c081a58460c858e8394e258033d898cb2b9ede158e651e46ed5bf001289e80dba61149ed4408491223039689623a40dcfc16a1eccaf6b6deb207446fd3db741b7f6bb0b0786f09e4941b284544ff13f86687d48e72c51b54d544f18f89a2000a7be04d8ec2126b25b66749f709d5732809412b31231c4370d6a80dab984e5837f548ce08af858e8ba3961625ccbb990c6785498a9f7a90779ac7c43d79bafd705e9d97b944d32628c44dc0b346c1010e94daf7019e6781d60a0c87102eb56af399bb7d1c0c277b6d7a7aa6c027c99afbabf8c11b12ad567c8a47989bc54880591c1d46a18e3ccc0399a1cce64c809bf5d4de8e5b6bb3e246a052c9af69c8cd473e27f012a118dd7744ae4d915b4b23a78d149877e428977f029ce7026fad3e169243ed0bbb655f5faa911b67eb618805942e1109a5207d50718250a3db8b3f4de5e6a9e5a1da7877e552af891ac957af801cbd6d5f678baa757cda210cc8bdbb368e35266b27967c71ceae0f1c19752a3ee156b530206fdecb3e51302b03a930420e59edf84265e926db99c09880c7ee24e5f98509700d63e921e48cf5b9096d93d3c6e68d6d4db7c77cf5ed5cd8352f590b63ab37d7f820bbc4261d6856c02ced5e1c76cdedd4203e80e32ce2cea446e2d05ee8b9a72fa5d0d4e4a3cca4723fcac7c13256f4285a9600a005921a479bffb06acc5b8bd9239bcb24933a474c0ef2ec07ddb074508cc35006d1a6f924363ddafb888df5d77cb1d8e0042f40476a38281da9de802485ae729eb0c7eb989d3ecccb306e425bc3882b85590737da37ca20614bec5f669a33cc1276f9b8ee5cee5f14c0207893240e2137e441378200d6e3a0dc36a82b40e4e350ec9c56671c3d6bfa58eda974603512a0b75051b0c2f57175527613a9e8c0359d9daaa1b7c0b19490c4aa01b5a6f8c6337128f459629dfcd75c1eead2f99a33cb61e85a005e43aa6c00f507230b6441d6c2f767db5875bb5c793f9aa1971e6577ac6d06fe606a70601ee379c0c0ea26fac9c202b5f5d56a90c2aabf1bd4b1b429bf9e6a13a67116493b45ed850437c31c08687240e4065f105b8bb193d3093498c1d62d90ced9fa520775e15e37f5f3d955ba6c5fe3daf6415acb1f7080112b391b73216b69001e0bd3cae32b6f4aa6be7116a1177d79f10c1ffcbd4afe2655e29dcbe5de13044f85f28ac2d7384040ff808b8bbde05b15e6eca734fb3127093bf50612e3564f709287788f2557758d09691e21fe5bd1043a7f8c7072aaffbc74f107d2a4952fac14ac5abd2a4b20219eeae6d26fc58f63d83bbdb67340f336060339f711d59fddb810bcdd9f025686ddcccb037dcaa814ab5d86fa12487511af9ead81b5f84ce1300180503774750da2d8e4b3e8562f28e4483b17cae025207a7f737f833a6f58e4c8e0ceb27b66c5db252c04f23b59417e5b7e18db8997075cf64ce7f5c51432cde664419cd61b5840e829783e2ba5a6a34a918a7a0f77080bfe22e0622dae0bcf7ba7bcd83bcc3106a48038cd6788c68461a97f0a56ba764bcc316af112e29c364bee2a70be2ac25e6d3ac326ce143700e9b2499a1cd4a51505d219630e567c1337b21673eb062ff626d018f259c21f2b5a69948278b159175632d1f32f79d845dc4abb7886e1a08ee3b88040072105a9926181e818ca2daf203ff70a6562888fa3f5a9e6ef4c38eeb65dd3a6ca8fbcb9fb549ed56df2335029a0614b9100052b509b230ad9a1cb8fb5932e4ae32ca8fc9c23c8067ecb0be654ecddbc4e382d998db6794000276abbf098d6a790a11dc838ecf4d000715c357ffe50aa38e4666f2e0a8ab14aca5ad9482220a88cd2cbc4d3db53f4388617aaa4309e569dfb303a9f67223ef35a3a06c58fddd0b41a23fa72bc75c92e87081fb8d20967be2418c02ac28de8fb340058582835b2b06349d2175dcba9e8e98d72112dfc8bd8190c2635ddcbd5e5f45d2d0867ba4d578f560008720399e36f0c0016db4b11c1f8ded6d469eefe43f7cb5ebb0423e77c1f7c7d4c9c8f0f6faca36136481fa58de5f16c1d85ec49f3afbd631064294977b6ea46bd406a8a8513776c4bf8e8dc5fa51a956fb4fae503cd196334fad5e7577dd2d8d80cf42b28a8a9b6c231e0853bffa0c8d7632a42e6f5e0d572a28cd112a61809ec7184bfbac717ea5b701a5c82398882526f697f14e9cd92e74680c96538cf10c830af5c9c025fc37957ee3bee8dd302378725451ead038bc19d15b6dcc13541acd269e56aa7e7fbbcc0dbb374a64591a049196cdac06c5072528cd936faee2543aed76e6976c988b123d3d0af907b0935ba63643aac5c609405494026470576c7ac97814c6319427f0980aed844e1ab575dda6cc9ce85b09ff34a0c7a0462e44f1d12975fa53a8f808b09176eec814e47decb90bd98eb7195897c92024e032f12b2619421f09d0cc3b049c959c260df7d0beff951ee4a7d0c09191775b9f93a56e489153646128d0488f242a1e2d2d13eddd57c9f1014c6fccb2d672ecfab197e314b5d9c538605cdf0450106c84026eb8355c51ee643ed6e139c7dec81c50363132b5a43b0e03292f604213c9a74dd5cf7812c6b1c3c7345d1c09fe022637c523651f45738aca92e21a5469a6136083db2093eb3d65b71d3c09c9edd66b3ac95507cb7b01b3bfb476cb7991f5519969f6e9675024464c4765d0d284d2b4a2bde2b92ae7c2b568f3b87f0d7687bb1eabcda4d6baff5d1bd1159911fbc7c1c8d7ccc82a244e24bebfcf622afab7a5cd71885ef59519d1026bbeab7bff29bbbe050936551d85a072d65e48bf63b4b59e80eac3e6cf47f1beb9ecc9d2729ffb9275e9fe493f33579b7362750a3b2f9e1a593ed3461dacb9043a7b54e7a6d695f56d9a72b233a8e88b78478bfeae62384cd2912a7fde98bb540b6b8667f54afc06d05b2f5c7377c2386d2fcaff74596173fb145269c229bc0b89e257ab08c7d2fa67ce08d66f582f74650732ed310888c0cdd439e4071daa714827ce15dbcb604c0f90e71e499549ee8aee15c1a6883c97a7a44150832f8630458db9654581be7cc316be3e439ec88a2e6ea934a3be3a858d19143565159314905920d88e35adedcd38574b87c115ef9c1887eaa9399a13a59011c2886013089823510ed006a4a0095560835ad29abc02f4155509b995acd9171d75667d1f82cc339e693d73aa6f36d9462f86ce2241a62c1c8209562aecfb69e29e660881fdc7f182c064da0e39426dd50c23e2f49d2f3392f0a5c0b81b966a6b150c79819bb3f8453d4d681e8e299c05a29173f0c4e836fc8c2477ff32178ae62c20f0dc3c4cb209e32636fef02463282071fcfdd1fa70d7e6748465fab71550dbebba3e1046475d687551c061db1c2c42cccda9beb7266272bf8a2857c6be3491f8239ebd3fdf49cb24abbf2e5132f52d63f533482a3786d8406ddbb1126288c31ba849d1fcdb6bac5e76a5e08556c034b6fb7701cd311c70be1106643d16811f45cd0427e12f73d6495ee02a0492cab80a387d9f1331f65a3d3252b9fa081bb16f79c56589e1b1dbbf0b97f8cfc84459dd9c3e5310a15e3f213cfee6bd404a1d201599fe45b05592378f1e5e2312788f13a4f282eec1cc0c514c8494028886ef223cc53ef158e0028f0b1428b9cb3b8b7b7034b78eedf8db48b33b8d9bcbda2acedf96a156c8a210390aec5d981f04541dc9b64b3679ebb39d278dcfb81dd29c531b9b756091a41c4d6c8de9d938bde653ae2b58fed99c16137fe798a9789fe25056e692721016ac4d4edc1b2409bbcea2799eb42e3687542252f9d861954526ea86e276df5ffaece88b8c02d6db0ac42b8dd338199b42ec5ae68f8f9024f1529534262e8da8a98de45539437618f83b384110b5ff1e08e2d73eb5f892f36405239b6415072f319a111cc6bf7ebac70e21848cf600555afb8d722dca12fffb9b28b7e805407d7bc8b682d088b273d2b713be2037a74eb8ce6cdc39ad3278dec6d06629b2f728eb09b15d1499b76a4f825db3ff98a85673eba75da82baddfcc1171dc707c87da6b0372044caa61cff99b1d6692ae52bd1f164a35d5157df1d232a64855710ea0c909d15aaade278c27954df94ebd23a55f6f00e6ccad916aafe71e6e1a323fd6cce072248c1b791252e87ef3a2c3b8266dccf0a7bdcd80f84bc21157f16b607326a740f2e90e75f469d8f960e08070c58ef2462e27777b12c9697e205d35449f488084900b982bbaac32ce2dc9a5790a243af71fd7d1cdf286aad156f32c0e34e9bfca28a6420c1c5985cc296459651c12cf6677c73c28e1f045c71d81208c27b0f8ee61a517678845ef5ff5fbe77f8211db21f0ff57e086bc49dde893c0743ad69b1dba439ca958d8632a3d6f12fa6638a41aec4051fe05f319ce2303a37aa9c87f599d6755116d77709f29c8889d00e4c22d169e08d8e4c2cb41f85c006bf7386f4668b27a105f2d7b7b2d75b62dd0696deb899a007cdd27077665bd7a89c00b46494138ca7353c802c74297071304ef009f920559af44f5afb4ea65e8e287587a471f735a7cf83df28fa5885847367a94900f25ea740d1e67cddcd70efa11e3e1fc3994c0647ea1e776db12ace6cce67d8ebd4d3115e582a662307379997cb02d3a237cf65849d1cfd2fe544126c1021f511b19f1033f7bdf8ef48408fb5d0bff22a5b26d9564b0610a65ef6e802667ac0be668acc3a40b79afb95a8127a511015e8a5ed52415b488517486afda890f7f49c0ad3fb32809cf34dc3fc79941381abb0052f03b1bad25ef69632078497ab16ce8a4cb90ae7cf148b9722a30c163f8d1353ca9bff602fad3405b27e6ed016b3c09356c36a769391dd8d64df0df777857f4d201faa95677f1ed5a8fbdef26ae6c9510f0f7302414842f5fc508d26df0c0168e1aaf3e473f93276da48ddab60ad93dc4c1dd3fb5e302cae528556934446700f379f16d6b21a31b5de3c1bc2958259590fcb5aaeb6ba26b3618512a50ff63617d8722b918371dfe78fed85c7c29f86c1590e0378dba80553692b1e8ec6b14bee89883ee4da719ccfb02e0c9c9363860dc5a38d0858fee1f118bead6be7a910cc1fbbee0ffb28011d919d42b03d6d89dd4a6b719ea2bee8adc2761774a15470879fdfb3c15c64c36c86d63233de76ab76f5b09c58e1e5ebef3b856036d8009142e0018ac39b7d85db7b22f3b80b59dd85df3663b3c73741490de5b4acbeb38ae599d4ef6db3e77e27fcf690d25be9114f76b81e2e44a268617ccd6286a4d2462c4765a3940091a882444f0414244ae4b814a910c8da668da567b891fdb9ac55d83b330b24bf507f8f2bba97914d1dd8a4051e9b4222d34bff2947b2b692fe7345b896aa53e03068e3b0c2d7fa127a4eebbd603d2b184c144ba19afc62095e3d1d33e02f91c16bd4c04b1f51b5e269a3522cdc35f69edd3a6d72e8d25a35cd35401420200f4874bda31bccc8f700da681b472191498b526556a83638ee08eda421b7e878fd8e223fd5eea9e33bac5a62cfc67afd316ece7c1144972233609e39e1b49aa3efaf79d5e2e6e7db78e3e516f02397ef2131abb6c389a7dc2ae692426868da65239696d2605d51ae554603c56704d7459f52aaf55d6be46ff01c217b896cb7c46677e199a912a350fb8ad0675f022984fa57448ee4e2b0d0e4b612e65d54736b8a58ba4c94a52c312d63137496b23b47ac4a654ab314e4e54fd27859cac94ba5e5cafdea1b26008822985b7011c3766a037f10fe6a001e053d69665c1116e09b04208a7885cdc2162885e7ab5fd4a2f49c1dceb6b3f33298a61e74386deb6a6a8f2539625168019037718ab3aa2216abc181886301fff10e4559666fb47fd9698a94fb04fac21fd718b5623fd870869b2b57b1493ab21f48f5b2612f5c180854e273a119ef36d307561d55f0efe5e2d7c8a0d44ad558845a5229c6c47e19f67e9e4665466b0434e87dc7f8a657f98c2f1740f94605b78eb416da9038f14b0051e722710ce7bd910c7052dbc412199af79196f0947e70fc970f40cbd1389c7c4ddcb5194c001a186687bfcefd88d72a9c08d01f23dc24d46ab19b30a16b3891d6347804efe8bde52b63018fcc29279ed6db69486399460074988502539f5f191688141da9dabdb0292fc0a619d0964ec0e8472cbfe4360a39bb91cc1ae75535683a1d7383288d36a9b32e4c26a18844b47c260fb9516ede85faf39bccd938a0f7fa00501c36245d07a87d89cd7cf3719c861024eb4284a522504a24da7b20d073508e0ba22094a407f1b7f7b4f58e47cb96af09a5dbd3beb46928a27adb9936fdb868d3c882938e38cbbe3f6d42a63661b5896044912c2ca482ac0b654e6bd5341db5908f9a4e0cacd6a659e392fc02136b6ad312ca0f69be9d66cb939f409b876dba9bde4d4f87938bf9b5deacb1a62f8438293bdb2281abf83885d778ea8046687bf649d024331eabd270baccb0774e50030476a5f8f8f7a7972eb4981b8ac998b22e9cfae0a8bfe9ef948a03fe9d5660d6e737b08330db4baeb6e9d06391171f73552560f665b6e90d095040938e3565dc6e72013e2c9bdd8433b7efbad575468d5e3c436d70d24635094c4b7c52c0e90668db8a7d5f3189a9ecd427c26ef0b8e84cebe5a44c094c048ee002f0724df1cd792a81fad07831ed916700a6e86604ca5c5101b8904e902cdc618411e2b1ed3a3ebf281843a504494092169685c13d717f2e29a8ca28e3ca83b4e63b90c90c46864a2841bb66a3b34a1fa7fbc54e2adc4843cbed95184271544ad1c9fe634730e6331f6627d6f93003998dde2c91dd3412af0695a7c59780eab8377b4aacdd06cb1a22634f88fdf604a828b06ed27248304a8d34a592636b60a309d6a0ebbbd1a10a894e2fb3628fb3da68cd5add5e6fc4a86fb9fd54db1edccf2f3e8e8f88e5e4b84e68cf3a1b738f2a846a0bcaba93a2cf2ebfb8e90f0c7e171be84cb5b949bad6b19e036bac8febb387fc8f773d7533cc0701fac2fa77e0d645ff2cfb493edc87e8fbf2503a961c1fda97b9d403c5a61ebd13ef218b0af0a0f36482cc4f506fbcc47daf07a6c525bf1f3dc08aa77d3c90ad5fc3d1f1620280648c19a33b191c8259fa1ae18a4bc8f4d44d0f203571820a1662680b5ff14151d33633bf1743c2c33463e8a0408762a149f55f06d0c4d2f69e2c25b6f689639a94ba484dab52fd6e1931835e517a0f475efdc235f50609a6cd99e2817b2d9e1afd4bfde743b53e54a75ba9c9c6c17960b317e1f48f31b07fb1dd878e8407f57c187c02d36e4a68644f601a1837c07ec6453f9a6b99db00b714ee93680e86ad204fc506b8903aba06f8681d4970999a250dc5c85f1dbdd2c4316ed115019a9e6cde7da92bf0c24da9159873379fba2a926fb4332dfdd6ff418cdf024ec9eeeab2668ad1a3c1d3e2b1de09b6ed19d610f6723abf25d1c8f0a852f2ba977d66ad80bcc90bfea283202a8baf8e7e80761196dec82277ad5c86a4f0b8e0cc41c947983fbadeaaa711192e7e161b61b594baa5f39701575237d681b79f6578cdbc74d526b2a0534071d78aa9cf49f6d818403e4d82a3cd1e4077dcd281ec2b0980dd46af6f461a18cba7aa4392618c56b415ddb936ab4cd961e999b3b0a5843914d6538f0a15fb0aec51ac2932c6d67b824f2172f1ece4166c82b91799f8eb5c40bfc3d4481d263e75bbc93dd6f22d8922633e96aa3dcc16e730332bf96f28512ec07367cbc8634b846ce63c825879ee29ea440fd6a31daa6b751e71a7b9b11aa264871387fb8892f06914b0a0ad5d72cd8d81f2372d873548e587a4df0747459ea1e2e38b2e8d6de5bde3572aa30da6601b1b39d9ea887e2ccf88382430831aa4fa1e04667fdb580308235259d827520807d3bcc5a3ef169d46a2b8eed530aeb63ba26590bcab0325bb8cce3518f90914418c2146587270d13a2ad53852a90ff6072f7a3089ad9254dba052bb00f0761e063ed9b21afa1258710ff433986a577b0e4b63875d8f3414dacd41438a8b196b35a790d0713fc90ed194852ec02ae10dd6a1019d61ff630a9bccd061c49f68057ab80b0bb1d7e9baab67778d4fa88b3d451937f73061b4951840d3e799182e6a1cfc80332f1400457234390e330bc35b72db5f9d8a33cf2a6c258d07b0d35923cfbd8a098a5abf8c5647553d071bc42b48329ba099b943272b135053d63951399062c1a7688f632417f312e84499c8c3c1aaa45c1154c69ae145e6fd21329fc8146184ef290367b1d90fd49d176d1b714a4d1974175801e2059ca0060488493d96726b6636525d3e20d08aeeb2a187a81bbb9b59429ffa928c4a0c2979b3a4aa2acd2d1cef448c884c8a465eec91a5293bf6a6a8772cf1aee24750a28651f881615882a36801cae01b57275551a37ddc9d86413b67b9040bc4cc871ec4faee1930b42c3c62c03cddb3eb04ff444a22110c00258251740890a001332d22e22dadffe1fb40c19bf99eee7fe203bd6bd4c8dd002d2a753b29e6832467dd7c7a6715f1922a1dbb0c3b02914b7ee16d208449fc55b04ee6931bd9fa6ca82c2889c28d0549641693eaef3c90081ab74efa6900b90a58950fd2ed454a911b8ac5c8b4e0f562b882f312a7be03eb963840baff3fca946c51b34c72f9bca90ff910c839852391bc45610870ff926e07e6ef5f9031b0ac9c28af888dfd466b486b1d404320380bd8dbed291c06d36bfe8a312e13596ac43030358b554c5a01231661cffb8cfe4b2111228010a9288ca4117966192e0719126eabb97ce01bfe46b852df3f65c26b73bb3b28251a4bb3964dc36aae8ef1e63b444a701dd6c4e31b3c55244de87e098648142683385ca3870a9908f9faa83ba8140a4212ba004b4f9a73060a25e98715231c0846c4b6fb4a9bc26d05daf58bfcec5ee4f970c2befd6dd9ea146f73b9578fe952ad1482892bc69f0c4bb88d2a6bb0fa14d4cbf2170ffa656186f4856a619fe9cb0ef3ee200e0508460df30a2328c85b30192feafbab1007b58ce8ed512c7608576182203f418a3d82245e2f229730a9fc436fd3aa19b6104ea96fb964751e5089d71381cd183a4370faded01970bf11d3cf9af4bfb3a34c3cecb315bb03ae806ac0992e65ed45e1f745c129d7428520c0b8832389a1195824f170a199c5fcb7ae6ea0f3f20c1e666d7d0dcbee659f790400fd3060aebb652e9debc51c8341b46280c7fe8016b8b3612aca83323ae61c60e6a490c40c9e60edb8823d48acc8a706b75fe8fb9c2946245bfb7f9b60351bcb8151ea0bedaca95de18e36bd740851d5121b4ea49b70dca3d4315906f6f2d5149b1b2ea45f0659852bd57fb3cec108646d8bccbe01ef077da0576a8a16914b680119c8c5348d25dae6a867eb3d94f89c0578908cade537351c0b5200268a888d00d62f74572d92105038b1a169e128b20e45ca24d7ad1d54344e0efd25120e1021b97b4df20c2bf90fe90f5e12688045fcb33425ef76dd865e3bb087161aaa82434bbc8c8655dbbb0d81acbb41a1efd6f5e11221046b4c9729e9fed43dac66d640e8a915290d5946d9f60a1dfa42f61233e5d75321c471958a032ed840a5d24763fb248c7cf7b692bb1f46e75c840bc88a6d0f50b0382468104009121ce7930f321686b124f24e043a7f1a10e13eee1b69dcb12da8be02786fe01adb0657890f5d44b07c43a2951926b45ba41181cba1b18b1e24001b7d03f40fd94cac9d6ebc2e23553692775a1002f55835ad6b9fc1d12c0884382f6b17cea47c732b8ce48d2461b16196dc7d12af00ea50af2293fa154e59933ab531ed7cd8ed52dcbf2658ea9041ffd1e0c3661cefb954bd405994a440b3a62c1d0e1438cdf0604dd45a8e5f4e28162bdf174a0b7d0b876dcd280c5c923222a9dc084bf281e7fc8972fbfc171c2ee5f03b5b9154bd130fb4f3c902ac21a0cf2f17e02aa6be69a11766615fa7b23ada6fc2e013ff8bc7134ba8f8ff078d2f83b5521bb24b78b484f921957fc1f1685b6233246e1eac421c893ac1b8c5d8ee88d8db691e9ba88d340dffe6338211eaeb7e9fdb10021f578b1e4e1faf1943e2da422a6aa2cbfcd86e7f738a6cf5cffdfc9d31940e982c02f8314bb691c9aa1049b286b813fa6c2ea7be9174b850ba7822855ac157ad755da38a63b64d61f80ddeea7d79ff18f1f039be81d8e0a1a1c5d3cbb1a2253926079a6cc6c6dfbca8c7bea601243802079bd29c75b9a50cecc85a31cea1200cf933abb95e26a59dbdb2645a4fbce1d11b73412e477c2390769cc6baac9ea232b4542a85f91d79bfd27943a7d9b747c526aeb846ae84d31acda5da4bf414ad612727e7c1c959475cd095136d8eaf0dd07766404a9d8a1211e1f5782de066bf09de42ac59c4f866297506face4df046694cf94814d3971abba3af18ef5582b8243f783fb477e99960789f5729e207be748bb1f6ec964b8feee88e2278bff93c7d162c9fe794d8f34c81d568f1000bf3424cb033336b9ea7917aeccc41b40577d385e9cfc17144053c9e648c22172964d3c471381c3cb2a877c3fcd562f29ef1de1accd196dac5772f92d400b94c27d8e4f1f6e5a92ca6c00128aaea92319deed46b58aba56579af5d3c7781f172c805103577af831042276b78c803cdf799b309ea184fe5d44371663cfcf5b5b4fb495203a3c658da743c48f3dc78e962a9699591027a0062515d46fe5d3741064ce0538af91a747fd1d0c49fbd7ccf81c552f46fcd75b99941675377cfe0865b3b63cefbf8313514c0b2f9a037fc542cb3a46d6cd29d4ed114796751ba168f0f34ccf8a8a4ecbe0ed52d69b649a7e875f00e32967f6dcdb44d822abf6c142e42580d27bbd25baa61a341131dd0a6a68a224bf055a4637d3bd6bf7f7fbe5b509081812522545d3d7f049d7ea93b9ca22527248f5094e4538e9c042980d89ec4342834e846f3c7c7d01b47913b1330cfff2bf9d07be0f43cbbd1453f4e42316954cea6e4f19683cdaefac2b75e1fa07a6c3172fe4009206bdb586759951d7fcb2a17a9b245af9330666b52ae46b63f82d48dadb62533744e7db1b67e0149ba30b62cd1446bcadaae12f94d99210f2700d173d230354a6c68213818b08de796044d8ca901ec043f570e546786870a366c923bee48e284b78b8a82246e1b2ae61d666baf90f96d8b6f6b6995b43329ac904bc701c207e82bef26f4bed9865966bfefc4731cce3f4fa40f687f0709b1e9fd99e7e06b1d0763b4d2ae99284e90b96cac6b44dbbdb9d6a059afc29010590fb29d071c20f4e1e41b7f178091b71f3f9098e653a6e294686b5c616fd6090a839b86d9ea88f911c12953f525719d4a41f177e893f9a7ac8acc5fe2bd09e57acf7122584b2b55cc30490b51d89ad6dc7de20158809e89aac40d6b673965784c0ead222d564e8f5b33cb51843bd737cbcbf53a90546dc84868e11ab170800f2a4da4f82904b75c862909a6a23b421146ef8d1714f59b4db966da56e4088872d88bd290c96de677dba023c80d553a1539aa10d80d3e5a7a50f5f6be924adb5746112803c4460a1be945472bc1b477e654de7760e6a565e0f27fdea477efb88efb9a77e00158db10ff1d1fa8b7c2d336d7f641cd1153a3af01ad90d6d51c7dd58eaeb75b369de120f79976221c495b0fd2175e7067c1dabff0c1082aba9569e5814812cd96694d076f017c345ba8a583955c7457d2ea812a5a9495d5d20edcac64b42daad5c18b543457d0c283b4f940a08c6aa1d60e2249d169252d3cf84445555c772f22dee0368a2d7ca841796af1907ab5941c6dbb2f8d0eb73013e9d5917ea07431f34af9d626e645b067519dd00f935eb09beea30973542024834b8b18e0eaf6444b171b753444f08fc5a272ec437969c0a489952fcdb2a09f37bb5ab4552eec9639d15bf6b9709df45a42bdb0beca85bfc01b6238f7cb450f601fa859a0dc689fc92617d0c314648463276b1e1df6040760e9f85302de4abc0e93279da28548ad036bbd806a4e1a4b5aab48db7e6a08412dfc76c97098cfbd630e029fd7f1be60b858e9f37fbe506bad4e29806a58b4b3d21fe5091b33a7bcb86b632ff3a09f7a2733452781434e4ac6211eb3d86ad151d3260815efba86ac87937cfed81d30087b7720fea14574284ee6b7d59c2c9578051c5747c1df0a928b5eabcd802b610eb936882bdf113a6cf6023d9e1e311e39bbd12ef00a0a8dcd0cb54d1ce43b0d7eafb7f2cb872db981145b02bb1a41bcc7817dfe4f8a945f5e55605f421fa879ae340e769dc1559d500be2fb690761c6f05a7fec8888556fd5ed02beb4438c2fbfb56786b8ed50569b414f2dd4746344dc8511809494669429151b1616d77cb78b8fc5d99a768a3f1356b40d9198ce7f6ea8c8058ad254948016bde7b0ab9fcf54c26d96e170a150b7fed4168d26550f623ea26802ed1785623832a68a2473fb27a45a91c15907e4605027178a58e032340ddd63be8a63b967735d545cb53b8d2307a536602fc45c39aa5903a9a0a7c1ea67bfe9d82cc52c88c6cca68fa53df8e2bcb85954c70f1dd86ce30b02df353c6931131d7f80a6b3c707cb5b8b061ed08f559c93f5a811f467420868c75270c366ea1d76b0b8e9488253aa5d0f9b9009fdf624a917a9cb2ffe033fa8f287473ec6f2f47ded260dcbe6622a00b0cb0e218ae15f0d1b8eef3aba6c80d97f783337379fef70a8aad1b1df0eff3188589c15e4d7666f41b96a58df1664d9962557ae6b3f9084a1de3aadb9407ed5013778713c372221482ac2eee465a9b47c4afadba01629a603cfa31f39b9ff4896eacf00c39260675e6908f8915a5737d28e55cbe8634b0aca89c94710d17a9af877de4b32f1fff5520b1aaaeb09a2067658654101882405fab32754d2856ed7f455094351eccd5da678a33209e63f09b50ec549f1f08c0e74621a5942394e78e8809b069f8c2fc47cbe5a6b602369c164c2c249337b0a9818daf96776763b2c00a980ae6479946a523deb515266f559dbb6c13f17013bfc80b21736987c1569ef23a09282ba7442bf0d510fe201fc486b771c9f03a66a57a3f0bf8b9b28803fc531b3813b067e80cef25f575cfd22449f58a8eebc50b5e39d599977cafd8ab58e53aec19770ee54c0437ee9ed03646ef46dd158886687d7224636d72db32d85b41a5e5db4ededa21170c4ecbac6b7fe8e61eb8f4cbffeb673bc11a0d5809d0e2cebeba0ec6fa1b74448d9fbf3652bcb9a7b3091747662be3aa9c9b60551f80e18189df2dac0d04abe11b198c8209055226fb705ac9cbdf0a6dc474aabca96d86f4415745222ee79b884b0ef0eaeb4ae613d5521ed690ba62249cb85f6acec030a49c68785cf941de45ba8e9f8242cb24c09007c9e311a6b8ccb90dda25b363a59fb8693aaf7adf2d7e934d4f5f537e658306b7a3bc9f0a74b4e0949a7a91119f36518a2400241da868602f06a18000202c13524cd3f39398442a850cd99002ab8beeffb1699cdd0719fda001a4a303e116f38a7741b91239480c9861a5019e990d300323aef2a029e8fa8e5c0ce754b34342486aee483b89f9117a7b05b755c332ef0f2292d99970d367df1ac5c4aa82ef7b0fb4b08483aa7c1b11a9b19d75213f7b7162d589aff9ddcf5e3ca01b3c1589e7cc7f086a3b8676b01511e998605f672073c2f37db956c5daf701ecfb6d8a1a0e2629b509c426bbe8509a52d510a32fb8d5870b31d92b8909ce9e33feaedcf48328435e16f1a88ffda92165310badd6931208ddbd000c60ffb0af303a96d6d9d4d7d000359e4777100b1f19f0062f5626d0a57723698b01caacafcf42401b4123ba383fb1cca6c4c602394259ffbb05d7e6d2efd93012aa37dc56374713786fa56b0b5ba582559255f0936ec2277d612492f358a43bab6eced8ee3ebbd85e09e5b47ff87217ae843bfc2639654238abdb5981235baca9e81432ed4a538a135e9b1c412a0766d4052fa2dfb5d5f46720cc4ce066240ae5b230ba78875a95d09c028da1e6829cc99941a6ee74591570fceab1e734856d2488ef8c28b5fd088356d65e281cb6b55957a1a3e65993e2f62a79ec7cdc66fc9614c7b5202d36be0d464e592b2056e9ff41b380b1b0633d57c8ed464d4a003d8cdc8c18c46d3a3e7fe4369c712a7839ebbc0ef759e71dbe3be456cb0d7be33add08a56e2a5d4fc7f56d35193193b3b8999bc468c77bd449d14120c40ef6064d7ee7de73305b99216ec9628c80d8d62c80b89348f50adc7462b036b40404163d7bd04592b24c780e471851b87a09a33ae381c828ce4eca60b06f62f75293fc011772bc6bedee7b6469a3a1614dcf48e578cb5b79981f1a1c8b13664c29858b5a53844a3629cb691f517d28bd7c624c36ff78e8b4327019113f1cfafc8a6f7ea9734f269873b8428ae843c09265553e22ea495a6bc7d2e57bd50db41018753cb5332b8831ca5cae08b3d1b84b834483a5959b1b11149ffe32d7557455dc1232206bdd3c091a8e3aac9dfed8c8114ed15541368c95da3888ede0981cdbbf8ac423e9dc3af6738db74bdb79370b8965392ed8161c719cc44db4ccb073e5ee4a3f3008b671f608298d0e1fb6cebbc934e2fac7bef21934a931a00d3244d8710b6e116ab56f91bad0b82f8bf64ad19b548a38f2b0150ac026ba223f0078a504d9550f301702b5dfbbe893e4752d702910d479dd9da820e718bd2edf4340049680f8f5c4609d6a9110fb878b918400158b390d2400b216ab03b095af4d8f767795332be4da14273a49420e3ab3e65b7f4cc6e0a66ecdffa2af7ea7e459fdc0501d74b73ca6bad19eb60a1c629ddd50f9471e2f6286419e94ba7ac0c8195de30e67f4996683a67b15607e3544e1ffcfd4a28e101433510b47bac0025669d3cb149fae2025f16fa487be1405ebc68b268e44109dcb5306733bc8fbdb36d6decb049aa1c370bedfa2a3143cefb5ca4e1f58720cc00d58b42cda2c0a623fe2936b18ffae79e5bf52a13382f81d3de2fb53a5f45cff5057d6414b509b142643984ac32323cdb5c6e0e167e06955830e17b809c7a82dae9157e38a1a5b1935b30d22c38a52a74b8c95717c17339af4fb8fa4cb884472375ee10ba9d7b4285eabfb063100cfdebb88ea49d05443e1dfaa2f2be6e9aaf404a3719ffc8fbaab90fb6bc67f2e5b3866c36d3591012904029bd3b7e20139e4e51d56a0cc480a93e5a0feef701d43941a60d5d1d1c1fa0d9a167de2bbf7a4006eb69447d51bdfb8554f997bbefca0dc5b610c572db8cf6d15abbb7bafe577de45cc762db760fdd33a47d0fbaee4bb24f4ce94bdf56df9d09680d8f6a12f9c91788070ed8f933916ddf7c78f8222abc4f18d7d8cb27c1222b5bab5e6ceb8c70f66e44c2e76aabe4241cb2612bded706d3ac4fac7b50649227d3c3fe96a4a63e9baab349707ccd0aa4389f599bae6784ff25927d9d41927be22ec2dcc092afdd36d4228f2d6da2227d2bf0a5e918d1e9cc9d8f0e4f7d6c9fc795ba63964d15bef708902651c82fb30a9c7ac98c069872544f380050c2c079d59b753bb66a586d1d03caa9804be3c5edb3bb99062bf208007539368016f34c2692b687ab9b6e732e7ee74275bfaf3f01b12d980e9ffdfcbd9e774b92dcd11a55084f8816646324996aa9271f9c88b780ceb55f64258293a6aabe339189bcbe4273433b99518e4dfbdabb6f96c5cfbd69e3bd03b742028e894c56a49fdb36d6f4c7e155ffa21f4bda1d32dcc1dac85a78351ecb0f4167137ab10ce18fe3d1814fa33fe07f3f175f86357d1bae2b57882b5e372d76c1f162e4016e1c92e1d37801a27e94f85b82ee9cf10d1f99cdf2ff94f58f765d1d537ed0a85273942362785c3281ef7a6bf0cdf5f26e5e28da123fcdeb55013704bd34996596e6ff99aa226552d53972d73ca00396dab7fc8cbab52d1f0ae6bf2d3d8419f96d1d3641a5728ed18aa29b2022478188a19c54d9dcc5eb0db4104434ff7427eb72088b82d4d59c16981e5623545951fe0b6f701a2e3e4f6a7a1f73054ea651efe529985fbf19406e2f8ce23c6dc7ec0adc7b75850392b66dce061866942ad575c276f23971c63f9a798f2b671cf0dd2f3beada409866563c827dfee7a8933041a0d1df2fb9e080553fe7902b78d6db5c7842b5787f05bdd8d84532a98a58a3ead10b92b54a6d129d7516be17def96c5b636150845fe0bfe31a06b3580f9e0a5a9d0db4f012e5aa71940bb6b7f25b3786ac05e109cb11efc913d033688f5acdc957721d701c3430651c97f873f56aed2efc9fa467d88b800ec7efd96d6ea03026f5c26be6f4a6d354b0d94e0b20dd62d747e50188994c1499d5c793a687900ae18152e574c57ab14b0b7dcc5ff5905311866838dfc51b06a27fa44d0785c74f76937e55a83824dc773370fc1a6a35cb0e5a54398dd79894eaffd24e1e238b43a9f463ed45a29cc7f5a604053e3eff3fabed153a69bb02247238bd4b29b3047d17daeda575c412be79ad227e31bd6f6df4ffa956ab214cb95a46dc96348284742a2490bb8b8940c23e05e795a2709d514345fd898b3a0b5383157008696ef5fe0ce34c13b995492f8c79554a23fe036c8ea82b93cccbf175a5e3782ed92f43cc5c843d2c143b0c56d74fd95304179afd7c0dfa8dcb2a185a7a6e5b2b8e442e45f3f66fc17946983a9e9a99fdea1e8fa946c82ac7556c04415c1712c70b03ded7d7b6123f6e0510bf5835c4407cc9675b5b4717675a552820ee58bad4045926cf35ebbf96f89ff48a38697d15ec7b24c1a1297a16fd1a911940c82745db01889c39548c184d92e33cfcc3617731e2531401ee7590a4a4a92a931d99cd93491fda89380f02ec8e1ba46d0c04365f372dda7b04a70937074862688f628e05cb387ad52a3980b00b612ff76667f0c20467631c0a2acd9d7c02e8a3a1b28edf9d0344e1f12af08602eb713a0476a6a7a50fde937000188eeaa04253f44ea8e956f3286ca4743b2d9d83dd558ae86e6ca8684949117b37fd6d3b8a8909fdefd0397760c94f34a099110680f631ffbd46e48124984464e4aab83067ce478b4479ee9130bacc787d0f1eb97340cefa95db97b925989fccbfb86e6430749af7e1b56d0e6b058324a4128c1243f5e6ad038fa91e903e76dc0161bb565939895063e1d258fe36f9230300dec879898d81fc98a7d99655a4bfdb6ff9d1d320b463a4a9c01117c96114a1d53964543e7afee0cac406e4aa069e3c6c7125196f3bf9f17a209b23cb480cecd5434fe08627c4357aad90f3194eab7aff140e18c3f0329781449bcf42448f3ee96c8390c638a0c2334ad749431cb121ae0b2618e39a92c6db842eb0b5b1f60dc7cb34b67e7164a83b199b02580e1fafb3e26fb44b15f4e36d44b6eae9c3256faa84fec364a8a4f83ea0493bf54ce926c423f71b138d0a4dd5cd1d76c079c0d73c97996e8545df6bfc85f177b713798481c681e8b90f4f5e0a9bde574cb62f35441c56e87dee76e4b09e90e24f2bff7a89b8e340ce8af0cbb7ef94bad9df15dab7d104f9a4f62324a420ea89277d6da438dedf3cb2e8a657c29df9318e13d042859f79643b3a265e720349719ec743e69a152061cc6b3b3da45c0f50e1ad0b888875dd23f5413ceb1660891eda8c4294a8a43580a7a0406248df2183249b19d8a1d179bdeeabb1ddf3ce6bda84894be20160209374f69fec29938fe217aa02f6b979d9e93e26f49ed5ae8c69609506f7bdbb21cb9cc6521d2e9c738224d20dc7a3bc4b8ee7bf696f5dbb9c3352aee739639aabd800a56591841dab86b49de22301d3de698b03c2eb1b310f84ca42e9bb6ab3b22c4b6c832e91e9b2ba7844efa8ef4e2be829401610824e33b440d704bb861e23b39345db6e394931311c6b6f5c2680911f0c83a0cb0b6df7674f691edc841f4da30dc6237e43e882e993586c44ded6422b2a9dc52548d52ec919dcf806472f48804a11ac7c511423f20f7ae6d9c65c8694d1e94d1e970bd3933396204622c33bdabc8050b35eca2c1d8e4436e429e4e83eac9aafb47a5b0f3654393023a643e4a4d61a2de5e945772601240bcc1a7f30076c120ced526559aff5d977274be355836c4c15819f25502116cbef787a69a8e48d06e99cf41ccaee086e5bcc1d3e55677236bf35c7540df936d6af2f831280ceaa735128f10b25bbdf6435fd6dba974042165a801a4a5f4db7254e0327db6afae2db40c02ab0ea6d068005ec66b24bba300e87adad2969831316af6eeb4a7cd8bfb602a72c334f1fe1b6c914479d22bb0e41f4694bc74b8c4668eb356a348b6bea2ab2449b17318777cffc491d084e44486e92c6331827422dde812a639441d2b68605096184ffb664e49a3bb05bdced10a816898807f5eacdae9513f41233b607966ada606f86df50b3031504e7f4cea8f2a6e6862ebdb92c9d643d89ac1f15317bd604c3e29fc462a1ba7ddf2ed1f4992694ac97f93c0bb6b2d612d6d7a217a1ac206cc64d855889e2e1309f472ec13435ae5f315beb6bb46259bf07b5ff90b09d8b437f800b7a46a7ffeddc11da0647a5b03157404a989dd7a44e80f96a5719c9787065265fbd58744ab273769fb22553700f0c00a5562b7cf3437631326a0e2906651926365742d8d00009845e1a294374ac42f43ffe7e399e5a63b7de715b22ce75f9158402c5d1c997f9df945b7c553e847b0467b46707a8302f19f87701031563159f50f28573cb8bc162aced081219757b3a955230c26f065a04e8bfb10aeae420c7e02f402c4205a4dd65c14deb4c340007f45abe6d81d8eeaca8eb2e33179f58f7ad880d31d1e10d568c2139f84c95436c72fbe0f54ba83a76d3b47d70f489058ff13f77df26ddf1e8ccc0d41591b5c20ea52a4e83fcd77014b94879aba9ece901873ba630dfa3ec60d9a3ba7927c20bd0b22de23456b93b4e2fbb1792ca303e6d37b02175838d89008c57fd3b1fda5862778491b0e73718f9084606ea8ecbf17a70eda435f7ec877c04d232c08d65d917386f6d6e2750ecc2ed597c6a8bf3685f10c43f0ab2a02b59febac7e7b0c7a80913e6abed3917f4764a432c5095569551268575788b22d98f87e26c0ce76a66c46d420483ff0e0e299a55eeff0c33ca41c70d2b27bd47c8de75dac1712ddb5c58559eff05874f90d8846c6ca7ffcbe95185c233282d3ab10a2fe83bda4906f4e4bde3d33471ffe0e1284ff586956c7690fa85c1435d73b0901772d413a502cac518437e5fffaeb5d2980a32d5a9654a8775a3772a3b7120f7bbbcf8727ce6bf1008db85571e4f9d4afe0b6d608de548c6d9801fa2d86e540d2236c052f9885f4218fe9988e672a04443fd8b9caeffcb50aaf9e86eefa66a30c8da2ec69e4ff58ddc6e64f8ad8b743a164fbf3d934b1856b0848a5dea6de4882ae3b5e47c5b7f9f8953d89d94359b7f5a34a50e74bb75e91b688ddecdd746ef6fe445426145fb1300c8451a4ed5da56df74970264c0967786cda6c81f9f387f4e02a97bb8199f16850786154ae57ea38b4df9d530bf9a85749341f831f2dc5853315a65f5683c31c6a40a67109c00d39878ae1bb9784009956d231217b2b9368a0499b9d153281739c7a80dee4172cdc9d2921b275ebcef1c8d2ffadac0a67234ae44d0f972a165413973b507435655eaf434716bd18ff321d0adb18d5bc03050fd6031ba318af09517e1d38d3e1030315f2085609880cb7dbc1da5e78353a1819ffbc9c79b3e73792bd84ed4e7fd344faeb1491377cd67895a940cf80b3ed33865f4c87bcae9d6d3fe8fed7d391006fc08141478ac988360b81716e7e49a4641c90264463d9a5d42163539bd69bd3065f138eab08d7ebcaa43521fca540423e19777f4d09e162f85948ec4d1422d0e1c3db79d650da8b3b18d8825a407282f67b0886a26948fee500d6512ac1cf16ac0cd0d96999b050f83873117f296a30bf83768a56a0e911645532627729a44a9ad3b8bad531b7cf321276234897c265081fd3f967815c200043434483bfa4fe124b986361ba2fb004b1cd4ed70c383443762923fcfbc0d00dc1c9aef1aa5ffd4358d58054caa5a916e36616425ee677745b24ebbc82fba500513eeeada1516c793941c6c8f7654b20fcceedb089c1b4f0abaf12caa2e045102c3e9487c52b1aac59f8a19ee576705d4b092a9ebfd7188914d71ca0e34d837467fee159e03e685bb305646da44087f147066830c7bd07ba3c170cc9453617f1f9fb07da5b65948322a733d897698624d5d2420dfd0e064dab1fe31414021cf968b5b06e41c7d80d7977b38364abffc68ef858a93f43b716a276ea40c914ccb22ae5f859b5ade943aa16a833acd47f4d95d8bace19d8854a06f8b108a40130ba04566b3b3125f7d95bfa0f3229a9afdf4cab3e8e051c945239bce60008ae7eb4cd6cd4798c1211aad38cccaa0bc7a7e098ad952b6f754e3efa56217729f915ccf7084ac22805d1b272629aa3751fe7624a801929c676a71190a9b0647182a88a40ebfad781dabc035a89d86117e9543ed478cce67426b7013bd9ed5f8949ce1bd27e0f35446a4c4e1a041aa9b3eb454a091964ca52d9b42180247e7b6a072cef508881f37a57ad482e68f3e1ffc0660adad216eff2d97ce26f47835661bcdf94f729b4a74e995d6bea74869ba7d13776a90568294f5446c0ae0785e174355bd0e926359e15c1a35a3f96845315170332e31c97c26a092d49e975ca02f0be2baabfb67979f7304722003ebd9761f804ffc4c64ed900051160174d91c280f6764aa573e19cb1872786ed871b1db3b72e319b6d789635b2abb017451aeed391f5f0bd87af0610fd5d957611f8c375e2890bfd60b9b37a911d9166fab96a5d3d017315fee08a809360d1d026d40057be88a165d8bc5f40ddd912400443f1f37653176bbcafc24aa47873cc0ad55312160f677c051cc5da4e549df151569db359e813289cc8cde7365a81a69639d8eb4f40b3c3cd4cbc31d0ccc9f8297e92513e5cdf79bad09fd1ece6c1e5d72b167e216c0073f8d060e19faefb8d3cdbaa1a3ef7c30f9647646620b7a592d8e771eefbca2e9baea48702d83d1be0b31afb82d91d89208640880acbb6545b16bd9940e011611c65de1263e4dfd2508359935aeda1b384803253b5430e85eb49d1e63017e5ad9f8689c206427d72c75bdf46026ec507b17a1089ec45e9814218c6c0ab6a130a3cdb7da94e43cd27b3245c73cae5e8adb5f38526dda695770711e44a9f270cb12392bf30519867ffbd95b84b324388f4e73921e15c782080e2dfc2f66e58571ce146eb1b27cd8020f30b47c0173b5cec10eb8f60e76f5e3120b77855c3cc9c00edaaf2b3fc7c10e79a437b8d16a09df434118a26715fd66e3ae5000263f0788dfdf2e30ef190c5392dd4b65f757cd55b848d2ab091d0586afeecaae5266d94873353f70bbaec7b94dedb99794c202282f5e179702a31b2032de44090878b9f27687c8de65458e28f2c506ad73d4a80ae56f38a157b6e4e641a60e9b4614ba5b9fec4f4d722a8b6f405221322e28f1fc81f746067963cdcee6316d614819dc92d103d8704954f4dd41843657caa2e650ef524359cba7ac4153cb8b408056f67c5188caa20165bc9d763bcd100cda0c695bada00376b2bd63a62b6cbebfc35c5bd01fe6b73b6618137c53eaa3ae9c7cdf610408da17628c744b92e0e89563b0e1a0d3a903f55ec03b93e0e3a8f6b44e3169e3cecc77ecfa60bf0ceba610c27e4994b02db437b7b30f20484304109e4a208b4825e003acb9f5232039df566a353fbc4c9b20507a3fb956eca5640912a1443a61197bb4935490f7b42e10754b4846d81fb4723da2438577671c44a53128c59dd015daca4dc48601c700f7bd5407a0fbb8a80240b73003a8a478e2e426af5d90d5bb15a2708631a3588e37c2bba452920d76181dc054d969c46854b9ac4526ade2a6d841ac012a75e68f1d369d6581d9c26a10c9933a2b8972c1dc2c5ef1812f5d772abbe053d179a753f57f04bbb3c959d90a8c451b760afbeb7af669233291fd4fda0d45ee99985ee0c7aac9e4ba3d5ad1d8707385aad019d1b826942464287a930fd5b45fbd0ca644356e87d4223d0636d2ada83b7228d2b424a0a0bba31a246ba452118039d58ab8117591a0e40105da2dda462646b234fb526c3de6c882cf24469b8b505ddda3dbc0e8f589c05703b0b420421702d239cc7052660dae3461ef9d61284faa3e021a69d77e427f609fb8a1dfb1313b0226ddc1664dce104dad78d6c21965ba5ef6260d99bfdba750179e9c6e6fe48dbe9134a2d318b51ce05316d652151254e88b19e97fe2040e98558602f055562b246dda17a8fc6bfd241c0daa007a4e232c24654fbbea54d8a9c693ba86eb989f12eed1566a7aa8b795019728566b5159659c8dd2049f2f38dd819152319554c508b15d85d69ac0ae9e5d551e84919aed41e195d4a78d4c61aacde19f8acb6956ae09e32c3ee206178795082549784158040ec2da298aa8ee88d37e8fd71f825e8be2b0830802867d5bb42fe8613a5c0169249078e967b17065b51803ff08683d59256135522152a422709ab722d65f6cbe553814f86bb80756bc3c3946c9f7abce3ea7d70c804ef531b17ade2f4f21f3d2adae2f1716ff43e940c080e7a4434e93062fbb6eedc752065f417757f79b0d0943da1f4cbc53dd07ac61338a6340e50f9ec2c4ba6db4d285e37e87765ee3ac1dda13d512a46ac473d6907f244ef2400745a200ec1c863e5416c549d4be7dcfdae03315bd6231a8c7e138534607fb45d245f4cbcf2582e74d3c6de6150c7f72203b9be5783e574df67826f3eee9439b3992d1ea1e19e40865a7178a54ee4612db2b765066d3435f2d498bed4172480bd20e1df6d32e1a689909f464a8213d916c53eb6cb73fe7675fc131813d213491206e286f06be8d8bb225028bcbd1224c3422c54f1cf62a0d6df43be502f16cd8174bd61b5dd802c65ec3bbb4c0af8f13d5bc3dbddf89613075094eb337f6a02a347cd369277aef60672510447024675939c78c68b5ec78415beaf2a9246ed29eb642c92d8129a0401363e8523e6195762b3e88dcb2a1b97954658bfb8c186fd98c63b452fe81f5d2119eb40fbd9a4018a3dacb6e6790e3930d9369a5c08023394b48278900221371955ccb3ca06802bb0232fe7241e27e42ef6c5add4ff062f043efb41fd0c51def906c0e4b230d19620677522564157626538a6be4148267142ef97663ce0dac12e10bbed816504f06b8ed0684d967177377ea0cb4909331150d42b92efce5abfefbc7c9420403c58aca25da72706ff24cdd940bb4c78ed21f56d7e512ad461feec04831fda26af999e367c9a692306dfa2c3e87e088c1990ca7baa75b615fbc1251202fe9f108674da82b51328db082e6ba298367a4af744b74b2de8df079e7f50474ea8be5638a639d601a3add439179448135f79e6bb6ca9c4e6879c90cb67bfda58b30f97ab2f6d22d5d731ad03bc28534a9d56820637dd50e60150e1ff10126399930686bedc55d77838fe6ff601f052256fe4f463e832fe0ab07af6ec8dfeda5cb9a982858650977ede035186cd37a8fcd6c3017002c0a2761b37c88b324bc2789409655a0e9563cc77ee91c3a515c8379e8a1bb4da68e4b28edd4a844d6d8886b2aee2356bb1744e8dfcab256d55d10f10551049ebf13ec0ba09c69e6a4a06c174b130963e6b360b9892a4816423a6b5a050ea494dd8dda773af7f0ed510e5e67c82d78d657118a9c23c1433ea0c333e5b743771444c63edc84c02a84a7e449be2eee2534add49cf3f100f50ae2d02c7ae237a326fefb043e4dab5464c81f6f2d933a0280b1edd4b3e38a4ec2a712d18d45f91e43fcc15255eebd714a4e117e8dff15a5136a0437d8135c78f7cb87f06ce18e87784715ee8489d41eec49033c99e214fbd8f1d7f0000efffd508b4479be63649592ca64eea62b382738c45fbae1945f24feacd1213a3fe8ac8b03a1b383cbf93811dd13bbe195644c30c000bd2215d98195a50441cc94f8f44d897d6611da083a7482f69dc1f158ef791a04282f8c24a8aa859bb3fdeab332e565e282a8b11a5c8abb93bcde8f58cf68117d7800ecbf84c16c8e458f06fdf6730a551d49f03d1032a53d12778895b8285b10139e35b68d96c11fb6e6b3f06d13cdb85c98543c9e2885d64862a71e19de692bb7b960c1a2fcc98072bd4ea3dedb22f5fc3a1bbde2f7fb753aaa287b3a0fe909a3d18b1da2180c84b662de646230fd8188356c6fab1ba2fb429c1d454c814228abb35cb0fa9662a5146443f1ff01508b1970876a083be050f0823baa6aadf38ec707ad40b98f2e711a29bf88ca2c32a331a4ac5975762e45e953b863e826312853eaa631a29982206f46b0e9a44d0c5b1952928d464c33a385d527ffde0dff2d3ac3d8a94c5b8ffe37acaac134b5d19c5d5223a91ffb30729ca6364d14bbe68b428de845951b317a33dcad4c586088f84660155502829fd9aed878df1ff9e2d57eb9a79221b9afa5afece26bfbedd5901e57c90a618d8e2544d0ca28ee5cbd22af637185acbe63d71c399e312815bdffad0c79a8e920bcb5895043c7d508d96ea3225ed199ddd57004d92bfd01bf4c3e368ea60c61868b9f22fcb5a37476fd724c8a70d6cd3907f3c2b60c9585f7f2cdbac0d1b49fe30578b1fa7f090945aec926be4cffa170db61a5780795ba41164b1934b71ea9e11339ed29652d42a6e74681a8641b6d4c9e0863543c6005e93c94f8d9bd291284aa0e6e183671ca15191893b22494db0a32193d2135fa6977dcc935fc6cc72dabff358c5476dbaae49439ef3873c116249292bd5140481b139d684a00844be00158a5e1f16c0ce3cd75a78d319c4496f2ba62e4d6388974c744da645ff2f5ab452b3ccf478609016876fee5208adcfb40afbbf015d42ec88f56644c5b5caf687789493edff44f68e85acc048597aaf6f48d6963afbb425f7d4f4a7852b5a8ab76caddf6a36b21e0fa4bc69e6538e7088cc4b87c77a758e60a63f1e9220aa1374bb9a86c1099104312f0301a65b2614994a44e7c7125736c4e6e180dc4f38b580561866e74dc2919604a2405de2141a3bf68817df40d3c8b43daabd131f53807dc8c49fe289069b72186cdc3c9d2842902d06162d7a64b21c3ee7daf0c608c2592c58c60229d1c0868cb2791cfe019df4d509d26f199794528aa712b1d135f229f994cdbb373f807412e2911637682d228236c5e6aa8f92114315b2ca2af9a4d3d5ed47a293dbf2fcea8b00d812e13dd7ab04de8ac78d0da7010d962ed0b5c64803ef5c7b2c262b4935264fea53d2fb2e19b549c8c69d45635c909bbdc9293f6eadc0de64c13d0b5939a6d30ece9484308347219106c5c646d0acfca7207668b038bd50180b61b8056c84f0a1e9f02cb3c95aa6e208bad3202ff39cdb933c64304f252f3070862921ea6171369faf4e1a57dd6b431722cc2ca517460269450d329dcaf0e0632005302351ac362018aa42e622f9166378a1621686f8db050e9fda066668a90c109941966eeeb5a982d73a8310a9c0d88aa6a09799a2b0bfc4489b90549677847f252e22af290979ced1755ced6fd97b6498957c1120d190368b9f01e6559c1e8ec3663b529c2c3e09ad053612e663a5628a2ea6fc046b418bf231d5d7c57365a9e28968b04330f9b2647f67834c96800dc9e19b92e694dd7968a4d12359d1e96cbd7fc934610eb172029c0f36955962754715a89550884fb77a2911e21c87838d1bf987a6f84754e4914f67bf269e984281e56a32b2e251a9e1af07312233ab9c3843c2f58c4e05db31c8dff88fb5e088704327473a200bda42e1fa43fa672823c488b31dd90e21896515c6654dfd7ac361474398be6e9cacd45c2c7b3b8dded4c60fe083ccce9d9e22418b89fd868913affb144a840ffb5e9ca97281cede2c1661ebbb144a17c7f0bace85fdee8dbfb45657b5dd8f21eea3c3bda57a32b7f1f0c5dc1d7a03909447e1a54796883c3582f9ed17a00a1fea0aad9a7ee391d88f85578a908de1885391048af06f425ea0c3eca24df0b501152a220f69e43c099ecaaa55c249bdefd30c84f0fbb23ea3b14d1db6206caa1483591aabf5919cd0779014204ba25d538e33f526d6e065cb00d3a947e1076142f15390f047b264e23183bbfe9bb917a4d317460aa2a5974d124058a2274eb2132374dbc079b61484081a91de0476d2f0801817119817edbb294af05a7645705365702b0334a46a607e7bc4c69ebb976980131a1cb0889757ac12fad530929d250feffdf23b927d00e022389d44a62df3f593cc1d9f4f17eba850e67a0770e8ff08fb6ed650e6f5e3789d2c31e50781e2b23135529b8a448bd1dc397cd0da31148fd17f04d88b01619fa3a437ad9bf0969bb27821a89704ce3e40c7c38b66ac38cfe13685f9fade91845f3d6b4ece6733cc16fd86e8e1bcabe5b6bf459e60fd4c5a757c07a53e2ec6efc5e6237f9d6c549f91414ebe6cb12d88cdc6a178115505a34b2a94524588c928f37db40f6bf7b37f4076ec3d1d09b2883f111ff71a1439d793c77cfb9f0c33ed45c1ceaddb20ae9a0432bcabd85ccd45ff0d6c04dc813a24e8681c893dfaf3e010d000f9df9025aadec73c008e3845f4fb0a1cb5dfb1ca9d1ff04721b6a46dc6aeef312b3055cbf9e1c6d289daf2ca40fdbf0ca1ad39c5a0dc480e27316f5edb6a8341aaa93e531357e8157e5b64b3c297d68632d83a064a4eb2049a3356dbdd662a115b2e1dba80f1221b3bf91e30c4c9617084d725b39c8da73be9d060bd488182cdec350f4c62de6fee4321624ab54de56ea9564923f51792ce7d955858792007717ab9e5a884ccf0deb87ff9de7f4b9a31480b44442b88304eb8853824fec1c85f3dc23ec7e30a4730b746d48a46788935211f60d7b0153db1c25f137b0cf33cd3d6c19bfb3ab0895ee3d8779797c46b3f1f595fb0f7c1dd3242a4afdbb1d8b06ce3443adaa36a8a8d15ccd8b338cf5d48c6c4d82a16d3efc30013efc7a3d7523a21251043205ad226d383b0a8b5b68a7e44cd337faab371a0dfc8cf6d538d4f5c082b36bc4e79e20ab01042f395808b76f19de364850f6a58a8937cf0f60fb01236ef16004d9c4f7724295e9912f591e21f0258176c830dd8a5054226dd17a950613a5850ae2dbf3c1a847ca85259268b184865732dbb0ff2ee9060b81a52fb81e4976c92f14cc42045ca049cbb9314db1bed0796396fe2c93ddb03a5689bee9d78a2526bf70fe98d50759962d13d93307521855e2052ee2428637aea59652be6a08bf701ae89bd03aebc697f936021e9900ff69c5fbdd1741c96c77e03aee3ee3304715d8942b1d697f97fcbac49ee8ffb620fe99aad2e6b7c1c85a5441a8d237dc069cc32917f8e84d91b092953a00309a36b6eabfa5e8f3d1fa9f32170700582463b551be6498094c1aa3c1350a1c24c889c8cd4ab5f043e0962f9038da8ea145f4d880c35e078588338b6bd29103ac3150e52c1dc8cede213c6609d04e51b37a468318860c7a43c9770b52b56d559581e1eacbb5c0152b131b9b181da220204076b1ae32ca3e28372fd8c9f4372698c84289b2be12408a17e8aa20c8a72288cc8961a3564e1cbff801a204f02555e1538d568b5a6a9810fe02acb427a7c5859a2bfcc0e08bba5d2a033ea234a92f119a228eb6f8bfc0b993b07eb2f68a475040e01bee8d7f6c792637ab252ec08be6b4b68662754d66d37cf6d05fa37e8858634550b51da33e330dc9bc67b7278e5185997ee8ea315934a081f8728bc10000fc36d6e3ca970e2f16a3d90fccd686ee421eb93677318e2600f2bd185e99946283dea0a002662f49e50b1682ede3096d6879e3114236a1b6d8482c082ee018ec3a7a28486b0e6ba5b14b12d36f522d38ff9f25611b8862ef9489c8b3038981b927b604bdd03b208d73bd723548bc2519591338eef661c1cba8ca2f4d3160bdeaa21dfaf440a4a742aa4eabbc8cc4c6938b991c5402afc7d9fa7a56c661b63170e42177325896c3fa2fba0865cb7306faca8bdc56b00c5feade2f30ffbe08e72bc11b260fc12b1ffae87a0a2a26fad64f7316ff578dd4dbdd9a99bdc60001828366a5916418f72ecc6b7a7adc8bbb5dad9bc1035f98552d7fa657c29e612d898cf07806815dec55c781460002edb6ac3a3060b310a663dd780600b0019363510114a203ead45a945920cec24a075f1fd00b3ac985d1d841d9c327d207938fe123091a38ef60b70ef480fe2ca167b287411a0e76d48b0ba3437772ead78059dfeb697eb128eb5b317df555b08bf1f4e7be234c0b43e210642364efbde5de5bca2453f008ef089c09417cfbd02aa8e30d9d8c63eca1715380dbd7b3bbb3bd0c4bbb596b619ee7d96de338ee529f39414aa90541109c370a285e7caec9662f537a003cd79644b5f3122848fa043c2f8182d983ec81db33a698acaa2643979f3550d949981aa8e4aa9f0d54d2191da281a6d34fef21d4d0d9f4230debe252a5f649979ebd1b10a69069187f59b419f119eb211a369ba961d37bec59c3e6f44d8cdb491a8847292c5461e75ab83e1fd88681091850f1ccb072d6151191273291b0cff418a29c48ddf07396f3c84c152a227e3a12b1239ca75bf6ac670d54cea379c4434668d03c3aaa324283e63c0ae2fa4f6f5a43f58c06f99021c6982d4c68aa70226d1ca520055d5c0942cb1139a99e9dd23a24c039320290de3ed3db3793f4f61baaeaad14f7e45cb9b334b261dd90cc3ce761776b654a7aceafc8f154a566a09ea35ec29cd5aae7bcecd853e7c41aab5031da51b58aa59e73b11fec90b825557acaf992a57aea5409ecba50fbad882a4b799655bd6e2273a5dd29409d2e5d74baf817b0323dbb35f3ec1d4d7a768e7dabecb4759ab8dddd6dd9ebac683a67cfc902167013974cd66db21e97b91432d8989f2a5a0c8762a8f71c4527e54a696d1aa452ba825ae90aea9561565a5d94c995759bacc7656e858c4e590e4685d7fd487d61155f14ac94d2d5ce14b7faacb369ddb8cef3587564c6315dc63ad3bec5ea6cf302d01b52d17bf0c66eb11b1e425b4c280a9b2d86d35d1d3f75f64386556733b8299b1bb029d80b5653ad8dca583d4e65bfda99bad4a98c466d1a8fab69aea41cfa20d54fdaba546fbfbcdcd170a7d7e85568a7d2dbe974a33a4c5cba8db48e3d40255748c55775efe7ef74707439ea69f5704534eeb81cf5763af123e7e7d88371f417a5e3b1a8f7b215930e8b3be7f8a58ed73e76e4f6e97e385803f5d390b5e496e1d307d53c15b70767d8c5833d297765dd308e762e5860f154f41847bb47e572547301724f39a99e7242406c1ed4498083bad1b60f8d22f4e175b77763f51e1893a54155c641bdc372cb290b52a4a8562345a2a6d32a3aa0619406b92a0f1e3a74dcb8d1336525cf97533699aa081567055c3171673371a7f3bc94524a5f1f3c1dcb2150dd954be99c8ed374a7f7a0ae86d12bc42defd34befbcc2715cadb5729b57eee15008634f7d3a7d51ca4dc562b11578603a68377efe08fda4dc500d54f6ec27ad81d8a7f7d39cc197e1ec190dda7c7a33d1a0eab3fa6a846db4d487a90fd8623999a6a55bc09286f33527a8445dc883200d549f3a0931a8a7524fa766ed1bedf4699a2fc35a43606e0c6febe9eb83fa327aea2114ea40a8ff0805c162b1d80a54b118498bc5be4a01720f08e3a1061947f5b23dce519d7790c03e8c96ae6723921dc643f3f6d8cd6e7b8faed4bba74e2be03ce24ea76bd3813be70c774e27f706f8e472cd8633ef59f15cb341eab96603d4b373cf351b687f9f6b4cbafcf4f00260ffe6a3d21228072a5dae067af1d7327ef310caddfc039b071932fdc57141226d08078a1c71fa6ddb98cc6fdb5635635c4f17193468882bf69b874e839884fd08fdb605a13e9b47fde63f9b0b6dee9bc3b607e18728905bba7325c217019bc2e79e6f7e69500ad63bdf1cfc2cf5713bf2277aa315bba9d4dd9d015128b23f9d7b16198a5252c368947f499374a48c39c194269c44b144b653291ac45d3ced5003963449c07042b6531a85ead6a8893dfbd8cd2e7b8ffe009d5edaeae5f5d2d9cc6f54dd58dee8f9e6c69267a36ad9397a23539f52c7f778e34b1d9386520ad074ad35a0ac86b0158149d0e7ea842153df0eab1ab249519f76d94bc8e7a187e311ee1b372aead3ab1146601ae4734fb4d467e4d0ad11b9f9c6297d4fe7943a68062d168bad304403d1b82e0dc435264f75aae4a87c77b49a9231bee498943c7dc93d7d7350df5cd4971c142df5eddb6490b2ffa8cf9b87a3a54242f7c6235bc9c94a2e566e54418ec0f187fa4cff4617f599541bd3469b7a33df6ea7f8f957569911e9892f219e7ffe8d472a12f559fa722651d1207ab72ad7f58d8a527d6f543488c9ad2a86546e545c12a5959cd28c63e294b8a599449f1ad64a502d3347d33a2b94c651718de385761bd7b5a777cd6d550632618ba72643ec3978ae29f1f2e0734d890d4cb3008df1de5cb4208303196986a891a5909dee1ac2c3922e62e0e1cb17723a1494174f5b66954f174d889145e1456a0b72ce90a50c2d9e62483489424e77715902fae93cdd569fd6d9ef0d0fb188001b487a84a1bc68410b52854e062148db08986eb540a911825481abad213b2e55635441aad065e142762337028cc89710eb9e7bd688ecc6d7089d7bf63582eddc1badabd02d51a5fb5a0b722ba25aeb484388bb8d08285d3f2f15addbf1881d8fb01266645842aa40ab203b3722c1863502e804b915519d3484b8948ea1963150dcb04992be3decad372d40ee2979012e2fc3cf59d5e0d960b058be123df0a007e825d3687036b4e17c90eb07bb8e72dd360bae56e1d7adba70ae8878300e0e6ceeb6400f74906e22088e421d4fca92a2e33a630feac33910d101edc3b90b8ae79c9bc1a579d093357637f0e0217ecead180694e7b7dd06822048043a282507b444d78acc0a5961af2010da88c181017237cf5ad060f12db7a28de7884755aad0b80183575272bc47afec8d387f85c3eac6412fcc1127d912a5e4b8cbe5355c23539f198ee464ce0884fa741c8b8608a461f63aaf6cc61e0de33cb8c1bb5dfed2b03f3a6e42fabcc50a99af37cf5bacace03b0767684123e6ebeac77c29393e57487ce7abe72d5666df39b8b14a113c38026918b7f2d18fe39f07bb01a301b65a2c160dfbd9d0412f07bc216b884cdae4884ce274a115f9bbaeeb4050a881a673365fd7e44b9e82a76e8f65cad478887ae18995f2c44aa95501254203aac8c23efccc3ef685aeb360d7cd1342cff3bc957fe2e7a068d97eee81111275ee75a3732c16ab7a5d188621cb57be0a576eb72f6eb882b1fad1d330aeb2c29015b25621087a6118dad0ce300c4317cfb7fabe958fd0ca6fab706421dd9e950fabf3a08337e14a9c1f861e86ddad42d079f0b3d65af6a6e7cdf9857415b244feb08c32aba732a4ec0803c170fee877bbb259fdd05985a0f316fc6e3e9c30fcf83dcfbdfbe5e4e8f084b6894e0ea0652deefc39c5fdabc5652a05141cdf0150909d450c9a685295c4130f3e90ed6cb7d9260674f63b77f0b398a81245b6084a41728d8913c8c8231c194b55a40aac841654948e742f205568fd40d2911b01ac050c104c243bf185136648ee8202309648900ba938936861a18a6cf1670812ec2cab34faa81ca450225be4718154a1e73c02e41ebb7d2e1eefd968d84c134bafb8397bde39524b79c44beb18b264b71d066b1f1a0456f168c6aa7cde5c58ed832293dd4763e266583c9ca6cb75dc8c27b7b4944a01375a41af41e1b86e04c7db3ed4614f7fc5825bf2930740b6e755152b47e056bf41ab6f0f8ab57da6c7511606178936b0a101cf6b47c7552387466b06cecdb561ad42f0f36cc76d95b614ea3dbae9c8ddddfd82eacd4ee7a4026edc302e6f58b6762ca7334ffff6f9d35d6a387a17e75c12d473dee2409bcedbbb9bd61d2814d53a92c0fe2c4261fa8bbfe7c401e3965787875e1fb47b8e82732b9fce390f0fad4cf8bc9d73ce5d1af010cbcb9ee75827cceffba0b801ebdb59acf7074506d8f8761b361e6cda9a197b509c59fe135f1f2ce0db17b08007d98ef35fddd85f7fdb0232df8030a50ced58b8257709ba43ba4ff0ed30333f1fc8dc61bd1d041b64ff793ffff4528399037ac90a0abde49e7df210e7b6f3b27e38c228acc697b01a4fdf470f19d70173c7e6edf3631c9c83f34ea49c63631f8ee7392f19c0ad9e1bcb1fe61c9f33e728ad77ee8086f57fa31091856b4714fc39ff41803f81d7a8f9ed4739be88c20973cde745fd2730e0bf71328ee99cd80fd6d08af547e0fa22d76fceb4fe6e24c16d90200d6be187909c4ffb5dc98d42e6e880cdb91dd04193dc605e7766b63c9179def2d4e54ba12d4f4a5fc3d353efefc9d178e3aa73c3b69f15db152ed76a80e24b1d3c5a4807f5929d739045b1d95b3cab07cca7a3c8c265e75caed1355ee7780805a63d09502081fdcd8bb8efe7a8cfe74406fc369ec06bd67c1d4158823273f0f7686f8721893aaacce496d6bb851a0d49b086f10b7a0ca5944ea73da659b0f926b56d4e29a5fd85524abbc7504a698bf969a91e4a296d312d4a29ed31422e293ae7a494523a9b8ae17183f4b4030f30b32494c8b2cd6655956971a14a6a6a4a0a4dad810618269a4dc6258ba39f5e448b284b814f1891c452b5c110691da0862e59106931c1cda652940fdc4c8ae36e2c16f352b4cdcd63b19897a2cec662b1a20ecc44ea6a54783191c22f53befc4024e469a0a2cab43a3589f0d407df972960befcb83af832058c8b084ca70b1643c03e1a535143c1476b019db5952198d0d90f8d295f7ef829eaa20e43574494d6392b0c733a097c6edbf43e014f3314004bf09b79f43193d01a466b994d35f2d33ead540d33fa49e9c8d30b48559b61ea07f05c73f205a8695b70aa073da1b3b445109c39aa53af28b4d36a1b16d229bcd5eaadeb7123dd38f690fd368c568e993ff03afe172005c11e4d60eff2f38ec5b29e8d377e2211d1047e960df368423b7be753e438aef5f36dfc441338e7f7c2d0ae44b65ea7b3fc7b502245df26f07fe32b84af23110a72e0e6d337e7baaf8e26b0f3b66dee7d2347bd0ffaa7ddc4522c3d08b05f6e55fdaec63b32378cab62f9b54fb1fd134b2245cf44c26f13d8b9f2ce741eab09ecfce0bfeaf841bbbe646f6757fad0801f8d899b61c169ba74b6999a0d69c4163898d93c432d65a1b5721c0d55744add045fbdd6f905b86d63abb6d3af7d2d57b7572d4f68dfed8d3f0d136a981d75346c7ae7610ccabb228b6e16849eecc7821092932a5bdc90c64c17275e3a043f1c71e4c41549c83085ebb9e6e4889ff15c73b21405b496991b6d68ad738fdda132cf4bc76dab3c5ef661afb56edbb6b9a0769e1fe1d95fdbdedab39d1228a880dcb37d9f77b57eeec15c97b7104cc09fcbd57305812f040cf43c6742577b1582099ef3aeb9160d775615800c63d8d679c90314c29e6f3cfb0d214f7b604358a2f790f094c74b78ea3c1c78da727de0a9b7883c0da90d8b9e7246ab5963f0bc157c37edd130201e58303a485d4ccf43a29ad1828e1d4fa0194384145d8cb04296ae77e1087461c90a4659ae54f9612a298a1cc2f86c8f07401cd0f32d04fbfe6902c6e561684db27c8de75a9325af0770a7aa75435b693f51426ff4f28ecdcb5abbba18ce1cb384d6b66d61156bd1f6b45ede38e78bb6a74230acc7e7c653b75a6b756d636bab1c477d736e9bd41b5647cb2da9d54614b74b5adee9a59de36d585b1710b889d7f257cb9cdbdb47159c23ec7f7e0ab5e89c1da5455c6f95735a044f1ec39b65165525d88c77468a106c460a046d5a7776df1f6125aa1c73dc6c3a7ba9a9cc96e8104cb3234fc7568ae242764b7bf62f9ceca1d2b3a3a9aa4a83264d159aaa46a282eaf2d4250aea090a0aa928da9c497369f2192a3455542580938710d14f77a59b525a6bddb68dabdbc6715cd775d65acfebacf53ceffb3e1004c30f04c3305cad562c16cbc666c562d9d8d8dc7b6f6e6e70eecd0d0e0ece8c19335aad160d5b5aa290c856a26e11e9541d1dda5387cba9f12db21b2009fad1beb72daa0822b020f1829a30a45195355e40b1c044124a8ec8f2e7b7cd81d0a0cdb9c67e73a10662f29b7bd040347e8ba10cd3115496c898e10ba32866a0a4d1420a1830c103b9d55abf59c3fa1b625bfaf6ae05b7f46f4f8a71e070cbeb49f190f88de4a9370ff5d3ff5c2062c6f1cc38ba2b9783222edb9f5a7ccf17b0d495f22121093f44ea963ff6e7c7fed81fefebc05a6bd9d9b2540586b017ac055311db6000d93798cccc4c99a03acfd61b4b0c88de4df6b1cd4e9a827a9e75ac6ae967678c3b9da3ba609269d006a35efe5467d1753b11e69c28f4e38d1187509f20d46792de983b7df6aceaa91ff0307951a6c79b5eac9d4bea1836ac15ded695262e7cd8b526488eea770facbb256a7bdc56e914e201c4248de73dd7988011031b52ddb2d54044845cb488fe0738519860f2d3ab4844e8c17cd6760f9ecb13bd615e377a56406b6d0e1c0f7387dfb12422049be3b7e3d678ae3121d369b999138b98ef99ce8dd2cd689c4e0d6b1b64a7b74f280d54d430a50e32d23eed3b4ee01cededd3061e1201e368b71f7645732c227581d0206f9f766bef8502451e9a3ecea09ee4041a276cbaefece0fcf412e74b9fdf4f2fbf2f9d839f5e72f0a5bff760af7ed077c6d13369d6c03cb4cd56692acdd9649a4a5b969f1e3f34d0bf929b3dc7b4d166505f6e491bd226db903624a4b925fd44da6661a484ce2fa463643806a13ed33b4072992c81faccc96548263f2f796a84d0bff115e473d099e45590707c3555c3a6d44f276ed952964984d2404a34c832f592ebbacbcedc4e4d251ac4e4d28c89f63da33ed3b5cc6c285244f5848740d0b4f165e40857d5c043d4414084cb7ca58ac5be8eaf193be232df2297f95813a424482d933529a7ec35931a06a561d3a79dfd744bb35076c604d2bc762cecdcd8d33eeddbc8ed039633be79689637cf227bb70bb53b6c8e2ee5aeae5cdbb05603720ff768e7573b1bad30050b14588144062b2041e22cad71618a0b25514690ecd705c436793210fb9c45da8d94d0403cbe6a08e5b6dba5bb25f15002be690638e806fbb4d3a4f2c65ce1fb698cdb801c1ce4ecc3eee59905df6510034868587b0d9a5fc43e50b4a1f99e341f98f0d4168392f42557f970f4e53c9a48e594550a543299573bf9ee2637bea78ab41b692fe1034d4203b11f6047adc9cd79e8073f0a2ec651fd868b02427dc8b265b46938c1d4529f29be84ccea9cf812c2d5b2883e75119a742adade1310b4997af6263345a6cf9869a986b177dd78709df5babbbb44758122aacb2423c5e18a65d34d65466588191527a6341d4bd347693a095255541728a2ba9091829a4a506694096552994b7d060d159f4953d53c9e6a17289b7b736f7066f8f0de1bc6de59cffb3e100c3f300c572b16cb66c5b2b1b9f7e606e7dee0e0cc98d16ad198d1343aa76bb872dad53abdb313f68ba7f2f0d09e3caf904516cbcb3678830d4276b18a22ed29ee842d96618b3c618b1aac7ecc6fd1c6ea878db0c50d5cb4a74f6a831b461d64d774d769edd8bc2ccfe559f9f05e83296cdc0d36709f6eb9dcd2ba36e099d3e55c6ec9e5b283ee95babb4f9fb6c31bb2f768cb73ac4c8c837d4719e739997d12597b03820f68e6fb97d7fefc940c3e3fe980829f6379b9e725a4c0f8f2e7d981f46007c2fe23a485d9a5c36d671d2e7d5e116d1d38c4fa4001473c79cebb0316ee7d5ee24bec5f60af7edcf75cbc1f276ee22a0c7329141043a158bc0775203abac51f51a861b434f2ea2203461d088f3d80d019cfb52525de05757f7c78ea3a7e9ca903a1fe23c489e1218a07a81e98bef4d9c3ece7df207a05e117428254aca2e83a49419e3a8be58fd22dfd87d020f6c9791ccc1be645d5791504f4e9451a367dabe1d2757d33b8ec3b600e9743174ffd6c0f55703767be3ea9b3ff70a57fc94ee4e737e7b1a371f6c8912a424207c723a087e3915005aad24829480fdaf126726512149924f2f3f36bbb04d8a2d96ef68e9d63df2a6dd1babbbb45bbd367d91da1810d271ea797d38e938e93cba986538e938d0d9c729c6838b59c6638e138dd386de04e374ed7c9c689e5b47272d169e5143a814e9f93383a7d4e9e93751a01e0649d3a2700f438754e3d01700a0007d4298d235a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3e2d1c046134fd3ab69a749a7c9d554a329a7c9c6064d394d349a5a4d339a709a6e9a36f0a69ba6db64d3c46a5a35b9d8b46a0a9bc0a6af491c9bbe26afc9368d0068b24d5d13007a9abaa69e00340580038f860bdf6c5a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3b2b1c19452acce6c68c0f3dad1897dbbcb450da84c698b9a72d5c8a1d1daa2be7d8638f228cdc0b9b93623005e328eaa6936ac5508809e1d5947a30ac1ae27003ad3d977a68f53e993058003d74fbd375bf9c8796fc638da5b46dce9a5c7418defe0e832e330e20a20e7d9415858673c64c3f358cc38c255085f8cc69528503c2ff15c8b92c486f53c64e365f85ddacb9fa7f988d74bf0f97b16e2d140349cbd4703bd5c359c7d88071d04a33e3c34c3d93d1ec271e66ce586959c4fefaab35375764cb05e8bfcac5567e43167865b725490128a8839428690c0810f3490cbdb8934500d77bd5ade725ef9b0ee79cbf37cf3dbf25abe596fbef597688275eabdfac08a9b839eb76639ff045e53549fbeba4328d7759c7942461392138ca1831112621c552dcd20862cba90ed4186581a6487739dced9d220170d428360f435ee8cdce88d1b882e714269989238a3d010674b9c47a210c6d18e234e8071b4df80f9f61be6db6d44661c2c31dfbe1af3ed6114647eca6034c802ecd39c864b185cf015dfde5f6e69e4375aa69ba0e7f90f7ecf8d36b10c7feb80855b8b72f46087dda4de15d1e7c659c5ccc15275ee4c969ad78bc79708d5371fa153a822847a6f9e8233599d49beb4677710b5fa7daee910e599812aac89275442d4ae9421250164b4c0420a7a8832c60559ce30194208272c2081b951907941131e920843071734f1c41349b848a9610c2334d470c593253f342929e0881a827c8099b50ffb00c3d4307e9fd3612d7bb7e8f0c41214539cbcb085acb76148eee4a2fcf42b91d389da5eb965c8ddf3470dc83d411e039e7a79f35c3807fb50970eb763314fbd5c3dfb94089b0f9bc46d1dae2d6d6c78e8e7725001d887ba8e0e0ecef7658ea7aca7391807e592c33a39798a62cd122eb0f20027846821ca89345864d093322d2de578ae4919e3719e6b2a2863d21a0804ae37613ed36c4929e9e7943552c3a611d717a114898e93991cc0189106881590db886473b24295cc8b16b8f00559c712c492a7be4737932f6129f89269327cc952b22f996ac9971349c9c7f0e56432c241aa872b5cd4c678c9822c9d0a1596a82a5ebac8aac8526887091da65e903d216506241da7142e9aa2f030554613d963c96612b003172b9021031aa460218d8eb43439a24c0c5564b090e54c02e24c11656267b620b1429673f63389110f5b7688010cc0c0018d12520c630b1800110412515c207d3c9111ab2931431213c9cdd0454d0c35637c7042b2c64802c60c3d6ce14316d2c8698ac674250c2cbec4d041196334e9b0c5122ac0819c3390806189082b98c4408a9c3e976890d10b6366556081a489531a72d2b8305aeeb2cfce2068185e3c410f4948c46c917d21a70d65ca8c4942892921b2e002fbe93cdd4e774d25b6d35d5c74e8a891f19ef2a3a25d56ba4bff9df9e9df9906ea59c3e610b4cbceab0945aa8a31bcb0400662cc5ee028283f5489d142104014e183fe9ce4c6d4c1d1dd64349be302292f489ee0e287353b57b03c3902ca0b4730b950685bd426b5d136a88faa2a81dc536eb36f7779b9d4594c3c34f4eddb96ebdfb37d93f1d0dcc00f5496402246460e515640e54c15573ca1427202755d56c40b6aca08824a88a617a6b024f1650b244a3421d1848620d2df3ebc22db9496da37d9963421505b0269498dcb0e3203f4d0430f538c5822c6096ab8de0310024a2c0a115cc8e00b18c040891b76a0a1d6248aed06842947950331c87285942e8eb60422531fefab0e564b7d2a110e94612a2c60c7f39ade737c89103af51156744ca18a90f6d5e7de370671ead3b9a42e1d53a8b2790ad443ef3185f695afc617e79e33b98d42da57e38b1b837cfee27ce5fd122174eae1dcd13d530f5d48574b7d3a1be672d648c33ad1457d3af73a0f97ee91b1a8619d7f33b83346053801237273ee29b728bba4e3d36b140fedf829fafcf4497e6e83389f1e049c0c75b439f3346cbaced06eb0873669380d457b9a1b6d5ba241d596f72be35872bfd76b84cfa98b522b5cc883a89f4b5d7488e1cb9e1ccc7899fa92c7cf9269b4b7a5069a3e7da3d240ad43775371517ad439e7b99500939bf7e88d8a088a9c5827694526ebdc66db926b5bfae9bc7946b4c31840ee2939d98f00fa37a630a65045c8f44ea1992df8a3e346247521d03f67ffc623557814f2b9ce78a4867f9f1f713908ba1159c488e8f2f086192b62844cd4b314cfb528c048a1460a5584d4f0e929b85e22b8bc7d488d309749707c75fe12327778bc4783acb3a374d98bd0a0975337425b4e7d2ed1a01ca75ece5851c3a891220d7be2aa489d0367c82356890093473baee32f91c91dff441d9f2ba297084624e8113022c1f1881179a48a101d077dc7851cd9a1c9991548703c726487266762a48ebfc4498247335edd8e8ef7689eb9b20e76d85175453aa887368f1a88675539a3d0d9144ef6a464cb97134949d24c9a4a5d650e898b35103f29913d75ee88aa808bcd498265324d020522c3ab5bbaec83f5451807753086cb5e3295f51f5c5d45dcfa2854145edd18644727c8d1110d9fe1472dc7717047e4711df1e52ed1edd2652ec243dd53df967868c753374283a65bd94da214741e17023acf78a44ae747da41ef46219fbfc6237347045d47fc1ce444fe1c91491a22932d91c919229338af203dbdde889ee788d7698836de1259a15731024c1e1dd9f8ca8f58af20a0478049d047a064b660b286fc9c57db18e4e808c9f5a3231b3f5a39e8472c3f0abd1aa13e345cb16cee125d894cf668a19f8651dfb15f2ed732e27a460b40eea9529d29dde8e64c72be89f68a94524a23c0e451fbf41ac0f971bcf158d65a2bdd2847ab4781986bad955e4eb41c57d65a79db782c3ff07cb3c59d4e89b888d899d68edaa6d43650093275f6c99672fd3c0bde2b46d3aee321f43db86d1b277a3f450c88dc175aa933a515c817243623be7c795b9dabf75bd87515cd9755183a55ef033d6ff2101202bf073fef6b0834040c88be6279de9c348461b15644b47605ef5c3d80f3cec3550826a8e3a534c01a16c6e67ade9c2808736f3c6fce4d863037389e37270d61706678de9c348499d175cc5aad4228c17fed5a734ecf9b9386303442d68a0504c83d254ff0d9c12ff989879010fd9e524a69ad9ef7936b2dc137ceeaf8f9099d9be774ea80864d0f6e3cc13acb6d2edc1e77eb6db5eaf2b85cde575bf70b6dbf655ac7fa6018320f79c03f3888e34621ecc4b7b32512d2cee0e7852153855e744c4b90886bdb5d8526460ced092beeeab9467ba24feef75ca319f153a35981816db54a66545c9be71a4da981b8f7b94693d190baeee3d8dea92f5d31eb2aedd7441218a29c647824a9599217a054b0040625254cde0525fbe3c50d6daf0d6f12a9245349cc5c57982731d06280bab51f96c4da87ff5f406a1ffed25e297b5ddb732d0b93b5c142975cd773ed8630d3bbc9f52268977bd3031d7383932ac30d2f6071433594ea8260cc2e6e0d3253cdb5cf352850541a2eebb90665891840a135204bb3bdade79ad2125f9f6b4964f1a5cb07b09c3ffdc10e27f5e982e1cc17b5c7745607b9ea53ac953e58697757313620f310bbc838d83dcb4dcee5ba979bfa74b9eead75a4b4e956e79cb39b99997396dcd969b9dc7d3dd0596fc6d2a86706356983ea96364f7d48439dbb01618a53c3489f3bbc67777d60638b3be3994b1a2e4cb0be6147b1cf341de49add51b1580c095936d4b7b753cbbe3bf42e1bcab7779214df6527f911bae19b87d2971c55836692324566f0ddddadb4d4c47739a9e4f0136a76692f2714dfce53627c370e5fce2adb59dc10440e4da8ac210309982b3a8a2fa8b058c1821223c874abf9420caa2a29a7318220998a2bb82079411661602003b29c4c4f684e54506112660af2883440e040451926a688822c2715104d645e284181171c66a40e2ecc344171c28a305bbe167cd9fa8fe779beecf96f06fff9f7f995f29fbb9008e3bf249480ede8e043f7564461f3f47c79d06b03890fba5d1181b0ef69d8e471b18ec0d060503f514252ede303cc54fb38c1f09e7bdead88bca01c61b4c448300c2a1a696f77c4d277be79ec6b074680f11bf753197e698db8a2e3a9cf2eb7743dfd741a4e447d47ad7600419e4debc6750be68c12b29379e42f57205951f3c21251c630da610aa4265c68a2a6852886c2c511373b5078a88d40f2d479ac06ba903561841451bc28c15b8c61c31449b4600a599ab6812a0a16bfc6262b9efa8eed42e69a3a3895363d41c65377dd2bbe084363d6f0c224e5a9e7d020d50316301011234389d10d6b909072f002c30a54e4132fd4e0f0108df9a508173cf5169d92544412468ca9a10819499f7690e254a5c915332491b483a6522c167b81a494d6f094524a29a5142700fd4402b8e4f1cd4f7fe5866f26ae6031040c434c41c4ecdb7f88d0a28625cbd78a4801518361e59b40b776f1d46d5644d4b2e09661d9e2f9e6f2c633400becdbed0c06dfee0a2288efa820aef81662f62dc475999d8047734f5660c0f3eda11461701308ac5aac2c3dbdb06ab9f6a443142b202e6f8ae9ad889aa84e314fbd52efbe007185836fe75644dd45ed6e1c65c553af4c45bcc005287ce0628d1259de0428295972a44353194e644f20d4a793bce001ecb25cd24e257e7ceab4d56ab731b0e1bb0a19528213770888004fab30794ac52d43174f8d8a98a1efa9f187da0f4ccfb51f96be04b3fcf41bcfb52969405a27f44951bbb9abe7b6acd465de68d467ba9db1cf74fea4e82775bffca47e7663df803085caeca99642724b0cacf9ea2c7c3d2a5b0aa7eabe3a073bda549369a9a8eb521b8d06d920d8677a1394123f9d09a99f341ac42454d44bd919e320a27e551d34a1006349c0a4068ad2405c9bd2a4d401ca7ef67cf955814a3f4b7029244b70966449366bc25403ed09940d51503accd4d19685aa81ca0ecdac6aa0d25a792b3c14a581da49f84338f574d1f1e5542253e60c0d5f4e343f67f872a6f97bfdba57f5c5ec4ceaa777681aa8abb2b1aeaaabeaaaba2a1bb34856d65505a109a8442d1755410b15aa190900000a5315003020100e0904028138301e8a9bf914000b80963c6e469f8aa34912e3280a8220638c310a10420c41c698192a1a07002f751e92c83a4bdaeb979eedb5c708fb48d241c945e54ff49a1368c555fe9f742d849a58b1a11da42ee0ab66e675d3e580d841d6e342d7e06f0ac052d05c128f187ee149acf001e939d7c0db5a7cf9c22e8cae595a53820d13c1d4ce347149e9b9ab68093d4f168743d8dd9fcd3f15d603def3096f4ea0336509f3234030b4c376fff5f16710549d6390c509dd7e5d589399562fdcf90f9acfab43dc511132a36a6168667b17fe565a9039888236966a7969c13c8208d21f24475183ef9496a1c112fb516b2439ae8afa7f4d8a6c68ab35c4f1d9f68e29e055b6be3027017cf18a1f36228f4fa9025b71919e82ea4c029ace74046dbd4921836633a2db83685c538024216c48eaa957f6a79768d966090df8d5ba1e60d2feb63f5b6052904069108e5d6bdda57810a1fbeabd8e532805ecabd8fb32e20468845f083f7fd9ec92b0b47b64e3772b6da6767fb8fbcb18f7460bc2bf80335b1c99722a04bf300ab1086a1752dbfb8aaeac7151deec0a33f02f331ee5877d3f3e830427fd88f01b7bb779bf5b0e193d1c32d173c58dca1c034ab7742c9d2723105c3409d88606224c12878706d1abcdfafac729d918dd39568fca1c61a247170d6d7b9157749d0d53d220a7d28895304d0622ba35fe5a6fc37576dc8f9c8294a3b94e95383d7fa980c3c9161af4765dda74d17fb8d6b346f1d4c5325d7c75987b656bd57a341b36c92d9966e23a5850c3aa83e5f7a985e8aca18fefc812fe15428f93ae569933902a44ce9ed909c6263a65beee84243a7298791958088501470b7e590ade2a9f25380a6a1b4281d45620557137b181695f582d42db0ef0ceba76bd72ffcf81018a64df9b1481421d79d561b4d7f4bf79c0cf3df50261cc1d2a0bee00705382351809414e8d44bd1f2567673a601cd7b2898206b642b0e113a996fb10596ec0ce7c4f9054e8059fb4ecba2151915ddceea8df6c25923e1d518642c09aea47f4ed5ec6311cce2e94037c16ed15078025b2ca351481b1965cc6b20b79a8870d65d310c6e6721bdd780a73a5d5ae6beba6f4f8d3123a52c39139d46d9d22d2a38444c1e08c428bde7b011c37c3459afd1d6774c9ff5107200f3a55ef5b6da55020decebab204c702ab964c0c4fa674bbf278e25d65f07b9f31fe8b65546461db0469a9743352447165b2d847deecfb74926530a6e434bb3575e4daf00d7e5d886fefbc4ca678483c4565e38096abd6d565dc1af2afac32646538b06b4bfe02c0cfa8059dae901bf1952ac666a9c05428b7c550ca2ccefd484516ab69771965a1459445033b37cfe1320d49376722e6b340c3c3ed81cc4c38fdfb7da2e873a351ecc144de01703523d4cf420a0d218d4e0141b5bd2b99e1ddf4dc9b4539165fc927204b1ff38af237a8d8b7ea2d743c498fb01c2711bf3adef2c7c48d0f4a82ab86538a273828884dddb63afed1e02bf8f774a752258f99616293844d9de934009d50747990182676fa1e9d85ee4243531ca5ae6dbd5d4298372b4a7483930715f81da5491e1624fb4901098045f290e07594216bbb807cef25f26fdf1ea5e91ed32938af08222401bd3fcec42122052503a372e11e91eadb9f5502d3f50138c6000a847b02cdd358ebb6bb0c0b0f5ab736226635c25f060c8b42c5ba9492fe26b551cb677550410921a29801b482e1c21642068b6b6a76807657224b247db3edf84499ca04f4e045a70fcf22e1f38ce3f34e67776c77f969ba20ca286b59539cc61257705483f413ae9e80ac709a9ac5d844bc979900231088e68ce3f60fa90af847318b40f717c7a44f29c93697cdfc98cf96c9856def347ca83b1b2838888258004218f76066c24106358460ec284d320785ca03d5d80f05c90c38f8541d0f1121d648f27aea44210d55e7d9a8724c54d19e4c43d2edad0a4663ed076eef05ae0aae06acfa354cc8a03ce5cf74f1d8e3223b43ed05e52e1005f11231b9473f5b966747545962d2b1b10929134bb1cea8a6952236101348602d7af94512ab33db9d297e1428cd3c39b866a7d6e693e3daa068e41218cf7af0d4a09006be841e60cee060ef943e4a35a5da3b06c0bb2286a861a8649b281405dde4fed8e09521bf3dc52953ec3cee2cae148ecbcfdf6391f935af0cc53ab78f1dd50692e4dcc1edc51db10ee1364ac822a772f39a1a237625b3a1e426117274e595832351357ac507a3acb1931b6b8c7eaa4b0e427236aecbd555d548ae1168bbb7a3e28e8a6b6442096e0dd543d2a8cfcf557acdbcecf6b26816059543feeb2e6c11832cbd20a1fe77d409f681c1a9317e25a52083e52a1111887da780bdbf0279b532288c109b1e91418e4effa995e2f4414580a2627eefbb883bfb8fd6789d027f5d1d48c4e6bf46bc758e1d1c7c0082dc6ae534e1717708910ad9f7583cf0393174ff4186548955ae0590d7f7541566008a814450120ca1cc6743416b45e6fa5aba009e4babd733c3ed612293d4155d1fa41cd86bf7d4da53caa46067a259f20aaf17d60efcd1e5b246d783696f5741ba4e5c30c398bc90657abe5445e91b2ec2b540b9b557f302c1b9d6b5dd483d6d98f80b39d83346f6a73f7b764e4b555bcd7a629faea5d40e85a8a0040529842a1b578fac2fbfe9b01de7803c1e0d4eaff3c0efff295f88682655abdc0e6408ac274a68d9fede0fd447bcbc68bdb8fca78ca1ced5e4f058e0c2ed71cff05b092189a3b464830a0f30d6692cf54962cfd0cd6310fa4bd5665fb1660e906fd142f12e3282c977b37938388e17c68e7b4fe97629e9f9750efd65cdcb149308065fae520153ecc548a1e3c505134ef47252d3dceec0d890a01ef3bb0b68590c5018c8b81032ea8220d00001d2fe29ee451cd1b1548be5007f0ceb3a5b589fe850d40884d13d01631ce5cc5ef21cc80f8996c283c6d5e676ea936fc4160f42a84202dcb382be863ec1aebe9a8ce68d356f0cfd45027ccd370f4334cd07cab5e26b614109ec257748b2de36620adbc0ab40c57dc516a8532e1527a8fc63c177770b0382ba8464665b8acfcbff22874b873cd241f82fde52c9d19aada7c3b9ec488b1512efe0b572edc0b9a7dc4c692f3459fd5450b8299bcc30054d5d20557d294054121d715f8e655e09716740ec6720f7117c46b8c5e64aebfa06eab62df1a55865c20a09ffda1c23a31bc37d7dd733d1506d6da4325e3c8c2bacd6d26f33ef3604dcf47dc3d219dc305e1fcc8d447529b66cc15fd06b7e195ce69834ec9f942e6ef6fefa66ca245f8006a192d23b6483a73957b7f1427d9b7ce20c79a7dd4d90ec692999d8326ad0b7bea4061341ebacc0e3b82662dddb8077fc34d3b4f3a9d13279e064d26d5ad922b0fc22ae10ba73ba167280769722fe99ed6d6ec34536621fbbf4dd2a3c667b8311f3c66810d128206c952efab12ee5112540dcf36d932c16b8813fbf2c76deb661d26a387ca21ea251981c532ace274402b50b57d181ed1204f3aaa45f778b122081722c88d746235b4603544f0e2fd273ad38291f49e8343c37b66abf5f49ac3f649dc18a5417f7f07256729c7b8c3d1e6869c6f2aa034216c061c7a4d55fae0f93eeea96d0bb379f9855d95f082ee75b8e050b26f673612451e6781a36ee238e81c1396ef6594f18077d16fa18f77165b7a5f73b8cbb9d4f2942ac992b8afa5bcfbca9c55efb0f7e90d5a64fb6af9f1a8c10c61e3182f2c9a56dda598bc4b3961ece06a93ddfd0f54904d0dd7e2c8ff6b5fa2dc806937cda5bb413b825cadac9ae47991acead6798bd872869c98c54a96ad29d788f9c6b7a2d642911bd3ea38e44c286bcb5c40b25ce4facea0749c2b9c3a2e5bae98a20f3263731e2234e96081ff51193a8571715f5dbe3cfc5fcca0a6e35bf9af36582a4ddcdc36691f88a9272a41bb222905435be7aea2e085d9ee1a2d9d3147ac5b5419c8abcee8f760a8f1a8534fe9dd0e24479f08bbf5d270b72a5a3ea8fe6842182cdd46fe3b0100430f100deea5dcaf197923114b6b60ca47fb9b78d148d540587317ce22624dffef3d1d3185c004bab92c54cc574a7be1c3ca2d7461bc2eabc1735e0c394af641776a1d5c5900ff16aa73bc4751818bbcbae21fab19ebe20a1226278ff4abd83d98a91066d34c989177cb47ec613fa93a4ae258a08398ae32d7570b7e5b0b2684ab050f3727ee7f737661e3f96a521d7ecfabb6c639fb2ce85da3aee32136878227f140e767e71cac7e5de07bde32d03f18df727c0fcfdd655bd466c73c7a9ff7c85411885191786bdecfd10dd783af82fdf8db0a6ba5efe842b0d556509e5e90e7bb9632358f16c5924a92351d9d9011d4a7b90a10b2e3e7382f05e237c6c14f7650426b3e79a4c224f4719bcb09727c9da23076886287f93563f1bc799ff05b78c82563bea62d66f6933ec8310246c7a53d191a39a549f33c5763851dd944108616acae6c23f5f5c6132331b86d74042e06afbb49e72a0bdca601ae6a519f469a1045266c2993173c1807d638cb92b11f4fae0600e731825cec06120e0b805270a24ae970526d8d5be1da73d49bd6f68cab6ff9474ab15eb0518893624d3b04c9a4c6fb0200b24dfc1f66e7e08fa95c1967808805c51a627004d9518cd57b0888508d9058a8ae7d2e0ed93566320d26aaf5264d3ac114c2ce802c3211be2ab9f7f1b6003216854a6061987b37bab82a48b68467059de3c0e2d5c4b04dceda998193176d1c6f338e3d481fbb009b97976b50eec2b26fecd7ec6d760427fc5379c5ab14003d8152340c5caa45b92ac71d1d9ed62a24d490ed8dd4e14ddbddb450a1a0546096a1e46321bed216e7cbae70a5d2d61662c486812100909d7f8e41db1aa3684c64b7efd66f34b95fec04cc2688343721bc29963ddf260d93b06c3dc9511acbadc18c734d39b8568f2db327db2b0d04d3bf828217d156634957362b28b61754cfb2675084af8a0a7283d35724b373f378a8439d522e231d322292324def12b777f51969f1a0484ead82025b2c128fead96bac337ed7c4bfd12a2bac9b0bd99fad605977b2d8661d4a88848db87e2305a2b7a1d93c66204ce4b626256866cc938a255f109fa1dcde9a82eac1567f3ac83a1732060e8f19669c1ff1013761050ff9d49c4d4672c5aaf857680785c48a078390dafb07b0895650a0dbe0b40aa56f827861e5b2669e5db0c2158f144f383dd10a1254c3f3a70ef206dcf06f876b845c1cd6b6c3c12a12eed5a4067325a70d76ee02ec8821b76bf54a37dca496f2bc21e0117afd6cf2de6bbbe57c3f29464409c8a9fab622e0cb9bd0d287150f29e028dae40fb0ce96be529cd331d0770ffed5630042d631cd81941e02c8016bab89575032e2a81415a4e58db75b57ef52ab981c38d10412ade7687bc14720e843e462c6db92042342900290d60d2c363986ee644bab407f38b0181a9cd308d8d33476494ec8c04bc6fb38bc8ec300ec05be418eee20964cd4090cf1f406fc7cde7551168a419b97d9a7d5aef3c1297e96b3a8189141168af7a9a2ba01eb11530537f7b10b90271b95c45bfe1caecc58480ec1d20b868dba5815a992134700e0f0d1ed51ea44fc1c51b2efdab16cbae5b64be156765e033988eec5697706b8dde1e87aa7500829df9b0f264573fd8f5500d609986307329a7a8e565aafc8748f5168cb729ebd80c7ba0a12d5fcd35cda6b6d0e35bbfe7f95bc86ee8853f84a6f395562ddb5edcfe57a8438b8ca2b69fc7fc73693f2256d678cf0147f9ebc891555f6b80a86f9c2acf8cfbf586ec395e5b820952fc9404a1e12cf3e166b0160677059bb10d278926fe0fc47e4e9a15b8b3996d17c966c5a785d9a70a51a562fa611c7150ef10102099e2528d89eaeb2ca8710c92059109bc4c409e6f65301045d7d5e80faceb0432ef4d200084459b1a88d3514804aba52875db457128b23cd8716707f3247788c55e0c710e0f1142ececf59650d6418ef9610a4f0c168b4bf39b3b51dfa611bc0cf51a507eb824b0499fc36d1f856bef2372cee5866e0f6819dc776c7d49e6da2889f4e83942db9d346b687e5d0cb4aa9b113f7aac1651ad6f98ea1a6965064c36360a4fcb88a3ab6ab82d2aa0843c626dded97ae744c75f9f33113072206c3517b982a7cb6d04ea12c051fe6d2026c654a5de7f162b361f18a2816d5b61b572e232fad55943ae3909a81bac56f8f7e09ab9322e66a8c29e8340706bb6ce4c188d86a7b03c6271ddf9bb6cd912ff429ffdfd28ddd2b032bb90d3d6382bafe633126763a621384a1468b3bf90174e7e1f02c9184a96a883a0b4f8fb56722f6c743515574937bb29c146ce50ee94e704ca34f4eb411148f17d2c81ca352c6248064786bb5f420d0b1e845ce8b9102fd16fc9c7210a39a0a42dcb10557f901604c5107c75149f43fde852657ad7179646001235d79685150ec052265f14171a4612e15c24e9ef9c5d140ca414a1786cd24c2a8350e64f0565646819b5e48a84818ad425d8890c443df7e4a5afd2740679f9871ec08394509091e9cb952f5f78c16810d5c2d670f8c71539348e2abb38d3e135f22b483084d3060a84c1c4b1aa6c3c2ffed6c660060f172c3d70c76d5574614d6134a174ff450a8930c8142a1447fea7923a08b13b02045c07c30c893c0204e010f88d3b501600aa9ba554c69d8a5a215859e503220d8887000de8e6032403bc1dc55b4cb5dc34073c7a40dca4d089e2e452684278d43bc2c45149b033a6dc79a6fb2210fe68079984d85b1ce5a5eb1bbcc679ee305ce4ca60390dd3e3aa05b2cfc68ccecd9610382b43fa790216e91b6660de5ce442b4ef760831a0f95ecf403df2603de5607069dc2fa81cbaa4bd4b5096b0a83d3f16faa4e1edea5d9ae6fe468ba7755021b53ee61896c844bbb789a758ec5a07bbab24a82067c5359dd90ac2357df31dec24b11367f0c05cf76663f5137c4f279ff71d3122ef7e640f958d3575b51a843dc8650b93969d95bbe1a627a9de8ff421984588f1348a5252a1b057a5e360abeec445535fc855a381d4b84732e208f3aa3e141e20c48193f9b814fe2941d10792a2d00b2c4a539b721df1bce830d098c03301a45b76734b658d4082bd3216fe16375d459a0e7f8e9b50645833658f956513b3b5a38025e3bd84b302a4d1db916b991c075e8bf32befdbf53f34294833159f301abc58498d4ca1a20caee5872a71193c3c37878429bef1063b8772f9ea84a10558474b1cd5227d448889694dfdc19c42e0769e89551e85e0ec277ea2ad4066127a7fb68d7e467298dba828e24c0548a1e26691f8edc5b46082109dafd49c6c6ad1a18d531549eed449c8a42bc31f7df13ad91ce8ef5fe3ca821e0e744a78fc14ef9a9b9042608fa3a4b2fdc4b2bc84f0bc755841b97d1874be20965b23418ef4acf8ac60599717dd25bb1cea870f621ee8f76c701a6b876b4a02548b21e718b779a969b281846563bce0b00cc63c0960db770c50ef2fbd255877f4baa514b4876c52105d27ad42822aa972b007790f048d72b11e0d67b224aac6a14f4521777c348121ac6ba1b5de2b3319f47ddcd65e0b450d63fe79200bacaa597439943fc0f02aa15473504a5d05b537b4199d00d3bac01b58c8948dfe56a499f6c9a99f96b1b160d973d6b1b1b130ce97633539096e0be47c0c5ab2889c5dca63d3a06bb3b9a14927e730b06605a3b765f49b7ac475e35e7c2ec409e52a6b9f5183bd4a0149d05098cbd9b4843324aa0ab770c623968fbcd01e313ea74ee49f29c06db2eafd48f4b9063e3e066a98ef4c9c37e555b465571171103ea3ab8307c911dd45746fbbf805750eaa7719e84916254ddbc20ed3c2ef591dd83f02f09c547f70217211282bbfc707f140d548905c429458e3a34dfd5c24b3f3c772e0fb64221b787fa4dcc812454d263f0708f4a493edbe1ed84083ea079f56413146b0729e190774ca56e9bddba3e762253c365d2ea2055868f1a9727c6ded3d7961a149ab2b20540fa6e658dd639610f46c691593059ae4d8735ecf45ac6d062d6dcd77fe2a8dea7d1ee53f967aa1a83812a9b4707551c49111766695845f9730d5fa314db87e98101a8578716eb0039c48abbbeb3c17cb595db16c114c56decf790f09642291258aeff494015a18fbd778d902c1f088633ad060d5fb870b1610164f0287060d764f23912b9042a4a2800785b604b1c238c05c8896d05592bd005c040441750ec8e0b35bc014e965751d33de00048a6197a4eb1a51d5bc4048ebd05e362a3db4396dd74b9f1f2b68cd32c91af253babf14f8053884451fed0e548cca5bf8f5242b1966df0ade697090239f8aef8c03173c27fdac08213e6bec0b0f7e9fe02c8886cab825768ca49fe1429973e56b666c07b238e5ab43e02a02dd431ed21e0f99e2ff68b7097748c0be289ee4a95a2c878ef4897a1bf3e75065c814ce71997db8e67ce7c3830b41bace56cca1600f41a26c2b04984ba5928bd08c62e63923481246218eb22fa568319a40c28790f623488affdadbd3314a2528fcd4107b2126ac3a714b02b0bdd952a4c1173cd92f6ebe25429040fd6a297d5357d42cdc0722483e41276146fbb8a00f994fac622cec974420de3744921f261de9ff38181a75461863127a5f4e4706de1ac6abc60d119a829e354ebf9ab980aca1dd88e58851651a0155b473d0d9b071a06c20b2e3e552ded146f0834e3aa951c776331b5a9811d0e8f8e7e16d83eef0479144aba9cfa058e38c6e6babb928174eaac3e28bb8551734d9e5ab04666c160cbca1cea14f232450138e94c69c8b2937a444497d326c1f6efc7b7a168a480f481387f293cad277540825056fd874eab1859ef33e4a147d6b106b5e061fe226b4ea9f705181aaa064e1830d0917fa953f4fad25158abdc165552e852686e624fd16886520137a7dfe53518405ae60b1815df12cc59cb82d6ee8f817ba392236a68a4cd1183bc6cb89964be4738e7ba539c389f1541df907b3c42290be8a149f5e01a18ec892b2efcdab83699d9f017d2aa1f92ac10e6177daa2aadca2ee61f136a654f00be921617beb2d476bd2473d57c834dfa9088de77deb99925e378db6569ff84827ba890eadb4641a5c4265e6aad86c652a22d516c66c4ab8722a5eed5333340c63d6eeec2baa92b1b9a296ff44ef8af80b8e93250b44f923912d266981343123bd50712e611cf490a150581e0ad8e225cc6aad068f9fa81cb0ec2efc00d787ee49cbdeae0c3e34ea2a147246a293ee10399c50e8f204133b9afbce1fd9c30127535ea31e8a5f91fea91fa87152e22d60384c24d6146da870fa90938ab46c88a8e53bce5ff99b3cb166c734ab956c2b95c5a11660deda83846bac956cf0543f7c275db01ad7baf799148f95d4f525949b868a5153e360de22364067e1a19e9ea12725f54d508b652210c8ba3980999af77e08ec0262c3833474c2406e36b44b5a12c0e9d3d8d504ca294638b2e77f4cb0b860ee5b87ee5b49874d1b5223711d0634180c5fe41ae1835dbc072f600ab25e684c5b140786429770d2413f7fef12a25e97d07d451afa2412f7fccd668b0e3027765608757d079b172cf896c02f1ed646d8b7497a8d2b822c0d09a23bc1d60119c1446a1690859d931577a866d600b5e8c2714108d4f090c376ea23fb69b5d1cfde2535d6a8b4c8dad9410e29580afc256c3540fdcc94367b62c2c75a818ebe0c208ed99e07fbc246d5b819147acc6201091b1058506f5156c5b74a535f4db47484da00de45ed81422b7cb3c342934973350ebc9eee78bb1e9ed5df5f8eb20b6a3a89ca0f9b41b547eb247aab8ebc7125195ed5a2b75be65edfe0ec719d1c4f557137ae25edac6ef4d0ae37410f4bc9b9558adf7035696751229be5965bd57837d726eb2ccab20f6e71551779db72f2f60d74a35f958893ad6eade4174a659b4c2a545e8a20cd0acd67a174580f72a1a3d22d9bbcf556511d99832060afbba9eead92a94b8d9240723c0523a1aaeae932a97e15116e2d4aee5635e2adabd9f31b0ebd5924cfaf52f4d6b5c95dab47b898ea25ea7d29496f55a3375d27e15009c9b66fe65631f6d6d5a41d3582eca3db4e55636fbb4ebddf00dc7155157f32d64fabbaa5b26c10328148d5af07d5426e541f3b5e84ab196417be436a8a1b179a0662a0bfb3b1b6ada509169aba380d4684be8fc94ee551d459c5e591fdfc010d9bb93bdc3a719d7a90d39832d75d00c2ad8e8eb5cdc488bdaeeba4cea06e6cebdaac1fcb746293e562e0bdba0cf0a629ed89b96eeafaeae6a46ec3ea2b9e5e69ef0645bf8eb94b91e4262ab2d81ffde5896f9dafee60d441a3d70ffaa5ecb417b5e7bea2142d1b74679a335de2e63a49d6a973879edfc8bf267b4c6f85a5d1b37fe411283ae1e61739824d1d3cdbcacfb0ea4c7da98e28195568c2fd8e552cfd75275f5cd5814527db4dd38976384b122bbce9975b9ee96d2b0b1af49e1ca4e1e58a9f912b0ec6e4779c4a11ed7711e441a5e23b6c9b732e0b3efab219d2c33765caa7a4e734b05059ca36a4a2f9904e5c77bf8204aa22f75e014810d109a515694bb07115c0a53c78ba0d459ccf359d0a46a7f5082133ca13bc41f9191094af5553378b05a55d2519d02cf41398171b501ee21466028b5243e7c5f103f0406eae48bab233ccdbcbca5d8d345612091b0243d962f6995fe6e389dd45cdca3984b45baed03ca8f7aefeb51c7ab553847d6cb14189f012a7eade9375b3c4743f9d67b89f764409f1b58140b36aec80dd3255fad705170766c42344d311aae17da3dd23601700990a04af0cbd394f5a440ddd5712bd2914d26797f38b5d5d498d8beb64f92d2d5aa00b01463611fde39a013b27d8e72b84631031c01f9390693cab5145895c6081072929e321243d9bd668f99c1fdc23ee294c3b03b13f548b9b56853f8bce5eee2927febe0d7690e93d348a2f0fb1fc107437fe45dcc23c82969487783c914427deb5275d3043b54ffac0572babe7c26c154fb1f2c9157af25db93df941f977e88ebeb5f69468df9a9680138d3f3bea0b931b9a709226247060e1137b19cccab433027c6b0a4b06b78934c7b5da1dbac9216a4392fe6e88a16ecd2adb4bf85ba553a52efdddf7d17f25c5e39a20ccf5e15b41d205da9952c4c069f85f4f235d2675ad5b20439b29e23aaaa93f60f2f2474581f6c712da4f5951e7aa25721ab6022074ad302a2e2569166684ea2812c9e035d14ccdd48be527daffa34d6b8c655f6ce2aea03882bc5868a9101b43aee9d2ff3516d0dcb5e8da246eb4bf83667f167bc505e105ff3da91e2666c434f661d97542a37506b3bd78eb3d0eb99e5aca0de2a1f7515616329d3f0b5aafb2f235ceca88eb8c69605de24279d814d0e1bf1f65631181d99b5115894d5574d6e1aba71aedb1ac42f63e9573dbcaca48f3f6958166c4b84bce5e0de99e6079f1e6444dc8aa0f60ebe8cd4432009aac9a548f16c86647605e8a584e41ed8cf5f035779b9b2e7d418260672a9931ea8c8680d99c51bad6f53c86eebf204b8692c5b7c2b703a5d690baa41b247f76a25e4f3dbea1cf1c39758def4cb43a3bdccfd9271b2f7202381ca7744328ab80ad34abe03aabaeffb2ded3646402130a9eb564cc60d28084b779ae10173c8d4432714828846c121e1571bb92a372209294804213db70f4517f06838a869c9cf424c410aadbada5fd46c880340ed3e260486540d5522b26ab5e590958ee47c9c748c949c173a917adfa75d6c45291175c08d711d270498bdbe334877a96e61fe18d5519e5ecad4cfe28dc7719c46c14fd44ed9621aefa5b880e4dc3da8c2c824f6e826ff6443c4730e270d86133067d70499e7e0ddb56b01a40a8b8125b023046c22402db527c007abc9f99b49e28b3f8cd530676bb7cd6dd2e8f96a4670ad7f6ace161cb161663a29e94f747fa1f75b3c9c3618c5dffdc8a56d6382c21493708e4e5707efbc37a3bd468353b051af851f45f26aff16368abb5ae0c5227e1657e8ba18df8a04bd7cc84863d58191f307ae39e74d59e36db04e6d70b6481fc1c98fdcc25a32aa9fb698246a4b1db809b2016b0c37c29674fdce70ac241d8127d9917ea45ddebc5b46d1a1f1414ff46151b4a29e2fe7ffa2c96c36f9c4419372353b71b2e85bbfd9bc26e2aaa72b7c255d10d261f3046c78dc0c0a85a8048b36adeb874edd864cd843b92bd6dd0d9a808f785da9c174693843dc435d0041b693f7ccc9aacf24b6f88d6d20cc997647fefb305dfd8acbccf86f89df9b2879b4c0c2aec8bae18ecfb516443d01ccc1e8e5137464b2c7c4d0511afb580d8ce026dd0fb479a372cfb5042a21e29193c30afe4767c37b7c587fb382c3f318fff581067c17f7dd75528541fc3b4241b6eb380c90cc1c605eaeea3bfc2ab4e316a9e14b6ba07b9ee997decea12dbb80f4dfb1b1736b003c568c068801fe48b51476af0d1982e3d4b5f2c7fbcafa79774096104acb0e36ea2e4a530f8bddad31547a40780e30183ecf57bcf7a7e5e43a35423298061d328a585a94dd0d93b320a9e3443a99aed49aa660447d5ec5c849e82e113e3c5db1569216008594285e21e55c9dbf9fb6f5c8c879768806deb1744b7788a96ba1d1f4862845b9c2f75356f2897585784b87074c0dc95714e96383a5095c69be0f641f3026d7a2ffc82fd5c9f847b020e3b6708c1fe643cc9949a46a0b258370d481a0febd691de253e90e42171cae5a29f0e0748ca96fd4c8e1bb3035f2974664305ceb87053aef186d48a6fb0719b5afd5565dd44d58b1a6aac03831e375845e612f2435ac773f5591631036c47af440e8e4c65cfdcdfe338a140fc03173ce2f46e25e8cd8cec1ee071cac9d40ca99593306d2938b970c0e08af05b3f85e76c85c240723162bbad7de80640ce17257409164d9346cd9ac2e470136b1a0af6b7bea4e36d11409635e95f6409d9a45715933139d9f6a886c1f3eebfd91406f47bcfbf96845eb5307c441bda3965ef88ad94ac9c526934ffd8dcdfe56cc2603ee8dbd6b5f8c989bbf62ba8e6c2af3d5986624d2e1ca1fe3f048a38b7704c26d9780edabae20e649d2f90169eb04b68539c318e86eb9e0621c5e279f99f412151edbdb564eccf1b1c22af8d30d80d6d447c0238cc404c23c624d112da46acdc8b8216caec5a4f501d92c61f542e8ad291fe9a01f960aad20c91b263051407ca607c4e244dd40645e459a709169e0d97fbb29ace1dabe5901f5a5c777728014f44372b0dc7992ce3bcfc597555e9d71eeeb5880b207390b00a4b93d6af5e55edc905d8b4ef730b53153de33d4390a60d019065d5db4cd0356b47997a60b4d01b955ce82c84e5b86d45672a131819711b94085c50b1c58fd2c84fc5cc0f350e674a7a8d18f126f874f84036631c6906a4ded7d5ef24fb8336b5725c9bfd68557048539b5b2c40bd79344a97af82184d9705006d96c7e98d1b0314e5eac9db783c96c9d700f5bcf09f29ff2601cf84720271398f49005c8d3436098dec9c0e28ab2a9b5f85dc97c3b540339bcb502b833203114241ca65201845859f943870314c5dba169c148edda79b2e8f84d461083ef63a28ca04af78db9903da2c25b69449ec7d1e9eaa687714380f498cc4077fc4b6987bfd19248b8df16ee9004013f3a49c5d8caf426cea5c8ef7849040bf4cc856d04d38b99766a3182dc05fa90b4eecb24378ce6bad33e6426a403ec34d99b1b2461e11c10bd8b49413d9a309ff756d6ac44a53ec2fa2821305e2f056c0875d6e7bae95018e438e95182a46352724316499a661cf96cf67398934013e5c61af89eb3ffd1837ce8ed594ddf12e4e99c100339aa676f76e18280b79361d3bf062741d2542cb08b563e2ae70d9ab61c66604717f23b8c8fa47b9845d77980e5dcb30d34626f8ef0055f1a24e4e6415c75e5e90b4b80be492df81ece3c385865537e9afa33c2ff7103af3e87471b18272b438c2f3bbebdcb91170ea572993ed1a3c2caa65a2a07c4a9b8dfa37e79a347e08940d3d7b1f253cb6ab7b398ecb4647025617861ac53c05f20ab553ffd9a66229114aa584a78a94ff3bdc73559ea4f02d0ad2584f5d60798c9e3bfff1009492c3824ca4b20ef753cd9e787cba9c087059508428fba152ea813a92c5fe2c7901f011ce7139349c97c88866fb4b2246c6a0ad9208811b5f38a35959264204096357487485df481d7a91d9e3e889260bed7a241a1e2a2c08df44177e7edf67aeff1627afb95dab6f5740012d3584ec7a8f5ec6090920bdb06b6acd1cf82cc577892ce845f3488a2791bb7233510ac681d13a277beef896d966282f82d8a40d3988186390405ad2c55568bf36db9eb5458537d3f4705ff7f7ddc2f24770c50e29e760fd2ecb9ff474e1a056185db0737411cf1230d7e2deab4cd5ad826b38e76039a750e1061d4f00cc281405786556a73903025315c2071a73403592e2475d3461710c9ff725c7e4b206f5a3ceb329bacf4f198432737c223e298d41e96b46ba54cee99398eab4d400e31c783728400111f0e97c24994aea0fbaa147bf443052e0974a804f18f78e00e7c8343fc05cd2751878f4b8e54f40f9115008763d389cc46fada37425f39324e6224edf161218d514eacf14d3824ec0d684eb21c1ac1c0560b1e422b9e7cb90beadfdfd43bcf2cc77f3c9461395301df9a0007d569878711c6f1de0f1766b7bc12e7a44c5d5bb1b3069ec64ed3154253ad3de9be288d6e130ae13bfe62c8346098e7f90ae053dc0d5cc08a2edfe61855b82e32368f740d92cba04762f9be936f0e2469463534cb9ada90d45472bc30985473d385ac8930918b4884c6e48acf01b41d0d2754047bbee9664dbc07a18498616a0c22796ef62a8b4351a2eab7b5b3ef9fdce028e7d1db5a1b085cc76d5a6a43729a8ccf2d6d901b085f6b41a2874225b19f535fb45a41b96dd72e31d916af339a611995c4e0bfaa9015c7ae0ea8498a1e4437d7937c1d61b498cf598217a72ed0bc5698149f5e0c5a53526bdd8332901decf8f038536729bb16c895f8bac87a58b732e059027f3320a200f23330ff1d56d7031dce7851e98e345b99afd043394c59ea18a03868ccb45f25cf0e9eca4d14000a7c58ea9c3ac5eb84b1963a22dbe916a3f13b1b3fc48123db596746395528f1ac9a6dbb82293c42d4b56be57edefb95652501efdb725c3ec0549eba3483f5a28828f634c91e8cd56b6d506569de1c7d0f47ca8b5135656717a73d621299c993fdedc60f02a15d68a2a8aa6583c3946a54e55a9852328078f357d43d0cfe1ec9009b959b6604f4cf8303cec9e423e45e21c820ffa65eb0fa0f7b576cb77be938f52a16e3cb63993affe71123bd0ad5621d28265fb02898bdc5d61b975ba9cb64d5df2c9dd1198f3a25efd706359f265b7d209a6830366225eb3e347821eb26a608750023d756bb47752eb855d89c25e85b21a425f5fea14957d773d0c4a274bdf9efc20d6285d2699bd9cadac758ebe8bc4564717db22ddf7343b31eefeb57843ae8f624313de8ffcb76db365aa63035aa0ad341d340376cc6c5df3c7f14c36eba91728d5b45e84bf618bcb2cf9cf2b29bd995c96b270e2afb2df524f2feec6a4854b1cd6f9b929f5ce54e6ab4676fa598285001d5d34054fc511eb5c28dc69fa660682d5b5d6416d2f4b92c2d77772939a9bad90044a26fe1d765c03f2149a1869ba5260072ec13617172a170d9175dc6aed5d71929639540ec3024571fa5ed4d1a348b1b571e58594e5f4dba8ba4610abe164578fc120c98225b80be736dc57f0ae2e893db3a0bfdb111a4c03f141e2930cf6f5ce8840be17bdd45e0224c0b5c7af8dcdfefa5db1cad12748a26c1fe6c69fe6989a3af82681a0de31305b02990b01bdaabce676f21ac8630c11fb466a47802a28156cd3d38b3e490e5a6c20be86387afc0b961b0d117828788482c47e971260ef60dea09d35300a0336295767b8d8619d48908f20a73532497d13297c1bf0b9ab6950868428a9aa9e5a40cc672f384302777e3f226bae0b39be0db81ff4c3b99b63bebd4b990db853210301a83ed956827b9b5db3a9986dc79fec3860585b619b7f2107d713ca82aa1dd071a0f08e805c05168ee75c88646219d0c75a541bf0cfd4808e2c7d7c62b50bbd2ee6e171e53402c464badd70041a7640caed574b86dbb0e752306cb07fb9ef55b473fa72b903469194cd46047888009533282e8afc7a36ff48247139a9639f8269670b96ef758cab3410b8f2dd423e2bd91f28d47a80bac66d9c571e9be5b0d27519d080dfcb690eae41fde17a48a94cb121aff95b79343a7000a460b9bc0ad96a5c9380a5b6e8daa181c77a92328820611ec0937124c550b6aed9d558d9148f497abc5633c4529e1352809d2ac9510a680eb8da8d07c4c11b654aa25569b51dd0e2366eeb9009e92d20bd8e66be8d702c1e075c46f4935727e32be955bfb7f0482fb33ff083ea99e026ea6611945fb55ffb9ffa18c3cfc6290bc13524109957048c2d0d0219ef748fb1417b6a6cb0afda3f3834745a53ca2946e11664db36b3ac381197fcac4b6494300e4887d065de3ae4e5e3f0ab272f30e98abf854141ebed4d4048919364cac315c284e45053e1434f82b5617ff3f5cc445a20539c02b8cbb2d06126b1ab75e071d41f03430578305aa54261704915dce14a30bcffa4eb5a717ae478cb005e452d5045b0e968e5eaddb56ef77f01e61a00527ed70165cfa04e8eff042127de058b54559d3a0502d178ea17eadfe157df8f0994a052314f9eb991196137ef0a2a91821688887ece2201bc1a0cb7c94d097123781f3d7a5bd175dbeffee2f4daa4b0e62f44145cfe9d9fe64f4b540f1fa19713404dfdcd266f1490fcdf0327e64736365930144751737801edf11ccb476c6241345e3e1f160e7beb6aadd19874e99c72a71fd3b4f1aa3125096d3c85c8d9860a2e944602c0582c3475b505cd63d2083c80c5c4b41254b165fc02195e858191c0055a60eb34cf04e95e7ee70a255c82e28093d6b3aaf92d4b62f597c0087da485b1d873b77abab3b2d55d056da414145e57706bacf72cbc465079ae9f3fa05a508eaac305f49fcbe0fb594cb2fd9b275a3c472d5e98d520a45e68ff160689116ffd4ac4a99953b94fdf1c15bc550afa7ae1783d8c4de6b6af2fda85ad4b9c0fa8ee4f313256eee5e9003a803b2787176b6fa6d155cec3ede2dbaa9f13023035b6dbcf46393a8ff5e7536ed7714d10f7a52596eaabe9445aec12e051be07237a937ea9360cd8b1340162f863b52665b29ddc17853f8ad3a8dd6f7083a4f4621abbcb1a04d8fd7e1d8e11f07c8ef09362ab45463a80d379c12f240f8d9acfef06e9fe9fa32239b215a58e9606fe496e26a086407fc8494e57099a4b582872f17b57e42574f1959b3e4e48a785f7063313f4e2d43e27081f0205ccce3a56194305c2cfa5a4184b5c96ffcfb886166ee92e25fd169d1f057066bde1649080eb8d0f86bd911a9781f1d6c51a83e217d5dd1bba566185001d6d8073277f2865f7f6b49ea76ab3b65ea887ea476843fdcf95e4722f1da4d25283a039c5ff475ab88b6b44d3836dd3c6d27af160cc12b2971950ab404d1d285edde6f749ecb3408ec12cc1aa5db89d9f20fff98e33572b73c29c435216271195ba075f64dfda888bac5593e9b51430e9ee1ce84c2c4c412d87fbecbb7c0939c9bdd330a4650e2b8adc5391d5a77a6aaba4f7753ebfa282b05c37001c38c33a1432a15ea9739421f7aa39636aba9d5301fce1b8d3fd290ccb829b504b6fc8bcda48f999a56a82a1fa064c7c24e2cfc2fc820aa5e14bf45ae46d267eb6a92c734ae126245cdd7f96a4509c9261a535a35c3038757f0ad10554d20c82bad27f432602035df6e4772709b0f1d0234998ebd6d3021e10ff9e6fba0ca2b852f5d7700393e2710c2854dc482e54c44b627cbd0f83c83253d29c01d768413c8978bbc049dd802946869ad3773d5801729f869f2eeefb551eebee26c6ff31b65473e4a21c6e26a7873711d3170e86f287f92b38d90158d11b0f7461af7f0567ea28ec87bb093eca40b8e26118762f0039e01ee36fe26e147f6376bd7add132e6a694145c2750457b774257a79955dfb00ca672141f140ed9a1ef0e9ee48a74a219d5b1f3c3b46c67287326789ba0f533c22670958f03572da89d39ca0dc44d9096953486421c82edcf3cf365972bc966ef40cf1cfaedea94855803fd4682b4d6c758aaa039240ab39e49cec4a9fd5ff4b64fd0d94f0b38a8f53abac9d6f54e444f33dd191e88a63a01fc1c4e316a8816f81fa94e6751e4dd106c4bb6f72129b9f775fa38a744a7cc61311c2ba59ffba8897c22e1c838d0300c0df80310268883ea6c36e875191507afe71cc14f7d9c1a4d1244e3f0176b3d4af2b6d3c4e1b7332f95615bc7dce29b7aee5acf9fffcf1fa0d364ed5c8401963cc7e2c3d03f8d533e82527cc7dd0c5f92cd46cd9c2dc514010e0129c1be5b8a608800ef8ecd31237292b86179bf6f7931fb21a5a55ad8fef039651dfe21d7e780d363bfcbcb9a365f9d375ffd577713d4380281a7caf1b8939006cda5fa607324bc08f567a199396bf427ca366cae4a281e4dba79b5974a47b9a7122c1484a15a521ac4895bef246f3bf6abe6ba471614780d9071a7886c5396c77b4227510c9e99c68a2a08748e38151990c3c2ccfe7fbf0f3da6777d5aa87aff0446693ce68235f7978cdf9fed07080d894ccf0c968d92f481cb6740e4ae8bb2613aa2d00454f26f5624c11637c6b216e31c54da173135abfc05deae09a4e4732025b687343e289096879f88a030aecba882788c551d25514ea3c1cad46472b9df5faec8560ccf90cd2c7401bcef5756e46ea1fa231b2854f1dccb5aaac8ee027b4640ec10b53f3599494b2a9eb95cc483e0267d59bb5faa5202cf78ff9e3876a3b3d42b78ca0ed7a4c4aecfc0fe6451db9a193bd7be6fa209b0fa27e7afd5497e639c8780bbaa55a4598846602280e4bde6c351dd3871babedc17438d0e8589b204b24987c8bbb3c83c35a09fb9919d45cf0d513c8316677294ccc47f076d0f29186f6527c527e70b249548a563592f2d650a10c06fe78b51637fb2bf549cf486acee4f3b7ed745024c50fd5e6cfa9b5199862d24443daa09b752690cbbd1a1c38d72e48e953c771d3617af0b8066e60ef40961b84af3498cd8f5065b7b2d3bf1f3c5de849f3f56d10266dba8fe34c8f811b3fcd71e51eb5b2e0e9ec376e4c6b7a3995233cfda67f072d69fbf46a24628f6c340950d2a60cb53e3d89665223c9f40cc525ac00f58472a5672f4132ea91445650cb197b4691e5d02c67e5c06c491fcbba4fbdaa2c811052236f57a71d0d4aead06107a382e63d14236345ec4c2b344a9603a6a53c645657d300d14a491ae18ac62cc94a3a30883b621f002b76f4160f894f03882c27f7ea86ef52093d755925a1bb1930e2a44197b527019379c263523a01565f42b38e11445ac9841e9c0b953486dcf0cc89ce023e318db9c6f72cb9cf8034d1cb2590e58a6343a44e1911dd4f190596c581972d31c09abafc46b52e3400593c5cfffe14dc3f3915aeab75ea9ba8a31efb80566cc0af274845aec5d493f17ddd7abeb887aa0b0f8743bdb1c4202ed2ae23e72291cc379e1c4374c17cdb87f2c39360be04a1ce7ee08948207fecde49c076fcf37751489d883666dc604e3e3f772250c13fb4ed2467dd7ff2d949be49869488bf9d36489b726c609b72e0a9dc3dd03a9191dac27a539db0f3edadfa14489a97fae2907482febe6f302490b36c7a1c21de1b974c2b164e40718e4eec9a8de34cc905bd8ab5c3848c2b27b5ac4c4e56ac1eaea47261312a2dcb0a8830ed12c3a5a181a9a97c2016bb4e7a93d5d726071b4b8754a9b980109396d54d44d856c9e9dad2c194541e1c455d77afc9ea34938185a1c2ead44c58885d375193a07e4736d278cbc25c9e911cf38cadfb337da83993e11fbfe49b4de2d7bc9726866cb2aa6f88c226d8109b84f34cd2db5585be26e60b0d89aa64693790185554c811743eebd384edca2cc726a182dd2e2236c626fea0f63fb9eb2a2e6a9a33554f0df26a78191db3461d100f02aaa172e4b8eef358aca13314ff69d5d0c7d3f565d4b3e5c5fef2a0bc5100aa904e7e61097419b1213f197a7f2912b3b5a5d2701df26b49b99aa1a654e4b0982712b9ce7c0c3f3d43dbee407a68fe65b87af9edee5006447415386a2278f15aa16abd3a437ee700e62845b661b0bd4d669233e85a8239b001bbed9a161d687da141b6528757d6fe38ed038b1433358ef82fe13d8e8bb2b22ad4b37cce09f243e9efeda6f0eb0bf3e53ba057bc5c5ea65f8002b0c94a14018f9837cadfa98ea10df2a75609d8a022cad00ae335cfa055421a1119ca5a445abd29ada1dd433a42b941ce6246ef0fb5839961082307048034c6100849cb109bf1fa4e13ef7703a019d2ac8cd66a7f81e9709e9b95140aedbba9528564a6ba52ac7934b242e532f9f5a9a673695ed4e85f3863cb8767173622a7ca03874fc65372ece96212420292a6be4361c753976381078273d75dc41224f380525592820c4a932ba4659fe19d9ea3ebe186d473c33f802f31dbdda35d2f0d22fc2bfa1990cbe97d07a5453b20a55bc0f90e04324645201d574aaf71973fc363c89a4a38b7004bc2faf117d2a24069aa90c9b1dc1b3111a9262abe89c25bfba7adbd161493aa3fb6263c6586b2280bd2816fe4b84e30f8f58e8f784b3f6ab59b36b6df135b12e98be2e030bd160080446231d4c38bba6ed09dc2c0f6e9a4d9c798bccae1498df59689455a64c66d073e6c43f2be80042977c5a2e4395600a5e82fc1047a8da7e7a9bf7c89882ef6e7db70ecf7d56eb2f0020ecbbfab1ed5e217adfdb3847099436b14fb791f07de3b2d1b31984e874d65c2145d1dd842ebf53d1d4bb73af49bafdb32262ecea02bd4424d6453c34a86dbf2fc4a703ea2cd454aa699940857be2f7d14953f3b936982cea5d22073a27a82df7df2c92ef057bd1d5eb0c07f889f5032b49f013ad1da171a83be0539423298b3717cbb8eafd659f842ce94c44efd73f0ecd8718dd4c6388af099891cbe70953949862a81b539c9525699f5f386824e701b920888b651d59031b1c60a13c7bcdd2cc09eb7d304e665474079e48d35b7d9ff9d407de85ed5568624d6bbbd1389358125968606fd235111bd78a87a988bb6e5a6089402ade5e9f1cfc580e021397d981cb0f011e7462c317e222a6fb144f4fe2f5139ee67a64a57de9c2651991c91a98c5e1395fefbe946a367cfcf894a3cd10e106a95bfeca26a0c73772393d501b0e80d6cb0ac9f79f3b4254ccd2543ccf4958fb6554d23c529abe29a6a13ec37085dcf477df231f405a57c591b7e36fdb3274b87e364cefec3cc6e99225777ae302ec994085cb9efd778b3efc692a812c46521dabce3617198e5a5f8476d1672839e5fac8fbc22794956f9d3807d884fcff9980b71a708ace3d61cfaea8e85e17a81291026caad070bad2eec2f589542ed2b4cf9a373dafe6037bccffc1c7c85bcd762c45a5983e079c0825fdeadb541163d28d58f2a81284dfe9471cfefb93cca978b95351c2f072ebece9ff337d4bb9aff2313e39595fa900fdc947540bfcb43746a0978ad23f3c0efc2f49ae6a81c847ed5f089d26421814c84d2b21b03b8fe8014fecc0f624fd51f525b75cb465ef93694a037bc06765882824656adfa58a8e0b836a0ffad35fe65115c6196c894cb8e38f0201ebd82bdb8192064225ed3432d5c4263faeccd68374322f6e525b39672e6174556f6a0a952bf6030594282bbcd09dfc1b45cf8c8f9c1883a43cc88c048ebeaf46b737833605ab5137c3682696705207e04eb0fa77c2f52e87463733ad6c33dc319ad6f69eddc91b09e0d040d2db39ade823a29d18e460d4851307faa27e6c03ab74dcbb317686bb65397902684d347919760c8cc1a982652e6bcd92b373d03dfe06a78f9e55ffd14f20fa52994b09b13a6f62bc872e064d4626f848b69f9a9e8952a9f26efaa0a4a6edad762e4bcbdc6a28b608d70e05b16864e00030b4538cab9632d1fd0b4f44257728c330ad8e84c43eca4f6d4371add9a13ab1fb5589594077b13460a920e2e493d15952b78bca96f66daf9d53dd0681aacd428d51377a25937eef17e7b8d2406ce88c77ef082d1227e1166f2a43c1cec040a2c79bc69649feb390e155cc7e3b16cd9d54d8a635eed63ab4b5b47d1424afa6708faa9bc40cd12558840f12613226cb3438c76162b44557051360a51cfbd01d4f5cf76a79f2d1107d05ada6f57cd11a4a04632870435abffa5664b2fc96667038db777e2e3ed15b4c684d3f662541ad776b92b13f021b287703f0f0ca6729239089723a88b19052771df657db5b5dd44f3931fe0010efb3877526380db9a606ecc8df19f7c224123b81dbc0a2d9cc068198dea4c500e4aad40732edc50c3f338a3fe183d1516081ad0f8894868edd8ea79861cbc1c96501a82926a891227e36a7a54fdd4318337345c7275552f9735a8e22732e2ec8abd688306e6c167060c1bc99d59238a4a57fa459040c37cea4a425705611e58ca246ee1b4124f2f40f240f4bea216708278c5f92bf1e57d9e9b49b01806ca8ce3e59891fea0225e77fb7d349eadab53cedc3be5a9ae6223a28309d76e1d27fce5743b679f8a00ab62d2c4dfb91a8bd184f2756900adfdb2afda195582c2772f32368ffeab16945c3076465900ec82ba7d9e294e1d55fd5ea0abf5175b478ab2b981138d12565f911435807ed223bb35c27d24a8b07e5ffafd92a69c5e8c10083f2d5cd91faf37f48d244cf3b2845136547ae3665bb8ecd3dfb21b033645d4b08395c4fe35be20772ca6d2fb671cdd3cb2b9c7bcb59e825dec2b604b13520fb0c27be4b687de4428e3620672aa27ad96737777d58df347ab90a80207c656a9b139dbfe1d5b7d887b40739baa07fde4a79b0faac11f43ca6331d10295cd135198c13e0f321d7711ad8cc314e5df2a3ad71380974ba298a19eee4bba32705d4bb7b6497615279beb73118458b47f8377332adbd6dc56bef9d6641a19df4aa5126a303d1b5762552e9628978b9be7e0850f688cfe5bcb56e32dd7ff8cb31537f78c4312c5bccbc2964495d8ffc680ca4ffcf4449f0232b26833a2fcfcbff1bd812e67eec3a6af41e9ae928ee7631b35f7a3dabb89826ffab88dd8266c589b96e90aab73340a1ef8cde0ed91687e89ed94a09410c270e37844580d4cb422665e484bae907c9b597082de1da808f61097f9fd6a4d0a8fc1a7fde3cf888098913175d709ac8e747fcb6e70dc1ac1424fcb0b1699a5ac019b017e8a3a8f04407d47b395a1bc9786a50ea4381b7f12261996f61071d59c09557b3405fb0812008db6b99c67fa0167702aa350d2d01335b1c39bd875f1f3595d41fd5a30368730cb035c71ffeff893602ae54d6e1265c7a259bfaf617480d216caf7555ae89a26121da226473d862b44853d0cbdcee12d788679145aa1484889e747fe5d38f6f3af2d0836605389be3d7df41667937cca1e09a720a66459a8b93de29c93971e428741c3cedccf774978a4389eecbac7931eb9ab488899c049f83370a8190569da2405af27b55f83549ec04290d639d224e4a0832d9d393435480b0a05ff27f009ff30c26c27169c5c757edfca4f0cf31dfdc11cd1b853464aac4a0c3cb308a9f33d8369989b2f432b273346840e70ed2181dc219b3ddf6dfd2fc9847b5894809f30497c06ac8caf998d35118ac74b00337a96e8ba0255e2e7e870eeceaf8b5fbf1134bbd85dbaf7d7444b47645747ea2f2323da34bf56263be0fb81b903c6fae48b571ca1084213c57a604040c906d844def77dcc6d283027796a5167f29134ca74dc21eb2dcca56aadf308a6d4b05c955845d8cf2daa92cec1b666ec4ecb1a474fc9a6568c8d43429a7cda22aac68fbe7c64a38784cd2aa88527a5ff209e4e0d445edbfca51cc8901821447150469218e1f7c3f066ed5528909985d5da49cc4bcac4d737f24b328226929bd1f9ebec52142633628b110862c750b639f70cb90167b54614abe0e03585b1b802820793de4d6593083ea57793790bbbd87987223261a73c251e7cc66b25bc9326aa2a1b0bf4fa3e81f567c2dc4a0c6731b4828ae5b5371f9c51310cde20065842b01d2aa577ab775644220796e52436c31bca17b7058c0b4751261adadf7a42705edd731f2607db23e131a48eed93f0d651ea7bc376d61b71b4625d79584e8fdb5af2d43b91ab4871b75df55105d24ef16744bfd78247a81a5890668bb4a8ebed0cde3599489f6944dde0885b521a93669f4deb13a62b0a0720d45cb3b0b96f077383752ce464f8f56a38e0ca25c5029f09d3bdb6bd06d18c6c1b6e88a7e8b843ae794da458fd086e8ab02db6a99427cab0580b6640ee87a2fff6ace44cc59f5d4a9712daf327409f6fff82d13b35b71d917a150edfe60d9b2b3a4270644f6e012394a90454182d92e4a4d7d1a6fa7e8b1da39494cca7970270770d95033c51df4fa87a3bcc96ef0dd5067f447a0a5a74b4c98a90fd8a4b87543509926d4ab704ff87ca8571971b1d7735c0e8c0cbda208f7189a14900a8cd66da1a2326da24218154de3343c874fb9e540c8cf576775966d573c3603e94570c74bb6f9979e855448cfa3eed96ff280cc8af22d8d27590374176cd1a07c234addaa3814913bea05530fe35f733fdfec3760e3c6f965dbe71d7baaa0b018ed5b5b73b91afdba15f7561c40fd5519eddc13559ed0d0dbef35921f68dac1d2bfc5a2e1ac718435b8d9e759980f40552b1a97462b1b2862fc972bded94cf679da3aad7c133142f310c5b69b3a04c0e18851300c181adb3b7de384aa59aa8383a92d51b469117162c85e0ac9b5aaacf248e4c3446378813e34a829cc49923e5f51a60d8df94af45a7f2cc140d8d00a00bf80d89adc9ba5436328df758ed945a73796245def250c38a87575a5402efcf28050b35e23c5752af2965c5095fc38ec074e8962983337cb630e6891307355c5ad6c88d54127dc135748d6f239c085844753da76f0c040f9bdad62a75dbd4c7776d139775b8550518af8a4d8f2b55872f9e66ba16aeebdcdeac9c448805420118d279fdbfe21878f691d561059b210b8158ac02caed2ef55083a5769ea59a24f15bf19bf7aad14403a082eedb7dd07a411401946315d991978547a313e28d177e5a9b5647e6bc1624c35e7bcb71109e8d7921e4ed576881194605581d0296733143d4207ad709a54ee1b0f3cd15830cbe16896998e7da003a87067b7dc6a4e65d270943fcff3487ce5a5d0b126056538a209e42c80645053a185e288e65b07188b364e7434856211268d1a0e9312f7b9030347283b338cc8ae01249e96ff9afcc6386042bcbda30b60cbb2f33ce4624706b88cf4e5fcdbc2f37d3f18834d8a378066382293003239fa7b8d8ed23c5152c3896df1ded8d047e5db851691430ac586ced7dd0f8f537a2f73338b95d6c8ab52be762a12b6e4c43c1908fc110c4ec3092b33edc0b92f343838f5ed63dd16e61e499c42640cf5c64c2699602f5d6871deff01126da61475fc71fdd89eb71fa486db0d25cd043f410edca3a5cd3a7902846c74b12b2c73158408bc411a739a54fa227fb74007e850006e7028a7971c8e27612dfac00b500ca9650475a2f9386953059b33989bf74945875e5a38ecc43daf59e2285b6bdf606b271cd55a6e436078f8c58b8d563b4e82e14d962bc5f3a795cfa169bf84c11f6b189608f360da988cde942d0cb21ed81b82b25d851a8a9620aa62bd74907656263e1293df62d3fef0c85f4c567f4ee5e41f7b00eb567c63886b796259a7ddc820a9bd1a122c41d28295fea3d018705d662f3d036493dc27e19fa04fdf09594ed0bb85b4b997bbf9440e80f8ccbec901722d46603b5f3082841247656cbc0f67e3d519988e60169ed7a9b4d88709ac18803cc706b2327c64f357aac45d01ee0a725a5bbb4e9c30837694dfa4d2dcac6a29afa78377152f5f567178a7ed7b91b1a4779f669d724ffbed9529b26d46be4bc65ff9ff1dccc838bf35cd9c93bddfb49e822e0146804e68a3f5ba8d47cccae6173e95434a331438374be27cac932f3178b31e063c5c91ee7b87b6cb2bac7ac15a172d510ca7ca87cb18fc51793297c3144908b39ce2c8a26eae0bcd3372fcfac88093eadbb26568bee2d36e46a806d52642c626ba458e6907744464756756a656b445d9f4cf9baa6b2ea3b4de677bab2e923a07990958dbe04ee121d0d737449a02386fa8c84bbd490cf55b4715a8446d71b6daf7e88cb7c84755b18e95bef012eb5f6626885805211ebb585ebca63e91a067889138c8e1eea2a80175db5a05e41558270a4acbf8a68f41f90f4ee9b28c4b96fea3451ee3fc699caf9c7fc10401d879ecf0f030ab522c290a4cf34ac4e82106bc6c566e24266584dae50ee613bd65002bf4e8c79163365d534acda828ad64b6a7d8139072b73b7ab71e04f51ec136d128dd0c75f0917f822fcb64249085f2febca9fde8fdd3d3afd3a222f63d97335153807a84d73851f1c598a48968124228a0851a8b24c869565e0622b69d0ab5664e1d2f09f7353cea017729441435a0de93f3c6d07c1763e84a630f4aa1283cd4771fd384485be1a9abd09431088db9dca183800aced7cdde946dd20650f55623cc3d34f3802ffee8711b9039083c768c3ac7715641727953c7be0fa98e91d9a12705a8eca0aceef250c3a8545b32a1b9a731bc8ec7fcba9dea06d922b2d46461be4f4a66d4c8ecfb6378d86977ef298819c35ff8fa6eb602105597444b00a4a492f4a22a669a67f02ccac07b2cd990a9352f0bb15905faef12d541f990c1ea5853da7e4bb223aca5221748093f9d7fe5ae82618a30232c4342e72fda1a93969ebd10836d40990368f08599ab592e3a1db793ae7119ce178785c19413c1a9bc2ac2d1e64d0d70f2fcef39e7cea00d9c2c440ff0051d5aae1617ceac0838f6ea4ba4c31d00e00798752b31b21dca04c5aba0c6e9a3b2e35a0c9bc47616370fb8c81075898282419f40a241a51a8a00d63b4d513af9d9b80d9b4e0e8f9c9ce26d9aed061b6f5e717b8fc0a250c536c1f099bf558d6ae5d30672d2d5180207131987e0d10e8cf48600fd43d3a5cf4afd1eba399feab2d734e3e8a655347996b593424bbfff162d5e9783fc7528f21d591b1c3027b7b40fbc2c804455f4414041ec922b1bd7832726f17021ca6e6655d02fc6202a67533c20f218f20ab061237f20664f58bc3f385dfda2266a013890a96d029e46503c75ba56f662debb4429d89e1e78feee4c3ac31de50003f2d64e72850eeb91b7a61346efc057140392aefb61f82fbd5bcab1a2526195dfdfe95a345d24fa9624093bd72faa58b9c908de8efec9599d53e890d0b95bb3c48961b817a0c51af0589b743166af5737c564cb1b7af15ea2bcc61cb6107c55e82e89035e9d07bf01445014d91921ff8d1c182a5ff0c0461390c854c700ee9d5f8e9a748cf0ef62acd82fd498d91517c8e6047c89774e9f98736272098cf02fb2d9f41e3f153299cb0d7ffe0ed69c5a2bb019a4f514baf947f5597fbaf82c02f0e7aade262fa0f5fff2eeac980b99f712e2175e3bec1e4fc85ff42d9831d9a13db4dbea55c65d2e15ad7a0bd88cc21f9b81594e3c9551ca6e29d22c01cf02152d100f927373f155150d036ceb0fce1609a49fe56eef4cc0f66987a466dc095fea4ac6a6f8af0646a1b25dfa778b7410a9c6eb6d2306470a1dfd0febd68b79611441729e7b72719df4754a587bb032396536c3d69bdb2684ef4c034bb0c8006c503c9c6f990311dbeabd7d87daf46ad9739778d2ba3ab812fc15da691d0b4f58f22f42d604edf3ce14c8590c491bdbcbc534075649253355b480e9432040712d42372d9a257c265770e624877d945b053829f295ac4c13901bca69ca23eda86cf0056e44e6c8fa96631a9f08f830467ba423125e5008651a9327a1524cf6f8b318d37ab81c21a7ee13b2c0ece2a8ac2f05511e83031ec144090f1aa18373c69054421fa01d96105bc2024997057219190104edbf8ef507cb3b2edcc8bf1475e7996b51e6ff42258c4ceccacf0ec945549e8cca67e775c02512c16a729e75808e2bea04336be7e92ba9c6139028b10dddb1887934b64d2585f365781059e90c0c0a42096c3864b682806a00b2ffad3615321fa2d92807578cc1ab28df3de370c62864f5de4406469fbea1685a37acd4d47571d29c6be2494a6ec30c6deeb64abb41d3ee2b3eb87548bd04f98fbe2d7ee11afb920a8caf4c86b64b449e329eff137a4f162116cc31cb0f588f80b9997871533ce20e7352a27c5dc00ea045bddd84ad50e7b963a4897d06622da5db95013c5107c9711504fa873bf2cb9746a3289c172f91b91f31b22450f5ac33441f0f65e062c03842504ea7a53b25920d22a4e3ae5603b0cc54767e4188fe85fb84761d92f5a38f09be35170aff3a8a0e731c7ab202fcca6a272fce944872a5d2110164607f6a691c3411fa2504557d1ee35a7ff7a892a8d15ec11fdcdd65a4208219becbde5de3b860a210bff0a930564bb489cee851c25ce7c61c6143a9a20e44b3a912d2aa9cc578f532c6990c38d47d0eaa1cc42f3505691e9250ef2543ae90116a101e30c656c0522992d0ac6d92a3ab2519d3e031267e6f41b88424a107399cf7b0f6319f5a537185e7bd3c3684676fc70bb3f3d8c6768473decb6bf3c8c3ee09e3a2a41779887118d09c8578c11903833202d326d54602c65d51416bd0a9441a517080003de7268b37c6f2f14e1409473a6941285b410c4a51051480ba629d9f110f7271968479db6f022c43d55447798d3cba028a48512f46419d0c61af91830c69a3e3dcca7c59a7eeabbb1a6fff265b1a68ffa6cace99fbe1a6bfaf8a3b1a66ffa3ad674dffbba2fd66459d217b214924432089feebdf7de4c798032a84a20f924666e6470f2dde00af1a68b740a3a774ba0ee01f76344d2ef66b9ba76cb6f66d9b2654bfbeeeeeeeeee6e0be6d0ee8efd8ad49312818b3e381984a594d8a249602d27814d5949772b23f5f252e2c4a27a148544284cff8dde63bfbe249f927e9540199b2dda20d7030e65908fc469225f3df248a82471be595ec9386ba03402c197e40976f54b9a4e754a297c750218dad78f40e284325b134a39036550aca1bf67e079d3e596637f82abec3d307a24f3267095f5c024d1e9aa6ed10fed988e8f4126433b988232e8d16502794c45ba456fca1e614319581989195f10c98af563b2288bbecba104426fc1114820b4532581b95c41162410fa9b28440a2920be64112e1914ba6450e89241a14b06852e1914ba4aa00cea167d97043ebd04a29750623f356671d14b2259450649214bb4cd6d0e13a5aa24302ac7abb0c4d9f27caaa84ee99e65599665dbbb7a72634e3a5fde331fef4d0ef3c0a19a655f98cad9b52f4c65ad7b9438dc439cbbece1cd1d186adc17259010e7ec977efbeab5d40130cdd2cb1ee3ad41b3357f6b507705fefce759f3785e35eb95cb830973c0b45919015798b79738a94c4fbb50394dcfe99b9f18ebe62e15ebf44a0f989e0216bc992c0984430944be7e3aa048ce64d1ae61aa2b72ce9928a448aca1a7d7e2eb84d8a2bfb929a18456ebfb6666c209cbc09c6ed17ad40181ab18758bc9a2dfeed822d3d78e0a7e8e77584172e30e2ea03cc901468fa45f3d40fc2029094b100eec7003469f435fc40e818b3ee4ab3b5a32c2f3fd2b84e955b40aa52cd7f562cc40483d309d9bfab9ba951dd7db0a9ea7ce47d810f8636564aec09aac2cdf40cadd319f2b8443699457db7b602889d3e5bed442e2745b6448d3668bfd89422214b92fbbf40be6bd813a3ea7a7de037b79f8c37c49baf529f9509f44fa5e3ed555dd0bf2bc3cbb41174231588449a16e0d798fb0a1d509deb57b46b4156495c9caf6092e92381d1812f1f223b28d25a888c1785c00632f7f015d18e2fef2bea442e270de397065a43b770e5c817b17b307aebaf7e42b244e95699381ab13b49edc302559aa9038dc2df7ad4002e9dbafcbbe28814820f22f5f6a26401ef5c40a5ef76577056f05fb80ecb6e35051606994b4051d47624c9b0692405ecc32d62f2a81ba2c518e21d4f0451d4ec8f185180168638839b0c8010e5d50c10551aa43fb56f431d77316f46ebf5095adb596ebee792dc8ec8123e8b20557e87e99dc77e7beeedab7e26eef3d5981c4c9c0d509ef3beec81a187a5f0952a09dfb4ae09ddbe2042414d1b4735f9198bd18cbbebd53dfa90579472e79d93eecb86c1f6e5ab60f5588b12634657beff1e5af1790ce16f7fee438d335fbee331d7ff2c964f5af6d097485047ac951c60e9858628a1a54a2161e4c5182063e315822d667b202895302c36dfbcbfb49fa757a9f3b7d2fdf492275abffd20343fda65bfda1d3e9a8fee9053c3d64bd3cc2500f5fbe081be298dc0cdc59fb0a56022df41db15b1c772e86572776eb26880d0c4204cece6580036dd6c92d26ab8bd87b20ca0a9647a0349aacdeeac0e176eed94369c47df7768804122f440289bf200324900c20d191b92787f8d503b38f9546c19748928b34924716a93461c8e0708c91011c8e99c3f115b33841858aa22c8660a5c9ab668e263a9811450e8e2802161f63fc8df1b101b4d67a31d56e0a3a51c896e9eb0f124ab38ad4bd0746b773dfd9ce3119b2e00eb7b2da996ce0cadef7c0aefdbe5dfb9064f40b1cb3f69d7b0d643264cfa47f77ee99cc5f7055c19b1c4a294da2d3ca5093b2bd3ba52d059d2884cbf6f5270ac129ddbe2279954bbfb8f78f98ee813c26d379b67aebc7ea967d89ead4c72c73295ba150faf69d12b8ca3670451f613b1cb8cac055f69db97d45c11b584eb76c12fb14ec75ec918028f44bc740bf04ae320c64a51fe15e027986764adfce633af723dbbd7b204fa9942d21a25dc7fb4ac7642a954a2ffd6a9ffd2dbd7b5cd59b3a90c7bb29ab5b26cb22b1ef6cc9c674ba65b9204d963d52129dda162efae0b40f75cef4ed3ba66fe04a7e7b0f6ca7f4fef69d9667e2bd2fc19dd97dbe07263d222d1891b9be0211b61d45446a9f85d94d56bbe2b60ca6693bf3a76b3f72ba86cf93fae947f05ffe02f268c7a8afc2380df52544ee75505fe9a4525f1dd1ae3d435d9a9d8e6f53a9bffc887614c883baf6179067680775ed3c2f4f3dc22c3ea15eb454ea8b302dc2b08ddc77fadcb7cb9fc9a25ce7954c974ea4cf64352c6adfe96be05715bcb09cb97d114629fd0dfde95934ebcfaea7ce0b44bd92ada497f4b348dafee6f4280ac1c97dfbd8968ba50f71fe02cf2e70ccf50480a16adc99781cb8d39d097e07aeee4d8fb0ed937986cc75b77d336b5f96bde373d4fb4e770f6432b49d097df70ddcf18e41267def3933bbb9563195ecbd6f75cfea965d5d50e7ddb202562a9365373bf07c58a9e8f8d0b7907d7886bccdf043a7cd9dc166b77427cb3ecb370c5cafd8186a9665b7fb6a965dcbc0150afdfb23f31ac8a3f10cf1dc37f7ed3ab85bf54e56fdd5f191e1be2f83f6954e832bfa7e0f0c5c51905270476392cd9f8cceb0d2e9fbfece7df78ef6093299d79e3357d9fd56d9ef6dc59fcefd62d71ae9948a94698c2f9f18e41db021fb2617e6f9fcdcc0bc37b9b0a9824d166c4aeed1a7c90d2ce63f17867a5819f07361310fbd05fcb06031437e6e60310f5fe8a7927904894049999c6651a4280427b71166654a737a57bf4aa7cf699d7ef1387d927ecd9cde64fa301ea96e7f4c1675b1268bda3a6cad32fd799a2fc2faa56f7a9e374f633ace993212cf2dd347afae6ab567d1bb8614a1dfa1d5caa36913d62ad5680b4acf4d29a5acd2afd46cd1d3148d39e0295bb1865e7a1f01648b7e7e3e4d0e8b12016129479980f000999e0359b1869ebb61496da19e034d88f9ca4b50a77bbe043da82f46c7c76775d37d781c9732bdb7a998c01d73d491c41c05aeea518f302432de513fe21d553a0fea289067be04ebbe08bba9bf745f7d29d577facc07d31df59c79573a9e573a8e30fc45d8ea8b301e5f84cd68a7c3dcc727759feede7d5eee837ae993f956f6f81116f3adec4dbf2bfbd2e7ea16bdf7a1e83bfa972f762bf5c56ec17cb15ba7aff34e474ac7204ff7d2eb77c4bbe9f643c1bb09e419dae95e3a8fe9de95e7ba6fb5a4850a4458f72544ea75ba8cd52dfa1a077d167dbc97aa95c9a22b7b17ccfb5616d461750bd67d11d609e17aa55661d52a15e9d473e74b0db4bf3dc21a5ca1909dfb11fb0de401798676b6dbf370cf6e4466f90a44d895738b4c2f270a52e79e933a3efdfbad562870d77e64fb0579ee8776eeb7f368308e3efb62dfa8edc4e1cee475c933c51466b95af5db9d7073a8d386d2a44dc7d8194c7736a6528e075415638cdddd7d43e96d1e44e263476d2ece9412480eadb5d264ba22121ff1a53694ca95b54229a52c9a158f19954c0c0c4de9e60917cad9b4da4e7e25642eebe4079379e45476658eebe487332a735e27bf9bb952273f9a39130ba9159540e27b64123277c2d854f23018dc8b8ca83971a69c12c8a9312d35a554956976bb5b39196b68d36ddbb44ee3eeedba9f042b99674b5dc599534e09a49b52109b32dcb44ff3a56321fc0196ef7704b76d05fab190ca8fcddd20a65e50276c2a795d6aa649eaa5793aeb2cd74bda3c9dfb54372c392abf138db1e6c65ea9e3b0e92b799f8cad3aedc6759e473579754a38c004ac8e76b7adb260939a953031e1cc166605ed6e9cf650d35621b871c668a7c8aa302be4bebc54eb3aef84d9ddcde33aea751f5742ae1438e5fad508a4662a909155272acb53e2c808a43ed6179b4ff81481bc8820f7252763ebabf3942b0972d7c8aa5576ed4b4b87806be5424ac9715ce7791d57a55e9b2b29b8cb755ec944c25327e17b216104cf084f4f900671b946e8e119e1e909801e349e111654ea2300011040288192e4481f86086000178ab3544368563dbc13f00058de0f166bc56a754849646b761e90f3f30712145670213424d6cc5f21648c478b005d9004923ac85792961b87032c7df0f07e0214a00b5abde4782e874dd34e5e14e04707d4adf94a850492507ed44820f294407187f84ad22d1ff2b4828b05c4d61c22444e2c1a92032ccfd5f098f18cf0f4344096e11995678404270cc4d54d89231f2f4a02998f1e9e02cb1054c978464870c2ae12ba3b6d64a5e2d2584296c5c078465850e90128cd19428580eb904abd40401843860c19121b105ff7f38249a217396211a5d82ed0044a62031c24251363278f33e46c5a53f645cabe12274649e73d65ca846a1b6eedda4f89fd680442eda3cd7405f9a3b22b3d6e8f25efeb382ab1e76ddb26a4fb91535187d2eb6cdb86ca42faa92d66995b9077c89e4ce33b8cd21e37146cf8d4c5e4f8d2ed64d7757793afdbad79ee9186fb3629a5bcf714ec08c43e469bed0a2a654251596ee9c27cb139f68d4a706aba90e51be7a87d314eac04c7749772319d29654265e612985e35737a699fd7e99706ba666b8600d81eaef27ce1ee74714ccc7430a917542a9b236ca1522251f1c39d425a9882c0208588700e2512b20a298fb284a3053ed062471969f460062cdec1c30f8248da82049014b0105f91445956e10150d64ea78d4744cbccc8d9ad4724dbb2fd81850a1918654a1330451ab97bce2b91b226ad1d715c2f8a6c14230833945025e4f938451509b4d1035212192f10238e184517330061e50926a61411840821e01eb047a4e518798641642215f1502a438a2ce8bd31e8c8f4d48b2892e04ea0c40e36d0e24a1a314c8185ae4c291165a091c3113588f1821391de5319641162898c070f320a27609ed73832a07023f7c8f4b70920caa8830654243147cc1272bcb1830e2f6cb1630ed1060d57460145115d670137d64822085464107a01169b10a28635ac3c81811b38c0e69435d0b0b50179665123cfd97509d041080829705851441111962e414fdce0081d888019499d6ec94f2192ecb072609c658a3ca7c032852529d32ff2b4a1541691691d9926894258a058606ac2ed249628f2a45dca08da4d333bdf19a5b4b36fc26277dfc07d4a6da535247d84241c76f5315261c4946cbf0853757b54145147b6df8451b08b35dd069e5de7199145e81fae3efab0b94ed59f647a4ef66b95d365500a34120808262bc90b701891327d920ec4d7e917882dfaf89910735b20be5c366164aad3af075c1ce4304691a3ec529b10852c892414894296d8ab4e884296641f04a29025f7a1ca4814b244034004a290259b0f0944214bb8004c200a59d209800251c812ef31d68403c8f4f40f884296943e42ac7921d6d0c74fc2106be8ed27638835f4d9277d3e2943aca1d73ed924d6d06f9f9ca1a3a1864c2f65904102352863b1861e049345534832599c0db82f3f04ac37c95cbf9511b9826ab228ca4a926913ba6e6e268bd21718ba26ebc6956990c9a2234814143894b124d3759344feb0543f240e8d2a0aa9a79f89af6b52154d16fd8906999e334df115a37cc1044aa06ec53e2593954402a1a61c91b8d909c195728cf6466cf5a7c4a98f9cc48143e2c4316dba78e70ee50950ac7982c3f8048712883b02cf679741fde2c0153d2773882f99145bcdbdf40e1c0a89e03830c6812e98cec3bd3b93a11d7c0cc6ee1e901cc20d427c491f4984dc2f7dde77b5609b031ea22f112056f2c0ee1c141c4a9fec2e08c5a30c24717490384f244e05fb2b23326b79843ac2a1f40192484c2ad24a16ed0cdc9dbbf4913f5188f6bea421da482e930584c8923edc3763dd6a19c764b50442b489b12fe788ac7e5f5026c59aee82b084024aa0c99acf40259305ba208174652281f45b281285c82e27c4979c23b6fa37372594d06a7ddfcce44099d4ad06ea4b21202853a8d04f0a0149209dd49f3f495deeecbca059a6c7b3452fbb5f69723fc6546ef9e514992c0ac310380c92a5e020d3868411563c66327da8424caca15721c69a5006658525afbc72260b29962b06dc0f5926fcb8912041e2b73222b303e62b46b6e847c8f42c7a170dd21d15b808983359946b8241ec43e2a42410da31b6e82da8d32dba051b65338091a6d9e03ccc01f29c947382bf3aa1d20a8394aeda4e41bfea91f44baef32a20fc5391729555d67bd319b83fdff5575afb22305f1156a3add656a36ec34786a25ce64fbf528fb56bc6662cb4b79fa57fb5888eb624d591347da4119738baccd8fcc97d3b6997a4389228a5540a8181638679eaa12b26c5e83572d3f9d3e36bfac8f593b1768263d2c83b71a96f5561be550557f5d935eea43a7eb9ea3933fb76609e7afd508079eaf508063894b19fd5230cd68f31e7f123325f813caacb9c07c833b4a3bacc795e1ef308cbeacb638e44751926431ab8937aea4c5e0e73096585fb7ad5875fbea1fa9576d57b602b0d5cb2f37215c824820fb1ccc30fdd47580c4c55653ef9d3ad7e89f5eb05bc571a751224313caf6384c6dcd6de2f54a13e54217ba8c2bdf65085eda10adc4315ba872a780f55305d85d2b9090373020a0c4fa92fc2a6cdc4f918fc4a9eb67159756140be5e4029740a86574921f97a01859acb6409f54b6e315b7d29b4a54a9f8b0257d0ae61b74c16e70487d66ec97d9bd4cf6278a2b470d107b7ca7d50e2f8a895473d1759b701cbcfeeeefe0d9149d554269b3e8d70e9dbfc0dcb1a65df3e16067d7e6e60a537b9301f9f2687716fe28275f76e02e99b5c58e9f3bd7d3f620d6263cfc8810c365ed1074b84d1860c72d0011c4930af0212b63d67e24f96be568c09084bc8f3dd176309e12b4f048409c8f3dcd661c1312c8d1061f5472381cc2af33777da2ca85a5f8a362388465bb45919b1ef3788d3af9ff11561159525d03503f7c39b70fa3f269836ab13ec5d9365634f9fc2b459dd2e27052c4c9b98e56317084aec27fb5638856b5647108db2edc9aa2e0af6f9646cb2a805b241f45688de02d15b28f4f66014627500407cd91d628b5ea57a79c9e133976f812490ae8a058afddc20a892c0524e1fb44f7f8993cad307f8c99a4960190ae0ed8ebbe66fe6cf9a70d4a06e4d2ecbdf695b90393bb941c312f4d4a09eb64f3b5375b89fd55a5fc12a44bf3a65b2e64c8e950dae9f4a02d3c74b1c2ff77db088a8c0212956dd26385541d54b1e4134ca3573f55bddfe4dff2c1f3f3a0ba27aa55f8bfa95a85621aa449528f75d3767193541cfc9223d6bac0a0cf2358298c4a19916e9168d6de810db504223a74424246c6c63c6e29f6f286351e28429b04b3fc856b6e4c34806fd7d21c75789635fc8992cfa0a466ec6623f40b11f1a62b1582c1693b108e81409114b1037bc807332259b60e6186bb8185fb6ce39e7b475c2a45e26aa4f139b66c9ebb8b969f366b81b0cbb158dc0692bed9b9e73ce97ad73ce396dbdaaee9e73fe180355e5d7986613d4c659be2757abd3adf9396790b96212afc054d05701fd89c662fdb98aa00e0563de8957600db2ba156132272bfe32176b49ba307be6bc42d0b4484d5e0af617e36bb55d29adad6989c1616a5a6270a031b3f722c5d8936f2c99b0b66d1a195946eb037ac2a627da75d36e66378d8cda37b31db5e184d2341f2f507d147da9d4b696a524e0192141aebf5f8871dba048636c78f94b4adaceee750fa82006ad97a230b4376dd3c8688eda904aa562686fdaa691c1d9d0b18dda10d3c57844ea63a80cedba6937b39b4646ed9bd98dda2063a45fbfc5105d2ac6a59766b68a416f66ab1320b6e8de79f5d28cdecc562757d2a657ca1b5bc6a8934b2fcd6c1583decc5627b66697da9a8951adad99137a6d7572e9a599ad62d09bd9ea044609a4d9d4f66be37e9570c9298c8167775f6edbc6654131243a3762b95662cb89f8dabe813226a5c8b108c48a745de7554dd3823ccfa3444230e4691fadb55aa954327550ecc6e58a75c923625fba78ea04992f191434244b58801ca1e4ac1571641409e43e06e59fca6641518282e6b51b83228fa4510c4a3d3ee7bad94b06758d38322681ccdbada98d81e5b35c99ac298b741081ccdf692485f27422d6cc5b19abb9416925d6ccc7dc804f37d334b9a5d80803c7df1cb16871a29b8b66c0f6a87ddd6c6da0cfd0fd7c130cdbdedb9b6098f66d035773dbbabbbb5b9b4912633addadf54bab7e2abdaf44c6b836b54ca7babba3544c8b74ab29c77dde4f5ff719eddbe9dbaefae4767c6dfb12fbd5d4e6f7b9c0eed75a8be8a46082eeeeee5a6b77777737fd6aceadaf75779da08ef69877641ec3bd7c72a63eee30a7c77cf8f593f9e59330fbc9ace3d12fe654f45ebe38bbbbbbbbbbbb63769ab0d02b380b3bb23c9d4066cc70cdd082d3b2c466c618593bba148b192d38007de5b0cb0bc8f5d70869079b03a46872fbf811c466480e87e43b06516e329ae426838cdc6550c9dd65782106d2c477087c053f8a7cb92dc2c82253faeb031bac327d06461837b432bdbd5fc829c07041a6148c2c99be7a44283e03a7e6ab63b1580c4a97a2f1f14336c9619021996689346ec9d4cb618ce3470e631d3df29450224105a55f647afa8b5567b9c8c8f437394574ba0042a64792c4874c9f829221327d0ab8a041cc0ba29f2dd32662e9028d202fac10929269c4725447a611cb1751647aea11a140f001c65bbc20cf4f0ac71af2e2dbe11b5f118605d083376e07649af33dcf43f3ae34abd32a593d3acf8ef0a292c1a899982ccb4c9b29dbaa649ba9cb36799cbc771f01638cefe1ee237c12f6f2f1accee3a88f676867751ee7a1b969a5fd48dff423b37add766e665956b3da73bb8f7927dbb60aeaf8cc5cf595e948e6799cebbe55fd08ef81791f7ef71dfceef51dc864a8823ba58f003259bdf41eef81715ce5b86d05b4a58a0b7218440a20949002081f29b9ad569936518a1feab06282cc758502c63d402634e0aabe37706705ae50e89bbe33cfe3f33c5efae9d475198fcc9eba9770878239e12a3ea577afd854f2e29d2f5162f7b1890be6fd07c3ba298f02559620113e4d2e4cbe0986f5c3ceb1458ffbe5f3e382f59b6098774a4fe7dbf3c0952655498ad458e99426a97a743c609e3a5dc5d01745b9a6fa5d936ae6a5f79879f7953ccffb8c577a0f6d2667caef73819e0108901565597d119d0ca460025a84b4465297ac76394a8a23e9a85f41821c05390a12647baca12bed0424108845518b4c2f8bc41a5ab4dd8416eaa5b764a5813a33251d1f9f98afee03731ef58b58435fbfcae5ab6078f5a86ac9f482f528d65c1466ae3a92d269aed3e3a58f67befbccc733f3d24d8f8f46f5c56e0d0183cc7c43da4b1f4fdfbb59f5d577545f814c86ee99f455bfe08ecc79804ce6659e3355e7f1c994beeede77c09df9ae33a7fa22cc743acc87fff2714f7da8cb7ca5c792cc5977999cfa224cc7c747869e580c751f9fd3f17d4aefc4982caa5332e113eaa72756a45b14a91e1551fad2934d141a7dfa29658d809c524a2adff2f600d3e75823604dffeacb17e9170563e36ec5c965c290a38fd0c6f276ee6e81b5df57a47e652a55f66560dbb6e42d1e2501850f599061048b971e9188bbb55dfb88b8ddda7e7b80577147fbe5398f0557f196037778327015c155dca9d75eefcd4a3c590f2cbc793b977d31592669ab660ef3ed29f40b7f7b0afa85fa76b965db697bd94c9b8cb283f4384fe6e3b67bf4fd992fc2e861beceb2ec34ebcfc874a00ea5adfa222cc24aefe9efd0ee974026316f701599947a60dbfb63d19f332222bd9507738e76db167f2b97ecf64734ed56035791477b06ae62846db75fce16dee46ddbb6edd4d3c9b6da712a26709697d06c82cc4ba807ea64194cf7e9c88029f496e56d3b927ec57c7becb845cee7e527e9ad50c84ebf939d824c86ba3329812beeb4037760de20931870c5812b14b4d31fd14eef79e8b51fb92fbd04f2f4ef230ce69ce9a9d331ccb9ed43b21d6f3f6d476d7fd99e326db09e582cb66de7b62d6ed45ba140af1d49ffbef44558f60d5cc1c6e83cd5c0451fdcf67ead60f72c7cd38f54fa6a02578f30f95d9ca4ef53e87b9dbe47d21883ab4ee61e31d39c36127eef53e8178f5fd5afcc6fd24ccc3502cb29d2d31e3d1d1487d2ee39ae47844daf02f83bddf4d15cd6e3cb406f3a09abf378f4565d0fe9795a5ceeab4e27d6dc7337e025de4d882d7855c50447bbe91159c27dd5813a271a1d1f9f99afeea33a8fa7708fa45ff4f73a51c8cd3756ca4362cdbd8a093c733824dfd37b1c09cd4778f47448a85c48f846e8d109e1ba0658b74cd6bd5bf23d773ad1609a8f87f20c75e7a9c7f5f83bf5f81db8436f5a7d1186fa224c5b7da8cf7ca9abbe97c77ca5cb7c30e7f19d503f7d2b1dfc0a4418369dc71761599565b612eaf874d33fed9aeef1fde91e75bbe4fbd43dcc054af5c4ae067eb2eee571b9c0b29efea27aec340121c2ec8e760dc4f61a78bbd53044c0d2852eb96a9fddd99e6d9fdebbd5158830150d789e3e54b140b32fc222855d29e79cf1540a579063ac913d67151b304c8e58ae60230ddc9951c7155dd45004e50a1c58b942685ac12f3962b98287a9c5153f4d059be6a043609c86ec82f117f3079f8aba087756fa07734a4839b03da2c88a28d305389523162b7ee60dd834470381711a34098cf2820b6cb39864e03bc51c036f4e483a3047850261ae0832aa0822081818555185b5da8401e38a15188f70831b5641403d8ee05144e3ba31c061c675e30a7263c49515161b74500101c4c375b1c040838cebc615e4e667e676a92146882eaa2d4f6082380a02490667b1432a29c6759fc082c38bebc615e4268a95285960f01336b801454451ea092176381901f4f204141b700c504f00fd98848c4e4fc47043498a10dec11c35783f1c9976904443170324624065b6620e2f4e63861f6236146d51439e1264c50c425469a205322052220f6124444326e2c787a0241c6ec85292b0ec000627b988203a7e5837ae1ce44628d6ad78d9b8e107a26e451e20c18051003168142f72e89a30601414b307b8bbc1bc037743730011a5e30022072d8080a1b3604f08f3094cdf6825b005422369692fbe00ca42065cab9048f866996f60ee0654075823923e20623a81b31cb1fc10b4056b3962f9e10530605828b85066c9c9adf391ea702a1aad4ac87107a81b24731e2e5d53274f9dfc5499436d91b954bca2820969e69125c8424a7e314be6646260522f2fa853662b6d1eb81871624f2981c47331d2b6944a39e79c734e2dbdcad3049a4ff39833dd2a2a5363c750982c75fb86d2eeeebeb9420331d7478f03979c80412226ef26225dc8424a226165b66acd553a1ce0572ece80c10537c061c3131dc0ea0f1974308428551081c50fb05aeb3ba4ee9e2c23d7d77bdd1781c9aa47c951eb7592283141ce000bf6294c1beb651988b7c919d9aedd848d03c3ec28d0a696d3f53132e0bbea8f69336d3290fa6005b9a9a679b281bbf1b5de7befbd5bbd96afab09517e25baa1b9cfd201acc317ddfbab81a1eade6d0c9cbd7e03433c421038bcaf0f2bd10d2d68826e21b93f26f20c2c433f3017e40d4c4441033644cf55a27e7ddd27ea578455b992bb978861cc008a2662d0431c4be4a08223d24032a2062e50eca05588ebcbc3dc81b3e774f68577857ef42c1816c936bcc9597c16f6e4ac43989b5c2bf863b2581306576293a026504e5819c99952601122160310175760b608aca45f3a462bf457d40e814309c5d5adfebd5d0d4645596691014a1619805cfd52a924a7244949bf22eceae0d8ea639584416cf5a514a1044c4289354b40e9cb1f2529f8775fc0e1cd4d8425e2e7300bc6d9a24b8850504699acbe09314b28ab2ba04869149ee6fb78aa7d751ff3f6f88ee67abbf2316d3cd7b459758f995b71ae7ef9f02107963ea664a12284850cae98c28b9ea12c340c495143d2141b4a1f0b8b22dd77bb283705763071a58932ac48a30b0df040063ae812a50b243736ed4a992f927d3f7c18e1234608226e50c41a5ec4ae88678cf1d386166bfc6c7106966508e16305094b1d61c01c1005961940a1450617ec009b77dd58283c82206bc71823555d33039bd1dd3d678c34968438a594d27aedbdd7d6396777539517ddd4d6309b8ba65faca1e006725fd68af1e468cd322e2a17b66e31bbd66e1cb6682a31c6b8d6da8dc316b5bbbbc034ca0cca6aad9946bbabcd7c5148983d00ccb4565b3d231c08bf13be7153e6e2970b2a59d1a4c8852173514c2a52a24a9a4160cc3be79d36e0538e597cac28820f152db02ac72c3e51b2f8bce043bcf45631ba170b9ec9314b0c5c620c465022ac0a9478b36cb3d90bf8de23285cee80d9000a172d31802dc1a5a807580db85cd7134de6c5b7c34c602e493c815129e2e2c30e30268e4c34dc99658816040168ca10863065cb0072c4428416584555bf74b16e54f2b254aea42da63cd5b823cf39e79c73a6b2c0f004153138c9fd4fc5c619332facd1c571c409020f3c6060801c763134bc78a189dc27c123d2780d187c73ce396d46a0436867891da145eef7c0734e36aa909973cef9a2c10ab80306b9bf82e30998225891bbcfc323d2375ee10697120acb8b76118228c08c0a8a8863c71c79ce39aff2884c35e0f064587664d1aa376280c139801d2ed8d2aa158aac0011f42387195e7c4007357431658c28b82051e28d36e69c13c60e4c456e551d3ae4eeeefe135d8a664b9ef31713414a9e5711818e3c8f3a123b8ea0b8800c3b7ed87204091cfc9071c7143c202272438e533c7944a8a4828e1e884cd7e753850ee8c0820e2184e8d9810b164adcf1859520fc00d6dd3d83dc6bb0913b2677774bd93bcc540f0a30c70cf2a4e327cf63d559ae1b2ca902e4f0c71086c8f343e822cfabba2891e7cf9ac327cf975c7db9051c544adddd2dc4935d8ac6c72bcf5facba1c6d6496942fb4e040099f2237dee876c10fe6b0b283306ae8c20958cf2472271991bb5f2402dddddd1e117ab40a4395c32063c48184107c840084108880458e2e8280459e376be06046f38850da81394c49118421387e90fb570547504a5279931b8e2472df7a44faaa6820c20c7e6240a207467054dc588207666c39030ad8d19c436a6c99734e1a20622af71b41bcd1456e38a4e456c537b27477d3f860334248e3c38d29bc480a4a6a22cfff8d17e479caba54d540102277777723a1472c7049a1a916d2641f39fcd1051b3d60438c368e70638635ecc8f3d3d5c61324259ca08183345a9003c50f5d880083157404013623529611c94422cf27f24c61d161983b70c8ca52c5e3898a0729205001630d1c721faff145ee4b9514f2658c1cfaf891860d5aa431861a4ba851048421ba14953736681f92ea44d59c32e4f9aa135bf30d86b7863c1f39703e4a95ec215751b39b755bf6ed6bc185ac0de150c64a9f6f03975e2afdf49d929d3ba563904909348137567a09bcf28c19a821831e6250128bc15c47c196fbdc3bee71b2c2ec2c74b9e3c090e6ee1ec759fbd8ded7c28fee76d62d371bc54299ac693b299c7d92e75fa5c07dfbd03e21808f3db77944ec8dd53332df359050644cfe74500765369115e26419a193d5fd15a549a6cd7c4918320b687452153628082a398c4768d491c3b885841c462e31298c71d0acbbfb26ccdcd49340b747845e02b5808b3eba548337d2efa6f4f426c8537048be60d84781ccf436fbdaceebcc2399cf29f20667efb52fbbfc26119d2bc17869a469910f230efd9c728909682622b63a2261f51256c19f0e56bfeacbbc3ac194fba64cc40aba63b722ae83b3e7a6903c02430a214d23a21009b6161f4f704881a2bc3d05a23710a24014880251200a84baf784b10929caa44fa116ea5817d1b18ebdfb06fe7e664ad0586b3f83a290ecd674fb2925da501a22cbde9ac046ba0a772981e10fd905e581e10d2f8764b105c3677b497f628dbd07865c1de1c137bda85ff108894b5214223f26a7f3c060fa095c1dc13c2726a69f40ece998c06f6815272b0a515fe9a226ea2ad65a6badb5d65a6bed4d4e111d24497c3cc1d6fef64b757b962b15cbf1934b9bbe1e18fd49b2dd25dba657b4e9c53450d3344dd3348ee3b82c3914a96ec8381f381c2cb64135ef9abff3f50c3b4a4094871cf2bcebcebfa59474981ccba59debefebceb2650679e23c77c8f3ae3b7fe9e2ce711bd881b40558e6985599052bed391fe58b1ddc7dd0c8a654d5612a1b3705caf41c125273e9a4ee4263f4a7632dd4b17ed5d37711d1a6bf88aca37e55b089fa0fef8f8edd7b338cad4a55a7e0d9f348e24cb08926185148834d833e4aa289ba7499b1f933812694195454647484c4254942e921aa7225724941490afa4561577e24d0cd4d4e119dfb7882c329448455fda2f10acb45d4d3680acd2ae114a24059109c8c1e88421a0c650c59e24850fabc16900318c0c2986548906c5e9adc5d303c2b4f0b821d48c15085f3a4d743c232a75f49c02b57a41097a45fd227b6e69d70791225c9f4640cb1664a24e41419439e9753f27c366b0fdc121370f65949f608db8edc679bf608d3aaabfea6fef5acfaecd3c96e0237a5e6068c5a0b6647e49c19bd0a4c5806baba15a336d87353b8c0499ec9710a1e9232d79dab5f1326c11c09c4de1e15854c10166baccbeb38cdf4a5a05b25d0bbb7c39853a9b6df5e5089d6466604ce40ef0b5c7fbb1575ec91d8e7d817b1ae2978fef6cc497dc04927ae6093a4a1a69e634d9bc89aef6f7eb1adb4968e00546bad3ff0c9b5d64c4a4b25d0939a53e4e27eade8fd91ed573bcf045774b347349e06ef7610c378b8f368bf5703573bdafbda6bad2d303007ae76eee7ef6badb5d65a6badb5d60cacb5d65a6badd92acb6c0fa25a6bf50151aeb5d65aeb118565960ec13a0ea8dd11755e9957b28d77daa4723c0f2cdcf09065b75455030e25950c6502c5f840afdd5e665f4582fe8acc326d680426cbbe3f7b67db401c597163e292b57e0e982c5b3b2f67b26c0b067802d53981a2109beda55183abecdcb7dba33cfbf34738f076cb7e7b837ddb0203cfcb2a13680675cb5a6bed75a450b7ecb66d27620365956e59fb9dcba09c22560a6596ab5bf67775af149a2f9a9824b2228b22962d45d93e8a91add11d39ecaa51163d18c561b31c7b3bc401b6883c64dad4cfdbbbfad5b757b82f9a9c410710d8c621441bb20e7c7f500712cd82352fda086cb39836c0770ada04be3ba03f60ad4afb802d0d6619d822415b30443771460c9c9ce1823ec3a7d5a043c2a1050e2e5ab840411a023ee2414b1632982ec795e8a0e1078d1dba7c814392154ed8006aa2056c74a9810d44515e90d728c2a2450b6e504387dcc615357608e2820720206521050e998d2bb8bb4fb171af2c815739629182944397137cc2228a1427c29042d486942957a4e4d03d9012c318d80d27882e8d416e86dc683192c901a58ea8242c980a9b182a4533100000a0008315003020100e874462d1681485ba6c1f14800c76aa467450970ba45110c328ca18648c210000600020c41820a9a26d00847b6b0128020ddb83f41b19e8719d962746b226f59aaa2f1baec8fbaec571efad7439ff382dcec3a0cb93b35207a3d3e90a71ac67fa6383a2037d13055532547de8ba807ae399f943884c38f1e1920e21f60abc26ac6e68422429729f84304cffed49c3260cd244c79d10d55ebf86a49426b6ac0d0b03f6e1d54aa67ec4a33e9dcc67e5116723adf2d2cdae1c6a0779268927d241efb5c250862325efe6bcf7d70aaf454b7d3cf410dc859ad54ad4520e2104bd07e89220157958681459bc4e0ba02b770bd81b911c607d831c707d98e345d92cf6e3387d045123f0fd174a47af91db47fa7d54b74f853d7e83c917e059594494840a6b807cbd5f98f1c0741719359a3d4a9be956e247d82cb060bd97665f14b99cdb851f48404567636ae15b7c790f9e0bfe33dab8868a07e298eb7c1ecfba5f580b32e8aa6e50cc3ef31a70181a916d2144f29c4f4dc4a11f25d1584cd8d25b93c22c677ab42515c482860a1781dc2ff276daa2f6f54d6c2e1c2ebd2fc70b567987cc6ff3c66a9206fe96b9b05bfce592ba28f2cde0729d862a0aaf8275c15b895aef932d38adfb4f1b68d3867c1504cd2b1897640a2844481e4e35ff225c70f6147d599767489bcce61254d7a50751fb9c533369a07790e1a63065c862a2e12251d30600ceaa2a3818b2a27b37c29b986c6cd0d7b50057c81283412283968f8e410b37326819c9390bdf82dcfe7989374a715f3db6f3c086e681a2fef8b8c4357f2999e8a0532d7e1fe40177d91e7db6a418aeb9d4843065fbd3e89706aeedd517a469123b4b7c19de14e4314571ac2ad718c152afe532412c87854a5bde1dcb6eb593bc9685184664541d4e8d2f2b5443ee9265301d852278af1cf129d09313e0f9f8724ca1043eb4dcc86f8e69dea2b014616d9fe47fa782be0c3b2c3cf298472de81e99c92de0b58b86512851b075bb1cd37a590380c56015642bce7f403e35d690e5ebdafc9c4d01eeaa31978b3dd162d56acce273e24d72609951f2acf15aba4117442f67fd71fdecd6c56815bc06367b9be34bc2d3ddb5950354eeeb51fb4e5cd55984035f5dec9f0320080d96652d15e1944277536895b158577b60a98cc11e344de02b3dda58b23e66e4d5e5580fa09f30b8dcefd19e6934a8108b5dcf4506c3101785dcb4e42d662c8ff2ebce2df821d4ea6a8d8b41fa55cefdbea06cad513ae55ebd6a68de5e5cb61c2f7c28e9978c57a5ce838c02cd661df29c8d4f5847e726f06c56db3e3e39a0820208c8176c803ce2ba89f2bf74605989309cdc6ed3e1eca5ce7a9e4dfb3f153426488c65a3cc81cde822dafa58272d97c6979544bb650227855bd71dde2917e11201135567c89481b5a958e16ede08028494c3cc18dea3b4b918ed1ee54eff415409fb197521e2133b6b192be82778f45879ff37878c2f175372a0dd5a94879ad440a4d7c9ada82819f5773d4eb96f7bc10eb757f44f49a8d650261a38107b6cd74a67ee718377f1669f41fa1f1d4605ecdf266a8adca197be84a95d5d83636d81eb4f59496d2a943433bc456d2019877b2efc731c9a7494b74d231d8ee2d3ecdea6d4cd5d18747486d0a7b0408c050723d4a6d6bb2abc63c05faf605cf03eb82068ece8a579d17f6c3d767b159be05eb414b7eb6f3623a84cb13814cde2d79b60b88c9eee893f53bf237521313ae28357f95fb8957c37a2d1a2d5b0efda89ae60ff9e1d68b43a74028784e7172a2ee8b47ef47b39529f94e484ace3dd580456e1f1a567fe207bdb36228fc6e22c9de4c75b00c87bbe0979815942aa8a524ab313e5a015e02f58514a651aa54de4628bb60b4892f6dbfe9059ac5218ff1448109b2a7fc1142370ce66d2389335c3717b0b074b41f24a3ac02e9f0d4e2033430991d81bf1cb3d2c24c18f92d033c2a0b26498b3e1a102b11dab56522a4e04fa6d5122e3e663f20e8323abff23e922cc493d480db50ff1bb7e3f12100c19325e542b8e9f65aab2f859aba2c3d272d7c409f12ac2b4e869e5735d25d635c10f5ca412948fb948b4ee9470228a7f88baae5a3c6b36c5b7a0beb69022ac8650fca09a2a88b92e9b52968d59a8be86eee706afb1834498fb0bfb1032f3ddc7030336f6b3fd1615623b42082730f66e4817c31adc8fe2cd1ce02446a370c5285233184556722ea27535c7a39d102b39d5224616cd3cf2b4c9d6bd732c2535c601f144cdaa7f6625b861f96936270e8e05544b5de440a7d34293d3c1649ee2b94e1da28de9d59a5e1e752f561a91b63e33d734655d93e3e8474de43e9fd21b082c682296c4e33ca9bf4171f5ca5cfd6eebadbc13ab7ed259837710e1aa8167d9d449da57dccdd0b9d184815bd1e66f833113e4d6428d8f552f23864ddb4fd2020461aa8854308ab4d4f17f17c2b2566673112ab813045f75f5a7b53c470dce66b282e06c7057bdead702ca23ad5574727ce718eec94fe8c69dd579a5f6089e5b176880f98a3f50c7171fa66f16de9ab7c3e6ae775d170dded8e62fde3ee89f59d6c7862e76a0f31dd41c385f542975b7d0aba48adcab569330ce0d8e1f94318335489f409bf11a14b69829f46dc54a0371225027dcba72d8c4eeb49f411d533a50007a8fee7ca1ab83eef12d15b0e82eda743b01553b4672144a121164ca48a78d8891b5bd290081aa9d84dcf30364b92592f147800465693408e7b352381d1a9d788166b44641fed08c1420b3e7cdeb36f0cc4aab99d2d94acfd9e4f1151d6730dda7c23ceadc56668736ff21a29e1f9de5222a6fbcb1a24b300360b35428747fcf3970346d9655ce086475947641561120e224e41a4964277fb568465d8bf0ae1c4f82f95cf940c72f7d865fbeea06ca344465ff5265b21ebdf9af22ba7c63dea76e6aa121009e6d7ef06480b818544a7fd578033fc827fb96357da2e49a67b64b20ee6c9c8b9d1bf875e150718de9443b92b52cdc9cfc0bb6868236897e43b3d7e31824ab3fc6d181262201bc85ab6509281376aa9c98f0f6c92440c5170baf135219f39298a341293d7d4989e1a1548f1b7ca67453cc41bdab1c70c9969c679c70b1864ab4e40174a2530d9ed3135417e63a8fe965de1b3e6f3b072c256415a385d22ea731047122694537262855c7b1237f32e19f2b537aae8e4a45402ab270c050d57973bb393258213f9686dc43fc82a08c9891c61fc229bbda8fc2d7bb258911496bde646ae217f2dbaba0707eba7e4f7272440a0506085e45963d212b570e0a9684b157a58c150581b5f458b2dcf445e0c8df41ef22b6bb4141cb95ba2dc8938905323305f2651d0cdeba796b9ea688260a45d0e684ffa525837103e67ddd0a1113422e1f5f7833779ccf91b0660ddd6085d94e138feb329069a0e69070bb2f27f0124c98968c023c5c5f9427d546c6fc19f6545596c2fbde3b2ac485cf9cfb27ea9c99aebe4236a1173bcfbac56ac85dd805a96caf94b81b0c9e0cd5a8de517e88260e1fbbb59f07946dff6d08db31999d182ecf6724141e94ce2ce6559dcf317de38287828bb9c1451af9ebc6019c25e814419a2f0763d5be84468db8584791c61f424e9c52fcca8d967d02b16ec10e29d4ed8d384a8ffa0bfe301205e6455ee798de14a562fe65d615b446bce63d88f06e2ee45ee49e87fdff83c3ccf159630431c31c4e4eff0eb83c6000f2dce07094afacb284a17ef2eaa52df9c0510a728e3907270f507bfe023600f187b8d84a8ab62c95cc143ce8545cab4aff640bc215052eed7b11c78ac144888e27049a740b91d0dd0991da6bba884540c2d44a313d7058d5ed9c8f6b1ff41fd3485b3124b06d00cdf256f620e004292c49bc8581a248f541a73185c8b0432841e97dd5ce6c786328812f40ff7f5f9e07c2a625430e98f4af66e15e3005de84fe1da5928713023c86c042357c83f22f25271f82ab1cc6614f229b23bb803bd4f1d3f24e23fd019b30c570eb73421e0923b7910e15076dd54e08dcf91f56ab7721d1d6c01c84ccccd5bbd58975aeedd5b52d9205b00ce67674e0fdda955e421fbc7065d71132f6ca35ee79262e4a5b5b4185f52bebbad82d555319f6bc7b919d5fe3012c5c7789080ae5d00ddc9f22460456d33a350c837f90a050c471fc1390038daa7e2629e496a511838f7cc4606dde0a1f58fd0251c310e28e003a060902ba1bb4f963319db6621b2164ead83ec4e47c990ffe3020ae255c1cb6b16773421b6f5eb6f7d488e36bd21dcbacb6435b6fefd8e09fbebd10c1a6b54b6ba977875244a9ffa4eea491834651aa2bcadc858da53be622d7da9993ad9e4b984ab887bcf06f2d991f30b9e9a8f11bfd9534e41b507a2d7452e0bcfe94eb3e8e5c50750f44d1df5dd9e754eeedd115392f70368c5c950bd081aea9c6d198b7a0c79503869a1fc149ef740aa97c94de69a28b28f449566c4e23e016721a6b3bd84422f28d36f5881edb61115a6c31ef5f2e4ee2d3135711ce1a164a09683949ca975072e1b1f492f8abaed120779bf375650f4285c5cb78f3821a49b6965c38c27e9d6446f2a7f0447de7342d07d61ace8c79a967a135edd5ead80878c451fafb21b9a84c823bd89d3331b56cc237f4fab401e194e5154b82b686929b08d4236491549f568daa1ccf2b0ea3e8ee73ba5760e34b7093a5867444be94440a9399b6a04200754d6fd44a3db7b8a3d245ae41f5f4fff4d390eb49274bd055b1ad899ec0518bfe492c2048318e08460441cfb9cae45bdb09527bea58e32428287153679a2c9b6df508560d4fbd38750406fe67ee3a1ab97eda2a3f3f059f12dd1d8da2f39a4e6d7e49571190e9aa8a9f093a40f10f35c64b5da72fc1e3808aa6000ee29ecc231d18a006fe8eb203cedde02f66c46977995797ae9615187922d1d6723f16e35ba639017334e9d992e1b3dc68278a6b36cefe0bab837c07af63f18405459c2d210aa2fbb0cd2090167907c27a0c230c9786048d176392acef4ff22a3fff48fb35c2cb324d375bdb2aebab18bf44b7ba52671eb33a0c25e6a1123fd329a40eeb4b7685eea885e9fce1f5a2582e630fe9dd35a5547b2bbd6546a594ac48a594141946b2577e31b1a78860ee89a56fe18c24da6b149c9a436225ebae21395e371c7e728dd9963310158b8927c7da97d75cd5afcf182c6d0034e07e8da5457c3e4f9b8b86acfb4a6323629414f09ec3b2e233a5498f01a543e10017a9c825a2a201098ad361e678c99cc6445c820dac609a05d13dfd9c9fbbd94012b9a239b6cfab35d8a9988bb594516f74ba30b43bd4a20a99f29f5ad561232c3bd4647f25faf0562479d7595ae2921a5a5462a8380dcbaaf7509e51d22a564c44f7621ce70538875f97bdc180949b93ebdf4d42c8cb8ec63c401145e121c690c0d0beffdc3e0fc16022f14e29cfda1ddbd543e02113a273cbe30853ec7de72510b2f9f4520c1233fae1f26625899e9679682b37016b9df3044d28f2d9b3262039b5fdf141d0c262e96dd9c9d513dcf9646935afca88f45e15cfad0ca44b315a804c806212209b50be7a6fc943df6dafddf3d7e2e34d57b290ee0d261fc4f60b7061259f0d8456007ffeaae4402b0dfbea0e0e722c1bc0b5776f9497253dc23ade308c5e93764ea6a57f4f444a3da0989d7c3779dec3cfb3929414a12a4cfad813b760cf8d7e3db2b81e23c3c6c1563a819c0f28cdc74a5d767f6109d52e97612db0ef2bb874678c4569829404e3fe12331cc5863307d37d1e9c064d20537b34a1596d6069d42c8556efe010f2e1d18a6092faeb56739d10f497894e1d02c83a4d74d217879e8a88318f8fdbfa3a764722b96d33fb28246e38b5515d08ba46866e070949de1b977e68f422e7849ba660f124f02dc4c369fa0ac8c3b939b07d9e19e8addd010e68a6add1024e17f88e610b5fa38c7a2876c04a83f1099314ede4fcf7d57127cb120bb433c362d4a6091a67d21fa783df68d00391815ba80e2798b331c6b47939a35cc6d4a2f405d3cbba5c6be75a0cd62b87519b6bc567b4be4b4ebfa588108b6339195a3880f97062b61d3a6994fdec27475f372b16958fae683b91f95b558f240e23a4eccb087d4d52fd3568e63ae22888bd2f40aba91d8b80d86d160c20f5c874310e8b43e9fd07c59aadc2a6084c2f51c657653bc39ec776d4df00877b11d00504b811fa827f5d26b27b03cb8b434500e5c8cc18dbb984613bd20e9d0259a1d4b1f2e4f7c468db50239c30456e5c8870faa60b1ece396cf76cc3cb39f172e66d516d68c33c90ba78c653c3c85383dc8a5b3307ec24e0d4f5a7cb616f4ee30389b82fa8b8ea726b32bdc08bc0016ada7b1b1a300d299d2325ce9d77561829b3211123815dc2ee2713031010591d6417bfd48b745ed8194b24fe1e1b67d26d6469a200ffddfa6ecfde2980256dadeb12fcb830d5d47e0478ce74351eb067e40ac3fb25565f00b373173e6547428b36369a697a944c514af595a4ecaea922f5a20d2857a48af8e28d73193d1eeb554d3a403c01c47e7d0bed9b8e1339a1cfe4b347a653722b3484ed2ab1ba5cf9635cd48c6f65a97a68d40f4b87d200caf4017c69c7b4c1c6128304acd32e4afbcb4eccd213c6940032dd3114e6824ff343dc08b34eb72bfcfb4fb58235227b3f79cd77685562ef7587f29132439d1c30a2c85e590b83f1a83bfd01b32f4e66d735c27027a1c208abe60c1a55a1a65371f00c508d4466ec4e98d479bf272819da0d60989524d3bd211e725d4400f03a146a2f468809cf404b640d2f69dfb021fce442d4839ddf6a75359927384288e4de7aec48675e8a9e8cafac7a8a36cc0ac1a2c9598dae8f77266453f60c1e3520317d105fc1150d1fb70305dbe7c7ad4e79c1d7325fbc1e8484f2a8640088e548063a3959e59692d07ff243642f0ed1641ef08ef68c4b80b5eb37c63ed4b6c4157d1cc74796329fa6928fc1108ff4851c049e79b9a34141375124ea6cd6eac3f6b8d736b3c370c8a78b91641991af87b0ea671b81a8fafe60b1d8dce49c8b79966c42f915d5cdfa375dad10c4029a74b6cf0137e6839b272030455fd571ccb0de1905f739559f7231fc2cf4a0f503ad91815f3bba8aae1ea6846730c8d8c3d737c2096ce9a428e1a0e93a7fdf83877ef6b602250f75aed32311c3ee35734e2fa90248937b47103e8ffeb49a9c19d65cdf54646b02118070f46f03639529e2138bfa207ad7e0a1ffb9b4e4089df58b5f14289635996c07c8771d4d1b071822d424961e4f0cfacc5de9c3a536dee1789bf819e18d2f1932404ba47993f74ccf06fdb327c6aa871e5b208e4dcb127155b6a9f51408fd9b0e617bf2cd706b0b80f9099472d7a039656d2a1e9e495c9a7212475251e23ee98f25c87cebadfce6e8298b71395a54e63836087b10c97a89c8c2b572927f513ec750f2c971b816e36003fb2b8930b34f0808af1323a28eaf67801004f550a495731713246d071f91da9dc2daf9a7b0fe88548aa62a1445aa6b7ffc93a5743879b35b23444da2e6149be041794ad7cdacf33485c7bf80702071f643f5c167dac740512a9d16e2e8c83f8c0151319e7f90a632addbd41e42e0127224f18de94abbcc1527f9084059480cd239d2dc33f0044ca50d2ae65da49bf24f776824f0e6be97e540ef065655c42fe069b1d0f56ccfe606147789be950b60cbf7d79197f327d9e6cbcf1e25e81dadd3372686f9c06ea54cbdec32062b7d29872c2b908b5497e48b545b0dc4c7244ae958dde4c794b072c89c3ab1d2ce73697e15341efba8654bef526cef5b8cdb1f107e346ba990c700c696af75654818d4cbc7506f374d8e88d929770af2a36ad96f42514929eef4beb8686305cf22a22c95759c71d4712af0dc2b8a605985bfceccf3ac638095ed5a9578a70aa796d3d1ad58131eab8eb66fb7b875af97f6167958e89d97447808528862a656572284c33480c6f3738010d06dd250088efbd1dce732ae81fe57f7eec91e5dc7e6dbf643313143e674bcb3c1a5db3e1a7c1f063ad89cb5dba7a43dad9dcb65521138e74373f5622a101ae142ef723d36c4d79f1a49155c394a70e7a726643fb8abbcc2762070b126268b39356b38efad7e2a6bd831a6a1a1a8efec26ea6f9e154049a476eeda6cb3023c5aa10f8d526e2e10c172a1acba591bb2356eb1c026df288afcf5342eab321491a2291974b9b32c7514007caa3cd5313de93840b0ecf032fd170ea93991340c4403ddf00b69deb2e2cc161497e4f2e00da33e53aec6238e355e359548d6e82e548487ee664840d71c1ef5fc70c0fff9f856850aeadaa9ac06a54ba656671de111217deb4cc50f68ba5604a50650f4b3378d00829838fc957ebcb2a2bc42c6e2d60d4c3d18fc511b4345a9d6eb28dd75d462e06f4accc2587f41072aab82a9cb14ad6dfb7d30599d33454c5dcfbc9f21502ce2ea05a459dc5bed2813131b8f5c77988dd72fc79218e1854a6084cb61bddfae63fa2ffa8b52fd145310d253648088459f15fd704c4cb7c4c7e97c31791eff10c27218c5ba25c0ce804c043d464bdd607eb16f2e71fd723ad2542bd790a797d0780ae5d610cbebe8e3d4c00ede13167b5dda3ced6d01a098f3973515c9fdcd1a2a717a5953a1dcdf02a05248afb52a20b931dc979bbc13ddd9ffcb5d1e72efb684a6cf5fd6543c7497b99cb4707f3ff0ce6b9b043119d3ab0a5524d797e40c3e756fde25b70efbd5897d58462938c54d8183611ef97594d6b7d8100c388943ebdd98a147e22ccd2a99e08449f3ab4cc440f52a043fd0a909ecdb939ae114d1a328bd23f230f7ea5e23deeeb9c4bdb92416b09f95d84faa68af474d62dfd1443cedc775964f2620f2ab8c069f8f111b3852a17e9e455e95b10efdeb9effaad8bbe2d0062d4ee9485bcc633f9cb8e2afd1a9dcae45d67c4cd5dc39a6f86a1d8a8f3420c3ddc2f7bba4e825f5a7f7ffeac5096280767ca3ed01ba771984235398aac64a4017751ca0aedf5bd582a636fff5fcafe76ff4c4091240bb1bffb2171eca474b47ef3f7ad87334f3c734810d81d66c04ee54cfef97265e6cee457b7987a631c20036dacd95f6451da9d81418bf4ea4dedef2afd7efcdfee6bfeecdf64379b1e39b6f83eb8d0c8e0bea0400ea560216609513530a59c0d29fdc595b3981653722d8d37940db0bb9774a999b66f6a3682d88e0c5e7287385a3f0ff6d3b6091e1c97be2011de621354974a2d9d7636587d0c5d3990e6f2e4caab1267e6eaa8a4195a53b565dec0d2a7542a64266e85cb1c6700b9cf02aa89284a3c97aecf7f8c8f963db0cd8ede12b4ee1e40cd71b2444315bc654a5d218cce8133a5622632ede0ff3b9bbf3a9b7a67d206fe6c7a5d8f219540455a32fcee5dd42a9915277ce9be5e3ed89df03d00a7f56f6161a9f3d0a1c841cd629ca9b640d99924be2e33dc8b30646fede2b35df13da7fdccd1f1e1d8e3e8218f87bf08719965f682ccfe0d91dba572f5ad720c4865b84d3eab53a1db0b7118af154a4c8c6569ca829b079fa5a627d3bb4dfdf81c0c3a350f8305020b47ecc0e5b2f8647a3901549b24f47fd5cc40ffcf5cb9a91e5584413a848b9612e1aaa836c353ce484e2a4387e7ce34224860b0025a4fcfeb4e3d9c584b30bb6c33174417f9f4ca20f7068f2cfe7df0c985818bb54556e0b36090f7013dc45f827ec6e73b50904821c994a27610c51b64020c75cacfa8da02f69e00adbaca3621a97dd796fd5c931cdff0517e5fb59add9fb8909a987c0ef52f4d470996dd85c3ca2e7d9765149b0e789426acc1a55f82e3abd32cec2f8e484d82f8f984b40ad2f16894693efc5b7c4d6f527562b17e692feb18062159f63fe94e79ea9b694906cc8e11adb3a65b6bf72bd6bb40a1375c0ca0916b745a2bfdf1c72b53cd4e3445f601f31b9f87115845c129941be550b65ebeabdeadbe164104a3a69b6aeaa419f54f1578f8fed84c79f2a45a7d6e32a8ab6ecb0d5dbceaa2af554f3f6149be659818c78d63f16dbaf3393acbf5deca42ad83d293727cb43c4b668ec396024ec4548f924bd16a306b8a695d20aab9bc5033cc32dbfb6a906bee5c59405efc1953c7ecad1cb147e0720a5614b2c26ca147bd704caa378b582ac57476c24f8ababe3a1cb169d39a50d9b1562327a22b9fdecc9841954b5c5c49a10fe1931009f2b5b55350510e33dce6a178814ca86732656ebc7cac4b2ab86459b3268470cde912c898f8a895dec3734a78a469ea8abd78f1d4b676dd7921fc294a1d054f598a241edd13e95ea9839776f85898b2e471a76a978a1765b0544a57e2a8d4473f995ca656a7a8367cf8131631973f46adf94f0066740637d5ca6b080ca3b4f6bc1ea3b2311dac6452bd555b7c9ac3e0292c9f70cac73826aeca9f1d513cc28824abe5d5e2fd63f46958dba25ee1d4c251d3d51db6653edc85a95191958e70454e33ab5bf94afe524768a937a70578bdea9bdac7cc7afb0d5bca13e47e643596d3409e3e03b7972a5379e4296f6438a10fd38b12439148d97f86f9359903c5d658e49ae9705187319194e46d8d32ac3db187939086ae8076773a6d0d24a3ff2577f457f9d5cd7baa8a5555af62fd49345d420dafd48062ac1024dded51173cc5e5d61dc24ae196d25f2034cd285fec6e6aa68a348a0a243dbeca9e3dd0f7c7ac82342a7c8784892023522136a56de70a724430e4ea77214948f0226cd75b110c16cc4fb1727237642a98ff6d024e69928b4d68d263109a020d76339d27333a08c274a1dae534c549757a401201d3443ee03536bd9960586a901cb445a4ceeb9e78806dc5ed2ad24045793ee652712562cade01743edb707df96ff78732551ccb30668b7eaee103fc8cc9bc2ae8e054a4f2ed53481e03727e3f641a609fc016c524604eec0ff810692e291942af70c2111786f69291ac85815f669a2ad5a793bdc280c0e396d9ad195c8db6e9fd186b47ff56662538d0a59c2cd4ab65fb7af24b0812e021a48283f8106c65fc1b9398bdba20e47fa821c07b94221cf9c63faf47a261aaf55af50e8e3978ad261750ed030c4b2332539a825bd7381a903a5d4dc6dbc0ee2729dc164568dce1ae1f3b89a10240b5131a0d2bd69e13f64462fe3a6fdfc17639ea840415a8064d78ee6c34e8490e85aba5e101f7e949b579483026f2a5c586ee5647c0aa0379f54398f2f4e602c3c51cb611e103713aa92c0aacfd955b2280dc1be96585485666f79918236c86854109e2ceac80eb86da3faf9dc1d085a66dffa75e7cf7b1e52af89b0e6a8730b6c1b948b56f25cc6ea6c74732440ed2085e5e31df38146810634bde6da1dfaa094b5a50bab7981f9ee7d0f4823dab2592a09f00de149ec79dcbe7a22fc56c1726e094cd4047e42db1231fa203273f2301512385bb376b65bb5c1857a2a81a96463e7d3c75f12441a0a21b569842ef630a3a54507ff632e16bb3054c34b4e761acdf742fab3b1399928e55de96e021b9510124c200a34fbff1450159c20c1fe3369a8609e2265c99bff444f0a4eb4241846bbacf1e8ded26261106f83ca64c9b50a3a573a660a562d93718b407e2cdda2586f316a2644469910144c8864ca4bf855b0330a8442939575f20db96c09cf3ba782ed2bee546d5098d72e0c3b8020a76a269d30e30416a046320bd5274e19084a6d3a2fbf085ce14d0668c9d44e98eb083481aa4b4dd442a3978d01f35b1e1ca68e9ee30a79ea6959ac953abb921c9608e71fc23c24c3489454ec47867246f8e2e37aa226211507f92d0d40b2ae74714ede18556b48495343aed328aebadf3d2e44a03c8077e5ccfc73913afe41cdb5518a467f178912d4be2f554cadfafee771fe23f669e522d1efecd1f551d220bb48eb1549f5dbd8e6228d5d9ef5cc85c4b5b5091ab111e4b64802f15a0f2f694aaf50b48a658f48404f80f9dedd48246e7cac8d8537dd979e81cad97bd4780c9cd6c3ec9017df4159029ab3f576ed83a57e902b77f7de3276dc1195df7ed1f976848d1e46eef19416b57654ac604080f152a20903a6788ea5ee49cf49cb255aad6809fca16edc561966f0023af5298a068dd5b4758d3118ac82e1b5d015d22a7185094db5c3b9eee6d538ac39976403eb600cb420055a7b7060749193fe222a0f20546daa0273ef00c0164b977102690256414a1ea7ab747150a518e1e05a1c7af569e2b63187acbf812b31113ac78c310fe890268fc1e5a1517aa849e0b5e05cc9a51b0b81088d89001d04e2d4af57dd718a81f575719ae0afe929286a8868fc2ee0ad3a240eaaf52bb9c841c6f509756923562c78f76816ea6b5c655e5b0efb9b7cacd5f0892ed8dcb76f2bff7b50003c3b39f3e3a579fc428679435673e7815efdf59735c0242243234236bf37c4a93aea8472fd7c5bd53252ad0f530ad57f265b3b84f39a591da6fe592db6742b1c942845c85dc7e6a858296a309acecb9dc0ef2164b6b8ca805a2e3e06e217341e9f2c327a627662548401ff895f85b6bded91d630e1f31fa3aa154f96d80af575bece692e33edeb6d912303792a450b4229408d33311de9369d0a53f730ed042ff0d315c1c5b23c0be266060947b3866a4d49e5d233f17dd0f158fbd0769f3603d6eb4c27fb7595ba40de27c4f72c435a67d8de56bb732df7cb2e845f9e396bec51c5f39b197f176fa9426e5bb60f624fdddfbc3cbabd8a283ab082c0eb4f168d8bc2920ea26ee279c3f6c32a18a9eeab19603c379590e3f3124c2350308919b8275fc8852e1a6a387b89f487c04496a69e4afdd0a83a5210c3e9d05c4c5dc6f200a5428acee36455c685f8e347ab4000fa093cf6509c542f33328a4b5d47320aff00a33ed210d55b0139d83c6252b4bb803f8aab83d8c3f157bd8c9c05cdbbcf18fe51f42ab116ac65c52c41011f827de0279563639713813ebed5b269cd09531797439489d0e7719df439327086b88f2d8482f175210df36472bcb23d2fd3bfb6667b9b17ab40847a852e0eef0836a2339a88341757ef3a816b57c891fb2db2e1bc6b258ce1ecf0db8cd71fcaf6c80b73543b0ea45cfed282992e2ee891966d6e8ddc0b69ab784fc80f81a049ff5100cfa617894959a208a380bf570b6fe88c6ee110455309bfda5708165719c10e16df67b2f428235ac88cd4c99529b0974e5f4610870d394edfd713658cec7905e7a0b254cb0e05d6af120028318ceaf6c1fe4cd31fbe60ce1b83e9efa2dc25055a906da73daa003045a1a01abce0d8bd1fd7e1d10094c2df9f316ec70bb460aa9492a58e61f821559276f0347f167e80ebaf73456caa0bc4eb3babe68f97b528c85ebfa135828b7d2803c7f45990c5f0228794aa0376cc6ca2e6e0aa5415fe77773dbe1d736567a1eeb6f1ea2313e8832535ec21b973635f9c85fd9f30256027761947f0d1d98d0ce9a4298bf6763dff263ce6b1ac69a853210847112832b7e25564414a9ddd70e0801d65f4baf855a6b3d4b10ed1f5812caf697c9586828d572e767fa2ceaa7ec64d519f8788a8856053db456f06dbc9265aea8b7fcc7ba564191cd3b0232d156213a687178655b66ea34d69e00e85e80797a7b00fed36a478e643c4f920bbfb259b626028b0c0a05436561f8cb29523b135659809864b48fc9852c1a8fae7af02b1445e65f51d2e18aa87d2015f27420bc8f03c271fc70fcc89fc8d69ae480b4501fdb0e2c34eddfc310f07387f2f35c80f10a296735bc6b70726ade9355cb0d7824ba0e225c8158102462ec466f6ee15aaf4f0826116e56fc8e58ac0d93f62a8209ebaf34427725cc85b7e5c5683a2c7d0541433a7b48f9e90c9e93aeb4717f4bd8079758a5e0f8f67f184c40c27af720640ba79769513fe7f189400d75d69243459e4082a32da3f9138bf22051c80d3535f8565f9c1afb64398dc0bcc72e3499576f6995e4aa8c4c27e6f5de94290f759315efab1f9c17dc60773de757f6899be699f931329ee04a36a776088af0b7ac3f2ed6900ab155bf8f428e786b672bf6bd202383936e8436f8aa0706a86ef32235dc23a7815348fef6079fd06534c9494372e2c818b4546fa4738897749edd3f8cd0178d337191b0346fac14401d47fe095048fbb1c48cb47620965eb57867a60f17d201501e32d6ea563bf080e33424af284d0fee82d546d104971c43865fd88cd3f3a0854181af7a2b390013d139453601a2df6f59926cd2ec03605ae4985c740ad1d16ae1fe741344f112e263d036bb6e2b6cf31c46dcb02ab3cc30b64f2da88e3e3a26c2c050dc3183f5d452cb81147453683ee119214d650389d13077eefa80b8d47612ba9d4183b072a60faf6cb6e0ed105d95de5f707857082e281d607e71002861196317527356c034b4f718d71130040864454f8c3af86fcec3a8bc0116f0341f3781a6158db02c62fc8df97631aba07ead233d06232321a4c8354803af67b60ce699615c2d5d5412d26024711e15129842f7f1113fbdd7df55ba1fa3a90e0d0d577b40aa1352d2c73397ad4fc6073b905254caa144cb89ec4f35c36d7f68526a206b8fe80871baa350b991e0480298a3bce9dcd4405402092770b644946cf941d48609f59d2ae9b141c462607682450df3dad0cc93f10760178c23003e254c4d204d9de88cd20d4862ad2327a6d6631c2507e286806aae6bd109976900b01edc97719c17aac207fa698776064a8940fec2b4d87701f2633831e714b2014c0a077abb4b2d225ffc46415779afbd7bcb68f8d6d88958190d4a7a0808a68a64e140f330c9740ed275cb1291a0d35444b7615a72f1168df1f93a6d9985fc50e1ab66f02d263f88ec3fb5d07e114db656c5a73412d9955629fb1072e34a24cc79741abfc043a70bddcf93b186b99cab3947efc7122115540bae86601ee4b7e8c3b4c16d6568ba349aacccb173b312084ed593cbc46d84e9eabc6ef265dccc94bacdee42518e79556e5ced649727ac21f2e8941fc5494fe5089fe431f6448856094581fe2b8707802123a254aebb5a98467705c1f0f934dc9d5ca8dd1045bb78470925f340da7a83bc39e43dc95d54c7ab14e244f8766902818f1ea346549fc548700d035da23ddc5569fae8957c8eb3d272c907c493c8a1768cf6cf87f525050986eadb6842671c6702bdee12b610387c706b01eda3978928a94d333c4d92073fabeddad9b8dec480b8ed06e26bb157a4d0e887076145b64d866378b8a2e205004885ed2840204297700d516fcd8c4530af887834233cc438fac533434dbc6ca954dc9670345ee2ae8243bdb9ffdc99f2efe1889da5cdf140a6a302a6590a90568f85749de91755158867819af5d1b9d8d656f1c5447f526eef9b684c69a17b2dd86c14bc992011d6a505fb4963e2e4b2097377fc6cb490d5ac0a6befd07087c956ce1437fbdf034b48ae2cb453ac5b0390745a8e60d19810fb95f9edd4ef679736ab7cb7d52df93fb69fb63ddbf9475687b3a3aa0502b279d61205514b10e8e3c22067ca4f53a9a52912281d5b42ed50ee745cac33beafd6cad30b1c55bd755b0f6dc4e2869019f0c5ffa5bfd8309d807a0d7c50dd043717cce9bd53ab23780d90ce96b042cb7f667d0d2149a79e7dab478989246a87a5859abd5d909c36f43e3a641936ce6765abdc4822280330eaf382cd6cbce6af39865bd36e8a1760d51e1fc727d98acb861e161ea6934407f561dfd06a4d8d2b151d3ed9231efa004518715cb92976d2a92a0b7629275ddb2adf99864d15631fad7e034ca8985fa1a6c1abac4be1f0d4c979c58aa99d1616e22d1ff401a9229724e3fc115230356373ebd2c0bc3b3fc352ceb3e8d187b37d650f4e9659d8a876ed8efe52e4f45d78778cbc32044b0be8b77ca107abdb712cc83d03dddbb4e906230e497d52a34ba675e4bead00dfff213eecafb95fe4be48d78f89900ca84b4ceebfb0480d4c19b1d98334d68cc56d8f0c08c0db5c3dea33de9c73107cdd8e0522a3781856eef151186a4ac4ac76bcc16c51e6c20d0298106d97bb3bc957f20524e6ba317e7bc10e9f1877c33d2ff75caf4c3538b008358f7ebfa1f5ca7ba176958ddd92dd7223957a561063448885f2cf25401b8ab8df33d12df0ba55d988ae8be7b5956a839b938586c0335ee66cee64281ee04dda92c260ca6770a31efff1aa842009d6469068b73053225f3da13deb3ced653635fbb26cb077c32611cf320040879f50e02b4e40140f68563a8dddea80bd17f51e3dfc11d56f5b152f457548f1b833b78758b231a09115316cee0efe4edba11482be86b8886d9bda6f079827c6941817a761c801974d5d0dfae50cc8a6a538c9363c624f1426edaf8428fdea0b056b510d358a83d9fbde6ac0bccf84d8bbbe74a4d10def9d3050237a42ad62775a0decc28ed3f57c90b446bc4a8e4bb88d096ec271080484b762516a59178c8908ceb3c5664f166be0e073c3cadc2dfa38521828523ad8915d8416c4a4bbce794844755531faf5cadf449b636085f0f5917bd616bd36b52d38a869691e280035356b909957aa1589b1bc86fa937244bd46d14ad9e92b788c46fa38f954e813d79500ecdb4fee947194367a482f338402e4b3faaae0d6f803c240e08473def478a2cd16029ae0cebc2e5e4e2819aef178f9dd601c8351bcdfcc0d2d077480c8df13d20fbc6ae202805a7e106204c45044bbe481c7f28faded83630e432b5821e83c644583f195ed8f1edab4a1f1a0e183ccd7e832c5e6793dba84e6d0b98410bfc1169079eecf04c7a58a9b9f26929e990b7fbbb46bcd39d90e61853cdd01c9799d2caec85595f5959afcedf140e46da5042a2fb5f2b7946bb51cd5292f95ed13027efaccd753092c5896ee3deaa85b3546b6346dae5d702f286c3888c8369266460638824db4dbb7f62ccf8fad5b1d1d37afd114fa815458f77f68d1b2d8a84df5642655c6860e883992c18f77b22aa1f56e7fdc4e7d4b2195273f825a3cfb8a0c06219015a6eec26983b8bc17ec0a9294f4cd7a79aa16d59464960f4a57bcf2a2cc8cebc9b11be172bf78d8de2e6f7fdc5c059984c8813c103b11b8a12e60a6df07d5d0955f0fd0c0aca270ee3472926f566f07ea4023f1a76a15828f2f7154b7030102ef35d10770ef5f35a523361129e0ee7a225f70b7fef601dbbc72829b48bf0a40f564c02f07c1efc1baeda74b95c0a6f1176a8e5a7e21d61a7ff9bc23b360ab72ec7c70c9864570050f30193de52c63078a351b603181f8efb75648be3cec4f85dadf200b33743b4c5429fb1ab51169bdd521cb762e63c2a30f7aaa77ab7bc15eb3579d0efbb8493e68c954efa966807a4914e6b3f698b0031f07371f47dc21c857633152957846232a5f2bfd9d6c44b441b74d92c310c19469253800c362444093d06b3e5e00b193d806b6cee20903da379eebe446e19de7f846d79b089a893039f084b49f6504d393a091978e12e37a12637c97ecbd992d2d7899345b91f4ac413cde22e51d539a1323546bea32f9755ff9b7581a4ccab34bb649e0446cdfd178abc0803887eefe3ffa71697604c2a9426bf61a94f663bf4bef5883b8bc14dc6eda673de51ac04acdb29d5a5535f9d74a6d2bf86d79f3d1f78feffb9045494126a9717245e2a527ea598ddc217e42f439f9527fa2342384584a4f818c53f4365a2ccfa6c6b32e78d994cae8598fd4e06e917c94c87fe54813b17028eec8341d9e734a2c6b8f378ae1418a8b6e82f841ea6d89b8c5f5b31ba9f7f9c32c74ea845e6279db3cd4b56a7b8eac54de2ae20c2ffcb63c1e75413946490357973d6eb985d1cdd8683c8b230d7d98d99c30eff4d45537b2cb896e9709a94059c1ffc1dfd5d4bb704aa0cfd1c830ad1f196823943f627b5e365f65d6a5de073e50dda934cdfb184ad1c656f35142d320cc7d089550149acc944f0453e4a9bb0e840036d6516288c1461fcbcecde789af83540bf9e7ebf8a20cc51879e9c41b8c59b275b7cde0aaf83974888d6be12063fff5de9ead265adc1d61979a01d076465ad1861b4768144d007ff4d1230b62dac34cc95b538d7c4e2635ad88dce6f4f9dcd02abaaaaf5db453922b2b034d14ac4ed3150f964989d78355826ceef4c39da3b62374f7030ad889c040649df169303adb748dd7c4c418f2a4fbd0e9ddc63fa61799ab86bae0d3dc9fbb2c8983ec31e48bfdd1aa3bb0ccb72899dca2be1c203d25f187590bfa55d58d1168690e7794257096105e7da7e1a8c716199b534807ce001f08f1a5066644965e4959d5f8653c34ee2355f471b828397538b066f0077512bd8faff085d58ab43a14ecaa5c3aefc05d5daac6504e248febbb6cb27832e33dd575fa237478fb44e73fd64f11b3973f15659ad7477a714a4350df49654b3d05f4f9cdbee8af3d55684449798bb97a27b98ff1a1045b89331691de45ae4cbf57bbc07a1752df6d1d4b8daa56dc0384d0a4fa697c2c60fafe44f2c518fd83fac81ddaf2cf8412fecb5bebdb28b6b34085fd42c3aee265f2b4d04bb53a665342914d9081308f00870224142a9c42ecefc873de184243d59dddbdd31f3c5c98dd1a69840c938b6b080ca175f76c63a82b0ae3ca0ca95d4f9456d8d03af776ed5f719bb140a6e761839265711a99d5db64247e8bded60dd2bf3c1f86c1e11a7ed7b0617b1a284afe471973aa0a93102f322c80c82294b174457d2dd28a2fb30b2006ba7355a59b57f3812c2a0c0a6faac7685336181dca29e3b4f71baacb0efc385d101770114511d9f1e731096d65df32756521e9bf7444cc45b160380232846b2ebd7f520fcf7503bc328a4ee165ddcaadb6acb06676621045f0c63eca8d36f3fa0969d324c5c1e1de0b943abf6c9b8aa2213c96e8d4806f17013efff67411d43e1a71179634362ce381320dc17f79fff5750e47d51670efe44339f8004be7a969c2fea2e3c5433d44c9e0a967e4bbad32b852ef20e5b4e6411afed6f5d06369beb935c3bf896c5bd2b0fe479306ee8f693541b6037f230e202078e192fd4a483b03cda8705ec96f56fbf3dfffce36c6a3f8f49d700c4703ab64a069ed610bffd28441c4239620b33e284cf15e6810c09db65c82b5ae26efcff9f332c7b76004e79566fc7b791f20d115d2928dd6c9fbd063bf9fb7b37ab43eb5aba45f6d0685b240a580e8d376f06c381e94c4f84d2a34792dbd1c399c6f8188975ece66c435b090e36837534399c1514b47aa2985b35d554df681267fc18bf3613dfbb99bea0e5586a46f526ba90116ba94000864718859257600e3dee827c9d30fc4446051cef8eaad1371502803c71209741768884193bda7eb9fba46af2ef08c1fc3c997aa8afd7a0ff98089d3ff00b68082d42938f0356bf0877ac91e64f9398b6a56a3e00a6ff07990adb9b784db4ebc90e1541adeb3d563be612e362248fb87e1e1d84949b0b7faa1588dc22d1ea911bc04e1dcc9f99e6ab83086e6badad288d9b97506bfdff4fe48619937f76873694145ca65af2964d38c14c8f3a777db482e4ff703a10a2edac4f74cee042debb1d1c00af464cf18b59e95d4ad26058e8a4cfbfbc5575fde13156b8e56c4d702251c096a95c50bb3dd01bcdea4d18a65a7b003cde36a213c649f9b37037dc08b07d65da1cd5486974867cc5b3a3a668d9a34eeab7f8bb74df75e53c8216414d1bcea2f3ff0c2b16bc0c219e9e146caf6aa06baf33169e1610b7d4e351cfa7bc5af6df1944efcbaaf624926e53b66601dfea26d00b0d15a0a8ef3f24ec63fb4c8bd369070d718d82d0cda09194afa3947b434388c48ba5543f50f3fb270ab83fe7b4d8048db736cb5d0732a0cca13f2360f3933f28441a4796cd8dd6b0eea03e77adb501242fa25e07cdef854b0e7d5651b830b74b1a75e8155794c31bc5d290c19dd44b18d671eb290b387e257c7315f4d8b46a3c0a87339502ab1819dda9aa9132d45ef00e78f0e957e59a7573f898fb94a36f8903556e88cd460a7c5674f2ea434ddc1d3fc519dbb67241e7439c592dda99a11694e1a7f19b8871ef6608c148bb54a878a23928f36fcf910ea0a4cee08349a046737db72591198547fbec06057c175b59c6b76b99c472a2ed52afa6431908fe6c81e51b97830e6e247123605338ec937025c08ff6c313d81037b21e82dbb3156703edc4f2491757db34b47a97c0f5fa78035efc6680bed550715751ca35815b17bc12c50a2fb5c7eb300de453a38eddd50d30ae1da77d1191923525b8ca28a3a2714139a4765f285c831f447460b2b6d3c38aee3567adc0003334ea1fb1ecc80f5f71266319d4bf612e71f4daba8c47edce5c88a33b6a77bc30677a50ba48ade925445dcd09a32b14e5789fe967fc614d8c9d5c7aa84b0090a64c45b553476f45ad37183bf94a395115b010567572975d56ace270007a218351fdd7b85762e6be87523596bebeeb65c7be1293c973c27824e751323c6ab2b382d8a7c54a72ab4ab090482ea133287cb76069014903efcbd55ac220288b6f8b1a08c6a99b329b3bb4edd791a98d3b0e63681fad14132f089fe34ec668044e0803cf06535c044e42f48c8b9b7ba3070f89de8310df3131a98bf9fed457279cd81e4f3852d3838c203e438ae60bba15d5a7e57da7dc42c3afd0e45296cd4b9ad3e110f47957f4e7e1703b8ed00c4350f1ea1094a83fcef3e43e3f4e3b49067d66b0bebc4721d7a61d6e1a0888629110abab2d15810c346dbe4f9a32ddb55791bac674d6572c473554dc8689957d4cd7cbe4f54016560d73415f30bd86cf19e23fe99d1d990c568f99ce25d5f00a33d10e6166afa5520afeea8098b34c86ca9d7b0bad45fb09e606ecf1041940abcba3a50e5c519ab6c9c93ad8759c9113c951c4671d09085a0f04f1dea5770cfefb8bb54c4822a86dd95d10ae02924ac5d2d92dc5a7d2c6b10b5943c71d61a0bb5ec05de2466afa15c02e7740da8c5295713a923608eae3d63202fe4303d3f1a5354efe720673b1a9846f3fbfd4cbd1d2664c4703b57e483f1252547d7a1638501e19a6f233a51e6c4c01dad3825f7d46357d9c3a7b2af04a8ba25c29c57f24fe0426aab8c7062ef2afc1da91111561f3c28840c87494d1441088d8f2b82014837f1bc57b8681f0d69dff76ca2f3a03a63c8472d6fc312c4a96f2d6680913bad1fd834a30bd7205c9e88f87dffe95ac3fdefb2a34d8ef72895f78004f123b595bcc6a482337b2da51d015cd0d9265d27291866cc360b8a213eac4a71f95868f131ead3be6cc45235509485a9f64f8d20323170c7553b6b0f129927267bd719fe265ca93dee3367bb644d9d1dea2c1c4acbe0d62bbb94b4cc2d2d435dba61a2a6465b0855424bc7d4a86da0abcdeadc8094e45d3e9a3767decb53701d085c9393fd20365a242a32ebfea0c01c28595a9abbaa65e3493233ffd599bc15937bffa9c0d2511e9e94b1b912162d02855fd8af9e5356bb80a0ad39a896aa232e99e9126c2f024913d5bb1cd3d363afce34a18396d51586ab9cb2f780578f445ba6f23b40bc1293704c58baed4918660e582599f0dfa1fa61bdd51fc68f4c91fe6f500104781f60cb8eca54265e6428a17aae212690b4e16d44340358a2db15e60dab3950b6b9b76ecaea2226a651d683035269656326c2287b4de5757e6608a2ecc381b6efbee5d65de8ec50526b70f7d82333d13fce525133f03d1d43f830aae047b7952257fed9004f54b8187abebbb544a65671b40c374dafab463c6481a26c11b4269072f3283a8aa6b59062e5127d84a230f3092ca632859eb952917a72951c97e52fb22aad8ba1521ae4f0c1272b1d24c7b44a29ee574d242ff97d869d1e19fe112022614f114be6087c020edf545b2f4dcbfe42fc6de34686b26337ff6971a23d57bc9680a76821f1969229e170122778eb401a2195cf976a15fe4c07319b0afeedaeffd7ed93e70914cc5d321f22fcc0e2c48497b78400dcd398671f34f20cb75b4290e642a0e6820b00a97cda989dd657ce5aab55ada13926e63dc59c0539d8c92e6c43561196113421aa1a5250b303e0f62789be75ef4562cf0c1be326f7f9f27fc7ce6065683ea3d4c7dc88424d83925f6bb4f73bb1ea84f532889e739b4ba67384983feb3405056535f9c59ae4d72db53db8a0ee01ec2cac203560d8712d533e09be917dccafecb2ab568e7b0c919fea1a91ad73b79dafc8c7885c704aaa1e38b7d7617b86bdbc027af569f846e1ec69f567c3a24b921110078265e18da3375b6501b6616186064baf3da052c65eaabd54b3f7766abf3a96463589d3539c0b7abb85cbe7df47069e3329ae202604ddab8081864bc3d30c3b80620f4a79bfaf80aa5d5631368e70121a5063164d7287aa7c3b41e2fdf60fce88eb457fa21fdd8774d641b6451b253e9dad8c4cd374579f52f0222a2b108fc8a92fe3f65b7fe0668fe38b61091947684f136dacaa560ee17b9d84e1519ec4f6cafc19a0770e574438b2758bdeaad5b15ca2d45da420b1c9faaf92b81124e911ccf4793f1a1aeceeaa120bebf70833f50b39cdaf8250c1ceea15fcf4ac31ff38431bfb16fe4ea0b032251cc93c9ffadc25e74c234be8accebabc8913d69a650334723e8ff84a27ad643d678256ff743bdafc6e1968632654b2709514266d1f8ae5738c5b80fcc9ff2760c5e564b212020afc86c21681bf2bc76e773a56b4690ec579c48968e2b6c1237604c02e108022a0b969bf8b322bfa7490b22f683c3e2b12dedfa97758b5592c19ed5118ae954b75531c9ed9490811a967bb3c73098bd9e7245e0c1a8c229827ac5ccec06a73db538312ebb040d0ad6c7cfd17eb3d684b4f6bf1ef46ea160ad029d9e3791b000b003910b8607e8d0cda91072a4e0a430117e2c9bf590d2915f9844d76cd58010decb5ac692ff75dc31e860ce179c1eb667113d7bd835caa63fb631819cdffaf125767cf0d350a5c211d82cc49f54ea268ec1de773f1d533760da191c28aa8a16cb9861d02f8c90baef8e016f1ae30071b5972da8d1b452079b9dc1ed2da6410e30c21dbe0f4e8fc0449e4fd56486dff6d3f9f6a89bff2a4e1a22a9a4e0174df3df3f6e52f8d774a042b98f9f5816fdac0ef6b2e3944cb9e77681582a95bb42086387b001c9c862c2dbab6d08c8b9be63483efaa498f8f9b0be161949c2f1e1a7b13d328c673368a9cf68c49b7698fd26ab5a8292c2582aaf123190d66b1502aac8f9ef3c0809d8730c69fa656b88f001492e53cf7b81744c1b8e63a49cea50123b44371d435e508e8bd80bafc42d1813525a460fcd866e98db6a0ebccc5b285d245edf06a72307c16d320e7bfdbdc2ed44416b9dc49f89cd86fa8a7f72d65e006c1d28b0b77db7642de1dfa1b530ad3cbef62b385cbc1c88e1a3ba71425120dec7d8f2ec2c4930dbb92253e1b9ab0d760f70a313695eb3dabb9d4718d398a37e568d5ed6a3b95a3f0b7f4a66b55c0dc286b3f64776785c859c5c55b1537a8751d371999c895eff104e212dea745d4d7094153c5f09aaa3c235fe9ee49f36f37f2e276fa9516b16b12b23e7155ba057661137144c128c67dd86c8963ba090201fc81661be477cbf3d0892179b3012150ab27c72291ad87dd0601ebb59b934385d29634e07f582ca0fcdad424d0ca091837f22caa2454a84d0e0dcb31192a1405106221cc4704ae197357db9172add3e010cf4dec0df047555a7d6d0901b4f26c9d7dc2d1f7041730bf2ed5a2783279c0b99b6a14ac774fd77a6d2a987c3869bb8ad1fedb24b632ef85d611438a24cea1327106ed851fcbce29481e8210796bb48016afffc43ba00c004ba6b60a3935e6ae5bebfe32180702d1dd633de637fcdad74680e9b6ff3e247c8176155df7ed1e69fd5d8c21925783dec7e3f984281321d8511bff344cca0a3b039a367fb5b0d98946d82d50ea248fa5894807034229ca31aa5ded468c3b273c8d21f4f615c7280096c9c09350f1cec072de3d711ad72c1f8d3e3ffd75ad038b69e2febf86991f6011cf31c827393eb04ef38e3a3e119da0e0c75f7478d07029f339fdb478e280a1859c1ca2cd690a6db35226d63e4718d37fe4219f96c31e32c60b966f51b597737bc555eddf6bf0dac567b533fa1bcbd5aa14d2d276fa7afd0054c047e163766437418b1db874332be201508e89404499fd6b25f3a4a7e9a538641f4c94e998f2a9d96fce744913aa951956eaaafd5920012ac1f800db1246cc5b2f9c83e3dec87844d805f0e2a70f005266357a17435381e88d1e12a620b04a4009a96763e9655aa9e155ceda9b66bb231fbee17a4a64034934caab239cef3a7993aa8d24529ccb9f56c60c3289330e6409109f7fb15ad0c310c851ab8bd156b4ad88bbceaf320df96d94274821ff39e51127a276e856f7fe5a6de92413942f50d99b5ee8cb45c88d52dbb3d428f123b186a74986306e255578609ec739f033efcc962e9894f7484316fa286df560c623d1969a57108e067c8ea1faf48a01981cb07965a46f33a48fc85538cb7150196222737f80bdecadb6c156701ba06456c1ff3347236fdc2cc8f23f1f640e807f010e442c30af01cd45497f206e0837510e5f30b12432843bbb350727e405ebd30f565f7202a4722870a5ac34d02d952176179682783231c093cdf277575a4444632807d5256d36091651c528fae7b07b670c7503cfaffdd4bef14d02410d57112c3cf608e73adccd8f62468110b335ed3ef8fc825fadcaca8e914d4e41b574452d1474ea36c8919012d52d65f3bf074000f830ac3e5b1b62c4c63faf2a2068682d4ff242e100ce1b26379ae1622d806c314091b8f4d5550d2e4665905e841bcec4131a0041857c5a319944b49f5683e08bd4cf6958068de99b6d7d64e2fcb43a11a2d2dafa4daf4db1b84a18fbec67c745b672a046d071f20520a27f80400dd7cbef73d78c871444cb2b28545bb7bfba9c302d1b3532deb0eebeaf670828ced338961255dc126289ed2d5b971c5d5bba1a1965e4a0ab8c702848b72447a4cc4fbcaa3a72f81d0b4bf26e80d558abbc04c5dc9fd1c7afe5674daf8725ec7ca798c4b8eb1ba1daeba4eab327f0aaf1427a9842d93b1b97aef12a9e7c1d4b02c5b43254a296c095b3eccb72c13ce3a63e25908296dc0f218cc2496003c7d3c8464459964c86ac5a07d7a3211a3cf78677dd0fe9f2d1640bbd15b921715bef20495f70ec9c923113b44d371c71c5fb10daea8c43b3569de896df111a79311372df3766924338203a1b296c85a20f12487cba596298f42c5aa966251161ea9752aa726663023751b01688e19fe9890ec615498e5ad8c8ec52936ec5127ceea00ad14be37992dd259bd2ad497e1015a9d8cbaddb97280e5198b7446a1a4ec41ce218d069ad46ad3a4ed82659ebb0cfc903afd3ef0a44e07ad1c5e37d1d1d9e020f2617510ad91fdf9e233c1601c68b25ceb1c205835d63a7ae2b855350f6304b95c5b5797182d9a9bb5b9f7a3d5bbcce0a183f2e15e783051b91f2bf5a2dbd0ab53f6df86d2988586c33deef07d4249d3c06a38c7f8af02e1317c933f1c1ec319293c22f14e497fb4e53361709213f9cbd6bba65254c934822a713c7ca34f34d2bc03a500546e42b28f6209d7800bd4d6f943e1da184a3a1c78c203f3be02bc4f48b49fa04b52846d66f009d6e60793340a4163c13a151f7525843ac4288bc4e8863296f5c667325cbceedeeac4ffb80d030f49ba7c5d4549edb78ac614e45e2129171b0e8f38f4d1cf4d3b57b53edc587bd40a42738f64f559bd158116604fc01428a7f054bcba1cafa12a0e79d648785f168d051787e32fbf22513db92ae15be65bd10cde664bb375307e6edeba12795acbd752da68f758065a7cb938511c47e9d7e028cc3a3f55c820abc95e12950e97eba8b41a3416e6437d0c7c35cd60f52712e788842f4866159596f81b10f89cba085bb7d006bcb71e23306753c0e47fa01f1e561e00a5bd14f82e2b2baf3403df749b8531f61470f4f432825d96c3598670db12828a2d5db695c63a47f420a04a07690ff8a9257687320b6b13ded6b07196a375f7d835b856ac26a7ad3dc71504767db882528838677c07615da721ee9ab45e953f698bf2c52c1d2c479948f9b1591048738d563f6d1593d314a62c36db348eba39007ae91b39ded8c84bd168a2a5d15f7857e9c734fb76dd52e07a8cfd59317bcb2af0c52bf2818a6561d189a04afe1e145ffdc06c6299d89b56cfbec614372d494ef8ab095205c09a7460f17c48495242d18afaa5b0d06e14033fb78594c051378a7d12ec02ca260cbc60cc2b05e4e514c9e8f52f8aef6bf452015ba48eaede0b466b58a0b939105ff58fc325a33a07ee7d0706fdd022b6156c0d58447755632610f33b3319209b65427118fb2b29f96967fce2abe4d0a086a5caf1d113cf4139f74abeec457764413280c66f4e9c60753f6e75ff6c9b15c5023753e73360ff4c3a59898b2109be4e88005089e814a603b0df99cfa47488425592ca5657170834d98cadb8c262e48129aac8fbb4c062564da8f1f34cb86477d756c2ece19c2309e96540b9252f7ec95e6821138d2858816dbf4f922764ff8827dbb9e5d6080b83ab0a09e282557e6db43b0ee9d4b8258dff8e205e720dcf42e848ba4f007b474429ff118aff83d66705287522675ae42e3f1419c47904e168ea4394e906015d582c86ce854f326e4bc0eae4003c5f7f5e54a7fbcbb3a06e080f5a310cd75ccca16eb1512e7335722e217a4b106a986ef60323a757e75c816b4f31ab1a4e2af356b719c0be72c930c9cea52a409b018eedca933f3d85888e71860e0416e910580e72d18e39a26531c4ee03c864b19df1da41f88a6bcb45ca6d82ca3c393a0edc7de9d0000d7ba82f6b6bcea2d930d89eb574bb5a49211597ed75dd297ae5b9dd60df90365b3ac95a21d50631367b603f3d43e661ce70e6b179afec0d9db337e10b5b9dfd2892b1230750f86a651a7113aed7d5ac02e1fe459345c2cc74d4bfafef3322564fe51133862b573f8ac91162ed8806bd79f9eb9557d8db5f41195f522f5ad119b08a809db818506c152eedcb8e0b0867016a9d407f8e44ed80fcb34303ec674743e1672706d567c78694cff382f7105f0b426f8d0954f5a19eaa8fb58c6188eb082ba57f7ca14e089c02a1c8564e40e7d17a0e0de61cbccee634cb87209879e60c69149a981e8aff1d6a133d240592cb34e520b78118a419a21da26c45db52729848457b094dff8c0fc469761fdf6cc1e9033afb6b45600622a098d727bc9f8b049d2bfac2baa5de7e9e1feb25f4dd434c6938b7b10b13fa4e9ddb42ba973ca142e0b0ae9f1799740f376b042764e96856106bbd02c88cf89af086e175dacc3a49cec6c95b1e53d94f2f6314b26dba2ab6baabf6d682086da7ab69001cc7f82f2dcf169e3e8e2b90c28b40f44cdd7b9e9af68d7b4cfdaa08950b51795cd969434eb24a6ba33fa3696da01631214aacf2edc8587174a9d424fac8d012965a16a954f31ada5161449c153a8f127c4ae59fb2b94ead26fef6a66a9122b22f1c863e6296667e34d33c4ef1022ce34e054ca9cd00a423d95911afe57b01848a1287eecdb0766b32e695ef765940ad7ed6358bf7a279beae4e0dd155ad9a31009219dcc56001f677686ca934f518d04a2b7735f69ea13d22eeab33789b17ce940476bb047afacd8e9ca31ad30c4ded917912860cca9eaa8442632fa29e3c7248135d43efa62b3602e0b8be6478e01c9a64536dc19acaaa1b4a19bfd0c3875e2cecd4dcc5b7c7c60e71e11b34c2582ac7f51387d02ebe27751819a2156f48925dd4bd7fc5d7446812cf849a3d6c72df7fd6c3457f4d95ae456d4915b9ed9c787a280df9b1a5aa7d8d72a7665e2ec6d3262dc40976ff9adca0c01c33ee03be17377e8027491f54e4f1a76d1be54771056443e40b2829818267d1189f854375ea4ace01645bbf656a454589bab0c009f7f3e4c6a6f0d30d4e3c32b1eda056564822419fe4613a946840e70c4b323fea4f1a023fccf925207810acf6dddb538e15c7cdf66490b0440c198494fcd2c31aa23f8a889c0190c0b3f233369d720d24a13564803e6778cf010549bbe64c55af8af681e6ad7ebebea7506dd418bc0f5181abeebf08cce0b707d129919993f0d11d26b0a1eebec7edafc4169f9260941c995fb5e4f5c38bc5d48590d5b49507708c34f02f554189647655bc49f53adb3617dc355526a35bf1da04f1a457e8da2e5310d98c20cde6fa03b880a263630f2d3ce5dc57dccc2ab0108815f0b6cae2f4fcad3b062ed18bff3cf0172764e6c784fb707f7e4107f77b40af63cd1eb875ed255bc9e46f2cee1b6c64f33809b1efaa4d7e63b22f01d078a3c2f7f120de7e1564c22962cb0b697392c7bf0e06fc5bbfbaa4bc4d7e693b9a2d34955e9306c1167dcf9aa665338e88103d8385462fb0a6fb4605813a4bac8c09f1a8e0ae02df114a7eeed067c91b98a39dde01e455bee3e555b3a806df3688a79c41c18ee5af6baa0b618413e39049bb187df22e325af4e63a90152c01cb230dfec0a1708ca06a50cdeadbb5975a8a6ad910823d19e8a0f7bfb575a82ebb902b72a6532c4a50c5a2e0029e8cb8874c3601f7dd56d1bf8f3ef10cca1934ea511c962da3d34dfc6694a1aea41d8fad33ec0ebf79c6d63ca422b99d19c0354218fb8395ef27e9e0e140804ffb2ea063440cc4b4b8a85b676118f8f43d938c596b900d850175b16ef88128baa42f73819a42cb9d8e83fe949874a877c42dba057b3a25e7ab8cde89304e406c5048de87422f035cf826398c823cb18ddcaf9dfb85dca5d6396dec66c6408782b779f0a64dc02cc550d5d5d9d026978a9b716c95337486c7f4dea2cc7182841a2dee157026192a00386082eef03abd90c0825c7a7932a9286e8e1678d1b063390a0adfc2dbf4e8f3b4d70d5aadedb63a9a613ee770e66bc4f67f13a934354d2708fc3b8798eee3c4373911c03b285b20512c31f3d419ef7172fba6bd7c768dd8383edd7d3b3dc43e8ccc1bae058306d745eefa2bf69cc2176137af14b5b4d5f061f2c2f62852e8e93769995895165636f2f722ba1f71fe502dab212c8edde39420250c9bdbb5443c3158965e7ebe3cf7580160c00439d9a830a2e1d67f39a6a42b7854d8e84d5dcdc4d279c5929f6ee7544a13ab33ffe6c2ac0d0cd352f2fa86e4037b992d3d58f07692c3c10b4c29035221f07bd7ca6572c05dee1236c781289d2938a912b43f6f6d5be3bec92f6d6a1bcab87ad3565d5fc7e680edc6a82b5cfae1b99b3fe6575bac9f25e8e7866e77cf01d69c4bd77f5c2fd7216165f7653f98c724e414ae426751a98bb48625631835a8301f979661921b69748e8cae68a428150abd40b056003863d094423e15024b899e1928653c7a5546372b5206114affeda26fa77ae2c1e15adcb0c71339ae4687aef44df6451f67a1a0de3c88880ef24888a9c800848c4f8b25130fbab756472cbf479bb65190c5cc25243f0e301a2c860d3edbdd94f0c08ae10e00a8120baaefda936092622e06252112b8c69e953cfd55671ab4b64c2bb2a9de49e391aabf0f39607efa908ccf46ee812794948f1e4412d83a5b73218ab82d21c75a9e1f2c4347ace7e6a1fa0ca8ecea6c8858d5a8f73e3bc2ed93127189f2b791c41d0ddcd6574102d4077bd5030804bf7cd9ae5e4fc8943f980c3f3567d14ed7ca1b64c1282bc378f21610a4644ecefae6014e0680b2fc030c37489e3282ea92e38d678c229d00dc1fb604b0b1e7d66e37b7d120efec03298bd636611b6a537a902a7ede8a476689c886d619eda3d2975b934cb73442791251fa608d688eb7f72d4b38fc2d9129411a20f519c8ff9a31b39b20e42892f2ac7e9c560213745a1d1e430da85490df21661e6a4ecbeda3c8aee19068c4d40b56d5180465cdb6a09372d24ac927d5d5d8e54301c650035e4a1b967e114b63e87d51b107049eb8e39dd53ece45df43b2de07e66a676786fbe61c2038d2fbfd29e3ff7532898200ac2eaec1279e2e5b570aa9fafed8cf1f8884fa1752e06bd90f016c2cfaa0c59f4471e9786758a0192492004e279630c71e747913e1eb1f8878e014fc2bb372b31bb4d57eb2548bd49077a98eeb386eb731d130bd93fc4bc5987369b2ea977c821bcf4a3a842d62c634fac5b972903140f738304504db08c3ae63a3b5f896c125c6e2184f4c6beaf32dd6378370d86950d40fc7dd7ce70c9dac8f020b207299ea000ce96a140be61c30c222b330a08307aee9a8b52be06964b0e8bba7db255b02073809530b3ce794ca81bb5a12f06bcf0a86d703a0f31576982164c6195e3ef7d3877c849d0200c0144167471a961fcd026a0d2f452d658652b278ed075c9328d323580ba8450786f5de46e61553bbf2744639805c0a12956242d6cc6e8193aebbae909c606ce56ae95113d0bad62f91a4d8a3b153a97df6ad45c3651c8c0014cf7680e9e3e32776fe7691ae5cc5891391f087d96f461405b1a00c29ed35d4b4f4f69ab860005dcc30da6a232aebd49650bdfc30a0819daf4f29eaa628183a75531c9ef2d5d04ca40cf523fad41adae707b0cf2d31e1c9dfd5cec2032d13dbabd825847c0ed523f2159a30761bf0a6e6aff37f9e92679e81ef2ed92bc2e98e0acf8bcf854989f2eefdeacde24c3cb7aad51898dd128c5390fbfd18d3e9dc8ba045cfed9a4760334aa7189be034a5f68c8a44676299ab651afb16b82c5722f7dba422779f5ab136451985013426cac99b97c706417b9aa12541a6081ff82da21835fe5b3e36f2c3bcf950140705e4b45b4e27dd7b400d9407d8ea85229cdca8f5a3d62817ff7845c3e2567f1433f775af25fdb61b13ee59cf7a4f91cdab714789f2269357f4a6e05698c40633168fed1e67e04566dce7a8b542652b23844894072c694a8ec8714bdd1c7fcbf87c557cc82bbab75ba539677688c8931e8c3d9b6b28c623fe80f4b2736168fd1c7837e8b188eb190cad146e1a9f31ece216865251a7ee3c5103596ae60062f3263ef2960f83544df2541e1b240a7ce37ef79feabcbbac5b2ef9928a96f5c8728edf091b2916842025956580118f9433234ed8e47c9d7f06f88f577c23a4b57d05bf4d6619d12ecd1ee50842fa3001722c4fd7014620963d33ed2afce9c0037ea6ae2285326a41d36d576d0da686dfedb440f0baa59bee77e68253a89394bb5ceb0fc2c0ce9e6d748f86c68aa2573a2fa0b9a1aed482f91d704c543d2469522671354a5d47454971c9f04317971c521b29e5503af09bcf3e68c4e6894b2d3ea4c695e7536cb18d9ff74ef6139366ecb0fc2e07056ffdf316c112f921419bcb5aeb3907b6ef887614244cb53653911454e83d17a982b5e57a511ebe86810dad389d8cb42c56dc6e098105455d7fcd528d047688f6cc88b0b9d151285b0fd75e274553101c05cc828ce2698944b5d7daa851d0c5c326c4920f90c7ba623ae58fa8bcecd44ad39082c81e8b957596c4df198e277086fc7cd37df8d8530618947b085595e9186fd407024fee4d06b73365e40fd18d46fe76b630241c4c7325b0fceff7fb09fdb90e624c803c0b95b0825e12f172f48e03b81c20becf9b28c07d22cc050f7fcaf377f8a7899dc7c126e4a96311027cec45b5ec9363414a53c71e34e9f09a0c18bd3c93a221afb1a51a993bf804180c04d340dc17c0b97b1ced318d1c88c74cbe4f8a2f610c9b55477da3c945f312a35230acf42f7c245c861302c2e94945f31ee21706c27fb457ba243cf5389dbc338307931b370849ba9a85aadc7d8c5c19038a6b667e5d3012bad21c8a66ec0b81e119bc54f957e3e5351d340db2b7d4d8f32b4cdabba71112624ad4a3db47c2db73a41b227bb7904b10711d1707c78dd7f53e6960661bcc3f8aedfc9a0ca8bb29b0c91acbb569f3a7595a2f2d2dd9524a99920cfb078e07b4073f56cc9ffaeebd7f5f317fbef76f1bcc1ff0fd1b8bf9b37afff6327f6adebf6f307f6cdebf71307f58efdf39983f37ef1e88a550f6e03b2c6980e806543920efbf10e87b0f087ca0d5d78440350f64f3ac108885816e643c0867be43d54d3867ae93d55f59fd21f53499d6a47d52300e58a669665454d49ab4580706cbfcf6a5167b61cef2d6e584410488c203114b04a48c179b37711ecd3b824b2da659719e7bbfe9778b251559fdfe5a960ae4d09e7e0e2a9655ac537c745ff291e94b82abd8b3261fbd9b82b82c56c0a468727142caf8ed09dc7dd9495a3e2236daa13771d80b94827a7682274d64cecb088376964819efad60f812ad99f1fedeb4f325f67cc0a4eb3c095fe2fa3a7fc33d9d9045df849f240921095fc22740e74d106998f13a5f8248c3921e335ee769d879127e925e6a864df8127e4baa91b5186df997309b3f5b7613c45942e84cb405dbf919afc5684f09efdbca134b5c42d8c41de6535c36231c610398457fe785481615817accf81d31688638690b0193dc29ff27a9f3e5ce0c7109fd4992104e724638495a9fbe50d6e2e32467cc101db6610c6beaa7ddce44430438a9c97c74ad498bf9586ab13c448238694b472c3dc6da318139cbcee4a3f370584c14b87458d84e10a122075246d89282dc9270e9b0aec1657f2fa26b40cd1fb267dcf83a60c364091f7ac04111224fa60d88d012648a0fb88089974eea21baa79b282d227ba7a0bb895837e919fe5cd84c4b301f957ccc09fba5e5888b94117ed003e93588c3738b5aac677827f9e84ebad5569a8041764dcac7f42d8d10e6a02dff56c8438b316c010021034280fb4508430861481de184338010b66b08ee17c33054e980fbdf3f46d82ffc04f76b31e7b1a2c3fcbf12e0f7d7669a152da635c1b0f741b84921222489f498c33e08b71f984849223de6ef4dfe0e7326cd85d08dcfa002850a578652252937142e5f727f0fa592e0896fc033ef498159f427b9a2ad26b8518081e0b2edbb0883bbbfe1948a1a4729a594a67e4bcdba0804b86c97ebc79d1d955bf12c2ad19d1ce64e303ad3e84c869a3929b4e419cbf38914af3cc5519e4fa63022e6f9448aa8dcae1e527d7f0b711efa4d8b52b8591367cee3e4b515da43f3ad825289656b3172830497b38b8eed55623bf9d8331ffb35968efbaab7797a8e8fa30fcd1f11cc1fefe9186db517e7f1be595a97a9d3efd9bf67db86f16ad53deb2ef3c7734f524b6e281c2b25b88c0186cc892ea452a50facda365518c30c1326cc1659f55b58d264aa1265a8198b250cd905edb71286bc893e6a8e91d144fcd37954aa52fb944c586231466ca294d8aeb0878860167d8db5fddcc41d1a4876b05872c51256a27a88820208061d00034411251ed081013e36907039b3943eb9e95f25b8a3dc3b0ae61d05eb281ffb5d445ccfccf7b39ec0e50b36721bf1b167c266f2563fa789b0a82f378418992139cadd4ab969d497fbdd2ea216d2433e76ff0bdcd5ef44515fa8f831c19ebba8bf8d8854ce32c29b38a547650084004218c303560d0c17333231a0c7f508d2debe1029448e22e088ffc18dcdea058d0aa7beeeafc8ea717fbb2f44cef746008c00b051332644084308b71de07e9c108310aeb06031fc301c9d70f931429f25f007e10d0fc21c56f04dc88315b6b005db84356104668057210cb4d50f239c010a2e6754ee7f11b68bb65a0aa6e99fe94f2981cb2eea176df5cb849d4405c661c362c266b25ae0b261293005f616a526ca86348b8150e0b26379465bfd5cd8315c70d9336dd3b27bcc161831a4986f89a1c2ddbddf95ba86d31e579aadfe6d23dac2955cc9979c67e629ce63c579e693245d723f916b517299cfdc29776fc9fd953a91f394ee2a729eb2b770f990f3dca63285a8b7f0251f1be64a3ef610465c09ca7184aadf9c60b5aaf9e6b072b9b25f2b3ac8fe32b62889edca0dd91f5b570c341eb27f4c7db95c2e233164f025c9fea9caa54341f60771d0b0f090c8fe9f1697cbe503b744f6f72810ed4a97ecdf45b95cae20ac21d586eccf6971b95c436e8a6843b2ffe603e7830c92ecaf79cce5721119a23341f6bf3097cbf5c3922a4af6b73a44d9b464ff3a2409d08aec4f8b5858382640e0726d89c921fbf7f7c26a88f87de44d142c4ad93fd615cf46cd5e4cb83c6045d3c5df6c4c62e35c5aa4dca0c805419e62308058acca8d8dd2e10aa74f06348ac8175ae4489d1581157171e95cbcfc95022958785fac0a25b12aa63c6dc1dda2a8572ec729580044eb5c0e7392cb514b0d5586549dd951941ab2b045515c4e516a502aa27db024a25a042529881b830204122849453ec8d459510c62332b86e89c60591a692ca96a626394cda390502d2dd06452442193729404a872bda80bca6c051606854a1328b3242c1c8c588d4282c196987a532bfd1bef8931c3d9b1912329f7e7b157141a2fd88b9ed295d2642c72e77edc3e56f19e9a9e92788ac19c519e64f0c4a5a33c61519fb6d080d421532bd2323e74434cd5526f51c0242687c53d26cf2856b6f8778d8957592c5779fecf58cc141c50cad3294f199195ecc55817343f73f838b707bb4dd3340d86acfdc8e0d7f9636aa61d175ae0550b949464444db995a78ce803d9f3fc6b1087ccb758dac8dd6fbbfb2d39a70e938d89a182bbefbd57a5badd54262c5f70114edab24f136e45e89cb95c244b1379748cb6eca7a2c0404a918b4c3df8f63f20989cc0e988082d92d881100980f4e0054989206400454a136d3e0002c750c1f7634296ccc77ce71ab264be3e7e6fe7be37354ff5f6739c677bcf7b954ad5e9f02d42592bb5f7669e0a65cd87e7199116e919dedfa00d53fad1790245b31911d7ce0c70b95add3c0e8bc7f633f770a28dbcc187dc9e682339cefb2decfe06a519066efb0b701f004eac5abc6bb8773b45dade9b9c165c6e194a0898ec4f43c11eee22376fa28ddc7ece1fa4ed4bfadafc09a6309f9be50cb9de7b5df0e1b98adbcff40ddfe6f53a445614955bc60496b3ace89509e0cdc9532bf55cc3bf9c734ea0f6e1f3eba473ce0aead0d1b13c29909d0a7e60f53c20ef0bbd756cdaf785a048815c6bc83a40eac16f21057e9b87e910487a5e0af6b5b2ee08409d735e70051aa67ee1922da767f86f604e4ed7c0195bdf8e368229dec45962dfa3df174ed2ced166c0e6fad910a8c77b5707d7b375df7ee2bd329f0d590700fffb1640f1fb16e68f50038e617ee17e4265215288f4f7cadebe70e8fb495b5d6b073886f925fba07996a0fd56bb30dff247a65f430bee08404f30c58932dcdabedd5b3f2f5ca25d5b29b52ff0bc7089a6bdb7a3766215f10ebac30225a7639b59286f5ed9dd7be190a7fd9c2594f66f47c11484b20571689c66c115689830d90b97749bbb73de57c192f39e5c4d61dbc40e1eb7e47e0d81eedbec3d9a137bb6ac3f97bd05a22531f86417c62c4472efc5d061f210f7372c89c127fba0255cdad94eb2ee98c9da9c6e539bb842ad1a0a4b64f0ac6995fa8f16d2bc1a511a43dfb355878ba5509ef52bb8a3ea787f176cd879feccea3cd87b2f8eb59abd2d7e3dc3b7b7360492c173376705d7cc59c1fe76fb92b3dbbd7f2d0e23a87f4582c91724524094ad6fbe89738e2c04d9aca36edb3b9036450bb240c38eeaaebd8f2d851536eddaa7e00a56ac9b8f5ad3204dbce93c544775b97a8ed68259a1c300ddf7b1e59e3af6db817454a0eb6395ad7feba8323875fb9656cf53c6446976ce0d155a7077aab4dca66c4851f67fbff1a62ad869723191ac25fa08081545903f3a0726a01c216a1ca8d0402c6a072c2f2aa467b17c808ad9bd028a115a484ffb6818e44a4f45e840fef89e000184a4ef3f66ac9054ac2e807e6ffb68e84c440029223d24a10be9713fb860c98bf4100709d2eb3e1a3610319845793650f264f47d349c4e36d04411e921ab09d2033f1afa912aae48223dc46182f4521f0dbb0812a49817f37938697044d230a8ab12841222c81f1d86f40e40bf873f1a4e2b504610457af839417a324970f9a2fa6838a1d860035a480f271727a43743b3e33444d30f6f1af326eda9d4b3833162690828928620900038451123690d83b422124841fe00395067af17f9c346112f4890f56918349bc81f330a59c525ac7ea1dc6e0511f963860ea458726b1834a348d242fed8d980510abc8a2ba820022369f85d417ada57c3a0ae018827f2077591d305507edabdcd4daf8bd31144240d3d2aa4879d7fb850bb179167dd4262da53c95947ead4a97b9dd4e7d439b2b6d2b7d6d2b754dc5cacd7524ac559eb9734557451e545bb76e254bac5f6943e67ffde3b7f7cf3717bca3d0deddbdf7eb3bfd97761b3e13ca58f0f0b61b6e28ae26d85ffb3b6b5a669da9d5387be75c7deb66de5c6820b9bddac77adbd7765e3dacdf1bd9b063077ef739c887dbcdbb64deb3570dbb6d19e95b7aeca0beed5f6ad6e9c67ebc4cb6d9b666f374513152848acc09b0aa8ef53b27f97257fbe3f7f3a71ebbe7bef5d2c71807c34176af43e7deb02a591edec922dc842687fd28dc67c1923d3982f61641af3654ca64f27cf7d8a1f3f7d6d52b1e438ee2d4743eca3a5e23775ece316e0fa9d4b1a997ef9d91bf387befd084860fed4b7aa4ffd7d964adc3166d5abc41c1fc19779fb36debe4cb8f9083e783b46be5fc6c8cd6d3404ea8c43a0cee55733287e9e7b9be7d6b2505d70df3a80adb55fce184ff97dce13d6497bd294bb7bd775e2e6a3a6559aa76dda73c35bf6adca8b2a0a7ff86f9c30c5b266197abaa74aa17cc55483548234a9f56bad150730f9731e190e5e32fdad0031d645ec3c4eddada8a36617bac8ca82c88220c95e4f2a96cb9dbc5d9c430fc7e7ee6bde31fa48c5af829b0c31c4bc87e5cecc3b3672f460c00b330c1521495a8a79f65438b4c4f31dddbb588af93e48e3650ae26c8170ccac3d8ecee027ce39a3dce13b68c8f7739cc75bc17774e2e8635fdf76b4ffb57785512c6f725631c0258ee7886329c3559a0bc05316444876afb5863976565007deb6af200a9c1843e7943075686deaf56f4863ee4c1dfa2a2b70b913c589604c420a21aaa89cf3396250ce08a30838e2cb10ff6588413f6a502922462eb1ffc1cb781962908c160042d0f9181f430c02e17544568f18e1c7f8ee4fcd3bbbc9b375dfef93e77e7b1d02288278b196888102bf306719646102dccf4ae9d7eca55e46cc09593d72fc75831cc4c89c17226584ac1e325ee775c4a0108a2065bc10e9d9c8c41ce1982db87fbecc3bcc79c0aff99af7d935bc4f2cfbc1f7c2ad67d055cd5310057005ae9e867eb4aaf95953c3fa1bfe35db7c8be5a7a4e463ddbaaeb17aea47d993ba46f9fdd6336abe06fcea634df670d49442ae7414d6ad67f4afc2ae67d0ecb1de2664dd848e247423f724e751729e49c37e4f7ce1db31ab7c50e052b322fbfbabf3d635e8b71bf9e8af46e2423fe972e2ca2dfa115512d8c8794a3fa24df1a297398f0bd19a9ce748d798d9bbfd5d87bbfa8bca065e7615d9dfcb1768b28bffe23caaf76f30ce43f3feee729e9987c1f3e2fd7bd655cc566c461916eae35edec5a17a4a5393a5b689433994433994435926876aa71ab067312f1932747440143ea7cfca8c8498d7fc01bfd9e6b465d9b6c862166ab531753a168a0b3ba95f1bd306357f9a784fcc91b7fcb364d9b26565c598d70a0688d4b01b1280747659919d7b7d33cf2361c60ed7428888ba881a0db5ab4bf84cb02f4400ca070c413ece604200be9b9585eac163c6801b10a905564f1d6b59267c1d0d0b65eb37ebc21d4dee75c28d9857cc2ce6c87b3c2fdceb9b71af6fc6bdbe19f7fa66dc8be545bd2e0b44e18adec5a31cea9bcd3e27efe18eb8970adc2b47130d10856a0560ebd664936d532c9485b250166a63e28eb824142cb521804f00200a570096a75b71fe35b45eec170b268518d98a360a2aefd48048e3c8e33d3e34b8c76700dfe6f37147dccb4271afec6fa13ea76ff6cdbc01605701448005cbaaa394d27a350d0b495d881ba8d5aea6615162edaa4a21436badb552eaf7de4a2be782d95aa9922e499e94d05a6bad945aab5d4db3f689d6ef45439f94c028add6d65a6ba5f4d65b5fd495e0c05e4dc3422ba548b7b15015203d3d3d3d3d3dd97a9f2ec5922b45aa97e36a05539fae7691923cd92f97d2aa21fdbd286c9a76933c49c1b2022c9ee4a9933ccd244ffe510f582c1d12dcbf715db8ae4bd7811f7c30eaa13bd2f1d019c1fddd0e345d91171d91aea8d3a11bd2e5d009e982e0fe0e871894c6f8c01e6db4677ea52d6f97c0aa73f2d1a995ecf9e616e96cea7cf1d12998fc25fb6dca9489f6c45090cb9af97446da7692c272a9b9e81724cdd5c1aa70ed395541814b0acb76eb582c16a3949b5514c870b3dd2bc64b3058d47d592fdec30d5d187d5d187d5d187d5d187d5d187d5d187d71ae0b7b71306b446cfbb6afd65a2bc55b752b57c95b494649d99f1a5124de7399eed25d32daa08c6252bc89537eb0175eb29489c2e875516a2497379373a2dc8c7245ce13e38c78cf77c4cd6e8c9bdd1837bb316e7663dc2cfb5bf17b7133da18c6157db31a30f539bfbbbbbb67759a2a5dc8aec5c7220b758d6c17efb1168c97210b46c8509ed788903c2f926b841ad9a8d7ac52d2d72adba89925af0a05d6bd7039669f77bcfec57992e60f18efb943143652d83852d83856f1ba60428a6eb29729905c72b7c8954b5a5494dd6a71114451d105b8ef1590a69706b9acc133cc9e1e6a2175a83615fb8b37714a6e7a1d15827f12e592265d99b7da07fc6df68bf368ef7f93e60f18efe18c78cb3f4b962d5bba74f172633796fd35912bba4cb7c95da249d9bfbc4bd99f265125efb9b21bbbb12a988b21e14d9c92525a8b7c8bf7b883e94287bc74c9e58329ddcb043373915bf4a820fe6edfb31282be3ae0dd7477cfd9b3678ff9d69b4c1fd31eeee7ca3e87b48db445c3c7460a3132153b103081635403de375984172e71bce7c65bbe126295032b4f225a9337dab342c2b40504f7cff7aef642dbbef9238e8f50b869f2ec6ec0fddd0670ffec86707f6703eeef3490faba1a707fe7c2fd5c18dccf6da1e2b4c0fdf3277355300706f77359e07e6e07b89fd341cdc77db1f9b81cb03e0e07b89fbb01ee9f3f3779bee7c1cd7bc582f613e0de107bcbdf6b676a55d3b4726127a0f9bca9a3042e57de4d17cbf5fb67562326539724285916a6d89499152d5ba2606192949692609306d38733379ad58f393b3e4c4d9c8929fb3331d5adce14c0e64f98bde932da973689dbde6631d2383105cf9cb56297a6d824efe9204516d6c4c66c93f3ccac93f34c991121d9df6af11e27fdb94d7b394f69ad3042628f9cc74bd043e0293382246f6f13e6ca3475fce726b3f2f14b8de666f3d1ed3401cb50d6b5e13c3439473dcd81d7cd0e29e81779ca764092fd6a54c9797c05d9dfa394527adb2679cbdf7649d9ffebe9be71f879cb351d1a7625644c90e49a3c6542c0eafd1a3651cfb82c1ee08de374b58a3dbeefc42056fdf1fb205615811030c9a7adbbf2a1adfb42a4f7db23e0fe26066de5cac79293c4f55ba0ad5b4520044c92c77d2172041729743b91d5e363f19079568f263b71c90490b6474afd245955e431d43dc80314839aecde7ba16b5dd6f6c42032e23cb3051ef072bfdc47f95edb9526c1ae12f9520167ad32e2d00c4d729e29638292ef5325e789c1b2de17bac097a99f7ad57b5ce89af96de637911ad1d6fd99ed9bdc84c82f14226bd8233565467c51894bec8321ab87cc832f23d257029ae4be49ee3385813fbf97f904a41e1469003ff532220d4b7a809f12599586d47fdf4f5226ac61047cbcf7372770d9ae54e802ff868362bbbcefbe1722633ed6628feffbf7fe5412ab6120f8df778b3dbe366a22dabaa038690b6c176d5d4dc4f53db1e3aa8bc6dcbd2842fff6b736b2a537684ff7f679506b6753b6960b82bf075ffbedb9f74416ed4416a51f647b782dd0967def8548fff6c22e9cb1a6f72971a42ddb89b6b3ffd40b2765f5f8defb4ff4c48dd543ec4a9f39edea2dcc02e169ef4d55d6bef4dc33e6e47c34dad23891c38175b7ac3d177483b6342edc615d1fd6fdfb9b18b4a4db444c5bda078d9a86a78e7d99a9f3330a03cad9ab558d66e55ebe1965c4a1c1df97cbcf264f199392bcca5346a42966858603ba40e001f367042816ad248a4a08b0a036682f8d944b9f19832b666c06a36f70ad6d2db304a659ddd8a0e1e3014cf3941181d2ef3420f224c772bcb59a37b827d4ce118d43f67f27d97fc3ab1f02df3c65445490cb7116f5d43f372201b9ba108e8f951e510e805aeb737a980c444517ad13de9cb557f7eaaec2d8d34516dcdffa71fed479c4003ccff9d353a77bbc5bf3fbc5e611dbc6a654e52ebbdcf34b1172bb3b9db32cbde3a3fb78cbbffa84377cf4faa54ffde6a9a2d32a96e10d1f1b5b077c401d0cc8f47d6cdc68eae33d4dd2f0d13fc0824b9fec3e369c877ed94f9bbe8b3eb33557322098f8d8a4f9e16d1b77c6189a0a5acd474e7fb8c54ddbb452285b91a5bd50d6f00af0f735f41e0cbb2ad284a07804d3ead35dab623665aecd1372c8844092e994d939411628a6e0198c7918bc674645055104c6542a4b82caf44e77ede70c4f50c147fb79ddd3a736bf19b91ee36306187cc426c0e5967f3a885980b7916e38e54d0c0c3ebc454b9f0f09ab10239e00854171de0ba210011fe97b3954104f78fb7809cb1af042962d3233305663eae9fb9821c8795e3c4de11c2a991918ab91f6b4eb8311ce3c43c7940a583c214b0e1971fcd4e3255ccea85478820a3e4e197e2af3aa9fd9688bbaf8172fc21a4e1f277d06380fcdd31cf3a91c1a2ca39a99212a26a4386627258ee574026badb3897e13d18781fe0cf4a74dc8ea61f34bfc6d5e8864852c1e355f23063559f342a48f19c29ab05db445df266ca222a32cc2e0e3128c3727f07ce12e9c48ed80b5bfef69e30d59f377682b644d31859a574b3a27b0774de0fa134c219ccf52a1a775789c275380e9b96f43886aca6e90657f1eb64e2053b22b413494fd697c4e1d340c985c75d8aabdf46a3b3ed96768086c32a2ce28bc705a68d756da2b26be3bc4003570028bacca0e2da001066040e440041a988204125415fe040a3b45f6ffdc89152b03db8155135988891055a44c4992c3152eb21c9db893237ee4c08503469c804a530894586dafcae41df1c4a3495ac0d9a0b4d1f071177112362c91fdbf95e747ec7849e2a8091d38500325bed8b00445033c9cc045963b3fb0207bf61f64d9bf3c653f94e087a4ee89c9d5ca756f9a58e22bdad087cdf36fb8510e9c524aa9572aa5e99ce2f412cf57d988d9bfa7ebe662e830b6767b7b7b23ddf7dea9513c201c1308c7e3a0d9d66a6bcde95208e97615cb0de38d9b3afd339cd99dba53778e9bfd33776898f9d569adb4d25aedd7adb97c3218402c56e573c6af070f159cd028d90f45b97f4e81752664094204566421d301291404074d52dc100229432090197dc9536614cb25e42933d221c7d4f76e0ded1b674d04f281b346c5ee426ecee8d744af3b18e02a941dc78bcc8026b8c4b9968de78ffd179a58dedfb654f7ee6127964240bde55bbf6e76cb2c1d4eb7f933b7a9d3ee7ac28452a1ae95a987e6cf14b8dce2868286390ea80d95492f817323878ffd5a1461390f18d7a00aae5c643d389163377aa882038c1b394ec841450f52fc0338264f590f2ae881881c685c0084113d1ce92b30ce53d6c30d38c030ace82a300805ade188129e04fef2941d794287602f4fd91126208e28c9e58cad64475e413a9c28e203c1aa3c654786f8103c93050eb8060a17de98702f98260a069904c1a0527de11779ca783892cb15dd01bbc853c6430db9af60304f99112f3976420e15563f7a5c5633af8a50911a58398ab2258a23b2f0a102c5061b84a882042a7685b3974ead7e610b9660594c896aa24357e1c2c20b182194a4c4b6408107ce02cd48135ee72933d224c7e42933a244a679ca8c4ca13e9bd239e79c5303b5a9134ecc39e746bbdaeaa2b53ed5a76aeb537db2b73ed5a7fba46db32a55aa54a952a5bbab501078a5b1b363c3c7c729c5f9cf196d6da7d5ca66cec9d223ae37d7e8ad76ce72ce39e79cbed56b8472d9ab5abed9d36af6380e9c5deeb2e77db4e7be48e33435a09ff7ec6a97674e9c7c819ab456992ed8411c3699da6aada5b5d6f9345fadb5d65a6b9d552c1d90a958997873ce39b9279e9813d76c755561cc7951983f5f68d385463327f744d3984c1678e6669af1c036c33d61ef55799dcce77df562f0c33129106f3729cfa415a5945e23a33c6788c00d724f187dd645d2e6cdc9d18d724e7cf5561817b85decc27e524a29b5afb72e92703f2dc2f5050aabdd1196969b373d9b1a67d9f4cd37c1299641b33c6199fe8c07282ccf67ddd81667e0d4526b2333c5689369a594d2d77ce26a53bbe08e0cd8baadb470a42d1a36b98a3b5c469b7c2dd7e171cc49c2f29a6470db7a7b58615c840b17239aa820054cec10bc6d5ad4a00628b87265d5ddedf692f70a3984337145e94a131925273025272f27a55aab5d72aadf8bc649e986dbed94524a6bad6ee9cb4949ca95a32b448e9c9c9c9c9c9c76a8cdc1c1198213aef015bc0355b7cded923b69573b7a3925d17e8fb49fe2ad3e5e71be9c5e575870e5e5f4727a399980527a658952292895a20445900036022296744a9d926e084aa953274497d42175417422e88eba241d10b585be789e4fa66177da9dd25a6d3ba5b55a5bedbd9ab66d9cb6715cd779ded779df0782a9540c988a89c1584646a59aa1a171e1e2c50b18abba5a51ef958c6a668686c6858b17302a0c18d41b868b235ecfb1feac1fe41903b3f2cac72868bdc93923f6a14163db9c87b5394f8c1acb0176d96a9b3b72ee884be25ed58b72a7b55a5aadbdf5757de3aec781293025a39aa171f1c2c6674d4d5adc8a17c363cee44cffe1ca971c168a20bc504a4ac271d1c5fa91bffc8513027714029794fd4308817b714ddc14aee9331261e32c14f76a920bdd08c98b8a36d88b1c1386200a9f93080208200a9f130e0054dceb4b146d3111f541d4f79a3f5eb264d9b2c50327f2e006447297bb401865ba4041dddcb04268e12d5a2c14cba6d51a41868c9c113a8b959c64a874ac5746412b95a11100000000b315000020100a87c462a13894a6b1347d14000d7896426a54988a644990c3308a8118c6186308308018828c21c628433536006cc2b3e3b141b12a1d4f8797fb6aab617846eecc6d28193c60c203843906d2d1a5b10187d5baf848afbfe5cde06b474f24139841fc203477cdde173d0b3979d1529c6517db8d4e5d0d1eaf49b7d361eec6c14c112a1f9a82b68b4db6be9c4f9fc3a01753e8852965fac68873aa80a9db0cb456f1e63765bd6d02b54f295b2a4e60ddf5ae0bd966db91b7cd853844f1778e73672a1471429fefa8cb74709e20718bd6e9b17fe724bbef9a1f00ea7c9d51ea8306b5564799c261ca6aadc5449631d829e0559db800b333ce3c2e801bfea12f07364f8cc0465d358f719129421d732ae15fc2140486fcec91d89541edf44c9af35d269baddad04d4c705b3870b627de7150f5930c5faa03f4e497d20b5f439909a664c7980c3a70669fa6a746ba0b8b7236975b3beac94316061be741e925c1c6f071d1ad2b19160132a82206b4e463840914d49250b6c18a6e22ea1fb81f5d9804666c422977f48407494b240d9a06273e8052af125aa01919ba6c76a2043ae3501a42539973688127358254e2de9db0439d624448a7c12bf7f029a06787afa6bb1758f893d4652cd2c2464ba5daa6d826b0dca206246f64ba34f85ee25e7e5445e66e705f640c8ec069d9aa692a6df3165ad35282e2986b6ebc78c63400d9f31e4b62e334553e6ad114bbf9f68aa1d700c9c67f8783af506491acb4e1e16d0c7c7ca3dc20c34b67f8071edaf683a1fbef16588a1c664a684abc7e04afe6ab72f2776b20ebca0f34fc3fd19dc02f7e6432a73885e1ddb74233f1cd54465bbc9f73b8dbc4e575df14641ef3e6ba66e558b0873630c519bbee2706d741e0e602a884b3348b380b51c4348079c14bc1b953412c5b1280b6824c44ac5257faf9dc3c3a1dd141acdf25012fa5a8e52013802c08556cb55063ae34c030fc376b1bd52f441c69d837e4c691938110a193f18ab57bba625af702275bb47147ee0f7bbe3f04936dbfdd16073264584b624c3b9efb93434b01f2660d7535918906dc1b4624396684314d943755dcbec549dbef4354323a3afa8dd105d40335e3c6f0d07293f4156aacb2ec8ecfc0560cbd76e46535caa847b593634a968261efdfc514707be2b136cfe0e08a513f3ce0caea52c726ceb989914e3853d12ff591b8812ad1bf9bf1239595a3deae4fbdef870fa2ffe1c4b387a1cf024f5e1084895cccd90e2aa8bd09a36814d568b27a62abc7ccf6df9b23bd5d5d380803a315b335c08aa88894c4260fcffc5355f1d3a8e5f0c5b380d05856457d44d76063b7719f88f399a2c37a15f9ecf54972f5518c5fbb3d96e8238be8d86bf529eab07fa0adf80703e1f30f4a1b02f0c5b0ddff0a6593523c8239879a2a154938ac62f4b7369eb4584f1813dfd86ac3821343919d2c6efd58a1ce0aaada33b4be6e81f0fac74f435b75c6a48625b2fb46917481c339aaad531a21cce00354fedbdf2fbdc5e4f5226dedbc5a577542f325e122fb845f2e3262c635f69b37de81d62b5ccdb472c988a2ca2031ccc139f4aae08a4546e75c345d4e1d14d1389a74f3ab4055784e4779abbfa51126a232e4bfab17870030dc0e8b14f7eef8032b5dd401e1353fb2707c4ce6a9a05f989f739e6c897eec5f1918e1eb81c41827e67d15db105cfb3b2b1ddcce88398b0b96fec4d1fcbcab97c62f321e0acf1b6c1a6140dc670399f166ccb70a61012036187fbb2aba189bebbd2b7412a7342dfec15a92d14e9c33e2b1b6de4f0f153dc288da840f7a9546b04b809f4660a11838c809011dd301ffa1075015b41b17095a053cf13d3c119c30a84cde4aebd37ad3bda694b20d4dd26e71c5b8b104d0d932311a753b9ab37fcce7d4ceb84546d8d493025d0f3c96316852f9070dec75b89ccbc60d0d25f35122818b641ed6dc41f3c0ec12f674344304d94981ae7c67c6af047dc5c316bac577668cec383b9c665148688de5a400d5c6eacdf4b6c4be59feffdb624dd726b4b4b2b8c0fc0844df44c474d244889af7f0da885faa123e54fd46adcbf40099c032d8a7fa7eca4277fa6c313baf761901a49de6157b722be7d256b5c8dfd9b060b960fd6c0025b9521eb9a8f4a3a5142162460d105f841454b638f0c93f0210e9910eb69160d40cb51821b3de3a537e90ba2b659e7cfb72aec57dbb4aded899a34c3a94f0249f20a339e82393883272e2d1d7d81c219604d3d13c4469508bdb0ef6cf0029440acf43b1163be709d7ffe1bce353df6a68350b8b8a8330b8c948bb0d337ac7a37b234c0db58287f21b7568116004a603436c5049365592d50c4ac3d8b5c8779a426809d64a69a188234e4921a6592573566121df3477d5bd48c225f4430925d9a87de15f43894756fb222b50fc51e555e4a1434219bb079b096fd290797b8dea8165a701f76ac821371999c15aa021ec886eb7ea3571b89e0f755ad3aa36df9f5aafb4d217e591965698854967dc0744ce200d44721c752c8f2acfa05a921f4efd3a5538200edb82b7c21d486684ebca71f400a5fd791fe85cd25e6232fd830b9f7224091298befba8c69827aca409a875db606f8b69d0a2c9b142fdcd40f64a5572f2226208c740188abb410fea9edffe845024d20c523ca9665c464dde366b63656bc327bf09c4a66f7c1dc624a5da6bd54938af4f0783ff8e0b432e869599ced5e7c61d4355095631c014e5f13e59e99a319bebc8d3a728513587c73b14a91296734ea3b76f86e64c8506e6971425f5ecb7c4623c67308d769784e42599e8b0608ea131f557afeca6749fc0ab429398805efb437b2dedc183188ba13261f0f78b5c8b06fcc29abcb473d2bda7c5ff6606cd88db5e878352bc5b670580d899aad01e2dde00a547fb7dc4ed0c9de7fb05f55e3e50bc0c65f81a87d615f1090782af231ed8eddfba1a16952edc75e7cd70ee633f906ebec76957459d45ea6dee0933bc83cdc6784f70b580bc56b193bce9d1f6d0009ad3e88ebf42234af9c10102904c15f4f512f61255702f1d4112ed1868540142dc9f5977c130781d55665fff0936a5b92dcfc752fb8dd9c75730b70a0f3f6a40acf0de987da8939aeff56ae8a1feebd70ce2d6f9b726977ec5187f09dde1b104f8c40aa85f7284eaf7f457fb451270ecb0bb9cac5d952f19fe58cb7e452b22ff8d36d6a284328d5330200aa8ffbb994c3a5801f55de1a908af68b407f182bb03cb40350f48c0d2fc8b797c825d0012c6b54194ee6ac6f32036e4024871d7713d1709ac550fb0bcf63a7949ff21865836c71e5dc1847bb3bc614313dfa391e06e47f5752f032c32506ed532f681f5b9d7ba52f39224a6a783b0d1934a6080e2fa069538b09031ccb6b3d4ee1598f5dbf394f649c0c9bd5b0ee67beebdc6bf68205655f5e052dc8cae815d9c5a1b6a469aaca7084a5b4c686c2ecc1939bb0a552cb48b553018ed8750fedf3056197cabde8438d55be98140544e3164b244643aaabdee4207819ace4205db2cab9a4d6ee6fc4a4b8a6ff09332af51832e0ddf11235677e7ed52db00e0f5511f26c5b16307e1fe99c64723e584444c5a12e83fc685e1fb536a677abbbe73d3b3349a3a3934a85864404fad58e33e5c24ee5501b20e66bf1958ffd0ab84511cdf9ff1eb3ba684ad83063d09de22eabfbc5799ca793cec55fa7c910a429a4382e119540dc5a0813a48cbf8059c4801a4e82ed3b012be1cd39a232f11e63fc3af8a3e563e37ac5a5790d3c66a04ede72bd14580eb569f24675bd3c99270605bdb305df92f83283ff78b079afb8a1da4e9a180acb1cfd267501947bf0bd8f6ffcf7c104d2f9f65cb6c2fbe2e07152ef3c15f2535d7a8ac51a539ff3632f2925bc81298813270a87a73abfea574919e030096b8c5fcb57278a31e54d5ccac7f27058bf8c722ef035626315d186d2bc853ff3fd56ccbe805d7a2218049a1b10000fc6161365014c4072272f8682e554f9b88fec03981481c6543f582e363c1f8665496573e118e0ba1c5c6ff4d7de902542d8fce6df6dca45ac9e2110e887dce6fa662741b650467051490fc9e38154363d0dec90e4b949e9b93c446f02ba00cc923f22b900c6523f8ab8c27e14a5c5dd1cd2c489ca24e1ec32b9a520d879783b1d144857059ba45cbbfc6ce361fce721fcccd6d156e0091bae97dd6ac27a23e789be9d43f69070fbb125328fefb562ef8adcc0a11e97c1e87d8b8f255d6f79a30bbdf86a0a2954d46d0014ca5d68b1a5ef2a6cf6715830ff17f4d7cd753ccfb959eda38cb807f90baf886b4af1e970459897bae9af624b4545c7f2204fedfaeaae9ad9e8eeae9bfea3fbfa92236b16023d78ef19167e06097d68d5ebf47b5fcdd29a266001f8d8ac3545829e8ac3a52ecf9a24eabea03897a363cbab38b4fd43d5790a5d85826c3bdaf742563c05d34aade2e2849a50b3673136e9ecb3a2020da62e2d1a8ecb8f80e48acec489cc4408eb3686ee016b90a365166794ca2820592c8e3039b64d23b09b2a2b044ef81acf026b4f455ef29baaad22499d865cdfcbf723608a0a2aa9490c07602501d2d53942e980d96303419f0c8ee962cbc07cc5b74b9ff86fd18849e91a0ce9c6d81f2c5343a3fd241546bcff21f4f85ef343a93f9d8d446bac07a187c66c6c72762d89a5c66132426ecab90ccbfcd9efad456c7967350f26f8922b62865ddfe5def572f5fb1eddd9606975f51ebf7ba606b41550be411f54ef1d9740506b9d8b2fcf2a6d502a56f7fed3a01b942642609a15237b00326ccc5e491505a057e066bd00822b5836af55ae66b4fe5402671ead042bdbd1492ce9a23996ac062d8ec34cecbc0d5e0ef547a75f45f120cf005ff05926c271222d31e83d54688770d40dd0cc63d90c826f6299f0ef957452fb30a2bf74e47719c5adf7af00203dbb407c517c87f3395cb55ae80ae02735f7ebbc44c9bfbfcfc3c4d405e0952f55dc7f42a617b6be0f0d06aa222a03e3628e15dcf9bb54c71a7f12d2174882f8b07d4cda475ba3095e318f0e04da2b1c8bacc3d75be256f0aa6458809c31640655d7ecb1c57928b916d8a1272ef975b15a5d1b1f316a6ab74059131e29c7cfe8bd2431412301dc2486b0b4c27bb8baa579acac162046442fb800515a6ed2817da78ba79e3ced4fc2ceac8759040214bc0ff80b6b860aacfc7e16b6ef267594de1a993c61165cf38159679947fb9931a71957c3821c911a328e282b4b511a1a4e36ce38b4aa16b12acec7eebc69f2ae10907855a0042607ff48791736dc737ec9029d4db864f8533c5258f626601da1953f6fd47549d11cbe23af6440f806f3ae0a94ee7f8d403accfa7874a9d056ff5b331b91501b5966bd3052994412d53de04d9a43b6463cd6cccb38130a4abd3f3f1789d18537fec547540b14605fa35af6e39bb27e8c54141259369d3d589dd06f0d8149baa5fca6550d3162bb2c3d1807c89f39c44bceccab585ee5c660cbad2e9995a7df9f87c8ee2fb38dd3fbb8bbbedb1dab4226d3f21f94790a39e525bc8df89405686c7213d1ef04464e173c99fd8e4f01cf134547df72b21f2af6cb87512d36b291f6601a1c298686c99371407e89fc2d9a1c9bdc8e902292dc26681adbe9961db4a79adcc78c8f761e9e68fa455857a735d57a6d35224f77f0e8bdf48415e48e507c1350bf8fad01ce8b940c9ecc5575a35a002754e57afa82a90cddb07264b43460b0076684c561ae5cfa8c493f56a0c6129aa0e4cfd15dbd7eb56934eae84d6824331058c4be9f876013f888be68cfa678a9aca454920bf37ac176cbcbd6e76efb0a2b0f5fa92abd67dc764e971652ee35f48b3cc1000487dde0e8456a95e31bbf9b04dcd69fe135cea9ecc4e7f2d9de7430f031e91201f30bf02740b5b799c903695ce427c78c01de6546055dad07893cea37626e9ad52e54118dcef605756c27781db723067bf66aaf1c11b5d40999b98be8aaad0b7c480ce845897e724f750fbccfc341c84d26b0c618ce73c1571e4cd8d82d41e7f13acba7e43a1f9d2c3d26f7eac01b7ba27bd961ca3b7d6b1bd651db38659b14b2af889f49ba65b9564c4730d13e991b17d6df50a97f4a378a30b72ce12177441f360eae8df938633833c350a09732c2aa4aedb8fd11f393b2f7073e967ad43f257a15297f4bdafb7343fb0e2694e81b27609d03c9e3b2adeae813bfceea76a438a5594f8e84cf400f479b4512e2c3cd1eb87c883f423e13e31520ca7fe8a323ba5fd76e887129874e2713cda8a0d201b29213bba84e199ce8c0798ad7b4648ccf47f0789f4b23b6a6ab16bc276c19dd0f674328d801ad80e96ac1a5ba1c1d4a86bfbdb623087e320736b359765f423d8b60f3a8e801c63aecc770ee7e196dc2a70d402590d7a99209974d51dd22b4a4c0ac94bac9a3cd67d5f502cf4d6be97a94a23c0839e5f1f74fc2a0c4b150e4f846686f4007e2ca10a7818ce2111c1290ec8672821404a5cd8881eb06cec8629fa8083865eb15ff02382b061c1527f3ff35f85dadcd4004316cc5d5deb92fa567649a8a8b43d7c30523eb86f3c09cd7fcfeaed0e0ff12a0418ee502467ba5c64d9210c3821ef45a1cd73b33689175faf5e14304b88e7b94475c901d5c4c67abc59f524ee8422629411836bb026abcfc7090101a5275caac71aa0bcf64e280a4e9a709c8e61580d3f9e50a81071cea07bcc94c68858910489422b25514b02bc11e68ad1101322e8475bc7dc997340e4f54842be253950d382a8ba04b52ae3f745d11bb84ec06347e5c8ade7e4eab2ced5aac26e34a17b473bae4c72b9f4860d5976479bb2f73b84dfc3115c28ca0799716bdc38718d32f7161ada45db10ad6f529da7f5695b8acfe847c8022eeae972282255f55920d4f120774b558c25111a0d9fbd21f4933365afb793f6c062a4bbe6337288dc51c51916b031212f35c78ee40287563388bb91126ef66a2dc1a99e0bd7bd2df3d5ed87e905489bd6fa58868ca6a8d935222f9b63a09e34ba6976a18d9789b17757def25061eb9447f47d27d7bf029e2ad51488ff085e4b81d4a5a4a3ae5da36ed5d6b988980eaeee1c5a9abd6609e6a9422c84850eda96d948b8430634b0c2b774221c8ba1dc9be00b874f6dcf5a89e44a9f84cd438005800437e4418531bc8a92c60178bf3ddf1df1bf83ae1497db29f7c246474b6196ecc1b7bb640f1b5e5cfc3d489dfcdd5506770559d96e7c362caee2dec38bf52a4848ad3f7b5535d1ee466a4d0075f57ff79cd27d8d1602843a6b4e2cacdeb84b842607687e8bb5537d5a6a935b59c2b54f4adb14d49ca82a2d814d3860384fbeca6f70542725c0995365dc000060a8097c2262dca47f547a590171da85e72394267b2c4acf93f9647e323273bb59553159c88c9cf8c1e2f0e0278464da19a264365b9f4b7e62c96a73767ff52b9cacfe060ca61f338253292db2799f6b80e2554023d198cb0a7a48d686f4db5db7e764c1bd5de0218a3f700b83dbc82ca0a9fc23f68ec12fd118f0c1e9f897cda8c9bbe05d1b3bed2f4d24f850be8b3f2b0539535f3ee100ae85dbc395dd38093066499fb6c189c30078d48efa7014231968ab57e086a02b39edc470aa7a656ff6609152dda3ad023ab5737939353b4afd4503312601a4a89b33ad53d18d154a4e5f1d73f37e5db89f5860f2f9aec82f5d0dbe08e4c3f572fe26ce4ba98580b0a74cc36d3c114e111ccb05f473af307722522c7d2fa70e362c18e8b10b71b12374b949f17f4bfa05672081a8f1e4b8b7a2c2b6274dcf82bc7504f2fff1bfeddf2658cf3fe27e1c95d82affbcf75191ff538d4c822304a005ca3a27f247d5b0bf21f96bbf1a6511123289a0a712c143dd28a6f17871eac30bc1f27324251d48bce56e4d4a99651eb06b8d08f717eb0ed193853590538121036d2f942adf71217bf0f4929547c6b15e18a513ca171cab3399773ec3ca7dcba9d019032f9f6c76922b073337395619e0b34bf590cc08f887829e0fd5ab90e365e9ca1a43607f3d0090d5a134d7e6c81f828994e37997da468449202821c5592583cc8f939a18763d772898ff0767b3fa880019569d9fc82d044716d6db8221be3e9f63fed67d7e5c5b90e7582b0f6cb6e98f20ed8a6a115f0b1504b69afc2ca900cc7315c892d4b59197b2836585de290caf052d62e7f56d4c56ef97b6c083613e2a68d3efb8077169b4c554822e8b998cc77c01fe1eb8d30bf45809118305a6002d414cf36944200721330243e52eda4a0206ad54da16010c00b66222ff17facda7c819b5030a7d79059b14b91968062e6e66e184c0998337448d861747f10074988838f94b4e7d298f8688c7c177be7a0c7398bc25250c9a1f7f7157a96e08197ed372b406dc47bd3694fc3664dae4828bfc5589c6f44fffc23fd3198314bbf04221f75a0b026a9881219d1bb4a612c2d6d090a6619ec20a339207e35b2217291404af136cc1f64e15411fcec319d831b5a3003743506abddfecd8458134461a4ed6b55e06ed05e978627447cc8e674560fe785e0f55d9be77ea110df2b68817b31447ea5978135bbfd676ae6677d956e05864406b425ca66d1a2e4a9ff12518d1d6673fa9bc16445021bb0cea2e94c84c482f325419ab2157ee78378ce659128bb5c49c5ad6e4617c7ea4e0c81eb1f8092af6a5114e63e894b45aab9f0afbc27d1297c48dea0536de5eef03171b93b9e7e165d46416ac35642ec6ff8154468f154fc0ed7a0a408fc3132bea6e4ea78c1ae36c57fe990be2c6b6046ea30ee758ed536c2a69d3eb166419b8e9ca06d2eebd7aa31c9de2b2f17fbf4ef17f2b73c54ec97a5bc011218f8b3c4891a0a86946aba4a0d8ccfc1fa177955697137e07b9e88a9bb8b9f963e63d82e3d8c5334ac76f66047b2451d848faae97170a3f38e27b674194b84e28f7af59f6656219675632ade868a9e9fa83c47f220018461bcf929dfb237e309a4d2a6b8f2814336dffe5ba13e9eba2944d91d356442c686a064f8a5f34ab39aa2268cfa1329e7c0c0bfd97beff217c543d02a3d84ec3df1cc42d027fb00275fb6e4f4e79a1c2776c738b3ee74321f0448d2554551391e3e5b3938e8ab1f6817339ae786f221e73ec3bdc5bae87a22474281d880b181905a64a639367e250febbe4b0c5a4245be1f2a75b93316103cdfd2639ea4aaa8047c4bc417aefa80c1207d718aa1165a4bdf11e9579efe1c5dae31ad801281a60c53991fd0640b3a8b7f657cb0383b3686413e7c7a7d898ce1cda5f1c93a0ab5fe581f83d196895133ec340e79b1b6b1af60fc4ca55ce27bc07c5f2e45a4dc18d171ed3d6516ae5cc200bbf6540a465130e199168c8148f0578a1598ff07828220b336138c168d5e0fd14d6fd49e0be71c3860ad6770dd7fa5310f845114f0f465f482939dcecc745c92aa776e1b2f8d3de5a98670c1514da3901cb37dee01ac9d653855dcf1b9cbe5c2087b7e468573c792cb1a8a8f0bcbd05823f5547ed94dc7eeca6402358d6414399af5ce3841950048d6e35c82559e570d5ca14030f25a2d8e1d6d0ad63345fb7673433d350d9ffd079370c1941f5cb39364ce396b603cdb65248d7eccbf22b153fad7a709345ead35f730b0da8f7f86724ea4218c703c106e306cff5e284a44c66a999f2360aca70a42c8dc168ab89118a2d06b6ca438a8f7e0cf7d802048eb6c2b2e496c8cfde4ac1bd91395788648036474a2613788ad5b3a380646e6443a460d0b312079303e8092a4cc435e1264fdf13b1f9fcdc9c9f94bc3e98b1eae3e1779f83b9ca1b3b08054fa0ad13ffa683cdf22bdb92d959230ea630d5e2632905609daac486d7b8a311bab00d34bbe55d7c49e0faf8e72fd3a867738ce0b57f69580c2a20fd43e06682843660ea2019b29385935dd1e5e6f9866b7751e147c37b5b36f0509cf3e9e88ad987e34d9f487efb7469428a474f23d3275a82df4f74b1ab19e27c0a1696cdf04f8c7c87eab9b7d1fbe94a8015a3a6d447ad10e907d90c7943360272a90a3f643f1b11a927b860d03fa7c67e010b1d8467e232d0dea847235ae04a9fc654b0e4a0fbfc10c22a390e70bfd00bfc7081c818a67d28f639932260850fbf321c11157306c867c0027bfd35056aae44093088c0eb55f7f0b1f7a6f0f003984d0e4753f2280607fc3ea45225e03625aa9358420a8743c0cbb1539ec907b98af00f255581de5d5931d9a5771dc2969abb08223a03a6688404bc5f7bb7228adde046e017812d53d70ebe8058aba3bfc6cec3c108c278be98108055dea2f7adeed5d83548fbfeb8c1b8050baa36f2727448a85c3185fde0df72ec19393be390059039d2e2e4630b1363bdf392700e738c07223a1b9a18fcca631798056b9227653a6fa1b267ce5678fe15b649ecaaeafa866995ea7760a3a8d5e89bfafa149ae541cd399fd765d1471bfbd0453d9b195a40f919703fdd976ee6e1d39f17c1754ceaca44fccfab44e9468fb7ad282ec0016fcb6b91290c3d31cf7a680381d16c65242451c44a429b8d889f3381f12152d14aa2ce191ccb5e2a20605367d450f6ff0e700dc789a4b9ea5a185b79e309db748536c1559339ce8de800a10a040cfd16c726b3c45b9823252254e35118d9dd0f083275f13476df3ffa9d7143aa80483fe50dae0c371b931ae12dfc3e397d7cf89c3a461bfb9a97911da8121e8d0377b79e0a88d4889c54df89063fca91a6ec30b83a9e8f4b5b9d78fb4450b86f81d129687ce0c083dcc05b083641bb2177fd4bec2f04d1ff0640b28fe0d0d62917649c992a56d55a97755616697407cdf11877c494416f70484cd196b32befbb06ac4bfaaf4a1d05245663d5619b0592de753bd88e9d2158d89c3f1bcbd61796a42186603fcf88c9e3b038a9000162a47486c1f35ba0052bc455542dfcc12f86692fd29a4b7371b432899812be838e63dacd2ab8b051de00cf4c408af924cf7b7414fddf07da81509e2b6cb4d6f79611f066b0947a79f50bf483274c3c2d6fd591584dba213c1e73be786459d2f35a22380fa1c0902e9d5c4534445248aa93f142b98805dacf41bf4ed097d008b89c991b6b7929a5d64532d0b9ff715638807ceca328d66c5ca7d13cc9b0cff79d69f10c57407ef326434ee18e5bbbbd013789d6c4900a3bf7e8b0cc03d7cdc6b3c28c2c65bca5cb472f768f8280852a0c5bd5d1f17184f3a6687b9053868fef547e845152f0d6b34ddef37dbc5b62ef0de2e5fd6473dc6eeb8916d61b6afad46581b6fed7d24b06f802bb03921a69d66169238ef966f1d27f1dce5bcea0ae6747183a20d48724a4ee16a9f3c8b04abeee883123570cd81b80ba79f955cea0461dfc149b8b255ee1a88632d41e3661e80aaea5f04a3b000839b72dfc6dd804511edb10b7c956e75b7ad811201e6903073c92b81695032dc76f348be54062a05e8a0f3b2a18ec35484fe41931e654350a7dba8752dd7903434cc3dc10447a6976b010c146a4fd3e1c1b11edf78aaa41c5d6c19a865c5d8e71ece8832f1664010be4905b8c5e316fd094ac9ab455490e8de5b43ed2147e00e9114c66b8c60a6b39ac58881883a77f15d694244cf24fa2b652efb546ad42afc38878bff7df5ba3b21dcaf4f8ba7821674f82697017a9dd6c6949ef7a12297955027f0fbce127ff00b54d9c5d4b29d426c98f120504d46cf482f6e96cc5a397237f688e6780f8d1a6cbf102ba5d2d96769ca3d9b229f94d363b2670a1b5069c469f8141114ce986288217fb2751259b2c45898d9e5bfb8c429be4545abf7510ccf2f5eea2d46da9693f0c54b8990aca4890f3f5ee41c570cba29199dac505d0265142e0f09845249547a1c8c648a09092945d0d7be3d80393c0b3c1d1b887253462bdfe688d0fee253012ef90d5f90e032774f8d1a2c8c9615331dd26af58b319630bb2da780405bae992b54816e4841a96e0e20cb863bd5931ee396e44e248027188b5c7e4f0ec6bfc7fe23c267762eb776eb022e2d54b5bd52ee146d4aa4d6e85edd15dcc60aaedd2ba79578d8e180eb6f23e8d4d91238081e5e021bdfe347ca7861ad91c7ac3011ab6c385854a75817f5d9eed393df17a9c60b698a96a753fd646748771bdef013e2683e280cac400aec9701291f5d4887ea647b66f0f4d4f48350dc661663e74fd26832356dc0cf25a1503de505724d2d854a9249b19153c883900a9924ff8bc33e263f3aa80cee8041ad10c8cb484aba1703ee3862dbd8487d1286fb130924c48dbfd45b21fd2d35f8468762a8459e9d95255f2411871d7d1bf883983f59cdbf74f6c60d1c2e95f9884a95d400ee5b3c4906a18b82fe34bf3a061136d136a8fa1a849feef6e8fc6bcd769dd50c0a298f5e10c188a493318ea0b6bd902f6984f0b1f9f531e55db2ef4283be2c4db97773dca09b22cda063c831f629a3466a46d2fc5306e1cd5c8587b953bd3d06eb69b5771c10bf07f611617da11760602654205ae02df1a31a601d1cdcc567f0e6cb0ad55543bcb9f0a040ada43df559b2b91664ffc6f8a2703f33ed33b4fd4ee7d3e72ef90189e5ae34ad9be44d12ac1cc0c26352e45f7f243d64c8262a237813a7e4f59e09299d9f717634a91e1a94a1e5c94f589234229c1e7d50c325851f1025fe3272d46fec667d5e5c241c88ac7e6d97403e4d30664953511202029d99487281a947b1bf9d38a3110a87a1f78d0b126691788dabd0d84c446cdbdf94558269c50dbed53842ba6612999b845587eb03a2280f784c386fb8a4d6d0624b11cda9eb04caa2ff0059fecf266986c89fccb760804833783d0539ed62e0df7aa40dec0d9207a78d2c091c07df06e845283736c05aaf9facb58622ade3a5323a2275354825be7d11bfcbab7baebcb3602189f15b07392874e231f3331cd2bc10e1ce4593c06598eafa8f0f15069f649f357a1ad588559f5945a5096f57bc035727e8e03f6c6c554a46ed5768072378cdd2db2b295f952a68c769f1281e2e32e638c5dff966f6aad7e7703259cb96f95185ed2f902f2408abfdf88f075c460bd216096861c8562f04f1c15b20e69dd66192d2261d5192ea5570277e2ff34f6ab8f1a676ef59d3d94b30b0095b4b6f350ec09078edd5e61e009cd15aa574b6aab77f004fe19a28baf191475b4f0b307a56b44555fc975e9e5eec1d2550da68cdef5236774b9e84035d4bcf5b6530bf7181654649aaa96fcab654c15826ccedb35611689dbe48f87f43383cf030c35a93356aebd0acec106d5d50fdb0073176f5dae9d30a667f6749a3458a7758e12a34186aac1109abca0a0ad685157819373d17626aa5c62edfa9cc330d4357af625b7e41c0313be2070d25293f618b034d459d99771c417c2e04824dd83bfba0c0cd1865055a1e89bedefae7901da2dcbbe5c1ec2676214be1005e9c55bd42d65f82fde5acd1eb959ef7905bc576660f8548a4e7c2f6453a524b9966190a3754b31f9fed7164210b2c92b69ff9daa0202e706f9115ab49b1442422161da6001b1f92f33fb85affeaf41598d25e5f6053dd47f8d7e5ffa15be2c319423f1030bc1bec0064909d95735eb116424a6c77044e56e9a6c1f783e7468b8a9612258b6ff6a5a4c02ebac808844aa1c7b086249cfcc0815f4892fcdb806df84f23559fb7d6d7341af0bfeedd25d161231365fd5963593d8e49f00a8a4d1e3c3641f5ee5d19b8c1f3bcd69301b0f9f2168ca0654a248e9f5ea8e7618e7dc69942129e16a0e1bd2eb5c8db31de92c7fd3c83662fa5107274bfe823b2893306402e6a71f77d28cbd3a2622d73526657c755e79e69862fb4c753a337a130fca5759e7f28e0b7db1ce9493efa7af401f5dc70ed29125c4aee35225aa396b304035687578ed98142b01c2bbce3e9485bb521f17c630a97cf302ee8ade1b525041a87259b4505b6a2e8c78ef79db5a5c7ec53533fc2e3fbeb054e98480e27f5ac87f72e4e24a2c638fb912318db7fd6140e8f400542f4c4340747a177e73d899fe43ccf44ef738857f9d1e2746b39f0245d526162abae1b7fc9c9e70d6e61cca3de89f174602e9847410044c8f80d8fe5217bf5492000509831f3e69a43044163dec479518999e19fdafe0871f88c21f7c052d8adafe6c19ee67d63457d3c4c4e4437f9d396e51931d85558a942d69f348cce2e70149b4a124ea380414c7363199fa4b475fdc2e3c55075004cb538d34e291f629fce7a5d5d10634131cc57816b490da900048f489b9198624246556e42a858d415a1c79c1dfec37697772c1ca2f19af2e1ac2f228ef9f78138669fb9cf05c7baa1780cbccc64cc9fa14e04312a25a764eca76ef3d7d8ee8cd9b2f80a6b73ac890fe71a4afa9d95d2b6a1707d3a405b65a4ba38cd81211847192e9baa21385eda1acd13d293ab0498c3e8e8e856d74f64d1d4a50577532490147ad1f742d744d87834acdbd6c78f9faf2e402faef625e37a4448cfdcdfa1504c818e68d231832c1237dde651a78865a571d4a50cfd070fd024ccdd93b5fe20ae1e4e41a89974488866c4928037c52bd3bd83a2d8ad676f7ad507fb18852f23bcea5dcf092c3f4a7ab8e59b4859c67826776668f208c112d6832f70474155ef5ee80b6c69af29c54bf9071596df1eaf53463367a34130799430c9ad3f3665f3eda858be0b9a5e6935d01275708141434b6f2c16e725e2cb9a100d47d09a2841982c8d851369a8279dc9c41d00a5c5fa70dadc64773daa8a3831347d50466401d688f9a4ab8c557666503b492386e9c08a27dbecc44fe2018a0aeb9d7f345c6870bec8429c09cad2d3b0d382f87ebf0efe9fe35c20777aad3fe5988e3be161e53df688e3e4156c1057e5af888996e9d3926884da021e851c1459510f89734a15244ffd9b3a265bf6c8dcf9f0f7e702504c296910b13d1774de0b3e51a693fd582e6f8d110ab7f302140015c208587bddc2007345b6436ff5d23db0ec2639f0c69aaab3b515d71a9f73b0db2f791d1b421e4940a23fbe7473c3a66a3ea87a0d95213ee40061116d0603f0e6dd734e05868dfaaf678ec0ce29cb690b33d885b3324b29b58ddfb93b97f30e379beb8c5da9c2d7367cae4ffeb9724d9f74c6c45a26cd189aa171389840a8ce73a1987a9255f0c6808b16f33d48c5557e07825ec04e3b053308b743c889c1058ff36e62a0cb9f6330e9bc2cf81fcc59368b373e6119b872e74ac1d92944d139177fc8e2aebba40db30cdd41c55b666bf40158b67b48a08822e3f6e2a0c4ae77cc11c1be29dea65003ae09185db3647d5a3a70d71f0f40dd5f237538ece20a110a62cf6a2b907a0d8b90f9a7cf4f669ab175b3dd04f31ad83e8fd60f4023d78f503b9d24614fdfe5c223a72f24cbe5f223dc7737d3aa05210894d78b1dd92f3d6cc1a8030b3aef13a63d4cc9e2d1ffba6d06397b562cf9a34a453b88859f10c2847831c83b10cc126c3a4bc151d68807e05eeb071e21cf408921a80c1f5ee10b26a75cdf90adeaa67410e0a54948efbc35628ebd9cfa24eb0a4c5b3456ff597fe1dfeef069b37f5ba96858b1a8c5a29fc7bbb72f302ed98b00a6cd5a7ae860625e2f0806a8ae75578e80086f17104da75c84a7181577edd6e29989dcb35a5f87202d24516b29a31903b16cb20e5f15a8d4414150ef52079471359a28fffb033b3d4edb036b08593f3204ae3c7a5d712ebcd573d6d948952e7d4ccd43a5aa885ff7f1025b519555d08120d7f2184d5372d6170d906d48000a72946cbc854d67d67a1d6885b1be507ac92a23343d4cb998e23790029f65ccb584254cc1d0605097d231855a3ea72cdfed19f7b01573263f26990eb9b7f2c94576ca0797f2fc95dd4c8ba954525856272192ff70c9a90b4936f8db86165a81e0cd1678e9f2bcc1597e62e8b2f34cd39b3dcd248626d7a98944fb96d5c2ddc779a8529e230fd96f09c2deea76b1af3e7c77f081b126a7b55868414220e935e6a63d7a291a8aafb75213de360d946571a95f062699730d9a5ef51669e23af014edc613426a26ed182980b7d94a61fd2104c1cc34b0a4b92b55455661e07afb5f5b51f99ecdec56c444bad73eb4592f8534ad3bd44f98352c2ad411aa228056000a2c687d4c8d3053d06f4017195efb4c987f8b88e0690e077f588f549d97468a506728b7e85c3a382600e6064627aead5c503d7f4a03667b9971874099a1a369a556643959e2ec799ff7dc7e5385830ad4756fe8a24ae075d8762bf5dc13bf076c96a73c4266390f07eb2039c21df9171eb0649b0449e88cfe426b12301fb0a7021d83f9c3fdbe42f67bb7c664613e9450d0a5caff4f5eadcf8ac641f9760b37ee609b32ecd92a8dbab4180ed25991b9eb849dfbaf638a8eee0e30e5de337482294c2e095dbb0cb1b6bfc43a991c7bbc226cd695508f89ba73296526cb34cfee1e98fc7f60d4bfb87e2d8f00a6c049e22c458c12e08c705158bd6e078cf1f8e6282ddadac0bc79f2a8340f4310004411204320702e68667d7a0bf32f86601fb14916c261c1d4b439a91108afe5c2206b0e2028228305426e8f4120aeaa0b8e2e02ea707386d6ac03bb2cf292037e4aa8844c355183db89ca0b2fde1f4a34ec7e0f1d78405fb82482b6b5c264ebf0c704381019a7cb336325b0ecdbbd4b75eeded28a2bd1393b634a010997807056f1b30ebc368b411b7cfd3c0eb039f8523e0cdcd14620ddc15248dc2b7f3d4750b0944adf3f3800ec442c8ed6690f4ae274e616de5ecca3a908d99f6613d1cd3e89c54e1d9aaadf388ef31c26987fad6ae855d4f3f513fe89bb858aa08581737ba96307fabf75e95705a30986c8df4ba6b4400085a8838451866af91580f960e45f516a3c2e9f4a077a0ba5d8e82282f99c9068917b17e6dbe7d78cf24d53394fc56320a8a29e67549c6f31f71dc1210c3f69017fcb06bb835d95a7fefded8f2268fc18bf13047bb01990b5ec560ab4a1a28423288dc43495542ba9d5676622e4ef7f836b0d5d15b97916838ca94e4c000387c870edbe8ba9459a960e49589178fdb806d1b15ff9087a5aed37621d4fab69bc5d8a32d020ab131c7464974c04d606a7950d1329bcd600fec317a13c6a9f04ccd3de5f3178f5ef24e1c68cd1d659a8b0b53d57fb71c7c793d7ec5eb1d799b6d2533723efb3d7a8aa91f310960c257d070a8465b65e6c267491a94dc55967ae6074f5a361f609ec91c077ab0571b6c14c434ce6623dfb00fa8879bf2143ad52aad5afd3be189ccd9dd507736a8b5e7565bf620477dc96979f9cdffbd6c265a5ab3f33db5676ee2a1eef60de5464f8ae9d61fc258d62e22f98e9d07c485f6834d2a8e1ede6a52710252aac887fb0530450e28057470e50a274b8dea05fe8cb0ebfbfe915231c9360111149d056ed1e52d1fcd27872c04be447bb3d846863657dea9cbd72aa285ec73459b254bda9590a58903a6496c10bda4fff88ab4828fcdc368872bfe7779b9debe48faad77428a5b2df9d042b4322e162664c9269d232cf738fd2145e0b605fe7bd07f5e9f0d6ba69f001059e550b79096ce116e07fe9966f80f3278ee439228666cb657ee5a72795b3ce972e216e1c15f5010c773b7ebfe220303a53084dee460c94fb796701268057e4bb5326a9084f30e3b026dc8e8445f20e9421a16737d6a524e4090fb91cfd14e8bc82a18eb84572302c153a6fe0027d6662dc296c3814c54cf98ad7c80687248821035bfdaef3df6ee6865cd72bd8ed28a8ace9b98d9874c66c9dd2d57195abf9121697ff91bf41edca4066ad58d02ae8acbe475a37dc850e7dc09173fe465f2fd6c0f5a04181191d7c483477447686ced22d0d05ea0ab69fecabf863cf4243b607b8b0bb38b96dbac9fc2d0b8e998d1851455e5f295fc5922f8e9ea80733391c8f75689aafee0bfb18df9c14671063e7ad2cc57093ba1b87ef04ff8554e355701e78bbe7352e553d240de10093f990d2b386f1aabe205dd64916c75d1155934a768d5a419df275e274ee208264bc51e64a1b2ebdecb4eeb444db5de490bd7bb52bb6ee3f64cb143fd64849664059bd1006b8bee221941de8f333990c1df5aed0f738c66424feb76a8d98153375c4e28c9d7620d67d7e650d39bd1ceeb2b98cc68edff8c6becfd4dd116db00ef8d473c5a45955505036805632f2f6c78e97c0534516db299b1a7de81e5fb6a12435a897ae20e0417004416d9811a173aaf145c766e1a2985fc06e26894b586a26060927db1f58f43c613cb4c4df91658a2d75c6cc36c5a329bfca2eb3b125841ceabd83ccde598911658dae9956a51e3c70ed8217f73e501e04e25282a5c59f78df0881d72bf153f9cfbb7ba70a12cc01105097e488573923b18733fe6cdf719c4af9cdfc3b86df2caf56ea17c632f2def79aa82210141db3ed2923308f2d8242e3f482a63637bdc6ce1e5c0153f951e27bb4bc077b0e3a8455a043a88a1c2d55c26f98dda7241dab9237923198cff1c6d079951b2033c4eae8bfdf3ef4ee7d20605790008540f3c4366bf098a88d33af0fe2c4872d39e4cc2b9beaef8efaa05f131a79059ec05e8919999a96cf5596e54297e9b1309971e0ccf79c32585a25cf1d5bdb54052bdebd2603247cdb65ee2fbb5cb354b87bbff3fd8ae2e7e8b559aa887d31ecb28d7f7729f4f9005e5d30aa2ffa3178d283c1426048b1905ff39018529d645106de0fc17b4a4b625170c50daea23deee0de8b7f6050f086139158c747cd7d648a5da9ab05b0c7ef26ba95942b33fdd74e7ca808e1921df58218410d295c497901d830d0e2799e04a38dd21be5ca212db177c5d34229cf6e2da59ab07213c2df89ca9095abd0f52dfb950d52cd19f03e2c6f8cc0b53c68e069528169382325df36016c3e2535bc0869d360ab37c40f80e195c1013b4f912e657af5b8dbec3025f0c80a2079073b786cad4d5124ce8fb63c9d8be59d9d2e1f1d3cfbd0fc7b704803b486701114be09bf19ba48a16d47af3c25498c8d7ca702653bfb31aacfc196fed116c598c9b195c6f37b6d644f07d0beeb0e36f7fde0926a9627f5ccb881ccab8d84e0d183c2833d6200764b3dd624cc55325e018ba61b56658afbd7c814072ac08ba88b4bd13d394b0dff569b4b68be80bb166a38d6944905d579f24734938a704fb27bc3955942402f082adbcfeb59e1401cf2b5ab5d31933eb1b20f97aa360dcf8e43841608d22dbf8165e5710523740c973f55d43cb759abbb279fdab78de709a5c71b2d87c6f13e7182e56ed6c8b748461f4e275c956f1f73ac4d7995b1754baddf538f0b23214815a0b1514af2c1f0c6bf015b65dca0a220f8f0356dcc8249487be7541ab3bb1667b44e01bec553ceed133fa29a8202a20d2f0dac8679ba00a00dfcf836216b75b4c6dd1f6abc718ef382947f939022aa70c436a451098f3268b31147e24e294b207d813845282eebd31968ce3c8be43a15461c9c7773e3421913abba79e96d14b992e0452f0d1f1595dc15b2785a2856028a7d2f43f84216a48e5167656a3635ab4bc46b1321a1218358a79c436b4f6cf97b5b6aa4c8d015dc76a650fa774f20a56f5b0abaf590182cf03642b0c2e512c7077f2a046b28537090b688688a00a5cccba355d18b969d6becf20c787d09cb908f5a492f3e46d61249899745b740a0a92131b654950cac71f5820b698400fe85b59c8c3150d4f3f9e875e758ea08b55c287a177a0e08f3f4a4fbdfb0a5a8b3480d0eefb16eb582b0af2a14a83a73887abc54b2273529d388bc8e1de1160fc42ad74edab7b0ab40a1fa8e6f67538d15eac7a385393102fba0aebeb689d311e43809aa7c1dad8739811b675d156173819ffe24d873efd4352fe7d48bdc9486a8df48c09c792643c6d530cfd76a0585d1db5921a6873c507d689e4d3a649f29fa99f1e8c7b7abbf17d05d1a0bdaccd4e49c544fef73715f008bcbb8f3fd20180348999466353d745342167828c7c042898aba0436d08b2c61e4a1d3f5894005b6af420ff13368b05a478a5c29821f1699a6f6dfe12972ed50ba9b15460c4db42546246eeb00d29930fd7dbc7fc5a40d9c27ce6dbfa477f1b62c4656be9b9b75321a65e3a27bfd81dc0a59be4f4fa3a21cb9cd246c9b7a41523781d15f206035ef87e71b4d8e15f501552f7cfe775c6e89494606d25c5967a3c1150805a6c29d272fb97a89db2ea47a3265b80566176b919381c16739cddceab3d177eeb90d8bbcb0461aee024c000a40ae444f173d9bf046921924e44dfe80216e75c00522f2109a686942afb3c86335d21fbc11e97a801772963255239ac132fdec5c8e7bc76287e007ff056bb9402ce1cd3cacb0acd866c3a26c6f3357e14be0aa38342dedf09ff25bfb0406daa077651960a00077b93479f176e49952aa2e579d2dbf807167a793e2263fbe9c3f14b03008baf20f48aa32e0072f4953b17c357cc5e34f4c0cd2bc19e00bfe318d8fe2255ff3336f48710da006114b102f48489e60c74ea502604043eb4f8c0239bd01fba9da2352479308d88df3afa48b3588dc61322368b83c0ba9f574f64c5cb18144cc4cc75c90f41ac1cb7ef532ef44044763f84c04f20a07e246ff2033655b52d33184a043558fd8f0a6d55b08f1b294f0ab868c81a3ea71758f129c25739a9b03270b412bd312b0262d1209ec3bc3ac8c9feccd4a247216ee2e6157a41f5057dac2115806b869a19a4094201a74baeb8b51904e034fffec37db30eeefcefc762e350af764e50afff0ec8ccf531c9219c26174020f751890679b25f86a53b47c2b5094da7c932914af50bb08e2f65253c5f4e35b47b574b16d5aa586413a7a7cf721fa5f08c16fca4210559ebb694a19d20842fbbe08ee3e49ee1b8792850cf824099fe531d382780fecd6dc54e441a6fadb23c3eb4b132fa3d045ab306d0e25255dc5803a1a3616ad835a155983dd55c062babdc9f35d3a6975b398df7e252b4291665dc3a77e112c8c64de32ddf5eaefe8ec77f4a688da6124de7738f32e62c73cad1e0275a7747d4b8fad380bb6112ccc1c98284055dabe58d8ba246b047215559f40020057e0f3562b3b537b095288470437faddf034e7c7090bbf2ef0e00778a19a56fc869a485c93ac03e60da46117270faf7d9092d10486fc8b6c316365121d887118d7e2c229a14076eda3a2ff1784ef5af1fde5f289a229a87fa4b18126db9e6c303bde7ad1b5c6a5c558755c310861993f02c631ba3858b82074a1e6209afda7908b4dd8a3f82f5d8dfc0022986aaae46d21a7d20b60e4f4976063c5dc0d0387aa79dd74879c4d8913de26ca2b31c9bee3e257be18cd1962ede3a2e4021a7d7596ab852d6865ad9c75a05ca28ab7e591fa6ec180cd1ceefc03e3619a49af0efdf3bb841953bba28aa774ff4b45de330dc5023f25219aec8462be6c15f46278795a7a612e3bf555addd197fe4dd81a014a7f2ffbc307de24f0748bb9368f09c1146c903f7cd3bbcefd321d0d3aff996ac0e74b904d31f0e57646c6cc2d084f320bf0b8cb0223797c82d984921188aa19822a38ebf41bcb0acad54d6c8a42a75895418b607c821bfff7e0ad978d7db16ebb6c6728a80d25bc893f14743d25dbdeacd50fe7e89032cd095c9af2d3d516c7ff7037d05959f289a5fafa803f7ac8f2ed7e7236f351749150a8e1761c63cbd4e822c1723fbd17a421810bd90b8b852d19e242ecc726a0d8100d649b376ef881bae0d008e0ba0def21296ed6fe5bc340514fb2116935c0350dbc450413dc3983d595c9c366b132f55951c7ad934aa7d8f457785e29abf8f9ae245247cdf5ddca10dd59098eecdee9a0deb6267577cf687c136c492fc9a880a333e62ce93f4dff249bc69a634e6bf8eb9fa7bdf4c69fbccf4c106a866b4fabdbd26fc5b3b030c3eda01bf432dc228f853ee57b86233e651b7ed50c3a46af16e5bfc0118533cbb1d73762071f5e332d31e23a51f6ee7ea27340e1168ab2018584f74168fca27c9b6b75cb96424fed60c53b7d0b6b6e5c9d3ede313004a05aad1fb06dff0c016e759cd7e8e97653290fa18cd9dfcc88d2fedbf80bdcbd1529521f1f622ccf1fbc51712071d42daab8255d2cef9cc1a4cd97ba6c0c93b1ca108bd2f2ca6deda97c9d6fe5f7228e12fedff33ea0bf2a30f4170bca7f5d8d93ff3c63fd338f519499c7ac5016ba9fb7610f07eb8d4339c201ca91f3de4d97787df367c7d8848f03effb989cb86dddd2e3f7f0e4f8e8e8e874681ce02c0d57b8df8e8103c21667f1f4f0f070f47ce27ce8747676344e1cf856b06feb076d37128b0776f3c251013bb8fb09d13565630803d5492ddbca8a010467a74cff46b88cd7d30f4fd992f3292088681506217de5d3f2ce98b3b62312667e2d7e51423effb73461ea329c2a9093a96eb7a42aa5893a60d00c155bcb78830d88006da07d97d18a7372708c179747d0214b40ab14dd74b352aff8deaabfe0893a21f8a33d649fd01e18508241621a94c4f434de37b7d4209dbc951b766443409cb3eaa1715c4c3ab827ef522bf4a42a104a70150ae8da96016229de5985997e9f6372bc93f8b83193643ce80a2d6e6b7260c8ef542a33146fff3117dcd46a16b09dea14132c9b3fcedb8fa45c7f18b65aa73df27d1f081dffc59836909fe55da6ebff9ac830bae9d3702dee48ba551a34c2165008704219b8b423c9bc8b18ae5388f185e49e87f25590ee0a94d489fa4a586561b838d54e1d8cbd80b2c1f8c0555a9aa4003278ea5b9a4c9f48e142f7a2e4f5b5ba172e377a0234206f5714df481eff8b0312f3fa3b2f911aec1752c640f02fcb3dbbce9442ab47df9cdf9a63eb23a3a5ab934e768c6216f54a4bb1e22ee7ef5f64d2bce5a6337077d8b8e9e31c1d0b9f267bfcb4420af61f77158c7c5ccfb6cb897067270ed54ce5eef40877149ad49a92a8118de6843f752f0782f767970de630e11b0d731a6bd793cb4cbc98a535c1fd520128b59c67081efbdaeb8a41313027c92ce1c6d55829ee896bb7b500676832c4d06b8390c87911ddd08e13b089682c4f791a1a66f1f45cb78ad997720dbc27d22cf9e3ba79ebded890abb199bdd286ef23ec2af0d4a76ad8d5480a5fe72dcb8d8ca56b15cf42ca0ef373155dd488276cec980d74587d372ede4e53883a544b8649d1849bd348e5a265583cc18f36095791af22891889460fc8ecf7a74309adead520f680589f3e4efd27a778e1931a7ccdea6d05a37497695a84206d115499dd629d16e44590f4d608321b6f313040ba07e7d363fdd04a608fdb0b8d26904fcf5b1222d5e0dd5e199173b1f6caf692b88c6ad29cb5cca81888e22296951f6f41e25e983f89d0fc3a98c4229573b47bd0674de31fae8cd9a912c969bf2d50f40f736f7c4e8b42203402c03d5ac11311fa4c4460f70f02785446b8e275018ad082b74828f3f71e8bc4a2d4204c5a0f717763fb00cf50888b9c191e0365985500893bc28d0dd93fbc805236cb517db2838876f4565cac7e2b761fa6314d10aa048b15dcb389982126fd511b7c11609961ff287239a844f1ab4e695056112636f68f2289771f308b340648ec205c940bfb3dd3afb5b6250a22c56d05a0e360611adcb8fbed316463822c95141360e85597cd9d717f20f5aaaf0ec100179e10752fa2471e1b0d18f2a4abdb7bfb4af72b2b8c6e40d718de1f0d3307a1bba5f6996d12c7baee2288a55fbe69cb21867e1a187cfd0fb58a471f554a48e997c5815402745191bfd1894855def027cd18c44decad90cbad09e43f2d173067592a10ce12255b205e580541ac580a2050feb05c40ce625778e2c7a6edfe92a56adc10675750d1e389a3d86443f2f37d96158a6505e42591a78669d2bc4cd78ebb9e177fb4a0d65c6c4b903f5af88c760887893b94baa949bbb19d0646c0fc349e03fa20c9673f5c7fd56b566c72e9e848cdc9167c1cfc8b83644f31da12042f488374afbb157281e39504ba8de2b8d1a583bdaf6355a806bf79861ef7cd853601fee296bb258b23b45c0170f75d390f8e9a31b7f566b4f1e68be6bd7ae55229f6d808196ed22320fc1e6c9d91c78a4d43887823e4144da617110ab8863e69618da374512a3d71eaae931a66eba757da03b6d5eb4e8a3b775838f0d30a252d9cc4828d02ffa096667e21d0984f4b44bb8daf4bb3497c2281e25e6a107bc081f7f890385af90ff059a30910693f77c09f94bd1972553ad3280e13984ffbb483e513fbaa8d203f806e42f05ba268ace9b2f7f46657bfb1abe6d6fa823d9ee116520c5c3813c68c464d9e923f1a0ef3e094dc38188e537e5c869a2c2033ec8bda9eb2d908fea3263350b6a0b448a29d02ecb15a8262b584082e66c71ce51681b9262b003539eb6e111eb377039ac8c16b82f83ff11ebe0b2103d4bc033acbe2925a2090329b0ea5089256debb18600017deff55c1888e5196e41dcac7d878d58bd68b2e348cc3a9341360a1cb2dfecbc243285049079773b2aa2487165250a1afec1dff8059221c33f5dc8703f5db979915b1ed6e7b4b29654a5206e008380926094b4fb4408d21a4196b9cc1c40b3658d04316361c914349d485a289d5cabbb1810aecd97082171b6ee058d8bbe1e18887252f57504a29754a29a592868ebd825d482b6432a3245c7c5029916195040b4ee2858c5225b97d5d949577a3a4058f2533e27ec0430c2f6ac4e890440ada04050d5d2c8993df0f461116cbfdcf3939438715c49882861b5451e44314143664b045962b3a50b981cb978406e344450627a921471228b8f3e9010ad29165cd8916170b5b2c065606dd0d77b9a7ee46124128a1f1029311153a300551012b9ece60c2a40627e8524555c3fd44772dd2e203ee723bacdc3527463cc93259d69cd4c082ee1304f16468808a8d29431a4390f9de94734e0e63168b2ec993e65da0c6994d14092152820a3b24e529bbe439ffda2a6bad95aabaf973ce39e79c33d69cb3f0b6ed02343a0a8e73c3167d23b36bc3167d39d1954911e2befb2129f785baf7c2a18e35c4855323d7b16cd8aaaffa51bc12b8dfbd9015ba5cd8a22190109914e8ed34608009fe5c78bb3a81f37caf95254b46a50696f38912fa8bcc9e5d4408f7add690fa5c8882bf841c29725f8a507dee85ea73d26bfd1b0e71a16847aee2fe98b08744bc2378fa18f21920656882bf0d67189cfdd60780b8bff786a34f50e67b825b6686d1249798a8b4d84f03be3f4e2ab97f04975ec47160be8b8c5eded697739c446188bff04531f708d306e550a3983f5bee32692ac7913e8284921fccef4099ed6a84279f0341662b3b28f499330e8bd3681215cdcc256107108df1451ea516364c417e05a114619186410ec4dddddd33151bd556b574ad52de1aba4be9524ae95d0f35ef08cbf760545660f99ef4f979864b59ba3765f9632d5a891f764a6b9dd5bdbbbb5d2b105c7486a32bff7c8bd2dd35d3a2abc57be7b546542778da6aebca08bb5ac2f34f209f8894f547978cc9ac0547569ef6fe075900f7fbc93fba4608d2d2b73294524a29ad952e84322c1cf02893655c322c9a2be89252887d1eb9cb3dcdc91da43c810b03bfa4f204994fc6be4b9998b103b9c6c4642c29476b2bb7808c35d33147a6e10932cec59333de0d9e0c404d428b52551c7198f592d603e31c73d8c6d98a60eee3b80f737822994547fc294729a5b488e3709c35c2fd46964bd995e6cb5f4ef0257b49780e2171a4ac37999380ec3f95640ecd3ea1984bcc23b283347bfddd7b9bca94edb64f705269d1fd69c045a61de56d75dfaf4ad5fd546a9c39c4d3524dd69a1ccda6713265afb41d94c8e374da41298f334b7618b7cd5bf65775d39babf932bfa76b3722454868d17d52366c1337ef4d26a60c2c7fca55bb5c3f58866bcc20a63ba5328fc3fbeed6181d9fe3f18e5007285bcc11ca80b2451c614c88c371d26042d9e24b18865c287e17f7396992a73cbb5944601a347d5aec69713ecdbca5f0973c679409a5451af0c8cd1987318bf55d9e83c12cecf45d4656cd40fe6a1aca7cd405380753193a32366b4f54904516590c993a4f787832430c93f797c97c502293d950a760b27b5f76c19efba3ec7ebdf6de7bc3b147b62393edf4dcdab5ca48a8f5fb0044f6a6444c6badb5d65a6bada5feededeedddedd3259d743cd0727b2b7cd8a57450ea699a58ca1d168b4a6997c0abe9d754e7777ffffda1330fee9912736b04e18e3f125d3b712c7ca179d3e2626a28c27110693c1645206bb81c16e6430d98c155c1957c993bf607e37ac5fed3077ffe9dedee1685fd6b01b99c36433304a61b0ae3d8982c1d2689bbdfa4fd9fc51467b66d397f5c87ac29b169d46e62f8382b2994a9d7efaeed46b3e0c21b3893167682ccd0d8dbd696be50d0d8cc6ded0dc882f69ba768546be9ae6e546439f34c160b5c8713ac7b91da3df212c5729c56fb17e37d3a264ad90b6781a7eadb625d812ec56451adbb2b62e355d8192aa3d45d96e5b727d3a9d3aa5b4fe4c7705c2051d6b913271420bd551aefd795722c7e99ea34d32a7bfd221e81079c44a79ac4660eeb073c251b9af1ae55a8d1a074fd5a3a55a8b922b945c95d6a092c7ca946bfdb13ae5b15ec9b5eb73d7ca27d488ca76ce1e51c85875b4832711e0b5695d32c7573f7552598bd6c3bed9b9a9d2b5edfbcbc29e1681de07e8e5778f048164937c79a59417a8c7a7c5f6f7e9019299f6008d361469770927d4dc5df4016a7f8e73eff6eef67eba3580f39c60b528d322d79ab37f504aa9ad284b1ebf16944129a5f467bbebe9cfb4e04aa62e7716fd19b25aa473be8c0c225b9cdf9986d239a369913e16dc851fa5c9942aedec744cbce9d9f1d991d2a7070604dbd9d9d1626767ba4ba6c5c744e0fe5186c3371de4fdfe39b99f99e35eda0abba1ac47fe2893bf1393b21e59d7b9cbf797f54829eb9137e2d3cc1f699ca66787c68746ca645226b381cb7626ab09fcd157894131d0f2551f0b70bf10912c231011229285088bc56211111a21cb084244848808f1d9011a01ed08922115b2c870c93b79d2b814e1c245852c2ccacac2a259bec8da408b1d590f8e2c6b4b466d5419f509a24142578408d92021fb3e8530c6582848c81521414233480868e7a667073c7e57829a84b608691a61cb9613aee0c6b236c58cdc4a70b0bbe353af4f50ad55a020ce27a81664837c806a403e4134bb4f500d7b38190932197d4a29a595da5ab3ac4d1943d62c17594f0f900f500f101050934f0f50a53d403bb6224d2474ac8413bc49daa8a43d4d4db52939c834944d9788a93ac256e07e4ff6146f76b89fdce47e9a30bfc7e7de9dbb93fdfa748c86eeeed3b333bfc787ce6eeef84c2e94dd9066a6caa481fb4758dee958145054943fb7b3b393eb9586edf874bf4f8341fd4041d3278806758cba7b904f8340b2d61f44bb7d82bae7c6bb84a99a0a2318ea199e40428b1e4081472a6b520a48884b91e0236bb2d68213d01f28cdcdb47f637bbeb5efb6ad6dbb7323bb997f236bebd5bb61373d1d9545a86f6e565cdc502b6eeb4dbffc527a9ffe7873df04fab29d9b09eecc9d8e71efceed8037f46615c4834c5f76b3e393bbe91d7a43223051a8c59b9b3064635418d93f0353a7df1f005cd0d16be3cddf38f51b96ac4d7122d370c43772aaa6619fde4fc187b7648df390a2d7fb1ead4f2b0f59e3efab054cca92356eebd75cfd5953060cdc55346d4a9bd2a63af22b05c3e9c369f7171efd68a8b20277efd1af7eab6945341aad05d70bd961376b74d96b50a4b2e3f4ccdf53555ac7c6e97d257b4b91fde78dbb8979058397474155e425145ba4451c87a65158855ea80adf3e0c78c42afda082dc81a3e8df170cb9f0a38eb1972985ca3c1207463ef10298d07f42a9619449253b4de388266620f560850653b3afe86bcee6c91160fa2e6b556859564b01955c93658d072cf928298fb0dad1d1dcc9352c6b29b065580aa072ff8d6ca73d9810fc019486ad07c819ed00533389040c49d4a085162b667d80a9999cd1afe1fbabc5dcfe82c2925c8a908a010a22a32448b3970c4d5e2841b2822b3998f5c3fa65fd474e59d68e96b20759d67850ca3004c81c8be4c29c49f1f7a8d0945198ab07fc593ec14485d2c0e2a4ac22055bfd3f9b60ab652d7a908faa0bee9b54a866e061a774d2a9c5f3a49c53ce091a991305afdd9d52a74e4731cbb9c59333a3bc626a093c6777af7ef07bee3f2fefd1283f20d04c6b5dbd67ed0ff7debd2c2c6ba6ea3d8e9b764a2359d37545aaeffb91df8fd784bd979717181898d5cfea076318fcb2c2313118638c5f66f63e8f7bf960561ef094b0b59155903ff3cb2a082b4ba72c5d29cbf724c7e99bef68bd7c37729c1e2fdf8f1c87871abe24cbf72432d6f2088917e1d07a4be338cd5fad7af90d15157b79f97d3b22a5d291261f91d23d0c3fd381539ffcd58e5fb2febb9ff7719eb4b693397478ab0f06945e7da12c5696543489d0e4928aa6dae1f8f09579e529d1fdcb0a040af33978c32b42926fb52a2eea251c5b4a6a1586aaf047930f4dd61cc963a5529364cdfc4e29cfaf51561044072e7c63e5604c3e00940dd4cbe900502fa7dcefe1f87270154472d9788cfc75e4afe6a1f110497dc114953b7081b22db29b0a0b3cea4ce94c71d1e9d231bbba3f6a512e25bb87f7a369a6e982af26591315f585634b4979e1d85bb6a8420f94aa9d69ca3d7aa0f43a563f3b92c00ec2e41e60ece6afb625b5188b12538a51892d71b12f624c2aef4737f21fbd686563b3b1f9eb36d562db70b15943672a05dd11a298528c8a8dcdc616360f6de4a1f9111eda6863cb4f3d8e2a3efa58ab9fef59ffe33db7e37bda2a15c1a98eb083ab0e44eac0d501541e13609400a222d94d36d585d545d6d47e38fdb8f2e3698d34b270c9ad33e5ae33b563945ba78beb4cf96b87b643b48364670917fe68ba5cd562f3540e7e2a3006153b8a418925c5a2c4a272c7b4c892dbc66663b3b9b5d8365b6cb85c1e5a0fd0872611d01ce8e5289b54114fd28e22a3265038b1083c4a289a685034cdccac7e54efa0ec3a0f069c597584471e5a770f3077a1f92b4f2d3135d59c5ad4e17906574637ca8279683c477890d8d83a76a53a76ebd8d38c2673b8efcf593a2a4aea25b4b1d9d8624a1e1126926d91dd2af776be47c4d5017c0358fd549da90e446a395dc902d562d41749f1d078888a5aec254d9076589a479335fd3a5c1a6747c7ec0e4bcb0e7a0722b59e05f2d076b4902cf1207f210f11016080c76a6b817887cd716a92bfe6377559caf3c73a95ab52b57505f81c6cd9671d9c52823377d3e76ae61770660cce0ce3a0cd15446a7d0722ed780c4e59335b2c50f607e36756cde7a0b491ea57bf8d4d4b55cadddbda5fbd037c0eb6b2e7e373b047ae2012eb3b56a803c4b0cf411eb982483b9e8608f81cdc91bd7f0f3cf060f56323d562775d17dadc6c00b0fad199c282471e1a4f51c7ec775667cad6eee67350267b1f7c0ee2c81544eae03b10c9f53dc059d1ccc85fddc1eb00897ee69a1d31913423ca4f394b86ca51d976673164a91f7f0d169725b6d40e401e1a8f114f131e281eac1cd0b31dcf439339ddf7e72e1db38ee3fafebc46e3c86eceba49268e60c90412d6926fd23728093062228ace148f91e388e0e4af9808502db68c89c085149b8705da9c215ffd2ed0460cf9ea17810b19b301438a0dd5311b5b8b67c89c1cdf6f6386ccc1f1fd3665c81c99efb7d922736e7cbf8d0c644eccf7db90217360bedf660c9983bfdfe666238693e38860b30943e6bc7cbf4d0c648e8d940d18365a6cbe9039aaefb7b1c91cce692a80e8b9ec10c166ac1f6d6e90fb6da6fa07ebfbbd0eacdc07a3acf590fb45b0394e526e25ca24f7631d723fcc0eb93fa649eebfe124f7cbf090fb71a420f7e738cafd7e83ace95f813e38f8e400d68c3ab03307fa2451813e4a401f1b644dff07fa2c91351d38730e50ca1a2422d8acc7e300918864b31e7f23a6c7d310e010b0720d917a8452da8cb2f643ee870136931201a3ac21e57e9f1ee407fd5eb52ff7b30bf43102f30d7d8a6437c78951e9573fd3ad76bb42cb42148b8a49e5fe310146363600c49498e09a8dad2e497f29d58a1c7cc91544723d758522d8fc558023b86f797c590b4c000dfc3105fee872731cd9edf6e306fe50fa41e5c712f883490df9634beeff3186749ae3fca8fd90fa285616fce34aee6e16a833a5d3e5c97174d638a263b50056bee18f261089e8673b7e003ed8d854dff7f320e17141e65c11456e41ea88e0347968224835ce8eef17c1b603d4e1d23f5ca8338505d699cafd3c349e233c441debbe5f6749e7a6c3d438390b0f4d76e3a1c96e3c34d98d8726bbf1d07868b7dc9f63b5801c7995c3a887116b05a455f53e8773182555b01a5d2879fea76ae2c46407a71cab1fee73ac40a0d995237b61168bc023e6a1e5289235fd9e8c056feeeaf7e08a72579a8dad16c99af99533ca75499e2f826dea24c9f3fb6dc8535e6173b04bc0e720e7e558f0dbf9e4eaf30c30138010ac74878e1def09934306c78d9897f93a403993574cc974095a7852cab7aaae7bd2e943ce316804bcd68e4d1c550e02827c81dc202cd84c7b7612327b3052f6a4b9e4f7e012a3d4698cf32aa814bffc752abab2ab504af1fb86545d48c9c05294a1d47b7ed3cbc0369cd5bb2fa53f0da51455217d0f544d1ffd3694dc163c43f028192c6941a41de5fc76a5942ecdaebbae8ecdb71dbd54aac0cf05e08dd5023adb104f1ffd3dc6c0b35dc0eade5eae01feb4abd5ebbaf7e7ee77e0f8dd776f95e5cf550837c80ad50209b242a6340c4f4a1d973fa60ff93d70c0f3e54e4eebf4944d1f370dabdff4bb5290c65d6c513e0dc39bedb4dacb75df8fe9ad6037b2a9a37af9393b53c79f0503e2ee5cb91e62e039237d0c4002d943e9a3fe5d01e9973eeadb15900e29d0d5159096a28b044ebeea7ba0b34a008267774d6b60c30f324a9e3e2229255cd00213962d567ed8804ad3549515b4918612307028b9414c14320c24162005e20a79662a6b7a3af72a9082dc03f9c89cfefb3f12a788bfeedfa6c8f7e58846bedfe03c226bee17b97f9ba07143bef7deff90f0bd61ff91a372bb49c01c87318bf53f8af7efcb2eadc57befabeebd5ed414b8e7644d81670addabc0e5fa3d9376711cec2f1725b53d2d3ea68229c8813b2db6867cbffa55c88214eebf10af4296bf5cccb415849ad00a926dc80199e70a48976dd80a624d6805b9f46f38634351059c6d8f8facf1e7c02021fd81ff057fe413f536059e593e0d9fcafc390e636fc29f7b5af49d9b2619e67fcd53c6eadf6bdd8e2ce40b845b7449c1415ed4f2e78445ccea21aa1f01fb6b7e6765c6859cbf561290516829729a714e933cd577efa940efdcf577fedd4f2445987bac1a721fe6afee67917984c8a72d20a1e026b9e3baee811aa7ebba19e42e0d0c85d3752f657d415ad1cc25753f89565fd71a72df7b2f64c1fbee2d2073ec773f8dbebbc0c4e1e4ab7b4c86a441ee5e4e1ce4ee2d089335dd5f905b8172f5803a5b85a2bf3a30f0d7a79fbd2267bad0843a55050499ef4b0ee62f0bd4e2bcadf9aef74c02c7f57b9112941a05535152ce2c37da3ed76e34dd6bb04759d65400254ffb4548a8214129f5377eed6e75c03dde7bcf5720dcf8af45ee73c24dce712fef5ccc120f1815fe9a78803d1e078eb9c48463464da82cf3890ad704e3b9c80dc78b3f1dbd78f8e1b7dce3781bf35173c9718a4cd09fc78c9a503138c0f9047a2ee2a424559e4bb03cbe70261d4928a1162d0fd89b2b106e3c91162deb8610a1c699f9c62f77a0b800cf1e7e8b560261abdf48ee709cb9c7355dae1a8a5c530552dddd1c08faac7e5a415ccfbd2b046ad1ba5ce1545af55fb0052434922d949723442fe1a47153c995d46211d6cf09de0960cb0333bbde487685e39ceefee272852dcfaed003b38ddc31dde2e1fa29c5834767cbe468a966edcf2cf62754c76e64fb33cafebc354ecfecf370d99f50665253b63fa3d86ed7632bd8f59e0b9c59c682941e0f86b221e0871f864346666008f397f52800be41cf5e95b32b7de1f19e79000094b9489116ed8feb79b86e3cfe171eb10ede3e8fb71fd4b177bd557900a503703e6569d13e0f7042d1eccbe3971df3fec6cbdc72ad56ae702685f30800a077c3b5c7f378155ab4cffd8e67bdab036bbf83702abdeb31112efb2d7012b5d80394e11162263c4a5b088221e72fac2446135d666028fdc502592ddadf01ba5a3ca1c5221deb97d7118e5cfe728423cedf8d970947569eb4699568ddb372f737c291158343989770e4f287846752388f6e98fb790486435813fcb9120ac087e110dda1ca2c0021e7ad0578165bb4efd9fa9d48bca99402cde38fd4c3a11f27889a9a85a15c35800b7b7c5ab42f37e0d1952dabdb4e250eb3fef377ccb3ed98ccd696d0a27d6b59d98601780fc0cbd510ec2fff3000e1908c428b191872e087e1d0e4d28515a219d8b1d934011471749b746c5e28d9ff897bd5fdee87bc1121efbba7341c1a323202a7114ae802057473ba4928a1ee5531df3d0d8586605ed525fb77608b767fd8cf428bca565dcc170eadc28eeb68d8724007f3aa6721e6bb1fc202ccab5ece46990456ffb5688b862a707e07e2bff146662f730907914c3834938c9692888c6e4cfca2227af9711ae5480a5b35147244c78e9733fce35c9a313f24a57ba7ec42307f231c9a377ff9c3c08443134a150edda6d39c509d5307a59a30fe18bc4960dfef1ff22d1b1751b8dce088469bbdfc8d972284a50879f58788b2bf9724e4fd8d70a8bdb0938cbc1f9bc8fbb18d929ce8d6c5896edd8505ee00af0ed076379513c97c43f9cbff87a408798f7f488a108ef77e6829fb13f1cc6e08bdbc3c0e5fc22199b09da0867a098639bbd14e312f67313ff6524cd8aa6f647695b23b4cd8aa466637a9cdf07ecebc7f79988781f9981782f998252118237f49a249fef2efc02437eabaa4ec2ca8c296d0eafb2e6c0d59fdd712d2854ed97f88ed82830c9c3871a2d166ee44d433d597a0fb2f6cd1d197b2ff2b0d2c6750504e4c321041051552aa90851274210d0825a244374ca32179a1dffcf5a34365ff3f821570a4294b29f24893a851f6a7469448a87befbb9b94fde56ca446ad2173a6fa3953bd77f1dbd7a2a1906b74e4ff31718db2ffa55d223b65bba87eb4b6dca221b5d9db5476ea94fd39902e81ef029633d56ce6912ee527da3b9a5845d1d44052d4c6155aa416505ea853a68e80a2a5861d98cc28a594d6262e274d2a426829e2861b6a30c56c64c9868a95c1694007275d6268620d25b6c899b125a22e8759b0f624acd90518aa18b54dcc982d576831831867dc3084bfe0491405c4683af24593194a58d080478c7ba0b07b1217c4061304e11484910aa4989d4515decb0f5996b47c6134c415ca7937133306366e70e249176870a183ec1734164f987278c08723564491c2052631c8995d8349239e7820820a23aafcd0b4e2d2031a388882140519947a962b9ea8407606961948614697267228810e554e70f3828c1f6c68dc2309ee6ac68c7ac203a594a2e1aa82f6cd93196262545ea085259858c152832c8aacc8018c0c3dd07040861033407aa21a65fade0d256c85c076e61b8e17c8e30a4fb429539713afa2c527ba8872a246132c5255dc80638244050c3960c122860d36c0422b7086143348b1c4195856209fb2a80590724617215e8032430c334a29a535a4b9f16c13cc2d9c88a12f7af71530640142882d32e032a5b1483086e8c209195ba2a88822c48cbea8048f323255065349ea1086961a4cc0061a33d8d25400712f184b7cb8a2c68464ca091e59b93db3648d91cc71d517f44676d1434729a55445abcaaaaeaae3e40d47ce8623ae3399c699341491393353265a5841152ac058524416d47b0a1d6e3612030c9ad8486a811456b4604629a5b40b27bdfb69d222429226d57c50c2d11769f712d6d2fa92da90c326c810cfc020e381163cca78a592d6ce72e62e3adad5ea5eb91a7ad005a597d65ec3cba9b53ecdf62f956fa5b554d65c5b2badadc609793ec9cc607a28ebac734a39d7e456c173665993c713e45396491465153a6349a50c1e69f6e73eea5efd29d741f660fa18c2d391e54f00645b6bf794c958e51eaabad166f9501ed731ea738a9efdd246ec9c31d802e32c6b3da031440f3148ea01ea4972b887a43cb2680f383e98bb0326e3d3c3512bc121d32c6b47502e8577a9e0f9922543d17bd2ee6e0864ba64c50a049bbb4cc3510459aaacc023cd6ea594e142d2bf321c0783c964ac2a9870dc0b18b358ffa24c96853890bf3c2ab5885a6badb5d67e9f8c9c9919a513104602d335eebdf7de7b67ecf784826f109915f2056ba625c817ecec150c95eda94555967ff1a62748266bf24942a99a9e9c3a36aabcc8355593ea4995434e61ae8ac0a36a29fb2f75ac86324cf7baca0644944fcea1ec8121161633957d52a42f5f142603922f6444e1cbd6dad003f74a2ad65a6badb5de77b9ce0b816296c0e34c06b114ebcb9c90e9a3803b9053c1b9ef3be3de7befbd37e85624f84ae5fbdebd773a1da275057aef9d5d027b912c9152a41c67836d6ba79d6dadb5ed817b635082ed4822456eb6041496b8fbeca8c982ec576494b5d65eee41ce5feecf42c2a3acf9dc00ddbf0f93ff4ad4092b1c3e40f6a7993efce50706f58ba2816d10cf8221b0610af7460e13748c99be7db74fed0d5983630399be8db9d1852307327d97914760c03d727d96b4a94f73df7fc67b72219e89e94006552056a9ae19b6ad9d76b6b5d6722f3b152800f9a23fd6c4643b236be813198732e57298aeec7fefbdf7de0bd42f7f595580ef8f33b3d219c20da714eb0a35049aef7dbf610a77e68e539ce9580d91b877592f28679c6d6b6dbbb5d6ce3a9003a5acd49d8894fa68f7e86ccdd9eeeeeeeeeeeeb3a9bfbbf7cf15a594ca10e9babf5f0abef033650da53f44de44c2f3633c7f1f94d5e28e9412c79b92284d8928ac4d093294a882e5f49d9b98d372b7a0ae81b3ac29b194c71d777724cbf21c78b4d6a75f80d9b1ce93334d2447a68f650ecdf44790395fa62f57344907fc79c4466a68f2440d0a0545507164365f741c1e183891318314254b40cde60bd69842a4650a1b5a6618005314288c28220a32c498c1e67b2190bf62ca32e4648dd7ab103a8f2b64ee8736a594ce96ddb4db5f8cf44c6166f72a08c3cfa4483a5b3e97a76bf694e9906210cb1c9f3c78e532e76ce69e5f573f5e5741463a1ab6808cd443d6095f962d1ac9465640da41cfdd861439e2ec5fe6642f2f704e39b9448eb1c111187e56dfbe9c197105bf6a032c75d480c79ff41b29082f1986d26e90f6a57235658da44c5985ce9e427697e168fe57ab5294e77320885966c053617ef5d084509a309f8626ac1a6082875f8bb706782a999ab2d98e689a4a70932c3f4a96a1e8044b28624014035a9eff29c15d965048a191e78772024a0f68ea9f0fc4ca8d006330c1128aa9a9ac9425145347f96609c51493cc6509c55418f921e0c9bfc1c6c465b45bb1a9bca02a2c2a6d924314d60abd3908c15969b24265c90a94a31c7ce858dc8783ca637284779cf15d255668c02b1a25898503961737b272843271e170680206479635261b66e2626693a31b1dd3e7048e2ecb1a1313c5a186cca7f4e12c6b4c471c531113110f2d39bad000ebc8b2864454d3e4b6d0a1246b4b8ca0515ec6053b90c8b2864411b659d6904041ce013482479a5559d690a0e12a53b71260593b22cb5a155be672c0ab283c74e0d0a3f54970b6807b4c1a96b52a4f72fe484399203c39f3d643213cb4c05124d1f22897f4286b525443eeef9f35c89c99fbef0fad50c55312a799fd0b0e9120074ab3eb843393981242a40415646be46e69db32d585ca76fa7b8872df21b5052cb32b040bcc589ad9ef69fb309ce309d3278fe37c565186e5f36bffacb09b16e5abc8c023b56e6df5e06816c164abd7cf831ab07c99bb852c6b4520c9348f34e37723caa791ca52d6c1511ba1d4d3cb520a004d33d0a47497f8c9c7f2658ad892251879fcba02c1dd7d4e2b93fded4b4f9461b9fdd91e98b962a96fbd002ee0d148a674f543a9c704b66da891b96f09036f9ce6c382b950464546387efe9eccf190ca1aebd6561b952c597e361a2a621ec5a8b6d567be3c5bcb37e7ec615b2a9ade94d2d456adada39b9ea0113a266b4648551a952d79946549c4924cc4960ea454965fbdfb65f00ff8f38ec4b928b8ddd0a57644152a33f93e12e79ff002ca852a5b2445cde4946b8626490697272172f004c40c6b787f91299567bc60065150aab480893472709fa25b4120e10213313859624412417c914653104250a1c1d65e4cc941a7ba49652175ab8571b484098824b2143106922442425a2c6a33e416336a6dd100420553b088e281872e6ea6688173a2b4c0640b173a38f961ead0177a3e4d410286292ba033e0a323538a5c58b01e7b1c96e1f0d7a2103670b3244a2a18f744868ba45f7249938e05840c99e338ee599ccd1cc7715c0d4cfd9235ee4abf640d59fa25fbd35032c3540d6098d190044651d1d1163114416581618684b602286450b2a45f32734a9af44be691c33e5388f0c771b521bc88212222c90af2086bc25d96b52447472e2c6cf0c69abbeccd4fd2b428ff956b720b48677f9b3b7b734e98ac45297ee720954daeaa1374d97c80cf60ad2033cf5419a84b7186524a296d77984c75a3474f77da7dd1e4eeee3d612dca27eaee1e1add17f80b8ebadc599352b74fd23d07ce53d1f608d0cb09c151039eef7f1b50bf9bee7d35046f8ba56bce564b5f528bc553554adbb097738ef321da7476aef59dbe676add86b27299edf4be4bd51c95a2b5be02e24dbb045fc7b26c4b82e9f9ad25a0b467dfa64ba2ec39ce23215ee06e68fa6568ecc8272212052b72c32073dd42427071032d56da30326256060da0dae09246134ab371d25ce3d7e35e5701f23893efcb36f2bdcf61c9f73e4b698c7cff8748caf7ebeae732c1aecc8932c7131eb93cad9c21dba7f68590ca96054bd9b2b89f4ab370e5712673f4f1925c8b4246a65296c8f49dc854a4345a76ef798de827187c944e144c5083c28b5b1b5c6081640a065048a0c48821ca1022849862835a932e8e90b62646c020858818b02c79141753c6a83d7c90f985ec5fbb16c413fa7d363300c3e33e9f3e80e092c320aa42c905509214311b613f9ed490840f2ec020098728679860871fa2f8e18b2c9aa64d6f71c49729a6f23019104bd8e4e6b82a3c39737ffab997b7611159df8625f8ebbe91d988bb49739c27721c51d6e2bddef5c2499b9836a9802a2ff00b388dbcf842d6b1ee3b598bf7650da22cf9409b5590ee5065a63a037b4fbfee5feede2bceb7a1f88ed31a626466c3f7d77d39bb8f9b70771fbcd96b39bbe94bbfe75685de0d1b9919998dd236d6dc9fcd28384efdfb45eefd2941bafa7eae54298430c283e0ef3dcd357b21acc57185ac0aa5c42d64e9ab9f36a105a4fb169059f37c23b97b2399ae80cc50caa290a8c46c18753d0a15d508000001054315003020100807c482c158288c3361f70114800c76a04c6c58990ac44990e3304c21630c2180000000010364668666db004c017a926d11c4130752b15a602131317c6e6a4bdd6fedbdd5d5e16cb797238b705e4ec4bf2adcdc0e0eae7e5e7490ca1e4c3dc2fda0b0a28590e934039af11f15557dab4adc8b9b003345e28d8e3af03ee2d02dde013db3a84b0ec17709c022b6e773912f663de7ccd62ecf8a48e21c907ee84d69d7f3263bf103d40285a56643ce28ea2b488998947048e7732ea6a833c311084b4ee013ebbac7a78e23838c13952dea58611784430c8aa0834c51a9f7fddd673d9a64cff4319bbca0022503f0f72e7c75ac58e032a7897f8c3f98e51519c54cc83f9de7ff6c55fb8badb0329ea7820032f9407be11de6d0c92af4868ff8273a2321aa14c5aed3630fae9421f1d729d7675c21500fead410d52de529413584a3ec0bef92257cfa60d8dd07cc3d0f48bddd7cb5e47599d650d364f6177669d8d7639fb59de51bdb6cfcf47a5f3fa5daba38e9627ae8c75138f24f3d4cdabde6e8eb7b1f26802aa06224f90113b5faef365f48b2a1fddf47ad771bf959cf960c909f8a4c1a6afb0e267ea2893ae4df3ad5b43a0a2d3171b5e71576dbc23e791ee7ae884c7ab35bf2ee467cd9ed31a2212ad5aa6b57636bab883d53eac9d91dcf306907d3d6cd678c22549617c50af3be4b66a5c29cf15328250ad7037d3613a78a83f4de9796dd4b36b353302dbc1500c040e77ad9565601c31d0a071a449edc868e57293a8e8dd58ecf66706ac10c6394d424cada709f7582bd228b2f43e2264fa399b43c7d4f4aadd21e3f22ac07f53a923dd0e303ba9cea8b6817e7e73a14ca4a26c6a875850386e9c840969d532704dd7b0a91b57f7db8e458cb8917a983a4ba1a9090df24b5dee710432ffd46b8f5676148313e05710998a075840e4cdc84ab881ee440a11f1bf8f8dba67046c517f0c2d9a6d005e24c58ab6ba5e9ee505e00bf930079c159bd33a293e57e9ef2203be3a7a970ce0008565cb9ed2597a5b215fb090b898aad0020af86bb4252117072469bb2ea4c19867126264c4787b7192f5ef6f100b71f451d3d08a2f43dca4acbd29b2bd614d1319ebb84879c855d46050e2c1219af6f0a60ee564769d75a910dc0e19c343600091a02913df1994fc869c58af395ed4231381366d87717f9b5d94b6ea8667e3bbd64d2ee654e35be1746c384f638c1bf66b152007b68c2df56078912faac660bb42b38c45e0f2d93053b3453cf38c54b41577fad46014c2f2f3057256068158a832359c26d65d04231a166360ee69685a434657240cd7f0b8d7f0ad286c3aebc1ec117092b44e93104f087293944a306d0c0f7421a4674d503754b02dcfd4f087ebf71bb2088a23358655741758743d972f39223fff673f510d516a1835877b2b61992d6af8b6fb7ca2bd55655ab098d19cca6b154826e89791a7ed8e274fe48b72ccc05a397e22a4f812fee25252d323fc3ed277932ec1cca62c2c1a4459498495217297183712ef00474981891374f0815fa8121afa132c745352e9952bd50495d334fed7a63745e2fb0337c4742146594cd34a77cb18603aee20502a81521fbb294d2f959881695ade4b3f414f99620e2c9473ec8da7bde60177c80aacf30699188ad9538d01aeb277e58eaa68ad0444257803313284b861bc6445c5cca2fe8553c85edd080677c6cc114af3011736bec038dfdfddac6bf5d6fb9132473818a6e29aa5749a8cd9d5932614ee8df262b091c226c7413d1cac9dd047499af9a478e6595522c8b060051bbc47f83d7adee0297f9a0de60e0b1de2b9d514eef9439bba12ebb5e704674ffabedbf20b14e6362755e693e586a2ca23a7f575c44ecacf5cd223e172536de5929f1206d38824743f5ffc1c3d5c776600c436d9ac9554de13d3be8e5d76b250616fc39bc71ab038503cfe5cf2890a72f1657717b04f41a390f6b9d204174762b546bf76a86c22b180deccc53e9a06bab72b474738b97685559b0ba0f2ef220f7a62ad65899a15fb0088cf0b8700674d40fe9b90298a0f07280a5f4aa2197b2c8ea1d33cfad4b1d3149f04740a9700aecb4298f5cb9fd80909fa2d78651d76bc083f97034d553d160c483f4b764b91500be3ce82b55265aeb202964cc3d6c6db02665c8d7164a2e78359a86dabcfce052448001b226979e0f19cd0c0eba44a73dce23ad60dff14ca36595dc08be939d67e90447beda2bd67098c4cd67e79a0e3c7080016800f14d38282d43e0c52587ebdc49cf86c3418efa90828df29b5be027de6949b63e3e7861465281c913539106a84b4d1ad2ab1168620a61693b38728dd6d8e0ca658a503b07054b1505f80ef73233528db049b149b8144336f2d8ff7a4c1b4739cdaef3eb0e4fa0005373620e21c209a49666f8bd516aa59f99f1369d8afe861212420ee38976b7c02af828851c3b07f15bb958a2ea513b0363785950fec0f4d2fa11de5726875cd9667b462dca1259f9938b18a081001852a1bab7266eeb1dcafe876220cdb089597606e299e28b2207151a2a46103f26727379f8c3e04ee65e4c03fda202d1432f0433ac62bba56b43c8c204bcb59cf38d94bd5d06bea452472cce743b002415c7e307453c671d5abca31f16b5f49c7b2276718bb969d5c8ea3d9652097fc53a48e56515ddca49ca0859e6de18fa96d5809896d7801f276336ccceca9406e949a83881a0522a6553b670982b629fe092ed4a78020bba267f3d1976d8332a6a34a711c618e9150d8e2503ff4793104245c6d83a694b7fa8cdd3677eb3f875bd93af00071c25e5d199c1d063a0c959a802b07eeca10ea1b9ade305f6662729faca631c883054a90cd28d88ede198bee83f7b094213015848894f395a8d77245faab9a6bb64b2944f92e5e711647adebbcc3188324480918b0a894f5fea43ee3cdbbd2edff521edb4a76c1c5ed2134c2e9d0891eb8e130d4a26729c2de5cf78c6bf9e3ebd8dd7b9bde8d333e772c264fd67a1ea8c044a80e021b2935af19f187b1a742b2ede3a46cd291e637fb75afc134c29e601b46feee37d71805d1c1f114a185ecc516ce52c15a6092294938354562558024350e93974693353788d82b114c9a40ffc49c54bd8d9f3be76f88fd22d86d3620f2468bdc4c1acfdecf0c5d7cd55358850d763c71b5f69e6f1041b1f93719e51e63b377fb80d6903491c0cb1c15e533578c39c8308a4e563c4ce89083fcac71ebeb16f6425f7dcc13d46a6aa425aaf0166eb0cb7ceabb482003ab5f5c7fdddf2cc2245b8882250ef40b554969da5eef77b35d06a72d7dc3d7bbe30fe0d21413979bfb24d6ab82eb578823c5cd35dcd6089fbfd20c71572d08b7e18f25b7fce1799ed72719e100e6c7efeff3a84edfbaa8782cfb2e7323d723aff626c518a1b57e08d2a06a72ac6c023be3473bcbd352a39e5329403ab710e917f84f2a6902530e002fac490be22e9c306873bc1a27d836d3685c8e6aab6acb39278789aae14c8cfc55232b00637c97c1b7be935af4eea83c318e7c24ffd5d0c5aad86943d393728fbe8c950320609110298e5e824bb697d49a8ecd67f2514f0644a9c83e619a8b0be15e01f0d2225386aafcaa6107bc8bd18a4c50248d1d93fd9803ee4ff6d83d8301be4b075e06c147dfc0397494e036e578be8f87e2560063978edc0bd0d10d4c45706e5d23388d3e34509b34bca40b3fa5b46402be816b8f51e0b08c94b3a903c6c8e6003e84cc41f43013f4cf1ee8cdf1d0fbc02d4035e3c23b207a4c05e1e384a719cf86328acf0a20f62cd001d94ca07ac1347884f6d969b0a033b3a54dc6f34f75871764f0066385ce2f876fab885800cf7cb5322ea5600341c142ba9080319bc4e5f6b29f789ebc4ce8b5b430a90e13c94c8c6a1b3ed14057bf9089475e539cb2b3aa925b7b8c341140a4c5b0ed350335a51db5d1262d59f9e928a8be51edafb581bc3e19a9ccb3f9ef0ed1cc78f2f5a6b4f2b385e0de044b106d1168e37190e2f6fddf75ed2cf8df1ad069195e9fdd41b7bbb40075185e892bc5a9f8e184a0275a14700de0acadf243116d8bdd5939a6855ed12f4ac4bfffdc71846e14159ff336a882f1e0ce1eb09135c2286191ab442e72f1350cca2d46b25df49d1c0f0d89759378cef2d2c99fba722b93c91a1f668a875de987fdfc2b6c0191091d30627281f3ab6c5d4def14eb50d49bd9e7ef7c5a45166810f3f166557195326008ce61d20bdafd271149cdd6d57ccb60cd6d7d533904c31e77d093f5ef31775fccb9945cebcce8b30dda364bdbe53c8befa3890365ced336991e817f8cc0d126a8aa869b936325f674313b7d4d1be411e13587dbcd3a54c23f09db4a6aac38fc5c9f022e67e7a6b49854a5f9c8bee1d178f6a9b2343d4edec70f245b98fbd30492c6d73054b119ae38ce26cf797806331f9c66ae6535117457e795ffba70ba4d78453bd7ea1bf9e7c02edd7fe6d170f94f77fcaee72d58c6ca1cdbba3e1938f745a402aee7c3be888ac871d91d90a324e9ff02087f53a34306065f8268792141b40ea7ff6c704080c8d444ba7ea4e00a1d908477902220e53980b2b5f1cb91849e63de2040e98f9c0f575146f0c5831c0fc5810c58d1c72034e2f975fb3c2f38ad01354c451cc46349e2157f80b1556533f441b2bb4777a00f5a8996924786f983d9d4b86a55c31a759080523907512a08071c645543093a21c1620d7fb0f0b2a1dd32db76e030cda55df62f92d40435890c7d95b4580baea0a73fd271d29bc9fe1e2c202fa0bbfdb29a3d643075108e5a7ca8fbd83968d7381b71e81aefc06852d3eb0dc777649974b6cce1fcdf1c87596f07f53f1cfa059ad22244b50594373331c4e16edb347d216f91212ea80a85213b5453adfde0b3c7c558e397416900e1e34954a69c1457db9fd841bf9b423f90f51c535d09eefb488938220897b6ef09b53b24ac6371a90d6666a9bf554ca7b104a354a6d92d40cf78175ef369df4f59b55711ad8da75cf388bf3a18332070d06f13009d016748f0f10de5aa10cb5bf3a28b897712995d9d24c4bcf8f8e1e8b79843aba9e5b35856194ccbf5bb1c2e1df20642b26b10f86daa5042493564ba741ed55d2910f58014cd33f7e39536c6f30f53c991503eb4e6e6656c05d25bd63dbcc112fc01e37db561b2d48136bf69ce350888513588dd10bc66e1f350f05082ad1b9bfccb57ea44fe0abb73932533d10b12e868e1353895114e25a5589c9d21ffa4547d62aa41053cce8b520af91188c1f86c11ddd952c6b4950d3d4366301177d2874a7be89032363151670d5e32be5d7f0c216dcd4b945793d579db606315aa73e8a2b40e888407677930f5cbaf20620f6db8bd23dbae14b5395922be99cd78d3c165772fa97dd79132b12fe97fd3c122b13be94edbc186a25543871cc3dc04d326168ae1eabe77dd6bfd5aeb429c189fb7267e0963a8876ad3967c42a99d850f4012a13a9b3767fcd8cff02bafa02ac5fbebcd17fde948abd72714d14d417271103577f5e17a92919ed54d847697fa429cbf75bf362746054cc11bdb12c99e0e30c9d642c165e1c4c7cd50fabe53349a76cd801d4b47a7b4e880b4135fde32db5a2af80f9275b688828d31218924a1130899d3df1d1a019cc617be01f138384e3f53077f9b2dd349236550228e196bb32f09fa2bb62971c86108a8f03d88e6218a32045de85c295d2eca570ba5889c24e58e0069f61fe7c71df9cb84f20cfe4490f55c23f7b9b5be86b14bb4e9fead0d7fd6aa5913f299bd0aff7d1162232222100156d4bef93eb971fce8da9c86e85403018ca543470ce1070c0f1903a4b4f81e3a4832eec2ca35e24d0af5ebe2c44445e67c9fb3376e57be0b019bbf932c7d1f807a98cccc7ea40799157291ad3adf5614fb3241fcbf774df85266827242395e5c6c7db16e43fe3ed1f0c8f45c4c9638a05dac9fc592f75b0b6616967b08e7ae212f5ed86e30eb165fe46106da8e89ca9b6735e87c0b56d0ddf3472360e6979f4efdad4c825a83e42b51cc102af56ae669461bbbe8b8ce62b7025120a5cb89ec00968da1ee3b33795bfd3b62331675de40d647969f70c20be0cb8ef85a6e395b46da489e2925a0048a90931c944425fba9f5ba5b0553cdae47c347cb6ff9f70c84a139a250b20dd13896fa3ab0acfa4a63620dce2038ef53b7588789e437af9334ce62318f71bdb3e5320cf1fdaeb1b4409209a94f5e845e6e2466e787a43653a45e699da00b24270d41c9443053a7ece2c445e806b734a508a9ef9830773401f87466925e23d4c8f8a426206b12b79e9d168a9feb96ca9eb2008cd67151eb7829a300a35eca2e457ebe5310f0099df39f5af407a1c728e5de5fb5d8aa1e39e1017225f1fb354f3283539a7637bb33f1b86242f26ba14f466dc3e958bc5eba056b83862d7320ea8b8f8275d3ffda1c2f393e63f51cab4403248b429984f3ce9242fb063ba9abe9a537481762ad615d85a916a7424bcf2144050bbec91a74010a490a0efc936508ecd3879409fa895e1c9a74ac04f92c5880622282594d5dbe3785adcb9d5aae6ccd484b05968b09be03f194270b6ec1ad91383150c3b32012d671f45eeee6cf5159cdd75b31566988ea1dcf71ff5899a463e18a491101d0583d9e0c8cf25ccba4a870c3986e40f3c5a6a9db7a4e0bdb606afe3b54278040bf64eff4df3bcffddb87c8e96913529a90928db9500f2078ef239c0784d8b098a4c107e512755aad461fa556f9479f6858ec1e5c3b543fb2ed84e7d3166f267897585f49a82d1ceee6a0098e1b641bd41696ec4769d54b20ec66946e3422b4cc84164ed17d80c2b1eff88c2e6c9119c5e21c79bf44d10a10c41f087b334b1cd184b98803b46118046dd78e73249215983105474b3dc4808ae4664ea041ccefea314ba107e256605c3251cf006943dbf3cd83d74ab670d73f682f3d06d29258d1191f8e27c904689c3e03598c205d2cf4e629c6833cb235d38b69c6638e43a5b23369ddbc179d99257127c096e2d0e40befd408fcc9e3ef4f59b1d0d8d304c91bd646c83645cb80638cfb133dbcd30457ca4a20794e0e760f0a90742324bd972d91d3d010920d4ed481f89fa033cee09f2cca7b121794e9683662c25910e517ce90488731969ce3039a41d5b3da47e89b8c3fbb675e263befda9874caf3e889128cedfa97f869f6520c36c533cb88928084452ecb5c9d6d96ea5a19539757ab7b6e4d6a2625a7fb33eeccee8ef73352984b58df8de3097082b3b3d6c17ad3a880928b12d579e9dd84b6ca7a3698784d2b97bcac69df9af3f10d9c0313658530f734f944befc9c36270f3118dac9d5c58345120bef2f4555e63ccee7cc2649b5009a4545197a357ae0d676b5866a17cb9deb94ddafa9da32ffc806d85a23478a39744bdf654d5d1f22bbc89e39cb1782ad4b7228d34dc3ea823f2f0c8348f7e69d1498071936121ef6b5c8fa5c65d36aa31800cb83cbdb90f361bff8f2c2f77723d5da47eadb10c94e495277f6b1ba6e7543a89b1eb92cce5cf205e16cab4d2ca73a2ca06b9f57d3f01ba3148373f2ac061d7436e536fbdd67760f8543c511a5a20c87a920e491205c80f51b9828bcc11b404b12f3a1c2ab6dfb7bdcb12e2afd9f7fc7bc34c1b4c6a4e6a2a0f0c214921e296273ab2bc0fab0c1f2a927b543599d9fc09f2d5eb966df51d3de3daf8db64c652e7323d8256e801f42b835347751c98bf84c0b5081d06d49756c5243a5a0f70c9b78d7e987a5e4953646c66198d82a77b5c056ab3fc5c8bfe0f36560fc09415c87ec2aaa6367d44bb15e0d1729a0eef1849e498a34d633a48ca79d0aad828ced8dc1272a4a26bb595166bee70787aa1683a8596f947d6ce811719c964f5a1d7d3d501879e606c64de0027af56ed5b280422ce1b7c3caba3606ff7da694858a767c166d41a090d808c6c4403ca4e684269decf29fd835f4ef1a2b3fb68f7392c9e34463d458b26ae4a499a5305ae680245dd638d9463b440f84cf8027d0d21642ceeb8a5ea662c406e2a1abd725f9fd7d666270b266b2f52babd7f71502a331f4541d7f146f5ecb63132261e912fc5d8a6c81054d8d1871ffe0fe9e42c0c749adbc8ecd0c852d749731bfdd2d454508bf17f54eeb41491e70320ba5c5ecc46270f469e39c1e6aef61eaa31ad082e570d9cc926c154bca27e101c8ed568ef07901fec56adb450252acbd1e42b81131e17698e88ae4f917b490f70326df19f170ea0381f4784b939961a67f7f7e88eb995b6651912d08857d964ea12f3c2ef5be033ccb2c4bf219225f7ee86ba128891ed51608996a512e45f6e8ed51d05294d5cb6e4b425620e3a76a90e550a086d762f8bd2f625682e777843c857b7f0328f800d320a24b7d3cd3f5cb24e0be57398484e445ab736c8e77433f5a6708226e4571903d1577139fb30ae5be3c717a1803a97f0455f1c0560eda7586e64fbf1522e15b4914ccb7fa9a8292894a9dba89fc72cbd88e19c0e39e425773d523f9062f9299ac7ab0f8ad6fe80805ceeebf15fd98a724f856ff0e7c0df547dd6b1c254f08adea0770665237d623af3a4bcacb08ff41756cca398cf7099cd512a7911db71788c3bcd1065bab756215bf19b16cf6d12d1de55a041a82d85b41afbd71f984f6cde1251b3683df9abc3a0f8bb925e6779b1190779c0568578c7f42cf1ecd889137369df11693abb9f8479c98645a7156db65b8594ed4d5aacbaf5a7ce113b97818e89e3b07dc2e3b89a25406eac687c7ac6766b9fca2bb191bd8e94887d72f0f2401b0834d01439d535828ea8562f98158e8ab14d8ec7e8c46f149d311c8077ccb7ecfbb994f797bf6158c6aa6dcb89e79b15bc0f102b18ab8424f56517615047594fcdb0740d50e18c398091a71f0cfab3a82d2e927554d75cc6e8e1c659543cca5d97908c08f517da2ea209497db7ec357e4ce51fd4c5896bcb1a1e3b6c4e1c053033aad76f8839541d9dc63cf2c4488c59f519f74745e5c7ac9b4452bbda09d5d8feb2d9fdb47461e5418086dc782e249d6f2a0678ebc05760d85dda602299e861afaf85063503a1447ffaa7d95be2ab3ccdc4510be89694ec662826f9cbc0740824d01c172188cface96c3d3539ff1f68e23145e00d53419f28fe041d15f76439bd0189f905b74b13179eceaa38cd88ae9af053d3619408677580e76bbc1bdf3bc7f50d64b7986bbbb1693b06ed7ca912b26d7767ef29e7e6640eb4aea4dc52576b6ac35da3601e7d91a0802428c1dff964cfd7ec4dea86bb76246c68e4ebe88cb621bc276c222034f47ea94c9f034abda214e223f2b14761d360b9bfc2d601ba2b79afc2b8a4a5bc4a38ca3b43a7f2bd65c5acec466491d7a6e48cd4bc968c118933aef80cf91a494df6a63c18f9391b182795c1e63486699943d26208898cbcfa3319b3839dad941d74e65b486f6ca87e46340b8068a994a6eafc66ff4038eff76c455523790438fc9cd904dc2a98d4423c375ee3ca35f0cdad3061af257fad6e753822e574ff35d70e315d9c9c1907dedcb1a582afb03d7c047b610f286a53972e5491904c096534645375343a0389d1c269e581dd1b9e62e2f4b36ef6c11d2be23b2262687252e0a02cb82ced1b483e3f729de7a6945b82a4db702bd8c865bf32c79f62cb40f67b2ec79e56904c02a7701320dda59794f164560b9a88dd8cebb55ec930e83b918d0886450c2d713ca464aba4495feeca9fa12b06967512dfabaa03549e0d42acfa4b36e5e12a888552a0bdad5de48feafe3790dc898745b67e88239983892881b711d5a0ae2a35c3b7748087d670e187c0c3b4de8691d82560a11d3ea96a4512352fafccf99e4df9885efd888719b8dfcd3111cc41dad3328eac01e1d9510457e6e1c8354e57c70f68024b30e4201be2aae72c3a7cc5f589bf80f269e32555db36e2d5170e10559f33a4779b7e67ea4f3dcb5a5b743921878d6118324ea090295b22d151d9ea4215dc55eeb0855186b7667c8b9326c842b6986a88d868f89052d84666386810829456e3a4b2e54e79c17ac5e1e3a46eef7748c97bcd44a224f6b2ff5cc9734ebb59f18f03ed87eb7942dfdc78570f9c6d030033c194e35ca2360614d624348999e252882db7744d8e89a98243838249d8fe00f31359cb2abf863c9eae258629744c1ee4ecf991ac82f8525d702ecb8d848558dc87f9f1bb85e2250d72f7fd72cfe5d6da74ba9aac906eb921b2c539158db0a0fa25805421cf4d99bdca1f4908f694dfe0253a76e94b0ebbea1a6bc5e44e2afb5e13c6566e19781d91ac399bc8ad63d2dd7798e6103a13c8742660698206e5547eebabc9b4dce8abd711be7b70f1b84611f984de9532aa0a9a6c4c1946f47c03da52ab633ceefcf07647f5afd6302b58bc5868aa3dfde13029b5dbcec2afdf06c29331da27032b7fdeaa106ae2887c70bd1b4290118e03f3efcc413e1453d5df5c1b84107b4d72038089e5794b5221f621b578b0b8e7f01601ac6ca28f9b5a0fee3c6e1304a0433bccb8ef1efe16609cbf93d8bd40a488e47defdc8eaba21a35ee0bc5a899d9d912a8345d27277b418b6dcf8a1741bac48643295c6391598ad564c59119a4b1f80c3d467b2613cb7e86d784999fc0da153ffbb4831f17ef4162baa108c144d5a01b2aabc67aec461e5b5cfdc56fec1b99f43ba332aa8fbf8fafb4eb0e3898d675409863dceac1d5f33bc1a83be68e6c192d634095c99e8606c643c620e391d7c196e1761b6be9ccdb4e47215ba8239254f65d298bce19d43306fa3d272f185bb6e364cabb81ed3be7b64fef67ef2a0ea9758e234c4e81ef7aea7c6fee2a30f6f1816b097c715f0db730c56bdf15f97caad7982c78190c2552137613b9432f38507993cf7d3a5bc0a9c41219222beaf63dd5f7da41cf6f0bc73b4757273e8b211972df4aa4c5203632b7e69b695d2faf7982d7f6f8a5845741c1c6ca70785f894c6e8a949897b1b1a762513881b50c2678fcf15e0beb343c3495627adffd1eba117babe4c7d63825510684f9d79c635c336ac42f76e936ba6263f32283fc2fbf0f504544d92d7b1b6a427d19f519478bf2c401a874bb7961b07c75f928d5aaf75476404d5da4165c3cd0c9be231399b44cd98f88a7d2ab508a4666df9a0a53ec189603a7f03da9b142194317ffeebca9e906aa393c2d60c1cdab03e964d5eace908905a7401d42ecc7c45ff3b2679e852226a5d7b6ee28b194f1a14c2cb893a4afdf44b500aaea1ddfffe18be797da81152306a74d94653428393cb9ce5934e492c7c75ea67435c358005d3ca93bface5466aff3c644504912da5c0772d03699ec4b557f1e0ad508caa74a6b83c940fb7af867d6a5ec86b7a4a86c0f4a22a389b931d704374dd6097cb4019ef28c40b10f2bca6067ab4def0450392794d6a8009dd75dc430ddf88e73720cf200d45f94a16faa6fbf9cdaec792111d09e85a65ed648eccf6592c33b421117e975764cd777704c37ee3caab3059ed1832bbd74d809d100c7e34eba8435831203b5c67373c19c60e33d05cb31942aa3aa69fff807a0d84c628a8c563f5a8d063ea688b896e71e6d2167244f37f2f90242b73833a227499a2d6d48bbc24d422474e4ce59711b708f66ac7248a793ed7a6a8d0e066f2be894d3155bb842108789ce7c6f4de5804dda8636c8ee73e9dddadea52a638403fa188f602b8b49a4020c442822548ec40d1f24dce0084013843132483245c8ea1b8e1abea3acfdb4f596a230e0ba9bd61b106282c007c4121c1433d5c599540d7043a437dc23807ce743a6a10e53e2416a1d49dc3c12b46e78c0356a84925f2f0a1f31a67bbb8cbfe6383c24472c0a95ca959ff3452f400792d954fd963ef994a8e5edf3a19e6c725cc5671ed411462befc9df16d61360a847791b60b0f9c5fa631fa14e83145d048aa3651ea9fdd463480204837ba53afc8bb628eb5bfa084021a12e6272087560d860f68c2ba3cc560644a407f25ee5cec10f7a4b96110e5a6d9b5d209995113a6a207f74b71ae442e413b8553372aeb9bc2d2212d2bd430b6cd61fcdb6f6af1086e3c5d9740524d216f0e772dd8eec979c1908b60d8d89319436b3871bf4741e231972ebe415ec1b0c06a916f8a1ac5c12169013bbc211afb833c18d72da16e8024880f96814252323439fcf10c749c0bd8f0f6cddfc8c95c554ff5889e33d480a54348f342c9f3bd646af3b94fb4c6a940be80a6b6ed8e53c11c3fe47cfa266b4d94e4a593614224936cea2b44dd5c08fd856db953e76a160392063b3dd7f53ddc9a92dcf4cd524bbb76ebc33a1b49c915532e630f949779d6b9c64c2f24e3c4f391d144f0f862c6e019e1bd72c843913631d022c3017f24c3dd9590ce7b3f7bc80ce1a9542efa4177493aeca3dd4baaa715c93f9db44ca5485c0b4945f461610d05d71ed998cde88ec42c4acb29529a90147a94d29450cd13ac8118db6038cf787bd1f40f50930d0268b0b6e7b48eb64719c9f6e426e2c5181891a6dc9fee4f2695a5a1813d17b713f32844b881355a24cc6793b018c5af6761540c175cbf0d514645283c6084de7055297fde1d3c0714b945f310b5d807e8b4ad3c901b4c19aa611438653a13b213152311129cbbae62b5e475f6856411b1a25cd9d20490bcc4c3da902e7fb163c0888a03d20eef58729c1d83091d2fc474ffcb8cdc32a85e32c22b20f7cd4496f109957b86d90aa6e3f5c104542223d17c24ece85ba36737dfdcf174342c1c857f5d2b69049abc20e1ca2f1a0529b119e7c5dab39fb55af2fdc41d1cc675707bb11870554051fd91b6119519988da4974a9b937109f0282ce2ecb8b8d12e8f90035bd4a428f511a85a3328baccc04a4dd78d13f27ac271f333786dbb83c4a2e480a5ae31d2141ca34905f7693eafbd601055642fbdbf3f8cf69895760db1ddf6e200571e38b0b5e8dce929eef322787033fa7b599a7fcf40c174d33c06042b141198406b88252188916cda6998573daad66dea074f9fc3443c659cad55d1f06323220462a1cc493b214a59cefdabb40e4bb7bbafa53636b82e6323d5956463f5d0c780dd19c86b1753435bf5deac40857f457ba0d713701596919aa32b5273bff40aaa89a72a9f192f588c1ae36e7f801ac3328bfaacd29f7c398a6eb9bef6a2b90dc6df9d51ed25b64d2cce305966a5027f1408c50538c6eab5329f0ee810a2ca86364ae694f01d1e950b4a340c33deda96b9d655c8c2ea8104a2a80c49d464a0d4c77231d0bdc4f875da6a64b3d6b1e92ce5de23d7247e55cbc18cfd73d0ce830d06b0cf8a851bb7dc4cc1a2124b79dcf9fa261c03fc41990de3964b96fad16452c29e3c86b29e8459f3acb1b4c0e4441f588e5470fada387439860661c977fa964ba9373ec8173f98f0eaa34e86f40d90574a009320caf809218de809f810232bd73de46e9b17b8670527629a49deaf5a70859ab525c7a99fa53880f45d5e0be5c8011cb001d6012e0de61c110d3462cb1955c0c906517f40c94cefa5518b4ee9b0c39dfb0ba9217f0b3882611644dbb1fb668fb5cbe7b1a97243da5c471f05ef44d1f17bc00c821391caa382f8ba839d3adb1042c69226425300eaa06236d29a2ffa5b314a995a747839279a2508f22a085fa6de4c9fe55e10f4a2e0af2b84ad6a39aa3d9e18bd4891c1ad123135b07a568ec84171f366875cfca3e2a76a48dc5c4953837bfb583eba42984cbe7108ca4c615701d6b8686732a2ed93188b4c78ae5e52af89c517a0ca58f2dad9551169917c4ecc6ba1237ec62065f1a11be2bf16963046f7bc7721f0ac1c6a3bf95c7e33cc7a3ed8c9a91fc54c957cf29333336e9bc5541f3fa4ecab2806ae5a9f2411d7a1f29acce32b80382606bc7b83aa9f2e114d162fa62b997cc150c938453b027872da5824d66c5d399c86b0576b2d2634740a8c83a48255e6307e2ee049d6621f98f809a5bbd3c5897106fa89236ad9b0909a07fc27592fdac3d437ba141d7694c60524ff51469d5b30ed45331dcc791a49cc8fb48590e3e64c88ac1d008586df68bba7eb5bfb23d4428ace187ffc9096b2ad89358e2d051cfbac4a04f9c1d31088dd4004d816601a0de651f85bd6c04d5947229d147707eefc0a6a5d7464497425696929a7aa509b0098884d45f6032c25cec2c8f69469f9cfc1d1206e39caa91d6409fa82a66cdec0de0745c83560a8db3b14c81ed05be2532b965b5b39a9ed917da9682b5563a79261f199425fdf392954c00bae73dea455576ceb5f0ea0ff9ee38e6617a11333e20617111bbc4b428d5373681362f5c7195a241a5f100147544ca3c5ea5e3bb0477e52a82c708f9cfc6d7ff8cff1c634254091b935c8617a80e28e686dfdaa7c0563aec8e8b1ba9c2c43820ef303372c745a19203fe44dbde40de7caf7fb598a332da1604dd35e9d75e7dbe9994f2d5296f6e3ed1505b5f21a79c6ded74461dfcd8a14c7076932d2aacac939436395a6f70627eef555c2fc87ed38135fea8beb35670a203e6a187354a7fe69fdc7f9a96bf4c49628fa0e15a9a1b0b0310f90a49b9faf3022b9c5d9c1dbdf88954a2161fbbb1238c51f765ddb0eba0d8cc56ae8ba26689f17a28f301acde996de70161bfd4ccc64a1f8b63adf66add88d6108822dcdab373eed0eccbe4d0082eb8d2e9cd9ba9d7a9fbfd56b69795257dc018965d1ab6ab03071dec5e1bbfb55500529ae3f87fe237e9dd2831c98aa4c595460437db5165066d6523e3375d8fca4ab9159e1eb003b665e6808ef863223cb168f706209c85ef362bef9000388bba851d48eaf6fb2bf5d7b2591214df8f7a0a10158c7925fb783df4a598d5ee7d146ffe9bd284aa433177776e0b759f005bc92a7b63771955ef5ab91363af29efbb7cbf3c642c12937b464e0898c1a64971d60aa17e714bd048094047c8d6ff4d939502ccfa4c51e875cc59ec35096d944d4233d3721b8318f96f14a72ff0612faa2d0c16e310b38e9abaffdb8fcdb17e75f28280f58ff92ea14e521e2f9d4e960a8617e38bd075a2c2899f3f088b255942b7a6190f602fa39333eedc702bb2403f930b21bcadd9b4f113248dddb63989634091df8852b2af73674c018819d333602617a34c33dcae5a28127e8881856f2606cc1fc3931e5cd83ca29b023226876736a5e065dda5ed12f7e7fc0cb2384414000c8699ff65f20781c65c44425318999c9a5a51662182e9bd74685e0fb28465059e0a1a8edf90c94f7bc42a1296a765bb0a4ec7e003ca59fc65a7f564615f920090e8aea3ed76cfca75994ab71505ad9b8e2891fbba6728c5c7618165941936f85ef0ca2989c5c632ff2d6154b4d142c3dc5241e7ebcf7fbe9567e008a8c0cd2bc99d1460d3985e853d31524c178df6faf452f7df013d87fc63b62f268af988c5db1e887141a081c5c66227c683d324acbc1809f221dc33aff29c8ccf94b6a20012378cc5e640260c4ad49ee6e8a0c5e152715a8148f790d8c5a10006eb8927257aa6c08d0b550524e0fd530a59cff0c37ee8054611cf6330a6381ac3d67a30943376e6c1eebf69b783d5636713bb02ce3975a63c253ee26669e139e9b76ef6765ebdebf1c84b32bbe6fd990f0318804993057d2a4ff62ba1f80ea0e1a4754b7182116f636d137b6de62378cbcece344f4442a2de3bb008229a9759c4ae789495a168df810d45ce598eae31d4012070779e9ad4707d3e283eb7678aa2b0e6d65500dfe197bb38bce87dd6e31c02d20e8952dfc92be73eb296b9dc6e5187ee617c337702660719f1fb7ecd693fba16271e854f8c216963baed9f395bea82ba2d47bf7b2c1cef3a8723a17569f79069f2dd9dcad80c436acdbcb8151238bdd5e1fa5274fb8c544ac14235c3d92447bc3937b81ca2943bef413eb152c08271646f3aeb82063e8edc3b3adf486af3edb670b0dfe8586d78a31c5d096a622adb483d6fb08f4d9233e8eab296d1048873ab60ba29368c82a9c461263132e260c1d99c6dcd95093dfe41f04bdd6966351dd7cb90c1d603bbc31a222d1de79bdd9012d2c64cb2d4625a565b4e42bc2f998890fda1cb60de84a774bf1b0644900e995fb96502c72f45d1a03cd732bbb3bb5722db6473168c30e4890d6c33e2a6895f5c07f9a1ead521b321f120518015c9da8962afff4b15cd4181e9af542997ac2609cdf1792b76ee2454b1abeb4941b409726a64535f828a450b221794aff72c5c18f593d5edad918c23c8717fcdd71704eee6f7e4cb47c7b5960af504258b0251ccec28a28895c4f974578cf713e288fcc3b9a8b004f38d5eabb16e11525218b784fe7ba96697896e1ef05a37b932b7105bda4016f18b43ff9bfa53893d37121ad7e4636a573cd1b8298e18dc8c50ed464f1358b9131fc0ab44edd2bcbece56435bbdb05f364375f03dc4109219ce78070babfc15e78a01f2d362235946868969f64dc5d0dd98a3c4694761ab86dbe0bf268fc23e520c8aa4a089f283387eabca356549b78d0ec89b0265bd8571adc27ca910e13d0bd2fcb465dc863e3ee892de4429e35bd6cebfcc25a8c0bf965b0af9bfa88d1d14b208990cc563ecdc1ad977dde2101ccaacb5767718171fe671a5692b848e0d0b5f459347301e4807d467606387f63b301715d18d959bc0132b0c580603daf1099e06f93a4bdeade87e5261e3345487b8d08765dcc7740ebd3880c377fc512552c930b5bb26015c7b3e59aa357385cde37120171ee7f7d427600139951114933222f29254d59ab03252e6e441a0133afe8c2416e705dcc59280f5470e22d72c695f803f52d789327867df1172e90509ad4a49095b3764a6f9f22051c32691cb0c4f9949df8f934bb67b63dac128f1cc1a098c376f1546aef74f30f66a7f1cc315acab3c2dcc74567f9362079f1ba2a4f3873a32b79e38abdc598a8cb2c3b83b01c73040ae125201205ccd972ce49421c6d97d73f13b0b2663c9c2b49660a5babbc2c786f36feaf8353b6d4a772ca1d2edc16a390be220f979b6503e3eb97fdbf58f231cb9003937ee18d67def3c8516b6b236a9b2f3f73756cb3c9eca592c064faea9369f27a5f6f37ca7393dc426caf85978cce3f4e240e160c77919573297a43230c206993f1632d4902490b16232ed840deef7a7b244fa6f8c1d20be77430cad060876a40d0b512c7d6c4f872d0398699504af026c5630d9da5e4b465c51aa42d27127e9a372259822948a1f8ba000ef0f414d83ecf287d6b6080bb4023cc53c83af0873239f25c16d66c61cc0d57a40f03897a00cf664434a3af9f9b686afb162907704438b591046bc8b58fdd0baea396cb7a67641cb41381f9687a3d2b26352e2df11fd7f156271fe34ecd6e28375064d8dd22ff8a49c9baf976df35e1be92eb7e8da67809b0f08713dfbfe0fc005189c00361a595659b768e40dc0729d29a5600be2dac5d1c5696c612d684e900c2228dc8f118ffec61fb389e0e7384cad7e6fb18ae059185bd66f9b550f931a03e136541c0e6fd00c26c7d355bf88b3e002ff5f37bbad73eb6fa078850f1c0bf3838651eca9e29818ce84d071df2f2e373fdc5249cc6a61b0b2203cb2503018bf584e8e95c3dd907f585de072dfe00bc1e62d053c68d60fb2ad97a42fc723105ce3a2c59a9e52b183edc2e18fe83a6ec1fb282f97b9ab3179ea730d3d618015b7930fc18638c2f0a3c4ec329486414d161bf055090eb28360f1ffcac4a20d20f11c74b35fd782e1a08bb07223a3d7a474360f5e7fb629873c2e08f95014a4f666937cf23d7a6dd24599b3b05c05f6b771ca16a4ac1d0e4726a0f4703e2a6dd7eaff350d078bdba3f616adec3dc111df654fcc9716873b3fab8990ff646617572eb93dfc67c742814138335bbc8c71e8e26b34dec2dc649a7a83ade8fe3fdca30d387b67964cd81d99f24842d660f6eebf6ac26716b3bb757f0d18e3245fa79d2bac8a5d17d6ba17d032ef9a3e605f5c0acb488187e1ca3726de24f657d90244a809fea7482b8e8c4398cb522c198140f6f0616fe335bfd19fac099f39fed9177b3360bfdef33891e374c078c246e0f899af60de0a9d9ab07225385f056b09a982953542fe9bd05d979c7e4ebc5d8da859b2eaf71262ac0e4f9239afd81ced68be0e52b35284fadb13f90d353b9383969dd34a211a8b1f22cc793084373206821c4073287fcebc3facac8cfec999641f8f810469105a310a73ce90b2535a4a0f7a5793f14d54004ed3de266dbb58c3c29cd7eb0447d7d01a742275332217227f40ef14636661851272e92c240247f67b61749a299722340e103442bdc42be055c08ce98b796afbc5a82e1150bfeba379d75c44ac7d9e176951c82fae8a482b5f3836c2047d9008ecab0695301e4ce20ccd15921f4e452b15eb53e5e25455c0081369ef0c15562b225424caa7d18c2c0bfcb28ca245fd5c86ff061965579ac1a01a888df2a425d7359bbab1e52b52905b17b1c699bef900a3306bd06b368ca15e7c5b9d0abf5c1e4ccf6a0b178366a5de706a723b9ac43f94a6bdb9ad40a30f5baf23f32b9ca64d827a88c3097d132cf3c7e7046f3567958e9ae462c5d0c2d3767f4528f8cb610acb28b2195315d887356297b0d13a982f5d6613426f2de094ce698f7b6c9ba0321cd813e8be095e800fe9afded06e162bd4177dad0466baaa9c18332a70b81ac2e69f61557a5c631e4e9c38fd2c7a82455116b21664170a9cea8806c6e8d5638a62e6c287fe1dcd97c113b58524f812bb65b34414b1ceeb1a33694cd781a6fa67855e60d53e230f74907e6db9978382123fc69aee6cb6630b527e53c1573a4b128dc3b718962acb148378bae6ba1185c952024203ee57cbcd3f962b61fe4573d05a33fc870ffd31e63a1e7e8e04ec2bda3634bc21de85460fb32474e6cd8e90c4e25c5beaa3eb4fd0d09f71cd13fd1b402cd92d1a6018f02fbf04542302e17607a36904849109773753aed2409e144ee991c09a15518446b0ee78dc4b0703a9077bf8f77e959baf07be9c58e4cf4b3518b27bdcc54facdfe787c3fb33ac5ce3b600a168c861708377180f8ffdecd4e025ca730c3eaa903869ff8a0e061aaadba996a481a6170ee6d86129d118959ebcc8971a3548d9c07a4c5163383993e173be7aa1d4b44f284e2ba73958df8fd22fad0be5e81f1080ba9f55785e789472484ddb05943dd95792acb6b6c55981fd5f87e3a4dfbfb1a87cd009d6d66d416b36988a9f9467f899366caf3c08dda1bf20ee79dd6456a22eb454a3970708e058203725ff901e5213e0b10dd2c75bc2fed86ffb33bb2f10a33de85bcefa819f12ec14c1ee8668c36d88052d150ae40b9c0e362dee63f26cde7dcb340e5e5f98637de3e0f9422300e1fca2b77cc38c073c0abb003e124b5ac4af0160d582658de0ecdb4375461923a279d59893c1b53fb08e725aa0cce559e441e9f6dcef0804d44ba81ca288691d11b5a1b9f39e66054e2b0da9032e7022376f4e8c5eb9aa3e849d4627cbb714af782181d0e281300fd2c80da4d57a9e7591e1046620971187b527f61b2a1ea54d4ee9c934b41803ff9f84b4b23fb8167e6afdc1b279bfd0266c0cef78acb5fee1ce13f62c5b600ee7c509e761eb635f25dfc0de14a3008596675a14fb7a2bd26bf8fa3c50a8252ef691077ccd4d10adc48f957a2ce70ceaab18828cf6e11b746b136f62bf7f1cc06f5d0f682035c4dec12fe20f5ee0820709dda6a4c6f1f0af3e333638b172be569b6fc2115891176c0c851bb10008bf0aeff12c20e3c2221c8a1a9efb9cd9a7fbad5bbdb718000222f47e5d913292fa83917d7401d1aa8689281711605b1e3b7cb38fb91c676a171a4ad42c02dc084a9a20ff81cc566e58e6f71b5a431db4a5c78860fba439b1d25ecca735db17e85a239e22be1405ad6120bb916bf9d7236e1a56235854fe1eb089f1675fc02f0963a4e5ec40997f5006d58178796d8fd2b4b833ea15485be3627be6c30bd0fa61e22c5abe2e2004da61c0b613ed135031d39924afbfafededf92b3516f28f86532c8e136e98a294279d344b27d0a9283a444e3e66d8e04d1714a49f593a439522cdbd4b430988dc05580110aec7b5fac42760e8dd2ff6292899c7a41de08fd8365666b2e33abfcc2a0004ca2bc10f861690a0563c60dc0c4159af5e1932b2672a84f7d3ee3feeca9f3ca8296ed04b515fb4e2c82b49989c4df38aff092422b96ef4237ccb2bf0957c3b5e93ff4b5f2847296594b23fbb87fd8f681472d3c346f313d03814963fcc237d7aea37eddfcbbe9efa0bf41465949e304c4a27fc26a693ea7ef2565e4a3d7a62209a514917dfa5d9c9ca08ce84f4af7264bd2b076e73e70afc13353c6245a5cdfb912e9d2884d8496c7f6787b2125f631b4b90090419ebdf746988c9612c598749b9d9ed52fe01acfe92051fe60048080b82d6748d0c6608c424563cd14a98d1c809559a476c5145a32aecd9937234716b42be075a706002db8dd75fea29a552e893689ebde4c01a2a12c2e9bedcfc8b6694d9c10dbcef35b9eda1d9d97d75f4a71386f08154f0e6dac171bab1f5e30e63cffb2f0424fd604d9cf676c980a54d881b8cf2a9068870e83c880ee5a00675266a7d547bdb1e8a11417306d08907c25e02a724dcca5850832a773202e5ac041e732d6ab01eafb4b7329782f5b5f68cf5607a6dfe32e64cf4f7bf119041174ee0c6fdc44a0cfd1430dbe97da11f439a6f584d15e34722faa7fa6e87dddbd08a279c32babc6c292e96c744df05d6300571727e9d664d2a9bb4502bbe6481074dd79259a018374a8225b3ade4b1c35d3f15d59167564fdb2506202088f6b1962cd12998bb82e6461396b8369520862000720b62b75835251a6241818efae61bed0c2c987df772022288816efe862628e5956213d816043dff88f4712da67437548599d8ec3ceb3dc1ba26d03cd31d71b9562f59c9a7e7d8d209103384d658206d983f1661ed213814ef5a9502d1a7b4f696fb764962b859d89e82a216c96b94088a73963c97a0102add0c5ce5f9ab3ab5c03ccb19c01ba28c31158bfbf4f7f90fc8f76c8da3795f03cd4a2ddb49e6738e6e65f4eeaf19bd3731e34c7a68c24e5a48c47daa135bc250b496cc8795ad494e614b15c06d1bb48905e70fdaacdf066e61c95b972188481aa943e45af90a89d34e0c8c4d2ab039159440a40924f087ec159061b663d2eeaa800d69c93b72f2a5cd4cb23a5ce403c24f8963e7cc2378240ac7cbc8183d56f52d63de183b80e5daaf7055edb14fb6e9ebebf29cc3c39ab3fa33188a254f4beb876893644cb9e11b28c49cd6159c91618f9dfd1b432f6d2d057bca22f731c04499b8c86ecf8936e09c4162144b82f2df57270ecdd577cb0a735317f6dd0413b68173f4bb7db773d1d9ecbb42c1e0d04a20a2fa8ee98e554709c1c5ad78f4b5795ad7d360b36b70897ee5ab4681d275393ef9c8cbc8b5d1f157280885ff00183fb286bb78df7b9ef551e25aaa50e423f94f507718792995e55100c0241580d62131ed0cc019a083cb40ff8508def866b560335a41891ba1a647cc500d1937afd17d51f357c421ccd6f23b21f94cb51572421dd6e959a77842d81d62ec3e401f66d36eb01ee2c2c16b95e2c96b1edfe93584a4ea03deb49b6a6e08a940f3b0b603e93a74106f25e8b7dc2f0690f1c7903f204e8098bd629d19893820769800280b8e58b8a21c9da7bd941bae8a173cd3226f511fc58b4f6b6a1014b262e10a90135df80ff981c4ce678b50ee6039be1b81b4b1c35b6dcf64b7a7f8649a0208a4e9c0d41673746469d5b5db3939e84d1601f554a02d487687dd364312c8f98a153e0b25c849d02631a4ef245adbc26f7ee225a824d9ba93d0330a32c9eec9c73417cc5a07e27e8bda06d6e4c830505fe6e67acf4aeb7bd5e3efb8f31d51a5c39c5671cb581204df0ac3570cdbfb5a119754bdfc62a8225f2c7798db5dd31d5d6a8e3032a34da3d1e5716ae9d059c35faa381852db7a683c068409c8edf9b984668fac2f079ff117a5b853bcf1c9b36ce52599ed84c8a3e8b888ef23b11584a83d707bb173703008a72f260ebb1bae69f8540873fb92eaeef55faf0b1cbb9209ed0a8d4f0e7d075bf7d4f57a886210e401182028ccf8bc3599800fdb56a94c47f40d75ef2216bc1848e83684a4735d70f980cdd7d496babec59fe381f53c24a942f36169a20212bdf883ec9b8a7b5beb95c6b20117f900c67d2d8437f62ca9482a68a9a1ea8ec880bfb3aed37afb573ad3c9b259354574f1a4fd654745e21e28c0c9fc26f9d9f0a38b8dbfd079f517a4815351efd96537be1a68cf0add5d638596ebdae729ba323a4b34aa59d11b0794a35980f5cc158838304e4a45603fb415bb0f2e9b0b7abb526d1e694c7ab10398d332e3e2c490f87ff9b4cf33365872f43f10d9e903f327b7065745562b1105e8931ecf2131923f1b857dc1b92a0c733d696430269cbe5c487a4219426e9516edc05abc96573144b51b6073a9e3345bf9e5b4acdd54b3e3d73804a2795234671fb8de12bb25cd40079ecc2a9d0b2505dc5536a0736e8d17392816eb090705d8c24cd47e0c154029533325e5e271b4959ea4b48b93a9e6133e0e85a36ab73046f8e2c7cdd8ab3018a9b47ababb269237e2633927f6403c422db9e9a67990294d24cb1ed8c9ec711e3f15cfa8caf14c8be59302c6b1497f4656ad76c04b431279927b9ca2c960b80cb038252cd70c115cc5564320312715c50b03da6dfb8a0cc49d3a5f88f4e95c652f3b399feaa56470b637598d9370c4bca8abed37712dc2e8ad4a215dffb3cceb47586d94bf6c14cf3358005d5fb0e0ad875e06809ae7aa02a8c385762200a640e823064e55ad39bdcb13c88a3a249c274013b2b62fa963da656bb2455316d8d8ed1e89068689a230bc6b9a507bc4e40b54e7a13dad93042cc31fcea3a2e584e1c9ccdd26cb4c18ba8fc7c4e1a3d07aaedce9956f0c740b21805ccaca5cf6dcaf57de3a0bb3a7f049d34b623b9c5210f69f0b2df56a559d4546898d01f1f70db35bfbb5eb6d3b20f32a3d48672f259fb92a9c00b699989190307021992f2c816edf2fd0053f30f01ae439caa9c90bba64b05772635ef2c172dd75c4ed823b3b53b27d63cf35895cd41b1b6d6dce54c566a78c52ab3400839548144b0496bcb6bf943f89d4a87fd47a0b60b08c90a10af9e1f880d43fda5d4c31515ba4792291171ff6a9f88706e0cc4be2402c50e0c2ac67ffff1445cf49990e392705175bd7e0a6fb62712b0ee49abb8b1b644c17e58950c360aa982b04cd3ac20c8acf68bd8d25d13d76785a28568d5258310a4f2115510bdae15ee32c55f13af6b17c76db8e47a579ba6f1ed274b430d94b663db941fd0aac1130be8452a0b3556ab4c0feb30ecfc2cc565fbb67e4d512670b73929ec4f576aa68b8a513e4ed71fd0d0a1c26fb46dc9199769d4955878ff217198f0af234878b249717a46260662e0998380a5b4696e9844ba879973d9f821b63330e1f97ef03074c127bd2d01a65fc3e3b2538c57ac2f80eba6e8661307076189d6ba6f885b734f71b37c8186ba96e02e71e9a3a122670eea685e77396b1df32390299d4b755a8470e3c32f83db440061e4650c12c3487f059cf7c47fcbbaeff5e34f5095d850057f6c6071c69b4f2307b9419d8dea05fa7005d8a14ee2bbf5072994fae433a0040eb23d9c182e4a9ae6af16d8b0aa297a6eea6d771e99954dea94560df7b8c7b68ff2a244b5b1f209830cd954957defae69c46bb6b3b5a0033c347d2a2223601630afabc1fbf95b41f3e6a8c84adf38ba0e0b33795de35229e241ec102909e7fb4562b31b247c166ad80deb2ee2cbcc3c766f9bd9685a5ee6c9e87a29d1715bb1359d2371656f658069b7b2d58502260033cb1cddef31ba979a75f794d1d03cfd0a44f8fb5f10e8267dfa297a12968d1d881656e2c8b6f1a9df2fefd06ada7a4b571f57c55c472266cc4c02d1d3a9e3ef95efd661563efb98338dfe4aba7b3bc6b586ecf9095797866d43a94dfb8f9638ad230b29e751968ed7319003ae232308fe1a4c94a74b24127cab70ee015b8195f45ca77aa0756649184125baf94d1a31cadf7049c2cb661403f42cd1df7344846571ce87435143e69a41f659a54bc8bbb44d0b12fac3c54695ad357ba3c0cc0ca878b09b8c27864add75e145120be84aa49f477da3dcc125fa9180cfa40816ecb69fba7c9a1c9769a3c885234c0dd48032f1dffdb25493fd91f979170a7df93d0d7cad3ceac1bf363e75a1d935fe1040ce51dcea587ed17c2c3dd9ab0a0e2125b7ae948f4da4b70093f0250b1320c54b63eabc9ad5bac0da9d99cd07fc6661a0780c8b7b71e8b24387741b1983bc1134c8c69ad60623089a9f29300a12158799aa2bec571fef9b14d4d472e16feed8d2f08286af04718afc0309bf00c4d560a3024e352944fd5caa484f9dc3c7c75521cce5426c0567d0c378f0510b30421529b3bd55541aa371a3b14af59825c0099edac915ec8a360d048bca05a16cf1d41e28c0ccc31340325243a0dd0633fe1744a28859323150e29a200127b0e346c01a230cff88d1f0827601b7afdf5391e19c8b60637c839601d0f30cd6b0655b38f8249fb493de113b1ad1f0802474c6440e2ac3d1a2e0aa8356e9a5b6970d7c7d3f56519d57023d33b3082a5f786dfb01dddcd0c3a97d0e3eb1298c3c4a03ba4e5a0bdb5c5689f9002692f34ba23a6dbf46c263400ccf38616af6b73ba5ca63562a68e92fed5783aa142fab02a96941bdaa1ac91e1527a79c6b2b9f87d47cd58c0d2023a8dc6fada70c39b6e23ca4fc41056ed84335996e2c46cdab5c7fa7f61ee1f5ea9d67275ca2e9041a7ada11ecc6870f54132e562e10c1d1547f96e2fd75a6f940db3cb1627c2b13381c6f603897b3b3fc6dc098cdaf9bc561a63b8eb0f71df4d12fee0bc8167359e780f4301e6134fbf8013ca9c825510bc65b57be27b440de09fd00dec2ee0d4f6048dd4e96df5de997aec7b7730cdb41f7a3a48c2421c0b2c41293dd2f89d77b27cd73f4c451d13bc67ea8e6d405cdce68f683c54fe6bd71e014515892510134c94465c1c1783d7ec8e243976d9e711e852c37c486cabdebd9c24826aa1103ee5b48dd4823f416158b04dea13b687bc4fe41ee9a783529c692c420bb7a8f6fb438b653007fe0f33c9995ae59dcf3eb9a274eece7e457bea49b628f3c43517c05c2027047260593d24926884ef2aa2de480a50ce5ab383ea3a8dfa8e1f70bc60d92e54bde8f37e8b089462d7a55d33aa8838d76d7a925ccefec1e09ed74fa9c6768da299a1c9382e368b3e99a22a72d606c084a1779eff2fa451027ea61727c391c3a78c7f23b697ba410325d02eadad416c4ab3eb4bce0c5819aad3df174bbe4cd313b64f266b20c2385440d1a0f853aa25bac320a6972d5d22864e39d0e108fec0593d60375bfe10747e99800c5af641a3c726d00e7c46b099b789885c82dd4beefff0fc9f4ad1f81026cfed37e864d721b7bc81f054f448263f06ec24f3d800dfa4d7c982356ab7504dea1fe1d539bf8426a5b553ee98fd59277640e87a607fe1483b005bb0ae68c9165f316fb8aae0f17f9a0036f39086f78894cb151f10171799f1cf5813b96327722e9827d3e8d2b928761ef93207a181507b9c17333fd35d37e737c5e99efcccc3c10d4497e081dde48f1d46530883b5f791ec12e3690b75eb3f622bf1cef93e0f7bed99561601a82e142b0e41afc58532c094eaaa8b494cb39cf61ee212439e829a64b06bf626ce274b45dcdc03dbb086c117f1a6ae4fb0110577182d685bc214891c106cc00ac5e8388e1bf3a5afd7515424556da1f863200f8e6be8bb8e992346d756fc14e15c19375dc759bfe8a8eb6228710e7453e1df33d2968c02574fd175fa5f2939b73a6a96e157f3fe8bbf853afa7ffc5899f33ea9f1b93479a5bc25b0dd4cf3aee321702273b3dd61d7a39202039b4ebc1fe4d0560866cc9b70a4a3c2f252456326f0a5baf350b5c0250c5ec8d5bc80411f4796c61a278dbfc747a29b29911e6dc6268d3517e9d0a6b92f2b21cc4c7d1b477f253a936c637437219ad023164662e26b4ef84ab0c2bc8d2f8283fa746556808d158b0736719098f7daa763be080ad411f29369da5a860afa9dea58ba1444e4a619f0702db57f091cdaafec18f3b6b5f7299b051027c467cf8524d8bf5c884d2957b45d1dd1368afc8f37b814c8835c8e620e72a9720f94c529a6dabf940dab0bb13f58a11ea739970f82309cc5bd7194e4ad71aa4678b8d59099ff0098e0f07bdc7e849c31553a433f08b40a74f3428a4780897d098101927865a0dd06245e49b0fc5ab966355694a4456ccd10ee094ebafecf4d3926c3bb91339bba1c560328f4ab72f0f044c964f0cef4de3f20294f6d4231700098feca547c70134a08e619933053e7da790e57b1372e97e30f93fd0692759693d6eaf26dd50949a2f46dd6e2781454643897614482c626cf7488a978648d792760eebfbae1d695525420278f2a0788e3260ee812ff32e49510308c06f984a9cf2f119646eddc07e601d561b4718135c457e11a84ef3b0745af88db8f7f678f0f8538ec21c940c9bbd0089578d4e1a7a2c4e9482187b492873dbb2207e6aa60cdb679205e41c4704eeea4180122742587e8c42da9169476d1b95ac1ec618cfe0b1f97d52594b3524eceb0511edb4fe8bea1c93945459998eecdffc6dfba4967b0118e37a778d84c20cf9d80c24e0579ce3ab94c51b58effa322cb81f612314c6f2dfbd4e903040cc55fb034a83c2533dec39b6ce264adcf06542120230d530732d7ca19bfb4e4f6b0a8ce0783534abe743b83d585851e33d1df82c6f87bfe8a0b26c10e24c13ba7822c3d7116c337cc28713e44b6c6e73bc09d2b71629a6e10b53aedeea644e14b9c5abcf5ea9ad0ba058e7176a306f9bb152df4e752f36aa4187c1271508b9818138ec0c593481434f402a10831bf731b6f2a4cae5e117cd2b85e97fcb018c23c9fa5b2366f457e5b07bef03b334a54dd7d22ff8c4b0fe5d2bdd5b99549424ebe81f33b40691e12aede8012060d99794f5e3bec7c479092dc1fd1d59cc085b4cbb9cdb3c36a4427846718855ed0c18f845c7fea6eb57d342f4e6cc6d2d23cf799c2fc9e849177ce8d009070e80c02ff9da22c995d1f17780bfaf4d12cd6d559b6ba5437dbc93f524805548ea5753b16c05287c464e8dc49d50046266d12afb04b27c4cc070b9f425b7c30a27759ea633a46664f2843ca657c61e436b1fd31a81ba815b75feac002d125dd925774c919b313c372efc5e5b4d9ed5a9856c1837dac1c419d29ba0454980197ea02258e0e58da293a321ff5d04d494e979de2782f373f6c5aaf2376ae7f989740894ca301f2e39fe750e3005c27dee1f8aaa8a7d6c6921807f9141b4849ca0f207663217768568524cc53c8fed87b00a0174bb5193d73f2560944f0e67bef8227c40c71c0097aa7ea4ab9dc46ac62a7fc09e117c884a360d9dac2b9723844c9081a8a616dfb15f9cfdf3996b18a727a5bedfda4ed02303e53a84cbdbd0d8a5c4b2ad7eea39ed1cc18da2d3f769b8928e858e5007231b6ff2c922b70afa50fb37eb5e692643105f470ff0686f2aa7d343602527a81358862aa8489cdf6f8b4fc5c2bd1d143410a273699e21f6f62705c5758403f5a29d945dcd8f1ea8a3cc4ec22be0e105bd6fa67d26d823b0df7a3a20ee3c13c9a4762ab644c1640c9551d6d6844119b2d868ae90535235fe85675a5947f0b156b90208f2cdd907867ce65102b722e1c400ef0e77614c00314eaeabe6c350b16158e2a593d4d9584f98e243076051d2e43c1ff569b8e8045ec7bf5791cb5bb348c9d36ebef82b85341a2179d0ba3f29c635912d053a1443dec2a0a5a203e0f04dc2a4f6195112686314930aec2fa397a11f1f2d6d2d5870dd25edcedaffe7549bbb6d902138606c24899d79e231f72edda06fc0069af96f8c374c7be33f13314d47ca89a8e1199d74b88b93bd6d26711c3dcfc658f6293b9cec3f6f18a53753e176b35b349d56ee12cd8cc7503772962a4ecb422def050e803a9c04fec2e7089989d168a4ce165ee08bc85977f09794713756c99c78f148cbd1990699faefca5f0afa2ae4d2625c34494e55cec97b830e7d5b619a935b35f7ad68dc07013c68c8779bc8c4e74bb05f011391ab38b348290a16b58de7675b41f50e7ae3629aea480d323028348c35d184a5349020335105f3449e453e3895f92316d5d231506125ae66412f49e5eb070117d052ae284d770d947a8cd24462093a2792040d95822c8293d7715a281f746026b2a5727faf1f2f7848129098e41016e62adeba0b233bf391f3e20c91e545df7c996f7ad2406788582ccb5381f119ca18bcc74dd5390e7e00af763216af81913ccfcebced47dc6bbfc2c16a8e912f0d079cf783ca335532aa9d32b740fa083a1efd70ef776dd3e519c48c033b4a83796141d806dcf6c7863d7abfe7be01535c8ae8ee6a894faa162536a99922ba7803ec5ae1edb7f93759cb71c4204fa85e98dfde1b0f6991be53ccbf4ea5e4e6ab38cc981599dad3b6e685b9aee28f4ddc8c8130f0f808416fa22d0354570c3c012ba96a3a1b242e3938c667f9c8f03a61aa6ffd4c45c500293997c14d162a5c41e05276462aef2e7ce4fbf962750ff5c1b02c0ae2a4d9a9c2a1c6102d1c79d8ef88096dc29d93af66634eb471aae05052947f373d46faa5bbdc72d5a86a2dbd4f367997b265ab1a5730c24c0e6a7e65754f624301c15f573968107795d1d20b90059abd4f675c10000f7bc5052f104c227215c04ea9d01dfa046155be8e0880435843b38c51574db3550e005d6cd30e0ab86ccf78a9cacd202665eac3946dcb6b59483ca4d033557a5b55fa3165968ad14f208978feb4d69fd53b3075bb0d9f082eda1b0b476d1fde2b35a14a495c1a69e2a6cc7e67506bc5b0ef1f4b3a2368b2a8643a6b9e3c782a1a1fdb9cc8a2689deccfa3dd3a493187b54f6bc5b029e55d99ff2118ce80b72a328085895b136e9b8e3f62cd84435ac3d79be475cf3383ab9649280f90e7b5c5c5431c795f94f5b1236c9c145aadc0652fb788e3a7f331b75e969988bd12ff12a0027859698865cabf0d9d6ece4cdfaf5ac1193da8a8e77da4679542100f31e3ca2284c5aaf873c4e83d7211211267cd3de4d6c46cd9f5e2b52bcb28bf87aa08e5a9dc82845b2074231a6b6c7c044e32212be1f68b05f240a673e154f5412e27bbc73ff12508532dee3336a0d62ad7fd996d0e2a8f8933126cc7702a643fea59090a79fbe994aeea5bf40aa1635fc593a2154662e2ffa60f59602e35acd4628324d123ea8106afb1f127e495d710931f7e537c910111759da9e11d3575242a06abb629d047105b3f1ee41f83bf2414281ea8c097b2e9ccfb8de57f1d09560cc499b50de20129aa1476597bdb3dbc9988acba8bfabd53238f75853a24b0ae275fc54603619350068f04e304740df164ba34e46db94288cbfe5e05d7196fb1e8d22d968a090662dec38d32aa711b7b2c33f5b86cf9b1c3985b72b810dda67c3b2a17e24a9542bd32be179096849ccd353aafc276cbd4a81fce2dea4b795e73457bb88217cf7ba621cc1045c6d98639277cead96eb2b9a67a11c9e499d7ad83fc36b5bb6905d9ea7ed611f46e80614bc02668eec66da14e4f9a154e03bf43639b51dd5790ffb4957e34b837bd8b7446140d6d70c2cdc050e23d41aae83434ec361e240f8aa78783fff790e5c41e6a7fd3045eec4a3087cd3b8dd266d0f6da9c991b836c2bd6506275ca336492bc09393dc77c5e14259a59fb73bf51a6f6782e7a93d9609e3a62877e6a1988d4f0a03304c05041a7193ab58309855693a7f79f6efd1ddeb6534a8f1dabbea165f5291e3ba7a8942646ebeab69cd1c6eb3b2686365f0fbb57a32636e8e832fd050b8e181a6dd6cb2f6cedbc5607e6f62741b03b258cb5f5b6bb6971a2d355ad2f6de72ef2d0a620ad90af851ca086d16bd7bccf8d7b38ee67ebbdf445f68933b9b8c455df7d5907e7d3f48bfdf8f949adb7d353793eee904c5c47472626262a299689a764ea471269ad689b4eb2a695fa954e2388ee3382b7497b9eb6c321e6d5e6cf062edf8d5455dd775dd8cccdd3de78537739fda29c8e8f834a48bbe286570c72779a267d8a3211d7fe4cd1cd3cce0df7b8dc8dc6566e775e66866dede9d8274e74e63a50ceda24f3b771ad18ce8a31a0d37c3e1ef36a85da66383da6baecd85c13fb88eaf2bacc956d63318f661d50b83c8d8175a8fd3ba6ebd161bbc66b2dbb026d75b3c95a042923b866b2fd3b19bf214ed2837d9b89332ce131d7b1d8de8ddd7bd351ea4731508491963181f3fc3bcee23cce3b8474d47e9d88761177921f7781eb48f380fc38e4f37d2312facc9d82bed07fc0e63cf38af7b26b51ca3731ee9ab4026bdfb31fa88a0890dea287df498bbd2171bacc9dd47a490c3b493bc9084518d8776ecfba17df4fdd88e9df36a48197be785a4af8694b50f6f8f3f99a47c473131e9ba7b7f7299beddc548c71cd69d747cb1ee240ebfc32613e699f03dc6183fa35a0e9c1d3b15469779f4eefb717f7deb2edf7d2196ef8f3baa1acdfdc5792129739924c217e18c317e777282bfd0e4a48b44dab9789257dd6474ed25af0899471fbda74633faf5b5c6e31d65da9277f46ce471a32f947944bac82bbdf3b2a9d1885e7ad466f04997e9934623af7336b799ee98d4e0f6d668f0b73d7e3feee5372fd4be1a52bedff588730d8933014ba6cf2c5278b3653dbebe0a91ad57cf2ccbaa18b6f5d7a3dd1285882c42d4b01c610a28bf34d3537e4fdbf14be7bceeba4cde95debdd278e06f3c4617852c99bb381c3e73dfa9f4d18cb677e7ae9b78e1f678d3459b77992ed3f8e2b010dc3b6bb76d3b8d8ebff0c443f4ed310b711a7d34a2ee9b10193f662172b7998e4b9fe9d7f7c374d1f783f4ab86255faf61c9a62fac6139dd4bfb9e72ef49f693eca47bd665272727ddf67af2abcb4e4e7e6d27ddb90ee5e4434141e9baaeebba29e0cb8ccf7138c3a32f1492471779214b167df34221f2f612f785cf5c77c235d885a47c72137715d317e2cc3568f2953c959bbcfb42d255bcd82d9ce83253e4c56f5e759217d6b064fcfb11c61863a9f1c0f8db394ff4ce0b5972f7a9d170177db1c1ae7387b98fa5c11a96f823440d4b2e42a2f4c6861ad290f2b0a837adaf7ed1a643235a26818bebe49808e528f7a68f484fc1c78eb157fae671187612f785af341ed831fee6c5463dbbf665df96bde485dae3ef3bec65217e864bc78ebdf0888cb9eed8179e7868cfb0735f78ea3e1a8d8bf988cc5d7b95dd635e88bdf4e5300463dfb3efc77df7fd20dda643fcd974de488f4f19d1903ed1eb68c47122918813d98be16cce8c4516e38e33ec1c767232359ace27c7bc136c3bc7611c87bd9ebbc5380ce362fbeee5b55d7edbaeabaf26a49bbbae19eda46f1ef64223327e6c54e926dd4b369dc30a4cd147dfdee17b2db43949944523d15784ccf5ba3c4d6a3c34ed9bd71d7b53a3d9de7db1417c99893dab358e66fb688357cbbeeb11db23d4906da4496a56f8dce401e4e8430550166315431fb956d565f85c738cd863fcb26abfa9f120248732398cb7a93ce44321b93f65d0c51638cb2454f44c31862c801c7da6d8a9599685369f7e3af93dca473fae73bfbe6d1e9e620a9d7c3d650a1c237c91714e11684ac75f48c47311295974fc4ae3c1fdbabe79e1f3b66d1be9e3414816e15f57d83d9e8793937e2d40e693cbf4f5edf407d1b95f5ee8235fafb41fb6e3eea1edbe130f4272774c3a3e57012199f4596025779f8f8c1ffac8dcb987ddab1d329f9cf342eea22f872198fb789cfcfa7e9c9cf4fd48f96553f375ec85dc6753f3c9171b3cd59cf2f89117d28cf2a9f52069523edae0e9fbfd628328f807fc85329b6ef2a8fd60ba89c90b37904df0b7679b37b389c9a5f643c87d7b6c30f491bb39b3d47e08f1b96fdfcca62fd44036797dd57ee86cf2eb077c0e48eebeb0de66669387b4c1ed7833f9b2579abebcecdbb72fccaab7dd666ec73c1b99af4b2dc76643f3f519ec6e814f3dc8cc5d66eef8d3de1a4d768b2f8fbf10cbd98fec16e3214f054f76c9d1878a213993da0f99f532ce93b7465092ed33d147f2c267d23b2ff491bbd24ade8e5fc25fb81223060c182a2aa7534a0a0a8ac974726262522a9148a39148d4751873dcb669dabd598661d77572f285366f2f95bed0666ce5ed27fc18634e6e3a7d6195b7a33cc58bf105e50bab8c6f824d5f4833bec94fbc185f4cbe90e6ed256f7b8ca8d2b7d13b107fe13de93293e4c5775ef59117dad4ccfd5ee415213377aad174e62eb51e2477fb85d9b97bdbb3a8cd488d072151a6b92cded67470dbb1d77de2775ff88c2f3339af33f6c27beb7d6e47cc3974003f6b0f9f351a19decfa6e6ecb38f9799328d79385f5ecce17dd41b22ac36b4a10d43d986d294b244912429b2d842e28178fc8eb8c1fc8090afdf114708a922038239e1e8073153a7291f3f0dc4cb78f99da69c1107bd2cc0b753c0218cb422c39e6d7588a625374e920683be807b26b98490fcdc799d90736f496f14cdf72ee18c7c6fbea67c59f205f37d6cd4bd9f5717b9bf93035edf7921799a6087caceaefbd375a5621220ecb248a3a0a019bbf4e9f1c958fc0249c68072288d64aceae9b902d6832463d88132f6abafc7c3136a40859d838c4d0edc7b3f43f3bd37423eb1dd7a277b21d9621d7cef8bb4914e6dbfd769f026d12056023f81731cdbfb9d44becfecb59ef5ccebcb9b6eb9af5a8efe8eb8e794e0fb3ebd9752449969a796434a91effb0431e6fa7da340dab0bfa7debddd259032ee97c3101cc990b15f5f9320be60e7c6801bc37e44a3eab1cb9b46d9639749481b316343701a959dc8f5b26bda9d57bb0d4a9ce6d17630b10ea6f6e25a65a53919d6c11d5a938aec1c86b4c4029b2072571c8122478d867322cb32ecf15a9665599665693d08817df3d78b11c4eec146dd2f3c22dbec636970f668706a1ad6d61637982ce09c2718032d43d501b6584016900564015940225555811508e30256555565d9b7a37a78895955252358e7bb2766fdd555619875495aa725e42cb1eaaa9295ecaaaa2a53ae22487f79d56aa55ce31440f40cb3abeff1e59f4225d3872a21e966ca92a2b1855251a351b1aaaaaaaaaaaac68d4e694fedd03c986e71e9a23a808d1b3b5c0850c055550788d181d53800f68533997eee98f33576647a93695e43987096d56854b46daf3001d698759b491c9977619dc561ad1c955e97090bb187758448d1d01517492c6fe6a6dd565b94524abdce72c6b9d1276cb533c7805a6badb5d666963773d6171b03b62f625619d0784c0b0399d2541e34dbca7e41ec6d6c9ef1e57a4f0dd0387b93f1a576e70dcc9634d20dc3688e912378d6b86159965503c605ac615996455f63a6ec075a94247008e669029313388c3f8f9692b39ce52c67398b17966559966559d3a5b4061cbae41b2535e030feb8dc60b13e7f23c6442d7ace39658c2f74ca1c481b8df12077481933c99c69b017ca73d0298c146e20576aa22c885690197c40828a0445b2653d1a792deb05a1a1f659e54d4a675f57b441185f98dd64fbe9edb836794ccb91dd2fc88cccd937447cb19f71c94f74df314d268ca93d0d281ab497e96b034c83e7a3b1d3a07d1891c0fd90c64eb6df21c660b7a8abefa4b5de4af321fb2e69edf723faf4c8c0c96265a7124a5c9315f490f2881aeb4e8c09414ced88406616764c2fc8344a267ee409327d0f7189c90a780b2d6eb5d5bd341faea5f1202457af327b6dfdd9f8d2971e95af69d4a93f9971008c2df4568299123018926c54e869006c805b7e70189700b131e288fe66621ed2281c2232a7514934aaba8e13456662a63bfdfe6e14b8c1202dab1bac0b593c92e5ab283294e56733738e8f04c850a4c0e1338a10b08c48c8c93503655a9fd1940654b3bc0f3c492650153f2ea8420e6b64f9e38223f207a8aa20c8f2f1d59327dbc5cccd36be5429636f34968d2ff130350e12b9e2c8b469c0e10d93ec39e7677f2a0d52531555745185162dd8b942476ad87e49849cc8550811c9b18a2dd45023c72aa0f045111c737d18b07593111714139bde05458342f2851d23c2ca184685225900395a31a44496da7c26611a25af4faa7c26b051ad6117e098abcbcc50f264923e41de8aa02b7072f4f9f9a24b90a38f154a7216b51c1806d88a9f1c7dac38924369822c1fabc8d1a78a25796a39f012b8afd81dc95dfce48d890e0ee9b4175bd3335dcc6df8bb0d529936657c0e7b3153dc7d73d20625e761cd33a99ca54150d6b8d1a0bc0cb0ccf4d4d603744a4a29c53068016602476c4a28ea6b8c3418a1f000eeafca2ca7c822de01d601a6591659326b13ba2ca17487f96d55e025a3d4100f7c07c1717c390cc1a3779f8fa3d1e8f3a2d798e6d743cdf3f333a241d16983a2cbb48fa125434be2c518c894dba6e5b0dfb41cd794390c99cf80e4bd982d2d87f79d9ecd7bce9339866763c4110da40c587ee57149ae31ce71df5e63cabfd572fc3b715f0f3573e73e231adc5ed276c41ce33b71e7e4ad955fd274cd3b39e6995c4f2e195a52dd7b29e568c66519a5f277c35986bf28656ca79ecc283da687a1e508429ff2ccae78b1418e5b59998f9af66d5e4e895d57c29887f1acde4fd38172fcaacda43ce59f96e3744c03d260cd24902889482f8c4b548e9d6a33303e73c0c8a4e8d21b7dc5ebb8cb8b7199696378d7a567cf61120c4fa5c17a15efe4812e1e8c37a34683f5289e4c83f5266f4783f5279e0f5e0f201aac277942345847de13df45df7d344a76ff1ec62cc3f53033a4fc9c77ce4b7935a9cd89b3cf6f5996655aa6e50832df7dde10db3430775c4fda5e334dd3b26cc39cfca6699a967997f64cbbb41cf3510bd2ddaacd6833b3dea5d372809acc0e441269943cc633eed0a0f6794d45c6dee6bda1dad7f3c2e8e3a3512ac0f4f1b296892b43f551186dda22c34630bd8856d143c980e7e9fc10ebb22ccbb2ece5e117e0beb53670352abcaecbfe621660ecea00cf12384ae7ecee9e945a1fa594dee6da1ff5013b81e729125514a9b5ced994523a6badb576adb5d65a6badf5b5d65a6bade7c880e92bfd583a874c9fc6208b98c76eb73cd18772f0153f4892a31a72bcc20744b24ccb217aa6e5d8a4ace2264e61bbb1519b683ecb32a9e5b0465a8ed177b2b279b74f2d87e8bda3f27a683b8801cf5f3e9a7d1f7b8d295bc7b41cd677eaaf879afbfd19d120f6aaed88f9470e1da83eb7ecb2c7bc7b79ebfde899eb071156be1e3311d695deb4d8f532fb4529c35e7a995096f3f2b0cb4c2c4b8de60641b992fbf4f0314fb3d66566837747a786900e82e3207d37c097a712e3cb6cbb7487cdc7273cc9d798b2fc7aa8595e7e4634a89ddad3537aecd906b1632a8dbaa7526eb237905bbee65e4c1efba6278f754b59a53623b5998c52179621e030fa600f5d7ce8d1a8ce340ac9139228bd1830a543b8b7351c013f81e569830ec8e223cb121979c66fe3bc80fb72cb3759d75430269549c406f23c11324f8984c643e14aae92cadb603d07064c7f53a89683cac60dd658e9596a1399262b6765c94b9768abe99204eed316f201cad1c7a727d3c3e4e8e393d3b2e7fc42fb85d6a541fa1529984c594a974e4999c91cbae497d8424f632220d3e9125fe8392fe0f9becbfc58e823900b324bb26f2f9c9fa941fbeb046cbdfac528c05f6cb03379e7e31547a098b7596411e700d3064f4850a2278a1c91a084911cafe588042596c8588e48508248961f036ed55424ecb6afaf8a551035a42f3918aeeca68fb4a75cd7b65d2b62653bc75ddc7148eab28bbaadbb4eb7cc914e0a6bf2f68db45df4d5907297bdfb7e64177d3f34925743cadb53469b86b27da16934baf7de7b5d2727272760209570de8ef1a9f4530dc9922c69b3597b4822e5ecb338926a48d7a6b89565d9e4ebb2d7b240ec16bb3d36440ee331d946f8c275611856c40a662d867535d75a1b9b9a5b73350cc35fb8aeebbaaeebbabaebbaae11d7087b536f41b8296b593cac7bb45b6c6c6a6e6db9a40bd78569d8e8984985851b615e78b1119661231bccc6c6c66654736b6ecdc5beb0e66a9aa6699a76ac8471241289d485ebbaaeebbaae2b73a3ebbad7647361ae18684bfb557a442525122e8c7e45379dc4a5705f4804cd45c0c8a58e0721b9f469d79e7dbbe665262251c783909c3556012159fb2cb092b30f137522518a8894322291eee8777447f7de3bbaf75e2e685e64d765814ccb62f55505d234d0c28874c25ef6cbe3ee35a9b07019e6aeb0db4e176d27fc0ddf63cc6da2df2bfa68baed3103396ddbf7e374d1f703e597e67bba97442a916e8944225dd22d9148a4eb1a91bed16874efbdf76ac15a6badb5d6c64ec1b888eb0ee35e6b71beddbdb8c1a3f43543c5691491f8acd6ca23ac39578afdc20c2cb1bc4aa4c15aab6a81a11cc6d7d41c4bb60a6a4e162e11e9a3939fa4dce425d351b073bfd7c3a6791be99db7915eedf80b8f390edfed5aca33cedb522ed3d876b563dc2bed877b7cce3b65dd372cfb68bacd880c2cc9f861b7a5685bcab7ef47cab3ef87e9584dcdc958cafd4c8f4729994e4826a51289442261f7dc8691b0bb61984824d39827c218638cb3705dd7755dd77571d7ebbc1c8660ed9c17d6d49cedbaba7befedeec5c9984ec6ed646049cdc945c89a739980abea1986d16a1ef364aee8e9fa0f7beb1710ea83f6cf1548bf4a1f996e3ab909ca53ba8bdee1ae3b8a498565c3b98853ce44ef2eced7f59deec783909c75f8ddafee9b17dec773d5edbcae13fd12bdf32aed87ebdd3de68e2faa8090cc7d1658c9f73b65ffc1fd3ee37ecf71bf1f908c0f248b2e7a783b91178abe75dabbef8776eefb81f2ee327d3d1b2afa6c68d6be0be5f1292634281f6dd0f4fdc4c4a4f451e9a3d2a8341a8d44efee48f4ae341a89eee8ba48a38f44225d41749945582402b2e1ad3b7932b3b3a1572412894422d117ca3ceabcc9fd07fe3d877f3faeebdc894ea2fff872e8003e755aa42c4096649b69ea8d0896b481bd1e93366ed43adbf7764a5febb36fde4cfdaa99ea0b7b9bd73c79753569ec1099de0252bdea715a989441ed2b2b65d0571ecd35735534d5ad5b6fa6ba65d964fabde65e499b80b142e997c935d24d3f3975bfce3dbbbcec9c8747adf1e87e554066d1af7bbf93e83b05d92eba6ea50c7cd1afe373cf441ecdbde89b37733fd1471bc434a219d1afe81a79a1a849bf7d9d3b35a9b0dca4c29267f6ce068b4ef7ddbde8f8c7fd4e4146ef4e13a58cf0881caf01324483f646d13d3570d05290b41c179206edbb2e2c20cbeb9f5a3b494f83d606d50b77e4eb196ed034056e1ffbce8bb3a5933d3bb267a8417b5a515aafea45b64aa2c87612c9761ac9a4a4b51a62a8a651fd816c73b5af1e0f3600ea699f46590f9b8a6cdf468836ea17c4063f8696a3ea9d78d3a0eda06e24f1c5bef2faa7b18832ec6b2f21dbb79049fbaa779206add00770d5415f0b35d86389d06080c34e92adbdf5216cc5d864fa41d007241dd4491a55fd415cb6142dc589e4e99f4662c91e19825c5d521153f8f61227c6a8e4caca31258b44ec354fb7643994320631257baaecc93939db91ab0645158f34787db5791ab4ef23378dba620c914e451c493891ed3412eab8f20c000e70d83b48ac2c12c261f34891ed0360036cbbdb4f9c9d4675b66f28e8f5aaaaaac739bd27b0b533c77eead8cf1bfbce6b2fbac5beb69221a6b51857b6b21fd85e341d438e41d91e8818736fef43a3b4dbf7c8606c5b8c6d5bd9b66f9bb76ddfb6ddb06d2cdba6ddcba33056e63dbd5e756fd07c60b99e61f5fabc7ecae64da7e4cf12fb899344a38a348a66fb09054fb633ac1294e490c5da1c4e9e6c718413bb7aa48fbdfc914032480e35ca5abf32090972288588e49065047686bdaecbe6d8a631824b1677007f5da677f41063fa0d88a948136da84819210b8cd2f5ddc503a9276d782c2730acd20df125c69c744e5ad360eb880d76ec79191b8cf1459b6169b09f837490cee16764a6f1e24bcfcbf437bf97a2b0a35bfad69a7e1fe24b8c894dc8fdfe8e27fcf064eb57b6cfae0912b89a01aec2fa45b5f30aa851d7b15baf82628cbdf5caaa8e64eb3d1a75e5281a75a4513a33b543eb64eb15ce4cd5e82f66aa2a992d96f5aa87061195f174054544653cf8c9d63bcf663c88a81e3c8db2aa85043e1121db90b997b2498e3355de2eeb5a1318b43035ea073668edc8da2f2fac91af679915817ab0b94783339f66e6bef0023d808831dcad99b787336337d7afdaf12a28e28bf59422e0b0c6fde68535635e58b35685bc3aa464a65c5c94684be0ac0e356855a1066d8e597eb50b7a352670666badb5564a4538c015cc97cb75baae6aa753d2a788f58ae7a74ac2182290902c82e40839bc35078985c4480e59720594ad2a875550b62a1c613594ad5fb247a34297207c0cd1280b04c2ba118daa0e24821cc62e4290c3e8054e0e59b28584658204c66436410263b3d6cfd05cab50a3eaadd7a11803447cb16ebd5221a21a1041ebd6337b4d90c058fd7a046008b8da99d6e3dc81e6071f628cac9a58424c42b6be6362975a4b4915e3f91a0888368ab0b9de7a8e362a1d291363e46be42274c8d66fc498ea01e105115faccb1e0d5aaf74a40d084419d6adc718633dac9ac8964a9bc2a018b31363e4ad8c275bf5e386e28b75508b8c6702c517ab5a9f9715812a507cb1ea4d7cb12ed32e50d8afda69d0ba919d6c619e524f261f59ec049237b204b3942859ce9087b962aac76ca9d5e5f7012c6ff8c4968a311086895f686d097ce5f9d5288263661b155a8f41d93af6b02606651660ebf29fad9138b9faf5b0267a360757efd37b8bb68cb4c100c800cf67a01338a7ca9c06eb4117e0500ec93527d71410dd525f7b0080073f3b7cb02ea5f4513f447d8ffa20eaf14c593bab95571359bc8696c0b26b15e1205a24b0285f38310675248726964ec536bd29500a1238344d528ca99fd5e79593c518341423672d6f39362aa3935a9b7699e632482a03aeaadc03882084a83e440d904665af5e71628c0172759b4ed5982d1557857248ad6c86df2dd56bcc9410b18b5c9dfbaa6f5f187f7205c403c2eb010eefc55d597d8bde3a08d328976c5931d7ab57d817adf7aabaf5f568b08a40433d516257c0f2406431e3e433d9a22ae02b6f8394ca1c2b2b65e7b495652f2cbbd6d5341fdefd1ab9a3c1069bf626bf533f664941da5d6bad550ec9b43b368893e9110dd26b4b602ce2e96d8c8831d6e9b3231ad52a907289231a158102c004b636c4b4ad5e4ef3a19332561eb533b54302400b6cbf4a9be972b4a2ecad8ab227ad95555d91c5925cad48a81bca6eb0e8509ed798c01226cf7a14142f94a02c892f284a660a4508c5a2088138c0610c254279dea0135f26f71399c0ade9f0f2e80bfa2532ea694ae0be75daa08d2fb1e27eace69090c4230591801ae47ea40d1214a28c49da02092a2613adb81fee67f40509e80428beccb354eb3b090508e586041423c692186088365084640c2531bc689485220531e4e983fdc497f989f508491bd81551c6fcc49220854b6d0c2537e8c498eaf337288931f41345499eb4fa62e8c4978929d17ae2cbac00980287580ff5b89ef832b725f165723f791eb132f6562fab99eb07c69799690c2f1a9c31967c319434388f04960f6328b9a10c19783125e5e82967dd4e34d503724b6d4625ab008736ce2d361e6d898d0777632670fcb42570a4026b53684c90729045c9066d092be8bc56ea00f3fca403c8d9a4937ab2c2b5be3cc125c71c72bc48a825c72a63b59d68aa07e4f9fd88d639fd8c4b11824c11c2ec3929a5b44e9da993e5ab2fbe884139450895137470294fc0a1d531a3ec496b65d9ad1f3f822cfaf464cb04b64ce0302ed1e1e5185fc25a24db6b4ce0b07ed1d79dc0615ca2cd74beae6f881b21d6d198c061a90765497cf1327dc471793277da12385c4992ed331e1d80a7ebddab27ea45efcbc698edd7af18a3fdf2702882c3ce6c11004e00b20070228ece8b38324d0538bbb6022cea66aaf6cc54f53353bd6511633400647b1c8a340dfbcb0b71f80287dc3bd9bec28127dbe3f0457f38e85801873800657b9d6c4b4338a4b103948a93c64ea364b65a13f8ba032a07d48b3e1a3b9d478367b6d897aa80c395a10c0adc1fcaa10e3d3974e8315981431625216651c2b224c66cb76701438cd16ecff2058b121c705894e49695242b425c96e12cebb24c9465a32c23655929cb4cb2cce680b3accb3251968db28c9465a52c33c9b2932c630287f8c84fa332248da24031e616c05a55a5b3ca96ccd96757ac10533af4e8f0c305d18854323931a11060442989d212a526949e506aa21485d2144a1f631a8a465d33d538b0ce2c922d8d1d3c7580aa1360bb836174608ecc008a312cd95ed3c121d681d1a1dce98ad00a172b4956821ac5ad4cc15be9a24125f1c59eae0cad6861850bb75a3b75e8d1a1a7ce4f014b568674e8f98648d5fcf0ca10661962c939a22d81431d7a3425666c7cd1e18a6803071da9434fa3b8dbebe0d3287c7b1d7e624c16ea90042f05899702145fec3be5a7417b1c74a48d942ca20c7b9ba245b60f7168225b9569e22697f203e327be608f45d37165ee7b83f6293f5ff9f9c208e4185fc417fb87291fca92f8627fbdbc6d21db7321dbfac5116b31c63a37544a2b4a2d4a2da517a518a519a597d2ecda9d39007c84c6ce457f7d65e8f268ec783478b880690e69ec3881431a3b3476421a51644be348b68f31e6393cc010876118c33a0c1361d808c3481856c230130cfbf09106ed714f7cb1bf3c2c4583161f3992edb3ec765ed45480b525707823dbd3d8a10184985a198a2df676250b3dd97ec50a31857156b880b3accb3251968db28c9465a52c33c9b293eb6a4be0b0d4a3e3caa59e06eded0e0d2968eccc54df9e060f8d28681c39c0508c69c9f6190aa529949e2855a11406a531285da1d4bad77c109229ed69d495ed330cbb36798c3353256a9f699aa604be2e5ff269d0bef4135fba977a66aafb6c56ea71010f101463e8ed637696b032946d8c4c3f969cf8620f6a81c395a1ed26bed89e46957c1ab5ddbef41363b2db6fde8c062dfd600030050eb39bccc337f1c58611c81c4f7cb18f5ac8360b4898e06407450b2d30e1312da18952cec9111590864c78f081914cd04fc9c230e10922228bedc55a2c215926b0a0130a1a31410429c05b88d8408413bae9c620822d8b1e9c851496d0b4d8e1b2c0e204d7093b5b16452841c6059096451498147a64a4f073710d44703d4149c6028b659143022b0a45ae2c3a50822ac98f5dc3072a148e5857d0a15ce8545aa060022999d5022501a72db617372168b644db33022620916182c5403dc89179820e9ec2117cadcf144a108020a0410a6da02fc0f80b3406f8248852815772f4910210e41af073f4910213bd041c230dcd0446a94217b8cb6222812d24482db8e04163017339fa706164ee70c1858e501634471f212c7aea10b2134d3f3edad08c9dc2fe4e34a1ccddcdcf2924cb79fde8f4e0bc02fa552eff889f33623b39be4883734696de41901610e105536822470760a0800bc24004212f6e962074cd13549d0e25a856d0a1bfea0a11501a370ae540149d206484dca69b5272f41152c274c12003001638b4b9a38c803dd1c4cb1cbf93cc33cbef442385e4acbdb033d91d813c3bce209d6b22508e5960f007584e2c64d92806b6bb7b04032b519df2052c3fbbbbbbbbdb830196dddd4db120359d9a1318acb1aa39b3a5abaa33535891d9d2395f8ed7d5440289b1c9a443c6e8058d159929ce62455632c7633790657ca9bd015a4bd07989dc938e82acab3181ad5f2d6dd89d1cc13983d8d8dcf1856a20d7f842bb9250c8fd5150c8ede47e761921f7c32b771cd2e1e5f955517a5952ac48ee9ef8d21f15c13d493cd81ab218634c2943d8dddd317224c04670cdd1472827d3243097a38f1007280ab095a3cf1668c026bcc517688084206cd14506d2c25a6c61c4905dc10b5c94e4f6b2c50f725fd37274b6451b323dfdb5b08802153e3dd0c10868b006277041031c5280051a58a0031b542106316707d3a71891517022003d40c3134f709103356011d3d0c4089ab8820d2b50411270d069822f68914bdd1970789cb238a250dfd0413eb9a1901b0a2cc80d8533e436a18185522194e96d900b32bdc55f3ca103991a21d35bd60c4eb0e246a6af6684e10c9bbd7509b451d60c4d7001ae827c904aa657484972932428532700654aa98d5e78810657da4f0b1ce47ec4556829f1a34ccf48ea94cc79acd1ddb485ba25c77e4074cbbcf5c974512401d6557a58196092fdecb68ef5d5d86ca95c725fc614c97e00f78c49252581802290a90a1cc69e9094863cbf03d34ff2f838a124db384dd3250bd9c9217d4d0ccadd56cab2685f627837554969d73eb06a8f920b31a57d4d440cb9de0ca3d7b4f36658889223ce6ca98d135fea2d1fe050fefcc89f0f3153d6f617626caa200803e61a43c4978aa3a489f4555ae5884a0df0d7e7c91d094583f5ed499e068fe8967a15151616179719213844b7543b2d1ee0502ac94a1aac97b55bbe82b3e2799ab3595024099d5c2bd04ed56a9d5249aeaff435c42e55a5239da73625070fe09af968598065b6aca5f1a0d2a82b574d89beac3c42477c306f9b4440c0f2b35bd20d4b15647d51cea64a9629a9c58d1ab27c54c912cc72c6ec4832c918639cd2caf6e4153218f9f9782376b19423e9246f1fcc3c8d98001c8b437b3dcc8c78433b0270401c9af6d16ea98f8d9aaf27754abbbcdbb31bcc6f1e088e437bd61701eaa747679687441c1147cb38b1cf8c7de105228ee95d59f3220e79b3c4b84149b51cd2cbb06753cb711df3ae2981594e9aa9774b85a9f531e654ca265576997480f1a5de145feaab96bd6af7569b3965d84ff79b3a6ebeea5962ccf59a2d1d37631fcc8cf8529f5204981e43a4fc0c6bae624c95e3cdb1071341167fb16555abfec69ad15bd953ac76b69ca2c9de9eb45696bd6a13596449e10096578932466ad263c50a92486215d151430e4d39bc61858e14a6392335cd9826956752e0f0c60477883137a6f7eac68d4e95727618623ece396769ceaad2094e7995374abe5a219b50a4c0b56b95a5215ab72005b580b6a0248771880b3f55f60555e8239da9b843ee108900cff7a58622044c1f3358293d1ddd504a6ba66b3892291aae5882062fa0218a52554318195a020fd4c8b47a7d9819a4fb306431e3aaa9a231c6b2d7d65b16a9c1aa62ab0885fb3052016bc10486c733801c904b88a9d1eb838831a327e42aa7c03d9459f842f7509ac1fa421a51d6ab9107ce967a1b45301b4a0211ac0f63cc7579939d29ed967519513e2c1c1f8d6a9d964044f50a22585f3fcd83e915e8f4174466d75d02a4c1fa2dc7cd93a066599b657196852dabb32c91658d2c8b04447cb1e64f6ca929ed75368e8ca9c6892df5d348b7f408e24f2612c2d348ef541c82b982f999a938edf5c3640d0a1b6020d289181373fd119d8a386a12b91e8c31d5eb7b00d1291fb325d6df1a82b91e20d768841004125feaab7a1f73debab262fa82c8ae1a27b79136d2465a27b7915cdff5ed44a79a48e334184a9d5c0f001ee0ae8d335b6c22aa9f884444541b011263eceba58e11b573727d7d8c31d66b3fd1386da473acb599b5d75acddacd5ace5a6c6df7f85512c63b4b9596f422bd9138408c20d9895d60628c7d556daf921029901951f2a7c16a651280cc54bdfc99afb37a55c6ac0b665627287043e08c1e6c08eb021bc294ccb0aa926c49296beda253d850b75807bbc5ba17d9c2943468cd39b1671886615876cdb8aaad41d6d6a04e4922f4d4212f1a1587600105911a5497609124872cd97a58e1541c619593ad5b70460f36c49255b5d68a0d615d6057c086ac1a54836472f8b0232a210947c8d677983e20a1345e6b671c40e646ec9961235baf11a720e11a8471b0206ca80661433528071b52319940974e55a11ad4a0f51a94adb370435889ba24ab31f2d5b61c69466f0fe1a37a1887aa0f6dc41133c6d0c87d1f7248a766c4a11b4472ef70818943497c4ce094e55b88181344ad2c7b6199a5f180bd58e24416fb5a8c9c753633da54463aa9b5c973399bd7091c56255589b42173f765e5911ebe6910071369d47c4dd23a4ee022f8061be95495c2fa40919b7424c6cc4092bb02f1d4281a3cd260e31ba1dcc73743a4bc9d5876116384861a454daf9567a64e3d240e2b0737393385a20332bc200b4252bca00338fa5867a684c8600ba0280089808a35cc00471fdff4314e3f6a561e8271f08d1630cd21bec90d13638262cca5f5c5524e966373b2d596c0fdc7ea5953aacd5c197b164540b8d565babeeb420aca936dcec93657575b025baf9a0e59e4a456af5a8eeb99091298ceabaabe79c3836f555595a3de04097cc22ef3494795b16f0ec9a2aa2ad5dfcc166b24c660328836b2cf06f18988bad77e3d6c2237d37ef220a6e417427b93ad10b2956da022db9f9e37f1c50738ec219985d8627f3d698528a91065d8639e1c8a2fb6a75becad953dd8a7e5006b2fc0610f2599569216d29ac0149b4332c58931b127c66040d39b3dd3a7c12993746a26d1d2083135397063c60c17171616959e79135f6cfd668eb5031cca9e21111470d83f758a46a492c9890965d60a49b7354102a39800dbc7ac11c9d167094e64691b89cf129264297d3ad543b3c5f6c8245ddf43d3ce2fc4d57ba27be585710cd906a179ca38e756ed5853744bba1dddde3a1263bc1cb248de883c23668bfd08c86cb1b73c308bbc385baaef4433ab2f0c22d70f650a2c7a288bdc74fdbced2c4b645923cb225956c9b24c2cebc4b24c9625ba27dacc95ed3322d735895cf3660e69548d31d94c02c81035291c77e238158e83c17131386e85e3ce711ec76d5a0fcd942c3265912ce5a40223c6cabd9b618cf16da5e3cad5b499e9467952811163e5de7752811163e5de67724d664e1299488373c8c49937277767472269d04a2498cf4f8cb9b1d5e78d44229364fb21a6b54aae8aa6c1d0b4189ab6a269d7344fd33e4dbb41d36ea60223c6cabdcfba37683e6096e9565e1891d8346803a427a8040c586a8b5a324524100000007315002028140c078422b17038cf024dd4011480108da44e6046950aa324865114e50c228c1062800c8008008c90066910008fed8ca9d50a679e83796a94c3b9986ab4bdbc3a5c50238467cd5ca33de5d5f1821a23392fa4f102e5f564811b217c136d8d0bd4af070bdc88c0ac987fc444b3994b0ad187d42b484aca4421a40bfe88b9127c45a8b5c81a6814696e31f20608ac22bf88a406095c317a29b28d504c45d5f2c81aaf4045e9cb486a4ca215cf47a051c11cc8fd88e4a288ce821b505fe37f27414777934dc27aa78d895776eeb210e9860856316b39d20d2b60c5be26b206695411b78874631456a1d667bce0cb0d7635b455da506a96262bf175ca9984671c6f793d37e5e513b31a1a2ef9c1c1871490434fcb78dc6ce41bc94623a35902b72db52b829a356c94e2b9d9ead719a14c8050c3867627b29dbf717664e63ef8bdff76fe9614668361b9efca8fbef1dc165b0e22fd5ef90661747e8410ec9ba50a03bad9497c163d3a9cb7379c75ae0387b450f438049e3dc80ca6c90684fa16d82fd183b535d195eb25524b86ef48dd2d92b095f57f5af4153c6348ddaf49685e1b3335eeba05f262a805f968b2693f2479d7e88a5b2e2778c1956a6317b402a9b2ab19171a54c54333d5b553369ffd92a5d2a8649c0acc4a7617b76683158240ce8ecbc9ea5ad12975cecd077dad2271153b2b5f677a33188b25dc7e7c89752f83b3e1973968b00ae5828f23d2e8a103d15bc6601ba9e42fd6439da32ea17d52ccc9cf751716e1571c0866c709dc98e716b02f83f2fffdbb6c759f5ad40cd70fcc9386a2154d78e5ebb1f080187c3c544f629c9c6e0bed321446eb4626454130b39cb1228353a01048f55c9f7db97c96cff9d222a23e4f3098389083644cde1f889a333f0b0a4cde2a3d39155b9ae6bf59b1559ac4a3fb6272e513902edaf2ef68471372f958bd91bc98306e1d24fef1ab9a9d03c6dc3b4df4e46bc3b0eb542254a67f19e3f2098a43aed14cbbff88ce2af0fdd82283d88d0b0e7e97bb0858f052a8ae70821cc89cda4e243d504da8fc49c5dc5af9bebd7f54606f933d4653ca31989521b320714b0599f36161ebbaceb930c4931cb8d901768d1f4b078eb262640bb28dd25159f307107c03906750dcfa31ab3ff801a7cae0a47ec9d9ae03cbd95e27b3a2b2923042606a0fdda58c3fcdff68e5b6fe38614ac77c9a0e2a56e98825c04dcde69c30b9115dec0163d3dcf2d7551fa9d14be88f09d7a8e00fc1b128bea50bb8d277c261066a0ee31346ccb422a438ba997d8ba8c40cd92e4828717c504af366cb6c5467fb7028a9764b55961bc1248672c901174722c4d63b910a96cd691209e22fa105daee335c1e5f2e8ce886020fe5f12cbb552ea3b75fe1fef21921f36482fd1a0a52a1dbc0e18853baf936394cca357694f979b9f9b5465895e5d614e9236f87c0052106783e5b61a466154a1605049b3bbc6e16476a702202136b02f6e017fae84c5241a4edc21bed1ce7d1cdb1bb55c7f4297c321280323edf8f5ccb999594cd56a633614fa052ddfaa4f79095cdb0a9689080e35bc44889cab1f1cb94d702f76087a1afbf0bc390fdc032538aa8a9ec3bb2b4952d696bd930632eed5d5817a16fc82da426ce6421a35b956bda70aa3ece222d6844b4860971b0df965d58d20708f3d848a7dc19c7fde931617db535732cd6eaa4136983e7b87129225cdf59dcf40b732862afc06f60e28016f4218e9b11904a8d103b04e41990566384592cbe57ebc51ac46d327abf41d88078159313e423f2008464128d71d9103b51782032c05f5eff44808241c18eee3bae4bc66ff003e6beab3852a856ba26c4b7f96e2ff1643fd22b2bd26dca08cbf95ff4e76fd2f9bd6c8672df64c3d4d9d9d44d3a1f12a6404abba8086d3180e29248b6bffb7c95f1a283fd2e27717690d2867a3b35a036bcde3db900f632b9634145d24bb53239d050af59f30a5bd630cf854c1aacc58e35e46e13af5aad2177629b4ed6c7c0a5c4c034599fbd149e6c4e50a7b96c7f1785695904c3658e5b6dd029f952e124ec067693287b8693c4032aff61ed927bcf313a29f17bb4e201e177d404f2b6c45d7e15380dba894eb4144a1267c826b28eaaff8061badb1f0942fe5b04716d5873d997192be15d6efe5ede9d5f41aa2a0d96803a3955cf657c8a6bc3d611b83960267dcf0295446618a45c795c3557b195c171a7749d8ed3650e0f25d0eec7e6e849d64849f33659671041ccddc68444e25d432032fb883c13d14f4b7e06fbbe0ac762d14069ee318be154359a57009105729b31abaaa48bffc74862bf5c20734e609d204a4f754cbf26b1a7818ca692c7945dbd492e72b9c0142e5daa57476c2695db1f3ebb81f44b046a1cdb93fbcdb06f1f48e991aee32c48618152c2fa815c874e157d9f664274f20e18f212cca0b48b325adb5e5773690c2bf80e620d366ba6a95a5602f490feb9883a214d9639ecc9222b0b2477ec4ff72555040de756e32c4dd891f79da1ba92fa55609119890b4c7b8ddcf29ae230fa75157d3ddb298eca15318268e6d077d6b4d434518d5ccc41c377838959023c1a6be0d4334862fc38f669174db6468f4fc38c9f2571316bc0b9569add6f6cb2e7a6b816a5bb06ec922047b34f3a1fe46c72b65cebec721c1d8f2034fdef89a88dac8f9bb561ddab2ba915390f6ed3df6c505a7239c9975e91d15aef868a56dd6480ec029f0e12af3b5c37862674fb4de0fa00446c2f701dd4ef006c42ef02132b11a80ad2c39b527463a097cb5f14b745efc7821e000a1dd1a8a0c2cab67b54705119776ded9c797267a193dc6f6507163b09db5589b0e5365ae996e8dce3dea933fbf1e4aec53cf3c7e3628ae9c1220606047a91ee8c3e8bff3383a72003ec2c309a08c2c32526d6e4bfada3206fa13e9b80e7dc4a206463ab0236abb60e8787c0e64355b53b8fb6514024c9c0d9ef4197e8762c40a82a567c5fb462836653933e369a7144a03f38b6fdda30b1be872d3021c6c6971e4e6973fcae4069ce2cd8ba7424472c5137ed588bec4be2f0ad49704191e2193079e8011350a1d260966f23d8fb7e57f6982fa7eb948cf2ddf40473eeb72b8b4d674f9311ec84a33673210034c285d37cffbfa3798020b339eb64b29147c04c282a7527b170d456eb0dae89beeaab4939f6a511bd608490e13d2f8d3c61315bc8761b93cce4a50605066f08ccc8c2c6f21943dce5fbcf547a522edd648cac02fdfa59182099040297a415a2de9961eac45d7454643cd36ff01353f62e215255113253be8f518624fe9a4c81750f55d7a95f6fd7646ec838d28f39d7136ba6417a878f1f7e940d8dc21d375d9901f400155bb9763f9555cd10ae929b81fdaff0a28c519032b7e4c6042657b8ab46e2c004deff0d3cdcc4af06dce1eccad45317f63e2ef991735a323aceda6858eddf0ad69c37cf976990dfc10f015bdeaf5504cba96aadae325912bc4a84f1609c97b412495ddcc8b65c6217c810cacc147cf41cb8900992f972fe709e25571dde0e4cf26f177c75429d334ffc79e2dae21154e1761537e5311014316687b133649ac94da484a1f8f3a023846d3ed185fef8736f4fc4d418d23c0d52e4327813618506e19ac254d52839cca9e02f915a54e4299552c70135e4971350a0b1f5d3fe140d3cc5d209922f94d418b880d7cf2a6172576fd47aa569304df353ebbf848b920a8f0cf8a2023917027817c491131e6b283acb9e7f5cd02da18935ab5e8a8a37bef98e3987f053f5a9f3c134c00c6d660d04072e97be4a3872e1d038abbfc11ac1c3e74e0230aefa39deb827de710d0d1fbbb01c007c72efdfc59a5b300716ae4d7e50d1198d6c7fbce737ea4c85e1c553a9285d351cdb8d2875c3076d25d0acf814f27b0ddd2ab4b39e997615db5861ffba9faf4b6b8af83c4c5b1d9c4e8a31a2c826239a53c9a3a165707dbeb60096e931c1cbf8998ef36f1c8f6288d7dfec300462e283cfe7f3cba51bd0bac7abd07d19a06c9b452f7a8f2c3039d73581721717385f35398465c3506e65d832cfb9cbca9723b534c8cf4efa5796908c97adbcfa33951a92d7a9acffa090eaedc6b8e677cf91830f7afa67f966d77b18c084521d70f57eda50754d5ddda3cb30133e8b8c4c04af284b84d92e81532198760abdc925e55ad2ca535d121481c30c22f0aed61acc14e825e7ea378c0ce45a078d6210299a7d23e71e0db0cc35e298d06e9e75c28d6771b84e7f76254593af4935c3ace9742ea5a7ac7dcc2e5d21840c07703e8dd4a8817ff857bba7d6a4857c202b458f814dbb6a43cf629c11b7503f3e43b12c6f1193d81424e6d4d44a66b84a34e59f94b92d75eedbcf811053b4bab3b0c0f6d7880cb19e1476d4bf17009b7a0b9c7c83b7cd529b8df31d9bebc36617542da4610a5eab5d2a2548d9b79ec7099a6eb07b549300ef3aaf25d57300d896d4f2975bba6f8b5babca5bedbdd42becd0bb66bd64f41d8b292f2bbbed96715cff011e15c1e0d5fdcc2982d23272e3c50de3a1d83efef20867b820edcf5a7d4a1522d0d806bbe5a32421d8692cbfd6f8552abe41d8f9a0039c45cbc7540121c4529808c9497f6ad983386b3d79bf9486f08584744f092dc2919f386463a1f61b328a09de843adf8638818922d4e5aa0ea3a1eb34903d3524ce10fb8286df383a8d8c38e86eb793f0cc7e759a6b0acf1cd9a92ba95a518db6f64aa91a12fb0da213d178cae6b278f8d51b58083c35f5c488b9edb80e8592065c4477ab9002524b33412e60546a9d9bf1014e41ac3804dd2546edc691bd5aa2cb7d1d39d5fd85fd903194cd9bfb0d9b9171f51c899b0cc62056bee48f8dab276d642befe158da23c9ab00b1890b6e719addd82476a1b2afae70e3b940a72b15a27e8985d3cad648e4ea7b9705ec6cd84bf60a2d28a1c751e1f454d17e702fecb3f6740ca019f7449cf91dc6ccbc6b98e2bcd662b60df8083e66991dea34a31161fcb408dafb8357dce44b423d15679f210dce1cd4fde3e1aeed1676a93bb53a16a8b6a4dd4a9b632a000e10a6083b2973c3120ec9c2d55d9f8d4095b2f3727356d9bffb40e46a93285f23ea21c81428774d656b53281bbebfcbb4df987ddee9428a902905c18543f597aa14c492e6828fcab0f8ae7e8d1e122e7a8ce81d8d92c38df04f066339a68080e3f2f95e590e496e61275db59f56ff1e4b01418b375b102274fd795aef11ed5ba0584c1284b60890701b97453adf565c0b8865cc39f81406e222e12c0dae7a855957fb247be9f3faad6c78a69d3d75d5174547eab16f8f68850e48fc52bee0106b65d61d85bba93085d71f63dbd3d6eeec2e9ca4fedbb8628366924afe965e83813853b03945f0398394b8a503331524471c1811c12045cbd28122ddfcce14ac3aaca07f43b3cf806721361edd6d244d179a8dd7483a4b08e6343bd2673b409e0116815e143d5bf85af8d243b4303d9fc13794bb6645ac5184ffeaede1741c885f525ab70eac22c404dc64bb5dd1c3a7b4a62822bf44a2d192c62826f44d85bd593e8fdc097ec83f6740543d4c157ccd0d160e1208769deed6c5494ae3fbd067317c5148564164435226538e6c0357277cef3e8883ea8a196c30b1646831df7d787a5eb9c67512e41991acbbe6984a46784f6b6b54b4195b541f430473412345beb46f9dae3c789a42dee904115cff6af229a9e488a973e7d54d210c0297232cb0be9e46cb4afd38009e1a79b513a7b406a97679a5acdd18a18b6e2e2ef55607fdaff55c7c7499fe4a381c7008343e486e4f0cf32573277ac0c40e8a1ffac36c6df073c2380c5b128857edf573842face340ddfbb04e25311f017e4c73aabb5e9c8ba46ed19f6bc763f3a9bb7d65402235161ecada045cb44bee807510838d922c3ad6431f6e13953bb92e7ad4215cb16dfa846ec798989e293f7054cb10c7af77f941b3761e145074e4b088bc674ba6236a3727b2dd7182b8abfa4d6c50a53fb28ac9e27aaf3af103c2fc9b7bbf776748177accbf0e5032f90b5124e8b7fab58fd28c2a9403ca2e19390c85300bdaf46d24921109b6a8421bba3aa77c1ffdb2c491478025c07e70ece75465a70049431928a3418e4f361abb15085b1d4f8d5a5f4cd0684fd1bed7e040035fd81db83e7343af293fd474b8b2e0f5a0a4a5928ac0d5af0207d79844b771e6366e404e6f63dc624afa7ac2d400d5382a63bb12f84d0d7909ea2c059d9c129cc239c70ef28aa9e20085d40835a9de816fa0f28ea7444eaf8a36c1601818c2cce707be211aa4577a1128435d764f55bcec9b74cf15cbb3c59546932ef9b9125b08966d652c996ee2a8f30ff715e9d262936f347d574c979ebcbf61e9e2d201bfa09472ab45cc3d82ccb6e1f8c9b182c3678bb43dacba773c002fb47cc2411a3af2b3bf0e8cb53a25e036ba8c655c9ebebd172499b4a381c0f21815440c3ee25fb4105b3e998cf88857ba682e4e10c97f69d85ca025c264e94c9d8b40e32c8c0bef80b872c26c473a2061ac3bcc3d5c34420b5312a6f6730bf95cfad68f56d3d8f6f251376def2ae14104152d7d5d0b1cc823e29e2135f552b7775bb03a6d79f5d77bf59f51cfd503e1d4303a129eac5b4865d6ce8c05c276612d199a098c29db91eb84db9e03b78252e3973bbb587575dd07904e8834b5b6c34eb6cb2ced8a78e73640d132595e799632c83c77e620add0bbb1f22763f1d31fb9f213b1e1426138ce29acb07a7f64619280bcfcd03559273e1e978ab6a33b75f0ab3e74f735962aed6e81721d8299f5f568b035f41140411fba17be167dc9fd6f41b510fbfe81866e00d094b08caaf74061a9e28095ba7e6a71b6abea43a4f5bac8b5eaeb1cc6e6f3c46f2d935b7692c35abd13d028234a7512a4eefc4df5d5da72f2e68c2747a93ebed2277008ea468966f069bd90ae4f87efdc5a94c6b4a2f44ef52c71b5c8eac348c57bab350a69b96297ec0e1ee8f2a16b2e0278716fae288edb51111a1f7c1c2e54bd62de726b422adda025f251ebc3702b5d6a9cfa6000cd4a82ed0b9ce11b5c775d2b317efd129241edcaad6308ea4a2672d4e4723962afe8c6221ab01afd4bc4625674f1a5a137ca7217dcff5bc90e218ca04d0a58c77819e01c74634151d433d959acf7964cac83481bbc8f96c1c2eaa0bc0f5a98b1d1e5e35a139bae6920f6dc577f3287a9cca7b688818f5ec304543f99331cc876a0cb389072640b8f52809e9245ff4b30cd6750171ed8234cb5989c4e5925755825e02f0153e0e34a9423cd79fdcf339856896cc5319c1da0d9245e317df878959a8375151a019d7d9753b5b83771dade7c7a42e1f6ac1a4eca00dce6e4f73c6fba1e4671591af67b68c454a1741a114e2ee568d5b70b6b3f48acc18f40120c0043f5b00134a8c29019dce6cd614d83d20b7bdb6a46b628ff8bfdb6f93a794404cc21e53df3107e1d131b6e20ae72191f7cf9d22d660c77ed8a386fc2ec4e90e5bf9308e40433e3e8058b14d50d9bba6b9ae796db96180fcd05612583eb34432a50858171e403cad1ee96fc9595c6e27474145102b06cadf1dc09cbf066372e2697bf127b922b8f06d6634dd7cdd5ba31af1c2ec3e2faf8600faec10eedbbd65810e28c1d13774160ef4679a4ecedc346c13d118a45edca5ea0ccb73efd0a6e4443337b95c12988cc1e5a3fd6d3752865105db1ee28f72361d7eacaf9b5ae861a180c741320b2d13d9ad8a86b52e34bc07ff73741cde633b3aea0906ea02473e0f47a06d7d84dd2646cf3f5e376a59b9379de1d4d4e7e1475beed24f2a663414db6148cd468da5a622b0b2f9866b69d9fd868e8940611b4c0a2558a4598250e8a8ad52453df728ffa23cb7bc3a492951fd6f4cf3e1df479b5354a94e44ce12030607b50487289df787ee229a00c222cc00dff3add2405b3de149c313b7101df2db846b48f90a6405fc90fad9220c2ec0411457b83e2d017cbf4f75a14a1fbd3952164d7f100c613a29b6252f4517f18514e1bdc6ac21458e75d49b3aadb924a48c6f1708864d0e308029561f92c04c2a78d4f74c9f3583e727bedb5c9302071e795b9d9d2d9b212e2c3ac9eba1c488428e1a3980fb46cf326cc39f200ca5d84da48503bf438b30159536d34ebf6459c79071de5f9db066b925e42c0b373a1273b6217805907e442898c98630656c35dda2a01fa0088d158caa092a7aa287cf5eb051b8acd7d0ac9e829469221a4b35a0ee7b6c482c9b57005dd54d2b5a5999d94a317189adea37fec8b5bd8a5c9d164d3a8aa22e8adab1a862ea245709c8bcab4ed1c2cf41b61834a288165d7a1cc72661b2d53f09ae0eb2a6dedfdff40632a7bd7d37246457f098023d5c5497616b5bd91fd597047bc3b7bf94ba3cad2ee12b0297a2aa88e6c31e4fc0be89548eb25a12d88bf4d087a7e9f61a57459f1515ee30e621d678b19563e1114e2503e6a707bcb284f2304ef688fdc828f4f2d91ee827701b28454e8afc24197677795f387c8f73c3a56f28e41ea9781ba6f02a332e1a276c33c0b58451412679bffe45a83a76634dfbc5796081985ed9c8eb63514f79b6019954f1e1742ead43b0a790284a8625cb3c6db21a9a348bdcaff61292e22bd6a84ac2f9b0be9eda276fdbda632c7abb489517ca12f31b4261fcf62234493a39807a88eea678f494f98bcba455635eab6ad19c272bc76ee5754701c6107ae2b1279a3cae55d5985e48655f6f43ca1a2b95434932217767024b5099a8abe34bc321056a22171fc79d1bfeebd01931b29490e557ded2c5d361960743b3c8531688468f3a01bbf70382d70b2e693d0ea9ecb62bdde77326351a7273cbb9731699e7e3f4fc3474fe7a6a76fb0ea2eaa914a7dca8e9b8fb7b31d817bfc12dce865e0a416941ee72812f5dd3eb7702d28a5ff40553b604515fd46f84fab546f966abd233b8bf5de9d31cac052ed77b26295757cf63b983a2e02a3369a1bc948e2ed80d7722fc8dd5829e9ce74af6101a3f1feac167e5d37920e02305c1ab435b0322a700784aedc27131a353621392891dc4912e820d1b4eb8ac690dbcd239adeeddc96677ef161892809da479294302aca12256e361ff9eef3f858ab3af147fc7f09a67d0d01815d241b392987563f3a6c2a2d156fb71c59d5e878ef824c2cd47e53369610f4fdb04c1edfe2ccdc0c1f1827ff5793a3f8f679820e569d4f2fa42e87c625b5f4aa6d1a4426ea8115e42036fa121531d2f1c43461ffac93e715d4876592a59ae3ed60571f52af5568f53f2ac4da90f31e42aeaab4839f9a8260597b37ec3c88b915b772419200b786e689f5ae9e9914aa77f226622ae2b289995f3d173a15ddc599ada13b6b32a72fcc416a826e32616c24201e70ee82bb0766382328ebaaed82545d07f7a8771edc9049a082d55024196c65bb8d4e6e5c6811bee260cbe931d95c5682203f20ecdb5b9223b86c6aa28bcaf06551475ea685967ff508c46997b885a0cf70361c486e92bc786e591476d1969b37966f03e53127c16fce13749e224ba8dc4dca90f16d0143efd4a6077d1cec8737abcc9cf3cca6bea45c9f52ea65164198cf77bc872763b58a646a09e8989a672db90e5c168f8c6cd204d999b36208b84ea4d695655190c0a3e295e33a805e774012e512dffcce3838d1f5471c24f0ce92571c8cc6e57f02d628466aec0ee3a31b74a7026153b7d95cd23f00433a0f178e214af978b80eea06da5a6a47449a32e2e126be2bc51deb406756cae9dd9becf078a0e9118ef611d0efed777fed694f6b74656fb169e76915ee032263363784b0692266c8be4d6b486dd95d6d2040ef8050d98626624d4be797aff0804eee9a67ffc22794f4de542d8d6616a06e6a2f1d6599876a6d951c8c574527d4ddcfe30af32edaa16816c1ff1942166729da2c02e358b8bde5cf0739332c89a12b9f8a455b34866ff5ad2d0674b43903a342ba3967362e2a18134d348b2618d8e9778fb04e39b1d214a6a2a0070855439f14330410c88880b501a9db7b995ca4606528b99b63c5f174e5da3ca28663361cee38d19df0b0e128981abd21f59e138c2c8bf922abd9de58f2f11630ddc819e81561eab4389e88480adf36259f3f812d3fae7a2934f25f9a933fdea5d96a9238aae2b0f8fd4c39e5ae6ca32d97f199a0b32eac2d038aae54cde3f14dc8ea8f8ab24ad65fe4bdb8b50ee928a04146b7c3531883468836a3360ed2bdd4f0ccfe46f9747b511a4093c770d0f83afcf42b2e0b7d67c01394358b9730602c152417b382633c5695e7e5708ee223f678f444625b53fbcc52271863d4461e4324bf5bff6f23c407a1cacd5d162e729c314b53a1f7f3e4040a6c5d87ccd479440983b089f2b9c87c3f1a62764f8800b4038bfb774183019ffb11fed17881a2988b117efc074c134f850f634450cc6b757d592c5dbb0d6631273e842ab8c40641589d418399cad9918eba94e12db13a1d140830186ac298b02e82d9ed76222b4a3c2c5f460b1cde70049d53df015eb6173cf6e2c8f370916fe7ed9be1ae5c9b236cefa3fd0a54e8419b83081508de00de0591bd5b1f5af4d9b156cb53d40ad7e6da22d939e9e7521e552b04b1b4d53bc158d49ea1725e70a08d5686ab41485efbc3a11bb953499dec42c27106fede58dfab855ab950a6d1b036459b85768d2cf8522d19e37d14581e7eae212b6b7bdb234cb81ead1e517410094f5cba5bc1749ed10e1e69c6bcc79b8f3c68c64765e3b024a39debd4b7af003a92598103b8448d39367699b3f1df6ecdcd0c32966622beb8ba60b92d3212967736a255e50d0eede9fdd9425d9986a36f830be9f9393678882b7f93fd871e546b2a2f509e7bf856781baf0b8346bd9a8cb7d5791b3fdd1d394e48a1312b00d783498cd6f15b4ab5181479102aa9ec3262198603356d0162afba85817186d66b9fa4783d2e726df5213c2aa8f20884dd5a8cf17086c99f9a0f04c87eeafa7c53cf33606d5e45ede3a549f3a454d75799e3fff071fda76c09c4b7662e1a6433a533c1ca800122f6fd1bc494721cd1adc042dbfb9c43f17e3b69fee1da17cb22d684463bb8971db144c37560a1bc8cf6f8de35122bac384d08c21b343f806fd9c0cdb32936f491cbc17ea1bbfa489430f1798c5c521aa76b2002af136e2428206ed8a34539cc916f537c57760de15580d1d985c730a78968200559612bf00a3b42acfce3749196aaf7d1da48fd506eb3ba7d15bb442ce45738ee0aecc61ca5b0e52dfb684df95f20d0c3c1440a81df393c5c9a87b03a4bb99194ff8d060b30b1cf82aa5bf793ac2a1f0c5cd81463e64d2983e45560fa4b448fc25a9a3420aca48545e71b493ec0711bafca2faaf23546ed148c25468e928cc9d372798e97a2989545f3b6fcecbd58f7f9196d23c8de420ef715f0fa65075bd7c2f2a65d3911331d55918da2bc68d308e456952248d0d43f2f97a3ef716f3c5b9ab0e3ee463b0cff157a3a1bb3967ac887fa5a94723be3570c3e63f675966c0282d5185a3e6376c2e80c07ffe017823f47a347420112c07da3fc0983aa4b5bf46b324399959f00cda400433de55265c7d08b2728f2e8e5ce5030f35dc18f8bf596b260e05f1292805e9429495feae50cc30d43f08251191b8aa0adb0cb04b516b52451aab02889260a42479af1e582bd105e833867490740e3f3ff814a1176837da9fff19f80dea8f48624b119559086b8ff1251b110de3c05c5bf2658dcc857997a6fa7512893ca9f9e38e765ab1139ad22410fa90ec82d178d6da5af32a744c93998c9ba132d6e2c3b5c45e33513e9ad9f9f1c173893a1a7f50b3e654b7c39092078e78f869ef7bfaf3941f683da74ce97a83af5ea20704a9cad0f786b35cc109239727372ddcd19ce87b1fe8b83352ae066bbe99b6d708cdd43ca8f8884bd97b10391e3dc66a07dffbd5a0e4c9c5570a78af7a0b85c91cebe4c880e9f735c5e140242cafd7176ebf69fc5ea10b8abf60ca6a317dc82374c9105ba69227fea497739572f6649deb7bc5b5a1c57d8dec4d360cba77cec528c1033030eb351c4a29b69e5e73045af06ecd8cef80669d405e524d4065cd35f59b143773fcaa23a4af2f8f92b1e5fa299781376121d2e9433225fe3379659577d707b246b5c1be4a055cf39a6a8131539368390f0613ba167c5fef1186005c133d14dcaa074cdd472008e1c6116aa8b3f35c16e2da19ed17ea71ec5a290ccbfdbc864bf3cc8a86d6cb733b707c27f16574c5a1750f6a0872dc7192199b49f60ab030feef6c8bf00302bb32d773d7fd09a9d27647ebb77e17e23b41fc427726ca0bcb3b4ee7600582b9faf65d649e9e64e1e8d52e95230b9e8bcd269ab321e9bcabf26f297d1856abb5ba90637d64fe6bed32a0bf32c43d091e8868134af56ca64e4573923ab321361ee11ebd3687206a85e2a93585744b1634ae7aefaab55ca37a1f3f866ae03136aa8bf39eb4c1b27f9f0d68149468a5b6d6145eece24bdcf5861f0b73b739447c1ea4148179a4158f70ed837682e660f49697c657d527d59c1a52a5c364226a283f86d118a6923b002b9f4db9ec9f7ea817044478a1bae33873c5da93e8eeb85a0c86bcbfce11026772788cac7077502836bbc2a77a3d3517334436de0ece702f382650ee228bb68ae8c50fcf385b3b968eccfd447dd16aa98161716b2db7a099d8c9ce37a9b7b22e78c80d6d773b734a81915a7e286a8f9a31b3882381756a630c0c72c3b09caa7c8f9d1bc020192b09527560318de4a059c2d9dda9e9e51ec1eec1320eab1c1c95f738f3dd2f03d50f74807bda0e88256d44084e9bc7d7561d4759729467fc95739c4688b098fe1fc42158cc6df2e2a3c23cbe2ae8dcf10aedb6aa131d4f8fe495eaa4a1e40ffe03fec6da27b347a464bdf42ff30dce7ea15004a8dffa5b636d0d21f4e34802a9bb435df3080096bf0d55b8a39e97699f16257d2a6a77496fab24c891c61d2de8be773b7551c0a1e8964b3a012d2bb30ae4e38ed9406248c9d19e521813fc91753d94b2b511ddd44b841b204496c4fe0002eb06895cadffffedaaa9ccb02e92b834c4be4b2b97f37548bcfe99b46269fb913bbf33630c57a35daeaa6a785b92b93740731d3cdbd7c33e8fe8b68c39ef5deb3bb1789a2f1bd2fc0be4595e19f5b9e203a98b3a676e99847325d861d1608538c201a621a6c03b54889da9111e0d41280281f90b76460b9d67b328a3421199ddea7f92b2a3722e82d66810c45449f6c9257e4b98e6a4a12521e011b3282298110dc5b82d2cd65db6219e24ea3f78d5f57b5a41b8e17680e43f703dc441a0d8765fb0fcd8623e745d03ffd495f724fb6b356b8b6e05060446fc80eaf810c0ee2e0657c18886dbd9f43a62fc35a9f212ac33983be187dddb34fd3464b4163e7be5ee277fc63914956767c249ec6fa3ae0100853937c34af14a39531de329302a9de6eb3ae2860ddc237586c6d8c9cc129de98b1c3bc9c1edd5b4429600319e0c1c7e5604f6c847cdc3d9f939e63b33165d5241813f10493850c644e976125fa81d5fa7524039e8d64e6c8be392957b8da8b8b4410013653fb287a96c1d59b42a0a4e55eef14a8fe808c6fe9e10337f013b35df41a46afc76cd5bf4fca7ba91ec643e1cffeea4bb23e1347d55d30e5e78eb959cf6c639a789ca8769a9aa905ecad6e9e2f204b1dc002b9ff5882ac3e01838640b9baafa140025b9e44ecda825bbf65136769e191477d15a163288bf49f1146e613bf36036b716ff49bffcd13d53b84ea1032c8009918193ef17707668e4fd62d660cb71336ffbe195f61ebae640345ff2771117b44dd0b93d733a14de613426781db2bc42c17ccc4f169cd72d9f4ae2e59a64020c1630c780186518a2fff721d1f4370bf1ba459c307ddc31eb34415fe88c431b1360c3221f4f729680ff7de6d393ec53bfe77290f0f0215e2a5e8eb9f7d5a1ca85e2bffdded0e87670e7d5131874488351f1858589bd8ff8b877815d396095e28f2ec5b6b93ad3e6f916f0d3c3946f9496d9290ef300038522cf5bccdbfab03a49d68053a75a0b5be70fbfc8e36c1f06c7786765abc0a341385eb3e35b1404170d36b6791cd73d2cda9f7b229a34c6ca092bf95faf2574a0044820d75ba0a65f21580e1bc803ceecddf99d013c85791f1e3c34fe0bd65a2b3210c9456385543f61635f7a9b22a90bda4d91720eec302ba738051e252dafe21569fe296880ac36730f728345ded9c44ab39b5eacdddc5a7f19d123055c4ad8351bfb54bafb34b56cf85128cfa2b1872a06bdb4e911fbb5fbaa7f6feb4a4570cdcb748187dd28d8dc579a38ac2af4d2678cbfcc9140a9c2a20f5b8cc95a1ee7017c5999a0b7ef9a4bde833f57134750eb7b160de70a11121a91bc2ec4beaf78312763a39edabe01685f6144f02b30f2ef4d5a8be67e4a2bb024e9910db4173875912af09569dbb563a79644831fb20b8a72f1afd57cabe1df49825f7aa730abf157e2e7fc8bcaefdd6e04800a22d0d776e4c89237605edee335233e9c51cdb42182551f9e8a81d5312d84710d031c77d61780e76429509cf1a55eaff100c47b9a4805c7faf48b7cc938f94600aa874d89f2c0ed0c98a6f69cada3f2629604459a6d32e5bc121522dbefdafedc36a3e155e01eb1890bbd19a9896af8475a97cb0a2f3482619d74c983f8c42500eac8c2ca43f9bec860cb7c11db41cb8b2f133c55b76ac54cbaaf07905ab94d61a417f6d1d9377fe2528068a3cdce41925fda5962c719a3f010f65b2171ecc925924e94c07cb0e28e0a557085155612ab12f2e5c530d8eba0a8442e40f2cc316428be4b3cf6496ca649436daf38d51438a2d7bf53b0dd87b2e57e3e4e994dcde854576ddf86da31e15ce71c35813ef16781fd9b3c2b1c4a72a043aa359fb565fd0b874d2cc61e3a892a29b3f636d0e981f54c3ca736650d42782e1a53c533ac0b9d3f11d3a8b0de30156e9dc8b25c44e4fa0cf67b042f364ab09579325a67fda7b6c88a54264b00ea7b97237ad2476a066e55212686ca3d4491cc4d991bbb6ca4df1b176ce0bc59cc2922d27d6372414db9ce8e1b1bf6360d1da8223a1b07c0d0e95895fb39d61c340d6f247f3b62c9169ce4c09c40df642c94258b8cc55ba144328887e233614ffffd5345ff249d0378ffcdac85801a2e84ff089c42d8ec540f5f0be7d5fdc6afc7b1085321f288e390cfc644347188702b05de34f54f5a5668f607951c05b599b53393fee986184da900f950ae8c7b3101d825c0a28cc667dafca45b7c68aca1a9094b30d54831ee043d879faa77b43dff664b0d7b390faa1f6dff028e9e490b5eb61a84f38c3d3f7d3ad6158e5d5ff5a0c7236296a6c38d80a33601e35338fc6ed5e62ab13d31246b54db228735e945ace98529e1942e240e827baea9603b2e61ec7e3ee9a87d3e2069cc6ceb8004d1906d7a96e6fecb60b0143a2637c4387babceb1f82aa0ae86cedb60f8a5aa009fde67e1c63c7d1829e9f9cfe6d2c03d4a2d5f064b0baf66d32b2461d9fece934ac737dd5e657e3b0da5bff699889ac44dcd87264154cb019ca94bc45eb8cf7ef419cc23c64702ece11205f236f7b64848814a23b2b2c599b8f0c1f35bf97d34fd10e49540a110828d682327c4e7dcff05048368b9ea798d091da14f364074a3e1a7d0b21e09ecab66eca2dcbf95fcc66438557925904980b257287123669bdeab3a2871d1c928300aeac5a3fe86809bfca082942e65748c285b30ba7cc7911f82a2c54e2602398ef9b9e61a31728fe6b2a06c056b72294001f620dbf7ecdd6a8e0a59640a173c8ad0b0dadd77f75fdb055605198acae855f96dfefbcc0c790e46f70ea52de05e4298224a0403cd7ac6f48cba0ecfbe4da2ef75b81d1302c22a0b7b93c90bfc0205b385551c09f197a7c1d3cf742e4230ec9c12f4e7b94c152ac0c0b1b5a2eb67a0a35c458fcd55840fb4efdd47b973c6d2ec254ef4576704ed9ba2841c6c56caf3908ae9051184ef35c28ee756da06b2b1c129d5d832ea4b1012ebd9aed83ec344d24ea8bb93001c457c9ca21b352e92470eda963135349434bc7cf509bf037ac28fb337de3688de8be84b3189fb27b61ff6c62d921e751d539335688a5e93882a8908abc3668724b06fbf8be8dbfb71fa2419010d47bda395a263d68eb75392a63e88d8c807c4aa094faf3c3ade347c10d7ff0cec4ddfb7069c6f107c04f3011e94558eddfc11167e9027b1bd38cbcdadc14db6830ad1d5fd986468949cfcfcd95cf9a896141234b2740d809a207746a95eac13598c2093bd8b7ac3a61ab7915d2851cd5203d3da924f676bac75576622dd73826645ee81a9ad25170c06f1bf5370f8b29b3575bcdb2b9369e906df4df57168f3f3c26f4a9870b7cf9fc762b66f9042e18f57e6f27217ad81c97a7dbb67517b8a32ddc59ebaece2deb97c3fb9bfcb40961ef4371336cd7ae804ba1a789137dabff42bf346363aff9f3653d3d67d3a3e76911811d26a0122c388825fd557710bca64681b82e35d65bbe20634d72f6e27d7291b289355e95806f4a2c2b10c105ad7fbf1001fc480fdc98aa9597f56eb4eaaaf964c7d3054b54dc47aeb04e1562e3b5a9ab8dfa666a2d09214ac5de56317f958eeb8737e1f2881baf478d456593f454f5b06d61bcf16a27d1eeeacba9d9d25714dc1db55febc1111bfe69a9a9b8c40c62ed533f37964ed041412af810a03113be8e56d269527832be87a0c2cd1c4d4d8d336b82317cf39821d5831f86a9dd13f70b261fb55574d3b391fd2078993a0c82eb872284ed243486b04284e324fb6ff1aa0a0cd4c0017e6ab08b3b6701b462582c30c01d73c406c2b2817bdf9c1e658026f4557dd175f1bb84dda06258ff75bb86e3bb6a4a74c7e03e201de1cc15312155273885589925a3de8c1304a03294daa6d74656695db74ea3217d15dee8447aee6fcc3ab58779788ae75ddda28a44f5b7b235cf955be69ad53d86a6f5c46d259de38ecb6f4b5a840c5d0760114bc2701d0d50fc7d3bc532c58480adb92889be1b841d51e84533b499d4314a2265566e4e21ab3afaa447c1422ad73ce3c6524acb9bc510c5446431aa14c90a6538698994c2b8611ffa79f5f1826e18fbbb7cd95c249bf614e448993d9dc090e0a54ff27cb0b3b015a1033f72794d0ca48b997df4fd820ced9ab40b0589b982a39665352a7434d85f7906b134cb7bcf950c651197c7b23cb86de5cfa043428fbe79ea943385a69726f0b8a05928c0573fc7837106d5e6ae4fe377848e999594996d6e180d75d07934dd1c1d54de185cd14e3f6a29d25f599e91ca1c99d8830510c63200f7dc544f74e127ab998c4a911a23a7f8e56bbe6bf3b1edc5f76da256d9934f04687613ded20d13d9d89f1d81309509f0faa4498c3220b03e610bac639fe89869101c44fbb9249263beee46c11c3072d29406c4cee85e7d37b3dde1e507898cbb8e7f02653b2c450b79c8569d59bcd08ec00e87eac97b19f588c7f22ab04afe199e5f98f78b91652f451391015c684aab76eac641b11dd43ac44b179643901f04e9ec0d00e92e2993dcecf3926e9e5939a44aa8ef65c2a05601ef319e2f24820340469783f37d6be5ccfbdf7857b90673e71ade173287eb06048e2634c4928ae44c0865198011c1bd4e34dccb06817fc9c8d129c6079465870f96d08759bdf19842a8db6df4cff03f7e60adae5f9a58b6a0735c3e8d5b498cffe0538f0ac0cdef52ba90152012b096611c1003c2281bbd1c7c4656a042601f30ca4d2998607148b419754a1d3ed87f32dfa93c9c77b4e81a02e6f75579bf12ef608e7a2a38e97013aa14b5778e2cdb31af0ed00fd627e94560ec96f31bcc1832d9428fd4aca0e8fd7b796707bf543a2360532a27d0cc9e5b74b49d14bd8922ddc26f13f217e7e30b907c8352c856cdde59d8ba89fc3ce080698d1f487ac8dbc6356f8ede8a9b4dcd4bd8c49299444442cd840110aa080328693d3eacb42d9c159556759d26e63251287bfe4425285859ed629cfe6100731eba757a4404a1c0b1f3b66bc7460e094aab19c7f212a1d95e65940323a36e2d259507e0bce9e65fca8888131cc60ae8420f0e5ada4fcfaf713f22c2237b1678abda70690ed229cffab97244b0b153b39932fc24c32227d2acbec2226f0d4d681c89386cb0c63a4a6f49cfbc68923faa36accf67026e6f29bb052ab9387522e1792b8d0ac0d0e86a95c74a800316606179354b2794fb0d5998a8269097775e42071a82268285c2792642c9f57f91609d0eed3b6e5015d1f47c72f24ff83d1c37785f9253d00763e58ec0bab029c75e0da54b03cbfca1cb073b48de788391e5c5988adb22d7244d5d8f11f988be3ab26d8ee25524d5aa854478367d62a62314300a518f51e2cea30a5c3a1c483cb203b6dd62ad0fd5a166469ce680cc2494086c9c95eb1f95a1edd4be2add42b0b0a18e0d520afbbc7a046b45811a4d94f02b1eef095788e33d7f071062160c894cbbe93f386a2507964abe5f51cdd1990a529c8b83a1a6d0e203d34e6aa8822abae4de4c7afaa0f01bec97ad046e3bc97c5da2c717831cd04e551f61dd9955dc124cff00a4808fff00366ce92aeb65849c37b1ca368c5126934a31c647244f7e060314c52b7090e1cb5b01772ccf43c37ab13c167f52a0d1da700a55be453d189fb3e83ad405c81641a0263ec6bf5d54c9535c60b2b15513d9af90fa031602e623c26b32db6384a5c1078a00b3cf011389a361da7f54b6d13234dd3b7ac28a7d381cb125d22c7d8ea60a235c75f18f05211897e71f8ff328fb0d69b601e56dd5b8c8280189f541f771e0707e796327aa07bfc9a5a0f5a965df21f1a9413ca1f199b7db3194ca7f7b59e61c1d369c7e984da0010920ac521b6b10fda717b4942350119561c4219052607837fc22ad95a2d96c1ff369aa2b84e9900995a17ba829b4d3ba670a50944abe163e6ddbc809869dd3b9018373e8ddd53145c49184a38924d969560ffb30c431eb40a842e1c5b82e938f257aa7af5d601da02ed7d5e97dc13426c79c0a683a47f34258e0c61094d1a7078bde53c72c61c3acdd244a2873a726edb1cfbac1537a1401b1358ea10d6eff36cf929c40ec645e2eabb224826ea2f840b40e1393787fa5f8c39c80130377ac13bc71012e59eea884172082cfc9011859fbfe7794f8262ca9764f11e13910944b78c15cc490bac568c92ba1afa15d27a051e8910b0809850df23522e829099e84d6b4520486d09a615f0525b57a5d027fae4e5fd331b839457cef61cb6c7fd278775e5b88478b346c6a338ae604f2feb39997a882878453dd24caf4ebb512063f1a98e1bb9d1135d1997c5b736bb8c45285af52eaeb84724160304d02e9c4b02ec48f20db866afad4b144e855847f90fb5190ae54056156aae76dd773167646d0e39a225b88d66819528dc44f042c0dfba6b61d891055f0002b4a7d077500a96de93bf726a8294ea0a4b8de8602148ae9152ed6f8783e3397972e67bab869ac6fa9a841b9b226f6be8f9f445fa94f0650100986c9bac59209303430c999971c068433facc8a1bf68c0a4904719dd2fdfc4bcb71b1b8388ca4897bb60cb064d7572c415684282bad3d601b4f45a32847a2282ff4af2e5fc4f11fff58c880ff976d5f7d7a3375f81c9af8494a7be5b6cd830b2383e6a4415a6883d77821a816f52fdf41f7a1bc3e909dfeb7aa15c048208661dd0c927523d1141e9eabab73cedf1d0ded359d1953b6c2fd69cf565549b51ef040d48687194431321abfad3f702fc7b10b074044d22f3a0002e3bca7a63623869680b0ced9eaee392f2281644d82f29b36f3aa88f5da09886e9006e2466b74c0c7e638bb016fee1298f8193096cec2d7cf4e24bfad15a91146bb4aa8266f6181f543a48f2e8a62d1d7794f2bda23fafa87cfadf18ba48c002a876997068f40686fef806b6c59220abdf315c4ab2264c0c13f824d4874006e5d82da59780c338b6b18392ef7fa1a8119073e5f7be5f09ec10153ca827642c441ddf0dcdcda50443bb1380a0a6532c71406e07fb82bb1f8a8e247e428fad8e46f2d1b404847f6b07820e820fec74e83c89fa3b068c5884ce859da3ac63922ea15d68a2c3ff9c239ab446e520f35fb102c6c09ea3857aaabf02dc5714d984e8768e22f9d2644b07fc5a78f68db27fa492fe57f572c43a38320c6ebfffe3576ff3a263d3c2b7cc6ba29929060ec9a21d9b3730c5cb34798380cfb639c96786e0c5597e008ed9ac973903e1956d8f3088713e0170e6da4afb4b8a27ce62d5e74d140263c817204dbe0c0f1259aeead629a679d43bdaddd45363aa91508584f8ae93a4339e84620eb71b1b3d65dc5637e0dc84590c6d8e51e18d6c4ccf86926cd6a8f780092d53fe130767174a82e369cc2b4ed60998d8fb637fb0187e38303df88fe046ff60fc2abdf5230aa1aadc02f2872b9cf546ff64f5c4b20ea275d867bfa0cc80077d2c896b0f460345ae82d3a34fbddeff422c8faf31285663f3e1d75861ce7d7dd1463aa490a776727086872694bb469b90395694a291a459efedeeef9d188e100ebc669319f0a9b944c425bcde767ea7d0a9688e348bdf63530fbf1df3ce810cdb3f2f90348f0b4e0018c1e6edd1a10e1c69b957223d732c8dece0a05349802484be2f3c59b9602a4bf84581e4cfc18069422cf67838940a11fc6983b778c4b4ba4f1cdd9b5eaf7ae11913c922293c19265ff3198c07d022b8c6f3f34a294a1bbec7dce08d260e8be47b5cbad7b0fb047374cd4a45a6fd4ec3bd093fb1b57fbe5cc78ba3fecf38b17820046788298dc76eedca49365cb01ecd13601236660b7c70d2b703095ad0eb5c5715755e88b1f48ec03bf85379ac20efb1d5f9301288b8f927e670a3ab13451d9349e68300c22077f3d9fe196a16c040bbe0821e1b500ef3b674f929ef9175b263219519e91a7238d4e56fd46704f004b1ff75b9b23d90f8cfc85e415cd3a589fa1349cb76de92b733d020bbed4881de7323a2737270b5450cb19d1b32b8680c6d1129cd00077c0e4ad83dcf0d72d27386ec85e8a2e72bda4e199610fc1fd8d5cb744cf279d4823cb8d3c836dc460c441a0004b1e5bfa629ad7a4b86e39891b019587fedb932590f4cea45221cdd7a5ba49ca12151aec22625567a2be43e5345b0ce2b46ae81431bbd165295cccc0d6ca26400e8f660a3ef24559a43b1dd497cae0558bb1d4293a1dcd4e9b0a41238999c5d2fc7a1c3bb641b5fc72f726dae53cc423c70a0dc8e663f40c347259d09fd0a513ecc56d24e29ad275d56354ce306194b33a775ac19d11eed8e9531cc1f0f598e30cec9cb5c6a145e44d7585a6bc509d71a5d5a0a40bad937a4f513f1b9012bf6993354d76cc93b5fcd4c16a1b1fd25f7cc96b763358cf7ed09c55885b101232b0fc0ec13fa4eabc2deac08a05d8d0cadd0dd255125d1323d30bb96b9d0c086ad2ec7df5e7e699f13e7b46e391e9353b1056239a4acc180a0b88c7317f53951992815da416601d115d56bb2a7c884b27059333708599d528f3214281a570c46a7c3980786f556c486f344ae292b6dedb06047c29f4b8e785263eef636122aa0f8ad39f8ed637f5fa036afb8058990ebc9172a66a5f1d02fe8674033b50b6ea567cd18e2b36f9cce5cb2e80b0c60365fc00ef539e9247f96549e3f6672098a2d5698c1f5627244c81b3ead39243085c97237c182b2c35e07cc47294b5a91286cdd499ee2d66c11d2bc65ce1c7fd3ff4e6d0e925e1f85f68d80cb45767c10da812f230a1acc2edfe5805b86d9ef9d6ad09e73355009a021f13ae0e58a1a36e710f7703caf8ce08ca6ab60cf553bc0af96e880ec41b27bc59a09c4ec5ac453606d9cb9b343132138526b6b9a2dbdec9f45640ff313b2f1e08e9159970f3e27efff38b1729e6bb12a4138fdbac4c1e773b6602f9447cf7f9eb45835ff501b3d252aea4b4573eedf78d5f62d1339be22b4133b18cdb62164422a946fa97003f1b725521eee5b6c60f1edea33da4e4f2cf4bfce4b83511457e5ec81412bcb8802f1bc2fe35f82750c4e4736f5911d4c574440c73fd2fc00bdf353798c98002af52de6f620c259e5a116e79cfffa00541771583056aa3c976eaa5a4d7545614505d7bcc35cda8250b2a165c30fdd2ef08faa7f381ce807db095ede804e0f0ed94ab1ad39bd5003f6de11d26f5d7e4bae01e6bab8b1d58ed420325c2414beb5d63ff64f2a594cb19d56b9ed9f664208885442c6eb5e7ef5874e3b9d0deb1e7ac110392204f9b1dc22d6b8ae83992893ab7dbe6906d7977f1906b1aad3f3d606db70e344b69bf241f9d3858d7423cb9bf3e2fda2d8d5e4ef70c8b6260dc3ac6ecebbe19cb4f2120983cfff56cbf1ec21dd1e1a7a0b7a9dc47e24e33e1c994fa0bc947689baeaaa0afd7337e12fd5cb703f1efeb0293a826ad2b3db157c6841535695b95f00293bad7f81de10eb0e6ebdbb33062fa8c3132adfab40601859131a7ca1de54f905d6b45e395fafa1ed2c4d3bea6b88fb3f506397389792d898c1bb47dc97c4036339608e1ebbfd76b745d989d2acd10244bd7d52fe0169a70b332f4c9131a34bb54210de226891224f01ffc77843ca6cd571f1ae73b8dc6c137ae7ef058164a00fd22e035e3dab27a711a094809cea435847601af055e3e4bdf2a3b14784f8137464e634802a890042cfa904222d937130f07f12695cf0d991aaec7e5b30704e2464f07defdceb36338e0b113351400f8b3b487cb7ef337d3d3de87dcf7bcd6bd952987ba22c373f400da3adf1b59b39c376a3b0f50b66e8f742aaf7d323d7d8464a439b94bfb724908289fc473bfe297784d4a043f23768d159e1ca72c31fe106f145966ce07c7c82be46ea55a83463c010db70a3397d238ceb2ccedef9b1810a0404441d7725002f0a2807a7521d7e7642f49faaede38b7d53ad27930d858196c25dec0112d999fbf799a4b8241d0fc049cf4a6fd2a1af826fb81c30fd6666500a28e442dcb93e6e765440f29c05fa7121eb635c8115d41b5e41f3f4a71fbc24b9f10c83f5391653067b20f58fae793e554851698d45ef3eb0a7fc45a7e03b660fe5beb355570df45060cae1665635d74d9bdb0b43fcc9b6ca0d933c27bc30add670f01c5086946bb5d7db1001d6605a22a0583fe99fe4939c3013bf2e76f0cc5d424fb63ce16b9db783f7a5e1625a6048275a77266be7a1ec2dc6eb41f4114255d77e2e0747cdfbda28942a2ec9e4cc2ba426c4ab60dd56793be5ce434b1937a37faca5ece715ae3fab7f78853b2b37519672ea825be4eec02553dda6bddcb23814ef91bd1e35b694588b310a70a32ccc896b0f875a4655aa8c17b667314fa1bdeeb3a984554151a0f990394eca6d03c444d4aaceff7f59b9552c3c28fc5bce83e25f9fd18a0557decc98ae4c1658d8f996e57391850eb21559053175753f71e8278068cab0873cd958d257dba2d4ef04620f050c258344529adba879a96bd5e09300737f5f104b3123c091c4242ebcfb5b463b4852e98aab1e6e717f02424043b13600c1b48816f7e33387e5de0aae4cdb99b9c136d45166d9500782fb35dc58efd5528be31489ae920af72bdf715848714a72acbaee2348d69621ae121deea958b9522e4f8f62f5c053af38819b718eb5461dc9811dba1333eaacc5469864db7eb8f12aba04fe52f96b71681be6ccb775e846b7d056fc47b90f21f809d8da9a3ea251bb56e511e8a31659c57b04c4fd3d6acd1cf4c382185a5c87a528393c1906001ca8e4dfa791a330bcbb62556412bf6188088d5bad3d62475605fc1cc99742b050522ce3603920a9609a537d750d4068071efa29aac42f55e1d526593f28cd09c48468428d02a6175a372cffbdff7ef4b63578c173d8d72bc44592e6739931c04c32089ae16bbacc0f5a7f7ee5ca45845a95cc848acff2314bf2ba3198b2c5b0e89fa8a87514c33e88d2c9705b2795e61fb7c31b949bc04f71a8a47cb95ddf98ca503be706dbe5c0f794692370edad0dd7c71dbc2a3872429ad0cff7f26a671e6e38910dc341c0f93cbfe05b6181a4469616737474b5946d90f673ad0d00ba0c820d554879ca55089477b54826dea5ceb0a932153eda384b5104b9f9af2ab1b3e7ea2ae8106d24f1e5a96720ddc47928aed969537985c21427d074a430fb403b16243d56511f3fd2fbfc1c522f8076de72d02e892b9f615e4864e5f421fec63fc70b2ac8a9d8600e4bf05405626d97b812fedbab0dd2fa955384d24656662541bd005805f78c2a4290cafa73efd2e062f1ebc9dd1cf4e737fc829901b20212221447cb2530125c979c64af15107b9a8813b89c4bff5b560a97817055899003337952f03d7cf6387f03cf34b065339938c5f29dc45d2456d5aae464f5d8db9a2204d1803ec5929f73659842b8476ab9df8226f9eba7635f9ba5f61a3ad391456434f179d6195567adbfe5de49f8df3c041c745b10025491f4ffee7b92841b491679018a04474aa0511c375e03760d1790b7ce0e8a80c5bf1b5394ff928f0d5d41d27dc8974690bab435860b6936d8d0d336eb5dd47510218c9bdc0a78ed5cd02b65a258741d5b57c7118366db4feb34294768dec493c54b0553d52632ad247f78fd521a04689360a86fbfbe53211be6830a4b6bca248301e9cb2d4ef0e0007857668dcfc2473f6adadf16ac0a1754b332f2ce66d35227ea3b5a1d0a312da7299dc3f4e23c452965d3af390c7bca517c0c3c64c9cb6cbd7f220f8cfe3d350563112f01b5ab0e0512ead5052e04dfd8d05c954bc03e972723b3af31bcd096dcf372612b7c53f7c9cc60055a0b4b96788a5471ec6ba6799efec167476cfe5f77efe09b74b1e8035a5c5109762b26fc400cf088683e8709c3e640c4a89557d181eeabf095d73c8663cf63ee855cb017f26c57d22f29ad30504a15b544549636c69b397d2efca6d2d224a273c06a543860e6a3bd3262a93f7a05fe6108bf844964b156a8af04939344c31a9eec40b2903358eb443ad9871565b253c7f463336cbb63986e743514a93e52344da2bbac2131a6ca0de3e04ca5c2b7c3457c0fe7e619d82942f8d6997b33287a51af251b52d5a7675679961bb5bd83fd97d6bccfd1fc74064a8fe28ca68562819aad17c3ca9b621d3cc1743b017ac8bae3e2793b0a7b8f1cfabfe32267c2a1bd7f3d330923e2ee67a09aa3087803bca29f285bf724970d0828705823e1ed96ae165462de5d387606e34dc79c5da04be66083c82378d2bd620e3a0214b335ef7215ddcaa690d0bedc35848a6d68953d8481cf919f4feee1916d2c074c6a153d848660639207973a1018560630e2a4fc38c1b63b9b043c86c1a20c13273fb450a061c3d33cba4b8109a929a394f9346b6faf25738fc384684d9e8519115f0f6b223e24164244841b894faad611c674b446b1aba283265f01fa0056158bb7fe6495ad671a780df9f0676c3a06f029adffeb8a86fe81169e623966fb88095803c97e1528d18b89819c7716499d051b8d89d9757e47b2e161b7071b9247ce39139382be39afaceddad118721677d83055ca55a6a8721d79490930b6ff2724f5533e5ede331417097f109537666e4ad0bd3a5c9ef6b49b48a4f34fdfe87d4f0a10214ce2d07702879dfc9ba6a4b622831978e7cf164a0a44a4b39a6b61ada882656268e6229844b22d6f88e7e0bc3c7b8e250c657a3d5eb2286cdfa4c95299c590e07d6f5564e5cf24df3be56c44bce82ae26e759f84b54f5ca7348cb97ac68d366d9f860161eb1baa15008002e3301422f947f09c1c4e4a6dfb7693586651a66172469103e4031936d6572725eb481c205122125ff110887feb0f9e005b956a122442d93bea341cd3dc8c080602e1b615dc8045e1294c06765fb1d3ab67cb2e3ee5f2e65bf40853432a4d64a41ad810a880caff40b5d45813f45deb8b03932ecdc09ea8286db4ee88c4dba89c70feac485ec6b0a16f8d538dff9bb32cb1c194f0c3a3be6862f2d5f6ba4943f9048675710a88d8d188f270e80dd68a2f8f8056ee0d82a3aeef6aa57725c0168face139a13e02ac3d6275c3a8949edd52e80d93b10927872461fbb7b1717484635030148ba80b419985a80ddf36bff2a2780ab6821a6dd83a2c5253f772f21418fa67e222971443f04c2b22cbe43aa82efe34f5a14a0d8772493542a913d9cc0693a0c30d8029b71a0ed287205251238d4ab3e9807f1f92f300ab110fd7abd72ba03da690ff2e0eec5fcaf8257ea77de842352894a7c7c4340d1414a26fd706f64c0111e2fc3a7ada6d2d5d6a9f3ce455839d09b292069fa0b7c73c37ac3f1e65a8986c38642e0f00da783ed0df59eb7fee2dfb011767f92683007edf33347c1837af7e94bbeefa3cf81df015b982b21265becf7046ef637c1128209685346d11470a3ea2e3268479cdba49b552d9e3a5978fc369b814f74ff697506c1f9f3cfd92e450ea9e129bae94789607e4c642bc4846aebea64a0198a2f252b00dea9c5382f0827fbe8e9c82024c883ff8a9306cba01f671288369241f3cc3b7b73dd267300863e779230d85d7e4ea613edaf424a0d580c9db58f4a7ace4ba0d82e02ece6dd1915bfe9e8ba39cc1400ce65620f336b40f3280c13b395492cd91096098706145d8b06b7cf5836eaff09234220e8f61485a85d5b5902e6443e211146c62a570fa34c29bfaf14b981b254974e0c43533200eb3d342086d1348e48046a1a2cc86e07dd3fb7a17421a1f8b58956c33cb041e175a6ec813ec3dfc0453d8f8082fb686263cc4013047c190472925fa7a1f0b30445543087594020741f296d7d50038990501271e3954a40a9cdf841fc7aaa84fd1c014f9e7e9fc5e0000ff61b9fb337497d7ce183517dfc70924f3fe9e7ad03103fb967f91ea54c2c0b4beea23d0b71fa20fd3b09e1022ec0e4fb345920ce5928017eddf16a7b5763cffc81445f24eb5f357c39a111384971f85081153414886695e2285913edb8adea217fb6093c92cbf0a97596b114da1a8f7fd29c94c71a32ba36f2f9d7834c5b834fa49b3ecd68ec1ba4b408ac8086c2aaf43142e70ac195e2f307dc7dbfce1efa47b61de9493c4fde43ab33ea086b04370a86b907fc09b3bbb45da028e1a83edfcb190a4cc6b81e18eb8f6d6bbbb2e9b187807a00cd6305bf626c59a8e0d86cce34a7a46be8539ef3af60a5713baa3d396807451584f6dd24c6088a8032c4ac5fb8e6d2db85bae79bb64508859e3624e52792d5011590b5517ee1c3b864b9815a8e1d566783e5b6406c687ae71198330d5fa1c350d2a242ebf998628a8c2acaa771edf83c5e58998caf4948bd06b752ffe0d54f868bdbc2356d10de56aeab98c756fbb511086c725d5b577ec659af960d79d9c325c4eb499d7d91d60d3413b5d60a9876333d863c1a9a6af844242df9dd1c21418b2a9be6f86f56bba8fd6914ff0bcfa5061f381226407b401e510a803cd42d381c141a01afb54e06234ed7ea0e107b4c6c82710ac5ff897409eef31cdea591fcc50199af3bae3e47464704c8bfe810916cda54e2e3029041e9781e4e959680430d4345dc5541e637cdff7a1d35fdc134cd39822d89f78b2216f58bea1efb49faa9506909cff0d633a12f2aa4bf98c3c5711bb08d0c81affbeb0251a189f84b5cc60d67537d0d2e130af96c8037e7597036be3914c6a138a7c53858cc4931ce0b715484e3628e15e5bc0887823914c5a13887853858c881228e8b715488f3624e153d52e80c272aff87634531e7c94467ae9c0d11aa9848db07889aa184fba64e4e59f2c481a67808260d078b06ef52bf5b43d24d3377842cf709058438a369b981e38aabac7467baa047fc721f7d918114191c4872c3f8a63943473c8a1f4e4fc2ca01afda14128a3df505bba6130400d3fa5dffe436459aae65c69451e96fc4af44e3e0abe6ba4853349748455f561ea2ceb49a69d5011704e6ba070706aac52de55007e64d9c1fafb6f19c49920e5f4ca851c0a292d2a25c9c968049371cf1aaa2419cbe4f742d6e729d58e5763a3df11cc313efa07c04c5a4e4b322b3176ca090c542ce797f9466e756b19eea2b4ae2c19aae0a4fe714528b450f1e46157b46b163bb58e4ca2854e48c00825c440d13cba403ae219b00723b4454e2224adb3f50afa4eb452017901548102eae101bdc10dc81359d588c7c6aa3374c7c53cd1be49b9c0206704470a667c89e0a6986c2874e7cfe3d2c153439a895db2a6f1d0d6099e21e21360d0bd96c4b0a5b83550a6d1c9967970bea42a2fa47f9f10b474600668308e7ac906c5835dd5eefe41797e304858a1a33caa0ebeda7800368cec02507a9ba346027aa9db2af9b413fa248b99c6a9bf429a83c0bd1e6b8ce1d5f58594da80139118bf24f9454f53b39126dfa3a74a8d063fa22016ece2de1d1315ca4d82116caab0043b8f00d74d09622029154c10c556d7a9bc3849715b8fa53a57139259e7490504d53af5032e64c74230bceeae163309e2f0bcf508f629231601f7c2983e4412d7c4b59705c0af43f6321fb261bf0b5b4475a0235dac2697d2052b157c6c297bd8b1fc7cbe090485cade0361d13452ecd18f3aedd64f4ac1a3a2c803d354d20abc8fb225d41f7347aaca1dc85415256b540b382cdddd08e298986a259ee56dce18c2792105e7a6f3a9689bc43c88c7e3853c39625652fd17f1705f07beceacb6807e51ceb2ab834d6189decbfda8018a9e9f66e9cc306d08d71df7db9f7c5a8f96847bd8b20b9cb705da0fd1ef2edfbb7d3012f486964d1d84e399c6f3e1ce2c369f71135ca7a75db6c62e3521e5f92228eb4b6e6c682e1bcd7f291bcdbbb614e2d1fb1db1db0981975c6a05623e035542afe0704fd014b5b0194d122fb2877c61422850dfdd40fdfc9a06e84a0764019c0cb26db00013ba169c0c9863b675db0eb98c6793163a640db70877a15cac8f6c3abb39228aa151daa9fd9823c6ec4a51bbb33fb0c6fb367da100d2f4ae0ac2674102105609551034a69034f298009f898c89296f7b9e0420415187def311a965920a479e1f7c3d80db9cfb3736ea0972e106e263352551401b2106e6cf1349bb9d632de87106f9076efb958d91badae7718d5ae5e92a0b1f171c7d1c06d2aa324d742b7f7567721aba62ea8e52bc6ce2948a598e9c195a93ae4a7898493d56cf4fed8a388245a1faa371441590b7f0e74304e72711160ec3d2ab486646be20e6c43bff08a8d1e3101ef8cd9387a33ad733306b50471fdd270976c45eba61842843e6ef9ba4044e1a28de07497975524d53f95035274d4958b8ebdb05785a25ee8606f6fb8888904c4a14f78380877bbc5ec815d41c3b4e145324ac399c8161d44835d1a5e4c938548225311290e54b3c874880bdb4cadb9c9612803b9ba7a692070e97023504ea4bf45101919e6729bff759c4af58bfd17093b6cc4a43d7ec251c6616e496141e93a3bc47633ad547609f7925697de50eab756d847cf47ab9e39f826bd1a374a69ab4ba4c9d4e8ba2ef3369aac7375a3a6f7279eb4822ab252a0317bc9a372c0dfe1f7579475da22f1e18926dbd209a6d22ca6497ab6a1b9acdeed17db5d98ec410f0d46a325833ae09528d1059b351af9c4e99f3c6fe0a28bd13949c0f65620152d28160d4854542dabc3c20ce9a3e1da27292c2796208e219af525e91d8d04b1a010e619c9411c8a48d309fd090f3c71c9909af74a9f0771ff9640cf1869a149654783285428261fc4fb9d8726279000120930744d39ef8291ab427c4d9f7c4efbbc99312e3ed1879646369933f111f83801e8a7ef1f078f23166660d93810ba43dc5cebd5ea3c90fcf4d2498880fcae35d9d5e27007968fe67c8c8943103373980f547d675ac5eca7da0be53002e5ce948f635056a139d30ba0cd01c75d4ecbde045a2dde8154d2eb1324371b91849c1c5a1ccc43a89b3b37261a8e96422f21809a7c63604025eb24d1a2dbafdcb0ca2eb2e8583f37bfb996f5aa85f0d29545403139f9569e98ca93a6d088d1760b6a8b013729cd6f9e8bf7e41283626c4d4ccff5ac0ec79ffeebc9e21b4fd444d33f79dec8041fbb73fca000c0df8bb7ef020756eb0d859ca399bbbf0d8eecaaecf88791e10cb98e2e8d1d17830f68a154fbca502fda34f7b8dc56c9de6ce1c70f386d457c40b741f736dfab9677f2321c27cba5d16888a9ce156a481662d871415dc9d109bbe6cdec09efd82ffa2be3bca4ce8cf0467e86012a2fb7004cd21b8e4d484b625a054176d2d03b57e7f426a57a8d148cf039fb41a014e837a542c149c1efa662a6678d7746255e1b43e7815ef942e88cb6ecdbc8fe9dd9e124744d6e87dbc613e61ae993ee0ffddf0d7dacba34ee821b3845bf0c54f15a5cd14f20c7956037b3b29ed59a0ef70803fe3e7a5251acf291632517e47903975d149d9d014cf61bf4ac1b24f8c2ca1d67065989ebc7ca5bc4f83b15c6eb53132858868707e2cfeaa4e9a9c870bbb103d047c3540fa73dca97352c5dd76cd1af9ae14f1aec8d855644e421fe159ab6fdfb48dd8a56c8d577a5a68d9451bc7f7202175b0cad2ecfa252284b9fc7b5d20ba50a62b3e3ffcbd62f5d0b4f08be6129da5ddd02f940ab7da0f51995b478bf4a68d326c6f516033f7fb3f9effc3915a9bfa744233be262c8c06b312a96cd016913c88dc13f9d69e09a56ded614b4d8c3fada88cd3c06a7a3fb1767c7d00f49b3a0cdb1a648df22f80848b56366e86569cf0ad05c663a845da23e500afc43e3434c890178075a2f9b1125ad1d82cc4fd40a071b4da561ff4b4b10049605009068aea55d4e5ed5239d5ee722ea5e99d943ac39d7f10f6e884c6aea37e15bb80f9c4d8296965a7390397fd90a7be6c8c09c030c469a4c6905ccf485635369959c040420bbb5c9cac40d027401bc0f042a0fd79e5a2092b29a0e1669870fa1f9131b2cdbc7e2fbc749ca3795058990cf755e3bf4a75fe6b5e17d35a61fbaa90a6d6006bb39183b01102b807a2c09d6915194b5898beac644eeeefe6b682fedc101e36c9090d6f6cab6e5de52ca2453020a8709040aa7ff71f37dfbf704fe9019c8a9fb1f3f75178efe3f3c28fb584323124ca6333c328b660c047d23df053c439638cc39270b9d23a75138895c947f61c06310d10e17fb6bb8e7119ee2d6b88136adeeeeee3ebbbbbb9bae5c7477751e2592cc03192f45abdce6eb4506937f652f6f61e4adc4d70a7b5e8d2f672c999114c6c401beb9af38a1c56ce2c415453811c51727b0d4f85205a594524a29ab05984a2453ca0ce690d5024c9b29b35c505d52960b585eb0fc49a7cfc97201964f5dce1964c4724133cd9e7353c19c93b35dddb62bea159488ee54ab132a4e73ce4b27a5f4631d55af5627547892884db60ea55fad4ea840d139bd7b22297dd1af1b37a7dd38ce8acd8a4967b76d4768e9a6b4d287c5023c5fde15963edc3704f5b6ed082d1fa5b408e92ffa2e7dea5fcf8382e97f16149f678756faa12a4dab45a54fea2b45a17e2c2a983e7da566ee5f612c7d3e19a2c0d459e9d35c580bc818f75ce8c361f2c7e6410cd8b32aafa5cfd67d24d0ba2f17777fff5cdcb44bc3a30d8521042d8c343a31b4c8d9144e866da8d68551173490c16b938b26b8232eb6f89210e34a1eb11403892cf34dc584d1a21e1152a680ce4850dec8bef1b7f8c3e00b700f1e2efec823fb69b66a5d18f00ca5b888b4b0fd085ea8bf85ae077023d89e0babb8ac6cabed15bd885b33a6872f702d8d29377564bd09b78a5459abbb8cbe002367600cc92149248d5a13f01bfa5d972144c0135ca47fe3460a1ebb690bf7f48d1cf1a725b4487f5c057191d217822191a5e460207d64967248ac659467c68acc200e1b5242c31cd0f41d5064c65a43e4d32e5729ffe2ec2e93dd41974bd0e5a106a47c971a9052035e7655f655760f4fd95d7a31f265a79e05816cd9c3ebb97fee28f7943b768f7157b9cbb873a1dcb6e0ce9d33d03c902e00818c232e1661d2d474a4086e2ae2b119a5232dc826692ca1f428c686dbaf51da2edb7e6207a56cc119ead1af3cde52fa6248e98b2979ec2d4c4d5d7349ad377770ea55f1bf78e8887c7d2805f94e8f28cb0cc1e0440d5b723bc1a486eca04b8606e81cfa3a1996ef06e687253e45297b98c4b3013ac7415519f9769e835e17f99e3c075554e47b3d075749e4d32743074f46a01a85ead139f4bf2cc176e28620f2dd3c07c5dc1f8fd501b65f80ce99e9c9f46db0f344a63fde64aa44a63f823c9d437f260c5ef158503a8f8bf45535f0f0fcf0f078ccc6d18f90135208c216ca6df55bf2f1eae485aac683253c28fa19504c441c0eba991734a62f50d48003ee024e8ab88841450d4b5021fb91c40b36b85294822586fa35762d6f7fa9c7ddef5fb5eefd6fd8189429d0056976f0f497369bcda2c04edf79d7dd8b73756fbff3890367ee0f08c9a97b6e296f1d38c482ede2146c2405dbafad898b447e129bb77795d0cfbde7e94cba1aca262eee3491422e0eb978a47e92fa2decdcb716c9e9bb97e0099c373cf3a63eae3b3b4c5e15af2c924d76726d228daa955b06cb088b1bc1404a89570ebb593e8ddc62694e1f3f3de4db99947b5bfaac0e93db07a4ea6c7751de8ecc8f3c1c942f03768bee1e52171b1004e5c885329c3051850a2c64ed37a777f00b476f4e07c71632fe091af1728ccbac02c51df503c261b057a96df1fbfe3e09a51f0ba8cffb4e3be88a8a3b5e5e308d8b14cbcd07af7dc9788811a0e88202df2a1521f71531cec85eee2b5d76984a1065cfae23f7153170d0a5062a9aa35ac270118394175a902445d7a04a0e5b58c0830f67445162346713ee26f795da0f5a04c1a35571c41526a60e596221c5114d6481831b907a90c92e5984d1c1164814c1250999fc96bfd0df17904a587e33c18124a05479c286232e31c846dc3904e96004175ef86003d2144c64fd8d815ab7fb444d8b252d984268b14c805d24d05c9fe6fa4133b062031296142031a1856c3ecf8cf9c0e10530b0818a920a6c90cdfff9e2fc8d0a1e3f53a5eef9ba71b63b7d422ea555fe7bcc5730ab59d0e08962c9d431e7ac72968066fb1e60f9f26945e29be7efd995ed6cb2c0a73f6d7ddbf324e8795055425d30b5983af812b28c0c444f91afed4ea8958bfd33fba594e279d3ee53062541efe61b233b97e7624b918e35db64812535c2936538919c643a7491204723ad641304168bb23c3ce5176e8f08d2c67574f7bcf238e79c93d6d47003b9b7393a1ca7102e8de7fa6ea066522d6c23a646b78ab2e69459ad6458aa1a3136702b3583baf1b93c9a1bc2094797c33e076e61ada1afa9c36da4085e77778b60a3e35513829f03470834ae1b332d11b5c2a26dc8d9cdd3acb9a232b5a52283569adc31ddddddd8e258672d27f343ea7362ce39674b4435fd2e0de803cfca6be1ad72d5d66a77e47e8e47eedf68be13caebeeeeee061973ce392db6cdc56caad68d5865bace913d96b686b0fcb696b396cb2a34b6b9c69cf3959bb66ea0e0e865d9366ab4502bc830d945422ba7f20c470a641d1de6fb3be8e2a9ce1ed29a3794d2097a764afbaf77879422d9e6d75a2b923a678882a210cb770f479991d46c24e987a19ca4a53cdd95d2b5b74f3ee79c74ce395759b09c734e6a2d47b71b638c1c078349e79421c2f2e9ec1c366911968f3debc9f480e5dbae9e77c5ff3ae54ed7b90e85677078351ee5c16373431c32d30a61466c89734a996384938d1aa18d116ad4e01419a2d4a4d434e409256ec56d32362b1c191c9bc36500f4cc9973db1a0094ea5049e9d5410ac05c7d4074c70a2b76bce039770460aa00201300954e1500ad127f4035e472c0320094bebc02c002487d4078f080e5a35662e03907e0cd7034b99452ca517a9de254ab85237c893738393b940031ef48536b11227e4144b079e9a8f980688ce35e350028c0c947f8fd808f2347081f101540cf175a889c2074a48949e80852ad47081f0f8d6be6468ff5f1e9b12203d3560fafb7cc83863bf9460fb07c03381929024c98bf3d4de59ee0035004745d0ef0afcac0d4e7677ab665835563f501d14f093c3f7b74723b60f932df8a0c5c5f7e8c2a532aa361e0fa1f50fd5664604a65db964249231b5cae5409f1a982c01e8ed4674c172cb538e54ef77b82ebdfeb1477f7aba15ad5a0a1f9d68035228823783b740460c3c3f3f2c843c7ab060c9fc7cdb27ba4c4819dc0cf41812a8e4af301cd989765077ef1f01b8fd9f70f8bf868c322f3060a71db685c3768eb03aa3331cbd34f8fbae716408695c84601a8c3bcc5fa5b8fb7f97874528f2da9523f8697d90777884704a03e7d1b1fd0b6440fda5d613d236ea38813a0c96380598325b352f9789f1298fe013ceab0a9fa8068c839c1f2e9d75a3f86568c3a0002eaea0bae151f55314330d7b012cd2a84801c432bcc343ca584fb5fb37addb3cd6daba0672bdd9edaef47e7d9e70fd07e3f36d98ee2b63052c070017f4c33076c9798e042abc225089f01172726e022349d28430a3c73058d82574cbc90c510416056ee2b59f4f0c5861464311303dbf04205185b0106563541578065ac30c114650201dbdc57988aa612f8cb7d85e986255804a18685195a6051c3a289c6cd60b1b5d8358513a256ed861839555eef43a5f0274445371a97442eda2bb0e7ef513f36962024d4981e1bcb4c18fc7a922b11b9be0c2246fe90eb8f5de7d44759dc0222e619ea00d39f4520918344ab3c4a2249b4034c5fe29881c1d414c60c87520faf5add451255a3ee91dd3992a84a49e4b18d56911c76c5617d050845b556cb6d34ff7225b5b7e4ba81f269fee5d5c7b54aa61a615cffeb677cbf83593e2c68871974910694211db4ccc0801554696a9a9da1644683cb030f9e1ed20c27b2bfbd620610663429f2ea465cebb0ee97dbf614cf9b793363fe35c438353d8892c0dd9d500a8447e7ec70de06d26af3165acb81a311cf35d2ecd861eeeefe2e4aa734f4a7d6fe58eb16aa50dfdafb8ac1f3ec6feff6b91fc3d62b06bc6deee5197e107746aa677577ffce91364ba794ca1b2c1955c6b76ff8008f74a4bf6d937683b26bfd6ddb727b0deedcba72e17452b9a90eafb7bb9d78d670fa9c435c191cea9792927ae1ececbe75bc72c7d921ce862cb06abe3a6c3c3bf315c1b360cb6e4491026d4a5bcdd327ad2dbcd912d88db3dde97a1f2a85635432abda37f5eb409e24d8ccfd65d5b0d19ab9e1a20901478e07c39a970e1b116e44982e5b145134a49d53bf8a60b690e7cbff8d085f625f89d7df9ab1d3775076982842be9c4dbe56c72bff6c40cb5a37d4e47bc37c3d30dfeff345e5c83785235f1c42be3134f9aa5cf9cadcc87735932fab956f0d1bf9daa8916fcba3202b5b50c991d94ca64231b3caf7864cbe2e55be3431f98680f3c591ca37c77ff9825ebee1cdb7e694efabcb5787cdd786cb57842d5fb1e67b43f31d61e68be3f9eef03ab0651494f98a39228ace1f415bdd9c8be1b6bac7398b57adeef9beed4d0072ff4ccf566bc4050002b63aa78e3318c873d562d7dd2a7c6dda298ab41d9e966d51630bd922c19d332d278a1e6b19edf4084264496b8b48cb52834ab72347d3b21c2dbab305a63a67ba28dde13195929013ccf882a67774b7962d54e2d7d3609aa472abde0f8aea8c06590efda08b1f509181cdd70b0dfdbf8e3c76c2f41396ccc041b05c81b9da1734e491df4c6ba9482a92ca0706cb666581e7778c85d9c74211161318252944a590c39848211666ece5f3e5b5c7ef37f3fb3b673e2b077894429f135ca56fe6bfc0ca20cf9994e78fad97c78acc1d920a0b3356a469260c0631d039f325158f5569258bc36a9e7de50c2cd3073f482a7972331f60c9a3bce20326524b9eb34b9e5fa9ecee22cf9f34e4e9248fde94603b0652dde33f57576e0f65eacf211561e7b496b353dc799bc591769f931fa7f9716b6d4b395255ee3bab4d440e2b721812d427b1d9c861f453b469a6a06315cbad92e97d41a6e34522d33f81d58bc7766864f2867e4523537b9b56b150e15388e523028f7776ffe4b0cdfb0fbcb3dfbe75061eefec87fcce767693b643236d85b699c396386ceb4ed7fb50a94f080af461c12040ad43dfab81821901dac703b600edd343f2740f37fa646a7fe478748f0fef217d48681dfa148a4c7f3c40a6de4c1d75cf4c0a199e8e3a87be38c5369bb4cea1aba6555ae7505aeb1c0abb61bb487dfcb8486d8ec042ddb3cd96e4fc9ec24fe774deb8cc6dd982357720cd270bce7c3f219ebd4f4827efa943c59654e17e490c2f136555ead3a7919cb5f6467171d63d4a863cb6cdee128709398cc975a17d76ae51a697e8da20c31f219d439f0383803e5ca43af4b7d98cf50ef7dd4ea634dbdfc2f0464122f10c62431ff632c9f4af50a61fc4457a8732fd219d435f7e40ae0f17696d1a1009e4ba903b87da3b0b7f5857d04f40f70cf118f6dc3eb275e4d3f7e130fbf48308f9e99eeeff3d954d689ffaf4a9fcaf752476d5e873930b256d865209289853cd4ea7d3b66ddeb5e5615bbbda6a247d6afda17b6c4dfab80bdeab091e25ed49c36aada145fade7b8dcef9a7d96412758f7ddad43d34e945c3e69316e97fe1b7523fe20fc71d3bb24d61ff39a342e31cb292267d6ce835a944f7ccd0c9e81c0ac473eda4c035e943c6a59ed766acbbc82813dcd2096b834cadc85426012425cd05b84952ba75387e20a3d039f46793293487261317e977e05c32672e52afcd9b1a8a327d3a9fd4866c38893a873e102ac013efcc585c301752a39d19fb9e625a8d664cd2e60d7d8a6d38628e69ded0a7292bd8bfe665ceb9855e733032c0a3d732fd49df6b1ea334afcdd914f29a83899959afbbd65a6bed1e8dac2648e70970c863d53ff2c85695036c82dbff91815d16b796c0fd9220b81d0d70ffa5d15a72a42e229b9d3792035db2bf18d78fbe42c312d7035ae67ac0e9bbefc2116c635f99c13f7fa9c20c5d24a8b2bf2afb2751c1037d858625599527d8576640221f51e5fe13e87a80777aef0123e0fe3be13e17ba8870dd735b8874c2c90bfd68e3ee8fbb927c5f92ef34c7529d9a71fb355aa3ec7f2f9eddd0d5a3cea94454da0df588ec2fb34d025330f3c61f8ca560e6ac0ad5a119f3889eb40f8743eb383704a5639c0e7de3bfb383835353e372c9fce8508047ee6b2d73b419eb03f4bc1939a4ecdf7142274ceb1e54f69013626981478ec6c2028f558aec53c908f5db51f6af65748c13aa758f1ca37d94ba4ad13df5bdca9ca243ab06bc29c91e0a9d91bdd5d45d183a143c4e97f5078405061ea752769f634dfacc502a491d32ba4786f2069dc305e1421bd2e484664c4ed137fedea2e5c06a817b909d13ca5ec614ae0a035ce6853e03a552e7d8a075fc3ba319d2d2b6843463a2f8df6a2d711b12fe0d691a1de5f1338bcbf860b2cb5a93f0149a3317fdbb60af088f76559b3f33e6bd8b6f3175064c9917be008fd687ec60b2fc94152cdf1a61234e2f739db32c5f4a774a29a5748e465635dc99f33fcf3ce71022f0e85f7c41e4be17ba8878a80f92852e22f253a063993792d6968677b895011e77c61da0cbdffb20d407c96a44ee7b610bdfdf9f2011ef51610b37f8c26b334618f5813f037c29d0082f609c5db399a062961d0d170d108327d96b5e1c0cf8859705ff90137e82581ae09d490b1264669a4f7fb2bf7ab970faeef4364fcee75770ce8e51d9ece427a15a93f52c66c145d9751cf733b953c8828bb295cab0520695e798788c0256d0c489294d32178e3b3b79b447799c445ea644f16ac07d850c2f799c44998c89659c4259be1ce750962fa514b42c7f16cd2859fe14f258ff4b078c2409cefe83ce91ef5f5a47be972e59cebc6c912b20ed62d7016d9be4381fe105dc7dbb68ed07a45becfed2ee3f20fd2ac2361cfddd5970f1f4a7772f0ef350cee690c34e217631e923037b6f97c097e3565df86fcf811b9060f29d23098452d9bd8c81e5f75269def8f5efb08061c0947da1c52d0e7c0fe4012671b16b5f8ef2d860b2945fa6642927d1e5c051660e4cf60ec8dc8548023bf288038e3646d9479b59eec8c0234b4989cbf15ccdd7e4005d44727ccdd7e4a8a9c901d68035df1f90f9e5cbb6e5000493284261e01c3f62224c7487728037c0cc9b3b64a3e395e3549b311b1da7daeb54cb71aa7da1cf529ab1568b8586c3e8ff2bb1d070d1cdc0544eefd2e4a28d8ed7cf1c363a5e3964eee2a287bfe4a26f9140f8357f3f09808f237411c1d15e944086e3836435a08b48cde7f81c610bf287a62c8864351f24bba71a9e3fd68c33a443b39102539ca11bac1a0eb0653560be61d48d817f69c65e5ff3a3cdf14bbff40a5935bff1afb139c2f61b560d47c86a26dd23b3ffe907b8f3c8aad22b48bf26046727596b76920d657f96147864d5f2833c7e43f30c775cf496e1005b1602d8328f06f8f3c8aa65af31f3af21e4cfaab1c0ec9c825c0ed4446b0825b7c606272fa0758145103d80528b01931631180306318c517df09dc0b71a3cd97c5e3c3b4459647f2b933b89a806ad13b47620c311a00a8e238014d60a8e005eee242225b664995c0e51e8c00a306870610a2d50083305144b54968ccc60c10f9f122c18c204494431e092887ac8a7dc49444343780fd846ee2b5c1c710105072e9448d9420c69832daed859e256da728e41748a7b57cb7594db4e73ab2450eedbe4b422044ee54e2272e135c3d85a4609a060d162080a181ea8280114a2c79eb516b7c0dc57c210c20b914709191e525661833cd2c0f2f8da61872e793a89419e3c3011615c53a69ea43b3b1da54b4db20880f71660fc80ba52a9090e79fec9f35a0093021d68f872c511404823490b19484bd400c34c5604c6d195b4d846df36cc7a493a64c9d35ff8a109c56d528728629e5fade71991c6174a4ce1658c2ace40400d8cba6c41ab52431599bfe8125b28d99fc757d9dddd678705de4165ee737a21d3fc45675a2c8f832b37105521c6d012383cc1418cec4939b820ef3c81473ad2b4153179ec51432dfb12494c5c70830d5e647fb1042cb841862a53575c9f6520337b379dd24a96ef56dae5448d4150e0510a49a1d93fda4dbe5bfc5cd88af5e704f3b4c4ee9959d272b4ab63e26cf14a99f651ac423211a5153f026346539119934c849afa0a2c3db766bb952379442342094b9fe046ac2cb0146adbadce91f3a8d229eeec9022930e3df48866e81bf9724af6500ae54012d137f28fb04964279878acf5c9a3ce99b1564bcad03972ca238fb990b7c82e594a59434af9b35d862c25cddbee91010246609ac4228f24d2ac066d7b69bd2878e4b27f8f5266f946acf7168e80f6b6d16ac4cb93e376785eec1ef9605c9afc6267fa48e35ce6a4b9d1aae1f65a9b72ab7337c1edb93cafb5cc7c0c499a6149f6eed2a3b1b496993cf69247a7c7850457fdcfdbe30d0a1e77f0bcd1fa82470fd504cb12ece3aeebbc7eef81f47b1f942938d2162e78faa0532800a9c33db599be942f7ce1560cb61d3f41767866d74f51dc7ed5efbf37d481d29cb0b1995c257cf7f563a0f590ebdf931455dfe021344b710d51853f6fea8f0003bed9993775270f99ffe31854fd758fe9eca9b3b6d3c1cc9e3a1966a0eafb034a81312a10bf12d8be14a197c9fcfd4b4f9e3d79dee9099a674f351cd5d3693b79f6e479a727a098a1a18b674f637fc125d7bfdce9747a20329f3e134eeffa4133eabd7094d97b99bf7a4412b94ebf93bdf7aae02f6c261452f0c021415630ab7fa43e0bf5bbfb23a27aacfafe96c8410f4bb2989722f346640d683534d1130d693b2a0268888bf54fa7571d814fff334fa0e79d1eec8cffc79708bd0c7b61cec85ec8c205a5e0f0c8bca95df3ee7b374452f3bd611864fb31f3293cc2031e9b0985143c362991d4c7842d3818216ab2d4e3ef1d28c970e431775a3113c242e51542441ed3bdcbca281d885d8c750e6a2cd4974baa54c55c56df9eba20f585d41f220353ae4fa4feddbaaeeb4e2af06e4344c163d7b2a3ea03e803269198c7610b1e12b298971f11d5a7c2160c50c483914ca502531f03621a747a1f0e3bfd4cae269f96038826a0a0916b7237018586ec34b3725d6012ba0f55e8504972f772a3639723c1d1fe26c1d1eb4690a5089074e7c8976397edcba7d9c3d1cb5d7778e5c730cb38b99360d092ef8cf9f9a5baa7d2c7fbee37e983bffb2e10987e09aabfbf721173c1f2a9f717ec2c1fff0a338ceaa21e0587390f8e668e2451a1eef57c1f88e73b9bcd76e8fdce1ee8427d7b8ca7c29e4ae57971a4c21e0c42d99f87e7799e1cc5ec2e1e719867e7624694fd59b85429fbf39852be5e2ebaf7de5b87c5bcf79ec354ef85238d07becec0de8f34f9c39709a91f28140a15feb8e8df02f07b31322ed6ea93c880d845172bfce19915eb2d8684a090827f11234758f0d894f9b35632e28e122fd99f07cf92a1ecdfc3656442171099653c55e8427dab76c01d28505459cf7e40312f3f12f0ab5e0674a15eba50dfd98bf1a1025da820e18f8bf633618519e6fbc6e08e1d17c3ac703dfcd16201a8f072ff92132504a61f683ba77b0fec3aa77b0952a9d3fd0537a9d3d9af019e36af098f4da33f34fe2f17a5496809080813264ce638570dc53cc11d32c0434ef809b2f21a49dc7ecdd89451bae3453db6e34ab1aa34918af8785ca4b4698f067928f1f28be6e537f377288e25f8c7771c267db83863413cfc8162d5034cc7d7df00f14229f5ebd7004c63b96ee1df31d24ec142dd8380ec452c0d478c672b4fa42226a5530a2121d1041762e46e820b30b9cb5b1e3defaa004fa5fb2ea9a4d47441c9f47d15229ebc44ee7fd753ba378906a42c80dc49342ca9d5db3ea0130de92b0268684aeebd7e25d81fb73722ebd71566983097aac0fd49be44a2512997a4d2d33f6981eb266916ee24b3b878a5032516eea53cdaa6b8e858a494ced9a4cf283b15a8d491445cf4211b28f3690c3cb69292142bf933a1869bd4417251ca50d2945c4472d125924f6b1993e4517efddb1f90514a69c4f380bc3794579c0b3d4ba429699c077ce8828a29d278d2032c64feb2491eb9e82d955cc4f3955ca6072c54694869c88552ee4bb9e109577a6d2fb52e6858115a927d0e252941729f3efdb77a25e9855af6242547524a8963c90ff048b3cba58d8935c3adddeebe49f711c4c52aa50f1779a60f1e58005aac5f3bcb4ee7d41d4678e4e1f1af5e7f05a67a48a97e8a83000345971698608623a4908d387b18337c1003252960e85286ccfdc91854589b3caef284e539935c9821cfef29f26cad56414a909098224c1542c84045900c688083232f6088428829b229676bce4994a7184879ce39e78c89028fafd78e2d55a481945d47764f4307d9c31c917d3694dd5b45e1ad6870e4bec2451350ec00f7cbbfdf0ef85df47e758fa42121a594a98bdd9d43bfbbbbbb694fd0768ef47e1a8fa594be4710262b40830947a3c83f7777ffd9d27398ffcabda919b76db750f577a84176e8f6939fbe76ee5abb3616e930f9b6c8288ac3bc3ff2a4cced34457e537ff4505b97af7fe08c0c054a267e530930ca26b3be4ba2eda7d4fa2258358c8515cca38d7ef2b7592d52e5ec45d030a9a4c5aaa461ddb30ab6ec9326e89c1a255759d40120f5768081545cb00c533826a59a73cee92a99182c23232323239f26cb7015c6ed086c290f942dbfa1ab32b0a4db733fb9962e86ef331851c97d650c2bd92677920f48e49bda7e024c49e8e3d504ba482db5dde94f70051264ff215dc44599c30bfca92b58cad0a5908ba2d852c252a86513228769c9144422733f53bed2a3b418bec8136c9a0c58727fb2ee80e67a29dddeceadd65aebb60209469a6c6717f69f402246e0cc852d247191fe90131cf6029648c8590d8174ee9ebabaef8c5da45d6749248bac78ac65425cacdb06244828c4ae90caf6691c46f323add40374ebd0bab8d1b4d21014889c3081148a849ecd429278e67e7b1f65e0ce3e3c46654180369ac473ad78d5a2e192c85cffb32ed688013f7d893b78d82db42bc0a395b4d30c22d05208dc4927d892f1bf7f40b7e96c5a8e09479af1db1f7e662c8595ecc2fad1c82aabbe3f8555385aeef153a098e7fec72d0af0d8349997d907c7fdbde0e713f899f5a3c53f62d57faf7aff805496158e16e370c4f98b09c756febe3bf6d337dc7ff7bfc8f2537f5b0268e4d57b961320b17ddb49f503f5aa14eb391c8e46325681e32aab5e7e4056906132cb7ffb16b00abdb30a832aa84223ab9cfa2f06ec9c7a16a8ea9c027fa0deff03398ee3b8d4cb802af8a7de03477faf3f13fc532f637fc0282370cb385538934e4025b3feb584b957c19ff5a3ffeae7ab9efb152bc4e0676b6362629e7e40312af033f72d607b950a447956f5ad025f4c58f553e595e09ffaf9511709f8f6b70ac7a0bc7a1538caac7a0c8eacf047778eeabb73621effcf8c01bd5f81322a9004d5a35e158e4656f97bfb9d7f660a6cf00476e006567934928dacb2ece752f2fbe1c6d856e7d0ced6ea5bba18230833c0d0ea393843c8729c0482e90c1870d1a176a08b883580114b18c96c68595c3a1068fb7bbb48dea411700ea1f485ac85ee4920a794014656657e90cc821d4877401bba6c0776a18b48d73fd3dace5c338dadc9e282eb8ff6b7d0482b4f09663b37ff986ebf466c84ad749f73ce20597f403a0e0c9251a3548ac5c245b80817cd29f3e8c934cab2b692130d711cec3bcd25ba447de33fca196a7222b18dd34a960ee8d836345b094ac7b6a15a93464e6b2a1b36308b353313d36a6d2e1791211b0fd97f9443db50dff86f3a748c1b2515d92889a45194eea9590a512d952e6fa96e71d163ba6039243cc155862c482667c0021d886436dc88e68d7f906c1b9a3741320e0c927553b024f29a8b3e5d736e431eeb6a1b51148775f6df8e3624b6a2ecbe59c9be21d1362cd9a9bc440e1b6badc8e9b792c3c656a24388780da3a3239165a337c9f268369bcd64a317652a47b7227d5c882cdf87ba2726cb6fda317082321f909c447969de30e1f0b08b88150191cc8676dedcf04edff6f754b75a7dfcd45ae775215ec32058e6df60065a08dee9937f4f92c7ce4e0f1e9e954c0eafeb5eff3b44514655e359d0752022099a8c0343a822f328948a94643b3668324500545869927160cbae0ecffbb6bfde26e746d36ad56089e0dd4ffebd7569c66c58fd6fede230b90d9d68dd33e4525ca24bb4a5b0b41251ede2a25777eab52ec990da2849500419e0711bda7c6e431b158107119c3cc123cd336c0cdec8bef150635096406041a526e340d71432a31e4c2e75d8a43c627827c8175af6f7b12361588afe8eea50b951d9537de328540b70101751d9c70d580533098acf6ff9d4ba30e59c7352ff5182a067297fe5208d09327b08be80e56f3fd2d06f39e79cfe94524a151044d35cfaf89b00896cdff39dff63b339f2fc3184dcad1ab951b9c78d762f20e097f4d97ebe756765cf913bc79f0365ebf85370b68e8723d7ad63bf5b67f4afaf823fb5e10e17a787481e0079850e9346dede412ef707640c33b7d9cd8612f4ed9b96b7b03ba7faf65e7db928c39d5434cbf447eec64d18cc8563d046430c05db3c6b4cca362d8c8668910f8adf9642a4bcf9f2fb65519659e6241fa8e46a420d2d0dbb2501dc493e30c99d3b5fe9ed289758809039e0546e2c615cf9a33cd2e4242846342d59f41cc0526b9247afb1d4867896e0913678432ac19d94c46482492a109de4da8871058c51ee57cf394315e89fb20add33ee04f58fb49b862309e8f78fd3af60093a552411666c214b23dd259320d74e7a822527919953c148d76028d370032f7fb4a7fcfd2da06b30943bdcd9e4ec26960062e6d986d19ff3b7bf144c7ab284080d22db73df1f03b6e7282ee2c73f88ff8e3f8fff9c4e67e03c2f7a33b6b276c6e4b7226287e7456772fb353917c76d398c28a534d3a4274899524ab58ccdd4e50b6eab222d4292c920c82078cbee19b83f22b4084936df415a8424eb8f888b31439599ccf18ccd198451761c4f543652f303b281c3514e507584270b625ee69870b40056bd7f24f416b9bbe00f7ad862c6aa49c1a3cb484acdbb8c66ac267497116d56ed86d18d5af7c4cc7ceb97a60e219c21000c758e1b5d571487c9bcbf6b4af7c4787667a8735c87d6397e9467c09112d162d54250cba3ea8dd4a19494101ef489a205348f8b542845f5ac9ac3464c14028863718654500122207440c51344948005773301cb0b18161a355ff352fe53ffb782f43128f36d026ba6e01acd37a6740f4ff6bf189c790bd30f09d0253c89063d28a51c86552c1d3236b3d6f78cd9ccfeaa409d8c6f2a959a01719ae010a57086c054d6519bb1cfe237fe595cf49f015fe906f85a6c667349cac6666623346336ef6f43e4308a33b4b47a0ee748479767ea9ea52d0e4be1942afc2cafa5737474d1d16483831c780001072c3356f336886c5e3cc9f203195c9052384d885221ced04fd9028f9f45caeb3fcb2b394cf5feafa57b5231dfed8333933afe317f55e06f79269c59377a06578e71cafb1a8e532a9cfa2933a623acc1d8e6a74cc9d6626c06de7e64d5b691e2ceab11038e5d1e3fcb885334e21c657fd668f37c0c7adec262527291a5c5c5d4745697eccfdae2a2bb8e12760a96614461628a0c0e11ce945963e3c80a0e920e96ee69a97ebc19e749fba87a3c80718e689f1493aca3f6f706e8fa1990e65ba04eb651f32ad00336bbfecac8c8d080364b401b2110956d66a9acc211c299b908ae1e83ac9f608d1910bf0c383f05229979995781a96f8139b906a4b216ce921970e5038c2314e2cc585ce01167c8652403dad8d03afe36409b590dd06689cd0ab461d239a8d7515b621dcd988ef76721b1bc66d451cb6e63c4bd008fac283844dd3384d3c461f8fd53aa97f9db9da6e751effb84608ffb14f0ac28ac299e4ad57c4ac7a75e9f4a81384b7084409c195ac773bc94149bc799cd984df84bf3c6ff975aa98712d064393e48860344e389d7e30ce9d0ba67e8fec966d6231c758e3885cdac66a97370660ec3598223d43df8fd637cba8ea5ce711d429d63a3844797d12fb984e68dbf8e17e89a81ae21175d4636378c3831186f1e716636b39112513406a45ca8246d7655512d11d50800000000e314000028100c078422a140249ce9b2ac0714800d839c40724c170a84490ec3280a4206196308008400008c3184c086b602722f905f137be5a7470e736967ea9711edda4f1ff7498eb1ae9a57449fc9cfbe3886807955925537a29cb78eb8fe54914ab60b88752af6955eb0dd38e6709abcbb9ecd67e24015b2e91bf1c6a1476d60444d8e971a1b9e6ac03911fbe549b3fe9ddbe3394b23a44a32e9336e4ca5af622193885f578e5879b4cc87882dfa9faa00e889c1a627fcb878deb74bdcf884acf769681f9c300b4c99345871bd8ed270d38fb3846eee4b12dcf2b8c3ee6ae0502c3e58dc79e79a5c7e24d0c4630e8266b2509f6f2113cd082f46dd7324c1fa0ea3f9302cacfcc6f0af2e6f9f45080e1035b5480d74986014d0bb8453be0a2ddb9949b1d37d4af98525af429313472b326a03d0a0266a0589c50892b435d2121f1f1ec8866c9d0fd5be8c14e928569dcf06be18db13ace3a1c0960e4bfbb8589956d2363d920dc99e796e0b87826985c2d4fe3b94d30db07ed52f9169215b7924b670ac576c357a511e11f97044640edd64af70089a748243f3753122a6722b4fbe33ae72f41e2cb7fc2791ecef5dd09abd80dae3a458a697558c4c9ce36d2cfc301d2522041e93ebdb76f629cb9dfc6e13891e84b3353250cb97fde514104c1ad7b86ad56c59ad85efd4724527487cf16621cd0ce9145cd0826f4554e0732a96b69f2ad0fb4ffa1048e946b4aa4096702b5d28e6e57334cca72c2f22bb4330b9250c10373597b16ba42026ccba9f248e3c66ef01c47c01b384728249d6d26761ccbf2b6c52c4d37bce62a5384ef8b1e3da751288805b3e2fbf4de3e892450853a0806d1e386eef69d39f001c795b029cef5bc29bc6e3141b683a9fb658502ad6f5827e8f4e9cb447a30b0418e3ee4763fa37f8d9813296a6d1a61fa1ba19cacf00b86825dc550ddad46b7907525a96dc7fba1c6dc4e2fa1d49945575498e447e71db73ade13b3c30ee562f3f7843df1adb82c741c80bce996ac33d3e2d21e5cbbacde446ebbfa23faa7820840adf6c097f842bbb3251d7475de715542c1cd96d980be1cf25985b8b2e9802468be58de71b624239a3a4a3ccdc1533e589b6175e0031c054fe61166cc92baffd15d57cd9432bbc51449090ba5b04001ce4f20b833de56042224e0e145be7daf84b46c6d1506f522a0e90dacbfd0927a76fb8a4e9b1dccb82379486bb0605f744c9aee69947bc9554d288e2cb38fee432ea106e399538c9aa0584ca5a3bc953c972b019b9525c72e79a6d4588d5cfbf33fda19e03006ebd1ca0e8a37de01966a0f2156c78fe09934215c1459d88f7667cd37f6d9d30164b78d89f2269a87810cc979fb19aa859dfed2eaa092d885446c78aa8b31b9293918c90888a3fd84738c8b535259ae375e79405e70aa1287aa2ec19b55abf696ed512299b3b1ee8973c069684df9141952a129264989dc54df7cef54f4cda4894e6a14ff817bb5702729e248a89fcd66fac019a66fc8a159536c17d5051eecb0fcd9b96d03fe438c182d008e6af5d78219f628e0e3f9ee9c72f56a5658d0d8b48ca8b084b31143059b9486b9033704b291204d9072679c783482242e44aab0b118d8c2a3d6f03197886fd429de824161a30e7c0d29ba8e986a89634625cb7752002ae45dc52ef80adc5276f3f241391ec9506d4ae6a30b7cc2a6a2ac633d69b8a09074a178b301fb06bcbaa993f9e9c7e1d057b192d4e157e76da3cdb744b56490dc6eb4f4adb5efb2c12cb1eee705e70e131855bf461b67ca8f4b80dd03857abc75a20a8437c805810e0809ac575717501324b23edd4c50950622069605a22813816d9e9a81bc65109b395e44024883e5ddd3c844159204396dde181ba210110d24b6c6ec9d6f974d6537e6fd4081995dcaaa044c334f5ea78107de9f8145f89e4ab7f63c413fed1a2df7c239af964448d72500c501000aa1e78e3c4884c96d1abf14ec8257f9f9c06078c487d9996b9feaef631426801565bd423ab942ad2ac5efb35aa4f117c59e675aca388dfc83775246c0645ccd164a909897d9fb80338fce430ee19d9313bb21360b9d49e3c454d50780801480f014e3f09191211c5a2a06d811d0911d4a842f9ebbb2061c387825005461dc844d3f482b6d4d03409dcc5d5f4a35d8a4c6399d2a74ca6c5b58ea00a7b867651f16bdf66e6f08f10991a910ec3c6298c3ee0e466fb8bb36a6b2a92561ae819268977e1b7276eb1a96b87eb27f15ae87f3396676482ec9b1b2dea91b2b307f562c46f5c15e8a8260d9474c6263e0f297d8a7d2089ddde27d640bc69958de74efab8e14a6c2fcb694f30c62d7348019e5b9cb4c935ac0b4c02b6dd53a91f7cab87b562306b8fbef173b6434d3f8830baf78727c54eb4b10918992a2b1157835431902a9d4e9f75b60a6f3cae252eee8c66d7c256a839dbf045b91b491ccc3ac91b97b78b8da9941ba5b89ea729d9f7e4ca31df6dd25798cec1259dc4633aced19b3757b195010210db8b985978a68512e83c20fe9580cfeb6ae8a04c243ec404a2234af1ea46215d22dbce5a55fcb1ec636dbbbc8118d24d9127f1cf4b5967160d11c2946a19455ab38452f5edf182794040b21854105ace0e4223b3950ced401f11f90a9f0e3226dfb71947a02d7672eb4bd0c6e2f6bf68bae1b1913309056e81b01fbc55dec02692977b0e7c9fa9d522142cb88289270b7ad74aad7b890e28e17db6a434f24120f83f299cad2115e62e7777ef652c3caaee4506acdbe8d412b09c617b06ead72840de019b83d44d89ea5ede38583cda42831c393ce99b8e739e03a2ee05050be466f49fcc3a08f9f058913a03d016dd0b83d8332eb70300d8902aba176419af32e7fdf40337c6320f8c7be328ca2dad08b2b888e6ebc0e6da9e58eff4036f6e29a1ae93b269d03f5152a40f7b3631b8937d49faa31a05266d55772726382a692e7d4e100a7ca238736f688fb752d6d6ce7dae6dee118dd5f35ff824c1be089e799c5f4344ffcd96902df5680a2b2662915378c39660355c44d5df24e8e44f7176fff91b71d4deb3912ec939491cf05b768ef979bdcbb58db7e1636f7be8c931f67ee9861aa53e34c2e1fd99542ff791fa73b31fdcfed08d1ba8c4b21b760ab86f4e1e96fee29cc95b8a61a684a3c02b1b0c3b044c3a9af8f0382562fc73c4ab1008f889fc98df2b4efc23ceefdf32010e7186d2278ae257c3bcab35b86bbbb0d885ebd64c4cc80d379ac178553a1e35cbfd17ba5baa9aba83ef5a7681c1edf2e269cd775795c0d3145bfb70476c42812ac6d34fbd7610f0eabc921d2b3bc65e5bc3043abda685a18751010edf394ff0a02953db571599101a63cd09391f0d40af4c385e2741e0ea9d19062beaa96bac6d18f49e2bd1edda16766e6cc173f884699f682b72d0995098d0ed9e628dbe147eae9b99e6d20e41cf122557522d5bbf61be49b0cbb2ca50cd6b9c8bd46892c14e082b4eb3ac18550704a7fe94511281dd62e9cf73b9e523a0c9fec6083927bf5f35d570207934079894113cfde37703f702ed24dffc845015eed19e01d3547ce3e970e7539536d81f304d70bfc49eb52a7ef068aef7287f018c9c37afdf4ff9fbaf69544d574ba9cd80f8a0f152ce75d114fbe111b41df44c44db6c335eee0f37b956b67ec0cb5953a45720624237bd5e3392273b4350d0230b94fe96682c3016093895dd8ebfff25381d1722c02cbda80f10f93b00008ddbbfa90dd6d9989bd07ef52d8977d424062a41998bbe4172ad440ed3c8dde77d746f442d78d97d4891684b6e8672511d843e8cda0c6dbc659d123ed53cbc2f4ac375c107cd0311b4cb39adca322db1a89f42c296b7970638a3cd88e0db425b270baf645b2c5fe02198643bb371eba8e5c761331e6c1bb92eb76f312e406b41a47f187c694e0af7fc2acbc23d737bafecaa4a4c365c755b78dc62157a23bdd99b3d333dff7c6fa402efa8d2d03bc09ee9be0a1a9ab0c0826ab449b7bb80e8834fc25f8a7e95287efa2fc9c92e8a7e557caede99451e7ba07e8006396e4a950795f29e395903a7736dfe10e9029de557e933e5740818b860437f13d3ee4f8ddae8c712a65a934cc9bbf004b8a695e1a31d2269f0a9cfcbb63f6f840e470bcc02738864eff62ba58ebca13bfe1042370734f4c9ceb9bc0533bbd12c863cc9aeae7fdc1a7be5bb6635657eea14b29f4c6768baaf9a19dcea6f0f703ebff97e6b3935426e404fff442e4a5592d76dd09848953da1f6d6335fa7587ce91e75b67f8a2ed39092b1e5a763e701ec82ecdb3d00f0c30322f069cb6b07401943aec782b47ac9eaee009f76ace58020e245c0d26e413268a823354b6f40fe3e433c09138446747980cca80271baa726076aa80b67b0efef9fd6fdd472372eec55b8516b569162196378dc9d3655ae1855224b0061e648f7369a31a6179cc7344aa17e913517d1ad49cebe93295b7e9520549c7896ec098dd535ef4c5ee36305ebaf1b964f8b66d8321211964eb46ab6d271077b19562625acb1a6efc17a44f76f5356d68abe56fa8d6c646bb6d2f38dda42b610e975f0920aab86793100ae6e5475e4c879ab7bb69e0a40034d9c5318012059320012de20bc71c6549f229e308ce170c8c4268443763468528870c424022d64035fd6e6e85277eaa195063fc371548e57358c4d5a4ce539470771b5a4b7b173a7a5df11e771c8b1966619feee80a02b57fd46bf0b14f8a16fba8af8362df6857a96585883888be27df52ce4d3c30b0c8633969742038c4c336733cfb028eca631898caf1fbb0c216eb740db0078e1063bef6582f3db739f15921da9846c4c4c238a80c5598b23f0d1d261c9f9156c8fcf5c4a20eb4a06cc25b5ee803604969b4a00bb39d510a4307c28111556e222c023c7f3642789cd0edcf17a1de0912b028567bb426151352de3a04edec2447abbc9a4f2118d2bcb809a0ee92b004e593833a6ee379365114878790d805a3f4dfcce0d38c796b78f022df1d8e3bdc6ec899431913bb66c4398cc66e27fe48bb87ef07dd89e993fac7598a3d599da10c57b8ba6b5bf178f4f13842573ace87a1646eb26f03c022bd49958ef14b73834df9c62ee813f72040387b0fd94f591e32e5641f6ce2193ad26921d962174c8e8ed1837504a90af669c33c2a5aadfe300e8801458b3b7a28b4c850ee0bcf9878958c911098973c366a8f052e12e94caaf8b91d4443391052dbd6617f91271dbf452d05a33b7d2ae0b4866a433737efdda46d16fb931f166d337552b659ae2155f067f2ed87496be51805ee2b3085810df78be40ca7460223e6f4deefa69c7bea5c8eca9f51512f9a83620a7e5b209bd5627ec9d64800be0144b1eb8b10c0896b8b119132afb1f51102b5782a5a7c960459f44a172839c923981dd941ed83ec63f1c3f2f7e755ad19bd1b74f7a8861e92f3bc9e179c540014c106de3ccee0a517c83c6d4c45246a4b40388ab7b1986700aa71c2af572862c0701eb909922742c6555a48a882323c44c9ef3c9137e027ebc0e301946d1b54176e6a58a01dce485ce8fd8ed5f830f49d5c6f61550fdb0f46ab5d10a94f0816dde8fc51dd831dc323b467f79210da67d44de33ca0f2ba597f0cd09768e453aec883810c09c2a3884372b025e3f48f1ddbfa4edeef9f188487296cf78097c25890982c743ef96a93a33a638223309bb2c8e6ca33d0c3b1d48878b88ec36b4ff6662c409974b56e676f2b7d47ca447812f1df15d5f92405ff93e6bcd08c8923ddb8e65712726bff0df0e4a546b9bf087308e86f92e6540d83d4cd0666d25bf321960ccd9c2684c9560002d6ea27942f2aaef19c24bd49dca9fd48927d2961f0eb6c20b07c41d9503e55cae30bc7b9a46e2ed584793371173d35b23102e91f2bb88d25361ffbc733ccb66ffeeddd375348803ef3decac492eacef165d803c8b66cf6067c86757c5b4f9758b68f41e7d5f8339a44ebd15d8664409a083f9d96900d93971f8b6787833a4110d03f69c4b88799492bac2f1fe7bf8efe7fab3cfd194708cb146099fc37e55fd4a9777a372593ddd24e7bea812a6584ad2ea3c33c7a9053319ed436f2d6cbb6a7573c6a491b975a04c08446ebda5ec520fba2830b2a16f3a7f6e5d218328495e383adb814b059c553ac1b45d17ca1aae290967b3583343726620d2cc68478be8eb39a3c6bf1e186b53a5538a4b84a9e9cd041da5a79409830840a17c842bc45481c032a2f716078b30bd8e127263fe55083903c749dc52a48e86c60937a3e3bc015d10400b3006c528a55e0c529f0aff3d0d38c1a1c9d2c914ece6990b2eb731abafaf744a19c995221b4d51b5f870540225b55c796851a2f295d4b2762814bd83142c2cc09ac29903d22bdeac7ae74cb34414d6c15e870052600e281493b430d9b6775b0cb3260844de84c1e8e0b08ff4b132484ad16da9d58f060a86eed50e2908d8c24c7d1f0fd5728b3859e076c26212fe43c26db85c9a7c9bc090c4af9865d28a6a5eeb367b08d7d81c178324c998b81351e4e8f0991ea447eb9d63fe096e989770338858e2339e50a1594fd12346d19b686047326a489efdb7a553966b413785afb67fe93316e445e1969401c0453f71862776312cefef2c645d1db88dcf0f503cd1544c1553f33935c17c7f5bc4e47a9c39628e654238dda6cced57bccad93157761b85b2dc76e82f720ff7cd9af7caf491442d1e949d4a38062c4b9e0d6016b218122abf3e7d11dd1a2167f017bad834e9d46e51c3aea6a3f529f58e494daf30f98b2d9dc5a9fff2114b935e952c3229ac47867a24f331b3e61e6e691166c8b6c769f5c083818d0acb2745a4b9692752bfffbfad4aae9953e8980cdbc6daad9134877b704c61a8d653a9d482be474e8cc0f5dd672ca1f0d8615c54092065ee2316d16ce29209b241d5266fe6e763f4251d8d9496e32ca47ce0412e499a3aacd83d9a30da96f6ec2c4083725dbb807cc282c7de2118f93a92c4975eca0e1773f5f8c9a76fd46efd5b6a452a1bf9d7ae4e9776d5f079841eea711af2e404abaa4df33e4f2451ca4deed5e506e3cf5cb4dbd55c16631cd7b6692621f51375a64a3ccb1b2a477e0aa03a0d1bf9f7fe3f6c12fcba5bc391dadea018e3b15c6e95a304751cbf5b1d0b09f1fbab51197b0226f3452b4c86ccef8e7ad604adbe534b995585925210cf276c6a223ed85400ad12004a1a89a8e5cdacea0d44d804f481f48ab8152582505e9802ae7bfdb08bc9ecfac3abe9f622e07cc0b3d7620af19cc4442ec5fd33344858771004f5a58629ab9b2c6238a53b2101ed0a93e0824b674fa7171aa5e3415aa48360cd07dfd17b0380cfc18f627eace1d2b9b92ad7846171666bcceac1c2cf1b81e4264090d65e2ddbe2c048b39a37a2cecce3bd36fd1f40d084f27d6dc6045485be9992ccb4715ede65666b6d7413eaf383402b3ccd675f0d75613be9595992e26b01c349ca3b018df11a6aa06a422009d1ef505bf247a3e1e76926e8de5c93d2232e335978d8931e45fc72be3478741b920c19051bc8c076bef6d35a282a3b46df40ab55560041781142b8ee8c0c61c12122512a127e92d77a2955b16d113bf5320c8e1e2834d724bd5ed38557412f14d687f138285379fcfe419b5ae068150f860115b297bf8a50a4ffdee9c0d91016aae92923fe0189573fdc08e1ee1dbe7742afac158e7de1d41eb979e7b11f5ed1ca560d3ca4a71ca539d048c94f156f1d0dd9a88603ecd60d7c751e63c7b22e4e4bb5ef878ee110c8d18ef01d36d61659bd07ac353c35f4eb9d0efb0b7abcc0c1dabf8b460e8ed143b57d2c2fffdb58a0c337a5dcc2401496621f00cb548f760ac920a5e8e2fd435d72840d0875cbff933a4a356b4896db47dd3c5904ee03059d3e9e445320f7eb3d96328e00e0653b822e2eac25afa62096c5a3d28c52fb7e4e8a5a7e2c017bc3bf90ec4cea8b5295450c6c5f66d4c02714ff1116d1cbf8c1da3bc05c3d9a0fc42614b7672a006fb02047d4fa73b8ad8dd502dd162ab2edf10a6d953231fe7917f719ca0f4fa6eb5d854b5a14873eb900ab1b6f62fb5d2817d9c5073f1be900eb620ea46ca1248ca497f56e2321403ec15facaa76e5cce455826d1131cc33f467c999e71ab81d31327975c7b2a6e5713a51060e0f9a92e52f50be06eb2e6f768b51e8bddd00d739a43d3b4a918f781dcc7599d79887dc8e47c7bb62101ce9c5ed4f50b4b2ce501c97054a2e31e2dcdb811c1e4147dc3a38d5caaedd755501b45c2d126d3a10e5d2b373f3c2927a5a2ba438a506c0055bfac4ed40571fa68e08ce4da3233808ddf525029c92266f30de1ae3f63cb445591b9e5281b261b5b2a22bda69ad2c1a502d4d54fbe60ac8eb5b060468309b2bd602f0464894f87e384b04e2de46c4de03e21d965713d1e0b211a5559ee921aa582b8ffaaccde846e9896f1f9ca30efdfce600bfd53f7b0f7a6f757be0cc93fd14cf26c73a92c2bc427449c438ac1e500650a2bd6c2c0dda13925a1cf3fe3f9f3ba5466c8f421a54e5b195215a695c7099e07f746d49510eecffe5d87d2316d099a52b63d732839de11d4ffa90ab055b5da085aa0a966a3565659b5da57098ff006fa0465c1fb00bf382e714564ece06c512d6bc6f216e74d5a47410db394a85c13b014147ca80905bc2f06be6555b15b508c03e72503b75c96a00d4b7949f1183931520954eaf0ed1e1cf0b2cc70d99c4b67a0fde5e1967244192dc155c98aafb44d90ae6f3e0393fc14403a08f70a730aebac7784f35f91206e8cb73ff238d3587288911eda5a289d86543e4cd13f7baf87448f88b63cdc3b3496f0f676e4d997ae2aaf8038593b3509ca9ed06553605811fe99a04201db4236deb22a698a29c68f36ca82770d8d14a7917458dd067472759a6daaccbb9f1ad3aeddb588a165ba0a665b8a97eebe0b69fdde3fc2ad1853344016712df630aa074b9bd4e81d2d9e4b21bc8b9d62aef165fc6005e14f900e388fa7d7c2c8c7624f9c5ad769addeecb1cbd38e109b526c346ef046a6dd574769948e71a1ffcc7fb94ed3ac398ed1a7eb347160467dd095f2ba66da4306400b5e49cfa9f96ceaf2901da946f32fccd191442045b8111fe95d6dfc098bdbe38ab3ff756cd0393df1f57bdf87b665beb2b9f81b719fa4299726d911ac3eb14b22b3304d1650a1e340ea46d7b3296b6d8870d355f77f0654b04ceb23c9e0ef75cac20e430cc3f025134ee9c7244a43d44e38aa604f48e3011f98c33a32a99c61000db28c84d82ab3f9d4dbcf4dde02ec4a8ab6b837451e309db76da39702db804873cfa019b59f30cf8306920744ea87bd99f5ea08499d7ef15959bb592a13ff61f873a23757a46f7c635f9ac8304d7a8f0a9c8a8f8b5929fa4756e3dba981492f6ac19b61774fd4bc4a2405871492f6122faad0ba8e1b103591288387ab481a0ea3832d39de102760058c406acbd9b65df88cb2a3b0f5599c4524978479e880de612271b369cc66ed1ae99d80b132b8054ccbcf44eb09451c6313dc524700071bba7e048ac4314444946c2d74993102559eb3573713c6d63c42f65a8bb57dd873ea7e0bfc1e1287c45310ce042c162999c84fa00989beb2691054464d2fc5c96158b6ad1b59a998b71b8b5adef26ad588d6d5bf206962864c217bec2c2a95a65fbad5781ddec39af36c088ba19c7a01c6cd8efd59d34c43cd6aba668e44f7c6ae42dcf2c3b01b6b8cf25bb4e87bfed6462e66ca256b136aa84276bca9de0120a3145312543568282582e447872081219549813ce1095b08c7462aca0fac286437c981e8c8c5964941a22876046c5c3a7802cd8779da1ad84bdd8a31d81c006ce11ed5bfa289fa1ed56492e01247e0ea9334010f676586c5a6a9d533ad6b21a42856c3b74cbbae980b3444184f21ddf42dec291a28738dd8bfc925b9b6baa3972140d4b2df43f205fc52301f3c43187e1d01472b31ff422d9d580e996a3f2a9bc26345d7f47a9b62b1bd7d5e756d50baac3dd9f73c7293da33682c31b03ba972204ca04d0a11065084770601c300ab7f6c403ebf0dae73d9c69e7b64affdd2a1f0c287a423942da89cb98d3565970723f9d6f695a04b5eede32d2d585c4442a25f078428d4e7540af9304165adc2f547998ecd0b510ae49f34720808829782894c671fd8f24e8bdaccc8ef93b6be9b67b37c5955bfd050e1e65e42903d3560c3423fbe9ee23498611633cd7d281d29f67a0be8a036c84e4113bc150bb9872373f83499e0808140c5ce127a14cfb40226ec24ea0168c6abcd7409df4432fd9ea44cce797c2fb89e83e985f4b3a62e0914ae362e39b884dee808931a080413a4c3d8a055b7c9f4e461b5875c0ba507fc2e0eca8ad78e7ca550e510a2deb5c4b3e6d2d963ba08a49d8fe1b3ba07281ddc234f46e5050a6c09dadfeb1283a19f02c81a042f606ede252e0be0a49f33883d269782f5faad65a9cd1509dd6aea5321c2c1a2cf300a4da6ddafe0f50bb62411eab1e49800fa3494838b30578d6926911e64802ce3756dc5b0607008e858383fc02dc81444fee824e92571550a544f92bfa860dc7fbcf96f0ed0d7ba011e788ab6e0d1f7f839d3f2926af360d8fd9e276e7d834666601243fbb098d22042e8fbf3e0a02355a106705d5343db13be5f040ec3c892cc7e3b6667f904b32a485b7a6699bc97c2e6be8341243f0697c32df14fd060a1ae7d3f28cd129d985fdaaaf0ecb972385c9251d05697d4a95434f2e84c933c35d672001fd2066023b5040ed1fdedcca6801922b301f7b2f577eaad6b00c3b778a96ab85112b29ce2b6d37a919d263060b49cbe21523f8e546fa0bf517b84bbb6a976e2123d5544f94e99b0400f86c6670970896445d00b966494a7a58b62027d0172b8ce0760f3dffc30e1bb133fb017094b16ed978422929f97b604b5df52a721909d4b26ea737a870efa1f2ecee0fe994e8b5275d8d634085c5892c22912ac64be5237b40f00a09b13f5698dd6eece0d9f89cb5d70c08bc6775569a0c74a999640caacd892944dbcb3336e8941b01a1175f57bf27d0e872ddb94bc6699c7b8b9e603ccd401c11ce7e0f350c3d38bca00e3bf5e0842057615c1e879cac89f5a73f2d2faef41ca599d2387c56c913a73435b643c90624d2b744eef9314a3add7b85a61d8022b0a6ce8164cb3d481b847252ee820fe17fbdfbcbb9994852f844d8ee5615723c9393e4a596d858347afe0dfd9832e5f0a80ec8f6ca61bf6841bb60e7ca7cadb057bf41c8b8eb4d7fe16008ae05186f5e14a6075f4e6df9a1875f20de209cc33bdb85a48b00684922ad2107076981807f010abc063de72f97ad500348c0176807820bcc242702f0c22bc000e8c558629bd037005b5af8de32d34c864bafeb845a5bbb621bfc286f587500c55c10fe3ae4a4a878eba0b06528e97fd75ace677ac0c0815b6e7a2baf21d1457e3edd35ee17842e5766e24ed9d268c51b88614f71b5540442f717fd35a002d3abe784f2012c912196e16514548aba933c22eb130aba5ee2955c387856f2ca0dea65fd01e6d0cf1203bb6ec8637be758bb44d7b75a31b6d66ad7c020ee30f4415ca37e0e05197b2dd0f658576be0ec21646b3e13ac1d515e000877eb876a8f34f51c35e94c20b4da188e5e2570c890eaeca4835615b0386057b4fed12d501e211257da9545b68d1bc782612a14cc1b62af5e469c028b6dfed1299ccfa9c7f0467db05a74772d6addbfa430ad56793eaf7ac4e6a6cbba1c7e4d757ae8ae0a24c933b57d5e05cea15c682eddd60187ddc207ce588497c1d948000f84d206e1d1cf3504c4b9cd7a538f573b17a84db640e95765e385a7bb082cb93c92957cc68fe944cc6826d4da81aaeaebb68831af4347b4c859b3a4f6192937de8ab240d33494b735382ccfdc4c6fc11152e07970f98edf0c8394be5ea318cef0eec4f8142573643ad5fdeba63dd0fbf0340119567bad5f180998b633623630385abd1010c5e4c5767cd07c0debfa8a168a9fab9b1719427990737d0acaa5a56fbb11c28551d43e7b18069911ea55dbe525f75660ba16cd62ab645fcf4bd873fd3c4ad3c3981729a22ef371a7ed9dc5d78ea2cad42c00ee198d79195505a5fd58013dfdcbec3d395f9d400589cdc53ac38c1ce0fae2d5bf93a906d494b82846239afa937693e9cc6be2e35323e0e45d35c1bbb3dfaf12730baf0764cfb15209244ba567e92f161f8a3096eaaf1211460870b80a8fe2d5c1387689e696a0e040a0bb4245cac088676a7a7b963b34a4d146e768bd7e40a6e840ed651769dfe32f61e0a518c34fd3ae75e1223f2d207ac169c82d1c376e18ef8cd688a38e242167ee26f2aae89eec9180e321204eba027ec4fef032f65a527460119ef46b4b0743a6b3515870a46166222835558b0dded55c5c8a0f9319d5b469577162883e69bf0cc5e36df91adfebd6fcad2e36ee8bd7fe3c605e2fa18a14cec503749b8b045521c4e9b1649df291d2cd25c40a17a93a13ca5ae299e6923be454b36a9bfc3819b1952a9bec20c24509c6640b0924d35e91ec8b824feb84a57920f57469a2e4f42d959bf16d22919109a6827cb46a78ff64d27902103e78543639e3a7b0f0899f26dc8cd3e1480ad58a2953ee561733fe1d06d102ce8c90479bdce2d3224c1b689479b76d83fd33bfdfc36e39dc7214b50759b4886030348bfbca8b130b05355dbb3fd375d7f7729192a72db4bdc207e8a664ff753f8e2b7768b0ff67957bc14cff790a566361cadc3367fbdbd08dd1c5e785a3692c3bf24a4778290a8522302978970e5480c690cd3db2e75bac7a712eaaf9ccc6c61d05d9877198d4a4a33f52e44294b349c1351f3fda863243492a9f3e9dc187a1d42c9196ca9ffa15af12234c4d254d0678e4de910afdd4e547fe4103afe7c6750d6cf0a37093ffb3fc25dc01ee955d57cf3af69f02c0215df68b2d2c1d318125a735d803038385373eb742e50591fc800b9a799731e468c8654a7dbf43ce86684f7c64c8939d7f4b563f9cede26fbbdcc66aacdecf603ceddafd6381331fa0ecfaa1440d500aea27805ebcb4133a74965752edf2d63d031a45c07771566c249f30391cc0663bb433920a18aa0e7ea63890f39512b2147b8c98aff7990e8bb9d8a24848507121c52875705036404aabff60b65e4d65c22725bdcfd97091293eef8324698e3f261e6894712daa53c7d49d8fec450d7c41d43bbff7ef6e2567411ccb9701d8fcd004dcb8f039d5ff923a03e3dfdb003e1bda22a866c99cf4e280b16eb6c0eaa95ce738356130de58e414da7710a6ca03a90023ce9c1d359720265f2c647248d749448ad8ab758caf49f11f2bbf5b4edcf6ab5dc00d4c2fde080e45981ff874435ba35a9139e986179848698b481a38f97aea635e5da1a1b082f17fd68917187d39df9e920322ba535cd1d01603606fab67594fc4d3565636f0fe020cfe8266c3a05cb0b2e011f2568d933a027739c86232423a39559489aee0d2f660da9ed0ef2223b1a5d5ba0186d7a6b630b147e0c6d21a8d65cf0731f8e9a8bc328121f7a96a60660ff27eb7fba40de49318418921725f1c37b0b1208689ae4f7f1c97e9b844d2c2f5684efc8722352469621aff339946e0235c7b8c619619b4bf477e1f7e5af4751027160be6706e30a573e52cfca2db45e007f46e3603f862513dfc253cfed582ebf86c04783a8abce33dc66906c6ec789c13ed3f3e61a6403edafe6b439a990a94712a8da862ea18909309f1e15da8b8812d95fde3b4595f624589fc045d309f287b5a5f22d8c50df6b7c9d69355a9dc9528f1dca2be79b24ace35632d002c45a672f295a85926aa9abaf421f6c9dc02418e68da3dd6303f6dddaee5068f014720a9333690e967c52541577ed438df38f8840c54e6044c7dab9409e271a4e0971f41d769a6855690171940aa8e630919007ca7d1b531ba2a28e73c8711ba2a16ec960591b2735a1df2ec8a5c4e559efeb618119f1949763f8d3ad5d3c1a13c2cc76c65b1247550fc56a98004c10dc4aa26c71820d1a268b7f5f1a8c9c28648130b77a340b8408bae9ebe062d14594a7af3a2b66135c144c1958109833bb5c082b349a0f88153a60f496253607689dff9fb5d0751a3990cc9ee5369a09cf11e2e6ef4f2f4fbc485c10d2b599959946722296741ec315d893d2ba0a469086ed0b704802be95535b4be6784b3033665f6d8ac4708d8bebb1499f56343eb4c018fed659f153996de7c5608778acc0e6dcae102b5109e4af790ca528c11ad5f22563cdff474eb3d205a968c5ae89a1ab5185c1e9a83d9bd6518f64f5f5c231188835a8076a9848d6bc6a9c3239432a4b3dfcb83d109dd851e4966d5c11ce0ed56579080ad984349f5c21fbacf54a533029020a13abfcde1e3cd4c08cc4aad4b7f0e8b70f2d26d9cf0f45097ed96d749aa52ba5f269de075f58ba5323d48374bfc4a69aeae59e20f9da4e0fea007366e811d44f89c0ff941915304ec2da0fd40e087b0b010c5e7fb3a1b15869328831e09b146f928bf84ca0e723913e65180268fa183a01a967211f516f44cd23e78fbf11886f6bd31518cacad4ea292b9baae82157a7f07a1f9c921b0124d6a1b84e0f4e94340e22ebb9540445c2242c26f46e121c44ca739770c21f0d79bfc1f0a21b08ac1a62997329140b9ac0ee2e4f0abd1841097c0e478cbbcd7d87e3692e3bddda51c6fe46172bccb2b49c988f4cacc467761e9bda6d8841020798852d4cb487afa90a66044843633f42aeda7c4634748263a826f7e6c716bebbc0282bb9b0a2bb64586510255f5610812b553560e9bff9216065f69ff98b329bfb08260a446c0f0392df4a000097c626b9704fd374798b816790d1aa980a916b2a0f5c81b4e84b1439685481d19b3aa951affd2a10164082260d4abc85690ffd88a372a6864b50e43046d78ec4543cca8236690f8ffc224531822318319fc74f1b5283e4a06e1f597b0e7d2f88ceb3edc721516626e346f0467898830e92e4e65f5930538fcdf08fd902f379e5586b39c844e126d34f96fa766ee72be3022bfc7dc4fe19a18267be10bd41cdd968144dfe318b7df421e5b9e790cc743530fc33ccf10553a9135ee8940f3de66ebe6be97cd9580f3292cd007ca8adc456ac9227114e20210efaf0b0d75381f7a27ac2d1229235b91def09acb551059a44f015b1ace6bed269e75b861084ba3828fcd30e61a1aad05388251d48a61e64e14a5ff68eba8632dbdd149123b8987169fa3c50e95b1b8a1e6d4cd79e591f51f4b595b6ee7f38a0601caedf274b853c2783a1e2def7525a03752d44f82e6aceb53d7f158256ab5ad1d4fce2ad6283d4d4a5171df4a09730ac5c6d5ed026b86dcb5bd25f8829f652155d813146d7b4abfc744d11c225d14d2a31220d3978fd5ef2175f1e6aacd827117c312eb12319bf905c0741cce21d6e438f6af9fe1d9e7236d1b317677427500135084c6ab4905e7097c006553ec031101e762eceab3cf011359045ec35ea43693a8dddcbc72052f1356fe819b051be42ff1f1de48bd39cff5dff63e04898dfb6aeb5750d93a9c5573e1e292c707115754b38e758787066dafe481630eca4e256bbe328f802c2d050270a8c69a6c96e9a14386c8d7936053cb37649adbec1706ba86014770e8a45ebfcead2b85174729a8e9f7eb76b56debe6d39075c6ef37aaa4363e8bfd7bb764f7581de99500301b92f67c5920d396762ad99c8b3c623149741e176c7165cf137f84131f57c9c08360b39e386eaf939c1610ec72b1d514c26fcfe0d4963f37da06af3dc152f6d2890b4abbfb34dc3d651d6188856fc9a8113890b01f3deda67045168e1f1dcc01b5fba8fb95df7f7d6c75698647deb20252d6a6ae7dd6065234d826682abc64f8da11c2c73cda8d4ed89a8b86f916417dc94ba2f7bd2a7a8e9100c8411bd2197dfccf0a5603e6506ce762e817fad0430511e5a13967482a3ffbf92c60d826aac19ced544d22d1288d07d9ea7d460ff35aa2ea82a98e79a9e2bab0c28604167f1c1878fe4b4ecf3bc36956798e676767b78fb3b515a75a34a714377f983100764b61358ceb4152961443ca4d2d334abdbf553550eb92137924679c852f16815af77b2015ba0817b9a98ddf3ba1d11879dc87b09ad52c8eaf6719ee3e654c77e924eb1a8b8369e58545b46c5d8fb2f30be78f4aa6424230e7970b50e303819bc7762a96d9a79f354ff7172d117fd0acd6bfd41377f5dcaa4a4e0dd397d8765b159f65492070c3657527d8e7ac95cdde897d54f34eac746bccaaf12b41f0c3254ed1f11a3b8fc2fa55d3e0a82e84cff29b5f3ff828772d1e2d0009c65225fc98517051c4b655aad449951a6d09e41f305190bf952a5ca77a11053bd161b0ead1330ae6d7941a8e07667d5a9311dd65879dc7a2857d6afad4b359a66943fab6de1b2e5651512a0a20cffc7ebcce74d7f210582d414570c8dd5ed4671e0088280fbe57f599cf81d4fe7edaaa9bc68d83f8946afe4d72c5bf4bbe105e0e576ba343c0c46ed69c37b6249e5730d8601fabc689b939f6e9111e782143dd390bbc369ca31f33de4e6bbddeaf88fb45de23c181f3639a35d7687792605f888025561aa7022d7f75ac4f46151654d1d90f902cfeab15ba5e58195b0c5a73b24931962ef7d281dacccb2ddb9b51b93461c5e273f6cbb926b5ff32720c67b3146e9e38a173660b798edc4c982f3c5ef0cff9f9d26bfc40c436ec346f1f728cc50e3ca14da161ef0d187f4386a308ae7e0dddc5d844dd73df681dd80949beac709d5780a6048bac8a36a0f008b590677a2a6c6a1626d30b4d074872d316ebab519bb9ac070bae0a8b03d9acfd5a1a83192e8562fc099638fa5476ec4e0877dd1b82d59065e97389b324b9d25fcef07eb24a630c8e295412caea10b9be9dfe867d9ae8857486970f7ff2e57faf7fca378fbef8a9dffb11f7695a9abb392e810104e39a22c239411d1fd4a05f7b49fac2117aa46247348a90c21e11289090921db95ea5234ba3ea04a43efcae44efe77016d39fe165ccf12a05b32c9e266fae82c0488cfb8eb68b59da389fda6593c92104d7726ac7c922c42dd562d0173e4eeac669432c165077823f0a8f017f9a07a712b4722873538d5985170bd6bed00281060ccec2aa9b306bcacb79e19851954b7ac143aa05d18f46d696492c46ed831fd14263c114ed5c8c78f3170b1c9e0ec46083afa31a3976d4e1720892927f21e28bac3a562a87d8460d6dad353984ec8148a28e0e018c107e4ff305026f312cc49dd04b4cf07891da4abf302175c6f4da4d407573babfbd286bbd215ae9f249fc533016d5823f1f7ca170113180534d4a0a75df0026a4208055c7dff68365e640386f4a7259dc9b52b64012268ef86018d20a16cedcf7fc2147ea8a61c818b65d8be736f9a33cffb6f4298c3ba464825364a771307f0e2245e7801941803c53406022d16d3e18ba5ec9c3c5ede66789500616c13e36f9e5e7e6535b6bc3e0fb6128b9c06c14b1871b6d270071a4869ce23b4783766dc029a976dc5e05879f7302b8ce1ca4d856836b92b479ff7ceed694cac98cf6da1424eec724cc0ca96a758c1fd69a7367da0bb5773150f11948b13152b162ab30ced998809be15db5327d7609c217d8506c7a2a9d5c7ec6ad8006da39e3c306146c814a2da24e81369f58a603fc2c81e9f940b1ab89ed40d431dd59221b69e860347044a18660d9ee24a4dba4582bf79368d883b0f208026e6f5ba5317eb5db45e5472a7793f568655597509ce5bd2f0b371f39a77c737774ee53933d32f6375001b010ee97628612073c7d503fba3249c7c892130785493b5b9ea1b7a13817b281e978c289df8507d82bb2e44dc798f15a91e5563d5f28115d590f8ea5d5f368d212c8f9661cc5ee57b68cb01149db4798b0065b2a0aa02222c7d3fb0b880e82f5d3819b614721cf56b335219c5e0f5994c9d3d9c5912f721aee3b184e4732715f71a2cb23dad766c571241e53d55e0af52fb1916fd5fa699b418faff598d3619583413a1e32556525c6bfc403136650f6ef87ed2c1c4de256e14132fb3b1e498a0cef87e2357e5c7145ec605d7dad4fa9723d174f9cc76412b1fa269de98042469845b6b2105cb64370da82a18e668bc4b182ae79053dfc8e8533d68a2026221e9ddc285c826fb55763d06e107f4b65f61f8bd7a6ed68e22970280d866ecfb8d3e3c6ab6c21f7f2a46e71b4bd1ad10e49d26ea8d221065cd3d523c00f8c09a6053ddd3b9bfe3ce5365c7a36ee2320d2b892dc33f72abd27436f23bd165c822a080cbc4811de8af64cedb9b4e2cad96a6ce9e9f4fce5432142cde43944c477856635a842977984529b6d43e575499a98dd7959cd2ac0cce6fce23a671b2b154833937b6381549b21f655f3890aba2739ef4bb2859ac5534b46d46ceeca3f0c5e1c949d834edf2b2e4a4e9ccb1d252ae3a02cc434f46adffe0f5ebd407aac94d0828a3974e0e127881a3a30a1d928a31978c47c8d7531edf7993982d0940950681bfd77bec4099d082a8ee80b107f7309bbc81f9d6954f147db17724d909f38cd8a34c9864e978e58eb6067c2680e61ee5a59d09f23c4990053173d8e7aa40d45175db6fb9010054a34e5329040a05ad68acd335610c611f812f6e0ed2c7c897ddfcf12734eee3cc31fab48071c08c45be4cb2fe1b4ace30e65afd0fbe76369fd951e0c242ff7416e2cc6b46bbe59a4ea9551fb01af8d4bc0303ec031348b49f77fd36d1444055d4eee3a778c983629543f1fad81c1958be999b160ca7a62220bce9c233963120cd05e478c9b1deb3a250aca8535e4f8ae3874b2415f4f9526916bae0296a46fab037ddfccb730ac923ce85231d051c9f8f64a37e43937806b68097f30cd22bda4e8935336023b670c4a508a61c38676c747053109ae53579894a9e28b456a817ed45e2e2a0a2ef6b1455a4c720315daa5378116de065c3ea456353ea69443921ad20e54fba92200b540977bca44d26cb0444a6d0fab973c2ef8b98694a09b1c44e7cd047f3c4da7e7a03846177a3773680fc63bba43f21d34b1a44ce8551f15cefab13af9e95dffde6e36f6e8e84e49e2b403706de5fa86e68a8a0d38c9a8063833c4b94464026d6fa84a857e439b93cd0e6a1b74014ce914a854549e4ded46c29b1b9ef4d92da3e6470a4a0496efc925734f34407066b777528258797174e294ec2857b1647988ec61840976dbf4da9fa63196eaff0b9bdf2ac9446e3518518346e63a96fba3360d769be52b6b8d572982b36e7a25a431cea32ba86d99138450c65f4922b7014b23f13db201ae224c8f06aba50f9712031445ea7240b6181df22b6e6587c713bbca22f8dc394329548ca6b17030a623abdde8955551ebb23e0712430fdfcbd3ae92af2f81fa104374a60cb7c4d3c50c026dfd3fcce877200a53dc0180a229a62b2d25d7c0e8ffc9eaf60db6fa16dd10525b6b5e33b681976dd9413cef4d564ee4c04417b5ad53fefc850eecd3a8dacc9bce1d22f146740e8c1d7472d14bbba3658e25802a2e8376fa4419e4f4d62d0f97562d5fbddaa1d3ad7ee1939039ee1fe7b22a2d750476284ae4d3da2d2337e40c77a9ff4783749f14a3f450475906b164037a6bf4409c4a3997d6323cba787fc2328f1909ae3266864c7be592d23a214234e23fc02362a25bc195ebaca45983df7022da912b1ca747a0c9d29701b0714b0a8545b39c1891a9cf1f4b988e86daed65a69886f146e6a9cee33674ae41634e3e20d119639dc965eb81806d3d0d68c3d5bc4720da27290ea35d41a513c4a6e0ae074dabc0f397d497f219571cf9f15c10bac82f14a9c45fdb8ae35c533b5f853460e9658bf40e99a81b38fd8318375ee9b6e546f550a2006e98b775725dcf1a299f10ab32ba213e2fd6b69f6c510b63b5b9622dd0eaa07b12494effd62e6b2a4a593bbdee706b2b53bd16ba2a1196879fa78798767eabb4ceae0b9d52a6690d74448abcb50a1f43f950815473015975729b60a487b11c02203f4889eb6bc3a48643bccccb9840aea2ab0e1efd70aec4528fbd6f2e852164a944980565693845bc04a0de724c2ff4a7ee03bb081f991070c9fdb0d506e1084a231013e32d06e1198d32c8c8ec448d8b81b4b151304179033aa66aa6c83d447622e9208ee6bbcac8cceb1b20ae522457a7fa9179511059ea396342437d2feacc51c7c4ec199ccff69bc0c0434b6555d44f75eef2170dc9972d68d7939c0e637bfb41f11c5d2384cac426827d10b3b9e97450b7261075e011f881347e5114cb13997ecfc60084ee2168de1dcfea59c9897cfe2fc02c9e347ded97485fee27c2dba843e6f4ec57e337b49f677cf3d1e9d4c42848e4a4d179ad0c1bdf422ceb811dcff90284b81cd7784d424ffc7f6af10f8923e9990016315b3fb1f50b126c28a2142ac98742a4739bc032fa0858a37fba741f20be0cfd3b19465e178992e11c119d8d3f406566147fd2741d91bf68b97478a674e19943510f5d4c0b081bab4927e927819196b1043792a3c03499228f8140a1d6439c026a8638328783ca9fa18e512a0bbf1830e887c7afe55b1cd8be895e1224a018320a384bf5eb0253564f427e9b38f25a94000f6ce6b9c1cc389e6347ff1c2a94a697a6c343776303ba4c4ff43d923dd454d8f2a05b715e818241ce46060d1152ffa35c2f09eb38635417f97ff9d106a092418b959905995ad31d303f6699460c79fa854bd6e37a186dfe2f8c39cc727833275c23eae2f2aabb903d1488597f143c13e6603b2f7448cfc28746fe02d746988a80fce8e7cb9b51f23180183eccd1933c3e3586e832821b410f4a9521dafbb40cfa6cf078dc3e65c71075281744fb2e6b6e01c5bca1c8cd11ae6b41d76ab82233c6ae6caf190ffce5bf557eaad523c1758f10a6137322e3bf8921140ff1c44bc2f46d5fccf4cee39f4de3a854ad50d7f3897aade66daff0d8b15de0d592b64a889d4c1323c79d8d138d0b805463450ee135cef3a1aa188afd7e90af9a9066d8e3088b1f768097c10f1fb8013594dc4717484f8685d25bcca62e1fad73d67956d566657b2f2ad3309ba57ce65a06a7aa98d8eba7dab0fdf96c947a48161066268cbadb9e1f1912044a62e872e5b294bf35a9143cca01ea3f14c436ee15ac43bed1bc0d072b9c45da9f57c16f89b24851427102040059019a1a146054968f867c276276d668b3c752d54c221e01263f21044f5f7027ed28456364200785b0258c0e1dd9ce1c635a80078ae2f62fd0b0f1dc5de404df74bf12ce3ff1d10a8f9332b62a1017b6e8ef9e2cb49a1b8aeed420a1d3422c370b03de8feeac3f0f4ad9f5ea45875f95178fc6086c2be660178a3e23ea3e6ff10927f0776b88e50e3a72e1027a9471b6904f8867d345941c7bb4b1816e9573258158e4ba392ead97e3eec428f6fdddc6ed0d9ee62b15398e8def8960e6d0d5f0e2c175894f7905cc8b200ff4ca0147c1cf85201ffcc97df085db2e9565d89cbc69ab354190cd22b7713a5603dabb9b1447335eaffbcf645960c66c46086e266554294108c257171b91537e751da80ba560219314c3f55fc6d05198bc021c0b3d2b84f3042cf9ba3585484acf53b9b6853876fd4b6de9813b8bcc8156f68db06b665e011ca3590f47e18ef1ca5980b1cbe8b6ab7fbf1f57fc9041e46930dcfc28dc8bd342fc9af947876774d7d2ddcf304a829f4632108745209f32829dfc221b13e8b4719bba50b5058ca1964f2b56a2036fc298ed426fb048285c98c2d8cf6f8f36adc3dfc4c31c6512553cb6a9c58247fc7834a48f2e402ecac58def374108b92879b77521de26076be56dd2f30559eb40694271d68716e921b07b680eb0d3d280e32cf978e41f24ed78c0f7e98f58fed6a8e60bf5e15d227ae1592ae346ca659052021080ac39199a67eec84233ff310d0ae0e9a7848552df9e3fd46a7d3f72c999a8a6019c1f3da9f2d1679a97d16706df92d547062a9974f50d897f2446148acb54b4f3ba2a07da7bcdc68329462823330d7e06b392494394f91dd53735cd1de3643f6306c8de2af3cec869d97facd3578bef4aed2da6b9633c632bdf50c1a24b325147fe29140cc719eb2018941ea3ae7f7bf681745261b2c0a3a9cf63769e1922df6a50b7cd7b97de35a3db74d469313ce4fbb1d48419c2231fdc7ca108d454bc82b58388fe75cd2e41b43b9e1067a3e71b6a2684dfc333aad0d74242d3b41eeaf3def0545b43d3514bb9e373545b405298883cd333b50b5a5a22835d01c5416424cb49663e8bd176bb5c4705e1127348ae67a66fd8ced114cf86e73198b9a8926cbd59d88577f3a0f926e9c6accafe1920660d683686185b70954b85108435fb13d5724b7ffa677129293a4ff6cca71bcf23115424ed0ac267bc9dd3b940e82ab41d68279db2df74a4a5e22e99bccf2c2f464c1a611f0041192321490b9b3c0fd7ba79bc58de2b35cb417247aaace4f342ffae5dc237500a5ed3eb69e1e6ac4748dcb2e1139ebe5612c011b7dc4fa639a12d66c309ce7c194cf489a3d9e4deab569d9fd347ee47c785ccd71cbd1f7f27671834e3acf96d46c00a786632af2258ebf1339359ca428a1c8b5d9e3d65e26f9ef70e787a9a5109adba7f3aeb27c79bf906341c34f50255e8bd0154c3a9b8ad1190f17ccd8ba68bfcb432189b080534e5768254891d97c40e0b4cb3d125283c63c368833c08da8925c7b081e562b6ee4c805fbb3f58f718d4fe75a5d300c6335a88404527783c459e42e1e2bf3da19f5e1e4bb0d06c6ed826db31f7e99d5dcd052513da566f15037284204ac6a3557b5817a4c143c3d35ee92106973ce6ca00efc0c38b835660920869e04e85b4b326f86acefb8254dec262fc93d0beefe919783b83c2e9a0b12da2ca3caaeb860957b0405a5bb6b4d3356a6064275f863548995f1110bf23ecd25984e3568aea7572ba476599f27d60713131a9a0e70dc3a5a5ba5f56f42db739039947a7e398ddcf91a5c39985a4a02cdfacbaae106c126c790babb2b09922732ec4d47d5ccd5d566ff8642d32a8379f275c2ec25d8eb5e0f2ec0d80c4bf53bc3fdd41066a7f0438f375f84c299c0a051a85d1c050a1ed3bc6dde315f5eb20061a8558810d3213b8e004ca7f8332e322ebde4f7fbd7f2dd5107f3f0c597e231fa50c8236b23bc6e902d0e711978e13cf20371e9bbf72b14d6e6145fa08c076c741f80387b2179c0d6d8f8785d14eefdfa7ef41ce864fd898055411b7fb8bf7a67a5ca2a7b3c02c29e61d2556eebdc69d42622123b3ce78ca952a2deeab6776c230f7cff2cef89e91c791479073eda87c0dadc68fc8947a24c535aba9c6053ab1fbfdd503330176354eb3be1b7a7a3f2cbd007a7a8292d15c2e7760325449d64507b4f41ffcfa2de31d40d7d0eba1419407069001d548a6e73bb8bece7c88a508b2a1ae02cb1d4032a37e902fb56168e841550cbc0b9fef9ea3c7186c84bde4da69710e482aa3c157ae6a5b4f2708f38e51b91a55827f5c5b01262f84a42ecbc5d5a3060c1f9c457f4d46390151832642d0ddaf3bb06485f1b5c95dab7b54501d0ceb6bfac441d939f8b34a86698995ae137b70bdad4428fea98dad15339b26bff97f6e8827d4bd583ca67e0f3c1b3e1d69dbec2d2ed46860ee2c252981956753523d45d4ee9509270a501d874cb24eef76e1beccf63b6a0ba0472ccc9215782a33289c092fc1fa41ed34df0c5e580aa74e6c2b50bcec6d787ff2d074c15f9e82411fa70f5fe5308fbfea3b0f485051fc99a29d0caba6fb3744f680b6fb82f1b645db7b5641c0aa8b4f5e3c1bd926229c18c69439e40cbc67e77369279ebac52174ea6d903b11d20698a32aace5b20a8d0bddae4750b3cce959eb2e67290423c5a1a74858c60177e5e33565c18c616ccfc0219315da6d1882c4a56cb6172c60f8bb0f0c086661e34701504aed3d7d6a5a81e6156e482c3f74cbde1156da3283cc3270ef9202fdd6e438e1fe9f35c611163efa1c703247802c5de878bec456fb27f81f71e6a23c54412cc6ee053b94f75918cc5dea341abc15da895082d8ee38f5e0029d0ad074a8e7c0d3845b8f594724266679c3cecfdc5a613872f16ec4b691ca51143ca10004fe85404815f469e566f89a83fa59212526d7b9feb62b45c898419e78232067b2776262e3225895877b1f9e189160fcdfb4837abc0df56238bb7f55c41ae34df1816368b784aa6d38bd470121389198ad0dc884da9278c10740635ab62bb638a8a539859f3ec874b7a262e6e8cfbe142aecd25937a3dd802497662e197ae9747ebafc72624c9c530a16bfc933d4f0b5f2e3ba163f9f36356728181c8ab2138b955eb01c88bdd1322daedf4141f307e186d0f05983143c178b8687306354069caf1d937b6c8086c440523e5a12a03b01a944110b89161402b73feddc5e7004ff7ce492ab87c2d5d24f4bc163e201df4773e2ae695c392d5dcdce6dc57902d217bc542645ba85e284d26c746c6cbacaa4415b71626740346df3d3712a8f111069877727f720deee0361da5ceeb1c0ea6e8284a579298ba75d1b985207b2d1879b3ed083ecb4e8b0b08e5e74c14310fb7e2b4ff75069833b5619361eed5ab45031dc27bd7987e412dbb7b39917f7d87c0af904dd076db1f88091211dd0b62210ae287b101971f71019e5898a925142e303dba0cd7a669d42a266c0f29c88e82eab9591c2294001e86c92378007b643d416ee108dfeafa8b57d76e68a2746c853712efb65ac315f8086fc97a420e372d5a001638b64bd2b44b383f5f21999b74e5e8c831526c6f8a40a6628891973553534cfb4d18b6bf2f41fa009e8a2fbd24e2d1ee0eb0b9cc1bbded316ea369609afe61aae588b40f4e79db8325de89743f97199f2a0687e639be24c5787a4848104c1649975e6eb2feb00b03061d811d87f5dd0be4da9b1be0b656efb7e9b15fdd43407cd4aff560062a2631b6a61a6bd787f453ae31827a1597238a2e804fba7dcf0fda9939f8f9f631ef53a189ca3063188794261ab61cc8d6ba57a61672c1f45cdd755e4ac2a8a1c3e84ef04756eb3b3e84f44722d14db131a6e54fa87f3be1b5a6fc1d43c5792272e4fbc30550d27873eabaf6d3d6db052e14431908541df7d99fae59b320b8ac71781f31162f15baa6e539a0a1af52704b030143e070bb68085055038d3d9dd62ae5941a52eedb34cc846666605ac6aa0fb74ab860176bae5ac08f2262af9fd0fe7afb276797f9559df36ecbd6463a3cbe52ab7e245d68ba0716e6f843a03b6041a0ca2d9fba15867864ff4c187f89d16c4296a8d736833c44be46f2eaf3d82544ccd9f8e7ef4daad51074dcb189e41f495e4021d9cfae3a67ea1a147f185a6d23bce5df827b1a01527bb7e1073cb5be2df0bc579fb1b53407a80fbba9ee25e18f76c3435b7ff0c800f00ddab191d38a61cbeafc9d0cb540b0098bde69771d177d787bae3abda7f50ecbe55f7ffba9dbfcec6530e224f7bfb2fa7b0306ead5f47ce4818548740d2b4e6a2ad84ec0d70617d09cd20d6d6cd6ac061bcc379dd64182604fa15db5da82ac77c81c52698eeedf52fcec0ad171f4e9ce442326a2987cdf712182e6e6d31b02d5edb25650cd3e43c374b6b939403315d3cd25b74abed79a33fa26f0bd76571c0aacb2da87628a53062f8ae319654a3acd1cf9fca1a887200deaa987021d39f95d4755cdca992bef28244b7f68b2cefda30281ec6cd4ed6c4ad84874dfec2e74e23684dc70076b2f9400c628ad95c1530dc32f23165df90ae66b25403efad45a8aef83eb58b656e57082790a31f35b54539b6bc13e196d52da139ac7e3dc6f375812e550a78a8c3cc03b58deb4e0e1ead5e53e613d6c3e25d331f38786973115f03533d3248a724b79e04b5a7249416abdf5b02de58054fc4cf8251536acf7f685c08458815c15e80e87420f727da0237157dda937791ee37dbf7abf9f7cb5fdf3410f05756f722db6fd2f3a091cefea7051e32fd76f677952f28f76fbb2b237dcc84671f02539e115fc60c295c9959dc3e0b14f3f6513686060de18f5ae2b37917cf3ce27e273a78849e53e59450573dc6fe82606fe16d4416373e7fc8738353fa3af662f6ee42dcf578d225e61349fe66daed2494b58ef36d8f4805b8732022e512e88b55635e21e5c2ff4feb6459b98a8cffc98605179c5194b89480881c9c002aa0fe94c8cbc2861c6b904e1df9e2a34c8ac664736d0a50ad5b3f47e94fa9a0738f559c65db5d11b20e14c8abd86f39d936e125125e62c1f5a5774e46a31bad445428274e11cb807537c0048a46c6186469eee4d460fe35f4d14c14c75f5987a26742906070713f8aeaef5ccb781c72ae170816ac147b2697abc164fce88150f397ea42542e60fa89c5003334b20c3577ba4a0813d39b7e96b9043acac0bcaa690bb2fef9a53e0d0236f98c1af45a81e2933dbc99a75ab90e45d098d7d25c6655ac53e3745e2beba9cffd16a4fbec5d70a5cbe95eb25f9022d9ab7b57915f8116d9cd41f2d729c0d8c177bff53a4169c08cb4b8db9cbbb78b4235d982176a89c1c205867d2099f7153e82718c0151b8f3443602a5f0bebc4ec08a69879f2c45668f5e5fecc91bb71a5fd33432fb5d100a553e9988e0b7fd9693f187014e595b423015de2873094a0930602dc84c42faadd4a09c9a4650a3d26f6468ec4b41d8223eefc9e8c1eaac0367353aa4c4b8a467e86f4b38ce369f02c326e5158e0e11898de34f63252b9ee353bf66b822beaa3dad78ca170430a1565afe413369282e4a649ed3ad9497a52182823209470202faf29fe3e676c5b66d49d07e2460ec50f27625aadaf3a8d1f8579bd5f1fc4d9e27c43cac2e006ea0665457ca216a03c171c6e0a292c4975302d20049473b6d569417e699043d3c71f5730e2e2951e6dc07b7f46c2d3bcea66294aabf5f709e69d3e1ab1f8f482b0d1231a2380185e3016e7da768be4c4f1b69cf0f8d6df71e7b923499d16bf46151d42f77d7a387bdd45f436ddf39cc02674b48dcea76747833f4cf4b78dfaa06b19e80c37cc5852f641c396827e22a7768ba2631640c8ef629cae0ce84d97e80c3025bd0182aac95565ba7e2ec95c8176aaa68ad1082e45bccb0918afff364bfe3b2d59a45ebfd451b52dc8e5713d9f50ea4ef1ec2430e819cbd25029a54c6600ed69f56cf49c4ca2c6f773057b1b00a0f37f9b8b1fa0848aeb9888340d54b944ae3ceecdbea14064d6ae268ad8c084c6185e4fe1289d33b8b09b279240acdc09ba1e73f9d961e5c44c2dcc629a64991431ce0e2bd7485e4ec35799b4b040a0c1bcc1c1c3babd3feba05f3931e2dcddd26219c2f7f3001cee6965a402dc0c8f07c461ae10a82834e60fd453a130d3cf756ce9f5ba18107c80618b7c9b6bc370b86c4cabbaa58dfae9e829d212a7ad8a7cf66bb914ae004365c15c56cdc17e5a64878533674be6633ef7508cad1cbebf367da261bdffce20c04c032c0f769299bdd63c2a9dc653d6bf48d37de5d1602e0ab1cd43db37bfb6023ff53a996cb4a4fe3ea2825d006a16ffe079970e8c3f64bdbc8973deeef3bdffddba6ec09c3c2306c7d3246c1a6df7a80316628c3a361d8164791267efc8793c2f27dbe6adc2fbfc07703cdcc725574b405ed11c9cffb623d945225845fe464d36a918727588486a30217abbfbd83b2b94dcfda69135410096458bf788adff2048d2b277f947a9efc41c93af95b09e9c95f49d8c99f7c7b38f957e7b96d88dbcd87e7172bac6c40ac9f4dad99e661551ce052d32fe0ad644a472eef459e7d7f9d31b7f51467928a8201553434a9aefd919b62fa42e962eee610da75b4dd8db79a4d3a7cf3f5a659984a65b8eef70033b0bcea3f5ac842d8254f00b297015cd080f6644c32d85bfd3dc20eb1798640c4fe73b690f59a5685e5918c936c7d885ae7b05aff41e9fd8da150aa4ba2c57458fc8558cabbb9a5eb015d5287fe1e6d86255f1d6fab7f8207183e631bdad03edf8907b4821193a5bf22349185096ddf86df999af5e621477a8bb61d72515b06354a6a4e549f0d6f4ed6db6cac0c75697dab6eeb5b4277e61c71b6941eb2529a2d4217ed81d085e641c80d0ebab440b3195feaa00d8b7511d7f415976ae11f691dd0782d4a257c862bd04a96a2d7cd158678ffbf11c44474e5aa14909eb8e21fd4fa8020961fb328556e3d1dfe02e32e5741f3c6091bee36276b88382f1561892c5a84ced8a0da45a24c09a5e1e425a7faba8759d8cc12af1931ec4befc38bbb68457e4e925bf1c1128bd09707e2b3ea1bd6f820002dc7091a3a74d68a4744c071ed4982d830f28723180b335dc21ef6a8873dbeb0d1d7c33a9fc025e2af4e721b0d83d13443afe14a59f76aa7eb5c6eb7a7c84d51dbfe38e10f1bd273603f068ce7427f4ec8c786f518e8cf01e7b9b09f13feb1213d06f69ed73a1b00789eeacfb102ab97301e39acf1aa4d62070634ae320c55d65e6d9ad5b7adead8ab0f4b9535579d66f56daa36e6eac3526ded55a75d651b558f75d5b0a85ef3ea69acda565ef57e76d6fe8fa03522ae9768d6f300ddfb3591fdff8379cce2f24b1f2cf116ffd790db9db9c0b6d223796081cfefece4e725cc7b4303c11e7d3fa139193372afbed43e6270ec7d80867c3a0742a76fac56f640e9dc18ac592a154c91cc6a36438e1c28d64cd15680c53b21adecbc59611672dd850fbed1eb95ff63a3e48785b711f0c5438418ee210a79f30f0b790965488212ffb98375fe153a5abe65e5c5a1c65df55ddcaaa87c79db67a8e5e136614c10d1e284dd149e08690c98e9e262b9a28893de092511b4a1e00319d837145c927828ac0a86b7953164ab5c55a905e984197a959ad70dec4857199689dffbdfbeae914ac5277af374be22cd896479d0e786caa1b206fdd813f0ee98f50a5b70d4252d568f8f4ef7581507958ad0ae861348d5bad258f836f3fcaa870b8b2cbef5685abc428f541cb0329d983227994feb1cd2787833411f5288c7a4f159227eae82ddee1bcb3041aeccf36e1c3e2bd0c9c05952eed317f51b27e686fe53e1573412a74c3e79f4bc40a3eb961f43071da8a8956faec00333f939d8400d75c1388d96d24e4b792d112b51e2e8ef0a1dfc10ffbe80b3dcd48e18fe69bc7c2b9bb2d84090ad03f060cd47aef2d75999f20fbc9c5e0038873231f691e8300f0d937283863c8c33dcc29743d0c9da34d40ae982781b3bb8d57bbb1e0875e51c325fbb428dc7b524ae4f28dc4ae25e6f87eb1dbe2d2e6ce6e639ba307ca531367ce6f7a290322b9cd33f7bbd5b186f916cd031b93fc49d217c23153148d081c83034c00322c93dba8aa2edfed4ce1a26c904c4df3b6a2af27e450bb9963c9755265648f1504355a8a9bfcaab2e2e1ee130e83669068f8193bc030177a3cdc505347e2f85331cb99f76a52a34c9bb67891b0fb2668a18de4f8ad00404f214c0befe54004e5d05b794e7e8906cc7bbb53dab71a6fa7d293ac5c51340b8a1bcbbd052d31491cd2be2579570ad251a1e4b927d39dca3a75b8c40555459e91c1146902ff1e840c723163436a179e34efe24ca188cd9529c8c95072c9037c13f00a0354c898e260411db115e6e0cbe1a1c6011e5a453deb2fe30ae5c5140937f0bed70bf0621ae14179055f22554eeae31e2d30139b7be480c897c36c43de0d6cdbf4db30428cb77656f06652094927ec24442460d41ff59a50a74192215a19cc0cdba2588bda407a43e49ab11bf49ac799d2c5db24dfc8547a1f46f4aa1dca84ec92e171df0d2a432e1ebfb935e8b5c2089cb717f1f27ae96750d4318e0756a8dcda69b5416e00e0b3a58700c17abf892f89e42798b83aee19344269f1c505e01c97049cb0d1420872d3afdb9fed72de9a0eca0df195453b31f2bb6ae8fa6926a4948af57d7ab0c838598836be1546c8ea14aab616d14d5ae6405797db1a98d683eaeff729becbb02cbe43bc0cb44ee7495c77cb93a0ce7aa63d251309fdca6f17ac1433d0bad2d1df5f5a5f63725ece921bf83bf6d1d88fafdb27debbf4cf351a38cd484546dc13494c4de5bc64cd51f8270679e89de8e5bdc359e03a4c107c2746c72cb511abed9ff3c7c89dd7c32423085991cc55b07adcc01d7908052748a0ac817313b35dd3e9594a4ec6970c490160e58d2a20638a5007793b1428cbb7fb0b3902d0edaf6c0ddcef58bb66701742ed555e771a73b458ca6cd211cc0d24ea6fd18e4013c4b155fc095aa8bc04ae282af5a4995a660ade8a0b125b663d85b80917ee16ce6a61b7d9c3f73779dbd9e151703946a03e79cbb5fc4b9328094fd755bf9a0e5968ecb6c38bfa4b97d7c2d6e8ebca409fc64b1dbd2197cb14a67baabd1363a12153b39bc0ba7adfe4078055c17fbca495080245b3a8d2e7e4b72806e750d46cfa0bc3116fdd5a32d326be1b4f82c212e1e7ad9390d221d68a61dfc39df3e3ca4eef4b835c47d2b9e3cb346bf2771778b869eb38bfa1b0cfe30a6541ae4361d8371230ab0c438e42fe903165a47ea98dc9049c737621d724fdf4ad0067a43cf8aedb906991d21dea3ccbb5c424f23ee5dddf49ce5cd8e56a666bfd19ef5cd7dce470640265fcc937d78d65f72c23bf8cd0d869c24418359455a488d36ba9c4f18757c72e2e0abc2f97bf5327b71ed42bfa2da8c8243ee825b332cefac021bd35dade4c3e2ae95281abd16758d899126ddb227516f9e44cc9082d57ede8463b1b356aabdf94fd2237517ee7fed14f20215e02ea38ba2d7b4dc1d90972ba234112bf39ea5c906ae7ef178df4a199bb94395a2fe1fbd18d5233675a279c95164bb606dd32896925587d8c2cb5aafadb11a281a302e25465b8fdb585b17cb85979a7a8af564587b7943547893c36f966133686fdfe9bd548d9bd7a7c1d0f7ab3befa4930d06ee0cdaa5cbf6f4917c7efd06ab8419aa5fb6a32a5c914934cf6fdb89c32f53a82dcb813015502f85611781cc2621d0225e486d01cce4a267ab7438141d1beaf5105587a0226be8c6bf4c447b3d9094cc9db1ed49d91a0a51a9a08284fc3068e8395c70aab4a777a1751cfffb998867cb745c7dc69ecef6ad3ef0896bca87302d522229491e45043ee08cfcaade8a8750382533d1a16a7712ff07b73ab3166fc787bc351879d0780156daf42a805a622d61fa9859fa8d10afc9d54cb014a17a587909586f3528ee1ec2c92e509612ef98f0e2309f58bbaccedaf702b443cbd2895d7e6657d3b7bd6601da15aa98e5f0055a2ccccadfbd12f8c211a61e20764986aad4606dca5811b00e83286f068a2aea0536df800e1a7cf0a5af95da8f73d8d89a918a86b5df5b39ab798768eb97d9cf3ca40f48584ca848130b0af1332bc7df163f75494672a2ebda5f364b7f44594c64b331d7d7604c861302d0d94b59dac11f372ba7e456aa83d9d2bec8c2a22e0dc2dc689d55d30c2ea7bbd4249f53cfe135eee78462de2c83e389e8e87da13b13d58537307d2d434af4f0bb75b120c8879b548a3bf62819c1214881c697232c2d121bc5e9029efad98ff1bb6423fdc8593faad54768fa49d75052c8cd06e4944038efa198392c6c2d6486422cdbefa0a662a35f0a1aadb681c207166af67ddba68649d59e8daa0c588a02a619f5fe03ad85527db56065232f96be8d16f2e371a15b4c253a74cffdeef4e8558ec44567d6856269e17da1ea895947e9a17bb61cb586ace373e4dedbcf8e8d4e1417dd1d463acd755c38708b0a77f0cce86cbac362ea0acbcb8f8db188a55cfb0ed64ff60fb95797efab79957f421803b648b00ca734e57703c6a5ae62c18e54707ba0bbdd34c72cc3d56ad3e186a508973f616cd2927879f6999aa5d7d4ffb97ee0c233b2c558cec9cee6d6fbecdd0588a12034d46502fe28a76305d4a63ebe31659f6aa725787033e08a8cd900d1f9132e7fa8b0fef381ef26668ad80a71448a1d74c9d4637d35bbeb3d56f582a7231d928b78189091cfddba90ec378d5044c581f82a20783817b88f0693e71260efa270f5577bf48fb1a4cd34f5998d98b19a5838c430766ef969a127b46334148de499cde245f19d73eabf3b424e9f09e7964d0e526ec7275510c51931c10ecf0a7c7cfb5110d27060d3c3d3ffdc01a62b7756e8737481152cd2337debab5c01df519da22e0e305dfb9f28a531d6dd22d2b6e894f662fc60a8ddaa6ec59b9ead501ad39346b9c13ff2870129ae59bbe1c5d0304a813ab60a3f855f291a042e7a00f86ab56c66422755aa34077aefd338b55bdc2eb8056ce03da00566a78ce23f15703d41b988b00edb34638734fe3ba915ee97672be46da3a389134fac5c351dd88c7eab563a86e5794be90ea1005171717429d159508e4717cc98853f504f1976d0d49050bbbfcdb3413e0e459dd6f42b74b04555fa3971335a8660032c7f6ac5ba29a6c04497025063a5b3f2430c7bf6d10c15c308cc037dd177f1ca4dbc3334e067920f078942885dad8136815eee93ee01451b3638a79db42a2169e90c0144a503ba6faa7c5a30141a37079d9c1aac1bf75499a599a3bd1333d9a4dc6d957636a2dddc6b0d63ee66ca4679f5e5bc41c866ef25645c4fec0bd6dc6800c7df6573b0784e66bd9dffa15b7e2ed6b45e44b2c446bdc728fe96365ada24b4899429a5aa07a907b20731de3396db7d99b590d8ed9bc69c26a8f6cc53c7bcc77bbca746d4f1036804cbfd0b4eb6c27096bf163cd7b1b8f843a5559565436ff5c0329f2cebaeb065d6ca5a592b75b356d6ea629f1b60a7deb8fe9e368475ffcb538e88e2329fdadd9d9da18004cb4ef6d6c46b21dc757313d6c05e9ef12f6996856ebeb065afc458cdd0034198e3462331c2574822d1605a17b55acb3e24fbd80efb9b9cb3dfd4029f5a0a2aa2bcda27870524a89c220a40695f6400a8eccb30a77f8e60c40a50394536190c01a58d4e8a645dfca0b2d149cc7c0ee9a8e560cb7a9dd4746cf35829b414db2ca0a9e82c5a082a803247ed88edb164327cc230ac62b95bd6b8be1bd89602c553d90e82a5709d6d7278c63fc9131538b93a5c7f250da5a1384dffa9775a49145bb68f92f6f1f1b9e2d33ede8fcb85d06dae892d4d26cf9dd26e10860e0b5b6badb5c27eaa5c7f6f4915f9c49f3ff30c17bdc7f008b664d70b318461183e69c31486190f9ecf38317d8d6db66fb07bb69c87fc4d4aebcdb6ab6977cb6ea5979b757afd3e2ea47dcd3e4dac9a266edbf669a2563531e3b84f1369a789d3f33e4df4af418ed3402ea4bdf6739b4e67a8b932e15524b3561bf41ee441d383466fdf8e39f647f48da0caf926d35ca5e6181281dda423d8f9620718f6de248207d56a6d18fe6b4f722d49d8c9def6cdb164588cc8e58da03cc8825b8b6d188aebef416c33ba2e12ddcf7fb8e4ff719c0ffb2d634e8b8fd17b4a74d52cc5f8207dcb93464fc5f4b73ce95dc4181f2feff22fa3abc6987ed2e83cad598ae9910647b9bccbe82da3ef60b1a5fffc7094dee92873a4bd151985435f0acef7441cfa96bde33d5ec575865d6ae9cf13b76c495eb7e2884b3b2cbef4a0ee0a5b7ad0f50f5961370faa1ee441ae1909b9680a0ce32e4e266fce9ecc9d83e31132cd1375aaa1c07a610dffeef628e3b2a5e97a6832997e1080982d1b093bd9b55a1b86ff640f8c5872eb3c91e43f4e9d2747f99b4c24aa6f8f259d637fc84a48c5bf37f3c4367c47c03ec7213ce35fc5b041d7df3b21d8b25eaf5ec37b33579b0b0bdbcf5858224e7c70cf4fa4fb7e22dcbf2c1127f389f4774f849febefde477ff773f4c1fdddebc0cffd7dfa227fb8f7e7ba691947e79c1f0d8cd2e9e7cf5a7307dd7af282c168ca11528a20a3882c538c5072798a11474e97a714a17379b290596ef38471fb656cdc2e4763988c1491830b809efcf0451358644f04ed2066042fa45081d7f5d7f43ff901c1967f7f66a3056520ddf9b57263997df69a902ccb5e4876671965947183b0a590ec4e127569414c21220a9e5b1ab9730a11aa4b5e9e4283d4f5c0a7ce5f6659f6b4e73ac8636ce27aeebade7ce27a544aa9ddac0451bed0e27bfe8c6dbce751bb990cd9fd4240b588dd0f88eef79ef641b91e371196a710c1da7c76b4ebbaacebb4aeebbaebbaaeab3442a59125d77b16790a1139b89de8d5923ffbd0975ce8b3a7cfbf81107fc97d22b0dc1efcd087c0186e22102af9b9f7d92235e33d8b65df9a6559c6bf818c72bf01eee773ef4f4108703fb99ff5bbeb5f4e1018a94c8d78ad6d01032ba00401e5226b910294d7af32a85cf141ac092ab030620951a05c64580ea84e81932b8ea05c6401098a882e584340b9c84160c23f00630a2b5e2817bd9613503909b0a204305411b0065074c1990055c4250accb1f0c5155817414ba2479079ea2c30488207657a82eaef187fbe62901f2f6b90213de6c93610ac5f55a3b2d7d9c10554f6aa46695fda981ed9c8d787a3b43186c798ec6bd07e1b8938f15143f6db673db6bf71ee20e29059ead7c48fc7fe187b38d9a679cabe633dc64c39ce1c3b32d9dad698f06c9f9366da568fe0b88e2733333328e20f71b6e219511019a1ed99a396cf65babbbc7c4e22bdbcf44b167221895e605e606060482e2d3030230b030323820981303030a6ef081818b549d90be8d840d43a3ac9b4df1060e7e74db5cfb2186d9421bbda6be39091ce39c5f058c6e9f63b3c6a1b9d506d72f6f46f0865c86ef6d938c4493a7e9b17b69b099e4743566cc6aeaff1950361e8bccf8644d5855d5a5a5cdc65489614926a584aaa2f4cc8902c2924d5b0a1157186621c304570bfeff648e90c1afc62a2460d7007c7732c71b03b8ecb658d0e00046005772bffa228ae100000d48811451a33421544981792288a2e62cb48b4a2288adb8d503f4c10301542fda60e0aee4dbd7e2f31f0d869fae4d12dabb53bbac3a33e5a86d73f2ad6eee77b9ab83d48df031dc0c2adcfd973200cd97b1e2883e952eefb6655fb8e538159a0ef8930cc02fd4e24f50dfa2dda02e4606bfdb8bfb1d2281e33151f9e253a299816507a5849b2c47ea834e151e96081b9a028610565b9f2734d36c2c892f4370c3f549af05015d5c102734109ca72e5a7d5c48fb80a088b14572b212388af849ab08d23d484a53c43fe6c49b0d47dc451da92904d3ae90c997886df6b8da76ffdf48672e0895d77e09329ed9fb77bce1962e2d567999bbdf7b494b9b5fc5b431bf692e600d23766a8095bfbb217da81adef75fb57e7a4d99d978e65ce659ea9d5e7e7dae9b3c6d03964fac60c8de09429edd7f92ff48df933942972ed7803cf88e6bfd0fd7c8e9bdd97fe897eed1796bee823e3d055e66affc5c860aff6f66a74d478e079b67b2ce9ac3c8363a66e69a2226dcba856e9b773f9371209766bfd2a4f8d1a402187b8b0fef9cb1385bcb01ee86ec30fd7696041e38aa661c5695041411a55501a5664f5d3401a57341a3ab065ad5078847868da130641d118131a998cfe1b63c0716bd2334fa3b185e0528bb5249801a3f7e726d7ff13ab0ecff868ac3a70c95f0549ae33b9fedb58c1d6f5076b60cb4ed59d79aaa9593ac2340d0493e00998a6590eaac016b89aa7518ae7ba3796e093eb60cf0559d77f648148f1d7752f1f0c0f56e3c6badde0a4bfb644db71d2e7a8f138e94f6baaa6fcd44baa2a49dda94d564faec36ef9654328e196953584eb9632b7f25cf7da73fde964398fd3388d97ef37b80f315f9873cef18629654a3fdcba1bc7496ff5ddd1aded38cd12b6692534609a0e82497fd776b6160176903ad2430b92b0cddc6cf0021f569d5167ff66ad36744a07ce11827a4fc039b656df50b1cdccc672b3010fd7fb5bc94eb35a9da36530c4660312b535f5b5c014db4c5005aa463da08ab278c66b84cdd258990f8e934286f8973575fd6b6a9e6a543e2bd6ada954cb2ae4364cf0b232461b943603b14db6c3013edd3013ae9341ce53894bfe3555ea2c79e85231745fb0d8925d2f5bd8b232d0c5c2960c04040808080808482aebc2ab3cd6016c03639b1648cd890ab365e6c4f5274b7cf2deb7b164e1fa6b238f954bfdee97244992244918100d69f5b79189e405bbb0da370ff266e36b61b5f24328b0f3fb6f9cebf80fc4d9872fe3343463923dcd9e34956c8462f9d7ce609aea248dc7a4ff2686fe3222934e83a3b4d1ce12aa4ab1760ad114a2299c64dd32337f562c8f22d10ed6da2eec17c26249eeaec2120478ccc528016042d52f57b84c6730033cf1fb4e6cd4a42213efaba8aa28ef452f2c8f369ca4945d3079c9399151212feccc28cd28fd8981679c131b55b9dfc184aadf456105f8f08c27a0ec299759289b07d73f014cc37ce253ff48c1a40ba88f7b23288f137b25366b76cf7d37f62aa69b15d3df8df5ebd83c63b76298935d935d935d935df3cbbec2960d639bee877148f27335710efafdd65f065ce217c628824b7c11d03798d422b8e4ef210fcff8c3fc63f8af551bc311068b7d3f54697087a6c39cf3e7e9fbe8ffffffd31fcaf8fff7fa3eb0dcad3e7687e8c630a309b67c8e39410441c0f7f9186af569f6f18921e59d01e13d1c4257c8fc6c903cd39f652de220a4823b42f7035d864f08ea9de30c3c799640c0226012709017135ce798d3799b7bced07c2a6a16d75d0831f08c3fc782c4623113cfe090e91c25bed1cf527418e0762cc8ceb6e3eeee5ebd6358ec0b37947ed862c4ec08ddf9eca7ef79e61b6f706f0c230c70085b86b77beeab2b8c76d9806301f1472a35436d4c919776a77e6ec794f8d1aafcffff9b4c24c902125419e670155be400085544e3620928facfafee6920b659c0f55ed9fa965c3d7105f87bee5eb97f9e7eccf99aaff99a2f7e0d619bf9fec4c91e1f01e7e824f8864b098e82b6c38f2043aad8ef961c04430c0dd9d3af211b6bb0ad76f9f8f7ca7fd542f8b265b7c856abd56ab5baf5e3dee1a0b62a1293c562b1582c56b358ff930536cf9cac56ef78e8fbc6927b4dcb3e0a665f4118b2394947b0f46fe62c8decdc0dcce79e8a25c3eef7a5919dcb11117d4e8c8f51072328f0fdc3197cf222b8f3150f564e912941200125b2d77ffb611b866234e6d8d1df3828c6f800dfe55d8ca03c2b76508c8f960e9a2577e119fd68f4951d9de5bd6061bd10c1961e731ff7f1d84b15b6741f9febcfeaa016ca1660f8c6728244640045092191512c506454f81d16b69c574c16abb63c0793b81038b3e46f3291e43fec4b86897f71867409f8d445e0923f126e9614ce84c160305863a971cbc6a2691af91e15fb950bab721da7099aa71eb34aa172458b56f29dacf708c3fbd34a5a89efd430ba73f574bb8603eb7f33bd3d879818ec5c6596a22e425c582b9674b2c5509db38adc45b7acb754036c82259dec1d78ac13e3e9739417022e4f89e2c37d02c5de16fb4e07f80b86c5499f3dd7759c74d5f539291deb9d3a3d8f9011e41e86bbae4bb93ea35ccf71c96979d17b45b0255dc5f48fde084a24c6ccb76f0445c988f908fde8a7e843f4966671d26bcee843df22da9c1c582afe480c99b0a5b358568a97fc6bb5360c3328604e2847f4a3508aed7eba57e7ea7c32203fb5fc774b1a03822d69ac95f9f8e9eb341f6c99b5b29e0da873358cc62a3886df58fe77598b1b4bd3cc6a7c604b2a856da6d028fad4b966c99ffec0fb925ed17d49b9a046985fd2245c20055c9ee2e2e2662db6d1ae7f9604db68efef699d6b9e68b5218d75ae4bde8eb3273c7383d83c6538e892bf675cb0ae58fd401545d6444ccc96540a95e2343ece00d5a35a46f6d293ace5a47f925b3a14ad0596fecf7f0d8461e41ecd4933d1ef038c7045091a2f4f714189b56e3ce6c796dee3b99ab8963069e9cbbd0d07c4362db00d6df9f9d965288a10e27a2d226da13197c7df092c4f71c2078aebcf77b6cf902a6b39e166ad79a231fa04ebf29428539a785def99a7ac35e5092c539ed082092f1e5069dd2996300fa8f05cffec4906e5fad71b121bd5a2df39c305b67a7737d7dd3e335cd06f0158e5e85b12d7a75c9ddee3ae9e96eb6766509c663ed348f1e13902b131e9dfb8f75590c7d318dbcc9fff52862db3d43d128b42a1601b3ad23178c6798097c6aad8d27be81036e634d90f519694c0ca2d6996eb1f4e61cbac15a3e1e61c59915cc2662eefc97ab21614d655b7cc5ad90da8d8d4afbba338a885ec3a0b7cb25cf21771a2eb2c2a4d220eea2141414141414356b40a9b6c3033576631366dd398bc8ec251abb547666d9ab1bcf96da2f695823bc8ab8d96c4699f06ee083d38963517fc4d0c1aa3405fdc50d302d18fecc8be68649aa79691bdc4b930a0e5e432dad08285b1d830862df9f563ecd6dce564bfa6698e849e7eaf9d749a17bb654318b6ac644d3b79bf1fed9c3dd670b29ec0765fd65bb20bfcc41a937d3796360361e0464a5e6feee06ab5fe55e1b14c29aa2c4262d3b29d79dac6d0b5b17687303c58ef96610c77c1906db21d26b6e3f1ce49129c83d4375a6c436194e89ab0cd7c52e710f9860be0fa97309e653bdc4fb7c3137a851d0febab2185d932dbb9e4cececececece8ea85585688f27f364ce7e76d7d1038ac852adb53689867517dd32311b4d0216caebcacfb26c766aa7837a49bbbab395d33815204ed31f7aff21f3f4bdf79fd8bdd775cf898c0299789f79ef39f78fdbff8d9d9a25d52c1920b4028be3a403b13aa6565a06cc5dca921603359525ae5b3614b2d2a64b6c7052b483dd5153a36535e4e660113ab68d0b2f2ca595ab349fb2675b435ed8d9bdc4a585cb75931d992693c96432994cb3566bc3903479405ae59ec57558739aa609c80464b22691c994e32c98e2041254110ec9112811903da144a39398ec996deaf5df78d8660157545f243615d504b96dba66cb96355657eba16375f48ab0a5c75af5b5d0bbd6ad81ccaed1938fc1257fc275a723a7023b369598077dc85b3fb6c67ab223b98da5a93479ec04b6742c589ca6ec18908d309c663ec902e914dbf0e53c86a3c6e4a4af566cd331efc17f15f355cc574042a368ace167c983b0292a4ed6d7365ad671c7fc8c6674344d95c73c88052ef9d728312962269e7999a82cf198c7ea0af496215a4878df9c81674c24a55eefac64e008304dfd2a537daa8f0f57b94a67680301d3642408e225e9a20571b2fb8797806033986cc8740e08902f5421bdc5b15a1f36125c84ad1b906abb1e9b244bf2e6851848b25b9b6b2e77ab63fb19db6f5f026073bd54b1e54f72dac832b16462624bd2a4a1ff9f4dfaf384f779dfd951546bc85a300c59ad3db195393be3c66bf70dee81ccd3e5923f57ed04dab6ea373ce3cf72b1582c1657371d374e368bc57de16962741c8967ba91e425ff26bdbcd4a7d6e285737416ac02db741078c66d98b66df3486ce39486506cbd2549fb9d256c13ea2cf8d42d2eadd8c6a1f889fb2aaf700ceb852bf117dbb4dc530c5cf20f9fb837f12555c71ef124ad049faa0ea0e8b2586e05966729c8f71228d647eedee0241027bdfbfdf224ae013e75165cf2976232954aa2f8027a01f9caab413a822dbb45ca38e3be629f1802cff56f2db0748eed5b6c2d78bc94b02599dd9244ba2114dbdc37ea6b608c270079bd5eafd76b76f7eba5c542477855aed76a6d1872f8ffaaaf202658e699155dad66ac6ae6c9e45c735d3535975f3ffaa7f2ec08c12ed79daef0ce39524ae7970cab4eb0432ac7a71170c99fe346a321b5776265d46aedff1776392d8413ad1fa08a7043f800d57d0e0b4850456850dde8a4ec57c332180cc6fd42f0a261482c8cb9635f10b661ae613ce32f433eacd5629b5e750f5cf20fc3d188d3b8da2037eee8ec5b487663b2f16b62ffc6fc3b0806e4ed8098b0060cd630186c4b85506032916418fef0d6010a123da8229b9420504e9eef9c3d6cb331c1a71ac54ac1548c804fcd44942e81aa97e8257ef8bd310ee8184bd2746c696b845a0eb60cefcac1ef9e9e9e9e9e9e9eeef9e196c0ccfcd99cd99c324c273e5071996ccf6005ec36a69ccc2fe224a65a61a71151485645266a824d71cb031c806dfa3f0a4ea8828721ba584d81722b331882142cc8a2478707943fd7865579534ba9e392d3885c8a1ef43dfc7ff8dfc4e0ca329c9ad512b0eb4488199d94d2e7e197f21cd9b3e1a79a39e2e81b97cbe572dd48d183d591cb3d40422501977b8084756bb5a19b4ca2327edaf5c978cf29d3d3dee69f97ff663215c76f8ae46dd73cb1af9c5c72fd7b2ced6d15cf20615ff3bbc1878441352f30b0ec426e08c2d2f71bf875691028b8a7b8dea2819d545ae5e2c1dd6eb16cdf925f9dba4cea8f97ab5d292f881b6fb2d137841999cd916783cdce4708c3728fc72b244cde1edb354bfe2e977705c8742b3664c5d2efcaf5675850898a0d0244cb461f16a8f5a63a95621b325e681c3f71d9f748f11390590ac34fddc44b2fd2c481e30299960613db414c930126a3300d8e936c03e434f4b9a0bf61d8208e33c0341f93fed958b9cefbc090c9025ce7691f08ce100a6f8f373e7565a968694162475b8b67ecb8b5b656b7cf4929a57466d9ccb21108a574b4a19fb3cc9015ab51fffa9e4f181eec16b262391ea2ff40226c8a95da627fd27c7c2a6b9eb4d72cf1380d578ff84903aa3a308d16e4a46b5160a7d161a5369d4de534b179ea23db6b9e3ae59bcf3c09c9e2c3dd2d36372027fd373098f4df94781f1812d9518bf78121911db5b4930f0c89eca8c5e50343223b6a7189cdd3c6036f6030cd660226ddb7d43c8dc62d082e7916bb1b0db6d4d6e46e48369611b6dc5a2f1fd6dc7ae6c9fee86bb5360cff6bcab4c15e5a74aa553ca3f3b3f9380d07da518b0ba96b6e14e2a47f8e38c449ffbee69620e8ef6e168b63b17e6789930ec383ad3a5b2b64c5965b2b02dc0d812af048147ce6895d7b6940f30426712dc8694015683ee00ea80428fe3cb98b58d00578c6bf0b1b3864e02161c56a9acc01ae7f5883a4851d18b67618665f8421199a9ad6df361ddbb6755ef2d739e2790c322bec7e0ed9c2f66a3b8dbebe08044cfa83de7310274722f5d2fc2adad04bf32d08482f8d989cdafb74ceaf8eb067e8a46b7724f6dcd0db52c8931b1a9bb59af71b716851076963db9e2ff7a4141337f2602a65583f668eadc4c925acc5dd49d243d60843d0412772780a2653b050394510e0c31228a0b42f828012447105aadb7440e568dfe5e05baec0333aae2b802ea0139d6835d07efb0a68bf8d31b302db73a81e4e938ddadb30c8101c2054cac9470f86350ef5ad0a2fa3d16ce4e093058065284c73523add338d9a4ca64dcba8b561ad0d53154dab2a928ac433a42c23b10dfbab48b70718c840a5fa81400c532357d3b1fe53fc2ac7829c3a35735464a8542a958a54a9361d1d3a74b40e2fb98e5ec23f1d59a6836d422f3787f67fbf3e1843437dee6ba8cf8d356c6fbdcb200cd5880f64ecd040e504103f28414d60071a9441043196e860069bc85b0dda73da6fda1341f56f3d56edb79c6d133514394b74fc204370804c066198389c6a85a48a98ab6894852dc330c481c16030180efd81c7e2d839265f54dff0af4ec536c172df58c1b582cbe572b9b21478bd5aad56abd5aa86bcb0b3b30ff96d7412173947f7c0377ce019ff52002fe2f0a7a52b02c54675df77600f909fc0adc2d52a0bbd1fb8a8822a3287806290c9078e3f6649c401324b44b01e03b6e95592663949bf5f6c60afd818acb2ce4193a47bc5334ad8b2e687051cc7ff6b7eac5eabd56a95d1b95a550ff5b49060ee44ce90472925e709038db3040bbb71e1fabf5ae5820b150683c16038ad96cb090e674dcd8fad66c82ca528a5b6fae0f5019ee9e76e8f41b2d8b297e89eb259ada46c9abdb605116b4cd586644d8debca0a7684b8eef95779e64d97bcee7831fe4368f07e7b1fde6fdf3de610e99efb6dfc3e87881322f5b7fec4d4ca7160cb4e1121b27ded3ea6df47fded7dc4f44843fded6ba8a31d6be8c656e164599665db4ac7c94ef5604b86a5623aa6c7981e5b1d47947f039807df740e1d7dc34f3ff35c850e17cd5ec7bf0e6d27732f891320274f605df48debdcf39c522c47a162ddf2af6a75fd65388a1c92dc1ef344c7981f5961cbbfd9983a400cd5ef69a08f1c157a50f4b31ab4dc6bc073ddd94109308a8ee12cf987848d80115e5c44e149e284104674308517ecf860091e44d16117810d884842c9ce1043e8005584162b86c4164e408107148f01860b9820038817f01043bd70821f24769ae04511c040f9fb379d9e28e4be6d21b727a272e592a61a9e8ec5aaa44db47def78bf7df97387c5da444a8e701d27037cb2cc2506e3fab7499bf44e6a04ab7ddb566393216ddb0644c8909c1a1aa3405fb4200486c02fc4c49e460cb03fbf796abce45eaaa9e1a9a9d9b69f53f453247a065b241a6f9822101b703ae5343fc5edc56ed5a47a87c5309cb90571d286f9cd77dee0e43604a7b497e464c9586a4227b7bf53d4ee0eaee2fc849c04c6d73d3dff9082307430eb646a9ff7313b482ffb76d4dced356b415148140245a391e7b5fc376a1965df6735cd6ad68a342d04825c58eebbdf3eb1a4975627bb668e3523efc7c463973bd16a771dd97bd9d7a66eb51880ef82a1eb6533f3ce38db42f445d7a324a53274febc73d26d6e74fbeae5e199b55a1b86ff2469ea3e0e84c1033ff0f340204256644521db8216bcc09060482f307fe340b86ab6b4b86c624ea7fc88d73c4e6a5bd8d2de1a6fb14df6348460d29ffbd20279669a56f2aeaa4aae9397679eeaf414074defba92d08a42e0e73dad65d35ee264bfd33a67af9498bc1ab53ccc935ede69883344204230e9ced19defa31617d20b8c0aa1672f50b02af0388d5faf9bcb14b5dba246ca5ee85421e4be300cbbd32e0d51ac2385d1b1ce89e5bcdd8843dfd253d365345d5c462efe4d0586f473e4c117e6fd1b0643a2898119fdc8cbe8499c147de5227aca55ae936556f494eb38e947445f79921c3ffda0af1e1c6873d67e2ac5c919f3d43e29a01d18cb573ca91df4f6d83dce40409cf41c27fd5f4cb873104f3f9dc15dc21ada735d7d8f137dcca0bdfb6c9eeff9d9fff6dacfeebafbacd3c23b438fda3c5cf2af48d4ef23f8344489eb4f80236ce9a9920f6dd27a32f2e00b857bff1f9ed788258c5a4a622b146fc6ca6629ea295769337ab01b0f37baa774e8ca93ccd0711a1c86e4d44fdd6ddcb66edbbc6dfbb60ddcb6d0b689b6cdb677bbbb15c79cdf9ce09ca1394573da394773b6ccefd1e43faf3672f7d43cdfc7e3739409678c21597978dca58227e08921a610d7df0b819fd771d5fa3f57b7f736f16fe598347935e454d9972a99a6fd601b67544d8f1f14e0d307fc0942000ae2334813967e2cbc592cc8dcd1bc5631fba4429db32ba74205b240e6e9e70ad5b442df6440f0c06cc346e70899fd6f9cce2a93e7e4396dc87cdacf4bb50f4c4a5d87161275db802ce0e48ffeed290f4c93023e851307bbb8895858be3d0ae9cb3ca33d05b8e4ff23f54aa552a95488230c71a452ad1eac90c06038323019980cce3cc9c8c06460323263f5c2fad7b45003ab81d5c06a603670421c30180c06c359b17e7eef958bf269246dc2c793024ae9143bc0b0eff77a5c5dbd2fc3d07454e83d51048a409128257a0b5a11113cadeb7a9cb6698c8f4f8cf1f181a1181fa2075f347a2aa67bf045dd8bbad087c0de8972a7a7fd44719fc7b988ca0a09fda1f3a8642e16253d0bd18c0c0000004314000028100a860322d170402e9935351f14800a7d904278589b4aa35992a3309032c6284318218680c00080c8cc681404b5b220f9cc5e6bd3492d34d766ddd02c8c3e5fa64eb3cd378e510d666d1b230dfe3827b357333cb6b240192658e0e40adc68a8da53dcc0ef3aeb9cd64ac2b925bd57efe8b3df3d2dbc1df83114630bda482d40e9e1b9747963bd8ab829dae53fa6c4e850e6f0e8038763e253f4534041e8b89b2ec538e2c7710d05ac49417048c945506497e0682b7cb2cb558305b36d020dfe0442cd6c589b98d83cb5928168f71069bc42595e49322875ab49e1755a1729a952358eb4391f9102d398bca7f0927416c399688e726de656ae55d349e8083006eaf9fca3fc09962a3cb3c1338ae7a80ac8e6f22c2c4485ce3b26257b273eab4bab644876215b5d49c7543c8589c62e99b76e538bc91cdd84935f78c136983a2e0f0f92645886b678f5c68417adc31aee1de48dc9dfbd002e7bcef52029a641c280b920a09a1550cd8572430c4e32e424f2a4f625cfc67b35627bff09844e73fb5ee50d969db3de64d32a84badd967a3e2295b150359926226f699b81e428533202ccea88d7c44fca73debe9b286d9990cac1909064ae6fa37733ec4edfb9360f483889a74f3f7062785aa73e81c596e60e88c8b01e256836902013d5095ec93e349fe6ce77496ff5902a8a09d68930d87c6750bee58e8d4e62937f7fd114528bc8c2ec13afe011a6b087d7d1a67d4b95dc57498d2ad83a113e2a4b9fc2cf5dab0ea343a9343309990c806b9428539b84df61d5fcc8d6a7576c96c22bfe4520d9d17b8ae171d25c5eca69f7633f2c83c4d4d2dbdee80d06c2b50958f9a948f8a1d488f06105eb7203496cf25394c87591e510a3aec8978d4b0c0ee01e25ee8c6a70ce548642803d21d5d00782cef316e80e3d55ff7f64c4dc40c0e1426a8d4feb5beb29c3fc9a25f817452759269314a45d215ce057716325e084db2414ba0c7019e4a0557cb24649159269c222f36ddd224944e262ce5f1c9d4432653bf413aaf2d49136e2c7458742dc2c92d4a4b04f8fecdea2ec63279ace4149722b51e5a2acee0a2f77030efaf01778e4631ca607337c04fcf1f9bbb595a589c8ce43fc6888a9ed9a6560dec997ff553ef856316160dee4088b4909b5186b7a15a80c7181490fb41f67c480888b974d4a1273388ef516d1c4536b5c9a5c139956d8b579f653ecc71445914fa1ac3a00e7c3e8137f4d50fa6841014a3d84b4764c054306be24302568e644cd1c3b41b30c23fbf3dd4f9c4bd50d5be0c5a0e8646846649f22eea8ca7903779fe88b25ee59c295f5a1fe74e0fb66f85507ecc6e7a8f24fd6beccc5f1ad72388bbd51620d3d0584b8068c2746656dfc0d468df97494768f935143a44efdb5868bda432d40c53bd399b3ca751a7daf4182fa07fa22b74dc2bbc3954d956653a3221c022d66e2427cf1116b4a3542f4d0ad54abe3b0a428d08d783580e265f872ff5415a37cd2c290cf0ab3cbc11dba9f0614cace9a8a6cfc2732b412f8f549490dcc598a78204bd7c5a26db73cc26e2fba11812c67582944b047ced9fc0e53a94022583108b3e6493e77195fb92c049b92c3346e3fa3b10a2ea307ea2acbb89336e2c82f7f90a38218c766726a2b3e56e65f3494365c02100b28bb8c0c40ad5e5c69bebd12d7ce85a5671d7039e71375566f919af789c3c05516778e3629a0100de9d4c0b8eb8bc15aede334edcc4e45dce45a80eed14f8bb0e94411b3c1a9a426b5bcfbc8de7c8205237777fd631ce7f2d493ced6ffc40ed7395f823b74bb58fb866c67a2ccba87e6d20ad94e54ea840493c026442fa60bf244ff9506194cf84d3cb9c66736044027fbc0c2a58cea051a34eb3977ebadbbeb85fbf55c063a4d99a49ad8ef100d32bb6d4c178585e02ec62bc71c472adda82f830c4ea6574786a30b55fc1fa6845e8edef03448ea162239171a5ff2436e1399c22b1b0ce5f3d4a689e1a034023c7bb6c71e67d1919edbc847b0a428f1402e4d85837c5d00d2c6e8330945913a058132eda3231634b829d55d6cc83bfaec8abf06240e6eb71319f4c4edad4c043e5a93925d18bde8e37bd5671ecdd472dfa7f7d9fae404fd2cab4130ae9db3d9a83a6c3b20df635e68d96fc0616306bbfe876bc7f62433b9ba44a1759a6216bbfb677201c3c237605936a03f0b80029e38ec1957166add2984bd5f5f6119db2a9cf1647d10b132b746ad72a313cde214316211d309b0c07168cc0b8a642863b05dff53d18288c33920979e68e12fd252b77c49426e8579ae15e23d660c9a5fc2b8275e72d026a8a667790e8b915c3fea82c56fd4da04782132a871225e13727acf4a14bb26c4dc6559944af29febcb46b0929d1e02df855836fe4e440d7e9675239269554f504b0d4c6edd59cbe989b31dba58dd7d05b3d9cc04874261bc8c6832b66a0d845095245f19f1041b59e8ed39f559d99f35c8b70901297295fda7fb6adefd5db117fe092ef20b0f1e0d74c7633977b691c153178c3f45b46ab2d04d51a390080bf51571d6e611a28a08b230317e47822fdbc2f8e580752eca87dc20416b0918791a6f0343a1caf112c740c43f9614366557f6dcca5a9943157e3a99bb70ca190ac526ace5a055caf112d385674364832b58648682337e17165d2215b2c1af89f4f9144774e1c135399f6eb018c5a6c49fa527af9fdc04b9b37fc9e5f6d6ec6813c334b8ade6fdb9e561efe564685d8851b32676ea0b145fc8694725491e8866e7de41d84e51ab884ecc48b0cb8094665b8689439e51aa1121ced1a13888c2442e823115cfa156b3f854c216171a64e766d3869c96a165fe74872e309c22f94dab696bca927b6a1a2847640a356dac515bfd8dd4e1b4573c3d670e2e98328db213d28594db5bb514b004992e3ab5f54eb324b0fdd74ba9b290eadb5b48140b85feb675ee4e965b9bfe929efe8e587f298dd67ffdfa291f248cbbb0f6049701cf6a64b57248e96540fda2f624513470dc62d05bd7455bec7106eea22ecac93ea14430a3321eb55faddea25b0a2103fa403cb228ad464b25edee9fea2de470da67139ae2c6cc9d9300880f9f5dc815f850348da410f14bc08906395d521fd4e52c486eb64c96cef7e3c38d575c384f09fb2b3361a1b75204e7f5fb464681b24b1c964c7fea2f46048da41751c2ba880dde09a48e886db58fbea804f3da3216a124da816811cf50534b82f3028a881dd0eee825101e39d157c2a2388e298425cfa4acc75bc2358e4f319b4f2ff2922e5b5b874d135efff9a564ec64a63f7b0f5a2b7bd3af6c4d1f51978bc27fff6f0744b587ca7c1cffe999bf6298c9cc8d5254ac1bf008fa2853c743f705fa88c8dc6f81c0028e4cd38cd6131ff14ffb58866cd14e88dde50e09b516e78eb5a7b31a37a90c49d6023d7edc20261524578857a907d139d6797b87bb7dec7e72e4e5154c3919a79c4f9c226ab140bcaf3b83c542b2b307191d187a7af02e1d5c444a680c55c42f61f3dfe5411bde8001c7bb065fc59020ba4e1de2fa0339ec9f7f67499efa0ff46000c3808019f7a04a01b448f198c36e9b8e251e7f3254acb7a7e2ad911d11ec74f225f6404c507766abab8481aa52fbb266ecc9f133b846bcc9427f7f2c9ce0d1c6aaec1fef4068e43525f3dba49189f67ae87229848a03dd352d21b7485a33c70f4243f575937285170b987ad1b826e853fb603a586a9c337b905c5a4446848fb1d43d454dd8270cefe6cbb00f42dc4fd4473f14cc7a5bb301caa190d9f18a95b27d6890d42b89fa4e7cfbb7e7a74d47400c8d9d61a9dd6383ca342ad5991ee506bb646ef2bf107a4d6404d5a0fa79511b4a3142c88a5b0884c626a5181ab542ece0541d39ca979e1a3c61d5fc98c6d2425575f673b1cea9670fa452834891cdd984de0933b2fcbcf20c351cec0340a9bebd639c501bfadfd31a56239f15157562df829a5bbb495708d0e5f9aed5c68a05d19a24540759b76b3c5b40d6e5f3e3b6c10456ed01093abda0189a28f5c2fbfe20fe55753529cc3355c44d67e032dcc8a2755756a32b1b1d6f230b312cc6dd319fc669fe7db9e468be156a14af8f8c4f87a96bc435068bfb4cdaa72fbde0829f3492d353d0e7f9300e69501b7b8a15502b74a1f64fbda140dcb703a8edd30986488fee286a3af049570e173a5be020c674478614c5be9a1ebabbfe2aa49051bff1fd0b3da17d76f501cd46dc95de8f323f6c3ba5795fd32c5d5b9b670b192eab5494c49e1638f93555c22515fcf792ad84f1ed0f51758ca0245ac30521e5821846ba1daba943fcff3f95f732aa65aa812d26d8cf2ed5475106ab5e732ef1bbc8d9cb9fe059a2070e0e34698970bb3291dc03a822f231c92432ec946a76081825f2ed14061e50e8528d5be0e5b0619517e9d9ef95e905d98cc97c24dc84e00bb811368179f898319d2aaf992baa73393ce00fe7c02f7867cb7161214dd761f597f033f1d59aae5309241c8d2250c532b37ea2bbca68eb2b71cb74aaffcd24232bf58919e5b4a247c1c939f534129583c2021a5599218e73aa013d73ea1068169102cb4a9c3247fefd293aeb4815859c1870de770af2ce275e1fecf0160855599892fb51f33fa3118ee08b27839cff185b0051a247a9d35eefa850db992a3dbd38e1024f9daf097926cc088ab191c578a23545c4bb7932b71d1ac33418cd43247640198a2bc905775d290c266dfab2751ba7c0a23703503bd16c2ac4deefd8fdc7d5e4ee4b2bfc147ee8d359b7907cb6a8c471528d2cb4a804a885c54a5975230fa1f25d0f04b150d0c46289e864a41b27ddcb2e1086b3534799f6b0fb2183e0bae2b5dc490e31855d569bb055d2da64f3eef297f7d9736c0b1853acb01e9e31ef7f6b54fe188d43e7fa8e604cbb6bb534175dadbbbb1837a17cd82cb058664756a71e68d9b70db0c4adf7f6bf7d2b22e752fcc0081be352a58402e472b463285d63dd8ab8cecca7f1f5022ef9c5619889dd7bcaf3f869506a855f5632ef2ed7eb7b11987c4a461923723983daec943d648c45ef421a6ac334805c6dac5bc496c83f043705385281f4d0d0047a557087fb74a7c1c18da86dd08131a7a015ccd3405cc01a440728f5713169c5d10778b0abdad986b3ec829aed2eef706daf42f5fda349eac87fe58628655044796c3ebcc215e49d934a7bfd57dabc49f09b1d12f011be0798da76722cf3889b6634d2f8c94f2ef82521d41aadc757f00632a48bc9587c151e2f414ea83c0460aacd23a496795f9a82a62385a87d9132be6c69dbad6eb8398df80f5480a71de1cf099d39e005c46f114910da7ad29b24689854c8f012cbf97c2db7b586a6e38e34e437afcf81a9e05714502b704c9c32dd84ab64fe0d360284f1c080c0a94a3810e0b60a7f049d5f993e9594bc7cf53ee35cd964d318a7f5f3c05e79c604046e82944c368f72b585973e184c8c9401ae15d57bf087ff1d464c9ebdb21f0c02881ee38280d524b22693bae9e902a7dbd414a16a25cabaec532c4fff0d8b6d5e90ab6fb688c902e7381d60e3a8fc49fab03e4080b599407b31de814cc1b9579f7ea7dc4610afa6a628be038608ed464047d0d531d8ec0e8264c17bdd463055467df7b2686ec6e04a8c59027aa86c46a9e5df0482f4323d20e2127d7e6979a550fec363bb19e570aaa6c50668f7cffc3c6a36a344431a5a8eb9abd56bc2dfa77de3801022d20f1e4e732707e796f83cf8f17bb68a825212575a0cbbe13653bd4aad488cc43673534c84de359260bc38ac4ef4cc11a7cd047eb51887044d75a016b5cdf83038f6521aedb681aa0de57bddcbaed920175c1bbaa86b67334124c984286bcd28ace9dd8d85b29f06440dd18dc35233633f439df75def2805dfd0799cf9f87c8d493448157927c073c8a20724d72b426c8824f1b44270e289d45171f126a8bb0e763f97f201fc640953805ca5ca681284a98dd32f10ef3bd62555a1696c45c393aaa0dee02f0ee949969280ce93c4182f2e4db8b8bd51d5a3e9f6f85c34e5086cb640dc7d2c8c439660b3eb3c58ed956d07b000842e5a572c6739db672943b6cafdb7b88e5a4905c24df2de942efae40a9f0c8a368cf1fb2399fcd0b4565e8f9922febf11cac81c3cd302b903eea1c195afac19f40d7f664ee8c1c2b1c467fe34eca5d2f0da2a0d83c02ce69fc166be5b68ae258680e95c8b73cc9d129720ae3e8bac0028bb2072472c3c68884c6053681e515c1722d7f3819a6f88054b91c65d0162a79255c63c95e39472a384a8605648c2d5efe43141073ee5b6ee9594e91fe7ef6a8201d39489bbdabe6ec269d9a4764465b0ccb274984c9e0e3d3682d018d61225ffb4ae14764d8c0c2a518f42a4384b514bc049e54dde2094514c0bcb8522d8aab855aadfb06e4b9f81107acc9456517d22031cd148d449ef204eb4030150641797512a3938b0bc9c9a03727cd0d7b280d46933b2c8e246b508dee97b60fbd0fd94e520ba782bb5144c36795d2cc86cfbe23c0dda84b6853a4313a15fb3ac058acfa771938141253681036325d84b020cc2a186b076df61b30e2564daf21ed95c5de7514252463b3934b4393a1a69e9c95c15db079148e6cafc7dceb86cc49066589653550af01a94ddc3700031578d0f88846eb75c750d791f70c3ab508c6a43084e82f5849132f2480a10bf82b177e5795bb4e23f96a3898caa3cb90a10bf42cfd9a771c18cb7914859db3406096ba12297f729daef7ef68721c860b9e2ac52aea3790ce907910683428cf9edbb902c3d0e5d71ad4936d855299261cd712b7b92c75250e11af43241e55b83dad873736e6aabe521ab418e0bd34c3ef67dc761d0dd544d1d45de63082b24d08cb2d6268f79d8433ca7bcdc97133f54625e6d0dc9f8392d8ffea88833cba64fd450829bbeb0a2e698786f183c4ca4fd4b7e741f41f56a989bfa9129a8eb79ff7fedae0effbe57601dd2dadda6c886a821ca7f498e4c41ab227cca9f3859f37c1fa7a2159301261c14f8e974b70a8a0efd63430cabbb3348875e3181c7359cf3471b9459e3bb1b37d8a8f7e4c6fd397501a53acea5744c712c5e2f89906953341c612447eef4c0e7b59ed9f2038ff8d093aa6e5ec1b96f1cd03de966b3fe3d8a908c69653e251ccbb09f2d3cb77e8b9d05ab38031f8180149c62deab136254137a07451f5690b2197392943fd0f60cb332c780ac63bb741ca6761c9580ecc10d0df4d7ab6f5ee2c29c3249cde8220ba07a440b54e723479f5e8dbc3f00c3dceac46564191c45a5a75c9e54bf8010d96071769392efd718b94653dad26cd21a68bd39d00ec9adeac6d39353753ce773c7bcdf63407096b7b78f11fbf2ba27d1a153f1df68419ad7fb2b9f32fe438aa2cafb18eed9fe9e2fc8a6c46375c128b53483aa8d7219e7b74ce49ca83a9da719358e2e75f43dfa348cf786b7c901d0b068f167974a71ef85f15abab42d9b613d65796fdb3eb45cadd2ab41c15b377612e7c70378db10289730e717231c6aaf4b1e8410161565cfd801fe77ae039f44b6eb4d5609a45a289dd644e4467b40b07a55f4d199c1d3abf5127b0a64db4418c32f52f93018b3338752146524982810eab0b51be033007614d9ca3c92af5dd449c9e978e69556c30c21f092a653508b37768f25b06742bad8b766921621b098b8d5daf815444c31425a38905e54b562568b96edd2d5e054480e787cfc6a011aa83db25833eb2c0d5577f1a4a66d8997311be975b30e56a8d3c86a82ace881e3e68c1ec6f04a27ead00af81e2374eeef410696de52c2e82ce4785b9e9e5cb3a72754acd9045f5e8255f1c5e8d0a128a9b19b477df0d7c6d40ca91b18a51e36f372c871e55faea6c408b45f46413cc397deafb28e6ef299fdca6f55ce7e6eecd5f004e334f97df96fd921ff6b7a5151d10b8d4efc0e512ce957bc56e6c0e7f721ec1d08fe625e85ec3c4d00bd1b8c3dc76757d6f116c33f6d508ef9e89f665c92d3982efab761496dab028b4c90550d72b112b74622fccc8b50401864c1830c2bc9398611c2dbf5b1e8af72bc254aa268e9bff8ce9039af739298ed003322c808ebaf34480d1c13a6c4f03a9f2d3cfb86253aea5c35825174781b7058531441590759e61a0fc7a13db7cd56f53830d9b9a16cb1077f4c8345ae64b316abacf9d1a4ebca4704b720fc7544e38df566db9c74a0579ccd52dadd0a2ba858bcf521f5ade825024fec2dd486942509838fcdd8f49dd9ba15a321f7a49d62d4861e683e31138a674ef1a2901882d1c289a5de8d830a0ecf55023966dcb0fe0378669460f5d5ebb6c9cfa6d810deca5618d5735b2e8e86744af37d0d4585e4f15aa9767be498985b1ace1e24501592ef0d65beb6f2e5e61a7d3983d9e4f82e463352b76520096610502f45f99307ccdbb2f9694d232497ba50088e3e5e76a5e26fc664ab59b1d0f81e49e8d54bf51f0fa1545f833740b4c4518fa86e437a6b9f47c7bed97840d6982a925bf928657b0ad46eeca8c03bb67f633e45a2e91ca9b9d994b47eec72bb13363eb30db9238db70a977494fe8bc4a878ed4e102fed583394986b7ecd648e86572aa081eb0df5d37e658ea4c90b347490d5bd00ddaa3a901f1bbff322b54afb6980063691476a4753f7e8c2cdde97525c398f52354889b712024e12408b64ef2c424826f7578d346e9d29f0c770798d41c1d28d4e8f1dae0c1bfb0e9d026d587021a4855c5dd8301830cfe21b8700e4ddcea802de65f0288a851471e95854ba8b6f43dca659a3ee2d1083c6087d31d3589b1e29af27c79598c4b7619f7ca63cfd034ae346ddd10ba8d43644b181683e8458ef9a7a05113ad8c48dc330e27864857003371aa91b5acf1e79ea87ff4c50b3042b9965ad4837bd8686985ecb24d89ea5e68f8d0f1d5ccc2c46b730fa11e94026d4f5a603b5c0da8c37e565821aca6f19c8ce5981ccdb6a272dcad28045a93475c62d2e270b38aaaa571f647c16bca172bd621f0cd4144b6eb56ce0d655889847767ed4633c2adea0e61d785fa16d01e1516383a71c12bbe08d73c7290cdb727b6af162020f25f105e6ecea13bec5f585bb4223aec4b8929a91382d6a68318b308ad1643a7789c8e3bcc748df1ef9c3d8317771109311395936e469cbfaf00fb0b7b5f495ac5f25eaba9af7052138e2cc04b5da6c667b222cfd931b6b8ac79b337d028a56e76e93c440f6416e2c93c8d536e6a4e9ee27c7410559964a9d480f5c693bbcad3201e4773b6a13714f253a6c4b3cd9afb493bbe68bfb615bba417313b151890a33372dfe06483875c0066cc7ce778097c66f372476f51483f1a827470654122db922082f6e92d339c60ad357b33ff03a35d4e5e4b006262217719798d30b2906ff1c7d335f5d5e19e7551d0f4c5a79bc3c24edbc116996221f5c4847b43cc03fc798c125c7229e656e974bd48376009458b08392a0de25a82615299be37f4304071f9076e375a597c8f05a1a4545c675b1f9aa8547d47c6d4a5807565c828c409d23fff6e94610835fc19b2fd3a4e6f9b6d3675b9bec26627819421ec1420d437834fdfa3660df53e674aba00d2b7e9567be14829d9013ae48b0aa56d60b15034aca8fa33987dd0eb878c941235993aa13741f3aafe5e1814c2bc63a510e3730fdbf641811359760d6fc1f67693cb0856df356bfc40f030927808576943851220516a2e89525b11adc67ab465c455a490e6defb0f3d8753e108ecc664213da2edb457251c42689f19c50c84b7d2e379309e66a793aa89d67e480de06daaec01f4f8a1444d8ce8a57f7691cd03af6d82c9473245230cb797540745ec5f9dd8ce1f4a0cfa04b8f093c8291314b098a88f4ef8ef3e98700fefd639ac1d97e49b17cc68c9472f6a4bba9de5d510966110fb0d464e5e06c0b27ca6d90910d20048bd3e123b9b69327733f14fc5ace278204696197bb5e0d4d4346cffea93356749be7b69e81f6f5000316efd449942c7da8f5ef2eccd4c42dcc49e99cbc9ac8512d32f70313ba53dfe32c760104baa3c735ec398dd6cdab46938e0afd36c09200f8109f6397b631892339d64f5a3d202dfbad638ecd2ab80e586f485792d4b3119533ededc65c5b24f0711afbd7154ab6f87d86437eef0342e58998a6ed48d5de90ec4aa054e92a18f0161aab97e89d9c135b2c3362cb55f194f0e6708d90b36d12b40c7358b7734e1929125f0567029fb2243bc1b0ce75657206815e31ca62c4635abc1a3917e51c934117e1be88bae0b41dad6e0553b77de79754db39496f12f71ceb7dca23e3e2f74704ac21cdd265f36d83453e705cb5298f4db5694960a48e6df389bb49fcbb9da40d36f43bf53e43eb6e77a0841b9e7a952dd8fecac89ce94b519da14393ccd9c10c107ab626f86d1403c56ef2633ed02e5f527be7c3c7af9d63417b065800aeed4cd6f63bd307b0525743d410666cf8f7ce6b2cc3271f04457644a26cc173c983f69944d03538d662e6d84325c5ca78a881a00f338a6d9287e4422682319c3d2dc38eeb2e37a42177789c9979ac762c1000ca2f1da35e466a73ca5e4928d3f15d83cab21eb80e89e1f330f5ef9b97916064a4d2e32bf8004d40aba154e408499ea528b6f897d1366f566f919077f00aeb712de4f8dd6ac3e2ab7b80d657776db871a979ed4860b357cc02718f1e52002b2088c28fa23193f509e2cd8ad06c41f1a0aa2030667868ca91aa94d9a19997b75d282e38c217e860ad8e0fdb5cb2bdaa56e9bbfe77943f9f30efc8b54ec34ab46bfb2fbf5c84d0d2f62688d84bc99f1639afaee5debef25ab74f7e4bb6448362ea59f3bf249c21b06e7f49f4a937b8a236b4458fe87d9492ae0e2935b45274ac00384758f49a9dc29446641804637145094ee13659cbb623fdb7b2316290e19649f12ac947999f19de34f4caac7f8a86c6d93ef95dd52188280d24e7f61e238399abc6ea75e3aa58dcdc9268838a07419b848d06425f3f5c18b3a6618602a6f49bdf1f3c30d999fdc7ded6d3fd65a74c5dad82196249e85b187d6ea1a58244c5fd3ad76cf7052243f595067d3847b5f6b38106fdfe3ce95f3e2fc5101b46c6e7b98407afa87f94f2c1838253c920adf96f55e1a257671811c6e18752756eeededd804638f45d551fccaaa2c9c049d98878bd3a842d1f68248a49c82dc760e90d5b8c3c888f9bbc6c4a68f1a0d406cbb3ab3314abfec1c6e2e0a4dc90e1cde9688018b3a2100bb6182555f4fdc1856b45d378aaec4be917989f65e9894be3d0878a2a8be32f71efbf2a072205e95793edef304a75a24c6ab6ec1900e509dd49ca7a828865b7e2d6ca091102dfd8886358a42f731e368698f8132059ded9668683de359946a3784f99ffa88cd4a7b4467eefba92f9b829ad934b974b41661a4539f19874509aa21b9e0e53c49f13f775b4d65015e91401e7f5023ca8cff4ae7c2e48b0f3603b26b692e8c357c6cd2f4120b388c40a9e2edf496d872aa14c75aac6c0401926c584e73d760bcbe13296fe49f47c0b9ff38d8b2d8baa69191e68ee0b1fa49f4fcf6e8492581af2090d610d9a2ce9d74634eece9fdea7db0edb9ba855581af035711f1ad5aa675ec070e1930594fd473093445e2e39ef090f30161a1b953f4bf70c17d690a9be5a9a3b2e6c94542d6f285b3e8f4b04604130a6a2cfb9b01827b1ccfe20a6df8779861afd355d532ea99e77bdece500893956ac257358023473e2ee2f2ba23955daeaa8ab3909723371a3f4525aba6fd5821da562edb713898419cb1ee4bb2ddd56d0efd05a04adfaa9780a0aa4d54e6a06b6f1d37b21ec094270185b585af17e832768d741f80592fcdf82276ed40b900003b7d8d69893290f32a2eafd495409af8aa3c28f932a4ad3992ac68e862c3ed8757baaf9d0895bab16a05c55faa752597b6b464340cb3f60c8bb09c74bb57534a266359cf9ca9ffecf2cff9cc7ad976c0a915c02b8e27e2cc7964df3cfd606124e29094dc91b7cc3754768db1e9686ccec4a1278797c2232fc58d990cfc64ed7f67cea084c80482d26ce2d68ee8a89cbdafda9d29092dcf65d5a0d1c5cd1352581eb5085cb53c27b427bfa8ac4f9b98f46d18d8b872b18744844e573850d12240da01477e4642e448d92413c670ae8a4a3f4d8c26e349752b46a7019f3a47d56ea0ac6a6d7b02ae33a0bd8df4ecf19d363a0f83b28ca6ab4195d215fbb35fb44a1cfd736efa4d164aeaa3d3930c5786967753c662eb4f264aeda54019e345d27485726413e812b93a0a3228853979dd9565c9457b08a8cb62e4512495ab044c89ba627b22e6b5b02746b96973346f7d03a21ccb2cfde7570d8111ed27f22fe5fade88cd256aab2bcd2c1329a0778b324b931d9b03fbd9a41ec161b860e82859943e64b28e800b28db506aba65c6387c9162702653e47b3b81653a403f05502dfb4bbdce7be4984bb1d3450326b94770dadec5dbfba5fe18097d7d1e850441b65bf7302ce9cd6b44e7210dfaf88a87eec6193b6431d3670f9dcaeebdcf4a713019430d338244abe3509432dd97d41862940c0c624ccb785f0b2424ddf59d203216ece1de9139c111d5bd3f90532ac23c8e134fd6cfaa2461eb8b21a178a1ea9c87ab905e981f90128c9bab4a4c1c40251fda8143b5f35d91facd50c4481ff3ac37d205d943c5cb8275a15d08076cd972ca228246d8c59257f8ea5b933a05e09ef06e62f108f296848cca9d82b66d733541c1768a5f21dba2dfdfe1b400b5b4efb94e15b1a255923db020c17cee8a8db3b800920cd933f03613de04d33ef5cc1e4ca88e63a6142dbff2c5da9bf3c340302a8f36b4c7521f13c910b2c6a0330b6638b4ce235bbc417ee7be721ba19876387b78ad9a1c9b44aff526d9c965860b404e46add946e6ad1df54845662db207a7fcf121799f2a967160c092036cf1dc73f45eb207a4c12403cff8e66ff4851e449872ff2fdddcaf84f9a7488ca34bdabc770055a34f1f0dce637f39441bdc7f728f0159e72c4a6287bf47ce2f572ec371d04ecfe133f4e1c7b02969b6962843468d012d539b6b51ccebd58db2f342f1b8f44d644a9ab3668c383f8ef4a45b4f67ce006254bd79537fd634978d30a6bb4c33a6839f56301521b05ca2a6d4e7063eb1c2d4ff822c0b4826f4c64f058010402eb571b7f80995ce52348be374f0721f206e39301cd30ef089733f0ab146ca959e9a555d17af546a3326a4c20123554af6f9cabfc0efb8f00408276d5f9f0d4b56a8aa019cd716891f47270d61f5c87e0bc63a036f36d7df1a18a962808678e0ce3a5fb0eb74845ff52950eaae3d5606663d24477e4096375ae12a05d21c6f79244275ada5174af269c7f57f97eed1c66d9be2fc3a6d8aff60e2c5ce3bbb445809847d2d9a99fe85e9cca4f42b487629fe7cfa6e82d10891c538ec3b8027769eac83fa75bb5a479b19d7fa401e7113fa938cfe4f675c8afaadbc9a69ddbcc9e874fc9cafb196c5e3311eacca2eb2bf328e228797ddef29273be545903003ade81e59fbcf2a11d81921024743f301eae1e90650bd8c3331f093f8a375cea55ee8bcfd0e788b85a25fb5e500ab64def3cd92b7f07be1f445ac683190a3002d9212953c8802078455d43ecdd5b0e6e0aa7882c1269184078f8328be6d485351d5502cce6fbd412886548010c32b43f47de36f9a5c2f0d281b2b6bf25b2e7014c60e7a5995eaf96855106f697a04d5ea492d8e87b911e33b9730f2d6dd9c7c44b18d2daf2cce4cbd6d2eeb11f03cf99b153a1d2552eade32298672658f5e8c12ccf50dd36957f0c09668c8c7ec03c56dd5885af5093ad69462fe7dd32f2417d1cd4a121b1b9c010fd7ca4db3d156739afc5956b0b5771e56c59fd67a635c773e4af3c4191f2f0d70eb98c454c86dadaafa997719ac723f6f65865835205752593adc8aea7326c71e4ca16461c8641992c3cb3274109cf515d99cd9cda3d065ff07ffd10ecf589a13ae6aa6003eef4e74070e3f0a885b2ea132cc8383a6f6784a4ed0e912c6b41b095000e2c8388f9670b5254fba4231f0e3f616ed2ca4d4559ebeda285f9f2c66063c27c2930c43105847b22c29000f43303eea39a5f12da726441509837cc9e26ff7e3716cb9117539e7195a7302bcf1b93482357de45b4f77a793c5349ad454e15ec6bee91b6c1700c5860ee778d16cd7a3af5b8a36e9768790440b1615f05a8991271d5d57ef1301464480fafeeab943d7d31582383945f422c2eb0d5e8adb477aab441077b34761da4b9cd730c08a2cd457dcd7cbe2052e439c17e8b41de898b335a9b0aa9e31a174cd9e15c5f091b3d02d4978ff90ab765d26780ab00fe4cc587f5edbf85b067be6759b8ff3212d56d3980616218db8154d06a1d4a163f461919026f3d742b1dcfa703d456f2fa34cc645b6810da965a76f16af533ba70ab78efd07f300147684df6cec0c79c56234794592d3e806fa1ac675f1387d5277820ebf07608eb29e360857391cd83c77682ad8e828017c425d916843aa0da209371b8299ba1f294e6d4825fe46080b730fb86cc060d3cc2c42418c8551241dc39b602354a1202b2df473a126a3046974c25cf8d05836823b3e9c4d843360fdc5fd5ef25cd87062b2b92b0e37674a5d635ff3e58db1024bac7a9ef7c5708ed0ce3d9846e5d1a089f8c31c859f5e656c892f212f416b4ce15eb956dad2a0660b485c43a854f9be623436971defdbf7331d571ff7cc815930b2c776892680cb1d90910c1c444410b0d8409f5725aef7c4d1268c89b6728f01f4648b73a3ad2c4a2c1930251ab8d5a12fd65d5673d05501b7112937e366ffcd9406435d19b6d4b6c7d592df665b0f2836be222b079e4582363586fe9db8dbed08a074c8fc9ff02478274a1a812489c359cde6df5e97481ac0a90500c3032b89027f2861b13ca1278ef10f9e602d40c8bfc57bdacda053db10dd2c6b62e2954857b1cbcb9829952a2e6b00628426f53b22b041f615726f82ac670598cad4d04ca803f4575e9c9fe6dffa8c220f9e20364d90de67eaf407eea6ca4f13cdb48240c9f1702e0a30a455338cbc068666f80f03156a179a98cf65529647cdf26a6e068ff2dae76278c8c85f56d619556ae934ac2cd92385fb5e4f4620d57ba662d1ece5cc2c30209b1e94a07c2239cab633f42aadb40d1b848bb26fef282cfa7ef57f76c61c880cef7a0168f351c8ccd8b425ce616b50791fa2da1760cd452bb59c2c60c1fc5845cb1f1b5e8faa7b0f8c67070fd582d2d2bdff8df6d6421c9d2fb564ca822a01dbe538545392f87ab8cf2ad3be1815efa8f41b58337f51ad1c67d1306b3d01fcbbad76aeaf0b3d9848d9f545a6c6827b4e22a005a20f097eba06089100ef949d17a36f7eb239ee40cb1a35780ee39b5c53e426173e8f2dbc3babfd008e7914f6cd1e54498e148187bf25b012d5c7f0eb2090ddb951f8f36a01a1f09537068a4d072ddf371ee6684d31b22c3f13469ba509518fa3a99954ac55772eca5cbeb22ad7e321fe96b09441a617cb61c157b2e1cc80f67ee1efeb616b2daecaab9707e5fad23dad6dd18856c012bceff01480b7934153f122b6ad4579ee71a362bba2cdf82509a8db4fd67ae36f8023d5b03a7a91efab38b081c375f59220a60c3a39461984f0502a7214cea89852ab33aace085e11a185665ee8e618ab3e7984d722faa67489f79a651a0fbff8035ab8a79645d737f3224104f7b1519133832721741c08b35622e63aeadc50361a8709df3006598259007d577a636566ff766be70844dd47eedbdf6cc0d7269c9e0d9502ff8f486142465ab72a4d7b5f6816eae59d8e33c4517f098551a52b63dbd556cf67ed7c1109a0a54cea89f697a9fefddd08b0f4e0333e6ac79e896ac1c195d45a7ac4f4e4d2681e2317f480ab471367a5995bb6fc6d19610c72d7ef0755e32139d8d8736be9df7997affa1e5afc5728d20fa9c9ca6409ba9587a60d099871cae703def82cd99409866ebc37d9c1b8f510db729f19a3f358478a3eec285ee8cce3faf2af48a285af253458ce95b8f0b9ebdf4d5b8b5d387b07fc6a6ed1f02d4c622206bdea0ecdc5a8b11ca0e7d0165873e303493eb9a6c7da2f3fec31409b6dc5f08641a42b53705bc82cdb06562e73c67573ea413656881620d1414647e1a2216aaf8dd1753b9742c6402751b5e26edfaa90525571f53d738b6951ca5d64530a0f22c3d1b6be0de0d6d4397b8766ba615114c4eeede03bad671feb6b86821d545575e18289d4123cd89faa803667809ccc74449f1e18b089a826379b784e21046f081e182caee05c653225b61f3e0f9abb6afa3581f696d7c0c7d046b3c0182dcc8688acc81c36826c98d534c754aeaccb0e3142fc2a890a36892b17d782044b9471917c90dd3a8fccc0b1cdea722debd1af90c6cdc1735f813fbd429dd75af8bdd77bb6e1ab7ac6ae786bad7027c8f743206e83bbf5060e2aaf8f4989a8a9b648bf0ca68e74695fc0606b276e47d5ffb2da06a6c31e2f7855f92e853c83673cc4c067679f25ad838f2ed690bd63a9f4bafc42006031e8c842475cfe711667f3bed9b1d85b3ddfc9dbd3ed2bb8afa1ecbc7e6c9cfe342ac5086d716572e35280649624866c4faff2692a67ebfeb9764425f8656e4f9bd17c60277486649c08289bc5f8dbe5bcaf443c9e7f8db8e31d9d294ada1290e5345ec3f18af0b598bc155d3f3b2ec75e8833afdda490e9f947e11939a5e122c301bbe0663cb5105a52a570ce08ab1ce5855424a08c0882aba1706473c44c48575d5605f848780813a23045f57a103c069d4d12e39aba7e6b0a9c02a652b6797a060acc2f8850e30b960dbf0b4b438304ab6fa7e0dcc59133390d8cc91f478fcd63ec196aa25a157e41dc1a710298b2a708966b0584e28d1ae1c280349a22c66b2430fbbd6557e233f989614bc8fb79ce5077aed9d804a1a8e7a387fc23d4aa81fb1ce117649371c91704786aa6a65e0878abf9bdaca5d43e20a6e7f01dbd8c8bce09437ee9fafd899461568145e2c37c7a43ac32e2486ba0f138a9670aed744c135d3687ef09357bdd1044457bd7ac52baf13c77c9d6baf7cfd55aebce6a297c692f6e65d77a1cb6e77dd9dae4237bbed6ad75deea68bed527537b5120596f9076d332cb515d877337d1c147ed27f9cec77aeeaed1d1bfce072a585ce39287ecece856464dfe73442df7c1b24a8df7cb7f48268cd9f030fc6005c36eb98810d712b000a3e0b26aab62a81c5f8f4eea2549554ceb9bd7e40b84825d6c552bade7293b768d0dae1f2639a9e9595418470f9cd4e84cb07e7b8af4def71238d46d58690beb5cf1c76134c892004b2ff6c1ef28f60570e5d0e30c4b5831f2104f6eb74ec292e0abe6ad26f2ccd0f76fd2e63e8f1f599a8ad5e9998d6b722fdc26ffc7ae81281a8210ebdfb33ddf2221ea6c2e689c73735c1aee759360386deb945068d65fe2ab3e8a806e3063618a791732c55c8b25e85e8b2281d85c494a34db4ea169249a93eee6ec2900f0a837401b935c4ce0cdafe0c99557ac103404fd07ac0b6f6ac073365234156b0cc3fbb051cfa4b7166caf34a2f0754b433ebb1b364a1cf77862ecd1a2ba063b8cb5b19ba21490daca4391c83d44099751d69ce7c30308acee3341f48f87680cc4c8a4fb84a263fd68e79d65d1f8d0a24021a5c643eb861bbca481475dc1634d4b003f121d4ab539e3a68e61ad53671c21d70a6ed7b75b298ea1617c40ba905cc95826fc6a95c43018d8562974a77c46b1ca33564950bf5dda91de16a40482db2568473973dc568ea8e2748dc4aca8536e80cab8241509e224550f7ecee1b67dd005087aac127194905e822f414da0c7581327fa65906674a9853c2cf2f19b517f5809efce85cc3dd150e5a17660f262dd4e43490ea69f19bea1a5606bf83e7a443a27e1eba1dd129b030f059b380830550c3798e6fa48c46bc99ae39623819129df09a61839ac945b452a81cbc79cb6a4860ae396c39bace4a4398a6108f2333ee04142ed46e59badc6b327d2ddba06220472b7028f308f4e10752d6c1e73fe3a500e11d77e56f2bb50198b35fe1c2f02cc599757a4788a3d2e99d1766b383fc9537c8771c963c12fa85fcaa6099358190aedfb6be394f33d0d352864aeb2e3a17b0fbae9131b48980d5608c22a4b7934cf5d33515caf807081884817013538803a45e7fda4d18587b69996c088ca11541d0d449604f521b767569b0f349bc2c84e2197f93512f671a5dee23f3b86de1ff4cd99624badee75c9d3451e444e14a51fe1e051beef3744edd0e0828beedc371a537cf9406c518b38ac5556071ec6907adb9848885479b4bb354098c07da6d0dfaeff973b528638c7f1a2740f14d06562f8b7ca085bbf373f54e957b956f56ab6ea7029b09dbf0d6c23e21fc05c4ca06401379851700017158edb9afa0ef507879d5109b7e65f0ddbe2948ec12fc6da51018637f00c1c526567d71a0874a5e5239371aca33ff6d1d001b1100ee19f2d8b03b53975e5fdc3dc5360ab3aa7a376ecd0cad9acd735da2562f140fa53bde46763c0bef5c7def1fbff28a9321fe6ef8384e37d0270347f0447c8a9a68c07b606911ed1f284921667fb220b3a6f7b50cfa18b4684b162d373726dd8320e3842709447a0542bec52e9c9255a77e57b9b30204de231f7a77f0183449a899a4290c82afad7199adea6ebbf6813612b74dab82d0b5027a9d8ded3fad20584e69f584271cc1c2d789b6e636adbf7cb780e3615dab7f9b6b245c7bd5ceff621c27bb715b37484fd71545132317a6374871a58d4c2afa5e71f99ae400e1084e995d383ab043037568c5896bdea1a29a4a15f325bb77a3c3ac8b1335715624b6bbe4ce09c2d1052f7f1669ab87809350c22f770d351f4c2531aa333d4620e7fa696872c2af81a426cab900544e129a5bd93df614ffdaea2a460c5654373bf57ee01b5a103cd86ca25433d0555cbc0e710031288d88d5f8dabfb31b07469cb691cd08dbf96be15b7cc7a73e4ea20b3f9dfbcd7701266c492dae64ac3f7121b23116a3156d0d81054be6f42289511992717198cd343479d44416485a0420cf522a38a58ba4a5f625ecfa5364eeeb4f3cb4ad7291a2ce96eda08c74005b71972bfc766e58b8521ff81e0c2f454f14e378ba4c061e2b58f8f4ce5f9b7ee127c567d9187468b6dc874f5acba749a807234b774473c238270f933819c447e915dd5694ab2ae0febd9ecc3f2bd98392700d9f7b26bd401e2a9d215880f28a6b29ee27737d2647399c1a011e10ba66aeee5b5f0f10eb9e87e4bece337119c961b1d73400671f47133060110b77631d43f8844333c30190062d4aae812e8c4f2703ca9fead86210e56dbf9637c63ee9a27ff18aa1946ffc48d5e4b545d739a1dfa66898c436e8711488c738aef4d1ef8c0da652fc7d9ab8010ce88bab2ccbe656382372251db130be9594c5cdaeaea66b4e8afcb84fe10e3de93203c1f4e9d8da73d06635702acf94eead99c745a9e8408b65b259e33579c1791c2686b76cdea8d7ec0f58da99bdbd743ee1b17f7e384c0f8f5538475cb0d8f4a076840abfeec9b306e917b86f85f7f844021476d496a9e4e754c010449a65ec335a3a26f48aa4b59cb2ef4336f270f56a07d62cf6ae7dae8b39b173955a0da2aa69c143a6313fa2d78ac77c0a141f28ea20acc1b16ff469f9b70954bcdcb25788c26c17807987a56b786d5347a54f855dafdd609182364bd7fa21d65af0df75bee0b31e547d007b821cd6a0266ff02da877322e370f379b14fe0fc8f638812e4bb02268fb17af4cbb1d9139442ddeab4d771eb90530f63ae1d68caafe927dad7c1b072a415087d99c7c0e83d2869ae58cdbcaa396c2f961a75db28d2874117b87a9a3ec035d6a28fc19ffe62c97e37a6056adc8e0437101490c678b5980e0df6ce1053c0006c6656e7995d7871d4f3bfad48dfc6e8e1d343961b3b3ac24543bc9ac84df0e482b233e6289a77bb3d711199351e814094dbbabfca34fc3823c9d62f853318b8f3dc4bda2c099843e0b7135500d2023b8b2e978cd7c25e3c32a7ba03e98a987effd21af6c8ecb6ba4cdd7885a69dff224115fca985f099626f4cca8e79e361b23dcbc6605427669645a942be1b0b1dbadd05be990c1b765740f462ceddc13509002c4027d31145709cf027a34ef722396f759352eb156f9ad5117c8e405e6eae8d18264dd40a5f2371f3b227a10e8053156a77d1f00fcc722709c868e6e0dc0f8e8e71a7d836e07a5d7962653a6a448152c470ee9987a99c846fea95a91270e16c5c845ae8cbe52c36036797578e7d187559455c24a9cd3293e88d187f5803870190dc07d0e50520d19a925ba20ffce9b944a6a1ae7fe2926b47c07f5394360c00d15df0df76c460b4bf7fe43e5e305ea26551196503a8d0784b8088a85cf9b7122a1be2c6b4d6b8aac6278121bcb6df67433bb8082486767328026f8d63f14432c8b9d115b229b046467776cc0e96d8593a4f0996da2dd44a3d243cdbc9ba74d0893d6124bb07f59dfaf9b20773325a214ca8efa59231f877de9de1c59b23685ab9c2004487be2caa590f65134ed9a4146e5c0ace8b1b27fd3d878fd076224f76e8c9103278c5cea5e9be340562d778d83d292c932ad6a7610afbd0fda0fdefeb6fa40774d4bce56a84c023961c3b162e5a6f7d782dd296b6ac8136551dc82c37a6948291e2910822f490c09ca8f8cda08c9089898cc00f62d2f0e8945bea351db5da1fe41c4b4e4949e486a4eb18c1e121c38f18630f447301d61e031b5799f1b6b2225b9c125fca7d5c4fe5ec015f0aa06b9fff76a6c0994986927eb9618370abbfd8644bc299df8a56249f290fc7de633187ed07004d7ab92fe8f09b9a28076aa0a73757448586731066c2df202cefb610141fac16bd0cdf1d946284113464d5d6d5bd06b94a12b7999a7254f593e88b4e7bc44d88936def40acd80beedeede25aa815656f340cfb0c092393362cdc23ddebd5478a5d14be8db0b85bdc414e917fd2520fe6a0c0ac02f2142c8446a06539ede84fb50302b951422c7786dd36206b32648004da3ae15d8b7b16682b7eb13fe4a4941554444f78e837afbf6c443436f290761930cefa64dad78798bb1ca3bef54dffb178954819fdf1741e4f53cdfd89c698e3a31c610f89dae29092c8fce941bd21b1ffd17fa833b263bf262d985777bef2b8f7c98c22881f8451b8ac9df03cff692570d7644dc250b4aa0dcf1496755dde8359ee17153309ea60d9d102b1c7d74b16b2651d264f0a3b775a90193266f62c4f14b1a31034ef41d963c736df4b00123ccf8fc008b262cee2317935ef4141c60287777341da011d5bf2ba2d23e2b3dc69942a2790085d5fa65116e885496502214365f126678f1e96d903b9bea8710cab4c0ac64885df61de06e700d507a7d2f22f865e062a590e5e078581a01ae0f58a3937a78611815a6da8cc1de712ec8f51864981d52612ab48413c62cdc885aec5b71a3931b48eefd9d708cb747a4fe35b3a0aedb3e9d7b26a9f1484a47a213867f15157e2403d41d90a3a8ff39e3ab738a40617a164e4bd71b1c3a75c32dbaec4717db7febe1653bb5d90c335f0bb82fc81add0c1fe613974446ce24bd48407f4cef23a60063e19778b25cd3be0af5393a298326c5b12c2a4b7935d50589736b494582fb786fd62316f6b7bba09294bae4a4b8224bf4102eb067e5595f095566c18b94932aa60638ae5a299d8ba8948c71702a356dfca6319d6787ab9ff9db2d196480bcf7483e6357d8776ce700b045f69a3b83c7690c54bc45931ad9142a9f500838c50828dbfba4829f5cb96816d9d38cb851625c397352ac666f2cdf688bb1eede289ace80d276eb225af4f4052a29467f7a24b77106fb2154fb71da3ac694ac3bc46906e54a7f695fd341297e586e5053143ae22600e7a82cfd11a7d7facaf1c23e2dc69544eec7f0e511f7139abed2f6d7d1f7fcbea18497f9484f5e0a38cc2346fafbf033ed286707bc5cc97a621ea77e3406f53a331fd4fd5eb643ec60df558eae1fe4de08adff937978c3bfa4d2f20a4156d952be9ab4f51e9719e9b33d34f2061d3e2f956255b4b0643851162494906402dd17b2c04d68e7ce5a65425914797640ed91e6e3d8d7aba0f4915fa4a355572013fa18abad8f70fd8645c27f86ac80b8ba80331e79f9e1901678656590c93096d635c4d64e09e906ad87a1d7c5a52d031d15599f01b8230f4737edb7f2623ae22047fd8c9074e9b9b8a8d9aae7538e6ecf1bb772545c8cd582f76994737d691ec8372e2d8e755faabf95d01566d6521b1c4001813a1ab0c46237373a21bd0f1236f64fdfd28e5bc23dc404d993227c1f98c202d34ff83526de75088dba4de2a58d1a430a089d0a745a0fbac706f41c9e9793f78b8fb72ef8903927fd72404a82a485483f00a60a78a58f8ff68f798c6a3bcf834204d7f2736bb1a8ed93740136929c01c7b789b867bd82fc98f98a6ac2a3d55ed0f79cf8ca360def2e3d80af00dbd51779ed732fcbd7a8111b03b4ee54e62e55a02e0e5724b8aae7e77d33b45a3d5f9d8aff55c988f66850c27898c244a96dade006d7023e2b4d03154cc7474b206bcd7436619a9fa207788bca8e7135b9918960fa77862db313d6b5df4ecc4a266a6d94e1bc08b8bd534e71cce7a9b96f63de77fcc5d03b177173f37d253bff66292b1f56a696ec3ebb9033ce26c7a5d2c6bd21f9460064bb072e353faad7f744275c230602f3dcaae68f8db2c7cc4c2bf89d486a01e5c51c260019d4da82cfc490f5e785eade26108d2d1b556156882370157483ef52011a157cdac2362ee5bf03ab90ecfecb1604a61bd72f97806eb811c1b6e18613b23548d57098079c3cef0bddac758303b7cdc899a4846b68bc4966037ad4282c74a4fe9bcb558f6c19a662202275dfed490b128b8eb1e261800a65fd32b34a42daee25859a1128a16cf86c9d1cf7fe985f2e130e7083f8b980fc1085d6e25971d84adbfbd179f5380e8a008326f9518866828f081e870ea8c2959051c59d9d2a18c09a5512783089f4f217cecb172b12bc4a6f0d52e44998c2162933044d5c9990226567513e2ae98e73f7de67a951ca41ea3fa2b9f56756c4c7f26e65f6f3eb7c9a20a261a8f3109bd858d012f94bc97c83c5adbae4a60a8623e91ed53b3fa2656c787abbb85240adecc25864256857742949a14ee16573de3287bc82a0b7afa77927aa5dfbb0b24d7f69e13fefd474bfc24a36ab909852a79d22e814b3b1b08c7c2877e6e827640a2f04bb516a68334ea184a91ed2ee04ff2220ea692a3b0ff63327800d2d2e2a8e4b04e60fed3e8be0982a0f8e5d9691b8530c1bec839063908bbd72bdfee210963e6f6d23dbb79cc95804922ede2c4083457fb4a490a9a494281257639a7b03926d4a25d8a92750ad6e0801427073545729249c0883f1d5820ff4609196d804533261b060b972c326e480011b4f2293e1eb847b41253e301b9aad19e34e448bee8edc23a9a054e9fb3f5f72e1d8ab2eafcf2c094cefced2a901d55f8b45cb225627c56787a487d435eeedb3b804d2da19371533fb831aac0f7a7bedd93d875c38bf8f6538da0d0eba65c784184d2adf2bee7aa7ae4455875e6f9c252956f195b848bd5982c58fb30da67e4ad2bbb7e7ca1e6f3a465c4e676a2b93c1085f8a80132bc7b6f7723dcbe239e72dc4fa5862425c7b12a86f53f5b0772614b08c4ebe9ab517cb5df3e0662c1193c91e9666cc3a6b9148b01db6bab5c36690f081fb48a552bd61ab73fe65d4c6192674f08ea8c764c4d993d661f1a52835ff84c6d68bf0599f829d77d4938449da80cf9d8853f9331117d4ce9702a03a196a1aad3085225d0e1546adda52ae909df79fb2658d386144c5d92345d52bb65c7b9de2c973c8dc5ea810fd1259ef7b45e54fb43aa50314931d8ad7abdeb9ce3da950c38ccb7205dc95811c4f0ead999453fa945b5372566f0ca3e3f28ab221e8f848911bc0aef239ea692e142f136b7f53929ac0b5bacd69ce6fe8e4dda0b9acc9f3bda2f5624aad4dffe8cf701d514be8a54745c014fbe8cb22f5177a146530d2de29dba52b2e882862a038868e0deaa235feb14a99950b7611f72d57d57944a7685f34130029d2c2c3d0d5ca9ad3bdc4f4610311e87258c19276bce586058f440be6a48d470f51c19c6639bf595ed02ddc0011b17d1dfd2aa729451092e3cfa3ef0219a66120162b3e9795f55232fc6946b0851c82a80efaf14d8369b9b7ea58d7823515f01bccaad2e71730df20cd8ea1bef4a5f5fb2abbba4f41664434219023fc591030e8fd533f0072cfd3668f2b2bf38a385a088c004c384f3ae8fcc364382f489c06ce60e8c337ae0b60f34eca2b8d124c6d312d8241d9e7df9598b1e147c3fbbfc8f2dc6a45d8fd0c048b4bd6e24b77c04b71e04a954828a5a330c97dddaed9750667e5fe7542920ac295ed4fb5866a0375b42a7575cc3b5cb684fbc0aa74bd0e7b62db9faeb9715b3ae306a35f3aa0b1193ca07aa710733ffd64e15224f9e2d42ca6c21a022578c55c675987dd375eae447a7a97084a51c7cae7c74459df4f873ebede1e72c68a31297ea5252289d28e6155b040509b739e6421143970584ffaef2d56ebcc1b1ffc310cd20a0fd7358fd488e83b89dfe410796aa3bd3eb2bc6d55fc3f2a1e008b6a6f933567bafbecf717caabbb01604a3b6de4e75b1fdac45d55b94d68e2e36aeebac012eb6efc752c35d70d633dbeb3d9183bf154f538aa7b92ba5742e2abe7af501064d5f7c38a364fc6eb17ea66b7621561d64ef0c33407fc65235f0e434d8630734191ee9933846be042e0292ff9d19c7aa26e70073879e3d973565d800d794459521686870de57888251d4f420577ce7ed02acc8f87752455811e46066c82c33ca536dbb89f4e70ff78e5c443f9bee14b7019f7e2ca701b841ccc9316ec08cbc825b1811c624babcfda0206aa85ddbacaa4e9c95274c5f6dd32a4e32f17678a0eb4989863ed418e4c77c5c4245d3a8dd7dab49385dd3e663f046a890c6eed0bea8ca312a6f49e939f6ecb408dd2b2900168c2d0c5ea9e88cba7495d39ffe9a2263ec651c2bb5a315f6aa708e2db2b89dce8160216a0c4c8bd14f3d535315c466b018210c3d31f465f869431c3ca9ec1910028ca80df90dd27cac9eec6c0da18700d025772775c9348f64bef1b49f51e879c4dffb8be41922f2e077f220f95b3aa0aacf89a07f82c9260dba7ac62b93aea6f1e1bb659126337b324d387f35118c0c1162af24fe000b441d32d9203d4106e5e4136d05d51d7490beb06768e8d9b5b2035bb8618610f79cf6a10694af2eacb57b740ecec3d53568a56c5da2fe2431995134f044a4564637d5c4aa0664fb577917bab48a6d1a57104f831b68377b2a9b5a627bc54a4f6b0fc263e4e1529b9c888c02d9aeac42549e8c5a69033ebe45554fa5d10af5336873cd23fe900c95969467384b30e5bea78a01c0e35beb13dea287bf67781eed641d5d8d2526a017021bc2f242818e29b872639ac057aa7e2e46ea196877d18205185fa157834eba6ad321bb5af0bdd536114cf928ece201ae1a2ef5a3af9d0085d7ee57627b12b2198f64d39bb0efd746a1cb4b0775efea93ee4542c9a679faba447c74bfe2b3b42e96183ce537a284deefeccc2bfb085c3033f56739068fe18411f7621b6a236df094aaf6b58f050c3b51f34a8c1476b00bc2555d60206e516de08af27a9ca62d762c8d1a3fe0837404d6708d77802cc1412de75d72e8f083353427bc753addaa56148b484e5b0e9a11aa9cf657cf452059ad3e53037c843048cd7c3cc09474f92d98a32ab9b2706e97b59af0e75d25d516481bafcc0f707cdb4d8590b9b93d77569ab2167620c0ca03bc97f74c249724ca79d174d1c8286f45c5e987fb4f9d9b1063aeb974a60d8d138c45111388298c162ea910f3e216970fa7d83a79edba58dbdea4e72a1ddb83c175639a9f7e14172e18cbb1b6b0bb86978d7fe120f34ca64999a5d28d9da402ef527e39c0a8f19f7505422a780ea8d40af437387032fa206aa3558844b3ebb7aca1f0e2a720ea28749ddb6713294c94e95d7fb2b93e877f30f2bf0b1a1397c5a1ec15058674330b27a23d02f1fc6904faf7eb81df8768e4f024da7cb486c0420131f72ab8dfe66b24b192800bc9af657d048f680d101284018a63faad50d7bf93053cd74842180ba4b844b8012b67553c0f3d6ff81247d43500af78ed6060cc6cc93b54ec6f5bf068ac8d7c22d260ad21e59895f6113072ad942ef1cb74f2cb7c4f54681d2777c9a2e195381b99ca58614a05f757f073b7d0d1c8919fef3fe2c976e69ce8321b7ec3cb7401b7ec0bf85e3ee5bbc857d1a4ea49127357b76db4ed6d55d43abfe978a0fd3232bde81fa9c95759f85d1ff6bed3de8568d9849454e9a695f83aa4a30ee8d1d9cbf9aab470121f5744a69bbd937d0ae1bc5c66196afa6d73720229d8288391dea2387da23bb31fd4ecc820c13ffd6711d07d6a1809f95a5978128c26fe920bf9cac61404d428cd6f81aa48eefd100324d3b9882eedf4e2e612a0412cbbfc4726bde32812011be3a6b7a6a7d9688f020e0865aa2889867daa37dd522de90a8e012a6c8a427ed90ae0127b46270ae16bd9aac28ccb58078e0d921a80b65ac7bfe782706a269fb508ffad1b14581af3f9e5d25cdc87a106a8ec9370e59d164494588a08b36af4664dcadc8f844c622365973505939799f90916c3855240d0eb050ff76e9d86b9945e9a0f4d635985b8642d4ed3904056358310e2a5ede0e17bf2115d3e009167bdaf3e49e15db9aebae09c60f21552454a101bc7a327898689a98521a494d5f4f40a8d42d7aac5efc9a455f324a780cf2f2e5ce50cc707847e7426d8853e26ec1e36bb514f140dc4b167a08656cdd91d5574b995bf402e552f112a1822c3f26d48a05f6d469118bea3afc5099e8c417150f5297f4c822d3e4d2856f87cc3e61935fa52003ee2d9ca2a6daac4eff6cce7d48e7d58b6f002a78bc7894cef7c50c8f8b1eea8c03bd66c8af5944d7f9bcfe4ab951413ae5e446067a2357705c407c93cced1d2eeeab4d8ce0b58cfc2ea586d9b75c72300b99254e30fb9f2489fc2ba69248173d9163c314e78c8e8bad6f99efd61fdd870199786fb18161d93ef076bb2c9b0ff7e1940575158d1242a9c67d9224f727f3141b7da3d828161dfb8d7ba048c2396e8104dc9f185abc41ffde9fb7ac4f14e78622cc614aeb47eeb208263048d0018bb17fefc78195a571c96211fb06868157dfc0263bf50d0880bff8966bae74298941f2055451c9e5e82ed69a1e6a56fb72d70aa33ccb12ae4a06fce06a41c4f4f978d7b9c9eb2ce73c7ea22dc2cf9c2a30b2cdab0aebf9d1ed1a291c898dbf6b9949aa17195f49f4271cd061083c11e153e578c7445769842d27e59bd106319ddb61d9a38bf2a795e63142fd9f38780277055685ac643179b98a3168eccf73d91b34fecf5cf3ff0548b119389c48ccfe81d3e1136cbd14362dbd60e74caa92debee5d50d522cc5e87f43c367b1c4e52c9e04f688736904dec5535a832f700c7899aebbd991e76afaf8400763ac6481d62cd8afa001f666ae5e376e31fceba6a99550e4531ebfb62f933554d9698f34ce45cb5a91290c015a36763bbd0e36640791de29c9110ebc6b1070b70badef78049508f994a296aa943b14a1ed4e335bcf4196dc1437e9a6f348861e10f4806b926dadadddafa197fad8dfe11de132d49d7a3a5922de0973d3739dea620130f111f4766b1dc63e0cd71730d77d5c5d04082de72b36676ae0e18d2796f41e766b6caa70d82c26fc2a6a9921e7e0822f6ea6de0a2c9973ba550a4a33b7b4fb21ef7dff05c318863995491136cd6eef5134eaed321db667e18d57d378c5a3adbdaf68d5a7286e6e4fb8f41641991482bdd056083efeb13be655f5f6afdf65285059c2ce9c994c2391d9fc869fd5700d7fed3890ea7bfedac3dee3edb0d7efb6f6c699c91a7fd66dd5ada0c1648e6991d472a23e936532b5ed12542a0f9279ffbfa569e7676e4bd2b853882d960561124c0b7602b103e31e98acfdea12f85dc222543a5cf2a0d1d416add68958224063163da7d891e682eeeefcbbbd16e6f3fcaf747de370c83450c5ffcc77bc9e746cd2e6b6c0e4af6c83869e66698a8ae686d7cca7032e793b0725cc41b7ba7a83f8b50f15ceb4ac5868d45b5293976aecdfb843e01eb21363419335903d84877581e899ee055c8c9611394f6a7bb5f488d55a630bb034a91f7537d379cf947c38016552a4330fd6eaa627e6fc07854db4127d0e1771e5f005147d3825cb6b9dd4767913ca4960c78ae1880f47ded302b56f77250fb723a846aaae8ebe8be67bb701fb2c46605f318219269fd9d1f936d07f9d0b3d9243a0cd2784e7e32b5e712cf8ccd403e604217f96920fed50287d8fdc28ba12a34f9433479529b469ed41964676ca95c5bb4e3cc5d6072a0265b81804d5508d23c05a7ce011b3870a7949d4ba2dbeb42ea6e1950f154c1d96ea27c84841988e1c992f573e43b33bf162b80aecc39e0e9b44e00894e67b6ac1a47fd73761f904cb9b0a780c695df2935a4711506bdec6c4e7f9623afe6125e104bed3ca06c4bffc59048e680bfc8de6507b20cd5eeb33ad1760a17fd0e7f4969095867c12b22772d7cac632731f3943c661913f6d5f67d3e630ba5e25ac19a95779f7adbc24c6ac86a37685eea61f1d8225fc2169398bcef1f30c5f79dcbef16336daa7b14f208780a6dad0c678395a60d5ae4e87a45ddd02da3f59efe3d5b48735405faa4be847d0f935b2ff2633abec608117d12fcaf4c63df8bacd0670906b87f6e35f49d7f629ef5b6157a4881acd0ec872cb602db9069405f5a1a9f77748d4965a650e90bfdaf92a97d520198bc9d2de7a0c1961691d04e613762b79eae46b402c55354f71f4744c3644c9c1c8f9c79614ff948a33a689fa3a0919676912259ff71853be4e315dd4b76435f210e789d1f58e2738d67f54b5c0a77039a521caa45a5cfdfb8196568dd4a845259d8439955545990c43e1f9b0b0cca4c293863ea38f295c2fd8eab38d9bfd7f464a68afb1e48839fe0d2ad901ae1218cec9407f3bfde259270553e8b08dd9e3ffa32544aff1671eebe318d83d82665db5844308d847498536d018c96c661613beb2cc92ed9630766d2ea9726080aea40066eb27739a317152ca94452f6fd1e41c0a4c3b87aa02c2e7cebf39f0cce2d642eba94542da773a67446b2aab4851c348214c8b0c51ca564909c6851281a5b35082a019b6f43379fbae3cb794d47e66a7d239ada2b6301f69d7b66fa97ca923f8277bb09a7d7bc66a9eef3cd4ac64a1eb22051180e761e324d90a0ce9f32991d10790b8177dc8c65e4eb2bb7474e2fb7a0f87bcd974771b15ca8102e124a1a1c9a3336e649cf8876c49a179f8338ec377252d56904ff200f03f0fe3a54377909f91b62cc9a61f490f4b8c09927dc3405a57bee6bdc9dd7ad5b8292b6cb92b4702f2c742434005ca0177933a230533314b8d7206ba81df857a9ec7ce82f76d8a3ba004e29db5e44f4e5b08670a1552339c5a81e7ae540c6964964e58d870253ab5b05dac986c18b010cc0377c78692728f4095e4acc57d95b08caf318c6bea42f643dcc6348568ce1966b6fb6d122495782850120b7788cb85bc45af115a31d78c3881744831c3ef9a166298a5b3fc0fada51837b0714cca2ebc3605f1bd54161404e7caa0ae0cd4d501bb2640570fec0a015d1bd07583bb7210570decca415c31b02b3580a1b7b5bd88c06e6e4c5849a9177439e16bd525c21a35f122c7aa0da694728b351dc523154a44435c3ce8ad1f0674ede0aeec9a2abb8440f19e5e94e0f810186f82f1402ffb3918d706edf2131c9e78201ef8b94a7ca6443cfb363a2d0d358d13640f0549cb9f7859a7dbe18e84b295dd67438e89827f8c48eb5d6cac32340c619be19060634e7265528459d30021a2233aaf940f45c277ddea98adae59717fe175541465196fe6fde619cda4dbb69ce977004b38af1c9e2f396bcc578d6f791c26eb8a854b2abc2bed57be23662f360dd9f24de2d1b282ce856abf93f622c5f4af7a21d09203467cb60c7dee314ffc52a47f193435e3a103739a05271d306521aa7f00a5b8fc3a9a57970a757d84a586a8d244310535d55120b192e7e954373172bfec598244248814c68b920eae265128935278f6e452a17b0d33a37015ea768ebb153beef89d247d5d48c31d95f905f278bf553ee791009b7e7a6a624aa3370ec58197da1f3e114e4610bef31411c264259bbc7879307b031e6554caa472853a0758560b778d86d051d1040db8ff13868c782fa8a8cfca520738193dbf8559f6865100a06321921d7fc0670c632fda66ff5d619970240eba08286f099891047884c5ea13da3e6f29ab5de91ffa269d09db9947e7ddb49f1ada721b915c382356d7bc10bf5c21701028a471a71a91f2bf0bd66f36a6d27612629860b24f88cf1926517bde490cc2c58b7fb778d9239014180e927f29f2fc29933f6b1773c698ef1cc5d86e756257daeddcd9e7845fe8a1116b273c4889b83fd102522b760c0362f8c3b8dd127f81dd4d50ce37560c3840c6e41608688edfc78c3348abf0a45de823d4a80388bc491d6a99714af6ab074f309de9f172707aa0904cc8abd161e8eafb633a141aabf4cc67c4c9632ce6e3a70c3ca648aa0d199f68aa2c244fefa4540ce9d593afced69d670a93ca463f410c89f6bf20e38d3e6301f4c3040aa52fb85a309a59a503022bd5a09bbb51269988adcc54adc38436d062eaa2f12e466ce94c03bf9ec512407b98e56f2fa458d4da5187c3f0bd59e78f54666733ce60f41d49bb40c85f1e65b94007344d22b0212209d97bef2db79452262903dc0b6d0b480bae769d9f1da31058fa0d6f9c94052e3d7aa63b3e1a94089803b4a6e6364dbc2d85057206293f8f35ef9df4a3b2abb7dba8f20b695844fbfabb381f3f212c9032b494b0c753b79976ba422c03186e43e20889a497f19c73d43deadd04e9bfcee5b0bdb003469e73e9ed34879c4fef5cfb3c9f6e33b92c70fe4affed87cc4324f570f4df9c0f39af34c8782f871bfdc2eb1789a5a7bef9f5b9b94db7f436eb5ae6e204702c22ca21da79ea9d739ceca6e7f98643c66f5cd6808c4bbd1ad7694b07f52b3be772d81e177297fb7e0091f15bd6808cdf7ccb1cd85cc8fd18614c9fd3a31761687eb54ffa0d65e35c59b3c0c59aea1aa40d4615275970032a3634872f62229468d3a0f4f97833bd5b33777f34b77000f23da9d7190427456f01031cb6ce476fe10396acf84fc0a114fae82d78a84208128a29c17c2cea81d20b217d2c1232fa50a3a2c8073a5f249453e4839f36c2e8617c2c3282e79170010f123be420e10224ed92a14da2cd596bad5202517119a6c1a64a2811dda149a494b5ee4c1e58d3f7647af2548908831505cc82020e3bf6dd4f1aeca346f289252d19b5f0f82c8f9fc327cabb78fc16cf19625242caf11952797c168fbfe2f1531e1f7b7c94c73f2df9ce959862b09da09ed7a47c8e2654fe7afc2c7e05fa1c4e947c539cf2ed8e2ac22daac0e155eaf9abd4aeb06153dab5f44d9590b42b160929b52bb4b029ed0adbe797e40cb15df1274d7cc5a021a376fd340d7db5dfa4aca75dd115bf1c663e9ec39b04861c5ea4177278a3b890c30c082687d9d0b77278755e727861df9ea3c99276cd233e7e8b2f4793254d7c9af834f969d71cfaf8ac2f47939f26b126b128ed9a40f8f8ab2f874f94a4a429ed9a467c7c972f87cf149f259f25a276cd1f7cfc962f870f9191d193764da18faffa72f83c393a6a6ad7f4c1c767f972f83435c9699223d4aed9838fbff2e5f0111a1af2a1d2ae59c4c74f7d397ca8f830f930f94869d70cfaf8f8cbe123c547c947c9074abb260f3e3eeacbe103c507c907a94953bbe60e3efee9cbd1a42927c7c992764d1d7c7cefcbe16489131f273e4e84da35813e7ef7e57022e464c8c9509276cd1cbc93244e789cf04869d7c4c1c7b75f8e265294949a5069d724e2e3d32f47132a4d989a3039f969d78c7dfcfbe570f2e324e624d6644abbe60d3e7ef6e56832a5c95293259d764d1b7cfcedcbe144070643d2ae39c4c7af5f0e2748767680da357fde095050500e274ada3585f8f8f2cbe1a4e7a397c3494f4fa269d4aef85409251f5a98dce1b13ded8a2f3f164159f261a61369e85113df4f48691ab744441aaa347fda455944c0477c7b8c4a45dc29984a689bbe2f4b4af9dd2ca34793e62a4d19a549d33094d2118c2a7d5bd8b41606ab0413df2e3bca1051a65d073ff8ee22d8ff44183e3d441a171ee3a1ad410c1e5a213e7b6873f0ed5769d2b8c83666871878f8ce79063e2c29f05b1896026ff9fa759f8e354d7cb5d0b77f14c213431fb26072c06e619306c66d4c466092e9abbe7d356491aff2e5d4914d0dc6bcec9f8eb54f83303e95a9c2bc327550bbe255ba4adf5da794e9349821f9769bc1be2d5368614cdf16066b7b041cb60fec832ed696bc5f3ca1c789181f308704da03660102cd01e31b4825dc7d2c62520379056ef958c4440548e0958f4541344d2860978f454140810a4130d18324fab1e86787125c5aedb458c59aa9897184168090650838c20747d3652b9d316a66f4e0049aab072df03e16158105155293b3a6b26ecb6cb2a89d73cecccad820ab523bb3be75d3e69c53bba91d58735e39b39eb5d65ac339e79c73ce4993d92b2f9ed315a976e79c736a21ce177184c2a94e9361c86846061950a9ecbb7fa78e0da4f04ae6752c59f659292ff0df8fea99aac56566aff3746c208557dd712c29a54b060364950ca972958e6a092d6ae7e138d2806aad35a334649a4ddd54a599b5b5d6fa5de09b9fda0095526699944b98d0a2658a315619da7fdfd1fb94461c7036650fafd37b78a511072ce9d62d51a669aa77f61fb813cd92ac805f3e161511c587335309a7904077807110aa28c2065d037cfa585444cf9c52c4cee401ab3e160535cd2c60968f4541522002a73e1605215101e38f454146334345413cb587cb82832376f00f3b2b38206207d5034be1c08724272062180729d8f1683084a2c243d793748a420ab8253d1e1149f8b02d41ea88b8410f9a12c7c30b6ecf5644440e30c85e3458d2ee8c1556602fbe1dfee126b56006414f92009134846406b0d84e4f8c0718d400a92806db21889d2913ef4dd00e0faa50e443a5c82788221e28b59c98be3443ce9842eddadcda4a2badb45ea799c665de4d96dd6fdb8c80ed9625ec08ecd3a5d174199bd60832075602fb31f1be6623e0ad525a378fd239718431674419fdb3c75bce061ef4b3c1460d0d4ea7323669b8ec5bd63aede7bcda74b9048627caa7632fc718656ab699f7e3807cea9249e7f463e265984f1ed386dab18c80eb8733be85363873c072faf6f2081c9b73ca9ae9a72c300bbc3ca41e01552081d773ce395fd42d3e22c00e90b08325df5ea7b36610964af073ce39e7a4aa17befde221e8a0c937eba3e0185007b0ff7e7adf1955fc2084279c481103a42982f044135bb8e2042b70c1abbbdbc9b715dfdddd4de5aa3eec7e4e9830c6054539a0e2a7e3e92c2012fc74074a0265600421982005195c01083a884f5ce1b3821e1f9e2baa6832850e3c2be0e008df4eef6f4ba0d6b2667b481b05d44024d3370d31a6b70322997a075c2c009ede5581c34ed609a4cb4db970665583dd7dbfdad5daf59ac3e9aad6be3b65f0cc3156cf72d7a0aabacd754eeaa29336d8aa76356e2983745537bb8e9406c1557d7e36ec6fe198aacd00f444f3a5726996f9e8a311ae99b917d6c3613029a594dd63ceb993346f4e0b1096334ca5eb730ae1f661effc9c54e8e1670d92730a5146edc155e2c3ac870a3ffd7eb687c60a1165369fec0748b0ac0f7e7a4fcfcf3a54f4737a7881f85983e40c968928430fa20cbb840863fa84d21a0d7068a1fc74db732fc62c96b43d3f3dac5b883440a2cc929c010351062b4479c552c8f25158f1530a58ba475bb3207c28756cd023c2906ebf1bfa7597b42892e96587fe821b6c29b30a77543a95523e951ceb08dcaef3334e19a65733bd4d17d18e7a379953cf469fecad5a76b2e18ffb83fabc7788d387dab8cebb57de3e31c118e575dc9669f50e69d54f357bb684ae6f105c8c09e9d0b7ec07f4cb738e9ea4807bdce0c37399c3363a8a512a7d8c335105dfd131106790485a3a286d30b6519491797340fef4b9f75e198444ced04924925377123943134919dab34e226d9f98a03c7a11902f542e62e475ca7467caf04c1a1ae4ace6a13d13c868c6e6cff4219233cc26538808a39f05042c3d9c3ede8e57acf2387428a322b10d119625bc5cdc884a88d710d28b442bb4e0c3d2cb483ca20654a078b9e4217e6c2e73cb7abc0125a53cb96cf1d317f27894cf0fc8cc4fe75abe1b80f88a87518af4995b9eb7b0c757b9e7f7bd6c93f21bdad5e22b2b53bae72d8e5a41adb4c81629575a3e7923f5d168b00588976d86f01739edfec9b94dc66f1f8c8337599ee310a5dc1065c8db51989d6fe9e85cbacc3f58bcf3f85607d3951695afc83cbde5f3d152b97c554b956f0022736013c80d0d4a595ac938cb9373a7cf5b6add689adf7fb7b94143891c180e44ec8785a49e50be254c26e9f17185336e4869cdc468b01d072273ce68d70a1532e76f87231254c6f87c58a37d1889987c9412c41351210a4e90a8420c8270829710a4d05112850972b8e2c5e241102ff891c10ea438c10b07e808323881931c1835a10aaf302efd9ce0b3f71dbeddc6051f8b7e64f0edd707831f8b8480e2573e16097124b96eae1481a3f7294726f1232ba0348190d2c7a499d155224de30de520699774b96488f634f1a1d4d188b0850fe50e118ca4ec7929a58cbd6cda351186945e83f4338791c9595bc051de1065bc0c8f6d9033d88f4c78b2849c5718937e49f4a68f8eda01962e67882f9d075c8c096b8c3fc819eeb76f728621d40db0ccf7dedb3344cf0f228832ed522862058ed72bd2648732263cf4183c555dada9691a1749363b6cb82105393ea5a657ba299e51c1ce03e131a46c1bb41b81c31a6ae79c53a6a646073b2ba0028e53686003534c310515173502992377e69c9d67e3d429241cb202a9e7dc05c7255183201ba23cc54212349062838de1d89d96a1be72dae29ccae9cde1fd9c62df32d7282b53c5b6e54d09c69f0dce5b3538540d9ce7dc66a2dc66ee40c91da2abc5b387d6054f2d0f9f87d607f7d0cee0551e5a20de3679ea2e3e9d2136d7c15f2edcb1a366a4b7c32a168cc754db61150bc639b73766284775bff8dcddf310f95f0e3747659ee7fcc7e62b49a74859bbec73d506da0cab58301e9375361d399a44fe8c303820311b0aa5720e6fbee2be244e9deea03245d22e3c9f8043b0e55b39872987b9d5571341ce73f6b96a73f1c58800260070171f974d81392e7399f23448398e9b91f2b40be5ab9619cf791e7a8ef3ef8b9173ff62e45cf5c5c839003e28cff9ddb6d557d320e7311ae468f464a0c0217dce8bac88f2a1dde9796984a7564079f954c9731be7d5bbd9f24c835ca63ab2d2d9d1a29c3e2a87467ea2dced0e50bbb8ec3166477d9e671a4465aa835537d5792cb262ca879487a75d21d551d2d3ae70c692768534c9fbb4abe5e10d312b5335a879b63b4c5cf8ea13221f678a24890df3b67278b14b0e714b0e592ef28cde32d569574863df41d4592c8c83b61cb354d7a0e6590a37d839128dd22ecda9d3242a850651ef084505a6e7a206702c9a02fb9f246220f49b7349c4e0c96fce85e06f7369f3d878f388f2cdb76df3e8dd6c362d009ddb5581e3c7a601805fe7993400c8bdd28af00c1b1a2f78f46e60709b9e4972060c4594114574b9d3fb2cfc2c0f59cff21869b228b339cbfd9b11dc1c7fa86f73007c9bbbf836e73e946b31a01cbbfb5b41ca6ff207c7222a849e737f2eb512131313636388c163dcadbbe75052e758dc51284c03ca711e22df73c8f9e63f361456c2f16444edca4e3d336a1a174efdc5ed4e121eca83dd858c4f83d463befe69907a0cb4b2d01cd84079408d408740811093688ce280c630a6314c6398c6fc9dba639f396f1fbd0d6ecea35d2a1aa3310a4483503936b8b582c8e77c08fdcd83a07e430d915cdef28e06a9cfb770ab645b82bfee6990ba7fbd24eb78b42bc4ce43556bfc880dba0b914f81649cc0b1cf3d87f755391f0140e322002d8820f5b04e9a00640b837c0323003079ea3f04cd6077ecce66c39fa3dde613c549af0676192770188d7e08918ff30fec42e457a0414a6153863aceb0962e56ea9ebf56d220f5f91675eec301c8e703f6e5f052dfbc1af53dcb588139df9cc6da85a2e17d95bfb8788b539e95d395e34c5f7017be50be0b8fca76472bc29cbfe0a1dd497576c7ee4419495e50c2e2e15df110c32451a08f063548bdf3b89d49332fcee1c52f641a9ba9ae41cf4f1e52cb439411442e718e92346cce39f5746c6ea9db2476730e0f918f73a8b90beddb379dfbe2c37c216e517ff9c2aec5b7c3816cf585d4c5ee3cf5962f54a968eca9df983433284b0eef4a0e716a263df5f6ee74d6c4197327efe3b2efa30dda4fa5c2c996bea64105c752e14e4a29a52c929d529a0017605054e0e921cc5bdb0dd9af934aba5a6bed22102a0c9640a930613d3a5767add465a78f8aa458a14fb12229a2d045522475d6dded954ea1ef763ce79c5ff696815101a63011059b137ab4a384a1232a9c6c0c38e2870a5708a2882b98783961084faec0a28a0b928a909ab0528484254746141df93441937e778ee062cc12eeeeee225fa907220c99a39028a35f5a2964468ed1e04c834998f2a88f454990c17ffd1129d2209d3432470039e0ead646cd4ce68743166a6c441ad47724e911918aa288c5a228723e8c52663ca9e425071cc6219b183b5a67fb0be9b4088a294f1d7c1169a451488421dda6332b5a11d83c462a46d0b9387faf76efe4e15a112822a7107b6daeb910f91468495f05bd8aac825edc7741cc92890dd625889650f24b48f958b40414181f8b94a0e2712ad2cc04f1d860083de80009545eedf7de7b1de763bc454a44211ec599a9848b31327a3b7e07a97449b34f19f9ea8a80b1749b4883c364a7a987209648dac10a2fe937220d8e1142b8c24993104028b1e2556bad3d3bca97a15a6b5502e86badb5d65a65ad35b662942f39b5d6aa0492afb5d65a6bf5286bad5eabfba895d2aeb4d6eeee9984207ad02e2075d7eeaf35c6d439e7ac996d89cd39e793a49f73ce39e7b474524ae99c34b3aaa539e77ce2f373ce39e7cc2ca594d239e7a4954e3ae79c3473faf438930579944a2ac252aaa70841330a1592c46048be0e38223939392378f98349f5ce3929addfff71f25afde4a78972ec9daa3ccab93bdfceeeaed33c87f2bae73387d72b4eeca35783d3380f42b53ca4c56b6eadf470dc0f42bd033fae9ffcc7f5c9518eba75eefab5dfcd4134ea2b2caa54aaa565ba8ae50b8b8422f8e9a92fb4f979f2f89ce38f3be57c8c3050393ce5e95cadce799dce19f9ebd3eac42caabc5c9c04b0ec7504e7e5926d0e6f952cdeda881f24fbead3e757e3a5dbae03c1897910cd43223ff39016ef7938efcc41340fad8b5fcdad5f9f5f78e774b13988979998c0c58fac56ab95912338be5aad565c8c050845f0d65ddca65d562e2e2e2e2e2e2e2e2e2e2e2eac1c63015c327e024b0f79bc7559ad56abd56ab562ad56abd56ab55ab1f291951b71c9476294992e2e2e2e2e2e2eae43cdcb251f59659cd8dbbc79fc4efa9d34d2e3e6ddd6fd686fe1d07e3a90ec87ccc791a9823597553ebcbeb9f46e364b4356a54a952ce0d0727899b8787b4c60fe72c946e2d16b88160efad6efd33fc2f4d26f9223485e0ec94088f31ac8de3aceb787e3f4360b994f43e7e4e4e4dce936376088fc2cb770dca70e247b9b87ccaf9966d9d27989a31e63b6cc294eeca5c7ef3cc8f4eec7c9739058e599600fa577cec92fbfa09e80a347ef9483c8a779ca743be28b5066d9f170fa85f7e9ab720e627d7a57432f87b7bd1b1e3ac857ca53373c5499042a2f82f3523967bd1a416495a71e5b28f706a98d160ae5284a5df378f43f824c0f4f3e643e1310a83cfe75c94395498093f3224111235580f00415576c018b2a2f951fc179a9f211163fe5205f8813fb2eb7ae4fdfb23778b30ef033cfb291ca593e233267a86707a693c3085e2488555e38c35abc54d9042a3f62e4088ea752a9148b04b2ca6b88ea2a255ebaea3b62040610a46812042a8e4003a457ca71982c21072d08d183931120e1a552a95443d01c238c5095f113d804abd791222658bd8e1851a552a9542a954ab1a452a9542a95721d6a5ea97c2404443002063e80a20a3ec45e2cbee2478c1cf980d2113f55f8208420b0f04ab911553e825d47470756f352390c7ca95c07c67aa91c765f2ad74131e5e4b05e24c89a7474005f249859e4bc54ae43cd4b958fa45890405194d4c31150207ab5b37cb865240c8c48820aa028a20913bcda531fd6594287064f9aa2387ab1dc809bcee5bcf493147a4418b2ba4d57d7917424a4a42425298d3ea4304a779a4a33f552eb3438e7abe67a3465260e60ed4232854e403c9132429db5ce5a6bf459eb9c73ce7066e6db5148c0d1efe760adb5d65a6ba5b6564a69a0b1a047b7b5d65a6b6da552b214d02ed306e92907983a5d41ad55e626f4ac2d4ad45a8764adb556f9d55a6bad95de202c12a594524a29ed224e2808abc7a61e80ff51f36db0e3fb60b2f9cc87510aef38468a5cbf9b96757480f1d21cc6f2ba33f453852c9f79f46eaa8759d54094f2416648ddc6b8859f5ebf08bca4535a9988c0485c62e715738e4b0f515f7348552f652e129d3565541fafa084073ff02096745dfbe2ebeae8d0afeb3005bca46b1f4c01af5bc48629335dfa1035687e73911b4586a8e1bacc456e9e353f6f148ac5ed01ffb0d7adb5d686d676cf69ad9cb2670eadedaeb76f6b53497477370a093846afd189287bd8173d5e488df9fe80ab771354d1ab6f21c61863cc818b9ff9cbf4ea1e0d35ffa8be65b5d25ae9f4e961ad744e1b16f9da56083fb2a7cde1b63d37bb6ba320620a537c183dde1eb27c1bcca69d76ce9a79cc6c666dadd46809e50d5d97f1618dd5638df1e5f404b787d6d61861c40260edc72590a8fae48f5cb2040e553c774e97b019a3df9f8f7e97dc1ecd49d57ebe3f15ab4807420df634c771425a2371b791a60c754e7309a47d5288096b1e24854efec28bbfc0e2210bdfe7b81cbee4d8e00b2e788b1c5e8d2547cddbbbd1a8834893a67bdca64780639480575209060f675e3c045b78e8979b81218733e04b0e416f91435f71560e592f646f19ea4286c9b0560e59cff2cdee9934f4e690e54340c1d26bdf6cba99541aa4b149d33d53863a8b75354dd334adc8d3eea1ddd33ddd036355893f9726951bcee3a4d2c414a314c6aa92058e455124f9b07b62394f9d73ad7b96b4fcc41ac8c5a9b7500fcda32943fd65b26ad09cead09cb934a94c269cf79c7ab1416f1ebde479d422cf23569e47b87b220cea282bf02a87118ac7bc88d1aeb8720ac2df4f174ef5b17cb741edae7c21eb531f4651d8fcb05d76702887421ac3113de1b28df85090da2593e88bba14e286a064ca87ace7244f266574498f60da004e82b82ba43f2608b87aa51ec606a35f1f1fb66a77cd0d84f5d6bb57f59b4320accf9c3ac1b1bf968dded6eb57cf22d3d38fb9d5441a9c3ce827903e367d2c72e2e839d4f51873bb1d2db85f68fd3a57bfea8393420d4697443662d3ee1ab24cca9aa3f51cab5f577da11c8a992e2c90413336d13d66fb58acd8a3c1e9493e7a8060cd8d18345eccccb0c97a90f4f4abe31660da8e728e76403e8b0abeeef9c981749f4979fa6ed651fffafdaedfcbfae4132221e090555d131a923e358730315618ad9c13e40cf2afc77b93b67661150bc6634e5ff44df36cdb9cfb429ce7502894db888fca2dec3f324779e6d8bbbfee7da190f73cc779afdbb66edb72b47e6f563b698b6083241b2c3d2a6a3182b1c17be517e13c666b6f23e07af56283f8ce9bdb7160df378d83e42b97d4ddb9b5f98f76fc1b0e5cd484298ff3b67e9bb59947efde0f48f759e69c2065a02f75a48c214518526bda9343841bece01d7007e21d1cbd48cb7a7c5d9f996930bee2940111d01e3da7b55aaf5996f38c182e3c78a65df66d749817edaad35560b56930fa107ac0f363d1112ca689267a607d2c3a32a20918cca08a9a38c14b3945ba0a091fc2c02cf133869f4aa8e2a7b39640f4d31de9053f1d449a62866615f4ba4f70dc044d0a25377648268e402f66028b2a7c302f08c5e89b76950541507c62caa78d4c3bef26dec4971c396d403dca9843e3120ec738d48414906ce10947988003093c11a5084c20f184931ac4a224347df4c8c2d98873768843ea2ee05eb0c0d31b7cc9229eb89726bcc2d196a15485733cfaa2051cd21570d1025362079a503246d93e240338df3ac06fef0cbac4b55d9e85d1c7a22c827e001f8ba6b0f3b7cacffbc7a22a489e9b2e001d6c2737bb168e8eb4669953af0e88dfddb1dd6670a2808b31594cf9d4c7a22c90fe5e0fade6f58b1f2a0b1c5bd5c1cfdcd60c3618e38c5cfdb22cacedb2653d5b079f7ef496cd3a40adbb0616b87aacfdd5876400e7e34b0f87f51f36b7ac570b7ecde01402d8e0ac6990c6784a63e02168b4cb86dfd8d1ae968ce15143dfd0ae76a0fe621aa4dc4b0e382646d2ee8e9436c1c518638c20e0a20f3a549448f96ceb3c1c324fbb4ba95fc7d7bbb93336f35a83e050bd0650cf5838a867e057af19a6c129ffde4ce37a35fa33ffd1366dd3786427a384c35e12f6922823f3a9f98fead7c3fe0982fa3a3d1aaed3eb54fb9c7addf1f1f886c8afb986061bc9a97b45e7ba29bba6a710e76f34385dc60a4c9d46bb5e34388d7aa29a70d8dafc7a6bcb3a40eb46e047b1146ec7aed486a8596687ee0a7694cd492cd1de4e4619b2c53b5f396bd5453943124a765ec6c7247a80f22ebe72ae45ddc6095c3fedfcc9bb93774adec5bb961677b92e9300a2e70aa28f47f8588485a5e7629c0b404c0c31842efeb9bb10b35ab5388c77d3d2c24396b7b4b05a2d2d0efe8bb35a5e5aac5a5abcb05afef2e2ac2f6cbdb4f8e4b73c1a5a5a5a582dacd56ab562b5f06858390bc6abd1b26ac9a10b003a292305a31efb8b8eb6ab8bad9717d66a65af8c48545e8d7e568b5bafa9e1408b7739c4f9b0d5b9388b77e3925b5dd6017ee71d27bb2883e5e1ca5d9ceb3ecd5dbe95cb5c01e3afe5e2d8a357a3e5e2e0b7f0556eb9641de0b7607d42e8b7d444182db915447e8b0fa1efe24150efe25de72e9fb74e832ebeca43bc1a2dd925374ca6b3439386c5c2d8cb3c9539ae020e2994cf320fa90f479d70f999a73c1aa85b28ed9a48344a94113f73ece9a06e5de543e4ab72a8f216a74fda1576f4e8e49953a376594f870422e7bd065c3fb9c5d1f9c9a3674fee33334e864e7e7c1927e0b0777cb2ec4f2ea384b1f79276fde8dc25ca6079e6ed4394d162e59973ddf7e22dbe20a8f716befac297dc22b33ef9288f0617ef9288019277f1ceb9ee5a7cf15961e7bda3c365e5f275df10f9abdc79236957d8e28dc400dc0164e8649411fd6a2e18f5c93841777e344f8359276930f396ded1e171f9e1e22d9e39aac54f1e0e97165727a38c1a3f1a4983990b91dfb0063397fec9383165a2c832ef2599b792768529e73cf3ee69d78f944bcffce4e9b83864e6663dd929ddf5ebe2ad0b2d6fdd8b343269ca646ebb28e3e4d666015ba7f625bb7ebb76757e9fe55bf9427ae433c7d7a6a76748680a5cf099a332eac367999f3eaa136164d9cb4ab8dc4a1aac49df4abe36985469d267beb9d6cab20ef063bcf4188f5fe22ccbeecd31dbccec5abb9231394b4f3fe7e9d73b0d1abd18f2e9228df59932b0498dba259f4923955aa6fdf6c5392c9fa40f2f351ac12379991b49157034d29122a9101d6a1775a16fa73c1d63e740e9a40a34581da5056c33db1dbbd6aefb748690f2e8095116b02462c2d14349c4235dee849dcc014b4fd260953cedf4933cf7933b381f3f943b3bed4ae27937d7a90d67bc8df1f6451863edcea457f36eecf7a4e92b112889583c92087505b6117d48283808a9324742a981c68e7ba3babb2c924a7c65e2ab5fe9c4574944d439a722c48e76dca84e5c29be56e1eb15be4220cad41a228c5a6b751bf161ed42128976925031aab7c1ec8b5fad95951d8932d553f0d5b5eac3d72875228ceafdb125b81853eb8976313eb6100f44199d479f51d3ae98301a355218956c3e1d94ebe02f541ee274726f9efa74ca5d96655996d9747b5ebe0dba1061f4e57278318be53974102996d3a06c6aa00e62612cc475db303303828dd4488dd4d80672a8652a876d80abdbae85daa50105ad4481fd45003af8877420335abe9ee51fd72d50d3dccc0a1cbfe60e6a705aab65ab63f3ccb90e85c8ffd1e08f5a4fb7c19a43e99a5f29447ecd2dd4400dce2a969870757bbbe8d56a33efa67a0bb5ab851a9cd9965b9affc802c801b7b68ca3baf4ea12e79b4a834c587ae640edba3e9b4a83424dd34c3535333320e80e04f4b997ae9859c78a98f08121d949d2aee9cdd3edd3ae56ed9e9fb37f2453839369d2f4d24b0e58324da9e4920a4b4e9935c69de9f7eebcae072a1065a44b9993a68676a17f8c34f405a485bca899345dc51cc1cf69829fb25156e0e9311386894a39a7a44d2885b7e3baedc4e97cd8d9af818830a4e34f2e913e0d4a5ff9e451835064142539452e354845f650ad6b50a5d1a0e550f27c96659206a9dc519a342aaafa62cbd8cc8532054f3d942a784b79d08c0a6c352d1f4d24da3a4819a8bff49108833a8a0a0e1b16ca1d9e250d0b1b565d228932b81444192a90336c3498a6c5a9d3b8d13c6a6897d7f98ad3148b83dec4a282a750ed4600394c67015bbe292f616c41bc74ed0b658ccaf3681a2a75e453c733a03fa5a15cf20dc4cb9e06a5cf9ed30da088f2d4fd6733b15ee38c33fad33c5f3438b780c338a5083fa713998e838e2883c60c25e0f67046ce3441ad421ac4a1c13a535fb4c801b770887fab3cc281c794a95e6b787968b9facd2c6b057e418b504bd9d2532ef55c94d172c1e9d7e75cc699f66e60e02ee7b143f06f58ab1c82d82f6b53da563e4ea6a71ebe44195cd2eca09df006c0431cb2029077a6cc0e8bfbe4db9d9d18616cb45d5ce402d07217ce5b3c009f8c3201b28594274d841f25529044d1243dc67c97366e3ce7ccb280e7fcaed75dadd3d932f35e8c592cf79e892daf16617c97091b71775e88b577e3cec56e8c15cb2bdb452ee6331213f30db1721b6b90ae7c738e5badb8956f1f277d6870d1e8431b933f3120a1a7edaa31fac99f2c8624611226916412d62e942522d24c983d4245394efec0268d8ce590857de6ce53e91305ee3cd39cc60d1a1965564e35a114d1d316bff2d03ee988f8a0d8a476b5df22f0114f3f8bf434b451a8671e07707cbf729567de4de6ab0fe4be0de7f87da1fcd5cab9d975b2bb9573ab2fc8c62c1086026b7e6dcfd315a7fae2ab3ef9b6674e59a262d4347888286968680a8fcf9c3346c913238d84b9f018b73d93c6454c6c7a37ee12264f2051105d5f8c8bcf454c781dfb09853f17dffc3e9739786888c8e8fb88c4fa933945b05dc2d99e4c4aa614030aeac1fe7dede2eba696a1cea18686888cb0fc49c5a48ff489c169629c3a456a575325fa8446a152fe884279ea315099333444644424d632a7c884090db56b23923932c7b9542c0614f4110d310de55447d4e04a478de850cb508f439808bbe09e9fb6238bd42e179cb63f793a145aa2580c288849e614913f34168b01057d1ec3d753965a667e72480ac9a09896635f963e43b6c7f6d81e18fc85176230786863b1185050bb60108a31461a09f38f1219c5268d84c162b0180c990ec5246ca888060a9a33d6ae060a9ad7850fe68b8592281693b0586c084b18914913a363f8008203b6a1b1d118353330c6d85b00406c5a6cd7ed6ed6ba7df4a54733cd33cd6e0d901e0e9b969a474bb7acdbeac7846baf5e8d76cdb7f656bdf2e2256e078f28834764a91c068c9979511363e8a3116e998e9416a159483b8f1e38a81a0cf1b766a7edc27b3dc4f36b5dd75c4583cdadebf5a7e7d9d3a7964315fe1ab2eae3cf5e81c58a2c161478649c4bf209d29c89c19244bafdb2a2741d70599679ce5104643e3dce3a10d6739cb56ed354721ce736e273b9d5f90fcea7679fa6b9b5dd17b3edbb1e3d0de7b56ba9dbcc4a7530b21e63381df5ba4df358f1c801995b3aea671c11da02bc39e7b657bc3ab7f2e9acb80efe5ac94374cf0ebbacd56e3d0a2127f6b1a8ca949ff9585445e83397ad2cfb0b06e6c5ac69574d7df2655bbf524a4f3990eeafdfd891caf7d6c43458dd7e30355f2594af2ea14499f1b5c624d0db2cb331328f27b76939f4622646cd0c1bcbc9cf935ee7ed7e925476a7bc7329e5951fce5f6c65ec35b7f6760d56d7b2ad6af14583d56334585fdcf0516bb881478f6a933c20242979555152926fc7ac952778c664dba677e4b28f4d49c9c71438da56c518715ef320d96b1d3b76f4961e3fe931768c339d64e5761ce9fc28f5ec55ac982161efdda8d5c16164623982439611608ee36c8c12985a29734f4c667289cf4f9090b54d99dba8f51194def95aa791108165457424129a7110b8e965ce0b8b8a943b836b58434715883452f63831f408f858e4040a9e93319ef4ccfe6919ea442ca1a714e82952cb179c043355286f731f1575f6928b31fa58e484cec725708da94b393939257855aa13c178e3440922183f9c3fa77f31b5552023d2ca1370d861556699703e3369b8ec0ae03c731b1f2d235d2addf898028711c9864c3a47135f92c85fcfbe2dcbeed558419c94525ecfbeeb3eaa80c3eeb32ccb5e482dcb315bce42fa32f280b7b63ee0ac4b89f2a974523bae01ae01268a30a4b3ac80b3240da5913a4abb3697520a4d9491aecdcc482469e27cc051125df2d4dbe7a96fdffd79da4a61f63787528a979e25f1205f3456d12979f00f587e28633d27f5592b95924ad849545e7a272dcd9ca6764d29a9cc9e43404c8f31d185053864b500677e1dd57ef2a9a150a8e83c6ab0215aab9d3479e3a975ca3aea9ffc9489ccd99d58edd2bcadf7e66d61ba8de3baaeabb5ba8df8b5baad6e51aecd89fa22f53c7a737a9c53ae03db15b950f25069a9c5d9a7ea69c364d57ced1733944eed0a3e1dbeb1c3da1b888ca01cfdc4848228ed1b7c08c1214a0f2ce0248b3bd9a23e3db32d9a75d427d2e0cc7352596b9535c8c8677635d8d060b7272fb7f9ad35f3ea35dfed9bde8da6759601617dcdde63d2742d533fda32b5557d7a763d6bd5498455050e635205d2bdf41eb60269b0fd063a054fbfc1519fa13d3bb620e9b2c00eab603c2629d584a3ee0385ce4b263e1625fdfc9d1ca553ceaed6c448c2d3e012d9438b2091b079830f2138f40e1e15b0a1460d0d9bfa33cb1fd615d8a6bdaec1d99a4e7dd2963541463e5ceda4273dca04156384cca10f20ac53e652c887da8d36435b46b6a44f3f6539c441f29967d3bb6151c1d6e5cca489b154d560744dbbe19c524aa957ead5afe3fcfcc24ec894b29b191314cac1a641797b4cbbeecd79fa4c4b6f15cdb4d658bde5d987bb191bb0a3c1e8f4931186d72925208902e56351139a3e94c1bbc13d387ed83ab72955c5d41f70c4e2763845ed17661edd5a6a53dea35dd86b8a13800ef6ecb381abfc1ada1581b01ee756967976afcdf1d2cc1b4ce59afaeea76a10c7d01d60cd0dee7eed9a20f862264637f86266864d8da42c1856b594fab014acb91183c68b991936747b42a5ddc9fb6e5701bc1263c570cc465abc03968e7770f53cbeb0cf4cd9398d80eade47c41b949293dd9eb7f5aa646ab0f2e8e17d3bdad57da3061bbaa50d914963633d8fe621b0c6a12d873787b8dde6069176c5df41c3263669624853a6fabd2cafeb52de8daac1c8e231259198a4944c9249ca5c244524261403ea5877de8dec6930c6d8b1e4edeeee9eef31b1983aa761b1450e0f8f121eceeb4ede8d97a592259fec69b0121165aad75a2b11be5697d5b9163918b35a8c403221b5773747e77af546523daf95c894a97e6f6579b4bbd9d33bcd934a228ce944264dc52ca62f9459f82a99228cea282b7018a17ce7d369547ff9360e79776d336958bcde15962f4699f6952fe66009b4ea3cd66075c9d43a4dd56d3e1d8cf3109ee7f6e4799e976fc41cdee25f4ffbe1bc9bcdef4d3add8bd234ef78384ee3384dfbed5ef7fc5aeb1c672dd5bc3fcd6d66c7f08d5102ee1eae657cc0c518aea5c4e22393583df368b0df5ebdbedd8b0074ac676d6d56a54a95a6cf3293ebf16623dd6b08af06759be556addf93524a339e55e09ffb3e703787403acef6607be79ba793344fc3d8c631c0d5bfd6f4ea767a7deadd9a59477d9abb577d6318d91d23fec431c074ce49b3147a49d41f8d021763f00e4b600edfebb776d7737aa29852cfc321ce9f3ae53f6eca39af1ffe525fbcdde18a3f9caf28cfad77f276452f94d6b3ecde7c1b9499c7d78a6fdf5df96e236007c74a85dc9bc31996629fc92c7515cbd638746a5bc5c2d4c419b1b3acb7deb9746b61da45ddbabff56edbaef671b3cbb42ccbb2cca79f3adf3eef0b25e7d1eb70bee3aecbebdb759ba97d99cd730551e2111f8b9ac0f34d807d2c6242ecc30570281bf125cfc7e80d25ca9050a4cb0e78070f1e11c674d901872c3a135b3aea530f65d3c74f4618a15422efc7a728cf3d8a9ad2f33a07c27aaf3b6559e67954f3d97d616cfaceed173d0ee7b97af23e7977729b9969367d3dcff372ebe43f3cb77ef2dcbae71647484c3a7db1fb709efb8074bfe5fec2bbe4ab6b5f88d3f3d56de6955b57262ec684f4e8a97b94513d46a49d4913693879eec598c5720fc12a83becad54f02352885688a5c18b00babc687428f4d47ad11e0b07ba83766f9ccab582fde4c8de492e3110c39043ff4687473e82b398c432f396421b1587238a30239240d2953a197432c39a4658a04438221c16aadb5d65e828434699a24b5d252f760016b9244143655990251a1068390264d0c1f520cb9671065a8e726f723c27311df3d58c09a906047485192a428f594a5a6d2ae8da969e6b40b069b50e651bbbaf07ed376eeb3113f73d55763d42a818402aacfa8742d3685492b999a010000005314002028140c078422b15038265484553e14800c92a64e725219a95910a31432c61842882100000000044064660201903e38eb3de841ee805adc29813239aff108b46aea00ee5368f5c454e1dbb9f50d57bcacfb0cea2a528930e4ed788a6b787fc9a547d295c13fa05fde8209b1d1b4b31b18c0d2f84e0074d5620afc02960a45834b953129461ec535b9b1ff227ea5b1b1bb0fc451fb139e3d3d7cf9fcc46621af7bcae73b2319fd2bfa5d579c55c560b119920c73ec05e6835f382d47a515a0394627efca95a3a41737868418ae7928a3075bb2fe2c3085ab012453ac01db100ca4d7c8479a082167611a8be607032d8e5d117cbd49a36d1775029e846a01ffa7b7658d360ae4fe2abd2224c09ef5e4da55b4cec642452c8524cf173a9534da093118d68ddc9333db22947833063262a5ed319885111adb9162f426de0ce1f5fef2f23e641991450a0c124102f647988cc78b6105367df783e8c18a5b2bd388dc6dcb96ae186344dd9846bf27d2ef388449c5bbdd88d4e22df253bc5dd8ee7668ccdd1eb90185799ef1f7740e709f224d1857d3d439b181bd49ad035a53a2b3b0e47e95bbd6a191ef2f02865bf808df98a664379f71e3c86eb00025952c91c0b6ac075ff33aad85955ea153f5bc27393c92b17bd315af0b98953d6578109979f36d8c2d1722902b6fbcd6a4a8c6206c1840278c3428f582a622db744a8a1cc590c062f9dbdf440be6240dfccfa2521539a5eb247e5cfe52b864755b016c4ee18bfc8b270dd5cefe90e627f775d64391800cee56576087f178bbf7202ce7d4dbc043c19d2b48b40e1cf07f078620170383aa926ce3f20025e035e8eab2937d6c7904db7b318654666cd318db12d5ff2ad4a7ab686d200eccd16804ad693a20e261966c463c0976c5ae388b48f38678aea05981ae55fdef9bb285ca531f962d8615e67f17ac558a2941a0f65d44575c2abcd72cfdd87151e1590d20d550492a6936104385b72c5f89120e414899551d3258a3c207a6c25a4ee824e15390efb7891f93137dc1121b3e888813f6f4f0b845c4ab4e5e11ee44d2396382101c4483bd9427d76579ae292753cb6220fc66ba96f25c4af9c33927d71056c214a98e4cab3641bea0245629bf827f8dd66c00d51df8ce5b8efbbeb9c46be5816462d20206f32d6724d5d2cd7fd5a142abe5e8d92bcf34df8b3616f3d6f21d40c4787a0837841e8412b6f300332cc9ccbff8f6833e9af4030e7ace7cfc0f2f78892aa31432c0fda978e80ebd95cf2c1dd492526f8b892b39bb70e8d76ed745c660dce61f0fe6513b1496111ca7d9a026293e724083133e1fcbca73bdfb96cfccb32028a01cd60c039638e1ddca8d9097ccd9f3a39c3c81513958f47648acfcbdb14e1c29ddeb727101e17c26cca1980e79595fa783e2cb7acd20a3acffcdc755fc5e16cac8fa2d780f581301f0933c26d4651ac6269920ebd9b4af47437fe14ef793b1fe33a216a4299cd92d70ef5b52707be2b0617d1bc2fa634dfbf489ec23307dfd29b0b0818a810e7255f6f05a58c3ed06780b2f55525f9fa32facb71e8fc95792bbe9f5d78dbfb04e864ef432a7050d8c896ab095a73a27957e85f559ef02516a234d68e6571b36bfdc3d6fa87c70259c4c1ca33af5b637342cebabd875af18040555fd6dc4f7fad8f5e6d6c5a938b94f69d17c87f76b2c9ca4140f314aecd305a28f5b599ada70a21e4b49028075b2040fe9045a15267dbacb7bf50fdffa88351f41172ec77546a6f74d9f4e3e3f8a45d959d04dd9c69004d298aa3de3e5c9544883b16560b28649b2639a1fd96ed15f5e4a5c05f57c5e8a912ace8410673565071e78d5ec2cc4ad536bf3022c4b7927d134c412d18385adebe470c510a352a18d94580ed352d0c2c4f854aa124dcded2f3db54e520e1fd49bd2bc063b05e5e0a48b7235f75ebb6dfc72da5bc115127e3c28e64b7bbdcd95c9dfa72dc6c7c7d253b43f6ef4b1ef3345486c545c5eb6d66f7f81a9d302a5f5f2730b40442f9d943b1138b11757fbb0bf3836cefa40446c5d2437f0040d59cb562949544de36150264e720a9f41b8b795ed73855d61b095c08b3d4194f62879def1961c56b551457c921d693edbe7c373fb39dd03ed54874dcb76a9460056aa0a9410eaabb8e8c79114c106a71b5bee9a6b817cb4f2384609e740a3a1640443a228533f0d28740ae804a4742d97eafd3787e28d1febeac80ac7e84e94b99645a1017a2793d1cc1315991eea22a85bb90e9655549a3e071505acde8f134da212f6032bbdf83a948ddb65869943824b3d7b8ad3d97dd09e1fe67db27632a0b55709335dc225ef195e42689cd145afc89c438ff4f891a421846edaf3264544e2ce14d98be57f08318c7cdc85182118b7262ec99de23f427ae18439596f182cc71602acf8613790191ff8ae078548cc08e38930d238a5b8631047b93bb21f5ccc405a5f32ec76ca8a68b808b1baae45c2ef8757af4111178219b7da8640ead7e71a910af8ce4d154d0092c56180600d202c9bdea24b63fe169c8687b6e4c453ca7ad28cd268d4bba08f946c69b197ae7b34c082bce29ab20091aabdbdfd12b480186cfbf3ae13f1a6914dba65890e221471a8baeccad6aaa1b52800925cc2ba9f43e0792c6c0805838a679eb1281ead0cc88bca21e426f912196dd3640210e1ba90a4e139c26d1df367aba3f7cdc9125ae2c98835df7a5b1a9338fcd281b79f23b846acce9b14bfd864b23bd08a8ebac00b0e98875812c564ed89b9274597bc248cb9622c7f1d060f3e7ea482866bf09c00538e2c2f99fbf15a272e9994e48b39bc0641943ea055b823c30ede930782df48b514d36f7cf13373dedf114aa6e26169d255831e9c1886a73f3a9318fbd60669138ae6991ea7e9e5ee92352de042ec0929b3670a3cf9d8b0e87014f1065c3d5ae2c7a85b86c0e4d1018ad1d30e2a69c7b6fe22ec02d63103e6080efe139d827da87d504c1031f9e6794744e7124a99dd69ce2c524f9c5210baf1de46d238130d9d6fa0c5dd2a050566489dfabcf8e7a31f49c7122ede3eff0d22ef77e5ebab1fa73440300f5a5d2776bbeee6b6556c72c62da6ad3db5bbe97ea1591a13aad729da07506e8c7b3ac47d6bfc22aa650c019641887bd26be4352bcd163b9489ec5c2afa56f4fd5d47a8957375de08a776aa55aaea0149de260e3d3f758679169f7a1660345fa4011fd8a8a224219fba8702a1a36b9cfbd49af5b0cdb1797f4a51c97f25854c75cada1a46bd7dea0f904c9af2735dd6c89067ac688c39a8309a304949b44530ec7080004c4a1b79f5fac1c5e1a86291d578e38cc7e0d21afda0d385693baa44d32cecd50ce34b5b45ece47220b55c577071c037c1d477922208cd3bb51e99eb02f3f1c9106135a670db491b17431ac0425b243a9491e24d2cf1f280e9319d37fa3ac59e56980bf8824f6866b15fee8c79d9eff338d83f0ca930b75e2612728b937dd1fdc54c769c27db36fe13b2b75ce034546b3a4d15c4effb0f057920f801836af97181d27ff46cea3cf6d4a79e9b886ba37240bed7a2b6cedddbc46c55f40e2813848f9d6307f5a5dc06d91a901211492fd76836e620caad77d188aa030a1a48a6509444c9a009d3bee5a2c8f487149890deb894a3250cba83e712afd090b22b0e4276a00c08a98a65563339c8ecd4fe49267bfbbe23a6f3acd030a7a7676f429f07525c0dc4ba6cf709175215b3754831d8a010c78821239023c8532429b047472a35c468f70026a932e77bf51f44034cc4f02da70966be349ea8ac74d142aa9c3e871b7f4a5b20a528c071461e747afeb39b51ad8945f7249e3955065dd1cec8f7082d53d17988abf76522b2d32d121757cc4a72ef8b7eaf417b363d911163705ace41e79dca7f3be205ff86346a95431ec4e708188443e8edb3a2549f6ce625856cfa995da85c7f45e8c8d9c12ded98e76f9c3a0681b8fae6256e2b2013779fcd9b97e2a785cdc1269f47a19f5621b16c69f5091b77c3e236edd46ad3178840c2202dea1accceb9d0af46cc8bbaa05079c15213f8417cc779c130ec11f98d2f2b8d32c1638462dcbe498b75da2cd627a04048c5e14756b2680dca2d76e7865d0b774e6623f4a654a6b369260ad8b72ddf1398dbb6e6c3f2cf841342d933bf2e98e4982e05293ba5e78c0e5e49940433cab9394e3a44f9230113e8d5cdf23262b6c5a10675ac26ab014301e20e990b618df6d0a04da623036e1da8c0ee677a5b2b904d04bfaa51f8950b7240d2d017712d651a69318880200dbb3822036d4f84c574965a91ab2a64a23aa22de87e8fec5b29b7fe22dad4207fd1c1a80feb55e078204e1209035aa2fa8fdd28d7f33423019654cf338594f746ada6d3533f82535c204d6e95954e870da628dd7146d029cb8473211e40fdb62d4f3fd824431be5440227c2f7cb0e015e2e10d16cd9b8eb6f23025fae1d71f25b70cb36a4d2c64192646e3152c85ec2a08cf62c6b7aee33cbde6dff633605c960347574e73776396d88bc84855253776d802812d21c669f977cd93430dd4640aee7e7d6f85536a7fb478ad3b23c42d7c5496cfcdf02f8f7515aa07204cb92e86ad02468fb3878826336b3064725aed1cf71209098b05e22c418661c1479ca243d2fc672c70b943af5012d709d60ab9310b9a4630ae8dce9c019071cc38957d82b40f8f89eaf87ae8ebfdde3c89a43d408a06706da48020680a02e792117e7df646b261a987b98c9a6f05403b33d6390728d84398abf925e8ca780a88a88cbd02dd3ec3be5d522b488a6b7cb7331667202e6b8fff75fc81024dce4a8a116e29a2570ce80897e28a3adeacb85821406a0f7928b2dc10704a6e021c0676aed92d89cdc065b4bb60146cdba5a44a769c44aa1ec3d87f6b961580f40e14cce69664b9612227b983210df05daa481d22138b7401bdca5adaa610de5597ccbf1d35443bd225b30913568abf490d455b34ed66d11ea0f1ac73787a45b206f61064297841b4b9c562703773051fdf1805c20acc9278b95a0cbd8680462a1bf762fc99995f17856d90bc83448c5270c687575abf8eecd83d25c3e1d1e9fb7802b660973561cf65c93c39314345db02cd9075239dbe6076c3056e640dc3a41f1c6d96d65deb9895a237687f54820d52b679c792c148cf03c2900f75c05a996b5b8487b9e95e02134bd37ad339b3f71fd5fadd0371d24932287e964e366c46219edd54a4f1df60436f51d5cd824325a2c4e95cf46681c4f496ba8c1ee8b8dfcf658cf34bea02e9c83d9edcaac44bda1ae62ad07d72a36a7b9864e8d0cf92c72803a2aa8207dbcbcb8f7a6e54a2f2d3e7d41e6ecbd90ed565c764e5d9d1695372906751e4ad1923f762cfe0ea5aa4b2c638517a3779efa0285e35700290c98b11c253b6ae85de98f19a074e62ccb1d58b95f7981a2d7dad2ac739c12c5bd2ee9f2dd8047d1996c0e95e7291713bc7d11f9dc20df43896b8ba7ba8a6adbf37735db4273ab40ecebe0d46cb6a457bb82a5c26477d4c86887593cb7578f2d27c1550faaae10bdf206446b1f0566e3b1ca9ffc06e7b7218efbbb15d784afa25ea8e634bc6835209d71735b778da5592e1770be931e5c5ac4ee7e72f90f65573019c48212a10a97eb7fc398d616a4f898c0e6e6c860d95d8cc86cd24d860b847e11602d1d04738c7961a85d34b5c93ec0fb2cff5dd9e8a188a40a6748659cd4c17dcd5a54c92b23677afc295be85d604cfeb4dbd392689e2ec8424e50f0177a1f62f00e42147a78f0a1571eeac31817fd6e5471e04c0fa9973a335d82aa45926c4d4f8c5481fed060312735b7b3aa00e32340453fa57c69c77c175203e836814f9234470ce570d0efff966ba21e5372d694c5d89e7dcc0b4ae6633dc3eb1f57f414b862ba4900dcc6170aae1d38c447f51882c0263db00d5579fb0c0814380a15f403b90b98c5ee30e2bfa896be45fb5d01c5efcc42bd89ab7622492338936a281970bb1d4740a7f069e24c11d6ff089becb221a2ff6c904bc289a3ae5dc438085259ddfd35a4f20c260db05f561c50e2fa3384e6261c0e45ed7c5219c585facdc3cd3028503ee85d13761a8ec9faa64180e530cc989833a016d0b786633ef29647212894d4979a9c5b736bf57c0e8105806debf20a31856463d310af5f3f349bbd3efab0647d386b5592793f62985d231b33ed5008b480bf7588629ffbb57edc3ffd1cd8480d7672993bc9d0316235a6a8d20a67f433ea9bb771b6a2d1980f77f465bf30887f00954a564e580c0e6b9cfd70737d680c186e57487aedc35f72b5bc00c97fb491efcdf5c17fc16cc33e9f4d38dee7d26342f51d33702b00903cfc4932b19f8d2884af85b3e58fd298ea59094a90a31d9cdff14b298e6434638aff029def133714e0bd4e2766669dd4274abf3826c50c9fd4ffb6936d4248a19af89ef2056ea7d01514d16c7ea0537c996e269e79548ae0dab245f11025a8d1069096915f966614bbb796b23d715c04da9160ba217d19e45c729443a6dc330a05e32878cdc000ff6a82ebcca021084c56069f9abf2ad7861455091ed0ce27d47c6599b3bdc992124e57299a4fcba04f3cbe6474b6c132bffc8d33238147a820b638ec08ceb4daae08b81ac302b3ea0ab8a202debbe46e0a9857243041d95df293e209666acf00d20cacae1cca2cd8e96045d98a3703d0cee8cc3433c87f5970f20376fb8620db21b76fbeae189570bd5c34e1d8da8848d177451033e324a8909e5de1950cef2b9d18030b57229891caf3517a45fdccaf43ccf025f724054e34045e90b7066fe24de80cde5fb77fa179a020394575b325274d6d55dc79043ec1656111eba75dc6029be9bcb6d3148115b72999077bf02e13fb4bd7ecf20cc97107cae742582337f35fdb74d61efd0bd1e5d82b69f2b274a027fc2ddda40914314b0ae803ba5270e6d034089a1ee2f80ba5dcd7d0c8049a5789eaef22d563be404499daaf98a721683ec92aa377c01951632c7f491a0aa941e4df20f92c01e6917e3279cc276ec03438fa0be22fe3383258202c751fd2643d55c106b3b06c8a3725d0ac955ea4b223e05fab0bbb51d9d5910bd5248c476736860d11114ea2acf3258a4ac39d3e548ad58d41cc0c237ef6d5524d5b728513d29fae1b79f46c2ac8f7ee0816e18386ca748fc032e8f6f006f0138004bfc07897a6325903b6497e48cfcb360ea02fd3ad32f00655812d5bf20051e8954cba1ce1086bb450880a4193aa2e53b296fe4699269c19df1d255d7fc99b0cdac5820bc5fda4214c44c062493047debf217ea25811ad9b5de12274c85e7c4900594bd82f49f0ddddc4e79abe68047e5cb13f286b3e8086208de8335c785e3d9c20194275f88258a7c202676d6c89f1e3241eedec0df147a9bcea1d2c39d1a376b3e79462bc59c7fb7e501d83d7f22436c9247ec506a61c7c912c51a8f6a282f0d995516e7c0195bec8429968655a7d86ec5b4825c01c70dbacc40e75f89a77d34330e5432a0bb70193106925a0ee617c838aa9ea5cc5826da6052d0261cba0c724d8dee16dc0a25f68ff3c50ac7e55c2d8c16eca86ba75c0d6acb6d733ce3591a04fc40d5a87038f770e9ef70867e1c8aea175f8198a93238bca4929f6ab887fad563ac50a2cbd1b53e46ddb0283ee0409a57e62347ed16472f21beff3726dfba25d1003839704c343ed46af7c362a3777e9802dbec426e0edd7a181351cde384ef375905d15e0c0292261aeeeb7d3e67321ecf79169c9fee1aecc4d5d72d53e106c0734cada0591b42b103b012d72d82df4b24351b2f05538dc87a2543b50d344e93501ad43ef5097bab87ff2f6688f953e9ce6166435f88bd3d98de84f382751952ca2a18610c4725909eab59f19a4349d2f1077a944445ad7e22bae368f069e4e03c7bdfcd521f8b2d30f613e03214811ac9001358632f44b655a3a1d17c1bd2883b89f0fc978c02f0216cba9e70ce2bda4bffb117c43eba19f9872f3773c17d715ec7b7048cd5754277887d1b10d942af51a7e39d589fa690533663665efaf4d039ad86a687fc211e16c49ef0653e9c01ef0aeaeda5e0b96af440c9dbf4a764ab5768deb69599e811ac58d38662b0e305626c1dbd3e82562f7fdd1ef6bf5aa7b62eb9c9cfb2447265951770cef3c638ef63c68e5163715fbf11cc730792ae34f638553744e1734a86ec1ef1952591c383805086a51207f444a91aa1a83ff4221e93bc9599b90e7d771f994e0d566e411a44e707fcb6b9e8e0a56df3ab4242289442369817e6b7d8ee82e963ab41bcdde16761bdaba60817701f38112c7bfc8fc354b30a3f9ae2bd1d1a16fa59a560b915930aba19f736996ac079f08e294b95022280521e6a0e4999067f2a9083e5973e7546c1414b65b1836ee71e5bac7e6fb51fa5d2ca12114ee93cc8c1c5134226a4fb89f993d4cdb7c3b865e19c88e7d8d710492334b1cccdd0804784aecb7cb621b7c77e3818e670132923eaf6d735be3e1fa3a12d1a42708bb84c0e8251db1749eb5a98871795b78608fca6b908c384362b2c457cfe41f40990075938ac53c0a90bb2688246ea7347be3dc17a147d3928d4e05c6f64dbf0144b4885d6bc9000df61949dc389b315d527b7065d33e4c5035ebca4b20fca91c1a5811968d3dd93c758deb1da039ad3fe4742d65fab3b3f9e3ed61bad9148ecb9abb7f14386830e6052e240113aa01e69b7c1e5a77f857252eaf9de860969945e86b66e5c1f2b25abe5ecd3d81fec3e71032a2afb34cccf5f6a88aa4c67df0d8475cd3af49f2008edada2c1b07e4dec3464854aa8fb5b4540ded46b634bf1bdaa69cd8e6c9a1ffd0e2c21d50977e75ab83baa379910e30a76d833aad38a3571a4374ff6e2b895379a01879e99195696caa14087e2060e04eef40b8bd72022d705f171f513ffb46d31aad4b45cb78ad4368ba83031147a9c5717d09a9a4456c07c11543a3d989376d3cb9e5ff4576aa110aeb28524fe5a27334328dc3e04f3e322a20ec8b3856db81c4a2a7ceed2773f4468b54a98be4e350e578367935a628927f03b272dc565191325cd2e24f45b40a2d43e539412b937880f0f4fdd5cbfc39fb51a960e3ce0dc58428c455a6799f1d7b2e771eb5e1a58a9d8906212a685ceb17ba216fc671f39f82fa4accc98a08f5d269506016c9ce02c73ea86108b140c4d86c063bc458bf99becd8a00f1fe4810458d26e3bb301c9bee3069e5779e8e2cde37fdee0f456a87d497c4174ddf0daf7c2599cc21d88fcdc99c8af725649bd8da315d22cbe6077583c4f6e4ef5600901ab106b4996b3fd3abb023fc5d960db6705d347124608bf2ca5e792f3c820ee7e6d5d47d10a171ecdc1ae200c2024ad581684ba999094edf385dc1a0fc9730184e0c8bc0e571c0554f88403128fd876a25c7af82a81acf837c01cbc7c0d09c0926372d5cc68fae1e36ecf90288b000fc48cf096ed170a86d2cd5914cbfabab6085f217218aa5e182f13d4d81e8a20590de96521df74081abdd675ac0023b0755a7334d942513f549b37fb8d3e4d0f2cd613575e5dc6c95de63f8423eecc744f6751638d577359b40ef1790638c7c19517f03c1a57c3a86f5a7b4d9a315daba8aac758f707c0e472d7a0ebc0787e5fb098ee63001d91de1cac35de41b9718fbd2ba7c5ba6df40cd81bd6418f9ec28d275ef32efdb55949444d9eed9c3310327c520ffb04c58bea517fb33b058b2078ac69b7c828ca122566c15f0e51f4dc1dba2d014e0793543f95f18ba5a256e4c0eec81ec5a4f5c5f0419c658dfbd898d42ba1c0854e059f778b4e32b20489dba8ec268eb0ffe9f5195cc308ba917eb1269917ffc8a5dca8ebfe2bae2c6770dd0904a2a12079a39d2aff55f58428496b0112afa6c6370d02e3c56b82adf57f7efb656b2fe3b9b7359c0a52061bff34c8e3c657b0d2278cc5e929858ba05f8f50cf5b1be15584124319491f81a4709a95057c6f260659ac175d47f8b2908cc44d2f6dd841e16ebe4e8969f187d956fc0321098662da82e8a3449c3b52debfd5718704e2becc8ce0067104a86520a0d4e15234a25d95a32fce03e0203630e49569e43cb6e75c1c56ed19e7215363072dab6e27ba3fefa409090c07990d332325dc16ab9610d9881326432dcd9d2da44978a5e9b75db46806ee2581978e3037e2ddf574452543ff541d8f8351bb784a53d4004cd2b702c254c93c014e6fb813d4cd1c8e52e88fea8a0f02f8505f37c5aeebfd4341315b627ea55d2e7359f97172495c9efc4528bfd056cc353698f82ab26b9804964b1312de60632bfe26eeed6bb08c9445e0123adc6e381e50c8b72be779746cba3710a98101d7260ac981c53fd1e7897749ac7521ac5586679e97270fb20dc75b5f5858a0f8c62ed82ffcff3c9d55f4dc2cac59684e60d09cb6f12971647765fdc8c0b79b13f446088298b8444915497486d7604c30771af05de1128ad89a008763fbaed40c53cbc93fd64f0a293a39c02bb9e26f740a20ee7a4f1538029f9d28dbbf2e9a4cee8a0576333a9be2efc284b189fe0ec3185792aed3d5bbae3004c4a8d8acea8a5da12a544ac0149d310e72df4cf9cfdb83b70cf27485a05750a430b8722d06089d8f88ecadce181ae9e20bd695f4f50ed74d45ba8f7e3cb4189912e9b1b40fabb064969b06d495d148572536ad678e8ae1138939a375b6b40ced16300ed497b5e5d3f766e547cae9c7ee97c1fb4aedd63e2ff3ac1c5f7b8f6042b13f9567972d38ec4ecbbad75f1117b0f915d56a085404d0e369b660bc9fd278673323e50d4650c109d2ca2da8b28a00ce03a78cdc121807bf2e38c5cdde0b418d76689802d23d789c4cd0ff819554c969b2f257261da6cdfbef7c1dc4c0883b707fb63f024a20592bf67f866d3f949d79de4ed9dda990acd60aebd473e3f46938c3014b6dbae91bf6ee02019b09e29c0c8b876d3fe67aaa700f5824760aab8b82dd3deca1cd9318a65463630fb1fbf542e72468a63181eb5523a7cabf2ecf75dd8856c3d41023d7e1f6146fba7c5993d0c92024b8b7cf1b1f53f0316a955c68bea6928636d32073a7d8a4e3e9d29a32263f1b3bcedff0724f457cb36f861f67228102663cb34686470207fb09a77a13e593dbf8622ca3c6f00e95fff8071a6d7ac400671ce24dffe66ed9dbaa33a914281833e4904fa99123f068522eb4050fc9bb83cd397b9629ccc45d5fc83245f10bc9b431c1fcda4f52d8c6dbe7f8432c39cc7ba4c7555ad11b43c613653790ccd3529ca4964900891759fd8392098428753ce1a80ccbcf826fd88f58591c4c7f46e848823b2d3ddb504243700ac6b3daa0d13f02b0864f548af43c1453671a1f8f9cbd2be96d50bcd36db9bd0d62746f0fd2f8e96bf223bae2d2918f4c05586b916387814dbe0246f86df18fa87a1713dcb60ef2518952c7082d80809109364a82481c7b0fc12e054886e1d2426c63c12079a01083cceafd083de16f9da68ea6c1a2e4050494fc2fe4380499332047758cd6356513b4b3d88bc6a5f83ad7aec5b9239df0ac04f92ac368988dae5d295164dc1869a11d209278d2fd3e7e585e15fd5372c59c36d2133c4c29f1879b384747effcd32df8db46ce852e45892455b55ffac2ee519666bf316b1bda30bf5a836ed4c87388fccf4ea1dd310a8c0627143b7f10f3f0a93177d03b79950d6b42138a68e22a72e6e451d9e0694c45d99780b9b2d74ffa3d18a362a803b4c920532bb2afc2f1fd8c8ca8f3d29827040ab453c8202b09940869c01182bc3c3d5032c050c15955b2e1c06a424ff9ceca9f6eb30d27009e51930a8c958771f541f2fa196836b69c871d3bb4a8f95d21337ba39786ca75a39da62a512a226edd2e895053cb6ca18d7dfc2eca7278ac1088b3086a3b5a66ac120c786eced96877a512479c2c3ede253853fb8433c5e7bc91b495322889c6852b484cb2dee10f2304d3be15e96cf99821b8c9ee9f2ed43708af47238f7812d66f672ce85ac4b1eb256b4da5abafd90a59dc87eb5da4c3a5118649a24343f83472f7af1afc0b62a64bb650e22034a696340b234bdc24c5840bf85f04a21d8b89ad151e4bbbfd4ba8cb15ec7c91e203d89cda6a57e4a00fc1a441523bdb4a9ef087f6d8a8c30e8ba0ff33194ddf446b7118c5618eabe57a2801b8040bccc9d3589de4b4ec320a8932e04b69a8a6d316bf7df251c356d7ecec115b6f97888358b5985516f800a9d2ab2cfe078f1d5514ef5f2243b5beb71455d5d9cbec03c3edd5f3d319d05e43e46b9ae75687b51179e326e4cbf9c6560328a3e24d573c1ed9c9e006a117c258889e98458f44205678a3d29ba242248817f794a9141268930b6798107bf759ce47aac9f3af05a358a31b03464e556dfc21949a0dd2939040e853f0ab9039e5fdf17fdbe4546b58df09712571901cf552d04df4d8093be27003fa00daaef74cd7fbbce66a5b626d050f999c1880a4fb42ef703474337bace2b5f1e404c04ea18fc2f71a7ca935b04f17d2d45241052a49505a33a802b44dffa5f4b12d46c8c52c358203b2d71d7868dc97c5c0123224f7f2d63e24a3c4c22a1335e5d2dbfa445835eb8c3e4422f5a2116c5d90cce21365057c1d8e58ea10ac57679308d9342b613ec1e14e00ecc8d5dc0855f377cb22687d858cef947df73d516ba702d1dc813ccd2175004238cd9615760e757213f94dff00751c98fe14ba26f3982dfa78dc2941d05e2c46546f12cef5c793031b02d4cf042752cbba659381c14e3de475ad90dcfcebb0d28b68a284455c4501851d0e486bb59addfb2a3cee89e6d611270bcd49fdd66293f16d168995d1c4bf83ab0a838bef3517122df71613e96b12210d1d2eb1a4a915b29a5203634e8ee63c16c07d9e7d1b60fc6949309c241682b03d79fe626413727ada262fefab412ee3f4162c8905e0d6018bdb7442afe4c9a63c4b2e4761138a63dcd8e164b709948a525a8b7b4b2a4a9b6692c1b4e63d7584bd0f6b5754d107df8404f3f357228e86cd77c7a96c5a117969026f5f4b91b0919c3a276759956f54fd8ee8919914cdb6ae2919b02cb6b7525d57d1c9df44afdbd8cdd1cbaeaf9349b222c4c7d2b35d869bd54c7cf6c431488eb852ee24eee7436bcedc0d58ee927dc361b2d694c86f93cd124d1af002e4e767b203c30c52af7ec682560fd9c7b1b61eaca2ec2ecd33384ff759e285474538babe673767a36fc85ce44687962439ce2af1c751254360ee422950e95a7099db855edab2a73ec4f3696f98c5bb09edfba12cb36dd5673d5f79bef34c24853f8a49f31896dc9e665308454996ebbb0a5881a4a7ee01c4d71e418664cf8c2ea5eac24195bd04933138d9201b4d4e378859cea80f7886c6cbdcc69c154e90f494cfb7f63423a5027acc7101eaa0f66cdc8ebfb70222efce8816b8d30311ec6ee9252bb175c11da1379354b37a106178839f9cd9e77c4008b811641694ebfa38b39c77927d010c4c184c5a5c3533761195ceb8f2eca99183ec4908e70670558ab4dfa2116f3883ae3c845b9c8dc4fe1d46dc18460cef89e91de09840900a98434aa31696fa3c488a76d0d3eb4bbf1c2a8bb96087d84f7beaae86af999dad6dd9a631320a87ff9048532e45018bc6caa561f0fe6070008137d2e9912ceed4da6da661dc86a09f9bce8cdd2688b892d311cfbce3cb455733c105e5f42b86f8aeb546bba3fb3883acec997c03906668d05d99eb1d00724045a690136c0f995140837bdddcdd303393055c2dd36b82b3ebd374e40c3d02005c8c077cf489534b5e5cee1b6b86d059518ef09b9bf29caa5df4150a7701951d55349cec0f6662951326c2d583074b1e96e2f86fa77cf6a2835dcc02fa648919d398e8fbb6df2c6cca1eb3e5eda0b650ac6abdd8e8bb6a03cf459e53ba38b807bfc5d8c435ba1a5d8a5b5e9e82cfe77b275e7df3e35ba545ad44839d2180136a5b3f3273c494b96aeb28b245e3154d9ec80fd0873169d6b675e354c85483b42bc13d947839e505d82d134fc0c4d3098d1c98f84acc4b4724b8a0186cb0537548fc6312ca16a21037d6139c8d051cc149641ebabd8272ccfe83b31095eb5c33ad23f4cc70daf9e2ae1b1fc6f060fd316c323f9bdcf7f1992e2615081ef832694c8961b8d31bcd4eea5312d849974e99dfc5c9a7f2a875fe8ad706fe18a938ac51b78be09896419e9f27f4264c877fc90e619633673b75cca253c268f3df8be16a70d2f6d099a4e19f3feede03892d18350e3716e2bb1eb144b9f432b27ba0c305cbfecfedaaeb2aa3df2ebfd7610239a07ec2a3c7b0578a10713842c9674ba3111cac0428206e62020ac3cd1d5df8fc873cb0ef7adcbd5551f8d93a788628f49f573e8eb78bfd4fdea2a2bca5e2976d20af7b01fde58b577db83f6fb38be3ea6408118ccdc420d76f6ceeeddde4058ad0b5f73efee007af8963129ef37ea84bb97b64d3e9bf504ceea4412dc3de6c73fd05f5bae1946ed7522e874c45ee7d306ce51e08a04e8501eff2d70132154176bed0ed9da1f1d523274918671582b5860b8c33cad6adc2d24cf4aea12ae7a2d7010d695bf7de7323dbddfa8a3bf4b5c759f07c7afe5321f31a8858b598e38c382b9ec48b4bb19a8a0bebd32b648a4bb5818505c2066140c4cc9e8818540ccab538945b8404c0360d6dab78cee409482295b801aefc4d8a431952551ddc124a658301bb61079e6e6de45ba567628705232dc84b68d2bacf44ba01f4ee2a412a05097408706636cda827e32c6ed29b6cfd97132e5ab76ef32577649b1f8a1ac2365f2d217c758de4117c4d5ef249f1b8dbf2f72f4c637ed1661cbc6862e5a8981b9c8e6ed27e86070248eee6a25d4663074ad0bda0e76a1c468910e11256200814a2321569ca2e7924325249c43250cd2782887ca20fb49bf50c62eb74a8ea48041d981788db8fcb6ac88ab11a7bb1f0aa07ebca07ac603e3bb17502d532682b92dc306cef387f2c8470bf93c14eb1d73e011a25136845cf8592d2a4623af13e004eb2a017c2e447858b519fabdad879914441a0f8bd37f339d20177e9d07354c27777e46741d8c53daa6ba0cd57f39bce8f891b598c4133f6b2b06b8999b86334152e94d5d42f97e9e09521c24e6fb74d5f89d4b85b08ad91ba91408c3443e9cb05389f1ea6ba46438b76f17233c0d0a6e5772456d5ff44f9aedd086a4bca27c6717cad86f019e5a51c587792ab9b2815546f81ee9a60e39357de46b9519d9cd4e6e7094a0255aa13d407722202e9fbab66cc3844faefef4622913528e1b204921d14b5a5fe85c14fa130763b73351fd2ca8ebd8674628992be6b03c217ab0a5c1904f66608aace9628728f24ac25ab863d869bfef3f0ffd928cefef82948a01058b3888a51108308ca6812d053b4a6345194288b8bc3b473003e75b0561bdbef461491c696d4cf92e89b5692c066204a34062e92932c8ae8ce0122f6874033a4d620ede3ed8c133ee7902b4fb00c9914ef42ceafd0dacc98ab9aaf50695d053d6b9d989d81a2aeeea49de11abbdec401ad8551a8e9d7b9c8c474eee5c261781c94aee458372a439300ccef50808a598d960ef1d3a7f449d3c46fb55f6c22aa49d9577abf0ce5def07f4f08588cb749ec24450a3453b7a73e9ec8cfd53ba8453daef4970d698dfffb49c8984e3a20a93386edeb05168030bf01c97a46cbcc4e217d164d27eee6445e01df8e2a64537e1e3396ac25a1da9634b81059c70882d50f0cd3282221022fb85b87012aed6acb5053d914c7d5bf079b67fd146c93e33f5dfb760aaefb95db728aaf220bd0f50db99cb8d6f140bfdd983cb22cbe5c7a2024c2d91088347a35aaf7345f2eba3b767d00fef22eaa103181b39ca45c101fbd2c6c9bb2a219a9ddd104fe4a46df44c96e7c2fba80294718dcfabf682c1336aac7bbc7142bc7159a6ef068abd85c032789642307c8327ecccaca471a40112029f7394c09701a68395118cc2f803cfadc4cd487d49c475f90a4dd32ac0cbecb3e820de2492df9bf789026355771cc202cac66029c9aea716b98ea492600e1893bffc5e3034e327482171f7ac36cc234c6bb4795552f18214f95c9c1d8612532573ed2c072786517cee8b3f7fe6980b30b917d32382411898f2e80caddadc72fbde6cdd3695dee7637c5f376740190b168bab6ba0121d59ceec09b47d3123be4ac773ef4926d4ca89782b00a7da6345df7553ecbcf031a72ec3f6de42111b7406036de0013f0968adb3504d2ce960f52884ee29dca7630d2fc13e3ffb95ee65b2d61c4dc9cb86eb89913aee8a3783e1f270333ad1ea07dc1b02a0c7a997c9456871320d4a985f81c22071e30049c8c52ab11c305c5bf52f97e2c7256afa0a0f7a1db4ee7d684bf82380d5b7f0dfbcb794d545e23e661c18bec6bf4b5718e89c1e67324430e9ec73fad09279d47b668ed1f21aa1428307843125390277c3b36a43b4d1b4d4d201aa7b5e0b059455e7548052243c662ab1c8fa630356462395f473717432d2c79d1891c3ff9260a2d0069976da555ec620e2900479ce55f29fac45ea06e658389dab3e685ac41b513c0c88b091f24d1956816eefc4d14daf23d5de22d5def41553ee224d29f292de124851729796f4aa37ea6fc5748ae2efa622f51ebbdefa4f49006f10248d05525beb0b3fd05e04c608782e16dbc1c2a2c2383e33c29dc214ccfc9e1bdfc153e5e1369c13c94e3d33ae7aebe02f687cf56be5c0bc81f95df89418d21ba9838b842a5a24736ae892252f0784d4e6f2b83dfe65d4575784fe15586434f8b31852308157369e812e3981776fa745140b12ae1a60155c662fa144d75392b139fa6d46ae16874bfcf0827a279e9499778bfb05c368c7b9384e9b7c7f6972860a34f98272d1804e19ca8b75a4005a0d3f2fec8be19697e7b5bffc0128006ff39a87593befae335a987034f4d55765ce8790672f8ce0fbed078de18c57712027e902e7173337c4564bdd27adbf5105aec57715faaf702cc655d079647a2731fbefa8cbcf039743344747177247afbea54a4f367c58a6e4d6fda233c663582d8e5f9a939091d6307ef889cac7175a1dfdb949a6eb87b8defad0162dbc900b0d04fdcc29f78b124c4ad5433c89a578c973a032307ee59a5a1773e24a545802401adcc64a18beb187b7267710bb2b91127bc1de5f465e370a7e66c2e411657565b7049a72bae06357cf4b880412b5f8c3c5b56ab735a1a7f3d30af730e502f6afcf2e990acff136ece9fcf40de3bbfe2e01a89e95f14f1efc40a4f04a8ef4bf127249202b5ef741183975ed2137d6a7b72863609d80f2eb6e1b6ebb9beacdbf0c1fbc7de317d729d767f1a6e7c42f01255ea39b48e94d11cd527547a9005d7c00f64b9c4daa9bdcf96a46512f6f28845359b4a6f2f47c29ec9df132a584d8d6f4540531b8ac1cf6c40db39eb715bc46328b90c1a2f63b53a7dbc8335158c525486c05778d277097863aee0e00327bf5c2b7539182beabe4afaa8ece8dbc265a5f1f15cf5d121868af3a648e6f260cf7aecd73097d21442d25cd348003ae6bf3b0507301c3dd3b7969ae053938be1db8fc80eefeaa33f6516851f9a6c6542d82375c3e11078ebd6cc85104279680c27c70a05c467649ba41f4a569d46dad683cfffcc28aaff5d0fa1dc6d29d7f9aac33b0b986d5db8b05909935a98a52db43c2b942e093d983302edf0b71742b8481719c260771ba365f563263e74231e3e28d2753a8f207818742ed5c39c5e73da2f082fcf4529cab5e4fc2a33e2390f4d2d4fac22fe80f0c2f44902b3924fefe6be2f260a96756daa5e99d6167c919052c9586fdd74d654070bf2df71114cef74fe41b092d7dd74af0a2570b1ad6459433e700c2c436b5ba8f044bf55704889aed445ae162ab7257d4b48c9ad16024ef26e4e229aa273c89a6519ce204d68eaaa826cfcdee10fcd6dc194089e99967120a7e5cbe842a990764a9a6bf0180fe479470e244fec32b096cf78fefc5cbedf4c293263d630f5995e12568bb603a4a70df9279b8ebb1c54cc414e23006c5325cd461632d25a88d62b259f7488f30670a4f64edf75f05b33bf672dee11041f08d6313c4052e9f930bb83bb211401e9a6adb019a436cdcac837ea95503246ef3fb8250218ed30e140ac9503976e0a89470a34a775401c1a3742804eaa3f882dbc6d42bfea95d0170ec799531793adba165872859b131b5fadbb03023c3d31e36603403557328453ccae9e7e27793422be638c536eeaa7f3b2fff30e82d5a7e1f8e2ce0e2735ce4dfb4335bf8deb91f526e72c4a9a4106ca8a4ff67dcc557c40fd0bd90df45da5bb767a9c366bf68b28efd570fec9e513b2098e0ce67b7f8cdb67ea6079a528c2a128f723c323d378400aa0a58000f111e5022cdcbb0f4df41b2ac6932c213288aa7cc83b0ad91e2e22780b20d472c544d638a22464c8bed5bd15cf255ad164643291e7809e71d09ea905893768806b5b847a6991b6bbfd20f37a4ea1aa9caa80379b49f8e8307fca10efcec6b940f2722b881b05770c335ff3d5cc06861ae9f93d67f7e4ee54cde185d2ecc9bcdc0b38ffcdfd1fae94d76987abd7f3929c7d416d5a32f60bc15f46f2dd5346f3844e66fc06594c55bf5a22f38c9b14e6c6a73fb9362713397f31b2eefe9d29d730cc5310004a9daa7385c69ada6796b4afe986228ab36be87cdfc0bb6f28cadde4420d6e00f54216bc6098572fec0ec8e8f2ffb05e8d39be7f9f90af0be562bd755f275123149b5d4d64c7dd5ebe0d6f16faf0bbcae9e5aa158797eb21168330cedba0d978a8152e9840eb4fa58855973265f94f35e37fb4203ea73557d5f8f5c17b2497aaa26d3794ec9fd03cb0a183b1ddb6519b673ff795ab283e09da99c7c33ca792a40ca13f3f82c28415adc9a1df5aba35067ea1147250e9d5642a7868f7b3b66e629016f4e3b6a95e91ce67e46d900dfe032f1d6c18dc55ed8258b7397d1b05a3b40b13550c328e4223c5a8da0669bb42b85319c08f45eda1c8cd449afa4a24b72381f13d1b9801e7521ecfeaafc66513b0d1d7b407447deaab0714a9cc7eeb81e801722b1de891d0cb00d2718d89a27989105fe19938e88f55b8f8d5d12fa1524dfb380aad489dbad325a7e7c0af5c79e424edc90f8263e1e821dbd208544e4a6c5ce65ac66ab457e42e8d74598bf208780b0043b540d0c6aacd0a39e532d95b7aaf35967cb372dad63b499b8163a518b6ac97e5051f7a4f2307c4d4dc0588358c90cfbe819754dcee96866a6455180bb1f81c0c1b4bf46977f182ec7cc66a6dc58f6d65d4ca4f043c47335ebe9d91359656d3a697d4350cbdc0e65533b901be87b6d18c67b3c99f192e574cb3e44d6a9fcfca7fdca1151bafb68bd86bb5b32ce9f201c2474eeba221fea82ed544dc628dc4db2df93057aaa863cd32b9f15b75aeb246a560d199eca647e69cbe73b4159832c33c210a5c2c776b96a4290b0755ac4079bbb4e14aceb5df64c0cd6078cdff2c0b9e50966ac032bf31144a99e574be358658c73eb520656d9efac08081f5b535114819597659432c87c761762e44c29e53f58cdc0d5cd75b99a2dcefe1ebeea660c43ce24cde9128b60970ad2e1fe9897c72c9765b3755d18e3923dd36a3efb2e21e95b67463cc68f99169b0428b4b944c8d400c209cba66b998ecac651488f1f2ecb780800244463ac719f60f9ee22617c11bb847797d48b1845dd285f8bbb965a1f8d48221bc6abbca41f1358d4f10f10df5414428820b48baf75df01d3d2afc6f7e0297e9aa4d403e06c04bc9d013f89cc233a02b9b5d22973032ee0a39ed3f9cdfae3573d3ac87da8c49e30773493e00692aec8ad0dcbd87c121a9c14a8d2792950d1df750e2b867126d67f98da3a616eec81234496ea2f8f367f783d766ccce7e783af9c1ebe7ffd3a3111f1932bab0b177d1649ad4457ccde092afad9cca0711454b5ecef7068ce226c9a520ca41369abc0eb5849eba0f4ac41ed48f6726adb8a1a2f22a458c2589c02f49620e5316efb43fe934ff6cdcbedae0ea0d1ec3b34eb88c9705f24f2d391649b506b74d68d210d61922b2a7fc0a2f219ac10e708913fa8e69ce37df99992052eb61d1ece17f32b74eeb2843ee910b7e6020494c4507e5ff12875494f9aaf4e05bae115000cedd8d2d3f93068afffa2d50810ae455033ca3f138b2bc312c63baeaafb4ca4bccca5e4165c8c83af971db673a41686a1b467fe0c392351ffb0467bd338d0e46a16d461aad178c49cd10cdd0435b4e27945a39e0e8bbd81fe6df23166af606c3fbb35c6c18998a8b815d2ecc7a54b20dd0b82080973609f70a4e8c617c020acd6cf65299be45e4026e2d228a11ff48f03658b641b202b8e52400a514e3e65ee162db4f9fa7a18e35a1f5b129f8706ae4df36696635b3b9c8979dce32be6f7fa5ac65547e82a22ba37c1d0e4fd8e5e637594a476711530805cb815ffd9fc1938bb0debb789be8fe0ae11a7a2c4ddde2905a12207af901243b6b303c9e6d0ca907e0b79e0389843899c66a90276f217097d9f07d2071039e2a3a1ff41b4b4df66fd0a1938ec3845b7dc2f7f50aa8da45c955e716baf9c6f58057aa5c6600122069a59f3311ad24e4d239a4ae5043213728cc51e32a97350100d74709d31eac0a1ecdfa88a6d82b61b1b95e940732add74adfe9649e15cc74230a8087fa11456dc33acfdb13d82d4b6caf63024fc8c7f5c62150eeba1ea69c8a98cefc82f3f7358e7efa3029e25408c336c581e75c97aa5f4c12d3b3062e6db81da74c15839614eb8039204383b46514d2010a02a3c8eba510b5b35864125e80709711e46082190549e70eba5bfa5fe02f19db513e37870a05ef14d7c65e8a32044299a531e81875bec42d75953405a21978ddb6a193e284121f5f2648d0f525c7a8ca3625e7d3c0ba8cacfceb66009c846f7676ca71f056391bb84715015ba100ba235ad10284431a66f9494f841e020e3f7cb9115c5380067e5c163e3a969a891d47bac102ab3c00d490e85f2bac049b5586f092be9966441159a21ebc0f16c79b22bf352c7c26d6adc0e996655f4608f85eaf3c866b760689a5013d280a196d6b023c708a8e76cd6bbe9479be521d2d055ae3515fab406b3f233c8c5d01174c5d50223cbaf5f3e794ae879404b0f3391ee1c249739f88a9125641b566a017219619c425331a23f516a8974728015c8ddc52b7bec501aa33182c07867e925cbbb017fe8fc3f1efe00d8829f50a320c561c5453518302428687bb6a2cf9e05e54e36284ed3ce09d07ee2092f79810e4ea11208e9ddf0832a8f628c3974bf2a80ae1d212e3bece1847fdf008f857a28265649600170e03c143fb17c68428f49e6484d7e219fe2b681b1adf08c474ee995995f395e28c13875fa694a98322b7cab0480b590e50a108a14d8af5a012e3c63e23592d56006fba75f1ed5fc81452bdaf19792976a2f7308f4bc17335d2a4df5fc9664b6606506e103151b99c75dc55b07fb23f79bf13a58eeae143184125e447b8ac6561a0f86732cc0daf703529ca269a5a4bf513e38e4b1715de08bd3237d54fef4411e3a6daeaace7055aab3298ae6a5351d5747865eb22e7cc2b22f6c7ec887d4ead995b35a676f4cbcb0937a19253c99b199d11066d5a29efd8aca7556028c0e6ad407adee589d6bc20161efa03988169799393cd51114d673bf1ab593b92df6f6dc59dc0eed83daca3d06de7051bd43742a16dd52b370a8b195301ceeb564ac511b3fdef93888e178280e5cd940815b8e1a7df52ec1bbe0058909e47122961b463ed6e73ee8359d06c420fe03fcea03ada3c04cc6ea205d939979d9a640e64695cd27ea10a8a60397d7119b6b1b30cc9d91cc9be78475ec1a537dd1d998977c05b65e8538a3b271c309d7bdd4c32229c5262dd700691eb4507ccb6be94b29d33310bc1bc2be2c81924f8e09a2cea055aaf11319ab99e9e4ff9d880bd7e611ecf56a4aec9ab29d69e50c88c576948c4290d52dcc0775e09927ec9fa414ab348542501726c47064c3ca3b34f8f0ef8bb6f6801e6293850b158a8907a2199b3c9fb341773c424cdded32b756aece6661b192dbf665e8597c33ccf8b98da5e731418f1a29e5b20cea388d4e1a61a3b3f66ee64663192d2f192590624307ad8dfd325133861310f6657308b02fde5afadb67bb98e5228a9732889e3c70c700033f725fe765fda503b8c3edb9b810212080bcf76e3a8b7d6fc137b1d9f2edae758eeb5375224fbff93efdf2b908704cfc52573c5494dd818c899ba8de144fabf87b2efe52643035fc9fad9c5743c457efad1cb2caceb029a6e89bf2117b25120203b21def16d510886f46cbbd536f05bef05cb7ef5df83b861cdf37526ec88aa4c06d3dbc36875b39481776dfc1af464ddf355c29e53d1ed76035038aed5f78374be7aa36a931376ec179655244f398c9efb35c4cab56190bcd97c0caf5a3cffd8526921c325da143c83b28e0843b03c81b0add5be411a07e123cf3a303b5cd5b06f745d31873dafc1002c0806f91b210e687a0b58ba13fc98da9a02831edb881af0adddac84eb2739f62c2341cd5376d865ee980a6b8390ad248008215f873635f5aad65ac4909ad482171f9f26c55b82874848f9ac6ac8d1dec49f6c4bae677b0a54f43ba8dbc31a168ad7415df2ac7079be1ddc23139eb852eb203c1fcdc883a8a3c6320ef5dd80d79824fbd0566e0b347122a36daddd05a0440d46cac12d2d6a7a664a40739f0de5df375882ad5b3b2c6d1372eae46209268163402bd77df4725ede28d1bc89d37db4c4485a086e0fa252f93e8f2203150c080ee0ee2c7ceba8a12c15aeebccc33572482d844d2bc6c408975cf3b4ddff7fd07f1a647845cc26d5a2edc465846ccfd665e62a59477f126b3a7d9b9e5d10584f44c4ea6fd0e7150fead5208a90be8554beb8c6c28c14efc08dd87debf043a822055bc962dc9fc753a4553c62f142d811fee9c0c105b43ee60c5ef495c880fb4125f456c896c91633720e18ed7159f0e67c702bbcc8b01984d1a0b7c245953af82093be2a3b48002f8c49b26b37504d9aa780c0cf392d2b1a4ccb7fc96c42cd390d23e29d6187ad03da5874ec70b57191627d07f500788daf429208f4d60c817b8d9a9490b4a93b4ac5f1a90b2dee478d092c9a61226c4fa84bd976ac7455fbb5fbbfdb308475b29494ce7aec458eb22fd84212a7a8ba43580d64278bffd05bc1ce1d0c435710c72c65eff968ba446b8695a7bba16603897b4e5f418f42150e6b2c29a2c15abef3e3cc86b1d5a9df043a97575227bfe67a83348475c11984e09b80f64180241320096cfc6c07749e2b1df8269ef57a2eab36b31d0692b46723ed4a6889198185754332ddb0cef8d23b1612e3b90a62b902b0e9739042fa4205cad924e67dfb3468b9feed7c8ffc14c3e0edc88eaddc353a3f76a33655e04c079b5c2e801c092818aedbe910055b62aa5be14c8d85a07e392a929e767601f87d13fb29dde58bae2acbf862bce29043add479fada22e43c78305065e2cc563c947478225a003f27827a9ffd2decef6e94e719d5a9ce303d4e643203334a50ca8b4868606da2e7a0d7e4a30e5922300eb9e08460b633b36237c817b1419973cb3e6c616d71d0be8b95eccbf7a580a6940fda817268ef9db5da70f739791967ed172f35be4bd1516189f40acdfa22d3b4b203161ef6d8486c5bdda637bab67889e2c24a8f414d21f70df1e3b3218b44c8699943a8754b2dee5c5a2f3d76046b58e70da8bf932810948fb650dd9cdf5d8f9947e0fb2ea2ea8cf6b9b81b1c60199fd0cfc0f3ab73e2dfd9d860b4a8bae844bd629b824fd876b38143a26bcdbd3ba669ebcd515faad665653493da5046568d7744c42316e3328a5230b61f37b002bc1dc335f82806446f4ad2d344e3130cd395024b2b6a3c656fcecd89f03ffb01b3ed5772a6e53c15f7e06c0c37305a43c8e9a4f7043d9c581156c63905ffc283c40ef068289d1271f58d8a10f234680d439e3feba3c62dea692f325a0cba55145931f7b54eae9c1f4dec0f31fbcd611eac0955f3a4ea24125b121165d5d4c07b0c31d3df228967aeab98af2888391a88a6650e655a26086bde9e131842465d842c5a60e18ab727ffb05c77ae66375277e57c6f11e904d95005fa2c65a2339872a3b71c7796f91d6421bc7c6fce3aede79bf992ca6b62f2fe48a28b4251671fa43e4025958d0af46ee16fdbe1124915c20155dade7c5ae4b1a1653427199959827540c8ebf4db7e168ea36bc3549da119233a0985ce1a83b277dc18f00b2858b000d641b1be45e2cdaa6cc117f377a9e34e89bd39b10eead1244e8b7dede0224ffe3fd606b97ccab952c2ec609dd56b5d2c87e0e1f4463598f6792d6180b97d7ddee6f435d9252223d965684028e56c1b59f85c11b2d9fb4ac4aa94cc89a837dd12ac4a8042a52823ed2292941a97e57d94380f3eef3265e9ba4c6daf799d352a57dbfbaf07ace7322880530b0a79a813e456b0ad45500aba79556bec40d686b3317911a6ee1e0a2dbb888dd7fc80ef27e3a8124a9fc8e10052e0f2610c78c6eadccaf0878e9c04a7d1250b2a8d6f188996a8afc367f83b7c85eea79f3d118193c7ca8c5f934f2393293c20478ea0add79bb0c532f058178b75019f430206aad6b7b20484e9c024318770896ef95d3c4065507e9c04df8b705a5626553cf3962d42ddfa79465bc9fece3c9b6d71f7008c05c2057ae8e7c9a3f0fafe98b2f2ac43d91db2c8fbc27d4f3cb96f6c5de5bba7809fb4c743ec1f96dec72f92aa2a848950db3b6dfbfdf0451443caab9f1dc7e06363dfccee3f326e11b9aa6cc6ab54ea9b66d465b281812402cc144403134cf29d633cf9546eb6409a5bacdd265fd0ff5660a2f338f9d5bc744ad6d658c2f299bb3c7c193c328336186c6ae8e288959fea5fba057b734cfb90c7e2706772966f33585fe94939a3152584c4a4b1a42b958919844d16fb426c517aac399b0add412b284b2a593f788bc0d47d91de2b70aa33e1961b1fe072180d4dab7aeac40998b94195054ed4f89f5b8593625ee2013f2929bcc93a40d920bef6deaad20aef3385a8bdcbc7371ef2b18acb15e082543f908277de31267300065a5edca12f2b58b934ef173817b2241a280b49969fb6f2172d617dc0c7fc2778be061963618298cebb2ad88b02328d08fd6f75bb9a1e14b6bd90d69e4ed0384ace3860f39756a279b4b2b1fc4b2377bbfc48f4a4b1e779d6688260ae4fbc9f3eddb63d7866eef8d5a2855f75cd308e1ac27d6cf19cd452dda5b292ffba75796c381a4355bccee86ac98fd430492273e625c4f16933db912ab4bba47dff57091b973d91f4dc343fc8f1b78384680c030716c3fceea5b4a1f90a956d0cefe1835f32ff250e732590d98cf7e74dc61488d613f6569141f0f7dc89d32ccd76d235a04ce3a4dbca7ee02d5b5dcf6d120fc5cd428a278bf7c27479caf94cbb0e32e710fcc3d5959157c9e5e283773dd76aaf0a6d1b612942a6b4aec04187fd6a2bf8352d9e4f56e5c43e9be84e68daf39c2285b451def3405bc5b00f4ce13da6fb67f3ea68f10c4b155c1ce2ee4a88ace075aed8c984ef717a86070a23a7af26eebb8eae8d1a870da02318635ddeda1bd93c58d6e992cd8a197ca2c87e89b32e732271640ffdc1334572b80321d771eb11540cbcec678918b118207836696f4ef7dae0e67f8100eb8bb79716c8a5260b408ce88f950df1aea000b757b1570f3cc3685ac8a03f0055630d47bd3a8e9a27fbe89cd19f4bbed0975691cc5b2c523c016bb88e2cc72481b9ad07d005da50a5cc17a3933423fed48c8643d8de74f8120f611efd3c9d22f9c865a7cb692ed2943f29fbee544c9907f5e662de3dd08aa707ca3475cbedd7c3adffad25749ec1aa6e6e1ab27eea1a07b31f5e2d823015fa5c4d4bfc9b3ce60faa70a15e17208aefa6fb800dc6c79f058cc31772f4513893e82b126f4ce129f911ae19e121fdc48991f12fa483e70173879ae448baa02e73367b9c1eafe4cc38cb212c13fe91f756f56438aff2a5d3813214af9c5a5acd6c6f51d12453b26cd6c2ff01167464f6d6c5de9e125020f87a768f7d81b6850e2201ff1f78d891e3fb45c39c77fc7315454cd06861016b8d1405b612e393b7f587182a15a221ae9c17e2fbde401a3fd15c26469e5ef211615c677cf6c7a05fb6601af8fc68a47dd1dfe608a3bd00caa4997997d83fdf1a502e32ced71ffdd6681dd4b15c645aa4dc833cd0f79770aa4ac144bfa23661c5054dbdbb6ecf2157679da0c047e8560c9e96012e55550616a240619f97fd0a86aaf8d11c7de03ca3e5d2efbfd9144e2cdea1cabe4112f3ab5bafc0031693a571f3468aacc0cecbc5b926f3a77d3cd0941118f50f167e7d8f60cdd3e4c482274419691c8c1b1987081dfe02422fa4452a075c1bab7b2dc80141c5d1800b1dd4a9b5afb2dd7318d37ca162a25fa72c4f3d61d2efbeb431072c61b5efa7cb6850dd2e538dbc80a3c9e30ee7535b45dcd786a199f1d587c59f99e7dffcf21d2f4758f90a8167fecab2b59216cf2dcea6ce082d0f245ae26dc76f730b76e6c6bb6de38c35ce683efacb13dd55e2a3ac1fa84abb993dd8f3e86cea7fc559c1c282c968b5c7ff883f5816412ef36e331d03a9b1de23093025f8db81f922843300c717e7e6aabeb5ce30545dba59709b2c7d96662eaf359f3ba13f96381e72bc144fae38c06fbbb0154f36e1d804639f7dfd8ff0cff61f4f60b694a56b29cc907e88d8c00ae6ffca7b08f4916ebf008aa7e2fb5e8ac8aa6e0b79d477a851cd394cb6f549aac2c7b39a0b5b6be46fca819b9bebbada382ccd2b33488a9d6ff23c497b70538b49b02c396cfa44e17afc6487f1efee02057b1f6ad10631e89884e93763f3c84001891e522f8c7d9b0dc2c2bcf7dd6bc3fd05e2d8eed1c2ada6c5796da48a38def25ec96a49da50b903cbd1ece43f76b65bd70d42caf4d2a6e8273507fbee33ebcadb874d1cd64eaf9eb7e605c4d5b91eb6bdd7eea956be143f304248dfad597c12c259159a4b0cfd6863a206eda5c107dfad8608fc45cf3f61606e5182ae753822942eb9a98330dbfd8a0f9ba7f9db3d8c9c229e4add97cfc257128181732dffa5c6c78db6fffad2d058f6209a96b8bd69e61dfcfc7f53118375eeb9f9bd206a071c8d44290c19a0759a4d10b6e08a5bf597398c34b4e5cd6e73e730d44de8003a47940d8d05eab85db69948f14a889f1f49e35d51b319991961e328af1e18e942c0ac3f900bd736a94aea17504a85d2d03afde1ede3908994644e1277cce83ccdc595c1a1e2808c7f0b01009ea1b0244711a7c72a07bddf4f1c3dc22a28e879099a749dceab7acd1db6b676185c2059e0dc08704378874849336cc4c971a180001fcd1578367e932b102693c2bffece4df0be8e5832efcda832c4d9a33b31718a5ff5213300e27dae1095b2044208d8ce859d196102841e433a536354d1ae0310d42eda58e9142df1eee411ce5366f6f8a2c8e06265af8df8065e012e05b4e5ac649eeaa246ec579c6bb36c24d600d3182238b47632ba49e264e43948a849aecbdd6d499cd856201a9cbb264136ab03f9ac892851d583cda6e3194c8eba893e77f8c5459731ccb9f76342bf376a9bc590129688de157b2454d498ba62822128388dba7af0d2d2760e6bef1c07aacab830d7ea594c135e56ab939091c6b2220840f02b0f110eeb11e84a8c181f63d3b95258ca064e2b2d72b15eb5b09cb0cc1d003d25832c3c42fe4ab8584ce24848d0155aa16538dbb2cd294d5e426c0625655497b06a7d9abd106c6a1cf01f87d20be67045af148f9243b7916c11325325512970e7631a01577b4fa1ea4f81fcaf54b10cd20c24154cbfdc59d808defa5de967a3eb8860735b273268be8a234085803db2c00a99d0a5c8eee25e61e384a1eee286365bc610ef460e84371984c5f3f38442c567c208668c1191b51e0b153a5e023a92a2b5b2965114c9b369904284be66bebc0d48ec3fecf3cb63e3b59b7d8787bd34bd432cb17c5240c1af29f9893b29aa6eabc52e1751ed041776916ed5d005d0d769b2dafb2125f3dacb9e59e25e5acc5272b436a46bc482a40c81f80344029b746e591e2549723716e96f87ee7a6ac9958884e0aa0abd8b9a40a53484b2518ae6066728c16d87356d6b3f83d5127a688be801169dbea85ab9f86f612c0e8fbc744390357ebd4d25c28f9d65b3090b8b1d125b4a5f325ea1c03f1aaeb2ec1e0ca36e8b76f1980d1eb10857fd557b6ad7417bc7e7584624682ea1b82b5175387b6fe333ea81fa0dd58f573f6a352b66e537d8972317fc971b658e609b195094c6bfb0d75b45a9b59ac89f122a7c8722f8a14882159e2d2dd6239f01aebdfb1a8ca3c90258aecdc40705bb88614622fdc15cc918798a1beb3e250234cc93c9b5d6696698df9475bab19e8cde596023927f748084dc8bd6e99760f1ba21e330484a9d0ca7e16f01600a956bfa14e2031695552335c940bdc0f082b18c1361904e554fc02b7db7099258b3d9068517c11b7a35bf054d450a38207e415556e73b1de589aecafd6a42f21588cf0fb7a1d869442a85e6d03a3baa9acb8dadb14006dc014f6dc1cf56a6839abcea3d8009566b27ab5fc3c80fcf42e51adb08c5fbea870b5b29c3e57af07cce911ae1c90ef6580e8df94ce0ae83fff5a6a981966da3cac3f973ac6e3a54348b2abd4d1c80e07125a9b22eb29e1e1e092477afbe780d9bc4aad8fe2d68dd853df15242af9b4a76f471b7d6da7a3a4a28dcd1de08909954eecc772772093522e31a6ca745fa398320c3489e6209ce53186cfe5fe4d67faf7f9d2be7fa333b910fb3ad15663537cca20051698bcb32654a9370a2c766b4e91ea7446d18234905c085ef7008e678ec9d68a62f2897a558b31eef28a3f39a2a4273d6b9209f6f37099389e585e6232b9fc463e02c2344afb243d79b1896bb6894314904fa9e69b7002b365f67545f629fe0e680382ce6ac795a4919ccb00d7c2cddb9aa47d431eeec84bb3cb5fb44def90a7dcef58a973d415b28ac1f8d968afee68d9d296f97710a772abb201e11a4d3da8b15dd5961e3d28d17cedcae0739e841eb90547d1e642d3cc0b9f61221ba114d9aaab60ff237e8e74843836e5614870a57ed96300a67eaee6717fce92468fb15a4b74337a68173211041ddb80d8a3186d92ef9dcbc6411110ff396454a7efa8c70fe20c1302c4a1fd29a66d02240fc6aaec7baaa3ad86ef4998079924aea7747aca4ea6098001cc8469c494f4ed2762e11b5a1d86610591bef158b29188ca4ba9ed8d81cd03a42faa40644464cd2ca7e8d2cda6cee7c33f24e10df9819deadcca14d4826946cf0efca58d6ebab789afe512a03e23a49769c6595878dc482ec5c7c5370be00bdeb695fd51a598982d8b1bb7000c26155b51df0ab80cc77de3660be454117b2a52ef80b9318065e8655aab140a3ad65f4fb034620a9f6ae7c3e4e66bdd9017c788328ca37d4fddbfea2c2f8a6ec45feb2bd0a6e7d59d284576359f4d3047d32eb082b575d9f007318d8dc7bc46a33d91376a912e0523966a2a7d6d715138438c635a8e0e46287fb78cbe154ffcc9d22f45d9215ca2046414b98b9c3023e837c87f08f8a64802a11b9edad24e571c27b73e1cac9cb727bba68e44461263549a3a820deb64e12fd35f5f0c48a7fce69b9cb32742cdb7167efb4d5752aad10442c444532c7cc894526450e114b751ad4fd684e7a9993680c2c0e794e33ffb656c7432b9796428248ab6c637338e1e4d4e5a02c9daa7f303670775496ea0c07192a25f9b484b9e60a369d91931ad91466fc0c9fe974c2562b4b34d0f8e466d276f768fd44752074c4646337582f34bea81138b30e3d302e583c9b1c08b40108ba4e4902800e53287d1112b1d20b6bde3f138cf6102cc6b5b653666f31918a0f6072d3c2b60dc3ee06a70a44f9d8b283ccb96fe03bde6578f9674b1bc9c37222a8d3c4a3575a6c395a9a40262f0ca26742a436d2f72c02f534065f965f5e61d12cf651772bb861e7f89e995661c5f17db517534daf4cafa81421fa0f1277521299216a3283c89d4137f6351ad9f96fdbda547146b39615adc945045dc24d7abdc44fd8f2f4343006cb4708334eb51254702780198814ed0889226556607893922733767c7b763b1a43c9a52f2478d04facfb02569f115ee2ae4b978316c1272d9fa461fd72958d233168600f73621a5ce8b11ccd6977112845d95631b995030cc0421c790d4e8b546095cd55b3567bef81b870dddcb90d87d4a9e3696c878689db63635b18d74068a7452a5db462c8c721151f272c1c90ff1a4c3f04b73b9421815c74375d2c0baca51785bfa2b1fec977944f1dbfc1d67d734206ac42b02f16697a05a9c070ae441a10d354a110906933107a33179970a2f81633be1f4224e7f8c030ce4903f39d9cb6992e7577832f9f853e94cc8cc9216aeff130dfdb36b462e6e08608cdfc83b2e5d0fc072410709dba730d51bf5f51ebdb6436e1ed59c15c1cb75924056ff34ee2cc8a5fa7c6143b17aa4381bd09bb831c3c91f9465b48383f2fadce7c0838dd1b9c1d99772e6cf2ff4906e33fd12167033ca4b4b6c1a437cba71f663892d4888a6d252450ea3447b41dc10270a02896f2d94eea68a2620b4665f7211c131aaec49a777cb7ee5d7cc102433077c97cc2c633de847d171fa5d51cbc967b8065e14cdd242869490ce708d3c287a35f5199acd4b8a8ec91745c3dd178a0a7f2ec333fa42d1b9fbe60a5efbe0a2f01a1b29b3a7322ca30f149debcf1505db978a7e6028c356af51a6f60e8009ade4ab5565f6d9ab7c52bb84a23e5dcb9becac8090e0436ffecc4a916654e9d64682bb694c4ebd181535fdac96b271c7846ef6465f32f82cdf8d821d5def2fb5a7cf5ef83c1a940f4df0c0c65ac49b90191f39de13bd8e3e406fa337749437756b5e62fa98674ac9198effd54153fa6bafb42550fef605ce3040b78ad18beba4eb9399687f0af5755ce81dfb355fd05b67e06a5f880cfb88900297c367a93b524d8d8b0ee8c7988e0084d35722c242c7042fd5886a1d81b3519bb49b1d5d0a0b73f5001e7d5a912b1b922ccc63dad8987019f68ab58fc90db48abfa62644fc84025e8ac966509a84271af2bb4161d6dda6f9039c041bbe902ee437ce54d4036094fa946418a3414bcca4e8bb7a3a47ea785e3d96f361c2147529a7bbe0c3a7719706b3f4f0519e959aa4113028fd2f686bbf07e2f442db85b0001072973d6191da197010476edd973aaf4ed5c042b5f790090787a2aae7a5745c424190404dd59abb717ab77a96e4b711c194f12aab6f068c05a2c0c4c6ea759a4147f743bd18316af7224970efb540521568d1f392ba8934ff1f15343017a64ced05d847e0228bb3f38f74b237c6043bccbf6bdfaf6b94aee9ff220bad548e22ad1944bf3673c0770da2c76be9d1b17d8501617fce2e17944da10fcbb9a1310af3f6d944477c0313ba19b66c1f8a0c9ecb44d0bdbaeb9d04532f1c1daba7f1dbbd9d61bacce7dc269f106276472d8f2099f552eea40406ae30107c892a16f09272618ad7373ff414bf8134e05e6f2753ef94a3a66af3ae6aec7296d0a2455fcad6aadde2885ed05bf16ea317c006d27f9130b140d3cd3840e220d83cb18100e13f4865966b2d1fc176b3dff8ea1ae21d19dbd257425c8e236e7c0b1c153d2b5e6b2d3e92915ecfe619dcf873c840a4db7f10e9df1b263a96f1f4957c991c8be9ac4363fba6c039bf97f9913f6c15f3597ec6166ea97d3fd0c38df737df2d91035e8b6e76e66f31bb2c9099d484ffb4c0f3450b6fb1e319b19b15ee6e64a64cec724358e00665c2c465eecdc17aa733ef0b3a2c48f001ce9f22c63cea55a48030c968a86ddad167674215f4c8bc43ef083781dcaa1a2d6e807ac9702b76a04cf4d715215f4c45df906f17717334726f8f3f6cec3843ba15eb08be75d2d2e29298292caf236640be8e6fd01f49fed2058648d2cacc6c083d2992e796a5497d5d1334a1cd9c19d285826cda734091708b7aabc43e14126d697047534a43a3bfef9ed1b0b8a698fddcb9f5fb3ece292cb4fe675ea8bf64b8cc40e5ef732133bb1e2cbc4181ccadf5f3816b8830de276d90fd532ed671f9d6e739a1b4c57d15cd82ed0d9b3aef7738ecfd468ce85e3787da7e926d1fca5bdf2e0dc58bf7d7f2c23330c476c131e5587f8cd4b0d28fba4c7452714bbddd0a04c21925fa3e94c6951d732d86dd7697453c0705af50f21a95481f9bc9af99e74d135d1b243bd8b4c18960076d69af3e582faee091d755e50e9f111f44cf95d7cfc5748f8606ee03154f065f0b7e67c7ea7cf918a08b6479732ad5ea72de2e982f3ed073f46078deae8ef31b5655a70171dd55f600d277b2e1e474eaea34245b82414d050088abc09c6913a7edffd797ed8a2946d82788088175e9fb1b4ce3807fd7916ce06552895b156c8d9df64475a524681f073a1c735a38ba111639af99d325435202ad8d16464233a224a81de51db791f9d64b3e42fc69d37cb0f5efcd2b46813da628d5d16c26eed4441132b71392d43163bda691ba821552b885d5a86ac847557949ea5fd3281e513ad4c9950d36f96737fa35c98d110db6866cf85272f7e1ada75b2bf97bcc4c7c4d4b5f432fb217e469a3f0eb03f47fa58e6295de2bce73635cb11adc4ef3be9b40e081c87d40f6d996c99adabfe58bc00b76adc15c35e96a571f0fde85103b1b58504fcfa5a0804ca5e371d43e83c5628929513b2f58f9ab0a131fc79395414857bd59027b94274a865a094e4ca32852fecbfdfaccb1eeac976e850383d023d879c88b398ee18e2697b20f9d503a3263028fbea6fef18e0f32d0d511435411abeae94a506c7ad2da7a3f13c3fba024de214ffa738592a4abb82ccc0dc36806a91fb9ebae89097f5771c643dad4ffea5c744e43b4e2735eb79ef96d965f5e1a1d8b6b8bcc79178e078ebe424c8e61017cc9f0f480791ecdd50fc29e7ed694123ebdae0bc92c62c704d30187b7547af6c6779b147bf3d756b75cbce4a67bfb233037df5d123078df8365cd88f560d415eab632de844b65b85c507bfc263268b645f8c4ef5205cdbe822ffca535d06b6bf896dfa40caa59e06b7e291528b7165ff94d6b4059abf18d1f950834752d3e8fffcd2a83d6bfbe4e373fff24de2d99eac8905a2ebfca0250ea8ff8df3f6818a794bb32eb51312896bf31f6cb027c2dd0635d5e014f09893eedfe0a7cca48f468b757c0534ca24fbba7029fb2123d5bc0ca08b070828ddcab78853b264afca52f04d58c3250dcac9b93a00afadf98c02175419c08366a53100fc7f05635f9a1f57acf1a861f52946ac853ffbecf6772b18319c1af8d16dc631e7d1470fd973ec9ba9f563137d5f8013d83f976a64193feaeb778fd563072f300e8fe6ac3af46c9bbd083fe34ac88a900fed93327b10e082e4b8db5dcd1803e0452f9555762de5402e85e3ed38736301858600f44e23aedb2834c6007b23d8fd62e94e73fd989280b40d3430867e8ed25e011e2d66aef8c29e6651b0a0e2871c0f50f1867f139e325d7f8ab2677dad43ddd1bb65821499e01627c28d53505a4ea5515f9a611d1490d6933f2001982c417bb58b5ada4e2d916aa16f8830d95b858528f39c12d76883fc9298da61a388c01580e6785886affd20b0d7fa88fe5c3531039e7deecac6db090c208b63ca9e3adb937aa45b2118446956ae141a8d1e28465411670e1fa7d442250862992d1d244c8358491c6f80516f7f8c61417e4e971f50b069c975c3c31556724899befa3c0ddecf9ceee3970683a5b2288d7b8cb113d2c7853153fc631ab0216ac14900778398dd45549461909924cb3c60eee9f151c360cfb0edf14f0ad3781c4f651aa173273dd869bfd4f41cfe4da02b66e2c25707a353a23901f5989822c01cd5de92bdb44698a84ce5203f295abbf032f1171d894188eb94e145a41176edf7aa66a331ea8f62983af71214fbc2286186fccdd1b270a8a2155601c11e31e45a9c6bbf52fde3a8c00d7d6aac367ab30268bdeb8b23608c5fc50646d5c4c21727b595638c2229fb07ba834a6c6ed79d1c8c3d618b34c252bb5dbc06346c44a7c1c72895aeb726bd024aa1a4de405ae5855c9310ab18bd2aa696f6180fd07da5bba0dc1286480917cf6dfb42da5868873a536c785d37800677dc9136bab022b295dfe485b5e226842273194ed3bebd5e5c78279aa761eeb0f38b55378cfe75b0f57fdb49deb7e23554be17a64fba87970c13b2610c72d65e14ae9d95ec23b99c57be100cec4c3b8a2286096c6ecbfa5f5fcc66fc4acadaebd6c9335428e0f957c8cec508a97f84241b700e0b8384fae995de0664045dfb4f47bb3f5e15b55d6d395982ebeb6ecd4f9ba5f79f74ee983f625756c0a0275f0ad897bb2130b0e2e82c2cf5e4f9c500ca0153049dc7700235684e6c66ff7f9951ae30cda9547f3a20685c4f9f9819cf6c974390a29bca20dc2d1aa8ab8f7271af57ba63e120bc5e4371de520d534dcbdcd9e3c4572fbc8a493586607b430b5816e52de480cd687eb6c231e7324fec496b17c8e6ba4d105279ef70ae3234fe095c4ccd1b3866b11e9ea895621fec67a2c932cb3598e6b1d1d3c5aa34d5e9d8488c84649e6ec1bd8f3f8562bb442958c87ea24005c70e9b5fc4bb2ef2a2bbc154147678aabd14406c8ad8074fc098c93a16f2df3340142de2c3aa973b1a3c062523eca51e95195e3c00ff7c354678929594869e17b8fb3fdd7553d02a1f25b832fff7a915c5aa121289205b1b2b15c422edeb24b2826f65df4d81a3232bf96c5c197299c6fc305dd9a4c05be4fd53ace9774439e0602830364c56489e2b4a5f8273157759969cd938d0ee0c55c4a8892eee2c623fd0e6b37969882e704d402950a1e3b0ac6795454caf80fb74aacdb017082e9b4d05052a44c5e971eabaa39cfc0d074deb662e5066bd70b8cf27332d2998c46f95b105cf26cc1396bf59b78a3701f122d33a30a40e62af7bc51437ff00d75e0481b01b85528bdf49210522f3b01a24a47535e0786f686fe0cef0a0c10dba804d897f4939f4a1c2888cfd5f99338ab1f0926d2bc474aebc12da6f84c49edef6138d713002f6161657de04429fc8e921292f65f1d5f3400173d73d611e9c1ac6fcfd270b7627f90000f9fb61596c985e64aa6a0183eeb7c0f7344a04c001110b22e022fabc5aba3edfcaae7aef2119d2f0131b0fd70807de8711989481ab482c16e289cd4c2354777000f808da821061021506b4e92b1515af0154af698b106b7f7d02c5adc2ec557791e8988c15fc4be0a51ad9de5b2c38730c09a2dcac3862c446bea45b503210a54d4e9817a2604faafe92cff0a33114020e7bcc8c6aa748c7b8e9950f21cd9859461a7581cf07078833d9e18ada3661df4654aa9eefd59ef99cd8564136f6f1ff48419a4c2b9ad313104653b3d4832dd0f94fe1211314865ba9694e12c0cc94b1d2205351c3e79da61f15b498e44c2041c470fc55d35f66006eeb923fb90a7b8ad0f1321563f37a1d2a3696d53a402671ca50dc4914d0cc6a32715f5a8b63f68db51c114b56d543c0f5458b311edc0fdf9e0d7d71b4d522d0e661e3885196aae02b180c64e63331b35158e5e360936a175012742ad2b88201dab4b4f645e4504c46956d32336af9b3a9e11f3ca15d070ffde2d55f087087337e8bb42c6fc597196be1a9fd21d6615287010b1a211bfd7d2afdc0af2ea0b6942cff2132095d6e9db15e11aee91f05ccd7511ecde462b34d3a26d67f6dcf9a2f1a99cde008d13d632bdbd0d6af1e175438082075e1f21217dbde1191349ef651655f4a2c6375b7e249fbe032b7081cffb2acc7f6c5aec3d8b00b97682b8c20c547c0dbc0464f3cd437c960460d6fe2d03e689fc3363b97b600ce185a277953eb7c809d35015ce854779e85614a6ba15238181c0e970294ae700bbc8e2385cfee8aa7bcbf1346eb3facfd0fdf73ebbad77f8c565e8afcbed5a33468993fdd1a1f4985747a51141028598459d87ec4b953b1e166ada21515cb3e07e1e62dbf4e2df06cb29b61f03104af823f00cb01ea2de7033c4c11f2a98001ffc22d986b0940814d55d93404c945bd116337270396f3723c767ce33813d6fdd793534528ccb4ae104e014db8cd7ad0edd8dbe1c20387772973b6c2ac654a4f4cfa1b1412d0b278a55751a7fb9d628b357d5524a957454f9d58706a1c6aa33fb970174456013a143ba3dabd72c9ae13fd64afa6dfac7eb862eb66e4bea6f31109463351107d7120eef99fb054cb0506afb4c2e28f366688a0e8b82ad329583622531b2862e10f3891dedfb50608036076af302288ba034789a0992b0aeff4af4515f52390d33af369ad0d91fc6110b825b3c53839213ac454d97aa4261e7c5b3454edebca5bc082dc111c0888ea816fb9585c79ba2d675a6ab4246064507b9feadb52a43e3e41381a0114746572898a58f7cef60f3e10e971ae7f9db798502340d6e4e0f62cc5bace96a3a0000e9a35405c8d128e4c132c8e5e7df082fa1148ae267608c2b87f4e4e46ffe5567dbd0d05014c638031e79c35c666a82d645d913cb866eeeb82c0d7522d4b44cbc8926793fe30dd2c751bbb3fc1a6a05726102b07b6002680a3f626b1fd2e16d8282b5f6dee81c7e0412f98cd856df53facc49ec286707ad8f4a83b288df6e313da163a0b81b31cc52fd896ed221e95e84b2ed5e936500543d10ac823161f5ea0d3e1e565de9e1694f6f3a11ff94f552d547eb67a1aa98256fc6fd4adb2872f0317480f50d7501fed312a3c26cb0387b0a47881d643718b80e482f1b5a946c13a5cab8e5f01add6584d02b59c61fe9fc41ec06da668c309d210d5e4d65d21960b78fe304e3c3efe14cca44077541916a8ba1e9d577a3347aa645d56252359ad52dbe233b52e37cc485be9f478900671599ec28b607a14a14d82d0b1cebd8b7c329fc30a056356ed90cbadb7ebf19f9ef831680fea1b41e14d15d6de416b9c7fa85b30bf1217cc13a8cb4d7736c3c1132e7712429a7060ca934890d151732f2e5c2de6b7a78cd4bd33bfee91d05772be0e29fbfde0843cabb5156fc21f51180eb3c482a9ec42e85b6d3b5a2bb4abfb63dc0fcaf902ab13bcf9d7c0fb9a0f2e7e61561a62c59e56e0e28f309875857f418dad4fc67a9c6bc7da3b894ceec402ac69f15c52657448c314cb2d3aa48677a744c037872a74deaed3244d72617a9c05368e8a13d1a78a9c427577bb8563166761a88d6af90f2a41cc6c4c40ff218e5489b4c678262c3df8e214fe9a89d03e7f40cabfbfcf116ff235e2a3c3d3795a197f52f8537d12491e0c420345d25e04add32ed440d3426264395782179a929a482f50d15177c9719e3f86b9212787076cbb240e4a04506693c73aa088d5ae0e7a4d8124c0e8c466001f61dde738b80a28f462340d0607eba5c46e6b20c2b8278708e99706bf60f5d323b0e2618cbf163483ea983aaba076071e0ebebd37e16173b16138d21e90eba08a43ccdc6a689685fef627f1ff2f152bbca78a211f802c2ec46b24cbc8ffee871a848226f8d8587c9b5af44aca7f6ce99cec8d96bce6c6e2f225b6d61fa1b031fbaf1c4677f9f514dc9cfaf234bd90e20fb1285895b1d4df267542de5044ce4287c2af8dd9c3d1f188127e292c757a578b19b20281f50f4f87e5453b3d0d7476a180c24f7b0d9d986a7ec1537c4db806de5f25b628e829222ea52e7763b0fe12a9cafa03c821a178a9e0a2e5c0d73a8ef5a87ee9f04eabbf64a8a86ffe9a5865e53d37f37cfcacaafe2c2cd461c0e983e8166b26cc8cc36a6304fb4e8f9685bd75b433de5ce23ce2f6a4ec20722be2a0e1d12ba801b957b5f67debb6d7768a2acca295b80c0dfb38612271c658b4fbedb58a1c6a50c2b9862533a0363ee94f77179f1cee6657704a4691bdd2259ca2dfc4688f2a1402eb807cbb8b12075f3077eb54b5a810378f8e1e1478d0157f4cefcb8c0fad8857355029e2ec62a210d7015b25a1acf8fb84f288361ecb1484254f9036bc0b3102c5465d45a16ca1bea6c6d9977969a047d926ca903b4b6c4fd8f8f52809ca3a3f82beaaadd02699ed2106d48c945f10bb4614a3c8a4bda4c38331d1e47c418f74c133959cc18c6318c360bc5f60f23ac280d21de2e58a7e7b3f42ccc72e01f786fed75f887b161525202ebb5481291a45bd74592cc99a0d1fd33300b9c699204ee2521aacc7f8877e8f42311a8d103ca47262573803eedca89c392351f24c4bab1fb2dce0defa4193e6c992648ac1e728d085d6e66f19b7b71f9f5fa40a503a9ad0dec29035a99868457205e515107a9e5caa1a5bc7a8f2e5e8a57614fc56e1f6a840dc0e699b4de01a32fb0f88b3b3d57001cc422f57806c0e245b49e297e031a3d0c260397b66deff559c28798dcacc050f66361addc7bef265bca2da54c52063408cd070f08a29fd6ce288d8c8c8ce8fb7382d5da0b661f5f94b21a50bb5c9d2be5f2648abae64b769e3767360541ddf33d107e7efafdd420f880b80f525103db67c057be0d14a78ea7e6cb55dbff6973519719f2f7e3944d2d7f06d11c88afccd9fd74959d6eb2d78c35b896ffd89b4cd1973e415a94fe8e8d27ef9db299af8cd3e5b2d9db4f9d29f3d98236bddb4f202a5e7d84a7b639f293862f652865f8539cfa886f1b4d976cc43f52688f12b6b3fd364e9abdfda6c446229544274c53d663ee4f67c053dbcf1925e80a4b15fded25d577fb23601e2474184c4871c59d734e8fa635065c6d271292a529459f9117cf39e794e27d29d1206b122719cc797f5e2c25e6380ee73a591b11ba4c577a54628b5e8adc6ff87aadb5d65a2ba7b156e27aadb5d65a2ba594524a691d43809b1065baaa4f29a5dbb63d0a6ce4d8be0912d83f51d56cc2478e4d37b13dadef5a529500043092404a1f640fa24d4bd983fc59459963db61d259710919c37943133e405c8003117290807e757777777777afb53e0a6ce4a8ba09aa8920d2269d761fbe04766f0add694bacb2a5775e279dd20e749df431e74a0f0fce4d0c4bf6a3e36ad1d084603d4070646280642f9d9a568de60353910feb018203247be9d09a1acd07160488cbcc80765e43b11a5a45d2904ee98bea5e51c31b7452569b5a33548bf9e404a133f447d08c678735f41373e54c9a2d678492e80902a5cbb13c6d133ae99d74772983ae9b2e250d393d9c3ef78a2cbfa33265e7bd225b946d3a383f20e89e2faba441c110456dd3ffb9670dab7407c2dd81d09656d801cd1e5f07339f93524a3becf8d613aaf4f6df291974a47006182e797225a79455573a67139d44b942a7a315e6c14a30140c6506a6e53a6463defed832345b7e0d72185d7135ae0683c160dc908846f4c3471413e386442cd429e67392d527f238612bae4655fe52c4e2388eabd15595fdffe9b4b99a57ae5681ea50a5056da74053cc58e4aa5f7bfc4cabcc53ce93c7cad5b6bfad19ab6cfb9c3bbb25fb1f248b6f972cee2359bce6e5403240609426595cb2b864713bf3a2dad6e5299fa9964ab6717aa1780a65bc292b6546431eb34fcfbf75cd7894939d29d91957398adfa837345b5ae126668f5ccd0a3e88edefe63637faf30b42eeac3a43aed4e5b4a0148d3831676de0d9520a5b0a6943cceec236e419e4a9191479c6f658f7e88d796a6fbc312b2ca32aaff6c6ccd98da154b6fdad9e42596428f20ff795fbcadd9f79fe9ea9a9f29a41aef2c73608f7e9b07bced690e7d71fe9ce6c656de88ee7c206b136945a1c0f73f3ce3ae9d76d6d90205fd99cd59ce9b09bd33688903c35edf9d106a13c9ef21984d2207467b4fd48775c95091b86fb7a77705f3971949ef23c7535be3367e8f215327702560c13ddd3ee69f7f47e77823c9f5eafe3b69f3f62e5f1f735f99efe8ed4bb776f77efa7aae3bff7512e7d913651edfb764e51fd5ee877a207c1dc223d8308aa25555991b71be05c29a5acb7459b31d259f5471644298fd4a1f267eb66d3b79152fb0f4b9dc667ea4b1a69fd478da6b4297518783fbd9fdecfdfbe8bc9d253f31b491515d9be64228fbf3713d004d43b527b76ffdb6f5bd6534c558947284f715fb747494996ee3739dfb5896a6f6fbf21e67b262ff33d6dd2e91da9dd7da7532098bb138944a8acc8f2e584f91c90cdfdef2ec463c91e477bca9506a80a5feeef9dd3bfa9018a3137c5f9b95c6e6eaf81f9c3575a1b902b1b54851f633c4a366cccc4e6221262063c858ff0147e49475e69e3b7f86d60b7a1e337f7dcfd51ce342059e8c6d509ef4b8e1f7f4a45a36c3c63e369f3864ea28c13b482fd6dfb5cecd78a31c694e2fa9bdc94528d040d42c5460d241c63ac9f0e81b5132620427dd1dbd862904795ed239652b5abd440dd15967f859f3883cb0c71a8220a1be4a00184274c50835a0dda40cbe1af624345d2f094a0e25c0ca6538fcefb22ceabf23d5145e27091a86235112917648c9f7e4e9cb04c595bdd5224580164fb4fff4da7cb3ceccf5977f80e11ecf91ba554a678ccf7ff5ea69dbe0293176b7596eff9d856a324be184f3cefbdf75a91bd17dfe771f1b5fb3ee57175a52fe98bc98bc98eca62edba6565b0389750965e4a29a594524a29a5d7524aadb5b64afadb532cdf6eff71049bee7b37d156ff4a675d77f2159317ff8dbe2df95f2b6af6fdfa76671edbbbce263b7ccbafdbde9fdf0e11ecfa57ee39c12a695899b27aac92477daa4fced375965a5b7760f98b8a3158a12a74602b9da215ac49e956b7cd0e0036ab800511f286a01921704881102b64489badb358e90d405e2b152b8bb5e7f752594c6cfae308767daa79506df2529ffe10284fcda76ae8c2f7f2e9515223114bd5c4d97f7e0d01de5d084e99f2de8e6077563aa520a871b88fdd7df79d48f4fd89a04cd5277560bf6c9be20fecf4f8814d52073d49a3d2fd612c53d5ebb1bdca1a82aa25e87509b552d4a954e9a44e88821382ac200a55c8e7c3662bada162e38657f2445b0aa982662bc20a9c10e48bc38b064d610b59a8e2892a0cdde08a23a220d403254431030627d49057c084d88615f4c07230e0a15c1364ac1456e004e7c31652450afc25078727a278c2cc0d40f8c20c0f0fdc1094022ccc64c1832cb0b0a9373ab08196a1d6c052d1d5f001ab0aaec0d5544cc51aaa102ab2286d29848a315001863d9e609d140010430e5a7110c2908fcc909f2139d8810f0e6ee0832b365ab3b193e52ea55c354bb2868d1b282216fc10010b6c80830d4f8e31d32253b47c8022085ee0420f1b803003191bdc5208d0149e0ddea33fbd128fe9a1bc92974f25af943de5d5cb35973caa7d6ca76fc0abed2fa61b2c4f8fd2fa3665ad88b26247931ddb6e931d73d77fb1f5ae543cd9f50933c784207ab9b2861b6cb206a5a7df95440f84a41f8923e2dcdf07c33de61e8b1b634ee4c13d7e9238729fbffb76848f5ff4bd808fb3806d89862d851001da9dca93be62e2491af3e5962f74e32454e89ebb7ee77522cef32ce8987370a4c7126c4e634f811ddd6cc59e02bf7e060235fec47a63d3d9ae1c1ebd1ad949539cb9e2c746d952481a66b12db7dc767274c23ac93d91473c775c355f723c9987ffa8c890c72a622177d6c39eaf3aff2a8e49a4b6a5d87f1379d4efe12ba308e52b3c44bfbd64c14f5f46c7d9bf5664edb593f3cd4e69c198ca1ada9e56cf5e79ebdcf55a5badadaa19962cf7fdad64c1eff5b71fbd4ab7a7db53913e16e5a69b288260ee1933e84bb9e33715913ca568427f4c6d4b75aac70c4963ced99dd2fd5a708a2b6ab6dcd5576aadb57ec557a4efa27e2e96ce19ddf5b353d1d796099b29fef5a59e3f9eca42421336ad9eb02c9684f26ffad933cd9bf5acc64e1f5379652754836af55adfd24929a54f7350f9d12b80ecedab8e2cce28cb483715a2aaba69d04cf1076184447acc41b365ce668a3fa783b6efee13af4c89de133b99128552259a2d73e66d9f4bf809e163e3af769fc5b93f710a899306bb53e877f75368e6e0b4c8abddd70f88df9d16c93b1d1c7d6ceb49fc24e1ed0877d5af4da938d67189ede39b1f17b830fc54a4949be7e7b6fccfc5f49fee48e2c84f99111c4e12e72ed5adfbbecfc544ee292359e6b6530572857b92c4f8ad8cae50f48451957daa6a4155a797bef2917c37855eccf93d8b72bbbf8ba207268dc445b9391808e3fcc67c94f10c9ab449bb3ffafa70afeb33e2deddb5733e6973e79f41599cb4fce30cda5668d23a92163fe2d3891b11e9abe49c47c4dcc247fcc568846b713a9dfee4dc8f90bc2df192934739f722d1f6e5bd38cd83f4fe237a243df71de805b9ccf9cd6f8a3368d2388ee3384e85e4df7123239fc5d1db233f47de4518cf891de967d0a49160e8a01f8496f488abdef478a22e9e937bd4e2678f5636379ed63b41959c449338f6b09bf42ebea32e9e7e2e262f3f92894884101f02c29138fa1171f4b147de3f9791cf39bf0eb9f3fbd82271fc50fcc42c8ab8971f69f49fe8639374a5c14c19fd8cb4e782c3bfefdb3e9238aa6cd277178b4cca5963d197b990bf21341a6d87ff21e9e79611a8013d0be4a9cf45a1688a8ca0dee7223282ba25fdc8923ed43246faa963a4cfda66d2cbf74a7f1a7553fc5df238bc085fb12fa51022b1881b872cfa51f610a18bb0324badcc7223a3efa6fd74d8ef48e29c69d1e27329a1243bbf1412d4b3e5f742921f87ef50eca6777a1c6e2291b028faae54c2a5984d267cfb9b25d516242dbc162d1e977cecfa2dbe5289387d62245178496fe5a3bc25d1238ef19e1eb349d74c8f82710c6d32fdc8a7e33718866f2a8523a1e843b1fbbc6dd157b9527ad1634fae905ef459b290bce8479f8bc8db212693fe7efe2efc5c3249bf7826b960d626d724ff7865df0be9af36c91affd017c755a6b0c35ec99561d19742d3bda50f4da148fa6b0a4b3788166f8eabc2eaa96679ac32f5c77d1ea5cf6fbfca4816efed5715481692b7df91881e08f7df2f79f9492591f4243bf2931e877b250be4a99915f2947d936869b6a46d90a7ac6f91be37d4259d151c7d6c2b233de947bc495a7a0aff20bdcc55f6311ed163ce233d9e4e3fa636f7a2509b709af4e18f5636a2493f1249243d7f78ca7ee8914824128944d21f989fcb94b12257192b93342cc99642827af6e840eafebe1bfbabb1bf19fb7b1454697fa4ffbeef4ede6900b98751c5c9d196e22a255a20195dd999abecdbd6b62a237a4ca5467a7c59d632911eef6c5b20eee32b630a488eafe8788fafcc20977d218af86a6a868f35f60d999df0b91d12498b7ed4cd3689445ae2c7262d3123f2c0123b3f491c296c8ff2e3709107381eb7d1f194bdaf6d85a48167933ee727d1469f479f45b93fd183388fcdcb7ee2051addfcd6494e72d24f699ff452b22491daf9af0e0c49df9dac2f8f8a8acca3c7dc2a2a32e9555364d106d999a75453e429dda905adcc53f6e90784ddd253244dd2a38f4d7afbf47371e1863c639eb23fd213e6298bbf21be97fca49f1f0ce973fe4cf29b59267d7ed1e79503912afb222c72e1863cf2a41f53db021922bf8c3efffc60f28f463fd26ee3a9941dfd85796aee91f69be9dd1ecaf1f80a2d8949dba4e75e9ee2786432e947eea583dbd11c8fa7aac8a32591c42b85a4615f7e5796a9c8a230f4d3fe52564655f6bfef4425fb1bed8fdbdff753cecfa5e7c27ce549a2f7232e8ebcfc488fc32f8faf640e2fbf7d0be42ba3b7ef228a4c05d66bf7f0ddfd954916dfd647c68378ca7e32fb36d423ce7a4c89f4f8243d9eb27dd51af2e8f35f99af98de6ea50dbf83266d65b6a4392179a479907e947fb4b2d11381c819742040b67d4f1cafceb6f6af28936debdc8be3e160272e840a3ab9bb704f1494999234a60c5d763041f61fb10e2969ccefea7409eba697f3697ce9bbdbdd87e0a5fb2e0ba92feceebbee71d709f56c19b4a5102c64bb9b1fd6638d3d6b583add4abb81262f1bd6e312fb7a7854128245cf1e6feceeeddfe856ba0fe2445755aae6775ea73b3da23af7ee15793cedeefde4a90eff14fd287982902c74772f84afd015d4a9eb5ea2741b1c94a02b9237bdf792ae5a889e449c5245bf8528a5ca24be5adbcf1a9d9dedede71fd9e654a287a7b6efaf38ff1375dc9fb72ad1c357c0df7e4a215799aab63f59c186bdfd9c18dfefee8828ebe9b46d23dfcf5ab789bd128a678b5c6475441a29995a909c4a5c98a04e5ec0e8444cbd6e2a3352be92dfbdef78f8538feb5483baa8ca529f9962696ca650576cb6842f9c631bd3e9e1fc90899134ec8b669a9029e1cbe6bd146cfbe1ebc66eecfef86a860cef7910bd1bcfc6532c57d9dfd9f5f470c258cd0973f83094f161178bc56221ca875dad16c6f8b073b95cae507fd8f9f884e2875d2b0cc3f03fec727242181f8a6ab55a2d7cf16147139e7c288ac562b110f5a188460b4d3e14b95c2e57e8e243918f4f58f2a1a815866178fa50949313927cc8d56ab55ad8e243114d189a3ee462b1582c2c7dc8d168e1c8879ccbe57285a40f399f99627dc2d1875cab15b6c2300cb99c10c762b1182dc4b4307ff84af80af7e12721593a97cb153e11bec22eaab23e21f6095bad10b7c2ef43fa614e580b6b61f8229c1386b7168698260c657c2e610e1e2b26430d394489d562b1586ddb176380ba73f974aeced5b97cb6fd17bfef5a5d4ed7ea5a5dabcbd9f661c0f05ed43a9a5aadd6d16cfb2f4e3a548c168bc568dbfe8989c885c8e5e372b97cb67d54097712b54439a1a82512b540512b67db3721c12db89a8826ac7d4d44b3edbb30dd1217e3685c4cc4c562b46dbf646423712ece8773712ecec5f96cfba7911ab2dde3ede15a5c0ed7e25a5c8bcbd9e1cf5318867a04676662b4582c46dbf673f71c49d7752eec835dd8855d58a4ffb5a0b88573700bb7700be798e8cdf4c6668afd9c69178a1e8e77e3c978319fe8b1240def47ac665b9a0b866d3f6b218fb8866b78c8573bbc17e3a97ae3ab2be3c70a64e7f063cdd1e1c7aab3c1af36be927af06b0e64cba5a1fc5877b0c1af359225c6831707a7cae0d4199cda1af29567836cb93c3c6ea84916fde3ab2b0314448bf98ac805d9726f6ebce0235960b87cb5cd66af19cf0cb641d4834f8f205b6e0c4dc2069f06912c2f1e7c4ae3ab8d8787d6f0d01b1e201b3c79f0690b64cb06043efd21594c1edc70706a3894854365827ce564856cd97676d030932c2e7a7cb5c9f8c8fc006db044886cd96e6e9ed8912c2438beb2b35990990b7cbff195d283ef3c902d5b8cfb6083ef3692a5c583ef31beb23cfe830634bd41b65820a0380c49961199af2c0ece0c470807247d41b6d89d9d30c424cb48c757564666a7070c1ffca904d9626f668abd59c2067fe64896fc609dd980a297c1067fc240b6d8981ebeaa3c433c3f36f81c902d1568ce4896f9e02bf1494816fa3319f844f8aae2509505ff45b6d49d9a6fc8803fc3575546882024cb7d70d461830f82ff8974cb902df50645b2d4071ffb8acec09fb2a5c6cc14fbe0834ff2b980af9dbab14bd341df580efac664e81b43d1b46d3f86f6589286500eff5e9efbf7da1fd1a078effd7b2f09c687daee6cdbeee88577b26d1b6adbb66cd29db60de7ed872e44259bcc76b3c99c36994d66bbd9f64112ae34db6266a5d937db62b6fdaf0536f100813c251e1ea06ddf1bb9249c1d50e3e0e0ec6cfbdd680bad8cbd397d5a5b999b6d5f94ada8ce6c4c9dcdeaccc66cfb9c50ae7bbc3d95078887870768db8e9b60ccb6600cce0e0e0ececef630b5b52a7373b3ed5fb13e768e75ab1ebf201da6b86d17bf999783fc9362e8a37d6690343efd61913dfab3edb33c96cc27fb803cf5cd5c651f646dfb557ba63f5446817cc5ff0baa619a5bbb354c5382431a0f5bbedae10b029a79e29c214e208a9fcee80ad7688d992dddd04cb1a7d9ac1baaa23f16bfa0981a5d7932ddd06cf1989029f6eddb6c5bc3355c63f14ed0c161db17698f8545ae3f86af6da594327c85afdbe32be327bb3e305f113fd907e42bd5dbff823ea18f663775d1d527a32a9fd992678a7d8f4555f63f99cb457d30567df75199dadec52b535b2755dbdbcfdb290ddb6e3f7eb36db7ace3b7bbd83c6f3e6db11fe64104634490c5a2ab4fe62afb18e7ac1a924727547fff3e28e32bd22795d882f47178dd9cb8833863aeb2e30c9578da7c7e9f8cae4aaeb2bf7d324cbd185fd116cb636dfbb44529b5d976f5017db24d7f9f6cdb10c53f6004dbd994b4a8cf6cfb5352fab26efa9b68777d5acceea8376772e549957d4f0865e50cf9b2fe4cd9049223fc2315a24292657a2c5f494969b8ced8a16d1f6ff473c12a9bca5cc4d0509a8cae68cd55d635e76d9ecb749c97ca986e425bfd8dd26476a7a990a7ec27575488aa244b7d5b656c5b59dbfe78b2ad1e4d355321ba7299a9b27f3a653a5542dbbee873a12a1e15df65660b0ba4bf40aec0b73fbaf763b8fa7e24b93fc2d8a3cb5c4965ea3e15bffb9b785da4dbb66defe27e2e147fdb06fb600005c8dafe35b54e204f39cdcad7d0ec098477d553e62958ae349bac523b6542d8a9aebae5ab4fc8a3f115ead1c83c9aedff09d1d504927d42734e4a29958fe5ea4a157d2953b445ca14fa72d6cdc3be9a405b86ab3f0659170b3270823f3f0bd996506c298448cc6611f1d979e3f0c1f041307cccb1c207b9c7e1639b1393d147f783a73ceff3bcd42aff49d25545f9c2ede334ce3d458fb979bc4099b870311a9d484840fca3cee7488f73dbc0cf71a33f7d2e23ee39d28fa3e7bec3a27d6e64c2719fda233fea5137b6a19a22dfa72fbd1de1c6df71e2b6bb18e00445df330cc31b8217bca0078ae38c5d5176f51cd75aeb0c7cd3e7020261f70d4fd11fdd67530f63fe2359f2d3eda11e02fc30d426398723df0bf859db0f47ce61bec2037cfadde8db31c32142b9f3f7c23d06c1c7bfc1ef72ad2fe9f79dcea3fc21e638f0479f71f860f86016c110673087a3cf8be18122f654178abe41719cdf0e70e39770e32ab7efb10499eefcddf7b998ec486dd086a7c0ef429148be26f96ffed40643ab6d4c9a3f4fd2a83736e9e9ffe02b3c463724cbc8d327806409458a142952a4489122458a142952a4489122458a14e9428af2f7af7e51fcff3f397914ea4b4afe747a93e94ba51f8d3ee7ffbef7bce7b8c7f86b7d6bff8482ea019d99b2e5dacc14c7d766b6dcd74ca12f9bd985235235c56002d36162199e94524ae1cafbf2c7b0d76cc133259423323549a48a5e2230aa0e426a3afb0af98aaf4821b59c4d1ffbdc19fd1be42bf3d236a5bb7ef55545f258833280810b5c0ba02051010a8813d0474820020f8118463cc001270d78510403168052000c2260125032840b217620e0f46212840e204c2e2d7e38408ed226c16180028c0840bad1c29207306263b5f20920003ed4e801f442951400d0d88187199c08ab523ae420a34b65000317b0001215a0c0048e9040042060c4031cd0802218b00005100193802184d8818097207400e1f2c301726c1c062800016eb4b00cc0c66a4500357c08400f2a2900a03183871d54291d72908172efbdf7defb1206e55e198ff23250f4bd3d28e2dcb7075685992c7605193278304c76effd7b77f860aefefbfae25420f50775a7ead49ceac4dd28aafbe2df981819266c5a33271045968e8d8d8d4e4a260312031da20591e1ffe785e83b860e3a3a2f26c4603c4d383971e5d06ad5cc2007c84d0d50281a17a26f183fafd7eb47068d5653c38c0c6b484989908e8d8d8d0e0a0ce60385073990ff44719abf4d6200011224083c2fd7104c261c2d23f3a3899b1a9a14f826f979bd5e3f2210d04c0a564dc80ca3914cc7c6c646e75faf1d27cc1f9f1e28f81e81515363630357101c1c7c5feb454d0e4922343f626411dfe1898f4f2c0b4233d916380e86fa603056d9546ad0e8d9b99182b19eb1f9be912b1b18f8b102b9c08f35c7022ebe178184ebf7af2cb932a2023f56190afc586726e0dee308f7a73f72150209fc488122f0230d82c08f94b6fda94f1a23fc854bae66eb013fd297037ea43c0d709f3945b83fa591abe962c08fb466013fd21b0578c9fbf421c25dfc6ac6607ef45a027ea4ac21dc274d08ef91ab59dbf1a3fb20e047ff79f9d1819c26086f812357ded2f1a30701e24777b978e9dd737e70b972d7017ef41f397e749aeda377f7c1f1e38cc3906421c9e4ca6306f871ce0af0e31422c08fb3e6b41bae23575e6bf971eeb0fc387b06f093d2cc146ac37fe6cc965cd1d6eac769b3f2e3c411807b5fc3694e0f49bf871fe550007e9c31db0795e7c08c1249481651ca8f7206801fa58cc61321573446557406a5cd14faaa1fe5ce0e3fdee0e1c71adb7f865cd11a152208c9d2a57ed461eb7092e17f5b2892657b9f5155f597a7cfc56d500c5cc00245cc960b84841e7d1b518118056213d03d66cb951da147df21a0ab8b25a0471c010d014d335bee8f1134d7e6012d07b41ad0ca992d57a7083dfa6531c0b50097025c3eb3e5d610a147df9b0c269680d810b14d7fd266cb3624841e7dcf1a7dbc438f1801fa45d3ed27089a165d6d363a5a40b45c5a39b365d3f921c745571beb007ac4395cdbe5335bb61a1c7af4ed316a8058016204a0efb4d9626f68daa696d2cc166b69ce6cb12d5e63f1da00bc668306aff48857f49805a06be8d1a965d99a99427f556554459b2d7568a6d0f7c11500570f2e159da2c75306801e338dd80c5da3abfa6acd96fa33536674556d6a664bd59929f479a0b51d744da56ba9d6d6418fa79c831eb30c8da26b36fd0ec380660badc160305a031245511445d145930f468b22caeb47d17a9c5bd4a228c2b438b708f3a1352882807ea2a06d4041b9328cf195c5f8ffff7f51145f14ff258cf81fe3c58f31f7ebff0712439cfbc51aa406a1f2d457750d4114f5fcad5f3faf9f1727272727272727ffffff27251fcc9f9c88ff2ffec9c99f9c9cbc10e73e11eb8fda44bda93595a6a6e0ffda5c9d6b737560a05028140a853a3939f99313d4e9833941a1fe4ffe4f50a847a1504030c4b9514033292aab2664869313190cd1f70bd1777759b7e6b22eebb26e8d8b92929292929212140af5285409c907832a293979d49fa0f43877494949c9cb853877894877a8137e7c7a28145028d9904c261b32399d4ea7d3e9545252f22525a7161f4cc9e984fa924795e871eed3e9743211e73e89d486dac0150487e2a0a464fa76f1fa79bd5e3f2d4c2693c964329d4ea73f9d4ca60fe6643295fce94b4e26d39b4ca6dadca6da10111afa83c614399da68d8e8d8d8d0e49a9542a954a2593c9f42653a9f4c1984aa5d39bfe64d2a5920f893877c9279605a1996c0b26d3f4dd42f4dd6dacad66636dacad86341a8d46a3d1a8542a7da9341af9604aa391e94b6f9a7ba447a3519091e844e87123944ab221994c36349273ce39e7d16834caa40f669473e9475f1ae971e6bcb3e833ae021cb7f196b360349abe49af9fd7ebf5037edff77ddf9773fe9cbfd10793bf6ff4f947f9fbbed92c28668836abc8d346c7c6c64627f43ccff33ceffb3e2f7f309fe7e5ff3e7f7a9cdbd39eb7138a737b3b3c5190c56053f8be97efceb26c8d65599665d91a11c7711cc7719ee7795cf8c1781cf7bdf79fa7c7c971a29983a933732690b903cf930dc964b2a10e638c31c61cc761f083e130f69e7b6f623c8487268b0dad393365dcc071a26fd1ebe7f57afd6cb5d65a6bc5183fc6f5fb6070ad1cd6e3ac75933fb420202f601bbaba33aad2992d1876b271faddb5d65a6b6dadb5da971f4cd5d87a7daca7b5356c0d1b3decf92c1e211f54248de93c78281975671ed60256c1f80583a5f09cd30cfe1f8cd5d84fb28b864d3f6791fde79dc15058b54df1d542913ca6ba19de09068351159517dbc8af13cfc3eeac05db3d5777e62ada220d2053f3318cae0e802d4c0c9b5e21b6698a575dd6b0b1e7ab8adc59a53eedd76d351764cb183e2e3067177c86ac8108112244881021428408112243860c1942a3d168341a8d46a3d16843860c193264884f11a09d3d45b1276950e92915aa427555f124bc196eff5757bff3177b437d37fd0d377b625fc170c83aea18767d2b5be6cf4c999fb7904d80c05b8e3dee9e1aa7a8c98b1e4bfb349fead4be22ca4ffd18d5ea9734aec8b4b24e29893d2a5357a2eab6fd86dad56db5d6befb948901b627ad76bb981375de0786019dd6a844ebd90b8bdeb1a1910040002316000028140e86430291589487b29ae30314000c769e44665a34184aa3148632086210530a18430c01003020002322541a0005f4fea0970ea8de067a9ca8e7037a4fd4e3837a4ed0f3a11e27d2f381de13f57c408f13f57c50ef097a7ea8e751eab999d61a04663651cab5761a911d98f684481d2c18483e2da1e08f722e11e30381dc908b215a380d04fc3c623937dac7d46c317315e8a7bcffe1c0c443f2c16500f1508fa3feb8eb805d950086b853bec7898f427702aca8bbfc5aea5dbf9d7f178b19885000d49669b9522cd37f8e2a33644123265492560f6b552e52df7c2d626c69703556a5e2205098a4548490f8cd22fe46370aad1854a916de56563731707c51b6200db0921d3e7935617f2a1b042b37fbae0ee108cbb8ef5c2bade46292bcfc96655880b5f40781d4be09cde4fe319b1913edfebd337ca8f526ec1437eb94d2fcfde41158f3e1f59df9827faa1257dea31b8d54b9cbc61ffc4a6a6c8bfe9244a5568687732a095dc807e34f3174a51fe45f279148c7f873d954d31476ce79f49a073a08e1941ae4517633e2c0a974b2937740d95447b77198537e5b038cc39df25f19e08b19feb7d4b34c6dfd4a79c794d7e63f89670cd8b6fd93bf8a816f935fd99798596dfd9779c620d7f65f92770c986dfc937e8a81df867f898f28d2f6a98fec2a2299a849d17186ad36db8a509ff47e2ec3e054969b1204ef0413346884fbe10f02d0deaf7bbc7d2f252899465a95a492b4694b09cad390462554927e5a2945d934d2aa2495a44f4b4a519a4eda2a6d7261913556acb2c09a15930b96acb1c49205d65931b9b1c8120b96ac618dac813288aa14de42db4a8f859c2944be8a84f83298ac0c42656ee0d6bed4192418841ad6b1658accaade00124fcaa071f09033ff44ce2c2b72dc667fb11d824ed8994d584c1ee56ee442432579dc7b12b3af9b013607dbd99ef4a0587824f10cbcc6b6d14c8aed1c38ebafe33e3e0d9c01a475de50265706c9250171c7a8514ed0bb0b376729850ab9108e00b345a15bf0dea4f9adade404fd2d681d07c617e01f44aef60963a771f409b1df58127250dbb5987f6972c551c9c45706f58c0a7c39c728932b77e2f62cd5d03a2ec4219813887ce50940f19541d92411772c3ae524fd1bbaa506cc053431fac528e49648293de142ca98815d3289ee0aaa8e037108e70722577d00b93bc56530ba49b1ae925d66d93521379499908bc523352821bf580677181168490751932b37e5e6591aa13a4e94279017984cf521c0eb97c1394910e1340a8549ba0bfa951acc85856c0b1321478b47360d7d1e0ebbd404ba3b281d07c021c81f88acf20124771a97c1689362d3869b90323474d431e72229547fd866659090a3cb23ab8d29689d5d7202dd1d341d07e210e40f4656fb00534f3050048ebb319b9d2609b916c62c5fa5167c49a65b06b5b700ab24283333837d7b964aed305be21f9b62d4385aa1e012f9063411729d798becca9059a86f132e4ddc81ab88f412210f8cac30483546c51ed405fdc07d467a73b98c04ec141b3686b543171e5a08ae1bc9a7a24370479d877e746af16404863692b766979c96f28045ad6d1be5cbdea5f94e825b46f25125b86130e034fa8e07226f4a871db7819a80de47e7f6bc546004a2b938faea638682781bb67ca360b46ac6096de2851e3775bce74bd845636a7c7543501aefe1acc0a5fd4181af4014c689572cf088128871d991d47a85eca07e6583b640b7aeb41c30396d39ae80c464b468e8da5e908224bfab60bab16903bd7ede16a93e7706582b31e0d253b7c946045ebd47fc3bcf02ae6c23dfd4bc4461b24856e830526e3b2d792386f9554b1f289c1811be115fe7d24ce3b5e1feda5a0e4eb93fe3e85d58d13f0823cc27c11276b941cd8817e2d7c4a0920ea61e7b55900dbdd4c45c028c789a45bdb12626999674a526adc79c9e0fca63babcfc85bf01abaa6d6ed1ab927b813d6df3ac7d880600051284e5ea2be68b9a8c4891befdfa7d14a62260d69875f4f9ac304f27b07ba5890500eae10c68e77eb259992a72d81ac7eade64002547db57aaa7c50fa9a80535002802beb5476daa82a69dbd7a5007445e8b0d2bcc2027d193f27227622697c64a38dc7136fcd5552bb0cf8f0f6ffe20077e9a984865ab7a2efc294258402b52c061cc40e0d1747e9021426003b0314714a90f88749e3b5de9a1fba3e2d69dd7c07e2f076b6c6dc18d675d40b6479dc581b3d2c32044824e9859751b8a75413090a3977db56df2232c773157675400a71d0d5a253b35f6b661ae4a2d4505e41c2ebbbbd58b9f2ce43526645ee0764dcb364de36d032530fb5e73062899d6cf0723b303ba2ca71864dbac434264b421a44d89e270d53aacf48f8036fdcdd0e99dc7ee9f55dee8ea36b99045ed2ccb6a27e649b991a09e60b44023c5f6cf5977a58af7c21a0f2f1d6215bc57a375937e77bf56de784fca5fd5b3b9b5aa7cbdc2b9d18df5eb35dfdc97607b9d9ec09ab846a0322d6168cdf8d08fe1a00964d53ae2333163455fb9f976a15d10f1a2e167b512b0187445f0f1aa7b7c132d03ad0cba2f6ed2fe4019190cf75e9045ec7909fa4f822e8dc0cd4911318f0d586f0795d8f92481d7426cc3d6a053d2ed44f479477e4b1a3eccbcdd471679bf95a96c9236d17fc53b3629738404dec089e3d38923b045452b8c98bc113c00c36211fca74889cf82fc8a2482df053ea190ea81c6d889a498bc5f987201eec5d12443252e1b30a3097016f16f7d08c4bd00f516df6fc8cb0066e5ad0c855ce616ad824b6cb579a819c33c2a5d627a2a88807e362f472e736b14a5651aa7c0e728a41bec0099a993f067c04b25c0e78b15a17773d4fd25c299be282003c2a2cae8c97554162f60a16640cbafc3ac631f2bcd5902f9e2215701a2e0eb00e17305a4e84550530e8445605894827e6125c1f141816fa24ba51569a14716b06762be67a14986255f6d62d610158a8445ffa14a8938179d9483aa05e2c8937827e91f94aa0c0e0bd45160f367636d7484b4971decc28b22857333531d9644b299c245991242415988b52f0ac15507be9cb3e8b3ae45011114565d505faca1b258034b35011b7e1df256132c855ed8ebdf441d359478f499331397164502a5641e329d0358d229fe576f027008047b27df1ff46a09ee0b7011e630a548dd9f74fd2c2c4c9b71b90acc951e0c1723ff0642fd637083c54fe93305e2997aec5c20bdac0b0984f06eeb52f8d32e99c3064326868fd597c89c58fa5d5e4dd0b02818e0f5c570eaf0302e45417007e3f411ed2c0489969e49b3863e0e641a8594b1012d751cf7aa3f22208281bd557216c04c932099e1a300ef02f4f88937f584f894fa9afbbb1a6f7521025b06589d6cae40831ccc99c085166962285fe9ab380ad12a27de216ab41c7aa10926a704bd548fdc439218168089a1ef6954a23ea54e3f0f07a6e0b554ff472109522a73b1fc665aa587744ac70109094ca5904cddde1834f0e59ca34ff52602a760b07682c2f45be6f2ebdc0153f15d74bb9862d9e42538369603a50a99d5df2054ee022e53a6846d787d238017470c6d7a62600084dda6f6cb26c648816470890098508df2646e98905a77b85170cc04e29c3638e84c2da9ae42d3d2d827446f66bd9a012fa3201a6ca0e54e477e754f06c900b077557b13f415de9cdfb843ba18f56e0000be051793f7f084112c923ffd42aa60260e1b8ce1605d0cff41d4cc2350f551a38d8cca0e33d0b28ee2bffa36008620d85642fe41b8ca60dba10eba6042f9ad4ce74879e5c2c84d6e8969c5d368ba0016df825f5b0ca0e50fdf8637a222b4cffe6308f08fa1213eb0b5e169595cc89b9cfc81b080c16e32f1819ed265c80257dcf7954311e2e90774a03663397bb9b30b67b10012a84ad4ca21612b651a114f3a63a98be931ae47be0c8e7877c96db326c49ae8c941b1079e9400e124a68c4e45b15a1a61c687837495e92801177a018ea0d05c2de9bd36d82526df041f4e88e62736b8d1d0a075dc6a9bb05a9057b4e30064b03f7296595b2cc94c734f32754c5393de437ac989272aeb32a39befe415094a3921e58a8d52bc8ede725f6ba28434579ae164309c20aaa2d9883cabeca66b30c34fcb4952387576458145b19ed3085f8d901a921bd40c21e660066d556a8c4c57c602443c95cb52bb8d81c80dfbffa03cf08f1c378a3f524c5e939e47d7aeb210826e4ce399d891c906158bc62218456f1c5bc8154b495ccaed9617a25b0f226d2094328f4a3608028067cab832cb281fe9181c28e47e922a703510528d9a0433a07b75e75e0e31277c95df92cc12815273001dcf2c805346f298f996f2761c19440a01f9d4ab28a2e821b78021776a58501b5618b9c3b630861636c9651eee838462f8afc67cb76ce5929339110f991e5806bd37ac1a8528f92460f5787559b006379ae256e73cd96983059c8fe76ba4c81ceec05ab3bafa7b996a13202a7bfd01487f989d0b63efe38d6e7a9984ea46c614c6468a2d8819195b20363276c1d88898c9d8207483a31b1ddf28921b8ed3ae606c44ccc2d84831053122630ac446c62e181b113b191b4237707423e21bd2b831c1de0a5caf69bafc4fccfd4d91d8613f61e4b14b95a756b109536af81523063eb75cd80766c20521a1bc5f8974b316c399dba2f9e35f6e78177b3c8af2c0731b7a2b153847832f1bf446fe539ed201a5019cc0f4ce01a80772e9c06986490257b0210361d06d2fcecc88bd2faaf6cd78dd363a5cacd3a23a9bb139e2c728853f4eb359e869ed64022e3dc2597ef761f128cfe9bb77dacb030144f0e738dc627dcfb084e032b12f9807c3896f2292da38dc93536d6118b2b4da74fe44d46d9df722870bb1be45bf860e54ef04656b890074c06166b0d9852ea382c2a4a5ef3db67409b3c238f7959e17612f64326208be81ddb9be2c93a6087b29b85b2d202cf8c5fb721a52ea0b9fe14a68d66796860215187db685f8fa2e54e5586c300fb1ef24b796ef546e6e89928dca3d355a2eabc12320887fef64c6037b6ad61c8e47edbf1eaa8bfd6611c6f976d860f0a356371e6ae2aa2fbb1212f5a011033352b9ac7ba0791ca394b952a0b20dca8d36db287d36300cbba20e449ce9d1d2520a01a0d151c17dc0ea225019b82e35fd99febdf253f64e4c6c074962b4da8bd87e123ff14d4dc87fe9857d7e5f05ad18ccf0d31e94ae8a3e55426d0a4ba7b0c613c07d108c0184a31999eb33e11dc120c934b053942fc622252e1aafbe81c15de0ba99ecd265175dbbe9b2cb2e5d76d58dbbd4d08ef25a1bf986d5b871c61e337e2ce3c71c63ec783a9ed9783ace31e5c2844623a11ccdad53a579d0f5bc5a0c13c03167680118e935881bb86ff7f9491015e85e27a8f550b9bcea5d3df7c424a2698db71e1bcd78f6b25e7660e1fb5cf6696756065e9870628fd0f391acbb3d1681708f2ae90076304448fd8134f33959bc6083fc75935bdb07353de717c85a5636cf19deed64258c2ae5d91a60e504b034ade8d668dc0cb28c82d0cb66520a6abf9adb5255907394c16c5f229a3cfd4bd8be009aedc3ad8b4fa69750a668004a683d50a75c2b6bd54438e64c30682eced33589f56b8cb660d75c68a20863d74c4d974dca4eb4a723808b4939ae9000030c187d426773a2a3c09fccda109a0535905cfb0a32954befd088807251958a62ab4e76f77f39975eb33725fae32f1234b3490ca632a25f3ce927fde89f698a12a66cdabfbab3085302fd579efc137ee4cf7b4482299be65f9ce469cb702b6c26914f70b8efe1ffac2e274c15f3f7eddf11a66cf37fb1f77f52e5f7f70f61ca9e7951b9b9a86d3d4c0992dff4e14fffb1bf0c1217a6eccc7f91b002a604c96f7ce0a7fec85fc4c70f5376e6bfa46965a1b1a2a83bc2e7301edf2402f8230a98b269ffe2c7224c09b4bf78d24ffc913f6b354959cbb861bb3715a632c96ff8e0a7ffd15fc489075376f67fa958075382e4b77cf8d37ff42f62c414a6ecec5c42b3075342d9f29b7e0cc3d4621785c5da51277ac2542e4857eac321839d7a09502e215121dd38e36b1dd6970dc25f42341e30b5acda55225afd8ed8106798fa40002702830f960a7bc054d6637f660b3e4c7d78f142bcc72562f64b18e0c8b8a6f330555e3ba7c1d4cf213eadd5886e717849b7bc78ef350266090f2de6ad39a11ea65666d741a117855243101be632aef96996b2c558ba352596d21c8513bf295ce977c8b5b7bf00fe53dd9585d9f728cee298329819807c0f1559373b0b6627b739d9eafc81d1614f27f8d00976474c1aaa9fcf69872c0642e0a91876033f5a73ec2f1e93d36df9566d292cfe66b56f831430d8af2a07b5cc4cccf8188c86725fe9096cae33461cae87b2adeb8d4812cdad45d2840b79293ee146910a52d49004b8ec89c594e2387d22e0b73891dcb4ac35d254553b8a5528ae0f10e2ce5434570a36150f6b688ace0e7181db85e6c3acebeaf5d902b3e2372b3df217e8479dbadc22cda6e514c75597e79690347c3e910f0aac628aca9472d662e496d8e7eb4120b1426bde1d637f28a2bc47f76fac9a6455cfb02aada7104027827a5262670059c88785973e84409f81226904218082384f2e829ac39ee3592f29148a6960c3427763bf19eb844a23070666e63a165b46585ffe607f46a106d6ce4b3fdca7d33cd25292088316349f80bc1301a7f0244e84bc8a568115ee367f372b40aa5305a4f8571132b5f84390e11fe96278630e0158c09e976db58a867ba8c9be41971415ff6e8114b4c1c80c69ab45a1d4d1eed3330b4d066e1555996cfe7290802334fc177ca06410887e75530a716db0fe92f61709208edfef28280f5bf32442312e580d8cdfe27d4b23b26b0f2d1636f63973a82418fc0e5a1208dedb7821fffbefe67a4953c9f9a749277f80d98ec180f8d18afeee4f08c5f560eb1a3feb1bc8d93f9fee043b85728e974cd19cbda732cec9f16f0ab6af056489c76ac90fc33d4918ea4e62fb6da3e149cb74819b1bf7fe1c9a76e479886b5ab12630c8a01d04bc4b10b9cf2063355488227a9b14212b00419ab0930f48099a26990968c97e80c0e41bab7b2444a0680bc2a5f03f10adf7e88e1991800e64321a6af489f2a7ca749615a67011c8fa73462f5044d4afa8889e1970ce1a080f3fe48f1b587744f4547acf2746da045f86a59e8f30fd1ec5f05bf659f3d0fb63c28d0e679089d33f3a92d34fa6266a17aefd8743626d9315370a1528e29e9598ed4a44207718a0cf6f26b6140ca47164cf862505d94b0518177c854e9745045b940531f81f4d317a706521c023a9fe61b36ce87bc015d7c18a2b586f2200a9190ee277b25be85b24c705f3141e3cc99bdd6546049952e4f9583cdf21e60f410342debf5426975cffe3c39aad255d8cecd6f2ba61d8127ea09f1147ae418c51f86c9eb469c5488970bb43335594e0938820f2eb4d5a73c0fc4badd92fcb40eaa657d5780a17b2d6274930dc342a98c9542349ebda10a36ef0b6157869e468e40ac5c5a8d942852d548910d48f657385234181dd8b88b344e391d3e34f737afa91563ccf91c776a3634262642ce737861b32386982e61aaea1056f88e6cc76433d07cc5c8f9a1b1434c4e0052eb37938ba5d4c867045200931f6014cbdd561b2c4a0ad0f23ce6570d9feed870bbf25a3d181f09d69043125dcb7c9a69c94b6b74d43adce1d48d276cc8891d024919f081aa33279a0efb033323d58ede85968985135024b8b895c57570c49691e06e64d70fc4a423eeba96ffefba7381cce9db1b072f1385e638d57933feae782bc210b75b76ba8ac291e15e33a3994b5de3b478a0141989e2d35754bb0c24b3ff2d2d9486e6692391e6000df476e6ed2f5baa20b214115b2b883add411a8aeef7cca07e82290febd170a977a9e01f1d642fdf22596924162c6ef9cc3a043d43462648b1ee13c272738aea5fa0d453a5c09cef8b8c677ac9d2d9097fe592e408742f1772102d2956bc1548faa6ec0316900a5fd929882095e07f0a498eaa30a83c3f77f8941628cdc26140e500896cb41dbfe85a023daf5ad8e0316df9ccb70b3a1915cf731860536ca5b1e49c1d93dc281b17097afe87ffc8028d7a63a8be813f9665bb45e6c246d362dfdc67c06836b1cc5b791ab7b930c27b67ca079632e94fb69dc86e46c04731dda40e8f5ea859c3e334c58fd20b304632da58abd6682f9e7a0e3d99f0f2659f76a465d98ed30966134cc70065c62b16beb0e9c01829cefc8901f34b4b40f98176d063be08ed0f55a69fd39fa84f271680c3d722da3b57f9339370806442f9bd8adc2494f8df73c91eaebca9a14fce8a13b727da5b8f42f9109c95923699ff931d9486a999f7725422755b04e715a28d8673c26d42c3528c535f9609cb46a82d082fbcc93bb9f2a8bf9c4e3b3317d8e46e04acce846e4be815f5769d88ccdc4c18658dcd519f4ed0d3be28791a93281d308f156b50aeca73134c8b2d9c4e5f759730e5d3716e25c7b343ce028d5c1e2b37e3dd11e82aa17c7a10ceb03113efc854e6e0c1216a4d01ba21093eae7123eeda42cd790d0b446a4c123018d09f11e60ddeaa85223a59d58c50ee78b2862f2c65fbe516873d32649332ecc40eaed9c2f0f69d66de8a5be847d53f1a2cd33c34fd97018a8385cae10fd9006e166c98b1bc820f30629e236b8f014475ee9c1a2b6f8a19028cdb83a4b067e35c201dfc9fcd07b8308ab2afbd77cd7ebfb65306e084af515dd3c76484c10cd5a085d3cd0b56424e45c41714fd483579aebb441d08c733f45009f4867e8efb63ac37a5976cc4f4f5d7559233b2abb3b9e884d7047c9404cbb85c974c936e2084d0d44fe809e05180af52bab1dc428af43e44252d373871907258e4f38e548ace53112c9d98abca439ca8370f5ed37d9e0cb97e7318436db60f0faa3d23d0a74b3c1b45fe969eb64c07d18abb786438c87209ad87947fe0142855c9c80a7168ba79bfae1d4dbe3f71e1ba492f1ec4f33e6083f3e6c74b0ad9bc0bc6979e94fa74040093f329709f35838b0dfe88262fc7ae6350ca8f9a73d5fe4c75883aa0aad33d50ed1be6897bd1840f0cdccda00ddec32fc73e44bed61282dd14accf3a984b17257611e497ca921f0d215cd39928cb05de34a4fe1a5f6aa92cde88966d9802d54930b158a96fee900f75de306f4985658b3317b115cad6297c74cd838b58a93715280fa9fbeeaaf05a84d134ff307d1b13f9dd090c3a08615ae29289ecabed8c6fbb57f5bc36e14e3f46ea1156b9da9d6951dae38d65a3f3aea2808ef8677e483a45dfb4b6a165bdca6f92cfc545674a6101879df338c3cf4e779f93679fac4501246314cab89cd1e4032190a24349152b9b2c25487a95236f9a5a73d4790ee5f1123850b37eefb032f7b990a8c352ef1592de857a7ef743f16f6abade82eab248b3fd578bc75647e41446ac41c40ccf46074d10d6a8cf882e3a40b92a0c2e97d2de4229cecb628715f7b9c0a3b24b251c9d357c0216e07e76a1a1d6d1a3e68c891d64f3430f7591c6e9941f09ce589a3745f4a2b20ee7af4039143378124c61d2bf558350e24049e6c65a9e703e54360166298e9b0142506aecf475ac9902f6815e0baf9f7241b514746a1d06ec8145a9f37f64e1e177a5212955fbd7160c6e6ae70f6eb6d113198aebc1cfe1fc65abe593a469aa02c41ba23a8f4ca3fa220411821207cc02734f0ae72ddc2976427780084f6a283286c52ba3eebfe23266bc1bb6930e7df42fd58a1b6f5d24366375e099f178ec3169921e93b1e9e4b051706d0262473955648ade2c00b939106d816bd17a281289dd9f5e952816e80d824de4ba9ab3dce3f7cd88587c492b810dff9b8c5930911c974a88a9237c63a836920def5e045ad19d783cf6f128fd0ea0f6b3da89816406ca1f34930c6e086553727dd6ce917ce7f1d2ef8749f1d0a03b31016a4181981453b0a15785ece90dc8e25b62c5588f9e068a90b1c9aa03f3b5c0dcd7bf67d109b464afd9e5b62928e3b95ec18dfaa412f40b83c25668a7e101b384fe2bad2b0e742aaf5d63450772fa12ad9bff51eeca350c8ecf823135587790215372ba333efa907edeed72c6e5d9061ad14c9bb914d2a8260c1b5d7e1785b7afea5143c4fc6ca3b25258812b102413401fa9d0929c7a6f9ce4c06c8d68856dc76893a5ac743aa645000eedac4daecd658f8c50229c5f8396f7918afe40a49a3ed22598d5ba242494671166f8ad9249c79a783e58e13ad11b0add3568fe6586c13e9b96e2e66aca71e01d16e8bfc423c9aab6b37199af2fca87c1106316cf35d1702b872b0287a6ea80479bcc7560e0a11ba8ed57dc0802273eef743ae92ee5255c98890a551c755b78de932db4640bd08125a5dd0b77880a04b80acbccca4882bf4276b83f2aedbc6c774b014c1b5191d6adf122a2922122431c8eefef545ea6b86420b700aff81b5aa2878429f6e8ea4d94a862c846fcce523ba7e005a0c2888d05184360ed9547e7d4bf7d8e69f0d05667ae84304654b03e765377fec91dc9a41a299f52456bf8a3fed341ac40ecbc8e58f781b8d41bcf126866140ba6a127b3fa0348e858e3b719e06008802ceec47d8df2a3d40b7b80af81a2a60f05da3435e3498bb39852375bb61d1d8f3a1f54b0b67674e8c83f9419a1c75741d87fa602d0e9a8b03a143a4f95c33f07bfbccde80db3d7b5eb2473016c88cc343b44b0cdcf05f63b2648a692b9e7ec518a2eac0cefe9690a78a38f7878e03fd0b6de1702d0eb73fe17bfab01f5c4f2d251cfd62f21729eb76fbf7c3a1fee91f637dc83a03500456bd03c501d73ce56414942fd5ba348de0ac38b9a123eee50c8dc9f8a5596a1d122b62430b0cced7e57548a2e3d64e2a1d68e261a9e118d5ea6a2dcea2e96b6b586763e383e02a64df6ca6526ff76a2101316aa479cbaf4081023e8f1ead510213dba206daa723d0ecc29f6f4a184be0168552ca36cfd46b14637a836c25025122d31a71d25753a6f0180e259dd5ec520d89942962a01d7aa5ee7ffcb784fdafc759740ca1351420f2153d6b3f012381febe6d47f836f97986e017123d31b1e007305a0f3d9b538c609be274bcb5df008cda2cee4c21a8a6f5349ccd8667c9d5964042329bb42622dbe5319704bf1be48db5f8a690b8a6d2bf7cac1a835b4bd578c6addfbe60e89e9ff798bbfc3ee7bd918546937aaa1aa12124d3fe43239cf7941f86add97371f48ddb45eef27b8fb59f21b734301377fea5dec71a71d00b92edc690c3c7da6760fdd0880111a0ce145e75aea84092a3034fb3555d21c1866155779c4da3512d58238e4431193ee762125a0b4cee50eaca5fc298b4bda65d06cd4271dfe35f2cf5ef0b66a1c661c616f868cd06ff6bd0ca9e676b7bc3e5e68d04dd619fe7cff2c6387beee0e0ab5f3f562a29e6a947ebbcc208c95f52dd4d78d0d3f7ed4521dada0030d3aa9b431482d894124087d652e979cef383b64adb377d5a19521bf130dc905e17e281b17d6d7c2ee30bad36913927fea135092d8edcca67a2059ce58898615854224a0d41dd98e576a55b471f258a0d6b05c9ad6b0c240a54f1496d7129b739450b59badec872a1a0b75af868ea792e8d16f66c6c07e33f13fabd34efa8316ae57be3781265115ba0db81deb129a5df871e23ff3ff8983817a3a80abaf26a0dab347cb6c721ae84d53677415eb8f3db5c08254735a17091e9dca1c20bcfbe0a31ad8867229d64f853321f32cab2efdd7b0a2e55e42b1cb8485d569850af92ffd5451dd140082748f0b9a59d130fdd839332384a307668d5a59e4cd1e4fb83f68266d906b770a81fb4a5cfaa1639d3367c7446dd30c803b87fd15f88333586e1541a56e8ddc9ca7051c01c1f053c4a6cb0470f6f0b199b4c1ab248d24691fdaba419cc91dd8987b5e49b03855053468ecfbcf557051b9fc7309dc0bb3c0059098839a4408da505a7f7d1b3baa3e7b29966bfeeb3cf7f5ceb644608d5d3b3229069e43edb59d79a6731f7cb0ba7a78c7fe32fcfc49e42284c40333ae68c8d2f7f877ab47fa7121adac00a013696c10361f774204f785359ebc180094a29dec63c7525e4344f615058af123d0286c76d0360246a9cbb2f138e83eeaa543b45febfed02e3f19cde37c032681720354560ea15c3bde261ddf138a4062c5d9964ec6629e8c6f3c2f8a3a04e66012a85b4fbc924d36fd3bf4479e85e300ba63463cdfc229bd647eb24589481eea25dc96c7fe45eafd622fe8b00398317e7c45e936949f6c0d59629fc557d405e3da778617b3ffc837cdb83c12a52df3f935930401d2f2d191153a06db5415702138fd2169582d763ae356ab7d7ea9b93c71eef30fe2e5d6da100695d96fbab46977b5feaba01dfd225592b6ee2ad2cd07fd7978b34cc977b812380678344df4402c54305dd0d4c7926897c30f3cd0abee79f1a87465c0e4de9f561254e93204df8e6a2d64744be91222e3558beae09f5906365596d65189930700087dba06f8d6b933b9135a7bf26e80d2260ff6d82904cfa7a1559d9835d8b4cd6cc079dfe7dc7ba464dc9ab18df3fcf6143e2103a9bc0770928cba23026be7deae0b6102ad8f2db62390107b027b4ea740e6052fd314c31bfd909756be533f9c9408d3eef99dfaf060a2214d19c25311230ecf78eafe60b061db45162d86624167dfde126639780073a63133573ac9c3977695e63a03c1bdceed27f0a6d56714aa25924a4da3fe9d2a362ed91c669ffcc1202892cde4e50d38a5a6721b9005da4f49b0e6fae7180bad9e80d3f7ffccc16dd9a076caf260109bad41a041f9b6230899ea4f0ce045ff0eb5941ba99cb66860f0ef736d351c197ae27eb11afbbb4f364e89e05af36520f497a3a1ba70070937b22a27d2e77b0000d296ab002a04d5c057580c51559fd094ed0197e5027e9a059fb16f298101da0872f0088a68b31e73f04a1562b51ed4245968e8142a2e1b71f23e4a9bea2656ba23a4c5bf0b7120b36e2aef626e360b5a7841cf3a46853d510a4c36f1f372c7257a8729b9235a8b66c29c091eef315dd87237b8b412d1cfc98243ca568d45b731e7c2ed0b0175ecbc0694ff931eeae2aab364103b7bceed0f0d5944983fef3b4ea79a69161c7d55e59c038eabcb22347f083907e0548cef2b1fc44883df6c39f45957895946b99586094033713987bf66114e685de5581a320bfbf766c4461ce36412ed105cbede013c40545d30ff04d23df66525028218c5f6dd59b0122864a4af0c27286163995676a7db3e05c84776eed05dc0cdd9e86181910fe9e885dc2f9eacab74aef6a17515c01d5aa7ab59d53b4af12b933a26207bd8aeab570c19ef602b13a630c1c26706abcbd68977337a93f7ac5526d4f74f971a2512472dbca6365d2ce3eebfb056aba0d1947688fa12595641656b4c4ccb8f3e837652ba62987d857a0d174a0c406174e11f9017e2e910d3e84b82fa775047f924d0c7609421e0a6949b10e3538c880fbdf8841d94c40680256aa9ead5a53988f0a4faca276c25a50a901818d25a0c50ec4a780b8330a8e61849a93315890a1896ebd2f7d2a0dc7fce0a7cda6fa0ae84f83c7bee7cef28b316ff881d0a980970174b99b521780253462ac2cd87c863c26362b22b07f4e0280db8d5c468a33e2758af2c2d810b3f04ed0b9c7d9da1f0dd6b20c60dd5421026243ac66a483c7c057790c9ad1f25d358de0d56d2e5334cb384928371327f90cc43b374065c38d5c8f9f57dd97d1d91ba31ce9fe356be07c18367cef4de49b68f317fe180ceeb7ea84624aef5c2b50d62c8d78334e652a40055e283ea47405199ae2577eb9ff69641ae51bd7c3826ccd7c27ecef2c1ce98954dc5e88dc5fa5c0bb425e629ca3a6381b26d850067d66cc80f531d9034ef4181e5ecaad8c86da9e37422fa9a6b544644ae4c71b3c2028deb771bcfbcb36cebea1d501e2ec48f62d31136ce2bc1b7718690eb0c647dc2cb857d68bbc33cc06042ff7cf97f5a3a8bd7165079813eefc2a4bbb466173e373139bae1e7db5fea7cbab68db333f533bf2c6071f05f9b0e5e9fb6bc1bc1690f1df9ba627f35c35baaddb16f1f81b3b4f8fb5a177b4acfa1308f28d4fc7e2d7170d3ad77a129994f939430832520ee644c2139803002def3c9c488f15aa27aee329dd54633ddeab14848943518eff26c0db99724a4afd4dc3530f043e766c989cefb0e14604e5708bd1a0972a6347315c88e6aa649d5a1dead00fe3b52aadcee3e4444477286b37220e4227bb691adcc8da208d525351a3b8d9971550a5898533016fcce9557ecb4ffa1e3daa8963a8eb42f4a3ab9c8b40128289fd87df1f16470c053d436fd6b2565b96751b014b0eebc571ea5da873aeb828c03b36f38c2d0f7ab90c6025a00672ddc1316f305e829037378a1cc350c83d19aaecfa14154059b8fe8b42e284c23f84a317a7d224e561c4c37a5e74107faab6595aa858ec75662dcf2878e96cdc320cbc4ca9c44175d6bf2ff7c415258c7a215622012bcd5a11eefe0c403c534cc0da14c4393a9366410220d51644246e615873380dc0d49d61d999762f34f3769fe0a4ddcb1427b8550fd7e2812ec96172b4b9b64d949fc89acdda04cd748de6c09093e40d0c25db9724a8747feeb0af75125dfe46a118b2d5a48cc6be3c805c884eec8ab5f8c9a500440b9642f6fa84bb51cf8684d08298c5c734481fae2e0ac186d208ba7c5f098172792f7e4f42bcba7552702efafe7be305624b03a26eb42c60e8459681b221a48c613cdaa14a837d3916ec88307be8d420d3b191a240a64148dee506e5da482c6b3d3019367ec7d09ccc2a18e539120fa6db5c0af734acc267725ee45e6961af858cc1f099f5ac212a37525d7c28fd33a9a627eb8d321451246dd3c926ce5d7151fdbefdde5a0e5340036999374f2aced26d2101f31322731c1b7358c443627fc473fa8842c40e426a2674a994e74e59797ec7a1dc5a8d868459caa81bf832f5fd91cce4b6976531e7d589823c69a785ab6aab7706ae758bebc8194ca159df7ddf4e146a4a9f0c9f2b0527818b0d07983dd6c896a2f63241c642d472347f2b3dd04b2dad0a284eaf27cd37adb9f9b2f686cbcfb46a51558538702a6737d1ba44f0ee44403838ddb1d1f12fd03e85f04acf1373f90e0d9e2821fd575c1cf1a245912e2b965a07a2743d47b0adbe5a3fe1c05b2aed3398b51813e621110e00dd545640bb222cceb5c83267b1c041b4e94c274c848474a44480ec993d48c08a4fe80f50efc461fb51f1c0f499e34628fa9e2c287e10e6fb05bc54c34122d7cea071a8fe63493c1b34182783e2e2e28b3c2fb65fa3b47071d29e5e093f8f16c09d4543aad09629b052e0ec3534e95992f44b4c08458d197391eb364159a3638ec3352c160c5280305b6fc7bdc4785cf2c8b1749b15068ad430099749b067d52d416917c50b8e090344f2e132901dffc8cec2b0a443955caf93535a25bda2daf4b1b951454f4a7bcbd507a025e3be535f490bb0379e1d1ac26c6b1ed3e273736b56d16eb6aae597747fb56a9dabb3afbc21a23c696bd05c101875dd0fbb876295eb3bbc2b7600e03ad293c76c32d285637eea80804e6b224d663f2f7d651c902d60c8f79320fd0426583986f3517203e4375705d26019e771818cc72bf3453c771bf021b93ebe8e891919b47fb08c3467672dc6663b243fe8469d9d931b7bdd37f71e014884b973b7a72ca1b31ed059f7018fd3112b0a9cdda7d5f3beffdf048891c62a1ef2de81c7ae016f197462033928035919e8b9f3bd957b08548eb22d8b3361954c2f9fa4a6a21ebfe1c6ae9d42b8bd2cab71d061170720525a555d9ae3ea9ecae2b20aa56c5b6b88b5c3d3f50d7b376af36f4e2e7deaabbaacdd55c0c358475f1257d0a605e05f970571ddbb9e6b8b042e5345c26101222711ff4d02b389e9d67ca406f01690ade5e6a8643934ec97c4b9aef00bc1bf1dce831d46a191f665893a2f603258340e884b644eaaa4befebf53bad4b1f7c260d0a51d1a2f9befbf22f2d2fdb990059ec9245709575f26b9fbef3866ca44945b828dd4d1dae91680a5e580619a8a9770dd32805c52c56e3387847f60b8864b64b2ac2030c7eb24d5f840ab64e7773c17274f7d2ecaca91ec3a0536f0a1c2a3dfd97de028b87b301fa440c25951c0a9dd740f74df014b917b08a2c6875b9d6311ec440c1d24882bebc2780f2fd75a8a7cb51811564fc1b02749986ba614e2b3a38686bd925ae0eb47f27a0a4467dc719947a339fcf015f367dc1de52242340f2c295c308a6e1d9ca480d25826f494103474ac112462adb60b94bb81789737668384df8cbf56ff93fb49b0b64c922553dbbf643fee7a3edb8b49e0bd7c9300b9c232135c96c175f8cd985d7d0520b56fb2aa1dac8730ce4075cb94ac36b5dc8b5c63050d18b93dd158dfa0e3832f5854bccd8b9fb00cae082d8f12527848906a067314dfdca45b728e30a7c86852f6e6cc55feec782aea58f432e8ce0949c37e9d90e6fbdddfcce8a7d8099d8488bbcb58e9f804201015bb45f69677d32aa38626bb5d3363eadbe769c24430c2da32b2b6743523b1be20404ac670dd6433b8709d7c29a89cd2369de6528e6c54b82efa90f8ebe8b49a144b854e25f8087363711189b39c67ea1e094ad1ae531e5d76f7c901afdb71d64026ff9b0e70ff270bde66c9569dc1bd228326dcd79dcd5814c2c621f476d59f01e2b7cd359c15a007b1bdb675b8c790c0d31f9d9b93945bbd1d24465a0a9b05d49a68bc3536125d181b212c2e28a861a1161169eef41833e5945b075721087347b756b6c3e4f711d3680100335bf45144d01700fcfe1f6b77f7b98396dea6a355268acbc9ceedd6b3906f88bddc296446d14e3f94f9c47fd6d7828fe387b7a3ced6445ae36e858866e5a345177d2567b50c8089d56ea476b9b78768f4fc68a2c9df42f7c3581ce10a80b63ec6d82ba2b0cbc34b1f594e852a6ae83983328a06245e295b0b8d34250fe54216441201add4a77567ea97ca32c834c2dcd91d11c443f9968604940fc61469806bdd88fb5ab1528d9feac47074334c90fe54b739bb9771053744633d12daa97b27769bf2dbc3617110196d80b1124cffcb4b164264a6783b22446ad31ef8098d92981c54dd9e4254a32210d31770ff49354065243e8431e1a77f595bb29ca8bcf829dff00b39c1250698f2b01de9d9af31ed7f44404b1f21982e06cb30618676e41dda64a90ebb8fca6e43d4b293563cac312cfeffa0b49122b8fba0aaee3247c2b0c7d3db96368a3b2daff04f6888108f3b23b8220f1c824885c94df2ce49a8e93ac0504368164cdbe47a0962326bfa0bea69892f1dbebf74364e7b2a66671d7f31942ce83ca557998f79baa1be5289b65956d6ec47e77f1b48800d24baf89ca4a8a27d0d9d63596f06c20be5b0fe99b1696ed7ff1642dc71717f4acff6dacf13f38031f2c0ac794d00c5708838322ff3667333948513f595dce4f3a6ecb6916d36010c9089cf477c29468d6cfd4ab91a1ac9bf25db50e4962a1991053ae2fc55ca95b04829be0ea125d966449b72a5d5b2efded44e7aca74067c0986dc2f30eb0dfdfc6a8e440411bb7ab8a9c1ebf2ce7ca68da42a1f81e2554e24b9566620bb14c4c9b94b05cdb24945d70a54b959642064dbd17761831b086bf23fd815be16b196383570e71a7f29f5c1f45bc65af43c0030cfb3ef86ce2c693de236aa91eb2c331ceb1fcc5d164e807994e0b7612a4912e3f3c5b93d58610c51674698c7da2c4b677c3d8ea6cd53dae8c3fb6ea731a9f76064e817aadfb724b9dc7940ed6293dde5cb5093c740ac0a8cda1f605944b88f58b3b4a0a0c22c0695221d33b3e3fdaa532129c0ed29fd32b010d23dac4648d524375dfc3e5759bc99415c2a07fa31658dc1e6b3361da808bdc9083d1710582122f820cba2c43d88244b16576f46029e05b808fa53a26f6e49696e5baf9bac71512d6a69e041568890140cfc4e227cfd303c984ffcbb15bd5f447733b82da7a0fe1b41546536bb7d70c6c5ac7b8503f7d9ed05376e66bf2f1cb89cd53eb8e262d67de184fbacf6801337b3c79eabe95a838fa36436455f5198f53ef8c1a8e235106e4d91d2b89f6d9f7025b3576a0a9bdd074eb8cd760f38f1563f52c996dc03475ccd2c2c34857d813d53a064e2d47275a6389c7c69d654c69b29da8921b2c9396bc48de6e05e739d69c3f8e7083ffb2653e4d2c064a1ee26720573392007eb82f4895415e394992214ab5950ed2a41863f9e79f1ece3f3229d7cc733e57def9935c5d70920a74ee01211997d225292ee9e7d9667fa9e982742d0753f3969c34f140cb75695cd17d52acc1e6436fe5e1d1b14fe252260fe8c5981c167c950a0c7f352650b3efd1022da6c877bfe411f2f86d637c4cb0c986230c2da100185a140d646c4e11c4081b0771ce9916b1224f6c7669354125d3ec59122892dff99e964ad83d65d2520701d0b9c64ebf5d409fab4c37e4727f275b1e8e83e2885917fb7138df07448bf8177cd74fa6e131449ae8dc61c714db500cf8e555d2b7674860e99e3ddfe454709e77a9d0074d3c0b28d5c290c05a81d30f39f74c04b944d15e31854e995c8cf56e35e53425c8644edb23caa3d176ab6cffbbf5f6e2c8088e6c31ba05a30c58c0f41eedf3ce310c5a10870f4a92193805bc7e10a1acbbfbe9087bc7f96713624d4096ab898bba72ac31bc80c0195d9a2f4e2671b328e53decd3ace9b9b0a381d0e556a1d67dce61149f8c96ae65d9ee6e8f8ea27e15a7f0dbfa97299e6b497e1575639a8254a627533ed5ae85a72b9084fdbb6092db598c38dfa94c7db71f473553ea1df17fff8af2d40e70724446fa8d48e45b6d9adfa18f76c5f98f4d2b5577c6ff0847b3d59dd5b16f80c5d019cdcd296fcbfdcbce8d9aef2c22bd65e9083abadaf1dd4ec9347d987f3b4aaa3712279dd58e3924c4dd9a9b08d52cdfce88f0b425537e972d148411171adfff85ad71a105abef0386bc319e688907fc5f6b389669c89613090e2e80bd7e610390ee617af6a6074966d812642fe9eebfb147cd8fc58b3151e3a27b49e87cb93f71938ff984e4838f74a23d0041bdd8d61caac199eb3d28b61ae7e6d19be2081bb07b4799c1c00bf26c7a7f2f27196b865d747fa0becdbf035da94013a43c261aeea0e8d233ff77c302241019ef9bdf199c960d368a2dafb0bee4f5c7516f6e1c7fd1161c509122761c97f965e10940345694b574680d94368a1c4837e907a2e658969b3bf59e27f28aac4ac9a47692ef6097951a70d35863a55dfa9aed1fbe981f0711338d9e1dff59c672070e6380a1d01106b369b17a870e502f143af0ee7222fd021818fb61bd4dc83d0faebe59b981a23e57d59464882b62c2a41593f48c2fb33d2b356852175d8dc01f17097bac34bf41662a1152c553a80fa6fc4c303a27e8d25c612387f29ade21f76f499638ad7f5bb31f1576a9c9ed0380fb281968032e87aaa99571b6bad614dbf1d7152bf362b2b4cf7287d7ddb89fc83d5c2c71a7deb36fa3908a768664c8498e8348e0a566425f5fbe1d04027795ad5bdc9ea32f7d9d651844cb94e09c31897f34ca88825544a0a8a822cf4a12d4c812991eb99faeda06112633c53df9968cd74931823b0761c2bcb0689d35e870ee3e092065dda3a7f773b9a6751aab42adc14137dde2e5c9cb0c8b5c7024782869bc348b0f9548cdceee1ca8e8e7f3912703fa0f387f5c4aae985f9fba23471b094d4592010722b2701af8f67a07634e6481d59a818df7805b12262dfe1e3275005bf9308593974c3eb88b579759bfc7ae5e9c3d964f30d6f92174258e66b1f861095544131c92b3d2f62976001bd721f71091002c0ba853e9f2cf4eb11c3316389d37c3dbc5589516e5f6f16743d205539674241c9169d725df1a326a111aeb350b68c6bafa46f1766d621ab9f2069ff40419456682a5f31d9250418c6a1c434e291ac3279c03ea8c45768e7663bbf6053741f07df2d029e09d91469d98ab9e0fe0073607b6ff8145d1457f2ce99b4ddffa3b26e9962bf070deb16728ceb8e7a6e698fbb6200fbe9576e2116afbcdcfa3ae023ceccad1f764f37049503a188ff175d611629c212f4c311203c0e2438336e660e65d05c6d1a106c2cf3cc17d56441b74fbae0bc8c699578544805813649b103c100a95f8ffe07b568e100827d1c182c2754ea10342c5a0ad6b0502b0f4d0d70fdf484bdf3979d458a03853b440a7fa0f81c444f9287c69c3bc3a30f46672643c5252f48678302b02eef5a0b15ad183e1e6330f37a2c27a7677da456389be488c1d6d4cc94ebd18e1eb36a988d121db0505009a3eda427db21f98de1925ca977101c7c8f9d493accc7968901958ae0faaeec982f686c694caeeaee2ec8adbb373a6a1df043fadc600e25c62c1722b3421f9436e60d6f250b6e1d3ff450e861c4452609ee440c4bf02125be23e39d5e7402fa3bf0400f932e8dd96b3f77f54e4c78f8240f01fff25da04acdab81d49fe0a14a004e138583a15e9f03774395343d3d9715fd99ff60a89791aa238a4d58bd719cf59e8ad325cea1a48dba869653585cd4dcb55d44ac11954e983d0c30d1e7650df4c3c5928f0629cecd18917b5951ee9a7a432ba10a320afb4eecb577451d2deae4cbd83b3dc74da8b10a4da34e3215550c68eb6edea965493b48404a71b512c90d7c29e55ac9103f8d662ac4a8a25acbd416f6c449d7c5961f0aeded282977029a06b781fb35c1ce3e7399041d81b312053a2988ace98357a2ed4429e0eb59aa8efdc037401e07ad0f30cb4e6df7e6b59c43c70cb365286f8fe7c04724a331b78eeda118aa213c23987a01521462024c3ea0065241d69d144ac1386e6e094d141755a9a37e6f2e6bde1cb84a844a80662b7c6225b34bcdead32cd7b4dae63c6633e6fc1dac881449f080044eca275403b155bb2b5a939dbe814180001245bef27834d5cb39eb1ce9ea9f1ec5defb4b4c157846d62a4712ac40ab7e1421f8ededdc3c96e389744ca9e419cce6f652c8d38f2a8c0e39726ee0b0f8d0e2d7793de8a80f79769ec46a9b57acbd83b6289923a67efb34b3b6eafc542a6189a003fe83093e805f40915009388e1879b44a3cd4e0e11e918223cba31f102879dd427b59a5fe1ee1db306171d077ceb7e07be9c9a993a0aec9de07ea6cdb22ad24bcca1f8ecdc1ebc5599fc9d6b5920bdf2301453c81edebc541faa7a1bc254ed771196274e6cde5565fad333ef3467098cc02eb11087fa0ffb54b1d3088ca4984a945c09c266b73349dba39e7030d3f7e08d5966aac10f9dd900ce81a20e7793a65f721711ec84ce331f5194d6521cdf52ca9bde54a6d05dfdeba87cd8f2ca9a6cc2a344c9c19c926785a233a123d539362831d9c0737007c8bb7ac328693717125cb024b764c97a6457fb0fbb5e4544cf1eac0c3e57c32800f43d8e460eab407c8bacf1f40fc09d9023c32dc1cdc889ea0a86900bd118eb859b7de912a2ea813bd1ef851b914c1b3a977b1ea3a7936577a6ac076fad336937322e5e977b97b612ec4d6bd2fce0ecb1dd81a0abe0ea2c101f13f4d49801102342f591333e166bd4df8a05441aed5b4d5e7122c53116d5c88d6ad4426afb93a35bbe7df4b188832a480a81e21fec59b263a3d81ca1eeb922d65a1d7ca89ee7d3755a4e0d29bb2e3d970ee8ee80ef3902cb0a5678417e4ca82e2821847e9174021589697f70ca38a0151cd015a526e4b2807b0e31b70c6423255fc4bb3a923e8ecd158b2d53c7ef36c631a06b6b40875c02e9c1c4929e1ab16e268aa360dafc6fa29f8e2f03b7afd94a23d810bc3ec23a540a94243aa115eb1f3fb67431da9025184bbd9bb1aa1afbff9552679171f5a0ffcd012f8454888c3c812b06898c358519ea0a14616baf9764852659999b0e7e83bb9ac94ecd7306f332c0690f40b1d73f5f6a00acda766903fd115e311134d3dd28b0cf303c16fd0467aa1b1a0db8e3b70277f8954507362874685b8564d80e01ed28880ee3636e61f709169b0812915517ff052dff463c18c26d0ebcca5f314b722b8c0490cefdd46ac93e62637781bef8e5cf8839d69e222d3b0661000046ac12bc8d0302d24cfb8b55cb45e8e9807a89ced9c5b95b1d39bd454af076dd2e57c6acf24842ece4ef90aa0564792b31a7f45f1d411bbed3757479aa4362025f0f4137f1b108b84ce17890127bb60f22e1b0ba4f6edf422cbf6a46877aea3fd4c82c929bc015feb78a2f171e815e8c0d9dc2f72006d0956ce3ace9270d29da23212e87c576baf8905c88b8c86789408b3117d25a7219bd96ac588a5ebb9d30835a272b514a1a888d2858259f38cef745c6495d3248b21502f0c42f4b52dafde246d3e01ae19074366b4d6e6ceaf1376933bdadca019c1baa9f14a63524581dc78b1f6d92578b5ff58c533b2b62a8acb52446b5cb3f48a229d2e7a234891a55ca05f045c9b12ca2a1923152b60a895941474e9a9778da3a308c7825d9cd486cf1e83af48067e2c7d18a03aa1be717008c93fcdfac962e7725905a360a50053213990d8caa11202767bb8962b162204242a22e254cb2d8b9aaa0bad36b3825edf0ba79e6a464e76a1d33dc8893e97cb5d7f9c03a8f4931055ab051ba11fc179a2af418871b52a822b6f82a67fc50fbad1aaca73f6bfdc0db79fb759e892f7257cf16255c660a087e57f0c71c2d8f02dd26f5c6d0d5f82b4ad82123a02cd179c5d6b98c0e459403c05b1e68bf88e3415d09fdb2f9dd5d5af8bbc542b0ad90cc625b4705ac91315ae07f7bd6e110fd4259cdb615a1a90656107468169698530a384669754c78ce5bf57f9fd7a870668c23d6190e6acc09cd5d23093f1ccf8d397ea67fcaeb607dab2f384bf13299266aebc4f8d6f15b445b393d804032174260a57bb5b06d9847fc299fe56b716e4765028c2314345763c476400be367031edcb3aa1b9445fb4cadb2ff2acb3e13c680fd11323af102b4bc60516c476b6915c9d5b092b8e3d4d6a98a83aca0561f83f6e0c60d88f4ce473b66968e485924bd6a1ac8546bf75306b20a1817ef10cd0423437db411f1c5ad5050c68a4b66ec358ef02a240d9962ab3423a2352cb7bb3c52255ef1a4e155d37ec4e597884c285ae4667f73b1914f1e8f3f9eddc67b9c98ae9f31afb0ae5007ece6533c00ad536b05e80c3142d64b376814aebb79d85242902328cf0d58e48b85e7f930937834cc64e1454d14861fec4d30876cfbb089805a72f4fc1c8db29ff6b47e2b3c415cd8e86423d3cab991c6b8d00298d3afa27a5f73bc2245412a479b8cc5c1ff10d8e6861da7db4c3c70cb62f16781c089a16febc2e71dc530e1b27358dd23c6a959ed8dbb7562825b96d07a49c21c9e0718ded5584dc5b369ad7e5119b31384a065cb610540c88f1d5d55bd0e465c35c121b66cb29f8fbf0d9276abaa1e0b6489cb0fd49668c990f7ebb15717baba6367cd944573987e78fd2eabc2f45003008e0447489b046c1f68dfa04c53713f8560153bb89bdfe48f900faa6a8648e8f80b390f2b29a5fe3568cf4df7d748e42ff52856c1651d115377f2dfe47011eac51da9c86b99cdd29c00cd432c14095a3fdd1362776b4d276602bb3829dcb4f645b72a4a9cd194c68da205342db3c09f01d8d3f73f0fded3e84bf9862ec1091a0a672f811a22affb0fde647a74b841057fcdca65eb79349c883f1972c902f236e21771492d2d8a0c26b0cb4af1e3688d207f328c20108176432243c0a5ae3ca4ac3e4f59f79cf242e4262c9e0c17a727777eaa230c2139f4e31f819c84adc7954d449d858d9dd1dab119b4eb499e5c483165267d70b6a946154e1d311a04aa2ec75f2d410a29fdb65d99d46e1808b9f5a04d0cd33a773a105fa29b3c0d2290c945d5e37f5e49aa1781e57bdb3d99f2196172c3c8d6a7af17b057dec786ce8635d82d418a9b1b7f4775207f9024299d7f7fec6ae98684635b7551abf24b18724b8aac065f16cb2ec58556deeda5984d09152628f76c6a26422e168582ea7b402d5e31408af4e59de2666839a32c3d40e514823e244c084ee13bdbeb4c5ed8fd96365bfe805da29e0633cb1ab464d556d8b50bf7be02af06af60df72f408240dcde94f82329e502ef27d263613767344fad7a75d8916b28ad46d1cb49bb49ff1c4619e631111ace6851a00b9e867be13ca61ac0e068785a89f74e547d8fcfa45773294ce712bba64d8094303411010cc94290e4ae993f5998c919aa21130efaf373e8231db827021e40ce33df80cc21d999e82526efe3fe257dfaafb4564ad07abd3dd338d055728ee24d6a7cece41241983bba9c51518965f2704376fc466316aaa5d291b3cefb91dbbbb996d670802995604607438ff4820ca2a246a0a483939f9364ebea9bc80ec6a5e662dc909086a8512787e5048b296e7cdb63b037b5690b7d74db44dd383edc370704aa02ba1eb2685e505815271d4985942aabd2fd650cd421c9df55d618cbffc1b5e1d35c1266c4f6e8cf611b7c4658be5876f5fe1e8487007ec401d73f7891f387bb56956530596ab0b3ecd4e940b490cddc519345d45beb2af8ab258410228dec4df6de72ef56064806ac069367fa34aa6134fb5064bb15694a44a0cdd21968969802354bb3340b0894851765e3b17dda1704746fcb911de4c3b337b2cf87f6c916c92259fa44ea03a174a627db5bda8ad71b1d8cd19aa815d097c9d0e4ec9142b2a5421ab4317e54c6e4994ecc27cc9ee9435da8103aa4411c13357f5ab265c9face39afcd89633f63ce3c7fa32a9ee2a9669f261b8f79e7f5fb6bce6ccb3eb7ecbb27cd8684f02e9a9892d93e5e1fe4f9855a7d8a9766fbe8b72dc79b3cd1645b666827d0af1664fb30468cf135a22ed6d501fb821f31428147440e2f11c838e5c41a604044c9d8434cddafb058de6c98fa0b4273ff47f7fe3c4db4f25d974b0b4b4897ec8fcc3092edc3159048b6a794524aa9a4b4fe4a4a4b37defdcdaf6c5f8d342a76a5e9d30f99b5cf645b65b2ad49b2ad47b265d59a6cbffae2e58cea4eb671c6d69f2dbb60b6675f1099b52fbccf9ec1d040f84a7221b98e5c17be5ca2c91ec31708a2c97ec35708a48a107c0d0972f3fc621ac7445d44ba64dfba349e0d8e4eca5544b6c79b8dce1988299beda94f9f66626a8e7a5aac2555bef08794ed3fba8cbbe106190dca1b1a94b141ca93eda90ecda8b562df342665d4c23bc94d4f90edc32df76b44d113c49205bd5317a681dcb17331633a48106e1ccac8b7971925b1207bda6a75b4a5615dbf6c3ab4635f109939d607fac2eaa3fb1a46c3f183cb111bf95344a6755a606aa60f257271bff0d87ebf203267bf361bd9b52b67b846ffe28b75b5d87f34f25e3f2f986cdf2f9b8efab51109e36a89267b8a2f96064097bff7915dabd1306de405f442baf62a38b97dc3740052dc9ec3588a0f6cfb38298bbad0b4a0b1b0d9e8cfc7f6f54df6b50da826a4b949f7da0784ce1ed29d3cfd32b52bf250466ded4a973ac66b8d78ecb3939681f068a22e96b5d65a0bb2b760c6ae0ef9f90591f97e5e83f63d6f2f1eae8194a00a3b78e209254f709fdc27f779797924cff77fe0653c985c917db871f36d970669c442b97a3f5ed8f31e742a756689de66df3ecaf16ee6ea62df6af5c30fb45b3a269cab986ec5549db595c8f4dbb6639eab55abb3d21b511dd3f2bc917cfb907f8e39942d2fb2fc5a4816c36964ce603af0c2d9833d9c39d32726d7f6992e3a584e9fb41dce8998eabc77dd351c7add376d304cb369905e9e4677e3dccc154b976c56397889a79f2c8d0a9d3e8b7886f034d2a58ee9dcdb8d6973d327ecc370fad4539b2f3124595863efd03ba786be988e99ab9fe93373664facb1b5fe696627e7a64693c951ca541a693025d3c903cfc7a43ee49be58198d2ce9dfbb4cfdb78f4651ffbec6950f6fce68f8f17b64fa6f4f4b3a74f24f99092a4ec997a4a442cd1a3e01e91e95f5e90e99b06995ea67732a5f46addd22797889d666c3e49a6f41a9630546ee02f568c421ecbb64ec73448bfe196d199a8a6b3e2ded1f1a691eca0d34f23624a3b0d270cfed16529f395f21a8685f73704438924d357d64e9f405f8c8e99a8f9d2257a3a5fbcd17cf9ed984c7b76385fee7cb10dd27bf35dede84f1d74f041ca315f3f5faf83d6226bcbd54d908964d3a8010954e02931f2f5ec1acd7e0e38a27a441b72284329d35a317dc437ea69bdaca7312b8d09815fef71454c7108fa9a524a29a535405146a6322e2ae715af0ca28f4aa86073dd8e68057ed58941bf8e02023d5f18e81908fbaeab3c7a49ad40b3040f2dc96711df88190bb37ca373b4e18019d9078e1939da833e7fc5527c668222f418bdd87de278318da6781af38db59b601438f446cfd93b1cba90b30f888c98fb50fec38c3c3fb0c70e7235238da6fb8959b4bc50b258240e2c48fc985658e58eafa18c899b0d0cfac2941c57f3f3f2bc67815506e2fe9012d3438ecf6e0c1ae5a4341e05013b4c94bc97156d48a07910f788516d72ad9737f532a6ce8052fa14cac8135b9cc8d8226a0790168fd2600a8b17bf94ace48487a4ca473c2ff18df8193f3f3fda60f76ba4124992f8cd17583cd0491da324f5d7a3c12ab3dc21bc291e8e547cb14b275fecd20d3234190dde4943ef9a325e7d7da105f2fcb42bf2c2104c41d97680b9c6bdf7de5b818bb51b8a8f4aa6709377c0a206edd79321d0b36b144228f128a594524a8fd0aab4d6d75a6bad94d2955a6bad95d6fa5a6b3dadb5d6fa5a6badb52261b68fcf0e3e3c999e877eee6cca29cce410cc4aa62093e9293d18a3374951b040490962947a4a78f3f431fa145e7a90be07ce3cc292254b962c59b264c992254b962c599203b3a7c7c3b9c3a300d76aa20ef0d12206f87a7c231e5f01be2a7aa81d7b86615a841661054f050538982910ed3f200084e6901699a8027cf2a54bf5d743f185dea8fb68117ad06f34d12fb41c068266ed936692b0eddaf6499afc436659134df517ff903c1963515282571f73488bd0227d02f5e9a54f1174d1c6c344ca8cdd5b7b149b8e7b7b77dc5b12103a894420883c87d887b61af61a0e83c876cae47ab10f33a35a79103302e58a16af6c7b9a6dbfe6681982233c410a4dbbb6b6f190dd12a75b87a729b517882a31b56ac2ea5ecf1b8d6ecb58e45ebbc4bb802c82c6284990e92f24d94a4ff6132fb9258b5ccd90a8c109d62fd6d0a841f95e11c16271606e9c9675ddb6cdacd3581ca76d1b97711cc7655cc771dd31eee3382ec3b287ddb36d7bc6bdc3346ed3b09a2fa63c1487cc1c7765e5b699751a4be3b2adebb68ccbb88cd3ae245dddebe564e56aada00ae2b2db6cc3f419eedc982a11537127a4349b8ee7cadd7becbea39cc6691df77befe5de94cbbe65df34f9c9cd6b97bb5dd669bff7765e77b1aebbc7befb8ea3db9cb2cbb44a2b474120109761da71f7d3eec6719f5a97659d9669d9b5675a0a8e1f5c8ed8c89f2232add30253337d28914e9bdcc67dd933ab6db85ee2fa0cd39f5c94755dd7a1300979d889c8044517f2b0aefb82709327a6b4e9c4ec993e54081d42e906388de3b8cf47a6619bb36e6edc5de1b6544cad701ba7715adc384dd3348d8ba9ecdb404c7130946e80e3b2adb2b24de3362ee3b699751a6beb384debb62ccbb22ddbb62dcb3a2cdbb0ec5b876199a671dc330d5f99db64cd64dd00f78ccbb4142a6edf6d2a52685cc7e16f985ec33567180899753cd7770d440e322a6d1368c7eff38b1d8e17efe97798a8fb4996da026537bb17e479d8c7723f4aaf5716fbb1449a258db2b9b0eb07439d30270c9dd365ce39e79c73ce39e79c73ce39e7acf3ded08046c9213d56a25c922e595cca8ffa345291e2272293a3085d7697521e870e3d10b043a33e4d6e3ce4c4e1e22deee2cba141392790169ff2c5b3daa7f649dfb556d904cffe46fae21ac64eb18b577c10e81cdede61ee1eee4237c1282ec2263fc1a2a748f11156f11578d402ffb0c00f79857bbd110a7e711246398b6758e60fb320e1824587e3e9b4c5de2b6b1be60f3b896c67ba649ffd7a331998b32f6c1630b7c0316b2b7e40203bb6f1d03ea7f6155a8b162f70d7d8dcb8c0b388972ed9cb06c4524c51dbd6b6c098d6e26614dc35b86d70df344ee7a8c0ad9302f7ce096e11eefe992b3c591ab4eff064cd96065d84cc21441afcf02ca24bf61a9e466ef40ce83547c6742b46b696ded21cc668a2946e3dd3204d07bc50e6ec3021d7778ed4913bb5566f925a44842ca684d78c468d50a3d68c2835100ac72c592ca6b0c783a46aeb470e3ba5d7d5574bece953df3ede203463ffd1652cbb33b22cfbcd7046f7de3b59b79bb8ddc4ed266e37715f23aa9bb8daefcd361e5c9675e7de65daed1cb0e59365f4d33dbd93ed35cdc3b4af6bb2b9f1c8beb601bd6f1ab4ade1c8ae2bcb70cc3646323093f429669b3b39f3a626db996c1feda7cbbd3df88f469e375db27d4cc1f183cb91eed6e9ee6e71d8475bda77d09781b49a06b383be20326fb75b8ed62eedf2c294c8f469b067f234681b8784f0c28b75b95c20c8f6574b6f3a409f8d2f6d459accf3b26cf698106990ce79ee1808d4df3eb377f7fbd8fccd7ebffd6ebfd9bb5ed2ed9976ec8921f9851c95506127afc08b3a2001916c068d62ee17a6cf7a1a94f26edbbca1c1e6b11f59ac5655ccd9c41b8ffe6e9813eb30ee8dbd3f179b8e79ee0b2273f785ddb7b7d878cc63d9e9b4e52865ea0dc1c33dd3340d5a1cf62f7d4434d9376e23a28a7dc5cde1de70b340aad863b891682394bed874c84fb24020d01017d68a67f2803008049a3c9307e402949dc3f5a0962ed91fb9583356ec4b372cf79bdb67109ae77f74395eeb5f38dd331a255d70ee0ee56eeedf7834d87d5d1db06f898086081b1ca993e272c446fe1491699d16989ae9438970b8c55623932cf393ac1074d912235dc07f34f2bcd92f3159bab8481797f930fbbc2669d3d1cfbeb0fb4299adc6e1fa79b1e45b749383308683dc4cbfe1205ea6cffa067bd84c64d2c603f4cea4d6357dc23aa662b667b1e900bd3f7d84e62fd644f58bc76d17e1098568b237c1d389a8628f024b8c47de17dedb7d5b1f2459546c3afad8e4e9977e89a69ed605a3e982962ee5508b275f2e24b344236746325e122447b02b5f2aabc14ad20e84b6d423b9dedb6d1a76d55bafd5a66157adb7d65a2b928aa422a9489080e09c586dc1adac58aab5031175c1802fc8b51e31428147048c08648c79a92d7d225dd7f50b5bdd3b64c807ca9714359451c8f5b2d5b54a9e0b471ec9552639c288a8ba7ea4ca4b4c955e6fbb37dbda87d993abb88a5f3813d5adf9235595c58b2cd7cf05b382b1a391e7ddeb7919a599da0eac03d6810980c3bbcaf539300e7c03dbc035700e780018079c310d2c001c000c007c0356e1177068634e70686710da1884d605372e1009030e690a727d0c1c522372bd098714895c1f43bd0a0e290c72fd0a0ee90c72bd0c38a435c8f533e090de20d7d38043aa24d7cbc0215522d7d78043ba44ae3fe190fa20d7a370488190eb67e0900a21d7db8043ca44ae4fe1903691eb411c5224e47a1770489590eb5bc02ce0903a91eb61e09046814e21d72ae40ae334c3c00fa97c69cd9b2b8b545db40a51659324b9c24f4c6553882a1b13f344aea7f5340a516565a44abdcc925c4f7b620af47aea44a550c815634254d9991927ecc4d4a584a8b234344bd089290d0951656b6a929013535d1351656d6c8e90abc74454d99b9b22e41a124254591c1c22e46a0284a8b2393941c8f5b426a644afa73e882aab2355ea757e90eb6889a8b23b4a4495e5511255b6e70651657d58bc925e515e4f6362cac5777ac0b3835c73907d7090eb690da2cafecc20aaee0a065175599288aacb922a4962eac5eb5f4f8fc4548bd75398985af11f1bac68c02283cc7a414ce1d75314201155b745aa5423318542255952a56eafa729882abb922a959e20aa2a4baad4b720d323725da920d7672df00b5f815b1c055ef16d3bc6f7192efdc3190c30c4886132c510838acaca8a0c32cc30030d34c89051430da7130a3563860d36a440175a608185e317fe02ff2ef08ba360172c2ec2a39b60d143d8e4de350d84b1634cff61d0e58b7db5b7b7bf5630c070ad92c4889124c6648a9189210699968a4a6b66656586460619686a6698a1c686061a6c6e64c8b8c1a9a1069c9cd329470785d2d999316327d75b1e1b6c78d8f5b627e503bedefeb8b06ae1f5978585a70e5a1f177e720b2b165872fd65a5c0f8f0eb2b2b8505e3df5978857158b3fd87432cdbc3c0e196ed53300b383cc9f62d601770d822db8338854316b2bd0d78060e4dd91e854319b2fd09873564fb1a706843b697814300647b1aec67c0615492ed65c0615422dbafe0302e91ed5570187d90ed63c0261c462164fb18388c4c647b1870189bc8d6de68b247b1027bd164df028ba2c9fe053c8a26fb639468b27f811f4df62e704a34d9a360309aec49384634d9b3c02bd1643fc23266dc104df6214c239aec3d9c030e1da2c9fec23d4078071ea2c9be847b88261bb11388058e5660a3d6cf4c56c951c9133639bbb4cc522ba913c0e3d18a908d95b2501b80c7a31573482cd5ec71121151bd8358aa870183443231e92cafb424cd03a1178b8ecb48ae2f31c10be79022a7d0ae724c699d910ea1f848a6759274955f782beb7ade68f4574148067354f2b393b3d831b2a675f13dce9416c0150f5db8e8e1c824a43bd843ca845c4f63264803e3e18cfae2e1e807bff0baf085b7852ff4bc940fc6178ebe6f0e9943ee2d0b64d5ff454b9bb6ab2f045ba0cc7021877635b2ab192461249aea33cc6a2191eb2b0bcccf7b4639b9bec6094b5cc9a8065e7deb25e4018f3ec9cd4f929c92a3929f2199523a736f9f68e5149a8a2dc7cc59f77bad2e16fa7263ca88986a41ec23e22993aadb52a40e691bb40c726de9178d521aca971dd9e99c94ce8d448f49d8ef69013ed2c3793373e6cee80be78fe8f7d91591eebdc93d98b18b2e8672492c568c2686596a2fe9de25cbbefeb648957469c99b76c3bb8e61d8439748c35d961ae634bc6958d370a6619086310d5f0d5b0d5f1aae1aa61a9e59c3ad6199bf180daedcf8d0dd2a422d5ecc31d7afd49a3813656ec8b546fb8cda0fb469d8cd72785dab9713ec25d64b239b0bf1279b8d6ca465b7bdd9a039997e61ca0f29d9cb17eeb224124da077f9c29ccf85b79a2d5f5883d1f285b3d992e50b837440f9c2980c962f7cbbc8cd17b63f974d3d52f385698e2610cd179ef98768029d46e39829d1043a912d4729d7642cb61ab4488345843a70f27a091353295e2f938829eef554080c44ba92aa392355eaabcf0872bdf4037366952bcaa623bba5b7945a7a91680afc4392e8edb16714936a485a9d35157ba1b95613458b74a9fe5e4ffb4ebf10a42fa2651c16da76d06bde96835e2391e85753138462efb61ad847a29f0e50d218b9f2a5412338a4f5b3a6510778953f06781831a2622c8571499657c8f2a1178e1e104bf2f6dba1c159b9bad58040839a74094db29cf30a3994794eb9edc06e2746e3ce7943e4455303fa44ca71730d65ae3cf4c0a274054f5e21b74b4a839a7c4602dd850492421a6c22a6640d264e867b34985dc30fa3aa8b902af2d7a5bc4c1253a0dfeb79a3112679e6bd9e1767172155d245fbc296dc7b640cfb82c0617bc4c0f60a0481c3768a4317a95a2263973dc89d41589099553081ed31cf1edbb71dc318f689430aacb2fd9c19080287384bb206596a7d8a1567f2b0caf18d796890e2cc20b4860185214030c410430c018221401082107020ca96f641b22295cdcd1a1d9bc6e62625e5a014e545afd278dd46c8b2e8f12b3466a986bc1ea44ad54e54dda30ce18689a6233536366753bb3866a11ada3bfaeb793d904b4495199a50e8d9c530afc60b65cb4617a9e06834e79c55b6224e4bcda7c8147c62ca0b4f2eba167aecd349c827e413f209ddc73b8d0edd0b79a150e8f1a3a2df066b0f96d8f14e60a6dc018fd045cfa49006ef0c2430b5eb42ef42ef42f69de854742af286e0a14091354cc3c0843e5b47f4b09590eb3ba771fa24ca1aa7036d3a445de84e1f6fd594d5d4a5e96663e6133c7366a2d13e853a6deef40905cff5460f0981e3855da48bc406bb562b66b644d8ebbaf6f2ccdb76c3c76db06edb8dd027fa4a4c78a2779f48345bad295b53b65088be4e0ad1709cd192ad99eb670ccc6cd5702a916342ef62626ad311fa687761ba1ba6fbc2a82342b1d238383831316573fde4e993e8f553e6cef4ec70c6c464214dc3354287b9dd33149a8665bfa093682431736a58e6cce67b63661fa22974cde6420be4d04751295e43f79e02cb58ea34ef2bbaee219665a87b085ff1a08d877df78669d164179b5c2f0289b61c5e76a2754edf64275a8783dc1cba3751b386a6cb6c31c193a5d3e04e97a4aa9b88e9e9234423e47af275a8c58b3d6362ea8aa9a9d3a86e1e9fbc62358ccb8c4971926f566ac0995ca74c34d5cf980fc4842e7aec93e80baf8f096e982348640bcb992bcb1d4453bd5422aad4d398a84ed25ee89ee763b61aac325f8909afca3c63fa44ea0e84e6b04f28ea6cf5a9611aac2928394260606a6612371ea53c43427892a66a76e3213a6dc9993ed16bd3213afdc9c917fa383910321fa17b7d371155ddabc9eb27ab751a27a642afde611a6684937f5627b866efe4f5343298987b4f7ef2939f3c0433112e31e1d5ebd8e7e7430404084fc332cb9f3edd8f74afbd92ee773265be196382634e18737c88a67a8b97a084869931132585b072c3f814e11919c59ccc96f1a472dc69e6b6af01d1542fb3948929d06703fb80903994e600832ca6cc99c4beeddb1e778b76bb747777777777777777777777777777bfc5b424162b462a529c884c5084bc8edbb40c845df86a1b0fd98d72172813039947b91e2b9441ed9c9f735e73b68c9d233abad7c52fee5fb885a6d9a3dceb55127631c22b4e599068710d1c073c5fa69196f1921cc9f5c5c319855c653c6f346a997fe16ca13c6c997f2198b31196f905cab78dc768743a3a1d8d5c9058ac9844a8052273385f22a657360abe9976ab6770d3744ddba8c07d9302374ee7b44ef3744ffbf4cf5c4d96c9c2b3054f970685608c27915944cbb878fdd927f96ac48b6ddcc34435a07b946bd75b6cdb21b79669b0355f24ab6560aa7d3893c82f7db25fac1b0ffb9e2f46a20e6bed376445f7755dd405a54731437060080e7472477f346a6de4695205fd4c7a3ff0425a841689a64a63962f51a53ec543b177c59b81ba565db2aba8425d6c29d621b9d63320aaac0ba44ab5ef610151656310835ced79504054d919ec908018848cddde00f63d62eabafd0ecdb65a35620eb27d0e31e5dd3e876e0f0001646b6f83e8f635a0b2bd0c1968c8f62b31c5224725db9ba2cae6e8c090ed4b516575ec59882abb638fa3caf2c48829d2edc19842b97d4a4cb978d1f322dbb7882adb63bf22aaac8f8a6c7f1255f627145577851253bffd28a65adc5e14532b6eefc5148adb2daa2e8b3d165597655fa3eab6cc6c5951a5de5e8ba9edf637a6620a3fe5bdf1b0d4d24f2a5f6ce4b8c7789222c5637ce185c7a8523dc61b6e788c0000c0630c40001ea30004f01869d0788c393f461c70788c0318c063cc21871a8fb1868dc768e3c663bcf11871e0c841001d3aa878d879c763ccf11809f018753c461d1e557cc74b3803e1ef180669f87a8735131c1a61d159e0d14998c55130e92e30ca5f60173f7ef116f829187f070e25ab85c572573f3e3d3c3b3a393837363534332d9998243388419855a8e40c7785450dd5cc0c00000028e31400003014080584c2d178204a439d8e0f14800c7fae54684c1b49b42cc7711042861063000000000000000000c80c6d00c7522080af9225bc55081bad444de53926bbaf896c9734692cbde52adf29c99825e52ff9a8f0c8d11ba235a77af1f2e495527249975119b6e76996c8c314c710069c6a7d41c9913a7521a2612298e9c81c184ec25b3b98e86999150846a43e1b7efd009d93c5734befcc2c61bffe6159ee9e70d49aba17ba64cb79c66441b241cdb77290418a3f840d731aab8fe6e879caa2008fe7944a1ab90dbb80fd89b5df53eb4f1d099ea5938f64e39f372016afef4d293b932297b9be825785bb83d48b66711448dcf4a1af1eafd7442679647db550f4266015e72515888d4fb8d9cdbeba9e9763af683f671ac2be641de938c62d930d59dfd98a45e910f9a3930bf0053d866b893434bca9ccb1bde903360bf3e82e82b824e53d61b2c5b58dfa7231379d67e84716b590e56e7354ca5f8a2fd037cca2c4f3b6a9f35f8adc18a8675322c738868cc04692c787bd453c9f16a87b9b026aadea6a6b23fc200669af31436c1d6c285006b561725271311a859be0ba9c073846144115a74d3b06a78d5a78ac25b072dff49e0eb999f2ff51763141a8b9bd1c8e117bc5ae9ee1aeb195a3ddad48af5970a767bdad827b49e8a19cdeb87b0bf206716f2f65fa63f9a6259f133b30795620c6c1ef729aa80e58528149064271aeb7945d4825ba7af3032095237c9c3951d8fd427cfd62e944b41a1f780ec8cc0fd1ee4752afeef5f2abfc95886e3fff082c3f4b06e485490dd9ef2127a16681e0a015e8c70e767dbb09e99694c305250cccf11ad1659fff79267806837802f8ba86dd03bf553ebcc9c28778f67c0bbd90466082688365fc8e936fa3f48b9c5de486fbf0f2011371cb8e2ee57a54765dae27adc031d9dac670e436baf4895c5d7e7bbc5245a482ddbb09c7685bbb4cd19ea2f78317d1a34f53df033e7cfbc7b68c1a9ecb74a9f189be279d30007a04254ea07234df599c8a0d569d18ca8a6d82a1f1f5aad8bbb38d082cb3fff115640f0b590ab67cf33f7204f62e470eed562470509d9e936cdbab663fe986e95553407bc433217bcb1fab837a1ee61a9288497712abf743c04a02d33fcf00925d3c00a138be9e081be50f30f68b8f3d04566f2b28ef2a46d910bfbbfa86e061b045cc962ed23273c070f7d46285e16a177f4ae3feb0890b1e0cdffff05dca16daba9f5aa936f0cb6f3537c143121b9c7520f98dcba215b34cf3a17772c0c20196bac29644385d294a934a83c94c766c56242faede4c30a270aa6e304fdf4882a94807b2c1cec983d7c5741ba63758b8e82db24063bf1b0b20e09cdde0547b87445109e28ed80cbceb1a2917686ac28ddb8615614170e4140ee90216cba457c026d6b129121d7cfbe789714931edbeea4266ac6235730c3932d3bf468492b71dda0b48563811a90b8920cc08517c760cc0fba91094729e22035a15cb308c40a42765f0faa9f03caf8f91ebc2e7794ab666e85b33b73c97b14e3a59b27e089e3b63634ac251a1c0e414bc57cb63308a8fd48210317b92d286a4a8a624ff89b6ffe5dbe11cfd5dc8d14793ff7b5abc46b76f246f4a952e079f50b7c1b6e913e9e31dd2be57da1d9c1046791d178865269ced199878f6738bad76cd99591ea76ddca7880fd669394a0ec4e9bb637c3028a3743b683dcb033cd288711004e120a6557bf77950d050f6a12613cb69d2c0d3bd064fbc754746f006a09c36aa1de203785470aa0307a2f5bdd06b2d408866b045cae72c96ee24884d6c4cc454919584ba6f38b7997e4d8e488fd9d2e8edb4f6900d39991c629a664a1061fc38c49ca3e308aa599d2328f383fc4ff316394a2f29ebc7379e4984c34fac91ef541fd57f5420b2ff9bd731535b861769e589ae277c28eb2c1bf9a7e9b27c847a29ee38992af10cdc59735a885c7a1b3d9539f22101ffde1a84bf15c20f78e00699e7057454cf0876e9edaf37a59b26456ea2daa718696d3987560bdc00073fdd4a1f32eeb9584123d1fc8bacb6b77a3c220f2ff48bf7be60eefddd8840733467322726302226863971ff351ff9f43937ec4df68c7a73455d8e3c25992d7011651899f32089716c0695796ab35adaa2c1f1e9e1719c8008775788e94772369fb8574e8d6b48e1397b746f3596e5a58b26d75a11056dbd309518ec225f6d50d9d99a403defa49accdb2bc405661c20558dec73b907221172c609807cbf97d0bdf0cc4a909dbc7012a7086b4604646851990678dc36f1d9b08c18d4ceadad9df46d698a4faf384517fe271ba76127b3737e67eba368582028a24024af4cf48c0a2925605329718cd76caa687a820e4ea7bfa614354b21c130826a091722c8a7fa7acc2622ec825be180a893eee4087a2d8d13763fb005c5970f9054794109c12390cfa6dbc3820dd41b21f96b83f927d52436709a1f4b268b0c9fa4e64d140dea0aae94a61ecee2a6fd0af11635dd99300a47a1b21bb5a11b98b2934a68a188f5232f44cc32682c36568b091fab814bb9a43cfee8fb37381ea6a57b21214ecaf3642169262714a4ddd2f0ce6ec6eb523de91cbfd29de64ca4744254ac6d35da28e4dcda0e5c00082c949bc4c6133df55043b29f9007445d373e38788730750f1a801d0b7812c70198ab5459b0e9180f424a15061b9f580ce2e278f8b9c4f04375ca781e2ed3ad2ed0671ac799b593c84934a877e41607aa5ab3be9bb8900ab6642fc19f8e25d29bec635461c5741c138894a59eeb51582c817ec5fa77b0d09ca0108e52b6b2023520ff49a0ccb38d51eeb40df99263622b226c5dfd17f514278e894610c5b4c6093e1382f66cff0faafc3eddec766e9906f9edfb1fccc174ed5536eb3ca6d9043f6746804a7bba80da9e09e2ffa567114967959ba48fa4aa2a58530d3e3ed0531ba114ec3a21f6166bf73acacf579bcbb58a5fb4cad9af87d52fbc857db4367d7a0d4b639277d74bb7e6a5794f02b944fd9dfebe74a0884295166fcf60c892f2cbeac10b3921aa3cce154a6c1a1bd3f108d055c8a6a5c399dedc0d8a3020f74e4009f45941e4a2192e5544aaf0cd25be0b5ecf424693c2057101ff3eafbae31520e4e64772c08ced432adeb47b31ec9b6e3dbe79c21f7478721a907a1a61173034b0065228b1ee7c076524c3daae122399236a947b1d17aa3237b972edf9441404ab0db41276118ff7637f0a0a476eb081563d64aac969c18a14332ff0ab6a55a50bb76cab9a01a81e204d5d1779581a3decd375f92b7b70b9f1bea38e4818e4bd948845603dfb00f553164bb533d2cb587828210bf1b01db26d6c3d6cbb8d0e7e34c7ac1eb68da5d8b50e9ac62e4de0f71a7dee0865d650aa90516792f7be4fbff091b426b765554f3b0bbd6f989ee6b8ae8f734ee2a5dbc0a0f5ab13a988b909f088472306989d4f7c1bc5949c5023ddd8301f46723197cb56c951faaf84d47e79ba2bd025091cdaf91f0dd348bb53b260c4ba743735fa2531b993f76fd808dac89ac4e04ac47745b8df45ace16700db84d3c9900aad093b03d87908fb53b7bf821f58783dd78f7336a128856fe96791e88d252b9a07b9416b61eef70e1b88d0250636ae96a699490a24c58f894e8944a42ceca47d5c39160082c0b37b4bc067070262b283a713d8f9c5ab69192ac6b50932ee589c6ab9d85b6402943067873a54c78f8f9cc2441bfecfa8e4800cd45c3271ade0d84856f5d20dc1a3f703bde9c69abc985f98fbadd3638426e1e9a08f1b8a33573a548174808dbafffe7cfdec2d3dfe2901819fe677f460f7a088fa093e9c9ac6cd001c909512c31e33d083f7a89bd0758cfe089fdf73cd51a20299da29978334706e6708f02f89908685c1565233e339e637810def394acafc86b82803b4fa1400c901479652aed8d1a8bbf9883a323a12a98eda8785298e99b2668a0d80494153ae1c8a720bae2b21332d523780196492159479328d32a2b2d5f6f8b960a1dc843a4f2bf18274c615b02efb1a0cc755e483e32a5329aa49ce30c71f0442f2d8bedb84147b22bc0998e3a7b261adf3d2b14d188e45120263d9533179fa1720438a3314082dc47de4616a1fa22d23658250341e9aa415dbde6a366dd68ae2a957d845eec237ea6e7f6817a08745ea59ff37f891cd70db8d7f6c9303fa1c76eadb0156a7b081b4da1fcfa9fb2f37ed4b863e06573cf26bd50da6bb4dc65995de90a09c70f0d724cd99720268a2f9e821bf210ee33e72d60a0ac272fba79cee3606868404090a18d3cad297d61ad17ab39c6eec3683827c1c7482dc0c1a23a90ff10d4db7f7cfb07f327bf46d9b9f5a153c8bc72321ccba28fe8c03e22d82b54aa51286db43dedd644a04f17ff7e2b50fac586feadcb38277cf2b124ab71476768897fefb4c0fe8ef11d92c8b1a27a748b99676270ebe97eb9f347def992bdbf89156c525e659afa735707595848a06d2a7e85fbe4568e0262dea669c0f83cf1d1f1b01665adcad363f118efd37171e77d721da829e33eeba0872be53284f01b65fe81d4f1bdc10f6f5cffb7712aa755175dc106035db6cdec09a7765515ff0b7a5fb5897214c399292713b41115395f0929972038f98d8db1f74c8e60456fd42a397281f91485140188f8680509e21e498056e0eed92060a3d4c87742f02f7a12850d0fe5fdde2b1054a83621d06894560be84263ae0d71152848430271e8b38da1a37e01a53f115263cf47b2cd0c31dca3d236a8fa9fca98d4c3de930465489ad5eb4078dc808c1aca52026466a3e575c926f2a7733aab98beabb7f80e005179f7b16657e66c9206512bc9152019f877c4cf58a3892831ffe2a31160987396ae2ab491a873d9c57bb860be6a114873dcb2c9de76750d9e3ed1dd97a54ff9847d2715b05ff4b7bec4c24837d8ff430f508a029fd8e1fc9e87d5f43da26fd5b049274e903550c39216510520d1697adcd4591c80f756aaf23913d3b3405a71d85efe0ffc03f05cff73dceed89dcc052b3976f9416af684fb20296ae10d929dd595d39a9b3cf958d292f2b9d1d227b79a9814e31ca8a8f294a166ee708244c590aa55e93c2a0774452ee8ac56b82832bcafaebb75a952cd9b0fca6924bca94312d993ec6a7644c781662242faece1d77d31d613b4bef9655bb72e27775e8f152302f9774ebf63c8f98831a6708e0c887f606820f3d9c494e1bf927ad30d6f23509a72a4c0b0b8b9e555d83eb888f48498f8cb19fbf8f8ba1a88f68c6903e1f0d437ac40b8afcf97f24e2238c4884cf6f20111f413d227c6e8ec037db604a8da7ad99e966ccd284be68195c62fabd95c88d28a79d253da3220b2f57dae8b494702ee2e505d2e6dcb1f36c084c480f0ef6b265728ccc25eb8e3b1600a43f3d012438dabb040a1aa396c890686dab3dbdf41ecdf817b6fa9d2f60d4ec24bd1e3d43ed8ab5035b4a2bec29faf009d348ac7c886c35788cd4ee6fe66155e28d292d72755778ee4c82f7b2bdde0d2586bad93349923eb3eb41fc3ba0837d320d8ad02ffa04aad642e784a83a0b1123b07258b5e1d0bab4564a621b3fd6023911d96587d48a11406e30b46b880b870957c5a5090220e234cb3cbf49f810212e126440d33f32bef7231bc7569571bc5be42236931130bf4abfe2fd339ced4577d989e3326e613cc6d632e4304742243650ab7ef5fc291db2df066ba5d5ed645298761c22adce8f5b9e583c81cf3e3b1f4c2c40b410e881025ee29db826693f52aee9b172b8e42fd15c348cd595477d5398d4d138b1d5b850fe24f6fcc33b323815e7ebedb7832a4359907a40559b25de92c4bb1bf2fd99ed4c8c7f5bd46e2397b099b9f0e915d470093f9e59a898f84d604265c9a353e7948c557de07d0291ad0579d23af332ee4d2b761975805b239d6dd02d0441ca17187e2dcd9fc789d6fb87fb60c38a99abcf9b02e8fe3164b74edbf7e56a1f1cbe2f67ef79c52e89231ac428738a59fce9884ec1206d70708ec5c6a7a2161b347c455e66fe741a529d757ff604c322eab342043614e572f67b17440f9dfd5a9ae8dff1a2439e8cba3a5e960467eedc4adc6aff9971492f15376b72019d5dabb65a3c06d69793668e4259869bec024cb650c1961c2393ff8a25b2467d46e9d584a8d9477a3e380f5544cd113145dab4b1df366eb29beda3df06e2f466e1faf90dd64656b2aced311409fb5bb74fd3a803028a02a9e0056d179b9a01c33c12d848631a2e0dba97d731c3673ab836dd0e31aeda607fd27fbcdc63d2d61e8af2228d15738e79a56df646ed13e7c422c2c856746b0462d39d6426f2e3c79c7b71a4c6a0b034dbb2273f26e641e5e4b4c4a06d4c03bb4f47135ea70f55024fd746ba9db4b040c8e13dc14684f2ecfce98d085134034ed021b0bbc85baf5f1d93c6c0d07a038a686142b666ec7626de38733137c96d11c7ac67207d6cef5989c476f28c380829b67d2d5540da8d77f5b67ef9ec048a17ccd11f59b09f1529b0df87656dbb92a0926a22b337f38fcbf9a7c1f8dc47833e49dc36620d76a540aa97d3f73b4f7fbb5fc6c61b278c6b52dcd7684c60af4eaa9b8812956e338a443a8a8823112e0e547cdf578c44a163f1109a380e75f98553606906f50a78353210e354de44e80f30eaaf5066fb008fd192fcb207dd9825f6d911e00464387ecab628928321c331d2ffb869780f121d13f521da9f580f92fe693420ee9e560b79bf09dd907498d40d61ef74176941f4de6a2dbd2e19d52d9bb0bca8de333bbe2989fe97a7c0d4d7172620333b5898aa28e193ee71108a4b1d41063a91ac9c4ee2e63670f52affaeb653d03499264b9bdf6750dfa16dfeb8f2e71700ba0215e35f8822ef6cfd5d438f30c0fbe575433f380678a3af1d1d74d40890b34684c70d093f28a8f86d2326e0a2edae3be2cc3ca30ac6f63c21a9ed176424c1d4e586988c2879401987560c6281ab3b99b4c7d590987d380768017bc521eacedb10dacc859630ddb4fa19c7d366993a8269b3c8daaef163104a5b37ffe24a9ab81a3eb129b1c6963156b1dfaed80cd9efd4a75d145deba74f1ed3c4c0a72e67fd131187c40f859b289439e99532ebf62b9a5305005e15332d940b2bb5fa5e4e842eca12359653dc100d371b12386577d5d80c4ccdab32029db05c2e595938580cd07858d795d702da8a4b0224e158e7ac2a2c19b2715e56ca240b298c047fa56763529a20d92be0227dc631aabc96167a477acbb3a66a0e7e5119d26731e07d33130dfbed79df9de271157d2c2bbe7278b59c6a6c7087c7ee568ba638e06b0657b43af8d230515c207a6eb580d170faea7c1e28db04323bd5eef73683811ccdae0e41c818eec7a008b9c38abc83166e66b773c236e0aaf440323d21b0cf0360e841878c3d4a011a96b6a1a7ec85502969d29537e014e158cc580deb63c845a58a71ec250a548baf8bac90a6d567b79e1264dc0283b301603025c47fa389702de0f8ac476e4f4429e8e6561e431b191d1a140b419cb976b83c283dc1ca8de47f91b068d57d5a882d25d7ae229585d27f33df8a693db1645428fc3bc6b33288bc65e1a9b6438e85b4ca18e91f43472dceca670205da2d282ce990369a9664798bfc07667fa7256fcade6381dd287e1e367ce1eb9ae26b3fd24b78d6c8661a772180fff4741a768ca563ec4b59a4afcdbd26de44b7dde844522b6c3a04b940616506749aab8521e48a944f58ae6ed13a2d3150383ee8fbd1c6ff6a246c743a16917d6a19bc2a91a0081152f73a5d1ba8b00fd69f802f005a46a9c94fb5eb8b98d24e0f5d40bee91f84780c1785e5a41bfe0ed4fb9cdfdd783a66413b00426a72302904a78fc8bac5a03c323f3a5bc00b3d238ada068fcce35330b5ab082b8ba695a3d90fde72c62b3d7d7ad032485eb9078a86a8c0be521dd8081bf4f0b75eb501d9d7e37e6970cc5e11b8c1e5d74f4d86f74e66d9dd0c31024cd9a7ea7c6dfcdc5139b660c8564aefa8208d8076a3414a36172b0b164f9b1c994464100fe0ceda92f9b78087278127209f7e550ba649b16bb0d2e2172646786fa5dfb5fe73222f8c2a2eec4e39baaeeff2853c2b526ae32c6a7abe5f99c171e4989a67634ae18b34f8318d36a95325d42504d3bb126c5861c5c3e088d8a3c0e8987122c3f8c0faa9aeff5035740685bf752f105db520719119afc1857ab1ac4a0e6c89634457eeebd3ecc2fcf66558b039de769ace9b74e1484881e9510517d1454410374f9762109845cf2721eee2e9820f7776ed740b95685523da2b084d6d4453321bb2630ca4d4860c3df03d884c81f85e5c7aabcd4b575f9f2f6b50f9702427ac13f17cf4b4293f523cb2f08584331097ec0bee76f39cdda27b226bc9c34ae77484d7e8ca4bde91f38950e49e6070766b797e2ee49f89028c0c5be4699a35018d588d55b5cb35cdc343386123c9063f9c112018a8e8463469860159fd2b6d0fe1122f0ed41fe5612706dd5b39b84ac7c983deb90582ed8afc7a5a18f6dcc4ecf7747e5680c72c00c6bb9164acc7082132a6bd1918307de830662f8496398b96ceb845015279420b8823b96e034253bbbacb643eb9f805e6b9767ac334e65b6d62fe235573054ea789cdd7ac17c0929c3e3211f7b8960cd2586b0244bf4284227132b2d22a3614c19b1c88f8dbeb86c8fb1142cc9d5d41f4a63b20e0aeb71f6667990f75d3ab07b8eb8e87ddd96d87aa5634a743305570e972a48d0687faa7d4baa1bbc53009cb2604e3651c79d6b3f1239dc405bf59efd17ad12b8c6ad15223f76f07a5006f75f8f8290cf116dba10e75696b9211ae35a729c4d04dd225cf0351dcb0017d9ceb05c57506c09e02b10491dd08e8d41e9f667c1f9cd1caeecbbd8a16a6ac6dba5e0aec98a6e12241826c329afde2e6f66a80f4881229778650ba79ad395fd753ccd51e12bffec18da714c1b56b99c607c4964f10048db39a956e43615205a3e3306bdb9915fcad154caba25b663763bfa7236d21e6e48656da3f83e20ee20eefd006e881fb4e2bb19388507c1d78e3c55228605ac53d95cf1d67e060cdc30ed2da5f073f1d2fda2b58fac4202d3b7a4bd12e0ed2a65ffe8c3f422a7aba913996af08dcc0299ca8a924e08640e3b4ddb3c1be81ae68938cc84bc2614767f48a96fc1b2ea4487cab0a8f0f8ca7d885abb15c61bb9be8b7d043b33774ddfc69e934ba5fc44eeb884e1704c2566f1289f217f6e123df7d82089c09400e70033fa43ac4042300b97dd3eb9080223671ed5da39b87509e38498108be03a3721cf952b2b96e2f0bf0d7be01e00e36ec4a2524146cabeeef53f7a6f40c90af14c5c870c92fffaa394705bc9654a59b86afcc8a7165459cf22ee0b90a0432ada220f21f3af89eed2f86e8503b626a17f15ac0e72bdd3d6cab40fe72520f8da3414ecb3d88a80f3491b22841f496d8b85d9e54c1d10609cf36d658a3c54611f3ccca7fd4e2475c68fc5891bea7b1aae814ceedd42124088b9bee0da7d3123618cdd8abcf407a7d876061fc722d8f57ec8b6542d69fba8612d0d88ac567dd40d390ed507e66fac0834ca9c64cbe4c1c678c2788362b61f120ab70b2965946a0e25b37b50b9b130d138392d327f7e14c953e19993ec1069890aaaa2d366cdc42bf89a8a0f2282ae3e86722409da81f64da2d122e3896a4d0a241c81040362746bf445f45c3803516e99740f6ff0d2bff66045286552ce2fe751c51d3b1242130ac1cd208d333b0b71ce7d7c6ff79708e4d723a7dfde5010531315ec3b44dd82a63ac9f77de9fb133fdff112c91f8f8dd024e6b4c2593a331352e0cc93b40d9f489824c2c9b495efcc12e974d029cdb0f14e8ce5816d9211bfb5f2b7cb3259579c7b3df512bd0535f1d4b85f03db8f4186361bc124c2e3bc18828c98266eb4e021953f004c7d8c8f3e72ad8fa7510c849a61900d9bfb4578bb171497ba08d7cce85848ef5fdb295287463bada0526a659c0434343fad5548e30167c81c98a708ef0a71af85787469eb3178b78c4ba827a1341d18c95c2b1673be5b2bb79d8ec5262d033ddea16ac05a75c8d71c89957a82754b6193128170265d8547726481a7919b35ef9768a30f918a8d884238d3a07313cce46b33cf326ff0ceac23cb41a6ee961f7b241a8f00a7e0aac337c22bc4d13ca5061ef86aafa088ddac579bd38eb480663a7477dc83216595ffb643ff063a2bc984bf59e8d91923502ae39fb7df1f4c9857a2e0586d6915080f96a18d347d1fa15ef79cdec727ad7e3ac5790c259c9936aad76262be021e950a6da9f9444e88c859343d7b758b1a064f2763166faa12182e31a691fdfe1ad824b1ca05bba03e477295c1ff5f70aec6fc22a5491654dd48ee5f5f85174de4b3effad8db62bcc50a349ed5d3946c37f6fd96b1ce680242c5adbe757cf9a5bd729cdccc413704baa95372cefb5732a73d5b4c16bf20743846bc7a67b2bd30e1c7a234848d887b525e08ee687954e9a9ef514c5b11e91dfc428a157c0216b56937224f39500b00c4af489a64be2ee5f30e25018ddd1745ac50a42f2ce2ef6b548935f580bc8763ef7296043cb032bf69a791447545bc5292421675f15aebf7766308f01d1258e3dcbf0bac2889cecea2092b22f9a4a6405135c2df287450fa9b6d686f964e47cd9919c486e7cd2f63e0b1e5e59957ef140151069d4970ef6d553c9130998dc14e40264be79a49339211e675cdd4af3b4aeea98a4bb9bd8f3b82fd780948e0e629d46df3b275eb051eb1b90eff9568c71835a2b5282ae82f455bf769c6cdee6eb96fd578d860f34bb9b8529d932d157af2a3c358c68eadb2ea4ac2a9a376cd1d4eee4a6afd14dc4b6cf196ef4dc4de3d0edd24068c54ecf299c91279855b3316ce17eafddb44e71c20cfa597b6ccca19d555e5bf923348ed82e8ad2d685198ff90cdde8e5905a1f00aff5c938b0d9b3e2c1d2e2a327b241b4e43254ddd13886e30c0c123207145ed86942a8cbeb9db8f03a83643ae471f47d7bc33a1107942368e6606682798cb012515f312bfab1baf1fad84cfd9e2e336e6e1556b12eb803656413281c515301fd78818e2059949488637349a037bc86c0f4af2f1f4d0a4777fc0b7e0abbb6d724ace292992bd5366d8ceb458cfaf1431e0dc20238ff0e89d6796ec2a68d581205340516e117524c517b835ee83766e44ab81bb17bce48691faf52d3c6fd033d6997ed988b6e8b776b3bc002ceb33162444eb5041b149f0ba3f4f88078e2d6051bd780cbacfc2c33ed57d4c904bee128aacf5ca049009433142a5d62a7b2169b3510ace6d3d30b6fe658b9bbe4f666557aaf189a04170cd0e6ac510a6df54e7c2f1f6c2af5d8451fbaf7750dfcf035e3b3442364a82580234589e011940bfe190809710e8d144171f5d981f384064516d6756a07e1718a6dea8acbd3338ccfc887ba56d6fa419773c3783251eee9106af40cb72c107928a28159aaff4371cea784798c7a92c3872728957fc4a1373d4357f8993b3c859a213d0f2376b9e8e27eafd3c4276f28f6292eb02d41ba2175d93734a013885bb4337a1d744f19c0cea856b0e634db93bc807209ddb513314a375caac36ffc7fca43c7efcbd59e4fb5327a6283b72b04f12a3df57e7f2041462f7ac1b3d33a46ed945c3678b2f533d0d7d17c603112c44af37433682725ef593411039065e00a86ae21f32748944c7b182da5f7ef562fd88439183d0067c9f8bbdf8dfb88f5769b0f26ff821eb99466422f930199f0b9715ca9e20d77b168250ee9d5a2abbbf5ee55325ceb57e34ef10a1dc3bad0df80f15e85fa663e8cfbc505a6f0aba03643f9b05ed7f8dae576e9dee1bde58224df7365f17870f72c7002dd502890aef07d557b7d2e3c8bd1a38da116e26a4f6dfd47f68b4bed6ee8b67b5dafbdf0b20df63bf4a76a26041e3ab2631e71ea88b59af870eb15d11f6a616bad54191de4144682d861bb61ec09f3a0ffd86814e53705caf7342ccb5d10db02e5515f8ab9b746ff9b401c21cf065e7bf596ed46ac6abafc2ed20c07f1e8dd5ab2d0b578ccb6b7d29882f5945948b0afc8b22728ad2d915ef3edd546d97f3d9dcc5070f6558e74e3048888a1534650fbc80f98b45794ebea5696a75db2d9a9d741dd5e6b367186cdb6f46de59fbda6c212c5bdf66baa3beb1cfa772166d3dd9fc9bf63cbca3c97fc146bed3e6b725e547192a4479a6047e73259198f73c0bfb9eeb77d84a16953ea50d3d720e9d076cb8d249359fd00c4196fdf9f5304836c4f3058b3b7c01371154a36a1e52804d695e8061447aab5ade5e30912f49f87b998d19062dbcf41d11bd3f7f355498185c72f57492a614dd63838f48a5f2a15f80bcf5099f0e08bd0c0a2a4cf7bd27a114e54e02b0d8a782c9805519c8aea1549d5b40dbfb1674d0b030f6cd397d963d5b099ef8d88d4ee8f4de6eed1ee02a3d5443ee0b1ef32d350fb45a2a0216fccfefdafaf9b4489ef72ff796a8cbff027d3acd7c5a125d4d01378a41df3f6c57aa47f9ffdb1fca78b0d953fcd15eee81310889e9b5ebe13c2c46bd99fd8cc017ada266f28bbda05c068f6b832c5b0b4c6340e955bdfda9ea27e02211b59872331a93b518503e2d8414c1f510a7aed8e58f1e4bd64ece33e8003882e09fc7c9f5bfccda79bc718fe802bf7f57958202bd4fd27f8f992bcd7b9bfb42b22ffcde9e0a20d8e6dd5584874b0bb8a00741e566e2108d9198612f7bc2edac5ec4af6efb7cd045f6b5e4cff79b5bfc6134a6e6addfd0c20c7a14520b524e63cb868b5cdf7956cecd2790121329e3b6882ab448616b4fca36a3ca1f79ae8bb9f7a6260366793f8194633f5f436abd0975ae7a0c771533b3aabb32e4f77492848a23d3303eef596762b65a29e284ff4fab0200f06a34f133944c5510753df386c5c833ead6b8103394d58c3036a685808bad22c82680bac8e3d6c674738fc4e22915d1ea0441a7f4a0603e7a7275963132c8a08fa6f5a34147509ee94afdec10fe2ad508b483c110e345079621f46ea85f7b61703eda5c75094e3147d5afba1b4b64f05464ec5895fe38e473bb024702f6b941f464e8eb589a9c0e0a16f8158ce6a4dc85a016f4b00ee52cb5f15a31d1a49c3daf04be7181116edbea18bfbda2a406f2fc72a384a900e0ac5751795621bac2eab817ffa2e6599d58f8009f2955de63674005bf94776bdb528ef3814eb84ce094dc2820c9ae83b10e5e9099fc66be08557fb1796f3500e94cbf3c4e515067cda9d3dd713250f54a0160a401a56b0c5caa6831e8f897e045b4e106d45eeef6a6f4fea9f561de1d0b5fe4923173d6dcb6a676f07b439b5c97fb379b230d01619123b9044e6936205d39b803ec2b55f4017fbc3fb68b052e31a2b051416b256a20808443b5b3971cfe7ad8e15998e093a12d3b1a09483964ac9d2ea39f1ed0734993bf34e739bc8dd4e3959ff7576b6c3d8601327ff3d0da013eaf52135608577b81b6126d81416b19fe161d984f6c249e5c303193edba3540b52029279708bc05b4c490ca362a5350d625348a17e62125305445afcef194480cbaa831653eda7293d8ee827f585444dc209c40be6d64e049ac28d7a7f4815e8945ae84c25ec14daba002241251b173da3fd39426564eaa7a350eb713ab841da781d5ef9b6fbaab9888de3a847f2f79ab56d83c2585bfa33b2bcdf12766b46b84789d48a413fe218786e5c4515c0e460764404e3e141c8f961d4a6a1c2359195b30d8b99d3983671453cf5d14d44550a8841e04478ae753fb2a3f21d03357618f7a053a80259ed53ae9930726d741606db63715fa4633c6b0507c773e16af8d243a336c377aab1d9adb3abb65122760b13a7c8b63bb212f03ccdcf0d4b528851641ad4afd9da0be54617970842b9cf02b0c242876bc508a9a210682f1d6130499b75a5f8f8573874bc28d311f9baf85a2c14c6e6733121c8ee1d531a4902326fba1ad1d4e19e191897507d867523627235fe5a7bccaf82aa5c5eb8126ab30d731da2f1cb362a16acfb2bbb03aa9167b74a1454dd779cc83c7a1f71264b75a3579daf011d25c50732ac7c358c5d291fc3a52e6cf36825e01d8dfdb4ae02899a32c78c04111b8544b851713f43161bb7040f356d56aa22451d530a5c444818bd6c5f18cb902456b6ba183e74670337807cd753258d6cd74f71c04a9a031187932dfc0ce9ba2ad80299e396ce90e64a0669b6ffd1fd41e200228f16458dc0c328c6061d49d29641fe4a29226dcdfb2dc19547bfaccd0472b8695ab526104e34bafa846ed182f227bc6bde74d0f46a2cadd77f435cdd5e60b706b342e64153fefe7c70a35dc77f982e1dd34115ea24353041f3d389bcc1f34d795c4aebca3028a3e88c401732a1fb454f126807cd15ccca941e9c85ce33adb6610f802b6d6075343e325c2547b670d9b57a3a4fa873d6b53d8b587c86cc69efb5ea30132b03b7c08349e0d1f5913a7e4c37944690e9739a66adb74dcdac779301b6c9185e9a502167a4c66d33482f8f10901ca7580dffbee4421218c84d9674581a82b28e9578652a37602869dda87d931a0d907a49b88100c3975969270dccee915103f7421367acaf1852fd0e30c9b4472ca4797951819a0aed0cbc3b9d37ad0b26f73f0ef057498176d55c0c1dbe8074215db4886d84d8738d28edc52a7200ad7b91d3fa241f526541a610acd189f87453a4996602f41b921bfe0490bea02f9680412401053c52e0cf3ff96f96915aaee460651d710567a02933db718972a79323ecfa92cc6d8d361a8d806f81931ad8b16092675bf0e62f4113c6c7c23f4c03751c24ecf26114139383e8268168ab1e58cc4fe1e91040e9ee88b9851cd757e1a5895ef8c465e94ebfb934e7681448ea5fac5cf503e410b709deb98c77c6f2aa0c134cb41cdb261c21cdae430d90315c01cc64120caa4cc04146c9c4cc599858c59848fbd3400e28e46cfaaf3c92be7e24ee852c4437fe63332e00c39d60078e72e42b68c4e38aee20a431d4a4548661bdf5a856abb058efaf56dcc43ad094f3552cfde6e6eac98f981314f60beab63dd3413d213ed323eba884485e9ec00d8e00f29996e91a2ca2f93cca0cb31a3c20ce34a2741a455616b75c3d3da10d0865a77f663f0885fafb752abc6a1411ec39ad9c775cf2c5edfcc3235f8a01710ae28d68fd4beb4edf992395e5882180fb62559416f43c8f807ec8715ba74faa7f041607f828c41658fa4fb7501ac580f81f1c5c70e967162ac39959f2e9bd0697f56d626f4359dd425b969429467a0fc075a7d2c24754111625e922dd18f8686b3c290ddfbf7113b025deae432da7efd8f7420ef79ff80115dfb903bd594188afb4b38a5a60858bc7e9ae9873cc8751038f211b9640095147a37b1cacbf4e69eb4155147003e1ef79e37e752e7f80b737fcae0e4dd1ec178fd64aad8b4ea2026708fff27f00df1cc6c8afcc320c2cd503d0157e7359fa3732a857d2927309c6b2c7d1e4b1c1f4afc10d9f09b3765a57f354ef4fb21334f0f3462596312862a0189029ba6cca706010ab5e08f18c5c7016e00535a603bac458eb1df20b301aa496f7ad1c7d61839c818c58201633ac51d7c9df2496b4021802cf992443bd654025413e55538979ca0a9827cd3a6510f3205d202c69ce4bb1ec73738f094b2d9d47f0607ad13e6173ddc2ad8eeac58cd5d132758fb2c77159453ec64280b90ef0090895b6a9548313e00e73619230b31b3ce0c12c55a3a1cfb9441c8b215473d39aefa28c662fe82651d139d27d31974a9c4fb98036113dff67cbd4913cf773bf34a56ece4c1a0e1bf28bf89580a0fd6a7a768b067a661423be7b5e3531754c27cbb107543d8f27b17b7d100bd209c2861280f37276c348926fbbea054cf7c7c366661c4ebe3f5d241fcdb3994f2dee62d0a159c70a1a1dc008ebd41ba1083ec2de8e737734b2bfde6887f6705885bd3a4001acbfe12ab098ac432044aab13e1432cd4bf32bb64aa58c3c0309b01be7764d944538504e290b6d444a31785380ad48cbb1ff9bd0a48dcc51110ec07f67617c6c32230079e973fbdefa74867357fa5396f9b66ca6bdf801e56e29354dae12c8cc6677a490d38a3bf8a97fead9a204847b7cfff6d6d757ab8dfe3b1cfacf780b5efffc1bef060522f15f5e5994b6cdf40b2a6a83d7786aeb0656d0a654cc9804e8a1c5d435a003966ced5000194e01b40b0663f64c125c7d1ec8fc95e87c8c793b2e19b078b7d51aee3ee1fda579e88ad61302ce950404968ce9ec78f6c86dbe180c01b1a999d621077755c51df1c78a71e09323af7139aa6307c7698847d73be0d8215a80d2f25d46dbd5586b7176c45c4af613ee249229f625e3e02a65aaf024f4450925b1abf9c38c4888209cef6360b5a8494ab60d84593af397d11ce9d2b7d9e6cc81e9f202d9a46f0555713b5fa5a0b7f495d0994ef91d270f86069ab0a05ba11aa9d9f10b66a58f8b2730a11bb0f0378133128127a688c0c5e821b0856c29846fbce969890246557ff20310680e2d83ed02c60ae36c887004520a7c4342e4ab8206e6bc6cb05fe9e97bea5f457831129aa2a38bf0bc12878e18e3711e414d086ce314979850d8f8f17dff9da74dca0cc4be8d1e48f0803372328b16bf03d4cf9b3b9fd58ad6ad657ba7a86ed59cade8c2e5a26a8a6903da82139dea0c95b7d83a77cd121281021ad37f32ecc74069a2bcf670f213d679a73bfbc46251ed7bae70fd18c65525e45447fc20539eb83087f6141243843add558eb080b509643b21d1e986acd5c740dc7f346cabee96b9cda5fef9c51368b1302c1382544a879689da9c5ef4b2dfcd6aad1f1efe9e1f0b202534112baf81e66314914041eec9031959cdf1cb5dec3bd69ab71de48177c3889b1d6c0b5598c14902c90daac004f8b6dd96f8a4b5b8bbfa36c124abbf48755a857a09d1284eb1702b90b95b77dfe651aba593077b2a37c50b7409624d11540179f744a1294ec8764d5df51181346b7cfbe15b161490b387f852bf2e6e5d84225bf1fe9815f5f32d512776d392cf988fdb662c38ee7a0da119ef44e3ed5b21c7247fa0abae9f296fe5132f51cb56768653f849a52ea9b33c5c85603be5fc42c0cf536dfe2bcfc5d8ae2ec6d306c81f5a3d9732ed6b693102cffe57e6726562626fb23cd23ec16fe34ffa97af7b28e0b51ba7717c855e40ec5d0e9ca9add810e6154ab4b56663e5a940e809cd2da4ad49a77b99854b9e7514c36cc62223a20b7574d5629e820454f1ac4b68e0251bab893d8af038451e3226c0b9ec82179158ff7e60115bacca74575622b4cbb75fb2b5f1b0330ad5201019182ecf91128f405224bd00c3b45faf73b706f173811a74af9c58a49b7a2be24bd63da41f4eef288a43b0323d8f2f4dec815951e9724af53585c793bc6c2d3c79c7c6fad2309f5d06073632dec2053b1a6d683b63cdc833c664ea4f87f3d80f4f8a07124ae5d3806350a9e54561e6a5676ba2e74ddb34446b931eff8c44279cd2ae47f03e4005d49a3e40f6cb893b9d1a5c972a074d84af322862400871ca57cc054444179d9d05d006654fd029a6fa557ef4311386053c79abc5509749b6bf56c1d36b7c56dd99b74188be239908a647cf5e40bce1732bff0d44d92bd34600a756a050f9cdc11de36ce5c179c2b4b7c91850b019912bf7a1c7af46aa03c89b44365003c680d5baf0e98b33603578eb910ac83a6b499216787ce221a20b1f389fb050c36c7716fa5531172b6276e1c5b7b238b9b5b1b79121a8a3f7dd2b132a6d10c1674ae2f33a9206f564bda0638fbdf7dc0c12278b122e2f4a2abb0003ce028099600c4f935fc8e0520642ae8c9d5bc5461eaff5b0812edec5f8e16b68d708c01926eb53b0391fee3f197d2baa8c95408185060550aa72ccaa2f07da3f66a5982f02e2ba259edfd82b0f1ac37c7689b5e20e1ab6f219fee80d9f8c608487e6de3b68b40844411d34d48e6c90071b0d5c80a20165a26eaa0df1b40456103c798055c42ed705598a73ce72b16d50b8a74d421aeb4cf4eb513e270c4ce49dc426f974f50baaf74fe2776d646a7646b9a9a092d53c6684b601fe9e23d76755b163755291050ef6d9357bbd64df9c011ef2efa0bc1eb675742a6ad278490d0952f6e51e9d59478640d2e5420d283f8718663f5eb3765d26ad729ac186c52b10885883df0c05fa1e1f2d9885285a0f31c879d4830343e8638932c61d9a9b1e755836bf55b0270935daca5384f9f0664be32c333178170ae09393801648d731a14ae01262368efdc9db5a38c52efc358aa263488aba74f46b3058c2b8c63825dc04fa6e9ddfc513b98d3b44b7a5125cbcdd89e9a493e0d5d7cfe6cf208147f4c1a567de44dafcc374582b33b63ad591f93e1da184491dd34323131b0287c0591052b96f2724f786b247d0f2e84ef06b9eb3ec84ddbf007ff2a4050778bae8042822c2cca4f6960185d5f17706dc0dd0cf4392b829edf5f58066b06754c080acbf940dc245c180c3a24fe050458122cb86b0443a5c3cfd23d09d3fa8fb20921ca39224cee76c30761695472e486197c57c0d5f015dea8a2b6ef28a4ab2634dfad931cf542f3e714562734c61530be4755b5ee94a3bef2fea740127a12735652dad01429a27f43ab181f5ba3b2de00aaaf2175cdd253e1506cd94796a1ed9ddd91f77f6208d6d289d201b550fafc784e4db20af02b1ad7b9ab1d5211ef1de21c8dabbe8a07354e5df88ffb397dc1a33a35e67ce364b73788da56c9257f072c24faf20f7eec7274441ab255746108234cf4c2046cbb1238be01fd8f7091142ebc747dfde7e006dc5791d9b96d260daa023749c7203d97ec3618eb267415b80eae631ab8bb34bc285977bce8d2ca8754c9797ab24961d2b514337d8d53f2c0ad9c7fc57301bfaa06a77e13a28472bb10d719d3f4be902e86537fa60c277c4816d8a09d9c940863a5df4df47bd6aea08882a81d6ff8c7972bc43a191f3b37634453b9cbddb9b10b32b557104f042d0804f9402e364bff0e8fdc54b7c2e41d6bb2cf5110cb2088b71f8ff7f913da2a94cf452ab87e0af09741fdec670e552de82c514018ed6730440bdb51837c95c386a87783c34b8e8e45c7153e6dcc09d47d6b31f24455e8d06f8859e9648c60d682d153787467cfeebbbb0ef65c3131cdc4d948ffa2540a523fd5809a934f202849f67eae4258b862491543dc186d92523f57fcc7172fac45d13ea4fef75021c2a5474ed29dda5e4343d0b244d2d1a76d6987a591409b4e2368b27ecf408c2c75bd451571dbbfb2e06822b0da97fede35a07c4c9c3e0907f32d9bb370ca68b0c9a3bcc79ded755d83b0c1275ff014ce3229f88acc320abdf2685f479fad2e75ad36793f5f4191d66d69238fd2256de8c6f93be8ae220a2b88c5dccbf095e7ea1ca47a4b879c2d34150ea20dd17d87e5c78a03905343249ef57246c888c477c04a4bbcd7d3d779cd41c1777f223fda0720cb3398cfd10b83bb6e1c30822b2d68500c18e8ac48becdeaddb3b2dbaa21b787d4601dd3f754883340000e444fc21382a44b3f124230d5b812ac28347eef707f711804ec676f360416a36058cb5c96ebb1991acb4e1da8aa2999a19bc63296bf2744794ddbce3d8bffd1983229c1d9c24ae5bc290114ff3be4cdbd03aa3d29fd869cc713eb03cd1b4453374940f637536a49999447b55e9e11d8fdaffd283a0d4d078667ae816588470e9338b4cebfe7824e191e7f0df80ca7f13df5b63dd5ef96f411b3904e3a2e743cc51e9b1b9b0fd9b5d9ec121e7a93ac2bfc63b140c32bbb89bec79e3965b1362006e24c56fef19322967662eb5b9f87dcba0cdbe14c6bf64f1f6b83fbc2fa739e35218e0993e9aeb80bf511c9dcdabb62a27924ce5cc706c3eac16be38744a46a953338b2f0359c8ac9b4d6ca5a0d3037b5e1d281477fa0ab533840650ee9cff12cfef0bd0d2c8485539d4a55ac31a942038182e9d77f7f711f9b792ec03cf7d1ed63cd8e78712495279b5750773e7b3f8aac20d0097f5dda53f18e5e616b0d4930051fb2729a1b046bc6c0a732f8d6dd85723c0d8440bca9253a3c2940f75e4e1e32ca9d062631c83d3deff680ad766bef9ffb2e7193d7544691b67e6a071d635a2267673a62a673a11d66ac192836e84ea0aa2b7632750b2328504bd92451555ec73f39b6b766238336df17e97db764f32d3d2f215cd3ff079e677488012eb643b4ffc9c832300444393103bb974d946a9ba930df48beb29dad8578be8880617f3f00250be0d631490aa5684d150373159485c5c5f963fd5ccb57bca3794755ccc65c6173085b4ebc4fec276453212968c3fc8bb400ec6af6048bb882048af06937acc8ee07ff0aeaccb76471d70d11619bfe393f4b71daea63d38986245b985d772c64fb8f906ac913ba50f9c8d1d0387651cdcc2538fd9ec16f5b0949cefa4b4f0a4dd4021b0426453596345ee61711efc93ebceb4b3b67d47ae1bedab861563b448b4c6300e5ce234ccbbfd570e18156779e71d605bd3fb0b6840142c7e51064b70385d6dd7204bf15e6775db993807615e56aa99f471a10fe5e92ad5a4549fa1461819933d8baadc48e56ade8bf7e4a4ea6d71fec0ba5e1012043e94407feca5ced1961e3ef763e7e403a4587c982b1033698f2b6bafb940211c72b8d65754d38f13121e428fe29370f72236086477ad78e1384b9eca7142c1a8daff3a6e2596d4206720da8e9b501f71dfd938b1ffd40efd2a9751cd1ea10bd0a0fb0945ce57648d68e738f72dce8a2b2d01fd1aeefb9652c11b521b574da961975418b6b69cb9494db043ddab31555ce0897821e600e717765c33522bfccca9903c19efefc3a080cca3a0f4ff2f0c5cb0f4140f655c1774c23ddbfe3d7f25e3fbea50adbc77de831e463db7b96cc65726be8344aabcab0e8a91ff8f10cce342f181648abe15ae2fee8e449d9ebcde585c1e54178f3073f881b5c847371d2b55308b039e40711c4190c1336380ce6d8710c8b06b2ed11dece1ca0fb76030b6499d8e45fc8fa7bbc13a4f71216b76ef050422e9fa901767c7bc97800312fd1d82dad5886056c136354439550de0d36024086b3dc8528a332e56618f28d0e6889a84dc31d4ceb5b5c1baee71040c792d86f58a608e22a5fc79233351252cf327def88d16a11de529881a9b5775b6c061f49247af68cc8fdb5eb1b5340a852efca79e39bc30bba78add0df49e488f0fea3d51af0feb3d91de1fe83db11e1fd07302bd3ed473623d3fa817fe8d70015cab039a7ada805820cec24a074dbd1637e8db2b83ba36d027488e20d5e308beae2660f82c9090be1dadefd38f240f9e9db1f153fcdf5349c70528244f940012e4c29bd2a3679ba79217cfb4d9b2ef08848899434812b2f7de446e29934c4906a108080890073cd880244aeca22a8c47e4fcf3c492294fdc7eb7b7eb5e00a5f3ce6be71b2891ddeeaeab81a4c90f28a872650b196c2c4de1850c134a284dd143b295420c26576e574a29a54b799530e18005e72584f51bb158fe21458e1ad6bfa5611bc0a3bfff037f0a70c321e9012155c4346e370c8e22503bb84290a8a63f6c4984d0eae377108556e091500563ff84066030362c24ba9c12cb1d884072859c32368c4f22481985b03c0f685853092588d030b9a161b9f24b2959729d25d11d893a04db7523c99c84b3d1c6105a983ad4dac2f4f171e2ee6339830e8e48971d8719743658ede3ed4868812f832e64e84eb06b580dcfefaf7dfdb476bfba5bb5af5756f48ad2f768217e75fcfe694fbf2bab9fdf15ef1f7f75df60bf8ce2e4371f71bd839ddf244c50489caa46cd3170213b33b315363e162a2845c145183264c02813a7560003374c90e012c6cd1157e4eb151d943dbdc5f06b893b51b384f31a803368c0e276bfa28cd1c4ad8086d96d37633e3f05c32097bafefa9c60e7d2df364cd24927c882df22b35d10b526405148a8ce48926aa2399de0494dc2c2b5ae6cb5c2afc5cdcd913d764a9fd648bfd86f6904f12f8a40f225047f10295d826101ae8c6d6085200ddbd2b06194aaa10c185a5cf927e8703054a266922e5f1f93a6e9fadc38c6055f5c0358246090783952759fd25c9e5d8ec27acf8a61de1c71c60b1cc448a15af9708226283386c0e286d8593356feeaa3788a67484c5fd60891860c6dd0c4e99133c6d2b75fd4561a4bb4cfd9af9972b07ce95be1d1dead8ff16ba47f7ff8b7152431566b432eb24cb09e59cc6216b33e2c4227e8d51a67313e6c8867e0b7c0b75c2ba51188a35774d47db143946cd84c2d7be7eb4e2e7eacd73efaf19514482b4e9c38f7b9ced3b4197ff5130ce373cfc9cefbe4d758645ec97173ceba581ef3d4307e1992dce88404cefde04627245097b1303779f73756fb4751fbd57332e9cea4293751554f697a4645354bf354f551667ffc56af11798188bc128c112cd2390ec8f2248c1b9d6498c2b32e367e61a4e2aad85904b2816711883fa78c65a5a9a4c4c4cc542b7f9c7a85f66a317623acd619cf629c857fc37a14a9ae8c3db38cc34d3b372435f972ea6818bbcf95eef973ac366ee340a3ed7d0373f8d42b4adfa303e9171be61f247dce863c094edfc1fa4876a7d5dd3522314e9c38695cff16fc56d088fef4417f3ac83f2618f3a94370fc832fd9929c8e4c695918a77232626689f3d719d8dce834039a04dce8340396db4e34c8eecb89062cb7bbb9568e0ada034295305d7eaebdaeda87d957cc49903f47902fe5468a9b4e073d89cdf86bd2a785618bfcf8fed303e23fbd11e41b4dd0829593ae6c249d0d511c71060cd23445618401bc305285d39b30a6fc90c21b2c4038b1060a0a59114d42a8a08c2c090b2f8650218a61a10a8e1526807892040f5e040a667c4102ca862356f0e40535375071048d18ce88c253d0451070acb8e269862fa8549031482166c105142e40a451c3b40513152480a429cb17305fe264f10508072a6e0843090f52ae48410707aca4008397308aa82269ca114978216208282772d84245c914153e38f2c311355232486d21828c1411569694686a83830a114a0c1459105e88c8b2c587401ae2043224692203032da050408ca5a732b6b00163cabb2001214615306ea8a0d2848c0a0b18f3a68b163c51d1851b59d0135f9e805aa2cb1a30b0aca0c1d2992a284c8001a5831000b0028c0c6da2e8012a88146258220a131534747144155c5a600514497ec8a10a064d56088a1f39f092a20548c4e042050c2ebe05055254d218424c0a6644c4a0d2e6ca130d462869d9820549ba2083ca155108a1420d4faa8cd9e2044f904122080ac880258b2d9638d13206016160b93244162347040184d3165862e00515a62e5e5400c09b2454ac30419c24315936587304124b901155c6970a6411668d0bd2f05085112c3b4e67c060c3a50c324f149da93244941aa09466b8125ec1c30a7c0083260635a480a444061c22ae5062a58a211c0538e03c6103103e84810ad3fd8ea73adb7666f0d065072d9698a2a647f8baab2c5e1007cc9724b210d1c3dddd7fb8ee5ee7e7e3fe802c6450c060420c0b989c9a2cb915272867dc70e2431247b840c19229ef14c393fb373a41c1c2094a1431c926723181a82e9600238a8b2f667c30e3c4142a8c880367872154d8f134460c383cf12107a71ed2c8aedc50c6892c905ca1428cc2cd114a34ed10c68c35ce43bce2c90565d84851a60552f494616202198098b2051967f22933c514a82e5610058d1ef1630a4eacc06e7ec65881154b584c31f5822698297e54418c982885b41c5d277b1773b44f6b1db4baab2bc13874997dae0fd786757314f452e50d2996bfb9a59b1c267568686888abaf9fcbdfe5ea1fe65c663133357247c3fc1d8538e55004f28ebc4204f20dd860c3a130a2b92e877ca0030f441060fc81bfff5124a2ef492ca8e6caeb9f635698fbcb3bb8eef4c80c367669ac6134d2f70eba1da5f4230a11e80413d8b0bb2b46c24a9c44031bc04004c104fe803ea56fe9903481c65ef42315d766e0ab55a4aabfaaa0376c3e472239a9e09479f3a689372e576ea561dc50dad40febc3efca1dc9fabccfa261ebd414dd9e822b304639b5ea33604b354c8a80044dff888024ac3226d1e24a1108ca862e356625bd542c97f9fd690c147d7e8f221271cfcf7d445adb177660c8dee491e0be35b9a63da77d3e73305226bdf44f939435a1245fc87b57f224fd23a756dee4b319b6e7e05f3baeb4b4f26fbee2bd8493d441550a79a09c925346530db34f567ebcaea583384a55deb43368b68773d991348c05e724ada561ec4a11888d3499f4d24146f257cf2f651dd40426127912bf51306497922b77a986b1d4a6fc8c1936ecda8a94925407f1c829d8546c989f712d2ee322fef0bfa0967199cfb8fc47e4992924532f36520d4b4db1e4b0ad5c2d3c6b1898867153c39849fce15f555ea20a39b8503f605897336e9ad5b7b5d6aaa3617247c3a4d720514a59e5b681213fa016c531745cf90171834f83afe7e3f9b8ff74befb6c4ef82d507a45736a5a97a3f3f68b77dac8b1dfb09d0f632cc1b008bdf67da258bef6873200c3a1d80e18c65e6008bb21eb5a384069efebc6732ed7bbe6f3f6357e6ceffa6e998deff8026d3c0e30b4b2a7fa4c4b2db5d4520bfc0e5c7290ab6c50d90ed211b25dabb531c2ae4eecea0ce93c7b453a9dd4b55adb810e0edd30269babf1fdf84bdff535dff9f1bdf1f1adf1fd88b7f536be78739e9b5dbceefdd85aeffae8d7f8b8b5883ffd5fdc7e1d30ac64bca18a40fdcec60d5751c964526883d7e0c3da23fbf0d5df4b52680350835a415ee201c3d70d3bb0c8bcb6c87213a744eb73be9eef1b163677655a50828a3241942185972b5e082926d0224394186a60cd8cd5be7a1f1196521699cc3df5cb7df752264db989aa7a4a33a39a499e9afd132c8aa594fcd5e788bc70354e5eadca55fff68545e8d5406ee2ab31952290ae54b9a20a1d7e4083872718c841083654634cc1c407ce9baa85e2134b494949469a8d16d913a64a32e686ade676bb714ae2e58612895c72c9b20e72bacd3385aa04505748c98c68a1664d8f4ea1082869aac8987803a7477f53f5779a213d4b3d8eb4d3311670d1470a94943152ce4caffbc1b7c626c4f890d4834eb19d04bae740ba6491aeeb6a5fc37a698cce0a8f5cb7d6187f5660eca13daddac70854b5a37be4ba73c710d7a66ad663d3f1b0da334cdc8903b8b5befac31d9767a6c34c4ec1abcbc58f659618b903d8b113c7f6eca8377a310323965fcddd3b3a2435bbbc41ce5c3940f9c40bbbf13330629b9b59b692a258b9eeeefeba614413d5c8f61e1dee596e38745f3d63703112c55967bfff9cfdde11cd9a3802ed0b34d63451064d932d63a25461460a1410d1c41764bab489646801861733a67428630a49cb15a92c4ddc5459ea32e64b14d306083763b0592343982f566c806345490a6250831354886982818f335b9ad4c045952553585ac060b3458c23d2940963441962d874d1c3130a5220065406b2a491240822aa64a862228837606ea0810728353b64315305144730d9a10566f830830112125ebe585a326bf2e58d143c2c59c2e5cd0bc26862892c6da214416b5a40d2e66929ca940bb288542c10e38d132931f040450999041306155792a431050592113d3cb1f1a24a0f4d7849020b075928c07b320176cd4dcca576fe1a3d4cf952f6741e9a6dab885f59c41f88a3ebd58b70b3574eb0b9b9db997bba4f8f605c6b2de49e2e9925b717f41d0c27bf4bfef4aaf9d1ebe8d56eba1f31f7286b7dbdc009d28f8f62324b8b893e7f68a08c2e1be33b681b268bc8eb3fa34b1ae5ec33e6fc413f82e15cba51fa17bf460f363c9a4773e97a8c3d264e173899b061779fcec92ca8524a194197525ceda09d178e1bfeec365c3574726c8be575dcb6aa1a9d2e9bdf775e19b082fa8d64c3c048b2bd281ccfae32173edde973d5a646416ff797d47dba9be93238e919ecbc2e8e5bbf17f5aed81bd7c6ad5fadde95d8eeeeeeeeeedfe6ee7ea373775f551ff3a50442826bf429a0ef5ec1a8b1b3c60cbacbd8943d123ea7524570b58332989aac1d94c1ce7d5d1cf56a33fe8420f046a89dfc5c97ab9dfc6a5c6ed5c94fe7721bc775f2b397eb3816eb72ac4e7edee55a9dfcbacbd94e7edce5723af96d97abd1695fec21bfd5e56a74f2ab977375f2d32e6783c3c1e1e8e4e7977b753b5dcf0e4f062c097a04f29f24982db3a0f4d60fe7dd7eb2e673b32a09f3a74baed5fb62049adf7ddc1168fef679049abffa2ebdf3e937c1d8b07a1d648f650f5cb4c04ad3a6eda0d5f3ecaca6d55f7940e8afbe7a2368bfd264055d7b9e40bc11fc35309ceff37dd2395d42c1712dd7e1b2711dabd5aca353c36d546f18fbb8b85f5c9ab9c19d4ea78beddfe9579c32827b7a74b658d6e1c23a5c6c98808b3e21cfed79f49cdf95ed392e5ad52885648ff8b1fbedb747ea315f5b45a158fcf1bed983dbbe212ec81feda9b8418b77d150384cb9da8eab758fe8f918228578496a1ad357c209f2477b0e220b6e747a82e6ce20512b540d6dc0e90635577b1d1dd45afb1029c44a55d69e80a504261f2e48210e1c22851e267fb4a794ce15939333554aae6e3ac358acfb0db4f267689b45b4cab16c967c1b0c4fb8ac24c6c61eb107d562435288fb2dfcda7343be880bddd465178650e0354d607d0b7c817b1658e58f07beb07d07be14da5e7b9814e25e0dd328a5b3cee8fcae706fbf21f923b9efd508d03e7a25c4e48f86c586dc5f9086695f6badaa433a288c5445b4201d14dafb392088a8de341cdc840c26c66e30d958ff68cf7d0c22853850d39ed3945a882669cf4b53aef6ecc50a83994949da132cd7899a1bb296abb1ec6a3f3f075c81131432142668b11a38d8f7a148b26f1c996106a56e336c372b7152146ed641f1a5fcef8940f25faf57adf6f5c3f522c25783082459e81bf6f468496ccfe51ef25ffcf29fa8b04e4e96aaec1aa6446906d7228a2a89c104928a6ad6581b255d6ded2c16f953b380423936a4031cda428b392591e2cef99d6b4eed6a9144e9fc9c28f9fcbcd1a909d55dfd50a9e4976eec76a39311196ee823b7b0f546a724342ebf908f2653f227defaf5f40f3fdfb8d1c9c8194d92dcf0d5682a1763fc18dfdd3dc6d9710c235657ddc69c477fc1e2c72a7fdabaf69b07448ef0b36aee6037bbe58bce9e1ef3282accd97a130d8d135786b9f239af484e217d18c095bf7945d20316b4698ab3658d1459f408b2a899e20353122d3ef4e86e0713c6527777b743018557f956cd755a42e24a26345cf9114ac2c2684154d5143a38b18306945230450d0f3f50597124d62c4929a54be94337ec969081c286ae080f35c8ae086088d4550ad377fad12341a3715eda183676bd11e098a31437c371774bd9fda33d1fdd7e5b4a8f537677fb953c3eea8dbec4c5b882cb2f5e1eb2c1d011c2fa73dd52babb7ae6a41466b5ba715bd8e5dcbd87d5e970b1f4599e8fd5ede1d2e96cb13a5c727274745ceae8e8e8bc5a9782e105dc29a560f86376e3ea0a2ec6866e85e5b01fbe6a58efd95f3606791061cdbd3ac23cf816eba3d703e3acb4e3bc07749d06dbbe59e9af3ef975a80ee95829d3a4763dd574cf5d6b8bab8e8b41ade72fe2ae76dc71afb655739168e3589edd581eb76d5f8b8b44abcde3beab57d471de77997f933f8f5c3d67173b76dc5980cbe1e76a3133c72ef0c1285f14465f0ac557f8bad4eb3eeedb3eaac99fe1cea5af7d473baef4392fef0092a334a6872bdfbd4a9d73eafd5f67d8d6e473a55356d662fab8942ed79dab0fedf6d9cbd335e7f394f279b4e4332ad76afa31ecf1597df872b9cff6758064db6aaddd57df7d56bedb8b1ec16e4bf9b93c82dd1d282f88b8ed75d792096eeeaf99999b89047932454418724a723454a41b73d415f1c0100bc6d55418937b949d199d753ec56b0e6bd1ebce2ccc3192436774a60115a2e7328843c62561430da870c834a0a2333ac3218331b973dad7ef5cb81cf7914160bda7c4867c85b86efdc9712c66666666e6efbee9baf50b4fb8b5d3de7bef2b83b0bdd7018966803093e572200f1021ae6bc460e82222e354dd0d3c9a5137ec408e6d45a0c9f23a6eeb408d40f33bb08a40f323687fc8abcd1f5e5df7c40ad966cec44cacb4c45a4c13725a3c29634366e2136891060536f4aad27855ccc44c695eb2384155b39e1e1e26a058215436e361420049686c189bdd8ecd7a04901454c5d49383977c5aabbd71f8c26530ac3f9f58be2eb442dbaeb9f06541cf71e5c74b3b6052287677cf4fd77df9dc3999ee0aec0f86afbbc45ac2ba96b8fd5d9b353172c8dc1d8ad71cd6a2911c275608952161c32d6a8b8a443d54966384ca78665ed8500049b32b80a49ebb4551d916c533cb218b8383a9eb9a6a888be3c0edbbbb3d8f1ec2b7b33b4d15725d76a25821db8cc5840de3368b55705db659a7f4f2c1861d95d2eda8866cb34e0907d33cc3836d76eba5dff1e5beebc0f0689b41b17cb9ee30c7598b7ceb6ab56510daf862cbb191831512a7aed19cc2865bd4eddfa222514c8c0d6dd8588a44b1db944efd28c0e56d895b3fb4f1c5fc6297c1c7e24a63c30ed2dc0e62d7c652046a1b4bb7350a761ecc38a662a494fd469201add5479f703a83c033c2cb38716e64dd8eb162b1310681af7c03ec883c31deede57b1a11a45017f56d5fcf8b7edc0d5c0d767ecd91ff52c87b290f1085bcbb0f61205c1921c05fa70e183b148363e76b565207dd0a21f5f077f7399b58fc0b5d39d5231191fabcfa1adbbbb8b7d1fd0d2222dee360fdabf53bf633584520fafd197c75e7f5511cdfbcf1b90dd7d73558070c2debebbeed5b7db55ed7fce3f8b44476437afb69bdaeb59e88afb8fe2d1211e9e7b8efef58fd5e2422729d1589885c8f444468ebe308449ff57504a2ef7d3202d1ef3eff6604a2bf7d3402d15f7d5a04a25f3fed5b45200ac61d9e316819b9b8a69368d374725a2caf03c11adaf0e8e8cea76c2983165c7d46fe527e12a55b82081d149d6a90bafd0fe820e6da75e50b91999d9500d68705f09deb39fe1c85aafce9f7aeb23fae62315808ebc0f0397013c1a54f6bac542c4117fdd09ddfc1f9d29d32b3df091e75d7c94c298f8c01e5d163f418638cee1e638cdeb034de69f2e9cfa9b9a651f0a8bbf3ab26a53182ae4d6845a54bff5a5f2f5b411a935378f41863f418dddddddd1db4a25d6070dc13257feefcf1c79f56b76de33ccf9352e674d67349d9ddddddb2372a74645ccbb2778db13b76ecd831ce1839c6d76cecc831be4e11d17a54968297c51c3687bbd8dd52b66cd93272dcc969c37523497e00d7c5beb863e022171b8b74bc8495c1ee8b972fd68b0dbb59f90397b511cc625d5687086b85b0117ce90c613516ed7670b033976f5dbe43ab5e11cb6e57b7596b86fb584ae92e5dbafceee62b5ce4963e5b54ebb42434ad565647d5252e36d792ec6a297d524dea7491dc59e1384e82a1656eafc8ddfad046a037823502f9471df10b637352a7d53badd3748ab0b5d6ae6259753a32ab53840debd59159aed39159aeeb74bad8f85d04bda239edeb599d8ecc86f67a6df9b460fea3c7da9c9cc9e9e8c418298d34d2487d7ad439c2b10e1096257773338356a4c03949515344531818dcb0d214563a338605ee15c60c3aa04230db6664b46cb32494b0cc6474206189534e53e29415cbdf1d2014411495c139a2329f46723245def8a4841bd78d4e5092a8373e1d11e5644bce8d4e50b4cc392318c7885fb99e0f0a4eb3f13c9af376a26660f2e49348bbe9cd6e3437c8e59a5eaf5a59d6c4b2d6cb8f9b8accb0652d8b9a1333618cdc661951d4e206d90ff99b6421d2652f4d1dc4ccb20ef2c2c28c0de96032d4410cf28c4095305d6eeb38af88fbeef3fea3d6b55aad0ea25a694aa6d446298dd72d254cb79999ff475fd63c0850254c1e09fef2fd3d9ef287ffecf2e9e0bea1b6dc626630f474cc96eddefb7e1dd57bddce7bafc8d3220a3d19b7e3e5b8468d1af22516db545a9448b4009645a21a1f85b53f39e6868662b12b613c3d1fda0d5e8221f773e8c51f573e1300a83f2000c74610356554e27f10fb800040393ce8e00716236ac37130b8793161ef10cce4d7c881a50cd3524a29cd50a7be863a7535ee5ea4e6f458afe92815c492d0fc6e823b2cb06c3cf0a07b07db55195dde0e838b5555d786181bdadb45a22f56c990a5a2144ff507a59191e1341a6c34348c3855d1c24e54d85c9e1b9daa30b9de8d4e559074cec5539c6a82a2b2fe825b718c31c6186bb8ba46f2dd844eea31297bb7e4f19faf813ef8824a431d9330ff1ef3d33e2abd7acc4ffbf94925db63be045b90f78a08e6d34fa9f698df9e099c94d463be44923d91b0c20a2cb0f0e951a5907f9457e2cbda0d80f095ffc51f7b2a4347d571f9e398101070f9636d80931ccc343d81a58c24c8c4d853b5e96ae25afd0a0c7908795da3d58bf0fc5f031af645ad011d2442037ed2e7ea156d200f7501f4867dcf65d7aba76a93b694425ed7483ed2fd86b94ffd62e7d341d29b55ab5158665d97a3614dc150763afaa7630d8b5e510e9f1072b8eb700ae1e698c923bce4de6ddf71140c37bed2a95318af9cae4009bb9dd7eabab55bba5bb7da5ef341e16a91835b67124ec5552b0f5f62258f79020a1ee475575ff8721748e5e564e39407394187b9c6a5bd9df35d07511b3d94f34b2a1d726d3a3f7da8df06289f3e39a66192a9841955575dbddc90a953e57de4f758ec02d661d679fe960ec86e6ccef74a4a923bc831217351c5a1b5d65a0d8c02a34ad0b45707668030b383e512aedca821830db98a4d8f8b4afe74947c6ad8042553c3a49516ba0182107ffa7b5c32bd646aa87ea6927287084204e207532291cbb0a1a2aaa092428840fc892544200986d5ca9d558b1bae6a587f0e5646754ec4a8e853e4b20b97b59e2622843daf69472f0d04325d90de73f98b21ecf6eaaf9473d6e90ef200e1f147baeea0bbec113b9f5c60297946d07e6a600326330a5d910e7aa1832a23d9d1813b1ad67d7b3d69076e600368d55c08a3d4102ab1ea1f068bc58656b26a3564b5f2d5bfd1fc8625a943585b76c474d0e9c31a0472684a49aefc1795252e89f46ea78a0d7dae7d1733f3774e867cd55990737ebec7dce21c90e1b09e87749fdbfabc48e5dd37c397562da8edd8a1699a4f4f13195cdc6fd84e15cbbf238b0d2592db0f874dec1d3bee04976e18a93a898bfaecd77590e17415cb495509ba724d5b813d4839055d7bf5d45a573d48d5fa9ae370429078828e2027d41fb55afb02a76e3b0f3f92fc7846e0d004e6f1d1970eddf67cac2e27e930f534e59c73ce598f8678e4235df948377e8c1f5aaf88412bc1d087a5d41fab3b94a3431d627b8f759dab729d943dfdd3ec71e0ab0cfbdbf60edad7ad462e3bdbfd9c3d127c1d61bb86c58eb8773858beee5af44bbdd69a0647398dfb31bd22ed6dc4f9a3a12b64e81ab1d6fd77d3f3e11569cf0919baa108d7c803c3a1ebbdff0674bb50c7ed3e74ddcec6ed34b85d006e67856c78cfbd8d2fe7e3aff1f1b7f859cfaecb60e8bdb7bef0c301dcfe29c9b8f3e9739fdfed8b577b214337da9e337cadf5b78fedc6b92799b7f87760a4dcf4224ee3a66bdab7bdf5d33a084cd0bd76520a190303d87e4e722fbf1efed83dbb94b85e5b78f5dad315a594993257cdf5f26d7bf68ab698b6bdf6ab0d8c9c57b4e2557da7226af5fe311816b9b2f2e5f7c26afc77d5d9add6289ffee9dfbe9764f0bdb04b347430e46f7b8e3b28ec065a704363437faf9f517f91c6dda5405cfaf7db88d8e5569ae62ff0d5b4e7d1ee40369752735daebed71a61ab2277e511b602226f5d316ce5976b956082083de70e1e0dd3c193276f0085331a0afdd3af81216562431e1d3b44b82197d0b07e13a4eb46af2848e7d6a53c725d0a86dcc595200f101e1f9ea116fc1af10e399dbe06b6e03782a09454ba9c1281e14a1a7b59574e13552d275eaffee9ed4317aef602f76af2eae1aed2d7dd3e2397af9ed56ae555d0e78a75594dd334d09370da470d8c9aa6f99340a475e77791a86acc6fba0f394e8ae5f11e293e8d7db9c53382f7ac678121eb3d16eb0bbd1ff1769f7c4e364884e3f8e8d5c7dbd42ba254bcdc9d1b9da4545daeebba4ffee643f3bef047bc4647fddecff74021fdb3887a5fdcdef37e38581bc6fdf6c9db7d2c14719918294c37de7837fa469bf4b6efb46dfb7ae571aec360e4d6e7fad52bb7fdf6c50a64235023794cdbb8aef41a89b6a7bfda2251f7f4b948e4bd17895a4f9f1589ecd36f45a29ca76f2391ced3cf8944359ebe0e83505db0c8e487d3c811683ed732024d9f54ab35c09c0824bf5f27c77eac0824bff57911483eebf33e2e02c9efbe6d552390fc950e7f2035902da8c3920c8e8d58a91b76ddc749363a511973f949e01a29e9ce475ae2fab3c0e3ce478a40d168823c232ce0fa2fe0867de506bfc1eddf200467dd7e162cebf6b338981fc6186344e238b7bf1006233d3972942cf91ec78d78802ce0f6b310673782314eecf638b53078e84f5a9975c458c98d83f67e70702b4884835b9fdbba1889a60f7b8970958fbf4498ea560681198436c26dcf7974b2a6b7ba0881f9dbcb6ff5dbe71b7bfef50be1460fc8fcb0a5f723845b412221dcfa9ce5ada5b340d0b705aacdb6823e0f10faf3a95c3d0701faabe75bbf90e3dc1ae7aec0f9f56b5ddae28ba4311b21938ae21ecdf0134ce7083c3b59e570aad8c10c942a7a7ab15dc37838ed203543da3d597f233a5fd3420dac1da199a8048ef6543909d1d808000040019314000020100a8885229148301c2782547d14000d7f9242724e9b0a84498ec32886829031c4186088210018438c199b991100f9d2e0966d7b80bf87620cf1ca882827bae51464d18a9364808a6c8feeea19badcbf01025721092335a1a0c3a936588296b992a3afefdab4f9ed77634a0d9dfec9c5369ff8d122c1b9953b11e4297091d14df5578527a5f10332d7999c6cd65837838a85bc0c470e32b7abcbe1e530b5e86a9c7cc6d6afa301402dd1fdc328d085057f7a089410e92fc54cb3bfa371ff511258975f54f6e81de66928b1b9d3698c9dbb1f18cf17cdb88ff58ca3c4ac52727b21ceb3ce70fda463e45a68ac7abf223a78d7547c7aac9da2d2330c3c44c821413dfd1f9925923900a60ef2d05cd2996ed33d3e9dc4259d47b450672e11d26b90aae9c7e966b29086d68ba1ae0e30e64170ec39e8d078603985ccd191b0aac929c2fd63f85776ef87c0024ea528007afb730bc10061ea3b324523758a8435757ea655a414ecc77b9d44cdc2a515ecab091f4c9e4a7f69a0fc29749a5ac18c9e10c09dc3a90d6a322ac22d4943cf3f087acf34d386264ea1f04600f11665351edf5ec9783ea1a197035de640ca1b63e492b2e31391645e8f470f643920ab2ca94750979ddd40bdc12bc91d773365f656750d137d02d260cd01da065e9e482348115933e3bea5f6e5d965e2ebea4faf9c6d2b292bd4e072d3f4d956dbc84431680f876bfc4554f944915f469903fa59a38b1e4cc2ce5d1417250fe936d4dd9a0bb6458b1378f2c670601bf2a72774de07441b5e2044d7991c260049ac8afdaa9bd68bb56daec75dff0a9aa4882aa47ceac05de874085dee94c1a08bf125ec73202eb7481027d626dcce337271255d2bd069ae4795ffb4cd6e4bc5725c9a79044e7485e5798972187789685e55770caf92d69bbab57504ffc7fd8e66b9d47c7c25dd5afacf366fc7a6680f942ee42a3d885b4e71e9544b34f4dc5a7a75b58ed8971f37c2ff7223c33d4fd9b899a9af5f410112cdca39cb9f5225297a7a598d86aa8a5b59a54d95c82de52f98ccf708b905f4a929ca5fd40c554c51a404375c572ff71d2c7f959f5b7d6d2db3136673489276b16ee04efbdf69128ddc8ed22c9fdf7fee8b261cd43184a106292b84a1df8fd44218de3b0b9a8296ddcedb8e2d7982810d407818d908c8364283d2ba35ac69c33df41a103a8cdb28a083b0d10eb2243d69423547501a32ca2dc86802bf99766db955d07c7cf41bb4c15d476d9c22d3920810a619c411bab78a3e703b0d288321609f5ad88adc2fee4c8af6347db640b17cfa84f5fc98a4d37eb708c37778144563287184a1e0add8ef536f037e8ec0544d872b326d9525e9a856292e25844826f41e64eff95b4663c3a13fd964c7a2e3fbc97b27720e701b1b8ce0c50653c8dc20d6db12426163a69cd9602c038c5a991b2539f6c0994a3378fd0c9859f4e6617c8ec1b76593baf31c4ca65baf3690d35f538bc108cba63a156c77ad1b6b7d8492729523886dbd0bfd4b17a58db646aff58d129f4881774b7eebbb9cf033161a649332bfa11b1412c13b1518346da0f033e45e4760a4d5d4749f8031d676b059e1b198a164f7e3aa6f88029197c372d2f00d481aeacf3949185294f43e967158f76c12d1c2f2855fc92bf1113018c20680b8ee8194368c54fc87c480d15809dcefc4c79c36c80c65c88869318697e634fb2a7245ec1da81d537c5500b181b461b6f9d6a3eba62cefae4a12708e227a12d4086f362c9dddacadb28c1545f5e90b2a579a44f1c6cd19da8b99e53cc1380113836bd4d809dd517231864d37448e35f0ced48b223629eb3623e1858b86485ace7e7e445ed98778b17670fab3edceefdb468dc4ee73051e5ed5773a0a78b9f879f9c3279b6ed7504a613948f51199b60af50b18c4b359d8e365bc39e6c091f73f225a489d2290b120399a418dae3ab1801ec19530c58ee40a8c111e51d09e7c241b598045f52780e6f8f255d019f78b38fa582cc69595b56269591e0ce04656601a1869f792009631f917f18e9911c74622e421a31a854d9edd2180fd118a29b5a535827f6a998a7aad84823d62bb1227b8f03c4dbbb61e70054cabd2c2ce5a3c994a0e2ea746a9afb72da53776e8cb2008b57ad94c2b606215c750a399f886db6bb94ace603efeea2d649bf885dec950ae5dbe2af09af0e47a52e4d774f17070685215c5a171cd8b54428cec9ad4c6e2920bcaf4bb6030939978ebe478577a9a06049cb03addf8a2fd7dd032b4c539a67bc23cb53bab896363f500c7969a9ff0662eaebf68c11537059f28fc0ab46aa3906a25de1a20d75e3cbe3cffa292105b0d8d62c71c75e57509c87c654feb186d6d5d161134cd3a9771b7dcac7b7b3b02083bcca3e94d9d584c056684b07de9c7463a177ae687d89d7c163ac664692de60a2d857c0ba365ef1910b86a3c0902de74dbefd52c0805230602b72392d1b1e81bb2ea60be6f6d929f3b168b41daaa53538de4a4989d930296965dfde1d467a3af1fa78ee3d5e38f6d2327b9fd6b0e9c562de6fed670969ab95c2314d8131e620c59b650b28ef1ce0f3c1ebcaa98952905a36457fb14aa2333e034521f9b38e051e86ca3508ca511c15d5cd4d1353e779a68e7c71ffb7137c4b0bc8a52c2740c4d6f98133b03e3224ab802470dceb5a66d1a6017c91d449ba1af23d99e70342cc0e5696fab5ae7091a0a74b6e7881143aa85c57bbc1a02ba6f9cb009b4a299b5fc38c1f14f355ac5960747d429cc80374a06ac659e6a6c74ab09120b0221ef37cf48b0e0ea097e7fad34bfc2a9bbad4421d3c4f1ea4352f12baa18e67e82f74bce60d764608099f8a11c8d63e334482671078f66583410effb00ceeefb59170ebf41eff3194ce84717ae83c6baca9d4d853eff0625f3fe8ad5df517094403b96e6e4920f5257060f0a212ca9a774efb0ab5564a1852c239ac233263fcf10bd8b2c83ab69222347ff70e78f4f2576dcac78ddcc06bfacb120802c612815d99e987d97bc3fd8c293992910b87bd8935ee4e136971d14396a26aa3a07c2547a86395b511f1003845234b4b90568d2201333db964088686e6df7fcb4530e29c3c3e9bb24d7f009188121579839ce239665a85930c1ad9e3bed35d41c0b7f40d0226ade65cffe87bd645d5f46e11e9a4bd9c6aef66f6d2d0cb7ec7c19810fe56f3543db8c6f29f655b8a57dfd0556652152dd554ad1d24f79f3bdd93348cbd8669d604dbd3657c3a229d768d1deb25ef7edde15ae2a4dbb93b8d5fad316273459c9befc742b4a4cb8a221b51bb67516a575064215db504e686441a1f1ba88719dece7931b4545b80c93ed25d651ae8bd0e3eb4140957998135f27699822aa15699086428df1d2594ff8c13a64c4312c1133350f2076bd724b14ffcbb4bfa17e0ba0184590c3b07cb6a0b33ea01591624f064ee9478627ba4a8787d826d85f06213a691f0b821959f3c5cd52322ca4e202909203a5756a0cea3d8dc917859e4783c6a8495bd1e87109fe866500400611ccc642f0991e86d00d5e78a11513313cabe5c178d90563428deb404bcb3b65e167f226ced9288d53dd753d82532f82d03a3c32d2e6c81dd1619b6a3e1b7ccfa6a66af97211e1be24a9c95bc56c4f348adeaf3ea4b784af25218f4712df57121fd7e312157a1b9aaecbfbdd92ace2b839231e77d914b53283817971a00a6da0e6d0b2c0420e5324cc15173ee5f8355081b4fd59db3bdf489fe6162e49c511bda21675738145c8b983e4482423df301bd19ec2a2f6d4197b8ad515a8804e8bb94c116db1119544a73d5b551833d5a412c8c8eab0b749bb6329a1ee8b6acb9ee6a759a4299f54d4d94f1049e090473c7c09d967b42ec455e7faa2f7107420fec90cc596109ee7c040ce2633e6d4a878ba75d75704ee6abe5483eda009df6cbb247b67a204163e8391ca606b5097231a57eb1dd7cb9f1a1086ba13fa7a3fec9cc42ed37d1cf25fddfd7a98e8b50570eb1c83ada4b75cedbd21aec1fd9a8aed53a2fe133350d6fcec562b0ee414a3e5b5a2d9a997db169a3b39fe759bb439f38c8fbf4bea07a8492eead2f39d8a02e105f885563abe73f6184fa28e5f8a83c101d00a5e75cd3f9b55686f0c4174890d2f050ad8e7eacbc653fbfaa52381c7e29be85d2291998199d4a13e74f7f57efa769522d01492ae606a997eca384d0d0bb0d62db5e3ed285e9e11a1c94a1d589456e0ec73d105364d2d67cc2a6d85aa6294ab659c2ccf6155191ec5424a9b6e8ce3ae796151fe805e672438fce84b018204801a5518ae67ada80992379ffc3b46964e7f14e1c2c558463f42fc6e34bb4726945eb29157d1f09e95b3de00808806ca27bee687f389b17c0714d2ee5ac65563797b60c0e8149a72634e75aeafcb1c62daac716e844a83a1d990b9511b0601c85b7f9d8469e62466156ef4bf295b508a6d1c7711254c60f6a51bb38f72292901913ba0b3544cf4884ef42f368fd338f8c1e70ea5455fa203856a221205b69ff20af0ca87778a2f16ca4bc1809d5e42ec1474966204e66bee960fa1dcb7c903489609f250e6d63780905614a381d5efe4ac68b1b8feae78b856884bb04865324706d76da797ff2a2b452b779768c24d55066d64be553f5e948ba11af955af1d89b4a1281c46d71704f7f28ec3d475220aec5708f6c354a0c6a27c0c8fa3d36d329fd096496645070d233cc4a73d395788e569aa8cc857b82e7a5903ba9b43fe0caa153d20ae2e80c96b11f082a1d268c07f3397fb4f126b30e68bcb26c010f540926ed85a7984a5843d79d8c4c31732aa01ba874df778fdd21cacec4187aa816395d4d9ab2c38f162880626db55cfb0fb6fd62934e4759743175594deaae680e8c7000cc3204b0316712d6534b113db47aeb08c4c33ae5c29db42b916cd2bdd627d33f8dca67cb390cfa80a0e6aae8503322c44fd949b5369299e3c2b7baaea149dbf08ce3f8268c3f2c63f02fbea714a15e5efe2484b61384124c2d30c1c99fe328467a6e8b2196737f644af829b6ef1e170e9b44d49809ba8d17ef7f4185027d3dfd627ee873b0bb41dec9149d9635ed6834648c5563466488a935016b13a23f21df1c0129e58fc1984ad951261f057c9d1296d0a23dccc7fe260d87c55b74978e53ebde0f3259a550825245cf9ba89b295bcc6f0319dcd6b244ff020fefa49d5287854655c8e63abe002817694d7f5c61a308b495e46d2ae73ae2d812b0633ff74728f7841f0b1011a1dfc7cd7ef5adafe9b9a496343bb7d423042cb1f50257f2a5b5964680ad5197134b10c8fc0804fcabff15d8ebf2870eec7da007a5b6be1bec3d12f38a5b09ff477140d0b5d132c517a35e7d1899d34d788610ac3c1e733d42a76fc3574cb6c9600e2357c4f3610a1fdfede6c5413bca649d378e71fbcfbffc487ca37db036c929f6e03400d438b1f1280867ab3b2250bc21460bfa65893145e9c74e6cead59238bb254af859951d8bd442437773038d8cf160991dfe97afd6d4fb797a9737200c9d34a9b2609c6b710f25722f19ff28af12640bac93fe4d05e1111445d72eb1b93e5c6a688c95f066979a3289fafa1c59d89c25c4bdcb3bd6c538d0c5295d11c7e8c0cd9adc498ec1fb9ad30522449d5e9af42252a55c43952dd561a68542fb367d234fef6f01f57999fe8fff7c8dd4b552a882a688d8e3e53acbf2d9444df2ec8a6b70a7f52e85f458479afbe4c3746bf2c95cd125fe1406e16956d00c0591f4185cf08db9d23cccca2899dab6bfb40b1f81545bf27c49cfa782e8ba6323761878df1f98f2b80bef95fe6c393371f65cab8f9ff7e2fff4fad466764be759e35d0f7143030b5b118e4816464509ca23c5bf55648464db3f2410203cc16f96c368ad24c3f21572d4c10f69532fc3461d404ee93e8634a703648c04849de2570cea61d0e37d30dec7093a41116761014036b63d3f83c212d05f768a57124960f9df9a7dece8be551ef6178d2a02f41a286b6712ad7e0f9e0b7ba9256346ec0bf7d24398f68e78f8d9f7d4fd2935d146425323bb35b05a1197b2b425fcf2937189bc5f67c09249b380cee0a2b1e1c76bfe8fddda5c48051b3603b5419ef5ef4ee50660a39a0101f18175ae86cbbfa146a74c1de8de64d0c63100efa83975a1338492fb1f77c5a0e268aaf5e9f44b645efdddd6903fe3bfde48a261d2f9723b016f70e5b42467e3ab7266df895ae31c7b330771267ae1037b523fe408e2192a6907fb5fea6efbb098f049b7c7183ff10ff790cc9d2522065b65e279aeb219aa7d38b21eb4efcfc5df805817ab5e9d7b471d6507a2e4f032f19ec0bc316d4685bdc364c1687d587c492cb7086747c9f8f6f85f6272f73548689fbbbdb20cbb3ef9bafe1568b7750566a529f698e4d8661749fd125880c3ed1c0a6f931b43f3dc43ddf822aab6e80b106740d1aa236ad2d1a41d709913f07d6664aa0171d17849ddc0a8b0ea71cc66cae39f3dfcc6b88a36f046b923ec6d1fdfd265c4b448677caba57d3bc5fb2ef9b64d30431ed362343926e0fc3f84793a8eb38f469cc5c934dd81ac296cd740cc84056958a4d85645dd93056b77eb0ba062bd412d67d7b87bd12ec9908b83ad6f0474b12dc2f20ced09360d304c08deeddc94f13639f414b65047c4afa691b95f6b730efd60c0f7726d1750f406a99165209dca46a99531ae2905c218f7df7701b64526e1b5e839f638e07b0cd220039c7808af900566975d2dae3381d1c6c9f3af2b3e605ebe2506bd839b42b25109ac962c1a5637c2dbf26faf722d0253e9cbe248b6ec43cc00858b394a2c9f2bad9c0e7dad6b21a54131ad9ba842ae0f5bf4db2a837fc84510b3eddd02d1567f149494af95ab605290f4c5947990cf0a5dc318c8aa06efd562fd5681d0ae6f08e73255560f06a3354b2a2235c68adebd53f3d91f39a9501a73adaae06225ecd56946681c1ece46224b65bd95ad0b8485dcef550cc634c1809446364dca7b515552c7890a348bb38a32f403e3741474adcdee6269dcd849230a4e32932cef491dbd8572adf9dd507a830c10f5b4de8f05e315eb32511d562306f557a13484b56eb780ce04d0876b9ad6e2afd832179fcfcf06b506c8d8412503371267071d85909de8bb147153061453ec8d7f575c2e9d1512d0bd93136900eddbe59be86107a6c328c6e96b263725108d96d857814cc1a05d58e2c263a3cf159c3f6a7c14d818a2706287c6c855388960a99ba2847572ca6209185fa5126151a588786fc099d5c6358fb900b274f90a1b0232eaf39431dbe07b029a571d7ac175edc9c16b8b9c3c57996450c3072c1d98781bc68feb2165f2251b33f68eab9f4e3bbf5c379aa001ca31fe3961fe03f856948fce8aa2be1f885c508b80fdf509d73b38f39f0b6ddd6079c747e1ed347ef81d15c952d997905aca7545489221195dd376364458d9e89eb198567b31d20ca5136f549428d15149135a36a635c55ec08cf109f3ccf13d5a012f4e780b46bacf1550d041a0ef1f2206cf49c004211404824d62a54a6441931766ceccdf42845bfb0bca5ce7fa97caee1221da9df7e8c7915895e58d60fbba2881bb2a2b66d2742eef504c4eaefd84cc0c34f9da5873e0e1a86fabbd734f7c2d0174c1a255dcc50264616b0771ed4837640b9600220204bc4551e14a2732d627cba0536c54d8c140dab85d0b8d58955186d0599f3dad3697e555ce03bfbb370d73e1713254217c8d0c00a0b81f777079c553104eb9d116cf4047905cc22bcf7f51da3f3a99ea8cb8b9553a62a1b59da438c774f8657bfc11417d2b34098077d70fb65e428a529a8f77a8242608c3df4fa5d83a34f4ef36abc5ed9e2f42fb26af3746793778cd562a65a38fd8980fcdacefae26161feeb656c8640fe8b3a50faaf71a0fe3f4b1fdcf85b44943b623e41fa09310941f8cc21d9d109c75039740c901e834d88cfb9191ea2db4ff701f9205a8242b3ced88b81ade9fa72892dea24e3598c566c7b288dfed7268acf94db899a4c909f833468ed0445ff87baabb1159cea2b1f21030f3a216df134235d9c1c9401ead39653e0c3fb08512730d30741c8758dcb9c0484866c7cf4dfafaebd09e08ff131294bd6ee0fde71d3f40c82966b9ed134ece91c75aa6047e0016eed8268a451d9ef6cf002337b46f1b7fe1bba7f4309c444a03c82ad4948f984af393ab0c64794143bd89095748f8edb8e0ba6f5fd15430617583e87b054db8202719475641cebac41116971656cc2cd282407f63e078ffc87f6f505a8963891e77991bc672d166384f37033d0b9d9268037ee20a1948dcaadf9ed59b1e920a3e13c5d117a21dcc571d70caf4a922fe3e766e072c2b9e21f0b74b04e5e39b22289aadf6c65ac32551423327755b3776b86eaf51a47036a0f4c6da9673252eda3dbc45c5262580e0baf9b1c5385f14945dc24120acfa98c14567634bd33d4d8aaf8bf37f6014624d5bd504ba70a0b2d8ca282af2c367cfd5b48ae431c3ac5c817ac3674022db9a66b0353588048f257736d37d4310eeddf4b3109915c32d4d049999a4ab290e9e574aa4bd86d55e86eb6cb230ac6da9548219de19a4c59876b1b3cb57e7c43a7db25164d6f989cdf1bc6f6790da69b3579a07e95163e186a0c0406191f4b723f970aa7449073a814879d3c2107a393f4b8d3a5590bf7c53eef20d2f887ebc475d48422d2d1d7c4a0b1602edfc2cbfb67e269c805844f5f674d4b61209f810f24aa74b08a8b95e7baef2369348a25e62e3595bde3ba2e2c42fecb765144b75c9737b411dc7c97853711efc48479074c3ac5ec8614d99580b4245494c74f67c38a90b6a0c28a9cd288a57ba18f42113a33bd75661add9a63bef68d9017a27fa7bf33b500413af0032ad1df94346f014303749d1c2bc18d830399cfcadc39ba6cb7be439f034ebd4e70f96d1e1abbd91554c4b5d0e7a27f897b66f914edd89b9712f4a73fa0f8095e4c5f9bbeb8cf1d38bd6b40c143bcd850cb8b59a07498b162fb5192bf2f7cdd7541c43cbf1fdb85f78986df00790380fa8797184c09c5e47e510fe72d4a691ac220e517def0d132947c4e3fcef90cc6ff6827a5d7f9503712409da406589756cb5403eb042b5514fcaa1c26378dc5578b55d5ea39bcf881e6d072f70bf37313142c26b75576ae333b1396be2b4ab08889cfd8bb73a6dab57700821d132ebb032c59a6794ce14da889d5d34c991e1138acd9ceaca2866b2b33c6526ab6b86323367e314702538b939cff0a753643c999cd5276601aaea3d40198d0172f6c7105e4355ca92b8059289457a0ec639009cd4a3d2b6e72d23e73e76b3c30908cc95720dc109dea83777956d1325d3f3ee63bd1db5e23883c5908025d1e2428b2641608a30c88415753be784777f235344084e83ad08bbb9a899e7b3d66b73b6ae7a210e86f1886a88ce6c4d6067e9d65ec4e756bfa94e8793efab25761560a5974ca6698600b1f8759dcef24478608a620122b899f26fcbb8a4269404b30855fec8b0598ddb314342428895003aa2cbd07816bfc001ff64e6b69f77d596346dee52773d58a1eef5062e8023657fa09a6064ee291364d1d3b81fb35065788509128422ff1fa9304605a059d30810415740abdee4e720fd4caea53328e97ca1928414e6afc954b079bf2498d6d0b9e65952aba76b2f18097f70e0b3cb83aaa0c8e8e7c963c49c498378a7c7a361cc71012355924bf7208514611cb8e9f836be8794f0dcd1b2dab37798d596a79ce9fe531ff6cfae4a552c7a5cbd51730231d63c6810a5f5b531ebea804aa128c770d6c5461247564b9988ce88f34d55b99b709000e3470be8733d4ffcb56091f220c451cc5fe8e275614e98618c2be276ece15895256f3a30a52f4080e35c2b0ed33b392a9825eb46608119b0ca0c250720d4950860b44b6fc70382a5824017e9ff1a7691316d405929cfb7c1967fd9061370a185ed8b7d9a04c958ceb752d863c3b7551c718df2a341ee13d0209e77de0d5f07acc830ca80b488244f19b58e9e6e489febdcfcfc32df44ccc1d9db57763f9bd4c4fce2a58a7f82de8a172224a8ba99b2c786749bdb06cfd19a49917a7bda14178ad2e70353378a289b9607471dd334b0a482b162d0cc3e64c64ec750ee952c6850d34968cc6e372ca0e2c12019dddbb05a42ea3865581122ee032482dd1b0bc562a2dd8981af201ec970e95dc4f08d7d4d6f49dbc1660c6e1705b621a8821fccb38391e90243946115b8d932b5f041483c4a8bf71250480a78488e4564cde6cce93c6c3bb9b3573885333c1c0253048d3dd73aefc3a5a0d7b5675b5f10bc74760a7d2a97b42202cedd29cd06003fc1e11957d5c7e46c165e8ac3338f610fefc82533c807c272fa32ae98ef26a2ddbc2889eb8532b38106c9e0243948e2ea28cbcd8b8f914db1ab34466b38db065130e64e5050dc45117cb4b7e1c3040a8e0e1f220a71597bd812de11c8116e06653fe98877806accc10f71585bf36c315cc9c2b7f33cfdc9c3ef8619329419082d206d34c79c4b7dc0e39910fcc31066c6e7ae376e71cc35aeba410cb22aaf12d2dca69b78af4c83da63e671d9507e89d35c747fb912cb285345a16a0b342d50d347c2c914079432323be310f373b33f9d1361563683f9b0500119543fd5c3307c0314733a46611fb6a256cb981a69fd3856cce57ed5328d07d5a1008e0ade13ebc9a64f08bd8f6aeb08b711e9c461a3a178ffbbbbee039e069c99ba20c5836fb1c7012422b6f2d6b74405fc0bc6709a42bc13e86ba8a20dbe3713f52eb014ca701bbeef4a0b891d0ddd2d7c1af20b7a9aa7e1f3acf4e2fe481a1691aae25b3b6e583e94b60d3a27fd79dd76a645e25302f87c40c916239db8b4716d7c2ffde5dc711ea13612feed637d31eabefc7fc163f1f9bcc285498eff66835e36af518e8d180215ecedb76d8fa0f11a67013735c3bdf6aceae9aec3105e99df212354d82a557d12cdcec248fb3db5ee88fcf2eb2eb1dc185ae8d264610f09a50e341db7f7ab7317ab0bae69870e23afefcb6872ab78006241bf10e3646bdda9a08d9592e891ba77629c8557cc64a9c185723da2d0f6d115b9f35440f589543c5a1c9a0a3689d42aa3dd551a48ec3bd06fe0dcbce18e15f8f82864ae9079e833ebb4547d57ef61e4e84aed93840be9d38b4ae8be687c1bce5c8671b3b0e889c640621f7e432236f4c689ca64c2d39a7254f45b030991bd623f054a5886dd470177540bed13af51aac6785bf18cf687f310d13bbecdb59d2e14f528c7891746e1c3036c7bdfcac68c16e1b8dc8a91f20dc85f4111b451849db18f0f10b38c184e69fb64afea8dffcec79f26b91701984336b4b031328298ac14096b59f53aa5357033eb9263066c91b5d98df69897c9d6ccf9fea5e2ed702efe902753389839c06a9f65791a3c41107ed5be1b30a48f48d3961fc1948641c0668a35e7a003e59860752c4cd9058518339e198c3ac40402871d3c440c40e92c8d0bb935f407b118ed400b80c2008d12884ab40c5f6a3a2d5c866a3cf35bd61988e25491865308c61d21ba28fe4fd32cbb1c9aa062fefd97b166a4685b749e61c13b8c1e82195299b56d1b442201cf5d7ef3f915cc20a7f1de1d140469ab273a1c795f4489b89865693f0f2691494eeeb91de93069b6979c71e40e430063056c373f9ef1f55ec10d1a696ba168e9ee28dade51ad9f5ab5f8fd18ff36fcc0684c4256fddf9bf1e1d7551c1fdad26fda83a750bd2e370cc566a3b775635d7f049989954b171b03f48ab5b1e33f2f1552165bb790d146458c99d54ce38c65f18723abef30558c0d063ca7aad694460d628059063b16315492a9231095b94be6ecf348bbf948e47809f6fc2583a382374139650653da3598369522028511f890411e229ad5f3ee6a1292f22a08dfbed0568ca04267c0228904f3410a47cb1b1368a55fb296a7fd8ab2c49f14ae4a45450eefce5326c5a1630e6e14f179c3f4928acfbea5fc7f8a803ea264ac5d67fcdc176dee0f0d63f2d330d0664b11a74311488d83d552d1777f23b604acb3dc8adf54e4a9b49be72f572df4011e9e63edbb7da60022f291104a009e4cded9b362a16dc0b9d8bb72c447349b6c82d56285dc961c524757dcd4821d136b2ccef38f09b994d88d4e2ba9ec23b3f4a24c53e016c5deb0dabb305597c1342a701f8db137acd393eaab07cec87fa80b251d78b18dbaf904aeac4afec8ca8a2c15302ff9a1e83bcfcd9e5ede83586e3dd05cadd78ac4c3f5e2fce8b20e9ce9546c19d0c51893989895000e56dfb03ef37556966829be310b0ecae3ba4e25c55ce8c04ae0eb843b9c1484fa0c0b9226a2710fbf7a5fb9ae4bc20755b2890973603bcfc629037d35c7776f6a28940cacb2f96bc1a9df949b094a90d7c9fedc4e28c6fbb4498c552975b6f58b63e2b3bbdfeb9841170823a8899ce7f23560f81b8aa64f70f429f0e4f3822f324d8623de9b27d4969a74d55ec643139143a0ead3829e5da5525ceb24fb6f498aefaa0f75901b5f1d20e6aa877847600d04057321d1aa74071d231b731f6b4f8325ffb37e14be380172e0bcae1bc35e4a2dc22aab569017ecb3098381f7f16f636b6e6a5b59e85fefbd648ea8a2f5e501a95295320319acdabb13fd6648da3490d745bcf93e00561c950ce6866ff8eb015e71551021feabef0429dc4b2003758de2a88257622a6aa73381aa16d6ff35d63edc9e1e57a62a059ab69e767ed0e62bb6475338d5876c4c6d52e6d307088f1cfc9ebca9b942f987ab8fc06fa7ca68e34bb2d714402998f7fbc39273bc25a17550debddb338970e59e36543a4adc3ea2be41fceb5b46681a72ebe486186fb60f501ece2f655662647064845a6dcc9bfd3d372f0c40f8823e42464c7d5e2d60cb945461013bb5ea04bb23f797e82c617a5d4d2223fe7e6f8f09d82eb45368c2ce18a7021f8a0f7cc91899e8d8105c9cee3f83426c3b2002cd293931651d9c9157428089c847cd52ca6b69352e29ac6b010ba3e78708be0ae5bf7ad0d974f0f572d72a0983498767a65fc4b24a299bb0ec4b4d5a15095f58d0119d20ece2fc17a63cef07d237152ded77a788d0b1f271035b88822a18ae72a7f2d389ca2eeb12c1b3d459856c6b23226a79a0a47c338448e37889b3cfd701124d33bcae87006299db4b1b9a5a510f8d309819aea31ec24a5460fc6d8a64f62ccff5767d9cec1cbc8932ea55f511cf668dc1c5d43734c701b416ef1d774ff35cbc3ab88f8c3d9601393cdaa071b61d131651175c342c3d82e6a8aeb598ed80cccf61540cf9f38ca342aa0cd401d8186af6fd71142ae91d2fbcf1081f961628781b96217f4f5135c09f6f5b38c99bf94d711e9fead64ed2d1f09fea19d75bc170f323a9d3022b1a34a82b71714a6418462a6f08c22a38d8535c3bc6230fa15781eba2c926ec6faeaaa2b2fd6052742ecb5c14c09941c8f9bdb362917c85bb1c43e3135996361063184639224027851dc500c461ee6d490580350f9c47be189cf4fc8207c5a88a1b9387bed77b4b442cbb23585989196bb75d91e13b66e2490223fc5598b356ceb2ee703ba5e61cc1153fc637b6c7340acce40220bf6afa51d05daff981ee3667b03cbe633256bbdfb1821247d356c276f32fcfdf7a7ad0aeccd444b63c2cad2edb35bb10e1f1bb727fc9bb0ee0aff55b1034b68d2ac88a08c8207804043b1018ec785df47a51c10f8fcd9b59a68295e0216a32b6a6ba9e07b17a0ff081c433ea8a9b8d113ac3ed8f30ffe38a96ea068ef3fa850c405c98a14d4126fcba1715e051da43df2f58b93771e4933f12b71421736773a1cea5b9e5428f2cbe94a89b697588d34633310406617f217293a52b45eea1dfd20063d102b69ec69dbe3ae510d1087556f043d18873fe5703e944012ef206afe6972febe612cb780cdeb03333985d29101b08a40891ef4705af5024de5943618cc1c38a492f5fa5bc42098c877a650be4acb6c68a53236fe729201209398f338d6ff0c4743e590dc80ec734101e64296d65bec59f216ce2072d681de7a4b008b1ede67be3edfb450c36bdca9aa13bd80b03579c463327e7bc87dc7a227f942be91f042e21ef6108d00fb1e1a325803fb07ad9a7c8d76a7922068b5736a7f64af448ddc93aa779963d7b138d6082b8bc641311b0fa02a46becbda8832924f08019988d423a30e0297ca9da1c5da4048e1a09e8dfb74fad3824b9413a3a2ed794042311074c81ea6e80230a57fb2afeff0eceac7f17b8a9581304b412634d1c448866e3acd0c8044f4767cbbbcc51bd65edf29832ea0d858c11cdd0a625de38b430a6e084eedfe7b187c15fd0a0005c455a887771aaeb7688eb731f32ae65f8e13f12540c8beac9232c8de322f2bb22ee19e356fa241d228bfed7d54d41887e1ebbaa697232e2695c2dd9e6ea631a7102d2a562148322c4af66c03e10ecc0c5df709c014ec07677ace687612b867775678bc0ac2dfbc3ddcd722637838b49af2a2184bfba7221af3db33fd015815638d6bc19fd6a8beb146a3a5654696aea76448e89cc06c4dd677866ab9dd4b568e4c41e81bf695ab4d40b1422906442e10ff2c6e1e10850f6c41a1ad087202065388ebcecb2add9c48332dcfd7c248f335190630e6b967ac6ab4b36c8a6a810032c42fed9f1c8fac16984377f3f294eeeb9b2895743aca2e7defe6dddee9fbf1fb883bbb6809d9af0c61617790f36a2172f7a2244416df10147ea817ad838f0591e37ed4d03891991d856f656491be096c1933e37ee8432ae1be3600c40d68b3bb290ad576db4587aa10e4fbbbc420874e2c12625d0689d0681998442d2d9be4232965f77bf0b58cda5ae8286e87b8f2d885437eefae21899257c688d3e0de7c4711efd832657460984a0c11c65f69046188fcfceeaf1e59b0f832779667f687df9eefe457a89cbe915db14510945f50f7e365d3ccf0c23aef2bc676dffe22c1868d30f37ca963402fd6b483b4edfd3424267c1d3976f5a0db4316112b1218223ab75d8c013c84b52a8782471e0f583e8cafa7687f661ad1b4369985d5f5f837672a45e0d8111e959fc0c31377e06507faff0a01cea18a968561f4a51e996bd3ab3a68c77c630c31a1014776463db8df50317ffb2dcce5ff2150c46920281022b7366e44471ff8fecdc1189ae0ab9aed2ba9d822cf9ac2b927b50f66f48aade429e0621afc8044c1afc061e23a60df3f80b5b1afcac28a4a686dbceee0a89b4d550178e4ece51537a5c812670efbcdf50d1468520a765fc9c7f3d45010617b6b5999e48d2ace2675c1f819084bf17fc8af6e2e42ae2a645f225f068cb00644e07a7263297bb4a28be317194a0fdce1204ecf7085137ebca053cb10a2c1c2b3716519ac2130a6a84f891edaa2ef17a83201df48be717240e6756d8e1950c1b86ce0b4d911dd36305184fe64345258267e70495704210d4d441449279a18b313420b5d1430d2893028970f2592fceaf48e28d2c96653275a79e644a1c5bef802590a4d691d02c75754e29e5cdb6ffcf2b20ef0bf372769fb56e38ddb974280fa99703d07265300630c4e01eb07299b97c1e60d7d17ff2e7033b45b7ce2bdc00fed950444e15a213507b4b835b562cc43ee3e742fcdc2266015e330873605d06e01c7464d909dfba424c60621c734620b283371249b5775ef45c1d731f95f8288c44939968618488168e9016c7329ae6ef4f1208fa480a6c6030872ffbf40e2ec196088adf6608473576b533788fba0a2e7531d5aaa0723d3ecc4f8261879f5aa31a3b60a8a824c6cc95a0106c7fe1948b20243126db5004140f611e8f4ac762b6784cb1789fef7a714fc1ac4dd86a645a63b5d83b69960c99be4c3299bc064d6e41eaa0641bb05b2df9f5c2bfc3a30d9cb2a81b74085ea37d395e6346ca14b4b6d09797aeab96cb4997c8e63681fdc31a978f20aaeb084f93c524403ebf32b860a3d9634c747110ba0be2a93e86e38f73bafe6f97eaf61c4e72d20879d829071e2c68cf478319645b20d1cd48990ebbe7ed6cc4fb6c86cac19c99643273d8226dbe756b78b4e47e1d050fba986991a89c0baded0d2d4722ccd51f0b2b384056ac0ebebdcd4e26d4e6c99b9f1a3d3f03a0873d5ff3c7a43fdbdb57ac3ff85761e8312cb8e81263672d633a495840ef76d7914c68f5481c93a202d75afee48fd98cd176036bf423b83824e5177c164690c706d55b3e95ce69e8fc942f477fef7fa83de7f88956b1a84aa706ac272af7999dceddc3c2f5443b6c7780fda4b8ee00f576590b8add6b4e841caea576b80a588fd6d4ad99e252c09e07c8c5d5e1363b2439d19edf337011cc38bb620cf49ebc1a6a8c22f0811513e655c048792dc64f21e3692574eff4006037b5cf5bd0bd8317d15aa884d0596fbe295c0336d138ad63903c3c3e6cf7876e701cd911d0641d203dde38950032c62a64d12fb52b999ec62ddf3f2618ebb1b701d48b7c741834a876b7c3a6a986246bb5a7f01a7952dca7b0e2c35b50b0e551f44f309b4225e98eda0aea7649b79c1d4dec675212d8f62be35d6eba0184709e6330bda2634efee5715611bd0ee2ccf86a6b8b226323df6a668ffb22f5068cac531376463f5020a158d983c4822c859d9457cba005b94b3eb12852acb801ed91e658880cd20f4ddb4347c146c280cb54ea5688f0e0b71019b83661e1bcaf53a7700b9048582f20ef8ad93a0d579ce691f78530964c51d7cf1d61038597b62b55ab0fe5c051223db485f250a970f3600c384418f8f4c9cb5d196d801191b8ada728182e42685e253d5f26ce0458fb757a9cb4bf3b4f71ee03cd3c81c4f2a88e1afa053f13d4394aeeca31dcfbc2dca14acbadcd2f2dc551413a221bffd715881df9dd0b1234ae919af16ff1ae18e0c3fad36f9670c85b5130fa86b70142054499d177d4dee0eb506453b319921f2955ee4c99e8006f338b0d6f4049a13c04e7f2442da35c11fb54ccabe779c1d6863cda51152b70a8950b3015f421a6ea7d270dd2c7a1e565efc3f4c122e18ead44e4dee531e094b049bf75a8a97b3ac76af4635cdca89270fcdf6b0a1e2c9c3b73444347e9f0c2f64518ab5b957deaf0f1663df2de333b8162a9a215a75c12dc8c511c93e8af972ef9821004e8639984f3eb685aa762028ebee7258212d198c0812e31ae113becfb43de7996d992b973d1c7488518b6d30e7f804cbb3d1984aa18f1d31ae4c09f1241bfd36f0446dba22d8f2ff7619a0946f01a88edb2af3e0ec5f0841f4f905775b97255c3975aba928af4347a0fdb426130cbd3a8be545b72bcfd4e830f78a45d8578da8347ee2c6fb3d89a0bf4526c74138f32f0b02a72d8326684a4ec003f7d80080cc7888d3c1bbbd46d6031f9b343d8b3f4ae5624db7373e38bf2c545fb7578c4b284d7f6793aa76f538b907745f85eea150c787957e2dac823d0d315bdf59aef71c07cc2a6ac0e64c5934e4a00f833cddef4512991a762b833ea3dc090b714a889c298e993694a388ad55fb833b315b112d985e9b77618fbcd0bba17cf8dcd0f705f762485b100a7067b174580a3eff1dd56791889f632c733d90846cf8781b2c00540634257e0b9498945a704ae0d7ee0c1474282c59f9c1d8cddb97fd6091de26e48e89dcca34e75019a2cd82766998a46cd5056217d2f1ec14c1f1a66d35d7cc3653f6aa4bb957f43b2d28cc4cd32187604c9f295aa04fd038a302b5aacabf079424dc698c0fea55919bd93cfd860eb0207baa0eda1531fadfe0bcd38845342bc69035829ecdbc61733e26e941c844680baea6cedf0953b795d55e57d16c006de2b6a55e13febe70304c118c48afd83a77c36f578e83bd90b376e9c109b8fe2634d69333f5b7dcf74a5b68bedd9284131d3c1fef82fe8cf505fb86ee1d008f0a0b9b7352e1f1c28448e64d671f54930fe805cb52367326344d846ba474d332a74f5d36596e1470dfc4078c1bc042d816f48d234b2d29ef02d4efcd4b68a07359bfecb58443d1c5d3312f8b7cb093057f813eaf251cec4145dc2aa65d6ee17229d4d839cf897eef40eb6b85b8970cb9f80242839635a57d10bffefd8b72211a3e14120c7913feba9e0a8d157bdff538ab9c6b17b4c0e15a8e9b61b84f0c5a7cd87b4047219aa5e5f2420d838f2de7275b934293d36eed7b30c6ad5e8c2f2b8cfda3eeb59b3685787b761e7e6b538b5f6b34fa179c1b2ee6bb4166736951fb6a269945c782f37b83353ee859c2ba2c7d01d1b6e61ad001beb1016b04f4cffcaedb06f9986dde62266fc5fd3cb12584b17c9dcda7046facda4a21ead7a7b37d4477fb49169aa24aa0931992974bdfbf1cc1537b1ef439d2e0a763bd1c91a2141f8b5e8e6f0d1c89d4f346ff93ee1564342b430ea68e3254ed312bed718fe263a2d01569c977102f7f408923b0427e5d8052191b022346974d9fda0169aa1c7a75d0cf8d8fc97aa1c4953ab52adfc286f7015b82633abb05c9368367493ec1aa5fe0e00e8e5f067c1691602ebcb1e32a4b003af59aeb05e9fa5ec905c50eea23185c493dee81240940b480bbf7d8b836b0f52de9c9c241111168b05ebd061968d22e5d335323e3406ad14e990182b772a9e97ae72a8d6db19a4aa9d098fda5d277f0cd935e3e025b9c07c90bde7176e987a0463ffa405ceb1f083a9b2bb773f33d6cfa6fe3d2696cfe88cf7668f9c5afa71df1bb75bf35f8b59b116bd2e60f1a1fc3116ade6ec7b8b3ebf636b2d1fa1acd26ecfea01b4cccaee6ed0093dff1f2b7dbb241a5d8f45c4c63393eae72eabeb3ca33fbabb05679cb5c7fadc06a0ac577e79b26edd3cd1f8e808e36f087ded9dbbfbb2517621f5c179278d989c1709c15baf5051e890f0d88f24523f9ee98c676cb289316d05a211c51f37628c4eff4f9bb4b8d1dbb914cd8466b5334028dd9cf14bd039072a2dd91deda4c7e7a4c34fd7c59a364a27a880f74c92c8af51576f27002c621c07de88aeeb015f28af2e0335caacff5d90156791be9834befc82543485c731529e75b7f15a39cd28e9703aceb142aa2064d701bb0b2f71d9c97182f8d5c0b4a9f96eda611807005f18260654470c6034d3d6d0a263406d132557732bbb02526c9d1ef6a3a8efe249a6ada2ae69e2988ef46d553a573b17994d2b9fa3c4eb466ac4e19d2efb6b68f7f648dde834d2557740b889b1bfc2cbde94249e6c3ba552d06155ed24be09c20a310e89e7a8999d7f4313e2b001fc351dad9cc81000959289762bc76750bb88c6357c7e1815a3eed994d564fff6b2b3aa00b91ac69d1b0ab4592de720c9db3d5f0a6fb818c350fdaaf3c509fc428f4961920aa474aaeafe90ec2d1ab850019e83ddc14d0961b5742c6a0e93c93d197ccbdba42474b8b17a5a7ba5b465d311f013ccfade23d07f4d6db33bdfa007a7186ab8f9075a127c5533618d1286dbc4391a9b2e2eaeac9a1bc41f26c4ac6c25bd6ffacb24816431836d51462675b5323e4d900dbcf144248336571b5a519702aa54c55fdf827b314516019cae05446084b2c1a89e1b9fa02687ed7f3710ad5af3aa89826c09430eb6199ade3a7fc9a64a8daf07bf1f259c7e3ff52c6373d6806bad5446b9229e94da4f66f5547aac7b20295a1e0fde63b71453c065251461423fb6920c1c4b22b2c4526ec1dc743886791e7c35826d3d871821332757e70e265f685fdd8c259de0556b01d4834d1d365126d765e6e0d3842551f81731d139a81725eed6f5367680e73adf134d29401c0d97a729cd85cd7c8c4763a12b63a3714ccb5b4c3740d3469725c97f25147d0ef6e75e3d7b4568b55519a2a94092edfedb4e292d9ee74491eeb24057ac8f9b10fb18adba71410ec7af4d0e71016d118f6c9468f737d8a447d70d89151c460645561b40394d605b74573179d27a4270828a2f0b4335345f5e0de95718739999127b2f59140d115250a90196aa2e656160225262a6e69a5a0c8a4e0aaac128a9a14aec2aaa0c809c55584d59904ec23e870e341f70193c53704da9520ae9ed547dbf724ffa29cd4478abb76a4bee9b0344c98069940a47ab8f61c6c3f14b6d9211910dcc3321919301497f778aa92962f9c62e3a2617abaf904a0a4b92ec9271859971e335a4831134a5a897ba0b49027ebed285314292bd72ebcfc838b7dee86449bfe18af38c3b645d0e8b3a09237974f1ee83551c9b21b4ad684c98a0aedc6150f3c96c0c45e429f1e6810d10c8cb92d0adcd2818f229fdb54457fdb3bba0ce4c28381bebe2f4e95ba236d43a65d496e3e5036262ef8af27556a9ba87b10c1ec14059c1ddd655a9306883a3ef765b86cc7194f6b9f8ac4eceb91c742858cd06dc0927300a018797d90bd2488458341bfae8974470c330731abb7f19ea41221d18b67a0e308113380c2103ad32b4c423f3e45da9a70cc331b47a6528ed18fdf70a235fb8fc611c2036fd5c1478cf136e459541b583f3dbad5fe24e57464179ce34dcf59e5b7b8e19038e805b70323de9441f126c3fb735f93cf0926a8e030af2c39c1850df11e727e3c99e17404d6e2fcbb7586b029d514b93163a6eb9333029545d96bd0bf0e776ad80ca3211ea0e5dff86f1469126667043d215a751fdfbbc007ccc4951137ef0815527184b099205a634400a567c02b641850d1a74c1ef26b5ec5b5cce2d6761351a9e2dac4301f45faf654e0c69c00161d674564eab2b8f1b852780f1d13b2b077e4d8634276c936898e9309a103fbc0de02ce68079453281a138027a925ae0db64ec2045ec28b92d43a241e96eca770f78845fcadc035a5cfb5daac514aa48c81b45ceb2c71a44812a93f1a2e6fade9ad417c414b045d2d772190b738ed033465f78d969d979f9e64aaad59dec6215ea1e50c1540dfedd3aff73f2f508bbdb7ae1d811a4ff7c27d7f8fe947e46bc564042548c3775c0c75077e833731921a9fcc5e34508488a7c9e2c6e6bce91ace3c3f2794f5930bab94b2fe3444a2cc46f15f4d4213e78ac06f8103063595d291033543c296ea85cd02762854f53cd3ee090336861a9caba32e1276c6bad34b47ba20983a23651918e4f879dcb234b0dd48b75c2c83e1d8c704b76af81caaae88397cabacb0a9f2e9945d16ea7ed3275ca69bcb2fbc63354388816e1f39bf518b91aa8c0bfd536c5aab6fae4237a8360e50d7a7d521ac02c4ccdb742594aebd3126bf088a21490559cd8042e4d064b005e0275388273075349f9fd4ebc7e07323761094c363c660bff6c196cc81b61ba0dbce42db7cc3be78385bcec67d587bea8c82f8b80c46e7c3eba0a6ef17a4794d599aea1f27637de12ea384d50c4d81db9be759b5da551a014d84398a686f0a8d342544596ffc0193c23e11c1635a249c0d51e8686606f7a690199221ae2d5bc4d09882832ec13925f85e087a944a59511bc55d47c9d9e9ce193a62b5e323d2259eb0bc216eb2ecc55f4ff8be7306cf1d010addf6c9663d0c9ba50362494141cce4ad093146af1388ffef4e202a5a9d526e005ddac2346e2186f74ed0a735956293968f26fb96637d39a1a6111dc0be4f5c269950ea9a48b517054282f0fb9fa511c9f0a7f68a5ec000eb891d9c6aa34036f9f956d938b770ee9da52c0c354467993e8bc8f2a36c4fc0cdb713eacc7e2ebe5dc89675e0f28c03a7804e02cc6b14b332d438fb84b3d905ccc339ad0be0227b49da8ffcdc82db6529140bac6db6cc72c3f88c557fe1e1e38a4c2160428b16c7d9fe3b02f1937d0467d9b3a9e4a274195e1a42cf30297abcea7c31bfc0a9dd00810ea892229ec0a1a536aef95b8bdaad28081fe36ef58468b5a947991d7379c9b02702613e05a614f5b2963a0ba25f62af17fa973488da43974f9458c524dde8b847a14779653d1f8a74cd6dce0d938ecf30055ca210b47f1d5bddcadee206a82ce5852343767ac014aaa17f992494debe25a1e7cefaad09aa6c0d71c1663bf78102631cb01720501909d099d8c61f69888ee82f521efd015ba2f4338f05d79d694790533657fe62df82b76c5e3f34555f9b2f0388959eed41dbee63cde7829eed34938b7cea7e5ea05721dff8736fe868d0eaa79eb20ec7cb0c40d6a0d8dc4d3564675852570e119ac7d5c3e6263f4b6634850c63e241af1c1a095c0d4fb66ebc9074091c20aa1e71a78b962d0045581559738cd80f035c03a7b7f04a4f7e2566faebae7d8d05ab4fab526033d6e7b680eb06952c52fbdb5ea0efee01b94e9ef0aaec20b5cc383e79851af3eef1384219122d5f64992c7e0688242bd339ee2634302f8372d24e07cac434d5048972f91b5edd8cf1ebc8bf592b849b28f2cb28cc53e7c58c28034934eb3209a557c65f877c519c61c0e1f846a91f5bc59354facba84c538b4644891fdfd7f201690bc408524f42a18cdeef077454b5666c9f73db1947d315b6704cbc03749b869f38cbfd172f23508722bb45022e4d2d19e3473b78ce8d8e7c76f6bf2469b91bfedf7d434366dec9cf446cd7290a477a6051049b9b3a2d46ca6ce6106f380c42aaac2546dee4220de8025969ee4127e03e33db4c750ad03c4eb47d0d09d00b3c291b78f5ba8c43f22aeaede4c608809258d8404834e39fb0b0a65325a7bc7952642a0832599078e493ec2cc06660aa3b209ebf3e6be44735f01486b19ecd421c99c9db0c8bc9326fe912751b93659e966df19f2f612ae60c45b30ae292977d367f113de79923af9c830616cc85182911151cee510f6cfc829299aa30d54d4e1dd864e5365506f34674402bd2ced601227389e688ba5956fafa4c23a2afb174429f836650d6257d8da882b62547ce502adbec9dd3ec2b57b4d1deff2705d250ba7151c148794a88d3d622ac66735dbe9854f69834ab15353ec0a964ed807ca344d36f3a653022dcbbae47f6bfffbb138295dcf295d0bfd48c4ddfab0cfb26503411ee88df07a211104ea46c096ab10c75fb7f92b716206bd604e2da40007b826934753d087f6cd1437305f6a4f3242c7c1037e18c9dbb18f8a986807b1201008519b2e928f23eef5cdbc136241ffb90e3dc1c3861d0b85d0c39d954c91be06dd811ba8a729ca33dd2442d380fc20493822d271e08b1d6078b58f284e13737f4d309234d72e6d7fa95e6e1710102744c75ec0f461c2a95d3a0a5901c1dc2b1c0b77456ec957fb1eb6cceec0de6af21f41200a60f96b564df7d8d866800e91a6710762ffc503c0a8563e419f370470806691e3425877195eeffd460f8616a6db49f344119e9d42cf51066baf2ca66276026e7ffe5e12675e4997a4cd8c7e8cd651c42c93171459053dae1c018fe5ead70ad33bab5b8de79ef610539b72f40c3aa1a36ecc4e6c5eff02706d6b8591b397efff9356660bd4807e7e550e5a3f01fcd1ceb045baf9f801753857c184b8e148f455d5249e83624674dd200a9ba1a2fb75687180b664884a179b4789ccbfe706823eb946f52923df072291c3b3b2401589f1debed268add0dc325285ede61d13c563bac7f436152f1cbd22890f82c8a46e990b3e2d3f83d211cf51e5ef9840058d93e6a3e7c326c1600d1611e37c01c6c371cc1c29144e2417cc74470d8e30e17054339ba0bc471016fca136828e8bb4b08566c25f9fee85345986f082ebdf5a09c34fc9d75f56974e207b1b515d4d6b18d1f1e80c006181508762035a080cc99761c841efae19dd4d64da264d051270ac7fd81dd99859b649b2372dd2f690402c73a9a963d028c63c9284d99c12c9da83042ee40dd5a34d742ee56be3c194041f1470040c51d395fd8c175dcd99b2ce28edde0c7e0d70da0bb2e5ee4a050b578eaa81dbf6dc41ae0b5b2e746ac183cfef5321ae295c5b6a779341a0959b2f83f289269c5b53f1ad7f9894e3be87400e0caac44495b05d3dbda2f9d34f5db7e5dd1d8f9b8aa1bb6d2b266f96fa9ecf798c2ed357ae93a542fe034f72aae4aa91ca5e3f62935ed202ce95416706f5edc7a0f105263c9d2a3391307bf922dcc8b976a404d5076bd9d2c204bf3e91e4be997f0bab2612900243432e61b61215dc69aaaa13b8e33809448830ffb59564086c2d064a53be483cd4b28cdf8505d1a6417342641cb243c6873c251cdd9c1c5a2ea9115ea133815435248a15459c6d145671d27988e5ec0fddcea6cb52fe9fe3c8a8dfcf85538e37fcea54564b72bda0a8a42ff3c42e1b7ed2091c8d1aef7085d92981b0fa4222b46f79a30e0c3cd688746dd05c2c4c72066be4f5450fc0dfc4465a57c8b315d43534d22cca73c6761ff9bba1ee8774bb6d76c2e682c101a538c93e774f73a84a8a7a85dea60e57ff3443121f3317198a4d5a4365f674d2632a4f0540294ef755e6b6818593337858ab791bd8b6e54ab6db0c4792f4aec1a247db3c0899e631690f53fb097a910187f7ed716b162e46c5d173de79a653e0b06ba9bdadfa43a224b7062b78fa6b4f3842002c0c9df9d8300c9b6b54f21a220f963312bd44cb0a3bfef9e79d21e3de39e9fb34653b01e7da5a8561303f826192fc9d6b0b46645cc376241c965282484a9f3ce99ab394ef3cc0980a67711099da8d9474bd8e21adba8a976cd2f06c738c5c6176f77b34b3cf6a73d0ef2fff82997fc88fdbd96f72a952a1f3e4dbf1e464763952d407a198ebf0501184cc938b8465a5069562069869830b198f5786fdab6868085b370b817e2a217572ac859e83f497b84ea0a0128e313a49f2f868fb8d9f3079f6412b81eef22db75a6afffdc6f6f70059f6c07ce988318633c84919a600b725a67f324816c5788ff5ec524c4428fea728b45f5a7802f99b2dda7a250c5c44b81c6053e8d7d8193cb8495b419e70835676f716ac7bb407649242b925661e43a289aba3e441ec9c5bad564c899e6245291603a1808b4c20ed52682fa9c71ab164b46c14c31c687d9a4ce9e3dfb0f054b906f11b135953815ba488134207e5f03781f778874ca5d2dc24add28b70aaabde8203a22cbee7c14d14fc21e5dc529d129803d0affca1931d01c83dca398b2e8a010cc84a98c260c3ad750d18f7f5e8baabf579fa39184b040458921610be7498403afb0a575c59805a8fc53c3006d2ab15e78718b6d5fc876c55b6752cfc6418b08893daa92e0ecc55d76feb897a4a20fa80c9a0be8327f82295d87704028c26e04f58fceb999f8149b886c0dbc060e7600410a5dec8f1bfad7be3a628de3e8fe782dfb9bd10fe898e4adeb3155dfae1359abff85f06d70001d9403076c80f1210b481c68b1c21593668bb91b494a5928c277c47ce152bdc88cb25930033c203cc7e971ccc35c52767de00d87455cc10c507aa8b2bc2f4a10ba0c741b516923ee6ef19df9f27d7beb81a03cc285562a0aaa17b35e2838a3eae64f531903823600c3edd8abac92a2cbf9e2cfc8612e5f91b35a5a61029cc1761c5c78497c7428531c4d02f271e0d040f6a4d13659135a75009f05b0073d6daab224f7c1cb17c4a004ddcced62a9d9920892158a07784c9e48ac9fca26c22143d827a38d0b918361ef3b13cf9e4d113e836e61e8c8a1ea2234b7998a06f1b8bd47ceb02577240d84037dcd8dacbfb5f1d5048ae484b243b780d0b74a614ed5e8a42602783339ad3563593d6ddee1015ea05b405f4e99c8472431c1584e80077d2187844140e4c4c27b8cdcef7965332ebb80962ccd133e70bd0b206ceb24a059137e1f70d997bd344cfb4dab0d96cb64e5c3af00f5e867ab01860503f1c110ab3e5b92e71363e0efe38b94b806508a5a3e017d36c09dc4aa2999665c604e25bfbc07b2d7134ad7d700a886fcd3a99f822f3d7a615d03a217761ba117a67194069dd614fd3a6c3e711fae51172e56032566f9eda875088caaf9d5a082f2285f5ab33117b5b6f5af2c1254e20cc710f59fede2bb1cfa9f86ca83a8f04194fb8300e22cba368e9886388c5d4b868648a7a21a5dde2ffa720388b4d40b44a63be63a3ae0082a569ba1b8829d07a8d9b383c1b112a666cad768a053b534926768837cb5dcc2286543493af91e75be416b9a1c22f50645c902cc9194f42800598f688a992bfc048f4626cd3416c34b307a007bd4959075e095fd6e2d585a968cd687ab075a8a45146ba7cdf5df4e54c83ab383d4a68d065805ff7261d4626adbc3d843470fed256819d17e569577ce68a7895a4e37ce9715a7eaa1f25c0a0239510a7a566d4a80dbb9b69eb608877bc217fc5d06fae05fef7e077e0e842821c899f310a9cab9bb057ee37f1c38b9d090fdc281f720401c3750adf941e107c0a20a9eaa0dd9931be6f4415026b7f1ab738a3733d2ccccfcdfc0b879c096fe22448b5b856738903b553837083ab06c8b3f00fe9f17a293f41ca6ed3fd5bafcb03311928d80330217e54331c5761ed1a4e735c457a544ffd829fa461a4cb6223c61d3d2bb06a55833271dda29433fb3e5da3502fe3cc0a0e18036d1917c2f2e56cd0ba121bbfb8b2f8b5d468c0e172b8367575514ec556d4843713a7ced5a9ad98415ee2eb95352494e5c5677cc7a974992bb868c3bd12d8973ec9185452a3025d2628ed1f6d633586826f423d3f08ed938b6776ec4ee6d4a2e152c032e68a457012a4db9ab99245e4246fd0b045d9d60aa3ef217bc89fc2ea4f007ba5948f51429fb10853766dcde2663b6b36ec02a4d270434103235771a241bf23aa5d8d448cd66127b092ef4120d87b759868eb326f1e11a678239e75452cd01b08992e30ec501174be5abc4edf83fd8a493fcd83805a12892e857c0608d7b29f5b97afc7434c73ef72c65911d5f6767ab75a70d61be36daa18f15c33f1b46b587f185a72e2426aabd9b2eaec28e247fb8e7e777bcc29d02d8c068ce5c73167cc99176a62f9e52307537ff2c3d3c3c85425ff7fe563b3868761ab238d4060dd30497353fc16bc7cb999f7f8efe7b90d96d0c037837e8e88e103f1e64dd4bbcc7112fb85308be8dacaf7c1a7d1757484d0832f20440d78f5ad3df97364f74439087353238ecbcb5a163fe7eb7d438d2503684862a9339c97e7a4d4eb4443103b9b6e42cadb8ca915d1ed10473a85d2c80c572d8e5fd4cedb5a6f2fec1583fff552db41e3c838cac5f7753374587726b8908ef82b8d5c375b38b59d40c6e948bcfc5326f2dfdb28369a4f15e34fd26b8cfe9f74d8407c8160974cec92f5ac806423e08b64bc487073618c48970fb752bd84f9aa0e20cb4486a70d36a8ec5e1b6e8aff462a5666b24834e70cfc85b441d9ff702ddd0ad0a21723b1c083d94e6ff4a32c7fac713bbc8f644942d422958b3ae4b52052a443ae68fdd0682fb77893e9a12197a84b58ff0887be1621116b6ca2f9417433ee99efaa001be62b5106258ac8dfa083f07e6d29d23d559d7f134e7cdb8e559a913a107051267eb0b580016f4d12c4c6748a2796eec2bbd6f158d8c1f13827a03a802a71f740bb978be7388e43ccd33278580a3b7836ff74b873b89954fc91221f5bde535ee86346182d3db46ae60eb4566fd9d4648baffd401e9101c55be6d8896f70c4b5a864f5cc1518e76ef6333dd9d114b029bed9382e996576d376ca364621f7399b4f5c6118876fe863fffc207fe4a4004ac3a59576615f05dec6db2ffb407c268d1aeac0b53fe47607d0279b028fd3b6df03beeeac03a5bdf45d6ad6a5bfbd159bfad3b85e13728761a8177be5d039fefddcaff03a3fead8245faf821c3e7cb510fd31c5a3434ae714920ca7c87344c5b6f61de217f136770ec1bd4854c4793f4815579e08a7d44a3800f0716229912dbe501988c7913f243b450a855811a2282bee930c4fa1d9fad4b95e60a10a8bb5bbf2bf167a30ab57b0ae72b6227bbe7aa07fa12a375f555e9d40191cc0e6160d46ee52604808a6cfdff3f68dc3bb17a8c9a257722e9f19cc027b181b4e3dee729151b7802f8152228ebd7a0d50e000723947ca01ec98f9e7c9d4a86cc3f9de6b5890ad5106339a9d2c3fadb598339fe0604419b02ae0548a03ba0b92eb540236144c7d11cdd014d078ec86e766ce2fb0515557d014c24a3a676de6a537f303126a68b538c74803397684940daaaf9708fe4dac14f74443f688f5d8e93b98ccfb0a1b41045f46a6fd35fc347e903c4f470e0be2dcb1b389458490fe8d396f7d78180a3fb31523f7559491c249dc9f247af4f8feed3182937100032342a8d53a6b0da58dbe4c9f06f711798cd648d836f859ceced34fad9debb37878f6059a63610c4ca27988438819ddd749eeb01a5fb9831ee1f468075375a35b78bfef0bdd401a459c4b706f5df376d6d6ff914ef7a812c3caf004cfac2f6213ecbec548f2258c06973efd0142884178931ee9b03dd624c1899466b76074b172030e8c4775268e9df7bd951a1e23fc76a0399c9210d434fedf33e762c5373a30d7c9dabc6f4e3e4657893bf435cd4dac85a036a9059d624e31fd69465cb91f4f134e6a0ef3e97d7cc0c8598e7d228f00fa76d309762a66efa8e58ee4ef266b8b21174be415d952b312e10d6e4674898e1cf906d80cf77c452b27a14eca6d2d94c6f5fb35cd0bac568b9fd68da404ce3675e512fced6cf7eab2998d114d8bc0a84a9f087be1a88bae6b3594f66c598c8760678132130ebbe746bdaa59919c445f48f39757e71acd320feaa25c68e6e75e5e92b4aee84a84c67d9961fc8eb1ec3a347b94a5bbe75d0bdb58edb201735a6b953af03a6e7cbae7c6a1025979e1921d6326e42bb0fba02e37f5c2b88fbd202a731070536237f4e1db96688f8ccfb1b41958efb2cab993a63cfb22e5c572f768003ff3e08c16f352a9ca56dcad829091edc68d8328acbc11471aed938e3b4c3282024d2eae42ab1c1664394d35bced12b4adb07b71d9a985264c392215bd1f5498c7a8a02863f12c99d903bcff43c749e3d23c18e937ff48f749d0fdd458541832bcd06f4d0548d15c1fd711bd96b8e8b26e665c7dd47eade80396157386ee8f6592ebf7431ec13db9c07dae8171809d7111c27f8495ec4a39fd240408ebda62a75c3d91f32b4228d94370068c8a8f45a87b2d126b69924db8ee7894c7bc4e3e2c7660f6f0e66907046a1958ca8fc6e9abfca1c87bdd4ce6ba7671ae2ed05e009f36aaf90a42f81f12f5d2e7091488844aa9c15434b0212f1752655315560c17e88e764a06c75df4c6298078785e7f80485bb7b7e6fc76272f9e0a136bce5c19be1c2e7843b68a089901aa583fc1323b664f3810a6b2fc4999e220645c120a6a6687b6fd3f8ddb24b3743f979f783a1edf94955f847f7f5fcc690b539492cc9a5b105a3434f1cb3abf84663b4672205aa1e0114d48d731cf45d4a1336c4ba50d5fd8960228d9d0ded26d307fef38135dbb1077d200a70a86ef4d1ca51317cce4cc566a88a5015c0021bd35422be272a0a4338736114e4b7b6a1059036b48e525ef018ae0d4dadc35fbc3931f99b36f4b21b7adcf1c2ed52b445cbd569d9a99878a937009d7f90f8a157bec7df466d3fb8e0073d03978c99de8133c5c23c097e8fe2be40a946bb0800d822435904e811eca1f3f6ad1470dd7f35363b95ebaab44b68f651f60ac5a96453465846ce7d3c7083cf1633aeeed01228cb1fbee83a6dc788c0233bdc71013bf605c37c3cb55259258ff038e88d4b7fed663661374a9f1873930af777f1955887c7761213ebf0e25822ac6778bd179e4b1a252b0deb3f0098c04323500203eded723c662775ad22bcefa66ca693e2a529024f5d0745a8e63a5fe9149dc9693670680eca2c0f66b99a756a041b306cbb63ee216be8a9872654bf445280660a08252193ad8fa1d99624d9bcd30706d22d957dd401d62c97aa29d11011b03fa2a130c6fb29f20f541f2b7d1e0ee9c8a7a86e7596932d545e798ea7f9a000da078ca5208b03c82855bd916d8d0e1fb1d3c1e52ceaa7da8650eb4f975f550299ac5910e998f9a35191099baba9d57d2867ec0449472be8b3f1ab872ed958579a43257d35ac58a97d892a86106e0f754a3208700f9050fa310d719a07ff8877f38e739aff99d151cd5edebfde8ce51c404a692acdd0942a6ad9caba2e5489f6786f6289906f307c919efc2b944e3fd3a7701bec18279f21d06cf00891a35f24bb2e2dc6a1859311ed3ddb1a8f8a5427999f55437436d607b5cf52499175a9467c5352cb1177442449668a371fd46379ad3f8c1acb94fa10a1b3de6e6f6c2033bf18ca5cf6e2bef8a6c9058c3203a29b2d9b99f7f751e37c504b50df1fb983abbaf0bdc96a54e52fb3b1d14fd850a61fe34ad1cde2f07adc507c5d615379f1b72833f5a41224cc6b5d43a32fe324d075dd40d26058f0256b8687f81419623d66578a2ec112a90a12d4cbbc8800f8c06a5bf3d81c5da549b58e731b7f5037af7b54030941cb6223ba9f3f78a6331a792bed5f528244c58644e0e1909c13e4a05a62fc8080c94f53a05e9083fac51f6cef47d1a7d8f7d792b7188008b1b1c11955477729d55038b951d6ee2e59f2e964bf4eb1ee47a4cec04be04e71a104d53a726b48ffbfbe71c76af40ed9ad9a840539f64155887c3ce9999ce11f4e040d3ec220155089c492c4e7acc3498e5660c94b67a6f7a5b54cf57371fba7b3536c456d32eae568e208d300c017773bfd85bc982fb577a1db88c1b371727d09a944f04db70903cd83779e41704b9873b2e69a5f5ed584791de0f1aa3a173181e2ae6a2d2181af62d34e31478e9a7e98c5fa5c5fdd3197b676468b95ee683019283cd74ef452896ed0b408e668447c92bf3083ee2034a802dacdf15d5d4014564d07f9891bf2f04800b275490c305f1af1b0c8b53941277fa8d4b94f076d1b6043c7f5e5f9999df89d1b302ac251767d07d8ffd66734f31c60f9bc3a04f1b70783fd813a37b8a2df93acbed427bffe88b5b6f7967bcb949294297005cb05f0051d4ece0dee291ad751aeeb76b8f295c40ea757a587a2baba28ba9754c92a159b4d31c96df33a4dfe8ba371d37cba49377fe27bd2aeb3d4ca8eeb28d7c51d5e9d775d17ddbdbddbf8bbaeeb883a8c53f24ca56a7dc59e614acf4fe7ee55c98b87fd7f7ce8f2c4e1629c0f072455d27235f0e91627e38aa4aa065cc359c12a1f8f5f98c1b35c6769d86e3ccbc9bac519f5aa48d67049526505d7705cb04a1ac51a78256d07c61dd886b49c51b7585c92ac892f8e0bae893b38a32b6de7451ef104341205bc4ac1a1dd33ed5ab077c3f9ffae777bfdc7d7ffe36c379e8d3b70acc19bda346961b869b6e3b4ff89af89f372e32b95aab5bf5388787998d2d3bda4089c3a71e3c1c6e118b6dde6eeefeeeeceff1615ffffa5bbdb96dba1944915f790ae75bb6b21734c47772ddc05b24380ba71db628c42984cb9054599acc1e9eeeeeeeeeeeeeef68f31c6ffcec2bb249616bc64d48dde9ad6ddb0ea6d5c740ce3f2dc354d93c9598ac832d0adb5d61a43d71f5d59758074a4aa7db420ef0528c748fb48fe48b626ac5746b266b33c3d3eb1093465d307cb9d42dda23cb49483f2c0bad5752157d2cec093430f0f1f1f5e9009e4093165dc167649426ab42ecaa369b3b442afa0719ca8dbf252ae2593352d934888f25c69298fac1142b6da47eb7c105c4ac677afe709d736aa6d59d0d662b413b81f22f5064377e0feea9f524a893c8cdaa59b46e4afc34cbeeca298ade3eab89472cefd46b98d46e19276b8f295c887a257b3f451682fa92abdac468ee3382afee70a3ae4fda95d8e6e5468dab4ee41bea343ca583b167036529582549d78aacbe6b224c91a14e52c0ddb4d675d15474ed97432817a256b269154c9de82fb0bb621ed046a275cd33f6095ec2d6ad076706b42b148576a38dc09458b1aad6b42b91c05e0f219fc2569a486025ec92f3825a9a56da49640c85ac308dcb7acbd5ab0574be2562cf0a516ead590ba25b90ddc47923592556ee5747de07e0b7ebbfe825b9d95b6b7e02ee54abd6b9a6986515e11dcb711c4d96eb8907b45c16d8a1aadcc41b63ae6c337de02f190f9302262c3721ba95bda4bae5ef205843a2355d2b258b16ed1995c494b0404457674250542d1a55d1869afee90ca34f9d224678356e238ed45ca5501989b4238638303a0db4e925c4722091048a2caacba38a642015838c04f5753b6bb9ce40728d71ba2ca8648e6965e37e043a2ba6ca3d7b08d8154dfbfd8dd56dede7ee852397e0a72b923524a29ade8342436df8afc8b63a4942c3c1dd946298574110097532d63aa5b2cc962c2d461e418d9318cc3b1dbe1575bc6ff51f0f7523d59dd4ac1aa6e87365dd4fb265c6eae51843a23579235a4b68d766eb24626ce207278637bd97e0c37ed67933dffe2d0631c23ed86447b52266b9e6ff4cb8d56ce644b12114181727404841b65179265517d11dcf61ebe01b6f4e826978e98e33dffffffffffffffffffffffffffffffffffffffffffffffffffffffdedfddfdef1a1235dafeffd79488b29ddf6ed34397bbb534dcbd795c8816e1f98cd6d2f818a3f7b669ac624bab78ebedeeb40abe1f769bac61e1699ecb9c8c85cb40d870c71bf8e083542131efc0423ec4626cd1c174628ecc7575776f45180032869999b9bbbbb951603ee14d983fe4acb41f7254db0f255c577777733ed022df1112be588dcb3760ae8b9999bd11a6d7ddec24f494dd8ad438dffb638c51de58438d2a3606528db6bbbb636cb1bd9c56667c96657c2ecc2c31509e6bf354aa8539060c944b0fcb2cdd2e1e3333cbf0802e5bf931152eba19ef73fae134c33bc16085c68b8aa551afabbbbbdb6b4a087a9e53752e873827185c577777cfb9b577bbb72235482841e575c7d7e5c8d239a64ff0c184520eb69caa45babb39c6ff046f2b52a3edcfa9eeee8e8217638b53901aadeb792e5bfaa403aa1217b7dddb04b0971dae2bea6c383a57d2580ca31c184f104f5093264d524f9ec02887241f28f6b18ff5ca27c621c54e44c42a8c063bd745578c8843a854ae87d2eee626a7511bada6596abd9483862e8e27c8882c7c6ad3b1a040321ae14b3ae975b76bb196a51c9ba6195104c9dbe164b0ebfa1e5912c748fb1dd183e4131414141414e4ef53ebd9d7430bc49d4f7fcdb1701f9e202f1e3675dc1dcdb5afee76f204c9a24ee33abad915685c37e218a3265c9354af5838c00b2a28a13579d93a909127887982be1a6ab41f0d35c6681e5f96f69554cd3d86ff448d9bf78de09be1dbf1c9f0c550a3fd5e5f14c18b2eb820c2690835daa4962d603411c22908355a20fcc0073d40e2c10e74703aaad16aa1cac284a01c1845c1e20a28272b02e09daa1040b700022035235058a0c0d8d68c97aa5565ed088a9c5091333513648a3ea75bdbc39ef4141d7972655c8999ba654ff0a53dc6bee9538e11252400dcd28f0dc96325d88654428a4026ac542759d31252605752a4cd53601b525b8f51241fe2b21d8aae8c32a5c21fb30cd9953eb5a75baa5e756bab7181553c6a58784196e3051850aacbb2ccc539e19cb04cf6aab6d205ae198155d2ca367e61c500a6d739406f8fc0352f74abf2ea85c0ee6e19b933cdb068aea0ab71630f174de03352a3edc008b2bd3025c3c244903fa9aa749321e5cc8cb528a8a44a258f2498b2dddaf8c8500ec794592a550f6248b1a8e3811ec9c752f8d8b589750fdb1fa3174b5ba7e44a7ece5bd0b85b4dd95ea130535556d557740ce35a41ba51dbb81fbaf2a384b402dbfedb5fee045f2e920a66c870ef68430ac531961b65db0e65d4c84801070ebec1514617d423ae6bf5efd3ad69f9663edf7cd1170da9b1c6a49438b0fd1eee4897f3e448979312834addd9ec8d28e37e0efc383e8728236fd4b4e721575c102e08c7235bcfe373a69d3e5ee7797c0e11c7f3f03dbc0e9fd33fccdb666aa5f0f4e48468964baa417cc0fe0457b2fd1e8ce379441999c2d3add7912b695fe775528cbc1159e34a299252843ac109b9f275b8240af4411ef63e9e47c4f13dde873fc1eba4c43cc8e9157d70fc88eb8a2699fbf8b8ec3a54891e2edbe781715023514676a9073f25a439e79cf4892b2910456224e2a04758465ad904c748eba24a5c693ba4b15311b1d3ab6ff0eed6ada9bdf59efcec608fdc49613df7391c8d71cc86c4a7b0983061c262c2844984b1ec0a875b91875c492b7df070de402b457737ac87ec4adb52c6379ec88d9f477284648bcaa80c4a7551242edac46dc21483a4b20f4d2f3a932b8ae44a4b91c89a169d5199b3a9a3d861797b2c876fb8752547641646aeba05632d8c889e98e3ddbb1c9e63826ffd3ffe9b9cc52996a75b26cbf67b9e88918f3d8c6f74d6c6956c5dc63738cb1d7d62e37b2217d2a28e52cd46fa7259d6b8b458dae15405dfc8c71ec903f54ae676f83e3f6ab49ee57a38f926903172eadbb6efeb81a846cba72635da1814ee54a1d332c8acdbfd09acdb4def24458d964f4335da93508df684831abd1313ef34abd19e9678a71bd4e89d94d4684fb21aedc906355a8ee2f34e505496d3132d30eed62d9622e59d6a00a3e5440317efe4448d33bc13d08b779a010def94c47a271980de0949e89d9a18c13b3101e39d8ed4f04e31124e4bd468b7bbb12e65b2f24e4a98e09d9238c13b1941c13b1589f14e4800c03bfdc878271f96773ac286c77c8375d976365826ad873a71777677777777ef52a9f0ada5e1eefe3eeefd2ffda393a0a7e6ddadcde69cb2fdb559cf3967cf9edd9c737afb12eedc2173ccff7777ef9054d6a36d7c24ead04641658363a49da50bc4cb624d6d1b00c7942e10af0d129b0d84e14a36c852254778a28e4ae518f9489ce0945cf972797825edb6559e21277852848ed4088363529e8cdcb9c9c3f7a69d27040f8c07d6ea84e0e1a9cee3415e0f3592e33ee0cf651b23293ed8325252876c7f0a7e9d83912362ebee342f96da3ae8f7857044cc6eba4f9f3e478040ca8f109c092356bbef876046d6dce4b6d33cf2ec5e7306d9ea72e870023108563a3b977244671991a6d6eecccccccc5d0ab8ed695590f26fbbbc9d2e1fae106788632359ad727b3337c749dd4cbbd9096a6e5590dbf469b539e7b41f458c45e9ff52b814b2631251fe9c433f243b32e13ede030b642df5c42d1568ecaeab4ecd264a4dd96288a32055dc10735437294a503b1bc82a6a8909195413911d4184081104e63d417ae82982cc10440f7a8031a27e53c426d553129f2471a27e45482e766208c28711049103abf47292206a08835002976805902b2293ea97930079720590a0bb417143cb55b15000168a006142c2a0a65c4e02c4880a840524f88a00240551809040faa8d58b2b6a0b163da49686a492243f925c57f522c90f24ea4e921f43aeabca1924f901a4d69ae4070a6aaa7ea9243f469004e683ebaa433d7553d23ef54b2252514b3d4ea4962e278105b144455d4e0223c1953f9525c986aa45eaab2a14808503d494ed8aaaac1acd66b3d96cc6840913264c9870b36f1cdb940c59b35190bf9dce823673bac0ba34286523572eb0ae87305215a4eae1c4ff5f1aa1838728709038c1c112321431801c4c6083155ff4c0210a1a783126a7231e4590a10b26848e7e80ccb04309901738d0e06505caa150c17cd54bb1666c522072ef93917239091050dca68117c3818b9999999979d37cfe4fd79a9999d66e8eb9ce24e57602fa0dd568bf99bbbbb7bbbb83d3dfddbfef64d3ee4e9f74eeee1eba6fdf911a6d77d409333377a3f785b6512ec6d8a4f34aa6949aaa757b3b6b4ac9050ed04dc5050980565be94c2556923be5dc63ae12a31bd73917d53d0cea643c14e8e69534d72c4b6e9a974c1e644ad182a2a4bd756ba34373f3a4cade95f7c61ebdae631762c42a62a1b8c4ab89321654784166c2026e5b5e3d777f03dc6dba645a299bff11083d8cdab41cf48202b845dd6918614d60b208932a2611b681205290acfa795b0ba374772fb510de8e91aaa8b5710a1bbd9bbfd882075138e5abc0b769d35fb5b2600a0665bdc00ce090bfb892b7b892a35c298536fdfb25eb5669dd2debcd5a5794ade0dd18030b1893947f9d8e9a5069a21b87d93f126bc372eeeede319b4b34ce574e5fa434729d572a791d4797f85862c4019f4a2b701786252ca89667f94a2e4820b45a0b9a6dad2546aaa2c2d67b5a41dd0e460c8db5f20161d85097afe40205beefb412c3a59f3addb4d9d555ba6b2aa5b8c8d0628c81291895d5cd4040ddb65aa3b6844c3159191eead2a06ec62c79321411ba28db9f5d2cd5ca823694001dba384a6725cfc01472afaa17c25430704c1253f32adfd80860c338b8c559695918c7b42d976d84758b0b61e0265c110683540555186c06b8a6c319a99aa95e46969c69630b36e56e71960353bd22e22ab3dd361bd60a8441bf0a7c14b5ba6cd32d8e0e6dda7c2f9998f53c59631a61bd929e4a85b012ee4718ba35005e49cb4a22ce50514441773bb4e11869bf246cbaee5293775dd630ea7226190b10d9da7c8f3b88407ce3e5b2cee6c465bba5eaec26b4089439383aeafea943aa5695b537c4000644b0c0e27d6aa95a55d612f98af888f87ab48d725d4f6ca360bce922175337fdd3757747284c58a964b27b781cfd24f133c26ca6c34c87d94c9b4343433fff0f245572f683b7cfbc7bd645d7dddfdf5e58cf42d336bafd88e05fae06bedc0864c76cc76c6868289594f453b37ca3cd94dff4e410191a1aa241a51211a2ed981189724444488c51ce1891de616868e847889fd96c369bcd7eea309b433ff483fbb74f79f43fbe63b664674a9ffd337dc72c06a5aa9262f2baee87b3ac6994eb381d2a437528c5f415290d75f253b063b663b663f6bde01b02859b82114fb8916fc418a3f709f1b9e06bc127e4e3f9763e167c2bf8827c417c40be1f1fec03e2fba1bb873e157c3e3e1fbe147c3d3e9d0f05df093e137c3cbe1ebe127c3c74f7eccbf976f874f848f0e5f0e1f0ddf0d9c0524ba23ef5757a25ade3f85c498bbe47a747e73f9f1e2983b78c4926ac54edee2323114a54ea718b03fa8072361d0ea85b2c0001b180457571441c51932640409e94dab65a5329bb42f72dc140451e031223040505057dcfb75b16e298233ea9ea3112ad8c31cecf833cdc993f3f0c57dad20840303a2a1e4ec573fabea65d9f4e27afe32c4521a17e804a6a39a2a5a725d6d204aba295615d322ed6a572d9546d0172212a99b416d40f5048a8a496235a7a5a9a6889c94025b9a09262a09260a0925049a8a416a02b2d8c0b51c985a8c5aa203596528e16ad8b25032c61296199ae05847da51c6e57aaa89185c69752ee0c7008b71cb8633b10036f35b05d0c68002584224239e14c3297282e452828a828b4b3b5be18a18eb6995c4996ac4617350acd641c83128a386894b08c131411cd922b2d277465cecb247389828a8282424db2ce246b932cc52433c94cb2251ca3843593aaa3ede83e092f672919c06f0baa9443b32ad1dd743b7007c672e04e0d4bc19d177086215a2c0ddbcd0eed8b2fbeb81902031aa8b7b30da362a4a2850ad2ca9095b8e2b3528455d146199654ac31aec46e8ca8590c856a80a6995cbd4903d2e209235943e304ab244d4e920a52b7687858194283b3c2d32d9a18568a74b1e2236b66861093b92e152415247b5d2a4825152415a41e2a48a995d8951646a8080a6a1671b0ae8882fa429a29745d367084584b584a5eac24a423a32222d6ac2564c938860419360d4ce2e1a92d2fb6850377606c07eed480a9215150624c7ad58071b7da4945c6312e4557baecba6ce4b09eb08cb4238036706cbc7a950404a9425a01595058451cc31262cdacb8d2a2a0441c2c254b220aca0c59321264a82e934ca548d6b86d1d97a2ad6b6179210d50afa41f79d2c77a7660493cd840c382b54b799db515a4d101697038c6d2f4e09c4b6303d3c4c0335ca09d23b09f2422847a25ed0b0da3238e61e98007529c64a7528e1d1ba39463c7da181cb8632d057754688038f66686f5a3888312451969699154fd806ba815ac62f1a36e51a33e825bdc0ef7703d52f54d700d67848cbd499c0f158a384c4f4419152bf47224bbb8bd80342d200d129a180aa4f1218a38688eb00cb582c6c82d9235343faca269e24acb1212cd9cc01923a36ed1c4c0ab1d9e49bab06ecdf48057461cdd1e593393839e2b2344f5e3ca1e57f270258d0dd428e7ca5832d6ec898883c58415647aa28634af1723d08863a4e9e5e88b3c3535a3822fb7439025eb95b429208b0864599065d42b69bfd20a32770b594245f505a401d29001d2084de02c819163a2a501d65e45af8319a1a4852f4635bc17f07624d000fdf5ba6930c6e5c09d17dbbd845bd712605c0edca161298d707b7999317fd572bb979719f367b8bf20bd18c9587962a2104301b05d0d7419ef45bbdae6a1502c3f5fb3bfd9d7260b4b0e972297227f9f2e453eff745a595951a92f46d2a69472984a284175b15e8caeb4ac2259239b09b188648dab026df238c554a26991916598b85bf3ea74b8c808f4e87eeb1cb8ff27206ab41e9ddde74ec7ec77b656a493de47856d217687afd2bce9a6c9684e9502ad75e933ddeda6e00e6737b0e9d65c683313c5ccd3f7a9d9ccccd8cc1431332711ff36f859fcb74177f7ff307b1a4aa526b57ddba17603bf65c338d83bd46a60b7ed4e859cac4637d1cbb2864b18a2ba52574a1822d42e7916bcc2d40a191646965ce4eb86b3559e2e05745cb6da06ce4083cd0d0d35bb4d70b314c4b1b9d92c07ce40c376b3591c9b1b6a3bf7f72da42b1c2dcd4e9b5d49b655417660b31ac83754b32a74831748dd8ea5046837032917d06e871478cd828063371e98d6039aad975da20b872f68e1f52235eea06307f8bbf3901881622cc6622cc622a59452d125179a6e9a6f2517de76b8b577a81d5182df38ce6e6f39ca6d94d334d7268fc3dc27cc7dc2dc27c7713bdc141f468e71ab5d8e6e53f88f86c5c60d78000bdd9a40af607800d76480526a350a4e40da882333c035110756495b14777835846d48db816ec51c59531383a044624023837825a3939c213cb0188b3118cfd387c99af9f329b57108b7e80ea59bd62914806ff48c879a4f9190c776223bb2bf3a97ed6a2bdd5daafa49943deb99ec9ef5acbd673df3ee9efdec20346e52ebe9ffce3c9bd989f4ee666666fe28f3d2a0195d6a9135ced4a17357c7bbbba5101d5bd7eb35ef5f2eb5105fafcb29f6888e61da4a295f06758ef8fda173cbc8edf37fccdc67019b33a8940c9bcb69e766060002100083160000200c0a060482c1304d03414bb5031400105d863c6e6038190823498ee328888220888218648c21c4208308320a11510d0006fc95527c1cfca1932aa5647a00254f0843b0651607b8ef5cdad1f8dd8de36ce92b0f9804fe73c0b0e6915a0827cb4b830d4e288c243cde880282a07ad13ec6969e18e131c09fefd9f9f98e4fe831fd1df17e79942f0a2b067bac886d287e82888882318123570b8cfe05bae3cf0a0939cf725e442c73abb42047da94d542edd38e6d0195c98b494e2c9c3f9c5831202c42fd0693fe170f14600381c5304007825108580c0045e0e70c7efd420ccc7e7dfb3c6b6f1c0a50fd909dec21240d98a68fd17dc6361182598c3ac4325e9046d75af161e8d4807500e80a1889ec85bc40d791cd9e3abf17d2d4089f6ab7b4a9787ad2d77004177bbef54d93bb5f023c55938e1d2a87593225ca155f07013cfda95337bccc0a1d751d5fc271bd0302af151adbf19770b8c5042c20bb66c6cedb689e7b1bd896b3ec51be88c9fb4ef764f2162ddf01c6cc44a3b4b04feb192a97daf83da7f9c6c13231b69c9e0d21ade7c2c741100040488b062cb1cf4e46ae8ef6d6b2b84b606547cf5ba91b7711179213735138848730a009ca2a114672287cbc4b67e08431109ef77f6107040793a8208b443cff68d608b6451a1dd0065227cfb87d23568d38a2461926ebd7a0466235fb413bb4342165b1e1010159a7463631b480a6b99b3c35df8b665477114310a8ac8a699bfbf4e7431fc2938bfe01480ab9f39f943282e654c2502f4c4e6ea87446b03a0fee662ebfa28843bd534b085a720a8f5b28a06a2bcd88b011526332be6e235488e4aaf7372a08454dda58b86b5debfca2cecbd3cf0e7e19b2fb330b18163e865be8903e33d920bce73450eb8c4b350315a7752f98f13a289d3d9c16528b8b0dea95a1391fcd8bca5e6f73ff0cbf756c09046dd3900e22c3cd4b18520dcd29b20b04384713360395c7fea3f373050abb99a1f74a951a30b58fc806617fb0a9146f33b2f9ab5bac545f33451a048c3244356763e1d0266a60bc694370385c987f96f0d02c0b5dbe3107fb7fa5562f589df162b3b22c17f38f3c2bee8110ea04ed16016ea2641ea8bfe066f6dc8f61715be3b85ae07019084c6590461a58492c76531b5ebbc514457fe5da9e18e3440844d7c6c36b9705463ecde9d571369e4d45dc701a4caa48176801c023be0d235ba252ec553314ac04cb886f5005c6b6f7ed52512f58000390729bdfb6a03c447e6218ac50991ab380de8a615e943a2348bdf39879d22a23df4028bc8a00d009462bdc21fe152532ba100c16339601f30f0418d63627512126bcaf2a52d5f75bd88f21458e7b29b488372feb1e13a67a99cb07e3863a5e90f401a5ae00bdf5d0050014aa8c03289bc732845e292a0cfad2085288f5080c9b31738f0fc9d9b8e712091198a653be3614c137636a5ddc6d75dded523655f3b15542b50a01db4c84fd1e682bb88076659b7004158a6ea3d7a159bf8cbb3a5569dd190cf8916d2e22ea71d8de0d5b00d2726be84cfb050e186aa838a496b35bb1bafdc57a07b22c990fc7dc495046a9a56dc96c5587019210a38d7656c5d7921f3a6ec01a492385d94307c57575731d4cf6e812a7624099f72b40137fb7d82cb5f09c9ae070bf095218a5e8543cbf0d2b02698e1fa520471005158b03ae2a476935c735db9feafe0b5642a5509c207c2ca8a4f7ea22702c3c0652ff48f0434f9f0f20aba52247a0642f82ee1312841705649d86bd13759fc083e381800f00267735e221a412ecdce7a2191d4861d34ac3ad9c8df35878141a5c96f4f78eee9fefe2250bd2e60d9cf85a61910c7e56b9137f5ef6bd2b6d27d01d2dc4f6e23a2d1e4fe680c0adf4e0fa0e4f59a8d4c2f7e3faad898cfda6c339e3c10f5da2ba7ed55978509f0e02102e863779e3a728bc37a38b551130239ce547487615a3f0409a07363d80022476e8bdd0b53f53f511794b62ba1d4b89f0c06b8a0280416e758c4f151bc8e3a36317755ef3452707f38ad6b80a6768043b6af093ae6f8fc583b6ad00e212a31143168e6ef6d59400dcef18bec04334ff932432b8b2f2198a135dcc6d0061b3eed72d8a3ffbca7210415a9b850bf6f2d2b1ea8a4655b4ab01885fca3e21bd34bbc9e94ae9eb7b32dbb66de7d411e34d05a010f4af2c2890299f408e5e067e62025350c81c06cf631c48ad5d70e53567ce722a23edf14d11facb840cf34a84f6476862b2ff6d75fc3be37df714a105d87ab8a12e920830e613b5c1016f705faaf2c59fce221684aeb6090ad9c83105a819bd3053d202a31b01800d3c23690fb2ecb126059f95a18d4d6b4d9c96e8a24af38b32b4ba03fbdc0ce32bc7224d2b2d35edcd40cadd73ac68b8e3a368c41ba14b2d6ea080b5e755b88bba4016be31a944be3a69f46501611bc8ab132bf44965387a26a6449b3e6d750ae18483c0a7e2531cb42eb4e5a552631427aff65a1a220be858b32836dd86dfa9ef561042f64436b2a1938955f2951229a0c27180c67526280134446cbabcfc8301fe05aa18d6b23c5035124ef17854e982cad178ef12c37d09ee9eb23654c9428b9b01fab58840a1fc0aaa91fe6be65027e6ee764147cce4e32848e201e2721530d3458453a3a90c34389fb769d9ce863f19b8800a304ec02c8708846076696dd014e58999411e10e07e8550f696dd87ec6d94f6cd8c6a272c9ddea0a83687f0e477322577e34f66cf7ee533d703c47acfbe80e747bb9d9d995ccfa360de67f049a7f1e949e8be59448b76918bda4c164a49cd9b8d944f77d2b60d75d09d6fe4c9f8f2fabe3ae1b7f53a8d23e381543b236abcf754f7de5c7011c931a16ee8034b26653d7c4377bd050010fb020f3f83315658da4b1f98dfcb865b7f24c5bbc8021d0f700df55d89da774e4a5370c624468502cc23fc0aa0aad2b2ae9151bfeff844fd4e5c85bc0948ddc47d59eb5b5aa2a469ddc84decadd1ae982c3e51f8f167438ca23af85fed5ce370b5414e5fc1f8c4ab1b29258e5868c5c3da8e555a42dbe10aa374813dff39b69c77543f205cbe0c14ccbaeb9b722989a5894b2c618cab60b1dc9f5eeec9f18904f3be1b85c42423fcb79c0e7fc90492ca0c50fd70f102c69b209b3c08824cbfa294f05941ae29e903918353dfa012bd4ea28ac7f19679a5fbd49c66374773e2f66151969c94fe2af25012d3c53ac0880c2a0768229b11af349e8e169b84c2df30f47fb12428f58c27846f518f2f8e05fb810b483d58b4d1b0996e9f9805c7be1353e36be3302e218403a4b5a4fc614348839bcb91b9f0d5a4319ee7b4cd2d6f14108657b7959d0b185575bed5160ab42dcb9d3fe1e472099f38a9e807c314ba297c53dc568b5e38f8f19f23aaf4eb7c633796af6f67ffc8d7f70eb1ec6776e9d541bfbbd821a70311fb3d0be2e56b70cb0beb2a85f0380162e3413902a6215e1466c657efc6b539040fe2db8f30a89b1a51a1bdb8d0a409c5ccc4ea6ac510a1177cdd2b33e101c7c9e85cbca830c80d83cee910c6c147c578f6075ce59a2f419229e4a468d0fe3b351127f73f2ee32968e9efe434e0c788ea20702bf494242cd5597a7092b81be1ab38651186780786515e5be56e98f2a8fe5bb14ad179e876b5c7288ab31aaafbe2ec87ccd1e374bd0a42ba3bdb60714dd6e0c89773ec442af79c7e5a363c8d0a6046e4f4119200aeaa79c1e798285eab6bef8524877a92db94c51c31b55a891c7a9623a6d987a191e6e20efaec7099d7f397a4ce922c876d3bb726860566a9052694ed02936594c3b824ba7aa7815106a89dfc2b40d2ffd6715cba412c620f6cc24e98cc9a4ab478550bf47358fe338a6b753c8a8aa63a9eaea8eac88289e1e7987db4c90e7b29a9b477642cfeb2aa56980a15bae0b4a5f202a3e7b570e63ace12e551a7cc761ec3948294a31dc53d06f55a19025a14d8a4b4259811fc6194bbb78a2e3083841250f042da2ab0bd375052c448de45c1f2dce770e73dc3dfeace028077d366f7a05022f66a8f69048a944c48d054ec16276c41e925f1958236f29a0de6111317e47470ae6cd981477158921b0a2c90d2dea1f8f8923738a7b0fc7542a289885f71ee7f6e92c260ddf000f69adcd1b4cb0ed486cbe5dbc4551de3e088a823aeb19a2b7341a7809edbc318c39816c0229d15af810c046762b521132492de3c83636a20baf30da4a7f64986d11a6320dc7102e99f0b65ec9e91731f3177e4bc46cc51457cab44da2aaa0b21e2a14a50bc6a2a65aac6c7a4f8d9786f78dd52c55664416cf651a1ab9bcccaf1e30133f2a7d6c4eb0bb9fbaa2cbeee13179142b9b9af36170a303c4677f47843cf002f7f31d23723ac09335c2286d7d4cf54990b2b2c2c71ac0577bf00bf6fbfb66d21bac5d75c3a83283c754b2ad8c2a825737e93ea19a601dbd91de472e08cb90c1e34959acf5f4331d1f82e7691527dd7ec46504789caf74c7ff03a97fbd9420a85ae16bcd0a9b1d3ded47f8024c0020375b05970e4d2cfd2cdd8ced9aaa7514f8350721977a16fd83dd00d024cb213952a6ff1476da030ecd696899b44bcb763e8b5a958d0b5bf16798be43fb19a79322b09f2104fa52840b4a3006665669f6a3f4276e9568c042a1e7646cfcc489b183918e519d5f1b8e58cac95fc7488bb8a4be81e429481cba776ddb69e195300ed90c7d5e82d02fa24209d19615f4f75d69714b5676e81db5bf06c0c8c06f1a11508bfc3208e4e5506d755a15576827b84ee7035f8dfb15c14b1b030c417d7db7f60fd494a55b64ce7b5b0de3aa635e43a11cee4747a976688bf8012aad3848398e8bc592a72dc05a272d8daadd490729706ffaa9794e8ef0193457b1e90dcdc9cb17f7b4265ad8e146a41777fa9410fbd21a19ca474c6a79fc012408b711324d9c1424f74b79d23ad260d6edb09f72c04221bba5b9a5ae546b7ddcf530db26f60555fcdb91b048d6321bf43e15bdb862dd8d63e52be1158e86b065e1b402a472221c3d6997b14bbda2b0d984eb97d8b94611e5080f998bf3f21aebcb9d559a27db5704064df9e4babca91ceebec256e38b630c04ffac9f219dcffc836283d6580381c05a56d8264ea35f1656d9d6ac65fb6019ba960aea4cf16dd269082a26b2b575a389ff3324a367bde12b5c0140e6aaa263c19dacf5669c1c6cfa286e530111585a932628291be8e0ec6ea06b97bf5697327310d8e208f289ea9b4d12d643b8145a3239f337e5b527d7c5ec29b6b5b26e74128ae544b31721fd7e334d665d4b28e1fb730ba6e670a7c17d0407b8066e6d6b410951f5ceebfc9b2cddfc308bbb44f3859d3840591ee535b6741d98b0c09644512951397ce95a07a398ac50e8b6cdc56a5f58ffa89d07120d398d12d1c35c94baba9366121b65145f7d81c7432a8c2e3a5325966084674ede8643a3bc4b830f8e9ece81c9ebe6a2083fcd8cb17c1ae5e8b69f8c8ed6b9af1652774fc61fa25d64c979ef64f66813b5405c4d7f608c582b7e283f0b178ae92a621d231c2c081dbf2bed7aa2c188cbdc64e34fe1f0b03711d4d9b86a47c5a8ee4e62bbf3923204553048cafb37e374b3d910fc5ef31d94c8fcc04c5f504f360d57b0190449a9098082a58094e7a704a321322e226d2e01aa4b6638c5ff53e9cfd75f7f51d925c9429b706322ca2b4a4166842a8d03873cb04b8966d8027ac9a2326302a1c4202017d93d60a19fa81b6cb997d0763c79bd200bf3c402425fe0ba0790570829897227d8f399e27f12a27cb9df21ffbcc7271d13fe6a3058311641798c21ae5dc10d87678f89c8d43859022fb36a0ae3d8de1cbaf49f502d98be9e06928e7cac0e1142c5cf2a5932ccf2effd762001760618b8a5ce0a3f6d09965c52ff339b73f762e7ee1ac45259c5d4d33ca93812fa96534c7071527209e2597154c3dea3c9fd321fb9ce267be13100a5cc03c0f848690b7df7a69586c7c23fd0e6032cb52cabf0ddb251c42b12a8448bc6cab69cce121af090e906acb5255d3473a4715f96d194b80c55ff72ad900762a6143a39a6e6ddfc583090c90cab13cefa352a98fa793b2f2b58da598abea4678384b5a6c8f12670d88a6042df03e793775a30e76d8a14c153e5dab2f9a84c21ab33b72b375b76bf05a29e91feca0a2fa7cd208e8205a581145492c7fdcb128f79edb4585d01602f940218acfb63681919a1bcf4c7bf917c80ce723d19b18a66ad042cd11c6db559c850fdfac7b71db9f47643031d7d23980c1881ea4e260b84c2b80ba0367565e377de4c2f9c2c10255f8be1fa72819f55443ac77ff1066ad0b6d3014ee2a5afa36393a2c9dc86e70d061767e454b05c8d5145d9cb0206ccb155b37e2d34471113602cba2aa073daad6ad6139f5490ea6908b585f5e2c898006a9fa8f8ff4645e7907a1d18d4d12d80652d926f5ef4b592efb2454c795c7f577a4c7c33693c8d1ac7a15ef1f8baa2aa3b0b8246502eafc742fafd37cbb6ea23fe2bb15983efe850f8950d8eb80f5b99cfed027ba4dc3daebf107ccdbaee84c9aa10209ec25bb3713c53605da4a7ea704e22bd4fc94656d18f3965d371b81c922bbfbe2a1e40819275a41d063d09310414a04477a287ddadad4c38f90f5372989b8ee52ce64c6c111215c7c8e33e064f420f245d2191cfe287539e4cb42d43968ad703ac5c980eb412f66cce0eced84edbe20de10e2975a112c3fa80ebecf6adccceffc4330bc8acf9a58e250da2413d21b2795a9d8d6a5fc52ca44a41878931b53ac32ecd6d55fe6e3b50493f8584633cb88d61ef2be59dc555aba45279f94652b281569ab44ce9b272d2ed92c6937f7fe96111d4cd062edcc3947fbee4aa0f60b0cab0264429bc27b1ec36599264df7c4408a40a76294374c2e2c2f6af202fda9db222852949333273ec1f9868aae2be056823a3c1c8069c445e6745b6aea4ee0bea523e1587a4869be5d49e2927e7b7a73a8b02f2d0e4eaedf38352a14a3e6dd822aaf10c64eeb93ca77e4b6e29596a90b5d3a96c19dab5f65b365e84fe70c0b2f1d53b7f80875e507f34351dd9a323a34053caf8d1be2b21d120a62337a4b4d1b019e0a853e3f3d22a97a10d794e4e3e9814dac874541e2d3dfe9b9c6ec62a5ebce782fe6a69793bc6ddfe9195a082d17080d8842aec95f0969a870380a1278b8f4b340de30a56a2a594fce59e5c8a097ce71433a8bda89f0f8bee1431e8489a5ce016a0da78c82680a45238cc17754aa2a13d6ecb58616b45a5323c020f3caadbe710931d17a48664d6dda35a254810c0557999f47fbaec34990a84a3ce1919ca26aa825f6f9ba3bed912e6de27a2e4557327e25a5a3201b15e37fa9a07bb879ea6c9a60977260e359bf3c4672aa215c9cab124dc4715a8aee7458a46fa8cd514b711929fe95c3d2bbedd9ed4c5fae5b8ca927a8640873e5c63addb17e7464a832738cad9f88ba6450958938e37c14027b4b0480ad7a92000abaa6e820ace299d8a226b9dacd366dba9b117f511e3d4c6432ad963313af25737402a04df29af183f92eaff7cbbe4d93548df47ebdeb6fbe300bb86b7443ac98a8063c3bd9d042e8cd5cb0a5d39d068b8e700a6ab46822778fcc2914fbfa8681f45eae3aa35184ec4b8915895447709fbe6c184ae766ceaea9d90a9688ef9629d0ec49ef6bb5b286e166f92904d2d94b47d508871ec5e8977560f1f5bfbac04c49ed3ed2648c12101de136a8ee1bdbc6bb6a6aeb040ae2a1e728e5f2e408ba1bc4ffb61c47b65cfcafdca7e8740bae094714ebf196f793a5a6b1e6d03be87492863642b751bd41bc6e444107afc1aadc08098967f1ff66ff78b0887ac768b51829c6a148a480f1c17cdb688e26d4cc59a90a5803da3c0afe7f2e20b6b8c5fc95ddb869436da02b7e91e683dd7b3242291d3c588e1971916abd19daa44c42e76eec43cfd0434a81a6093e89024f039af88b146807d24a6a15f5b2fe0204ca4861ccb9b113e3f4ffbd6da765cdb19d1e3aef5a8c3c685d917634016b0a9b27ad0d6f44e0e1dcb70c9ca2796bb50c6adc99b2a892e0ef7f355e10f56740c276590638b3d65e2a63b55016aac2d0e045706ff940a923c642084fc8642b547c558eaab85703f2f013ec86f8a6447b8a11fe4a65aaf8274e0c56744cacf10972bd52461398a95275c67eb21e51cc0ff5b40f2d07434ef513bcfc7dd3e3f5a39586ba5bb9103a79f28b89549ac619dc6d4c326812ae6f5b5bb265b455b1aa4e3d86238012e8fb5631850b26e3e2a40b12af7015bb5ac2accb5078c360385be42d85f0eb616ab0ed29a2b0e62bf35c799ffa4d3a8b0781187a8d4d06196feab4e4f54dd3e24f885647430be6a8078ca9b77ca90aa4f87357033fc5a08fc362ef144820418444f8794968346d615c8b3155b07bf7010f718ca050ecf3cb5154142836f69b081d8b2ea3f2b4f48b9822126efe0a24a8a2d5c16b6fae0b6b902991b101d455c69d16babf43b3160e640af25103542ab11c11dba87548d4da4817eb28830a8a2fcdd88c21c8665d334869f98e7465e865832ee718665354f6fd8874d08f88f6aafff15da03b95d33fc585d296de7b37957a4600d36fb6a3cf34454b06bc11b9fd952b7e5606a7d008d28c6ca1eecca68d90dac526d7a59988117c140957ea76d60162b6ef00551324b52e7b06216edfa920b30b32ef2a67bc5a0e19ae1929ef8b1613db73e545623053cba721722c4eae1e39c5285bd729e5e7b9a95ccc051d37ec5b24563d69e46e036f5e255b0528eea08447fa6a1b240fc6ffb7ca936b888c7cfb1afa64c9cf5d994fb4ef113b13c8b421d3037318c687515d00b4a6a7fd06807224a9eb6235cb720a1a019ffe9f1c143c401d3803d9ca15ac1c953e07fa77802d04c01853a7da2f2e796ea04338766d90b335c2d1e6208c9939b0ea4400be823570fe9d3c86dc94e4e595e5d2e5394d6ff54cc27f31c7cfeddfc0fb301ccfcf9943c5a836349680c05b7ec531e54074ee30251cb1a2883860ad8d9c4106cd8cc84d1b874033a3fa1ca7ae02b8d46e0c4503738c0747a076ac351a6e0440be565732587eb552fed23248e33c72163e575a46bdacec8a55d27f6bf107536d63edbd7e939511e0f17a27ff037e00cd14aa48093658a337bc080e227c8858d7673e8037c1536ae0db14bb7378d0c9ba40b5b182270ac3014094308eeda526c6a187aea8ff5284e94814071ba13486d6ef02d2dc9514d22d4875ea4394a8daaa0ab37c2046ff378a844349ba619fa67cadd8868a0921602564031d09bb5ebea8cdfd03ddcf5275378f6a5794635853ac7d7569cadc7d4c7f301240bc365ca40f3fc5a8b9e9392fce9ec10e1dd8284bea9355083fbbb873db1171ed59a3a52ef69c240ffc950bba918a4a6710801c0592df1c8fac3fad66e6b76a91a25199e142e0cf0592c3289cf07637b63879f7eae6d44a67b2144632c0c4d2b2208c14da16bb7a67f68671eb47041e672934b4ac8db367f5b08e35526d49eeed700ed516b9c1192cb191dc784150b528f05c0dc3db6ee010f2bfb1c45e12a6179ac1ba5745c740e19879090d95f02f51ba9a9429e84fe54a6f077bd24c8ad896967e43c632b21029a8f7c4ed0a5cb67856a0cbe37d6989a5a594c8a4121256460d0b20d4711bd3f88493de605172612cc362e0909ee206fb49443e21cee37fac62060ad65ca5850499731eb5f63ff55ff65cd2166149ba536357250373ea52e5d5495dc837114495dcfaed54094ebe25653359e02bcd64d56a52655e17e40b1c3325b4d7c080ab26cf19d6b69ad8e2aa14172f49a0a95c08cab47d9b98519dd274fc1350aa56f56e2208b1806cd253a14d89dd2de01ea9e43e8ceb8ba08490371af6491847bac5e1da763dac6fecd97d196f57090aca8ca112650ecba0b54f39815d1f925dec5d28c6d505f5589f08e4a2298dcec335b8ca179fcecfd5cf0a60d985d2f5e1f143bf03f074b08eb6f83e22ae879f2a26b95e322893c9726ed3fcd23a232efa9e9981ceabdce59fc193c8d36d5f33c252413a62dab828e3e81127f19ea5752e242b51ce04a8f09310045a61f1e8fb145c8df154fc04d4e986b9b70514ce8931ee5d86e23dc90b4358ed27bc553656b641a1a1f2241aaec1e4ba8595c727ed71d80192bf8f5392be68cdcb126969dac9c726b7a5d32b844faded3eafb24ba1390c535c76beb094eadfd3eb7c0336087011c13eb4f084bda017c8a9b2521d884e5a229a4885843a690debb8e46f41355462e864663fab89f5051c9f557e7472fdce2a3dfdfb962250844b4e51d7eb870e9d95a23a4a73b03b05314e4615bc0b8d17f44874d9ab156f8725b73336a387ef4e5b1f34c95fe0158e4c01e099e1d4a4bf0785e4fcb40157ac40d35b075cfcf3bfe79423b78f2b192d9229b045e122ed40ee1d5652897f75c1636dfc5a3d7cfdcdeec5f6edd4bfa6edf30fdc2fc8714fa2138a774a3f822b32b950297c02ad87917c283c115d258093cc79bc172cdb0f3602f65b52aa317f8b102af09aca2d9445a83083785acb460743ecc467a01c911086f70bdfcd139102fa2e46ac8d4f9dea40cd1cf050bac96dd9f05db7811c935810af9ccefdda08edbd0505b7102c2717daa89b21514e9a1674634375ad1e756c0e23427154e81ff2ecc5637bbe436f2a27fe80a68654eaf925625bbd59ea555a2d2709289de1998fbb6c21029f0d8caf8e9c663665cf37917e7ffe5296a139a4d412d96880f81b784225d754cfea44e29bf9af69c4b4fec927c0b62925db3c4d00ef63cfda3cc535987caed4d366726f2485058e6f69bc7aaaab9143fcfc33013fad601cab8d5cc09123a10a7072213119790e7311ab480f486ff9be7e9cbc51714c7befd8d2c05a5a50cab2a8bf9c39f5e50d903940c64f575ffaea780aea81801af5a71403ade992e37facd503e84de819c3576e2b0974b25f886fba1ac291114ab6ce9911dfc5f12b805a3769670ba157e35605ecb6a46a82bffed7afad5ffcde57dc23191b34afec357e7aba5ace8fa8673d4ab9ba60214d3e965b66addd6158a2323de11bed2f02f04fe481e16bdf50f9186e0344fc510b4e3b075c8a205a722292a1a798b4cd432202c1cd9d23f78a519e95357476cb7ab4cc0a8818d25ba1a58ef1c01882c5cafcaee589aa890ede905360a13704827d86d028773d228b14cb5def068cd2bf66a71fb2e3c892b486c21eb0eab6e6a763d4a620771d99b41553b33b209369b01e3d76a6f558a5c35d334927a3a94904489757cd7dd4b1b0b1556e6e47778b5b41e6ad382b0fc6c3d5d0ac6a4a294ab4b5975d30f92f8bc2b86893b57da5d0c3d42d266a875264804707e2a5c5ce27eeea7c97b68d15510fcb99294d48eeb1132b8e06ed1bcff987ae5d5ae0658520f83cfcc91a039199ad48a7ae4af9f3fe11ddbd8c078ce5b4b23f07af35ef47297be3678b51439d0f4cb50c3f18b977cb98f04b7453c60cb2f7c488cf813a3d7e08644f0db8adfd3e87a160edd62ce5198565d38cbf1de656119a69b485fbd413bfe453e5dbcc08e4c889d3a9464a5460323fe25b2f550d2badc0f0982c6d85973aa11fd6ec44ca65a7f608dd61de09f2dc5bcb92c65b6bab149b476da38b438c5ede4b73deaec433a15de2c54fc421114216448d8f00170ad1fe365c56f57198abca44d184d4022c227d1ecae7172f984e8cf1017dfd4e4c27be1ce96c0d12b23bf1c8cab6f87d4cff74965b451162a9a2751e3662b962529fbe8e91209c1bb31a5f42dd429f70e31f5f5c77be6d2163d05a8d7b7cfb57d94daa47312de81ca1dc5fd1f7cbbb1e84899e735c524d9add72e5b4e8c451c94a67bed192ac731fd9fdfea458a7e30884e26914f96682b01735b113747d2c6b17480374f7dfaeb700804a0e8f176140ef6c20a1cc3abf74f0e41f9513c37d3c9c89541fdc022bab45537708184deee6950afd9a0bcb90b92eb387fa4850df1c9c87bac92a708936386aeedf436e5d7552404d3a48602d50cd0d1d303b845cfde3175cd85f4090871e4ab4b8ecbfb557455221ea5dc1c7cbd3c1a94026b9945137a15a7569f3b3d0c0d01cf507c3f5e769506b9453a12ed2f4fb64d572dcddc8d10f6966dae136b5e1600d2ca0071e031ea7f69c1d021d404ab08829485c834b97443ec4925a9ce69a38c24912058dd6799b82f8fe1cc28ca816c937fe93846ebaaf693da10b9d89ab520b5c1892898b65821fe2d3e44618c83f8cdd55a4711fe7eafc1307ff6b01845160c120d2574185c232fc1c1c3c0ec228aabab7417a561b2646fb29dd506f83d6e349f169cbf13f6f7e05d805ac6688b35454ac0ac0fee7b5fc9bf40499f51bcde1b8e6786fdd80fc6c4008c87152bf76486e2f3a2b2314b22fde68ddb7cb9994b72985042225811a1f48b1192f21852b7176c8cd6f7d57e70a4ad7215632df1f8e0dd9af0f915f5c7fa3e428d5745e1292d31969203703379989233e8b44f542c73ffc18ba9343ef0cd6283e6aeadb215d20688e77aa01c3f4024482214c24a81ecb503cc57aa10e94851cb5f605be62cf8c510f170e81a88a40264c9d283f528566b264a655196122de27f8b80ceb8f1c399b09143674bc21a047c83666ef475dbb99e7af24bf1cc3f38e242b00b6622c81e2a368cd35ecc14d81937464ad6804750e30fbdddc107488f63fa53802bde833b140ae65d7775dfd7ff9d13e88c0c67b5695cd0c89820eeb94ca69f825c41761394017ae7b37203250cea8892afe953904908c841b84fb8146ded12692ea7de1f7b482a6c2cadc0ee1fe9693d1cdd011471d64fa3a01dec690498bb1d6b882b5e5a5ef6dc089bd504dc3e2474ce9c43bb8bb4a8b8cfe1b1707162469d2657d388f33100e63fbc27772a38acdbbba23a79a5bd51d779e890164e8f42456b2759f80a2ce8963feae6e25082ac11b9a70ca384c20132475ad01139460cb482b0dcf00ecdb6dc38387feccc7179fb48984d1681df67a05a2b3214148a811f31841f3be6b1e03a2ce232df3f0fe402858514663f0b08980f6c80d278ab9253099c6f60dd585b7c01d73a718b6e06bdb2cf83b18e7312d0fe1ad84094042f807e354dbab388ee5bb603700ff196e45bc3d72333574df5b1688292e2162b625ebe6204c53cdbf593a304883ca2c1b911a08ceb3da638ec42c54555811b4b515609ba5a5fdf11c5a55f511606da9f1b4d58cc2df1b0c246844478da87dab912953840ab435b2b89cc8924d2252762a083b16548269e2b45dfb959a2ddce16d3fe02d2dcc61d8ba328c466bc06473ea631fbeca4e64fbccee17ac3c80422b5a7a93f829b2caf1b50315b5aca495a4f059ba2e7185682275bb1b35f7e0884236a4d8e7aa94747fa542dc7323550ee74bbc7cde425c218cb37d20335014820823909f476295e41f30b781fc7cf29d597a6d4ef53efcc8ebe2e7ec4c5363a1a1b9d330705bc0ade260a47ac6d2b2fbb9ad83cff922681b3b2edfa5b3601142c4b35536fe33367161a1e318e86f9fc51236e575ed718addb05695f9cf1b00d1c078a38155225673b0d7aab689b3c46d3ccdadfa91ec2b1af3344e43b3817f392c3411319e6c660ffd65899d6d615f863ccd697452a5be32a47821230dca706aecb6cd859a068e22ae2f83fb957275997aec63ee0d1f12a7d0bbc41057854b8689fc85efd24a91e32fcf8a0ee5fb20df3be03ba9f9ec1fe24047d02caceef800c008e65a46d35a91712b7f4c25734f11a56dd632f90cd2c7b6ebd74c4ae0473a8841520dcc88c27f6c2324808257a76ad04bc79255e7ec770d122786598f8f2da41f68a9c86daa85b033bdf306fff426e287b55fad3b0310f96fb864994918bc900c6c4e5699146fb8a0484694085c0259fbab23f191a60e9a45101558970fa41c3669f12ad1ea8e8eb1cf50391862029dc39eab31d9e8cc035f4fed0fd1d88b9f32ecf8b4ec4618930d8f3242b2d87c6d458dc51ac8f321a006935f3dfe901194f8ad4b0a81c05e66a92aa4ad7cb76cea36e5a4857221fb9c157045cb872355963edc4888396fe718dba0ac1226bccf59bb1c9f7e6fe2c68b7bcfb27970cd1749238448e421c97e9e27ba2161fabb2042ad491f9a735df909d815858cb50ff9efd433fbf6019e9488b7730162a7365dbddd6c6d20b1785a192caee2e658974f8ec1e1d9ba7cd056f6a29c06aef24f7e913158394082858eddd6515602764af8feab436bd8a8341d6018f71d6d30802047323c3a551b079c129dfae36a3e54c736565e9045e95ee34e33aa7119acabfb902cce0f1f3f1314e39c2a74543f83f66750ecfae7087a6f196247181e3d41d06ac1763c0b8511a4f6933fa1a75c96bda06607b4a59c65ad4440b902fe4828b69513398efcdaccd6f8c7d26f78fa4c7c62772b629d7fe067e1a33b9cc2fa50eb4bebb1a0ee3d977e861ce8159b12681be23e6c9b3a58dd57b76fe3127ef7fb56b7779f0ec9c766833917c35872d2c581a38b3291237e0f00d75c5ce012f64392dc8d784eb12200f602ed5b0e54a1be8b5a0da958453a467fcdde958e3a2519bc19895b0e46eb58f4f55698bee96e243ff6268a453615138b1c2b2a5ed7f603a64f6e3da0263e5547c4749c2ca7b63d37c7717542c1c9339956f7ae81f24a94483c8dbb3158ce86c2b9b400934941d3073bc3a843a1f09cd85d57d7146694d4fb87781bd72d6523b7015fbcc52038e0d902e9192d77dbe4600510a01baea65631614d905b56aa3cc609228bfe20d8a2ee3d60db276cbf838a7db9d28da9eb939dfbff79f98b46818f379480d2427d74eed7bd78c3ae89e4e9d083fcaaa3596d69870407215d0c6ce15cdc265e07b6190a35410de11129815004454a9e89fc645e07876911e7d8b24a9ab8ce55daa4bbf4f0b08c1821d4862ad7205e7a5a6f569b410bc8c4b965b85a8ec5d0ab53a98456e240f9238e898981023e039cf73820d53db879ef7beec1e7c7dd23bb30ca1eacff1582219f951a89f46ba8df472a1d02b3ff5fa33c9c348ff1cfc3e258305c99dcba96ad28623f860535c53ef77328d8903c7a0eb41429d2148d0a295d8a521ea408c65b1898b69e947f69c87f14131a2d6541434b14e46fabe8683d054a33108f8d4fc828e53851da66679990013fd7541470e4bb5269a6dcfc26afa8f479cdade8893638f22a938749e0c73ddd9c1518d5d5c14ab105550f9ccee0f6821c5cda278f21a222c33a2a972b24bc7434cc143e97ca7d0edb430a2151cf27650e70e988263c945cfc7d13c0d33bb90b5e7f1c4e90d26a5efc184448f65cdbbb248063eaeec994a1ed588ae9c8ea580cd22791787aaf937ae3c6d49d2aeff406c3a6a77ae1e6fe26d961fbec863c50f53a839ae23948a214890800c93ffe2a1b05bd7b0c0e2e7b18b535315ed0c34f83d05231f3c126748c9634acafa66518e6a780c369ed4da38eedb2b33b8ffaba619e76230ed29e62c6d21e9c2f736a9f0ccb70215ca90e14bbfb1dde2f7518c6f1825039a0daeac027442790f8fb702722493ecf66f146634ef9d9e50ff1bc8b45eef50c7ba6661f0a9cfe039f25ce1705546a9817e331073b4bd1d8d7a83c49e25390205a647e2a01ec9bb747d597ce3990ae3de38c44191347f4a31aa96f32bca739a4760d6d86cf4b7b39fa6a2c90a4779f2aa3160bfc445d2ee8724c1d1201a42728f8bdf1621de056b86e9161cc6913a7cda61c8769ca34f131e51f280d68c9f5e323cfd926d11ea52b244fb5b1379ef7404e2846d3426558dd364f4b638d18864b6cfc13b3321a6a2c5353a0cdd9da2da255c16c537c9c29200a5d1e30388138297412dc1202bda4210a4f0cb15df2407fbacbd1253792da7e4ac4f7a6dd8bded502cdb1098a5f3d0250066da49d6b4f42fc469728c94e089ccd9858d3355cbb0640c0102825db9f98bd8a47500dbd506e45dc8c06a4c2b766965f1c22f8876fd8da38056d703534446bc0c68ac7539f8c0b5392eb3f460d4a6eaeb275352bc5a25acd3b6900df52b8a4544d3c5fa8c236f6626aea81db89d8a9365fecb39e1afea8ae4d29a9d866433c64776da764f66f3e92b8c2019330ffa83f44977625aa704ae2a92752becb498af1d6df625ee6108873b20fa378db86674e1ad170fe68841a66350d45d30cc54a56267d322c15cf302da8124424c2ab9566916ed1cd1cda043606df1f7b961250a46f944b163ca43d076595f022ad2dd0b4b7be7bb192a031337157a7ab295c15591714035798ba27601009e49c290bc2939e5370078eaff1438242bcc85212ac4a77e700f6710993fa5b65d7c02c1eb586aab3962f783a0720fb3a74c46f317c81581646d5a577cda21f758c988e742c1c4eb31858f8714c1831e4b72a38abda0311c8a74e4a7fc9722bb1825b3d42b31c1b29cd310326c7551f000c86ce7ba6db4f8a4a254d5913a7a3cee7ab115095fe86572fc8c779c7645c14565306ff36e650976a24a0dc28b7ecacaaacfd23dab25049cb1f08c715b81b80e6bb2c5060272842924208a13cf2ab3d61519da2901cda0b8ffdc0b8d42d2c707f14d9fb3a5dd79583a80693589bd8ac2cfbedcc291e174802020986c6400612155982c1285a2db95d39f5529c9ebe41eac4fc2afde7d430f9c27301e9f30d5c80975a7ab55cb5dd26e66b32056fedaf18e6ed7bcabcb4acdaa753413b18a50d7ada08ab84dd57cd24cb0e4252ad35107c94591820e6501c49db23ff3057ec424db07f1f5728bb5ea5f03a1c9e71737147db91342db1bd7ff337be6eb942b6f26456ae1f56a1c7b6d47272c55326fa4cde1a8107d564f9a0b3b6bd572798ddd84aa2f748c93d701e4359cb03a887934899e3eda44e4744722ca4da978414e478fa27489d5cf1a4ec50793992e75c0ad2b8ca0a3c88a52417dee44588d8634ad37060533a3005cf1db2752ef697dc76d81a953baf1cd3d7056abafb2a84879764fa559fd3e52407dc907759ff6255cf4ba3ea71d2c437f325277df70faef756ac9cb86b03af39ea96834f113114b568ad840982faf4761f7d1a5e402eee33010157460e71dbdebfdfd8825614d35e7862e8004709e7a85d65f25f9ad5e34a8472727c2fc1e095a13c2e9a7cd6650b81ccfb8516a840f508b43b3936e1656efd343298061329c2622e56917deeb030d114013a74f85678bfb9f520a3438597610eec49dc7ab04fbacd0ba33495b6c8529bb2f0e25fb5fb79cb1c8cec218aa9c52a59a1f056fed507a8b33fd257d10a0cde36ef9487f59a5d8051bca1e8ca63a47ac7a4710c140cbd5703eba6c216ed8f93a8dd16a4aa6fe0575dca106dd69b79663ed01c7d78dbdd71df06a5e0bee8613eb2568e46563995e6392842a84dd06324596e80b6d8acab744c5861eea5463d6c62d05da24ff07124e6454cd8b67c9a89f944bba7b1d1ffb2972c473b25180048240f7707ba0b97dd70f9f31d84645628f0ae813fc77bd330c5fa452212b2b774a333e812f4b18fe6801f7151c786a6975ebf5974bc745902531d07641e8128e3f23bf91d073bbed6c2ffbb45afe022ae55583936738294865da0bddaada6494a1fee7266755421afa9f27518a27066232e7e50a8ff057dceb212ae12b2077b6e554cc22502445f7b99aee2302a0e3924f643958f78e3fd8f47d6bffaea7fc40e61c3e0bdacde1fcb744aa1e1c38b2f6ff200c9d4872400f6356132038219ed4734c958aec4e0d80c0c1fc05676ee305de7941de5d0756fa2ec188260af293366d19cdaef3752a258e39641224e68f600b16c2b4f09063becfba79e1b78a6ea229336deefd10c7896ae4657db6c409b4b914aded0e3d30b9ad0bc72b47512275799bab0e1247ea6aa3a0b7b321ed1ec169740585768c88dc5d62e344b05eb57b0b8f5ccaa157f8dd3a0bef30204a4ea7d22f38044a48e546be37ef888ec56e48ee856eee845e66e46a1f51975eef171b88327ce13314146d020d9ccb9e7a2a64886d772300a265d3909de34c6fb93654379cb951a44428cad208d351977927b90fbf0199ce16fb4ba4080e01ab194fe04526e6ae8f47adfa7f4be6a79eba7a8d5995fd16783f2d5f468190036e0ca12cd4767cb8daa0711928bc3a8854d21a9e988d993670754512eaf4a634222dc8e2115798a12d50792b8d7943ea9becae311fd85a8b391e454251968090f0726e871344ee682749a2011f119434145e1261e0dc2aa53556e6c892218c6c0b74a679aa19b879b149d56b3babc34c97ef53786152ebfd007878accee37e9bc06f43dfede84e37d8089329eb74e2176feadb7018b495baab94e3501401b983029599293c25974c5be4a528e584e010929818230b42fc4cd309aa4a16f6d23a0c20fea0494f144e133d4f3cba6cb127c8388f26b413a211cc3979e239d870ec84e3dc0ffe3e015844c84e9f77d726c7b13354afeeccccb33734e1f5c38f891537b7a8318887781f773de5ddf59b25b0d501ff186d7fbbedd87075424313a32252c3a1a2d51657638306a9a38741d46a0f407b789406efb8b67aca7a93011ca05d2084260c0878023c190036d4870af90b166ff4196a00556021ff86618d640ba5a72c3ac9520d4ad67f053ccd876890b01e2b58a9108e06057297067e768c36b0b21864b3f5531a98fefeff6f50db1ffe01dfd860db9e946e6276ee422373375052fe058f03cd7a9f87bd0bfec6bd8c4accdcad71e89857282f42c3567166aceae70215d59835c5bfbef7f93509f4ea2ab1160232e8d1c4308059366c34b53c72e2ee5b5b49f3f50dfbe28284efd1ab213587a63a7272c4113b379cf729a6a65b67638debc8ea1bbe0925711afbeec32a3b999513960151084c6040ae2329ec31a7545363742e955cb21580f5fcc2b49e416d587ec9b6cbc486e484155328eed39ce1790ebe7b032882b3f04e308be7c3b5a34a3be501ead7ea3c066c3cc0817873d9a7a5d862250d185671ea03e68be7109c965f4e5d82d31f75a0a961e18675a3b39130e7f12178b28b54a0cdb6a8960577e101d8a3e433fcbe5db1c39cfaafe23f419ab049829250a7b002579e3d3642442052bf2b6357e8c099da60a476a4c43b5d65849d980c895320dc871bc32a5a8a5fe4e254b85984803cbfcc8ecb7a100c4125223aa713e654b80a756abc29041675356c0cc0c70f6dcba11381aefa2c71a9a6652e120ae9432b89af335c7f82463f5d5781844478f05f47e279e446cf985b93672881c99bab05811cde4d0c7d52d0d6242633cdb32927564e2b5af5a5a7874518a275b12d34704c9f0107397bede671e944ef7bb5aedf232868b34161daae22ed05fe566808324940e93d10faf2e033ae0bb83aa0aae7099cdb0fbb40bbec2ac6c86292a2a135a969aa0177d7e719d7e866b0178ef2b5d9a8537f3bd9dab1660b7a1929044538c88bf6418464beb553e2e04bc937e859dffbc6af6d8d25ac50cd2dd76695077f3b15bd02cc1988b420b4c58a9545c6454749141e88819f40644c3aab54bb988c6b5909dec3068dc9e2610c66d144a43cbc61d5ea8d1036c0ec0dbaca117eac6f22f05118c389e6bdc562b36b62e3501e2b5dd1c88b0d993c6eebe23ce983dd4abf47f25e1dcf0d950ce19df0b85cadda41c427208045d2bff1a9ba06f4a607d822d582fa6bba1d549bdfdb5da0dfcf653a1c5a09cc930146b57bf01ba0d1c637c5e2eb92734b030de00c1803cc1deaf6c4822966207d0d1190bf07ea8d48508e23f34ce8d431a9d65346416289ce62c5690e8fd7183120289a821e61e4c563ffbd54434e2f54079f19447c3c1be943ffa00a4441a6e088a966b03f89c597d1b0f119d23c402583e0a0fc6cfcf983418e9b8e5a3e8cc5d8b7d556bf2cf9aad95caae5ba81ca308c10da7ee2d739093050ae4706061ff495297c01f13295f51fa70e1e3dc912a8f40e15060b87b9875de3d813831330c4a84278bbfe2ddbd985ef5586c59fb4aac171dddfd90f929ec73c86e000cb4c85dc99651dc23700ecdcaf4617938c8881b9f5ad6d17678b42e5367a9d95eb76907632598b36a23443052cde884f1e7a4d528c211e0870ad1f72bbc4ad72faafc8d102842f340402ef946f4ce172c34c986a9b6dc908858086ab5862046d27f526d0acaf184520173bb016c9c0235d524e4a5613bc3d922258571a2c3b6896b89e2439c3d46f78715ccdea040ada11ece0af0af4a4e4a90cdb33ed3ebbb43bfa2f1b9d9a44078804637633ee68c74e62b2e658c1a3f53e766ce56eda15cd9207a050584457cf4b47624fd0ff80bc50caf3102bd00023bc1179f8b0b34bf186e4e572afd42f810f6722b1223f87be7d030ce405302d5c6d605442b45397c07667128723198e5378f04c93051de504e871122a6e320b26a1c7226251f19185176b1de604a1a63d0d72eb5323f3d4efdf7275ee588fe1d086cffc837cae5e94d59d18c54c8bd11694147d2a7d99d83cf0ce96aff79edcb853ed8f50b665a4a28ab1912a60459408da0bba0f0ded5fbe14159fd88209894806bbc3509a808b2d6bdc08b58a4cfef490d0dba1aec4e663be0e29c621b0c0f57985a43f8738626e426ee8f144bfd47b7583b7583e0f659f1aef4ae3a19f2bea1ff2c7accb72d5a9718cce4ffc924b659ad3c663a8aedfbf98445b17cf2e422f426972ae5640a018d54c8b4e73c49b8a91174790b2c5caa6e920675c91381720f72caafcd72951aadac213defaaf6cb9990527eb61614b8fff4cb978571a89c7acb78c5839e14cc03612c5857b89071a29e660f9ad27e498e64a6647c7b90dfec059c6b9c0125c5634ddafac6683f061a8a2bed29a428762f39d32a3d291eba2a33031cc82673fb2d4024606a6314e9541eaa4542ed5fc1056d3c862fcab7ef6d0882e0898602050b73c4e0d800d70c3fad771c2b6a408fdf2585a0d13c36de25327d98d38238c21e33ab71e5fef9c4d71107864631484a89094f20f5864e9b12e2d03e0431fa553d5f9197ad70549f8201c484684949dba7907cb9ab48fad87a1131d446928f2dfa1e17cb30e0e9772b9c3dcec1cc6de9ea63dcd8f9c1f4de5fa95bbe5a9802adf26c245ad44747ac9f2df322498078c80e670770cb68b796a85f0fbdf6290b0a85147ebb703b83a192328a65b30d7bfc4ee8ba4f1a13746ba40ada1591430696783d9d9d060b0d3bc7e54249228910692011647848333b86dcbac755e3ecc5fd026cfdd5e5b9eef66dce99096cf7879d064f446cbb87e17c84bea6863abe02b774a8430d81980898899507b1931f4773b537b128d8dca91dfbda1ab7616382e7079f7fb9e50cb2f38f548fe2d5cdc42b64b75917d0242641ea829741b22b6891cc6e56d0de01914951ec1612757d2c4178ff5f321efa983deda6fb851080d12128fa53d54aec1abfca2168106d17e38ca1d5508b48f4eebd6b00ca897af36b0be102871659d1c803579cb3d5222e635eeb3ecbd2c1068c934650830574c6f8c3900671a6344d4d605da8e9f693ee85f86ab3a9c257088942dd4fe504b8179ce741ab928770eb327f3c2d29af6c1a189bb41d74409475650c7975047441777e0d96868458ebc92ac65a9a884668f8de038ab4e34a473fe30a68e10de68512e4cda90f4fb86c83a5ee2a61bb1b4c0852d91fdb62ead420162a916259a34bae2cf28796d5ab2dd3208b2329aaaf68191258789d93f12797510a279aa1d7a33b0c9cdd40445583ba3aebfbc70e53568e90742d621154440d4a8154637237204a900a1dd03a56fae0c9886d6f15c96dcc0a4328d4ddfffdc9607aa551c8956d2dbe89e62d98516a46781740a95b9c724d93a2a7b42bb9133fe832c9a0c3b56f0c5fe0f091f694dcb20f8392ac12bb1c38d04fb7960aaf998c6104d1a518e3153f84c3dd4796d02b7df9787f7f5492e4e9da688d8000241b054fadafa7e522ccb30a627dc49318346fbf8827f7b6d41fbc3d3b1d1310603d7e1a381d667f2340018c0dfb53f49f037dc3a341c73af8720d9c1832d64c34bea85504e07fa04cd12862288126eab349f029cad1a54645f1eb6f8348dbd7fc919d7259203c2010cd799634a96fe6d94ebca5e3d1a1f2abeb2c1ec371cfe9168f12370a4750c46e2395b33d8c4701e050027101dabe768bab8c0029aba52f0f66d013e76e234ac047687640444272afa0415407d19382d4ce83ca4e149aa340363168cba0263494024a02466d5f4092595507559bcffa3810bf85f6c18383ba8aaf50b72bc890878178960dea2ac8fcb014c05a89a7f86c794bb61a3cc0c15d54067786b027cea8e5eed5fee6be27586dafb3bb4f466f030547ed8f2d25b663169d5967fe76fbdfff524a2953f6060707af062bd6b6c1020b20205b3608ccceb23d809758a2b30082b0871bb6e44d984478132a4378132bb0246f82b4ef8609e14d94fb6ed8116f2205f82630923761c49b5015f126566f8275e44d04c1c89b08df84b887fa8dd934d8110a10b3ef86e136ec16ad0cc76531642f90d120ab410603590c4c2eb3e1c2789abb575cd602990b64333ccd95dd9e46062e630197bd781a93cb6ce0395b2cc7b55578ce96428e6b9748ae8df34ee1250f81d9f399739db219321cfaddb29e6b73b7a8ed3e7165b86c867fc96cf8cf81e1a21c1b031a7110901d48bf7bc50e1fbc3b8bf578f4ee588c500649c3bcc1a37d909d8de4356bcd0a1f5cac48ef2cf6c2a32a6888f1a8c0dd59ac8547797c4db05f7c5383c44e12354a76d63bab39e2d1bb6d1d0464176d9ad82d06648a1fbe892d913790939fc035b4986e377fe1ff321a3af25810bf6c781327bbe19b82cc0559b0df2d4660c49c24019e27308a28ead222ad572d3d12b95c3226143b3c1f2febbcc353b1c3376416e9989856ab05c3c7153d138a154150a8d8dd481a4c0deaf5bb614176fdaa4230e09d29b9331a075250b1db3b3a2b2854106083420000208bd59c60819148c364fc72f920d4d5b5aa8f1417d8920846b991e202f296f7de4b7e49846c0159a67425914d963e7e9718731442e43d5b6e99089be5627a2155a832d5133ca19cecd7bdd046698fbbc5ce6921ad542a954af553a95c8654d9260327838b177c403d74dca8a1f0f5696c2e43bf755df7c86d824f337331b3cd6af8d5e4a25709505d718e47f5c2a4df6d8384afb9bb8597f86e11cb69d92f47081949727b9aab83b7bca8ce98ba7361ed7ee30be77969c1ecb387d487b1f772e155bbbdc024aa377ae178ac880f00929719799b9733a6de7a407d40d3b187d70bf77aa81e8d1eda437b2f1c5fa1f008d3830102e36bc5d0b2b5702d1acc15951492d8ea810102d383f1b56268d95a34b470268c6f05c6a702e34b81f191607c228cafd5531a55b504a2c6b1b45bb84a5085c94a6d85c70a6fa5b702649be48aef372a5598a804576a2b3c56782b40567a2c2b3573a5b6b2525359a9a5acd4566abe0e2c4bd4f8537fc01f6e051e61ddc633a6aa2570456ae136f216798e35a276a8025449523afd544b5440955525891a749b2eab906a03c42f21f5696c8ca82429a202bc339b2147fadda59edbe9a75a329e7ef8f4339d7ee4e977fa9d7e479ec688d045551555a24a608e6f91e7ce5de23915e7b1518e1bef16be2611e46197faa09242128328bdf5e4a722bfd82f099b2227eb9dd90c39719b1e1b5b10dfd8c8e001b7c3373632f860e322bd339b17822927c11e27c1e08d208d2089d2eb2b94d3e6f6537fc01f4e1f3172b3d5624d8217bd2b29549e317555cf1a7005962a91e7f81e79ee2cb59ceb4ad64e9ee2273fe0af6745e26b70b29bcc26e391bd90ebd3c48031a124568b13337264557f2a10f81b4f9b1f114e562c156b9fb88dee69704fc3a2d11343864761f45f10500f9b0a54f198ba3a396d6e2a8b9563ed12cfa156dcb6735a48422420290989b4967628e94abcd20eb1446e1a594a5bb8cbd07ddd316543559922099192908024a15e3b21ada51d4a3b4a3c91b492564c5a4da4955c512bae949e4e9bdbaaaebfa1f5b7aa5c3a36ad2c1e0b9654bb650554ad56fc05e65c5ba32a6654f1cf59893ca7daa3aa65a53a592e432aa0baae405508a804f853b158ab75fda9424025bf150a97a11ac115c5e9a703921653d7f5a70a0195fc60a36a665489259a51a51af1705cf1cf6919459e5bedd20ad8692e2b12527de9e11b598affc547ae78885feb10505d57a02a045402fcad4340755d81aa105009f0b70efdd475fda9424025bf75e8a7aeeb4f15022af99dac2e43173dfdd475fda9424025bf7024d22afe392d37f34a808794ae9695cc72128fea177e96d78b5e98bd392a3cf76f3d83e764afa8ecdf5a86fed1391409fd91b81d11fc5a1f12f4373978a379f8d7efdce36fb40fb987fe3bebf819b94b8dda20d15c9dc786b85bec1cd6fe1df338a49fe9658f6fedbd39e7dcf5c98742872922151725f765429173d96112362d344142aa0367da6298bd0824a5c88554baa369149231189e83d99adb009fc6a6894dd066b55172d15be4e753718ec765b70873ce24fd6e9b23bee6c298addd2296c3dae710214144fab5a93dcd6db1cedf28c7252609248da68773cc4bfbaed3f4f45b048982d26a9d9ce7dc2dc21c1e9e163bc7b55bc47264760caa03ba437190501eaa04babe6a2f162246b1272f1b4d0d19d87d2e1e0be20370b70c8f09f1b2bb63782ce86dee0e61786cc52f9d739e31dfcbee76019e501d501ce88e84ea80f25025d0f5c5e25513511ecadb28ef04e58d50decb4653433e696ac03a12bf4e221d0a0a46c1a42dc2a4303ec0f45a2eb46aad5beb85aa85c54c31b570a8ae345a81f1012685e9b55c68d5f08d4d0bad174d5ab73b430109698d0ef4c6a661ca206304638a9f0a1854d854bc50715361830a9c0a9d8a1a8fde1d862469f21d1e7ec1f0b39d246fbbb31da08a172a6e36fccd8e12ffc2373b2b7ea9d0a9a8c1337777dd7ba261c0a5eff05c43b66979ecf6343b4976802b56ec2871ba321a03172e56dc5ec692af10698961b9b609b0ede23c2ed4452628c3c45573e170ed5c3b5c3cd70faed4e57301f1e8dd6148922ad72f2623b4a11c0643f2b2bb5d38ccc8dbdc3daae170afac4bb60a65645c3d57cd55d3e172b95c2e97cbe572b95ca31399d60a5d900e0683cdccd0d09cb7f3c5893b819cbd1388d3771271fe4ee09964e55c49bb54ee94f3c5793b71270e9fb895137702397b2710a7ef24e2fc9d494ee049c265ca8c0945cb9e49893a34ab2555122153645a2c1dab066bc73ac252594958409612acd5a5e6c28254b9d8c290244d736f140dfba585b857777ad0fbeef07c3c2db26ab074ac9d8ab5636d9a49aa9cac1deb084b65256101594ab05617162e35171bcc08254b0291c47e415b75d26240e6e9a4c57a904a2db6eecb6ed139da39eb1362f609261572b7fb04176e234ce12cd894d2c55314b92b2d9b25c54f09407e7a74045049294977056df3b6b3c28e3c0d8bd0455b2c4c150269adcc8422d72aa53cf1c43db5b28c995ed307b3673231832c2c586a2c31b0d858702c343c7a771892244bcfee8601c258883b62e46b84801755b2c282176d72d19a6da73e684590fb4abd206930d5aa3b1f84ccf4ce6246cc1ebe89ddf8994b82cc1e90d9eb61f674983d5dcfd3d89898c13b8bd1606131a3d7ee2c26e3d118709dc5c642030b6eb3f4c08c20b30e05958899bbaccd1dbe69914288c9ec149e6be556ca57b2286b250ca5ad9ca1bc953de5cfa37787a1aa2c753ca0917ec370a6a75f0e3b62063773f2191b7ecdf0ccd46041d8106c8529e130e0d324e91766e461422398fa342d90295f79d25c29272d3664c782fa0dcf944f3c53be9245592b61286de50ce5adec297fd05206884aa5a974b753fa2447ccc71c1bf235363391d1bb51430c1832d880c82738987aaec005e1fa155378eed4bf4ed3b533bb68305508393383c258122163b3d9b837692525410a966a9474251ca55d89c7bf4a3c5ceadd1290922faf768ade3636e46b7e4f5344bda81027d27d4fd3434a42485fef2c16f42829886f6232fe15ebb93a8d145405633afadd61500ce262b560a9460dfdce62311ee511e3e19b189026bd06098fde6d83a94860457c8d2a24de8c27dc656f1787c1945c740de2d77579934e4d1f9d30a08b08326f1aac3c82ccfb84a49254abb63869e61b140470636214b928869bf865636208300ec0249e2b7ab231665281d4f5995d3a0b6fed8b5a9be22c5fa3ea76a7002285cf0023e6345d3c31623ed355dc3e8dcb558a98c3ba8ae78c989bf003e8879e143b52f0643f807ee8e977a7e0a5d8a1c3d3d1a163039d1d3a3de8dc4007073a3c7472e8d040a7063a3ce8ec746ee8cc4007870e0f1d1e7ef8f8f1a3dfad93eaf0a0c303003a36e8e07478e8f074763f723f0e3062feea327a04755ee8dc623270c17eb7ce4de745b7f5177a0a3a0cdd4557415f418fa1f37414f416dd855eeb2df41374163d86eec2f780f13d5cd0efeeb7ee428f417662c909608fa1db7aed532552200270901840efc491132ada6930b5d7e0203100f5c4119c1fce109c1fe010c1298203049c217088c0118213048e0f7084c0f1e104e1f40007081c227084c011c231a2df8da3e2088143440c87070e0f87089c1f8e0fe7071c14630acfc9d8ad673d62bca7c1c1f138bb1bd3f1afbb55578f0d890501f5dba3dfbdd3c5703872f4d88d1d3af4188defe9b1181f93e161f4bb676c329ac394f03577b7384cc52b2c086bf23477bff0991a6c897ef709a8a0de8a1ebe49f1c3af4b9bf5c0dde0fadd38bb3bdbe15d1c1c3fdbd9f1688ae762604bfe756148fe75f7ce8e9fc18e3c7a673075651a514e2c96164b8a272994cee24c37a331cbe1696635663acc6cc860b6c39dcd98dd30c361967b9a3bebd17c2663e633fb59f1d90ee3cb36bd6c94d3996be121083b0b0f01d9ed66f19773969be598d5a0df3ddbe562760bcfbdeccc73b6302766b366b97fddcd32dbe15f778b6232f4bb6141980a03c26a421f6f16036fc45d331b96edf07ba6c32cb39e74364317c04c05154c28f4d6615570e9a41012b5599b0653595c7cc5ffa227fbe48f5fa9adf96f2e8eb88a1d8e9ffd5ff269ec09db1ac5521cc5b90a620246cccfbec288f9ee6289e762ce14510123e62f2e978ef91a1477e9342615483dbb7491866666660666b3d579012303c673a7f09c6b93ce18fed875923b6778c2093422dd80f18413685604e1adf0ade8cda0f41e008111f3544fe1399916614e6bb7b60bb7ed1c9915bc8b66263f5bb1a3866f54ecf00bc7cf56f8b022ddf1331534a8c05dfdc3dfecf0d8e1ed68207eb613862469ea9aaee95affa09efec57a2a0b8f09e131df4583a42c8c766a6bce820c8721812d61f14b2cb1c4123c2d768e2a0739e79208c6ff4bb1216611fecbf545b3499b4626513489a6d16432654e92999b287f6242452251d6b91c738a8acaca8a5d491953a69531c564a698a659a6504cf384344d13659e4ca6699674f9e671881209bf177dfb38b0bcc71985d622d168148624520d8cf11645711c4d18e31316618cf188370a6f144642e24d9a5098c1262727229108052535a29872ce5a8435c61ae3aba26255b4de755c96292356c1ae52aaed384414499cb1c61a6bac31c65a63adc53c662d128db4168d3aa5c37b348eca9234d5300c47dd6a656e20096ff175e4688bf78e5a6bad75f8eb78639cd2e11209a9353691a612a506a4cefb44629c31499ee08c31ce592412894438637cde7df9b374107f73fb82a485fdce5a77a34029afdfcc83b0c744e0eb768f46a49d160219c1b34046dce87693aea0048aa8d11f08c950a791fd8bf8e90f8444a8d3ccfe4886f493fe40487a19d8c16f074276e063073b9ea603367c03693713f22ffaa3c6ae3bc0478d5a905e901e803c15208247440e226610f1826e77d8015f105faf3f908fe7a381af4610a0a05eb77b0ce205c9a0db957e7d1ce871034a8fd01fa847adff0efb03a549baa83f50eaeb9608dc04e4dda28b71680625ee0ecf0842ce41c8e61c821079a0453c30e540a47570ca3112f90873e841f340ca4239e840e650430e3066bc1073e00967f08c3f489842209846d4d07844b8a2518456ed2669f868f8e879861f248d1cb99cc1098d19399430f5732b730a8a1152c61f3456b092aed0b095846029eda025ab74c1bcf2c161a503ea8af0744172cfee21d38ed08554c24b8e1f328cd8ba19ba1a2c4e0783e43925cf50c7736be586c2989c90c9e7b96d811467901949be71886b24f9524540c7d1c748f6ff91cb11e666e4ecceb9c8b53055d61007551462c320137cfc88c1929ceef206b09de843ea7ee050439fc16e1c5c8cfaffc08167e8c70dc09f4ff7e386f4c70dbc1ece409a7e4ea8b15bd7edb96ac1103086b8f5071aa206042574dd1f0808c16ecf0688aea118e40305c1fd8182e8fa0305c1f5db1f28480b7aa93f5010186ec075da68f70a43e8d0fb030d31a3932900f980627f205f91106f06e073fcfc0292bcb33c9af68617fd7b73944cc69ef76a24b3486b91de48a05c889077cf68d9c769a67c3216314779202d1a84df625c8e1790b4fcfdfea891d680d4f931c6196392111263bc35296b7ebbde404a7f537680cdfcd4dfd48f10f12be1853e461036e7c78f818f838ff54e880e4218d1389cff451a8f447cf711cff64b247dc32352f8223cca91bf69ba168eb0e8492e885ae71c84ce3908fdff347edffd380731e7c78f310a58c8638c0216f218e39edf57e31d44bbedee4d8cb6ddbaf626c26db77d6ceb96c5a31bb7f0b35bbbe86da21bea5182f35d5ea789dd6ed2db2d8ef8eeda926ef41bdac21a1ff95884610b3b253db74bda85d7a55f1bbe1915b909e194f48c86fa1d35f1d169da054fd72ecaa36db989c5d12fc09097d0efc67f8e7ee3bc7388826d7b408eab7ef78ffdeed535f1af3137193710f96f44db3e8ad7fbf55992244ee1be1e05bdbfc4fbbe5628fc9e417fdffc3bf39ce20c3ff541ce38bc9bc4afdf3dee36414eb8f33d777844396f349653e8272edfb9007ee111c77b1381fc9c70db70c0688b4820327db450a4ca67aeb44dab0d1fd860c70da7700293c0c62f31b2825f2d4774a2f8d50389451e5f0e820dbd45ad335940d2de77025fce81dfb9b3996a057b6fc7fb9eda04f8e61c44b12482b218e36b6168d13e88f088fe9b047c13e17e55e3740d77c6dadfc2e2da3f5af1780c26d9d602ae66cb46fe46d7f02bab6fe284e3d122437ea67717ddf1a89099e65df4871408df452fca0989daaf7fb1122965b7d8392e7ad35c5db57f5f8b538a3128be79d1577401913ac8ce8626155cfac9fe135714e372fa9d889c54d40bd40d45030a87cafd0bd5835fa8dd455138562a5ca2782b825c1d9648272277160b52f14d4c464f3da9374eea4945bd40dd5034a070a81caa078503b543f1fadd237d92a24232a1c8b9443b2123377e3d74a03143062e2643ec859d2db96289634d4fe14b01441892e44a15aa56a158abdd99e6419d7066d5a280a4697a82922a553097e1a933b7653a1911d94f7644a6ca96c880b2a08c890a0035153b15381ebd3bb5c3cd5ad17b415a2dd3c974329d4c27d3a9643a1911d94f7644a6ca96c8803226b220006afdee90a744969165c43ca6a7f0dc19b35b842e1c066c11cb719dfa876f6674c19e15f79bb9a9e4c4bc40d2c2de32c29caed3053cd9a84b54a94c287227e69cef544c48ec172bb2d11cce0210fdee58d0d7f44e3c08508c54c70d1a326c35c115860c2e5ea8d520e15f3543bd068947ef86a92a4ab803e3bf09d4cdd6f0ebb6ee4b35b7ce1bebfe258c7b288930446bc79c8768ad75369d6670c218f300c502838837c63afc92595afbdc3e4ada2f0acf1357f3db4998c746de2d760e0bbb448e0d6c7b0e475d7fb81fefac45e10e47278955ec8d8c7ddd7dbf46b4433e7af15fa22d6345a97f89b6c8025dafc0bbdea2c8bec5a791d681f82d5249255ca224a608e143d2afa853e0a2a21de2589a568925614892b6c68464346d55554352a9440af745c5d5641a4f23a9248ea35822997c6c943dbd80b48fb239b456abd56a2cac90add1ed0f0d2f601095dc7df7dda8552f7a42e404258598acbfc270861b432f89a3c9348ab5d2ae91762ddc281ca0a82d504fc810df90f895535488b0f648911b2ae8e6268b4aeaf66facfa2f247f433af775a1d3ec124ad82561786edf0aea84328d2811552285a89108a55128140a9531eada009c90a9e89a396bb709bafe2e92776653d86c6d4bbfbbbcf6714c5b73bb0844fece1b9f2b1edf47dcc561fee5d1bb4ff6ee9a7f599cd5d7dc7d5723209db57e7e32020ea43501b617cf20ede6c1b5145c7b1379df941c69714d06a2ca0de168edbf45fa77c657e506d26efbffffaf5a21ec777fa05fdf68f7ffffffffffffffffffffffffffffffffffffffffb24d26936994c9d80767d107e7920fce3e38fbe0ec83b30fce3e3c954aa5522e1f9c4b3e38fbe0ec83b30fce3e38fb9ca6699aae8c7d7026f9e0ec83b30fce3e38fbc0b0582c96cbcbcbde9c9f301906065f0b83fae0ec83b30fce3e38fbb438e7270c4cab151323e3ca2e17bed68566ec83b30fce3e38fbc4a03e38fbe0ec2323e342d1d7cb041360337966065f3b8366ec83b38f0bf5415fa318f4fffffffffffffffffffffffffffffffffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3fff3f3f3f3f3fff3f3f3f3fff3f3f3fff3f383b18ff8ff6fdf06f15b16672dc25a340a499944c2d7924421a9248e791cf1b5e35a1a4d27149949125f4b9ec813945499cb125f5baec1a126c155f3e8da9aaad9fadd293c2efadd2ab61518fd6ef3c6925958f0b52c355e8e1e783b1fe90e03b11056828158c54378c5ea0d07a306dc6d46cfeda277f7faddd7c70dd2efc6bd4ccbd927ca2211be5694d38bde40d2f20d4f2ea2288aa5daedb9babbbbbc9bdede9af67c3f15ef6ad757abd56ab5fb53816b90b4dbed76bb5dad56abd56a2f2727272731e51abcb56bbba53ddf4f4dd3344dd3dbed76bbddb0babb3dbbdd6eb7d3ed78e9ed85384dd334bddd6eb7db6eb7dbed763be506322695da9ef5d99f6a81699aa6e9ed76bbdd6e585d55555583b7766df77671a3344dd334bddd6eb7db0dab2a56b94aa55ab1582e2e2f2f1bc5de425c8fee02699aa6699ade6eb7dbcdf67cf6a702d3344dd3f476bbdd6e38df5e36e7e70903d38ac93131f8da98137ca37b80bfe2605ed775bda5a6c5d91eabb33bcb4bd334cde999539813a6d58a89919171a11945f1b528190b532053b3b75e0045f736cd350492abb96bd696d7d3e7d379cd79c5796db546ab7b74fd6e9919323af4bb5dba8b5ace42464d2aa039fadd2f930ab6eb9d09d90413f0b5268427d50a2412747c6a54378187bcbede835ff846f7fe379eb4b074d248d3f4f9748ff6d174cca9ffcf204dd77767291ac8fdca3445b724c911cea296d538ef3c023b144446622fcefa57a629ba25498e44235c228525bc0221e1688b56c818af400849e402a9a4c5d1f42bd31cc5d32d49522ca146fdf7681461aef97d9a7cda9c45329fe8cff8febf2589a517a2f8d95a6badb5566babadb5d65a6badb5b6248a56dbd174b2daa2acb6d65a6bb515599454aa2cadb6226badb6a5b5565b91b57a8bda8ab44561b51559cb62378bb5d66eabc36a6bb5b5da5acb72b1dacac06a6badcd7777c0daba3d47914d098a1735323960b44567095410b039a26d7344a76dcc196d5bcee111ed16a10372cee86cc1029523da3747444d006f8c39edea641a0e7bc41a3dd6d6b8d97d77d8de7d5f2a32448b7bf67eb7d662bc51829034b38b1d6f9cb3ed9cbdba05d01b6ff3ade8beeceabeb099046937cd1c95525e188a763520d9ffbdfbe39328870474bbcb21c8fb765f6cedbeed10ff667b712e8990b414743a1a9544c890442a954471244f27148a8ca1a0a0a0a452652ec3949c12b64ab234cb5549246fd1debf38b4a33eb3b8b23b268231d952eac8ab92c86a65d2211d41b2582e2e2f2f2614019a79a804cbdad169e9d00800004000e3170000200c0886236196a641107bc80714000e537a525852582c10c882d11cc56110c46010638c21c018620c21862154732700868559b5b3201a75f7b59a2d0e44b4b25c1ac971e0cb5fd40f41f1e6d2a81d7dce2389573d4a8dc1013143e345a907d358014a4cb8e48de22c6e26d827e66c4fa65b7de2eb801824ea3792734cf0c2986a495bb2f50caa4062422ac21a8c28ebde7108931ae877cad883705c03a39b78b62f91cb38f29f6e037171efb2bda099b81390f5b10ff10ccd9d994076eddb8d90b55b061306e93d0069a4cddabb96feba2d895fdd45c699f8def61f6b2f9a7931a626b208db588084c62c68278534521d5bcab59c7cc45c8c9733a2d2f69c9bdf2a3b85274cb61fb22355e4ae62b614b5cebbb7492b6ef9764bc12f65ffd022a150b23cac4e708a4e046cb470e4f9ac01ee508e60278c2efe849cec74ba5414e6be0eea99ec27643bbad067803ea6675c0b60fbf4fbd29885c1d8a4ee5ec39b27dcef696b614dd166a4cf42d9a53989035d8d02523938e4fc81f26db64a9631b0034f4b50a028351ecbb387783a040388343c3f83fa83c9a179f567f86afeaf745212735dcd8b3c5bf1c0c0410d451ef3958620054e393d32955cb3963b735ebda8a30077c34daecbd368ba6a1f2b8e11875ed30cee1d41da278b5b0fd9c617b8fbd822fd58ecae453ac35fa27c1dc2ff6eddaa596bdee3580e061bd2e9ce7c0f8dacbd0bc52728b0a274e6eb4e11d2795f732ec0549ce849fb8b897581ddaa9a8c0f4d9a4758f9da62524efdd88acc07bc46a43ac59d362be40501d93de404285e5ef3861c4336771206ba50369b9c2be49d5a3649a11d35ec0411bd8e395943c10e9604d189c0432d3bfbc55cac0b509ca7b38a0b30ef400ccf397be620f90862bae4ec290c291bd6d1bbfe3ebf3889a2eeed342c8e1b454515556dc3c04c57ee004975bd6dc753eb48d9079c4c962b80622102605ff2f4acdbd30c3d3889e8561f891709d456f1a270ec7a481035b5a2d8bb35cc1014394cfba0d60618e84e34e85bf4ca50d034be5209dc4493a7449ada558f2718f2f1cedabac4fc9a1a18e5b5ed633395043b5d8dd42498a61563b1c27c2a5728e639689b15ab0b02a4de641e433c532630015c5bba366f289718b4997394054d45cc71aa031d5d13d21ad4904613228f706f93287dbb036e031411dfff4b28baf9a25df6b75a31eb1fa2deffd289dafc602b6c11b05635fadb48d4178a89d83dc841fc390db698c8a49b398c548bb3b45d3f52d80ed6de3977f31d70d0f03a53d4b89dc00c2e25792340521dd7c4e922079f987cb81786acbaa101037e6ad7bf78091e26f06ed604739be72e53d94afba808269e63447580466f1044058dd71fd2fd98c5983a6bca2654d6c4d2e0269f0cb442b1a3a87b12b715892912b00326997110a8e82045e701d9366bb34e35af6b0d2fdadc51a61f0c360c26a177d995a6aa8919ed418d00f8d51ce50fe8f206c61627f4c5389759c9e2aa830c812520d72b110bd5f2a7a3d556790b665d6d3d0ab877033e29fd88893bfa41d1fb45554894147a212fde40b8c8724ae1845abb087906dfb59381b5065f063e329ab4eeb8c445f0c0248bcc36b624c4d86b10b6dc8e50a3671f13ab0210e9bf700128c48462b76639db90a0c0c193e3da255bb4e3904ce3357816bd68ad6317310880ae6e239e3a8d037a1c138b506882004f9e2f89c22f21b39d545860a0b59d71f0b19e5d23dd6c8941d99974a3340cfca92ad12e9fa263da7183d91b1c053cc6103b745922c4626e1a424bd67b0ffbf7dbee8b102745df909842b76eaddd41fa981ae96a3e73d5d44857d399abe699abe68d7435cd5c3599b96b9eb96a5673d73473a979e6ae79e65af3cc5553cdbda699abe6997bcd33574d33f7bac936cca0da7c4e308a487bb238e435d9a71503fb938648f640bb7d069dcbe6e790116ee8d455937b6f113764aa1593abf7f87a8b65dc0b71a3fd3674bce499e86e5e641fc1574c2ebd47dcd029574deedee3eb2db64139602b33f43f0d91ae81f6fb087ec5e4ee2d820d99b26a72e92d5e8f720fafc3ff6eeee24f73af77c1a5df864eb16272f716c1864e5d35b9f61eef77b10e6bdb513c2f697228ef72ad84d9fb1de20fca6c1fc1564deebdc5eb7d72943143e922dc859cded5369f618abd699b127dbaaa0782bbb66eed5d3ca6d14f43a46bb114a8ed7a5beccaa54a792af44dbabb14d939fa89ed537962e0d6b4c6db489bbe4d6bb86fdafc69dafcd1f4c67bd3e6afa6cf9fa699bf4dafbf4d3b7f9b367f9a7efe346dfe367dfd6ddafc69faf9d3b4f9d3b4f3a7693fb04fd57d1613bdd51cd1187559546fcefde63251fdfa234a53a38bba69adbfaa6b2d2e7aab39a2b1ff929a70a77f5d2eaa5f7f446934facac4bbc7aa577f8ad252f4cad4b8bfce1075e32d7d33ed665d1dd40d9026c6143dce6aacd034ef43cd19b8f812988d69f8d3395e7fa853d99318350109c9bb3ec07f72003ad0426f51e62ec1d2fc239ff8954185c193ec1d6b6daab09fa32c2cf9c5ad1ab6ba955b41043774ff82f88d5311c2cdda9f03f76202a5a2d09eac395ffef15506c067ad83a63af0a81749dbf8c5ad182cbab508eaf38bb4bbfa7192d3cabd543ecb4db4baa0453e53ad4e4d8e69775e698bed83aa60452c5996a74fb74bf3fea6dc7b16d1cd4219300344f11f4173350324c49bf7abd7a7cf489fc5c625ecc40e0c2813c76489702b31376cff1e835984262b1e463113d7527b4181bfa9c410d37c5e331135c95815fb7c10a73828afa628c2ab7b032586c39dcfa02db57eb44a211891d684038ca64dad65d97a49ee85d1f3aad48d5e89bb81a1d5050a13fbdf251c96cbc973cfaf566a96fd5b6bb3c75127dee05fa1e9ef62290cd08a3b006c73efedc5a83d869d01ba0c0ba620b1415c0ce860e60e8617c7a84076bc7b9b2d71e06a74119c7edbe4dd66f3e6ef206c9d63be1ae6ddfc6b7dad676a576cd4bea7f8d71fd5845767b5ac51416b831f7c4559d44203f75da31679e5a17cad43e09033d9daf9809509ab439704e9c4b53c2dc82d731900d1b8c63a0ba0037771019b29f804513fb29837239eac4f0f4cf50e4a1a5d0d5d04c72f9b79b3f12f0b298a7cbee4dbaeaa6c94e9dbdb506e3b3ab59d45c04ddedc28de93a382d31e6ae9236fb941bf9cf2306cb25f7f3993efbaba8ce0974560efc1b222f1e8fbadfa262100ffa2aca0d86f389bfc144e64ca832462a58488ba2510445f50b9f6432901f1e016f71faaf43f4e6143172be90c79b3c8c08031853dd9e43830c75aeaac026de6cd266fe06290e88a3323756f8d6d3e60967b12b2991c3d0959aed4638b72a0225c54b305149fdf07efd5d8c54f2148a45ff042b22c3791b17810f1b8c459cba32b8c7a69cc0dc8f861d6592109cabc327b3950076ae87faa1b73cb1f2f40032ca32e5a4b33e366962c20dec274881ce2b5c7e69828b728e540c5de674d18ba185d00b75f36dcdb0e96c51a558164a3910411b622ec0008cd66cb4ade5d616695e61e46baac82e13775752b6291cbbfa1b674a34892954be63695bbff5eacf69b9e280451f8c077496122217d23fe9d71b8d0d1debf6101dd26f72adfd67886b1c686326b04359b3839127eb38384b96a4f8945c1faf6e5ef7e22454191ce4e5928ef10882b258f447266ca4379b9f772b8cfde590bb061bd463533a3c5690df4346c2de4d922055a8c8fdd3f7ec3b9962960b41ff225286a5836ea9a16aecda4190ee0650d8211d4ef2134a537c1dafae22689c8906b1456e1dcca82940b0b1b442334373dfbf63a7d7933ec3890df109d7028a43325110ec01bd539edb6175ef3a5fd287fdad1b712ea890c71657f79b2f430b7cd6a025f83bff43cd7aad8cc98d4ab179449036668693edf7032f987d8a1f05e3f4e27c005463445c495b9e1c20f63947ac7cf1961dfa4a9dc1eff4f7055784b033e9087d1bf241083c2e6dbc68d4e1b8fb6e423f63a5574281a24f344db71bb188d9b1f39f1c23aca33c31b5215f259884dbf98265115eb8721a3c1af285a460ffbb74df4c6e419b7ee9924e587b0df0803c7428c4b22ed34f4d0bd4781a1e3ca0fa66d91e9b5c523bc49133c8d58bd96c0f98a13ca28077bf92a52249d8f495729d6995d4e89605ed9d96aa360d07d92190a323a2647c7c126dc706b9bfda08c4a5a7878bf8e587e07a70fd3e6e19e5ff41b01c8866e75b32cf5b353fb682b8ec77080d5548e7d8c58b733564aef1c08961d3a32eb85b8904733079878434404e404d72ec1c0ccdbf60d679ebda8fbfb2cd46d7d0d6ce89c9fe3fae21f2c0d182e277984bffadaeffb1e6ec6f0e44aecafde5a57ecae29af8eb04eedf00faa7b1d078929499b425dcc17f67da3e3a431b5e07d4eea56e4163195e5e7647d48515a6922afd1c32d27c731372e7d27be09827c94e53561c3af098ee756c692cdfec98efba55922c29fa97ead647156dacec53c4b0d462fef5322e3475b3f1ffe54123b410b455ef37b712c180c3b83ccf9b3a741d7a32511c8627912d377634e9544525579fef03c3d72539fbc6fcb2ff7314c593c8eb013f69b0a14db4574d44c14d9d456b4b27f20ad6b8b627b958cd35d8a712242fd90913ab4880686d0a53e46d1570c74432b6462f48d53ca8429dd140860c4036abb7c4a35dce56b50bc826a0355f4b705aef252ac5070ba8694845080cd6b83cd8da57128ba6f06cda7301a8dd32f31af59990cb9a98d2e11d95ae2ec5f57471905a5b2334df6412e88ddb836d4df72182bc77e2fbe8514e883645feca0a7f134eed25a0d79e7697fb531b961c78750f374df0375d97f72e067180b11fb707f2f8603e31fe9b8bb917dc95331bc34a69f35b32463da4a33ff67a2ae5e2fef45c0589dceb960be78591b83ab42fa12814de3413a090854c1b2364c2aa1bde3db81d7dce816d4814e316af5920150fb1e3f6511812bb1195a73e29dc5e736dc0ce40e42683d78346f1abba6a4e274faba988bdfa8d90c2b2d968b530c4a2aec94c42e65db0cab15e0ee472eb05fdae3e7fd2680c1ea650938dd0ce22a4354a8bf9b4178921adb0ae77db81ff0eb5c05a76ac5ccc01269e60a340272a1744b5279d968ed33c7b1258525cf0a4579fd0cf274289348c9f81ad9b7e5c05926a22da9d7949939614bbf4cf469cc84a0a44d5c1c0e9f804ecb4d5aeb0ab989cd603af19dccb9ad5dcd0efad7842b97a1c6aefef1966230f9ce749e4fe47cd3a3dd3b1b1ae8791cb8ef6e182634bb703f49d66e3c72370d8497a08473f45a64bb01aab1f3fdc7ce0a2abca42ea52d5366f52a7424707c8c578be6067059951c75d2061057de42764b3b613a9da2a7dea46bf4354b23c42090b1519bac75d7709f047d93b6261808848858646fc2cd3a5c85a4af743fc343fb9d724d18699704b77059b18c69e5a7d6c47ed6c4270fd90241738233ae5873326c7e59e4782f4fc325313f7eff0c3794e12d4c07fb257d95c796806b9b90aebc654bc481f78544257d277612f63c51b31ec6e238bd1a57b421388c4da68b83413d4a5544c500726b7fb9333db8d2d41c2eaf324ba57da854406ce1eaaea9c6a46af1348b5d109d64fb0c99d5e99ed822295bae7ec54643bca3885bc71b877593f443315d0d3f253c72f76ffe0ddcf7a264db0b8a962210c81672b23a980a1e1b47768f5e4589da63ace047f251ba9243c9d1c0004b11536e6924e48b447c5841d75fc8b82ca4b93e3de03866ec444d110dbd6f81e275aac4551c50d9c8662c0e2a9d3e15a933460e81b9e61f43b107c44b17045dd7588baca67fb9f8c38caee4b3b6496d37a086e00f31e23d2e7abaff81baec3d39f03fc628af75c3074cc5e15dfe48e7c902e10257fd4b03138eb9c0bcd4eb2a7c24455a87529bc65e6e88220ede57e5aa9a727946940aa7900bc8aa18c8da498b80f61085d70ade2735bfff2f63a9e4c4a2b7d5d3410ef46080f25f9c373eacadeb4dd5460e806578851e16592de3a519aa3bf69333d5933582b0941e15b94943b08a32a40575b2b690196a95a49c8a68620476f301dbaec19f973ef8d137c7fbba47e15d4964dc9f9045f467e24d1d9a57c113b37af65f2b2eed87221e4089ee47398b974f3210bbab20b7bcccc6698d52f4f1191900d26309cf4f353cd6ca81b77e0117cbb0643556461523422320b5932895ae37ea12d6a318a83faff35e37511f2d71a6d07163e1d0c9ca9c5d1834b649ad9c18911ad178b12f37e3de4661dbfe23e3476419bf586970e6a648ac73517a9a046c4894f7b9f6c038e16f377bc5ca896370710ff2e3236bd16fb682173e1115789aa11feee9083ca686c159ad65960f3ed386b8e6bfb48935249d7d47f7a526472006b9194d3c3a1d825778f4ad2f28fcec60c9f02de27858b85199e9b589dbfb0802ee1439b4640b11018a17b276b1eae603a2de9108513b1f33b31e4245d83c8216221ec06cf0d249cb77427970077fce5f41998c2c76142789668c3fa8dff0ccd9b30a66754a6670a85c44d00252e1dd051dcd66824ca07e00fc816bfbf5bf34484da47ae3e3d26f07bc38019f6f11ecd87c0b76e6bde1c29c76a239ba29ea966aa75bf38170fb71802fb3b630ae9d35bb9da331935f9faef05f542693662e6501df97a2e1c7a1b4c876ab61df945ba1e1bcca4f0579e8e0c0611ab8a49e4e05924461bc3d8c2be8459aa78fcfd2f47777861ab8fd31a03c1879fd5e89f6288b4384766287fca865c7f74358b25b0643f38cb0732d6b7cec7bfd1fb1586f70fc59f0e990cf49bf02fd38ebcde5475492b400e061029f71563ce70bf260033010c1ffa486a61f6c84bb33e95d675b566d4e608942a397e9a9da4b90d9eee0930832c173fca5877bb9c6a0b71d93202cf4b8c8323d091d6d704797f22a4df2d0983a64bd54c121dc7fec92c96140e969c405e5f48307c6b9a768ad1104c240827c2d0f03e82b25da40073a1aeecb4802105afc3dd91c146119573fc5bf685269ec9450eaf947fb1a39b61abebfff010ee9f6fd4df0da743903fc4ffaf1d8e8ac22d678e827bf2a45f98bd1379d5cbce2c939e38145084014ee003b2b04debec5e0fefe292d8f85d8794a619f62e86ac067727fffa6dcccaae74e090e5694bf40eb465a64820c218f8cdca0b9682cefea2d9599ad555d1602376b6dc789e7aac7febac6b463f66d383200ad65003cbd4d732116e023a357f1d9da50c536ac18edea40f3de72ce860e040458433a00677ada40e5c17502434b41ce00b85fdabd0109b530b09b83f3f2056916cbf3266528564a9d9a837c8a3ac6674a2a1582fd6be0e9af76bc29e6619192a6f556141c1353deef31d28a14755057b0d68a4dfd3bae33407a6de075c4eaad41090025ecd8432a81632c0211c02105bd3a15b05cd1054f64f5e70f08fa6db25dcc507ae28962f39d60a2759b1d7e978927da37b5195e7cc08738c9b47160c86ce564a1b31ceff2a43d9a970175cec2a03b2bb3446a44f01f58c1079fe5d4cb93b1b114e746e7db526a1bd513694c9ed9486d7b20339798267113e47a96e593b550a436f46a4ac181cf83e7e4d2ce04b2a1f5856aa2f14e9cdd3e7c88a5a6eea4a80405da3c326eefc84da76a68d35c97ff7efef4bd40ae4a9f8a08cd597b09ee72782cc4c0f02438ebe2c10b2232c1cd84bc69388e821f3916e0b82ec64fa43e95c38d9d849884290eedecbae98afd4d2e1534fa5757166c649c3180ca940898d249076a621a7da50e7aa9c41d1aa2a7aa556241a5f725d119ddb85039789100aa9b2e98f3781a0e3909a00a2ba4aa49a3ae8d84a8801e06cd4e68bd4aca2f68bd4abfce750f71c9b199ccb732c260a3b62ad478d6e37d799d5d472166d2521d8a6361747b54abffe6bfe69979a9bbe69795425121b2d0289e10478ce74c903cca37f0debc36c26957665d15ca86077980d41b866bccc523ee1fac751372a870b08eb8a9757e5462435fa55732ef0d9dd644691ea57e8a41a933e710febb713750ff32265610ccadc74ab98448bcda8755febc61b203fb40347015cc3136e1e664c2d7f10592dc11f4444281e2ed905888451963c5e9287d6f1ab64630ad8cace6587c6f45c4370344099445e8c69d6f7a267d6991e6726cd27c3b7e5e7e640e20b9987f507c8a3a426dc6de055b49c453ccece23b640afa292ec4a20e846be5c9d3b1e1287329f1b3e7b10ee5679d1d82d1c1381e40871932ed278c003b17e050ef3529292753cc9d697b842fa17b2da9250a4ae29c6ac78e3edd44fd0a5c9ccf32601b063e8f654451953871b653f960f5ce44a0fbddd6e31433ddebfb336bfc657f036325f6c8b0f3aec5472fc2eea38f8c15d0b2bf0082338b2541e2661acd24415e60dc7866264783d12667ee6e3003f121815351aa7d01536c686e1ef2b5e72dc728d53544a6b6c2dd04865927487aee48d6eb75b4c3d183d23a2574aa156a762a9988cf9e60cd128c3b9455a327885bf37d08375b02d412f0b1fa1c7acaaa8c6bc5764b51915fd8bd22a189a3ddd08037c4f7db0a808d360168e40dc04cc906a19131dd1ac290be16ff0a0756f579dabca5e45ac5184eb34589777ae8d3d2411818e461c498f18e2cb890c60de3f0449b5e0684789c17f8dccb07cb02e341b92bb3d1dde5163cf54430d29081667acbb3c23995e0398797c35fbce94b822a3a532f5c03b94827a8e392cba44a5e8209fe3db4102d632b5ae638bbdb3e70679725f8dd85be0dbddd2b3a7a74e3d4907dc39bb804717422f5a2c874012df1aee8d50bd2936e540c459ad2443a998679df1447a29b44d710f4933c985d1e3adc7344b0d91e2239a58c77a6097ca772ce4a22000856305850062100e2f060dc1d0b127da8a8dca76f37588e86f260a3af0f6003b5f77189ebb081609a9453c44b5501ddef3f2100920b1691164b1d8fc8d502144b481d67a102dca94f4446e2cc50ba0eaad0ea23078263882d2c8363f4c3c448bcacbe73c3a230a00b0f8548344bfcf5e14bcd7f71b6af63c55f27b94beca35d257a829fb0fca35649343c30a0a849b7aefcb98dbc12648dbde94775e220bb525adb826f329018eb4fc660f7848ae4e27ad942b64007c16f038f7e28dc83f4cf8c50d1fe3ca34bf0dcc2c49b1d25fb03e02b9b3e7ae4a4d8ec726ddb36cfdfd68b29a66bca28acc0d70d96d97d053a56d86dfcb3123bf130d308f1fbaf73f69da43931bb1edd0f40f23f05c101d8efdd001bcb2480950bbed259cf2f974ae17ab0995cfeaee0b3c49dbfbfb87b343960949652e0e9ba2b454de766f51d28fafb8263e99d7dd1ea95e598ad32ea1cbe68ea7b2961d9754029fc5ede02b35455984736a47c19db3911617236c4a267d0f6e9cd6dcbcc6cbd9ca708fdcd27328dfbe45ed359f211e28001611271e88f80ed4e442db39a759b7e2b412940f8b1bdef46ea2f746029e9e9b30c4a88b9e861be4dba467605a14a0964752451d6430f29f01af804dd7a220d6d2609bad1b50e541cd2ad5cfa14309bc981140322209dd9c813829bb4c86c19738dfaf45275e59bafcbbe0fd7e59b40f4633a1769154e5af2b5e7bbdf5381011a1d09912b124ce0782e06e10c8e4c8060d9f679ff7f80b0a52d20f2d98d4e83a3658560bfaba8b4c4522147e846b3a1f01bae7a1add79563e0869f008cc9ae8d7ad1fc3f19450fdde41c8afbebf95bfb45f5a24ecb638d92e08f40c29671445071e8cb1f724fc723cc4768eb76b6bb8464bbad8f71b6fc7f29665a95beaa57b8c7005082557482cd888ef11d7af82adb3839e854e1581d86601f5eb858a866487993f869717da6de289eccbacc09b9caa65f7dd262bf821ec35f3110f4672f6294722c28152d874ad2fb48f65a4d139d434c32b8ba3243b2b43fbcd03bfb13451206611e9220b5fc69819cfcb839e21aedaa8c652285dc8f081915ffbb06c89f05a72b44048f2c0f0d45ea8ae20aae88508b1287926c918b9c4e4a1795c7d4c2c5088711c43f6dcf928b640f58ed0317c700724a8e34df5c0ac91637df98b9117a06156a51b56e7e44fbb82ba49767cf0dd66aeb7e443f34ccda5f6e314f2b08b43d0aa20ab099f88d9c8db96292567406c11f35dc101f0e37134391b05f1bded7b2e44daa1d18dfbc2c885c21eb2dd583b65794d4a45f005e01227014f568b98a74f965c02a83e44fc7606c1be9b6606124b400240220e317922e806b64cc114655c9188d18e70654b834dbe67045db40251204346ba6fe47078d499c32b0557b605bfe4b138797fa1f1d447a0456609039c7e89a2c52056b42dec45cad27a42da16b51c1523a2925f2a546d0b0b04ecfc88bb6ef42afcd3e7321b5e5c2906a19a3f9091d02616089e7b117b0a11c22d014e7e9239049ac97e3d26e2b4530021c66f1a51ac5134d8f015c6f185a37e6c019b9ae83102204b814a0fb423c5807c048024ba42ca5982b31c797924b077bbc53869b558a75fac1c1de2f93d02749f9b268b58658dabe6271127f0c5fb3acf845b2a0a58456647c49a82590737e57b2c9d6175aa2475d955258ef4aa66035140b23fed4f6365ff5e5eec3dd5e953b6578765f8feab0effc166afa5fb693a22e03eba1de8a116b7d455b36f5eed0f8ed340fe3a0e5819ffc6cac1fedcb2cafe60889e486865f601e9c0fcf80c3db3975d7bac7e9aa856a5254b9c55b3dfe0068d255530f7d2a15cf70e20dba03b2f465b129d2f581dabaef726ce0096ddef79918daf9e5e6f9fa51e54ef6a92a31782aa48247b3ad7f8e7509d2315589305e09c85dca2819d25999a67824e54eaa768256a770a2dc2fdb7bc76036a484b47e35897a8996b153dc2dae728fbfff4a4c4ef8363a1726f3c2fad7d99a52f829606f8f1ef56a5cbb63ba45856c83562f3d200aa872c7ce07810fe8a08f53274ffe2224842a892ceb2a76f43e5c3feba6261f3b4c57cc7f1f5d4494e602378948780678e649c36d806ceb258beec3329aceaa216156d646277baccc514137cb3a1306502793f19d89db77daacc7ca31cf8c7720387c3a6c5f16e94cae66cd652cb8b8451212d75136acfc759bcdfdf7275eab2b483e4655ec3c084e21630fcab2696456004b9d6d6d82d82899d5f7ecba262515f6cac0826c8c84e03050c6a8f7ee65d1cde098f196b24fe41ca76b6a6a4623467f5ce86612fd859dde04e11854b637dc661f88d6bd8b0474fae7ad29486c37a33c506206d181e80b69eff17f32c67284296ebe9d84fe86ac07dcdece54c8db359c3b3762e5b26c086c14e81c1bba75dfed79f02bb8931708603eb2ad0a723dba1aa662dcbd424844025a883d8795a621e87aa460d11708d2811c233d5756230159741e0995c0c97e6c4ed8b6ebbd57f00e71193bbed67bdd588c316bda9d086ccd604a66863fca35daefca8adb42317f2f4e1a28a5bcaaf5fe7550e67955868fad0f034aa4d763b16f80d4024b97c324414d53818cf5573fc030f08807048b2aa5835ad2a59a28313e34b29d18094a8756f02e372dd5b691db0221418b5e1d3b19253c5984bea4677b568a8aed3e4dc985e3fd82c145834d8a232b199e5733669d160dfb82c17e80038853a7f95b41515dbb2d14495f0b0d45a5a14a4c5b824355eef01f21a0227992c722e2789a313c7a13b149337213ced718142393477a7783498fbbad9ed247af67d812c0a9f09d2f328ea3ab39953734795c173b12fefa328e8fe8664de17befcc29aa1d2a04e129dcb24d9d7d05a2ff14676d1fc6d62623325416d25151f9e543274faedf3fd43738033876b58eb1880f5c61254b1c0e9f38aa9f88b0ea256e6c63510cf66c985bbc1485df41ec44fab84c9a9bc396f07bd64188fa32a103f422aef304598866f2af487acf777ac5e0a5a7aff965353f13afbca442cd30589902418700e7736b584246f535dd7fa6d1eab42cf772206e9214194d5b6d7ab84f428ff336e07ad129e67959bad70ee552af13316cafeac5a4e7797c84d9853fbeb4e211c5db82efd49edb1d6e9f40a4758e5e77b6ef5ef5c5812fe0c3c94e7796aca38ec9db6237853cb1835e363d632b209da6efec1530e8d389bfaabd31e59ff6f56b676de021eddba7794e5560b6ca2ae3e75ad96e3c090baee433a99f6a76df4f3cc991e96ddb5711e2174d1f836ee2fa45d7ce94bf9ab5a4e52e330ba27597d54e640e33def5931285bae60c35a54601828e5b15d4121f46360ca518cd49389b191f27e18f3eb6219236a48c9874af499227b28f30dae1a5a006d31ca201dfa13a0360dbe7ba2dcd11d05dab689c30438aabed8fe002af48bf053c3776ce0d9545974503f5d96d2e810d9ebdc2d9cce1b9cdacca9de0aebe145ccbd2f453b6b17a87449a5fc2d759a0c6dbc5c36212e0b0f67e8d8d6900246495399c39b98aa1ddea149a35b0878c3745344313e75e8397a96ab7302796692b4fdee74e9c6761e56c1528ead5f10e3648356b2c336d411fa0af370950d42e534fad9707d2f6345e63e69eddada6ba726dd030a85b2dca6ff1b4c50b7fa742ef8504ad251f046f62e8d65c734a0243c94242e9d32f995efaa285a050bfd2fd3a992551e5fb5c943eb8b527910c5b650cf6759db903828ea2a7b5de6a7240c1a8a9c0d2db325bf8d96b8bce22bf4ce97b2c66121523a4fd216842ebeb0b3a46f11a8944ccd158b8c53c9db5631ebaf8cbc28e3d8d7fb5847461ae0659bc3de8561d80e883220641917a39dba935d212ec17742cdc416375799d5db23a29c5f7d2764a4ef0c557c8381019d5e8abd783c8e4a2ab4ff10bdbc92e75cd75d7ec583505ecbd93226ec645962f5532b14d751918918d027726110a02d21add89e906fcff9b16202626b48902fcb3f819117b52976ba385671e4fc3c722d049d3b458efe4986baae15947be3abc9690f84f87876200473fee8ac5066e2d5c1bad1ae518a40f85a614385dca6e6f23cd2ab5a5001128ae2d6b0ba9dc51405f8c44152f749a17f583a771e07d3ab1cb5fd53b0765f715143b539a66459f33bcbb7ee7f72fbbea0fa9f7e3b73c6335d55358982b1b9348105002965e076e3da374d4471675c958ba4a30bb77d73c5c2c0cad561cd31981c830a971d465912ebc6464dff3dff8f970286bf15bd944cf403643ed37349d9ae4c5918e183c4a2315dd59125e6ce50eec533991ac740bea074e4dea58c4ec43337a743ce73a371cec1b36e05b808f5b4ab3e6c9b293a60c541a2d559895d93c8465f688c919fed19abccb1b0261a63faa50deedb656c4b9637c4a7edc7c47273bd7d2f0e875a0eb29af247790e64368076b016a3e1c592f4df5660a560734a8e49a2c7a8c13d95082480e2599625ab420ec773c5b68ab7ad85047bafc3adc3e75d5746521e3c07930d31260bde9f440f5e2812239e843b93a2911f0a128f4f9dd43cda715f77244c2e50a84faa8eb36cbeb9993b55f851432c8a0d338019bf82bb065a07827f062e38118c9dd8f0c28436b4f89de077d4606f5848d1ee45024b65082ac8fc8811b643bc02511746bb95d7381179a3300ca7b23a2e1b5985422a2de05c8fe4e8a39fa76031f1352c7be4a42542f38d99398ee8d233cfc4631ca45411f3e64d433595de354049654dd79458e5f5bc283bc811139bf76444a0fc277a54ccc841f1a27f3e580ad2d1c037ffee45d3b656f4de7533c66144e80f81e28b31de2be02060ca6578e24458a303205c31a0a07a80c2e4db3e619196d9b898709002000c720c11d1a86e649eefd0c69a99eaae72172f022a2f309f59937237a98e136acd3f0e962d7f0aac201c2e848717a6eb14c992a31b3075ab52373bbdd7bf904f9b90b7516625a1fc5af2508657aeb9bdcc178a82159a0afd10be82c0d851f61e543c7c65a4ebb0a7ebe17e85698b1315c9c066d18d91695c6fda692cd5e1f34d85df698ae4609836ae59d4808f11df0b79c6196a0600767292d5ba473beb3c91671c9c8d606cc3d84a8867963affc8efe21a31c18bae76db6d011baf46561a5d7ededd0afb0b5414d07d7641aeca7f8b90b01336166ed4a0074cdf9abcf5b1b6f262d92a2c3816e6c40fdd12a3f7c0f495ab79ff9890fb20623625058d1160f80b39fb507671a19d42a786357a9d0a9b4ec30a37511fa1c10bfd76363ef2882005817c54d15990c35761d69197e5ac185067d4f4561a9ebfb210d72013a31e56f57d2004fc8ab8998554a3ac0815702b53968914c035e3ff9e68484eca11ebc1508219664537625911004b473ce4d70a2ce8f84f388bc8ccf8af7a5e28fb3ba18973db16d6400e36899077c4cf3b6a25b0b3c7e17762a10780f1153758c2957b2126d11aaa5cf8ff319df4dc093f32d26b446e320f8df52ce63b654c3e2eea062f0aed055fc9adeec6c1ed2c9aebcc456d80edb905e6c409ad7141fc4d00806eea41774acbe0aad09d53a9c43c342ff43ce934a5e582f18460df847d331e4e3a701a237447fba28293165355ba0520a50fd78435b9d11f258d2dadb068c5e419b96135927fd8e0dda1b32d07d2f865f42994c879149bde118ad4597f32dc5ff2fa5476e291d301258dc360df6dfa34821422f30eea111f02c8bc2b199f35e145d05fc4908536c8e4165863d29e7ff3f55d08734c29bd5bb53f04bbff391966ee0e0f07a732225b1f6316f259b570098403464021ddd1a4d538648d247030a749a38619384c9dc34e1d3555a4b5480f730ae946539debec0555d25195b05e75d2ea212a2b32e889c8a3d5a5dce1fbfc52c47dab30b1c0ae342fc3c4c1e1e2e0b436e6d2df90837f66054004e1334c61a452fdebe012900011bf31b21ca33283f6dfc3994d9baa2186542a25ed9394d8f92e48913d8af3484707d2eb9fba62dab7ac1411a7e3a697ba561229fd4b8f09c2f13c52e7b651af585e3fe12b4393c76aed160bc0b9ac33d87a2b264eabe563980520c1084b6c431d6b148183690d1a34cc9448376645c0b7585fb78aab6e3bf80c7b39b116b00f0ea671553cc35bace5a4b1a085ab3aa9a0a413427e4021d8746e6ca0d6da2277ba027cb1335ef2b598d8d0165db7e3c3773815652e4ff3c86ff473bf08156296907344a4037dedc299391956136e3dc15028b879c80623530c23cc56b063acf47b818bbd09118115d1680c60eb4b8a2bc1dac542206e2b4ebe47dd14425345b355e857ccd21867998533028e93cd8c6f62e7fd8b7f10bd3e5afe2af9afb3bfcafea29edd111a1b253ff43a023c226619f8bc4739c115e561908b30f67b1d5a3485d4631926742c0059f5dee7556f2d4597aa1bdd7ea39943dd1bc849fa61e7eec093d92aad28b1c6290dc89f604f32aa21c48c51c04bfe834d48df78efce0f269afb5fcf00aa93e2b700858e040cab70c01844144ae66006d32a8a51279519dbc50f78295b86fef4c2934969daa806eea23919a8c54df369d67787166b5ada837f9ca89c4f23993bcf066a91b466dde5fa8eb3e4d76311b6aeb2615e29ecb6a89308b8d9d25b98c7992e4ccfb934725a25553240c4e101e883755bb8296a7dc6c19ad4d84ea060b11a0356c577eb8feb14b03e50fd27ac54b783dec028050d0f80b5110d5fefe0522f9f323c214d3c470668d3760f160dd82ca99fb74cad281da43a02aae6addbd65bc817697f804cc2b490c49f032d252b80e64fa0c9c422065ad9dd5272d7363a5d503b225175052406bbf91a084d01e1e93f3b05339d6c26a3bc52e9f9c9dfc84ec43d6da2e0002b406a29c6555e15af36b61cf0ed0c6f85ed7cc5fa1c72ef484af13d13b04655e9482555e55c71eb55c0dd1d6220f12d981911073f43bfcce9a7e3970a981ec54a79e2ae0f0674a63d3c8a34b781fe9c661f745523ab8f5017fcc3fd98aebde89051e45e3e7048911907842348de2f14b6bfe822180b62ef48f8caf15bb2175684c1b02ad848bd3fb6261c620a2d1af41c58938f5480a9269616a3a00a074570d8a4944b819de466d23ad1a041aeb0fc1537823bec0c510981066ac3eb27c85806e8b9c53171833b30a8b34172e011ea7bb7f2bdd8521e8067743bfd89e898ab8eca15307813da0146b713bd0bbec3455b819c1ab951fb03af80920c324545fe5a370bf06c945d22be44414786c6906960f22cd72b67980bda95d08ecb95281c35a7dd5a8111aa64acc5bb13ef98fa69db881a99c04caaea3ea85f6211823d60dc4c885ad51e791f9875b2b0c2eb1c48c9ce8ba288eb648361d7ff634d15dfdebf0988770e41847b0f19468f3a2ffc66b351431e9055ef5992c4aca18ae2825d2e7cc21398bb2c208b906768878fbdcfdae5e8407e4379e560e557c093c17daa5e08f157222a05fb57cffef8e7c897089291f7e0d0e274c0cae8f9d2496b4b445fed90572cae0e9eaaf8385387b977f56e4144cff5c275f9924819a2d0b6730dcad88352d730381425a986b4e84af7aa2f12dbf15527bb953fc55d6ba494a631885ec9b94d74055bdbbe480beaa868e213d063bae3ce88b024d7cb0f773da43ef945357a75e812f886a92abf435cd401f188644c459549188ea2d5ae4c80b244744e5f9055ff4c06314b628e5dd9e4d0082436481eab52a1a1d6ac4451a175988e4ca8a69bd2a43013419810d50142eb50da9970feb4d6838d40fdf5cd04544a95aa75fbf89c8cbadab0669a7c35eff9e574961e3416b48c635ee849275f21a2186a860f918c23559e0f3d55672acd574b2ef95c5a1180920787d1fcbb596ef0e1dc3d19b0b03807fbd812630fe9983929a0242f834119e69fbdec0c221d1879d8346835631c76bd60c2d0cbd45a329adbaf869e7d08fb5c9c7b07f3a8d45b1271b194c5382256020fb0f9c3aa5484f020b06cf23fe6d14faa2841bfb79b8aea20d9145d56fe11b317736825181cd1996c3fdb9c69c3bf25469fe9af4f4c3ab84660f789bb6ac96c60b51d9a4c272bb02940e608ec10f7896877114518297ee25393ae0ae2cc8f18b643314d5d0546a5141dba1ecaa9ee3c0286288a9b155369a0b36b797a6b05d3347aff59bc6f69ae0bb571c0b4ab0f6a47b3304e9796362ef5f90dd606df41354d27a85d7c90a909fd3855d2c24087de640f9c145ead76759510c61dc4f1eac3055d8762132a61535e3a5010d625c4161a01ccc55cc854d16cd0e5ce9efa7461642cc3a9188eb3a6a99a09e20da8b8998f8ab3c0216380b6ee856e369a4fee5756ad780a8fc64f03b07659a8659e6de0cc607932a8d033e5c1fd04dfee7328021bacfc273fe1b4d96688948789cd366c6029d7f03e681f640b9814c15187fc8846f2cc3fc241dad4ae826dd3115fc810aba6f8a88146ae91bab0f079d3b56efaf1c3344fe7cde4dd80219cce8b6e1074e30bd6fbc4cb6d67bfcfa6b5c7957ade5c03673834ffe4934ce08000e355ff5d3e04ecbc90b5387f9eb5805cc3bc5e12b8bcb4226e0b86822324b145a48d10f76c231c02d7576864dbdd19ea59b35612dd10b8068604ee4a9257e828099c7fb0dbe8f7ea0df9790cf884f172903c91a3781559950d961609be6f9c6b91c175b692bf35c227a551b57836ad7d1f5efe087f265d1e6e35360b109f24a6cfee2a317cf72bddcbc940d65e0b26121c6569da3db0e121333bb0355dbb872e404150e73d40801d9df081022e88ae667b6915744839c11fbc7daa100227d05a59a2643597602fded7fb2d4d020427c9a9a36b9b18adbd04b2514ccb872e8921aa4b1c52c07fcad5672e2935dd886c6b71edc6e0fd00c5c410019b61b7a3b34f098d6b3e0afb3da02f01b93bc08783db14ec98f9e58d77a006be9c81d5dfaf1da516ef2811bee5da9d597005a7a5c7a9f72d2dedfefcee867006c6f35ad06adc0e0312a59d61ee23569d3b225dafd0867342efb202ccc348bd0ba4772cc6fd21e34baa5db31aedc61fa817b53e4000c022c6af2f38d205ba092a81c0bcade6c76ed19898adf83fcebbe56a2987ab0c962abd1bfeaf8bfc09439ae6365a35d3c97340eba350b6ff9d8436c0626dcf96c1456eedfb11d3222f46f8e44dabd314a297840a608ec577ead447a78e0f0f56e4297947ed475ae29d78d12a01a7511c7e36449ac42edea2ac10453fd163fca2e6f69f9779d9f02c9183947d446fd52d9133955e304b6d112df17516f9ee8d00fb82e419bfc9a7df3de5d79ba3f4d3cf914ea2345b1e32a4192fc086e61fb7c8b2d15d8a774fc3be9648e823fc60b27bf74006627e303742a400769013eda8dedbbbf0462f40814abb5ac1992ceaa303fbb29838829c99e6796335677963d3d53bc8973d4bd65972df74992ecd400b992015af3608f5d1e583de7de86ce9d82d2d6ed865b89e6063cd51b8dff9e65a50654ee6346f6382ba8d449f090c093d02f97e9d0a15315d7edc68c2dc329567e7fb81ed27bdf8fbdf2371ce01af3b66a031588db0038de6c8c0b3724f82c3646ecffa796190fe6f804b703157061cd044cdb3fe63cfe10efe27376a099bd63c2cff6e974c2d0cfe978f78e9008c2daae22636f6d4289bfc870f6596bd670cdd7e531fcc338513c310e36fb4ccdb10e5bfbe1b78facf079773775b5474ab0a1ced517dab407adfa47cce51ede92e01774110110fa139056b79065d70117fc81c28702f8a5148ef83d8e8b4679d93cf1a224d37df056ba8d30e4f93572b5aa3396194667518a49e37d3ebce093b92d430f884b0162cb088c94a3742480e61c03c12ed66adbcbc39212363645e9e7af0da4f4295e7dc9a665de23f58d970c2ab104729529e05be6c649f4b38379ff7f9ebe384e1464411ba0e1e417ece077352b0a98eca3516d550550b1a38ce9c844db2e00a12d61b733b4defa84c1b1a1c9ef4627a69d1789507d1fdcb21f6efd04446f6f564d441c59db19a690b44355cf63b37ffe0f36b77dcee95be7e7f655b15e6700f7377145b2cf2995a19a09bb3f8c63b90891e8937f7d0c42731e956187d9c30404c5483182083758b93855a27814e3c239d6bf9c4a0570cc5b0aaf7f6f83925443ade8699838cef65f47487ce8f8096674f5624acb14bddc92d257d64962a5f4184edd1757d9a5d388f76ea2424f493f62b35cabda54101e258ec4468eaf9e02a602e7e5363e0cd8f828603c332af8df2cbd3bb203dd08ee23aca29c5e606d5515e70bb11929143274869ebb136ba0e2d26dc8668d32072e78ba1ffaf441bd00f24fe70a3ecc6f00d073222b3aa893e4d762d283ef179cd442c0e5d218eb788b869c6f32001184c51aa38aa4248677c54404201c607efd542d88627dfd79387b9b57d2dfcb3f766beaf20fea7a9882698af8885a0f5979feb7ddc7b172d36d0df77a7a23d5011c589465d8e624a5918b7601c961c9c8426e2f73163dbf4b9e1f967eb96e9c428dee93ec961c547222cd1022c241c8be2cde2e65a597784a0847f2a1b9804cc2168c687ae0d26b5e55800bf5d95ecc0a00e03e5cd4b415be98885705eec1663f22e4ae3154e2c38d3f94a2f021efe0d9c30dbd9434870f566df1d5afe4159a8c7efadad4eb3068c7182c5197609c8edf7a25105ae0f1524d4dd846ce6674ff051e2ce336c5f15d8473db1de64b99298dc93ede24352acf80cf59438644523879b311c2bd0d34b4245b38b1d069b9d8689d522e4f86244c60ca570214d7f72dd0aa7017fb7a3ae6e857d4a2899fba1f4017d584bcb6eabc74ac49ec2287e5d757bdb3e19a5a8f9d368f82a1e57619741f21aaaf48d425285a074c449d456f5ccdb720d2c9baec9a9ba1a36d5ded75c0661835b3b9521e7b9bb942cf54a7d23806120f40cb1d621eb2183e6a33af99deeb35bfddd51f3da579f5d8ab208a21e743314bab8d9262a4f70b83dc2ddc57d64321f00ca845c1e59b46046cb640da37762e2da4cb0241ad43103ba6f6e845a208e59361db4ba730d9973b86f8662a0ea877acfc750df46cedba07d11a51dca803c7ad843bd034a847a7985d967d40c0c040604030600809eb957a7b60d7e9cdd25fc6a6d15d29975ab95f20804c828c9debbadedbda59452cae1078d082d083e982c79736e8cb46209ca0c4840e4a4e121e6dc58898794374a2e6c45f1293937668252264c8fa5394e61e7c64d29a0ca7ce860d222c39b73e3280f5a80f5b4a48615ab37e7c63e13d0126de6bcbd7050b8652f7edf25a77bf5d6effded1b2626e6ca54942318580072ee8bd4ab612a870ba2c070eeab04015a22ce94396fe1bc754a766eeb653960b7d53ac0570e0e404b54df35cedb4a66d4658f9cb8b8f959e9724111323ece6d91be98882ce941266cccd5b9add257385f7e6501e5a0a425aadbb0281a308c1ca9418773ce8d272e64e0718483c9b92b520868f8d2e2c2ab86185ae7ae4db407375736864c4125113977657a525375e30a07961862ce5d7d2b5e8cd9088b3187878b38e7ae516f7f74d72b176f8e2c11b5da02f6d653a02768d9f5400dc2a7750e5822ca84a846bda5f1f6ea0530a6313016363a8004363546ac6a851dcead9f9249616903c313981f34acd7180cb01b4d64a872eedd6fa07743962a2caad0509d443968c170ca02258b9c538ca33716df68308acd57cc6485aa571f79488a01007f3d63a6a6b1dff99cdd83c45c95a6eaafdf2c363cc6389fb962d5cb22dad4a1377e33873667681d2cadc345d7a27411bdeb96af0e6ad1555f1dcca2674a7a579fd140e11661245bf639a771fa9c6f11f565bf80082360244a44c11e2361a51914ecabeb243d13a2c52bbdf18b561f7a8f75b5b21032d2a06dad5432245a66cd6429891ab71b327a51a7a56eb9da0d89782c6c4b7e537500d3c23628fc91fd867022fd792d21fd66621931aa2a51b2d75e6b99465a0f1687d6ee23096287068146d3283fbc9e7a0df2d7afb82d9365b24474e816b9e3705adb5a297edc2363e401d31e1045ad7138cbd344574badcedaec41549a060d66132ad5c0ba9d59097b9847f416b1eb0b60cf9e7529607d733e17d0a58063903aaf6ec5596bac8043e9bc391c83d4494b0fae91d286def87daa53321d5ef84e2a674c0ad2495dfc8173022d49117de8ea348a8f2757639693baae1c581d384ba06c083b5f18402b8c2f0cab7f359e6569bf6a2a78efbd2d7830ce18678cefbdb76a518abe96f6f4e60ce60ce68c31c6b5d6fa82d5daa5a6427ca03d757befbdd65a6b9f44d2da5a6bad6e7e17042ff8f3e318fcb8836006fd52e991520d6caff51674ea22d01355050813004371461bc91285dd95d99f12b951072830a812803b0643463ad6c01abd3a1217863ee26a2db4d7c8eef0bd1883b584ad6d2330e7306731e759ceb49cc79cc99ccbcc43a537ad0ab1740a1520d013a0e8d83bb0bb323b0a9b004af463c2a8037ae282bafba063408b6c54a8baa8dbb5fa25dd55b73e8212519a735824a90633a69146298d62f229cd292d7a9a338016e1e815e909fc3437a21c844ef3139508749a2ba01265a7f9494f60a7515ad2663aea24ad1c845b7c9aef72976f6f49ab22ad956273e6bb2ce92e7fe3a8ad25eacde96a0922945a4a69ad23c63a22f6a5abeb96d36f92e27a8d8cd6d6686cf5f0580bb24414ebae5f2b2d6f57e05ae38cd586561a7af31592813a66248aa7801dcc3a15aa0f353f7a270b54d0f5ae97191cbd133bd6e52d5728675da16a6d261b69d0cebfaf572be078fa644e19fd3dbdea15f9ab444f5c1fd5a07a456145c4a9eebb5b34002c6ebc38f5ce4a86c20918a234f97a673dab6975258a52efac59bdde596d1c254a3dcb747b481fb27480f5ceea76c966659585d9df15aa7e5ab2eab5c9a94679e01645dd220ea63cf9ceea218c60a36e113dafd215aa286e113d2bd562ad62cd3fcc355a9cc14b4bd051d8300c75e834ca871f5225a1f261013efcf16118566b44da7e82843deea99516d70d63b846f5b1e28a31c65b9ad67b63b859b474f550062bbabace8b2eba7a2d55d1d5cb47165dfdc353abae36b4a4b59a97ae5a4d9e4260a4b40a04bbbb2976e8ed3e84e206ed48d84c7190199da2eaebdd36eb4435cb074f13271a77afaef238d13c54ba7badb5a013bdc9a796c7094f95c6d9eed5384f69ca43a5696829106bf56cac51d51eb9f65e1cece5a9d2389e297d75949c8921485e8c71ce190cc350146733da9007f67794479e805ddb3a2011445e1a8a241d411ca9b183f6e2dcbdba4d8e23a9adaef6b419475aedc519a4c2436575b4baf855875a8dd6ee56d87ac248ad091edc176b89ea1ed86ad3687da4b5fa687324a87f82eaa592a7a504c65aabcdee56d86a7b637d8ac71d913641a0b4e9f1144a1b1c1f3e85d266c6974fa1b4e95960ac36b49ef83cb5ea72f4d5a9c9029398c45f02f96046ba9dc788b61508a57fabf5bf4ab9aaf4d7ef454135b84eb18e036cc0f2bfd68bce741c604315f55147218abefa00dbfbac534173285230d1b31f7a531f4f91d0afa750618928584150e73bf4ecb499a780268aa0a89b006d4745d1435187cbd507799e34d6eda1a740c6d90a1b804a84ca32bb318da82badc411660c58848ed7ad9603cf2c87ae4ebbf7de1914aee2957bef0dc38c33167a8be012bdc5d28cde22b5beafb5b32f7fc40fa8f5bf964ff416f37d5c3fd838e8a96e45017e14733fd2c89ad3c93f92e409b9ccf8deb2b495ce6acdcd74e8d96c56629ad6d7abea7c3ed095403e1ec9ba71d5e1325873e28f2229ce667b452547da8cd26b2bfe1a3ea975d6c381cc58ae750a0473c5f6d61b829e05638c6bfd5a97dc2041904b915aab954ca7c6213a0b31287a7d4c29f817cf280d310e970599524aedcce630152949857288cf362a949d82ba4a299d51edbd72ec95ba380c6734190cef1545ad7138f7ddae7412eac79608fc20e7c71fe65cebfd30e7fa41fe9a6b0dae4ff040729ab2e4830d95767a8b488c496324860e1a5efcd05ef4db26042c0037c478d30b50f24c9cfe1dc73b8edb7171474897dff1a192b7e3543512d4c46317733c76ed3b21381e03b1f2d84b943c6c84cb42e2638918951730f01904dc90e389cb9117bd169c362053dee604287936d7a0e46910437c0fc249943c10e2cd71b79b07dd7ce8e6e24dff8dc8df6e98f3e6066ffa004a9e9941c9c32e54f26e4638338ff35dc9c389b6e7117b1e1e0fe2c1cea3d4ebf5609c3c57aae06c35ad9141caebe4e152e6c21815af321727dd7a1efc8365c46f121d08fda6554a7e28997abc0486c74bcc3c66c2e43166d2e5710e373c7601943c6c4dd374bae99605f010d2e34cd98b2132d0a1379d9a34eb4df04d336fbac7f0a6a368c19b2ede220e7c84317d690961b2c3692a71e14d0f40c9337f0028793c5edff314e77b3ca8e4f538a1f95bfb5be6af7b1089a14b64e9af792b947b7d849d7b89c4f9bb73ab946bf3cbe3bbee681afb7bc9fcae575eb16e4e8394e5d861aa0990a3f30ed981cc963132c67a38b7feebf41600e98c0b409ec67c386eed69a085262ff812c3911a48ce14d45cbd008d989316acb94f2ffc2dcbdf1c839277c361f8c985c718bbaecaf310b9e1793c57f2788e30bd70438585a31db29c5bbfcd16f636176fd107ccec1842e497c505acd346c4c7dbdc4b9e2dc4ef38c9f91d1f51f27644d3fc79d3349d5a78d3c9ebcd216bde741125cfd4af1da7879479ed40fe53f2eecff71fb0bcf90fee53f23e5819f235f16bb4ccd76a35edb5da15395fdba9e131350fa1e622d46ecd690d4b99afe9d4b0929a78d6bc2e5922fab59a53f11689f84a1a9b8a210684e9ac353dd101030d598ad8a8495d7991e1abe1668573531f094dbeaea0b4b09981aae6b4561bfa5d0363917aec21705357e8af8b567e7fafccf05690fc7510a6699a6e3aaee49918bcb692f4da7b4a9ebe95bc9a8e539f0ed7975fbae0cbb3fcd2495822d34b1f32e2e1cb0e4a0fc1125d2f5d845a59fa089cbe2ccbd22b8eb2eef8b2327d09834fc9972037b6bece52fb4aa79b967d597b5f5a65b978addd56f23416f3d8c51b78c020bef6fb9a7f2879b515467a68a9a14bc9961224270ef3613b54e1d10298b11acefba4f4d7754ade75b38a913779656d7a2824d985ec224b9224b7c80cc8af271d932e3a09f2a46b276d9eb4baf2e413214f924fa49ebcb2f1e4159927dd2c79a4f9e3387ad038fa902daa4ce3383ab5f2e39420f337896da283124fc3fca5325584aaf7d7714ea5f4d777607f7d884acd5fab2651deaa8a86bf5e96bc8b6ffc367a8cbd3c76fff2d8459fd663274b1e2e79a311019ee6b4c6d39c26963f739c25c2603f73dfcd7cc8cedcc8888b9f752035f3106ed12cec673058e02ad371c58613937376844b09c80c5c74cc4873cee8d5959f417d3dc65082feba868251240caee4cdc8c09297993ea7a2863a25ddac068d5be8e0163a1bfcd6c1483a3a3a3a3a606f5d074b47ebadebf4aedeead039bf758e7eebecf8ad73550234565f3d222e543939b78e16ab2e6facdce0f0c5e4dc3a586ec8414a892bcb776e1db135695e403635c705d5b9759a44e0a5cd0c37aa98d9f23ab74e946f8bd4961e5bc43439b74e181d66cedbad6f9826d365afbdde82bd75ba7598d85bd758b6e8b2b96cb4dea2a26b8edffac86f9de4b76ea1654a8aca0e2f39589d5b3b65bd70c31c222c60c9e1dcbac71cae2528499cc86c9d5b33bd9d7a8344eb07123152e7d64d1fc81155b68255d29329e7d66f6eb461d1d5c3ef4bcbb9b5155ac2ca9cb7bba655d3faeabbb6a5a5a6e677cdc9ef9a0cbf6b576fbd9655d3aa79d5c0a4be3cb921568686d79473d76a50e99023c46a07a832e7ae294dd130838e346e9e1c39778da906333e283441ca689cbbb622062670e024ddf0262becdcb514516062ac318325e6ebdcb51a68891ae6bcdde66d32cddb649a5e26d85b37b16cd1f559a1ea3e53ebad9b5dc8dfe69159ab7e9b2efc36af6a1c9951c3a969ebc9b94da41b5173c814d18185eadca6d22c87df171a3a78f5a8ce6d32a550c6e205178d252e849cdb6ca261079b2a45b2b4093bb719f5d5260d12314f4dd09cdbbc35ca5ba3f4df259c8bc30a55dfa5188e92cd5b2fb7c894457e974d7e974e6559fecab0930b961e5860006381e4dc25125603a4abc90c32358e9cbb540a2bb2552606468ccbccb94b267a068b0725a5155c00e7c7b9cba636b4449b396fc932b28c2c23cbde3ab9e62d29e737b944d2f09b2449f21781ae1e584e5a76c8e2e3dc24d2882d3856472f4401513a37a924001d5f36f4d042ea880be7269940a0c482960cb030605e9c9b7463a6cb8f16689a90f0383719f5360a0b489cbc8ce5803a37e99b424b4c99f376cc1ac3de8e366b1cbd46b0b73e62d9220b66c72d392399df63eff748f57bbc2ab281032acc922f3b769c7bcc81e486992a6f5ac0e8e1dca3d20a1a3dbcc2cc80d4e20992738f3ebe6ce9fd58aaf1c5eadc230f568e8ca13164ca8ad8b9472f698c2c65619ae1abc9b9479f119a111aee372d8a16458ba2d15ed068341a8d46fbd916cc6c0b663dbf679669669966b31efe3d3b5a9acd66b39f15adb8c5ea48a2452a7beb624f3cdae0b7f8f45b9cfa2d8ae2ef024e52c8a2b49c2993e3dc22d28a223dfc70c1050b48326c9c5b549a3ab3c6aa052b2a58e716995440464c0c5ea670497939b7d8f4564a8f2138baaab47ae716a35080c16424490e921d5de7167d6768893373de86642159b8c317747e872d7e87337e87307e876118fe4eac59d28206264e64443977889480177c3576a8e2c2c3c9b943a59e245da4fcd040018972ee900a10b02730d6f000c605e70e9b3e3c9933c3c816234d66387718f54e9a6248a3e45ba3a5e4dca12f0b9805dce016700b0896d17e8347200882e04f481692c7dfd9293b65a79c73aeda192a1fa1f99d9df2150c3078cc79c18289d396f30d0f1632d0280325ecdcb9e96d0b1ce76d6df1d53a86539170d85b8cb117067beb18cb16d51df8373e42f21b2f0df98d9d88fcc65711b07286ec45342c8164ce55304dbff08909c74fe4aa61cb8c0ad212ad86ac712b29ea242998185bce9c2b800c45ca4c32e7ea4f47afecc4391f2ce758a3d2e64a9339477d4872ae4e9cf3d1e11c75a684957dc06a616927eb852c5b9b21513c55ba946e447a18e3eb01d13bc710b6d210317e0029e33b891cf1c80ae1b3e6f459d33b471f5ba94f183119563bbe7ce708e2a9ca15329f35206c3ebd73fcb195fed429323e29a6730c51cb925c40fad12184f87c40c1beea70cea7fa0f8cf30746efcc399feabd7304b29502f1b8393a73ee002ce88083ef1c7d9a900971d25f73ae3a5dd1423a71ce67cd39fe58f1c10444e61c95e21341e6dccdf104e91c61a5948c71f22a038c1cb221289ca367cea95079c09095733e2bce0c7a9a9e2b583ae9798e189039a70275c242990a3f30ceaca3f6009bc4409773d7e9743a5dc637a8f6224cd889733e2f38359943d3d52ec1e8af5b9a739873982b26b3d5fad5d51b667763b544f5eb955a9b57bccdd4ee154fa9e79c73de275813263618dac08409a8a63037739cdb99046cc51cf62c8ae1d344517415664933cfbbc6a825a88739475dcced9aab316a89ec21a59e735893727bc55312849f0b73fdab5a8219a417ab19672c6c0032112ac776a31add68e5e5d5086bb1678c45608cbdfacd3a7cefade6e301b9c625dacc88ae5ea3e8ea33ab71d6a28bd79f60dfa4659eccd32f3f66acdb24187144d7dcb846531f7100e268ea2b1e63e06b2ef75134f59cb33292a5198219e36b6ba534934efd38236b6ef682a6ee53a906b4d67befc5f482a2144da3a56975db83c7515403eab9f71ab5ba1cd134324f7a5c330369039b71c42ea577c65887c328955a31959e00d97841bdf54a56abd5e4e989eab8a4565e4b5fe9899c010b5dabdf0ad0354f5bce889141840acc49a950a5276c062c343d513360a1ab8fb38b67745667e26c667338036be64ee1ad67b1d91eebf1c31320584fed0e8aa6581a3fd19b6645d19b5649d15be4b9d25cbacefc161d4b63d1e5ef7acd0a3d6d5a1a77492133562cd8aa4b6a0c638c291572ace3001bb4ec8e6e6c8932896be9141b300a8023b03b9ba3140441d05e2ada5dad2482aeefa0a722d5614c79c474f5a07ba36602094e101f3bd689507a63fbb888e9b44eab532f79f649b188aed6fe98dbb50c3b1848da2a07b6f737084fc16db9da5915770853eb22d8229c15ba602a07d5823d1eb2bb2b5e1fe2401058091cd8116489e8d3cfba9a985bf2b03dc16f1590deea4660779583fa55c6d12d1176b13aceeeaad7c6587b6216b896e162685abfb0781b041684443cad8fc8d759123d8e95c45e7d575de3ca2fdaba4e15ddb2a3ae73a54d2aba567711d098691a5343101d74c8b9ddbd7e8b2897246532b4d0f0b1e4b462062a365ba0cefc48c3e2acae714af4367a8a6f4e04bba34edf3eb5ea8a7ad188b6baa1ece222093275675c2a4930121dda28a3755e8851b9d83229183f8d5da0f2e540660b4ed2a10d4ba6755eb0417f803ad2a215180da455111f49d668d150487b6ca11169fbe972c5a6197d5a1445abb575a5438b54ec101d6ae969f2a9d55617dcc21699acad35326cc9a9557468e38bd67901062d5a8dd12515183a8ac64f1a7c6ab5650d8ec40e67d33f412abac800c5c8ddc87d22ce88cce8eae7f5f7a2673dc63da6642f3ed3e3ffd07bd1e9abd71694038c444b54a7315e3c5d81831aa82b393e186a1c393f80a21a5316c54248d6b9ab526d9a4106c6922353ae66a038bc204b142c1e33d2f468972f3e2c73be085972ee9a65e2054d569c10e9214bd8b9eb1636d452d126bae645ea94fa53c751a3a73e445550ea2350fc3404464f02e8a1534f01851158a2ebe3e98fa78e82fa8ebaa6bd5ecf066593d5ebf566d01c3e671749da8ddf419f7def486ca3cf8e02e933cef40aeb33d5b2d24a92c53ef8a66c8a707dae4735a9c777c5b1f4d92b1393cfb7dc1404e7bb4255df10aecff90aeb33889c7358150b51d68bf3d91ef9f8b648493fbe2d8e2526ac2f9edfb64710a8cfd9ad6fc767b755d9627dd6b2bf169f2d18906f1b26f6d9dede8bcff7e86dbe2fd24dfa9cefd2e7245a06f81d8740c9db4192e56d369bcd7739b7d9925cbd8db4a9b17908964884db5c049bcda9cde636174e6ebcfca45cb8387fd810464e922d16645f36dfa1667edb7ee7e777488cf96de277c67ec7f18e8b51507e0707a8df916ac1efecf890d497df71104ade8e08118e1321c22992322fc21f50f244e49c6edbeff7cfe71ce77247607cee489bcf19b1fa9c3ba0e4e53e2879b61151aafc085751f24688b7074af3403b0e04647320a0154cbeecb04549ce0e5f309c401f00bb42038e0c38c2da0045c1e18112f0dbf6b69fb74101f2369b0dca95b745a1f136f7a0e4d97c67676767c71b50f27678250f684a9437dd344d0f22618944b8e943545cbc7933d198a68b608e304dd30472d374d3344dd3eb55c53275a6699aa6699a2615afd7b9ce53943c9d98cbe572396740c9cb39951def7e963c1781808080fc35f81153b87e84a3287923767676767676767676767c01256f4744d98b7005943c11a28dcddb3c0125cfd641c933b16c3c7e4cf6187b50113eb1763c46f3218840c1a8d863ac228f91942cd10e8e1db509eb710d72802b4b04e4d82b9656f57afc26873566b4c6a492cead1f339123c683982419a69cbd206ce2b0b043111f6bb0921b1949985cb93202e7dcd4d7ab518f81ac1e080808c8392879400e72f03b2eee6009f91d3f51f2766c369bcd66b3d96c364740c9b3f9ae91690edd1a058bc69b2e9a58496fba51c9338b4a1ef6ff91d2e57f9ca8e4fdf8f8f8f8f8f8f878d6fb300dbde9072879a618124979e1b19b2879d80d50f27c8c42dca0f5217c83921742c8d56b9ed7dadd12d95c7b90f6217dc3d46b3da6775c6b536b1d385b11cc19772973058915140c6c95dadadadada0a4311ecea7215294008c15c3e207b08823ab62e8fac21f2fad5d9b42dc4199bd2d3d6d3565757176e6bcb09d75a6bad15c536ca14a43aab08051450e84ccb7c65434c5d74f51830fe82bbbaba30be557430e842c418cc983e6db9e8a27fc11863105f01e102d7c88ddce84b66e1669a31a614535cf1bdf68b1c8cbbbaba5280d081d1cd185f298bc12b8e2f488a5b6ceec520f81464d36e9492b4c6b991095a04ba1da183a7238438d385bbf495a1ab577ba33c6d3d6d3de5e8d1d193a3c7851e1c3d377a6cf4d4e851eaa1d1d342cf8c9ea41e193d317a60f420f5bc00d1d5e3a2a785aede73d4c3420f8b9e157a7a73e4dce2e8ea373802c018c9ed0da6b71e6e3cdcca6e6e6e3bdcdadcd8dcd60cdd6b800d4234b504a5e5ea49d6b1d2d35ad5171908ed3297eb63aabd4a8b7402902780a359d9eb0eff186a0cf4c411ae5714b5b634cba906209c381352140c94b4b4b4b4b4b8b8b8c0281004adb22af7deab775e469cd1c45918e2d9571cb4b8b8b870546b4a581818d6b3729d5c5efcb8b8b87ae8fcb87e5ef7de5bb57e6af08f8bd65a2bfd5de1e2e28a32c24a6aaad695d6947026d24632ea5e926be46a81c675c7686517d872dcd2dcc86e686e63b73337b19b995b991b995bd86dcc4dcc2dcc0deca6c30dccedcbed77fbba79b975b979ddba6e5c6e5cb72db7ad9b961b46ddb2607ad3ba61b95db959b965ddaadca8dca638905a4204c37b2f78c799e6a277cec5eff511e071659181d0ae87efd55adf8ab5d65a5f8d20c9581d04154af1438b50f8892a2e0974675d871279c0ee2c0ab7a8ea7635a92659a25d65fc09faf8aab8453b0f9080c369bdbbdae17062097623c04355690708293d0c7217dc91184bde0f8ad33b7731e721ac2285915de35fce74be713a6a85dca9d4206226ce421104719e3d582b14d04fa69a8b0b38dc0f082dbd3ba877a8003ba19d7b9bcbe9e4723b5a34c28f6ef9514a290a151310294e73d118e31144516b1ccebd0ef9bc96b2328b9106d5a74236052d0a419ce87af7a1ae1e1d2d1d2dd5a3113c70916c3dca3509cba0a3b77ca268ad16c98b711eb4b33bbf41eea28b7e64f45a771e848ba1b16882585e24529dd18f1115a3664898000817b09eb191911bb99191d6888ba1a98f346702febdbbf45a4be9dd699dd9ee87ee7e9c9e20ebdddd0e3ab07dd1203b85ba58a4c20a59af3174c90b29171529b01b5d19d4c6d038130505c5d1db66b30ee21aea682545f136b1e18c0b77f8de203d04e292f4bda0484f84e35596118a8a8207f1ec73b82b64e9ee0a8962034abc076668a3a12b64bdf6c0d8fa369b2dc74dca2dca0dcaedeaf6e466757372abba35b951dda66ebe5b0e3726b7253725b724372910371c6e51bafa0dc9ed88ae7e337283ba15b93ddd88dc9cc617892e298638dc8688b71b6e426e416e406e36dc5e706bbafdd0817645abd2550c2905ef059331d500db6c3bb67b2f48d2a0589c8f8072efbd57a4b3149eda9d978b73ce60e8240cc3300cc3522b34cd843af41083a178af19d16b566963ce5924b5ad04db386612c4b6191749e632bbcea0e3acc360ee82e168062c455c9ab3297b71c6172b450aab31e7662ffe68c1cf8fdf3ece99c83223c5aa44531c6da5348b3338d3a5a8324a215605ce665dc4510c694122a96d25d86ea609bceb216d766734b12a3257ec238d56f2421f4912631cba4edff2ea5288555d7bc72c2425618c1883189335b2a6c9fb538482f6e482d000da631d8a269668fc50348e3a1428c6d18baebe71968846cb898f3fe8943fda6c3c64904d24f56da77c000936db079dd2041e7691529b88693bf54bd22c1b70bb9526f0681ef6d45ab338ab8123bd3a454e85dd893a146774755c2936b3ce2c71250d50a044614da15547da5d9ea2edce5e6bad1dca4b4830120cf923c190602e94a2a28e40d1235134ea080cac82a9bf1042a860473e9fcf377a19bd8e6636cc9a442691c92ab15236aa3a55272556090e5068bea10a55a3f0180ec361980c93b948aa312a12ad4947231986b35918cec4d0462d7d605ba825b25fb7b99bf45687c25af77bb3cd65246befbd4e8a157441c565e907f094aa8ad5ef12c812e86fd62432cd82d0df2cc8ac49649ad9300b320bc234527aa224291d69945a6b31b6f75a6bad0dc14c29b6d6adb594524a29a59496e468ceb4dc7b6fbdf7de5b7135412510e42181e78661ad59287eaaf7da4ab0d97b71cef7b6503305c1e0a724f0d868570c45b7ba200f22cb9998e9893c2695f45b7bfd5800a2ef53ab5fef9f747e6af5f5e6f6f8e221850a10c8a12fb0d9d719dc17d6978f8a459237449ce2bc215323ba518365c7952b676a9853b37e1b0d7d2db12cbe9818691271062f18863a31cec4e03576c30c0f4a7e3a808d39b17222468e08667c597c419136527c6af51504ccca8befa9d5d78e97f2d4ca4bd6db70f484e836fc6ddb607ebc83559bb0bfba13ec9b00f71b855eaff727e06a6f8e94670209ea9c1773340d2ef95bfcdf62c801d689ef17cb2ad9246a5bb04a33ec12c80a558f444555611b559dac94cffa9a90e36ff4a2a397f137feacb08bd9a9dd89a198db275c303030bf10c05430b09ce46a53af3fe13e06411154e16de8d7ef781d749bab48d8ba758cc7d15e0a5a9d53ada0718ebcada0b7edd2abf380ee480c99a07fb791d197106c0876576bd2584df2d9481d06b723316482b4913a841a238914d30ba0147f7083c3f97218c7f1378ef497e8684914be3aadf9e2a8b5ba0af5e2b05e7195c0b5514b50bf4a5fedcfeeeab5514b54b7f6e2b0bb9aad759cab98ec0787c1e8fde313b8c0637c71e41ce94563d2bed0dbabb08c309d3847579c39b7e21c6d8da5134f81210c7b51ca4e58029cdfa8048d777541ca0cd5cc100004010316400020140a078442915014456922ed1d148010629040744e34948763b124c55110044110a38c310000449031c4186688684701ef02e9e9f10d5928c8ff26b9734e1f04f79c4fa21bef44782b64cff33b2f6b34431263acd1a9cf0aeb41cd1374b78a6dacd518332b5de98173d1dcccff18a7a54196f43e579d8bb618906c0021fea9ba38a1c086fd556f2bf2d9050d1d075559a5579e76f5447f0bd6e87211202510d85d8595881c7c80a9cf069a92c668ccf222a71e9bbcbf403a344f859ac653afd79c2a37306d68952ae84a9cea103090d4819e2475e42caee6426a3240bdd616122147a06f8f162b742e4540dae9cf0a951ae0206f63548e2cd3940fb1a7cc335351f3b3a9b0dcf0d93763ee6a4eea2a574f9138897f8d438c8f7ff22edc651a75cb9bb6d293cf3c220ea9e56eb05738449466c793c48c33b89eda1105ed81bb1e33a5c36c4a2719471d5114ce19626ef769a5bd894d54d819fe8b05d3f510f43dd0149f5da6038dda77a442643ab0b63e51599e9161ce58441867c52513c2b3f7bb16344810dbb61d34195290ff0a7c05dc48d608632fafa76d7055cdc17dd1753aff69aa9979535a61dea977a427894a5710a2622eaeebf1269e240a5a47f2ea109131451ccf1ffe5367a0bbc0a74e8462888149fd9606fa54e0cd81a7e2bead170dafa0bbf65fdd151b543692095e9b3bada3fea9dd4180af400f92fec9e014b979b1ce02c019a78a4d81a6ebf117450246d3e46d4d91462222db459b2821d3ff2d38a851bd4ed6252d21902575414b51a54a7c7079afd43ac95e726375c11e5a26074cd93b76baa5463bd34c99f0a5be06f13d7f9641b299b4b0540fd3d5f306a64824185b587e75f765780bb9f10b5af5a41d2de15ad4bb45bd39cd2b07015f22f936702abbcd3e99799c8980b432e7715c981b376d5954d9de16b2f7089c7b4ce816db5b0867681f271437ce76348080387345dcb81406080b278d16e17b82935b40f0c32a64866e70b824dd6ebe28722686245f67e4804f6826c8e047236e628985b7827fd4c0076121ae477f46a93d6e2fcd286b270091dd88e0f6b06b9e503f01b7514cea2b52a3356069561365b8df78ded3ccdebb3c3832556c3265dd675174c865c54126cac226118c4462cc1c89bb1494bf36ea0fc7c11969937330578fe4075fb4234985809b38c547cc283e2af604e57f9b92c045976e04647eafcc66400617cacc00ced91596e35e78b57da2bab2811a6ca34993c11cd7195a7e471da8dc4f355c0235dba8fcbee36ac235c09ecad96d75bc3ab31466a426550e0d7cd2e114d44591a58bf87e66b6a5d12c2adce5ef6b50807cb8790678d5edeee501b427dac4e5d01fd93045cd6c2ebcd0bd4d7a5ce1159d08bb5025a6619ebb839a2c1598255a546a12d5ddf8a55412d12fb7dfb5ef5ebe6f2fcc62d143aba186ee84747d02ca6f002e10839796a9972214da513cc96d8a8ef20743ef19104db649d8aa46cfd5bb732a75763c156900c24655d942392247878222feddbe5cd4492a468e7e58600646d238600902c6ae3f6194ca4ee348b025826e317611eabb0374f7163aa038095d1c62e0900c7c1eacb408962e14eb14d3d4d986af0e9f5ae7eda2046dd85782ecb2004d1c01212af8486d0bc4615f192d0d190f4ce0dc0d5a45b2af7ab322aa4bd126c4d58f2e205054395d51681a766e069a12a9004903215614f227f740c11152ac9f9a54950ae162a990b467095abe984f0c45a1d3a904ced3ea25c85e1d076d587630a090096e0785e4ada6a63a6d30307b3986ea8237b4409cc7a869005bc3f18876e8b6099065d2a32bff2c0993a4938c188901c3655b4ab97a3e6cbbd596533a859cb90a283f4c85959b856f6fa1683721e3cd4c0b2727b30fc4611d952815ddb7272b351990a9e1da47219bc3a71043be0b787142581e7837420bac917928fc082c0a968c0588a048ef610d1b8437a9c139e5fd04ecf810a2c990baed356e8ac0b532300cf46c83eafd85a31a1d9018e8b286eb4de7a286f41664997faadae4e246620b6293bc0b29820723861972b88a4d71081d209bd183749a6ca0a0fb035ce99beec0b0c56a56c974a49dc25e58ef28908996b72f783a72da51b76b13a454666d829b5a7ddf1447dfa26f9fd9eec236e9518f8a8e646daa25a8d6a34de7458418a45b2373a36ada75d4cc028eaa2786ddea03776d7d0234d5f48ba5e978ac7f68076967733ccec9ac03bae1917267a9992d530734e3fb0172b242493e3d0a97ede939be4ccd985a74e7e96a2e78121dd4f4132271b9f27c88bb758312218532a6a45a5af865d8d0cbabac64c0f200eb3204e2cabd3e53b0b52b7d331c5860ed66fa8640d7ee748c41dd75107e29dd162ed220f8934be8d15245c42e882fb05d85305938de2077cb0e54670f647cd10bf606c98d0b35d0de93ec28fd5202823706cd3bc51fc9d00b6d13d7d94f6991ab77cd6d085d724059bca53c9c96288a65a6662edb3053e85963b3f8644a0564801814b8920a2470be15bf651c04c967c9cbedc0c03385c1550526bbd37691bb57aeef35096c1a4efbb7b29635d56bd9f86a746eb70f0640ebf17902f9508d8c40be744b7939a80bdf38c0507189344cedd299f4299d6e47946c5943bcd46c6c8e96a27d673a92feb743fc26cccc89ec415cc0857cb3aed15cf3d2868716f13a6268176ac540fa5b61c659bda78b7e4b192b57e8ce1a6e8ea4cc66c1790cc2e26ec21577411ef4b6452d4b3d33e31a3100c3be2f04ec19f034d005f9237c0cc80f3029925ab0b8684d5555f5be48876258bd6825235c18d2fce326c6c1784c0cc05a6b674af317e86f3dba8030860608f33607a08316e67a2d689622230719577369d94cd12d111b5f3c0c69569c1722dc3a0ecbaedbac7d1b4ae0dc958c62f28aad554920536ae125189bfb908538dfc1979abc495c2bb5485b7457c34b740023c22dda52b160f8534a7ad78e6a013df659838866803492a3ddb419876fbd6ca6c22b018bb11099b2309a8ceb187f0ade8497cef6ca94f467012d885ffdeafdc4e545ce2447dc8558743fdceb7693032ac7f4b3d6d8ad96e6de6ac91e7dbd61ca6a9e27bcfa55d26e6f7b7f0d113406605f37a29269b3433cd344c611f75d5e3ff5e14c44416cd106d7cdcfbdb3eaf1dc0c1c38c08e80196b817e41b9907f6bc1ff55a8f66380e86b2f9314da79839182fe0befa772d52b9710e04a32f846538fe9a76e836ceae18d224b41ab02262ee97e1e87c49f12bdd6e7228811b6046cd2e2796a8f748d8f91b9f13143512ad78cf3d5b969ce1e98c440ffb5fc26c2a627d08f00b32b0b34866d164d97e8614d805e7579dbe060a76496ac49d30a1b68179e05868a8a7022c4e63740d22b9564d5f0ac3f845a78fe947a20374e71b683433e960ef70d14a14dea7088a6f86f5430cefc7b307881e1576d40c765272b293f914a872492f56d239b9f18f39a4faf2973fbb98420777db576d778751baf4f58445be926862d826c27523aaf904a647d9112cacb90cf34f43165edae6afff410a825ffd6a7ecce85d5e1960ced819f6e7d0fd7e3aac42c89038a2090bfc95a6b5872ddb2e7f9bf68b71b81c1a7651e225361f0fccc5308f2755b0a38b0c5ef2ed8c96b98abf2eba8e5b36510481f96c1824660b83f5c6eedff13464422fd8e72ecf5c344f42ff07e3661d91eeddec7f641ade2bb2f41d74a522d64ad0a085c6a6d8283d55e414f9cbafbde423728491098938c1c39f48924c3508a5e37bc5bc400b65960b521580f00e920d18a0a1c3cf2bda02f58edf01ec80eba2be54bf1ad9ff3bfc2e1234733c45ecdcc6381b250a879d768296c2ff31d40cded6c9a163e35184174df29cd41615bf3c8f0321fc73f8d7c05c5d93e062443c2ac470aaddcefed66906a31e6fa571c465a86a1982c1238efc183a547ad5db8c4027473cb12dcbe89abec70029a8518ce90f672dc7062e3e6f56b6c2ce681bb5e58e5df0c397e4b1c995ec18cf9f768b6e53261fa94f97715602bc0639822c4795d076bb957327b17de77ec7a2b5c0c2b87c6852d36f519009d72ea3ebbdb0aacbaf98a37103c3882dd44f1c2718a4274bfe7f9c9d1a2a34162adb6b43e2d5fa62d03b8e712f2b3f889112ff7371e686867c561c548b4de07715c6f9814f552cd6a0943c364a37eb0d75a4cb8b5e0c17f2345cbcc7325692eca54c085059665b05c0603cbf80063b1f3d9c8d03c990480f062e5bc8a7aac5371867f445580b10b1b558973d1edb05e8948515956854c9de6266a6bda5fca4a3f9959dd804efbd6215acb918a9cac9d21a4194fbc2dac38c821501597304ef0c0394841a67dc257abd43a6d4ad108c01eab5c7efdfecb03711ed5328b7534b32c621db671cc42d15fcb2cf1492bbcb03190c6b8716dd1033dd2bc8765dcd86fafd15b1077e20a4319462310e0799059b6faeec8bfdf6088b39f6105602f3a9f3eabb6d14b8a2cc31ab7bb4c1a6e799dbee7bf98b49217a6068675b0725cc0c1474542d27411b02ae17b1f8e28ff979473eba20cc82c727c979b2b5d78c1d51fdeff6e0e46d218251599c15899b10f3994c6cbca8e4302a324e1f8e69d48ef16c581a0de0f0463a27bc84e871d5374d6eec8b816629c258026a6f22fc52322e1e41208d9dccfbde60fb9a379e1adbdd36f57b88cee6042e07f9dd53006a1af5310fc5c099f886a50238c61e287df30bfc432ecad37fbb113b2eb9936fc6703f8d8e084dda90023b87685aa3dae550cdad3375040ecc19ec69c46fdf929e63e05367443f3bceeed21120389a1a2a79e853d52b06fa196b0153165a20d363ae4fb6a0a38cb6f655ed4b34420fc7cbff100ba8c9ed865f32c930f37b2c14c12ad42f166443658e79824c6e3dc8c4d6220c00d9bd0c8e22f7b7916654c2b1f528122f83cc1bbc3030b17e9c7a0e432f9e06672a6c48befc603d7122f2d9d19c2c786e95f1acaca592ed0f67e9fa77b08467ed8217481e1597e6852eed75310beb865244b1af0707955f2c5c06e46247c0cf6ffd6d7f80e41642b9999cf7fa218c698c6e968270c55e745e5ff4a20ec0331041fb5bbd68723443b1b9926469d79ad9948fbb0165ee1c22e0e430aa92d7af794b2f4cb32b24213b418c6b5e7c7b8fe84c22469b2fa0b9ea09bdb8bf7eebf4aad2ca1ec21549e84b44082c8ba12a502a21ae94769e296e78f9368cfdac80f0e4e9b13b7c94dba5015b6cfc24d4a91e1080ddea35b32a6cc4a4d395d0e2f35b602227dff9707a9df7a80c3ba3a1d8d58e27600491ea66180a6b08fe621c3816291c147a9a274a4b83da92f6fb83c0ab6d0ef1606744a9f2e6ae5d470f829c01ef189e61598489b0b30c93f16628537e1b36413530446a85451f8df9d0409936d219e8423bc2ff02ab4a1e78b027e90b50a339219cad82bd54158fa0b6870802182ec13a82d9f3d9761a546232b0a677d8aa29ee55dbd312e91a31c4980ac098a930fba0cdb397980b9725597e5205feb08d913c7e8e96ac3f4ae382de170bb6f6b1d9d8e37b5e31b8352d0117246f160fdb8847d91850de543ac84c4f3d80ddc9fad385f72579a7e9462eed90c63c12bb10a072f9750c7b187693c9f063bd81669edc183f3c9b0aa4ff016bf867938ca78fc556a5221329176783052443bb947581fdbbf41d3f6a1ec0c3447a277ae14efdbf69ad4551458d86c5cd9e633be72e94deaf7c504ec64132012dce4a6d1a6a0d917df4eee155a796196fa50c9dc080f6436300b383759ca67de734bc54772e59c592a0444754a999d2561b19cbf6280dfb1682b5061de17ffd4e150031a66fddf0ec9d23105e54222433dc2795a6496e2b2ace303cbdcd4eacdce7cc2a4b143d403f261664be4ee29b31a212cdd33d08cdb765abb0f2e58f5399533cb074b1ba4e7228b7e26f00c63f18023186dc727e32dff97518ded73690d3f17d6127428dc2797ad38de1c1b82c6634bf998a21d47642ef74977eb633398a380fc540282badab46360c2b40905b664ac10b5fd62d5f8905fa61b8b290ca74078d45dc70131d0b9c83461c76e063e28573131273feb7678788c2ebf3b2aa08159e3d36c2489a5f5a148ee8791ddaba2ca434b7dc79d77e36799d059d16513864b304d69cba3bf241e0b4103a1716261399bba4f372080256239626c3c689ae1d73e23bd9788a1ebb31edccc2919464915beb9e215abdebbadd28756454a96a1b5e144b601fc9659bb8841179bd7be25fe227bb2f06206f8f77b7cb8996d1a2c535d718ca504068884d644093975c5e9d02f12f260641aed9ffa27e3d510c09a28c4d463df9cbf9fd6204355ec35bbcedff2847b56f90042b5d2259538246635c55b5f442bf61af979fcdcfc44d46fa2c6fe72c6af2f344a2ad2384caf8802f0432524e9356132dac085630208035175841dbc01068a6c4d000f703507aea4c35a589a62352c53a00f46ccbf8ae59f30325899d7048489c5929682eb48e388a7674ac777aacc007204f0f3c7a8956a5c236ecda2ec2495b5b263aa78c0c687faa3e8da6a4a5d539fee7632923dab1959531672d2a70bcda08ba01efbba10d190508e4d9f883188da0bb6ed01320e2c02cae0cae375dd705349f302ca90a695a4866cd7eed58797f94a06fcdbc26d8832d88d89677a051253b6c1df6c882d2543ed550fd08f7c7434308aaa7da649ec05665294904fdd4578a258ba0a119d10a89b5da274e74f708a781f3e9e9c1e9b41b9f84717dd8992947074ab1dc2bfe8459ec340fa9e1165a77b51e6b3f0a19c34722fedf6237750948040fe126e9ff0d50f14439165463da56fe144345623434c56048baa1ae81e1a463682362ccf14faafd3e28762601f8a98a9fd3bec163c569ed79ebe4e5f4433a21dc6ed4bdd2c3ca6f90e773871a42c58dd94f66aa0fb69905e8eb6e08857e166814471add392270bc69aa57101136ca2e0121ee1ded488f01a8cd2180c11ed4159c06361315df7927e9fb20a5c815633a05f936aa627ef4e780a47a3fd2877bf81d323433f1a7233b6bb8262524c46273e897a3c8be3fb71214a9c208120177dffa0865fa29f2e347269fb02b1fd1da71680c58d06ba08db27ed90a7ae40d060824b8f55aef7118eea0c1497f168243c6c6ea4105d298a5e8ee016bf2af062da3dd102a65a9053a14dbc84a03570561b0418436f76d15d95470a43db5a8e50d15b2bc5420483671f727bd5f5fa9e68b9099dd1249425b3c5c358f7af19663cd9a020caf9662e6cc73b40709efdaa4bf911c470569b362a248e18020872a5c37d090a1a327a3f21961c2c605374c2a0de9be3f28da73d6736147f99213941f72a7b8e2a18ae6b43f13f29527bc38ebae74aab3d010a0d9171e7e6cba2786e7ed58eea2033610cb11652ed9ecad016745fc84c6e119006801abadcb6921746b28d5f3aedc41e94496bf61a971dfc1c95b6e7674f47f80e09a87985354a003fe3b650590aa8434b80772deacca8ee7a649ff22de2f288be7b51992373433f69a22afb7b8172212dba7e15f364a2ce03a3cbf90f03f689d21f727223fae286bdf7a8538ce8f18289cec07b6874b41e388229c967c777684515de8bfb37aedc884476fa87ab8283c1c8d9491d9a3cf1285a56280b5c01853fdd0ba6f9ef9c902f0e26fa9e92d6eb44b1c59bc7ab93caeef89a0330e0157dbbdcfa9254492d8c853a6a113df350ec459c35370b2a574591c81ed7ada50592515d459b29590cda9db15a2375289d9317e2b4ccf324bd522de794423f83ff50365a6409d2329d99bf1bbff745ffbaf6d21e60908acc987be61eeed9d887b8267a708de7d89d588bc5318025baffa7c255b2cc271ea8342a17c75a6a6f73feabe40f7ec7083289f86fac99922da8cbd066041083abfd57608ccdb2462e5b89cf9f3ee5a8162fb3e33cd8eaeb1f45f8494179035e03c77dad1f2389bfc708b64858e7688e802ae1425151e5a8bd1b7630c904323a599de93075d8632c2e7d9f41f476b68c09cf8845b574b5740ef46156f10891ad4cfce778a05ae16ff67f0dd7cb8d84a1d1deb7c0de2fca06b6789798c536e58afb836a4de6acf4b2bb7c8e5117169e7a1e2f142677b2d22e1fa47ee128a921ad95130a110c3af7c49f07082c85997d0a4fcb594426498399e8491f43117167320f3a8f766b844c3727d9c13ce33ac780354dc38a8646222ba0da930761a6882a814cf45bd6d3bf4ec937717cccb125a800b76c1937e60ada63ba4c7ca012c6e5e8fc538db572900b59e7bc97d98ed277f0a9bb34868d4f6d66b24f1e99bc6917a27b9cd7e5b341396592177f751b03ae239dc699af8f2d4a39ac42145877e91c1f0ee7a9c8d1f91c4ddf4f2e7e5e22e4c0c8eac460db60d03d1da136608014af44139ce112d06236131eb846e954db4f450d4aa7b6240a3b692eaed5bcb2e2684d57b2279343a45400be3220dfdb420e6bb9f0d6b88c774795695e70d7dc114964bfb079a37f65f386a915566cdf18515d5f56129ed2232862438d04c4b8593055edda2558c8676359c74b195ee36a2a7d968d2ff634d007cade3b81436ea3d74a99b77686e41b53be0cc466d6e14f04b4397dcc9bbc755564d3c3db754ace740571c0a6ffee3d7927ca72293901b8185d6561a59a014f56df9ed6138d67f442f6501128c37a9cd8883402ddcb8c07fa8b0b3c601ebf253b66705690580f5d50f2b13b4f0784bd3724093e367a4695d8e115e135dc880ed503219c1dc8b16283fd9003b4926fcc32f670414f235cd18906a9f454b69b2a6c58a180dce204121481bfe63a7b0b210b5604bb903fb965d1a04b8503c23d0a39260539f256ca409eb61f14a584f0310209ca3affa0b20967b3a66c4806e4414212e5eb0256c06818001273a441c20585c874baa65c0ac81d5b3041ca8beebf6f89e74f456e9a1113f415e87e0aa79a25fd861142152a8dca84eaf6408aa41491668c00c223f0d88cce2dc02248493e73fc419b8620e326a05f6e90cb76ae5f99b19cb9b9ac974d8b7a92ea570e7741eaad4450f17aa7f4622d9f519fe86a95dd0e8fffbbe7f079ec08997ba56ca2feb671ed8b5d45f687c65645977770ee4f901edb0edd0c6ad2cea1e2ad3b1ca9165698a0f4c88587520bdfea2055589378e95901ebc1d1fc1712572efbe107b3c14dfcc692da23007a1d85cea4b50bcdda5aa51700d6771cf505dd8e64c4f53b25a3cddd8db02dd4f01e044ee99b2e59a44c7d5aff464205144cf60b3a71b41b45edfc8eed786d32ee67cfb2e89f08c39045148cac6c990da90deeb9da4ddf1e4942e601e5d8db1b961777fa0bf78e8f750938ee2c16d60a3c3ed806cb7b0f54208f36cd210423def3094175955c8203f16a7bda8e64326ee227f4572f48729b1ccb9581916a43884b4b45cc7c68ad26550e668036d7546b91a08d22bb48df29e2c7a672f24812de604ac9a43da3aa66055c2b2f73871c3f019cb50e71a44c1d05b6daeb156f70af500e1d4620d384ca0fa792ff13bc234c4bb6a302ec48afdf088b6a3067ee7b4241a9b28db1af824a63e2f60a48c390acbb5e9fd49cf69a36a8b89d54029f9dafff1fb30aa177c4ee700b91adfb2942e9eeffcdcd2a63a5c2a876a5e7e147a43f3c91bcb10d3df9255af5f578cc35656122bffcc62146899406e62ddbfaa717166b45c8be6bed4b6cc08724f1d015bb28aa998f87b4720e6881082e6804d345109329d10166cbce231d83a8c05c28a45a5672f8cbe5a1b44430ebd69dafa9c4e4c270dee8b6b7eff3475ff808201c497c080c4edd0b37ef4d6b2b0017fb59909e3c42c79066b7a35e0597ba58039071f412537132bdf77a8cd0a0ef809bcf3074694ec62ff748cbe0d00109ff7bc022d6fbb47200c4a9c043a239e49e3d595745b5be26657dc5339b0071ef719984bee5fe80979c04c373981c2ea80086d0d5ec77f8c6a00a06cfc177582aa435ad1e019637cd06ff5ecdd19805abec100a577091185d9fe0ca51e354cdca922af45f050cb8df985e20e89146f55a161056c4c16d088dc35f06f992f626fe32fd9b3626879b9f8540cf1fb705fadd0339b12411d24f7ca897f7f481cf0c19f38e32868f61743fea76b61f7ba17c77f6fabef026688677496e81e908faee0788fcc5b44a04fc329e2041b53af9e5b121b1b3a3e164312e44fec6008ad12c2a6da802ee853f02051bc4a6e46b15e0927c41fc53a2203d306962f168837bf0a44bc0c5f0746ca2e5e2b98f866609019d6c91af5716349fd026d2e3298915c36bf59e5a5a23c0d3da19230951177775d7feecf074f07088d9cf7be0d4bed51d6cc4904117771242d2c55f2891f28fa054ab5292ab1d9a24ed2b60b2f96c9d07a4248235b7fee8dbdcaa507ee87b05051fa44b216ab7bccbb8a098d554a67bb1450298caefb3404f65489726253064e0424d6faa5338ed78f6ec97145ed035257fe0cc2f389148b326937a17952da36f41b33d3aa89607d8aae51e9ac31ac1b32b7bd8c78e5c925f886e2974d4895057cd0616a4c28426faa2ac7d01ae8e9f226b94bf253b035ff5d30a65a4986d91af8f60b696f945c5517daadd565c0b3a5b45b084848c5601c27086546c4a65949282c6e8c649fd0ebdedd12ddc3dac2fe15ad952dff272d08bb058c9181a11a1e2c45c2298a42f081926faf9a4acad2c0d38113568ef9a78858d9ed266c9eca6fbbf7dd04e5417ddb1aad1e2d875e509a4e9facc096366f65fcc37a366356458127d83863a24faa80e29343db0857c2014e59eb2fc1120b8cb4e09db332dc004bade517b2a575fe657ba564d6270e7c8dcda27d178c1c18395efac9255c37b122c3fdce9caee38c96c7bb6e57c2d70db3b1b353e144487deefdbc214f2e89032e30a96f410fe1dd22943d5e3c388158dd0deb57d0e29d350e53ab256b701ebbd838e37a97c51483a5d20a2204f037c0988208e1f8676780089da7ed0aa082f88d8871688f6292be3d2288dc178bc95058f94fc0fab84275e406c6be0ac0b62b7d2ae45ce461244f1f15aa16c20a0245d4b7ea442b1743930ccead6876d95eafb5440d5b56156a6888a91a0c59c7c2dcba734c2628c4d598e6479c013565e348ab79e33f816a0d551b1bd015aec35307382cf041e4a8350741f01a0345f88f554e5aa2daed7eaecbfed2fd319d8355e4a7e7155c15cd0aba187d79a8184a4f32ce9c81cad7c91d926859518903904e5fd5a4cbc7de8ea2d5f3c8cf0b7cf41cf92f18b4ed1e24e7d3261f14af7c2381b394e375a14f51ebef1c7b891121a6ffcc56d844add98ee6159691bf1816d1c45374e431a5be533c5df340fa0d95353bffc7094497ef1acb843b82aa2a6af5ca5839e3e8d877ece3289deab088fa3b6d26ed8d22d0674b45471762ebe3eb48e1071f1852ce09a84944e48987e0dabc8fac4febcfffa4a252da1e9c325cd47a53e2d21bf48ae8528a07b0a0921443e5acb83add1841675c2e6194d2885423a4a619b1270d5f00bf9159831592944fb256212982f8d4e8aac3a760e81955b0557be904f8af03ab1f3948e66ae88044244451c889b9271ea944d9c489063d57462c5b901174dff0aa29f4fb5dcf4cd5ed908c320ffea6bd88d31d5a2f1aeea17fce98936f10b7c21108d0be7a769afb7142b32525014ec2c504aa01bc44c185f29a73f29a04d86d99919099c7e757d210bb8b8c895c351b09c74b420a4a629b9f4917bdbc1b429a74094e65b4e61fd614a1e4f9a0bca3068c196d9173c96569c2e73035b9c2176f681d4a92c1c16cbc33104acc05436c3008fa641208c008fd61b1ba5c29d3b7954ee599f631978d30d6c74721e43460390844b0d50710c1de26a12a09072096be9f0e208fcd021db857aeb0b0f2b9c7b2ddd05b139076bda48248ed58822404e40e272c1ce2e5d18bb8e9b92870f911260c1928ef1796333f06e7c40cac95dfffd17ac00192cdc951befc597db677a1fdc36082c566c01133835863baa23f2990a71a4b997e2d144ee93b0a91875a59392d3c54b0e87b17b5de427b2d93464f3c283a65aa4a25952b9fc553b12ccd8e9172eb70e690f3fa222de3d28739dba43c3e79253b66887ac4f9f4a09aae9b62c7f309079c2b60ba11fb4c116461a0596b945cc7282d00caf7322efce75f9e757da271b9a5dd40a083bd185df84e635589690a595e5fd4772cd8fa313958aea8a28fdd0c2cd69a30d1876a01302735f2263ae5b905805fcc138afebe58c1542060aab4f8d3fad345d6c5b24ac37dbbf111101ec0814c3e56da82ac1561fd41ddc52f84443004bdf61c5ec21596ed059be17cfdf30b030cf31076558b91dcc6183e01e4eb299419df8745a03dc3468cd90dbfe2089f3d0759048710ecc987b94f2fa01b068f6004531902d3c4970abdcda0949a90c8deda60bc8de05f173924fb3f092f81e990825bd0c8e572972e1dc1874eb6889a1451afd3a4962b11a682b6a9f2f3abdff3e28e04e93eb90082c18ac882a8f08782b80d41284027bec42c9815f5793b32792ae6aeb2950b6f678820880322122cfcfb80d2004f0b10c364a8139e27dbe6a1c7ccc1e133468e92d70f372e0c2406c2448a8a79b7f03a58ea8911ec2445d4e48f049157f1f73760f1540e5ae1bb528df6e1b9c5722abce3037ab875b7016a6955f05fb08af0a02f493579b5662c9973115c8b8fef596d92a1ebda44957c35c4e5df1743f2f30e9fb81e67e6045cdc8776c3aec3c2f82399d96e853e1939004c684dd8840b6cba8120f40d7d87c8b4bdf597b730940652cd4ae2d9317a971c0fe8f007923c0d33f9e9b1613dea80236c6d3f8a0e2fa20478847c934d7b5d1cf8bf74001b368e6a5c33f22737e5f7f72c7664ed9783698b9d6b7efeab0bf226c74f0a71ac2c4b36452eef74426f1f013bae8e18ea99eb2a7d659aa1500cc8262050ee117d899952ddaf07a2a5481edc903b1e08193337c35fbfb1de16c9432cab419eb4439cd2d3a50b063dc0bccb370843b385d87e6f2da5c2428c996cc69344a89efd5eb61cb82083312d84076aa26078c3ed1fedf4b81689b023d4eac17a0f557192df61b6a53384cefc4c3397cdf2317dd6d9454dd6614c1e37c7567618e3835f55e7e3633b6feb348b8fe33c7dabd4968be894edee345e4f9522d4d18bfdedc77cbb5665d33f4905adf6be7d141662ce9286cb7da0daab97c76eb1e7fb9c84f06945188579d13a1e3194201d718bd890bf9599715309fef4e27e7179701de72ff442fb9a459b561c14be893d2c845f060870646d1587d3d2b08f0707b2908d8604cd925923a12d02993c9ce7e5f85839674ef2c986d6e654d3a762647f94c50f36da6765f4067e09874e8a877d823c308a44a04b0afe64dbe64bac3431e2f93192e2055a378e0bb80a4c001b21f7463cf5c95aad90e2228c8f908db455b40bd2d374cddf2f7a6599163dfbd5963fef1ded16002494e1ba3c9fed023897cde1519b0590413bbdb7660ca452ce9c13bf06a3a3008d9329714be932c5b84ac3c2601ec13ddc29aa0ef7c6f924cb40f84fc02f744ecdff09fdd86c119bbc678161dbe617c3fe4bd80e50ed6c36a463a20264f5a98537fc427a7c4c77c5402786d89473099ade2cee4388377f991b981b9041738c405e7ff58259d2c9a7123b403185aaa83eec41ab703a0d52b24d9cab539528a7267dd597c01d6e1146fd67473c5291f6e3de64306b15a258abe4137d8d44c013cd6888bb74ec5da31383c72dfbcdef3cbe36b655cbda74676f940bb9769f8b85656030813ceaa3489de2a8b8b5c7086549c566b55aec8d7d97055659c65abf2c8e0fc455695872702c8065c1236ed243eadf29a31d994b97bddae79a6f239f4f3145234d33e68768bd4606b09a31e7ea835e7451caeec77492201ceb6589bf13e0243eb8f3f8f4a90962ce5ce189c50f473e4545b91f9570e2ee1508991ad920b9a3b6db2322e5dd787a7325b67ab849ccf4aca3fa26a124f9dcea59c4649608e12e378c2e1981d449143bbc2c06ce1d4ce53deade73b4082df58cbe0a198a0aa39efe57956b2d1b9f66aa6ee4f04d2c590c7b4e4a3f1ac8f0505b1c994add644de618eb18ec5a5376e91a4c4bdf3a8e19187b66483fe63859d4637d304635db9597609a2437f28519ca49c1145c4986a99f55d8f1261d9495d48485c2c30c97e2a4652e6e0954c46660f6804e8f0471baeca2eec71e63e93000a1764c4a3c040b9f39c82294b801d56287a8f843b5cf4bfbc2b70fca2e7a00567ce9608e6767cfa782db60905d581f3ce447f77cd596ce2017d18114319e8358a425134e77afe9186286073d779322082f633f420bd866873d4fe9eb19d4215d1bf119b50e65b67aeabe49ed29dc05a2fed0890c042a42f561dcf4b893da44b3576221b0ef2aa911038068c3565ce303ce94c7111739c42ea431a2a529cd4df23d7ecfa69bf6fda218be80f05b26fd00d13bd12f2d4897e3df06431ceaa56335c68d02bf7de39c24b052f67c7240c126983d6c254f9ced702b7f02418e651e5012189557226a627ed16a899341ab2381529de574a16bf6aa1f5b167899b39310eaae45264dc0d77461f20f093bd455c182a1d70532f64990da3dc0b9e7683597981336e9006c930f5b615a08fc3543ec03205691c06ae086c8100c60e08a526e102105a48f7f69b7b2fd30731d128ed2225a3ae58e5bb638e8949580d3406a8f2a5aea5910c32514cad377c593c69be8314eafdd311cf6a0f525f12cf41ecc94ec3e70db21c5bf23a4f817caf08edbebcc6c6c16b5015040fb39cddfa5847683e3f723d680922e7b1f024c4580edcc4f89a75bfd4d0c11e08d9a0953b519bebf16688dcb29ac1561e6cc1507247e7acaa1c9d91be395b269f03b906839cec1945977a834482b26c3705fafe3498d8a50f1055027f60c89b5b3649c5dd074faa4d6dd197a6564b6055b72f3c842d28b881e1fc76ac3b6c11e9e51ba04592ffca86b3eef2ec0bd23bfa1307bd5e844c45ede73a65fb87db96a0ae05ef363eb5d661ce9d027bc5e89457a66bd457250e9492e4e86fb89d0f137de0556179c203a24ec0e19f1156a578829744350c87a582cea4cd2c0e778ff44bce112cd17c49edb7381804427371f0d2492d69aa39a1ed431fd41addad86fd70ecf6b7c2f20744bbc2f9896620883bec68494b8b6eede4e8cd338b31fa907949e8d5d02205b7860800ac3c5904bbccfba9d19ce7eed9ff64abbfe17285350eb7fbba4dc67555c41ccdb5dc9941eec72e78108996212225186283350e15798f4b06651461520c7123c86853fa229b448d16808820d972d0362240b8e9b23a36bb1422f1d024670b40f2e526b472d958b943452053ce94346cb79a5dc368666f669d5becad046e516ff20ee1599939840fe6beab9db66d5a3bf3318f3d363cbb6a70ab7ed5ac232330f0cf992cd75ddd82991540e4d01634f70853192f90ece149425a863adecc4c683253ee7693310c5f12111f0b7f164f0be013f172ff1f87811f15cce4d4317098f99abf5c5a228c56ecd6fa580a50b857ebf7fc28d2cf6d77457597f1480014457d484e270cee270572d7e69f939f94dc7fcc87e19c777d08800ce611f7d840ccdcc0594877179e368e90425783cb91c950eb31a5deaa245f663c59e0dd77dc0b3cd763920f66d6c5cf332a25cdbd41d87b150f1d12686c5100129f0d0ccf3649d9a81064c2ad714bd56865b8b3490b46aa6e116d08de3d2059d07c5071e7bc0f2eac3dbf9ca595a8030304ccb64ac907733a5e7899e8f090443af9af1db5bb241d2b39010727b06799cbfc17999ca786acc443eb331e983fb1528ad7b1389834374973be1015cc08199e3f68c8fd9bb00f767fede5cabd9883babce5c4e7f571b597cbf0fdd33fc6a2ba542c6dcb3b66a62c2c6fd08af88601cab16217314f348976228a1e014255ea46c9e85dacf9e3376322315524d8bf65c706b34b975dd723dedc7f331f5610e0190d34f3777961f3d89ff2e46f8a4ae545bc059e8659daf1fc5c18108c60153e832a19c3ff96fc212d95fb68ac759154fcb43610a5eafb51da4776ab83fc0710db5b0212b90d61f5881864e9f24a68298c4eae2aa9f69b18300380070210aebbccaf89b73f61e0cb3d397a895c1773d7941680790bd1239d6e4151bc0c5b97100031b817b3f3930ef444cde816208eef6276e5c5eb3dbe779f534e0e04f5e1f422af9a9258876e952a9fac954c20185c3f4bbe396d195b18915080254509e680fd59d94320a99b941a6251c1fe8c22ad9cd293c3e72e8333e24b8217af7da1100cab8c0acf22328c3fa57292c43bfcef2b29f2a62ec4ad3e913d51c4e62f3eb9dc518c8b2af7b8787a2a8106d18a736a1192cb268e984d4c511f79ae2d187f886205f1e72a3f6bdedb7c7f41e5eccdb40495838777aa465034d1435164713b38f36644ef23ab828d6bbe9da6c8e46f70b07a1c17fb2d61de2efdde9e43a72ec7eca7f224f529c7755433f04c9a06f5a8aa699bc45addc4dd71926958d590eacdd950306e0f6f6503901ea31b3de1b0bd7e2c853b52c1afce2250590f32b3da70bcd162d2bdad0f0d8e46ce150feb300ac9f8d2b7d2d60d0fa3e5686a8cab9ba8e8502328df1ecaed17f1d2f50af5b29425d50e17919fd552f53cdb9d2abf068c43575c6c1e12a158160596cb7fa1b2627fc95fe9a323e320dab5306ae4d035e01cae3b4ddb9a217c59d479066e8f5791f084e17798753f498bca161f48bb937cbe2443366424360b6e928ecbe99b8657ad86af7533adbd5713f682088e072de72327ae650c2cd6efac5871d61057e89174b381de9168faff3e59b4bd4a61a7874b33a091f68d12bb24fd17ec840a867f6a8b7e96244446b44fed1030621b4c38ec4fd05ba35720f797f37227b6de1e7b60528409c16e98bf945f64745a301a363e94416c34a2e710e6efff7c7cd67541c7bcc36adf699de0198bd9121e3a366cf410d8890d4859013712f4096d2b4fbf44bef516771befa0bcd1811dd2f3f751ece82be2dc8edb3d70852cc61c77c269daa1970c949bd85872b2d8bbcdf61e70a74ff450c6accb50e17c2cab8bcdba8e03cf207a69f11df6b4bf34d2156d8b521e6229f0cd73737aaa97eecfc1d98a9cdf91b7cdfed8bb3264d2e47853d3834b278b20b8809b10002d3620b835a4453024d4fdd6bd1b3d1b34f694f6bf120e699460e96409d75332057646e51daddba53b8a4a3facc4f705470774815bd2fb95d86e290c59904d36d0cef24c3ebe4acfc57a4a6fce3347ff9b49fa7a69db3beddcbdd6ce3ae3afaf3bb01f271dd3d5ac859717f0e5988468aecdfbb11f3a106452661400d659d4cf33e9fc89110e353c4b7ed30dea4465b02b7c2ac819f182ab3f19cc49802c384358f68580c8f76d2159684957f4f180d82e1c89ff43b046f6cb860937c8fc7be58cbd62feae57522ce6d94351617bfa5da6aa0327678bf81e7bd9896f14f1b9bdcab41776a94b4ba32087b5293fd26f06daabf2e9ea3d6e7a33ffff255b974ca955b1ab10d9be90b6816f18f8c92e747e7b3fa22641b23dd437ae9a3c7c995391eafd3cb1e5794f6386d94e94011653bb6c298638c7eeccc8b95c538d7a8f6319d592c06272cd6d121a44e5b21a7678b10e6955d2f5746d74f8a60b0d494f76ebee87bff88febd74c801cc06ba3b88be316cc0a7f2e88c322ca6ca3bb49520cb4f94fef7a41f346ec7f83ee375300a01ba81ef6f8da14f06d9ff4f8c4a143a16d727d4c0219d3c7692901963caea4f9ddf39a208b5bff660507f366a3963eb97f5e3138cbe017350e59e58f936548204a8afaacb8843459263f34162a260d722ea833283651309a6720c233c88ba333122cf449dc50a45a20181504701070511393c7195be9be487ac0fb3f384b09f2e490f9677277c55264742d509551b39130c7f661bc1dfe211660dc6cb3347c125c4c73d244af5747ffa761ca50241b8816b2c3f589a93bf2139f0b4ecf8f5e5fff1ff00d8c639db58a507beab1ca3351af3a16a20ea3352b1682535decba9a19e3a26f4b76084b51505c365d45722dff850de8083217aafc1cb578cec3624eba28f881841a506969bc7d07113719efd91bccda70716ec9e98ef01401d2a37c5978eb4c38898922b9f9dfe597084f2f53951afd0c5ca1a9233150e5abb17dafc4f83b68f82f3838464661f6603c16e1f1386452f92e0ffddc19a31aae1ffc6502a00af83a722fd40da46f9461e048604b20ea8b5443e8618ca2304d010bd1f02a12806917ffa9c07c3150542525b63a9564afa37636019c021ec6de2966645dd6250e6ff803958ece0ff7d39bd6841b5eca6e69bb1ec22c753d7f43f00a3726441a21d9597cd9220a972992ab2851e732bd99fa375259d1261d76254b471a7d17b9a449044e39943848d8c153ecfa476f1d59d362eaf30cc3bd5e988f26a2a345274e55123e2f596f4648a129f78ce14bf68d411fc981cc983a89d418cc98fe777a91fb50f2d9be48de5eb7095981070f6b891180a363d61abefdef8812cf89009631a8b452bf07806353993a478ee078ab879a40b8abc652bdbcb2ff267121f946d24298e94a09d17925fe10879a2bc7e16ae62748c069722a0aac10854370f24fbaeb551e7c10829c96dffefacafa2a62846a663eb8faa5ff021aba4ed44e1fa4608cb77ad705625f2edcf335dd8c56b8cfaa10349404480ed1079e6c7030f958162a51877bac2bd8bbe7d09d9dcd27870ea8e6972fccc15d24aef238d04dc059fabafba265c5964fc83040188cf369809d1566ee96844911ceba812f6632d3a281253f988ec0505d58d537694efa8ab382d724d84d3799e3904fe81856e84f4288eb7a073f13f4d53afaa68a3005dfa4af55139adaec4729e563d8b6dbdbc105f099ae744c357961af17c0db3110b92419db1446e680ff6e43617e8e00f96aa845a8f9b72befdd67bb2646962f259b167cbe8c07f16022a662c5aba965fbd614b2810aed808e300cb8bc01a8a5797b9871f2fa7a0606db9016e12d538724881bb0ccd0ff3e10aaf741e9bfd18920612ba8ac4e0061a2cfe0787b6f4068059cef5d8cb55833b1a84571509ad2db7856348e8f8313324ae21e5aeff9dd49a3a968f468dc9538767be9aa4b1863548a95ada2579a73846a1e7303c231076e260447b8ddb1033b4c1545ea89dc3b7c858446168db07f5e2c0b88eb7186e3882aac607d562358328b50b3d64b2e12bbe9fbe7a381a307ecea3db8dc6b1d8e458e27b553dea31b1d3c60c1d649db94d38e1c87f1480ae5a9b0b7680d461f97a5a79baaa040de33dbbe5c23405f33715a18e1ce72b08b7d293542cefd5a38e45cb13343a98cdc5e76c1c3070445b828677744d4a08fbb160e0bcf76cfcea02dbb7345d3f92df42bfc61f55807bc006a307ca878a8c7c26c8f4df28379e2b1da71930dd24ab8eae5a762546397250f8bfb222a971fe17fb117cd425e78a3426d89b827bc16ae9b0caf4b6ecc03bb9e17a6df10ce275e59fe315d2c352a7886cb638d2ee6be479f2e6f20afc0199ceb8291e277d29aa9a13860cc84be8bd5aadc62417e7050d5bf9ed84fca73f7c2392481779c497d1227348648940a6754ff0969a679cf884c821a30182510f389a1359625f4fbc1b4ea876f90ffeff23fded7c2e20f88b3e52d906013da72aca0e61bd532224c3a65550c2434ed501bf26de889784b52e232ee716ef565782fb7e9f2008207fc0d4d90501345805215830855782ad185d27698fb02694ecde8c18dadf6c3439c101195de81ff633668cf91d764eb9fc7af00dd284fb94e3b1be92fc96d7ec41110362ddfc88f5560d2f91940919285cbf4372146e1567e5adc493d64a076cc92f2366ec4633a4350fa76ff7bbee20bb2d333cb8813807a5ccb76f1a6acc20aea104e651d403162f448017edf9b8348ec412522ecaf9400ac16d840790a5411cfdb977f871161d27e4243000cee87614e076040bb0422301ecdcf24eb9eaa5ab043af63469c5e1a76603ea9623ffee0df86ad5daf9776435798bb94aad2a89f0a1d431a2262691790fcc717fc8c3930adc820ed65f7d30f77c30f098d3b68fb2bdedfc0bb4d79bf611bfba7a4c9698b303088d5fe58dc54afd5a914964e974ba9514f9cfccf2e54de0c05fb4bf890e55bb34c1932995d923e2845e04381f3fe65ef5ae6b8764ede8a923f3ae4328c37256c2168876330669c626e10135864d237c3cb317e4273901cd4c8b8a765fc7b4c46b7e522d4a7cdec9194a21e3a7473e8f84dfe228b4490b9cb725ded47e985c4b80fd6d299f3d0832efca7308a14e682e46c0ac9acf6073d516968814522e1dbcb3685c50e5aa69153ad1d88763d6128ad44de019714c90fbd246ac8e8ed2336eb13d3ba61cc5de202dac63684bce669b073a7395085bca61cd7c2d881ea933136efbee219ee2ca20d5ced503b3e0961cde624313e23f5644c16fd582ddf7e3b169db5b97274f3b59ae2ecbca190f8c7d90540058079c859237d5c4a2f139c24e4886598fa9cf4cb9f6df3194ed6467718ca346bccd2783679ac79e110351ee04b429a5c07d06251f1f9be41311b51d39d8b532fafae0790c5a5ac6011aed06ed3c68cc598f0ef439b34e9c84d45c923d269f22816289554d350c0a0c9f6e38a0a0eb8492981b0b3f08c311eb2f395ad1f419f8837ba97eb1d664e2f0f8f6c1487a2d998ecfe6cdb25a1e2b7496177ccd8c706f65e5140bd3c9ee32557ba7ca3819f2b1b28dadad6e2a634858667338db045ec66144c6487199fad901c2ac8b1f10cfcd292f6314ec7ce98192b44233e16cc14818ba0cbfb70f6fb4b2293a0c57d69db494609e4b3cc26d3599055250d7db2c366a058a3912786df6f4ebd5f66a9a0be1c6888ee03b219dd41510041269a23289136ba819b19445295f5af7f85a4b9879d5587517b16caf66dc622d52295527801670d91e29cf77384b170e96f44ca70dcc0a49441372495f4099b0036b77d553ac0a4df8bb3125cae57e95c131d4d07c746f0ec0c7a744d881ec94f3d0453ea1a0557235234efb605785656c2e584dc4d4ac2188974b363a33271330140de601a97c9ee923928335777d5d6fe45cce10cdc6eccb3babfbbd17d2ae4d1dae227420a31d1d0743af5f7e8cfea6cdb4596e80df564ecc94eb8d7d7d01d1cd178b97adb97d35ecb2ff6a314a2dcce94740fab1bfcd1cba38573420e30231068251b29738dde480dd8745c08130b3863957788f74a46e9dac12d5bcc1513c8d48ea38888e7c73bdc7abab03d786a0a2d16608fa3d617c665989645a2c54cb8157d8def0c14709fbb06cfe41867746572dfd299b3f2500a825bdc519cac44f8992630deaabf8cf31367c8c8428c1cd935fc2b64f8c021fdd41f730a3b3f2b39976e99e4edffa1124c267bbabf5540b4c116adbf290e10f29d6304368a8fd1cb4c1953982a374b12a23f072cd843a01e6bd4b8609207cdb5c381b9230298d017ddfec228a310bd23b9d0fc37f0aa07b57b9f245d9008741eed2caaf711dc444e8b1999de4b136cc862dedeeb63082ae27a32fc27e55b447000300565f188fd9475789dbb906c1a7abe63b12b593388330016900d8caa5ea84d3724095f0de600ffeee1482a6b0558be5798ae260ec2af075eb07a7ebd7abcb31bc8ec4246bcebba0c6343e5e7fedf02e7f9f5ca2bd2eea855e9fbd3d7a532faaca01970224731d9b57caae0e3b3a8d12e16e8b179061791a3cc69bae0c42740afb4049d814044281667c018b31e67936027ae44778c6c7d7b37099f26564b24d431a0cdb2bee3f0c710ddef073cca22568448180df0ea75a9ad252a55514186b99493fa2e17f50788462afa18a8906dc301b8f04c2cc71e230b005fa7e89c5d43a3247086c4598824b3035845943bdbecade8737e63302286355d8513a515941496a32858f7df232260720486d622354c3d9433a407c9e1ae52d116721d0f6c7980b186496b0dcef0c559563f4c981cf157ccccd965648c850468c8d12d6704ce11dfac36d8592002ed3ad68a9aa1eefa0130f5784edb28ad93553e32417e171c42d50714c89d20aee0759c2c7ce483fc3d002de3cba8250e1107c364f0bd94f8458d22ec04e4f144b3601a10bef98518abd98b5399f5237140dc93792f4e5fdd2942cca545e8d516856a6fdb618e418d35b00e8048207438b5ea8905bbc0ce483e622f9edc1db9d9dae936ffb0364ac6c399ad6862bbe9a519d54967f0b4886b889dbc5a82929ef3862f5b824df31e0328a219bf806d7fb9337d4a6bc0c886539667d4ae38ffaebcfe540a773d702fde8e228f5a50a6a4e925a44e958f4bde14f25b5063be0bb36f6125401da006c67062a8134b70063047bfad1e366d5ce8e32f9ba462df156c58699c80101731d86bd8763ef5642de9faa7fcf7dec41c13fd0824ebc8c770125eb65fca93ebeb04dd7688951430d324627a2dc3edeff189a96c38c1b92805c00969d9888ade9034f183379044197258d4de257393ce9541a0986892ae45185f98b68954c8542e202ca9eb3279762c5ac00e6e7883d7a29191ac33be0de26840fb23e97686cba62fc312b4cbd16ec0547038c09debd33148a1d3c6fb8741535734a160ca715e4cd555ec39101ee7aaff4bd23a29da06fa8918f8ebee42a486c0422391480697d7092d3fb2c66ac9daa2b6e035164bbf29e407f815619e59e39a872e873f170434943c8046ac0e4bf30fbd3b14270ef2a0638599918a92e93d31fb1802e4b334a78c44067f00028a453863959c000ba091704ab7bd07fa7b1e0a0e84020902e8244fe560e2999c7a7920146972a9822c8d4cecc723e7445961b95be49e2b3cf7d0fa6b0875c3b6c8d57b9c156d65657cc9018d9c9ec286aba18ce26b4fd9f5a2cccc25e6bbf53a2b955f59fba3f7905ecd773a5bfbeced8dfc2a4ad93aa970574a412298568dc20fdfbda2ad9f5f2753d4f8988c2c96e1eeaccba564a8778e8ea4383e92b6682559adf0fe794e56900180e71add691c9202b7e6a7682b136e2af47096de09b4bc1f13ba46b502856f0709967e4c9c62ad88fe23aba10e6487a9f1f96749afa0c1f0a062a90cab54b095fd3b1d16583ee147419dc07d5534e5afdd33940a82a766ab14a2c025b73b60a44628c2d50822f82fe7556e7b2da8c248339db895c94378c0d96edafaa947e671ff9dcc2e8de25a572194ed52867c87975feae97e2fc661961487a0ec690dc0dcbe99a913ed6528a42e5717daa4f13b9611a1f1fdfcb822a5bb48e762ac7514934d95a3502c2a0874f9fd591266989b4d69e25ad8b852c8676d558200f49ed1b5cfba1cf9c31cb6db723e2e710d676a1a2c59be3a20a52ffb223199bb6764d60f1636f61fe02e7f2a43f0dff6fc5943172d37a8dc23bb5b7e93f50dbcb7509775086bf0284ac82f3911461d95de7e714e42b11dff3f6e4a28861360bc4d87d66163945de8c24a8e216568e3de8ca52c238a38b73a70442d9d8aad6887088f1598c10193e9c82cd59c3b5d019c661a5f02f17af6bf9400f256b63a75938309c130d19529a0710bb8669ca97ca7bb37d7bcfb58da7d5132f357650dc080e1b3eeed302aa31ad3ad52e5de796314ccba0cc3bf64b0d2fd9d7c618a4649b5da32dc6475f104f7a3e29fca4a2b826000ddecea892dce7c0a622853685da24ceb02bf168d49b428bf04564f48140b663cc4811889f6cd1fadd168402893f0fe6ef19d284246f207a5531f853c52b15b586033cf7d1cf8b887e6515589a474ae1f586531c171c9dabc621b3982a0193f5447bf1aa4c37b6f9f8fc95a0ea398404a5a46eec60f41017a66169200592d8ccc09ee7e0d20f5a75ce512c82fa6b8f8744085b7e1460c106786558479b7feca42c15e9c3a3abfcfd09d3d9b4ae2ac0f7f4cc79fe92264fdb1f0aab30e36543240e3667f26458e040868b1c6d39e6b94165ef54be9e94d8363e1f568ccda2470a71782ef5e0a8339b09ddbb11f3638d6abee0a141f9f3b9037aa9c091d92786e03aaffbd06c942e2b821fbd66c5633a4fe12dca109dd0cb41493623a306524201669499987b4f60fc9048be134303c72c50d9232a34d74ac405465d80c284da6edc0a28e2f1d9f411409d3f6c59c737b95129f4919d0d162da9cebf4815b43d0cb00809b43b7f8840c386f49da831741541a96bc88aac92064651e0657b6f522ea69c05e4d79018789fb41556515eec0652ed08936a51ced6b3d41e009e85f48b4c52d91467e9134845c95e8cad5c21e58dd35c106a573fb8a66e5bb8cf7a87642848bdbc9c6eb5ac03016ce456946dc4d478fe41730ba63608d00237b9a19cb0b938c9d368d80fd76b1dbe26e2dadb9e03e3c9ccfc7b447f16ed7d128ff3eecdc61c6805046176f193e86a5d673ab32f8f0df63ec4869d20400d42f10776eb7d23200578cb9fed14749c0a369c8cad33bbbcca68e88b17085efd69e2e551c5770aa3ca79c64a407028d11c929dcfe92a381c9b3da5125049b345e28c639858e594eba9a9068c73c1d592b3ecb1b5be8ca798966000363afc1832a9f1fc8ae7493ef7ce2093b6c488195f5840d6db72b75836764f721e4f05658b2c37d287b03458e4f819fe453468a690eb089c084039dce0a283d8e7f87cbc9b9764095f567023371f2bb4aaf931bc1e8fb40f3cd7da33756baba8a0b32840c212e7c8622eb2e1632db7f1c10c8f4f5831b91cf21ff5e24555cd764132c41ddb917e663a965a150bd9f6db24ee50bd3dfe19b77551462a49b1dbfcc745c79a96aa164a15e566d09bc640644ca5f5b666c069898c752f963b3d0d2ab3cd7ea15529bdae91a99497bcaf51f0367f9a1ad0328617540626ad1b25abd05c6bf8e7fd9ef907f6d7081d5c03cf4ce01fa54714a5c300acae21cb5fab6d470842210446877f33379a5ea36959aeeb543b0ec22af8907b4b84dada6bbcde19c780e8fc7f978b9fef1fafe3d8ccb455196124bda32c4384876a91b801c27663faebdd18559ccd077146788b426bbb79449cac00485049504ba3fc7c03f61bf96674e1c2c2d5b2c0cc1ed9fd553a03c5e2ffaeac7e50857bc599cee2277fd700ef7829a4f4dfda282fc7594527fcc7f1db918c19b376e3abffdd91ca10d0cbc79f306d835ff09d0ba7642a7891d9d3673b10f5d04279db82197df9edbeac4c1606305c22ed6c65ac638cd892b1af9af23c7268db102b98b9d3fa73d0ee4353a917e44c0f41186e02295397111f4e7996356e789dc4315df8fc11aa205ac2f5851583f2c1c9c60f5b21358297c12ac20f90d168f5f73d5e673b84a7325068bb9ba7295c31bb95a72d57455420e73a5a388d51265acd8580961d58395965762a5835513ddc4aa1784d5112b206dac72790355486022aaaab012555c5e4c1515cca66a498ea18aa77954a110a44a47f504951a2a292a2a49a872f82e5430642daa22180c15092fe50c1251ce98693af3c3992c2150213342504a851e553a42d8c1e6278449131b844842871825d29ca0a4052d1f1227c4d0f04493134cc6683122cc9db172a649cffd6639e31353729cf912396ee312e34c1c1c6ee2701377d25596dc5592247f912fdc4951bcdfe22871c6c27805b30313b43de4e99013f36f7f3831feacc3accff145527113e74c75ca71a7f8acc3ed7a99214e8e8a90f368f453ec0314b4be905ef12c4abde2f44a3e80cbe48b47c25d4ecc31d227e15ab9be128c9b36232b63b4339994a81e2e1b07a07c6068686868e88156c64246f862188662188645341a8d462b92b15881d33ce71c2e4990f38cd2ec8004e6046d9f518e52685c3dc10435946a28856186916164181946869195f2032d6610e47c63dc1837c68d71635c08eeac95edd3466981051760ae2f80758199d912cc983163c60c0988861e48693f4179a306054214940510c0004e1b268d6ba30218d5892d2833b880004357e8c4074861541a63a06fe81bfa86bea16f680cf4d0032d46fe1a94380161187270efd53ceb129779288ae22af630ebef79bd7a4d40a95dec08790eca0e4c1a0a28ef0270119094139b39e71ee61c66b283721cb98befa591d5293d2db79a8fab2bebd2a591930c28f7ea1a7907e3c85d9819e85c8ec81cf9d0c89e79a63df0e0010f2021a0b58640be108041460341ac138275fa07ec4b9816f93034720497b3688c52eab085381bdb52e79cf38bf768e9e972efbd65ce39678d53f29496c3b5847dd1fc08ada3c2605cce1d24b0aa962be1529a8e8a20145c05ac8a5d4b3d2474e2b01c56354669c728fa4a8d518480c6282912a523f8ee24fa220e8387171f60f47582df871442314c081c0214c77297fb6b2e68341870bb42175caf1749963794265054e18823a6be8c19336607060c183060c0e4f43c5bce7da424a4bab447ca231d21654a77a422a43ad21cfa06ed00fa04ea0675026d026d83065126d0255025d0245024d023500ea05ba8116811281b740daa064d836aa159281a140bbd7ae289d235860913e697b30ed3f36c391f01ad42a9d033e8144a046a061d022d830a819241c7a052a8183408340c0a060502fd82fe8002512fa80f6817940bda03ba01740b1a856a41b3a03ca058d02ba815b40afa43a97cf952bab01ec5a43d848ca4e7d972ee44872952a24081cae1090e4e9adc6083af061a982c99410625bda724680ca8130a03fa02ea02ca4391a02da02ca02ba02aa029a047f08bbca8bdec599ee7c99e2dd4092407dfd062d6e2027b851f9f6c53397fb1020a54fde313b497f8662ff28a62868c3486d3d63b817e80f04fcfb6bf8663ac50da3b6c424a633893cabce6ec3e89fd253fcbf564384311ff0fb3ff3b77562c8a5cebd70feae987cbf82b0525fca9ed67fa24ddcfd495e9779f343a69cde2c2232fd7cfcac2a4c16324147510cbc4043622d168619358ab9934425e336b689ff57bba6fff81fc4ce6bb22865a75b5bb4b8489a82bce6bd961ebd30f4ee239de12e24ae50ac4e24599fa3861f8ac4c3abed4c362f6734f419cc748906b5cf23695edc1da930725c5286398349e548c657c9957ddf7faa4aa780a5b9dcf04001c4199fa88a07ca6ad431979cdbf3ce302942a0324b21ff52d0c672f53a71373b3c6ab8798b3b072faf0ae06ca00c0920c094a32af629a673e9fc4d23043f6734948416bb8c324054368788d7a8276567c89f2dac249cde79365c9dab4b95627e380ec081912b224644ac8969031210bcad8c88a901921db9269c9d2c8d4c8d6c8b2645bb227646ffa73591bd9962c28cb9a9204d5eea0887649433f5905655e67dd2989c742b15b58202bc55661adb057582c2c0f6c16560b1bc5eac0eec052617f2c14360a2b859dc242b13fb60beba53f67b7b03f368a855a82d463a77ab22924d98eb5a02482b4fb76a7ead4232a4feda942d413d4212a111505b5886a446d524b504d5083a83b9504d587fa4305a2ead49dca828aa43f578fa83bb549d5adf0a249f535b1be17eb94aaeb2f0b5a5f2acffead7ed6cffaadf593f85fc64c2f706e8b6bbee6ba85d926956db65e9e7dcd06f5eddf9a4d8a6af37575fd203deeecb94dcaadfd8e6b83fa20f796f2e76f4209407ef82041d7c3d67b1ef7668ec0b443041df7b6c361bc67eb7522453e003e3cdc254aa238680e8a023d511d34455114d5eabfd51f0df6e73d8d9e41a9d02ad40abd42b15034fd799ad59f677408d40c3aa2b0151d8392418540cb5ca2e7b8a7e8a98322a71c3939729c1145d9a046f4e7a8d67df9aca88abe5814632047589753734740befe214a000cf6fa04c4b54e549ebd0ed447e9fc8c549f1c4620e8ec1b655ed90e5b9f82d49ea002c0d6a71fdeb39abd6c3699bc3c7b7e644ee0ea4f41ecfff2ccc6c4c4c579f0252622c671f481e9d3f1e1cca6fedc00655ef90a332ac3d5d569668da0ae69b4a222171a6c50e6d5a7c3d627d5a7474d008d56a40092462b2a2a32693c1394d7bd0b9d2d0c68006c7d02e23d188881d6d772df0b8ca77d87ebf54997a6e04139931c69cffcbddc9b8e0ff77676b93714dfc36fe0b7dc5b19756fa6967bcbf23cdc1b0fffe77fe5addcdb0e7ffe55ee0d766f1b8c0fc77d1001f0e1cf49752bd8472d7ca63f2fa9fa73b2aa3fc7b1eacf73aefa731468fa739dac1f8718cd8c445ca271aaff3a8e81ad23999d7514622c73897450a8fc54790e8ecacd9254d971ca6764331ad19f8f5a978f3e63d6287589c4ee5a335871e1af635e84c18f990b5f6b38e55beceb1f8e0e28f3eaf2a0ccebeee1aa1f10ee59bbcfcd93c958e8508b5c6b31f409772b56080909c572cfa4c289fb1c8def093971ff03fe0f4b148ed0f7bd0c6c0c20457952c06ad86a1ff4d87ee84fa0053444980032e1bb593db9345593aa497da46a52355a67aa75a63e4ed79354eb4cd3a4a9d629e3595c0e9daf0eff5e2e8a5c461e4d1a9787857b7319a99e264abacf0583e4ab7472722a9d9c9c9c78bc2099957574a43f3f6acacabb0c15249d78b5325892bcdccbb3f7417d3be517c5253765217908506a476e6dd95ce8cf6dbc2d1e8f942a23e5c4e3f178e4cf4dce65a660704fe1f57c506e82bc20d973e3e305836e829b97773cb2e773c30b067bc8cb44f28e9777afa053cf47b2cf2b5d4ebcd00d1924cb201974ea2de1e51d2f0f114337c1cf227fcd67059d48f679aed14dd08947feee89fcce89c773e2edf26c055e6f496ee2f1782fb28593249faf856ab7b4c056aaadd25a69afb4585a1eda2cad9636aad5a1dda1a5d2fe5a286d94564a3ba5856a7f6d97d64b7fde6e697f6d540b350509aa9d82229a429a3921f178315dec488c17ebc584c44e880d891189a1102b1233126b8a951033211624b68b9110f311fb110312d3c57631166248faf3d891d82ed614d32d79d18bf97aadef856c2aa67b110c3a79e29352b5d42d35a89e51a9d42ad54abd52b154346a963a846a4625429d52c7a8645421d432aa943aa5b2518de8cf552d754acd52a58e8272b1dc91940564ee655f7692a1f2af5c3396bc3bca505f13f194afb97ef716669b8f2d98675f53d27dfb35a51f99a794abe1ce947edcdac37095745fabe1de521aee0dc6e4de747e8697e195dc9b89f314ccb60f26b9b79c189c6078c1857b7bf1ee8d0c069d3cf1a5bd144a30fda551419cd486fedc4c6fe8cfcfb4497f8e2275d29fefa438f4e73ae993fe1c96e69042a54c521a2e510da9924b24433a43ba4407b68302c73c535faa434aa53f4fa194a92f852ad9b46704041aa5bd0b0840ee34ede872d3ce8e46dbe1a5bc1d1d06e40d3298b56594779988114e6e2797550092565454e422186a172e888208d080dc4017b908725054e422f84e3d9e4e9760070c20b3918c847c6524b9857bd3b9ed6415720af9488e917b2b726f2950b8379e3c249f9085dc1b999d7292fddb517d43c9d936f4e738fb86fedcdc4dfaf3733be9cf516c1cfaf39dfda43fd7d939f4e7b00db5996c1a760ddbb7956c19f60c7b096c4707c59983636edfd66153e9cf3794eddb50bbe702082c5d98b9dd6b80033cf059ef92cf2bd1686dcb799a02d3a71397779708986d792be3e7a5bbfe411dea6090e7c40be61def6bb8c7b9e96b3818b331809e14e54951638f62a8b4036109924c7de4d206e33cf8520fbf9c821d946b1faef2a09ff1258aaed0ace1ea38d4fac545b386eee438ee143c2f927d2671eff52928af453d2bf9b33231c15c7e701451138e3a6a32dad29f1b45e9a61563f9f6f14fc783a63d6dfff9acfe5c0d62d1f7fac4396c7d6202e22ab8e6ec33fd0fe67789731eb42515bc87c500886f5e7b65c002c170778df00b5c8842c75c85918e794f28a407f530545e57e8f5b17cfb40a9a9355b413751197f8eb4ff954b94b38ee712912a686c9e57155d27cd3a8c2df7fae4c3f5fa44faac4c4c22f0ed87ebcf04af8e39e6ae8e1d70c4bb5336de357a0da5638eabfdd09fd780f7a6b93ee2f5c88066b3d4d499404cdd0236906a818758172b68464647b8cf1d9579d5f9599fe773d273328392f98e51e773cc6bda75fcbaae5f850827e658cf3f6bed622fdf34c2ae9f7f5b84de1d27f7efe29b3fd72fac47bef7c8f7aaf138ae6188b5e6a3d6235e431de2fcff1f6a1dea8c3f06320c726a303dc7778efba9f1de69c769cee572b9a6a5cff9e6340d798f428e7be72eb5f9e6ad3a2ec7a72feee2999317a7812e6eee12797fb19bbbd690933bef7418f2ddc57073321311f7de221fc771df0d04b4cb13bf50d73aa4b0e1c4e95e3e5e75681bcbadc75d8a672f9fef6fdf25aea5be36bcf47be79c4ee6f5a76d35d714d01d875227c5fc21bea2c8bf8b9aec28bc98457ef70e459c4bcc66360c1286617886e3edb8d2c64ed73c6badd4715ca9fe4c8ca3388a9a8ba2c945760ce2848de338b2228e18b7e43851144dd3344d3eae5f2b4d93c595e5ed6317c53bbb5c14f91ef7288aec8f00705a6471c4e831ba66452aadc73164b366738e7255fa16c7477eef38eeb5e4323ef37bf7b5bdb88bc5897cb3e5298eec796b622ec31556f6dbaf4801dd435c0ccd86d0c31858fc9f0a39ff73ce55a4ebe9c41ab7a2df8fea39be3f2b74892ee731e1c4fcc220ee9cdf07db2c811d0f65104c6c46e85bfc4b248b7b484e2cf21076e8db4a1f71493e067b0337700337700337d0c56d882150e99b8f59029b8b2f76e4aeadb9c8431ad2d0257afe63e8e7fafd03f941f99ae70f7429f441b695fc7210b2cd24b16e5705b7022897b4fe423d67c55931072627590d9824e7b197f0357af172c54b6f92fc974492bdd7e8c57d5a309db74adfbc4adf2150e9a6006e95be45fc9b82aca444a3b52de7692acad8f866516c87be15e201b2db3d72b2288a2c8eabb8cf661697f6300cf58c6f4319fa97a2eac4ed145f4e0ca4917262d784d4ec6471f7de6bc1ce11f634cd99ef6b137966b5c67a49955c9bf0d725888cd4c411e24fcb17120989a614038974f59b25cd0b33e05c0ac48564503063c7c20858cc40fd4892438c169a233f2d4ba23491c5e525d65f497ee2fab9d972d5c6324986048f70b1f294048e178cc86205b442b34bc1872678778cee3877a5ae15ee465d609a1f24928c3445b4dc24c124273cb5366d648e01dcd65deb1c620c84965513bde04b0b85561214e10e52052c8aa28d9c6f318dd55a2b1c410157579638b204103e2ee10821f418a205041719aa7a3c41624cba42f33788fbed3896484a31dafcdefd66d11aa2444e9af2155240c2852754b8ecb8a04b971d5c50b0e261cc98716a64fa6d6112af76d862640b1912b45cd100abc969081b8060c2cb96ef016787966e24c3e5a9c70390236b8670b3d57483d48f148e88c1a1d7ff8ac94b847c451c93a4ec318965af72d0557663c6a40a92b4748c3cfd66c952933595025d3ab2b0e054e3108b3867eff6e5ba59b2c07c386a2ef24b94595c0ef19fb95c4ec7d2632559ba36566d803c5c82535b474a602be0d48903eacff18cafe170c07ae61b60aa2d2fe5b9db70e0eff7fd565f9db89d392f81b25e537e0d57d47fe4d885357fbdee8fa36b10ba768958dc238eb28b6c05f405c2c81086d7c33741a7032754d5100d29877e02951591a6a8440b191462e9684880008317000018080807849124064220c975e50314800e608446645e36944a83811c87611802310cc34088210018630c31c630c65859018621d1758f9e40655cf568492e53268830b4c85596ea483e2a7c0529ed05b523f8072b1b45bc3775096368c12a525c21c92916148396214ee46d21bbd7b26d45ce439ca429e4ee8ca10b31c9e4f34c840de024274c649540856fe74ad5544f8c9a039b36e6dba314c1f519bbac4ca66d3e49fb7f9e12353e6811f76a705c4aceca6371278ca7124a7cb7acd7e0468bc6657b679d0f1bc2ddcb474043f42d199f5acd8f80aa6f17008bb7c01e5acce891709088e6e0371dd577939fff3d050ffa94b78458d90404d2ae17f6f23966b21a80c59f520cf49de1816fa786162d4b271d3c9f8f72c16544bd85dfd8555b0ba27f0605951ce69d87db4a1cfb2d668a71eb3a1e82c0ab6f42cb3116c42b09cb206e381a93db44427ead706c94a65fccead8303a15d687e29921b14d5615a4e065da7853da9fee1432499d1041888b30cb7d96428b9323aff9287b5ec874903094b77af846132d44b7c1d7e413a4b1307d811824960a0dcd51b46a4ad5cf0ade66e66f863af956ff782c99568a4ba3126cfcb9d7fa70f8137277153710dd938fa4aadd19ab019495484afb6556dfdbb0cef275bd43d1f63052921dacf1f364682feff2aef6f5dad1f4d171ee5f63e30eecc856469207a5bbb6e28ea41d8831d3e98824ff5fa23afff93fdb77e030831740c3d9c3b1fdd1ae83b2c0a2f97716fe4c2004b02d0dda2868543a221725ca6bf7047c52693bc2d95413e12f406857933899e28bbf12e74791c9fc4d0599b8fb08dc6d483c1fc400db2cf2949e8afe65f7b52b48023c802c03d3694eff2642edb5dc130f8a40f73b8288f6a9061c5439cf19e573c2858d02d1b7477a2457cdacbbee7979446362ea43cb735ad4dce848afcb80ed8140da842cf3823b68cccb057ecb4757e1814098b705622f71103af66fc89005913a2927cc6e7f5086e6a9807421f9515d340b2616daf4acaa8b736985a1e34b71b041b75682615dc72e8e5e92755ecb1b1488bdf095993313e2bbbad6edf5dc26b8bebe3812eb298e89749b8530fcef01a80b4dc4468e41562fa8e28d0d07e2ba796c04c8afc3e92471e36f40eefe70ad64d1f98385a172f37f657f0e7d2b564db8806a752e3ac521c5dd846866e95cae69f02f40984a621c66c60f4963e2e1a3beccc5d5860364eed26004ad9133acce70b5267aeb627f03a659ca448f0f78d4fda94662fdff7f12fc14059f280a192182fe20ffce1d9e13bcd25f99bfb50da725a124b59f19457438c6dc8b1cc5237b16f2001f358416d007f6bcf10e09558cab7ffcf6e45a520301c0d5745adc136d0e350d7a11581f89c5e3f00a35469582427234355220e6c866c8f98afe1da885ff891e84656f0027a07d625a0ac3eb099486c5de7b1ad5578106b919f55dec7b4127c72ac6d706b7b40ad05df6a3f837eab71020227b741712ac09345bba6e8da24f4b449cc516f0830edea7945bf9f02764ed5263cc76e228c2be10fd7ee72fdcdde09db01d51adcccfdc1fcedd33dd0795ba97189afac6a51ebf0eb92437c7aa54858a26e228fde80321a8f33019f20b1904e72de5ed3456568d41bff7b990fe9e96a088be1e6e40dda589b2953247ca5ff96fa2c0540e23b0d5d9dab55e0bc09af6d04dfb87dbbe6ac1b0d4a2e6928bee1caf68ae78982d4c58ad14bca39bc6182098a1f6f28d2cf4a94e7bb2e77c5bf34e5811c69ea504549b7854cfc4c1d55294c51d24269f2597e1b9cfaaedc2c3d092dfa663fac713f68866b88c58f60e8207cd49aea0a94073b34dba34a0716868592475a8d466c6f10c2c588bbb43c748af556b02f95058adf2f4c4bbe68e84a36ee004ba916119c2d13e2c780b46343d437fc17d92cc7db2b60997bbedbfffee9241ba117379421fa0bda600965091ac5c17d623a455ab8d899c45df92e5ed676001666af20313c2db8298f076a41e4e170b423968ebfccc8b19f11bd9b241dd1ca6f4f4af2d371b700bbfe4fe110ff8ce1cf8e78d3d449f354564f33e1f36a327eac4155a1bc631019be06e0409927d20a24ac7110413fbf76a38c3dbabc3daccde6f36e9fb34cb9c1571e2ab70fcf87c12208ca6a43ad7744027f756005dbc248fabc0bfa3d491c9ca52120644db68b8ae33fc92b08804c1d9a856464548bed2d4471c1f24d8ce98c2fca0e71943ea61902ca80bf3b042717ccb29cb706559d05ba36a2b6230cc568e325e431c30b4d9d86b3c809a149f7320a8a9a5d19281b6b1266f8e0c81398a5fa95066cd3bdc68445b2cf8440ace5e4f6327d12bdfebd00bc403a4d840c239d4fd7cd852ca313871251a097e6cbd8aa1cbc3354cecd01b53a8d72842e754cc97a4260c408615d18e1835a95038c69674231fa20f25479cad5156a9244d9c8f10d1d6e72c9cf3cac3314637fb243035248a1f7525a171a2ae146c4488dd1aaa194bbc02e8e63e42d2fc29a280aa0f628fbcc81de63a40bfebc8ae2cd94e8f77075ab8f3e3023c929d1de5a1896a205cdb06dd8646c8e4b95f3d3f2a47c19fac1d8cb54b4b3b54c3eea3f8e03ef2b4cf7cc9e92afdbf0de9e90620499efeecf632bd3d68da5396cdd87c7c842a4f53eef63c071db55df5b3cab9587e8b3c1ecd9828d71566b86188c458fe1288365799360f49da1e2480cb09e0388af03a006a2ccb4e1a69891041b5b53199858e8d412baa6ab4310b6004ecb88b7bda8c41c3984f647227e5afcae463805af362e41ee38cee0e5d527cf5514502656f0ae112ada505c1834894e516e35ac046bd49060d86e6597f24a87efb1d4e3e0d31e9462cbccb62613cda374c274529f284b6960d81173064b9c63159aaeaa4d26f861742d3191f6da6e8b0a9cd48c59194b34c41379590b4a60b9822310b76e607becc43b8e63b72a510805952acd495cbb32c0dfb0c64f6a39d7432a55c6d1f87979080c3819a19b4eafeac58ac0c05959ee6fdb927c959bdd34dc7ae24973d0f792208fe3691037f4aa96be244de4e227c7646a57eb24e592e838eddc5a351ee779cc5dc39230cf48d84d216cfd5c54a8c27583a45b328c875645b325f3054c018f50f21dac4412053e4271be85943ab62cd10fb5157b718743ca122986efcb6b29a4407af8acf5eb042b5e01aacfbca5bff0b50a02c9fc1906f29fa4b5f880ec0b2ad4e5126ee4a691fa4a81ff393a3759df6e35aa2e9218d2c99733332acdc37d36d08a499b793258b60345c365180681df9cbf4006930734cb9e2e304ebfdb1f1b1d63680584cfbdc0b1fbd2b46d2ad8813be33a813d2c5e699d78ca498aef524f632c43b93b4da854845cbb2de09fab895a8b91a340eb5cfa1c572a18e8efa54a87a877c4acabf2d993c4b621ac83c44388a7c42977ea17b2cc4aa4dc2182213ec8fea9ec801311e6d6391264f553923a2f9e4cf3d766c12da169f5f56377cfc7f80fc8bc2164304dad7d78c479504174f2b5d66ab919a89c99afa1486c33baafe5d7385eb959bc40d9c8fdc4f94c61a26ccbbaded593ec2839becd4bb76717a407f711ce946e75247a6be5e569b79fa05039b050a8b0cee5f7a8b71ac5260a63fc35f38382bb6767af66021020a935568573ad197afe9c5c9994fc42a17bd11efa1e34073c96a4f2e087b79c93c8884a508276d92777af5b39f2d01b6d2c496949a83e92c80b09554d3c4767f1a2901f0b7052608737a950c3fc0058e4262e9c57e8b5c12656cfb5ae7b784b89a25378dd8c300c240c8055f3041a7a1e3de6364a8982f569b1b50cdb582f3f4be47af2ec44a582b5983b8763d2ceb62f035eeb0adc4722d73ea07061911f023b12541dc8f2c4de5ed7732afbbfce0390e51f8b1de85cea6df0e21f8d1e0a5d3d37b52ef8658e6b6446b613508dd54d656b7a3230e83a58439650671cb8faacc0516bb862aa503e3c9aa347d2dd70ecc2a06651a43c56adbd724f0a1a9f8392cefebeb260625a15ed81247a97f100f966be7121565c1ee938c9ac4e19fdf4cabbd217014658daf77686bf98111eb046ad2159783eb323e5956d96fd3071ca522127e98edb6e52ece278bd6c9d3432300ee24f811c4324074ad91a6b244355355d14846b59cb24cc8552f2be1b4453c87488f2d6e72ca527bd09f8ef2ce72b6d504943b2c07473b2a55a69cb2aecdac7760344e5b074f15a35488e72ba72caa8e1da01b989d59613b4255f33868c834352fa2d170e61a5e085db96035a46770e83249a343963ac2c238b2c9f5a45374f3301075d8d5fc448a96ccc83d0df7ec9ea1e560cf3f2c0cf57b9d31a81228a631035ca71e99c8190b01d574ff47c00af9da5e0d9b370405dc0a96a91f0488ca106c882b388bc3353968aedcf999ab5d849cb1b395c5a4842d1c648a951b7dc1047c1e08169d98e0d857a1bbcc0cf7424b60b09ed4690908574d8301cd4c2db902aca1191421fd5d1f51a16fc5ecff5089cc5c2d6c81bc7c004d24a544e916ac4fa5abc9794c22920c619b0afb96d67ea10b588b71c97ad954499b9a2666d959700549843b0d6cb14bd4b7385584e10d0841234a8b82d2524401169a026e02f7c41cb027726c84fa3c28c83d23253aabc674c9eb0981199bba54c600493c065b9218d4bb7e59fe4a9e67c24ac6b080ebecdd74a92fa72774c5e982f4cf94080b38ec312d1d6846d872ddd2735e3edf29a9a2ec1420386c4cd906a10efb4624ffbd55d6fa7da09c56d6b8f1758a9c273bac3042cb8e4e2457592171fef827888241f60940add95d7570ffd7b24dedeccf08155c69fd72d97a4faf11471e9a33ef96dac06a9732895597cd49e10bbecfe446a0deb159ec1cb86c4c28cec0dfffcd68ae0242d514baa625883fd84231a966a3224d87d9affef31997f4cb5298ce0c42885755d5e27614c71c49495f0186c57ee83bc8cd7d9ebfade5c6ede905297dd9395236a3a1cfe18aec6669cc9543800e0c5215ec6ac83b7b55a8e4198314d596954a7c58c7dcbc06c0967a70d99e00bf1677a30ec831f6249850f7d297567055a0f3f1c75edd4f36a371ab1733421637220fd4b5a900446a99d0d4f8bd11e7acd505ba1049b61fc0cc561295dc31f22df935184939aea7e457b7b5b5669120e4d116f00dd3fd28e895cb9d7b32155fb1802a366e8c9197e2ba32a89cad5373757ff257313eb7bbcd623b17c3388129753b23d9fc1a29115a1bd9f05dd2001048059df43107829b38ec28913c55fce84788a61c31205ed31eaed281cccd0898ab6eddb8e86e9aeb71d8f861ef88f3b23e88fadf60ac1029fe90834ae5d8ae7f0c58fca4c614381b60f62df7a9827e98e892449efbb66a78ad72ed1d8653d267553c6d16d4e1796300e6542c50b3392217f36137ab73b86334707b7d0503987df54fea2b54dc433f638baf0c5db263997580fedcec5592b24579865df8ae4f348f9c4880999dea0330807ea110087d20afff9150ba5005987937e2a471f49b5544a7f06e369bd49da9d4e4fba0285e534f93b24b9d79dedc8655ae18bb27a51ed22574861cfe14ce7a601ccc862b0c937e2e28d905c675f5c072c12a0bf51b09c8f9b457d2809a5f1c75ea7ac252d3a676a7fb0344f7127a9a94ddd1a167e636ace60ad97953f6105d426666218ef08417c5c1343ce218f9ff8ace5e60bcab0df00b42010e51e75f04a4c0737f6d9b86b4e9639b3b0be261e20d75d06c0586c40c0635c97ac1d9b79bfb36cceda1a3063a3577891427578d772b34e26ca1219ba998173c594341d6ef6d7ac60c0bbaa2ce526d6a8a32ca0c3597a8d5298c13cec101329ed64770aeb35d82e855072ab5dcbe66813868ae906ca590cce0fc0c5d3b9152685d657066552d9645a290582af79c3bc7d1e6c8425d496ca5bf8f67ed5040f94b41a17f95b68cae12c0b2f4e7002334b9d333acaa32b7a174ae35ccc214c3732c4a618e5b06ac4ee22bc95a6158cff4864806ed004b006cae19167ff31efa39658357031d5263da8770347e1312f7346f97aac2e1774bb82021aae26d77e28c216af6364e840ce61fefa6631af550238d2c75b40b8f1ff752fc8f080ef35f1fa4ca30a039586dffc7b34b4c7016b736ec6509431a0c10c3b339f09bdb3f55e077b5624b3fe8d11a1cb64094845c70c6c7b9c392e1e713a43c3c587d29382f570564156f7de1428be965baecad2698ee1d9cebd98d4010376060f4024129c9dc826fe03982f38b14a3ad55357f21ac4f66bf67263f7f17c082b0bc661d0fc3c51dc5742f35eb788237c1d99e9e3f7a337bb8c02138fbd96c62e4d1dbe604c179fb06c69159a10568d711ca09b8656cdd641f700c08119194ee3536da0567dcf21309cef55108a647494c087d99d9e7a98125c186ffb21be268029356d714de3e5b70aeea1de6d9440abac8076a2bfe94edf8646a6f8632e67933fc8c4425a811ceed16c022de298f9cd1631794326eb15f069966a9a8b27e378848525ec513c32613f0b6407d08ceb65ad570efb9df2433f2e0897dcb6b7cd18f1ccba4e2659f37843c660140e0ec389aad7f46ef43dde1997878eefef75bb260378f57f85f8112d7519ff52b36f0ac3407eca7e3b6b1ee0589617f64a5c7e39e905162c8cb011e48dbc6b79631a7c49d21065de1114ff69c15e47077ecb9270d9660bb8f063205d4ddc70d9c661c8d0f90cdbaac2dc88295559dbcc711dc43ff4ba32705992becd6120ed988db96b62a1d1c972449f360b9cbdd84bced2d95e44d2d041e5f8c5332ea3536591db78285181ca0ab07b1999e2559c1c2661149dca9d0e36fbcb7df8836ff570699a935d92afe9fa00ea527b2bce6c28fc1c4ba967934d16c0f1fd92a1b72aa45c6073124b7d8b357d6b01b9fc22d3ac74486aa59f143a2836123843ac93bf3e7ba7ae56f6477dd2c1b2fb2766749acb6b069f6dbed43c0d254a2d8438d9f72c0f379fb0a94b392d8fe5da415d797ebbb04b39db3edcdfe5b50a1c747b06705d7f95b0089c11dc7428c581bfc252665b6c02e5ea5bec76003527329b008ae7a08b415f04be29d85032191131454d4a8e755ec9a14797bf6962419942033a5a305eaa8beac51dd41a596d1891324a39196305b17e172e7cb81ae98c3f28dfbb45953efe044bb9cb57759907b6703e9adb209abbe3c3cce1fa3e02ca6a9e6d7be88e3f7e077363a34bfd4a4bdedfd6e6ed21d4692dedad371606f7322635fa5d9fd0bd1cbaa07fca13d91d88a2e3c4f72f28ae10642db9ceda2c5aefe8618127eac5883f994bd5752886056485c4d19df41747a171874319e0c2dff04ff046f73ab45b5f4c08988985cd3b6ba3eaaa3343cdb7b7b640b2f47b564d4f9701d20d94d3c0ded50163d3bc4c940d5cd4bf8c63f35c43302433bf7b059b5c8d137611a90ff517771df7eedb12f57dbc4df028d6b73d300e9aecb05645c9b7395f05006a966cab9766c07effe74df4407185cf8f360d8392e31cfceb279f6b563d3459c7c2df617cfba99f1a944eb4e20aabccd5c91b7cab899381ea4d21cc549d3eb93183187b64c22a72d1a81825faf4cebafc2f00fad28782faa20208b3978dd145c098209741d3d29f6937ee638f7c08cbd7ff2db62cd6b7442de25fa88d41028a84fc356cef3724df099887520d58a6ad272715c0aeb914e98bf30219c94b3a5f41d417a2a54b60c0b50aba31a5717651fef67e483939cfa1058f96dcc05a6b505145940b70c48c934fc81806dcffc02fdfd1da77767f52304491003a77fe422e424ebcad3ace8421328ffac7eab454ff81ae6d2dc53f7db4fef06fe96b09815261292fa2dc15eaaa397d084e3bffe30e01202e3854508ec5f36d6e98802979a462e87848a1c8ae97670a10b00cc3eee2a7d4e7bad56cdeeecff5d9f14bcaebff84d70fa084de43731806fdb20675a581d09b0c2692d371f0ee75fa89f3fa8b1a6451b0dbab019e6bed88a8ca4a28173976d61c1e37e11073380449a94eb4a719328dc74b42e1c93c9fc1c0d7319f48d7cd41753fa04f56a68ca6467fea5108e0cb1dea11ade5d93cfe7de95dc5488f05ccfd213ce446a6832938d42c40a0ec94110c081c83532cc6e45524182df9f08f414fd38317c332e17033b9d6340c95ddf0dabf460256d69cc78c9f37102aa6326cb9091fb3d31654056b3268b72a937d5ec28c1c82ffaad440183aab19ac66929e58b9bd1076b82e00e0acff4a99229c1a6122220b8cf1d49cfab2ef257558f141afa8ef808047029834fff894355d97d1e701229b5d4ee35b325a70a38e1961cc9e922a5df17d45c21cb181b34b4aff7d147570c111407b8623ab4622fb05fdd15462471af126fa55d913c5e1b3f5736c07a6697d8f785d87085630c731059bacfcbdd8d678daf6473a881988795445a1c24994b662c755196ab35ea75c721b55f60ba01114c0497d78192ce2155bd614a8c6d4df4624b5f2e41d5c76430fef8b8cad2a2560c2d4aaac100b19964775639b195d98416a02385f3042a7e909f69700273baf37445ac2623cd99dc4df70cbc39b3a3cfc0219dab346fc39b119f3576e460f5b23109f791e407de240c80dde6d37b83515e4faf265faf3af099b987b55c116baf4a284497b2b60ccc1190f2637330cd631293f45e89131711ef6a045d412018f23f0252c3535f3db7a353808af4506c6899b3960a00448a97d6d7e4ee57879f99fb5acd1d64e74d79827409c7c041df86f35e7806a3f1a520e49fea326901f15c1dc0225865a1ff4600723ee957de1e1d571b783a2bf65bea8e3559719057bd4c8f26aba13af6b3b9b5d55c4156df4a28449792ed34fc199cf14173309b671a20f15f90d31711cfd504a082451efa370290e7935e656e511d0269761ec552429ea5749d1192174e83a7beb8d5a6fa92fb92e1e6c770824925ed396ed134821f72bd59970c476bd7dd23144328d92e904a85643ab683767385649a87d9ff6416ef3fa6b0c08690c40b956fdf45368a5fad4dc7e3071e3c139efe282274e0c7838a16da3240ce3c9d04d4f95cec758a9de301d2e1d89a1b2752cb751c5a82d1c2c8ebc9ecffc95921f336f17c0cfb50e9a4f048b903d22fd64193d599d3bbcd643e838d72cd6af0055d7db8946c22235e4298d8dc38ff8d4f811840ea06d10cd9789308546ab5554319ce21c3a6a3857c82dee1e5dd30b48c64d8fee9baf1e9914834e4bf38f871e88e0e699e1f9202fc67828267546a1e55badfcb33da2cda971b0c1fe053487f87daa15f0ea268241aae9f03540d109a5ee772aedf4893c15f8b2caca08c460d2e7994d724d11a631442a4d1da92ed995da6273515fcfec27d388e2a62a92a52f26ab5cd88af9bef1a87811619f671270e82a572a45ece4d8ad21052ec4a112521c4d052bf740ad64d7b335c47c5cde3753400cf42f44f2f1b7ac52452b6a57293b82cc21a0b90349684eb40d70936f9813503bca3e40c35526b74f493537340d4024b472c2029fc22ec7c537c7fa0557e66693b39408dac1eb972e2c9418e73d6821fb5d6ea8fae3a41dc23144bf21c2c0f4979043ff09b64d59288e06dda237c037f446af4f6df07169405ba06e530cf8af4a975a3701814f8da05fdfb9044a749e08cebdd7847fe02a043d8698c8e3bdd6b45e6f4be1c056215c9d6754100b82ebdc49ab3ced282e5290c2599b3b88e0fb1d82513f8a7cfd1302b3040983914d004db4eb9400dd19226843e040e5160d41f0b650c6d66ec6bfb21657d52db97a4a072ca2a39d4fe2f19d7e54591622e80a434337739ec8822e589ff63c432b16b66690628db9523b51ecaf79506a912e3bf10600e1e063a98131fefc4995d450f07732216b55521419ab66d634de5e642e835b0726019886b09bf45edc3bd09f3f511d3fb50e0ee3316c9d052ce99efbb94252b5ddaf4d1f2d2eb6322906f4be891f5e06bcb709df30c267e559ad72c58b98d38c7a9894de601d5dbf2dd57575b887eb92a3b129aa439def8e11fd66eb0e279c700c056c410d8753983bc0983ae9c35e76e5aee3fae04010ef0e744460f241d642a17e0786552bb266d689d94bd01d7c59cafed4d4d0bcfcb57f9f367c77173c258f540f473b4cccf298c7e4ef4678e4c886f2939442af3336254acc2902884cd5c748ae54d1a2fb450d472d06a00ff57af444373242a7162b694bf56a57d6a1e9cf20a3308d903f215fc09a4a10d7635428cae27521ab880e22475704e961bff9700170fcc1bfda3e63024014e2559df23f729cac9b481a7d16a1ecd04d8370df979337a8eaba8ec296522a7aff6837d9bf61ec0ed43a50203832286ca91c5e2eab1a2dcf5b354386502f07d2d580799305a02ac9ec58c91527b3c8f7eedbc98cf770f51add31ea34c4a823ab3cc6457d420ede21547d13f7d2860aa16122299741c9f36de441d138556d0dae83eef445319bc8928bb885356e27adfa2adb9eb38b0fdbc16f360db97fa33b1c51bff40971465deceea723c414ea07896d3ac42a2c68da5859d413d7f0ffd17a53f37593f3464dde618eaa1a489c6513a2a9727ce10d9fe87466d6c345f3f349022ca1aef0764efc6c65da668a47e99682f396aa6c144639c3a3c7ca967fbe792363723fd43f6f1ce8127c3b26cee2b040eb2d6364ab71e7bb1af4143c8bc454b66f2eeb0b3a142def02baac68869c3287e0f271128cced18ebc1f2b7c54aedefa086cd5ae0139e27827310fa3c99a28207f75b5ad3d323da3037d0651fa3ff5aa4b6a16ab50b8835fc64594308bf1a1688351a4e3c34ccd57f93d508ede0891ca307b1067385652e18b1e139b68ea13aaf0d10e458ac3178dfc4ace174c1632833e01daa6e833e3ec917651974ff5d1be81910a9e96a73570fc51c075b1c2eb8e7bf7cd2a2fb42d80c53ffb68d367b3e2af9254b6afb8e1e6a5930488cc8592979ffd63df8f7133612e420a8bab704d8e903583b80826e9317299fe9d3cfafda9a9c44e6251f6a7662c7eac2ba2bb0dfecd0722f7c0aecdb4b8df27f082ef5d28332e55e168943cf016847da6a26c647bace4f5727f71a21a65de3f395d2266c1ba2d109219b93d2ca5ee22df92047bc9b82ed3585dc1aceb8a45d12d9e31d9666dce0d3d3d4639507c92272ef5e7e6e2b242ae59c84c8e1e066c20e08a4ad9ffbdae2377075ea5c9fb8b9a642759e983abb1cb9246821ce6e785c12b82426f9c0c52cbdd815e394b98b5dbd74d1b2d898b4d6b5fae383b1abcea5ed2853d5d7c0d3e8a0d061a507851a96d42ce4129fd97e0835a1ea22655fc07b0d69f0d71323ecf316bb673915914422500f9ca730e9d7fc50d15e7ff5abad842911b6a481682dd8761e05f1fa5a48fe51a8788f0171149d1554d1e5afa4345282e3fe0a1d86aed24bf0628c82ab0abffe1998b08befceb69773d57b54f54b41e7fe7e664d9a6b680a0f164062dadb3bfd92ca3a5af62bd3e8d340439a878f473a342148ec1f1d3198edb0b76a7ea907eb3919f642205e18175ff20aa04990093accd5316903a6313c9490526600cbcd68066924a9b3770d9a1831f251181824966a0d4406af57aa2dcf4481770c197d1bd88654e81f6718b01c155a46846c74577f1e8c3ad197932052ce92732f6204231d78da6a4cfe7aba40092ec5b2ada6175e641b3f5cd76c38ff6f1fed8259f2668e372595453c98ea17b4fc53cb15818675dda5f1f87c0fc9c2433dc931704dda6ac106a5c531ae9d52a6d6b8b02be97bc188d96f871349fd9357758d5b1d7cf8ff95d2de20c7538b2b010d2b5d534a9b32d4fa5bc7495d5be2244e3931b71b0602357c81b747e238fb3ab5350407bde17c2c364760f8fc58516cd56566f90bfcde3b6b1bb93e7ee30593fed726412bcda51d47690df8af7ddca3919492dee5c6a34a25f8517efac9587c3bf7cec61ddf8cc18eaa168c954146f0e3a401020bc75c2a8f4a88b023e1e524149ea531278d297c3427b20f7ebec467d1711e282d117dab500338cc68431934160afc67440330d85a691d969107802020eea80c18b931eec858b20beeb09fdf78b11db45439d058d64d9a67fbf0efda553c631e7a80e0d42588efc01fa9d3367c14265f7419c49293f743272bd70734ff5e2039b49d7a38f5c73fe7a08c431915ed5418324debd5ff2ab2262747ca7ad1d5004b7b6e3223c68ee5d85cba7065e6815acd627599b9d17cf2351ea25dc49cbae87fe18b780a029ddb2fb686d26f1c01d70c0830dc4d493a45fc42cba4beb5a22e1f428870112477a7e163189c32fde5067068084470f612f24e573fe85dd451258f522572fd3fedd7e0fbc776572193cc5ad8cb948b0eb55711ddb3c4475a1f4d3c55e54a02df8a63eb9c76758fa23b29f63d1763cd565b781b5a700c3f029b8ca768f74a84fa8d8fd2e70c0d01e63fa7d5388a0d11ce6cd1aac6229a39aca29e74b354ee51ea5d0bca91fdb7ca12bd07c0605617808ab616148da97071ecbdcabaa8fe0498bfcb4c41244067e8feecd1a0dc2b427fbc97fd9af8da45a187a13a712b75772a730189f49313be5a68524154fcab2cb9b30bdea7f5cacf2e66f007d3f79524ce279153403aa49832fb27e195f543c26d44b2298f571880144aebf40f32921f0ce25921b978d8e8a77be6f477dd3d1738d0b2238298bf6d8bd7bb278d26a5fdb5f35056b803a1c842003ce48e690422cb5a60b42f59d6f42d60a0762cb34c809ee65e54be92494bd41880dc4788a13446cbda260008d1294724ca80b7044244e4a1951122a5e33243314446b35863b4acae469910aff0cce1fd80929695de18c0721c3396db537d83a92561c9f2d10205cce9b4ac4f68afee3b2fd6f5041fc64afa389914ba4cab31e56578df528c279e80f05ba586a9c2ac65820bac3e28d5046eb58398f5def42de3feb2cc54496e2766b3c5fd3a9cc513c5ac6cfc5f25a647d4acd59d05f5127e913f3cebd32032d7b294d2a8288338fca365cd4c53a0177e07c75cadaf664d62ea0c67b8d79ad559f640fc2fa0a217fca6cf1ace8124159e10f00b045813a5af03df624f642abd66a538e71585f585520790c05d62b6a958b02390dc6141a93ea25e0dc974f489996c56bfaef18cd105675a0a704ea15f026b2a63b0aba1cdba8cf86e15e92e534796b63e99a2a520c8daac89cdb1cdba52d312c125c756dd115e7f3f73d768ee08c4ee6b9bb5eee190d59f92106b6973b3e6b3c3ce3eeeab11dfca6ed6724ff77aa779f42b64450e629f6279492e3de70a5ad19ac53e01eb80079ee3dfa643e7788d2f9abbf6dd7317c0389c4febe87b6c62409e31547f0c46cdfde7974e4f60e3d5d3a6d9998d6ecf91b284bcf94ef0cd1f2d3c516c5a59b51cf9877d611064266c135d4e31065f6fc02d0bd5719672640e44ce927a0e17b614414acc952072b9a88203316f0742f02d0da433590b281fd24b23e6f3463811ffa2c579207aff5b8f28f569c70bd24e2ab33e9d8f60e790597d4af7e68d5e8a5e0a5b49e8a9a970dfaecbf12160d50826a8026c021d041d322f2a4487540657c66ec6561fc45e604c8da6dbd410d4662a2af184d2dd56cdb31033a669301d9d5b5bfc4c03143bf3889ccae6df9ede544f110192efbc415fbbfc86cf9766a63e1f3a3b3bd4e36edc64dfad7eeaac44fce8d4f6278c8fa983aef9111865eaf7c7187b9b06f52db3e606ad7f9549276f9e1844aad68a8029932bd7f8b8ca057de29f28df8a99e6b27933d775580f23b6cdfba57366b2103069c56ed6c1daf301ce1eb7d962ff0f696e75acc61cf94b0ae6a77233c9d69b1a2b698284a5d384afc0a99b2bdfc0b84fe349e6824dd0c9c4da058839a589499bc97282ab409df0eef02d2e9531c1e74b6bb6a3a1140aace0eaf188b5ef690e8a6fc1c6aa84f25ee811121b82429ba02c61ceb68e3ffcf861d465ce248c69203d4d82eaff80d03f0346f4cd3f9446faa20c4f33831ff16ac72a93c641a419c64decb30810b0dc3f3080d2f90154b9b5bff663b4bb4c6bfa888f1abb6afe932f3496c5bb8e36f51e6ceace773b0329318cfbab3827fe86392616593d556b7f2bc4574d3c33e2a7adc1993a1aa33b379b16cc273292a21ae55acfa3835484edc159adb1ae247d3dba5b74b7246f88680854e202aaf755655c30165aa5035ad50d5ac0a4af1c465dfea8daffc38591dc620d6cf8e76aecd8cfc058717789abdfdc5a9554800cd7b6d79acf7bdcaf007026fda845ecf313b068c73698a1f2bdcc1628ef05609aa17ea39584ecbdf796524a295392013606250676063b6af5c94016b82d5bcb63ad0bd6eab0b6c7da1dd6fa58fbd3c24e3679b8c9b950f3db6899a3ebc99a84ae0750eeefce4be9e038182ae5aa0b1cf6d70f6353f62f434ed7636f8e1dfeef60d8a270e243de7c7e5ee001438f0320a0eba17ddfc3e1ede7051e9283a107e50e8080ae4702b2cfe40e0e9437468eb0df713539da649c7ce312c5d1c8740270bf2a02c7e5c6aa2254df794584a87e5379a064757fca6ba5b8a442de521e902053e010a7250d1538b49937b2f540d9f9805d0ab97a40627096dbd84adf33fdc206488f9537cfc77fb8e937407aab3140099ff7248c00009b01d4d4a43297400db6b8a96cd7a37fc7393a2f9ea103ae6c2977b52d5f4ce5cbfb55fa653711c614ec9156b17537f1bcba99c3e6d25cbacdc9c1695d463c7735972df0cb6f5746a9fba6edc8cc9b7f3dfbd117d772084559f63522e3f537ca51a15dae7b2918c1edcaa10f659ebf5cc8886b81c3ee12769798236669c459f2a550bbac607f5924841b24652fc7256e00cb9baadfc6410934818043592d0cf3b7f799bf7dd141a0dc9b17f6f710cff75ba171d8df91967eed5658261b6589036d64b7d36b6bb1df4cae7b9f5eb79046e96fd3d299c426937da10c45a83e63e223fbb00d7088e77c0993659efc8c49cefe2af19be9b72833a2733a6759c644f5f331ccca6ff04f4f87b326f73b9235e7fc7bed776f3ff5f581729665d95592a953a6cd690e4b796edc73df8502873753ff15f794fb68f7b791e7b282c25632c77d0cdc73dc03994b70dc5caa4e1fc3709de6db955fb85a751ff785e0fd5ae5772f3fc579e1ad216b7fbdf046bedc77de7490d3761c286bde1099b36f2ed138ea5f6f475afa59a0866ef1fa38e3c5bcad6810c1ce340fcaa1733a77fbabde64efb973773a1c6e2e70ff0c71b2bc1c8cd354dae65ca1733ad3190b5799732a734f7ff63efdd90794332f9c977a40790ef16cbf151a87fc1d596d9c0b4a238d300ec6b97d8cb46e91c619638c51feb66d5bf470b49777d32ad5b22fae629441e78b40b9729101124600808d1a32312b1a3364c0c480f18255a98edbb4ecda4aa7f45e2202b72db2b2b2b29272b22aa2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7b85a4bbbba3d8a2cf39639c33cebb74efbd59467d3ad76fc641cf3732fdfec221326a73c96a772edd4be712b4563a95e61274c9c1a65fb3c9d530a59558638d754a2bb34a2d6a9555a416555a91562a9d5f7d4eadb51facac601928910e910e914e121d984e121d221d22239452a79261b57eb413e660b495ce1e289d43f3357ba074c226cc2910b02d52905479014a08ca9523942d4f5baae408654bd39627b624dd0e70b199456c0bd2e450d01b0b6825a233aa148854125b6177cb6b0b087086b4c5f564ab6869c2820a26b4c4b4c040cb0bb434c15a946881e5e90313ac25e8254b148c293184402507597c806fd0a54123cb144c650559a25011ca9294e5284b5116a26a65c6b2e8b0eab68c804998598266f03f453d3df59395f954057b73067faba7a6a7d813d2bc22c8075e3c1df1a4e409f61484250acb1316272c566059ba589a6031eaeb8214604e6019c2585e36b0b8ae68b962a5872956ca5cd290564645e095d7952015666cc1d11f95fb866dc5032e9a3cfbf37896d91ff4580a82b23fcc07b213414d8172ca2107d5942314a8584eca110a1413285027c85a8e50a08872dfc814d84023e40d998e17de30e5943df017657380fba3b4f4d26b69a6d55ab36ddbb65a336da3d756207091f33807a56c5a4691c03d54af2c5d295644150d11f59010d71d52830d317b7b33aea3cf554a29b76d1ba5cf755b66af8f21cef257a5accccdab81a32fcc5d4f08161cf6d0100c6608e3fb3e2efd1722574fd97663c8f47eccbe1a1cf421426c3cad54ad0979ec8ee2a07f2ff56ba9876044d1d2ad7bfbbb7da1e7ede36398ed7ed4386f86ce5a0fbff9186b72ea43b08b35d226d37ebc456b7a08047358f3859fc31a99bf8f0bee36ac5f5e60981638eca55ef256db58752b81a0939393d3cb56f14a75a6848ba02f86bc80eabf760a79adbc564a79efbdf727cebdf77ea17dfb5d25955352a27689a383e3dff77e3a9ce5bf34add31860ce6722484120597e04298d61e619e913d9ac7d70e646761c07fda3df449de8fe336e63044309430466b29c9e00ba812c9f7a4dcdce0e094e78225959f2e2c88a0c29529c46d802c6e5888bd0e7057fef74ff98a5acb2d69fee3b4ea9941fd7f1c3a9418931c8c148698c34d2ee26b99f1ee5fec221d25ff54573a0542828074afdf54a41094aa0c4d631c621293189bbfcf324940bafe2593815cfc2ab78150f224eccc5e8d1a3475963a3e3d97f9331d22c94fd0b65885ce477c3c1f620aa112967a553ca1f78c07d23f21063bcfe4d07dd05b19bfa0a357105b781e21584a61811e0e9e08e2dba923fd597afd68709b96c6ff6aea274d3b8d760bc461f675ff8956e4f3b1ffaa9dfdc57366e84e171f677e48b17f30dbb49e84bb9934257ca5b4d6d489d73e44e3a27e6cdfd29a7aa0870e74ad79dfcf61be5b8bf5f2803e74e6678323ceb4e1a0775ef9fdeeea48fdc89d26b5030cda77c6951bed686f66354d1522a18f345fb2aed7c66957965eba99bf7d3e3a38399bcf75eae4ba9f00b8cae078c97a982cafd37e585f32953dbd1f9d4b42877f585b92f04baf34a4dde5879f3f22663939165166f644496fcede3bc31478693af6c09d8f2f6cd2b8d83ae6eddb82e2539d5e4f00b8cae478c2f5ee936b29c57669579a5ce278e698b334188094971244551aec97189289e721204705c220aa7fc392ec14425bb2655cf7961e790fb97fa1dd439f6eb10bff7e7f729fb859dddcea7d6bf3d75f5e2d9c0f537dbf55459b39cf2c29909658a83e8cba4bcf5788c0e58fb42a01be30b1997b3b761d32e015bcebe46927938b9dc481a47ccf4b3ae87fa5b22f1630c9f98e3dd7c464a07e5e570ad02cb7881e7ac745e6165bec03dd064b8e0f9f2278daf7dda7bf7c28d59d3b2ec7e1537163573c659f3a30ba60838bc795e0efb0d70b42fd841ae736ee42eee2bf6ae6cd713636e19654cc1087ebfd7d64f52f1d4060cbef31d802394a426775e6e7edbcf3efa82fd7b3ff6b59ffcd956ecb6ba5c2e827b99ac01403c9cfdb69583dbc338b8d5a71fc2e4ed756c2d9f78c35bd96fdbd7784bfbed6dbce5f33d369dbc6ddbdfedb96d66fb2c10997c8b1d76e0c1872243433dc0b4df5659f892c3981c252bcee9bff5bba7faac9b1cd4bc49cce1af7d23792bfb9a4a11fc2f73e28f1963fcd3a3f3f1fc5ae9e37edc30eda5f6421cd4b4df714de3fe65071cd6646d9b53fbad230f98fbcd2badb4d24a3d4bfaf167f6af9d228df62f3ee0ecfdc31aa7bbbdf4686895174eeddba3a17353d6de7ae190ce6b260e6acf7961a492b5e7a8e0b09d74985e1fb59183dac7f800d6be9d348aa36ca7ac7d8db7344d7b1b4dc92b6b9aa665ed5b48fb09f8cde4d1e20cb216a1642d4691b52ab2f65183ca1a1728214dd3b4ee0befd566b4cf02feea74303404ebe1fe16e331ec222f0f43caefe5230ccf06209e9797efe5a5d7df8b37e7bf783586200ededfd18103c3096bffb203be5913eee517e22bd8fe86df7a29fc7556bdf5ba8faa6b8da8be6ed23ee05fad42a7d97efdc203e65ef5f6b1cd9e7a3474ca3372bf7a3474eebcb08df27dce0bb3cdb3c1c1fb1a478507f9fe7de9b5cbc1fb9917e308bedf47f7defb5388b7eadf08e594efbdf76dfc66f2dcf701c45bfe37265f2f5f56be1f439d7c97f2dd42e5be387141d8e21637d5b5dd376fdbea716c18066f1ee65248a9f00b8c183032522cfc0223068c8c1934300823068c8c1934563135f972383a516905c210617e1c44e1c2689a0c4d9ba16934346da569319a26a3693526101f0b583de8e365013035336ec349d8521e10793382112e95b2a919dc4effa7406fd588a03f107913e52acbdd8ad16915636c0d1b00188184f7bea689b1e38d00a84878ef2b61c6841354a9f7be12664c38a1eb8142aaf3be12664c380105ae763c58dd8abb1f2e61c6841350005929d44fe7572b1c7ac227ef8c1164a510801b342ae0d871b01f5bef66b04113729c29e1068d0a386a04b0c2833352fe4cf61bd8f564d6da40a1ebb152aeecc5297012df96f5689a5301478d0028b742e50660399bae0750be915b6e28c82aa7c0e105008d0a383899950d015a373805c891d3f5805981c60c95ca86ab99187304e8529d4fcdf5bb05c891a36300165ad8f907738d0dd7a572e4e81880851676b2dc705e06000393a36300165ad8c93c0558e101c890a16300165ad8c93c2ee4e855930ae770e05f0ebc91e7ccca6d260d1a1c0b2dec641e1774f4f45310671b9f57dec24ee6714147cf8eaec7cac15f11c1e6e06acdb5f2d4ea42ad3a6aeda93531d70f520179436b24ab3ef57024abbead61ed84f94dccabfe2c6282e42d9aebcf256fc5274251e18ce24a4439e4e851088e72b8aa4f42f0aaf5672cd7c994ebdb76a55a25925c83c821978372d85272adf5697857b27c3edd5cd82970822dce6c3149b85fdbbe225227e4d865996a2686096cbfbefc50f5f76be44deaefedfede7b19305ff5b16380ab3c234770db77deccb4abcd948f1d0733957cef6fd98720e837d993cbe522c273bf46c75b31df7cffc7c72936f932416df9462db33193791e9f84a017895e27d435c208941e459afb02cac4647dd0ea83561b0476ddbcb35a4e2510a7c6efca25cb5afc12438a686316b8061becbbfcfe7790d5f2c88b28fd317aa1063838384a70d846b8febd18af5646f407e91287e21835134e1ba586805db66c295527982a11c41c92625155441cf6e492ac36e24c41aa10390d09654be91b7925188131c55be0b08d421c8af32f3bc80d9cecba4532314a69adb5f6c82821a304be55b26c0993308f792c2aaa8dda48ca2841bda04260336c49e214248817386c1893924dbc11c7d594ae53d629535f28496962c147e6c442e7d03a651019659451468ff4c96d9db64e23d8a313ed25cf766030793d67628a526030180c06c284c061c372146f5179d365300db6c138d885759feca2e44e741152127620d3973904c23f6855119992e2ee430137c8fea1a6b4b3730587f71e799efbee3537d583b9236fb0adb70a0ee353ee1c7963ab92f4bc71ecd4ec4425f04e400ae1303e09e172cc25a3c81667c2de0109d92edb65bbadd4565a042c7fbaf5ead4a7ac74563a8980a5bbfbe4e24e7dba743a259d320abb539f2ebd167947c96ca5b3d2a93a02e7de28c447fd91ac14979863755b49ca6692dd94d94a6da55dc427d927e6b861a3a5aac011808dceb624e9bd88c3dd0ba45f9d233f07c41c8ce0e7febc70d8af259b2852963ad5109d232b9d2929553cd02955510ce91c99e1d4a4806da5b6522d3f6da5b3e6a5d259e9b402fbfbc79f582c16fbf97921fdc89b864d29fb255953e030c6ba2d6ae14b67f573315eafd77dbd24f8ba5f74c21e6315f020b7b93a3118a716807039f3d7561cf4dfee132c3fc6228d1638ecd7cbbfa3aceda248e30f448826c28a7ed97e7181c3d8d4312052058218933f13c7411d1c1aca1fa70687823764a073860ee496ad1a0158a55cad1cf4d496a758ec865cd954973b654fd93de029bf106bdf6f3dec986fdfebcb5239c061c7c27e65fb524a296bba630c8cd1693f20170a0e6fac632edb6d3677514f01ca742121e58911548c8b299804d19d40cb0f497881e58814120541b2891e38ecc0c411d712633164718287a418f06004125fd3951ce450650543d812d45de4e0209974122190a83cc1854814228824b19404145d24418310b80089105a1cc112693044144060b1031055b6a822b422821f3c7043135eecc061b0ea7205890dba48a2031530ac60892cb080558195e0c824c10e8e4ce9224b6e88d2c4143b5c9164c8135b3081b2820840182208490898ca91a82c4ea220b1c4674a174d42c4bc783909922fa8a248d441cc620827945080450aa11994a024892e7430428b2b46ddd442907321948194589185090e401082272bc0f1031f7cf18414441011c20d540039090289278cc0c189245370b0a1e3a7c791a8d1da766633055fc8c0265322f260ace1ce4999072b08820a23a81d68d0e50b27393a71840a9ab04192144de015fea249a64852583a25849843b23c75855cc5d4b8f7ba70189d98700f3ece17e2d8cfe7cc81aa82644d1264512014b45dae5fde544082f2a683e4e3184475030ebf47f7de654834950f584a2965cdc67991a7bd705593e38702c8311c404da4f10fd239f21340c4e1ef9f0ffce69da7bdeb93caaf031c46a7201cc62e3dcd4bf0c0d62f70c2044d8d1f36d00f07154bb878ea03f1d807fa41fffefd6ca00fc4133f4bc29ff200fe67a900a90926576c4941149e7e20fd3b3f3f842458b878a8177994763a67e6992319a8e4a62e26a010421731c1044433018595d6ba99300153b7bb8fe71d0793c0e16faa9744a2644a2080e8926790d2df6f5e5c3f3fa27277751d119f1c5122c7217a4a5fe1f477ee27723f092acafde44811f7430e6d7247c9a18e677f1fcf5e65972e59cee81cf9713ee0cffffd7ddb7bfad6054f3ae79c73ce39e79c73ce39e79c73ce39e79c73ae82204a42a59c934a2aa59c53ce29e794734a29e54c72c3d63f30fdeaf23dcbdc7dfaf4e97efdfa74bf7e7dba5fbf3eddaf5f9f73fafcee9def35e44c4a6f7372507e96dd2cbb52be0c4cd665445a654b9f34da68a38d36da58239ddd8f76ddce5aeb8e95da660b220abadb8acdc69ae5b86dae5c7f8b91c9844d462907a1794427132de2049ad588c49c266a1c95a80851e7b4527db9dd23aa44e40acbe4011d8830c5a84f15aa72a91ca8c3c15a942bcc4123264647b91239586b51ae443526094fa35c796062a4a4c9c11a66514c411aacc8c1aa19f1967f31fbbf28aca7a98d69ae69198b49cb9cc95bd4041ec5fa8ca22175e5fa3d7da957e307fdd5af3e1b70b703f6afb123cbb8bea234cab2a82cbb9f45f90d0a2ecda50969304d890e411a932c4a7be96094438d2857ad4833d28e72fdecdadbe4ad701a39792bacaf5cdd8d5092432e5f2a2185d130548301333840677ce4a15e0d06d0e080128d8f3c9910a64792551fc9a514946bfd507be5fa3220c0e1846d3a7e36e9a29f7449a01a3f56abcfa324ab4a56a59e84d558b75c72b0324929b2c95b313739f8e2ee6572c9a378cb934b32e66094226ff9d7bc6d30130623a3dfc05491e1de7c3958dfa35c1e255d33e893403cd5ca2a0ed68fb2519443523b11d3256f62befeac296f59aee8c6722fa308db24ab94f2b497f1224f8c1779ac974515c9fab87e9125a2a722525edcf6935beb4b4c3e30e9e6a96fdb9fbc1525d79fd9ad59d40e30674d6006da782c6977d9f15882e42da4d6523f8f25491e8f254b5b70528e9d0f8f254a1fe7d19166278304d3a29a452d45240fdb08971cbb17ac3f8f25473c961c29cddc97c792240763e6b164c9c198ddcb5ee6080e2f9357e347ccd39807e291f16afc90f929f3403cb1fbb1fad5f79255e96703fdd5b792acfa32bac0e165c2008e4f6e20cab3681a49968f0993acfa991156fdbd4e92557f93dd2fa95003396651de02ca3c9628f94d7424bf993067d58f99c7922699c792236f01e598b778011c9fdc209463d7c363c9518c2f3c963099301e4b9a743058ff782c397230e6ec29e6e85ceddbd8658a39da2e756c474dee2f26b5f5c06c95bc751d69c95bd15f6ebd51e9e5c3500f50b9cec9535fcb6e16e5ad1a3bb2dc4c0ed68ff131d7cfb8788b0ee1f2160c23419a90b7b497b786bce541f53b8f8696e1d130338c17ba925c5fe5d130ebcb2c8a86593f861762185ee851b9fe56ffc50ba551ae2f8de44de4914c523a49d8e5a991a7767f918c922bfe421994ab1258abac2ed9a42499a06245ab5056a8e4ea536410ac71d40fe50c642a3553299a4ad554caa65237954aa56c2a55bfd6243efc870724d2546dca2b2ff6afc60c9d85449a9825d7df48d001d78f393291e3130f44c93487add40aed2bcc9e2ed3d1d0844da227448ef29c4c66935cbb1d32790616728c34b66807b7b5cbd9ded6bb6edddaa6b9c1c9b6f70334d7ff58f9c09285232b71b0ce2a1608192637f970f9461afb753e30799b1ea54c75dd2e270d6f9e0eda1b6d96ea2294289e72e6dd486333d74df36416664251908bc887e6ecad97fdc89bd567ef43dec47ccca7b93e1f3b92152377da6f9917cea29c7dcfa248934d0993b04893f9cb5f9126cbb2eeeb2099af83dac7dfda372a69b64fb355628b1cd4aca665d7e903505a6661b688e889d092ec76d0b730cd165925de72a297b76afd0cca5bfe59c0bedfa6216fddcf0a3968996cccbbef629232a4a49824bc69d9c7a6d8592afb881cac7f0212bedf5acf092ec0a9565af2567fd5b2efee071273c85a9f4b823f7759e070c26e4c126e9883f5b5286c935cb72e06ccb6c384dc9ff5fad92007ebdb24dbc4226d73fbcdbb9146d33eeb72d05a2b43488975592342d615e42d6b9984baee075c634796336ee2608d97a9bd2bc5419613922af7dbb3f28603355e8603ab8ff91a9fe692acfa32df95acd5c77c9e5d79599464755d478b5cb0ce3689b231f7b25f79f46d93cb74a564519acbc6ea93b7b4af5fa1a2bc5545de3c552edeca549d4ae5b9124049c95112f701a45c7fc6e2930f28e5fa93c96f224f7d55aaebbaaedb687775b752c6d9d3e79c12a742a02d6eced1186b773e9dfd79bcf319972d23970a379063f77037d268cdcad97b39fb3026671fe34dcc872f39cb3ec6eb1859e14b55ca9a32a76ec78dbe358a83d5abb1bae4a08d231c9a48265713f4c9e5831cce231752376755aa4bb97ecd9a72fd5aa3e42044510e7b4aae358a53ad5f635993bc99f1f533277943e3eb5bef67c7c1fa42628e9be8a8942d78453164686800000000e3150000201008064462b15810a5719ae93e14000e6e864c6e4c3415c8824992a2280cc2181862861000080086808111182a2b0069b489b2b05f35bcd653cf9b13d7d75e07d2645250931e000e577cedcbc50ba76f9335faffaa80e10319fbb9d42d17f8682027cb9d02aec49c0bccade7ccdb0fde0c02e299f6f55fcaea0f66efd1b536a46ef12590a1f00ccf4786ee44bca43d096dfa081c03a8af4afc3074cb0691077779a0e115e0b481f7ddc99d03e2f72279073a8c9528a6c0f0d202f28de6a640b27640b7fa5880addd2da834e8927308863eab4356c136c37c09257ad374871b46e4ae5e17fe211bc02c92328747f4980ae09caf354655a98e9318b3366d43d6615ddf2d9d1f6fc31b9a928e00e80709a1bc6f90096272efe4f11d58e7eb078bb7737fe54aa26b43425cde6c52cffa18b25f49bcbe8202d2f06967447f4f801207cd3d1b10cadc4fa5d59ec69f70d0648893b65af235598e320fcd29a23e121fef84aa1bb59475c9e48cfaeb48d4cab55e63019509f62feb72d4377a7084c4706ab15d60e0a0bd1e698bb7e550cc3b085a67f68fbba9779eec83191c3fc786f284c3180aa96388df431260c3b62793934bc37819be80b870647b3b4bf724aacf04fca71e09e1e2389a13ccf0592999432b61cfdb102dbcd7790eb93b804b7332dcca20192f9ba3bf18a83ad04e2bc9ad6489827fb2fc2091e82428a5d659bae0d5962d00efa6b67f43ab7f992177ade95ac0e6ffa2c490c8098230fabe425d912c20b65292dc06f04b5e718227f6d91f8713357926a532918854551c716df9aca939dd654dc8ed699cc69cfa1cf54ca5b0b0602c0ef6bca43e64845817f9227b6d3104e7b55191aab27df0bb37c9b3fa4dfe4d24e0cf8660e4657f709dec7ee250621694f1b0e4bd08ee091cd7ed9c1ce29c582bb43f53dc2d6c3fead2bb47ece0b5aeeb21810ad1ff0acdf1c3365fbb0ef97fa5a713351c684a1b5044fddd90e5bd13a5a8bbf7fe0edc2dceaf14f1787a60cd8e51c93f0ef3cbe81e01888e8d5cc1b7118ddd5d5af28eb322b88d5286e6da4a69f8eb525f4149463ada06dc1c647a76794c2728b7a597061492c310e7112e63560b5df76d7693d54e3ed79f1df55b8a8554e440bc6d87d3b6443593d10e872dbeb9bd990c99e398abc1b3d7cc194605317ac907cef383900a47fb55f45f7071028bf9cd9945f6373bc289f2d2fad1ffde4c5ebd05b40434d34d54984dbb040b50d183c3f62460b7caa200dc701e2022f5b19c14403cded0de19b2d8cbf70fe8ab26bace05669168b6a69328eb9df4d2ad0cc181e4e3ea2fe857052971f61a1789bfa3aea007353d5a11b6eae8e50a0617fae3fe736c9593b757984ec92a7a281cdb91d7737fa6e9c4cffbec5385d33555e8868018f509f63db48a91eb4b7f88b4b18f38c1b993aa75f4b3be8364d5c456f7beee96d64d642aeadd0f421af4279db501cf611bd9df389dfd7bfe2df3e4aef5a7be1748b7773c803474440a6ee2a3a5286cff500ecd8d508a45419b103af1eaaa9ab8e85de7e2282b73b767c837a9a159d14eff81d289115d702c1265f407e8ecaf72a18d5a28d3602aa4ecff8a60dc8802d4e70db552d30639303144ce8f70fe9c66af96d3a6866ea3870b7451b9c8ef004008c91669381dfda188d5486e2e029ee74bc679bf278ea00c8ed5762d8937345f54f05b3a0463c3a2e8dcd4ce024c5452c5b57e757d68a9a28453ab10217aacb009be226ead43355e30c6226e078220e63c2fdd0a7550bd113bdbc68b82f58cfa5de5b2705e8ad22d53ba2d090c0232c77fac02fc749a966b3da3560797ec101454e9a0cf8d60f7a1ee5b6bc12780365e9fc34b0a616a6291b681e471b1744feb5b9f2769d98723bd48c36c20185b7070c2b54bed7f803d97ed53896bc449d209acf8e56a61c1065a742d6fa53c302a7abea67f98c3fcc76185ad2d6229bc07ad60238e950d0ad75cbbe31057c70c5dc372c1b2b178814bf46f43f3092759594c1c665cdbc535b343276df343f112476840012c43b4d9f18e68a87d957e2c470e67db67186400b900e48ad993b36c09ce7b2856d6af9d7455fc9872f95e00ee3d632fa58fc415c351eb3d71347d82fd6597f18b71b5a65fba32e16227344d59197f9137d1c2d5730dbfbfeb43dfb8301fa29f5271a407c3cf5efac1a03d1d452ad1ff5b42e6d8beac2cfca98fe1aa6c638b4d1e1a63be3434aa36637cc48beb2bc7913c40a629770b36d258d287b8e2765ae7589f1a6109a58c4697e163561eb8269fa54c303fb092f0e118feb2790cbcd0b2c4d6f2aa9ac2456e63af13f3e344eca65cbe7123f266fc9dc81e09aa763d5f510494478f7551a3ba30faec546f6a4c4aabedd253f724a07b6fd50feb9697fc52d5d5a948851b2813f8392e479c99e81bedd66bc3637fc83679b6cd8113dac7a49b4e7faaa154caaffaf1ee5e95c0722895d110a3674655b6d282c6190c4997346f3ff388f17b6e480f8761acbc6fbc5f7328072554e47aa30a265dd5dcdd1c0c3eaaaee53ce24faa21840ba5237bd894d75eae50a48fc5bafbd0fe879ead58dd4073b496bdfa7cf7fe429d39b98ce5e6736b41d5212557e5ebd9ebfd54acd16b750d80c4c420bd4645fecf56470af0cc070727197146528f39ec324536d0bd36a66ad49c0b63e367171e91cb1a042a9ba30c7f49fc35eb11eb29a6d3829d957bf563e992d4d6bcbf7bc1b30994707a9e3c3baf39d4b54ad86a019602a980ed2bd3d63efa536c5f4d172e0a9c8657ad2b8a22d9bb84652b2f5fa9f3c890b6d7fdda7f153f5ec94013493aedcdd4c5805fed7fa4a47edef1852d16f79f542fccd0dabd845801803fe550923a2e7d2d669cbb83b3759c73db66e343b7bac2020d3453949bcc45eb80e36c6462f125a6f6fd2a033bc25f20ac95c4122119e832379cf11ec143bdeb6c93b14ecbc9bd4b85465175440e056ef2e93c0bae10c0fa07ecebcce386a9df9478c2a722b1aae0842d7e695012d56d5dafa257ec7ffd5a3474caf52cce753da36f9dccc26278e2c88b78d9998e7e03dc71d03f175fd1887b531feca30fcf9669169a32282bc2453e286a0dd6deacc155b7c8bd300bbb637978e274b70c25cb63af7aa7306900dfdfa45e7ddebd436baf3ee9d2fa904b2417bd676f7f6855c3c7e5fd80c6fed33baf472db14f936da8406023e111771424a2d3637ed0a84b0c9c4792828c245f905eae2d7da9a231db7a2469eb55a1c76a6875982604b843f5dcc904fde352a4bbfff504638cf3b3d0848e1f2cb89f396dfc4c97ceac4e033f2944300014295b2ed09277859e688c5687a24534f322370e907888873cd095e544f160fe17a0850854047c076256e1b3f92f973c6de0c5197e543f226e2ace78ef451084f3fb55ab803462415f9a7ef806d2b9c1d5a20e46430784aba3885947338ed509828ceeaae14402b6247b1cf418ae233d6c5884e90b34077032f9c857d45954edf99cb525fbf6c8c7a61cc43c565b92d556c245ae1bee4a357416a1a75a15111a09c0700af18310fa06c1081b9a7c5a2d25aa6c987973a06248957b2cf35ead8d83d16baee66eefb6821006ae5d453e89f6e5bbcedc9656c7d9c4513b561c2fc79be34e275c5564b28c26cb90586b30a496fd9edba1b9aa3f2b4bcac851ddd54c5a6db0dff396522668c874ed941c345b10fdfd9b782394f8008a6c2673f770f3fcc59ea9f8cffd52bc1219c30079137cf7b624c3c4db7ad4ffee57fd761b763b007030584b54daa8da7eae3a7664e06b149fe16db01fac910032efbfa5a6c7421798fb8e4533329418d5e66aef639ffbefc113dc49e9ac9bc1c3e941470fcc5818cb45438607ce75f19188af23eabf46076d6549449068eb890e7b241c250190d417a2fa2385bed3fb136c9e84ca7db7b502a9ee198339ad347de0eb2298211fd8b50272fe622f70fcf0537d9a8ebbc818993c6a1830ea649461647acc659bda021ad50853a4199631f883fe683df9210c0d89e2afeac95483e68edefcdb8cae6b37661d012f30529cc69d44cba152dc093e7f1ddfcc4188b10630045697469dabcb9ec3a1ab6c1b9bcb363a63ce0c7435a011b7d14dde64491dd713d7fc55ff02b0f1e70da77ed1748e5af8516a8255fcdc531c0736056fa656d25fec05972a5f204460d2f432b90bf899e0de5285a6f7b73f75e0bb06106995b63323764673783207919c0debc2370bf76a8541580d4377373f75f9a77b44352b8fb301c72dba7ac5d617c69d852d4d4b6dbe8c903f2dfcb145a17d0282d002462fbb4a1d89132af60dd9c6c7af17a7a8ba3e39a48a890abf6953026dcc0eaebc545dcfb4f4f3f8d9444db769d22f4cb10f6847325c0e3a355e5dc008e7555160fa5674da1c6ed760eb738083a4f649c7883f50b7902e9b8e3fa5556a76b5193c441edd2e22b174ef44dca7aaa435227840e5218ed99160bc074456e79ff26469d0b340e60780c3858aa863426511b6ffb3cedadac7b2721aca0c9e3622ed9ece8f54cc752921ed539663e220b16a81cf6a758d19c4f807e2a4817d3dc06d7346c80515d659f0098ffb80481a0977a1d907c5c1ce03892f388bfb48668735a3429e07c7ad015d11cd3cf8b4917d0202ab262743775f7edc58eb6fcc896cd30c987322ab14ca9a01fd20ca7938d4542122f8c2225ac90581c038c2205f465622404dbaf03e3597e944663b0566d8f04510be903450e0410898e1a6d0c60c39a8ddfce730ce689fd023a9135ce801fdc96fdd3394ded29a87b834c724afa55a41e2b965695b3a52f453c7d0db88687864b0fc7abaf688e37fefdeab4dc1d3983462abf6d5e3155865108d590c9eda533077aba252905619d4e9c7324a4d6ff2e7fc17dde39c71ed1f04531d7349fbefdc1ca7d994ba78f92432a250c2963732f94705f2140dc53c81bcf52e2aaa0a571c0845c647bbdf8943080a794c06dfc27d6068c82fd090c6836486e6a3cb2661371ffbc1694a8e310dd753866b9e68c646665f0c9d402ac4376dd4810cfc29dba08142ba769660b477926c1c536d8fddbf2f25dd0c80cabb0f27189b09e5fe230b7d4d08f8787aa9ebc0989e1144b15086b07c71bc49111e72a046cee89f974dc3420063385a684aa0c008446f678cbece3170b4bb6b1d70953aa90297daf3a0a172cf5c5da915fa2608dfe64cbb3cca9db2575b27d37052a45f7de6ed739e928a744a98d450d6b207fb900e4ac0fc3f725bc6d50d7298bed17ba7601c247f9e6492f13210a795ecf21dac822feef80f4bf07c7e0a6099e7248691090a0c3dd034939338f26baecd27ffe3a9205d211d40df41b99c8ccd60d0efd80addc8055e22253b6151f8f942d4d803dc16c905ab2fe45d6184a0d6616dc5d67ef66d4b0a725046c57baf55b81526c9d18ad7744057a9e01fa93d8cc4ecca650cfa853ec383e801ce07cdd0b07cdebec81f7ac838c30838d9223f03c6170e4dc2e6e754ee09ec37ad1d1cb9ead205b1d41f496aed425d2d136e74e4f0463ef90387b68f9e291f6cc31672e4d38c1b5949e47184ae252d3fd36c97ca35422cb16b8d092a25bc11952d6db73066706be9b78835d668f1ff2d8be29d892db5a95e7c3cc356f1109d6814369214c30874609ed148e3236a9fdc1a6a2a2b2525962de9a5d82f32404125158adf58d852e3ea3e74a3da276e28130b3e208ed1aec70b3379877f04528c3891d6f6eb28e86bfc19574172d7552e6fb019c3c0ef91e06f75056c03bedb037adc232e9b24e83a083c13f74c483f8b6d7256d01e88cdafba930cabf7d25b5df996a045b70282ed921821f96d61df94580ecb30483e27853d4797b7e343f4e33832b782b023adac382a3d1eef61959a5fc393cd04c5f7a303f37a1644850df503ba1953f4ef4927067777eeaad8f19d617e12805685f52709b07874525d022ba9bd3137ac9b2d1823870992737fa7275f36af62f3a043618d6cccba4831cc1391c95fac53c43fdeacdf708a2b28a3c4d6c225137dd2d78829d5b1c77f4fcb619eb1361b99a0278fbd00eac322cf1b2293784ffc7a50bd536b4e024399e8561f806c1475b975d7335b9533d0f18b22b7ed2c69a1ca0bac6e59cead19535a4cf0385545e82f7fd034727a7d6da70e0380278495165dffea2e0716dd95912d1b2bba4408c13e2ad2f552b769c99fb91fa075138a7648bebf586e8c10ade795619e546ccd5a48035b9bdba345fdf162fa7e0a1611439f8d60f8b8c55ae5d87b135c7e046d3b80e789611df554cdf21f2c3912450525c7d31d5dfdff0f4451a5a9ea95440c9ba6f653e84d1a7b3a8617a12c0334eda026332fbe4d9c9dd864d55e5f28e98a566e49812bbbcb124703c87d0bbf117b7966f230972d8f947c6fead6d836a5229d375ff1fff59db95b4562f2929c10bd63092889ac53d24d03a6c4ff7e740f04b555f4d32a89fa0dd81421aec1574987fcf53dd4f180cee78bace985865868525c106e47a43208a43f58dcccfd0fa80736b3e325a42edf9bd817587e4a98ac008ccc5306328fdee421a379803fa75e5223db6ab159d77022c628ebf53213767c19fb9bf4b251f36e3c98552d1d5c1f9de6f915e3e51a475b58ebeb7b85de7bf1085ed34b20514e4e6cfd0b52ea55c34611554ce09c684b2017646ebf5987d710aabd7a401506838dc51b02d3e887611af908d31cb3bba7bba4d3f79dd6b9a84d3b88c603d81698eff3c1f45b2594159d09aab4cd50d8557e1a831a55f466c94056426a5fdfe4c88e22b49a957600be5f0909c017a11388c79968c45682bc15f6d985355fc44443067bfe380504271fc24f407e961a420ca4305796f21f5facc68072cb170282bddd0d648461259bd33ec60eff070204f447a488ca34271d46751f8625d3a4e5dd8d0ced800e86582341168b9a571ab6440e9ae1eaa9ccf9e1c75d462e7000587485e9171fc5e0d58ddb582273fc02c11f8952dcbf6fa209e23f1101f6fa6b24f5a9065d34d54a78fec09d54c0f44340188017625348508c59e809a73e92ff516ebd77731254d9af9a9dfd6ee5ad2fd0e9fa01d04b06d8827e6080dbf0d0844967d480ec9d1c920a4cdeb5b28f8a9d6d9ca5d5989317ca2f66aef8bb3100b8df57fcf0d104b768f1efa484f5b92c7ef9f2910e256b1d7a14f667f416533809f7c1dc84a9aae53559b4990d486cf80ceb890376115dada0ef929ade1b64dfeaea919059c01ffb5416f2d3fb46dcfd6fa795f9699f0610e44fb31a994544bba23b9e815ec1ed454d4ee1f1e5069e77a2664d8a8d9de28452289d3af7476c43909b461ff2ec63c17791dbb1e926fda0a5221c8623be1c09ec07ced808cc5b4d23c1eee2f04c777464814d742ba2cd022b00ddeca24a6e454a1e578ffb58a56cf0dad3a783686e7db24f1baffd0307eff538abd0588ab4007517cb411dee4244c67fb9acf48152c51f36b1f8b82ad8c4d6875b4030f2234e0852aaf0369af967fe3dca05526f5a94e6b06269fecbbc2e3dff3f112700ff84403c067f62e162968474f25e72e866e0fd6511694a0242be4b8a9b95a9ef8e10423c3ae438e681ca153890f98e1018010955c69894083faff18ad4f21fd3693e99da150df13ca53d33be3e489190e91ec5a458bc5a41c1a6ffc7b2537bd02ede4b0a769f1ad9490f6e08a80d670de4fd0ed43bb1d0fe88644fd7e986d96188b38b88f4d6d07ccbd3d5ae539e67aa77874ecf6f7f04743ddc36019bb01900501dfa5329d42cb84fe209e228b78fdb3dcaeb9f947f547b0614f7137e3d901164a6e64a5bd6ac84fcb18a40b2b669b276e961b943f156619fb2ec970295fe189f5ff3659ad111a7c12291b39ff5c0b6609faa8473f57bf6ad7c1d239184c7ce258d4abdb807269465f438ff69cab2d2e16409e6d4d9065895e7c7a3b043e2ffeb981777a9afd8e1ca5251f46f7b7ba58ae8f7fe29a28b18b188e484b2bc194d1ac438a1c5f9e72be7b580d92a256631f99886a422aa8d5048db469ea64bd52fa58d68b6f7f8f4c4d9afe3999e27c0e811e0c2a929f2f8135e18e3fa9202b263493a4c25c58292ea66ff560ab2dbf224356abc25a9beba1cf15609069162966afc24ad14836590a89220868f23300857ad7f4c060ac8a5447ed75986a156ae88f1045ec0310df316f3e8396bf6629c819428c1da506dedc584bfb94b3fc2aa1648ae56eadc8dc7a8e0e3d11329c4a69e49adcc458726c023e5141fe8a236771a3df3ac3038fda64abbb90216982a1f4a1ca081b5506ac36b84b75fc5a41fe0280deb3cbb4d8bf3944de04fae3553d92ac910e69ee4e3fa19b11f04cf3344d24fff1794c1ebb3e8d14fc22dbb20f4ae239212e35198a3059d3687c9c73de8fc8a978b456bd8f4019fc12474f6f2117218a782a4b9b2417027616ced63b6e6f0e9d9f4830db8699f01f54205923a6fa9472a54882cbb1cd84e5b68a9f4b004d03d7e05dc0050376f86e0166d74de3d0d919aa20bf1102385b4c8bf7acd6106383f0b006f8b5caa1cf7ca4f74369dd807e1de114239b3fd2f06dd137ed1861e8cf2dde3d247ccf968b45b859ae41ca6845007d7642dde3da7a4ee01fee8c4e939452f744d6a4ca1e0032e37efe9f61ac0523a094287f319cc8ad9181c0c5d90ee51550f0f610a2a051e0717593fda0df112c7867d56efc7e0e104c06c4c05eed7f87a3999e27922d0af9c49931f12db13bf328d4015cdafb8b2c830ed600d4739284da0f3071ec0444d8ffd6a159dbc239539d019c5d01108d9b68a517abdc247d3f370ef2043c7b81bdf9377cf36d0c6dd70550f99fa49b48807d67cb46c0725ec5620fc9cf3c1dd5ba35d2222bdf1e0165d219d7175a5bccd8c72c1a32cf64d2c1ce8e8ac2873cca65fa673671f2a3692393b9fcc7f52bce821382aa7757812192ed2a4da95cf5fc6c2ade860ef6d91c71b0d8f65e1457f90cb73338250b41321f37e76b2cddf5258743b88cb6ae4ba63f3b7e93b29aef4111c11571e5501ce482e559effadc5a2e7200e0b8b72f786d12e1189de81715315e688106fe017ec8ad7bff561e810d244bf4b9d4ea03aba66039da8f25889a3f7e09f71a3dd95bbffa478d347c04855223a83d8e34fe6c357203cbd9e98edd2c9c24c06e85b3cb6078ed5518fc689fe8d038c368ed38f220147f8b53d4aeb2773e1581902e9ee4027a773a48751dd457b11d25089f5bfedd7c68b825e8aa3d1d3880f5c09c507c80829d791f0a66e0664afcda5f3778dcfa565e01fbd2567da5e3ca1402d9a2c2f31e16a78c5139b87e239dfa093465073d6d5e8c88a82a17160c7437b45b3c960f148a2acf11abfa5bf1601a3973b5c2928048d76b1ada1997d5f0359546e81673955e48115f72bd8e2761cc66aa7cc1ec09df8954df16e88a745e195d654a552e821b473ee09c453cb426c0f7e63684ff8f7b4336d92ce0929d351f4722820298eabdaeab7a957080eb9c1af6148efa5698bdc3287d39f4da80100cc54f7171b0a2db9cb6509aa22f65fe705a98eca45d32084479269ad653d696fb54e4165d4b85bf358a7834a835f0795dbe8567acdaaae4bce2c82962ec2f736a892a9cd01c19e9a528cc169b349dff236c2c4141add351125fb8e8af9d0bb1d38d3162129c6194e60e68142dac14ee1c0adf43fac8658f35ce3f83b3208ef8018e59130491c564aa27f89eec90edaea60bd1d40ef94565e4a6257c4e95542ac464de63bdc7c9e028384fd18d23c2870a1952acab767e20edb3a568ad12ff388b8a72a1a0ff10440b99c49076a497b0579853e6fcdcc12bff8ea11275501672097030b88a7003816bcad73a4ddc530d10a1ca2a24dfc22e384bcc82b695ec420d64e5c196b27e512253cc0a1a3b2c39f9da4f0d78f1747f3ad7a490bffa605983a297e5bbf57804405976e186cc0ec458fb73ae990b68bc2d430cae704b14f35df1f08f6d5602bdcdf89486774089589ab83d5028d699582b309d4e35f91c3ab7d9f2617e2af3c62ad765faa58299a2badf97d25ee3df91be278fdd6e18486beb7ddd28fd96d317cdc9bd7a223844de9c7386c8042018d264804187d41d3286fa5393b3547a0f217d2ecc37fd8da02bc4f1fc0c2ea312de33b4489e647b1d3aa70a7e2aec313514e7a0f9582b04c6dc91bfc3d0e227de83c3491250e992303352064a167dbd2536078ecf5f82fa0b25a21bbff464202f2cfcd5b2e62f52241c0896506829c55403506ddd2fc1803490f1e9c678c0f67e2d8dad7e20b209552a55bb72d766439b1a81015167a471fc439681c18ac226cf69daa871aeaa401718bded1fb8f05903f99aa706420a1e57d1017c6251840cdfa8417f71f6a1c8363b3aead9cb7f9ff671bc16f52edb7ca9c5cede0ed61b2bbd89b38ac2c8425bc50d16b54b97c0dbd4686b4b241fb95b9c95ee352281d7228d14afb355b20ebf5b4a7e8350d6098419223ea4bcd26c926da11847516957cee6c01b30f251032c7ad1c612e0700b95a0e4215ed63d375eeca3658b2f291a9dd0a9db7d9d9aef4c95ad32dbcf8f4e3c6fadc4e2bfd275440533dc806f8ff77cdfa250d61386a573f76d961aaf5f9c2d97dd428bed692b026be52eef695e1227add2b4dd742a91421c9737ac83940811ea4610f9d39df86c5da771a494ffcb1d6a125f4aed4814cf25c3d0c4f2429ad03b443e5da43091741607419847cfafcffd3e67e54000c17c8f69de09da900fb42e402c1206b87e2a117c8bb0be8ee30d1aeaad14a3c6152e94d8a6e4554b53ed87971e7e3214138e413018ef64398607d7720b05d3838d6f1d50dbd283416969c95749ca2c62acc67ea3eb178402175351250873afcfcb3b958356027733eaa6d5ace2087250538c46b80706ff1e59374c983485030e1a168345b0201b17ccceb2095781c9a084fbd224ea2d22e6d48504b8403ad66946c489896d47697e730a979ff80c424fbfb7d125881e11acb6b593148cac656d6432299a5abcdbf8bf3a2683a3f435df9097e319e6c7f72d6be20f307a00f1a3233a4ea0842a4e3ce14f3cb06684717c8aeb9506f0f37fd927691143a6998fa9480c466e1f1508ac6ee2492d826e429a291045205424e80658214fef0cea34d33c0e50ac387f55f1724d1409ec8f402d05909b1b56a2694106bc984b7fcbbfc389fcf0eacd3709e71ed00122d082de6c2651774f04667ce5f8b9f8190dcd211c8d2c8ce8806efd01fbf296677e8cc195e8c11bd20a568b2700927815bf6754e0588e3a4ab0b4d3525f47da6c3a900e0f99f1dc4c6e25c5682588abd0f16835451615980f49beb3fd1b84d1e122e6a8b3f6cef372b03532f81ff59d9f79aa08ba38f0d0281fe4079806b7728559ddde5dcd29fe661acb96486f7853c042c55b38a926ba01310bd1706ea46d645ad3840c7c081c912af152af167efb63d425dfd7bb8668a9e1fccf5038d0155e8e7547f3fac131a11fefbb3c4bbcb3a037fe329499cb5217080a38f9059d8542562872a0b86946d7ef31f8d852c77cc2a1c3ea2d5dd8b626e9bf53d4ec627295003b9e6be0cb09c6037e6f0c61f6dac7efed1484730c9e3b0469b7521f4755a4ea4df946ceaf6ded1243b921d3d1d65b913846ede9a56094aa8f5ea40958e74fda531668fe16597bcefcf65beeafe8639af771e2dbe40da67bee08ad7939b8d11d97474b57fdcbbf053e63ef5f8d90d3d3b43c1bdcf64ba4ff1f0ab83bc5d44a4ce344a4b574a21efca9a7211d922333511ac622ff5c4ee7b925c72c215da58bc11073eb0e4a265530036450128322761f1aec49bd3d864116000473c63c7a063c96bdb2960f6d07890454ea202d4befe101c3f2703d07d8c0a580455060084580690ef7340f0905636342c10a902fa48680805a51570524f30d0d4216c1e92196fe6b4df934c445d72ded16a0b8cbbfc40d9b8e6fe5dd5a9cab408ae9c6cd8fda86a089fc2db0ecaaf223f718cd80b5bd9c93120028ab72ca4f7378c291faeb2359bcafad76dc4d895cc16c3605539cdd0e7d7436c7631edef085490b13dfa002f11df3fe713353cfb98e330fc18273db849d6243bca0864b127838fb02361e0ca48bda599b6ff8067c26689310beb2b9cf6a311c1a12aa1b562c291be3d978f46b0f89e25b8a829323c5e218ff5cdf1900773bd893160fa6edb9caee94e0b360cdf484c4f61e6aea6ffa4587c8876961391afaa9e619763849ae52cce88c3fb8daaea91e6a4f95504ec2e0e23b1b66371b1bd692d182f6258ceaa1fbbab5f1d7e8abd4efcf9c89d4fabf74784887079849ee9f0d00af14be20f3870fa8e5bf899563a83f6ac5219c4ae293cc740a45176278a4c9ecdd2dd0fc72c59e6b5c27eb430b0f2bb594fca8c5f9429b2d653ea24d2bfbbea90d9a5a23d1b9d4dc35099193b0863da910fa0d19400c62a46679d2ae9fbf100ce898db248feb41cda5fa3c4e9fe043e6efc184cfee0d81d627f90cef527432f9cf9213dd50f3c976951dcac92bd335f91366336cb2eb24f19b2ad42fe13948815e695b092464fe2bb39a858f7ab364f4f29f5833ce3650f431ecdf366fe2030ac3c5e817969b2fcdfd27393a50530ebfe13dabd4de86fb93d61d3ac1cefe37c09834155f3e985e62264cf9ee123c64afb85df8c3169600d93c608c9b3efdf5a93218a5480287606f29c90b0cb9add26ecb2be81a4eac976e3fac12836ae49ee7c67897a5db7ab25874af7d39dd17bfadf20931429acdbe15df3340477d5c3f801112054d1c676547a74db918317c4f83634a6db855228330504116acfef47b00376d3b1db78b87870049ce7848b90c2048abd6ca790115aac4f947b93dd29a18df9db0c29dcbfa4ae1b2a6b8623c32fb0b45845b40c26868c1facc2c95fb5b8cc623697041cfff66fa1f1f6250e76b2905387c4106d3041cff48b43d5fafd0dc7b8d3f55abf30820772ea75d903e51adefc129f6db6b34e876b2e5593a9cf5b32a993b2cd1fbc29567e020b65fad62d2fd0e7d660f948d79d92b69f7d23065c744c31f5f2b76d2c24cc92a973c7aac841033d3aa0cae2508ed4607276aa6c811a74229e7d83dc61898990c6310da62e8e7dc8c30ae38048bd0d40351e4793dc53a26a972b77ba33ad154cc58034122c72f7e29ea0e11660e1879e8b634a8f953ce17bc6c43e9a386f3afcbc43f0cdbc4ade9febe4e64bb08655de2d75f64d9566a8461f928dfb9e56373326377443c040f20d1cc6ed2c68308accc379460ff66ff10de568ce4ead08b67681584820742c136809c4fb0784bd1f34f2fb689fbd9d1ec643ee72131f1007f61fb1bcd74c976be84c8cba617a500f23797fdeec1f0d20facc9e739344b612ef7ca24aa35c74a9a74e6708337388b88cdaf74d3c5633aaffb59e1cb3147bbe2f046de3bfbffe46e3f0b44f493f0a2506e13c964e7b4e25623d9e30b22e5219c960579e800b1ffde715e68054afca5edd5f68cd48b629c8d6b81672f80f1e4b70b8daa07537b0e57927ed8e9b541f6d6b6b5cb8bc9a1fcd5897878b303631aa2b81ad344de31f1605013b3d7e4c074bc0a708e214d9d6e8ccdf62331ad87f08ca97edf713d8fe6cebfabc7e8a32a7621d1649e3fc30eb1d939b3aa4c5329df22add6ae5e4f48573b518658bf3afb2df3614d4aef1a5a0eb9fbc2e3023c5db7ea304c8cc6b130584d9b75ee66cdc58ffa69435871adf072f71b09a6290ca430358ac05abae54a1cf857e7a10f5583c8ae9989652e5174408aee9ff54ed5c41c8e6df7c0386dfbf9f528c5a1b7e43914003d7b319ff29716ab8c3afeb3b136fedde7a3af587301bbf4c7c0751aec048352713289f3b2434be21ac50641cdc7751b2965f204184d87196412091689f0300809f2b052cdf02497cc5f20aa27d6517e19b58ee70a7594258a7f37299848db19bc11782c7fa870452f4642e614b7d7488b8654ac92ae835d6c9ecb4464cf81e775fb6a11520500b618cffc068987adb75885b7fa5d08c1bcbb24ca6304ff1fa818748d7b5d18e1009b2c16f2710b96a89643a2bd665600c88c8859cacd4081053af5b0bcce05d0889f28250168c222cc7335c32f5f134d4dc19164208f56babb5f240deeb9d29354c0ab1b60a211037e9137ffc5939e0aab3c6fea13aef8830f09b59b0a128c4f1a49833eab6debceea2b1095636b9fb60863faf9be8d884ae048fb8060ba170d962e7e9a1b8ad3fc76f684da54dd213354229d9d17c6e8bede2c53377ab5a194e5ab70f9d0ea3fa788b0882f4f4f1ecccce27543f21e6001d164d572070346804d587143b71c0783dc17fd5145ff4ed29ce07f8d429edb88cc8554d899d017e39bb151747a5d564be7db80ee37902ab2219d55cc9b2262f160c2dfca4d9729df3e180bbd9bddac9803309975201cc04c478a719b2d830c8a406a7a4f12a76ece1b88a227f08d92dc578303c61b91f07ff1c8adbada2f210bbb1607cb7adc516b278043c9cb76096f3dd5d669afc6ba0cd4bc0af312e7fc8b7476b2fa608df4e2d0b3e809bb01b18457f7bb3ce9cdce20f8a4f4e2831a9b00bd1fc59d428dfd53aff99e34927dea257396f28df11789cb5c6fff3636b1ac3e86bf42ff7e97e3b743a6192ea735b3868cc030506cce8012c7b5a2abdd5cc5ec3c34c3f00cec380cc309465d520d6d7d562d5a73454ae2b0caeb1775e2b9d526aaa6a805b95236d8105c691d6704049f9485d23951408863b40dff01da47a2f8d3939ca5fc292bcc98446cddd5ce4bc60499d3d41decb2849c60c8e19fe621012d9eac1e186879e3f4a19a1a7e21f9c5723ad5d62bf9be43dd2be305fe0d64a0a222c4b4db99f4056cf660c767d0d86fdc35f645e95f1a0ee64c07b670762bce2c611bd82f29fb0c344e81594d10dc917b9ae19eb2f42afaaa6050116613ec95c20271b90790594c9f33fde439ca25ea05c17c55f592528c8de077970f72a26c1ad9ab30b272979ea57cb74050e7c8d18738934ae581757440096ea0f71a3b83feb0998a0e7b7e41fc56ca26e23f9be3075b2b1ddc2361236105e084b4ee9907ad0b1697691d072335462c353accb16f13cd6af9ac61c97ef4daa2838a57be8a598b927e7e6e05c76b9b16052e1062d3dada0c3e037b24566e18b3990293fe6b6fdd4219e677c270d5e96af6516c7906963da9fcdeb239da77ad1d74c4962abf9b47eea4ce7a2dbccac47565d866058b547102d0fd3030b1faec649c38418b97615b4a78f839f38eadbade053e374be784ef8c7d39a54e561b063574cec0f0100c8ff477d54025b4368fb4b047261f1e7422162d94ace15a0c7b28fc3ffc317aa6fa1b3725d9a540fb622ebc7c6ec022aca2270d38e599a352e4d661bd14340e28f18c8c2d4c815cad4ebbdc3b419b88745c4d1887bccaeb06dad9959f47b911b1ae5a8c3ea74d185bdc6d7c20ab84b97883191acaca0e1c575316f6b712bff32fc687a4e09e80a70f3909fdb2e8be2c67d6e817cc2f6e2760e173b3514f81fb83c030c74a53e2c9795d1361186b06b8671ad0233783bb54cc5c9d0b7d25b9292e22f8c3feb53e60e4696d2769f88933a4de499efb30f11bc54dd5b4f9b1f0653fd62a48b779adccf7f015e72060c1775da3c87408c58c9413963fa325c9c8dc6031e06321f6212ec364479a14f2697c1c6af22c1a247b07784cacededc2f78708a0c283557907e3c0ef064d419326809bd3cc779bb5468d9cb27569f64a51dfc92479cf5f8bc691675827ac4c949d1f160e5ce77842e2d73cfc020854da830c28315703e5f6d5f93047a2d9300415db5482a562307bca699f1775d36afad1fbd40bd56445fa9599a57fcef074f2b5869223d7cf7abc222ae0d444216e1d1d79a2bd2f3ae97108930404f820885bfc75239a53f0f44bdd611b5fc44492dceb35baa953f4afb4a38a6e01077668128afe8aace974254083bfc6852980c84db8f4b3907f76b671d40fb664218c8ec8776105250a1bd920c8703d9ccf6a516f1c4d50470fc9026dbd572ae73185930b14a22386e24f3f64fa8bd192ae5c5eb5cd4d9af47bad6dc65381cc83e5382933d91740b0529ccbebfc1f4f0082305c32e09e6edf0f1081a19118694190e24d3775f5074e2609f4433d4cb501fcc0f41af890359d35b2dba2067aa800158ad956bd4aef39f6398e3332e9b82ed6becd578faff3005e0655a3985e08adb8d8de88239c1073ba30bfb25dcdcdb4b6bb03fc4fd1766718848c7e95cfa087b711f370c30bd1938248e950eeaf1bd005228673c1a88d1f4f76b459833bc26cc9b99eef94dd12fdd45ea2a209f4d79cb30f311cf92fd26f57d56f0c7955b7cd83b1896b280b313f144e7898f2849828cb9531fad8b4878bb8e2a939bf90d160fb4cc063779703ede1002c45a52b5957803d923f2b722037ec126c5658a2cacf7288aa176a40843d33c533d54cbe951c81f0a95a23fec7ae9ce063565744b02d0e5ab34dfb06b2654dccbc2e4549c2de9adf390901ff66a70879df0cb2885dfbd724e151404b27f75ae7f86bd59d57c70b0396115f54c3e30669bd44f537a1f39083628ff8acf63246b0538851a9bb174c10e89b7b24127b2b7dca3e9302881369111f8e12e3bb56c8a80a7f4729fdeca7492beb363219ccc1c05e9bbc288f84d414ec954ff42dd30f6f7bc87614fa781f4bba2f394c0e2438c395d29ed85dcc4924aa4e24bdc8f3502e0c31d48078fe0dcef7d8828a859d95e88ad069d01f149baddfdd0778e0257c6619bd8e268453a330be071909ad98fe5e539b7d971da4f020248f0971b9e0f85d85d13473e37b29e025f4561dd78654802710efe372ca1f9b5feba23a80ff6327c1ca426b6b12cccc8da0fad5183470530b1f1c7196f2896329d25f0f64273ee220c9c416cc4349c0da98fe85035a05472ebf524fcc5f2e6dc12587c474c43590338892d3a791c200e9d5033a2fef0b20d79bde58d5d2f1e87b4c41c6025eb313a21178c0c2ad7a9aacf3aeb2c3d06600bababcc4a63cd22c50e7f6810884b6aa3b8003b0ff1bced2f0bdb516de4c5bf061ef29a0e83cae93dc21b909ad9155b97ace819e733e12ba523ae6c31e48722af041f07a919d43d06b9fab1f9399b913f9d20925e3df98baa6db00f876092b4e9e610b8fa05ac2b023de54e246bb766cb0d80e01782726081338257388d9b8ecd1c66a4b4efbe87704834c68dc908a033081ac4d190ff8cac7403bc20e12de1aab0b549c8b31314243a173721439c02aede1498bc63984aeb5b502187cd09ee0c52604d29be6f5b02251816ac13073cac4a6d5775a34e8781cf1a19429b27cb2ec5f12b90ecb87c37dfab05da0faad252e89d664cf917b89162d63307a8c8ef320abaf004bae7460090be3c3a86eef761ab08ac6b1c611c9069161ac210c7c074d401ee5d4f02b3e2e19affa7e9dbe688477f74dda6aa0d4b8697999d7dc8e4eed202c6c983246d411c060013581f79106c55ac3b50fbf0c1865a306214f8d31fdbbb76caf5e88a78c733fd9e10deb02dd983ea188bdfcd32462ba0f984761c6bc29bfe6469f579d008677d9334ed064321f16ad70d42f509eae246c4cffe8a5bf5dc2d171fa25091922eda5de00fbf5adc24bad18c26c094c5f4307a3c8560f8e6382214818523c28799e0298e081aebb629e1884071e7bed2aa22ec292e09d7b9e170c460419fcae2d27e20bce60f25860b479a359688464751460b5b4be548b7563aa3f9a6f1e3b67d2814a143cf0c52e35125598993d718319e9e56eec0a9b72354902a781f7e105fb68a47a9b2ea53976f933d297a463c2e2885370d6b004a46d6c5eaac3ee2c74b4215339a6f85fe41e424acfc4fb756b15fa85f76039b46cb45f5a660ba2a071909e86d33fd6bcd70ed61fa4b95cebd5bd29e3a4e75f31d41c2cbb60e9ad1a300b9c889ba2abca568ea60dd1941aa88ca23fe8005356e7f66dbe088d3dea09232bafd7570f17b1acbf0e358893b54cfc36f03509ac887ce5f4ac35e0c98dacb48640ed1a9023c7c2685ba8dea7892d2c133353b1ec7ad4e243a6cb9ca56d6dc06c16207b3aecc5240a18fbd0091d93511680451dde121db243c7128a2e8c1f204007c650dd20b46cb7ee2b8d04a4688664b299e3789e603d0ca70db33636353ee770f00c5c145fae0856b44084618d72fee51927bc4628c4d0e4a4cb50162be8ad7c19e6447b1a3ec30f93258112b20ccb2a76e2827c5077aea8ef0fd0f8311d80f0e5285af6d877b702f541bad9293e987ec23e472db62a80f4673f29899f6e701d36d1823944abe37ee30283dcf23c0720cb5d1795eb64df267f63dd9b3beb1be7074806850cad4315c43ad9900082de85be7c0fe8195c8596e0f2c3c589979492fcf9006f180db286f8912625782bf469d2da4bee4fdf48a02884bc4010ece7c939fbdd21255775a0b8f3c1bf4222c1fda1b46b49e1867ce2d6921f526a73e764daf8aaf388691f017d5710273666141fc7cc86aaf8f38281f629735a99755adec85652f1e3e068405a09030fa01d48f7b61b3ab45174d7f67188507b29d98cf383dd92ff0b468cd8ed9af0ed001c059616c47bbad96ab31057dc24767862fcec3b1363151b5539bd872e007f8730199172bab22aa7cb0b1a4245ea08c2b2252afde8b1878f5d04ed340ba8ad662548a90d7f99ecd6d7cf92d67b098c22389d8a580f6e092070cbf958b713ce7407e8e3ffc0999252a9ee6bb6cfd883c7d252d74519ef2344622e413284f8b6d271f8ffd63d56b38ab5a4f572c8f3afdf66cbf52ee937d7168e72e2478d606b96369779c5cd9e4835a8d94fd01f4cd87b35658eab31163ad7ceb9ce4920d8bdb24f836801c10d6104bd848098efd777975715c3280fc3c762e9e7e465b7e022140f4706a31f9fbf8b3c51f392700171ff3f498d340c23f62eca8a882a1425fc3fe8b5dd909a78ddf9c0bef279c16cbf3c269d34a7dcd716e82df9d62b284e1a4f5f0a6ed4bdb936737a74c9ad4d72f2c66da7541b8b2b41cbe13315502ce9277dc84430c7acd9e95880768489eb098ce80d0f5d6b84b33e482496d9b24cd00c89e19677fb95f4a8ab969a3da9bc1c8fb6903597bb0780d48ea1de0fc4ef06bf19fe653ae17c97a18709b88d3bb0ef92cec05cc9f5379c76b5e7e781160344e9151d96f4aa224e574f105a51c58a1b3f1cb2b06cc09cc5a60d5ce380e4e676875e2d25920efbea035e4a0c85224ca80b1c4fb1f1a0f2ee49dfafd8c0624fb586aa06fcb60e6dfc189801695498ff208b9ca884c16e1493e44e90a4d64c9175c5a6c671a7645e9275bf3fe5471e34fc3d51db1d7c036a154fb7db4416ef83a7e4df4013a6f6f4612a4484e091856bdf87b3e69d357f9c0494b57b7ed42ea847b02923fd87af429b53173c15361f1de05e037f7c709e2f2b69fc195d27203a41515f863037db6751f96eb7f9218b5ae2afa6cbb07d92405f3ca6acf04916da385322129bce3f0416862273124a8d9d1aa56d11b07e1aae0a0887090484bcbd1e5a21d914782885363004539b6f06e5cc9e3417177866e5a9a1e2f54378cbdd7de662029d025b5cbd2d0f66cc166d1e9d9c76e8a77ac9f7754a8535d83f3ccefea26f410360230f20a5c43b91afe5161cf54c841f10069297fb13014efd3cadae75b7345b1c26b65080c3de891a62c59bfc3fe52699cb711d5bda78db832034a7cd227b1df93d9a06c85c836c6f94c29648b72a4bc48b30620f17b2d896054940be2a0c1d2b0078c567f7f145c58f83b5a8e2d3feb1163efd57aa75f950f18fc3bddb6317f97c95ac1813073483cec192adb397b33dc9cbc546b1feabd02a17d824433887c8d44f5f1b2b5807e1b679fcf74ffbc75abb1bb0548253939c2cdc719f15156b41498d4e894091ab1729a61cea0f7485773e33010d8047990a0dc65b874f56b314807572258310ed02f4a7618816e2d847092f29e68c9376ff06fde7afffb90b7c3e6e8819c9e9fa28e51563148d155e331e416303447406e0c6e1b822b3d6bb4022b35422874f654f7a119813ccfe751a41e1066073710fa661681099940ff9c44058c8b88b3778f4652de8e989aff5a03f83ccbb18e906d25d020ce3c9f680d1503050ea7b1bdbc6dc2c92b9da609ec09038d38cfd708ea7209bf6163a7b7629a714f08fe199cddf999fa2986e39501d89712063681e14de1b969b70f7581fa7927671fad6bf11d766d8e4c849e44d9564f95d0cd743bf3190c82c80e437d73c85be4f073e72516ef38792e362a5680acef0d96f13d29e44751bebd43b9d4f1aea49882521cf98fbf61babafedf15c64ed5baf3ffcafcaf19ea4215cd59a0c2f353ec4a22c269b8bcba280cce2d073d9c3be4475865de28081c8d0b29e8dc79bd26e374bb4ef3311d5fd7789cae2b691a7bc7f51a8fefe28a66e374be46f37141efdd318e2ec97ab5f7f873e0b965962d784ce14a7e02d3642a62653f72c6b223161d60e10c0ba7b1728eb573ac3861c911d61db0ec106b075839c20a3ae5c2da910b7cdd70e1176563d1368eb2e1e81ba3d910ad4dd16f8afee6e81b44b559948da3d83ceae61725df54d6c7e194955409c5d807d88952d1a1782914498802eda4fe33cc84b35d6833e76cbce050df26b2a7cf7cfa620bb87dd16ef0f9e054bdbdcb83d0d757c2b1469ab52804e3da20994dd4c5867fc23135c7d77dc581f10ba1aaad1ac94668570e44ed08f56d28a16c8d297953a9b3222efbd604f4974fe409c720ae8b89421101839c551910c665228d10fb4cbe975840738ab2da185a38de47fd54ab2509be67bcba95f355559907dc158837acc8e986a33a0e9e45cfa476f924c8414de721cd8b6e192f5fec13260fdfac359dcee50bccf396fd870f0d2de49dc254c61a892a45b9db9a53e7798da8c65c556d738c41512c4434954fa1485a4a3dcd554f83034caeb78772c56afb750529c560a538ed08214bd59eaf01a4c33a29e5747b481c12954247eec6143cab93dc3e5e16024c9a12b846a9b597e6504989eeffcd943902f5335fd1976826429d95a42b9400d428316cf3f06ddc77592afdeff3fef999e96c95f0fd9f67b9ef42a37e1f56677775f37b9830f617d4879f2ca529b0dcb6ced693a8bc5a7cb721ad67469d56173c2f72b3a8e36e2ddc1cfd2f4a6b833c8b0fec8cb8d0766dfd453a699e2ec6fc697a7c6bc11a41d467cdbbf4a2742c53bebd2df487172b4af1a3c171e202817505ba4378788f8ff9adde7cf0b122f623a4dd87a4d7c4bf59aa727910d7898653ec47bfab8abe1ea8eae714474d0c5d2d8b1f35b1b0bdb193eacca88d109f72a12c7ce694f7e8657aa608e287d4486f5e044c4e1f10e990b182661407f31fa2eab3034a79e79d24c3b79442701afdede36f3d3f49508ca43ccc16b3a3845d244e40dd2f3a2daa1085b43b4468a3242ff197141dd1521335fcf63dc48f21a403f02bad34e7b0fa06824bb302abdca8e4487687e354e34332d6282653a5116efc9f536710bc190eb7d88c5a3c8122c9db53cdf20357fa653bcfccc00df292bc9326f2d106f91604a420326c40bf6035cdd6d20d26c10a85991a524f0c463a6d66330ac05cc92819a0141bd800be2cd9354be9c590357091228c9ec65714db88440380c8280d14e41d85ea788db586b2508ad9a9a1ba08534eb6928d20a34c21d4c0862a6c46699da2deae7d60409c97a1d0e18fb276366d4f670203dbc7713ac39e9d244e5b08a8f728d22465a61a19f654d2c46f8edad36b41354ba72d167fabda18866f790c92f051cfa422562f24b24d98f1c5020232178227e960be05d4774d128040d3133a8edc4573be05d9f9c3bc2db03f4f55fd52716a01add5eedf37ee18a1b750f6dd2b8c530d7a2336c12acea8ff3afc367b8a043d655f347fe9ce29894cc8c7f3895e405444287f14b42f2f4b3ca63b9f17a0a0c4192528cb8650cac1c99a3b800df83beb9d4ea8259496c384ca85b89c6fca3af09b123002c0ed0add575dabe44e11dc4d0926c1b54f41037438c9449ab6c9360d725b9b1812ead8a47fe9a659ad8265042f747712a48b15802769188852c55a27dea3fdbd6fc9134cd1bb4b4135b935fca434921f632718311412a375e5bf22d8d59b0c2e0e77221489ce758d82c47396d7516712b8f637f09684247d4854d439a9a44ae83f922c0d6b761dea731b391af05d6fd3b1d3ac5fd41f54453fdd48111492496080cb2a1b1ce414beae2447a2d9f22dd025fb1de61e66fa550ac378bdd2fea8a5c7e73651d44ff4621ed6cb75a84dd90e839447beda52f8216dc79885e3670c1a77518d69728fcf1c89df5c2d00235ff7b29874d5472e488c79a482e4327d23d72e4133c93ad1dbe30a922cd1d88e2baa593cff86a6a4a9589d846f3f742c66e50b226305ca146416d3982884a0c0137d856727728166fd462bace5840dbef1d42e6be6e3f3ceb68ff2b714603f53a343069d621235b08a20e84a202cfe7f10de9e3e0446d91ea00dc1faf4e0f5f46d9c578e28e2e1ed7091e4ca928db625fedc28cb2988886687982715d959693932e808e4600150952180974d4a394c83ecd3ecc3adc11b64336497d94299a55716437533fc687c23af2a2dd28a5c91e89ae0a244723247bface9bcf7fd712c9353555a09d4555c3923c940aac94d6aeb2b5e035e2666c9073a71bd6a923ed5caf7f2c923040989f9b0bc7aea331c76fafd863e85e5b5c6d33ed568573dbd89d0211a0475d50b291eeac48c6801ca47ecfa1d7743bb552b5c99041296352fb329fe8b03bfcc1c005d0e3a66502db81b5de17f0728264ea24429204334147a613a7cc1a72ebc9673157c6f180f7fd1f12526d4402d1f6d3059a40516a233c5592eb417b9e64ac350103ab9341d7e9a7c0d17b029b88d6ee15b21860380fce52a400308a30118eac95c6f20115893c006581e49653476f800953137cfbc86ca9408f91fbdd3a716f77ce528709b67c6212f8bcd807ed6aa7df3c61ed7bdfaeb728b6f10cd604d45a7b2f078ce3633c6b97b80982898a5b610183a4f9ee4de5a6d007f8f48213885eac89ea113eec9a857c32d8188241a86803ef06a53574bc9cd200c4fa3b06b5ac6ea77bff1a15076584d4940a953c0bbd49fc47b337f63c0ae1532c7b67a0a6f572aefb55d54ec97800885a5a8d8e466e86aa014df943f0c44888c83c04203a11c91ed63308c528428d7dddaabbcf90887fc3349ee5be2e5d4729f6d8feca14f056516de1ac8821fd2887049311515797c92728b6b25ee8d0c49f3e50192a3e30ab28330b1a4bbec2e707964e7657562de74b080bd8db74d431f835604c3a092cec162ec3b4c2816d532b6db0513a00a2bc792baaaa072963dfa4cb1b6e6676db6177b8e28b229847a4714855cf6988dad01ab2af4353d6453c94fea8cd06558a4d4e1859eb485c48e724836b26f8e7ca09357dcbfd88fa1d33fb3be22041a56047c7a0b1793e527cde1ef404e43fe405bec5b0b925cea77a033de48bc4e92a865a1e773751c9c14ac2f17b3bb0203bbc866270e9c474d47ddfb9ba4806ec269a70fddd4bcb100f28603aeb33647bb9acb19849f0b879d612369190accee0367f9f0ad82cf600367f44b3b5470aea83ac076f4ea2bec116618a0d06ff8368ff32402be2f80f8cb56291f367b2711449eda2023dfa49245d36877ed75a64a5599a057b767d38a1b380690c2fab9f02d80492336c7b0597bb06d19ed213eea192df6182b8986a31df6fc494f4611ae8001a8990fa33421b05204638d63b2b2ddeecb279fdf8127eef2c91b2a8f9b102f5b401505f7016316bb99f8b9828a40679d767fca7f7458e858273846e89a73f41c38d83d5c2e94110abe8e1264b4711a02e4f07a2612ebbd1e54ac3451569ca6bdae37039217cf3e4eb7cd813f87fb4ea880e3e65a14098d8c5f0fb4672005a4fac1149b59c9cb162a131c94164781a00318967443d10caae6f15da81630b185828a592c0b1e2e879d4eb8fb8c4de639cee4970a9eae3f6fe01988b1ae7ee4b184a274f448f4dbd6639a94f73bfa26e238517dea4c4661b6b1838478f8fe7fc088ce5168068a73308e04a8cf83406603fa2e4c97a09699cc50877888af06aecb582b884ce8f959c8d138b888be037539363a6de2861b0df3016c1903fafb11645a30d819a7036dab9a05a134ab75779d9d6bc0f2e01c666843da549bb60a1c25bc219eb06530849899cd658b01dc07ac1f1c8007b461d3b229550700df9c26c40108241c732c4635c05e3e46b40c01324c1adabbaded2d654a29c9300626064006222ffc904848eb1c79a2e7edcf8ac88a7d9ec4e2602955361459dcd53872cc56c221aa73341b16a9de9538ce4dc51e3b620d69cca8fc7153b147b852e547734415c158e54773c426e28835f438bf8717236a32610442e802257f7fb920368c5a779668b1852de4d4e844091f2d9c30e29e1a9d2cc1137d50a3931c25eaee6c92e58931c68d9a6218f5228a1b0f83dda4dde1d3caf7b7f2bd701f336170038211289a8c000a272478252981e66dd96ed1021b9fa01d5439c20c57ec40063c5802089240827dc44a66b4c0092960408508aa1fb4e85021b410c58f155bf8e009438800162fa862486708415358ae8655a92af6bdf878f558b71530b161684a08a080418523ab0d08f4bb7f77fa312b68b83bd20a345c9c2abdeeeeeebe727d79766777f887636e8a1f50fef803e9dded3dd4b269b54bef6e6f3090880206182d67777777777777bf9cdedfddedee3e3528507e39a79cee3ea79473ba7bb7ecf6f6c69470051a193c81d6ed2e99999999999965337377b7fcd6a240b33ef5c73ef547e37a95d25b4a203c39527637b333377716822f5abe90deddde434c44e9a20b1b1b274b0cd5e86489292fd4e86409a04a3bc53aace3e2adbbeb8ec1b8d170aa914129a548a1289c0f252af372d7042ae50906d99330c821eb24a9a858519175560085bec80f59a7081a199c330cd26e63d65906bb2ea8ecd84974f8c421eb546769464c21c414d2bbdb7b280a215148ef6eef21160c61c1faf6902aca1552908214335248ef6eefa19eb3863f6d788b0c17dc5d87d6b44629d0893284273c410911aee0828b1a687145139a105f8bc3466a01a5cef3c3021ce077c7fc60228efefebe96daed1b49090dbf76ff37779605e154378f1c3c6af80f82dfbbbb1b5f74099f2b96b004200061c8979bb9fba5866e66ee76ef6666de961abf5b4ad9bcbb91e376408b47285132a46468552ab659a262940a88922125434a866c6e6c8edca89454515245c9909221255e28a9a264488b2b9290842af6a60a2abdbbbdc190bf19a9bd39a3dedb5dce193d36478ed9c718a7fcb09b13fb4e6686a1eadf55cf89512726a9bf9f6055fd379e1a6e40788e54b9b9948e40fde3131e55cd7ac4ffb13d269ffbccdb9efb19e65edbccdb3e8e5e7aa2cf3c8cf3e66fd3ebaa795dbce13ff2449f890bdad8273fecfa09743f880da3ced00826a57c203c47788e2ce703fbe559a1b00f08cf11066305c263132b101e1b46491e1b9e150f109e1583b102e1c9613056203c387cc4796e24109e1b1e1b1e1b1e550784e786c15881f0d830186b90280b7777cf8c40639fa477b7f76481075c2e2b6aac7097deddde43be052db4d0c28b9a6a9120a0eea489a02a438d4e9a50d5f91f10aa5a6f467aca7fa161c3a8f30b6fcc9f46aa1130d6190259a253e70764894e58fb812cc161547f38393b497a4e204b76188c15c8121e0663ed6d4912d6c969204b7270705614c8921c06fb03b2840873082ea5c43029a59459480cc330296973320b29a5942ea5c4b028a77477e95f90e6a41d83bb31b83d737641e7d7b91361d088f7507e77ad059b925b36db67e9a1999f9dc23ffce1fef06f94b1081aeecf142a0a883a3a32fff00ffff04fec41dd9fe9a4dc9fd58124c2e839cc3ad187e0e8c3a6fcf9f9913f3f3f3f3f3f510a0de3cff2ecc79f1f2643abf209fa614afc51ddb494ea242460edb2654d9f3cd05d7786ad2c01630c5b1d81915cd0588c0c75515e5e5e0b3818b4bbbba5b7f4c602edee6ee92dbd494e707777671836a54fe9d817b472f76cd9de185683d5484123f38a57bce24c646a914cb02b5ca480865ff35aaf644169d7692ee57f9284936594f2076a45949c949c04d1b0eb4c5f632716a32736b19f69cccb320fc3b0bf91e9217aadddb3ad04d4e2a2fbcbed9227f69e6dee85b37e42f41005d1f5e6337b0dcc2ccb1ee35ee22764d2b8d2b22bdd911449911449911457e2fcf9220e0757a9324ca64ff1b508449725c6e83c621224a78c9fedf9400497a56290bd9a19168bc51a8d3ea0da9fed89a2062e8c3e214b454b8827e401a39177b700d1b2713e6ad5c8081936bd3bc69f9806bd124b77ab3a25a4a56aa95aaa96aab53388277e295c39e0205c41abfe2dab09d522a3fa1b49194b947850e2c136d65ce98a4cc452f24e31990c106200809258b29d235096158eb422acd46026131353323295574a5790368dfb354c3c647b31d83f84f3f16218aab61b69041d72ab59b4dbd70e5bc987eec0fd10550e87c60f51cbb4fd85608bc17e1cfd34bc8e1593759b762880017a38e1d01312fdb27b10c0f9a3389a1cbca1d88613c0fd002b7fab8282fac72e06fb77703f44955bcdae99da630133f8f841534303901a8230a0010e783109b1e18607e000811c7418b2030c0f312b2a4e4527acbdc1914f1c61d32c26471ca9e12659d694de2cb9a436a62af14ae941eb588c0bc23fc322fe58da6613c3be1aa0920fe5c9bc32ecc42a5182a8c4571c52d9605428430299ade9940ccbd8a83d83000101ad7657946b74c2c48d4c7b6ce1b6c1a7fdce24aa2d7508d5e60bf3104ec9b0a352a93a60b3eaf8cae955ede79d42182595cc39a7a8ce8dae20505c41e6cc0c9cf261b39e73ce193dba660e5af8b453f2a911f4288360a770005540c308640314400d287ff82d7db3e69c406950cf408b354a3e1486253434c508838620682326c747132bf6827d0cc2a8f9ad28eb41bed809096d9ff2310cc32287cd30c83981beba09ce065b43acae6fbc41cb1872ff42d9043a0514bebb3ae822fe01ec08118a29a8a03fbc2a028a3752e032a73625f54ae9f202b2022173ba03cf75b76d3f73f92f7f86f77351b62267296d8afa73fd2af6b76da3ddb2c56b99a1acfb655f8862aaeb934b1dd12d62742550e60fb7e82267ea3daa51d6d85e4f214ce0a2e07ae87e8c379a68a14243dffafd13d5e839cf6021d5282327048a96312a63c99e7fbecedb84cc25894d46f5f79731d5c5d3a264f845887c5415800ddc437e4acc24bcfce06aaa5bfb7be9df2aab7b35529b2084eec5627fdb9af64ae9eeefdbfc4259e507444bf52e341a986ab846aa6f4705bb6f9be47cc8f67e1eb5bf22b2723e649868cd4d7a0a1afa14b4bd37db3c0322b6aa7c8f359a936831ee8819af3717c78e65f9fb2d8ee8dda2786366f1062ba188cb09b6752af001561bd44bb3ebebde3958460c6d937df5f6098788cf24c228223f0045a0171c583ec6c7f886f8b6d5a77d233de68644ba5ab677d00f44b62706f56873df350a442f6a1cf275ef00046d1313a82a7f6c56931328e962a036c90e136c77776f9c1821822a43e5173541b38ff12c8ffdeef470cfc9091a6ecf3ef128197b88a8705beb4a7281f945b23a5adf1d5b77e34ec6e2458e0145b6b20e83bb3b0cae885656381c48868292b8a9e14aa151c30dda292b1c8a752a0c6ab8d59d50f4331c744a8286dbb33d51c7acec2bb453469826b78d0bb2b27d33f106edba461cfca21d54fe9a9608b2280534fbe8931eb3a14ff2e29cdbc3a998171446c5155a272b2fea4aefa9fbfc2dcc0b54f7b34ed1ef2507fa2ccff211d1aafbf4fb5e72203d7dfa11b140dff6f4e0f8c5c66504743f96b6a0f1c99227aacb13f46f18ac6154e5bb7b60756f7b60a8cc3160e446a45cd2d3c362396fd4485ef28929f9d428f9947c5c7a4c2e52947c9ce8ec11d4eb0d39bcf1b67119d75be4dc4b3ecff291c503fbd44fbdef53fb683ca06157f7a6de6053f0295040b2a2b513f485748a7e9d62f9ec232780fe3e919b2fe3de1b2ad0300e099165438cdaba7da110755bd27afc33d3dbc6d558af673b99edd7dbfe860a95d9ed3a5066c5f4cf7dbf793518eccdc4a08d182e09345691fc0c08991839ce08da3b1c6f5f68fe9a7b91dbeb2ccb9e26625ea8533df3b0e9c919e69f9e911abd22b2ca8fc7125e480450e5b542eb263cfa0a7c317880bed0207a7ed1bf911abd300b85086dd416456cfab737d35fb62bc434d8589041e7ae0a943f069bfee6ce324abb2e26823518ecac8a160b4079e868abf5b65faa1dd16cb0a0a321cda6ca9fd9d1500ee9536737524cd10fbbba6d1cc463091e3a5264104e0da5942a5ff3666030ab9285d6e0d44a34712acb21c328466d2fa58d2a57a45701ed318c870ec6711cc7ad17fe0684aaf6dbf69c68c43d75c463898969abc9c42e95eab55597f311356ff4dab7ec91b57127346c09b2c9324aa3d63dfa4299da210bd5d1c711953a8aa36ef4244f3b711e0b716a063ef58f9e831a0abb639b8aec82c86fd468e5fb49dfdc87319917e3893f5a82a05a01d1cbd7b4dda4ec6ddb9e26d6eddb34ede548ab44f0d4aca1b038e447e9882714ae9acd4c7a504c6fdb0a7a145ad1d07582b6aef510a39ad5a40fd1ee68e3e639fe5a8f46a3a78975f4466a4fd01c98fd940907c673203e7192a3f0b6821ef1b31b4ead8aa79cde9327b6c25148d56b49a12d3feafeb55eecd0c89bf16660100a83fddc375745d0d081221684c17e01dc80869249ed17c26064c2a9215107f60ec4a919dafd0c1736ae5081faac16046362783b06ec32193f4d8064cc70a12bb9bc98666cb36adde6b232b2b75c59e1510d89a8fc41c9322fab9476dd7f4c0c14d992515c5e4c30345e78efd53f322e74d3803145983731ccd75fffac931850a945f82404daaca86329e5a44bba523ff4697af19a3287e28dfe928fc92601a687015d71ca027deaa863e30c7ff499be96affbc28ec6cfaf593972d4b0592f64198d8fc27c5d277bbe1ca84f1135b988600721a2a61083fd3cc4280cc693417dead74cde14298b10412e224a4a8960bf1cf2ee93ae78a385f08660d0a6f64f1583dd3795b724a58b942f529aa484919286942f48f952ee7c75ea25a7b8c01ad36dbcc383e883ba1fe3f2a8a10c7579d47d31cd181d0684212941638bafbdb0bdc0f9e028eedcf2381fd39ddd63178353d8b0eb483793152ada69739182c6b0b9f4d018381f5a90e6d2f32e516c0fcc9bde647ad3bff0a68f3d60beedf11ee661be215e7280791a4fe323e2853f27683cccffc37cece17dfcfd0b1e23e006349cab8d8627d97ba161552f35acaaf2278318ac5d0f68d82d8d8a0c582cfa0f82425348a84e61948c014e4eed9752a45011560b48bc381a7775dc0ddc83bb2a68285d73c5a7fe2ceb56bf2f988f5c0ea607ea148c8c25d0705f41800289265520b1e3535751078b53fc3222a64ca6cfe72a9c2b919cfc71cb0634f41eee0a747ee8402d1726d49f5f28228a1a9d1c2154654b3ed1b31d6f5107b7fbda1df3c3f6f5d3a75e2ac49474451dd8378b88579de21707219efa5dae560b08885bfbe2d60bfc5abe504ea9dd7da19c82dc825d928b2953a8cdad78a306346c160090a086a20e30e20e6e2d8efe1642166a83b5fbd8a7b53bba8a88a3bf49d58506346c56017ac4d70cb045d3875788291a5ff77c6198be8e457080b94242b9b5dc6a3593948c8eb9d2c1f9983873357356253d985a42426d5ce3b19590d070ab728a0c8a326caeee8e3fc09654c056f2a1fe33dcfd2524d4e3beb21212ca7d8909caa0cb0c8ea44cdf5f6c569f3cc2a0ead188fbc23d52973fd3f7cf9cce9a5b3231335bc987d6920f057d94a8a06bf337f146bfb600d3e75bdd22a366b8b0a8bfa4012bff20f20374c2f4abfa6ee6016a7c52444e754abbee1f04b5066c251faa49579ffab31212ba7d89099afd68a471949cca429ab632fce316b704f3d9c92e5c38f9fa65959050d29798a039b5e7cabf8484baf0252668b8af9e954fedef86b25357c560f7dc13b4e5c366b54b0aa382a20e197570a760beb38860bf8c1fb55ee32c5a3aec3664071e7af02102129872bbf43d64071e7a989b0f1190c0045c5cbe38a79cbfb3eb580637f2c2fe2975cee2b33fdb64a30c68b8aad9e26acfd7abf64f57d4e1df3f5b5118f5001c2090830e4376f09e9f78a39f3d6769cd97267aad7f7a315ffd43a28e51e50fc4d414e2e6920fe52954fb6384e1b17e34b1b28c15fbee9ec7599d7bfbc426fdd3ac66c220121a39f57d42a27dba097f2524a80b5f3a8286fba25dab8b57f7da72bb9c18e62c030b744612d590971d5e5e6685f1dce4c5fe6c8036345c958b17762d7d17d8ce0bb3ea82176e95da3fc393e185a0d7e26d1246b17cefcef7423fd6966d523b46f647132bedadc2603fc90ba5505d9b9b9c6fbd70c2f433ecdebe3a65fa9ac55f483f9742d225a9d4fea46bdb98f3c159e63d9daa61c3811cc85d2bdc8a17dc31b6f55c6a09092d31419dc573a0226818353a4982a7ca1a2e50ed47f5cb20209c19a559eda71cbacd629bdaee5262add5ece914ed9940325ab89543c355cd559f7a59dd56e1d64b34e216bb7ceb548df66952fb1b4a6d186ac7d19ceaf2f4770ea3244be1d4aafad4a5d5ca57b5377bc4086a53f79de37cc4b9ea54f773d5a21202c3352bdee8778901ed5f15b7f6a764af964b4a615450d421644e1be6bc61ce07cc89c39c10983387397570f5aa539ed3d1efdc86cd6bc987368b4f0dd6511028bfbf108551fcddb0317fc401025b649400361c3218360860e096c33c6d3ac818b6213e806d87ae988d8d072e0857ac87ed0607c448c530021040b4c22f3d00dc8fd9ea54d62757cf6f5fd3618bcd6a563391691683fd24269a95a2ab54d0907f5eafddd99d7d09f8cce7d31aa9919bddecb97d577bb4813b441dfdf4c2ddd9d5de0c55794d71c5568c52e7fc9199609765d3230a1b79410c51b316794174cd58fbe19c17d0ee0cc9c7bc2084a8d8871c0802abdc2bce321a807e9c318bd26dd4a5a7e4b3eebe1ec456de27d195c36d85fa84054295df07a12c04df6384838030ab33de989fe3bc38eb615f58e3ebaa7ec4199fd6f81e0251f708a388cc9df366880b472b00218c8cba1f93e346dc3124e2e0dfe7624a668b3a80f0321bba8fed973188ed7e473c9a45bdfcf0c90531f2c3f7880cf2e2cff9d82c8b01b9621983ccccdfeee63c72d4f93db7e5caf7fec4055d2a756aeeeb91fddb2d478ef871c1babf9e7bdb1734fe4b7c890fd6f92ff1ab01acf38bf18a788dbf358e3e7236c41e339c83513b84332655a1e10bf6f1c16f6745071a58058803283c1ce12ff2f0440f3df2a544e56aecc1ebc8039ee05079822354e3139c2835bea8c6273840353ec1e9a921189fe0cca072353ec149d2e97c3c68228f3a768504a3da1935451d71052b2458255ad448181657d440ddd45748449ae40de993ff8a27a44fcebdfc9020998f6514983e8389213f60de10061dc33e210c3a0e0c7a10067d981e919a2450d16f2f7fa66bdc9d52703fef655ff4fc435435b9c1b084fa9f8068188184e030845118110afcc0a817eda3639fa9d638f234f3ba1606fdd7471fefc41b9b42c04b880f3f147955f79c55ad53622efae84f5bfc41c9022a67205935b451fdfbb5de1d0901c39e33828a3e7e48aba8d6302a460da358aa63b58651b4fa8719a8f36b8474f619a55df721887d613634d2b60de38078b2d6306aa586515c0da3b6ea5fc328ad8651a31a46895e681855ec67c5be3003bb8ac1d2b4920fd89e93e2fe68fc5a4c4ceff3f449b24146c65bb6d45086f3e12244f98756391aadfe582136200041b8345ebcb1efc51b9968fbb1394dfcc8d1ac918a713494926c10713458f51f71378c389aad2491bfb6f1b66ddbb6315bf19f619a4f1bc51d71bb9c58365a914f333ff94d24748cdeffbddb525c7cb01b83a4e7838dee2ef3f2ee322fef2ef3f2ae0b16d4054ae782056db942d35c7a688c16e37fbb351834c2bb2368ffd0c42677f79ceaeeee9fb7732f37096a1235dc6a7b96755dec22700bcbb2162f66669c5665666666de8da168b1866f5bdd976952b7c6113f35fe787f60cf2ff6696b7c13d419dc9fcfd9cb6f7b7112cd861b6c60928f183c40399af6f675778fa6953003b599c6f90b33c0136450f6e8a7676f90318b58ce16f91c828dad906aa4236da3116f981687c46e40fbe5afcdde708a7b89935941b38cd2aefb9716e814113e4907c453cb2deae6d51069161041f934a20e1d9876c7cc731ec827f9222f870c8f98ce32950d83b29be5aa3adb1d2c5c6440f7e38333a6ba4777d7de9f396ff3b23ef96bded45e14e0d3fccc23b243314cbeb6fe8519a8312bf9bc0d4e51804f44189c60a7d6065c500573ce1762084ed9e089bd058a4c51c6222f9b73ce2712312fece6ace2a58607ab4b8d92a5741c2fe924091ad2796b69186c59c841415aac116231f644c15481132e55489728cb5e31b4cb8965a2d16c49d7cb67b8aaee3921099bc553c6c49c96a0a103f9d6fd0b7cf18d4b09e8ba7405f18d8dcd11579094bb0298d22998dfffc83875dd9773ba67a7f4699f469f68748f9066f589270a7952f4a2f2d21cd5eedec4304df462caa5e0c550d01dca64703b94dff4304f63a4b99751e75440f979e34a54ec10ab3624bcda59a9d3bcaa193b5efbfc30d864bf66c2e06cd6b6d57e6ddb5a1c855bec9a51635c271450e638ce879c3edc3fd03f92b6432c5d306f82f1765f432d2dcec75c195459a90de45554ee05d8345e807129c453d757f4a04e99be210ff217501725746fd86c2e3e35c49fca1bbb309ed2a79ea6e629eb05d560bc66c2271f065f58ec0b67c5ce9d6a6dba8c83bea591481add28e7a37b8d6340101b46c500add9cf28fd08a2c308a366eff2a5e7bef42ee3472fe35b1874a146ca20ff864074957fdaacaa0aead494751ee695bc66c91b02a038c6109a294b0c00b4c898e182bc912b7973d331b6920fe5958b12ba1f722bee8b999999bb65aa0680ade443e57497da8ce8c24b72141934656954aeae546dca9d7ba8a46e3625b1746a4600002800a3140000200c0a864462a168389e47b25e7d14800c7a92407c589a4a84498cc3280c520629030800000002048080d08cd04d0250a31098e106c0adf602ac265b1a4c07b3f479c66cf619dcd49303f938dfafca7d9a4e0cf4458684414caaa78842a000bb7b71f4485742a956e6232530884323317100af3d90f347ab5ed6d25d3dd9b87adecea6c5e87594b6d1838f50ec874c23f185f32c8f872fddd7c1654e8915b834d788361149ee09724d8614a5ff669be3042b66303ef4241834401c167f48ae9ca6c74a67c7a8920d7eab4ea1e00a5bd3bbe7c772a8027b441a109f57a60ff1a979461a860726939df0e9333d73a45a5bf28cc7ce17aa266109d2a74347080f5707de57c3cac26ba78d676a7c150fdb995f97406a1e622268d2133554e4eaf08f432751138e98b392661ba26b196e08e49610d0876038ea05b3b66d36c38abdbffec4bea6cd7e52abff6be78db7980bc6246b4c159fda3f4039054fbaa13da87a16497e251ae97be1b9dc79904db5e990561781ac95db096d503bf25521db59541935242e5c3012a7ca2c391f13081d14180ef9781e4ab847a67490b0b7db9f1e3105ea494c43ec97b35a9152d0d157488de65037108267c916b9c076b5615855ba622dd3188935545abb926864cbb1bdf28a6a588b5d217f58bb75bc445c9d277c2b62a270d1d8730401e87994c239774f37ada719e849b0aa7ca45f1114293072043a13516bd70242e019b4c17bde90fa6e0df44c6de4dd2fe7d9ad819e462c41d0bcb5a38945365161d4255e59b5829001cb58a832e8ef0a536e5657f85b812d4c0b017d0014afbd067a7ea0219355cd1339de245efda69020163d7c5baa5afbaf4761c11ae8fb71eeb7eac0dddd584fca5a5771bb35d56824b000728c5e02f7d4215ec91cd998b0ae469b15ae99dc845cef93009b4941a97dcd5b0c2d23b18bdf888cdb51f9476448f970e83ee57ddc0c34e92fef13a524505eb9039cff4f5d51b41ff44cfe887fa58750faf5ae01e3596f26ee1db70e9a42791322397f5dbccda2a713836379cb14ddff665716ea77292d8b4e9e554f7e04f97c225c8894c880ce91752ce26ba63492707200360de5b2e644d09401c96c73046a160b2f12da07568cb361e94ae7094eac14e33ff1f1327f0d5043b6f6dc06ff499daf706b2e7be1a7dfbdb7ef24bf34b6c43ad8fa8c7c6a346f9607759b9890bfcca2c8fa8103a5acbf917a132b5cad115edee21abc417d2b0cf9abd706cc424ef688b10128135efd9e9708117ebd80e2be805cd0952d6b02ba75653f4f08595b6e01612b12ed86181db35e6277e8e4d04543c8b691bb218553c5b94a8517c7560c5aed408ecc8aebb2d852c25a434ff78d789eca491b87eda1fe51a7fea70cfd69e4087e6197133af390ba55e0e30bd20111119d22872f15945bc8dcd49c840921c5ae356ee97a4008853bb6703d306c877cced7a48cbae4fb1006b861cfd1745771555f00c5f208d6bea81d56342c5a559a87202e6442e6cb1f1f4d44d6c4ef5b220fb6c8b98b9df34c632acc8bdb69cd5a2e1c49013d20ca2d1989b5f8221caa83896741f74f1a57d143042bed7338fed1662e548cc8eaa9276725b51ac66ff9fb3ca6a9a74e53f0b1cc7b6b00ebc5dc001775792338d703167ca57ff04637afcc1efc511585bb09a206ced03d7badccb5121e3532ecc5bac651175ba4342f705c3e2602ca293f484bccf7606b366d53d43f72f2962c836fbb56225084fba33bb0df158fe54a7a8d5d0a05ac289116689a7117c25b25dead2065322f80a8b988f6f286ed14c537184d68bdccd41573abc1e2a888004bbbd8374a21a40c6bcc0a63bdb64f0c6ecd9fb91bbbf2f53da936902efd9fbc244a339e715fa21245e68cb98b7179c97e17e31a79631f4645b634a1e6269319c36786732fdda54f3c0d38bb3e634279982086bb29b6505fa91241e57f6f741a178f956962e2b0a737ffa1ceb55610d7869c88074c401fb7d6ddc7ce81669b081ac3ff82e80010dbb671edfa8dc2c0ac9f42568468b0a8e34a124de91f38975dd31d8b902e37d87f9251968e6b03e0c09037feb751b2477053900674a24addac25e0040b8242527e8a8d7b88939264f257cb6073f84ad5e21edf43981ce1e4739ed71609fddd67df25a2798fa2ae973f1fdd7d66a183b257231a6e3af28792ec2b4a7f67b3e71506b263d31fe2f81b8a225916a9004b37f5e529fbeaf8d37fd7f243ed661d22288136fa8aab32b084d8f6602ebd85abf4f17859a1a6d876f418d4cd94726e6cfd306b9b239af46441788d0d7c49f2555abd07e669968b85fa94cfe6f957aed0dc6f166e4669b44c629a649a888653b4fc127fe51c7856c90b4d7daffc78049d12b915145f4a489f99e84632be896bc10964dfb5160c4a2743cdddd0e66257fc0899d3cdd80582aa90546a2953a637c204d658b4995fd34b4fc8c341e447ef9ee97946ef5b2c0ab7675ad68a4982ec44f87696d48f7d702583eb491b3013eb4907f4126c4e9e6ef83109f696c2b902e75d3ae37fc00bb372fe480d5aeffeb319fc9fee1f73751a634ee57d3c0673ba9ad6de7031a0e80da7448694154d539d53a678732fd46d9d4d1b783e9e283f56af79ccf7f7c66aede5e7bb8b72a826bdd32a800c855d2323e89636ba18adf62477b37ca7afe44289e369cfe15d3393b3e0ffc030060a9717248231416e4a5dd54ea7e7a2c68e815091faf56fd679ed1e16e159c16f4a077d8736d6dd81928844e3490819aed4616d2c50d588218be4422bd771aaaf8620722326f066357b98ad011137373d4b14f56aa911ebb194475a6907e0520e2b603e6815bd528056a45bfaa66fd4a58b5c09e2ee19321baea98b1575a0b3dc37ad952c8a98f6d221bff721f86ca799d08c81dcf0216930e2f94ec0738acb6670a5f1b2c656a51ad8e0abf8c30a2b3852e1dec24ec13673856974cb01b66b3b39fb4baa210ded68738352a2b051ae7708f7a958b93c010862820e4af55758525aacc8637c052982d5323c15b8481d5e20feb4addbfb5d4433b8df9024a3eea4cd2815ded4f69be5998e7e4f3c0d4e2f701805c53f55046893345514100f39279415b6dfc737add6c5dccd85ea91104f65c50bf2080fca0f4132a1aa3a4b227f7bed66a18d425866786864881bd33ecd84c87daa3f88410bfb717b7742fb94aeada8f9d2f45440683fa45cbcd1bc3def25ce0115b1a50731bec5fbc772535ede8e32bb6f7af85c85e417d05bf8510882cb90efce510ea52f9c7aba755d31ec73832a6a69b248df425b9238f738a06a7ad6058609ace5763ff3e83bc1d25ed73ab768b1d033152dfdbf581bd36c13b6be7aa90cd42c15580ece14242495366abd68c7fe92ec18d73d918ceb7dff1dc748abb7667d470a03126c272854b3abf1a109cc7b5d6b40662a34c7859389192645f652ea28e9a29907891338cb7be39e64e633d5b67fdc1b4b0b7f1ceb18311aef251d0ecb4b5808d5c21fc6c2630e3e94fc5a02ae3344912b2cbea76d693aa21e9060ee63d8825e59dc6184f12ad4f2ed48ddcd0846ef800b537fa705cc06eb542ab6feb9cdf45888d6d7b1c94e6b3ab84110af23d971f92148d1c281736754d5be1952ad08232a9b11a3f733718023fd5b3f4da237eec54a741e4233e04d0e2109f27b0fc2854016d0e450840855b99132c8f35c9a5acf934a5d984b3dc1c2d99695e585cfca3053bc1d8a71af7b9e78e0a7580c50301ef5988d3486c21aa5242188cc924d0cd4eaffaa3fd3e0f55ff3a52a1854a3eda58e8b12ea22cc85ab694741f0f382b69f516ac3fe59af23c6f948245c5add1e65d317fff90cc86767e82b42d2c2a899052f0583880cf9ca3cde22722ca20ddaca9d3a42706c3a22df5ce90cd7c1cac4ebe237e2b31a5ee1fa20b556f76a526946bbe851368177fa57beb4e77dafa4dcfe2905ee84d0ee8ffafba28244d527dc32efce6a69758a7f5a858f28293f3a8ab849b45cecc1daa0838e2b19f695ec3abe07079b673498d9207209ae34e8aec2688d1fb5554ce9bbe98b38d5f93593e67bec184c7fb71f663bf7794a3825cc78ef758fa0b0bd47c2fb540af30e076a4704b3f69e5fc9335536654594ddbf1bc6b9c72720c0dc9c85ae869b36e8450097dd5aa699a929f5a203da0cffe97cbaf6f266101ed62bb2f1eb11750c18e9ca4300f3c59e84ecccc10dd78b150d076b82ae26b8e015621b7d7f3413d2cc792fa74e75864387593e8c333d6e7097e3f4134ce689cf268cdd92cedc57b30679d41b431a2270ba4ebf5663a23de134712980ff0742a1cde92e5c29d57ff5a46fa2b94a8e38aacf19e19795a0234b58538cc332fe2e7b315a79568498f32926accfa1da0483eff9b576f45266b799d207e3a34b53eff581b207936ced7b0d60f541dbabd520c2a297a1048c3efc35bd0d681a4e3618483fe4165d0a43654045d6204c4514b74759bbaf259605f6a0cbb099b74421f57b374591c140f74442adbc2839de69380b644eb011eb7392e3244ecb94b846eca83cd26119f4f9c2a00c309a8ebcdb1b1b8652c19293be95e43199e237a03c8fa32cf85e508e8c698fcda6a6e3a36e5fa25cd46c9a5cefb35ac5474cc4752ba77024ed842e853e913680aad284d3c0c801c9c986f4e22db1d82f641827af62de415aaec0681a5b37b546ca5859917eb6af22375dc573e1dccfbe2397dc77fc62f16a1d39c3a234c573ebb963c118b96a8b49ebdeb9286e1a132233522a6e99fa5321811fb7a9e1577214f39596a3d5decf0f7addb4272f3a0f2af01669d71f48e020c07ac3d8e2dc652ed0fcc1e4d54d964d138ec40b78fccacb955802ebf43f07353191c0ef7908fb4194dbbc5d57fd6bff8ecb170c3b459fdc7fcf3416bf2b993bffee963d61a82b3911784c458c615cd5e2db83a4ffaebdc231b70f3fe2a874657acba9c1e06bfa986919edc899e9382e4c1e7131ce44e8f178bb01841b74b71b730b7d1f14922835643c8ff6696ea265b928ba35d4fda027c8be16e2d928134cd52715ae12ed3360d3674e54bc0e6bcff719eabf179f91ebf8bc1c8b30f1821ac23e7eb2887afd8b67df4bb34c12c0891249b2048ef41c44bb07b7d158aaf9704f5deec51f11c4cc5e6395b39cd32e90350f217817f902087612f461baa94d33d954d39a50cff613c591137e930f8ad318f1b51c7dc523a3563f8c78562eb54f656f0ac2489fdc900c46da01cf0decd88be42606d4a976fe8bcfd8416a588032a0fcc1cdf3721fd872059777d05f2a4856b1f9a2d9bac03fc663ce382173848aef38995272b366c37abdbe2737a27a73bc308964b16cc22b2477ac73ac62ea598e1ef0e06e3bf67ace489f0c6a81bf951a85980f59fefefc41ae02aff09a917a0797c03ddeb838def4c1dfeab7d2962fdf358eae858a549f5d80564ff2f3a4432d8ec68a8d85eecc3e5a61e1cec34d66d92bd8adc2c5338a6ef86b28510a19174437c390d1925d9185ec14e93598786055f0dc2a627610cfdb27254754449a8c418f443572bb17430a438f4d4a428e6c1a249cb8430c2608f94e07147ac4f7831f47a516799115798d26c2d78cfe10bf677213c4f701d5dabbbee62d2e3ac155cde6658bd830e7455221ccddb249555e6e13ef82849e2a0f01ff9b10efcf97e267dfa47caa730c804e92659caed3b01c6d57b4bf4eab69ae755ec2e33a152f58cbae54d3cd99dd1758d7fbadb6ca91a769511caddbf145e8d6f38ed038675e3e7ed94cde5d786ade0ca5c55d77a7233c4f09dfed7cdb620bf5a07b0ce35155abe717e267b62e6d1885d43cf2390a0a42f9ed96c18281f5b649aab99b056e11dd439d01c21d3d1e9a885653997c006151576b705f54135ee59bcc78a62f11a19cfe37e3749ae79a4914bf41fd6e7a59aee27c79c3ad2d66787b659b79c64686614ccc6067f92377852047e496cc9b7102b0a1238114610f8f937ae256005853c8fa63c90ccb8a8f3f4027cc3b0d50b32bf296d1d74b459f30d186932567871fb56cabd128eb63bfc17b24dbb0db2ca980d96da66a19569fe867a76464be7a7bfd80b01fa25ac3e57ddb0edbc59de2859c001188e83c5793e37398a34f7ebbf64712b1d1e11490f70598d55f10fd66de4e11d6c33bf6ef8270d38e41f98959163ed6cbd1c0058e77c41066e87a21a5509521881fba44514d20b9aa630a17f6e606ac9ce54ed1697242c0386021469435933f5f3a5494955c2a66553acfc2911ff89f927031e77da7093c52e85397aa1f20dc1ecc71bb2f7ac0f8181c5107cb78c2ef90e798e2be4c6a0a66b19d370647bc239ea03f4bbd3a2b00740e2e5a58c1d367f2183a5b199dadd7e2911a7a240e66f9fecbb8095174dbcce011ff2a582b64fc6638919d251662fb00b24fd28f5de0adbe5fb0202fd62d63af4eec42a470581f09608d942a24f3b991581ac90020014aba57ed2cf6be335ca53e8eabdcd43dd03bd772a0fcc3b143d5d35878f103bf97641dca858a218b7774bb0149a06a598db924383310f75c74864c729cd19a52c2ecdc44cb197f0f0492c1e70dd748300fbfd04654fb881b922c4a0e417941c4ca047864ed0068577b527dc069227f44871238a4a402d8d4fc3545362e69d7eb308c3a2092a804667f3f120add195b31c72ebbb7cd0d49a14372ebb1c0f26063745c8b8660a3faa051dbd0454f67bde298db790490317e94450c7bb9def8ca7db188267e60a904696144c3641a1f4be6f95721837b493cccb92fd69c5647ed6fd7a4c3b7baed6932791ca645f0b5ad890d7ad63e876c9b331f7ee94cc51c2da4eccd7de49c11b1fd891b40444c00e25ce81555faadc6f842dfc4c014d22a954d4a5746ce5ff9a90c5e204a7fd278e3802a013e383d74af85d2814208a15b752aa09b8adab524b496b6a46cddd8322e55852ba0f585311dca821375a2da23b6f5473ac660957596b2cc2291384667a861dd284752a96757b2bd3c85210fc2185822e4150b2b136c248a4728f3ac66db9ae60535f58d154e761ea58eb64ccbc5f8c43fae007c94e80ffa1c8d0aaffe20c9943ceda6a539afa3a3d80ffdd3930b3dc192d3ed79f4b8de81ba779be6c12b0350a0a55a4438cbc5fee74f0b568c4806c9ee6221488591ed0a9a852ac301bfb9ec332fbed4183971fca3425084bc1f255080ba9ef37e5e1d40b4a586b7200f6883104e109043e99b61f833a77747cf9f7f675f6b0a198705877d945790a0fdef8589a2a47d060b89a0212de8822bafd2c3f63c148114ea4b070aace8494d16870f7b02d5eca144014d7fd09f0673e67f2db6e1ca2a6ae4346af5275e939990822ee4db0752cec045028c9bb95d48a582ab5d4ba07b8658160611f6efa6fc7811ec81fd2a317d81baba9632bfa36cf7b0f3f4cd6035bd0586d85a450a7a2a658b1d5edac5c513fe042a5d276b4e55808fa5518ce414611cad65fd993195564e64ad1ef27e3895eea8f7667ae03f89f4338a29c2ef30d1053ff0735862fe0b9d8a85a0704643560c3f47eaae96cdda4420982a780f875ee635399213b81126b4308578dcabf55cba1d5768443b170e69050cebecba3fda815673d27acf13a607c24a41e509d88265851b7c5d3816533181adc715955f5193bf0fe90e8c03fa82dfd531954905ba05e169e7126c2442ccd5928c13cdf366d50dcf8200daabb9ee451002f20a17eb1a082f810a9e05d4cacac5bebecf3a8bac649965ac83155330652c6a98c206547970ceed5e84a98a49adb0c1d376742bab6b5aa097179a874610e1e53fe9cde4f150a86dd1015a77e0e1d4abd826707beb26c785899c14810fdef1df30821debeb7907b4dfb48d2a7db0a8a626366d52534d6f122629e5db820b57d52ed8e76ae5fdad67708c8ce7151b4515b51fe023e482bb1f182d7fbd07d404a0edbbbcffc34030966528820d04a2ba225189c225626764e0dfae2e5494856bf6caf8b5160295f62c81f37cb1b89ecde3484bda9545277f9c6ccc3482040894f14e364283fc5b269ec9a2416f682bc76265c29680d985ca098af26efa11e5bf8cc9bdb34d14f1b264b23b9dd2d0847205c29fdc05f48aeeaadc72bb0568f118379b6014bbb3b2bebf71a4e9541a299b25421ee9645df975cc2009a4cd367ed5f729e407231061cb7e98bd6bf66339c30ae4b04eb9b084657796c2d0c94b01b0d97eb9b9c47a022dd9de138313b586d04aa4cf2477d972f849c7122c84834227ca441c85d5a44629771bf2ce8e5bebb8985f8b2b62b203d3b4212a7ca51b3f25537da264003b08970c243c851ebbc6905f8258bfdbf1d537c5ff6fb4facf68524c1a2a749bd84e49ce091ae3606206959024677706955b7b6e6a2a8eb07fc1f78ec99458a422516fdbed2481e7223fb6286fc46201d56b0c953e9b45a09b70ac83b4464c00923bd24ddb43275c49ea5caa43fdbf857bc2e2912853225b0ddebcd94d52444ef6723d773155b006acf05a082bf38b1c27b0bb78b2c2c293cec075e62549c043b6b48df0d33e789f2b2f70cf3772e1dbc5e9e51f57810f9d7d3b4cd4e49bf1f671c7a88dc12ee11f31dfe74494735b766257bbf1f69ed8f53ee2b2376adb11bbddc55c76d5f881e741386b627936c45a22462d9eb330d726774d72e469bdb7d36824fb77afd7221021832772764915a89e8c54f52e50b96e38d354e27b162e944d92d77498346e253c1d83bbfcecf69429349ebe4b7e208c6d6e6bf12f76a82f577dc318e294a180bc1c5b62f13dc5e0d9a4589e4c1d667545f9976e418a26d0ec15ac755a3feacd82a4268ab290596ff84cf773b5daaf659622cad78bba496ebb74a4da67673c745d76c908dd69a3a1e70941c1445159f9dfe6361967de0fbca1ae5333740bc7c08221c07280a76c83fa44e91793f164c2a4334ddfc42b791cd8405f9a12b6792ecef386eda56bcea2596832acf121b2391963a7959f9854538fd5ee23809568b0a938bebc236950a0ebd9ef9c2a5e8491fb0eb39a262ca29ddf37bf533c23d1477a4b4b73c63296481ac171a8cb1e550019b6280e1e1fdcd7184b5d0ca06d9846bc73032405069eb51033254437452dbd0512f4b284de6292db75a0e8267291606a3c7883ac719f9e9b1b4ad224b2f84b9c88ab57b5c40b5e0695783295fd692296f5f11fb2b1ebf9ee25e502bcffbf37621144a71c2e6dfec06097e22bf06f17768134f8856f24bc809043fa3b338cf462accf232754e8f699bbf97cca4223cca9612a2542057d8789573fd7c3895e1970310b05ba9c5efb244dba14536d95edbc4c9f647bca058437a7d85098ca5472c71e7f7b63764d82cf48e914d25b5d4f281964f2b96ef69bc0704f0295abdc2c3526518a650fbee176a1d5817a73d144b2d7be984dca57b9f912f13570431eed019c22f43f316662547b78e72f816571197c75d5bd45af31db8fbf6b04ab26155f518eb63d9b3edc4d13c12f8e300881521f20c21ee72a9e2b706fd4fee78d775eea7c0e2b5321278c913f770b32d8fdbf3f368b7cfc2f3110f55202c8373db9338d48b3bd9398b9429498830aa187b59add40e16ea3de6503a8c4fb38e7b4cf75faf897fd7e14978f4698faacdbb431001181a0476c962a0406945c6067e1454427a2ad81c7fa5ee4db5661063dce5d06ce5da00967b22e654a6fc752820baa068f0f1ed7c4c72afb277e4ff6a490ecffa688a35a70cbe183a0929ec8cd4f348e48d145f45c8e3214e4864a4edf92e0c4ffdc06f1bf99d35b5c889499aecaa5117066be00e62a6cf86abc554616bef177e5b05dac1dc12594a8fa6c7f14f4df55b75f575542b8542735fef01eed7a65d2921f8584222f3cf2a1ecd668ca87115e6a9bc055067343fa61a87f0e525c918996bc067b17c34be94cb10cb6ae4a4770f71c55ee54719a1d5ff89d8bdddfce26b12cdf56aa86ebacaae8c7900a3528251ef8a50aaa7ddae0e427bde2ad738a04291b69ef8869e2d6863e0e9aa08c807f667625e01f0d5adb4e0958fd3b22d03143e6abd51f5341430605b14ed840e84a3a1b798226e816df536f122e3b36c6bd116c70fc6338cafc00681e4e1905a56d2a39039e02c8314504f6588cbce1da9a40ce8eb2223ce8ac2a2e09c7df98509e4dbf5ec27f136b06b62a95049d69d9383bf8c0762fe051bc79ad63677c85ec5e05540191b2f5aaae3e8418dc0cf938cbd00682e87d7c4b3395293e4c87f91826752fd8ef6625ca7615c2f107c48111499068bdd166e382f99ff8f0c9b42e6364774a11f7396306903c3323c3d7c850faffa4db5d1bfa2f7f12444d70d1990641f1c037e559f24bfabda62699cb12b9f8f72eb9d6ab5396704b567ec3e85d89bc5b207605c1fb2928989f966b3c1f331a3426d0a1857dce3b02577c0c8b667b2bb3d7ba04cc8f27cc17e80f08f9bb0882929530a0dba491bab9eb7520ead756064b1a53578b82d52e355ce33cf7c51bbd492f268a5086e8dbf9c81401889a2b611239528c3e90874d61c5af30df46e057929a6832f0bd344e46c62a09f0ded84cfd9898e94baac2e14c9cbd1cd3ce6d785926d347a3a6434ae94126032748000eb82488d95bd4abce2a3667c9b89850cf156d35a4487a3f6520951e6f0a3bd9d0fad17feaad6dd903a05e533883cc586f3db778a742c4d7105cf075e917c46e8d6c1d5039655b9ccdcbc4b5da2ccd3ca55e9bc3f95f0ce833bc1d819bc3a95ffd1f986d7333ab865362c9ceeb9a4dd91d5689a2bf60b85f8a7a49a5ff6198319dbb59140a8d47370026475b3baa45d78c5f0061cc188c4c1f6d8b59dc2871829c6776647ccfbc55c3c80e2e79f7e8b0dd3fd1c05a8e7ab0fcf8aff3a1ed265a5085715ad594f508c5ebda885e4ad07662643832b7ff6a906e09f52374ba5983279e8289c8fb47f191d195c8434b974de943adcddffc9c8c32c52a9ac11fc73593a016daeb5554c6567ff4c64a031b5c69cad2dc2d5d6b298bed28d922ff4ccd4d78d861c6e10c9e7c39426c7e8b3ec980478f865975ebe808a9865a954675e67c0ab3a7bb5697844d015f7fd00740dd7d5330cf664d23480186ef6003a70f507d8bb1b8d57bbb59b08c5780d7a92f7c7cac59f8ac6bcbdaf10fcbb3bcb38c71379a1b268da17e72881573f0c3b0e22cf2405cdd2a28147f3771ef6777d3c9b0337737ae155e441ad508269719521fb639cedd353981fa9105736fc670caa65285cc1314745579f7568fd36d24cc1beaddd81abe3d2e2434753b9fd95b1feec7041efe7342f7fad1130fbad4c4fe4d2b00fe1ba71f23038613d5a966e317c7f438bcb24894387ba703c2047b15374f589f42cb027034ea95f30b763355d11a4739bff1aecbf9391b95f3c38e8b9c5fcb982b1433e089472310282a13ed2880048d3b0a9abcc3ae651e603e4a8fa04218d7ee492527732a75765d1523e8be2ba666ef609c54472ccf2e6d9cc3fd2ae90765b3ed20d2b45034aaae698740c91972d7812370272607af0d87b9d96a112bc67472d5f7a084d03d2c7d36b2718401d4932813db389fa16b0ec7db10032e1aea7a4d8941e8624536b7ddfbe2523c9a8a318e4d283f26adb70c13e387753e43357f11655d42032bb43f46a213c5ccfc804e627806836c0975d700d928c8d62c55833bdcda9a5c01a9431e315e7cff696838b82ec547b85fd3dac08b198b08aad26c66b8a78fc3c149140d0d8ccf72d65867f1ac7d2c8a150ef96f8f1f163e331460c4881179418d164e514e3259de8920c2a4eb58a084abc60f1ab8b61726bfac1a0dab3abf6ba56a4e075ad5d9bcce516ba80b080081eb04a135e9fa1550b11bcb0650cb9d818f3d251ea0df0139bba7ba2991c530c17d27a203d4453d0524620ed2f6c51f7617cdfa1f91fa79cc56942189abd86e0b19653a4a97ae281cbd810a527e061e225b5372e60b7086cb78abfb48d9523d10196b7be226f4f7896451f69987ebe1474f055d4698b2c09aeb2b7ec692cd339fdfe885706e9cc4091224ffb218fdf9adb9a4907ad24a9cf67c98663045384f92726278c9a26c9897934bf74e80401475c58d6a095eaa1fb773cfc3f002b877fea65728476261a005bc3327bf9c90e5ce79ea5d343bc216a8832c2bf7eb4845a099ec1eaa50f67d85ca092cc791f63539426839340f3908d05b7d6ed312c03da087316a381feb09ca781a73bb5b131194246bd19eae28a708f05921fd53f88f847bfa40ffb7d828970025e88e2d8e86825e60b8ab8a8653c85f4450830cd81566302fb035bc2c46f745607ba1ecea939a957538991b832a6309e4f46a62733338a749a48265970669ddaf3af512b0d235f31a4deb71d383c9de50df7ce2fa1e09abf46f80c7c275a63fb56fe34f207f24a4b8e8f78db4511f911f12370b655b0f6615ee350cc17881b63f706238a79da088545a66e93a3795beb607efabc846485980aaafdd472abbecacddb3edeacfddb757c8835d313d50855b3ea2d9697e13853d11159ef265f150828fb1fb661683bdeae99723b4792320f454c813498ee3f02f83892b40cc735848541eaf41c6deab4683986bdf951dee94698b7853af4c76c5c411e4ec4a53a7459a7cca498838cbdf60597469266be562a6d3e698c868e2fadcc3810c3e306af47b67c4365c0dc5b24884f78db7cb4c3b5a47af392ab895157d3744af6edf632136d3d38195d7505dd28a640211313ff4f3d41e787f10dc379d06204da2470c5266d324f79c867d0861045b634630e149811b4b139596a559e49f204cb288081b3304d693b8f31b5eb4c555ae21674bbf83d4ab693efebf95a9d35c4885f3de983a9a56c8bee407040437a0e99776008e45dae1e8b3b03401ce8f7661676267dd0874c921548f4fe3c12d4307191f4ca80a24e7cf6246ffef38f1542cb98a6e1b1423a8f1e21ac22a6d8ffda37a0997d5889341f9bca877efba97eb4a038e6176ea558d325e3b4fa2dcdd5d2cf07f9e766ad0040d62b350c9c61099f01110ae18a9c4a1d8ded5471f757c720788bac022c852478cd7989f00e202c33ed65ff58aa07f3cc45dc432891f9d482ecde48c52b24a18c70c9936fab54700bc6208a1435416885cd1161fa9adca5d778be695e6164ccc7924e124f0959d987787684dab6540228e72660e8de1113983048fa6b2d33b8747bd4ccf9c4bddfdf1f262f83a275d53f7332bc4f6d8e37c67a0cd653b960502da974071224c401286cf89d8cac66cc23beee66c8855ccd1d5aea479c3706a53ad90d4914462df24d51850a0f0db86c594007efe4b1080fbf7dd1a72acb60ad2203974763fc08761c6af2098125f0f0835ceef65a19172a5418d20fb4d4064ca4c1286386ea595d3a656ddb59db73279b22ea84fb57fb1fa8567e57337c17fd5bd42027910b16469b715e7c974dbcbd6ba06aaa1525991e4686f40c019d5bf0fbca3f905906fac8f26c5738a799928b739c2ae44ace20852ea414c9d387e16a79518b0b921d76bab4a52cc9ad8fc9474997ef607c57cd2df50c673a43597be3994938828439c8974362d08344dc17a9ad0fc1e75c4d86f5a27dc75cac2f50681ff10811dd2c01c48d0bb70deed3e9eab681d5b556a607331d49f4f4b635836cd572eb20550b0d42f59b223c5dfd2e6c010f972cf83088de2ec96818c9fd3dc12b01e0a5aae68df9d984c877fa841d115a23509148c9e51692ea3e20eb45921a9a09d320637578b34ff7d40d3dc981f7685b12d5ba2e4a78cbe2d3a97513a434ad3aaacf6d39fd6a3f262964e840a9032c7fc44d1a38911af69b50909b22632cda1291d7c8a5b73cce2d434215d4a25b8f6ecf69502b9101a56f587234393cb7d584c27a8a5c50cfe6c6a10e6029c4bf54257c5916860336974cec13953ddceca1fb8c6641f59f4afebbcee2c2e94e697f19a7144da2fc903a5c6a87661043e758157451add1d5d2b686b747b490ea134a35b651351cfa1eb07ea57cf7bb6483a6bc07931993a29ffafb4ed8ce525fd2e7e00cf4222b740075451ddd122b330d48a8be16d37f395b44f29ed3d4a204fa6c08a9c491d7e90aba2537cdfd09370788730ac5dc808b2f2b158078c6c036424996942091b653782c2f17a3ae9e6d84ce490248f64b9478bdcd95fc1625d8b4705542465017afbe238e202760dc7d32280edb7b2e28672b010720981a1602c93da8aba489de24a58c014bb84412c8f22338c4cbdd01894562e6dd96019737b4aad195f5e27af6441e7e58835275763384c826431f78f62a66378598174461ba8496560877ba01b8e8992a0e85bb5e0cfbe0b8a1c489adcb1a8a5df113c00669d91e9f61db4ba043a1f63a3a424e40d728685f607eab893080581825e82f051c05b47cb8302ebdb6132eb8b8f40679dd67cd37694ef96b47acd33e10aecd126651560049e5e40c755d868674eb4b822676c942c4200ab41d4237a2bf05372c2ebc29a342fe58a08a10066549f768951f8f7b080e25acdbc76ef519b147f901227410c1e498e26e7ba769f3850770a601880b086e2365ed4e5e92ec5b8ef4690d8285eb734afe485b366a8baf20cc6d240d41301d4b64af3a657131a6005c0700e13929d1327338e74c50cc3644b5f047c400a94b46607fb3421de629a06c41ea6a7354a69fda3c5e3f8b89178267b1c24c7b032e72352c5ffd7a904697f718fee513c927b175d1bd12203fc3de98f2bc088e9459d026110297b0030e339dc912136be4a59846d9cebbb66e2be2b119f7165175e3d5c052fbff0c111581ec7c6184e8a4ca4d4e1caf11f7441b7303735e6fa5199a235afa9ae7cdc335d867a97380388e0e3a6970c959f17a2c47fd84b709cb83c12749c265531759319d00db3cffd276c565b6193b7a2c261f3ac211ca5d45c037f94476d43f52a7359a35597f3fb60f807f4c243f083b79261ce1c5c158185c107d3088c04c57019d89eda9a0ee3c137974a10996999198e24b6875902f68882a50e3e1c32af553a32034e4281de0cfb292b8f709974206c70d303181847b8d78e4fbc942c2d356e770af8b3357b388912e6bca6a466556a930ad32adf825ab29eb2da03d248be84478df2fb505562a61b34b6a9fe404f771ed9a398207063fb96dea60175fb97c74a98470c8982f6529203b58474595ddce4f048bec6c495c927e501e9eb8e82c78a160957b8da7a9dd55efd0868db50efba466b39143dad72083c08ad6c21c3a3a29f693f7b8a85d86ce7d6144d308a18c3cf7c723ec26c28949cfeaf562df8b3bd515010a1fbe4c8dcb52bf78b2f0ae2dd026d37c4fc6c81992b706cd772012372002f72b42cb4f0917f7661efbc05104f3a10d39283a6fa02d93c8c51616e2138ac45b35db396aad448a0d9ac6e0be3f5d4a822c84247ff43da2ab4d39891a55d5446818549e264d3fe79cd39b01e1a8045cbb704a51d322d462dc04e9268b4e9c5706d9b14dfe3361c89b6555a57e8ffae1f39bb91529b79152d7694814f691957d0896e068043514e784c09823c812c005cb984169c9098b30ea24ca1e2b0927e5712c50b6b6bf40cf431c8dc752b666e124d95bdcad2a15ea74da391b00bcf319e41da77e5200bbfe362356e61292cf8ee6bd071a792e0397a6e864be0518acbb91fac586c1d7191920d37f1dd08b542e985e5b65204cd7091e8b14c4465d3310076b3a82a064c65baf3090c0021151206ebaf2dfbe9840583bd456a22479eb1d90db5c09669d94ef6d4d6fe624d3b13f31384e1d567237407636eece1c91883609a173d3cfbbb76566699073b4a583a400acfdcbde24e422fefecc33abbf2382905d05270db12a3837c0b34b5d65912d5280605ac11693fc8425c3731c6f63b7641eb465d9a19b1a21a0aa233931d9212c32d37018db4226fbf283dddb0615ef3d28c53e02a42893b58a23c8acec793e2b8a577e4dffb68cb9a9146599261515263b38917d6233d9b4995324c8f18c8c04462a91ef35c4599ee6c4c748a38f6aec9fb165ba60f505ea52d1d10d28f512242aae747858e01e3a22af55542437c84c641db0d90e4cde3489ad66292979cc312c931bbdda7e4cfbcb2d0f27481382d221585019eefbe6facac8bb31381614814e263cb89fc2ddc89241bf54b02575613e339ee16981358eef77777c6327103518274ddb64935c5310e431af2383bfce610bb7874a1c33da5f4e438dd78978623c64355283e46adc2404a9009ba95c68b69be5e40ad173bf6ff2ee6ad4c8b4cde772abb67a794dc4cc8f4ca54a401b44cfd150e4295790aa37db011b2787afc59c9dd6046530e514076b08293838bb44be524793050f641d427a8c4d79805863ad1aa9ad1e460dd172c47289a4ea52d1e94d39204085a7b7b90fbac9fb75e6508fa60de60025647b28b5132afdce63cfeb27e504352adc59d5554a8ab27b7aa3caf3e349e8eb335e07de14e020f99e1f870ff9081cad80860148fe80a103d4ba76c1ca28e9585a3746118046fab6a84aad53d9d9596ea9032cb3120a8bb921e06e1532dd62940c0e1d0b2d03dd7eb06d124379bf24c199b4a63e7aafab8535addc4b233f1ca88fedf068d6cbb614a9de5d261412090931e73038e26710430c6418fd33f402ceb1f0924699aa075439caba8d164fc728d36cea309844871b388cae7c533878ab02c95b8e955411df645c074cbc1771e865ec4fda77de18c3f1972e7d4ec51c98dc29ab4e98a170db29dacfd0f5314baee9c48e1447973a2a24f0fddff45e40bfdcefbe545c11818c06993736a18d1832c80306a88950376114fd277d3d521be2e7d876f1511c6ee1e74ef372a5a90a23c26a73b0ea4114e59d5b6a68543295def9ef632f4cbf4a7bedaa5dfcea4777bee6b4a0b7b9bc70c15f4e5cf5d7163391bcfcbbc70205c1b37300c46014d91b7ccb5545476c29619aa29eda7490ed266ef17d547a1f51e4a8d692714ab22cfe94e1be4000caa0fa5ac24a943bf39c71cdd4955be433dff66ff90a55f934994b696c7948043df6d05b601aead1f268262b26fba135175cb57eef9e02526523e667d26c859e1fca66af7d68faddfb995b4e574d833f8c9496f5192679df8aa221e851ab4f84a7137f188de97168954c549ba3842dce976be3b1cba946e1d4d122cb8fada50ea87b47a8f2fa74489bb3bb351dece1f1d3d436ef5b53cec942fc0117f2da3c209e450b3229d5c560de03b6fa125a8ac76ce5508e308a234603b10aca3dc08dc58b9070775363834336aafab6512b28d636b290733085455290afab3508d83fe606b12e6eb94dc642c0d6bd30ceac6ced2fb3c01783e2671adf8e0a8e8a6e607259f9ad99a95c45c6a51f52faac7cf5e4af40e9d00bfd9513fc54d0ca992bbdab3ffefa5311c69f0d323d9651744a9a65de7908db92264ad0804a4962f9dbc8b99a734141260a60da93933566101feb6c6410d3ecb620b13d1cc474c091f1855d3efe853625a62ea717ffb0ce4e878895f13a68f193a24caa54f7611021356818a47bc2d7430fc59bc1cafa5a198b71f2468d38ed78c19d0e5ab0229b7826a0becfab9809b39b59c1b27be1497a6b85add4bf00377aad1a118d7dc88a4374ce488f0308d48a07e423745ceadfadcdd4c8b3f7040dbb5fabb846e1f4461fc738743ca50f5ed2d4f80ce0cdd8c4cf83dd48429437b2bffca89af53e7e9c84f9c9b39f534c4612a8ce4bc0921ba036534ca9b76b8e85a19cb655b503edd664353a2415fc482dabd89e149609fe30e6dd26dec5e91a56f5904f08d6c265c62011fa03a1cf10ba3918962deb650745c52892eee15597210d909c5668973234c97f4930c379255088138a38bba57236015ea8e74297e9fd0000f4abaab905772cf5594d0cddfcd902d709a1c7459690a9a890e722bc75aa7bd7add491b0711e589c569c223f35dfb23f4792c9ce1caccde5f0265cbf40dab8e16354e579480d2be71590f4e9867bd2650518614c898985bc24167cb986ba0071334dc62eb2e007bed66262564f5a007f276be620012cc80b13b91cba85eeb2d67f3e1e30f857ec78fe3ccb6c113923b154a8b31ca84bcada010e99a9863bd831a57ab5efa0073423af80f3aa40ddf8bc393aca0a8f37eabb03e1b7e343cd05199c5ca6dfa7a667c3d7b6fd41389cbb2d0a03b1d5a192116b14e43f8b26b4c92a3ba51de3b07288668286191049cbebb710a66bc14d311d748a92c2bbceea929ada6ea57392b2e876d40aab33cb8284f0c9def1a812769a0ed33b23a336b237c3a623e2975c54903a415b6ba1d6672ee6594ac509fdbfe7428b147df5457496de05ef60bd65331e00f11b6b80c21d31c7d873d33ba0861d16f08a4f560d1a0665dfd540a783b8b0c3816cfbb1dc6c33539619b7adbe669eae4621027d3f39d3a57cb3639e2a8c03a3a728673832a851b08901874066eb047ba81ede82b38f94f483ad699ee1e9a80c70aca291f8f6d328d3d517fc9702d611221aee9880d8943b0d12327d204f9ce0ecd826cc9d46012f05bc76f6b0032c1ff4bd484e1fd3e6d1d2192d527115dff6f88a7862bf90c81af61864e21abc9198ef3dee241e93a50538f2d4c939e230eff8be4a2c465942511855e4398bdb40b096ad6bd6914175bb2f5d5a0e1fd1a73a994a569d31945b83f3c87a388f3bc0de6286805847f314a69a11be5092b747220b2bbcbe2aa22ab90f2ed72e8b88e2abb3f664582b3ae6bc38407136d25196d4ffddcc12f537f0da7247e7f303ff272c403412d5a04a3a9453093eb349bc7532b09058c608e2461d6ba6112fd4c2f3a09739f810a28ea367f9a173481ca52ac5e087906e26587b9edb1e2c6cefe05160bf62559a120f93700d5aead0296678da3df47b34a4e87dd18810c733acc6f06c96638d54ee1fb5e54e897b01ce406692abf23316c0bc0d8bb56ac750e0e0694e681223248bf461a94f8385853213768db1d2a4c1a1c7c17bdd500ee32ea563185d549da86657fd1794635144020952ae88f8d35412c1c4054a42df44829d26e6ba256a5613ee822c2334e1b2398a7319373f1449c98909d13890169a24788aaf884aa128816638331493af2250aedfc635bf14c69362e85875f0298888983cf19a593ccf39682bbbed82388f035b66b9e11c7ada176fd5c851bb8cccd36fe6a1f11b604b540919816d9d358b5278092fed1b15922b5e192085d46f5e6ae8a153ddb994dc69ebd21ba28b32c9175d26e0bdb5628868b26efc43a3c3de369ed4ced70a918a2208d6c982416624a1fbd3980c851e223b092e07901e84dbdf43841b7861c01ff9ec2620c6bed6f8eb16132feadff9c674c2d839546e9ae4aa7a18a540bf43d4058db08b5787c08bdf2de4871048013c60933db4095c30b1d390d02f0400024e872794a26a47adcb9808d43ae6a949cffb786fdfb8ee9d67346adc9d773e65c301c3377b2d21190199b87b1e1e9f173b68ae98f65344bf75071d39f086075868e486c946ef94555ca795399278e425d321f929ad15b07e50d8dd13f68f52528d4bc18a8003898b21f14b63ca157381c65939d22ed597c5b2937ace086e547e9c72d017dfc33f2bd30ffcf542026950d04a2b58908b9da775891ff3d540e6f66d69c2a779cf79ac2658a0f71d85982d9557a6769ab1d0f51aac784c4c9cf486562b6a064a5c79e0f5e2b0a8963da996a350f24e54358301d3b9b1cf6b29bbf70883c11939afdaac6b0ae8a8afb305b1f5169b956625eb24ed92ae2f28bddc76c6b16192b93fdfff58614527549d8dcb9a8cdf4df08451f26300f8cf3d49c6e25b4da90c90bd8c73532fe6ce44cb0546bb4356e7eac60aae701d61f1dabb138c31a4135519fe177c992557844382a3c93a8133449b4cf6289e4ef883195f74bf1a819a1379e2790cb13b5880cc66a9c8a8dd8ce08a9144710b9304f1ce2f07620c3bac8538708052ce8d162b81511552a891a77e46f1901d39749dca23c24686d0f6a87a207cbc52d6e26c0b91db31e9c082aa48c18a360b101b55c7f12ed5bafd9bd55b6c9ac793660de5604d3cc52620a2ead5a4a30b4545e385aa3a4afc36466eda7310ab748b8a26fe89a8c3b91e3ed1130f53c7d6075998f2a7308e7642e370b37d8fa6f62329b916424e532ef88ceff5f828615b6d5618417b714b8950ddefd9a7174e4813a829bc91038b60e896d69ec5e17f2c95392690711088acc194ac0693be5acefe7ea5c1516be9ae361a35b0617d19119a82e5e370213ccde3cf6430dce430dba8f59a5033cb23f1581673bf9c2eec4731a0dd6586ad74dff6610b5bcdb4e3ea89d3de59dd4bba84d0235b0a563aa5180f7628bd54c299937c78308ea388cf5fec5d95d39064e352ba6f1edaa460ac6a342d95fc0c621c33fa8fe3d21b00e59c73d8d7b729cc2cf6b25ade85205d4130b50f924326a5d5af505fcd1cb0c8f49d7af0163473ba4e20e96bdeeaf4b361ea5cd6395789f7306f82916ee0435e83ef658e4dcdaac306eaad4824a653405d42c9ba0407adfc69bb277f225dbded4c88431411dd88f1bcf60e89fd83737eef2349e1cdba5714e8d2e3da9336d563868c88287f6f2ecd54020957abf1ebd08f6495403847089a24b64cd906623290d058519465e04af52575c962ee95792ed796d437d7c9dd2fb70d3c8e15459395fe23d0c8a8a611c8c7dc25b202e51d73e8c795f99c0fec6ee20da92545f0406c00d480813d01f6c2de04c8a81213b793c6e1fc654ddb761847fc7064e06f942fc2d0083bd3a4c1fa0813041190843b0f80f46a9ae27559df89a70b0516ec35066f5e5adfdb9f8d25ebd5a9bd89f56a1c259a1f8397503dc0351f167c5da3965344476e176e96a4d62fe6c871689c9671ff6184d94827ced1d49c95ed53cc5330d2da26540a19edf06c7aa2fbb2ad2bf05c75c8fdb0da0298f645da46e103e85c98ed8ab5ed27214d3364e07e3c61cb9f31e6c6d509c2aef8d2b326a93ab2f6762a016e1d7ea1c5ca64bd6b2764db4374b87a5bf6f4074ce33609f04c395e30be4b3c18d217a7d77648dec5888a1edec2deb487ddf28d07a3b218d4888913f090d17576e91abb5f237b067d9abb4bf99c658e7b91be362b5606d859de81e7cabaf6f7c04ce8cdb05e6693abf9fbb0fa8eca6c73900f3b27f7c4b6d6f6943e1fd1646352621aa499d66a1389f1c869ae03a33438f495daec9257a4898ab495d1f8420cee8f1ccef6281a2ce15c6831c1c358993bab8077fd89dbc4c117b4b9e44700f80b372a7a4ee38f527561516724dbc62f8d7df7ee505fd1ab537b17ead42958d06780f3b864695d672a1edcd30a99888cd680d37b27121c6d58560c92ffa83770a58e2f776cc7e003f1b4f9010d283b173eb1582709d00a2c995455535a0c667cb60832d9159d706aafc440ac9a4cd1d91ae23a98e8e780ec77827d86de9c02247889c85f0ab5208ad575083765bc8593ffcaa47e8c72ca9f2d4c399cd47e06b3cdbe31f5e3db72d673983642bd5844a292a4f83a07a10152da6032540d86194292889568e174b07a64b3730f74048d1e06d34a98de172b1e88da3c23303e59caf3c3cecd16ac8388d6acbbd2a26875b63ee827128c840034aed69675b62984f5dce1e737170d0b0f027fb9d0d635a013ae0252624e5a64cce11b64c0993d321b93ba1de6c84ce5b1e76b81e69bc65112206f8ee7e8fd0a7a626ecbd31ee6dbd073b4dda0d1c1e851ba417bcd930c2054230f8568ac0cf54f6c9bc88207e0022f27697c9706aa940c6de90f16ec9aa9a269dbd9dcfa48e179a9034922c12b444cc72a8cfc4e670a6b3782615e333a101e5d1f7513a33496f4ff2eeff426313951ced75bf946001ec53780f29107580303d213a335bdbf098ce74ebd4de44fbb50815ba32b12be186f1641daf5e742b6ecc81e1026a776a4619a251c25c3457aae2b5845d1141af43c3b3b5403dd609dd9ab517d17f2d0a5155c877752191aa4a633a26df8e2a1702b724802c3913f4f77a6918f85d5de4c0f996d26b775fe1ee89964257dd2713813215fc7130f769ffd5156dc9db4d9d5c0255ab20c9a691aebcb6d59a68a34ab495267366f25914a5effa372fb2fed89526948d98134f4447248d835f128aaeb954e2bc919d9075361551b844a3624352f5f5a40ec0cecbf437174158896662f94ad6455f33d6c188c8d4d2cf6321ca64026fdb0341f1b274f17a056aa0f339917b46034875a09704357421a5f03c82d2b8b60c36f98723de825bc35eb3167db37d73b3d9e3d3adf426ff3d279b0de8507ba5182e4d2b2138f0448043e1574aeaaa190291c2460e895130a04fe89791b1a9ab1c9638817a8b177224845b67011fa6ae380eb0e09338a17d4085d158aea2d4f3fcac7dcdb323a98ab2daf13b3917d5f06614135720340aadbdcc01c24b04e15176619aa1478584ad35eaf3b1d3316d5d84e7d19923838b9777d56131e1279e185ed722815876bfab57ee85a3d3586a95eb55243c6298e5b1fb6451bc3403f046913f0df0a23de83f6d727283ad79c4f70337e8e95b811afb13eaaf507935feb0b951fa484a331848a098e7bfd33765945e34c03a9c7c34b3bd585020341a44a3f8d45685181645c62beed75663101a257de1f4eb86d65817f9ffbb8ef19e28e92f341abb9d57c02ffa815332411934b457bcfb1a711f1bb8a116f0e7e542d5870d5f9db3bd8346401be71068aca83297450d588a568752d8784dad081f0604e8b9cf9c57b9278d92fd89749ab9694b233f8a2d0a45d3508a2ad327d134648cad24e6ea358ddc998e1ada96d85fb5a480078a0f12109cdbef2b9e38ab2b1f8ba72cb2a9463ceaabc5091bada842b3a1971cc97bb1dfbc4bfde17b0133186c742f56d51a9662a0148ca19eb78fa65601098397526083dd3f3882a6edb1ce7e162c383e55d15857cbba1b501084ac66bbab68ef6b1a9ceedc4354a19cf23c59381cd1d42532abcec4d596e71b3efe575845d6dafe3a90dd95d8e8463f3d5b0fbac1b1d41409afedce46cc4454e4f645dc9983738ec40fd1caa545d3c5d25c281ba00101f14e1fdfe710bef22fea76ad144a3cc3ccde53756da1d3b3eafa80fe47bf15a134274b47ff912adeb514d2604201c818b7d7a84fb795c052ade8068e8e83dba08df6ba746e932f125014878c4d979e4ca41321038d7badf1ee01419c6aa21a81d7171d2aef10dbe1e35d787bfa49908db001c5b5efa9c7b20d58ba9fb31981775406167c5df3e34740ba52b265c85ebc0a13887b4f43909bf145448a2952a6d11b89b09063beedddf0aedd1c0056bcb3a750c8a1399cac42f19586c471c929a7828860f28ec1a63eb5d6e7c70b77c3ba0f6b12b3e2dcf1bab3c572a6deb2bc77c8de46f405782e4a771fab74de495b5e2b84a06b5f5a2e602228943829425676ea9ea384de84a0964a1f49d9d621ecf2a99d7bc57290911fa28cd4dc1ae90a479315bf3eb2775199f6811cfc9a6862d52a7ec1f0e29711d66d08c5caf6605ed7ee9c65d6c9c90ee9afa1c7fae6b33fa1bfdc7488e6361f23181c8edc9729a8e62fb7a6668ae52901f72d3c0f2c8cee55b211eef76b8aadca9e84317a5ed97cf1f51f72bbfcf19b8c073cf09e85f6c142110620371a607dec981a5f5a524a383265533ab12a996d440faf49e815ed041d95b96c5a11cac3d8c2584383bf94e5efe17f0c4c7be0db428b98c28123296ec31dedcbe5faf5c5d89fb59ab6893336e9c8b9b7f883513c1e5d0c7a4233ea3ce9b9c3105469fd641af70ab8d6d22065bec37da13b6446608f2b629bd5ed03ca2cfd052aa9c055e43dd5cc1eaff8942a91d77936b8d86bce82a1608b56b117c1a9829b3ad4bd70574528ad0e10520487ddf5611751ea92e24e1132aba8a85ea5faa258ca5bf69ab5ebd5ded45383d442477f4c852e1766aedd16e545303e5604cf27daa50ca68e9b5b15b44c9bd42c7dac709a88ad7ac3d9315e5f828caccc680c9cccce0efb4bb2f4ed3a9a2ecf596af549bc30b93d4956de52960c8adf452e7b5578f390c06088d83e118d7c55f2c7c13bf4f60b1eb28031622fa67dab9c330e5ef1925e4508ba53e13da440a3fa59fc98da45327f495d0bcd27d118984878a6f87dbd438ac67dbdf11bf0dac1b0685cdf65e54cd3b66d907a338adbf69f8325837b4315bc634b699e2825990db573a1ca561324d1045918d5434caae081601e3d59b806c2aebda1cc92c9102b40ad6be25b627787784fc09172af43b8f6647a8819605f4373baf54cd37c854e4d9251d55f6bec947a61ac3dfe248a0e8ef72903b5cf4ec91526ada2b04a25795b921f9a30bbbe0e2346ffa9476fc7b8cf75c947a2726625d83deff03a96948d2c8fe9d8bc1c2558bdd6cc07e7444a8bb9e3c4389a67574628d5dea3e459043629b71664726a52555fbaffbe53e9735a0900f8a25c6a3d68e638819b15f9a2a7077378370e9182a1ef1dfd1be1206402527bc8508c4389a6f12d3b21199e287cfcd13ed02736773db13445dedf10524dcea0780eec1de97c502cd1a8c96c98731a242447624f53c638b13d0535552c15487ecf888945f4b983181aa21c3552953ab3b0c5c6fdaa7fc889ee510eb9f0ad22e55e36931b11a59ed0849e0449bbb2de9904ffc75129010d1bd867639d7d2c755130ab6c541218eec05e143a09f124198a03dffbad2f8682679f01d32ba66ba6534306c54f651f3970a34d1a0ec20b0f3fc82a5ca0476e4fcc861ae9e8ac9464397ae9e0b8b6a3b3f05ee17f3c7810c1836a4147a93ac61473bfbfa38e1006aa72cd9d8e79f0c0d36aa858e59a06a990e252c3e6bc62f6e1527573c67bd5518c910154bf87dfbaf0329873fe6452c101bfc9d9f7ddb1a5d2a9483cc9c0f5d69db63a96189d1d4553dc21837c001acfb5a1054ebaca6b897c984ac15b548a495d4aadd67224838b1f9502214ef663afdd64a949d865bb80e4724b58f2eabc4f29e36dddf50ac74417433bd01efef8715bba7190ead92a358a363422f5962ca58b1bfb996e3657ac449fd9b01b825a86acf79488758afaf2bf59d9d9136cad61d20100bd81f4d9d724644072ced881fb1fb30fe6b100a86ee9211ad669b4453c3a3a3c4a93fd28eb64019b164ec9dc9c31a2ef3a19b45e5f915ca3880bf79758a3a0dcd6093a2be1c7cbc56348cb2122554cb07acbc072db1567bcabcc8dbe09dd5e9ab66bb2240291def3f38ef906cfc726ef8187b15d5203eee8c919370ad1131da67ac3271eb340db67a26217a5c78cc413b99f3aee381d662ab17e1f24c024f8269c7986cce8546edbcb05d9d42597d992b05360a3be8a705a4d4281eea13ee8f1fa4d249fcc6534e31ce4a5c4f1bdf1972bbff6c9ee8a362b2911a1a5182637ccbc8f878cdd4661ad53765d15703c6116f000f5626ed919ff04e727a2a1deddac0005322804cf57cb087aa3503b11519add5eb8da6cf4d003df3461512f4e88c78522f984107c8f17e8349a5d14b2e0f1ed7121688929799d0d71c360db180635a476d2b7b9a14f1b97a80f6eebc0c805e262c0efbf18f4b51e3ddec0932a732fe93f27e708dedafacdb3b3e6e35e55437ea51a8628c81558be9a7c9059a3622d7d83ba25a293fd08cd502d75a1bbb115f4893caf8763d442152acd11836fe311ab03cbae40a4478e0ad4f6e00766430bc986da5859675ef5504321ab982831e2882c99f1ee5ac3b5b22f53e3e7bca408dc5c190d76982cfcc8360d0d8c6a932bf900b7f124e9a32c0284599c61243637a26de01e05ec173f3795e268fa6d9a73628a2704aecc3ea365f6928a1291f9fbafe01a276760401889c544f39d689b5068f9534e242e98a3d86119ab74114e6447a1e0eed8108811b748a6d373a201f283c6231faa5b5f9eabff9f761de5705307311557f2aac8ac9d56e92f2787d234a758a696ec3c069bd012f44ecde96585aad35aef1b1da2605b3e45e1047af4cfdd4a2718ac8429edef44e3c4b3353c3db39d09e42047f8f13acb8caf5c0b83b63477ecb4e9746862cebe7e9ef3c42fb2c1006917ca6a9e1af9dd01f8587afc5d57f74840126a47134591a91a6cc6c4168c85288acec1a91b8f4e82780d66c8d54c714c19ca6ffa7038758981b5435164861898a5aba8ab2e3414054b93eea4468918d23e2ee7a0b0860d5e17fd6d35b61a1faf3311a6ff3513a16dbf05440c45d1f049543783653c14c5a489a447260d5114649e5fb6cc752ca2b20b2d5edb6bd150701f3a9dbd5e07a285a77e2a17fedff77b37de870f9fe38710e79f8e02f4b06f86c2bd73b5d325f27412a9b6a235d3c822877e1eb8c6740282062464bb450dc509e3021732b7d7c5bbe5ad0c7749f45766ee8d628ebca65dd4afc3bb25abb81e5cc30a36a12cf6b36cde9ca752d53925b7f3b45b07a8c33487de9995ec5e2672209b12c9190f2861172d34df14d535e6651b618a1a705f4ffbed508c748de034ed42bf3d91966182224c7447262a567242b60a51812a36bfa138ebd5cf8a61da7ab5831fd080f4cbacb020922b17901206d96e01bf9f0574ee29200258617e031386d4c4e37bef426967da18ad7ea3a3ae579f5213a920ea2a2dc62d2bd5926b05cbf95b4f7c72749ad7ed94a83e3c04675ea15d1399a1e69cfb15d99ba883a350f4f0d2e389b3cd1adc7b3751dae635700f17fca191cc2e09f092cb84363745c855c7c57386a849d045718e18011f4b59df2f0d429be28981f65a74029e502bdf7c2fc680f58133daabf31d1dfe8ce66322f9f7af8e54e5d94e40937ca403ca754e7b5930d81da5037d9bc9c866b3d92c7a6b3c50e1cb1a77bc4f1c1c48add941c8490381c2a8ceead5edce5cc02fdf38d4220f1ef07a42cec68d385871df0e7a06735c257c6657132f17b9420c8be6ac9584ecbd37915bca2453920196072207cc07140b94f5fd3b5037d062fb552b7f2cd644a1546f08edb56e3064fb552b1653e8287ab51fb20ec7d3938a9e501a455db2075e484cd02744a42659e4200e82817ce0cc3ce2237aa4653ef9b1fdda69fac4e168b20fcfd1d1d17322f721fb89ff56a8f7858d48d7e301834023f37b26d7936324c535c580f9f16d98313ea91a89d5c8ac3d3993ca2c28ac5da5ad5cb5893a0a9ce97f393244e8287008d47ee12c3487a679958ddebff7ecf619b1910c0ee8ed28515a8a562487d861a37282c4f67f371f4322b66f084f0b1d24b2b4cf7c359cd482a2e0bdb1ed7707ce0e9b20209f1a04f9fc0021b1e4081217b22aa04c502eddf59697a3280bea4141bc1cfd2fe7e538504e572102bd0de4400c7017b222fb892f241901bdd34a2c68fb04cda0eb2ab01d740417244a1a0073b808ce0c81f6b78f63d1bd2cb0ba23a4d87e1c3b1e11330e2ba0ddaced844bbea7271d662704bd13d6f880cf0a54dec3997e1f5de339efc5e7ef410066c14717e48f00b2fd2b1cccdb0f55773dc3fe8f5aa1545e500ad8812ab2fdaa15efe06f511b56774e604526f179fb715269ddf5bc2cdb94b5c389e0c50824ee03db4d73bd8993a1264750a4482fdaf1fefcdf77d75a5014e8f5264d604d9be0cdf407310403db2d44932ff6d297838de0cc0ff4cea939134bac375962fbb9c9500ff11227bc845ffb55c7da6133df0bfd324c509926d0db422dd44465fbfaa1f77442f5903bc1da71c3a48bedc7a91233cacc7ce3a35fe8373541617d37cc1dff0d059a06be0d5b9ca679416ca890b5047a59ac97e101bd28942a26795e984499efc3d7755dd705e1c52c351ece48bfd0be8141af431564f2dfea7a904d01dd9e406f0319d5cb947650031d6924b63f2b25815efa7290f2211b1979bd7c74f4eae52d9772974babd8db40b619007364854570c6a404bdfee33f0ee4415c544524f9e173e2fb0b2ff4471ed06e1f1f9f143c0c9a466503274c6b08d1abd98c7db658cda2382e94bf4b810f90cf0ff561a07ea1ffb1404fb5852428c7042a63047ad9a79d64454eef4ecbcf4b918b95c74817caf3f85d0b3d2d09b2fd456274875aacd7cb3e8f2b62842752815ed6e2e5ce9af7995a171b649cf356b7836cffe604aa3150ff3410031dd14840bb835a6812b1c4f66b27f83f7c8eb3709c7cc8f043efb40dc316433df3a2502254952fea8c7ae65918b638ea9967af4d375c821fd19d5109db8f4364fb7d54415948bf8d01fe71485b5214ca6ac0cbe9b4259b11e8fb37c335c5ffdeea50c4c22cab218e3ce1ffe17364f417fa4b3fd014fc6771d4553bcc16ac235e06e86b09a1c4b684aab0f4877de017a04b8ee52f6a9a0c260623a4f497a679569a1cb1cfc6c0248ca6795698788901892a9a261503125d7c54c7911551d2dfd596f9465234290eb9e98ed3f4717afb7e1caca97dc6a56963b2875f8a711b13838699e8e9f65898cfa026833bfddb6020840fe459585332bf035fcab8c77097da983f7132dcf31317e3df167b6c8c41a1e460e9e340182ac542d7782a43a1cf6524a13b22276d6e9ea5af695886b1233066c6321309c31e23c166228be19af4faf724fcc8e14067fb362e8aa88da63184ecdc90190a3333f638dd750de74052c1eb61c47cfb1dffb93de97b2cb3d46c6bd83327391c9861d3fd495d8cd0fd278c30bed7989f5d1cab830efde7c78ed2b2a2c3f983948a2aa25a6891fae3d8418ea1748dd633fef15b4af638ddeda0affad5e5736e3f703a3233cbe7975f8af1bb4b3176c72c931dabc3c162c5eb23ab696275d2cbcf481cabc3d1c97fd61f0ed60524d64922aaf1f8e2ab71057a57b7c3d13492fbd11d3f7bb14492c47a565957d0cbb2114298c3d3e2520913f31715c3b9807bb09532c9e334e46ec0000e449a866d7c0ae8d8e0a36be8c76771ac615a390866d788bd7e4e9e189e859af61bc735044376fe659f0bbf647ad2c31e4b32695fd2fee26e67d9aa16e78cf6396b6a72833fe1f603f3487a12370011507f9e1cb3c64e20b18c65c39e9947c933237d06996b2ac8b3fc409ee59aba7ee7afc7786c2471dbbf2d468e8866b79edccdc1668f03b5884241adc6384540e54792855afcc9b9bf5fc0c6273df7ecf4a9713656f7cb0410420899e85108af961d9bbb6195dc59b22bee3e85eed1dcac837540f820ac7fe3bdf75a0455dcddddbbb322dbac507eef6191cda669d83cda3df2cbd10c6b77f7912cc2c7b97577f8de7baf9344c7b81b06967de84010bdeb5116d9468e9f0e089f1bdab6469a7bb48e1b5f7208a0f59df8367bdc8dd3448837d893f0a12834fa0d7843c6e8f344f6604df49c8c357e33f02307b301db0f63fb5f0eece9e61410c54775343d5afaf38cc4ace4c139d29b91df037549f758b9f949cdbe55fba4608d74e76858949d21632f4ebe19f80fee603b9e6c0c1be3ce519b619a73269b69259b699a739bcd36cd39cd6625cc6630da45b2d366319a739795d1a1cd6668282d55a346c96b7c39e0c30fd8e6f7020481ed8f355210426f41d69f9918c040d8c2cbc2cffcb9bb7bf32c053d2c58b060c182050b962b57ae5cb972e5ca952b57ae5cb972c58a152b56ac58790f0b162c58b04ce13d2c56ac58b162c58a152bef09e1439e0416772c58b060c182050b7dffa290c17fabfb8ae0d1e3f1be7830889ed2e5092c84ff155728c7ba03b35b1a57a80ff9101278048f58043c3a198272a8113f6294d2fd69fede7bef69cfdf7befbd1791903db797edd7c25e7d4d22a02af649a975f5ea1e638c1e6574f7e841f0a24108d3dd10763b1872c2b894eed121840e55e0ec1c07d8fa334cb37b7bb787e1b1fb61628c578c1cd923757f24187e3142edc1d73fefbaae4b9baa192dacd65d37b7a6d149213333bf516bdd9b7c87793d2c25373bc312bc27b8d8cc94694f64b13f1c0687ee4e2794eaa58c7172befd8811a541a1e549f981c2cb27e189efe3e3c31321333f861042c8eda9212c849f1f1a11b98101a24204919b2d78dadb0a691f1f1f1f9ee6227bab223e3e3e3e58ad083a9b86888d851305a11512b3c40a0b6cec571eb6b0f216fdcc833bf41feecc27c8f865bcfd809ad74d869efe21f3cf649123575fdd1c5f728fc4acb342a9eea796d0f957ab3192838f8f8f4fce0f9fc3403002f8fd3523d5c6be3d4be8ebaedd23113e96b3afb54496a12674d3329d7a7d96b5c61bd7e7429c91b8ced410146ea91ad09deba3bd6fb35fc1c7c1da61223d89547586b8bdea497542a99a463e8f421e534250f96cefc34fd58032b4699a9210cd02c13ab0ba4415f6be85374b50b1f057382c7cdedef6b61fd14677cd310ef458f9977fd6ba823d1b93a0602b69902feb85f07f70bd3d52be8e14d7691a7f1d0804272b13982fc17ca65d9f9924174bc3be961351c895c0248d846dacaf3b1d20885ded638df3e15f3146d36f5cfa382b0fb4db5579da6e7fb17ac177bbd59d59b787d75fad5ed1ca9df9dbac3bf0a3dd6add6888165606828f4418b2292907f3120766509b1465678cfcc9c9a75c4cdd62e405c3d1dfb8ecb3c951ce7b667e36b5c7698d7b4eb9494f284e65a43ee8d03a1532f77e776ee7ca87df834bf7cf1a5ea5d893a4fd75912ed245ba48a4d7b81dec5d17e9358d94658fd31b86fd45b7d7fe9a7e23354cb63dcc46aa3c6fbb5bd30cfb2f715a46babe1fbbea6d534b2038416407605fcb09244efc580ec2be56135dacbf97b5d46304c39e8164929d4bfaf83d58c95d12bb952efd79da46d2750b335f63205887343d562a61a512f69c84dbd9fe6114c3e49bb037491d20e56e0f76877f7bd3df98a7f3ef29be7f8d1276e25ecfd02a612a4f5bd284a9314af572a0e7c6549e7ea63a25f6fe1bc74068bcd1a059ac5ed2aca7ee3893a7ee6a0fdc43fb42a289a3eef83cdddd4451134d9a48620f605f8b092dddfd5bff167f7b06da7a8bbdc5cfb2fbea7529b7f79bdc36b9bddfe4f67e939793ec38b4524a281f06411d60f677fbf72f6edb766575e3913b0da52cd58df426aef2409bd5db8391a63c695b5f4d98d0820927363365d9dbfc71fd3c6d3926945801d8d7ca81172d1601f6b5723064b397f9a5733d035d0f5ac8ede8605ffb33b4d77b7fb9f2b4bc34f9aefcbe590d7d3d736daf959ef2407b7d7c53dd2a37801f2a2b0fb4f26f1cc00fcd2a4f5b1981c8ba239b5479dac2dee19742f865d568d0acd7cb0f2b03c13aba9eba2b755cc2c45e2552d20c42a269f11fecdabc6d406a3c069f85dff006d8651b57e3513264626c3d46e03bf68e04831bb763fa1e2c3f1573b76aebcb2d3cd7dbd6f42dd4db5cdb74b9325677ae373d77576a7b8c3084fc38f0cae7e91dfe1e6cc34bfe067fbbaec45e1e0cc3eaf524b663c088c188c188c1187f7b4b8cbf7d04238c30b68cbf54c654dd05dc3565e3e563cf823537dc781aff33b82f9d626610c2df1fe448b8473fdc1e076e2db1afc1993620a6aff12513f637b4f077dad4bfd473f5bfdea877f60d0c6eb05ed6b08fcf828dd5fe1b2dd4d23f5833e31face18934aa58ae6d7018c0a1bbf834b4f9da80d4f8ed25d7afe2b4674e665f837b36c5c5a751ef44d53b67d44b65d44b4bac8bb2b1be8da9b7b330f576945e73a3cccc3bf3b1a7ee826bfb57e5e571ed1bd318c3b619aed7be32d3df385a53a497f5e3df70512efca5ab0740e5a0c75ebae52f5d3546bbbd7c6daba6f8f1dd05d89b5ebbb02fd5db4741db5fd4a576abf23dc85d700306ba8b7f495513bcd8b7af95441325b458967d2d25a0d856122d1c081959cdc29f2fa5950321fb827d2d285b6cf62c405f0b4a14dbd9d78212645b50aad8ccf2cb18ff623ed334989f756e9fd5cba56d03527a7f3ed234d9f3fc57fa2c9bf57a36dfe7ebccf76c07fb1eec7c3d27cf78c66920b8873f67d3f4d33467f57ee6e975a71ffbf9f72fd77bf518e9c1f26733bb2198cf49ecbd9e87db99df8365ee4221d06acce96441f6f234b49771da371a6edbf9d9054b7fb1f7af978ffc35025aaa7c4f32efd59ff972e5696b9a01ab332a4fd7d48ccf84cce7e7671d06e21efc59e58bfaabc4993620f0b9eef0635f72feeb7d7a85a5126f1b90f93b1b0d9a9df57220db7e68527846d350a0d2677f71fc3238ed6538983f6d25aea4d35d4c0c2e857df6cf9a3e26864d1508ee717dcda0d5fef2c0cb03b5baf357fb8cb37fdb0cf3d9ab36430c29a6a6e65f3cd0f25f1ea899b4a7309c7747eb9baadc80f0ef94eaf671a341b35c2f077aecfcedb5d7d1b4f7ed873669e8ec2ebb640c8e7922f3476941047c848184d8d2dac19716951ef81498738a0bbdefcb8a7b823d3386b1900e05b4f8922b4454f1f124458cc49f58048330b4bb6ce609d48f7417dd2709fa110ff2c0aba126c73857e21ec581403b4b77f1db28c90dfc664cff483f30474cf53df06a8a3c1e4a9aa649d3f44f1fd80436fe7b9783b011fbcb4beab5ed2084b358edba333df7197313ab303049c644329948da56c48817347bf9b78ddc54df0533ae1fc3d2111bebd7ca64f51cc7d68d8c8c6e1bb5112b9b9327a98522526e3ffe33267d12e84d6047bfa50ca8ff8c5afc1a1d124ecf9f5a13fa7cfbf1afc33db08f2f81c783f4f127f068eac7d5df590380bf9447cc9b991ff39583af7b3ae63bf75e0e0070f357dc7cd224658f9132794d1700e002ac159bd5492492d73b9f7f879f3e4e63dccf76d243278ef39fee805c0000d755f44cfcec0a22213972e4fa111bdf93344dfd9ad5c7f7218fe244bec4a1b8146fe24e6cac9ec493b8121702d236c583dbf93a98ecac33361a4c3f2b0f6cf9698c2345608dfb40eeefaca169b8ca909176ea633e5538ae72fe133fa505bd5ef5368a340dc9c6c7eb230d24ac24c53c6c4dbfe340ddc59feee2e368e31b61ec6bfc15d7443d1365feb611071d61201f1cd80277d9b680d522ddc5f937b21a9ffa22469ae6dd981bcacaf8f7647cfcc9b95077f17d63962d026b74e04cfc49aa37e2abfebe2f36d807d4cf97db10d417691a1a1fdf08fba07efedb66bdd9c37c46bb8bafe24eddc54f7135381a286e06c764f532b82ff732121bffc4a16c0cb7c305c3518e87ada9cad05d7c9d58aa7792b46a64e307c9e244cd1a6327b9ce8b6e7400beb4a8404901a14505d14f9cd2a2224a6b8a93d614252da2a11691921497860f1241a2088b580b248244cc85c8aa9864713e52c9806addf50f3ea2f66df1b73427c1d2401a427a3f72d8c5892cc50525c521b4888efcfcc2356849b1c576d132a8c019f6b5a478e22e448c08296770c5d8d792e204b3740592530183d91285512b8a1e584db3af15c514cea40667925c40ad50aa57fd0a27888fee7c10041314558a1044e18b143889c1cfd050163168c2959f2a6e2004143e40824251a5403d4a8582353850361394c8c2022438c1c9175d8ea4502288265daa60832fec25007bb57c00642f17ec15054e10a6a2f5a0c85e971050a81dac57354e10d47732ec6b45e1c45e949d42193fb3669cac806261ec6b554194da8164d974c73bba63a2c4ea8eafeb92f4fa560f98d8eb6a596185bdaeebba64ca08191d768a0736fc46d027650ad605fb5a52b4b0121dc87b45580218562001e5b13181d086767e42c5132c4fb278e2c59110d94f67eb39aa6780e66cbb60fb6dbc175053f80e91dd08bf8effa669701bdd6bdf1e7868c87fd334b25ef5f54c6cd5cd45d570e343061b6c6c90f0e2cdbc22bc9967635dc243028c37f3ec8b5b64f0676f3f6030a8c1edc789b583c2681066f6a1b8878dbfd2be8f525e0ef837a268bc1ed9bf4fa96cdebf1b01eca039ec03c05756db7835f486634e33de878f1b1f37383ee01719f48b8c61a13bed8c18311ea76fbae6f988f190c298ea756b7a99b7b9f1d7a6351a345e0dfba0de3f3e1e2dbcbfe41e36defdbfc67bbd446e3a8804dd6123e53d0d6d7ecfbf51ef0d7bd947c8cb01df061703eb80afe264e09eb36183a16d8d4a8197033e8a0bf272c04f5da1a8f77f3c9a896e66c8e8e2fefe685072ae1ed2a8da3f58c37d3ff74c4f575c102754bc99675de08238c1f2669ead5c102759bc9967358ee38238f1e2cd3cdbdc4571f7054188a7ed9c362926f4d9798a115359ca11bc99778237f3ec13163c91014be98ea83b58394a0a0b9d9e03f9c8a83b16cb491196f0669e05c30a382cdbacb5d6de2b2d71a921be637aaf279bc5c4f9d341fe1068a53471cf6a7cc4466c746f6c0d3a37dcdcdc2061052723707202272c706e4e0dc66b726c6d89f3d7f5bf8e7bb6eb6b0b7d84a038acfff53278338f8889040de2a47b34d37158a7f06995281c05d6bc1c7b33ee596814657e141d98857f368233ece587d3d9d4311fe3e0cb319ff466e6c37f818c9d3fa104dce76be51f1f37ddc1bf9105ca599a862fcb280623b25c160cf2848837f3907833cfcabac49b79419e40f1669ebd9ca47d6c745d92fd708f5785cb40d8c726783cfa0bdbd732da62ef73148842f9f4f10efa249d907b4a0bbd9af526c1e3d14960f60de5635c8f85d3a5c7da2923a87ca0d7c6067617d95ba5e093fe67049c859c27fcf3dfe3d1969482ef3d0337c884c4cdc9fd3073606c8c61e36fef85f8292ae8f517955d1c0f3bf04ccb1ef9751104969faf19380b57dde17a9e6999af8d06b7bd491eda5e281ac9f169f3dd4bf6568f47f3db81ca410160c3da938ca4217d3fd748fd7438d7d3cb21f3ba7be3146fd808b7c7e9e67898d6bf833535c099f853ab77c61ae09c57c642ce36d7137d1dfcc9696fbeee75af8344ac53cd76571d8c58ffb7d5221bbaaba1bb2804855ff4ba481b2069e940a8abc123a0af850589bd5a07524f6c6c524ee6b479c0162b8441851c1cf1450ca29430743044145e00c10a51b090f3fe65c0a15f7e0485d2885bbfae182b8fdbb7c9ca43432e9ef4bffeb8d3dfffac777bf7c3ea49e29c97bbaa5445f418af2b5e33cae8fe504b947c5d9c039a80c678c1b8bb9452ba747fd121840efd0b2dcac265f69a86fdb645ea3d1a4b2064cfa5cea579969d9a869df49ae6fe9926a5eca2e80db786301420352623fbc5457708358fd16177c330d81906652fc6a8d3ddfcebd44de3163bec7b2c151534ba9cae095d9d5551412f9d539b4d5454d03b55535453bc6a8a0b321b42d810a2a351c511a0745cc0c6011416c0212339c4424546c821a31546f4bea0a20418510fb688432cc5141dc4a176550e9e5176c513492cbf11d6e5c62971a889888338e5b211858f432cc51429e210d64568bafbf329b4bf4027ee108975f7ef30ba0612f5977a894029902876ac2ff6d22f32acac10e80af0081c6a1c4059c18443b28b6c6243894c10464405bbc2094c07d8152c95178ce87d41c50f18914c90c7e8f19aee369d143631b60a0a270f03baff73e527b42e72680a39050a394565630839c442454de490d6c54745e417b5a326c883365182c01afa59954982f06ddb8882f2bdb1b1f1099a441642d50ea87b6bedd55dc504f5245818abea4acf30bb63af1ea5ac44d83f254403cfc87a77100bc772485d3373baa8020846d60b1f2cd4e88988bfd6f222082b2dfc8714caaa3a1d415513d69c5a5d8461210972504d6465a1ca08faec3d39777a23e8ab615fab0b2258695fab8b2cf69e2ca99d087e5a1366ee6786b0a686380f4fa16ab25245798296550b08559cc8deef9f5102bd7ee45ba86a09bd7fb404aa2ac221f36342318e646fa5c4baf5990566b1fd503b350d95eea6f5aa620289b2d808e1c3f02fb2549265a61417ee66950d5436500d51c5c8c852c166a6b7520d41999999354f4d81c2ef77d58eea570cd96be3831ba15e55831f60c1022b508ab802143d4cf83829a205250062d2b2620a5b0f7e5cf8c11693155e6c9b7d2d2eb88091457b352b8a322ba890b870828b2655b4b848b2dda48e503ec8544df314c24cd360d5e1b25946e25259a60581eef09348cf05d01646566a9af640aed7bec742987197b10cab902349ad36370a72ab8a83d534f077bcbb177f90bd55962a028d97641581c6a979f187711aa5f253424e317ffa1ebb4515bbc3bed60f8a669089f132df63b38c3512c635cf392de460fe6d7c918a5892f61897fd3c7d0c2e262b71f14d5ca95b350d7dbf9e524ba90d467866409f7d5b70d8de297083897b16fe9c33aba921d7cb0772b911cbb454e2604a6d618a4cdea8a9042b6972279a0a02bd2c164e0dfefaf9cbef1972d5a7ba9ebf2745043a7fa5ba56ec836191bd08f46fad1f0cd9edb3f76867a040fb79eee09f690f7174cdcf93cdedc631313dc9f4441008b5b35ebe9a14206fb7f7ad3a94f2e6c77fe76615465abac030809008398942a528cb16f7d2346de1930d4af17aeb0c73ee00e45952dbde7e94ea7b3a6ef6fddbfb36240436db86b4cd74e8c1b721fea47adf661bdb21304a77a7aad11848699a6c88571825bb88f8fd40c8f6f726845afed93a309402a7bc1cd0ff2f48053a815e08e5a3cf92ecf5134c989afb8988de0bf2e371c9eb4a0d5dffd75fdaaa3ba154524a499b6675b50f9c4aa28bee8ff1eb9b42aff6eeac512dec50d0a6b97e7a8fd10a4b297fa4a2a01765a5ea579d464f8f1bc0cf75315f2cb998d76482fd25ff5e3525a4fffabede3fbed39c734e8d3952f7cf291d637c0a85deffa68953f6169990458ba126b410cac28bcdfa35c3d212f45d6922d423cb016dc11f130fb801ad804397825e6a639cf4348d4cc701bdd0f6e3c14e541d50fec7caa81645277b5b4a0af5b9802c6201c5aeec6b5d41c566ac71e4d8d618c04fdc892fa5046d22441989eca5b0d059543e592879a92cd03bedcf23ebff5a586cc9f109b7575f9b1d04ca1eea3b677966b3df3e631837c8f505d1805e0e2aea1f48b9e8c4443dd3cf2c63c65f8ae24771336470efcdcced073351d36854b2672bf34d03236b504fe365fc8c9f4f83e32d908f6a66d4d733d8779d321c0cccc7701db76ddbb6bf7c64e3c7f853a97482f918300f6360602aedae8f60cd0d6fa6bf69bd134365da63db0f0d7ee90720029a7dacf0f98837cea8bd8ce4d4502e4d0981e2f48d0c11c2222ed6eb4541786acd8825b685b0d87245d0153eaf087dad2cb4d87eada22ef64ead4796d24267b2d70e98afbb02644056a92f2b084396fa42fba78c41aec935d84e669c0999253f972a6c519654167a1f15ef87a79ba6995a8643b344db73ddc3483da7eef8150551857d5b9200e3c7094514f1c41204584289087650842f8e04fda46ff6a3fa9bc55a322cf67d0d51b6e822c50ba814f1454ea4c110472862a20589949c140fe89b3f64cc849b8832c50e04648fa67b5da42794eaed7b386c58753a25ee5714d838d5dfa07128150632ee84c2c1c23f11b150a33958f831ba8c1e7d634741aa33ee57bd61c0f2935202932c33bd6fe64e2581762a095d8a0bbd3b3cc585c61e2c5727b2b7ba1ce49fa594f0e37a1c767df37b3d70601dfca924505aaf2b5e2c6dee2b08ac9938708f15eb6053bda84dab5db770a615f7f0c12b865dba3d585325f272e4d01d7fe783b469b5eb9da46fab2a8236dbd73458bddae3c2e51259d9f8da101b6175a62ca7685d016a5931e29242429696952aad2a61b4aa0cb5aa0805b5aaf808b55a495248685989d2b272a4d5ea4222699ad6ddf6b65477b2e7d7ea3f12e5b13f9c886679602132961fd2680d8c17639431c618ecc088075a96944a313a9abf57a7a4e89581e8aea7184ef52719ecf5260e9636eebe2c9ad4d14911c163fce581a418dceb99cc76e57f5974246dc902eee61411fed16364e6d411f88232b6066eec0e7f1070a66c40af0ea73dc339ec8fc447660f56feb5c39c7f31c11e073af48149ced0626a07d7558022b44870050b22aef892002a80b8b0020a3f504151ac125b3d4c8ed4e08b2eb090c40c5c9093012a745142480a41f822899ccba25205152b6cf73fa216152a4f04a1cfa8944e35653dea1491040040008314000028100a06c442b148241e942545fa14000d8ca448724c96c7a3208651108410818c10020001003020033233228e02189a53f8c2ac593db9b1df8584890e7dadf10bc0c48a43dcf91e9f091ece8590e19bf3617be8cccba6527550910b5064248061ed999fe183c18d0e956a838d74bbded34de032ef0374209cb1b7749de674f703d6297c40ecc6cb0a37f1c88d4e815893d48db9a4817ea6582d975bab7001f968009cb115bde45679ea2ea637326d34eb6ca5127f81fb0bd859608e2b10e193208646dc3274a60f1b1279e5ae5b3fb80f0cbdb28abb42e20fbc17c3c7e082468fec89ae1c2293106e2821d6c6fe84a16a0b5da518d25585934bc69e3bb0da2fe6d0b436c4ee870b6263e620ddd042ecca4a452d377655dff990bc2aedc3c9c6124c8ccc19e7732bf0ba7af63682415f43e147701eb587b1d27da7e5aae565efeaed7b647fe700d242b771bdef87525aca94afc32c3d522a2e091aa7bf192df65b55d9e350a3cb75231e86655724abf8b587ccc9988c5931cc344e4c63746955bf30f98e15480521d958cd4a441171f77554a244301282f8aec3f21682e88ff13b73c501268391b4387d66d9b899ea2fe225aa83bec6b0ffadcdf081ca585ec49c40e2f3076928f198e63e0bb023dee306cec7c2a80074a714ed77d61cfc88a19e12c42077ac5af8fba258f713b4033c53eb99c4b4900dacd21ceed1e3abe29b43263a780cb23c9459c61fbf58a818927b7f1bf13bfe623f7b1d0e8f6863f80944d939fa452b6b8df5dd5cc33215bc10194909ab92974b08a8cb33cf535db635ebe0066b7d500bab3067eb7227e80d4fe558477f51a98211605785439e3d8f4f6453884567b15e44d65d00930831754e28ac272ab493c0eb80e045ea7d4e028c44a84323c9415a2e70193ad25b4b0a26f1ca0aa39a4f5375e4b7cdd0c63dfae11e04a84c2a1b99e8580e3dfdf6a65fbbbcafac183abeffb891bc931ce7c966d58cf1ae4904887d64629b87184ff1278bf8853f199bc9fc7c45f9acaec85dc18feea7d729eb69e01dd5ad34eebe09db3f5d029da4a5d8a5e4d44f850de89ec9ef51b2a4e021d8bab6c33e5d2e214224246d4b6f63a27d15292a6d2a4aadaa5c35c946668ff380b924aaf0336bca4a5ca241448f1925ed40623635031e8c82b98b2c817a58c48279042a1c09a7764481e9905520a6d4ab3a947614c06c7c6b577b5d936f5e09ca291a61cc9d66314ea389a46b754ba63814460da5c40294e500392274ed94df2c7e366cb81bc901ccce82ede76e50772de14655910f35224dba41372d047ea373b70fc17a83ec54d9d2c691304b3ae05206b7ecde8d8f2d4679c97ee27bb1ae247c7d78eddc34da3e680a66a03df338d51ea6d2c4d90f31f78193ec1c5eae308024171b47825208cc1255f9971db200a68be4c784d62dace502141d4d636a6a9d2c545cdcc67d82e335809a48c41a32f75e639cb285ad91d83d67af04e951dec940e39438b35c539e896232533fa03a24f04bcb5367f5f5e52fd074172a1eca784c8693726daa8b0d62f239ea51430a7ab6c732bd78229d57b781314b8bd38b1a9be3711533db3dbc8282a577189836824c5c6127bd9ac16b903c9320c0b7072a51b901dc1d679316c4f5d5b113f8938ece1dbd0e43485cb748fee5c5672ea6dc62a1ac193769efd5bdc58eef760651f1db2a6312154a20e325bda069dca8be5674e1945ca7244da46a40ec33b7783b9a1003a325e55c68c485a1cf6e7a0afba112e8eec74d46def717d81973d144edb30633c841703943a001319858fbb01158a9af89702fa0b0a8d5d06868738a438dddd0cbc0edcbd9fea885cc6bb75c790e5a907f4524643772d5a11ea99764e08636727e55260cead63be8c96e6291c90d93661255ee17a5f2dea0c5194b1672571cc08eb7aaf4e5033304ca6e0afd70522b2b6407d070ca8d09ec1fe3f6d6f69c19c52e4b0779ce85795bebaa84e45300172ddcf226d6ca1878ae19f3c660624486c9ea8b3918408c69663b17ddca18860422b54c65ae8ad4e4520c9cf8913d6dccfa10365458b93a4827873f2389026fc4c82fb42c51352d6317182fc373d1d48bcf052cc69608cba8c37d42760380654f3e86758c93924febb72feb47553f9f4b97f544e69cc0f95be3c2bd25b946f0328bdb8229f1f1a6fbfbeeef777c5f87775ddeeef076f7f7bbbfdbe99dae373a7fa3f3fbce6fa3aefc3eca470059ce8018c15c86f79ddfeefe6ea7773aded0fd9deeefbbbfdff17d1dde7579bbc3dbdddfeffe6e7764ef4f2e77505f8cbb865b01fc6719a9945a304569a2acc03844ba5e24ca85e323dce5a2a8281a03e16e16475df4f808e1ed6241949bef1a7bfc68bb283db0c069d89fcd8362de64240270ec23ecc731ff556016f7f310f46f67300cf132390bbd9db3e97c93af22d623500dceee937b8b490005d47e3937c2c3551fdb69fdc9f3028c5240a461ee8f2d2ca03b2db9e63afe1a696564ebf03ce387f60e105ee4a76567cc866c049996779df7f76cc3ccd1fe4191c62c2d7de801a51ef5e79cf9c894c5ea6d9393b07aca944b0f55c1e6a5b46fe706443341664ab3716dd279122367115f3a11746c4547891281286932a547b8378218373b59271a5954431e2976e73352f3cdeabaf6f6b9227d78a3d078261cc5f27872d29f52a27a9332b205e6c84aabcd55adf4b7acdcb38befae6b951e9d4e647fd6ad84c85e021ca99d7614bb20841963a3b21f2b7c6703149f0632fd78497f05ba91d7861e2fc699caa1044acbe090cad3ae0b0650adc469965470959bf2a5f4361b4897575a360de10cf7cce83ebb06e8579a7af2a135f67a929065e8c4d79896fd09d620d1f0c1c15f0245f11a7db1ac6c4d5046d54a3578310064616a9c678e7b6c92e553bd3fc6989e7a022343640e7b86c71170aac10047dce65558e26b6e158bb303a3b5240fcbc26d9a4c7306218956434f81dbd7d89e4151d3a60cfe40f3eb71e698671ada6b383033be1a5b61df7840ade6b0dc8da2ced8cff4bea0718ea93a9cb118e8c5a84635e18c843b53d14db17a4367f661f6600a7f44e03d41ab32cdff34239c7c6d9a811eacebc250ae16e8e25ed86c87095ed5c2081af34cbf1e72b047ef8105583e994d6214131b2abe3c07cac07d106f422f06443ab0e1a49622c537fcea79dcccfca64d4d9a446a3cb3348bb600facd544615b84dcba960215114c13e8108bde142dfcb1c8654b97e192a440bdf06b938ff39c84861c32d8c911ef0466050e5684d6a587330c998aac394e7b2d6ed866f5a3d7a8853bf88e8144df957f9f067a6ac65d6d22e94665b56b0e06cf3ec90c60d1989297667b2d2ff9aade8db3f4f5d3d479a74aec8f767486040f07d1cb0a74f70ebfbcd2ac68be79d7962a36fc6c4c0a54134b1d8d6a00692323dad575cc7b0d7e92ebe78921f9f8f49027544f3490d6bf30590c975700ee55bdd06a1810b0cea7d23d8381b731ef909dc34cfd1598f544f523b15b72438a4e67a9590cba86d97996a27ae2c6c4f90d35d66efbd08a26b001022177b5524967c32643ebb914c9dbc9bc074d9262f103259128210e73a73694827aa6182721fdd4f530c601438afbd9f27e9bb40112e887c24c4b8664f984c75bb7cadc8107c783f60699d8492d77940bc581e10274947e49b970b817cc6e68e8d9fc314fbe29bf54711ed677bf9565adfb221ba986f387c072ca0d6929e2a758c81deb547e9e8c1bb92df3fbafc87c32d873b632ea7a2583776dcf1a1e30e074500d9bb717dc6c064502c24f9f2475c1e74bdc0f32e6ce6bf0ff1612ec063798624c7fac36c93f4ebfcf76ba36e370652b9653e8c7b9ab780a5a761a031f0d550166d7c7b1eba0b14a2c3ac6907d82b2e548973e36ac955e50bd57875037e99262e9b7a1b7fa4c6597e06f020c42faba9b4ed552cdbc6dac19f9c5d43a774714c9f10f8b6af89e9b00c90f43861aa4d44f6e4a871f3ac0ead09f3f2c7e68329c85107250e6f30836423b5fdff67a2e022ba80e76acc35b1619c0bda9df7efd555bd212d6f13795b2d2f35e15a640578c68a9ca4729060609ee7cc232ebe8739255358ec4962029044a3ad92fa15e1d827f422d76168e4eff77af9a33440f891ce824c1f54a7f5ab7d52d7fd77492319ef5c6345310f5b32b712df21915687cda6c01dfddb21d81026ab2fa8ae0af073d771b64486565e98cc952aee4f3d466480f8ad504bf9d31a8d546586390a5f6dd9c1dac065f472c391bb292c165445b96afff34673f17ec5173e1006a59fd388b653d8ec67660bf2344f19a271e547f042c318284f03035ab72a99e6de1e8268e4b2c8a60f95ea5bc7960abc0d29f9c2d71b8d0445d7c96cb7051e9e388c690a438ab00eeebb2946881e7bb903caee7bc1ac68ede97489260fd82d92c99431d3a754c7a2602b45765a7e10cd1838b39d8d46a3d09fd7f26a81398851be9b75cb8b5c596bebc508e9c700b6d0117d098f18ffc3c7addc238f8212e0bc23511f972495def6e6b133c01511bb16b63798d326abfd3b21dae4ef25a087f294f9f2865034dfa8f10862d77eab2b75d6aa10e3db86fcc8f6a37c99d7b1bbee4dae3dd79f36b1bc7100050be4a4538c8618b1bdcc0891015c48631079afe58fd9bd60e7c2325ca48fa92f0fdc81d2419576ba6f5f4f96a3db5141cb18312be1555ab8dbcc0525fc04acbfa465073c897c5cc39c436f0deead219c9b26178aa814277f2ff4be011a5ea156a1418dfed5431587c4f1122e94b9fe7a129fdde2a185c54fb2abd3764e451256e23ff31d1755dddf0f01c1ee1b66bd7cc3b7209a3a61bba07e78ed6271c6d944d4cbdd887ae9ecf784cdf49122b832023ba785ec726b80170deed650e6c085c265b0826a274b006c3f2d06713559e114a571999d1efc05b5a140eb0db8ed2ffec34147154571a5084a815886aa709598bb5200aebaae55e213d61594e66fe1327274d3fdc6e09026c3d82b58b4f99685d10be7b89d6ceb00d9d28239548433c144e46653ad99a3dee5f181090d4066417fe1ce780458d85b62f2966220ec49ce013a7dadc372585d915ded03a6e8e42e24383f10677bdd3f736fb28d00a13e46c1d3f59a5a7092568e14bcbb66fe05ad0168470db0eba24d70d8774ea59e2d86651e8009c8fc32b5cedecf46a62254ba542c9d95498d4eeec5eb77268636e9df941eeba8c6c44d0e24c5bd69fa41510e7fb3461b50488a9fb1dc29b1852945331c7bbc20308328900a4ef926d25b95b34f8d659713dbcee53f82baa2364bd8def700b3e374b90a9268c1cab8485a2b048bbe7539007fb649b9f29f0a0e3b5d5a9e92aa72e77a27c34fceaa130bbc0d933441b44a21f776179737605ce13e27cca57138d4787c683530d1035679b5cd9a98e742d1379fe937b7f00a5f876297af00424950e1faf6751c33518a5c0c97e3efe938d108eeea4fe88f2a6a710c44a3eb6892231b1b410ed3eee38f7df284560137a0558556ff4111405f2100992106cfea644a12e0d243d37627e1d85d1cb30270c8c7ff3fd11726732973f2488af5b4cc61a7f6a4b31fe3107dd93ac0f644d5492fb4d27d381be12ee6b4f71b45ab3701a0bcc8b6d13fc8c295da734bc71ddedeab3210dc2a08c96480e7d54bcfd75c981e25cc01e2560db6a9e4d396bdba094925a6f72f3957d4b30c6e2a11f3641beb58936e95f670e3d223de52aaa3dc38a7743847a01116acc661eaac2607a3be94aa8beba7ed1269747ed5375b820c74285f88f3aa4404f29486e0e592859b85518a6def0c59da3725dc65403df604f4e1c07859de95503e032cb39ef9254715fd2fd6b4c63a287c24491402d8a55dda05f8f3bef64aea8f35e9016a3aadd0f03a967b0508533dbea7acc5dc12179009566de0a2581390734c66031b62f724925197a070578f0eac4cf998c710dc9721acc1e2bee96478d757f2e5afa03cf9d54c507b994a6237d3dcaee3c92298ffe594ee2f00819c335a8a49708e5dea6fe6dd43c164219f03e3e47f70eaf5211651427a8003c80ae14899c8dbdf7487ac2787604813a3c78167f60348d2305463f7b594594ea8eb5c142530058837ea17613a725256031fffafc9e87bad6668e1fcf4623d167a67929c15d1f8df27bfbe5527b622362f08b08f392b36d1684a027112ecd76154f382b569dfe7b99ac5fbc2e2c366d0069bf7a84bb3c39e650919a1ba3080f3d9346dff27948808c97505294fbc4e117792907ce38cae92efba4ae8910234c893b7484be83ad34f111736e82a2517834bc8ddf87694b6eb28d49e208430c9f3669b0868d5469a05e5d3d287d35ef0656ebe03c26be46ce04528a201247786cb0927a8817b514f3335fb089ed01c0106dd19a1c0c227d6c62c660fa777a0a4ac574de82f66fb04185d326529801908870fda1f78e33bcb4827bd54c8145799633eee7b8c4027996465a47d5d383e797551add939b7627b9e84edb4601d5b29d580e97bb18a7b655bfa205914084c22139b1317c7e43f2c6b9443423d92b65b23eef6653dd01974a4f9545bce89cccbe7c9727638af48499c7db34000a90a0bb591ebc69ef03796b1284d83f3362e9ca7c2b579b9b1cd13b2355d8c7ceccbce5b674753ca2e65e6a0e83a6e5978a5d9efd5a12f4d37fb12312e2176dc607e853418cd7be85a98c857c62013e3e1ad2175e2ea985b3cf47befa2efc57bfdf5139478c45de0b44f525f4d0eb2611aeb1d314b5a95c923726a00e0c4e36217b0c458701abd5e49b7840f7f5593873a91e6a4e02dc5c063d0c4371c7ca821fca3970235600694c10fc0e7010f1d361bd06cc8a14f0a0b441d64813bf31ee379aeaa5bde0c16dea9f6bdf74ef361e72004e0a7846d94be2d3bc5faf6644d1275b135ed0d75bb231d7c09aa922b0f01719d5e9f1635b4a76d4c34f02855a027f6fe5f59bd10891bb1d82d0c69e70a08ed75da062ca4da096392c1de8017187de1409605bd7c50e58120078a6c90c806291f6499a0c901420ee872409607bd6c042acb44f90a20ab65554d992cbed21a246398a7c434c5a629364b91090a66506e8262738acd53d21c05261498a6c434539098407d0e0e1929518a6b5d1b79361dfa0eb7d266e102287a329711f163d5299748d9102f95b5985a772517e382678696c1a590e9cd7de57a73319ec0059de1613da252d1b720abf24ccf0b52063f2e74969ab4d6d9f940d1437507082fcfffdbee424610a8c7d5f415cc5061b683c3b1285531a0105669acdb8b29870517a05ba801458162684caeb089fe222b478cc35b2ee6567f566e1fbee09a72ef4d3eb86315fe821bcb74924caf7f372c01d6a7675306799ff9b4761647b0f34a9354c68b14e32e619a0b1b2da496af8a1f80ad22b9bf3b84f8d6843601f0ebd909370040afc4abc6b12826d0ca1d0b6b416e7e2bd4fefd72a1685cf622baa15702a3f89248d5b1758eeb0ec4c5580ff6008b6996bd2a3bd7f62313f6edd930ef2c594fe8068672d4d94d8cdc21277420ec2f9d713a74137e72adb3ac345559752e39a495f4741e228008b7e8ae620b3b8c9d91b229a46b206a0075c7b45be246054fe0491123e9e1722ef0865ec144fc19b224511b101be96953024ad257a29d2f3cef771161180b86acc33f7021553e486ccd9169721a1cc82373abe2ac160241e8d006222b9fe4a64702f7cd2a6c26fe7615b204523f74a9e14921f62942ce6411f192311fcb0a19ec02ae074474757cbee4d9037ec246f8901afb8bea60b8b1fb28b10290225cc98c6fe931b988417f303e3ec0097d5ff6a13d97356ae556ddf12410d88a8ce885b1ee8a1c305735ba00fa7a4660548182262c54809c4e2f3a46f637a6176584494aafc668ad190f5c0c276f9574ca354803d85c7061a8ac1e97f9da3bf3f27636e7eef21e327f3de122450d059b57b63865b48b841b8769cfc6cf2e9e23abb744b40de8830b5c4b6465776892d0d7be43922158429fe7a23b013d00d9471759d5aa94aa25c8c5893bc755717e3be4c5aae5b97e067afc626c5474e82c7df721efe575046b6152908beb578dc3a0d847e8f35acc2a742c7bcae9596da0d12be5c4eb5b5cd314a50c34b2f6be69845255b3c95afd0e676f83f74d853d02688cfbe087b34fdc6557f7810daf0ca9e2b5bab7be04415dda1fceb64cecf0e275ad6b8ee043878a0792e0e9d39b06facda256b994bc953bc7724f65c1ee982b857b5ffef7105a9ae2b7f9fbcd1fba59ce38593eca32ab26a2a91d290b6d9799f72de198a7c8a9261970bfbb6c68e6b82b2da781626d5d09a5a43476211b3c9d99cc127c0721de8c500e865371efebfa30b566819f41fd1d0a5406254ca8b684b399663f9f70e419a4eb5218289887435221a65c204d8d324288d883becbe1f79fc0f4d763dd433909dbb4a4d6058deea0de853cbcd15af63ca4f9e2c80c960621d2c745250a42bcd3d509bb2ea030a84321292b1795ebdfb803549992cd3994cb05c7dcbf1172c3387373c202bf12241bf53bff4669fc1f091ae1fffe3f5ddec490f48fe642fa14d739d2e6b291d508702cfd7b4a322ffbf51cf2231cbbf5fe0e5ac86f7a9b47f2edc1fe84331fd872248891f55211387ffd473e9cf9c0549b13046f79329e9669f6800ccd40201337f4b20e13fb37fcd8df1102b014271e530752499e6c0a61594e3b07f2f3a173bff5f908eba3e30b811c31d27c6c700cc9f9579880266bb08bb844743b0f4893b72f170ef576c1868929b119fc864b69ca33f8862424382c76cde7a3662fce19a48ac6224f37fca6fc0451cc0018f7180b766e51105d5d3a9f22b34c22f71d0641c7684045f6c9e24941d07cce23f0141070b2a28ead42148f37d58a83e8d7e22070a984b557a30003e432d82e037cda8acb92aa51eaa0b32030890426e308c81459648839100df4b22101b5fcc827c44781e66cad539a5c0b0dd4ba58726235823e388bf5a2bc2d5126228abbecb06677c7cce52ebf737fc48a0726449b5d3f37fd8ea24ea591dee2efa2ae8f860766cfeacbc7704c7f6f02835eaaaf3ef93726f538fae7f511f50298554a9bf0ae3a93c9d76914a94d9e500d02b2c86b362720470b20038d733e7e8d21475b7de7add09829b86448f6e30d61a3e0c1e838b18c1756df8daa8b27a8a4f0e9e19e79aa74217545e0ebf7aad221da57b04e95f3f210cb45a6464002997d0c54681a3e8df2c3905956a3d69a067725b6f35cba3476aa7892bcdee8aec6e18fa9a61d1b9ece698bd9789aca3937abfb270ba89426f0ada8dd210e81028ca3066bc5f3da9a624942552a5e956c4406882e5cc6950176cb3eb710d1436afcbcf82acb5c2961e9a75dc9c670f47fecf37c7e892a9bfa8f80e14d5f3744180f5359cb30eaa97fc0d5a35053ef0fa46b37aad67bbe6a73a5435e3bcaaaf1a94269bea4d8ca57d857f96a2221785e052f00081eec02c284413385fb4e52e2830e8e7315535b2693b59020238e6bd670daa60fba165c411f594b7d98dda9827edc000f91eb016c173ca1657808ae5ce88ce5f4feff1399cb390d16c63bb5c2aadf83a8f8374aa0b32249b4048feaab9ab1e9886d1dbb86e66880a83d6a3bddfc19f0da64a9169f93cf3cd0b04a29eafafe18720058dac691d2cda354be39b195bca845e2703aea915c86dff74e3fdbba071a140fc3f1f58aa5843f808f3a7bf04bbc38f088c8f4003cba67433ff007e8378b0d6756fa9b9411023c56fae1fa67df50ac7c948be608ea279cbb502be7ae3266b19727f20df9a80d420c82b727d413fe4d90053aaddba28b31ad661141392b80c9ca5207e9e9a85e551e6c9f11c943a1587da861ab2d15c158e32725032d486bf18262f25fad58120e9dc5b12200850bc7374c843e4be875d82b7bc7a4194146f7b3da74cbd95c6a5c5ad0224366d36189b71c0cc60ff3a4b6a4cff802c8427b51f4edd88054eb8ebed0481aaf7d34e9c19ba84c48f63472370f0e979799fdf600b526c383feee9b95d5568b1b60a922691064244d4b43020cb5077f03f91f777ba7a3ea544f9a1a932b6a46b546d2d09225144d4156de2b76647dda23460fcefda98feefe1a9a777b0e4ef11c5c0e6c42f852d89e21f6279eb4d84339e4b50f02c67c1013a79ca183b229a93ffd7bb61244fb10d1e224bc905ef447caf1eeaef572a831301b43263c63d5a4292dc28e62afdbfb40ae89a4e900d0e82bdcbf3ec55c1f56559093f68193d5556f224148cfc6736be45f860dfb0e68f4948082c63804a22bd5ecb7d4ec60c18b770c70a2e41baa32b01753837e2246c394b80b66c82dd9188b566b4f40c850947eefb6d970bd7ac9b947cb0972cbed362fbd87e4aea6d11e341c8407b9f9e011ffa72d1043f07264f65023ccdbcfb2bab6b89d58055688bef1e841a0300c9c7d4b4b2c6ece7b598a856918dafb1de2f63881b2535065aba79b347117677490729dceb24bd5a403341fb322bd3bc8bd98e3b97efbeaef16218c71557a5a1c0f048e314cb92c6ed6a2e23498dfde92e1f28500d75660210766f9bb080fc7488f7e03bd2ac9032f50c9e7b18045cdf6a8f4e5d7843950f396694e37e25a2dd625930382b1dcbfb8ebcd4fa295466bfcbaaa28c4f1cb3a5b00b6e1df0c837626514e74ef4f811c6f0917e417a609c41a47e0de75237c84f2edfe70e0debbd7872b2e1e84f4cafb7b95d2cb9b157bf983490acd3a16697973dfd494204335cd60c77b4b6d56ad377ff4d1178fd1965a0f5cd92807ef679b56a32f7316a803be746fb954bda619f1b45aa61ee4d9e9524ac6f8cff1d68d9625975921fe84ebc6a57ffb68646e1628b8b28bc77b777d27a4e25bee94e286dd003adec8dfdab7b8076fc4fa141e387359a9b02879cd32584a4e98cadf00e35a7868bf5306e4e66c930b38b2bd2e6c786c8c14777e0cd83209db55628823700d62a9dc1ff53271df42f972ee5b8e6f95ded02330bf566348e64fa7124e00e9765a582e0fa63f82b6dabf017ecf5c2bc37dd8ad74e9039c37c6ab8c4e8e8988075ffdb8868fcac2d26231edf858ae420f383a449002a55d222f09cd78ac57f547d2878361cd6b44e2af0b1fd1622123c0dc042cbd7c67c8a5af23b17a06f3c332f830203fd26ab086979b80ea59b6d2a18ed8c242260b82a6376644dcbcd4932534e5cd51b9b253847d0bbc8762a00d1804e01a82fbb73a5176bbbc8e4210ce1ab6de53da8560926492c3cdfa637da9485513dc94b829dc944fb6ea13156981a7cb3972fc13f6766ceaf1f21ef066d4677e8d5a2f7c81ad0408e3961d6a0ae567987c568dce62edf5796d649e391cd48024685e1ea9679abd9ca042f72ba1d0581a5ec1227b1642f862f60d4dfb744a8870ccd268e2b5728dfadb366993d7ee4626e5a71101e6f8dae63abd0139eb4842b9c05f45e27ba037b2006e29a0845f279b18ab94a191cfc5594fcd82bee7405611748f800b396909e9481517ceb43d7e2fa734ef120e8a5244dc9e76c92d002521156158c56ee9f23588250ee8db22205e7c724d9b5c403dc92bfb1660f48e483a13ef4a299ae747e5c82f6733bdbcb9b65d6c0faaf1aeddf385412cfc23c61fcb54803958423df1fd5e0c3ad88c22731792a988f764516c94e7570709ff55024065ec8a6b97224387bf6344dd5dcaf0bcc9ff575788765d31b6cc4664d23b68eaa91fffe5078954c45b7c5ed7a734c58f63a529b2e45463dfbd68850c3f5210ecb1339bc549c108e3905813037f94fdf63db435b4af5e50429c77a4f28183a136d477b3b06f731038b563b3b952cb074f68e3cdabf9362887954292ed3af40038547bdd791b3d62663f02159a4fd5775dcc584203a00ea6b4f2807735c12ea9061d4dabc93586e8c3d5386cea9037bbaa450b5100c860f482409e38295c6da43d7039086a22287435e5e519544d30d31af4c20256fac222f4d251c06fbc23a47d7e500cf15f9a4ac361eceb2fec68a8d0084a210e1fe161e7ef6cd3b856d39e3a119335ccfeac210f0a3933d2e8957b5ed896c3c6dbbc1bd3cc49d4fcc9748c50a238d15084f4137f1db4ffb71f33750994f5232613301250514d9dc1f7dfcd09b0fa31d949dec092b424a680a33cfbcc392ee467ec0ee7aac44357503400ea0c69253dbfa4928bbd8185ac747226a55efc21a40fd9da5e594fde0adc6fa19ae13c2b6dfc403eb2e00d0e3de7cf2611a9db43dbc3ec73663435033acf143c517e44277659f72777a987219144ec8591f2263106a58503aa8b64300005f62ea92e11129062b0b94315c09ee3b0623ed80346b3434a4e23790a8224260116fb5bb78dbd5bf63c04aa7f7f49f755da68cab4ebac5e82901922e3cc5cc9b3537054621f802621d5bb8793286fd337c56bd261875a45727171d77436941c00f705339666983750bb5e1a812274c15fecfa94d2fd409ad440d7c75997db3c06504a17a4ce0a32a780ad5487b302e7d498839bf57f41bdf153a20093271fb669f9c1a8eaaed2a7e187e75b7d238818c7d54530d9902806e6db4e272c9a481e0a1c854e73eb526c8d06fe4990bf13998b8891e4ce30ca50c3b9d090ef70e955f1fd73ed82ab053a9981e4c89d3cad96f77269d471cd19e0a641269e32b75c1fb1959844f7c8fca512eb5d927b9019b362bbdb633059c044ca77e7d3d37cd2b24e5d46c3034f52cbf863afb9749178c0019d3b53ee9a5a04b63bc1c17dbb3e6bbfe85684b05a32e18106ab06d7db8759447abb6b334fb742f95aeda71cac957479c126dd9df6694d60fb8f546ede90344b272229eac01e122775913d643960423bc49ae4818f1297a32b8bfed60395c9683519d01a08262abc6c163d33dff6515bd371659278cdc557c22376a6ae180931771d0c597f1ee10ea5138d9d9f7b4fc282ad2d5d04ba64f1c93dcd5ea77fa48497a7c23c650a107da469afc1f25af48b169ee7a4d798f6397c3d8bcbfc9a00f54fab1e5a6466102887bfe2c08443731dd4a929be9c7b108c58560804174c8d444aa9345400110fafcb033ff5a01444f94568a85e2ec0d9c39f1dc9d88d93c5768e6c00a16ec4ffb19ea2303f41f7d8a30dd1617ff35c2df55b366fb3d97dcb6a2746fa275483fb1ff1470ca0721e273e75c684c350ea11118a8da0059889350238a5d5b5cc7178f4b9d3a33233859302885d899658adbc20a758e2373994a1eae538995ed160b029e9ac3bb9d0dd62813bfabce3e3a45ac0b3372bdc707ca6f532f928c3d623c24fb820865df970e58fc017585688055e0c0acc370d71a6bb132d2e24b4631bde3830b83563a7ff72fa648d935db566e092a54641d8292c6c364dd5a531b494a021f62a8a59d02234159389b523f0e2b9d7530d84e7597c5a4cb6da2a307f2bdbefbec2aca580e53dab43e6972dc802e176705069b7f836f263e42c332628f0167a08c38cbbd4d8cb6368d6884a00391375e9f3004dfd6f0d1bb385dac45a4652cc56f8340affea585127596bce92650361d2fee254859990dc6f0a9f8138f007aaa71cb451f236a3480f153a73b05402876c11cf4dfa231a6e717307c46d28626692dad2b3e6e0d90fd2dc3f249e0324105754f0013bbd14003b7a917334f0c9f54e0f0b54ff24735fc226848143bbe6f835b2cf6b4445c54547dce73e6eae9e9beb00bbbbf244642ccfc69b47523a42dda5913f1bed1aa7c5040de4fce431770c7da2d32dec54e116eb10a0e7a3d6f8a53aca3827e3569695f17fd4e712ffc7d19095c09d580263dc098414ed995b0982e39a5f25b087f8c1bfd64ad057beb6a87207400c98e3adb98f4a5cff9309b149d098fbb01fc44b72ddaed42f886aa9b2977d413b2f20588e4bf109002b72ed6d428f1ee97cabe388059c77d13d7fad529201d4bf8e7707797f01d640e557a5ad44bfa53c983b6dbace0e93a5bf43668712816b052e864a9945ecacc40ffe0bfb686902a0a5b59b1f960e5a5be30acd52f685a0bc1b840cfa9f60ad9f9c3806080b2ded718b35a3ad3098aaf07916ae5e55651ed92d3f36b23bc6d9f464e569d81d7abe5d23cb5bd324dde9ba19105372f10dce6964d1f9d97e9413d65889ea9e8872a858454ed0a26ed7eb0d7d211285dc02109d3d79f901389c105b1ccfa33d9f3a789f2bb575fc8cb5dc184a0953d6dd6c458d1d5440dd0e89c1ca72ef4ba89083aafcbf7e8e9b3d5f27025cb85110cb0d2ab3f17bbebc1b1b9cc49cb957bed2ec0b159a8728d4c6531cd324c12fe8e82883ad96a1e3d27b49b6717c2cbb5d4708c383f2dba74d436818f48cb5f3840e0172d7f781bf45a240b8cb6e91be0e120bd5019ebb700baac0e8bec9d803e666f9e50e45ab40be2df2e4b2547279870a1b0ef4487aba7d4015a25d70c8f8dd1961d1ae3190842352cd824b0a206848ffaf15d83abdad2a473e64aefcf654ddec907909b014650d4d5e95b937cf882320c63551ef432e52a0710947f381dd5656a1c4eca39cbb7d4c08833eec3cec5664594563df6e274123b85f3c55033873089fdb3a478e5e9779b0dc9848d5259bff77be992dc562bea3257c8f755e766c196520f9a987b8fc955ffef9f2ad9810baefbd6c7b112172b040822d46d95be376487afe4714a8423be1234882d64a93466095b9d5c2ce7ec54f8952491734212b7dd8f0a8fc7377a051c1bb643d9388e8fcb887b1d2a8a0097b5ee30409687495e90301d0c2e4f092ab7ff36f48b7c53442fc95edbb16456b6b0251a85365ff10da25caea4015594c9e8b7cd59330e223dac82dc6a8658ecf1b6a88055761d89f166f44c8375dc7f0c635c21f67fde1c0b8941263ab3f2a2d79f14af83a666697d677f11f83b7a084eb1e46746c6a3235c7493070d5205daf00500ed073a41143694dc0fc0b269b60c783f7006ff478e171b10d9e8e7004090198423829a4d9465499056b6065f710008ec3630ca6400716a79bf53120df8ed9ef7414ad155070d8573990e34b0528d3afca3d3ccf5a8e767e22c4b4df4fdde11ef9c2652fc4a21dfb70df10e27633149475a1a1fd4e84303212438ff4cc8dbb62cfe8207c7fded656f07478a96447f22fbdd723b8f6b081f620ba8357f5764f58e641fa310f621879f5c58c64b3f5f1bd385c45f0195c40685f49dcd16a2138d611ec0d45a15feaf2865dc7c1bde1cb2b76c9555147b5956b035326d75ef5b349fa2c26569963970c5d2c57c8beb07cf2ee55b5e74a88ed1934d044aa17d9f98118e3f5366d3239a98ae16475dae6bed269d5262f41adfee64e0e401ca276e0d413cac1214b4efe402d5565c81f11217a716366eeccfea381069f564064c0d1e7307c7b9201d426c361edf1a6301c9c73c13419d1d4c74d23eb813dfede6d0b747b33c29996c7622580cbe07be9ac54bc58ecd762c9dbcbbfe61a3923ebcf2a8891eb228ee86124966b302d28f00ae0a20aea56ada3fa223a484c1ddc47d9af2704366e151b3a73b882c9b61d6679682bb80560072065f00323a83c34861a20dfc0078d80ff23b80bd08cf92acb45307dd93e2a272ecd0dd26e81d52d1097c21d7c35e3c8d28bfa730780f2011c6c619830b860670c37fc99bf91e5cf117e4adedad07ac59cfa1b4c577a38ef1c572ed2f2f8b19ee5ae9bb5db0e4509ccdecbe0638cac7fda4a6bc0d1b76c7653e1068cb6cabe5c14c3d3e3b3f5a0c71f1affd23c2cd22fcbd8604c2e48aea52b31c87dbf6d01afb88032293aed3d64d569d5026a53a22036cf0e74d8e050ace61695ddb14c96dbb682b58ba620aa951a961202b6f2ff1501fcef8cb403457cff719fa538e2bde80148b8fb56c46813fff9c402a61566f0cc65f146d626c8c4c2159fe3400624b36be5450eb26793602aa43728f568db518d1a44740948d7fbeaf89d2f83e1657aed76110a7f580f9ec5c38f4fdc37aefdd57fd61237e7cf16b315e8833dae142d1c3aa173ee52ae7b5f12513f8a8f4fc2b43384a624c2fd1e84ecc8569d147439244fa7d03b9feb3cfae01fa70cbcbbd30614a47b437ef4e95a73a2f84424a0c71e04b57d742a093c4c259a48c02f89e915e8063893fb5f72ffa41518b6f8dad03490ac90e468c66eeee2a9d54dbc871c3e090ca271ea3712fda73954a210e496f138552d589df4776a3724ce21ebc0f68d0b06fc01a4432c9a9c2a32ab436e93c1408268df80a9f2e0f569f9bb9186859d8294dfc381802b048a9457ab57417f048bb3d02c0428c649c6b1f92e4422d219b63fb07aa695467c4d46b689202fb9703d75a051cb23b82e5c7452cd8de86b8c8f9e1b48ca2e8951acc5e754c26a096d2cf83d3493281a3c367dfc8aa3d860f005cb0a7083d338f1a31f2451f82903e4d9336b4bc39e9e82bff7e94442dbc87cf5b03a47fb429b95d05e60bcd923eaa5ff97e2cc4c4b12ccbfab3d7e19109ac3634a57b4cc924eecddf5b8fac304fd28422fd0a41eaa779926ea89bac8062876e2864d6e011708c3794851a22dc65fc99c25da713bd0027a54ec26711bb20b5993ba79979f959affe4c0e8f433023b267d36870dbfb91b9c41ad528b3b19368a1a36ecca257a8dadc6c93e72781d318e0fca097bfe4aaef5ad8fdc5f20363b9ec2de4c480006e80f2014b791f644b890fc3ea1000942edd46974cc878d6f5ec5000cbc18408834b393c52f0ad3d4bb667f9b1f155957c2c9a8d2169a902b78d64c7185e58748e62563421e1a9ffef08cb36d1e63342e5f4768c675e07d248311f17e8972f2e3685220c1842f21be2a99c7ea2d78bcf1dc7112ee20c11875dfa53a763c017af0cc25039f6f26804a7bc4ee5624034b8ea3f4c8d3d798941c7c92127852abe6a1045fc4ab1b617236b1d3869db9ba09ffcafe5829fcff5d8f8e85ff6a8d657c5b519ef7206ef83f5c11fff2bfb06ca9b95964fcb1425fc79ad83540c2b4cbe072e9e8ff0a96ce6dc295e0a5763908045e9d0203c8365c72b1763932bb3ac0cb07109ac6bc0b479d595d69033f3493f8f82be7d6400d21d3077b3325dc2170bfd98afe02dd8f68a15cf859dea4e39e8f7e20666867408f6accae4c8c5e5cf22acfc7748e80c4360a59ee1cbe7e9740f46e50af1766e1366a4a72988fe787ab01b604a91e3363c770a809bdf11fde0e8a24bfc7f05fad1f4a9ab8bf81cd4e4c02f17bdf690184cfcabdd672bc37e0f2972e3278d16717607e0a1e93ee9ed846338c2305c1b31fa64cffba0d07e76e5b123f2f3aee150370602b62a4b489f36adfc9b5de312634042a6644c50f5996e3cc34e6cfdd56560164d73659e833b865c8706bae83365ea3563de1fba2d055847cd0f0af95b1f35e6631ae0bec7cc50e2ca126e3bbed304275cc176c53148eac625974e78b0a058685315565da4e8193d00cd80ee0d8091fa5e6553d4e39b932ab70d90cfef768f9fe2e5ad3bccd2f21d3301662382eae932ebb407b1980bac067cca00d4d342e97da82addda7754be7ddee6a41c43600545197c0aaeae924af74b98f652c36504e8174ebd7e82f02a0685e5cb3499b6eea696dcee2ccd671bf7e86c5db18f6f00c4def546c8ed2174f740aafae5c3d118419db9edf0fb6b2a32c95c17d64144c1b68291055ca109aa156aad4f3d9858e19b4b1c7baa706fa8bd87eea38db97f784dd4ada2cc0fc868ed0f60e66b3c3a78863dc08927c6eabb5ae77f240b371593d6cf3b107fd4dd6b679a3a5d78283411b91b9051822027aaaf03ed7db4018203d4d41d617d36b08cdfde4491dd2f041a2adf2d0b7de8edf1a352938045853a40e2c95d0bfa01a7a32c8d0a93b30094597b00857a23f2297acdfe5feb4769353b6654e4a88a333c0b26abdbed19c9901d0ae6c03eff12ab740b135a013507e6f3ed99034ea0bf062c6a78ce2919f82554bc77d1bdb28163127e81f0bc2a4dbe9432d0815c1678be989ac0168763ccb94265c7c12853f9719dadacd87e73470a439dfffeed502a8b8cd2657a71a5e1ddedf34bb55b98a1a9a2ae387614399a5d32b4b18966ee0804fab4a70ce6fcd10ddf57d86020b7a8643af0926ab727a0be8971a8c8240eb1aba4d25258e7b4b94ac1ec4a16938a18a772375f49c2e7f5059d56be417bb49d12af3f8d422b9c370447c266de42feacaaf62e0560fc3cd329b6385852f50a009459ae86c7baaabc5dbc6f360d8e24c2e81082f4f29f4d6db956ee9ecbcad14556e957ae703be5387fa72606e493032088a4af99fc51fe467683dbc93f4d7a0f011f30f7ade9e022ec0eec10856172f27e417ae64fbc68600e5a0fdfb353b34b54d3d2d3b13dc423155c419615b2ee1fefa7bf172e72ca26bdd4598f9986ff725da41505b77d85ec93679ea0d0a8bff0fad572116e80a2072d83633b0e033f63dfba5d2872e17f98bc1742cabc65f3e7adecef4b356ca894907abe73d0b773ec5ce2db7f0069d960b97141a111708842178316d89ee616b3e7b28071b00baf7f23d2a0c128d4d5d26494600afcd7949b9cc4b0e36f7bed117fa2debbd5b9d1fb0b1d2fc2d9dc5507beb06d6f5e02bfb43d2731648d1775bffb793e9a983e33f77b40d85745f4fbca71d13c5ea62949f9a8617a1f61a53201333d7d310c90ae1ff6cdfc598f8c22b11f0f5d7eae49eb59673241c55c9c29ddf5aa0b7bfc1b8cbd9e2569a8b505acee2fcb52b9e164cecbac4620648819d91f62ae7ae5d809fedc654800bb6ca3338f127179ed85a6c1006d434672bd986e593c2d06ceea22d36d2bfe210b2684384c2d3e347bf31991b30e0de3a3e68358ac86614733a3ddc74d4faddac75f7caa694abf5605986356ac3fc6a6ab58591c90d294e94c48be1e45d31f16ef3e908d67e1da8c4af976ef3e5d091a1eb9debc6ff9089ad7a9ba6e50753a2595e410c802302fa87f81e9bf31376a622faa27e456c5940017c09c94a1e40d4ed2e2b88911281f39d1a2adb89e2354262665fb15c5d17cc2397a5a3b7a362e29f864dc0fcdc4ba59211470a1787040c907477c29e62e7f4417c27e25e0d8744856473dfc9c8a0b4f4c2ea47118fdd3defa2eb319ec189ad0ab460c02bc30fedddea0d590eebed33e36e06e6b0fe6e3825211352e24b8a991e08f106f8698d2f29de323f86e53c57a1ff5fab7c3dd460316e249ee104e6fcf7b2ebb417fc7642994d5a859d3e17b56c8bb817c434c5a467bb16c8c5f0fd3b794dadbc8967d00f568b53e43c75beb283fc9baf96ea9c081c34e2548a6244b64479060f300a50bdbab89e1a1ee63be723642924e4d20ab460538f26f10560f9ea34a65309cab5e8fadcc49a94626b4b47d89b1b64f82d0c272d78d7dc44b36aa11cce4fc527e84f015a0c1d9d04de20d0523aa3b7aaa60deae3283f9b65cf5adaf79bd17bab1ee901fdfc20d45223ac5cc1487411fee0178bdb022c8e14b9b1185007470814b306f42de89f292b00a34709e2dc81a52ef4acc503c7f2e99baa33c9fc441071b848acd842f769fa38a77473df5877316fd31ea6a4b805e2c7aad033000a5c9b48126c692a8d2856855e6af6330c71acfc835bb389e03908a32954afe11dc65f8a79e724c8a578f9db46b4ce99389e80d9b0041a141ce590e5007d96346207fd8bd9ee435eb566bc4ad8dcbb5b0fb3a54f354696770a7e4e6ccbcf90a3090cb6022ab1155b204865763077924070783f8120526267d8fb6d0b6feda500974850f2448c9c1a0504e137703b096d4afad6d816f5b2650f8afa257ec37a1d10e8abd00b67b935b7adbf0436b1d5c186ab2897e93e1631652a21e094e33b1d5a0cae6e70e1014d43905d8d0ca68ca49ba37965ed38c9eb7211f6939c1b2989095a4c9a70fb9b4ac7f054eb00f13a6f0232edf444a45281bd5c35a0f288c3e372499bfadac112d1ce096f7afc7a3d68f918f41b6ee595a7853358a14dc70cdaf450c24f0c42e6c8fed448f6c7338bdabe983e43a4d45a2ef1dff0478c69c53b64987eb1ed47c657ac0a756f5999725d0f302187a2d9fdfb258389afda79f749f6dae38a8338aa7574e38947a58747b5b83b2844f377c22280425394406ffb99bce1923f35d6125de9630fa4eaa9222a0c42d028479c74b73d067a00e698724532b0523af1adb36254cc987929029ae1df18e496fd831a30b3174cd97529132ea09cd6d19009fffc68eb68ac7603a9a933702f58cbbba0c1d61c3ffc5fcbb4e6ec00cd7b8ed1d2cbc9e810c04688e9e688f2a7b442e518c42859be6f16c02c40a5478da86fdfb0f4af59e704fd50586b5126ad9c7f022e6e120a25ce3deb5f50c15064bb9a3e6dde07a41368d57b583c4345d6d2199e79ca9f7cb9a49091a08a3160623c6c41f050e54b16be21ce916aadb28819bb648a4538c60f68b5a516d09a6de4ebae0909f97f8c259f3b227ba43436de6643b12ab5827435b9acbd2c398c59d3f818e92e87cb1a8f354b8f48a81cb34db91810ebb0c69800c0713ff91b1562fe89c2bb0f937f5cb348b55b4d011a5c75960c49de2b79c75e315ce8a868d10cfb7f6d61c4052d9c32dca69dba9d0d949b8897d465b9e7c3a483629491d7f2f4762e812ca8e5195787822e7e887a8bfbdeb8c0d06aedf19181162fb9f59eab9ca9aca26604cee5ac24b58d7558639900c811ba65357fd3411b72b35818d556ba8729c388f87e79190a0baa0b352ac019d512a129a4a5092423e457aa0a2e63ff3231ce4fa056dcc48bbf42d9b345aa14d217f35339d02658db3a925f665b2de9812305db837b0293e01027a9b40e8856d43605edd7ed917c4c17b561645d2498fbcdaa376f0a56fd4bfceeeb9f8258cc48a7a93fa981db32716dc62408e483d42c3947c6053bd758e76f533a694a89a60087fb1454904f215fea009efd0b9e7354e86f8d1771dc4a0ca4837738ebd932517073625a5e5a13bfed1b7f54e346e5defbe586ef1c85724e0abbb92b101573708f4268f2a202a1c70bd40a8a9b779d7a68ba67812b5a04f579b59a05f27cc99dd3ac97de6260c335a2f87fef3d16bcac1c0164ee993b3fde93ea93036494bf72eda48b661778b73e0eded587c8c7436522f7ce336aebce41d95d0f9f439adb776367c8bf119ac4113627558022aff8b00d1d4724fad3983895a77c82fe09e7b1fa441e37a83d2bf443204de76a06c4318028e4c0016cf30637c8cb531d45ecb906d33d0451552b6c2c0d8d86a62563d833ca63eb3c70dccf6a1f9acbc6c4b0e85ef3fac83bb3acdd3648570222ce583cb403a2dee151df0b439a5eef33cfebc9320e2aab002a225b100712f5ceb0f59d2e4989534fdbd1b987e6c01f707b964682291eac5b513887e8944477e38169fa213cbe5da816b57f20ecead940c1fb8066522986ad3600466635e26fb2e482e4ed5b8b113993aa62c370b3d36050bdeff6c852964b0f7511cde0a09163559e432331f377962a3da830bdd855a4c3f8a84d896a2d0351a547bdb1a3ef39cef09b940f68c438bc45468d9e0cacd55df2e536f38868fd36f8e913e4e2220495fa2d284553442357b07b3fea6115b42666746091f8ad55f050f7c9233bda10e9f1972247499aaad4d9515c919964d1846d8f915d3e67209d1bca575504b150503d465e29a08aeebb2cb16dffac56257ea03d1a8c2b99567ce23ead111263a94c687ee2f64d27b3f807a0eb32277b96ab611f537702ac349b04c412c5ba899a896a40a9275b6943ab633ef975fc1296811c22a1c1c12e9d5922563d199e5c1f08988db2fe46f940f45364f2448c30992f17abeb7308022e1d6ca59c58e2965377e5f7368c2862d4360bb5bfb688cfb2cff05839427ebc094a5a041ee0effaf9746c05c123069d6aac6cafcea28ae8d341a27083ded1125180c97c185813b57fae221a82403cdee0e15112aac5b394c5e6fae3cef129bae28ab171d334ad34aa4dae4d7101bf678bd4a48c97bbd8b7b1fd6572aa540c58047ce7ee393de140584912bfb5e2c3cc6a51421571f1e4dc5bfcdd529ce5deab631c5e07b7cdb1ae1ad80b384ccda784fb308a7eaa018858d7c992368f8062d4cbdc38cce32a53a8f086208794783dd5a6710d77119821c34e83eec50dc633721c993858fe5f9b4d5f6a592f098f26f4fb1998f6ec47ec9423de1bd810ca6a401dbf533866ac45b85509bf692c44175c2d3fd87b5581980d73a70d801abf99c63d16fa57b4c0593a4dcf794c3d1d8552df85bcdee98d120237f15267798e2469a5718408634f75416ff44105c1e20c7b2558587c6b402c441a80f1027332401346172a57c0c1055c9e355158731432476cba508949f5926461bc99cbe9670dae82ed82dc92dfffc3fc2e61786789b11178f3c5dd562629afba80e84f3c55a2c7c972e21452bc30ed754c9b41bc66808d72661278705e7026bd1baedb9338e268355994333f7b6d90cfda7b8ff0451e573f897bbe5cf47aaf921233f9654dbe599986a0f6bb1a0fc7837179a6776e400949be71403bdfc554f00fbc28020dd9f7c033279993d119924caceaa6c5f71a94a82701b334d82d7cad6f44fd4d288752ad508a53eebf2e0652f8ce6b625bb78421bc92dd5fa2333b7a1137c61295632c451ec93acf8a7f278ab91fc2af1ec16a76555ab653268ecc654d46ff90a413196c1adea1fec7f66b5a92a06920e5744c2af1cb032fe61760b7f33b05159c7d267089e6470ae7bb786bc5a47dc406bf01ba33637224d1fede3acb7e44408ee53f76969ae7f5fbfb1f750b30e2bae6ec246300d2665bd97a21ecab71735df804098956f58162ac1adf42243d5b1406379255bfb78889c820c78784c69f5632fca15a2a423b22bb12a0c050d2c5fbb0ffe9e7acb9a7d55fa9cd74a4dd12b65ff73bcc6702031a590003845d2e5f20092cca4a25e368a4491a36f584102ffd1872be40a91ac803e34e390b9ac00b276b6c20d4882923eb35ae29ff871f9c5697d7236018926dd93e0f554d543066e0e813375135e160ca0c1e340a090322f54f4cb5b18a04fe52f776258446acf5cb763f02275fdc4e8d69b5862d5252faf18f3c4353c331af40c2c323a30a4880b7de9bc4ffc0e1bb6d9b358c6b13681db9a1bbf50726688e236e0d6703c4e8e5b1254e9f6b42fcc60c3c5f79737c7e493de4a7f8201510f3faa6008d6ee58193ac4120588da4ef91e0c0f47b2dad9b26c04d194dc107f46daacdce2b20b71c4a1c386410debb48650ad522e5f816e0786899aaea1fc14eab105553a5ccb51a2fc7076f544f137e38110b5d8e4a76ea07fffb1bb75e8ce38b5ee2f4f77f61da78799fe8e479f6e2374b6ff51044f3e5dafce10bc5a805111fe36deb17fa61cb3803df04b133ae995f44cd3c5046d550ad98d02bd549b3ce702f1cc54424c4ad50258cd81fcac664e1a0d19a87ae7df3157a067f4c51385fc714afc41c5e698d9941c1bc58ba0a796ee5dc62673718a41c8d9e0036395cd8b525488c1ccdd4de32f28aa8d91c96439521cabb1119f5b77547bf78e134f092719254e8910d2b3d55247c8236e16b801cd08df4459fec711e61b3e422ef73861bb9c930b90f7d91895168b5df4d80e1e510e91f6e5e3b6e778f275db913c081a444fed1803cfc04a65804bbacffe832263c76075897f11283c11458dfc99661da5575a69e957825014505e6b07035354d1360575a46024c50af7e258cee6dc18c139f3995293e5a2dc5a600d021f870c053a671b7c49e4e88ac2cc13357d0a7551d6f42e2d52cc5273a15b898884387b68f3ed0f54041609988d3ba424c2f5070db5bb6af9289f01c3c463887c5760066715185089749cbc647e85d1abf0834b79de0c846e6c7c56f15a361331d490c88c1c8290d3d0097a0314298364fb8b662f9de547afbb0ca70cb0ae0a0cb2dcfd3056ba8ea3414228aa07cbeb3adc761e37583814d5ad8c003b1983df1f7dcd169428d5061a259ac65cf5b787c8fb39a7f59829f6673302b8f6ac68986133a41e39cffa07ed2fb4ebc00809d6694788f542c4e2d62101d526af12b395e8131c732251f3f807e8b30d32204204cdd64b1b8daad473c855e94ade0996755a4a20e6a3f8b5525da4a667f7afa7969428a6c8f49d5622283a52d86f1697a7cdca90ef15654ce59e997b3793e818a23f5dd9dbfb4dcea8ebdad679c008d1efe43fb776cc31e3eebaf0d017d5554c88c6f17c604589e8015f820189d357d68354c16b44f548b06bc304dabe0f0f6878c133ebc6dd02f0a5a931a5525d2f4886a647c70b68dd7628406f89231c27ad4b66e39690039937e1ed3d33ba911712a6d9cd2c21831a8a0542af1881bb7e29e33e58659dc782ca52f1f65da4e4d382dbe11f48fd763f112ad7691e942412462bf0c16eadf6bea806473aed118a951c93b1679c7650a89294b9811ca46344fdf5a42f1cb02efc12819e08738b856c05b3f4a157cddf4444c227a3a3e879dd4fc8331b1b8a04c047d606320aae484c024c4f8a74019c985a7af2d5eb89ba0a3696b93f1d1ad23b0b54035a3e0a2fd1b913a70ec63ee68030a8037b4aa6bccce6193b3b74db26d15b5bd56514ecc065f154a36a0889b206c265f034e990a992194b2cb1ac242c6f2901f8fff7b8511e3670a5cbc8cd922128fa141d28480222880273e5cd53d4de72318e7741e32acfea6f90afa9c259b77392c19d9c91a2cd3c99542a83acd074b1ba4b3727d528d99ef7ad5ff0e28ff516311acfef3b5357033830f77df8b98e7d3bd08bc240f23ddd16954358169a9b4a825a1ece6035d07d195c61a3434e2ac49ab2dd16dd9f77396f4c86077e696de511cf813626f405c71140c1b01259fb393e2352daee3ee14ff782e6ec14445b3457839d90c0e987be9cf1c3e031f6501033b9d2cd0cb10360a427696e90aca312e0b234040a3b46c740fed270ad4cc55a901f85ff4b9571f9f632af91d681333ad082c5e3ac9dfd30557c9f9bb07d1dd436b699fc7b84f836f58a016700739ebba44292ab00dbc3ed33aae32a924b8f75e415b895ab40630c718f8ed777aa6f94bf40a28f1d354d02140ce750ae2f7916c65a5d450798a005204102d850fb07de843cd4a29bee1420d0bf763ba27bf5809534e7fd16e9a12ab6d73573abe81252f2f8c6fc77e572df2562b201d9f1efa99767863b4686b7eef07fedc03f281a5faa53b4a004723b67add7fa4f3d4e74203fafc3012a5a695e67890ee76550cb33ce8a5a77ac0d40e701c7b50aab5e4c8c2cb220d56eca1d50f25d7951e9bc2cd6fb270a208087d37912a19ad6bc91a81e334e595d031f8d01abc277132a07b05af4ad31eb0c0cae703febd51ff3f3846ce0349b0e829abd2f51c7b980c04530a381977e6fb4563227ab5adfdcbd3f75a2ec9415daf8f59bd41b845140f67cf42333632689fae8f51b057faa82401ee7e4dc5cf9dbd54353adba50a646c28730b8941460243a992cdff43c4053355e4d8e04c770fa104091c1d82468531230737de5f7a0ce124c5da24008d4f9592ca13f3e454427e3b0bb0c1d7150a96513a32418d250eb18a890cae9cb3fbb8edfd2b96a79727044131925d72a5396c3c000c7f3932286d62b9a71c2178113bebb6876c4e79b37b87ad2a8d60c86597c508fa84623dab67b0050c84d413ee044c7121949689f114d7640f57082760d5ef8618264dc975b3c534116245d471401338ba7dc53e6898e795c6c84330262c99af4f26434e85bc0576ce064afb598ba5fb0cc6c3f3b0fb4be4c15b9327bf2ed23bb87da76ceb43b25bb28d78da1a028ea3f512fe961c31d51734f7aedced3130a64cd13e71b8f8ffc24b4810b4e975d536290169e788b4da3d444b895a2fc4bceaf43bec1df11638c49f0824c7560e75a112c0af34bafeadd90b1a1aa87ba8218c2466a95597ed0bb49671b17864aec5cbf499cc0b941b2a3d64109ee1c4b1a982a373f0850ce4c1d297266db6c8b5427532aea34c0c54b386562c2a23451a886a8d55431a3cac5b0c801961261138df7591a8f948ac9ed14d27535543f3d81b1015222a67934d79bf0003aa45fdeeb65f160ca24db33b49a036afcc377708a65799854e1f3ae159434ad474005f175706888e924d8e8e8447a5e04a015cc5b599079523b1c174ae7bacf2e703dd75deca46922e5a7a14e2019c740d04ed6556593c6c0db284cb1942bd41fd772fc4e7eba402510d4c6b4d6d723b13ab1d88cb387ea5a5363a1b199c84f7941793b0302574c69fdbe2e28b86f3d76c4a1d35ec0718f869e076bf88387ce5fcc02f888b56a554973c7cd1db49ce74a958a578d0b5bd27ab8c6dc26a2656bd856043689d7469f3ae80b473af7667a01f6d167d27f214837e2dbdb8a7398fc40e7bad24a8a932e769b27eda508758e91af59a048c8feb499b509b171f504c381fc320f62d81cb49b7a6ff754423c42e519cca69f46689fa9902f813c563f3a74f8621dd4f23a549a1384d632a2e70917b74a5232481be1b1e0ec736ba91035426f555ba763a67802867620201edcc1cdf1453f260609b6955376189a952b54374372b0d57c279924b7d5ba31ff425cac06be41a9f1384a157d5fd6b5e9c9e4cd8e91350b4053340e8955c7900fa08a81ead5a06560794a054069cdaa92ca610aec63a540018728d3eed619ad144505d1567a19f9c5e9363e1d8293f179c8e25e59aa103ba31fe2f64f8c0082bc06f08ac3e5611c19a65bf3ae0d05ac68f666636201e910f9f84c34613bde30725a00809b1bd4be921518541dde5c5142b02630f5c41192c4bad743d1de29aace5e90d9e0b6c3e320fd453b5a41e12935d5869eb9d523042ad3c97733a8120aef6a38f612abb7ebbf7c8f516c83b22530e5474705d66a0025d88c40f39c9ed7f54c1446910e5076c402747fc0b12db30805cd49f82d67bf2822810b127fe54d4eb174e884cb5d71eda12c93da39036d92143788d3ccf7e54d3bed7272535c39d75f675243bdac184d7b8d013bb2ecdb25d2864a44ea8471c3c556ab694f05f08fb88d8020035218065ca71c9efc9e4de383a2740f43425f036a444f9df8fe99f51eaf6c208bee7ead802e7b786ece93d4ab779ea6d394c8c353f5b8088366d271bad259030b613a267078e740c0e296420fbced181b7a0064682dbd5f883aa9c47e873d5822abd4d4ed8a7c4ee97116591ec017acb1567abf51aaaf466b617b350d6402f0ef97b5910e0f5f3ecaaf8a848cac2cd206fb3c3e79a3e6106a7a32d5d48c3c930aa6015ea4cefa222436a4f9ee854a590525325ee4acd43415aba75b0af9f1d613f220050155fc20478b6bc6f553c1203c7b92d37ac47b7c8ed1855764943a305ab42427668e8caffee01abe0265431abc4a39fb6d6562ba02a64c8fd45c142eb951286140383b61dff8abdda1c6f1aa16f4f36e35a2f5fe4c71d542c8524e37c132f1f11c4f4d8d019318705cafcaeba2e874b71fc0acf9ebdf41084a881ccacc169b7adabd4a693fd16e074bcfd75ee5809d4625eef22741928f151c32c1b2d71c57f2a2259d4f12c4122841901f737f3fc24904f282db39601085134c1fd3f5ac574c1f3635b6e9fb609dca2524842d326d77bc6bad526aef1f8e307599117b7f01909501d6088a726fc17dcb0a4d098165ce1f07596238172a840f6ea6bc5b3b944ed5d96484bf84dfa29ede170f6af41137a63262de208841b20008072931c490331d88fb82423d28c9ad99523ff277d8eba39babc68d13079d923c1c22b62f9d70ef12b85432a3844efccd330d3b7a2c4903ef5378e092035a521adbed29ad1daf989ab378fda625aeb6abd3120de27c7d2a528c920221213fe36a4d9d630a093d87cf0021d794e75483d840a693e0b22a0457848dce5be7ddc218926ba6ee0562e539b0899b82adbfd00a48a19fa33b9a0dbc48410042bbb090ae480edf6cf98153832e5082d415a3bd595e404b74e17a3bdb0c48630dc8702f0c221f44535d598c0c760feec9866b6a2b3d878638396ab8e1bdf434228a7bc1f21c67bf01cc94719f09d1785ba0ab56a9010973121882745b6c72ccc5b107209b692ae894a02c3f82f035e5a342ace22ba0fea4a80b2c86ce9cdcf58d16d6f15dc84afebe3f161bb513c61953082e845d5d9d03f07ecef81f3ab47c2cf7cb7f857f738fea33b2677d1094e131771e0fb3f4a4ebfbc267575941fedecd0f588cc42fe90fa781e32492a6287e66fc1ab957cc35c4350fc40ce46aa22f967ccd30801b5575d877385943f0fa0c4530190ca9d62b0902a71cad6b65dd076ee718ee84d2020b7b2e0282991aae66f24ed205689230453045322a731c8cb34fbe1e319b31b4f0063e3295dc6276567f5114c0e0ad37640d3e88a7ecc02f6c9dd2eaae80e51c7c1e39afecd36b622101d04e8b66a28099dc03eb2e8e05ced173afd3774fefd9d5e5b36fb159367048d8ad6964b11f08c37f618a78637fcef6b0d5c5f7899a05f97b71b672a9bf179f321daa4a524e169dd5a820dbfc1ac5ec4c3703054b8fc45ba13c500e06cbdbb274bcbd96d82fd7a8d7fc73004d63e52a56fa11ac723f3d627de309976d0b1a200fb7e748eb3ef74f128766a8739424268c091861f09f92743f33eb9dfdc6ac1896dc58e5a8bbed6b576b138f9821e248cc2763ec04251bd05738de9f3749eea955eb9fdd902ebbd7ffe8c5cc05b564d1e3f4078cab5355d4cd7d586feaa1837bed3732b27998e0c2571c3951606e47d9c9e055de17a4220a79e3f39386b4ccdc450d03a0834c5bc33dbff4cc8a4c1ce576124da4dc172514b8d0c14406735f39a33ce148f1944fca9f6d427d7e660463b783747ed0a999e5028675ad406e1982e5f18bf6de6ab6e54a5741c914b0b58e4884b7071167a05faff881d437f1fbebc115a039337e5d2e58fef1fc8544044de3f6865d1d9a2fd23fd4197d46cdadd2f60daa770caaac847854eebac3ae47c17abfc985455c0ed79dac4e6c6093b7b5620ebc30ed054daa8294a1a2a67d12939e9c2c4dc4600beae57d6f57319aa78feb2b95d68bda8ee4b24e08ee2c9aa6d807e801efb690f10ae8efe86e8780adfa031740eae118ed41b21ee8cc1936c337ec0eea1074470ef1e63db08e87534112b39f587f83caae5bc7db5d44c2e369a881d33fe5bb62285b4652f24173f24bcc166f3fa6ef06266238103bf3900a2c19639f339258e8f0ed493994f82ffb351dfb2088e3dc66f16606e2e5af48216e425f175daf97fc7496d61c7a57644b0cea7ae77a1628a4c488e3f329dccf8fa908a0cdd1b963f96b2c8fbf9cad45515cd976dd782e3906101eee75135834483ca8eee4a035fcbd3217c7e60cd07b14f21d1ae408cbdd582ef7783b366e9c447315fc9dad84bedef3cc5ede7b82ff67e0127d33a89073cef25428bcf74a77fcdd4df868550c92393fad109f8ec9de84262efd9f2be2249228ac5618095075241319effbb98f35253ad86a2814a147406552ede378eab1440813ab4c4aa88f117c7862d33727efdd511fcce4f01cbde798c27a842ec09a96016e00d1ecf7937b045bbe0bd84968fc0a052bfe0bbf10677df790d08bcffbf9f5c5a07eb23c0bcf3fdb1031053f7ce7964fa34c82d10c38e6645259e9b47072f22382685980114dcf7a7990e2ef78eb8a3d7d385eed35e310a0aac1f3ec2e4bb1b3d4cc40866dfd0f513f080f3e99802ff8742c870f2e205be061a3341d69d3f9277f5dbcd982c66ecb4c2215a623fa860ec4fc642ce0fb9fe739c15a75c39d8d95feff6a2b6da686410d25480c50094e9632c9858c2dc52a7a4cc2d58b1a583c566416428e63c2985aa80aa4d61b7ff140209380ec1b5a3ce1cb0c023c56af48bb31bdd9ece7ef7f0b319ed95b916c1d982e6e8501c1e74212d42ec632555de8a55052c9e32928c4ebc84c1baf32f56068b505d47f0e4fdc7a817429a59adfdcf22c1b6dcc9b3a5756ca52fff56df24592e4f4574571bc85c9ef8434282268ec877ad5d5250f37905cb9b9f330500c76a14b7ca71b6a555361ab5059b33995f983dae855a7c4fb026415a3b4fc3389f96d58d8e307451c9685184ce6c2868c3ace1ba541d1ea57a5c87c6622d0641c9950539a17c1d0970edb09ef36c82b81db75c69f55d8e608baa4dfb087b636975bbe36cbc25c7b253715a802a05dd410b9f8a64cb480d52122999499e38e181794849992967cf560af127e7bfd8fc2353ed5e312c29fab7463015cbd3691cd71a10459cddadc0ac5f2649029229c18e34ffe8904c350b631088f5e9f913a801c7b90d8c642a1692a855ed18b85772585730cd1710998ffe3139622d153c44a2664e00bbf3f3ea40ac2d1370408ac2b65e8a79b8d295090c8b8e89ca2131129dec1879527d49d2ce45268982b8b6eb609442aaf18f585e3b8a5be035c1c0dfcdc07bd4205cf6e7c73ff524c191019fc3e31b51c843fe100f12f7788462eaacc335f5d8bc7707ce56d571d5d229574b5a22b3cd7e201e090d42f560f93a74f3b7ff63d6cfd507e03b308d0280f5cb78a6085dec7800866f93467f7665dad839f1956eea8a10c64d81e2466f09374d5d21d4275372065cf6290ddf2c7685ba19f9301ac9aa1079c17d15f78356fd3b4807aab8f015fe9abc619be01ced26e0f650da4a55d24afd800b6db382953442e36e640e7c689ea9366f584e409e104a50024a5a0994fa030e5d2d869550092995030ed0c621f87c5e2318a98e466e640e7c583532a972c77c85785c42bd80232642692b81125642a57fc007b49d0c8334564e6c48ebaa04bd2461759044236eacc6d188e0c01b6ae8ab19bfcbe012ad8cf1491020a346b8f198ff8dcaf87cb4db88acbd1775e7a757b06021d58807e8dba8d0b46d4d4816447ba093ff2bcb6ab3d48ad6bcea6386509a84a6be1a2556e6787d9f1975423893b14643821d19076bff63227427e91aad539584e58812080e87575ab738cbf529321efc0312c112618d2a13489006ec6201eacc042a5150dcd57405fcbf5a84a45cb9bfc47674404f11780d37873fe197014f709c38e385cb50ae8957450c5216defc35aaf4dadab52a5ff4a242d21e01f003c70f1d41d54631d7993122d6daf6df7b4b29939432930286028b0276c8e3410b903edd63eefaa1f0d4de000f3421f799a9b90f58c725cfe5b1d8028fa063f55c46fc68d08c3fa60613712fcb0a4dbb64ced98d11c38cf931b1704b2dadfc70d288bfd1e624e1fa73b1aad987645fb54ad5cd9568f5abbb9b9b5591ae4101a54b3683db344ba1a10d35381d4cd9a1db287f785b3e2da708ceb00537c5af3543bbddab6ba7ebedd92a96d7d55410077cc805b82b2c8e89be28ee0a3a6575956d1120794c250678bd9c7ad6b26cce3eb98d6259329649f5f21d3b6459dc148ba88fed426138ebaa29ad2dac822c1f0ea34d62b18f7e501401d046d1ebf039bcd0e3f037bc0dbfe46b781a7e86a7bd0c1fc3c3f04afe8577e15bf8a067e193fc0a0ff4485e854fe167b94c6e23b3d194c9e435b21a398d1c95c764a88c862b57bfe99b5ecaf7f0499ff4571ee99d3ef7b977fa9abb3b0e5f0c9ce3a5bcd3277d0f6f7b280fe5931eca3b7dd2277dee739fcb5f3ee9933ef752de09cf9add5f054f5df754c275cbc2d0867dcf28d886e99e54e0a9b30dcf3d6d58eea9dfe96d9f5b407c2b8fe57d78dc4bf1637eca4f8f1bf3d38ff92923f14d2ee4a7c76d31b3142c9359b675cfe3759f5e9ba4b4a734383fd00b5534483826f0fb2fbc99eedb4768582b87d1f7a24a69aa83d40b368811d0eddb9a331f7888d8f54c4920c7ed2d0668cac700fb6349f9aa757fac262aeda934a862cd99c394084d26100475c153cbe64c4efaaa3b768a356744339f09446116816fba12b62370186de280c3bc249189f219d98cec9473b98cec253765a64c461e238b91bbe4a5cc256fc9b8acf45e7252d692b3e4303218f98b8c94b1e42bd94aaef25f642fbe2977918ff22d53c95ce42db216398b7cc4374929a5943bbc686a1ec7982b8f79b462e6579d291f9301045cea3ce611478d1c2543d9015e181f52dd9e145261180fc380335fcebbb156e690709f5c9c0ff361fc042fe3b58256bc80a7f6aac1caedce095ec675784ef032bee39ecbf064aa436956f7322907ab2a13a9db77cf96df248fa8a84def15db9ff8e84e6c990e7618fd71f18ba22f36ba19183777b1c8c8aef6eb7aabdd331bc771f4eaabd52d8a9a4f4e4a2bc645a27dba352b86fa7c4057f4da15bd7645af5dd16b57f4da15bdf6cdaef889548c5e18d65a6b75ec21d178aceb84c2868486b6cf7d52548bb690c774c3dba6a213d88bfc898b7e67be84aaec942e7e55fcbbf947fbc66f09f50983f86e77fc6e77fc6e77fc6e770c6b58fc6861921bfaa252babb7ba538fac296e90a594b460c9025a30468cbbb44c9964cee1217b24f35e9e1d5b64f3db35a9d024a97ec8aef0496bd36f57a815c7cb9f87ab9f87ab9386bebce2c4bafaee8337cb40c3a0b78b6654ea3591c7d7df12fe7150b39c6f48def04ced9956c59b61ea46f9423701875a9b664a8cf97254b972eb99c983b76ad8f36de27378a5b44b50fd11e4ea8cf8d72c79b26fc8d975a2e3c539cd3e48feb43c55018a5b92dc5e4b64c4b2d469291b3459ffe8b0eb1e73eab9bedb51a98339d734a39e594795b0d6aa9b3ee7a38f67a620830d0f16cdbf251059e7977ac0982d05704b198cf1021ab159db256154f7d79d1b80a45670ce7d0e593f31278c210cb4db83b1378de0ea7cb9e672cf0bcd9454b28f149e0a945248e207262c44ddedff4453c1178ea213cb56c82a7fe21f0d4f285f820f0d40f84943fb28ad628b09500759f544ae964340285b2ed8f3cbbe3c23a28dacb83a20930f34d792a524a19aef4501735551b7937fe86a63466db437dd447fd91d091d4fe514eab3faa1d79ff477f6484a706bb7842bd284a034f2d75512a1d9a15cea9c1f1f1bc0e87cdcd8d0d8e1b3835397845a3a3da49f18419fc999e2f87a73bd66d591dd5e500d000a600364a42006cd0769efeae7771b5370b9893901d719fcd5c89bb144b4bd949d2a805974568688372a66a545609232a4b9096db6fc952fc162d352da316d153007c243ca5f698b6d4dd95dad6a12db53f1245c253cfaf5b92328bc7f26d79f359a5bc6dafcadbac553a6b76d6b6b7655e90fa58aab71c903ca8c6326bd5e7d30d9e4f98a96e3aee396b5e3d1998d6599a4bd3b4169ae3bcebb8015e8e7b65715c15bf0e815815636154d1007026fcb6b7ab4ecd1603f4b500b6b3ed3d18b8563bdb9e9cdfa7aa2d55eeca952b57ac58b1622587019f870c6409e053a194522a6ea9a5eeaed4b6fa53792ab20e0a7052aff676de37038629150daeb9617383032767a5b3c3937f86abcd32cf8ece2a07a727c7d7ec0d8783e3c6e6464d0e9d6b4eabc134aa9466f14cda7d4a85e07dda54cfb05a3bb46e7c9af9ba968e70ef00c255ef4cdd9a358fa6c3b5da528a9f988200677f62cede018425ce16e58f1e1c2d4c792e3e8b77fa293fe5738fc56bf1653c16dff44eefe58f3e29dbb0ce5cd19c3d6d18674f25ec79386ef6d4366cb3a70ddfd8d3866bf6b461bca70dd3ec69c3aa3db30da7f6b4e1704f1b06f7b4e1993d6df8dbd386bd3d6db8dbd386ef9e1abc09718438614eb80a75747654aeedac75677b6b0bf862e0a925a5944a6fbe80830ea074d1219ffbdc27b9b5d682f46d3e6aadb7c94ff167f8bcdce5ee49de8c37e3934039a9d7cbc54ff165c4a68724a594524a29a757153ef74d98f608424a2973957adf5df283474aa7ba23a5938e94755573a4bc16a7d312472793524ae9347df43236ef64efbd01ced48460586b18a7c2d40352cb9e1454291cee184c614513506ab0828914b964658a24be80b21205ccd218162a65b1bc22a85563db45896dadb55abf3cd6ca4f621ece96465d9874b80f5c5be6daf27a8dd1d77ccd25265c8c706aa1ceed9a3594d5a53276ad1eb83c3f51fd526f09b775b634ea8264096acb5c6e2be16645282b5af0470ff563b8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdbeab5d6ea135cad74adb5565b2bd296527f4afc971e243d487a90f420111fcc8379300fa647851e15c8d460b4e589d11625a3bd92917801fce48cb9206ed90a57db326f95b58b2788a7e51642673f9fc641de4f121f3f9e0ef27ebe6f8605c2f7e903f67769cd2d40e99c3ddc9b781a943d2d606984a36d99a7b471419b52a528361542673f9e963e83bc1f4f4bf9a389a765ae3b7959b2116457772d79efedc008f5b4cd9629217536132247a7b287940b502d9354169946a8aed56b4ba666cb5900a58bc886a087449db69d9ef2654db0a1651501b5d5e007e4dbae7f4296fc991276b50d5bf702a9d9ce53414a877a48aa3504d43b52c04464d4a725d33ef273e4876a4dc4d3200b08d4d861a1960e9b81a98189c8a86dc4f7d5f2474a033a2dc39e9e101c09e5cf0c4bb6da1485edba6e4f83924a293da5fa5a21d0ed69707af75e07a9a78d78fbb2889c3a40ad3d02a5fb1055a2ab2bab48952f2be46b4d09b39f089b7e43828888f8d81d18f1ede9d73ef2f36950a680b8fe58409f4e4244c4c736e206dc7b646f190a9e92c0664a29a95579ad10e8ee9c82136aeda80d2b510874775df5d6140225106b82dd74ea395bac22bac8b51648dd7742994f4f40f3697ea9cd1f8268993c7a2c58d2da2a32f7947a4ae0d1232cf50924c00c8002e0d85575d8d66575dacbf1f5cc3c98439ed44e8ed3a82490f397d411060080220023170000100c0c894462490ac349168aed0314800d4c6c2e6e423e2a8d04229138300e8502a1501084000c8300088241180392248773d4d4019d1820bc7f92341bec6ed4e468eb0cbc22fac7144473179cef102c1b346a6b4963df0e49e9877cf5a4e5046ed62ec75442ae30d98ef32cf3dbb444bff1207aa94fdba0aced08fa6e76654e73bb4e531288a50840999db2c70ccd6c69efea8bf1b8a391fe892c35d34d0c8380a7b05ee6f8c138698efd77647ae375e6988c5614c68b3b88d5509a22e6e029017bb0d0913956f14ecfce28dc8b3fd5ee9cc9090c1de38a51df355431d1e1665a28d3266248d32c27884deb3745e8f809504d4b1c48f48e81ce0117a2c29b7d9bf768a63a31b6e47fcb82e11c5ec34c760e4f670e019e3700a8f39c3a514a4e99a465e4b4c8d20653f071029f94d22668a21de6be6c354ace4117fa5b84014e432df058f6dfa78fa4b733a3636f2d6d7beaa9901dbd6e54bddf2ec4c61bd39e5728f6021eaa4ceda39f605409d370e51fd5cae917f5e291da63cd72430c967658fad322119636567d63252646c38957041ef68c1f0c32b78026c8ea8cc26ed4182c9535b39a1c2ea6dc951efd3a065bc0477f063c18d6f03b86ec5fca66c90f0aaf6d49c22e96b49ecc520ebecd02b961b2ee8284877da5c5da130eeeea9d4c347ddbfb98d4f4e96c497f1cc8cbf4293d615d3292af07a4a82b07edfd6ffaf97fd75c3311914249e4d03670682c235c2b8ff39e8657ba50c324075758ec9221d1638c3500ff87997e20b9eee3945500dac0f88a6d4de3bca6b470afcef9f989081d80c4b4bf864898e17dc9e952b9081ee47dc9e952b9080e8cf601721a057a100f08ba370f9141c93f90d4a570912dc8ff90d4a570911decf6a7d2b588f72a44a49ea48152bf084ee465e48052bf0826ca9451a3d4f14424a652cf8840e467248152bec826b2392315d0cd8a37e1e46912c3a68a23e190a1a10644cf3b92d3a7f2111c68fbad8ab4d9bc244ed19dc0ed4cc48c130dea4d8a9038d76fce8e167e3d9c86b7b346b60309d88d67becd4431f423e101c9895b33fcbb4df58ee7a28c133e2806024b5a81d8d37553a84d9c2c2ae0e6de05ad1e580e8801fd020f57652f0ca48ca30523f18ddd0d94aeb5c601e460986aa2fadff45ec22a39a4124f43aef86904fe805d2742397b0919908cb49ec8c362b8544d8a8697ceede6fd02de87c5c677e3223e04f8c103d1862d9b61fb858e696db89ee8b969feebd80ad9b0431ab67d78fea541251d2f76cdc0c40ffc2c60338c8a4a8a0a792d612fc4adc7848eceb6f71d3e9f0d33c8e893b228695811f9245d8c38700f6037e2c6cbe49ad06024627aa0de19fc791a7f720fadb7311167d62e1a3721aacc385b84567b2f474c7e1cb5c98eb4a0dab6e67381566302745e9048ca81b3b28092b52e25f4011a6e8bde09288073d210930791cfc1af579ff2136776fcc3b6e7d0706d1fbfa933ed708cf27e5cf9ad7d610b8c9be9913da8d5795550220c244e1ff117e42377c8c2c505e30383b629212c494f4b129347e82c78fcef506d591e4675d0d22490c6c38d388fb861e4870903ac220296aa674f7bd92f39b6b520b76e45d2830d8282ed636bb9f9f1b51a11d3fb9b458636bf604c36f910e3fa427c0eebf2cc352e9fb700c92470439559803c79f7df59752bc1d199c678ea65a2c66ab916b9c50acc605d0264bf70c8e58dc7071c0f1d386a84384e6d72e59a6dce907f1bfb32cf4bc503c18fedb29f7a7eab1b706d1781ce0754ecda84e9b5aaebbf56621d4040171538516f8e4400bd179eddbf93c4149e93cab61ed0d557164b21dc19e5b1b2bb87c2c8277b22b5656b7e4ad1b9a4e5852cc9ba669358a556a1b05984bcbd9eb3514815f5641bd857bd027b6bad268f20b2d012371ae85a14f6b7d445bc635ee408b0416452de46a9da6774c7e195d8b8310a56fd786c55b2cf8d5de8763444b500ef608b6fe5f80563d5ffbdf28eea32ac69a45e114775a900618346013055ac024a9f5fd001e35b4f0b042482370f855e50425be08d8ba4727a70ed787a2126108ccdf056773db64cfc4227c84d0fdc5dd916e7dc6d23a17b16e8559cbc04a074e39551879f15c3e244625812790b8e1578c118424966e3c72eab688ad3f3852933616ed2b0ae7b733eb9e91e6c8b38990dd966739540dbeb3d3ba2baed3e8c0946657d708a4e65fca8222c6f73bdccf02d89948fdcc8ab8be4f25721c3a910030940c20b8037b8a73375fc1c11cffd264b7e7ad96c7530697f65a3fca57eeaf0f09725aee017268dca6116988322011d38b21da810d166085e1c7179fa78cb767ebe743f53d29b684eac50cfc52234e56de0ddd8d25ab6531ae927103946b46303208f4fb26044d1a31e5846edee4d1ccf3d39073a4907f6c7c0643bef169e33c95b33bebf28a49603a2d605c185ba70a24d9163e12a397b5164ecb0cacfb415081b26259e03843ab80556a03ce30fc4e8e53de5843dcc93ca476b06df33af0a4287aaf4ec8b2263212a39f7a250c8908a4eb45e142254e513ad0b1d0977d1498b226384557ea68d2fd003fcfe6060964c55f3bca3e462fea0ea46d7bfa4bba878ddc5834f3591e9eed1991dddeaf465bc722f3bb297aab34eae834a5361968a74df0055c280580e7b0a2ddad25f43414dd80cd141ad9217d0d8f9319dbb8baa549e55fae7dde6f9b22321d0810fea73b26ae2ee743c6efadc4ab38fd28d07c9e41f3b25ab2d5a737358b0d076c9d7a1eda36fa25bbb6126ce117e845b728c631fbd02878c16e5b63eb37d247b66163a37ad4286b59bd04cff93a0e06ceafc30bcf04773df482875c195ac8004cf5f733cd57755f8fdef4246183458daba369e74d70d898dda81893222c472bb007b54971bca53e907392aeda72c65def250033fc67b020a1c295e950ed748d0b1cb807a108d30ed42570bf1c8f040b4eb8e3b5a4637f864cbd0a8517b8f5b09b34f54a2efcac01f34670f460ff242b6ddc5624f8c8b7d80acad18597896c6454e5233e424e21807b47470b674b96d8ad1045e05361ba256e3190abf8edfdcb1e8076ee2e654d838f5e342275f33c18f8bb881f2ec0b957bf775d7a37f0b338cdd4ad07b7f53203b47bdb9386c04986c9b40a12d1324ac8c1b0b1ac5bc37eaec188f5744d9ed50f9179fef65b645abd05be8842f67a37e867f83be94619ee0f549a93124def038509e31b246248f2ae4e632a52ac306dd5633576190aaa4680b210b1116059c3ab078a7abb2ea9f91c063db22bfb0e110b790a478a9f0be585a6a8dd150b9d65a597d7da8d6529c99e4df2f1bdf578d80c06264d1f442ee0b07ce7ec2daf6732560f48d884bab129e2aa0514e33740935ac9c46655e5eccf2b4bd67a17b46254f62b4434430148debe518764fdd5765946c1be97293ea8f75683ee875cc6337178753e74fd9dfaec5d1250b51e890a1ac629f6ae771fcbc27e77e4445a33339cca89495059d0b61c3cc3793da7a36ec3f704e8f7824885127d3a2a2df7ab4094b8a14662e909226ee4f2909019e91b867a48f4052ece88dbd679d89098d25d9ac2626d4e9248448f3e9d1aa330c19f72d7cc39d53a983d08fec86037057d6471bb74026b6fdd4054385b6f228a3039df025139c8bd347c93de3d3e312e1448debf45891ba81470e6db6a7c8b1183d56456078ed988cd1b2fd0cd9afcec73a538f75faa019ba2272ae9c2fcadb7a383137e2e4d43bf00e165319d16fdf596cba210eef970d5dd4caaac2371c128c0f56d9b537866edd806c8d9f90d5cc82d99286214f4d9246c1d8b435490eea709613c8d94491a6005cb70a813c1a289cdc3ed170dfd2fcd2340293a58f5474b25d479a1b5737b46eedef0fc49bc6b3fdfcd7a0b1ef9c89d8cc6faa0fa17191408fdeecf0e455ab8869f49fa3fe0beef772f2fb29523baf4e06998d2b51557e6e3a57cf4a0ee4bec736798d162f983b404ca6fa6d6a6f105e6cbed68acdb3e9e6f867c57deb8633f19b4490ed88382d597bd70f43c766ceb4f4f7dcd1cbbfdf827961c104b68751e5a921570a1b1077a45b3ca5ae03e5ec5cc754d55c75f11f8fffb3b00d50aa8a55df096eefd2d1d0e694c22c599f3bf7ca90992d02787623cdc79e6724ba30b260ebb7fbaa3d15fbe7a64959db086719ab9647d5e56c6f51a1e36851ec46cdb29b07203e2148a95a3565af363332568600a7f8522ab84083713700cb8b72ca23360b73d841c802af089ae19186ccff07e8d626878d063e2f6f239529391eba7e5ebd76a3be5522d2a91d048d01fa4d1be9c998585da96b057b3708808d423713adfae7545fd98e683c9eba592a7c70ee83e01dc8e738b6301422ca432ae6416a0019f76da99232cc8df8732d94323295fb1f189d71bdd8f51a11f3072589f7dd4b88b5cb2bf954b618fac34985971f727b1dc9ed84b7aa427d95179b69e5294a8d13d94efb80acbfb94bca740c682bc6077dc63994ef7bc8abcf8676cd4cbd6865d31a03c90415b3a97c670e5f496f3f7d61ac72f91bb1bdde018441a16e6ce806a953e3525bd86e1001d18878850a2574dacab16ca3cdfe3a2c7b71e8171f72a4aaa4106dbd68cbb6ea959a22b033fa7ecab71e5e5a301c1ebe46d77df5b52af3c0d0f4130963d73c858d7db7a2ad1e8954a2a64e7e05506c914a70059675a8f5ac6c4c77c6a9106c567a6d4965eb7ca1b1c3819dfd17ae99a7006a150f6484573ae86ed39f1ebce0c7b581071faa46327ca88d829e6b7cd48e63a4173667fdcae0418b666c322d00b0a53d1b50b44c55c0507ef1f2020e1d79aab28d2a67d6a371848205d8f16e07e4fcfe066b9778c650eafe419f54ed66f5f1e85f34ff52c10c23a18b9a13c7d52dca76dcd1a91fcf3c93ba411110378370da21e13eca4689d984f0799eb13ac3550c1ca06e3c9fc1c1d9aa4740e6f5f3c27c7f4c52dc0628ac7738dea59f0d05f2614514cb5c1d13262244c15ca9da425c5ebe8846e479752443ad1c6d40c914baaa5cbde917a0d415eed9c2812955d55705ea698b04c37751ddc0404f77ff317c05dc7255374960a3f53dd18d59df9f5c79f60baefb95f0addc3a50adbae166b649c2b6107d4df808514d51b8248b55af7950dad54caa6a9ea13f14c8ad504ef467476a701cb33e4d21c4acbce840b2bb5259a09d8d9e1cf8369d44c772d109ad921c516e15c66ec46ea7d44e5d48bf539f5920d76c875ab86bf0f5bdad30f518509a0021fdda052fc2cbf2aa30093d39d39c5b998706bea9b870d887127c976dda05f6b53a6ebcb1c3a5dfb5fd26cbeb3bdace7764524be44d0948af537986a4159c23877f89a8c602f70b3e51519a4fe1dfc9e8281bce4400e41fbc90541e423d4262f7a9e0bc7b5a56ab6d502fb9bb764b03eaae3454827791fb842cee332bd4468ae07198da0850577dde6ab8ebc523751c9007b853990a74a7c0704aa111eff46c5e3924122a6a999f90c25e75c9857c33af1407bb87506357eef2e64a691b7d53df2b379f71318fd1d3ec3c674c27f55486328cd84f44e0c158b059f6130121fd8db2b0734db1aaa8e11fede24a947d049e9557c28696352dda02210643b4238d8ad033da3646abf52a7578dc081c7995467d28e7b81aa641976acdbf4d11172764190e18a2f68f6fe6cdc75c9a1985dbb784533efb5646a203f38906491dd7c700b7359fa4b3bc5252a61e146c6524d547be98f814ad41859a8f91873b093ed0c768ca83127497d907b06dc0d9c3f3fdb1a09d0ab68f18353459073c0c295a97434f7fcc6d979eb3c475200055dcba815fd711ecec47705ebeb225aa54129baf884cf6951220fd4575f86442a80a9fe36b33bb8052fea509adc6e61ce52468fe6390ecca478aa4ad6ececa92e32f1d9a9c59c5b0afa5bc44d73b784cc0b3552b99994a66f9ee9ebc370bb44507e20664f26cd521bf78b5d536e0c0bff2bb8ab1f10236a44e839ca00d4ae6ea3e3f9cb824ce1168dbc0d034fe634b2ac859f5f0d8692c539d1607a9cbb8131bb7e8930c4570d597081cddce2843ccaf08ad325d523887fbc750e47826eab8c928b6638649511aa37d6b952ece5bd40f1e9807816aeb5f51cb25e18c8b5bc826b6cc0bec5ac6f7ae4462e28504ccbc52041b33f8f49519b942ce4a1133d395e0e5e07b2b5297633c5576d415e56222d19497e5e0f4a5c7d4e4f285d9a4de36288ed251349345b869a8de895cfb726bd6f5c5e414256fd967502f10ba35cb19f910ff4b9baaf1fdb6f801c29433e06b50a64b54468897754442c4ae0d93b9a6ccf81a2b61052fd0a34838538de31a0180ab1095a009e1be8fef5f025e6598dfc98c6c5351d0708d2d7a1816889cce5a42ea4b6e0bc54ca6d81f40363f23c8d666143d799e37538c047c816b15751c727356460f9b1c98efed73afaf9a08ebec32e020c3b1e0445bfb3eaf23ff5ded1d1161c81e7350eb8741e2b18688667b9c21ecbd748b125ae3abddc9fc341ecba33176201a42d4f8d1a82d3e9c8adc6126e154df472e20c8a48c20c34b2cf04b321530483c14043df609d6428292a233b8c76d11487278225ea80c015acae14184a53983af4583ce084068f889193c728ab9626972234c7a85d490814621fe901f3325a0354f48d2d6df38499b4f43638b753f0261c7302e5e5a14f2866258fda12200f13bebb09a19cc1710d19f5f5e5a8ddd27dbb16c869f7988c9c387fd97178643992f72cc498480a33c6f1b7997e986046773cdb5b2fe43cd808a97967172dccffd69e2e82cbd1b66ff591bb3de9c56e4837e1aef6f94a39ff9ed0b26690458295fefc71369eb1512ba338d09391636b4ab7623c6821f35f3fd9ec576e7b12adb8003ccfa6505e1918ccd84d11a007512dcf15bd23b6808cfbad7942deb278578902ecf1acd89e808dff330fd405a8e24f45853b7b4817abe48cc7fe4f1440ecc3e5d87189b905837f901bfb9ad6606198f9ab4428e0f54c4cb33a59307a6a0f0f70ae0a0e01477eaba96ba2fde62b655cb5fd1362041910964a2e3ac87fe7921367d385fe716ab49f3dfaf242b2c265ffc9b0289c3ee9c2b6b6a9d6b7343190caf6c7a2d3c8aa5d6f5cb186195414f889362e396f032a653fa771503444c66f7fca1fd6021b1729365dc1e27591ab0a7c58ddd7a009011bac8397e1567c6683f82a60c5979f69916c4c28be511453b940e88c44643e88a6caabc6725103f54389ecdbec3c3aa63b8c4a4394057c5391c180e18de80201bb0213129dcf3d2e5051fbb46e8969282418c36846564c9c85080c80aedecc4ba8944821efd0610a92c4678b12421a938709354fb6451a58f1dd3951080ec7224814125e6dee995e7bc6dd20470a24b10188d7e03adc70974f2ba455fb57ba87dfd22efc080e4e5f155297e89f03bdf639e024416b210de5f90e04ff0664dbf2221c68c95f8e7eae3042f1625338228f9d83a3fff56945a0dc4b6c207546d236aed277143a78e3d9da9a5ffd981bb410355ca2d2bca7962c55ef9951bf784fc86cf8174e79f2005bbb60b59876cad729b7cec4540da77a9c5b5277b1e38bc34c67ccf8670e1e3c52d7c7201caa28fb54ccda360c97a4c4798d3e95bcc70b13f2788bbbe5fa33b25499636ae98c87445a7d017492309dd66c13d4ed765133671d5cc432fbba7e95a51aeb5e8b4df5a8a01b787f2b6698280a14f58a82ee785589ee180a05f17228d6f6568012c5992cdc309f503571c934b6003c1878b99c9b1825727a2cb5313e8b355fc1391171975e3a47faab073d45a4cd9e6fd44f8404df5730942954270c2c35bd5d0c50cf6f8c5531019bb5111e6b23866164dc872a3b068e3e210f7d6396d4eb1f0e09ae38a529be6f0ad5cf5cf28a16d110335dc1f88b2dd04c5f302c235436f6bbed544acd989b0b11de2317a112e89f82ca8993a52651fa601ff57f8243d9820f296e82174e64312171ecc0725726b6a1adb532a51f4d05da61a150c1294ba076fe9f93af07775621d96d4c214d5a0cc5f0094f048e7175286f155a2faa8e7eae2ac7dbface32fe4d9f65c8a97e59b364cb02980151ae2e3a4235a189e72ee25f0caa941fe169d370243e961ba66484d7dc6c98c10db039f3a4fdc4ceaaba9fb0ee4174b794f6b8039e730db1c3a1dc2ed7d740c54eeb65825ce528953abc7bc96ce7884e4d5f81f7f7ed51c8dc4fc5ae1f41eaa45b9117998533aada565516d7e33c48d14c68b7399dd2147623b1de6306bb88e4e81ad7f3756848aad3488bd68ee2efe62c7417d5c9d6d6b83c2938c3daa8c0f931716ac2af32f280caa26331cc0e59963460128143b06a88fb10fff9ae4008618f38fc696d90014949f5b0d833ea05c1d8f9f142ec6d5a4346523b5e81c47d9f709c6460cec452740ead057efef15acbddabd6697a00a3e58c688f805ab0194b21a037a7e691e8d54700a9d237303fbd4bc313da7cf72a5a340bc86a0e8e8986bc591da69c2914fddf6d31c6e5740389c8f42e92bf7b77e6528c68b585116c61044cddbe90469bb0491a1e98472aa992b40f3e47da4130ecf588f17ffcf53f5e814d02c179d1cee009bdf6c26703927919b97a30bdefd1dd03620509ca6f19dda0f9993a2a3b5aa0041923c4c0ca2ddee43fcf475db921adb9591d9383458134f35e7375fc8707111854b4478c565d4f1a2b56adf818df04db5bb8fe73a8a23c54d6fb07487b86138894fc8c3aeaade0d5256bfa498bda0d80e99b7f133c3202411855f9271b6d20bde5439c61dc2570e7ae2533d50fe8f0b57d7271c05034012d735e00a30379fe5d476a4d7f4948b2351d6c02e50cc45d4b5566d66b7b7d9157fd582d99fb0af30556d0d40a000502752a42cc82f7a9bd8baff369c13f467e1c252a184d8eeddca334304656e41b33aaf105b61abd679c5bb458c53fe912c749130c5b4aa47ab9e1ea5f6aea6ee201a71e0b845a8da0a619c51e1cc3733ae625a756bd922d9c5950b1cb12374832192774b3432bd204bd1b6d41c21c5066d2b395224ad0aff6d899ba6b7aa031ef6a6a98536f22f2a9b9e3fec98e0272431db0e40db02a5f0a4250cea9b04aff057032e44327503ec23fd652c6a13b6647f96fbc76ead66b53b1fc71f8fa04197663eb00c59b72fccb09421b819adaaf86ed259401ff3ebd3c789cf70b710d800649a00e9160a3b0f1732e3449f394e1af76dc4e67fd5ecb27e3a96c2ca94516324e118b85bb1f29ca4acd3f0cc4646dfb08b7543ee769382c879e8e05f91bb1d32272c209da0f4c665a0ff13c5ddaeb956a6a7a44ac95544e04549369468a9c7491689d3b75b983fe8a3014383de67e71e31ec4aab86ac24840c25e2f135080349454f58541f389bff6c5e1a582a8d3558408fb9e990118d66687827b813d90fdda00a22ec3cedcdc2670b4f446e2302f8f957abc132b098515e9d601f0ebd2e011e1292851c1f272fcb4e05ddb019a231fdd6ee567e16ec64f601aeb6aa5a05dcd8c5dc342a0a16b2a170697ce83a664f4d8b312d1d9059ea98f72c3df5b05589e794a7500eff356a494c5d2036ef477168da76593d2e7cc6af603d9792e583d7468cb4983005366205fd498a8147329af01493f6cdbb02c76142e64f24c088296ce312ee6883301c9318c7f764b2c47ac283f0069ad24ab854e54834597546088beef24e73fc3d5041cd5215443e4825c0739732504c270c2df9cfe15d271880ed4b51e272534fb04d8680553e56f889f62f2b525a44f125ec42b232b840b7601e2ad7fb55deb52dadb2336c3318fca8c37a1d10c13bea8a90597bd9e2c8d805680daa9aa979fdaf8956a88e640807aec09c158ee68f4335a8d15575ddf9dc0b5982c1551e85f187643c81f070d737cdfe782e4e49f260af4b57494bc69a8fed6f0d43df62f335c1971ad747a5b301b84ce96c13a91f0f4f36c04028008d4d30f64e1fbc389082f74f11bf6a2b1dfad3eab21f978e9b2552dd6884a53aea66b40c3326dd5ccbce499f834d3150ac35428a4a80e08201fa9a36e90d14d3d012d33456ae98712183dddd30174d563139ae5838cf5a5ac45f42284ccbdd96557f862d67c1e93c4decef0b23595d3944613b5e412186265dccd398af5c44ebe47d2539b635a3d08ebd0175ec11da186553bddcc6895457f241e804df37dffc06e45881d30842d6d2b485949f762ec5eff79b2a6badd88a267c90007da30635958661d1717296901d8e45354ec6551baa43c69bd148fae2caa5484c9c80968354ae50427d147c178923958442c387f889cabf9605e7f929716f622ed44df41b65263a279df7c9ec8f82a98332d30b1c0a9012877e0c7cdccdce2a8c7c85537f561b7c26f03b5916250a05d9047cfbe38c9f6fd609f7fda275c29bdbe7a673e6d4000c88b8a2a8f2fb0093aec388e756a62df94de7fb1734bd1d9aa9e75c7989013761df397c199c3e597bf2f9e3d9c3ba19cd0cf96c249c745ebc991ee8d7a7136a97cbb4947039f2a84775132fe637cf80e074afd34bc6d35b782fc7c140cf932ce0e1dce74ccf53edbd582dacf14036d0bf65a33516aee24726ffee926bf0259886afd00220bc1ae529855a376b4868d358b9067d9c4b198b5bac00cabe5fbaf3bccdb3986910be7636b358f4d1ba0feb01506f7da65f56f1ac5582ae5deaae564c10b0b8db8e001c5375884a42fa72f13b11724781632cfa09008400fb4e92f8b04181a4c65502c635a0b024a030b0ac18ebb8d50a9d06e30af50e417227e27f4badfc97d4fc647ba4c2f0a56345d1f84ee42a9c27db211ba6a5e01c26e119be4b29c293b299f6b047c0de4a89defbae2d2be11338c10d620ae5bec05cd87084dc469a6f667d223612f13c6c595bc7f99bc706d1c2c4a2049e1f6b41e6e8e0f3c5f04891cd4d6ce791a2ed8112457e640e66d49afe1da3e2ea92e385dd901688304bd58c73cc13f203c1cd4b11330d246a1258bcf8150586d185f99b32dcf51032cc479700dcc9c9043b934551baabd13c90a20656d4194924a6cd3d17f5d3d9f468afe61bbe100cc0598369580ea261194f8c573b8f225bb86028bc01c495128433b18ed3280bedbe74ad158b791df6f7da17418a4a949c782b153e057160cca019f9cead0754387f95cc9e5eb660691f0814c2f68de3e313fe99abd7e6399529b5e9c6742344a79013a22bf853587cdd3c298f23ac4ec4082875a9822401302595bdba98d002078ffd9c74b8c5ff898fadf85b27f37b1d7d341a6507c5de39a88b43644ad777945754ee5d220e8e4ddb24f3c2badb480a1054c013db800f052788ab90986fce4f564419246b070c668fdee3411c25a6cc48d640cac702eaed6c042bad74d97903a5b51d482b4ac2bd52ad7797a11b6d5f368ea50eacb07a3255633a6aefa1435acc1d900a28f23ca2aee3593cc0902687a65f384fa5ffd8be9ed04dee65a11d344b3f70c4291ca9c2cca59e2a8702bef49e23c7788fcd5570c550ec168522d919217e7dad8819b7449ad622cbd528af7e69fc2ead320b04800382dff1e24b908d9689c6fa265057a5d6e79a6306253dcbba3b08e84d56e71910d5d51876330854742420edea075d98643d7a8097a92fa751488a265c9d5094e9e27bf1fa87f5efe028a2329615424174cc8ce365c995fe122c84102f8250ccfdd6bfafb7c1c2e9d2162c697cd73de0bbcdbf25844a3d04c565827c7a9e2923a85c2b81fe1316030002abb831cf812a84ba954f694e2cc2927830e998353a2471e3b9c1759459f3bc48a3bdaa765d58a1f0d7336d1f8cd0a0d3c0c8fc03f92260e001c90129f67c1401b09a997ad410abb272014b297a48010f38dfedb5a739548057fa495a471e1d092812f541357290397531bfcdbc45f2bb8710f42a91ce61c557e91a80bf1c2fe65023f9777e03b761a746734ff1d356e162b40cb8a4b0a3a7cd64640b084398b9de83011ebb0b6e96d05e0a091f0b5c301c0ec6bc0ccddd78e0773a6dc9cc6f60603fc1f900f647a78e22810bfacdf901959288230fe828d7b146a25dc3a109b7a1c855d50cb3566c09d31ececb440708ea404c994d9aa45709aa5c7e1acd161d89ca39c91f601a80ae49aca95b8ce623512430222d2e9033ced9a0256c2c09442bb0231cf6368978d890c79a8a2579188d7e0d20f3891a6094a46d8533368d4a82d4e4cc02fad931645d408a59361db0a263280ba7b6540baff216efa5a6f3a30807de336ba040127337f02e1b853335633ac60e8a512bbbff18acac3b5c37d51f40736cdbff6db81503c087c2c69a4b38565c5bd99b05c7d3602822126a6477db724b29a594324606ba05510662a05075f3c4b54b3689c93239c19d80abc29d809b5242b517675c126e15e29cf3ae8f3381d3123784d3f2a3991a742bbc7e4eb229913d3540214cd4f5fbcaa35edfcca0871f86b81fffc4ef6b60bf5affceaa48c13a58414c825703f54b9a9b34b90dc3300c3fc4781c00152054fe896b7087c25e5686e9b2346957bfb634695991c04480b03bfba70676671f8742982c46087ece3b14c31275db9da6ba34658f4f3cce15638b71a5f9b15873117c522b5eef7af029323831f6b73bac35d5e013cddbd5b6697a1a2703b71f5a1a3600179c5e75b326e19af0fd269799e93213b88b5fa6571b7010a34958cf950cdf9224076858cf5a091f638cf10933a8e34a7280d7c31224eb10efe2aeb8f8e2e6f86934d74a5a49d9d3487317d47369029b20ba2ce4e591ee01299090e932a55f46e28db342aa4391bb910ec55b07c3b024b2bc1e7e0a9a410f4ff430b43bf069fe3e2351e46e941fef306ffb7de2ec392e0837818082848e78bdf65bb6c96d77e1d34871b43f61b89fe89a1b97a2a5595c026eea681c001520558e46dc146eef5bfdbadc78caad2c904037c5df75d2b5fe4f97e62d8a5e5b8a29f2c2ad8316575a4e888475098306478cd24ff6928014d58d1d4fd8f0d88a7212a58b0c1a232db60809f122ca09170a51aeb48471c116060b4f579494215036bc62c850438402634400e1479616624a0803650b7324a7e718a2eca8a46648520b213300098d8c39f2420d5b5d9e7c8d85a1b1f3254a009d627d5de9399d627d5d8d4b1ec7630802f4c7720aa050aaebe708b6ddf15c721dd452f1527d15b9476a5f3cc0b0f95ac115f585e3c3218c1ac1e01082a1a1df748a05e6fcfab4feae3f423921755d412104a5215ea817196a90a185075ab08cf1fd8108049828b1c8bd6096004002dd32febdf9de108633a3df177720eed5e5bd608ef47bc158f57befbd392a076d9d90b9eb1607baa54cb7bf75405758bcae3cc01fa69bdce0e32dd4ebebacb3e07fe09bd8087cb0b46306c512dbc73344800b13a6242c6656c8d8a235e9d102142952aec068a08c182e2af4bebe3411c95d96386181218407151c388ae85312583774617999a95fe6e2c53470e0a2238727685c442932c71c15153118585a78b2a528db10f5e51abac9f3204a8948982c3cc08ace103c5db171b2e5d4c3ab899713af2441d545155c5395fb658e5be2b8532caf10745da4638c31d638b0cef86bc6e245a4dbc7f20a72bf64ad1ed6778218ccd070440b9424438accb17791e0f5d4f3ee790593134a08a2e584258433451f72dc2e1db419e3f8187f59c3e39544b49544a9298411c26008d5805a71654312293abcbabc0420735142448798172914a858116e3ce98232616bab0a4e94714b8e1d292f8034d1e89185890a241d3868e4f0a42442992f58b52e156eb296a0dd65c21eebed2f493afede7e5f56e818638c77d01d22bab7fc6204c607a25c7ea1728163ef9d4eb1be78f0a5452fd380c59584e5a5e98877e949bfb13fe997ab855bce92df0cca4fb5f4fb78df7b332ec72575f5965c4ebab563a7585c413a4ea7585c4e3d29a43a5db5d61a07d77aef764f31a8194245cd12225a49890c295b8044d1b155a49f6e2c5d6b9d4b119783d38e7b8f14b7cda5b28017171a70fc176bcb4cb79689959292254fe8531d256fce546bfa7d545f7d5f139192b5538a5f246dd60ac254b58f0ddc04ea95005402da2b26753d26706c77660ff5e226ef22a5b5d4f57c0f198a6956b51eb092597d20bb322bfb815eb73e48b8d9c3f1d554dd3e89ab4c878f0a3e29f888f109e353e553e5f3e593c407cc078c0fd89eddebe3c557f029e3d3a6de7befbdf7de7befbd63dcbc4e216cadb55a1f3357bc4edbf6ac8751a4da537cc4f89491f5424a29a594524a29a594524a29a594524a29a5949234289a904ebf3605d5e973d2b4463a7d1bd50dbeb9b9b6de581f76473fcccc9a3033d68545023bafa09c57537a56d5a9a9f2a84eb5a942551fbd32d9adaf4eff76d12d33601808f16a3785e8555058adbd17e35cedbd18e7ac71d6fafbf606655826bbb6cabe0d823259188ab3d9b575260b457136a3d16a335aadc6b9cd7683837170aead383738b75b4e8e8ecece88c7f1da3ada6e70706eb79c1c9d1dbcb3736dddc9a91fc218bf3d27ce39e78b31bef862b07783c0c0af152d771162eba244a8b6953fe8f1bac9eb9a7f9bd2202b4dfd3d39d72d28e196954bb38c5b2accb64e4cbd8c1f0f801f2330b2811fda48da8324af6fcbd26d2f42b995a9ca227214b91e3e7f3d9bc0cddb6c0f92e6d84110acdd6eb79b8ececd83b4bf791b6992e8b6d7b308806feedb9bfc763b1a8fc6dbed76dbd54a93c4ee569a24fa0dd1b5e9fc0d69de3cad1c800b6e7b3e019db795463a5f2b8dc4b71d8d4763d7991d8d5d1cc5d90e00c671676767e7b673cbc9c9c9c1d18065c60c969913ab8c901a698ebdf637d224d16fb7ce7fb4d99e3fcf535b9adcee744a1c92e775746ca599f3b7c7791e1c5e9a620e691e8dfde6c5a7ddc6dbedf9df663f0a91186b3d702d70135713f769c913829ac0bd529aeda7bbbd38eb3177bb5013712c7053a4d9d9157198651abc590fb7bcf17ac3dd1439ee19b03b91f384b85fe2b2b84c0b4e08b724cd17a03d63c59f6dad96841c06d673b628be6e649f05daebb5d66a06751125adb5d65a6fb64fc3be7aa3703370fb9a2c9ff0ef4dbe69efd3a5482b19bec513d88ef9beefe3f1783a0aecce9afbc97fe2e183cf49d1ee6a95f6d56a8ee62edcdf751847f87d1fe500edea46dfefff489376f5ef432bd22edaeca592be2807a80709d5382995f3361cf29fb86ce7ed07dd219dbfbd0de7addd7d6fff86c43d76f7edbedf2177f6e77b1df286a45db60bba4337dbe7bc8d1c89eceefb1af9c1eebea7cd4891bc0003dae1fb1c72db9f0fc4f7b6cb3ede8dcd59f0fe28ed9fb8ecc1a791d8eeea177e2f86bc8f3d9491e63edaa5f83607a5f4ea2539afa0945e9db310100478bc5da308afdfb350787677f5429d52f55ee8ed1342faf404eee67eb3ac65c813a2e29edfb46fd6bfafa287254f08f1c30f4b13f7f088d7c557d1c33d9bfd9e95a018ea50872525cdefb70f84dd85187fb03b9a6bb7165b4bdaae6256b118a5988876a0b79c33bf406775a4eb398b1b638c69883b79fd6c0201d9dda8690092e2f5f18f8f6915bdc2a2571de342b0b3204025a8640adaa19a42145c607f2a2fec0bcc0c092121d5ddec10102e0a37753dd6437e59893b9d81b2f248ec1bef4808e901f8ec71af15b043d5eafe804375c9fe804f9740d86808d61d1d7c73a853276a00f0cd04c868352110cc2008fe3bd1d9cc8260ad2008667097486a153885a4825848a5fa3e9b26ff897f8f03c29dac07cbc396e067b28620885e68b30f860fce6618d89d7d1bc9e9590b1abbec8876b056f6077c107c5c0c20f820f818800f822078e2ebb3cf0f7ebdb21e660f3e686e502c8f686f867b26290882571d7c10046b4fb685884a9001ed50dfd6847a82ba8da6c9daede66d592985f37a78adee013d48c874c9ea22ecaed6767f7faa2c68f64fdcdefcfdddfd1bd978877838184084bfcd23dad565bf29e085e215657f657f81ddd5bf35fbcd08e901b8f81848eba7e18f3f848b232e782a6ed2adaeff9d9c9058fd15ca88f5604b4dbb90520aff08fd7b739397d48a7aff13077187ead2eeb89e1e710106d6c39476f2611ee92a9627b08e76b06f7155b8d6afb343bb483f90e5d921ba755d8ffaf91dc2d58a5e4d290d42efbdf7664d73ce3ca46ff5678c31c6a62e975f8568074a9172ce99524a714a78fd295128256fad63980c14325038b82093a5e3408543530faf98a1b08fcbc3765d9696c9ee3e7dc2561c5d9ab4ffcc6d7badf5be43387f67cf1f87bc21a9fde1a5ae81b87fe6aca4f571ff4a23b1ac54b983a591fdef6d69543ff75c5ecb24bbfb2863125d7f88b19b243a7e22fbe9920323c6649ac09c3aaeaf2dfe70369b801593d59088fb73fffb2a55afd548aff85baa6199926c8d24bb44993e9d499cbf52c41d0a4b5de252e0e6de370c53f4ef3159d6a806c09fbfc4ed39d32c9fc234e7d0b3a71fd607492babc03ec8006a2dedb1ce7ac06535f98cbcd99faaff96f4be6eec58939b946152f696a45b3756d1e4b7be8155115a3c06a7427d5b0377858a9b2f169685736026cc849dbe7aefbd39056ecabef7e2a678c0f8daef02dd11fcbae2a8e0a6e08d337202779cbf7edf87f5c558286c824b4a29a598528cf1d30e144785d7c77705dc145c2b16ad8c315d58604958013282f4f8ce98e80308288e3b3d6548757394f104e49411820b1d104c767a2a8d755aa7a7125837837a5e4b575a0d04b650e9b86105275a7c88b1ff63bafdeff45482a1cb84a0c83c5e90bd7956542888772e193967aca0e283ad2d1a96aca055e40b0923cae9c595274c5496dac86828d1ec8c1f1f604383eb2b045c8298e9c00213a9ac0e68b0a0c25219bc9466d7a81f5ae94ac60932b85825c96edca0554d46929a703a3d9544d8147f5709049c801c0e59a7a7d20dac1eaf17db3329cb19a38ba9c5928b5349a945ae49a7d333a94cff3a3d93be2c8fdd3c7a7b20faf44d7a8232bbed8ffdfd9d31b23aa5d269af7ee91487d44ec0836aede30ae560fce0dd7b6ffd6b6f162fe74237be15400b1c5e43b068f11146858f2e251c2841d3d4e505346d483361c02e32d186a91c12b539246ed2ac1c928a5ecbf0deeb0363ec542bb5d626b5f0810884dd552c9b0804c6328018dbc2e090d5c64c0e4ea82a60021634362869e384020ab32b5a2ef3442312b46718750ca149df34edd70c624d5a59be3c4a8a08ba3ff449f35e59bee41def0f3573e65886c94cfbadf5fbbeaf04d13b5740357179a186534ccd6692c534a566042e37b498d1718d1c1c6e9d9e2b98f4b0d37385112c6b90dc211d6a9da5a52b120c285847aa54611f05681106f7c77e63112645dc9fefbf12047a97491e0bf07e5c6445ecfbf37dc6ffe9c7f9f36ff2f1a7d840fff7bb44da40ff677ddca1a93b841370b34454d24ed547a2f7c782fc3c16e0d722fefe58eb638aca5a2d26dcb5fae1cb4610d96a45557b6159605a5df4fe34a0f25751143eade4c13f0bab53b814615230245e9de8fdb12045f8b2b7bd05296a2fbe7e132546d8fefb11fa675f81d4fe907f1e217e7e13253a507b891214a045b4a7002dbaf91223782c0069116d048f05b4c7799c120445376503acd3fda96f2339c953145e9202b4c8056d6681ecc1bf234684236a25ade4b93ffb906492913cb714b1cb71a7491e5b8aa0f74745d1063515d77e3984d4e69274ba5c0c8c753468470c5f8c85ec8e62459df0e54898165a1833c379456bcc922d688434015f00401581978197980b2c98c8205364031b36ac20e6e403a82d45f8e953c7d8c5908e713a2623ee746db476380e1d12b236e372e06feaba79cb235d1f65a861f9c86f4242bdb661a1dfa5a27bdf14e2519053e2b44eb1da20e926e740bbdd4648cf9d62b591eae6ee0718ea0c06a60285135e844541534585895882093564f450b092c5c2e24fece67b2b8b34600ba4104c9f2cd0e460541d501ba6ce3bc56a8324a64f165ae70a31b46e9e5e73c01a0bf1648cad11e2f61b0b73f182d3ce81763d138ca5e9d18d91e943c2efdea2be3cc6cb97a58f795919c6a6248df55cd800748a3556c2988f6b73001023a2f560aa08ad68c11b1b243d36561f1b2f6c04a0268bbd564b0d143555bddab0a690c4c9e54078ff8e76f7a6ae5bca43e9bda08a598b91c4b8acd65203c38a55b1dc5a6bedd34b815939e8367f757b827d9c17af4fefbd379752fa892c267becce014096eceed36c8a006ee6e70921ebb92c51ebd8cc2870c7cf13a2fae8b8a4567a5027ba745681a55862553afec2f363537f36c58cebe2fa737e4cda6ed551e81906eb286e6aa19da1d02295c044d5ea28c4aeff3e8d4a60a222f5120ae8fa3952d7df3503e8151252d7a58912140c09a99fb8e9fbef3d21d2d90432e8da440905744b3be03fa1fbf761d5a489ef4fe80760a097c8955040afb40306c25862533ab51e36b6b69fa8ddea24d7de7bef0b1845ae7740c5eef5776d415765eb7269adbd449d7981b9caefc2659d628581e97768d49aa1e30abb12836b0ceb29e9b870ae53ac3022613dba191425b41ad3ac8069eef99b2f2398a912a1832f23c4d022064292121a5290dab147e8f8c51b3ac6d80bc662491d63311a1de31e3c2f23c739ac1163d1f1045eb4d82956988d5c428fef1f7bc294942c579ab1195c639a30185c58a701d3cd20233587af30b1344c5870a5a96a81e3b2fb0c589a291d778a95a69782be2159228599e9fe0ea8e8b79646f777a654025d9af5f1ebd2ac1f3ed1ad2169e6ffd0f4c43442ba399e01eb67ba2670bb59bf032a6ed7a511ae8fbb38f8b49b62075f87c8f9abd1fdfd5ff8cd2af8b40e918deeebfb9934c26f876a900673e6aa17a053ac334d9b92b0dfd7cf9852fb192c670e50d1f77e703f899e5f8b381a65f6fa987a1c0d4e675cf46d06095699af9c9dc7f999a913a0a614c85437c71fa755aeb30a34ad207f9fb7e65ff891fa3319e2d36fa4f9471ae5cfd9e8fead8c1ab130dd993367cee86c65a3d3457d9fee13e185cef8da1f37baa9b362135ae15430c746a7fbce1f4bfdfb5efc7468f0184fbcf54c8975536775c5aa7cb045ea0c8b2145a2dc68c324480b2b76d0903165634fed1095a6926452a0d4182e55034d8a8a88f34556833d49603aa17ae4d6f042385528432687d20d3a552bc6f088a1e3498bd4909b931b9d24a5630cc9f0482d8552caa820baa8a69ddd26c74bc7ca8d0b270927e4d2e4c6763a3da7a63a003a3da7c61a183469a03eb09d9e501eb410561bbc0635e37c32728e607542b9f8748c1c97eea7d3f309cbd30c2598d82640c8ee68ede58d077de1a678ff96b79c3fe772db9d26ef2dff156ff56fb5d6ce6f2fde4c71e409f195b8e409817974b6e57f4bfb73ce39d775eb9bf5eb572f2fbcdce939820a61a7e70854a68dc03e9dc044edda84ed9934b17d7cefebdbefcdf75e1eb776434c25e0f90136842da90414df6a95ea29c254b72610d0dbd34755b7bfad86d14d119bd928ffa67657b1558cb3d387f1d5e9ef3b6340fe4b23703f63a3bfe52e29dde18b459b33ce240a1b819e6a00fb7a96ab81db9e3a01d86dae74c600160bd0623d545a6b6823b8f6eb18b0bdb97256ac58090b3b7b742959f2848995524e4a4e4a4e0a0c30c00043aec9b6b9269fcef83669926308734bb6cd2df974c6d7e696f05b5e24dae163a1873326897a58d9f773ceb9347944f94d1e05198f39df2b38f7987287c09c6f16987b70c939e32b370848777af680e196b9b4f6b63d48d871b3c61e1ddc6cd6879eb307938e358bdc95170182980e1123b27a1510d103a18b8eac7dd3e909420c3da7d31384aa514b2be7a4f62fb02f33605f6bc2aaacaaaeacaaa09c48b26865d9d2caf2d5957531ce59eb6fe3bdafad3b6bfd7d7b83dfde202893855b0465b23014c5190dd368d7565a288ab3198d560331b3b25fb3aae250ba3dbbb54c76c93631d925cbc33ad924ad2c5b5a59bebab2a8aa102354554815923a5591d6541715694d5daa491a605c1369f6629cb3feeef7615bbfacbf6f6f507665326cabecdba04c1686e2ecce66d8d699280bc5d98c46abf1cb39b695cf6835ce6db61b9c8b83836dc5e1b61b1c9cdb2d47e7eae8605b7564b719036aefc9b9a4a9b3f9922650b73bbbb337820830feda712e47bbb31d9bd5da7b31ce59ebefabc24de0ddb369b92b7e52091e014eb2f48a2b3d7254383d794c9d3c9c7253383d79249d1f2039792041c91b47b956cc41e119cfa8adf4454de69b73dec5c0eb87f5525c3e5ec234e47adcc4e27ddb8f688fe756c859e5b0722ce498e498e49ee458c0f5e69ee49ee49e540cd66dbfab71ceb8dfdc139be3927b527b8e0bafddcc61e558c829c929c93da921e59e9c2008397920c99de70eac1d4e72597ac5b9a3c9b9a3ca6e000992bd6519ef6c4b1073102f488817a63b4441093962029418bc3c40d21ec0a045b00384155a391c8162d248414f328c3f80c8ca1f29ee5d72bbab1b470e1ad72909138d39d9143e8040727f68cf447768df0dd6314e41e4207420dd1fabc531c660199442500e1f39a63e102151c23188bc7350e51c472ec67b6807f19ab25ed72442138f5e0312d60f2baddfb29e3b5cf4b08a50fa25afc51d9be8f7f4a04905ae1f386fac396f28710d62221e05392a8fe8bec9a3e0ea9bdd55a9ccbb431f6c5353bf43b2143895d12ab7249c767a1504f455cac784a5182f4019a215a6c8dc69f06484cd112c165d382832839a6c54399b7e77362dddbe189fb601a5832be78d182cb478da1853ca9df09d364e1b5b36b274f3f64c4586f086e838c8aab105ee6c3d00e94e26aadb98ff8174547c7780c4feecfd59cdce0ea2883d3fcf9b08c74c53f767ecb13ff53908d373a727075ddd1ca3a6a674533c9af3e0dbdfb737c0614c9411b2540102a508192c4e594a72984253f4b9b0f3586e074fbcf58c9d9e35a0d4b87aa1b5fe01e4c51495146af0983ae3454347d79acd8f29ad8108e57ed4102283500da95c2b225ae11722e81f55be1ca97241c618224c3d2d5e609012030a5a376ab8f86accd0c96ba2442b3ca3c777cff8686834b68c607961e025d8682ce1349e6433767c62a850108f460898f7e3879e2143b4c2313e7d6f638e04be3b3d67b0e8a6502e8a7f9d9e32d400ed78a7a70c30dddcd5e8199ff70373e11b7e2c65e5252da0d00f2b42ac36dd326478014406d60c20bbd3339b05a8b42eb6352d64332508080410004316000020100a8703e2704894a561aeab3d1400096b943e665034160723914890a328088228088218c610630842c838a3d04d59017debb7716240c46e6a0eccc80ae3a432422ef572eca94e4f1eba0b0b5a15525d72a92ec6ae6e1890796ba6180fa03689c55555785cdfd62fd74b0bc9510376521ab0d9b1e76761cf1ede0c23cb7709b0d6bac4e5590c8fd157e82058f8273ec4657fc58876fe9348f011cd8b4338cb4ac21a70dac31cf89a90ab308f59559a43775e85aca75cba18ed30eece29fd98a1a68831498b6c94fe2344649e46f797f5b29f48bf2c864b4a959674e40fd8757f689d08b2725c4fe292aaf629a17a6a222edb714f9d45cc9190015203d55c04b569c85aa19da7970eca1916ac3a541e9b6abf47688e14864b14b733a5c8419baa3955dc8788118afd9539c82fcede95555b8c2be5dba70219251a02bbe581140280a1c3f8ffbf6371eca98efa46f8057e307ee621db776a1fcf3d04e8a1846b29c8286dec3424bfa708b4dc8c0302f15161007989c8780c9ac5acd6790c31fc44a44c5d051f6be200105c73c680671657e4bc9b6441dc17c26dd6092737643f455464b14a60ab8ed0b480847674202dc66879c7e3e616f08b0b42a25558c6035e2c4f8984d8f06310832181624488a746c4ae62f4bbb7d9af3c84361011e29d9e7c8f7781cd201a1e1c833b819096ddf89478de50e37049f1bda5e6ef37815091725433ff1a39cc513c209d4c0d2a29f169e35645f232c1696478ec6bdc651afe079f190dc93c14ac591e8e90812f95765944258bcdbd19cec1f61802cdfc7fa47d619b99a88b6babaa96ab2bf700ee316cabcfab6eb95d02ddcffc8556ae3c2ce49e7547fda1c20ebba8be80aa5fa7b8a9e8a82f039208c20c62b58bc446dd2d3280e13409315bda84b2ad190ff65df683efa83bd7078802303d267155b77e31dfbdf20c6a74238cc912ee129271fb01b93177aba269846ac80e32178efae7d83a12d5d6a75d33a466c3329a363cb28de8ca21501cfee551844ff52ead0242d3d6a32c144655cbe67113feb90d70872ca33263561cf7376bc0bcc4e178498a28a33e6592372757ed3f0de79f7c9c6a49ec7110a7ad1df37759ff4315c4622581fc245df1ed293850e11d6298e7fe948003b9402b673e73c1d8a422393a747a32c6866748f4dfb5e4f620115c24c7e0628d5b5271ba9e85596cf8330b8b66850e63e427ca188e76e1056ff7ceba4ff903752268b35c647ff37b1423648972b54b74c2d9c40e97376c92176fe4e39f77f87a8a460ff1ce304f63cd52f15124a032fb9df6641dae8ac5b123ad6355b55bfb2c8d3c15434866e8d91c6498a368ae8ba14ab5c583e3e6467dbef0e6e78f683b293dcedcae478cb76bfc476a5b9552d885ed94c3e8972a4004711c11a47ded18adf58e55eb020743e0b68144c522e2c611466725a35202da3f1a2454bdf704319e1313cf76b1fa1bad9308d16ad255ce701950ca2111e38a4817b254f69e0f2d2b1011d7c879dedb827b8b740054edc143abf8fb1ae4ec621e53d92ed8f5e4d8fd2ba8090678829de5f0772eb7cd7008223fe2e915abe3df312a8e3f3b5ae43e27c22b9ad0030b2a11d863edd7fd8a3ae5b6c45546d85606dfca5b38efb65b8e07f2635ae243c50551f8a7ac0c873799585a140736040f10b595ff94fd58dca01fb6eeff111cc6393c61159cd696691ca36a28b6011f91666e5ed31a9eb69df663bb05b2c8af8edb12278ed2ba74f0fde448537876194cb2aa8420e3be4cda9dc90382145d1fa0343f32d5d70be8f0e75ff1db27d3b5592b1c7622c16c8608bb25f2532b04716ad825f97ce1ed12d4601ab190412236f14dd986fc7f6199e3ab54c34f17ee76e281d02e3bbf6b49d146d1e4eba8d0c514ed779361f0bc705057957ce0fa670f53a7db260f35fc17063df3b2c75c14e1ffc211e88a3cfd56f7dd82847d9e37ccde265f34515cf9fd3e2aefd932c8db6831e075eb78f43595c553596e0cf205b9fd33c1e1a9296535192503de7a073f78b0043a4b21072693a03712ba71a101af6deffd270bf343dda73450d63ee9eaab1d531e8c1be040575be10ef0abb821f489d142c53f018d877e4e9c5513b95770e50248ccb2ec70932defb3d7f248c99279027f10dc1041bf4f478a1813352716ef481585085ce55130e30fa0ea1ec5f53aac01ed7842490d1eddf4243c1802eb43c173718ee0969bd04c81a452a024fcb3209132db17c1fae231201dac4f2411e7ba8ba7d46faa7325a8d4b112b2cdf3cdd407563381a7a040d4d117d261d1c13325812c9a8be68173a5f23f1e91b61f5585563f239b401c3d8831ea7c5d801cdcd9ee3795387ce131ae000d47345674c442e552c09004ecd0fb5fb5c290b8dd02dfa2975bb00c74b31eb5a2d5fcca3f43b898a331ac0b86181aefd4880bad0b6f6e2eca99dfcb4d128d8b51e365c21226e84201de342151fb2ff8a8c522e1b4c548de520bddc6190060b2f7739d6dae85dc45ea79c1230ed965948337cb1db99c7f96b06abe42c8d098603823412d579e5b579d97d5dd8fbd25acf311da65b31254eb5c817b744082759d2fb5727d250c07caaf7305062115afff2bcec01ef0debe99a3e2e0e493199d2d970caa0ff573710d94a79da943b73c98224a90bb9697dc741412694282e0c11aabae9917368a57bbfdf53eb5eec663a9aec59823f33f7e0326db33c2b20d2b3293242711068548b4bd4acea6174d22c38aa7d74fde7a880dd8369e614f5f3686d726c9feeb3102ccced732b320ec31573a1a94834dc3027d342801496e081bbfca199e140d620f0c110d0daca7b24f605096385d99f82726f8adc5461a3b2e029ba327b0c1f7182b1da39b3d8bc4ebf1913cc014352c6c1f3b930e84cf2da705371c1611a87aaa985da79c967f5728f19850217b41f1ae82a6cbb115f4d86b59cd81b5a6898b6646f4e3c28622fad3131a3cc1c20a95d3e8ac188294d15304ab1818689bcaa24464dee8601d5e71e85e05da9ed46941cbb4c76d7830bd28211aab256f5f61d6a11880a905966656adc85bb0db705b3ae836ab259a31045433ba7fcf075dba257e40d4e1f480ad0aa687b5d1f95649f02ab213c5f63ef055c950c2bcb063500171bcd6d1a1c4651619e2e06ccaadc90e2b07ba90bb744f0eb8b86da9afcb871719a59320e530104c5fe65a28183bf377e2b349f41a28cda01763316f8dc18750573e3d690bc0ce7ef057ccb92cd6952ef4ad542c1269a12d02deac5a23f5f005abe810a40b86d96f397fb519aefe87465a39becca40a855b24cea86e1a709b750269529d8c7eb05a2dad4b10881af89ff52eac4bf276e77b31f375beadac2a0bbf01b94c870a6e4edadcc7acdedbf7b8bc799ea2a357864a31be58678bcd340203583b916029d05c759ff25c8608a5d5a44375e0db76b935095300c2563be15c961af1e184f5e678a74b363be739563e2c84f2820c78a9b28facaa839a88c2a9c61883b6a1147394c97d1d21a92378435135c36a45729e416f2aff9dd52efb88d92a74e24173fcd07da30561008f2cabd69074b6211f98e014b6d735e075c4a2678235309104bed1b5f6eac0c72aab00b502e5c47bf4a6de4b7e8388dcc3dfe2ba61b384307620f9ce20ce8605e6b0bd46b358f574c55a69f1b2f48c4c7947be981686663bf095adbc2a0900e1a341ec352135578022f6b888955770e059c55da92fb09b8703ea794b0f3bef772b9f44ea8ae6b0cfd0477d8bfd4137af4ed606cda0905026ab754f334d5096a5b5a7258349427cb6a3d4d2322371be6d9bfebca80cbbe4263cedbb632b5714b64569bf6283ef1c692a9b845038150421e6ac711c3a8424b7d4d9d106ceebbcf11569c3795ad70c49c276e6ee2d76c3147183ca241dbf50428c9cdc07c782cf78be356cd7edf74c39360566b9c0190a19412d6347cc4f2d738c6a2e148f082864794a8841f1fb45c782a56506b15bdd4b6b485a29e30e3c97ffa872977652043b2a6f186848f85d686684596b0e05a4a4f54d766f207222f130e321f998b536b3ea8fc6280280825a86ea3fd8147592d426588dcff1328cb3114e412fb29e223a54c40447fde4acfd8e788b65ddfc2b2b31a61d4d346b173b07a9b6bcb6075d044ca8ae3706905ab54f481d8ec1bf446bf44429d7087a12a65f2485e891875e931b15a1b57741e6ba816906593eec4c476ea73fe4a72a7ab9c16aac91fc416e83521c261c8af7dd0b4bc76b37b7cfb20708850e4028daa54376d1ab03394ec2407354ba19e387e008ce87b6a9bc00fa4642e333087105247c830d145304584ce0b4e5c6d46f78908fd618e66083f7e5556b928dfceb1307702deae4181ebfb47f63cd1206c192b1f48c21c738a1c6c3d160879319d285b715fda77ce0b1fe89dd1387b00b841506d43eb1d803ee16261f6d1b893e275037cdfba6bd81b33fb409b6ffa1c359ecbdfba7862564e0c25810d5ada8092791b165d7757372247a0058c48f848732e3fe1a39981f64e11f622eec2cb1320477a4a583064972ef9e022aabf624096d34af7f2a5ae0c46c66e36daf723de988af4f01a87470f1a408b175c420a0cf2e789fe395a3c898251fa5525705db4159697f24dfa688a309974fcf55a64a2316d677ee7ab5e7bd7c3465954008fb40f850a3d67a69e94b66e985c2ab4862d05e55f0c8ef2486c9412c4764400a418747bb69ef618721d45183997618f79b47cade8446b23305ad8aec91da691ce00143fffbddf0d31268fb6c7e7b148f08825c771cf35b4cf5912f5d4eef0c1c3297bfe51667485a06fd3121a85b4c40e9f18cd6084ba5c7e098454f714f78d5e68e68680ced6395ccf20477f74e03fc65bf0705827bc3b766ed0a0a0a0635a81e80ded646be26726be7530221dd7f4573b536529db5607178237975cca3e135c742f7386a9f193191fec745c978a9074f3142a1b8b8df70eec072867d3d99137e5167597b82baa4fe623d1377bae15b89be52f0a3369350f237cf9dfcd7ec9acf45a240718c8ef4cf7a3374d941122e7e0f42f8966f7d865d458efef075e9dd53bf05121f301d3c63f1d7f95474b91c84c0f117a20102545901a28d188883c504b808a0d25def07813f40d8ab0e94a1d1f34ca0115873d94550435c9bc0362bd3c87a66441237bfc6f0ff166e02b19d8390e2b004629df16d65bd1af38c3fc4e77507eb67c8dc21680d3a5b1ab9185cc11b061d1acb37ec1c5865e751223eea959d9b83eb22a6509fc15e0bbd90cd64e603ff18e1b30cdfce38cd189b294640bdc8d52d08a618a1ebee893deaba8a661a746e41307e9d90144f58146f831ee253ad14c09ca2d81c08a837a9cc55de43b2e501988b844fc21d9bd9b2e586f32451dfaed796f61186f19191e238dd9c5b946f28dddc3e37cb84f974333ebf549fd3723a5443dcd98940ee423fef9110c768d75d36d0093bed7a8560bd8b5ccf6953dd09ad7e8a1d2e57919e6a55498187cdc4a663d85b02ce4d8a46bd0494193e10d293d6d2a0c97f6bd01ffcb292ba0ac8d7fcac55525ff961ed0c981947c0a98e49d9375b8a79899930773b2b5c99ec9d9f12c939c499f5a2f687cb3b8e34048ef7e3261c95afa786ad946d3b844be5a73904a0074e9b9a256ba17ec35342ee2f4e56581942b81237e84b3c98cccf4dc5be03751642df8e56384b9276c09a3665e48bbf14e21406777cf4b9749eee940eab73ca2662b700a951c19d4fa41ad26ae55bd9f86a28166948562dbfafc4ba140a05db539108ba858fb3ca81e00546d41ddf3d9dfd2009f65df7eaeb166928a6ce3e5f6380d80d1aebf1998b4f1ca31a4c5aaaf1ae619b03b4d2cca9080286f59e41212a1d7db04661a06d67915a5f4a2f4eec2c70cbc1318fde725c002a9da96c462fc95ec1622de2481b8ac744ff953e5cc052c85e6ed8f69edf3a03b0c7e4f0b43b07054cc6026661cb8183fcdce9bc4e168a544e970067ad263f1a460f50b3185dd880ac41eb2e0fc95d7a6d416815756c45939503402bc928bb497fada8d43aa7b76231da22aee84a4cd2d29b99114c79b762e0353618931573365fbb0a31289a42c27b6702f02e0c6ebbb7cb99844cfa2d73b74e204887bea0168d85b333c703cab562c43b9d4d70bd33c7bfb9ad7cdf62b03fb01fa24c517ca6581ae5aa354e6774b68f1abf62f8603c3748d892d3babc64d6694ca9a7759f4db988e2ff61818d1fd409be60b26c9ceaaf5795f4f9cc2f757a6c551af0a0148e2f977f2b3d3dac8322665fe97403832f06ccac3db0f1b238ee6efdb8cbf294b4362108db8c312883612560d00f1d6dd952c33b09401e190616ea58d34fd116effa103d88e97cb03532d8d3abef82b39470d6aabca9823be9435ae801b38ab74b587e072843e71a0b071d0e2f801728aa6ce59ec6240752eff5fa707fdf70c5856fdedb7bd8f85180262e4529a7d9c904e2103c9f0165b2bd2a96db3d4ab8778b30fe5895b09bd64d9843f7951518f198ee088349b123cc352b3c93173e36806b2dc0c08a0afed6bbdba4880ecadb852a5f0ae2677969d9b40630eee20b8c7b039885f63006c27abea6966d1306600010a8e8c9d231c4414bb72055384771ee00084d6158a17938d1e54f84e20e14986b9f9a2cd28348eb0105241b98aa4ea4803b482f5bedaa2f269fae3757071143e6f0d691b659b168eae4705f7e3012abbcd94b0dd4e10699cefa82579824f2e3e0a74bc77a20729ee5af00a0913c0894d5bf2b4a88731c72147563c3f1f7e4c243658236dec0656261270693d6dfe536d4bd9213928e5a21b96f8e73b1d88497a3e42f4db823382c24a5c7a80609a3ea61687308d380069e4826ad0cc22293252e0b15c0764f17c6defd54d02e0c099cd40fc23201a4130ce06af1066d12be4b31358039ef098b04fa08cb68b1d48091156d12cd29d3303030db042f9a1b169d680769d7365eac54ba9b11da1267db81e8a448e1bec4cd0289b456edccb1b3d87bceac950f1f7ce6c4972d27b1c3173258eae36ee6e83509ecc50e7783c620258024e847e38213acaba32ada1a68cf8be586abdfa53ffa66feb851a3d9e2480e2b82e48f679ab298844138442efec3862ea5b0bcad4f3076d527d01082e97dfa1504a4195269577b1fd20a69f4300479e6180b49b742a79ad17a39cdb2e417622f0234d9429af70839c6af87e0df5aa5f6ebf732991ba88936cb08c4ef41f9a0c968516f5e667b06e62c0edd1842194fd4a57b68a5fc1e6e5a2f92ab500882eb019a199763cd4ad6df21da8a463a4d315afd4180574c9e1f01a79c9aff3fcaf065c2ced236c1eae1d0e516401145ed67a77f426b470bf5a41c5dc625c873f843237e591e26b2a6b559290bb148679c515a9c84d9b767913d383f1ccc4d21c42bd30582f7664f2ca3e8e66d5728851988feaea47c5a0293c21e3a1229849142876224dc511e767bd8d2ed98dc2951769f71f33458c84814ae208392d17a20610d67ce45a280929fb271b778f904650af409d60f8c477f3351223d5b1c46ed962c7a162e6c883e8a30e444e8af7e7ec549840be1a261c2258f2182ecabfe20f1762202ba8c8968a09c8ace18d0185f09019f70f0b280ff951bc73ef2a722ca2ff9b0e0e9d9b2648a34b1cf9164ce88cbd48d01925c7836a45262ededd139c34da6437eb49e28187e5dedc98b78d7e275bcc7cd7c09389772b7c19b1909ffd42c0fb66afd512f0b9443f7e88885161b80bd90a6bb70d4a91537917572c8ec2c24602c67c1ed91f6f2c70a06986c3fad2793635e3bac9cf68dc33a98b398ae6ab317eb268be519ec0a02892f3de92640f4ce5b445003abc58b12e1c063937057a9211434a324e57979a250e7709c0df6509871d626b24de5a6389e318d1a88af22075ffc200b38ab0f1118174cc51a3857ad062dc1b4c83139290f5d2898f1f235c124bb893c22da63ed4e63fd2327c884105f86d0352b7fa0b6e503f6472b496c3441dee7b8c107b8656fb894e98a7acdee48b72edfbc705b020c263c9ac6fa1c6fbed260c00df46cf09bf0ca4ee5e7fe8ea82b12922cc3e9e983226c3051c0e316bced25bd8c4b4cd8d0ae7e497d17b545dbe0d2c7b2a172d73891e887f3dee642e31c9aeda2e9a5b9d4967ec104ee68e4656f0f710db78c82c2f8ec35a0d593bad15e05d324dd2b71d7c0cf5181142ce514560b94c8ece153fe2a7f0db433a8b89ea0634f115aeabab6759b146d32b7cce1274d13c0a66d38e6ba30c1786717d1070178372a8b338652ff7a4075da351358af0292fd15b04c40f82b48432dc7f5a66e431c517c5ab4c4637d9dc0493e799f434a7e0af2e2e75cacaec3e1d3309e1a0cf1426d74374dc39674ffdfdda70edb90cb8c6a2470b2239a80042512e2b19a70f31614d49fdf1e5114671b0a5e0139c1dec113a0c3e6ece59265b14390071b4b011065561b2a5c9315fd478f1c626b49a2611a94838b9ef503808d90c80337b67243a50178ca70a709f030dd35a5c58481cccddb8cde03ee4a82d204c27c0d588cb57e5fb646282572b15ff071aacc25d25e040036a780ab4622e9f6a4d99a6f9814dddeb504005e6f76b04734a64df5eb7382ec5a64175c1e3327fe2a6f5575906f94fc12416511730dea1d9ded0ac0df9476d80e3a70e1ca8a1d52db7a03d12b8fb403c904698906295bced3be2378d82aad64e7a4f77059fba82b1671840dfeb969bc571e60d306b03f9003e6e229f8b2933edc4d53868722c9e7e8bfb04ad04198872052ea4e0ed619a35c2cd7d3051b91e04d0fca60bbc709dc396ccfdc080cc0c7fa3fa8bfaa949ec2a22fbff08d11820bca49378e7d29825dfd1f30d2bc09ed8fc49aa1ff1daecc448f46919d1901151dc4a786071501cf12970223b67c9dd3c1f5e507b648d026d989eb44cca092238123c5a67d2a2ace788279ab54689496a84e70a03c95459cf9482e73bd9f06ba5ec7eccd93ad1ce3920f5ee25f6ec161a120d58b950a7df3e2a96131b4429c353951e715abddc5c81bc6483499400e56cd809eaea73567748b16c76acca53da77c8a9712301a6f2def4c9544bc15d4698e37ec7b950856b8898f3748067a87f72fe34cefb0e6bd880f6d3c7bb8aad40905f085e5b68ec9e2b9b9eba689ed5ed4a4929af0f606d41ef11a9ef35a36c8c8c1f1b2b109e0c2749cef9d2d0206a549ca389d3f61a553f373002e634a371d44ce4e44edd488a6232f0a1f81c33f468470f8a323e9c895d135436bc501e11790223191bdd7c52d584e31c44dae120515a0e61990a29aa92b7d8868d41a8d239401293ad6795b0fa77dd45f4ba6744a2fdca535468413172b9500ff0a013ef38d84abffef2101b332acae7ee1009074d001f0a92c97002d9a2315233c0a400bcd71fbaa1aa5f24e0df1388be26cdaa3f10c6f2dbad9e7a6fd1e7cc0a71c6fac84dc83251cc8968c52dd52295c614f6203cd12491fc7d116c3ba97ca765e8945c587256376b54d9ddcd7a912ce22431912a32c47f64911788b79fc45ee20597abd127bae2b4e8240b620afe36b6ce41693d63e4755571486642a2beab05f0c93392d3f6e19eebeaf8c344c423588d53393f5e0f785044a7e24d0af9c040c945f1452cd2e259a345572d4eabd738f03e7098c7d93c6deb50ed78632d610f318c4312398e5ea452afdff51ff7123db33859631b80a15636bb06d8ec7081c726ed49d4b37907b00d505e40219dab3ef6b4ef1ca4c35fc58b43923c44f162dbbd3a96fc58152d657541235d26a2584e305f9fb55cb256c766367810083d5169e4b3c39fd09be674adb1025d6da3658f1f84df823ad5d051c8258b4fce634d213d05831537985795d287ffd3c530ebfc4360ffd76d78e7fd5ad0c6e8c4e3b818ccda6cc080e631b1cadb5f289e1698558f9044947a20a63cba4dc260328d44e287ca6c1a807b4c5f0c655287b241a9555d1ce7f3b0fe845c53f55e27b62d68609f8ec7b21e8a1aa449d51af617ebe1e1c84e09c757b8784a111413b7712cc23893baebb7e813fad104823464431827b416a622fb9d495973cc3bf47c762cc60b02fedb69a5bce04cfbc7f9fee9ebe15ea482db8665a8f338775fa96da7327a41a69b7f8c63c3694db0b897b8c97fedf40242410788c784e7404591897d3fa11a941049a97615200afaf69adb02cf8037ec18fb5692b27ee75b58a84b0202dec69ca725bd9658dbb09fd6318ffbb1580625fddbd8f96903b3ceeba3ed75851d83825def415de683b84e676046d2d06d96ac4a7ee4d4035183ec5cf3a956b44551089f407a49a4eed8a9eb35a63216c53142d37b6e23b89f2ffa95adf5aea72130e4416de0153745f2140b1c139d8064c3976639b4317057a241e640950543782953cc29604357a1f04eb495fc3c5c982bac67ecf2c67f2c5fea65298970f7567ee91af6d5869152a68c6cce35304ac9c191d2fc756f15582c2df19ab54641d3b92cb2c58199c6786451b912890756cac1447bc744b73f362b1d0d5787442bd1c8a2a86f4fe45871aa118d601ae41b81cf4a022a39e9a93cb5878ed813670b23cc00c71ad2b8699c6e2001f019f102acf7bc502e24bb7372e125a93b628db440a5a2290553a48d2251c60f32d559ffd1aa4a493443fdc5eb7f6c97ca7b94a6748a861476d3cdc1ca248803827e76159d9274721f6dad43a8a85a039d5f9e0c73f08f816314c70ab3c08c9d8f1d5d1dbec9a9b09349c6e5cd038be81aff730f17f5a9d956d96dd546d491c2b938cc8287655e91c149f8219e1d2aaf6fd9486fda517e09ddad02b455fdcc6c629d56b95ac92eabcc05a95def46f196f3e53f183f57c03c2325b18a05c576ff604d06a64353ad3c44ea57892e0947a2577477291bee52362e714f1482888c565b9c181113efd769b84f2d329acdcd6571528f9da1d7f64316eaf818f893b979ac48911930966411fb91483a02f7152e4e5cf3d3d2080a0855bc270ce60c0295275c320431287721e7baf886ce8e46788c3b2b7b985c163a0382f331f79fbdf4e0d0c6231c106b1d8f2caa9fa504f13e51c81a5ae2b7c3829728400f720b205d821bc72430a36c2e7eb8e12e5dc9de590d93a27a455271460361ee0917289e4e8898054c94abf0652708a6a10eed2f5c5465023285245f8ccd53b860c6b381d693873af5ed7137f804ec216183c41b60901d3f4cddc14794c04898aacc7e919eafc835f21582fb2561b7c620b0a2331a39f14b97e4d50ea3e58759529ea58b18b8c85bbf555d1a58e73f38f0fd9536df6ae4f61ae45843f6f0a75ad7c6c5a1db9cb51ab437cf70993e7188b43f7a4c87510162331c853db06c0a5780436854ae9781ac320e283af156db4ce175400bd7583c06e7b758bb27d18c60419964797c60687a02e071b9ea4da793a4ee414061fd5f5ce83e00f4d02ac4ef6f2f1aa56f8bd2f76834f48d6b50cac64a1b1e879a9c5e5afb8772aeb91b87279ab550c57b79ec9e568cc045baac0b681fe21a92f995fa4a405fe3e609b23a671f171008dab10da2fac12c6979b34c60cd5d6520be8f96e0f93c2db460881a05ecd9fd4901a47fa220e87520ffe1ef7e8020d7228844a5b440247590378057b0d83c5328845d06dd0778c49df45fbdc18ad74105ab199d2bf764c9b5261473a393748f43c49951e922fc96976d20f95b9d4e9a1593d98da1a651d22421f8006aa55ff1c1de10cb91f92e1987eb69113c53dd5faea794b92c9bafb35f68743389d3834e1257f7f917bcd525547d85cfc4c6973bb62180699d602ae8c9674c3b88337a2c0a6439e1d9ac64b19bca033defd87c22f35f5292b659c41884a66b230f676bd7b6f05f7db83e98e4d2709ea4f6543460e817d24325688c8f67425bef63918b8cc2f8ee1a6dfc302c0ccebae53af71f52313f4191f2321d5d025506f5f6ea951410f3093cd94b12d5379dd5400c1c995a8a3a81d3d0517490242666d266f097e06c712e4db0a06f216e8e734d450895db3a1e37595cd3ef525b6840cf8b588068c2030c8e4612ed20462f5bbb18e049bf62e16e923edaac2dbe32ead92cd15eb852529df66b3e747d7382ddeb74b34b8f674e2f6baec688c76b45a014280152112ce3d49bdacddcef8a5640d19270bb7a2ea3b4aa8fd5853ecbdb61751a7be630a3434eb8ee24e14c7c28de1a6a96f8795e46ea025f2d04286dc9843115972fbfe411f553a80febaa26bbc051503fb2bcc2eac763b9ca513d80360d55da05fc49ec2b5e2257b2258d0ee3014d2fdf43829aeab86bbd1e05b593b266bc46a51a72762004226ae92b33a439c14119a7a91c533a235253a181ff419502e5ee9dcf3dd7e8a9bad20693073599fae02da21d9efd9b5986be20a01d052cbd6e9bf1d3fa034b95f61ae2be05ed5e437d196f8b28aca0c9cbe1b8242049d2bdee901258780ff3a54a46fa26ddcda2269f0f0e0a278632875021145da3904e21cb3bd2e5d4b4a05d58026b6efc6b86d978afff4b1b7c911b88dbc1c43a2aa0ea96be926bddf684287f71d1dd1b163e42bf3b4e50533e08f493d47d8cdfc0663020d4463b7c6b9f2e0cc3b2ff2ea16ef7fcff21f880588c4901daf4c4e34922b8d36fca6c4ae8af4d84d7e4333ed683d86ece76c8ef482de44bb11e89f980b830a857a8825e566a54d5c693a5fa655597de93d5a8fe6b1aed7812105c5bbf659c7991d898d4f39127d8bac6879e9a7ba34411456d12af1d2597f965e31fc092068e6c13584eb94978edd992d467e777fb17cbcd338d03579011405d24ce6432fdda3d7e65691d2b6c2d2899c62da465ab49a49b32a807b3f73c14e6c7968a1ac8b9b08ba77004b5d6739f9c83fadae6fc546b09dfc860c62698b7de819933bcce4e65202ab4f131424accef94701e22a42d4d710f21f99455293f9805067dd77b6a74d309a4c18190101fd58c427bf81ac9b950604410cf7500ec710c01542ede1bee5bff4cdcc97eabd867ea3224e2225eb12d399d4069a3dca583c42ef9157eaf7938879390bf0e2bc93e570059b46688a1e1b7703081981f0f119c7aa4cd89d287f4c700161d199e11126e1bb3c47fd14784df8453b5e63453b0298294af434c5289dd251565c870832f34216f53a31c8b5549fc5b807935e4d310a97308ea8419e2941a66f483dd56cc146afcd2400a1d710a1b7d27861a43c45e90914aeb665b3761e82c7aac9af98e7aae9fee0d84cb1630359ff2b8d7bdf6030b5b40c50b53f313a1920586e9b756c4a1307d99851d00ceb104e8d16c54a4e87e01a7bba15ebc60e25371b5f8c4654758c18f862de5bfe6f12d770df198e1283240650f374b5294b709fdee51c569951836c4ad9b77a35aeb2460f60aed729aab9a4c505eb404f473315c368be3e21dc5c5a0439c16032bb52691712f07d3323cbadb41863241e66f25a21f0576eb80f93644d60a0f0125762614332ac98e5f6d37880a94d763a3699222ddb36561cb5ac9dcfabf1f6b635be302ca14dcb06af78792ef63141993c6ca965f813ee9abf7317131cee4ad731c888441e2d5267430efcf6d7a94e117a0132bbc449b3afbc960e776671fb13c28a133506a4d491482cd64811fd085b0c4d4d294f1d4aeb990ec5917b7f16eeb7298f9c2e924f903c70b249ecb370cdbb316d45d15d1b21bc968e982c84309f295fda480642fddbb3cce8f1ba90cf86ba4ead60d30b2d2f0974f0d098feef3fe491bde0892b16356ee62041e53bfa97f75708b5d3a5a3112cb0b81f8f9a420c44963dbba94d2f7c1843462bf208ff5080caeec26f4654048e9cb54983c70fa435adbc596510701ac6cc9526750e52c4160b78da81411d3e083e9118f47cb200c451987223577642874872d229907e7b09a75b2d6b39dc378c2b94735eb5c4def66dcb43c8e4dd32b5c099c2f49a243c1b3e881ba7394bd784dde2ea27886974b2c5348a4809de288bf4c7f4338ee589ec4cd7f54be60455b50d5778455fe597040578bdd25828b3e684dde47b473315186103c134859ed260084c102ae5bf2a713d0f46ab4ddd6a12749d280b9cdab05361c139e7f3e4c163c366a21557a12a0a4b261bb7d5de6fd9b49628c6b5d295a2d0cb6e32dd458c3720470ada95cc8c307be32406775b58a1817eab0557551f92c3e842a76f098e9ada6c8bf3ce90724b388f4e9c797cff3c10422cc0bd81f73db38027d0c89bdc5e22f703e5110f453913873616aa98ae61ae26f593e419e1a25a588919011064be2ee5dc45674ce0905244b330187a2cf38598b779d45199ede3fe8c3d6f6c758f1be16780be569986923f6166e6af2a96a0ac8ddcfb80924b20e458b3589470ea45f34c7f47c99322e0a419552f2f98254e8a0fd075e50661001a15e43f8e625c67b6cbee561bf8a652773ae1fcf290bb7e278ef816e8f2de4eb9d1c10f3c740c8e23c57bd7222adab28e17aed1ecf1c265bff2646bd8211bef58807b7524efcd69895ec39adb39995be8a72c90613152137f36b8ba554ef6c1ad0b098801853d541be2d497dfbe3e38e5495b7ce5ae6d09703c1e6107944b3e4000ceb6de3ca5e18872a1aa6e92c6c5e8da104111f22d5ad41fc0a9dd4f0e15036e315d29c0305db652602d9443468592110a832a850389c68d1e3c1c1d3f8d0720d6e3c94421394f1af15e087c2fd7d9143c4d5c048a8241d6e650f340713cdd49f569ffa98edd2abfc223c08354f2f48d8d6a651ffc89e4042bb89942f59951aa9653d0d294d67ef467471add6f8e61cf0643e1c4af4e2bd1bdac11da9b27a102a4e89001b9143ce80e9cbfee8a868f33c948d8a46ca1bc01c246061a16faf6ac3d28ce49f407f252933171a598bda958562cbac75c8148995146f21e9895377c763fc4f5f47448a52ffd1de1c750299cdef603e69ef75e0e86050e3fab275f1f7b88a71f36483226683a81d030d4d804aa8c3b6c4e68604a13e73524ff40d9e64e8fee1ed782d2080335862b7c963fd412110a40fa9b75e5c622cddd773695e8d7c735d0e31d6b42fc6393fdf1b6ac74b45b83362a685b18c77ca4bc1e0f974b15dc03afbb92e541f33ea292ee0314962b49629489684f1c388c0976844c19bfb4d53eef3242d23e2ce8765aa0e1c8c10d7a829c82c42d33a705a4518b772b0ea2f1b66a30729276585067e2ac54346951fd782a9ee781f8dae22d4106188d617faf0116eb3ec0d1109d8f39c5d99f41cabaf847f3e88255ebfe083e990f846082ac0a26db2f12c9cc98a4304b3dfcbdfa6c1dd56711d158781c48120255a1f9a2b68d6154db28ce6a8dd6184718367f2e88cdbcfaeb496a02b21746149dc245eb54ea675457d6286a656fa1aa67ec3362e0a7fe45e15f3e82b61baca2d159e2c19df91dcdc1b6af33e3ab62cc919e10e1556af11bf72a2bbd98c434fd0fd426dfbb5d8840ed6b813094c475990740b880fdb78cea32e9ba04d4dff882be94f73f67d22489a46fb22b8700e8936cdbd065ddc8df9acfdad484717ec20d015b53270b8e0ceca80660ba4218109b1bdb2bec10d5618bb278a7bcb00cf89aad715c5d0367f4f24add40edab548d64804cb388a3735189702cf2f99f1f3359a3c5691f2984f86b9d5533159c2218ee954b02ff159a9d00735f18140874007e36ffa79a1d5e8020c6c246318de7ff842987147697175902050bfe71b40384f693ce31d87e17f0f3cbf46c974eb63eb81c4c2484475b029a194917a0bace1d88282848595ddd56f90caa91ae6daac9a7548f39a6a1372a0a3112c70ad3e32e886834438a56eb9d33a6d716e7af290789f66e0e425708456f7ab81feca41d4fa8e4b603b95175c30b54ffa1c2c139d417b8e79031cec161cc5f3dbf3bcca9beb1c5c43abe98bc941840ce48008a48462b01070b607480464a172c538419183595f056034a6f4b1210ad3307652393a004412ef44cb1a9f0d0d8578d7a848facd693ae528f82f38cda9f32886b18e502eeac036895a263b06fa374fc22d8165c958df6b1901adf13031f2f8fb24ee637f8aed3c12c980ce4fa43bd45fbc76e58dcb9c9be91db8ac4ae0f9da2fdfc052b309ea3c08cb8c147320f44563de3a6ce47560ebe1f35e5e0dbd562e7edbf46b41f0c26faac1c58e756de2638b4cfc421ab01502b4ed746a8236f6e28fbfc9f38488ddde1b63a37544bd692872fe8d796ed391d343ee822bc844211d7205f58eb5bd17072abe8aa9924887c3982308399cba1f6c14e8d85cb0c0fcd823010ed683fa8c9b64144c537cf715678c2b4a9018ee666d571ff603d9337583e2dbec212dd87ae9cfdb99cded26d7918019956e72ae3b34eea9cb609e280acb2cc3a8fe71848742a44d78bdf5e2f56a159cf7e4e1ba4a43a5b8e5dcf201f8f643625b0d6200260776d3a3a67ecfd412f54b50e87b29f1c9c9ec4afc077f095972d9896fae93ba61a5be99edb505d9148e0fd7cdddd0c7a4073b65ad219a25a381b364a52080af893828eec96804fc8085f11f5ae9666d57dbc78acb7616b9beb6c916d6b83c047aaa4b98a78468d96dc4aa699be85c37b99527ae2a608292b8a7dd05c82a1742a6109fed843ce3a6e93cfa72268a02b0ab09e6ea9f0faf94b2ede569a69c3babd1a3512245c33ae73c6d0a32f4d1b730483b233410625f561a1fe0b39b20f17de5b500af494d38f212bb873a2fbd9fa428760f0f48609163b9028c9627e7f6f6319b08875a820f0130f6dca063b80c71199416dc019de61499381aa1af9a1074b727ee83d8b3ae9abfbcd865f77c0db9533416df02738fb6d90130e9e3075fc73ba5580aa2a99660f2275b706e0d31a9f3435e7e6fd6343e2777a468fe5e69fda61f409f7af30833d4168f143f81b30e27266133301d9023c5285065997aeeb0848e7a55f030b28d0694940acf0f9a4f138a1871e5267b1f8fce79dc2f2e24d26707dc820afe70da1776084cadf9f8e28dc9eaa9db6338dd1a65f9ea15fda30036cd60d993432ffab97c574bb26b4ef8826e078f4ac6d531c40365e418f6fa48db3ff711082b0cc3e03dd0fcebda5461f46752b88657cc3fb81dc08f7068440c4ece644d09689e9e46654118d256dd9cb0dff128ecbbb8a39c84f1ade40095e0978bb00e0f087f5abe4c841b21ced7813dc3e34079d6ef3959196024277f16c19442b61f837ac278acb2a08287c3c0b4ab145bc2c6c2da2c36dc006773159335b3fd0992ed70ef0de153787f69a6d8d66fc2293dc29b927f1065e7c0ef08b0e3d49816a61d8f29e23a4c8951820c9f715bae449de5e12047caf3fb2a05af46904ba259596671956cb70ab045a278d6aa708d39c4c2395ac00bdac1bd68b8b78ee4464c47bf0c6edfc6a091943ee0ba15b3d36f630bbbd1fbc7d38fa1dfafa32e4be606164611815abbaba0830c7ca41819a9f275d159441a6fabee72a0216328e8031796b4d2e101d910b6164c9e3a84431569d28db0d6d420365b8b3ecf26fb66e1f1cad96b55eb0e692027494e5bdd9aeb13b33f49e264a191e4b076a70476c7bd8656d96c10dc7357ed81be15508dd109c00d31230987f3bfb40008241e340c9c24e35a3f0bc3a814dba62edf6d1494d9a1035e21ade99ae641e9b6fccccb9a0f9a397bede62df0596347f842e505566583a85402b8910630e22a742ff21974bb8a85916840aafd2407af57239120ceb4674c88679c6ccfb11c9493df92a0815c89bf0f1d1a5e31087913df04dea912436187aa888445a3197a81284447ddf14e8831aed7f31385303373d03b5a65c18569080840557a5f26df9fb8816543d450c2c2c12578246a2e1873d261baa5d65c76044510bf69d09597e791b4d4373e95edc9f09d47f54df0a06f7d0c55ac2054835c8551a813db388d7ec2c685ac128c98071e234b4890624a07b7d1c7a535ee440b50c5e2a536dd9ab6f1229a7aa8fc76f0085f9807953c77dda8f4c7daee0e7edb582645efeea892d96c2689fe0f263cf4d68edefeb2a5f091498aab4246d3076d5ef3655a061fdf941582eed74c6759880b8bc5988eefc2804136eb0b3dc653b07e924d6483f24ebd90892e98a8580c2c4bb0546a913d30c4ed0dd5c29aacfa92076c64af7b1142a9fdebffa860d47498f95e8a3f3c761ac5aa34593793f942fea18218cc15a2159ca73aaf661d73b8345fed3a61e9c32bf1d9906258c3e6329e97b6ac4b9f6bc11c94b82d2457a54eb23aa00c4ddc60c37473f3d09905520b48e42155c60df470b433c7c218f8ed12ee6a90aa33314171ed23c63feadc38322aa31eaa585e7abcc11d5836313bd3c0d03dbaca51461b8d50c04740eddc02002e61ed43e926bc8384317dbbb6b02d4916c732d207e1dc1274db08524ea8dba73cbe6a11a1e19adb67b91c7c5361b2935e126cab424c350c853b665cf66416cebbadfe0bdb1ae5e427086e048d09b999228e2f9a149a92b8ad80053cecce66ab5063cf7a40401ec15ea1590dd62b829adf08f7af1c28fdb62395cc6d01ece858f9da2e987dfbfeab88e4efaf04f17243e9eb3b0455c0f10ff419661073d275401679f41bba181fc2f6853521937cea92c71ce9d5b6ca7f869ddd9eb2c609349e011432010fefce0cc66c5ea6669bdf9fac71536b7f1e07d7d012aedde00d189b5fcccfb10b0a1af6b1d8bd07588849f63ac04377976c69d0b6d599c19fda522d61c6f952db37db06ae142989c1300600f63011a8f8c398152638bb31bdc12ab1b54906d92f0fc25f25ac60bf5e2197eee9334ca73fa3ec23021c4f8b3d89e98f920393fa61406b7e2e0347907f8c88934624639823c6275f9a11252c92bdd12503a0b2cd627fe65fd7c3c7801c355b310c232196c8f8d546ae30629e0f864a1da948ce1055d8f5c40c04d942a6859220c404e59b9b43b051d083b4ee2cd8f5d7dab678bafb930fe9910684e8fc5b57aa290847bba30db7ddce48372a10b46bd283ea5690a0aa49738e81bc370322b73987290e1a1712f2f21ea0d86f5b125ad6025b88407caa570ba4d21508e33af309107cb8b7034e15738ed3ab6a503614abc2cb88ef6508063ec4abc935cfe77b8e57e4499566d6074c94463b55124d1eddca973d359d577afb0fea17facd1428a42d460d08774525828bdb9cd4d27de4a6927d3fa1e2ea15361d525de2cb43c13714d5a1422cdb24ff481c9fc94010a8ee4cd37831ca15bfc296a3d4ce336b849b70bb97d308f0319de7a30571f90d60f7d4020e113043701fc057396764cef9c828add6b7d3df612c0c4b653aa4b974e47e9f63380684cfe126546a324abff437e384cd9f74a37a50bf9a5e088df8248cd5ed85666fb74332b301cfbf9aa2bca5dbbe4ec16d2551eb226b8ed8f374b97b379839f08e84dc847e47b105cc927de222b09f132bc89b8fededa444a828f6855d66b6f2250e5fe6ee95bb0d1dd3a5b31896bc67bc98de98b48f81826605790dbcebc56c5c10eab0ca9850729bea03d4b926a760f4ef41579eb50f39d75cca39edccb16bb927b9e33bf2309fef0e2d31f109bb95814747beb9b2a152325616c43432654834c97ed2b6e446f10259e2a3dda5308c23e2c095593d6a0b4ea619d6ad5c5d1a854a36fe8f1229a6b940a7a875de6f7d2f1014d5dc2177afd8b7a8b41b4f1ec9179029c881d03403b23542df429124310ab92b89f9192b2c87f8025f6a213c6c8b33345e6c3466ff63e82fb8fcacc85b87457b17dc773e2c6db39ffdb9e78cdc1cc0ab1e1e079b1d63564a04ec5e115332eb58179dbd4d738c21b8a03687e5d42f094423160fc18e8dc3dd70a463c628f8e3d0f00890a17fa0eaf94320bceff7c656fb642fffb4e1e04dc42103e840dd12b1d22f4acb68568792bebe7d388ca9f58cfff4eca96e83da9603d94fd4ac82bc954b70ed07879a44743bd6c60e14a1b4fb80a40c2170be49079bad5bd8dcffe554a0db56d41d1de0e092c354f85dbeb226d2c1bf39a5cd73e0f333ea9c25900c0aed5cf184b347b2bdc344689c793ed5a574097b35403c190b23040c441393cd486c8629f8138be467543849abac581fd6b36e876929d779e726feaa50932d4ec433b5bc45174c24804a8914d95c0ea5b68dfb29e4c3641514a753d4b8f5dcc539447dc8d6c43f2a234411f5bb0525007154d8fdb8a473a27ea839abfc0e6931b4d662a2771078c8ce914b4cf405dabc74af4430ba4325d964c74b0994c248f045965cc7f27cb49979a5f60b20cc43712e97c9a6e77a912860678ec70b32709ada5d09c91d13c19b12377248e368f0b138d92ec6ce7604d0b1b5f01b45286ca3d974d8392fb686a28cd11602ddfa67b47f2ba5cd89f79617a8e3ea39922dc92e8c21da2f747c4e02c97c5f60e9b61a1d18c2e28c692eea7c59f6b0fbce85a1fada95e9f4145d1ac01e5c81a4c56cdf5201f2bd2ed2227b9d2b25be60e377f43e2f5dcd11947fc3dc853d511b59cf1a07fb9c047923528331b81c500378bef20e8f53d87828d8ca6c125ee1bb3287e10bb42285433194cad5bd64e21c9cdac302ae70953d8a0fb31135b049254d0b71144d7d9baa1ca0e544a2d9244f10344e0180d4de190abc54d3da0ed8b612d4f306034dd167e633524bd0d6d1d2f227689f7c220509d5cc8bcf69dfa3e06ffac33942ebd76d09427fbfa1afe082232d304a595a4173c54c4efe8eb7dd985f07bfd669c1530f2d71aac25a9dac001e264176af14ade816e58d8c2e3fb956373d0e3679189a283074cb06f91628861cac9de74db8415a803daec2786b0827240cffb1478d779bd03552b92dc3ba5708b8400a9986ff09cc236e9707817064660406d4eeaf79df1016dd7c23361b91a9ebb168ab26ac282d78f050e605699d3c5d620863541996144d2a76052a4c41d7fc570f08cc5a41c62df29365801203f721599f54a88cd17a2bac5f9a25be4bdf94212d49ab137aa34863cb2485c761b2145884b4009317263bc85d15c22fc3019240b52102e734a3956c3dcb485f2f951807e9bddca20ddc8c6c08d60c09bb980f45e1cf490c65c3a784e55328069ea35023ad2e350a63a831c4356a3e85407e27ca40b6fe4417ece23bf8a1ca04e0450d72a87f426229a7186cdb8700e632387d14bf3760ad02358d9140b905247cd09ea088486f9b8ba412a3e9d3a844710cda8a3a32287806a92e2d6cd37bdcd91151fbe0211a0ac974b6e22c71ce18507ac1255b6082a53d969a798868ce37e2230a01d021ab81516621522f0d2fc083939fe9d42346ee2c802322ac32e048ed13531234d210d87a7231a594483a38361b30801927fe90af357badb3618297980fe6436dd4a0ab1181e05e3e9d6cb46311b11d761f7254d619e1f2854be04808c164249299a6a5f389ce25388958730dcc790b6b1bc3d3ff2ffb3dd619ad7eee3297836a063f2c366bc763559cd9b3aa0d92a8ea686e6ee3c08a4b68f668e1cc49f22b70baba754ca08c036ed3eef23f1b95c2220a9e16423be4b435b64846eb173f4a46921d3909598818b62699a1f8f9e027d5536b699a589eb755d6e10fc8d5617efd733542016184e4285bef7eba36022ed8deca38a66fc828a91b96fd007181de80e060697a90c571a78ab79828f12a9047d504c61f93e7b5355bb0cf827f06c6a1415b669081d6db00df329de9984f2729eb58860c128d310a087fa50e9f572e914b0399d4922ca8f1cd12f43be7ee414419f0590d30404aaee3ec522e6aa547fab25b717b17609cfcc7a45f117b04f61d3f5190297bfbaa29956d8ae09d4ef28ff8a054b778066255e231d236ec6ee4a79b41c63e9b8a645919742b259b608daacbefe287ed1a006d1f79585319f45aa37f4ef80cb5ccc8655b28e6f6ccb386e4401d2bb6278fdb1e64ca83b09da1af5fcd679b53b1ce1852c1dd66c6db81f9d186c1f32d04bcd431245bdaf5c6a4b0801dd83112defde6be5f043637533be372e836deeff73f36b9eaf70c845116209213877f5da31716d356f2174f6bd362e1afd1de1a19246c2c55426753a92938505f3025fc869995fd537b9b70acd667efd5d0d4b91097223d6f24a6d077e91a2b1164373c9ea81b366d8e6d8602b8e68f55a1ab24b477a55c41454b9534224862423637e1d1c5e2c297550260e8c8176a908ff14ac7ca1911839057d83d2144494d085e95364956e5b751f4187ca231dad721fe47bf84c3b06447e5458dcada56604b8b454fdb83526de79c0dc6efbc5d0fbe29cda70a7394584c431a71bccc645baa667d092e1384987090d78d86d3ea0f53e31769aa2d6e62bb7820bb56af403134085007e1244be44d81566762ad602b2fa62b4921ff81c05dd21118a2835eae6890580dea4140ca0f7c5cca768b1e2428ec8e556965979cd590edaa49fb49853af223d0c5804453861ccbc29c5dbfbdaed50d2be67ca2d15d0cf32a910e9834055583b9472fb62125d55755840cf2a3f20d448c19476a96102fab9844c81876989095ce9c4b050ce498206beba47d9ef63bed3c6142bf0feffab403dfe1b6925fbe7e3e3c6c93a923cbcd8a52d7f0e1bba5823332279e66fab528498880015224ed74f758d633d125ee805154719c85cc6a0f1ae89e8f7dc14f7da88aa347fa485568b57fcf44d2b0d2d09ee57c9f0e652de1f0084dfb83613a32c0fc5b10da77c7d6eb2d08a2535b808ce0fd40534ff66804c2964bdea41ff5543d7644c01488dcbcad01d84fb98e10fece4f1fce80ff5113055c5039bb82034b721d73805d834a953e1ba2a0b1d3a7b5b2f976ac1e7f7ee6d86cf8b5ae86f14580e9832d0c7127b5128557b885449d7d89e91e7de1c09168c105ce49d22443dd9a318f4296784f8b466f54e1098b88e995391cc32d42a6660b0c03ff39aa2fe32b85503ee92239653e404a061c18141c2e735e1404963086f850608189c6757d3f54f86b5b5e056864671e8527442e0e875a09b4e5035251f11ac7e854be375d5f3834791ff7b62f533800b707f2191640aeac57c5275daa8ae4dca495b25e0aa919eacd138d7dd8ba52fcce76852c255ae85537c17cdf90214f4628efb0c054c1beeca595aa6b42109c904a662ea5952471d9b22be7c7250f7b5bc67c9f4c806e9a12ea907215951890c0fa7e8c417f25ac76eca5eb91ba6d19cac8e844820dd19e55db035f818dae87588fe51fc5580ace9d7fa99e86db977f6aa8d495daa94605bf9dbab7b4b7fc3eaec1c088e4300c947923482a53592505a5ac753c92403a3f520c15012e864342be60e66071e43c0c10fb3b9362945d596a5291ce1bc92f92e0a909f7c0f037a4f92755c30503a4a738be37dac1d1daf5ba2bed79a4a76d5829d265dc5f5967f9dbc9c39c9f88529ae4d1727a0d022a432ef42e8e8e63ebe877931dc793af298bcca2e2fb035a3a311b9ea370e8d50a3f79963c01e11b1b6b5d6464b4b4b9b04f50609075807311b11ea2db69b0133203cbb4acd7e8ec7733722d4615dc5fe9c9cbfd70ab137161abb72106c6b2b8d8d7fc41dd9e37b328c6634e4d4cf70379ede8af0fc78f8d12582e263452cbf41c2df208364144f7362e3c11d0b3c872139ab1fedefd9359c3d270ec21d98e79d79e66db5da71734e0bc4e0e238c9dee4620f9f237170205c499c599b37bbea5a9f7da0679c70b3964e8c62afa50b1d7fd94a818cb8d0f33e8cbc0cd2377fe423d0c592c4d9cb109fac798b392fb8aac752bdbf8ed836c5a87b19e6857fea32a4ece9b29db4a68b73c723f597b72239179f40743ff196537ad57a64a73928067165a4c479c7a8997cafb53735326c68661cf5d4bd3603cb57ad47ca5a6b6760d7292cfb46860daa86e6fe64faeda19d7e329d9ab2264b189ec25824f526171236b559df96d8b714d13dfd3a7dc4f253eb6192b1efa516c4952953a6cc6d538672411306cd1a65ca9439bd4803e67280e469a9745def81d7aa5fa63dfca35f04fab9e0923ad3326ce1eaca57a95d07721a68befaf43b3d0caebc3c2cb9392b0b1f7db3853265cab47abee20b573c90a503de6a7d7c4fc53d70258fd1009242c7986bc47908c440f132e6dcf30388e3f11c8fd74bba5e15e4bda2b292c449d2ad1bb0ba74693a0604bb4ef3696ffaf5de5573e76e5c286cbad380982e8cba901e2621435e783e16b319cc6f4862d390179ecf505f404a9740a6978ec29be9d76bf066da8624607a093b6555d6c7e7ccdc47bc698fd46a42ea6ddea290d265de3421f298e9d883ceddd8e4af679a907ad42ddee2292aab8260371dbbe9d87529731a90fa2d669a175f731b4d48fd967d2b3d3ea787189c2bc0664f6f83e1d36bf04693b7a10e7a5d3afd0c0632fdf5c540da4ba76c836dd4b93c9a909ae598391eefcde0bad60d905a85d7554361288999ec693eae0a88941ddbda3ffb190db3187bc47e5da507d13e723d880a4901c357e94e43c1d928e54d9e6a3dacfc8f9e76cec6264f7fb0e6dc8dcdb4035737de0fece8c21baa819a1397854ada72694b3373ec2165a76fef0997a79e25cb4d7a4859b84025f74a1a124a7a48491706d2ee425b67edb3b9b2a04cd76a6ef3065b7b4dde609bb8f4bb4dbec436fbac9519c874edf625d9a4243026a4eb2fe9ea62da1e972bbb7cbd5623f3a6e520d859ae529a32d07b4fe465b5fcb258bc0549954dd2756d325ee5eff5974df22f8b8c1c24ed254f4fae2c6f2a64814a5e161b5396a48bc2d96bf0e910df3ce2d42dae8d04573333a7d1decc7bda4c967afdd855e30e996766b2ea7a599e02ecb59b4d29eca2ce0d7651479eb6f7244b968794854b963b0369b660d9600a105f7aa75d0b8824da064f7a00ed5a484cae74d6f4f1ad27ad205a41b482680561fa924453b3d78cca7e8c31c6183345c53bf925d37ad097e50c7c4fb1e91946f1c41d2e45038704fa120eb51f2c6f37f5f2f698b755d3bce1c0b2ec3dcb3010bcc9b2c7188ffd2fbbc933b6205843c88c1a7983ef25e40d471209795b61c1c9db99a091b7140f54598914a519ce8ae4c00e4fb36a1a12a27fa6b7991e0481a2c46acdde3309a64d9a90cd94dd74ecdbf55af326bfb4e9f03b3a78c6ccabcf516da6bfb2db929484e50a13567830ab9404f014a02bbd296330a319cb59de68dcf4fac7e452dd6a279b5f463ccd1bfdd384c42e4f01f43db1b30de7a66ff24bd7a3f049f3418fba89caf8738d1c4bc8246455de2ecc115e85934df49ae6231e08e6fb2cd298371586aa0c7a9b9346ffaecadb4e9b7e615a8f8ac28f067e4ee043a2d1f487a36a3e50cf6ec2f13d7106be4fe199dfe096816df0095fd8de2db5f1f4c4d11b0fada1b951679e06c7ea4f99f2967a79bb31bbebad488e97b79da6a171c20b17a874ecd6cb0ecf4e692b92632b9263a76986d5675856b12cbdc80f441c3856ab3f957a4cb0d3e768d3b72239dafe34835fbed86a3e4caf9a90ac4d7f4ce2dc8942c70a6ecd9012f3562447633f656bc2ae65e9e5e1703dea73a2e35ae9e525e1bc1ff114fc043fc11077b31ec9ddb2ce501952921f74901269494b9a82a3a0fa00c6872f5ab66bfda0840accfd611acbc7e650143c300dbf93a4e1732ca086fb1c51a49562e753877c6a8c31468a55f0d3a76215cc3f5c0b749ece7855f072618c515e05191d10f0d1276a280cf9443ce4e383961451cb960f4b5a1e25b1ea9dbe05a1230a381f787ae713b1eae51fae0578e783450a381f22177574e4efe50496f339029a57616610d4e8f72144f17007cc411d73ba396dc52a56b18add6119085ab8caa8084bd0966c8411f6c458f17661139bd8b7d7533ede8850cf4b6967068a325e8faea0dea01a63d4f8d26a128513ad264da46e132a77ea00854081b1a486070c1b8076ad26336852c3497073f02c003e6765a3446b88b9d66e2820edd99b1009c96e3520d96de9d99f668f6558bbc54080ea2f0b046dbdd3b23fad3ec340ef18762db57a0dd767d7707c2603a56fd7255661aa926adaab9e9546845a664662cbecfac98d4dd1d8af4beccaefba6b51d1a5b15cb23d31cb886bc0a2916c1f8c21690cdce623564d394bdfa8aa947f2e95cd3f57ab6afeb9fad1fc73756cd8af5df74423420de3b3843424212902abdd0ce6e64c0657286de55ecbc996f14049c7961062e818630f9e88d2f30ed5523e2bddb54a5a08913adec64adb5c7b3c4cea9adf3c1e76e97a394567a7513afb7d10a874ad9faf4b3a3e7e7b4f3809c2989ec1dbeb995bc2db14523abee6f1704e6c5e2d486cabf9b83d6ab299b6c7b4ec693f1eb8f1405f416a7ef393f682b8f29b76ed6526cd87a669da7ba016c4e6da4fb462a099c7bfedde74a8f9b89f353d6af256fa9cc137799be9eb260c34ef1a35e38106c2db0c053e440e3e4051c5113a58e2e39ccee99806c4763dc51876d27ee24f782bf5363f6383b71285a5de4a0f41675ba9af10f494bf58cb5ba9e72550049a0f41f7b8ae1db3c1f212cbb8868164b81ba599534c3521f555fb91bff6f40b5bac0a72facd4fbf396635d9237589b36b18881ee874cdfe7ae946e91653bc3d21306e34a75995ca3f436dba0a88cd4fb7f9e9a76737983e33d1f80db681f2333e31cd40d967cd4ff3a8f7444c663738a331f4c28c97c0b9023c9f9e3803cbc0291406caeeba060365c7324de60992fdf4eca79c232403653450184ac2051486865ae6cda4456028092785ba05ec429786fad24a87da853d8876e958297b106d23a7ac85b6256ca446979268cc85b6570641aa8352436df3364d3987f67cb8a5762d29581db34dc1991438fc1edc4d4627e1caed702d27ba347cce7b4470628a762d2758bda35dab8956afa468357441c3ef44155cd76a026906cc411bba25450d0d77449febe0be0cae5cce01c3c205a75d0b0b152c44606992d3ae85c504368490a35deb4a0fa876ad2b2c68c013348ca86162c2210a2451b0bc306961a206890c8819ed5a50dc40a35deb8932ad27b4340d37d5aef504151aafe266988678281d98eb1e98eb5fb9efb9a73d8a1befa26c23b557a0d14eeff01dbec3a701b90f0e01b3f0f1528ccd4218e77d70a18d0990f112db380077a137f4e21302361b091d8e406a0ce6290db7ccd1163494cfe74ba358e8a0685192d68084137994d45a48687139304b1ab047c37d872b26f08824318c4c9a54a894fac4c6a01405c915062bc28a115a32164cf1349c297065a28149026c1cc089ce7025108c88c1dde1c7826b2f4dd22aa20b7434251360af871eae9321816498b2cb521832f01430fb491e92e4c2254bd27b5f8eee12f94576915ee41796cdb04ba8e9a7cb7943399ca6df9ee52da7a98da679dba1f43b2d557037d932252fb1099fdd077c267334b5172d92a34bb2e7cecad51aad2d3d27c2584fe773a4c4192abd4984d42a424a8b881a72c82e4e7e99a2e4ba1611442d28637aa65d0b8a1612ca140e0350a8f496ea2823928e2d28513ac28e1152f8e66db85bea3dc29d245090a4baa6706aadb5a2ee829bc249a16e80034f01afebeb59a8a9c77232c49876ad21626803b46b090145cf2925bcd1a58fd95b308488059e025ecfa7b0811984ede6208399587a0e1ef7eed2661795c2813aeacac675e1588a314208772ed933788561275c316d6df69da7715d2bbda4f5d04a1a967de54b0bf24e58f6854f389a606b353be157c3c984a565c195f7367c3d0db650a774ed3048ca2e4919c40165ca94e90c7b09cb9697944d71bc540b1e54966198c93dd716ae302cbbf692865529cfb9b457ecddc6cecbd78754d175b715ca597aa60bbbaeeb8f494379f0d042edc2f6252c9bcaae44882092990b57cfee70ddb35fc8eb57be8ec3bdae6c73ce0aee96d35247477e15e58e7bd9f9eb0fa9af78f90b1b101b10c37d6003e0e55338cf813ab2a79a40fce91580278221fe945d3c0cf0a63cd417de6e5fd7e9f5d243ea4b5e70c37cd46f58de721a7bdb694d9fa7cf1a6bb319ee66a3e74bb7b143f349f3399fd1cc600757f79d9d707613ceb2c536cbeff1d9845c68d98af0386c830e3be734325dbae3e96d28496bef81af94b50c941da86e3c526e59de783afb965db751c769cebde4a096b7ecf53d31c3b684371bb28a10524ef3467a5cb74634700de9f9886bde5ebc94515a9c048d1e32ea76da8f2b42c74d7e6215907a5e15247e3ee699a4af6bb7c140d76d4e83b7eb34422ce9795b6b666077ee469661bc4921335a8cbad37adc600c5789d1bcda2138106a14703e3838f805d54bf07c4a9a0f9bb2f6a9d32d9e313521f3b6a409b98ed50c03527dd0dcb91b9a90f92dda675590d79e48e3aa63d91dbdea97f2290c6473247519189e8137d3e55358c8bccddb25e45e66ed3df1061b790de47acd65601faeafabb0c540da6b8ec25b8d765a3a61a00baa7edc1429338bc9ec18661f441b3b8265f298bdd3507036ac0ad662a56c5e06a9116740f559c2558e1d1f2e6cd712c24abfd73b21326f42aa1f27bb074ecd080b44241801ab9d5b39e76cccd84302ef3203b907f31a05fe428d2f361feacc5973eae2a0e63fc6958f6f074ae181125278a044114ad0a09f03949041aec04991e161ac60071c84b0032423a66c41e6053f281a020a2b8ca82106576a93304874a0a530c4d45a6b0d03a9c700c38b1c6892deb502b11c4428526bd702a309183af43b46b3161848ba5deb0b2ffa3d73587c91d4ef0f87fb962facb46de78593762d2f741840bb56175f1accc9b5369e2fa034ac4143db90062beb6c50edc776ece86e2345e70aae6b81f9d29b6b7d85d79b9b12010a01b8e3f938b7510ce9a12681874970a5732ef7c4cc93c3e5e9d8031d8f232bd89e0fa20d9bc23713ff9a067140cf6cc4a1c0448787c3d11144f8dec562b158527c9c95f8acc41d72c9afbcf1bc94b71dd36bde56da67de9ec2f269db3165179f6517e3e592b803f2f0ecac564851e7a9d4bd71c807c62b8e87eb78ecb1fa075e69a1a397afe83c2bafe578c8c7c97aac27f4583e3d51273e06d5a6b4e22da836cd403d6036e2ca34fc0ab561de864cd0d7f9389f991fcb273a31fd2eb7da308813f303eaf8f1450ddcca75fce2c8ad5c6fce071d5fabe325d629739d9876bd392074cc82480c95322d008386964f3422082dac5c99410daa18e1b38021a278f9018c135f9af80cc9a087317a904118305b74e1e39830410d4008830b1c6060c2672807124831c31258c45c31f289df893b6a28f1c50d9290a2046168e11385f025081e66c821cc12627ce27be20ee7b3c4da00737063c2075de4604a1357584186c905c6548141134038c183146066d0032348f1828a11acd01eb00bc0b4c0d8002649864d30a7c02c1c82d0858e9b0a2e34fc7d7677c3e62dc2fbf8e0cd2bbcae4060d7256d5f919a47c71f982babb7781924011d33bc096231ab2d2fa234fd94d236662f833fb52742ad0b2b5d24b5fd4cd44a874078348cb9033cea67de03abf6536f5541ecebedeb4c29c876dcea4df874e7780015e1d130778047c3c37c043ec874d34daff74e0b62e3c5d9eb863dcb0e689e5e94629cc3e59cdaab48600e621cb0121e1e7c83b53c191197da2f2c2e64687d1122ba2e36a6c08186a226556021039fec47f81283dee49d10323998b78473b2462f4fb69022021838b183151cb4604b18ca05ab29e5c104247cd0c30f5228090306ff2280762d2f58bc38c145bdbb5ab87e3a865b382272651775ea4f6f016a7fd7e5895c97f0d963167acfaeddf4135897be7ca34efde1cd67ee6f7ea1232232b01379c7b0bf973291d44f59e8f4541e1275ea67b2d0119199bf2cb4136ff24c16ea893af5c22cf4a8532f23d788b39bb0267f9d882c5de85ebe9489c46759e825893af5e59b853e705d59e888c87598851e2b27113a22f20eb3104be88808fccb42da614e21ead40b99eea28e3dcd4ecd9d8f4bda1ecbe6cee755d45f126983858e86a0b2d0f3b99f790d6e01fb751adcc21111ec7a0b27b066f2cd0f29ead4fa1360b8795eb98579e7b33da4ae3f611f0d3b22f8b7450c5b2c69cc41074d3da886d057e0f9ccfb486967c20d8e8839b258728316536ec0be68f1440b56f7002ae24effa83d166c393836bbffc0d53477c07d1e02b93a70757c3c59386908818ac4673c2f8b25b00332cb11ab5d86f34527dfc2e5575491f32adcb1e0e1a9750b22240278aec5a2b674c1228a2eb56b6551830bc06001a6bbf4fc0ed4e15a5cacf47c8e79ecf4ee8302cee7fdcf65a1a321efee2f0b3d9f2144f5563581982ba09a407c3d9198858e88940e1fc3f38959e885e7035fca442570804f7c0ccf07de4a284d3dd8e19da8337f44240b1da926f0ee4ee4dd65a1237a21177588bcdb2cf4f2e3fb09aceb7c9c8f238a49f208f85a5da2b4b4f0c1b5c8c991b202b8054a0c395b7e3841cead90cf426bd7da52842d567adb9964ae486a5d71e50a2bb75deb0a2917f8c176df5deb0a2687e25050e928df9c314a3763bc944e4a299d8b0e462833088dc40cae1c7c3146082184114a079d6a4a28a594b01d6d18e3a98c51ba28a593524ae7a633127bba197f328439c3cbec6276a587156da71b1fbbd8ec3ae79c73ce39e71c640283598c114ed1f2f029e029607b0f9241b6eb83645f3faf4cbf573a16238677743edadbbb8bda57caaa2032dbe2df679c120ac97afa57a7caceabe894703e4bcb74cd5a98cc5867400592be9e5d94d64a6bad95d67929b3476badd96b0c487e6e0a88235a99cd8b6adb7520094ba5286ee91008b3d737949412cb46ddc8579c3261a7931664288a0c657a1b8ac262b50ad15d79932c56d77ca4c56af9a0cb0aab2f54c730accd4961b5cc321e8e887a38e2dd11babe074a1b7723de692877233e0817caf1b81e7f695041ae3b713754df743c75ee062ab60d4ac68561a36c2e6b5e47a154efa2b5a5bdcaaa3055e9ba6a5ef4c97ab9a905a9af5a9454d1a59f2664284a1256ab30940412abaf4bed274261b54462b5cc466218565fd9943d1cb189fbae034d4d441da13a4a379349b823054b4e3abbbcb45e7bef2dddebde8b5ded66d744c4b465a037fb6cc6495eafb7525e97bcae96d8b177954eabd6e31dddeb0e5e19bea694d259a2224a679797746a40689c40845db9d7b143202cdb20d955f25e5a8f7ae18dfebac52afa7a62c54662d74bec5b4627147519c3e8aa91220a298c708a88785f8c1c155a54e1210b24b490e08a1c74b8f06004152040c1c196115841bb30f16284c516464cd860c388d78353d14d0c8c5c17b7680c235c444251038260a485568485110e53e460c40054d10c8a6024a36804482f08cecb124ae603838c7be2065188b085910c8ca2aa688b9c450428f2c2281761792fcc99d19e73ce99d9312a16b7145f2ae59c65bacc502d1ccaa11e75602ada297a55ac783368a1c2c31546618a0083910e8c0e90538415392b84784f94311a6384008963a6e8044462a4f430c5e80da10aa31b3adc0f9c30d229d2c208464ec018b92c460228aa3754c69420892a35481aa308313738616224051438f8d09466c1a484a21bb2307a91109874f18ade0eef8b295cab545483918bc2c888a74807ce0a16231a452a302ac228893382018a28a565c6684ab1684203a31766556482ab62534485d1116e09288c5c13ae073a7c61d4554c1006111064c25062db52849431b454e10409583ba05c6a30a328082ede10ce0ba305f45b92c3ca00142292c80198255550a08329587c40022c288841133e3c1881134db79850a68973ce39e7dc73ce39e79c73ef3b1ecb27e20073391b0e2a8cac35c61a638c31c65af3362505a3c47570f562883a29c8c35ba0fe3fda7eb3f5b122ae9905b87a42f2722d220eae6284ace902cce5b8978083bb2da0e3b76785e6241c00720139f171893b4c78cc5f49517d3b0fbee26e367e5dc78fb7c07be278d45ce7f129c41dabc7b3107708e03100a5c33f262b27253af9b76f4f49e91517e1e9223c8dbd8495ee0a95622f77bb331e8fcdc0db6361ef59c15a5cfb230243a28455e28b9a91b799836ad74cb3145ee11e0b7c800521560d0d7e4a90ee09bf24302b2c8000e014a2ce3b00f0cbc16f49d4793701678c5fa3048c63a373628c3fe006a00e9a02267ab045bb7694bef75e2e41895b3a8de37c7b56548fd9e31f986705fe71795d3afe07cd4578e8b196bbd968a8c12ff95d1e8b852bf488dc8df7127e4ae0ea3dcc1b8371f08b41659f8310420883b69dae416f0c18b87a6701ae5e17cce5c2d7ee48c3c33b74034f3ec09c1621c703031c50e13c81d9b883e8a83a22f047dcf163bb3bd9c49e82e3114df29af02aeabc67e77995669bea81d8853884410885cbae1dfb80e321e46e04a1697cbb3378e20e1e0c0cb1d7b0ad9587d67a93dd7eb4c934435363636f527790be044c027650e726cbc00eae6c322a6fb2ed77f286d3f630a7edcdf599ec6d476a7833e52c0f51e1cfc07107c45b6a984c3970036914523a2bce39e79cbce29cb33d7072a0768c3b608ecb9c938aed11b17989619fd385c159dc2bebac62d4c35e9c21fb75cdecda5b2a1d5b614df64578fac21576ec100575b043ecd9cc8132073ced30cc5a2ce3b1cab3e5d83173aa65a77f0ed4819d7e45a9b5d7329cc23970656d0a8de5a0d9d8afbc822b7be5c395fda954737dc51a8c449895622eee661febb1c2740a63912a5dbe23121e35c3b0306587195816429d152a4cc204d602faa19e794efa49b38703da234056802f7b38cb95b607ce9265b65620f4311e81bd8f31a65d6b0ca4ced1ae35468c361280eb1c8001e03a9c63c2253a92f359422873bef10fb0745a8ab2d47e5a3b6fad95544e7ceb898ea6cd4097e1299873ce8903571888bb65fd4961ac71775280fdbc2e41555170556feb65cd2e2a75e4b5bd8551e64cc2253a9a2f95e6b4a5493fa79db6c612603849c870b9407c4da090d84fd2c32019c8407003c97920f2f035aec0010d2fa4c75a6490c0431b4977b3d1aa998dbc8e3ff23a3ef7f0d5834d602a1a33eea6b5f7ba9cba44471322203eda1f2e8c62eddddedbd5f1723a97bb116dc055fc8e1377b37175bc8d9d1c3c0bb8b901f6f35ac68db7a133bb9f73f6506daecda9887db8d91d52818100c35035d7dd519a9c190238e72e11e7f28900262dbb4e62d7253a82f129ccc106c01d0fe6d27d09c0ec7592521495184b1bdc2d6b0a53f8c86bf9430963bdf34447f110860ca7630e5c6120ee869373e435bc84b9072ae90830dcc49df7e7b59cc1e27592cc95762d32ad2640bb1619254d7f6053145ccd53889f5c89517ce4353dfd735a8f875d8b0c92761abc4447f1f558c5f7b515c7c3e9a09f4d3f067bd7845da223a223e77460ae4a80d2acd6fa9ce6031a61ced9191468f004ae5c6763c0f46bfc8ee8f754880f1fdd0c5371659431c62bef0fbe07df7b6fc77d7763baf483ef1981ab983783f8e52d6b183fe79c0f1e5701658e100b7aba923e6a40e4e5b1392790cc370ec9f774d06408284b601e0200031149556ab0840d59dc10ae6882821964f1c31739a045381973853e1983a535c64a8e0e592d8d708ee9414f31b264920b964a425a018790ad3146d83a8687a9431863c2484189cc0b557010c30f7840854f184dc520030021868a9650a962c30b4d5441c609122f626469420c2b45991852daddd4ae25065267d81263496fb62506927eb7485a63a61083456fd0f449bfbb777bd345125cc24c5102871b681037bc20c2862e3bd8420318e4f0601bb00d3c928c6572c5287291469c9b6a411454ec80c40e869842c30e55bc1082881c6011a28c8f94456859a3a50d5a4a99ba36565ab488f1222ec1de10efd58b81aa6286d05bce082a9812d41fc4481103859896944e88d5116f8f861bdbb5c444e9c78262a4f8e0ec0b2d8dc43869d70a23a5379c9e615ad038af3bec039a34a844ad9595492c19aa11010000003314000020140a8644229150381a11e6a4ee0114000c96a4446a4896c7a21c46511003410020c6104208000018620000c62090d20112f025231cd17cca3aec304d174d2ba2afe627f9d8814b87e87518220d6d71901109b4384a421c6dd529d4ef8ff81119a839b9aca92d6681ae4a99e27cc30ddd07838696aeb53aa9bea4bf0f8d4fe9071d29d2456351f677e05ed34af4ebd82507eee328443a6d0e32893bcabea6350d297b0e22acd2f0b424cd3d1ae0262310698083908548839b10624759d6694c14b2e320c23a0d4f0afaedd370cb041d74d5811074698811a8e018218e76b6a8001da77da3d10f32f201eda774d0912b0e7be390d6f14a8d49718f43adbb688cee44658731e4e0d6d1b5a35e47a5eb20718576a6a511edd3e2904ccc31d29af674799f83afba1ca1f46404451a10254450d0b078298b9286c5c9084a1ad1b2220e1a8f9b3149cd75a4b3a0b29dd8efb2042af4e7448f1fb650c56797e090805a59d24da1bc7db500c82c6f8066cefd445d982abf889973d132b8225ec80bf93aca3ebf9216a732ad837b0d57b8cdab092ea3512eb452702adeb14d3e92c25a59cbc3b15b79ed95534740a2d6c5cf0fd3f5c625c85650c26ac26321114250a02522a9ec188dc5d2c820077a7cdf816b0e7b3ba477bcdec1f53b0e294693510ee8a0371de7efa67deb42083eb43e241f738ce4a22d6b42443f8d8f48c78e6e72b4fe0e92e8b4cf51d861226d0e2281e3550eaecf31428c26a31cd0c14d87ee0efb3b1e4d07092bdad310b5eb28e4551a13d24e2d874410475d75945597f6456464251a22252b3b8a144f3b5354e00e68bfd3f896f4c1f9300707c84913f7697313436cda850f2e867ba205247d13dee4d9a6903db84e46f2a1fd2df3a0e3345cb4674946f6a7fd940e3a72d5b42efae7d8050efa1cd276b8ea586f0e1e8bc390ee34f0598a70a4f11921c70ef473a3010b32829fe6a37c8083ba769c769ff64bc9c80d1a6525371c490d66e1cb6c2fc835b18186fac2274773e190c43e9a2da57db08d8f7c98b2d21f8faf7a2b873601a79014a09d1599011c64bfa5f5218fe668ee1c44ead36c49316b937649b111c7a3e9b091abb4271177e778ddc1753ba62d46035969fe028303ef86b68941447fbaa1b36464a45dda9c2241c7eb71607d1c24251e192973bb865cdb2fe84df4ef5cef3cce4c41e75b36460602738d97c4ea3a85cd032db32f3c6249622d02f671709f5914db97fbd188ed2be30b39dd30d8127e19561accd796fd61d89da5956109e1c2049087a2be6e2fa7118da7c16162b35321e3098a8883017132ebbd3e123cc0368ac73c90189e3290e42cfd29268e0f321f81846fdc0f897ec5d270a209b6640939b3f29829e13a1a520a73d92289dd5c9f804fa085211a66da6e56956243931d6e3dab0415110815f4d7a354aaa4d9dfb7c530fc378df9cc1b6b3849514abbdc93e89a5467d6c59fdb837414718d4853da856b78c1178a6ee63a59ea7b7c0c9a8c650caeef34f3e9618608f5ecad291ff8b74e7f36fc5a9040c5df3fb0be17e1234f6d2dbe8aa49d4e7dce71ab2fbe9e87b82fe4c76b35f09d4cb9f560ba47d439bfe30446f0d0aa987fa5374b9b23c8d0e32d74b343ccc7be61fcea1816d0b823a5a2ae748501ba27df171f1c80d568a07bbd6110a7206c0bf8e9601e22ce61201d9d037405728561aa83e25214a69ea0d107b20920d2b16b1379c4ee6c69bc0bbf944f23809b8feb4efea5b755a18d8033398e0c6762aeec0561ba23a6df6eb84a98400abfb33809567b239b314b52cc72af8c139382a957b840c78c60e0f92ab7f3713ded39c9a0edd447d860deb07d036a21eab127b6bf3637b52961409d2746e768bd69e08028057ffa405f000e75e97ca4c61a9b68cbb756aee060f7357bb2aaaaaca20000b411603600bcdb3319c8ee4152da1944631297ab3ab918df0d62a4a5cdb7b1773539394026e68dca1e357135793241a12731918f537c6d98147011d2c3d53b9166220b9c98b87135cae02e17db9e04fbaa24e46c4d84a85b44f29a8ec2576bb5f73b2ffa80cd3e398156052c11ca6918e6d85cbad1d9d1d7d01bc3e9f18cd5fa3939bcb06586797614b34799eada711704da6c807061ba529a2d34d5e6caeeeb97f6d53ea8636704e0133dc180a0f41f8250905177923053ebcf1402ef683bdc33b2d7c609fa15b69e34679f4471534cbc1472d496ba964dcc9cc4450115453a5fae07fabff9224d41c6bebe5e13041f0d31723541166a69769de6f75370ddb30e9edcebcbb33c4b5086730304edcb22b557265350a3de5625a992706fa1811a491238b741a6f08b7ba653f8c9db7ff162cd9770d039efe099faa5e7e7afe600c8386cf38b663e9380d9c9008288c77f297ab612df9deb74d9a3e14caa0f44affb753f9b28e0836c24efc0792d34fbcc0092f2dcd6b10e003de3d2176bee9f94a481f811fac2da21c978e37f9e67189925cd4041db1bb4e0fc74d3c7a2e9347536b4c8d29c5cd5571e5e1aa7552d1eb5e00f35cbb300eeac3a40812200f554ce3d81b59c6d55e29c53b4bc23432af5c10fa053c8589ccea38483cbc59e98ade4ecb26000f5e30c09b80824b37a037886f3acdab5004c3b55e5f70643e20a06b6f2f844f9fa3405918f0e6e4ecf24c83a477858f64c0dde4fc9c011b3581ad75ae5f5700463f43844997e680e1386348ab6e2bfc68ae577ac988080c0b7c4d711f7b421d2e1cfc4d4f3b98164608098a37b3c965a0baa5a07d4b8810af1bea2edf9b7b08ac8e9a0bf5f5ec82175cf6943073a801c2eb51bdb0d9b82f82bebde1fc7d66d363ad91f3e5a70fbff4176812dc7808c990ce210cbae71ee166f8271263a2e8e3d7d43f6e349643fb91b010629412262160245124a426782b3ce17514ebc1b711dfa25893d66ef9579ebbacc157d2b671f7ab135ad92c8797bb9563a3d73f6daa671ff9c045f4fd9259dc82d8f929e63d4520dc2f0f8177787c64ddffe2e8feaf2727e4f28c233055ac469b1f10bc2d05b6887b3e29326e0ed31c8a5d4184f5b2330360ac18b424620c6dd9e66373e379e6a8701aa3d076fee29aff73cdcbe0ce0a54216fc397bab166b499cc81620c35bfe7249a0212ee05061da1f1cb9bba0f2376b4cd86441e87a8ff99c27c865225fa0748a3f42ea848a0c163c65a5c22229f230c196780a701d0a6485fca18e6642930ebbeb6bea7428710a9d7036428570773031bdfa8382e5dd56b825a6ce752687d256d739077ca0fd54819e3557da5f9b219def147fbd6841969898161be0c6dbe033c6fab206e27c08efd0302d68777b4de616adc9d50f4e7dc3fb43ca2182fb47607a6b461313f68fed5de0efcf288b53a71fc5f6977b257771398fb2d546fadc25959eb304b4d3aee6e7aef90f66453e1355b7635e52bb81d3ec5be56acc7357d2d8ad26439fc2cc430601b6fb470e2a8dc82e134c040ba1ae84b91576c0a6707d4cd9e42e7aa5ebd53380a8bcc47c808a270e54b88b9e9b9800d54489a42edc95edc06acd894aa11a8c0a6eb8aff01500538ac453356e1580f93d07f9a15cffd2a69b3f17224f8c77d47e92d9a564117c2574cbd08f73720116f40092bdb326087dede370456a7a137231716bce008a580bc3f687e2623f4ef0c11b4c1271ea831a2683a2af8d2d6fb23cbae533a33348c160f7dec8db61f3c0e6489795c970e4c548741558b40b532dde65f44681736a442a0f03d19034f0dd0125b1f8786dc4f1b144a71e0abdf04c5bd77c9a052d947032752280758965183e1a243c0b8f498c032a108950768cb0f95230dab3086fbdd9dad518b73acab452b5d0106273b4faf02cd43c5df9bf11ea2bbcf2ac9670d807c3eebbb0f73360e54ccbb269808b5051ca4993df434d768f88cb36bd3304f31f3b9677c264a6da17b55847cc9d0ebf18d9fe2a9aebe51867977f98f83d495f530457afc25a64e556317583b05228d67b7607717ac193ca92f0bed673d4f489a7118b4176d94b6564d35be28abda91e6fd4edfa9227146c830675e1f9c9641b451aa8857762b7b77499feb4c3a3692a20bd80d70102e0670afc174cf98554896a4b03ae5531e9334a71537f18364fa6f09375d9b3a428bbe4aa9444f9bffcd6219ab7c3247eed8f1dbc57fd5a813427a555b8905f193c91339352700958e9f244d9f92717bf9c12cba2f7f7344c725a8c820fd003cf2f4029910d8c20607fb5ef89cee87c64185da3694df063547021fe9ad7065a5238cb15cb5e92483a23dc3876b09a08c23ee995a2515e6dce56eccbb79567d127c97c98e87c052c2afc3326cac0c36f67dd07e6e2e1afa7df2a3fbe33903290e6dfb3e7af9cc5ffebf17542ad0189578566806a621815575f1fcaa79df4b8aa0f9ca6999e595f9e3d3726739dbfedc42085df84bbb8a9428201248d618f4103bdbe378853fdf98cad13f6cc84da980c218454177b78d17fe4a5967ce0812234298181f64a5f27d4c67042705b031caa5eaecb4cd933f4080bb0a81329fe20633b44375b971eb23d16df0670b67aa341973f49d77d0360c293badfc27a93b0a581e3ebe7f5003357379f922ab032f1abc40b13bce7117f3d7b7639e966793d369d84ce7e6fdf56e21270c9809141226ca614e827237eba3f6958aafeba242371f14cbcfeda4fcb7d9f1a25431d0f842052f79668436b563358c9fbe0e1afd6164930ab4f86d6eb804bc7f1e23addb9b961bbf81c2c8772cbb485f5e1d114a17315200db1d4812bd68071bf16918ccd43100e51a057530d0af1653c36bccb07ec3a84f75af8cc074e814ba8a918446ed460381532584add66c514c7313ecd20b1ca0131999e6364076da0daf20eb0b6a44f1d73fc6295826948dce730fb70e8a90efdd800a3991c286af051c34ad7f68def9d7599bdf3768230482912223cf858c55bfac7d1ceee28634fe15b93b7173e11dbab6df3dceccad7e6015ff3626f4c5de857d042395ff0b256a181f8b68cf5fc81089bb371e928054da8ea422d9d882291b17159a5243407296aea1c7ca3090ac5621ca69b623fc6d5b72b10bbec913a8d479fba9378a411acba5d61b3007bc32713b129009f62d8a8b1c1578a1f53ed91c7b72b3c98c492983b60c481133a1ce46760c473717f4d8736c9bb152661d1d2913ebaf757855f405962dad3fc2c8dcfee2c2420543ecb05d30f22baf984ad5c119e71634120e79d6c46b3e454e6f8f3ce1ecdffcd5eef54094c94284b7eff6a118ecd30c5382c9086dd56b018e10b3a3d230b28426f5b8d9c696a3d0b0638e01996c0be9c32a2de999d15e47c8f963ed7076717e713fba7a071e2b50c953772eb9ad10559c1d1e56fdd59f16074989b0a2843c9ec0e4b87035e1e8330c6b79698f9784f4ba013f96ad5de2e961a6cd651d740c84a2667d9bacc0a4e451d903e412631f43bb3c432a275c731b1f21a4da75e5a2ebfc245618f0912414bb40b55696ea80e6031bdd42176982f38de8bf64b05cf4055495af246adddc38638e4468849d569b9841570a76deed0ccf7960d6f254c87d1e62767d8da3c4c0520875a3ca52b84a23db139af6302a3a76ffbe365bb610ceb3f4b89f3165730aeb1f6a74e62b9e7cf10e5f07b0e184e31be417a49ca5bbe8fc4c2ba720902ac032de73e4c3d5869bebd6ebb980cb5941ca7486a8565f85a56cc6b2ff12c7fe8cbc58929c99460aa401fbc693b5af71547d193dfbd52e78d60abb28128070657a1433fa0d31039a610d9e66d1b90757ecede92ed5c324c65196146289340ee74382d08eb86764e20712dab2d17c022b0cdbbec10debc0f0e9f0285f4b1410c11756d6113cd269990ccbbd4216a9908501112525bffb8e6904975b5166ae9211d05340c0e1c75188b5e1ae53830d7c4a09f826e1112e594dd616456280e89275f00ecfb29c129ea8413b77db7dfbd226a8b2ebbc80f4d9446f0532bec693a20bb6243bd17a3d3bdf76faf10b9a4a368d4a1bd1d6cd9e3e9bea9be820aa2681f748687d35682332c419a873d257104474d04ff45cb350f9256c0b6fe64fe6728a2622985987cba8470d42e321ab64de3c73941df17606daf837af3bb6e174e0c6cb90ddd3bc744dc0151fe091e77d09551aca295028f069dcb6e210d3126148bed21392556ac1c5a96fde6c61c7eb4390417ce66ec56a79595cce0706ebe54245bbfdee4b439395c38479582ef696a112cc7aa6055f283f4c7b2254fc7e08dba8a8b68eaa6f209d76da541f85f122ed295de2b1893a574f12792666b2e01053af87210af24974faf63017d040c60f65f301f59251344bab2ad4e8f52a51a59684835275d96758207cae1d532978746f4db2a0d90c16bd57506de8dcb82e9d70aaf680b1607f069da73a53613c06f7daba304a984d624be4a124d12ca04bfa13bf1016e49de7e475bef8f9e34c90387e1f477bd7f87b28e58aed7c5051847c379f9fe7c7d1385824b2d85f952c41ae8594be6ff33542c5fcec0283c5b665842d4439ba238b3248b8d3c717e5a5392021fd56cb7fd6eb118115693090f32fb98f627e82d20ce6b87c3712c1d8b6b016709e02d09438a9876259fb3953b2eafe52b08142b130fa5057ea5499d64bc932622d2eb1927393ba4a032236823fce7b8c024919e1734800a3291e9fb52aa8f48677addedc086ef10c3ea547710b8f71bdc7e3936cad75279d5a9fc8ff28bb1b692172ed89008e82faabaf59ef0cf379e74f4468db3fd59c726baad5493cea32ff1167d45ca0a0eae7ba9a9cb58c1e046651d3471a325ae6f05d509dca3a3fc93c8981c758dc3da3110a3455eabb275a9f9791797e0860741cd2a167971e4e7c16cd8d39a3c42349406f936afa94d669ce0000ddbb5d57a0a51b0d8c2cba92ba16b7ec1ae6b96962774b2afdea4721f1e4536d13f2124a13e29917cc06b25745ba7542a5c758e01e02c5eb4e478cb27ad504476827559fbe74ced118e282bfe6718349004233a00d564648ca56dfa3f7e7afb6a425c64d5a5080f95b704f6bc56f2da67a1bde0c03aa49177d5e4451ff55ad165519ca518c83b09736747a7bcdf72759bd027d6c02409bce0e62a66738d24ecb8e195e097bce8a299bd3673d58d584b7cde0f5a89d8bd9072a9b0f2414747e5efa43aa843e2e3267e8fe0ce9573dd2b3b6520407cd508ee08b2f4e444cc496cf11ff4af2dbf9c05495fffa80963b15438f7d8c4f7f337e7241e38a960a858907f879e8d73b2bdff6ccba6377738f11271aa93413dd79bce3ce08a87b464fdb5e927034f9f76138cbfda1420b0886044ad97a5be1742acc25890ba2818dacf20dfea0ae2b6c96a176879ddc9c191848508884d2c8a900f294d519c9c67d3eac01dfdc9b460671736fa0dfad9347b9216ebd385750d860e0ca57798782d24a563f63104eaa0307224a4f7f4011d421703812c05a894c7c10140b88008c04b8bc91c3e3f291003091e8134a10987402c8d99330d11e05200b37c5035260903f63408c4945f41f8b85b2fee6382801146f9737f48054794d63ff890e221f36dd4fbc8f11e3dbe2894e13ebf3bbd38af07c5d878039281a0d7605094cfc53bfda4d85582fb15c3ce11d4b3b2b239eb70004ca99f7b7b273c1fbca84a85cf0c72db799ef56e20350a401272fc5b778912e65f9954f50b37ab38ece4fe2b4e5bfaba78345524d97d1ec809a0440582025a681b4607247b33b9e904b3b2402a0f409715674c110749e5314da4cb9f5a4ee8e820e5965df00d1957d7764a11ec8e5adad2b89f1efbab3e4f5efba388ae95670c897a10a23f9069a8c4e578ea118ae5b8b6ddeb6dc4e412a8b43c338532e1f703d336c08295224a4b7c2459ef0a10bcb187b0115f8febd85ba4abf5ccccea00aaac9e68956fd9fe0ce593c7602aed6f61dbb2ea96780f87603411f66ee13974b3ccfc2e3b502395df4be6bec829b0baaf35f66f53f2ea51987a7ed760b1866431926d9c8cf694aa6bf505330f3cbe773ec90c3ad86031c0238f01e75a125649b1513cf0b6be2d36698ae291ce57045bf0de0371a90628f98ab98b01943129f2775fb0b05b016e0cea20e7187bf497b6e4ab3e35eb7b56f144338488b1cde2adc46f856948542a350125b3bf42e33393657811c3d6fb7b271c34dde65bda8896514bcc7310116c4dcfb5315f689b87ad2078c39bc41487e8855df6cf09b42dd40707827d18afcc29c607ac2f573fb933a4e43b8d0a592ff20d63207bdd3aaf0ca9318ae6578b970915a452d4df05e119a788879aa813ab5a4734ef24d07b0deb0370cf5ba5f6bfec3ac9c4e2321319463c6fb76ed5e70486a86b26ae416e0ad356c0c1eaf56211be5e00118efe3a03a7b753c8a66f2f34dec8784c9d5ecc1759efe61455f8d7dc1a5a88dacabc735f203e157509f9984f1e259d2f6ba150198cf06aa99df89fde90faff8814437541f1c3422d48f962743386102c56bdfcf18f1e39675500beb8a2e2f181de95baeeac00bc74f945cfa6ffbf5b69fd8c8a7d9dbf90e1ff5d6f4a28be00c9fbc7894717523960b0284aad3b22d0c3f2971870d852b32cdfa51d5b1846c124d824ebfca3300383bf2e053a12e8a63b1001af4344f18535c2510858fe6d2db712098ace7b1ec697230992744f055757e2784dd1e9ab6b55bf2137979a1cb9654eeaf966a788d3cd8eeb9d0f695dd5f4aab5872a2c63d3ae2db0d948ba218893f29391ea99c60b16289d9648722fc5bad38bb236a67c8f32e6f757218b8f53eb40b8304d9b54650dcdd14d07746699bfd127c0d0df715b93b80b8658160ce7bd0802db9a0b0f511ce335d9d2cf10a912a084714c4394282a390ebd3bd7f329c08b0fe88de15798928bd400b77fd8a3604b34cbd7013f5f2bc04f951aa5a99884314405cc7b86d3c9c97b6487429776461d321970e572910ba81f7c7d2ce959e77be42979539a4a1708a40caeaec04bb5fa57d92193050debe2914364346fbbd0bf997066e3ee6a33cdc959068b8787cd8dc168f416a56b3393d1986546f55f74545cf96290357f4358b79f146e722034b14dfb32a4853961989492076b569cd71e177a6ba8963edb38a1c283a03fd0aa80f671d728dbfa35b77475cd59370cf802820433a366739610cf7f6b51f88cc9cdc399e56b8afbda0111625179b097bc963f0c2d353b5018320088d0f977d2b4420a29a4802c7ab70160ed1b9d325cec2e117c468b4abbe2499985612b3fa4af56000d73899b2e4ea35fd3cd59edcd0ed3437284fb85f188d437840e9d7fd138ed301390b21e097f5265d35d56751c482be650dda09c873904349b6ecc99aed4b56068fffd282d905521a66b5f3596ec7fb335c5da2799292a777f634650a0e37abea33df4d351c891696f882ee775f45c4ecf38d5b0c50a3ab1340eb3b3e6083c1316a118cdf79f8b200c457e48c3adf94412bbbabd4af89e88b50f1d555bfe69f12e65606526b6b48cc754e29de4c39c1e06a2972045e5f8d3081608cb89467d936610fc8d307a5b0ec3d04ad80b9b8c4ecc2d023ef2ee0925e36e6acb7a9177b558a3b800fc02d737043332348b9b6300b7f7900e8c6e43d49c6145dd3b8c2e61e49973f0679b62168b70ca6fd602af53bfad76a586aa7d8911f5231f4410e52d02e027995f46b9185dc039a2ba3c13148a5ddc01bf3fc23999d7192adb5fab1adb50dccd68f6482d3047461d4f946ea89743f36a5099b3b0bf7ec9828a37b4dae579f435747c21f7b94633fd7058453472ef80ca5b72e6230a6d132ab73703a7c464f5175538ecf94005979afdc218726558d86eb298ac4999bc00958ae29be35ff7b71903fb68907a8476cdc5b6a06cf9b180dbea925c0b3206593ff4cecba74afed4c270f8b1efda36f82986aedf13edcc3207492b92a0a1896f0a9a26d3372be6ae08d2762a5a2955ebef234b747be20201ab436025fd953eceec51fa1ec107ecd0468c3eb28445db598d8943a426bdcc821a7abbc8fc111a1e9f182cb4785a09330ee72a9c301f2b0ad96a313867fcc1234b4928411153f9a4aa2437bcc020f84a98930abce6ad554ed84ceccd0529d2b84eef63bdc50a28d0ee5788df460685c432e14f5b93026fe0d4e928bb75274ded636f60117d1819f29d646028aee3543f74af04bff946be69fa0bbad0a513a40c4b7b194f4b3e64583676f1b2c421e25946af9a4ec48a1b4c40106a7758577e1a929353ecfd44699f871b96ada12c384652ad9b319c9b3185146b44e149684f59f5a32424838463d435049eb04c571238eb7eab9418cea7ed086d667286405b175a41a480a1ad0586e3160c5fa1e39700a531ffb4e64dbbf79768966232a2b33cbc1198a09f5f75b41026a7562d747cfdb1c1d789bfebb66f09e61bb68d849cf32687be017945aedd9ac5b90c152baf84a7e87a454fed76f677bbb6949305dea35ba267b1deb0e74545792c9b10199038dd34b4bf2f78315def239c8174439f909fb066b28e5284fbef9c10063c9283a9ae85d0318466e109acd06bfe22c39a2dd486bdad6bbbe3a0072afd8f1d44c646a6ffebe26f175ad65be69ec255f87d03123a564a6097872d7c45a096dfcb1197408e5484444107d0cb7f0b691fe22caffb053de4b2afc7e01b77e4414ce2bd286b44d78cf0d9d5715e0d8fb7e58ea1329e0fa481398f87189206071af5ec54e5f604b99fc43272e28a2b4d0ff063157a84a49121e004fb4c952a3a0160ec0e46f7ad91d3bf189542e425251717041b9ee932a6a14df38a526407c06aec5b89d75db153a2ae575b0750a6e370992d4a547ec07e1348f0e55f7e7fc3b5748fe4d2374cc7dd856cdfe637e58a251233e5f4ebad279115fca9a59d9ba9f64335a3a326bbead8ca8c204ea60ee764270e05045828ce4ed001512ca982ced54c8b53677161cced4db004add00600f736dd64f9e2396def65b2ed4fe51476649e67e1f3c8ca625eef6d697c948b41f038d1352a7ac616187615eeab12e3a79064fb3fc1cf939e6a3bcc7445c7d2855f66fd1a307d00d9b93021698807b549b00760462dd4bfb08a05685e9ad6371d9e167436e7f4d182fb47a751fb4f284ec8044dc4d7043a5f058278515ba8043197ba5511566039db0d1abd0259c1a5370b5c500a5695f8136fb84105495d8200f64f1c946424a21b99c8d0c042b43c2b22b129f15cb96d073fc508a47517a366ae62d657bca5135921b11c1545c5f507299129788041d3082667ff9924768b23ee35cf02cec4431e7b6299158f6bae9f09a6e67a2f9f9e03ea6eb893c34410a7389c5a9184be4b3753a9828fb9c4a4d23d84491612ee8fdd9a36e3e8be967c35899dd6b2833c9beb70ccf4e30d23226c0a26ca0583811eab6cfee702bd28189b7b85527cdd48a4def95f39d0ba4c92420795e993e4f56468c7336362176692ca94df00ac2cd523f4dde735f615596f5e8c0cda37f731d9b2407b509afd58407bb4a03e7a101e19748f07c27fc41ec323a488c582cdc6482a9c4ad73b6982c8c3942d2ed450a4dbbfca89689605ec22a50cfd192a8f2f555840d02a4ca6d167381c8e6d184ced95d4c3f38fd1e6f6d7fb835f1590be8547930865acb9ef237f5ff9dfc27c76e20e3f6d87e7cc65d1f75e4d4fe8e0cc5f5ccb0e62021bec245d18a652478b516ec405275bf48edd854d1e2e3595f650e0084aa149d9f91b4955dca90030429b5988a9a41b39fdbdb8f883dd64112d97111888e81d683c09a950ab1ac4778bc90fc88956c7ab07409b0aebd5f980e762c6aa86b478de986a05ea4eb58f44934f165b5e419c946577b2e2d647f5bf0dbef8c456ccc53958d34aed34e1bd9a11a4e7e2256dd86327fa6f29cd590754420ea328b94a5d248359cc6c851e63aec1e21c7ffc28a172d82be6ead7d9256e23cf8a0d9a8bf31c7d02bf854c99a6fc07c1129bb81a37ae483331d95db167888dadbb27018fa9b144e6ec9d0f063a0f6d08d9455a6a3bb01b5d1387fea1f42ec97cd7144cbb0f570b2ee78852638f1f9b2143608a6f7927afe2bd7e6feeb22c0d0d135531176eb40f60d4015a2ba811af000d6c8595d409daa9100c5120e2e0b0281cf8627820204574492be191b1ad9db24dfa0449d4192c272789a2500b83c515bd2576f1f07aa5c1b916a78c6e8686bf8f555dbcbf3508905c08e3179d18fbb2ee6d52e6900056c0b52e2f864b544611470c6a81756ffefbeb7c9d35b7442c85116e7b883948ded276d81fabea9f4d54c86451be502a21d885a4a9156ff75020d83c78566ad9a0848c1e2656ee97038da58db9955842e60cda322a689915a6f47684d8c0a093f4c81f1bd044021c36f031a8d4e1c2e79dd4d57921f23aaff771ba9efb3f4397dda1fb15929a5d92fb9da30c935babc78d58f57fc117a8a3b5796ede2cab78e081a6ed319eca3a4a6b87e618463d437e7825c5a1ca8496c550e6863fbe4a093de72557246496908582d795861a777683c669c3b94505e3bf208a40f933cd96a60ead4eff8fc22501ed5cebe0ed3e91952bfd35678ff6007ebb5fef7183cefe3da8d8ba0ac26ef8c7c73133279671f47b38b2ed5ac6ba45bd16fddd5861324e498dbcc4f198a45ede1414683b233adae93cba355e75ed82ffd90b3b785075eb1aaa76689040b97b17b26830ca00b7c8745200b10abf4e1289b1c07b3b51aa5f03714cf0c3069502ca5404c5444fccafe365cee81b13f479e886299bbdd0b4b364db2f6e240f122708be9ec1209b85c4cecae61ecde190da835f795cd6296e3a624b29649d7c00ee181bb7fe126d89d984c5963c3bbbd60676da50be5db2b85b2b913eac8b1c0689b02b1cdd4dc54ec9fcca0c1cfe4621e366b5512f836ecb750fb6c782a9addf083334759371aa5e9355bfb601235791be596eaf22cfa848db2803068b12635d3c6225b944b209207b4cf398ed1059130065fdc29eb504d0e293110a11f70eddc8a3a28e6ad6184889745282237b011b9bb40ca18cf7b5ecb02e7c2c69e6d44fcf0f4b62ca353270a838b7155b42f257ce85c212250589aac320621c9771863f50c5a6cf5be24647f8fd23ed0a4c5dcc25d65903a8dad0920d594ff48f9a202f7329be4f518092c121985079f42900af6494617476c09437d57440458126b1da94d78e1c036f952987b01403879e444a2354ccc526c193bbe11461ac7c83a4c9e5fd11a8e17460f46baaba67a69e58eba555d6b43229e36b1506adc0f3288e72eee780bccd5996ca1bbed070614d6571797bd8dee835e53d0dd55465a95a536bd7673e1d0f662af232622ffecc9b8fefa4439c9b6c54c9c0c24749ba1b017bc15b54b71f37291c7264813e368faf07af42dc0bd9375a03cfc8d73545623ae3299b823d4cbcf52be973baf62768a6ea1f271c09d44a6ec94d6e196072b38fac80f9d5fcde1efb08b6a6a694dfac4d0691dce3a428f9cbd21b6759228b3cdebdb9810d2d7766d44218ca53bfb1e3a36cc09dae3ccd9ccb815f1aeaee22e1fa5dec0942ac0c3a0d9c28394c07c62744eb9722ee89b2513315fa79491052adb1414ad13661abcd71af0aaf558cabc70873e7127b041dc6edc9d82631b5545851a3ccff124f359c15512febb237f46f949b2bf82b3f0af316ac26abaa5fa07abeee269cc05450c9a2575b8e2df0a3f0799cb935ed4f38cbe7ea1ef9eae351bc7ea9a172c9432c45c24ad554808b52efa897a207860229908c8730964240c6c8b195699786565eaef7e9975e53320e330e6927555bf0ca61d54a51990912e0d817a58989e03417ac967d7e6bf958d6ff94b5612ffc3eded6d5e1f2d2e7a2e13144101c6ee5b6208e810f047e766e3beef0d18c1c7ed8ef49695814b575527409c27eac8f3bd43e991d1060baa30f3157ffea00d2e58dee08cfceec268b52f0c08e39b975445530cddd2f2ad34e940ac4f494923c8e1ffa54dcc8df8987c2bedabc92ecfbe0e31c8659c0ccdd09cf6779e9bf290e6b048972b945c89c6c57ee6673289ab39a3939b68751c063c922ed378f6d7c3e7986e584b7f32ccf34d3e26f8b471c615715eae8a6655cb174c446913be1d87b83bb6ad203c5308cb66d22e619bf24fa74ac2353fc2225edebc54ce54f55212cb71f511492a1b31621fd9efe6ca9e2ea95c2d103249534f892b5ea91a18de0bd9fbc563e7e12263719eaf9d734ac1134302c050aa5c56004081b8244f8996788a790b2b3ba7579bd5a913e0569ed0371ea0ae897e0a608e4def86854c750ec1577ac6a38787b079f5adcf23271e98520f9e144060adde7ebf732cd2fe06af8f7cf14180bc80492ffc982a96c84da9193352aa475724cbe5d24787582d5d2e22588ad8d1e7f6d58b29ca2fdab9e39a8683ffa0ef188b9ace1fecbfb01207164442353eb6bd02ba43a0535d9fe64f42f7b0206240d76fe37557b910ee0f651d3961601d2119b4ac0941244a8bafdc5f18ef510a0bb244be532038fc6b3adbdae578b767941d31c9e18c5caf344e348ed6fcdd0cad169a50f5bba5b613d7b55fcef699623794814c8246f7c77f21e418edd75dd7d273b718bb8960b1993b18a7c782894ee44599c047233bafd48b87294cdc28ec889519dc55199f5a8cdba289cc5a89f85a89d751d9d3317c5ad73e39f8df6d89c9e724ebec9f54655f7f4fc80d28ad615d32202616981688d65eff59d7fb989de139b9a06045ceb5e52be773c098e361446e18118383c67fefec0565f941a973ad1c4d249b6d1173a2c93594372ec33a44e11b1a94e72f19bb56c35443e032ad055b3702a40e3eb995f0325a79e9b7bb816b0da7fb77fb1cf16f6b1a2676f85bef454588df0618029774fbb78c3a73ab5c3d300b6b6fd1aa9bcf9aa1650547fe8dc7c4137e2171366c455617bef9af20de25c264d66c6ffbbabd505418c114ca9717de3694a3eaa6a6192dc02b165fe4376ed6bcb89aaeb5d895843e9754a08aba315ae48a4891b2d4fd44798a63a79ce5a7d97f0675307e4f49b88e14fc6ab7314694c486eb544ee2a098e7a4bfb6f48f48fbdc057700f97918439541b0ff07702cc5315ffd83b312451236c3bf30f590eaa34c2e2ffa5944a923cbddd26d18664f840131382330a45effbce1bcce53c34f5cf11658a8ee951dc92013cb35f110be3cbfe869ad331febc79058587f3531f7293bed1538cab1e10653f2e9c9e40398ed302d22db0d49991698669cce7360fcf5ca5ce3cef00c994b95d45d97d9222f1eba1457924253df0dd8973a56b234ed5c5f95d28907f9793062980bd5e11b3dbf1357c388b5b2f653e3f5efc01c1dc8bcb9479f1649f0645cc62ca70df6b3006b617f33b77b20f74086312d8a644182a2885fcbe4bbb9983ccd3712c2740252799cdd0fa19d7ba9ac71e61f8e6ea16cffd4b088a5008a9d9809b24436a724a3ad32ade98c8475b674be355c17d191a5a389e331238117bd48e4b281e1c4333d520f687335074fedf218216315aa80485724c542c315bb450c5f89cc0d5e238a96d44d49a3683875c535bbe27550dded8209b866964cd83a2126a6018868109da909060a5eb34fa7e68596878af9a2284fc5c187713683cca8a664d7be0729f469bba9f216b82c69bf89d0a86245e6525deef9177a3dea919df1994a3c68c5ac6f234650ea79ae89f949a4eff2602a3fbc1695823df60a8bd449e58b33372d6009a4e5533b097eb98a8bf832430bf912c0109c20ae2c80d22ef5b3492a03dfa508bd02e364f22919e93a4e679e953f662e156883147255384dc628ed6190d110bca3ee6ff43b9315f3df503697ce195a986d9417450eee553ae3c3ec70a368c5964c79ba281b65e37c3bfd1f485eead1f47de46819060e57a8cfe4586232418acd42b855e3444c95b06134bb87002e41d5c106bd29c1882ecfe52a94689218413e410092cdc6fa725c7194adfaa2c5eb61127c9e05944d9e309c8bbd8267a0b2e811be1d2b1af914ec128cb08a2aa07f5b25777238ff89aaf00f1b13ff3232c24c2dfcd1577e264eb6bb911f81e4a32250d9d6c14c71e28c31ec20b75c58e5a63151b8a4213621bb94a6938bbd55423e277b8b9b35a5ebc3939b73aa7df57c02def32bb590e615bfa144103ed78ef54f215a667a4d8800c0e2a7b11acb2a63e3f2c088a468238dd70e007733b61bf9ed3139368d39afd0e6006422f73749bc60e3318e0913043dd115462a478b7e5febaad7d5328cfb941529f0f5d9de462f4f5542a76e39b40efc605ce8612b5cb57a621025db5a44585b73e2564cdaa6b72ce02b684118351e3767bf9bf03612b2cc5a3a54d3483b8f184b081e48144370761c196e735f7ef0a5943fc32fbb52fafaad4a55fda54a71c1175492abaaa32479cd7ab1f1044b05b79f5e8d597209877953dc35db4849383bcbf1646ec0384f78eae1917bfaa10c61ef6f1073b6e858d1f0a44dc7235b6c3c61004e7939f1fc3135861e1386838b2557f9bde39e7964016dc89069a9594f3e959a4de6e6d202c25a923394f96cc2a0597b4d1167cfa9609597eb9cd3717c56f850cabf1d370322a2088763cff3a51a82d66f9d05788f2df9fbf2d41d3aa3f1cb6f073602bdd8c80e7b53a7294dd27b583fdfdf30d40b289f3bc5866cbb4e05cb20f5a71f1da1ec77fc435383c2098b4e32d865fd774ef81a57a5ee997672df4ab825a15916a4a5a7580e7b871d54b1c1c243a8a536dcbf90b8fd66a297d208f01fd757315ea7777e56cff263a4f79a8de73b0d708f92ba0e9cae5ecfc07321ba1f5920161e0ba2f2456eeda2b496fe7ab29e28a19819bb39394d183136195ecbb93550b74c3614f6c8b344a9ae74853c189d31a340886eef9ad7b472c423238ca6e93f81a7e152da2a92e0068154c1f00da8b71524002dc751a2dd523e5cfef51f58f390c4ba58ae1d7176c88c9d31bf650369da5c183f92f00dd3371844fc060a050d79357ee3f127a195772c6776d116790d67070609b333a41a74d9de72a754896eeaea8565484b994aceb47113fe74c6c6942a3dab3b6374688fe50c0ad2d3ef37ca41d774301780c6cbe458889488743102f28fa4b16088cc67814c6c6c573156a123be172138498f8595da004b9fbc6bdffb153da25e9317a977ec44e9fd9760677759dc5582593374be299d4176f0185caafb866d32bd40a888c4ca4d97288bf9e9e5b93542fc66d99229e405e4f7b9213c201f44feb4e564e0b5d0e724bf0007af94249ee32657059a41523c586920817d19f636dfc926025d642aacfec3de4c317f583e4d65ad11185c54dff32dbae0970761ab5d45de1da1455765fe15f65c02e78e7597c392449189f23b9b24d9dc39a033e0aa2b2f276f9d1e761f6d2a1159d10053bb32475f2f3993d7bcde65e7ddf3c60803cdf2aeec9660776ed493056090bae94a95477c902a5f381c0cccea55d7222453a25a570ba9a609ebc217a373508a3f162c134aad15c880f0abc4dbc1edd5e966f9f73aa34c16dc388e182885130483a61c9504dce22321e876096ff76544de37e2aed3ee32307347e5853ec1869398ce4cbd087a991146eb638387a3168c7cf20b4fdd20492855c7f7a3b19c86de60bf108e40ca755d8a2b16fc22101a63edba7f755090cdec8c7e434438201651c09ddfb2f5d7833d4bbc394262fab4290eaa295e2acbd7204e4bedf8cee3f4fe9c74493e626925dec15be876d5b79a86aa96070ea7e579434515d78f034188692f6d08c658eb3ab6f5065cd4e25e014da6fe5feba05a07d6304983da72bf07c12d93907e45272bb034326c6aa0cdf12150b0d0449d5009098165c1ec185c4698c28a65094c74054fcd773f7055434272ec7c472ebba6302ed36c992b26dc7476f6017b21c58ced29eef1f6b67b7b13d581ad4bf6f5878eeffbc9edf9c9f1df84a168a1ef8c9d73c5810910e3c6c3931610622e4306b93f584d7ee734efd06a8bdacaeddca4cdae0f99d65f1ac533931f98062d2da5dfadb7aba8e5a2c721e10104d84777f2db1227dc415adcecfbc398e0dc1c0e3971db26f6cf3b71bb428d44f1d138247ffb929a59b9d4cbdc3d0fa2dfa8033f19b39f1acea9e7bc93235f5d3a8fdacb45a69deee8385888213f0d270e4d9cc339cb7f053cc0e593c0a8af6eff9297d13e8f3bf94a228d6d5618813481e1d0d2dc74a2d0e792e6d1523ab0dda58d090925f7532025ac37381475d49977c7a7b97abd5ab36eeda75c5ef66521e45225d01d8ec246448b96c7b63a23412f58f3e31f52f4cf503fb57ba7dc86388e654db87d75dfd03a63cef9883d371028b3a4f2d4111c4a9e3f0b90db9c283cc5aa7992a602bcd677b9f2fc8ca6657d92a61f87f22c50d937c85cece46b433f42f9b593ad16788fcf7b9ee7b220abdbcfe083a2e03ca969a2b1c762f4f3b85f865b3ec93376e421338acae8d0b9df940f1e65407b57b9e669d19b17f2199890a6af3f9581214e133e503dc9fc1e87e2d6716ce312facdb7dd0c1550afd974ef7fbb865b5872faa704dc72740fa9e929d0068359a45b78b3a1910b7d4604e9a8a79795b62aac1d7789dfdc1f7406f6d61bf311dd8729f4a09ca8e0cbbf68dcec65a81e36f9348f5ba417786a6d8cf97c442f5fe56965cc442944e8fa644550ec7ac9752585869df21017df39b2e8590a1e842f92a0013e0c619c4306c0fd68699964f66751c19ef17cbbacad6caeed6f7e568b55e42c9150e997648db8af31cc9f46bd05c17890e993b4ce9e372de63bed3a9df18269391ddde81d4a5718a07bb510244a2b286c07df865a63c8a8dfb14206388ccb30a8e2825e8c5f2a2b6c934993fd3c3fe1327d9330404980908b3c22bff72b6c610642de5dfb53b33449462d12a7e6e3531e962dd5aef428cdd9ee8785990d0df641d86c840afb81e78348129e0bbb6c8326a0882284bc0370aa031afebec41f70d6085140940bda09bfa0def992b7d39e711a3266e3a71fd3a3253c5a439271c53e7095d2cba8e489e49437191442ec5caa8b64c2b0e1ba001376e37614f30a4c963b34e19b45f7dfd47e26451814d456469b11b132e0394e38613a74dbd4960a186fded771ba449e1795ef2a0b687fda50b1043e9b303403bafbbfe2fd70a792d4239c6240add797fd8fb36e2d628a86f6aabae9662b3758bd6e4ede97f1faf2495b67d9e464ad637277fcaa6d5f25e12a4a4f0260ebb8425ff17c7f5af7999a9df1607b003a01a64c656823bacc4626865d578be76d83da2fcf2b49f0c07850f1abdafc00dee54e55c7e4acb2d3215c2cca6d2d82dc04b1f8c69120f988ee1a5501dae9bb9dcaa558dbbb0a6aa724f0dc98c80cb8a97c7ca7366067ecdc51cb45787ff01a6851e76994d7b99a6005b823284f041621acc3a2b48739d555f9759430b9616003ec63cdee19499ed19705df9818c7f4d6a7731a3317ef707655544445773fac11bf7f4b80041b02c1d8fd08d41eeaff5e8a86da6a0b8198e82c71c537e0cb8ee0d7a7f4ad8735200df348bc5eddde8e7aec1805863493d4e4aa0bc542efbc38837ea55d14be82bb248c90e58c6435c720a308eb7b95b6a6b64f585eaeb5854ab63be6c69c94f3355722e20fb6fe255938c9674ae7591c40e2cf9d5cd07c84f46340108e26ccea0be11d192054d55e053929edfff88bba593bb09b6e0186fe8b43558e86714a4f5f9a1b295de67ae4be84255c60b479ba4ba3d6a6cf8972b6b190a59c53a79090a3215ae129aad656ccee99b52fa827b003d7fb910c3e43d5eafe343338d557c6b0fe029b97c325171b041b2061e34f6cc35b9c4cacc1861d3449404188b06e1449a5052ae7aa7bf7a6b864008a828b88bb04f63b4624390ea100f63e8c46b5db92672654075f2fc2dc6bf8a319f90fff51b5c0ef19d7f9e00b2251fdd6db342f7a8577a699f4d6d54f92ef308562eb04b8cdc7c69ec4f663ae7fab8b2b40ae7c60219b952f36c50cd94aa4739c9aceaf27e98660d6494e601763560d014e7276f73b764d2c672d581c2a2e850bff9443b0dbcebe76203e5eb99a8c97793444c0103b6873b4cd835c9076667014eaeecb9dd8b3f1d0dea86932b8815b3f4d66ae9997d5733791c274d1790ee3dc495360591190afead67aff0c5b927c6c939597419f7afcdad576d20d794ca4b3c73f6a0fe263844d878107a116239c4f95c2b4070820ed36a5105d120b15138d2dd107b399651a4bfd4046891ca18d122506dacb83a3d8ce58118472a3dd5bdccb38756c3e53e816fe3ec42f1b67eb58e24e15426380c98ec8d1b3dafc3386551296a7176c48d07fa046059951b7f058b2b862894315a3c450981cab87c26b930983f678f6047047c46e0151ccc1464557e18b8f8c8facbf8f8e7982650981010bb4901f1c9c0e61ccc9b10eb9321d248143f7dc487d9703735ab4805df31e95cd878c67da8b1eb062be001543715aa4d3c3b2ba6cab6e4cde4e309e038d9c3df278c1e4fee86c966341b35ae1663d029a10ac62c06b0699e4e273e891fb4b4eb41f4635a8ecfd9bb2d78445e5ab42620bcdd01391b73078b1a7d38b8bd8090ef39ac764b04291bfe30365992ae3089ffd9194a703208b40399037c12f1f68bed3f9bc2a135f1e377e670e69c8b5caf30fb054df086b77a635502ab46444d57a4014449206c22a284dfa75fa1cf5349aa1bbe392f3f808ba1f402ef10ff2683b62782f400e72e41d10ae2a0413a7aaa95f5bb903b527e48a86cb6d267114003549032eb296ad2bb8ba1fdf27c3df8b9b08a515880ad65f8a835713ce5cb0c2be0e0884a069e05fe9951516224669a9fcd364e63b1a91af5ecfdd80285ba0e590ba83a21af4e074e32de33e43661773e26c0ab233d4a1aeab73882dbc354d9d379fccf63f80fd848bed6be0c1724f214d28cd2eb283f600c0f7a5cb85c17ce33b214d9f482b3608cbe9d09fff2e3559991955299ec769445d8a41d691b908f28fc286d503e6962af48adf59f08a8f1e35df8b1ced3d11eca46c1c4b0913ee422dfed09e801721151811fb17d428bbf445ed31cb455d4176f1a223b6a4272709a6e822c09c977564e6c1fa54c836c9b2b2a4c3f194d63a749b567814ef741c795b851b60f6d1a1c87a753150141a6237bcd7e0a31865986c640f57046081aaa4855228beeb61dbb8cb7535433dcdab9041c76112ad98f48211f7b061051172732385e0017ec332b7e4407d3a7c29652592b361a7f4b18502ec7b88fcccf483b3f91d6023b079ba46ee7406a2de710ad4d242b577e118c16626cff8434a4b58915ca50415a4b37b24d73fed1da1cac72d03c5a9b8429744743fa699bdfd1da7512e11e06e4365a534c9843e6abd11aa4b4f217bc9ad1daac55448d458dd988ae3e268f395ac3d2502153ed23e511caf7851504096c34f39fd6ee679933eb0b8b2b95111d4bb446c90c5173f3980a750a8f150cea03ca1b78db358f217473b1f26cfc2f4787d6a096a8326530c1eb686a6a3db6469ae82e0b1b5a2bb044f25becb8ca36ad6d605705ff8aa26a57ab50fe94bbf4d6b0143ea149085cc40625c90f156b09e24a0fb4e6d92509c7144b219f35beddc58908c149d01a79a5464c979f0e0cad15c45a6c63614e165a4b61897e2afd23912631165a238e22635600e039e39d5ba22acb58e4a3b58e869727db9e179171dae4f49622610f19aa426b126ceb489fc8da70f89b13b6b28e73f339047c19dfb953cedefaff081427b4a672bb4da6d8e36cb36ee786fb03aa6a021b6f1b5a5bf8c283519b8bd3a854a2008306686d663e05ca365ca9e21cab37df0aedadc69b34252d005fce4ae0b3666ed44cabc9934ecf4d5c19cf5ae35e69aa85ae542d5508b92caeaff0df56a7e6d725a88267cddb9c673feaadf1ae7793aaf2152166f4aca46731c1f4b91814010fde79d65a185261f5ac0d6d91ffd1b33686b8496c928f676d5fb3ceab724977ecc893d0035a94f40db06fd4ed74ed5a3b1f67993a3139f84f269f85c7516f18aa2c9c51153350f481dc893c024e8e440babc9e7d05a7931668a2e06bc5498ad68c51b30bf3a4ed562520be4799fdd1b6003637a18777b04f72d510fdc5afd2107096672fcc8de0f14a4221f26a3804b17ba0fbc65b937169b37cad5ed8ed3013c1692a73dd26a2078c26d51a915579a96428d0b457a4229c3f5c14194c59822a469d85ee81b37935f6842d945bc8a3baa086e60ccdb08118339273bd25868757f71d47d901cce6495f62a430d6f4f198b9d01e0d2345f284c495318473b3c0314589ef1955691502ea2210b1d1151c92d73d9826c26922d3da465c52614ed4566827aaec164e266ebbfa33f0af55644f54b9bc76bebac3a1c209eabb691dfa58345f2490b0330aeeb3e294b8fd36cb476d39b0e7e9a18d0f86c54a232cd5b98b12fb99f73b319a6582cd97a542df95aa8e8363f31b2647591ddfed90c8b44b264530ea93a9cf64f2ae8bc4a7a330c475625c642112392489b60da1ed7f4826124ff9273a5dd5bb2b6e943560d5530b41898faf2baf1d37475854d90e14920aac08b0db0f14002cb8ad5c60627775a50b189204f92354d292c117e43086a36587e36877ab5e76a2bcd2f5f58ad42731c5aac4e9305128bba8f2e43b36a8f5cf4095a81a91f42fd6e6cbb2842b863cd480b658f4e1120846bb1e3578433acf270b02e0630d4af50cbdf1b4c216b84bd3e37718202e60eb0422596c8815c42f4742be11bc77a0baf991db9691b8a6bece9ac107b8267a26c872e19348e322be375f70e577757e8bd1dac629fc8e91e9d05b086415b1982fe6f58c3878a9c18ca8f88543d4151c132ed2df57e2b6746c70e89e3102775cd99e5e804ef7924ba9b0f0995292c2667cb89d72b069b1a18d92f9dfbe5481bc9245053efd31fd1f37508dae36d6f7cb113fba55962fb451af4b4cefccea985ebae40a40295bf54e018daf5a9e2a687601cad5aa578bc90c9c1b04ae877ebe84f56f78f818f4153bc86b2a15ff664975ec4c4aef73ba9c79fe9a3ce69f065867c4d25063dd754d46cdb620bf0e1dbfa88ebaf59bf2a2760fab0a918927cd061ec761bbf312ec132aa2e323b19c336d49b70f563a21ef4bc4da46cf2442ea4cdd59457f167a75232aef08b61dba0a1740ee18b8d17b637019ad7b110a1c8c7c491cd5e9709aa99b32bdb83d30888040615974c7a3865e4a2296da316cfb0278f6e015a7b73bf04b0ce43313fb70afbd9f8ec28d24c169acda6e4040db6eb85706b8e9eff73d8eefa9a68a33c276772aea99729bfeb904927f63e75d04adc9a41e4d658e91b04461a86a7ef58722ed54dfa1378fcedec08f60a136b1845a39a258fb732e0cec8c3cd1cc19d051b4ea6af2e4cce1530be1cafe2ef738deb4d6e1cc24491c2786c2ad76e8d6349f36e1eb8c9c7a705ddfe28cec1fd0a063d92ed2bd3fc8091017b5d061eb7c9bf7206db04c7aee77bb6bc3043a4d6b7c08de129a204896c314c4b9cf08027bbcb54e77b70e850f6da1796320645d6bf4844809ca56ccb489027214f0829579f5031452b669f2a4d99b822d450787022d313c2d0f379e4f995856677703390aef2a4865a757cd20cd74785c4f5197c19c9d11cadbfc6601070f59cdd9d6048bbfeeee0cddc73d9759468e0f14ce6b8acfb0b19ca390a362d012f3998a97bd582ce77d8f5635620196b12c07332918cf219f5189e278849a31c83bf56c0c97f893f608e211c13211c10601bc428cb91b0d608228117dc25513a420302864c143bc43d9c66d1d039fc0ef7845b2d41dbc23df3c16c91ba3cb827b05e13f6c20c9dbd316366a40ffd51b06668641181316b6ecff61c782784fecdfbd49f5e96107fb9f1d5b9aa25eaeef0d71a35fe96a9891813baa5f9bb6e180f1152b20cbbc418879b550a90583ef138496f5636c3d06c3065254c21178766ebfd665512917e6a48d21d4a104fdc198d5aea7bba60bf2a387d12731770ad3bd600b4e8d7081426d7f5ae6a59c05388301e5567fb1bdff1d6b2fe793d3840a2464171369a8c618a32138e4b585b2680eadfdd16b7498d914e50ea3a48380b0dbde00849683af14f220a410f991bc3314d4224db9e7aee16cff75b9f10d816113553e6d5aa754923e83bcc24a8f809584c00963239ffa63230887774cb1ae38a00c907801670dbbf73bea337b8a409bb885dc8c190778afd4715c5e696456e1cf084511a82275d1f0235c0d83bb6d32a3da992c62574213f6aae394917023c6e34bd1889193cc2ffaef1d309d1e1e3c6034206da7fe0adfaf752238cba2c21e6a94f401f05dd646fe059f2e11d748da9b27faefff1ccaf5c1e604960b66f68774b64370a2af69e6257172150efa9e16925b8a14b5736849cdaaed1c5325831108c302f8f2eea5bf5bc10746144bdaa6d284e5dfc1d9a892665b4b708d9a8491878092c79088a72a8642e4e1cfb2ed978660ac68730950cdb4e5de1821750d058005a9d37c6e3fe9f286b569d89391250a857eb3efef7156611bb9764803d44bfd4d505bc399d44953e2ff5efbbaf629532eae8b822a930f14238619cc6dc292f9aca6c408d17153afd142e6e46e9267fd1bf1656ad1bd10ff30931c38d94ecdcaa9d8fdcb9a3e01248e341bbb60473d24deedc485a6efe5934596bb45ac9194ab00bf60cc4c3ca088c88a1e53120f05cc04d207d284f1bcbdf5c3927c162ebcd31add7d295e68d412051c722f98bc57b7e06af86dc28b5c4fd80901e5c08029a934d5a58300edd4e07487fb045d04d939ba6033c200b461718099aa4d6a342f10970c10b414916602da85208b55c4cea64412ed3007510a09bd40353f2de4f90286becacb8bc50ce06fe469b88580f53ee57b7bddbda967b6f29a50cbf0532059c0546463cae0f239bb225c51045e4ecdc9f1ec51d398a48355c1a612e8e56710a868a15ae4e520b42c5209f12491cf74512e6c791220dbca20e1f4b37770acd060dbe197554c53616ee958a38221f20d7aa4807ac48a6580449519eebf503c51b912fadd83756245ade5811f55ab87e4c45bb245e90fb83484491866866abaf889e1288bb07579cb1566c51f6e5e566b540acb7054a8d29dd24a4228ed0de8f6b37a453541b0a31a4cf0dda2b420511d143449b0b341fb122dbd2e571e51a91a25312fb906e088df8293e09e9b83142215c65df9e511062921e666d25d09ac45caea58fab2293149992a804c94fa8c8f5538c4ba1e31e4911c215d2471cb20717a418d6c3441b0f5ab18798fbb4acba2abe49b933943648452721b39f1828745c351421dc157d327b45a52045a01e4d6d6e08341749ac28b62c5a5d6d49c9286f20b948848a7e8a5d2774dca31321f8fa8a7b6e8e20ee4e0fa291b6e2126d895890a50b73e5e249157994c52c2418427d3f7f2674dc33132188f421d9d36ad3203d6dda03c82d2fadacac2804b6b6e55255b5b928ca5076243e734f0425f5bcd33768a8ef37f7defbe1101006a46603047a45a440553c1c084a070562aa398186764c209e9d0d34b3f3c3b7f343c7639a30fc23908e8e5459f1b2d1986304fb63a6b3fe78e5a83f38c1136df1ab755b684f3a1f361ed4874ce7f4c1ea3159b43e43383e3c3b3d486886c33fb21dbf3c6f47aa9cb160ef4f8e1fb31faca7f767e8fdd9797f60f27ce85e1f36950c7882157cf25132d5d767a8e7d787c7070649cf5b48e721477be06c7a6eec983d3f300b0b2f3a656cfeb889ce39e729cb8ab241990d1ebf8e6c8b373c45da3c1ebcb61dbe8e1bcfcba3d691473c156b2a2e20ba123cd9a74dc1bf76de53b08bdf9e1b1dbf3d70c59e2e8ef5d4916952c76f0f10eed9e96ac76f4f4ccfbf8ac06c08c85f922d6b66a9c786161990b9d7666dc6bc31418da01dbf3a6a48e63acc39e79cf3efe823eed84dd162bdd2911bec8ea257f6f0c47a9ba7c975caba6c1378a6ac8b46d5a0caf1d627116802c2af0e57df6c09191ff3677cc41f64f7abeafeedb2e0a0e201e660d47aeda6310495a13e6615e3abd384225521b268edd6b467925c6b1ab96c2db0e357c744776bb4c18e5f1d111d694ce9f8cdd1cba1ebc8e1ded339cd38d5e73651b03ea220d7de66774d3936eeb0b0f02fb3ce794c1330c605cad1f0794c4dcc08748fc50db287c1aef897db8e5f9cb3217089137683b3a4c1c1b239e9f8c5519eb51dd0bdcd8da63c4d795412f148cb3b7e7188da76f8c38e5f1c9a7ea643c529ed062dca90735ec4da1c3aec1bbd9fd6668a737e9a5bb7edf06fe03af2f8f4e7b6a4ce98b386131c3ee75d3fc00e2650eda4bc986cd18edf1b17473d284fcf79737363de84a0d94a6a8cd8bc367a33dc681a755e9ba9fe1dbf3636fa858e5f9bad8e54396369549d9242fac04fddd44df706dca2827e17dc8f1b003ffdd48d41fac07f79007ea3916f60346ed0f36e91e89f6b9699817e39f526a1e69cb9e11787afeb3727a96b15f8df929c57a062adc200eec5b193e992d97a65b0360080102c461ae30c96c391139b734bb209a1f7f2445457038f2808afce04281023945b71098815a739a52941ac281ea018fb81622c2e39c3337091b1e45a2493a9e69224a7e0924b923a8a72122ca42fcf2f093703a3dd14f14a96487270c09b0ca1e8e783a25cb34cc210b1c06a4afc4657231be9ad9a371fa5538a3b29ada4a48d9e11a7165a475a74489358fec36f36b14cf64812a0c826b922d952d4972631ca71f11424e5e63502241d5f345d772a5eaa8ea2794c5ec901140d8bf016a6b34b6a252f1469261bc90e8ae2c8924761c9cc46220986d97efc8dd0599156a44a6be9450a2f6d86998e15d9100c0e5448d3ce4092af1e73c92caeb197177a5e7535667d256a207514c563c26dd9cc0cd729a0082cd995744a8e30405156d267a785d850674292c444a09268918b23c097a7197743b6ce60fe72e02813c283c65f18179e8c137ad704824e8e0efb21a08f1370d3fd26e409e82308d628d6ee3fb18937c6287973606161312e5be4ca87e6e38ddbae47a858bf2da83a7e5b30bd517eaf8ba1b7859fdddbc2ed6d11a63fa778d0dd7c164a807cc1dd05fa38a762ad5de46abafc83a9d3a9b37ef2fcd4e2770aae033fb739841564dddf289e7eae67366a3dd2f4c08d626dfa48e0f9ead9e296b3d5e999ae40e2d9db13bc92b2c0cfac4b0ad04d53feea099cf30d9357c037fa26ce5b9360146bcdcf67c6196bc62de38c15f4aaba5a055df5116f41c084fc6a0b24e2de616f30815027a07854b88e5cc1787ccd7a08baab3e6e2356bbdd552525b69c8832345ee50f4cbe910788800979d5ec04703f3f3dd3471f7866530bf9bf2bc63a72359fe4baf9ad39ec66faa8cba9b316eedc79ba9ebc4cd52bdb04fe07fed2734e53e4f9e17441ac7d7f0ff575c7af4d930d04ee0dcc664a7ce99c68aedd74d537e2165be5d63815afe907d5340b5f15be4a0c0a892eb857c0345fe80adf7ca12b140a858f0a1f7da19beae3bf74f581cf1283df61bffad2814410c39ea244f345cdda0d456fe517061f77164551f47ce2078daa287aa227abe293ffc9b22e52f3f4f357d03db3acea7e78966559e2aaa2af6a6dc29e905fc8ba983cfba65ed119eb9c49c82c4a34f99465f50eab51f4eeadd63b1a886d5dae47e4cc5156a4e6c9e09eb8c816d9667745591f0575534efe7c96e50206f6d1d73b28e7ec9b98fcca9e404466f63564c8af7217e5831aa5bd00dc32219e10452076d047f357dafc3aeb1d9347ff24e66f89ea6be2197c9393bb27c63add1c4ca1ab7e481f7de10b856e0af0cfcf6fb62ccafe7316ad410c390b5ed4d7372350960435c822823e9ae819063bbfa62d27bbaed2f1eb74d59ba2ba013a7e99926ef4b5b7af59a0f769e855cabdcaa957b9f32abb56b90d67a26f56b91cd4da5d7510dfac71ab9c5e09721dd4fbd5f603b059fd5c3f0fc2e9e76b5e47aea059e2870c3f83ae3106ad3386b81c02ff8cd41a5e67780d2e5277e12ad7f305a1ca75d65a73f783f033bcce5aa341e49af741ff0cf607f5cd0a875ce976907d54b8ca75f4397739df7c73a1fb21788173ce6e9dc15d831ae5d1e75aebcfaf6ff44a906591e82357b9cebf65db0c2f6cf9c90e6b77d61a70fdfa3f045d645eb506fb7798e6c1611bd6f7eb9b9505ca40cc441da58ffb51887a4aafb46ad72b5cd7eca3c4fc18d0efb0d90bfb511336fd6c9273664dd6b8bd065fbff0f5b0afee87e0f3671f0962d88569cab2423668f2dc8405ea481d954f0cf11a07020902fb21f54e67dd02ccb420863d3f674160bf9d8eae71acbbc6e504a0bf6a0e62a86fd638391da5a7f4f1d43a6a8ddb701a0851b156b1317d24f2003d7399ae7fd5dfbe50f3bbe466b4d162ccae4293dc57dca273561a75cc77694cc3e1331430803940601fa06f13b79da1c0011158504881031ed80065a857ccfec1b0e377c945df1dbf4b2cfa998a8ea247e426010ab2a453bc5b92290a41229537f3c3edf16622ad3837a6b45cb272d35ab8424c39492291201b152274a89829fef4b94c7a6e1248b1062e6d0d4759d1889718964b26e54239551d11ff4e14eb94487429d5bc296141c41d1284288e040e17edacf802ac38228be8f702ebc9a543729d525c25f5cc24dd1495fcad3032764725e786b5d5b001f3f2d82ac28821054542c96b28eac70d29670e2906089feb4248516bae98a483b8e4c60eb1182e37a7aaa813553cb244dc2b72659470e15ad821d24208629124c514438ee85c203b70a81587c8de2e57c55571cd4571a989c668c8448ba3117747346e7e4af65cb03a17058f620cb6229a99dbf3e5a69051cc314544a2cc6b410f851b288aa2a999f5f475c24985d75c1beeca8bd251f6cc61cd20c093064a13786d5c3e6220e1809223582e905e1e3e4b9ee74a2e19502b8ed8baf08a4000c38708b4afb0175a297762d25a644c48c2b2eaa2d67c48d4488bda79334a9b5660cb8392f2233b723b6186520c10a39fb8bc2894e9379b0f15b97d8dd0667488c4d570090196e451d3676494755a4d944fecccf958536223a4040fa9caebfbf9fba1d903737161c9c882cceb85981fdfb28388f7adc889b12827302414e32de993eb889f0a09acb9c8b9536a31e43482913b311d76304a3874702fb513258d2eb9785213371f18a49a1a20da43d3fbcbd13763a2a7841cddde138d9e2f1b0440daaf578f900438fe8a2879ee7ab8e1f72cb94c6b6bb1a4c62b6582b79426dfb98b8aeca2b5ec828363274ecf4a4846969c138f0d0c56de0d5813b005a6fd069d84a51d58abbbe3199206276447030c2c272ae60f6b09c35c5aa5477c895295dc91a6af9c97242d258db63e19265044a146e632a7a3a790349939e79cef76d8a2e07d728e53b365e1c81217673c706e34e11c7424c4fca658c09475e9c84a9d73deb6ed0795b8726c5e9401c0a373a603c791177d02a8a11461048990a4aaaea7a5f33ccf353d5272a3d503ad6aea46043fa69f9b1b3523992e98fd76119970a2161b4921222d2c256073474670dd88190d31c275d7cf778984f3a1df388e326f34885405798b94a13921e0a67cecdc6c84a9091d6396540ffb6fc9cb120ae25ae0d7f12359b8d25aeb9c73d63a6bad172044be3001bb3a2e018f999580bff23a2e01eb01e9780eabe3df917ceb9bbbc26e4f1de378c365551d9399758c43c75b921c965909a058bd81b003849cf3e442a407af234c569680323d376e002a8e43024053766262004a24a88067c0c4f415f4e7ae5d640a6e982a42a40ab44e8256deba6ad7b1da0e0a912d38e33d4a404b74ec72a6e8b8e3c76c0450a034cd652799e6364d739b7b9be6dedb34cd6de63221b2ef181dbfc956c72d3901990f592d83640a6c190956f1d995d1189aab636166a777828e07d9d9cd03b682a41ca8efbdb74ef40c6a352a7eaaae91ab0a32b2bcb6527cbe5b37af5689a4d33ccdd37c6718edf5ddb21c8d79ebd188b3fcba49c7af56cd12a31019595e28b62bc11d9b446f1c96034306ced052f86531f9a0e1483bb246374494776766a2f19fe53a8cb1229d4ca530cda1699ee7e3997ebe5922b88c2432ec54ab94fb782d69d9f841a2a68bf7e8e4f0d3a369a1c486921f32dcb0ea50ce63a4e33c2949335431c65e152f5e2c201c6e901d01d93a13d2da1dbf587359dbad0d47501f1858961491201dae8496d8f584050959d905ad8c9c70126e1a3a7eb1cefadaf18bd5d5c29bf18d4dbfc6373563dc7bc5f65eb9bd56633c7a94808be0afb9d61ff47a1ae34cb916bf69be51474b8a07fde8af803f1005b704dfe4afc20cba7e733dc12dea9bfabfa74407744ee4a0e3ad9e12f15fe7c4140f2374fd238cb0b74fd513d5950ae67cc44631ee48b9888e1c765dd5d5f59b6f15968a95a975fe539bab06fd7c4d9426e10db8234d3c5095c01b70475de4495c51635aab41c727e859af1af4bc350ce38d1a54343188c490ebd8afe317c64ecf1d08cc0fcc2be8d4041d99923be894ecba401f814117c4b0e3c7bfbed1ab05e2073e4ec1cf3ff829e6eb6607e2fcc0578344e358f0f1af0f62eda9ca895b17e823f039500da604831f4cdf18a345aa60e5a88ca557b672975e09cb63f92c479dcfba48e37a9ee7b9aa7bc5a60acbb22ccbb2b9b3cf591258766d51861be89520664dfefcacf2018b08fa78be90d80102c4d4aa151283cfaeeb1a7cf5390f06553e6009021fd540e09f41adb23887ef0bbf772a4fca4865ac5a31cdad43d0fd437e8c57f223cd3f31d61b6bcd7a306160320c8c5ea24a8df5fcd8a85f29b6aeb58bb1ace5cf1fb9f6e301d03f5114a8d3d43481b91bb9b692c2eaf875d1d591c7d7c553efda44f1de3b1f8fc709e00f26eb726e0fbb7d93a7113efb994db140fb269f42eba6e0a67b838b0bdaf7e33f17137047dd02c80433ff33e79c733e41430737f8b5d65aebcdbaf0aefea6da547474db645dd0aebe6aae26918ece344fd6e53c33fef33c5d4c14453912f3f1a990b22ebaabdf019216f8c107b22eb9abaf0651347dd5453fed28aa9aac9bf5b0ba1823407599fa238ffdc8821238e8e9bb708072d05397adbb4853a333305f54bdce9c30b945cf6f46e116f3b37b72d051d81c4e18bc417f9ab6fbbfe38edc27ce305147b4363ff284d9301801393f6f3fe5916d3ad4f0f8550d440e7883fe0c4407e00dfad3ada28fda037cd4afd16ee66c12d1ee01edfb1d304203708bc9eb1701b7b427afbf05b75c78fd20e0160c28af9f036e01befe15dc127cfd0cf800b7acafdf830528e0370714b60a6e49bf2bc323f1d70819be9b68782171e986e5011ada130d8ff218882cb41df817882c0c3bd05dd2e0a67bc30966407e359f67cf40ccd0330350c88fe142fe935668d2f3b3edee2bdaf30779cf0f4c4d940bf7770d19881c834eeb54f355937801deb699bd00fae79b29a06eeaa67bc35fcfac4bee262fa6ce38156a13cc0217b5ef77514df25fc7ac8bda751e6249bb2a96244625f694c5927afe1405635d798a3fe9211a409bc6cceb67be76afdcdb860a3bc07beac8f5a893828ca9e397ec4447e68ec976cc3ce6d791e9585ec7701d69a667c7ef585bcf6fee31b69eedd40e820055270ee3051e82533583c8dc8e8f34fc56b4031395a45a6362c1340c33abbf18be22312a3086d889f9b851f103d675e16be94451144d4d1445d13357b540f98a73abc8c15f108513a30ded865d5b8714635a693855422ac28abc344099b20733ec4a602d797c7c40add059faba63a154d385de99e78ffc8264b6f396c3472c058c1532ca27b431b2be28962f9b74ea749e67eaf4db6befa2cc5584a24c1180221254d30211974f0953257888b24228b10afb3abb829cb9ea3c43503bbff8c8828c88e1fdc9806fe78e84121c12155892194bac72ea84ca68be7435785160756a2a8e98b448982599a55803a3bbb0d5b46d94b822b2646feac8006ee4c839a3e1456342a8b5b4c55e30a12f462efa9e96868492652d633a267ac01c29bbc2762e6431857175f4ad11dccb97cb60425fb9648033bd9c3520426d71332bf8d2f8443801411186a465699b651a9315f9bd6d6a6865332d50fad2110115d4f58482c564a24b2b0d5dbcf2ea0aa6c9d38b6acd4f6d2364243569dd40dae211e7276326c411e5178be255fe409d91d791dcd4d31a108c144596476a6c865e9a137c239d5acb3cc9e94e0b4993274a7a177114f52aeb68aba5341492113143c0b6aa70289d5ef8e051a3c64d9712146e951ae7f84ad8d40dfda8f1b4bd608ae959a3a90a8bbac0ab98b60aa0548e0c289abcd139c26b5a31939303821a0d0e4e1fdf5ad109d589eaed2be5f2ca2c870820377553703d6b56454abec2eed6ac65bebd508a334b26e96501c0086aeb4d085755727996f2189b73ce7711e79c73ce39e79cb766102050c6799ee79a9e49a094baa51c1a8bd179e1e3832a052c298b06168d97cd0406f61231c4c8da8811468458e044589b5f1d94ddde8bdd12a44429c5d394b49684b796e30396a2ef6f2f65d2a9172c1b5b6b55543b4a3b28e004b30c72639b81d2c1b9e4805205c083c95a7146a74411821d5b457d42e6864c2ced5817e69f3af85681a2288aa2289a9aca269f246240404a00e1a5db8bb322692d84c8328a4e136a69b9830d733acff35c532d209d1d384841c2f48062769079fdd8aac132697172218dc3c8140db023496292884c69657e6e617676613cd60cc484a2289a9a289ad7d1162b3b754f636633363f2fc8316327076d091157570c61d412fa69cc7eb94c9698f46ec0901d5146c696cee45e80d676ec25594a3af180a95de02c9c90553dba96048d80ea3be6789500437183e96bb96372a8a5bc64e32b4e140889c31194d667c3e6a54a064d792724f7da7cf10993049306a844ac580f4aa70cc208000002a3170000200c0a88e33810e4300a876bed14000954c23e524828104b45a1288c821804422006a1000001100080108661100a252a521d06decd6c3ff9791fa6a9bba3b2a478ffd7726006fa3093a99d30b318691373455ad3625567aa0d2038bbb60a56848bcff57c91574fa9394b08e1a206bbd14d392a8dc5e0cf2a5c454da2bbb609d39e91f61ee11450cac83449490daced7e7b5ff3da60132a1f73f722ca3748aa9a951a7b94e2263045b0ac49eaf03ac802067c5555d88dca2818c1ee36ae1a5539d82154c6a4009fb19e5972eff18238266b93b96dca9cd05dc90ec458016a1639fb89eb11e10b249e2e62cab6d16217144ce41eb9da0a21e7b5f9003eb18214062976726bdeb475969730025e8c40615a897852473452f5167b817d6efed22717a3d0000b744e7e34271c61a5267d655d9878ed86e897ef50c4e2f9143593c8a113c3f821652e2b9305e6cd21e5da9c5e5f8b9a74a80b65582c857549765c1e69f7ecea682fd464cd71b2f17b8d51cf33db4b7941df2401b1b869848ba894aa0a39317afe0040ff3ad331a4a3b69725a88b99876f7eb8076615318bef1fc51df027db6e9aab488394ce89bd6e3b8c48cef1bb3a4acf131e4e40c663bee191fc95d44c94c3041f5e4d35f803f6e620b6d0bfc9ff3ecf7437421c505435f9e06a95ba6459de685d23e2baa41e4f5ba314922d9e43868eb022571abf08002b8949a8be8c76bd630d53a74fa5cc99fe249db95f9dcb70a0b1e4a4ba6bf65595a4ee7f8c08dd085dc13ca16de8e809adfeefc376717fe8382af313d765aed0b3cacb2797e41fad15ca04ec44c4d23b3b5cd2573763876215ddf97a3a2137ce7973c16b331d9ae85f6b4d65329b183ea34c4703c737d6ff55b39fa4eb13fc942f75f565ea0e6e215ac3f7d642c0137fde5b6d4d7648012293634a12984cfdca684ff2adf0e9959605d6496f4eae4fea6f7f0b93ee5cd72a0ad314812287b27362d90dba29f3740be0ea4afeab38579fbd828bf24d939ea455b23a8a83dec8427263233342cca6a51b0e8e1d2be97bc9cf5c52bacd662a62176dfad09df53b3aa923e844e08d9dc05618952514a7ca0eece5fd32aa72b9307a17a5bad71e5aca36dde6cd2c4f350d4328dd0909a5895e7c51b23f86295a825d23b601c49f52a1e1911ba74081524cacdbded277f8c303b6e50c16693dfe65e462a3bfc6bfd74d3758c2899cd52a59d7a2a584b8b02542c9f2309bc3674dd900a430d1412ca4adc521814deb69194459eb29b5c899d14a3c6168f0c11822332ed5fd777fe07ea84cf7ef1e100c27c46d659a041e943853ee918a861188f0b64e464f6a4e5809f2b12250ccc4a4a21ba55f7a3b21142507d2fde961b41e0a95046978dd832acad1bc88c412f8d9cac41b8f420712f8376efcf24851955f22c36809409b4a6bc6ab4a6222c2ff5d3abb178e92ad74c00210c6b4d5ed063c4544e8e5fef147b9ff4745bdf83fdd16d24daed101e86e9714070fbbf1299ebdbc3837c417d86950628bc62a28396fe16766e0a519b4f6b1433b10f604d896a04183c8125428b0b3b92c41463383363490b29e1bad3f1b6dad102752824eb6594e36e4c932541f5c3606d340290a206382774966363d0fbd12e3af1146d1acab4958e5cd490c63217c33d1eb4abf3b127d86e45b14488db1e10423c59e9075a0ae0eab622345528124836d6f040746555ccd0604a55ee6884ac22fc93bd91052c61057329b4626ae8cfef39d30a4e437c41d02f2bb7e94dd51cf144a742952004ce7e49a9b568222859127c579bc7e8a4882cabcfb92542eff91c6296740f0d833945c9763c0184c7eb7e75712144b8ec02e48a9d70b901183295c9229d49f804fc8b4bfc5819b978cd5b6d40e6f93eb9890ac31f896b779f43884a3498f93f9042fef5c7cda922558aa8bc7a80c28c7b282ae793b95645ef2f3cb1b2cdf7a7966c0bf63ac9bc1913446d015a133e1834433345cfd0933212e897c4d31ca42a0c5e44fc0191ed67a165698d51071e40dff3228ce27ee0173d2c67a2751ec3f2cb457a00d0139e54b12435f1f4532d133abca7adc668b6ce3b79ad1dbaddc2a2bc9f0938cdf914a5ca71a4012c34f08c1b28739027c493d697ba776c7813905240160b5fa62acd9c49eaca6bd95596042482d49a11bbaf80fce12258a933095835ee57a91a46c45566c884389999df2eb7332da8291a6cf0a8727194757d9f44e4d64e11f914771009d13c614e1f424e0b874eea58a88b4813d2f7e6fe195dbe3d60889116c8218460101f010a4945f1f1989225da55c3192fec2477dc6300ee972490f85789ce595cc70517738dff0d2e783bcd052ec222699a8c9a34c65066fc3825826599ae8e4c1fc49e533cb966435171eb30202ea4bb0aa0bc7a800a8c7b082ae78bb2bc958f23765280cefa93262fc6adb4ea6e1b76aa55cdce42ce623faecbdd40e033958a6652457aa9ac6d5854671193edd6a82b703c974360c087ba68a050a82478264fada719292cf8ec285cf92b35c41558cdc953056fcecee12c3bc4d468eb52e0e30b09712aa5d6a0f017d128e4c38a4054be442981a688e6748f0bbc3fc072847d66e5390840c02466ad58568369a5cf6e10ff7a81ba8babcfc4a3568daa4c6cdf19325a36e5e38560c6fc38492c1f2bbd4e303234e276da0f1ba7b894ac823e233b4719b0e04e81fb422391bfc8e7d0621aaf2990354cdea48d016ce4c156928125369c843b4b9f018a9997009f4edfc3d18f83f9c0a16ed25ece092d913fe767c982be1ff1b5b0e5f0337cc4b151c4e81a2f10bceb86faec9e57ff811a084b5318cc09ae27384e00340641432abcb72ff895f402e7a9e59dd0bbda4d728277b0f81174e97f2816190c59d5ed341eae031ffaaae377c60f8f2c1fdaf28c2ba53cf06b8303e2091dd1fee26d0af8868ade8c23d876d752cdd7ce8d8b176ec5fe62449b64751426505b97d2faaae293e85f2037f08c4981e02b95ee2a902714c6c187929275432a36525f68bdf054aa38c8ba7f83239778a4bc4c80f69190e08baf89d4a9fd101ffe9c1b3d655b2cd2056901280268b410378cb9a164e923eb320d0403e724afeb0554a3fd868f68ec64498269f4ea81e86af014b20016653a5a57c8a4b500d85f71ec88ac686e6de3e265427e1fb9cd5aca43447bf0773fb132c35213b0d3e94b88aeb9106e422f6e2b3cd0c546f331b456ab668436281d96c91e9e0c4cd0faae9e4d67f90786fe24e14f0700f0f729adc98006ee8b5c86be636f11dd2537674ff4d0a2760e3cac2fb0b92039fc54f33320f8e045f570c8f4b0e964e8a0a21251848ab9c897aa65a1dd57a16ebf295f3279224835ef91ccfdd54f64a0f5b678d39e3890e3612fdfbfb414e73a75d140a9cf858d872c7281cc9af21a10255e6c959ab719eb595131a2f78f8f2fd2eed28a1179096fa1b4d4320099377adfa649ded02498cdc4491186731a5af906069b1987e844200c980cc9364072d5e7b5ccb01132817ba6a5251e40813c948a9b38a6ee5da5f60598d5f0226a890e493c2cacafbe266f81834ccc85007a75750d6425a861d3c1f2dc21e3059de0735663e424e6157e519231e6b28ff10b0853e48bfd1dff9a59622490474b72bb48b91278cd4df565a9a11f79dad4ad9904064d54a735c3560669394fbdcfebe846640ce24eb9eb85e288d909863fe025d4ec042196dd2dd3ef872d06717657e75120af6fcc6c0b964f33a494e3b862f675929a058af1418e6237b28041b1843e19f719239fcfb0d278057adee136aae89f5649d3ca4c71cbabf450e47765d0b9140b5902b330498c3c083d260742fc3c3ff2c8679309092344dc2165a7d4ea1e48673083cbb731eb2158a66e5cf5e52029902b82524d6eb4ee021a16c0c704e3b92c7bcb0820e9a7392b05c85cda1afa0206220729c2dd9ffbbcce660b03cf0e284e8a9a4ebcce514832e212e49e448c2c892e8afc80cc0a08cd84b90d084311e41f7180189ca659ca47d153776ab343c735439cfb6c65cfe17d2404e4672a34a6e076690930b76bee4702eedc06cfa1b1aba10b2a6040b656d80cf229481d31224a4ccbc7ff47790258213e61e41a9cec74f78606fb34a637d009fae71dec156c45620d42ed0f4c2719f9a51da63aadfb8302895de36edd7638d921be6d4082fa3d3d00ab0470a7b9e89535da773843679bf981e40beedc6071159b4bc3322fc5798003d35a8b32aeb2d3c696a4f05a3b136fb553e58f248dbefcdefbab27f13391f5d63e9e2c102f4aa3098037cdc0158529e147a04e312c774fc42d97e7a6596a8e881eb1231875e44f2f8e4064ab9b53a77645a2b08143401a9632233c1bee71fab1585373eda48e316dc2f77d5ffa2ebee67fce7f71bc34a19fc5231f90dfbe42e28859dabdc8c4f2d67e0af8fd5ec743b645996f4c6b73ca187b352bcb1dfcc7a58d9f768d058cda7ac82bc4e978eb0d66ea901a43dfcb1b09432ac9787306ca2872ce3b2db7f38837dfe3b509fe5cab51cf71fecf523518b389ff4e0f62025297653c69b92c2f90596be8285601ff8f3379d847ee179e62a0cef7cab723497482ea97c8c883d6c42554ed530dae30ea907628f4779b2f1f9dce040cde18d6874c737324fcf50bf451846059cf795eaa7175c94749a0a36053adc1d6b4ced44505f021f6878461731e6f9886b03f3117fbfa1bada380bb5750d9d2ac46e549f0a87818ba58849a5c02796bd1ee3e10548d901b1433ddd4481e0b08fdcad21a4a2fdab2749af4c9b2ee6f4487dc53c1237b306037c49a85f3f10616daa6b5cd3b0293d82f886aa7a8e58ae7802337fafa096835118f2492808e4378204abf8919ac8a22438c9bd81ebb68889531e54133f591f6677a434a7e9f27c2a92c98fa0215018ca7d84d06865bd555c18e586c777a04c67fbfb420ee111e70cfee2fb6c6d84c955419e5295b9c7a4a4c77d2b8cfc5f3f9f37b19f2560863eca0d05ff5838454c8e79f40107b60e3ace6159fc3dc3e7f0bae7ccf0177ebd6b08a08647f0421806df210c10ec28953485210c3f8ac0cbd98a379e5ca9d65aad072117b5c100b87629bdd6830aa510862ffa663322b4f60182e13ed6df86cbd10887c62e51a314c220619241e9516587daa7c4ee4960135062dae919bcf2b07017bbbba63e21f3a7eeda3529fd36fed3e6006cf5a9673033cd4372aaaafa0a91c28e9117fe5031c8b8511918e25224f64d37bcc1cb8e6d58476a0195c2a0c20ce55ea1b83168aad4839b7b9eb80a8a124848525a53dc69947214dbd9c3d8181823a1b051288f0a5bc031fc6cf72320da02765383d79690ef1b4741f419de4a63ba638f5c3467aac665caba5018c15db93aa62a4afaf5dd2ad8ea14072aac801d105f41f3c981e7f79505d254d582a7d3cd7b3f83197be696afb1471d842f5bdc32eaaafbe6fc7cc3e660ea6c4cebcf63b80101d0704df09d39eb8332795a1daed33b6c045017a74648bbe17bdc2b4c0a5250957dbbffbac451315d8cbf8c390d0bfb85309212c4d09e16591070a29606298c94cc406b53072390771e8777c54d5e6135e445782f873f9b300552c852197d8ac883ed32180f96f2801e60d79ea82b43b7ab2e1d94ac069757aa13fb357575e9c07dc4308191c9f90f68b4244de236e9791ddb0d0caddade33ff479b10e52aa2ea6d16428b2f011d464c109b2d4298fe36473b128b07c29810018b03dbbb12b97dd9f003fad5c6c4cd6b1f1088b1223a4159ad9367c7bb82944ad304322600a6188baea373f4a3ba60c014634c8a26aeed17803b12c12f8fdf460cdc35fa61274095ead46a1e07546bd800a34ad7a6cf033c4b512acf52116080c0a6c614c07ab8ec43100e59479eaf2ed88800c115bce95f17166d76b4c3c12245654378227154ee01fc1ef59b916fb4600ae60519a466a4a8f44c0f4615831a5001475f8554bbdd14b5b27786da434b274bbaacf62ea605350059e25dfc480bc51d4492d615f8f49d7581ff1fcfab9d5883baf70b1aa6eb8cced293688df06359982fddb76ab37c04b6064fade9d25bf010c9566110aa563b7aa4ad714fd3d86e9bc22e05c1099d563f3ad2b5d16e7108d067b961b936082b28e83f6b00b3ed978af2ec70b8f2c2f0ccf9db1576398c120518f4182ce2eec0f6d2e6017f10883c2da30952d1bc2f2a39b77bd0fa2c4d5aeec475b6456f9ef9e67e1c2d0297e7d5f66678091857ddd3b22d3698f6fad114fd414a80626b4463cdb54f305f0fda58a2f60d199ffb29404acf68efece3e52d2426a5ae19399e1072982b91c2164853dd3b478061717ae708bdf050f84cba4d0c808f48d547cae6f244a02a8c2a6d0520f151cd807d51b39d6d75e32d660850d466c6100f13f4164d7f80883ea003a04b42dad1f7248b7e0114f3fcea36031804a511112a62e52b04b89bc03083e343268fd4d05b61db6f06c4ae488f66ce60a3ac3f19bc6dba15f22bb4879bd1102b44043b8c70b4afe49e4741495f79299a95a2d1d44afd433c990e523f2dfb85de92125d9f451054f886149a06c8a6d529d87908151c1fdf9d9193c12f573a63cdb1124ab3b0c3cf1e8a502ebc31833e64c7623fa47ede3cfacf8480f01480c3088360a5b6a84158862478fdc35508010259eb978a63c0df0fa5e4e023ec86e5865bb62b5fbdc7abdd09ae43d77f5a098bc274a9823ef774ad27b9038dbe0240287cf481fae265471f761f834d8d9a3bb5ef1b4c2c10bb2fc19f5cbc1751901da151512758a738a8b72032963cf202d49240a72e5ccdbc059bba7a3791ce5360e6ec477b7435a823e6881522cba58b4d70cd7dc9b11762220c4c2249ae23a6a49721d5be6b96166d66420942b1337664446ebc5ab3509e66b17d137da72daf29978c9a290707ed79d61a9e76160cf4b406c17924d0eb6bbf6c8662a045ddfd5ab06c90e9a520e24b06aba565d986c02aebf67e3c77c5a80d845f386d9267e4b9497671f529237434e3a85f45f9ace27edb51e9b8edb51de0a3ea401d6558186a2f6ee73c3c1dc33f1691f2c648a6b7a3b600aa31540a72adea9f05fb9f8bd506effd5538c57202fc4bb276fdd263f272a1cd9dedfe0870a0dfc836511aee2d74839a71d752b428a83faa22c1d5b858dc9499ee084008f12aef4d9d30f89eaf8b78a5976d58b3fb84347133ea3da9eb2ac0ab46cc4f475ff9c52e0cc556be15a70d0689023d50b0716f21e85821b2168a8fd064ef079dc82eeed535714ec8929620ce92619a5c8681ff0c14cda98691585475da29a15a38ce1ea60d663a028d0c45c0cf0758e00d0ac1190ee278dc5c0083b901917569befa5c07132d7bb1d556e82a47d43b9fc46b30d8602f3b6c4505d1e1e1a412a5cb481061fdd38cd26582c386e1df9754808c03546b41e0640503d1533f75ad1e74b943a5f3f351f9cf8f46e9c343036a6a0a33d2e589fc035f0316853eb8a1dbb1173ae4b6ddfd3d82574e7f7dc3eeed2e4f03ac60de0b14ffec6eebc9197a79411002b91de88dcbc7a426aa6748cb4f2e14830229ba5bd4a505e0b7d2b0ad1fecb2d7d7057cd49ef167baca5d9e1baaeb419cc6891dc96f63d17ca4d7bbbf577f523116ca4e631cb6ee099fed5779996c6f67bb05574c3b1184010e4232e22ca99dd16c9d02fff15975078c3831b602b3de0dca623cf94e6284bbb79210e4d4a772ac2cfd98cfbe34e483efac0e061064d3878800b71ede0f5c2b98850d3e210ac1e7781fa92370c684019f8ce48cd59fdc980536d96d8c6e0e1b71a3eed8132c90cdb931b604c99193cf659a3f41901690478893cea18b2c2aa4818e91449030f3bed48ccf3f121d16a3d5039b1d20de89abcf3adf7e7e3080e83e4670e3edfbe9410d1dc1298358cb6ebb79cfbba3266284aa80cc283bd63bb3f276f5562b03f13f013922da47f4bb8e77a8aa9edead140930b32d8302d876cf0901916cf87dc5f903cd549253d2b35c4e1fdc4a4321e78ff214317b2cea3b1ee149aa23250487811e63d043a2127cb4b1a40055056282ef2a45481d0500df65b6c327f560fbf82ab0a339096380ae393f6d7addd1d2274214be33148084d340f892fd84af3d75799a54ebcc506a1944a42bc7f93453cf12b31ea912b8c754b0847c4b90b5869859805436853498d5a1f635a74e7234be9874848d305861804c84be1b7518fcc0683733611ef08d24ef465005ed38675fa1b1fb672fe96a7e52ba9e4366c4f20168017655afe9693f85c6a08e1c1553e21dfbab9864e449511c56292a6657e7ea388bcd615e97103dfe02ce0e33121634e1c4e6b496180df48a4e2a70bf96a272323ae58e771d6b97546dbcd6aa7e5cced415cb661d7d9469a186aa16dd5a5fb310020cdb079e6afb8e6dd4ce05a13e7c3282b8b9986fa10a8cc74ae7375c94f553a9680172de8308a4c13dd1e39c901de59a502985d10825471aa06182ec743a0decc03fc06ccab448d7f6f52d603fbfc0f15853d1ab42dcf8a227d51d986acf6f7cc10324cac1c3314b9088794b80b73fd91eb2828e46e195844e4c89d1107b895adc06f4ef9f620758224fb6ba30dba71660e8cb68347fd2a614ba1129b0e8b5d6314f371ae82c827b23dd697bbf7d8eda2296b050b453578625928f187ad34bad1afd68ab38e5ccdcc4f3d3708b458dbfa1f1b4e945920bb60833666c08beda7d1ce98ced4cd47dc91e0e0d9a04fcf2a51968078b5352292f16c081cc82b7bd90efc63899ee3afe35c534f9eaf78b1f0b3e1a36153ec493470fb53575da7b559059dd3a5e4dae1ec0b5c8de7922981e2c6cfe2626c664da2307013804c37c153f109c199b147df6b78382c014673e1fdf178fa4e7554294b4530223bdb70b413534c81c5470b6389be1efdaa1f8ef0bc210f84941f9026a71b5b42e026804b21ab81f39351a91c9d10ed4fba95eaa82b27b91c7e458c2979fc26e5b92bcda7a9c9611a0d2c4b69b45eeb366b8ebe27de233b4d90de0de29e83e803b6b9c6bbcd0172a8b00d7e9e07e063c4602ba9c995b90ebc09c86c7ab9d3bff16adf12ac2431928730675c6d0bd16bbc4ed4e51485fb101a5861519e591d2b322b64b2f2749744e94c27062f1f647946212bc86af6b95910e9fc89dc696207014eb496f01c287388825c8d2ffa189258a1990551f1948f77a6024e685c06ec1ed37aa75a3259a8a4880e0579f2aae4c044325838bb598516c280e50682c4c43bfa9633f5a007e1e746bb610547bdc5d69d23ce0c71510ee3a5c6c7e0ac684977cbd83b8955287db7da12f1499df3143d8548579e0f6c7cf44e4f6e4c4cb5f7f56f02299d29016f404cfc18e9423bf9bf641831556c7882bc6bd04cc57d7480ca2be045a70222094b674eae83983cf76415708f299e3ac0ac786e6401ae34d4fb13689ed68dced9059aca56e5c361ce9ffc2dc2880fa02282832a74e8b319162465e985751b0c2513d77cf8b97a0b40f8310da92581982a37af52b626951c29376f85b6b2414f2b152500b9c1704a5610833ab5fe85bda6815bfacf247b0a0b1d926b015addba8e2aece83e05d4e057409e7be38a736269d9f14c133e5eee2dee92116b6609ad4a36b753443cc403bdba6549fdb8e8bf045cbd3838f2c00153f161b3712e6f7c6a81df88a4b6462365680daff0242022c3b5d11ff3285040f66e5372f17725041f6fce1b29ebccdc60c3dd986fd32ae34d6f85e3c9aaf4cf83f866a5252f740537f317e7d5ea4ed5daf5c00b0cf1ad555a728466fe6280db3e5e53548beeb15fa395ebb4d4d075d22d488d91d0e1077743d27122922595763edd1c1bcf8c52706835b91a3a63d10ce21cd2e3e8b217fdea281590c5ed80fd0c1b0045e5e98b074cdbdcea276bacf82eb25e4f34230c56dd2ad7e7ef91940406c772b188a9efd549f5d2d16e8891f81033245931940c25617fdeed82c65a09b61fd327b84415b805a9946e68a83539c9c52840e503ca2c20fe4f6c8b46200118917cd15ac604b6c21771de0a2530cdf62213d124b5e397373f32b6611dd6a6967662d460ee20e50b2f221df4e04d53240510c53b5e296d8e4012bfdca7c3d68b24f374157c52c154eaad0ea408bd68aad00ac3944d1d3e9ad631a0c1dc78d12aad6b4fe73b561a7af226b120a3ea1faecb38bc6820922d57c8f6b35eef4fe342b702a9c20c19dc9c57ce4b8706c05d35b775c0bb6c0a9a0269802b44616b562eb037dac5a76813d7a79237979f344ba3a65bd81cad07c9b4c74195597689be6fb85984279da42e04d77aea927153010b4721fc1944e25486178020810a556dcf554c84beaacde7c4b218cac5d9f9380ed2aa39380090852eeb38bda85dc3d3029b96ed61270d66a6b08d80341efba7b366ad1f5c3aab7daaa076fc119fbe9064c0ae73e8601811ab7de82468ad78d11fae33b0daa935a0d19b3412a62760851c7bcbd5ce6b08d082e1a00b222389e81765d0c2ddd4c961e534cab9f198a69055503c4339623d83acbef86c123b50d873676595ae4f03e3cb5d57833eadb30e4586a999701475cb858d38c91fb6818d2ffa1927b82f2376a82ff25ef987f3d8a96413d650cf2606115aa7a874af433be0cecc51bea99cbef81b462d24db028a7ade516203c9a568022d0e28706ea24a9771b27674ba2efd566881648722e214b28e79423e4611a9f02708826331a2aa6124985585a8af28756e9d8e723d32920ed69ca0ef511fe32f3d75a43cb223ba72475d097d9877e6f3691f31f0b8c8fbdb8863005fa708052080a6515fa814737cfd18415f2d9939caaf00893d193fe47b22e21669865e7dce092180d9028e0019e5e024ad7ac77941fec01662bd48b3d5eb4093917464dd9f5847f7c53b2a31d83b2214f416b7ae459016a0cc35129a0fb7a7a863d47145156d04c4bfe23845052f7af54f3b1578221849bc62dd420fbe99e47b9ebf97dcd129affc7ffa9156cf08ae8a57113959acd1e20e874a915b26e1bd900356d0515f5cdf0f133cbdfd194561864ceb7125aa12d8dadb0585a010c4635764198afc98da252a185b486fdfde5cb05bddf470d0b3aae0f80de4d8104c0b0888c3ad2374033a852f6fa7279990f0ab4b54a3970a678628ddf98ce02d91160adcc4278da1bf8afb656b6843e4a70dd850d909c1f5a0ebba989ea32c5c658cc5e627a31b550d4470a8cc3659af3b108983e16744c2a96ef8aec87aa7f852da6a5958558113cb81ea80aeb5de0a8c6d24ffd341e592c39cc3b7a117ac7326dab52b2e44aa64eca64d9786a6489890f717c62f750ac4ab2c3baa1d5eb7e4c2015a66baa59c77eb22ce8eb0026d4666d8dfdc474cda70b022b2cc8a366ceddfebe4cbafc4329fb893d2f073ed53641536c6f9d84f2f5d646d87212b18cd77fa211311ba78a112ef8e1a12708f39a2c2b191f83ed6683501c9fda597d9825bac133888e5783fdf7462c23e57379d7cba597b914d351f065ebd41d8e4019504094606efa2cf36996f4301a5817d79413f9dced5153f4092b651775440a84bb8b5dc88a2a92e28c38506ec493f7d9ef6237d03680c29f434cf6e64e5619102216300708aba5ed49de04320028fc4e8720136313667aaa539144b5b016a68535d2dd145090ece8703da4efba40f7ef839ed36f74193da6032d847d1cb79bfa5a4c1612c5207589edd03178669e6257ce0800df96048fd192cbf4113d4c03ebe21a58bb9f333ffbd858bb17001861389b778f70df16c0000cc171bb66137109fbb161c1b36ca346740467fe193eb35771432a502d56092569b3cebac99a1b7bda2c0685c6efdbe76cf5f44d6cc45ee4084ba9b26fdd888c5741a4e4da413d0ca0e4d47ad5a2304a3de56a2a0893e27a86df463c2b838c944884fc2cc6d44cacd19d110ff91866e3dad932cfeb714016c76119c86ef92f17e75b3c0c07e5193ecaa18eb765b66dd4105dd2a65885b6b4397685bd7250aac6e2ba40c339d1afaa17a613412246158cf4b0c79a4037b1364c07eb958bfbd887f932ceb0fc80e045c0a00ccae1accf81b91e0f0479ed7213970924ba692e60218b4364073235803e72164eaf0352fda09b2d03dbbc10bf5256287dce189511378f5e183bd4b7dd733d2a9f38eb2d0408d98709b896060f9f5348f5c497b447769c4f129ca95a2b1c5e6556ddec87e7247b44c61f5dfc4c48d1269eb402885766afe0da0f1ebfdb54bd5c1e0532b31403ebc8c24ffe2ae385d075cb0b574e392be7dccb9ba8ca0062fe371148f6f226430af9d79a92acf7a7c1d3d6daa4ef097b5210e0ac4ae11afe44d6edf204e3e7e0cba31b9a2df3ca501a4be3d004c9e67f9680094da1f7db79b9286c257ddeaa604c241b4e8712cc8ca28939b5f167fdda79b9df02d811c54592ba87382f1d3a8a45cc81cf6c4ec41cf3426cf916e88864ed2bf11e70216f6082bd4007ef713ac0ffe31b010aa4e94a5de06e69b3889bb214b14582cfac6744af291a12de9acaac1e07a2aaabc79cd0593137c7fa393d61e476e24099d2e561c1fe8ef19847d7a2cc49aa1098dbd164e39e44188221acbb9defbcb83fbe074d7008a23f215aa7a1e052ca81e3347356d4347c887e3adf37986be6669d29e08f9e58a81fb6fa45c037f1ad428bd549348738a20752061a2fe7ba0af0b5e54121063fb7e5298910e693f8a1a5c7449ab7bf6994620787438109a6db6b657264faf1bfa4d03405811b367e18c7ad50c5fcbc1d8ae79ae6e00cd4f47a4969ed92b96267bb6b7b2d158a9066217befb4c622c0a4a2ffddc2e40a8adcd085a59b1518f30c011b7986ea18629dc78f2efa92465a83f40b2c5d8aa8e299e455c55d81af78779d82ab0fbd5dd7b6ea2d648b2d6431c454513a26db370b291ecb0a52f208769411ab70e67ce1c245d1e066a0461db3f0957fdfc31bd083e5a58c190921683ba80ac0f0e634fee4a06e0554ac495af5be633fe9278d9ff188d2869bf1e18dd33b29a5e41011b08ae21b3710ffbb1fc24c604837ca89d557f0884b524bfa093bb14f2ce95b2ac0f977962d8c550ffd3e1b943ca477ab9622bf2f42d55d855439cb1d911c3dea6184d064ae174e0d44451e462b36516dd753eef62bc4ed1038cbd55a753a6a214f6b69efdab8a1bbbdcd726ec0f43a6ef98e189520ffac0bd22335a0fc73a08aebbced8b065f2a21922602f7d93bd909800dcb49510b911e1ba9ec4683acbf107f0211247d4fc703fe3dec434cc017c14d81fd083a9e27bcc8b28f6bda40502674d0153d8e42661926e80a791c0fd921ee02b0d1cb5baca27daef83003f879321d07a88629fb63261b9d327fd80e0797dd5f829a8899a7acdc9a2fb632ee50d68e61097fb967f10f79ae3f48005303c4b053593ffd74ddea5370475997660df1d99b9713b48e96e49858c8b0c8ba5dbd8f3d3f5ff545c26c5fbc4d9416467aa72a4f970b77434a54fba472ea722dc6647aa6b59f169812e8041ddd9cc44462813268541fb91114fd3a47de6a65a488528fb262c0a8a0232d012103ec7bb96f70e8f69f16f67f8a81a11606769df53d3552b73cb7cc81c7ea5586ecff45c91603d94b0029d44152630952eaf2ecfc77310221d9925f987061018cbc4148154b90c58f936fe66f0a9f44c77c50b20ee66262d3aea8fec277ab51c3fc967153f89aa425202a43887f24d54d9fe9455fb6a9e98fecc4438b782f75d795a41ea998243d842c1d3c4d902e5abf5f64bfa4d335b7232be9a20411a5bceed109b549eb46fe91b0358013be0d7e8ad0cde564da3b2bc3c9794de68d41c2dfcca8c340b7087f8b45f14de888b9cd0da3cd0b59907b312256576c0688fd4d9da9863b1c389c89c7a7674f5c8ec2c06a81be44bf1a898c2785b57860f12661d23ccf20549cefca2aeba1faf7dd09217d1945157ea7bd36313130008941002fb2934c63fc68858209499e2a31e936509de9fdd5d5e374b2700a7b0e5d680b7dc25c1d981002098e783a3501cdddf2979fd7dceee3a962083c2f585197efe876898c4e23728cf545bc0e6d1678a65bb78e2018b0db7cb8cddba3fe52ea860b6c88c745fa698435e80aa8a8e6e0c7bbbe7a99ab0ab6d0e4f19040ce27094df4ad14a996f64271dd2b5dc57e6a171acacee74e52be5b6f1cf7fb1dc5b86894eac67eb82b4beb199b29691bf91c496a244b29be69520fa8c1966ff28610f543014ca244b57e6402663be079942bf83b767e4fb44afc00db2a9dbe62422e1320560d8bedf32a9c6aa6a1131879db25be37e40c22d1f0f0e75549e1bd21409fc8a612ff6a4fd7e4997b55c1f56e8779a874385d8b004be8288665a7dee09124c9a173340528ee73e29fb1b43f51f143db5de5e86869fba89e29c27129dbe757c1aef4a0b4aea2499664348dd455cc6f96876d0bb56e1f520ac4517c689db730059003167b47444485f4aa9c516eddbea31d6e0daa1c109086d92e160067f9089b485fcd882e8668627a8f0f376303b8a9f68630a003df4742bb353c98d880371b3d46120822e7e0ba576cd40d041d1ab572ea601e08e392d135da3a82355eafeea1f3ff31d3c1dafea82a93a315cc823e782b4cf6e8425dc3a99d77e8f02951d80f9be8d689a296303f5a56febc21feec93518e34d0dc353d6c7df01a6e00e19d026b896a96ee1e320f9260705fd4ceb1270743ef38cd9ab639644483c62acce4e0eee07b1d45d2d31425d43a4ed34b93ac52b689062d01b403eebdd3a090a070bc77e0d6217a1deaa7d3148a7a87be1c683aac3a585d07abc7d39458c892a1e1c8462d0b11a68e42ba69fa25235eb4b2652838ca8ed388132bdbc1a23a9aa924ea68d5412f87d81dee737077e8ef4083c38a83de35a4af67428edfa91d13df749f5745430ef438ace300ba0e92b1b42816a991d3641e19d9a0b51568ba5173cef6cb2371b4582023e568b9a28923d0a1e628efaabce66c7b8e0966c40186314b395683a6d9c9489396104446403482a40c8e4adf71fd1e1bb5cabe6448ca6a7071a48c864b72a363249db640ca0a6683de9d4d29fba0a570322248232859d661b68e7652103aaac53547c3a9423cbba37f743465b93e877e07a2d5aea466b965998866a1c30923e448b202475743eaf233708c91458c6b9510751d7a72601a8721d734cd2d245e6b96b6b3aa285563d25464291b4c239620cb41a48e96a685221368cab2be43bf03bdc36a0d5790c638672c346878700dba77ee5a2e244c1a54924174c8efa2490819014e2396321d741c30e6104358ac9bfe4e9ef14252c11922641d48af438d62e33f5634f4dd027a071a6a5e400ec29a0e8af1514c28cec8ce63f38feadafc106de6cb5b99a17d8dc8265b61ffc8691ede2c47ff8eb4756e66846579686f7b197373ff89a6139da89746805cddd41d204c707c55605d43ec356e58da6432d242dade7b6fb9a5942949196a063f06d1062d248ffbd427bdd32fe522e1a5dcf54b837932c2e8598b615a74aa620bbab2a8330d4b273b6708963184725011a3551192b1a04a0b396856e191e7b74c0559936c69cad314a7294d53987a8a6c8a914f299a02a35388a60cd529415370d8249cb4bea1cb0942454c9647b21c79beeca09b73869e23b11b154c919a70124d50601ff0122c252907c6ae47657994c40e7914b5d88d070894c8110394254a2ee0c2d3c3112f40aaa10a275f40d09eb8a01dfd782c9c182d32ebe5a1594db3374b2c3c51e221868820e418211c7010111292c4488e4e33fac1f183e3fe782c1c293e64f608be4f3730a1657924450794a72860a2308181e9c245142633c68f162589a669f4c763e144b1f2b2a903ee88e011d3cd488b0cb73d2ae2894c878517407894b117300c05bff88147fc62086fe1bd3d40b8f75e4d6707c6ae9f591e4101936fb23c8ad203ca531e45fa52ce49fbdea6d23f1b94b664b43aa5c2c8a3547c4617547d46eea35a7daa1955cde8ff8faad936dbc2b448e7db46d2921a29cfbf73faa55be56c5739db69defd34ef7e1b8a4b6d282ed5614fd5614ff5ad5032724e3aa59cab2933e7b3e8fc194ae7cf396773ced89c47e6d0143293104da129e79473ca69829c724e39a79cf435bb9dd6ea3dab6ca7b556ab7956bb57bbdbc6755de7791f2a95f29e29540a7ba9af0656ad54ab95f75cd17c2f1957cdf79ab1b969dddc78cf1bd55763ae3c09ca7caf99658b352438d39a69b5bc67ab86feb4763a6a3c01fca4f44e7f46f3339a1f8d4f8a916ab602ea9eb1aa6661542a6a543f1a94babbce0fec4e93c50ef3d900330d37a79647456e95d367a49af94e8b6854cdf24cf274ec49fb34920ed7f1f414260c18305fbecc9f51b01f40c396b9506b4680295934264b6fed41dde59d281f72ffcd5c9016a28a244f4a28282594129a2d3d5fb07ee99dae4c95a97ee92179fab9a0a1ee196b8fdc41dd3339214e48f78cb3471899599092aee2a8b2a48d8a8a8333ea268bcc19b90fcac977fa3b8843cd646ad8a8c8ad248f2827d44c8648f2f4a3661e24796a2b735238a31639a3165b0a6a6694fb5340dd330b8372423dcdf72b33a3a17b491bfef55ffef56fe439bbddf35ef4ab0dda547a4f8a1a6fd4af2ef76dabdfd5aeb5d63ae545f5bd365ef5032d727defce72366abc91ebf7baf47b5150f2d4ef656f22a0bdfd1d9bfd5ef546d6be1795f26e1cc775dbc6f9085af8f3e33b1b573b97ab2341b3d5dbb65a3f8539c66df4d7ae524a6bd31adeed7b49d9715f37d869d3c3d8fb46d0de7a1f095a685f32d7ffbe1064ae4e29a52fda7d36ea7f35bace03af2ed32ed7308539668ec9f495bbfbd5d872f7c9fbd550854cc3c63eba2e25bb6eac61f398174e580bbbfb734ce67ea449524a29e55694bb7fce39e7eca6dcfddddd7d879adc9d6a4694d2ea54ab1dad92dc2fc6e0fb9ba77d2268d9fbee6a7fb3a63de8fdcd5efdfe5ede77ef9f0dafdf59685c50ee9a943b1c654e0a812ca350613eb79b1b14214a7f7ec0b4eb7460a84f654c20b8983f47c42de186382515491d2277fab925b2a7c620c52ad422924a4335413d528dd42295481daa43b821f7e16075862a43155263a830d42052864aca58714335493d411fa0a5c2a919968cddaec675a8cf6b7130154ecdb064ec7635ae437dde4a8553332c19bb52e114457d5ec76d576bd1b46cde88d88498621c8c83b9cfc8156db014914b29a379f9234e09a58ca48252432d36f782dce9a701391e7287cb21450ea8c517381738205206fe7eee07e7836b41ca90f97eae0717c4f14805b94f05f21d16b81d9c0e2e0757020e07072465544f050929c0c8cd90fb39a13e40ebfb3b4cf3299a57ad687ee60338db97013fe0ca36cc8126ac59e48e152976162976ab7ea94fe4fecad404e8af4061059a016b5255923cfd3260d52279ea96ba54b9a4c0ba040aac5de8075624c9d3ef81f58ae4e9efc08a45f2f45bb066913c5556ab489efe0dac4a489efe0b562b92a75a0da5bdcd34606d81b58bdc692dac5d244eaa32f799b202c928c8225799fb705ce52a57394da3dd5a8852011057274ab4825d499eacee84e8fabb94f46f97abafd6dbe6dffe4dc32b7952393ef38d3c3f1aad1a6b7888432c79be17abfbdb060d41b0bfd7eabef63bbe9b08dcd79e84ab71e1ddc2ebdf4bf59a366638c3c63f3535c225e1863fe18fef58d46bbe7cedb33143aa7d2f4f66edab31d3dd31626c5ff8a20698fb5ee0362fa7d94a5d3687a21f5a15e6984c027aa3b6e644f752e23ccef9a2d95fca2b25751a065e3b77db795a57a9267586f0c8ca7604d9869e27b93ae747c3378f4a19946ede95f128121a58eedff2cd14fc80881653908b8b484d914a82ba826281f342619c2ac80d57305c44ee97a92972bf4c25c9fd127525f74b140bb95f7e2ec8fd52cb148411c3e30ad612923266d3e611e9a070623826ad20b97f32794d5bb882794472ffeca0e4fec989c9fd936392fbe7c625f74f9b29e8628899980bf60ea1cdb41aee0c11b9bf5de86e2ee45ec1ee10b9bf35ad86dcdf5689dcdf355310c7896b667368b509758206b9df3bf6a3cd91fb5d0b57b04a9dc8fd4e67c8fd9e29c812028f2b188ba81d096f27cbaa21f7d3990d1d89dc4fbb75c8fdd452d00b028f2b984cca985a70e47e192029a3ae603255544c9348c5f4e3d492fb6bf5b0528d7e09057095f5ab62ba52c67cba5231e519ae6051f0fc71b56d28a78165d9c18d6db66e702b9066999a9a2a7d400bf08899c0249184f444062b829dc02fe00e83673a6cb23cb26282bc657964454811429dec523de0d95ead763f0668df02fc4a9ed94e9a86dac8c94926d158ce0f393fe4fc40a783f427b8e59c1eb0d768a1501d68e514480094b1bd914968915e5a34410d4c1393f52313ac6d3441fae434bb3f41b73f95f4d29cf64bb01498a367fffb68d849420923aa0163eabbd94eabd552617b65eac0d185b85132cd591e25d33889b49ad303a6df1334a145fa394db064aaf36915c247747200b806e59b692cd3ef727ac0236d80cde38d4c5ba4d49b35c8d3f0c456324dfa26d4099b3090020181de329f4e0d76ee72c4d0bf41b56d732959def934868f6851930fb59153be39e94a6d468b68d12cfb5fa2392d535393bf26ebae2ee443fe1a903b8db5511b39657f0ba3d4674e4e4ab27fad2d9326d154d2d6c1647f2b7dca241b7730ddd3efb1ecaf4919eb9ebef3ef0cc58329fb7757ca903de02dcb0ecab883be07d80ab25c2d7a0e0bf0289bea674093c9b77dbaf7cfe901fb90fd3b973cfe394db0e7db411d986ec32b79bad2c984a5cb119323a6ade8b0023467607a29c98d9ed026b04a74c42af1438920aa5571aa12e656994dd1e5c0ea3c40fd7e17ae1ef65c2e705cc628820433a992822a4bdce6e2097f5085471522982c07153132a7195c9e68810a723401d2c4087468c18f254b6449a20a9725203b400942a905463ea42081022994f81266c9519125ed88bd957aff782c9c2a4f6022628886602b984a182a60a82c51416202e3249ca81ca1e203152646e47cd12328201df583beede4d44ede4e63c7183f70721f24a0a5a02f4260be30df53a78309a781dcd3679443a406e5b1ce5c3a45b568b206724f9c131378ac33eea9ceba87f650f9bd280a484a7713827ffd97bbbf58acf6a477d310a7e64fe956f268ee5ac5f8decd7de75aaf366adddc31aeff92d9bfbe3fadafcf467f8df992ee35ee57c31b1bdb0adc924ebab09ad6755aa775e02866cd7eed728ee056eef79c22b80b47edbbd9badfbabf61a875dd77dc7b524a29a553d6b49f73ce39db286bdfdded75286befee4e9bb2f694d25a9435ed6bb59ad6853935e4d480fabef740fe507f5fa947bd7f36526152d61ea5759f365a25591b3d296be10ab28c82bbbb5027d43e5764028ff8b6485d1ab0b197fdd43efec385f8912459ca981cb2d6badb25b2c7ad92ee7177777777b743ee63dddddddd5a77afd6d65a2bfd4ad3aaf7e975d81fa21552997288e0517421cb4181bd3dcf531170b93ccf59acfba294865e2ae786ef4559ac96b3bf176e516e360c691016cc1982e7091ed422fdd162ce103c7a50a635a945fb63cb6673ced9006d6ca25953acbb9b7e37c5dcbd299629a5b5d68f868f3289863b9863027c7f7e776bd4bfb96afe1ec895860725c0f3bf570d9ebf5a3876586326d7a056ae61cb5aa4e1fc16da883afb5d2a27091e69b6f946b6d659d2a53b97ebf7666fae3fe7e76f43a8d54a2a5fda0897046b3fb7b6dafa79083257b7d37eaffaed59cd82734cfd2ef580fa9a779b1b8a3e75800d3be986fe09e18dd94e6b37ed0eef3bd47f4f02eaf33a4dd334eda67ebd6fff8adad719f4191975777b0f0db9bbd34929ad45b3a8563b8382ac36caa4a41c2398be954fadbdbf7920dbdfb8700b67f2fd1a33337926dfb09d344ddbda4b3293754f2bcb7f6a89bb91da47ba0349190ee3ce649bf9f63d1c69501ab39d56abdd8debbc0f95c2aa950c6ba645e3aab11969d4f0c6c19b1ae2489eb982455e72ffd7d4e5869f91e4e97f61033c72432a2a94727f46b01679862aa71737c0e34a3672aba7dea9afe3ba90dccf24d71f3f21f5476ea89d5a74587ed9e0d9ede079a81f71fed195bbcfdd07b9ebc4163803b2401950fa8ed7a1de9b1985fa198d3ff5ad54dc86c35438273751e0cc9cfd723fc8f7de0ba37d5c62ff65727ddd5cde1e0226b88f2df2225b344a265be4b2455b68997474c0e30cb241f68b55f5668319cce479f3edf4bc8e033d16f0ccddbcf393be5367fe7e6cc3cdc4723db21199f0067ef209dbddc20ef6420c1eb98ea87b9c6a3b714e307aa7d6d8f2168a1dac4bc2d4b59644b441cc27d8d76add8098bbe9554dc3c9dac8ca9accbb8fcc4a15e2907b2fe4c42570ea51ffbd0f76f1382eaffefc68dcf07652a43a7cb194af38198ee3388ee3f0a7be4bedc033639427138e627ed1a842d4d7189cb9e79c42fc473fb5934c7d318f798c0bf10dc71879c4b7c268f2f61ef37b311ebd7bf458eedec2d4500e0d78b4b3d9ccb586567f865f3b4b1dc9fd9a6cb6823339ec863b5185ddb091ad60d3013187c82385848531424ca92269888603164c86846cf882021b805c6084213f4ef0821fc43449216310020c2ce040c41339a4503212c40e43d420c31020bac8190466a00208103f9ad8005300162650207d71818e235aa36200537080a4ca16237ee800827c79819b2b5da0cc40830f2223f9640224848c14e99001952e4d607c450926454909c6ae6f42e9871c2f78bc59e678c1f23b1ce7bbbe37daf3c68697d056b674995b64ed2d27b42a113184f884134dcc765aad7637aef3542b99232c376081a145e3aaa137e8726c80e77b18982e282a620640e00c4e48b628d9d24948be6568550495e1a16e09b25c159a27c5dda2d8b82e9ccd5aa2785a62ac2c8fb43041c96479a48588162058698c4a6906577ec8784858f3bb99241c2dd414341e142e8e4bcd5116249b569647599cdc1c6519b2627ec7c2c3e23493e51116265a1c61b9f2f33b108b1033591e2969e940690a38bf7be149f0e88a979ca32b4857a48828ed852e670678be97a39b43c47a00e1436de43495c8a40c6661221a892daec42f78bef738701079c4b19940787479f70896591e5d39c2816e0ae1c40819cc44e8e677bd35d899abdea5b6dcb514a1cceb966ad56b905b860fa52d5766457030060409ebc1ca0d6690832f47208c18480c358dc4c3d5a563c07243112c3004510a62338526c50d14f4000490189ec44008cc0d2150406181131eb818c1c11625417492b6c4909384c4e3c58c10b41755f02acba3a422927888a1832d412e8455b4004af1e3a3e8d1a24b0b2c94585811c5059c988f1b26426cb41089a1c6b500f9e1521ae3028dd20c845a2f9a242d41f283951444260987d06a0a175450fcc05c5a4821f5403999e1d341062fcb1018ba2c412f704cc8b061b982e58817b008d143498b0c4a53ae5041e48a97a12b4840ae48094a7aca34591e6df141e9471e638850a6ce8e51ccfdf983dc2f6fbf8b892826e08124e43eb675846817ea47a3439a023c7fa45a77adb556dadb4743c70896e148f3c4eec5881c438cc98982a1e0f9120848f22081e74bfa34749f73874e76a96e903a50ce11e168011b74d2c5d892eddddddddddddd4dab6dadbbbbefd6ddddcd755e7fdddddddddddddddddd3de79c73ce39a79c53ce29e79c724e39a79c72cef99e943d3252f41fab8e2cbfe2903db82b504d76d92f655390eb287a826577f77e927d6c0b1de9a12662d24a1ad6b1c909b5e82ffe80bda4d6622dfa670bead8a129955e922a48cae8f757254919f39daa208f5e527615136d09cd9c227037933cfe35a865644ff531d6649f2de43edabd18bb5cff2aa627d9bfa35f0d55926a49a514e4197946ee1965f7aa0fef490dea1e8f08d9d18013e284704fb820b8261c132e09774493a2c5b427bdd32348f654654a2909729f1493d45211124c8a922744410c654f3511caee7dc9ee4f412f69a977fcb50e554192c7352f898b491e7f0be40ebb59d56256d3eedd36aea35de73dbbbb715cd779ded779df8742a5525845552aefa942a53056a9562b99958c0c8bd59a69b568686a6a6c6e6e707056ac60d182b668e13d5bb824385e098ede956087b3820565c1c27bb2689c8bbab171675a2d1a1a97aba6c6c6e6e606c7cb1183b3e2a25a16c8144661b4c58480a6174658053580b1220324a2ac42dffc806315f39cfdfed51067ae33c41da819058765597b07313e4c718115295680b0b2c4458482ef959a36aded5a9d52ea5e3b67110a9e79bcf265f768777fd119a5b5bbd66aed7c2de7e868363b4a618e09ed0e2323a376ea95525b6bb5d6da27f7a391f364368bc568e43cc15f03b6fb92d5269d4cc66a222299554c3585324ea1b2fc9c17b4ac7bbe2c7f5224236a9ded23a90faba96d56b6f9b58f06adb5ce9738228cac7990b7cdbac7c2b6a7cb6b513bb985ae4b492d07dc5d4a7a937a78e527428c068273a4ee77ee79564a299d7af7cadeb6b95554ade1814763b6383f1a29cc2ac48cc92ad09fd94598c99486f6db57c383dc82e39ca3a8fadc57c3834c5ddeeeb17989993e4ebee0ed1d1bba954e5135bcc975876eced9002f42c2307d11670e9ca024a28c19c1d689a4f91fb40bf7b370b2f49e08cae3e3b8c83f598927c10b920464cccb131d2b18e6171c4de429cba326c0e42c8f9a88a2092f4c3835b1d4c415da84cc864e07ca9197264c3e6cf2c5613250972f79be045e0c9151591e75b17243888526805401f264086c00359801092735784296440b54481e3f3f28c0c1693882c869f02364e81e881f1c38948a6490054a0b4848204ad00cb0142c39e2881134a046d4e0352070380d3f382e13429eb0e18921978c7ba2bb654e964871e2048f143431e3871478a101081f3754717bc45cf35c1e667537ee229072b70b16f65c5e67d9ff4e72b7c7f2986860125277c041882342d38791a422745878c1e588e9051fda9323a6168e987474d34a2b0dec752e9c77413bea6115e8c0417429d2c588076c035e22881c5d82f0f084b4450aa01d3964892226831210f9d114030d4438ed4926c1800483199e4ae025073788782032c3e10a0e4e7c1063e4c763e1f4907643220897262e4be449002e397041c285ca6fc15c9e6c796a6579c46589621d57fb09a630c7805daea0940370ba83476b786fa6fe20507003bf771aa7c5c7c92d79fa6f72bfdf641f426749dfebee3f79461f21b318036aad4804cefc2cd664ddccf271f1392b1051f8c9b5c6d8d2752949ad8e126cc3f1c776f76ada4b6b6badd74a297dd1eafe72db9dad7834dbd6e06c87631d43c828fce47eaf425a95b556116aadd5845a43a8b5d6ba7d7d134400b78fe13b9d0029f6cf0065de065cf137208be7c0d5e3802dfe82fe316658ac16275042dffc7c391f46fbe0703fe74fd923e2c07386acf67470c0e394c54880d8627bdf8ab7f91d2cfee6518f3fd4e36f845901b9d3df6b1291627f05a4d8effa9a77fd9c35a1cd3a38e031c6cdfc157f03041bd0e65b3c0936dfe2e7b7b8f915e10a2cfee65758f1367f038418fefd9e0e134cf3feadbfdfac9749618ea1017368f12d30079c6781f349fbb4f8fe89f3333689dc87c5f74d7d06dce7e61b3da35eca47a95eca1e0c808c4370e5da0a67a414591398e14865c016e00afca0c55a7f05285d058e53490726388978edb2df0f80e6b1cf811fb4681f0784d13bf66fc0eb3b7644e1a7c518e07176c9f7bafa0ba26304077941275d883062d4bc7c1728001db9d381145d0208000f0c0078b023ea4819f767e4bc70d14108c65c3f7ee0fab1e700aeefef58322b154d6be676dce67d388572cd88b164562a9ad6ccedb8cdfb700ac592597956d1b4662e4ba6f34a65c19971cda76a1ef57935dfdde7ee6f37cca1c6a5bdad7169a1cd15a4526cf5ad42b7ab71bffd8efb1af7db93c06d57bb776a5a5bebb5524aabbb6d31d66d62b5a1943c3586ddd2e588d1c101cb975a971f591e2d219798794b302d81f4248f942e31449ed8970822dba0d3b96109ccd29725a425294b412c35594ab2746469c796279d1ab820911d7069c22587a51f5c90e4f9d76e40ced9ed4e69add6cad75ac2c812069aa6699ae6c2d968a59556ba8534bc4058660fef0be6cccf6c65ac70bc915930d58fce440529f68fae439e373f7a129c1ffdc88a1f5dc8e83f5c474f5752f3307a667e8c11769080192df063b84f07a11715197502648f47b917b30847bc22c409c70f471104fd41ef22775aba2c8aec712b45a0cd6e45f6b4bedfb3c89e2db2d5fa9befee47e346bba9594ef75a20cd2ef3e99e5d067676590b9cd9b348ef220b20e500469c99fa00069079290bb0d250373fa35b2d50cbe0df84b2b30167be069c99f99aa7a199b133343335df4d2f847bb1ebef267324d0936a404b5404b79efb1a90655403caf4d780aba61a505504e2195403a6b24e113ccaa41a1095758ae0991f5d9669fe669a70f440a6f91af0cb2e902604479125038efe65f427e40d723f76190afcc0b10ad518912c090ade6e652c3a35c59c33854b2bac3fcf1b2a518bdd5721f74185558950ee5a7443dd3a2545bec38552f26cf651a8b0ea14bc597e5eb7a639622a9420c2c4ecaf82066c2875f78237e0b07c82f6f6b5232d3627b9a17aef57f065ff3e0af55ef7a4c23adec855044a7255fd28821c720d47144dfd8cae31edefc5b8c65a64edc71655e5b5c83854397df8bb4efc9501f9fe1c57e0867018428aeb913f5045456e282cd327c43ebd2bee29f7328afbdc6b344b1e6b4c4631a3d4824191fb6bcc7dbe23b9d35f25e64a5f9bd3a8c44db62635858648008000003316000020100c090402912c89b340cd7c0f14800d66884068583618c863a124476114833114639c01860003883104196588ca800060ca2805e4112bdcbf57f8c9e468645a4ebee14164e3a00d88d866473090a756ac0482e8dab7f3c3542de536800d3e8200753b9a5b62c34cfb79b872a498712fa8ba413326f91c484690fb50948dbf73b175740ccd6434f521153fe70c891477c585cb4dc31ed30d723f4a1ee24f432e506fa8698c603f696c934ca0d94f16723f3e29f0b21c39eb48527ffb6f488f45a14ccb3d0723a521ec8deea3a47c0e2ac4c160041e00644e95f8eb3739de147bcb98d16bf7c9f9b9125b88c40f24c90eb96b24d124629b16e65f50c32246214bb1b8336ccc171fc6476991f2bf5beae347dbd88ea3966c3d6d7ba005b68ec1a71ad65d9da9044737a9af686d93974b2f5a55414c4eecb2b650b463115bc9cb0061266e8d288ae54b991df2ef9b9a206c0ebe7dc51ae10213f15f484abb464ca3886dff9b1fe1d2558936060dc6899fddb132b7ac47ab299670e620548aa13cd675d020f4b16ef77c15ee9c37eb5728632f624f051b2c40b18f17db1e92993ef3c04a241959525f44f9c802479c48780cb4153b75ddcfe51093ba308239198424aa32a5b97cea4a6f459136cb5e9dd753fded85c68c1d63fb7dcb6a5c5e6e6b927aa4ceacc042a6d62aaefacff527a7c6ac6830d7cd93a8cd09ad71bdc7ef5be3bb23b439e083d30e3dfb82eb27231804d19799a1319fbd277881354b30bb5093aea290d97a7c5419535d40f3f6631a2aecf3ef4a9191ce860ea44d86581e53a98f059d9de6563359bdd365c1eedf92c9dd241dd06213320324d6927e0c6c54945d620c3bae9e02ffe8a8a8061727e1392524f29e2a05b02fe0949787c64627a6f8d4c6362e0c711419c2d05f7abccdf5c3575ba0811fae739d0a256043c1c716f098e02f7aca805861fa35c897285772276608b30b50ef48ef1ffc07916473620965eb4ab1ec21903365550ad9bc75d6aece520d807b0ef4352e51c10fd5170bdc7df0f3978125d60c065dcc2278a28d6c669d23c97ccc997e97b415a7f558968a32a6d2609fab5b308b7c132a3e9b1ae7adba398f2cb28971c23e79f6d989da5f6cc295cd632bce5d1de96188b0f2115bbb14ef6e9a0152e34e6b38a7bd5855a567cc82d37305feafb432754f428dbe734a685d855133a2d80c5f8c30b6bee08e3df162dd2ff791f41c5b834162013a2c4d2a05ceae3a4318276b06841b0bc46f068b59b24e4b375b95b9693da445457a9429dcbb4f11486f7eb5eaba0616a106918b4df68fa82ae92cd8eaa337ec6c998b2639e079ecbf5adad84609b7fc29d3f0988e746c4835880bf0bea3e121afaca1745016df38c6c3c894b79b1a2d982a37e20ff8a9cd04b7b21ffd78427c352044a59e40942a90b2abb1cf3fcc3d2147b8945100096050afbf2182e550466821e6e220a7a0e0fabeeae8cd8125504084e6f35ca8bf7c08785ceadf88107828157417f88ba03a612e10b840a8bbca9ae0e7c4646a5e168859b6c888aad4fb35d25ff3c23c1666d4681beef2f6b7fc2d0b6dbfc11db072499dcc140db15726c3ac03341c31b1b8522539b652ea667a2ed21351e0a978732e1d03bf2d34cec22e120736fe6de21832450662987e2767544e4a154e465f42a032440977f8176ea24c61081530cceacb8af72c2ea171d3562cdda7a79b866a9424ed64a016354a7d9a6e367e21706768e60cdf8706f885efdfcf204af9db72810811c04cf5ea7d248c4dfd6d007ee14deaba4275793b3121f152e1322a03a29781886c40d4c2a05d98e37f611a446bd687d3afdbc5dbaf3ff56aa47aaf9860cc07613c5e2fde1e9a60504d019350b2e6e63d2a100d178259160d4a218148ca24b5a5263a7917424040f95146730f37d3700838061296ab76a784f35384b4e40c015f29ec320aff1df2deac5d289b6352e66033ce37790dc597fe7fa9b1de02907083cd0a939dc0bfb63b77962b860935445d37a017ec0873218291d8c113843e34e4b41765fff8a30c3f3e29ccd72f05a8f8c6605698e2ddf364abca32e776b3c26e22b92296e334b9d6cc92fdd458c78becc08c351f8b399258940b9450a2d237e5e0510a235e12522f6195b3e99858dabf8e87b7a03424dd5ea09c5e1c71f02d810d774275c09b170c08bae1122790c580688e75453543b9b863f069a9dc45dcfa865c4938218a15558fd0feb78852a6cb20604bb2ca9d0cd1febcc82c9a6db1044aaa101d3aecb8164e4a8137517eb606660753cb1cfd455bb98a19a18c27825d2942bd20da0ea74d4181838121847aec954a4ce82be256b9c4400054d52d941b515e59b4e75a7e7f474aca2d63ce48c3accfa9dab35d2c3f4d9b6a7c62c2e81b882607217df151759ae02882fc891ab0a0f8f5315dce7252bf15db21e530a1099a235e82d5de1c9bcc05c4eebc40c4a724c01316d216ddb899a907a12c9a7ae3cabe52c39eb4a037af2751ac5ea9992c56ac9ad033764cbf6886068bd559f38faeb4128f950e354f10290e94253c0c0ff0061f454f959537f54a06983dae662cc24b8c458fa8cf523465fe7234095220ef1b921bd2d8bf5604a4c98d35b6f287edeee42898fd8387583caecf0ac0e284e5ef9fbcf4b51ba1be82b40cffdfab308f0198c725e0b745b4f5d7a85b5bac36f04443b55ae6bfc7cfb00afd333b5420a4198d3345c290675b2b0d42be96a76d85b33439642d562fac7082cb46702747b92b797a21536d6e3fb05630891c3b13b9698fc65b35ff7ed62683b713b970528dd56dcbd1433d8b10d6aa77f6348849c2bbe076b92083117e3c6e454454c432269017ea95d19bd813242648d62f8585a8fd92059deeb24be44fa6fc1c2136f47d63d9c835ee4bdf53d856b433792cd719960a5880ef29e12020715c46a89f96e06dbd6932712c4a66835df537649e8e2a175c8319cf8b06c6d227c98b6a9f54f9c4105841e6d764949b84101de421e1d6341b95435e221c857358e6c0024e8a5c0628576a97d495d3dca868e30467b88b72e7e2f7772071ae817271ed87ae71c2922e2b815c493e03a4de8d7fee4c2b74a0534bfb90be6eb361285e6fdc263458b770056ae9d5734b83e47d7bb8b7c8e88ac95f7aabdcbb38947d8d706c80a918021a63f48611963cacb7c619b7a92c6b7d55d26c7704579a562260cbbde879dd27e11e4474d5b69156fdce0c8779add42549cc20db318566ca7e02c2aea5417721aa5df10a3c66bb0b1b085d685c42d6d15de38d35888e75cab2eeb26c44c477b973a6ec6a8f84c28311a10ffecb238909916859c916dbda9a86346dc2f97215af99f5c1b3622a916fa9f42dd1f205eb07bc004cc15c4a387981675173571330df3878a6a49d3dd91ed784ef3f07b2fd92e0e4c9e87e579cbad00e16273bca2ec4e40492542482178833251fca483666dd8be8fc69a69d5d196a1186b57440485d5c0a9667bc9cd2673f3ca53d27e1c1380e56515fa04ddd60c11b3320bc094730bb362b56b94488573910cdb5696c9a0eceb31a136f9acccf14aae7057bc64bfff4f238e44c0db77769c4c9365353630f7bb09edf0d2ca49a12b6571d087a09a110b629915767ef6e9c6f5a4dbe4eab70d6dd8b2638583f9353b333d2a3db67cfc93b18d14276f9cf8bbca0b93d4d2af5bfdc8a3eacbff7a650eab897a637365cf319b5e7ef555e28ddf8857f9c0763be559f46d21c6acafeaa6f7dd0c1234364e75ee459ebceda783ecd2ed106810438b56c03d15d8b19daf08cfe1e798355519ea9d08fda10b5182325c1aef1ce2070bbc1fa4ea3a0e7f769685c8b327831b198c7f7a786f204d6be3dd0449e34437094deb7e7ffc5e48abe215f8451c4b26f5b12af45046a85aa879782e89c91ba6e2255496b1205d76ad1730f2bfb5672316be0ce0d69ebd01d88481254326829501104148ad25d41de95ff4e91bc39474446bc2ef38e0f89a2db9ec8563b9869d27d669aa4ad2685bf00bd2ed7af1af28bc5660a938535c99598aaae7bdb2ad2d23c9f708a9bb81ac4c118658ee419f4f4d56e6ef89eaf6c48a8618300171e716f1f74afdea63de8f3219afc00761507e6e620abf4698bebb79aa7bb861efda4230187538a26bc8203735583b5f120060d5cc9552cff2e4b5e4695363d44c154991f7c2bbb0bf9a679b845022b133534ce40aceb2fc8a8ec7d3fd6c4b6044dc9962c24f4024426330830d041c524208e657dc2be75515fd67a49d500677ea8b4cdad10f8c4e00fd07197453fe94a50893a8dcb57879200a82a9b03bd6a4ae118072200796f2e23402bfedec7c3a1c94201779a14d3ee40914534b74c4823677b3e7e3c988789e655d7f35630c4c03fad1f0f7108ccd8c3523a6feea74e6647879c4c9e2a7aaa9554f5121fd6141f10bbb1f0bb9f55efaf5a574897f3181630b177554acf2a669dd008a15501e65f7d90cdd86a8c421349f30f73e9aa658fc2c9c84949f8f39f8275307ef834bd3113918e0048acc37acde63d52e9ba4baf177860d944d4d7dc5248c406ec900f707c7a92df0d4893a80870625ecbc474b66f41adcb864d01a1eee612569de59bfd321c4a3659a948445852ca3409cf782f03559a3a07d32ed68721aa1be375bbd8c77c0acde0e326b622259d14b9ca43de706ee43c47c4b3d0ffd153b45bdfc2ccfd46526c076f2c5aeaa3515433079d0a670596ec48460f1443595510feab00bfc2af85cb590c679a1131069ba0b1b7a3533874f6dce49b9dfab1d2f69202ce4b2fc7a58b6493c9c6396a98ff608499c8e8467f815929c232fabe621a4e39371147cb60a3992e34dad20298f014b9122f357a567a00a568fd75f1623930f0dd1a8c0de27b214200fbdb01587fd87a8ab7c8acb782069b27901bf9feaa15882e8369bdd85a470e1d0c66f98ec2dfc9a7fb1dddc9607445027a8a4c0c5fa9d19719c0ebb2bd9925ce39bc51ba1f7743121ab6f4bd1858b994c83f1588917f396d1d5fabbe66a2da29d5be99a0596ac8fc35d3800b986da0e006e2af0a48b6f8badb517cecc07e83e46c0cb692082387ea285a6c08bb0e8c3b7e483c0559d15a50c7a949b432ebd621b0af5b18220f415e30cd41c41e245b43459da8bc71b89f68f3e81547bbaeac402776b4956546594bd96dd730c9063820bd52e5840b4ba6d60c2fe8383a5b43c23bd08db0a88758192f541a8aabd46d34ec26cf02892002bd69045c32d785debc7548129495d30aa7cbb4514d6f4097369b721d7a03fa8d1a0002d0763bd02a945c181d94b55509d344b12fd22405d224812020953a589c60d17442af5192c0c8ac8115fc6dee807d1af90c2bcc0167eaa5c1f17f7cf1e5bafe2c6905b2c351f86393d034258cccca5401b5ed1822fad18da83f5ba1048737945155f078f8072057c960b777a45554a654543e0c6a9f821d28ef1fb6ab851a2c2557d0301ab1faeda578774dd644cd78f402bbe49f52b2e226da7aec539db427ce842a22f394a25e5c56248204a5c08fb1d39ce2b072c48321c3e3ba04fc062935c8a0bdad95fc83be478bf31035943b86068023e302403c7c41fb7702d21a96530bcff7106ac7c89a02b84b07fdbd80f642e78694bff054e43c6a7590addc3267ed0b3f91733b93ffe800860c63c9144b4fb6e7b53516c56e85a16aa95b019337cb6390f5c6b075fcc9f312385a1319104841bc992550baed9091459126ca61f7706ed9a824ab0c47d9205b19d4377a4596ccdda941222d8b7cff46aadae3f625cdb149b82a8264adb106c24d97ad641c8344589fe0426b2e3be760622dcc5aeba226d587b54152579b84c7e6a42b9ebd29e1f52412df92c410d99ecc00457ae97127a52294dfc79dfeb34cd1e20f5b3e2a184b226805e2173db572ade2232e353f96be06e74df2c8e7edd613ded9e252f9f62c2efae566fb9c8f79cf362bda8928eb2fd716953fcdd068b295cfc96d8ac22fa125244c1346d6e4eca4725c01a75467d90c55039498d75204a41cc86e91594980856185fd82b61e40d8dad25f899b4b6dc5666155375b8967746fcf63e9183b1b2524b1a26559463f061b7a4c0d9a5706992aa05fcefa146a5ced818932f9df63759c7ac2b234f4550ae1125a0333a8d12ce46cd62432a266c105834da3c83af606a43c45f9b5d6627774830219cee0dc10f706b27ee6d469b68560f0248692697836e2076502353f30642bc75a18aaed3b9a9d1e85a629519e4a383c45641146c21e15fb20d6f268346b3871d6205a8a8616496bbf26ff212ab072cd7bb8ac8bf0faf3e51cbdc84b808f5f2478cdf7969ffbbc751fe70a2c5fdb1da3dbe614ef596eae578e2a658d45bfca7e1b59ff7f0b65e99f13807ed25382de052b334a090e4eda2a2cdf7b5d3622db3586643706d657526fe9df489b6dc5bba7bd1a2f2a7dd66b01672ecf5111681a8b3dbe28a4223374e5b23e05f99e9e6dd944420d55aa83a03695048b69160dd8abaf544b389258b95d55870c33780d3f03d793c72079a4c0feff9bdca83d40331bb5d62da21b28badaa818d2681d026dc3a6d0dd6febcd132112a345898d1a4254424a05e0a50185ac635e426dfce2e15e5db14c9366ce9d56e9700637262d637a75cf028d1a3f40ce78a5948c2e94105faf0929b13c57af7c228b73fdb31f5e36a7d83018e50739d883b5349fd285e180a0dc4537162a9bb9a574386670b149ece19d066ba5a20e3eafb0ba8fc50d45157d458b094956e7e0869b35035a6cf822d4510403f7acd08ad0fa66f12d956234c4bbada10cd2619716ab0bedb98c0d93c82842e185d63f06efecf79a9620406c24c4c4680c95ffc3bdad37ed902504456d42fc00589c21aac450d49a652b87eec30d289969064f2f445944142a4927b3c373cd4ece203b096055dc9e37541d877960ae1c82f8d0348ba45633b5782310d3a0d5aa027897d4c3510baeca6ad79be06ccfd7b5d8e1912a46a5f05fd3a07d39f517890276429b77a5c700485e6b16057625331a4a910626d6b271c4110b4e721909a35be82bd5ab55d825a70fcb6e128c870596b0592cef20d3bc12d9f01582acf2b4f342005a5e784c4d57ce084cc5c9562275a545a5e50fc5a7438d23c96d034fd614a89362815eeb4b13f8c74e8c5b8622b0601aee9460b4dda85fac4c13e8afd68c2d3a2b217d8a2e3cb2b92b46f9074b63c74cbc6a7214aca8ed8e09bacc8b25b272b752b35a380ef60213b2564df5248c572c6792d0bc23d0fc6268bf05e65415a337909cd15f706869abbda9033c1473991b99b7db79e14b12d5c50f9698f1cb9f8ddeb637dafedafe17a85cc94340123c1f86975e254d59c2ca910366032eb5f2a323f20ffeb6b21c5edc7c405ee0546d53ad557f97ad11e6c4071254337f9440504d97a9c64c2d8d6c5aa384e9937d8577071a6560a8e5775e2a669a14d8afc0738a7d7491791ba5117ed1e8e7bb56e80e300aad80379fadc749f0861c499e8eb2e27020e4cf406c8f06bcb602016a67c10eed90146fa06d0ac88409ce9ddf1d244ef554d59a81cf751c7f5b04240fd10ed855b132722fc4b9d91b6dcf03828ffd7a197d4cc2bab2030aca6f95bf5eb4393d272407c161858c108c107b9cd85d3337bf43f2a1ae9e350e3a71163a1005a6717a0baac829659be34b354bb718607e4f4d17ae542c0343042793e22dd5ad7b438a913b9aa8ae1503a40013392ba3c5be7d19b596e6318837aac860d8421cd0be9a108f01273c7576dbc526dbca825638a16436551738e1e578b983d297b87789588bdb405a9b7959009d379df6b5fc27ffb0c3f91096a31ced8ddf949541df476f00751983912114705f2504338d56a681a7deafbe4e6a914728e8d681f0ddc8e8d58039dc3489908a6036b324bb7c54985bae8007028b63a788febd15ba83eba3d32be380cf0e4289fc129abe116f299c803b37c04174a2f1e5c17ada2d4b679c4466457a171b880f07870f3a38ea4082fa60781f3d6450bee8382b1391e46ce2a3c0b8c41a54f53f48e759c9785705a125bccefcc8c016d7b51778d765503ca99b0577762b35809240cba8c5d22b0ccaf8b7f933fe6fc5457eb6609450f823c112219a51b041d64d9ab0ab612ae3d6a8525b275b57d8ad808bdef09d1b7b400ae3e3b5480a5ebd662f1f86a32c0f6bb2470006d0c9916a34c02973ea250d6072c3bcdfe39fa51e069015616b23ec98ec9c989f01ca88c32a5cdfd1520b8487c69d9cca554351a3dd376ac1df314e031261b2195424704cad9a5beaa65b59b98210b71266074fe5f8a4e0e7fffc40c2b61c62aa8aa8cefa8fe9e8ca9a9f39eb9982cd5f7ef13335b2655a6a86d0a9f17f8129395098e44770868d9e8e8dcf5552ef0960a864ba8883f1bcdb36682e2b3704e8fa598fdefc3005e09d7b7064649d1f50ef15b51f309babb8b9878556ba961d7c25cc3787fedde98727943e95450d3023f230524911a25538cc9a4e8f6818a814eece236e1875e6696c65ad92bb91ef66b163a670ae3224084077b1ac9195a0466b121c7fc47496a0fe3329c869c3e78a330bba28eb716a1aa67dbd491999b7c86df7261bb3c57bfda69655405b341a37329974b8cb9b377189dc9b38fe1049e9800edaf030562ea5ecde545336ed67ed481b3773f1be58cf62cfc2dd22bd05ea1525aae60c4739202a700d5dea2228ac081dff7ff09e86d3eb20e38801779e15d77a10c16d4e2b5ffb60ae66e2f372d2de55e3e896489db62223b96debd97a357dae1735bbd87d9d65aaa999a91e8d60e0d8bad7348570110c1c81311acf661d489f3277f4b8d7a27cb2b76599104236ebc978a17c2aef97345cf296107edf341e559615d620fdc089b73ee2d317119089b8e2cc96865f9eca2620bf75fd968d8814cdac34f8f3b42c01f2afdbef6c47a4989895165fbbaa7f203342e8ebc9540289192f82869cb287d09e82082ae6b392934e652771a4a1880127bd3f1efdd2f3b94d628bba13314df9d5f5176c26ba4831a5e38be73208d47f5dbf631331459a291d7eef542681e7a78edfb18998e23db53cb1aabe7335b8947a21ff6d0ec8ecae0696aa2fca54da90de4680e6d047be78927123ef0bff5ea0abfe2f6c1a111545241eb9e57b905d21be8c134e9de3a8f4481475aaaafc249ef88851dba52e12a170a644fd7d3813417ec02b471f12e2a56ab882f5a71ed4872c8e35b704367c72ef3bd66c424c0c3eeff45c7ff0ea36f9772a5d9fd4ec53529fba40fc8c9c553460d2652cbbbefb7210362fc441905a31b4e5e108d1ce2e4337b2e7ec2d5ff5e14d531a7039226ad3ae2f7721454b3aa6dc319e052a94b6c8514dfd5be6fb6ec3f600b8bf7bf9e9eff31bf69daa8b7d9fc039989dd2c7ed6a80ec207e37767ca61b23207463a59e36d84d940fa74851e52a1cec19dc5f0d3e79b1118486a985f07aa17eb2d7d49bc897ba753c9a1cd6a61baccf9f10540da614eee0634023b4f9551399f63e912972d2d6837ff866b904232e1771c4eb1a4875f1d7640b0b7d3d01879bfe16f571d80ed7107955c1c9e6a693d5f0629aef5a7163e9eb1729bd334fd5df9552131f80d9e2686b5fa891a3104dbdd7b39d3fb4bc88ad42a57a3554aa5721a57a152ad557a392b312292d68a554185fe9d0c2255467847a3a557df3e3d49946b96db5bf50af40251ffa538d9f5ecf2035d97beb8568052a735780520ff5f4472498b6ceb8da6bb41a29d7ab51897b059510dc9352d519bebec66a140757fba5f82a2aafafa2f27a052b7357400906793aa50af3cbe99a44096bb5dc3b0c6e52c038a4a934c31caadafd19ccdaa7ae2af2b0821635c957516c2a2ff838d02e95f11c52adfdc810265c3bbdf49e04f58d421832d92bb4d8143e621021bc8362be7b26a0ad7b1dc8b4b456336dc28e7d496f2f630f5e9a69ff3dd4f9f0b58d07cbfd6a69601575aa817791d9931e52801f298a656164fef3aa503f3d4d3ecfba32a89e202257ae8c18268808dd56867b1344b4b856c690cd050ec332e2b070335efdbeee6428e2bcb71cbfe8f433df4a7a21843b5e136f7298c2b740a7a0bf00407e70888cf985a4bb1c1eea868398a0ac4061ad6d3652c19b2e01eb97d9894f19dc3d0f8370e99a53c35d83bab486aadbc38bfb0989b6f1400e4b5860f3f1d2090753247db4a6c7fde21b2b16926d4bf099c1063863d79621c74fa3c2c53190906fadd8b08f9740640e037961c546507ebe9c886322c30ed811862a4f8197f4e340326e5af80a85bfe3f3f0e9e672aba1ce947a5de018e31c3d369b8a814f8d28d004801f102916b67f3138fd21ef237354ff5bf9d1a27cccb19be92db9ebc2aed649607a2431c63e473fba9b5df9a286c76e5a03df1e7de8e3cff05329862200f207e52404b0cdd2b9f2a10f268b1af33f3ebde74240e4806571c1392ec7a765b505bfb2801a58c62a26c17b84a1d99d6033795a3a64fe00fdba017e5cc8bb0b023fc3f1cae3b893fd60b613c4bb29c4bc0e0f47c8e6895cd6d88122e881e25cc98b1a2b4ac05feab897185ba3a152716922115f7dab8c58b28242e8b74e3414a3c6d538d7dfe79217a2ecabb74392db331caaf11ca9a4f067b618609f9416f6501a3f3a8643809c38f5f6e519d9084289f6614d7da7f709cb7e32be85e86b3f165fffe3b3eca2e737a8453ef5f812c3591f36461fbca4f85e0fa2155d9ebfccbe48bd4a5f4d5fa3d4cb494fd43df9e22c2657cf8fed0ab0f3ac33b3ed4f9d1a2ec903775156f95900a23d38d47ba13228e4c6dcfec1f45b45b03962896265bbd544b7588e360dcc0159de917f0c59b1d53dc32d727b77c35b8ad24a46cea266e05106b9459d33ffbcd20d8214984ac9780f24d86809e2868b229d5a431c0a114a534f0d1e95be03cb8dbdcba2f5250b00860ce21d08a25c1c5ff0c172efbd9a1f97851ebcefbcfb1dd546389f65a7ff30e9c1b0d7a9687b2b9ae9790b039e10777f68212d4c8e1d3a96ee1f13dea1e01735fd7182367731f849b2c41c20f0bc5146c75352a1468390fb4cefea930a49b37f3617410744608cc37b6bcd2b1ac29061db1a316edb8a6445f87ef75458451efef8538bbd293b21df778c1c3f7325a1be87fa0e0084fb32f452d58e72510b5b6e79fcc454a2c0202d86942bc1261c6b5dbc738b2807def6cf0c963377eb7b5c6a87bbd36a036402e17853830991812a7ac005b351a160a9c9c00e4d1f6f2a73cccc6c5813744fbe2295932fa6c85d0bd349b0160937fa24e0c883117985a76b0da7f26a941e5cc7785e3bbd127a11f3adaa6f7989be12d908c760a0706533a0752f6eb00cf7876c4a57f1166b79445548ba3e9d58e5838eead1d87bec329315b70dcfcd75a6dfd4a59d98773257b8ac245adfd4efaf8cabf4571644fec226c1e4c274020ef4ba180771087062558117f4490814d4716bc7dcbc89a92069d846158e494875c238efec61d875daf571511f3a5ab597ade159d25d504988d0670cafc6282ee2a56d4f189639b2da89bb37a9cc8c62b6d5813f8f64af12bafd48c64daebbc44294a168f5d43ba6809de26236220ec637ca768fba7854f6f1eb72e58071bcdbae1944be2fbbd5d985f909a03a0c4ef51c6cbad78df43ae0e4c3c3a4d2e8189295d5ecd5a447bb49a3ba2b89199b8e4aecd62c3f3310059336b0f57fce5bfdc8d73c55207f94a88672ed024abafc524a041817d3e49ddb1f27f9d660801b5b7844a30aa42ce2f614352df815ccc14cbea1efd0d4811f4b745d11b7b97424d316a03a5ca586af4473b3bfe50a2be93aa5674a0cd5318251f9a4e498fd5b45caa236e11801d907a006e6e82c2f8d86a8ac2d247b0b0cc74a6e845034041a0264c409b8fdc45d3462865bf13284420b2fea8851b9da835382231f7817f6ad458a8270e615ed39611cf39f69fafbb6ff925cf3e2338332c7bf706bc3c602d5ef35b51ca5028a0255736c063685b9f39b0bf8080bd19a8ace99626ae1af0e4d1f095bd94cd753f3cea2514fdf7ea12bee72d35926419554149735dbb6bb9147050fb10a0c60789ad466dfc302128c7e2b9ca5e1b9bda7b1dee47e24ac3b7a296bf98ed5682c14de6c5cbb86405d41efe66b1d8e4bc501f4ed98a0f79fbd3da5311afb266c32dbfa7e567713e8ed2dab354537875cd934e7e05d98b10077e0184e094e848e7ad7d089f9257e11da5dc2d363031d462131cb57414feb2f24b727b7b8a263e8e3c6ddfa40e0a72b4cc0d3e0dd9ea6932c756fd4225a10b5fca2848c778412949233736beb3f6b50dc571b86d49ba4ab0ae588ed390d37a3f1779c7776c48f9bc438c50291544fb02fdf3c4eae66f416bee6d59579c5ab98809ba7440c15c36238aa0315d232d4ca2d78dcb76effda081063eb712a08dc87029f385e04c5a50317b14e6bcf5099e93ffa846b251b52ebff839db862252d0aab4112cb24dd8449bc0cc6f97f123b5a0daa0099b01390039d71331d0ad72818e3a56db3fffc332171668383ceb91a81888cb7f7dadc76c89a7188d3fe72a04c1d4fd178606fb99265af9b43bf9c096f12d0410f61e0ec7ba26b5252986824a8a5fe91cbabe4e2b5a752569951b4dfcce5b5843087026fb934d2a582a0920e7a84f2430a8da7990b9c059a5811e654a30702454b3957e05c31268e70733df2b67163a6e59bb471357cea7d4a71a10d7f8941a1424b0de024b8461689c055a3bb2dc4221e0789d2fecd59ad6d8bc8a23dc741272451bdad84b26f233a7abb50cb73119075bada038f63a167d8f3821f5cc6895a1eae1a997c8e10290ae8667303f33124709b74cc19879c9d80d3b0ca98b65354d28ded24fa996199047d9c2ae0e95c1d08fe286fbb62f7ae34e94d77881488de2839f1d6f4955cd22d7a426ac0162883a5e6a0493c9caedd3785df74d2e3a21ad822f37868eb9eac03bfa819fac61dd4af9b03a6a03b582d520fbe4587bd6cc512e317cc288457b1a1b2689217170a32ac472212e82d6ef3e20d5a89047af3b4f308f44604983b925b089b94315c1edd220e3aabcd45c0341d94cf1da059f0c7827aefd15f9d8ed868dc18327754231a0dfe8aeba9a62987721549116bcbcccf665603e5b43cca7279ce6a83bf660eb7fbdb0d4a9303d1885d2573571ed8c7057ffdc46d37744d113f369070722181d36305fe5a1c45fe3f0e3bda99a59788459005029617564b10fc357ffc591224f19e642c1ab1998c5292413dfe1a572fb4f00483be1a21157f7a4e676efcd5ed29d3c92a6a6e1dbdf9ab2c4b9803d3084600df8a52d6335cb9a8dc1fad8d243f7aa7ae2bd55cdef8365ee7f5b37c3474862fa6882a5ee3bab067cfb2842c8f54d674f824d28036f48282af00e0cfc2399785f3c41142008f29db2f1d0e2cb2c74ad930e783328e0564046254e6fbdd7952782f3a75605a10d3a52d27938033990fc1702dbd069bc8b21cfae0d48c0742a9395b4b2374b271e0076e3f28d418e16556ad49fd455339936682e67b8d660eb804892ac33538c3f005920859920d290191e881c37d35649511cf4f92a62b97127f6ccc247cf938b83e504b8476423e822035c1a018da90cb906d97dac061c706d4b0fa75c42a73ad7dcb4236a4661ea65bbf2ef41864252c3900e80a47865c465894d3a09ac5044c3e1991ba601303f9c4e02b27558224613b11bc8531708de288d6d739829da6f2b5a0c564a887a00d24e40fb9fd23c0d6a8592892a8453a2c225f1964c3eeba098f1950547ac1cb17cb5806dc62c0c41259e252d187d5f4d5c09684d965dbf56d46b7f302f3a49f99164ea04e01b9eccbffa30891ffed7894ae8a50ade354a3b8e5ff7973a520161d249c1ddcb0e5b2495c59d6ebceb477b6b59dcc714f0a8f3c30aff10e82a1a25c1fb7b9dd9c5c7d8f5b3359f1c5c1a7140056bc2a1eb6f0493fda425896559473acd45817a8d4452cb705516659a9354111afe0e9b052830b57b9e0d7e3b4c8443343c31d714779ee599668e25a8fab27ca2cf6f9a2be6df10824118dd799a89830f31820631847fb28adec3c9260167250a9fc60e452ce924ad920d6a4f093f7713538b123d6da307a9609a2067815ca5eb0eb4fe3e94fe3938461be22ca973860ef856e9c725a4e79b99892fa13970ed17902a59a8da722e6d9b2e15d27572bed868247eb2529b7ed98552c8044d6035376aa92d534faae6dc3d3092798fc2232903421df51bbff827e0b4f78f4a8e1e963d064cdc2da875ffb45280bc4829c9e287199574fcbe648e12377217b25b3028557b9810d6461888285c149be703e936588eef99ed0b6dd2b325829b5615f35858497e6318fabf34f18a9ad4b097bb6ee9bc22657d28cbb914bf66904c11fafe75896e98957d3d2c931c86035743a51e0cc33fc37bc76fccb6ac1a4ba8f31b2c2231e1311f1563b16df6e9e7294a4fa7328eb864fa46ac15e5dce233ff22161b361d66886b6789479dc302bc3ffd9b129e5d398f6a4498182f9a5246493d1d18c51fadcc6dda2ed93811db5c684b9a04f1f688c5e498b62d1e638fd0f42d22dc9d8ad6151061a6750f5c349889e8dd3d26884812eb5859a95d4e3c3e359fba43cf0ae145ead09ad59d8d2b30f21df62ed82517dece5b676338632ac576abacff077a50596358915cecb8d9b8843d4e13a9ba53b5c1523a97553412f127cce469f8cfb2227d3e40a0a7a7e2ebf224e242cf100714607449cde5c0b08cc1035e33e1e16af4b28f275194698530f9c9a1937bea03952a25cad9a605b0457a25ab5483f71037396a2614a79948c2199e26486135934aa1134235088494e671a5a49f44174f647a56e93675c96a71d29719bb42212d4d2749155e05c78cbbdb2cb2527e13f6105ce95ab1cd5610af94daf76e54b266e43e7b597bbb96e16624e51a1741f5276892ba283010c125a6596585738755d1c64308940a65d8541207908000b6bb3006d0e50174d14086a263dce8dbd4469379daac2273a29542dc32577f0cbd919a4dc6ab6cd151fc45f0c82f30afc3bb12ec428636679bec8adbff57edc375f5d8211fa15274ee45066f325253d41577cccbf648c3fb60f7d53e8cc3b70dfbdf2edfdea11bd11b96e217a6bd91621f4aa6678986b948e9ffe7de5557d4ef6b6eb8470c1f0ef43db1b6c955a992d77168bf2d292a512144fa59e1c6ad3b2f81e129270d4bc4ab6705dda736df89654380c2af1a586be23dcf5bdbc8ee81d47a7b4af092046fb9c854e815b412ae625101ac9629b480b7597022ea8903d7454e724e332b0d7099741f10f23fa6ee5296a3962b00b0b1211654942ddbf289198a32c71d8d8c8b4ab4a404b1b5280015bff56dd588fff50416a5f45704b648abffd541644e6cdeb5959b462045ce38c1e94693b9dd2fc60a5420e16c85332ff7234993aeb3ee47a1cc2a95cf416500764f520060b9e91f477310b3c0a3da56b474943bf4d3f98fc68faae5732daf19228140000d48f38948fa21a05c36cbfc5555455eba7ab02ad792fee9e48b3ad3db6c2a2c8d6f0d405310c1f49fcfd22b2ebeef533fc0b0589bc30767fae83a835fa09a281adc072578cda1ce38ed0c1b9193750c33278b202f0236a61c13277b01cdabbf1655ee9e063b14f2282f885a2fd47fee865f266a939df1e75f64868235bb4661d2c6c5f48b7aecd565953cf45bfa737738daae583f1a90b6be50c4369e85b25175cc6bf4dc620471de0823d85ca1c5d1d6c1f5602f79ddd49d1864b1c48498b672ef32720942120942e9adadf06a70eb127eb28a141d78bc9de524ef0cdade3e264912b968bec88cf77a373da423537479af0605325b0d76022e51a26e6dfb988ac96e7454b3bc4eb347ceafc13a46196e616d0131711131da20d2eeacf47f08dca2d130c120395711b488a3290fb7c013b890e61cb18563172482e34923047ec48ce8d07ad79fef15bf3b2069ea00edbe74ae95674b8afcc48dd4516b967be5b372f1ca3e1db2f9917f977301eb6a7e3f9ba255b066bed53c1dc7aa66bb1ee3213e27bdaf8889f24a3753000f7a654c2ec8169f643dabfbe76651bbf211cd4ff82a2cfb4796e21c07a1efae07cb527ccad21a814fc935c9b0f6bb76ee2fd67681803440f05f9ec00af2cefdba9dd0cf108ac6c96833aa531f3e54c1e964e9519081b4931ed73094aac6eb31ab46e27525561813060da18ca9bb066b1bd28246a772c4d659c846903c1dfc88e1d610c508e751cd6fc11d38e99b4db6e740418a98d54660822a60d7b7012d11d6c1f2220a79630edc3cadbcb86024fd7b6ad73900c0e3cfe20a6fd5c363bb0a427ebc969176ee5a38b6b91076e0f2671936b05d5f5ef464e586e6647df90befca0c22f2f0b9d3641cdd6768a11fc399ed1e075d058e874c64d551b6f55c074a18c1cb5f76a32633883d83ab6789d9bdeaabda75fcb221db670d506169b08d8b841ee41936111ccd5c280e5202bed4bff64630205e4e0d3d9dc238c2d76a31347efcb3fcfb569e5051670dc29b8c3b9b013096bcb34c0aa39e039fac5832ded03435122034e64a184cae662315777e005c4abe03984426981bde9c4ce21b8dffbf678dfed7bff3eefd3f7dc88837d5a440190f4e7105996bd562668c7adb9a190717b92884f0302d4b265d2d8b31fd1fa094f4066d6a94a516071d9c023e50429d157ec47cc00a350c584445dd773d4d7808343bd8be1d6a9ab7b9f72185c1db20698f70fb176bb2b52ad427d9ed0fa4d43461c538ed3892b517a1470dc735e7c159244aad567670e6a08f7c809b0c775496b6dd186d36b30a838bc5925bb61bff9a7561840bdced404dd2e335759c6a28007ba23aa157f2100dcfa9281deca0a4dff29080407e1d4fe227c41e5741befc58e28b3a819882bb7d7e484bf3a63e9fc46b5ba5bb3e2170c9b5a3568e411d7af8bdfbdc3e3e320a78b58c25926c8b02c0b8f6ad3964e93e92ad69b38708d345bf9f0c5c0920ee04859ab2008985ff939268949490fd345cd22cf232a697744dd712681d6c6a59d70087cf9c3fb67a984ab4d77dcbcbeca48de7fb5165ab045296aadff891ac65d81ef334e6a939c3ff32d5abb9a93222e692a4149dd0c2e462db44492b8e708e983a12f8ca97a9f6119344c84fdd12d91c0a62387f2ad7b0fa88baa8c756b9b3896a4146198cc2022c7ed5d234cae197ad45e5828b751039bf5d21de1aa0fd64e3d38f6af836b17273eb2826cf8299254e4aaf65a21b8d31bdbc0dd5c0eef07f6edd3cfb879088461887e0d2aef4cc7aa7cf6935b0f5515e8528f7ea7bbc00c6b7fefa8144cf126458f7281e9c48423c6f42f5d3cdfc3193d2a23ea422cf67ae2c3898db75c4f742dcc9f91c8fe0fba0b468f488e59406d2a7d995b3219b03ce6bffe04117ef90b4f473cdbe87b8ac364332ffd44d2ea57b06aac0022f7ba3489470efb8152b40f5e075952a9de4fb944b874ee6b65e89f5a5702cffcc476192b726aa406def7714e10bf4cfb009d7592dd887866dd9ef2ce21a72aba49ebec231e70aa0d5f09b4128173443da5ff407424ae5dda1b2dc70e5de12ac1b76d0433da77531dc4e694905764c16574b916aae06e4f40cbd1d9c24c9435d169ab75d2ea9ef35cd676804e47f938da6b69daed9b38f1b18d3b3ea3ce4e3fd4ce16e0a818a6ac13651dbcafbb63c92d2c3173834415dc88e785b7a11ec7ed1745449134cffab7a2352154f3ccd2abf30072c36f434936f324d5e90265c8f7f08337a42962ceea3c0f6308aed94a7db1ca8f5625264b17e80947bdc9023354bd1696214a76f03e64d7055cb3b65900acb75e16d5f01063469b4eb66e2c4c5539f7f6f7ebe621961974811621c9bafc1f4d884b4b04bccdd8a4ceadff3c91f52dd833650dd927cf0e14e4d22193fa634dab0a494b4806873cf5de994e8fb22f935db2b028e239de01874096753ce3c8c4d66194609aa646f5a901bac95607bf36346102c555354e2e337b4412a5d4ce2fc51a069bd254c32a7fdda7ead57ed57898440d6aeb8003eb5653a91afd7eaa8c5827780d0299abbac7fa01ee0d5bd67775d6dc09e9c9633f93b59df5c84a178a55e5c1a0dc93b5cbc70b09025cd9d6b1e9aae188c59634b15bc49ac7cb6fd90b5f6b1caeadafac17ddffc0b196fef5c50d3b1284df26c1999db97599ad77a75a6c380094b3b2ccf70647a5c3821e4c28b5bb3d75fbd8b53495cfe83e5fa267a1ee13f9e4d64d40281f9f7e109b27bf8e79f65ad16bee810349f2479d035215684877bb42038107b88c08001964ec64b52ca1111a276643c0ef2217e9552d77c6aba34be04a06c2a6c6978303470e1d1f7474fcd8b1c3a30e0e1e7574f0d8a1c3a30ec859c824e76a28dd108cf4c390ecffa972604d596bdccadc0603cdc309f9f3f71fd78889d36a16f81f796d0e409faf176c7392b7c77cf28c159cc7fde02d4139a2073a4b1d1b4b11d3201ae7bb7c3d354700a938e8f8c804d469fef96971f573d8f9b54806af1b7b95bbbf003880db8b64b7ba3921072a9838aab50588b425c37473ec8e6d68f29b7a0c685b70033ed3a30cb6c6d4fc160eb0cd1d6829764e86d5b681edcb850fd1c37302ff96418c126155e51ec37eb6f3a940b23dedb7c167564d55d70615167f6283b5c4b6f36da41570bba7360bdef64b4cde8abaa0d6d3eb44cbde8971303ff09f67fe9f660f4c890733249fdd0f46b34bed505307549df091d7b908edc1a94f418b55053d3add81738a443df08f7904e138b66153e79a17400799048d3ddcd1e019afcce43b2fc1628c57509cce3fedf8504924104b3236f2e0d5d0381056fc2cc6b8f554a27f686877e5ad9434af1836c5817013dfb0c36edb9cefde21dd3ac78620ac66a273410813d3041dfb2475be2859a01ceecaf559d7a08833a20f23a5c7dcc9fe2242045eb06275fd6f6e5ec11a5e2fb7c19e29c6c0c9c30e9fe94a9ba4dec96acf1e87104dc54c8fc8e5d61cf13a7c2c95a4f4900cf0a951d0cab657a0fd2d67d153b2135ce0c018cc496ffcd4559b35e2f78884800055a6ee0efa1c268727db93c32e86d5549341da6212289bb800cd0850a6158a5b71d80712c8947b5419768e87d45b4af4ce690361e2ca029c2112dd3547cd125970c751c1202d1eb2726019b27e8979948800fb0b8280996039c61d6d3782135a0995f6fc19059ca84834acedf7d43bd0932edadcf660a31a4b8789d85568c81222098d0d6e4a4e8a6efafc290000b8cd40ca8e33c76a7c6e25f70433e4836120a2c90f1965adf8bac4c05a0ef95f68ec336097263ad0125b26ef3a2ccf881ce1eea2feb3825157eb0dcc1539c31557dfc2c4bee53b4087b5d9680a2decf713d59abfbe7a3bbc856861ad1b5213eea09a81b19a6ab1c9466b4ed4454de0e135b486d4913262f32c5fdf93ae101fc2fc4ab3e6227c1eaf8684141a2595303003ec6d2c517b7480e6117c3bfd3a2492be2412ed94fe9c1ae334deef1e632057ac017ab2393b8c405a3f9ed6c0fab66578a014ea4b9a3bd6da2a3a3bd3cbeebdded32b13f47733ad5bf448bf922094c9f94e743737d95352c5c8f2ec3bcb48292453579689ba34bfc418ebb6ec4fe348ab75d9af9d7e53620a32d637a7fa0376ce26ba3f2b72e3ce40531d5b8ed4e0464c42ed261f81e4cb26f450f4c52a35317b51ddeb0b81f814fb6179449c0a9ed91873ee9dd41f6ee2b7d326d4b058b59c513ba9127464591b3b61191a67d845e32c051c08d85b0224e96f0398a22fca6fa3579af550f5219bd044cf29d37f863f99f96c759c6a4cd347b220a34a1e9f8a1e14349846096af41ae52c344db2400124584300d0da3198b3ef5f73d6d4751c6aa544722590f2aead95e1b06da56a624e9761e6f05ad769b0c4916935fdcd679b4e0e5ee7bc2b46826bd7a067c74cf2016aa1a52b2898d550f17a5e4333073c09a4b662b598e1a320fe444f15d530508f15a5e6f33584345d1a5ebce2c5296b86cc5d1a6631d028eb16ab71165d2dbe0c727d4b45261534a4aa4db68a63d8f5d4a500a94bc332c8671e535c85fc886c1aec50f48ae69b8aa21e254139d93f83284a528a4205321083d2a561790a4fcd6d52a22520c198ce059e895643b84f40144c11db08c067d448f2c2b4cf8cd097fe77629be2ee734b8715822d0e883ddb171d5bff72e82415f6aae58bad854dfcb5138956e0c4b07e86f741fdfb88a47855a7452a35126195fb83223a7681f8bbfcae2085e1f751591006c1c21fc1807230253ab628732901ce883278acfc23a2f4678ed4771db0eabc93da4f824eb9c9c8454bc34a513271841df63d51b8a0ce060a664da38f68a713a44b6e55192e03d72821293f1954054163094905b8beeb0972ec6d1b05f10f71ea62eb7a5cf4e0608827a404a9214ad41973ad480a16ab2d6e0fb1d806719ddd99fdd8f2f103379f14fa7dce35e810b6587b4dd4cf560a5984660e70ce052b2e995392bd2030c15708828a9a4c8dcace7d66ef4d933dc08c1152d2c3201e9ce19d957f29b795981c54a5d2c34883a803e259cb6068f45cb4c963b27f4b0f0fe4341fcaa8047f439872d93874a1689b6e31966b8bfff182d07ab594327e2a4597e84efac093d268e457c65a57ece89f5289e25e468f64d62a2a62564894f86bec576272918eee50f975363c6f93fed081766d9e37a5f4f3bab6a7e4b50a442f84471b336546fd801383c210a236e3fd040b5563d1ae129b98b8f9a74cde545a3938d318460202e89ebbc17f6c401007c7a50850c82b9ecd221232e4ffe6cd92b7b4840a9a401822af9b93a728389f988940e21470b10d7e3a2d40d4c7d4e0f4d1af44d966f757f3a4ea06606859b21b9589fbed608bf37aaea8b007cb9958c87a38053ccb6c171a9da752c836af5674bd72e194773bb0b05239d0716b2fe49dea0ef0de3492618d18d65aa248cfc5f4100b1f8a0a8a00dc436fe7c24a676a740ddbdb0092c5c9ac8ba0ea3b84196f94b5d6c9cdbafc73200f36c4a4620137f9b99386b3ea6773055d66b127e42943705231ff535e0e3e7950e285a2af601cd56b7f7466d0f58905b0d334a93cf20984a3897ace509ff257c6c5870efecf869f95ed8f73889de3539db9a1cec552ce60aa7f90c7c6319200c6e473560e99d57d35abb05d796926464ddaaceba201b6b12e9c21e6208674b4db4de5c2635bd0860e6362b34540ddccc5d2fc8d0dc9e5d5998b4b293d1092f1536d79f64e47b3f5b5b8c46431b6fa4f33a699f3e8a293dee177a7ca44c5a415b35883855b1b02e9e1486234dfc793e7886775b6d1c35db1b4237b8f23b18587c61c997e0bfa4f183266de02d58235acc884523f06f5ffce13151650970ada9f493b8b212b745e19b2d45da9c1910958fb66e462f3d00ecf699b767f10251e2b702098a71b6b7a00cf8e30a3d4ced30c824d761d2eb758a9337759236fa774c30da95c88194fd51bbcaadb782b77c53a6b519eee6beae266cd3b327c9f0696db5c216065ac081cda9aeee84c524cd762b7b85b9d3bc7872b77070eb8893c8866cce8c8738a7287644b62650b7619db4c06f48d940e30c9b428945965e233310937dd2c6c2019b8d57262503543ac9f9142a25e143c5f219cc56eb70d80bf63b08b3cbf9b79001dc9b7abe9e4f51dfb08da07ec99404daa5a4f70b963b745d96dddcfeff4aa6250d6181eb835c7b88c734de58896cb2f124db0e27d45c62c4ba33a9010b1d2e10979a141aa5cd04cffe1513a7d9411ba00cc3308927485b7bf760cd99fa964afcf0d0244521f21780001c14613d2b45c8cc3a6969e235d510693bfbd972676d3f10d817b9f587c1be8214f96402861d9237a4937d4b33cb5dedf76ebe2c16fd599063f718c1d8f9ef3745671489acf8b24f4a66098cd10e9509a4aec897f1471019466a974d2d85a08f47ced0748af8cbf03569280f5d8727c42caa858189aa71c32b67eb4fd9cf78c4301276623ae83045d6cd1f3c3b1e07b5496b319c90cab20b6a01f22426958bd5d44e14eae3c88b797d872f57a5647d2b98760a7fb55d5613637b6aa16f1415f4b4006737c997cec8b04067e00ea03b4aac50e76a55123f2d55609d8db39b6397c9f5cb8bba9a7001ed6b55c7f3d24c87af8ec46f70a38a8eab70421b52643528c3637af232d612dacbca6b9640f9ae63357ecf9152b2c88d330dc6101ea7be5c898dd0bb703e8bf8bb4304446da411e912b33fbf3a939b7a61ce12474a161201102ed5c0304a3b15f64217fdf66dfbc0b0513702658e83f0f17672ca5455756745776c99d3177169cbfe6a107ea12850573da982a2b5da50acac2a1429c659f07b11cb76b62dc1e92cb2849fe0e261f7acb8e25aad23bccd32c7b869870a021871498504fa428cce4151ab2b851a61866a9e36abe82c092bce38e0be9bd9a4485225c510792c96262b6ba84594d5be3c4b8a24c60b1f8b9a62a08e9135a18c1b4a56ae01b31813841010360349040ee060db388d5f45cbeac32437c4c3b19257a2826d7121ffedda13697ea577e73bb8ef0ab98f286d4add176347422b057dbd9fb4db3e460131fec163711949f091679d94ec0854d78c8adb90e33150184a35043f73357f16d636503e81b8c9aab2b3db09f29e41f3f081f6268f8dc2f60b5ce457840f5d10f627fe06a163283ab59e4421c2b6c5f5e4566ab828b952aac2b1579ac6274ee822d9643440e9586a22ca1f8bc76102556b05311f628450449406777fd89982217041d44abac287a627d71eb44a97144215426b8cf7f59c8d1ea842d8722f21a4ee16d371eef36d2aa0ecf6c1e47d2517ad9fd8fa3a658e6ceb76d5a33a4b73c4ab8c6c91482b42101faf9d984036c5570e291e42fd0353a75ed3c1a44edc45c788d993e4fe747d51ff2f858bc6c610bf275e63774295f208019ded05182c570f6ba115b0cf011b93dee452fa96005e3d57135d72efcdc021264c19a53008b3887bacbce2c49cea39d70ca6a08dfb8bf48589d6bca93a5b12bb4b9eaeba8ea3b05aa45578569a6bccd6f66ba7455de61c69962faf947dd69273f9f1c69b14ffa6c93269b7d21190415d161fa2047c15072a16929e3535d6afeccc9ee6d773e95afb0d79da1220614abb0675df431ac72bb4cf37b5eac2c779c0646cc3b8957bca10185ddab63180a8798b64f3b817e2ff2df92ddd8cd9a7bb0506901ddfa887668c5191578d675256f73395991225126b651fd587f99c061e41dc2b7231377c37f076a8c0374102a1b78d4d882607d1ed45f8d4509fc3602004b724dc273b47c03d71c6ac9b8de203db4890e822238e372847fbec42c7f149e9ddb0e5bbaa76e92f1cc2318636b2a9c2da69c00bfc0ab2cdbc8c2849f59f03e6104c3f7b80ca6395039d652b840eb69d2af613268d91252085596393d122dda7b3ce167401f8380ed18aff8da6aeb0c748b5ee75a1bda1e14104791347d0abef308ea850ed1ef5b1b2190c1f05d61acb62421f43b040dd3770fa569acb472034a82967f5c40941da70d0e7da867cd8e79cca79c348fe1448b2d069ba0008b1fe8fc5ac0f5b381b323d8478de064b8c37d8598477110c91c446dbbe726179605d6aab2022eea1c4e35b1d6ceda2d3639d3f283fe9b99d94d24721389eceeeede01370823089f073487e6bc9eb335a5e0d9ba66ebce969dad3a5b06c80638c001103004012e12c07a9180172f64842fe84f7c61baf107f57f3aed1de3c79cebb9cb6d593fa7c7c72007c1e4a8909873a914132323f3c3511b7f240c9a24a6e22f51d2044888c6d6954bd3d09d6ef4a709ddab4d065af8c5788374f835e20b9ac783e4075a1774f834aa6ea625c3c2850c3933d8950a90cee5528c0c0b17b92587584137737ec01595c14bc103aa31e891d2ea8fca8a5351b1e9dc9e52c59d62a5c31fab33c506e67b3ae55c3f12de57da44c88025e2747ad414156c2076141f4851440a2152fc90e226ca14517c446945018af213a58b1284283ea0484841c66006077cc41286c832c413569d068ad091810c6c6670031d4ea7474521a28909a0184187214bf41cd18a22074620c2942b186912854da784472f0786608207258c50c509ddcb0ca8f460052e10f20108c94d72bd2a3c47e8f6e70c0eb7852397b4d6566b6d10d65ef32ffe6230ac5a5b6bc6e3b2b108fc17dee27603dc3123c4d27a39c717718b1140912d1f15644b89653b589dcbf17e3a64b2bdcf5c003adb7e7d39a7d470521a55d7dcc7146e54d7c87f0ff5eec3877bdbafc59d41e5f3d7e23eae796a09dcf0e0a169c0d7423e7c2dee0c7c61185f1746c38c4cc99fc1ae0b6a1ab29ec09329081fc36aeedfd7e272aa7e1f630d2f1ae0639de3d67a0257a6ee475d0361bcd905ff7a8cf1f597a631b16b31ec311d1d63d8c38e6d9c8e7e6df7b5b01b8ef9f32f9fefbd7865757bf4318d9c9bc4a8809f0c175b5da282ec1e28aef4d703058f01faeb8162d57b2210bca3d78ddbb1e247fce545fcf49bbbbcc65b3ee3a6d7bc645bfa7cfd1cece9f2358f41e614000f764e0c72b70864a84b2e02812912bc18b8546fa137ce42a07212898ffe858f465cf42f5cf4272e12f1fdfb5df8fe16ae6daefd0bd7fec4b577e1dab7704d7b4cf30a9efd0bcffec4b377e1d9b7f0ec4d3ccb1ed7bc82eb7fe1fa4f5cbf0bd7dfc2f59bb8fe12d73f39b637cb7ffd0a1eff85c73ff1f82e3c7e0b8f6fe2f14b3c9278fc118f2fe2f1378faff1f8198faff98687b8abb37a3cc5179493b1e1b588dddaaf5fb7d7630c42790492f35a482034326607c02c9db3300ea89dbbdb7300c551b75b04ccbf0eb3cd2db6ee14f04305d9f0317d4ae964a192ec5a6badb5d65a6badb556fa04c6a820fb955041a20e1251188912248a28ae441145141f9789095080de2823adb51ab153b0af9ef844c783adbbd6255bc8c601c8095b89dd832d53c5be74ca080548e7503f4e600ef688511953f7b5e0a81f36fc1db4c7b8edd782f38e838afa9c74f6de120200f4baa2d753af2ebd65011d5f177ed1e9764d9d9b7326e20ea1ae0142d95fe858c618a1c419fcad3f4d23da9b09d0af00fa95e937f200747ce5c8b78eb11cf9f3e9184b29e3c338938f44b79a06e61ddf1cef7db65e74de1e77cc58abb8f6b2f6dafb89101ef7c950eb5efb640538d7dd68224ac6131c20ee131c76b055702a141f6cadb5d62b586b6dbd4e96586b6de54e43d84e88b841e5309fc40093f264054f709ee0e0a48a13284eaee06489139e798b38b901bc9c10d1e1632f93c40ab3db29a262ae4d093e5c4e743e5c9ddd821e55082a9df0b1c3bcf7de5b7bc4a85badf114515dad486c177a6cee843921c5c90f4e6e3aa7c22c7808a305a6e273282cfffb990dc2d8d75f8c6bbc2ea573523a679d3493dfe3c6c08ff11b9c9918d8cfe48bf94d8f9918796ff92f543411c2003f73157ea1c23a8639628927a619db2be516ffda12830f67f3e3c9d431bfc9417dfc2d104685a9b7f231e62f06ae10c68329aec214e6d8f750c5c0af5f4688ba1f5f755dfad210c0e9397156b7d356f9c4a37e71fe5a60fe17e657a676aed7b535b8cbedbe16bc06f7893bd54491e3783f9b08fcc570ffbd6a2642055cff70a9677e63ffba9f318731eec52dc53cc690a59b1e1fc3f410f0341f75cb9f3f18e2571e80878aaf3387f1529101f7de7925dfddf2f95abc1830bf62dc183606be19f31a035f19731a036319f31903e38cb98c81b3cd5856455df32b535b7319036359f3180363f8229ed9d44a3115a36768199f9e59d635c731f249eb0c6b78c317f7cce663be652adedcab86d98bd46fbc3350125f0a52caf85290194096240690a505107490d849402544278014e1840c6a0ba8104ef870a2475015579094fe7a829a042511f4b3b11954a4c39f49e0010c3d61e2c9139efcf4d7f3a47b3284273e78c27a62832742a840c1e95c7e62e889a026fdf53c91a467c627ba278cf4d7f30491cef1fe9e20e28920b3e7091fb9bf9e276c2eed116a01c12af42397908fb04eb0a811ee6646ccdca04e88487f3d4241fa13fa21c483ca0912219b0e3fcf1ae001484101fc0a1cc1086bed1164708bb044cce090841ed6367144ca0a360f7b2341060bed41a7a78921b909204df8904c5ce95cce9309a1cee54c6f0e3cfba7634287891decb0f7e999a8020f1157b07131050f5700f2039ccdbd0e08bb87dd84c80d145408eaf03354d2b9dbd3e4486fa2d3b9d984959b10e1018fcded28ec17ec9cbdc40f8fbd4fdf444814f04928e5841d6e2b5802099c258a007197c8c10e76891ae0d4255ce0035d02053ccc2570789041d8dc89091437354e8f5ac2a773aa1f3cc445026645a2cd81bc3598d805140575091c3b24c5bd9840d1e107713101ea21677250b960a20427e652dea51f5bc564083ac8e430af995c8a61c244e764980041264a60708449c764085307263ae820a512567890c94109272b991c9468d2432687c9c40826423aa7fab136413f8284f4b7e988f09f0b2ff41bfbb83e9e2fc6964c3eac09800fb71c5190457fa82e75cd8a2ee78c2f52fdcde7808cb20818b02b16814989e1d333bc23badc31156501fac5b10c43af5bdefc1453eb43082194158818f22dc1a21d6c265b088d279b921131278f81524a0971806928689c1c04ec04bf80520e23a594d0ca8521b9b05c465c2bd4ba024a2965ecb977658fa851ace52f502691406ca541927b1f6ca2521ed7e32e239b2ea56c61bd163c6cae85e562d3e58f6cdee5a70520fc64b8979f1e3f4339a9a4d5766f567b2f5a5bd4de0baba4959d93d4d90bc359d7b47408217cf04d086bbdaebf6c707a7c7ce3a3c7cf38fac768155171dae89a964e593a458ce8b0783a564445ac6b5af42868d46424340a8223206e2465d41a01c115901f4180ac84e4ace0cfb8ba233c488e74939484f443ea48432429a42ba42152ebc686d482314a59f135e17efdf58bbf79b3bd5cb2ee51be254a00bd18300e240afa7bb97a3160ef63d703a368c27b98f362b0ef25f062a8ff7e70da810b1317215a784c534c4494a0f056cee75e6ce076da418f5f5d98f4f854ba08d1e35f2d98698a3411d1e3c7aaba2528f4f873cab971f187c562b1582c1b1b1b1b1b9b2d1a8d48a492c9c65caf4eca42df418f9bc91642e3c9a66444c4a738e68876b0996c217afcaaf1c46c4a8f0f332268cc19c2d2e98e745dd775dd8ac562b158ac6e6a792ef9d5d7e1eb138382c6c941c04ef00b20d6d250e8f1a5c6e9f1630e02ec143bc12f680d459c5504d2b174464023a011d00868043402ca301796cb082b402b3dfe053b0c498fbf2f2c3dbe7619d1e3c36b85eec88f12a0a0aeebbaae5bad56abd58a85953da246893d17ae32fb9c3da2c77fb15b8dd2235c412030070e61e9745dd7751d005e3a0d7a4c02ed03aa34a83449d06deda193cda3482d0963e672a416a9c5915aa4568fa552a9542a954aa5cb4336513955a73c240c95cce95847744a5d7f4a8e7497fe62f3831effbdd8bc16382f362f3f162022cbf60128065530c2922152a8c26eb293d8568cb022858a1fa8b8992c0814e54ac66eb682a03027882036f77e8208620a2b4c5ad1850c8f07090b5aa00e9f664686850b192490952aa0543132568e58292287583273a4d0a232b0a224c7ca112b3c3f568a58e1811523ac11308a09d5d62a63ada86085552b3df870d52d8b0fd7891e75097a97ec304f5f6b95246aad35d62a4c389d1e65a506476c803d0410f6eb5e9373ef47da802a55979a86193a43d77bd1c898aea181d5ed3d94a5a1c6e27c71a7095c33301de1bb99bdead38828b9ebb6b19c75d635d7c62570c38307ce72d63466d84b84618c31d6b49daf199686ac6b6cd77ac6484f406730cbb22cdbbae6befd7d4539db39c3d2a0e91adbed6352defa224d4ed788a0482412895ebc225d63ffb29b0d7094354d03f7b7358df89ce85a116c51e957986f301b37aa288434db37739fc518c6d9f21afb18d6ba66e62cb3f7b1d7b48dbb61363466ad66b1b53b3aceb2c62ed6b4ae81dc853dd6a2a6699a065fa66bf463f75e36748dc63086bdc52eb616db70cded36c02c7673b063fd980d8d695d53f7ab41b4335d4345f32a6198c90449259dffaa91589bf6ceb0c759cbe9d4b2b7a66b36dc7beffde2cdb0a789bae67acca4f36bad35766d3470c7362cd23551943551c6788bae61941f8f3286bd0b865d74b6e5c725582a954aa5174dba06fb9cbf45eb17692c5ff9adde68e09e47ba066253b6bc067b8ca44bd796f48661e6ef18c18089c9924072d55fec72de4f24c211ec639c05c708a58c394f4cb58d6b397e7a06be3e4e5d03b517d8fbb2183f4d9458d6dc1fbf3fd23e7b1ea2df6f350c0dda67bf6b106d4fa644f56a1b10565db3ed65b509689f3da66bd0b62753d9cce535da46be2e5d932fa66b4cda0c797baf05de6a4abce675fc99aef13ade2e8b3a625f7f1f6b1ca17e12cbf45b1b60961932ec78e374746c83b181751dbdbe9c6f39cc42e7664362111b614505567aa892a5ca1455a0a8e2449525aa2ca99244152654e1e9afa74a912a43aacc40a84a0b5045180a1a3a32c4d35fcf5091a19ca120fdf50cf918ead1a7109ae2a783952589d8295653f4c05d2a429dcb41fdf5505142a5099d4a11d4109c1852326483a19c29ae4c013445478429727ea0724508547858a04ea51d2353639f9eca902a43439d43fd4881e5440cc8911703fc4d81c4eeb58826b9e71681503f983c3047ec225c0005203c58c1ce8f246c296c21f6142b5462107444147a8c00bac18e4f0a499418f0a0040f780881406670b877269762a620e92d2c59c20f8c39b125bb0957f401a182362a68a382f6a342d806c629279d94c628e7a474d6a129859b57e610bda136d89cb4da2924e7ba8ef0203972716e26f9b1f6ce0ba31363fc18736e02993f824c20dc14327330ce5367746a414d8482a4b4565dab6f3d634ed14d44451f3530ea1ac8c91f3d7e49ae4cd36492119af2bffc38c31c753af658e63cb1974038ce7a61ad8d4a2d2f30bc249aa8d84448fe534522aa584465441579dec79cf802c3b87125de441f3ffa030ac21361c0d50a2259c1242bb864d5040af5f7b0833f220cd8c12050089122fd19e9ef371061c0964cc5bf425bcae9ca4d7f0f5737e28bfa4b3ec909c97e3c2c5ee614b35d44183107c3ab9c73cef989b8dd4fff504e167ed1d6322df165ea78baaeeb4aab1fabd56a458a2f6a130d248130d0057431168bc5a2363637363636362c5ab80104d4ad72ce396795aad4552a15964097966959ba2426753c9dec70d78d56125f57c462c9222c9664499664b510800ca9c3d56ab55a7518638cf10dee6ea04baeae96e92c45f7da1d4fd7755787573f56abd56a2581641309248124900492400578215561d7755d775dd7755dabd56ab5bab1b33a4bacd97b753c5dd775ddeac76ab55aad6068015bad39e79c1376dced70d7d9567d9d25beb27a61cec43a9eaeebba4e9561c8a822dbe6989393b38160ce39e79cb0d56aedde92c2c156154273021873767b17fbcffe73f91964ffb93c8d74913f2de7647781efbd8bcb5b2b7fbafcdce47cefed5b5b5ddedad3cc4c6df9fad265e3a0c9daef16656d7679ec5970f91897877171f917973ff1962dc74c376ddc8e0eefee769fba3d3dc6ec35a194724bbc8448fb4fcaadee38817c388364afd05ed892564a7925e6f2b2e54ba3df9fc5f02761f87de14fde1b4d1b77491bb7e529ca8fa82e519153f58dcb31232f39a14c28138a295a1bec20b4034f9c57d61d8af401f4e7b3838f2b1d01fdf9f438d2f1c3f3c9279f7cf2ce2be943fa66ecee8cfb4fbffb77e3f4cb7ff9afdf51f4f6476fbfb4713adebea66d1c34d2b5ad6ddcc5566f3a66b7cfd59779cb91bbc51ed71a6e879c7f3d9783bec401dfda806fe14fb995b458794cb1153b1229c622305e9e833a39eec0cf7ec61731fe7d3d2366773fe418c3e26357fb1b7abca418e167f7331e9dc59d58fa98695f8a3cc64d478cd88643bff6d8bdf672368b39c6a80315101f8b3cf6e62c5aecede3e774448e8b5db7ff39e6fbf7deec332ec7cbc7871a02da76a37e0c6e30dadbc7628c51e7665967b1c7a6871b0e3843dcc1e2cd6132c1bd372ec7ddb0b883c596a6336b37c88aa8b8d92ff1fd2fbce52577f9c94f4fe2793f4d14bd08c2b8331f6ef873e49e618f6139722612dd1c39ff89bb704ec7dcbc85cf1c39eec49db81377762429b9f1d1bf3f9f1c3a1dae74557f3e3a20e93e39f8e020a4d573a71faf4f0f1d9f1e363e3c7e7c780cf1e101d471d777922a8434325e4f35bcae3a2f1d71e366ab5f2fb3e1a0b5d65a6bb5119fbb6810a4412e80582891f9e933c298d9b44cd7705a21cf30ce3f00bcfecc3f005878987f2bfec5e54d2f358dab67ffbe859815a716ba698c3e26441f9b5770d8bfa1d300c0d7f13537a5740e6201d870e8b7320f0340f5dbf8d4851964362e76fb2e70b3c5b970798e850c17d369a9d319659e6e18e7e05bb8c1b8f0d7d78f1b4d844304f05943a55ef4c28f08f01ba53304c517953ec631ca3feb19ea6f4f808d53bd909afb51031000fceb6bbc281669dcb810ecc01b8b4e7f445fb435ba5d9f7f064d28399c431758d753fa57d7f0c768a40ccf3e005c3fe7af6f9c73ce39e7af074514fd99df0030d3c2c6c2a9cf978d7b978d4399364e557a4a6189f419697e9fa739efddfb74fa47a15417e67a6ca6939e44fa80840187c8d4fc39da38154ab471a8df1bf7a79d376e4f289abf70b373cc74fdf4e91afd382cd533e4ae1f0e9130a612f8d3a78c4eee70e372cce8096506e119180e814486c0211b44c1182718a7a2908a820f0e3f7a01faf3b169f291bbbbca5aebbd353aad99d1a3bff5b19c6d4a5fb437fdeff4a2b61a9d3e8d3b56335a9c588650507ab204f9d824c1e9c962e4c5f4a4e76e3ffda9f42bbe050687f6f8b5cf8f352efacc2ff85916223d7b9720597c54b1eab20a215d4a39fa4bf4d76b35304be6dcccbd7b9f4eff2814a7ea7983116dedafd7fefaec479cdbffbe01a517693cfba723007be96962b665d1e3cf78d510c87f6999966999966999f69caa67da06b31f6f1c4c0dccd235fcd7c6c1ec8d06ccd2b58dabd1afe76ad4e8d773fb6bf654d780bdf49873da8bb6176eb6b6cd50fa6cc3517ad186c365f31c339cb6bd883a7d76f9f7309c9bbde5a57600b4e1b2cd883a6dbfa2a5256793c9943f7b9c99b4cf707eed33537e0d679fb3979797972ccbb22c4341d11e76ed737ef8396b3e363c3e36407aaab8b2376e47e65c8d9eaf4cc9cf2b5a4a2316fe4a182d7ccc8a534be99f54f51cb3c16cdcdd3dfb978ddba78d3bf5ec5d36ee94bd69e3beb471a89e3d49b471aa2cd39f39577f7311c0aefdd43662d71e6a0740edefc6e1d77806758dc663d7360e766dbfccbf376ea6e7a7915aa641433f37d3673adeee7e33356660e236c2c5e39180f7f0be1d64d09843a6d344148432e4b06764f04829a594503ef7786a3ca067f3708fe7069b7b3c3ed8dc7621079b7b3c58966091d26f7f3d5884961840139b9bfdc919213721949dce2f017d518f334e6a821bf7d2e59670c2b9d180fd42de616c70546e9c0bfd71b05b0b6f2f62c61720b001669918f51e4fdb2fdce363ccc404405a710ea4ac177f312ecf7c83a181a3ec72ebb2c7fe3acd98bf6ef2dc2fbfc1308c09905b0ea4084edd3e0874dcfe1e0c3a4e1dbe17f6e7ecf2512fc6c5a98deff37f5efdbab74b2a379aa78cd7567855dcbfc62a05dca491686b99ce18bb202f82ad744a58fa01ee00df60a4773deaf437a39c3f6917258536d65a6bada5d6da3869b5b3da7bdd0bc3139b386b2ccfa927076f26cf6eb2241007c99c70d5e34b12735e922844490c12893c2099968c1494d0953e5f47a783870446907c40468f51c68f3e5fd6803ee65309d0c01fb1c90e204b9ed08e1c6ec7aac78751ca39298c52ce49699db4566befbdecbd2e0cc3386335575d334d574dcb75ef5c45a25c4773724e354d1c8b963c5da8ad9cbba149af9c4472d1e797822ae758f4f92653ae9c93a1cf6fb9a1d5637dd8abb5dc0d405c0ef93492ab9078267802188b112c3afdf5601982050610cb0f2c37fdcad0945782ae2c9957905c3132e9152243e69520a57ac5c7951edca562e864a908a2a2497f3d5424a142095474fb52c1a2a20849058c9ac2dfe981edf2e494524a29a512a614948c409242aac12889d10ed3a4842905252390a4906ad0e5c351125d3e1cedd0e54329390b504a197f39b73bf77986fcfab63d22a583940e606cc4975f13b5f8f85a6b6bad94d239a79432c668447cf1099cfa6163b172df89d8f5ed033d808646c2b81153f2bb1b2f490df145842183a6befce73bca215651b551dc8e34f5c3e666ba4c15b1b977a40212c6fe217992115fccc09b9987f18098922f6bf460d3ed49b9575330bd9ebd36e76e545965d541458f453232918c4c242313c9c8443232918c4c242313891ef6ecb1d1939e54a38b7e74a38bbe728ea68b361ab961cfe4bf165e1faba692a8d78bf31951d6a5cb9f5c0795a25879e0f8385e6b6ba5744e29896c08e50e09a30f7c30b5bc7e89f9d3bbacc81e5fbcf419cf316f9c0e1af50cda5f1ba7835e5996e1fcd9e37ba1b5b15649e99c934a5963c4f9caf7b5b87e6ff7b5c8d78f3887ff7d03626238edafed859b7d5dbf02c5c2850ac5c24597a5d7fee2dc0d0864dced17beb0a67db60166b9d1ef85d6c65a25a5734e2a658d314564e7df40d7b0e8f7630e7b44917e6f2fa246a2df22d2b6471b77a36b9fa5e32be6b5d20c317f6d38627e6f3856fcf598eba09cb6e9a03d265be1b2629b11f5b23d7571696979fd34b145b768ad758bd6da6432f9f0377a2e91467f491bb70130128958d041ff533decedeef73780ba8eb92292900481038492540f288452384b8038f2c3159f1f58dbe7879c95cf0f36393e3e80ecd74200bb28d563b4b7f6fbf75f38b42dd56373356af4fd6fb4477ba77080d001c2878f8fd68c548f548f540f2d0bb6f17b5e1100a06b3034d4a75f43dd6a484d4d83d637658434e3766c2dd6c8786fb456d63a29a574ce2a25d535d2c6d86bca880dfb8d3b64a43bec3d1a8d46db7e1c5f4229eaa29a39ef88d7bc3e7aaa6bbc3e9a5f754decf7f1cca396273d0fd397be6a181a5a9ef435b46c4fa6485f8369137de9eff5afdf1f71d8457f1fc3d1e37ba1b5b15649e99c934a5963ec7424fa4bdb5e44895e7bfb3471c46187b78b36d7ff748dd4648e99ae69d07e1f731a928695a22eff051d86e57c2fb436d62a299d7352296b8c7d669e22821fe6ebc24faf5ced5f4f235fcf91bbadf7b590bfe382d2c9bd7b9f4eaf52c9305ba49717d3a251ceb07cb13b338526940a74e08a36ee06a1fe05659950b2c81def11f093c19104b18b281bbb88b213bf9c8fe5fa62ceadd6d979dd381875fadc68549ce72faa8a1d9f2b9de20e4400c4906faf520e69155bd70b548120e7bbef7ad853c9f03aa9d9ed662956a4ed561adadc03da2829360eeda9e8b5a78937e20bbd472f82223ed2ffb4c83ed4569370aba10218a821bea019061eb552a89d52a6784018882f38889262c31b1be8408e0fe820417c914d2810066dd3e477e0ca8d0d084d28da14d2d90cca28a7b70965e624ede78d5361273b7382d80d96e94fa6a41603ecd7945360bea82a9b53a1a26c1cd7d3cea97afeebef8cd4f7ef3f9d59178db49b0ce62816322816f1457dd44ea1164565d7879d53b188a8281baa248d8cb80361807fd364463e9f9b255dbe0b19e20ec576a3989671d7876047b7a5b4f51282523a5b10ba40b1789953cc2e411c99aa6ff3db6d524a21a594525ae9d35e29acb5ce09a5d2c426a921b64dd160db44548543a921b6ddf256712e0cc29130607742a96ade222ba2ea77b2c22199aa5faf4b6edcd3cd6ef7ee7d3afda350a99d5d9f8343bd72b148afdb0b375be6ec1c08218494e28bc6a967502aa59e412184f8aa15524a131f920fe46c9ba2c1e660912e7f3e8dfc402a67db6d421b45ba9a64739075c11f30e7c92f02e3ed033225ffde5d21abcb4f19d9f22fdf5eb8d9f046cc8361ee76ba9d6ea7dbe976206b3e86cdd0ea5a39c355d71d08216ba76bb1e20e8d3a2f76718752c8a27147731731253f731aedc9449171e7a584524a29e38eb438c5b3ab0868c7511fc346803715cc29249b53a594b065c6af4eafebcaf88c298bb27993aaf802671fef49dbc70f39ea7bc5e85b5e1fd2c0b02e59a064ece28e2e02314a8a8da33eedf07db8e92ffdf9f410d471cc759b50e03fbba1a0d8901b019621c5b3e7d34829e5cfa079efdea7d33f0a35c3cc4fc2b87f5954b7f0b52aa3f063a76b4f2894cea02934832694d9a272064da1d98202619841f36ba47c367cee1d99506c68c43cc8a24e4633fa59cd322ccbb2a8836559962bfd9c5fdbaebe32c3f7b5be1f2f1677e24edc615d754429a5238ee3860ea985a391ad17f6d88e138c4623ece23bdaeec53edfcf581e8d9e26eafc18ff1de59b3129da99bea3fc388ae28e5acca28e19fb1bb1eb6e5c7f3251042d156cd25a6b6ac2b4a66f32ddd776b6666b42796d4d26d34f2193b6d6647a6bf56b5a3399346bd27ee3f4af2841280ac10e6ea697461b77e27e6fd7ea8ced8432a1dc234a7c7a20e2d343101f1e703ad69e4cfe4b428ca9458283c3befe0ddd62183f84f4bea5f557bc70835db46bfb186b8fb1488445222c1261912822f98731d6f06b5624ca44af23c66e526dae1061ed491a765d2adab81060fc131ae978d3f0d3cfb8ced7566ddc8130d42defb8238b37076b5f4a3b31aeb83ec616bff71ebfc7f8a3ce93cfda9f3c18e3d8598c71ce183f97d9cbfe9530486f37aef43adb76d44fe2b76bfd26ad7d66f5efb77b97445bdbeea6f98ba88b34c2361db3ef94f22502ce908bcb4b595d7082703a1c563f407f3e3b5c996fdf9b41cf4e5baf0bb741f6773fa36fa854cd374631646a4600000000d316000028140a07c462491025519c16dd0114000f6e8c4c5e523c14ca834990a3400c42c630840c20040888888cccd046088626532e739645693f3d45ec3175f561dd32dec92c20b81a86fad1ad0bb4acda3585dd40b23e8ecdc49a88dd12fe656b8a2dbe2edbb2a4dedf200bb12c28840d1080e55d3e0c79ce045b1a5ee639c1a60690cb9f60d321ca4d146c7b48ab530aa2ede407a8751fded31e37daa2de87557e6187f1e64f3ac55a2602021fa0dc744fafa03d4daa7579a057f0602f4461c6a7ce09d33bbfe076c82ae314bf97cae95de17125892c2092badbc1ae410f45e34a7d7ef973503b470c8f759d5aecfc78ae1b743271adfdb79e8b6750d483009d5f433f02e6787503fff541756c562a71dc0f414e5afd9d520f7e0f2f3d80454243284935996a1fcda2509a671c1e9bca1813ab9327554b5186d0eaf60c89897688cc73ae8577307eb6ca755e4d481e01ad3ca66a90f11fb1b961c4b3ef3eef8b96dfa9a5036fbd5a7255eb17d988793ddc190fe56e4d68b7b10dd6a4ca1b06ec95557666199c770c0bf06741346e2daee31e1cc8a4ab083f1d3975d9fe2965f1638653e6087e2da6fb29365e22d50a7e9d561431dfbc0133942fe2e8b5ea26c6cacf994f3fc3472465e691d82eb3e9827c217ffaa36700cdd17ebfd09d011296a71d3fb7b9a714bd7f332b574f9cf3e272982ef7041a9688b7ec10bc075f25d166f97b624da8d9a04966d2b3c1264698433437d8954668a609e87d55f188ace997f8e9bc312524eb0d6b8a339d06bcb59caa0588e2915c7124dbaabff556c477bb6d61f259b0d9b4a20acc289b9025e1174e232ac05080c3adbd7e646cf2f3b7f3d9e6d9473e75f36effa8cefa34c7e0edbcefd57c530a477238b029596cfd33c699d164de815a7e0d7b8c68a08bbcf787ee5440ac0fd6cc8a68f43b9688b36060917d955599beae9ee1418df8405e58ca61bc9df2d703a5a8e0b5c3861cf9f7ef3868d3f5f7cb3f98aab4f2bcdcb3895f47536e526f119425f06861b34e82de93f382fbd93bec8d196a3e1cacf2c96694cf0bcb5d036ce15a039ddf0b425423b57808af361abd79c7827d8fbaea2abf3b622b21c41fbb8117c40d23a8778b01f70b2268312de1075fc1228203600fe119160134b9899b48f3cd567de088eb8b6e6b435fe12b6ad615352d8d8744d0d1543eff3f4ee8fb3bfb13080f9963febd54ddc2e2ff98843a67c4372591b84b8ce701f013cb80e49cbc0f8288937df241f30de9d2598b4d83901f23904217751c36b27b8d23704f61fc8ca4516dc85f00798de995256246f4b8a79a0e075e24eb34d6818ee737efd65e5df3e2fb003f6320d8518928f322e889024b92bd00251c9c961cefcc90df4574a8212cde1841d3b14932f1d6a40807fc05609476219b2ad06791926b5a66057dca5d43a2d1852ebf4df0c0a5e6f182d206749d13e55ea516a60b9db6240f71a599c8a143f485f174e1b830a7284c6d349ed2a308ccf9bdbc6974a14f53c1094bb66d375dc4e919cca9e75046173a0e2519bbed66e6feab3b696fb69cc39953047586ab950c77eaea05cdab86f54606b468bf29a834441231ed51248ab291b4cd054a3f4850bb96a6462aef61157f4efa41938eb481a118725a659fa82b8eb78934dc76045b131be78c17dfd1fb4287a0217d7ff26f8cad62f0fb02f88940e7417f24006b898864a4ec7fb41aa32e8bac42bcd4a9f343d1027effe85bba2765fc9e6e0b1c283466dddf513a2ef297d6e3750cda8a7aa4787818dbebf8fcb40706be643f63dfc855b422f0999f78e033b77bc1e740eaf9e0f3b57d4f46b32a463137c8ed591b3b20c084b6026aa9ed6ead03e9fd5484bdd84d82219c00a93de454bd968e8cdb0af21886a2a7cb9dcf3a581d7412bc16a38e31e4e59787e223564b20e557533afe2a89925b391d3c53ea9bf8d55c953e80760f15f6a4959c44b6f983d16e38f695d52282c6b9ca97b1188706f8a5964451b25829148b6ff47c35379fd7d0d581ae41b59b8ff23f7586fd18f7319dc63af64cac7cbecef3edf3f2f414e0ffa32d0ff5b58956da5841be467448c4f4c3b8e1e1e2b02ed9bd0670b0095fdf59978640e2eab9062cce5149f06bb5cbf56b1014d3809889807ad73faa83d73d3682af4ef5e4f781a78a87f751078c9dc6450c6cd04f96f691787ff0338950ef5d8680de36766022002763af2377653da1a8c3fc6d7a040028e8a67f5b3d0bd1f459d8cb34379b68ff979a7e6d21c93e982c24cf9a14d374979cf39d8235d9fcf22cc761c177c745b89fca412b127efd2d9e0806e732809bd1c73026f8dc4b7d20ed46f7ee049a46bad20c32d141a8c7e644124e2d3080cd5abeda3d530e5a635b9818362d9d324097c42b91731a3673981584a29aff601d08b4bbe8c224ecca23422fe0c3016512655ecb94d6e0a8953fb8ff0749f82ac37534971cb062fdc0ae32096663d767ee387eff125a6579d3275d07009fd993b785358926cc190f5c7574bab36195f84e7ea6b18421b8889822ed40ed7253d7eda1ae049b5b47f37acaa2db4f907e4a35ef886c7b043504a56195ed97905cb1253f96214ea8524a6b194ff49fb1380dbe3876406c203f2044a9ec2c9f3b3b007a69c7776aca285abb3cd53ee50888c0f92121f4733228a4e46c885be4095f8cec6dfd1ac023d848faf43d5cf6868447cf3ee147abdd3e023e947db49c6164f3862e471fe75c20a6cf79c94721b4482f683b294611445d009bde5e188cf341c146a933bc67d240ed3c5b9bf5bf8df23cab6cb9a66f0131fa4056875c246b18663576107d55c53fc463a8907d6d86bc9fd31d5e97d7f5ee56c85f86a610b89a50e1d871d2e42294eda86bf64d50fc5915060747b0b4642d61a44ae30d2897a657f7447c901e264768baa6d975ff7b4fd1c1f007921019255cada98968263c03d4e2ea3561da1f8906e735be0e4cc7dc08a5818fc5ee2474d0119964c3d20b4efe8338b1635ba375f9e64c856461e19cd438adadde6cde3e6fb1cda5cf969a019a63376811a5f69b20055c7a49b1a7e4fc836666bdbde2481a53f6689f1179a4b4aff6d8387bc4b3c40a85c8ba766632c087351b587d3deff28c86c2b1d0673cfd676392007b2bee97d60a5bd7752f76f3bd2f9e407686e4c7bb7abc7ca41aee76683a557581e2620536cb775cee25753bbf8b2845c7540f91c3e37e61bb700ec21184780db4268a10718cb3f414910899f4fa7889a4365089d8fc0073b5c11f86fbade6a166d636d1978e80dc19088f83900d3ed4c38e62d3a40ba531f8d3e2ee23cc807491fd16de23701806b5266fbea77110845e179c05570a87d86fe8d04b93f419fbfecb6ff1831f1296822aec3977d83ff3ddc148726be67dd93334f5c49966b52aca66d6b9b04abb80369d4f07ccc83b3bf60c51866f564608999d7cdf728c11d44dde3564d7211d002f52528864c6c0eaffa88d3106128b1b743045318531dd178a7dbf017790699366424c5e99d5000cf9c4c81b927bd047760a4b5ab4a86e55f7ecee3d0b41d8e155676a36f60e6e0544e6c38669dcd70e85774dbcd8b5c60d9455c6926a1018f4ac560d70bf606ce40e925a5ef8a8ac8805c01784b839b38f85e01482204a9854e00e39622c7c14cdf5033ab5f190868e7c437f2b688c9b73e1a3850b1969349501e36ad425433ebc45fd19593b9829b688b235bc70241717f9a94d2bae89efc7d40788ff54308e4b77b8f7ca4aa052e1a3d9680dbd7bc247ada49080ac375746a57fcd859fc1760c1df16df34096b080cf447172178a2d056a64cc65d94776ad909bc15067a0960500087dc4278ade938a146b577af9c231d47785bf147f7f6e01e5e82643dcd388dea1ab49ee33f87642f8280f867e2e7b835ab8e3d8e6d2631f1bf34b558d8188259116edd9b01e7c9483f781fdc6dca4f74bc3e64aea0c60f0d1718a16739bd88fa2e9961a1db1577689a08f524c4b3a4580695f85f7267654ee61eacc89deae96a57c6e32301f3be7896070a523c82ea29a22304c7119af973a881e2202e5aed078c53606e3f806500a39ebbd09ff6624968dc70f99ba4c29ea4e76dcd686a9915fcf554b0352aa7462ef2831a35e5428848229dae4d2e3c2a3dc7d40f6ed2bce24b12f7437242f63e2a70a4042514850c882b62872fe4d330b5823064d1e606d623c23be9c872e5c2a1a9af95952f191b31f8000b271c2896441bdb2cea41d111978d064d643b122aaab52a7dd90e68629db716441096cae562f98e8efd2e5328e5117af9896bce71232e0b22917e972e24ee7f03d5206bb02911a4b3ab83915b62f72990d8a9250a99531b56ea3a3d5f3a49df0becb8be6889c3a17695e175f63f3d346c36e5404a974097f6cf7520a1410b09d3cb4afd1f8e4e8609d1e1d458a54ec8b12db0eb77679c79428b5aa7cdf68d450701c20a4025a73de316fe58fbb6ddcd63aba2cffa23f7537306122c69afde008842e545412c6a29311de30088671fa65f3982a991772d8c7758010af2ab956477558e9a7369f81e81cf63b40baa4c815b66db78f30824e32db5c2ffe4dc18a24bfe797d0f26b1d9b4d11d85d3bda46e9b1eb1c88fe21fe173a94d566114a0d2b99eaa8736051c44b042f88fc093707a3e99873b81ae1e9ec2c0d36c3c9e4233586812adbdf52d61d8e0625f5aad209f7dd367c0c72522c74568cd88e9e0897d785a82dfbcac1255a4ac0c760275be002650bc073ac407a74839cedfb8077c90143c491dbd38c261e213e27f51f233f48b4a7a9d657e4e9ab40a7171e7875d1355718610b0ba82e23f44fa657a1cd5f704f1ad1a318a58efeae3114b270d7e4b52cefbfacb187ef9479885e01d053defbb254827a9cdfd29640143511bb170c7d1b3c3a560b3d63e302fced3e932ce4ec94617ec234520053ab8b0c259c75fa4a81141117ee1b73a699d68aa597ad42c27fb6805d9ee1226e6a59c47b568dadc877164aac4bc347389bff7d2178fd9ccd6e51e32309eafc7e840f5ab571f5fb095fea491367bbc8cc4c99862c98d78b580b3e9de9abfe9d6f7818de0757cd70711d45268ce1f1b3727837e8aa74d9bef4a777261aea44d685664bc74c11717a9085d5159632d0eca70b293bd2ceb3afb32b95bc64b0790e73bf38fa46291a55075f9f8044e4d5f4b915776393a184101fe8397b65715f7b40fec480cf94d3c899d091fab8dff8d5649033dd635d23d57f39c035c028b14b10ece2fc7541d5fd7d5dc3c6fa4577519cbd71412fc2802e6df1f75182a2ff70497972c28da851cdb12b1827b047a7cdc066acb179136f55f96d6def4bbc1474036dee6933c579491acfff87c64b1fbf5a5d4dc4775a1393de8214b969999411c265b4c25ef36c0408b3c3409f47f4ac94b3d0bdc067b7265eff4d9a6a22082bae04b93c9f496199b8848b4922c02523e6ab71b431eee27992882167cfd7b75a38f18f916887be28eb61a48406d6df8950d18374e0029cde8ec3cfc0285239c8cbf2f792889cf503230a0dcbb3161c56ce99e37445c8880a6bfe1dbdf7dda660b010aba5f14b13d1225a51056b3b809fae885b570c9870f152a44545b0957cab7d3716a99bb62fbc9435f7137cc0b3b842cd6f3c65d46c7811d323679d06cf24756f9c74eaaea078d65ca0b9f78bdfeaa5e2e2506549f13155445cc2f7e18a728e74f641bb58234269767a78205ce81fde2677b2d368cb1a9166e8188638854279f53c47b2464483978d18dd25bd705771ae86e501b01e6f8b07bab5ced84f5b34786c33d65aa7ecec152dcd0fb43e10b48835c52bcc04602a8cd16f36cfd2c2ce6fc5b64212b1d946b1ba324666d8da3bd2392870eb3591227b92834f39dca497b2209015db0322b16fc1fd1a45c5bb591736c907b7b5e518aed13f3cfb1c5fba7a911294c3144fecc4348e5cc6b9a68f04b5dd682a0295581fd4a42baa079ea5b2a4233b5ebb4f4da98099fbc4878e03eafd08fe7c7f4cbdc2e742960dabe2fc979b7884eb47196df7cc0fef745a0d4097ad87879113393e48ecdd7357bd61d9f6318de47f1ebc95fa52a216ac08ae4904838bf99c9364b838ee45e27f4107bcfb9d5549fa653f66e9bad07824dd04ef0011f4331928ff467cd22fa427d0330c115ff85b794655b39e9717acf5a6508e65a240f54780e0d507b586c8260915b93411a9f1bd38d6925a95133ac20723f2f71160d63ad6a20f9af5ea835227a63a8c71b496a2e6cf1bd105ee08b6c5580a431ab4dd62788b699003e98296824eb17e8d4fd8233c75f4f53725bb7114df4c1da181afa04192a80406c2cb2921f58d1aa6a2004d080f48c2b63913fe02a1a10de875432a678f7b7b7ffc3351d02466621b89a9768c695de26cf1aa196f7ccbdb5b08e5b52ee01c9447cde52132317168fce2d07f2e1d7857857ecc7581be90139a89ef5e002016875a6f31ac523ce1b43b1d8cc5aed75e76fc98102a42d29e00a72521708db3415de275aa47ed08f17390c6157d0f672dbbf95524a046fdbd5941d6536ec3655c1cacd3f99a46ba5e87a8ef8f1709d52f42a8be6877eb7e9045a58e490d3cadd332a093f69139b0a15eaafd60f0c02fe59650fd48aaecb3042b20449a815fc2202d8197089e30a420d8460a03fc11a70ee6d294604b03c780c904d8a1e238d042e475c21720576780e66d077f81d23056ee3ee1bd409b26123249f0fc79480c87399cbc58f7fd85d81b30ae02503ce9059af503508e3201ba8ed3c9d215d170cc5390c63f9446927a42b6362e7c07b963082af51d3e3f3cc8ce5cc0066e4e22249af711328cec47594ab688d1f751a6301b4a3a96d7fc1bad5028571d564ff7a61b15865047ed592e1dcdc08845f389d707f1e1a9af5c4e8e2b75819e84452f7ae70867e8e1e95d783cdcf856094f94f33b7ba348a257201791191cda6debf75ebe323f3527fd915e98a1ce1a06c04a7c8dcf6f92cf23411afaf734485d1521961f966014078a9d6ebc2a2bda02d2c8026aec4b851a74b568705f34cbf6e800089d97fb6826cab5bdffb13497c2e2c00a54bb4acfa3a5ff863053e13b986e146558510e3ed9a7403a479d7714225b9ddcce013f4498e6a8502203200325cb675b8998292b1029d531749d3e756c4b0e9d892840c03f04448b09a664abda0312717621c79ee08d2fcf23e7db4b43564a556e47dca582618a08f79e086476ee732f0f7638f815aca920c2d6498192bf740a571af47666f2b61d021187db18d83af646a67b252e414f8c726af5b45d45601c82f5b99ccdd95da497ca5708bd7297956088d42beaafb117ad230870287a95b8befa83d2ba7d7b6105d231e320d4593791c0b59dfd0627dd2c3ea1e06c98111c2459e5d623de560bffcb96675e238a594abaa1f243e3506197f8688edae88a88cea9fccc836f2a01ee8ced0a159123f83ed2482f392188909c9bbb15e647a552f011f89c0e40372179632e74faa29ca752aaebeb52a146ea65e7ae33615d9a20286060a55f01deac18afafa69cfcde202caf4fef33480899c291d237a582b1e644f43a6a7b11f489427d5aab896a953f0baf1f51cc82dfbad589e33db08cf55c573b3074f6fc59e6ad7f323151d17d8c10340feac1a039ede15eecaacc4c882b886e0bc9c15bda395040c48c84765684422cdec1fbf78d67d74af569797178f5516198d11d1e66699daec98e8bb3b06bc5d08f90b4442c099b3c2323297c88df7116bc5638707cdc50fa650bdd69b902c9bee77a1720cbf9dd9e74c1c50c88c6d68460497dcfed9506efe358fbb5c64ea792dba1146e7d1891708b6d1857ae47c2af7e6aaf913bb1be00d96f278c221de4196a9b6b4f87a6ec7b85610170340e68e0490cad157fc596e08ea275e613cf680cfc67c9c2f112e7d9c5ca811fb2b2555c565628bfd75548efb65c2fb82e3839891b74d1f2915ba06d9ce98eaa0ecb45157993948623745a6d50ed6b6237071efe1fee5eb557592b2993c01a85433e733ec05f999e4c67bcbb9a628cee91b3fdc48bc21e570f8040ec4cafe3de2a1096a9731cef2d59dbd18820bffcfc11bfd03a5ec0f54bea3103c3aa9d1245933cb5af60be8fd2ad706d66362a75b875d993f16b959bc009f4ff1ca5ba31f65e423177b94b5ae0e6b31197da43f8ca7ebd1b5d32ed878211ccfb7aac512222815b34dd753403a3cd673bfad2f8ed53c19e6772e7599606f2dce25a19554a3bf624369fa7dcf4984642836dbdf2912cc81e4701e99307e3f1237d05600e369a4708e0c10c6d969fff6c204cbf9e24154f15403ed6d23bdb18094722f8038a9a06234d2c6dc7c0660757077f53648d49874cce1b6f7249a5ba4ee85d9459e5e89089183c8cb9037bee453d42b44f16aca30c0375f1e14d87bc389e552d2b18c19cbe7e0bba58b8850093e602ac3fcfb9e3a9e74b24c8aaeb6a0ed5ba974c2e73a302f8c54c519c46e81123b1f796206681fdd0ebfba53b41a71040adc31face03a7c747dc3f5596c1ee1b2762ab92d89de6fab402b081eae9b0e2d9fc7e2556d8a688850ca56b43e82deb49170751447e53c494985084229c10803b8f8e0dcf71a4d0c7f69b1de251be75ef6f4eb87d2f5999442880dae6bcb89c2978ee62b77eda21315a6ed043357c7855c01641eab6482baca5dad0a5b1a846998362945ea803e211dc29304d3eea73a405d0a4d280690ae0b5139145d985eb778d4e3026f084c3e52e8b30d617b56f202c0f10b906c143b15bc21822a8a17cb0adf97a7ea642e06bc82a8f28958d1eccea55242a8594744e001a7f5e5993a7365974a09fe1d45547014192c2074fb6961dec4c85ee248ca194036494bfc8115ef35068ed22d1c044124bd318063dd4d02b0228af7972c9fd84cb7f177cb3b39376de1e471b604bcd49a4b47d22e2359044ee893631128de6d55caed94ea12bd356d692d3ad9f079ec75ea904d62834f2297b3463c142cadfb23590bcc5c88710d651f942ac2d2bab2b9b9ea00cd140d96ab07b3c42db50ab5eed460fa1ba42de5cd7644e6310db34f8f5689ff604a103984881cf0bb0ecac86e42c590b62503fc997b25f29c82fdea08551719b9637d056bbf0c7c9d976195d6ac04b5663d1419a8b5255c9973bef4cb5c6b4f5aeb94d1bd3ad9c49e545bbfab2361b50ea441185bd2b7801413b7950fdf59d7f1f151e98f081d8a19fe0b8a381e432cb485a3f550e10363ada10b48a892ecf00f8d7c1307cd722542e3845a17b24de917086390bcef1aadf7b8d3a755e662d01ad08d95188fd7cb652029e80578bb1c7167bd1aa701c0992b2ad9d92d37acd0da8c0717a94e1be5f43285a228629318f064fd4bd93798ea465b2d397151a1b5f93cae42e36679d8355e82da3e96c3e1503655a6bfa624c6fc93dd91e879b814aeb9c13c61fbe0099fc2e49ce86e8ffc4c12144cf713fc93604e6232df3d0e906282124914280a7a3761d5f8e30d0268a2647fd3cd1384661781f6c6772120c42556a18588f0ca539085466d17b98ee36591e47e48e559d1be7b11a456d65b131aac246c611688c23797dfd00872bd8bef382c5214404ea867870909a59caf8b7642b2f32b0b093899b4177650779ceba5e04fd66f52c976fc4608a6e92b947a9e45603364fac4bdebaf274bb0b96288600f93bd9b0af10a8f9c371b6d59378bb6351f0d127663d0c8d9ab08cd0c5eae7fbe2b3e6b7668b55190bc289664c0349254bd891f58385a745431bb96dad01577fd929dcb7636e318602d5ff1152a4149a2087d5b179033d9366a7af3c3eb3fd718ec9ca276fcf0c1158d7cdf8e2ece482f78465aba5a04b55d49de5d68cf8f54d38e615efe9909c1126dc1cba1a584e2b334d924a9ff8c71043584a0b29fcf8fc809cecfcd48d75b4747634ed3bd73a29a4daeb1027d5d3be85613cae2635f9a15f64f18d7eee91fecd335c0ca4b624252a9323654b6d721df8cd02abb7f9e48c1311f63aa329484ba001bfdbe177e0ec15f6e2f4e8fe28ba8de88836eef5914b22882069a7490f96220e3819a09586beb41f87c3913b5ed9296eed7da14ddec110d9b9a04dc95bd471e4938650918611a7b914a980641950ff31eb9cc17aef7998dbed609212b032e2de302bae5187a7e9156f248a6ccd43b127f115922138b06f0beb1165f7ba0251cb858ca0974245912cb0527e70ccce96526ce82ee04e01115ce3772cc85f93499288d91cad6bd97be49d9ef88be4a0debe777e43aef293460b60ec746200e98ca0b2fa06c2152f9e83797417a4a0598fbbb040ccefe8136494d8d8f6fa25eb7dc50cd59961b615153eed0316f34ea29f1ae1829e0b4a976236a979e6123d3b39f757852c89ee2d35c618bf2e6dc21b77caee950c74811836e1305e846c652eaca3c21c4755d0f4fe41b51695da1314ec6b36e5e22f8300b7b6d2c3167771c5ac2793b3e9632094068e03a9018fa2b886532ccba3ce6ffb4232b7646a2be99dd567d394766bea4d69cb5fcbe4d4d4d63db5380e9f4626537213cb91a79181e3e1772ef8063879578d563a037c3bbfaa872223147a0bf9c909c8d676f10df19b17dd7c6b4d7f93982c193199d91c0b10740d2658a0743817a37fa17cdeed91d54ee14d8259cdcd2242e3c6dfda3f78e0b3364cced4dff5b292338fd13190b3edf9a4ee97932e96df046be9491dd4819d93fcac816d965208932a26a28233b6b06ef74ed808ceab19b2bbaaae3dfde7743980c3139a449319343a3c908ee9003db52f01e792b88e86cc7f1720b0108c8c8f3967eb4bc07f4049561604597929523aa2494064a045f731d12680d7091c80281bbc2fbaf1bd5004bbd3e04446f65bca7c63e98dd7d84b51f99b4b759feb4b74684bd65d402eda93234b76136050787b8eb21f64e69a4b1b89e5e881d6d839fa5de2a09a0f53f6a05f559c8bab92d643420fa6df91e19abc731cbe4a3a71d295c00292ac8e7ec397eef4c820d0ff8c1da13470d8fc9a21732a4e5d80540daef06732473dc6b0987e5427f577790eb3d0ac955d999a36b92b0e5cbccc6f87288f59319b03797f0bcf048cf69eb859530685a2b978aff9294a1e51099fe2ed0bcfd786f42a5a311c4088543082150609b4a563b062d356c6546a75e19712ecd394c94e6dc2e68b1e6b33a87be940763d76e7fd892234c1b19def524f48dc14e4e472f57963b2d32ffdaad9b7c660a33a044c4218cdce81c5bef55df19d629f8ade648383df5a19b247f038f4f4813b53cd0feb13a894d945fc23c0665848e8f9dad5adaaad3a9055148d1d7c08efd817037bbde59507346a0ed1a0b138c599583a01ed0d75c8dcb3cefca8e829df1f50cf4831ee0c965b3da1a19705148b94ff23af81095e2c43784aa9c4f731af72f4d6b2c2ba5c163cfa69d512b82d37e2c7de7635bc8cca688a00bab7d34f0e56afbe856b54afaf6e9c48826f7414f09e7524ef7b90f02a3e3e8e587843e1d2067af0a46b3f4fc1a939a95799971267e67d0efdf43871e90de3e69e306d43a9d7c00a19ca9d75c233d36a2f00321bd1ff9573363e638072312e45744a2d4290257c46a8f06f8ce903e106e9cafe86e7abbc9e61d5be734903dbee8eea82b65f1a9882965e8010884c886107104f92267a6842ae2b05480b48335a4de755e0438ebc28e21c3cfaf5a0ded840c8cb66640d5a051dfc9829bea565ce0116539f1248ea510baaa10fc8624c3baf093f8a72b201cdf52108bf3ee754beca54888d9dc113c4cf51fc47f7f3345c57d3d33e22da8764ba16a705095f460f7652881a65ba7e68c3ef0d07f3c4059cbcc376e7560412b5311f9f3bc387bf54e85329739df9858d818b1345e960e59004c2ce0885927e49695b316d497af4817b8e071c623e569baf04cff59cd766db2630d3c13550a16bd45f16894b5d4fae46e6151a580c2289bf356859d3a468db05dfd0a38849af410a717989f191239f1e4bc3299b91b605d39e1c5f9d7bace3650d0fc75069caf412bc744ac3be03d5f0cd8b977e10751ff5495de0549ef8daa731c37579699f6b7220eeb7b50492e29b80cd141c1301481bc1498f04c32c0800ee6e660dec452e70cce6ea5c26b1568cce4abfcc9581dd2306e4b1e002d5235cb12efc32d834775ae7f97a095867594f5fe2fe62f18aaa77e87c8d75f74b3ced29d05cb8f9cf82ae1d0df521c001d42832b7dce4f7c8a5aa032d4b1e0c0e37ad8931da0d627145e44cfb12629661ca1df83ab87a07e973da801ebe4cc1b49df5089a2130cf7890b79fe88a684fcfcd83062b2e426e5211c52fe21fdba135c389e4e1b74c97540ad8ddc599d62e6f3c455983b76a51328bd656044232259f70b8d034ec89e49185e07bb39c500ca495e52f117bb9e227a8d74dcba6bf56d57746bf90fe2521375e8a0d86d7e108f77b533c60b6637ee529cac1c798b638398cc397ee34b641e3388da3b5bee528cc3ed443d8ed15224394717466fccc8651fd1c35f2041ee9e397094400bf1853b2aaa5cc223d96d945597376fc641037a6856b112f0328f83b283412c053bfb95132893606bf36a72f6f663ec3e2de4f13dc78286f94f02f053ef00a0eb9d4c0f35a07309c2388f970aecac146194ef31ca07606db100deef373b742511c7bc33c958504d8f5122788826c7087a98457f9b16d84c03e80274ed513f51d4069bad380ee84cbaf19cc70f426402825ab0c7eabf5a558393ff7b753baa29e4b2092a9be1d2d705acc54427e8acd9e83b48c8ab92bb6a7a20584a3d3294767fce9e0b3c10cd9b94c35e3181fb80f27423efc41d1318a58a667d446895724ba5dddd1e743d0d5586cabf7e3809619993c93acd96054379451504b916f08fb8b98f0cb03a8f358920899e7a0e6a10ebade903d53989fce966f4c75d4443c271516f6a8f5ad7b5ca7faf6d0c2d6089339c5c3551df0e91d3e0cc34527ab356d29af8ee8d2cdcc6d245890d65aa63fb0474ec9c98b214f1715ef20839df7d20d74c6625fd705b705ed96009c5a208fcc15bae122b9aa6d493a73fb6220b431ab27332901b5c35687d95a2874f96c642daa3cfca498a169d8e3483e64c8d742cb460d4c024cbbcc1afa8f6d0df4cd58a8316d78419aa9306693b81145a4ccb726b5dee1e01450612320379b03c3f08b02bfaafacf976d06adc937350806e317228f3e5b3d1352643c46751900686199cbff6e7ffae8200a54c2384cf41c691c699156c99d704d1949fa6f343e34e0b0085ac7b136ff6c0ecbab65454f29c2b3d648c6461fb44bdda28127815d69271568920daa48b2543647cb95e3029bee8144f9a3ae2927179a94963610dad0c19e75ed8302019d28f1cb2eac21cf6685937239f6a4ac76d89f378ad5a4b7d90ae6b5e54815f0cb6c2aac210606603cb4ab13013497444cb69238bfd9a018dcf72973bb0075e2e37b67829f930f9cde0b588011088cf892b4fefb17bbff82783eebd12d362f7148bb001cd182780c0307fc9a081756f3420dcee247d461fbf05f292fc197dfea93e2d9cbfdab9dbc1b3d72274e581898b0face09f103a8965f46ee88e8eb8a2b86aed3cc66882e59d23861b211643c3395ac263409cb792b8b0ecb43014798acb40240e4c18a6a768ff31b14b340d88962c47640ad35758de28f6c2538ec74b80bd8ba9fc74ea61f69553b425bd7ae7d6d919cabcc7332419684946e61a5bb1bc8fc9aa98dd65ea08df055de0d8d8f43f2d1e0ca08fe32c919a7471c9c3718b50e717806a62071f158a23b26b145782baf44b9a6d824e988f9d6065426dea5103cde1ed265424f573871a9a5f49f6c24452cb186b22e2147b1c5b1032887426e08f2a6c196a41c02a02e3cfeea055eed6d6324513f822353e1580c877553046a058fd5c1c629e6e96aeff9496f4e564b8e1d0824f74bd30d7840d1c7ef3d104a3b38eb584d8c91228a9d060b6dd90b2487f38a5dabc71c8f4431f5ea187ab61b29a87188e1fb4a6c703aa5945e3c2f1ccf3da6d41a611541b4b6f804bbf08e21283cf186311b62f86a14a572fc6b2e48b1b8568c4a9a69dc4b4445b900b61a854cd203a11e26472319e03b3d7f48f6151e2cc733505d90a33a5e698f5429d9c28e54fa463ab5257ac3a4e913b7e48c26a210bf18d73b1984ca44e289af872c5f40129d6e446a29b0c967a0a333a3c49c45b9143bb484e63659047d4751c32b612826c8b1de3c194b124ee4b74253724bae4b0dcf359cfe149d21f6619afec7d6b9010512f2f7e0e2d167321543851ec923b817e32598c664d09fb453590e2e849266da5a38c65793a4c244bf14166ca62c62742ac60b238d3d9cb2989a7d7048fbe37d7565eae023c23a1a7d18817c6c8beb020abf1fdff893ef891ef074d343b93533481a8182357de8741aa312e6dc942d0c77fd271a61d4acf7a4d2bb563a45f719c1d0bec0481fe346975051c13396d6fd787680ebd2647b0891e0823a66e07e3af3c92cbc2c842a7daa84a57e31aff28902bc58b23de5a4dbd4164ac441ae800f8e2b527c9a7a500b32c5cccd689c7585196a6dcdfbdd3c2b4fc5201dd498c05c7d8b7b20301cd07905a8281bb595db09e37d152d37587e619c2096ec1b7156de9f6f6afd3680b68d7e879e1c70155f7ecf78e6563c06c0f86ffc619de6406009dc307a306336e66738887af3ab0e28ead1a612ecb5279af9f402af6d6ba1621de764760fd6957a69b8b3f47c273770d477de1540611b8ccd64168171c0371abe2d96fafec79338b1ef5134805fb5b72b83ffbda9d339208b5a9cabe5e64b1bdbd6cb5b27a5b63ea0efd10954f0137b3398cc67b2da488abd11acc980be4c5a9e115cc114b1c75e3bb5ff4399a42ed0c7b9b118466657b233bb0fd0ee9c9844e6189a3f468a023179d9fb91812221c5e934f84ac6c08cbae4ea19a5af7d1690f97775769eddaf105b33892e92ba407397f8ff12f93d6aca08b8278fd06ade1d803455c30069bee2bfe78ef3009ca9900b4c0ca25170b367dd797c3656e143fda7105eed9ff1552ed8a753118fd8d4bf07c94111b4e8c83d77cd60925f10498de31650d899f2dcef45b5355c72e864fcb8e8b032f2596498db71632759fddbe7ddf65780d3cf67671ea5d3463e9f0e6dc3321d12fc83ef71be043c3e3cf3308946e889f84c3cab7bccb01fd0a99c2a3a176ccd1c33f93696933d55a5b5ae9fb4d03ea5660019e748e747eec046fd416c85d8a3011ceb1d589bfc1eb446168b9fb524b6c9f9183cf3bfe837abdd787295612fab6584d2beb97ed4cff2e1f39325ece4c11ea7499d7a7eb15aa1c40804853446918b32b58f10d7cd29cb0818304eb4800eb6696b69ae2572be199f16d8a4c72fb3e32ce52e874729bb6434de1d6b862073fadd74b09fa38f4392ae703b5503b069764d925f3aa4bebed48f4b5f9db30aae48cc850d52bcc6e975652ae8e8a47747e7e61a8f4af6ebf77f44f10b69a9b2cd1b2a91156661ee8df53e036fc241eecddd9ec8c69746ed6a33690b77f1e9f34150df7b7c08dffc6b7874bfa007014d2d3aa6d3dc20692ad20b2f61f16b9428f49ebede116774f64883f269c6a8c614876156c877e0dc67c4e03d958165ac6de439a886a62930ae80db9063f1f01e79ba30df92b7d887f42adfd15cd39597488bedeff78ad309e30d609caf50af3274250f200b764514b627135e2d18337c082df38578506355ae113f17594b12637974fd6575a5ae52d756de1263c852412ef6cee6ac054b2b63d8e34b9b978b036a5d56a1d4ea8f72d6d19708145ab82771b6656deaba3579ca4e0a36131fa67b84d814ac435b104131de643964e30ed45f870b2cf68bd3b55a3c65df3d31fcdcf919d5a8f7e92f65d900c39d67b64b114ed80cc464c1130b109c874441ebf390df58f11c9cff82ab1d4d560fb792d1b6e3cdf53debb5ad9fce39c0f62522668472a0d5bf683b457fcf1762486e2a3d48050c51f8d5561286a5624c9b0bd3213862d073b4dae0ce5f4d592af1e63a6f5c0a562e9755529fd1e15af008d3647acbadc295c3e9650c2ea3786b59eec94505c8fbe426de566d435d6c085f00b50babf58623b1ad3fdd1086c2d7093fbe9dcf40199c23cbbbb12b0b94c64c7b60a97e40c302aa427959a1c2afdfd0f100c05f8180de9d06fe808d861214b8759e19d48a914ed482ae11fa5844bd2c08b34fb6b30e9d3adb6a00bf0efb5b6ff8ac29ca5c426694e7d4d717afb9b11e5eb86f622555133a993ae56c93683be914d728ac46badbd8cfa896dd7ff347bd659f6c7a166aaa264b486acb248cede20c9a1e7c16588f9b0831783ca17f0656493ef03613e0dd6315e202cea5be726b1398c284c3d9a12110f78c0a72e640876f8891781ac35d106c30e8d80714c4c1657b99dd640c4033e107beb40b00fbfe24db3e481914b695357c183a2fae78c85b687fd6f6de8dd3e284e2b8308d37766c3a3e7b48376dc0da3adc09876b0e30862a1c0ba8692f359ed1ff366e8a8061624588bc079babc0665fb54ace8ee6babe374309adc4c13218d2e6eca37fc9ac69efb03c121ff779cfafe83b05dbff61e088e9378e836cf5d8be9d000cde194e8e2a38f617dff093b65eb62fc0f943581eef14d9a35bb794f5d0ce638aa85c4bb7e4e48c09ddc0a869b64251f8658b32e864edc7001dad3d5b34ec65b902842031e7aa8e811e1fe27f559bce3909762d8444e4a5f7bfe24b66a62ce07ed9ced1f93db5f78db1c0677063d71825bb18587247ebb996b583aa1f4bbf26fadd19d0bb307e7bb7239ebefbd628c09aa2eaefb7ac68223626f35e426dd889b3952b41f28c1daff50f6a123bd703483cba177b512fca26f57ce0c00efcacbc540c9c175943b24338f8164e4c866d472f05fe8bd8e48f8fd3eedc6371b7f12ca27e3bd698fc4935dc6bc6d94b1df0997f129b8fa7983b090bb5a80c2b84a52308a297edcd55ecda6ed9976f51e42ea29828adcfc83a9abe9344dbcc159efdc8ec83da1436ad0f2a6719d8356dd832c00e5c1ca941a9f3f8e3af7edcedb4cbab94a506ca224af4f2210ee97f47ebf430edd5965378d03daa7a79b9f262a7f1bf4b34d2482dbbdb50e20114e2590ab4b8b59e3aa551825b9c664fd8803a0b25c6aa5f74e42f0969780c9bba427cb67c92d17a817b5f68990d6fcae15871460a2c50968af40e503ef12d0a2d39543af2c6ddef81ca3eb6daefcbc85697726030a99181e94e4f30a04d03152af7d12bf9249288dc5150652c0926889299aedacb1451e390a9187fb9207f1359b9a40cbf35055bd008761124f15ba5c88a13dedc14063b03c004f3646bcdae0704db6822e1281fca624057d0b1bff4e688996482d1be8ddf6644dca643ead447a027c1a8abe0dc32fd682eab1849a052138fa33803ab1e3e1a5dcc1ade818c48933398747c9dafe2fadd8e5df69a40f4b1c8871e5a13cfb00e5cd15b44124cb2b7114071ebedb29ab39c28b5c0eda36e014222885d147990b0727507fb4f0ce27b4b449840da2196d63b48e80febb89205e88dcdce1bf91f3408336237330dacb8c3fea2341b6e3842c18ef5ff3256ca838a204494e284fe8c960d226920a27b7424405275c660671fbcc7aa6f8c388e812d39fd3b1f83d44a97daa9e0f48ef27bd6dc75cbd787fa2ad8db22f0ce84627944684d737cae47531a654333dfe6879bf1cd43e7ccffd622100d537e6bde30f41d627d77e52e237edf25353dd68897adbecd7723a2453b8524583cdc5c4474a0ed93daa0e68222808c11dfa258aeef6442d0f4dd78fec2202d6245c5124fbbd923d971ee6ce697eca9a7aec79877243f08a115555a393152d406719d6ffe0b718099a26c0e79e8583120be824cf48ad466e51fdca8a503625e8827c0057a0b423826e900c548062514c7e441a49cbc7778c6d4d8d8a29a1fbc8989d35073f2e8b984a3d41822796b208378ccdf58bcccdb1a700f28bd86bc9c196390e8075d1c288319074dcf9e070f3bed41935a032d9aa1e14dbbc1e25f37001ba1e37f05af8610855d28ccc6cafc2f8d5706f9f37e927786c2e6e56dc666b7b7a543e8bfbe0f6c7332e64a7ab2e9321f1191dcce22bfd798527758e45984389b63b4bb215b28e0d6bc21ceb46b7a8be0f0359afbe6dfdeaa00e192b919da44eadee1e187da209dddcbd5d9dfa0bfa71307a93c30229773b19a152aa9e67054d2c47c018a15697603940e9ebd63467bbf4134bae0ef7469a62d09782c4480a54179e3d99fa77d562f2ba1952d6ef8588a190ab0e44c1d044a720f3305372348f35bfc627a70780a12be49d52c1a582f922b21cbfc567b773dd718156a238e968c370614b06313908c1d736d72984f503c999c92ceb0e93a975de977c6e4aef5eb938516d538f4e93bd2f7006fbc6d008fdd397faaa9abe2c87cde26296e83c17501195233af77bb580cfd3efe12aab0e0960386e961fb99cb76546be11aaa68b842ad6ee28f2a05f7c559aab95662ae999a075831929086e088e8b0acad246a63fdfbe4c205d37dc993cbf9be9f20ce525e4f8638e721bf2d02d07d535e3d8addd9cb993b2c1fc979d56f56158da24aeab22f59c386fe536b58e3fc5d37f3f1e860d41d9c88e0c012ebfb2d08657785084a35aad7343305732ea6a3e736dcb112717c2580fe0207125896a7efb6a1a24b1d5b8c007505af8384c6850fb76ddf5590a8502ab8fefd4211232521e75f20a91c2e09125e0f35a39918c8bf127f1509282d20905467393d3422bc7fa8b97bc9519202bb1adfefbc86cbe3633d4f9257b4cf1c5fc927726c3ccc8f06e9e983a10951a329ea55a0ac989737ded7230470b07c2931f42dc235924b9e441066bff5d562b3400f9a5d94b30117ec87569037b128986b064711e72224409cf0bb87c76fa7beebcf3057ace118522c5de1665957f7bcb51cf64000a68a9ea3f242670ed8149b2585f35448b4acee0c4ac27a104ccd0a65d76443c2f1fab64f089cd1426c52c7c95999271af496bed4decab8b9293671fc108b7456769fd388df1e439560508c68e751c9e20f31bb8745bd234c2fbb57f8ebb562396836e54f848e0eec3e2c30df4d09ea29290a5871a47878bcf79aa46049ef9844254f2d9142fcaa665c4a0716cd6ef5e894aa5505639eef7c442d105968859e273456415678a91dae2d8982905d95e33aa20067aa5d7fe2708590d9dbd0618504ed02c618a8fc3f61a77fa496c4c8ce1f822f21fe06b8bbcf801dcedd340c3267b8d1b0f7ab93721f6870629fc5ed5a0c3629b48c5ce5e133bf2e2de7adb143ec4b296c5a2f2c98339ba8e29eb6b25a5ba23b1964941a372db6f800310b35f91355f28dc93fb3833fcfa40e394a8f33c9c69a29190a7cd13992412df6e40af78ee050689296b02c34ca21ee04499b9fe7e2de8d6accd0aceac3f8baa1e82340b16da21907f4cc9b629d9dc70bda2b09edfff8c59937f0775b6b5d2425c53efd0fd59e616a4940f0bc09d4e0e6f25fc0e432a13a7c1dba0fb1a1c2c6240103f6d23f8d6a0d6261e26a53e9aa6d08696fb0d0282e8f8ab4eea83396f7e444428f2180d863a7dda88838f8d1a76270ed0756ac6c0cfc9d27b36942937738658135b44ecc16e867ce572a8b8d1e7f9a12b6cb957c4cca8cc77f322aabf3005920f44ea72d40d5d8b904cb847e7ccca8424500954c7570971ab02bef8c698e8e2b06caacffd98fc01017a5360608ec8387d4dee18123b72292c00629c3972e0d6f242d118792dfcc981c0f2c64977c236800b948a83e34f65f429ccf13ee4579da761f17b1a653f33245e8cdf140c46d9442e3b565785998051e01a6e88e941731e66526cb0b25ae72abf8bcd3b7eafd1192a56cc857bf803190cf8cfcbd2972cc6916e2ba3a3985fe79a0a4e47bd25d4f9edf461382eafbdd25dec00b621e631a6272c9788d3b3bcffaedd516c9e0c8587a031356ab2e291705f4b4978c2ac2486333ec7703868becec1fbc31dffd2f3a4ce1302ec0fef624c675e604da9df3961ac3591ca2e553a8c09180efbbb53ae2c670eac7dd06361077f510617eccfa5feda64eff84158f446f171d70cf217e338c8e420351d8bbbdf54efa3855bb2834880e25e98b6be534dd726e462b4994d29d21755d7a753dd47c684c21643b74e6a73d09fe9fd87b5b587ae8dc39a0710fcf626718ea621b6e42d7eaef248c3601e1f5b298fc9cdc8b4d449ee3f3cac7a078f0de460f8ccdf222e4e97401d119795aaf13714929f4cdce3cff21e89cd652e10ddd813f0533f99d8169ab90dad4f7dd43c9e054fa8b3c250df7b7a22dde21d45802466b50d7cd15ed31179022721ae843218fa0725d66309b1e049de7f289e94142398214db6d48b84d245c535fac978d2819680790d7794d1bbbcf7c98a4d54d7de2f6278a60b5ac63313ce14032765f35cb263f069a8eb3dd5289b722157ad89b4f3245c4b0568b0061d29359f493ed6f754a399018f06118a57d609b86ce067dff15860c506c8a2e8bfc2a3cb33103ed4c30a37d941f8ec5050d52612ce86e33522661a9b998e284789f33535b541cd9018fdd33e3ac5da2d7805f83ac388b833c07a97fdc17850abcf2340c721faf3e19765abafefa586faab3985ae107bcf82e43785e293c159fcb7fa485498bc9a0541f5363a3358cfea017b6c7f8702fc46cbeed55638d75c0bdfc305a2240373e4f6f779e464fbbf625cf7ab3905ebc89c77ca2ce8466fdf6c5fe5d49cfcc7e4dd462eb51c1fd6b66265a660dfc610f899d25874c321aa027196c7ac5fcd193214fd143dfe13acde2a7d131b6ca1ab61c5dc7ba047d1b578cfe80e01a6912c0663e40e63dc88b160ed2cd2f67058233946aab2382b667618db01e40c6019f7deca76be51e94bb8ea6824bfa979b8c7c3c4d971d8f28d65cffa40b33648acabf33ad3acf1275dced5f643ee50fc4f68900eebb80eb9c2a116960c6d593af8d492c1744b33444d14be5c9d91413f0fd5d8cbeb701d148fc33fc97a0adb31cfce25b0e069a90d88e575c622445d73b587c85074e31e3078af669bad6999ce81042f767f6fec41cad6436ffc4407c1b5136a62b660b67bfbcc151233dc76f4bffd1a29d2e9a74654b3c01ae1d8a3870123c45908b1e0e83c67b7a62969b05f8d442bc45dd3a0e897091f8f907305d1ece25826709f3b8cc37158078345ea27a1f6ea34885184f280150921194963468803b1e6dff3d0caac48605e093fec160b674180cf085b3e0025ed5ef0e3e40c99a8ea31801e96e3d6d58224130ebc123e951f4bac9fb2ffc8e6aefbf503724f0fa52b2a322bda77d421e3b7068abbfefaf8d530db634c7bcf3f6546f54bc3d9ea341c688faf9dd6349b2a4aef5934e23e36b79cc974a1c42d544d711183dda1eaddd6b7a9dc26fc4b254136e1e72f0c2f50759535cd2ad0031316522cbb6cdd1cbfc0f53c40b25db2fc5335a7d4dd2f919599c497dc65af4aed56accddd0a78c0656012074d37dc692b2ab936edb7460a3becba6fc390b856fbc2ccb16042e5ac6432aed6d742834a2d851a494d7bb4e585db33a539c014ce55bd25b41fca4eba0308468b4b26000c2f2ba76e3e69cd2f311952c524afea669c793c5f11db925b40696364aee4d5e3a21a7757eb292b2fdac653aea8ea1c996ada9afcdacab6c97d85576324fa44f82dd49eb917768bdac12d087efb24321f4b02c7e46ee4b7cc60e4ff85e113774eb982de7fbc1ce40d07f6257cc2abd5363561259a8f08fa4786365c9c6c19bfda419fa510518c4cd866ec7892765c2701cbec65f474c8feced9c19d5bf4fc2e6fb2b5eac8cb4704613ae49dcbd2e8467f650fa094313289861e6e16cd1109c93b6f445e92c81a1bc63cf53128ca27f4d36f0c39d171800a472f9e29c2f90e0179c4c159e56ae229e7f23896bc9787304c39797e87fec20478601da575bf8af0f7bb0948a35960642dd9efa36f42e98097c3ea9120c8cea0fa8dbaae33799ce31a90a86613f6a30dbf0dbd517e9878d860d2d4eb314923081fa25d9c8c1227dfaab583f0a327bbe7a223049ca879c5730543f22851e3d797978f180d4d426c89cae0d6015843e21ee53ee6b2ad45e3a49aacfcac3b0625ddac4e8a7833faec8903122448487c48b244dbaa55aa8ac77124b463facdc8d43651a25dd7271cb08b3d0801b146c01fc46d5679d22dbdc70aaf5403e9bed3f4bfe3248f005589d6e45bb1fe2200752b04dc69535c04862b0ec965d15fa930db24e7b997a6849dc48d8c52d4047637b3a0417a128c9a2a0947dd6505b3d0ef09dd2da7362fd6320b09384aab771e2673f104528d04f7de7b2d9e68b63d184620d41d111f2c1d2df88410f317e2de4cd75fffb5d3f0c7a2abb2b781462460e0c8d8f108f04cb4729608c9646587449cc557379cfc595003472fbeb42f39cb7181afd5f5e6037a410f394f93a4566d262887a2d49333746235f4ff8012dcb6213a0592b352de66e832b2bf2ef2916b32746aea0fe2ea6a9ed8e9812ead0acc98a255bd2b92b3f2211a59841db0d8d5b42141fd96e6064c610da6729327ee53f75904bac1de6d5ac4f59ec6d804a6fe8c2da74c5900246f1e2dddcb07cddc8b33296811f43ad59a1a6f7a8feaf4c6857606950afb28097e709694101fa2bae2c1562fac3b6667c8bf4bcebf06e811f6107dad93b2651122f85e0a2e67dc7aab0216b22dad1c29f588426c568a775ef4a0b6f95b905067a5f810bb0a24809197704d14bc34198ae6b31fe5815551424f139b2c2b25db0c44c32c72e93ebfbb529bfb2eb879735db21786a20eae5484c7b48363047e2d95166f9972c46421ce0ca5284d4d0e8883ed89c7852d9769cd5b121c62ebb46b1b8c51dbd2b118767bf12f7b20f27d77f19ebefdaf6750994f1c411aba46cb3797092349a962691220dedf112dcdc1dcfafe3cc17afb485489651129fa6a33f8f0a9942869af4025e6b2ef638a1618c781fabab403619a2798e6e4465aa1aa8640cb30759fb194a96e48fec4edd9d91735cdf2ab6adc0410e7749c5624f97b7283347241c4b9e1f52628cbc541942c8f3a02a837d1e0e4a7fd5951a4b01c8892ee4ad9ae68d5321f3f0f8c80c6d03862888c43df8edbb0832b99e42431de0a92a7d5afad99249ff07921d5d44c1186c50cf97e10acfd48630bc6448f2044fdc57a02f33c7ddf1c4cc02810f983d149d17a27471efdda8b4645a6a54e88937cc0471d73c83730e02f264e5815d00426986302386439212fe37c1eb136bccba89eb596666cbd3b6c9599b9b2fdbc0b6232ef02abcd0f7dbb7ecb5c570aa3770d17396b412efd6d595b90e394e72958af7db572cd6f456b7b0ee8159359bea94bfa2ccec54f179cf353da35ea2236926a19c007eb9fed7a695d8d11d9c2ed917a345019a4e19c78742d2e73064893e3306d1adcb3f1da300cbb76402eaf17f5c63b1a39a9f51ad6d102d7c12d8d9a5da47b60e79908af0b850288736469f10a204b25e0f35bb0c78749a41359b88a8c65bddfd99876e5e2602c810acb9808d4282cb8427f14343bc8588973f6830b52b8dae1c4974fd914ec1f7ca9caaa309b4e03ec8c4e68bfa8e87498155559e1c1f56eead94f27f375ecb912042fb9d4acb7a30191801063506231ad7703eaf6242f2c2e6228173486d22f5c1304155f5461e6fcbc78950e6f63f4ed27561709bee260492bea521dea9ab0e047f1c0b692fd6947075003f2c82515a74c9bb937ce0d9133b7daada0b28cd82ecab155c0935ba6f4d93fd3239009fe632445e5d11cdf37e5db226c87bd0a58cc521e70e9007bf87222cedbf0c4d348bc9577bdf3c1800716aa5428639b0a676071ddf9d0b83564717e79f07946bba589a5048f8c25c12555b6dbbc26604168b905a68a049d4fe115428db9f2035afd2518f22901c5d1c021dc6f2e6182d7917d22307ea24dd5cce1de314903b6ab943c87c5009ef2b13c1fe08451ad6e235839827b20e7d32f8139cea17c7e20eeb5cab4df606eafe54a7a83a7388bb77841aa44e3af95d06331f5c503dbf07c592ffda235768cab126035623065ecb61569efe04235cc0cfce41c9dc702868eb1e0567cdee8ec583a9a5336d8ade4408eb7da2e0b35fbdf79d1146c737dc99ced3410a70bf4cd9c0b22d89db42e6dda319d54b5b39f6ed79f92c27394bf2feceb8868197f21dcf03528f20bbc18ae2370071c11b995e68e28cb296881d1271952222a942b8814051425c542c59272c909a2b57e8213a9c1a59903116726e9ec160814f20865cfaa182856ec5add8702ad1fb39d0230c250ecf9921103fc43afd59aa5cd52169832994bb4eb63ced7f1cfeba71f2d33301b59fd411a0e6f42866b8179e45a8948d6af323cc5c117480b707a3e3bca0e767873554dcbf6f69beb8582c9daa2adba874012fe32473d68a0325e6fee09589329f06d625566e765832d9b3883bd3400b20a0daa2843fdd2b680e9b4dd4ff30eaa1284a95c95b717b7f7b3eabb9c6c76b6b70e8e8ef59394de011f042ab9818f5d0e51da2b4f374bc45d827f79dc0a9715913c895114e4e79ab3b2b938b885e028c5865999adda14b14be1a73c8c4bfcb374877176d425b13d18fdf9c9573a739fe9b9263c364f2a5d6298b4a6f7833366403cd1882cc63be781e9d844efc9e781e6ae354c2a73482cbf0c28037bd3325d7e373ebc6cb6080d257cf7659da597f7b536131787485292662278560e00609f85fe2b202bd7b25185b1675ca5970934463265a6fd42a2235c41dc57ec6f66ae900162737bd599299d2e0b93eedd215b53fe7eabfda6a0b6ec6e0d6c32c4a686c547d87e750a42f05829b66db1ecc9a86e2f5aecbbba99210689edb3549ea5014871000fb6ac679d9f51ba883b0c42812a4935fc2e5d2c29fa52048524cadd0d3b3f45a151de2551e195c7356b179a2858391291ce9a13924eb119dadbc60d5084907ebacf7be0410671a8735fe8a995c57d96e8b616a199aedacd789ea1cedf3bebf39febb23608e37e14ea02aad9790d0bbee2f7fbf6c2c01d606570c5822159a305672138f4b4c163b256d98fba83135410810fc412ad95aadc9fd4cde58d316097b1e0ea92d26585b4bd157286429ea7a339d78ed688e575f31d14dcfa65c64f8afc46efcc2ece9e3378c40b56fac0f191c80c0aea909033c516236ebd6f178d9d349539af26305d734ab1081c93f83d8b65925b1e02c9190d659466dfcc1bc9f843ffcde46d39dbd424ad073ee404d6243e5a8c60d7f9221c401157ed18336239cdb9a3eecc6698e1df6bfe49c0972c00534fdebe32bd809ea00a058f5920485863ef16260daf45f63af01ff010bfc016346b3ad0717d1e1cd2161161b21bb8d53890672668dbfca03bb2972ed43387859fe06e66a9a7067c3b64ac8961ab2d9bb7d72677152848cb8d83a8309337474b6aadc8de58676afc37e20c9726cfd6d4621a1193eae82d6eea9b782787cbd323d3fad579529311ceb400013737eef17851c63e69d3c508f522d38d5ede79c50cd62172206014d4048573796cc3ddcaaf8643931be0a42518eb4472400d18872fb5980006c5691aa05209abaac0f81d25c96485f65cbaea3c2d64e2b3a9a79a7cef657dd58ad29c17590296d15e5ac4b5c6a5ead364688c7eb16ead48e90f4983f90b318f0f3b8972f91afc454f98838d4d33100637b20fd1b175d821e12c9473a80a3c6ecdea56d3bd62e6c7dcfaf72d387d30f1172f6808341131861a031b61d3faea438c867b5b02a98a1cee764b7e96fdaae10e18503a5aadaf066bcd18081996a6faf3c557437c34c6b6850d228e33917b966e8809440d0858e22e90134c8da8e10c1a530ba9c846619c066dc63f6e340c6796d1055cafaae00f88cda1f1a43db52860579bebddaf634cf77c96a6557ce4a30d87702b722919ad809f9adb47b54e841f18cc16badd8e298a67545fbe3acb4156647d4aa6d345e576d236988e4a5893b794d40643c527f055801864bd44041180ba044b9e90abe833077316dfe081650560e4ac1e0e35e57684b9c00229c478ecae732da8d49614b7f15a8c8552d025aeb45e2c3b9ce08ed6f3f8027bb8ac14d064d20fd10d6b82d3f5fc40bdac7d89a919a329f985519869f72ebf07cd401fd714b67dc1525a8088f558a7546ff549e7649f22c45ebf28eebbc0126ef372380d8d7fb02b51f50165bef3eb0d1a96383c807ed58c592f4ec71f5477d25d148f99bf54746476ce4437cbe8f37429f58a7718a442268f4c17c50c02af42c0a716da4db31ebe45540cb6b48a8300d58a414f999e05b2d2ecc39e46d0842048f3abd91ec057afb1677c2e36fbed3a9322035256ca195b273658eb36a1d8ede3cb3519da39eb13179cc1ca2911074c42bb21fdace13bae95775f0f62d78e08a8aa80ea58ba9ff5a2f4d6aab18f03829b761fd7cb4f71ee477d762a4d368b674b37757114c0ea03881bb0efa161aa2b736a1bdbd0dc5eba4610a1b7dc063478bf08aa562c65f6ff05de9dcf9a5b901d0ba47c8377cbe89c52d1904be2d91f069acf43c6c9823ba3e0ece612127ac5cc42fb56b72bd6e2d9562a3e4028e6bf35707443c1f9bdc6e970081104bc304349abfd2762e4918f59fa8646925e6c254239bddf2a9b2eed9d8f2b1fa7d3fbf5580f66c513c5b557d7bba07534026d479723e3032d60a97777cd918dc05a95102173bd1ae3e3fefa0db81eab02db14bbf62e2d6e993b61c345039e3c3a1c88bd73fd0712e8df51c7c015d4899c6f0da6520cdf003904769a8b65beb19c997824bcc67f1ea48f838400cb506cce5513a4adace6bc9c50aa4224b17df90a9b1ecf0ca558c6cc311b7378c6a8884c1203303b1c6ebf6691712fade8dd23be1d2ea5d61b656c26878382e37c99147ad858f9cf5023ac2f26f8b996f44108db8f9dbfa161f37bae2ac8dfc2fddaa9a85e8af6295d24794df7715882a58d392ce84fa07f0d4fbe865e0c61fc6f810dc389fb84d36b78caeff8cbef123402a45b5974408c1c48f75fb03bb36933f600ea69016546ad9b16784d4a1b6574e61582fc2add3afd520f81b3922fd3c7d0584a6cda3dc1aac1a0dabf915d45424a7bf4d0a66e74eafea7802065f4c75b4897f203a042467eae1c17fb3d36673e1009071b0aa098dd8677cc0b0df4913357679e34529b23a413a0033b5a51039a301db31f472f9f515cd5e3af74baf6e2b28cc50ac082f4a6fa0441544eb04e244cc9a96901bd44de00087edc804396c4c63c80213eb45dd0214f8ac1bf0eb728af0e13b5e21e4a6dafb2b650fdc0171d2bb9a55209655a975b7b8523e2ee9cf82ed0d774fd523696026c07e411a67415c8fd81a7e955f726d47f847018fa2c2bad287b87b2485d37d0621401eb1603ae3f3da0aff945c05eda3302b2dbe591cf14db01e5a82fb203f7a0e9e7f96258db36d3520adb007dce5696cbe03a37111e30643e11bdf316ee7e939bbe7b861436df39b4bf456ec351ba05ba061cd1fe546d4a6ccb7802b61d5078702240c2d63cb3f868f4ce883572130c8f8537787593146dadd7f1993bf0bc55a8eb0d6064f59d4e3996b4c98b8fdf7b16c6c234bb365e2e9b08ac5d4822146bf0580576ea2938385599302baa469094d0c8f708d1028bf92ee0abc698dc54cf6aa7b6a6851410e44e348abf0edae237359565e195f89425342bc6655fbec79a60d421a603dea021b1dd1b519a60f9102dfbaf68f63ab312d16b95c6a28a95e0300d7deeb3660eab2b8831e4d68e5651ad63ee55144cbddcc0780fb33ce3dc31de15c06ce97d60c01f54d3fa1c381f0d8a37aae6aa40cef3a82d3606ef96a3f338521196192c8e81bfeeaf08b96a1137cb93d462a4d96eff3bd49a8bd552e32085a35802e42592158e893eee4b02a80b55c99fdc75896a82effbdacf3d5184f383bb4eac9661a6940c06c66e4ba3f237d55ad707db8c3c612962e3aafb0a45291299ce6a29f539e5c30815de15ca668d9c0e0355ba19ad08a2fd2ff58a3971db4062c206e860039037d820867f433a2547e83cc94e2b406c63e3f8f7963c6ec2ced04b060777b3892ac3aceed635f1a05ddc4f8ffbff63a80bb13c5b08f879fb0cf4ab0241fd44b3dc6ee47e24a75e9da0297500c262040f3d61edaf5ccc92bc78d3b07ab4dbf4c4e662c4af6975920e6761b818fa4cfa42f0edc7c66c3cad433bf8080fdb9424e233255800f2d3e0ef99282bc6f0f0681c7c3a1cad5152a7bd552bfd5ab5012846a4b7be0ec79409f883b6741d8e562efa3f6c38cca2051e5f453612581050e9481d025f829c71c379904f0374e8a5b3e7d8e8a5f880fab3fb0188818c809e2649233aa3c84aed20b2b9116b84c8dd943829d34b430c7a5875c47ffada7d58fc47ecf4503ab4faeb3f7de87ff8975f5a237b4b99026212e813ca10557cb4af01560e398059b444512585b17e0ee02b8a2a298cdffb97f16b21562604ff57f53d88d57259b0d55aad1e746501bfb502bd857ae124493d4a29f572a2671ff572a2f77d0ae0ccd7196b5b2ef02d8d1168bfba66607d055bd685a5c995430a41dfcfb8b268a941dfe7904210f8f583ac3da95e4ddfd7562bcbeab57cd0ccab3e68e62bebd5f465998186ef675e4d5f961c3ea8f596e685966b061abe5fbd9abeb7ff217d46332e2d3568e6572e2d3568f52c57961c52089a792d3588f52ceb6a3dd86abd666065511d7950eb35431448acaf3fc3cc5bd7ca954314555218515f83ca18512f25277af69de8816f64bfd2e0ac00be7d2423f07f0b0bcc39b9401538dad6abea7fa7faca41a11e7425497972828dd68411b54f9526090a95d6cf654be00b07f5f55d202c85a3fa136c845958adcf04cb2d12d5bdda776badad469c58b0048c191d4c13628c49aa26c28caa5616e38f0ec6092846d6d704cbb3bed68e13b284a857d94fbd05df4fae6a539f7af04f453ed224b19ffa93cb698862b5c298d8efd3a1b6ecbbecf42714cec985737af0744a724a3d2b857a925a9dbed3fb09f52825d40b07f5a7878da717cc7b7c57b0bc47924aa5c2824955f9f8d110d593aa7542a1bea2be72e112a68af9522b93eae54454676aad33af4ac3e453a550a82d5b9abe4f7da94fa45cf8fed40497f1f4281b1a3636df8387b3fc0bcb5f49a92c51249dde2aa16a50a857b97caca1213aad4eab941d5b373d78882c41a29d4c59b620d1c58ee0b36cb46e589a56536ba5c2d13aa9deaadeae6898d426bc8cdfa7524f43a4fad43b4d68612922d40bb430f0ab6e4f33687e9084e51685aa19bf476174304b6c71022d6c4868bf2e6942893108e832fa27611995404011e387002de3f7eeb22510e74382f3bd68fcab0bf5ee6252c7ba3f158d1327a857d20bc4327e2fff643e26a1bef220dca093d37bce831b1c3f548ac649759284654459f0fbbe57cef8a4befdcf55dffe2909168b656d0af6d917cef7604e924ac9fec7258cd172f962f4d1eb6861e0db3fbd70c01c4f52557b0296db95f515eb6b75b9b538acbab27683e30aa55abd96ea08e230a94a2898fd7114a4ea7f8f42e1a050d6a65c38a9f7c749a1520f1bbfb7a94f29f5b131a16af5e0ab94503ff32b57959ad55757d2cc6bf524f5a814eac1127c317eff8919517fb262f4f1f445701945181d0c114c8c4eec30da8c0e260165c65dc7ef1db572e1d49ff955ea51a917d8fad4ea53a89ff915ead452a1c0577dac6f091c699c9c1ef5c2a9af8a4252ebf7a84fc251aa8fc2398138a7d59f50b13042140a85026988beafaf541f47f5b011f547ec56bd98c046d4a35eb0d5e9853a9d7050ef38a88a3aa16acd58732ccc554db0ece748c1c4e8a38f0610625cfd49a6faa4efe4c2a9abffea4ae5dae0f8adff6890b0686ac625704cfd0647f0fbbeef67beef7b9026098dbb2cfd5ca7f5c26102f37755a96392fdef6756bf7202ae5e382a96559d7ea53aa9be24587d7f7dffbd60df2bc7c25c044888199d95c3c20171c01713d8e8dfd32001ff737d39348c35954aa594525f55abb7384c604a49af5f384c6063fd5aa58e27d797f30d000c12568cafd1c120c16554e2a3fd2570ac52c954a91957b59e4017cee9c1d3c346d5af4e95ccf881a7c7f9fc05b3306b8f60e5e4c8d2faee3a58c6241cfb38f6c504063bc578103a4ec5a90fc24670b439aee33fc6adea95a41e7c2cab5712165b5f89bf50335f5f1febc9e9ddb25cf5224a3da8f4d520ac6a92a0360d8dad5247150ecdea67bebafc695c55aa93d5a75e4bf57b3141bda7725ad58a80f51d61c5c87a55925dc7fab1158449389ffd5e4c602b55129cd5cce32c81a37f959ad1bf55a5aa5e4befafef95a43eca7156afc259bd98c046d50b966361d559d572a00a17a0c862b4b55626d64b0d687d945a34d61fc072eba3f4d9131223f89f113b8ce07f556a46779d27a06be9546b036ce07501628cf6595c8cdf8305c862b4cf428139c016e3e9534a3d51386afb2c96d330a6583549a7df757ce1107dcffaefa5e42202ff54a566d4efaef33db1cf7ad7525daa23f8ac1ad52b61a57ef53855eae8bf048ef5abd48c95e609ea53afa5fae4f4f5b5f4bdf5718628f5dfa75e4ae0ef3aa25ea092fd5dc753953ada179880ea2e809faac205b00050802a5518dedcc0fc84c30436e68caaf1fb2376470c076195fa7d5f9da141824aa1689e9cc0d6077efd0fc6047cfb5aaae3097c150d123036d69712ea556f5d49a86702beea513444a857fd4753a52ed93f3dcab5549dd8ef34a6a64a1d3f1bb11d8449a7af9f048e3667b45f992cd5f17ba932c0f21e7e9a71e1d49ff11ec472d5d4b47ec66b8619df0adf8366603dcdb330f3e16b869a9f799ad70c5120d5fccccf60f32c960ba7be7ec6d79ff1ba12457dcdf8d60ba73ecd6309bad2fa9a17eb6b1e89f5355fbf66e6695e576a7ee6afd83cebb1046109729a1baea37a9cfa58825a2e9cfa4a915a1faa6ea078d08c0f5f57ae788825c8c675250a249baf795d8922c9899a67a1e22002609a826a6abee645c30c47c2b779f17cb1ef5bfff22fa60a5fa10da2fa195ff3493c4d335efec56a5e465fec041c68bef5342e2c412d1796a0afaea5cae473d5a6d6d3bcae5cd17db1ef692fa62ff67d0ac583705fec7b9ad627d5a616cdd7caa56ee1520417223be35bae9a67b140577d2dd52a76fcf2a548353fe375e5ca8c9ff1aa5ebed837a3e66bde83667c52f5327ef64157b53f569a1ae43aaa570a077c0fa23981b494552920081822880043440f6088c802668830c68c0e66882dc00c118211353a9821b000338405c00c11458ed1c10c31c4589f950233c40d2a3004588121c00cc6fa3dca005eed079e5061c010e08b14a383210013a31360887166743004d8618c6511fac7b2081d071361fd568c025aa5f29a4a558b42d9ef74fa4010044fdf7742598b8a3520ac63922a497d7729b156d867551a1c6ec2f103c790068a7bec7b1b57f29d6894b8c3beb77198fdf059a7cf56074fa1ebd40f6336b01a9ff4e3cdcf8d1f1c391efc1c8f23fc1b61f835c2a711be8d2b7c966dd5d711eb13fee9c3e7b1aa0f5da807623dd0f793a366c6d3dcc0f1ac1c385caa97fb84af2420d51fb1b5c6a54ac1136833569598a6fa05046b172eac576dd1be988d3980b6aa5c9a7a08932a171a9ad657bb24ddd4a6ca85e78a8b01551f589bec572e9687078887874b0f618ae6a7f59606f592c2b1e0e16952dd8025458918071e10e30058c1fffca65625c2ef95d4a3de845c621c08550fa85e58960d306764f5e031b2de59ad91f5e02bf680b08ea9daf4d99645a17846af64a480856f43a3d6af537c3f365e355a2a1a36420bc476a964a4e83126799830616ebe6781a8946a75aa2d1b3c926e926a52250323135b22b4b11dc224db65b45d6656eff67bd577fa1ef4626176e6e64faf141cbd4bb5493dac1734b64585ca35c57802bf9cd1bfd563eca13a559eaf4c095e6cc08030432b6d9c021026391836b4b058077800460821c008a165548d0e4688262264c004e102304184b16082b0e20313040f37dfcfa74ea9d47bfd4755952a0c6f6ec21b87fdffa79ea7c90f71a9cf92e205bb79d8a752a914f08c817901e4eeee2c2230209f73d383c714e10a41a02b0547283608e53afd0c96fd53fd420baa401004938058af62bdea9158af7aad5eab25ae631f7c39f88a5520b42716d0177ec5bf7c40dfe7840a082809080808685b0b6461f60b141ba4b241345a8011428c1c304140d1054c104cc4bcc4a2883d1144961baa882da0e67b2c6c05c78064dc2a711dfbbec4c29a88c11306cc066f6dac8630c93bb045c996ef07080890d3eb83410a5583624b84493c29205f68ed971d958ac7c280d86f67fc7ec765cec30b1e16f69d829aa8636c89b0b27064c2241fa3a56a51a9c2f0e6e69fc9ba0dca54d60c9e9fa1a2095b37ac9f81ad784615eed47d3fab1f9fd4a392545fa84696ca5577e051b5dc10d64f59588eb5299bba80cabf1f2752cf8a2d119e2c2c076152c873e43bf1d8184f1ee4a8d357296ceaa687859d5ee572fb1676fa180ea13d59fb89d1244608bec372788000113932b4a48911929429a7a5a22987ca658cd006a18cac4f7d5a89fb53b799ac4f6af55292c5fa68a93bf8f8ad56558552f9d7d7890b0f2a0b0b4298646366b40f74aafb64a315ba4001844937a316310f8449372a5518de7c03204306073600c78063be7c0142cb0f63bee810c60f5b6c00c40148e687324014f1e50b1553542f547c01b3a50a305b9ea8191dcc1624ec1428abfaafd65a6badb5d65a6badb5ca137046ca468dd5aa06c7cccc2b35e365c39542d1b86ab892c2715557b5d6d56a3503f542bd4559313313ba4e10426cc9626439cba6a5040d122590acb01faa89857d340bfba458d83705eabfd1a25e43b127425b5bf6a74eb18010b4b0df620a8d45a3c68d1ca8d60dcaa274f4b0495ab92cf5200a85b261c3860d1b366cd8b061c3860d1b366cd8b0b152b98eb5afd801c224df02f59282632ab78ff675d600750aeb63abf862857d29f111e513a68ac141bd3c655faa978dd697f0082b6c398d926a6bfd5e4b2553c980d5d67ae36badb556f0860543b8f1095184857d27974d615343a386c6874b542b2cac561182dd62b7d8174e874be77a552b7210b6926c93dd6285b045d8a6ef63358d4786a4b0d6da6ac5f7a562918565d970d12cac0a2949fe658aab7eb1b1d812d5fa90857defa30d17cde2d8c7f91d7fe3c2b1bf438af50171fcf7382e1d365e491bdce162edc8910327470e1e3972849023870839728c9023c7e7c8e1ca91a3a615e3a14ee15bd4903285e632f03f95ea716e5c2c009c70e3c6cb6dbc56aad50c1047ada25a4153a7b021045f49b0191fd22099f14a427dea51610d1abec5163663529d0236c395545d27d0488304f502ed67554b2c179818168dfd2336c6439874135340e82ad5cdc7033eabb66c8038a07ef538a45e354314a79f21f52a9c1550bf7a24d4af5ea71752ea552f1e37388429fbb1542ec6c1b7d6be8af5b52a8d12d5db174bc78d0e11fee686c7df54119e078d1c56846f845a47a84c65be9f5ac686061336063e0d1e8cb288f03c9e15eb521fe77b293a1ee7a5ecf89b19a240e2f1373f83082f1c9ac7791d8ff333f078e1d0bc70bed70a38afe39170743cd2eff89b97bfb25818accc5726ba7c7ff3588250df0b67051d7ff3483afee675258aefafdcbc8ed7f742daf13a5ec7eb8a8edff1b2597210daf89bd7f1d60a2cbcd0789006921410f5ddf870fcf77ddf97c5fae040fd8d077f427810c78bc9c2c45cb9e1aa4d5a5c56e6fba95f74d408c14f7dd28f65aa18368b85811f82cb36d998140b031f458362fa827ad530ae037edd62464c016152fd72c4d62d3e9415358ccbbe4f52b15833686c120e033f44c2c2c0afb971e5d898fd1d23ea05ab4920617daac3ac75cdfc8c6fac7925d9c0429b1aae24980d57526ba431f32a57d209c472d6d7fa52bf58170efc4aab1fe3a188d1353a982c618c37afe393dc06624e9dfdd9f1e0db5abdec7055315c3ac0bf71811f6b0a93ac97117cebc5653e82cf046631d62f388428558c8730c982654e362c1f9a07df36b9ecabf9243bcef05277409a3465adf8f22c2c5ca7068d99d6e7a271815eea9095efd80747fb35ac1a34665a5fea183b26592fb58c0dea69becf7ea171b90db0857259d7b1dfcf8d8e771dae96db586c8910e56558a75aa696b979f0ffe6a686f66b2ceb6f5ce02be9c78065ca3069f95cac2b59ea0e758bb56161d5821f4b40a8e36f3ea97e517d12cec2c0afffc5fab01efc6a85fd71fb3df8c52a161606d62fb58c9622ac0f0bf5594f9da83e09f55285532c0c7c319ab2589f1daccf162b84f5a92f298e14bb22848d3330202a5518dedce4d81fcb848da12c1722c047d17c3fe302ed67ab0567a09e956a7d1f3803f54a6a8d4853ec8f07812a2c362cec072c67d57888fb7e6afe53a96c84618d9b1b1aff362f1f53f3f22f07667d5830ebb31a1dd5ca12feccccab5aa1cb5aff25dd8c200d97cbe562d58460abc6a96583a6090ea779c1582dd6ccaab59ac17a5924d6b6b0ef4f9c857dff4a9aa9f5e4e2d2b22b2958ae1a911cc9127aeb86e7fb55ce0baad70baa2fe6311bc29305a760790f2594f8e2add60b04df9b802038f2c4101002b950366e78f480e58029d809045390e459c0a4e0c211e4f9923c0bd8e954eb176322045fb126c2fa6e4fefa7d70f21f8dfe9671e4b90eaf4c25961b53afd15950a69e55f2cf5ab179620141016063e0f0b8b251126791637df2389b0c99493f5b9eaf8192195160694c343e408482695c5862a56cc8a1096c475aaaf0f1f4b1048f3c25981c6878f44e3c3d7952868fe4af8345e2a241a6ff336af2b364fe3852508a4f9ff1e3c40df8fcddb1935ef35af574e0ec2d5876ff3493e0647f3b606fc9a9a9a9a9a9a1c9a1935ac19ae2b3db685a96ab86036165b224cf2307ae47c0c8730c9c3807d8d13347e86cb092d1f44e39d86e6a5dad607a4f1f69dc6a682de93d40a335cebdd9c7bcfcd3c2fb662eabdcbbddbc4c934d13c954b39e86091ea292eb53cd9d494d7d4138e3aa3aa7c803ffe09018a90b22ad509fc6af51c5674eb75053085eaa92127af4155afb59e4e402ccb7aadf5b34b6a4d555beb87b262abad298b034aada89a725b69c4cfa250b099afa0b5e0960ad66a672a0f6c5df9a982f6c3b526d95aad2a8ac5b1d5a2405b6b606b45d9ba42cd51eb67551b387da9191587107b43f55503545b51b656fbcdaa056756406cab1a51d5538faf484d590bf67c33d6d6588d4f9501ec6125f98cac836e9ba8e007034fb67e7e2240754175eb61187af50a565b715055f6845aedc9daafd61f75a7aa6acb5a6b5142d5046b415b793e91a5435542a5a1b1c312514f564705eb57dd1ad99fa9a05b13e54fab3c7ceeda40f52357d8afa22a98aa34f52bc25a95ecab405fb5167cdb5ac1eaa9d6140fd833024fb53328954dd52aabd6aae043d59505bfa5fab2bb5a3f9dba62d9aa4a557bc48a6045b0217cfe79ada9286b2b0d187ea0d561adada80adaeaaa47659d6c3dd5193f389628c55ad95a53b552b6ae5055b57eb68275490dc10ab13c2ccba6eca97ed5565b653504cbb229b07ea7afd69ffa55585dd556ad754815c1ce7ca9aa02abad55562b0a87b551415b4f60156259f5b322d4efb316b46015626ddd6167eaaaaa405b6b95d510ec4ceaaba00aacb5fe7cbd394145e443e154bfbaaae04d5dad642c187044a80000abb5289bc235bcc1c35a52abadb6d6aae329d7f90b582a191770e0ef821263c2b8e0fac2062d5c1730204a0bbc7024570afc480911e0a3ab854351a20a034071e5272b289144c6e80239d244091176b0e4f2e011a2c4cd8e903be3899f16d0f4f871cd549f16d4317c68651cc0e7c9709116041174685d6c8e1709c839cf73020a5c72c01881f6ad113e070de752838ad34e3ff88337b87feeee3cb3bc67baddac34cfbb94e63445e2ee3b4e02cee5238ce6229e37f229c6ed0ce489bcdee6cd2c335aa17077113c1c01c8fd66d676723bd4bd3682cadd65d84dd1a9887597d4788abb5128c2179d8a77df724653f3e17c1768027968029ba04ef89ae2410d61029a4ea756aa8414d4002ad103e66831f1046a004d6093158e191e373734ad5c005ff844606d544b954405849af986dccc9c3ed0a49aa2c9c6941a563f9f0174d0eaa2e95446d3a9b5c3044dadfdba40d3ea66864d0d39ed9c84d07283a583081bc01d9a6a5c35ae4fc76647756353860d2714544029225fcd4744c544d3e7a46707358382a2e963a1768039ac9eaf822d306726c90985940ba81da8195452cb46cbc6ea668523e5c237860da81d9f171b4e5834919004e67e464208a0ac5583b201eba7f3098146ad1c289b6a2185932a3add985151acef04b6beefb35fea537dab191ee0978a92a2516334639342a5ea970281803b6055e9f8706a588d707aa269061435d8a0401cd5900d601736bc66ef65902103146c2182301ec062773bfa610b92a0148afc64140c1cec9810d6a85230bcf0a4a88c108819c1083ef01441c41123ba3881161dc0c0041ae0c48e58c210bbed834e799003325c94400c072280858618a6bc20e54991922423889003471515c0d20006d030c32c041e191d6146172ca8926b2a830c1ff4600a0c3970d89ca00357640003139080037ce84100b3185c68e14911911621b80204162862890a52a045073880810b2480091d70b82107644e50020c5ca08a076469c22511121b1a3230f8a20b0f70a0025448a1801f78c0e52c862930b4408464882c6643a345083c6080237ec882cb1854204a826488c3e00b2d4200e68a0a502185031400440f59a8a840546488ec07484f8c860de865c0e00b1680f1c0151ca800150e5000103ff4c0431617cca84c4961471423084d98d4cc58018f2d3be820a5051e04f0a0438f298a55191f19768c53102c10544f800bb00ab04e541fb6e7e3a9224be743e17b1c23581e1527b5e3d3516fc01c15c7aa060d1a5f08d6b46684343b5a3a583c666c5629152a6551a8d3e9ab8eca3243c2079a52abd40a9ca1f249adbe105a483514bd5430d48c0a086cd554b0859a81bc8fe6b301b7b8c18826d40c9b5a813934210031a06986855609e6cca8c102c13945012b8a88069a8a3419b2b9c182336093980f9a6260c309616aa82799cd08476ab0e08e4f47575d464e2894a009851ac128cb8d9a201c3e253504a049c7aa85968d960d2b496ee8547aa6074d44f64a090d8830b5fa684e4aa00076a84105ce4085506b7ed4200304ac4e3041165519226658d6e9884d4a08c804c3e0c5861a27d84c9299125053c01352426ca080124a6c58800f38429e2c3464930158d1c4dad98eb881c6474d6581c73724a5c3a786999410d48c6ac6f743a4e40b6126490f9a6652443447aa25384892ccac96b48e4a221468ca01e6f8cca064356850332c24af1812d034a35a22424d87a6d3179e2a6a05d4cc6908ea042341acddca04d512d44c4a07cdd0aa0758b4ca618347093b709ca042810994126a985189a91f343f7e888c522d30b5e324fb7c562dd0843f925aa566c09d131310e8483de17cae130e9e958afc84201972a39ac1b1faa941a888054405037b803d500b683a6106387442e124c2aa07c8a3a6e70bc1862671a77eb64f07fc4e32bc6208e202196d0003c27cf1d280053031002c333069251b2669314c79410a0b518e8c9aa8b04406f4035602090078f00a0e9401860d64908211125e3b74a4b880200b2712b00292146421dc68b15056544145145d9c60620925b818200acc841df10823908eca10238cda0b60e00003b36c03840fa6cdc8909954971fb6fcac50b08502bac884f8e4b0c19a51a578e04c40025370f9a44805a2244864407a62346c66d08007e8410001a0e145100b3480010bc0d2020b3f3e3736401184d9010b468af8ac522807e440c503103044103ac890c4010ee0c3932223434c68d5207c40b06540328f033b0694816dc1c98b9a027b026b8255095a62ac08ac16b60aeb80af0156094caa3dea08aa104e3c6874d81cf646b56153e344c3dad4109c6159a719bb52a96c2a75b260fd6a45b9fb8c0f091ede88e167ed6ed44c753bb8cca489eeea7713c66fb868f3511ede54096f4c772fc1c31ba1f006c8696fcb1f7bd83f2e4d8d9aafcddfe60fe9a537bd38f2aff9bb57bbc971930a6fbe30070f68b4bfa126c6f13ef645883cac089147cd1bae511145118692440581f92928ccd1e43a15d114ef7d753be9d60e7ed66b88a3080e186a6e8dcbff9dd1d738971f836132dd658d63e646185ea278dfd737c494280e6f20e0c696c7ed1b8acfb7e5c7a479ea4fb32dbd5baf60ab1dbc4b37d6e7b5b983eeb1540adc7d04f7930dace0a181bb909e37f269ba234f10220448c88f101f2141840011f243880f213d42788408010202fa01f2010a020404e807900fa01e201e20213f403f3f3f3e3f417e80fcfcf8f1f1d3f3c3f323c407c8e7c7c7c727880f109f1f3e3e7c7a7c787c8404010af213c4274890204082fc08e223484f109e2042800001f901e20324081020407e00f101a407080f10213f807efcfcf0f911e407901f3f7ef8f8d1f383e787101f403e7e7cf8f808e203888f1f3e7cf8e8f1c1e343480f50cf4f8f4f4f901e203d3f7a7cf4f4f4f0f408e101e2f9e1f1e109c20384e7078f0f9e1e1e1e1e261e77b38608742ae6762815771767726660b0197e86999c1919fe735e86ffff77771d77146235aebccca8c611f797853544a8910a69f8c05f36f3b26dc3e7ff0c6de36e66fabf6df8a4611cb96fae88ab4811773f21364548a308f7d7e6ded5feb5f9283e65ba4477b9f6b6fc7b97dfdd6126b8cfc87edf2fb11269a25747630a0c321a3c7ed6eedee568dcf09795b8f6bdf2e2f4bc7be724cd698a8497d33407d97049d6ae10209b2edc5d1477ce66f34468c3c429277632c2dd713cb4610a6d807c89eeb2ed759e46c5e7c7e7f88081f6f886def4316e3f6a3e36cfdf7a977bd946cd199359de74966e3dcbe5d466b1a0f864ba391e1b6a16dd221474441ef10240f44eafce02ee264ce131614aed8d3c151e186cbb5a15ee0e000f4324cf4205770b867d813dad8ffd31c408307ee2410bdc8b18a2c96b828e78cd818bbb09a1fb37c6dd4b16aca07281d15c447b349b5bf72406eb6d5d0ecfb429da76351714511ca93439c556be577b5c9a4b66f979ffd6e5f0deb7ce3420d6e3f344887c0fec89fca3c0802716a08019dc5df490a6cc4e9be9ad0958538321308052d1050f05431f0f0ea043054048f1021c0510f95220e6470e0320b1c3142390ef4b0f37247662182ac3879caf090cb8be38f241b1029d1e9f0e4d434c62b0e2841638f0f87450c01e05b52161c46c7c5164101e110425906819a0f50d392264020633f8a12756c1efc38c0046988016b2e488f1a17478a20730247004891d61d81a5460a949132798180a6d60c304e1882b2860c28921225f5809744181088ea6bc00024370618f700197087041c4083c3231b6862270bcb003195c475d64616d597c088e20c114a626acb04db488b1831c3a788d2ea8c2f64c800b0c1c21cde0022190c2e2e8c11546a44b1491c41428aa184d1ca101202a10b203094cd416b020491216280097d90b90a81d20c009524c0102115c20204485028b20471cc062082a4576a840e400430e40c07004073490a5f2a440fa4289de0a6e7857ea141dac9864906206251c760d42406e89021801064f85b39a90828a8372878e071a9495060827664e02706420658aebc00a2a21fc20a84a084620f916377440d5c38e27b00adc2de085051868072ca2e832e408b80189801e315604c10422ae258927269490c3881712209e2bc10880987862064db81c3ff281061f219081065374ee405f8808810c1e48dd8080bb0835a42084a98c10ab2cee5f144c35e00014b5561963c68869222c2304481c11a30c0888600239a4b84007ce09a38c02ec504516ab6ca4250b1b9441431530509540d8eca8c1176590630420023ab8c1c1cce0a20ca205f4e85100530341023165885f4c09ad38018521a82ccab05180145c70451819b0406105193f00811712a2e09e1e09544146170350912983819a1b4a5290610505867441f8d0e5082da0204301651430d342cb9e410826c8f021015d582089296030850f4890b1bbf801af872e4683058420e385218a70d8e1072a4a88b203192978a08a32684032c07394850c1202509b9dce2b2ae870858c990844208a2e0b28a3698a3d06183b7041992058b0811c94708e5182259eb049a199a5a007e51854a8008ab3c70cb526b24c19834b104a4984a00224786008a43170e0c00a864cd841021050d1640ca52c5888e9620457a43064680ca30700dd8270429007f440648c206000992922c70950c000c818385c72d00204446080883072c6f0136c1c3e40820db20ef41023065b86a041065a52c4131e627c4003a1103e4e82c0c28f0d311c6003111a3d2a1208410b2d3186604016443f1c613aca95018a21030d9078f10080031d3e208618d70055a0f0218a0478200261889184042b4c81f54431e2061b8891c3439811a2d882450c225f88617301167e00635432418b8b20f4a00c9e03c4b840155c3c1113042e9ed09191a303465c01c6dd9d8c2c82908118ecd0e22bb2034917eeee34332a938a6f68c1bd2ef570af555aac3ac3042bcbdb8bc1702eeb9936c518acb6d3b3122b9118478a1bbdb53a103260e508894809d1d04c09d1d14ccae8e48b18f9180cc5a768dbd578a76ec9f50e357de7ee3b1ea276709e37f21bbd39b1d4a988e6db4dcb9dde0def9b6ebd43e2d2c73e6da7231fccb800230461a037e0a2898b0ef8204a15301de01f8091238b28fa0b5906d20c2241247821841b3e3da2b8c2e6a5840754414b40802d6ab45001d529b1c0e80a1a377602b0050a92482f408200c0508478a5ba2d9ac108257c09f88145471520f092022b402e5424d082073600c0fe62a7e606d0185f17055889043052fde6c93783147e10460a9c20e20628d00d4a0c248d501dc4814e6e84010728c0a8010039e82067c09d17440867d540035e7b74dc45c8d2e41fcd11f7a2774f8571772c6c51137777797822da379fb659cf2c77723b14a799e4f92246feee1397f865fa6a7797e2219801af99e74e73ed86205217772624dc79b6b87b16f7cde49e4e39ddd12920cddd5f2188e4feaab9eb702772076dc0ef73bfe15ec3dd69b887ee5ee3ee3380883d3a1aefde79dedc51ce4829b6d9764155f78287d60b5c6612973c62109147072888aeedd87a787882f4dc5acd878ff2a7c7e6a3934d51e37d5352eca55b9b379ceee45b7ac99d1497a7b9abede05cc6edb83b0d1220db8e92cb69127767b90a674bdfe6a9d34fdb9786c3c3eac5dd69786881dccfbb9473383d95a076c0ddab87f50a77cfe161f581bbdbf0b0bec0bd08132e5133c51a35752a9639b743e2a5d86b13f6283e9fc8e3da97e72ee3f4c7dcdd877b45dd5d8787358bbbeb5424d19e9ec1bbf32e3d2d4d91d01ebd8fe6a7c560ba9bf2d09d4cb6cf9dc650685fe2f3bca4bbbfbbadb9fb8d87f5752aee9c6de5ee2a4f739ae247ef97184d771ba71bc5397cea72e66ff3a6fb6fa8b9f7cda5d9f6b7005e8f70f71d1ebacedd693cf424dcfd86873e80129fe6b5bdc6bb57fbb7e547b3b9eff7d29cc3399d8a1687bbf3f0d061ee6ee3a10fb93b2f9fb6142fe5ef6d93bcba4bea5424f1d28c77d31d32d53b643ecfbbd34b6f5a334f774fb93bcadd4fad0f74773be304172cffd2d434fa37f3041996ca634110234646ad2e2d5ac02c5ac0570eaaefe7ca151aec7f4f437df0af448194fa99bf9245cb079d90507f7a1a56ffba92c3079d5e576858bd4e8f7ad1807ad54bcb077d9f7ad1807a9d5edffbd380aa282b3686b5f5fb98b0bc0acbfee95465b1a106fb16ac0f825f8d00cbb1b894ea2ba1aa7ce30a0441d0053ef8bd4af5f54bfa6a7df0fbae24d547815796ecabf6f7aa577daa2b29d56bd32ca949a9ff9450ab97120afc93eac1d7a7b2af847a3968df5fa99ebcb0bcd624d5afdeb258be7a3fbdd20d3eeab5648f7ca0abcacda8847a1f675e55ae54b91951affd2da9a3eaabaf2a57c6d4eb747af015657a496d962f80192de599d1d75d9c6aa169b3c418d51aa7b79cea1ecde8a805e601e0c2fd16002f017821003c8ed3dd46f1d289750ee3764e89f1466f7a77b7df19cdc191bb2374a7cd1ccaf80eefaabc367360c25c4ab17e8ce2de69a637cdba1cde3908e5a072176fa8d94bb70e4ebf57dbd996ceac715aa277df4b6eb344efd5ed705881fbed1ae1dbd5e1dbd5612be6914c86840393a77829cf7080e2e9124e75d88aa96fd7c8881322453adde51aee666274d62b2f4eb7bea71123d88a79f439d88a79a4bb7809111f3fa2e48d127d0ed04e77310e5b318feebe43e5c54bca8b8b96cc7af7c4e952d6cd986ece0812918d8f304e9182b2a8442742526e00e3862f4e65ca3b11927283016e38dda94cf99cd27c18a688657a79282f67273219122fa3378c58df50f3869a387d979ce715b2210c9a712e6b1bb8b868834e44b10d47dcdd867cf26ad8c2c534bf8fc7b51a9adcc71a7886a4b84c73d318f78c8e4d45b8d8bbbc1497e82ee7735113cec58fe134e760df43ef36a189c779d8f6396f2c5eb8d77e9b803e162ddf3bd12746a7c626969bf3bc42348c1169586267f466eaadb199e6194e5fdc0c657a66e599711aa7b83d9b010bc7337099cdc0e4e63e77b999be3823e3799772aad3dd341b218273265279e65c4e6d3c8ce6ad67338c535b182fe215da11b1bcf262897917b765f0c25d8c5d191a20034de7f3f72ee70c21795e3e71edf7ae26c34dde64ac7c41cd9c152dd70acd734ebf57fb8dada850b327a6d6d3a107a4ebeddd2dbd2f966826b1662a23c64abc615f3271a9d570f939341a13ce5dc438b531bd184bb3b97471b8a6cddca7d9c60b93e27dcfad61af6fc8f3c263ea9de7ce4674de9d9168bc25ee222d89158c531bcf71f9e735378a97b07017699ae93ac1b98b2e1581bb99b81a0b4b46ee22cd4879e62548446c4fd06ba69705da9767c62d85dabc2dfdb81b93536c65d7c5ef6b349bdb2ccd5d15f3fc5d93bb8877282f9f3b20b1dce4c5a5894d9b59e6dc4ea583535dd02e81bbfb799a4a3b4818a7b6190fa3431bc5b911a766698408895990c990743ba59c9bec23fb468f71f7999ec2f7c5e56ca69b765a9bb3d9d6bba4f26aa3da6c63ad612e52a1a54038ef4c87b1d20c4a490445a3595f9c2a91b94969a2d84a4e6d254e6dfa76cf2437dd4c48f4ed9eb6d406f737a2653224224c39485fa61ce4d3b33bcf4b9271c76d9357058bf3bc332eee5598964c5cceaa18dd2a3c3872df5a15550f2b41d16d31445416e9b4b88b39f3b509dbfcac3f072f99e76facbf8799e87062d6017177715f93cc5a5fddc34a9f8b740f3391c9906c39a73a5ddec2dd7b5869b6b1ce5c3216f3a33dc836948bf330fada447369163212e3e1f1f909627ba2777a8d3e67c4291ae6c2b8e78ac8e95cccd9b897b8779e62dccf254e28a78b354ca23897cba4d217b134cb9df334794a4c4a40ca9b536a255d2089c95d8cf54e9d2e63f34caf8eedc4fa3c4d5e1290ce49eea2364d2c4af49a5bdc455e46cd9a8b38b74d5eec73609a87511c0f5cdc24b6d9f063a5bb4d1c4e8abb8bb8d0dd71edf6455f1e5279e6db16b1bcf9bc49b9c15c44b295b185b1f56ea8a9cdcd83bdadf6a8f94844d29cc4b6e3ee1ac7c0ddfdbc3b6fdc347a887f44a36f52b2a0dc1213cf7c4f8c3e11f1be8f7729059339d5e697efee9ecb413c24bdd82922491c5923e32ee2c8f4625dc3c25d4c917858dbda3cb3c6e9d55ba748b59a4a3c73be53f8ad8295ee3efa9dcbab7466a3c7b87d63b42ddcc5cd84d18ca2e6f3f0d18bbdf373ce13ebcb7bcca3797117695a441a4d9b34203f75656658ccb4cc8c66b01c7a33cb18ac7017b5b943cd8d667233211961c2a816e76134065a0c412e0f2986eaee62799667a642021753ccc3686f671ccfdd651e52e1c1451a4e334943cd1b8db6ef631c121523f1bca899cb2815176928c6f16829bea1f9fc2961dc457df7142e8f5e52e74c4e619a72a3b3a3a4ef7e9ce6dca7f9319a3fef110632230c5c60c031f1108610ca0f0f5f08e3be6ff9c27e01002e608c7eefe6943cefe682961b6adaf2bb407301a674ee82de96a578f15d44257da5e8c4129f2726d13b4a81e9a0196d81cca7f7dc91e6d8c2177711dddd6ea849b6c0452c51fcb9765f9b630b52ce9c8dcaf2b6f0a9cec4e914a3bc272c8c71e76116b4b86f33bde74e6b1c79f1eb1b6aea74f7ccbc9dc91f43168cfcbe2d4729b3519ceb5ddd794933ca14ee62695e5d0c35bf77a2f961463c8ca273d4cce5ccd7e6cd6994253c8c8e5152a430ee48e65dbaff19c5a9908748524a143fb6dd4f3312eedda3303b3cdae2aeb1be484740eeeeb5abd15d69e625285becd23493af4db8a70e0a93fbc69b34d3adff7e9a6d38857d120fa1c0fcccd9088abb3bc9845f9bffbfc21747c715b6b8bbd83b6fa8b9024ecce16dfe631c52b80213a3322e92e62d9ff7f5fd231e1a79116fa8896ed4cc3a2326f7dfb8f73ba346372e626d428951f36e349337d454d237f78f71484fcc53a7bb45687d75bdd4d431e12734bdb5c9847fe7a227404f542ef6769117628971e47d7cdea22a448c66a534172d51d4049f576f268c3ece6512f6452138b9c2c5126332a73abdc36d949751a58f6ddc836533c5bad7263eefd1e3340bbdde658cde8ddead51a2c738a43387b7a9996eeec4282fcf7818358244c4f6a44c3389a1a018c7db79c9d4e13493f8dc9518471e95379f4a69e69544504a9348495f24257d7792de36cf8b9ab630f89c6dac9b78d182ee9ad0b6b9d3ab6bc2e3bd2661ce3a2663d21dc9040b17a9d0987061c2e4cec4a897e6ad4d1c13d86ea64299990a6154e82252a16db3c42939c3fae2c874a753a1490529e45581c7ddcfab672ae8725a12b10097bb33eb72aa9b299dbbdaac774f0c05939844ef52798dccced3aca5d84897dbd566e7e561b444f10c35cb8bcfad53d356e6193eaf9eed5d3e4fac6768366f38c569162acfac671aa7999c9d179766695e5d89e21cdebaa7b719a4a4af139c66a15dcd4c379ac919de77c94c75467438672261dcbb3a4ca2f7bc419834539ddeedbb8f84d06c9eb797e67d4bf4e690ce9b4b339a6d28d1904c86a4c40b39864a9adcdd95d07497372a0172acd35ddceba0f9d1274a6edcfd96ba1d0d979994d1929071777d7949c2f8a363121dd617a7242e79f93c833c4c5263888748b6707731cd4f4bb3d0795ed24442de7433e1cffb9320e9e2ee222e33b9cd5c0d89d12ddde9fe869ab8f6b99c7e4eb1151d344412a278c806435d441e464bf4a63b1263dec6b527b14ee72e9a8287433417f13675d6e532f93a68464b8c4b933477474345dcff739111bc3b2f0b39bccd1e6662a48799f4b0122e33a97b58a9b6d3425bb84885a6f1edea66ba8b71b38db5d0142e52a1096d61326d38d50919b90b7d50190fc1c3a030e7692a057599ed2026910a4dcff62ee3b6c99b6dac8396cc94d29bc369186483cb9bde74bb5989ad9869c6eddd9df54e9deee2f45d229321e132934690747923d9285ec2585f9c11d617a7c3249a51d2b4d976faee3b74f72d37bacb42360c45a7374582493417c964489777d312b74dde6c9b491bbd3c23b1cff1f103e72e8a73179de17357e65c9a5173869ab96c4389b20d3567256a2acd6644454a69e66d8ca639e9e666b8cc42b8d6bb4e54c0b7f46e23b844b1de283675268953bc64deb6ceb9d9be33cd7473256a32c1bade9e21c1676d9787cef3921b2b91e6c6564c3d9b31dd1c109f1dd3cde9349b29de285112739f3b273992c9c82edd1a25224262167ef858229223a5c8644096508cf7dd1aa72f6eb6519c5eddd0d6f866967ba7c3e9bb64e79cced025292e2fc619414284c42c68d2d4a8996e266c04684784c42c50e1f15132e112251aa9f8f8f143807630b83fc0dd691e16998197a7b9e3e9117de8641b6aeae0f392bac4e8cdd4fae2f6357a22936d8df73d65b21cc9847399dcbb9cde50b37775e7dd6df2c6604472a4148db16deb4cfa04f1b104a72f0ea7ef921c29a53467b319d3cd9de7258176335d8e9492e634c5255169ce88f8400428a3b41956ba9bf637d454babdf20e1143888743ba0c8132c4cf5d8937be95bbdc2e6732dddc5fdcfe14ef8bd3db6bf36557c87462ef3c7736ad314996fae2803c948520444c0c4d730df6dbd448223a0ab109097d5fa026770732729c3651d3f6681e8142f8c1c2c5f3bce4fdd1e2e585f263a4bbe97e14e7cc7b92b97ce4ee4a3cf4e9810f17f7dd49a2a6cee4eb6de6306e9b6836517314b7f9254e339a713cbdd317e7837347efdb32121f180fa39feef2e7bd6f7a3373416810448820475ca4c53ea724fa1c180da7bb7dd19ba9fffe0d35972e8e4477b65d3984769e5788f6f7f346cd477738725f9d36cb256d069901648ae7e5d79777bfbcb811c8520804e62ee69e34f7cde5bdbbe87ddc6d9753dcc6ddcc12e37eece047123f96b88b68d6edee97e8ade5fd399f93e27dcfd738974f3cf441e64b14fba880bb47f1d087cd47e8e2dee52fd3fc39785fd23c75bbf2afb9cdcf49f112466fa88923f7cd5d5cfbdecdbd7857e809e3ee2c78d863a5c7049e13f09cc0dd8f3ce4c912f2f0b858e2126314d73ee75b2da7bad7268a9752bcd18ba67829dffebc3bf43a79b1c43a5d4ecfd3e4f5ce9aa975d63dc66dbdcb29bea1b7fc12bd1fcbfbd36ca6baf2e2f486de47cd87a1bbd7e66b1387249638c5af51f3e6300ee9c5b277ea5d369770aafbf4b589dbdfab3dbedd346f8d9a5f62f29e3a5ce225139338fd5e8a7755bec49bc43a26243b5bc46839524a89e223bdc3db966137c5bcd71827d9a140b8a373519bf8865e1ca977af7579eb5be2adf1ae0a4e710b3b462e6e14eb7107266e21c69c0849d138d531e16d8a3711c3442e252671fae3a14873b1c43bc54b252ecda59cea9e4613558e7369ded7dc266ae24813c7fbd2bcba5d159c22e98411d1fc5a89cce77e7d71a10e530fbd9f66dbcdc4295ecada7c74a3bb4c92f751bc64de1ee390748c5cc41b9fba5c2651d38990943f2f999be8c0dcc52739633ce78be3944433a9836634a70871a3586ba41c2347818c1299cf3f4f73ebc7e92d312649f492e86e89872870114bbc71aa4bf146cdc7b5cffbf1592bb1de3ded310e0985008428f0b8bbb8cdcfdb96bf77799fd1bfafcdc7a9de5d12d7be779e37c528be7d0e3e6b17475e9ca2bbd79757e2d4d4b87c8c430a4ff062f7b99cdafeefeb8bfb6d3e265153e33c3cc18b8b29dea769d368361f8a87271879891fc5b8f678e9e25e7f0fbdb9cf19c9dd9d78182be3369cde93c4e5e79d661c895e1d9c16f130e6c5ddc5bb6ff937d44c33f918b795c87c8e616c8abb889a17476e9d7584c2d80d16ee35f2eaf4dbf22b91f7fcfb5a77712936124b14eb47cd8fdd5073ef722a62d1843dde696de25cd6af2fef718a7148214c27a239c5ba9c71dae47defe2366e9fe7853dcacb8f7148210cc8ddc512a3662ec51b978f6bbf334afe6b131ee390dc7d050f4d4881c6d8f61bc529b6d9ccf271dbe4bd7ebc730ea7bfd3acd3edf4ef8c7e2e3fe751a21297468cc460449872d036736693991123e7798570cedce80e8a4c66e466dad02cf4fa3e12112239520abe5dad71ce8891b1858dbb99983763bab9ad719923a530e5a05e7a4dd2b46114d77cfcd8e5f0366718dbc212b6ec73776db82c4157a2589770a4849b9d661d3ef126618ca358a7779a842f371e92c045a442c37b4762264c028e0420f71cc944822aec1146a442d31b6b7d6729b669f4a67b869752ac713a2bf13673e619f608c2dd5da4425b9ae19a9992bab6d3e86e86c934db64b28dcff31ad160788c8f528cf232908f9edd467739778d68a6ebe47362403e7a769f9362d3666e1497444230222466a1c425bacb413ac5b99d4633996225329f67ce35bc646ef2ceb499e2120ad668d6699cee6b2406b4fb1ca01dac6733774651f366a639c5b5dcae4889cc67d0de659c33b791262546b3a953cc0291149b29de55990538bd24fae477644cbf2d8b5468b627286ec2749dd060f85ead77eaf3e6a4a60e978f7739df7e7c71a358e74c7cbbb9e7c574d08cc2b43904c5280a52998bf0ed1ae522283b17d9d8d009d9d85862a24a506c6a78d143efdf7447e2749b35b8b87befd2683635ce5e794f9cc669129b1a4464a91cd4086d5237363450e0b848a7226ea631b6c96439dc86860a363486d0987177b1f7328c6de92ee8c597a5394d314d264b33ad8799e84ca4750b78ebdb44df7d87d29c4429e720bd82ee61269b091745b109a3b009a7b8ff545cbd29b1a921e3ee3b9f486c6aacd066ce6535575c2967dc8ccc3adb763ef5c569897755dc1dc86d66c02e2e65e0ee363469ba2b79f9b4bdac49b6a136343436ad226c5a5adc6bb06919ed0059da21536d737755c525aef2438e8a9e1021091545516126657492d128473a26ddb8f1518af191de9dba747704a5b70bbaa16679e6257833218132945e7ce65368d4e5d22528d63d11e2e1225d2e5d8234e4634926cb91526c667e6c6672dc73249383516c5654d8ac6a50e6584e0f2bc16c5655dc7f5fa567c2688e64b25179e17e4b2f4e284d91283d4182348018dcdd749b9416152ca7d8ca797edeafb7597b9b89d33a3a15331944f4117fe8e890daccedec90daccc9641a5504ea8a0dea8aabc70565859e6a51b58295550203b815f72a5cd89c409b938f1791b8cc68f5012a42648857991020f7fa537dbac4604af9afc4669fa361255cf91c580f2bcd7ab5d9ac975ed4ac891a3541191429f2e98eb4f9c4f80d3587dcd0b7f9906cbe232308e3fe3270872136193d7d36b68bbb8d8d8dc17d6774a3f868684c22de7dcbc7f888f6b6acc91f87306e672129bc12bd3c2327516ae526af2dcac65a7c3193a8a9d3f492af4dbc74f597e82d6f3e6ffab913a719a77517d5b56b9eb5bbaf7813f1e7e420d56a57f7f8bc2f6eac71db7c8d5e9c3b6f8a7318a733737fc95d99532a341a0c5fa2f76a333f3abe48c356cc14be8517b199ee1225d1bcb1a642a3c1304333ede293dc28de55c11acde4e74b9ae82e97cbe4eb4cf230bacff334a920197a222493e91e569acdce13f36432994ca761a0028399f2ced3cc799d7cdbbdce59bf1e5fccd11b356fba49acd338e56134ca8ba7d661ac4485a694f312bdd328d66816a2c1f058ebd4f2d6ba4c33ef51221d26c97b4b14931a97dfe4cb9d8bc69d8b6e6650ce48bda0121be927b31d48e1eeae149e369b6d7cab07a841dc1d04e2fe75e5eaa578093f6abe0ec294e8ce76840e02a0039abfac4477369c12d1418f0e68fc659b09e7fce7bc6cd6dbd564322d74434d1da5c4649a9320a1d84a4e9394514aac54a226a93114a2124a5089376a3a29314edff3a88512ef6ba6fbea3094281b1f6d9d9b6cbd1b2a312f972812a21ca4e0e27977a89933b7c68fe2ef9dbf73d18b2e8c66a59b4b7779e9dafebeedc43a0fbd0ca3e6f9b9bc770e7a31636d7ebafb8d6fbd9bd38f77b947f4e2511efabfafdb3d8a7559a6a4cd34dde59efe5ca67809f3f2921769259ee17447628d9a34fce82573728aad9827a427fc2ef7de46f4ae275090bcf045e312a487820469440245c923097aa4a3284e5e44b12e7f3070fd952b5fe22f62e4af5cf9184c9b22c6a9ade8c512cda91de91d8df6f735d6bb1c5a2fbdb834f50dcde77d1aed758eb6d9322e6fba73414fa3edb20b33fcdf7d873ea74c8d3ee78848cec41de9ae137cbb45feff65f834dbfe334aa3d1903c00fed4e52e13be5bef5d7e5bd6db44773308c10c6e104d30e165a539cbd1369c7e4ef10b779f7868a83cf3928b876ea859bb258a754c485022dd44a7a24e8e64dac9914c3b37d4bcddad65b21cc934c6c8fd65d98612e95404b22493c964ee7e3d94810d0e64e9f37e7ca6d99662dd794bdc2b2f86a2cb21349b37a1d2bc4639bc89d0dd0d07e5cc7b0a1d21c1e57502e5ccd9a8c42551898d4a9c96184a6904833130f8226acd749d885f128925ded908bf58e22fd3cbc3e9f9e979434dadefc6375bcee563de2ba599174377b0174dbdcd1c7c5ebd7159dbe9f3c4a436736fcbaf718fbc377cc6f4cd29e9bb61af5353c784e4bc3c8c2ec945e769ee2322349b4279282395689471e7a2dd93f2cc47689432c54b4f72510b42a599596042d23bd38b04ef9b974ca328ee164a89cfd364c2c345252eb3d0511730e8c2025d289de71532020a7274246e25d4c4385e46955eeced14eb72aafb5712a0205abf1e28c8d1117a7bd104252dd48644ac949750536f9d9abb54f75be3f2ccb0d761ac947b27df448b5aa64bf8379f8673f988f6224e8753dcb6a13ba36f333f067baccf9be4459a8f1f3bda6bf369b4dfb7dca5484fc3b98b9258a7692f6e9dcf8c7e264ddc7eda2e6d262d96437baceb6d8d53bccd73639b2d6b13bd3852e3257c9a27a9b37137f37c4d9a5b9ba4a91b89d8325efafbbdda6b9cea700afbbf4e9e66048988ce9517a7585f52ef926c5967eff2e7db7ea61cf4629a7338a7839a7f59d8f7b4a1e6636df2ae0c09d293a2175f4c71ce8b4b5ea4d18e3eb699709983bd7e51639cbe387d9de4dcf2febdd39d37869afac521a29dad445113b7735189cff30a0da199cc484419499718179158778473b788c4ba2322d40c4270e35e7f541fb8abbfa80436628280031b884181982cdc3de5a1180e88a9428c13629a10c384ebf2c74880fdcec748f8cf21e165bafc39243cec73f4538101979914838a418ac1f6fd9e2ea71bc5b39b6ed49461a414a7d8ca0ccd280996acc2117881cf5a0fcda1c5981138616ad4fcaf091100b9fb967927a1e6ebdee59d57e76e7a753f3f7ef498baa7455003112c89c09653fc029a8bcef386a1e1facc59e7a723ee1f8f1b29b14e67a65bcb72faf288b8d799ba3aaf7b55b9bb2ae5eeee4edc95782d62a5c7c45bda6d9d56d16534a79464e26e2fb820a50516a2201d4159c1a8c84913262a2c519204c99050d0b823eae494514619376cd4a0615346b5652144deba5e4a2e6561e43a1567d84cb799ee9d0f9071473fcd36327771baf56333dd2fc3a2e903577ca0cbcbf0ce79190cefa30f0cf181268fc1ca2099ec869a6789de251ae3246ee4fee4035e12e1349355ae70011829eeeeeaa548454b7c9664b2d9d6a63e6be60ddd2529b112999b40c1682e3ada3943e15d28b3141b798009b12cef97f83c470f5cf10026d1ab93620fecf040e8eeff211640c06205eefe46fe6548901a80c502b0d8f2b2870016a5bb77712dd1811df866c23a37dd5dd79f17ebbf5076236ada22f0329d8a3b3b4a24c6913299de4cb80325b8fbcb4aaca3609c904ec59cbe3cf2da7476345e32b79933539d6e3321b9e9ee1a651b4a74748591e6b9e9ee5ef1ba87993c07c4b87b89f58e68367b590e7cc0831ef0200c1dec400739d0410ec8801184155270e0eee00dc20d9471f72d7c0355d036104446f7e76c409734da9727366ddacc6d40c90680dc3fe765be4242035cb84ec51b6aa65bf7cede3d75da1cfaebee3670f71ab83b0d66e0ee22b89f44d8e130ee6f7a75a71334ee73b609a8bebc4faf4e676000ae5331c54ae44d9196e8d94e269b91e8e895852f62b8eb94e635c2380992a8f3d6995b97336de6a9cb5dcca3d14a5d6e57fb9c7cf462d6e77dad778f794fa3d17e480c851ff2273c0a8fb16d06fb22af33f76ce373484c676e18795e7256e47fe333cd497e67e773b6dea5341aed459db9bf57fb9c98cedc9f51d85b31b54eef4cef69e6d05daab3d14bd2ce9b4bf5b923cf4ba25967eebf77d768a5369fd6db26ed5353e7fcd2993b35975e8ff74e97cb24edbc4f68d8f6e37f2c0bdce88c717795bb0cbcc4557e4889378a73b5e6eea887554c71f721f2e8e894b88a17959e5461c35d295740087771f34e8cbe4c4911511194b1850a34b97b055ce348bc7bdb4caf6e279749b1b6c36df3b6b323d3babd71bfefa7ba1f52e214df6e66ba7778c9d428b6e1f26f48c5170c80c2fd653aba1cc9b4a377a8e0b9fe5196680ed22115b25c49a47b58e9ba7b0c60408114b83bfe9e57f2daddbfc8359408a75908dbee918f1f51363e2acd21ddc34cf4d6b74979f1127cb435be11ed7c1ef17091e6e5a292284a690e193541893414a1f284a2cb9152f4d0d67989eea5782993da1cc24df4798e1a9bdb093e75668a9491caad4d243952ca4671ee868fd02825def786d3db6dd4293eef5169a6295e7aa2cb0d9550304ed24bf19279e2a1f14897e65089e620a11b6a6ef42ee11343d9bb27ba5cba64ebdb4428c5465a974b97949844771945c79dcf239cbe4b8c6c39a74678df7d8451a2a3149ba70ee3249b091795172f21ab38a9921bdafa36c165163212cae5ad77422de872e992bbef109acd17089598878ba4b881bb3bed310e490a0bb8bb98dba1f86934296e17b7f4f7b510d2795e21a6eb44fc346ff226b9a1a68f1fe56f13dd7d8a758ff50d127dfce861255a2cf6399f0383d9320a0f8466c5d47fdef29627c6a5d9763fcd36d296f5ad6914ebf2dbf2ef235e6e9233520b4cd749941b9ad31672d1ce4525bacb49762e42898e525ca64f6c4ff6ee490fbd4e72463ae2e124392315e5a2128f4e64b7b6d33b3ddd59e69ccb0b173a1553ac3b316e96e299363546f1cd052d70b1c4555e544ab34eb2629ed74cb1f6d2e42f2b7a72a482175bd8801c889ff76754c76bd8801a0694e12fb3e112e3f2267a91878bbe892d4990c1e9bb442623cd14a3f8a6d35e74e1e016078d70f7183c64008ef7f4f4883c628f0e9a75d20c9729c6fabc78979ee74e278a243ecd3820e18231a280edecd4767ac7f6b8fbcc4328a6d8f7662edd8a8b78183d1d31033a2527a78891221f632a4dd8b6e1263228e9936dc34dfeffd129323c5369cef0adc43325f2a63b8712b242a9fcf40330458cd8f0e98122445078149fb35979e66f0520172012291e5b975fa40899eada8d028eb83b2e332a43f1cbb820d1f33ab613eb2f52e4457cb33eb83b0c1e76c982ba804424341a8d424fe4994a53e7440ebc976e14efe86e6e07e374d776f499c91d1f3e42807c7a786c3f808290d7a7a7f603fbf8f8d87a4a5b8de727080f9010d2a7e7470816f2b3436ebd4b77c88de2a58de2a5d946f1526d37d3e54826ad67ba34330b32d96c363323674664b219ceddd49662a59c06f5b6799a3b8ade9dbbdc4e28474ac13bbc5b419723a5688d8f4e7c24a4b30d25d2e5ee36f23944d29ce43cafd0798564b219990ce766999c695cce505c96266e66eaee4ca76532db89b54c668434cfdace48a71b33924cd6e3f5fd9bee462237dddd2ab912352f4e28b743673323387d971880a7446f5a22414644628bbe48487117ff1f1d91f84f8ff0c275ba23b6e86ea83986475439e24574977e3679dabca5f7fc5a3e6b7f9aba5c268df0c25d2cb5998b8ce86284d1d6195700327a9b35139753dd17808b8be82efd9d59803417dd7d877028519402dcb897e9e5e976b312c57a96cb22bcf04d5e1417e180d9c65a29bdb914efcb9be972292ea8c43a0a6e9726af446f6d5662bd33525e8c1bb3922832d97967f9362b49234bca8b71b39b5e1d115f10d1838bb48ce3d1be57cbc1d8f6349db969b6fc39348c6d33da8f18db66e7d5fa9e425adf93c4d836c3d836b3cdb0be359db98d609c8408db986e6ec6c36889e6da105ee453cf86f0b2b11e42cb1052dcbd2cef10b38d3501cab8e7daac4712000bf79c234011face0870fa46cd195ebaf89ccd4a34d7ccd29c11e02627bd105eb8eb2984d8e2eea210383407097144f370511065eef76a414420089a8b1bebdecd3d79b3d441047103c41820a670206840c0dc1d2fe5dc0f5eb8bb889a3c1e2efaa10b6ef20393c9c20f4b5cdc42469bf018b787b67cd9b2c5dd7d0bcedd7d0bd096d045bcaf0f5ffcbcbb2af8b559fa5084a7183d774b28d1a33e1cd9f9d4e799c9abc3279a6bdf236f6996282f9f423e843d6ce1ee3d74f1598e64ea81a987a1194e5f5c0f2edef4ea6aa636720265c9a3230f6178e0e29edb699cf280a46dfb482623f1924c566e3dd3691e7e87277608c2dd757ad3c1a8697b9c66272fe25d95abcbdfdb1b9764c638a41d8434b9cb3d2f2f29b1decd76b8d1818c0e4be860a4c33700070cc0077717f5c58f6b680f1dbf443389d3bd82138d5e9cfb137a273eb33ee137d18bbd13e39d8b448c6b610051dc457cbbba01c06e5700387d714ff309e263e6032bddb346130016dec56f02d022801eae77b0a7a5d98816a9fda8d4aface5a11c069942ce08c00820000000731200203024128d874432b164d77410f20114000580be627c529c886194e314328c186300000000000000000110006407e2caaf031ba6634736565d9eaf727125db8d6258742d94fbe8064855b1de6f1eac49e287ebc0ca6c829b5b6a97b61b3f1ecacef8b17a64852dfaabaa48c366a4c2fe21d3fadaa2685f737317afe8a1cbe88a9b0f314ea6859c8c17ba8e30cfa2805557720680a991d7fd1c6a13b182f0bb669f5c05860f100d58e95a647cde5f8abe79e65caf443ae83bdfd90aafd8b1b5a3aa47b4ffb34b143468c197626f303e2e5abd3f1b291becce09bc42292109c488bfc5837f366673c3b430c71d2ebb960463819a945409d89987475ca92f306ec17801f328ac89b57f1faf8ee43ea75f59af68fa570515fa190003bf31ee1b62a73cb782dea5c0e3efab7349e692979f1fb3b9ff2d580c19b2c4439614772c4bd95cd6b62714ef657493dcc60ccd7e5f6d9f043f5f4d072624df28798e0c17ac95e146ef98221b551ad98815dc33b8b8ef1d5d292a65df9298a2c8a2a89b7932f344ae3e762aa6fb86f7a1bf8b32029f8ef768305c5e4618c89e70f70785b28ea32b873686a12907a49e54fd1c3a6ab284471e2606a4c530b190c87858baa485ce404af3eadc618e22595c14b1fdd1edd1af04a42f60d93f640b83a5f0a9b923c9e0a631be86639df42d2f201dede038624f3706fb4b36cb271ecf40d46fa74520ac4d7e832f21a78493d91395bf7df274139f4d0af3bb215a27a6148d6544b64d6c519ec5e10d8701dec82b7ad8c2fc0c3720c95a03d3d3a1de82a4d61d9c179ca6ce324ba6dd25d5aec54182cd071d2b807e174ef43e0c5261f56f384efeb3719f9cc1a700182a3ac6032dc2ceba4107c470a7af23621c1eef0625a4fe57643df9dd4ea01932edf83738ea80eb10f1f557c42ff0731c02e6ea01c353bdab132020ac074eaa41fef1cbe4e9dbc72fd3a5979cd94883f4a5ea7255746ae3db5d73ad9a4b699f82e7ab8c40f53b06a54f66c617cfcb501d04eaba3fbbe9a79f4aad5ddd83ad98c5f6a4b68272b7dcdc52e1a9ef6c82d08accd673694c6112b7b5a71f97dcdbb2fe9808a7f6a008ee552eb37f09670c4af6d98d3ce3bcbb0d94b13552dadc715e7e3a93f10c505b8623ae51e82a3f0ba6dd68128e5571d6f9dd6003f9e85cdec46724a31f431c832ae32604d05f83362ec6499aa6403b29bd933ad5005c9eb48688fa3048d338632187a2b91ccd57d271e8a00aa3ed5026c810917062913d99eb4e9fb92477949ea10f520b95071f7ec095a3254deeee3477ea918bdbe020a8236e974ba44b1ef95a1b198d5992a0d7b210639afd09dcfdfcdcc206c6febd22bd7e7d189ecd498e69a40dff71e4d3fa480cc479b52685634e9379ace687ed5385ae738260afbf15dae8e0833120e8636639cb7320820cb75d158e151b8ccbefb284162cbca3a8511c6ddc3f1a77ad087c1e06e6ca3f9168125bb5fd5f34143e3470023ff2a65ffa76f317dd5b8d25038c7023e7cb3da98fb42738e8d16306aa89c40ad71281f1fee8231df702c9e9b81e01a00d736a32088e91407c9520bdef5dbed5965268f5e706dea53c22242b1f88ea19e5a17ef3ba1e35abe9d0d7fd276711595decb6c535c691baea15f0a0542b33309721fad985f90c9607811e7c701446e109ef0228869f80b64d90fb7d092b739ac3ed5921f05d32ee9a94db5a0d34e954785ab7f8dcf06e497b90e6ffd3a4fed53e584d2e39be1397397e45224ea4ee1f3fb97110c791852e688140feaeb759cd9e6e4c9518a6cc313b848c2ea65f68f6a289fe88217ba04a430ed8d1dabc9ac16dc13b44d42bc4eb0187f6d7adf4fd943fb5b04f93d99b7dfee3d68c2a5101bd4e55507fef2f6f767af335bef360e51945caad42a1e11b87eb0b60736fd92c164a09c9f1f7b6935e5c9587bf494b39323e482c122708b02576bd4b27931875bcff50c0a832c1d6a97bdd7b7bf9e8894a82141c2b89dd4de7ef0a9597b2578ede724bd0cc38fc5c44a5e928b9a657d09b6a006c31da424a70959110cf93bc18eb0e76a020980300d8132d6bae1d38bccf8b24cc3909d988804dfeabddddbc04c0e965e33e3b97f52584a04d8b157f0b17612da09d8562a04d67c477b30972c4e38fb99734e7bad051ed1f9b19ab67c3f54ca8771d4925c3c84bb31d602b45c3a2f856227fc4ef766392f4ae2257ee582982114ded8ebb835f16818429f95c0ee25605285a1f66149a1f5a86e8f48ba1245b867eb87c2740d2238cc0489204e594349fd7a23f8500947f39591590087724453ba763bab0159f9dee7602183f3bdcaf0229a6e54099300babba0fb70ccb65c30da9b046296b8f99002c02439307acb842368e79e931a3c0b6f56b88818ae7151fe6383aaeb4a697d281119f4490e05be701d8191e9219412b46349df28cfc8a529dcb14dc9ec85d0333e46533b98c8d280e982bf0ddbcd5c6d94a20983ea58aeccb56bdd6dec1d259a7e6a871070b6ccc9e248d482e1c0c9bd0d7d466695f9a0022806b3b7adb34054cb03a679179cdf481246aa549ed47f4ae2a2a612e4578a2782f96ff401f5f1c538f9cca58affb4a705e43070ef77671f7f4f3e3ccf1cad001424074b780907119f68e5028a061bda5d03a7d77c623593224314b7ee2394259940185a20eac9859edc0161f442f98a0679638f8c1191106f6453358c95f1200c38f313e15f3f4ddb46d36c2d8c8495b06ec95c125bbef2eb3018306f2bbd0085aec18506ce598b7f36f86226debc3ac1a2541c6c64e74553f50da19629100b219f5b591ba95b914b260c8b145edc3cde32c5a520d84ced4d5994990a3e15bc4d9b765ef9625b01814c6f9b1db5865651d3fc602f735cee3cc1ac1650b22dc91a0f315c1937beb9f41f477e12bd96a1a85ee7af8ecbc8f412cdc1705b8892c095ecf0cbe14296a39e78513f06de6a541b251ea8bf702f9aa01bab145351f95782c750cd8a9bc9cab59d36db1007b2ced8d2de39e387524044e558664c5ea1170d6e0026ad1aa61509255ea61fbfeb50f841dd1a82905522e10d985a44cb718bdff56deccf72a3f222688a2b0597010a431c2bdb9b5ec540bb4d510cff9ac9ab331000148601a5e4e609d5033aeb7371bd5c5ed1b3368e499d43acac0444dd9225f011c1ffd0a8b9aaa6b6eafefb66d870ad85cba06d725e72e17d4f21d84a9cd6e38a9b4fa37d6f8b0225c8cbd8772f9dadcd6222dcae3aa0b41f1f0813fde01f6b42d33f3a832b9314c42b0b14da85a1b02309d37cebb3717505443e1349208e5a2610113615cc7f0312cb096c0ff78a7c0d25b9fd649fd90c3c941bc7537caf3cb6f443915fef25c78f4422226666a526fb47378bbf721b405c9f29e4c2d74cfa47bb06d7f01a0d77083ab37db4907ab044296d5f5bc944ec1dabb727c90312eda037657ebf60d6b502b932d656b299e987bc78b5f2f10f089f8733194843ebbdcb2d2cf769158b88deb005e8b6949887302e91573a12a0861a474fb36fa4e1ebd772d361eebe9b7794ec25a52e3b4b3d6a333fc6db4098fc4b4cee99fad061ab46530af220c98877876884a4a66cc1488e454c4ed5ce798fcc3e7f1242d16c232be8dadee82ddc0102ee61e35c6c12f13bc2386bb88ce16c7252638484108e37e36b3cda8b4fbf9748422ee946e52ec2008831b36068d3ee242ec2eb1eeec3d0c93fe5dec409b566763247a5d3dfa34f6ba3366339016232a1ec8d145ec1a8aaf769caf4684a727635c561a5e901a5f4967d40e39be9858c858397b20176771d8e3e18851be0d9be95b0e85a930e0b609dfd1b45272049bb3ae3716067cf8c803ba51c5bd81c4b6ed674add669314844836fb765607718290bdd854331491757dffb54bcdc85c598dbc84f17b0a836ffe3c5d5805312eb2cf0060556b1401e304dd5f44f63bbf036e77f6349cd46ce0cd5a055785a4084b8323e690014ee0dee9933df2772ebf48eb187e47a998c16efa79ae8295f96e3115234073e6987a77527286a02e8db556a3b62cb284973209ffd228ec70be782e88d7e151de25961e3ea21990deed12780c89fa7638aeab257f727c1a7744715b8e257bebdb98b26c5d4c60838a728e62a94d9a98959ee2c77bab6b19a61dc828a149d7e48bc73b408582910dad47f41b428bbea64c06fa79da63384c98f637e5d2182d4f90a289cb1efa06b4cd72e546526255547cff3262d7dc99a6827d574d17421b042a1e0ea5c895845f31f216c062e7407e15d967d4b6570ddb868d5502e5ca0b7b47b38a9b5b1e1dd2d6bbeced2f5718d08fbea9ed1fdf9709048faf583d00757888e76f9619e4117c235f143c598bf41a3a96133358d823ca1cfaadfd29238772a2b68622f8b93c4f6f64ff0f9e7978a3942c73199b66be3fbf5f42b87ecac610e8a13de2b4e62e6db7f3d9be1095637742d649f68bfbf0567eca032c8c04373709b1add181085c07d20f47e313035c43c7d436579d5af57f3a5555456e4c6148050b1a4aac5d25c3a74162eb054e1e0e9913e4348c3b6f21836b0ecd4e2ed8722247f8acd83508453e85dc338ce5d35f8a2d433eb2d4e288ab60b62f291686c047cd9b738857442f3923e92aae754aa1fae01a6980bd31cbf587cab95340211bc49b35649db9a3eccad6138aaf5ca979e5286a89986e4c51de27edffafd6bb623a8ee932c91dbe8289b7f9b5ca23833314ae7c975b04c87622b9ed8fd56220fdbbd03cd2c1d00f95dd4fd3e289a9ae52be864f1eb58816a00df43372c1299cb1fb91ebd940c00417599aab3c3633880e29757c1210b746dd20b8d28104b8b5512f9ff9b0ca5265aa1514823ff3f6dc10306033292dd0c9c73331a2d373c8272c04e8b64d352ccec4075d10b837191950e85a39d20bb28d50f59bab514b50faa5aa18c5854d3c411dfa5559b1a2d15cacf82b38da102479553f69e96bbf8178bf6973cd86b4f4a32a6528577640b1f0f2ea9b568e18257ce5743b3d0d2ca345fef43e78d72623a49e83965d458088c2224d8dcf833d2a453242f476fb9882786e12405f2b6aeb5ae8520889a51b4fa7afc79c0125481519da95b1bdcc9eb3a66164a10deb8d86aff31d7cab170001449301378d33ad786565993973131f592ed9cade581f6d14b9ae7018db3725b0c0423e9fcecb2ae8b8651d669f6e2b3bf30011cd65e2a710a711dc9af4522e8fcd8e168e33d354602ea68e25142343e0b49402617ae20616d2ffeadcc99b5c38f82b880dac2a9a72b0c4387863a3a34d1488451b4bf84fbb2f760fa077bfaa7f3b0638c863ed4016a88c9e28f6f4cebde257439b7518484148f662f45c9a319177cf0ffc4e94168a3f43e49c82074f63a811138b9aad95c32eefe4dbb3e1685b746ac75e145ed14fb98ea7d70a64f22501b6dc817ba70ccc7cfcd15eb2f1fda3f08c0f1307329a0c821d642168b40926d45035453c8067c048fd09cb61a630de9574b798b4f41cc438d5a3a62b76a6e633622e312a49f8f14b88794c6879660d48e4809c4274bd7870c8eceaf596035119be0df4956a77556054e803126babcef28b868a0188aa39b0448b1d836028b859537a75ffc6110baf7177be5cc0418a9068feb50be2d2163d12084ea9c94ef75e6d3f1b2991002d92af44a6404b96e1906e20b2a5c55facb9db4e7f51009caa89a28a58412fa9b232a6069e0d1fecd79ee4f138ef48166704a89c056a007a705533fe6cecbd98a808c1fd371bb020e5c7644da226eef725c611ab783ab29d189cfa66671248b0ff8fe8f053c5c02ff3c48c689ad7893d9e87a5a4c2cf19d189571b03c9da32d0e6274f30925bcfe3688163e28db85fc06bd2677189c643192ef3e51a0c73ede5cb169b5215788119452881271901a10ebf4c4c12ce4a0a62bcd3dde7839ff93bc20eb23bb24645439cf130d399588ad7ec5bc2db5a2ef7ce2e4bddd7262de2477cd58ea50127dd6c2ee8c88374662a75432f1f549fef6912d3de79a11cedf5b48ad878e3ac1490593649438f4ccfd414cc4c1787c34baf134cab0a11c96836ef7be67c5a9b4f4c06aa23ed75125fe5cd506c8225b980d645016aeeb83eeaa84d02fa2bf5559c8386b99045d35cf301d4fd88499450d8dc73d4a440c0ef4500fea80e81064f392253cb6c11a89b3386112e70db68bb15980de23136061392f41d2520c48b5b1d593871f74f5936ba801996a34faf1d276c51b45d17063583e7e60ae0c5bf97b50573d14b93146a31b214ee55fa7514d0919f46440ec8208fdefaea0000d9d3a49069e9508c7390cb6a0a181fe1a2aa519367704a9cfc987fbd25794e04b3cd7a63debde0c4faf1d360348dff6797835cfd269b66bdd4be98fea721df16d11ac9e8bbd7070cee7bea8ef1c60586dad4dd30d91d6e3b5d4bba980987af72b40b971f54e3f1dd9fc6a4af7da6925ffa916d2b6093341a45573b44f8b55dac3c64dad150ffbad45ef8bc7e63bf7df00d7141066785da4e67e4eafb1e15f4fde9319f99c86b70d18188ecbbb587ef51ab5135a221b27e95a4d937a832dd40cdef13426f2e7ffd09be5fad4dc9a8965a624774af96667c183cce84ae9d88a1ed5713226f07e889a52c88c2d8f7d742291d2c80ad74b5de371e74bcaa4b47d7d5120762e620fb466e2c434982935a483c572d7bec392ba73dc4837881a93f7e220cdcfad8630ef84e903f3b2104fe7ec3b494f2e98673401f614211bd094649110e38b2a081f960fd33614e50ef84080d29e07523565ae9bf1b6cb9c0d8de8026ed4f18a812d40550ae685c7e292d7a649dcd4b6bb7bb8c763f7f4d8ad85b4875cfb11662dcf6aed0cce86229e0c538f88a72996cd4828b3145ad7d61130f2e40d4d83bec8b7f8fd4eb796b36c085bab346e3f17b704a5a70a4839d194cfac3ef4eb67cca27592f823beae1398e2bae5d3c70385e1ae208b66b8c93b6c6b03549686b488452bf40c55bfd10447e60e54e404b5d183a9079cf84bf18c7793b270b1507e8d426e0274aba20da3c5596219dfd64b162214b06701a0d44f3a1715360de7fde8e6211fc3febd22984abfbe3b5f2fcbe48282ef62d273c754f9df09359f288874a8c2b788d49093afe8247926952b46de86ce593ee9ed9f6f8bf389bd94f37918f22fbadc8e94e415839d75243138139e77fdc3f6bdca8d1ad589bed6a11049793d7c5863c3a55445c2e59a1732a765cd2c180092c8e903f12cd8237395ad6133036d35bd7039d4be1381f4b59914dfb9da543f0dfa2db4e558cd9e035059c03c9fb746ebeecb6f14b48df879aed2a0e8a5a9e4d07669a9eee3d358d0b68bbe2f8ed91bd03db8fbffe869586f7fc12f2737c7377956ce9c5b7602c8da18d0f01fbac6410f24645d80c791c8939dbc64213f78b1ff2f12db4e41df0367adfceaab5ce22cda3f601fe7516a29027f49937df86cd578d9a5002dc8d5d976704cce1dc22ff55ca1039e518380445d6fd996b8c22636eaa8dc7274fa0901a3651d5eae18b3219c88101d913ab986c823b7e682447a4e7523a02a4e13ed10e068421446faec6bb26aea908a2d40625aa8120858394aa20983a83de38bc8d013577710daef6eb1f6e415118b117dcee0d38495f0bab6671b08ddd7e800d328ce8d272fac236eb168cb89c93177ddccef51edec077f34168178dae2422c2200b890b3d983298b11bcb3071ae02e1f960f075e27c03f9f1ebda58cdf87b7035981363f713a1341ef16091cb06f1f6df5dba3ceba095f2e301f1684cd022831928ff719e2d60d3246f52a5cd0b66a460192a132e7f57ba808990fda86789b1fde6602b393bbfe20219680f5c453248513621da0410613c1fe80aab56a024a1d39869875aa3e52f17fae8b97bfb1e491c1321c5c88abd046908ff8d920182a26fb4d90f414e27228a9c5938dcaf47ee2d3122cfecbdc12beed13c5e6005cdd46ee3f10915d005b38c0289522ef4cf42ec6fe4282f0d71a26d9a6f09e85fdc2954618afa579a3c217f6fb73192b4add011288987c3d778a90c8a96e0c52210964eb31cab192a262a8e9d0618d621885fdc26800e8e31185b724ab3ab0665ab0b2b62dc17e97adb4660ef325a67fe0c4dfa18ed38c353d8e919251140cb62a68a0418d64d5c8846cfcd1916de30013263ea701c9f0325c492c0291e5df55f750c81f57ca159bc1cdf8623d41e0a600754f60d62ad32d7fbd84ec1f749f91e9235b4bcf759d76324991ffeea6648e4113131c5b068ef8e602f53c842ee28912787fb10efe73b673bb26c18464ee47ffc01761aa2757bee8fd218a1918f8994462a5ffa444cf061322f78969f0f606f6aef921f1784ac9ef54621807c76e7976693657d504c89de63e1b10ed3fc0f3054850a58c9024913dfeb29a20d4227e05a8dcef1d6bcbdb6c552fffb27a7e25fcb0c9277103cf03bd0f8a99817a0253772a4e393b0078e81af9241982a40c7647f4b7296f1d117428e247c3df1d394030b548fca5bc51e9909b3a4d3c81618a6f9ac02a9c10cf08b0423f9009f8e3b575e954c9f689a7903e89658d0659fe82cf1b53c78c73a3aabf7b44f1e648aa1c3f71548f77e7eef9ebebfcf68fc9a10471c90cf3df56f55b5c948688b897f0aa8a26d3cbf1f50a7a0b0000a91831e413dc148c74ec0af135b7f845933e0ea00856dfad0b5e1d3fc240bb98000b8ca20539d3ff7be2ed069b660c9117d288e226ec0623b0033212a365f21c4dee2b9e79aa03f4f0ebff6878112770a0fde1173ec2cb6d9fbaf4fdf5c24e52ec1523a1152b643943e268c940e053d0d7ca0c5838425a35c39206f0fd98ee101a5482541a0789a6ed4a82e0d2877ce8500baf95b3e802ae71b69dc4b971a2fa3fe7dbbdbe60e68143110d912928e8a678ebd0f5a76f564ba486d5cacfdc066bb38424db58ce1ad71e2eb9a33a2ca674aaabb903712568722fabc9216c4f22ac1893ab41d617277a969d51f236408990a7eac066fe52824e312c5039dfc19221cf0c3bf549a67d377c502ff38339587693f61ca77eb65103a43dacce003cebce6334e5dd583a2e22c67e15053c54ebcc4c4c8440a2a194f2f8f94afabde8bb23112d7eef08b8b132b8d0191cc5904adbc7057ea1686aede73bfc315d1022092a15aa6ba6566e496e56e464eec2950304cc15c28438d3543160cb505f31c8131aaea642f2664060cf39953a2c08fbc88d0b240aeeb77d464384004320363a9d41c0cb11ae207b3f11c3967cb8f561dfb138d72b31013d0b7fe2489b38ff51e77d17bed0daba5a9015b1bddaf42779f6524fd93e5e10bff95f0fc6df1d5f9ceb5ae88764a439ab97831c841ed67bcc39d626f00bc9fbd207299528a319065c2cff7a2315c2a48174ddc0df9d5b14c2730e9264d9a17b973b93d51545baea7e812e50f1f19d96baf73ebf4888f26f7b389b8ffe523017aa1cbf764f38b19a502f2bd04d008ddb4cf975edacb66f69c137b3a53b09b7a8f2aec954bc10e5a96c207d650e0de6813741409bf8e10fc5e7016bdb04c1d5f71177329b4d262aef03d50038bfb7e8768c85d97b4193f811fa333bffa0b2267a98e6078865efe1ed5fa08bffcc35ecc2e5306ffc9190f6a71787b8aed45e4ab329a648c544aefa8314f3bc86eb961dd9cef48d698eef63e94bc71473d9d8ab67395733d89c93cf9ee52f1bc0334bc040663378c191bb2bbdd1e86c76bf7e91f21aedf7e443e14b5faebd9c63e98171cefcf80e86a869636d4478c1bd185db1e4acdc1140f803252da6bd8bdd7fe3ebd54e279d559ec0d593d0e190d1df3fc56289892272a5f361403e21e91919b7fd3bfa75b5140394f93a130764539b6c5774b45c7df0f767954ad557cf03920c4afc5dc713c4e46cd19ca9de214e63a9fa9e3d11ca8a592e94d58aafd6741fc2ea85d70170de8523f83ff241303ec08fd413eccf47d4021bde6e1db271f3977b07acc4b2214179b3051778313dba9d0817c0896e3909069669f5b2ca38837c6e46fb712cfec4f29758c89abfb144047105d8a0ee2e4cf29f89142f606e56e9759d021fe994306e23467578cec04f824be3496e94bee4fcb61231850902d0cdca51d4b0a62f17e827b935ec1313d0e48ffdd2ea0f18977ba9efd03f79433f386c270857cd1e03df0dc59fcae8045c02a7912a681e2c97b395356626197fd22975e40ccad39f9b486887faf2b2fea55e418612f6729e28859c0dfd9009781a8632fb1ca4eb455724177f4b57e0cd0153dbf8c937918442646c89958da3438420e4cb9173800a442fd94eddbe1e19384eb2ec79fbc5daa6ad0190241909abf94a0b681e513428cadafa9922f2c0d074892984bbcee65258897ecbc3f74d8a0ec315d035ac323fe285eed5c47ae6b54a39f6d8889887e49338158a559f1e58e0acfc86a5946dc6462af203eb482f47149de2882dd94e8374ce2dd53eebb0b6ee58f1f4794efc494b0be94f1b5f380468256eb14d0a41b21693e93d87b7145f32689789b625e1bbe08e1d7cdb19bbe9ad73a4c7f2416163c85490818afa4c0e85cc9a963b79e944d2be1d1b58e9b37f0e3a62344d3393955801a207746dbb38afc63b6de8354591acfb8591b56151ce9c8226fb156deea9c432530ffdbfb22ad542d0aa1fcbc9f799a8786730ad4dc2fee6e30400aabe8c849985b6f4910bb8185cca86a188aa628614c78daa82afed3d99e9d190f72bdf929c3f7e6b2dffd0b5aa9216d317feb98bde8f079441f9745335471b9c06f2f53f371f37fc0374925ad88f63d62267087e364622cf5bc63328a653c7bb88cbe86036640637821771535dc6e962334bfb1efc8c10141e10407feaa98af17e48c288a93065bf3d8d6f54e747e9d1abef8f1ab804eee28fe0bfddc89de8d13f3fbc636f5b4e5f8d8286316d2960d5bed149b3214575ec59e44b70ac2cce4388c85e1143616e21858729af9d66ea88109f0332de6e209a55cc0dfeb912df47eb2499e7e126e544981884c6538d6192aef4eb1854207d0e2a22f9382f966369e4558f0eeae6bcff3bfa992cbe821d3f63db3bbeb6a58ace1bd590a3a927448bb9438aa99786e35c7fcdd9af099b673c76022e39143bf938236b0dac440639e16184979bb676a1db4c99a10cf561aa741ad9958a2608e7dd0296e6219e632125f800313a240c81fa4d5bb1ec400134dffbd39909ce37fdff8791bf3a2567600abe0b81220f6804e067242d28e732abfb650c8c3da28641c8cf0e0cfd2acb7fe1fa1aee8a324c615239923d67692b4c227b717c8046a5836839a8298fd372be6b696664a2981816603b459adb0ab13882c31fc33a19fb208587b8bb34ed9cc8f6e213b102c482528e602ecff81fe1960b6a3bea738cfdf5a8e21a9fdfd204ff981dfb7a0e00416237e4947739455dd41d062ccc39ef54f69d0af45db356087d7eaa7a98ced08726871d1f0220385dfe6f2bc7fb5edc55822c5c24e5aa2c9d44b3c7a3890522af291d7e7493a27528218de70433f30883212f6f9543d3c960bd06720df1ce984bfb06a64b661bab5ff3cb249f8a352c9cd95613fd1eb9ecb0c7c60d20e32c737c779a5954a0f9e02448213c9cccd57b52938880b4f6e9e677a910d8402158832b2638ea27b3d64e63bb8bbde7743091ded6455b95acffdb516ca0532ea04837977a689b93c8d46f14e4a237014f83951c1b117e461c11f87cb2226c597078e6ad2c82bfb0cafb6b3cc2c4ecf0e8deb0b3ac14c3edd43411ac63a5dc9564a9fb66916c9d9297be4dd35452a1f2673895c837347083bb57509f8be23216dd02e5a1a653884ccdcc57aeeaabeaecc6669288ede3e49c2307b5f4f64cfa17c886413f1d62fb103366609b712cc7b1d2708107ce70571c26de126fcfddd3a5de2538d7fb8b7cb3d3749e067222712ea20568848d2f2ee6df0513d2959d65c3167472552be590516969d27b812ebc32628fc074d5caf2f608ea1b1800428bd5067970f6d897ec8902ec296bf6c3d27212c789da199f5982356fc32c0e8704ec55860d2309d0abdc2457f627170837f58e143ac24508685e0997cfb5ee7db2832edcba553b8df27af62fada09893084c330998e693dfb70d7a160a72737a076fd8b0b738b6dbfd2c7a1dc06ec44a6c9fa872382512e713bfd4470dd1341131f092abcddf56452195f466ae4e24861e766589c0482d47cabc6038db39ccc2801b51f851a3355305c4731d1658129cedcde3c8ff916a2c7e68b5af38d097ddba1d3cfde4f1acef85276d684ec6b04c810059af2cb5f9a19181827349c685e5c3e3a7d62d472f5465842387c6a2dd8d672492a2e1c9f5bf708c695fdf1c2f3e14068947077b4c9aed241dea65a66f70123ac5c736c4a72540300bf4fa62e3dc28c3700fc2654b143ea50606b334841df790d1ba3ff5675898317d71f8c87133d03094ee462d6986d8c0c2f2770077914e2f3dd7ad08285f7d42a5cbbd71c72600434d8098348ce15038ec93cb64b9d9f17d08ac2e2877a3203d98ae89678ebe4e145a5dfd01fc9195ea1b69d0be952f5c164283c82d97dedaf40749f9906d48293bbd46b0f7e8523d620dc018ba28828715333a8cdbbb166d54b9668168ebdd5575688732b22d2fab82af4ed4b32630309ad96cd51706f172243daef337e42926763c8b7adddbbc25d31c53d99ebb907b220d9f8ca535527b309b72b2d2b3a9222b9796ca6cc8eaaf3233233a95b1c05e9282e7ef06fa5c106549d40743865ac1bfc25e6d26389e27c3a3db05814a6ffb89a637e8d0a29cd30ee843132a7e6fe17537312cf8c9410ef07211d9a72d979534cb389907b9a3cae80b19c96996f960266d1f0a8fd60238e91c4bc89a29ba5b0da0e5fc7aa1495f63728b4636998e9c4761440561b360ad59b62ac6a8661ea7d139c65c586983e69b6b80c56665e6f1fdd13e494b4275bf9847d5ec244cb4f188847069bbe8997f9aee1950c5457c1f220a46bd6983fd8052e5c96636fb6e13d257c87b8911a595e54baaa1e1e3e8d1263160cc31bd254354e8590db70a7b081260c8c0458348d8a6097a0007a164ade31eeed31974af34eff84e0156c697cf1486dce507f76d371609b7f4ad9cd682e189f35f942501af47626c11786b4053ca45121bdf1a035bd71a69c640517932c3227bdc4a139ce0511f57a3268c80db9928cc724ea41ef2a90bdbd296c12c58bcd706a63fc612aa3977ebb01c8e4ae912e5c71e3009d71014acde4c68d15a8a040947048459cfcb399a4eece80668c212e306accd7ee6eac95bae64989be891f5bc02db207d0200213e8e1c9779c661239a70e48d38bdaa35f1cb87e25f5c71d04061e6dfe8639461a75cb856a659f80275b4f7d44c61820a25ca210c0f0f001aa09010924434dabbb41ae4900c38618f1061b23af3e0b40689764e2a32549ece2109bc82d04e748026214f6dd2bb6dd743b1133f1af2d3eb43ceceb083c9fe3ab1c60c4ef61d4d8df30b30b3a372ea09a91e6f63cd0e2c8a04b7ad2214c41000996abe58bf46e9be7cc737da0f44e8b19a73a0658241341c0176e0f467a5b66647cac0b872ac62c011302152ce7de3d41e7d2908cbbb45d99e4ccd153887f6a1f2f1b044e336521f2ab5dd77a4aa46057d22379038198981ad9bbf5ab420b8f20495a30f3486037d51f003b09688eba8e199e3a3fab572d856caee18da523303f2cefbead8c41bf813cf53b78dc5b9509ffbfdc8813f69a572ab3fee51cf60f4c7eb6ff02ef68697b57cfa601f225286f4a63d9d7180e1d4e42a3911924dc795bd640cd3df41d4c0fb761802287e7e4be599b0a9c54f229308804aefaa5f676b7652b96c67192b585c1ee4e7e3f37a3955c0b1f15772bf80d0cb1e457840eada1054e6a81e793cad3c384bff38bc44e5350dc66df52630ad00e00b0a77f86c2e65fc01b49b1eeaec794667c1967618f3fc54f389481272369cf8fc2627c8f4e4f8765781eba21a02e3231a3d0170613a70046384b71c7e686ad2ddfe2199f4bcf338f2ce293bf0c38b3a026ad90d7a099f89c684b9cd0241fd46a1ba05aff32b9b49cae86d2198c775d2327d9e4672596f52f8dbf1bc63b479c45f8b98bdf94c961039ec4dea2b541897cb839ebd8fa64a7878d60c680fb0cedae9d458d35796aefc62809e379006cc59892be1658c55aeadb1b4eb52443ae63ba474abf8cee13dba2e0c9b4847c37b1d00f53e6fb6591d31f3d5ab3854abf585da9b10789dcbec83d6ea60d93cee23c2379148fd9d02f15e585002b50dd77caf93bb86eacc82cf0186f59215d758be0986a66b8be0f2da35bd5602a136702b06d42d1b0a5e8082001807c341ca961be5fbbee2923688c5066d4f297ec28c6cd82caa4735b1763d3f7ae3df1f8d8de14898e51db45dc3e19e4ffa601aaf270fa670a14ac55309b55723ac2d624b7e21d78a015d4c7f0cce722a3b8fda35bd605ef41855c85c93743e1fcbcb9f7a9c63310c0fee503e03490c6d3ed93b854d2b3b9d8710569ca9d7ce09bf1fbd60485a78f3b39785f5b72dafdc174a444434a3032b326da3d9fbe619a89c3745b258c3bbada45bfdeffffa8ec887788f9c330ae9587b137e2109c4ad40d5c84fd266833b7fb4f8ba94a6fc1ba7f43f95de37afe7f3dda23b1e2f496ab60086dde0d840231a871428afb7d730c3590ab230cc7420f0d78b4c8643206cd6a16860af399f25e9451933b5f5de13a97ce8b9890c4bd6043132d5e8dc1b7ce25ba140ac26ef72247328470f26b5b593058757ec4f8eebea0fbc401acd8567da8cdd58eb653b7e4038a8a2a386427265c36ab9d9f77a4f7d7abf9cdac04e2d49113bce36c605aa03b14ab8c6d5264667106c880043960d2a3ab73d1af6b8d034bcee55fbbcb9a6c29180a19ad883a1f90eff4e9bf76acc1247bc22d8cfa50750b15f4a5f8b1b12ed23aad0821ec5f4aaed07d385d20c04f4e61d22ddb153abd923590cdbf87dc02f86d4f591a4f407d24df8c2643b10067ecfabc7323d7dafba3129dc8df2f7039b65ac368514839351aaae561e85010ac246ee9d9617e87bc839a0d2a4af5368485fdae795251bb148215e85ed3ad659a3cc6f485c3f99a8b8a2c96b249cf61d0d960c45b61bbac47a63c25650362c914459a1c0fab2fe51cb656d1bce901acb6d26fbfbb4b2e340df7c4739b2e0ceef1a2f91e4efc029667e27263300050219715755ca1bbcff112516b50a547ec87773cee98dafc9268be23140c2e064fde056ce2d0c93d1d16e42c17e55dbae6dd5a105b1921e93541fce243a1bb9af4eddb151aef20e81ea0ca2daaad77cd071bd8e054febb640b8b7858c9078a527cd449fe1903fb2023dc6fc7744974d7060d07fb3b4beb9c82a1bfacfbc6485b3de6f585de94fdfffa1666d8abe750c6c035bc073b19663a7f69fded9e8584e5b6d56fb32c235cf93c1b91cdcf02f47fa1a8003bfae69cfb242ec3d52676183eba40b4eaf13624b3c9bbeba42037e84a497f7c9a77df69e3afd59cd9388739bb07c567f828c50875b17a81474a637c1075cc8b16c857e42b52c3a780e30134ade1880117d76b9e2ca7255322c2ecda9c13e54343a2ff516274f2eff11ddfc6f130f24e7017479b55889ab378cef755cee1fcecb0e2861793350eeab61b611b7a84706852a217aabcebfb1fdda6db49a6f74c3753784029763fed634ae5e5822f6bda39589958437f484ec7ac413b4c15e5d9fa5ddd17378acdab5a6767ef9b9f298ec3b798a50276a9b6f05d3d2a39e31d3c90d90ab96d2f1c9fa8a56942bf0ad46ce0a4596fe7ccd8aaa5b0baba0cf52afd00dd0cb856937059f93fa688030fb86b6b72b9c179d15f2761345ee4c656b6b854aee0d90f78044f5cf231729ad9630fbbbd132add9a2c1d41e29dc3989c12161988d388026820521721a3565183a6eb786af617858d83f7a5b667db65d9a3303dce98086273de78a8adf15963917c55ca8d2d2ef39dd706cc4cb6aa913317c33091671bcebf7e0ac55b20491e0ca5f9f8347b90cca84c7a99756ce46de55d5d3416b1ec713c602869e725635901b7889993db076809d5b46a459fc92e696c4daee0a2b6f89c0c8086b9bc1e80c1545cc76a4acf972fe7557ddada4d38a28ca1fc1dbb13abdf2e28b1b5d031f7c7ce8a150dbdb2179fb592970b299355194d219d88341c8b55f5566a728cbf3a064548a4d03032fe31b35f560e4bbfd27d7001bd730015454cea410a4a4bc129428e751994f19db35d5e5eb7d49188677f6b99768e5c53720c391de33a41bb04b28d1740458ecfd948665f40a4739031bf0caaa61ac6e0fa8bbf0c421cfbaa8f78913381b8a9757d7be7907da18c1fdc257aaab0e6478affaf39fddede71f44ec6ac47717ddea8ab52fbd1000685c3a65662dda1bca58c0b7fbcf693c3fedb2023037e148af309450f97ac44ae6602940cc5d75d5e0e814b2f79a90da468aa631c10b2710d2dfb0a456943a5dbd88cd5249c9f4a1eff7201c0456e26f0684a5fa9482c991c8aa4fdc62c7421c00c023d2d5d50b90c38176ceb06667de3f3b62d12f6a2e1548368784cf382bdc50f1cc1008ea764b2eae2638531179f4e23242f229d201e95333fd42e58174a46821d556f02b07a50eb539a89bf7c49b462e9ef4d4372ad1f4441ff886e1306783322333c92d73430450651bd94c49b61577e68d67670fb5e5e24ba40b7358034013e49bb5dfffc1ecf92d58a592fea7e01bf0581d23a767c54ea86a580e6313f7cec95650535ffb085bc47ad54dc0c6c6969f61775d4d892f94ffd9d8c9c65920830c9de8b8da7acfda7999934c636630ab999e947268c84d5c022596b430814d0b4ffec25dc687e581ce8480e472c73769c71f7149650d8c49b90f7fde16f11298678bf312f84bea618382f1d87ba285125de840c6f12b1b774704202d5b8277c314bacead8e2e22de62c2210e868fcb6449feb2b3a4e9d2c2176355fab8145ef01174f97b4cce0a4d3bdc47b580741a9eb74fab22d85558117ef20b16749b18c9ae864cea4eb8445e1ae18c7c2b2cf5b460a178ddf486c1b1db8a17d1473bebd4773dd65fbc471a8f8ae8ee75d224c413f6b057e42f11ea972c431d7dfb010edadfc88498fb40fe5995eeed57c609d24083662786addb062ea88a05d8a4a8ebe4b0320e32e213c55c966eb0cbe6d7fa067ed403c18ab93861f20e19b0368c3ce99b708a92356b55c230638b108338063ee004c55ef3bb1d14edeb291a49d41a486004722e463bc3f7712a2a7e53d0f6403f9d7afde9b39b36e5d848b4a6d9cd9e5a781f62688fe83f78f7188d86af6c110d9dd2534daa51d01fb081cfa4810db22898fcc06c328d60aec9e64603f33b0cf33dcd68eee1fe81678f4edc232e1008617c5fa3a99540a4f310837291d4907c1c3e45e38975c460fd2652a4803c75b31725de415bd72fa0b3e0d7486999dd199e828ae64451d2d8ab673ef8c3399f68f780c4c80c26f1fc1dc31c798133539efecebe49cd305a9e5dc8076d7ce01f7e44a5d39757a0e7d45bdaab9151c61c6a31a23377a1e00e015cb127bcafda349c2995efc735d3e4cb2f475954ded60bd11b61af5f758de7626ea71ff517c32b6067e4304dca4c2358d4d902c47289cf27a9c046e1b10192a3662587cb960f39a7487eeca53b190fc7c0e036a984025511c3d3a1c967018328010903b9a307fec4aa8fb211af79e68dc4f7f1db82e1d8e6a63bea43fe924e0133ec390d24167e409788500264443834c6bc575c964583cc75dc835bc52b5128c1cbac52843e60438e521f6add0605d6ce08971cb67159d7606e6911cb85315c3babb69ec6a8929ef62a0d6117b2e64e98d89b9078298f503f20299d80203634e89687b006378e069626f806ba2f76e49891f421ec6df476e5b7b942a3461e220e7f346c7d2b0788f1708ca601581faa82cceecde70a42a13ab3d946796d89c3d51d09a488323d7311888dd5acd63d3f0dbc791555655e3769c1a36217ad63163f5c6ccdf1cdc1d3424db24c539322b411f275dbe9fbf706ade17626c2736ce4885bf80e031b6cc9a6914349a185c626df827fafa4ed25930d284acd579de2a02e12e84065adf34c6888307b6b9f419bda9b661136292718f482ceb85878a8749ecac0c7bc6761ab346a4b9b6e33a3d7a5ac3018261f7b29e22e44a3c67ef5bf95cc0ce709853857b410195d180b3719682c98e60d135fe4a3a5fdc4660801dae7f1665671eb4890e36e119d857e15735b5614fef0c0d334cb8efd74595132cb528d4e55786beafca5f72e252c1ffca284be399c26106d887cc8689f5b6bf8ebbf4e532db9e5ed09ff253007b85bc424420de0444d6b0b0ef3857825ac61e4efae3760e20de095c5153986d13fc5c73b891287e8dec78412f560dc0afbe65fb2f9fcc782414c23258f93377d91e03321cc870e0e0571ae114e3693a22099c8f93b05b70a6e00320a16cbd473c63b12889d8c037e4589a78990ae9c10fdb7ef0c88e65cd0812408f08fd4b62c2bc951a5bbf072311b8a806ac8b5fab948373f0efc37d1c0cf5d6f07f25c37b7e9541cfe9abf1cb8b34c369eb93a86959e1827a57fb4be26bc35b2ec6b4a8172795c45e71f03580dd90c16876ac49761562c2138684b6953fe8a22a90977901a6fcfd4ad900a8358a75734801b4fa8f38b5505bb703675b7cd979d75b40b273fa0b33e3ed3f567025051ceb9578feb5fc044d8f853722471a479e667633059c34b8ef6bee3a74bc4d2306fe69e57131f2f25935ba6a52f51795ed77aa3006b9eacd7d4323fee62e9d8162c1ec53c3efe517cfa968a6af832f3963f0df43e2935aaf8d906b352264d3e811fde1a9d205afc915d3e08e598edd7d1e7827a13b8738fcda57efdfff9923b65c7b30b647ca094f1bbd8e27047f030be446fdf49741143042d9cd3b09b526c7f1e2cf1c21ec8a2fd66540c7ca804550fd5e3037ded21a4a56cb3f9d67325ff4da37cf551a8f5c6dc81f5bbf53e7d39ef64caa09aa786067da34f276e69517d362614a880f791da1b37e872cf7906efa44262ab31bc638efbc186e02c51307799c23cc73d0ed77e22405c00bff94df884890fbd5648537572315538be45dff9ef6f8672debcb69ffa7e491a5cfd862ccc7809cf181690e88449e570f14544cc3476dcd29346f10b7e5d2e1afe8f1bc448ab23b06ef9e896bf9d1b668351af4d9d1d0400c010a5a3b513b2b208efb16543e4d585212032a062dcdac51fe112ac0b3b0efc52ecd9c52a956105d084da55c1c00fd54472784f30f491a7a8a21ba78109832bd7f692b9e27a9d588c1b3560049efb4c37935d90af980b5315156fa976c6963ebbfb7ba48ef9762f00719865ce8d6a2bff7706bbd34474646dbebdb4a3e3c342e0ad0a17525be0fb3c045ec3e91df48fefbd179baf6c5bb0216b35b66415597af59e92ec821419c882fa4f6faefc57a739dec20228b7681ae9c33cbc4b20c081f483f033edeb35895c9b7e8ff2e7426cc8b3069dfb342c1eb6fb79f052136d8205c9c063729c2f7c01ec921eb9a206d6772c435dc83304cf898f75cbdd700c9ebd66c00dd234c6d109df9c3eeeadebdd12f2e555e22141e337ee1b0568489b3d4a21d31be6e477ee86087b919c8eb38de7ff3c98cfa41e3791cbe2f2be3b25bb1b3c1e4e55d6d02a81c0a40c6b15e3b992e274c7286e4a3434f3aff695aac05a6159418ecc40c149f69a759a0aec7049dad1ed812a3f4f957dfdc6f7285c840489d44a6fbb29eb488eedcec0e6636f0531b33935d9db36c2648eac3487ac3b5e91fea57e63bdb43dc78a71561764a52b6b036d003739b52df8f8e1467cbe6cb2cfa41216fddc107076882056a30b22c01baf96923bcd3525dd9dffc76f3f8a32ea3060d4120faef43b4077d5077297320f4bc1fba7be26453296077ae162972adecb730ebf06088dc2e1adc08d3457d3c65d5f43cc80f1cde8b6b59bb4b8df3198d48b904d857bdd1902047ba9738a287ce9e829e1faee1ad25a17c37ca690b6d4855a53112fd72449599090380b4886aa5faf0a101d14f10e433201acf9cf4f7d2ff5bda94349ac8e6535696973bdcf144b6667106c6484cf87a60fa180035b25db2a0074730576bc00459d1c08e1f9269c5a6a4f8aff32e33992d74449d679597b8309ed101b9fb081bd203512cd3d14ed3cf9cdcc8d8442df69da05579a3458185467ab52f08d8cd8c5b03fae7ba383c0c364c4f6e0006a30f049d049b7e69498394ba018e898715ab048ad0b703ac240c2bc89471509feff161a7b1d31573eef347e09abf83dd8ee79e24737fbcad8cb7f3e193c525b0c59181cef806b9983c756c07c03be6ddbaaff91662ad7c73f077e79d61212815559df61dc9ecd3fab715a1f3905ae01ffcc027d6334a5f1ebf00993f317cbc35f970541fe8114a0c69c6d4cebdcc3019303a76b09e72773e92ba10fd2de90f2be97eadb3fd8450ef0dfdfebc46e614f66158ef77ccfbb72b652f9a549cdd447487ce55b71994d6189d10d01e70676fa6668dedca9f0270a984a7cd2bdcb6b1c0c973d7f697dfbc024bb88fe5f0a9bdf4153f33e873691e05ff71f2f5efca4bf4990c1e44fa4dfc9a7a9da667ebad287dcb7fb8529fc2320d5f256ef82a60132c0dc11ea955266f7ef14d1d1fc092b487cd5903f530f2dbeed647ca58f623577469dc3ad04fe0d97c13049b21c6cf13c335c04567bc4cf6031834a929b9fd79fafd9caf5ff6bb4de4fb289d4bd95f45a46d4dbc97c47eddb74ae9783806155655a8fd0fd10e48c73183c809b2f73cc4e07c7ea2d710420033aa3b7cfcccc85fe34b5cb97a8dcedfb5f39978a85ceb141d2d6c5ef2efb7b69af6d472a36c0edaf1cfe316c91140d0620bcf74c7afc3309ceef49847d6d45c1b7f9bbdb6e1ed815be4a3b05750bdaf6f14b73e91bb1863ff10c5bfc5f58bce1ee3366bdd0715cba579cd67067a110e33a15f92ae4bafe43a191cf966be57c6efe3c604aa238c602dcfc4e3b4003b952d7c3e4db66af665e8759f4b185223fd87db7e627efcd2643899a5cebdd36e0cd6c5f4d70cf73eed4bccf25ee19d92f4d1f02eddb57ed1dace6f96115e054529d4c63eb3e0c7aa2df6845306f332a9da2ebd0ab7d0a85bf7b9ffdd7f0072a644b6f979130d8f5789ca0414cfa596861deeda3bf3baa24f1dc7d5752281d72bf623bcd328ddd8154d9c3ba5edfc820f6f4f60b2b8c8cf443932ac7c192d7bc694d59973c5bec309ba973ed3e5cef0f267bcec195f7666973cf34bcff4d267ba9cce3c782cd0aff46f73cce8278e95fccb98cd99fac24c29c038231e5f6262fb25b7d1bc3c19294bc694911925c99c5232a5349928478694272365c9983232a32499534aa69426135a3a5c2abe9f710ded51ea199be1037ac78063c89f89d63873623cdc85c45d39d2f819c550bf7c838288fa5ed1d26572df9132893bd673eaa2a848eb2d4e06931bca4ef2da9afaed68e67dc89687b66f4c154705325491bd273afb2b4944a3e9b0e5ad13bdffdcf503689f7fa91ff86bd61ae0f8c14d086214163f93090b8b276911538adb030fb4d214ebb1522eaa4816c39ab92280ea4be52d7a4744cbebcdc961eda166837c51addfc3845f359dba7692109ec7716feff17af0dc9037679f3cca7f01fcd43fc8794ac0a0d32e9e8017d8002893398cf317a52018556689177d0ec48c9d2f12a6b3a0965659f936fbadcf5f901e59f6de52c44a09a46448aa37f80f2d13031017056791b93326a0ff0505e8a5c4f7618b6276f8e251a1d90561c69900ff1a9339987e47d9dded209a5c40998bb42a431f626e149d0f690d1499736bbe6d3a719ba486e7e87d00a8da453652477f7a8b86fe72587ea6635a09bb074e696ccd552780ccf312e5628b400010e1e6ccc3193a11ac7b4d8cc8b18f17cf2ea53f85d078a412ae8cc6e78a3540cfa25f3121bc2461da57d3fc875dff6f78de755117d210ea18e3469bd0840ec70cded065b78fa47b53aa3c8426294ae505174a362a03753a0293d1f96eee57c85fbd4fe80e52c1bdb689409aed5cd2df04ed9eb1718d671b1caff6369a3f28c4b606874004e7fdb50c1664c90e0e84eec20a548743a7fee038b8fb04ff182208e8b61e0406f220eb2f1fe39c0be4a0b970b3cf0480b6ed82f76ab6d34ef88572744d8c618d4a6f8883a09864b096ef7512e21b2e8d420fa3952fb46369ac1fe8645927b8c0ed8129b636ca70df76787e5f8e93c96913ebc67b6c826c6ab9dc90ef3782613b2285e180195510412af084a47abf12c8274551780f7446974c4c9c0d4905b067c2728328d82a37cccf6413a301ca2e9bc0482ef97c02fcb9031ded25eca9d51d1cf91c88851ce49a1201cafa1130ab12e3cc1c1dc4f176a51b729643dd1967156242e628c731f8d6f0950a0af093435f6ca6b0b3018bf68e82bf3a19d89bb66f41ee1578bc8f1c3d8cf967a57e904d1bc7984a52fa7136aa54f3297006b3b9ecc19c4ef12c68fffbac81c024aba7066f38ce07efe6cbe076353d93ce1bf7c05cda5a998f1483f22523c1a1e0fc9a894c68f00a5b7b5ba1be9c9c7be001aed206edfd2999af30f1c49f5ca47594370800ce382d1cd065d7d36c2878cc2dd5c83519b46dfb149a105254352cd45e8b2862777bc536197cae151d19931fef239965a5e65e347325eed2616f8117323612af702e2403062fd65cc45435968afa1e349c2b72e16c657472f0bd79f47f375b8beb0792b6a6ba811ce1c1a10ef64513220e27e95cd98a8d785f28aca6db745dfbb1b0799caae79e4ffdae5a24715f5054cc21a72c102d44f2ea770eb1a21086a2e501e00b3ca71e3617b62c5ca0f96fe97cfc7b18bf255ce36d93f6c6562c37bd9d8c60f1e590e274bde581f0585304a035d19d744f63f83c6ec4411403dfa7453c6e10fd72fbf7801b29f58e7e4131063ab1f19874d3383c3d374b3dad4a0a735040a024e7fca586b56ad7d4e39711cda9ee89944366d82c8b6d5f33e497fe474146a2c90da8c95af0c5f88c5db5b96d725b4ab02a461e6613d48e260089d0d8a3b0857ebf706f71f5cc4361d8ee0974800fba6b76e78ed8f039f5746c0a0f47792f9d021424007a19f0b34f84d14bf8a0a724109496ca546b742e10c6ec72fc8abd32f0b76abe682bf46ba7870afe2aab1c090b260a1c0a20a0a4c0b27f0fffb190c2832eb30aaa53a7bc1d6f4c82de8ca53d0817e0ba77e80672d22859a7a8924876d1f1a0c0a4dae120fa435ac8bb0c4fa81f9dc5484704c433c7a76171a65e8622b6fd626a3ac56cd63dde855707c5d04e784d9c24e5ac4ffc39f10486299a6c10ebc28926cfa3a0ed06ba52bb7e8746d27086c9f21b3e7932ea18321ae0c531e7c5738786d0106faa29e1a836e4a285c69854510fc85c2eff36be03b6e9ff3cb9b28e0eb16ce077e50ad265e6dda249b3de252ba15c839e57d44f4f04ebb2264e80454543d08160dfa1f3a9031e0c85b6e3ee3eed77c809aca7d65992e51ec3e427ad12c310f33c3971763b5261d1ba127d03a0c26fe2e9c060f176329776d96a3b3a8f2f7f3bd329ca77afd75caa2a0b56df74f2ae2cc1d291157ca6e255fa3053b5b5256e708a8179126035bb68fb8ec5ec347b9daa121f9062711c0fd5138a4e7871139d6100bdacf7828329b2e56b99be197c86f229e164014749c323a6f072f6e9f00561f895bc7ae80eb9e670207b4fa5289b3e17747df29aec0ef610c2e1b915a57440742cdd2445c5178366483056165e9f1adf2c7c9780f45e8e7bedd9314ff886ffc994ac44532130cb087374d48f353170ebdb2af188661a3fd8904e7f25d799479b04475b327ccbac646c72941625986155f064bd5fec4ad59c1602a6cb459a1de1cbec2162ff8400deb4aa7cd0f715194ae4f253bf36a9186d383cdf13b9be78040e0088e33f669d625cbcc1716acc6ef2a60acae6d623616b9d89962a4c1e2ad44165ef2f627cd818bc95092529faabeb5338cac761b7aec4c574ff205881b10fe9587eb6ad5346a93f3cba8a947f43321e9a2fee451a67658fe53890cc33bc534f001554c307716a0f58bf251afb0610d1de56d3dc9063392acd3fbcd02793c516cac3e55a1449c689c7f8a4df75d67ce7009a5c47a9fc9904149f4fb06d00f97ad35d1d9f3746ff0a7b53e1aa856d90eadffea5d55fdc20c607182ffac7128f9152f55c6bb76cc60a0203b347344b3d700b7dc0a27e170b77e09db32d5bfc09207b5e126ab26a7d50f23293a1013c1414013005e5310497c51c30bec27be1f2219d9c54b8444622eab2e3751960ba59efa329fa78a1413c2900fe2a4bc0c9fd33d3ab872c8f20590808d25c75a6a0d85a15c498926fa4712a98bd304cf5baae436237d35ea97ee099965db01f6f18af59d44537bcb0222fd6bb538c30d8d73533d02efb25daf8e3faed00b90f948cfd3788389f1fcfa38c819c010e414ae87231709fc4e462762aecaf8506e2d5b43c984a013f3979076b3a9f3ce7f0875da62b9dbda339f0938e60a7b0d3159c19dfcf5ee17e02ff0dac1c17991655e856e649281a1deeede86d2c3c679a42fc4e7d3c8a7def669787f0e872e219a64b49c6c4c05101c48aa8e53e9608e8bfe8d126db45a5bef2a25ba97cf1657467aeaed3554e9d70ddaeb9222182ce475c49a5dd940d0ece9d5044cd843907f56c02ffe993318c9ff92bba948a88dcb0bac0c7fa0ef757dc67b63d13f4004de08345e78ef13ebd895c84f59033dc05d12e21c885d147eaff538b6b71ab77713e0a24885316ea0ee672a2f94db1683908c9081f6c5058989daf0ed8cf22df1f1e6815c6e58f1a671339a46a52ef24571d2d75c098a60c4767cddc1988d8a60e1f3a1a8df49106511b71d34ec44aeee2510cea68ff8a5b612c4ee00e01077dbe7e64c663a1a53dfd8cb8ee99f462c5824a8d1d59a4b049042124feddd4dc83cf884392d085af94602ca8ff95b313c2ed03488dd9ace860beb6e5a9171ad6fe27b457a27ca18fdc367b9e63c4e6977cb4c7c62912a10c9654587ab35bc2184cbbefd3dae8db46e7beec5465c6ae1adb4cf8e0040eeb1a8e22af6a68ad216470533115fb37833004ab7c47ddf7091ace7964471b3ccdd87308acac50b20479709e16ac5a9aef8784137f80481d5b76512e531439bbdf14202ce24e70e833de396596375ec630e51a6db212248ac01af6c209b0a2603b7574ac10470ba3ae42676ddc86adee6de4ea85428e83a4be28f5afbb68caae068508e5b434abf2d381fdb02d85a680a44e3d638b3dddce36be318394ef910f1ebfa7db53e142b288fb2eab16703b22126cb0c696418bcb48e546c2d2b15877aef80442fb892977a69f995f77483daa071f9a1af0e801f4b95f158da219ec513c845a023a6a557c4d492cbd53ba0751f3c44b4bb29e74c6282c8d55e767e04e72336de674cb55330ec72612717dfe43721c0db05c0dbf0e29b8f01c18519d6d4b2a64cae89879c41071ce1ed7864b38ba1b7b05b0d06f555f85f8b02458ba44ae33bae106abdf3a652c681973c2772a50f9a319059966ed275d31dffcce48c438aa8c1b9350ccec3333c7cf518eb3b66f8b46f2280a334ba74e1b7776e8dd53ae18c7ee4c48352d3780bfe45c733e49b4266d36d3a11595462e4c12e3d1859c83d6fb8883527b9251ce0443a5a2db4bdd68c8d79c8ca5c1dee879e4706e05318ce3955dc5e20fcff1b784add7eba3ca0aaa2b8a79c34ddb836a145cb244f0adf1b910a0c759c6ea2b8c2beda2e62e927216857f74d6a193f0028fa4f978526d6ca24c3e39ac7dc7c1887487249ee5b8c104a089e4d8118ba283c15179a04ce7aa6cfaf54f574a3c1d9218e85cfee774c054ca6d0defaee075070bfa863d79f470282e716c0c9c54a40a235f5e94510b4e4205910275905c95a2750f9ebfcbb24c6590164cbaa58049705c010695d1ddc6157f9d4432ef44cc348dc3f472b43cde94617484d6a3ccb8974d94636ce3b4468e59170e418db7dcbcff5b5b0695461cb1845147968faa49013ccca7d3b519e525fb00721ba7214f10465649a7aaeb4a2d6e0a897d880d870674d8ba7503390ec2ce91b9c39f3e36ece79de2945e7d760f9c5d4caa010a47e083700f921ee2af2f3617e1e995aac64f76e6b9ad55f97604b4c2083da1305732f13d09fbab4521b85363d6d69508e3fc9ba7382a65e9a566f28f2a6ecf510266255570849d15eb4a392d4368e0a4d5042be00287fa7a744efa3471748d4cfe8e1e826efcfc80343e3790defec4017832518aafc198a8a2139e733a0354aae63bb7eacb54dac962b7ae0d513a7e619868d7806f5d96a05765a4650acfc538d0ab6a30f239520d37a6c5b8348b63334bc0e4eba81650dff0b56c8047469edbb36e304bba7b267000b0188f14b782b3d97ac00ea0186c2710bf742a2efc32c0c981fa97900120c5e392faed2366c5f1726088a214e38354b79a5f35568db81e861054cd66144d14d7a612fd0ca32786c8e8cb79d6f62a53a5bde209cd3f912070d786258b81410a1c04574f1b9abeb0fe68c1f38cdd34d50ee682a6f0865dd3916f11ce15111293d5842623b0af41e915a857ae7704b44e6edb300b318f49ebf0e8d0bd5c021f0c7531ae05850660cde6fad0090540ebe023264cd6ad47889a345a01274e216b6eb09bc211de2526dc20d0d33956510787365a88dcccfe876203ff8040fe81ce86552d751beb6260ad5257500a716ad0bb09bda58437c1f9e31b443a435e210f2747e533a4dbc8613c26dc4324c235041f2f45327b99096002bfb354b762e2bd139d010d4508a4f93d7ccddda3514176990cf238ae1c5cd426967e3d7231152e7ca1815971a3d8c7bb3e0b6828c3e6db1d2749eb06828162c0a08f799519f2e0ea7b2e4304cf2204b243abe3a38db3fd085b2357f069843b28a14f30f3883d8c7d6c2aea57abea3bd3ae884f7d5ce2a694114893f3fae44038e5462cd33ec98faf653aa36ead81e076889e2a7e29d4de4667a71f240ce338180768a3c3928256d6ab026d2aa5a94cf4b53f62c87666c412690c730d2f7628a0b20d95b707af009dcbe4080ed81b7ac07112ba067a78e4dc477c98e631721b503cdae9745c128b240c87a8dacd7d4f6269facd7c49deaa732a2feae42e66db8b35acb6a9c9903f90d9977d5d90652381a7eac5159808e71cc6f3dfe5e7fa29d31d297d8868edbc6c4478148c519a862816e85cbcc56fe79f9e260be215c8c021727b5dcb165a60b422cc579af49ddd7589d0c1381617a2b7ef242f800ae518c55a748201975c1207d4be72e3768d088109ba469af207f7ce1bd21c53d93a692184addd9fe91eb2021e5240ff6455313d2881436407c0ac723c7fb7753ba2f395d1cfb9b79479124c345fda1d82fa19f12c08d7903f3a857bd316dc149bc1e6a6033f70a69b969b88f0bd2d8cc1d7a14541e05afffa973181871cfd28a09111e27e985978332cb1edd1ed1a5a0ada114dff0b5822cb971061119f98fe4ec5d84e97e93f0ea6022f2308f9dfe0fa12f38101f14d07b9ab6fe4ea7770d88b527aded7873e02e59f7633c662f26ac78bb4445275c4e52c2cb61f5fd917ed066c91ec959076c06f6544e3dc410fdbe7d45bc581164ad3596efd9c90b12ca7280b280963a11c381f7f09ff994b438df59b1cac088afdad482cc73b5f5b70aea394593a4cce9370334114b475b73583dd3d10224b7b997adc4aff2b7ff9f54da974d11699258b6029ca28f0ce023bf5a9c2a10aa11601dbdaa54c8ffa200411c0ad0560ca7adf145d6fc81d4eb71574dbd905657d1c5287b52612c2bae40c2a63dd484254386b1ec8036ad5b7731b15d05ffb4d6df151f504542bf504ae5ed7dc8642dcfa8ffa04f523846b312f5ba0c913716a31675c913049c3fb3518bab1aaf0bd5f0037611c286a87e2edd221d48cc7273b47b7eda2c6a593c89cf654d7f05416d0c9b9caa953ab30f0ad7c7d63f6375e8b837088e0b3d4c1145d417f112d7249e6026bfd61a5205f5489deebee5e982b8f1e8a76a561a5ac9237f2106d96da116fa104aa47e706459e7dac6d578a32df3fa250b8d54c74ab019453bdca8b8dff693912e06a5b9e9f5d156a104b1da89c80321328b1818c08cd9a14a25cdd8ce4397621069b0baeee4f16b5c42c9c6248c05d945ce8682c9ad74572215943985726b06b40ed6448b3b2c5913d0f9518a1c138f3af53fe55d136da4b22d001233edffb95a37f6c3549f4730be9ed1f49627c350202450b5b422d2d677d3eeb3accadc2e5042901954e6a81a1fd5ab38fd27f0429aa57d42c24ba08905df8d2f144c299507da7c31fd0d84dd63200e0ec69289e4d32b08fc12bf4789f10a66ca3d87f518ec8e096a62dfce5a9dac39c0c07c601d1f3f28a80309a7293517be3a9216ce073010e198ed0816641bb62dc19ed8fd028fd69b131598cd5b91e86be333bf74d73ecd627dc7746b6042e40236252384723024b384c23e243385223a2cf26ee64d262ef97eb7b64b83f49eb7d686255d92891436bf258c23ac31403e3cce61c04c9d27159de8951003d8f9afcc5f36a8d248b2a3aac68dbdc087ef64680e13a8dc41933d79fff4e71f67e232cb942397d23a782b2da229e110c388f9f9c429f01f0c998c99f3dd0604d5bce0bfdeca11149f49e099b2522fa589c1b7c0f2c42d1f6409c0a545770ab53f64f9e7b487cd74ef435ebf9960f489146b6d40840d2a313a630c6c8bc00ae8e36ee8b104a0d52421a77b734af66d6a9ef5520c29fb66c16921d929141371bbd266b90fcf7ba69d20027e659b5699d91a66f512b58181f8034a9187224d85fe578ca9741dba413e33b289c523ed3666ddd3c886b8c92251532a6d3118903c4c8544326a2db7d768c7c8483e99c24952592269a494cd651b552b438b50a9aa0877347da965258623198dd48ef9e3275647dd55271ca4f54c74ffb620af5d6c86ff1bd3b1672da4930cde99c52b80f5b993e2681b64dca5d5de618fbbe4a5d96f61918b612dba8f76a24682e54f606d6cb04b692da83e477af15d0d5aadbbaa8f5377729292c0cfabf4f030e08149d81d5d3dafd4401dcbf3433451ce2a56cddaf8911ce30fb43c366f1a7056dbe7f79d452edad244fb51a0be90ab9b6a05f7ac3e332a3fc7babc48f983dbba005fa3b8120eb1ad08c0c2e1e8df077939fbbbfe175275b37b70d8e2791a7147725ade70d2e13688ab44a8ab321060a2aae7712dc1656d05094ecd4b81d26a12862470b4f4f349cbf927084b2e7f4943087b8481281f95d2983f1e9c16613bb1bb65a692016414d3e5b75adc228a35c6010f84661ac9f3e941f1cecc620cc52652a4b9520510b32d77f30895688d27d1ca33a5c05085eb727555dc1458dc1601e819d44e8106c7cea2f824bac1c918be08ad060af31bae4c91f9a138ca08f56d572739f16a3653ee219d1cc8fa183e257ff537c9ec7f6a40ac49af701f521601004e4c4af668f1daa0431972b1dc9eda601d57b45665ab786ad428b18f41b612c15e2ec7f4296c24a154e64e1c7e79fc6f684289e36619ba664bc1bfe1508c95b79fd558d561f2d78731be863dc385364dcff4861d9f6689d16f4e131684e6178f1df6d9d258ea4d73c93470ea871d6beae631b6446aa49814cbe76521707c2759faaf2548b1b279a4810ba4618b2014034268a19d4345a2929d52159644170af35d4364a0d144e85a7dd35c28646ecd5a2d6dc2908a2e58380678f88c67563053a016556123be69728bbf16b166b2730ab6257fca41a479afbe76fee8cf50e61f72f8d2219d0a6ef062bcd55ffe07e5f83b59e725f661e16c33009c884dcdf30c628073e2f2e8e8af6a3cae6ae2c099fa7a9d89cf5ec99719782dc6b0a8a4490727b95a18688813886735ddf1233ea82bee467965533e34745b85dc0eef7bbc888adcf93fb2bafcd9e196b7dfcc5f7d34c593f5b97691a40d2413b672f2c446ff468be24831ad9c5235d96328047f825b50fdd1ee6e847d1853acfead050b8d6a0fa776caa8ac47edf5240ba00c4997ad4de94d58e0b08a99b10fe2e7f6489d997f919bc3db689aa60e3fab1c16a9b15698b0ed9359fc9f054db65aed01fbd449d780a19bb014b0b2023d3c7272e278c0eaa00689df3f382c13717640991dd5a7a866aa11d408f93782088cbffc9ee1a077c431c87e4546407fbad82774412b75b1e72d8572b6d4f4805f95d6c6431089c24c4fa8f22fca2b59b05af85877764ce994ea05674b84eb057cb47313800de70be7f59efd46e7b6d6e96f960703c6edfb5367ea4f78fd827521adc6593df997f7258cdc3dbd8af922debbb3985d90d8c8eaf36a2b1a8f5d2f1ba3b35d8d3299955389fbeda64599deb43ad5eac8874793d787786f1398a01dd8feeb3f5010143b6106b2630ed707b2f2c3f4859ad8a2632f2966f16d17457436e1da542857907b6d1a1bbb6a456c6372d39363803fcc093b66d15559fe13fc6938e33c8dae1450c26465a90dbe05ace8f94f5ba454901c97ced60558a0a7001acad3e6f487499138234b414ddc1845f5f3f08a0e7b195653e8e1f7882a19c137ccc5788b46504142c9f20edeab3e6580021858120c50147579822556a432bb7794fbb250456856ab668fcd477d33cce7642f393bdc73d5c9a3fdc88bb460feac433778c816b7232e11c1af111a5b4f7cb4f1bf322e6a9baadc34f0144b2bd1cf6200e1c6934d586bd8e1ab56ee8ddf22f82e49849d9bc0a9ac0d53f6a15ee0fdc149eeb1f050327a694f1fa2e1dfdf84ed950b1c54071d512032a9c78a9e7bcde4c4cc0f5ef731a7c34283e61c2594131bfef0ae03f68ca211b35bae01faee7eafeef2a5c303589daf9b5972e4c22640a9f992524b04393084f4b5d19127289e1ca25ae45a5725c029a2a9c8d610d8a943193f5bc4d70b0350f790a02f935d28eb7eba1d961b53a86fce7e4cdc4ba27d2bad64595f4171848d57f012fb4688bb1b27a971a487ae8ffbed70d22c8754609a8ae14a016264a8793deeadc0cee9878ed067b82f3d9c77f867f2c78a18dba947573cb3e4c76d0ce794c755c6bb7611c54734382ba1c3d8b383f3a6baace6d2b3d3ef308d0e211d8ea551f84836eb5b080c134edc6e3d9c0483eb6d20d2e0aefc255c86d581eeba295916d5710fba55511de560ec3c56b91ddb808b305eb23ddb630a6e56a22bfb128d6cbea21b87929a60b5721b661798c8b5646b45949cc9756457a1bbfc84e377e4d99fc4de9721e804676b24e50f7f1fd2b6e62657407a44a6f99211c7f0832a23d65dd5b4eabcc7d76f75ea8dd57981cd9ec15e43209d71379d126e831c372628ff5fe518ff633d94f5b76c2e8f1ff51b4ab7a2a4bec118eff4ed13e5ebfa76eb8a85233f855737fc17ceff98b2a771705ec80dc619fe22eb0fe079db268b5307074d61a61a2d585df7289dcac9d914c56fd69085615ada4b7950fa8b095effbb0b7b7c54d5d2ca3b296474115cfd4ab6edcc15bc5804f24e6df2d477833c6170d5968abde4ba6f110b7e6413e3ac103da08b2b4b37c3e0913acc2aade2107067c18b74c540b4b68c57feeb7cc21ffca23a86d1a884d69f4a5e7ad79803a3b8241e400bd649f525046e1060817cea20b01e02c97ea1a70394e7323eedd947ca579e48592358b09e4f6d4d032006eccec5b0573287e1d93f6a6a2c16c05b4390e5f89381c51b589f369b5ece87c631342d1028017357862ce7eef8330afecd0dde8c8b72336ede30133ce192ea9ad6af2fa5d1f7994302cff9145f9b73977cd4398f5147d646e73d3bdbf9af82d30ace0b338fb19b4cbc13112865af520d9f7efd4f4fd4e78fa9cdc127ff84ecc529e4051c6c8e04f446c34d771877d41d2c9c0571e301d6c98b402e207d585e339ab8b7a3526676b7a36856d0a1308ba945a17501d06f909a5a246afa874826c3d4798e5f34efc7ecdb6a69e71914abd1901127bababe9af558b7fda969011c9433c70830d75e5adaa37cd0090d09a0e02d12e2b00231b0d137617dd2cdb963ba024b36e28da7056c6cae001448271cfc6efa310d75116b50049a892804460b9bf5ececa73ac055dbd271b3e52b7f51dd510a08c8b37e410ec0d56d4843381019ec396e6bd241da0739d889400bcfda8b9a5147e0e617ff2ab32779cd0c850d4ab051c7cd804ca046ecfc78ae87e5b85b88ce4e589229c68c735d2adf097b717c03bf1b56f62b5951fe5dc8620d63dc597c636c9ed25c96815aa2170d580962792d8a1a9406b6f96998ecbd9a0e2ee3681bae3c6dc98dd03826cab043f10007d9ae100bc9ae368295e18cb50945fb1161d14bb4d4ff45a80324a0b2a8b219bbdd00f0dcb76e391a8290c7faa5ccdc3a0e0efceeef827daa1fc2b3ddb284e10ca8329c1f46fd989dc621873b9baa0a1a692ed42654a6f92bd9b14c3c58a2d56e3276541ad1540120afc4879902a44958d81eba7bde56d2ed0259cd44019c3459b23da76b2190a1ca506f6771b68b7228e07880fcf41f942aa8348e0a052789c5e08c9cae225ed6d224e8bd356a8e75a2eccd5f46d0024f795524229d943928c0ba7e177558b062498e07cfd9435fd7b77d7d1b1a5a74ebd3b8d85a01cec24f49b3433efdd48a526efc7b8bf3750649c40782593cfa1acce07926cf49ffcf48a4578e01985b3b190fdf0930e10c8a35ec995fb282b4f42ac674cb5d8a9a1b175222a1e295fbdd5da6b127a83b7057e7466141435027826514af24740898036cd3dd97a6bffa051f636267c07c455533849b08b34f1fcdf4216f2cfec3c01011d478c76ed1848acf25d2c448ba5fcac4de1d7b2b3015ff8e1deaafec6496daca7adbc206d7073afcdd6308f095f52f4458abfc48982abd6bb4d18185c48cb173141a08672fdc6818b0b5995eb3dc3f79bfe106a6e422f2dd43b36b65173c3caec29fbc357e4927ec2d3fd47b0b74edfcb49003fd3308c3d1fef8bfd13f57ade3a5589349f8ebd804f7c7ffd95ec1c4a6cad03f75008d1f4016a9b84caa6c448f3e38e345cffc1df3bc4cf82b42a7a3e5dafa8d87b46b8fabe0a6e07c4bf9018e65feea4aad9a33a67f80659ee6ff91719ac67a6bdff0f19f4f56df17cc274aafd9ff117f83ef457d50ba63fe84619f7c9f5de71977fd7dfa6593764e18033478384e658cdddf8f24225b9ff5a4da8e8dff46b07fc50edc965bb823a0140f32b0330fed57372f9f890fd8f46139dc5639cffcdf505ad4c07282d93ba9df1735c23c209a7c274fb8dd7dc5989cff62cb4ebcebf66904e8cdbf4338f8e85b7dbc4dc1fa486cbcaf0596362e33e7e7226f1eb792176fb97b38ed66090789cb5094b907fa3e7547c41dd3e31504a32a3f2af1f6e1e3fae9869ae51b2a6a1e8a6a9f3035aad11bb378ace4e3ee1635a64b22875c808f7c2ff04021820d4f55136e3032d57abac1759eb9654930edab9b031997f0727ec4e1e22bc089196641b4e3412151d3cc868976eaf997f9aba219792063d8c412e87b373db63170a1e05303092032c89e3738ef62983f6a57544907cf9a4d3a9fbc259b88b8313675e9f55bf88b8a2fa90a6192bd998024cd3aba2fe3526a7173b75e9718db3d759572f532b4b373a3f5cbf24ed0fe1ee1bb2ba51a0b88161d759de596419257bf5dccceb321edf23e95e9a8f299818ac907edefcf2b9b1b10b7862d3662f45e2bd6cccb6a33c344faae5e3104c59e7c6261070080cdba013e8db27a706a05abb493996e0229d8a4f5a61ac374b388b11d68d9cf078204eb363b7bd5f4827c5cd83316ac2986369fe0a88a33db5a43b050f16ccfc2d2be790880004f582a389e8e63d0d5dfdf4e327967bb6f1199d5c450d5c6576d21cb9b5cb5467b9d30b77adcad6683e69177933377961f4c470ce2859d3abef737683fa7c2459845dfb403d49ffc2d2398e466a4aa215795cec02761b71ed9a670032b6891633dfae92f74aa70dd2431ea809000440b284fa98b1a6ae2740012781dd33e691257e6ea5b02a9554d9fedcf73c65578de8266e0e88f189b259908f0597280991dc239879c88b2a7b1a199bd34e539a62857c36014e65004f0d64f328594f58707b6314073c991f82dad19fecbaf471d159d65aa7a667ee996675321bf3d16be98e6b2b0e7a3c15a045f142c2fffd3e8625c9e8c17a6b2ad40466aff5082453ec74fa399fa3fb17d6a95cb90ff50e85b038264002d7419c9412295e228a4703e2bfcef5ba5a675154408ae198da0f561af7f7bbf215d4786adbb4cf2b8ec579d143f3eefb7f3f3a5c7b6ecf82b28ea9edbdbd9f8cfcea66f9554657d1529392d85b0511413d8da13e42958ebfa7772f991caa7311d3c993634ce2ecd9dee35f89ebc6f623a401f7b451748bb703407955e4a3dfae2e4e0aa5415bf6d7eb69359a541b8bc268d233a7fe512047c1ce6de6ef54efe5a2344e13df91429666acf02f2e3853921a172c354cc3fadce9b4aab453d6b70ef34514c51ca74c4c5100a1616755f2747cd34545df5c8c74b9caa5af87e81fd591b488f559605adda23b4f354743b8378e6435e143e51d491adfc7680104e4dd792a6c638a1f51d4001f010745e36f8f5a785ba6b36b29a33a6d32187e2f8b225c80a979ec0d2c281916d115b270619d0dfe35076188cb8ea841b199b7764d87bd69109cbe9ebdccad4c6170332a95e34970e9acf1e29dea6fffba3ec0645a3b866df1170be3bb303a267e19fdffcee35480ff6f109a58917dbd598b0c99d1ecb935250a84c40808d70269e924f79b06b34eb57930f535864e8a358aec458db04f9df7dcf0fefc58bb0aac700decc2f4da6dea340d83ea6e234935425226045403d2ac54c85a174cb1b91bdf9c76213e1d291c274b9a06c43c2d3ec32ed856f22502a5b3eef5f0d24af4f407169abc50f50a78da68bc473e4811bb50d064b0da00f8171c37fffab641eb5ae79faa5f8986854e6fbd2efbb56df570befafa3f8c024dfefb26b870473dd36b7e11abac16b132c5fcfad69f54b57e1583616c6d67e2f7171d606fae64b687d526677339fa49ac198c22eae667d96b59fcb09fbe34aec55e8ff5c378a24c22f497c09978680bb98a10b5639057c48e4deced7063205baba066e11f88f0c5b50f40d3cfc05b70d8d896061b81a62bcfde8f64877c1c912b39bf0c4e21c60f33058990ddee52cb4b1a91bc3689a59a110015f11e8c515af2a4400ac02ea68ef25afa15459d869d8a74df2eb4463dff5b54650426790e11916b8e60522b87ecee03a9e773df92e1f2e34e6231296d7ce386e2f9b0bd1f7aecb659272d640bc25995842eb51a36c0949779d1cc604dc3bc36b5bc54bd3ad97cf13dad09fb61c8fded303c37091e99f554cfb2d107f8e33fa0f2614e8ff4c665b83fc59dc7552b5900e57a69d098b6954ec5bbc8e6861bf33c68d31bd2fd60066a4fc2bdc7819615aa6aed73c1b5ddba870aa77d9b48c6528694304c0db7595828c421c71f27a4689759c0d483fede24a6e4098ee3bc5405fae91bbc97eafddb4c898f9f629d581bab017a34c37ecbda14739c5abad7696c1c66a0d2c2dbc1c62610d0acd868eb80182c4096f431096fc1181f8e8628b1c9f1990a4897a5a24a316730bebdc0d3be93b9dc0aba4c9fe1375d84339527bc6e7786ed2697a6029a10a4f0d6d7224ebc73bd8e88284b7de287bafff6edb4afd013e8340bf502452f958e53fb255df63facfb021908b5fbb1deb1e23600ad116e2716291ab9534fa80b71ac088cac8d464ae837d76e1b2bf7b36449f7bf2b551e10d7341bbdec080c7d1e2e2ef23d2a38d3cbb954422983a8e4601e663e991f37db3cf3e751050c11af2b3e5217907c7f3e0cb919575aa2035c9161ff541438029560b7b4570d3cece0393395cd599e48d37a06e9c6a269350dbbc1ec9012e2de84c87b43affb2014498deb07da02a48f77031dd7233e7fd896f48866d68d864162de8527e7ff9ee7c290b00a678a6e2c0642591f2f1fa2e365e410e86a4d5e69e8315df70c8d388a59c767aa25b69b5d8e19c64b741491e4f23e2bb8a050a4c3e9330b274aa34b376d1b2da884b8489499df08296a4182a6ff02ec877ec6f2af6ddcad755df8aa8e4c9c5c9bc1cd06f44642c8cb501ffab8c20e878b5a7f560d13f132693b2570ca1433391b578606f4dd5e0030122aeef186859ca5791f8c7b5a5a83c10258d309f969c3a07d526dce822969133c100b524a03f9fd48cf850cfe94b4a8a148dd298a0f8866ecdc65fabe4c66b734ad71568472406f391ca439bd4e7919525359fca3c1da3474ace85fb8f41551bb8d0efcd09c08d2bfa87548868c16da4762d872d3c49454254ce9f98b09ec4f4174c310484252403dc739a6d892647f1337b5d58434e5abaae4df3f0db60614766c3b299c5a2d9155986629a2239976bb67e5c635924fcf599670a80d1ea147ce1dadd41cb2367b52c3820b16d3190d57eb132e501d4220d3f31082adda399eec2bcb9cc843f8297f2a5dfe0a2038000c092ad97835ec2ccf0d1a6fb3be41cd522c3369131e95ce6cc2b8fbf9da08dd7b65a5a4820aca36f3361ee825b7fa89e85ab767e51ce811ad340b7722a186a31ccc6091411073d4165d94278179fe1fde8f9aee95ed28a4b0c42d63127cd7ddab72d80b554d08587028633477a53df14441ef3c2cd2ec8870f8b804ffb4070c057662c45f5e7ae16e656db34b889b0846bb036bc100bd70d5f45d6c2fa325deb839c3bab9952a0a7bf175aec3b1c16997df2295d884a42625f1ca1b1c01338aa48c42353181ceabd9e2411236b56849e86c029dba9d7d75f1a0f6888e277d3c882da0e10bb2ef9fd6dcfd9f7e047767d27f477fc1bd5b60d396fd74a566368f694b5ca1b34707cdd47cea10d308b386c3574938133904d5c3e4d7f9e20afd00ed5b9eed4f219c9b3247b795c1ccce936a67b7fabc91715e7ccf0826fb85a0a52e94843d608623d7df4ea9077f5c13102919f2d765a7bd808061c03808b1b51dd8ddbafe2980ac826bd141ff063c70c5164a5798f2046a7ec2146751255af8b5d506dadfb5aa1587188c0b907a14c91e3956031f7c620a23c5c2ce025895328a0cea5751ffa9d7648d98720c0df574830a533a01b1c590811e0db5c0b7ea77f4fd44b699bf7e76c04d96ad1135dea01bab177d233bd0c3857aaba95e00192b867ff054cd9e62ab997823a0841fd9d69d067a710d362ed20d98ef634ee39ce7a57c406b053d1e0ecddf97ae452c0781cf2b673a264300eb93deb956eff30d9a9a27a2282cdb3783a8ba5f0764b51717cad5463f6bc7906b1d7fe88446c4f01d043fd722c2d9b35b0bac942b53d38fb86e367b8aadec1cab6e38d8b9d9870b54e58885238b17f4c15e16ee2a39c85566c4cca61011df4bce2e472c8d911918601013cfba2baa1456e6bb3009cf5bdc1e6fbc010f6977a1546246fec9cb86daf49a4c9e27228616894edbf49720044dbf031cd1209c1529cbf65fd17ac4a545e2a84f536905a9c3c94b899e59db7bd64318fefb6ba36e580fa6e6273e00c6db83704ca64e742a444a4a6ae89003239310d7e33f8b168e191224ccdad34566d24c10c5b1f4569f98ff836ed667249ce12627a3312cf18b7ea47c35318e1bb27c655bbb4501fc699252213840672aa53aeae15fd04f3216eb6278e7762ee21edaadd8285a0a50e6b5ea48edc55eb2b7fc2694e53e54537d46c3f9387a3d3ea64b3af12a04c9e746653f56d40330f0e14de211ba892174bdfac5661d45d5dcb455a5deed1a35cd7101f1ba9f9aa2c23fefb99848b165e51175246536a29bce56c34b98d38d64f98d9fc7359cbc61c8aa97574f384a762dbc0448269a29064652b00a0d017dd444d1a584684c64897ed5adbe88409614869bfb00d3511d7bea49e7be27d3b93b93bfddd3ac83e6d6d10bc5bda0a080d1bada78ff932206e73840e98a554be66a9ea1513c080e8d3512a9c958d410348d4308c33bd7787b3708dc1016382d3f0cc85137e84c57c1fffa93b156d06b3eb3fb1cdbf97a54d537e41164c011cda65e4464d803666f071800e62a58d4941a2eea85c196194f4f9ca69e8c72f3d84b11f96f0019903433d0c552b6aa035a3ec79c1fc82273b7cb419fdcd96d63c055c7f4e71c48374a552bcbef5da795ea1203289b1d1731c212f4c94f976df98d3ae0f83202c682dc53b114679308163a584b4a57daeaf15bcff21cabbb9183381202182dfbc07168dec49905bc2a683a6b583d002157a3d5ffca3814d84b8baffad0c77123af596b50a2408a7a91275807bd96ebcf6774e2105d9bf226abdee11468110fab3c17ce37eccd08f984b184c1b52b3306dedffbbd144968ea57cd7a8473daf0dc967ec1d4b61f8f2f08c4865116ea41f60abacb6526ed25f5637f11e223e28f77165d370077efae6720b3cb9f0b86fb7c52f33de0ecc9627f4336ab87bfe524363e4900bc4021b30f4633c5f546d740d07f04524a42c8d5138bcd3222208b6e8349af4966b427ce46b82e9718201b914ca19623d11a7bb26b7c0fccd92e0df2046faa4e54429870c36673d8a0f8d6a8fafca0b4cef67ac79b467b8e229c8e5fedea2ff120d447c0717000bc92628a80a8c5e7275c92daf615562d2c304f5193ddb079de14e3d61e02f6ecaad1f367b6a745567361992757927cea022e1f39e4cb8de711a7ade97603758606d99e20d382be754643ffd90ebafaa1430f8dead3da5f49e4ec7ccc36fa98d0aaa7106ea6882f25da0a2ccb04be5845f9114c1028b2b66d0b0b82aee8298920b2e149aa59abfc4b2a156081412a808f2ce90711d12881759be07e5cd9edf5a1298a856016d019169b918d64365ad9ed956deb53a70baf57f16225fa81dfa6bd124f1581105eeaa5d96461a3b139e1ff3757b1f228aad860e49f876cc4c1c60430d528822e3646363c36b0bc8a953f2f046243c3526c34a9640d0cfc7f142c85c252247ca0c777d6105a03cd1a40582a8e323ad31a4c7856a347e685d0fc7f94ff2d6f52638d18a2b8178e2b0c6990d5a981e5ffc5af08fc5c5043843412790c1c313144a54194864f1a5dd24099d228e151d898a95c99826158134d40c1c8dfa071c7ffe8698fe532e2d050a247d411fa3c9697432034ba3c181627e25d5c20101affff62ce30434ca9543e5f7ac2fc7063549c169d9f2f406154aa1409263c07fd4d6738f9174356d6e08b016f85110230636b2889925358190c3da28f9b51862065cc21862855194960a2985d62880ab91786b9339511011992907188898c399e7b29d11bca402632942083e7d5d302c72fafc0a854580ae8111c3ee791ee731ed9f2398fec7cce23a8cf79e4032626333a6f3223df6426843799b1e04d651e79539942de54c68d379581c09bca30f1a6323bbca98cf8a632416f2ad3e54d655a785399ec4d653a78539926399f63c1071b38d920c78bd29bbae0c09bba18e44d1aa0dea481096fd28082373991e44d4e1c79931341dee4e4026f7272c79b9cb441c1298782085cf0395a5cf2395a28f2395af8f1395ad8f1395abcf1395aa0f1395a64f1395a38f1395a04f1395a187d8e16429fa34599cfd1620b064d4e181ca2e6730e11fa9c43cc7cce215e3ee7902b9f7308fd9c4340f89c4330f81c432af91c4310f91c43f0f81c43d8c0608393063fac79d30f3bbce987d79b7e88e14d3ffc30d90090097c0e20567c0e2007f81c4086f81c4076f81c408c3e0790fd3980bcf03980f07c0e20f6730031e17300e1e07300c9f91c406e3ea78d249fd38691cf6973c8e7b4f1e373dadcf1396d2af0396d28f0396db4f89c36547c4e1b273ea78d119fd36687cf69a3e673dab44e135c9041930922980003092c68928797cfc9a3cae7e4917d4e1e247c4e1e177c0e1e8d7c0e1e7a7c0e1e6cfebf820a32780fa214e04d5180785314ef4d51bebc290a7e5314faa62829bc294a086f8a32c19ba058f226288fbc098a1f6f825281374121e34d50b87813942ade04e5006f8282c49ba0ecf0262836bc090a7f1394306f82e2c29ba0b4f0262815030f20c860021305165cc04193a7400449c018fa92134dfc9798f82f19a05480ff1201fe4b4afc9706b066cd1a01ac4962cd1a12046b48376bd6207184116bd6ac5983d3d2462423d614b1660d116bd60c214429883552a4f0ff6c6c5032438717405354f02207832ea8e8e962f5637411001eb890c0ffe73163f9d1c963c6c262fd135c70f0ff2512b6e0e349502ce026842d58da034c59b490cd498bf2cfee31e2fbcb3fc60557f602f44fba02841a4fe4a1211070f2ff432cd7d0ff8f1f27da1e3886e701288c5005cf03387840045840828506b06883051d58bcf1ff5aec5f8f4e0e5f3a3a2d5d66c7061bfe4b35fc97685073f45f322afa2f11558104066998f87f521539fc7f1e8b68d16302456a4cf2ffa5ceb3023622c80010f9ff1b33ae1f2bf070728923ff8f4385140e009341186ceeff935810000c5f9008e20062fc3f6988444688124250c58aff2f75e1582064881486f0c0ffdf6870c41c1b58c20062d4f1ff4c9a204024a43b5296b4f0ffa5412049a2064a9610e4fe3f8e13411130b17a43863ffe9fc42d702394628f31f4f87fd21ddd54034d1133f43105e505151434e6f10562fbf3d2048941306052bca03c725498201e823c288e42792e1d162bf3a0c697eb0be23c4bd0cb35be7807fbe7a178c73c97ce6a140247958af37e14b246a397e7d2e1fa43f115102aa8831fd7d2bf1e9e1f950a15821d0b0acae14b8777de6acce16bcca210c8ca181fb39431045722dfb925965002167a2fa129620f189daf07431d812eac078c0e90eacb1ad5595c1c3d8c8b9e988b44504ad098312b56b0202fb33e50b3c491e5bdba2962c678ebb58538cb43857cec4242192808f5650f95c5a020f10b5fe21766ce0241d71048c48dc69087a3ce211f47d62b4bc91f180c150a81ac2d7a524496977b0862e3cb85ed2ff31d82475fc63e509441a54205056179ece1d8feb2377e3908e3c13c974e0e5f3aa81cbe32068a22d7fcc37ac0e860aa116489286ccc528e58397ce9609e2b63413b431a64ed0489a8cf1b8fc24c8412b94b07f35c3a64bea03c57c6f8500816a942b0f70e6228cf9551dc753406002bcaa178c487502eae8b0e4f184cfcc1b020dcc7575087ca1e11b6b908ba8c3876c487b898b5609b73d186ff2ff36bde28fb274170030987920e3faffb2fe556fe488afc9782545f8a214d098dc879e744212fc1c0caad2f1771224fe8bf74c64c99fd79432532a531623ee41e698212d03fc92b85f929bdb0bdecfac4648fa5a33d17c8c3f00d7a1139cf32f2309cd5653ea4c1310459590c3991f789093367f18c9abb8cb8f0b543b53174cc1ca34aa58e008fd8a52e3825174a5c2088c364c4201ff2d1d3311961d4a627024b8c90e0ffa340492208531157fcf311fcc214d14311658a98520407ff44443208115efc7fe0474407449886a0840e91c310744c4b97090a6ae932e3104f5c3c74094189108208c185144f881ade24041493101f8422538248230809fc3fd6d265b8ce13e36abcd7886553104888477c2808feffa6204cff8f0a0016b47bb8eb08e888bb1034048aaa0f0cc28a46171fd29c08e31bebe0111f125318377f792bde61429fc73dedb5c00fd45c047d8eb80bfb070efe1f13fabe091ff8f8f7810bd187285ee41e2a101f7048d85c8df78394b634f9ef210794d0e7e53087afdc03971ea0fc4b898287438282c090ab113d226f877c8b39f020c1835282422e94bfec05f419834442498bce63879772e400a403174f42a603133a3c31e9e02487397ce51c8ec8219126c1811231448d469c892b982b6b5511ffe2d0f97951f92f8d70431a429f97e5150261297da5cc248918ca30c9bcee61ac8f0914d4f8f1228cb7c030184aec5ee24a7f39dc61d280d38d08388f1cf12115092511fe4b21348900ccd19737f88de28bcb7f0984ffd207a50e2ab9a28635461c2be250d4f079accf1b038689864750472cad83e697f072385261652f1c31be02b1aef50a359631916ff05381e3e7a97600435c8d61c63811d885600fdfa0cf0e16ee8540d8fe722ad5b55e21cf02585eee41e3aba51ac5cce24429f022354ec41015825cc0d17394c33fabebbcafc4c17f69035225a5398ea0fc632934de6484e68fbe9c41d65187f1f0681cf3e6afa0546af3afe80bdf429a93296950094901598abab080f0769ec449654ae0a10bdbe06ae42deca893a274818948ff7f2a95fa2f39f92f65f0ff3827473800c88de14daf25264a4a949ca239d11732c2f13c3c2987332202fec7a0ff0ff45c99afb094d846ac800c543c96e26abc3060c410158e1132f1a8cd31ee02b5887d5e18cd89544399170d654e06cb360c75a319d22899fe4b39ff250cfe4b17fc972cf82f5550a2e0bf34c17f49829c5293ff52041fc57f8949024c333879fd7151e42a1247853904bbe849914002092498e0cb3b186ae44763e8ed306320a6930168084afcb80bf4728805015a84e18502c02e628812b304137c20f68147d8a7930a527da0a7c61b7bf441f20191e7e166792bbd88a9d5c2bfc88536efa104818b8fe250efa00b042265613d8112b10fdc397ce94861759ef871918be04a63e10b3483a1c430bb5c19db2aa8207a612eca18dfc15022d683611fe87a6141fc158e4118383a000b9cffd2cd7fa9f45f225df24fb2845409181e161f1fd2b9033fee612b2b564ebcb712571470f1854c424afcbf5adde65e38a600c5ca3f8992ff274d72a58bb1f906351fc12f73e5516c796357a2affe7bf61f093862bc0c2cb53f2ef21eb896c752a118a2547b0d38625fa2e1028698f423623851d9946e9320b979a4044738d008238b6c40032445d4b0424ae48c09c041c793e630e5b124812943792c51744a7ca80492562530ffefc29b547b3c2605f4e951150003853c1014443ae49f64c83fa9907f5206fe49849004f92705426af34ffae39fe407898f7f1206fe4917f82759e09fb4c73f498f7f521eff243cfe4977fc93ecf827d5f14fa2e39f34c73f498e7f521cffa40afc93e0f827bdf14f72e39fd4c63f89cd3f898d7fd21a6a04bd4e8328869c47759e95290d1762885a21cd12ff2b31af324fe3c1eee95fcf50980fdc29e23e45dc676f9d22ee8326041836f00f6482410af17b8554ae6046b9853a0ac2c48fafc8e4cd8dbc2c186ad43c0cd30879fd13396b7b9b87de9198bb60a823d651e7891e91117fb582443108438951a41c8d50c4cd3f30e42e4c8aa7c59efd813b2d30ccf2338a5e1710680c7908f6e8803e23e8237a2c8e8c1b6390c9e27310e79d4ad018822e23ee6dde6a794141118c89448231628c63f6bfe85da9f279ff0fc19bc6741963c2ff9b808d19c35a5c14731891877beba85e9b7f625c99af5262aa8891422a91d03883740314462a0534c90b200069518561644413a2f9d074341d1a34684a40d3848426282614987ea8783144855cfc29c34d3f26fc7c806152542fe0f0c2eb05d60b31bc40e60530270ac59fe89a3f51d7ffe770f0272d41fc494b0c7fd202e54f5a487fca52c89fb2b0f1a72c0ff85396a23f6519f3a72c3a01f814b0abc19a67d2410459bcf1c5430009203742a62f81ec8f8f222f9ac25bde880a3a21019f80277541a4232a2d44f9ff346ff281e3ffb144655346100ca7702f1c4d5eca90e2e89f948028ba6cd145897fac0a86a1386b7baa0fc442b07fe922f4433a7718d8bf9e1f2c9c67193f1ee667e71da3ec0535a6090a8201fb3cbe7b07a71ce52130cb32090503573c186a7c8147b9c5f272178f58ac57cb133bd10b4f196fc450da6be5ef6883622e1abb29990affc0b0d25a6d11180a6162eb45064305056141435f82beecb1f26b06b0a807e31dd6418cb358b9254ae153424e048e01c1607ccc20eb28bbf290c87b10f7c5ffb35819e31895ed4ddcf8ff42e2176ecb202bf1e5a292c2973516365894f8f0d5312a54b00861014385c57a752c188f2122cf17cf595dfec4f0acd9fc7b75206bc7c4b3850a4fceffdf98b95d7e0709b07fde4e99ff29d8e7a9014d3b50fe9f0b6557d7a1e45f47902741c7c80b4f190c3bea44209d2eff2d6d840214a14fd5d2462dc0d10204501f8861a83c06041372220c1c370b04bd2211e345230ff3e7612b9d3526664ce444210f83b1e0e4ff15b9c2e60a15586a7c81578ab862f45256d046a9d407b65aa056895614792cd502c32c56f6f8dd63650a2cd5257b63c41015f2d1957756f52a94fc5771234c98918baa546af32a413f6915edb1149137661f51470c51262a397c6a144314e889aa14074ca93dbca1908baf90bb5e61449d1d15be84d4f8ff190b41cc0353aa051418284ca97f2c0d306d69a0b6d779e1145b0f2308864bfc7f00deb46d79226f85cd82476d2e8a2c56e651a144168b44a1f87fa3b1c5f5e31c51324cd7e89f85201282b0cf13fac0b008b37bd83922306252b0540b0cbbca64856c8f65520f0157452317c5f1059aea1462880ac110fc3c95eacb3a95d224d116d1f6c8ab222ca58da16d2101a259f02f1ef1214c62a31157e5f0954df38b69b230a4b911d090e6ab3104bdb8725864924f3c9672e5b0884b1f5ecc6044ceb37c628e429697b300897c280371f1e7681fedccc3e8aca0c60a0378717f5c44fda0421063e50e6469c1509b8719431d7d52b8909873c8a560c15045e03734e4b1781186ea20d6e21a6cb158bc08133326450c5f212862e20bf4c24cd4258b5fefc1501dc4505158acec617273972b875ee86517162548c4886c9d1877017b94f8813c23e8c345315fcf9006bb8cdccb08fa889c670933823ee290068bc02ee3ebcb0e03fae49dbcd323f21f9ea3d1d5e238abf37a46d0070b6775f91393657f3c8c989636823281090ace8b7cf3a1c5a4c00576421714b4a4b06a6923d5feb26a84143040e1122c25625254e07e151fc10f851754e03e4110d30916309d3087e98433c2309dc0c53f8649c18cb63785f38e4991826152b051e41b93824d390127abb2d85e498c0f282e822c8d198d227f09612e1064617c359291600294886163ce22c10428f1e32e570bd439ccad95fe78f7758c95bd6c264d0f456097b12312100b849207ff8e90a43c89e7494cfefbf877c384f17f120f3b80a3e7040a2860196685046ce462ef200417051450c0ac5cc1f6c71f278cc7c1020788c7e1c1d9791c1d1ccd84311182e20a62889a321af1548a7fde4ae7968a8f997be188618ff1d11343d4f74aa58cb211163002128fa58282466e1a61fc1b9c26a7196efee4fac09f5c88fcc935c84bc0a494c53f298b2a388c7c487be33fe9061b4835889c771ad41cfd938cf43fa9e89f44348a7cff935edd3f69fc2789e10c2ef06bb1866420793851a0fc7f0affa54b40d8ffef71d3078afcf30f14327d50c6a78ed4e42e1f24f1c118a5a5cbf031eb0fe4c146f083827d802595dadcf4c11321af4b6dee81121eb014f9ff29260fd6782cf582170c1c37a7260f380f3af87f51486c6152bc74e0c4ff83b9957744a02351e442194c185197f9e142190cf7050c07833cea881524a652e20b3c3a62714f88b3c4fdf110ec513c28ac3c6e7e24e5f3c4537a69f5983971f9e2ff73c857d05e78e232e64f5c7afec465fb1317d49fb840f9131710fec425e74f5c70fed413c99f7a0cf9ff08722ad89c05fa6c16b8e5e36a8a4016df39627d5fc470ff3738b0f20d3736bc08fad4b0f3cecdeb8f7217d0e767f45a5e97ff1b1ad4fccd11ceff8dd14dd1ff67716b2e0c07823f5cf4c93baed710287ab90562afee25f2a2eeed2c9b7f45d925f2a3ccc258798b61651e868760b6ce8967007fe2f9f9138fe9ff37782a8fc206db69e7037fdae1e24f3b477fda39f3a79dec4f3b13fc4927913fe9dcf1271d2cfea433fe4907e84fb48c3fd12ffe4421f0ff1748d013e668f4c688017dc29d7776de11f98fc8adc6d0dbd9e1ac3c86e1e16b7c6519bdef25e489a84dc5c35062c6baccca2b23be1a43afc332f67558142898c8793ffa7afe6f5e23962f3c3e9ceae7cbcf0fa7e2ba88c1f2c58baa8c8e97546acc42d90586ae2e4457843210988f0f8140acd706bde02b2d3ef60881acddc33f4fe8e358a0e149ff4fa21f5271a50c31441d752a51ccae1b305f3e7f5ecb0bb5e86db125e6a2f00aebffa60a95ff312e882a561e731f02454c4a1ef3165de2e6ac3ce62cff24bafd67c1f27f83efbf6805849dc5ad792ac5c3df1e38e64b95321e85429daa80b9c926dd3b048b443183598dd9c8ebbcf5126fa648f91f732b04b3e02a86d97907572ee69d9d77be54860aa522ffffa3a050282818f7734a653cfcff349ae5cef4845161e1cef474d1e9f9e1c2a8b81f31403a3e5a7ac2685b9affbf4101735846901546b3afff9b139e8c202bcc8d42e572e5546a93b5ce504ca5542e4bc73f9ef2f5e564bd0e4fa9122b1f11583955ed9ff5ff2788214a5485dc55a1fc3faa62ff8fa71413459322fe7f4ba5302c043b5544abe1b532ff27ed8457b5c050e51275ce1ced88f1b07c3ae14b6bf0a60412fe5961c86990c5d2c1fe6f46b811e112096e1840fad295120a243c9e24c71ba4351290812086a89d79f8a7d30aa087e615f6d737762f520738704820243fa688719a8280294884f0a729abff67b15e1f96e2aea3710a9629a939458a142950a64830e5a328d27ab1408d61fcf33e50ccad1668c457426018be44fe81ac28fdf3a6ac10002c04319fec838da010148c8fdee7754ce423c8878ef850942c32cb5e68a6645996bdd040c158a0d097c3a0126c4823252815a4b90c1d887d39ccae2c5fb821fc88f241390ab32acc2bcde2963099208a22a72848dc847003c2ffcd07ff371edc748003207038e243226ca2c0e10a1a46a872c54871206243881b26201105879dd40bdf0e160003070b54f860109c2390b8410f2041409823bcc00d5254f1854b0b30047243ab0f17aaf801dcdc7007a9b2e65f4fdc700342251b131ec8c28638946083260a115d6c304011932c48b032810d5a00436578b004151b5600734547a291486a90c482315f888fb051431a1358b1061a2ae01a906812c4901c401fa8c14c0376b2700044076a78e2431c0850c3444503234314683109ee42031858fee021004564a1c1879b0c9482b052070d60e448a4a696c0000d1e648023c10366625043880f381f48d182831a2daeadc104165743c3104204612c40355924a092c3143244a83145414917e20ad7111f1a1b1314a9f172c40024a0a0220036568e420bbaac404514f2a84a2019e030d21d8e22f860070e341d2019d5514375718941c8e8000358b34515d91446420e48450172e40ba3a9013624e49c214991253d2d688520004d119b141368287243164503e89c3c31c644a14875033465121e5c4550e4201a628527ce10396281004841840d1a109161c61f32a8c96110052144c806d8f8ac400414468432515c218208dbb9018b2e96e4f142e4a692409cb8d1c88b0b392cf1296a52c62b0706700a04d0127a7151c01d7f808100052f0dbef4818348469511900ab498675ca2472bb4e880081d0640c0f8aa230e31a31a0e469dd70c7d74808d939182454028001906fc20e65146102b8e7e70c404b4f1a20436c63944561363e084403a8836906c0e18882a20967c8a106b50511056800b0d897c405a112ef1b51184128f041176644490009e0edc114eb12474028a46c2cce0014bce00400745f031031a53f3438d1e82cc6084176054928499a119ca086005a9e364cc0c2650718219df87c9b588083aa12971886b025150d0008d2a805c3d60896079620d8f0b8c0762e6b06193c3d5011a62e4c0060d0ee0204f14a0022d00c00166610337c3939315504dd7a40b263c2801721fa454187a8208cc0944890d886183940f033824d8f081fcc737c513852c31890e387c3370f9f9831221263e2a558840a784477c4c48d0e20c21cea8a045071746542a71b85a4f0001200c01089281567780156dec7c49a435491893724234815549c9042422f9e2b2d8d00e69610d49d660090009309ac06cd0c14ac3451a348ca86007560a68dc6029b052871c29bd1103b85518a2800c7e548203932a86d688c141cea13086c21419e0112863b2a1114e30e3070630d24506431221611f8a9840862d16d1a6dc053491010742b02d66988832f4f4d0811d6a868e90c189f7801d36632af0da000db18a024b1e5e15d81f165420d5883716c2811e7a7467bc16be6873c5153e10f0263043480d165cb458e121e4c32463e0716695801110d0598265cb6a88bb712b4959b0aa893491e286127aac486bec20d3a4280321382e10a191063c4944a8003344e08c22210421941f3102022670b943480a1836100201961f7a0726590211190e80fb19dd1f1b08230b497a11325c32884a87233a993570781181382ae825d8914724475060856f60040a9cb9c128f32f3ae01a48490ce13c68e921e7113614e13e7bd8e103243ae11c60f1da83e7089e2dc8181e68632bb01b020530d3c56833661f714e7eb22d8c3696cb333a72c61a3b474af540941514da026dc694d969c112bd802d8cb2382134b5eb88010c41c00733740a871f8e1029e4d0104c2a2e22864cd1cd014918d522580f9d1325887009217f47c7b92084891ede019d4cd3839a391e883c091b3b3cb08556415e23c523e5083479e424d8d43158b0c128a3118340bd31c923198533ee3801a4048d200e54a21ecc2c510a1aa3043229288d1c2008089f2b5040a4a045d0cf14638680e2e791a010f020c3bd21010da80ae9e38f2109548da8b6d8809045525ba4a0ba8108057090269150b5c5b5c3d12062c0a0caa0888a02530008c4f0c716310028840c0e3138804915461338c111c3088198294118c823861670b03cf1528315314810458b4920f994e68e1cc8e02e3c78a589a25f3125cd03d248238395365b0830482b8d668917267e802193e6df70d5e1827884c68d3f3c40e4f403031a02e0b0250c1d0cd48026cf280810474f8626003d682508428517183a7083c6102f302b3098f104973069646009188898c233012a131001c318219e5062052a0a80a1842636f094a16487338a5ca083008629083823010d3eb8353cb9e1cc0e455b24c9429867bc0cd100483a60f3e5cc0652289286932858301388216378557400879907e8ac8001d111589831d24e982292b011333c681049640a1433cc5c3089c9688b4e2db3079b0bac1185a6421905d81108ce214a1865c009788ffc8e1a65b64c02125f87cae078e142a469162133c79528909ca105116498a80483450e60481b32db0d11850d349003992c01907c91c1933ee6034f8861e68b9495316a70c9b4aca1842563926882021475e4bc31068616acc0c183cf69cc09705862a5ee718198465c6fec0064c08298300ab1c20e2311dc10f3c3cf227c04712085981730e00800b6c862881810062082105fcc200265800f0e3e491e410314811e2c4e50020d1b806cd879801440408102d2b21aa28badcb094027302448c3864b84307d74e0d5edb1850ed38048d21882059a2f61c4291b68808c0d1584b9f200221ec0818c4598264328800c283e4f7eec90e2103e813d42f88162080309a29861e3c72b216c81f180ed47fbc0080aa01a61e3e70140c11b65cc60bdd086187f58614655f382124b7430465381cd0b41468098d010d37a410518485e40208606309068d0442438583a3065f8a1c100ec013e002304258bbcc0c8242a30629638010329ac80031812380904e203194c7c49440a32e6c0e31218be78f1011c0d303101802f3a40200f452e7062fad2a5cb1019f44ca08b2f00508014af3b9448c227101074f0401f3c0df0b9e2031e1104d82af029428191471f6c58f1d919828d158340b17d2cb862cc923200145ef418230a1d6cd474e0458a4ac2b8609b448b97af4b4390232209bde02f3787b02009245e6e420f889e93e8224708af295c4f7cd0c50083c41082235968d0451f32c820673882a5cb0a7c24e0c3f9a2e58224367869e40041645c48635f393388970c5c40028c0c048c1507b870a6834364e880121d179e0002c41a276dc4c185112e58ac3ebe2b5cc000438b1302d89071f121d741d44725657079e1830bd4b86137c1e583186e68e246ea881e4208915124808dd8a3450f296cc14514687a6a6032852240748d9e2c43126084070d027a4c8bac61460e1bba6ce123027974800336475b180089511a6da2c85bc2341fb81e7450c8162b3a4a2368a099624b043b00f1c8135c42d052471c76a800800468a105011b7cc143c6fb40cbea24891c4798205ae84e0ed9f0b243164be67002913d6ab8c9c28601829490a58d27b20ce08b2eba80e0db208baa8845d634c9e249962857cc6c51108108f7886cf3810ce8410257460a85ac30c249072e080bcc3088253d9438202c1e91c0bca91c36840a52a09492c0828811cf91050ec005962ea694d800844b07b0e4c0881c4cbce021002c2e7830481e812c02068b061008c1a80b225ae001a4cc20414958791e2bcc6809f9b0791e228d8c0f7b44f13c3a681cdd36393c4f054390a0a0fdefe421461a2220f1bf2345f604212dfcefb43c50428abb85c890ffdf2945a1c22a5001be841e3de8d31c3aa81e7288440634d61cd182996fe1c9b3c0c8a4af1158ff989411d0fc69842a41df8490ff4ff32711285037be43ce0d70122187ff9308ab073a89b0a5f5faf849842aefcaad9308524e2238f92fe2df490445fe4f2194398570c763a710be789dc3bcd2a7100c1084855c888f9f770a61e814421714847d99b340b03bca61984f2090f17f838105ef05091040f800e8c33f73fac000ab31bb56381a6f2eb8b140fcc1566376612c964e2af56f42584619659481731b4002045b9c3cc84e1c0c72e2204c9a4937c0c2d6b9e5b4c1b8c1f8ffa25136c46903281ee8526d6ee475ed8dbcb51abd30af70da00e7ff65c0030791c9881b300460bb4a59c2a0810376686025e88d3cfa50e38495422069a10c251040481523ae082730848c03a85145933416c1c14b05175884028e5cf91191e0c34a6800223ca0b132a4b8dae081ab3c864c395a63001ad962064b046a8865403083142db280a4831b8215521c4da8c18027c8b0f009b1c3255d82bca8a1e3001210682ab018193031059a282c902175650455133c5849ccb863043df22064e8f8c0004e907281cb494ef603217cc871830306091a4458fc20c4ca974b8cd823ca1b2b9c50e5032512a046070538faf8b9b102354646c10c1548a54e1ae09313362727588c21d8e980b144a9d4ff959598c1ece8ef48d4115b429fa7bf23311f6516df5f11b85920a855add7b7c18fb35447a16af3495fabd1c5f76905fada3da994e9052c4e395ffc09a79c2ea79c7acab15205c3524141d8286617868de077c2600d8ba5f37f03c1dfe0dcdc7c4b97b9c2fa748ebac73d3e86b9a0e74f1674395950c64de9268252044c989c2a08d2395590a3e54f1494f1281696f9e8e99c2858f3ff28f1d59d28e0b2277d9d28b8e0ffc5fc698209a0c48c49e9c1504722177536e7e26af4c84c702568628e193feee94f478ee13f4d10519d9aac3935493d6a14b990d04b94c2c5bcf23935b9f927e22278fd90985b975c3224e691e58ddf6b48cca14e93c92b10d4d1e2630fd813827af78069f1b187953fd01387c40cc40241309cb57b54ad304058f0942f2d7a5e38cf7223c9bfe9e6e5b9b22a9b280d954d11025941e2f752110d05617c357ae194233ef45ffac07f4992ff52241e38732a295194432a51d994af080cbb959857a7920b5b8979cb9f4a3b4c2aa5f906f5a4af2baa90bb8634c8aa62945b57267d5531caadd35f2266d67371fa26ef51095285a02a28ec4009927fb07f3d625e81e161968f0f81e36bcca318a25899952d39f38f42a1b011e42c91c589522c4ec459aad44acc2b4b9880e098e262c889788885a8959857e20634f05f894e255378d260f12883e1bcabc4234a86d82cee99f9c4e41db07f3d5ec4a30c06f411b9190abe4c1244e5038c7caa908f9e4a7397b882e6441fb8e3ff254123490b1f852912ec81a2ffcfcbab2b57aea452dc0b47954bfc40954a486c7dafac1a3f2e4efa820482312e4cfabac237c873a58ae663542abe415dc43f95b87b78c8b770d087c57a65c963de1c98a274812f59608fcf63c662e58b05829d4a1cfab27b267d355285111918c9b2089845d82c12c206b0060ca0c8218a78ff4928d2e47f911d940213a644705ee56a852b4458ff5f9ec40535a4730786a00f9343b8281d62f4433a04cbff7f6688212317b9ffd21dff253b6af82fd5f15fa2630e394a7114a208bf1245687b61a703a58ae663547a86966a86d6c6a4ec9e1c663055c617a872b5c295cad552b95aaf495f4262cb9481179e01842ce0ff7fde44481e24881f38821fd8fa71a30d423810248a17a407419a047204c81c8010d1a68cd21aff25359ca4f15f42e38c3fae30fd31c49bfef0e14d7fe4f0ff4c7432df92c30ce69fd4fd0b71ae56b885a7c7d50ab78c202b0c2befcc7ffe4b6590f147e1f8713bf31f2f2308fe8451a952299e1f0d4c91011f78b020c52184b4418505aac0a001131c9a743181a90b3992c0c25053fa1cf102041f04f9e2040c7ce8c204094858300970002eb898c2010e0ac0411c783800cb0d75000961c50f267dd4c00725260bf460da83cd1e388226f478630426ff4df4c0ab37e5c18688c7527980792c950708586a102c85c715580a8ff11f4be181e5b1141e39ff58ea8e3db0d41d4ffc63a93b56584a758714d08b044bd9510696b203e8ffedc0b0d47e531d5cd451039632d5c1011d6de8a0630b1d4e1e4bcd71c53f29037300c9a1084a0cf9c85be087a576de793970d068e159ac179938c6080a7a1d06c90830c64ca0f485042ca0a60a601105cac75998952ad8f65a51a04480b492c03f1bff252fbaf837e080430c515c6cf15d74807cbe9ce9f9f9b265fc40355e08ae82bc88f151715a74c284e1bca8b0fce8900123465546c74b10e7fd0d40dea8e38d32dee0024be5964af4302cd579a81aca6f44f158eae7cbcf0f87914002096fc0f0c699375a5085aa155678a3c93f6fbdbeac553c7c891f77631037e0f87fce52ad545ccc60911b59fc833e6eece0c60c2856de9f3784e1ff770373e3db98038eff1f73876146d94b1b54fcb751c3ff07f5bc46166b3cd1460bff6d3c51b5f198152b56d818c2268d2f02599df502c9087d01b77659c6d9d4369ae515155cc6956e7e7795946f7763520338ed6a9de59d595679ef775b198e222531809b956a95f38eeb6675a6f3284ec1699d55ad665ce20d04bb057038eb99b67a9b59b7e1cbbbc7caa402b8db71d574dbab7559eea616a5e0343c29ddbdf3deb1bd1aad09e0b44ab5d53a9bb5e67b33ae45c169d556ab7395566ef86a4da1e028ef28e5da669ea5dc349f08e0a4e4bc5f7ea5e4da866d9d07e06cb67d629a6d93f29b59bdba52ca4b7a82d3aca6d6e68ecbae65cfb4e804d7c99df3d69a4f8da7969336c1719e6bb633aa55d9b5aeddc904d7d96dee3dbbc63b4da7c62a959d06e0e8cd329debb5f7decd62b9ba72090e6b9cf7cc5a7c7957e5365dae5701389aabe4b3ebbe75186323be52a92401b86a73ed9c576cafd497d2d5957c646525382d25df36dd342dd36edfe40038da4d2dcfc9b39ae746e9a69104c0d56aab96dd6d579da7e5564e1d52125cd63bd39e332af7ed3deb5657eeec4282ebb0ccb28db9edeac6e5241dc175da6a58e3aa61aebb66edea4a11dc9aace1285a92115cde3793b3d66e937dde22fe5114a9082eeb785f49efdef9cecb2789088e6ed966bb5cb36c777b66beba9295c33944cf1dafb7726b27b799b6ba124ff9528dad8e49427077eb24e75ad6b65d35ef1d9e9256152908ddf1b6698ab76dafd6b9665f2a2e86d9a5c9d6eba33ea4355c46f7c67bd77177b74de23ae91612101cdf34ad67b2db7925d5c431cc2ed5e6a36a67d726433e6e2c907ee0faad16db8d4bca33bde1ed035bad96b7cdca917ce07ade66a7b7d6fba65be26c14bd2e1ba9072e5f4dd3b6ceadf54ceb3a3e728c493cdc296fd775f9da6e6eb766ab2b6bc8bfaf470c6907aef22a35bfd9e676632de78ca403a7bb4dcb79f7cdb3bc75bd9ce57a0165a41c3899755ddbbe37d67ac5545b5d69ade0c0496dd334cd769db3a45d0d23251e5b9ddec065fb6aa9c999fbf56ce06cc7dbd4b2aed9c4d996759d9dc1a0506287adcf1995500d5cefd26a5dee586a79bbbc73e5b0e87b5169270d5c5773d7699a6619573c3915b7ea734655468ddd2a9f984ed93bc65ab7f311d771e614638db5dae1cce28d8a91ee322c65763b4aaddef44b11b7d52e6f1dd62ad7b68d65df9f57f4bdc08f88ebb89e35677dadcd5a530cf6af477b5f06834281fdf354d7e78caabf66bd9adadbf16c6e5b27b9a6f99c51e991ab986f5bb659d749aeb56ea9b453e42cc6b7ebeaae9a7653d3737555a237e5167252ef7ab74c77b452dbf3b6bad2aa722854e5947706aeef3cef9d1bc69add7be7d595a31157a96c5e4df1867915c6c5751dc7595bbba736b37bfbea4a554675408e4b6efb9c5df7bed9cab3d5952ad59c2c16d7dac765db5dd969d6cd6b37cc2fb53e6754685a1ca5b2ab9d76a5a6aba6558942b1585c9b61717d5bed623aab9557f7a9adaedc9e8abb8ec6a34f07853afa3210f539a3e286b85ab37db5aceb33cbd3561464a894d3badd7ef5de72db7656893cceee6d6f926a5d9f12679d35da3d425ffe41a15028d105aae4f539a3da2b4e6f8cb1a55bc5145b9aef960971b6eebe49aeb7ed6a2deb74d2d7acae96475baf8f8eadde75cca9c6b7ccb9eb249796afaed42417ce6995eb2cf3ababd6b7dab7e65b058eaa2ba96c2e5f9d65946bdc333cb5beab2bf7f70243d591664673f86e5c52b9b7d45657cc6dc76575669a6b33e3d3de6df69ab99b2fd7b36add7618e7d105aa3659bb0471badbb4cb68be16e3ac8d3a1bf19945c5553a67bd1dbd9766de757775659512039737e536cbb6a6699b96f35d5d39f19686cb584a9d6787f9762bd6f8eaca51e455a2e1b2662dbdd84a6d9b36abdbea4a15ebd5ca64a6194d2ae4ed60db7a7d307059cd6abd9d96694b6f27f3ea4a114ba1cf1bdace70b6cf8e6bb9ab92f70c6f7275e5c759fbe32a6dd66a86b39ad6d17eebb53dd3b3eed595992c025d2ad6abb5597c5baf6fa70c97f5dcb0a6d19d37ad77aab590b1724e29379cdd2cbbf8ce39a7ec6eb6f1a639d572d737de211f7b7850a8cd3d17a8512854969f27990c4717490163389d4d6b7b77bbac93b472fd811bd7df972f8bc5a20b4c89e1b6bdb3db4ddbb46ddabee9d595630876d904206ed3b6e35ac6bacb3a3933dcf98fc8c7a39dfe249b2f96abe55d2bf4eaf87685d93d6f7d67ac799dfc5ad9f3832bc65dcd5cc31ae769575752175e98b4ce29b78a6f96596ca7a458676cbbbbf56bf9b6199d920b182eb31d979aa6f96a8eb5c67b839bc45f38d965ce70ef5b97554a6b5f5db9c1940f87a79e5ae6aab1d66dbaeebdc16cf3c2ed8c761befb0c5b26b5a73b675e1b62e534eb1d6bb8aa9e5747525cbd5f2549b877925e2eb0297b36dd2ae7bb75d9bedbc7352e1c2e56d675d36f3bdb69bfde695786d0f87b59678935daebdebbdf38f137115d80243979ca92d5cd7b56673ce7ae75db77b5d5de902c32d332d5cde92d62cc3586b736779aeaed49c48058e7466e13acef9b515df8ee79c7559a5bd318fe294d5e78c4a721cefb49a6937674d6b9f1685a2351c5d64b0703cbb9ada8e4a896dae1b0a85abd8d9ea7346258687db986f9b6bb65549afdce8ea4a292dae62a7f99c51653b1cbd99cce4cd19cf89335c5757621e823c611ed3ed68f2ea70b9d36d65ee995abd7558b6c065bbbb5be3bd3ba75a66b75a9f33aa1858e0baaee2dd6d3b77ba35bd67efa06aeb7285ab925bbbc9ae75adf2669d15aedb3acb9c6fdbcc33a393aeae545169add49c88f654e1f0963399d56d6f576e5defea4a950ef3a4fa3b12352c5438af5def36b65b959a56f1eaca30af74268d525bcbd5ca2da33acb3a4dabab2bc351c4d5e78c8a68e370ede4d6499be9cab3a47b7565fd40160f0ac53f90a533eb73460503e632aee9d6aebd73cfda51bbba5235a51d5b9ddacb751367dec98d5e7deb26efeaca4cf20d7e5a9552c7b64050c5daa14a2541e5aaac7ae7cdafd45bc392afae1c43dc82c66dd99dd9c699d669b867bd5757aec4974b1582ddca2e94eb1ab67b4fad9bd8e6abe1d595aa8b2747ef95dbde74ebb9d3b66b5757627925a7f58cd2ab5d8ebbedb2de56571681a1904ace8ce3dd6ebca9ee9addb89e7775e5feb29cf7099096273a4f789e0015e59f4ddeea73466566050e63dc3bbd718d37c6d7da27405a9e60cdc7a0509a135930289dddcc2fa538c35a76bca15028d4d4f814b951be697aef9c72525957575e1a2485ab5db65a96bcdfbad98ce3d5954f80b43ce16a3cf1f504a8cb13d5e7d9198eae2a01d8f0c5b3d2bb4d2aef866a11b158284e4f592bdedde6beedcc32d5c6472f2453c756a73c04758f0a9c96d9cd3dcbb7539dd16d47e1b68eb7e41df76cd377676d75e5d8eaf40990962742af8e6341a1c65647a178086a313fc9e6eb09d0932c3f4f9e007579a29a9acf199516282af115aa541ea4208d726b0414a40a640981ac0e4ea0d993144c8052021412a460298ca0820859085140c03e28c183199ad081ea040e660bd42a1eaa5472c8d6e9d25cbc1b1ca95426006073950a1ab49ca84270cc1948cd867032028b46114cb64e2c674a954ae8f356625e6118947001eee32b040b8cf84aa5d2a2d702a1821428103b98608260e88104433a772c16a842939d79e841042230912a2ed4010452c4915eab0542b9992594c2cc59fa0392541971105e851fa030d10589b7f287038589442aa0e0ddb0828dce77381b4e079d6fdde520550c69d0c070c64c193263c40085f979c1c74b1717b8f46cd19285c3c2b3a373d3c20d0b57fe66febf0a97fce9a6c99f6eb0446e14a1e0dd68001f3a3f800a469881b4812aff240d5c803406490c013c49e39a800f2651926025b1e594c4161deafc3f1620b4dc80c31a247ac86982e584c44760c5e988054c80039e8c78040a04fc3f01c496f761cb187aef6527f3eff3302a133b01fb38ebe3ac7a2a020d51071ec1e22120ff9940e288310fe63f90e14f44644c3630c419ff3c0bcf3fced77a1cd6d08f21c883238387b37a30ff3842fd1f8777f9ff26385b85a33fffe3043dd862fde3a87062380501654d114e1a34ff8f04107ce0c0f0ff4d70cebcd0e7f1a13caa58f907ec079c930f4a9c7600e1a443d0e720c7090729271c72e60d7adc0087993264fe710000040404043406470c90680368c3fec709f3f3ff02982f2ee470e1e2e385cb23727471e1b9fcff8bb8cc2d63cbd4325a195a6696f9c7e9996ad85023c6162d59381c2c3cff383bff8ff338f57f7b2a9c1658e069e932ff3791e05cd184b032a50a08537022d06ef4f080ca3f4eca4802461130c2c2480a23041831914a6918e7fee3d87a2abae31f070a9d325be11f073505070356ee4096967f1c2963985dff38016812be586036cb5bf11cb1be2f79ccfb1f47852847dc859b37fe71a0a470c487b279c435ebfde3a08063c20925c011f221ef03bd8cde278612a552632363065ada68e42cd66bc703f5e83dd27a7164400d6af087cc3fce1313fe874230cbd1ceff8d1c25fcffd1ce4d2424e08cf08f83e18cf08f23c23f4e08205c60ffe37c40ff713cb0238e36420a84609cc22e6e1ed97cf34f0c0e071f58088bf5f2f9b21730a398799751e71f678316f88559c68f772dee856b0f1c7314bec4cc43a00ff4bccf6b6d1dceca9ef6f68b0b27f284789795e6409c488fd1fbc4e456165f2ee000e08f3e9d39836906262d6df48fa381930cfe714e38a61c3930b8b9248ce6448f73018e05ff381550f0ff4d2ac189a0121c2638105482835309ce8d8853aa0487b4bd918be225bf2d49a5b44a2aa9a4924afeb3e8d0219dc730917056973f3160401f56e658401f238eb7321759390b8bf5cae2d219419f2f932895621db13e574b8c2b87409cc813411048e43c8bd0e7f9f09697592f30fcc48c21e852e38511370bdcc2595d1e419fff1b0fb058af2ce22b1c3f319b7f62fe6f3a00891e378f38f2ff250ce6186f0d2f8cff6f69236d0c1d23ca904a6944a9d4ff4d239891bb88dd40d580a6084d6496f9bf41e4907f598687dc90423240881f83f821c89aff9b407e7b807c9b29b48410117f74206bc70f3ffcf0c30f0c6c9647e6ffe6021ccbff8d05f6d8fc0b8ba5c7e62c561efd0b8ba58387f87f73c7f7eaff3776dcd4f1dbeba1e3ff668e0ff419b92852a213173a713437724c1a879515c0706847397cc38dffffc488622aa551a2546a6a9428959a3da914254a83afad1a9d57d2488c0a0c5574a85345f33157c0fef588573ede25043faf158232e4d6958f77e1adec6394bdb4f297c7bcafec9eeda942deed9e542ae4ae97d8023fbe5be0278ae2667964ae54d17c4c96efd5778ffeacc46398c11c6d70670cb36bf71c6d70e7ca9549ab4c2a66143f900793825f55f0942ffc4a93cd233ea4d9d7f6267d050172d304137cf4c2a29d79f87f638025fe6f0a704300cdbe26912ae8064509e2200a96ea60e721141503be135d227e693e46f45c3aa2e7ca421f6fe54fc572b53c31061a1ea808e451894f8a2406094e6980f82a22dfe0774ac381962b68d440d3e5e3ae2fa0cf098635679cc02f336dccc081e571be9801225122fec55319ecbf34c210efc3894c14aa1c74a0a1063250fef3281a9dc6f0f1e018d61829ff46fbcb406d80be0055f07f23c4ff4d10ff376b80f8bff9e1ffc687ff9b1e78f8bfd12187ff1cf21c72b5d65a6bd5344dd3344dd3344a29a594524ae79c73ce39e794524a29a594329bd9cc6636b399cd6c66339bd9dcb66ddbb66ddb368c31c618638cefbdf7de7befb5d65a6badb5b6d65a6badb5564dd3344dd3344da394524a29a574ce39e79c734e29a59452ca2ccbb22ccbb22c93dbb66ddbb66ddb8631c618638cf1bdf7de7befbdd65a6badb5d6d65a6badb5d6aa699aa6699aa66994524a29a594ce39e79c73ce29a594524a2933b9e16bab460318e066cebabd7d76d5ca2969be00a7b3ecba677aa3db9669335b80eb366d67797b27bb89e9cc7b705da699a69abeb5c3bdcbac07577bb6592bbbdd76955ce63cb84ceb5a6e3777adf9b6c98c0797e9ddb1ccb6ac4a2aadcc777059dd5bd6f149f5bdfd66d90e4e5bdd65df6cef757079ab39e72d35d9f1ad753a3839f3b619b7ddd2de6d3e07473b9ab37c6bc7efc6319783d3b6bcd90e57cda5ee57c7c16d5b57ae5d9e6939a5a6b70a707267be69b2ab75eb4dbb0d0e4eb395529dd5a9352c69dedee028cd7ccbb8ad9aad58db3637b87927c51b9e33bbd972bcb5c1e16ecbeecd5d6633d326ded8709a524cb1b59b6eb7db7763839b58dada5dae499be9aedb1adcdcb6ce566bf56e586edaa6069731963667ddb6b5de39ddd2e0f0ecb69c699657aa75463734b8de6599dc74b75aeb8ccfed0c6ecb6cdd74df14f77bf7dccce030a67deea975dbd5369d5b199ca5dc72dae14d37dcb9dcc8e0e6a6f54de74cd22cdbb0dc28c0ddd9b156edd554cb3096db189cdeb2945ab6d53ae5ec5d0c8e629c6dd56aa71be5b887c155bbbbcb73d637eb7ddb60701ab5945ad9dd7b6fa57b025cb76592f2cb35df1867fb0b4eee3bb9964dabcdec728d25c0757ceb3ad34dd2cbb5d4d80b6e4aabfbcd73eb364c7787bbe0e8e4396b936a92ef6c76980b2e6fbdbb3cf7cc55e76ec35b7055db9cf55a75ddf4de174780db946f9c49cdefdc38be580b4eca0cd79bafcc53dacce22c382d6f9d627a7bd7f5de2a8600a759bc352df339b5dde18a1fc06d9bc6f5a6946ab3e64b31169cd6376f2d4f9d759a6d135fc1cd5b719658df8dbba94d6c05b7e94dbb399bbbd6ac3789abe07495f2f22a65ef7ab3123b809372e20d6fb74b9bd99e612ab86d2bed7766bbd352c3196e006737def1ae6bdd96bb4deb0ce0b6b933dae7bd35eb6ade53709ac6a5ae35bbd2dab9ed057035eb7ae3cd6b26bbad6e05705477b6f78d73cb6d9e5b0a0e6f934eda6d7a37cfae4e00c771c57c6a7a37af9deca2e0f4cd726f5a3631be5ddda0e0b896d9deb3dd689731dd10c0d1ae719bfbee5dcecaf13d0037a5ddd7f22d6b999df6de27b84edb79ebba6d5e27b8dcf54def5d7967e5c66d131cc69ace73a39273d9676582b355d335eb6eaef8d2cd009cce7a5eaa6935bbf95e6d090e671995176f38d3b6566901386977766f3771be1ded04e0e8bcf5ca5e2fef56cfa904276fed1defb2da5d4efb00389e31b7b4ead94d9eb500388ebbbe1aaf99f76e7649703d9b5be5936fdde16c4382db99ca6d9399b6b599a93c829b96decdb73bb5a95d6d044731cd18732de79cb599dd2238ab759dde2eef1c5bd989e032baada5fce23bf3d64370b7bb1c4bac717b2304d7ed2ea5dd7a6e4dcb346e6f82e0b66d2dd65db6ddd6f1bbedcd1aaea398667ae76c4b7dbbb63740709de5527659b651bbb3adedcd0fdc9db672dd499ce7e4b7b3373e705d66f7d2aab5cedef4c0ed6ccb72a737dea7b43ddb1b1eb89975726bf9d29a6daa657bb303b76d384bddf3ce36dad9cddee8c0f59d3bad4a6dcf8c5b6db337397033d31bcfa89dd26e3a377bc9dd64b6b186b58d2de1a886b194dd6eed56acb54a38492fe61bdeb4c3bb7694128e565dbbbcd672996d9f84b339ab96669ba6f52bf5fc0077336d6bb6f6562d379d92703debac4dbd377d71ed32126ecb6c77e636eb9b67951ee0b68eb36cca4eb74b67ef00a73bbced59bb2d2def1b245cefe89675d69dec12cbcc3ec2557bf9de68a7ad9659965947b89e513e6f969de33e39073889a7ccb863ace13d7123dca41c5339f5a4bb4adc19e16a96dcf29aed964969fb221cb795f32a3b29b7e67b039c9d536e1deedbaeb7db34c0cd99713c69d5b44cdb4d11aed3ba27af55eb368d529e08d7ef9552eabd5daee7e48870d86ed66a54cf3aa9d60fe16c9675bc5a3ee9cdba6e43b829a5e5d66e1bbd999cbb10ae67ddeeabb3a9b33abbce00a779cedb36358df1d66d1d219ceeb8b4d9be5bd7b3a6dd20dcd5dbce9a9cb367dba66d827056b35ad536cdb4a6d5b905c25d9ceddb6d7bb39ae70d03c2e9d95d3cb3ade3d9a5196ec36df66ab3cba9f3b59dfe83cbec9e77cb769e7552ed075771dfe6cd7857b5aedddb07d775c6b85e4bf14635bd7c7032ebb34b4a71c615530c7012d36e4e39355e6996f6025cdd5c6b9d77b5f7ad336b018e4e9e4d5e379bedc9376d0f4ee69cf3cd7ddb9c655a0faec3b94f5e6f96f39e95e6c1ddae6bde32de328b6946f1e0ba6dcbdde6bcb37df67e07a7e58df699354e33cbb71ddcad53d36ecf32d7cce6590797abb6cddaf79c950e8e6b5a97cd6c536ceb95b2cec1758d354b73d559958393756eb36b33935d27b7acc6c1c92cb336ebe49654e3b75780a338ebdc527d67957d6e38b89a7bdebacb6f3625dddd1b9ca6b5cc25de9276ba6ad90dae6f5aee76ee2df5ec66d6dae076b73bca7bdd9da4546e1a1bce624baba6b7ba33aa69acb1c1d92ddb8d6b58cb8ede8bb535b8ca6d2737aa77c6ddcd575383d31ddd709dbd6f3ae3d86a697099cdb8a6a5961acd19b61a1a9cc6edc455673df194d56a67703ce32b33dad13c3395553383cbfa7633e3b777195359b532b839338bb3a665536b8d4f8d0c8e675a5727bf39a35d66a546018ed739795779c65cf32ab531b84badadd652aa6ded32d3c4e0322d27a677cbb0ddd9ee61705c739bcdbbe96aed963b181cc77cd3a6beb76a5bdff804b8aafbc4dacc38be5bbf9b7ec1d53df196f1ed5aefa4d554025ce61c671dc699d6b74c3bea05277b97d9a9efc59a67bcd12eb8aecbae7667d76f8775b6512eb89b6d9ed999ed5a33db31dd82e3ba6ab9eb2d67d52cc63402dc262da7db9452db8db74bb5e028be5b675cdf2eabc49666c16dbdd92ceb6637e9d6ed4a21c06dd4eedbb5b6dd9a71d7e803b86933596bd57066afc627c582cbf26af3d69cb3997597f40aaed3aadda89639ab97ee8c5ac169ddb74ceeda751dd6ba57c1757de20df79dedae5edd1dc0653d93b3e22df32ebbec5470d25e796f2625dd5ad6f106709572996d5bedac26b78d3380a399d5f8e59a66e726279f82dbb2e34967edbaacab782f80b372eabecdcd62ad6f762b80cbb56f586ab8a3b566754bc1d94c6ff2ce7df3b47cee0470d26afdced96d996f36eb28b8aabb59b58d6b7df3d67550707a5e2aa59675dda4951d02b86eeba6be99ef3c3b29bb037056cbb6eb18f3dbd5aee627b8aef2aa75daada59d9ed9096eebeebdd856ce35d6786b82dbd9ae76579bd1ce4ebb31c1610dcf69bbd4dad6e1ba1980bb7bdeda75d699d6f2dc96e07836abac1ace3aeb756e05e064b7946a3cbb6eab966d04e02ecd36c59dc637d678632538aab9d6b36df3eeb9673c00aef2d95dad6d6dd6db2f1600a775b4da8bf5cdb3e3dd9d497059a632677a6b7c6f9bbd13094e5bce65a775eb78b7dbce23b86badbef96eb976dded751ac175deb7c41bd72e6f18d7592a82e373eb137756726bb756678908aecaae4b69b34c6ab94f3a4b4370ba6a8bb9de3aaddad6739684e076e7b3ab946e5965a7e52c05c1c9de6f87b78d377af52667690dd7b36ccecd6a6d6fde59394b407072db9dec5dc519b67cca59fa81eb34e318d7aca35b777b364b3e7052f73975cf324b336ab359ea81d3b6acf14d777b6696e35ee281e3bdab756a56df3cf9eca51d38adf1a6dbeeaef596b39774e0b2ae756dea9a6d97edca4b39705b9d9df2a969dd66adf525dca5566695de4d77b7c39d25dca5f556dcf5bcdd3d6d5709b7a7cd7096f3a43c9bb5a384b31adea4d66bc59d76ad9b84db5ad355e65deb366dcf1fe0b46de39d4a8d6b2dd32e4bc2651ddf4d5e8ef7a6379b23e17637b7bed979ebcc3abd7980cbbdb3759392caaeceb9c90e70dbe476d68cdb9d6d77af8484ab53bbf56edaf9ec32bbf2112e937377bde16c5ed9bb958e705aa3dbbcdbcdeea69a57c9012e6b3cd3f8eacc52acdd2a1be13aedac56ebce58d7de51c9085737cdaac45ad357634be5225cddf2deb64a8d67adbb2937c0dd3c79363bab7937bb3ba50638ba51dabb9e657eaba64da908a7e14de9ec66ae9d96d9948970d46e35e3184f8e2bb7994484cbae86ad76f3a438d33293877012ebc9b70cd78b75b7dc102ebb7dcbb49ce1d9bb4e79219ccd327badae74e35b963c035cd6b3db517b6ba51ade9b106edb6dcb95d6aeeb8beb1e84d39ddcd4de9b6d5addb42d0887b73e27cf78a5dae64e07c2f5dd513bbbb6f5d4eca601e1a4b69ddb9a7bd786ebeca5b7d73a2db55beffee0f2cc946b75675a26b399fde06697338b279fdb66b96d7d7073f7db651bad739bbb6e7c70b6663ce34edbac96718b31c0f5acd36ebbdd33d592e20b7074b3fad2abf7ce5d66d8025c86b9d5baa376ef8daddd83ebb5de2eafb4fbcece563db85c69c73467b8c37367350fce729a352d77de6456b58607c76b97498ab3d69aec92dec16d566fb593b8dadd6d46ede0fad6328a6fcd7570f46eac7515535cb3bc493a38dbe56ce26d66b6dacde7e0b8deb66a69e63a6b9d66d9eaca2c4f80ba3c11c75c5955999483937ba3575a2db59d99c63d0e8ed3ab51a9bbed76dbe67aab0057e7ecf8ec776a9b6f9673144ab33e6754694870f48977c76bd6abd639bfc1e54ae9ae5a6a56d32496d80d2eebf4cdd9ccf8a5bdeb6a6d83e359ae1c7775674eaf76d964594729be75d3f76a6c27890d8ecac937be353e2bd619add5e78cea87b40667bbadab755259b7a651cc5f6a709b9c985fbce53dabb6694f83a3597627ed7aefa8b4f26a121a5c97e93d71b5156b9c35953293cee09bdb3e77966dd9d6bab6ba52b3976446df19df55cbf2cebd0cae6e724a3e33bd3b5b69af1a195cef7abbf7e69cafbe15570a7092f7cd6eb2d24ce22a311e83b313dfed7619576eebed5c0cee66dddcb4e376cb1c5fad85c171adcbf36a6bb76cf19e6070b6cf69a7ceb8f78ef9ee564e80c3b493bce3986ab6ebb674b9c47cc151cd52ad4dde3b35e25b425e047699319024c061b9d15da7d6e5966baeb50791bce02aee996b2de70ccbadab952b52179ced59bd9d9c5aef9bd6d5cc0527ebecdb66bbae62dbb5a9b7e0aad62dae33d39abc55667475257e02a4e58966bd302d30cc8242a150e1285a2152043899c96c3bab2f973a5f2d06850243aee6033f14aaf5faaccf195547d282cbf26eadcdcee6daed9a0537b7aa69b961ab67a62b95aa4d561204382debf6a6bbec5abbd169b330e11846ca1b8e22253d80db2e9f966a1c4f8dd2be65ab2b711f5f9b88c351a4948405b777c6bb3c67b66972bbf3aa3ecebaf2c805eabaebeb09d093aebe9e6c937405b7ddda517cb77d6ba75da6cd1648567059e25ef9c66d576d77a5b658485570fb76f4f63cb72d778aab740097e9bc03a074a864f0aaa6a198a41432840000a8361200900003130030482c188c0583d1a0585dda071480024f966eb646984ca4d130475110c430c810630000000040000186a0b41100a84f064edb105d0b793131399a8b74cc223ae3d69f0073ec8df9cbf13bdecf59cd26d2ded26125f727564e1f444f85e031698ef8445a593a5909fbe4e34405d1a4d02d2ede7eaafa48ce6dc1fd6d43a12429448be9e7e84a2488256d25be4f2b4e271061857c3151399a8874b3c45b49f58b24b86dbfbffa50e724c1d1f6fbd577750e1298e6883db17481b467da271eee4f8cdbe69eb5fafec70c41ba775badf13b680a907ccfadb6bf83288e2435323b50a13bd0faaa2141072be38b87cacd442ca7b191b0702cffccf8e8d0109779465761ec25be97a0dc9bb0d73ab973a6e0c8f7db6afb3b880a50e28826d28fc0ab3a21e7b4c0f8765cd57872120bceb71b559d9373b7e078fba9ea2339b705f7b71faa3ec9315a80bf3dafea831cd382e4db6f55dfc9312c48df7e54b54d0ede82c4b76f91d15f5a1fa2725198a2c89d9b594434abf8e70ac17def67ade689b46469be92f6898fd30aa29342584c3e4754224d2cdd56e27d52714552df76ee2f3094238948344bbf95ae3e41386910f1155ac574e20813c967896aa5c9af5b70bcfd54f5919cdb82fbdb0f559fe41c2db8df7e57f541ce69c1f1edb7aaefe41c169c6f3faafa26e76ec1f1f653d547726e0beccf114a248925da4abf3f577c20d06865f8e259b9391107b3e35b87eacb84d30d225e21554c24ee6de7fe8243ed27a98f76ee17dcd57e906a9a487c96562b9d7cc238f920a22a3489e9e6882792ca12c94adba73fae50f549ced182fbed77551fe49c161c7b5bf9d3aa92f8a58713475c5ff3dc5b70fe7e86fab6cf5d83f3f663aa6f7eee161cbefdb0131139003d2777c22cae4be51bed9ad641f7dbd4558c26063351566cccdb684295b3c67d4b1a972bf42e6628b50823d7a1741067cbc0f0b06a51a871d6cc88fdd55d977ac1bcaf45aba283c4dbd4aa220f0c48ceff83289e91a71fa2339f6cbb2c05375468382182ee1591245004ddced938dd257acf72511b06b42dbcd7cc5c9757b97017b623c10020db63ec15d34ff3a70487527f119a0e0e7f927b6456ef440fddc74a4ccd8063af5f6da1b3826f2c9d1a00c7593de32c3862562a63b3335a8ec532809e7434459c054bd9c675df3f240b64dce48ee6fc2703b22f87b26f654a8b00591936ce8dcd42ff068363792390e08044ec88abd2c2cc41a8d6679f2d2d4b4f2edeadd6147edf4b20c57b01d1e4cd8a04d9d00ca5db0a6a44f3f501fe694d41e4fb92745058d05b0c300fc77ad6be383c05bb5eaaa935db547d21a58c5a27aa56b37642267145578a58eacb4fc82cc621ea47675a04a8616bfd866cd742924f03917e93925c96029ff0acf18c5690d8cec08c20a06592b9ef79ed8c7b503639bfde91499940a2946a10c0042a0da05395091c4e62ab5a85740bd44fb12d52a8217a272861e64b9636a82c36a5d816c5215c04e3f919e566234b03d23500f0506303076838c0008d074e80c652636071bb428018fca2b9447dbd03978429e60741f42101b11cb280580c19215286d25fed3bcc6387868ef3a224489703824859b07796127cd28a4ee2520ba528c7bb0aaa58b7a68aee46f76f03d0c3a7ca8d41ed04df1e8e616f6d7dda92afb667d3213c85cceff1a540bdeddf717fddc84f057592104185d5a0ccc16b5682d8333bfc5f5c009b5e6bfede6ca3736ab277687be4f3c70370d5f6b2a8a7c8c893517760c6792ac52bac5ebe7ff6f4ece7ec81249585d0c86856b0ad7240192b0d425daf85d6d0f6a47689d7b6d1d71d062d00178acb021437250da88f8582201258525546c662596a1fb8de0ab92f251b66d51f801e2c8150fce3fc01a8d706a06a30e82a595b0a836cf1e994a016177492e953f2c9af9f1d15a2efbe15a67133cd45637f25e0e49c9ca7406969a83331e20d1850566f110cfcca4ee3338a38bbc290843d0c19203071f7c15c0971f9c0dc94c62c2b4c50d4a1da2d0115ab0651043a974629b3d409e6baa08c015f705810faeeab2bbb6a16f2afcd223ba1fba120b3345f7b7bf66a6ec7467e59b1951b9c08f2a58ee7095256e12bdb162c1085b9586b819f1ba9cf98002768be53cd458c3141bb131a433c7e523ec2d83843d3209b3be53bdb8fea632781143bb52a0cbd398bb18020489811a6e2d5495b5ed523acc69861d35630589943277d4466b729e0477e350ceac633d26eb1e1cdec138b19a84addddb5bc649b08dbefdffe33f818f143019080f25a898a406c5d32e99e6e9f90a71be9443a43acb4fe442fa9aaba68c089577e82fdfac75df2b9a9550e3c702da81d17426a5baa8f9c0c868223976f58cba360fad4f91ca112a0a6f4b7a0bdc183306086c9f122e7b7ddae4e2ddc5d9ef13ee149fa5bf16db12781f1073a0bf52d210d2ba3dd4241a1690da267ab615aa0af49158f4c2f5651d73eaea80bd0d09a494d3799fdb38aee8b4c6dd4694b250b4c9b0f1c002fb54070d8af243e24725994aacdfc6b5393957903b5a25e30506274d303d4328dec9856e98fe17cc122fa8b41ff5aab83cb300422b6d9416d6503b619762441216569998685a2a6db56dfb0482c849c1e0cff2a379a83771b807d340b6067b3ecd53ae9345b66fd0bc0937d07aa678a9652baaec54d20e88d272a47165098a11341230fee27355ed5a130b6ce4fe19375efb58e51b4caefaa6e1b7bd7d67ac79c757743538e2993eb21784e8f1aae94202fdadd01b195ea3810a0068eccbefd77a05bc8459006c0934f418b222201f50a939b015acc2a39dffea1bb78e0010cc0cc96fe21cb26d69208dc5d261150e66f40cf63663ffc345acd69d188b2f87d438da91e05943ecc52461a4305c28546c9fca24162061f32dca3dda62aab9e6f07da2508813ad491d7083e184f854368214f2d6f968408498944de58121ab8562f40282a6ab59e4780a830932aeea09b522309d47b1691e64bf9fb78205b6942d0984c8701c67fc9e83e0a4c63631706004284a5a719200141d970d40f2dd8883aff3c927f811cfb77d542fcd86324e2e3b7c25b006a30faa5148322d432376d7c74f592d8985231d31a7c994678628e8007695d69cda5dc299f2a1c6f067868d74ead6e43d0ba4b46eed8b995e742b1a15e5fafd971893ecfd227677661e9d18e47ca6486886214f66f41b47d7f1635ad3a77723c88187fcda7e5a5eecb7d304a95cc81fa114279a0ff888621c1307fd2b91338fe6638b1c1382465e8f7b765b6fbe4204c146c239acd9a27d24c0b5f035ef7a96333bf86c9533acb0c63ecf6665e39dda1d649b366f63126f777bbfe7993d035d02e98de47a5d150534d87f506c02d86747ff8893722011cc17610e3dc8b44bc181218a0dc1d41408d4636e1243205660cd1d218a4ca2cf9701230e799ebe794b872d124fd26621d28f7ec2ef626a80d86379046076b130a3734c8ae82b3ba3b8c86d55cd602cfb4f88002ca6390a42dc8dbe0eb6503a7bfd633d2244484dd10ee67f0169fecce4f9ccee423874a8a21155604f487384016f2c648c92d173c9e17f88a552304ef546003b10a12bdd0439f37ca4090384e4d0dd040cdb24797b1ca2313055092c137d30efcf7d243067259794450fa30639a53906a3edb0f0885349f1f70bc593d1f025b715dfec370a33c70cbeec4fbef8d9180c8f50b8c38dc7705fd81c00bb70c77f49426cc8dc2b4f9a269e6b63bf0f4b37ae56f5a5194c70c592c9bcaf0da681fc3a389fb367471ebbf6aa6069d77243c66c7b7e47690273e1e00a47c73087e19460f168b862f3bc88fe0c25627d127ea1273f9cb29d937f3bbc5a684b27465aebaa78c3407de98f4a6a235b9fc6407e0d6797f9002ced1df50284a797874290bfd8285e54c39ea1ee45bf7ea0e3941f86e5b3f6ecd9396710ff8266c7687d78cf82858c639033abd7ff1630f58000d7334af193adae6e8ed65dbbcf184c13b3e5feb84b8a0ea81f0e8db8237e9692f3b682b9bb8d5c8b1ac9c61e61d6d5b7eedc00a62e04b5bbba83c00fbb27db3bff4caecd58ba741e7e9f8c4f82c1b74713acd02077ea690cfcfc3d1988bcc195fd032cc0c793a1abf71dbecd82ce498ea8a0f59d8445d7062b536e742e9ace3cb63c8e33465441b75f882c5cac3ab8d33a127b343c7f67843f9f5286f0fc05f0a80aa5e5eecacfa6799fc3b9599ee35ba2c802d0ff9411119a7c44fd970a2dc3b36df032afbe67508436c6f86caaef641d25b6c6ea32a6ec250e36a60fff31d1d2fd89b01a4c799bd06a11d9a9d8ba4aeb177a83e1e40e717460b735d85b7c554c752af5b855e4386c2b1955f27813d8a84b830ad453948e994346fe63bd886c1be1b69d7af50ef7854062155e25f726c4345ae66f0fc65dcaff0a09687a08d4e8b3e9740f2487caccafcb0c53d6fdccb029a7535c37c286c94494756f7fc08036a250cbfffcbf30907b3bc7ddc2cb0241ecc502ed711ba7b7f0eb48acab3fa5ebe350e278c9a419ef175a34dc3470ab3605bd63b85bb3a5618cf725bbd77cce97cb6a5e57d5598c376cd1bea6870d941c0b2beb12b45c3d906194562a6fc77694c69bb0e2fd010bb6f065ff8d50cfcf9dcd71f293b20a8e7b303be4022fae9c27439d65714ad624d11a4b5917c92a0dfd7e7ea5524ff7aa86ed9db6546e9f1f7fc4bc43f9e38c4e087ffb07a001a04116210c3b2fbd36cd32eaeaaf0fcc224a9130ef38410ec861312502f85a0bdc19d7e53589463fe51052ad0a0dd5c9a395df2b1ea69d3f0eb26494e17e743b7b063e979448be503e3df81a8ae0ae08ef727c1592bd6a7bcc7c3bf4befceac5958491324f0a7ebd1202850f452a1f9b7fb240880fa20e1cb56ffcec9ea0acdb93664ef69bcb49dcc2b759a83b1a9578c9bdc21c90fbb5f83bf2cbce78f255ce4f2ce6f51ee3e3da43f0d1cbdf7c3fbcdbd9f62aa3932efc250bd2fed8ba418d61e1ef8a3bf6fb11ddce2d7984cea7e94b0d5d479aab6b1864dcc5cc0e3962f6a282c953ed9c3bb2874e1dee7d5e3bbfdeb52063b3b4e400ed7b0aeadb4dc90700da3a330e707978eb53309cba8c326e2f527b8e28297fe90ea9f34a84437eb330f90f0dccbc077972e8ba0ee5674328d4702bc169d3fd11e582ea0586231a8afcfd8c28bdc73206c86865eae998541151b53ae67ffb030061aabf919f377ce37379a28fd42a7bdf870782098a492b3f4e629c3706837c24f62aec21dbdb75644f5a661eb83a25226ef6c543d33c1a3d0d8480b354f569f4dfb5433776a32bb9b93f2dc592ba8332cb00c6d182bc5c0a00695788311c0e9edffeb2afac896fecb847586f4402d80720e4e978dc96bdd0f05dfcf7092cd8c1c7c379bd71f6f5e106b095386f50ad85d1100803c807a41e392b0fbe9de67d042815f7b88fa110678da3174726f28ea87af1e6edb0003569e6729e812e90845ab0f6f60b90925363f345f362dab0e30de574902915dde25fdc42dd65cf8a55360c62339dd8824b55046dfd0da75219d32a27690c27813812246f9d4b309542ba05687cd7930cc8f34c3410b3477c614df311f52ab4486954e788601d6f3785f7f7a9c19e0bc155a14c73fdac54c9b86291d4893dc7f9d9f33c7c8736f00783af8ca85937779d1c4d064044bcff3059ec3d58e6279beceb10e777b53490aee47d357be9295ff70dca766ba84c2ea8f59361050d7ac7c4ec0f9b0021eacac5a954470846d39c72b7f7791d28f574c619c3c0438f53eccb16f2c77aa7b310f9fad31272802939db3222204f47463a2ba2d88592d19a994254af629e3b3c173be93a355b49b219586824b07215b30d61a77a3233a699232339291bee5618865239a4e6b3b3224bc921950b372b1e561051664489c11f94a9777a3ea538974ef2b7b62982879d0ec5089fcfc5877816eede4920ea59fa968dbfdd0b736be67d8eeb8866a07a63a1297a85e5a68a1269c42804dcc1b0791e6bed355ea58cb7e4a16b35165353049c470c342843234d19ec5f8c50c211e3bc0164db7054d2bdd399d8b1bbbd6b1d894af2e9794c1f00753eb58cd753be90958a2112b311bad3ce423614b68ea0263e8f41a5eaca813f356ca7383e710fc30cc1d033aa5c63cf680ef06d81498f1ce9ee2c0fd2692fc5eef2738afaeb5f680f3c9aced0799643cbeb507a70db022bb8f112073c82f0079306d61de771b2dbb9f3cb6f3508d49f5bad62313bdb066849d0ffccd277f0b70f80f983f422f88e3ab93052f18231fc1fefb535a0b7908f10eedf2c0350a9d584497e8e442eb07e60694bb6adac2f03f0aa963753777d961456f7a461c95dd40b7b70cd75efe0932e53e63aab3f9e16f0cd9139cfec5b51f434d26952bd8b75f154bcf23dc85cc6c422bc5df6eb1174a021accf49b72f81584e707b464209aad63d54988662be024f537165149dc90d0ab848e97cc4ab748f7b282c2b4cc90c94c9c29667fd549d7f2a0ac33bf8b88ae418bd75086bfda5c64dd2ca213fa6d3bac9ab90903e8be0ae74eb4a26180349513d3d0710299414ae391c899643bd8f7d8ddb69a84ed73873c28c5af4f7e9a5c1d43fd699dd49c24cf2844d7de803eba26b6f03129eab1add8102a75acd8a9043796def1a0c76170cfe02b4247094a6833c94b0492a4ff2736f395b85521b2f3561f8e8bd87e7ef65dde94a6d5dd118ee8a97a3e134465623347f0330f8fd64f163410cf7d67ca30ff31079a18d1a08e7c1e63f41da4ddc6dbe1c752fa523a656f2964c2f44a40e05ec9c433cab8977bf95c620a76ef3b1351ae4d90010b16ccf0136c5089a9e36540b9e8b098d60ac22f47c55ceea8986bbe424bcbc662e897ce9004e5645c09ba48187b9e9a269e60ed013f46bb7218de6d443f7e03f3fd6e96536763536777bb41d2becb3afbdad72b208bbbe0885f1f333bbe0c4996b33072d7e3ec0d6840ee4586c225573ea0e54fb424cf69a6bca3e6daa9cb339789d02c39ea40916831dc17f6e210fed7428b61f1876d3b54bfbb36133228162fe54470ceee6efae0660831c68fa69e378a38fcacf66efa162333fe8908fcc40d5d3595d291cd3e0ef4b11f1b270a1d1958eee069752eebda237d3fa1c6ab5bb406bad81deffd126e94c9316427e68e7d9f8634cef75e294d7f91a1de5dd08cff1289331a8be7d8f0c7d2e5cae67a9a15aca19fc807bfef99e87321f82448c7cfba651bfa9ea1f796613b7b095e513dce29e4e76ad0c2fcffdb7aeb207bfaadc5bfb01800dffaa2dfef8576b266bd9f40f2ecc287b29be57834fbd2a39a2b9800dfccfebf1de9272f524f9e9e46160ca3d5f5ff4a65fe5b40441ad5fe21c70251f3be35d8c86b460bc2eb2155c0327e055b91af8ed4128bd93bc04a7e3ba693580d6f400bc2eb2155c0327e05598bfcd39b69e409fd73c32262f81c63b40c874f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b4054b474a97092dbdc12d619b8fb5e5cae631b654d7bcb796a99a8fa995ff46143ff2492c37f6607d7e046cc76de907c749c1e02da3238fcf6b466242f7af90de740f978135389fff71db711a3aa071a5f079cba84860f399919fd7fd23a030db8b6b7c0dcee547d86e9cbe7e681c19301150ba1aed2574f2d98773a6d93373a265f361a33eadf2a7115add60412b9728c2b784f5dcbebd3697bd648a7c576a469267d6efafd384e41681ae8d89a8f1beaed1235df70bf8cb883c6de4b20d14463e66b9d10d5c86e1d0cb5dea1533706b05bbc18665270c134c3fbf11438a885513e3c376dcdfe54b48cacbe1d9da8a39bc27c366fa0795424fbc84c35555436d0ee68b76ba347596425fe5f1ef86f518e0951a00a2a0ee7f8ae6e0a87e51e5d65ccb9d117e7d228578585e8721c0e4d3b44ddf7ae4808d954d7478cb196fd2c7daefd884f7cca906dd403df240410a9d7ddf430b7744af340871c76be5bf2f2d35c3fa2b03c2ec433a502a48bd27da70db081a25d857a5c22447492fa26483b25bafc5a78cd400e0f5955c6ca54bbff5a1f30fa5c7bcc74ace93b959451baf420d967c8ff20260d2a0532d4d09fae3d99e54e811d41293d1d08f2382755d10773405613c407a572d9b2976b9caf907c840b2ba061662047426cefdfc8b991539d30ad44eeff2f8a733bb5dbf978f88b8eecda197560ca0ab914ee6efc07dc657707129a0ede8b27dfd73ecd295af8483de39b3aaa1a01489998fa7d05b16b7321e24b654fca8e3aff832a7b11b6cf6451b758c0367e8df767fcd810b733f8cf2761037e11df8978a0db311bd26b2e948179d3e1ebed69b4cf6e0044d7d5422c24c50c1a1efa4ba1445b27a88a71a64afade3dc19a6138b7ba4c5799cd477634732945fa1daae70060089c8c42b06ab2aad81e9d97b477b581e8ad4330244a3e19fdc8e3101d07974ad0af7c82460aa28d56def2a13a6ea98720a7dcb4afe7127cca4dedba9b6dafa6575db1562e73840c1f54723f25d5856f31daedd1dae2e1a88d823ffe15dc46d61844f27cd61cdb330568236cb828062f049cef86e381b1036d5927e35703406c5b5c24673ec7df8eda0bd583a4b6806432845b794a5975798d3c1680d82aadcdd143672e36a5ddf40219f22bf1b0da978e54c322cd95461ef80eadf349758f71108622b9b77df16e16585ccbf1fd10614a952be44acbd1cd2cf8e2a3974ff264a0800ad9e44703aa0a02684b2dadce19bc8eb1e45895647530785b010eb59e362c278ea5a04ad6b9f52a7880783751cb83658bf5a644a73ae3149e8c4b8fcc550d3e7189b5511b50f837d37637b990e9b2b08611ced687b940b52b903a109bf5b397b87ed3ae7a15c27aad144be162818cf48b7f96cd85e88cf78f5577e39544c07a49d81a9dcc0e10b5ea437a894d8ce19348441d2836fd0d1efc85f190dc00ae3fd43fb9c827678a616feee4fac0bf77f5415858e13f55be29df0231ea6b0563a9f79048751e4f29612a40bb0d470741628b3bd7f87f8e3dc0e859fedc222559099382dcc958912080c32ceb2055c8b52cfc16e8ce32a29e5aa591845b0ba727565f69e06dd225975bfe9c44f12b903bb73c9feb38c4eafb8b80587de5a9627cafa6c0b3d7f1ada1c99de151d4c4136025b871d588415a0bda204ad1524ebcd8641648f42c1014bd654c5630c3dc1fd8ad1fbebadd88d316d2564ff6ca5b62610b41a1ac241c1fe7e0285d25136152734f4165212be3ba69aa93acec6507653ec416fd9eaca4c36c228ee7b4819594a885e234eef5bfec41c85dbdbbc117cad704f6203e3af03d93919975b63277396f8bf5f19197216116dd644cf3900495fcf831c70b7c0ce83f3e8e78b8a3f299f6d2139ff517dec292c962dc4fbcf44c433a8ae338c243b4b4c8e6ec0e4d9bd854d1f8989fc4f8be4aa8563e15d47c0b81fa6a9edfa6fe88149d25de6988b2bf2e72854d20be5da58bb7f64351d3a893e6238effa9d4f26d19d9673857613b158a331b492bdc4d38dcb2cfcb6e045b83a41a79aa8a93eb89f518de329a0bcdfebd0fb7cf184a95599a2977b2c5e917bd58cec135cb0551d3f487f971b6db3c170d6ccb36f02e13f4877a0778d6ed3359564f18b83567931cf18a0d3dc5573f9c2c498fcaacebe869de077d0dd782863830c5c2cd40e5a3910dc2e159dd3711b16c08e9dc3223f08612bbb84111d6233aa9ddc0eeb0083df6c560699c151bca4fec73c967288e6c516bf985657bd2d5ef3e36ca94eb6d42c2881d3ce1151f4fe3def1ea967cc7f433f66e12a6dac1e1cfacdd9bc779e7d3cdb503caf9b660d3e2e2ee74824c2523786c280ddfba7d7a6a21374c918e20e3a4c103b2814e6c57e9969210fc9614672d2d8e3f1348169e45cf984fda7f3a77277c3bf1b4fcaa9d18fc7d104061a79c05cda510c38fc21cfd8f68499e4ae9d81ee27234442c0108653216558cb8f81ca6ecf3846ef85d5f90fc5fb9900087677de698712fc69f26b39c4ed28b97ff585c73c39cb4a86b85605261fa0640a33b0baeed4e2223f0ced9d83666a8382bf43421bb4fdbeb5246b8c207974efdf7f8d2fec94defed6ccc5ea2817c94bfb1d4a46220873937b91ec75d85c557abe52f8e0fc4a64c79ffc8b54e591e93a97a200230191ddaac18ef905fc0f9bc285c142289c7d8294b55dbea8ad15f2f0432effb7fb645559ac964c3327bd06a8f9c045487fdabf86cd12b5e34ad001290a5ccb1368eab8f2c1d2f33d45febb01735c1d5ae15e6a9c3d0d3b0b51281df8bd695702196246a07f2903df4b9db9e6807dae1c0cfa79fcb83e0de8f4931cbf8cb85a65c8861b3b25edf04f295c2a7a25694beeef896e7a680f99130a8b376e3437aa6bcdfd429bbb65010e65bd47076ff9c50d955a60c3979f2f9dd69175760fc172b6fd9cb464d9eecd598ccb6b9e5a1c4c9b780dc5ce47b8f2dcb9dfce4f7cbba97dbe642d4f8088edde9553f0df0c5cdf80da6fc25be3025a4e5aaf9df6e377895043cbe0373e787977f73b9033543a3290076068ff86f68cb78c79bcb1ba0bb03454f800622b83bbc8f0ee3a9a1d62f65a19bfcc615457f6a4b6eb6fc502dedec439df46b3c5076b8c839cf642fdbe1132fa23553cd845423a84d1711d58cf951b5d2946dcdd1ff075de4de27e3c214603b50075b163044ce9556c27f57067e748695cc9ac842e53dfe46ad0ca68484ed7fd8763224b0cfb799a6e823d1476fededa66e666fd1a81f66945376c60d1473809f98f5a775e5f2843b8dc861c762a6ea4064d677f808528db338b173729a775775e7c6853e0293a15f5951479a37a75e098644515d3bf7249ca4b57e372bd5f553a95697038de3146c7ee34ed1e738e0eefce67ca434c84beb26b83b14e6f9deeccdb5ba104dc99e9aa46e2a893202eacedaa789d863de835470b973949f5d81732236f91dfdf944756477eed28d138831c3ea23dc1db3f70d92e43dab245177e69bc84fc1935cd5813fe84496cc86aafb225325a5bd45b3c1ababdac80d5e86cdadec1becbff3b5b8314ef3d6c9d3b7c0b317a8fc0560444fd6587af82b83b5c97b28f37eaaa98d1f3d460b73942aa9ba28f25eca02c7dde408db1c97976b3804afdbf69126ebf3ace95e596db028c86452836f0bd9e1303eecfb3a24b27e9a4c6d254b8dccea78267be378fc50992f30c77823984b1c68cecb7259ae0e9da5aa392680241135ae10c0f92d0d7c9d436e377ce1d6fd8b6ad160c9f07ad9e3e82fd562c0325c9c61f8d07c60ca0fce42ec3f073bd334edf03fc397bc25703229ae80c2e4dc819f095e29c64a5ae9db16a04686d80b8ad66614abb605c0693a7a75280cd74664026dc4a6f5d880cf6a84d455b3d4c2149acf6e133434676e3c497373d76adbbbc00aa0019753aba70761d1783da4b53f434f15fc7ae3b2a07f1bc2eefb4e31911f5663f42b41cdca81e453ada9201c503e1331fc8c358ae137114108aab2a1ffd9c40b0bbfdb19e24dc03d6573359c6d435f506339077d83c3dc283a6517bf1340e885bcf5ed1e6c3324886f5014b4e69f23d23a5777b2917ab25271dd214c34f876f00a51236131ddd09e66e4f957b6087029cf62b4079d2e41e360a4c24cec4c721e494ed5f3b29d7ff49d4b291fe749bd626c8ed9ca1083e7a1b5ea703ebc3cae629c7f379839a6484fd36efa0d27b000ea10de0242595f143233068821d4f62d274681935e1d3cba477acae75dc7591836487c76e8fff2ab81bce2e7dbaf58fb7e9914a3a34136d01f25e9fc252063958b299fe937e5ebfdf070777c71f5c444e98afce9157559d6c98488112bd5e52ecec74143559c8b1c1ada44a02baa1a9b3571eeb297466ee3a19041c3cb0519551da78d9d548bf7f871446119ac9004688065bfd4abf4a556250af0c94131841e97fcac453549c5348a928327910c8150b0cd389683d99706267174e70b0f571ca84b52ef6add159ad448576dbd9bca8a3b0f14acd12895077a972eb22fbf3489731bcf54632afebab5275430c17c04c6942d6aa31a25ce6885ef0fd312b7777c77e331da6c1c8bb445c447a06d99eb26fb603932fd421ce2a2d18d11b191b4554348bac6392fbb6bfc8fd6d08e6586e7435fad99a335d69dfc730709e6a3c3e5127edb10bbaadc95db2f84e1a42e40cb75a9581cfa9c601cffbc7ea802b58bb0dfe5fd62ef5f8ce70d2e1a31e1302d38bca8fab351f3fca61270eb3c05de752b6e4983a5f97e9497dd1f3f96d51edea57bdd45a1bce555bdc15b29f186c20cf149c399476935da374bb874cb66a5b921cce295906ab0777b0325b9a32f423a7bc3ebbadbe90d441bb6193873d91b0be85a4596685bd3aff4c8dd9bd97f80f1113956349c47e3e7db77e90073b9e0c3920a380d0379b826a3e3bf265c724cf738347a6af6ca93faa30410cad8c0584ba2aff31eaa3198132b30fcb4ffd5e3619a07e9a9745be82fadabbc2a75377ff4b704f494ce56a0ccc01a312075efbc5d98cb3199f3f7e0648b1cfe7d797fb88feb770aa07f722d36f9bf5ceeaa6ea352164cfa439afb8dd78db2939c7ca90cfed937b2255845c870201789e9478a0987d4340d5966a455f39f486b5a491a593c0160188a4b0492331a45178cf76521e0bdfe02cb6a7c755a0bd391c34e6cbaee909a36dce531c8fc11003e82e0b7ed5b948c1007f5d8ca22a39297f9a479eaab66bd7ae368aacf814d02faf66a042b9a189a3ed2bce584e5c361f8e2cc4ad2ad9c731ff4f152b8c861a6686e5c117724b3fbd4abff82108f92f49aaad76534d1cd1ebada27940707064d46ec2fa38805267144f8ee1cb2d648e669d6c8b601bb324773a2bed1caea6d68a3ccacc9c0076fc04db2864d7916d9c7011f114037f3973fed5156ab8a05397a33a063324c31c8a905cd658f4d5f9dbbf7c04b403557d3415a6ae8a5deffb90d6e6ed98be9a47b6f2a7a40d1377b25825dd6d1d4a6b525a646a000313fba5b1afc947e28dadb63663f7213ed16a5f6b5cd7e0312baa83ba1f5c5f23fa88bf0f098aa6640bbcf639aed67f83ed6da0b6b31f20c24a0833a367ad36e2fc5a3eebae6f200e77cb18b0acd451631d1f8e72c28059a3b2fc27c533cb27f1a9dd30a5acf213607f90314b9d72ba30ba036c0b223a2749718dd4b869e9e4b1c6d387b2c9012621f828aa5f8210ac97e0dcbc29726e65ac8a3a6a490bdb8ee427c619852306c3ce7c72fa58333d75918069b56b9be6fac612436eadb15173dcb1f9bd43d41f70a51d27aec41d8b6862de44672753a70686e57b17957fcb6a4a334e5f94a3ae95c77c78789a539fb9adc24dfb1ec71bfe6a84542fdb0a9ff3534dbe6ba68f47809f33071b2ee608468acafd2b1f3a7fdd07f9affefee5403593eeb90bb79dd02135e9ebe5b24397146bc4cd1040d70e5ead0728014d026e162a794286d5135f3e69b021e4eca00d2ab04b0fd2df409b747924779eec44a6fea3f9b0e49651ae253d1ab629e6fe7edb2f89c6aaab9b4056d05a0e2d460dd0068499910b45cda4e6459251adbb62bf7e89a95f1cdeb40217d430e9b0d213fbfe44e924be1940442e9a9361b9e622df1aeaf40116e8d0587084b0b679636d9fddd2c9b1fcf807eeeb0d22921c5f111efe5e42c7363edb8b39e940fb24708dc9c32a67a91231e0ad9cc1177e35833f3fe7f547ce0648ead9cca98561a0301cad7d11eb46d1049c20ca5693a29fc80e68e0324d6b34c366c27706608801e1bb50373c56d325e5ee899b4291a39c20a74f963ca9b609a333540d9484a57e26bbcbecb04c5a04d914caf75bd9b1cc18aa1f18ea79678419bcee7d2fdc8bc1a3c8e24c25cbe527c58c10a67187fce8b13c35faa2acbd402d85e8213c9fdca4189c1a9dad28a1bbcfb2cb33546fbe935082b8a50e4ad87cb81368be7829465b3b0c56e31338acca17f3751e2a8d012ddb63b3fdc971272c6b5b594d726fb1eae7cd557ff7cead9497bf8ffcd40c9c386a067842dfad36b98f2bccb96da13714ee754461328b6cf5ea9b83cb6c94d6fb5e61015b346f28a7c9418781a5504ae00826184fa056821111d02d5846e9b78edaf56fec6932d6d2c21d8f7a071686f0cbbeaeb3117b9576c78c54e00da0ab5f511ab10919e353635c0439c6f5b71513d05e04d237dcb55b2bb88773e3ad5db6cf42beabea2f1f8d745bf58b8230bb97127efa7f3f28403d8443387bbdd7c391c606e9abedbe690e5e4b2e3de47bb7684b1188608ed3accaee66a27d05be7cdd20b554daade65135cdf68ea0876bbc9a5eacdeff5d542816a484b6a65a2338da8f173e6b5a434fb02faa141d3d9577347ed9dd1c6931eebcea322524a6fa140800dcfacf0eaafb07e59cc2ddabc78650e72d1c894e2c9cebd9a0f08d7115efce4b6f9e2f61b85b3bd613362eec0d2a066509ed10c863f97422f49c3a80642ec16ed4d2d6b386f856bf7574c2ae7f705a5640be2fa2d48236d0179d8b5ba7d63150a2121f37804e349af3076a85605feda47b11b96ee153c994dbebe664173dd996f6d049876a7936c309f33bf32ee8be266f16ab1bab3f2260f78a4b894e26841dd0f43b3bca06bf31cbb4f81b38e408f2cd9e44ee8c82e1adc067bdb441cd82d957262d9aead55ac844f7d6f9b60488520d5d5dca02da59233df8ae0ff779a94ffce377d87b30200646b29ca385b23d3bec850de659f18c07c65a52c850af41318d40a4856ba88aa468415672cb2f3bfa3079fcbd942ca7a3b8d1d4bd7173a0f4bac1a37a22e25dfeccce29e82800cbcb25f9fa4094456c7131618b5a7f00d83fc66d06a6940fbfec01b0b9587159f821fb3b36b48da94cca53e767e9e1b94ce4dfd7cb3fa07cd9ccbd122e1e2c6b0da46563024a6da0a706d21f98598b7cad58006893650e74b57556cbdeedb51a4f7b27be54c7c2d9a68e65824970de3bb1b3b8d3d3346c2df136c6f1f952b7a41c99b36fa978d2faa1904b3dd689e2de88d780ea118856c3aa0fd13bb865f35f86569ad386278db07d9174d71da5769447f816740314e1ae89df6bc1ace8c31ac75efb0226c598d032f209a3e9912e66bc9addf58b5bc6befc2eb68f7ebd45e5614a7ab5d9ed21bebb67c20a193d842b29c38a1fab2e58833ebc7ab430a1c43b4bb010c9b11c547012e76ae12b62b11ecfc1e0354adae7834f769f6ae2a85c642cf759acf26b5a086a6aa4937abba9b6dc0db97f2a9a71a9e56bd8c73c39a99bb4d541cc0810f3b5a7b6a1d7b53abefd03884a0d689ac9a5dfe758372e66701e7a6fa38f051729e856003dcea9b16c616a0a46beb497196ac5e21cca220de1e34d570310c207e8c817c441b7c3985f519547ca55fe130bfd159ac78acda5fd89c775eee20f81b7b28072bd7be2d69b66706aa06719485bb9ae83173f71f368ddbb11b0d6788eb3217f959ca5ba4b73a64e68cb76fdd100d8be66f0f82bcf21c23c16382f48879a144ccdbcd5c5cb9cac5f78666cba514dfd0f442e7f7ba64b3defc2033a541a4ac55deaa4f9c4271520aae058f29542ba7ea6e4777f16f8f714d928726c8b8f0bfe7778dbc45a13d8f4a049c75c14498acb71675f222c5ebbe310c8518885215c0c30e4c1dd7c4341dca7b5115716f7d643d6b402ce683c4baea2f119405c43d0489a1eb1a39c5b60fb3fed4c2f3e391adb3b21a4e0acb003c9afee8a207f109b227b8f8d1027d47966a45b8306a722b188f4a9c3ea8eeabac1edf0179ff632aa0b9d7da1c15af98c070828f3045e55cc70655e855f38b1b9658641705118b1bb827822ec39be7cdddda8f000ebda0cb7229eac79ac3d3b3e4e2436a8974c015f147e6437d39edd2add2ce0f8ed6922c76ee86f22e9df24ce289b54fd8b1ec7de2d085b93bcb66df30eb581ab849fe71a8f8248c9d133f3349fd070cb1bbbd5099738a465f3dbe1e666f2cf5bb815af6d57435ebca6a197aefa96b887ae1b336efd16d0f60d74adc801b12b900339949d17ffb484b99f815b76c597e941bc188d8c73dce8b4e09019630b84bb7c1eed71dd7ffcdf96338740fa0e38c3a5ce087d05320a572450bc60f9be1b956be7bfd82b0656b3666ef33e4e5f463ed5884c89737561e076a93166f0474d9d0e778abfe62320be5a3dbec9a15dae59e6b954ee2bfda7b7ac27f7f5b8ed612871bf46cc60813e09afccfa85973142b9df22331173fbcd7b961164dc2b60c473f264ebcffc0fd47d6a561f2c1861af2b22c536e48e9433c6247bf9b9710c8ec630eee68a2c2426c6f62d762a16f9ccf78a3cb19ee1ecaf4fb94fa2881227e0a10d32738aac374c98c2b616b215770d1b3520699aff0b94a6fb413987771a3a8f4640ba34aef1f580f7d4bcb046ce3d590d8ca809935019605e3c5c1cd697df70cfcfa993117b551cabd74a4501c30513377d0033790b7f19288ff6c6e1e3509d875c2e74a664de0dc5f9760ca5cf07b91fac089c0952b2a385193550a22dab1f07717b971b9350e7a3c3dab216044fdc63a1ffe3827464c57de949966122e93f750ede5d6c5cb232ef4bcb2d5257d288aeca2938374ba85345970ec8de5486c01960a81c1bbf99e16c6b0159a320ce364524b1ceda394dd99ec57db30b565feeb12d8a59e05b20d27fb6d6d72e99994ef9917ba17463c3bbaf8aac6362ca5d8a9b532d1297130877534466db5076f0216ffc99640f7fd133cd57889c18a0dc96eebd7d83666a49903e79dc263e1f01ef87b098bf7d62166cfe5da411c4108eee4ba4d4592d738d255aa0d239f3a19b336d08472eb35cb077a4791607da72ea6fa4444054278f97305c31501d6d3ec62572d26cbc9108fd29871617532893e4ebb36b70e043b81435320f34fd0604924b885dc9e3d91df478d99873f34f4b5c87f801df7af9076e3649c46054929549f5d3178f73eb94d99aa930e0bd39344da86425c17316219892f40de9fcc111a4e53ad3df04ff93b556f8df939dcecf0796f117242bd17207fa6c6d263119ccbcc61facbceb726935581c418a8ee3b2467f2cacdbdb86d1b6c5d3510dcdbb40fa578e40f8f868401cafb510af478bc03ba0b3d83bea60c3a2dfb058e94c45428d8ca400f99849cc4622d0c661ca0c5f05a888f798ffb2ce16ff587193e9dc85e21f58e0dc4e65b1c5ccf642ea02d464d7010f763903b224cb5b7f6b8b3f1724cf40187e1f148e6bdd0ca43b89f9b36f0c35b075b72fbdf8bcc676d9f70e66bc3f8fd69c90b7cc445e0ac72f0e521e40ab1e3300a17656fb284b0e7e020c4dd8c5cf4d77178056917c27b5fb33243c273700a4fda71e6ff2b548e5c0cfbb336a970b28fff2e710632c795a450921608fc42108bfeed10b79e853ae21be00f59d2e1c36f0e780d5b361b3a5b9a61b6acacecc6b32e5bc1334d97ee51de83d44cda70e4eec0d8553b840df612208c59e77517c556b63072c0504964cec4d884ab1d44135da69b826fa7d4e6bc933786e75d8b8194e5bc3a995737c1d3ca7933658caf9eb87a11a81fd94d6f53ce9cb8fba9194104c78a4291902cc185a6f540fba631d970c0f12eabc48c3c73f8a9e0171a3cdb18ca514bb590ba8794e3a7af35ca16a1998b3e7a339c469c70d42984e04a8a6a340f15160a279d939265e9b29effff76d519900e866358af705203ddf9925d507863992fe3699e7403798ca88ebefb3e7a2be8510b4e057fcf177f224eda69cf8c5b9b7294938b066199edd569c368a4ce7984b6a2c4843a3b46b4b60c0673cd65b17798445a0b1b218d0e769c4f630b9097571c3e520fcd300675fc5da73c7647f3dbc9afaa9e3ee55de84c74099cd2e75811fa922df7cb84c862bc09106fb4fee4d20e9b2a858ab3137c921b788d56ea85cb1944f0a15fc0fa5ef62d607d8ae9312bfcbc9256259532cc8a0de5572258182b2fce5c505a7d33f0ea124cc2b8cccd2a81f0ce98b275426c224ee5bc9668e139d4931930897bdb09221e702662e91a5790147ec682667553653952badf1a487546bc501319ed10b583f6d8b50c7894e9f09c1c7f1f3e3d74d39657b301880b985361692c327f0d2e41d74bbc9eecc68cf470b4555ec84d1b95891950e16d30fe8691edcbb485cdfdebefc0f0135982ffa290cb27454040634c4bc9667f25bcee0308edac2ba9fdf328244cc0e44a3a3a0f254d823c46018dd40223db0481043471250ed4de1e14f40a2185c7581cd4e4fe7d7b0dc0b34497e755d32c870c22c70065603e33e81a5b611db3eef2e07d28197ccfbf77a148f2099d5b1b60b72b5ca4c59328799640d0c5ce64be2d37555043eac57b922d1d8dec3f04cc6e7f2a251d4f6f803202df30662b6a907da6220cb4065a59751eca9344a8740b54b851e710762fcd4ed6e507833cc4c91368e014025b23a1488093e26587603b7cc483a02ce5ba7514eeb2bb873a9251343d8cc35342188eea69bbaadd4646fa137269ede0998ecdc19ff20b0a5d3c1d8b98406f4dbd03fbe606f60b6372c1353ce40ae6f1c87a26306d9f352fb287e8706aaf9852119080b26ee6ce168e3766c2acc076dd06dcd18c783d2007250b4c9cf6bb9a430ef014934acde48b64bfdbc7378ce81b3a9334519972d95bad65373f9eeda37f1e008a292e8a518cded91a04b082346093078880294843734f466a6db7b3a71dd4d0dec391679372a29f2b8f4ac3083fe7a97395f2112b73bb9c2506e37b8ec724dd53f69eb50341267dec89d31274260745911776ac1d93dc7efd198f55f9bec503762fff93b352bea1b00a3285bbd90c17bf3b48ded0576c3446d4c7eb3d3131990126100f041e07c33e9be5cbe3a064dd11f0548f03f0b84fbf856e5c0f9838d5e3262d7daa31ac70e5d63149cd7e97933498e70648466e166d4c3471831b046c0130e9cb21dbd799062f30880b9ce0df100b8e6b21f9109e342ec0164160eabd9f723d5713bad1a2ef793459627bb0b7c56a21ded0a6026b1118d41a5a8ad14863b704c9440ba3c5fdd9e580ee8f17ba665e2a130f79363749f829dcf6d9a13095e3e618e193e4585ca67fce1ee9a690d7b15ec0d7f6d040027373db908f2a3498d872d34fab8128f8d563074ba0630e5688935a50753a2c378bddd927609b9aef24efdf5e5025f9970b4a49e293af2137a20dcba94f08f419f126b763eb5d8c67886af3a3f3787fd1b50d9a2623f66ffd2f23e1a9ccde5f203ede6bb1becf4a1a89071b5bb3d65613054872c09aaf6e73ece29bffe5b7ed48ae454b656ebdf3d88f23f6fb5d6caac04d1882bdddc790611e6ed17a0cf7753f20bb2214772bd65cf97e7793bbc30705947fb9abcacb1f81754d6a967cb6faa9ef5b1f25a0df01df68dc6c532ef56cd596b38d937035772eb97a34c41fec71cfa394815984d652864fa90681ee51b81b098a11434d90f9bdfb48c21b65b9966f5564208cbc2968fdaa904767f8d854b60a20d80141595b10a088723ee153eecc28bd345aff03a1258b8e9557860150093c8a5cfd839a42ce204f8cd2fd829b854704ee97d862f7dc62dcb2d61937072f9870236d2db75f3e0b13d3ca2747a27b83485037fd543eb189f6e986b74aebd379477e090ba7fe23893e72a583f7179cc6c89c18978f286692bcfe6df0eba8c2487fbc801d9c9bc03ab7c0031d972c217a798c97f83e6deebadd4fd709ebc12586bae2a9c47829c1ea1c9bb578886b5cd0e0fdbad7538f05341fc9f23320464e4ea1c36d5ef0cf7cf1632b3f43cd612f9639e4e9e40b85d076431a1f34ed9b2cdc47aa3008a050af58c14a955bf5e42cff7e3e311244d1290b49bb6107871f672bca64c47f120b438b54ec23464dc2f8c327dbbb45434ba3b382650bb1d2ed02bdec188b0b7b8656b93db52c9eecb42ebfdd995335a6fd3bd0cd7c55c46b01f0f0a7740e93e8b9bc6bcde581d874a0c481bf00072c0a022c97d98b58c84886989980f45b4cbb0fc133d51a940fcb657539171957a4b373c96ab0b0145202828f3977bef7c8ff5434d56f152133c5e1d223983b7494758afacefed4544e388b7657706731ea2bbac0f816540ea5e52e5abddd5d3832068dd396bb7dceb9ebfe97d87f0e83e9a0994cde615eced1b1946d67eda628cd252ea0985a5a89843b68c5424e542fd232518b9209a0bba1710890535572272ebcdaa748145d9e4608e41dc39737b166c39862b37ad0126d0c09e24f43490c95a6b6108b486111643ecbc1d85c1d4513e6b64df501cf0c7258fe12bd5684686df9e20604647b8a210481df81c0c0bb794571617f6d9cbbf5bb40b6a795f46c194b0b6ec3e9bd890fb3767044461ce1eba5edcf25040a4dd0cd3bd6de43722ef2932fa4ffc942d2e1f27ac982918c3c9c60374830028c31b4f03febf0aa6a0b0f755c60c487feb6117b12c84b78c0c1e368ef1cef5d07e4151b96627a4595a81038fa7e6faaf552a294eb885a442f99b1cd010bba4d88983a1e97414117e6efc1c076ade9fdb7ed5857c8e16cd2893ce846914a5ef87140da4584f56ce6f9a794f948f37de0372c24f1284ba0d4f7cbff456e68a4ad281f7877d79b2b769ea37371dfc3fdd88b76e8a94f8951795aa7c659fdd393b90608a9fc20a006ab935791f73d51b6fc5023f959f6ffc19e01f589e88069c0a2a3f0d3f073fd776109b162fa042d92cdce04bb1e913ebf117f073b5ff0802c85321bd16ce6ef4e0a1d65dfaa41ef43a9988427b98ad536e7ef990f45d5f68e7e244dbe68de06cadfca721f40f351e22407a103c4d88232b314fd88f4cf74be8861edf8227e95695cb5958ff30de78921f67bb5ebb5a677bf5dcdbffddc31e11e1d24b3b5c1b57f5b9e1e28faf3d718c476dc10744711cb88a6f8f3529a0e4ab81190c82fc251bdd3a63a878d8eecfee3177db7a0e938b5b6d6afb7da1331a361f3b0f70e0f5f917d0ee90c5f53964d9d9b272af4cfacc123783867c53b688555eb268b20b5c84a966d9950d333ef8d619466b9a33dd66cc29c7eae736e3e635ae55fe0e721a4ead3a6f2049de47c267185fc720d46dacbfaf8d1ddb410152734add9e6dbdde993ed3a3692af6f978adcd2a7f1c11f1ef54442ad17a5027f76fdf2719cdf57882403cda5acfcc45247b9ddbb6fb3ebac6b8d689392d8ed211ab1f3b6316192bd3bc5374a317f43d1abfa22f9ab3629f6ed849ace5fbe76b826a647ad7b2b6d186058e681aa46f0c65fe77f8aa412ab85443298d8754e8a54b7b7b3e8a0bd15986bcb5352bfd3de2c046eff1952759a5373d43f19b3d8f2ba6d25462f5834a9b975375fe24d8596dafa0346e4c0890f66de522f4559a925ea5fc3dfe7b54ac6d97eabf1c20a41b893aa9ede58e80134e3fcd90b465d590b57ef19e2df169fe657915853505b405317e199b7f00dd9486b39f3d98a745d399fbc6abcfc90d36d2fa7bce9cbfdb1aac2b6c55f4d9ad7b958530293874cfb008de89b456cf598f0d73b20c9d1a7228a773366a94df83d1b2a5d9bbb8cadc44e47587bf73dedd6de42a0570ffd4583133ea161457f3cc009d266c44e671647f8b7e9b629b64f26893cb910b7278e817af547c87486481529cdbb7cc6efb79352919cf87d9d348798ee7664a576161fde49a651a8730b83a19c504f1d61177642a32f01a4da386f8476aa6f904df80d9af1cfa382fabc75eed61b72cb928dd4c2a0250da55b38dd4505dfdf2fc583852787c081f302d1794b3eb212b59635ce0bead5e4c1b20d88ddf6b969b0facced21dd09df4d14df4cedd08493c858e334b14d84bc7763209efbaa34dfb1f35a04412bd0c71f93a9ebbf12d9702c66e631cec35d4cf51699ffdd2742df0ad9e86dca6e0efc10078ff0b72a6ee6566529801e3a1f91c0fad0040d0347bd3dcae827b6140cbe11f7e7ad87a3c06f6c222aecac2fd6a07d88f5aae217fa259a72c4ca7f29d3411a98d6dbd92e203f3ebdc32d226945cecaed8ef59b9be19f6f42457e146c2f0c83923bc163e46ad2dbd3259fd7d0e434a60d85e6e6f53f3f2907f761a1d178d170ddd0cbf3a38d0d6cd5e780c193a0be57a5fa083d5c20023cab6aff94c7437e44f1e6405a2dddfba4479750e07a12536998c4019e512109ddc9ef505ecfa72dfa4ad4b13bda76a97dc4d02dc5dfddbe5de3dae8d49c0842b1f31b900ecad06b29b452d6d5eb176207f6e8907e97b6509cb4e3ae21cb03123fbbccd3247b94d9be9aa7d96e09be049265a3feb46b0af2a616d6c9c8fe7a828217dbf292dc8721e6ff00fffb9663058555a9ea14fa2bc5c3b2889ccc05f2e7cd8ce227667b91f3c45b2264feb0c9886e32f8dfe843a771ede012b69fd019803024083e2151d48dfb4763bc489b81fd1690d43e411de9fab1127ed89678fb2c3b7fd2e9b31f5ae86b5c40ca9128f07b2947b683f70b004d60ef782832bb71ac22761db267df9e68499e6572e0e8ae79f8b6eddfbe2a202a161e33edaa816242d6ac3f1ee0ab1bd05b59c5465648679b4738a308c86e085034c098d3ef7172f757c751c9fc72879f1fdc04095cca4fd39592789271603812fa2d5b57d14eb1a6fa0fa7308571a39fa3f05ec80acf819c873ad525df9a03d1f867d8f74a592d6e1ff5272157efbccd498b72dda4154d6de12eb1c325fe16754dce62610ae19edd8cb4dafdb38cfc65edbcb8b696c4f95072e4193eaa2226b834ae31ef4d0c0b1850b80a58e3844958c6ef4b38279791606515f55fe20f6bd803de2698f6d6feae19aae4d93f0c2a02d210059543afdf32a0370d6a71a6196eedf80c9808b955c30f88ccefd404e4ff6f1036a5be3d5bf171c6842c3b5929b20b0ec9859abfbfa69bfa284dd978079c4547dff3d3ef9ff8441f8ca1ced84627295fb8864f8201cc5866bd00f5a903d01c20f70a6e906f64c03ddb8daadc89cb159b3b3646bc451e5208c4ce2c2ab08aa19955eac7c11094b61aec410afcec7bb2ae20be88e24a50456aad87f8a42ffe399dd7e197210f2f97fb1ac358a2e753207abbf49a1ef5ccf4e0fd0fc0f3c94a7f7edca36bab75ed6e752cd4a72e5221f2a7f8c28f21f1dad862ebd4e7c0478ee1f7a087a709476121ee678fe3009a9f29d0ff7a067caf07c2fd0be41dde9b2e2c52353325b4db798bd914700f918d6c32c1278db0034e6175dce3f402993324537b2e03be6c59c56a926a0c343e0cc2c433dd6ed1232816555afd8d9626bde84220d94e02c9a5251a8c995eb904234cf7a7f8583c5a31012e8791d03ef388d3761c82fb0b7a58b900b4c562b4a71eb375145b3ca64a5bd3cf962c8b975615932a47e28c0e1e8cba2c39a56d976f35e8036afa5da2fce99e6258b1536c083c90e0c13e826708830f74106171e6993fdb6ba33f2c4236687d06f934fc3a327a367e95aa6f0f8e65e6cb5ecbc7d5b9490be1ccf6296ce1d737b475495bc46c6c5f8dd9b9cd9b1d7f50fd28c284036a983ef848954b3472a775d91fdac923451878fe91d3c12076f318e23c6ae495d259d3170409ef6b2253de305ac234c6e880ccfd204b481412e376455e78e0fe15ed0a900f64fe55b72901588efac596c82d2ca3c2aca517e500e1b23aa9cea8b3af12125845ad9a71718e8acd53f587b354c987030de590323b542732494f1eea11c4bf09bc59cfa49af15eab6696c70c90df66f7ec488c02f43d183fbb60d6c42369a51f07b65f52304ccf0b4d80a766ba8db3d53d24d1707179d4382de1bd727b038e2d54e583eb0568984a8193a19bc0a58af3ebddadbfacad2f812853ec0c063c3b8fafd96d16714b45bb7ec162ad182811e1f2a64fe826830a9d686cf7453e56923530060e62420a343f1d75dcd1dd0359a967bb8a4b50ca212b70f7ce93743ba014ccd7d99fba0dee6606a1fc2a21c100fd2db3fd275486a52a014a1f0cf74bd7a1fe32e1bcc9e2690cf3adb3fc9d9277a64fc38384fa00d9eee72eba8323a5b1ed8705eafee985973ecf60fe51202634b41cac26493e2bab462000a76785f38368b2e107da77eac5e46a5bb4699f7d4ded25005f48cb7e7bb9c9890a2d2d367e2b484c5fc65aa3b9c96badcc42e1fc2bda51526c7a710cf77115494b468a200d67a40cefcf8acb0e7d7a9a664e194b640c256b0c9bc8837fe2e96199e49aa002fec3da848ef00f49ac00b5d60f9e841be391ac1772ba36fa2810459c40eff8a4e30a4bbe563ca81af27820d8b1f706a55a3064820688a47033db08cd4f4dbb3618defae88e5795a3f3dd065ef7d63eae566f1b9ed7a0f70793d639de9214793d7846c53e1e7d771a7b00ac726d8ea47edad1ea00e496a02fefda58752593a7858a5c053f54790c3e8c9bc12127b0900eb18687ef03a1c988b8d4a17b4ef835219e75375897365d96b2cab52a3205ee9c8a2fab98cee803210c96ec0ba5eb75bdc7230e0eb9f839e050d5e1749684edec85c44387bdaa9c2c7781f0af89a76fae3d77e06540e80778f28970e001605074048098945f491c17b9f214a8759891e59086463cdc3520936bac043cd0f84e81efff1a7027b5674b8c561983f38a0475af65e3786b6eaa82661bc1f1db7f85350ca38eb2560cfa195f56aee6cf3e0b9806e81fb88f6e3dd5a62338e98271aa1cdab4dc32b567df90486091b47bba4043428ca314c795e2dce9507efae44c11f6a663fec4350fe55085adae0ad3c26f2519ffa102663984709d9a8b65606711fa012b47717afc664e6e51988ebbd98837e60c0a8adc7af4484527a6f44f8cd82db2352e705386003e2459b808a98500ae4beadbfc4a505192380e1004abc8019a9883a0eada09f7fcdb349b2cddad2560433d032d1675aebb20216831cffb29ea9021b4dcc40968b5df1281614f06d0c7fc28b50049a34d239b3e4430a4fa970000af8aac925734572409d87d15d633ce5ace38038cc6cb345bdad87816aa669f6b5c13b0c37da3fdd76c19ef09f5e4be4ac2e4aaa8bd028261de630a3076f8775ab1ff7a947cca659f7c6b84d08e050b8059db1850e3265e4f3f914f8bfdb14c1eda874dcdcc1d22532bb57d4fa3079db678f572a9dfbebca979f0e4dd36b3a5145d3ba5da082caa401a8636950fda65c6c35e5265e6c5e3e2b011a735c0240895eca28d81e0988e4948738c0f013d5ee5752392e6816def22350495b936c84b653febc7507684d93a3b80fbba53cbd283b9ccd1491dbc41b6d2b57f28d97a176fde8fb3069aeca915661d0a0652e8002c073112f93732d07401af4e05943dce3b079e7ca48a4c140088ed1933d059c7528f8af7385f001eb24cd0a1319d6ef740379156b83966ab73338e9ea5eeb05298864d4c0b1679fdfaca17e94b44b8f63d3d20999e3bb8a43e27c7fb17d62a08fdf440f5f748b0cb027f6d9918c0c626faa0638c15551b10f5bcda98f1ceec1bdc7fe66d07caa8f75ebbf310b7d6f6328e6987c0f11ebb5fa8fc9ccf16dceae90bee4cf735f1bd488d16fb0080c6ad786396e09f6411df2dba88ed890a9be9cb08aac6a9cc31fef31391b7a8b4289780193749664e40462160f70441fec5c6d89c54840c499e14acee9e09b39188c861d478492d0f0521bd0dd236ee8b5a40c90b4f9bbf4a372130ecda37a42c5b06aee2e7ace4408ef15f107027841ee4119836ea79a6fa7aee9115b93952ff43119a311bfa8cebd56b363e0626742e2cccb85e9ecf9a7f7f9ed6fdd9c01f7eeb0f94edc38b697702b7f86a4ca44b5a5117844a5db2e3c40ac7351eed9f38381e22277607f50d5b4ed68e0bf298cf29dceac9813a7e46d4cffa734556ff460eb67d58edf27e554c0a88925eb93a8a405f6cb08bd71c4ca242f86627614917a45e4f7d050d2bcd8564fd9fb21b51055126d2ebd7cbce30bbb3e94d56c07386e290d3fe11e6e355e981f1c7ac088f303b89ebe17ea62e7d3c89bc6f2e89b67c8d4ff429e41b80b4a6922e1f3fa022306afe3990747e1ac8d30b84e5b78eb58c2fd332c7bf3c715fe1fe16eac3d3fe7bf4ee6178be9c9d12afb7e2622315a4aecc6e758094ce0ccf30d317290cf43acc3439d7307d65ad942787925b687ab6a8e64e90c335412c5ef0aa212d0ef3987fda7e08be228d8460d1b2589eaa53b0569a3d811502b15b5421b0d75ccaf57e45ffcf8e302da623d84f5cf71a9f5580b12823abdba68420a5b901301200e03e15fb8af1e0a7c3dff1df0435b7f996da225571a6e6e28da00e6174c735f244219101514c88a6041168a0d552fb0112a608e5afe4a4d100b724ded811a9a886ff67f588ddd78bf38958322e9567ac18164d11139ff80e58341beff18e23faecef1d77c636ef2c4b53b4b135c0785cfbf8f90f606256dc9eabcac8f944aa868fc741e3377dee41ae12bc14cbef0307140e0f17a3ea5b1137c10f1b0876dfffdb0f32aff150dc9d39282d9c4d467e0ca14a1d8c3af99817408dc971eb0e3dd079cf4bf563ce1733f9923594ebcdc5eff9b209675429750f123f5a54b8c0dd2a7a7be9f12e9b92704b69927bf4fd71ec42ef48ad165cd9912d5eecc10be0303b9dc55d5eeb8279c96d938e49f5675dd6d8905683bf8f883503e8f575af5f3b85e1921d34525421b33208d878956e11bebc5a25de8e17d5f94621c8b7a20c7c7796ce658260c15911f85c73de4f6cf6da7e8cc89354c8d9a839bcf1936f5cf8a6654134e726d6876c2a621d4f8ed729042f2dc637184e811471f4ee09331c5fad1b3688c43904c4bdbbcc38079674d2fcecde9a6fd1aeb4e8ce2867315ac6281c03b4097e88ac3a12b9a5e127ea321ae94ed232ba90dbce7d26b0d7852b80716d2800e34a373d9306c9eca0bf3ecb1b7af44aa45a85803a182af2e321f09457bb55cbad0d375b0b17ec20f98b65eaf40bfcdbbe2b8031811b689d17d719390bf973f56a7d8ea77716c74248ee2abbb3db47581193b2c06651d4e121f4cd25a829056c4439afadfd1753f81c186066273938cd42dbb038ceb4f44dabfeb6b47d84c848c1a945f3932335703b87bda8ec135c5f85ab0b7bf571380835f12c34bef5bfad0c91de1bd57634bf4910f089862d9b724e8d25b5a5469336d25cfefb99429f186b4c184263bf1f48799c6a94e0e889babc475e7069760827e661dcb212ea3c011209308e5036f55f69ee799391c0694f362f3dcf8a3c5a7d99500788c013938119c7a1f445f0891c3f4dd474ce173432b78db066e7c2efc5d0d246bf530c312883eb99df7297e7ca28b4486ee08e5ef30b279208498704748d0e38e10687141f0d1f9081253c6cecf1fdfa0b2fdee1274cfe334275cde3a41011b34ba80acdc1a0d4b9a4090d5c2cfd2bd1eb97e324a20d644f08ae942883b2ec607fd0b3c99be57f33f107b3fad4bf708f9b7f47b74d87e4312652c29037586b4b8cf224d695257c767ca2d7b1d00ae36a0585d9e0a50ec7176866ab42bb41ad73522f2e39804f94d8194564eab123ccfb8a6a9899fa660a791989d8fced2f1fc0388cba6fe1370abb42d635bfd5930bfba52979be01f9e1606eeb088c77eece7657096410af87440b80ea823fc630a5f335fa677f5225159905688f0272c66e00a1bfcc553de04e5015fb4c4e63563ba3026bc056f36d100f551324b3879c32179e1fccb0e75471377775c19d0c9c2ea2235b9eb61de4192ea34a4e84ec94cb58c1b2df67584701deb55ab20169b8660fb0aea96750e492fda93462b787c686014867e2d11a08c04c85d4bd9cd5c2b1b50be0be398fa0b423a7aa1024f37194bf7e7f68c00a446fa749a48e5f0108294dfc6247ef94c8070803b89c2eb8c7da2838f6428a3fdf8da2b85c3e32aacdd98b7db4340ccb1978fde0de95ac2bacc7ee5ff32d82c12b69b0f3e07227c82551a6aa96bf19a48fed1ea461f4d44ff27c04f846c3b5a3e81beae7c980e695a1a52baecb4d70dee370a1eb287c8cefb8807b2caf523c95d97a4bcd42dc828e41bbc9273d0b3153bab91b2b195d82b4cbf7bc26d3c4929f7a2116d275c23a4db332d96918b8c4c196087f49feaba1f4bf7682b27a8b5290b66955a560730dff498f8ae17ac102c63645323e713f3da9b947329f50ffbf74f4df5b19f0fd18177e9bc7cb643fb12681ccfce90bb5d49be2b99532c7a4d3a0c891748b3ab9cfcdff41b6cdfffe53912b56468d27e2c9ce2fe08ed085b6c9bff11a90f5ebcfb1753e5ff0903f19761364422d4745f93399895579a9574642d0be5503c79e39ac525db8a43aa326b6812f8569188ce2887a36a8ede141f33488359d14eab7ae8fc7b5d3d5e036e8b77c17d0c616d2f573959b025bfe4c3a63281f259f1b68d66eefaa5b2e22bbef23c493a8f382faaabfc23c1ef4dcb3522b401f1f33d78233c9feb0c289a82b3c12652eb9a8cc55512d2546200fe3473a84ccfeb252bd8be2ce819e2d6d84c96bcd4c38fe1ecbe22ed72cf9233dc6e3790fa64ca36192b419af4284f38f8b84e26ae0bc7a3dac30bae2e46f83f108d8ebe480d3d5b9c2fb721ff8080f647aa1f2d66d8c5b5a6c2b4b6e2efcd67fe9031efdf4e3abe065296b3d3c0085bb637117be75dd607a59b90e37927a28f6f588c8390508abfc0dd651fb372b452f3205b760b04746162e24a63c0f923871ca84972840a26dad692f28c555d2cc7e2bd6757222532c564d2abbad134f9ad74b13f766a16a6d519f891f1327ca5c936b86a212cd8c486d8e021f323764f8f12ffb3a636d8f6b18dfd8afab6fb9e92f3e6366a330123cfeeebf8fe49a4cdeb61e240ff13a0f22606b8fbc4f0aa71c4e67f8c7f433da937eabebb81bb3b8bca4ca4c7db511bee48f7b2fd65eda386093b6e9773f31bbb5d72ef0381de285f44f170cc3da0b35731dbb5713f4704e30d5c2171f697afb1ae284b4ece5c7a87ef71cf8e1115ab6c177655e9a09a7c02574783dcc54eab5c3750b2605c19cb8e23adc7562c7a50dd8ec1e2d9af79996a63774dc405705da9d81306e425da68f4f8ee66a12e7f5222fe791d37ee00c6c95ffc314879148cbaa3e8e320c463bbfa5704ed648965f6995c3acc5f42e7ff53f2d453f3f69f192b3079f818795206b3b11df58ee3d73af915a49a5d2bc218b0821cfa0a37a97c1377a51f6fdccb01a5a38bd2299e7ef44b309ca463c102090daeb76f1655236fd138a76eb3d056491acff99078893a42aa0db3a50ed402411d46323e80c0b5e7390997038f1ef3579b373e4a215c8584be5ecb34c9ac435e65ccc71d2923f219438aff32180335efba48b4b9a11a09eefe1fa2c0cf0f644d6e5b5efa912f42661904a157b4d82a616171b188d0f95a6f29ae6a75ca9464683a7ee55ba28e10a755f68a0774def914bce2f9ac7078173cf2b239c219e0f65e71149499023ebcc44c0c3c60ae26e900c3300cc3300cc3300cc33074c4a0f97ffb0aaa0999929cdc43468ed091755d5e534a52269992e0ccc4979ece4c7c3a33f139ffffff91ddea0b7c0c8c0c076179e94b45445270f5aead0e7bde9a4324ccc65d868e314482d04ddbddd01739964224f758ba98bd62e972234492c5b0954b1b6db55225c8918048c4bf04393a12c904800b600c2249c78f2a9ff9f2553709f8b0f181800f0960e1050c4124476df34b42cadc7c1cc28319f061e3238b0f2db0f888442211c445a40cc40ee222b1002310c9d933764e13516f970b49cb9170ec87165744223c6ee8e8828b2e6000228b1ddb6365b977ac70b3aed0a4322dde9821f921010a7c7c48008b48a48c201221418e1b0bb891231221418e1b0964a900e30f49b536f29352f261730b49fbd0e28a8f08053e6c7c340087172115405c1c00d400861f92b47c3484a98ea7fed487c4d26ad19cd4759af990944598d97a68bf94827b48d43cf25cffc3c9b49e1e12fdaac23f3f95c5479793596c01b203461e924e88ee533206f129966e01030f497a2f6679cea8b8e778ee90149336d1d9fba1e3ba2b76482e7539c237bfa4e61e92c7bc805187247d516b29c65ac89047229148064c165b8080c0a043526511954dff7c45ffcc21d933cd3d07e149ef5a42d2ce0160c82131bec6db86be0fd965485ab1e45c01230e49b9dfacf23cd4dddb2f7a7b78a10347030c0260c021b1359338351f9f22436f48aabfea17cda3b7977543a26b0a72c388bbeaaa6d48aada1c83d4bdbf90230c362459baa7931522173dcb8103640d495a64deef79ce20ba1a1264e68eab5bdae3c26948eeca146f62a3a4bd684872f98c39f62b6fd73324ef27711daf6be163cc90182ee85ca63497c658860495348af6d6b30c3a32246b6652d2c365ceddc6906c5632643f5c6c4c25312408f5789f37ac66f33024a5f193bab94bd4074352f8ffdf5ce5bf687f2141c664a9ac3e95ccf65e4874cd1a7e83b6d454ea42e25ed89293e184facf85041f0db275664c4fb885e411a57b745ce7ff722d2496b69caad1475948f64af576116a2c24fdbe76eeb46f41db7d85045dbe95f543580e765b2131e5e4a26dd55263b8ab90f81ee61ac4aee96b9b0a895521e28212e9a6624f2141e4cfef2c97e50c5b0a495e4acc4c27d32caa8e427238db8caab9629590a190b82174c69db77958f8094959a2972a67c37c6a9d902c9ff9ae479caaabb609c9611e4586093121c1cd844ee9d799b95d4272386fd3f334632aa34a48ce6cb59552df87bf68121236bba66db9efa620242485d2b70c6e9d59c7232486d1cdd6d93c9fbed20849bb1994a8e7f591a92c4252e5187b3ceb8708c9fb394675a896ca9633842495829eb28b1d2124fde914eb4d3ae85f320809b3d12dc85e35d1d60021613d3da894e436c8d5fc20f947e5ba11a74d69d5f820b12e6ae80d16e376d2f42029bec88ed174429fc6f02071a4a57c9774c62463cc0e122daa2825ac4c3a48cc8bd62d9682b434e520e1e45874139b948a493848d2af34ddf4fcea166f909c7afb662a9dccddd006891b355b949329c5e0d720612f77bbeffabfb53448ce59ccc4e8b5cc20c94505d5a192bba74c914162899f8db5a71824fdb6a739b94e1e8241f2a9cc9c936c66aaf40b92cf7f4b9d34eb4dbb20c1536bfe4d328c16246fec73919e1a359d30589020f62abc8f1891f5dd2a12e37f4c27478b9865a78a6411fdfe759e56749e8aa4ec182ecca7d2a7332a9277376c57a74e95d74e91e81ebe2ee77535ad99291274dd69287929ee95aa1449a74f28194f64a8ca9222592ddf59e8b5ec54b25124073df55d59af427644911c847f586b2514c93997caf795773c0914c9315784febbbbcbe3f81b20e847b086de73200302a296c516200bb0e21389a9c33cad099952c61b7c010484159e48f6af5c42a7953a91989741a7b2e4a2f33b2712e5f754da58de269234ae99a69841ee6f0a0708ca911fc0c1c5170a38a782b04213c9651927ac742c13c9e97b2a54a89c4c4dbfa30788d9ebf8420b341e00088415984814297f6b5953fda9a04b246f9d8cf6b96d46b36889049d4a87160b2d246d0421d073082b2a91a4b5e683584ed1635e4a24e5afc6defe7ecf519944629996521ee6d5ff11840024f1df0041bbc003cc05200f564822293db626993bc6fc4e8a4452be117a93fa1883e710249282101bcc36d4cdeb472479c79ae76b7665b71d91246ff93a69ad0b3e6e2392b2bcfac49f8c419e8c48ec30da32ece2c53db9880421734a2fe61bf25adb1b8c63470434ed004014562822f9448474932aba54502292d4d7e575fe1caf5a4424a58f9f29a78d29ad7842d24072b876c08a4324e6cdf2a343e88a9e6388242b338be9d229a1635c88c43b99b19da5a774ac42d2fe03fe1fd8f15d98cb61052112b4ace6c78659101e3488041d634a5aa184acfd2c8804cf0f1f4447bb701986a49d644520123fc35bea6ced523b024492d04aaa6379fc87e498f1a1fa3c5ec6510949bbc1df830748086e78d1c63eb4b8420b2c3ef6adf04362c5a8ac393d6f2a4e7d480a0fbd2c51bd29899c0f494a9452225d6f84ca7c0f499553cb5d53ebce8ef49018cb64d5c4f56427390f09c26d3c8ae5b1a0f38a87e4ccca1e192bc8ad94de21315f662625eab143e207e1c13f954a76725387c4d0d3dc1525f767a143c268395963c252c5abcc2131a7d9f9d2dba5d3c942d23e8003c4fe039ef06361e3e3021f363e1ef061e3a38717108844767c08767017e929f09e238b48241289945d009060851c92347bbc7842655c37c521c994878aa8656a2ac121e1732dc3e352eafddf90e449dc754c64b57a3724d606193ffd9ed08fb72171647d792599c46294d89014d6154ac6590b954e6b480ca2bf59eb3e5ebd490dc9174f2f3dba892837a521417a6c6aa9fb28a32634247f564fb10d17849de90c499d475787c6d11c663243c28bfb97ead69b90496548502564485319eb3698c8909c4f552dc831ada692c690581de2229aa1b27b490cc91a36bc7fc758a972290c09fac44fca49df3b2d8121b192deca78326e28ff0b4967dd1d2b8df04abe179254d0a42d8fce73b1ef42b2a512a1bc3c2e899c0b09a7cd74b79c8a6ee8b79024df3c56fed8eb68bf16124505bf3f6d1bd5f5672139a45f8aa9b01a133f16923e297da5f19d9398ff0ac939368a6d0cbf75f7ad90f419736177d9c9b35f85249d3dc71362939fd0792a2458cef4ff73298feafc14123d2e9d4cb12cf96c5e0a4996151fb3a92d8ef0a390fc21c34566de67d21e0a09aaf36ce7c7dec8ea9f903c9b496bdefec94a724272cca72083de3bf352131253253b4d0d13d79f0949f739a8ace761a4f94b480edeb9829a3ba9bb2b2159764f29fd36d1b52721299d650d563a4b9c8e84e48fa6f2d6cdc354fc0889a36288ca237473b48d90201afd3efb62e9921621e1eb53547bada94f9d0889957544ca660809d6ebb9ef62cace092139a797d166397f6a198484f7ce9d25bcec72070849af1e6331d67cd29b1f24f57abcd471991af641d2cec86dd1fa2617f62059bb65fb335d675778909c5e3bf67b58a8f576909837738d7af5105d078931cedf8e3ef5fae62039c72e26d933f94f390e923aa7e0ed7baa7b946e9034a242c9147e64850d924ee36becafb0d942c88a1a24fae6ed4a0d16df6d64050d124c8a69a79c97a66164c50c12456c380bfd8c5d17592183c456150d2ae7b31c2ab2220609e7a57dd3d4659a0fb20206899f82761f95730a4283ac7841524a32cfec59a62c1a64850b1284f6b724aa458aa5202b5a90f862bf3ba2c4c7c790152c48d2b93655f896af584800abd8539e53fd0aa922b18434e9295a44ac291589416bac0deff2573f2a92b4f79eaed1eb1dfb291253fbf53573532475c7b9eb5a121eef522477abbaa82d1d744e9322e1d3dae2977e2d937b14492e22daaf671bb46d5124a9d5a7d0b90e45e2e9b0bb939fd5a4daa04812729643965042d9d99f48d29bb152bf527930db1389419af8f9674e297aba138971af9f266447119ee644d28758da7ce7f17cd39b484a29eda75c715398d89a484aa74389cfd3cb6be94c2485b3fce8154aff7b8d89e4f5fa710dbbb494f52592e2df2e74cef2ad6a4b24dca8b1509b32fbe92b91acdd49684f7147ba881249667b722ea9791249e253b2bde441c3bc2c89a471dbce20e66299cb9148d2aa55fe29fdbcad0c89c454626389acabb4243f22f17494eeca525a4f871d91a0bdf9392baec9fd702312d544fc3f656cda0f332259335ca94e21534e0f2f22b92aa875f5ee06b9614524ac59fb75a9a6998613913473428d27d3385a64442476544da6e25a8e06b903e6973fb4c0e2e3a020230e496aebf1956a429bb864c021e9d462b45119bd224a6f489ccbddd9563659ea7024f204196e48ca271f1e3bf5065db90d4979376e3655e9745d2c39d30ac86043b295bc921fa1b2785d64ac21b95f64f251756e966342d24aa4011f366e0a32d4905ca1622ac275b7e6bac16307028e211c5ff04003c84843c2a5b84e0b4a4d6b500a490309e1e1858eb2bd42061a12b46438af0b99d3ef7c86a47e2bad1e5aa7d469a320c30c09e2fd2976082dfb6ecb90d8517436fbbc66397b6448d84e19214f0651f56a9d20630c096e17639596e7b9cd90b4341b1f957c48008b8f2cb60039810c312468dbb09ef42915a39632c290a05ad40661a64bec8238824864419020030c5f4898cf6b9d2f256155b18323b802c8f042829acc35ffcb2cb153489a0e414617d299477db23124ed4e0632b8b0fecbcccdf5ea02195b482cfdc9b4634a57aec19034906f820c2d948c2c94b2f5abe8b45752fed42cb60001400e646021312c63e7ee988485e884a495c49320e30a6b8e1b64778a69f385a4fd0e1d2039dc58dd0a645821f14c45533243fd05654722914824725676699051853d77ca5ab13aaaa5ee8ac14e5467d827adac391b1f36f4f8400615926dc745b55f8a135b4f21c9f268f9ab2ccbe9974252af78086da23a834a461492f3e9f4f9423dc8cb18a4c77fa0c75f1189ec0e644021d1a27c75f6d8d02fda2724c94a5add6d4d7735c60949992ad6a9b4fead7c13122cb5a99171cd91291392475f3e2194c5969074b3264f4366b4af78a184c4f2115a47a9d5dab64948f8dee0b1cb93ea6e90909c35749df55950a2324748109e529fb9dd9cf4abcc40861112630a96299d8cbb97531112d6dcdfb26555ac58224292989c98daec595ffb1012f37beed2a24db33c4808c9e71e573c75f0f5980e42a28a2995cf74b659110321d172446d998a7f902093e638f517c592cefa2059364707ffb3b87b393d486c3ffd9744e44d8a4880f63abed06207060c063278907c7a749215eb1099e91d248ecd5ec5128d7d2624430749e549ee997fcaa70d472291c8ee909183049577a7d72d28517fc241c2f7789f18dd603e9f1b248f0e9e4b68bbd820d9a478bd5f8c18ab5c0d12b6b26c978ca3ad7e1e2091c8bbc92cb60021810c1a2485c71a7d9d2a7b076d165b807840c60c12748efe33a7e232481895f6efc35d4b331b83e48e959577ef62e729810c182428b1cf95f77e224e7f41920ed7d571597537d31d90e182c4b46fb37cea4407437a7041026b192d48ea8ba3945063e19b4e32589098e2ee8c86c78bcbd12a12536f5e5b3d61579b7d05b430554582b8ca5815d6c2adc3a94812d2c3299d5355396ba8484e2944cfed75dcee4f919441a7cfa8be0f533aa648b61f9556bce276435b8a84df2d0f9afd4bc7a563902231ed6ad0aa72f7b1330488e02bb01a63148962a3d492967b98fc185124d6c90e9522963eef1d928627462812743c26d54b9b0b1950248dfcb74bd32986d014e40338627ca214254e43e5361187a41d4f74820431389198f459523a99eb6efb9b48506d153a6ef6b64db22692dd3f7f529edc4c24c5f6c75341638b102a26923bce7c8a33ddd9ba5c22c92dba558eb92bb9e92c91b0c1c3c47fbab2cb2c24ed8b1d3dcc74c7f7e002635422592c3eb57a558a6717f522062592b6b2b54ce78ff3f2e8f13c78200073120715d76bf552caa737043124c1d6e7f5a0524c35670deee8a12346248c69ef635d26b9c1c41e03129a250fde9eaa317d623c22c1bb4a084fca2e67d82c80021f6780188e48dc191d3b2fc598d37b2169e3e38a372012897c688185d90f6234629dd19bb289d97c311b831109a3dc66bdbf37b8e6622c22413fe9acc93c6c87ff2611431189f6322aefcdd68e25212462242231e5f9aebc5c47cb551131109198efde82fa7499bb8f0122c62112334468919df4bf5529242d3fb4b8e2e36f78037044202f8b2d40024086188648b2978b1f65d6e13b7f861885483679e2834cf94975a643d22a1089ec781e63884188a42463e944530e3a056119880d225174ffdc47fba4188248b08a8f29e8c5921733c73a10231089a552acb867a9410c40682f1e33c58bbf43d248c03b1e99187f48d00e55ae56e9346d3e24adec438b2bcec920861f9246b606cdf1ff3db512f0a105161fdc831570b810a30f8949698977795ed1a9a285187c48b24ec1f28e9e52bd5524620662e5b2d80204005788b187e420f2378ee769e91163e82139df7d2a61c1c59387b13422461e92621ccd66629f73e5080f09a253651b317ea6c3e80ec9e9314c94e98eebbbed90943f27edaef650cd751d923786ef52df613a24bcbc8f860afe9a7bce81b5d8e017345c057d2412899c81989a952cb60001802762c821a9e3eb43e7521c924b45644fedc614730687c494d35cfc5097aa93de90202a98568fd1c22f7ae8e822c111810f1b1f8e838b077cb1e3bd98c0878d8f0f1b1f23884478bc8e2f7a0c22861b123b5d760d4aa9515bd23624e85c931f57c9b2a6d4b221395f68964ae93fdac715440831d6901494de18cf31c520cb53a210430d09a7beab9e9ad7648d4240de045cf0484352eeaa323d661be2b45088818624db9c2ce7cedbdff93f43c296c8dc369fd333e966487ccdff4c1f6fe3622e030140136294214925691efcd294494178f4c0c167ec6c2006191294bfc9abd4b3a04b1d92b638628c2125539e16195378e3064722da238618123e495f3df91d192b179276628421e94aa71837c858db9e1c7923061892530e1bba69a74f5c3e24f13eb4b8c218cb620b1015c4f842d2ab75ca56665ebae237760831bc90d451f3530cbbd183287521498de8b1d5a0662efc5c48d4aff82ed9d51d8d6f214198577e8a95f9378bb49028aa424d74bcbbf9370b09f24fd46e9daa4aba6221e943897938bf5c9acb2b2475eb67b88d39c8868d159244878b23b4e4c449b10a492a67d7df6f490bb15021297d7c3abb14f3c8cd4d21314673306d2f3fb53b861492c6b6e32e962c0a49ea2986b3f1cc2017141236a9eb78daa257cd7fc21d35c9ded67cd4dbba88e184e4334fd2abba4aba5b423212e11189a09a1d3d747020461312cc37cbc6790f711b9990f8a9a40875299408bf428010630989ebd7aa96db0d2e42803e88a18424a163904968dcb01ed3e791848453167a39b65e558f8404a1b264af259583cce908495aae4bbb36e5cfa61192d3becd7da877b6d417213905956a614622689a74eb88a587282506620c21396f30b124eee4e82a8590347aad627f3008c99965f3d7f8a526ed0021c167cc3fc7d99357152f74fc20d1bf3533bf443e48eacb0d1da24ad55fb4074932dde4c6645d7af81f283d88c183a4b2bff29316eb3abf8384d323d7522ead3e99749068997450eaf2bea1fb1c246936b72842f9ad577090204a7f122136ad8daa6f90e8d7a6dfa6f267ceac0d92635a2e554d31b3b73548527a964ebf7a72bb940649624288f08a4957e7cf0c9247e869b4b49ddd622383641122f45c4c423b9db5a3475d1189442266478e1ebc45881183e494d48679ddc8c9a08741920c953e9310b9717e7f4192cdaa5ba5f8599b6717247f8eb18ffe7b29a9589d250362b420a9c3829219f31fa7372169761688c182e4a03193a76a7c6dda0bb50f2dae8844de23912eca66b10508006c00631549caf406958325594d5e48dad95d5145520c1b136dd1d62b038c5424c6baa096ffd6f531848ae42cd61a4c9dc8b849c7e37b7cd1cbc32c8b2d4022304e9160e9a468146162bd02b9718341f63eb4b8e283c78d1b088844788420478e10094422918899e2507b27638c9961643dc02845e2afe88e1e43683395d58901062912947bd2125a39073ba50f1b1f5a60f111310331a30518a348f64afa2ec69ed2edab2892e2e952af3d719be2ef0618a148165bbd64eaeea3390b49cbd10324123103b13a33c000457292dd167eb9825d7643d244f03c3e91f4b2a2f2c59cf2d48743d2747017208ee33381e18924ed9564f0b1533a2f0b49c391e3460e04d289fc2e7d4cacce02290117e90c274c9a84ffc7cea1623abee80112827f1d190263138951532839b9b178537f81834f04606822c174cea961e365de4d960bc0c8447248cb0edef29bb26993050c4c24acc6d6da4df227375f22b1e7ffb4969629952d1fc091e3864e0086259252cc245b6fff3b6a2724ad12c916e2bde1d13fd6867716804189c4a029cc59759cdd749a44d29c0c39dd34eac208e14908011892482eb9d9f2bcb44c99b40b2ed07b24922a4fa7eca931c39e1e9296723cc88e1c90f8f23d9aea60a990b4127cc102c62392c67256d9cf3199e388c22e6393a6ff2169660ab073008c4624e7fa8b72954773dc2824ad24e6fd0c0c4624659dbda815df3c9a5cf0e0620466c7f3d8c11220008c45248cecd20f597379d55544b266d0cb9694ccd39e4d44a25765255fba0c640c34b490389b9ea4acc9decd310b895e9f736e6ab30834b090705aa4964cf1275e63481a0fcd038d2b24db6e6727b5ec20220b493bf6a1c5153dfe8ab3c2a1ca4be9fd945b77b3071a55d8949e93e1f36b44e5692fa0418584f17c7d7ee132a5cad9f828097cd8f8a8087cd8f8a82c3e6c7c14043e6c7c14161f363eea011f12c0e283c614126c64760ca62f888c14122b79f89439979b6e2b23684421417fb018751b4a897a43d272470f1d5cd0804272ace66c257e17c4a54f484ab14eb3599af56817485a82861312e7a2e7e5703a98855e12349a907432e6946515db92a9e431582e12349890286a94ecbe0c253534a3b184e4de28a6bbccb2059f43d20a0d25246e093d5db2d5f3b7a311349290f06759630ca3a3c83c4242d25bba0a212f692ef1ef11681c2149f4c4d6d85d52abee1a818611925c64ee2cafa13399b80889a2e49b66a98e69849d55a04184642d753961fad356cf212475df8bbeffb8eed7508186109284fb966ecf3d08c9222dc77e110d1012b7dac36c7f4ce79a3a3f48eeb7f1ee9a51963c46c3078915f4e9ec1662214e1e5e380848177a4da307491a3afe77b6fb8efe19101a3c48928b31731e4bd963ee17a0b18384d3d9da2f97455f2aad1168e8202944edcbc9281089fc8dbe001081460e9233697adc98d945ade6c3868d0f6fc0878d8f48a49081060e12b793bccccf9d9e5396bb038d1b24478f93f55435259440ce7044c00211d0020b36d0b0813d7739e9fe8fada690b47210f8b081810f2db0f828e56c0d68d420f12ea664a926d34c551a2429d369fb84d87e40630609f22be328e5393d78ce073464909439658cafbc52a9550c92a3583aa1162e0a7cd8f8a8097c48008b8fbca1a30b2e763c0e1c5c1c25d08041c2e595cfb135b22a684da0f182e4ab7a75ddfb58614e172459505d4ab7eb767b121368b42051357a0c672a8dd506d16041b2e99a6fcf9a625a6815c95ba1944a327809575b1549272eef661c99f308995424e7e79b699d6c0bb584a4e50466a0224929754a7de35e34972430e314095a721bbf512c37f7a648b07c1bd3d539938afd952229cff6655fd59f976a1e308314497fea77ac72ce1002c9e13b8aa4e06a156436796d5d525124ac7b7fe84e8dd1608e1c3a407278862231650f36e2c3858b3a1bcc004592ce7f174deb668adb84a4290d667c22b9e4dabd06d96226a39034901c7e6430c3134955967539429fa628091ec7efe844520e0b1754eadd92bd85a47d6162308313496a32dedc5b4adbd82612f62ad9a59cf3e81b2487230c666822d14a93fa89d8ac56f290342f3291f8e1df9256d3d13326923c9f0a9b96e3bae71289f33aa67ea753fe7521692039fc5c30c312092a99184fa9520e3a3d9548bc50b3e17e955a5c0e49d30192c3ad05332891a4b6ac5296e9e59b3624ed6f341b0b664c22e16aa388c9399d636ccd304312096ef963e62df19d5f8c44221110d3199148ce675f42861191a23121696f8224c10c48248a5a8836f7d98c5686a49d11cc784452cc5a28f15bd5a9bf90b42a2798e188c4127a47e6ad8b4e1a0b49fb1bcc03c4404280b8e851b6e3439063c717331a91a473925d4d7953379d1149dd2273286171943ad12212b6ae343c28bb905e86a4993d6a80b1198a48cea235a6101d6ce7737ae0e0a28b4424567a865aec68b983638717bd17988188240b4bfee14d7d0ce286a4150f661c22e1cd825e8e3fb5a242e0cc3084193ef6b3e870d6c9425c1ae42b2c736bda94a285198448f678fa84b668f2da4637ba47032291482412b9620108c28c41a06e2fa58a921ae40f49f363382c509629982188e49c6df47d55da0fba0f4973636b811981483e37d37fd9964999199236821b39be40aa0510892194ee24426d2b7a5fece8a15fecd0c28a1466fc21d992e6a4b34187db759230c30fc9de371e97a63e2467d56b562aedfd69c487a4fce9b9fcff524de6ac1166ec21b984c99d13a69485e9f490982fb86dc6aa320fc9d1cb6eb35f140f49f76516933a2ff996df213969c9eb1237ca72ba94812c61861d92ce532c7ca63e211e05cca84382d09a9e848b1eab14ccc00c3a3442a886ea1263ff86a49d197348ca3146b6da9a4add3a0233e49018ae3246bfc3452e1b92a61f981187c49b51a9d2834e42680e480e3f26980187c4f2ca74357af45cf043d278600966bca1dc9060a3b51eaf3742e6e2468f1c38ee4630a30d49a2222e68d2bf502a6576f4d0d181196c48d00d72a5b4e51c3dbf630d89a636b7fea67c3766a82169332813d5f3def47c481a17652098918644d39d5410b539c59423c20c34249c8914117a154ea45198718604a5949f909b66d7926e86e450fdf1a37cb2f338cb90982bebbd97c7e89f62a5cc2043c2967f72f7f92474e72169261261c28c31247807ab7c42b73a72ec504024a223c70e6fc0073e8b48a40b2e22911962481a556e32ab2a7ede38e4438b2bfc06175ed88c3024a56813611ba488b6e8d9058e036680814ff71973fbb4f885a47d2131b45a37fd64cce6a09d20667821d9de34468fbd9829cc0cf8b0f171c5878d8f057cd8f8483e6c7c28e0c3866a1712df55b37d2ee175f1c485e40c63dfa3c22c4fe52d24074f97325632c718a50a66682195533e939eee2c24872719e3422c735bc54272cea083da50a269b7bc42d288673eb597be82d2592141254f3199ce28329f6c46152c6dcbcac12aa714538a29337abb5db80abb9bdd871657981dcca042e275d8b8fe297ad2610a49d30f2dae78cf61a3c757608b0733a6907023cde4854ababa5433a490a0424e35a6be13261714cc884282caf699f1a2af74b341980185c4dc0e6db1b17be38d16c44ece7842b2e612535a1ff27e2f663821d13bac64ba794dd567485a08c2c13b1a0407171ff83efbd0e28a1e3972ece8827f478f0a3c173bb488442299c516202298d18404fd14d53ef8dac50c26247a5e66f7d0bfcc29c7c58c25247710d272c47f97927266b105089aa184a48b8d717ab3b369f9242468bdbcaf9637c9c51a0949d94b7e974cfff1527c84c4bc0a1115ec354282289967eac13795a908091f1efe59de7acf2742c207a5d288fcf3b9740889db5f32db161442921abd993cc53008c9d984b2acefcc2c2a0442b29ad4bad8a23bf45e7e90947d74ce6ce7830411f61b36ae5d4dd983a468af266ee37fd618e341621a9d95ffa41d245d0757b3b00e12f36ac7da17d5d9aa34302307092ebb9d9be637741a0749aa42abbe798ebf79832475a35ab529c472f536488ccd25a754cca43a5a8324cbd96629e5d329e89c0649f23c9857b438911dcd20c1d2a767c5b93291b30c92dacef2c566f37ca118248ada1cfebeb3754e2e0c92d2cdb68592ea8c1724ff5d0e4aea65190fca192e48b0be98e4e34f7b8e39335a9024449f9299cbbcff9a192c48929e4dcfaee550e16356919851835ab098d12d8eaa482c0fb23394be97853115c9a54c73ab2ee7e0a2828ae44e753bcb683945f2eda6ef4ebd1a231653247ad850ba44673be54a2992c4fe85feaae829f72345528a51bc2ca98bf182378a24bddedc175d1489aeae5d9a39e6dcb68522290919458d322b191b1449a133a90b1b663f69ea13892e4a6492a9844c0d9d271233c98a5529c46d0c071720df83cd3ae18e8c5bbbbf6d23f300199c484c6225f32bfe4d24fc893f8ddfd5ca18d544926a6829b9f031648c3391b4de1aef73867e3e0f2612b3f87525e9e125123c8c341d4b755f9880b130698924fd9a2afae6dc257141ea3eb4b822f163614a0f199548dabb78a1740ec2372d831249325cfd7ab4289d278724c88e1e3acafec89884513b6ae524aeaeea0265e0ce17322491249355eba5cc610664442231b9464d33ea9a269442229148a4b4900189c4baad6fbf8b3d22d9b5938bfbbfa698b32312bbf38d1c6fddcc1c6b44f29dc69eb55bcaaa970c46249575adddc808f73c1791d44977c43fba7790110d3214919cdbaa3e45116b639f0f2db0f88844400c2f11096a46d59a9bdc30eb87a4b13f20758848cca2d4c7ced70de1fd19641c2241460979d1b796b4980c91fc22facc76f363aa6cae1009af2983c5c8fc241a2112bcfd457e7aa5eeb866f161e3069194feaf2bc7286b59418620922e43fe76f4fbc2d74024f66c12b929850191d4374249b1ddb4c1ae48e48c20123923b8fd4352b81513d9799f5310fd9058fae64c8a8f506d497d484ae253e8cf4c69367632f890ecd1aceb3d7decda7e0f8643861e923cb7df8285f2542ac6a3040c810f1b5ffc03743004b2d0c15de0301979483669aae7c3b7621e914824823c7070c15f788e04a408f0684024d2052a810c3c2466cd2932edad9bb22c023bbe871711f8d0028b0f2c1490228064907187e4fa77ebdcf39ea4694d1764d82179bff336af4ce73ca3312dc8a843e227b3f27e95d3ef7ee1820c3a24c5e4f7415fcddb624c1664cc213176ff2b898ed5fe17392499b4f95471330670180017fecb5aa954ea86016c21194c56308da9db42e55a70e35b43c6187e1747223abee861d830802c245594ad9cf1792c24970c2663da89e7ebfc0a49514673bf7b66be85ac90709a7f76b2765afea942825bc88fc98390ddd95321e134d8989c174d21b14bb3697a897ebda414924d88aad51f1b39134621b1e3668f8aa9ce190ac9b9726955b8acdefe13124c8cba53db4d7dba1312e54c7c3d868a418fd784c44e7284d2cb1bf59b094952d4fcd5aca84a295b42528c8b8f97f22b21795b523ca82b19932167470f1d2618401212538db2f1ca9befb208098929ff097d1d439bab7b84e4719bb93fb9243c3542b269cd9d64ee8d29a61621795c4e567f5eb6ae9308097e9727674b865bbd0c21d18412b1fde661ac3a21a4d2ceef6d3b6cad20247d30193b664148130261004048562b21635e0f9d9bcd01fc20f1bd3d7dcee3bba8a70703f04172a6e6f75d3839ed2f9930801e24be7a4ada1ae36691a990b4828401f020d94bcda57f9eea5cff0e0e0d257536eb89c83330001d249a3a1d742fc8dbdbe8e02eca007250b08cafa61f2a963ae1201db467f9a47446e9fc0dd813793a98fadab80deadf34631a5ed942a8067b30153fdc69afdc300d0e739d3aba8232f9f10c8a1f453bc9780d23b4039041418bf9f8dd55d93b8018a433851f958581495beeea144f78d2ed0b0c66a56329753a6c5517186ede42866d533a2d48b8a8111fc4cac33a0016f8f1f7d28cd00bb999551c36585c853cfd5115e69a13fafea5c25c2a79a891a1a228a7d2a8fc3cbdb93a056b2a67130f0b993445828a31d7bcaf45c5684bd1767c85eb3fab8c14099b5bcba2b58c9e7fa370e6eb3eb62c840cd71045b2dcdf6ccef1b576ad1ddf830bb3bfd12b861aa148f4cd5dd61fee2c37098ac4acd92d2732e6ef793e91242ef365ce92a1349e48b6107ab3d426b1fd7d27923dcb59a9a034e61c744e24d7754a498c369942bd89c43442e8d724b65392a92692cedbdab4726bff66870a353291207ef76222a763ce87807b5c710ff8b0f1612b851a9840869b9da97cd4d225924525f978ed32b7fd5b02954f5e866bcb551c5522c92e3b3fe78c7ad4a004236e3bca6b335fca933865b95b57cab0efa2243ef1b349c5d8a1ae48a05b5314a1c3a75421519041e9cebeabe5a87a3ab88bb21cd4784452f08a5db1f98bb95c1d0c81bfd15ee450c0b182450d4724e7d49df2595cd228eec80162b7811a8d484e95b3b8a9f775ac54a00623f8fc0df1a431af6e42d2bef81080e860bb321ddc4529418d4530fab2d3c466cc40729cba1a8a48ce6059c572c9ead9bc711ccc800884f4e002039148952cd448445210e6a2b973ceb6dd35109130da32a551669e2975882fbbfa8aec3c327a84234716050c350c91b01ef3945026739a172db0f888448c50a310c9765254d8d0d469e552b0062112337d8c9752a325a11e3972ec1844a2478b16d325d1f84ac261763504919c59abbed762928f7319c142a146201284afd6767cbb54fabe2fd40044e2e7c80eba5f22734b5da8f1871a7e485041940aa77b653af49034335d408d3e24e83d5777fd9e0f49d22d3729d5b3d032f790d89db4f5938ea2126ae821b152bde6d17ba533e47948aaa4a62e7b41a8cfa80ea1061e12af46b35b903ad2e3e50e4917be2e34bed4886d764810b5b6fed5ef0e6ad421418356984b5d73214e74488e39c6bab4179499eb7348b40f179ede6c3f942a87a44d42838e5015ae1d8c43929ae94c5f55beaa3138a8018744379d6ff684eea04c266b50e30d0916cab4685c891ddf0dc99934c510bb516c64ad831a6d48ee1f351f3bb3fda5320a35d8909c74defdac9b2bbba5ac2131e5e866e8dc1999a61a9253e8dda421e1b3eb4a776dfe90d190a0ddbd95c1c488967e8604f9a1a2698b69ffe1364392ccad2ed3ea79c173ca9094fbaa3776eecaaa890c49619a6468a98fd39a1d4392d26159f53b848e2bc5909c254ce797ab4fe66718925774d0b1650386c41759ba2a9d1a13b3f9429236f1fa58ea72b4d2bc906073a5c305994b23b52e2408a1c39fac8f594468b890b827bb41a9cd1612fed2849a90d79e64d442828e77ea84fa11a15dc942b298284b11f116fe332c24655d9296332c4da5dc151264e54c3a088fc9574d2b24a5911e9f528b67279d2a2448739359d5727869900a095a66e6559be55e54a690b86fe15ca46e76084f0ac931549ba95259a14d1a85a475abd8b55da190f06e632a9bc7176db127247b5fc69648cdc154e38404f5e172b9fe0611a969c29ff1b64125128944eeece8a1a3a4a0061312845d99071935335bbb418d25248e90d164f8f12c4a4a484c6e4a4bbc4871bf38098917ef496b89aca6b320212993c71bfda6cbe4a747481acb7556ea9f726c23249ebde510a73bfbcf5b84649169f496a8933a1f2442721293a2e4f209cd5d1942e2ce8a68f470d9dd8d109254b5c9504275caa5ae2024c954ded6a3ee7e39034252de303556d23e8bcafd2029e6c609cfb9a563c93e48dc14b4a974f9dcf6f33d4850eee25ea65b6749850749e365e2ab37b383e4b31b8d166e1656733a48badc64d237c8ec24333948107e5ba542c53848dacdbef9983b374812b3b931e3558c5d521b24dfe5b9dc1c27d4a841b29be9756bbb29b59a060926f3930e9d9387fe76060963f964ae538baeeb9141b2988cf58d956947b431483061214b6c954a1e736ac020b13df6bb05539df5f11a2f48ca14d7d53c8852a73c355c905c42f594dcce76d1548d16249985a8d70b335722afc18204ef4cd3d06c3122e755248a7ccb4e9e1b4766a68a84cd74397f3afda8234c45929fce4cdd2963e6cb50911cff39db16a4b7c7d829923707f3504945a648debcfbbe501fd15f9622c984d86713234c05d14891b45935977149890f975124c64a51398f56b936a12892aac3e5cc6aa24cfee1e9c5c7eff082478e1be8a3d38b0f10bd5024ccad9db4d3cd3ebedd08ce093440911c224c788c53b31893280391c88d2012e922c78e057c81d03e913c1ac7dd93fdfcadec89c49c9a6396f84daa9972837144e0c34627923bc36bdc4e27f75424020d4e24fcabe998dd6c2229aa8fa6ebf7303e1f4d240759a9396a07e155aa0422112fd0c844e269dc4db5f1ab714b4c24696fc5b2fce125923283c668237ae4a6c512c99e9ed47aced34cb18240a312c99d5247a96ce788d8299194fc472695119a44c25d8ae99d4efffb4c92481c3791e67174ca958b9148501d841895dccf4aa501894c7d12aa4306dd69e52392e6cb55b3887c54113b22c136a71026ecff63ca368259f712e2c27821698506231236ffaa635c994eef952d2251b4ab82fd07d56a15452478f8a6ea6e109d3f9688e4709dc2e7d1d37262cd80062292fe672f99305d3266cc2192e2a8fb18757563474324c93f69afa7c16455104f2192fa53e46b6d7912dadf811f5a5cd1c30b1d17031a8448fa1bf9b392a53b5a1a445252b37ac94678b2d00b224925a59fe71d26c2cc4024e694c52fbccc7ac78a16587c44223d10ebe84206340051ce15ccdbac53271d59593dbfd79d1493a1f1874439cbc17cde2e080d85a46dd2e386173bca0f4939b6b69312fea24dbb39a0d187c45c5fa29fbd69ea530e0e68f0213986ce49eba51c99c68336a0b187a48c75bae59d620c4a9bf86f6c0a68e821495baac8a6161325d379481053b2b6fc653c2467fb9cdb43b977485032c7a4e99256161db443f2da58aa8889ea195d489a832c0d68d421d9ad3f9afe8709af28d221d95236a1694768cf1c92429aa65042be482412c9620b9012d0904352a611be66c2c13774b433c073d8884392858d7453aa3579ce095816d080c3779e52c5c60d7aa73222a0f186c4945ae3b59ed208d95d41c30d49b9849c7db598b73246a30d891973acd09484ee546d485a2954a0c186e4334b6acdc533783685a49999800b85028d35247dfc8e231777556e4c017c81861a927295fca714a39c3a73241c5c5c0281461a1244ad72ea8dbf8814e1c8a1aa041a68484c4294c937cbb80c3b248d07173c70e4283333335b3501ec80c61912bdbc746b18533a0a628a1912654327d53bddfda613920602f25e9432051a6548aace7e298c6fb6745f481a081992323e654c73356eae86a4ed13688c21793343c8de7443d2ee9040430cc95671b5b2c80fa22c672d0e34c2901c459978efedca5d1d0e34c090e8ada393afe9bcafbf90687a336ab820653fa5bd901864e62851955a1b68742131445ea47f0c4f59e242e2c7d12f9a5b3de818b485c4984264afff4c278c3da8242ee643d2501c0a85028130100c0a9aeb9604f3130000001016920763e17848d495791400045642324a2a261c1e1e141a8a04627120180a03c2a140180c0683c2604020180e86d2d21cd47e8711bfa02112f9f428e6cebbdba83bead515e25eec74668d33d28860f9b1e8258070f9198d5aae8f2f40e0e8acdd75b9f811a710c019e2985bbedeebd58043b035b8295183acc0874315f4e1ecc50fc7538003528fd297fc8c0e741bdc3480bae1487a863106ff9556bd3018e643c27dbf012a03870c1c5b7076820304870f7c80b011133216577a137ea717d0ed00821f8f1c35f53b53d2ba2f8fedde3ea3f317d7abadf461e8247511b6018c0ea620dc185c3038bfc126f4061669f53851576c75c498fbc78952622a982a1f4db891b8323b6d14db17dd0f4f7d130723351e453f50a31b0b608261507211fbe03e8f4faf99c5887d8b465a3fcb4d6fe00ae5393883fe26d21ea0cd7670962db41f5e2ea5d40a3533b1e3e62e42d6e9d2a51c9fbca65f0185baf2e87b178f047483d792dc9add7dce28025811fb52abb1a707cebbfb4884ce139d27608e6e56e245b4e5e6471bab6a486f4152da2f112132ed288ae8ca487863fbd6776a4ad2a9243edaaf0ce66fefb5b864e30078a93aee3146c84a00f0d284a93bd61d89b432f2da0989de26d90bcdf68881aa105f0ea306c9de565c4491cbeb9245844622a501c72ceec0616b38067e210f93319797137189dead2dfe882f61c958f37faaf5080d10a361c066a56e259d5b55273e3ab849102aee39c4f79d45ce9e54e7cd27943632472c303fb45406a70d26147a67b079b4c67892f91afd50fc0e8a7fade3a424818c07f086fe560fd2128c010b5c0e796a4248dc49fab0fdb7c65a306512b140926b99d2b4d421c920323996b029d40987c5c99724b149759c53b97af4278c788e27447592e8681302fc60ffcb042c2144a9edb21ed5a17cb2cc4ff9d522472c79c8aef196abcb22c251b91e34659a5d40b15e660ba2b8e07980ee51560ca5ef81b98e814757ce38c66b50a0e289205b8f59e8f31bd15eaf06200b0f78c11ee3080fb6f74c8922f25018f9fbc065919e4b658a8e25e9a6ed044c0be6b8390e1005d29d41283d9820ba9623669ff9c63a5f1b32b2aa4d39ec24160ad3ea9096204f9ab7e0307d6958277bb3203c736cf18eb9490046afcd8191a1918291c22a0aa3fd9786b52ded6304fb547b4410ab7d0c8b074aa84791d007f15b7cb966fb35057e8a570cc02340a77b913725631b359e5e4270b72a2c5f6ebbc9daf2b32047cd61e915fa4ddb12782e3f8edac192a8196b5574d8351b95cb1856ebc6c48bc0996c86ce680cdf33f436f0c69fd1e0071a25b953d4b7b4632094785bdd5627e496a5ea47561c418ec7e0b136c6921d7e1aaed30547dee2e764369801df9de57dc7abf88376cb15fe53b4636e5fea08d300a58dd54cc514ba4e123e1004dcd3dca5259fe6fa4e630b64b26d923581044497136a4f7b153054f7e5c8107bd2b1c9133af84505b4554cefa5e2935f2503325d41a8bc03eb2821c19279435ec789d797dea125b521cd6ed9ef1ce7a7a346f9da0b162dafe39bbc366b7b8d55a46db3573288e47f6df3ba4e5e31333498492f025ea9bc4244af37d4be7d7ba35e6f5ee8bd9a7b35f06ae4d5ecd5d0ab815713af30fcf5442b37d724f6dae885969745bb5d0437b6e0c4bf6469947fa0a5c7f3d07fe446486a7e24934c5f37060662f9ffccab3f3aa27ba4043bc96da8bb8c4df25fd6abb017895fc5c0d27b25f16ae995e6d5d4abb15703af665e0d5e79bcaf279ae3ae25b9d7b617dae375241de972a1abd72202b06bee5ea6f5d54724a743ce33c27c30e2784df7bae775e5b5bbeb7ed7a6ae4d5d1bbb36bc36786df7dae6f54d5fd723813e1b929769af035eebbc4ec8eb384ba3f2181836e0f94ee5126df9c36591af40578dc46f4418c2805c9a9ccad20ed388da89dfb1c2d80fa22bf394ddb463a9ee6b85ae1aca6a419a1bef25250515dad078747f013d52888b20897a4151744bdba41f5341e0a875f690657e2410f9179426b8cd4b4366f72dfad28f3262617819e583987436c0933672a9dc64f3b10b54efada4c08a48c6a6887521c78074e5ccfe1895d15ff3d239782d7f85d009a57c8f1025230896dbbe2aab14634e6d5b03cd7d835053f73ecd2e04562170847c080c017970330f3a431f141751f7187764b5b2dd0a3e0c18424b2f17d9d9034d531413953c2f8f2965908c8d3b8c5f38eff21fad1b2bca6a8f31874567d77b901f3adedf2739a7f9e48745adc7d34e7c3cee08f1dffbd65d5577d37dc3011cb62fd0d80b87e70b4cc7b3c012ab7680b4d96eb08e3a359ee99b0216d8286be809449e55b66f0042824961a70a331c757df6b82d6852141af579f997e7e929a6a2d96bca5cd5bdaa4db57127d87d39c596cbe223352e7f67af42fb8025fd03f5bc3528e9ca0f5388f5fa83426e469ef0d5616a7db5d8a123c9b465ce44644473a1fc2712ee990be40cdaf4f35b46a5d9872125d601caa1af885f73ff8c654ffed36fae06142fc08eaa687afd88344248409d872310061a45c1a6be35213ed5e9d30ce677d42e66f24f2f62ec4b0cf71176d6c75473b81b045b1d2fe87adedc474fc31d963487bac2b91515c2eec39e1abe697c7596a1b86a8d8a30d26725671018b0d74582b11c1194828329b58d98186815693112a5aa26cc4481991aa2685933f3912e2bd11858eeb30aa1cdbfb5eccc10163d37e94295e04d97c9e68e2a295838946f8da3dc0524343890e17b734cec52b70c6b9b6f31f492c65c1d3989831bf42e2f81745bb314ce56fc557a0c75484d3a379ddc303e8656e4340909f4561c7dd5185a14dcb82daddfc8f56975b80e2d1fbaa1545e7e14d1ea59ef98efca731d15662ecbeebf401e03b06f0b7151a99f943eedb7c41a1273412797451317e32da398961b1d313e40264ead1df5c8f2e024a9a936526252a40e98906a8d41aa587dd4741e251751277a03b39849e18c9431c2e4205b86593a3bfbb122c8559de532a23811d30bb1ebf30c321fab5e1e6c3eedb4d0a92e5410691193e12db0c0b62f558c780f1e731e703094fc9fb7cef03a3ada5bbeed29c9f6440d0b15c31363063b1e3234a3daf839802fda6589901ad58a9d218980462c9fe95e3fe054ea9913eb2dc29032a693a094fa60a721ba03f1e2739806144e9048e6f08d90f8450d97479ddab9fb22df6e0514d77742621853994328634c35c4fda6843bfcba4fe751680ea214b38582e49254657c194f58a89c7e742d82f4ad643398773d0ff275a7c9edc9278bc2272a5392d59c74882549895bdf5936eb98eaa511596d12065fa876c6b122f923a6e29cbb1257ccce7ee51344225d6a8ac5d6d60cabc3a3dc891fe440c3ef739a59d48f2222d75464deec75a7b9915d6163a01b8f79027d4a538a953a4b103116f1744e7007431ccc6a013d237f04222de23952799f40fdc12e3e4a8021f032510114359e346f835e157f028fdf91eeca388de4558c1799ad8e682fb67bdbda08cca953a224bbbd9c8d5db7380a006855dd291b7561dc3819d5898e467d50f3955dc6d9f482363d48cab463d0a5a1a0fc33159a9e92d71b59fc784db73eec55270b79e80719384650b4e46fcbfd5cb0e5e763d107ef5dea5175d4d82a79430dd407a811314118962ff8c9d2ba88f1a140b5d359bb828a1411b4bdfeafd67828fdb37dc5e338cdb37513684e45629f5c975bef42b2ce62a26f572018b0205cbb8019fd7aa7773cf28369fe9f3fbe2d5e71eaeeee13e07ae1ecc72ad98f4b87c20af19cc5e1a3b07411eb6a88fc9e3103fde5091f2b1a4d7d8c9cbba5481a4cc6cc13626f57fdbb569d0b3b6ca620a5ed663dd43b081fb6cad749bf12df880a03ba0e78f4b374e20370f5dbd4f900d62f650624fa0f22fbb51ee32d3d68c2503b7d1b9d66cbfd5e7f7309b4addd4ecd9beb6b7bc0299e3ff3d6d5d269744c7e150c0f9d4f859a405e304d45aab362a191e737fd7f45f8d763555270ef466681999df24b910a503ba41f47f5c68005460f338cc11c31437ea1ffa204825c293c0376e2690711619e62acfe9a9aaea3cf45738f76e5f49546f2f12555fb649c99b24a148c969e9f22d1dd8c3e695adc087de1fd1bfbf67fbab5728a49e94414fb9a8d2c685cb1ef0ba2092fd7c7d422d88b4ca6d4057094a4d05670623843f769b2234168ff9eef4a50fa907932c0e4c0d27761ee398094251b77c18d2ea3c53dbf8e18711d1ca034c23282ebfd199c7c2e2b6e891db9d20c15d562b606093e8a4833589b37ce5fd6a69788682de3b6527a113ae44cab750fe9ad3748df6c277bc0042b8013096b877f17d30748a99b205e40b2b889ebe81bb155aa11805c550f2d26e24ad16b31e587104ac5a83b9c1d61420e3aeddb34bd520280fd00a1a013496f14d0dc5888eab677ba57cab7a853966bd835edd965b9220aa23ff0452b6435a2f397f98d313ef4de5dfb1925826d51c44f318e5fcab4fd04073ec991cae975d9ca021fa5c72b9977ead24f5065e46a0755124d401f859cfd1183920840319504076f252f098350ea5d77dd23f0f9fb895910fc8fc0d769c98120d520f618c048951e913d3eef388614643ffa247fcc2622f2c862755ffd22a01641aa0cda634fa755a6e142646fcef14d73d2b5e5db1b99660cf1040200ffe942f478235fc6908d6ea56300e136ac3989f9cf63c1114a383252097ffaaf5aadc6e5666db56c4cddd4a788f31f472c8f02957f64d638eaf2ac1e7a9342822738c22fb2ab14ba9cf82328b1fc12b8c1c6df798febbeff7dab6b2c6b9b04ce3698e458f7681bf0320dd77c76245ab1b6383d6993ca07701c515ca23601052961711f76d69a9c646b4260383750e4a485fe3f1082d6e1ca74e4284b36fa405e4248c289b37cdcf11b2aef5abc1143161687adc9582855e12341c0ad376fa55b4c6e36c2d61d8d71673779b411f37dd246a3cd7d430052ad5ec10297d330d90667df14ce7c3198b38fbc6251165b93b3406305e24637cab9b4572934e4023a06a22d07c661a2b53485375dc07485560ec062da0e7c35686ece1e0677d3d99094adc45cdb4e9e40a8b4cf4d235ec795cd811c7195c00e702edaf3a33153d8392a711b89900dddcf4eceeffa1fbe1b9141bab124107905904f1608730acc47a738297c165451aeef620a7162f478f8202fbad29ff814c5ce6fa674d739c96c2f109686194938d03b8129f7d5bba5a2524fae0b875695f3811405aaaa4c51245a5bbb193d6ed98e312784f606beb7cce6a4412ed862c19450a9ea4e4464b2cba95a3095ceaf1bd6ad7be3be5c641ce0068388cf20c7729102e148b73334cc3faf4e6cc40857360df46326f580c761bf694fb75396119fd5ddcf03dba0bf3df6807152ab7b8c9da92a6ed921137f59fca4fb2bcf8528a7d507db10b7329ebdb2ca2ba547a56460af4478d6151d6aa90e794fcc3a8280f03963e10799daf9f6e8320026be84e50f9eef877f7432a5b308164452b85da159241255983209601d36b8dc7b686b1f4a5432a9430d74d881a2f50b13cab720a471a98e4c6a9daeefbbf48c0e06446a2e2014aee9ea4a934daf17537295bf90095e33774968cb2e62106be15420323ef671d355f5447213e2c0e1b30e202f5be206befda42558dd6fc7ca8faaa6fdfb4416a5efcf58e5551e82ccb507d8cb2dd8174f14d0fe6b62add66b4cbd7863bb9ebbeb82e839028147c381ba0217c1be6a09ae0beb38ebf730b730299590c0d232a21abf315e5d069340a1e0bbeeefb1eee09d8454f6fa69319cd16562b33b23449b63449d8f55a853f27d8e851a8738e1aa8490dac607c60d33f6993401a0a93eeca3af6b21e9638c241769e919b554a99a2e29f75a94e7a8cb7bfee8663a477fa9d21a0df0ce3662ec1b8967353c685315ea5f599258f3c648ceb8301528e2c0528196899ca2a4a3a8d9b6b3867ee437f7c1ed11ede35a1197bfa906a86a0ab89c555428664f8710d3bda35adaea98f70c36be3f297ad09a5ad31986bd89ba6c5baaf8b5f4f34d017defa327c030bd5f6dc9423d0907ce049b9be9a5b90192d1d8c40026dc17795fa2b2389a530a49b9cfb66e44e173b7e4924a9d3fb3e869b354cb9248042abba8ddc4af9b7b2f7d65bfaf9ec645c4e33efc38edd1428afd5d8088e93bc72de8d205de454f5d8e3de77113ea7af725068e308bebf82136b193f1e46720407a2bf7f31e072ca52a72eb18a540115f1db0a0b024731f8d05d23c3b5898b24d5cd421a598828472d4f3b7cd61d20d0848d49cc2e215dc13214629e3f3a50a4c4a0241c1bfaacd3c0cea11d2d84d98c6ed16c9b2e9094050fbe1b854d9a461840f782ca505043f817d5c2250e703198212bff0d081cc4e81070f30e7765cd5ea04588ed02d2baaba9b4d335352ffb8f73e6a148216e4584ad03180bf96cde48168a34c9397445dd9566bad4d5e6ee5fbd5769ba420caddff14427fdf71dce8a46edbace525b6c91b6dc4db8ce9280d93747883acb365062f51e6ed87e0a9313d0dadc6bda4be55c753c700136dacf8d04b2a4551d968f3bdd4eca1c76a68a39d74fa06684bc6e98e48508b4f395f1f878c1e0583d8f2f1ce721bdf12ccf96466b0806989acca2a56080f01f63690267f07f56235a3d62a3303e6fd54d26fa6999f25c12a476130b8b3e1b29813e0d4d24f5c399718854c1aaceef4c307d0651a05a6c272d2ac9ad14b4b185f650df4ef114c0ce8604f910f7e527d9cb7e3f1845293e4342295f410c1e35e049b09690865d76bb40f6591a901c813037a55b45742f8d9c9a5f90666b909be043c0c91575460f8853e68b47e63f51cd9081ecda79c25d0b84d0ed1a9175431693626fe8d06fa7572b0ecc9edd47222b27e157e6309d1c7897928f69833cfe8b7c4821db5766cad5c188ceeb80e532f6d1939b450ceec08ec041280b861346966ff13ea568847e688fc455e2454a0f6e02eb23a6b95ac04636f80bc86e9de1996da700fd3808c59eb02236bbc34dc6e51a3727b45060f79530d3b76afd60c6bbc2526a8d8d2aa836c8225b407e40f95868b56a9fa13d38a1b341f22e58abb5511528059e9761c5c6ab9fe63cd3055bf981cd25bd6940e125a6efe9abe34a552d7e58f8f406b948f963b1cb612b8c21e9d8097063fd53ea36ac9777360b347050c24d75cdef5ae69c995737e4e0542f546d399c838f8cc287d3ee164a67cb717821696d2308a0ce62c320dc8828a0397f7d3754998c8410ea37b9a0a6f82952c029b767736307262dff20cc6909f09259451f355227bf96924518d3cdb4c89147363dfc7abc74c28f6a50c15090f26b27d4a6ee60b759910a8cc82c272aa3c959ccd1c95e9a543912e8aa4c4dd469139402ef31c583a3179baaeae44978dfbcfeca37ae3568260d6242ded79f00bf50e3fcb11381a7ea61fcd34cfc7148c31f1902d23bee6c8092a61c92415cde24d1a330f1c13b491500271a6aa8b79ec6b4e443f20203c4d175affecc1bf29c92ccc06aed21557a37bdf785c25d7fc03f6eee454987561f02d71bb98a7981f868136c73bca60b6376e8789b9c1065e481e3652b51805fd8b4dd6e2ecc99d50d508f9af32d4a55531f2706c8becf3567126fdb83db7fc28ee9cbdb790f0d87929ea689f1c7a32c622ba78aefeb446e005971640d248d4ea418c27c14c3ff78db05f5053e85658d2bd24c9e3ab5a1816c508244da3701bcc139fd4fb8be13b50964a4666cf5a22b98e05c2bcab00f7c68e9debdd638db466bf7a9349472a2e91ba2c047a3e84540968bce209afa11c79a69814bf47b798ec8c7d64a95909ebb2bc69e1763a3ba7effe74fdd9a572bdcd127b41eaa2a9bc4a7127c767ce699d30a62324d370a9c15a2d6e4e4c7c0533f020adb3aed659c77dd28c528fa51f810ea0af89ddbafd7c0d4a96d00f18a306e2e92161ccb9cc5de6707355f3b9796e8af53e8d85f9be891de7827c024424680cf234220d52604eab982396854e9eea57100ab470268ac803cdc96f9a74c293a85ce01fd7bad9959d4f2fad381d309b537dbaa062ee7a9aa2f49951cac2ee20d35927fbb190945ed484a8d4f361270f3233a60ea2c993ec150010d76da1c180af03dbc4ad7bf6de02c6008ad1181a634b647cf78dcae98c1a8a5933c9c983531cfab4f85965e57a7d89bb278b4db1c2edec071c595c6098d8515248ce34337f9be637cc5ce075ebf1828060b49821deef52c33500dfe88948f16b1525219093f42bad306f4c3d2659b47dffee94e9501dfd0a1d821f2b7f98870d98633b1039ffabbacbca0fe657c51b03e58806031c2c558898f392f92597fd611bbb931f3f563c0ba808d4770baf1c8a23cb23265a3c4bb25b46a703a78e8c8ec4e6ac87d0d7799924b7fb1bf95f69e4aea5425e4365ce0dc647bdfbc53161165e34edd28fd7f4e561381c5a7ac0c4963d9895c1dccab5467c5cc34c8b0ce5ef1f44bc869fbc83bc4207540548261ce628a11752850e9c81abbfb2c862ed90aa02c5be292dbd53d7c4941db7d419ae87006ddadbd809669531e2b7a236bd53836d3d6cd3652e97389651772e50dfbf7e02f14843c29c40be501aa6cb80abd3a5f68c24f86b01054a4ed83c03388ce66f13992016ad6e4110d2105e8285df323652da9132c549b4c395e686c4d4e7a187ee2bc48511b57b9736df805fce2e846fef32eb41145a650fa0641ff91eb6f6257d7b5fb670a419ad23026aa1e0913ed655d41069000902aeaf96eb1009b30ec595b58f3e0203ad0e3c4636a290743bc05697d185e4510f27f1cd840fca61bbb1d7c5d8e35e1ff1a2763eacef9af3684c1dc0f651ab0ca675a5ca730cd5b4ba8bcfdebe60e9fd5e3b2d3483e1748ae4619a327a99ee470ddf619f72ae0c78a63b2cdf70e2e85300940a22df9c68cd86b94c1c373cd96a09ace0529fb664db6a18429666c00cff7d93397a81be3a7f6b174de297291554296f54647b0a3f5eb3f73b2350f58f23560c3c5c1594f417a53b997d2c2147b05dd967424dbba657544ddea3cdc6a028d6becef57d557c4363b01566947f397669d52bebdba860dfa090c405d74793126fb70706eb483bf1a11aa34f9a7048da48ad4096098885b9ee175d6f7780322ac91cf0c811b3da6e05061499f6edf0c89cdb69dc0439c3ac46df6b1ba0193c8ed1d837891875b31f9743712bbba5ded65acef52f23ee86a4724046f18c0218053363eed13aa00064ac0ca362a07aecfa2587999e9a8dad738f56539c92ca14aa2b9bdbc97d2c8c1913052eaf2253655a51700c0e06342668e7786263c6387a4da85e0ffc96baddb4687e09eaa10baaece191a0ac95346b7bfbf52f1527eb8184c6c963534db115ee538ce2668501353b507a3a8368109e6171b4c2a0ff1597ef281bd623d9ac4e8cbbdd601d3ac6361e528932461d7143988fc2183ac30b00e9552594e0235879037312ff7658712c71eae96e8f63194e18040520285b5985dd13f0cfdf652123febe22745677b9c58a5e200704c7939bde60e37cb538f2a846c8e64dff2062cb8869ff244abd04218706b8215204cfa7d0d48647429417d5e76cd3ddb50022a4f2e85bb6f161e9c7096b2b43ddb98934013dc842c0d5c1d4cb94e8516af8d0457b2d04d8f525697227ea07ae9d8d8b75b1b9bb06fd06020d441808313069606460d6c0b081280c362f33287f2967dbc1e2778125071b3ad591f4f486650335de73e967b10d9b89cdd65165e2368edeaccf274e06412abe282965b91629ed95aa51535eb7a28b85f060433f7e86cc5fc1bd9ca504cb4bf78fb9cf1cd3da9d80fd032c05dc8d008ce4000ed13720ef54ed2f3eedbd70dc87c10001842dc1ca8424e00093296d182941ddd23a91cd1000f71280694122cffb36495085885c44ad446588fc44d44a6486c80e91160810296993a89b584f168de2d237a701910a3263a1a6a41b8c8280850b1a970798ad280337210760f42c033b42bee1f4dbda81af8e8e64322745eb6903376cee30fd3031d881883273f5303581d43c6b7f7a28e0c64e4f92b31373f985f8b26102c648bde3fe7c3c1d9ad45b556676026e69f18f8fb9002274eb1516b18b27b6e442feaa694b4f3ff52775d81c18713a59751a3728d3cf3aec15c80840e993b74f25521427b683f08cf1a2a8e30cedb38fa4edf14728c480baee8cad3b57bfc120999f0c4578a35e7e38a8f98466f98625d04958d6d3b68b07bd652ea75a84bfda7d0d31021ac196fe0af988c809d4e64d5b500a5a87282501965c9827b57b8005eb28b7c96a92783ea3d3971f581b37322d69910a8a582a8d50cad0b922ac4932e07ea82a16352bc7e4458168fdd17c22152dc5b07cf423c7b729bdb5bce47faeed0e5ec70833c7be19de42457db7a1358c8693be362a6d8e87016a4d828b4d455ec2cddccde2dc8739e892d3d5bdb16a420c6ac83b8274ab4f6499b31b4f89f18f891c3aed015c2786515af29a8d86fa5623686ebdd159ca92cf5c86f6734af45439a5857482b1227164c18f64f532b1e70065ec00f4cfb82d7469ff93b1862004a313e61285471347e1ec2b9cda3def4208cf905ac66b58fe9ac8c479f6965eb772ecbcda2f9e4089183fd74e11a04206e883b21017f972db7d82247a66b6d4ba5ba545f152d44fbcf98e95b999aad592438aa91213c617608aa20a157027c640316a5ebe7b4d3e5688a499fe3932232acd421f6a326d302944ff9ecdb924abe3ce910e5534e0817cb2ba78e5eacaace45e2e2a354db2c2afb9b39db80c4e64b7c3f512ec547a43bf602fa4aba25879b67d07a87e2a3d4d4f3b1ff75554c339ac8ad32ea5f75f0614f9008e99359315519631cf075435de564651fd2e9c31763280da110129257491cca783bb7e8515678099345d80d8d569985b60e31a790218909f180a38ca017042bf1912165b1f4c4f67847284c3077e936b50f51243eaf241ebb12bdfb93618137930be353e125295940c601948e3385d9ba116e7d4a62a8d8cc4622ca32308eb0b84cb58b95252915a8556bdc2dfbba81cde3c703ee77b4117049cba053bd66e63f3888437eefe471b12c304e6ad8c1bdc4d5f8b8d39ad998394123fbef4da3e36b42df736dcc6b69ae63f0bd5f2ba511bc96a6f8e5b58438a64bcddcd096be2db838dbb2164c229a885425134aa40379440dbe81f35834bac9409fa8af5a16058da6cb04db5128667563c65342e1891492e31ff1889dfe8880c5801f363312c85610e284077ed54cfb2c435aa4ae86ada7df5a5569ea274cb1dbd9727e3dfdf4e2a8a7e4da5b719790152611819530ab59342ac6855858e2851da50bf52f26ce5b5e69aea74bd33b3588325b3598af4e39ae4a5b3dd89b30c769ee9ebe904f6ec143bd8d203216772baf812cc5bad767ea23d45d7538ababa4b27d444aaa80bc706aa666674a875f8cfe476b33a43dcf11a20673ece18dd9ea8dc1031faa0ef7fc21c9209cd8793c88d43c042b01c64c8fd5e511aa5c3f1ef185522ce660240c8322f5d9a9444cf95c4ce329735a643f3c7710d80bff1f4af7fdfab47eac3d6e1d1dee0a142e90aac1613913c12487c6c531133bd881677349f6cc122b7284368b996b9aa728e966b99c3aadcb7314b1115612f8955b58bbf4768e90480059a2772323c1f4488bd1d048f3666208a28501945cd23976de567e1cea134abeb1aa92b46be0db20fa783530a1714a44fac8d5f2815d7c9504967ad9f4eb9bd78563cd1d212fc02d34136068d06503a213ba40d49b0366441fa2cd493057b5bb0470bf660c1f5b20b837c9f5c1199056e7ae11b31fc4db83022b747091a0252760e014057fd001c13d21a6ada3f71c5e3547af5008cdc1bafa8f5a6bbb0ca648b4cf1b1b8959e0675e2c5637ace9d454fcf0c92097221276449fac838992227724816a4838c2b539f8373fa599a5e324026c8899cc922e9202364925ce4942c930e65ac33e39c9db3cfc2d3ed8c7d269eb37366525f7926a30a5b66649193ddbdef473b2d8f7d0bef35bb37fec093eb073c85b59bb1de23704026c5b44fb4a935bd537a8a4ccab49bec85ee12aae2f14c3b8dedcc50675ef8eb06bdf0a68a640e2a4f24b6d4209af44327e838add12bbaa5df74868ed0da7aed7e6f675d5c3efc86e44cd0291305a29094420d149dea512f2aa5c2ac61529aece6024ebb44460c3650b4960efcffffffffffffff8fd2887c6ff27f2b999294d58fcab9ce94924c29a5c87abb0580000000000000d04d4484b42d80080001360c380c950c32be6008595bbed76e5cb23bbc604e3975b2d27e51818c2e183cd488385d97dd846c0cfc21830bc690d5415e4eab2c22af438b87990f195b308fd7285111249d3a6f0632b440b260f89cd7ed3d96b8fe32b0608a20d4e79a1094a896878c2b982cae44f2d8a7ab94ead01e3f72b8775ac1e82b562ea7f3a7f7fca71c2690510593ef7e8a4b530c32a860ba0ed3edd8bdea49fd2106be0978f48541c614ccb93f07b17de1397e1e8c1dfd053230c8908271cc5e45ef9e8c387a13f0d8b165901105539989a4bbfa74aa5aa0b02290f1045390175fe36d363b270a64384146134c5931a922a5a4f5ff924306130c7b5a3dabd7e5d07d410bc858820c2520c962928c65d58ae21626e7eac8f7152cdd29305ff48e1f6180816424c1a45dfcd7a1c5a3023870f0e88104e375c4cf6939c7945a8c307af87030100bca0b328e60102968c4ce53659915e204194630e4a477b671f2928629a308265192f26d574e87160c6410c1a493f289193ad2a1555e206308063bfb522ab25ae94a76ac005b204308b6984e6b59e2a2436b05cfe3eb9220230886f19222724ed64ad708328060ce733a692dc98e7621828c1f986329a5e517238aee8b0e2d1f984e9d854d51917f97d2a1c582771e3b7a603e4b27224fd27aa1ffc1a34d904390c103636524b57f7d0786f1bc9274d0ae25c602820c1d18cbf4689113ea3284ff62e4c0604a4cc850fa617a2f311fc8c081215b12722ed3e31ec8b88149743e7e07ad7568d9c09421f53fad5c1621bf1a982688f4707d8f9997f040060d5e1391b28dacdcf2608b5998f3c4a986b4a85a57ebd09285c15eb5b7526e9b66c8a3e8608b5898622fe5c9493c5a8a970e5818a496e7f190b34e8e1d71b0c52bcca9cb4336579f54278be10af349af9b70a163b32e1d5aa8bf08a315e6170f22e56bb7b2a7fc8f1d3a5690e3c79b8015e652a2848cbd0a5ad1bc1b6cb10ab3843b397d395bca35abc2f49f42e860fb2162e55361ea97d5920927edc46936d8021526b12c1122c9ddd6f33ab4c816a7307cca99543d97bdf42800821c15d8c214e6f9b99bb0676d396ca53059cfacfcbc84fd3e91c238497930d5151a8541eb25191f2c6ce6e4a23067d97e086abbd65c120aa365e59ebd3c16d6c4a030c77d6b6fa79f982d7ec258174d84890ae2ba2e9e30db998f70cb35298bd60953dbe92a31312d54789c309e6e7a8a272e5c3db50953f786d7898e2da63b4d984d727dc82e52ae2d970993654c1a1326d74aa329a6730953522b3167ba92e59825cca13b9fe167a9ffe54a18fc92d249e1514c871825cc62db9d4a2cce8a8b26613075b9b5151d7f594918f5e285a4a265899d239130ff8e8be452da729a14240c9e5c256ea5f434e9cf91b6c5230c27afa146c8890e25f5c3470f31f0b6708479dd46ff8292fa398503c7168d3079578e9714bef46584b153b810364a7aa560151c3836b0630337bc78001eb6588451f742122161357b2b3ab47280d1c347ca91e3c70e30767c9156ea070bfe40b085224ca63eaa8d2a199de3096c9108f3a7ce218e85b5dc35025b20c278fbb12e4eba849c637708938ab519b3f5d1a1f562545d952d0c6108ea42e70579218c372a7b50f1720e3b13c2a07a92693b1321727610467d2d1522ab4910262dc95cd28de7b69502611011abcd523e01c2e4e9efb3e45c349dff1fcc714d474e426ab609bd1f4c232aee7605f5c1a4cdce93aaa550d17e3e18430825673fc410fefd1e8ceb414c6d248788caf560d2ac0ad92d6769d9511e4c26e952505126553e840753b635b5aaced9b27eee60eae83fdb913c97d01d3b18742f8cac1362ef731d0ca77f3b588ef85eb8e9602eb92d2741db5850f61c0c7af44fa8d0feefbc1c4cdf61fe34662f54380ea6142684f59451eb398683c92ee5c5ce65926bc26f30a42417e487707254dc0de6f4a01e414fe47c396d308712af949c1637b46c30e5f26cabde497554d7602ef99175749ad09dae1a8c15f2ede4aa4d83c1554d3b744e75bf2a1a4cb6276b6694d2190ce1fdb3da6454d2999ac16c273bb1d3da2799963218b6548c45eeac5a9742067308f7d913299733cd3118f48e6a68eda9d1498ac11ca3a5564444f52485c1b8bf3a218c12ffca83c1ec2a33c9d7835f3087d1989c9277f6f5d00ba67496ddf537ec82b943a56471bf37fa73c110e43a8bae2569aab30593aa5c6acbb916b43e2d9856de4ac50b1fba542e0b66493bdaa2164f8fecb060d2dd117ee296a13dbb82f9fd7d4dc65356305c081682f68668b55505832c95b774b0d4eb6a51c1289fdb838e9e29987405f54e5a42f8a9a5601c0b97d2f327319e8d8221ec98eec50be2270a0583fc1693b150d7957b8261262895b3a2bb677382b15286d787c97d3921c0164de0524a31dc24048fc96d593917534772ca703131c1f01f7cd7ff4b8c8ca00e2d1f3d72fc781360ed12cce129b5f78770d7955282794f3c8c1aab2c1ecaf15e77852d9260128b9ec3cb2a2de5500b4e0e317ec78f1d39c21043013f586070e0781e3b74a0068330610b24184290b1133674eeac901c373420811b473055face33d17b4fe81a070e1c595b18c1702da23f8914fe923415c11892d36be4082148fe8960d2185ffb4a6dfaa11d8239899e2e2513e286da72f04839c010a3579023478f1f770ad84208a6097ae6c42e547c82d01641505494afc69b50dac514fc02d073a0ecc748c17b0ef42ae01d5b00c11cd73b6c5f6559b7fd03d3ad09f16aaa4cbb5b1f9843bc8a3d3b49ea57d9a207f59d505555a905c2163cf8e2c85d512a4359c4d86207a6ca695fce3fae0363e71429ac7e69470ae5c01c4452eba52a7b75757060fa306a49dedb83c8e506e6318fa192d67bb6326d60104b699dc74499a9af06c65bd950cbda163430879295d729b7591873d3237fb04a0b59982df6c8cff1c3788612fb418b589842a827d1f5d3cf237b8316b030ec8e97522d95da93a3052ee82f8a85408b5718ae4a7890366695aae40a73fc6c4a72d23ffa766e85f1830822bb649315a6dc921b2e21e4ce3731ab16bc09418b5598527ef1d423fa4244548571c6cbcc63775cdb3faba0452acc9f44326dfd11265a04155699502dff259fc230a34cc88f2915d6f6358521cfe75628fdabb3b018b42885e1b2f7fe45f49a14a60e134cdfa828b1bb1c85395c5acf339eeac2afa2308eeabc48b9d3f45c96408b5098cbf7b44539edd65c3ba005284c6ef26974c3b49b7f72f470b51f68f109834ae9f4c7ab3bdd73da400b4f1854884aaa840a75e408038c318e560db4e88471945fb2244509a1d3ad43eb6c075a70c2a0539e74d5c5408b4d18f34c6507a1224fae980e2df5e2878f57c18f1d1eb8e1c58d09dcf02203ff0cf027560a2d3461485a232f5aeb3cef291346ff4ff2d292e80afd63c2d8e13c4493f1b86df94b98228994093bb2342b6f09e35616190d0f6779f74a9823e57a4fc152840739250c22e798a0746cd2567c12068b2fe5a2b327fd159284c93e0459f3ac643ae648987278ccbfdc25344b0b0963abb88b2ad13ec224223c789824bd76af23cc9293e571d1e3e6d1d2084352298e899810234cb225b65ef8ff7cf5220cc1de3d6878fc34a529c2d41d41df52b8c48f13618edf9cf3e01e57928830756d45cecb8d649b8fd721463b40145a1c82331591218c75d9794cbd2d43d742984f4e7b24091129e8130e1ca702e3342d0861c86be1ed3e9aa987e8200c9eda4f5f5032e2cd220883d67de9b3e471443c03610ae6e9df2cdc9ef65a00c2dce15762ebe133299482167f300735c1652e47889cd65a410b3f18ab2a64cbf1d64e418b3e18c55475b6b353d2adbe2868c10743f6d5135aaf5647655aecc1a464d632540c1145490a62c17e410b3d98735bcd2f2c678d11c181038c1cffc9c73f036e7871430c15ece80ef87813348f9ec03f0370e030841679305dd073aa53be893e3fb5c083219a051341854887568e1e583734d0801c28e8d1c3012df8b103f5f8e23980821e3d74941677305630154b295d314f5b1a9000056e7801868f1e490b3b1844ba9eb7904f7950578726d2a20e66f393d153a254c4d3820e26152bcd89acad2d212924d0620ec651423fc511f25da43f5e0c315a0b391894dc91266cdb93fe6c1c8c93bdd2e73c3e726d32410b3898c22ff57a7c9af0277f83f9f3072574bc3531be26410b3798472895eab3662bbc7c072dda60c8273227a94fdc15291b8c19f7a373b0aebd11d7609611f94295a67e75eed460f408c254fa9a4e923b70d0220d46afed30f6975375d7dc062dd0603e35ad382af94688941b5e9ca9418b3398f3fce5152dc2a4e792a1410b33183e524fde3199ddb6dff0a2c6a0451978ebedb2f2fca2f722d6879324a61406c3a7df8df5090c061dad4252b944ac17a52f18bf928e233a0891523679c11c93c4988bf6d1c9a62e1882c867852f53b2b4c40563e918931afbf3c1b40593d0f13e4a4584da5c0be6f68fa3a498c58aa32c9853d788d2794f941661c158ede37eef5e2a055dc1146e3c7ddd862419b28279fe4bcee8fbab6090e62616744c8da07e2a18c6624c2edca510d69f8249e95dfbe0f7e1ad7c29182e8cd26b23cf46463e0a269dc3c272fe50302825ce2b45f34bf67982414fc57b0f2a77dccf0906ad274fe85375555a134c3a8708593a449e5039130ca67f365b72eebefc124cd992ec7f8d9460f2fb5825314549307ad67a4a592349bc2024984ebd8268af9d8c0b3a827945a276cf858c609025eada84451e9b8b607899d4bee7fe612b870826bda2fd272c4dfed2108cade9ae2573fd742e0453ce2794d899dc9377108cb91fbcf64a290f910682d13d4552232d5e9fff03b345d5105bb99377da078664559354277c52ea8121db0753ab5f7d7df2c020bf4c84a8d7eff9db41e168020d1d9893105162890fd1b9710e4c49d32575c8180786a0bef44bd27e55a51b98c5f75b6e926c609291c44a50313fde5f03f3e59cc2e2072d0d1a9892febdd42a1b4feecec258b23c4cc55b656150fa21251593a4fea8b1304dcaa3c4228ba70b515898267accadcf6c0f7b7985b9f5f23ce8595d798a2b8c1d5b56afdee1e45b5a81865e532774921586144ab4d8d8f6e55856618e89be267a91fbb4a20af3e851a1c3ab877b2815860b8b93f20915d209156635792ab77592489f5318b28bb689acd3144679718fb91475bfcd52148e19a4308a57aa5b4a99fda65198440c69a1f2c6960e89c23041b25ef6271426cbb1333ec2a030669c5afbd04974c99f30fa6ac88868f93e56d0139d309abc4bbb3e274343e484410961a3da356ff2869b306875abfcbd5013e690f357055342849cbf4c60c268d13326a99cf025b74b9874a6c911fd99b6bb59c29c3c884d0d9d44866e953068cf9de81991db4246893c5e658859cbe973d608332661d69850df7d41a77b86244c29528f4d8e15260445c210f4225f277123f503095309b9b1a011b7acb247983d674f743d0f92e208f3a6c4d4fa485e426f84419bd7d68ff43f1f19610a2a75e53849c482482dc21c16f4eaaa9a84982bc2b45d1e5f553ba2a60dc68c44983be864e796e5647f1061ccf1ae60123dc40c4398a4e50e2b9ef6ed82448756a92f9a478f1d5785306fa530aa3bbb88b7b00eb531f80433086188a35ea4c596a7e8fa8c4118d5238f05bd386f396c8230c6cf5a85b6331046ddb018eaf3497f889e236df978318e0033006130d78bdc414e8964f78c3f184b4f765dd6f51f7df4d0f1638caebaa10109e01066f8a1cf4962e75e32ebeeae0f26ff537aa7b9a227e5834969f3ab1cc242f86e0f065552825f0c911ecca2cb528544d339964e1e8ca33e495dfef849780e1ef89a64415a4a4ab98b7807f36ee4135ac4b2f34d3b9cb25809e9fed5a190b6e4fd4513f25266c20c3a18c3b2c7cef924fe7c7330c90979b6b4b7f8596c861c8cfd97221d5ad21c8003c70d0d4860461ccce51546ce2819a2b28203b1cccd2a588879a787a524ad9c9b5e1f9b958f1f61a861c61bcc2985c91c15f2575ca243cbbfe8f1801ecc70c38c196d306a4989db7d2ada154d9bc106d35cccfb1625f2dfd83ab44831054090430033d6604e9e7447f850b711e42c84196a304ffe1cb23f3c92e7c98c3418ac74b22e11ef4907090e1c3870e0c0d13ec6e81968b825857dc81354b2a843abc78f349f7106f28e9bd05f1dcab34d3136c5c8007f608619cc55e925f731544a1a6ee086052430a30c85635730830c61cc188329d9c87e49d2728820d7a16580196230e4122a7867ee684e4a0312b8810307f9c18c3018dc3e3eafce25ffffc1601a197eeab79d2f18d5cab3d28cbf5759bc604caf53714dec9c2565170c22db079d2a642569c805b39fc6dd64b336ff666cc1f849747a74cf2942cecdd08249665859fe7e551b9314db8c2c98a45e557f927ce1546233b0608a10473cd89c55a490fb30e30ae6d0bb6a9f99f45d991d6658c114a6b3e73af9ecab38d36146154c492fe5d8ab6fa9a602e00c33a8608c518bfea5840ef2f41b5ef461c6149890b44e4c8f4f1b6648c19cfb2147496f414d4e6b46144c41656fa9b0154a07bd176640c1f81d7412499ea494ffc486194f30c49617d5dafbffa934cc7082714d09cf15d76b82a9b2e699fab0a4df2530cc6082f14e8c89bea518c2f59fb1044338d13a22ac2d82194a30e49cc6248884a01742c418a3c3c821c60e6e818f1fe30238706ca9624612cc1d63fb268b30155aa4438bc78b5139988104534ff28bb0358f1ea52aa0a5987104d35bffa7ab4776f1920e2d6bc40c23983f6ba86d9365b6629addd040038c0f338a505cbad89e2b29443045ba2472382192a537cd1882d92c9fdbe4dc0d2f1e8f304308269b3def5c5d62b456403023082691a2f6243523449c3e030826554b428a89084984cdc630e307e69e113ad9dd7c6c4fe1c0e163475b400c31ca15337c60f252b1fc3ea558e961a1193d307bae9945ce7b29f97c43030df8b1438c1d1cf02f7e070372e4c08103078e1cc452318307c61256722a5f2f57590670e0d0e2cac38c1d98b30891f4f3bb622dae03935cbe4b672aa8bab6941939305b75bf07ada2c2b636030706a132421223bfb43a35e306667349d739445fec6e0d98610393ce7b9769840ffd7a0d8cdafa63914eab9bf6cca081b176db364e2779aee82ccc4946ceed8dff08a693210be3c559172177413bc5636194f38e2b278260613cd7ce31d2e39da6fc0a94aef8fb9083ee09325c61ec7cf23a3ce4e6faf60919ad30658f9c96142537bcc0218315a6999073e4ea910e2dac406291b10a83acb7549adadb2e4a2f9cc7175ff4c0814355610e42c7f149e6e9d022960ae3d9e892a3639f305161167db7186b61daa7c5ba828c53a83e76aa4445047bf4d091e30bdf61860719a630480ecbb94eaf2c4e2cc8288539629c6e8f16d7a1556490c22017a9b3e4725bb3d50b1d3f7674408c2fbe48c00d2f6edcf0e286173732a0821d482a4761b4b2a0c4e7876c42045114a694dd43540f391446c99e647fdc49ffa2224106284c21e662c4c5f809835ac8ba30df19d5db138631b7ce412e44cd8e77c214ea1be1229987105e4e98f3660469bb262f4b043761883ef239a424e652fe6568c2142ff2b9cda92492ac3361082a4ac7896b1f3552ca06199830697eafa59438bb9294a606199730a91472393fec32ce34c8b044f1e1b533934483828c4a18238454562a4d2a7d6f0b3228619654df9fb3f6c4c93f13644cc2f8a3a299ac1b99f2519c820c491874685132e6237f098b181d0973ce9582ba7e114ae78a9546410624cc397b4fd01e27be82e8112699a49fadf493490871842927c7aa30c16b84c14d9db814132e7d106530c294535055f14347c78f1d86b508638cfe6aa8091b1f725610321461ce0f95cc278be534aa1f850f321261fe380badf7269a21727490810843184f1b2925ed8dd172d4231e3d78941b641cc268b5a293fe9b0b13711c38725496187a906108d38eb85162e96154f40b6152295b596f48ca1631214cb9ae944ad4532bba0ec2587b29b2abc3e5944710864b89912bab2d4c326504c2f4a22599c908353f512b62c8008429b279e9cf130bad5518eda3037f307fbcc4f8bbec74effd603aad350b31e42751ea53c1a9c0fa60ca29fbba878f947416e6833182f7972a9d2e9550edc16821bbca7e9c6fab8f1e4c25f78456ee4aa3cc9307e3bac82d51425b3e7bf160f4bcdd799797aa454887965a828c3b140e956107930862593b5adbd529ad83412e3dbca9cedc7a4a87961264d0c174f165a636deee1fcfc1b4e1bed6d1dcc2a5580e46577d8bd955da3ed70641461ccce715a247e4a685d02e1c4c55791b3176bf2ac7379852f2527d13dbb4c99d4508196e309d5e9494c3855cefa71b1a90c08dbd3bc8688329447789134a847ab4d860086e369643aabf20630d06d3ab164fc505196a3099c8113b2ed7686d4f122bc84883410909e95208299ee98c06438af4a9e4d9a4d39f9d808c339894ead8ef3956e7b0350f1e0fd811460f0dfc1829d8c163033a7eece8000e1c26e0b1e3c7183d7ce0c0e1e3cd60d2a3eb391e2b88e43c92510693a84fead6b1db316e9c1ce3c60d2f6edcb00006bcb8e10fc081c30212b8e1c50d1c3878f4d8f1c3021240934106938f29edb916513c3e7c7c8e301a0c6519633045fefc7471afa4fd9f500632c460b4cb712409157eb5727561307b2ed91a592343c74d9001066307ffb4506da3723e468f93429bb04cc1e05993432f220593fe892ef1588a8249f642d5758a9ccf74a5810d2818a2de256dc154a594631b4f3005a57b2c7b6ddca897c70e1b4e3098fc29ab0fbb8d26186dfc4ae297a7581d31c138c1db5358a99760fcce5e5bb335a244af049338bddfd5585127e6eff0a101fc612309a638a722e85cd9ab944782c1f34f5a374f5727130a8020070f1b4730a7cab6b09775c17392114cef1527b6adbc2b45304a94c97fcadf3ab8fb3688602abdb1e2794255bd3504f3e587cf0895297a9484404e9e93b9a52de8e1e38718ef6a413067567ff6a9708fbdb16047e206d80082c14c9427b359316dfd07e6d841c7eb493aa77d30bdb0e103b2840bbabeee8139b57c643fa52b6b877860ccddfdb1dc9dd424ed173d72bc0f2d0bd8d84127529410baad723d2e810d1d184bdf4bcafde1937e6e47056ce4408b91f76534ecb64f3858dbf7c4247bfa8de8d0b27103636c97c43711d361c30668966f7687168997ad816b7fb927a6c2290bd9a081297c7cb88aafcec254c12dd8cee994e654b230aca82c51ecd4669f3c16a60d153a9fcaa8e4e8ead022b030eb6eea878c32a52ee815a6d0f31c952fe474dc1506e595e553fcd22263b6c254617977d262d7299d1536a0062b4cebc95a553e45e598afc2a44ace6304f56872d6508539f7be5498e3cef87d4950117c820a7399d5968aa4e4f97fa7305c482747e5689bc254a1bc8436292985294d8638b7ecadaa954981d611a7fb4dfd3e7ae4781e3bbee0b1036b8cc260f27241e59cebe324573fc600831950431426a554cbdbda6d49d2c9e1a3478e15b48fac5098449b2559417e50183b55de587f3491c4fd845172f04962d259a41250c31326d931a25cbb69c4c0b8ea8441cc8b2c112972501d7528278c3f27a95626a55d52f5f8811da8b1098330f93a3bbaefc953ec050a50ffc8808f0514d384495f859c773f972ad5eff0f183003532611cd7ea1ca93e4eaec7043530611222561a3d218a0ee9f4eef145a97109e3bce87a90fb31a1a52d4b1872cab57e1debd2fe54e2501734e4828a52c2202254949b8ee63ba3496422ea537c477bbcf0a22c06c00a3524916d5aebe54b8e732a913067e6ff08d9134aa4051f011d62b403bc68818f1e60fc04b4f8a106240cca6544b8bc9fee0f070e1c38321f61489dd536d4e8c7d1d611a58fa79c92295d75a0f6a21185a30623cc1e3d7cc5c9c832a754eab250631124255f215b96861a8a30d525b3b150154a525e65093512619c9c3b251135ddc583a48a500311467def2c2acd64fbaa09077e8cd1a3052d0122d43884e15d2df4ed1a400d4318a28ef2583a7cc84a6f214c979753d985d850ca25844195d0124b2db509f90dc27026d2fda739a5c65382307e8e1df2fde7304ae8cf63053d72f4f841017cefc103b74620cce31b26522701c22cf6f15a2caf1c618c9103053d7a7ca176811b5e707d515450e30f66dd3adb4a7e5af22460ecd021068effe2f40f151004410d3fe81d3f89f5b20f47b90f1f175d7664e4033162ce45313ffdbfa0c61e4c2f1f2f4d5d7655317f50430f4813f51a9d9da72b561298c016dba0461e8c966a67cd2f88ded58d900703ac6ae0c1ec17c9b2ea5ef6e756f650e30ee6de8a92fa849957bedb50c30ea68ed6692d8cb6e4bdcdb8461d4cf2fd3cf578e5a778c3c8f1a3ad6bd0c158135b4e4c3a8d144e1d5a24a83107c3aae5a0b4940e22a6c5e460f612e27954362f754b183b5020461c0c3bb6292a4cb669da3ab448a971a0061c8ce731474f68fe88f9cb1b0c226f259554861ca99230d47083210825c67334f39c7bc951040b35da60f6dc972da4654922a86f6840023770e0b8420d3698fe947fb8e5939aa51346bf036e7871e35db0c3046d811a6b307b2959331d961a6a2852bc53322aee23a635d260ecfc9a10b4b607a88186f5b4ee44cf609693263e27a3066a98c13462165e91b70bd42883b1b3c8f458a16f3984a506194c39e364e9143d8c4e2a5553630c069d464e87b42a492aa544a08618cc22b2562276c4132daa11863c755885cfb2256ea006180c723e399af652ef59d7a1c563051d8618955f30bae7647617494504690d2f184ee879b40ff69d1ee9f183c70e52d70573c9897cd6cb95c4a23a34470d2e98f284247b47f6c47d7e0c3072fc185fb40ad05250630b85cf95bce59a5c122b6b68c1f8396f94d0398f1dedc38b478d2c982e549ed9f54bea3f8885add37cae31bffdd2bfd8a1c30b1c382c20811bc400a0831a5738a5895421924e1303a386154cf9e2b7da8b5fd4a88227bf4274f79c1692a840907fe1fe6905a1c6140cb2f44d92a87f22844f0a06553a9f08a92395582b0ac6ff9ccfc9be46574728a8018582bbc5d6a83c411b396216d2ac091daae104a3f5a589a182e59ca46f8231d26d8b2993ef9dfa13d4608241fefe856ab88b1a4feab2a3c6128c926e945e9db80e2d1fff83c72bc19052249720440c912c9b6a24c110fef6e4a988842bf3336bc289cd521d0c6a1cc12445251d2392fa358cd099b04bba222d656419d42842fa5c5e539474bd356b10c160523664a81c77d6f7342daf310443163b7d3a89137e419aa330a186104c1e26e88533fd7fff8aa046100c3a98ca76aeb14f5a0404237af6783a45ba3ca2c60f5049693e7aa41c39c478efc1c37e8c510107e0c0518a1a3e30e58aed263f6ef5942c871a3d3088912d572a44b20d3578e027f17493dfabb103735ced77a8990a57971a3a3087943f5458c9a73ad960e4c033d4c881d922fda928fd19e1e339c46852622440af060e0c179e3f9b87d00b356e600c0b4a4793f74f178cd72ed4b0812949df1d6d6abd638735307d8f4af9ae4e09c9162ed4a081b947c50e2a09a15e4e3486037488d10ed02146ebf8a27d3c18af230c830347de2c8c212c52775bd29fe6781e3dca6461f6bfdc2a7b3ba1b5d6520004390a402316e634e194a7240b18edc3470212e03c3e011cf8e28b0623035f7cd160dc6981062c9c9c73ae5675a0463edec7991368bc829c3346992e9bff920e2d41d07085e94fa7cb2e73db772740a31526954ed5a893af0e2d14fc8b81ac30975c18d7ac6c17dbe34a0110e470008d551872cbf9dd4fc8e621c80c345461521f4f24d1e5a9fb16038d54e4a73921c9ca5ec1da916305ed637d40031566af12a7d3acfb76ad5314219f09917c6cae44caf0950a020d53986754b9e874fb90d26c5da0510a1bd02085d1c3a5df13ad9fafd71f6f82bda18106fc0e1f1a50c002c0a0310a73c8dcd2a33c2d99ceac1c804301bfc3870670e05851983aa66d7fa5a450183365c2c5abda9af280c2f471d7bf72752539190e1c9f30296d4a694790ed1cce22030d4f18d47cc5778e36a94d3c061a9dc03d4dcb2ab897881c6d25f6f5d161bc8f30d48b0caca031b0464c063438613c51266e4d45a408343661f059f5a04bbec4451b8940431366fdca7dcf7163d2c45a9076fc48cb8471cf52d4fa6c75685565810626cc394bde3e1124df098b0b342e61ca2effeda87ba2758e1e89c7abe07d9c95818625cc2112c4e8add7d27d791f581a584163a06c05ed4302342a61ac5ac92a172f57e93f0337bcd021463b602d8c06e3980220c801011a943097cb9588a6ddb144476312e6cf27c9ecbd52aa3449c258d9fcadae258ae552240c97826765dfaf58c9878479cfe244ae6f8d6efd11260bb3fc68e97e270539c2a0c2c9bbde11a722db8d30a8a042f3fd466ec66684499a276d62b45e84e152d85af913492d164518ab5256a7f510c2f3980853a5892343a99e5069448439dae8ee0f3d514be50e619e496272a2ae6e9e9a28a061086388fbcb56915408d515c2e8b3d55797231f4a8430e90a7d4a6245cf6ab11c5961f4040c01340661fadcfe97453541d457f24f4ad8888f1e583f7818b3301a8c3b340291eee4654ad51bd00084d1f8c37d4a45be7ce7eb29411a7eb0e3c8a4cbec35097a0660e086d51a68f4c1543ae5d3c9084a528ef1d841830f26c9a52dafa6dede227c008d3d98e2c76c46ea91e2f1530ca0a107739e144c04a1b742d7d963051a7814f4170a1003078e1e87041a793049c8f1b3766944091d1d8a85a583061ecc3979b476ed088d3b9852d4515d794b72cbf3020d3b98279e14714d6fc9b9a4071a7530b9a5ad7f38f921beb581061d8c2a2a74a9eed892b3c6061a73305c5e5910e97477d2b71c4c171a274aac2ecfaf71308c0afe156a42e868270d389874aadd129d722a914f1a6f30dadedaa7ce6621f97783f944333d7679d2f15687968e80461b0c13b52eeeb7a738f5d16083492d788ed12372f2d3d660caa1dad4472d1a6a308e8ef20e1d9d278eca6909288d34182b9dcbe98d243ddd8206e3298b35e22e2bb682ce60ee971ce6eb7ed5e5651c689801d1b32e4abd153c8f2a1c380a216894c12cd1265c96d16c097fdff0e28617371270430312d8000e1c8767a0410693b8ec89f82989bff60109783076fc00038d3118f4497df3eca81da72406932475e26830f6769a9c7412f14e5ece60b42f29625732e24ece0ca690b29f71225e7dca3298538de796bc351a9223836947663cce047dcbec6e0c66f118ad58b1c6d08518cc7e215b5b1669921a06431a4b1f8408263018d62c249e2a1171dd3318baf8824956f4641db7a0dcf35e305f8a914709e5915474bbd005170c237a3b692451af276aa18b2d987e4f462959d182297479da6e87859c9845e8220b8b09392aa6a89073ce16bac082f1d4af0879c1fc2cd2573055f80ab25348ce1a522274618552b8d8ae4a51726433b79817a5e73d5cf9f758e8a20a260bd62772b96fb1bc2074410553f9dcde958974895ee2144ceb63d944d8fea04d772705a3c987dbeff93dd7d754605130889435bb6bcbda4edf0514cce27aeaf489a4271845899c62891482e53b39c1a89e7a4177a834e23522852e9a6048df1a263c8fd08f6d0a5d30c160113d89b68c4d7fd112cc23ef849a70093a59100a5d28c1f8923ccce8709554971908ba48823164084b62941a9b5ce1c08103075617ba4082d14aa43eaf12d94545f108c60c791d215e756104d3cb7b5aaee9b5ac1626745104a3e7526aff3c448fe5218239d889a4df7a1e82c9652497484229953a672ac8612ac8c15d08c1a85a21e1430a6f0bba0882c13da478c976ba501110cc622162440941a5f6f507864faddef20e2654ba74e103a38eceb994be3b11d6d403739bead57c51fe154d3c30e9f718bb8dd4c9837660d26ef7163e483b9f5f0786f9911797eae7c0d8ea4959367d5b36e3c094ad73775ec46f609e28b271fab781d1940a69e369c452e7d4c09825dc93c7bfceb4ba0b1a98f4c94792772be22fb3309d1615d5aaf2e91861599824e989d99525457f636196cbf20c4b49bd220b0b739c5dd3d77b798571267bcc15062194c9b8aea6c50bb6c21cefd2c87cfc5861fa709395f3534eddd92a4c32940911e53a92bda50a934ae192d8a79718499b032e5261f4d06149e9adfab81415c619cd0eff5449a5e49dc2ac7a1ed29ac8712757cc8529cc21f69d3aad3ad1e7be808b5298d2bec73f5f8c49f196643515df744b0110e4500117a330b748f068c96747b72c463117a2308499b9cbe9e4a1302479399e5bec10e3d4a030f929799f438ff64ee94f984fe6a9b394145722f48439554ab1546676c2103c54f0d4f1d3a4cb09a35790b359d242ca966dc210a48548d9b3ab54b65613c6fd8ac9219b7afd138d910963c88b31c9fd8409f38920b27b07a13ae87e09431ed7b81153f13a792c61ae9336412cecc70f57c2a85bee4946357e46a684792dcca8d4900f6fdf240c2e1639b6ed250953ca3946a56ce1f3ad2412e6544a7c72d45232250b09c38e8f78d1ad5dfbeb234caa2bfd848d312174ea087388ada43b5a8de494d208a3eea8e7aa161961bc58790f134afc9db508639f9d56c826449e9ae8e14211c6f288b5716e39592425c264712ed6b79c4e297b106174f708fba52fcb5cf210668d1c52ce25cf10a653b1df26e4b417c285306788701b7ad2955eb92084397afed5cf16e71f498330eea5d39bd7e9f17358102639efc17d74ee4f6803619212948a15f92cc4a880305b74499252292df7297f30484af36b9bd30fc60ee27127680fbd2df6c1182284042149e3734fb8e083714e9c1cbfb0ead0dc432f2adcdb2c25055ce8c170156fd4a98c502ae7c1d4c94b5faf96af248bc78efef13e481d800b3c9827f887ca8cd0154ca8035cdcc1d8d559bb2a516bcdb68351a469aa44d3c147c975684d7bd221f62d74307d4afa82789c6429b23918d7dfbb3c3f85f66cc9c1fc6a2e323c290e868b6b11c994ae3093c0c174226d85dcd8d688578716c7808b376c23513bf54d0660e0866d10b8704351f5730ca582d68b29b6e1d8503812176bc84f3b07997b22cf05065ca8c19c15c53b981ef352b948c371818633983decb53cffe80dadf2e0c20c65e0820ce68d98135cac432ef975687106b818031762281c18e0220c25792247b780551bb800037a629f4ab22faefb8239bf54e948d0293eacce85170c07175d30baa756a8ffd38ef51817cc17e14ff6bfc52e9d70b105a38794bb9e4ba405439e90440eb29e2c982de65dbcfe0b93ca830573fd88f80aa6129d645f1172288dad60ee54c2f3daaaa8986e158cafe29e477dbc934ea9600c8b1a663a2a533057fc701fa6c4cc868a140ce2d2d2ccfc1905a3e59517b97c49de07a16052e1f465316d7982719405afceb52ab19b130c9e53d5bfee4fcaba0986a433d573ae3b7ab4b86082b94ca99f90de5f8249bfb25ea7da9e7b4809c6fa3ced2784f810469d0473a4f86f3a799f4a949160fa2064e7a0bd83be6f4730ede6967ace13d721c58511cc77a72bc764cc45118c0b2218bc6b573dfcfd2c7b968b2198c35eec8a33cfe9b2bc75e04208e67893f3ce635ce93c291ab80882d92b299d929a4b3f1ed601174030598821a394ce5ebef20f0c93948af390fcedef5e0eb8f08129eb26754851990978ecb8e1c50df4a32770c38b1b3888e1808b1e1824c44f4fba2d073dd31909b8e08129e9285ff2ed4e52f8eac0c50e0c672a764e49fd3a30bddbfc05a5e7c090534e77eaf6ff727e70602ae927c54267d50a3937304ad239b6599738eddb06061541c83e59f9ffb9b9a88149ae7bbed694a0b4ca5cd0c0f4e3ff9f575eab559d8541e6df95672a0b8327557af26b5c8dcac5c2705176ee2e8385e9fe4f6419ed10f373b8020ab0f10a539dec59e7fac58edb1526b5ec7a5e210551e96d85d1934cd35d174195b4b0c29ce3e933da3c26a5e02a4c71db9252e496111d55984e2bb9ba488bf7a5c220397409e95a4187104385f1f25772d01f611faf531852a98d105b21bba6c914c69ba472309dbac472bc14c68a687be31f77ec6a5218fff4c66c844effd78dc21425988cdf505a73335118accd2c34dd0a8549750e1a4a85adcff10185d1447d1c4fd9d1fec4363e61b4937da7bb69199e270c3a7bfad4e2419212f94e9863a8ce06274ca7674936376468d7c6264c2145b2d86225b7c3c786264cfa797cf7bfbb53884fb09109574434ab1cb53ddbc08459edb292240faa335b1f3d6e780106f2800570e0402c281b9730493a9597b98f258c22ff734a1b63f2835a09b36f29214feee83115a2432b058f9e035afe60831226f1523282aa1c361e96828d4998d3e8f8a444885f22846c48c23cb9245d58b687534148b01109c3d59c641b0f3944cc06248cf6a5effa3b7ca6866c3cc2d49e2fd684485dc086234c4a9f14217374e5d0fe4d7029b0d10893d0aa8bfb693b87d57a78f5b0c10883b0c8de651fb63e692810a327c017051b8b30fa25df09494b844abb0d4518bb4cbf77457c885c6d24c2181e3cfc9dd4309f30220c9e93f0121a56af351ec214a49a16a5661f0c3084e14347a4aacfc536dd033210c618fe0bf00f630c7fe7828d4298a44dca4e77499d5cab43ab7afce0f1000da00f2303387078516c10c22ca3e457bed69860631086f8492b7f88916b5b2208c3df25f716217ecd4f360261d4ca6142994f1821260ad8008451764d48bea0cfc6471b7f3065fadb6e79aa84c1861f8e204f46e54c57d1c7f3e0b1237f60a30fe6af93277252a95492bc8e60830fe69195543a112dee771bf8d15f3000070e1effe3477f918a60630f0679baf37934c5d2c46ce8c158ea259aadfd44af78d9c8832917dc2359aa4a7a4ad99d69db2fdb47d9c08349e5d592488bef6034b5b5bc30aa938ed20ec64f39443a15456997570773ac65ff7277f3942345041b74305ccb5958fa768e31cdc1282a96eed6658ba54a7230ffe6e7cb895e39f58f83b9826ffae532eb4b3f1c4c724aa9da9b90635efe0de67c237de28f2e7929ef06e3bfc90fafdbb5c1f4a5b26548f2cf2284d8608a286a4ec70b21cee5acc19cbd7f42e85822df78d4609e09e7c9f39bf4de4e1a0c263b5cccea4457d3a2c1a0ecc7b44cd4c7dc3d83293c8fe4b524c7fa5c3398fdc476bcd1b50c267579bc275e8e98aa643068a5d293214b8aa59431984fc9180b693907991531983c5bc7cf8c4db41cc3600cadaee31b1ff252080643c82942bce4ef0ba6ce9e161f23e8657c5e30f99ec452ffaba6dd75c1704ac5da7d23a67cb86092cb3aa3feb52d98cdbf3c5592acee6b5a3057c8dea698d887ccb260ce93375b693145898d5e0585061b5830e828e163e59b5dd05ec114b4355594a79d39cb0a26212b4fd9b5dfa57c150cbe3d2a053d51ca83a8601a917d7af653018f54a894d025335924100743c15010c4403cbc6d13130000100c1a92c662c17848268ccb0f148003543e2a523e2a1e262018890503e140180a8501e180180c06034261602806c349a28751d20310f8b499e19b9d6bac49a2423657152f5569d5e3c380512ce05266d1bddd8312c362bec94777db72661ea541b2ac36b3d306f1af80a70ef03dba974a6be65752e034a571120bae82e15cbf6eb5e1d18b8b7d6154f19942a700305298cf2e3415a7267fb2961ce7e5e56f2d1d53f4edabb0d3e32e066c7a12232aab4beb137361bbf130dcf1390fe5319d83bdd9537b6025b91e92be782e07ec432bf114e140259fd046b6fab2a22f3a7e06f1d24f435a69f112f44f9e7f906520d8e9a5a2dc07813d937072cc0fd3199897d87328dfce1f6e1f6a861254fc2ed413e7c7a5e1c5229016c2f828b1a9a733b70de650a90d572f6c73a196b17fb05c8db0e6402a0bd6f05b872ca89b00dbddffd5b68524d40cb97695066edb939aa4f00a09e6f1c10a9c856e64312afc5e1adb219956da22a50ac3a67a477f99a43a9ef7178bd5d75a361e08a77379d190bd810b622ff7c671dc052494eeca73c9adc552ebbec6368d3918eca3e642911cc289dfd627fece88620e53aa2f87fad6bf6fc919121d98234ff8b1515ef24a33fae3c07196c2ff76baac70d578fe92040146a16e358123ef8df6c684c0b16e420346674d8dcb9ac45af489a6189a4832f5a8bc3bb76ce929b07e326f372bf3f21f455e5b6b0290e1eec65e6cc5fe8d645439538fa8c1465ad14a6bf2c1620a41ae8c6a00553950493d2386564935d5e9a8f2cd06d5b1965aa59a6729631f7807769268858738a4c4a6dda019a84a0ef5e459815517ed0d05ac5346a7cd294869dee33e01d11e8d836296db82495eddec12b10e683e3deb459a05b37ba42c79b44f1da27cbaeabbc54e4706099767991557a93dee0a07a70d058ff86ec48bb1acb54a39af575cfe598f2d6d61e9cdc773081d13b51c68340462a8c5bcfc611f15eb58c8c7ad925e962e938f58e5a376f9231bdf712310c02cb86e384438b5385bc9284f3d7db9d73445c3b40898941cf78d6ce1ffa37fb214c5de568c3dfca68a7db66adf664c7e84dbfeb905cd1ce96a5264ca05191612a54651311f061b891055b591c0fb2200155011b0ce43134029fb3f005028019445046f5705712b21be2accf6e52206e0ef8e38d9fc78937c467586bbe0497c57751f58f907c76d3a61d3349ba66dce9ccdd8df06e383cf6dc2c566c6aeab5ea9c7aca650ead4c37cb60b1a56dbd09bae6198241209692381d0b761954cd2fc2b6a478d8483db0edddddf3f0b8d5279a71f7b3a4c1a3f3a0d52d0029b5bff1a4a6fbbcbc2a5b3d15cf7261daa45536d6427c256a4b7b4f2eb147e3e71b59535edfae4bf4b5256c21d7a79e966e933983921f1be0ec1e111ea3cae8132e18f5b8181c0b5687972090bd9c5d9722346b2be794fc7c3879008da71cd91f6564c9258db5f40b58bc9d26bab985f46598d0cd6854fdb0ee72bb560595aedb7cf488a53d5bbef2a852d0673b671f076a6c4af980a645b4e8c863a0e96640a64c78e2232e174434370784c61e35f96174dcb54f7a823385adcc81a612f4636a9143b54cf050cbb38a062ba61c1648392636b97c5e2972ee0fe25c01c0f4e2c871d25bc73538d344e653042bf476ed8d971368b34e5739ad260e9988434bd9beec966fa7b9b0c62ec605e991c665ebc3901172e8e862f602ac89cad4f46647d5be043e51074a76d8b08fbb52c80d6af0c78cfa4e3b1a4db49896b0afb556af762711aceeb0a45e94a8bbce25085ca965cf59432af3a11bd7823d4965a751b7b915db0c63c52c9dd869b642d9f5051b4468b1ba404c19c80e2d28b0df68d3b440ed19d21d3239d64c90fa8832d2566e7d55f9715a9346e58b23f6727f5a6b5e0ff2e6789c3bb9a60037b1526127773261668a3aae671275fcb71fa47779967121f1a97b2cae77456676ebc1b4639a50153a22b0c8cd0b27072ea0a64a430036c5b451c4766100855e6269830bc00c351cc090e892760078d2905276bb91950c0065c91ce663266ddd5931585a07a827432558fb4464a4f3a4181466333a5b0b810d0993edf6e0a9a2a40020e3184b2cf5184f54e37f76ad388e3b72bbcd47461b339d8a51a92d097d5ae450fa4e32c8d36a703c96e61a3298c4c61047d23298b4e5b44b48fe64ad9b34c2023813d8e76f522c30c3752d77ddefc93a7ddc199c30fddc4a2a7bd1038e4e05ac166817c02aae06052aa4294396ab6965b5736b5590281e384a690516894f2028399cd7d0bbea4106db01981bd891e648a7a2aea9ad87e40d7bea36fdccf3217bc0e994ff290e0c0cb60c941029a8e06e238a0e9665d101bdadd546d0baa4a293528038af25c73199f45655bf1d8734a5e060cb55aac4f0d1b93ca924d2cd78230eefcf001b51970671fdc72535e08cfc99c72a66bd357a1745a4c7552e883c3c0abc67b343e457b3a22c29a61ee78c3a7efbf8fd3fdb37618088746246e03d1ec6607188179121cd9d28156b122a69c0952d52f7eda3c751c75b331d6f3b82953b7dfc41f57278c09e3bb74994b5cf36dd8189a35ca092b8893e22a3919e666ef430afe86b59c35b85e085fc03f85650062130cfd45e46cb653278e54f94e2ed4aa4f05897762437f48032d1f1266e014e5c341a77368f51855ad204e99c684388f7db0b9594800e090c6d145d8ef70a71c1bf8db19601e19819dd4b11f28fc2222da55eb04cd7489e900d649c0ed7e1178e75d505f6fe2f69afd762ae9577914d638f593f6cfac05dc0c71d36d4e0c156175f171a57c5272f426bdf93b6d5ae6b630bc42e51307f6ba7d72a704a90367b59fe738f3630d699e56d84d02da54b304f8498bb5334aae74845f6110cd0481c198e712298b37298657995ca0d4353af8f4926d114a3865065b4f7dc57afc1b4842bbab1c21dee2807495908c6fa3f6323fa7047fc0fcb0063399ea6d72277a0790dea6bd710c09e468ee9f3606679f76314f13bcd7ed0f2f714ece14dd65e85475e7a3d63b9d9af7194cf8f543d8300f202c8d68dd9aa20837c14b2bfb3c2904752d403a5387efe5252d7af1ccd30142e6008bf107ef3f65e697ee696e7c75f860cebff4b865a2ede1fd93a222f42c6720729d7ad59bdce495ee5d9ac7b0279b77aae28d70c11162ef21cbfba007a3679d772f8f8ba770fe3626ef665d2079aaf5f0f47cf3c2f25242bd09114008e5dfd3cbf3c52fb0091f1b315214a07b5479cec37b7a47802c5c01e668960647181d85101c078c47b833a25347e5e55895076977d9a081fb483a54a6f0c6051e965182688cdf9f89a98aa2bcf117908fef69de2acfa7bb66025efc100c2d46c03055452898b990ec20698ec4250e06126e23ddc76a7720822ac22ce6d1a6b915c2ebf4555fc69d4743dc39f9040664a7247476c848dc11dbec2386721713445d2b1f611a1dd78216409cc6e2d3da6df6a8aff2d1e4c3e59a1a2f7d143c2b35ebdfecaa10d690eebdf4c7c6a03218d74d0e53f6e227d72c070dd329421c9070f100083d2fed8df7a627a46a741a1b06d106be25b990de10ad8713de0c783a66b3fc6a20e90234318ae816cf0f02cccf8b5e6fde327459d8cabc8117f5c640ce521091494731dff2ae38892a89c34b9c21dbab4d999fb699b077f2562435f17d42a209a93cc046ede4c80cf046f6166945cab259d56a0a477a78a3e0b009fd23e219bb513fd462b9db0bcb2aae456ddc06dbd38ca260a65ac13b50fc71e6a08f16e6b81bcf426a3334d41c7f5524526bdb3c8430bd9c50ac2eebc7fc0b61aab2cc5e9c16ac485417bde40617814c905bc7bad1b9f93b30f705e8cdbcef093c25c19fad8d1885694a4df2615a5bd0544c481c611ca3d6dc08f0b1980fb1178412706435437b95314ff7ae0225e4f7f9a893d98c95460acfa0dedb129b3edcbdeb64e298d294fe9b9723734538d1a49a26543759017503da47c2f2e402c78d9ac5c721691f292ba2433df915acc2a5a652d435ff226f89ad82d4a878246f4ccc279c4be5ad07949204799bf328845ea2ad218cd4da172d75168e4ab2be4adf32f50a5fd6b15027a5db36b343c4db31edfcc996c89ea87c29689c58b48cf46b72312f416d11954b3626d98005af89cc2bc2ca34d7ec882407214ba12a8773f95dbc1c0ca646295a83f76c6c3019e03854150e5ba115607a56b05ea97711bdc3d805482284d5e74ff2abe116eab0e4a943b9351d39e41c3f29f6456b105507681b5d6a5f780919b977483365040d368855885f2ab46e5ac8f29e11e7f324bc82a1f64ed01ec4f7f3888c665b34eb275bf068f9d3fa06b1e77dfe69222412dd15bdeb2b08739aefb9d10e5017405806fe1a3a6484b20b6776ac3db078a2fb7fae0be339fa5c6cd936e2ec390fa42de5cc339d5450dec9f586acc1206e6ff97819e2a30ca29a71be3afc18e036b83975a802f0ef03a90db6fca0b9864a36251679b5eed17f5f37fe0c8726cf9df2313c6a54460a39cd6467e4fcfc0296a78c692a0c47b825165097ffbb2a53de36693a049e6adc7523eca6737748601e4c35cc8033909ee502852ed838886048b2664f4744bd9d5809a486ba4def9cb6be5021e71775e544a7e060e8dcab300ff427501a1e39f0cb0b823e73dcc07eb714820a686ec76795ad0dea05105c66fba636caccede7c3a0863dd0688dbcf92de66f7b2c33d6f23dea12042173887d0b43dd1676850d0100dedd02b3139eca4105b64c168f2005514910058144e34780d62701c2c925bc30cd85fda2e93fe37b797630b91fbafab1181630e70e45ce4b7efdfec087fe31e5134387e865fc4e47773629c01e90f477414134466d0a98ad5a0ed8fc41e8c81eb162e068c4165027681f08d9a9e0e78e3f25000b2b0cc546cecca2da9eb81bea0d4dcacc814f073d9e8efad6e26dbbd7343421caea293fb2aabb615866d670049283ded9b6397c5df0c339c84a72e2e29ffbf6148522f74febf91f269490a1ce31902810967cb0c6508b81b068ec4b280bdd009301807fbddd0f84ce622a80f094dbd5868dcd214d55414f685bb6976eca808cd6c42e16d80ec3916596da24c31a731cbdc80aa51caf6d1114aab44604657cfb633ee1ba910b4d314304acf879c3498924d1dc7c48bd986307866a9c9e3991014dd752883c08c932831ac49eef518c87998a08240c44a27c596959d9b090c6056d8744f4c605e49f31c38fda7a3c62411750d95fe157748ce7c868c66cb1025121e0645dba5cd1a8b6a08c5b8cb5e3939d0adc4f15022c4c318065463dabafb62b9a8f0f20a7cbd44c86c0180b187ab22f98a7d35b4521367ac03732761288bdc36912a2bfb8d0c44b9d99d93406276b558cdee0d69990c94f0cbd866d6dae13d2e6ece317134f00e66fd08d0dfca7cb3d1ddeba20b62627799ce4547e1c99a62c6f1ea680f34adcf213c55cb5d5aab3497d90ac89d7fe461ff59fd3aa7994240d9860e9b4dcf59a8fed33825f8e4556d95135b69f7c046030c5b4c342ac20a11a5da9d601a45f1db42e503327c86ff24d5b0f97ce45d6545892926ceec0f441b11711f2d098024833dab0f26c43fd535aa41310754df2b0137f55ace97e0a2b113b52ceb98a6faf6a6835c2e0091ee48b0aac5f046a188e9447e8bcbc476914c73a9712d4e41b155cf2e1d82315efd519328c0a5528671198d360fb249c04db4c0f328a583a0528394b019ab63a8c017ff0864cdd3cf53b3ab76d5443dd1750cf806d81f3706580d1f107b29e0f37347909aa5e26ae36c1707c8cc1167feaa1834c25ffae7ab1411a07607604c2b5d978cdf51d03056e41e0ac6e3700ebff0fa2fd008f6497133f3edd116539f66d296f6cd25aa5a284d1e3b643541bb18812092ce7cadbdaca3189ab97a601317a12b3a3f29e47f338c950accd3e1079629d5dfcc40b06c87d014747413c83f02a41c59bae590fa251982e974e0a11451b1219c7a3c605dc7287253e29ec039d5127be398228ebb4c2297907c0587f366a7d3406589ed8b3ecce89df6803155098f5bd3e40692a99f0a0c4ea0ebb12a66175b8b488f1436761ba9336a2b80974cc89af07fe9cb0b53c976c588c12b69ce5bfbabd172994d4086b48f80ad80523d82f70a73028ef8dd542164bcd118a2685d650585cc11a8e57cd63926981851f3b0d49559d7234d3e61ff8f3859979309be98a9cddb78d2b571c5c622b12b2b151c6bea4adf23a42ff101393101ac4052628486987c0ec47df25532e40e9f931965e3b135a1c3f543752febad0d5bf38e6eaf8bd584a9d5a348bac32b5d5939ea50a29a97ddb46a2b52bcec65423495533e15c0c75f35593aab8ab1259031bb07671c8ace71996be3668de2c1a15305bbce4db594fd410d27c9cb89ec304139d9d168e2ade362a8107e8f9f3cbb63decdf4a23f875d88e9625839de55465783bc6d9f7c721e4dc844b60e3a09723da6e3c0a76a2476027ccc3b3ef13b8fe6448e15141e3320369b440fa52e344844a03218067a5a0f435026870307a8458508fb9e15dda2bd52d9dd6dcd2312606bb544680847e57d571d8e71ffcec94ae7dd6097a2cb3716a4fd90bf935960d9a44b175a60d794ad78b757836dc7e865baf5e4f531de949e70a1917932503588819d246b17e5b798d75078569fff3a30cc8c9c52f265bca384b3eb8aa02fbd3e85e110c92010321d72040ba517f729be6cc8fb743e4d429855278b501c285a453497fa9fdd91425f4ae34f18dcc0903842b425cbf862511985892c0464bf1bb2d5cba68f5972992f0992d9e50b04cf10dd7904942214bc8ac2a727dcb8740d15e0f3f14f24b5c88684c2413e847c330d2cb6948a060bc7894da9ec75dfc80ef3105a05d7e8020f30096af6297179d876c20aed32ddb4706c4181929e9118fd7411d03afed58e614760625bca38e3f60eb0cdc129e6c134c541d6934b4b52487d7d570b023d257901b82a8328338ecfeb2097148fdb0863e38820dd0a3f5b60b0af72e2b301a1e3c030be54dc341ec81c232920ae897ab12e9d037967090a55c0f033d0a2a7ff9945260d61f7bc5f0f08148706a80643ab38abc4510424c024c48804680f7263a0fd3906446323e5a6500f3dc922a6a32dbc0712c2e478ebe597a430cc7a4f29051a2040b4c88210155970d549a4e0e891ad23cda8a26a5a1f0a4d4028a32f9c0cdec6ab8505f2aed1d01742f20098753a60c990e6118092870418961c58ab965b706f4d7d3e854407eed61b33149e9457b2aa8e2c451f5eb4781488dbddd5a7193934fd0c159740075b7ac5492f841473d86d18d4c1b4e3d6692e6d0841934e7836c80792d548edd89a8c72e5a61c81412195b586063ed5247926e772f5e29300fced024a689d8623752c4899345af69b5e819af4b6add870d90e8632d4c1f87bd25163e99b7bbe57acd0e352e6a58e652af0660fe91e25fa0104632df225ee104899b219d7df565c78e5bf8e7274153dab03d1436760185990dd1f249dbafcab550b959b4351c3c318c638e2f3b48a415a546d222249ebd1496c4ae90efab227bacf0050cb8f1b59f2b4cf8c21ac9c6884756dc49db1c7d63347a110b2dd7c99e41cc686373898a7e4bf39b319222f43b5d2d9a7bcd364949940269f98602980cd64bc63a259d0935407463267a9f392da6a7a6aa5702eeaaac624efe27d360d4158b086e24ee243c3bb91499fb3dc7bdbf2d43c22d42377305136f0eaf1f2120615999034c798744bc8073a4ea92ac45118dbdc0109b51570c7aa8c68f7248ed8248d92644e4cd19f80d5424b78144392b80bd0a6a2fa26647cf958da8230d6a49751677144f6b8e0291f8b0e90446794ac942d30d04a1c7c138b5975183c9501e9b072e9917f4c88fd9e8d2bedbbd26f1cf42a1c1cf6dddd65478a22ed63431fb1a4d9746ed8096a03da7af7519ba4da9ffdca676ed187d92dc9dbfcc10004c8daf7aa631ebd253df0d88b622126d13db3dbb57c50eb6265d5cf16fd9062cce13f275b87e5abad6c7363112484409f79b518e957bbd1b205b0f7c16165db760f03df9c44057a28de50c200052136e6a6ebf7b5a1e2e527959345e9f1cce5d92c4e6755df175fad643a462aaef562b3d013bda9896a47fa054a80ee8bb84237d6200ae9482d1ead57c369e5aee13c2eac391aa268d22fc0938336a1aef967827095c2db4dca77fba0205af57055f27c7c32b7886d72bd513961c7a274909a428d7ae788e72661d24a9a9ad47d42177112fd570ba2cc62907940a6d989ee44efd47c69ff38da1e400f76f3b991171de02459eef153ad5c8d574cdc59e46c0fbbe4a3473b1ba4e326fd71b538f07ee2ed20cf112551efcaea61bcc99f856f8888bab78afda2bea0ea28436dfd2705efd2d503c32d95527640d46b809c69ec016019e6f0f79e0335861592e6b90bc1be1600123150ccbb6267e5d71af0ca5eff72a282acb556280d979746324e182b71b1782e422b2b72c0fcb2c7690b8bc47b08843c1af11074c7238d3caa689e69743b9435e87904126710f3795b4ac6f25ce0a66486e86d7c81618832acb52cdcf162c34c336aab183cf12846f1b5e4fe956f250d0ec437e55874e21058955ec5f22e89b03b1530ab68f1c4245883277d53224f2dba9372d4456a3be07cf98cc32c414f9d70f18d2de61bfbc6270528776d9542c12e1edc37c9df0d565c818bca6ccaa8f5467e49498398ac4b6b1663aae2f182b2b61e4b51a235f880ec5193976956cacf33ff4862d521f6903838aa32e590419315736936f442f74091e9312d1df638195a0a191f2752327efa417d1ae2f7276569be2b4e28deb8b4dd45dc121f9ba3d142850a635dd5654ee704d0e3a295bf69a7ed65fe79077a7cf3b76b318acb024097520d505fe5aec52d5c08d490cf4d613749a5383110aec14c901761cb056fc74f3abee5d20208798181aa3e28d39e4f18870e9c6d1d6cfb4782d672cbb3f8ce2b36cceb7201b7175266ce1bab62761aaac623042a67c49a5a2c1289f1faeed37ec88d57cb158018ceb9eb4d15f64fbfb4d4d64791de7432c0a1220851797cb4a632813b94f088bcbcac4abe06e610a4b1ec39659868bff25fb8de228e71f5ea540c7f8a1fb69a0550a18dcdadb8a0ba611635627746b45baf1620bea216b2613641ca3ceea465e215953679768696de92ecd38aebb2c1805bfd615c8657440b4ea5975566bf26d3cb19eb9eead9b91cacce90ec5c0771755250d1cd2fde39803648d81352e3dafb90a652d4b5af7c3190fc82873835b1a31d4a36a5d3093531ed615850b66aec3b5449423be54f17328e7a16127bcd1ad4cb304dce5955dd1d011d8d15d46ba4892a263dcd9be86562fc78151d32e491bb4e3b3a3596efa1344211afb9d5d0f205e69840f47f37e12e7fb7bf5735b8599d722ec8193241838f828799d5f5378e43d06aa7aa315f2b0185219ba08e563f82f4a50a32c8c6d7d48d45064b794a0c6d1264ccdb9712d39e33aedfc1d82c94a39701ef9f183c1239a03b012bc8cc3e8a40405cf5ac115898ff029d682197c7c349bf1cf9642497dce19a9cfe8a032e35b782efc8795741e9cb934ff07ed0358df888992134bf6d0b2d9feab05c67fd10dbd24d55148f0e825f673f3958282f7235e839dd1ee52e6d43623a557ce0152886f01bd65f309494ef4187deb2c6134db3649c5605c9fc5997bfe7c2f39f37f40b9a2ca0706008576e1c240dcab0280da226e0de697ee9a2de840bca9ac507894f1302b18c2de24bc77d8c0c3108289e403324fbc56888a750a0bfdc2a3c21c2f48a21bacd74e4fcda98033c5fa2daea57bf3af4f6c1e0aafd056c248f1beb48f17962e6a8113c22eeedb15997d2bc69467088dd4d7edcc519fb36d93fce07f13f53d32ea5ec781d7679f349ffe6490b0c1b3cd7cf822933b0383e639b145f578cc619fb98be6d55e373b4488441ce087bd78599ee1644ec7e0dc7c169c529e86cb4011698530e562910cedd840a9eb61849406b6a27b594cad486c57f50a67dbe456a9b7c1744e3e60d72891464770b7a50436b8115031fa227ab70170e960748fb05e9fc3fab51c78aee9d805654f19ac225807b5d3bf2fddc30e49361b576f11ab05ddaa854a816c04dc14376442d21657983b097d68c2324a21f2f0af304d378edd082e22c2609984ca96617e8c43cb5f1802cc2b350e0b4b0b768cac2f061b85437bddcef9c48f6fb9c6fc6b005d895fcb798bae8bb604d2a9fba5081c55cc11b96697ced4142a706945bafa111ad687215b4aa407aff3cd0fdf98cd56f5e8c7eb7768d63b16f20a5dd8f6d8a3951658b5ab48772a3876b633248872b2eb8341ba69489121f4354a4a9961a941da2a8246b2304d340ab1af105502eeaec973f5db55729d773430c14a00200766d175d1cc51d9768384495e319709320c6efe50e9acb1703ea79237cb6d7cb85ddfb6ba933498d8fa36f936ccaf8c85a82a28f214b04c0b419e444a856fb2fcf8c20641408e26a67f18c0a68132574c260b580b429d8740d074a3240bd0182641c8cea043e55bee357a798aae228a55e86924a1da2509a5ad0f7a243b81caeeb662cdb0357fc2a7feda430df1ac4c7d3716d680510d9faecbe152e64a8ddf65baafbf10c0ed442d02423ae63b17c88d630029d458782ddf224a28cfe7e9739e7fc5026640609806721612a47dc91e5a5d8f8375f4ff0eb186fb50000d4e5ba3af8a487ba8d46cfe65deb392ee628f30ab7c320b45a3724ad487c03fee0887a36a9d8f3950e84bdd6b802e01ae8e3f5459c73403e88624a0b3931eda800fb690dc0fbdc741d60d7de7b8f01031f8734b34041eaad2338dcd6cc544a2682ffe6e7c02613a8ffeec91fbf83cbfe75382f0dc6e7e9590eb0c961c6aaa6e78ec2760a0625548e59ba705b1c31f91595841541cefc2612ae76cb9a01f4cb38edff797470a414bdab7b28cd4441c5a960ecec11df7a017863bb7666d401e70918772c58269b6e7dd5ca4ba36e08d534df440d75f11400714bdc27af48093469a78e7c21f233bdc260b7ad3507622571f5b8c41429276582640c9348208ea080efae161ba1210237284fa6b4f19cf64d92aab1d6bba2bc4a0701a548d53fd997ee4505967a16ada03368236222bda00d6f961d33809153854b6a22f2ed447867a2151ab4842ee125be7705b4c5cfdc70a36d7d6244e20f9602f834194fe8c626881b6aa3335d7e000d767d3294ce945b7b38485db47aab142485dcbf70702649ee8cb84c3ac54bfacd2f309c5c6533dbece1d774ead1a857194239b07dcb14d97d3f416b7c942f1eca0b1d943889995dbb66878f50971be3a387e62325fe081ebc2f372a59043173e5669dfc34a61c7903c564eca80a04dda0d90276be186f89cf6f895e33638690d16d2e1df7b53ca0cbf1f707dbc078f38d171078c1cd8c759cb5914ba7711b6b3e5cbe1a404377a681b6e14bb2421751a8d908f3234766e34d2e834da4c88c3a9086818fc28d6e213a73025b0b4576be2602193e86e38c88c443e1b8fba8100e62aae84bd8e7af742d27f9ee1e680ef10d7f597532144be142e53d90f78c7f979c0fc1172d5ef066eeb71b60b7452b2fc6a87f81ed9defdc3418abc4bf9610162b2380c113f75143a4d85422a93b44e2d50ede6cf890b5b2d39f3844194d2e3f96c70907449568efe72549e923289dbf9f3068ede75ea5a07e051d54e91472efa8a006ef6894aa5f9704d1f536dabb8d6d7156e249b232b2d7e865b2f5a8c11d1e82805afc030a780c3a9696f4d88e8d81a7d77df019ba37ae6152715edd15144abe8c8e484c7ecfab0d624aeba8a939ce0fac1afb7465588012f91196172d62d10382635cdacb58af9a9b0b8a5a14955fdf0510b5a65324434d421abba887bc00f47ab805a85084e282f788b83dd9a64620358e0d7316590585d28e5f563bda56922a8749118364b8910204a1ab9c1e5e55647247abe63ef13d13413a667c63639d7c3394e65fa13de103b4f36255277c755d3fcc9ded573252b54eb13976bf39bab33d446edabd303d25d49e448120af5c23ae3b87720a337af52500d32341af8b8300c7c1815647a3974455f3b2836ee48ee558d1a6e596428c6df716f7f801efa11daf5aa33a4fe45705e395eae28cfeb29e4573bf14d5a6be3e8711f50c3eca66f37432b66e2c98423b4de95e9a61f18214ac8de1273149816185a5c3c797b877e41b0c96311f9187f0d1f4201b06e2520ca2187407381985e1b403bef07d9db6a45804fbf824c24bfe0454c733a71c8113e0961e1dd4cee765db8dd585f5cbc3d3e55b1a50a964a3a3e8a0164a4e74293746d2a7950728f8b38c7259c9dc62f78ff78ac9e632f6e6f0a8fa1db2508ab0c7bd85d7fc6260508106f8a2dbedd7edf159327d990b253a32c44051d2a41ecca4043048a974b0027acd2d736e67f4e5f0b9909b5a0f37cf440280e57ff2d5ed824300df6234d67832768308877416f5180f61c96a1b9a427f604402f3d031cf4315d01c62277514a8698b9476139a3e459ce0740ecc4e13f7351e46da49dfede9b6bf83b08f1e76d917e3a51c82dcb66dbb31958f3f086e70ab726f1db8115b77469a163fe3dc98c88814115c78551c011ec66da45ceefc07a8dc1fa724606c96d8020aa47e09d6bde8e3e29f99cf09d61fbe3fe2c0621a2f7c73e660c35f492775b6a13c4caf399d1e3070d11275c92497602d4eb05734730b76453e0033a1f31cde2c7b87989e81084cbb84b3a66d1d059f4892e2c6be2976db7b981bff440800246ac1062f9ae5ab8edaece3bba3e31683b14986dfee2d23e26b0390c5408c6c6b593a5b5b4bf9ced44ee174286f5a6ca21d9109b18984aea0c11f4bf091846d1db507c523aa42cb103fb2dd60c3ce030f0ebffd64cf129dddef80818efbd6506f053c4445c794677891ad3c9e864e7347fc1f456e3951a9d14d9a9be058b9f73947b65f1f6cd001c2b970d090bef270c52da18128f1c73c791a6fa3f31d2e527e6f3a3ffcb145c88899b3d04edb0214ca5ee5eb181a888d31141620493468e1b97430a0927150220925a16ca59ffe804af08c1ea12f706f7c463f379c3cb0af1311e356e25bd47eafd3a35fc8f0e6455847908c7ce7ea337c69c2b7cf1cd00648968acc669e0680438b9f92fba685ee09ffda9c4c21309dee92143d9772422fe51a1708a1ada5698503465f36cc13aa1d835f7379f7ce223f5c631538916940c440e375d22f1ec7897fe07cb80f40f5d7d8d3813694cc7729ba4299d01027f81ad0d6f3d628e412aea186e5508e8fa3071f2724cadb1d808baa27d20880d375c61090357f91da029953f424dc06f3efe744ecb7eb72f4fa150686d5407d326c4842ef146a0eff4852752c068e495487dca50a8e5b2bcc81612061d58de41baa3066642f10cecd173d0cf619d1b511915fe15800d0914ae75b7ad69591694ebd273d5cbe6680206c8884f9e41f948624aa762b88412dd3d99e1b11e76783effa23e5696002f44fa0494e94a5cf9b60ecebd5d9d64f0681b91ad0c9be5c58e4157020db5de81f06c2d76d3da574547aa0687a3913b69e088a97a1619651f5f2da1880d9aff817b8a33e16a709e255235692f8cd61dcbb2ca36b7e07d9b288506090a286a28924107c10306e14d2521402b54eb5142b2bf0cad02378cd271d4f3564dfca9c84ea1f5b6695892ca52498a72acaf16d1f24ea7247498e8bc09b1fe19477f45284a3d76648ac9d6b5fb28d265fda2cdce0977055da5f99dd39e0dc4821e21ee4b8aba6e03c9aa09f6c71f6cab0301bb565b7e20ee711dc7a849cca1b6266364375d6f045a57f55e74d21971778fcf4debb85671d42b6b0a146daa886d80ecbae7a51a3abd9b15b37832c988884f1820975d824c7d7cf71ad4af38f46befd91649141d65e30384181599e1b2543250d2cf37c8e0808f9a3813824f43a548bdb33b416c03eb8c98a12a8a21d54ee7632ed553a7bd84da1a6d41e3d42352ae15092c91bc743309fa4dc8fbc8ccc962949f7ecc45902e399633ae71a98b78ce48f9cc3599ff9731ef83f15529e040159491902bf1d5e1805c13aea94186e5bb0363dd61c9055574acfeb2a9a1d6ba80350f7f403b0748ef3df466d05c585d1ab4da3888cd48c66454306c9eb99a07295635523c9513759c9861ab25ac6e2d3ac4822f941e990764364a2bb81df79c055657f0a36fd9a8ec3020c0615c3747aa9fcbdf6e52c90c5d6074f6369e14c5d92d0c81f75acdbfbd5da7d8b49949152619d5ffd543ed5477aa76a8367807b7153eabbcdc2a01fb09da5cc46860cce790f2344625f524fc504e16595943c0f890ed161a89a037371011ba0a1f0f0e1184e3dc404c08353fae7c73f64f9aa0263da1138a6377b657e8f11eeef625f021a52f3fdd502cf02048d3c70ef42ba2c4ad48baa15f915f88e324dec2481080f6e2128ed1ff940ac5f412d89d693fc6cd38ff944b7f5a297b453dfa24aa326ed4e9aba97e72c8f48d346da6c1fe46c40f03ea21668870dadc160714499c8aa9e2f1d192d80a1d6165d471395b8b70af40d8f16cb3f27dd8165cafe11e089ffb06a5e11f3532fc512b2b36583f5f70cbda50894f97333a1473135c4ef4adb28e95dd8828a01b9d4812c91efb92026f71017c0ec004b8eb460391df501c329f4f9608d0b3ca342da857199e02d71d1d8f3ce3af7961f982c56ea62efcadaa629936ae0d94ab3726a0da893e9554233e8fd96f86918f98d9d416624a180092463d5597f391a4c234616d74828fc919f86fe6517321200988891736d76da16e7dba75e9be47971e5dbb75df4d976e5dd5e96eb6b4e7546f57b73fbaf42dbbbd74c4b034701721de6bb68bbebdaabbdebaf5f8386a81cea7e5f34f4bd1d732ce5662e4ba44defc8c2c1619dbb127f9d1156d912a8b382a13962fce0a1ca663dc3a5ad7d455b725d9c999638c0201353038ef1fe174ae0a317563ad7d6b9db98d9f2c424a8eee521b1c471730b30d1c9ecc04ea4095d5528989a6a133a4eedc2525f8d03d01c918954cfa4277092fbfbc68cb63838143b244d7a0fd1eb560b1e58c139d19ba49f2f254e286cfd42c6085b24c81cdcb65d78a1a6d47fd3049cab59c886aedcc9904447ce68aa98bf11bbae24db2134ad52327c04be2bfbdcc940653e8cb6577a26e53d282916c2a9af1faaf974ba9beda2e1f01c1d3261e702f53f6395046211f579201c3b6a893091d469b86b57c95488837a69cf84b38017e5fdfff7f2d5996ead893951120ed28cb083ba9b1162fc6628e805b4791f2e56dfeb21b5ba2bc2df9c30f7f0e67a417d37e9e19d252ec3b9f31c89723d5e75e4b2d49e85736253e3090558a4a505ab52292b8a1364d6e234645436825ff92ae0815101052406d5fc16bdf9a58d06ac7880bb791b18612a4aa64fedb6df61f05a751d944b93379ac178cfb1dd67bc42581d513eea04b25e1303d49a3cde3f4ad7dcea809780e473821667e2cc18238ce13020629eb8c2d8c8ac36deef0bf92599cdfa6797811d0e6bdb4d7eb68fd61d17604b8006781bccb5e5ad7c87d1e977677297828bd17608a2c37c122aa9cc7d4b8f6358aff73b490c06adda264aa02babb5b7e73a9808c4b8b84806c9b24b804da864ca9d93fb369406eae6845b622e7d5eb44070dd8e1974bbbd82934410812cd353bb909638689d11e15395487272806055a55717743284ccc017a5b27853070b4940e000c30c000030c30c000a811c068cbbdd901003441db006c4a4a4a8a1b17c34ad606c04204d64684c6c5740111088a08eb07ba67a01127375bffb5e871f44ea8a0a1459fdb9f1b52fa351ab6c52c1acd182219c3183da7b5852cfa890cf3c78c2c3ac61b046c118bc62c74fe47e4b895f1b0e8a17a8efe57cdd931f28a1ee5fc2ecd96e18a3ec4f1c63c34ed49af156d87cc91bdf8cd2503b66045ff185c94d2148fc99d55f4e92077a7f19f7cccaba2d90e19b224f3e81049451fc3587769d0985222a868520665e6a153eb75f0147dfecc1ffb28d43fe54cd1f67accaf7a6652cb4ad1c4841822a85f4e1749d1ac44c5909daa1a83a8b718451729e4603e7aa1008ba2c91ece73d60c14c1960c78052958c002104080048c60051430818fa051f02cf8c316a1e853c393c4c99bc75125822dedc016a0300b6cf189c653ee144692a58c99819ee884d241cf63b80a27ec6cb189365c8a7c1015ff93e8c7852d34d1447a7cc6282b3b8e99e853c51909513a4c7429c6fc29b8bb97e89159ebc330329567b444ab96730c21836a54a2efac12b9ec5aad1975c21694e892e38dd9ad934de4338926c4e48dfcf954743692d82212bde77448c6a1d3c5f2256c01897163b18a38913ea28b9396e3fb6a92613aa289ede338b998ef65f6a891f39f0dc262446b2da97b324216d1e588102b12b9a13bb78522baf8225912a721852d12d14aeede8f5d0d611f8e88ae716758cf980cd31c87682c446a06cf0925a9628836c7148b50113d32dd2c44e7fbc0273ea892499310cdbf4b947dfc960e7206d145cec6287c8cf078ad236c218836a5fcf499903a659203d1698e8862bea6c1720b883ea34e5afe383a3f86ffa1f5fe7cfc9fa1cea5e8875e4ece232e57ca167f1f9aefb290e52ce3f9cc7ce82c2d4af4f929cf921cc2167b6854e39f06f79f74c919610b3d34e12cb87b9660953a9e875624af7ccceb7ea07cb0051e9a9f8d7fbd9c71ce5c66618b3bb455d92f698a595a2272852decd066ef24eabe2957c8963a34293639b08a7e10424587be7189745f65af3ece0a5bcca18bfceb7d071233c62b87ce71b82c9a2118842de2d03a1899f76d89865ac2a12b093f6b6d314ce390a9b0c51bfa9c7e292219e63cc711610b377421a358d9b26b869a32256cd1865ed73ae7c773f605890dfd86096a95dd71742cada1879da32bd3152d46aa861ef4a71c1bf2581c6fd2d03cc6f173fc72cff80b0d7dd8e0bffa7032a34e9da14991c2f563cec5e766861efc7f5e85bf0cede543df2c0e3ee64232b4197f0e951b1bce9e3c862606cf3b93b269c8be8aa17560213ecc181286d63d76792a09c9424660e852b4c3aebcd12f7429495b876a6fb6895e68534a8ee54da1916ad785f6a232ca7b588998a25c68cc349557b098541cb6852e834832edcf0b39635ae837c4efdcb3f9915b9a854e7b7c2df36ae3980f0bed46648390e921c9e22bf4b94f5fc343d10a7dc631c404ade89e64af4257de8d33675648ee132af4e220f959d0065b294ea15db56425c95229f46b793d438fd98e411a853683bf9ce2388342f39343d47ad5b169ee094d7a2e0fea9ed173684ee8c2a9e6a874bd09adc588a771c2c387d399d0c99bc857ce7d315e5d421fa366fc95a1435d5395d0777c24daaaa924b4129fa2f59e67cfca21a1772c71f2a923e68bed08bd84135735ad8cd0869858b4905166fcb3083d6a14f3a470392a894468265b9e0fb9cc81867d08cd49528b214b27ff09854045c72bd2132c41e85385f9941971924309103a7f14a29784fc8362caf78933a3b0850f9a7c904365ca8f2d6edb83b6f4332f1ea778d086489ddce4603ffedc411fad2169c610e64fa33a683cc85f548bd9283e2a07cda47a92164fb7c0419f632aacca477cef788b1b749a23c59ebc3851f2dbc2067d4a9919966557d41fb5e8a16f2cb1b48e75cf68d15774982b8a9986d8e82c9a3c3925cb8b5316cd67c5f1903ffec6ecc6a2b9d8680c332addd0102cdaf1d070d16c7945a37b59216a85ae68834ad6d4973c71e3de8adee25e94b2866592b3a24bed712efb649595761548ec8f57495a51451fbdf17cc61ea5a1c452d1b669650921e555ff0b156df89c71589dccff949ca2774fcf3e0e3d486bca149d07536f18e2a77c9a2d459f2304bfbc1d1df385a468634a19b9a8e5565e3f8a5e7d3b2ac3208aa2539387a19ee43ca75828fa8c7bbaf1977be1018a4e826438a3a7394c353ed1ef34b43cf259ea31e589d63322bb3bf63583db891ee30ce9bed93cdb839c6882a49768fa83f1dc6ea2ab2e8b1957eb5aee144db415c28694f2cc99483c7a829868a25a6494bc217527f912bd3cd269733f13b1de127d6ec8e6293956892e6e8e6ca2aed5d01c946892374cfa59a911369d447b9acb53a84e128d4ca3b0ab55d6dea1481ca65651a53248746255a119eec13d858fe8e225c78ef3c3ca2bd3117dce58f344c78550f236a299d0911be69aaaae31a20dba0fa7a15f8b6824e6ac67354d116d9caffc72a2aa9ddd44b4e32ebec144cbb38c8826365c2ca6ae58640ed1b7ba054db9c6ff2965883d3c4aade22916a2e9503185107de4642134677785300fa2cde0d3f02a4355a4a820dab896c2a5b428104dce30ae86dcac267b01a2f5071fb4f16cfed0e5488a1317e2391af343730ee4e286870ea7217de81faac80789d39973980f7d66958d6f7c1abe617b389c7e4e21038beba1d55cf9f02776575e9587aea13c508d241efac7eeefd6f9d79fda3b341222412643349c4e7668f5d27a354e4ee88f7568d41bfff707df8f5fd1a13dcbe8193d27c544630e7d6a903a462e4e475972e8f5636c8b1e322a8b2f0e6d869cb099a3b741dcc0a18f9807b11cc7af56f00d4daa14afb9b22d69446e6845b52a5ea33bd449bd21006de8b256489f94d2f3f5168500b0a1effc986415412c4eb3862e83e01d354e7838ce8c206f87fe81789f47920cc6e73a74a12beac48971533a8a0e6d568dfe9f2d5812499943e3e11fab727ce4d843e4d0ab68ebffc7d37c968c432779a91eba7c67f385436f41b3e9594a6f685f3f439f881d1fc7e486ae4cdb3bc3dc86def5655a1f57ee87980d3bcaea9e35f45ad9a38de14b840c574363d528945e76d3d0436fcd791f5987103568687522ae4fde0c8f816768d6db1d26f70811276e862e455586af61bb1aab65e8413cb2ee910ab30c2243f35932632652a4451f43173dc44619e53f5e85c5d03fcef961f3b4bb832f0c3dcede3fbe2176082a83a1c92093dd93e867c8e30b3dc8aa93175ac79ae3c6771251005de8321f85594a898f2524822d8c8200b8d077a45839c7901b52dccc04b085f6a344de90cfd14227d9a33b33e452cd4b161a710939c429077a591b26002cb4f9ba71b8cec61f257385662c2442773febc88b060158c1aa18e42b3447f89855a11431a654546663eda8503cf62e491935f69ea7f0895c9491b8259659299017d753c8339a2838953dff57544bc6120a651c2372c33d66ff8462cac03a23cf09bda523cd8c8c4a2608a009bd54a360e9ae2122250026541d9fdf1a5eaa1d670967ac1123431cc9f4a98461f846742a034375803417082009ede5d8b1fdffa1537924343199ae04990f319a2482008ed07a945897e84c35040118a1d74851f14b31698e3110045084662ba74c1ec7a417727c1000117ad8539e53267f087d7a6811b5e887ad4121b49e83244be1435e574579104010ba8c7d751a7b4ecad4c6e00d0a3e50566f1000109ae85c296feb07a920801ff471398478297e5010800f7aef8c9a1bb1ec82007ad05b9ec8f1e15846cf1516000f4c25399ae738612e5b10c00e1a0719edc87238778975d087c951f54da61cb4256116727c71d0e30e7296d338e3b884057083b63bae925ee3c6552d55110460835622ee766ab1166d0c89526a118df14669d17810ddd6fcffd92a3e66d14399378dd10fc99349010810e07f052d10c30b59f4f8a43b7f6587a09f7906582cdaec2cf7e94f0dc388b0e831dc4d629553c6ef06e1c52b8cce065b0ec6b2cb22bd62f3ffaa75a5acec8ac67b35b664aaec9f4a2b9a90254ac8ab6156f43055ceb8dc234f625d05dbd132b134265c78a18a3e46caa951f87c3c9a118217a9684b5336980fe6d98df281519d78818af6247bca39c4c4245eb9c28b533439464bd9c53536335378618ac662aa6c042a700400195e94a2cbd0fa3346f85513408004c8e08c6005af8108d8b94035b0823741795382920213907100c8c20b52b4a3b2da99611f47596ef06214fdabfce4bc709a49aa17a26872bee885128f7937065d8002b040072f42d1eac36fd1e4a963ccf402146dea98f155d66e7ea14ff4215c349c2a4b95c7f5441f53e4093b318996981fa84e741ac9a7d362e304d79682ac964679041fa8da4df4919b935ec453045b32201f304341060a50009385179ae81b6a78c70dfd235c26822d05d08b4cb4914534062fed3832bfc0441763eefe0c834a5be97589e627e843978896c53f23bcb0441791b36c437ff06fa14a9492bcc295f57f3e112f28d175380e29755749caae400a2f26d18fa54c8d0ed7d7a03759bc9044975c2eba7965fa831791e8cd813d780189c3c6286696e747b4272251b7d4252cae8ee8c483cca8a4e30dc9042690c10a5ad0882e545c8fd93da77497e5032d781664125e3082bfa0163fdafce3b7884cbb377a5fe5caf6f042117d37b418d3332bfafa44b40f723d7262c590a888f002117dc6cda09e521cc18b43f4d93b3e4644b8decfbbe05d5081340abc304497bd77fd31300d934108072f0ad1676cd08b199385dcab092f08d14f0e3942a724837f80135e0ca27f8c41a444d1d8186604d146ebfc1349dd22791e883ec4d9dc38338c19a718107d8ad9a1e768f8c133fc437331a92d777898c5ca0f7d652509172b5a5cb082165c60032e78d18756b733fa820fb987d3830b5ee4010fb581177770fb736f5f8002f80a3050b78a1776d833b2e614d9a93cbf827f41f316295ed4e19827c348615bc3e274d0cbd5d21fb4aec77c0e85241242438eeca7be90437a713a4279ca97ce0426f0c22d70410450b0800abc88430f7562b53898ea901c0e6d3b0c0d7973788994bea173a0f966a973d8488d1bda98a12f8cc7ebe7e55eb4a1c98b9c98bb5286e8d00b365895691ef125d19e650de234e40fa99d2a075eaca147d13535344a08b1c2176ae8b22297a5f893c36f9f863ea7896cc8c1a3ba945b7881867ee432cfdc9349709033b4aded69514d6386d65ac6a28547e51b7e199ad78a551136a3e4284e863e7bce96d02ea2eb2e62e1c518fa2817b55f73a7f5a75e88a19dd4be1aa2a1b355270ccd47868e34af9ac859c0d045b9e8554b1633ce78f1852ee25f94d98cfdf1910c2fbcd087b0cc295e332cc478179a8e9faca77371a14dc957e9595115757bb185a69259c40d31656121165c59185e68a10f2172c4c73f29436fb2d054af874879debcb863a19df81e3bac34889fc9021480062faed07af889112722ff94d6821756e8f244c8679e8fafa00b50005fc1b3043af00116bca8429f2148f29e7330155a8b1f29fd09c1ad2322d84a410b50e0801753e8738a7e72eea1d12662c0bc904253ed2177c8a2d221df1751782b8b8956c6b2922dc0023496f0020a7e9271949b65377c022b9952e47811fa9e13f4cf9de2af8ef77d5a1ebc6802be608215005f2ce1f830e3f43cae793a25507933e38821fa7a4d2a2dbc48429b3d8e82684ad6d2233ae00512bad0d981a5a08d2258ce119a6e146172b538eeef18a1711cf2f195e5bcca7c115a1f0dadd25973c7f989d0e5bc12b9722c3a67cc10deea4c0dddc9170d085e08a1d1fc9124664f49245e82d0868ca715733c53f70910ba4c21c78c611a98c47fd04c726fe0193de2317cd069d4369f5207929af7a08f7290de604336fd79d05f8776f09508e7baeea0f73469ac66ee3184a983d6e7311c49e19fb9e6a0e9d8a92da2486fc6210efa5021c38b9af2556adc0d5a738b16b2725ed8a003c72315e98b412da95c14c10819a52442af1ba2ccc2f2211c1742e83aefa328baf2f0f4c7175810b808021740e8818410ea16278ba9fc833e68789037637be3f73e68b2667b7fbc637c0eed4163595d3e233c681de67e716439e939b4831e7fe6a00e72c22a223ae8fc1f4f893448a95225077db0ca9c1ce928e13a3868524a68387e6a2125fd067d230ba29171c7850d9a8cbba1697a9c4e886ad144b8c5f58b9c3b3492166da485a0ab39c54cd12cba6e20939232beca0c94459b9127c4a7e610b632b168633ee34fcfb08a8c8245df5a5182e536ee4ef3154d683f4c111df67e5c5cd1f9b8f86eca1eabddb5a2cb31b89ca6ee99d02e2b3a0f25b3d1718239ec57d189548b449bae8a1e9679bce66b53d1c5ddd988ba1df11aa86853caf27992cf3288e6299a38552ab37153347da1f35ef1bf3b4ea5681c8be5e4a9d318849e14ed9f79eaec3b19453b2a151df349249f90287a601172c3bf4a61cc118af6ad3b9c88ea4fe81c287af0913c65e74ff4d6efb9215ad60b21c4138de5ad488f90184be39de85c3d780821c4c5fc83134d499f74cea94d34a79f3dc710323b21d5441f763c895af846c828136d8cf951e6e82f267af3209372eb76896682e78b18af32498a30113725a9124d0e1d52ca30a41c9d90124d4c07176f129a442b9e4386cb8c24dabf6ce12375ccc92d25123db858e19366e7490c89369ae6e33857b54e9a47f4a92791376e46083f3ba20f91fb1abb1d3fb478235a0f79232b55cef6d230a28b1b91fb4c5652fc2ca22f09df71a2c4aca93145f41ede1523e88598e825a279cb7810c91be5184388e8b1c55ebd1caa734bf810edfe684e5e0c165763883e63eff75cd179bc55219a8d216b8964fd0cdd428856c463acca9c83bf3c883644f335478e1dd59420da4a19458d31ab6b2507a2d19d9e8cf462c7cf2840741e9a514453428e2effa10b33b1cedd28dd38e5873efa74849042b333c7fad0a6141aaf328aefc8333e7439775ae71e6fc912df43a37f792dbe2447dad4439bb14c74c61062ddc379e82c344caad141d47f141e9a88d943f4412aed68dda1edd37c49228fa79c658736a8c79aa68798fa31ead0475506e153e65392930e9d690a3d0ef23887f65f3f6c989ceb930e39349b1ae68168461cda8fe3197a3e929cf387439b62342ffeae4a0abfa1cb8e672eb6916ecad80d6d87aa6530f19ce42c6de8e364fe95e4316ff09e0dfd7f0817f2641c1ebcb78676ae420ee929357732d4d068b5c79332c7395c340d5dfc0e9e19f31c87dcd1d088bb9ab4464aeef93f433f9b3d787ef64dc7a319fa30bd9347d72f388696a1f398eb31c1a4bf572243ebe162d2f7bf4acbf818da8c8d34c54b2962682be49c817bbecec647189a4995b353bc08863e63c43055cdf3cfc92f3461fd73d44c122fb4213b2efa4746179ad020f89c7c0e49c2c9857eaefa5d2cc30a916c0b6dc8127363a94c0b4d68cb94a311fe17bc2c746e162242555bea14c1425f1edb8357675ca1ff90a81e72f058a1d51f4d92be39438b8f2af4214163caeaa7a77143852e83dc22392bb7418a4fa1cbaf99e69d53293431c3ee90518617ad5914daf5fc29a3cb50e8b378aba49b674e8a3ca19fc87a1ad95d62734c045b51188013fac89afe3197e590f135a1c771529e09ed37cc0ea7716c90d45f42bb9633eae988081e2e25f430e67778174b999493843ec376e933d94cf23148e8a57bd7a16c6cee851ca1ed8c3316654dff1d67842e3fca9974ce1f895c8ad0eb83dca01c7e86f87922341be61f0407b137b61b4217de11fa326728a6a5109af9141556821c843ea6486cf02c19f9fb40e8d2d7d73b6bf4079d37feb238417a32321f346995fba83d85ce690fda14396c748ca69c7196078da869dc080f3c553877d055598e5fa17374d0756f740c2679eea7680ebacce42fd59fb91ce6e0a0f58e8faf5937860cf21b9c217f4c5f42ca006cd057bc6c8ea1cab568559286a5ec2d2d5ab3c69132aafcf27c66d1c69f1491837ab604872cdad78f8d93cf63a3a4c6a2374fb9d9dd8fe2270e8bd6424a578821c4f490f28ade4198f611cb0ed263aee8223a8c39e60f392d525ad1768a0ccc333caed0162b9ae8d37e155d6390f4fa53aae8a7753d64734e45df30ce231deb64c1345474593de6df48de50937b8ac632be1442f08a299a721c191e4b8666922f45d7317b8a1c723b7f3b48d17c66cafbee1933166f149d03cf9a51589c69df44d1a514bf1b36cd628c53283a8dea2037bf7a68c780a2adfc1f2fb6e4299df8273acbfa8f634e33debe3cd1e5c9a83e97fb3f1aa7134dfe8c49bf62f88fcbe144ef48e6f3a7946ea29d8c33248bf9728c9b69a28fce2886661c3f99954cf49aa3668aeef9b935c5441b216c7a78b72ed14567e7c8d2184b745126c7a41d231c6f5a897e5b525c6d901be3a69468ce73d4af709e1bab3389262e58079d183733d624d1669e6821bc5fb8ea8c48f4e292819af90312cdab6793890dfd63668f68e22a672696c6114dfecc949672a6118deb65c661ed9ff960443b9617394a5ef5fd16d15be4fee5dca422badc334dd21f5295e24474dd2186bc55952d328c88d6c1e68c2f557b88a6b3f1bec6f74a8d7743b47a3a1fa288669c520bd1564829e479fca8c32d42b451331cff55f79b9c8368f2a8e3ceef7b29d11244b3f94942786604a28d77bc61da16cf2b02883e88be23891c4e8305ffd0bcc6caace3fcd05fc6e76b6d621ffad0ec31c8c71cafccc387364bffc78410c2e61cf7d0a9c72491f35e0fbdfc9a68e8966439b379687db24f4b7ac620d1c143334935f5c3f4f0bac13b349f0e274b95cb6a19a9b583e8dcc828a2c9269961d0f7d2f8a312ba4844bf7a1a5d519e1e2588e8bf7be63c8e26f09202088c6f401787e8253354c8493454340e115d18a293d89d1a49a253632a449be9c0e3bd2382e882104db008dd92f906d1ae07f91e6f1452f376852e04d1bf8e6af7484eb75a02d16f8a9443638a03a2919d5839aa577752c22efed085698f255a9a1fba904c26488e29c42efad09f465407f91c3eb41e3eb9996ede98dadb432b53e153f69e8a6b165aa10b3df463e6fee9b06284ed4330828382149497441779e8b26bac637fd4090a07a826d0051efa2099a3abe2458e29b9ebe20e7df0d78e513374286fefbab0439bf95bc9933c9c0ae295a18b3a349f752fe722c70d5de9431774e8439e6f92289752cc1b1fba98431772c83b25a2b1498684b46c39e8220e5d9c6b309e277a8aed403c7401875e1b9f38ec8bd16bf10e5dbca18fa199f253fe324b961dba7043933be42f4971e81f5355872edad088ee6c65cee7fe1d3634153de4e5d421ccc235f419bbc2f3b7e5154b0d6d8629e48c3957529c4e1a9a559f0a792966cce5a3a15191768df8bdd69b33f4287ca3ecb9b01eb3c40c3d0c9eb3562ccd19f932b4b964757e839856cc20438f3ba520d92e92a2c3636836acab72c21543fba922bac38e86a1f3f81b3a316e2e130543531ab63a9ee70b9d749aa954c86fd1f742f31353d685364455ca3155d7e4422e349125632765b685a6417955f6bff05995167a6c21f10ebac8429fc26590bc8dc475dd3974818563c89e41e6f7d5eae20a8da68877a02947c81d267461854eaea2e2837c56a199fc3719dddeb6dea8d085f4d68c634ac9c263a6d0e7376818246643e78e14daac11953334f158311285b62b9e9883568bf71c0a6d0e4155e292e3096df2eff9b8972ee16427b459b55276c7acae689bd086945f3ea5993ece6b174ce8416358799aa34b686582c34f35ab0b2518b12693a29a23098de99f9585f3ee6c31116c0dba4082e2a79bded3a39b2f47b02abbe58a1961cd95db3a3a772749045b2e60c107ce0b5d14a1ddec31f31c4cf0065d10a1f3938c2956c434e86208bd69f438aa3cb99d1c42e83c3b861216d5372a82411741e83f6eea74f2a80c930584fe533408ae979e3246bbf841ab250d27aaacfcc4e583d624a70665d98c41173d38fafd522f28822d4642173ca05386262167e59cfc10bad841133363a554897c31870005e8a03d5fb7941c37fce3a4052c48010544d082116c80055de4a0ddf82e9d8267cd9b5d1c343bb3adf11fc75c3e3728f47abebb1fa60b1bf4fb41437825c91ed36bd145ea18d23be7a4a132e582167d3689251e634e2eebcca2ad081f77ab8286fd03de0213bcc9222e64d15e1691e4e9172ebab1a802c0a213f1d9941e62d24979045cbca275d1ce352bab50b941b87045a3123f06cd8fa1638b6945ebadc182668ffe215758d1794a67c879b3abe85b538c41443f935e45156ddede7c31261c64bca7a217cd9d62563f50d1e7f8eab03de34adef129fa141737f4c3d41da4550d70618a26eb666456a570518ade43498c8f3a04ebfce18214ed75f45845cf113aca32f015acc0f3045c8c02b9108572110ae4021455005d2e3ea1838eb953e26211acf9e22ab8800cfe018c79092e3cd1673031cfcb8430a26e27fa94fead21eae8b85938d1540e96926e996748392e3691052e3461f0bc992506cfb14cb4bf7ee5a9719a37f660a287f1c28154e8ec70fc257a141ac7dc2198264f164b342986389eba65ab3cbb12e8c00748c04525dadc11729c980daf5d4a34152ac557999cc93c9f44fb0e35558a19368e6848a24bcb1d427b78fc9647a2cf0a8f53d0df98543124daeb97acfe1f7dc4db171a73e629b970441f5cd671e675e32c9a3f70d1884e5e827a8a8db3354e6144bf9ede61b52c67941f8b687bfd3464dd8ad3f3ac3e507b7ce04211bd5ed239c97f94f3337c082e12d156fbb57778e8558f83820b4474197d4adfcddb53ae021400eb0345c6c521bad23c21b5c7ac21e7e4097c054e0114a460043318c10946d002154ca00005f88026b830448f45726810553f2ea60ad17b72dca95274f7c00521ba96bc10dd3b6507d1658f7a1252bb68c31441b42fc1439e73e412b90d4497993d21c6c999717280684df79206c759c662ca005cfca1b3f889ac21ca51dc1c3ff4d79d25f127c44989f4a155911c99cea69127ce87ae1cba5476871d55dc3d742165ece39ef3d812d543b3218a87df18eef2e6a18b7ed987fd7933b2e2020f5df46f13a914d7b2f959e0e20e9de338f963e6d64ad5d8a11f4d294ef020a23daa15b8a843fb302a72636673e0820e5d85649798d9433c0f99c0049e8005e8818b39f432bf39538a5bca8eda19b89043e31a3e7314c9cd0a713f70118726c5acbadf6072c3b94ce0020eadfe064d7ee91733242c8881b18b03176f683a857666947fcb3482810214e00a5cb8a16f07f9e7416b65d49dae818b36b4924dfcba5747aa418c0d5cb0a199a0297214efdc4a4faea1c9151e579cc851432f997b19a63a5a5609388071c069818b3434526ee618580e9fa25482920208a0a1b914eba8baf16124242c707186b63566338adf0ced6492b5d0b82a031765e871e8460e894ab9d08e810b32f438568eba951653cef931b49631f665cf8c2524590c6d909093571975a36086a18d934a3fc3ac21b5886068542c87b0a41a527fbed05e4a9ec9b18c0b2f14a707b8e842ff2f1f3f3357e8f20e17aa00cac516aa00a7855e92846472b971c8052eb2d09b24cf39a4a2c45ccc0803175868c37fa86cacec3ef15ca18bf1dc2a936c6ed9d80aad8ca7783c1bad42dff02acf635589b21f15fa0f2fff383b0c3ac19a421f297986ac5f7444090326052ea4d06f87caef74ced5fd89429743f9887b83a1d085acf0a70e33e74b9527b43166235c8e2924f775425f19840d1e37725d73885c34a1c9792f348639c6085c30a1d3d41a2ac4867e86e2127a1c6364678811442e94d079c528a1fde32209edf9a348d11ba3be8a20a1d129cd31db272332e3089dac7cbc181afec57d10810b23f4088891a8f4ee4402b240241088c3a160300c068db31fb31408001824268b0663a12007b4c10f1480033e1a1a362628162a20141414161014160c088442c1701808068302826020100a0983c4f02858f8006e46c45e7c8595611e80f12b06c584b131749360fa200db4e50ef80967c1e9f17799a7984f3f49c546f61ccd8d0b435a58cb82c69a6e226252af826c9fc08905a2303efa85c4ee29fbc680b72b10b5077e5192d40c8452aab118ce40b481a02ed5d331c293563d67aeca8d920c09068da55142f5cedac8a5eab088cb48507ea827c7e6544a9bea023c7f892a04cce976608d588fd98724796131c5e171dee344e53e0dde0ca4660489457b6b9d67bada961f2b910d0211bd03159f02df3fd9dcaf5a614b0f0def0ecd01a810407766e2b218b6bc65c8071001e1d640c3c93c5d609d5fbe97bb7851eeacfb74ca570512c03f65f07ec9177e1bdde66c087f62eba911226e20240dad162ef765304ebae279aea8eb1597d0d72475814aec822f57012ca443f8704d01b9eabcf5da6580e69287c18bf139725b84310e6dcb6b35b697129850d2827d548722f904e880d9854f45c12887c61007c6d40417383e043efa36fad4363823183d4a8787961e5bb5d5acd0db3eb3b46989e366c6a8b87ecc8aa2d6839a353554394d4aaed28c0a05ca88e3c562205a5a412c33771b5da0680559b74a07f3d7eb8d5ae1246cf3f9f98fb828981b5a111ce9873bca6c941143851d9e384f0795a7aeea2e825b457539f9210b8ae1dbfd134a0b3f41bbd017fcaa886e8a3e2e8cf2a7cf05e478630db7cb184219a4ea23df306a11c4c94d0fc0397f32223a63abbdeb2e81d0a966a78319653ba2d4e7349b03e51c441c4cadd49302124624d014cccd0825835b013b70b0026e378820a577c6f8c51242447ad1a19573feb953852d44845d6905982519d8205d6b7fbdcdb96bcca4422826010cf5fea2551169e760086feaeb07f24f30fed84afe4058041f14c0518c6b2b1b34c8cb8ad2502184f63f03c6976f79dbf811144e47eeac102866348356b5a0c431583b7a531c814943d56aa700f2ad1dde01b67cd6491284954fdccb8b8eca7f92360c14b82a1c3ca0d94a273fc6868587d1ca3723183c3313e1cf0d30f58fabf38e4c034de97aaf652231669fa760506d4800e9a5376371153ed9e10f6ced99a9d2c7a4762df8ff25d8ae6c31aedd06bdb6293f41d802ab0128efcea22354e58a8802e11a54fc6fea5fb95ea646b6b629b0879a6bf7d340249e480c404edb0d238607c69b217de9f87ca3ceb259bfbdbf7cfe3fb8e4c1ab85f7245387eb1eccf1e7e0953ab7ebd71638b69b6a3e0dbf9d90b1351d60c809f4b522d7835befb5139e04c7a3e435501c7762f4c52c523a3151e0b8d3fb3974b4cd35413d60be69fcd1b64c351e6ff3007750c8208e88db45541272825f561cd0747d462f5338b3eff447ae0a2d3c0c0907c734fe7d2c6cbffb7b128efdd72ea729897d3c832c5901922671d77a90c2de4e11e01dc1395e4aac381bde312b5d6dcb99c460765da04b4aa6889f7665c16277e7e78690ee08e3e0e40bc837ba96403863e1dee0cf7ca899ae9cb77e871e078d700ed8e4ae2ad047ee22c01941448cf370e640568164ac81e81af1fd8f07d6014831ad9d5e6637a1b0765fcb1406e25244e5b8d48824a7b86aa57e49c5d75469cf6092e320398e2b4738e6a03cb6dc3203559b864defcc146e43d00785459435200d3d8000170405cb8fb9d43ed59a48370c15436880b61f83c576929ec4f338b84ddc524c040070c52b13b9d24192714800932d1ac08de6087af871ab6f0486b25b6cf2e3b5c9041ef096f7cdf15fd5c5b185088cf792b5ce9eefbcedc1244008d1c534b030c88762998d4c4cc1b5b886820a67cefc0baed7b25afc37154cdcc90dd0cb0b3025ac737543fc1048cde4b9a20fad0a29402ed31fd40d026a04f318813e72324a7d9324d325c8a8455adb2f90fbe67b43dff0029cab32162b487e257e555ca4bc683ed455ef1aa08aabe82dd67193cbc4ae4894e96676120b68139ce6718a5b167036ed23c90d89733124754ac8b95c2d90af0c881a83bcc8c2e0ce70e4f8d338f769a19b7dbb93850daf236c02bf1189b9f0ae066a91ddd136006121a50f2313284d51a5607ebdec64d00a8cae210cb3c21eaf40f45ad26caf20844e0990a96db5128df48744a74e74cdaaa862c516e303b6b343048046adaa1f88e5ae039ade3660302eaabb0296d394fbebf0fa37ef3d3a88b26794b8c75980281887d2e07c75ba6118032b454e3959a4cf9678fdf9b406474296fcccd32e291638a08a5cfa33b2e8a211568ee5fe5c18c1f18c4102110d332ec40eb5df374ab6fa60405e9ef928b00a99898eb42c59db25636ef6568ef209a515ac89392f759b604a5ce7c90a59cdc36f66e5cb3327631b2e52788567f292506a096bea8a58663c312954b19a2b787ec21d5ca533aea07a5b739ca1fb6bf1dda747fa094cc4087a6d32d48244229b966ddacbb0b461d9332e7e4fd6b6aaf770809b6fcf8cff335a1b6e26ef784e5b6b4130b136a10dd5a6c78dcd7066f78a06e78a34f34f6224f2b942030edf760e86ca553cc2d62b54c270bf4cc891487dfc9425d24baa3f28ef130454ea0b9fcbc2646b3ef59f6723ac0cb69f47a5ed7b3d1d3a8f3be2787f9364517616e372daec1464612dfc2c468c06526edb747018999461916d09acb9d2b5d20b6bbe612f5203887f49e9b1c978fc04beb8192df45e2076a91298f8d438a55ed5b52a31862d13ecf9419fd68c048b3577285c800747b1fb70f1a4b3ccbb6225560e17e9d9aa2b9b25a6a0bf46a9a4c64d502bd2ef24319d53b7283b8055eae090db8d93f84bfd8d1f1d5f3a81391f709d9610576e0b2ad167ab3ed0c49e942ab4a2ab61d95365961096d681c37a8255114a09e29c0f24568f56ec23863dc4f03a40a130289142c5416844f47149b9501fd438d485420f149092d252209e7250184521290e2a134a8382a058280c14024aec28609c85d31c304a07ec05aade4c144d6a0a513a62300c0f0315a13a52d43df1dcc5090eea80da50bb58940e711e91ba42e54101fd2a8a8337afc37182ba16c5fcce040a88d97f361a0fd5370cc8f572a8f1a8beebd3f3329009cc2cd4f0a280867c13b6c5102584fb3064a98994f5a8578a06c540c1a1b8a3a2524828118a0ee53c0a958a42598d724ae150001411ca874a430150361407148582a1aa505e2806058782298af9dd17af3828a86685b2d8d6eb8b26a1c489c2a42828209407558262a11c280c280c454005a1fc50b887ea63b80f1c2ba8060dd507323deb0354ee45017f8626cc71a0741823967e87d2a08a07a5c32171ce504115504016054c044a26e7a0ee3d5f82d538a13250009494a1fafaf57e407250ee88027efd90a79714b004467dd514580a4a040007bf256bed36a816ea0d3541c55008280c0a81224355508408d577c0c5580328461e457bf37a472f28c35196140f4aa4a2ee715cd2a974a80dd4196a110af83090e4543427e0c90144b9c92854e9d561fd02c5a094a04828010a0005433150290f05b49c35585dcfca227e14d036bca7b166a038545a096f01d52cc5fc2e194a04243b8c770215400da17828311400854321a050281a2ad541dd035ed262f05009d4188a975040b6cb0f1417541cf1fde08a1b0a856aa170a814285305d527e260c80dcadca080cc8e6b9d80d28111e9d24f8e2a985262a474408de38048a830d43ca21485f3a6a11d8a84d2a070282c14e450ccef06b6726201e5818a360a1832573e7636503a90959a7a8152a0121b258293255a2605d40935801aa122282e14028541b1a13a28d245f59d6407fc1a8a2184ea4301fe1a1f5402a2fa54bd97fa59150742dd782811d0261b7813ea54d42e8586d24279283a140d450d45a0b28d022e404c4501ac39b48202f24528c41150f158373127187ade58cb2c440c254b8fccefd18be1bb791e301dab06c8364a270bb406c8235cfecab3a43286acff4b0fb08eeb87506e40f210fe30f45d79002e291c91c4000e03a373c4999671ee146b34f7d6cc536bdaa40c21244d5616589295440a150691720d2bd99c1482f74d8d8649569e29f210a70440df052c810ca134d69c1a6c510ee3d0f2cf55bf5af9ced6170351c0c9f49f16224bcd25e548e2827cb247f3a14f95e4f748dae0ac7eb9378422edf8ece4a50152433393d744ea92ec4c2de3dc2904f2ce552843693d011824d32b2b2432c9829564a530057401d7cbb2d6cf692768f599ee84b3de535c6332c09dc85a879a4a514dedee97aef6e95044c4f307bf175d5fb0629b91a92a737f221b44a64ca5fa37529bc9e70edd3c722f6f316c08f81ee95a299ce2b661a1c5e1feefd489765d9aad67dace086d92c5fb5a822a8076118a60a84a486ce89e453c079da505ee5fa047f133243e700c182eb5effa9a308775529ff6b24061e3121eacbfd3522017a610cebf043f0a2f18e7ad60b216490b22a95c480fe55e7d483a68575433a91fc006468d5f0e2cc8fb39706300d961cb4a4a33e479bcc1ecd40f5366b424bd0b70577eba4481d5d4e0157ec9aadda368e82140ffd1b95431a003903f6fe329f7d7c18cadcd17e21ba715e2bf79653005771b68c506c7c393ec438b8cf03244b020db82d22196df1b2d4311e6556b6512d1c0965555dfc8767ac0e97b1fe3ec40fecb31e0a43639160191b98f7da0dae06326df0a16ad4377209aebee2c8f4eb26c3e89731d59af75a5f85a9c82310c10429992f62998b7fd5de8b6ef68f3a6f847cf9945bd97ec285d5c573e48ad88ca573322e1eb3360f3ccf188e5a290b4f4ad9fe088d49b974ca2358cbec20c2707a9cf55b80a77d61538c90c9c4f8f5668310bbd7bb511974600120a8d2b0f3c975c1f417b8fccf8280f2e39ed117b8752f3691958c1e3210a0c206ed990a6c671bbf665f9229b6727ea76539641ff36789cf131b2fe364119e5c80701761cc2319a7fc461a77bac8d780781236534a374662726e94012cdd3272990ce6bdcdd04c0bcb8313c130d8194d60d445734b6ee1b835fa4c56396604912a4bd771c89f52d854c0fc49f5f33c0d5fc22cd8dc8b7471a75e419e93f025a2e4572e1916ab2e8a45bfa15ab7373536051bb5ae84aa185023186ae8bec41e80bbd565a77742ef4566d1bf2b4d0ee725539d6fa61ae731d4833a23f1ab3d684585ea4631098d3582b50789d1bbaec7cccbfaf06ab5078e8a7d20f293cf3a3622d633fac533c6dde632ba82e6e1fa0c1978a0cc5e8eb3277f566005eb90102968c64acc19ad2015f2dc00050b3c01429510a8b8f1d80172b1208f9f2a4d6d41b61ed293b0db5968dfb182b2e13f98de895cf90d080ca3a7a9a3803d9e1205f048811885cef0ecdf94ef678e22cc55b9e695dbe3a2261601e706ca9753dba1eccdecdf24d34256732c9928a48cd550c0e17fa38446bd6462f31631c3a2e712fa386dcd55a5607fd2252e0a29e0be055d3bb3a45dbcd961a5e04c0dddb9bc466437fa119095db04f40ab51391b691e29ebb13a437e8e9d7fb57b31ac6a8dde832384f56dbfa187f53a2aafd679652acbad0d9017b90cf4cf381f46068b17c80ad87b1bdeb4b254acda337f4fa5bbea2e27df8abc2f1479fdbf255773b3d6c78a6426b39b38f87c020f9f7da074ca6646023891a7327755400b37db0e463c432253536572072964babc600205bee47f5d906114ab642b3c2411719381a8a5f9f97fae5ca4a13e8958457400d495292b0906a33f390a7ecc3d574a339842c1d7d84485fb8bf2d22302300b4b8d8c755143928266878f94de5a3708370adb69a4d97e660c20d2bba533a01ce963e1178761802905e0f502592f380999fc22e096b5def281653d22c4c9b44f04984fc385793dde3f36915a9b526e2ea1d506371d61c29c1e01ed146b1b15fbb248a6c44be240d0cce1115327ece8cee03b4524f1cf62aa9d95341c817c1568ba2f7a82100619d633a82909543899045b18600d9285811be68f33fcf7e4552a04c198a85eb145e361d99470a56936e2bb96539f7c0be614b6a81c7d861af0a6802283a1fecf8e57db5d304ec151b595d95c3da38d48cdb29ae1f944ddab4a6d9880bbd72b1842d56ac26bfbb29e6ff881e1ed0c9e6e61304dcf84306b76fbd291aac7ed99b64d6d9c430e4b67d8f5e52b3f253d8e9eac57db23ccbd83471785c494f5980d2c094aff481d4d909735204ab8090836c184fc2178ccf5704812d38c18521219924c1691efb3765ade214759d11cc680c2b84f2b9603b657e4d3d84e715ffb982b518156210ed79f78b6e4d166110d66ac0a32c1eaa6aaf365c93ae103b0f465d6499084f86a0d0b86ea5080ff9b2249203259af40d4a0cf84559252ee0251123393961cab5269904d88adbe18da72bb050e9d8ae0815933ced2ae52fe79b6b0659ebc478a945ee20d6ddf0a13a4343939b36b54366e281501354c40778b203b5cebd340d451cd1cb7032de304ce290254e0b7595092cb95a0bab292825e14a59c48352cb5679aa4b07651951dda606efb851afd483e5352d0129e40f5c101d111d58e9a3a647afd10d54651d0f79b0dc89af5133fedb1e2ee499b1407c709102cf8c7fcdfe3eac94ea44c15b991ef90ff1985857d4482b6b890ae64f92b7002b8e55bbb3708e880c4cc968ae54bd1629e0fe3b181143af0276637f1b2f51f11e07a856872b26b4994e233728fd0c896ba884c6da979b04517161185df790baf7f857ded591887eb64cf1065342689ac84d6d40573b8eec7e9b824a53bbde1492216cbadc1db42ff88d4c62a58e758534321bb12e7402237c6b54d3669b305e320471788c864f3df66c8155fe44942b40701140edb4209d3a2e024d4b7f364e024c5edf5ad7d49e809889fd1ead6da7f06c3180dfd6c5d40379d00c4e7653d47a8f1b3998788ea1b9e0b1124cc2db4d0350b070d616c1143ca9e9f965c5c60824c277b5afa243a068b226a1b6131af8c39160abd76c0dda2ab18699609ba487d357c13750631c73027acd5030d7139496e2dd18834da59a5aafac6e09b1af23679132152d373af232f8628e88fda6df5e6348895d851ca5a4bcebd417d184a54e78f38f04749bae54b475943891f696f5da7b59eb250a895bb4ad9992a28597e782215abac9bbcddd0bc99eb2f208669915af4ecb2422ab4719dee7a7bd594c7cd99e7f79d13371ae538529e796f7a74b97528dfdccbe3fe724a7ac52868f3e8f4e922182c938dab8a161aa3e16e3c7eec8a4b413a738ec8a0e6f02d65fef0c3c408e846a3480afd359e2337cd6f938d42d5ef55efa6f9c9a2f575327484b4fa2b8ada9753cf94010143135af37fa3ad398d962ca3a5e7f815c2ae2e5b660945102d53018ec62f03b75e944e13d7feb6466aab40e4ecb4e0d82d35297117bad252ed244fc9499eccab16e05ddaf13df64fb69d87db78e18a85879a116f5346e1d45990d16556d1c356bce4fe2c6fecf090d7fd5e14f7827ff09b75949c596492ca829257253ed312f07add55a7e27b8687a2040a91a86f6e78fc16b9e1da8be2208c0e9bb1c332fc6b21d9d029081ca3081461c6ad1f2998d27152e52540cf2162aa045d4fd4e30fda3005822a5e230597c7b621557dced1438d12643b7a59e7c45574b9cf3e8db7fa93945bbd477369f8020cf67e060055503bc8aae6dc719b89f7b868a4dfd3877793b7c530d814f41d89a8c8ee5d809dac870088564c5f581bb9a50a0aba88931135364bdc67ac0900600e35a4e99e69091d6041f40aadd669b5b07fc95d6d2428b4cbd7c50adf2a398d1f344e79f1cb39f79a3df5bae0a15a7167278f832123894b7a34289ddce8abe8c1ef23107af300601b2cd707fcff644c357d1eccb000af628ed655bbd43b1549fd01128a796c7f4a8aef258d252dc6134fa53df5e278d8c8098d5d859ee5ce9c4a677990790f61753c51805e35687a6f6e2ed6a4d34541f31aa5c4e67e14289a909e236fb4099efaea8a7ef128d883ca0b5a054ded235f429be3af69d3b7a3209ced2da1673f286bbeb0e69b6b1e151daa0dd583f2504828060abe83cab61995351516b0ec81baa1c445545fb024ab19ca4951a84653f80f0b5409f5873a50582828140505a1d28da87bb0fdacac40bd500a8abf45f5ddf01cc3018ac9a3744c43deea1d4a41a92fa87bf0a7060cf50b6541812a4a8711312d4841f150a308459bcf23d720a04e18b5499d5009a1fa78cc07fd375407a0802f98b26947d497e626c183357fa173053b83ba97b408110a350215426d040514f0a2eca427e96979a243c9507880d28106ffddda501ca8b9400155dc04f2a540dd4bd97d1a00144414ad14a4a8f54686074d38a81f5407b58102a11c282e145643a12a52b25e08d4f8a222a90705840a41a9a02814ca4201a9d569c182e2a0568a7b1f41cec6004a93229bf31965598f81ef1cea83929f02968f45e98a94d49850b4a530aa4817a8035584aaa1ac1e14f0edfde77e146d0b0f440e050a8028d1a7bc14e2282081761a0618281db233a47871eddb8262a02618d5d72f30e5a85054358a762e150dd4a14e501dea008529aa2fa7eb6b684145739408a036b0ee02f581b2a14828351406554309a03c28020a3614b05c2f7ae45204a09d68771ee3be3e7ba2a92423373db0070724083178720252ac0cc502fee144760c3750b2a4d001c33f3f3f3f3f3f3f3f2ff0bc6dc1362b0a5bd85226f2ea3665f3de94524a29a554068005462384b4c602c20680c3280007029f0c1e0c5e0c3974e0a8c11683183f2846f70c426a76184483069723860f8a7153b5c3439cae57174870e0c8f1014b70e0c80106aa22460f0ce36d5a638550cd6d0c1e7c3e27bb89f661a2bb48fc606237e6e4a1f2c0872e968f9b73874f596235b8389ff0918ba2499b11b22796e8b33f705192f74caaf012a14f076f715529ef51da47a794f8b04561999aa155a5596b99bd95f6673a96d0ee79c4472d4a92a6eb2f61539592c4e5f0418be2e75bf334a531c8963e66518eda316f50f2c6b8199445e98495e65d9b143226f98845416b50ab175edfb7a28e0f5894367827f956a4f9092d31f0f18af28fbf89cf3933b5665d513eb165af94f2d8a283ad286f28b983e7efd71bf5072bca277d492d257eae38ed6315e58df3796327ee4c92f95085d99af655f2ba23966177d2a4f4eb6bb055200408e02bc1472a4a6772c939b4866c13c30f54946fb53da9cbbcdec6a7284731399f464567fbfb618ae229a173ba7e75ca94e58d8f5214a48930515b9b55922c091fa428273159a9def07e8ca224f4cd97940d8dd1ce87280a6f3d424eecbdaf1285a23c2a359ef897e94deaa02809a7f526e7fe5c9bdbc727ccd9365713d93bb132bdcf356a1e4f4c2ba164c7f0e189626cd32499dc98573646c34727ca3173c97af1fb41a64982011b94a3c207278a1d3dff3b9bd69b5bb5f0b189620cdff145dcc5c91e6aa2fc9e6c935013a4783ef9c844617468cf13748e3c810f4c143396f6d0d6fbb693f9b84441ebc44b8dede77b630c3e2c51d09afe5a4b64eae6eba312a5df24df93b2519e8f8cec482fb09111005ce18312c51651da57a1793eb993285f8e94bf3549a298c93fc713dc4795d47f44a224b498feca093f2051feb8a369b4c66ece691d7c3ca214324b8969339c245e868665e2c311c5f8d8e8f1eb3332628a8f4694426a9824e660c2433482f0c188e2bae9df9dd12a4ffbe06311a5f1ff924ba78512ccc187224a3b6a2c6bfce492e3a9c1e685df0f3e12516afd64b2acfb40c49961937aa7f410a5cd135b74d64726e5a8147c18a2982493dd39c9a28f4214f4fe9fa8ca9f244611214a6a94ccb5657d0ca260f1a9df6265b5b3461025c9c3a70e9bc31f8128e9763a3549d43cf80044f1ba939af2e0c1ab3a1f7f2809c26487dedc96e7fa871f1236e98b6509267d28e66a499126e7689d4af8503031b5f43e7b7787d31ecaf9e22628fb204d68480f25f1bbe745bc6ecde7e4a134fa6173c8efa84d62f0507edd6b8fd9aec4ca983b944c4dce76e9de6dafb143398ecebae32a2a37df752867f79b4c22371d0a32a47649a194483df11c8a2f4aebc92d5ad55b9543e935549e096fc6a1a044b76d7ad072ae261cca5df5267f43399f1862abf7c483921b8ae19ae4bed126fabc6d28a8124767c7ae8c3bb221133f61a619aea1bce5a6dae39f546e721f6a28c63229a32655d3508c7255a3624dce248428f84043491233a566d392e6d3c9198a73fae7a727d5c58acc5092df74cd68f34fea97a124aa8611136afe204339b9fa8a38e184c9723fc6e08cd8b9cbce76698ba72bf92697bceb7c88a1a04555345f357b898f3014c3263d4268b715a5c2447c80a1604289a34207f5e30ba55332c449d9f1041d99051f5e287ccd7ba6ea99a04914c747174ae3e22736a76912c6b4c907174a72eecc310962ab57fbc716ca65720c7ba208b550fed8fc68aaa6ac64ed230b1f1f58a081808f2b2ce0c30af551051cc917366850610a0af89042023ea240c306181110f980828c8f27847c3881c68c8f26d098f1c184057c2c41093a443e92f0858de4e30309313e8ea0c3e3c308313e8a20800f2214e0630803f81042003e8290800f2010e0e307340af0e183f259c853f25c8743c71738de6cefa30725131f111ea3721a7cf0a0989a35e38ecc5ea3361ebb2809257f92e4777451dc121e946ca1af81472e8a69374b8811a573aa8c8b929f204caaf0a8c72d4a623dbf624627e143ec618be229b137d3ce8ec9985a94a4d02254fbcc4394092dca37a746f39ba459945634a86831a92c4adfee73aa34e6cc61138b9292d3eb7d1ecf9a1f58144e50aa4fac737890fd15e5ce38a2b4957e86125724bbae7bf3d76e45c1daa44ee277cd8ae269d3a0f155dffde72a4abb73324b9f3acb58a9a22464ee4937a95349532a0aef71e2e6aabf5d4e5051fa0c27d4670db92de95394547ecf5849f32022539453cfcf58975b8a92f67c3a75f690a2f09ed386e739259faaa328a689d19c46aea7b94e14e5fcd4a737b6e9f0084579a38ab0f3a0620f50144bac95b1f43f71c79dd331e7853c519031797a69f79d2875dd99e58cdc8dc9e144726e26f1b4ef9b285b6dba65fe3f6192ae8972988d87926da545da9928668ca80df7f149f38b89b2e8a876b63e2f51b2912597922126d57e9628da9bc6ecbb17911bab443906599ec7a42729cecde141897257084dd25a096ad94fa2ecd935b7e794cdbf614994736ecb3d99838a6a3712454fcab6752ddfd30b89e2c9f1329c2c329bab3ea218cf43499ba7744479c64f2c5525265964641a3b3c8080116c0001322861ecb071c3cc40066964240c93238781814723caed66a3cc54073f25c38882f092e4e826bd069b17e946ba51bc483752075240c3782ca26c6a62d66479be1e8a608f44a80722cee310c5563749be13356b67507a18a2709e41b489a1bef5b5367814a25c77e3f2334abccbb5081e29692f2d1c44c1a4cb94498e5d102531ef093d9723eb9607a2bc27e3565b2bdf641210e568f7fb72728cf79ffb434193d4493ed93a3f14cfee479baa4d95bf0f2579bdc3247dbaaee4e6c187a299ea49659d79b44f121a683a72a4c48b3f015b261e7b288712cda4915dbddcb741e0a1875b81471e4a729de8ed9ff66e27091e501d4553c8f0d89dc71d4a1f93a053c8a81d4b7e6bb07d916aeca081be30ed61871c498f3a1c1d4a72c796aa5fa6065bd9123ce6502a71cd3e6164b4c94935d8e450d40f6ba3ba9471380f3894d4e724ea26cb4cdfeaf18672bcdde68d1d7c33a46e285d5fbd670fa623cec481471b0a6ef28853d2d4c88662bcbdd4d56af924de494772c0630d45afedf5202dacc186f8031e6a289eee7c5692896ab09d471aca7e9fe6c2c4d618b6e12cf040838d9c0b3cce503ecf72ea9b83699c2032818719ca23f4273967b030c99494c1830c4553db64e2c70ba5a311468e1d57028f31946327dd9fb226958a654003c70e31943fdac6f7936fd27e57830da51a8147188a169faead73d2ce7c028652dd67d3b1a3ff8592e7d5f7c9b16dc2425e78946c25d639c977a1e8a22b93fcb9f927e9b950bc12f4a8c8eeb750fc2e8d53b276fa6c1a2d144389257fdcace5b6ce4269be447a149bb150f252999aafdc2b94cc2fdc24bd415e6c6b85727af7369da327cb5fab501af94ca1268918d3930a0599639acd9a7b5227e1144ab922f49b8ebe6b31a18187148a7163126e37a75f6f1885627293cee38d8e85060a2599613efe868790b5793ca1a8e97c336e07f1239a8713caebdfb9b3c4a38cb63c9a5038a183a7cc1831692c087830a1a8d146db7b12ed37c9f1584279b4cc49d3e1342859bbc14309c52f53d731271dc489198f24e08807128a3e5f92b69e93a772aec1a6630637c0c831031d6927f0384249b63e693d36f3069367bd2f3c8ce03cf0284231c6a877256544c38308c5dff97513738b8ec975c26308259d392dddb7a6613b427808a1246dffee9fb8eb22e1118482d29cff79f5727e42878407108a39c969683eaf175d5a8f1f94b45d4e54bed66692a271100d1d666dcdc30725fd7b676da5d37bf735d89c032323347460606484860e2f12101e3d286e1e0d21c2e3e998ba0b544003089800022ee06ff0e041399b8aca3d498ea977ac5de72e0ab71b9e5bbe4388f97451b02a1d3a4a3cc63ab928c8b41a1af4091d64c645b9e4fe31694eb86f32b945316747ffbb098db649b628d90725a679f82449fd602dca26095a671b935c722ad1a298c1c4c6344dcfa238f284b398ed6cb208c9a2b07173728ca11a1727b1289e70a3e3bc29f17444b02898bde6e4e163623253bea2389e4f49f275d576d4150513f98c7766af26795a514a3d1ba5a164585112231a36868ee9f36e56513a17f99bbb2564335745398bd8f0fd18b3a990c15494f3b56f4c327936284145595b664fcacca728864eff6f628a2c394d5190ff50a3d4272da324b314a5ff2034c6cc31294afaca32e357ab4a7e475112ec4d7ba4898a82d1941e4fd2170a4541e9fa189966c24aa3a028e70e272e4cae2a57fd44b9fc9490a74a14cd58f244c994d04ebea14a8a5076a234a2ee4cce1742960972a21cfbf6fe66bfaa3fb7899288cf41ddc7d1d4b3a326742bd97e2af5cb44f1547a7d4ea2f39912c344b14d4ff674ee319420bb44b16c8479e8f91fcf181ba41d33f0a2c60e1b3568a361004b946e44bed73bc8e0a355a2584ace91d125fdae579428471332fbbe841da5348982d796161dfbc4644aa2681bc424a557278ca891288e2a858ae6becd251e40be50fe1c3be72462d76003c38b1c356820b32f6c981a13e0168078a13097a5c524f7185476e400d285f287ce583dbb9a199db901840bc5ab6b4d53e7569fe4806ca1f578f5f0baf46e1db19ecbb87c173fb525102d944451a6397bb24f0d205928c84f8212ce7e4c49b2090b4513253489a9a32b14dc2d74d2192d959963856275a9d3529dee66e32a1464ed548c8ca6e4b01f0815ca9f44e79960029942d994fb7729afda4d5f0ae53d614c129b7d724e2a90282cef3557b76e9a25f3fd3996c56e205028e90eb725d7aae4bc19c8130a267e3815fac49d5056175dfabcd489e1bc269436eff4c9ac291026143bc97ed895d304a600b28482faa78ea5d5a4149303a28492ec59de6a3b8324a19cb5f24fbfbf54f30b8284926dd665d261ffbc24811ca19c4d4bb6d062f2349a18a19ca5afd2aa4cca248d0a528472de9aecf721e420442867d1f462e7268901902194aa9465cc70726aae580805fd0fd2d44bab5b29418250ea3f294f63ac5ebd1b04082559a1565726f64e3cbb00f283e27d105e1f5ec3241182f8a024dbac4ab215dd81f4a098aea7a19f6994977807c283f2e899b61a0d426ee66e1705d1cfe6a6e3ca4aaa580532745172517ebacb45394b4e9dcfc6940519b828af686c9f9e26d5e1b94579c3e726354aeee4bedba294e95edf47666b51d2db1ca3e9abb64c8d1625371193ab7485286316059329c6dbc32e4316e54da5745f3f4531c88845a9454c3ccae6686f2219b0289df4f93a9a12eb9b9b8c5714cd45dfe68d0c5794946d3a490cd1e91964b4a2e49ed4f6c5c6cee9560519ac285cb6c764a29eb03eaea2247f5ae9249c36d3b165a8a2d4d946885ea96c7e2f15c53cbabda24db59598230315a50af78c491a5d1be4bb21c83845e9ca635dab29bb3c4fae0c539404a1fa4be377b94908324a51d43a613b87f90db75f06290a23ffe54ed88a8c393b10648ca268e31f26bafd3aa8900c511473fa1835898cca0845f18350a227eb1cbdfecb0045d9a434f1dc38f113c5b44ee5a7d751836f3c51b8f594677aeededdea4441668893417adf7eb832385112e2acb4c773fd0e4fc6268a55a3dd257a902236265313a573d31ac63d6a09edc944492db5737478fe9c4a30516a4d6af28afe5ca2d4a9046919c44be5c9c8b0445957e39566ff16175d6625ca2f636b62862ed19ecea444594e8635f946ce9c44a973668eea26f1f2e34c4994e73794d8ea2945e73e33122555a74f8e7ac209150d248ac9e2648ee2f6238a952332c8242fc311a572b54eaffa1b517055156d3a9597bc31a2dcefd5277aadd996691125d1d119ff2cb4cf9e88321451dc1639b965ba44144dc88779ef9c46e5c911511819fa4bc98c5b907188f2986ab4bf924b33b788320c5110f396a14d5092a41e23ca284449b2919ef1b22444d932f6ffbf6809d3a24114a49bc99bdc4409a2398228e93c6679aa71198128bd959fff6a79723b6500a2eca5c48ec93e7f7628c9f843e98469bb91f1c8f043496934994d468ba7967d28a9a9b23b53afaecdb80c3e14e4dad589a2a1ccc5a6828c3d9494162d5561aa6963848153197a2895d99be8498c51909187e2ccd789419c6b1e25b604197828071dba57773bc997ff4ec61d4af2893186933b47861dcae6a743c9134d96cca78c3a9474ccd164cf4e22830e2511199d467dd494d1aec126630ec5ff24bdfe733e4ed0caa124671ccf10fa8a437175bcecdc4a666f090ec5ab389d1ff4fb8672dec59d1c5e7b7b536e28e9933732c6bc1a3eb4369494ec34a194c72463ccb1a174379a94a434bf8652754753cd93da4f5743492cb973b8c8d422364a4339c7674ffa3a5a9ed4a2a15892f44dd1d99ca15c72f89e13cacf5483cc503c513c89d7f15f829dca5092ba4c6b12e9b9276cc8500cdff60fbe4193a4248da1d4b59d2fda8aa1b8315d76aecbc350d2f03945ec2c030ca54dd649da2064b68c898c2f14844e25270d6fa9a11e195e2829915de2b376968a25a30b25e5a1a38b8a51ba1e86820c2e946d841077824942999a750ba5ae915a279af426c8d042e142b45fbe9bef4972441959286cde1ee13185300932b0501cf9314d4e733f4c9016645ca1f89ec3956d5677ee8c150a22b398f250929f4972aa5012fa73fcff91e1cad4543074c4690a29ea299457b3acf6f87552af91424949a13e4267eaaf3f0aa5d50d9a2413b47a3e29148af1996a4bfe89257bfe09a537f939bf3e57764f4e28b79f9053e33937ebd78484f73631a1e47effeb7a270923cd25944e7f9c66123ed5c7aa8462c896b4a19e4d4269b764d50d5d8284829df420fc4b76d8701ea1a4eb2f37ea4e2394b59372fb24660ed37d11ca172626939fe029534c8462a8ba87b8bc10b1dd10cada269e5ffa2784c27627993e2e5f69c241289c0ea1744f4e13358e8050f8cee62d9b8492a1df1f14740c9bfb4fda07c5e0a5946062667a5076134dc97691cbe041499bb42fbe1da7aa25bb28f7bece8ae6eeb0a7d74569ff772c4e664f77712e4ac2ee4e858c372e0aefe9e9d1e47e8baf47a76b87756c511a6592895d7ad7a2e059b2694f4a981605a5243b25468ff5ebd12c0eea374c106551527af494e46a23c6a2b05fa5cecd3a8ce71c44589c639f2c32534e315e512cb1c7f3efa6677d4f1262b8a2e07135e9abebf220f318ade818ac28f5fe78fc90711d637c15e59c4e8e41c55655146ddf64cb7b7b12833c1525196192e0b7a98350a2a2985b723e6b87114a7d4e51d01ea6449f0dd79e39539433899fe37bee1cd4a64b5132fdfd277a92cc3c4b8a6252d2aa5a6bcc49cb388a626649ea4df81266ca248ab28c6512c27d3d683c43519264d5566b5e415112133a09a546346f27f944495ef772cfd7149f644f14f443f79820bc189d28d665e8a9501d278a21435f9fa04edff66413c5faf56c26af26dd9b26cabd793366e9ef9ce32813e5cc234a18253c4bd02326caa149a68ed194d2f8da258a9bfbdc8485c6ef972d51ca36ddf8499b54a2243a29ed4a8f23d43c9428f7c85c929ce5bd32bb49943b7318e5a92949944ffa6463f267ba3e51248ade7bb31a350b89f2686811a6a7d442ad8f282619f6fdb64a8e28e993694bace3334ba811a53e25b6d586d737b16684a7a2335d44f1c4a8399850ff1983ac8854ad669d3aad1351ea581e4ce4dc9ccc258828bddde792d496621ca29c77931c4df29f7b098a6188a28e9039369e14a2546922cb5326497eff310851d4e0ffe2b2699289d0200a624ba8ef518fa39ec51084f96fc64498307e06a251e53b621902a25419a388ec127438298df1877de54caa4d3a64c9c7f043152246084bedf39c1a6c17a30fc5d55d6e9859dcb8a5b96bae1f1b133bc80fc7e043c9b39c1893e469be598ab1879250f91e74c9223d1477d3cce68c9f3bedcf4369f6e48d9bb388cb128387c27a9834c983e59cd21dca1fe38970dbd81dbab643d9e663ccd3b3259ab37528c69b0e9364eefbdb930e59c9d4bdff1c0a379ea3f664e5a0598c55ccc8e8b556cd28d1e492d46a3a0ea5b714b9fff81e9a4ee050d2240916269b9244e992371443ef98463329277b891bfc244369b8d69836944a949f1cb3a82741cc865207bf101d54e9f053d750bc92734e77da637b490de5301127eebfa4ff36d350568f9d5f366631d0f08693995bd3a8629ca11cd566abe42b71529c662809cf3339537c31ca50d2294e267dd5c520433974aa884c952777b43bc618ca3b62929c73fe7cda670c31d8488c30149424f24cadcb8ad6135a186280a170a3ed253f4e31bec096cd557ec7eb16c30b4593255bc2eded85185d28e789f7b9fb6a262a1b1961440c2e94cacb3baabe9d85a9636ca118b2a4bee7248ea1855249a624412e565468c79b4631b250de16257bac667c743c31b050d26e9258f7b75ed680185728e6b0994f940e32f54c6258a1984c990cde7756a1acb98492af9f4baa53e3c92006150a42a8dd935b62a6ffa631a6e05f783ee1779248a16067926442d865d694a350aedbd82ac94a4ed50485c229bd7b9d3b339c376ed88801f620c6134ae29c184dc912fd20a7bb410c271483f856d66f4847138ae7ff9a3b870d9e8493098513b4798c2bca1b333049424b28aae6d4efebf969e65a183194500ed2844c326c94a7ca8883184928e9c9c613b4bd55735368831848288a88ae9cde6c92b3c311e308c590f2259fcac4644a3742a994542ae276735e534f478c22945c6f3cfe83aeae5012a1782384cc23d44e36874937be6fc41842d14d12afdb1b474c73f72286104a9b93cef14a0a0ff2e4bc2008e5f89c7973f22e693aa363611003082579f3e72cbde9481ba5de467c694e17ca49a7103949479446b950ba0e2d1963f292771d36da74dcb0b163060e10d942f9939ee6249a4a12056820a28562aac949dc468ecce9ce4249aa90f9a5197b37c7582867eae9b0b626c7daf20ae50e6b9dd4a42695ff5be14acf954bd3ba7bd50c72c32a946eabc47ffed3399550a1aced26327c7fa650ee346b72f8d448a1a04d7c3fbdd255d1a14814ca721b4acc3a08d18c43a13036235f236493c62bf284920cef9239c89c2fe389130a2ef6be75ff19f763449a502e6f4f2df798cc348709c5cb5ce7a72bcd52c72594e450629a7b0d4fa289514249aff30935fdf897b948120a4ae7f9acfba5de4716414241b50825e652f2224728e6f18cf13137c67b2b62844db74bce4e6cb75cf5f4fc735a7fc8ba458a50bad5ee241ecb800811cabb49d4d9fdd1ef71141942f9f336bc33496b828e611aa62242e0ae3bcd3575ccb5ebc35e283d42224128ef08a5277ff3a3230242f1e54f68d9d56bb0991c33d86174e4edf8c2068e0fac99210d911f94735266f27e624510f141f1ba4e378c7811651f911e1464ffa7b6938d31453d32827610e141417c5e6e8fc63cd14cc82e4a17fa2faea4f35835d24539c642eef45a673521b928989f68e6a7eea574878b82afa96cf19d9bf5925b244753f229ab726d51d8d3aff9049dd49d94d5a274bf7127a6ba79d3295a94eb4e8e5776f42e9b675150bfcd4cc296b2b063cf754dd776e5ad2dc3946f3a9da3e4d46351ecf02a5f1f7a766f9a542302382c5053088145c1545a96183637c97ee515425e51d07bc29c4cdf1e26d3ae28a6cc2686d2dd0b21d3ad2858085582865535e14fac287fbda9fb953969c55b45399fdc6db2d99f762f5520441505fdb14a6ec80dde31c3869054942d3e5f975882810ea480068e1054146f378406258c8bde1ee19840e721e414a5d3204784a84ebd6914628a62d0d0719390c6990d300c0d2f74981a3407424a5190316689e68e93a2fce3f9437309fa356821a328655b7d96d62e3d9328d8b2757b17b1b78c1f694abf9de4261a8a82d83cf1a25386f4d98480a2e8f6b9514eba219f2807f51bcbf8d9cf7f433c513825db58e8d589a228ddf1c2a385d637cc1739d20e3e1d3a72cc60023ad28e3024e09c289d980ea31fc2f2541bb209e77b444982aa93261d219ad845c4dcdddcab624f3b339e52675d6562428799b8104cb49fa771f566f2a199c5d4c90d9b904b1484963e379974d4dd6e89c26997151d3d5c4b682a51facc0f639bd3ddf4a2045ea376a59db6e2597726089defe4924bc49c4461b38e1e25ba9960298648a2983b6927ef0f25bea190481444ce448426314e928443205178d33cb2ac67d466d203421e51927e55367237e7f02347946466d9664d0d25c81ed28882550665a22991ff358730a2f46153f3da5d791e6de6224a82f8dfa4398bac0e934c4594e4d4e8662685d02e32c44414534d29314676f21d250f4144d9e3be8a9efe1384ed87286e6c91351eb5f32879288418a2e8e16cc65c733734ee3d08294451efd7a4dfa87949720851ea2d492b7be389ed1e32886297b97ffac6d4b65510c5ffe0b91a36dc64dc8404a224dc2895fb61bbb64a2180289a2475c6f0491bf28782d6cff7c850881f4ae2e59bdd270fe23f8dd887d2951c4df521848c5b08e14349a84ce359478ae9b4410cd94341c6c9ae351e0fd143e974ba3399531f9287921cef0f5fafadac31082178285b85759b7d521b97ef501c5bbf19134fd212447628c8ad8ce727ed4e50e23a94b4aa4eae1b910ee5340bd99a4365e376e6500cbb7fa55743cfcc54851039947aee73ebe383ccd81421240ec552a35ac2dd835e4fa9100287828fc6b0fd9341dca76f28c7125fed4b7e4f9276433983aa3a21bdfb34d73614436df63e8ecc6ca76243d9ebac640de50cb7de4f520809216a2846d3bbfa1bd31e084943494c323267ff0d32ce090d651f194deabc4975533e4349c7e8e9a7d733611d33944346e8a09d3a34fe2a0326baeed615ef5a756d69397b4227133c870c8c503a68faeb3b8662923ec6a4c397ca18436228ba5ca8a75f3f0cc5f864f287e9f89a660386d2cc082d3ae612f24ed5215f28f868a8fb8ad80be5eb70e25ec649178af1c43d974cd326b5e642c98496933b7c49a6e4e5215b287b92844fdbd2d142b1543c8cac7337f9b31d21240b059d7a9ba945988ca2c5427964ee1979671a33ef902b94846d9518647718930bb142d1d48fc611b2e4fbdd2155287aa8f8dc768d9b5e542a94c4fca3b3bccee6ff35640ae5f64cba721fe5cdd31029943cc92685bedb8444a1f4254f9fb8957d1fc41028145bdbb36d4f5a71cd439e50ba93df395274520e1b14b04288134a2174468dbb18597aa24d086942a9c47aebad924129796d258430a1a083f0ccae494f67bd6a30762005344642965094b70cfa4d946ab0d5d031831b896620440905d3281a54e98b9067d6607b018e1b189284620e6d25c9d1d4ebc48cf922318e0d20a1abb84bd52f57ed7477ed2f4943ebc4d231492f78018e0d8081a30323232fc07143438e50eeffd5d839978c50104a2893791aa54048118aa976763f9c44285d8fce12ad5dad2f838290211473e3ee663511f2632e849228b14b84ce0b13f50e098297d662b1dae6d696997dd2768a7febec307608100a9a3a8e0739579710f283c29d3ce19d94185d4cea5808f14149caf8892729f9fe39ec84901e94048df13eee94986ccf85101e944bfe19fb5d0bfb18d500d94531ae96c6ec989500a28bb2c91a6312dd7cb6f4e7b928e620aea683c7677bb868f33ec5f36b64f4d23d5433eb6f79265121f3050e1b5ed8b8e120b728f5ba8c959c45ce44df16a593ac2499eb37b52849aac3549c8b12ed3b4600a145498999361ad24e3a4c8d0f300d0b03641685b5d2ef41b72a8ba299bc994e9ef10451e6485f00894531c86a198fd1438735018145b92f45ba8f277ded5f511a4de12b52abbd37c334cc15c5934ff55e061311bc0e1b5e24482b9283501bc62cfc1c660616b8619c15259d4d7c338d56e357ea551494524d22a61642d3abc1a68ad297d00ee2754c7335a928cdff06add9e52276b20e4050518c614ed0a15a66f7740a198098c234ab16d5dcd1b6b85c93c4b04b51da68723cb9c40a1dae2245e14569937f4209c828ca697e3e5dc7b5d4b841445112a37c8d4932588bce86a2bbbd31cb5cbd7299570f26e79b4b0ba52350147e4bf66cd289cdcee5274aa2bbaedc98209e287612b78474fb309e459d28a77bc6f5bb5b838dc66e200212f0c0062cb03b6c90767c81e369ac71a2248fdcb425c9de0e1d386200b28962abfa08796f16ba7e4701441305fdbc0a694af85cf24787b9310197024826caaaa941296974734991f9c0c8080d03c144714cb579f6a46aa1f49728766e36fdf3b6f145098825caf21faae4173d8254a2f056f623fcd449aa94400184124513e166268a6ab609029944297694304ace410b2347f7054024515077214a9b9cba9478a11bc946054646746c183bce0b209128e7b71e53e9b1a4062151cc2155554598f586e811e570be994aeca9d37103e288f2094235ddba8c4e7dd2888289cd7ca8944bdb9f11c538cfb6d1d3f6dcf9220a4a6d9d3eabd5543a2ba218375ed207b9b81373798928f9865c784751d2c37a8a8882cbefe92679276812563d4471ecd3a49b983a8f980d51d87e8d9a4449a96a21ca2574ceb61d97ee7a6a1a4008512c51212343dc54add783288c69d90d76662686ba0451928412f79817d3a9633410c5922b931fa46f8edf8028aa9bdce3eb39893f1dd53f946563efe951e29d0e0dc78e2f30303212468e1d3a6cbc20e5f001881f4a26eb4e12a4cb75ec3fad046d3901903e144dcf4ede58e7e7beedf8c22c1f8af1393bb2346934181949a301eea1186bbc44d9ca9247da1cb0014604d468a046036403881e4aaf3d5f1ecaa2745e7cf0d4776e0282874c7fd32c766d903b94ab4c099ff1be1e80d8a118f79d444d3491ef79903a94abc64e7692322074e0e4ea8469de8f39d3207328a8e692b9a4aa92ab1518618ce0032323378ed9910307188d0510391484899afbe1dbffe4d1c8881840e2509017699a31ac9c5af10361ec0863c75d01040e9ccc6c6db8a86dd6568d8bfe390b4d2f1d1840de50d48da3f9c4dea83e017143614ebe927d93aba1d394e30309d286d2ee954ceb7e15669240d850f224497afc3f644cf2bd86c27ada5bdf2e13b6936a28b9ade9f4df2bf9b408928672492174dce42428b184d150ececb6724f7286527a68f5384de2a8d640cc50fa5c9da579739224e945014819b22e7db396cb9ccdcab60ab91b435c329483c9c5472f41a6f0fc1709640cc53e416ace4932e95d8f183c133911f3b894176ffd2cda774be82409da3094e48866feb79e3eb1beb091e80002868214036628f69a9cfeecb1392f4349cbde079bf1246a92a12c3ac58927b66994c6504cf5d9caf327f9491443f16343ff6a34f9d10b43418c8d7f55cac61018ca416b9650d1f00bc5603ac953ee412f143c6e4c6b0d73d5a42e14d46dafe38bd5671217ca9fef9318634cd361da42e1c2c7e563490b2541ef62ef04cd12cf42d13c2613a733c81863a1709acf4b26a5f55d7485525f27dd21f3562886fb15b59a5385d2fc86aef6b0a2939c0aa577d7ef6aaf3f319f423183b4d8183a278572e5ca565aefd6b517859286bed3f4f3db3f0f0ae535d1d3bfc94dd7dc3da1a04c18ed9dec5ef3764e28af2641fdf5f3a6d8ae09c9f5b16342514fcc6e6727f163b484a2897a8dd149899e7f2594e47ad73f3d6523f3492869f66bcfa5ba44ed91500ca54fddca8b34217384f249a2e77f527c148d110a4a66f9bd986c21572b42396fdde9188412b755234271e4372971b65f42694328886c759bc999108ad19424e61c93582a0a42b1e37f480f72d63c09100abe3a3af4930e17273f28284126997d261f144f932e31714c9f7f801e9484cf415975d4fdf7003c28675f3de9a4162bf377c1ca09be2e8a1bbdb54a2e4974d4e4a2e8d77165548e8b92e49bb673ee5b9462f49489a2db99c4b628a9eed30fd98d49676b5114dba0e46aac6d132d8afd7f2f22c3f8c8cca264c266917d22d4c9aa2c4a4a12d3dcd13fbe83b128888955af76b028fe7749ba47af28ae49b37152349c1c734539c878b092561453ad4e6a69062bca412693ac24c964c62acaf3a394789a255baaafc1965e9003477268e7aa2856f95baf9bb8561eeb9a918a927a9cbf37cb74062a0a329c1275cf2c3d8612d55394b3e77ce2b57ae9983e39c314a5971367e489210c600e1b34c0d091368719a5289a28414613a91e834e328314a59149c75c7d255c09f3288aa73c6cfcd56d12bc1545418ef9ffa6e7a431d65014c7d3d9fedec8e634ce0045d9cc63da8d267fa29c6378537b0f2f08333c5134b9d94f9cce933c9c086146274a5ba7a93fd5a91739274ae2c27ffb45661345cd6a66829c68a2549e644e63b9759d431d99289fa432c79c3b87cc66c244c13c9a70728e9de424d34b94cfe478d0a9d34dc7184b143fee97246e095fb2a54a1474fc28254af2e329a52d6f6338d3248a26a8d52cfa45a937257692a8744c4aaedff845c22caf8c8bafcbcc6640a2a0252725ba4b660d763fa2ec26423f9d876e80e10506c290800e1de6033f32628e28ca261d7f54f56473dc88f2bce64d723849ce378d11054b39f94435661125f5257ccc3e1ddbeeafc1b68a289d34e55b5b42a912ce1a6c896846224aeae733c70fa74f4bfbc00c4494a418956d2b0b59265a830d2fd9c8b1230f51ee12f22491bab6316d88a249db498d495b0d362fb610659fdb9884931673926c924e8882892935c9f9bad2cfe54114c634adf4478931ef331a36c051831d2d88e27a8ac664258fb867f502512cafdd76ed58191b5483adec055fd8a8a1c3d4600ecc00444136a3ad7fd49c4cbcf50fe530a3da4d78925525a9cc0f255b2f0ddd1dcdf2ad1a6cfe3a6c78917d28a6d298a64c30d9b0976d0433f85012a72709a34e7c2891ead843a9af3bc913e4c44c92e9a19877264f4306714f661e0a3fa7a49244d66c4c8235180f25bd262664b0f90e2571691ee5e490f13b5866d8a1a03bed9efe2872b3dd3a149387cf5869a1379ea543d94af3ccc3c8e8756a0fcc9843314bcdc46fbad5242b95435937e7e6f2f7d62c9b068d2f72dc0566c4a16876b2ac7e9998736f376ed8d0c0c8080e1b34c0d0a170405fd8a8c11733a0f1858d17a42380196f28ac26a1ddcdca33fcc80da512a49669d28c2d199cd106adb3765c4ee3ebf6b624e126edb406931a2c81196c28789589b44c4d621499196b280793ba57b23769c56723497206374c093050810a8c8cdc0c359474d07792c54913a1d58c3494eeb46c940b1d0dbc75a8c766b85ab57bacdc76c7757bdb93d367f0247d628dd68f3135c30c5adc985e6dc7b7cf9aa0d3e9b01b3c2e98518672d75e5dedc9e1248d93a19449db9fd01babc3555a983186624c279949426aa8927a1d4498218682b5bb991c113a97492e0cc5d1e26d2eff926006184aff497cc6541d4d27fe4241cd49f7dab02e1af6195e2828c9afb232fc6a841d82195d2807d5a964f3dfe7b26770a15c6a25535dfd334fce164ab277ed33b4a0cb8abba95d768cf7e954219394a1429784195928799824a704b5f179923768243070bc0eb45481195828d6edc94db6a13e06255728867cd3d1f74c073193154ebad7375528099d33a375cee0c1cc195428c694efcdb7dda227c98c29b81a5aaeefaab55b3bf7c17474c96ef26d7c86148a27fb397614e5aaa504258e4229e7347f6dc93de97fa6614607faa43b980185c2a726a957a3abf5d68c2794fb241bfdb20e223c35c3090539654a12219e0d1c25581ad684921457ba69b1a9c10c2614e3ab986cb9bed924c33037d2054646900d528d194b28cb891f19b92e66b5cd5082b7367abbba79e99d1b9b1f4f3213bb8327abd28b1949288acc7ddf70531eaa9c8184820eb293f5f999f84e728472ecbc61da24f1f58c5092bb214e47e69358a72214d53d93189359c2dc3e8308e5b23053da3c4fd02447c28c2194e4e851afee5763fe720f6608a15c32cec5fe974128690f2243892598b7d84028c924aeae2773db2fd98c1f9447274bab0f194684b855878ec6b1038c3072fc3260860f4aa263b29b4fc2f95a23233b8c173970243474e8483968b4ea408ec18c1ea04e77f860ab5bff60060f4a2f2ae36dcef50329c72e0a57c29bce6c2ab77545745136493e88589b45725166aa5c6b68bddcaa665372cc5662a5082e8a26863ebd9fe46f518e6de99a525f933cdd16a5d09251ebf67115e25a94574bd2923eda9c7f9a0722b42826af3c99519e26671f0922b32829b9d03a32f20e1c37443032c2344cc70e1c3774b489c8a27842a9c7c660bde3ab482c4a4a8c7bf8acb2af994404169e7610defb723a2bf20a342e652f3fb52edc56664f4e3b3ff94950fe8bb8a2241b94924508a519b3482b4a6a4cecb4666445e916ab57f7f2221f931c4a64ab2849d2fce6cfd19f04d1c006420a789f79aaa2e8663da6c4841505915494f6ab75115494ee4deba67dfe5a6e4e5150326da9dde7a62896bb8949d4a41a4e264b5132bd762f6f9d14056d576272e3f7f9dca3286bda98247b6ca2286d12be2773cc9cb39484a2a0325567aab7cd730b14bda5cee88e8bc5dd9bcdc9cb9c7429ad41fd094d90499df613634646aa20e289621a159367279cccdd1736d01733c0c197db9d28996bad7f4cd169e18d8c340d4e944da59670ebe3043651925475bdf66755359188268a79afc494f5fd8a934cd808264c2f6123229628772aa953995279caf42295289a4a134294d49121935062d35ae6159b9426c1ea6668ea55c678d5e59ba45398364d566e9244a9475b7cadf2b2486ca1646e0689e298c91c79ab0aad311d11794449b0f592e4ef501131d760a3618354832d8788234ac2df8852ba57b33b74b84823ca364ae77ebc0e8d18a40406da17228c288bee1863c7cdacd9fd46c0082c605208d2dbb82182911a228b28868a5f8f6b9fbb9583e1858d91111c364646bec0a1030634b405228a280977dff91f5293683d1105214bd89f0e6f15999374208b20a2fc23deff74fa523a260f510ef11ad373d0a618182fca54c410ec5dadd88a58ebee7ed7bc0793ae329a8a931a6c5ee0d8a16306bf35102944c1840d1354644efef2498108218a394a64e4751c573f890ca2bc1bec4e7e9f2cca4f228228a63c65db1b736ab0e1b07127120816880022edb091c3047f285b9727a9276ea3a13581881f8a9a42555ee329d287a275566cd49817e1c36154ec9c52377e61e36c0fe5fa2469773ea9ea7ee481f485074620a287e28aa692bb9392c29987b28a2c413de7524ad398be2081081e4aba2fe2624d4c3207e51d4a97fdf9a5ea59262f0c88d8a12c3a5c7f971426326d7528dec8ee133d4b9f7adae9508cdf4e72963d615f631b7328a717d95a1dda47ea470e0535cd7f259e395ddc3814f4ff860eafed24281d0e997ab4366320f286f2266d720c278e0803113794a35f87f7283f32657403078ea37a43a40de5f513446689d85012a2836937c14e67c3226b285a6813b3848786306d0944d480756dd5c8d957b5876946d951557991034705bcc88123e11c3bc04843e1c62d94dca413414341094aee5eeb3db34e32226728fbfd8e8e8977a208cf0c6ea41c60708b98a13832c9b2a14ffa128498460dd2176b1210294339469ab9072ff17a823576980f648d1d068c0c63c7a50819cad5ceddd4f48e11d797b992c3acb16387c8180aaa969fd4c888a178aa36dfc9d341aafe226128e68b29b935f747100143e9a450e246448928b143912f94637e9334447cd7c639125b8ed41d10f142f9248cf8a8342ee622e25024120704c280301406087817631308001840200bc582c1603c8fa4e5071400045d2c1e382a28202222141610200e0a05e26018100687c2603020180a07c3a07060889848a13cc84a465924120ed7ceb69ea9b4098b722d7dd09432be77f5e1807c0cf37d9cabd472e45825b01d523c9f625f3015d69ec37630284183e3934398af55e45ba2c7890e55515dc24dcd41c9f84d1712d2c96bd9bff8e486f9f87580baa23820464244a92aa08ff6605deb60c245f6e102f245f86bb530e918290e8e6173112a6c90ec31612c66379e4f79b63327ad8dfeffe8a6d3caf1eb006545814348321f473e05797a7aaf45c4eee9748072549d72bf545b9de82a852bad73119274e0bdcd53daf917e5dad18b328b5014ab95d61e3a8254a13a29aaeb9e35d053ee0e780dce9cc2202c11ae2d7839e9cff3ea112d7fed0fceafd7c90fa108b3db0353144633c05308fa5226915d5774e21ccce31292bee1756da22ac3e93fc8b373d89970c601c2aab0266420fce15b02aaa558ffe3f174a17f8ebbcd1f56ef873933a82c69963386035ef8c846391bb4dbcc6abe3441d2ce5771c476370a6d6f79bb365bd4b0927fdb1876b1577ae1f46a73b69b320fcabbc0cb36e4187cda4229b0259ff42178e4651e0b0f7145aea331f45a2a46c09e9092a6666c3d523cadb41dcde8ee927cd38d04aa6ae07310a2878a4c6d074d2b08e5d82a0a3f3a5d4f594cfe2c8f439ed132eec9052460439f23106b1b386904f6de602d2aae1b7f959ba5c9adbd8acf26738e272adcdbc23ca535de78a5aa8780edab7a37057da4bfd9e40a3fb22259d3fb0e7ba4139275ad4f3fa5272ec2572dfe1a3c2fa1872f6c88bf00f377fcb2dabc820bae4a30c6b880dd6fed190455f501db1421240fab0ba7fbce9d59800ff6f4819111f35be8aae8d0794f410b7e88dba392d6820eba407a34b797c5c7ace324961cc0e4acc67bd0f66c3e924107ff53ffef850822ebf57c21383ca3bd40bd5f8c6eebb46c4795512fd0e2a2da14154d0c57cd84e761a410f7cc68f5445d998e8973b6e4ae0330d0e72f0877a15bcc046ceb0b35428302e7138eafa5687a8bf9f1b5034ba1c0906d77ea591c75a1132c7c9155b1096b2a6e726914721e60b31ede0c2eb1a7774ecb684256dacb05c791aa73819be13f0cd744c422b8146168522b818ba6ab5c8c52ad333d888e2d54158987e84544d36c760abd0d5c16a1a27e949e422b02eb91ce192456d448426eaf1c652bf8ec0bc3d39442a53b62435662298cd421099c3930450970c4d62a37d460c25c512568d94213521e0c9bd41165ad133c04cf4102419482d6eaa4523b25caec01e398d97027c0890aa113f29716c046db743c285addacc176277816c63a73ee486f346ac2b7d6428e53084937d8d6a68365b82105ec37914bdb042becf5154d92646d13885f9ff1fece955ded89c7265b950ad2774d2310476e678984e0562f731f56913a9919ac2ad81c57d8725694c04818e64c54303e1abadc864f0d694d912894969298e8f1bc078876677518e38327911d46b9c559e6602801ca4181515d810ac477eb7ee99ef8ff24c2380c3f76c3b1e19e1f3e3434018683fb0054e9c6ae238f69728174e1188d2a6bb46ae187780b987abd86c3271b9502660f34b9043a6b6092b0b0762910ae706332b35abb588a0260cd62ff5ca4f1cf210758749902e447e0e1b6de7d0d8d2fb4df7bd98cbb8fab0dbb7aedb3178abeafebf6ebbd3080a146a46111b19e099e4c2d83de232888aa25f6501121a04a0c94fd9b9adcf151bfe7fe20dc2c908696fafd38f7e2ab03e2635cb81786eaa225be282a45741dc820ef176a50b6dd1f8cf99cae3a41901f3c1f3580b6212039d7180bdfab291cf6eeb430b183e69924ec706c148f92aa6e038b3cb76c18a18a5797fd959a493eafcddc0971837f22089ce69bf88038170acecbdf5a8105706b1ddc15028571cb4c9b09bd01d90f384c067e78151fd87db988dd50a3493a1a898ef43a621f1053d427e8b20198717adf2f7700407008a0b8be44be5cc54afb8fa43f1ed028b253f4c52745d5e3050be6f8be07afd12a011e07c2383614bfece97e44e624b5e60bcab5756867fd2311f1db8804c87d5bdd1f846edf8396b2a324ce82774f1bdbb8772fa3da6376eca4c7a5c5b91b0e2d1038321eda4b598d45c2afbc285f48124a2d2687647822aaf11fc23a55b54501c4fe13b8beb0f390676a09687ae6f9f890ce357359d51fc5b1bb1ef3bf8314ec4115336adca10e3fb7689ee97a9203915eb59a3b0c70c41341b34fda31831d8470875101fb1f3c819c9e88f8af550aa36d5e20b54a78d21999a066502a6791f0ea67564afd6205a578b00927fcf6e07e9a45583d9f981a6d640e20ba6a99b9a45723051a9bf7b85becfffb0f652525a046181dd930370e0debc158de1387e6b738510219a3b331ca5905eee3948fed9bbb8d6662926a0e492dfc8c9a92b30a33d9c05d76ff32e38a48bc0041bc960330881d04f7229a401c3a6da64863748dc1f40ede9c25997558b4cb268033eb420264806441d030c47235f008135c20db999d028aa38c8df7f1f9b5c9304c38518c52be922d56cebbdd281ad2803a81eecb38bb2c025d0be123366106a2c82cd8ceda11e7e7375fcfa93634240c41506f9b1a2947de44a04d2ebaca6a11f930412771d67a241aa388a9db2732da1974606872240260881329cf23ea6233b923acab2565438fa42704a9a873f759fe845a6e6c96cb53692fc526fa20b22a33667c5777c71a91ea814e1fbe83e324902d54713dd6a9cb15ed8443b77525f75ce922be2b61902cc3ea5682932f925ec05882610e4434a0d2a8a3cbf7874bd0f2d95dde3c9b68322560576ba8c40166fa6baae98e0eb6d211705d25f0a3285a53bd212a7e5d5299396fd8200066a88ca9d6cc66ec4a43f49e8ec9c2465040ab5ba90f346a08230b0a02ee10379d2ecce245d85c49e75d62bf865588c021c408206bb18cd67742306b9d44c786b6ad9a99da2b319f78d19a3da02b5ea597e208c0020521653a050ed39ba49b24e084e4c98d52ab0c946086387b36b320f8140963fd9477b9271a60323a7f8928c5fb44abb6a62f9998b0f672fc7f28627ab761b6258095861a9e620d50dec416d2fd0b8f0d81c8fc794364232ec93f557adda65681487724ac6445c56dab03e584a755bc1242ae5210aa77c9ccc16250c612ff66f1efe3af262d5c9ee6b89bc62d99dd7686e370052f1c6da49e500a5cf9aa90c7dbc2c65dce2f74e2a754974b01bf8dcc066429ec044494f8c7332fcde675a8ab36b622fddcfec0989740812a3456443a70a274ec6abd01e9aa64d0246915b93443e7a516db548a0a2071b1fae220d337c0a444ecfd9769c7a03c251839300210f2aa621706a21d110f8139241fa0ded38f88fdeba549e0ac8d683fc8921fd3342a91ae0c1b30a15dddd32adcfef6497d264564c6b595143ae972033c71ccec92f90c4f09769a24d6d225dd4b958ceb044db512e870b73c84d5aea636e98926b072544c8e64582bb76a2c259aeae4d0674c85530cd021b234957de14da2998278baf587b19fb987909014791e133a5bc03170fb1e474b1a8c12b4794f0038b912c5883b3190ef07cc1ad14b1f86989bb8cf245c7596705e52ca4bd0498014f880714173b3610d5279110895611f9a8dcd3b653012d0523d411993e164a11401285bbe007224d9cc86aa01763f3a4ee02a13864984482f61050b23f57f15028eee973784aa03b4a6a6f6468e3e9c03f26ae2c390946c736dc50832dbaf9740c59f954552c497a61784629b5c09decbcb91cc7005527bb38144443a2019e46e0729ce956ce4f7b9c56a012716183798de658daf4f82f9337260ad0f8f32805b2d88190a19655b74ee43a6d8af6aecd71ad32f5d0dc2312da6a2d446623c01f687bf9fe0c09a73f3bc583d22a4ee9d97eb78c8e18d44d8113173b553a8f9ac56407a39b49d394129fffa20a25953e17643c6d9d97aa7a0ee6b9c23aec54e3a842e96ecb6512c8b6c43dcacb4a03792d555018a38ffcd3c915b98904675e09d177cfe7590b2e469b16755fcd8896c462b8038bfe94e3668f4a8d0a1fc44ea95a2009c0e0e5846df663fe1eb0d1374800b4f0c87321c7905aa5c61be0c0a934206ed412a4e9b4d08ee7215b933fe0807d8b95e174297d4787d26d12df268d20a1500d787d2080601e8a2c1a7c7a57c889d7d7b08b71248825eb0297e524ca3f6d5935ce4749f8ed09c52718f8008bdd8a5581308e6638d4b97bcc8e10401d97257730998c366d829fe96f82b339854553f430ad419f89742fe06115f757d742bf6d3ae630905a9106d2addf5da2a9dff21e49962f4b37b3477e2308914605bdf7185733cc0ec50cf9b9c02d55c9e520c07ff8ad4941885975bdde0d48e9aa6539187464c02db071d575a9f6a95a9d25301e12cf88d4141241099ff670d1d1a5b1d152ccd1f1c0ad9ec0d72ab68cf6b53244f00fa33140059409d4a1f1ac604dcdda0ee6cde7259f8ae32255209c19b59c8d8b24349e02e092a48427192a2c3b8e2ec6b5a1a8acff40ad186f4a7026b17955db315090237b0c5fa68538b5c7c313b91cac115d0a76855eefa8ba84ab85ac03be9e1220f3942b518785f23ed330f913ab13bff1a092c85942334e69c2edcd117d49b785d935340fe60099e648edc43d3b17c54add1097f678362c7e2dc615c513a4942bf4e65e57f6105d6b0b44e8a7c34d6f15f710be6ebf3019b7a4af447fc6e77fb174e2b9a7c922b6b7b391ac14c344db53ea90a0e47459a134052c822d2d853652d1a2a26efda3bd70f5db50934ff96480df5e3317852ae1e2866bfd5920911dd369c33067c9065c6569c0c750f147809f6e05c09cc2d17ea79c19491443f476133d22b88db39235966be720f040a30c0aebb7124119069c7acafd02338711f1342e2f16f8a531bbc2af5fa58de49ecfeb1d4a2fb59e785ac7a076627a8a83406592d4e8156304be368b9d1a5d4d566d18a4a24bd8c520886a69b4149b7ac08215356b8db0eb952aedeca6255f2263cdd8694855eafbbd2a20b2e7b42b418a7828ae37b5698b9afc35cf00d28496955768a3d9d9450cdd6ef514f9bc781270c635c8f0e1d37c75b2f41c35c218b55147feb49a695d9528a2d406263919471a163ee93f1a0b43a245bc676a5d09f2d5171de63844ea1381dee42c152fd26d2d858ec2b4ab34f1d5713ad35419169170656e90a1ae2e2feee208c9a2063083174c9222ee2e33ed0210858992c417266d729ab583d5288093b29a1e530a7f3a95626cabbe8b010f694d01ac3afa60f54fb3222292c6660f3011116ee96720aba018bb695ee373c9bb60dd6671599078db77bbcd3f365c3ce2b88ef01b89be8568c17b384f0ae388799d0489974679e365d304342a3f278249f024d7715b26292905070c1a8e4d23f47d1f51f15a2671c6c466c2003b3af8daca0eb678c78d15c70f0ab61e967e6a7d8b35addfc2f2f363887cb9156ff632e790e6e2fa1d422f6135bd63cb0318bd1a63df3552e40793db508f7fa0ecada2ff7cf0825ea9980158aeada8600406a292f7db5d05719a1d3c42629840424785f37fc9c9417e82135449260ed2c3688fdf1e08958a4e764e30b0b05a5ca6a480eed3e1a2d78aed03383e2f07aa6976faa70bb4ed36a92b138890662f8844946ec6d23efba0689c00a2853641981fccf52780fba03a24fd4dcf40011e2353a88ced001ca2cc6058eb72672629474bc5d267c3aeb615cf5f1521607192fada480a670e2868364996802e0559e1ad0b07b2a05601a11fce3d27d095e99eee507734ad6dc2cea8e56769768726801fc13f3d01c40c1212efe19eaac4990350275eac36100a511ed82a60ca88e3bf731a257f0b66f10390c6b067a43635b6fbbd7308b2dda078189359c70c9bcefba6c0d6b0489c2490248ac9e053ed00289aa6ce618d7160958da7f66f9fe59766e6cf246f585e583082a51cf29a524847386ee83d3436a13c49f0b6484932cf2e4a69957f41f218333ce0dc5c905208dc0cba181eb608aa23f9fcf07893f8cc9434f7d163862e1aacc0f79855f20f122f998a2f634c1a88e0126c79e4b883135c41b4af65164292839e200d83f73ff1505e85a202fd9401cc2451e036195ff860ac012409911044b5a13673bfdb10716b10300f758ddf91d9eaed1287a9f94aae6b54465f3d35468b0d78d89257a36a969bc25155450e900e0ed8854753b16a8a743b89583fa24f52a25028dbe8fa2714396dc6610b242f1c1d362e44b39855667510555bead00be5a5baccf99073870d1367a527eebeb32d0c40da80449ca951e27d41cb80ebf4c53aeedbf0f2a008ee04d3e990018e2d7092c2a4f4110a148496bcbe8fc0f043d352de135b9675ca06c9e473d1640c7534f36325f54b2d71e2d54eeef8c58984cc9b5f3129dcb3717b87e85e0ca743c235fd540f751f4b4836040bc138441f88c9a8bd21ae2329604646c7d0efd663240839e1d143f660058031f85cb363c2d00812aa084f4e6392570a757417276167c498a2af66f9514f1295065ac794073d238b107c18ac480f83a988e1d782c6f21634a913cf3e8e1c8520aa8decaa2c853edf1b8d604fc9b69b5ea11ae217084d0b336e4b2da8b9a8c3680761dca802bc61aa790630d1cb3d80e1684fddd43720480fe6f21c011d8a0e0f7b9d8749571b77e050dab18587b43254f4fa2821350e4946d5891803639da942810f20c98165a5614d77a1c622067843bb40055d990ecba8122fe18b5ab50be26d006b4de9c0cc1f096cff2437443b6005881a0e534de1e019410311401761578cf93691d9fca5c63727a949bf5a09f3d9f8805715df84a735aeaed726e00746913dccd81f35495d44a28570d863496b2753d7d7432f99776a6cf4aa2fae3373ad8f1e267993c2b3d1eecc299cfe272e64b9250e3e05b3639ab648b510b2ef6f92a2ef428d8171364b63feb31662867bf5f80b464ce79e5cc2c914c2b5a18f496975f232de27ab265d805ae906dd5fa973569a56f39261d9a01d67fd14c0fff1fb30359af2428e869067958be1c4349896038db61cdcb84a87c5ceae01fffa6fd4f49b6383bd1dd72dbc254a907054ad4c34c4ea2ed96b48822f0f3451b5b8dc88c3b87062a2e2489aef45efb86311c2ad0c9dc362861e347f41858f84e79991069b5a2662f826d56f5e7329b70135759a6b106b2b8fd80ef962831994c701c74d4e6ce09433437022744cddca30112e6dbcc9e07cd078a716613abc9de5a7c92a30652e5c74c4f14b8e327966c638e9a630112abf1139912c12259f6ba401de71fbcc94c09bf680d06118354c1412469aff8541aad5b16d39bb60bd08a576d53ae1cfc32a34e3838869208239d99808caf56cf810c29bac613e940aaecfbeb389e78d95db8f410749bc3aacce58b842aa765fc02d820fc0c62317201ca1f9b3686a08d3c2f6e5ae5fb4a26dab4b215e0010642c8a08f02ce5795be2da317161b57657a9f0ee14b3fb5b52016e65c9799e2c85b70af4ec05b50d19b0b4ff69009437df6db762f847fe47257c6d0b435754922118de5810b2279664d67ae0898ff235e1073dca5c36b533ae29983efc0f08d67dae85c97ef76f6051aee294081849a04cfeebf89cdd049270ac70f3d88751f9c354aebd9caba94120ea25f81e2a2ca9bb910dbacceac207dc7b0a1a87802f48d664723166abb052d9db3b811d98238f0d1d4a504007ccad3e821ce1a4750bbe0c05f9bdea66bc207b9bc678bac4da816bc2a5ec9079ce995e884b4107194051f6ff570471dee9fd9e628ac8858499056fdfe752282632dbdf84a70be8ed265966909336f3c5c86c38ba4887c4f198cd9d7d04ced89506d039e5693db71569381f028b0c7aaa722c0a9e5fad662a4347006f314a90b69d0139dc03603db6ba5aea88f6b515a32bc7e86dc558a9c7f94a663da13862447004266b6ad1fec40316b5cd40b7a3f3f7b7532acf66c9d887f4419f421aee2e299c27579b5341619428e199861db1b7565a0139a198e0b179223bd765023479d4ec3b7e29ca85ae8650245e8bcc799b3e56f2960cc40c040318a0afe517a7ace32874a1b27f66219e06fcf0ee3909dddad8c050e2a0708fe007c23d5e6b64b2c4ac0e3eaf77dd25b7d9c1829eb1ba0d5a6f065d9a923d697d6ebdbbc885f1b242feb9472654920750a0467883c42416cddbbfd40d8d02a9a9530d9b547e6669b593c4e35f84d0be638732908643a86105448d39fc9602f890eb4503b0547004727b06033c13ccb2c6e6854bc1d35c5483286d34e13490089381b59eaa681b9a27b27d70df81da0b4c05414e03ef8ffcd08005ec5fffff036b5c8e68534190542ed9a5161f90ed0fe7da74c0edb685d34457bed81660b67e900af064302043f3b410d8664da67e78e1ab6fd54bae558c6520ab154c53d6631691bf7eebce1bd08dadbcbed946ee0a23a8a48f1240942bb6d931f3067f9d3507e3960b73404b6971a98b6a2e288c726ac3cd03b70e4760b46e9562a911a3cd98234a168aa703f28486635ac54e37d817a8bb63d362ff8db5d41b7cd235663c2c37667e7b10e3a473c0d5c620c3377f5481dbf59ad255a25493ef846e43088f22ea66cee369f488bd479e6fa718052155b39ea498f3daa6bea009324465860318cad62f5d5c9f1ecbd5dc8daeca28079f33c82e93ae9da915320484d7524f59f0733e1ce5fbb140bbdda5bd289f03aa3283d5912df7950890e93ad9318588dc242d3eb43d719712bbf16235f7575f3c226784dfc1e3795e55e70bc8ce3853e5e13655cab5ec77e41072b9297d4febabef0826b1ff442e9d5d52b8217a1e5eb180536c6b57dafa3fba0e338dfeb388d42fdc9d645a32b4221a13f91afdb9fc250e7b7f8f61e734fc45e030e287e46696200702e6222c639841876b35143c659f85cad1aad1e32fa178b0cf1ce6b243971f3d429ef7c604baf5437d174296a71b988507c410915e55227b9c5e108359da2764b41820211aa15140642e990caaf94afb7562a9062094508460143324498cd45f5a1e6a46b6f287f15ea1ef05fb02880920c550c54f780bad79b6e189a424d812a12d45645011bc22207281a059c2cecdb4a100a381a38e10b7b7201aa1f54538702628c3c02ae8d0252214000d1a280324b0cb38c1e052c7342331416d577fa40967443511305045d9e9de2c6a29abe0c8b17ab85e09fc5d70e2048e024a4d358e166f40c8f6b43ed817ef0ec4331f4131e2baf19ac9d057914a7e0e3eeab7c3db2b208e2509d71c147f86a9f3be50282713d55645f88af7a15be87615d4495bd2dc6a8fb427d232a8214a850ada00e270a9b14f350bd4061a7281d922af14b09144250ed3a0a083a032e3d4e244640b534a921543ea35890ea050509d568a87507a54326e8fe690425010aa125aa0f438b9506a8eb1054df0f7cd40154a20fa583f18c1c3d8132172a9ba1fa3eb58b9222c24b7e84c248a8becf3d2ada0475eea38072f268647c72a212fc25469197164b81e9e37f9b83410b516967b8132b6c44695f149f812806fa540762448b86a5473e1ce550e7f966e453b4efe4cfa52a7d84f6626b1ebc250b45213da1d7d8b04a35e5020c9bfb946b1bff1fe0dc47834ac60ca55d7adbf957100aaedefdcb5384b1ac4a1138699116a4586a672578886349426e834c431650aa69f7f045a5c2f5aeb25d3bb7201d5e17355380e5fa78005fed18818d43382afdac8c11d350533b9bab8845c66afba0b9f2cea83e66273351f5d5a20f94b34587d71fb1862fa036cb7e001b06c088b8a4029187c1896742172d1ff9cac5be32225871dfd7f9a7d86392fa09f3fb413ea229255274d6c46d6ba7b8db43d7f7530a7390f9112461eaeca3dc10077f49bb8f67b43176a4982c2e7b6eb905ef2a864f6781f4c543880ead514f03a783dbbf3e29852f99d0719dac27ab5282e66b3933d858a0c627d215dafb89895430da6f8620b9df8e71d433bc68efac086cd490cc331502c822bb1cfe12104b4271fdbdf3fc83462865c5ec3465acee9a0451429331a115f63509b5ecda5fadb866df81dfbc3155110201537fb67b356d823cef5c23465362e24fd1221039ba7531b49053173ae3427b5305bd7d7a78291cfd00b3adbecc73051fd6e38f133060ea4164295a43debc0dabfbc2bfe144775e04835f3fa5e3431e85c66dc4677201ac0d497898a8c126ab3ca41388604b4bc2c6ad24feed9825b2134a80de1df788e6bb61a9fee2dcc10f6232682ce7e0b4513cf63566900ba69604ce80420a46e04cfc784f3200145212b5b9c87f37de88c8c70fc4dc100004f64d31561b7984cfa6a144dc8daf0aabc5474542afa664f6b227606de89004c3e9c81d4d1098e1b26a6bd20689ffa36ca8464f4f410fa371b0b51538053cf6c0bfb83f6047875a37ba0d979909c4b79549098057a953f6168df99b8393c5f1b4a925830eb7ff085f58340fb5b7c2291e86c35f9012282b6e7b4ea26926e13f89eaa960c2a3338634375f55a0bd7141120b004bb767106d0174c5d5a129481437e7b381745ba094a00548ab30bfa58457319333410b318d92934977c406166522750b523d17b0b5b76f6cab8d15d6314ef8ecea61506e1cff1547e14212a045b86aa38d98d2e3a6cc932674a4541daf848f998421bf044ff8bb4e0cef0481d1c955155939525f0031bb15e9a7535bcb75d7a6f48d3da0229a5a9dafa1114c46a8d0b960024d461b50d03b52df4d4a61988e799b62328d20c0691c3b2ea93d990b60d1d357d02fef1a1919c4df20a36174ce097c124952e103517da305901d0cbe73d9e5721244e1ebef4a1f1e564781099050400404f9c730c50d68040cd277ab6250e2d8e6c1cdb7cd0f6a12c5fef0fae0ae31e9f1cbaf6ffaebd1ac9e9a5ee3d4951b277a5ab3752ec69e551f6d0265161d1ee233c931be066c7922f1b2c0d7728f36ac95dfdc62de49f7d21206d9c2b237c096c93305d6728114958856ab6dc3cac125dc1b9ba148b5860ce98542645847757e2a6b25830b864de20be2d971ce56894beb10e91296872a22e1294121c5b7438601f62c6e374e880f530da3b8d34fb7ca0ddb79bd8b23d8cc4b6ded4d5f840bc5831ced1814178e774b706ed296e0516529a6a8a6afb31679a5bb6ed0f8f58be9a293f1a9529b26675da05c14354111f0c49c5307abb8c1413596dc183a54017a9c8f2428b80d4d8277d95cfe3124b17aea25e28f0bdaff55e443d0d97e7468477d4b89edc0808da7e2f167182090900905a258e42fc32c9af6513789e6fb6f3ca792bdf691ab00838b79be43d6e537485af9934497d4138055c49c24f94caef989e2b738af4dc34b67d95a65f0a956ee4f81a1db335e92682ea6bcc7c72aa1dac88ebd3e8b1334ee77a0aece4e992fb6f4d822f6e8108eba420df73d185279a8559df1751521b8415875338443f5c6566d5e8246b19c690ca1771c1aca226fc26f372cd0fee2e2a87067ce6ef3c6796e78c61f491860bedb2f699699746c9d37302ffc9381982f17cc88d0e402d84184c417785a7326b8fd24ace351ec97b0f3491612d5a33e11df9b3b37b740658067878f696fa1fbab6c2eb5fb50f1b759cc8e888c3c2945314749fbfe1cd7d2c1bc62657003e5c7cc4dad6e00f5b3d2b9b6a6569158805409bbfc87eb385121c3e3992325b1a8bd50fddc237a40e0994d78c39fc9b517ee5ffa70ed8a785e40b453250f08d1c5fecd28eb121de2005a0c3b030b23fd5f0ab120eab7932e316e752ce4a2f328bb715caf12d82113ec7e4e60ffcf1252d48a78d74cb7e7739efe3516d7ff9c7b22bef729617727b4de7b309e39a1b0dd7954dcf9e3e6ae387c66ac4d0c88627619ec25e2218a993e45aaeec23e257fdbfa3c48c5c05e46d2b02dc890de9dc69027e5bdf8040edbdb7007f00a3036346c8db331162fee34b4d61f244497e9e611e2316cc5be715a1206dc1619f5bf2de851dc5cccb8b388e74d0f36ceca4f6dcf5061ecff616f1e743e132d8a55f1cf06c788ef50de070ec446dd48f70b278705a9b45e2f8c788a807df8dddf5e5d8b69f24c4009b69f7f5366441049a50099c6bfbe51cf8397458e7e2c0c058fef0e6ccaca709474eda08f08f458d76f1b43cf10e1879a1905b7ad3d7f70247bfd76583ede143cd279d19252b97e912b9f62c2e3ba249f337c0246e6ca358ce825698af2e00956159e4559d61f53526fd9e3e03621b182f0d62aa10dbd138a13586cc00e22e789f28e76b64219ea2c0a744acc12a0fa90eb06249e82680205e4db92b08e2d2ece25f6fa49d5be31b780867860c0c3cfaff76a8118a167053c1343a218e981760f90c69301d07a70dd3b5407a91040359003086903c43ecc9cc8005802206306b82b49da5c724605dae7a572e2db6fa63a3515c910b81015c4f295d82e84db335e190040b8dcee04d6ba3cc97cdf3e1021320cd99f8158116c03c593f69caea5ecea96c9eb5e07862c3c0b0a535b282788cfd4945a2ae7b1e4ef5db7210d5b42e3653d92b80e18f375849bff5c18855e0da1437c89e0b6623271f3b0026abca69fd44f606d9b07a38fe6b1c3553745062939c4eb7d1f314eb3012f31832801fd74b4437635d5504cf515a98a7a2e47792c19e511dda055a51227097872172d09dbc96ebb02348376922b433d56a4af9772e1b6898a2525372f9f0e69054bc45c3ad1ea97fa3d5f70a013f687280ede6a4b24979f954efd14695ac661642f30a7b841334365f3f4cc95e74d3701de1b71084f60d40e84b9b41931e0a048ce8c742d0c2f866e93ce3dd079219f251e74d4c36d9abb4df5ec7086c0b19e92efaf1da85d777cca341b0726d43825d1a57d8b8b3e611bffc8416075b2691e7f0123d7668795c4b5eae2578f562970637afcb8d2df4372e5edc646d9da6447b39a6187897ecc4f4dea88f93472040c443165286281df7b729579977da34c8a4a8ee25b9c7e7b93e17f6684bff827e48b8b3192fefa8edc6a276ab44d11f0a3c76a6021989eb788bec353c7bb0a2513370849c825d7b0c4f58855ece67e308c5a194bdbb9a5fdff961350c4ad163af8dba19f54079de9b08274513969251d2ac63f10eafad0955c5946961d95ad84a1aae0fe9a880c5472b5742c69406cd586ea500bd530017c1b40231523801d69a773518cdf0759d03a85876d22219e49426e48065736c081b086da99ee171ad2da88bad8b141215eb9958af43a91bba6d0690077ef1a49ceeef7942954a1f9f8c2fa15aa2030d463a8b940b3394f0ec413e86ee6d5dff7b688d492e05a996c8bc5e99823b81f9cd19f3531273dbdce2ae5067b3d3a15ce618001ae80f71dea281320268a3a941457c2f4c4d1b8f69b11ebf27b6b0492dafeb83a8770afb038874a9b54ed2d205ed8bee5d893896dfb9814aa713731fa3bf6270126108de6fe9c28f46a30a69b881d7d10cc0336ad1a72495c89c7cb13bb50b7fd229b60921e0dbcd5ac0b8e955fefead53ee03b12ab3a4b0db9a477296c753ee200d2417797d29a7389343fc4a7f306ee11793cb080d4c925aa7e695f17be035c45289a572daaaa42480000d732eb800346362af97d2c5d772497ef5563ee9987f9405ac80605ba82ffdb7189d1abc4ad5787eb40d55bcc2511c9daea03453b0aa150e47365cfa1b02f61ad6fd03a9faf4c089bc0360e08a0e15bc29d18e298552edba11352b4810c30f67344350c0e27aff690064ec5590d5782fcdda9b17cbc6b716eaeb0710ec04d3a52b87ccbe40e006acdf6e5a630d66f7d4016ccbd8e1f1cc602210333fc45908a37856fe6f77e6d839874988c93d4800fd5ecd8836306637e0fd4a0c3703dce2d747618cbde702c9b67b0d48d397bbee38717fb4cc08350e2317106ec3d5899442e4555026e31c49a0a3040f26439ffffffffffffffffff68ddd3d6eced97494a0107a00494c34d29a594528a54ebdee4ce7c86fd0cbb98cf5c85220a6b0a840a8ec614aec1468e363ae25098902fc64ee65822d20e3854b6151263c2a95aed49b6bbb43469e2e978433985f966f32444103aef7043f9ef3289929f3f1f74b4a19063b0f38cfb39c682d0c18642b44b35255dd735944feedf7e08b7430dc5ff91316bf6c78f49c51f74a4a1187bd63ff5c734a3b7103ad0506270a0e30c253125e4e36dcc2544af3bcc50909e5a4aec8394b352a30c3aca50bebdcc21851295f163c8509239dacdea32ffe93d86c295c91046e994aa698aa118ba3e36c61cc404fd75b5d01186429435dd1da2868c2dd3460e4a07184aa57c5683921e3b843847c7174abda5937fe63105ed05ed3578a6698977a1dc72a5aca4ec678d36d25081c9a1830bc5edb0109751d479e8d842396af893fbf2af3b3a2d9474f29dcf10fd1c3ab2504c2173d431314add8646197e461ac70a0ba5cc5c265574a8c8efafc000d17105a4ffa6a97b8bd661855244cd89216f8520e8a842b14bb375279f9a9d34154ab11ac145783c8572b610e937885fa76aa55050be5a969b614714ca1663fa7faa5aee35502809617ad4b65ebfca2794ad747ed6908b4387134a392ab3e4d0a33be49b5096fb1acd907d261453d6ac88c7897c553b9650f624b664929e3b94505af56d97905eb3ea2809e5644aa65b9173b7294542314a0ecf96eea7b573472899cb66d37d9af396c1a0c308c5cca263fcd7cf31c85884924819d4526e420aa97510a1183e889010f53dc41802816158d6e2915acee65b5496951002c228664f1bb3348e4bfe8351d0a36406cfa571ea074629364bec5af38be2686797a55cc9d5be28c69c1a654246b2cebd28c62fd91b722ed598e545495706f5e91ac36976514c7f7baefe7153a6ba2807d1ab9539cc4531472bb5481b2e4aa26f2447b7165d57ba456994cd4e2ebd4c62255b143fabc389df1bd393548bb259a78b9dacae2392685108b9cb46e424cda2985793781579d9509245494b4822123d4c73522ccae14b820cda437c33c1a29c43870e6293d445ff8a52ff781e8dbf2bca3123674f93df4ff7ad286cdc6913fa643345ac28e4fd0f317a3fc70aada2a0b6b466888899095245713347cb2ff166a5938ad2c79032530491d51d5414de32e78d161af4daa728c59d560f49dfaf98a6286ddda89b91396784a52857793e2136aa228ea428e8dcbc0f9d789ac151944cf47c5367b748de8ba22436ecbfe82d14e5d27ba7746e5bf1d483a218b3e4147fee9234e43f5134395e3a889c8469e43d51ce342d39226fbcff3b518ee18426a1399c28c8b8eb52b5d1a3f64d1474744d3699d8bc194d94c3c374da70268abeeb9ac22cef56c64429432edd2c6d3ba54b94b6473f09bd6ecdd71225cfb3ae08afe94f9528beaef5b5afecfd4a89d2d866473b731265efe4a3a54d499472eb3cfd885c9f3012c5f3d22aad7cd39e834431527eda9c31dd9d087a441f26c848724a8e3062ca53230a4148751f8949a8f31951aab198bc3b9a45072da2741974fbb4e4a02ba488b27e18d7ed9335cf494479a26cdecdb62cdf20a2205e363b825fe5c81ca290636cc7fa244c89b0218ac1c342d9bb461db710252f2d93c38733d32921cadd3362cace4114644cdee7d964f53c41143fcbb3e8d0bb480a44e9c3c691a94ab6ff80280815bd273f836ee70fa50fb1b5ff3698a7cd0f85a0b55dff3f8910ea43612d536efe1a1d3f7c287cfbf5e8df9424ab3d1433940ca925470f852892e45e270fc5b8f58e61466435090fc538325769758ebcd11dca1b42dafe14214bde0e05a16f22e4dbf81de3752895663febd74c0bd1a1e8a33b954effd1d63914c469efd8f9d6d468e450caf1384ac910fb6fc7a17cfae6b4a73a4dea854339c9b49e5a557f43c926071d1e71754329638ae93da1944dd2db507a0d416a529963f6cfd950d2f9e123f3769ada7c0dc57513f22bc33c85cad55012724f5cec0419a2e569280619b663bccbf960391a4a42ad3fc62c32bd267e869226dbdd1c43b6e9c90cc59f53e9df57695b2a43215988ae293d190a765e9dca2fdf3b3f8692104dd2845c75ecbc180a327a630c51257977c250cc63a249d2959eee6028a88ed21395971e355f2824d5133944cacca7f6424967c9ede5eb9c5077a1e01a719208ed124ecc85c207539e4972caff780be5b0f5ac9f25d5e9ab8562e5c6e81a5b3567f793a7f35d64ad5828bba8bcd91d9d33d32b14e2c40bf5971b6244ad50b8ce263be909a7ffab42d93be9b83f5bcdda51a1b81ff446e579a868660a25a574d22ab78c140a7224a6afdef4592c1385c2ed874df341c3a4080a8538c14275e43ca1304269849139b683dc09a5fe984c88903c41f72614743e51a1a13b9e76261473cea52175b3847227cd71547ce874b6128aa5f1c4e496104ed249287c5f973875915030b5924388a66a847a8492eeb93f0d427c9025048c50ae944f6db31e378c1028424963ea07e13242930e024428eb7a9de6133e1bb61e4641b4e5071d542d8c826cf7bfca6fc74a3a18c50d754ac66e9bbc0f8c82683cff0ff52f8a1b43740c4a72d4185f14c46cffb675d0089e5e94356bd67e0f7951486a5fdeafefa2f06a23364f2c99b3d18596e952c69496735112274992c4f619ed322e0a9fbe448a49f916a5540be9631277a2c9b628984d909d437f5222c9b5280669daa6f7bc3f089916858d9925b3e32951139e4539282d1bda93888b902cca97273a3d8ccc86a05814ebba9476f99c2b826051fc2a95c9a5834ce9794549b76ddad09dadade38ac26a455a8f1d74ffde8a82975b6d6dd69fbcb3a26c726dad2c6d3ce8ab2884f0ee37e14ae868545170dd1f5995d95414cf4bee6d5667897a5151104d5e1e6f94eeb97b8a72dc7e31b139040dd93545d973e26651dea699dc521453c5c914d95c521454aedcbe271174c61d45a96398bc1d39a1425c51142dae94d6b34addd486a224ef3f71b4fea7bb161425911bb4f7e87c9cd0278a2e26fa749e06553a9e284b12d2df44c37dfa74a2bc1e92c4242386248d72a27c9e5644f82053a9573751ca78ba66bf3d4aaeaa8992953c6de239a685a999284fd05e16a362a2ecff2f7aa2e78ff9258a6922a4fa38f968254b14ee75346c0e19edff4a942376b43ecf1e5f3b250a5b2164e8b127519e1c9b240949a218e46a5cfd90c3e670240a4ad5a9c8935ee68144b93609691e57d9e94714c2e5defb9b8e287775c70df935a21c747cbc5f2f1951c8db1b4fc9f6a064968b289650ed6f114b459452b34c2b72fa52aa4c44e9f37ab7a814fdda23228a333327bc54cfe81c3c44e1d6fa5f53e38d7ad0100599748998ddb93ce460218a15bacaba640eca5b244431b8490f51bfce398a83289a8a4832c78782285c7e98dcc40e371906a258ea35b5a6fa0d9f21204ae284675e174f1613fc43e1459b926ded414608faa11076c36587fc1cc2a90f65ad50e222339bebcf8792e6add166be1f25bf87b2e5c49f38b77722d243419bc71c26c4dc9c4279288451edf8fa59dd3f7828c7cc25a3b9e90e458b305a4470ffc8713b14ed7e339ae4dbcdbd0e85382266984c7bf31b3a947653ce3b26cd19f51c8a79f5232e9743316445be516294693a0e45978f1a3bd2a49c150ee5d2a963ca37c6ffdf50cc596f2547c88734cd08b58ef788881b645f2885652cff51db6e78a1206312399c9cac31ee7376a30be78edf525ae5a35d03c20d2e9444bd77d0a63ce41c3b32b6d08d2d74b9a674762ca99ab171430b8810cfab27d3fc290bc49831710f2f319fa4bffa347a030be54ca3eaf3dd154a42c4bcc8ad9a15ca5a1faa1183da6e54a118bb753f414c6e50a1a0f2db1b42e4d48be4c6148aa5496d124ac7e6e07a8e3adc904251c495cc5144c275d26a196e44a15c42a4d451b6de1a8238861b50286592fde9db4f87db9e50cea0646ceb56f373957ce186134adaffa24d856a7dfadc6842418746126966936e30a174d5a321df664fcff124c38d259443b756ad7eee8612ca39caaf7d904105861b4928c8d87bed21f462b88184e28790b09b3d7df2fc8d23946377e4911841b4c67a037682117408e28611ca9a51dfdfb3b8c9b06e14a1149ecc35948a521e4b48871b4428eee78c7952bfe6321c438daacb1181619464c490f93f88ad68100161946c924d1049080f5e3a608800fa64e76c3da5616014b329ed75ed73ea6ce38508fca2789fe5b661a6436c17015f945d5e55ce3a4424c723d08b729b70d35f1d23c08bb29c485254966997392d02bb28cfcc4a947cb2a93f480d11d045b1fe47dc446e79889f5bc3043388402e1c397ae5e2df13015cdce29fb16fa44188c02d18dbde9c5d7266feda88802d0a398b7cf52a99a284522dca23eb35b3356ec2e7694192d3289b3ab7c46751b40e5b9fdae2535976cc88802ccad9343c9dccc5a2782f233a849c8c002c8af7516c3d651c1b8d9c118157d835f7f2e961716e123cde766ab65dbf48045c5190bc9ac4874f6a45313d738f3e0fa9a74aac2855083d253b4f0e39b38a529777cc3d5913c57e55e063b15957b2a928d78b300de9b64b464521868c938966652a3d45310865a33a53e893a3290a263eb54f1231a2c84a51522e31a479d4ee0f795214237c349d5736c73b8da2ac9f3583b80889a268b5c1aa430f45d9b2344848971d7d501425286127b35bfab34f944f878a8e67503b29e489925575f8a07ba4264f270a1fa9326950cd8a3027cab133af4f5c7d9d7513e5bceb1bfbd3e690a389b2f5dfabaa690ff665a2a022c89da821e669eb60a23caa52644c86e910bd4431775d6998d012654f6df910e49528da6d456e917133274e8972081394cebc3e8962292543efac772c5f12e5fb181a84ed9128074dcd9ccdeb3be290289598d7c60fddddcc230a6aa1279a8cf86f6a4714db4255ed29b99def461474d3c4c8ce25a2938c2887fef8a5cc4514d3dfdff376883175461125d350fa1e4d836f661251aa789310474f882869fb24bbb447243f1da23442b8767ca62ed31ba2b45d91743e85289968d9d97dfdfc31210a329f2d7f826e8fc14114a389bd1d6d4d22491005a94977decc7520381342b6feab064441698876de6e1272fa434156f9c651b26264dc0fe58ca3944c267242b8fb504815274ea6e94d36f95052eac38608713de7710f65b7132d6ea5db3d643d143bcd043f551eca214e6e9d84f0df12c643c94de67ca414d99b3b944b86cea1ca3fe9903eaf244f1d4a673a4810f725eed34b8782e62c7a738cedcebd7328cf5a8527edae1cca9b71528b87f8ae228d43c1c243c78f7982ed46e1507c312124a6fa0d25312187d3cd1a7c5f3714e3c4d48fc9e531576d43418bea093f496a4655d9501a95b549c5c850b25f4341a793a0536e926ebb1a8a397ca4eef43f22cd692848c81fb63d086b75d150ec506a74b75567a467287a38954126e76042966628e629d9a3fe3fe9916519ca1372d7aceecd88243214735093444cb2fd161a43b94c4eaed690139e8ba1b82544fa2419d4ba06c350f6537fbe9fb7dd2f0443415675ebd7499e24ff426944c6dff18d170abbd93fafe613b9315d28cc7aa83df972a12454fcc907cf73b36ea1284a44d0f1a5168a7ffd917aafe46c2c0be5ec1054798e1c168a490813b2637a85a25f6a966e8dd1266985824892948acc5bbd477c5f0db22532a34239d4e790fd42080f4253288bd56e881b7672f848a11082d891d3c9e7f5289427c9187f93eedf8843a1ec5a6e3286ebfcb97e423974909326896821612714c434b54663dc9b503c3fa579993af9499950d2d13759949aec49b984c2a40f1e27e69c44664a287f765a3f534a4212a92494bc4fd72ff6fadc8484424c93cbbba3e7c9e808e5382ad3e8f18d103f46287aed88ee3939d375118a49f2d585dedd106d048850124af95cc889494bc3285ce668a7ffe9f4a6c228bbcbebe64fd1486530cae99fcb237e10fb1018658b5425e3bd360715e9f845418698aa79643c0fabc6c85146ebf9c20e21b58d7dcb9bb817458e249a311f325b991927f2babd3955fae474f0a2114d32bbddec8e5d14dd3c87a90d21cefcf9840e5d14334e3695f7ab2317e88d6936efa40329e8e01774e082bcd17424657e8ac8d842ebb845a22d1e1bdfa15e2172e3c8ae830e5b14e67388e1ec74c6c9cb3930848e5a94cf3594dcadf0493a8816869cc69877db6c7b9d6d9abb933b4137f3338be26a9498e366f3111f248bf27e129dddf4b3d0118b6276d77042453279a35503160531f133c6b98e7d59c72b8ab9bba972ea4496bd3a5c510ef164fd933bb3fca615e5fd2f91e3c8fb17ad5941b6ac9814c1be3d2ebe462d84aeafd95c1b5f4571d3241753dac11a6ba8a29cc4248fb964bd88a05ea1231585f3db58cb9be0fd2f2a4abb1927f1cc3a7beda7287e30e5a9426a8af2c556a528cdd56d12bfe65f1a2245c14fc9a844eb46519d96a68779d6243bed124296a43e89a23c22c8f591bf2314e52d2fd5c1c30e8a5227fd90476f9f285dfec73ddd1dfc64f34461a247f4bdbf34f964274a66e263f8f4f426f366a8d1c189bbea4ee36653d29a7b6a7f8610b7e5e94d79878e4d10c49d1075bab4646cf5e071a006b1269a38eaad88a0df4a9d08658297affad4b0b62c9370f61184dc858992f978f0ddd4fe67327589ff2567ffcc1a1d96c82e52a4893139b2758d6889481d42262d69117454a25c5e9bc9357d1938420725ca3926a13a9cb9a4f01831c44042c7244a1b49a6123ac393d49c240a22265a32f3d9a37644a29884c9983d4634899b3a205152c226be99dbdadf48c6561a6c8c81c69b3146d9073a1e51fee4353ae6cf92e3d50e471425a9f7c7c8fb2849ec6844b93e6af977690a397934664479ed5a2b248c32a0822b3678f0b7818e45eca12639d56bbb458612932b37e215ca418722ca95125deeded39108a2aa8433130d8dd51aafd638923409a5317623a26ced499b52d3f09b443b7834c658238d31b8ae4ad07188d265c4fbcd9c4e79a686505398db281502dba83f62ce4a673b11a21c1274c7ede4e918c432496bb44ac6abf3c4c40d79f3a8a8a4431025912468d2bf0dd0306b8c61d4c831468eb701fa319279721d2bf0001bc60665e8c0a0c1aee80844f135b8e95ed1b14d66d450a30d4014c39af49ceb438a30331a6da4e163acf13b48c38c8e0d74fca19823a99724b2c90fa5d8ebce16dacc44471f0a314569f9505c4fef3946e8d41ff18e3d94f3c92ca6d3df9c909fefd04349ceb55ae59bd7d5df918742720fddb959b6466936e8c0431a1e51ebe64e443fa752dd0f2266e81d0af9c3c793e33966e246a5c30ea5f5a063af696bcda61d75387c27bd928d339275d0a13041860966d2c3935076cca174aa3225c8601dd388a1d02187e27f4c4a8d0c061e6220febff7cc664979e229788481c190c3e30b2af0f0824717dc13b149d926f32f079e223cb850543b530de77983fa881e5bf0d082d2c0230b6c786041cb2a53396bad8c8bd5b83cf14c22798cda35f0b84259d3cfe79f88b34ed50a851c6d84491f8baddb3caa50ce1141d3f40451a12c693ec49b182d35bd29146692f58ac849fe7d8e62c1430ac5cf692d841a8d47148a7e25acee268756f0804241ed7734488c1133a9c713ca5ebe2554c37959f07042e9b74bc96c59b2b51e6c42b145c6ce301d31aa3a0f26944644fee89e4bdcc62ca118ef45dc9a899450d2d89a3b7f699250bc8d94debbe9de348d84927c92e95f8277778a3d8e50126b53726635424908a5494dc6e85b393d8a50384d9dd3ae6a3ce04184831edf8eab700c03f131e7d2b954cebd2dc0210c15e008463a740826435685c6c06803c72fd217ba5c248bb7129bdbb61725a5730c727c933878514ce741fac577765108396a7fd9aaba284d180d7eba9994569b8bb2a9f967cc49cfa3dec7818b8238fd99c6fbba4549e3a9efce16e54d4a879e9cb1643aa15a943e6dd86c9ab373020e5a14ef730cd3fcd4e02bba9b45c9b4a42bf1349e2ccaed1263be451c993f6f093890b9b3c553a7f22e71c0a2904cd6e9567f0cafb79180e315252154c9a025f77d89da15e5d3ee210711a2e7a0dc56144fad936b44119fe75b1170b0a21c23627a6c844d323f52c0b18a82694b92eda3220825c221e05045b1c4a9667d1b254ac871a4a2b8f1d9438ac42c0e5494a486d1d9c3341ca728c693a7c74efa8b876ca628b56651a396bd1962689421022b4539b53ddd64e89b1cd4468a826ed00e22ef8e378a92b65c9a85a67ecc9e2c0e5114e26ae8776cc9041ca128c859bbd33d39384051b40edae7afd24f947f833ecdeea9b141e5896210694d4fccba13a5d0b2595e7af2468c878313690a1193129713fa0a706ca2f29c21284f9ab4d5d1811474f01d7068a294d157fc7b7f3c4f08195b6288713ae036aaac802313870313382e819628c6a89fd7c354899246cd3f2f75ea92744a94435e5dd1d8f1b6f33d8962be8ed0f85e133a842581d01dcd09b2d94894bc24d9bc6b87e8101285ac59d2e2a467da943ea224334bb0d17e19ea7d1c8e28fcc6f7583f214fbbd888e5347e7466949c630472563536735693229bd7a70f2f6993887c882da294b1c4272594aed586c0a18862a80832abb9c95ffb89288d7d7e493561e384d21d010722caf6aa9a515682eecf87288be88fe1e757e33bc5618872ce7963981215a224d994dcf48ea15d1d218a97e53596a74395781005cd7709bac2df35523804519220b37b8920e2084439d28e5abfdd0051d011e52641ad697811ff509ce0a9374813151470f8a194311e6d4b275e7787a30fa533b7aff0529327e0e043b14444194d991c03c71e0ae163f492fd61c3a187723e1965c39418471e0a326889e11f9259c0914c12825bbea98fe30e05d9d9f43a3fa162531c7628c9cd103dc78f4989ff5c01471dcafd13354428b5b7933a35e0a043319386d012d4ef8d1e6b0a38e65012d3f21d746ed80c3729f0010e3994cfe4d3c55b8cc9fc6aa600471cf00638e0508827573c2cdd5b5d52061c6f28e87c95d09c565428251c6e289a0e12545644f89e0f8e3614929e989a44bdad4c0e071b4a918459773ef1d1e521634b71acc130df7415f96da6cc81430d79f0fafc8dafbd91b1956ce0488349c664b8968c2d1c68289c47ac7111961965b800c719d80cb623b3bf946f86f2a4d18c24c3a8f435d90047194afa9ee4345637c9242243e1a4085f1162196e1a4349d43fa5d98510713e62c04d2cdd2ecc3e7c378edd66d3e008434989bc2459ebd3ec1c188a41262144c5bc38be5062180e7078a178bd5173dee45a7aa60b251336271efbc424b972a17c2263b6971eb750901d4cd86a66add8d43a0e2d14f4c93cf518268ce93f0bc551aeafa9f3cb24a562a120b433662a5da5d64f1c572878ce990ee623e3ac638582d4b2d5ef72cf90af423999aaba7e4c3a6962a8f0a89a9698f2e0984221a91919f245676f7738a4500e5225c63e21bbb7c41185237aaa45ba332d8bd7a4627f0f7040a11c44828c7393b473869f50489741f96c4c2325c49c5074096a3478b6fdf7ab09052b15fd9e31e34126389850909e43bd4f3e2da11c224ddc3053e250423987741d722879752d8e24149212aaefe7314868547dd0133eee2314931e37f364fa3a723642216586fe20b73ad4af388a50ec1cc66af3b35a5f868308e53989fe7baaac4c88dc184631839e519abfbf7356710a37845188a974cf9a860a1531c128db96acb27d9d2fa54368bc0a6e00a3d4721e9ffd4bfd07911bbf28f5c6081ef4b68650fa0bdcf0453979d0dcd3d350a65e9425bce8ccb7ace8cf8b52e74eefc174d22e0ac23ee6a0ee41a68b424ced398da033b37e2e0a3a2d463b4352c91d17a5ca74daea47a7b4cdb945496ab69a9c7d4e9be7b6287a6eeeac63a5dad4a616c5acdc3c1bbb6951483275eaa0c9d486cd6fcca234775ae2368409a665591426c947f975bd7b7363519074a54c6bae4fd781453169ed1cb74ea8b5385f51d8205ce28d2e35d2465714731435a544e738fa6945d16cf3a585cce872b3e2186fd4487af356515c116ba3c2a48a422eb3d4efd2b671572a3a21e142dad7858a624f5ceb3aa1ffc3e814e58f70c2c4ecc414a5fb70ea8389d7cc7679e146290aea7f669d9b365f45a428cce6883326836cb66714a5b6897972892039ff2e8a82708db921323a44be43b1c70e59a9a50745d9e34679a7dafab13f51ce253bfc52d2881fd313a5f1c8f93e9c30099eeb4479be5227648927b244cc38515ef5c9a3426f44df7d13c5709d12c36d242d9d9d0ea2704313e5798faab339b991899248dbc9e9e42fc20d4c94a3969e95c4dca971e31265d1f5b36dd9b2fdd912450f26e759dadfa844692b4492d31f53c60629513ea1b1ba7bfdc13d796312c55d7bd13f26ffbfaf23dc904471efb3e8bdd2d5ee2a156e44a2a07f6caf242471ea2748e023fa99abdaf06e38a2a46183868ae65c234aa1846be81699bbc18894f69db990a6faf28d45701e6a275f21dfd35ed54e965ef986224a8c44941876831b8828c785879c0f9ba64575e310254daf29ac6f4b686acd10c51c52bcfb568da58cb810a6068ff712a2a875e7b5217a1d4449948ceb3f273c7dce53100521527b437573732e0c44c93d7ad5e6c60f1f4b7703109aaacf7fe30f2506e2e0861fcae971a55746dd87a2c9f44d6f69be39c8eb0337f8b046b8b18792045572629639e149ee6998618331d028830c36cad8811e523237c9d40d9e79f464e468438dcd43e93aede659d1731833c6683c943fe57c4fb6576d63947195a34370e30e6cd98911f1398937ecc0577858b5a6565b8d7d6585ce4fb72e6974d0e8410e5275286ea9f8b4a9a195234287e298ea3f493f1efb437328e418fe9b1ccab6b964460f1e6763c72c0e2529bad6456efb88689bc1a13437aa73c7999c413d666f28fa68896da5b953c868e68682a9cdeebcbb897964ccda50fceecde82933ba556dc686929c9cd91aca31e4ac6e4f93a9a1fc1f4f288f27374b43419e6f9c244fb3c4d46ea0a19827cee9e7cce919ca13e45544c722cc46cfcc50acda8cfde2316f67c96e94a1983e8d4ca339758ad0da0d3294c389e6fb9c105472888da17c6283e74ebf9fdd1043715427ea8c58175d8b8c1b6128e498f24f9d68a481c61839cac82276821b60288bd4036cf0e02b80860dcac8818009e4a8c09be1868d36ce58c0040490e3d768830df3698c055000001f5715a8aac019b107f4d1dafe5e8f07103170c528071c3672b4b1460102d003af800200c0468e3652408000bc0d4c1b3640c398810001e458e34f1b288d35d44800097294a186514000da0080000000a1f16ce4f81d5400030be0000072e47031082000c3864162142000860d83d258438c030060000d48401a6a8c818363d8c831c6180b0040001870802b830d1d9481da18638c044820093d5003090790c011d438e39cf16718400246b832d858838c1c668c3146022450841b630c044880086c7c8e33fe0c037c0ca31872b01ddda5f487241546f9f7439f48224c9c8f05a3b4693532564c7650b233fe8c3de3cf0046e1377b5052eb811ae78c3f43cff833f2e31757061b669091c38c3146023e7c7165b081d238a38d31c648c0472f8a9ab34f4ca23f23ce2363d9aa0dab337e8d317450061a0395715e8d1c5d6ca051c6e540a30c36cc28a30d1f638d37a38ce2453128eddb318a67a8d211dcc00946d0d181147458e26317c69f9841578cd4b02ecaa6b494a5e610c6e40ec1472e78172f2fafb5d74eabb3f2972443e726b963e2a2b4616467da399911823e6e51f6c9b32525870c348c192778944609d816c5121db36869994ff7d7a28b7955af3bc961631ed3a29833dd49f37c612af62cca1b24077dda22d88e289541468ecea1461a63900f5994efd2d74568927e2d2afd884516176b5722afeffbdde3213a58143c795755ee5a9a675f51ccf3b5516eb6d695b9a2983efe49f6edc6994ac6d618080d6386b123c2472b8af174c65bf3b9eac7c0624539c8cc6cf52082dcbe3e565188b9640c16e39b3d44398c1975830f551494f2bdd538ff4845f1c5c773f21da981e775d01fa82889ac1fe7455bdbf4e84cf0718a62a8d8dc5d6fe618363bdf820f5394bd4f899c2072646c8e5294ab675486904da4cb467a33f083146551539b4d6f928f519473d27d5635f9d5639ea2e02cb28964bae15be32314a5c89921537f5216280a3eeaaaba1e33940e6123c7186994400c31de0cf732aa3e3ed19a85bac8d85b6b6b99d79c0e4ff93879d5c1631967a481460fcc48e304ff1d60f344f1c7d55e5e44e6ac3f3a5194d87d523d74be36ffe044b1d327c4f02673305d7d6ca2ac65123b7ba51f9a28879fe7db7c42992854d54526044992236b576deed01bba57f48189c2be2899e7645077f18a21860f3e2e5138a5c397c7ddce0d131c9c34cc604be041c8646a9eddaec1861a9530d5f5694eda84849950a2a4f2f79374cb842c419328c868fa8c04d33907591225ed48717ae39829cf22619c207c247c08a15f32b63e20d1b876a7e496d85d9d564bfc9d901b46c35a8d33cec7234adb279310f921e786b0e1c31105d3ba31c77f9124845023ec0c21d3a9c91c4694d4dd640ff1219cf6f563118d4d3297d312fb3e14510c3246489b9912ff438940442292da6ac32b6d4b8ff5ffbd8bae107f1ca2fc2753c7641d43143b4bb4d53d717a1e2b4431b47ba368fd48699e0f4294a4cc060df7a9053e0651ce6c9a66af564b659020ca9bf39921e7d31a9f1f8128c765ca09f771669b06c4c71f0a623d94fae8f9d3c522632b071b3c7834728d6703046f83359e04676b3c1b65c8e0c30fc5d0216f852991333efac007bd7773b469c8d8aa3d2867f51bd207b95a42157ce8e1230f25ff1a7b91928312268207336ec4ec26898d8a786a2aa563d4901c84c8c71dca51c29a86116f3139fc6187a2ea6d6bf68cab92e0471d6ad55a512f4f5b395da6218ed894a10f3a3422a93a3dae2d6564c2f8e65e73db0dad081f7328c43c217bf698ff904321a9c412951e44d01cff8843e94aed830a19ef83287dc0c150d58de52963c918f38693a6aed0968b587babe1c30de51c944c6a1f4b7af4b90de5a4574d49562da53e3694ac4359a7d0d512a4670da5afd2412999c34612b61ad06b21b369289abed788fad9bfe93ed0f07186f2462aa14aa699ade43ecc808c89f1abbd759a3258dfaad9766676d25d92b2f598de7f90a1142a3dbbc45f7b0e570a1f632827f1e1e488a473ae4a0cc513a9c49d28dd47180aca22fc474a157d1ffd0043c1456ee88728faf84279bb7cee23e731a57d32b6d028e38c03c187178a953ff236728c813e051f5d30c9e8985482cd5d646c3d0e0c1b639861cc8c0f2e9422beaca989570b3eb650ce5affea611992ced142e136a34d921d42a39e4af09185f26a45dc1c6212195b58592cf8c042292fb2e94e7ae6ba5d64461a1fa0c3c715f8081f56b84fced429a1f05105b7bf8403b30231c4c8f1418582eed0acebda1054842ff89842414b628841eb35ed8d52e023adcc668dc997c647140aeabc376c27af091f502875fc8961840ee7aaa3271483483a6fe891f97042612bcdc36a849a50180dd3900da1fb44fec184b2e479bfcda59eb191fb5842c9d55c9350993f9450d226ae1fb1c44b927e120ab6ba39e536089d4f0809e548b761c4feb68a6c3f8e5050dbb6993ded9ad8e4c3088c66cbb04f6d7e14a1a0f523368896edd92602fdc407114aff2fbaa744b4c4581a46795bb5652cdfc4e4d9431805d93a1f53deb36cc8c128c7b4a69b7a3ee4371518854fc276ed639c93d4fda26cb9eb7daf79f8a26c13fc2509ff7b51cc1c4cf7e0456945627617e5d249e910c44d8738395d946283f639dd3517c5338d410f5c143e95a69a48ced324bf45713b8a8889253d9e981eb62806a5a324e1aa7ba34c1eb5280819227f0ca225c8b0072d4ae119723627e9dee979cca2301357cfafaf43c4d44316c530d95427fdb24194dc231665dd51b133e29d1a7758948466b72ad1f21585682392ee0d56bec1f3704539cbe6f491971b7c3766ad284c8c5632ab88a0d46f66ac280617d39dab378e8e6c15c54f9bfec48aad8ab24419db9b7f5351cebca123b5326f574545418968d9c136b7e6bd4e511e5396412c440f53381a43d2b759bdd941a5c84ecdee3c4554c37990497b6e73abc9d8da8107294a9aed45b6388aa2276bd75ad1431465917d61265a92b135865dc70a46d061c782ab367884a2a0e7ebed3555e9a022288ab92bcd3387d63e51d8d1f5214a10f144d963923b19f3dd89f2d6aaf5e69fc6e42f27ca793c7604d3e8260a5123ab281511ed9cd24499b6e1a4954a3c3251f4bcd2161af355b685e78189828e49261193d07eb23c9728c94f3192d227478e901cbd050f4b1443bafbac91ffdd6fa6c1a312c53631d9124ce4e62494286a943ba521b3314acc4da29046ee6bc367f7a49d8724d2d0736d76d946a2783d9aca4a072142a29c2e36a64e4abaa90f1e3ee25d13919eaa537c506a20031e8e2809eb09fa4c7754174f1a8f469494e40f153ac830a2a0737f28eb95902ee6333c16b12ef05004aec0231188f038c4b22e31b33c535286289cb80f15273fa8896b218a7b327cd2e499e447888cad5f6351e04188d2a4fdd5fcbca32e52973c0651ea2fd1e7deba3a121744f93c42f8d872200aea46cae8ba1c1d3d0344b964ccf91f4aba1a6d3e78f8a1d8373a8fad6cb995f43cfa50ce392893d9ea1ec284cdf850c84168b68a51923699720fe5ec22742e6d3e79d3f5d043a55deb33db4ffae41182471e8ad6ddfb396709e5ad93b115030f3cf0dd216dc9a665261662d9a1782f7a5bc3485b931c8f3a9493d050b2e4860ea5ed1c244e4c2268f46c0ea748ca1ce1d400c8c1430ec591ff6cdb2e71061e7128897bb816d712f14c8be8018762ba13e1b3651ebf51f4784331f665a4bee98a921c0f3794e4892472e977108fe0d186724cb2ce50b5fe1a71f4604359f44cba58a4d750feccd94fdb720f35144ef6ec94568f471aca9b4b359b2ccd9c51f64043b16eec3aa8c65133efa1c719ca1e73278f6fcfa896d1c30ca590dbfd575122083cca50bed8b89517094a5c460ed68007194a7274c964e9e135d4308f311494b687a89e27e524a9218b50a824ae0622692c128845e28030180ec469a78701f31308000038228c8682c168a06992f201140003492218362a2822241c10141c1e1a22120b83c17028100a85c1803020140c0402e1b04defac0f40a9a01f865815f2df8f9132ec9a4e7c45842a9d367cafaeb3c90bdb573869bec39f0e0271ac5e95a4becd9a1767d3c1fcffd1fcfb368002da7d0154f37368894c17e1e0d38571a8785c2883e7fa066ccd2f13f20f5f18fc82babf04bb9ffe573d58f69e3526f7b118ac8f26f3c751aa5d099966a32c3258d7ad2fc8da566035aa7f981f28f46542d8ef62c910b509356ec5ac4a81dcd64d5f19b43fa2a8929c944e31e7ee4b771ba6dc12c673c7883022a94ca02fa034dce32e76d84dedc34ffc92b4fa439c81a62f830cce980a02ea56697b7bdc335288fbdc823b395c3042d5c31ff105115dbb096a4594c46dd298ad2177184c4839d0f5afa5f83681f7f581e83cd917da019c01adf5208384321c164aa32a6f4ab7e801fe667eacb6f6a6b4a02b565079055ce46a99d074e168bca04456c384d19aef20a5ccf570eb03e756132c37b7a725d6660c51299495448a3bfa112743def697da199f44dfa11911e230d52b5fc7367d58a4a96140f711cf2d9bf61f05153528ed3d253b50da219e91881ad6aec3e2bb936529227d1c6d580ddb0c0f1c284889f1d8364a77f6143993d5f5e0659d533a90c8e9cb5d8d9a0543d8669a145966ff4a2c584fa1e1d648e8da520d8e7dd76d9c8d1a89000e0db7f5c8418441bf941ace3ebacb88e8e825e3b0c4740b19b3a137ccc9e08dd4e7222fad61fd3fec18524c44d00500883fd1ba044a982be7ee4734e3693761e6ae8b515cba146fd87225bdead0c2bb54f9ec256478ae2973eeab34a660df0feb2ec0614d57911c1ad4cd125b127f40f27d7f5b6ca5ac540d886b227050e64bc50a006dce22d8dee6f64c50333f33da7068499c12fbe0ac5d76d339689f50dde3c674ebcab985e90304b2d194a0c682f9140cfbca6a0bfa5432f96cc7fb66cd7bfaa84b1ba13d086f20e7bc8416bd01c342b3a4b184cfb93ff85a79b8135640d3c1a5907e259836b63434d21d36115c33e6e10df209dd1fe30520f664b65d0c0918e01370e19da3a72add89653b28663860ca2ce7cbe72af59e460ba1d1a244120f5e977290cd8522a98a2a401b170a12bfeb0de0bbea128a53cc03da8c191569ccfd82562b8c2a6a52fff20c7b0aa604a4f9f195f17a5d3fad581e0cafcfef43bdebb0ae6792325cf9c46c6820cad8ec9a4bda07a94c5e9c6637a2edd891543cede49d9de3a75c6306d8e57176d4d4c346d964524865f33069c22375e1735709030f4a80362cd5b2849b29cea9d3a9730b9167795cf17da83271358618cdfca307829d3a321ce42119b3aa2be752fefdae278f180778ca66349751aef60382fb714737fe2fba9884c945876f675e516d7a157cee9093e8213c057495e16fd49582a373de85f01af3c7ae779f96d94a9e8c4886ac2ed8efeac17f43a5497449b4d790b806a18241e9143ec1fa5f232bd0dc4da423eb821a9849a66691a5a0115a2b90019b57d11bf49036ff9935cdf9e7ecadd9613296ace0f43b077f3240cc010aace39ceb4a2c97e8c84111563b6e79e32ae98415e76a5d37178e673cb0b75633ac33b0061379803004b104eb55c8c5a7ef2de8324a9070dcfe73b5b21544a6c51e11e2c46f115b92e31063ddc2ac9ac79f21ae64aac664929e0e6449423d6da371fdd29960a221b63185fadb7c662965eef9ed0c560844006dffc9866b29a4c4b65240974e0e8181aa1e1e1090ef576e2a7d92c62e6958ab308d9b158245f1b27d03f17b68fbba16bf3f5c6df79387ce4c8dfc7157e6e0ae93450e15b813f78b09bbf947fde627120f1b200363536dd96d402e32323c8dfd1c09bd9ecff99b4699f7996e40063d47956fcce4c95a0494ee22b1f6a5894165eae66052d7bed3caabf2c879bddec2298b4f4dfdf8efd8949a7047450cbf651cfe5e6e051c5dbd1d00c3b1699a48e0bd9cd80f1d000a172a8737412bcb2a4acadf2700b2f3df64479b3dbd333e4916600649fcb91c477720968733047c39f1a273aedc71599014bad6296bb50754bc895a1a459d942924664a99291f7c24ddf83fd2ee93adb54412bc06aa2216d011e6776ef52ce5d49540cfc736410600893ecba510979a74c32a5c01f62a1e0ca5785eb78947f5561f87d964892a64966b9f8d2151bfb1be19a11826fef43d196d8781207631bf9ef7ba6d5ac011f16346e47ef770ee1eee002c477c9f79caa4aad7ee225e0756c7f3d0eb05ca56bf86232ee6cb261eaaeb853b1501ef4199f5ca12a3e5be5d0b3937e740a6e440b3d8eeae9877c41a5fadb672556b5a8ffb85ca9c4dad2667a23274ed0754f59b3fe93fd200e71cf9062e191e9d6544944ecd5762f932376ab350edae1d767e7bc4ac43bc10f20bf751f85ebcd03a471dde9b47a7acf611411a9cbe29a9e739af560656e0cc81c0d9b575509253044eeb39a0b78e7ed75d91b813f12721961cd5fa910c57a8645d40f505eb8ee328caafc0822f3b17812d290de611549beab9408481f2176221a4a7485653107fbe5964f97b37c49799946c393370558ff1511678fa3d545ee604b4102a2ff9d2fa7d67a241bba2f50168e19d5ae33435b4c2489c71fa66f9fb4fe7f131eec05591fdedce8c4f0a84717ac6506babed27c915ccb5cfa331e7f5605deadf2372aa60f0fd5e9e7be2d93c035f83f4d636e15820397009f36cf7faa42dd62afa48c0d57e8fbde5ac20429fc1717dec1a3242ed76d495c22f643526f6c387e325dc59e8b8acc478a068b8a315a756d98bd371135921ddd870c29d44c53289a5952cf4943f97191c047c0d891690da29501ec4aded5879c32841191388a4544185cd6c87453de28152709a0cbd659ae17e2802519ca35efeb0b08addbc3a1dcd8c97f96de1b4b1a1433477704630a3b264fb177af952f43ebad4ece29339cdde9f316fd21d736f3f2e13c017cd23948bac849d87100f62617364b75486c0f259450ead543dc3f4bfc2f2480c1e1950e73b889597c21d52a4ddf0e2923841785762cec61d20423ba476812e2c3d0eac00c51250d0c1e81b11d010c4c8a07d7532459900f602b0a24b0e74ad91c5beeb94c3ac3f98b79b818d8689f9dc735963fa1b2efa5e1b2bd327420c55b589c1f6c440d9b5bf8850214211b3966286c6b67721a77e02b220b43c6c0b65b926a2b41ddf53bca345559f926a751bf03895cbafcc1e23cc7d3b59634ee35ef5e0e30e09c1002f92444eb7dbd202ae6425cd3e67fc51118e54a2f7072008a162983a1dd804085ab6870b626d121a81553003708e6db86146a1cf7d4d3b7719e733a1d71d2ae82f15b32fede9a96f6f85d887e7362afaedd4fb2cd74283bbf4cb9b80624717d146f69de93cbd02dcaa56448b516f04455e079e04ba24d0358a0d1747e25d0cf8c5f57e2efa98e2f1be52af0e7dbc48b813f082b1140fa131faf0d0c34cc89026f504cdd619c4946cbbabc400983d73aff9f35ca296423df2d34f415bc7e6365abb8519f1f6c6c9f7b018eb2612abd827c96d1c14e27ef0e0422b6bf3823b66f68c10139a7692366522a999f9ecee27cec29be7a0358fbf6671dd9498cbf6c50f7a00b696447c51293f8012d24cfcb5b49937ff9a61f169c837b68848404b47575957bdf3c12373ce7035eaf15817490b2b23fbe8bfd6f865a9d70adf9372a3bcb3205a311f1868a974144059d936d374cb14d2b2024bc1fbe88cf48cf023d0def31c6abf80614d601b635c379d9f8739700e1e8b2796ce2e023f790cfca2ff0c742cae0f24a47d31782cb229fb98fd0a3e4106ec9d0f711c8f8aea7226c259792e8201da1aede1e909b63ee7fda0a4a71f0b36556d3a35431893bb48f3e3dee81db5256133d6d205eac2a799d94c6a4763fb7434512d899cfb479c2e318b947ee81fade83481fc504c0058c9d2593d1362f28ffe0d27705272236316735bc3f930b4d9a99b5e23ddd52c61f6917ed347ecb59b1910b39f4cbc22797c12a3fb572314dca1670d8d701d653ca4278d8a27e59bc2aff298882aeced3c9508f402f7a9ee9b77b3c0eb9ba2a321679234453429a567f6a1b7a0de0dd0487712a53f1b499a756a6849a5c1d7fd8c2c2e8e1888e0ddfaf2a2b6cf8d2fb856db04eb4e4acc40e4d87aeebf8e08ddbf2445c69d79b04ae2d4c0eb25ec1ddb8c9fe90616aaace858acaf8686adadbc81e0f3ac2314e4bbf5fdbb97d0da4d8b4eda5f951dd553bad64a19f754004c49f8a074b0f90d9d9a3451d11c4cfbfa5b0084edda65aaeae2cb5010904ab0f46182642a6c421b558f6ddc63c6e902cee0caf72bf2a503e286311e1cdc8e21ee2e685ad8fab9185857dad90e3ed9465db49b3bc5689a0dbe0bf801be58e0f3e8f7299e1572499f1a06ee2f3384851e9e6ec999dbab705882a1883e18fe05c56b992a6d8a220fa7144b4cd620a77cc03f201a28fac0cb4c8fd5790e9921d32bc4c3e1cf662965d81bd0d47e5852c9d27452a208057979a28dd358b9f8c6c2c64aa5f196608782286086fc638e5c1a255085bff3b971b93ce87f3a3e9ec85c2f6efe1e1659b20f8c32bd8e53a94cb1e97ea6a6484e7117411b5465cceaba0033d1bde642216db596e89889be57c2b1cefa6aa74f23ba04c2dc2c1683806bb6b8c140a52151cfdb98c965b2808cbacba3d9f9248b684ea4740bd9ec24792527998d31c61f84072dd846f3be0e895baa3f89506d6edb19038b726059c136de7bbaa11808e8589962131aee81674b47a6e309d396c0aa7a4256896c76e14947e5cdbd81473fa5a907d2a49252fdb210193285b4e9a17b7c4565aec10a2c3b0a143763eb8a6cdec235448fd9ddcaa716ac794dac9ac540512768553d9514fd3b3c24a53579014166b7bf265bb7e13f79453b71f4339ecd48e8685b9146ca24a21380e7655102dcb8147f60b483ce8914354964df71213fb4495269a432ec2f73242de2f7f245335814d23e8012b568700d46728d000161a8d6c80a3670d18ae3d7c2a98124e4628b1a36346a228fa7c9dd43404a2ed15c4038902649229b2e5f1772ce96a3cf141c153cd184043d7e7fcd4d17133e67af4c631841699e1d0dff123252d67f6586b386b307cf842a0e125a00f9063a9abe0e9d201440974fc4cfcf12829e444f2027b1c5735de713cb8af5696b40949b5e265319496f61039abc62411d0b5112d0946bc4e324ffe423739a969f3a83808da538f4ffd05d8c37d747b104d80dcd508bff88575db324bb96752da9c13961710abaef7ae5e29e76cab698b358d61e2fd995886984d40208a2ad3d69553eae2e9aab1989a32751b9d4ec645d89407d801b41aa1148054684e89264775564b996071964870a52c4ab2d8d64158ebd3fc948b0770d2b3e3c1d24051e39d9a8ce88df9dad46b5228a3eb1a5d4d16b2bcda1e9daab28797e12280306f8c8544f961651dd8e80260324df1fa68a6862af3fed9014538d3e28e5e1f039285899f396143076a46112e6cb0b89028638efb8e3a3c9062570c355f56e35dc6f2d4e98468b067964fc9a11e182b1796da16a41287a70ee27c1ba54002e4c0a792861b1ddbdaf61ce40d8c340512ff4a642c29e157c03077d8ddc8da54c46a28ce44a923732c44b32377ab3b1123de07c0638840871c7cdac663521f43d420484c8d14f7c972985271bc59712f17c2fbd401e242ee0c7095021328365c707e8ecffc630843d8160d71b934e6503acdd4cd08ced11b5dcf2d08137d1c0113d36b3636aceb8ffcde8d1f44d519691385f33b282750771da65788ce759d31ca158d85920f1e33af5232b90ede49caef50bca28430ee310a46ea81a6d58cba45c62a10cc8cca181ded9093db489f5e0f8d626c74a10b499b79092f58a2be97d76de018e1d961d64ebd2348dd396762c3f99c7a9cfa76420f71b79ee8a1e4dfbe7558bb86180593c525df26213b739a919d99d19266eb2fc6a4fe4a88a1d8dabe19b82fd6c2043d493db5d911fdb6ee06305a1c8141bc601a04258ae498477d130550b3510dbfcdb1408a52a9bc9876a25cd47bb4a50c0338cb91d9d5496fbe23b5dba137d21373586024ec55e6089bb0d1e7c772fb564f25d65cfb59b6d1761aeb47e67057890ae5838a2d82b4e80c20cee515065e0a5c0ff014a9e569772e68f58899a114420971967964382aeba7c58d7ecf33e4d8d894b3d326d3718284381cf29e0ff6b5e7370ea3e1e5c02584cffd6098a427c1bc406f4b87436845017cabbdb4d6038b89fc1984fc0cd95d98afeb46c817471b62cdb7857890c2968e34de6f377bac138fbae7adafd434d67dc792bc0f15914f12e4c3b2bd98569b486094a07a51a453ac8e0bbd2556cfc707a57c5f9eac500e16e1f89343e2bd3ce98042df87feea31fe3053b4307b4478d6dd30074366af05fa1e1212256bbb049e5c72115f9b7a8d5d5b052b4cddf5aa02fd6f8b7eac483452ad0b717c3d2ae683b313421ba5c767fafd53570a325c4020141f81065ef61268dda35e727a3a284506e15ad958270cb42cc4e326f61301a2a82b5696462e62169d58296a4c088bf1465a48a268201e791a88082dc75fd1f10bd4905eb0a8d77127bdd88070d6c69ca8df2470d7e04d2e37a047b4345a93139efd0f72d66c64d5f7985d90a62bd406ff778b0d1d6a330011dfeb55a14dc0aa2a51205638a7c8599e913ef4afc6986c40923201a5def91295c94985e3eb77d64311c97318a84483118ce2090a9dac25843c08529965632e0302634ab7906e46d2db4002197154c95c7d97946048fa887b7ef2ae3e14e9dec7a2552a57ad95a3fa8d773b192262e6136a3df5aa3bf8fb2b42a9d815fbd42a47e6b443a88d7512ad984a7975000533f7efca0dea7316afdc9c9ecd74183fb3a1966c5c478dc4907bd441790fbebea82159013780508cf5d523972882903dd7a893cb8e77492c64c89d9e7858637384b3c8c2dba298a77441c13b0d1d73f46fb72f32f3adf1766782c9c424fa990f93129642e4fc9ff25e04185459bfa12f0197d346bd079f3516d5ccfa37df9523edbc350c0b93835cc24275b7d8d704e244594bd3ce6d2af58211c4e0f25aeb7a669af938e7cbce395a8868d771fe91d41e1d144123d0f149511582e141c10501bb7a694d95b81c655338934f1a5febca2aae60d96707334134a0f051fb06b5b2c22b23096199a4606be514cce6a1e82e4d589aa4f876d7b7ceb3b386684c9fbbbe1995973f7ab54389a087c1617a9443d144dff1bda6c2e9f6db64e9ca672523fba4745a54d88c854bc256935e5e0ece66bde507a65862fd3bf9059458268492ada6545cdf53c5e84c169784b21a01a4b2273d79036d44b13ed5a1e8a439625b3245e4be58a5919bfde7f5aeb76a012ea9fac83cb08d1bf444fee572a39127d3306630437b986d594581278e34fbc427fff2dd3069d294a58b4b86e10fbf6d6dca43c13399247ea6708b54bb48799533ccfce2ef1b46d08dce79cf127c23d06b2e5c41cc6378d2b187afc36de1bd0fa8b9022da88b732ca39a118c0609ad68ce0315f544fe633e802368a28bb7a5c31937af3669174b23a16ac092034ae052db9c4c3894f08929d9d5a4f951a2d3c042b6dd11f550e5e726fb806d7a61e99528c158a58e32d834a181def489bc37f00e2733d547fc08cda12051006c77b0a2627b0a55b93ddc9003eaca6296e57a3b7d74bfd2308d835637187969c87d1a22969e41ed629a114091e0dd82397c0c0f0098f1a411252e70871c2a3ba274c0df305e43b21af1e6cadc78ca0df3eb32e6cae0b1d113cc50efc3d53c65125f14e1b475918c65dd4f8e271a47c5d3c2cb452b8ae5d508cd80fcb6820b18419115c774233400f8fe410fbfdcdd7c62d16bb91264601cff45fadaeb0a49e9697b75f5c9bc2a7a3019415eda76e67d712079bfce8189f918db213de05504d2b08bb6a7e9f4643f12f4ef66201393019c79df380d3ab0eff405d3ab8ec7aa5229976b4c912f2ace265a86c8bfa4ba7093ac9bd3c5d2bd04b4a1700b0b3a19c1b07d54a8566fd4fd5ef5e18c8d808cfe93d29393bf476d1ed5400462d1f0ef34276b85b1908a1c7c7b403bccbb9a5702e8976e8f2228f235b7b14002abc1b0eeb9e8ed9e0675e87ef4b9073291af44ecc8387b86d6cc5acd2981a7640adaeffe5144c94e8ffd33669fa90675f71785074cf1c0075a155e660d0c2d83ee9296d4657906e46c805ee54551b6ab5a6985e833ef3fd82a143505311125de4a885ae64926fdea61cd99d4b242ae26ba063e6a71cca9da6d2d95189e3bf885fe01a4ce13c19949f91786c0138e07932c25f7213a4e847e4f02b7a13caebdb7f133b5a67618ae10a4c87114184cd198c7035affcb08ffa78bffb6c7ff9639ff5af4cf1ce0dd6485824b1d282b6aa4e11a58cb1c67a8ab5f206101ff10ff0d22ee7952e311fb6136db8e53430f2a3c2a6850988af50173cdf47d2be89e44c9d6c935e2a65f705108be889fa63ae6b093f8ecbbbe40bb70824ecd8d51144287451542a82819b5d8677e7b9b48fd43bc2e6cf3031e1894b01b3744901d4cade23321ff92dcdd33beb337922f3239fd272ba133533d22dc15fc421679db49c2f5d640fc2be813dacbc48c3f613d1ceb0534047f7fd7728ac87754debb37a75ee2dc09d53f3d7d37a99f9f22e448bea8ad7bbc096ae6e977f3d2f77bd2b33bb2ee7f23665bd09c776cf9d9178e4bc08b7d44cbf9c68ee5129fcc4ebc4fb0aa2765d1afa610a932229ced27487ecd542c58ea27310e5e9ad47ff6705117d62ea21d891cc80c5c044b62117100ac5f0044e8d2953e3047aed3eab19914ef0b831e9a7bb35529548b26b01cb4540aecccadb429a7b0efe1f429eba0c80927de754759cd0c4d67f24709339c8d37ba12d5c2b5f347f2158810a81cb7727046a16782c5c34d7db2135077552ac948130974cd417428c13e9c5c3aeca02c970c159d45817605f6cd8b13bfa644b0f0708e440c316dfb6ce9904e365dab8f0e39c5fa1e69515684d2f8b7d4017ebb22e5a46ceadcc26e75e01020d15652da53ca490106d751ea68e0879bbbd43911b9c8eefadcaea005a72f6de39ed768a651b2b69b2f74d02ba2c945defe38b5ab8d3acefb474f50bbd9da4da712622102db1518f152434720f74919e8ed000b56c34a7aa3523fb3b31070b6d8e8e91ebace4ccc639d79e8e31a89a55c8d8ec9138fc19b8813f654f91e97234c23533656c09a97e2b8df4d21ce20e61aaf051965f417da0cd709155d424eb0bd93f2d6f9eb3abc611f738a58db55a9b5bb2ef731cb564bdf7f15613c2ca913c28349e8f1fb9bb4ee6dd20d903cebd02b9b8530dd908282e7155fcb30b9f0f8aee2ba56be3ea2f51f99bf0ef5ab871c140af2cd5b3041d29d4d0c6919b348b8cfe69e1b9a0dbd23887da0752ba1482ca7e232fe79d74e05f97f12c9742dee722369091142b2dfadd49c4c31669992edebd3db8e991c330ebd23db4cdfce2b551cdca08d061d97ace56c0d2fae80eb6f27b8222717d10d1ab8495fbc66b22cde1740a7a0bbe0fd1b449ac39abbe21d226d38670c50b01bac44490c9af9cef697214dceb1110f3a14579cb62430446a53688cf0c7f0009f7ed7598bc9cf4bdff54fe98682aeb2866124b6b33a22d37ee9a82808c0c5ff9c6d5361f917c8c20e338849b2ae32fe4692f012ff319a50fc9092fbbd252ac32804a08490cbf5fa76a7bea9edc1c14f20b12c57cce0a641d8dcc50670ff87c227b031232c266b0cb36684a0667bc1e1c667ade5a7e089ede81c7450d7f6752813b35366c516e299c742bc01d5c181609946af531191611af0f1bd54ae4d67b56fdd64adf15f06b082f310cbe366ea3575a7da1dc838511ea950a9cbfeef9f5ef2738b57e4c1f6f0bab3bd5e62349240b63381dd10ec6fa5fc8de2abb8a4a58660a7f28ece32c136698a7553d074c4e7d164681e63215f9cb75c5fb561f5b3b114ef965636d90f4e4441b7fd087d6d98cbd9f5c1e226862114353234e7aa9bea849fa448cfbd02e9e81b152fc63191874bbe0fe20c48e1cea50826d43afa2a72a96e9f24fa97a9d69925d554e663adf65a9fa24c4a2b1b67bc7e4b76421cd5ffe9e20105dbb302f20982c0957814506053b543233a2c7590d0480cdb0b3438e0b548520f0fc2711c22a1f767d354d8c38b425774275df6de4b407b91ff8b30dccc9c6db9b851fc660e1e21d0935836b4728cfe45f51c3da2c39898de35f0e5b4ecd0e0c24f98d9d6bf7a846d078e2e7c2ef0fd14d0e31bbb6e4554d6d9853caedf08a224240acdcc67a8c28a0062623befadc038060588ecf6de5af498784f5e0f8fd2bc5f6e6baef5831ffd27ab1dc35f09b0159ba74bc834c30c88e8ebe0476b38535e78cd6837a87471d1e9ac0b684b70a04c7f69ccb0acfd4ddb2a7e2613cfbb959fa87bd16461c6aadb01af448f8527b0bbbc6395a084aa6c1bd3c775d413dbfe95713d162017846aa7f05493869d34f0114c24833b46daea03705317963e662df4824ad7c4c6b46ecffaac0415f496639517c09261cc67fd5e098c341a8c2225ad411379a938cc0098fc30661e1bda1459c50036112b21a4a90afd0dc7ba085678d9c3118b0fa1197105672e8579866656240a2fc3a12ae3ba44ea1fe87a40c22d632402afd2eae70127748badf13f7b2d2c57856ee6e8389910e639806ed00e4341a76687c65207a6795855a6e13303c94c4bdf8caaf77404b3b8ad6c2d3e9a8a6789ae5393b1dd2b86fc7187f6aa6d6482078a4ccbbd09590f1cfed30e4529c12db2cd681ae2c748bb956132becabf5bd1edfb559ff8cf3947c23e61a11cf4ca7e82852ad4f1769993e5e6687e453125d5c2773da4f0fe6d9bd881f349fef305fde85b8d855bebe0b71b9eb7c790b07c92c428bdc9301a73b6dd6510f2ba408bfc0f3da9d1cac75ea1df87e923fe39c7dd623e339255f232e430cf601ffe425ba6b382b53510ac1ae9337dd25030dc118c89809e079278f779d2fef725ce94a5ffce5f304d17acd19f3ef69d0b0139b32c7b2634686e794d4351368542899f2d1fe8448c97141969dfd2d13fb32fb7a5ea84dc9e33e03e86b2523cae600732a910cc9491d299290c494eec4965deda04409f99617ed2c4feb644efa799cdb299fbed3738394c69277af037df9d763692c6cf9224d5d5456f3b78f4e990846d6559edc29edb010a1bdc9c9759e7c1ff63b1b68fa03767bdaa1f33a1cc2caacf3ad8db2d4a51856cd6640ac5b5daaed29e479768c95453219c3ac7b8d5a6497b35d41d2fb5279511ecfa62510099186fc97e2f93cddf93ecdf33a9df37ece3033bb26bf7812217dd3998e6b5d0d2e39711792d0325aa5698dbefddbb10b1dd0009d1f99dde87aaabc1156ca9cf193c3148556ba93a9dc071501b81ba5cb7863fea8c095bf7d97f5149957dc35f18bf03dd60a88bebd766ce8e1e6a0a050d2911e594838f9494c05b1455d0d06d308f1293fa52bd8d55fce8b5de98b676706bc5940fdec6ef54dde811ec4b042cda949ced3329fd4d581a2c120", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x4dcb50595177a3177648411a42aca0f54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb333f71360101c6556bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3fe54f28cc5d1fc584acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950133cc54b617fa6116175726180bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b8cd4c3513e0fa5761757261804acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe284acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252cbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x04000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} diff --git a/cumulus/parachains/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..29fa0c9cde79c0daecbce029c8fa377ba1ff6918 --- /dev/null +++ b/cumulus/parachains/chain-specs/people-westend.json @@ -0,0 +1,84 @@ +{ + "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", + "/dns/identity-westend.bootnodes.polkadotters.com/tcp/30532/p2p/12D3KooWKr9San6KTM7REJ95cBaDoiciGcWnW8TTftEJgxGF5Ehb", + "/dns/identity-westend.bootnodes.polkadotters.com/tcp/30534/wss/p2p/12D3KooWKr9San6KTM7REJ95cBaDoiciGcWnW8TTftEJgxGF5Ehb" + ], + "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": {} + } + } +} diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index 3393a7a46c1d55e88ba150b9d764dd96bc0ff80d..ebc9f822beb2c0069276876161158243501ab288 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -1,19 +1,21 @@ [package] name = "parachains-common" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -log = { version = "0.4.19", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -num-traits = { version = "0.2", default-features = false} -smallvec = "1.11.0" # Substrate frame-support = { path = "../../../substrate/frame/support", default-features = false } @@ -22,6 +24,7 @@ pallet-asset-tx-payment = { path = "../../../substrate/frame/transaction-payment pallet-assets = { path = "../../../substrate/frame/assets", 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 } sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura", default-features = false } sp-core = { path = "../../../substrate/primitives/core", default-features = false } sp-io = { path = "../../../substrate/primitives/io", default-features = false } @@ -29,29 +32,28 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", 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} -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} +pallet-xcm = { path = "../../../polkadot/xcm/pallet-xcm", default-features = false } +polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } +xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", 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 } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../primitives/utility", default-features = false } -parachain-info = { path = "../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../pallets/parachain-info", default-features = false } [dev-dependencies] -pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} +pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ + "codec/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-support/std", @@ -62,21 +64,22 @@ std = [ "pallet-authorship/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", - "rococo-runtime-constants/std", + "scale-info/std", "sp-consensus-aura/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "westend-runtime-constants/std", - "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", @@ -84,7 +87,9 @@ runtime-benchmarks = [ "pallet-assets/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 107cd5c687328bce1369c59cfa7d4fcbcdd377f3..6a990740f0f1930fc259726fd85e966ae48887a2 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::{ + Asset, AssetId, Fungibility, Fungibility::Fungible, Junction, Junctions::Here, Location, + Parent, WeightLimit, +}; +use xcm_executor::traits::ConvertLocation; /// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type. pub type NegativeImbalance = as Currency< @@ -109,20 +113,78 @@ where /// Asset filter that allows all assets from a certain location. pub struct AssetsFrom(PhantomData); -impl> ContainsPair for AssetsFrom { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> ContainsPair for AssetsFrom { + fn contains(asset: &Asset, origin: &Location) -> bool { let loc = T::get(); &loc == origin && - matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } if asset_loc.match_and_split(&loc).is_some()) } } +/// 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: Location = 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::*; use frame_support::{ - parameter_types, + derive_impl, parameter_types, traits::{ConstU32, FindAuthor, ValidatorRegistration}, PalletId, }; @@ -155,6 +217,7 @@ mod tests { pub const MaxReserves: u32 = 50; } + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; @@ -192,8 +255,8 @@ mod tests { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } @@ -265,13 +328,13 @@ mod tests { #[test] fn assets_from_filters_correctly() { parameter_types! { - pub SomeSiblingParachain: MultiLocation = MultiLocation::new(1, X1(Parachain(1234))); + pub SomeSiblingParachain: Location = (Parent, Parachain(1234)).into(); } let asset_location = SomeSiblingParachain::get() .pushed_with_interior(GeneralIndex(42)) - .expect("multilocation will only have 2 junctions; qed"); - let asset = MultiAsset { id: Concrete(asset_location), fun: 1_000_000u128.into() }; + .expect("location will only have 2 junctions; qed"); + let asset = Asset { id: AssetId(asset_location), fun: 1_000_000u128.into() }; assert!( AssetsFrom::::contains(&asset, &SomeSiblingParachain::get()), "AssetsFrom should allow assets from any of its interior locations" 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 4ebc2cc6e1e2a8abbd0be507e07ac3c1e73f42c4..b01d623d2b93da529f37bbfd8cf139ea5b98d9ae 100644 --- a/cumulus/parachains/common/src/lib.rs +++ b/cumulus/parachains/common/src/lib.rs @@ -16,11 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod impls; -pub mod kusama; -pub mod polkadot; -pub mod rococo; -pub mod westend; -pub mod wococo; +pub mod message_queue; pub mod xcm_config; pub use constants::*; pub use opaque::*; @@ -68,6 +64,12 @@ mod types { // Id used for identifying assets. pub type AssetIdForTrustBackedAssets = u32; + + // Id used for identifying non-fungible collections. + pub type CollectionId = u32; + + // Id used for identifying non-fungible items. + pub type ItemId = u32; } /// Common constants of parachains. diff --git a/cumulus/parachains/common/src/message_queue.rs b/cumulus/parachains/common/src/message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c9f4b840c9166ba9380f06970ad15bb1cac14c6 --- /dev/null +++ b/cumulus/parachains/common/src/message_queue.rs @@ -0,0 +1,55 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Helpers to deal with configuring the message queue in the runtime. + +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::traits::{QueueFootprint, QueuePausedQuery}; +use pallet_message_queue::OnQueueChanged; +use sp_std::marker::PhantomData; + +/// 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/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/xcm_config.rs b/cumulus/parachains/common/src/xcm_config.rs index 4b0215d672b2e3f8b5602976524edfd01940725b..a9756af7aed245ea792d12addbbb2fff529eedaf 100644 --- a/cumulus/parachains/common/src/xcm_config.rs +++ b/cumulus/parachains/common/src/xcm_config.rs @@ -45,8 +45,6 @@ where >::AssetId, >::Balance, >, - AccountIdOf: - From + Into, { fn charge_weight_in_fungibles( asset_id: as Inspect< @@ -66,77 +64,105 @@ where } } -/// Accepts an asset if it is a native asset from a particular `MultiLocation`. -pub struct ConcreteNativeAssetFrom(PhantomData); -impl> ContainsPair - for ConcreteNativeAssetFrom +/// Accepts an asset if it is a native asset from a particular `Location`. +pub struct ConcreteNativeAssetFrom(PhantomData); +impl> ContainsPair + for ConcreteNativeAssetFrom { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + fn contains(asset: &Asset, origin: &Location) -> bool { log::trace!(target: "xcm::filter_asset_location", "ConcreteNativeAsset asset: {:?}, origin: {:?}, location: {:?}", - asset, origin, Location::get()); - matches!(asset.id, Concrete(ref id) if id == origin && origin == &Location::get()) + asset, origin, LocationValue::get()); + asset.id.0 == *origin && origin == &LocationValue::get() } } pub struct RelayOrOtherSystemParachains< - SystemParachainMatcher: Contains, + SystemParachainMatcher: Contains, Runtime: parachain_info::Config, > { _runtime: PhantomData<(SystemParachainMatcher, Runtime)>, } -impl, Runtime: parachain_info::Config> - Contains for RelayOrOtherSystemParachains +impl, Runtime: parachain_info::Config> Contains + for RelayOrOtherSystemParachains { - fn contains(l: &MultiLocation) -> bool { + fn contains(l: &Location) -> bool { let self_para_id: u32 = parachain_info::Pallet::::get().into(); - if let MultiLocation { parents: 0, interior: X1(Parachain(para_id)) } = l { + if let (0, [Parachain(para_id)]) = l.unpack() { if *para_id == self_para_id { return false } } - matches!(l, MultiLocation { parents: 1, interior: Here }) || - SystemParachainMatcher::contains(l) + matches!(l.unpack(), (1, [])) || SystemParachainMatcher::contains(l) + } +} + +/// 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: &Location) -> bool { + log::trace!(target: "xcm::contains", "AllSiblingSystemParachains location: {:?}", l); + match l.unpack() { + // System parachain + (1, [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 +impl> ContainsPair for ConcreteAssetFromSystem { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + fn contains(asset: &Asset, origin: &Location) -> bool { log::trace!(target: "xcm::contains", "ConcreteAssetFromSystem asset: {:?}, origin: {:?}", asset, origin); - let is_system = match origin { + let is_system = match origin.unpack() { // The Relay Chain - MultiLocation { parents: 1, interior: Here } => true, + (1, []) => true, // System parachain - MultiLocation { parents: 1, interior: X1(Parachain(id)) } => - ParaId::from(*id).is_system(), + (1, [Parachain(id)]) => ParaId::from(*id).is_system(), // Others _ => false, }; - matches!(asset.id, Concrete(id) if id == AssetLocation::get()) && is_system + asset.id.0 == AssetLocation::get() && is_system + } +} + +/// 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: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [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, Asset, ConcreteAssetFromSystem, ContainsPair, GeneralIndex, + Here, Location, PalletInstance, Parachain, Parent, }; + use polkadot_primitives::LOWEST_PUBLIC_ID; + use xcm::latest::prelude::*; parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayLocation: Location = Location::parent(); } #[test] fn concrete_asset_from_relay_works() { - let expected_asset: MultiAsset = (Parent, 1000000).into(); - let expected_origin: MultiLocation = (Parent, Here).into(); + let expected_asset: Asset = (Parent, 1000000).into(); + let expected_origin: Location = (Parent, Here).into(); assert!(>::contains( &expected_asset, @@ -146,12 +172,12 @@ mod tests { #[test] fn concrete_asset_from_sibling_system_para_fails_for_wrong_asset() { - let unexpected_assets: Vec = vec![ + let unexpected_assets: Vec = vec![ (Here, 1000000).into(), ((PalletInstance(50), GeneralIndex(1)), 1000000).into(), ((Parent, Parachain(1000), PalletInstance(50), GeneralIndex(1)), 1000000).into(), ]; - let expected_origin: MultiLocation = (Parent, Parachain(1000)).into(); + let expected_origin: Location = (Parent, Parachain(1000)).into(); unexpected_assets.iter().for_each(|asset| { assert!(!>::contains(asset, &expected_origin)); @@ -170,14 +196,29 @@ mod tests { (2001, false), // Not a System Parachain ]; - let expected_asset: MultiAsset = (Parent, 1000000).into(); + let expected_asset: Asset = (Parent, 1000000).into(); for (para_id, expected_result) in test_data { - let origin: MultiLocation = (Parent, Parachain(para_id)).into(); + let origin: Location = (Parent, Parachain(para_id)).into(); assert_eq!( expected_result, >::contains(&expected_asset, &origin) ); } } + + #[test] + fn all_sibling_system_parachains_works() { + // system parachain + assert!(AllSiblingSystemParachains::contains(&Location::new(1, [Parachain(1)]))); + // non-system parachain + assert!(!AllSiblingSystemParachains::contains(&Location::new( + 1, + [Parachain(LOWEST_PUBLIC_ID.into())] + ))); + // when used at relay chain + assert!(!AllSiblingSystemParachains::contains(&Location::new(0, [Parachain(1)]))); + // when used with non-parachain + assert!(!AllSiblingSystemParachains::contains(&Location::new(1, [OnlyChild]))); + } } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/0_init.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/0_init.yml deleted file mode 100644 index fdc1aa258d42ac681c10930b8e6b32858462944d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/0_init.yml +++ /dev/null @@ -1,145 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9900 - assets_parachain: &assets_parachain - wsPort: 9910 - paraId: &ap_id 1000 - penpal_parachain: &penpal_parachain - wsPort: 9920 - paraId: &pp_id 2000 - variables: - common: - xcm_version: &xcm_version 3 - require_weight_at_most: &weight_at_most {refTime: 1000000000, proofSize: 200000} - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - penpal_parachain: - signer: &pp_signer //Alice - decodedCalls: - ap_force_xcm_version: - chain: *assets_parachain - pallet: polkadotXcm - call: forceXcmVersion - args: [ - { # location - parents: 1, - interior: Here - }, - *xcm_version # xcmVersion - ] - -tests: - - name: Initialize Chains - its: - - name: XCM supported versions between chains - actions: - - extrinsics: # Relay Chain sets supported version for Asset Parachain - - chain: *relay_chain - sudo: true - signer: *rc_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *ap_id - } - } - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *ap_id }}}, version: *xcm_version } - - extrinsics: # Relay Chain sets supported version for Penpal Parachain - - chain: *relay_chain - sudo: true - signer: *rc_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *pp_id - } - } - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *pp_id }}}, version: *xcm_version } - - extrinsics: # Asset Parachain sets supported version for Relay Chain through it - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 2200000000, - proofSize: 200000 - } - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: *weight_at_most, - call: $ap_force_xcm_version - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { - outcome: { Complete: { refTime: '1,019,210,000', proofSize: '200,000' }} - } - - name: polkadotXcm.SupportedVersionChanged - chain: *assets_parachain - result: { location: { parents: 1, interior: Here }, version: *xcm_version } - - extrinsics: # Penpal Parachain sets supported version for Relay Chain - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: forceXcmVersion - args: [ - { # location - parents: 1, - interior: Here - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: polkadotXcm.SupportedVersionChanged - result: { location: { parents: 1, interior: Here }, version: *xcm_version } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/1_dmp.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/1_dmp.yml deleted file mode 100644 index 0e207e632a023b83e5e5fac5b374eead16b74e61..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/1_dmp.yml +++ /dev/null @@ -1,263 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9900 - assets_parachain: &assets_parachain - wsPort: 9910 - paraId: &ap_id 1000 - variables: - common: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - wallet: &rc_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - assets_parachain_destination: &ap_dest { v3: { parents: 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_account: &ap_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - assets_parachain_beneficiary: &ap_benf { v3: { parents: 0, interior: { x1: { accountId32: { id: *ap_acc }}}}} - ksm: &rc_ksm { concrete: { parents: 0, interior: { here: true }}} - amount: &amount 1000000000000 - ksm_fungible: &rc_ksm_fungible { id: *rc_ksm, fun: { fungible: *amount }} - require_weight_at_most: &rc_weight_at_most { refTime: 1000000000, proofSize: 200000 } - assets_parachain_account: - wallet: &ap_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - asset_id: &asset_id 1 - asset_min_balance: &asset_ed 1000 - decodedCalls: - force_create_asset: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - -tests: - - name: DMP - its: [] - describes: - - name: xcmPallet.limitedTeleportAssets - before: &before_get_balances - - name: Get the balances of the Relay Chain's sender & Assets Parachain's receiver - actions: - - queries: - balance_rc_sender_before: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_before: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - its: - - name: Should teleport native assets from the Relay Chain to the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedTeleportAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '764,772,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '166,944,000', proofSize: 0 }}} - - queries: - balance_rc_sender_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_rc_sender_before, - after: $balance_rc_sender_after, - }, - amount: *amount - } - ] - - - name: Should increase the balance of the receiver - actions: - - asserts: - balanceIncreased: - args: [ - { - balances: { - before: $balance_ap_receiver_before, - after: $balance_ap_receiver_after, - } - } - ] - - - name: xcmPallet.send | Superuser - Transact(assets.forceCreate) - its: - - name: Relay Chain Superuser account SHOULD be able to execute a XCM Transact instruction in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: *rc_weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '1,014,103,000', proofSize: '200,000' }}} - - queries: - forced_created_asset: - chain: *assets_parachain - pallet: assets - call: asset - args: [ *asset_id ] - - asserts: - isSome: - args: [ $forced_created_asset ] - - - name: xcmPallet.send | Native - Transact(assets.forceCreate) - its: - - name: Relay Chain Native account SHOULD NOT be able to execute a XCM Transact instruction in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Native, - requireWeightAtMost: *rc_weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: system.ExtrinsicFailed - result: { dispatchError: BadOrigin } - - - name: xcmPallet.limitedReserveTransferAssets - before: *before_get_balances - its: - - name: SHOULD NOT reserved transfer native assets from the Relay Chain to the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedReserveTransferAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '750,645,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { - outcome: { - Incomplete: [ - { refTime: '1,000,000,000', proofSize: 0 }, - UntrustedReserveLocation - ] - } - } - - queries: - balance_rc_sender_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_rc_sender_before, - after: $balance_rc_sender_after, - }, - amount: *amount - } - ] - - - name: Should keep the balance of the receiver - actions: - - asserts: - equal: - args: - [ - $balance_ap_receiver_before, - $balance_ap_receiver_after - ] diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/2_ump.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/2_ump.yml deleted file mode 100644 index 2a0bb88090e928dd51db3f92dadab2cdd4eb93be..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/2_ump.yml +++ /dev/null @@ -1,191 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9900 - assets_parachain: &assets_parachain - wsPort: 9910 - paraId: &ap_id 1000 - variables: - common: - amount: &amount 1000000000000 - require_weight_at_most: &weight_at_most {refTime: 1000000000, proofSize: 0} - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - wallet: &rc_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F #Alice - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_account: &ap_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - assets_parachain_beneficiary: &ap_benf {v3: { parents: 0, interior: { x1: { accountId32: { id: *ap_acc }}}}} - ksm: &rc_ksm { concrete: { 0, interior: { here: true }}} - ksm_fungible: &rc_ksm_fungible { id: *rc_ksm, fun: { fungible: *amount }} - assets_parachain_account: - signer: &ap_signer //Alice - wallet: &ap_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - relay_chain_destination: &rc_dest { v3: { parents: 1, interior: { here: true }}} - assets_parachain_account: &rc_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' #Alice - relay_chain_beneficiary: &rc_benf {v3: { parents: 0, interior: { x1: { accountId32: { id: *rc_acc }}}}} - ksm: &ap_ksm { concrete: { parents: 1, interior: { here: true }}} - ksm_fungible: &ap_ksm_fungible { id: *ap_ksm, fun: { fungible: *amount }} - decodedCalls: - system_remark: - chain: *relay_chain - pallet: system - call: remark - args: [ 0x0011 ] - -tests: - - name: UMP - describes: - - name: polkadotXcm.limitedTeleportAssets - before: - - name: DEPENDENCY | Do a 'limitedTeleportAssets' from the Relay Chain to the Assets Parachain to have funds to send them back - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedTeleportAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '761,173,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '166,944,000', proofSize: 0 }}} - - - name: Get the balances of the Assets Parachain's sender & Relay Chain's receiver - actions: - - queries: - balance_ap_sender_before: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - balance_rc_receiver_before: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - its: - - name: Should teleport native assets back from Assets Parachain to the Relay Chain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedTeleportAssets - args: [ - *rc_dest, # destination - *rc_benf, # beneficiary - { v3: [ *ap_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '539,494,000', proofSize: '7,133' }}} - - name: messageQueue.Processed - chain: *relay_chain - threshold: *weight_threshold - result: { origin: { Ump: { Para: '1,000' } }, weightUsed: { refTime: '298,716,000', proofSize: '0' }, success: true } - - queries: - balance_ap_sender_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - balance_rc_receiver_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_ap_sender_before, - after: $balance_ap_sender_after, - }, - amount: *amount - } - ] - - - name: Should increase the balance of the receiver - actions: - - asserts: - balanceIncreased: - args: [ - { - balances: { - before: $balance_rc_receiver_before, - after: $balance_rc_receiver_after, - } - } - ] - - - name: polkadotXcm.send | Native - Transact(system.remark) - its: - - name: Assets Parachain SHOULD NOT be able to dispatch 'send' call - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: send - args: [ - *rc_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Native, - requireWeightAtMost: *weight_at_most, - call: $system_remark - } - } - ] - } - ] - events: - - name: system.ExtrinsicFailed - result: { dispatchError: BadOrigin } - - - name: polkadotXcm.limitedReserveTransferAssets - its: - - name: Should NOT be able to reserve transfer native assets from the Assets Parachain to the Relay Chain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *rc_dest, # destination - *rc_benf, # beneficiary - { v3: [ *ap_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: polkadotXcm.Attempted - result: { outcome: { Error: Barrier }} diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/3_force_hrmp-open-channels.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/3_force_hrmp-open-channels.yml deleted file mode 100644 index dfdae028f00d0d62e8500335d4c11f256a01462d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/3_force_hrmp-open-channels.yml +++ /dev/null @@ -1,122 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9900 - assets_parachain: - wsPort: 9910 - paraId: &ap_id 1000 - penpal_parachain: - wsPort: 9920 - paraId: &pp_id 2000 - variables: - common: - amount: &amount 2000000000000 - hrmp_channels: - proposed_max_capacity: &max_capacity 8 - proposed_max_message_size: &max_message_size 8192 - channel: &channel { - maxCapacity: *max_capacity, - maxTotalSize: *max_message_size, - maxMessageSize: *max_message_size, - msgCount: 0, - totalSize: 0, - mqcHead: null, - senderDeposit: 0, - recipientDeposit: 0 - } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_account: - sovereign_account: &ap_sovereign F7fq1jSNVTPfJmaHaXCMtatT1EZefCUsa7rRiQVNR5efcah - penpal_parachain: - sovereign_account: &pp_sovereign F7fq1jMZkfuCuoMTyiEVAP2DMpMt18WopgBqTJznLihLNbZ - -tests: - - name: HRMP - beforeEach: - - name: DEPENDENCY | Penpal Parachain Sovereign account in the Relay Chain needs to be funded - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: balances - call: transfer - args: [ - *pp_sovereign, # destination - *amount, # value - ] - events: - - name: balances.Transfer - - - name: DEPENDENCY | Assets Parachain Sovereign account in the Relay Chain needs to be funded - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: balances - call: transfer - args: [ - *ap_sovereign, # destination - *amount, # value - ] - events: - - name: balances.Transfer - describes: - - name: hrmp.forceOpenHrmpChannel (Penpal Parachain → Assets Parachain) - its: - - name: Open Penpal Parachain to Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *pp_id, - *ap_id, - *max_capacity, - *max_message_size - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: hrmp.HrmpChannelForceOpened - - - name: hrmp.forceOpenHrmpChannel (Assets Parachain → PenPal Parachain) - its: - - name: Open Assets Parachain to PenPal Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *ap_id, - *pp_id, - *max_capacity, - *max_message_size - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: hrmp.HrmpChannelForceOpened - - - name: hrmp.forceProcessHrmpOpen (make sure all the channels are open) - its: - - name: Make sure all the pending channels are open - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceProcessHrmpOpen - args: [ 2 ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/4_hrmp.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/4_hrmp.yml deleted file mode 100644 index 02e53da75580a95c35eedb7f311eb68758b195af..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/0_xcm/4_hrmp.yml +++ /dev/null @@ -1,388 +0,0 @@ ---- -# Note: This tests depends on the 3_hrmp-open-channels.yml for opening channels, otherwise teleports aren't going to -# work. -settings: - chains: - relay_chain: &relay_chain - wsPort: 9900 - assets_parachain: &assets_parachain - wsPort: 9910 - paraId: &ap_id 1000 - penpal_parachain: &penpal_parachain - wsPort: 9920 - paraId: &pp_id 2000 - variables: - common: - mint_amount: &mint_amount 1000000000000 - amount: &amount 100000000000 - require_weight_at_most: &weight_at_most {refTime: 1200000000, proofSize: 20000} - amount_to_send: &amount_to_send 500000000000 - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_dest_routed: &ap_dest_routed { v3: { parents: 1, interior: { x1: { parachain: *ap_id } }}} - assets_parachain_account: - signer: &ap_signer //Alice - wallet: &ap_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - asset_id: &asset_id 2 - assets_pallet_id: &assets_pallet_id 50 - asset_min_balance: &asset_ed 1000 - penpal_parachain_destination: &pp_dest { v3: { parents: 1, interior: { x1: { parachain: *pp_id } }}} - ksm: &ap_ksm { concrete: { parents: 1, interior: { here: true }}} - ksm_fungible: &ap_ksm_fungible { id: *ap_ksm, fun: { fungible: *amount }} - suff_asset: &suff_asset { concrete: { parents: 0, interior: { x2: [ { PalletInstance: *assets_pallet_id }, { GeneralIndex: *asset_id } ] }}} - suff_asset_fail: &suff_asset_fail { concrete: { parents: 0, interior: { x2: [ { PalletInstance: *assets_pallet_id }, { GeneralIndex: 3 } ] }}} - suff_asset_fungible_fail: &ap_suff_asset_fungible_fail { id: *suff_asset_fail, fun: { fungible: 200000000000 }} - penpal_parachain: - sovereign_account: &pp_sovereign_sibl FBeL7EAeUroLWXW1yfKboiqTqVfbRBcsUKd6QqVf4kGBySS - signer: &pp_signer //Alice - penpal_parachain_account: &pp_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - decodedCalls: - force_create_asset: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - force_create_asset2: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - -tests: - - name: HRMP - describes: - - name: polkadotXcm.limitedReserveTransferAssets (Asset) | Assets Parachain -> Penpal Parachain - before: - - name: DEPENDENCY | A sufficient Asset should exist in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - SetTopic: '0x0123456789012345678901234567891201234567890123456789012345678912' - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '1,216,703,000', proofSize: '20,000' }}} - - queries: - forced_created_asset: - chain: *assets_parachain - pallet: assets - call: asset - args: [ *asset_id ] - - asserts: - isSome: - args: [ $forced_created_asset ] - - - name: DEPENDENCY | Some Assets should be minted for the sender - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: assets - call: mint - args: [ - *asset_id, - *ap_wallet, - *mint_amount - ] - events: - - name: assets.Issued - result: { assetId: *asset_id, owner: *ap_wallet, amount: *mint_amount } - - its: - - name: Assets Parachain should be able to reserve transfer an Asset to Penpal Parachain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *pp_dest, # destination - { # beneficiary - V3: { - parents: 0, - interior: { - X1: { - AccountId32: { - id: *pp_acc - } - } - } - } - }, - { # assets - V3: [ - { - id: { - Concrete: { - parents: 0, - interior: { - X2: [ - { - PalletInstance: *assets_pallet_id - }, - { - GeneralIndex: *asset_id - } - ] - } - } - }, - fun: { - Fungible: *amount_to_send - } - } - ] - }, - 0, # feeAssetItem - Unlimited # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '679,150,000', proofSize: '6,196' }}} - - name: assets.Transferred - result: { - assetId: *asset_id, - from: *ap_wallet, - to: *pp_sovereign_sibl, - amount: *amount_to_send - } - - - name: polkadotXcm.limitedReserveTransferAssets (KSM) | Assets Parachain -> Penpal Parachain - its: - - name: Assets Parachain should be able to reserve transfer KSM to Penpal Parachain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *pp_dest, # destination - { # beneficiary - V3: { - parents: 0, - interior: { - X1: { - AccountId32: { - id: *pp_acc - } - } - } - } - }, - { # assets - V3: [ - *ap_ksm_fungible - ] - }, - 0, # feeAssetItem - Unlimited # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '679,150,000', proofSize: '6,196' }}} - - name: balances.Endowed - result: { - account: *pp_sovereign_sibl, - freeBalance: *amount - } - - - name: polkadotXcm.send( assets.forceCreateAsset ) | Penpal Parachain -> Assets Parachain - before: - - name: Get the asset balance of the Penpal Parachain Sovereign account in Assets Parachain - actions: - - queries: - assets_balance_pp_sovereign_before: - chain: *assets_parachain - pallet: assets - call: account - args: [ - *asset_id, - *pp_sovereign_sibl - ] - its: - - name: Penpal Parachain should be able to send XCM message paying its fee with sufficient asset in Assets Parachain - actions: - - extrinsics: - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: send - args: [ - *ap_dest_routed, # destination - { - v3: [ #message - { - WithdrawAsset: [ - { - id: { - concrete: { - parents: 0, - interior: { - X2: [ - { PalletInstance: *assets_pallet_id }, - { GeneralIndex: *asset_id } - ] - } - } - }, - fun: { fungible: *amount }} - ] - }, - { - BuyExecution: { - fees: { id: *suff_asset, fun: { fungible: *amount }}, - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: SovereignAccount, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset2 - } - }, - { - RefundSurplus - }, - { - DepositAsset: { - assets: { Wild: All }, - beneficiary: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: , # None - id: *pp_acc - } - } - }} - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: polkadotXcm.Sent - - name: assets.Burned - chain: *assets_parachain - result: { assetId: *asset_id, owner: *pp_sovereign_sibl } - - name: assets.Issued - chain: *assets_parachain - result: { assetId: *asset_id } - - queries: - assets_balance_pp_sovereign_after: - chain: *assets_parachain - pallet: assets - call: account - args: [ - *asset_id, - *pp_sovereign_sibl - ] - forced_created_asset2: - chain: *assets_parachain - pallet: assets - call: asset - args: [ 3 ] - - asserts: - isSome: - args: [ $forced_created_asset2 ] - - name: Should reduce the assets balance of the Penpal Parachain's SovereignAccount in the Assets Parachain - actions: - - asserts: - assetsDecreased: - args: [ - { - balances: { - before: $assets_balance_pp_sovereign_before, - after: $assets_balance_pp_sovereign_after, - }, - } - ] - - - name: Penpal Parachain SHOULD NOT be able to send XCM message paying its fee with sufficient assets if not enough balance - actions: - - extrinsics: - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: send - args: [ - *ap_dest_routed, # destination - { - v3: [ #message - { - WithdrawAsset: [*ap_suff_asset_fungible_fail] - }, - { - BuyExecution: { - fees: *ap_suff_asset_fungible_fail, - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: SovereignAccount, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset2 - } - } - ] - } - ] - events: - - name: xcmpQueue.Fail - chain: *assets_parachain - threshold: *weight_threshold - result: { - error: FailedToTransactAsset, - weight: { refTime: '152,426,000', proofSize: '3,593' } - } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/config.toml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/config.toml deleted file mode 100644 index 1ec06b3fa10435e5b865e8da0683be24214c426c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-kusama/config.toml +++ /dev/null @@ -1,71 +0,0 @@ -[relaychain] -default_command = "./bin/polkadot" -default_args = [ "-lparachain=debug", "-lxcm=trace" ] -chain = "kusama-local" - - [[relaychain.nodes]] - name = "alice" - ws_port = 9900 - validator = true - args = ["--state-cache-size=0"] - - [[relaychain.nodes]] - name = "bob" - ws_port = 9901 - validator = true - - [[relaychain.nodes]] - name = "charlie" - ws_port = 9902 - validator = true - - [[relaychain.nodes]] - name = "dave" - ws_port = 9903 - validator = true - -[[parachains]] -id = 1000 -chain = "asset-hub-kusama-local" -cumulus_based = true - - [[parachains.collators]] - name = "collator1" - ws_port = 9910 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace", "--state-cache-size=0" ] - - [[parachains.collators]] - name = "collator2" - ws_port = 9911 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace" ] - -[[parachains]] -id = 2000 -chain = "penpal-kusama-2000" -cumulus_based = true - - [[parachains.collators]] - name = "collator3" - ws_port = 9920 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace", "--state-cache-size=0" ] - - [[parachains.collators]] - name = "collator4" - ws_port = 9921 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace" ] - -# [[hrmpChannels]] -# sender = 1000 -# recipient = 2000 -# maxCapacity = 8 -# maxMessageSize = 8192 - -# [[hrmpChannels]] -# sender = 2000 -# recipient = 1000 -# maxCapacity = 8 -# maxMessageSize = 8192 diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/0_init.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/0_init.yml deleted file mode 100644 index a6d3fb3ec83439bc9f9bc5e9ae42293573dcf5e3..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/0_init.yml +++ /dev/null @@ -1,145 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9800 - assets_parachain: &assets_parachain - wsPort: 9810 - paraId: &ap_id 1000 - penpal_parachain: &penpal_parachain - wsPort: 9820 - paraId: &pp_id 2000 - variables: - common: - xcm_version: &xcm_version '3' - require_weight_at_most: &weight_at_most {refTime: 1000000000, proofSize: 200000} - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - penpal_parachain: - signer: &pp_signer //Alice - decodedCalls: - ap_force_xcm_version: - chain: *assets_parachain - pallet: polkadotXcm - call: forceXcmVersion - args: [ - { # location - parents: 1, - interior: Here - }, - *xcm_version # xcmVersion - ] - -tests: - - name: Initialize Chains - its: - - name: XCM supported versions between chains - actions: - - extrinsics: # Relay Chain sets supported version for Asset Parachain - - chain: *relay_chain - sudo: true - signer: *rc_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *ap_id - } - } - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *ap_id }}}, version: *xcm_version } - - extrinsics: # Relay Chain sets supported version for Penpal Parachain - - chain: *relay_chain - sudo: true - signer: *rc_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *pp_id - } - } - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *pp_id }}}, version: *xcm_version } - - extrinsics: # Asset Parachain sets supported version for Relay Chain through it - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 3200000000, - proofSize: 200000 - } - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: *weight_at_most, - call: $ap_force_xcm_version - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { - outcome: { Complete: { refTime: '1,019,210,000', proofSize: '200,000' }} - } - - name: polkadotXcm.SupportedVersionChanged - chain: *assets_parachain - result: { location: { parents: 1, interior: Here }, version: *xcm_version } - - extrinsics: # Penpal Parachain sets supported version for Relay Chain - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: forceXcmVersion - args: [ - { # location - parents: 1, - interior: Here - }, - *xcm_version # xcmVersion - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: polkadotXcm.SupportedVersionChanged - result: { location: { parents: 1, interior: Here}, version: *xcm_version } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/1_dmp.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/1_dmp.yml deleted file mode 100644 index 36b296f3eb1f3f8ed93b9cdc4354fc89b692b50c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/1_dmp.yml +++ /dev/null @@ -1,263 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9800 - assets_parachain: &assets_parachain - wsPort: 9810 - paraId: &ap_id 1000 - variables: - common: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - wallet: &rc_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - assets_parachain_destination: &ap_dest { v3: { parents: 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_account: &ap_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - assets_parachain_beneficiary: &ap_benf {v3: { parents: 0, interior: { x1: { accountId32: { id: *ap_acc }}}}} - ksm: &rc_ksm { concrete: { parents: 0, interior: { here: true }}} - amount: &amount 1000000000000 - ksm_fungible: &rc_ksm_fungible { id: *rc_ksm, fun: { fungible: *amount }} - require_weight_at_most: &rc_weight_at_most {refTime: 1000000000, proofSize: 200000} - assets_parachain_account: - wallet: &ap_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - asset_id: &asset_id 1 - asset_min_balance: &asset_ed 1000 - decodedCalls: - force_create_asset: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - -tests: - - name: DMP - its: [] - describes: - - name: xcmPallet.limitedTeleportAssets - before: &before_get_balances - - name: Get the balances of the Relay Chain's sender & Assets Parachain's receiver - actions: - - queries: - balance_rc_sender_before: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_before: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - its: - - name: Should teleport native assets from the Relay Chain to the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedTeleportAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '166,944,000', proofSize: 0 }}} - - queries: - balance_rc_sender_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_rc_sender_before, - after: $balance_rc_sender_after, - }, - amount: *amount - } - ] - - - name: Should increase the balance of the receiver - actions: - - asserts: - balanceIncreased: - args: [ - { - balances: { - before: $balance_ap_receiver_before, - after: $balance_ap_receiver_after, - } - } - ] - - - name: xcmPallet.send | Superuser - Transact(assets.forceCreate) - its: - - name: Relay Chain Superuser account SHOULD be able to execute a XCM Transact instruction in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originType: Superuser, - requireWeightAtMost: *rc_weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '1,014,103,000', proofSize: '200,000' }}} - - queries: - forced_created_asset: - chain: *assets_parachain - pallet: assets - call: asset - args: [ *asset_id ] - - asserts: - isSome: - args: [ $forced_created_asset ] - - - name: xcmPallet.send | Native - Transact(assets.forceCreate) - its: - - name: Relay Chain Native account SHOULD NOT be able to execute a XCM Transact instruction in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originType: Native, - requireWeightAtMost: *rc_weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: system.ExtrinsicFailed - result: { dispatchError: BadOrigin } - - - name: xcmPallet.limitedReserveTransferAssets - before: *before_get_balances - its: - - name: SHOULD NOT reserved transfer native assets from the Relay Chain to the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedReserveTransferAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '2,000,000,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { - outcome: { - Incomplete: [ - { refTime: '1,000,000,000', proofSize: 0 }, - UntrustedReserveLocation - ] - } - } - - queries: - balance_rc_sender_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - balance_ap_receiver_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_rc_sender_before, - after: $balance_rc_sender_after, - }, - amount: *amount - } - ] - - - name: Should keep the balance of the receiver - actions: - - asserts: - equal: - args: - [ - $balance_ap_receiver_before, - $balance_ap_receiver_after - ] diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/2_ump.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/2_ump.yml deleted file mode 100644 index fa84d4b006a7cf76019ea895a5da08dc109fb3ee..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/2_ump.yml +++ /dev/null @@ -1,194 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9800 - assets_parachain: &assets_parachain - wsPort: 9810 - paraId: &ap_id 1000 - variables: - common: - amount: &amount 1000000000000 - require_weight_at_most: &weight_at_most {refTime: 1000000000, proofSize: 0} - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - wallet: &rc_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_account: &ap_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - assets_parachain_beneficiary: &ap_benf {v3: { parents: 0, interior: { x1: { accountId32: { id: *ap_acc }}}}} - ksm: &rc_ksm { concrete: { 0, interior: { here: true }}} - ksm_fungible: &rc_ksm_fungible { id: *rc_ksm, fun: { fungible: *amount }} - assets_parachain_account: - signer: &ap_signer //Alice - wallet: &ap_wallet HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F - relay_chain_destination: &rc_dest { v3: { parents: 1, interior: { here: true }}} - assets_parachain_account: &rc_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - relay_chain_beneficiary: &rc_benf {v3: { parents: 0, interior: { x1: { accountId32: { id: *rc_acc }}}}} - ksm: &ap_ksm { concrete: { parents: 1, interior: { here: true }}} - ksm_fungible: &ap_ksm_fungible { id: *ap_ksm, fun: { fungible: *amount }} - decodedCalls: - system_remark: - chain: *relay_chain - pallet: system - call: remark - args: [ 0x0011 ] - -tests: - - name: UMP - describes: - - name: polkadotXcm.limitedTeleportAssets - before: - - name: DEPENDENCY | Do a 'limitedTeleportAssets' from the Relay Chain to the Assets Parachain to have funds to send them back - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: xcmPallet - call: limitedTeleportAssets - args: [ - *ap_dest, # destination - *ap_benf, # beneficiary - { v3: [ *rc_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '166,944,000', proofSize: 0 }}} - - - name: Get the balances of the Assets Parachain's sender & Relay Chain's receiver - actions: - - queries: - balance_ap_sender_before: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - balance_rc_receiver_before: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - - its: - - name: Should be able to teleport native assets back from Assets Parachain to the Relay Chain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedTeleportAssets - args: [ - *rc_dest, # destination - *rc_benf, # beneficiary - { v3: [ *ap_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '533,283,000', proofSize: '7,096' }}} - - name: messageQueue.Processed - chain: *relay_chain - threshold: *weight_threshold - result: { origin: { Ump: { Para: '1,000' } }, weightUsed: { refTime: '4,000,000,000', proofSize: '0' }, success: true } - - queries: - balance_ap_sender_after: - chain: *assets_parachain - pallet: system - call: account - args: [ *ap_wallet ] - balance_rc_receiver_after: - chain: *relay_chain - pallet: system - call: account - args: [ *rc_wallet ] - - - name: Should reduce the balance of the sender - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_ap_sender_before, - after: $balance_ap_sender_after, - }, - amount: *amount - } - ] - - - name: Should increase the balance of the receiver - actions: - - asserts: - balanceIncreased: - args: [ - { - balances: { - before: $balance_rc_receiver_before, - after: $balance_rc_receiver_after, - } - } - ] - - - name: polkadotXcm.send | Native - Transact(system.remark) - its: - - name: Assets Parachain SHOULD NOT be able to dispatch 'send' call - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: send - args: [ - *rc_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originType: Native, - requireWeightAtMost: *weight_at_most, - call: $system_remark - } - } - ] - } - ] - events: - - name: system.ExtrinsicFailed - attributes: - - type: SpRuntimeDispatchError - value: BadOrigin - - - name: polkadotXcm.limitedReserveTransferAssets - its: - - name: Should NOT be able to reserve transfer native assets from the Assets Parachain to the Relay Chain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *rc_dest, # destination - *rc_benf, # beneficiary - { v3: [ *ap_ksm_fungible ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: polkadotXcm.Attempted - result: { outcome: { Error: Barrier }} diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/3_force_hrmp-open-channels.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/3_force_hrmp-open-channels.yml deleted file mode 100644 index ecf344a073b4c55243a087af6704acb554710341..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/3_force_hrmp-open-channels.yml +++ /dev/null @@ -1,120 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9800 - assets_parachain: - wsPort: 9810 - paraId: &ap_id 1000 - penpal_parachain: - wsPort: 9820 - paraId: &pp_id 2000 - variables: - common: - amount: &amount 2000000000000 - hrmp_channels: - proposed_max_capacity: &max_capacity 8 - proposed_max_message_size: &max_message_size 8192 - channel: &channel { - maxCapacity: *max_capacity, - maxTotalSize: *max_message_size, - maxMessageSize: *max_message_size, - msgCount: 0, - totalSize: 0, - mqcHead: null, - senderDeposit: 0, - recipientDeposit: 0 - } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_account: - sovereign_account: &ap_sovereign 5Ec4AhPZk8STuex8Wsi9TwDtJQxKqzPJRCH7348Xtcs9vZLJ - penpal_parachain: - sovereign_account: &pp_sovereign F7fq1jMZkfuCuoMTyiEVAP2DMpMt18WopgBqTJznLihLNbZ - -tests: - - name: HRMP - beforeEach: - - name: DEPENDENCY | Penpal Parachain Sovereign account in the Relay Chain needs to be funded - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: balances - call: transfer - args: [ - *pp_sovereign, # destination - *amount, # value - ] - events: - - name: balances.Transfer - - - name: DEPENDENCY | Assets Parachain Sovereign account in the Relay Chain needs to be funded - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - pallet: balances - call: transfer - args: [ - *ap_sovereign, # destination - *amount, # value - ] - events: - - name: balances.Transfer - describes: - - name: hrmp.hrmpInitOpenChannel (Penpal Parachain → Assets Parachain) - its: - - name: Open Penpal Parachain to Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *pp_id, - *ap_id, - *max_capacity, - *max_message_size - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: hrmp.HrmpChannelForceOpened - - name: hrmp.hrmpInitOpenChannel (Assets Parachain → PenPal Parachain) - its: - - name: Open Assets Parachain to PenPal Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *ap_id, - *pp_id, - *max_capacity, - *max_message_size - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: hrmp.HrmpChannelForceOpened - - name: hrmp.forceProcessHrmpOpen (make sure all the channels are open) - its: - - name: Make sure all the pending channels are open - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: hrmp - call: forceProcessHrmpOpen - args: [ 2 ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/4_hrmp.yml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/4_hrmp.yml deleted file mode 100644 index 681af698c16da401e580bcf2b12f28f6ba5043a1..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/0_xcm/4_hrmp.yml +++ /dev/null @@ -1,388 +0,0 @@ ---- -# Note: This tests depends on the 3_hrmp-open-channels.yml for opening channels, otherwise teleports aren't going to -# work. -settings: - chains: - relay_chain: &relay_chain - wsPort: 9800 - assets_parachain: &assets_parachain - wsPort: 9810 - paraId: &ap_id 1000 - penpal_parachain: &penpal_parachain - wsPort: 9820 - paraId: &pp_id 2000 - variables: - common: - mint_amount: &mint_amount 1000000000000 - amount: &amount 1000000000000 - require_weight_at_most: &weight_at_most {refTime: 1200000000, proofSize: 20000} - amount_to_send: &amount_to_send 500000000000 - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - relay_chain: - signer: &rc_signer //Alice - assets_parachain_destination: &ap_dest { v3: { 0, interior: { x1: { parachain: *ap_id }}}} - assets_parachain_dest_routed: &ap_dest_routed { v3: { parents: 1, interior: { x1: { parachain: *ap_id } }}} - assets_parachain_account: - signer: &ap_signer //Alice - wallet: &ap_wallet 15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5 - asset_id: &asset_id 2 - assets_pallet_id: &assets_pallet_id 50 - asset_min_balance: &asset_ed 1000 - penpal_parachain_destination: &pp_dest { v3: { parents: 1, interior: { x1: { parachain: *pp_id } }}} - ksm: &ap_ksm { concrete: { parents: 1, interior: { here: true }}} - ksm_fungible: &ap_ksm_fungible { id: *ap_ksm, fun: { fungible: *amount }} - suff_asset: &suff_asset { concrete: { parents: 0, interior: { x2: [ { PalletInstance: *assets_pallet_id }, { GeneralIndex: *asset_id } ] }}} - suff_asset_fail: &suff_asset_fail { concrete: { parents: 0, interior: { x2: [ { PalletInstance: *assets_pallet_id }, { GeneralIndex: 3 } ] }}} - suff_asset_fungible_fail: &ap_suff_asset_fungible_fail { id: *suff_asset_fail, fun: { fungible: 200000000000 }} - penpal_parachain: - sovereign_account: &pp_sovereign_sibl 13cKp89Msu7M2PiaCuuGr1BzAsD5V3vaVbDMs3YtjMZHdGwR - signer: &pp_signer //Alice - penpal_parachain_account: &pp_acc '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - decodedCalls: - force_create_asset: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - force_create_asset2: - chain: *assets_parachain - pallet: assets - call: forceCreate - args: [ - *asset_id, - { Id: *ap_wallet }, # owner - true, # isSufficient - *asset_ed # minBalance - ] - -tests: - - name: HRMP - describes: - - name: polkadotXcm.limitedReserveTransferAssets (Asset) | Assets Parachain -> Penpal Parachain - before: - - name: DEPENDENCY | A sufficient Asset should exist in the Assets Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *rc_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - *ap_dest, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - SetTopic: '0x0123456789012345678901234567891201234567890123456789012345678912' - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset - } - } - ] - } - ] - events: - - name: xcmPallet.Sent - - name: dmpQueue.ExecutedDownward - chain: *assets_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '1,216,703,000', proofSize: '20,000' }}} - - queries: - forced_created_asset: - chain: *assets_parachain - pallet: assets - call: asset - args: [ *asset_id ] - - asserts: - isSome: - args: [ $forced_created_asset ] - - - name: DEPENDENCY | Some Assets should be minted for the sender - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: assets - call: mint - args: [ - *asset_id, - *ap_wallet, - *mint_amount - ] - events: - - name: assets.Issued - result: { assetId: *asset_id, owner: *ap_wallet, amount: *mint_amount } - - its: - - name: Assets Parachain should be able to reserve transfer an Asset to Penpal Parachain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *pp_dest, # destination - { # beneficiary - V3: { - parents: 0, - interior: { - X1: { - AccountId32: { - id: *pp_acc - } - } - } - } - }, - { # assets - V3: [ - { - id: { - Concrete: { - parents: 0, - interior: { - X2: [ - { - PalletInstance: *assets_pallet_id - }, - { - GeneralIndex: *asset_id - } - ] - } - } - }, - fun: { - Fungible: *amount_to_send - } - } - ] - }, - 0, # feeAssetItem - Unlimited # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '673,627,000', proofSize: '6,196' }}} - - name: assets.Transferred - result: { - assetId: *asset_id, - from: *ap_wallet, - to: *pp_sovereign_sibl, - amount: *amount_to_send - } - - - name: polkadotXcm.limitedReserveTransferAssets (KSM) | Assets Parachain -> Penpal Parachain - its: - - name: Assets Parachain should be able to reserve transfer KSM to Penpal Parachain - actions: - - extrinsics: - - chain: *assets_parachain - signer: *ap_signer - pallet: polkadotXcm - call: limitedReserveTransferAssets - args: [ - *pp_dest, # destination - { # beneficiary - V3: { - parents: 0, - interior: { - X1: { - AccountId32: { - id: *pp_acc - } - } - } - } - }, - { # assets - V3: [ - *ap_ksm_fungible - ] - }, - 0, # feeAssetItem - Unlimited # weightLimit - ] - events: - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '679,150,000', proofSize: '6,196' }}} - - name: balances.Endowed - result: { - account: *pp_sovereign_sibl, - freeBalance: *amount - } - - - name: polkadotXcm.send( assets.forceCreateAsset ) | Penpal Parachain -> Assets Parachain - before: - - name: Get the asset balance of the Penpal Parachain Sovereign account in Assets Parachain - actions: - - queries: - assets_balance_pp_sovereign_before: - chain: *assets_parachain - pallet: assets - call: account - args: [ - *asset_id, - *pp_sovereign_sibl - ] - its: - - name: Penpal Parachain should be able to send XCM message paying its fee with sufficient asset in Assets Parachain - actions: - - extrinsics: - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: send - args: [ - *ap_dest_routed, # destination - { - v3: [ #message - { - WithdrawAsset: [ - { - id: { - concrete: { - parents: 0, - interior: { - X2: [ - { PalletInstance: *assets_pallet_id }, - { GeneralIndex: *asset_id } - ] - } - } - }, - fun: { fungible: *amount_to_send }} - ] - }, - { - BuyExecution: { - fees: { id: *suff_asset, fun: { fungible: *amount_to_send }}, - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: SovereignAccount, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset2 - } - }, - { - RefundSurplus - }, - { - DepositAsset: { - assets: { Wild: All }, - beneficiary: { - parents: 0, - interior: { - X1: { - AccountId32: { - network: , # None - id: *pp_acc - } - } - }} - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: polkadotXcm.Sent - - name: assets.Burned - chain: *assets_parachain - result: { assetId: *asset_id, owner: *pp_sovereign_sibl } - - name: assets.Issued - chain: *assets_parachain - result: { assetId: *asset_id } - - queries: - assets_balance_pp_sovereign_after: - chain: *assets_parachain - pallet: assets - call: account - args: [ - *asset_id, - *pp_sovereign_sibl - ] - forced_created_asset2: - chain: *assets_parachain - pallet: assets - call: asset - args: [ 3 ] - - asserts: - isSome: - args: [ $forced_created_asset2 ] - - name: Should reduce the assets balance of the Penpal Parachain's SovereignAccount in the Assets Parachain - actions: - - asserts: - assetsDecreased: - args: [ - { - balances: { - before: $assets_balance_pp_sovereign_before, - after: $assets_balance_pp_sovereign_after, - }, - } - ] - - - name: Penpal Parachain SHOULD NOT be able to send XCM message paying its fee with sufficient assets if not enough balance - actions: - - extrinsics: - - chain: *penpal_parachain - signer: *pp_signer - sudo: true - pallet: polkadotXcm - call: send - args: [ - *ap_dest_routed, # destination - { - v3: [ #message - { - WithdrawAsset: [*ap_suff_asset_fungible_fail] - }, - { - BuyExecution: { - fees: *ap_suff_asset_fungible_fail, - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: SovereignAccount, - requireWeightAtMost: *weight_at_most, - call: $force_create_asset2 - } - } - ] - } - ] - events: - - name: xcmpQueue.Fail - chain: *assets_parachain - threshold: *weight_threshold - result: { - error: FailedToTransactAsset, - weight: { refTime: '152,426,000', proofSize: '3,593' } - } diff --git a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/config.toml b/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/config.toml deleted file mode 100644 index da53cd0ad4f23d5405b0c372d5531022dcd8bd2a..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/assets/asset-hub-polkadot/config.toml +++ /dev/null @@ -1,72 +0,0 @@ -[relaychain] -default_command = "./bin/polkadot" -default_args = [ "-lparachain=debug", "-lxcm=trace" ] -chain = "polkadot-local" - - [[relaychain.nodes]] - name = "alice" - ws_port = 9800 - validator = true - args = ["--state-cache-size=0"] - - [[relaychain.nodes]] - name = "bob" - ws_port = 9801 - validator = true - - [[relaychain.nodes]] - name = "charlie" - ws_port = 9802 - validator = true - - [[relaychain.nodes]] - name = "dave" - ws_port = 9803 - validator = true - -[[parachains]] -id = 1000 -chain = "asset-hub-polkadot-local" -cumulus_based = true - - [[parachains.collators]] - name = "collator1" - ws_port = 9810 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace", "--state-cache-size=0" ] - - [[parachains.collators]] - name = "collator2" - ws_port = 9811 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace" ] - - -[[parachains]] -id = 2000 -chain = "penpal-polkadot-2000" -cumulus_based = true - - [[parachains.collators]] - name = "collator3" - ws_port = 9820 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace", "--state-cache-size=0" ] - - [[parachains.collators]] - name = "collator4" - ws_port = 9821 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace" ] - -# [[hrmpChannels]] -# sender = 1000 -# recipient = 2000 -# maxCapacity = 8 -# maxMessageSize = 8192 - -# [[hrmpChannels]] -# sender = 2000 -# recipient = 1000 -# maxCapacity = 8 -# maxMessageSize = 8192 diff --git a/cumulus/parachains/integration-tests/e2e/collectives/README.md b/cumulus/parachains/integration-tests/e2e/collectives/README.md deleted file mode 100644 index 9c4efe7c9504835814e1a693dd8d57710da1bc3b..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/README.md +++ /dev/null @@ -1,26 +0,0 @@ -E2E tests concerning Polkadot Governance and the Collectives Parachain. The tests run by the Parachain Integration Tests -[tool](https://github.com/paritytech/parachains-integration-tests/). - -# Requirements -The tests require some changes to the regular production runtime builds: - -## RelayChain runtime -1. Alice has SUDO -2. Public Referenda `StakingAdmin`, `FellowshipAdmin` tracks settings (see the corresponding keys of the `TRACKS_DATA` - constant in the `governance::tracks` module of the Relay Chain runtime crate): -``` yaml -prepare_period: 5 Block, -decision_period: 1 Block, -confirm_period: 1 Block, -min_enactment_period: 1 Block, -``` - -## Collectives runtime -1. Fellowship Referenda `Fellows` track settings (see the corresponding key of the `TRACKS_DATA` constant in the - `fellowship::tracks` module of the Collectives runtime crate): -``` yaml -prepare_period: 5 Block, -decision_period: 1 Block, -confirm_period: 1 Block, -min_enactment_period: 1 Block, -``` diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/0_init.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/0_init.yml deleted file mode 100644 index 33f4d603e2a74b1275186fd5f237099d0c531648..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/0_init.yml +++ /dev/null @@ -1,166 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - assethub_parachain: &assethub_parachain - wsPort: 9810 - paraId: &sp_id 1000 - variables: - xcm_version: &xcm_version 3 - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - accounts: - alice_signer: &alice_signer //Alice - decodedCalls: - ap_force_xcm_version: - chain: *collectives_parachain - pallet: polkadotXcm - call: forceXcmVersion - args: [ - { # location - parents: 1, - interior: Here - }, - *xcm_version - ] - -tests: - - name: Initialize Chains - its: - - name: XCM supported versions between chains - actions: - - extrinsics: # Relay Chain sets supported version for Collectives Parachain - - chain: *relay_chain - sudo: true - signer: *alice_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *cp_id - } - } - }, - *xcm_version - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *cp_id }}}, version: *xcm_version } - - extrinsics: # Collectives Parachain sets supported version for Relay Chain through it - - chain: *relay_chain - signer: *alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 2200000000, # 2_200_000_000 - proofSize: 200000, # 200_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 200000000, # 200_000_000 - proofSize: 0, - }, - call: $ap_force_xcm_version - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - - name: polkadotXcm.SupportedVersionChanged - chain: *collectives_parachain - result: { location: { parents: 1, interior: Here }, version: *xcm_version } - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '2,200,000,000', proofSize: 0 }}} - - extrinsics: # Relay Chain sets supported version for AssetHub Parachain - - chain: *relay_chain - sudo: true - signer: *alice_signer - pallet: xcmPallet - call: forceXcmVersion - args: [ - { # location - parents: 0, - interior: { - X1: { - Parachain: *sp_id - } - } - }, - *xcm_version - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.SupportedVersionChanged - result: { location: { parents: 0, interior: { X1: { Parachain: *sp_id } } }, version: *xcm_version } - - extrinsics: # AssetHub Parachain sets supported version for Relay Chain through it - - chain: *relay_chain - signer: *alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { 0, interior: { x1: { parachain: *sp_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 2200000000, # 2_200_000_000 - proofSize: 200000, # 200_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 200000000, # 200_000_000 - proofSize: 0, - }, - call: $ap_force_xcm_version - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - - name: polkadotXcm.SupportedVersionChanged - chain: *assethub_parachain - result: { location: { parents: 1, interior: Here }, version: *xcm_version } - - name: dmpQueue.ExecutedDownward - chain: *assethub_parachain - result: { outcome: { Complete: {} } } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/1_teleport.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/1_teleport.yml deleted file mode 100644 index cda04859b195a6158f7ca97a0d34721d3287de76..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/1_teleport.yml +++ /dev/null @@ -1,168 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - variables: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - accounts: - alice_signer: &acc_alice_signer //Alice - alice_account32: &acc_alice_acc32 '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - alice_ss58: &acc_alice_ss58 '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5' - checking_account: &checking_account '13UVJyLnbVp9x5XDyJv8g8r3UddNwBrdaH7AADCmw9XQWvYW' - -tests: - - name: Teleport assets from Relay Chain to Collectives Parachain successful. - before: - - name: Get the Alice balances on Relay & Collectives Chains. - actions: - - queries: - balance_rc_alice_1: - chain: *relay_chain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - balance_cp_alice_1: - chain: *collectives_parachain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - its: - - name: Teleport assets from Relay Chain to Collectives Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - pallet: xcmPallet - call: teleportAssets - args: [ - { v3: { 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { v3: { parents: 0, interior: { x1: { accountId32: { id: *acc_alice_acc32 }}}}}, # beneficiary - { - v3: [ - # { - # # TODO use a separate Assets to pay a fee, to receive an exact amount of assets on beneficiary account. - # # a call with two assets fails with an error right now. - # id: { concrete: { 0, interior: { here: true }}}, - # fun: { fungible: 1000000000000 } # 1_000_000_000_000 - # }, - { - id: { concrete: { 0, interior: { here: true }}}, - fun: { fungible: 20000000000000 } # 20_000_000_000_000 - } - ] - }, # assets - 0, # feeAssetItem - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '4,000,000,000', proofSize: 0 }}} - - queries: - balance_rc_alice_2: - chain: *relay_chain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - balance_cp_alice_2: - chain: *collectives_parachain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - - name: Alice deposit check, balance decreased on Relay Chain, increased on Collectives. - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_rc_alice_1, - after: $balance_rc_alice_2, - } - } - ] - balanceIncreased: - args: [ - { - balances: { - before: $balance_cp_alice_1, - after: $balance_cp_alice_2, - } - } - ] - - - name: Teleport assets from Collectives Parachain to Relay Chain successful - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *acc_alice_signer - pallet: polkadotXcm - call: teleportAssets - args: [ - { v3: { parents: 1, interior: { here: true }}}, # destination - { v3: { parents: 0, interior: { x1: { accountId32: { id: *acc_alice_acc32 }}}}}, # beneficiary - { - v3: [ - { - id: { concrete: { parents: 1, interior: { here: true }}}, - fun: { fungible: 10000000000000 } # 10_000_000_000_000 - } - ] - }, # assets - 0, # feeAssetItem - ] - events: - - name: balances.Withdraw - result: { who: *acc_alice_ss58, amount: 10000000000000 } - - name: polkadotXcm.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: 0 }}} - - name: balances.Withdraw - chain: *relay_chain - result: { who: *checking_account, amount: 10000000000000 } # amount received and withdrawn from registry account - - name: messageQueue.Processed - chain: *relay_chain - threshold: *weight_threshold - result: { origin: { Ump: { Para: *cp_id } }, weightUsed: { refTime: '4,000,000,000', proofSize: '0' }, success: true } - - queries: - balance_rc_alice_3: - chain: *relay_chain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - balance_cp_alice_3: - chain: *collectives_parachain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - - - name: Alice deposit check, balance decreased on Collectives, increased on Relay Chain. - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_cp_alice_2, - after: $balance_cp_alice_3, - } - } - ] - balanceIncreased: - args: [ - { - balances: { - before: $balance_rc_alice_2, - after: $balance_rc_alice_3, - } - } - ] -# TODO (P2) assert Alice balance before and after teleport (see example in kick_member test) -# TODO (P1) test: teleport of non relay chain assets fails diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/2_reserve.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/2_reserve.yml deleted file mode 100644 index bd17f07524a2ddff5e26fe06057fa7dbd5a288be..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/2_reserve.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - variables: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - chains: - accounts: - alice_signer: &alice_signer //Alice - alice_account32: &alice_acc32 '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - -tests: - - name: Reserve assets from Relay Chain to Collectives Parachain fails - its: - - name: Reserve assets from Relay Chain to Collectives Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: xcmPallet - call: reserveTransferAssets - args: [ - { v3: { 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { v3: { parents: 0, interior: { x1: { accountId32: { id: *alice_acc32 }}}}}, # beneficiary - { - v3: [ - { - id: { concrete: { 0, interior: { here: true }}}, - fun: { fungible: 20000000000000 } # 20_000_000_000_000 - } - ] - }, # assets - 0, # feeAssetItem - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '2,000,000,000', proofSize: 0 }}} - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { - outcome: { - Incomplete: [ - { refTime: '1,000,000,000', proofSize: 0 }, - UntrustedReserveLocation - ] - } - } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/3_hrmp-open-channels.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/3_hrmp-open-channels.yml deleted file mode 100644 index 17a16d9ccd7da8e0d7fc53973987bb0ed117705b..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/0_xcm/3_hrmp-open-channels.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - assethub_parachain: &assethub_parachain - wsPort: 9810 - paraId: &sp_id 1000 - variables: - chains: - accounts: - alice_signer: &alice_signer //Alice - hrmp: - proposed_max_capacity: &hrmp_proposed_max_capacity 8 - proposed_max_message_size: &hrmp_proposed_max_message_size 8192 -tests: - - name: HRMP - describes: - - name: Force Open HRMP Channel From Collectives Parachain → AssetHub Parachain - its: - - name: Alice calls hrmp.forceOpenHrmpChannel - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *cp_id, # sender - *sp_id, # recipient - *hrmp_proposed_max_capacity, # proposedMaxCapacity - *hrmp_proposed_max_message_size # proposedMaxMessageSize - ] - events: - - name: hrmp.HrmpChannelForceOpened - result: { - sender: *cp_id, - recipient: *sp_id, - proposed_max_capacity: *hrmp_proposed_max_capacity, - proposed_max_message_size: *hrmp_proposed_max_message_size - } - - name: Force Open HRMP Channel From AssetHub Parachain → Collectives Parachain - its: - - name: Alice calls hrmp.forceOpenHrmpChannel - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - sudo: true - pallet: hrmp - call: forceOpenHrmpChannel - args: [ - *sp_id, # sender - *cp_id, # recipient - *hrmp_proposed_max_capacity, # proposedMaxCapacity - *hrmp_proposed_max_message_size # proposedMaxMessageSize - ] - events: - - name: hrmp.HrmpChannelForceOpened - result: { - sender: *sp_id, - recipient: *cp_id, - proposed_max_capacity: *hrmp_proposed_max_capacity, - proposed_max_message_size: *hrmp_proposed_max_message_size - } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/0_join_alliance_fails.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/0_join_alliance_fails.yml deleted file mode 100644 index 9aff8b1db1023ec8bb011d60d2eaf2dd76886f5f..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/0_join_alliance_fails.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -settings: - chains: - relay_chain: - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - variables: - accounts: - alice_signer: &alice_signer //Alice - -tests: - - name: Alice fails to join an the Alliance, since it is not initialized yet. - its: - - name: Alice joins alliance - actions: - - extrinsics: # Relay Chain sets supported version for Asset Parachain - - chain: *collectives_parachain - signer: *alice_signer - pallet: alliance - call: joinAlliance - args: [] - events: - - name: system.ExtrinsicFailed - result: { - dispatchError: { Module: { index: 50, error: '0x00000000' }} - } - # TODO assert with Alliance Error variant - alliance.AllianceNotYetInitialized - # issue - https://github.com/paritytech/parachains-integration-tests/issues/59 diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/1_init_alliance.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/1_init_alliance.yml deleted file mode 100644 index 1e01c701744a82cfb6ff1ad716bd1bcb91351108..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/1_init_alliance.yml +++ /dev/null @@ -1,256 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &coll_para_id 1001 - variables: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - accounts: - alice_signer: &acc_alice_signer //Alice - liam_account32: &acc_liam_acc32 "0x3614671a5de540d891eb8c4939c8153a4aa790602b347c18177b86d0fc546221" # //Liam - olivia_account32: &acc_olivia_acc32 "0x24ee8a659c6716fe9f7cb4e9e028602aa12867654ca02737da9171b7ff697d5c" # //Olivia - noah_account32: &acc_noah_acc32 "0x9c6ad3bc3aa2f1b2e837898e6da9980445f7ef8b3eee0b8c8e305f8cfae68517" # //Noah - emma_account32: &acc_emma_acc32 "0x8ac272b333ba1127c8db57fa777ec820b24598a236efa648caf0d26d86f64572" # //Emma - james_account32: &acc_james_acc32 "0x9a52805151a0b5effc084af9264011139872a21a3950cb9ae0b2955c4bf92c18" # //James - ava_account32: &acc_ava_acc32 "0x348ef0b8776adbc09c862ddc29b1d193b9e24738e54eea3b0609c83856dc101c" # //Ava - mia_account32: &acc_mia_acc32 "0xaebf15374cf7e758d10232514c569a7abf81cc1b8f1e81a73dbc608a0e335264" # //Mia - decodedCalls: - init_alliance_members: - chain: *collectives_parachain - pallet: alliance - call: initMembers - args: [ - [ - *acc_liam_acc32, - *acc_olivia_acc32, - *acc_noah_acc32, - *acc_emma_acc32, - *acc_james_acc32, - *acc_ava_acc32 - ], - [ - *acc_mia_acc32 - ] - ] - init_alliance_voting_members: - chain: *collectives_parachain - pallet: alliance - call: initMembers - args: [ - [ - *acc_liam_acc32, - *acc_olivia_acc32, - *acc_noah_acc32, - *acc_emma_acc32, - *acc_james_acc32, - *acc_ava_acc32, - *acc_mia_acc32 - ], - [] - ] - disband: - chain: *collectives_parachain - pallet: alliance - call: disband - args: [ - { - fellowMembers: 6, - allyMembers: 1 - } - ] - -tests: - - name: Alliance initiated with the root call, second init call fails. Alliance disband and set again. - its: - - name: Alliance initiated, founders and fellows are set. - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *coll_para_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 3000000000, # 3_000_000_000 - proofSize: 2000000, # 2_000_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 1000000000, # 1_000_000_000 - proofSize: 1000000, # 1_000_000 - }, - call: $init_alliance_members - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - result: { origin: { parents: 0, interior: Here }, destination: { parents: 0, interior: { X1: { Parachain: *coll_para_id }}}} - - name: alliance.MembersInitialized - chain: *collectives_parachain - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: '1,000,000' }}} - - - name: Alliance init call fails. - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *coll_para_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 3000000000, # 3_000_000_000 - proofSize: 2000000, # 2_000_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 1000000000, # 1_000_000_000 - proofSize: 1000000, # 1_000_000 - }, - call: $init_alliance_voting_members - } - } - ] - } - ] - events: - # TODO can not currently assert variant AllianceAlreadyInitialized, XCM Transact fails silently - # issue - https://github.com/paritytech/polkadot/issues/4623 - # Next test with a disband call will fail, if this call does not fail, - # since a witness data from a disband call will be invalid. - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - result: { origin: { parents: 0, interior: Here }, destination: { parents: 0, interior: { X1: { Parachain: *coll_para_id }}}} - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: '1,000,000' }}} - - - name: Alliance disbanded and initialized again. - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *coll_para_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 5000000000, # 3_000_000_000 - proofSize: 1000000, # 1_000_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 3000000000, # 3_000_000_000 - proofSize: 200000, # 200_000 - }, - call: $disband - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - result: { origin: { parents: 0, interior: Here }, destination: { parents: 0, interior: { X1: { Parachain: *coll_para_id }}}} - - name: alliance.AllianceDisbanded - chain: *collectives_parachain - result: { fellowMembers: 6, allyMembers: 1, unreserved: 0 } - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,321,495,872', proofSize: '181,779' }}} - - name: Alliance initiated, founders and fellows are set. - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *coll_para_id }}}}, # destination - { - v3: [ # message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 3000000000, # 3_000_000_000 - proofSize: 2000000, # 2_000_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 1000000000, # 1_000_000_000 - proofSize: 1000000, # 1_000_000 - }, - call: $init_alliance_members - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - result: { origin: { parents: 0, interior: Here }, destination: { parents: 0, interior: { X1: { Parachain: *coll_para_id }}}} - - name: alliance.MembersInitialized - chain: *collectives_parachain - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { outcome: { Complete: { refTime: '3,000,000,000', proofSize: '1,000,000' }}} diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/2_join_alliance_fails.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/2_join_alliance_fails.yml deleted file mode 100644 index 2afdadae60224f67b5aede4798bc8c48387b6336..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/2_join_alliance_fails.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -settings: - chains: - relay_chain: - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: 1001 - variables: - accounts: - liam_signer: &acc_liam_signer //Liam - -tests: - - name: Liam fails to join an the Alliance, Liam is already a member. - its: - - name: Alice joins alliance - actions: - - extrinsics: # Relay Chain sets supported version for Asset Parachain - - chain: *collectives_parachain - signer: *acc_liam_signer - pallet: alliance - call: joinAlliance - args: [] - events: - - name: system.ExtrinsicFailed - result: { - dispatchError: { Module: { index: 50, error: '0x02000000' }} - } - # TODO assert with Alliance Error variant - alliance.AllianceNotYetInitialized - # issue - https://github.com/paritytech/parachains-integration-tests/issues/59 diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/3_kick_member.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/3_kick_member.yml deleted file mode 100644 index a5941cb47234ee2866a095feb7198ed7b88f6860..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/1_alliance/3_kick_member.yml +++ /dev/null @@ -1,175 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - variables: - weight_threshold: &weight_threshold { refTime: [10, 10], proofSize: [10, 10] } - init_teleport_amount: &init_teleport_amount 20000000000000 # 20_000_000_000_000 - accounts: - alice_signer: &acc_alice_signer //Alice - treasury_account32: &acc_treasury_acc32 '0x6d6f646c70792f74727372790000000000000000000000000000000000000000' - alice_account32: &acc_alice_acc32 '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - alice_ss58: &acc_alice_ss58 '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5' - decodedCalls: - alliance_kick_member: - chain: *collectives_parachain - pallet: alliance - call: kickMember - args: [ - {Id: *acc_alice_acc32} - ] - -tests: - - name: Member kicked out, deposited assets slashed and teleported to Relay Chain treasury. - before: - - name: DEPENDENCY | Do a 'limitedTeleportAssets' from the Relay Chain to the Collectives Parachain - actions: - - extrinsics: - - chain: *relay_chain - signer: *acc_alice_signer - pallet: xcmPallet - call: limitedTeleportAssets - args: [ - { v3: { 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { v3: { parents: 0, interior: { x1: { accountId32: { id: *acc_alice_acc32 }}}}}, # beneficiary - { v3: [ { id: { concrete: { 0, interior: { here: true }}}, fun: { fungible: *init_teleport_amount }} ] }, # assets - 0, # feeAssetItem - { unlimited: true } # weightLimit - ] - events: - - name: xcmPallet.Attempted - threshold: *weight_threshold - result: { - outcome: { Complete: { refTime: '3,000,000,000', proofSize: 0 }} - } - - name: balances.Deposit - chain: *collectives_parachain - result: { who: *acc_alice_ss58 } - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { - outcome: { Complete: { refTime: '4,000,000,000', proofSize: 0 }} - } - - name: Get the balances of the Relay Chain's treasury & Collectives parachain's future alliance member - actions: - - queries: - balance_rc_treasury_before: - chain: *relay_chain - pallet: system - call: account - args: [ *acc_treasury_acc32 ] - balance_cp_alice_before: - chain: *collectives_parachain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - its: - - name: Alice joins alliance - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *acc_alice_signer - pallet: alliance - call: joinAlliance - args: [] - events: - - name: balances.Reserved - chain: *collectives_parachain - result: { who: *acc_alice_ss58, amount: 10000000000000 } - - name: alliance.NewAllyJoined - result: {ally: *acc_alice_ss58, reserved: 10000000000000 } - - queries: - balance_cp_alice_after: - chain: *collectives_parachain - pallet: system - call: account - args: [ *acc_alice_acc32 ] - - name: Alice deposit check, balance decreased - actions: - - asserts: - balanceDecreased: - args: [ - { - balances: { - before: $balance_cp_alice_before, - after: $balance_cp_alice_after, - } - # TODO (P3) set `amount` and `fee` for more strict assert - } - ] - - name: Kick Alice from alliance - actions: - - extrinsics: # Asset Parachain sets supported version for Relay Chain through it - - chain: *relay_chain - signer: *acc_alice_signer - sudo: true - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: { - limited: { - refTime: 4000000000, # 4_000_000_000 - proofSize: 2000000, # 2_000_000 - }, - } - } - }, - { - Transact: { - originKind: Superuser, - requireWeightAtMost: { - refTime: 2000000000, # 2_000_000_000 - proofSize: 1000000, # 1_000_000 - }, - call: $alliance_kick_member - } - } - ] - } - ] - events: - - name: sudo.Sudid - result: { sudoResult: Ok } - - name: xcmPallet.Sent - result: { origin: { parents: 0, interior: Here }, destination: { parents: 0, interior: { X1: { Parachain: *cp_id }}}} - - name: alliance.MemberKicked - chain: *collectives_parachain - result: { member: *acc_alice_ss58, slashed: 10000000000000 } - - name: dmpQueue.ExecutedDownward - chain: *collectives_parachain - threshold: *weight_threshold - result: { - outcome: { Complete: { refTime: '4,000,000,000', proofSize: '1,000,000' }} - } - - name: messageQueue.Processed - result: { origin: { Ump: { Para: *cp_id }}, success: true } - - - queries: - balance_rc_treasury_after: - chain: *relay_chain - pallet: system - call: account - args: [ *acc_treasury_acc32 ] - - name: Slashed balance appears on the relay chain treasury account - actions: - - asserts: - balanceIncreased: - args: [ - { - balances: { - before: $balance_rc_treasury_before, - after: $balance_rc_treasury_after, - } - # TODO (P3) set `amount` and `fee` for more strict assert - } - ] diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/2_opengov/0_assethub.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/2_opengov/0_assethub.yml deleted file mode 100644 index c53efff51fbfb218bd8bf57d4876084bbaccb30d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/2_opengov/0_assethub.yml +++ /dev/null @@ -1,149 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - assethub_parachain: &assethub_parachain - wsPort: 9810 - paraId: &ap_id 1000 - variables: - proposal_index: &proposal_index 0 - chains: - accounts: - alice_signer: &alice_signer //Alice - bob_signer: &bob_signer //Bob - decodedCalls: - set_candidates_ap: - chain: *assethub_parachain - encode: true - pallet: collatorSelection - call: setDesiredCandidates - args: [ - 3 - ] - send_set_candidates_rc: - chain: *relay_chain - encode: false - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *ap_id }}}}, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 200000000, # 200_000_000 - proofSize: 100000, # 100_000 - }, - call: $set_candidates_ap - } - } - ] - } - ] -tests: - - name: OpenGov - describes: - - name: Set desired candidates on AssetHub from Relay Chain OpenGov Staking track - its: - - name: Note preimage from xcm send set_desired_candidates call - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: preimage - call: notePreimage - args: [ - $send_set_candidates_rc - ] - events: - - name: preimage.Noted - result: {hash_: $send_set_candidates_rc.hash } - - name: Submit a proposal to set desired candidates - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: referenda - call: submit - args: [ - { - "Origins": "StakingAdmin", - }, - { - "Lookup": { - "hash_": $send_set_candidates_rc.hash, - "len": $send_set_candidates_rc.len, - }, - }, - { - "After": 1, - }, - ] - events: - - name: referenda.Submitted - result: { - index: *proposal_index, - proposal: { Lookup: { hash_: $send_set_candidates_rc.hash, len: $send_set_candidates_rc.len }} - } - - name: Alice Vote Aye - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: convictionVoting - call: vote - args: [ - *proposal_index, - { - "Standard": { - "vote": { - "aye": true, - "conviction": "Locked1x", - }, - "balance": 200000000000000, - } - }, - ] # TODO no event to catch https://github.com/paritytech/substrate/issues/14687 - - name: Bob Vote Aye - actions: - - extrinsics: - - chain: *relay_chain - signer: *bob_signer - pallet: convictionVoting - call: vote - args: [ - *proposal_index, - { - "Standard": { - "vote": { - "aye": true, - "conviction": "Locked1x", - }, - "balance": 200000000000000, - } - }, - ] # TODO no event to catch https://github.com/paritytech/substrate/issues/14687 - - name: Submit the decision deposit - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: referenda - call: placeDecisionDeposit - args: [ - *proposal_index, - ] - events: - - name: referenda.DecisionDepositPlaced - result: { index: *proposal_index } - - name: collatorSelection.NewDesiredCandidates - chain: *assethub_parachain - result: { desiredCandidates: 3 } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/0_init.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/0_init.yml deleted file mode 100644 index 1e4b2dabe21178d3b7c1bd6087c9015665ae93e0..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/0_init.yml +++ /dev/null @@ -1,209 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - variables: - proposal_index: &proposal_index 1 - chains: - accounts: - alice_signer: &alice_signer //Alice - bob_signer: &bob_signer //Bob - alice_account32: &acc_alice_acc32 '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' - alice_ss58: &acc_alice_ss58 '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5' - decodedCalls: - fellowship_induct_alice_cp: - chain: *collectives_parachain - encode: true - pallet: fellowshipCore - call: induct - args: [ - *acc_alice_acc32 - ] - fellowship_promote_1_alice_cp: - chain: *collectives_parachain - encode: true - pallet: fellowshipCore - call: promote - args: [ - *acc_alice_acc32, - 1 - ] - fellowship_promote_2_alice_cp: - chain: *collectives_parachain - encode: true - pallet: fellowshipCore - call: promote - args: [ - *acc_alice_acc32, - 2 - ] - fellowship_promote_3_alice_cp: - chain: *collectives_parachain - encode: true - pallet: fellowshipCore - call: promote - args: [ - *acc_alice_acc32, - 3 - ] - send_init_fellowship_rc: - chain: *relay_chain - encode: false - pallet: xcmPallet - call: send - args: [ - { v3: { parents: 0, interior: { x1: { parachain: *cp_id }}}}, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { # since batch_all not yet allowed over xcm, we have to send multiple `Transact`. - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 1500000000, # 1_500_000_000 - proofSize: 10000, # 10_000 - }, - call: $fellowship_induct_alice_cp - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 1500000000, # 1_500_000_000 - proofSize: 10000, # 10_000 - }, - call: $fellowship_promote_1_alice_cp - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 1500000000, # 1_500_000_000 - proofSize: 10000, # 10_000 - }, - call: $fellowship_promote_2_alice_cp - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 1500000000, # 1_500_000_000 - proofSize: 10000, # 10_000 - }, - call: $fellowship_promote_3_alice_cp - } - } - ] - } - ] - -tests: - - name: Fellowship - describes: - - name: Init the Fellowship - its: - - name: Note preimage from init fellowship call - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: preimage - call: notePreimage - args: [ - $send_init_fellowship_rc - ] - events: - - name: preimage.Noted - result: { hash_: $send_init_fellowship_rc.hash } - - name: Submit a proposal to init the Fellowship - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: referenda - call: submit - args: [ - { - "Origins": "FellowshipAdmin", - }, - { - "Lookup": { - "hash_": $send_init_fellowship_rc.hash, - "len": $send_init_fellowship_rc.len, - }, - }, - { - "After": 1, - }, - ] - events: - - name: referenda.Submitted - result: { - index: *proposal_index, - proposal: { Lookup: { hash_: $send_init_fellowship_rc.hash, len: $send_init_fellowship_rc.len }} - } - - name: Alice Vote Aye - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: convictionVoting - call: vote - args: [ - *proposal_index, - { - "Standard": { - "vote": { - "aye": true, - "conviction": "Locked1x", - }, - "balance": 200000000000000, - } - }, - ] # TODO no Aye event to catch https://github.com/paritytech/substrate/issues/14687 - - name: Bob Vote Aye - actions: - - extrinsics: - - chain: *relay_chain - signer: *bob_signer - pallet: convictionVoting - call: vote - args: [ - *proposal_index, - { - "Standard": { - "vote": { - "aye": true, - "conviction": "Locked1x", - }, - "balance": 200000000000000, - } - }, - ] # TODO no Aye event to catch https://github.com/paritytech/substrate/issues/14687 - - name: Submit the decision deposit - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: referenda - call: placeDecisionDeposit - args: [ - *proposal_index, - ] - events: - - name: referenda.DecisionDepositPlaced - result: { index: *proposal_index } - - name: fellowshipCollective.MemberAdded - chain: *collectives_parachain - result: { who: *acc_alice_ss58 } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/1_whitelist_call.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/1_whitelist_call.yml deleted file mode 100644 index 5991c7ae2f8a2d44c87ab7d4bf8d8501b790f254..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/1_whitelist_call.yml +++ /dev/null @@ -1,146 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - variables: - fellows_proposal_index: &fellows_proposal_index 0 - chains: - accounts: - alice_signer: &alice_signer //Alice - decodedCalls: - remark_rc: - chain: *relay_chain - encode: false - pallet: system - call: remark - args: [ - "0x10" - ] - whitelist_remark_rc: - chain: *relay_chain - encode: true - pallet: whitelist - call: whitelistCall - args: [ - $remark_rc.hash - ] - send_whitelist_remark_cp: - chain: *collectives_parachain - encode: false - pallet: polkadotXcm - call: send - args: [ - { v3: { parents: 1, interior: { here: true }}}, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 500000000, # 500_000_000 - proofSize: 20000, # 20_000 - }, - call: $whitelist_remark_rc - } - } - ] - } - ] - -tests: - - name: Fellowship - describes: - - name: The Fellowship white list the call - its: - - name: Note preimage from the whitelist call on the Relay Chain - actions: - - extrinsics: - - chain: *relay_chain - signer: *alice_signer - pallet: preimage - call: notePreimage - args: [ - $remark_rc - ] - events: - - name: preimage.Noted - result: { hash_: $remark_rc.hash } - - name: Note preimage from the xcm send call to white list the call above - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: preimage - call: notePreimage - args: [ - $send_whitelist_remark_cp, - ] - events: - - name: preimage.Noted - result: { hash_: $send_whitelist_remark_cp.hash } - - name: Submit a proposal to while list the call - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipReferenda - call: submit - args: [ - { - "FellowshipOrigins": "Fellows", - }, - { - "Lookup": { - "hash_": $send_whitelist_remark_cp.hash, - "len": $send_whitelist_remark_cp.len, - }, - }, - { - "After": 1, - }, - ] - events: - - name: fellowshipReferenda.Submitted - result: { - index: *fellows_proposal_index, - proposal: { Lookup: { hash_: $send_whitelist_remark_cp.hash, len: $send_whitelist_remark_cp.len}} - } - - name: Vote Aye - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipCollective - call: vote - args: [ - *fellows_proposal_index, - true, - ] - events: - - name: fellowshipCollective.Voted - result: { poll: *fellows_proposal_index, vote: { Aye: 1 } } - - name: Submit the decision deposit - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipReferenda - call: placeDecisionDeposit - args: [ - *fellows_proposal_index, - ] - events: - - name: fellowshipReferenda.DecisionDepositPlaced - result: {index: *fellows_proposal_index} - - name: whitelist.CallWhitelisted - chain: *relay_chain - result: { callHash: $remark_rc.hash } diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/2_assethub.yml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/2_assethub.yml deleted file mode 100644 index c0805594808cfe6cc900080953122f12174ad528..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/3_fellowship/2_assethub.yml +++ /dev/null @@ -1,126 +0,0 @@ ---- -settings: - chains: - relay_chain: &relay_chain - wsPort: 9700 - collectives_parachain: &collectives_parachain - wsPort: 9710 - paraId: &cp_id 1001 - assethub_parachain: &assethub_parachain - wsPort: 9810 - paraId: &ap_id 1000 - variables: - fellows_proposal_index: &fellows_proposal_index 1 - chains: - accounts: - alice_signer: &alice_signer //Alice - - decodedCalls: - xcmp_resume_execution_ap: - chain: *assethub_parachain - encode: true - pallet: xcmpQueue - call: resumeXcmExecution - args: [] - send_xcmp_resume_execution_cp: - chain: *collectives_parachain - encode: false - pallet: polkadotXcm - call: send - args: [ - { v3: { parents: 1, interior: { x1: { parachain: *ap_id }}}}, # destination - { - v3: [ #message - { - UnpaidExecution: { - weightLimit: Unlimited - } - }, - { - Transact: { - originKind: Xcm, - requireWeightAtMost: { - refTime: 300000000, # 300_000_000 - proofSize: 10000, # 10_000 - }, - call: $xcmp_resume_execution_ap - } - } - ] - } - ] - -tests: - - name: Fellowship - describes: - - name: The Fellowship resume xcm execution for the xcmp queue on AssetHub - its: - - name: Note preimage from the xcm send call to suspend_xcm_execution - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: preimage - call: notePreimage - args: [ - $send_xcmp_resume_execution_cp - ] - events: - - name: preimage.Noted - result: {hash_: $send_xcmp_resume_execution_cp.hash } - - name: Submit a proposal to resume xcm execution on AssetHub - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipReferenda - call: submit - args: [ - { - "FellowshipOrigins": "Fellows", - }, - { - "Lookup": { - "hash_": $send_xcmp_resume_execution_cp.hash, - "len": $send_xcmp_resume_execution_cp.len, - }, - }, - { - "After": 1, - }, - ] - events: - - name: fellowshipReferenda.Submitted - result: { - index: 1, - proposal: {Lookup: {hash_: $send_xcmp_resume_execution_cp.hash, len: $send_xcmp_resume_execution_cp.len}} - } - - name: Vote Aye - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipCollective - call: vote - args: [ - *fellows_proposal_index, - true, - ] - events: - - name: fellowshipCollective.Voted - result: { poll: *fellows_proposal_index, vote: { Aye: 1 } } - - name: Submit the decision deposit - actions: - - extrinsics: - - chain: *collectives_parachain - signer: *alice_signer - pallet: fellowshipReferenda - call: placeDecisionDeposit - args: [ - *fellows_proposal_index, - ] - events: - - name: fellowshipReferenda.DecisionDepositPlaced - result: {index: *fellows_proposal_index} - - name: xcmpQueue.Success - chain: *assethub_parachain diff --git a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/config.toml b/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/config.toml deleted file mode 100644 index 20fda92bd08f5dd067d40e197ee7ea7dd151522b..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/e2e/collectives/collectives-polkadot/config.toml +++ /dev/null @@ -1,42 +0,0 @@ -[relaychain] -default_command = "./bin/polkadot" -default_args = [ "-lparachain=trace", "-lxcm=trace" ] -chain = "polkadot-local" - - [[relaychain.nodes]] - name = "alice" - ws_port = 9700 - validator = true - args = ["--state-cache-size=0"] - - [[relaychain.nodes]] - name = "bob" - ws_port = 9701 - validator = true - - [[relaychain.nodes]] - name = "charlie" - ws_port = 9702 - validator = true - - [[relaychain.nodes]] - name = "dave" - ws_port = 9703 - validator = true - -[[parachains]] -id = 1001 -chain = "collectives-polkadot-local" -cumulus_based = true - - [[parachains.collators]] - name = "collator1" - ws_port = 9710 - command = "./bin/polkadot-parachain" - args = [ "-lxcm=trace", "--state-cache-size=0" ] - - [[parachains.collators]] - name = "collator2" - ws_port = 9711 - command = "./bin/polkadot-parachain" - args = ["-lxcm=trace"] diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/Cargo.toml deleted file mode 100644 index db58d8d330391c69ae01f99aec23c03278616829..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/Cargo.toml +++ /dev/null @@ -1,56 +0,0 @@ -[package] -name = "asset-hub-rococo-integration-tests" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -description = "Asset Hub 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} -frame-system = { path = "../../../../../../substrate/frame/system", 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} - -# Polkadot -polkadot-core-primitives = { path = "../../../../../../polkadot/core-primitives", default-features = false} -polkadot-parachain-primitives = { path = "../../../../../../polkadot/parachain", default-features = false} -polkadot-runtime-parachains = { path = "../../../../../../polkadot/runtime/parachains" } -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", default-features = false } - -# Cumulus -asset-test-utils = { path = "../../../../runtimes/assets/test-utils", default-features = false } -parachains-common = { path = "../../../../common" } -asset-hub-rococo-runtime = { path = "../../../../runtimes/assets/asset-hub-rococo" } - -# Local -xcm-emulator = { path = "../../../../../xcm/xcm-emulator", default-features = false} -integration-tests-common = { path = "../../common", default-features = false} - -[features] -runtime-benchmarks = [ - "asset-hub-rococo-runtime/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "integration-tests-common/runtime-benchmarks", - "pallet-asset-conversion/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-parachains/runtime-benchmarks", - "rococo-runtime/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", -] diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/lib.rs deleted file mode 100644 index 42f54bdf49df9f5674360156b5de35db1837f12d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/lib.rs +++ /dev/null @@ -1,92 +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 use asset_test_utils::xcm_helpers; -pub use codec::Encode; -pub use frame_support::{ - assert_err, assert_ok, - pallet_prelude::Weight, - sp_runtime::{AccountId32, DispatchError, DispatchResult}, - traits::fungibles::Inspect, -}; -pub use integration_tests_common::{ - constants::{ - asset_hub_rococo::ED as ASSET_HUB_ROCOCO_ED, rococo::ED as ROCOCO_ED, PROOF_SIZE_THRESHOLD, - REF_TIME_THRESHOLD, XCM_V3, - }, - test_parachain_is_trusted_teleporter, - xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, - AssetHubRococo, AssetHubRococoPallet, AssetHubRococoReceiver, AssetHubRococoSender, - BridgeHubRococo, BridgeHubRococoReceiver, PenpalRococoA, PenpalRococoAPallet, - PenpalRococoAReceiver, PenpalRococoASender, PenpalRococoB, PenpalRococoBPallet, Rococo, - RococoPallet, RococoReceiver, RococoSender, -}; -pub use parachains_common::{AccountId, Balance}; -pub use xcm::{ - prelude::{AccountId32 as AccountId32Junction, *}, - v3::{Error, NetworkId::Rococo as RococoId}, -}; -pub use xcm_emulator::{ - assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, - RelayChain as Relay, Test, TestArgs, TestContext, TestExt, -}; - -pub const ASSET_ID: u32 = 1; -pub const ASSET_MIN_BALANCE: u128 = 1000; -// `Assets` pallet index -pub const ASSETS_PALLET_ID: u8 = 50; - -pub type RelayToSystemParaTest = Test; -pub type SystemParaToRelayTest = Test; -pub type SystemParaToParaTest = Test; - -/// Returns a `TestArgs` instance to de used for the Relay Chain accross integraton tests -pub fn relay_test_args(amount: Balance) -> TestArgs { - TestArgs { - dest: Rococo::child_location_of(AssetHubRococo::para_id()), - beneficiary: AccountId32Junction { - network: None, - id: AssetHubRococoReceiver::get().into(), - } - .into(), - amount, - assets: (Here, amount).into(), - asset_id: None, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -/// Returns a `TestArgs` instance to de used for the System Parachain accross integraton tests -pub fn system_para_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, - assets: MultiAssets, - asset_id: Option, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets, - asset_id, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -#[cfg(test)] -mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/reserve_transfer.rs deleted file mode 100644 index fb25607c635e6122022bebb9ea43ce04d09d79d1..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ /dev/null @@ -1,442 +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. - -use crate::*; -use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; -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(630_092_000, 6_196))); - - assert_expected_events!( - Rococo, - vec![ - // Amount to reserve transfer is transferred to System Parachain's Sovereign account - RuntimeEvent::Balances(pallet_balances::Event::Transfer { from, to, amount }) => { - from: *from == t.sender.account_id, - to: *to == Rococo::sovereign_account_id_of( - t.args.dest - ), - amount: *amount == t.args.amount, - }, - ] - ); -} - -fn system_para_dest_assertions_incomplete(_t: RelayToSystemParaTest) { - AssetHubRococo::assert_dmp_queue_incomplete( - Some(Weight::from_parts(1_000_000_000, 0)), - Some(Error::UntrustedReserveLocation), - ); -} - -fn system_para_to_relay_assertions(_t: SystemParaToRelayTest) { - AssetHubRococo::assert_xcm_pallet_attempted_error(Some(XcmError::Barrier)) -} - -fn system_para_to_para_assertions(t: SystemParaToParaTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 630_092_000, - 6_196, - ))); - - assert_expected_events!( - AssetHubRococo, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereing account - 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, - }, - ] - ); -} - -fn system_para_to_para_assets_assertions(t: SystemParaToParaTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 676_119_000, - 6196, - ))); - - assert_expected_events!( - AssetHubRococo, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereing account - RuntimeEvent::Assets( - pallet_assets::Event::Transferred { asset_id, from, to, amount } - ) => { - asset_id: *asset_id == ASSET_ID, - from: *from == t.sender.account_id, - to: *to == AssetHubRococo::sovereign_account_id_of( - t.args.dest - ), - amount: *amount == t.args.amount, - }, - ] - ); -} - -fn relay_limited_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::limited_reserve_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 relay_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::reserve_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, - ) -} - -fn system_para_limited_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::reserve_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, - ) -} - -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { - ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { - ::PolkadotXcm::reserve_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, - ) -} - -/// Limited Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't -/// work -#[test] -fn limited_reserve_transfer_native_asset_from_relay_to_system_para_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = ROCOCO_ED * 1000; - let test_args = TestContext { - sender: RococoSender::get(), - receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(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::(system_para_dest_assertions_incomplete); - test.set_dispatchable::(relay_limited_reserve_transfer_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; - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Limited Reserve Transfers of native asset from System Parachain to Relay Chain shoudln't work -#[test] -fn limited_reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain - let destination = AssetHubRococo::parent_location(); - let beneficiary_id = RococoReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToRelayTest::new(test_args); - - let sender_balance_before = test.sender.balance; - let receiver_balance_before = test.receiver.balance; - - test.set_assertion::(system_para_to_relay_assertions); - test.set_dispatchable::(system_para_limited_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - assert_eq!(sender_balance_before, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work -#[test] -fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = ROCOCO_ED * 1000; - let test_args = TestContext { - sender: RococoSender::get(), - receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(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::(system_para_dest_assertions_incomplete); - test.set_dispatchable::(relay_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - 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) - }); - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work -#[test] -fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain - let destination = AssetHubRococo::parent_location(); - let beneficiary_id = RococoReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToRelayTest::new(test_args); - - let sender_balance_before = test.sender.balance; - let receiver_balance_before = test.receiver.balance; - - test.set_assertion::(system_para_to_relay_assertions); - test.set_dispatchable::(system_para_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - assert_eq!(sender_balance_before, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Limited Reserve Transfers of native asset from System Parachain to Parachain should work -#[test] -fn limited_reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain - let destination = AssetHubRococo::sibling_location_of(PenpalRococoA::para_id()); - let beneficiary_id = PenpalRococoAReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: PenpalRococoAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToParaTest::new(test_args); - - let sender_balance_before = test.sender.balance; - - test.set_assertion::(system_para_to_para_assertions); - // TODO: Add assertion for Penpal runtime. Right now message is failing with - // `UntrustedReserveLocation` - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - - let delivery_fees = AssetHubRococo::execute_with(|| { - xcm_helpers::transfer_assets_delivery_fees::< - ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) - }); - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve - // transfers -} - -/// Reserve Transfers of native asset from System Parachain to Parachain should work -#[test] -fn reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain - let destination = AssetHubRococo::sibling_location_of(PenpalRococoA::para_id()); - let beneficiary_id = PenpalRococoAReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: PenpalRococoAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToParaTest::new(test_args); - - let sender_balance_before = test.sender.balance; - - test.set_assertion::(system_para_to_para_assertions); - // TODO: Add assertion for Penpal runtime. Right now message is failing with - // `UntrustedReserveLocation` - test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - - let delivery_fees = AssetHubRococo::execute_with(|| { - xcm_helpers::transfer_assets_delivery_fees::< - ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) - }); - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve - // transfers -} - -/// Limited Reserve Transfers of a local asset from System Parachain to Parachain should work -#[test] -fn limited_reserve_transfer_asset_from_system_para_to_para() { - // Force create asset from Relay Chain and mint assets for System Parachain's sender account - AssetHubRococo::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubRococoSender::get(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000, - ); - - // Init values for System Parachain - let destination = AssetHubRococo::sibling_location_of(PenpalRococoA::para_id()); - let beneficiary_id = PenpalRococoAReceiver::get(); - let amount_to_send = ASSET_MIN_BALANCE * 1000; - let assets = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) - .into(); - - let system_para_test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: PenpalRococoAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); - - system_para_test.set_assertion::(system_para_to_para_assets_assertions); - // TODO: Add assertions when Penpal is able to manage assets - system_para_test - .set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); - system_para_test.assert(); -} - -/// Reserve Transfers of a local asset from System Parachain to Parachain should work -#[test] -fn reserve_transfer_asset_from_system_para_to_para() { - // Force create asset from Relay Chain and mint assets for System Parachain's sender account - AssetHubRococo::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubRococoSender::get(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000, - ); - - // Init values for System Parachain - let destination = AssetHubRococo::sibling_location_of(PenpalRococoA::para_id()); - let beneficiary_id = PenpalRococoAReceiver::get(); - let amount_to_send = ASSET_MIN_BALANCE * 1000; - let assets = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) - .into(); - - let system_para_test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: PenpalRococoAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); - - system_para_test.set_assertion::(system_para_to_para_assets_assertions); - // TODO: Add assertions when Penpal is able to manage assets - system_para_test - .set_dispatchable::(system_para_to_para_reserve_transfer_assets); - system_para_test.assert(); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/send.rs deleted file mode 100644 index b8ec370e3f8db347bb3394467a5b01efc684aba8..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/send.rs +++ /dev/null @@ -1,102 +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. - -use crate::*; - -/// Relay Chain should be able to execute `Transact` instructions in System Parachain -/// when `OriginKind::Superuser`. -#[test] -fn send_transact_as_superuser_from_relay_to_system_para_works() { - AssetHubRococo::force_create_asset_from_relay_as_root( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubRococoSender::get().into(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ) -} - -/// Parachain should be able to send XCM paying its fee with sufficient asset -/// in the System Parachain -#[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { - let para_sovereign_account = AssetHubRococo::sovereign_account_id_of( - AssetHubRococo::sibling_location_of(PenpalRococoA::para_id()), - ); - - // Force create and mint assets for Parachain's sovereign account - AssetHubRococo::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - para_sovereign_account.clone(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000000, - ); - - // We just need a call that can pass the `SafeCallFilter` - // Call values are not relevant - let call = AssetHubRococo::force_create_asset_call( - ASSET_ID, - para_sovereign_account.clone(), - true, - ASSET_MIN_BALANCE, - ); - - let origin_kind = OriginKind::SovereignAccount; - let fee_amount = ASSET_MIN_BALANCE * 1000000; - let native_asset = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount).into(); - - let root_origin = ::RuntimeOrigin::root(); - let system_para_destination = - PenpalRococoA::sibling_location_of(AssetHubRococo::para_id()).into(); - let xcm = xcm_transact_paid_execution( - call, - origin_kind, - native_asset, - para_sovereign_account.clone(), - ); - - PenpalRococoA::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - root_origin, - bx!(system_para_destination), - bx!(xcm), - )); - - PenpalRococoA::assert_xcm_pallet_sent(); - }); - - AssetHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts(2_176_414_000, 203_593))); - - assert_expected_events!( - AssetHubRococo, - vec![ - RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { - asset_id: *asset_id == ASSET_ID, - owner: *owner == para_sovereign_account, - balance: *balance == fee_amount, - }, - RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => { - asset_id: *asset_id == ASSET_ID, - }, - ] - ); - }); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/teleport.rs deleted file mode 100644 index 4b2ea0e160cb9df2f18f2ed00fa793b0179e363e..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/teleport.rs +++ /dev/null @@ -1,408 +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. - -use crate::*; -use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; -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(631_531_000, 7_186))); - - 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(AssetHubRococo::para_id()), - Some(Weight::from_parts(307_225_000, 7_186)), - ); - - assert_expected_events!( - Rococo, - vec![ - // Amount is witdrawn 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(AssetHubRococo::para_id()), - Some(Weight::from_parts(148_433_000, 3_593)), - ); -} - -fn para_origin_assertions(t: SystemParaToRelayTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 534_872_000, - 7_133, - ))); - - AssetHubRococo::assert_parachain_system_ump_sent(); - - assert_expected_events!( - AssetHubRococo, - 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; - - AssetHubRococo::assert_dmp_queue_complete(Some(Weight::from_parts(165_592_000, 0))); - - assert_expected_events!( - AssetHubRococo, - 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 relay_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::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, - ) -} - -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, - ) -} - -fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::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, - ) -} - -/// 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 test_args = TestContext { - sender: RococoSender::get(), - receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(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(); - - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let destination = AssetHubRococo::parent_location(); - let beneficiary_id = RococoReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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 = AssetHubRococo::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 = ASSET_HUB_ROCOCO_ED * 1000; - let destination = AssetHubRococo::parent_location().into(); - let beneficiary_id = RococoReceiver::get().into(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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 = AssetHubRococo::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); -} - -/// Teleport of native asset from Relay Chain to the System Parachain should work -#[test] -fn teleport_native_assets_from_relay_to_system_para_works() { - // Init values for Relay Chain - let amount_to_send: Balance = ROCOCO_ED * 1000; - let test_args = TestContext { - sender: RococoSender::get(), - receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(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_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); -} - -/// Teleport of native asset from System Parachains to the Relay Chain -/// should work when there is enough balance in Relay Chain's `CheckAccount` -#[test] -fn teleport_native_assets_back_from_system_para_to_relay_works() { - // Dependency - Relay Chain's `CheckAccount` should have enough balance - teleport_native_assets_from_relay_to_system_para_works(); - - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let destination = AssetHubRococo::parent_location(); - let beneficiary_id = RococoReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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_teleport_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - let delivery_fees = AssetHubRococo::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); -} - -/// Teleport of native asset from System Parachain to Relay Chain -/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount` -#[test] -fn teleport_native_assets_from_system_para_to_relay_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; - let destination = AssetHubRococo::parent_location(); - let beneficiary_id = RococoReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubRococoSender::get(), - receiver: RococoReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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_teleport_assets); - test.assert(); - - let delivery_fees = AssetHubRococo::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 does not change - assert_eq!(receiver_balance_after, receiver_balance_before); -} - -#[test] -fn teleport_to_other_system_parachains_works() { - let amount = ASSET_HUB_ROCOCO_ED * 100; - let native_asset: MultiAssets = (Parent, amount).into(); - - test_parachain_is_trusted_teleporter!( - AssetHubRococo, // Origin - AssetHubRococoXcmConfig, // XCM Configuration - vec![BridgeHubRococo], // Destinations - (native_asset, amount) - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml deleted file mode 100644 index 4b6b8874b6a4b2d7c998f15c93715d7c524712e3..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml +++ /dev/null @@ -1,68 +0,0 @@ -[package] -name = "asset-hub-westend-integration-tests" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -description = "Asset Hub 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} -frame-system = { path = "../../../../../../substrate/frame/system", 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-treasury = { path = "../../../../../../substrate/frame/treasury", default-features = false} -pallet-asset-rate = { path = "../../../../../../substrate/frame/asset-rate", default-features = false} - -# Polkadot -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" } -polkadot-runtime-parachains = { path = "../../../../../../polkadot/runtime/parachains" } -xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../polkadot/xcm/xcm-executor", default-features = false} -pallet-xcm = { path = "../../../../../../polkadot/xcm/pallet-xcm", default-features = false} -westend-runtime = { path = "../../../../../../polkadot/runtime/westend", default-features = false } -westend-runtime-constants = { path = "../../../../../../polkadot/runtime/westend/constants", default-features = false } - -# Cumulus -parachains-common = { path = "../../../../common" } -asset-hub-westend-runtime = { path = "../../../../runtimes/assets/asset-hub-westend" } -asset-test-utils = { path = "../../../../runtimes/assets/test-utils", default-features = false } -cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../pallets/dmp-queue" } -cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../pallets/parachain-system" } - -# Local -xcm-emulator = { path = "../../../../../xcm/xcm-emulator", default-features = false} -integration-tests-common = { path = "../../common", default-features = false} - -[features] -runtime-benchmarks = [ - "asset-hub-westend-runtime/runtime-benchmarks", - "cumulus-pallet-parachain-system/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "integration-tests-common/runtime-benchmarks", - "pallet-asset-conversion/runtime-benchmarks", - "pallet-asset-rate/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-treasury/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", - "polkadot-runtime-parachains/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "westend-runtime/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", -] diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/lib.rs deleted file mode 100644 index 0133cdbed5cc8d6e1c85899d3893fa13b24ee2e2..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/lib.rs +++ /dev/null @@ -1,93 +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 use asset_test_utils::xcm_helpers; -pub use codec::Encode; -pub use frame_support::{ - assert_err, assert_ok, - instances::Instance2, - pallet_prelude::Weight, - sp_runtime::{AccountId32, DispatchError, DispatchResult, ModuleError}, - traits::fungibles::Inspect, - BoundedVec, -}; -pub use integration_tests_common::{ - constants::{ - asset_hub_westend::ED as ASSET_HUB_WESTEND_ED, westend::ED as WESTEND_ED, - PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, - }, - test_parachain_is_trusted_teleporter, - xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, - AssetHubWestend, AssetHubWestendPallet, AssetHubWestendReceiver, AssetHubWestendSender, - PenpalWestendA, PenpalWestendAPallet, PenpalWestendAReceiver, PenpalWestendASender, Westend, - WestendPallet, WestendReceiver, WestendSender, -}; -pub use parachains_common::{AccountId, Balance}; -pub use xcm::{ - prelude::{AccountId32 as AccountId32Junction, *}, - v3::{Error, NetworkId::Westend as WestendId}, -}; -pub use xcm_emulator::{ - assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, - RelayChain as Relay, Test, TestArgs, TestContext, TestExt, -}; - -pub const ASSET_ID: u32 = 1; -pub const ASSET_MIN_BALANCE: u128 = 1000; -// `Assets` pallet index -pub const ASSETS_PALLET_ID: u8 = 50; - -pub type RelayToSystemParaTest = Test; -pub type SystemParaToRelayTest = Test; -pub type SystemParaToParaTest = Test; - -/// Returns a `TestArgs` instance to de used for the Relay Chain accross integraton tests -pub fn relay_test_args(amount: Balance) -> TestArgs { - TestArgs { - dest: Westend::child_location_of(AssetHubWestend::para_id()), - beneficiary: AccountId32Junction { - network: None, - id: AssetHubWestendReceiver::get().into(), - } - .into(), - amount, - assets: (Here, amount).into(), - asset_id: None, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -/// Returns a `TestArgs` instance to de used for the System Parachain accross integraton tests -pub fn system_para_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, - assets: MultiAssets, - asset_id: Option, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets, - asset_id, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -#[cfg(test)] -mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/reserve_transfer.rs deleted file mode 100644 index 3b48e6529ff7545c09f7a4c619f4d639f11c7e39..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ /dev/null @@ -1,447 +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. - -use crate::*; -use asset_hub_westend_runtime::xcm_config::XcmConfig; -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(629_384_000, 6_196))); - - assert_expected_events!( - Westend, - vec![ - // Amount to reserve transfer is transferred to System Parachain's Sovereign account - RuntimeEvent::Balances(pallet_balances::Event::Transfer { from, to, amount }) => { - from: *from == t.sender.account_id, - to: *to == Westend::sovereign_account_id_of( - t.args.dest - ), - amount: *amount == t.args.amount, - }, - ] - ); -} - -fn system_para_dest_assertions(_t: RelayToSystemParaTest) { - AssetHubWestend::assert_dmp_queue_error(Error::WeightNotComputable); -} - -fn system_para_to_relay_assertions(_t: SystemParaToRelayTest) { - AssetHubWestend::assert_xcm_pallet_attempted_error(Some(XcmError::Barrier)) -} - -fn system_para_to_para_assertions(t: SystemParaToParaTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 676_119_000, - 6196, - ))); - - assert_expected_events!( - AssetHubWestend, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereing account - 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, - }, - ] - ); -} - -fn system_para_to_para_assets_assertions(t: SystemParaToParaTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 676_119_000, - 6196, - ))); - - assert_expected_events!( - AssetHubWestend, - vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereing account - RuntimeEvent::Assets( - pallet_assets::Event::Transferred { asset_id, from, to, amount } - ) => { - asset_id: *asset_id == ASSET_ID, - from: *from == t.sender.account_id, - to: *to == AssetHubWestend::sovereign_account_id_of( - t.args.dest - ), - amount: *amount == t.args.amount, - }, - ] - ); -} - -fn relay_limited_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::limited_reserve_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 relay_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::reserve_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, - ) -} - -fn system_para_limited_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::reserve_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, - ) -} - -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { - ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { - ::PolkadotXcm::reserve_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, - ) -} - -/// Limited Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't -/// work -#[test] -fn limited_reserve_transfer_native_asset_from_relay_to_system_para_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = WESTEND_ED * 1000; - let test_args = TestContext { - sender: WestendSender::get(), - receiver: AssetHubWestendReceiver::get(), - args: relay_test_args(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::(system_para_dest_assertions); - test.set_dispatchable::(relay_limited_reserve_transfer_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; - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Limited Reserve Transfers of native asset from System Parachain to Relay Chain shoudln't work -#[test] -fn limited_reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain - let destination = AssetHubWestend::parent_location(); - let beneficiary_id = WestendReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToRelayTest::new(test_args); - - let sender_balance_before = test.sender.balance; - let receiver_balance_before = test.receiver.balance; - - test.set_assertion::(system_para_to_relay_assertions); - test.set_dispatchable::(system_para_limited_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - assert_eq!(sender_balance_before, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work -#[test] -fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = WESTEND_ED * 1000; - let test_args = TestContext { - sender: WestendSender::get(), - receiver: AssetHubWestendReceiver::get(), - args: relay_test_args(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::(system_para_dest_assertions); - test.set_dispatchable::(relay_reserve_transfer_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; - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work -#[test] -fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain - let destination = AssetHubWestend::parent_location(); - let beneficiary_id = WestendReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToRelayTest::new(test_args); - - let sender_balance_before = test.sender.balance; - let receiver_balance_before = test.receiver.balance; - - test.set_assertion::(system_para_to_relay_assertions); - test.set_dispatchable::(system_para_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - assert_eq!(sender_balance_before, sender_balance_after); - assert_eq!(receiver_balance_before, receiver_balance_after); -} - -/// Limited Reserve Transfers of native asset from System Parachain to Parachain should work -#[test] -fn limited_reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain - let destination = AssetHubWestend::sibling_location_of(PenpalWestendA::para_id()); - let beneficiary_id = PenpalWestendAReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: PenpalWestendAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToParaTest::new(test_args); - - let sender_balance_before = test.sender.balance; - - test.set_assertion::(system_para_to_para_assertions); - // TODO: Add assertion for Penpal runtime. Right now message is failing with - // `UntrustedReserveLocation` - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - - let delivery_fees = AssetHubWestend::execute_with(|| { - xcm_helpers::transfer_assets_delivery_fees::<::XcmSender>( - test.args.assets.clone(), - 0, - test.args.weight_limit, - test.args.beneficiary, - test.args.dest, - ) - }); - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve - // transfers -} - -/// Reserve Transfers of native asset from System Parachain to Parachain should work -#[test] -fn reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain - let destination = AssetHubWestend::sibling_location_of(PenpalWestendA::para_id()); - let beneficiary_id = PenpalWestendAReceiver::get(); - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: PenpalWestendAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut test = SystemParaToParaTest::new(test_args); - - let sender_balance_before = test.sender.balance; - - test.set_assertion::(system_para_to_para_assertions); - // TODO: Add assertion for Penpal runtime. Right now message is failing with - // `UntrustedReserveLocation` - test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); - test.assert(); - - let sender_balance_after = test.sender.balance; - - let delivery_fees = AssetHubWestend::execute_with(|| { - xcm_helpers::transfer_assets_delivery_fees::<::XcmSender>( - test.args.assets.clone(), - 0, - test.args.weight_limit, - test.args.beneficiary, - test.args.dest, - ) - }); - - assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); - // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve - // transfers -} - -/// Limited Reserve Transfers of a local asset from System Parachain to Parachain should work -#[test] -fn limited_reserve_transfer_asset_from_system_para_to_para() { - // Force create asset from Relay Chain and mint assets for System Parachain's sender account - AssetHubWestend::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubWestendSender::get(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000, - ); - - // Init values for System Parachain - let destination = AssetHubWestend::sibling_location_of(PenpalWestendA::para_id()); - let beneficiary_id = PenpalWestendAReceiver::get(); - let amount_to_send = ASSET_MIN_BALANCE * 1000; - let assets = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) - .into(); - - let system_para_test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: PenpalWestendAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); - - system_para_test.set_assertion::(system_para_to_para_assets_assertions); - // TODO: Add assertions when Penpal is able to manage assets - system_para_test - .set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); - system_para_test.assert(); -} - -/// Reserve Transfers of a local asset from System Parachain to Parachain should work -#[test] -fn reserve_transfer_asset_from_system_para_to_para() { - // Force create asset from Relay Chain and mint assets for System Parachain's sender account - AssetHubWestend::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubWestendSender::get(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000, - ); - - // Init values for System Parachain - let destination = AssetHubWestend::sibling_location_of(PenpalWestendA::para_id()); - let beneficiary_id = PenpalWestendAReceiver::get(); - let amount_to_send = ASSET_MIN_BALANCE * 1000; - let assets = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) - .into(); - - let system_para_test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: PenpalWestendAReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); - - system_para_test.set_assertion::(system_para_to_para_assets_assertions); - // TODO: Add assertions when Penpal is able to manage assets - system_para_test - .set_dispatchable::(system_para_to_para_reserve_transfer_assets); - system_para_test.assert(); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/send.rs deleted file mode 100644 index e603af685bb5c50ca4ab695ab8ccb588bb52edfd..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/send.rs +++ /dev/null @@ -1,105 +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. - -use crate::*; - -/// Relay Chain should be able to execute `Transact` instructions in System Parachain -/// when `OriginKind::Superuser`. -#[test] -fn send_transact_as_superuser_from_relay_to_system_para_works() { - AssetHubWestend::force_create_asset_from_relay_as_root( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - AssetHubWestendSender::get().into(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ) -} - -/// Parachain should be able to send XCM paying its fee with sufficient asset -/// in the System Parachain -#[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { - let para_sovereign_account = AssetHubWestend::sovereign_account_id_of( - AssetHubWestend::sibling_location_of(PenpalWestendA::para_id()), - ); - - // Force create and mint assets for Parachain's sovereign account - AssetHubWestend::force_create_and_mint_asset( - ASSET_ID, - ASSET_MIN_BALANCE, - true, - para_sovereign_account.clone(), - Some(Weight::from_parts(1_019_445_000, 200_000)), - ASSET_MIN_BALANCE * 1000000000, - ); - - // We just need a call that can pass the `SafeCallFilter` - // Call values are not relevant - let call = AssetHubWestend::force_create_asset_call( - ASSET_ID, - para_sovereign_account.clone(), - true, - ASSET_MIN_BALANCE, - ); - - let origin_kind = OriginKind::SovereignAccount; - let fee_amount = ASSET_MIN_BALANCE * 1000000; - let native_asset = - (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount).into(); - - let root_origin = ::RuntimeOrigin::root(); - let system_para_destination = - PenpalWestendA::sibling_location_of(AssetHubWestend::para_id()).into(); - let xcm = xcm_transact_paid_execution( - call, - origin_kind, - native_asset, - para_sovereign_account.clone(), - ); - - PenpalWestendA::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - root_origin, - bx!(system_para_destination), - bx!(xcm), - )); - - PenpalWestendA::assert_xcm_pallet_sent(); - }); - - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts( - 16_290_336_000, - 562_893, - ))); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { - asset_id: *asset_id == ASSET_ID, - owner: *owner == para_sovereign_account, - balance: *balance == fee_amount, - }, - RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => { - asset_id: *asset_id == ASSET_ID, - }, - ] - ); - }); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/swap.rs deleted file mode 100644 index 7d1615c9e29182c97146ca46c966f0ae85991b11..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/swap.rs +++ /dev/null @@ -1,350 +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. - -use crate::*; - -#[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 { - parents: 0, - interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); - - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), - AssetHubWestendSender::get().into(), - 1000, - )); - assert!(::Assets::asset_exists(ASSET_ID)); - - assert_ok!(::Assets::mint( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - ASSET_ID.into(), - AssetHubWestendSender::get().into(), - 3_000_000_000_000, - )); - - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), - 1_000_000_000_000, - 2_000_000_000_000, - 0, - 0, - AssetHubWestendSender::get().into() - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, - ] - ); - - let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); - - assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - path, - 100, - 1, - AssetHubWestendSender::get().into(), - true - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => { - amount_in: *amount_in == 100, - amount_out: *amount_out == 199, - }, - ] - ); - - assert_ok!(::AssetConversion::remove_liquidity( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native, - asset_one, - 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. - 0, - 0, - AssetHubWestendSender::get().into(), - )); - }); -} - -#[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(PenpalWestendA::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(PenpalWestendA::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalWestendA::execute_with(|| { - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(PenpalWestendASender::get()), - ASSET_ID.into(), - PenpalWestendASender::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 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 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(); - PenpalWestendA::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - PenpalWestendA, - vec![ - RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); - - // Receive XCM message in Assets Parachain - 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, - // currently that's disabled due to safe call filters.) - - 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(), - 3_000_000_000_000, - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] - ); - - // 4. Create pool: - assert_ok!(::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] - ); - - // 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(), - 1_000_000_000_000, - 2_000_000_000_000, - 0, - 0, - sov_penpal_on_asset_hub_westend.clone().into() - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { - lp_token_minted: *lp_token_minted == 1414213562273, - }, - ] - ); - - // 6. Swap! - let path = BoundedVec::<_, _>::truncate_from(vec![ - asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), - ]); - - assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - path, - 100000, - 1000, - AssetHubWestendSender::get().into(), - true - )); - - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => { - amount_in: *amount_in == 100000, - amount_out: *amount_out == 199399, - }, - ] - ); - - // 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, - 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. - 0, - 0, - sov_penpal_on_asset_hub_westend.clone().into(), - )); - }); -} - -#[test] -fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(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"); - - AssetHubWestend::execute_with(|| { - let pool_owner_account_id = asset_hub_westend_runtime::AssetConversionOrigin::get(); - - assert_ok!(::PoolAssets::create( - ::RuntimeOrigin::signed(pool_owner_account_id.clone()), - ASSET_ID.into(), - pool_owner_account_id.clone().into(), - 1000, - )); - assert!(::PoolAssets::asset_exists(ASSET_ID)); - - assert_ok!(::PoolAssets::mint( - ::RuntimeOrigin::signed(pool_owner_account_id), - ASSET_ID.into(), - AssetHubWestendSender::get().into(), - 3_000_000_000_000, - )); - - assert_matches::assert_matches!( - ::AssetConversion::create_pool( - ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - Box::new(asset_one), - ), - Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) - ); - }); -} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/teleport.rs deleted file mode 100644 index 4fe0062dafcd20baaf6cbda7372e824737ee01eb..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/teleport.rs +++ /dev/null @@ -1,411 +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. - -#![allow(dead_code)] // - -use crate::*; -use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig; -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(632_207_000, 7_186))); - - 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(AssetHubWestend::para_id()), - Some(Weight::from_parts(308_222_000, 7_186)), - ); - - assert_expected_events!( - Westend, - vec![ - // Amount is witdrawn 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(AssetHubWestend::para_id()), - Some(Weight::from_parts(148_705_000, 3_593)), - ); -} - -fn para_origin_assertions(t: SystemParaToRelayTest) { - type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( - 533_910_000, - 7167, - ))); - - AssetHubWestend::assert_parachain_system_ump_sent(); - - assert_expected_events!( - AssetHubWestend, - 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; - - AssetHubWestend::assert_dmp_queue_complete(Some(Weight::from_parts(164_793_000, 3593))); - - assert_expected_events!( - AssetHubWestend, - 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 relay_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::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, - ) -} - -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, - ) -} - -fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { - ::PolkadotXcm::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, - ) -} - -/// 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 test_args = TestContext { - sender: WestendSender::get(), - receiver: AssetHubWestendReceiver::get(), - args: relay_test_args(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(); - - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let destination = AssetHubWestend::parent_location(); - let beneficiary_id = WestendReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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 = AssetHubWestend::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 = ASSET_HUB_WESTEND_ED * 1000; - let destination = AssetHubWestend::parent_location().into(); - let beneficiary_id = WestendReceiver::get().into(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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 = AssetHubWestend::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); -} - -/// Teleport of native asset from Relay Chain to the System Parachain should work -#[test] -fn teleport_native_assets_from_relay_to_system_para_works() { - // Init values for Relay Chain - let amount_to_send: Balance = WESTEND_ED * 1000; - let test_args = TestContext { - sender: WestendSender::get(), - receiver: AssetHubWestendReceiver::get(), - args: relay_test_args(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_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); -} - -/// Teleport of native asset from System Parachains to the Relay Chain -/// should work when there is enough balance in Relay Chain's `CheckAccount` -#[test] -fn teleport_native_assets_back_from_system_para_to_relay_works() { - // Dependency - Relay Chain's `CheckAccount` should have enough balance - teleport_native_assets_from_relay_to_system_para_works(); - - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let destination = AssetHubWestend::parent_location(); - let beneficiary_id = WestendReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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_teleport_assets); - test.assert(); - - let delivery_fees = AssetHubWestend::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); -} - -/// Teleport of native asset from System Parachain to Relay Chain -/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount` -#[test] -fn teleport_native_assets_from_system_para_to_relay_fails() { - // Init values for Relay Chain - let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; - let destination = AssetHubWestend::parent_location(); - let beneficiary_id = WestendReceiver::get(); - let assets = (Parent, amount_to_send).into(); - - let test_args = TestContext { - sender: AssetHubWestendSender::get(), - receiver: WestendReceiver::get(), - args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), - }; - - 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_teleport_assets); - test.assert(); - - let delivery_fees = AssetHubWestend::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 does not change - assert_eq!(receiver_balance_after, receiver_balance_before); -} - -// TODO: uncomment when CollectivesWestend and BridgeHubWestend are implemented -// https://github.com/paritytech/polkadot-sdk/pull/1737 (CollectivesWestend) -// #[test] -// fn teleport_to_other_system_parachains_works() { -// let amount = ASSET_HUB_WESTEND_ED * 100; -// let native_asset: VersionedMultiAssets = (Parent, amount).into(); - -// test_parachain_is_trusted_teleporter!( -// AssetHubWestend, // Origin -// vec![CollectivesWestend, BridgeHubWestend], // Destinations -// (native_asset, amount) -// ); -// } diff --git a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/Cargo.toml deleted file mode 100644 index b3ce2a99f70a3fd0cbc208b54a724166e077a796..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "bridge-hub-rococo-integration-tests" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -description = "Bridge Hub Rococo runtime integration tests with xcm-emulator" -publish = false - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } - -# Substrate -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false} - -# Polkadot -polkadot-core-primitives = { path = "../../../../../../polkadot/core-primitives", default-features = false} -polkadot-parachain-primitives = { path = "../../../../../../polkadot/parachain", default-features = false} -polkadot-runtime-parachains = { path = "../../../../../../polkadot/runtime/parachains" } -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} - -# Cumulus -asset-test-utils = { path = "../../../../../parachains/runtimes/assets/test-utils", default-features = false } -parachains-common = { path = "../../../../common" } -cumulus-pallet-xcmp-queue = { path = "../../../../../pallets/xcmp-queue", default-features = false} -cumulus-pallet-dmp-queue = { path = "../../../../../pallets/dmp-queue", default-features = false} -pallet-bridge-messages = { path = "../../../../../../bridges/modules/messages", default-features = false} -bp-messages = { path = "../../../../../../bridges/primitives/messages", default-features = false} -bridge-hub-rococo-runtime = { path = "../../../../../parachains/runtimes/bridge-hubs/bridge-hub-rococo", default-features = false } - -# Local -xcm-emulator = { path = "../../../../../xcm/xcm-emulator", default-features = false} -integration-tests-common = { path = "../../common", default-features = false} diff --git a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/lib.rs deleted file mode 100644 index 3c4dbe18a9ac42d126efb3c999645f926a521483..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/lib.rs +++ /dev/null @@ -1,67 +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 use bp_messages::LaneId; -pub use frame_support::assert_ok; -pub use integration_tests_common::{ - constants::{ - bridge_hub_rococo::ED as BRIDGE_HUB_ROCOCO_ED, rococo::ED as ROCOCO_ED, - PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, - }, - test_parachain_is_trusted_teleporter, - xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, - AssetHubRococo, AssetHubRococoReceiver, AssetHubWococo, BridgeHubRococo, BridgeHubRococoPallet, - BridgeHubRococoSender, BridgeHubWococo, PenpalRococoA, Rococo, RococoPallet, -}; -pub use parachains_common::{AccountId, Balance}; -pub use xcm::{ - prelude::{AccountId32 as AccountId32Junction, *}, - v3::{ - Error, - NetworkId::{Rococo as RococoId, Wococo as WococoId}, - }, -}; -pub use xcm_emulator::{ - assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, - RelayChain as Relay, Test, TestArgs, TestContext, TestExt, -}; - -pub const ASSET_ID: u32 = 1; -pub const ASSET_MIN_BALANCE: u128 = 1000; -pub const ASSETS_PALLET_ID: u8 = 50; - -pub type RelayToSystemParaTest = Test; -pub type SystemParaToRelayTest = Test; -pub type SystemParaToParaTest = Test; - -/// Returns a `TestArgs` instance to de used for the Relay Chain accross integraton tests -pub fn relay_test_args(amount: Balance) -> TestArgs { - TestArgs { - dest: Rococo::child_location_of(AssetHubRococo::para_id()), - beneficiary: AccountId32Junction { - network: None, - id: AssetHubRococoReceiver::get().into(), - } - .into(), - amount, - assets: (Here, amount).into(), - asset_id: None, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -#[cfg(test)] -mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/example.rs b/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/example.rs deleted file mode 100644 index fd34299ce1dc412c9ad6c90560f12b35d3447300..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/example.rs +++ /dev/null @@ -1,98 +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. - -use crate::*; - -#[test] -fn example() { - // Init tests variables - // XcmPallet send arguments - let sudo_origin = ::RuntimeOrigin::root(); - let destination = Rococo::child_location_of(BridgeHubRococo::para_id()).into(); - let weight_limit = WeightLimit::Unlimited; - let check_origin = None; - - let remote_xcm = Xcm(vec![ClearOrigin]); - - let xcm = VersionedXcm::from(Xcm(vec![ - UnpaidExecution { weight_limit, check_origin }, - ExportMessage { - network: WococoId, - destination: X1(Parachain(AssetHubWococo::para_id().into())), - xcm: remote_xcm, - }, - ])); - - //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!(xcm), - )); - - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - Rococo, - vec![ - RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); - // Receive XCM message in Bridge Hub source Parachain - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { - outcome: Outcome::Complete(_), - .. - }) => {}, - RuntimeEvent::BridgeRococoToWococoMessages(pallet_bridge_messages::Event::MessageAccepted { - lane_id: LaneId([0, 0, 0, 1]), - nonce: 1, - }) => {}, - ] - ); - }); - - // Wococo GLobal Consensus - // Receive XCM message in Bridge Hub target Parachain - BridgeHubWococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - BridgeHubWococo, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] - ); - }); - // Receive embeded XCM message within `ExportMessage` in Parachain destination - AssetHubWococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - AssetHubWococo, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::Fail { .. }) => {}, - ] - ); - }); -} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f4f8b3603ba61e44195d6be8151b296f002ac720 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "asset-hub-rococo-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Asset Hub Rococo emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +asset-hub-rococo-runtime = { path = "../../../../../../runtimes/assets/asset-hub-rococo" } +rococo-emulated-chain = { path = "../../../relays/rococo" } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["rococo"] } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..2acccb9649b89aa8aea6349026fb72ea511cad78 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -0,0 +1,93 @@ +// 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 frame_support::parameter_types; +use sp_core::{sr25519, storage::Storage}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, get_account_id_from_seed, + PenpalSiblingSovereigAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, + SAFE_XCM_VERSION, +}; +use parachains_common::{AccountId, Balance}; + +pub const PARA_ID: u32 = 1000; +pub const ED: Balance = testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; + +parameter_types! { + pub AssetHubRococoAssetOwner: AccountId = get_account_id_from_seed::("Alice"); +} + +pub fn genesis() -> Storage { + let genesis_config = asset_hub_rococo_runtime::RuntimeGenesisConfig { + system: asset_hub_rococo_runtime::SystemConfig::default(), + balances: asset_hub_rococo_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096 * 4096)) + .collect(), + }, + parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: asset_hub_rococo_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: asset_hub_rococo_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + asset_hub_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: asset_hub_rococo_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + assets: asset_hub_rococo_runtime::AssetsConfig { + assets: vec![(RESERVABLE_ASSET_ID, AssetHubRococoAssetOwner::get(), true, ED)], + ..Default::default() + }, + foreign_assets: asset_hub_rococo_runtime::ForeignAssetsConfig { + assets: vec![ + // Penpal's teleportable asset representation + ( + PenpalTeleportableAssetLocation::get(), + PenpalSiblingSovereigAccount::get(), + true, + ED, + ), + ], + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + asset_hub_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f1e972e869dc94465aa28356a7eaa1c4cd4503ef --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs @@ -0,0 +1,59 @@ +// 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, + impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, +}; +use rococo_emulated_chain::Rococo; + +// AssetHubRococo Parachain declaration +decl_test_parachains! { + pub struct AssetHubRococo { + genesis = genesis::genesis(), + on_init = { + asset_hub_rococo_runtime::AuraExt::on_initialize(1); + }, + runtime = asset_hub_rococo_runtime, + core = { + 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, + Assets: asset_hub_rococo_runtime::Assets, + ForeignAssets: asset_hub_rococo_runtime::ForeignAssets, + PoolAssets: asset_hub_rococo_runtime::PoolAssets, + AssetConversion: asset_hub_rococo_runtime::AssetConversion, + Balances: asset_hub_rococo_runtime::Balances, + } + }, +} + +// AssetHubRococo implementation +impl_accounts_helpers_for_parachain!(AssetHubRococo); +impl_assert_events_helpers_for_parachain!(AssetHubRococo); +impl_assets_helpers_for_system_parachain!(AssetHubRococo, Rococo); +impl_assets_helpers_for_parachain!(AssetHubRococo); +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 new file mode 100644 index 0000000000000000000000000000000000000000..d4764f63bf64d3cc650d53ee8c637129448121a2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "asset-hub-westend-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Asset Hub Westend emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +asset-hub-westend-runtime = { path = "../../../../../../runtimes/assets/asset-hub-westend" } +westend-emulated-chain = { path = "../../../relays/westend" } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["westend"] } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..e30529aff42ca02a02ab9c93dc502f7fd50d02e4 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -0,0 +1,90 @@ +// 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 frame_support::parameter_types; +use sp_core::{sr25519, storage::Storage}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, get_account_id_from_seed, + PenpalSiblingSovereigAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, + SAFE_XCM_VERSION, +}; +use parachains_common::{AccountId, Balance}; + +pub const PARA_ID: u32 = 1000; +pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTENTIAL_DEPOSIT; + +parameter_types! { + pub AssetHubWestendAssetOwner: AccountId = get_account_id_from_seed::("Alice"); +} + +pub fn genesis() -> Storage { + let genesis_config = asset_hub_westend_runtime::RuntimeGenesisConfig { + system: asset_hub_westend_runtime::SystemConfig::default(), + balances: asset_hub_westend_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, + parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: asset_hub_westend_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: asset_hub_westend_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + asset_hub_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: asset_hub_westend_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + assets: asset_hub_westend_runtime::AssetsConfig { + assets: vec![(RESERVABLE_ASSET_ID, AssetHubWestendAssetOwner::get(), true, ED)], + ..Default::default() + }, + foreign_assets: asset_hub_westend_runtime::ForeignAssetsConfig { + assets: vec![ + // Penpal's teleportable asset representation + ( + PenpalTeleportableAssetLocation::get(), + PenpalSiblingSovereigAccount::get(), + true, + ED, + ), + ], + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + asset_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..7f05eefb4c208dab2192bc111347ffa4f2760fc0 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs @@ -0,0 +1,59 @@ +// 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, + impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, +}; +use westend_emulated_chain::Westend; + +// AssetHubWestend Parachain declaration +decl_test_parachains! { + pub struct AssetHubWestend { + genesis = genesis::genesis(), + on_init = { + asset_hub_westend_runtime::AuraExt::on_initialize(1); + }, + runtime = asset_hub_westend_runtime, + core = { + 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, + Balances: asset_hub_westend_runtime::Balances, + Assets: asset_hub_westend_runtime::Assets, + ForeignAssets: asset_hub_westend_runtime::ForeignAssets, + PoolAssets: asset_hub_westend_runtime::PoolAssets, + AssetConversion: asset_hub_westend_runtime::AssetConversion, + } + }, +} + +// AssetHubWestend implementation +impl_accounts_helpers_for_parachain!(AssetHubWestend); +impl_assert_events_helpers_for_parachain!(AssetHubWestend); +impl_assets_helpers_for_system_parachain!(AssetHubWestend, Westend); +impl_assets_helpers_for_parachain!(AssetHubWestend); +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 new file mode 100644 index 0000000000000000000000000000000000000000..322d8b44e6ea6b293fb1c2f52e0de80fa9cd29f1 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bridge-hub-rococo-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Bridge Hub Rococo emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +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 } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["rococo"] } 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 new file mode 100644 index 0000000000000000000000000000000000000000..12778215b1320f591bec787198243c825313e858 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -0,0 +1,81 @@ +// 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::{sr25519, storage::Storage}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, +}; +use parachains_common::Balance; + +pub const ASSETHUB_PARA_ID: u32 = 1000; +pub const PARA_ID: u32 = 1013; +pub const ED: Balance = testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; + +pub fn genesis() -> Storage { + let genesis_config = bridge_hub_rococo_runtime::RuntimeGenesisConfig { + system: bridge_hub_rococo_runtime::SystemConfig::default(), + balances: bridge_hub_rococo_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, + parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: bridge_hub_rococo_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: bridge_hub_rococo_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + bridge_hub_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: bridge_hub_rococo_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + bridge_westend_grandpa: bridge_hub_rococo_runtime::BridgeWestendGrandpaConfig { + owner: Some(get_account_id_from_seed::(accounts::BOB)), + ..Default::default() + }, + bridge_westend_messages: bridge_hub_rococo_runtime::BridgeWestendMessagesConfig { + 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() + }; + + build_genesis_storage( + &genesis_config, + bridge_hub_rococo_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..8c18d112bc12fb4883d313106fa66841dcad8d2e --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs @@ -0,0 +1,54 @@ +// 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, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// BridgeHubRococo Parachain declaration +decl_test_parachains! { + pub struct BridgeHubRococo { + genesis = genesis::genesis(), + on_init = { + bridge_hub_rococo_runtime::AuraExt::on_initialize(1); + }, + runtime = bridge_hub_rococo_runtime, + core = { + 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, + } + }, +} + +// 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 new file mode 100644 index 0000000000000000000000000000000000000000..ec1386b7f6e2b231e7f9ffdc925b3d0c802bf4f2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bridge-hub-westend-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Bridge Hub Westend emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +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 } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["westend"] } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..4be68e510f4d254dcf645db682b6cf34ff69e634 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -0,0 +1,75 @@ +// 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::{sr25519, storage::Storage}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, +}; +use parachains_common::Balance; + +pub const PARA_ID: u32 = 1002; +pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTENTIAL_DEPOSIT; + +pub fn genesis() -> Storage { + let genesis_config = bridge_hub_westend_runtime::RuntimeGenesisConfig { + system: bridge_hub_westend_runtime::SystemConfig::default(), + balances: bridge_hub_westend_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, + parachain_info: bridge_hub_westend_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: bridge_hub_westend_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: bridge_hub_westend_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + bridge_hub_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: bridge_hub_westend_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + bridge_rococo_grandpa: bridge_hub_westend_runtime::BridgeRococoGrandpaConfig { + owner: Some(get_account_id_from_seed::(accounts::BOB)), + ..Default::default() + }, + bridge_rococo_messages: bridge_hub_westend_runtime::BridgeRococoMessagesConfig { + owner: Some(get_account_id_from_seed::(accounts::BOB)), + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + bridge_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..b0dddc9dbf9a5b71a776e3ae48b97bbb9f29adf2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -0,0 +1,51 @@ +// 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, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// BridgeHubWestend Parachain declaration +decl_test_parachains! { + pub struct BridgeHubWestend { + genesis = genesis::genesis(), + on_init = { + bridge_hub_westend_runtime::AuraExt::on_initialize(1); + }, + runtime = bridge_hub_westend_runtime, + core = { + 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, + Balances: bridge_hub_westend_runtime::Balances, + } + }, +} + +// 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..03f755b666afbf87e876a7f7d5111aa98a1bc727 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml @@ -0,0 +1,24 @@ +[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] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +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" } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["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..6a28b1a9dddb8ca8b118592d85ef11367569d2a0 --- /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 = testnet_parachains_constants::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..65a358d0ef2f6b697453f2c1cbef20d3b2cd93a8 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml @@ -0,0 +1,21 @@ +[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] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +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" } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["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..b14009933029bfbc5cd5eeda9263126cd92a23c6 --- /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 = testnet_parachains_constants::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..075698848bcf0705c00ea46e3443802ca2fbe6b0 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml @@ -0,0 +1,21 @@ +[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] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +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" } +testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["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..d385fbebc821c05205f55bc5ea5f04d57ec9af21 --- /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 = testnet_parachains_constants::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 new file mode 100644 index 0000000000000000000000000000000000000000..f47350b00eb18f46ad33f23ca490664c726dc878 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "penpal-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Penpal emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Polkadot +xcm = { package = "staging-xcm", path = "../../../../../../../../polkadot/xcm", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../../parachains/common" } +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +penpal-runtime = { path = "../../../../../../runtimes/testing/penpal" } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..48901647fd052ec1499461ff7cb1dfa8cf797e82 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -0,0 +1,106 @@ +// 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 frame_support::parameter_types; +use sp_core::{sr25519, storage::Storage}; + +// Polkadot +use xcm::v3::Location; +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, +}; +use parachains_common::{AccountId, Balance}; +use penpal_runtime::xcm_config::{LocalReservableFromAssetHub, RelayLocation}; +// Penpal +pub const PARA_ID_A: u32 = 2000; +pub const PARA_ID_B: u32 = 2001; +pub const ED: Balance = penpal_runtime::EXISTENTIAL_DEPOSIT; + +parameter_types! { + pub PenpalSudoAcccount: AccountId = get_account_id_from_seed::("Alice"); + pub PenpalAssetOwner: AccountId = PenpalSudoAcccount::get(); +} + +pub fn genesis(para_id: u32) -> Storage { + let genesis_config = penpal_runtime::RuntimeGenesisConfig { + system: penpal_runtime::SystemConfig::default(), + balances: penpal_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, + parachain_info: penpal_runtime::ParachainInfoConfig { + parachain_id: para_id.into(), + ..Default::default() + }, + collator_selection: penpal_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: penpal_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + penpal_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: penpal_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + sudo: penpal_runtime::SudoConfig { key: Some(PenpalSudoAcccount::get()) }, + assets: penpal_runtime::AssetsConfig { + assets: vec![( + penpal_runtime::xcm_config::TELEPORTABLE_ASSET_ID, + PenpalAssetOwner::get(), + false, + ED, + )], + ..Default::default() + }, + foreign_assets: penpal_runtime::ForeignAssetsConfig { + assets: vec![ + // Relay Native asset representation + ( + Location::try_from(RelayLocation::get()).expect("conversion works"), + PenpalAssetOwner::get(), + true, + ED, + ), + // Sufficient AssetHub asset representation + ( + Location::try_from(LocalReservableFromAssetHub::get()) + .expect("conversion works"), + PenpalAssetOwner::get(), + true, + ED, + ), + ], + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + penpal_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..651b3a52306767446943d43a6616193ca8205838 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -0,0 +1,79 @@ +// 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 genesis; +pub use genesis::{genesis, PenpalAssetOwner, PenpalSudoAcccount, ED, PARA_ID_A, PARA_ID_B}; +pub use penpal_runtime::xcm_config::{ + CustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub, XcmConfig, +}; + +// Substrate +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, impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// Penpal Parachain declaration +decl_test_parachains! { + pub struct PenpalA { + genesis = genesis(PARA_ID_A), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + 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, + } + }, + pub struct PenpalB { + genesis = genesis(PARA_ID_B), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + 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, + } + }, +} + +// Penpal implementation +impl_accounts_helpers_for_parachain!(PenpalA); +impl_accounts_helpers_for_parachain!(PenpalB); +impl_assert_events_helpers_for_parachain!(PenpalA); +impl_assert_events_helpers_for_parachain!(PenpalB); +impl_assets_helpers_for_parachain!(PenpalA); +impl_assets_helpers_for_parachain!(PenpalB); diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2d27426cca7599e3284caa6c8ca69a2d546a8da2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rococo-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Rococo emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } +sp-authority-discovery = { path = "../../../../../../../substrate/primitives/authority-discovery", default-features = false } +sp-consensus-babe = { path = "../../../../../../../substrate/primitives/consensus/babe", default-features = false } +beefy-primitives = { package = "sp-consensus-beefy", path = "../../../../../../../substrate/primitives/consensus/beefy" } +grandpa = { package = "sc-consensus-grandpa", path = "../../../../../../../substrate/client/consensus/grandpa", default-features = false } + +# Polkadot +polkadot-primitives = { path = "../../../../../../../polkadot/primitives", default-features = false } +rococo-runtime-constants = { path = "../../../../../../../polkadot/runtime/rococo/constants", default-features = false } +rococo-runtime = { path = "../../../../../../../polkadot/runtime/rococo" } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..55437645b0523b577c2e9d455952f5526ba9df0b --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -0,0 +1,96 @@ +// 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 beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; +use grandpa::AuthorityId as GrandpaId; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_core::{sr25519, storage::Storage}; + +// Polkadot +use polkadot_primitives::{AssignmentId, ValidatorId}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, get_account_id_from_seed, get_host_config, validators, +}; +use parachains_common::Balance; +use rococo_runtime_constants::currency::UNITS as ROC; + +pub const ED: Balance = rococo_runtime_constants::currency::EXISTENTIAL_DEPOSIT; +const ENDOWMENT: u128 = 1_000_000 * ROC; + +fn session_keys( + babe: BabeId, + grandpa: GrandpaId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, + beefy: BeefyId, +) -> rococo_runtime::SessionKeys { + rococo_runtime::SessionKeys { + babe, + grandpa, + para_validator, + para_assignment, + authority_discovery, + beefy, + } +} + +pub fn genesis() -> Storage { + let genesis_config = rococo_runtime::RuntimeGenesisConfig { + system: rococo_runtime::SystemConfig::default(), + balances: rococo_runtime::BalancesConfig { + balances: accounts::init_balances().iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + }, + session: rococo_runtime::SessionConfig { + keys: validators::initial_authorities() + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + ), + ) + }) + .collect::>(), + }, + babe: rococo_runtime::BabeConfig { + authorities: Default::default(), + epoch_config: rococo_runtime::BABE_GENESIS_EPOCH_CONFIG, + ..Default::default() + }, + sudo: rococo_runtime::SudoConfig { + key: Some(get_account_id_from_seed::("Alice")), + }, + configuration: rococo_runtime::ConfigurationConfig { config: get_host_config() }, + registrar: rococo_runtime::RegistrarConfig { + next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage(&genesis_config, rococo_runtime::WASM_BINARY.unwrap()) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..e0d37302120fa7c7ae9e8cd4f5e62135b3febcb1 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -0,0 +1,50 @@ +// 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; + +// Cumulus +use emulated_integration_tests_common::{ + impl_accounts_helpers_for_relay_chain, impl_assert_events_helpers_for_relay_chain, + impl_hrmp_channels_helpers_for_relay_chain, impl_send_transact_helpers_for_relay_chain, + xcm_emulator::decl_test_relay_chains, +}; + +// Rococo declaration +decl_test_relay_chains! { + #[api_version(10)] + pub struct Rococo { + genesis = genesis::genesis(), + on_init = (), + runtime = rococo_runtime, + core = { + SovereignAccountOf: rococo_runtime::xcm_config::LocationConverter, + }, + pallets = { + XcmPallet: rococo_runtime::XcmPallet, + Sudo: rococo_runtime::Sudo, + Balances: rococo_runtime::Balances, + Hrmp: rococo_runtime::Hrmp, + Identity: rococo_runtime::Identity, + IdentityMigrator: rococo_runtime::IdentityMigrator, + } + }, +} + +// Rococo implementation +impl_accounts_helpers_for_relay_chain!(Rococo); +impl_assert_events_helpers_for_relay_chain!(Rococo); +impl_hrmp_channels_helpers_for_relay_chain!(Rococo); +impl_send_transact_helpers_for_relay_chain!(Rococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..abc40c2040681cc4cb41246a9872d557134b6dde --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "westend-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Westend emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } +sp-authority-discovery = { path = "../../../../../../../substrate/primitives/authority-discovery", default-features = false } +sp-consensus-babe = { path = "../../../../../../../substrate/primitives/consensus/babe", default-features = false } +beefy-primitives = { package = "sp-consensus-beefy", path = "../../../../../../../substrate/primitives/consensus/beefy" } +grandpa = { package = "sc-consensus-grandpa", path = "../../../../../../../substrate/client/consensus/grandpa", default-features = false } +pallet-staking = { path = "../../../../../../../substrate/frame/staking", default-features = false } + +# Polkadot +polkadot-primitives = { path = "../../../../../../../polkadot/primitives", default-features = false } +westend-runtime-constants = { path = "../../../../../../../polkadot/runtime/westend/constants", default-features = false } +westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..700b80e63f6cf68e6095b7e02d84bb285ce720f9 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -0,0 +1,105 @@ +// 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 beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; +use grandpa::AuthorityId as GrandpaId; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_core::storage::Storage; +use sp_runtime::Perbill; + +// Polkadot +use polkadot_primitives::{AssignmentId, ValidatorId}; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, get_host_config, validators, +}; +use parachains_common::Balance; +use westend_runtime_constants::currency::UNITS as WND; + +pub const ED: Balance = westend_runtime_constants::currency::EXISTENTIAL_DEPOSIT; +const ENDOWMENT: u128 = 1_000_000 * WND; +const STASH: u128 = 100 * WND; + +fn session_keys( + babe: BabeId, + grandpa: GrandpaId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, + beefy: BeefyId, +) -> westend_runtime::SessionKeys { + westend_runtime::SessionKeys { + babe, + grandpa, + para_validator, + para_assignment, + authority_discovery, + beefy, + } +} + +pub fn genesis() -> Storage { + let genesis_config = westend_runtime::RuntimeGenesisConfig { + system: westend_runtime::SystemConfig::default(), + balances: westend_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ENDOWMENT)).collect(), + }, + session: westend_runtime::SessionConfig { + keys: validators::initial_authorities() + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + ), + ) + }) + .collect::>(), + }, + staking: westend_runtime::StakingConfig { + validator_count: validators::initial_authorities().len() as u32, + minimum_validator_count: 1, + stakers: validators::initial_authorities() + .iter() + .map(|x| { + (x.0.clone(), x.1.clone(), STASH, westend_runtime::StakerStatus::Validator) + }) + .collect(), + invulnerables: validators::initial_authorities().iter().map(|x| x.0.clone()).collect(), + force_era: pallet_staking::Forcing::ForceNone, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }, + babe: westend_runtime::BabeConfig { + authorities: Default::default(), + epoch_config: westend_runtime::BABE_GENESIS_EPOCH_CONFIG, + ..Default::default() + }, + configuration: westend_runtime::ConfigurationConfig { config: get_host_config() }, + ..Default::default() + }; + + build_genesis_storage(&genesis_config, westend_runtime::WASM_BINARY.unwrap()) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..4d29a8024d17699a66be1d1583aa763884a0a94e --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/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; + +// Cumulus +use emulated_integration_tests_common::{ + impl_accounts_helpers_for_relay_chain, impl_assert_events_helpers_for_relay_chain, + impl_hrmp_channels_helpers_for_relay_chain, impl_send_transact_helpers_for_relay_chain, + xcm_emulator::decl_test_relay_chains, +}; + +// Westend declaration +decl_test_relay_chains! { + #[api_version(10)] + pub struct Westend { + genesis = genesis::genesis(), + on_init = (), + runtime = westend_runtime, + core = { + SovereignAccountOf: westend_runtime::xcm_config::LocationConverter, + }, + pallets = { + XcmPallet: westend_runtime::XcmPallet, + Sudo: westend_runtime::Sudo, + Balances: westend_runtime::Balances, + Treasury: westend_runtime::Treasury, + AssetRate: westend_runtime::AssetRate, + Hrmp: westend_runtime::Hrmp, + Identity: westend_runtime::Identity, + IdentityMigrator: westend_runtime::IdentityMigrator, + } + }, +} + +// Westend implementation +impl_accounts_helpers_for_relay_chain!(Westend); +impl_assert_events_helpers_for_relay_chain!(Westend); +impl_hrmp_channels_helpers_for_relay_chain!(Westend); +impl_send_transact_helpers_for_relay_chain!(Westend); diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 1691d21e173d2f038bc492b93e1a390f2ec6a1dd..8c44cce7d922dff0a69a4273af7adb69819d2fce 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -1,91 +1,46 @@ [package] -name = "integration-tests-common" -version = "1.0.0" +name = "emulated-integration-tests-common" +version = "3.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Common resources for integration testing with xcm-emulator" -publish = false + +[lints] +workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } paste = "1.0.14" # Substrate -grandpa = { package = "sc-consensus-grandpa", path = "../../../../../substrate/client/consensus/grandpa" } -sp-authority-discovery = { path = "../../../../../substrate/primitives/authority-discovery", default-features = false} -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false} -frame-support = { path = "../../../../../substrate/frame/support", default-features = false} -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false} -sp-consensus-babe = { path = "../../../../../substrate/primitives/consensus/babe", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false} -pallet-staking = { path = "../../../../../substrate/frame/staking", default-features = false} -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false} -pallet-im-online = { path = "../../../../../substrate/frame/im-online", default-features = false} beefy-primitives = { package = "sp-consensus-beefy", path = "../../../../../substrate/primitives/consensus/beefy" } +grandpa = { package = "sc-consensus-grandpa", path = "../../../../../substrate/client/consensus/grandpa" } +sp-authority-discovery = { path = "../../../../../substrate/primitives/authority-discovery" } +sp-runtime = { path = "../../../../../substrate/primitives/runtime" } +frame-support = { path = "../../../../../substrate/frame/support" } +sp-core = { path = "../../../../../substrate/primitives/core" } +sp-consensus-babe = { path = "../../../../../substrate/primitives/consensus/babe" } +pallet-assets = { path = "../../../../../substrate/frame/assets" } +pallet-balances = { path = "../../../../../substrate/frame/balances" } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue" } # Polkadot -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false} -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false} -polkadot-service = { path = "../../../../../polkadot/node/service", default-features = false, features = ["full-node"] } -polkadot-primitives = { path = "../../../../../polkadot/primitives", default-features = false} +polkadot-primitives = { path = "../../../../../polkadot/primitives" } +polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain" } polkadot-runtime-parachains = { path = "../../../../../polkadot/runtime/parachains" } -rococo-runtime = { path = "../../../../../polkadot/runtime/rococo" } -rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants" } -westend-runtime = { path = "../../../../../polkadot/runtime/westend" } -westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants" } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm" } +pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm" } # Cumulus parachains-common = { path = "../../../common" } cumulus-primitives-core = { path = "../../../../primitives/core" } -penpal-runtime = { path = "../../../runtimes/testing/penpal" } -asset-hub-polkadot-runtime = { path = "../../../runtimes/assets/asset-hub-polkadot" } -asset-hub-kusama-runtime = { path = "../../../runtimes/assets/asset-hub-kusama" } -asset-hub-rococo-runtime = { path = "../../../runtimes/assets/asset-hub-rococo" } -asset-hub-westend-runtime = { path = "../../../runtimes/assets/asset-hub-westend" } -collectives-polkadot-runtime = { path = "../../../runtimes/collectives/collectives-polkadot" } -bridge-hub-kusama-runtime = { path = "../../../runtimes/bridge-hubs/bridge-hub-kusama" } -bridge-hub-polkadot-runtime = { path = "../../../runtimes/bridge-hubs/bridge-hub-polkadot" } -bridge-hub-rococo-runtime = { path = "../../../runtimes/bridge-hubs/bridge-hub-rococo" } -xcm-emulator = { path = "../../../../xcm/xcm-emulator", default-features = false} -cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue" } -cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false} +xcm-emulator = { path = "../../../../xcm/xcm-emulator" } +cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue" } cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system" } +asset-test-utils = { path = "../../../runtimes/assets/test-utils" } + +# Bridges bp-messages = { path = "../../../../../bridges/primitives/messages" } pallet-bridge-messages = { path = "../../../../../bridges/modules/messages" } bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common" } - -[features] -runtime-benchmarks = [ - "asset-hub-kusama-runtime/runtime-benchmarks", - "asset-hub-polkadot-runtime/runtime-benchmarks", - "asset-hub-rococo-runtime/runtime-benchmarks", - "asset-hub-westend-runtime/runtime-benchmarks", - "bridge-hub-kusama-runtime/runtime-benchmarks", - "bridge-hub-polkadot-runtime/runtime-benchmarks", - "bridge-hub-rococo-runtime/runtime-benchmarks", - "bridge-runtime-common/runtime-benchmarks", - "collectives-polkadot-runtime/runtime-benchmarks", - "cumulus-pallet-parachain-system/runtime-benchmarks", - "cumulus-pallet-xcmp-queue/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-bridge-messages/runtime-benchmarks", - "pallet-im-online/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", - "pallet-staking/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "penpal-runtime/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-primitives/runtime-benchmarks", - "polkadot-runtime-parachains/runtime-benchmarks", - "polkadot-service/runtime-benchmarks", - "rococo-runtime/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "westend-runtime/runtime-benchmarks", -] diff --git a/cumulus/parachains/integration-tests/emulated/common/src/constants.rs b/cumulus/parachains/integration-tests/emulated/common/src/constants.rs deleted file mode 100644 index 93abae753b94b9eb030bea3f953a638ae5214bdb..0000000000000000000000000000000000000000 --- a/cumulus/parachains/integration-tests/emulated/common/src/constants.rs +++ /dev/null @@ -1,687 +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. - -// Substrate -use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; -use grandpa::AuthorityId as GrandpaId; -use pallet_im_online::sr25519::AuthorityId as ImOnlineId; -use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; -use sp_consensus_babe::AuthorityId as BabeId; -use sp_core::{sr25519, storage::Storage, Pair, Public}; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, - BuildStorage, MultiSignature, Perbill, -}; - -// Cumulus -use parachains_common::{AccountId, AssetHubPolkadotAuraId, AuraId, Balance, BlockNumber}; -use polkadot_parachain_primitives::primitives::{HeadData, ValidationCode}; -use polkadot_primitives::{AssignmentId, ValidatorId}; -use polkadot_runtime_parachains::{ - configuration::HostConfiguration, - paras::{ParaGenesisArgs, ParaKind}, -}; -use polkadot_service::chain_spec::get_authority_keys_from_seed_no_beefy; -use xcm; - -pub const XCM_V2: u32 = 3; -pub const XCM_V3: u32 = 2; -pub const REF_TIME_THRESHOLD: u64 = 33; -pub const PROOF_SIZE_THRESHOLD: u64 = 33; - -type AccountPublic = ::Signer; - -/// Helper function to generate a crypto pair from seed -fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed. -fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -pub mod accounts { - use super::*; - pub const ALICE: &str = "Alice"; - pub const BOB: &str = "Bob"; - pub const CHARLIE: &str = "Charlie"; - pub const DAVE: &str = "Dave"; - pub const EVE: &str = "Eve"; - pub const FERDIE: &str = "Ferdei"; - pub const ALICE_STASH: &str = "Alice//stash"; - pub const BOB_STASH: &str = "Bob//stash"; - pub const CHARLIE_STASH: &str = "Charlie//stash"; - pub const DAVE_STASH: &str = "Dave//stash"; - pub const EVE_STASH: &str = "Eve//stash"; - pub const FERDIE_STASH: &str = "Ferdie//stash"; - pub const FERDIE_BEEFY: &str = "Ferdie//stash"; - - pub fn init_balances() -> Vec { - 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), - ] - } -} - -pub mod collators { - use super::*; - - pub fn invulnerables_asset_hub_polkadot() -> Vec<(AccountId, AssetHubPolkadotAuraId)> { - vec![ - ( - get_account_id_from_seed::("Alice"), - get_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_from_seed::("Bob"), - ), - ] - } - - pub fn invulnerables() -> Vec<(AccountId, AuraId)> { - vec![ - ( - get_account_id_from_seed::("Alice"), - get_from_seed::("Alice"), - ), - (get_account_id_from_seed::("Bob"), get_from_seed::("Bob")), - ] - } -} - -pub mod validators { - use super::*; - - pub fn initial_authorities() -> Vec<( - AccountId, - AccountId, - BabeId, - GrandpaId, - ImOnlineId, - ValidatorId, - AssignmentId, - AuthorityDiscoveryId, - )> { - vec![get_authority_keys_from_seed_no_beefy("Alice")] - } -} - -/// The default XCM version to set in genesis config. -const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; - -// Westend -pub mod westend { - use super::*; - use westend_runtime_constants::currency::UNITS as WND; - pub const ED: Balance = westend_runtime_constants::currency::EXISTENTIAL_DEPOSIT; - const ENDOWMENT: u128 = 1_000_000 * WND; - const STASH: u128 = 100 * WND; - - pub fn get_host_config() -> HostConfiguration { - HostConfiguration { - max_upward_queue_count: 10, - max_upward_queue_size: 51200, - max_upward_message_size: 51200, - max_upward_message_num_per_candidate: 10, - max_downward_message_size: 51200, - hrmp_sender_deposit: 100_000_000_000, - hrmp_recipient_deposit: 100_000_000_000, - hrmp_channel_max_capacity: 1000, - hrmp_channel_max_message_size: 102400, - hrmp_channel_max_total_size: 102400, - hrmp_max_parachain_outbound_channels: 30, - hrmp_max_parachain_inbound_channels: 30, - ..Default::default() - } - } - - fn session_keys( - babe: BabeId, - grandpa: GrandpaId, - im_online: ImOnlineId, - para_validator: ValidatorId, - para_assignment: AssignmentId, - authority_discovery: AuthorityDiscoveryId, - beefy: BeefyId, - ) -> westend_runtime::SessionKeys { - westend_runtime::SessionKeys { - babe, - grandpa, - im_online, - para_validator, - para_assignment, - authority_discovery, - beefy, - } - } - - pub fn genesis() -> Storage { - let genesis_config = westend_runtime::RuntimeGenesisConfig { - system: westend_runtime::SystemConfig { - code: westend_runtime::WASM_BINARY.unwrap().to_vec(), - ..Default::default() - }, - balances: westend_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ENDOWMENT)) - .collect(), - }, - session: westend_runtime::SessionConfig { - keys: validators::initial_authorities() - .iter() - .map(|x| { - ( - x.0.clone(), - x.0.clone(), - westend::session_keys( - x.2.clone(), - x.3.clone(), - x.4.clone(), - x.5.clone(), - x.6.clone(), - x.7.clone(), - get_from_seed::("Alice"), - ), - ) - }) - .collect::>(), - }, - staking: westend_runtime::StakingConfig { - validator_count: validators::initial_authorities().len() as u32, - minimum_validator_count: 1, - stakers: validators::initial_authorities() - .iter() - .map(|x| { - (x.0.clone(), x.1.clone(), STASH, westend_runtime::StakerStatus::Validator) - }) - .collect(), - invulnerables: validators::initial_authorities() - .iter() - .map(|x| x.0.clone()) - .collect(), - force_era: pallet_staking::Forcing::ForceNone, - slash_reward_fraction: Perbill::from_percent(10), - ..Default::default() - }, - babe: westend_runtime::BabeConfig { - authorities: Default::default(), - epoch_config: Some(westend_runtime::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - configuration: westend_runtime::ConfigurationConfig { config: get_host_config() }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -// Rococo -pub mod rococo { - use super::*; - pub const ED: Balance = rococo_runtime_constants::currency::EXISTENTIAL_DEPOSIT; - use rococo_runtime_constants::currency::UNITS as ROC; - const ENDOWMENT: u128 = 1_000_000 * ROC; - - pub fn get_host_config() -> HostConfiguration { - HostConfiguration { - max_upward_queue_count: 10, - max_upward_queue_size: 51200, - max_upward_message_size: 51200, - max_upward_message_num_per_candidate: 10, - max_downward_message_size: 51200, - hrmp_sender_deposit: 0, - hrmp_recipient_deposit: 0, - hrmp_channel_max_capacity: 1000, - hrmp_channel_max_message_size: 102400, - hrmp_channel_max_total_size: 102400, - hrmp_max_parachain_outbound_channels: 30, - hrmp_max_parachain_inbound_channels: 30, - ..Default::default() - } - } - - fn session_keys( - babe: BabeId, - grandpa: GrandpaId, - im_online: ImOnlineId, - para_validator: ValidatorId, - para_assignment: AssignmentId, - authority_discovery: AuthorityDiscoveryId, - beefy: BeefyId, - ) -> rococo_runtime::SessionKeys { - rococo_runtime::SessionKeys { - babe, - grandpa, - im_online, - para_validator, - para_assignment, - authority_discovery, - beefy, - } - } - - pub fn genesis() -> Storage { - let genesis_config = rococo_runtime::RuntimeGenesisConfig { - system: rococo_runtime::SystemConfig { - code: rococo_runtime::WASM_BINARY.unwrap().to_vec(), - ..Default::default() - }, - balances: rococo_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .map(|k| (k.clone(), ENDOWMENT)) - .collect(), - }, - // indices: rococo_runtime::IndicesConfig { indices: vec![] }, - session: rococo_runtime::SessionConfig { - keys: validators::initial_authorities() - .iter() - .map(|x| { - ( - x.0.clone(), - x.0.clone(), - session_keys( - x.2.clone(), - x.3.clone(), - x.4.clone(), - x.5.clone(), - x.6.clone(), - x.7.clone(), - get_from_seed::("Alice"), - ), - ) - }) - .collect::>(), - }, - babe: rococo_runtime::BabeConfig { - authorities: Default::default(), - epoch_config: Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - sudo: rococo_runtime::SudoConfig { - key: Some(get_account_id_from_seed::("Alice")), - }, - configuration: rococo_runtime::ConfigurationConfig { config: get_host_config() }, - paras: rococo_runtime::ParasConfig { - paras: vec![ - ( - asset_hub_rococo::PARA_ID.into(), - ParaGenesisArgs { - genesis_head: HeadData::default(), - validation_code: ValidationCode( - asset_hub_rococo_runtime::WASM_BINARY.unwrap().to_vec(), - ), - para_kind: ParaKind::Parachain, - }, - ), - ( - penpal::PARA_ID_A.into(), - ParaGenesisArgs { - genesis_head: HeadData::default(), - validation_code: ValidationCode( - penpal_runtime::WASM_BINARY.unwrap().to_vec(), - ), - para_kind: ParaKind::Parachain, - }, - ), - ( - penpal::PARA_ID_B.into(), - ParaGenesisArgs { - genesis_head: HeadData::default(), - validation_code: ValidationCode( - penpal_runtime::WASM_BINARY.unwrap().to_vec(), - ), - para_kind: ParaKind::Parachain, - }, - ), - ], - ..Default::default() - }, - registrar: rococo_runtime::RegistrarConfig { - next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, - ..Default::default() - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -// Asset Hub Westend -pub mod asset_hub_westend { - use super::*; - pub const PARA_ID: u32 = 1000; - pub const ED: Balance = parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; - - pub fn genesis() -> Storage { - let genesis_config = asset_hub_westend_runtime::RuntimeGenesisConfig { - system: asset_hub_westend_runtime::SystemConfig { - code: asset_hub_westend_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_westend_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ED * 4096)) - .collect(), - }, - parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { - parachain_id: PARA_ID.into(), - ..Default::default() - }, - collator_selection: asset_hub_westend_runtime::CollatorSelectionConfig { - invulnerables: collators::invulnerables() - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect(), - candidacy_bond: ED * 16, - ..Default::default() - }, - session: asset_hub_westend_runtime::SessionConfig { - keys: collators::invulnerables() - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_westend_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - polkadot_xcm: asset_hub_westend_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -pub mod asset_hub_rococo { - use super::*; - pub const PARA_ID: u32 = 1000; - pub const ED: Balance = parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; - - pub fn genesis() -> Storage { - let genesis_config = asset_hub_rococo_runtime::RuntimeGenesisConfig { - system: asset_hub_rococo_runtime::SystemConfig { - code: asset_hub_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_rococo_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ED * 4096 * 4096)) - .collect(), - }, - parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { - parachain_id: PARA_ID.into(), - ..Default::default() - }, - collator_selection: asset_hub_rococo_runtime::CollatorSelectionConfig { - invulnerables: collators::invulnerables() - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect(), - candidacy_bond: ED * 16, - ..Default::default() - }, - session: asset_hub_rococo_runtime::SessionConfig { - keys: collators::invulnerables() - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_rococo_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - polkadot_xcm: asset_hub_rococo_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -pub mod asset_hub_wococo { - use super::*; - pub const PARA_ID: u32 = 1000; - pub const ED: Balance = parachains_common::wococo::currency::EXISTENTIAL_DEPOSIT; - - pub fn genesis() -> Storage { - let genesis_config = asset_hub_rococo_runtime::RuntimeGenesisConfig { - system: asset_hub_rococo_runtime::SystemConfig { - code: asset_hub_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_rococo_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ED * 4096)) - .collect(), - }, - parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { - parachain_id: PARA_ID.into(), - ..Default::default() - }, - collator_selection: asset_hub_rococo_runtime::CollatorSelectionConfig { - invulnerables: collators::invulnerables() - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect(), - candidacy_bond: ED * 16, - ..Default::default() - }, - session: asset_hub_rococo_runtime::SessionConfig { - keys: collators::invulnerables() - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_rococo_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - polkadot_xcm: asset_hub_rococo_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -// Penpal -pub mod penpal { - use super::*; - pub const PARA_ID_A: u32 = 2000; - pub const PARA_ID_B: u32 = 2001; - pub const ED: Balance = penpal_runtime::EXISTENTIAL_DEPOSIT; - - pub fn genesis(para_id: u32) -> Storage { - let genesis_config = penpal_runtime::RuntimeGenesisConfig { - system: penpal_runtime::SystemConfig { - code: penpal_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: penpal_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ED * 4096)) - .collect(), - }, - parachain_info: penpal_runtime::ParachainInfoConfig { - parachain_id: para_id.into(), - ..Default::default() - }, - collator_selection: penpal_runtime::CollatorSelectionConfig { - invulnerables: collators::invulnerables() - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect(), - candidacy_bond: ED * 16, - ..Default::default() - }, - session: penpal_runtime::SessionConfig { - keys: collators::invulnerables() - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - penpal_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - polkadot_xcm: penpal_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - sudo: penpal_runtime::SudoConfig { - key: Some(get_account_id_from_seed::("Alice")), - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} - -// Bridge Hub Rococo & Bridge Hub Wococo -pub mod bridge_hub_rococo { - use super::*; - pub const PARA_ID: u32 = 1013; - pub const ED: Balance = parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; - - pub fn genesis() -> Storage { - let genesis_config = bridge_hub_rococo_runtime::RuntimeGenesisConfig { - system: bridge_hub_rococo_runtime::SystemConfig { - code: bridge_hub_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: bridge_hub_rococo_runtime::BalancesConfig { - balances: accounts::init_balances() - .iter() - .cloned() - .map(|k| (k, ED * 4096)) - .collect(), - }, - parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { - parachain_id: PARA_ID.into(), - ..Default::default() - }, - collator_selection: bridge_hub_rococo_runtime::CollatorSelectionConfig { - invulnerables: collators::invulnerables() - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect(), - candidacy_bond: ED * 16, - ..Default::default() - }, - session: bridge_hub_rococo_runtime::SessionConfig { - keys: collators::invulnerables() - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - bridge_hub_rococo_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - polkadot_xcm: bridge_hub_rococo_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - bridge_wococo_grandpa: bridge_hub_rococo_runtime::BridgeWococoGrandpaConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), - ..Default::default() - }, - bridge_rococo_grandpa: bridge_hub_rococo_runtime::BridgeRococoGrandpaConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), - ..Default::default() - }, - bridge_wococo_to_rococo_messages: - bridge_hub_rococo_runtime::BridgeWococoToRococoMessagesConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), - ..Default::default() - }, - bridge_rococo_to_wococo_messages: - bridge_hub_rococo_runtime::BridgeRococoToWococoMessagesConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), - ..Default::default() - }, - ..Default::default() - }; - - genesis_config.build_storage().unwrap() - } -} diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index bb4c9d102e98a928de6d721ee6fb8f1dd7abf9f4..5fc08dff32c454d053816f547ef90ec9b70e8b0c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -17,50 +17,61 @@ pub use codec::{Decode, Encode}; pub use paste; pub use crate::{ - constants::{PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD}, - xcm_helpers::xcm_transact_unpaid_execution, - BridgeHubRococo, BridgeHubWococo, + xcm_helpers::xcm_transact_unpaid_execution, PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, }; // Substrate -pub use frame_support::{assert_ok, traits::fungibles::Inspect}; +pub use frame_support::{ + assert_ok, + sp_runtime::AccountId32, + traits::fungibles::Inspect, + weights::{Weight, WeightMeter}, +}; pub use pallet_assets; pub use pallet_message_queue; +pub use pallet_xcm; use sp_core::Get; -// Cumulus -use bp_messages::{ - target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - LaneId, MessageKey, OutboundLaneData, +// Polkadot +pub use polkadot_runtime_parachains::{ + dmp, hrmp, + inclusion::{AggregateMessageOrigin, UmpQueueId}, }; -use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult; -pub use cumulus_pallet_dmp_queue; +pub use xcm::{ + prelude::{Location, OriginKind, Outcome, VersionedXcm, XcmVersion}, + v3, + v4::Error as XcmError, + DoubleEncoded, +}; + +// Cumulus pub use cumulus_pallet_parachain_system; pub use cumulus_pallet_xcmp_queue; pub use cumulus_primitives_core::{ - relay_chain::HrmpChannelId, DmpMessageHandler, ParaId, XcmpMessageHandler, + relay_chain::HrmpChannelId, DmpMessageHandler, Junction, Junctions, NetworkId, ParaId, + XcmpMessageHandler, }; -use pallet_bridge_messages::{Config, Instance1, Instance2, OutboundLanes, Pallet}; pub use parachains_common::{AccountId, Balance}; pub use xcm_emulator::{ assert_expected_events, bx, helpers::weight_within_threshold, BridgeMessage, - BridgeMessageDispatchError, BridgeMessageHandler, Chain, Parachain, RelayChain, TestExt, + BridgeMessageDispatchError, BridgeMessageHandler, Chain, Network, Parachain, RelayChain, + TestExt, }; -// Polkadot -pub use pallet_xcm; -pub use polkadot_runtime_parachains::{ - dmp, hrmp, - inclusion::{AggregateMessageOrigin, UmpQueueId}, +// Bridges +use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + LaneId, MessageKey, OutboundLaneData, }; -pub use xcm::{ - prelude::{MultiLocation, OriginKind, Outcome, VersionedXcm, Weight}, - v3::Error, - DoubleEncoded, +use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult; +use pallet_bridge_messages::{Config, OutboundLanes, Pallet}; +pub use pallet_bridge_messages::{ + Instance1 as BridgeMessagesInstance1, Instance2 as BridgeMessagesInstance2, + Instance3 as BridgeMessagesInstance3, }; -pub struct BridgeHubMessageHandler { - _marker: std::marker::PhantomData<(S, T, I)>, +pub struct BridgeHubMessageHandler { + _marker: std::marker::PhantomData<(S, SI, T, TI)>, } struct LaneIdWrapper(LaneId); @@ -77,21 +88,14 @@ impl From for LaneIdWrapper { } } -type BridgeHubRococoRuntime = ::Runtime; -type BridgeHubWococoRuntime = ::Runtime; - -pub type RococoWococoMessageHandler = - BridgeHubMessageHandler; -pub type WococoRococoMessageHandler = - BridgeHubMessageHandler; - -impl BridgeMessageHandler for BridgeHubMessageHandler +impl BridgeMessageHandler for BridgeHubMessageHandler where - S: Config, - T: Config, - I: 'static, - >::InboundPayload: From>, - >::MessageDispatch: + S: Config, + SI: 'static, + T: Config, + TI: 'static, + >::InboundPayload: From>, + >::MessageDispatch: MessageDispatch, { fn get_source_outbound_messages() -> Vec { @@ -102,16 +106,13 @@ where // collect messages from `OutboundMessages` for each active outbound lane in the source for lane in active_lanes { - let latest_generated_nonce = - OutboundLanes::::get(lane).latest_generated_nonce; - let latest_received_nonce = - OutboundLanes::::get(lane).latest_received_nonce; + let latest_generated_nonce = OutboundLanes::::get(lane).latest_generated_nonce; + let latest_received_nonce = OutboundLanes::::get(lane).latest_received_nonce; (latest_received_nonce + 1..=latest_generated_nonce).for_each(|nonce| { - let encoded_payload: Vec = - Pallet::::outbound_message_data(*lane, nonce) - .expect("Bridge message does not exist") - .into(); + let encoded_payload: Vec = Pallet::::outbound_message_data(*lane, nonce) + .expect("Bridge message does not exist") + .into(); let payload = Vec::::decode(&mut &encoded_payload[..]) .expect("Decodign XCM message failed"); let id: u32 = LaneIdWrapper(*lane).into(); @@ -135,9 +136,9 @@ where // Directly dispatch outbound messages assuming everything is correct // and bypassing the `Relayers` and `InboundLane` logic - let dispatch_result = TargetMessageDispatch::::dispatch(DispatchMessage { + let dispatch_result = TargetMessageDispatch::::dispatch(DispatchMessage { key: MessageKey { lane_id, nonce }, - data: DispatchMessageData::> { payload }, + data: DispatchMessageData::> { payload }, }); let result = match dispatch_result.dispatch_level_result { @@ -153,14 +154,14 @@ where } fn notify_source_message_delivery(lane_id: u32) { - let data = OutboundLanes::::get(LaneIdWrapper::from(lane_id).0); + let data = OutboundLanes::::get(LaneIdWrapper::from(lane_id).0); let new_data = OutboundLaneData { oldest_unpruned_nonce: data.oldest_unpruned_nonce + 1, latest_received_nonce: data.latest_received_nonce + 1, ..data }; - OutboundLanes::::insert(LaneIdWrapper::from(lane_id).0, new_data); + OutboundLanes::::insert(LaneIdWrapper::from(lane_id).0, new_data); } } @@ -168,21 +169,25 @@ where macro_rules! impl_accounts_helpers_for_relay_chain { ( $chain:ident ) => { $crate::impls::paste::paste! { - impl $chain { + impl $chain { /// Fund a set of accounts with a balance pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { ::execute_with(|| { for account in accounts { - $crate::impls::assert_ok!(]>::Balances::force_set_balance( + 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 based on its Parachain Id - pub fn fund_para_sovereign(amount: $crate::impls::Balance, para_id: $crate::impls::ParaId) -> sp_runtime::AccountId32 { + pub fn fund_para_sovereign(amount: $crate::impls::Balance, para_id: $crate::impls::ParaId) -> $crate::impls::AccountId32 { let sovereign_account = ::sovereign_account_id_of_child_para(para_id); Self::fund_accounts(vec![(sovereign_account.clone(), amount)]); sovereign_account @@ -196,16 +201,16 @@ macro_rules! impl_accounts_helpers_for_relay_chain { macro_rules! impl_assert_events_helpers_for_relay_chain { ( $chain:ident ) => { $crate::impls::paste::paste! { - type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; + type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; - impl $chain { + impl $chain { /// Asserts a dispatchable is completely executed and XCM sent pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option<$crate::impls::Weight>) { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::XcmPallet( - $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete(weight) } + [<$chain RuntimeEvent>]::::XcmPallet( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete { used: weight } } ) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), @@ -220,37 +225,38 @@ macro_rules! impl_assert_events_helpers_for_relay_chain { /// Asserts a dispatchable is incompletely executed and XCM sent pub fn assert_xcm_pallet_attempted_incomplete( expected_weight: Option<$crate::impls::Weight>, - expected_error: Option<$crate::impls::Error>, + expected_error: Option<$crate::impls::XcmError>, ) { $crate::impls::assert_expected_events!( Self, vec![ // Dispatchable is properly executed and XCM message sent - [<$chain RuntimeEvent>]::XcmPallet( - $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete(weight, error) } + [<$chain RuntimeEvent>]::::XcmPallet( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete { used: weight, error } } ) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), - error: *error == expected_error.unwrap_or(*error), + error: *error == expected_error.unwrap_or((*error).into()).into(), }, ] ); } - /// Asserts a XCM message is sent + /// Asserts an XCM program is sent. pub fn assert_xcm_pallet_sent() { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::XcmPallet($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, + [<$chain RuntimeEvent>]::::XcmPallet($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, ] ); } - /// 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>, @@ -259,8 +265,8 @@ macro_rules! impl_assert_events_helpers_for_relay_chain { $crate::impls::assert_expected_events!( Self, vec![ - // XCM is succesfully received and proccessed - [<$chain RuntimeEvent>]::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { + // XCM is successfully received and proccessed + [<$chain RuntimeEvent>]::::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { origin: $crate::impls::AggregateMessageOrigin::Ump($crate::impls::UmpQueueId::Para(id)), weight_used, success, @@ -286,7 +292,7 @@ macro_rules! impl_assert_events_helpers_for_relay_chain { macro_rules! impl_hrmp_channels_helpers_for_relay_chain { ( $chain:ident ) => { $crate::impls::paste::paste! { - impl $chain { + impl $chain { /// Init open channel request with another Parachain pub fn init_open_channel_call( recipient_para_id: $crate::impls::ParaId, @@ -326,7 +332,7 @@ macro_rules! impl_hrmp_channels_helpers_for_relay_chain { let relay_root_origin = ::RuntimeOrigin::root(); // Force process HRMP open channel requests without waiting for the next session - $crate::impls::assert_ok!(]>::Hrmp::force_process_hrmp_open( + $crate::impls::assert_ok!(]>::Hrmp::force_process_hrmp_open( relay_root_origin, 0 )); @@ -350,7 +356,7 @@ macro_rules! impl_hrmp_channels_helpers_for_relay_chain { macro_rules! impl_send_transact_helpers_for_relay_chain { ( $chain:ident ) => { $crate::impls::paste::paste! { - impl $chain { + impl $chain { /// A root origin (as governance) sends `xcm::Transact` with `UnpaidExecution` and encoded `call` to child parachain. pub fn send_unpaid_transact_to_parachain_as_root( recipient: $crate::impls::ParaId, @@ -360,11 +366,11 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { ::execute_with(|| { let root_origin = ::RuntimeOrigin::root(); - let destination: $crate::impls::MultiLocation = ::child_location_of(recipient); + let destination: $crate::impls::Location = ::child_location_of(recipient); let xcm = $crate::impls::xcm_transact_unpaid_execution(call, $crate::impls::OriginKind::Superuser); // Send XCM `Transact` - $crate::impls::assert_ok!(]>::XcmPallet::send( + $crate::impls::assert_ok!(]>::XcmPallet::send( root_origin, bx!(destination.into()), bx!(xcm), @@ -381,19 +387,47 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { macro_rules! impl_accounts_helpers_for_parachain { ( $chain:ident ) => { $crate::impls::paste::paste! { - impl $chain { + impl $chain { /// Fund a set of accounts with a balance pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { ::execute_with(|| { for account in accounts { - $crate::impls::assert_ok!(]>::Balances::force_set_balance( + 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, + para_id: $crate::impls::ParaId, + ) -> $crate::impls::AccountId { + let remote_location = $crate::impls::Location::new( + 2, + [ + $crate::impls::Junction::GlobalConsensus(network_id), + $crate::impls::Junction::Parachain(para_id.into()), + ], + ); + ::execute_with(|| { + Self::sovereign_account_id_of(remote_location) + }) + } } } }; @@ -403,16 +437,16 @@ macro_rules! impl_accounts_helpers_for_parachain { macro_rules! impl_assert_events_helpers_for_parachain { ( $chain:ident ) => { $crate::impls::paste::paste! { - type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; + type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; - impl $chain { + impl $chain { /// Asserts a dispatchable is completely executed and XCM sent pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option<$crate::impls::Weight>) { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::PolkadotXcm( - $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete(weight) } + [<$chain RuntimeEvent>]::::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete { used: weight } } ) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), @@ -427,36 +461,36 @@ macro_rules! impl_assert_events_helpers_for_parachain { /// Asserts a dispatchable is incompletely executed and XCM sent pub fn assert_xcm_pallet_attempted_incomplete( expected_weight: Option<$crate::impls::Weight>, - expected_error: Option<$crate::impls::Error>, + expected_error: Option<$crate::impls::XcmError>, ) { $crate::impls::assert_expected_events!( Self, vec![ // Dispatchable is properly executed and XCM message sent - [<$chain RuntimeEvent>]::PolkadotXcm( - $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete(weight, error) } + [<$chain RuntimeEvent>]::::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete { used: weight, error } } ) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), - error: *error == expected_error.unwrap_or(*error), + error: *error == expected_error.unwrap_or((*error).into()).into(), }, ] ); } /// Asserts a dispatchable throws and error when trying to be sent - pub fn assert_xcm_pallet_attempted_error(expected_error: Option<$crate::impls::Error>) { + pub fn assert_xcm_pallet_attempted_error(expected_error: Option<$crate::impls::XcmError>) { $crate::impls::assert_expected_events!( Self, vec![ // Execution fails in the origin with `Barrier` - [<$chain RuntimeEvent>]::PolkadotXcm( - $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Error(error) } + [<$chain RuntimeEvent>]::::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Error { error } } ) => { - error: *error == expected_error.unwrap_or(*error), + error: *error == expected_error.unwrap_or((*error).into()).into(), }, ] ); @@ -467,7 +501,7 @@ macro_rules! impl_assert_events_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::PolkadotXcm($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, + [<$chain RuntimeEvent>]::::PolkadotXcm($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, ] ); } @@ -477,7 +511,7 @@ macro_rules! impl_assert_events_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::ParachainSystem( + [<$chain RuntimeEvent>]::::ParachainSystem( $crate::impls::cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } ) => {}, ] @@ -489,8 +523,8 @@ macro_rules! impl_assert_events_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::DmpQueue($crate::impls::cumulus_pallet_dmp_queue::Event::ExecutedDownward { - outcome: $crate::impls::Outcome::Complete(weight), .. + [<$chain RuntimeEvent>]::::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { + success: true, weight_used: weight, .. }) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), @@ -505,36 +539,32 @@ macro_rules! impl_assert_events_helpers_for_parachain { /// Asserts a XCM from Relay Chain is incompletely executed pub fn assert_dmp_queue_incomplete( expected_weight: Option<$crate::impls::Weight>, - expected_error: Option<$crate::impls::Error>, ) { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::DmpQueue($crate::impls::cumulus_pallet_dmp_queue::Event::ExecutedDownward { - outcome: $crate::impls::Outcome::Incomplete(weight, error), .. + [<$chain RuntimeEvent>]::::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { + success: false, weight_used: weight, .. }) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), - error: *error == expected_error.unwrap_or(*error), }, ] ); } /// Asserts a XCM from Relay Chain is executed with error - pub fn assert_dmp_queue_error( - expected_error: $crate::impls::Error, - ) { + pub fn assert_dmp_queue_error() { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::DmpQueue($crate::impls::cumulus_pallet_dmp_queue::Event::ExecutedDownward { - outcome: $crate::impls::Outcome::Error(error), .. + [<$chain RuntimeEvent>]::::MessageQueue($crate::impls::pallet_message_queue::Event::ProcessingFailed { + .. }) => { - error: *error == expected_error, + }, ] ); @@ -545,8 +575,7 @@ macro_rules! impl_assert_events_helpers_for_parachain { $crate::impls::assert_expected_events!( Self, vec![ - [<$chain RuntimeEvent>]::XcmpQueue( - $crate::impls::cumulus_pallet_xcmp_queue::Event::Success { weight, .. } + [<$chain RuntimeEvent>]::::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { success: true, weight_used: weight, .. } ) => { weight: $crate::impls::weight_within_threshold( ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), @@ -563,10 +592,10 @@ macro_rules! impl_assert_events_helpers_for_parachain { } #[macro_export] -macro_rules! impl_assets_helpers_for_parachain { +macro_rules! impl_assets_helpers_for_system_parachain { ( $chain:ident, $relay_chain:ident ) => { $crate::impls::paste::paste! { - impl $chain { + impl $chain { /// Returns the encoded call for `force_create` from the assets pallet pub fn force_create_asset_call( asset_id: u32, @@ -601,36 +630,6 @@ macro_rules! impl_assets_helpers_for_parachain { $crate::impls::xcm_transact_unpaid_execution(call, origin_kind) } - /// Mint assets making use of the assets pallet - pub fn mint_asset( - signed_origin: ::RuntimeOrigin, - id: u32, - beneficiary: $crate::impls::AccountId, - amount_to_mint: u128, - ) { - ::execute_with(|| { - $crate::impls::assert_ok!(]>::Assets::mint( - signed_origin, - id.into(), - beneficiary.clone().into(), - amount_to_mint - )); - - type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; - - $crate::impls::assert_expected_events!( - Self, - vec![ - RuntimeEvent::Assets($crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }) => { - asset_id: *asset_id == id, - owner: *owner == beneficiary.clone().into(), - amount: *amount == amount_to_mint, - }, - ] - ); - }); - } - /// Force create and mint assets making use of the assets pallet pub fn force_create_and_mint_asset( id: u32, @@ -666,31 +665,255 @@ macro_rules! impl_assets_helpers_for_parachain { ) { use $crate::impls::{Parachain, Inspect, TestExt}; - <$relay_chain>::send_unpaid_transact_to_parachain_as_root( + <$relay_chain>::send_unpaid_transact_to_parachain_as_root( Self::para_id(), Self::force_create_asset_call(id, asset_owner.clone(), is_sufficient, min_balance), ); // Receive XCM message in Assets Parachain Self::execute_with(|| { - type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; Self::assert_dmp_queue_complete(dmp_weight_threshold); $crate::impls::assert_expected_events!( Self, vec![ - RuntimeEvent::Assets($crate::impls::pallet_assets::Event::ForceCreated { asset_id, owner }) => { + RuntimeEvent::::Assets($crate::impls::pallet_assets::Event::ForceCreated { asset_id, owner }) => { asset_id: *asset_id == id, owner: *owner == asset_owner, }, ] ); - assert!(]>::Assets::asset_exists(id.into())); + assert!(]>::Assets::asset_exists(id.clone().into())); }); } } } }; } + +#[macro_export] +macro_rules! impl_assets_helpers_for_parachain { + ( $chain:ident) => { + $crate::impls::paste::paste! { + impl $chain { + /// Create foreign assets using sudo `ForeignAssets::force_create()` + pub fn force_create_foreign_asset( + id: $crate::impls::v3::Location, + owner: $crate::impls::AccountId, + is_sufficient: bool, + min_balance: u128, + prefund_accounts: Vec<($crate::impls::AccountId, u128)>, + ) { + use $crate::impls::Inspect; + let sudo_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::root(); + ::execute_with(|| { + $crate::impls::assert_ok!( + ]>::ForeignAssets::force_create( + sudo_origin, + id.clone(), + owner.clone().into(), + is_sufficient, + min_balance, + ) + ); + assert!(]>::ForeignAssets::asset_exists(id.clone())); + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + $crate::impls::assert_expected_events!( + Self, + vec![ + RuntimeEvent::::ForeignAssets( + $crate::impls::pallet_assets::Event::ForceCreated { + asset_id, + .. + } + ) => { asset_id: *asset_id == id, }, + ] + ); + }); + for (beneficiary, amount) in prefund_accounts.into_iter() { + let signed_origin = + <$chain as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone()); + Self::mint_foreign_asset(signed_origin, id.clone(), beneficiary, amount); + } + } + + /// Mint assets making use of the ForeignAssets pallet-assets instance + pub fn mint_foreign_asset( + signed_origin: ::RuntimeOrigin, + id: $crate::impls::v3::Location, + beneficiary: $crate::impls::AccountId, + amount_to_mint: u128, + ) { + ::execute_with(|| { + $crate::impls::assert_ok!(]>::ForeignAssets::mint( + signed_origin, + id.clone().into(), + beneficiary.clone().into(), + amount_to_mint + )); + + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + + $crate::impls::assert_expected_events!( + Self, + vec![ + RuntimeEvent::::ForeignAssets( + $crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount } + ) => { + asset_id: *asset_id == id, + owner: *owner == beneficiary.clone().into(), + amount: *amount == amount_to_mint, + }, + ] + ); + }); + } + /// Create assets using sudo `Assets::force_create()` + pub fn force_create_asset( + id: u32, + owner: $crate::impls::AccountId, + is_sufficient: bool, + min_balance: u128, + prefund_accounts: Vec<($crate::impls::AccountId, u128)>, + ) { + use $crate::impls::Inspect; + let sudo_origin = <$chain as $crate::impls::Chain>::RuntimeOrigin::root(); + ::execute_with(|| { + $crate::impls::assert_ok!( + ]>::Assets::force_create( + sudo_origin, + id.clone().into(), + owner.clone().into(), + is_sufficient, + min_balance, + ) + ); + assert!(]>::Assets::asset_exists(id.clone())); + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + $crate::impls::assert_expected_events!( + Self, + vec![ + RuntimeEvent::::Assets( + $crate::impls::pallet_assets::Event::ForceCreated { + asset_id, + .. + } + ) => { asset_id: *asset_id == id, }, + ] + ); + }); + for (beneficiary, amount) in prefund_accounts.into_iter() { + let signed_origin = + <$chain as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone()); + Self::mint_asset(signed_origin, id.clone(), beneficiary, amount); + } + } + + /// Mint assets making use of the assets pallet + pub fn mint_asset( + signed_origin: ::RuntimeOrigin, + id: u32, + beneficiary: $crate::impls::AccountId, + amount_to_mint: u128, + ) { + ::execute_with(|| { + $crate::impls::assert_ok!(]>::Assets::mint( + signed_origin, + id.clone().into(), + beneficiary.clone().into(), + amount_to_mint + )); + + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + + $crate::impls::assert_expected_events!( + Self, + vec![ + RuntimeEvent::::Assets( + $crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount } + ) => { + asset_id: *asset_id == id, + owner: *owner == beneficiary.clone().into(), + amount: *amount == amount_to_mint, + }, + ] + ); + }); + } + + /// Returns the encoded call for `create` from the assets pallet + pub fn create_asset_call( + asset_id: u32, + min_balance: $crate::impls::Balance, + admin: $crate::impls::AccountId, + ) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::{Chain, Encode}; + + ::RuntimeCall::Assets($crate::impls::pallet_assets::Call::< + ::Runtime, + $crate::impls::pallet_assets::Instance1, + >::create { + id: asset_id.into(), + min_balance, + admin: admin.into(), + }) + .encode() + .into() + } + + /// Returns the encoded call for `create` from the foreign assets pallet + pub fn create_foreign_asset_call( + asset_id: $crate::impls::v3::Location, + min_balance: $crate::impls::Balance, + admin: $crate::impls::AccountId, + ) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::{Chain, Encode}; + + ::RuntimeCall::ForeignAssets($crate::impls::pallet_assets::Call::< + ::Runtime, + $crate::impls::pallet_assets::Instance2, + >::create { + id: asset_id.into(), + min_balance, + admin: admin.into(), + }) + .encode() + .into() + } + } + } + }; +} + +#[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::Location, 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 8a8081c9fac388c810d43f45ccbfb67a73334f0b..40204ca297a0a2626e4c3946a090199c3269e7e4 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -13,336 +13,178 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod constants; pub mod impls; pub mod macros; pub mod xcm_helpers; -use constants::{ - accounts::{ALICE, BOB}, - asset_hub_rococo, asset_hub_westend, asset_hub_wococo, bridge_hub_rococo, penpal, rococo, - westend, -}; -use impls::{RococoWococoMessageHandler, WococoRococoMessageHandler}; -pub use paste; +pub use xcm_emulator; // Substrate -use frame_support::traits::OnInitialize; -pub use pallet_balances; - -// Cumulus -pub use cumulus_pallet_xcmp_queue; -pub use xcm_emulator::Chain; -use xcm_emulator::{ - decl_test_bridges, decl_test_networks, decl_test_parachains, decl_test_relay_chains, - decl_test_sender_receiver_accounts_parameter_types, DefaultMessageProcessor, +use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; +use frame_support::parameter_types; +use grandpa::AuthorityId as GrandpaId; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_core::{sr25519, storage::Storage, Pair, Public}; +use sp_runtime::{ + traits::{AccountIdConversion, IdentifyAccount, Verify}, + BuildStorage, MultiSignature, }; -// Polkadot -pub use pallet_xcm; -pub use xcm::prelude::{AccountId32, WeightLimit}; +// Polakdot +use parachains_common::BlockNumber; +use polkadot_parachain_primitives::primitives::Sibling; +use polkadot_runtime_parachains::configuration::HostConfiguration; -decl_test_relay_chains! { - #[api_version(8)] - pub struct Westend { - genesis = westend::genesis(), - on_init = (), - runtime = westend_runtime, - core = { - MessageProcessor: DefaultMessageProcessor, - SovereignAccountOf: westend_runtime::xcm_config::LocationConverter, //TODO: rename to SovereignAccountOf, - }, - pallets = { - XcmPallet: westend_runtime::XcmPallet, - Sudo: westend_runtime::Sudo, - Balances: westend_runtime::Balances, - Treasury: westend_runtime::Treasury, - AssetRate: westend_runtime::AssetRate, - } - }, - #[api_version(8)] - pub struct Rococo { - genesis = rococo::genesis(), - on_init = (), - runtime = rococo_runtime, - core = { - MessageProcessor: DefaultMessageProcessor, - SovereignAccountOf: rococo_runtime::xcm_config::LocationConverter, //TODO: rename to SovereignAccountOf, - }, - pallets = { - XcmPallet: rococo_runtime::XcmPallet, - Sudo: rococo_runtime::Sudo, - Balances: rococo_runtime::Balances, - Hrmp: rococo_runtime::Hrmp, - } - }, - #[api_version(8)] - pub struct Wococo { - genesis = rococo::genesis(), - on_init = (), - runtime = rococo_runtime, - core = { - MessageProcessor: DefaultMessageProcessor, - SovereignAccountOf: rococo_runtime::xcm_config::LocationConverter, //TODO: rename to SovereignAccountOf, - }, - pallets = { - XcmPallet: rococo_runtime::XcmPallet, - Sudo: rococo_runtime::Sudo, - Balances: rococo_runtime::Balances, - } - } +// Cumulus +use parachains_common::{AccountId, AuraId}; +use polkadot_primitives::{AssignmentId, ValidatorId}; + +pub const XCM_V2: u32 = 2; +pub const XCM_V3: u32 = 3; +pub const XCM_V4: u32 = 4; +pub const REF_TIME_THRESHOLD: u64 = 33; +pub const PROOF_SIZE_THRESHOLD: u64 = 33; + +/// The default XCM version to set in genesis config. +pub const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; + +type AccountPublic = ::Signer; + +// This asset is added to AH as Asset and reserved transfer between Parachain and AH +pub const RESERVABLE_ASSET_ID: u32 = 1; +// This asset is added to AH as ForeignAsset and teleported between Penpal and AH +pub const TELEPORTABLE_ASSET_ID: u32 = 2; + +pub const PENPAL_ID: u32 = 2000; +pub const ASSETS_PALLET_ID: u8 = 50; + +parameter_types! { + pub PenpalTeleportableAssetLocation: xcm::v3::Location + = xcm::v3::Location::new(1, [ + xcm::v3::Junction::Parachain(PENPAL_ID), + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(TELEPORTABLE_ASSET_ID.into()), + ] + ); + pub PenpalSiblingSovereigAccount: AccountId = Sibling::from(PENPAL_ID).into_account_truncating(); } -decl_test_parachains! { - // Westend Parachains - pub struct AssetHubWestend { - genesis = asset_hub_westend::genesis(), - on_init = { - asset_hub_westend_runtime::AuraExt::on_initialize(1); - }, - runtime = asset_hub_westend_runtime, - core = { - XcmpMessageHandler: asset_hub_westend_runtime::XcmpQueue, - DmpMessageHandler: asset_hub_westend_runtime::DmpQueue, - LocationToAccountId: asset_hub_westend_runtime::xcm_config::LocationToAccountId, - ParachainInfo: asset_hub_westend_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: asset_hub_westend_runtime::PolkadotXcm, - Balances: asset_hub_westend_runtime::Balances, - Assets: asset_hub_westend_runtime::Assets, - ForeignAssets: asset_hub_westend_runtime::ForeignAssets, - PoolAssets: asset_hub_westend_runtime::PoolAssets, - AssetConversion: asset_hub_westend_runtime::AssetConversion, - } - }, - pub struct PenpalWestendA { - genesis = penpal::genesis(penpal::PARA_ID_A), - on_init = { - penpal_runtime::AuraExt::on_initialize(1); - }, - runtime = penpal_runtime, - core = { - XcmpMessageHandler: penpal_runtime::XcmpQueue, - DmpMessageHandler: penpal_runtime::DmpQueue, - LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, - ParachainInfo: penpal_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: penpal_runtime::PolkadotXcm, - Assets: penpal_runtime::Assets, - Balances: penpal_runtime::Balances, - } - }, - // Rococo Parachains - pub struct BridgeHubRococo { - genesis = bridge_hub_rococo::genesis(), - on_init = { - bridge_hub_rococo_runtime::AuraExt::on_initialize(1); - }, - runtime = bridge_hub_rococo_runtime, - core = { - XcmpMessageHandler: bridge_hub_rococo_runtime::XcmpQueue, - DmpMessageHandler: bridge_hub_rococo_runtime::DmpQueue, - LocationToAccountId: bridge_hub_rococo_runtime::xcm_config::LocationToAccountId, - ParachainInfo: bridge_hub_rococo_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: bridge_hub_rococo_runtime::PolkadotXcm, - Balances: bridge_hub_rococo_runtime::Balances, - } - }, - // AssetHubRococo - pub struct AssetHubRococo { - genesis = asset_hub_rococo::genesis(), - on_init = { - asset_hub_rococo_runtime::AuraExt::on_initialize(1); - }, - runtime = asset_hub_rococo_runtime, - core = { - XcmpMessageHandler: asset_hub_rococo_runtime::XcmpQueue, - DmpMessageHandler: asset_hub_rococo_runtime::DmpQueue, - LocationToAccountId: asset_hub_rococo_runtime::xcm_config::LocationToAccountId, - ParachainInfo: asset_hub_rococo_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: asset_hub_rococo_runtime::PolkadotXcm, - Assets: asset_hub_rococo_runtime::Assets, - ForeignAssets: asset_hub_rococo_runtime::ForeignAssets, - PoolAssets: asset_hub_rococo_runtime::PoolAssets, - AssetConversion: asset_hub_rococo_runtime::AssetConversion, - Balances: asset_hub_rococo_runtime::Balances, - } - }, - pub struct PenpalRococoA { - genesis = penpal::genesis(penpal::PARA_ID_A), - on_init = { - penpal_runtime::AuraExt::on_initialize(1); - }, - runtime = penpal_runtime, - core = { - XcmpMessageHandler: penpal_runtime::XcmpQueue, - DmpMessageHandler: penpal_runtime::DmpQueue, - LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, - ParachainInfo: penpal_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: penpal_runtime::PolkadotXcm, - Assets: penpal_runtime::Assets, - } - }, - pub struct PenpalRococoB { - genesis = penpal::genesis(penpal::PARA_ID_B), - on_init = { - penpal_runtime::AuraExt::on_initialize(1); - }, - runtime = penpal_runtime, - core = { - XcmpMessageHandler: penpal_runtime::XcmpQueue, - DmpMessageHandler: penpal_runtime::DmpQueue, - LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, - ParachainInfo: penpal_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: penpal_runtime::PolkadotXcm, - Assets: penpal_runtime::Assets, - } - }, - // Wococo Parachains - pub struct BridgeHubWococo { - genesis = bridge_hub_rococo::genesis(), - on_init = { - bridge_hub_rococo_runtime::AuraExt::on_initialize(1); - // TODO: manage to set_wococo_flavor with `set_storage` - }, - runtime = bridge_hub_rococo_runtime, - core = { - XcmpMessageHandler: bridge_hub_rococo_runtime::XcmpQueue, - DmpMessageHandler: bridge_hub_rococo_runtime::DmpQueue, - LocationToAccountId: bridge_hub_rococo_runtime::xcm_config::LocationToAccountId, - ParachainInfo: bridge_hub_rococo_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: bridge_hub_rococo_runtime::PolkadotXcm, - } - }, - pub struct AssetHubWococo { - genesis = asset_hub_wococo::genesis(), - on_init = { - asset_hub_rococo_runtime::AuraExt::on_initialize(1); - // TODO: manage to set_wococo_flavor with `set_storage` - }, - runtime = asset_hub_rococo_runtime, - core = { - XcmpMessageHandler: asset_hub_rococo_runtime::XcmpQueue, - DmpMessageHandler: asset_hub_rococo_runtime::DmpQueue, - LocationToAccountId: asset_hub_rococo_runtime::xcm_config::LocationToAccountId, - ParachainInfo: asset_hub_rococo_runtime::ParachainInfo, - }, - pallets = { - PolkadotXcm: asset_hub_rococo_runtime::PolkadotXcm, - Assets: asset_hub_rococo_runtime::Assets, - ForeignAssets: asset_hub_rococo_runtime::ForeignAssets, - PoolAssets: asset_hub_rococo_runtime::PoolAssets, - AssetConversion: asset_hub_rococo_runtime::AssetConversion, - Balances: asset_hub_rococo_runtime::Balances, - } - } +/// Helper function to generate a crypto pair from seed +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() } -decl_test_networks! { - pub struct WestendMockNet { - relay_chain = Westend, - parachains = vec![ - AssetHubWestend, - PenpalWestendA, - ], - bridge = () - }, - pub struct RococoMockNet { - relay_chain = Rococo, - parachains = vec![ - AssetHubRococo, - BridgeHubRococo, - PenpalRococoA, - PenpalRococoB, - ], - bridge = RococoWococoMockBridge - }, - pub struct WococoMockNet { - relay_chain = Wococo, - parachains = vec![ - AssetHubWococo, - BridgeHubWococo, - ], - bridge = WococoRococoMockBridge - } +/// Helper function to generate an account ID from seed. +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() } -decl_test_bridges! { - pub struct RococoWococoMockBridge { - source = BridgeHubRococo, - target = BridgeHubWococo, - handler = RococoWococoMessageHandler - }, - pub struct WococoRococoMockBridge { - source = BridgeHubWococo, - target = BridgeHubRococo, - handler = WococoRococoMessageHandler +pub fn get_host_config() -> HostConfiguration { + HostConfiguration { + max_upward_queue_count: 10, + max_upward_queue_size: 51200, + max_upward_message_size: 51200, + max_upward_message_num_per_candidate: 10, + max_downward_message_size: 51200, + hrmp_sender_deposit: 0, + hrmp_recipient_deposit: 0, + hrmp_channel_max_capacity: 1000, + hrmp_channel_max_message_size: 102400, + hrmp_channel_max_total_size: 102400, + hrmp_max_parachain_outbound_channels: 30, + hrmp_max_parachain_inbound_channels: 30, + ..Default::default() } } -// Westend implementation -impl_accounts_helpers_for_relay_chain!(Westend); -impl_assert_events_helpers_for_relay_chain!(Westend); -impl_send_transact_helpers_for_relay_chain!(Westend); - -// Rococo implementation -impl_accounts_helpers_for_relay_chain!(Rococo); -impl_assert_events_helpers_for_relay_chain!(Rococo); -impl_hrmp_channels_helpers_for_relay_chain!(Rococo); -impl_send_transact_helpers_for_relay_chain!(Rococo); - -// Wococo implementation -impl_accounts_helpers_for_relay_chain!(Wococo); -impl_assert_events_helpers_for_relay_chain!(Wococo); -impl_send_transact_helpers_for_relay_chain!(Wococo); - -// AssetHubWestend implementation -impl_accounts_helpers_for_parachain!(AssetHubWestend); -impl_assets_helpers_for_parachain!(AssetHubWestend, Westend); -impl_assert_events_helpers_for_parachain!(AssetHubWestend); - -// AssetHubRococo implementation -impl_accounts_helpers_for_parachain!(AssetHubRococo); -impl_assets_helpers_for_parachain!(AssetHubRococo, Rococo); -impl_assert_events_helpers_for_parachain!(AssetHubRococo); - -// PenpalWestendA implementation -impl_assert_events_helpers_for_parachain!(PenpalWestendA); +/// Helper function used in tests to build the genesis storage using given RuntimeGenesisConfig and +/// code Used in `legacy_vs_json_check` submods to verify storage building with JSON patch against +/// building with RuntimeGenesisConfig struct. +pub fn build_genesis_storage(builder: &dyn BuildStorage, code: &[u8]) -> Storage { + let mut storage = builder.build_storage().unwrap(); + storage + .top + .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.into()); + storage +} -// BridgeHubRococo implementation -impl_accounts_helpers_for_parachain!(BridgeHubRococo); -impl_assert_events_helpers_for_parachain!(BridgeHubRococo); +pub mod accounts { + use super::*; + pub const ALICE: &str = "Alice"; + pub const BOB: &str = "Bob"; + pub const CHARLIE: &str = "Charlie"; + pub const DAVE: &str = "Dave"; + pub const EVE: &str = "Eve"; + pub const FERDIE: &str = "Ferdie"; + pub const ALICE_STASH: &str = "Alice//stash"; + pub const BOB_STASH: &str = "Bob//stash"; + pub const CHARLIE_STASH: &str = "Charlie//stash"; + pub const DAVE_STASH: &str = "Dave//stash"; + pub const EVE_STASH: &str = "Eve//stash"; + pub const FERDIE_STASH: &str = "Ferdie//stash"; + pub const FERDIE_BEEFY: &str = "Ferdie//stash"; + + pub fn init_balances() -> Vec { + 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), + ] + } +} -// PenpalRococo implementations -impl_assert_events_helpers_for_parachain!(PenpalRococoA); -impl_assert_events_helpers_for_parachain!(PenpalRococoB); +pub mod collators { + use super::*; + + pub fn invulnerables() -> Vec<(AccountId, AuraId)> { + vec![ + ( + get_account_id_from_seed::("Alice"), + get_from_seed::("Alice"), + ), + (get_account_id_from_seed::("Bob"), get_from_seed::("Bob")), + ] + } +} -decl_test_sender_receiver_accounts_parameter_types! { - // Relays - Westend { sender: ALICE, receiver: BOB }, - Rococo { sender: ALICE, receiver: BOB }, - Wococo { sender: ALICE, receiver: BOB }, - // Asset Hubs - AssetHubWestend { sender: ALICE, receiver: BOB }, - AssetHubRococo { sender: ALICE, receiver: BOB }, - AssetHubWococo { sender: ALICE, receiver: BOB }, - // Bridged Hubs - BridgeHubRococo { sender: ALICE, receiver: BOB }, - BridgeHubWococo { sender: ALICE, receiver: BOB }, - // Penpals - PenpalWestendA { sender: ALICE, receiver: BOB }, - PenpalRococoA { sender: ALICE, receiver: BOB }, - PenpalRococoB { sender: ALICE, receiver: BOB } +pub mod validators { + use super::*; + + pub fn initial_authorities() -> Vec<( + AccountId, + AccountId, + BabeId, + GrandpaId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + BeefyId, + )> { + let seed = "Alice"; + vec![( + get_account_id_from_seed::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + )] + } } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index a65b2057afda2a9cf8006427b403aecb2421bbc2..6951de6faa728d1ea88bbbc4cfb7f7683ab14935 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -13,28 +13,53 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub use paste; + +// Substrate +pub use frame_support::{pallet_prelude::Weight, weights::WeightToFee}; +pub use pallet_assets; +pub use pallet_balances; +pub use pallet_message_queue; +pub use pallet_xcm; + +// Polkadot +pub use xcm::{ + prelude::{ + AccountId32, All, Asset, AssetId, BuyExecution, DepositAsset, ExpectTransactStatus, + Fungible, Here, Location, MaybeErrorCode, OriginKind, RefundSurplus, Transact, Unlimited, + VersionedXcm, WeightLimit, WithdrawAsset, Xcm, + }, + v3::Location as V3Location, +}; + +// Cumulus +pub use asset_test_utils; +pub use cumulus_pallet_xcmp_queue; +pub use parachains_common::AccountId; +pub use xcm_emulator::Chain; + #[macro_export] macro_rules! test_parachain_is_trusted_teleporter { ( $sender_para:ty, $sender_xcm_config:ty, vec![$( $receiver_para:ty ),+], ($assets:expr, $amount:expr) ) => { - $crate::paste::paste! { + $crate::macros::paste::paste! { // init Origin variables let sender = [<$sender_para Sender>]::get(); let mut para_sender_balance_before = - <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; - let origin = <$sender_para as $crate::Chain>::RuntimeOrigin::signed(sender.clone()); + <$sender_para as $crate::macros::Chain>::account_data_of(sender.clone()).free; + let origin = <$sender_para as $crate::macros::Chain>::RuntimeOrigin::signed(sender.clone()); let fee_asset_item = 0; - let weight_limit = $crate::WeightLimit::Unlimited; + let weight_limit = $crate::macros::WeightLimit::Unlimited; $( { // init Destination variables let receiver = [<$receiver_para Receiver>]::get(); let para_receiver_balance_before = - <$receiver_para as $crate::Chain>::account_data_of(receiver.clone()).free; + <$receiver_para as $crate::macros::Chain>::account_data_of(receiver.clone()).free; let para_destination = <$sender_para>::sibling_location_of(<$receiver_para>::para_id()); - let beneficiary: MultiLocation = - $crate::AccountId32 { network: None, id: receiver.clone().into() }.into(); + let beneficiary: Location = + $crate::macros::AccountId32 { network: None, id: receiver.clone().into() }.into(); // Send XCM message from Origin Parachain // We are only testing the limited teleport version, which should be ok since success will @@ -42,26 +67,26 @@ macro_rules! test_parachain_is_trusted_teleporter { <$sender_para>::execute_with(|| { assert_ok!(<$sender_para as [<$sender_para Pallet>]>::PolkadotXcm::limited_teleport_assets( origin.clone(), - bx!(para_destination.into()), - bx!(beneficiary.into()), + bx!(para_destination.clone().into()), + bx!(beneficiary.clone().into()), bx!($assets.clone().into()), fee_asset_item, weight_limit.clone(), )); - type RuntimeEvent = <$sender_para as $crate::Chain>::RuntimeEvent; + type RuntimeEvent = <$sender_para as $crate::macros::Chain>::RuntimeEvent; assert_expected_events!( $sender_para, vec![ RuntimeEvent::PolkadotXcm( - $crate::pallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } } + $crate::macros::pallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } } ) => {}, RuntimeEvent::XcmpQueue( - $crate::cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + $crate::macros::cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } ) => {}, RuntimeEvent::Balances( - $crate::pallet_balances::Event::Withdraw { who: sender, amount } + $crate::macros::pallet_balances::Event::Burned { who: sender, amount } ) => {}, ] ); @@ -69,16 +94,16 @@ macro_rules! test_parachain_is_trusted_teleporter { // Receive XCM message in Destination Parachain <$receiver_para>::execute_with(|| { - type RuntimeEvent = <$receiver_para as $crate::Chain>::RuntimeEvent; + type RuntimeEvent = <$receiver_para as $crate::macros::Chain>::RuntimeEvent; assert_expected_events!( $receiver_para, vec![ RuntimeEvent::Balances( - $crate::pallet_balances::Event::Deposit { who: receiver, .. } + $crate::macros::pallet_balances::Event::Minted { who: receiver, .. } ) => {}, - RuntimeEvent::XcmpQueue( - $crate::cumulus_pallet_xcmp_queue::Event::Success { .. } + RuntimeEvent::MessageQueue( + $crate::macros::pallet_message_queue::Event::Processed { success: true, .. } ) => {}, ] ); @@ -86,11 +111,11 @@ macro_rules! test_parachain_is_trusted_teleporter { // Check if balances are updated accordingly in Origin and Destination Parachains let para_sender_balance_after = - <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; + <$sender_para as $crate::macros::Chain>::account_data_of(sender.clone()).free; let para_receiver_balance_after = - <$receiver_para as $crate::Chain>::account_data_of(receiver.clone()).free; + <$receiver_para as $crate::macros::Chain>::account_data_of(receiver.clone()).free; let delivery_fees = <$sender_para>::execute_with(|| { - asset_test_utils::xcm_helpers::transfer_assets_delivery_fees::< + $crate::macros::asset_test_utils::xcm_helpers::transfer_assets_delivery_fees::< <$sender_xcm_config as xcm_executor::Config>::XcmSender, >($assets.clone(), fee_asset_item, weight_limit.clone(), beneficiary, para_destination) }); @@ -99,7 +124,7 @@ macro_rules! test_parachain_is_trusted_teleporter { assert!(para_receiver_balance_after > para_receiver_balance_before); // Update sender balance - para_sender_balance_before = <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; + para_sender_balance_before = <$sender_para as $crate::macros::Chain>::account_data_of(sender.clone()).free; } )+ } 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 dc6d19d06f45f004f643be1eb87541467e3c9397..25e1cffad543c0aad6d955315721b63c6d509ca1 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -13,19 +13,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Cumulus use parachains_common::AccountId; + +// Polkadot use xcm::{prelude::*, DoubleEncoded}; /// Helper method to build a XCM with a `Transact` instruction and paying for its execution pub fn xcm_transact_paid_execution( call: DoubleEncoded<()>, origin_kind: OriginKind, - native_asset: MultiAsset, + native_asset: Asset, beneficiary: AccountId, ) -> VersionedXcm<()> { let weight_limit = WeightLimit::Unlimited; let require_weight_at_most = Weight::from_parts(1000000000, 200000); - let native_assets: MultiAssets = native_asset.clone().into(); + let native_assets: Assets = native_asset.clone().into(); VersionedXcm::from(Xcm(vec![ WithdrawAsset(native_assets), @@ -34,9 +37,9 @@ pub fn xcm_transact_paid_execution( RefundSurplus, DepositAsset { assets: All.into(), - beneficiary: MultiLocation { + beneficiary: Location { parents: 0, - interior: X1(AccountId32 { network: None, id: beneficiary.into() }), + interior: [AccountId32 { network: None, id: beneficiary.into() }].into(), }, }, ])) @@ -56,3 +59,13 @@ 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: &Assets, fee_idx: usize) -> Option<(Location, u128)> { + let asset = assets.inner().into_iter().enumerate().find(|a| a.0 != fee_idx)?.1.clone(); + let asset_amount = match asset.fun { + Fungible(amount) => amount, + _ => return None, + }; + Some((asset.id.0, 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 new file mode 100644 index 0000000000000000000000000000000000000000..eb0a8a850d06928d67147dc14a11f566d1ad7c9d --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rococo-system-emulated-network" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +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 new file mode 100644 index 0000000000000000000000000000000000000000..70f23ef8260ca408e55079e5797b09cc817872cd --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/src/lib.rs @@ -0,0 +1,55 @@ +// 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 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 +use emulated_integration_tests_common::{ + accounts::{ALICE, BOB}, + xcm_emulator::{decl_test_networks, decl_test_sender_receiver_accounts_parameter_types}, +}; + +decl_test_networks! { + pub struct RococoMockNet { + relay_chain = Rococo, + parachains = vec![ + AssetHubRococo, + BridgeHubRococo, + PenpalA, + PenpalB, + PeopleRococo, + ], + bridge = () + }, +} + +decl_test_sender_receiver_accounts_parameter_types! { + RococoRelay { sender: ALICE, receiver: BOB }, + AssetHubRococoPara { sender: ALICE, receiver: BOB }, + BridgeHubRococoPara { sender: ALICE, receiver: BOB }, + PenpalAPara { 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 new file mode 100644 index 0000000000000000000000000000000000000000..744cbe4f8c1e31ed5a9b122a5b5939509234236a --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "rococo-westend-system-emulated-network" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +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 } +rococo-emulated-chain = { path = "../../chains/relays/rococo" } +westend-emulated-chain = { path = "../../chains/relays/westend" } +asset-hub-rococo-emulated-chain = { path = "../../chains/parachains/assets/asset-hub-rococo" } +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 new file mode 100644 index 0000000000000000000000000000000000000000..ee8b038a364d73301732f278786b30b18d534643 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs @@ -0,0 +1,100 @@ +// 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 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; + +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; + +// Cumulus +use emulated_integration_tests_common::{ + accounts::{ALICE, BOB}, + impls::{BridgeHubMessageHandler, BridgeMessagesInstance1, BridgeMessagesInstance3}, + xcm_emulator::{ + decl_test_bridges, decl_test_networks, decl_test_sender_receiver_accounts_parameter_types, + Chain, + }, +}; + +decl_test_networks! { + pub struct RococoMockNet { + relay_chain = Rococo, + parachains = vec![ + AssetHubRococo, + BridgeHubRococo, + PenpalA, + ], + bridge = RococoWestendMockBridge + + }, + pub struct WestendMockNet { + relay_chain = Westend, + parachains = vec![ + AssetHubWestend, + BridgeHubWestend, + ], + bridge = WestendRococoMockBridge + }, +} + +decl_test_bridges! { + pub struct RococoWestendMockBridge { + source = BridgeHubRococoPara, + target = BridgeHubWestendPara, + handler = RococoWestendMessageHandler + }, + pub struct WestendRococoMockBridge { + source = BridgeHubWestendPara, + target = BridgeHubRococoPara, + handler = WestendRococoMessageHandler + } +} + +type BridgeHubRococoRuntime = ::Runtime; +type BridgeHubWestendRuntime = ::Runtime; + +pub type RococoWestendMessageHandler = BridgeHubMessageHandler< + BridgeHubRococoRuntime, + BridgeMessagesInstance3, + BridgeHubWestendRuntime, + BridgeMessagesInstance1, +>; +pub type WestendRococoMessageHandler = BridgeHubMessageHandler< + BridgeHubWestendRuntime, + BridgeMessagesInstance1, + BridgeHubRococoRuntime, + BridgeMessagesInstance3, +>; + +decl_test_sender_receiver_accounts_parameter_types! { + RococoRelay { sender: ALICE, receiver: BOB }, + AssetHubRococoPara { sender: ALICE, receiver: BOB }, + BridgeHubRococoPara { sender: ALICE, receiver: BOB }, + WestendRelay { sender: ALICE, receiver: BOB }, + AssetHubWestendPara { 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 new file mode 100644 index 0000000000000000000000000000000000000000..64bc91f442d1b27166fbc4f0a2dc22798c97ff39 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "westend-system-emulated-network" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +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 new file mode 100644 index 0000000000000000000000000000000000000000..9fbc773bc50e136af3272e851f826d7cf63bc0b6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/src/lib.rs @@ -0,0 +1,59 @@ +// 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 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 +use emulated_integration_tests_common::{ + accounts::{ALICE, BOB}, + xcm_emulator::{decl_test_networks, decl_test_sender_receiver_accounts_parameter_types}, +}; + +decl_test_networks! { + pub struct WestendMockNet { + relay_chain = Westend, + parachains = vec![ + AssetHubWestend, + BridgeHubWestend, + CollectivesWestend, + PeopleWestend, + PenpalA, + PenpalB, + ], + bridge = () + }, +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..13eb7d8dfc49a7010f61c2f106c508a7f9bae031 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "asset-hub-rococo-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +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" + +# 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 } + +# 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" } + +# Cumulus +asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } +parachains-common = { path = "../../../../../../parachains/common" } +cumulus-pallet-parachain-system = { path = "../../../../../../pallets/parachain-system", default-features = false } +testnet-parachains-constants = { path = "../../../../../runtimes/constants", features = ["rococo"] } +asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo" } +penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } +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 new file mode 100644 index 0000000000000000000000000000000000000000..21d858f1fe514b8005c968f670f7ff05714d2f79 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -0,0 +1,91 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod imports { + pub use codec::Encode; + + // Substrate + pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{DispatchError, DispatchResult, ModuleError}, + traits::fungibles::Inspect, + }; + + // Polkadot + pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3, + }; + + // 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, Chain, Parachain as Para, RelayChain as Relay, Test, + TestArgs, TestContext, TestExt, + }, + xcm_helpers::{non_fee_asset, xcm_transact_paid_execution}, + ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3, + }; + pub use parachains_common::Balance; + pub use rococo_system_emulated_network::{ + asset_hub_rococo_emulated_chain::{ + genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED}, + AssetHubRococoParaPallet as AssetHubRococoPallet, + }, + penpal_emulated_chain::{ + PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, + PenpalBParaPallet as PenpalBPallet, ED as PENPAL_ED, + }, + rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, + AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, + AssetHubRococoParaSender as AssetHubRococoSender, BridgeHubRococoPara as BridgeHubRococo, + BridgeHubRococoParaReceiver as BridgeHubRococoReceiver, PenpalAPara as PenpalA, + PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, + PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver, RococoRelay as Rococo, + RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, + }; + + // Runtimes + pub use asset_hub_rococo_runtime::xcm_config::{ + TokenLocation as RelayLocation, UniversalLocation as AssetHubRococoUniversalLocation, + XcmConfig as AssetHubRococoXcmConfig, + }; + pub use penpal_runtime::xcm_config::{ + LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, + LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, + UniversalLocation as PenpalUniversalLocation, XcmConfig as PenpalRococoXcmConfig, + }; + pub use rococo_runtime::xcm_config::{ + UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, + }; + + pub const ASSET_ID: u32 = 3; + pub const ASSET_MIN_BALANCE: u128 = 1000; + + pub type RelayToSystemParaTest = Test; + pub type RelayToParaTest = Test; + pub type ParaToRelayTest = Test; + pub type SystemParaToRelayTest = Test; + pub type SystemParaToParaTest = Test; + pub type ParaToSystemParaTest = Test; + pub type ParaToParaThroughRelayTest = Test; +} + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs similarity index 100% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/mod.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs 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 new file mode 100644 index 0000000000000000000000000000000000000000..705c9613b647a03c444fa304d69cb046d1df20b5 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -0,0 +1,1142 @@ +// 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::imports::*; + +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![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == Rococo::sovereign_account_id_of( + t.args.dest.clone() + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_to_relay_sender_assertions(t: ParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance, .. } + ) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +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![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + 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.clone() + ), + amount: *amount == t.args.amount, + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); + AssetHubRococo::assert_xcm_pallet_sent(); +} + +fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.receiver.account_id, + }, + ] + ); +} + +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![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance, .. } + ) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_relay = + Rococo::sovereign_account_id_of(Rococo::child_location_of(PenpalA::para_id())); + + Rococo::assert_ump_queue_processed( + true, + Some(PenpalA::para_id()), + Some(Weight::from_parts(306305000, 7_186)), + ); + + assert_expected_events!( + Rococo, + vec![ + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_relay.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +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()), + ); + + AssetHubRococo::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +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![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { asset_id, from, to, amount } + ) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + from: *from == t.sender.account_id, + to: *to == AssetHubRococo::sovereign_account_id_of( + t.args.dest.clone() + ), + amount: *amount == t.args.amount, + }, + // Native asset to pay for fees is transferred to Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubRococo::sovereign_account_id_of( + t.args.dest.clone() + ), + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); +} + +fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let reservable_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); + assert_expected_events!( + PenpalA, + vec![ + // Fees amount to reserve transfer is burned from Parachains's sender account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.sender.account_id, + }, + // Amount to reserve transfer is burned from Parachains's sender account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == reservable_asset_location, + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); +} + +fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + PenpalA::assert_xcmp_queue_success(None); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == system_para_asset_location, + owner: *owner == t.receiver.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_to_system_para_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()), + ); + AssetHubRococo::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount to reserve transfer is burned from Parachain's Sovereign account + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + owner: *owner == sov_penpal_on_ahr, + balance: *balance == t.args.amount, + }, + // Fee amount is burned from Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, .. }) => { + who: *who == sov_penpal_on_ahr, + }, + // Amount to reserve transfer is issued for beneficiary + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + owner: *owner == t.receiver.account_id, + amount: *amount == t.args.amount, + }, + // Remaining fee amount is minted for for beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + let relay_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcm_pallet_attempted_complete(None); + // XCM sent to relay reserve + PenpalA::assert_parachain_system_ump_sent(); + + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == relay_asset_location, + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_rococo = + Rococo::sovereign_account_id_of(Rococo::child_location_of(PenpalA::para_id())); + let sov_penpal_b_on_rococo = + Rococo::sovereign_account_id_of(Rococo::child_location_of(PenpalB::para_id())); + + assert_expected_events!( + Rococo, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_rococo, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_rococo, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let relay_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalB::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == relay_asset_location, + owner: *owner == t.receiver.account_id, + }, + ] + ); +} + +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { + ::XcmPallet::limited_reserve_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_relay_reserve_transfer_assets(t: ParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_para_through_relay_limited_reserve_transfer_assets( + t: ParaToParaThroughRelayTest, +) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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, + ) +} + +/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work +#[test] +fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { + // Init values for Relay Chain + let signed_origin = ::RuntimeOrigin::signed(RococoSender::get().into()); + let destination = Rococo::child_location_of(AssetHubRococo::para_id()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); + let amount_to_send: Balance = ROCOCO_ED * 1000; + let assets: Assets = (Here, amount_to_send).into(); + let fee_asset_item = 0; + + // this should fail + Rococo::execute_with(|| { + let result = ::XcmPallet::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 99, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); +} + +/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work +#[test] +fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { + // Init values for System Parachain + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + let destination = AssetHubRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let beneficiary: Location = + AccountId32Junction { network: None, id: beneficiary_id.into() }.into(); + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; + + let assets: Assets = (Parent, amount_to_send).into(); + let fee_asset_item = 0; + + // this should fail + AssetHubRococo::execute_with(|| { + let result = + ::PolkadotXcm::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); +} + +// ========================================================================= +// ========= Reserve Transfers - Native Asset - Relay<>Parachain =========== +// ========================================================================= +/// Reserve Transfers of native asset from Relay to Parachain should work +#[test] +fn reserve_transfer_native_asset_from_relay_to_para() { + // Init values for Relay + let destination = Rococo::child_location_of(PenpalA::para_id()); + let sender = RococoSender::get(); + let amount_to_send: Balance = ROCOCO_ED * 1000; + let assets: Assets = (Here, amount_to_send).into(); + + // Init values fot Parachain + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(relay_to_para_sender_assertions); + test.set_assertion::(relay_to_para_assets_receiver_assertions); + test.set_dispatchable::(relay_to_para_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = Rococo::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &RococoUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_balance_after = test.sender.balance; + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset 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!(receiver_assets_after < receiver_assets_before + amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to Relay should work +#[test] +fn reserve_transfer_native_asset_from_para_to_relay() { + // Init values for Parachain + let destination = PenpalA::parent_location(); + let sender = PenpalASender::get(); + let amount_to_send: Balance = ROCOCO_ED * 1000; + let assets: Assets = (Parent, amount_to_send).into(); + let asset_owner = PenpalAssetOwner::get(); + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // Init values for Relay + let receiver = RococoReceiver::get(); + let penpal_location_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id()); + let sov_penpal_on_relay = Rococo::sovereign_account_id_of(penpal_location_as_seen_by_relay); + + // fund Parachain's SA on Relay with the native tokens held in reserve + Rococo::fund_accounts(vec![(sov_penpal_on_relay.into(), amount_to_send * 2)]); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver, + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = ParaToRelayTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &sender) + }); + let receiver_balance_before = test.receiver.balance; + + // Set assertions and dispatchables + test.set_assertion::(para_to_relay_sender_assertions); + test.set_assertion::(para_to_relay_receiver_assertions); + test.set_dispatchable::(para_to_relay_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &sender) + }); + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's asset balance is increased + assert!(receiver_balance_after > receiver_balance_before); + // Receiver's asset 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!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + +// ========================================================================= +// ======= Reserve Transfers - Native Asset - AssetHub<>Parachain ========== +// ========================================================================= +/// Reserve Transfers of native asset from System Parachain to Parachain should work +#[test] +fn reserve_transfer_native_asset_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubRococoSender::get(); + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let assets: Assets = (Parent, amount_to_send).into(); + + // Init values for Parachain + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location.into(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = AssetHubRococo::execute_with(|| { + let reanchored_assets = assets + .reanchored(&destination, &AssetHubRococoUniversalLocation::get()) + .unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_balance_after = test.sender.balance; + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets 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!(receiver_assets_after < receiver_assets_before + amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +#[test] +fn reserve_transfer_native_asset_from_para_to_system_para() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sender = PenpalASender::get(); + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; + let assets: Assets = (Parent, amount_to_send).into(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let asset_owner = PenpalAssetOwner::get(); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + system_para_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // Init values for System Parachain + let receiver = AssetHubRococoReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on System Parachain with the native tokens held in reserve + AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount_to_send * 2)]); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let receiver_balance_before = test.receiver.balance; + + // Set assertions and dispatchables + 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_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's balance is increased + assert!(receiver_balance_after > 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!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + +// ========================================================================= +// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ====== +// ========================================================================= +/// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should +/// work +#[test] +fn reserve_transfer_assets_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(destination.clone()); + let sender = AssetHubRococoSender::get(); + let fee_amount_to_send = ASSET_HUB_ROCOCO_ED * 10000; + let asset_amount_to_send = PENPAL_ED * 10000; + let asset_owner = AssetHubRococoAssetOwner::get(); + let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); + let assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + ( + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())], + asset_amount_to_send, + ) + .into(), + ] + .into(); + let fee_asset_index = assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + AssetHubRococo::mint_asset( + asset_owner_signer, + RESERVABLE_ASSET_ID, + asset_owner, + asset_amount_to_send * 2, + ); + + // Create SA-of-Penpal-on-AHR with ED. + AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_ROCOCO_ED)]); + + // Init values for Parachain + let receiver = PenpalAReceiver::get(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_foreign_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + + // Init Test + let para_test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = SystemParaToParaTest::new(para_test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_assets_before = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &sender) + }); + let receiver_system_native_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + let receiver_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + + // Set assertions and dispatchables + 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_reserve_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_assets_after = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &sender) + }); + let receiver_system_native_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + let receiver_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + // Sender's balance is reduced + assert!(sender_balance_after < sender_balance_before); + // Receiver's foreign asset balance is increased + assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); + // Receiver's system asset 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!( + receiver_system_native_assets_after < + receiver_system_native_assets_before + fee_amount_to_send + ); + + // Sender's asset balance is reduced by exact amount + assert_eq!(sender_assets_before - asset_amount_to_send, sender_assets_after); + // Receiver's foreign asset balance is increased by exact amount + assert_eq!( + receiver_foreign_assets_after, + receiver_foreign_assets_before + asset_amount_to_send + ); +} + +/// Reserve Transfers of a foreign asset and native asset from Parachain to System Para should +/// work +#[test] +fn reserve_transfer_assets_from_para_to_system_para() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let sender = PenpalASender::get(); + let fee_amount_to_send = ASSET_HUB_ROCOCO_ED * 10000; + let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 10000; + let penpal_asset_owner = PenpalAssetOwner::get(); + let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); + let system_asset_location_on_penpal = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal_latest, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + // Fund Parachain's sender account with some foreign assets + PenpalA::mint_foreign_asset( + penpal_asset_owner_signer.clone(), + asset_location_on_penpal, + sender.clone(), + asset_amount_to_send * 2, + ); + // Fund Parachain's sender account with some system assets + PenpalA::mint_foreign_asset( + penpal_asset_owner_signer, + system_asset_location_on_penpal, + sender.clone(), + fee_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubRococoReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_foreign_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let ah_asset_owner = AssetHubRococoAssetOwner::get(); + let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); + + // Fund SA-of-Penpal-on-AHR to be able to pay for the fees. + AssetHubRococo::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + ASSET_HUB_ROCOCO_ED * 10000000, + )]); + // Fund SA-of-Penpal-on-AHR to be able to pay for the sent amount. + AssetHubRococo::mint_asset( + ah_asset_owner_signer, + RESERVABLE_ASSET_ID, + sov_penpal_on_ahr, + asset_amount_to_send * 2, + ); + + // Init Test + let para_test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = ParaToSystemParaTest::new(para_test_args); + + // Query initial balances + let sender_system_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let sender_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &sender) + }); + let receiver_balance_before = test.receiver.balance; + let receiver_assets_before = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_assets_sender_assertions); + test.set_assertion::(para_to_system_para_assets_receiver_assertions); + test.set_dispatchable::(para_to_system_para_reserve_transfer_assets); + test.assert(); + + // Query final balances + let sender_system_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let sender_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &sender) + }); + let receiver_balance_after = test.receiver.balance; + let receiver_assets_after = AssetHubRococo::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &receiver) + }); + // Sender's system asset balance is reduced + assert!(sender_system_assets_after < sender_system_assets_before); + // Receiver's balance is increased + assert!(receiver_balance_after > 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!(receiver_balance_after < receiver_balance_before + fee_amount_to_send); + + // Sender's asset balance is reduced by exact amount + assert_eq!(sender_foreign_assets_before - asset_amount_to_send, sender_foreign_assets_after); + // Receiver's foreign asset balance is increased by exact amount + assert_eq!(receiver_assets_after, receiver_assets_before + asset_amount_to_send); +} + +// ========================================================================= +// ===== Reserve Transfers - Native Asset - Parachain<>Relay<>Parachain ==== +// ========================================================================= +/// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should +/// work +#[test] +fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let amount_to_send: Balance = ROCOCO_ED * 10000; + let asset_owner = PenpalAssetOwner::get(); + let assets = (Parent, amount_to_send).into(); + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let sender_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id()); + let sov_of_sender_on_relay = Rococo::sovereign_account_id_of(sender_as_seen_by_relay); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + Rococo::fund_accounts(vec![(sov_of_sender_on_relay.into(), amount_to_send * 2)]); + + // Init values for Parachain Desitnation + let receiver = PenpalBReceiver::get(); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para(destination, receiver.clone(), amount_to_send, assets, None, 0), + }; + let mut test = ParaToParaThroughRelayTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_relay_hop_assertions); + test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's balance is increased + assert!(receiver_assets_after > receiver_assets_before); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs new file mode 100644 index 0000000000000000000000000000000000000000..364fbd0d439f62ed1fce356d1935331ec8e0d90b --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/send.rs @@ -0,0 +1,197 @@ +// 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::imports::*; + +/// Relay Chain should be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Superuser`. +#[test] +fn send_transact_as_superuser_from_relay_to_system_para_works() { + AssetHubRococo::force_create_asset_from_relay_as_root( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + AssetHubRococoSender::get().into(), + Some(Weight::from_parts(1_019_445_000, 200_000)), + ) +} + +/// We tests two things here: +/// - Parachain should be able to send XCM paying its fee with system asset in the System Parachain +/// - Parachain should be able to create a new Foreign Asset in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { + let para_sovereign_account = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let asset_location_on_penpal = v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + ); + let foreign_asset_at_asset_hub = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + + // Encoded `create_asset` call to be executed in AssetHub + let call = AssetHubRococo::create_foreign_asset_call( + foreign_asset_at_asset_hub, + ASSET_MIN_BALANCE, + para_sovereign_account.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = ASSET_HUB_ROCOCO_ED * 1000000; + let system_asset = (Parent, fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + origin_kind, + system_asset, + para_sovereign_account.clone(), + ); + + // SA-of-Penpal-on-AHR needs to have balance to pay for fees and asset creation deposit + AssetHubRococo::fund_accounts(vec![( + para_sovereign_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 10000000000, + )]); + + PenpalA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts( + 15_594_564_000, + 562_893, + ))); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == para_sovereign_account, + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == foreign_asset_at_asset_hub, + creator: *creator == para_sovereign_account.clone(), + owner: *owner == para_sovereign_account, + }, + ] + ); + + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub)); + }); +} + +/// We tests two things here: +/// - Parachain should be able to send XCM paying its fee with system assets in the System Parachain +/// - Parachain should be able to create a new Asset in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { + let para_sovereign_account = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + + // Force create and mint assets for Parachain's sovereign account + AssetHubRococo::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + para_sovereign_account.clone(), + Some(Weight::from_parts(1_019_445_000, 200_000)), + ASSET_MIN_BALANCE * 1000000000, + ); + + // Just a different `asset_id`` that does not exist yet + let new_asset_id = ASSET_ID + 1; + + // Encoded `create_asset` call to be executed in AssetHub + let call = AssetHubRococo::create_asset_call( + new_asset_id, + ASSET_MIN_BALANCE, + para_sovereign_account.clone(), + ); + + let origin_kind = OriginKind::SovereignAccount; + let fee_amount = ASSET_MIN_BALANCE * 1000000; + let asset = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()).into(); + let xcm = xcm_transact_paid_execution(call, origin_kind, asset, para_sovereign_account.clone()); + + // SA-of-Penpal-on-AHR needs to have balance to pay for asset creation deposit + AssetHubRococo::fund_accounts(vec![( + para_sovereign_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 10000000000, + )]); + + PenpalA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts( + 15_594_564_000, + 562_893, + ))); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Burned the fee + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == para_sovereign_account, + balance: *balance == fee_amount, + }, + // Asset created + RuntimeEvent::Assets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == new_asset_id, + creator: *creator == para_sovereign_account.clone(), + owner: *owner == para_sovereign_account, + }, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs similarity index 91% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs index 8faba50fc888b2155836d54c8e111146bc85c218..5662a78ab67f555c4a0a331fda79cbf984526cc1 100644 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/set_xcm_versions.rs @@ -13,20 +13,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::imports::*; #[test] fn relay_sets_system_para_xcm_supported_version() { // Init tests variables let sudo_origin = ::RuntimeOrigin::root(); - let system_para_destination: MultiLocation = - Rococo::child_location_of(AssetHubRococo::para_id()); + let system_para_destination: Location = Rococo::child_location_of(AssetHubRococo::para_id()); // Relay Chain sets supported version for Asset Parachain Rococo::execute_with(|| { assert_ok!(::XcmPallet::force_xcm_version( sudo_origin, - bx!(system_para_destination), + bx!(system_para_destination.clone()), XCM_V3 )); @@ -52,7 +51,7 @@ fn system_para_sets_relay_xcm_supported_version() { ::RuntimeCall::PolkadotXcm(pallet_xcm::Call::< ::Runtime, >::force_xcm_version { - location: bx!(parent_location), + location: bx!(parent_location.clone()), version: XCM_V3, }) .encode() diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs similarity index 50% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/swap.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index f9da0bf946ed5f1c6c838f617cdb7796638142e8..87f0b3d9f90a6f57d61a6352233db4858ba64ca3 100644 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -13,18 +13,18 @@ // See the License for the specific language governing permissions and // 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 crate::imports::*; #[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 { - parents: 0, - interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); + let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get()); + let asset_one = Box::new(v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + )); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -44,12 +44,6 @@ fn swap_locally_on_chain_using_local_assets() { 100_000_000_000_000, )); - assert_ok!(::Balances::force_set_balance( - ::RuntimeOrigin::root(), - AssetHubRococoSender::get().into(), - 100_000_000_000_000, - )); - assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), asset_native.clone(), @@ -81,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![asset_native.clone(), asset_one.clone()]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -108,7 +102,7 @@ fn swap_locally_on_chain_using_local_assets() { ::RuntimeOrigin::signed(AssetHubRococoSender::get()), asset_native, asset_one, - 1414213562273 - EXISTENTIAL_DEPOSIT * 2, // all but the 2 EDs can't be retrieved. + 1414213562273 - ASSET_HUB_ROCOCO_ED * 2, // all but the 2 EDs can't be retrieved. 0, 0, AssetHubRococoSender::get().into(), @@ -118,128 +112,39 @@ 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(PenpalRococoA::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(PenpalRococoA::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalRococoA::execute_with(|| { - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(PenpalRococoASender::get()), - ASSET_ID.into(), - PenpalRococoASender::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 = + Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let foreign_asset_at_asset_hub_rococo = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + + 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), + // An account to swap dot for something else. + (AssetHubRococoSender::get().into(), 5_000_000 * ASSET_HUB_ROCOCO_ED), + // Penpal's sovereign account in AH should have some balance + (sov_penpal_on_ahr.clone().into(), 100_000_000 * ASSET_HUB_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(); - PenpalRococoA::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - PenpalRococoA, - 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: + // 0: No need to create foreign asset as it exists in genesis. + // + // 1: Mint foreign asset on asset_hub_rococo: // // (While it might be nice to use batch, // currently that's disabled due to safe call filters.) type RuntimeEvent = ::RuntimeEvent; - // 3. Mint foreign asset (in reality this should be a teleport or some such) + // 1. 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(), - 3_000_000_000_000, + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()), + foreign_asset_at_asset_hub_rococo, + sov_penpal_on_ahr.clone().into(), + ASSET_HUB_ROCOCO_ED * 3_000_000_000_000, )); assert_expected_events!( @@ -249,11 +154,11 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); - // 4. Create pool: + // 2. Create pool: assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + Box::new(foreign_asset_at_asset_hub_rococo), )); assert_expected_events!( @@ -263,18 +168,16 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); - // 5. Add liquidity: + // 3. Add liquidity: assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + 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!( @@ -286,18 +189,15 @@ 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(), - ]); + // 4. Swap! + let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_rococo)]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), path, - 100000, - 1000, + 100000 * ASSET_HUB_ROCOCO_ED, + 1000 * ASSET_HUB_ROCOCO_ED, AssetHubRococoSender::get().into(), true ) @@ -307,32 +207,32 @@ fn swap_locally_on_chain_using_foreign_assets() { AssetHubRococo, vec![ RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => { - amount_in: *amount_in == 100000, - amount_out: *amount_out == 199399, + amount_in: *amount_in == 333333300000, + amount_out: *amount_out == 498874118173, }, ] ); - // 7. Remove liquidity + // 5. Remove liquidity assert_ok!(::AssetConversion::remove_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), - asset_native, - foreign_asset1_at_asset_hub_rococo, - 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_rococo), + 1414213562273 - ASSET_HUB_ROCOCO_ED * 2, // 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 mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get(); - asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); + let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get()); + let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocationV3::get(); + asset_one + .append_with(v3::Junction::GeneralIndex(ASSET_ID.into())) + .expect("pool assets"); AssetHubRococo::execute_with(|| { let pool_owner_account_id = asset_hub_rococo_runtime::AssetConversionOrigin::get(); @@ -355,10 +255,140 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), + 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")) + ); + }); +} + +#[test] +fn pay_xcm_fee_with_some_asset_swapped_for_native() { + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get(); + let asset_one = xcm::v3::Location { + parents: 0, + interior: [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + ] + .into(), + }; + let penpal = AssetHubRococo::sovereign_account_id_of(AssetHubRococo::sibling_location_of( + PenpalA::para_id(), + )); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with ASSET_ID <> NATIVE pair + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + ASSET_ID.into(), + AssetHubRococoSender::get().into(), + ASSET_MIN_BALANCE, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + ASSET_ID.into(), + AssetHubRococoSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(asset_native), + Box::new(asset_one), + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(asset_native), + Box::new(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubRococoSender::get().into() + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` + assert_eq!( + ::Balances::free_balance(penpal.clone()), + 0 + ); + + assert_ok!(::Assets::touch_other( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + )); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + 10_000_000_000_000, + )); + }); + + PenpalA::execute_with(|| { + // send xcm transact from `penpal` account which as only `ASSET_ID` tokens on + // `AssetHubRococo` + let call = AssetHubRococo::force_create_asset_call( + ASSET_ID + 1000, + penpal.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let penpal_root = ::RuntimeOrigin::root(); + let fee_amount = 4_000_000_000_000u128; + let asset_one = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + let asset_hub_location = PenpalA::sibling_location_of(AssetHubRococo::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + OriginKind::SovereignAccount, + asset_one, + penpal.clone(), + ); + + assert_ok!(::PolkadotXcm::send( + penpal_root, + bx!(asset_hub_location), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubRococo::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {}, + ] ); }); } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs new file mode 100644 index 0000000000000000000000000000000000000000..0cc5ddb9f64d31c93fb73a6bbacf1844b5e161f6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -0,0 +1,805 @@ +// 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::imports::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(631_531_000, 7_186))); + + assert_expected_events!( + Rococo, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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(AssetHubRococo::para_id()), + Some(Weight::from_parts(307_225_000, 7_186)), + ); + + assert_expected_events!( + Rococo, + vec![ + // Amount is withdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Rococo::assert_ump_queue_processed( + false, + Some(AssetHubRococo::para_id()), + Some(Weight::from_parts(157_718_000, 3_593)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 720_053_000, + 7_203, + ))); + + AssetHubRococo::assert_parachain_system_ump_sent(); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubRococo::assert_dmp_queue_complete(Some(Weight::from_parts(157_718_000, 3593))); + + assert_expected_events!( + AssetHubRococo, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + 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(); + + PenpalA::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.sender.account_id, + }, + 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(); + let expected_foreign_asset_id_v3: v3::Location = expected_foreign_asset_id.try_into().unwrap(); + + AssetHubRococo::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubRococo, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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_v3, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + ] + ); +} + +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(); + let expected_foreign_asset_id_v3: v3::Location = expected_foreign_asset_id.try_into().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.clone() + ), + 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_v3, + 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(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcmp_queue_success(None); + + 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::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + ] + ); +} + +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 relay_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::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, + ) +} + +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, + ) +} + +fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::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, + ) +} + +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() { + // Init values for Relay Chain + let amount_to_send: Balance = ROCOCO_ED * 1000; + let dest = Rococo::child_location_of(AssetHubRococo::para_id()); + let beneficiary_id = AssetHubRococoReceiver::get(); + let test_args = TestContext { + sender: RococoSender::get(), + receiver: AssetHubRococoReceiver::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(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; + let destination = AssetHubRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubRococoSender::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 = AssetHubRococo::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 = ASSET_HUB_ROCOCO_ED * 1000; + let destination = AssetHubRococo::parent_location().into(); + let beneficiary_id = RococoReceiver::get().into(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubRococoSender::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 = AssetHubRococo::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); +} + +/// Teleport of native asset from Relay Chain to the System Parachain should work +#[test] +fn 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(AssetHubRococo::para_id()); + let beneficiary_id = AssetHubRococoReceiver::get(); + let test_args = TestContext { + sender: RococoSender::get(), + receiver: AssetHubRococoReceiver::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_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); +} + +/// Teleport of native asset from System Parachains to the Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + teleport_native_assets_from_relay_to_system_para_works(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; + let destination = AssetHubRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubRococoSender::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_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = AssetHubRococo::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); +} + +/// Teleport of native asset from System Parachain to Relay Chain +/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000; + let destination = AssetHubRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubRococoSender::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_teleport_assets); + test.assert(); + + let delivery_fees = AssetHubRococo::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 does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} + +#[test] +fn teleport_to_other_system_parachains_works() { + let amount = ASSET_HUB_ROCOCO_ED * 100; + let native_asset: Assets = (Parent, amount).into(); + + test_parachain_is_trusted_teleporter!( + AssetHubRococo, // Origin + AssetHubRococoXcmConfig, // XCM Configuration + vec![BridgeHubRococo], // Destinations + (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() { + // Init values for Parachain + let fee_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(v3::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 1000; + let asset_owner = PenpalAssetOwner::get(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let sender = PenpalASender::get(); + let penpal_check_account = ::PolkadotXcm::check_account(); + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); + let penpal_assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal_latest, 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; + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + system_para_native_asset_location, + sender.clone(), + fee_amount_to_send, + ); + // No need to create the asset (only mint) as it exists in genesis. + PenpalA::mint_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + asset_id_on_penpal, + sender.clone(), + asset_amount_to_send, + ); + // fund Parachain's check account to be able to teleport + PenpalA::fund_accounts(vec![(penpal_check_account.clone().into(), ASSET_HUB_ROCOCO_ED * 1000)]); + + // prefund SA of Penpal on AssetHub with enough native tokens to pay for fees + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubRococo::fund_accounts(vec![( + sov_penpal_on_ah.clone().into(), + ASSET_HUB_ROCOCO_ED * 100_000_000_000, + )]); + + // Init values for System Parachain + let foreign_asset_at_asset_hub_rococo = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + let penpal_to_ah_beneficiary_id = AssetHubRococoReceiver::get(); + + // 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalASender::get(), + ) + }); + + 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalASender::get(), + ) + }); + + 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 foreign_asset_at_asset_hub_rococo_latest: Location = + foreign_asset_at_asset_hub_rococo.try_into().unwrap(); + let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let ah_assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_rococo_latest, 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalAReceiver::get(), + ) + }); + + 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalAReceiver::get(), + ) + }); + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..8ac8efb5218fbf4f9871c2861fdf923b273abedf --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "asset-hub-westend-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +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" + +# 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-treasury = { path = "../../../../../../../substrate/frame/treasury", default-features = false } +pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } + +# Polkadot +polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } +xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +testnet-parachains-constants = { path = "../../../../../runtimes/constants", features = ["westend"] } +penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } +asset-hub-westend-runtime = { path = "../../../../../runtimes/assets/asset-hub-westend" } +asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } +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 } +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 new file mode 100644 index 0000000000000000000000000000000000000000..3f899d1dbdbcb77174bfec068d0257b63e3c0a48 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -0,0 +1,95 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod imports { + pub use codec::Encode; + + // Substrate + pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{DispatchError, DispatchResult, ModuleError}, + traits::fungibles::Inspect, + }; + + // Polkadot + pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3, + }; + + // 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, Chain, Parachain as Para, RelayChain as Relay, Test, + TestArgs, TestContext, TestExt, + }, + xcm_helpers::{non_fee_asset, xcm_transact_paid_execution}, + ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3, + }; + pub use parachains_common::{AccountId, Balance}; + pub use westend_system_emulated_network::{ + asset_hub_westend_emulated_chain::{ + genesis::{AssetHubWestendAssetOwner, ED as ASSET_HUB_WESTEND_ED}, + AssetHubWestendParaPallet as AssetHubWestendPallet, + }, + collectives_westend_emulated_chain::CollectivesWestendParaPallet as CollectivesWestendPallet, + penpal_emulated_chain::{ + PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, + 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, + CollectivesWestendPara as CollectivesWestend, PenpalAPara as PenpalA, + PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, + PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver, WestendRelay as Westend, + WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, + }; + + // Runtimes + pub use asset_hub_westend_runtime::xcm_config::{ + UniversalLocation as AssetHubWestendUniversalLocation, WestendLocation as RelayLocation, + XcmConfig as AssetHubWestendXcmConfig, + }; + pub use penpal_runtime::xcm_config::{ + LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, + LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, + UniversalLocation as PenpalUniversalLocation, XcmConfig as PenpalWestendXcmConfig, + }; + pub use westend_runtime::xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }; + + pub const ASSET_ID: u32 = 3; + pub const ASSET_MIN_BALANCE: u128 = 1000; + + pub type RelayToSystemParaTest = Test; + pub type RelayToParaTest = Test; + pub type ParaToRelayTest = Test; + pub type SystemParaToRelayTest = Test; + pub type SystemParaToParaTest = Test; + pub type ParaToSystemParaTest = Test; + pub type ParaToParaThroughRelayTest = Test; +} + +#[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..2d02e90f47fb834326a7268394671f5ef03f84e7 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.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. + +use crate::imports::*; +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: Location = + Location::new(1, [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 = Location::new(1, [Parachain(AssetHubWestend::para_id().into())]); + let root = ::RuntimeOrigin::root(); + // asset kind to be spent from the treasury. + let asset_kind = VersionedLocatableAsset::V4 { + location: asset_hub_location, + asset_id: AssetId((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(Location::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/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs similarity index 96% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0c9de89c5f98f04a8a8f79048faf260c7bae27d5..3cd7c9c46d69edd738f067724485824ef51d3259 100644 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -13,6 +13,7 @@ // 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; 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 new file mode 100644 index 0000000000000000000000000000000000000000..8c836132b5469de948fb446999cb2e6f20f11135 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -0,0 +1,1144 @@ +// 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::imports::*; + +fn relay_to_para_sender_assertions(t: RelayToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Westend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); + + assert_expected_events!( + Westend, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == Westend::sovereign_account_id_of( + t.args.dest.clone() + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_to_relay_sender_assertions(t: ParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance, .. } + ) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 864_610_000, + 8_799, + ))); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + 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.clone() + ), + amount: *amount == t.args.amount, + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); + AssetHubWestend::assert_xcm_pallet_sent(); +} + +fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.receiver.account_id, + }, + ] + ); +} + +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![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance, .. } + ) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +fn para_to_relay_receiver_assertions(t: ParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_relay = + Westend::sovereign_account_id_of(Westend::child_location_of(PenpalA::para_id())); + + Westend::assert_ump_queue_processed( + true, + Some(PenpalA::para_id()), + Some(Weight::from_parts(306305000, 7_186)), + ); + + assert_expected_events!( + Westend, + vec![ + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_relay.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + + AssetHubWestend::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 864_610_000, + 8799, + ))); + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { asset_id, from, to, amount } + ) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + from: *from == t.sender.account_id, + to: *to == AssetHubWestend::sovereign_account_id_of( + t.args.dest.clone() + ), + amount: *amount == t.args.amount, + }, + // Native asset to pay for fees is transferred to Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubWestend::sovereign_account_id_of( + t.args.dest.clone() + ), + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); +} + +fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let reservable_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("coversion works"); + PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799))); + assert_expected_events!( + PenpalA, + vec![ + // Fees amount to reserve transfer is burned from Parachains's sender account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.sender.account_id, + }, + // Amount to reserve transfer is burned from Parachains's sender account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == reservable_asset_location, + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + // Transport fees are paid + RuntimeEvent::PolkadotXcm( + pallet_xcm::Event::FeesPaid { .. } + ) => {}, + ] + ); +} + +fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("coversion works"); + PenpalA::assert_xcmp_queue_success(None); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == system_para_asset_location, + owner: *owner == t.receiver.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_to_system_para_assets_receiver_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + AssetHubWestend::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount to reserve transfer is burned from Parachain's Sovereign account + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + owner: *owner == sov_penpal_on_ahr, + balance: *balance == t.args.amount, + }, + // Fee amount is burned from Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, .. }) => { + who: *who == sov_penpal_on_ahr, + }, + // Amount to reserve transfer is issued for beneficiary + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == RESERVABLE_ASSET_ID, + owner: *owner == t.receiver.account_id, + amount: *amount == t.args.amount, + }, + // Remaining fee amount is minted for for beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"), + owner: *owner == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + let relay_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcm_pallet_attempted_complete(None); + // XCM sent to relay reserve + PenpalA::assert_parachain_system_ump_sent(); + + assert_expected_events!( + PenpalA, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereign account + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance }, + ) => { + asset_id: *asset_id == relay_asset_location, + owner: *owner == t.sender.account_id, + balance: *balance == t.args.amount, + }, + ] + ); +} + +fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_a_on_westend = + Westend::sovereign_account_id_of(Westend::child_location_of(PenpalA::para_id())); + let sov_penpal_b_on_westend = + Westend::sovereign_account_id_of(Westend::child_location_of(PenpalB::para_id())); + + assert_expected_events!( + Westend, + vec![ + // Withdrawn from sender parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_a_on_westend, + amount: *amount == t.args.amount, + }, + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_b_on_westend, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + let relay_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalB::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == relay_asset_location, + owner: *owner == t.receiver.account_id, + }, + ] + ); +} + +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { + ::XcmPallet::limited_reserve_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_relay_reserve_transfer_assets(t: ParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_para_through_relay_limited_reserve_transfer_assets( + t: ParaToParaThroughRelayTest, +) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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, + ) +} + +/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work +#[test] +fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { + // Init values for Relay Chain + let signed_origin = ::RuntimeOrigin::signed(WestendSender::get().into()); + let destination = Westend::child_location_of(AssetHubWestend::para_id()); + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); + let amount_to_send: Balance = WESTEND_ED * 1000; + let assets: Assets = (Here, amount_to_send).into(); + let fee_asset_item = 0; + + // this should fail + Westend::execute_with(|| { + let result = ::XcmPallet::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 99, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); +} + +/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work +#[test] +fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { + // Init values for System Parachain + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + let destination = AssetHubWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let beneficiary: Location = + AccountId32Junction { network: None, id: beneficiary_id.into() }.into(); + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + + let assets: Assets = (Parent, amount_to_send).into(); + let fee_asset_item = 0; + + // this should fail + AssetHubWestend::execute_with(|| { + let result = + ::PolkadotXcm::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); +} + +// ========================================================================= +// ========= Reserve Transfers - Native Asset - Relay<>Parachain =========== +// ========================================================================= +/// Reserve Transfers of native asset from Relay to Parachain should work +#[test] +fn reserve_transfer_native_asset_from_relay_to_para() { + // Init values for Relay + let destination = Westend::child_location_of(PenpalA::para_id()); + let sender = WestendSender::get(); + let amount_to_send: Balance = WESTEND_ED * 1000; + let assets: Assets = (Here, amount_to_send).into(); + + // Init values fot Parachain + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(relay_to_para_sender_assertions); + test.set_assertion::(relay_to_para_assets_receiver_assertions); + test.set_dispatchable::(relay_to_para_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = Westend::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &WestendUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_balance_after = test.sender.balance; + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset 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!(receiver_assets_after < receiver_assets_before + amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to Relay should work +#[test] +fn reserve_transfer_native_asset_from_para_to_relay() { + // Init values for Parachain + let destination = PenpalA::parent_location(); + let sender = PenpalASender::get(); + let amount_to_send: Balance = WESTEND_ED * 1000; + let assets: Assets = (Parent, amount_to_send).into(); + let asset_owner = PenpalAssetOwner::get(); + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // Init values for Relay + let receiver = WestendReceiver::get(); + let penpal_location_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); + let sov_penpal_on_relay = Westend::sovereign_account_id_of(penpal_location_as_seen_by_relay); + + // fund Parachain's SA on Relay with the native tokens held in reserve + Westend::fund_accounts(vec![(sov_penpal_on_relay.into(), amount_to_send * 2)]); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver, + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = ParaToRelayTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &sender) + }); + let receiver_balance_before = test.receiver.balance; + + // Set assertions and dispatchables + test.set_assertion::(para_to_relay_sender_assertions); + test.set_assertion::(para_to_relay_receiver_assertions); + test.set_dispatchable::(para_to_relay_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.into(), &sender) + }); + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's asset balance is increased + assert!(receiver_balance_after > receiver_balance_before); + // Receiver's asset 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!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + +// ========================================================================= +// ======= Reserve Transfers - Native Asset - AssetHub<>Parachain ========== +// ========================================================================= +/// Reserve Transfers of native asset from System Parachain to Parachain should work +#[test] +fn reserve_transfer_native_asset_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sender = AssetHubWestendSender::get(); + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 2000; + let assets: Assets = (Parent, amount_to_send).into(); + + // Init values for Parachain + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = SystemParaToParaTest::new(test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location.into(), &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(system_para_to_para_sender_assertions); + test.set_assertion::(system_para_to_para_receiver_assertions); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = AssetHubWestend::execute_with(|| { + let reanchored_assets = assets + .reanchored(&destination, &AssetHubWestendUniversalLocation::get()) + .unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_balance_after = test.sender.balance; + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's assets is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's assets 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!(receiver_assets_after < receiver_assets_before + amount_to_send); +} + +/// Reserve Transfers of native asset from Parachain to System Parachain should work +#[test] +fn reserve_transfer_native_asset_from_para_to_system_para() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let sender = PenpalASender::get(); + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let assets: Assets = (Parent, amount_to_send).into(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let asset_owner = PenpalAssetOwner::get(); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + system_para_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // Init values for System Parachain + let receiver = AssetHubWestendReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = + AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + + // fund Parachain's SA on System Parachain with the native tokens held in reserve + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount_to_send * 2)]); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + assets.clone(), + None, + 0, + ), + }; + let mut test = ParaToSystemParaTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let receiver_balance_before = test.receiver.balance; + + // Set assertions and dispatchables + 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_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + let reanchored_assets = + assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap(); + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's balance is increased + assert!(receiver_balance_after > 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!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + +// ========================================================================= +// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ====== +// ========================================================================= +/// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should +/// work +#[test] +fn reserve_transfer_assets_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(destination.clone()); + let sender = AssetHubWestendSender::get(); + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_owner = AssetHubWestendAssetOwner::get(); + let asset_owner_signer = ::RuntimeOrigin::signed(asset_owner.clone()); + let assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + ( + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())], + asset_amount_to_send, + ) + .into(), + ] + .into(); + let fee_asset_index = assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + AssetHubWestend::mint_asset( + asset_owner_signer, + RESERVABLE_ASSET_ID, + asset_owner, + asset_amount_to_send * 2, + ); + + // Create SA-of-Penpal-on-AHR with ED. + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), ASSET_HUB_WESTEND_ED)]); + + // Init values for Parachain + let receiver = PenpalAReceiver::get(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_foreign_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + + // Init Test + let para_test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = SystemParaToParaTest::new(para_test_args); + + // Query initial balances + let sender_balance_before = test.sender.balance; + let sender_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &sender) + }); + let receiver_system_native_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + let receiver_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + + // Set assertions and dispatchables + 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_reserve_transfer_assets); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sender_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &sender) + }); + let receiver_system_native_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &receiver) + }); + let receiver_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &receiver) + }); + // Sender's balance is reduced + assert!(sender_balance_after < sender_balance_before); + // Receiver's foreign asset balance is increased + assert!(receiver_foreign_assets_after > receiver_foreign_assets_before); + // Receiver's system asset 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!( + receiver_system_native_assets_after < + receiver_system_native_assets_before + fee_amount_to_send + ); + + // Sender's asset balance is reduced by exact amount + assert_eq!(sender_assets_before - asset_amount_to_send, sender_assets_after); + // Receiver's foreign asset balance is increased by exact amount + assert_eq!( + receiver_foreign_assets_after, + receiver_foreign_assets_before + asset_amount_to_send + ); +} + +/// Reserve Transfers of a foreign asset and native asset from Parachain to System Para should +/// work +#[test] +fn reserve_transfer_assets_from_para_to_system_para() { + // Init values for Parachain + let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let sender = PenpalASender::get(); + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let penpal_asset_owner = PenpalAssetOwner::get(); + let penpal_asset_owner_signer = ::RuntimeOrigin::signed(penpal_asset_owner); + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); + let system_asset_location_on_penpal = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal_latest, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + // Fund Parachain's sender account with some foreign assets + PenpalA::mint_foreign_asset( + penpal_asset_owner_signer.clone(), + asset_location_on_penpal, + sender.clone(), + asset_amount_to_send * 2, + ); + // Fund Parachain's sender account with some system assets + PenpalA::mint_foreign_asset( + penpal_asset_owner_signer, + system_asset_location_on_penpal, + sender.clone(), + fee_amount_to_send * 2, + ); + + // Init values for System Parachain + let receiver = AssetHubWestendReceiver::get(); + let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = + AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let system_para_foreign_asset_location = + v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works"); + let ah_asset_owner = AssetHubWestendAssetOwner::get(); + let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); + + // Fund SA-of-Penpal-on-AHR to be able to pay for the fees. + AssetHubWestend::fund_accounts(vec![( + sov_penpal_on_ahr.clone().into(), + ASSET_HUB_WESTEND_ED * 1000, + )]); + // Fund SA-of-Penpal-on-AHR to be able to pay for the sent amount. + AssetHubWestend::mint_asset( + ah_asset_owner_signer, + RESERVABLE_ASSET_ID, + sov_penpal_on_ahr, + asset_amount_to_send * 2, + ); + + // Init Test + let para_test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination, + receiver.clone(), + asset_amount_to_send, + assets, + None, + fee_asset_index, + ), + }; + let mut test = ParaToSystemParaTest::new(para_test_args); + + // Query initial balances + let sender_system_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let sender_foreign_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &sender) + }); + let receiver_balance_before = test.receiver.balance; + let receiver_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_system_para_assets_sender_assertions); + test.set_assertion::(para_to_system_para_assets_receiver_assertions); + test.set_dispatchable::(para_to_system_para_reserve_transfer_assets); + test.assert(); + + // Query final balances + let sender_system_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_native_asset_location, &sender) + }); + let sender_foreign_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(system_para_foreign_asset_location, &sender) + }); + let receiver_balance_after = test.receiver.balance; + let receiver_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(RESERVABLE_ASSET_ID, &receiver) + }); + // Sender's system asset balance is reduced + assert!(sender_system_assets_after < sender_system_assets_before); + // Receiver's balance is increased + assert!(receiver_balance_after > 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!(receiver_balance_after < receiver_balance_before + fee_amount_to_send); + + // Sender's asset balance is reduced by exact amount + assert_eq!(sender_foreign_assets_before - asset_amount_to_send, sender_foreign_assets_after); + // Receiver's foreign asset balance is increased by exact amount + assert_eq!(receiver_assets_after, receiver_assets_before + asset_amount_to_send); +} + +// ========================================================================= +// ===== Reserve Transfers - Native Asset - Parachain<>Relay<>Parachain ==== +// ========================================================================= +/// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should +/// work +#[test] +fn reserve_transfer_native_asset_from_para_to_para_trough_relay() { + // Init values for Parachain Origin + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let amount_to_send: Balance = WESTEND_ED * 10000; + let asset_owner = PenpalAssetOwner::get(); + let assets = (Parent, amount_to_send).into(); + let relay_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); + let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location, + sender.clone(), + amount_to_send, + ); + + // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve + Westend::fund_accounts(vec![(sov_of_sender_on_relay.into(), amount_to_send * 2)]); + + // Init values for Parachain Desitnation + let receiver = PenpalBReceiver::get(); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para(destination, receiver.clone(), amount_to_send, assets, None, 0), + }; + let mut test = ParaToParaThroughRelayTest::new(test_args); + + // Query initial balances + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Set assertions and dispatchables + test.set_assertion::(para_to_para_through_relay_sender_assertions); + test.set_assertion::(para_to_para_relay_hop_assertions); + test.set_assertion::(para_to_para_through_relay_receiver_assertions); + test.set_dispatchable::(para_to_para_through_relay_limited_reserve_transfer_assets); + test.assert(); + + // Calculate delivery fees + let delivery_fees = PenpalA::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Query final balances + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced + assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after); + // Receiver's balance is increased + assert!(receiver_assets_after > receiver_assets_before); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs new file mode 100644 index 0000000000000000000000000000000000000000..eb0e985cc0ce6f84318f763cb37e707beaeca718 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/send.rs @@ -0,0 +1,197 @@ +// 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::imports::*; + +/// Relay Chain should be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Superuser`. +#[test] +fn send_transact_as_superuser_from_relay_to_system_para_works() { + AssetHubWestend::force_create_asset_from_relay_as_root( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + AssetHubWestendSender::get().into(), + Some(Weight::from_parts(1_019_445_000, 200_000)), + ) +} + +/// We tests two things here: +/// - Parachain should be able to send XCM paying its fee with system asset in the System Parachain +/// - Parachain should be able to create a new Foreign Asset in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { + let para_sovereign_account = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + let asset_location_on_penpal = v3::Location::new( + 0, + [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ], + ); + let foreign_asset_at_asset_hub = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + + // Encoded `create_asset` call to be executed in AssetHub + let call = AssetHubWestend::create_foreign_asset_call( + foreign_asset_at_asset_hub, + ASSET_MIN_BALANCE, + para_sovereign_account.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = ASSET_HUB_WESTEND_ED * 1000000; + let system_asset = (Parent, fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + origin_kind, + system_asset, + para_sovereign_account.clone(), + ); + + // SA-of-Penpal-on-AHR needs to have balance to pay for fees and asset creation deposit + AssetHubWestend::fund_accounts(vec![( + para_sovereign_account.clone().into(), + ASSET_HUB_WESTEND_ED * 10000000000, + )]); + + PenpalA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts( + 15_594_564_000, + 562_893, + ))); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == para_sovereign_account, + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == foreign_asset_at_asset_hub, + creator: *creator == para_sovereign_account.clone(), + owner: *owner == para_sovereign_account, + }, + ] + ); + + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub)); + }); +} + +/// We tests two things here: +/// - Parachain should be able to send XCM paying its fee with system assets in the System Parachain +/// - Parachain should be able to create a new Asset in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { + let para_sovereign_account = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + + // Force create and mint assets for Parachain's sovereign account + AssetHubWestend::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + para_sovereign_account.clone(), + Some(Weight::from_parts(1_019_445_000, 200_000)), + ASSET_MIN_BALANCE * 1000000000, + ); + + // Just a different `asset_id`` that does not exist yet + let new_asset_id = ASSET_ID + 1; + + // Encoded `create_asset` call to be executed in AssetHub + let call = AssetHubWestend::create_asset_call( + new_asset_id, + ASSET_MIN_BALANCE, + para_sovereign_account.clone(), + ); + + let origin_kind = OriginKind::SovereignAccount; + let fee_amount = ASSET_MIN_BALANCE * 1000000; + let asset = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()).into(); + let xcm = xcm_transact_paid_execution(call, origin_kind, asset, para_sovereign_account.clone()); + + // SA-of-Penpal-on-AHR needs to have balance to pay for asset creation deposit + AssetHubWestend::fund_accounts(vec![( + para_sovereign_account.clone().into(), + ASSET_HUB_WESTEND_ED * 10000000000, + )]); + + PenpalA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts( + 15_594_564_000, + 562_893, + ))); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Burned the fee + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == para_sovereign_account, + balance: *balance == fee_amount, + }, + // Asset created + RuntimeEvent::Assets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: *asset_id == new_asset_id, + creator: *creator == para_sovereign_account.clone(), + owner: *owner == para_sovereign_account, + }, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/set_xcm_versions.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_xcm_versions.rs similarity index 91% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/set_xcm_versions.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_xcm_versions.rs index 2133d5e5fb7c7537ae757c30a8c6ba3b96cc9c00..474e9a86ccc22777bc4f2ac574ef70ec39843a45 100644 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/set_xcm_versions.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_xcm_versions.rs @@ -13,20 +13,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::imports::*; #[test] fn relay_sets_system_para_xcm_supported_version() { // Init tests variables let sudo_origin = ::RuntimeOrigin::root(); - let system_para_destination: MultiLocation = - Westend::child_location_of(AssetHubWestend::para_id()); + let system_para_destination: Location = Westend::child_location_of(AssetHubWestend::para_id()); // Relay Chain sets supported version for Asset Parachain Westend::execute_with(|| { assert_ok!(::XcmPallet::force_xcm_version( sudo_origin, - bx!(system_para_destination), + bx!(system_para_destination.clone()), XCM_V3 )); @@ -52,7 +51,7 @@ fn system_para_sets_relay_xcm_supported_version() { ::RuntimeCall::PolkadotXcm(pallet_xcm::Call::< ::Runtime, >::force_xcm_version { - location: bx!(parent_location), + location: bx!(parent_location.clone()), version: XCM_V3, }) .encode() 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 new file mode 100644 index 0000000000000000000000000000000000000000..04740d3115834831356fd6dabf6a85ac5784270f --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -0,0 +1,393 @@ +// 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::imports::*; + +#[test] +fn swap_locally_on_chain_using_local_assets() { + let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get()); + let asset_one = Box::new(v3::Location { + parents: 0, + interior: [ + v3::Junction::PalletInstance(ASSETS_PALLET_ID), + v3::Junction::GeneralIndex(ASSET_ID.into()), + ] + .into(), + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + 1000, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_native.clone(), + asset_one.clone(), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_native.clone(), + asset_one.clone(), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + let path = vec![asset_native.clone(), asset_one.clone()]; + + assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + path, + 100, + 1, + AssetHubWestendSender::get().into(), + true + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => { + amount_in: *amount_in == 100, + amount_out: *amount_out == 199, + }, + ] + ); + + assert_ok!(::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_native.clone(), + asset_one.clone(), + 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. + 0, + 0, + AssetHubWestendSender::get().into(), + )); + }); +} + +#[test] +fn swap_locally_on_chain_using_foreign_assets() { + let asset_native = + Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works"); + let foreign_asset_at_asset_hub_westend = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubWestend::fund_accounts(vec![ + // An account to swap dot for something else. + (AssetHubWestendSender::get().into(), 5_000_000 * ASSET_HUB_WESTEND_ED), + // Penpal's sovereign account in AH should have some balance + (sov_penpal_on_ahr.clone().into(), 100_000_000 * ASSET_HUB_WESTEND_ED), + ]); + + AssetHubWestend::execute_with(|| { + // 0: No need to create foreign asset as it exists in genesis. + // + // 1: Mint foreign asset on asset_hub_westend: + // + // (While it might be nice to use batch, + // currently that's disabled due to safe call filters.) + + type RuntimeEvent = ::RuntimeEvent; + // 1. Mint foreign asset (in reality this should be a teleport or some such) + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()), + foreign_asset_at_asset_hub_westend, + sov_penpal_on_ahr.clone().into(), + ASSET_HUB_WESTEND_ED * 3_000_000_000_000, + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + + // 2. Create pool: + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_westend), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + // 3. Add liquidity: + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_westend), + 1_000_000_000_000_000, + 2_000_000_000_000_000, + 0, + 0, + sov_penpal_on_ahr.clone().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { + lp_token_minted: *lp_token_minted == 1414213562372995, + }, + ] + ); + + // 4. Swap! + let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_westend)]; + + assert_ok!( + ::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + path, + 100000 * ASSET_HUB_WESTEND_ED, + 1000 * ASSET_HUB_WESTEND_ED, + AssetHubWestendSender::get().into(), + true + ) + ); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => { + amount_in: *amount_in == 100000000000000, + amount_out: *amount_out == 181322178776029, + }, + ] + ); + + // 5. Remove liquidity + assert_ok!(::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), + asset_native.clone(), + Box::new(foreign_asset_at_asset_hub_westend), + 1414213562372995 - ASSET_HUB_WESTEND_ED * 2, // all but the 2 EDs can't be retrieved. + 0, + 0, + sov_penpal_on_ahr.clone().into(), + )); + }); +} + +#[test] +fn cannot_create_pool_from_pool_assets() { + let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get()); + let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocationV3::get(); + asset_one + .append_with(v3::Junction::GeneralIndex(ASSET_ID.into())) + .expect("pool assets"); + + AssetHubWestend::execute_with(|| { + let pool_owner_account_id = asset_hub_westend_runtime::AssetConversionOrigin::get(); + + assert_ok!(::PoolAssets::create( + ::RuntimeOrigin::signed(pool_owner_account_id.clone()), + ASSET_ID.into(), + pool_owner_account_id.clone().into(), + 1000, + )); + assert!(::PoolAssets::asset_exists(ASSET_ID)); + + assert_ok!(::PoolAssets::mint( + ::RuntimeOrigin::signed(pool_owner_account_id), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + 3_000_000_000_000, + )); + + assert_matches::assert_matches!( + ::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + asset_native, + Box::new(asset_one), + ), + Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) + ); + }); +} + +#[test] +fn pay_xcm_fee_with_some_asset_swapped_for_native() { + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocationV3::get(); + let asset_one = xcm::v3::Location { + parents: 0, + interior: [ + xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + ] + .into(), + }; + let penpal = AssetHubWestend::sovereign_account_id_of(AssetHubWestend::sibling_location_of( + PenpalA::para_id(), + )); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // set up pool with ASSET_ID <> NATIVE pair + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + ASSET_MIN_BALANCE, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + AssetHubWestendSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(asset_native), + Box::new(asset_one), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(asset_native), + Box::new(asset_one), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + // ensure `penpal` sovereign account has no native tokens and mint some `ASSET_ID` + assert_eq!( + ::Balances::free_balance(penpal.clone()), + 0 + ); + + assert_ok!(::Assets::touch_other( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + )); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + ASSET_ID.into(), + penpal.clone().into(), + 10_000_000_000_000, + )); + }); + + PenpalA::execute_with(|| { + // send xcm transact from `penpal` account which as only `ASSET_ID` tokens on + // `AssetHubWestend` + let call = AssetHubWestend::force_create_asset_call( + ASSET_ID + 1000, + penpal.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let penpal_root = ::RuntimeOrigin::root(); + let fee_amount = 4_000_000_000_000u128; + let asset_one = + ([PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())], fee_amount).into(); + let asset_hub_location = PenpalA::sibling_location_of(AssetHubWestend::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + OriginKind::SovereignAccount, + asset_one, + penpal.clone(), + ); + + assert_ok!(::PolkadotXcm::send( + penpal_root, + bx!(asset_hub_location), + bx!(xcm), + )); + + PenpalA::assert_xcm_pallet_sent(); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. },) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true,.. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs new file mode 100644 index 0000000000000000000000000000000000000000..61f547fe7c5ec9b4368b9af5b17dbdf009177cda --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -0,0 +1,808 @@ +// 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::imports::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Westend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(631_531_000, 7_186))); + + assert_expected_events!( + Westend, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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(AssetHubWestend::para_id()), + Some(Weight::from_parts(307_225_000, 7_186)), + ); + + assert_expected_events!( + Westend, + vec![ + // Amount is withdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Westend::assert_ump_queue_processed( + false, + Some(AssetHubWestend::para_id()), + Some(Weight::from_parts(157_718_000, 3_593)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 720_053_000, + 7_203, + ))); + + AssetHubWestend::assert_parachain_system_ump_sent(); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubWestend::assert_dmp_queue_complete(Some(Weight::from_parts(157_718_000, 3593))); + + assert_expected_events!( + AssetHubWestend, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + 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(); + + PenpalA::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, .. } + ) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.sender.account_id, + }, + 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(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(); + let expected_foreign_asset_id_v3: v3::Location = expected_foreign_asset_id.try_into().unwrap(); + + AssetHubWestend::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubWestend, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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_v3, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + ] + ); +} + +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(); + let expected_foreign_asset_id_v3: v3::Location = expected_foreign_asset_id.try_into().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.clone() + ), + 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_v3, + 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(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + + PenpalA::assert_xcmp_queue_success(None); + + 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::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == system_para_native_asset_location, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + ] + ); +} + +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 relay_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::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, + ) +} + +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, + ) +} + +fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::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, + ) +} + +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() { + // Init values for Relay Chain + let amount_to_send: Balance = WESTEND_ED * 1000; + let dest = Westend::child_location_of(AssetHubWestend::para_id()); + let beneficiary_id = AssetHubWestendReceiver::get(); + let test_args = TestContext { + sender: WestendSender::get(), + receiver: AssetHubWestendReceiver::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(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let destination = AssetHubWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubWestendSender::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 = AssetHubWestend::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 = ASSET_HUB_WESTEND_ED * 1000; + let destination = AssetHubWestend::parent_location().into(); + let beneficiary_id = WestendReceiver::get().into(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubWestendSender::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 = AssetHubWestend::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); +} + +/// Teleport of native asset from Relay Chain to the System Parachain should work +#[test] +fn 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(AssetHubWestend::para_id()); + let beneficiary_id = AssetHubWestendReceiver::get(); + let test_args = TestContext { + sender: WestendSender::get(), + receiver: AssetHubWestendReceiver::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_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); +} + +/// Teleport of native asset from System Parachains to the Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + teleport_native_assets_from_relay_to_system_para_works(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let destination = AssetHubWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubWestendSender::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_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = AssetHubWestend::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); +} + +/// Teleport of native asset from System Parachain to Relay Chain +/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000; + let destination = AssetHubWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubWestendSender::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_teleport_assets); + test.assert(); + + let delivery_fees = AssetHubWestend::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 does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} + +#[test] +fn teleport_to_other_system_parachains_works() { + let amount = ASSET_HUB_WESTEND_ED * 100; + let native_asset: Assets = (Parent, amount).into(); + + test_parachain_is_trusted_teleporter!( + AssetHubWestend, // Origin + AssetHubWestendXcmConfig, // XCM Configuration + vec![BridgeHubWestend], // Destinations + (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() { + // Init values for Parachain + let fee_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 100; + let asset_location_on_penpal = + v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works"); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(v3::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100; + let asset_owner = PenpalAssetOwner::get(); + let system_para_native_asset_location = + v3::Location::try_from(RelayLocation::get()).expect("conversion works"); + let sender = PenpalASender::get(); + let penpal_check_account = ::PolkadotXcm::check_account(); + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap(); + let penpal_assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal_latest, 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; + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + system_para_native_asset_location, + sender.clone(), + fee_amount_to_send, + ); + // No need to create the asset (only mint) as it exists in genesis. + PenpalA::mint_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + asset_id_on_penpal, + sender.clone(), + asset_amount_to_send, + ); + // fund Parachain's check account to be able to teleport + PenpalA::fund_accounts(vec![( + penpal_check_account.clone().into(), + ASSET_HUB_WESTEND_ED * 1000, + )]); + + // prefund SA of Penpal on AssetHub with enough native tokens to pay for fees + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubWestend::fund_accounts(vec![( + sov_penpal_on_ah.clone().into(), + ASSET_HUB_WESTEND_ED * 100_000_000_000, + )]); + + // Init values for System Parachain + let foreign_asset_at_asset_hub_westend = + v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + .appended_with(asset_location_on_penpal) + .unwrap(); + let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get(); + + // Penpal to AH test args + let penpal_to_ah_test_args = TestContext { + sender: PenpalASender::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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalASender::get(), + ) + }); + + 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 = 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalASender::get(), + ) + }); + + 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 = 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 foreign_asset_at_asset_hub_westend_latest: Location = + foreign_asset_at_asset_hub_westend.try_into().unwrap(); + let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let ah_assets: Assets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_westend_latest, 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: 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalAReceiver::get(), + ) + }); + + 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 = 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 = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + system_para_native_asset_location, + &PenpalAReceiver::get(), + ) + }); + + 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 = 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/assets/asset-hub-westend/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs similarity index 87% rename from cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/treasury.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs index cf06f58682da2d8dfb626305a9e8f0aff7b48804..6d8c0f5e5de6ae70559f5d60545959db5e74f735 100644 --- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::imports::*; +use emulated_integration_tests_common::accounts::{ALICE, BOB}; use frame_support::traits::fungibles::{Create, Inspect, Mutate}; -use integration_tests_common::constants::accounts::{ALICE, BOB}; use polkadot_runtime_common::impls::VersionedLocatableAsset; use xcm_executor::traits::ConvertLocation; @@ -24,19 +24,19 @@ 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, PalletInstance(37)); + let treasury_location: Location = Location::new(1, PalletInstance(37)); // 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(0, Parachain(AssetHubWestend::para_id().into())); + let asset_hub_location = Location::new(0, Parachain(AssetHubWestend::para_id().into())); let root = ::RuntimeOrigin::root(); // asset kind to be spend from the treasury. - let asset_kind = VersionedLocatableAsset::V3 { + let asset_kind = VersionedLocatableAsset::V4 { location: asset_hub_location, - asset_id: AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()), + asset_id: AssetId([PalletInstance(50), GeneralIndex(ASSET_ID.into())].into()), }; // treasury spend beneficiary. let alice: AccountId = Westend::account_id_of(ALICE); @@ -71,7 +71,7 @@ fn create_and_claim_treasury_spend() { root, Box::new(asset_kind), SPEND_AMOUNT, - Box::new(MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())).into()), + Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()), None, )); // claim the spend. @@ -103,7 +103,7 @@ fn create_and_claim_treasury_spend() { amount: amount == &SPEND_AMOUNT, }, RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, - RuntimeEvent::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Complete(..) ,.. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, ] ); // beneficiary received the assets from the treasury. 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 new file mode 100644 index 0000000000000000000000000000000000000000..89f0d2a9ca6dacae72e73d5b6e8c310347389070 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "bridge-hub-rococo-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +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-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-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", 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 } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } + +# Bridges +pallet-bridge-messages = { path = "../../../../../../../bridges/modules/messages", default-features = false } +bp-messages = { path = "../../../../../../../bridges/primitives/messages", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +testnet-parachains-constants = { path = "../../../../../runtimes/constants", features = ["rococo"] } +cumulus-pallet-xcmp-queue = { path = "../../../../../../pallets/xcmp-queue", default-features = false } +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" } +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/primitives/core", default-features = false } +snowbridge-router-primitives = { path = "../../../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-pallet-system = { path = "../../../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-inbound-queue-fixtures = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue/fixtures" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..b5e19cf3fa3a252abe28b31229c876d104b6a8e9 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -0,0 +1,65 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod imports { + // Substrate + 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::{self, NetworkId::Westend as WestendId}, + }; + + // Cumulus + pub use emulated_integration_tests_common::{ + accounts::ALICE, + impls::Inspect, + test_parachain_is_trusted_teleporter, + xcm_emulator::{ + assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, + }, + }; + pub use parachains_common::AccountId; + pub use rococo_westend_system_emulated_network::{ + asset_hub_rococo_emulated_chain::{ + genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, + }, + asset_hub_westend_emulated_chain::{ + genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, + }, + bridge_hub_rococo_emulated_chain::{ + genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet, + }, + penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, + 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, + AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, + BridgeHubRococoParaSender as BridgeHubRococoSender, + BridgeHubWestendPara as BridgeHubWestend, PenpalAPara as PenpalA, + PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, + RococoRelay as Rococo, + }; + + pub const ASSET_MIN_BALANCE: u128 = 1000; +} + +#[cfg(test)] +mod tests; 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 new file mode 100644 index 0000000000000000000000000000000000000000..787a82ed32f7376f1c94c584711098c15d4da198 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -0,0 +1,219 @@ +// 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::tests::*; + +fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u128) { + let destination = asset_hub_westend_location(); + + // fund the AHR's SA on BHR for paying bridge transport fees + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubRococo::force_xcm_version(destination.clone(), 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] +fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() { + let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into(); + let roc_at_asset_hub_westend = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + AssetHubWestend::force_create_foreign_asset( + roc_at_asset_hub_westend, + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Westend, + AssetHubWestend::para_id(), + ); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `roc_at_asset_hub_westend` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + roc_at_asset_hub_westend.into(), + AssetHubWestendSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(xcm::v3::Parent.into()), + Box::new(roc_at_asset_hub_westend), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubWestendSender::get()), + Box::new(xcm::v3::Parent.into()), + Box::new(roc_at_asset_hub_westend), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + AssetHubWestendSender::get().into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + let rocs_in_reserve_on_ahr_before = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + let sender_rocs_before = + ::account_data_of(AssetHubRococoSender::get()).free; + let receiver_rocs_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) + }); + + let roc_at_asset_hub_rococo_latest: Location = roc_at_asset_hub_rococo.try_into().unwrap(); + let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; + send_asset_from_asset_hub_rococo_to_asset_hub_westend(roc_at_asset_hub_rococo_latest, amount); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // issue ROCs on AHW + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == roc_at_asset_hub_rococo, + owner: *owner == AssetHubWestendReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_rocs_after = + ::account_data_of(AssetHubRococoSender::get()).free; + let receiver_rocs_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get()) + }); + let rocs_in_reserve_on_ahr_after = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + + // Sender's balance is reduced + assert!(sender_rocs_before > sender_rocs_after); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + // Reserve balance is reduced by sent amount + assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount); +} + +#[test] +fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { + let prefund_amount = 10_000_000_000_000u128; + let wnd_at_asset_hub_rococo = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + AssetHubRococo::force_create_foreign_asset( + wnd_at_asset_hub_rococo, + owner, + true, + ASSET_MIN_BALANCE, + vec![(AssetHubRococoSender::get(), prefund_amount)], + ); + + // fund the AHR's SA on AHW with the WND tokens held in reserve + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Rococo, + AssetHubRococo::para_id(), + ); + AssetHubWestend::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]); + + let wnds_in_reserve_on_ahw_before = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + assert_eq!(wnds_in_reserve_on_ahw_before, prefund_amount); + let sender_wnds_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoSender::get()) + }); + assert_eq!(sender_wnds_before, prefund_amount); + let receiver_wnds_before = + ::account_data_of(AssetHubWestendReceiver::get()).free; + + let wnd_at_asset_hub_rococo_latest: Location = wnd_at_asset_hub_rococo.try_into().unwrap(); + let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000; + send_asset_from_asset_hub_rococo_to_asset_hub_westend( + wnd_at_asset_hub_rococo_latest.clone(), + amount_to_send, + ); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + // WND is withdrawn from AHR's SA on AHW + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_ahr_on_ahw, + amount: *amount == amount_to_send, + }, + // WNDs deposited to beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubWestendReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_wnds_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoSender::get()) + }); + let receiver_wnds_after = + ::account_data_of(AssetHubWestendReceiver::get()).free; + let wnds_in_reserve_on_ahw_after = + ::account_data_of(sov_ahr_on_ahw).free; + + // Sender's balance is reduced + assert!(sender_wnds_before > sender_wnds_after); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + // Reserve balance is reduced by sent amount + assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send); +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..88dad06434b0d4a28295708c303907e02e70927b --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -0,0 +1,109 @@ +// 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::imports::*; + +mod asset_transfers; +mod send_xcm; +mod snowbridge; +mod teleport; + +pub(crate) fn asset_hub_westend_location() -> Location { + Location::new( + 2, + [GlobalConsensus(NetworkId::Westend), Parachain(AssetHubWestend::para_id().into())], + ) +} + +pub(crate) fn bridge_hub_westend_location() -> Location { + Location::new( + 2, + [GlobalConsensus(NetworkId::Westend), Parachain(BridgeHubWestend::para_id().into())], + ) +} + +pub(crate) fn send_asset_from_asset_hub_rococo( + destination: Location, + (id, amount): (Location, u128), +) -> DispatchResult { + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); + + let assets: Assets = (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::Burned { .. }) => {}, + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..a1d871cdb618fdddfbbbc3e7812d0ec7f7ae7866 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -0,0 +1,183 @@ +// 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::tests::*; + +#[test] +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(); + let destination = Rococo::child_location_of(BridgeHubRococo::para_id()).into(); + let weight_limit = WeightLimit::Unlimited; + let check_origin = None; + + let remote_xcm = Xcm(vec![ClearOrigin]); + + let xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit, check_origin }, + ExportMessage { + network: WestendId.into(), + destination: [Parachain(AssetHubWestend::para_id().into())].into(), + xcm: remote_xcm, + }, + ])); + + // 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!(xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + // 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); +} + +#[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)); + + // prepare data + let destination = asset_hub_westend_location(); + let native_token = Location::parent(); + let amount = ASSET_HUB_ROCOCO_ED * 1_000; + + // 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.clone(), (native_token.clone(), amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // set destination version + AssetHubRococo::force_xcm_version(destination.clone(), 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.clone(), (native_token.clone(), amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // 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.clone(), (native_token.clone(), 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.clone(), + (native_token.clone(), 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.clone(), + (native_token.clone(), 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![ + // 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..1e74d63e1d5235cc0e7bfd0d07f75655fdb3e5a5 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -0,0 +1,548 @@ +// 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::imports::*; +use bridge_hub_rococo_runtime::{EthereumBeaconClient, EthereumInboundQueue, RuntimeOrigin}; +use codec::{Decode, Encode}; +use emulated_integration_tests_common::xcm_emulator::ConvertLocation; +use frame_support::pallet_prelude::TypeInfo; +use hex_literal::hex; +use rococo_system_emulated_network::penpal_emulated_chain::CustomizableAssetFromSystemAssetHub; +use rococo_westend_system_emulated_network::BridgeHubRococoParaSender as BridgeHubRococoSender; +use snowbridge_core::outbound::OperatingMode; +use snowbridge_pallet_inbound_queue_fixtures::{ + register_token::make_register_token_message, + register_token_with_insufficient_fee::make_register_token_with_infufficient_fee_message, + send_token::make_send_token_message, send_token_to_penpal::make_send_token_to_penpal_message, + InboundQueueFixture, +}; +use snowbridge_pallet_system; +use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; +use sp_core::H256; +use sp_runtime::{ArithmeticError::Underflow, DispatchError::Arithmetic}; +use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; + +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"); + +#[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), +} + +pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult { + EthereumBeaconClient::store_execution_header( + fixture.message.proof.block_hash, + fixture.execution_header, + 0, + H256::default(), + ); + + EthereumInboundQueue::submit( + RuntimeOrigin::signed(BridgeHubRococoSender::get()), + fixture.message, + ) +} + +/// Create an agent on Ethereum. An agent is a representation of an entity in the Polkadot +/// ecosystem (like a parachain) on Ethereum. +#[test] +#[ignore] +fn create_agent() { + let origin_para: u32 = 1001; + // Fund the origin parachain sovereign account so that it can pay execution fees. + 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 {}); + // Construct XCM to create an agent for para 1001 + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + 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; + // Check that the Transact message was sent + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + +/// Create a channel for a consensus system. A channel is a bidirectional messaging channel +/// between BridgeHub and Ethereum. +#[test] +#[ignore] +fn create_channel() { + let origin_para: u32 = 1001; + // Fund AssetHub sovereign account so that it can pay execution fees. + BridgeHubRococo::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination: VersionedLocation = + Rococo::child_location_of(BridgeHubRococo::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let create_agent_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + 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 }); + // Construct XCM to create a channel for para 1001 + let create_channel_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + 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; + + // Check that the Channel was created + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateChannel { + .. + }) => {}, + ] + ); + }); +} + +/// Tests the registering of a token as an asset on AssetHub. +#[test] +fn register_weth_token_from_ethereum_to_asset_hub() { + // Fund AssetHub sovereign account so that it can pay execution fees. + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Construct RegisterToken message and sent to inbound queue + let register_token_message = make_register_token_message(); + send_inbound_message(register_token_message.clone()).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 { .. }) => {}, + ] + ); + }); +} + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubRococo::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubRococo::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Fund PenPal sender and receiver + PenpalA::fund_accounts(vec![ + (PenpalAReceiver::get(), INITIAL_FUND), + (PenpalASender::get(), INITIAL_FUND), + ]); + + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + CustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + // Converts the Weth asset location into an asset ID + let weth_asset_id: v3::Location = weth_asset_location.try_into().unwrap(); + + let origin_location = (Parent, Parent, EthereumNetwork::get()).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + GlobalConsensusEthereumConvertsFor::::convert_location(&origin_location) + .unwrap(); + AssetHubRococo::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + 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)); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Construct RegisterToken message and sent to inbound queue + send_inbound_message(make_register_token_message()).unwrap(); + + // Construct SendToken message and sent to inbound queue + send_inbound_message(make_send_token_to_penpal_message()).unwrap(); + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + 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; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending +/// a token from Ethereum to AssetHub. +#[test] +fn send_token_from_ethereum_to_asset_hub() { + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign on AssetHub + AssetHubRococo::fund_accounts(vec![(AssetHubRococoReceiver::get(), INITIAL_FUND)]); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Construct RegisterToken message and sent to inbound queue + send_inbound_message(make_register_token_message()).unwrap(); + + // Construct SendToken message and sent to inbound queue + send_inbound_message(make_send_token_message()).unwrap(); + + // Check that the message was sent + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the full cycle of token transfers: +/// - registering a token on AssetHub +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[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(Location::new( + 1, + [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( + Location::new(2, [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; + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Construct RegisterToken message and sent to inbound queue + send_inbound_message(make_register_token_message()).unwrap(); + + // Check that the register token message was sent using xcm + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + + // Construct SendToken message and sent to inbound queue + send_inbound_message(make_send_token_message()).unwrap(); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + let assets = vec![Asset { + id: AssetId(Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { network: None, key: WETH }, + ], + )), + fun: Fungible(WETH_AMOUNT), + }]; + let multi_assets = VersionedAssets::V4(Assets::from(assets)); + + let destination = VersionedLocation::V4(Location::new( + 2, + [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })], + )); + + let beneficiary = VersionedLocation::V4(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = ::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // Send the Weth back to Ethereum + ::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; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + let events = BridgeHubRococo::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == assethub_sovereign && *amount == 2680000000000, + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Construct RegisterToken message and sent to inbound queue + let message = make_register_token_with_infufficient_fee_message(); + send_inbound_message(message).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::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_asset_hub_fail_for_insufficient_fund() { + // Insufficient fund + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), 1_000); + + BridgeHubRococo::execute_with(|| { + assert_err!(send_inbound_message(make_register_token_message()), Arithmetic(Underflow)); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs similarity index 93% rename from cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/teleport.rs rename to cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs index f00288a4d8c76ccffdf3a8ef00638b73618476ee..8f51f5b180004d3f694cc68ba6d3c11ab46df95a 100644 --- a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs @@ -13,13 +13,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::tests::*; use bridge_hub_rococo_runtime::xcm_config::XcmConfig; #[test] fn teleport_to_other_system_parachains_works() { let amount = BRIDGE_HUB_ROCOCO_ED * 100; - let native_asset: MultiAssets = (Parent, amount).into(); + let native_asset: Assets = (Parent, amount).into(); test_parachain_is_trusted_teleporter!( BridgeHubRococo, // Origin 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 new file mode 100644 index 0000000000000000000000000000000000000000..9d55903c858308b9382fda0fc03132d95b9f1028 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bridge-hub-westend-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Bridge Hub Westend runtime integration tests with xcm-emulator" +publish = false + +[lints] +workspace = true + +[dependencies] + +# Substrate +frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } +pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false } +pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } +pallet-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 } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } + +# Bridges +pallet-bridge-messages = { path = "../../../../../../../bridges/modules/messages", default-features = false } +bp-messages = { path = "../../../../../../../bridges/primitives/messages", default-features = false } + +# Cumulus +parachains-common = { path = "../../../../../../parachains/common" } +cumulus-pallet-xcmp-queue = { path = "../../../../../../pallets/xcmp-queue", default-features = false } +bridge-hub-westend-runtime = { path = "../../../../../../parachains/runtimes/bridge-hubs/bridge-hub-westend", default-features = false } +emulated-integration-tests-common = { path = "../../../common", default-features = false } +rococo-westend-system-emulated-network = { path = "../../../networks/rococo-westend-system" } 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 new file mode 100644 index 0000000000000000000000000000000000000000..60c31ce5a4aefeb5852f87ddd6804ca267857801 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-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. + +#[cfg(test)] +mod imports { + // Substrate + 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, + v4::NetworkId::Rococo as RococoId, + }; + + // Cumulus + pub use emulated_integration_tests_common::{ + accounts::ALICE, + impls::Inspect, + test_parachain_is_trusted_teleporter, + xcm_emulator::{ + assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, + }, + }; + pub use parachains_common::AccountId; + pub use rococo_westend_system_emulated_network::{ + asset_hub_rococo_emulated_chain::{ + genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, + }, + asset_hub_westend_emulated_chain::{ + genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, + }, + bridge_hub_westend_emulated_chain::{ + genesis::ED as BRIDGE_HUB_WESTEND_ED, + BridgeHubWestendParaPallet as BridgeHubWestendPallet, + }, + westend_emulated_chain::WestendRelayPallet as WestendPallet, + AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, + AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, + AssetHubWestendParaReceiver as AssetHubWestendReceiver, + AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, + BridgeHubWestendPara as BridgeHubWestend, + BridgeHubWestendParaSender as BridgeHubWestendSender, WestendRelay as Westend, + }; + + pub const ASSET_MIN_BALANCE: u128 = 1000; +} + +#[cfg(test)] +mod tests; 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 new file mode 100644 index 0000000000000000000000000000000000000000..5b0990973d2103f7fa606c4abcccd41a893067d2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -0,0 +1,217 @@ +// 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::tests::*; + +fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u128) { + let destination = asset_hub_rococo_location(); + + // fund the AHW's SA on BHW for paying bridge transport fees + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubWestend::force_xcm_version(destination.clone(), 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] +fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { + let wnd_at_asset_hub_westend: Location = Parent.into(); + let wnd_at_asset_hub_rococo = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]); + let owner: AccountId = AssetHubRococo::account_id_of(ALICE); + AssetHubRococo::force_create_foreign_asset( + wnd_at_asset_hub_rococo, + owner, + true, + ASSET_MIN_BALANCE, + vec![], + ); + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Rococo, + AssetHubRococo::para_id(), + ); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // setup a pool to pay xcm fees with `wnd_at_asset_hub_rococo` tokens + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + wnd_at_asset_hub_rococo.into(), + AssetHubRococoSender::get().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(xcm::v3::Parent.into()), + Box::new(wnd_at_asset_hub_rococo), + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubRococoSender::get()), + Box::new(xcm::v3::Parent.into()), + Box::new(wnd_at_asset_hub_rococo), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + AssetHubRococoSender::get().into() + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); + + let wnds_in_reserve_on_ahw_before = + ::account_data_of(sov_ahr_on_ahw.clone()).free; + let sender_wnds_before = + ::account_data_of(AssetHubWestendSender::get()).free; + let receiver_wnds_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) + }); + + let amount = ASSET_HUB_WESTEND_ED * 1_000; + send_asset_from_asset_hub_westend_to_asset_hub_rococo(wnd_at_asset_hub_westend, amount); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // issue WNDs on AHR + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == wnd_at_asset_hub_rococo, + owner: *owner == AssetHubRococoReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_wnds_after = + ::account_data_of(AssetHubWestendSender::get()).free; + let receiver_wnds_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get()) + }); + let wnds_in_reserve_on_ahw_after = + ::account_data_of(sov_ahr_on_ahw).free; + + // Sender's balance is reduced + assert!(sender_wnds_before > sender_wnds_after); + // Receiver's balance is increased + assert!(receiver_wnds_after > receiver_wnds_before); + // Reserve balance is increased by sent amount + assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before + amount); +} + +#[test] +fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { + let prefund_amount = 10_000_000_000_000u128; + let roc_at_asset_hub_westend = + v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]); + let owner: AccountId = AssetHubWestend::account_id_of(ALICE); + AssetHubWestend::force_create_foreign_asset( + roc_at_asset_hub_westend, + owner, + true, + ASSET_MIN_BALANCE, + vec![(AssetHubWestendSender::get(), prefund_amount)], + ); + + // fund the AHW's SA on AHR with the ROC tokens held in reserve + let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + NetworkId::Westend, + AssetHubWestend::para_id(), + ); + AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), prefund_amount)]); + + let rocs_in_reserve_on_ahr_before = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + assert_eq!(rocs_in_reserve_on_ahr_before, prefund_amount); + let sender_rocs_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendSender::get()) + }); + assert_eq!(sender_rocs_before, prefund_amount); + let receiver_rocs_before = + ::account_data_of(AssetHubRococoReceiver::get()).free; + + let roc_at_asset_hub_westend_latest: Location = roc_at_asset_hub_westend.try_into().unwrap(); + let amount_to_send = ASSET_HUB_ROCOCO_ED * 1_000; + send_asset_from_asset_hub_westend_to_asset_hub_rococo( + roc_at_asset_hub_westend_latest.clone(), + amount_to_send, + ); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubRococo, + vec![ + // ROC is withdrawn from AHW's SA on AHR + RuntimeEvent::Balances( + pallet_balances::Event::Burned { who, amount } + ) => { + who: *who == sov_ahw_on_ahr, + amount: *amount == amount_to_send, + }, + // ROCs deposited to beneficiary + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == AssetHubRococoReceiver::get(), + }, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + }); + + let sender_rocs_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance(roc_at_asset_hub_westend, &AssetHubWestendSender::get()) + }); + let receiver_rocs_after = + ::account_data_of(AssetHubRococoReceiver::get()).free; + let rocs_in_reserve_on_ahr_after = + ::account_data_of(sov_ahw_on_ahr.clone()).free; + + // Sender's balance is reduced + assert!(sender_rocs_before > sender_rocs_after); + // Receiver's balance is increased + assert!(receiver_rocs_after > receiver_rocs_before); + // Reserve balance is reduced by sent amount + assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send); +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..b781d6e987ca1fc7f2a64710263f432c1cc8b3c6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -0,0 +1,108 @@ +// 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::imports::*; + +mod asset_transfers; +mod send_xcm; +mod teleport; + +pub(crate) fn asset_hub_rococo_location() -> Location { + Location::new( + 2, + [GlobalConsensus(NetworkId::Rococo), Parachain(AssetHubRococo::para_id().into())], + ) +} + +pub(crate) fn bridge_hub_rococo_location() -> Location { + Location::new( + 2, + [GlobalConsensus(NetworkId::Rococo), Parachain(BridgeHubRococo::para_id().into())], + ) +} + +pub(crate) fn send_asset_from_asset_hub_westend( + destination: Location, + (id, amount): (Location, u128), +) -> DispatchResult { + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + + let beneficiary: Location = + AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); + + let assets: Assets = (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::Burned { .. }) => {}, + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..b01be5e8dc84b4edf35651d0388baa1462b54c9b --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -0,0 +1,183 @@ +// 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::tests::*; + +#[test] +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(); + let destination = Westend::child_location_of(BridgeHubWestend::para_id()).into(); + let weight_limit = WeightLimit::Unlimited; + let check_origin = None; + + let remote_xcm = Xcm(vec![ClearOrigin]); + + let xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit, check_origin }, + ExportMessage { + network: RococoId, + destination: [Parachain(AssetHubRococo::para_id().into())].into(), + xcm: remote_xcm, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + // 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); +} + +#[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)); + + // prepare data + let destination = asset_hub_rococo_location(); + let native_token = Location::parent(); + let amount = ASSET_HUB_WESTEND_ED * 1_000; + + // 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.clone(), (native_token.clone(), amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // set destination version + AssetHubWestend::force_xcm_version(destination.clone(), 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.clone(), (native_token.clone(), amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // 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.clone(), (native_token.clone(), 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.clone(), + (native_token.clone(), 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.clone(), + (native_token.clone(), 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![ + // 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/bridges/bridge-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/teleport.rs new file mode 100644 index 0000000000000000000000000000000000000000..c960233c08b73df30b2f873f2ef5333ea15bf428 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/teleport.rs @@ -0,0 +1,30 @@ +// 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::tests::*; +use bridge_hub_westend_runtime::xcm_config::XcmConfig; + +#[test] +fn teleport_to_other_system_parachains_works() { + let amount = BRIDGE_HUB_WESTEND_ED * 100; + let native_asset: Assets = (Parent, amount).into(); + + test_parachain_is_trusted_teleporter!( + BridgeHubWestend, // Origin + XcmConfig, // XCM configuration + vec![AssetHubWestend], // Destinations + (native_asset, amount) + ); +} 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..609376c1fee606c6d3d284c0be8f5c30997acfb6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml @@ -0,0 +1,32 @@ +[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 } + +# 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-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 } +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-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 } +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..38ff08b486d4703513ab74d681ae5f07107931ec --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/lib.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. + +#[cfg(test)] +mod imports { + pub use codec::Encode; + + // Substrate + pub use frame_support::{ + assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchResult}, + traits::fungibles::Inspect, + }; + + // Polkadot + pub use xcm::prelude::*; + + // Cumulus + pub use asset_test_utils::xcm_helpers; + pub use emulated_integration_tests_common::xcm_emulator::{ + assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, + TestContext, TestExt, + }; + pub use parachains_common::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}, + PeopleRococoPara as PeopleRococo, PeopleRococoParaReceiver as PeopleRococoReceiver, + PeopleRococoParaSender as PeopleRococoSender, RococoRelay as Rococo, + RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, + }; + + pub type RelayToSystemParaTest = Test; + pub type SystemParaToRelayTest = Test; +} + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs similarity index 97% rename from cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/mod.rs rename to cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs index 1eef05c6b9281e26d3e412f4aac8b9e265d2d5c0..80c00021ca53db3850d6d32c1584ae7ae924933f 100644 --- a/cumulus/parachains/integration-tests/emulated/bridges/bridge-hub-rococo/src/tests/mod.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. -mod example; +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..3f1f8638d6fa1491288cd6bbd60f08c13af566dd --- /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::imports::*; +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://visitme/", 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://visitme/", 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::signed(RococoRelaySender::get()), + 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::Minted { .. }) => {}, + 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::Minted { .. }) => {}, + 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..3abe5c6cf66a357a5dfcaa67649355846761bfd2 --- /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::imports::*; +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::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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::Burned { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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::Burned { 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::Minted { 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..f2f3366798a0aacc77fe279efd1b47032c62c7dc --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml @@ -0,0 +1,32 @@ +[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 } + +# 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-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 } +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-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 } +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..77ac7cfc78c78c6f51ea174b2436d05ee612e0b7 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/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. + +#[cfg(test)] +mod imports { + pub use codec::Encode; + // Substrate + pub use frame_support::{ + assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchResult}, + traits::fungibles::Inspect, + }; + + // Polkadot + pub use xcm::prelude::*; + + // Cumulus + pub use asset_test_utils::xcm_helpers; + pub use emulated_integration_tests_common::xcm_emulator::{ + assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, + TestContext, TestExt, + }; + pub use parachains_common::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}, + PeopleWestendPara as PeopleWestend, PeopleWestendParaReceiver as PeopleWestendReceiver, + PeopleWestendParaSender as PeopleWestendSender, WestendRelay as Westend, + WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, + }; + + pub type RelayToSystemParaTest = Test; + pub type SystemParaToRelayTest = Test; +} + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/common/src/wococo.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs similarity index 90% rename from cumulus/parachains/common/src/wococo.rs rename to cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs index 5cd6121135a3321369fee2132b696aef8a2bae81..80c00021ca53db3850d6d32c1584ae7ae924933f 100644 --- a/cumulus/parachains/common/src/wococo.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/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-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..3ed8592918d65b81737a4e8206a3bc23b3684b5f --- /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::imports::*; +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://visitme/", 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://visitme/", 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::signed(WestendRelaySender::get()), + 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::Minted { .. }) => {}, + 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::Minted { .. }) => {}, + 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..eef35a99a8331d0b84dcd8d3f8e848855fc56d41 --- /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::imports::*; +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::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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::Burned { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Minted { 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::Burned { 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::Minted { 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 e85112ed8ea24e9897ac7bbc6d495867738abf0e..691be02f5b8e390bf150713bcf127af1e2ce44b0 100644 --- a/cumulus/parachains/pallets/collective-content/Cargo.toml +++ b/cumulus/parachains/pallets/collective-content/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "pallet-collective-content" -version = "0.1.0" +version = "0.6.0" authors = ["Parity Technologies "] 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"] } @@ -21,7 +25,7 @@ sp-std = { path = "../../../../substrate/primitives/std", default-features = fal sp-io = { path = "../../../../substrate/primitives/io", default-features = false } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/cumulus/parachains/pallets/collective-content/src/benchmarking.rs b/cumulus/parachains/pallets/collective-content/src/benchmarking.rs index 1f145f725b139b4e93481a9c69b0715471f7ec0b..3d6bf073778a294e39287285ddb8379d57d36c8f 100644 --- a/cumulus/parachains/pallets/collective-content/src/benchmarking.rs +++ b/cumulus/parachains/pallets/collective-content/src/benchmarking.rs @@ -16,7 +16,7 @@ //! The pallet benchmarks. use super::{Pallet as CollectiveContent, *}; -use frame_benchmarking::{impl_benchmark_test_suite, v2::*}; +use frame_benchmarking::v2::*; use frame_support::traits::EnsureOrigin; fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { @@ -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/collective-content/src/lib.rs b/cumulus/parachains/pallets/collective-content/src/lib.rs index 7a685858accb67868837e734ba9808741a4f7ea1..b1c960ad6a0d337d6b84aaaba59f7fa0625a8124 100644 --- a/cumulus/parachains/pallets/collective-content/src/lib.rs +++ b/cumulus/parachains/pallets/collective-content/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_runtime::{traits::BadOrigin, Saturating}; - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] diff --git a/cumulus/parachains/pallets/collective-content/src/mock.rs b/cumulus/parachains/pallets/collective-content/src/mock.rs index 2ae5943f332acf61b60792563b7d3f9b1e808c70..5cb0126425e59a67332d880cc8b925f50b1753c2 100644 --- a/cumulus/parachains/pallets/collective-content/src/mock.rs +++ b/cumulus/parachains/pallets/collective-content/src/mock.rs @@ -18,7 +18,7 @@ pub use crate as pallet_collective_content; use crate::WeightInfo; use frame_support::{ - ord_parameter_types, parameter_types, + derive_impl, ord_parameter_types, parameter_types, traits::{ConstU32, ConstU64}, weights::Weight, }; @@ -55,6 +55,7 @@ impl pallet_collective_content::Config for Test { type WeightInfo = CCWeightInfo; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = (); type BlockWeights = (); diff --git a/cumulus/parachains/pallets/parachain-info/Cargo.toml b/cumulus/parachains/pallets/parachain-info/Cargo.toml index c63101bab917c35530e9f8d47d70450cc17c5885..0e2f965e1cffb114729b16612b12b146ce61e0da 100644 --- a/cumulus/parachains/pallets/parachain-info/Cargo.toml +++ b/cumulus/parachains/pallets/parachain-info/Cargo.toml @@ -1,23 +1,28 @@ [package] authors.workspace = true edition.workspace = true -name = "parachain-info" -version = "0.1.0" +name = "staging-parachain-info" +version = "0.7.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"] } -frame-support = { path = "../../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../../substrate/frame/system", 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-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-primitives-core/std", diff --git a/cumulus/parachains/pallets/parachain-info/src/lib.rs b/cumulus/parachains/pallets/parachain-info/src/lib.rs index c17a6d5e1460a2ef319109c0d867b6c73a6493e0..a4ef448a6b6b9b934e9e49a4a4797a590ad55d6b 100644 --- a/cumulus/parachains/pallets/parachain-info/src/lib.rs +++ b/cumulus/parachains/pallets/parachain-info/src/lib.rs @@ -54,7 +54,7 @@ pub mod pallet { #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - >::put(self.parachain_id); + ParachainId::::put(self.parachain_id); } } @@ -64,13 +64,18 @@ pub mod pallet { } #[pallet::storage] - #[pallet::getter(fn parachain_id)] pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery, DefaultForParachainId>; impl Get for Pallet { fn get() -> ParaId { - Self::parachain_id() + ParachainId::::get() + } + } + + impl Pallet { + pub fn parachain_id() -> ParaId { + ParachainId::::get() } } } diff --git a/cumulus/parachains/pallets/ping/Cargo.toml b/cumulus/parachains/pallets/ping/Cargo.toml index 3acad9f371d33f77e8591e418c226332462a26fd..1afd55eb0b9273d37d025eb3520c9dd8be122048 100644 --- a/cumulus/parachains/pallets/ping/Cargo.toml +++ b/cumulus/parachains/pallets/ping/Cargo.toml @@ -2,24 +2,29 @@ authors.workspace = true edition.workspace = true name = "cumulus-ping" -version = "0.1.0" +version = "0.7.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"] } -sp-std = { path = "../../../../substrate/primitives/std", default-features = false} -sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false} -frame-support = { path = "../../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../../substrate/frame/system", default-features = false} +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } cumulus-pallet-xcm = { path = "../../../pallets/xcm", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-xcm/std", diff --git a/cumulus/parachains/pallets/ping/src/lib.rs b/cumulus/parachains/pallets/ping/src/lib.rs index feda3d0b6f9f0f52ee0294e1af6cff7c6764dc66..a738c05e0366bc44da562f59f7e9a638a8251d9c 100644 --- a/cumulus/parachains/pallets/ping/src/lib.rs +++ b/cumulus/parachains/pallets/ping/src/lib.rs @@ -77,9 +77,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - PingSent(ParaId, u32, Vec, XcmHash, MultiAssets), + PingSent(ParaId, u32, Vec, XcmHash, Assets), Pinged(ParaId, u32, Vec), - PongSent(ParaId, u32, Vec, XcmHash, MultiAssets), + PongSent(ParaId, u32, Vec, XcmHash, Assets), Ponged(ParaId, u32, Vec, BlockNumberFor), ErrorSendingPing(SendError, ParaId, u32, Vec), ErrorSendingPong(SendError, ParaId, u32, Vec), diff --git a/cumulus/parachains/runtimes/assets/README.md b/cumulus/parachains/runtimes/assets/README.md index 78145395cbf905f57f42b32c09fdd52a8b73b09d..05466e537f426f6edcb8f4bc569debb8b5bc0f96 100644 --- a/cumulus/parachains/runtimes/assets/README.md +++ b/cumulus/parachains/runtimes/assets/README.md @@ -8,7 +8,7 @@ Asset Hub allows users to: - Deploy promise-backed assets, both fungible and non-fungible, with a DOT/KSM deposit. - Set admin roles to manage assets and asset classes. - Register assets as "self-sufficient" if the Relay Chain agrees, i.e. gain the ability for an - asset to justify the existance of accounts sans DOT/KSM. + asset to justify the existence of accounts sans DOT/KSM. - Pay transaction fees using sufficient assets. - Transfer (and approve transfer) assets. - Interact with the chain via its transactional API or XCM. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs deleted file mode 100644 index 5c51a3a52324b16772aca32475af467600643a0b..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/lib.rs +++ /dev/null @@ -1,1481 +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. - -//! # Asset Hub Kusama Runtime -//! -//! Asset Hub Kusama, formerly known as "Statemine", is the canary network for its Polkadot cousin. - -#![cfg_attr(not(feature = "std"), no_std)] -#![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 assets_common::{ - foreign_creators::ForeignCreators, - local_and_foreign_assets::{LocalAndForeignAssets, MultiLocationConverter}, - matching::FromSiblingParachain, - AssetIdForTrustBackedAssetsConvert, MultiLocationForAssetId, -}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; -use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Verify}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Permill, -}; - -use sp_std::prelude::*; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - construct_runtime, - dispatch::DispatchClass, - genesis_builder_helper::{build_config, create_default_config}, - ord_parameter_types, parameter_types, - traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, - InstanceFilter, - }, - weights::{ConstantMultiplier, Weight}, - BoundedVec, PalletId, -}; -use frame_system::{ - limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureSigned, EnsureSignedBy, -}; -use pallet_asset_conversion_tx_payment::AssetConversionAdapter; -use pallet_nfts::PalletFeatures; -pub use parachains_common as common; -use parachains_common::{ - impls::DealWithFees, - kusama::{consensus::*, currency::*, fee::WeightToFee}, - AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, Hash, Header, Nonce, - Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, - NORMAL_DISPATCH_RATIO, SLOT_DURATION, -}; -use sp_runtime::RuntimeDebug; -use xcm::opaque::v3::MultiLocation; -use xcm_config::{ - FellowshipLocation, ForeignAssetsConvertedConcreteId, GovernanceLocation, KsmLocation, - PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId, XcmConfig, -}; - -#[cfg(any(feature = "std", test))] -pub use sp_runtime::BuildStorage; - -// Polkadot imports -use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; -use xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; - -use crate::xcm_config::{ - ForeignCreatorsSovereignAccountOf, LocalAndForeignAssetsMultiLocationMatcher, - TrustBackedAssetsPalletLocation, -}; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -#[cfg(feature = "state-trie-version-1")] -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - // Note: "statemine" is the legacy name for this chain. It has been renamed to - // "asset-hub-kusama". Many wallets/tools depend on the `spec_name`, so it remains "statemine" - // for the time being. Wallets/tools should update to treat "asset-hub-kusama" equally. - spec_name: create_runtime_str!("statemine"), - impl_name: create_runtime_str!("statemine"), - authoring_version: 1, - spec_version: 10000, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 13, - state_version: 1, -}; - -#[cfg(not(feature = "state-trie-version-1"))] -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - // Note: "statemine" is the legacy name for this change. It has been renamed to - // "asset-hub-kusama". Many wallets/tools depend on the `spec_name`, so it remains "statemine" - // for the time being. Wallets/tools should update to treat "asset-hub-kusama" equally. - spec_name: create_runtime_str!("statemine"), - impl_name: create_runtime_str!("statemine"), - authoring_version: 1, - spec_version: 10000, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 13, - state_version: 0, -}; - -/// 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 = 2; -} - -// Configure FRAME pallets to include in runtime. -impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = RuntimeBlockWeights; - type BlockLength = RuntimeBlockLength; - type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; - type Nonce = Nonce; - type Hash = Hash; - type Hashing = BlakeTwo256; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type BlockHashCount = BlockHashCount; - type DbWeight = RocksDbWeight; - type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); - type AccountData = pallet_balances::AccountData; - type SystemWeightInfo = weights::frame_system::WeightInfo; - type SS58Prefix = SS58Prefix; - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - type MaxConsumers = frame_support::traits::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 MaxLocks = ConstU32<50>; - /// The type for recording an account's balance. - type Balance = Balance; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = weights::pallet_balances::WeightInfo; - type MaxReserves = ConstU32<50>; - type ReserveIdentifier = [u8; 8]; - type RuntimeHoldReason = RuntimeHoldReason; - type FreezeIdentifier = (); - // We allow each account to have holds on it from: - // - `NftFractionalization`: 1 - type MaxHolds = ConstU32<1>; - 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 WeightToFee = WeightToFee; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; - type OperationalFeeMultiplier = ConstU8<5>; -} - -parameter_types! { - pub const AssetDeposit: Balance = UNITS / 10; // 1 / 10 UNITS deposit to create asset - pub const AssetAccountDeposit: Balance = deposit(1, 16); - pub const ApprovalDeposit: Balance = EXISTENTIAL_DEPOSIT; - pub const AssetsStringLimit: u32 = 50; - /// Key = 32 bytes, Value = 36 bytes (32+1+1+1+1) - // https://github.com/paritytech/substrate/blob/069917b/frame/assets/src/lib.rs#L257L271 - pub const MetadataDepositBase: Balance = deposit(1, 68); - pub const MetadataDepositPerByte: Balance = deposit(0, 1); -} - -/// We allow root to execute privileged asset operations. -pub type AssetsForceOrigin = EnsureRoot; - -// Called "Trust Backed" assets because these are generally registered by some account, and users of -// the asset assume it has some claimed backing. The pallet is called `Assets` in -// `construct_runtime` to avoid breaking changes on storage reads. -pub type TrustBackedAssetsInstance = pallet_assets::Instance1; -type TrustBackedAssetsCall = pallet_assets::Call; -impl pallet_assets::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type AssetId = AssetIdForTrustBackedAssets; - type AssetIdParameter = codec::Compact; - type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = AssetsForceOrigin; - type AssetDeposit = AssetDeposit; - type MetadataDepositBase = MetadataDepositBase; - type MetadataDepositPerByte = MetadataDepositPerByte; - type ApprovalDeposit = ApprovalDeposit; - type StringLimit = AssetsStringLimit; - type Freezer = (); - type Extra = (); - type WeightInfo = weights::pallet_assets_local::WeightInfo; - type CallbackHandle = (); - type AssetAccountDeposit = AssetAccountDeposit; - type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); -} - -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); -} - -ord_parameter_types! { - pub const AssetConversionOrigin: sp_runtime::AccountId32 = - AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); -} - -pub type PoolAssetsInstance = pallet_assets::Instance3; -impl pallet_assets::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; - type AssetIdParameter = u32; - type Currency = Balances; - type CreateOrigin = - AsEnsureOriginWithArg>; - type ForceOrigin = AssetsForceOrigin; - // Deposits are zero because creation/admin is limited to Asset Conversion pallet. - type AssetDeposit = ConstU128<0>; - type AssetAccountDeposit = ConstU128<0>; - type MetadataDepositBase = ConstU128<0>; - type MetadataDepositPerByte = ConstU128<0>; - type ApprovalDeposit = ApprovalDeposit; - type StringLimit = ConstU32<50>; - type Freezer = (); - type Extra = (); - type WeightInfo = weights::pallet_assets_pool::WeightInfo; - type CallbackHandle = (); - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); -} - -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 PoolAssets = PoolAssets; - type PoolAssetId = u32; - 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 LiquidityWithdrawalFee = LiquidityWithdrawalFee; - type LPFee = ConstU32<3>; - type PalletId = AssetConversionPalletId; - type AllowMultiAssetPools = AllowMultiAssetPools; - type MaxSwapPathLength = ConstU32<4>; - type MultiAssetId = Box; - type MultiAssetIdConverter = - MultiLocationConverter; - type MintMinLiquidity = ConstU128<100>; - type WeightInfo = weights::pallet_asset_conversion::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = - crate::xcm_config::BenchmarkMultiLocationConverter>; -} - -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(); -} - -/// Assets managed by some foreign location. Note: we do not declare a `ForeignAssetsCall` type, as -/// this type is used in proxy definitions. We assume that a foreign location would not want to set -/// an individual, local account as a proxy for the issuance of their assets. This issuance should -/// be managed by the foreign location's governance. -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 = ForeignCreators< - (FromSiblingParachain>,), - ForeignCreatorsSovereignAccountOf, - AccountId, - >; - type ForceOrigin = AssetsForceOrigin; - type AssetDeposit = ForeignAssetsAssetDeposit; - type MetadataDepositBase = ForeignAssetsMetadataDepositBase; - type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; - type ApprovalDeposit = ForeignAssetsApprovalDeposit; - type StringLimit = ForeignAssetsAssetsStringLimit; - type Freezer = (); - type Extra = (); - type WeightInfo = weights::pallet_assets_foreign::WeightInfo; - type CallbackHandle = (); - type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; - type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; -} - -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); - pub const MaxSignatories: u32 = 100; -} - -impl pallet_multisig::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type DepositBase = DepositBase; - type DepositFactor = DepositFactor; - type MaxSignatories = MaxSignatories; - 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; -} - -parameter_types! { - // One storage item; key size 32, value size 8; . - pub const ProxyDepositBase: Balance = deposit(1, 40); - // Additional storage item size of 33 bytes. - pub const ProxyDepositFactor: Balance = deposit(0, 33); - pub const MaxProxies: u16 = 32; - // One storage item; key size 32, value size 16 - pub const AnnouncementDepositBase: Balance = deposit(1, 48); - pub const AnnouncementDepositFactor: Balance = deposit(0, 66); - pub const MaxPending: u16 = 32; -} - -/// The type used to represent the kinds of proxying allowed. -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Encode, - Decode, - RuntimeDebug, - MaxEncodedLen, - scale_info::TypeInfo, -)] -pub enum ProxyType { - /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. - Any, - /// Can execute any call that does not transfer funds or assets. - NonTransfer, - /// Proxy with the ability to reject time-delay proxy announcements. - CancelProxy, - /// Assets proxy. Can execute any call from `assets`, **including asset transfers**. - Assets, - /// Owner proxy. Can execute calls related to asset ownership. - AssetOwner, - /// Asset manager. Can execute calls related to asset management. - AssetManager, - /// Collator selection proxy. Can execute calls related to collator selection mechanism. - Collator, -} -impl Default for ProxyType { - fn default() -> Self { - Self::Any - } -} - -impl InstanceFilter for ProxyType { - fn filter(&self, c: &RuntimeCall) -> bool { - match self { - ProxyType::Any => true, - ProxyType::NonTransfer => !matches!( - c, - RuntimeCall::Balances { .. } | - RuntimeCall::Assets { .. } | - RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | - RuntimeCall::Uniques { .. } - ), - ProxyType::CancelProxy => matches!( - c, - RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::Assets => { - matches!( - c, - RuntimeCall::Assets { .. } | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } | - RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } - ) - }, - ProxyType::AssetOwner => matches!( - c, - RuntimeCall::Assets(TrustBackedAssetsCall::create { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::start_destroy { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::destroy_accounts { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::destroy_approvals { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::finish_destroy { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::transfer_ownership { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_team { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::transfer_ownership { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_team { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_max_supply { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_collection { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::create { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::destroy { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::transfer_ownership { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_team { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_attribute { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_attribute { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_collection_max_supply { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::AssetManager => matches!( - c, - RuntimeCall::Assets(TrustBackedAssetsCall::mint { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::burn { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::freeze { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::block { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::thaw { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::freeze_asset { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_attributes_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::unlock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_properties { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::mint { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::burn { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::freeze { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::thaw { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::freeze_collection { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::thaw_collection { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::Collator => matches!( - c, - RuntimeCall::CollatorSelection { .. } | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - } - } - - fn is_superset(&self, o: &Self) -> bool { - match (self, o) { - (x, y) if x == y => true, - (ProxyType::Any, _) => true, - (_, ProxyType::Any) => false, - (ProxyType::Assets, ProxyType::AssetOwner) => true, - (ProxyType::Assets, ProxyType::AssetManager) => true, - (ProxyType::NonTransfer, ProxyType::Collator) => true, - _ => false, - } - } -} - -impl pallet_proxy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type ProxyType = ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; - type WeightInfo = weights::pallet_proxy::WeightInfo; - type MaxPending = MaxPending; - type CallHasher = BlakeTwo256; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; -} - -parameter_types! { - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); - type SelfParaId = parachain_info::Pallet; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type OutboundXcmpMessageSource = XcmpQueue; - type XcmpMessageHandler = XcmpQueue; - type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; -} - -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; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; - type ControllerOrigin = EitherOfDiverse< - EnsureRoot, - EnsureXcm>, - >; - type ControllerOriginConverter = xcm_config::XcmOriginToTransactDispatchOrigin; - type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; - type PriceForSiblingDelivery = NoPriceForMessageDelivery; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; -} - -parameter_types! { - 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; - type NextSessionRotation = pallet_session::PeriodicSessions; - 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 = Period; - type ValidatorId = ::AccountId; - type ValidatorIdOf = pallet_collator_selection::IdentityCollator; - type ValidatorRegistration = Session; - type WeightInfo = weights::pallet_collator_selection::WeightInfo; -} - -impl pallet_asset_conversion_tx_payment::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Fungibles = LocalAndForeignAssets< - Assets, - AssetIdForTrustBackedAssetsConvert, - ForeignAssets, - >; - type OnChargeAssetTransaction = AssetConversionAdapter; -} - -parameter_types! { - pub const UniquesCollectionDeposit: Balance = UNITS / 10; // 1 / 10 UNIT deposit to create a collection - pub const UniquesItemDeposit: Balance = UNITS / 1_000; // 1 / 1000 UNIT deposit to mint an item - pub const UniquesMetadataDepositBase: Balance = deposit(1, 129); - pub const UniquesAttributeDepositBase: Balance = deposit(1, 0); - pub const UniquesDepositPerByte: Balance = deposit(0, 1); -} - -impl pallet_uniques::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; - type Currency = Balances; - type ForceOrigin = AssetsForceOrigin; - type CollectionDeposit = UniquesCollectionDeposit; - type ItemDeposit = UniquesItemDeposit; - type MetadataDepositBase = UniquesMetadataDepositBase; - type AttributeDepositBase = UniquesAttributeDepositBase; - type DepositPerByte = UniquesDepositPerByte; - type StringLimit = ConstU32<128>; - type KeyLimit = ConstU32<32>; - type ValueLimit = ConstU32<64>; - type WeightInfo = weights::pallet_uniques::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type Helper = (); - type CreateOrigin = AsEnsureOriginWithArg>; - type Locker = (); -} - -parameter_types! { - pub const NftFractionalizationPalletId: PalletId = PalletId(*b"fraction"); - pub NewAssetSymbol: BoundedVec = (*b"FRAC").to_vec().try_into().unwrap(); - pub NewAssetName: BoundedVec = (*b"Frac").to_vec().try_into().unwrap(); -} - -impl pallet_nft_fractionalization::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Deposit = AssetDeposit; - type Currency = Balances; - type NewAssetSymbol = NewAssetSymbol; - type NewAssetName = NewAssetName; - type StringLimit = AssetsStringLimit; - type NftCollectionId = ::CollectionId; - type NftId = ::ItemId; - type AssetBalance = ::Balance; - type AssetId = >::AssetId; - type Assets = Assets; - type Nfts = Nfts; - type PalletId = NftFractionalizationPalletId; - type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; - type RuntimeHoldReason = RuntimeHoldReason; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); -} - -parameter_types! { - pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); - pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; - // re-use the Uniques deposits - pub const NftsCollectionDeposit: Balance = UniquesCollectionDeposit::get(); - pub const NftsItemDeposit: Balance = UniquesItemDeposit::get(); - pub const NftsMetadataDepositBase: Balance = UniquesMetadataDepositBase::get(); - pub const NftsAttributeDepositBase: Balance = UniquesAttributeDepositBase::get(); - pub const NftsDepositPerByte: Balance = UniquesDepositPerByte::get(); -} - -impl pallet_nfts::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; - type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = AssetsForceOrigin; - type Locker = (); - type CollectionDeposit = NftsCollectionDeposit; - type ItemDeposit = NftsItemDeposit; - type MetadataDepositBase = NftsMetadataDepositBase; - type AttributeDepositBase = NftsAttributeDepositBase; - type DepositPerByte = NftsDepositPerByte; - type StringLimit = ConstU32<256>; - type KeyLimit = ConstU32<64>; - type ValueLimit = ConstU32<256>; - type ApprovalsLimit = ConstU32<20>; - type ItemAttributesApprovalsLimit = ConstU32<30>; - type MaxTips = ConstU32<10>; - type MaxDeadlineDuration = NftsMaxDeadlineDuration; - type MaxAttributesPerCall = ConstU32<10>; - type Features = NftsPalletFeatures; - type OffchainSignature = Signature; - type OffchainPublic = ::Signer; - type WeightInfo = weights::pallet_nfts::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type Helper = (); -} - -// 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, - // RandomnessCollectiveFlip = 2 removed - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, - - // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - AssetTxPayment: pallet_asset_conversion_tx_payment::{Pallet, Event} = 13, - - // 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, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, - - // Handy utilities. - Utility: pallet_utility::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, - - // The main stage. - Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, - Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, - ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, - NftFractionalization: pallet_nft_fractionalization::{Pallet, Call, Storage, Event, HoldReason} = 54, - - PoolAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 55, - AssetConversion: pallet_asset_conversion::{Pallet, Call, Storage, Event} = 56, - - #[cfg(feature = "state-trie-version-1")] - StateTrieMigration: pallet_state_trie_migration = 70, - } -); - -/// The address format for describing accounts. -pub type Address = sp_runtime::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_asset_conversion_tx_payment::ChargeAssetTxPayment, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; -/// Migrations to apply on runtime upgrade. -pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); - -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, - Migrations, ->; - -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - -#[cfg(feature = "runtime-benchmarks")] -mod benches { - define_benchmarks!( - [frame_system, SystemBench::] - [pallet_assets, Local] - [pallet_assets, Foreign] - [pallet_assets, Pool] - [pallet_asset_conversion, AssetConversion] - [pallet_balances, Balances] - [pallet_multisig, Multisig] - [pallet_nft_fractionalization, NftFractionalization] - [pallet_nfts, Nfts] - [pallet_proxy, Proxy] - [pallet_session, SessionBench::] - [pallet_uniques, Uniques] - [pallet_utility, Utility] - [pallet_timestamp, Timestamp] - [pallet_collator_selection, CollatorSelection] - [cumulus_pallet_xcmp_queue, XcmpQueue] - // XCM - [pallet_xcm, PolkadotXcm] - // 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_asset_conversion::AssetConversionApi< - Block, - Balance, - u128, - Box, - > for Runtime - { - fn quote_price_exact_tokens_for_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { - AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) - } - fn quote_price_tokens_for_exact_tokens(asset1: Box, asset2: Box, amount: u128, 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() - } - } - - 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 assets_common::runtime_api::FungiblesApi< - Block, - AccountId, - > for Runtime - { - fn query_account_balances(account: AccountId) -> Result { - use assets_common::fungible_conversion::{convert, convert_balance}; - Ok([ - // collect pallet_balance - { - let balance = Balances::free_balance(account.clone()); - if balance > 0 { - vec![convert_balance::(balance)?] - } else { - vec![] - } - }, - // collect pallet_assets (TrustBackedAssets) - convert::<_, _, _, _, TrustBackedAssetsConvertedConcreteId>( - Assets::account_balances(account.clone()) - .iter() - .filter(|(_, balance)| balance > &0) - )?, - // collect pallet_assets (ForeignAssets) - convert::<_, _, _, _, ForeignAssetsConvertedConcreteId>( - ForeignAssets::account_balances(account.clone()) - .iter() - .filter(|(_, balance)| balance > &0) - )?, - // collect pallet_assets (PoolAssets) - convert::<_, _, _, _, PoolAssetsConvertedConcreteId>( - PoolAssets::account_balances(account) - .iter() - .filter(|(_, balance)| balance > &0) - )?, - // collect ... e.g. other tokens - ].concat().into()) - } - } - - 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; - - // 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::; - - // Benchmark files generated for `Assets/ForeignAssets` instances are by default - // `pallet_assets_assets.rs / pallet_assets_foreign_assets`, which is not really nice, - // so with this redefinition we can change names to nicer: - // `pallet_assets_local.rs / pallet_assets_foreign.rs`. - type Local = pallet_assets::Pallet::; - type Foreign = pallet_assets::Pallet::; - type Pool = pallet_assets::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::{KsmLocation, MaxAssetsIntoHolding}; - use pallet_xcm_benchmarks::asset_instance_from; - - parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( - KsmLocation::get(), - ExistentialDeposit::get() - ).into()); - } - - impl pallet_xcm_benchmarks::Config for Runtime { - type XcmConfig = xcm_config::XcmConfig; - type AccountIdConverter = xcm_config::LocationToAccountId; - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - XcmConfig, - ExistentialDepositMultiAsset, - xcm_config::PriceForParentDelivery, - >; - fn valid_destination() -> Result { - Ok(KsmLocation::get()) - } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { - // A mix of fungible, non-fungible, and concrete assets. - let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; - let holding_fungibles = holding_non_fungibles.saturating_sub(1); - let fungibles_amount: u128 = 100; - let mut assets = (0..holding_fungibles) - .map(|i| { - MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), - fun: Fungible(fungibles_amount * i as u128), - } - }) - .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..holding_non_fungibles).map(|i| MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), - fun: NonFungible(asset_instance_from(i)), - })) - .collect::>(); - - assets.push(MultiAsset { - id: Concrete(KsmLocation::get()), - fun: Fungible(1_000_000 * UNITS), - }); - assets.into() - } - } - - parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( - KsmLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(KsmLocation::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(KsmLocation::get()), - fun: Fungible(UNITS), - } - } - } - - impl pallet_xcm_benchmarks::generic::Config for Runtime { - type TransactAsset = Balances; - type RuntimeCall = RuntimeCall; - - 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((KsmLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) - } - - fn subscribe_origin() -> Result { - Ok(KsmLocation::get()) - } - - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { - let origin = KsmLocation::get(); - let assets: MultiAssets = (Concrete(KsmLocation::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::; - - type Local = pallet_assets::Pallet::; - type Foreign = pallet_assets::Pallet::; - type Pool = pallet_assets::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(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").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::, -} - -#[cfg(feature = "state-trie-version-1")] -parameter_types! { - // The deposit configuration for the singed migration. Specially if you want to allow any signed account to do the migration (see `SignedFilter`, these deposits should be high) - pub const MigrationSignedDepositPerItem: Balance = CENTS; - pub const MigrationSignedDepositBase: Balance = 2_000 * CENTS; - pub const MigrationMaxKeyLen: u32 = 512; -} - -#[cfg(feature = "state-trie-version-1")] -impl pallet_state_trie_migration::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type SignedDepositPerItem = MigrationSignedDepositPerItem; - type SignedDepositBase = MigrationSignedDepositBase; - // An origin that can control the whole pallet: should be Root, or a part of your council. - type ControlOrigin = frame_system::EnsureSignedBy; - // specific account for the migration, can trigger the signed migrations. - type SignedFilter = frame_system::EnsureSignedBy; - - // Replace this with weight based on your runtime. - type WeightInfo = pallet_state_trie_migration::weights::SubstrateWeight; - - type MaxKeyLen = MigrationMaxKeyLen; -} - -#[cfg(feature = "state-trie-version-1")] -frame_support::ord_parameter_types! { - pub const MigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); - pub const RootMigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52")); -} - -#[cfg(feature = "state-trie-version-1")] -#[test] -fn ensure_key_ss58() { - use frame_support::traits::SortedMembers; - use sp_core::crypto::Ss58Codec; - let acc = - AccountId::from_ss58check("5F4EbSkZz18X36xhbsjvDNs6NuZ82HyYtq5UiJ1h9SBHJXZD").unwrap(); - //panic!("{:x?}", acc); - assert_eq!(acc, MigController::sorted_members()[0]); - let acc = - AccountId::from_ss58check("5F4EbSkZz18X36xhbsjvDNs6NuZ82HyYtq5UiJ1h9SBHJXZD").unwrap(); - assert_eq!(acc, RootMigController::sorted_members()[0]); - //panic!("{:x?}", acc); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{CENTS, MILLICENTS}; - use parachains_common::kusama::fee; - use sp_runtime::traits::Zero; - use sp_weights::WeightToFee; - - /// We can fit at least 1000 transfers in a block. - #[test] - fn sane_block_weight() { - use pallet_balances::WeightInfo; - let block = RuntimeBlockWeights::get().max_block; - let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; - let transfer = - base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); - - let fit = block.checked_div_per_component(&transfer).unwrap_or_default(); - assert!(fit >= 1000, "{} should be at least 1000", fit); - } - - /// The fee for one transfer is at most 1 CENT. - #[test] - fn sane_transfer_fee() { - use pallet_balances::WeightInfo; - let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; - let transfer = - base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); - - let fee: Balance = fee::WeightToFee::weight_to_fee(&transfer); - assert!(fee <= CENTS, "{} MILLICENTS should be at most 1000", fee / MILLICENTS); - } - - /// Weight is being charged for both dimensions. - #[test] - fn weight_charged_for_both_components() { - let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(10_000, 0)); - assert!(!fee.is_zero(), "Charges for ref time"); - - let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, 10_000)); - assert_eq!(fee, CENTS, "10kb maps to CENT"); - } - - /// Filling up a block by proof size is at most 30 times more expensive than ref time. - /// - /// This is just a sanity check. - #[test] - fn full_block_fee_ratio() { - let block = RuntimeBlockWeights::get().max_block; - let time_fee: Balance = - fee::WeightToFee::weight_to_fee(&Weight::from_parts(block.ref_time(), 0)); - let proof_fee: Balance = - fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, block.proof_size())); - - let proof_o_time = proof_fee.checked_div(time_fee).unwrap_or_default(); - assert!(proof_o_time <= 30, "{} should be at most 30", proof_o_time); - let time_o_proof = time_fee.checked_div(proof_fee).unwrap_or_default(); - assert!(time_o_proof <= 30, "{} should be at most 30", time_o_proof); - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs deleted file mode 100644 index 9c7a56687b314e51ff3d77134b5d389e9568b293..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs +++ /dev/null @@ -1,77 +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 . - -//! Autogenerated weights for `cumulus_pallet_xcmp_queue` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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: 5_467_000 picoseconds. - Weight::from_parts(5_634_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:1) - /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 5_409_000 picoseconds. - Weight::from_parts(5_570_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/frame_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/frame_system.rs deleted file mode 100644 index 96477ddf4bd3fab597a6d2a89ae30e30f33840cd..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/frame_system.rs +++ /dev/null @@ -1,155 +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 . - -//! Autogenerated weights for `frame_system` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=frame_system -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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: 2_106_000 picoseconds. - Weight::from_parts(1_884_213, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 0 - .saturating_add(Weight::from_parts(388, 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_528_000 picoseconds. - Weight::from_parts(27_081_927, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_730, 0).saturating_mul(b.into())) - } - /// 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: 3_882_000 picoseconds. - Weight::from_parts(4_149_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: `119` - // Estimated: `1604` - // Minimum execution time: 103_389_161_000 picoseconds. - Weight::from_parts(106_870_091_000, 0) - .saturating_add(Weight::from_parts(0, 1604)) - .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: 2_236_000 picoseconds. - Weight::from_parts(2_302_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_045 - .saturating_add(Weight::from_parts(763_456, 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::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_175_000 picoseconds. - Weight::from_parts(2_238_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_040 - .saturating_add(Weight::from_parts(571_397, 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::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: `84 + p * (69 ±0)` - // Estimated: `80 + p * (70 ±0)` - // Minimum execution time: 3_843_000 picoseconds. - Weight::from_parts(3_947_000, 0) - .saturating_add(Weight::from_parts(0, 80)) - // Standard Error: 2_188 - .saturating_add(Weight::from_parts(1_212_360, 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())) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs deleted file mode 100644 index 281c013b3372f57a24b57812de91e684ffabd0c9..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/mod.rs +++ /dev/null @@ -1,43 +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 . - -pub mod block_weights; -pub mod cumulus_pallet_xcmp_queue; -pub mod extrinsic_weights; -pub mod frame_system; -pub mod pallet_asset_conversion; -pub mod pallet_assets_foreign; -pub mod pallet_assets_local; -pub mod pallet_assets_pool; -pub mod pallet_balances; -pub mod pallet_collator_selection; -pub mod pallet_multisig; -pub mod pallet_nft_fractionalization; -pub mod pallet_nfts; -pub mod pallet_proxy; -pub mod pallet_session; -pub mod pallet_timestamp; -pub mod pallet_uniques; -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 paritydb_weights::constants::ParityDbWeight; -pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_asset_conversion.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_asset_conversion.rs deleted file mode 100644 index 702f3743a7203f7103474dea554cb7aec2448ce6..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_asset_conversion.rs +++ /dev/null @@ -1,155 +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 . - -//! 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: `[]` -//! 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-kusama-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/cumulus/.git/.artifacts/bench.json -// --pallet=pallet_asset_conversion -// --chain=asset-hub-kusama-dev -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_conversion`. -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) - /// 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`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) - /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn create_pool() -> Weight { - // Proof Size summary in bytes: - // Measured: `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)) - .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: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:2 w:2) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn add_liquidity() -> Weight { - // Proof Size summary in bytes: - // Measured: `1117` - // Estimated: `7404` - // Minimum execution time: 153_015_000 picoseconds. - Weight::from_parts(157_018_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: `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) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(6)) - } - /// Storage: `ForeignAssets::Asset` (r:2 w:2) - /// 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`) - /// 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 { - // 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)) - } - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:2 w:2) - /// 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 { - // 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)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_foreign.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_foreign.rs deleted file mode 100644 index 7c237b20389db7af3bab0e5e231dac0097210328..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_foreign.rs +++ /dev/null @@ -1,534 +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 . - -//! Autogenerated weights for `pallet_assets` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-20, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json -// --pallet=pallet_assets -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_assets`. -pub struct WeightInfo(PhantomData); -impl pallet_assets::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: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `107` - // Estimated: `4273` - // Minimum execution time: 30_485_000 picoseconds. - Weight::from_parts(31_007_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `4` - // Estimated: `4273` - // Minimum execution time: 12_991_000 picoseconds. - Weight::from_parts(13_304_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn start_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 15_689_000 picoseconds. - Weight::from_parts(16_063_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// 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:1001 w:1000) - /// Proof: ForeignAssets Account (max_values: None, max_size: Some(732), added: 3207, mode: MaxEncodedLen) - /// Storage: System Account (r:1000 w:1000) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - fn destroy_accounts(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + c * (208 ±0)` - // Estimated: `4273 + c * (3207 ±0)` - // Minimum execution time: 18_533_000 picoseconds. - Weight::from_parts(18_791_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 5_059 - .saturating_add(Weight::from_parts(12_049_659, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 3207).saturating_mul(c.into())) - } - /// Storage: ForeignAssets Asset (r:1 w:1) - /// Proof: ForeignAssets Asset (max_values: None, max_size: Some(808), added: 3283, mode: MaxEncodedLen) - /// Storage: ForeignAssets Approvals (r:1001 w:1000) - /// Proof: ForeignAssets Approvals (max_values: None, max_size: Some(746), added: 3221, mode: MaxEncodedLen) - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy_approvals(a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `413 + a * (86 ±0)` - // Estimated: `4273 + a * (3221 ±0)` - // Minimum execution time: 20_028_000 picoseconds. - Weight::from_parts(20_148_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 3_401 - .saturating_add(Weight::from_parts(13_897_319, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 3221).saturating_mul(a.into())) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:0) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn finish_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_949_000 picoseconds. - Weight::from_parts(16_241_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// 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:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 27_156_000 picoseconds. - Weight::from_parts(28_182_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// 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:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 33_503_000 picoseconds. - Weight::from_parts(33_860_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// 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`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 45_065_000 picoseconds. - Weight::from_parts(45_856_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// 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`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 39_913_000 picoseconds. - Weight::from_parts(40_791_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// 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`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 45_337_000 picoseconds. - Weight::from_parts(45_980_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 19_012_000 picoseconds. - Weight::from_parts(19_326_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 18_656_000 picoseconds. - Weight::from_parts(19_205_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn freeze_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 15_440_000 picoseconds. - Weight::from_parts(15_825_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn thaw_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 15_465_000 picoseconds. - Weight::from_parts(15_769_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:0) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 16_579_000 picoseconds. - Weight::from_parts(16_931_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_138_000 picoseconds. - Weight::from_parts(15_435_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: ForeignAssets Asset (r:1 w:0) - /// Proof: ForeignAssets Asset (max_values: None, max_size: Some(808), added: 3283, mode: MaxEncodedLen) - /// Storage: ForeignAssets Metadata (r:1 w:1) - /// Proof: ForeignAssets Metadata (max_values: None, max_size: Some(738), added: 3213, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn set_metadata(_n: u32, _s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 29_846_000 picoseconds. - Weight::from_parts(31_607_649, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `406` - // Estimated: `4273` - // Minimum execution time: 30_582_000 picoseconds. - Weight::from_parts(31_008_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: ForeignAssets Asset (r:1 w:0) - /// Proof: ForeignAssets Asset (max_values: None, max_size: Some(808), added: 3283, mode: MaxEncodedLen) - /// Storage: ForeignAssets Metadata (r:1 w:1) - /// Proof: ForeignAssets Metadata (max_values: None, max_size: Some(738), added: 3213, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `81` - // Estimated: `4273` - // Minimum execution time: 14_186_000 picoseconds. - Weight::from_parts(14_717_332, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 517 - .saturating_add(Weight::from_parts(2_595, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn force_clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `406` - // Estimated: `4273` - // Minimum execution time: 29_499_000 picoseconds. - Weight::from_parts(29_918_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn force_asset_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 13_815_000 picoseconds. - Weight::from_parts(14_138_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 33_029_000 picoseconds. - Weight::from_parts(33_524_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, 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`) - fn transfer_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `520` - // Estimated: `7404` - // Minimum execution time: 63_205_000 picoseconds. - Weight::from_parts(64_078_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `446` - // Estimated: `4273` - // Minimum execution time: 34_948_000 picoseconds. - Weight::from_parts(35_484_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn force_cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `446` - // Estimated: `4273` - // Minimum execution time: 35_722_000 picoseconds. - Weight::from_parts(36_266_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn set_min_balance() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_855_000 picoseconds. - Weight::from_parts(16_182_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn touch() -> Weight { - // Proof Size summary in bytes: - // Measured: `345` - // Estimated: `4273` - // Minimum execution time: 34_984_000 picoseconds. - Weight::from_parts(35_512_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn touch_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 33_041_000 picoseconds. - Weight::from_parts(34_124_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn refund() -> Weight { - // Proof Size summary in bytes: - // Measured: `471` - // Estimated: `4273` - // Minimum execution time: 31_728_000 picoseconds. - Weight::from_parts(32_012_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn refund_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `4273` - // Minimum execution time: 29_432_000 picoseconds. - Weight::from_parts(29_968_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn block() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 18_827_000 picoseconds. - Weight::from_parts(19_172_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_local.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_local.rs deleted file mode 100644 index 10bd4b1f8b097d40bb23e9a24a1f81ee0c6fea09..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_local.rs +++ /dev/null @@ -1,531 +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 . - -//! Autogenerated weights for `pallet_assets` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_assets -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_assets`. -pub struct WeightInfo(PhantomData); -impl pallet_assets::WeightInfo for WeightInfo { - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3675` - // Minimum execution time: 26_510_000 picoseconds. - Weight::from_parts(27_332_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `3675` - // Minimum execution time: 10_899_000 picoseconds. - Weight::from_parts(11_395_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn start_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_593_000 picoseconds. - Weight::from_parts(14_108_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1001 w:1000) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1000 w:1000) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - fn destroy_accounts(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + c * (208 ±0)` - // Estimated: `3675 + c * (2609 ±0)` - // Minimum execution time: 16_216_000 picoseconds. - Weight::from_parts(16_636_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 9_346 - .saturating_add(Weight::from_parts(15_306_152, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1001 w:1000) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy_approvals(a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `414 + a * (86 ±0)` - // Estimated: `3675 + a * (2623 ±0)` - // Minimum execution time: 16_745_000 picoseconds. - Weight::from_parts(17_247_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(Weight::from_parts(15_634_963, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:0) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn finish_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 13_650_000 picoseconds. - Weight::from_parts(14_721_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 24_121_000 picoseconds. - Weight::from_parts(25_023_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 31_414_000 picoseconds. - Weight::from_parts(32_235_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 43_114_000 picoseconds. - Weight::from_parts(44_106_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 37_954_000 picoseconds. - Weight::from_parts(38_772_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 43_051_000 picoseconds. - Weight::from_parts(44_003_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 17_048_000 picoseconds. - Weight::from_parts(17_614_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 16_705_000 picoseconds. - Weight::from_parts(17_581_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn freeze_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_284_000 picoseconds. - Weight::from_parts(13_735_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn thaw_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_030_000 picoseconds. - Weight::from_parts(13_417_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:0) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 14_174_000 picoseconds. - Weight::from_parts(14_660_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 12_737_000 picoseconds. - Weight::from_parts(13_172_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn set_metadata(n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 27_707_000 picoseconds. - Weight::from_parts(29_036_880, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 688 - .saturating_add(Weight::from_parts(2_426, 0).saturating_mul(n.into())) - // Standard Error: 688 - .saturating_add(Weight::from_parts(776, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `407` - // Estimated: `3675` - // Minimum execution time: 28_514_000 picoseconds. - Weight::from_parts(29_216_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `82` - // Estimated: `3675` - // Minimum execution time: 12_452_000 picoseconds. - Weight::from_parts(13_095_356, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 275 - .saturating_add(Weight::from_parts(826, 0).saturating_mul(n.into())) - // Standard Error: 275 - .saturating_add(Weight::from_parts(808, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn force_clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `407` - // Estimated: `3675` - // Minimum execution time: 28_181_000 picoseconds. - Weight::from_parts(29_050_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_asset_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 12_253_000 picoseconds. - Weight::from_parts(12_545_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 31_084_000 picoseconds. - Weight::from_parts(32_052_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `521` - // Estimated: `6208` - // Minimum execution time: 61_756_000 picoseconds. - Weight::from_parts(62_740_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3675` - // Minimum execution time: 33_370_000 picoseconds. - Weight::from_parts(34_127_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn force_cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3675` - // Minimum execution time: 33_753_000 picoseconds. - Weight::from_parts(34_613_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_min_balance() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 13_508_000 picoseconds. - Weight::from_parts(13_997_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn touch() -> Weight { - // Proof Size summary in bytes: - // Measured: `346` - // Estimated: `3675` - // Minimum execution time: 32_578_000 picoseconds. - Weight::from_parts(33_675_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn touch_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 30_768_000 picoseconds. - Weight::from_parts(31_710_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn refund() -> Weight { - // Proof Size summary in bytes: - // Measured: `472` - // Estimated: `3675` - // Minimum execution time: 30_028_000 picoseconds. - Weight::from_parts(30_793_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn refund_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `402` - // Estimated: `3675` - // Minimum execution time: 28_354_000 picoseconds. - Weight::from_parts(29_097_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn block() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 16_607_000 picoseconds. - Weight::from_parts(17_433_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_pool.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_pool.rs deleted file mode 100644 index 444699e33ef7d714aac0f0cb11a4ead143c76c87..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_assets_pool.rs +++ /dev/null @@ -1,531 +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 . - -//! Autogenerated weights for `pallet_assets` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-27, 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("asset-hub-kusama-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/cumulus/.git/.artifacts/bench.json -// --pallet=pallet_assets -// --chain=asset-hub-kusama-dev -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_assets`. -pub struct WeightInfo(PhantomData); -impl pallet_assets::WeightInfo for WeightInfo { - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3675` - // Minimum execution time: 11_591_000 picoseconds. - Weight::from_parts(11_901_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3675` - // Minimum execution time: 11_184_000 picoseconds. - Weight::from_parts(11_640_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn start_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `314` - // Estimated: `3675` - // Minimum execution time: 13_809_000 picoseconds. - Weight::from_parts(14_226_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// 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:1001 w:1000) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1000 w:1000) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - fn destroy_accounts(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + c * (208 ±0)` - // Estimated: `3675 + c * (2609 ±0)` - // Minimum execution time: 16_439_000 picoseconds. - Weight::from_parts(16_743_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 4_792 - .saturating_add(Weight::from_parts(14_463_991, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Approvals` (r:1001 w:1000) - /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy_approvals(a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `451 + a * (86 ±0)` - // Estimated: `3675 + a * (2623 ±0)` - // Minimum execution time: 17_218_000 picoseconds. - Weight::from_parts(17_585_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 2_056 - .saturating_add(Weight::from_parts(5_323_866, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:0) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn finish_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 13_848_000 picoseconds. - Weight::from_parts(14_325_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 24_904_000 picoseconds. - Weight::from_parts(25_607_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `3675` - // Minimum execution time: 31_477_000 picoseconds. - Weight::from_parts(32_338_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:2 w:2) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `6208` - // Minimum execution time: 42_994_000 picoseconds. - Weight::from_parts(44_041_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:2 w:2) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// 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: `388` - // Estimated: `6208` - // Minimum execution time: 37_551_000 picoseconds. - Weight::from_parts(38_648_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:2 w:2) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `6208` - // Minimum execution time: 42_829_000 picoseconds. - Weight::from_parts(44_029_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `3675` - // Minimum execution time: 17_304_000 picoseconds. - Weight::from_parts(17_782_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `3675` - // Minimum execution time: 17_040_000 picoseconds. - Weight::from_parts(17_698_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn freeze_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `314` - // Estimated: `3675` - // Minimum execution time: 13_238_000 picoseconds. - Weight::from_parts(13_810_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn thaw_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `314` - // Estimated: `3675` - // Minimum execution time: 13_034_000 picoseconds. - Weight::from_parts(13_603_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:0) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 14_357_000 picoseconds. - Weight::from_parts(14_774_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 13_040_000 picoseconds. - Weight::from_parts(13_616_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:1) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn set_metadata(n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 15_274_000 picoseconds. - Weight::from_parts(16_096_881, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 239 - .saturating_add(Weight::from_parts(1_631, 0).saturating_mul(n.into())) - // Standard Error: 239 - .saturating_add(Weight::from_parts(2_334, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:1) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `444` - // Estimated: `3675` - // Minimum execution time: 15_900_000 picoseconds. - Weight::from_parts(16_526_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:1) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `119` - // Estimated: `3675` - // Minimum execution time: 13_391_000 picoseconds. - Weight::from_parts(14_047_176, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 172 - .saturating_add(Weight::from_parts(2_617, 0).saturating_mul(n.into())) - // Standard Error: 172 - .saturating_add(Weight::from_parts(2_081, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Metadata` (r:1 w:1) - /// Proof: `PoolAssets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn force_clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `444` - // Estimated: `3675` - // Minimum execution time: 15_794_000 picoseconds. - Weight::from_parts(16_279_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_asset_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 12_538_000 picoseconds. - Weight::from_parts(13_080_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Approvals` (r:1 w:1) - /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `314` - // Estimated: `3675` - // Minimum execution time: 18_991_000 picoseconds. - Weight::from_parts(19_812_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Approvals` (r:1 w:1) - /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:2 w:2) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `558` - // Estimated: `6208` - // Minimum execution time: 50_336_000 picoseconds. - Weight::from_parts(51_441_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Approvals` (r:1 w:1) - /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `484` - // Estimated: `3675` - // Minimum execution time: 21_195_000 picoseconds. - Weight::from_parts(21_946_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Approvals` (r:1 w:1) - /// Proof: `PoolAssets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn force_cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `484` - // Estimated: `3675` - // Minimum execution time: 21_568_000 picoseconds. - Weight::from_parts(22_366_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:1) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_min_balance() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 13_690_000 picoseconds. - Weight::from_parts(14_086_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::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`) - fn touch() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 18_240_000 picoseconds. - Weight::from_parts(19_000_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::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`) - fn touch_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `280` - // Estimated: `3675` - // Minimum execution time: 18_469_000 picoseconds. - Weight::from_parts(19_040_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::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`) - fn refund() -> Weight { - // Proof Size summary in bytes: - // Measured: `406` - // Estimated: `3675` - // Minimum execution time: 14_633_000 picoseconds. - Weight::from_parts(15_296_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::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`) - fn refund_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `439` - // Estimated: `3675` - // Minimum execution time: 14_751_000 picoseconds. - Weight::from_parts(15_312_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `PoolAssets::Asset` (r:1 w:0) - /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `PoolAssets::Account` (r:1 w:1) - /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn block() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `3675` - // Minimum execution time: 16_930_000 picoseconds. - Weight::from_parts(17_653_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nft_fractionalization.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nft_fractionalization.rs deleted file mode 100644 index c55a18adc5233d75dd05f8b35678023a548051ed..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nft_fractionalization.rs +++ /dev/null @@ -1,115 +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 . - -//! Autogenerated weights for `pallet_nft_fractionalization` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_nft_fractionalization -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_nft_fractionalization`. -pub struct WeightInfo(PhantomData); -impl pallet_nft_fractionalization::WeightInfo for WeightInfo { - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// Storage: `NftFractionalization::NftToAsset` (r:0 w:1) - /// Proof: `NftFractionalization::NftToAsset` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) - fn fractionalize() -> Weight { - // Proof Size summary in bytes: - // Measured: `462` - // Estimated: `4326` - // Minimum execution time: 178_501_000 picoseconds. - Weight::from_parts(180_912_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(8)) - } - /// Storage: `NftFractionalization::NftToAsset` (r:1 w:1) - /// Proof: `NftFractionalization::NftToAsset` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn unify() -> Weight { - // Proof Size summary in bytes: - // Measured: `1275` - // Estimated: `4326` - // Minimum execution time: 125_253_000 picoseconds. - Weight::from_parts(128_238_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(10)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nfts.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nfts.rs deleted file mode 100644 index 2de706bbdc70087cb8fe8128a30644e6e1f2ce74..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_nfts.rs +++ /dev/null @@ -1,773 +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 . - -//! Autogenerated weights for `pallet_nfts` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_nfts -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_nfts`. -pub struct WeightInfo(PhantomData); -impl pallet_nfts::WeightInfo for WeightInfo { - /// Storage: `Nfts::NextCollectionId` (r:1 w:1) - /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `179` - // Estimated: `3549` - // Minimum execution time: 39_124_000 picoseconds. - Weight::from_parts(39_975_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::NextCollectionId` (r:1 w:1) - /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `3549` - // Minimum execution time: 23_444_000 picoseconds. - Weight::from_parts(23_857_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1001 w:1000) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// The range of component `m` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` - // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_224_365_000 picoseconds. - Weight::from_parts(1_281_136_346, 0) - .saturating_add(Weight::from_parts(0, 2523990)) - // Standard Error: 10_484 - .saturating_add(Weight::from_parts(6_910_740, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1005)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(a.into())) - } - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `455` - // Estimated: `4326` - // Minimum execution time: 50_489_000 picoseconds. - Weight::from_parts(51_045_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn force_mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `455` - // Estimated: `4326` - // Minimum execution time: 49_146_000 picoseconds. - Weight::from_parts(49_756_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `564` - // Estimated: `4326` - // Minimum execution time: 56_059_000 picoseconds. - Weight::from_parts(57_162_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(7)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:2) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 42_406_000 picoseconds. - Weight::from_parts(43_187_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:5000 w:5000) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// The range of component `i` is `[0, 5000]`. - fn redeposit(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` - // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 16_960_000 picoseconds. - Weight::from_parts(17_167_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - // Standard Error: 24_110 - .saturating_add(Weight::from_parts(18_046_970, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) - .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn lock_item_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `435` - // Estimated: `3534` - // Minimum execution time: 21_023_000 picoseconds. - Weight::from_parts(21_409_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn unlock_item_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `435` - // Estimated: `3534` - // Minimum execution time: 20_706_000 picoseconds. - Weight::from_parts(21_030_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn lock_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `340` - // Estimated: `3549` - // Minimum execution time: 17_449_000 picoseconds. - Weight::from_parts(17_804_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:2) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `388` - // Estimated: `3549` - // Minimum execution time: 22_958_000 picoseconds. - Weight::from_parts(23_499_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:2 w:4) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `369` - // Estimated: `6078` - // Minimum execution time: 40_105_000 picoseconds. - Weight::from_parts(40_800_000, 0) - .saturating_add(Weight::from_parts(0, 6078)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:2) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_collection_owner() -> Weight { - // Proof Size summary in bytes: - // Measured: `311` - // Estimated: `3549` - // Minimum execution time: 17_832_000 picoseconds. - Weight::from_parts(18_297_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn force_collection_config() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `3549` - // Minimum execution time: 15_027_000 picoseconds. - Weight::from_parts(15_370_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn lock_item_properties() -> Weight { - // Proof Size summary in bytes: - // Measured: `435` - // Estimated: `3534` - // Minimum execution time: 19_912_000 picoseconds. - Weight::from_parts(20_258_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - fn set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `539` - // Estimated: `3944` - // Minimum execution time: 50_138_000 picoseconds. - Weight::from_parts(50_971_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - fn force_set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `344` - // Estimated: `3944` - // Minimum execution time: 26_385_000 picoseconds. - Weight::from_parts(27_086_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - fn clear_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `983` - // Estimated: `3944` - // Minimum execution time: 45_687_000 picoseconds. - Weight::from_parts(47_107_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - fn approve_item_attributes() -> Weight { - // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4466` - // Minimum execution time: 18_065_000 picoseconds. - Weight::from_parts(18_371_000, 0) - .saturating_add(Weight::from_parts(0, 4466)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1001 w:1000) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 1000]`. - fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `760 + n * (398 ±0)` - // Estimated: `4466 + n * (2954 ±0)` - // Minimum execution time: 26_680_000 picoseconds. - Weight::from_parts(27_010_000, 0) - .saturating_add(Weight::from_parts(0, 4466)) - // Standard Error: 6_351 - .saturating_add(Weight::from_parts(6_584_290, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - fn set_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `539` - // Estimated: `3812` - // Minimum execution time: 42_038_000 picoseconds. - Weight::from_parts(42_758_000, 0) - .saturating_add(Weight::from_parts(0, 3812)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `849` - // Estimated: `3812` - // Minimum execution time: 40_220_000 picoseconds. - Weight::from_parts(41_026_000, 0) - .saturating_add(Weight::from_parts(0, 3812)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - fn set_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `398` - // Estimated: `3759` - // Minimum execution time: 38_135_000 picoseconds. - Weight::from_parts(38_561_000, 0) - .saturating_add(Weight::from_parts(0, 3759)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - fn clear_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `716` - // Estimated: `3759` - // Minimum execution time: 37_583_000 picoseconds. - Weight::from_parts(38_215_000, 0) - .saturating_add(Weight::from_parts(0, 3759)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `410` - // Estimated: `4326` - // Minimum execution time: 21_405_000 picoseconds. - Weight::from_parts(21_803_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `418` - // Estimated: `4326` - // Minimum execution time: 18_713_000 picoseconds. - Weight::from_parts(19_185_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn clear_all_transfer_approvals() -> Weight { - // Proof Size summary in bytes: - // Measured: `418` - // Estimated: `4326` - // Minimum execution time: 17_803_000 picoseconds. - Weight::from_parts(18_270_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - fn set_accept_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `3517` - // Minimum execution time: 15_982_000 picoseconds. - Weight::from_parts(16_700_000, 0) - .saturating_add(Weight::from_parts(0, 3517)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - fn set_collection_max_supply() -> Weight { - // Proof Size summary in bytes: - // Measured: `340` - // Estimated: `3549` - // Minimum execution time: 19_501_000 picoseconds. - Weight::from_parts(19_785_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn update_mint_settings() -> Weight { - // Proof Size summary in bytes: - // Measured: `323` - // Estimated: `3538` - // Minimum execution time: 18_914_000 picoseconds. - Weight::from_parts(19_292_000, 0) - .saturating_add(Weight::from_parts(0, 3538)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn set_price() -> Weight { - // Proof Size summary in bytes: - // Measured: `518` - // Estimated: `4326` - // Minimum execution time: 24_625_000 picoseconds. - Weight::from_parts(25_257_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:1 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:2) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn buy_item() -> Weight { - // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_833_000 picoseconds. - Weight::from_parts(52_161_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// The range of component `n` is `[0, 10]`. - fn pay_tips(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_220_000 picoseconds. - Weight::from_parts(3_476_001, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7_084 - .saturating_add(Weight::from_parts(3_844_820, 0).saturating_mul(n.into())) - } - /// Storage: `Nfts::Item` (r:2 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn create_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `494` - // Estimated: `7662` - // Minimum execution time: 21_983_000 picoseconds. - Weight::from_parts(22_746_000, 0) - .saturating_add(Weight::from_parts(0, 7662)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::PendingSwapOf` (r:1 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn cancel_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `513` - // Estimated: `4326` - // Minimum execution time: 20_875_000 picoseconds. - Weight::from_parts(21_465_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:2 w:2) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:1 w:2) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:2 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:4) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn claim_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `834` - // Estimated: `7662` - // Minimum execution time: 84_771_000 picoseconds. - Weight::from_parts(86_078_000, 0) - .saturating_add(Weight::from_parts(0, 7662)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(10)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:10 w:10) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 10]`. - fn mint_pre_signed(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `558` - // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 143_265_000 picoseconds. - Weight::from_parts(150_978_773, 0) - .saturating_add(Weight::from_parts(0, 6078)) - // Standard Error: 49_443 - .saturating_add(Weight::from_parts(31_888_255, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:10 w:10) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 10]`. - fn set_attributes_pre_signed(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `588` - // Estimated: `4466 + n * (2954 ±0)` - // Minimum execution time: 83_754_000 picoseconds. - Weight::from_parts(96_685_026, 0) - .saturating_add(Weight::from_parts(0, 4466)) - // Standard Error: 72_592 - .saturating_add(Weight::from_parts(30_914_858, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_proxy.rs deleted file mode 100644 index 9bc4ba448e5af7d50988a319f62718ac486b689c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_proxy.rs +++ /dev/null @@ -1,226 +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 . - -//! Autogenerated weights for `pallet_proxy` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_proxy -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_proxy`. -pub struct WeightInfo(PhantomData); -impl pallet_proxy::WeightInfo for WeightInfo { - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 16_417_000 picoseconds. - Weight::from_parts(17_283_443, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_409 - .saturating_add(Weight::from_parts(32_123, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn proxy_announced(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `454 + a * (68 ±0) + p * (37 ±0)` - // Estimated: `5698` - // Minimum execution time: 37_572_000 picoseconds. - Weight::from_parts(37_045_756, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 2_896 - .saturating_add(Weight::from_parts(139_561, 0).saturating_mul(a.into())) - // Standard Error: 2_993 - .saturating_add(Weight::from_parts(73_270, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn remove_announcement(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `369 + a * (68 ±0)` - // Estimated: `5698` - // Minimum execution time: 24_066_000 picoseconds. - Weight::from_parts(24_711_403, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 1_626 - .saturating_add(Weight::from_parts(128_391, 0).saturating_mul(a.into())) - // Standard Error: 1_680 - .saturating_add(Weight::from_parts(23_124, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn reject_announcement(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `369 + a * (68 ±0)` - // Estimated: `5698` - // Minimum execution time: 24_162_000 picoseconds. - Weight::from_parts(23_928_058, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 2_072 - .saturating_add(Weight::from_parts(152_299, 0).saturating_mul(a.into())) - // Standard Error: 2_141 - .saturating_add(Weight::from_parts(39_775, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn announce(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `386 + a * (68 ±0) + p * (37 ±0)` - // Estimated: `5698` - // Minimum execution time: 33_858_000 picoseconds. - Weight::from_parts(33_568_059, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 1_816 - .saturating_add(Weight::from_parts(134_400, 0).saturating_mul(a.into())) - // Standard Error: 1_876 - .saturating_add(Weight::from_parts(57_028, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn add_proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 24_947_000 picoseconds. - Weight::from_parts(26_235_199, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 1_363 - .saturating_add(Weight::from_parts(41_435, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn remove_proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 25_186_000 picoseconds. - Weight::from_parts(26_823_133, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_259 - .saturating_add(Weight::from_parts(34_224, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn remove_proxies(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 22_156_000 picoseconds. - Weight::from_parts(23_304_060, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 1_738 - .saturating_add(Weight::from_parts(39_612, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn create_pure(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `139` - // Estimated: `4706` - // Minimum execution time: 26_914_000 picoseconds. - Weight::from_parts(28_009_062, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 1_978 - .saturating_add(Weight::from_parts(12_255, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[0, 30]`. - fn kill_pure(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `164 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 23_281_000 picoseconds. - Weight::from_parts(24_392_989, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_943 - .saturating_add(Weight::from_parts(30_287, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_uniques.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_uniques.rs deleted file mode 100644 index 43bc74931cb0e7c7ae8c90e8dafbac12df83669c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_uniques.rs +++ /dev/null @@ -1,467 +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 . - -//! Autogenerated weights for `pallet_uniques` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_uniques -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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_uniques`. -pub struct WeightInfo(PhantomData); -impl pallet_uniques::WeightInfo for WeightInfo { - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3643` - // Minimum execution time: 28_845_000 picoseconds. - Weight::from_parts(29_675_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3643` - // Minimum execution time: 13_492_000 picoseconds. - Weight::from_parts(14_049_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1001 w:1000) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1000 w:1000) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1000 w:1000) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:0 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1000) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::CollectionMaxSupply` (r:0 w:1) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `257 + a * (107 ±0) + m * (56 ±0) + n * (76 ±0)` - // Estimated: `3643 + a * (2647 ±0) + m * (2662 ±0) + n * (2597 ±0)` - // Minimum execution time: 2_920_070_000 picoseconds. - Weight::from_parts(2_983_862_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - // Standard Error: 36_415 - .saturating_add(Weight::from_parts(7_589_778, 0).saturating_mul(n.into())) - // Standard Error: 36_415 - .saturating_add(Weight::from_parts(479_496, 0).saturating_mul(m.into())) - // Standard Error: 36_415 - .saturating_add(Weight::from_parts(562_056, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2647).saturating_mul(a.into())) - .saturating_add(Weight::from_parts(0, 2662).saturating_mul(m.into())) - .saturating_add(Weight::from_parts(0, 2597).saturating_mul(n.into())) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::CollectionMaxSupply` (r:1 w:0) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 35_329_000 picoseconds. - Weight::from_parts(36_019_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 36_474_000 picoseconds. - Weight::from_parts(37_190_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:2) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 26_786_000 picoseconds. - Weight::from_parts(27_400_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:5000 w:5000) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// The range of component `i` is `[0, 5000]`. - fn redeposit(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `738 + i * (76 ±0)` - // Estimated: `3643 + i * (2597 ±0)` - // Minimum execution time: 14_546_000 picoseconds. - Weight::from_parts(14_831_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - // Standard Error: 24_362 - .saturating_add(Weight::from_parts(17_972_938, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) - .saturating_add(Weight::from_parts(0, 2597).saturating_mul(i.into())) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 18_919_000 picoseconds. - Weight::from_parts(19_547_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 18_643_000 picoseconds. - Weight::from_parts(19_000_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn freeze_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 13_530_000 picoseconds. - Weight::from_parts(14_165_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn thaw_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 13_523_000 picoseconds. - Weight::from_parts(14_055_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Uniques::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:2) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `356` - // Estimated: `3643` - // Minimum execution time: 22_131_000 picoseconds. - Weight::from_parts(22_628_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 13_841_000 picoseconds. - Weight::from_parts(14_408_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_item_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 16_954_000 picoseconds. - Weight::from_parts(17_482_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:0) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1 w:1) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - fn set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `559` - // Estimated: `3652` - // Minimum execution time: 38_493_000 picoseconds. - Weight::from_parts(39_513_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:0) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1 w:1) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - fn clear_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `756` - // Estimated: `3652` - // Minimum execution time: 37_918_000 picoseconds. - Weight::from_parts(38_666_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:1) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - fn set_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `348` - // Estimated: `3652` - // Minimum execution time: 29_810_000 picoseconds. - Weight::from_parts(30_363_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:1) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `559` - // Estimated: `3652` - // Minimum execution time: 30_877_000 picoseconds. - Weight::from_parts(31_430_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:1 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - fn set_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 30_478_000 picoseconds. - Weight::from_parts(31_065_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:1 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - fn clear_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `473` - // Estimated: `3643` - // Minimum execution time: 29_582_000 picoseconds. - Weight::from_parts(30_160_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 19_328_000 picoseconds. - Weight::from_parts(19_866_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `461` - // Estimated: `3643` - // Minimum execution time: 19_131_000 picoseconds. - Weight::from_parts(19_569_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Uniques::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - fn set_accept_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3517` - // Minimum execution time: 15_212_000 picoseconds. - Weight::from_parts(15_691_000, 0) - .saturating_add(Weight::from_parts(0, 3517)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::CollectionMaxSupply` (r:1 w:1) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn set_collection_max_supply() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 16_290_000 picoseconds. - Weight::from_parts(16_654_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:0) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn set_price() -> Weight { - // Proof Size summary in bytes: - // Measured: `259` - // Estimated: `3587` - // Minimum execution time: 16_095_000 picoseconds. - Weight::from_parts(16_555_000, 0) - .saturating_add(Weight::from_parts(0, 3587)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:1 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:2) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn buy_item() -> Weight { - // Proof Size summary in bytes: - // Measured: `540` - // Estimated: `3643` - // Minimum execution time: 35_506_000 picoseconds. - Weight::from_parts(36_305_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(4)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs deleted file mode 100644 index 3628641544b5bc006388dd8d0d074171606dbef5..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs +++ /dev/null @@ -1,641 +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. - -use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ParachainInfo, - ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, -}; -use crate::{ForeignAssets, CENTS}; -use assets_common::{ - local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, - matching::{FromSiblingParachain, IsForeignConcreteAsset}, -}; -use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, -}; -use frame_system::EnsureRoot; -use pallet_xcm::XcmPassthrough; -use parachains_common::{ - impls::ToStakingPot, - xcm_config::{AssetFeeAsExistentialDepositMultiplier, ConcreteAssetFromSystem}, -}; -use polkadot_parachain_primitives::primitives::Sibling; -use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use sp_runtime::traits::ConvertInto; -use xcm::latest::prelude::*; -use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, -}; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; - -#[cfg(feature = "runtime-benchmarks")] -use {cumulus_primitives_core::ParaId, sp_core::Get}; - -parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: Option = Some(NetworkId::Kusama); - pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - 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 ForeignAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); - pub PoolAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); - pub CheckingAccount: AccountId = PolkadotXcm::check_account(); - 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, - // Foreign locations alias into accounts according to a hash of their standard description. - HashedDescription>, -); - -/// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // 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 don't track any teleports of `Balances`. - (), ->; - -/// `AssetId`/`Balance` converter for `PoolAssets`. -pub type TrustBackedAssetsConvertedConcreteId = - assets_common::TrustBackedAssetsConvertedConcreteId; - -/// Means for transacting assets besides the native currency on this chain. -pub type FungiblesTransactor = FungiblesAdapter< - // Use this fungibles implementation: - Assets, - // Use this currency when it is a fungible asset matching the given location or name: - TrustBackedAssetsConvertedConcreteId, - // 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 only want to allow teleports of known assets. We use non-zero issuance as an indication - // that this asset is known. - LocalMint>, - // The account to use for tracking teleports. - CheckingAccount, ->; - -/// `AssetId/Balance` converter for `TrustBackedAssets` -pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId< - ( - // Ignore `TrustBackedAssets` explicitly - StartsWith, - // Ignore assets that start explicitly with our `GlobalConsensus(NetworkId)`, means: - // - foreign assets from our consensus should be: `MultiLocation {parents: 1, - // X*(Parachain(xyz), ..)}` - // - foreign assets outside our consensus with the same `GlobalConsensus(NetworkId)` won't - // be accepted here - StartsWithExplicitGlobalConsensus, - ), - 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, ->; - -/// `AssetId`/`Balance` converter for `PoolAssets`. -pub type PoolAssetsConvertedConcreteId = - assets_common::PoolAssetsConvertedConcreteId; - -/// Means for transacting asset conversion pool assets on this chain. -pub type PoolFungiblesTransactor = FungiblesAdapter< - // Use this fungibles implementation: - PoolAssets, - // Use this currency when it is a fungible asset matching the given location or name: - PoolAssetsConvertedConcreteId, - // 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 only want to allow teleports of known assets. We use non-zero issuance as an indication - // that this asset is known. - LocalMint>, - // The account to use for tracking teleports. - CheckingAccount, ->; - -/// Means for transacting assets on this chain. -pub type AssetTransactors = - (CurrencyTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor); - -/// Simple `MultiLocation` matcher for Local and Foreign asset `MultiLocation`. -pub struct LocalAndForeignAssetsMultiLocationMatcher; -impl MatchesLocalAndForeignAssetsMultiLocation for LocalAndForeignAssetsMultiLocationMatcher { - fn is_local(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - TrustBackedAssetsConvertedConcreteId::contains(location) - } - fn is_foreign(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - ForeignAssetsConvertedConcreteId::contains(location) - } -} -impl Contains for LocalAndForeignAssetsMultiLocationMatcher { - fn contains(location: &MultiLocation) -> bool { - Self::is_local(location) || Self::is_foreign(location) - } -} - -/// 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 -/// biases 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 which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when - // recognised. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognised. - 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, -); - -parameter_types! { - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; - pub XcmAssetFeesReceiver: Option = Authorship::author(); -} - -match_types! { - 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(_) } - }; -} - -/// 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::DmpQueue(..) | - RuntimeCall::Assets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::ForeignAssets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::PoolAssets( - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::block { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::touch_other { .. } | - pallet_assets::Call::refund { .. } | - pallet_assets::Call::refund_other { .. }, - ) | RuntimeCall::AssetConversion( - pallet_asset_conversion::Call::create_pool { .. } | - pallet_asset_conversion::Call::add_liquidity { .. } | - pallet_asset_conversion::Call::remove_liquidity { .. } | - pallet_asset_conversion::Call::swap_tokens_for_exact_tokens { .. } | - pallet_asset_conversion::Call::swap_exact_tokens_for_tokens { .. }, - ) | RuntimeCall::NftFractionalization( - pallet_nft_fractionalization::Call::fractionalize { .. } | - pallet_nft_fractionalization::Call::unify { .. }, - ) | RuntimeCall::Nfts( - pallet_nfts::Call::create { .. } | - pallet_nfts::Call::force_create { .. } | - pallet_nfts::Call::destroy { .. } | - pallet_nfts::Call::mint { .. } | - pallet_nfts::Call::force_mint { .. } | - pallet_nfts::Call::burn { .. } | - pallet_nfts::Call::transfer { .. } | - pallet_nfts::Call::lock_item_transfer { .. } | - pallet_nfts::Call::unlock_item_transfer { .. } | - pallet_nfts::Call::lock_collection { .. } | - pallet_nfts::Call::transfer_ownership { .. } | - pallet_nfts::Call::set_team { .. } | - pallet_nfts::Call::force_collection_owner { .. } | - pallet_nfts::Call::force_collection_config { .. } | - pallet_nfts::Call::approve_transfer { .. } | - pallet_nfts::Call::cancel_approval { .. } | - pallet_nfts::Call::clear_all_transfer_approvals { .. } | - pallet_nfts::Call::lock_item_properties { .. } | - pallet_nfts::Call::set_attribute { .. } | - pallet_nfts::Call::force_set_attribute { .. } | - pallet_nfts::Call::clear_attribute { .. } | - pallet_nfts::Call::approve_item_attributes { .. } | - pallet_nfts::Call::cancel_item_attributes_approval { .. } | - pallet_nfts::Call::set_metadata { .. } | - pallet_nfts::Call::clear_metadata { .. } | - pallet_nfts::Call::set_collection_metadata { .. } | - pallet_nfts::Call::clear_collection_metadata { .. } | - pallet_nfts::Call::set_accept_ownership { .. } | - pallet_nfts::Call::set_collection_max_supply { .. } | - pallet_nfts::Call::update_mint_settings { .. } | - pallet_nfts::Call::set_price { .. } | - pallet_nfts::Call::buy_item { .. } | - pallet_nfts::Call::pay_tips { .. } | - pallet_nfts::Call::create_swap { .. } | - pallet_nfts::Call::cancel_swap { .. } | - pallet_nfts::Call::claim_swap { .. }, - ) | RuntimeCall::Uniques( - pallet_uniques::Call::create { .. } | - pallet_uniques::Call::force_create { .. } | - pallet_uniques::Call::destroy { .. } | - pallet_uniques::Call::mint { .. } | - pallet_uniques::Call::burn { .. } | - pallet_uniques::Call::transfer { .. } | - pallet_uniques::Call::freeze { .. } | - pallet_uniques::Call::thaw { .. } | - pallet_uniques::Call::freeze_collection { .. } | - pallet_uniques::Call::thaw_collection { .. } | - pallet_uniques::Call::transfer_ownership { .. } | - pallet_uniques::Call::set_team { .. } | - pallet_uniques::Call::approve_transfer { .. } | - pallet_uniques::Call::cancel_approval { .. } | - pallet_uniques::Call::force_item_status { .. } | - pallet_uniques::Call::set_attribute { .. } | - pallet_uniques::Call::clear_attribute { .. } | - pallet_uniques::Call::set_metadata { .. } | - pallet_uniques::Call::clear_metadata { .. } | - pallet_uniques::Call::set_collection_metadata { .. } | - pallet_uniques::Call::clear_collection_metadata { .. } | - pallet_uniques::Call::set_accept_ownership { .. } | - pallet_uniques::Call::set_collection_max_supply { .. } | - pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. } - ) - ) - } -} - -pub type Barrier = TrailingSetTopicAsId< - DenyThenTry< - DenyReserveTransferToRelayChain, - ( - TakeWeightCredit, - // Expected responses are OK. - AllowKnownQueryResponses, - // Allow XCMs with some computed origins to pass through. - WithComputedOrigin< - ( - // If the message is one that immediately attempts 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>, - >, - ), - >, ->; - -pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier< - Runtime, - WeightToFee, - pallet_assets::BalanceToAssetBalance, - TrustBackedAssetsInstance, ->; - -/// Cases where a remote origin is accepted as trusted Teleporter for a given asset: -/// -/// - KSM with the parent Relay Chain and sibling system parachains; and -/// - Sibling parachains' assets from where they originate (as `ForeignCreators`). -pub type TrustedTeleporters = ( - ConcreteAssetFromSystem, - IsForeignConcreteAsset>>, -); - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type AssetTransactor = AssetTransactors; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub Kusama does not recognize a reserve location for any asset. This does not prevent - // Asset Hub acting _as_ a reserve location for KSM and assets created under `pallet-assets`. - // For KSM, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); - type IsTeleporter = TrustedTeleporters; - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = WeightInfoBounds< - crate::weights::xcm::AssetHubKusamaXcmWeight, - RuntimeCall, - MaxInstructions, - >; - type Trader = ( - UsingComponents>, - cumulus_primitives_utility::TakeFirstAssetTrader< - AccountId, - AssetFeeAsExistentialDepositMultiplierFeeCharger, - TrustBackedAssetsConvertedConcreteId, - Assets, - cumulus_primitives_utility::XcmFeesTo32ByteAccount< - FungiblesTransactor, - AccountId, - XcmAssetFeesReceiver, - >, - >, - ); - type ResponseHandler = PolkadotXcm; - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; - type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - 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; - -parameter_types! { - /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(KsmLocation::get()); - /// The base fee for the message delivery fees. - pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); -} - -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, -)>; - -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - -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 and reserve transfers are - // allowed. - type XcmExecuteFilter = Nothing; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type Weigher = WeightInfoBounds< - crate::weights::xcm::AssetHubKusamaXcmWeight, - 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; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; - type AdminOrigin = EnsureRoot; - type MaxRemoteLockConsumers = ConstU32<0>; - type RemoteLockConsumerIdentifier = (); -} - -impl cumulus_pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; -} - -pub type ForeignCreatorsSovereignAccountOf = ( - SiblingParachainConvertsVia, - AccountId32Aliases, - ParentIsPreset, -); - -/// 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)) } - } -} - -#[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)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs deleted file mode 100644 index 7d49b56e461a0a3919e2130dbb6d84f6888349ad..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/tests/tests.rs +++ /dev/null @@ -1,634 +0,0 @@ -// This file is part of Cumulus. - -// 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 the Statemine (Kusama Assets Hub) chain. - -use asset_hub_kusama_runtime::xcm_config::{ - AssetFeeAsExistentialDepositMultiplierFeeCharger, KsmLocation, TrustBackedAssetsPalletLocation, -}; -pub use asset_hub_kusama_runtime::{ - xcm_config::{CheckingAccount, ForeignCreatorsSovereignAccountOf, XcmConfig}, - AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, -}; -use asset_test_utils::{CollatorSessionKeys, ExtBuilder}; -use codec::{Decode, Encode}; -use cumulus_primitives_utility::ChargeWeightInFungibles; -use frame_support::{ - assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, - weights::{Weight, WeightToFee as WeightToFeeT}, -}; -use parachains_common::{ - kusama::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, -}; -use sp_runtime::traits::MaybeEquivalence; -use xcm::latest::prelude::*; -use xcm_executor::traits::{Identity, JustTry, WeightTrader}; - -const ALICE: [u8; 32] = [1u8; 32]; -const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; - -type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; - -type RuntimeHelper = asset_test_utils::RuntimeHelper; - -fn collator_session_keys() -> CollatorSessionKeys { - CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - ) -} - -#[test] -fn test_asset_xcm_trader() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - let minimum_asset_balance = 3333333_u128; - let local_asset_id = 1; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // get asset id as multilocation - let asset_multilocation = - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - // Lets calculate amount needed - let asset_amount_needed = - AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( - local_asset_id, - bought, - ) - .expect("failed to compute"); - - // Lets pay with: asset_amount_needed + asset_amount_extra - let asset_amount_extra = 100_u128; - let asset: MultiAsset = - (asset_multilocation, asset_amount_needed + asset_amount_extra).into(); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Lets buy_weight and make sure buy_weight does not return an error - let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); - // Check whether a correct amount of unused assets is returned - assert_ok!( - unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into()) - ); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance + asset_amount_needed - ); - - // We also need to ensure the total supply increased - assert_eq!( - Assets::total_supply(local_asset_id), - minimum_asset_balance + asset_amount_needed - ); - }); -} - -#[test] -fn test_asset_xcm_trader_with_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - // lets calculate amount needed - let amount_bought = WeightToFee::weight_to_fee(&bought); - - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - - // Make sure buy_weight does not return an error - assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); - - // Make sure again buy_weight does return an error - // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` - // tuple chain, which cannot be called twice - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // We actually use half of the weight - let weight_used = bought / 2; - - // Make sure refurnd works. - let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); - - assert_eq!( - trader.refund_weight(bought - weight_used, &ctx), - Some((asset_multilocation, amount_refunded).into()) - ); - - // Drop trader - drop(trader); - - // We only should have paid for half of the bought weight - let fees_paid = WeightToFee::weight_to_fee(&weight_used); - - assert_eq!( - Assets::balance(1, AccountId::from(ALICE)), - ExistentialDeposit::get() + fees_paid - ); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); - }); -} - -#[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy small amount - let bought = Weight::from_parts(500_000_000u64, 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - - // Buy weight should return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // not credited since the ED is higher than this value - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); - - // We also need to ensure the total supply did not increase - assert_eq!(Assets::total_supply(1), 0); - }); -} - -#[test] -fn test_that_buying_ed_refund_does_not_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are gonna buy ED - let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - // We know we will have to buy at least ED, so lets make sure first it will - // fail with a payment of less than ED - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Now lets buy ED at least - let asset: MultiAsset = (asset_multilocation, ExistentialDeposit::get()).into(); - - // Buy weight should work - assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); - - // Should return None. We have a specific check making sure we dont go below ED for - // drop payment - assert_eq!(trader.refund_weight(bought, &ctx), None); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); - }); -} - -#[test] -fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // Create a non-sufficient asset with specific existential deposit - let minimum_asset_balance = 1_000_000_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - false, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 4e9 weight - let bought = Weight::from_parts(4_000_000_000u64, 0); - - // lets calculate amount needed - let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let asset: MultiAsset = (asset_multilocation, asset_amount_needed).into(); - - // Make sure again buy_weight does return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has NOT received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); - - // We also need to ensure the total supply NOT increased - assert_eq!(Assets::total_supply(1), minimum_asset_balance); - }); -} - -#[test] -fn test_assets_balances_api_works() { - use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApi; - - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - let local_asset_id = 1; - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) }; - - // check before - assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); - assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), - 0 - ); - assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); - assert!(Runtime::query_account_balances(AccountId::from(ALICE)) - .unwrap() - .try_as::() - .unwrap() - .is_none()); - - // Drip some balance - use frame_support::traits::fungible::Mutate; - let some_currency = ExistentialDeposit::get(); - Balances::mint_into(&AccountId::from(ALICE), some_currency).unwrap(); - - // We need root origin to create a sufficient asset - let minimum_asset_balance = 3333333_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // create foreign asset - let foreign_asset_minimum_asset_balance = 3333333_u128; - assert_ok!(ForeignAssets::force_create( - RuntimeHelper::root_origin(), - foreign_asset_id_multilocation, - AccountId::from(SOME_ASSET_ADMIN).into(), - false, - foreign_asset_minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(ForeignAssets::mint( - RuntimeHelper::origin_of(AccountId::from(SOME_ASSET_ADMIN)), - foreign_asset_id_multilocation, - AccountId::from(ALICE).into(), - 6 * foreign_asset_minimum_asset_balance - )); - - // check after - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance - ); - assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), - 6 * minimum_asset_balance - ); - assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); - - let result: MultiAssets = Runtime::query_account_balances(AccountId::from(ALICE)) - .unwrap() - .try_into() - .unwrap(); - assert_eq!(result.len(), 3); - - // check currency - assert!(result.inner().iter().any(|asset| asset.eq( - &assets_common::fungible_conversion::convert_balance::( - some_currency - ) - .unwrap() - ))); - // check trusted asset - assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), - minimum_asset_balance - ) - .into()))); - // check foreign asset - assert!(result.inner().iter().any(|asset| asset.eq(&( - Identity::convert_back(&foreign_asset_id_multilocation).unwrap(), - 6 * foreign_asset_minimum_asset_balance - ) - .into()))); - }); -} - -asset_test_utils::include_teleports_for_native_asset_works!( - 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, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - 1000 -); - -asset_test_utils::include_teleports_for_foreign_assets_works!( - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - CheckingAccount, - WeightToFee, - ParachainSystem, - ForeignCreatorsSovereignAccountOf, - ForeignAssetsInstance, - 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, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_local_consensus_currency_works!( - Runtime, - XcmConfig, - collator_session_keys(), - ExistentialDeposit::get(), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!( - asset_transactor_transfer_with_trust_backed_assets_works, - Runtime, - XcmConfig, - TrustBackedAssetsInstance, - AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvert, - collator_session_keys(), - ExistentialDeposit::get(), - 12345, - Box::new(|| { - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!( - asset_transactor_transfer_with_foreign_assets_works, - Runtime, - XcmConfig, - ForeignAssetsInstance, - MultiLocation, - JustTry, - collator_session_keys(), - ExistentialDeposit::get(), - MultiLocation { parents: 1, interior: X2(Parachain(1313), GeneralIndex(12345)) }, - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works!( - Runtime, - XcmConfig, - WeightToFee, - ForeignCreatorsSovereignAccountOf, - ForeignAssetsInstance, - MultiLocation, - JustTry, - collator_session_keys(), - ExistentialDeposit::get(), - AssetDeposit::get(), - MetadataDepositBase::get(), - MetadataDepositPerByte::get(), - Box::new(|pallet_asset_call| RuntimeCall::ForeignAssets(pallet_asset_call).encode()), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::ForeignAssets(pallet_asset_event)) => Some(pallet_asset_event), - _ => None, - } - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert_eq!(ForeignAssets::asset_ids().collect::>().len(), 1); - }) -); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs deleted file mode 100644 index b58d094deec65ee40437d95c7ad0a6a96339c3c5..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs +++ /dev/null @@ -1,1313 +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. - -//! # Asset Hub Polkadot Runtime -//! -//! Asset Hub Polkadot is a parachain that provides an interface to create, manage, and use assets. -//! Assets may be fungible or non-fungible. -//! -//! ## Renaming -//! -//! This chain was originally known as "Statemint". You may see references to Statemint, Statemine, -//! and Westmint throughout the codebase. These are synonymous with "Asset Hub Polkadot, Kusama, and -//! Westend", respectively. -//! -//! ## Assets -//! -//! - Fungibles: Configuration of `pallet-assets`. -//! - Non-Fungibles (NFTs): Configuration of `pallet-uniques`. -//! -//! ## Other Functionality -//! -//! ### Native Balances -//! -//! Asset Hub Polkadot uses its parent DOT token as its native asset. -//! -//! ### Governance -//! -//! As a system parachain, Asset Hub defers its governance (namely, its `Root` origin), to its -//! Relay Chain parent, Polkadot. -//! -//! ### Collator Selection -//! -//! Asset Hub uses `pallet-collator-selection`, a simple first-come-first-served registration -//! system where collators can reserve a small bond to join the block producer set. There is no -//! slashing. -//! -//! ### XCM -//! -//! Because Asset Hub is fully under the control of the Relay Chain, it is meant to be a -//! `TrustedTeleporter`. It can also serve as a reserve location to other parachains for DOT as well -//! as other local assets. - -#![cfg_attr(not(feature = "std"), no_std)] -#![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 assets_common::{ - foreign_creators::ForeignCreators, matching::FromSiblingParachain, MultiLocationForAssetId, -}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; -use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Verify}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; - -use sp_std::prelude::*; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - construct_runtime, - dispatch::DispatchClass, - genesis_builder_helper::{build_config, create_default_config}, - parameter_types, - traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, - InstanceFilter, - }, - weights::{ConstantMultiplier, Weight}, - PalletId, -}; -use frame_system::{ - limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureSigned, -}; -use pallet_nfts::PalletFeatures; -pub use parachains_common as common; -use parachains_common::{ - impls::{AssetsToBlockAuthor, DealWithFees}, - polkadot::{consensus::*, currency::*, fee::WeightToFee}, - AccountId, AssetHubPolkadotAuraId as AuraId, AssetIdForTrustBackedAssets, Balance, BlockNumber, - Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, - NORMAL_DISPATCH_RATIO, SLOT_DURATION, -}; -use sp_runtime::RuntimeDebug; -use xcm_config::{ - DotLocation, FellowshipLocation, ForeignAssetsConvertedConcreteId, GovernanceLocation, - TrustBackedAssetsConvertedConcreteId, XcmConfig, 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 xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; - -use crate::xcm_config::ForeignCreatorsSovereignAccountOf; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - // Note: "statemint" is the legacy name for this chain. It has been renamed to - // "asset-hub-polkadot". Many wallets/tools depend on the `spec_name`, so it remains "statemint" - // for the time being. Wallets/tools should update to treat "asset-hub-polkadot" equally. - spec_name: create_runtime_str!("statemint"), - impl_name: create_runtime_str!("statemint"), - authoring_version: 1, - spec_version: 10000, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 13, - state_version: 0, -}; - -/// 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 = 0; -} - -// Configure FRAME pallets to include in runtime. -impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = RuntimeBlockWeights; - type BlockLength = RuntimeBlockLength; - type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; - type Nonce = Nonce; - type Hash = Hash; - type Hashing = BlakeTwo256; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type BlockHashCount = BlockHashCount; - type DbWeight = RocksDbWeight; - type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); - type AccountData = pallet_balances::AccountData; - type SystemWeightInfo = weights::frame_system::WeightInfo; - type SS58Prefix = SS58Prefix; - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - type MaxConsumers = frame_support::traits::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 MaxLocks = ConstU32<50>; - /// The type for recording an account's balance. - type Balance = Balance; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = weights::pallet_balances::WeightInfo; - type MaxReserves = ConstU32<50>; - type ReserveIdentifier = [u8; 8]; - 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 WeightToFee = WeightToFee; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; - type OperationalFeeMultiplier = ConstU8<5>; -} - -parameter_types! { - pub const AssetDeposit: Balance = 10 * UNITS; // 10 UNITS deposit to create fungible asset class - pub const AssetAccountDeposit: Balance = deposit(1, 16); - pub const ApprovalDeposit: Balance = EXISTENTIAL_DEPOSIT; - pub const AssetsStringLimit: u32 = 50; - /// Key = 32 bytes, Value = 36 bytes (32+1+1+1+1) - // https://github.com/paritytech/substrate/blob/069917b/frame/assets/src/lib.rs#L257L271 - pub const MetadataDepositBase: Balance = deposit(1, 68); - pub const MetadataDepositPerByte: Balance = deposit(0, 1); -} - -/// We allow root to execute privileged asset operations. -pub type AssetsForceOrigin = EnsureRoot; - -// Called "Trust Backed" assets because these are generally registered by some account, and users of -// the asset assume it has some claimed backing. The pallet is called `Assets` in -// `construct_runtime` to avoid breaking changes on storage reads. -pub type TrustBackedAssetsInstance = pallet_assets::Instance1; -type TrustBackedAssetsCall = pallet_assets::Call; -impl pallet_assets::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type AssetId = AssetIdForTrustBackedAssets; - type AssetIdParameter = codec::Compact; - type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = AssetsForceOrigin; - type AssetDeposit = AssetDeposit; - type MetadataDepositBase = MetadataDepositBase; - type MetadataDepositPerByte = MetadataDepositPerByte; - type ApprovalDeposit = ApprovalDeposit; - type StringLimit = AssetsStringLimit; - type Freezer = (); - type Extra = (); - type WeightInfo = weights::pallet_assets_local::WeightInfo; - type CallbackHandle = (); - type AssetAccountDeposit = AssetAccountDeposit; - type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; - #[cfg(feature = "runtime-benchmarks")] - 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(); -} - -/// Assets managed by some foreign location. Note: we do not declare a `ForeignAssetsCall` type, as -/// this type is used in proxy definitions. We assume that a foreign location would not want to set -/// an individual, local account as a proxy for the issuance of their assets. This issuance should -/// be managed by the foreign location's governance. -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 = ForeignCreators< - (FromSiblingParachain>,), - ForeignCreatorsSovereignAccountOf, - AccountId, - >; - type ForceOrigin = AssetsForceOrigin; - type AssetDeposit = ForeignAssetsAssetDeposit; - type MetadataDepositBase = ForeignAssetsMetadataDepositBase; - type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; - type ApprovalDeposit = ForeignAssetsApprovalDeposit; - type StringLimit = ForeignAssetsAssetsStringLimit; - type Freezer = (); - type Extra = (); - type WeightInfo = weights::pallet_assets_foreign::WeightInfo; - type CallbackHandle = (); - type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; - type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; -} - -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); - pub const MaxSignatories: u32 = 100; -} - -impl pallet_multisig::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type DepositBase = DepositBase; - type DepositFactor = DepositFactor; - type MaxSignatories = MaxSignatories; - 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; -} - -parameter_types! { - // One storage item; key size 32, value size 8; . - pub const ProxyDepositBase: Balance = deposit(1, 40); - // Additional storage item size of 33 bytes. - pub const ProxyDepositFactor: Balance = deposit(0, 33); - pub const MaxProxies: u16 = 32; - // One storage item; key size 32, value size 16 - pub const AnnouncementDepositBase: Balance = deposit(1, 48); - pub const AnnouncementDepositFactor: Balance = deposit(0, 66); - pub const MaxPending: u16 = 32; -} - -/// The type used to represent the kinds of proxying allowed. -#[derive( - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Encode, - Decode, - RuntimeDebug, - MaxEncodedLen, - scale_info::TypeInfo, -)] -pub enum ProxyType { - /// Fully permissioned proxy. Can execute any call on behalf of _proxied_. - Any, - /// Can execute any call that does not transfer funds or assets. - NonTransfer, - /// Proxy with the ability to reject time-delay proxy announcements. - CancelProxy, - /// Assets proxy. Can execute any call from `assets`, **including asset transfers**. - Assets, - /// Owner proxy. Can execute calls related to asset ownership. - AssetOwner, - /// Asset manager. Can execute calls related to asset management. - AssetManager, - /// Collator selection proxy. Can execute calls related to collator selection mechanism. - Collator, -} -impl Default for ProxyType { - fn default() -> Self { - Self::Any - } -} - -impl InstanceFilter for ProxyType { - fn filter(&self, c: &RuntimeCall) -> bool { - match self { - ProxyType::Any => true, - ProxyType::NonTransfer => !matches!( - c, - RuntimeCall::Balances { .. } | - RuntimeCall::Assets { .. } | - RuntimeCall::Nfts { .. } | - RuntimeCall::Uniques { .. } - ), - ProxyType::CancelProxy => matches!( - c, - RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::Assets => { - matches!( - c, - RuntimeCall::Assets { .. } | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } - ) - }, - ProxyType::AssetOwner => matches!( - c, - RuntimeCall::Assets(TrustBackedAssetsCall::create { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::start_destroy { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::destroy_accounts { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::destroy_approvals { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::finish_destroy { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::transfer_ownership { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_team { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::transfer_ownership { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_team { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_max_supply { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_collection { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::create { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::destroy { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::transfer_ownership { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_team { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_attribute { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_attribute { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::clear_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::set_collection_max_supply { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::AssetManager => matches!( - c, - RuntimeCall::Assets(TrustBackedAssetsCall::mint { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::burn { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::freeze { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::block { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::thaw { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::freeze_asset { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) | - RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_attributes_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::unlock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_properties { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_collection_metadata { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::mint { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::burn { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::freeze { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::thaw { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::freeze_collection { .. }) | - RuntimeCall::Uniques(pallet_uniques::Call::thaw_collection { .. }) | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - ProxyType::Collator => matches!( - c, - RuntimeCall::CollatorSelection { .. } | - RuntimeCall::Utility { .. } | - RuntimeCall::Multisig { .. } - ), - } - } - - fn is_superset(&self, o: &Self) -> bool { - match (self, o) { - (x, y) if x == y => true, - (ProxyType::Any, _) => true, - (_, ProxyType::Any) => false, - (ProxyType::Assets, ProxyType::AssetOwner) => true, - (ProxyType::Assets, ProxyType::AssetManager) => true, - (ProxyType::NonTransfer, ProxyType::Collator) => true, - _ => false, - } - } -} - -impl pallet_proxy::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type ProxyType = ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; - type WeightInfo = weights::pallet_proxy::WeightInfo; - type MaxPending = MaxPending; - type CallHasher = BlakeTwo256; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; -} - -parameter_types! { - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); - type SelfParaId = parachain_info::Pallet; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type OutboundXcmpMessageSource = XcmpQueue; - type XcmpMessageHandler = XcmpQueue; - type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; -} - -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; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; - type ControllerOrigin = EitherOfDiverse< - EnsureRoot, - EnsureXcm>, - >; - type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; - type PriceForSiblingDelivery = NoPriceForMessageDelivery; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; -} - -parameter_types! { - 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; - type NextSessionRotation = pallet_session::PeriodicSessions; - 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 = Period; - type ValidatorId = ::AccountId; - type ValidatorIdOf = pallet_collator_selection::IdentityCollator; - type ValidatorRegistration = Session; - type WeightInfo = weights::pallet_collator_selection::WeightInfo; -} - -impl pallet_asset_tx_payment::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Fungibles = Assets; - type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< - pallet_assets::BalanceToAssetBalance< - Balances, - Runtime, - ConvertInto, - TrustBackedAssetsInstance, - >, - AssetsToBlockAuthor, - >; -} - -parameter_types! { - pub const UniquesCollectionDeposit: Balance = 10 * UNITS; // 10 UNIT deposit to create uniques class - pub const UniquesItemDeposit: Balance = UNITS / 100; // 1 / 100 UNIT deposit to create uniques instance - pub const UniquesMetadataDepositBase: Balance = deposit(1, 129); - pub const UniquesAttributeDepositBase: Balance = deposit(1, 0); - pub const UniquesDepositPerByte: Balance = deposit(0, 1); -} - -impl pallet_uniques::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; - type Currency = Balances; - type ForceOrigin = AssetsForceOrigin; - type CollectionDeposit = UniquesCollectionDeposit; - type ItemDeposit = UniquesItemDeposit; - type MetadataDepositBase = UniquesMetadataDepositBase; - type AttributeDepositBase = UniquesAttributeDepositBase; - type DepositPerByte = UniquesDepositPerByte; - type StringLimit = ConstU32<128>; - type KeyLimit = ConstU32<32>; // Max 32 bytes per key - type ValueLimit = ConstU32<64>; // Max 64 bytes per value - type WeightInfo = weights::pallet_uniques::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type Helper = (); - type CreateOrigin = AsEnsureOriginWithArg>; - type Locker = (); -} - -parameter_types! { - pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); - pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; - // re-use the Uniques deposits - pub const NftsCollectionDeposit: Balance = UniquesCollectionDeposit::get(); - pub const NftsItemDeposit: Balance = UniquesItemDeposit::get(); - pub const NftsMetadataDepositBase: Balance = UniquesMetadataDepositBase::get(); - pub const NftsAttributeDepositBase: Balance = UniquesAttributeDepositBase::get(); - pub const NftsDepositPerByte: Balance = UniquesDepositPerByte::get(); -} - -impl pallet_nfts::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; - type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = AssetsForceOrigin; - type Locker = (); - type CollectionDeposit = NftsCollectionDeposit; - type ItemDeposit = NftsItemDeposit; - type MetadataDepositBase = NftsMetadataDepositBase; - type AttributeDepositBase = NftsAttributeDepositBase; - type DepositPerByte = NftsDepositPerByte; - type StringLimit = ConstU32<256>; - type KeyLimit = ConstU32<64>; - type ValueLimit = ConstU32<256>; - type ApprovalsLimit = ConstU32<20>; - type ItemAttributesApprovalsLimit = ConstU32<30>; - type MaxTips = ConstU32<10>; - type MaxDeadlineDuration = NftsMaxDeadlineDuration; - type MaxAttributesPerCall = ConstU32<10>; - type Features = NftsPalletFeatures; - type OffchainSignature = Signature; - type OffchainPublic = ::Signer; - type WeightInfo = weights::pallet_nfts::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type Helper = (); -} - -// 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, - // RandomnessCollectiveFlip = 2 removed - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, - - // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event} = 12, - - // 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, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, - - // Handy utilities. - Utility: pallet_utility::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, - - // The main stage. - Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, - Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, - ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, - } -); - -/// The address format for describing accounts. -pub type Address = sp_runtime::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_asset_tx_payment::ChargeAssetTxPayment, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; -/// Migrations to apply on runtime upgrade. -pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); - -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, - Migrations, ->; - -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - -#[cfg(feature = "runtime-benchmarks")] -mod benches { - define_benchmarks!( - [frame_system, SystemBench::] - [pallet_assets, Local] - [pallet_assets, Foreign] - [pallet_balances, Balances] - [pallet_multisig, Multisig] - [pallet_nfts, Nfts] - [pallet_proxy, Proxy] - [pallet_session, SessionBench::] - [pallet_uniques, Uniques] - [pallet_utility, Utility] - [pallet_timestamp, Timestamp] - [pallet_collator_selection, CollatorSelection] - [cumulus_pallet_xcmp_queue, XcmpQueue] - // XCM - [pallet_xcm, PolkadotXcm] - // 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 assets_common::runtime_api::FungiblesApi< - Block, - AccountId, - > for Runtime - { - fn query_account_balances(account: AccountId) -> Result { - use assets_common::fungible_conversion::{convert, convert_balance}; - Ok([ - // collect pallet_balance - { - let balance = Balances::free_balance(account.clone()); - if balance > 0 { - vec![convert_balance::(balance)?] - } else { - vec![] - } - }, - // collect pallet_assets (TrustBackedAssets) - convert::<_, _, _, _, TrustBackedAssetsConvertedConcreteId>( - Assets::account_balances(account.clone()) - .iter() - .filter(|(_, balance)| balance > &0) - )?, - // collect pallet_assets (ForeignAssets) - convert::<_, _, _, _, ForeignAssetsConvertedConcreteId>( - ForeignAssets::account_balances(account) - .iter() - .filter(|(_, balance)| balance > &0) - )?, - // collect ... e.g. other tokens - ].concat().into()) - } - } - - 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; - - // 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::; - - // Benchmark files generated for `Assets/ForeignAssets` instances are by default - // `pallet_assets_assets.rs / pallet_assets_foreign_assets`, which is not really nice, - // so with this redefinition we can change names to nicer: - // `pallet_assets_local.rs / pallet_assets_foreign.rs`. - type Local = pallet_assets::Pallet::; - type Foreign = pallet_assets::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::{DotLocation, MaxAssetsIntoHolding}; - use pallet_xcm_benchmarks::asset_instance_from; - - parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( - xcm_config::DotLocation::get(), - ExistentialDeposit::get() - ).into()); - } - - impl pallet_xcm_benchmarks::Config for Runtime { - type XcmConfig = xcm_config::XcmConfig; - type AccountIdConverter = xcm_config::LocationToAccountId; - type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - XcmConfig, - ExistentialDepositMultiAsset, - xcm_config::PriceForParentDelivery, - >; - fn valid_destination() -> Result { - Ok(DotLocation::get()) - } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { - // A mix of fungible, non-fungible, and concrete assets. - let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; - let holding_fungibles = holding_non_fungibles - 1; - let fungibles_amount: u128 = 100; - let mut assets = (0..holding_fungibles) - .map(|i| { - MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), - fun: Fungible(fungibles_amount * i as u128), - } - }) - .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..holding_non_fungibles).map(|i| MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), - fun: NonFungible(asset_instance_from(i)), - })) - .collect::>(); - - assets.push(MultiAsset { - id: Concrete(DotLocation::get()), - fun: Fungible(1_000_000 * UNITS), - }); - assets.into() - } - } - - parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( - DotLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(DotLocation::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(DotLocation::get()), - fun: Fungible(UNITS), - } - } - } - - impl pallet_xcm_benchmarks::generic::Config for Runtime { - type TransactAsset = Balances; - type RuntimeCall = RuntimeCall; - - 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((DotLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) - } - - fn subscribe_origin() -> Result { - Ok(DotLocation::get()) - } - - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { - let origin = DotLocation::get(); - let assets: MultiAssets = (Concrete(DotLocation::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::; - - type Local = pallet_assets::Pallet::; - type Foreign = pallet_assets::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(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").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::, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{CENTS, MILLICENTS}; - use parachains_common::polkadot::fee; - use sp_runtime::traits::Zero; - use sp_weights::WeightToFee; - - /// We can fit at least 1000 transfers in a block. - #[test] - fn sane_block_weight() { - use pallet_balances::WeightInfo; - let block = RuntimeBlockWeights::get().max_block; - let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; - let transfer = - base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); - - let fit = block.checked_div_per_component(&transfer).unwrap_or_default(); - assert!(fit >= 1000, "{} should be at least 1000", fit); - } - - /// The fee for one transfer is at most 1 CENT. - #[test] - fn sane_transfer_fee() { - use pallet_balances::WeightInfo; - let base = RuntimeBlockWeights::get().get(DispatchClass::Normal).base_extrinsic; - let transfer = - base + weights::pallet_balances::WeightInfo::::transfer_allow_death(); - - let fee: Balance = fee::WeightToFee::weight_to_fee(&transfer); - assert!(fee <= CENTS, "{} MILLICENTS should be at most 1000", fee / MILLICENTS); - } - - /// Weight is being charged for both dimensions. - #[test] - fn weight_charged_for_both_components() { - let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(10_000, 0)); - assert!(!fee.is_zero(), "Charges for ref time"); - - let fee: Balance = fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, 10_000)); - assert_eq!(fee, CENTS, "10kb maps to CENT"); - } - - /// Filling up a block by proof size is at most 30 times more expensive than ref time. - /// - /// This is just a sanity check. - #[test] - fn full_block_fee_ratio() { - let block = RuntimeBlockWeights::get().max_block; - let time_fee: Balance = - fee::WeightToFee::weight_to_fee(&Weight::from_parts(block.ref_time(), 0)); - let proof_fee: Balance = - fee::WeightToFee::weight_to_fee(&Weight::from_parts(0, block.proof_size())); - - let proof_o_time = proof_fee.checked_div(time_fee).unwrap_or_default(); - assert!(proof_o_time <= 30, "{} should be at most 30", proof_o_time); - let time_o_proof = time_fee.checked_div(proof_fee).unwrap_or_default(); - assert!(time_o_proof <= 30, "{} should be at most 30", time_o_proof); - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs deleted file mode 100644 index 65844ce194a0e66415179919a444eacbf6cccbe0..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs +++ /dev/null @@ -1,77 +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 . - -//! Autogenerated weights for `cumulus_pallet_xcmp_queue` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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: 5_240_000 picoseconds. - Weight::from_parts(5_487_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:1) - /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 5_243_000 picoseconds. - Weight::from_parts(5_549_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs deleted file mode 100644 index 3eb3b925e651836446a7ea771cb20f9b9444594d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/mod.rs +++ /dev/null @@ -1,40 +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 . - -pub mod block_weights; -pub mod cumulus_pallet_xcmp_queue; -pub mod extrinsic_weights; -pub mod frame_system; -pub mod pallet_assets_foreign; -pub mod pallet_assets_local; -pub mod pallet_balances; -pub mod pallet_collator_selection; -pub mod pallet_multisig; -pub mod pallet_nfts; -pub mod pallet_proxy; -pub mod pallet_session; -pub mod pallet_timestamp; -pub mod pallet_uniques; -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 paritydb_weights::constants::ParityDbWeight; -pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_foreign.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_foreign.rs deleted file mode 100644 index 51413bb471b953b6724def2d8d009532425c02da..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_foreign.rs +++ /dev/null @@ -1,533 +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 . - -//! Autogenerated weights for `pallet_assets` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_assets -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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_assets`. -pub struct WeightInfo(PhantomData); -impl pallet_assets::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: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `107` - // Estimated: `4273` - // Minimum execution time: 29_979_000 picoseconds. - Weight::from_parts(30_763_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `4` - // Estimated: `4273` - // Minimum execution time: 12_255_000 picoseconds. - Weight::from_parts(12_614_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn start_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 15_240_000 picoseconds. - Weight::from_parts(15_627_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// 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:1001 w:1000) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1000 w:1000) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - fn destroy_accounts(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + c * (208 ±0)` - // Estimated: `4273 + c * (3207 ±0)` - // Minimum execution time: 17_814_000 picoseconds. - Weight::from_parts(18_006_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 10_358 - .saturating_add(Weight::from_parts(15_409_972, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 3207).saturating_mul(c.into())) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1001 w:1000) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy_approvals(a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `413 + a * (86 ±0)` - // Estimated: `4273 + a * (3221 ±0)` - // Minimum execution time: 18_957_000 picoseconds. - Weight::from_parts(19_347_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 5_051 - .saturating_add(Weight::from_parts(15_416_931, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 3221).saturating_mul(a.into())) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:0) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn finish_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_409_000 picoseconds. - Weight::from_parts(15_835_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// 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:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 26_753_000 picoseconds. - Weight::from_parts(27_349_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// 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:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 33_918_000 picoseconds. - Weight::from_parts(34_624_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// 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`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 45_863_000 picoseconds. - Weight::from_parts(46_674_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// 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`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 40_592_000 picoseconds. - Weight::from_parts(41_582_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// 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`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `7404` - // Minimum execution time: 46_170_000 picoseconds. - Weight::from_parts(46_880_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 18_421_000 picoseconds. - Weight::from_parts(19_003_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 18_009_000 picoseconds. - Weight::from_parts(18_683_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn freeze_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 14_702_000 picoseconds. - Weight::from_parts(15_118_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn thaw_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 14_329_000 picoseconds. - Weight::from_parts(14_857_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:0) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_776_000 picoseconds. - Weight::from_parts(16_337_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 14_290_000 picoseconds. - Weight::from_parts(14_655_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn set_metadata(_n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 29_296_000 picoseconds. - Weight::from_parts(30_512_261, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 474 - .saturating_add(Weight::from_parts(530, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `406` - // Estimated: `4273` - // Minimum execution time: 30_342_000 picoseconds. - Weight::from_parts(31_030_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `81` - // Estimated: `4273` - // Minimum execution time: 13_574_000 picoseconds. - Weight::from_parts(14_181_016, 0) - .saturating_add(Weight::from_parts(0, 4273)) - // Standard Error: 262 - .saturating_add(Weight::from_parts(420, 0).saturating_mul(n.into())) - // Standard Error: 262 - .saturating_add(Weight::from_parts(1_118, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Metadata` (r:1 w:1) - /// Proof: `ForeignAssets::Metadata` (`max_values`: None, `max_size`: Some(738), added: 3213, mode: `MaxEncodedLen`) - fn force_clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `406` - // Estimated: `4273` - // Minimum execution time: 29_679_000 picoseconds. - Weight::from_parts(30_346_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn force_asset_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 13_334_000 picoseconds. - Weight::from_parts(13_827_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `276` - // Estimated: `4273` - // Minimum execution time: 32_648_000 picoseconds. - Weight::from_parts(33_555_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, 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`) - fn transfer_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `520` - // Estimated: `7404` - // Minimum execution time: 65_431_000 picoseconds. - Weight::from_parts(66_502_000, 0) - .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `446` - // Estimated: `4273` - // Minimum execution time: 35_207_000 picoseconds. - Weight::from_parts(35_915_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Approvals` (r:1 w:1) - /// Proof: `ForeignAssets::Approvals` (`max_values`: None, `max_size`: Some(746), added: 3221, mode: `MaxEncodedLen`) - fn force_cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `446` - // Estimated: `4273` - // Minimum execution time: 35_768_000 picoseconds. - Weight::from_parts(36_553_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn set_min_balance() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 15_108_000 picoseconds. - Weight::from_parts(15_556_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn touch() -> Weight { - // Proof Size summary in bytes: - // Measured: `345` - // Estimated: `4273` - // Minimum execution time: 34_373_000 picoseconds. - Weight::from_parts(35_200_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn touch_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `4273` - // Minimum execution time: 32_201_000 picoseconds. - Weight::from_parts(33_591_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn refund() -> Weight { - // Proof Size summary in bytes: - // Measured: `471` - // Estimated: `4273` - // Minimum execution time: 31_148_000 picoseconds. - Weight::from_parts(31_751_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Asset` (r:1 w:1) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - fn refund_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `4273` - // Minimum execution time: 29_127_000 picoseconds. - Weight::from_parts(29_922_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ForeignAssets::Asset` (r:1 w:0) - /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) - /// Storage: `ForeignAssets::Account` (r:1 w:1) - /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn block() -> Weight { - // Proof Size summary in bytes: - // Measured: `350` - // Estimated: `4273` - // Minimum execution time: 18_386_000 picoseconds. - Weight::from_parts(18_762_000, 0) - .saturating_add(Weight::from_parts(0, 4273)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_local.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_local.rs deleted file mode 100644 index c8420e72ba213fda9f11754552cf05fb84a530d4..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_assets_local.rs +++ /dev/null @@ -1,529 +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 . - -//! Autogenerated weights for `pallet_assets` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_assets -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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_assets`. -pub struct WeightInfo(PhantomData); -impl pallet_assets::WeightInfo for WeightInfo { - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3675` - // Minimum execution time: 26_698_000 picoseconds. - Weight::from_parts(27_507_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `3675` - // Minimum execution time: 10_833_000 picoseconds. - Weight::from_parts(11_314_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn start_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_389_000 picoseconds. - Weight::from_parts(14_231_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1001 w:1000) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1000 w:1000) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `c` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - fn destroy_accounts(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + c * (208 ±0)` - // Estimated: `3675 + c * (2609 ±0)` - // Minimum execution time: 16_027_000 picoseconds. - Weight::from_parts(16_455_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 10_266 - .saturating_add(Weight::from_parts(15_263_742, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1001 w:1000) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy_approvals(a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `414 + a * (86 ±0)` - // Estimated: `3675 + a * (2623 ±0)` - // Minimum execution time: 17_167_000 picoseconds. - Weight::from_parts(17_397_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 5_072 - .saturating_add(Weight::from_parts(15_429_203, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:0) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn finish_destroy() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 13_694_000 picoseconds. - Weight::from_parts(14_239_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 24_406_000 picoseconds. - Weight::from_parts(24_981_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 31_372_000 picoseconds. - Weight::from_parts(32_021_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 42_982_000 picoseconds. - Weight::from_parts(43_918_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 37_161_000 picoseconds. - Weight::from_parts(38_756_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `6208` - // Minimum execution time: 43_141_000 picoseconds. - Weight::from_parts(44_187_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 16_721_000 picoseconds. - Weight::from_parts(17_433_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 16_623_000 picoseconds. - Weight::from_parts(17_110_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn freeze_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_079_000 picoseconds. - Weight::from_parts(13_700_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn thaw_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 13_026_000 picoseconds. - Weight::from_parts(13_444_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:0) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 13_945_000 picoseconds. - Weight::from_parts(14_792_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 12_800_000 picoseconds. - Weight::from_parts(13_183_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn set_metadata(_n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 27_637_000 picoseconds. - Weight::from_parts(28_967_060, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 464 - .saturating_add(Weight::from_parts(572, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `407` - // Estimated: `3675` - // Minimum execution time: 28_427_000 picoseconds. - Weight::from_parts(28_961_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - /// The range of component `n` is `[0, 50]`. - /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(_n: u32, s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `82` - // Estimated: `3675` - // Minimum execution time: 12_251_000 picoseconds. - Weight::from_parts(12_928_907, 0) - .saturating_add(Weight::from_parts(0, 3675)) - // Standard Error: 244 - .saturating_add(Weight::from_parts(1_800, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Metadata` (r:1 w:1) - /// Proof: `Assets::Metadata` (`max_values`: None, `max_size`: Some(140), added: 2615, mode: `MaxEncodedLen`) - fn force_clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `407` - // Estimated: `3675` - // Minimum execution time: 28_263_000 picoseconds. - Weight::from_parts(29_165_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn force_asset_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 12_343_000 picoseconds. - Weight::from_parts(12_659_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3675` - // Minimum execution time: 31_113_000 picoseconds. - Weight::from_parts(31_798_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `521` - // Estimated: `6208` - // Minimum execution time: 61_428_000 picoseconds. - Weight::from_parts(62_707_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3675` - // Minimum execution time: 33_538_000 picoseconds. - Weight::from_parts(34_216_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Approvals` (r:1 w:1) - /// Proof: `Assets::Approvals` (`max_values`: None, `max_size`: Some(148), added: 2623, mode: `MaxEncodedLen`) - fn force_cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3675` - // Minimum execution time: 33_870_000 picoseconds. - Weight::from_parts(34_709_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn set_min_balance() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 13_358_000 picoseconds. - Weight::from_parts(13_735_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn touch() -> Weight { - // Proof Size summary in bytes: - // Measured: `346` - // Estimated: `3675` - // Minimum execution time: 32_159_000 picoseconds. - Weight::from_parts(32_998_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn touch_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `3675` - // Minimum execution time: 30_709_000 picoseconds. - Weight::from_parts(31_486_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn refund() -> Weight { - // Proof Size summary in bytes: - // Measured: `472` - // Estimated: `3675` - // Minimum execution time: 29_557_000 picoseconds. - Weight::from_parts(30_510_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - fn refund_other() -> Weight { - // Proof Size summary in bytes: - // Measured: `402` - // Estimated: `3675` - // Minimum execution time: 28_027_000 picoseconds. - Weight::from_parts(28_865_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Assets::Asset` (r:1 w:0) - /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) - /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn block() -> Weight { - // Proof Size summary in bytes: - // Measured: `351` - // Estimated: `3675` - // Minimum execution time: 16_758_000 picoseconds. - Weight::from_parts(17_280_000, 0) - .saturating_add(Weight::from_parts(0, 3675)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_nfts.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_nfts.rs deleted file mode 100644 index 6d6f7cbbafb44c31b55def7c36a731aee984bf40..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_nfts.rs +++ /dev/null @@ -1,773 +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 . - -//! Autogenerated weights for `pallet_nfts` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_nfts -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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_nfts`. -pub struct WeightInfo(PhantomData); -impl pallet_nfts::WeightInfo for WeightInfo { - /// Storage: `Nfts::NextCollectionId` (r:1 w:1) - /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3549` - // Minimum execution time: 37_915_000 picoseconds. - Weight::from_parts(39_275_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::NextCollectionId` (r:1 w:1) - /// Proof: `Nfts::NextCollectionId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:0 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3549` - // Minimum execution time: 22_722_000 picoseconds. - Weight::from_parts(23_500_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1001 w:1000) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1000 w:1000) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:0 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:1) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// The range of component `m` is `[0, 1000]`. - /// The range of component `c` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `32170 + a * (366 ±0)` - // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_231_520_000 picoseconds. - Weight::from_parts(1_228_960_098, 0) - .saturating_add(Weight::from_parts(0, 2523990)) - // Standard Error: 8_836 - .saturating_add(Weight::from_parts(6_818_975, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1005)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(a.into())) - } - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `421` - // Estimated: `4326` - // Minimum execution time: 48_581_000 picoseconds. - Weight::from_parts(50_020_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn force_mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `421` - // Estimated: `4326` - // Minimum execution time: 47_171_000 picoseconds. - Weight::from_parts(48_084_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `530` - // Estimated: `4326` - // Minimum execution time: 53_591_000 picoseconds. - Weight::from_parts(55_074_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(7)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:2) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `559` - // Estimated: `4326` - // Minimum execution time: 40_935_000 picoseconds. - Weight::from_parts(41_835_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:5000 w:5000) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// The range of component `i` is `[0, 5000]`. - fn redeposit(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `729 + i * (108 ±0)` - // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 16_543_000 picoseconds. - Weight::from_parts(16_769_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - // Standard Error: 23_638 - .saturating_add(Weight::from_parts(17_762_895, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) - .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn lock_item_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `3534` - // Minimum execution time: 20_446_000 picoseconds. - Weight::from_parts(20_740_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn unlock_item_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `3534` - // Minimum execution time: 20_088_000 picoseconds. - Weight::from_parts(20_627_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn lock_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `306` - // Estimated: `3549` - // Minimum execution time: 17_036_000 picoseconds. - Weight::from_parts(17_435_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:2) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `354` - // Estimated: `3549` - // Minimum execution time: 22_528_000 picoseconds. - Weight::from_parts(23_047_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:2 w:4) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `335` - // Estimated: `6078` - // Minimum execution time: 38_473_000 picoseconds. - Weight::from_parts(39_353_000, 0) - .saturating_add(Weight::from_parts(0, 6078)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionAccount` (r:0 w:2) - /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_collection_owner() -> Weight { - // Proof Size summary in bytes: - // Measured: `277` - // Estimated: `3549` - // Minimum execution time: 17_708_000 picoseconds. - Weight::from_parts(18_022_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:0 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn force_collection_config() -> Weight { - // Proof Size summary in bytes: - // Measured: `242` - // Estimated: `3549` - // Minimum execution time: 14_606_000 picoseconds. - Weight::from_parts(14_891_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn lock_item_properties() -> Weight { - // Proof Size summary in bytes: - // Measured: `401` - // Estimated: `3534` - // Minimum execution time: 19_492_000 picoseconds. - Weight::from_parts(19_919_000, 0) - .saturating_add(Weight::from_parts(0, 3534)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - fn set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `505` - // Estimated: `3944` - // Minimum execution time: 50_583_000 picoseconds. - Weight::from_parts(53_846_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - fn force_set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `310` - // Estimated: `3944` - // Minimum execution time: 25_937_000 picoseconds. - Weight::from_parts(26_540_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Attribute` (r:1 w:1) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - fn clear_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `949` - // Estimated: `3944` - // Minimum execution time: 45_738_000 picoseconds. - Weight::from_parts(46_468_000, 0) - .saturating_add(Weight::from_parts(0, 3944)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - fn approve_item_attributes() -> Weight { - // Proof Size summary in bytes: - // Measured: `347` - // Estimated: `4466` - // Minimum execution time: 17_361_000 picoseconds. - Weight::from_parts(18_191_000, 0) - .saturating_add(Weight::from_parts(0, 4466)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1001 w:1000) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 1000]`. - fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `726 + n * (398 ±0)` - // Estimated: `4466 + n * (2954 ±0)` - // Minimum execution time: 25_884_000 picoseconds. - Weight::from_parts(26_265_000, 0) - .saturating_add(Weight::from_parts(0, 4466)) - // Standard Error: 6_423 - .saturating_add(Weight::from_parts(6_507_369, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - fn set_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `505` - // Estimated: `3812` - // Minimum execution time: 40_802_000 picoseconds. - Weight::from_parts(41_742_000, 0) - .saturating_add(Weight::from_parts(0, 3812)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `815` - // Estimated: `3812` - // Minimum execution time: 38_904_000 picoseconds. - Weight::from_parts(39_919_000, 0) - .saturating_add(Weight::from_parts(0, 3812)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - fn set_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `364` - // Estimated: `3759` - // Minimum execution time: 37_012_000 picoseconds. - Weight::from_parts(37_632_000, 0) - .saturating_add(Weight::from_parts(0, 3759)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionMetadataOf` (r:1 w:1) - /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) - fn clear_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `682` - // Estimated: `3759` - // Minimum execution time: 36_243_000 picoseconds. - Weight::from_parts(37_313_000, 0) - .saturating_add(Weight::from_parts(0, 3759)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `376` - // Estimated: `4326` - // Minimum execution time: 20_919_000 picoseconds. - Weight::from_parts(21_505_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `384` - // Estimated: `4326` - // Minimum execution time: 18_943_000 picoseconds. - Weight::from_parts(19_969_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn clear_all_transfer_approvals() -> Weight { - // Proof Size summary in bytes: - // Measured: `384` - // Estimated: `4326` - // Minimum execution time: 17_320_000 picoseconds. - Weight::from_parts(18_071_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - fn set_accept_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3517` - // Minimum execution time: 14_934_000 picoseconds. - Weight::from_parts(15_422_000, 0) - .saturating_add(Weight::from_parts(0, 3517)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - fn set_collection_max_supply() -> Weight { - // Proof Size summary in bytes: - // Measured: `306` - // Estimated: `3549` - // Minimum execution time: 18_715_000 picoseconds. - Weight::from_parts(19_025_000, 0) - .saturating_add(Weight::from_parts(0, 3549)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:1) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - fn update_mint_settings() -> Weight { - // Proof Size summary in bytes: - // Measured: `289` - // Estimated: `3538` - // Minimum execution time: 18_249_000 picoseconds. - Weight::from_parts(18_826_000, 0) - .saturating_add(Weight::from_parts(0, 3538)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn set_price() -> Weight { - // Proof Size summary in bytes: - // Measured: `484` - // Estimated: `4326` - // Minimum execution time: 23_529_000 picoseconds. - Weight::from_parts(23_958_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:1 w:1) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:1 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:2) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn buy_item() -> Weight { - // Proof Size summary in bytes: - // Measured: `671` - // Estimated: `4326` - // Minimum execution time: 50_885_000 picoseconds. - Weight::from_parts(52_157_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// The range of component `n` is `[0, 10]`. - fn pay_tips(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_258_000 picoseconds. - Weight::from_parts(3_342_691, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 6_268 - .saturating_add(Weight::from_parts(3_761_373, 0).saturating_mul(n.into())) - } - /// Storage: `Nfts::Item` (r:2 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - fn create_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `460` - // Estimated: `7662` - // Minimum execution time: 21_220_000 picoseconds. - Weight::from_parts(21_654_000, 0) - .saturating_add(Weight::from_parts(0, 7662)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::PendingSwapOf` (r:1 w:1) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - fn cancel_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `479` - // Estimated: `4326` - // Minimum execution time: 20_430_000 picoseconds. - Weight::from_parts(21_038_000, 0) - .saturating_add(Weight::from_parts(0, 4326)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Nfts::Item` (r:2 w:2) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::PendingSwapOf` (r:1 w:2) - /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:0) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:2 w:0) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:4) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) - /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn claim_swap() -> Weight { - // Proof Size summary in bytes: - // Measured: `800` - // Estimated: `7662` - // Minimum execution time: 83_344_000 picoseconds. - Weight::from_parts(84_898_000, 0) - .saturating_add(Weight::from_parts(0, 7662)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(10)) - } - /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) - /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Item` (r:1 w:1) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) - /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:10 w:10) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemMetadataOf` (r:1 w:1) - /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Account` (r:0 w:1) - /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 10]`. - fn mint_pre_signed(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `524` - // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 143_435_000 picoseconds. - Weight::from_parts(151_744_537, 0) - .saturating_add(Weight::from_parts(0, 6078)) - // Standard Error: 44_459 - .saturating_add(Weight::from_parts(31_293_503, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } - /// Storage: `Nfts::Item` (r:1 w:0) - /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) - /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) - /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) - /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Collection` (r:1 w:1) - /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) - /// Storage: `Nfts::Attribute` (r:10 w:10) - /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 10]`. - fn set_attributes_pre_signed(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `554` - // Estimated: `4466 + n * (2954 ±0)` - // Minimum execution time: 84_627_000 picoseconds. - Weight::from_parts(96_076_065, 0) - .saturating_add(Weight::from_parts(0, 4466)) - // Standard Error: 62_058 - .saturating_add(Weight::from_parts(30_461_383, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_proxy.rs deleted file mode 100644 index 99db2865692fc6e0316285f4985a974d85dba766..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_proxy.rs +++ /dev/null @@ -1,226 +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 . - -//! Autogenerated weights for `pallet_proxy` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_proxy -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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_proxy`. -pub struct WeightInfo(PhantomData); -impl pallet_proxy::WeightInfo for WeightInfo { - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 16_130_000 picoseconds. - Weight::from_parts(16_649_312, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 761 - .saturating_add(Weight::from_parts(42_507, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn proxy_announced(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `454 + a * (68 ±0) + p * (37 ±0)` - // Estimated: `5698` - // Minimum execution time: 37_732_000 picoseconds. - Weight::from_parts(36_993_926, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 3_278 - .saturating_add(Weight::from_parts(144_955, 0).saturating_mul(a.into())) - // Standard Error: 3_387 - .saturating_add(Weight::from_parts(64_624, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn remove_announcement(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `369 + a * (68 ±0)` - // Estimated: `5698` - // Minimum execution time: 24_229_000 picoseconds. - Weight::from_parts(24_199_507, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 1_672 - .saturating_add(Weight::from_parts(124_324, 0).saturating_mul(a.into())) - // Standard Error: 1_727 - .saturating_add(Weight::from_parts(28_481, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn reject_announcement(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `369 + a * (68 ±0)` - // Estimated: `5698` - // Minimum execution time: 23_868_000 picoseconds. - Weight::from_parts(25_293_069, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 1_728 - .saturating_add(Weight::from_parts(114_080, 0).saturating_mul(a.into())) - // Standard Error: 1_786 - .saturating_add(Weight::from_parts(3_690, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Proxies` (r:1 w:0) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// Storage: `Proxy::Announcements` (r:1 w:1) - /// Proof: `Proxy::Announcements` (`max_values`: None, `max_size`: Some(2233), added: 4708, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `a` is `[0, 31]`. - /// The range of component `p` is `[1, 31]`. - fn announce(a: u32, p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `386 + a * (68 ±0) + p * (37 ±0)` - // Estimated: `5698` - // Minimum execution time: 34_343_000 picoseconds. - Weight::from_parts(34_539_112, 0) - .saturating_add(Weight::from_parts(0, 5698)) - // Standard Error: 1_917 - .saturating_add(Weight::from_parts(117_360, 0).saturating_mul(a.into())) - // Standard Error: 1_981 - .saturating_add(Weight::from_parts(40_908, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn add_proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 25_506_000 picoseconds. - Weight::from_parts(26_350_920, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_950 - .saturating_add(Weight::from_parts(48_972, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn remove_proxy(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 25_234_000 picoseconds. - Weight::from_parts(26_232_489, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_468 - .saturating_add(Weight::from_parts(48_955, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn remove_proxies(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `127 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 22_184_000 picoseconds. - Weight::from_parts(22_974_929, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_200 - .saturating_add(Weight::from_parts(45_741, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 31]`. - fn create_pure(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `139` - // Estimated: `4706` - // Minimum execution time: 27_044_000 picoseconds. - Weight::from_parts(27_978_605, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 2_206 - .saturating_add(Weight::from_parts(13_736, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Proxy::Proxies` (r:1 w:1) - /// Proof: `Proxy::Proxies` (`max_values`: None, `max_size`: Some(1241), added: 3716, mode: `MaxEncodedLen`) - /// The range of component `p` is `[0, 30]`. - fn kill_pure(p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `164 + p * (37 ±0)` - // Estimated: `4706` - // Minimum execution time: 22_770_000 picoseconds. - Weight::from_parts(23_441_470, 0) - .saturating_add(Weight::from_parts(0, 4706)) - // Standard Error: 1_959 - .saturating_add(Weight::from_parts(47_317, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_uniques.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_uniques.rs deleted file mode 100644 index a88928be65348beb3fc0fe29c893e1de0bacacfe..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_uniques.rs +++ /dev/null @@ -1,467 +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 . - -//! Autogenerated weights for `pallet_uniques` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_uniques -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/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_uniques`. -pub struct WeightInfo(PhantomData); -impl pallet_uniques::WeightInfo for WeightInfo { - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn create() -> Weight { - // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3643` - // Minimum execution time: 29_513_000 picoseconds. - Weight::from_parts(30_346_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_create() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3643` - // Minimum execution time: 13_600_000 picoseconds. - Weight::from_parts(14_110_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1001 w:1000) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1000 w:1000) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1000 w:1000) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:0 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1000) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::CollectionMaxSupply` (r:0 w:1) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// The range of component `n` is `[0, 1000]`. - /// The range of component `m` is `[0, 1000]`. - /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `257 + a * (107 ±0) + m * (56 ±0) + n * (76 ±0)` - // Estimated: `3643 + a * (2647 ±0) + m * (2662 ±0) + n * (2597 ±0)` - // Minimum execution time: 2_945_869_000 picoseconds. - Weight::from_parts(3_037_917_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - // Standard Error: 35_850 - .saturating_add(Weight::from_parts(7_558_563, 0).saturating_mul(n.into())) - // Standard Error: 35_850 - .saturating_add(Weight::from_parts(501_089, 0).saturating_mul(m.into())) - // Standard Error: 35_850 - .saturating_add(Weight::from_parts(538_921, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2647).saturating_mul(a.into())) - .saturating_add(Weight::from_parts(0, 2662).saturating_mul(m.into())) - .saturating_add(Weight::from_parts(0, 2597).saturating_mul(n.into())) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::CollectionMaxSupply` (r:1 w:0) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn mint() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 36_225_000 picoseconds. - Weight::from_parts(36_858_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:1) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn burn() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 37_021_000 picoseconds. - Weight::from_parts(37_749_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:2) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 26_884_000 picoseconds. - Weight::from_parts(27_414_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:5000 w:5000) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// The range of component `i` is `[0, 5000]`. - fn redeposit(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `738 + i * (76 ±0)` - // Estimated: `3643 + i * (2597 ±0)` - // Minimum execution time: 14_797_000 picoseconds. - Weight::from_parts(14_943_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - // Standard Error: 25_250 - .saturating_add(Weight::from_parts(18_014_600, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) - .saturating_add(Weight::from_parts(0, 2597).saturating_mul(i.into())) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn freeze() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 18_864_000 picoseconds. - Weight::from_parts(19_299_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn thaw() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 18_530_000 picoseconds. - Weight::from_parts(19_230_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn freeze_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 13_807_000 picoseconds. - Weight::from_parts(14_270_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn thaw_collection() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 13_657_000 picoseconds. - Weight::from_parts(14_059_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Uniques::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:2) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn transfer_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `356` - // Estimated: `3643` - // Minimum execution time: 22_108_000 picoseconds. - Weight::from_parts(22_520_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn set_team() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 14_128_000 picoseconds. - Weight::from_parts(14_481_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassAccount` (r:0 w:1) - /// Proof: `Uniques::ClassAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn force_item_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 17_114_000 picoseconds. - Weight::from_parts(17_570_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:0) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1 w:1) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - fn set_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `559` - // Estimated: `3652` - // Minimum execution time: 40_412_000 picoseconds. - Weight::from_parts(43_009_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:0) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Attribute` (r:1 w:1) - /// Proof: `Uniques::Attribute` (`max_values`: None, `max_size`: Some(172), added: 2647, mode: `MaxEncodedLen`) - fn clear_attribute() -> Weight { - // Proof Size summary in bytes: - // Measured: `756` - // Estimated: `3652` - // Minimum execution time: 38_044_000 picoseconds. - Weight::from_parts(38_871_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:1) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - fn set_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `348` - // Estimated: `3652` - // Minimum execution time: 30_016_000 picoseconds. - Weight::from_parts(30_723_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::InstanceMetadataOf` (r:1 w:1) - /// Proof: `Uniques::InstanceMetadataOf` (`max_values`: None, `max_size`: Some(187), added: 2662, mode: `MaxEncodedLen`) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `559` - // Estimated: `3652` - // Minimum execution time: 30_942_000 picoseconds. - Weight::from_parts(31_527_000, 0) - .saturating_add(Weight::from_parts(0, 3652)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:1) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:1 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - fn set_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 30_727_000 picoseconds. - Weight::from_parts(31_688_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ClassMetadataOf` (r:1 w:1) - /// Proof: `Uniques::ClassMetadataOf` (`max_values`: None, `max_size`: Some(167), added: 2642, mode: `MaxEncodedLen`) - fn clear_collection_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `473` - // Estimated: `3643` - // Minimum execution time: 29_844_000 picoseconds. - Weight::from_parts(30_403_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - fn approve_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `428` - // Estimated: `3643` - // Minimum execution time: 19_155_000 picoseconds. - Weight::from_parts(19_909_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - fn cancel_approval() -> Weight { - // Proof Size summary in bytes: - // Measured: `461` - // Estimated: `3643` - // Minimum execution time: 19_163_000 picoseconds. - Weight::from_parts(19_804_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::OwnershipAcceptance` (r:1 w:1) - /// Proof: `Uniques::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) - fn set_accept_ownership() -> Weight { - // Proof Size summary in bytes: - // Measured: `42` - // Estimated: `3517` - // Minimum execution time: 15_413_000 picoseconds. - Weight::from_parts(15_762_000, 0) - .saturating_add(Weight::from_parts(0, 3517)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::CollectionMaxSupply` (r:1 w:1) - /// Proof: `Uniques::CollectionMaxSupply` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - fn set_collection_max_supply() -> Weight { - // Proof Size summary in bytes: - // Measured: `282` - // Estimated: `3643` - // Minimum execution time: 16_477_000 picoseconds. - Weight::from_parts(16_811_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:0) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:0 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - fn set_price() -> Weight { - // Proof Size summary in bytes: - // Measured: `259` - // Estimated: `3587` - // Minimum execution time: 16_415_000 picoseconds. - Weight::from_parts(16_906_000, 0) - .saturating_add(Weight::from_parts(0, 3587)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Uniques::Asset` (r:1 w:1) - /// Proof: `Uniques::Asset` (`max_values`: None, `max_size`: Some(122), added: 2597, mode: `MaxEncodedLen`) - /// Storage: `Uniques::ItemPriceOf` (r:1 w:1) - /// Proof: `Uniques::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Class` (r:1 w:0) - /// Proof: `Uniques::Class` (`max_values`: None, `max_size`: Some(178), added: 2653, mode: `MaxEncodedLen`) - /// Storage: `Uniques::Account` (r:0 w:2) - /// Proof: `Uniques::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) - fn buy_item() -> Weight { - // Proof Size summary in bytes: - // Measured: `540` - // Estimated: `3643` - // Minimum execution time: 35_814_000 picoseconds. - Weight::from_parts(36_569_000, 0) - .saturating_add(Weight::from_parts(0, 3643)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(4)) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs deleted file mode 100644 index 061992691a605b42702a292eeb11e0520160272c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ /dev/null @@ -1,329 +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 . - -//! Autogenerated weights for `pallet_xcm_benchmarks::generic` -//! -//! 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: `[]` -//! 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-polkadot-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::generic -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-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: `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 report_holding() -> Weight { - // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 425_235_000 picoseconds. - Weight::from_parts(432_935_000, 3540) - .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: 4_070_000 picoseconds. - Weight::from_parts(4_329_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: `69` - // Estimated: `3534` - // Minimum execution time: 11_464_000 picoseconds. - Weight::from_parts(11_829_000, 3534) - .saturating_add(T::DbWeight::get().reads(1)) - } - pub fn transact() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 13_574_000 picoseconds. - Weight::from_parts(14_021_000, 0) - } - pub fn refund_surplus() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 4_276_000 picoseconds. - Weight::from_parts(4_479_000, 0) - } - pub fn set_error_handler() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_833_000 picoseconds. - Weight::from_parts(2_939_000, 0) - } - pub fn set_appendix() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_797_000 picoseconds. - Weight::from_parts(2_901_000, 0) - } - pub fn clear_error() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_855_000 picoseconds. - Weight::from_parts(2_961_000, 0) - } - pub fn descend_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_589_000 picoseconds. - Weight::from_parts(3_720_000, 0) - } - pub fn clear_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_786_000 picoseconds. - Weight::from_parts(2_889_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: `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 report_error() -> Weight { - // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 25_740_000 picoseconds. - Weight::from_parts(26_355_000, 3540) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) - // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) - pub fn claim_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `126` - // Estimated: `3591` - // Minimum execution time: 16_206_000 picoseconds. - Weight::from_parts(16_651_000, 3591) - .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_819_000 picoseconds. - Weight::from_parts(2_944_000, 0) - } - // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 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`) - pub fn subscribe_version() -> Weight { - // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 28_216_000 picoseconds. - Weight::from_parts(28_878_000, 3540) - .saturating_add(T::DbWeight::get().reads(6)) - .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: 4_795_000 picoseconds. - Weight::from_parts(5_008_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: 135_205_000 picoseconds. - Weight::from_parts(140_623_000, 0) - } - pub fn expect_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 12_791_000 picoseconds. - Weight::from_parts(13_114_000, 0) - } - pub fn expect_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_091_000, 0) - } - pub fn expect_error() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_828_000 picoseconds. - Weight::from_parts(2_947_000, 0) - } - pub fn expect_transact_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_980_000 picoseconds. - Weight::from_parts(3_123_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: `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 query_pallet() -> Weight { - // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 29_672_000 picoseconds. - Weight::from_parts(30_318_000, 3540) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - pub fn expect_pallet() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_421_000 picoseconds. - Weight::from_parts(5_614_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: `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 report_transact_status() -> Weight { - // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 25_621_000 picoseconds. - Weight::from_parts(26_486_000, 3540) - .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_873_000 picoseconds. - Weight::from_parts(2_973_000, 0) - } - pub fn set_topic() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_861_000 picoseconds. - Weight::from_parts(2_923_000, 0) - } - pub fn clear_topic() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_845_000 picoseconds. - Weight::from_parts(2_970_000, 0) - } - pub fn set_fees_mode() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_773_000 picoseconds. - Weight::from_parts(2_922_000, 0) - } - pub fn unpaid_execution() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_980_000 picoseconds. - Weight::from_parts(3_095_000, 0) - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs deleted file mode 100644 index 91663a75970ccdcf657d7b9f8cb8ec24724f8bb2..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/xcm_config.rs +++ /dev/null @@ -1,553 +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. - -use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, CENTS, -}; -use assets_common::matching::{FromSiblingParachain, IsForeignConcreteAsset}; -use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, -}; -use frame_system::EnsureRoot; -use pallet_xcm::XcmPassthrough; -use parachains_common::{ - impls::ToStakingPot, - xcm_config::{AssetFeeAsExistentialDepositMultiplier, ConcreteAssetFromSystem}, -}; -use polkadot_parachain_primitives::primitives::Sibling; -use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use sp_runtime::traits::ConvertInto; -use xcm::latest::prelude::*; -use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, - EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, -}; -use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; - -parameter_types! { - pub const DotLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: Option = Some(NetworkId::Polkadot); - pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - 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 CheckingAccount: AccountId = PolkadotXcm::check_account(); - 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, - // Foreign chain account alias into local accounts according to a hash of their standard - // description. - HashedDescription>, -); - -/// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // 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 don't track any teleports of `Balances`. - (), ->; - -/// `AssetId`/`Balance` converter for `TrustBackedAssets`. -pub type TrustBackedAssetsConvertedConcreteId = - assets_common::TrustBackedAssetsConvertedConcreteId; - -/// Means for transacting assets besides the native currency on this chain. -pub type FungiblesTransactor = FungiblesAdapter< - // Use this fungibles implementation: - Assets, - // Use this currency when it is a fungible asset matching the given location or name: - TrustBackedAssetsConvertedConcreteId, - // 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 only want to allow teleports of known assets. We use non-zero issuance as an indication - // that this asset is known. - LocalMint>, - // The account to use for tracking teleports. - CheckingAccount, ->; - -/// `AssetId/Balance` converter for `TrustBackedAssets` -pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId< - ( - // Ignore `TrustBackedAssets` explicitly - StartsWith, - // Ignore assets that start explicitly with our `GlobalConsensus(NetworkId)`, means: - // - foreign assets from our consensus should be: `MultiLocation {parents: 1, - // X*(Parachain(xyz), ..)}` - // - foreign assets outside our consensus with the same `GlobalConsensus(NetworkId)` won't - // be accepted here - StartsWithExplicitGlobalConsensus, - ), - 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, ForeignFungiblesTransactor); - -/// 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 -/// biases 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 which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when - // recognised. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognised. - 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, -); - -parameter_types! { - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; - pub XcmAssetFeesReceiver: Option = Authorship::author(); -} - -match_types! { - 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(_) } - }; - pub type FellowsPlurality: impl Contains = { - MultiLocation { parents: 1, interior: X2(Parachain(1001), Plurality { id: BodyId::Technical, ..}) } - }; - pub type FellowshipSalaryPallet: impl Contains = { - MultiLocation { parents: 1, interior: X2(Parachain(1001), PalletInstance(64)) } - }; - pub type AmbassadorSalaryPallet: impl Contains = { - 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 -/// 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::DmpQueue(..) | - RuntimeCall::Assets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::refund { .. }, - ) | RuntimeCall::ForeignAssets( - pallet_assets::Call::create { .. } | - pallet_assets::Call::force_create { .. } | - pallet_assets::Call::start_destroy { .. } | - pallet_assets::Call::destroy_accounts { .. } | - pallet_assets::Call::destroy_approvals { .. } | - pallet_assets::Call::finish_destroy { .. } | - pallet_assets::Call::mint { .. } | - pallet_assets::Call::burn { .. } | - pallet_assets::Call::transfer { .. } | - pallet_assets::Call::transfer_keep_alive { .. } | - pallet_assets::Call::force_transfer { .. } | - pallet_assets::Call::freeze { .. } | - pallet_assets::Call::thaw { .. } | - pallet_assets::Call::freeze_asset { .. } | - pallet_assets::Call::thaw_asset { .. } | - pallet_assets::Call::transfer_ownership { .. } | - pallet_assets::Call::set_team { .. } | - pallet_assets::Call::set_metadata { .. } | - pallet_assets::Call::clear_metadata { .. } | - pallet_assets::Call::force_set_metadata { .. } | - pallet_assets::Call::force_clear_metadata { .. } | - pallet_assets::Call::force_asset_status { .. } | - pallet_assets::Call::approve_transfer { .. } | - pallet_assets::Call::cancel_approval { .. } | - pallet_assets::Call::force_cancel_approval { .. } | - pallet_assets::Call::transfer_approved { .. } | - pallet_assets::Call::touch { .. } | - pallet_assets::Call::refund { .. }, - ) | RuntimeCall::Nfts( - pallet_nfts::Call::create { .. } | - pallet_nfts::Call::force_create { .. } | - pallet_nfts::Call::destroy { .. } | - pallet_nfts::Call::mint { .. } | - pallet_nfts::Call::force_mint { .. } | - pallet_nfts::Call::burn { .. } | - pallet_nfts::Call::transfer { .. } | - pallet_nfts::Call::lock_item_transfer { .. } | - pallet_nfts::Call::unlock_item_transfer { .. } | - pallet_nfts::Call::lock_collection { .. } | - pallet_nfts::Call::transfer_ownership { .. } | - pallet_nfts::Call::set_team { .. } | - pallet_nfts::Call::force_collection_owner { .. } | - pallet_nfts::Call::force_collection_config { .. } | - pallet_nfts::Call::approve_transfer { .. } | - pallet_nfts::Call::cancel_approval { .. } | - pallet_nfts::Call::clear_all_transfer_approvals { .. } | - pallet_nfts::Call::lock_item_properties { .. } | - pallet_nfts::Call::set_attribute { .. } | - pallet_nfts::Call::force_set_attribute { .. } | - pallet_nfts::Call::clear_attribute { .. } | - pallet_nfts::Call::approve_item_attributes { .. } | - pallet_nfts::Call::cancel_item_attributes_approval { .. } | - pallet_nfts::Call::set_metadata { .. } | - pallet_nfts::Call::clear_metadata { .. } | - pallet_nfts::Call::set_collection_metadata { .. } | - pallet_nfts::Call::clear_collection_metadata { .. } | - pallet_nfts::Call::set_accept_ownership { .. } | - pallet_nfts::Call::set_collection_max_supply { .. } | - pallet_nfts::Call::update_mint_settings { .. } | - pallet_nfts::Call::set_price { .. } | - pallet_nfts::Call::buy_item { .. } | - pallet_nfts::Call::pay_tips { .. } | - pallet_nfts::Call::create_swap { .. } | - pallet_nfts::Call::cancel_swap { .. } | - pallet_nfts::Call::claim_swap { .. }, - ) | RuntimeCall::Uniques( - pallet_uniques::Call::create { .. } | - pallet_uniques::Call::force_create { .. } | - pallet_uniques::Call::destroy { .. } | - pallet_uniques::Call::mint { .. } | - pallet_uniques::Call::burn { .. } | - pallet_uniques::Call::transfer { .. } | - pallet_uniques::Call::freeze { .. } | - pallet_uniques::Call::thaw { .. } | - pallet_uniques::Call::freeze_collection { .. } | - pallet_uniques::Call::thaw_collection { .. } | - pallet_uniques::Call::transfer_ownership { .. } | - pallet_uniques::Call::set_team { .. } | - pallet_uniques::Call::approve_transfer { .. } | - pallet_uniques::Call::cancel_approval { .. } | - pallet_uniques::Call::force_item_status { .. } | - pallet_uniques::Call::set_attribute { .. } | - pallet_uniques::Call::clear_attribute { .. } | - pallet_uniques::Call::set_metadata { .. } | - pallet_uniques::Call::clear_metadata { .. } | - pallet_uniques::Call::set_collection_metadata { .. } | - pallet_uniques::Call::clear_collection_metadata { .. } | - pallet_uniques::Call::set_accept_ownership { .. } | - pallet_uniques::Call::set_collection_max_supply { .. } | - pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. } - ) - ) - } -} - -pub type Barrier = TrailingSetTopicAsId< - DenyThenTry< - DenyReserveTransferToRelayChain, - ( - TakeWeightCredit, - // Expected responses are OK. - AllowKnownQueryResponses, - // Allow XCMs with some computed origins to pass through. - WithComputedOrigin< - ( - // If the message is one that immediately attempts 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, - FellowshipSalaryPallet, - AmbassadorSalaryPallet, - )>, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, - ), - UniversalLocation, - ConstU32<8>, - >, - ), - >, ->; - -pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentialDepositMultiplier< - Runtime, - WeightToFee, - pallet_assets::BalanceToAssetBalance, - TrustBackedAssetsInstance, ->; - -/// Cases where a remote origin is accepted as trusted Teleporter for a given asset: -/// -/// - DOT with the parent Relay Chain and sibling system parachains; and -/// - Sibling parachains' assets from where they originate (as `ForeignCreators`). -pub type TrustedTeleporters = ( - ConcreteAssetFromSystem, - IsForeignConcreteAsset>>, -); - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type AssetTransactor = AssetTransactors; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub Polkadot does not recognize a reserve location for any asset. This does not prevent - // Asset Hub acting _as_ a reserve location for DOT and assets created under `pallet-assets`. - // For DOT, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); - type IsTeleporter = TrustedTeleporters; - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = WeightInfoBounds< - crate::weights::xcm::AssetHubPolkadotXcmWeight, - RuntimeCall, - MaxInstructions, - >; - type Trader = ( - UsingComponents>, - cumulus_primitives_utility::TakeFirstAssetTrader< - AccountId, - AssetFeeAsExistentialDepositMultiplierFeeCharger, - TrustBackedAssetsConvertedConcreteId, - Assets, - cumulus_primitives_utility::XcmFeesTo32ByteAccount< - FungiblesTransactor, - AccountId, - XcmAssetFeesReceiver, - >, - >, - ); - type ResponseHandler = PolkadotXcm; - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; - type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - 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; - -parameter_types! { - /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(DotLocation::get()); - /// The base fee for the message delivery fees. - pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); -} - -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, -)>; - -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - -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 and reserve transfers are - // allowed. - type XcmExecuteFilter = Nothing; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type Weigher = WeightInfoBounds< - crate::weights::xcm::AssetHubPolkadotXcmWeight, - 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; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; - type AdminOrigin = EnsureRoot; - type MaxRemoteLockConsumers = ConstU32<0>; - type RemoteLockConsumerIdentifier = (); -} - -impl cumulus_pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; -} - -pub type ForeignCreatorsSovereignAccountOf = ( - SiblingParachainConvertsVia, - AccountId32Aliases, - ParentIsPreset, -); - -/// 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)) } - } -} - -#[test] -fn foreign_pallet_has_correct_local_account() { - use sp_core::crypto::{Ss58AddressFormat, Ss58Codec}; - use xcm_executor::traits::ConvertLocation; - - const COLLECTIVES_PARAID: u32 = 1001; - const FELLOWSHIP_SALARY_PALLET_ID: u8 = 64; - let fellowship_salary = - (Parent, Parachain(COLLECTIVES_PARAID), PalletInstance(FELLOWSHIP_SALARY_PALLET_ID)); - let account = LocationToAccountId::convert_location(&fellowship_salary.into()).unwrap(); - let polkadot = Ss58AddressFormat::try_from("polkadot").unwrap(); - let address = Ss58Codec::to_ss58check_with_version(&account, polkadot); - assert_eq!(address, "13w7NdvSR1Af8xsQTArDtZmVvjE8XhWNdL4yed3iFHrUNCnS"); -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs deleted file mode 100644 index 7200ebc16a2875336fc50e711386cab41df6c9b4..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/tests/tests.rs +++ /dev/null @@ -1,659 +0,0 @@ -// This file is part of Cumulus. - -// 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 the Statemint (Polkadot Assets Hub) chain. - -use asset_hub_polkadot_runtime::xcm_config::{ - AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, DotLocation, - ForeignCreatorsSovereignAccountOf, TrustBackedAssetsPalletLocation, XcmConfig, -}; -pub use asset_hub_polkadot_runtime::{ - AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, -}; -use asset_test_utils::{CollatorSessionKeys, ExtBuilder}; -use codec::{Decode, Encode}; -use cumulus_primitives_utility::ChargeWeightInFungibles; -use frame_support::{ - assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, - weights::{Weight, WeightToFee as WeightToFeeT}, -}; -use parachains_common::{ - polkadot::fee::WeightToFee, AccountId, AssetHubPolkadotAuraId as AuraId, - AssetIdForTrustBackedAssets, Balance, -}; -use sp_runtime::traits::MaybeEquivalence; -use xcm::latest::prelude::*; -use xcm_executor::traits::{Identity, JustTry, WeightTrader}; - -const ALICE: [u8; 32] = [1u8; 32]; -const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; - -type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; - -type RuntimeHelper = asset_test_utils::RuntimeHelper; - -fn collator_session_keys() -> CollatorSessionKeys { - CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - ) -} - -#[test] -fn test_asset_xcm_trader() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - let minimum_asset_balance = 333333333_u128; - let local_asset_id = 1; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // get asset id as multilocation - let asset_multilocation = - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - // Lets calculate amount needed - let asset_amount_needed = - AssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( - local_asset_id, - bought, - ) - .expect("failed to compute"); - - // Lets pay with: asset_amount_needed + asset_amount_extra - let asset_amount_extra = 100_u128; - let asset: MultiAsset = - (asset_multilocation, asset_amount_needed + asset_amount_extra).into(); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Lets buy_weight and make sure buy_weight does not return an error - let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); - // Check whether a correct amount of unused assets is returned - assert_ok!( - unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into()) - ); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance + asset_amount_needed - ); - - // We also need to ensure the total supply increased - assert_eq!( - Assets::total_supply(local_asset_id), - minimum_asset_balance + asset_amount_needed - ); - }); -} - -#[test] -fn test_asset_xcm_trader_with_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - // lets calculate amount needed - let amount_bought = WeightToFee::weight_to_fee(&bought); - - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - - // Make sure buy_weight does not return an error - assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); - - // Make sure again buy_weight does return an error - // This assert relies on the fact, that we use `TakeFirstAssetTrader` in `WeightTrader` - // tuple chain, which cannot be called twice - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // We actually use half of the weight - let weight_used = bought / 2; - - // Make sure refurnd works. - let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used)); - - assert_eq!( - trader.refund_weight(bought - weight_used, &ctx), - Some((asset_multilocation, amount_refunded).into()) - ); - - // Drop trader - drop(trader); - - // We only should have paid for half of the bought weight - let fees_paid = WeightToFee::weight_to_fee(&weight_used); - - assert_eq!( - Assets::balance(1, AccountId::from(ALICE)), - ExistentialDeposit::get() + fees_paid - ); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get() + fees_paid); - }); -} - -#[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 50e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(50_000_000_000u64, 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - - // Buy weight should return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // not credited since the ED is higher than this value - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), 0); - - // We also need to ensure the total supply did not increase - assert_eq!(Assets::total_supply(1), 0); - }); -} - -#[test] -fn test_that_buying_ed_refund_does_not_refund() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // We need root origin to create a sufficient asset - // We set existential deposit to be identical to the one for Balances first - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - true, - ExistentialDeposit::get() - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are gonna buy ED - let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let amount_bought = WeightToFee::weight_to_fee(&bought); - - assert!( - amount_bought < ExistentialDeposit::get(), - "we are testing what happens when the amount does not exceed ED" - ); - - // We know we will have to buy at least ED, so lets make sure first it will - // fail with a payment of less than ED - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Now lets buy ED at least - let asset: MultiAsset = (asset_multilocation, ExistentialDeposit::get()).into(); - - // Buy weight should work - assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); - - // Should return None. We have a specific check making sure we dont go below ED for - // drop payment - assert_eq!(trader.refund_weight(bought, &ctx), None); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), ExistentialDeposit::get()); - - // We also need to ensure the total supply increased - assert_eq!(Assets::total_supply(1), ExistentialDeposit::get()); - }); -} - -#[test] -fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - // Create a non-sufficient asset - let minimum_asset_balance = 1_000_000_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - 1.into(), - AccountId::from(ALICE).into(), - false, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - 1.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - let mut trader = ::Trader::new(); - let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; - - // Set Alice as block author, who will receive fees - RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - - // We are going to buy 400e9 weight - // Because of the ED being higher in kusama's asset hub - // and not to complicate things, we use a little - // bit more of weight - let bought = Weight::from_parts(400_000_000_000u64, 0); - - // lets calculate amount needed - let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); - - let asset: MultiAsset = (asset_multilocation, asset_amount_needed).into(); - - // Make sure again buy_weight does return an error - assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); - - // Drop trader - drop(trader); - - // Make sure author(Alice) has NOT received the amount - assert_eq!(Assets::balance(1, AccountId::from(ALICE)), minimum_asset_balance); - - // We also need to ensure the total supply NOT increased - assert_eq!(Assets::total_supply(1), minimum_asset_balance); - }); -} - -#[test] -fn test_assets_balances_api_works() { - use assets_common::runtime_api::runtime_decl_for_fungibles_api::FungiblesApi; - - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - let local_asset_id = 1; - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) }; - - // check before - assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); - assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), - 0 - ); - assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); - assert!(Runtime::query_account_balances(AccountId::from(ALICE)) - .unwrap() - .try_as::() - .unwrap() - .is_none()); - - // Drip some balance - use frame_support::traits::fungible::Mutate; - let some_currency = ExistentialDeposit::get(); - Balances::mint_into(&AccountId::from(ALICE), some_currency).unwrap(); - - // We need root origin to create a sufficient asset - let minimum_asset_balance = 3333333_u128; - assert_ok!(Assets::force_create( - RuntimeHelper::root_origin(), - local_asset_id.into(), - AccountId::from(ALICE).into(), - true, - minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(Assets::mint( - RuntimeHelper::origin_of(AccountId::from(ALICE)), - local_asset_id.into(), - AccountId::from(ALICE).into(), - minimum_asset_balance - )); - - // create foreign asset - let foreign_asset_minimum_asset_balance = 3333333_u128; - assert_ok!(ForeignAssets::force_create( - RuntimeHelper::root_origin(), - foreign_asset_id_multilocation, - AccountId::from(SOME_ASSET_ADMIN).into(), - false, - foreign_asset_minimum_asset_balance - )); - - // We first mint enough asset for the account to exist for assets - assert_ok!(ForeignAssets::mint( - RuntimeHelper::origin_of(AccountId::from(SOME_ASSET_ADMIN)), - foreign_asset_id_multilocation, - AccountId::from(ALICE).into(), - 6 * foreign_asset_minimum_asset_balance - )); - - // check after - assert_eq!( - Assets::balance(local_asset_id, AccountId::from(ALICE)), - minimum_asset_balance - ); - assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), - 6 * minimum_asset_balance - ); - assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); - - let result: MultiAssets = Runtime::query_account_balances(AccountId::from(ALICE)) - .unwrap() - .try_into() - .unwrap(); - assert_eq!(result.len(), 3); - - // check currency - assert!(result.inner().iter().any(|asset| asset.eq( - &assets_common::fungible_conversion::convert_balance::( - some_currency - ) - .unwrap() - ))); - // check trusted asset - assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), - minimum_asset_balance - ) - .into()))); - // check foreign asset - assert!(result.inner().iter().any(|asset| asset.eq(&( - Identity::convert_back(&foreign_asset_id_multilocation).unwrap(), - 6 * foreign_asset_minimum_asset_balance - ) - .into()))); - }); -} - -asset_test_utils::include_teleports_for_native_asset_works!( - 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, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - 1000 -); - -asset_test_utils::include_teleports_for_foreign_assets_works!( - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - CheckingAccount, - WeightToFee, - ParachainSystem, - ForeignCreatorsSovereignAccountOf, - ForeignAssetsInstance, - asset_test_utils::CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) } - ), - ExistentialDeposit::get(), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_local_consensus_currency_works!( - Runtime, - XcmConfig, - collator_session_keys(), - ExistentialDeposit::get(), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!( - asset_transactor_transfer_with_trust_backed_assets_works, - Runtime, - XcmConfig, - TrustBackedAssetsInstance, - AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvert, - collator_session_keys(), - ExistentialDeposit::get(), - 12345, - Box::new(|| { - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_works!( - asset_transactor_transfer_with_foreign_assets_works, - Runtime, - XcmConfig, - ForeignAssetsInstance, - MultiLocation, - JustTry, - asset_test_utils::CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) } - ), - ExistentialDeposit::get(), - MultiLocation { parents: 1, interior: X2(Parachain(1313), GeneralIndex(12345)) }, - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - }) -); - -asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works!( - Runtime, - XcmConfig, - WeightToFee, - ForeignCreatorsSovereignAccountOf, - ForeignAssetsInstance, - MultiLocation, - JustTry, - asset_test_utils::CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::ed25519::Public::from_raw(ALICE)) } - ), - ExistentialDeposit::get(), - AssetDeposit::get(), - MetadataDepositBase::get(), - MetadataDepositPerByte::get(), - Box::new(|pallet_asset_call| RuntimeCall::ForeignAssets(pallet_asset_call).encode()), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::ForeignAssets(pallet_asset_event)) => Some(pallet_asset_event), - _ => None, - } - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert!(ForeignAssets::asset_ids().collect::>().is_empty()); - }), - Box::new(|| { - assert!(Assets::asset_ids().collect::>().is_empty()); - assert_eq!(ForeignAssets::asset_ids().collect::>().len(), 1); - }) -); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index b1ec66f40bff5cc5982336d1278ad95193f3f0d2..95691a045b72284c5d42218f4346b0dbb9e62e8f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -1,90 +1,96 @@ [package] name = "asset-hub-rococo-runtime" -version = "0.9.420" +version = "0.11.0" authors.workspace = true 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" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" # 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-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false} -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-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false} -pallet-nft-fractionalization = { path = "../../../../../substrate/frame/nft-fractionalization", default-features = false} -pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false} -pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false} -pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false} -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false} +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } +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-nft-fractionalization = { path = "../../../../../substrate/frame/nft-fractionalization", default-features = false } +pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false } +pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false } +pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false } +pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false, optional = true } -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-uniques = { path = "../../../../../substrate/frame/uniques", 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-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} -sp-weights = { path = "../../../../../substrate/primitives/weights", 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-uniques = { path = "../../../../../substrate/frame/uniques", 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 } +sp-weights = { path = "../../../../../substrate/primitives/weights", default-features = false } # num-traits feature needed for dex integer sq root: -primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "num-traits"] } +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } # Polkadot -rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", default-features = false} -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false} +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} +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 = "../../../../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-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, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } assets-common = { path = "../common", default-features = false } # Bridges pallet-xcm-bridge-hub-router = { path = "../../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hub-rococo", default-features = false } -bp-asset-hub-wococo = { path = "../../../../../bridges/primitives/chain-asset-hub-wococo", 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-wococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-wococo", default-features = false } +bp-bridge-hub-westend = { path = "../../../../../bridges/primitives/chain-bridge-hub-westend", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } [dev-dependencies] asset-test-utils = { path = "../test-utils" } @@ -93,7 +99,7 @@ asset-test-utils = { path = "../test-utils" } substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] # When enabled the `state_version` is set to `1`. # This means that the chain will start using the new state format. The migration is lazy, so # it requires to write a storage value to use the new state format. To migrate all the other @@ -101,12 +107,13 @@ default = [ "std" ] # This pallet will migrate the entire state, controlled through some account. # # This feature should be removed when the main-net will be migrated. -state-trie-version-1 = [ "pallet-state-trie-migration" ] +state-trie-version-1 = ["pallet-state-trie-migration"] runtime-benchmarks = [ "assets-common/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", @@ -116,6 +123,7 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nft-fractionalization/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", @@ -130,13 +138,13 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-router-primitives/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", @@ -151,6 +159,7 @@ 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-nft-fractionalization/try-runtime", "pallet-nfts/try-runtime", @@ -170,16 +179,16 @@ try-runtime = [ std = [ "assets-common/std", "bp-asset-hub-rococo/std", - "bp-asset-hub-wococo/std", + "bp-asset-hub-westend/std", "bp-bridge-hub-rococo/std", - "bp-bridge-hub-wococo/std", + "bp-bridge-hub-westend/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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -197,6 +206,7 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-collator-selection/std", + "pallet-message-queue/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", "pallet-nfts-runtime-api/std", @@ -214,15 +224,17 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", + "primitive-types/std", "rococo-runtime-constants/std", "scale-info/std", + "snowbridge-router-primitives/std", "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", @@ -233,9 +245,13 @@ std = [ "sp-version/std", "sp-weights/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] -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/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 20b59368a5b5274303fa8f563591ad450b2c0ab1..d3d8a495ea3f226ba28410ecd769a5fc9123d49f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -16,12 +16,6 @@ //! # Asset Hub Rococo Runtime //! //! Asset Hub Rococo, formerly known as "Rockmine", is the test network for its Kusama cousin. -//! -//! This runtime is also used for Asset Hub Wococo. But we dont want to create another exact copy of -//! Asset Hub Rococo, so we injected some tweaks backed by `RuntimeFlavor` and `pub storage Flavor: -//! RuntimeFlavor`. (For example this is needed for successful asset transfer between Asset Hub -//! Rococo and Asset Hub Wococo, where we need to have correct `xcm_config::UniversalLocation` with -//! correct `GlobalConsensus`. #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] @@ -35,19 +29,21 @@ pub mod xcm_config; use assets_common::{ foreign_creators::ForeignCreators, - local_and_foreign_assets::{LocalAndForeignAssets, MultiLocationConverter}, - matching::FromSiblingParachain, - AssetIdForTrustBackedAssetsConvert, MultiLocationForAssetId, + local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, + matching::{FromNetwork, FromSiblingParachain}, + AssetIdForTrustBackedAssetsConvert, }; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::AggregateMessageOrigin; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Verify}, + traits::{AccountIdConversion, BlakeTwo256, Block as BlockT, Saturating, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Permill, }; +use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -55,13 +51,16 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use codec::{Decode, Encode, MaxEncodedLen}; +use cumulus_primitives_core::ParaId; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, ord_parameter_types, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, - Equals, InstanceFilter, + fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, + ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, + TransformOrigin, }, weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, @@ -72,19 +71,18 @@ use frame_system::{ }; use pallet_asset_conversion_tx_payment::AssetConversionAdapter; use pallet_nfts::PalletFeatures; -pub use parachains_common as common; use parachains_common::{ impls::DealWithFees, - rococo::{consensus::*, currency::*, fee::WeightToFee}, - AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, Hash, Header, Nonce, - Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, - NORMAL_DISPATCH_RATIO, SLOT_DURATION, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, + Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; -use sp_runtime::RuntimeDebug; -use xcm::opaque::v3::MultiLocation; +use sp_runtime::{Perbill, RuntimeDebug}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ - ForeignAssetsConvertedConcreteId, GovernanceLocation, PoolAssetsConvertedConcreteId, - TokenLocation, TrustBackedAssetsConvertedConcreteId, XcmConfig, + ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, GovernanceLocation, + PoolAssetsConvertedConcreteId, TokenLocation, TokenLocationV3, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocationV3, }; #[cfg(any(feature = "std", test))] @@ -93,23 +91,15 @@ pub use sp_runtime::BuildStorage; // Polkadot imports use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; -use xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; - -use crate::xcm_config::{ - ForeignCreatorsSovereignAccountOf, LocalAndForeignAssetsMultiLocationMatcher, - TrustBackedAssetsPalletLocation, +// We exclude `Assets` since it's the name of a pallet +#[cfg(feature = "runtime-benchmarks")] +use xcm::latest::prelude::{ + Asset, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, NetworkId, + NonFungible, Parent, ParentThen, Response, XCM_VERSION, }; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::{AssetId, BodyId}; -/// Enum for handling differences in the runtime configuration for `AssetHubRococo` vs. -/// `AssetHubWococo`. -#[derive(Default, Eq, PartialEq, Debug, Clone, Copy, Decode, Encode)] -pub enum RuntimeFlavor { - #[default] - Rococo, - Wococo, -} +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { pub struct SessionKeys { @@ -123,10 +113,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 10006, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 1, }; @@ -136,10 +126,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 10006, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 0, }; @@ -175,25 +165,17 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; type SystemWeightInfo = weights::frame_system::WeightInfo; type SS58Prefix = SS58Prefix; @@ -205,7 +187,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -231,10 +213,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - // We allow each account to have holds on it from: - // - `NftFractionalization`: 1 - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<0>; } @@ -297,8 +277,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); } @@ -333,36 +311,55 @@ 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, + xcm::v3::Location, + >, + xcm::v3::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. +pub type NativeAndAssets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + xcm::v3::Location, + 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 PoolAssets = PoolAssets; + type AssetKind = xcm::v3::Location; + type Assets = NativeAndAssets; + 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 = TokenLocationV3; + 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< + TokenLocationV3, + parachain_info::Pallet, + xcm_config::TrustBackedAssetsPalletIndex, + xcm::v3::Location, + >; } parameter_types! { @@ -383,13 +380,17 @@ 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 AssetId = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< - (FromSiblingParachain>,), + ( + FromSiblingParachain, xcm::v3::Location>, + FromNetwork, + ), ForeignCreatorsSovereignAccountOf, AccountId, + xcm::v3::Location, >; type ForceOrigin = AssetsForceOrigin; type AssetDeposit = ForeignAssetsAssetDeposit; @@ -615,21 +616,50 @@ parameter_types! { } 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 DmpMessageHandler = DmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +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 {} @@ -638,7 +668,7 @@ impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(xcm_config::TokenLocation::get()); + pub FeeAssetId: AssetId = AssetId(xcm_config::TokenLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -651,21 +681,19 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: >; impl cumulus_pallet_xcmp_queue::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = xcm_config::XcmOriginToTransactDispatchOrigin; - type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } parameter_types! { @@ -691,9 +719,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -727,12 +754,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! { @@ -745,8 +769,8 @@ parameter_types! { impl pallet_uniques::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; + type CollectionId = CollectionId; + type ItemId = ItemId; type Currency = Balances; type ForceOrigin = AssetsForceOrigin; type CollectionDeposit = UniquesCollectionDeposit; @@ -803,8 +827,8 @@ parameter_types! { impl pallet_nfts::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; + type CollectionId = CollectionId; + type ItemId = ItemId; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = AssetsForceOrigin; @@ -830,46 +854,16 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } -/// XCM router instance to BridgeHub with bridging capabilities for `Wococo` global +/// XCM router instance to BridgeHub with bridging capabilities for `Westend` global /// consensus with dynamic fees and back-pressure. -pub type ToWococoXcmRouterInstance = pallet_assets::Instance1; -impl pallet_xcm_bridge_hub_router::Config for Runtime { - type WeightInfo = weights::pallet_xcm_bridge_hub_router_to_wococo::WeightInfo; +pub type ToWestendXcmRouterInstance = pallet_xcm_bridge_hub_router::Instance3; +impl pallet_xcm_bridge_hub_router::Config for Runtime { + type WeightInfo = weights::pallet_xcm_bridge_hub_router::WeightInfo; type UniversalLocation = xcm_config::UniversalLocation; - type BridgedNetworkId = xcm_config::bridging::to_wococo::WococoNetwork; - type Bridges = xcm_config::bridging::NetworkExportTable; - - #[cfg(not(feature = "runtime-benchmarks"))] - type BridgeHubOrigin = EnsureXcm>; - #[cfg(feature = "runtime-benchmarks")] - type BridgeHubOrigin = EitherOfDiverse< - // for running benchmarks - EnsureRoot, - // for running tests with `--feature runtime-benchmarks` - EnsureXcm>, - >; - - type ToBridgeHubSender = XcmpQueue; - type WithBridgeHubChannel = - cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider< - xcm_config::bridging::SiblingBridgeHubParaId, - Runtime, - >; - - type ByteFee = xcm_config::bridging::XcmBridgeHubRouterByteFee; - type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; -} - -/// XCM router instance to BridgeHub with bridging capabilities for `Rococo` global -/// consensus with dynamic fees and back-pressure. -pub type ToRococoXcmRouterInstance = pallet_assets::Instance2; -impl pallet_xcm_bridge_hub_router::Config for Runtime { - type WeightInfo = weights::pallet_xcm_bridge_hub_router_to_rococo::WeightInfo; - - type UniversalLocation = xcm_config::UniversalLocation; - type BridgedNetworkId = xcm_config::bridging::to_rococo::RococoNetwork; + 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>; @@ -897,49 +891,45 @@ 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} = 3, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 3, + ParachainInfo: parachain_info = 4, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - AssetTxPayment: pallet_asset_conversion_tx_payment::{Pallet, Event} = 13, + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + AssetTxPayment: pallet_asset_conversion_tx_payment = 13, // 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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, - PolkadotXcm: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + 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::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // Bridge utilities. - ToWococoXcmRouter: pallet_xcm_bridge_hub_router::::{Pallet, Storage, Call} = 43, - ToRococoXcmRouter: pallet_xcm_bridge_hub_router::::{Pallet, Storage, Call} = 44, + ToWestendXcmRouter: pallet_xcm_bridge_hub_router:: = 45, // The main stage. - Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, - Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, - ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, - NftFractionalization: pallet_nft_fractionalization::{Pallet, Call, Storage, Event, HoldReason} = 54, - - PoolAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 55, - AssetConversion: pallet_asset_conversion::{Pallet, Call, Storage, Event} = 56, + Assets: pallet_assets:: = 50, + Uniques: pallet_uniques = 51, + Nfts: pallet_nfts = 52, + ForeignAssets: pallet_assets:: = 53, + NftFractionalization: pallet_nft_fractionalization = 54, + PoolAssets: pallet_assets:: = 55, + AssetConversion: pallet_asset_conversion = 56, #[cfg(feature = "state-trie-version-1")] StateTrieMigration: pallet_state_trie_migration = 70, @@ -969,7 +959,66 @@ pub type SignedExtra = ( pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); +pub type Migrations = ( + pallet_collator_selection::migration::v1::MigrateToV1, + InitStorageVersions, + // unreleased + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); + +/// Migration to initialize storage versions for pallets added after genesis. +/// +/// This is now done automatically (see ), +/// but some pallets had made it in and had storage set in them for this parachain before it was +/// merged. +pub struct InitStorageVersions; + +impl frame_support::traits::OnRuntimeUpgrade for InitStorageVersions { + fn on_runtime_upgrade() -> Weight { + use frame_support::traits::{GetStorageVersion, StorageVersion}; + + let mut writes = 0; + + if PolkadotXcm::on_chain_storage_version() == StorageVersion::new(0) { + PolkadotXcm::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if Multisig::on_chain_storage_version() == StorageVersion::new(0) { + Multisig::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if Assets::on_chain_storage_version() == StorageVersion::new(0) { + Assets::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if Uniques::on_chain_storage_version() == StorageVersion::new(0) { + Uniques::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if Nfts::on_chain_storage_version() == StorageVersion::new(0) { + Nfts::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if ForeignAssets::on_chain_storage_version() == StorageVersion::new(0) { + ForeignAssets::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if PoolAssets::on_chain_storage_version() == StorageVersion::new(0) { + PoolAssets::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + ::DbWeight::get().reads_writes(7, writes) + } +} /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -981,19 +1030,16 @@ pub type Executive = frame_executive::Executive< Migrations, >; -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_assets, Local] [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_nft_fractionalization, NftFractionalization] [pallet_nfts, Nfts] @@ -1003,11 +1049,11 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] - [pallet_xcm_bridge_hub_router, ToWococo] - [pallet_xcm_bridge_hub_router, ToRococo] + [pallet_xcm_bridge_hub_router, ToWestend] // XCM - [pallet_xcm, PolkadotXcm] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] @@ -1017,11 +1063,20 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -1034,7 +1089,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -1111,18 +1166,17 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + xcm::v3::Location, > 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: xcm::v3::Location, asset2: xcm::v3::Location, 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: xcm::v3::Location, asset2: xcm::v3::Location, 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: xcm::v3::Location, asset2: xcm::v3::Location) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -1175,7 +1229,7 @@ impl_runtime_apis! { AccountId, > for Runtime { - fn query_account_balances(account: AccountId) -> Result { + fn query_account_balances(account: AccountId) -> Result { use assets_common::fungible_conversion::{convert, convert_balance}; Ok([ // collect pallet_balance @@ -1245,6 +1299,7 @@ impl_runtime_apis! { 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; use pallet_xcm_bridge_hub_router::benchmarking::Pallet as XcmBridgeHubRouterBench; // This is defined once again in dispatch_benchmark, because list_benchmarks! @@ -1261,8 +1316,7 @@ impl_runtime_apis! { type Foreign = pallet_assets::Pallet::; type Pool = pallet_assets::Pallet::; - type ToWococo = XcmBridgeHubRouterBench; - type ToRococo = XcmBridgeHubRouterBench; + type ToWestend = XcmBridgeHubRouterBench; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1297,77 +1351,178 @@ impl_runtime_apis! { Config as XcmBridgeHubRouterConfig, }; - impl XcmBridgeHubRouterConfig for Runtime { - fn make_congested() { - cumulus_pallet_xcmp_queue::bridging::suspend_channel_for_benchmarks::( - xcm_config::bridging::SiblingBridgeHubParaId::get().into() - ); + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenLocation::get(), + ExistentialDeposit::get() + ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + + fn reachable_dest() -> Option { + Some(Parent.into()) } - fn ensure_bridged_target_destination() -> MultiLocation { - ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( - xcm_config::bridging::SiblingBridgeHubParaId::get().into() + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), + )) + } + + fn set_up_complex_asset_transfer( + ) -> Option<(xcm::v4::Assets, u32, Location, 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: Asset = (Location::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, ); - xcm_config::bridging::to_wococo::AssetHubWococo::get() + // 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 = Location::new( + 0, + [PalletInstance(50), GeneralIndex(u32::from(asset_id).into())] + ); + let transfer_asset: Asset = (asset_location, asset_amount).into(); + + let assets: xcm::v4::Assets = 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)) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } } } - impl XcmBridgeHubRouterConfig for Runtime { + + impl XcmBridgeHubRouterConfig for Runtime { fn make_congested() { cumulus_pallet_xcmp_queue::bridging::suspend_channel_for_benchmarks::( xcm_config::bridging::SiblingBridgeHubParaId::get().into() ); } - fn ensure_bridged_target_destination() -> MultiLocation { - xcm_config::Flavor::set(&RuntimeFlavor::Wococo); + 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_westend::AssetHubWestend::get(); + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridged_asset_hub.clone()), + 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) } } - use xcm::latest::prelude::*; use xcm_config::{TokenLocation, MaxAssetsIntoHolding}; use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( - TokenLocation::get(), - ExistentialDeposit::get() - ).into()); - } - impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - XcmConfig, - ExistentialDepositMultiAsset, + xcm_config::XcmConfig, + ExistentialDepositAsset, xcm_config::PriceForParentDelivery, >; - fn valid_destination() -> Result { + fn valid_destination() -> Result { Ok(TokenLocation::get()) } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { + fn worst_case_holding(depositable_count: u32) -> xcm::v4::Assets { // A mix of fungible, non-fungible, and concrete assets. let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; let holding_fungibles = holding_non_fungibles.saturating_sub(1); let fungibles_amount: u128 = 100; let mut assets = (0..holding_fungibles) .map(|i| { - MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + Asset { + id: GeneralIndex(i as u128).into(), fun: Fungible(fungibles_amount * i as u128), } }) - .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..holding_non_fungibles).map(|i| MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + .chain(core::iter::once(Asset { id: Here.into(), fun: Fungible(u128::MAX) })) + .chain((0..holding_non_fungibles).map(|i| Asset { + id: GeneralIndex(i as u128).into(), fun: NonFungible(asset_instance_from(i)), })) .collect::>(); - assets.push(MultiAsset { - id: Concrete(TokenLocation::get()), + assets.push(Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }); assets.into() @@ -1375,12 +1530,18 @@ impl_runtime_apis! { } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( TokenLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(TokenLocation::get()) }, + Asset { fun: Fungible(UNITS), id: AssetId(TokenLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + // AssetHubRococo trusts AssetHubWestend as reserve for WNDs + pub TrustedReserve: Option<(Location, Asset)> = Some( + ( + xcm_config::bridging::to_westend::AssetHubWestend::get(), + Asset::from((xcm_config::bridging::to_westend::WndLocation::get(), 1000000000000 as u128)) + ) + ); } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -1390,9 +1551,9 @@ impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(TokenLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(UNITS), } } @@ -1406,42 +1567,49 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(xcm::v4::Assets, xcm::v4::Assets), BenchmarkError> { Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { match xcm_config::bridging::BridgingBenchmarksHelper::prepare_universal_alias() { Some(alias) => Ok(alias), None => Err(BenchmarkError::Skip) } } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { Ok((TokenLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(TokenLocation::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + fn claimable_asset() -> Result<(Location, Location, xcm::v4::Assets), BenchmarkError> { let origin = TokenLocation::get(); - let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + let assets: xcm::v4::Assets = (TokenLocation::get(), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(TokenLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { Err(BenchmarkError::Skip) } } @@ -1453,8 +1621,7 @@ impl_runtime_apis! { type Foreign = pallet_assets::Pallet::; type Pool = pallet_assets::Pallet::; - type ToWococo = XcmBridgeHubRouterBench; - type ToRococo = XcmBridgeHubRouterBench; + type ToWestend = XcmBridgeHubRouterBench; let whitelist: Vec = vec![ // Block Number @@ -1478,6 +1645,16 @@ impl_runtime_apis! { 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! { @@ -1497,6 +1674,7 @@ parameter_types! { impl pallet_state_trie_migration::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; type SignedDepositPerItem = MigrationSignedDepositPerItem; type SignedDepositBase = MigrationSignedDepositBase; // An origin that can control the whole pallet: should be Root, or a part of your council. @@ -1535,9 +1713,9 @@ fn ensure_key_ss58() { mod tests { use super::*; use crate::{CENTS, MILLICENTS}; - use parachains_common::rococo::fee; use sp_runtime::traits::Zero; use sp_weights::WeightToFee; + use testnet_parachains_constants::rococo::fee; /// We can fit at least 1000 transfers in a block. #[test] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1e5c6a74293995b6e3702f19b8b3750c34b192f --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,80 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// statemine-dev +// --pallet +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/assets/statemine/src/weights +// --steps +// 50 +// --repeat +// 20 + +#![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/assets/asset-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs index 26ce4c8c9c5f6c30339f465ff96b7a9e0f8245d3..70fc3617bce93e2fa9246a649cd3adf81c1cda23 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -1,43 +1,38 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 `cumulus_pallet_xcmp_queue` //! //! 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-09-15, 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("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// ./target/release/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// bridge-hub-rococo-dev +// --output +// cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs +// --extrinsic +// #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,22 +51,98 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 5_467_000 picoseconds. - Weight::from_parts(5_634_000, 0) + // 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:1) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { + /// 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: 5_409_000 picoseconds. - Weight::from_parts(5_570_000, 0) + // 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/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 59f97d2c8e5c3a6777c8077a1b20e75e7ca69f1a..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 @@ -15,6 +15,7 @@ // along with Cumulus. If not, see . pub mod block_weights; +pub mod cumulus_pallet_parachain_system; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; @@ -24,6 +25,7 @@ pub mod pallet_assets_local; pub mod pallet_assets_pool; pub mod pallet_balances; pub mod pallet_collator_selection; +pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_nft_fractionalization; pub mod pallet_nfts; @@ -33,13 +35,11 @@ pub mod pallet_timestamp; pub mod pallet_uniques; pub mod pallet_utility; pub mod pallet_xcm; -pub mod pallet_xcm_bridge_hub_router_to_rococo; -pub mod pallet_xcm_bridge_hub_router_to_wococo; +pub mod pallet_xcm_bridge_hub_router; 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 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_balances.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs index d373d0f8e651137360382a995db368db8eac1fed..299a801ebd5964829271d6575a60a123f156b7e3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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: `runner-8idpd4bs-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 +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 55_040_000 picoseconds. - Weight::from_parts(56_106_000, 0) + // Minimum execution time: 42_706_000 picoseconds. + Weight::from_parts(43_378_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_342_000 picoseconds. - Weight::from_parts(41_890_000, 0) + // Minimum execution time: 33_090_000 picoseconds. + Weight::from_parts(33_703_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_723_000 picoseconds. - Weight::from_parts(15_182_000, 0) + // Minimum execution time: 12_678_000 picoseconds. + Weight::from_parts(13_068_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 22_073_000 picoseconds. - Weight::from_parts(22_638_000, 0) + // Minimum execution time: 17_336_000 picoseconds. + Weight::from_parts(17_824_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_265_000 picoseconds. - Weight::from_parts(58_222_000, 0) + // Minimum execution time: 44_817_000 picoseconds. + Weight::from_parts(45_453_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 51_485_000 picoseconds. - Weight::from_parts(52_003_000, 0) + // Minimum execution time: 41_468_000 picoseconds. + Weight::from_parts(42_093_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 17_460_000 picoseconds. - Weight::from_parts(17_849_000, 0) + // Minimum execution time: 15_344_000 picoseconds. + Weight::from_parts(15_878_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_259_000 picoseconds. - Weight::from_parts(17_478_000, 0) + // Minimum execution time: 15_067_000 picoseconds. + Weight::from_parts(15_281_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 16_756 - .saturating_add(Weight::from_parts(15_291_954, 0).saturating_mul(u.into())) + // Standard Error: 11_009 + .saturating_add(Weight::from_parts(13_050_024, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_139_000 picoseconds. + Weight::from_parts(5_511_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_collator_selection.rs index d98abbbc2d3db26bb757597f9e859752cfa8ed88..aeda7bbbb6a7e47d8d1e1f403c7419439f7f5953 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_collator_selection.rs @@ -124,7 +124,7 @@ impl pallet_collator_selection::WeightInfo for WeightIn } /// 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() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -178,6 +178,30 @@ impl pallet_collator_selection::WeightInfo for WeightIn .saturating_add(T::DbWeight::get().reads(2)) .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) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..45531ccfa797c52ead00833f765347f1282816c7 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-24, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// statemine-dev +// --pallet +// pallet_message_queue +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/assets/statemine/src/weights + +#![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/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index 909d7f2890784a0f72477c6a8d9b329a357ea3e2..51b6543bae82ba496998706ab6c2aaf6e0ff604b 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 @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-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/assets/asset-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=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/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,35 +62,97 @@ 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: `109` - // Estimated: `3574` - // Minimum execution time: 30_015_000 picoseconds. - Weight::from_parts(30_576_000, 0) - .saturating_add(Weight::from_parts(0, 3574)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 22_136_000 picoseconds. + Weight::from_parts(22_518_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: 24_785_000 picoseconds. - Weight::from_parts(25_097_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 92_277_000 picoseconds. + Weight::from_parts(94_843_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: 18_561_000 picoseconds. - Weight::from_parts(19_121_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `400` + // Estimated: `6196` + // Minimum execution time: 120_110_000 picoseconds. + Weight::from_parts(122_968_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: 143_116_000 picoseconds. + Weight::from_parts(147_355_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`) @@ -108,8 +170,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_298_000 picoseconds. - Weight::from_parts(9_721_000, 0) + // Minimum execution time: 6_517_000 picoseconds. + Weight::from_parts(6_756_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -119,8 +181,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_912_000 picoseconds. - Weight::from_parts(3_262_000, 0) + // Minimum execution time: 1_894_000 picoseconds. + Weight::from_parts(2_024_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -128,6 +190,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) @@ -142,16 +206,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: `109` - // Estimated: `3574` - // Minimum execution time: 35_127_000 picoseconds. - Weight::from_parts(36_317_000, 0) - .saturating_add(Weight::from_parts(0, 3574)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 27_314_000 picoseconds. + Weight::from_parts(28_787_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .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) @@ -166,12 +232,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: `326` - // Estimated: `3791` - // Minimum execution time: 36_634_000 picoseconds. - Weight::from_parts(37_983_000, 0) - .saturating_add(Weight::from_parts(0, 3791)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `363` + // Estimated: `3828` + // Minimum execution time: 29_840_000 picoseconds. + Weight::from_parts(30_589_000, 0) + .saturating_add(Weight::from_parts(0, 3828)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) @@ -180,48 +246,50 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_940_000 picoseconds. - Weight::from_parts(3_085_000, 0) + // Minimum execution time: 1_893_000 picoseconds. + Weight::from_parts(2_017_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `162` - // Estimated: `11052` - // Minimum execution time: 17_400_000 picoseconds. - Weight::from_parts(17_759_000, 0) - .saturating_add(Weight::from_parts(0, 11052)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `159` + // Estimated: `13524` + // Minimum execution time: 19_211_000 picoseconds. + Weight::from_parts(19_552_000, 0) + .saturating_add(Weight::from_parts(0, 13524)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `166` - // Estimated: `11056` - // Minimum execution time: 17_287_000 picoseconds. - Weight::from_parts(17_678_000, 0) - .saturating_add(Weight::from_parts(0, 11056)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `163` + // Estimated: `13528` + // Minimum execution time: 19_177_000 picoseconds. + Weight::from_parts(19_704_000, 0) + .saturating_add(Weight::from_parts(0, 13528)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `13538` - // Minimum execution time: 18_941_000 picoseconds. - Weight::from_parts(19_285_000, 0) - .saturating_add(Weight::from_parts(0, 13538)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16013` + // Minimum execution time: 20_449_000 picoseconds. + Weight::from_parts(21_075_000, 0) + .saturating_add(Weight::from_parts(0, 16013)) + .saturating_add(T::DbWeight::get().reads(6)) } /// 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) @@ -234,39 +302,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: `176` - // Estimated: `6116` - // Minimum execution time: 32_668_000 picoseconds. - Weight::from_parts(33_533_000, 0) - .saturating_add(Weight::from_parts(0, 6116)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `212` + // Estimated: `6152` + // Minimum execution time: 26_578_000 picoseconds. + Weight::from_parts(27_545_000, 0) + .saturating_add(Weight::from_parts(0, 6152)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:3 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `206` - // Estimated: `8621` - // Minimum execution time: 9_182_000 picoseconds. - Weight::from_parts(9_498_000, 0) - .saturating_add(Weight::from_parts(0, 8621)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11096` + // Minimum execution time: 11_646_000 picoseconds. + Weight::from_parts(11_944_000, 0) + .saturating_add(Weight::from_parts(0, 11096)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `11063` - // Minimum execution time: 17_519_000 picoseconds. - Weight::from_parts(17_943_000, 0) - .saturating_add(Weight::from_parts(0, 11063)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `170` + // Estimated: `13535` + // Minimum execution time: 19_301_000 picoseconds. + Weight::from_parts(19_664_000, 0) + .saturating_add(Weight::from_parts(0, 13535)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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) @@ -279,12 +349,50 @@ 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: `179` - // Estimated: `11069` - // Minimum execution time: 38_680_000 picoseconds. - Weight::from_parts(39_984_000, 0) - .saturating_add(Weight::from_parts(0, 11069)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `212` + // Estimated: `13577` + // Minimum execution time: 35_715_000 picoseconds. + Weight::from_parts(36_915_000, 0) + .saturating_add(Weight::from_parts(0, 13577)) + .saturating_add(T::DbWeight::get().reads(11)) .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: 4_871_000 picoseconds. + Weight::from_parts(5_066_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: 25_150_000 picoseconds. + Weight::from_parts(26_119_000, 0) + .saturating_add(Weight::from_parts(0, 11205)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `3625` + // Minimum execution time: 38_248_000 picoseconds. + Weight::from_parts(39_122_000, 0) + .saturating_add(Weight::from_parts(0, 3625)) + .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_to_wococo.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs similarity index 64% rename from cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router_to_wococo.rs rename to cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs index 1ee0e93371556d09b3a4a6bbf7f3fdb721fa20eb..775bc3bdb80f54a8db97d1c1fdbf5a837fdb95b1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router_to_wococo.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-09-19, 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-guclnr1q-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: @@ -48,59 +48,59 @@ 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: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) - /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ToWococoXcmRouter::Bridge` (r:1 w:1) - /// Proof: `ToWococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `3663` - // Minimum execution time: 10_936_000 picoseconds. - Weight::from_parts(11_432_000, 0) - .saturating_add(Weight::from_parts(0, 3663)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `154` + // Estimated: `1639` + // 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)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `150` - // Estimated: `3615` - // Minimum execution time: 5_165_000 picoseconds. - Weight::from_parts(5_356_000, 0) - .saturating_add(Weight::from_parts(0, 3615)) + // Measured: `144` + // Estimated: `1629` + // 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)) } - /// Storage: `ToWococoXcmRouter::Bridge` (r:1 w:1) - /// Proof: `ToWococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn report_bridge_status() -> Weight { // Proof Size summary in bytes: - // Measured: `83` + // Measured: `150` // Estimated: `1502` - // Minimum execution time: 11_420_000 picoseconds. - Weight::from_parts(11_946_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: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `ToWococoXcmRouter::Bridge` (r:1 w:1) - /// Proof: `ToWococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) - /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// 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::VersionDiscoveryQueue` (r:1 w:1) /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) @@ -109,16 +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::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: `392` - // Estimated: `3857` - // Minimum execution time: 52_129_000 picoseconds. - Weight::from_parts(53_552_000, 0) - .saturating_add(Weight::from_parts(0, 3857)) - .saturating_add(T::DbWeight::get().reads(9)) + // 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/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs index 22c2f2450b29642fce8eadbbca294b4aa90b2425..8e675ad0cf8e627a1f547a181db1737767e84d7c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs @@ -24,14 +24,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,48 +50,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } pub struct AssetHubRococoXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for AssetHubRococoXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) - } - // Currently there is no trusted reserve (`IsReserve = ()`), - // but we need this hack for `pallet_xcm::reserve_transfer_assets` - // (TODO) fix https://github.com/paritytech/polkadot/pull/7424 - // (TODO) fix https://github.com/paritytech/polkadot/pull/7546 - fn reserve_asset_deposited(_assets: &MultiAssets) -> Weight { - // TODO: if we change `IsReserve = ...` then use this line... - // TODO: or if remote weight estimation is fixed, then remove - // TODO: hardcoded - fix https://github.com/paritytech/cumulus/issues/1974 - let hardcoded_weight = Weight::from_parts(1_000_000_000_u64, 0); - hardcoded_weight.min(XcmFungibleWeight::::reserve_asset_deposited()) - } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) + } + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) + } + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -119,47 +107,35 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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 deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) + } + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -174,7 +150,7 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -186,13 +162,13 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -225,16 +201,16 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -246,11 +222,11 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 9d95048c0ac2d9ec2486d81db326ca47cc9b331c..7fab35842509deceba14e89e5bbf6bebe2240528 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,28 +17,26 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! 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-11-15, 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("asset-hub-rococo-dev"), DB CACHE: 1024 +//! 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 +// target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=asset-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::fungible -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +// --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)] @@ -56,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 26_104_000 picoseconds. - Weight::from_parts(26_722_000, 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)) } @@ -67,15 +65,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 52_259_000 picoseconds. - Weight::from_parts(53_854_000, 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:2 w: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) @@ -88,49 +88,54 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `210` - // Estimated: `6196` - // Minimum execution time: 77_248_000 picoseconds. - Weight::from_parts(80_354_000, 6196) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) + // 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: `Benchmark::Override` (r:0 w:0) - // Proof: `Benchmark::Override` (`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`) pub fn reserve_asset_deposited() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 500_000_000_000 picoseconds. - Weight::from_parts(500_000_000_000, 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: `109` - // Estimated: `3574` - // Minimum execution time: 482_070_000 picoseconds. - Weight::from_parts(490_269_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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_970_000 picoseconds. - Weight::from_parts(4_056_000, 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`) @@ -138,15 +143,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 26_324_000 picoseconds. - Weight::from_parts(26_985_000, 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:1 w: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) @@ -159,32 +166,36 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3593` - // Minimum execution time: 52_814_000 picoseconds. - Weight::from_parts(54_666_000, 3593) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // 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: `109` - // Estimated: `3574` - // Minimum execution time: 33_044_000 picoseconds. - Weight::from_parts(33_849_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 2c51afde7b34afbf689d3a66838c5f67779b0444..4454494badcbfe9b4f429312e24b63786b83ef75 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,28 +17,26 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! 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-11-15, 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("asset-hub-rococo-dev"), DB CACHE: 1024 +//! 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 +// target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=asset-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::generic -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --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)] @@ -52,31 +50,35 @@ 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: `109` - // Estimated: `3574` - // Minimum execution time: 432_196_000 picoseconds. - Weight::from_parts(438_017_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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: 4_223_000 picoseconds. - Weight::from_parts(4_412_000, 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`) @@ -84,79 +86,83 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3568` - // Minimum execution time: 11_582_000 picoseconds. - Weight::from_parts(11_830_000, 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: 13_955_000 picoseconds. - Weight::from_parts(14_320_000, 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: 4_423_000 picoseconds. - Weight::from_parts(4_709_000, 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: 3_028_000 picoseconds. - Weight::from_parts(3_151_000, 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_966_000 picoseconds. - Weight::from_parts(3_076_000, 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_971_000 picoseconds. - Weight::from_parts(3_119_000, 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: 3_772_000 picoseconds. - Weight::from_parts(3_853_000, 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_940_000 picoseconds. - Weight::from_parts(3_050_000, 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: `109` - // Estimated: `3574` - // Minimum execution time: 27_734_000 picoseconds. - Weight::from_parts(28_351_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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`) @@ -164,8 +170,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 16_456_000 picoseconds. - Weight::from_parts(16_846_000, 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)) } @@ -173,11 +179,13 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_974_000 picoseconds. - Weight::from_parts(3_108_000, 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) @@ -190,11 +198,11 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 29_823_000 picoseconds. - Weight::from_parts(30_776_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) + // 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) @@ -203,114 +211,122 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_966_000 picoseconds. - Weight::from_parts(5_157_000, 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: 141_875_000 picoseconds. - Weight::from_parts(144_925_000, 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: 13_147_000 picoseconds. - Weight::from_parts(13_420_000, 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: 3_050_000 picoseconds. - Weight::from_parts(3_161_000, 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_930_000 picoseconds. - Weight::from_parts(3_077_000, 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: 3_188_000 picoseconds. - Weight::from_parts(3_299_000, 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: `109` - // Estimated: `3574` - // Minimum execution time: 31_678_000 picoseconds. - Weight::from_parts(32_462_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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_638_000 picoseconds. - Weight::from_parts(5_756_000, 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: `109` - // Estimated: `3574` - // Minimum execution time: 27_556_000 picoseconds. - Weight::from_parts(28_240_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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_932_000 picoseconds. - Weight::from_parts(3_097_000, 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_860_000 picoseconds. - Weight::from_parts(2_957_000, 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_886_000 picoseconds. - Weight::from_parts(3_015_000, 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`) @@ -318,22 +334,22 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1489` - // Minimum execution time: 5_088_000 picoseconds. - Weight::from_parts(5_253_000, 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_874_000 picoseconds. - Weight::from_parts(3_060_000, 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: 3_029_000 picoseconds. - Weight::from_parts(3_158_000, 0) + // Minimum execution time: 2_265_000 picoseconds. + Weight::from_parts(2_332_000, 0) } } 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 ae4a275d43ac45ba087d58ecdd828c2f8a7e057d..c438361cc177eabe2bfc9056621d21e25db42db1 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 @@ -15,86 +15,84 @@ use super::{ AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee, - FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, ParachainSystem, PolkadotXcm, - PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeFlavor, RuntimeOrigin, - ToRococoXcmRouter, ToWococoXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, - WeightToFee, XcmpQueue, + CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, + ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + ToWestendXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, Uniques, WeightToFee, + XcmpQueue, }; use assets_common::{ - local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, - matching::{FromSiblingParachain, IsForeignConcreteAsset}, + matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, + TrustBackedAssetsAsLocation, }; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Equals, Everything, Get, Nothing, PalletInfoAccess}, + parameter_types, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing, + PalletInfoAccess, + }, }; use frame_system::EnsureRoot; 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::SystemParachains; +use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; +use testnet_parachains_constants::rococo::snowbridge::{ + EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, +}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllAssets, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, - IsConcrete, LocalMint, LocationWithAssetFilters, NetworkExportTableItem, NoChecking, + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, + IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeesToAccount, + 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 storage Flavor: RuntimeFlavor = RuntimeFlavor::default(); - pub const TokenLocation: MultiLocation = MultiLocation::parent(); + pub const TokenLocation: Location = Location::parent(); + pub const TokenLocationV3: xcm::v3::Location = xcm::v3::Location::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Rococo; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = - X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); - pub TrustBackedAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); - pub ForeignAssetsPalletLocation: MultiLocation = + pub TrustBackedAssetsPalletLocation: Location = + PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); + pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); + pub ForeignAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); - pub PoolAssetsPalletLocation: MultiLocation = + pub PoolAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); + pub UniquesPalletLocation: Location = + PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); - pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); - pub TreasuryAccount: Option = Some(TREASURY_PALLET_ID.into_account_truncating()); + pub const GovernanceLocation: Location = Location::parent(); + pub StakingPot: AccountId = CollatorSelection::account_id(); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); } -/// Adapter for resolving `NetworkId` based on `pub storage Flavor: RuntimeFlavor`. -pub struct RelayNetwork; -impl Get> for RelayNetwork { - fn get() -> Option { - Some(Self::get()) - } -} -impl Get for RelayNetwork { - fn get() -> NetworkId { - match Flavor::get() { - RuntimeFlavor::Rococo => NetworkId::Rococo, - RuntimeFlavor::Wococo => NetworkId::Wococo, - } - } -} - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -109,15 +107,18 @@ 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. -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -125,7 +126,7 @@ pub type CurrencyTransactor = CurrencyAdapter< (), >; -/// `AssetId`/`Balance` converter for `PoolAssets`. +/// `AssetId`/`Balance` converter for `TrustBackedAssets`. pub type TrustBackedAssetsConvertedConcreteId = assets_common::TrustBackedAssetsConvertedConcreteId; @@ -135,7 +136,7 @@ pub type FungiblesTransactor = FungiblesAdapter< Assets, // Use this currency when it is a fungible asset matching the given location or name: TrustBackedAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -146,14 +147,34 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; -/// `AssetId/Balance` converter for `TrustBackedAssets` +/// Matcher for converting `ClassId`/`InstanceId` into a uniques asset. +pub type UniquesConvertedConcreteId = + assets_common::UniquesConvertedConcreteId; + +/// Means for transacting unique assets. +pub type UniquesTransactor = NonFungiblesAdapter< + // Use this non-fungibles implementation: + Uniques, + // This adapter will handle any non-fungible asset from the uniques pallet. + UniquesConvertedConcreteId, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // Does not check teleports. + NoChecking, + // The account to use for tracking teleports. + CheckingAccount, +>; + +/// `AssetId`/`Balance` converter for `ForeignAssets`. pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId< ( // Ignore `TrustBackedAssets` explicitly StartsWith, // Ignore assets that start explicitly with our `GlobalConsensus(NetworkId)`, means: - // - foreign assets from our consensus should be: `MultiLocation {parents: 1, - // X*(Parachain(xyz), ..)}` + // - foreign assets from our consensus should be: `Location {parents: 1, X*(Parachain(xyz), + // ..)}` // - foreign assets outside our consensus with the same `GlobalConsensus(NetworkId)` won't // be accepted here StartsWithExplicitGlobalConsensus, @@ -167,7 +188,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< ForeignAssets, // Use this currency when it is a fungible asset matching the given location or name: ForeignAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -187,7 +208,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< PoolAssets, // Use this currency when it is a fungible asset matching the given location or name: PoolAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -199,26 +220,13 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< >; /// Means for transacting assets on this chain. -pub type AssetTransactors = - (CurrencyTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor); - -/// Simple `MultiLocation` matcher for Local and Foreign asset `MultiLocation`. -pub struct LocalAndForeignAssetsMultiLocationMatcher; -impl MatchesLocalAndForeignAssetsMultiLocation for LocalAndForeignAssetsMultiLocationMatcher { - fn is_local(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - TrustBackedAssetsConvertedConcreteId::contains(location) - } - fn is_foreign(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - ForeignAssetsConvertedConcreteId::contains(location) - } -} -impl Contains for LocalAndForeignAssetsMultiLocationMatcher { - fn contains(location: &MultiLocation) -> bool { - Self::is_local(location) || Self::is_foreign(location) - } -} +pub type AssetTransactors = ( + FungibleTransactor, + FungiblesTransactor, + ForeignFungiblesTransactor, + PoolFungiblesTransactor, + UniquesTransactor, +); /// 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 @@ -250,19 +258,11 @@ parameter_types! { pub XcmAssetFeesReceiver: Option = Authorship::author(); } -match_types! { - 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(_) } - }; - pub type WithParentsZeroOrOne: impl Contains = { - MultiLocation { parents: 0, .. } | - MultiLocation { parents: 1, .. } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -285,34 +285,34 @@ 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(&Flavor::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, _ => (), }; 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::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( - 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::DmpQueue(..) | + RuntimeCall::MessageQueue(..) | RuntimeCall::Assets( pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } | @@ -475,9 +475,7 @@ impl Contains for SafeCallFilter { pallet_uniques::Call::set_collection_max_supply { .. } | pallet_uniques::Call::set_price { .. } | pallet_uniques::Call::buy_item { .. } - ) | RuntimeCall::ToWococoXcmRouter( - pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } - ) | RuntimeCall::ToRococoXcmRouter( + ) | RuntimeCall::ToWestendXcmRouter( pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } ) ) @@ -497,14 +495,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 BridgeHub get free - // execution. + // Parent, its pluralities (i.e. governance bodies), relay treasury pallet and + // BridgeHub get free execution. AllowExplicitUnpaidExecutionFrom<( ParentOrParentsPlurality, + Equals, Equals, )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -530,22 +529,13 @@ pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = ForeignAssetsInstance, >; -parameter_types! { - pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); -} - -pub struct RelayTreasury; -impl Contains for RelayTreasury { - fn contains(location: &MultiLocation) -> bool { - let relay_treasury_location = RelayTreasuryLocation::get(); - *location == relay_treasury_location - } -} - /// 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, RelayTreasury); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// @@ -562,12 +552,13 @@ impl xcm_executor::Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub trusts only particular configured bridge locations as reserve locations. - // 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). + // Asset Hub trusts only particular, pre-configured bridged locations from a different consensus + // 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_wococo::IsTrustedBridgedReserveLocationForConcreteAsset, - bridging::to_rococo::IsTrustedBridgedReserveLocationForConcreteAsset, + bridging::to_westend::IsTrustedBridgedReserveLocationForConcreteAsset, + bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, ); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; @@ -579,6 +570,18 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + cumulus_primitives_utility::SwapFirstAssetTrader< + TokenLocationV3, + crate::AssetConversion, + WeightToFee, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, + AccountId, + >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< @@ -614,16 +617,23 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); - type FeeManager = XcmFeesToAccount; + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); type UniversalAliases = - (bridging::to_wococo::UniversalAliases, bridging::to_rococo::UniversalAliases); + (bridging::to_westend::UniversalAliases, bridging::to_ethereum::UniversalAliases); type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } -/// Converts a local signed origin into an XCM multilocation. +/// Converts a local signed origin into an XCM location. /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; @@ -642,19 +652,14 @@ type LocalXcmRouter = ( /// queues. pub type XcmRouter = WithUniqueTopic<( LocalXcmRouter, - // Router which wraps and sends xcm to BridgeHub to be delivered to the Wococo + // Router which wraps and sends xcm to BridgeHub to be delivered to the Westend // GlobalConsensus - ToWococoXcmRouter, - // Router which wraps and sends xcm to BridgeHub to be delivered to the Rococo + ToWestendXcmRouter, + // Router which wraps and sends xcm to BridgeHub to be delivered to the Ethereum // GlobalConsensus - ToRococoXcmRouter, + SovereignPaidRemoteExporter, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // We want to disallow users sending (arbitrary) XCMs from this chain. @@ -667,14 +672,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecuteFilter = Nothing; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; - // Allow reserve based transfer to everywhere except for bridging, here we strictly check what - // assets are allowed. - type XcmReserveTransferFilter = ( - LocationWithAssetFilters, - bridging::to_rococo::AllowedReserveTransferAssets, - bridging::to_wococo::AllowedReserveTransferAssets, - ); - + type XcmReserveTransferFilter = Everything; type Weigher = WeightInfoBounds< crate::weights::xcm::AssetHubRococoXcmWeight, RuntimeCall, @@ -691,8 +689,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -707,41 +703,15 @@ pub type ForeignCreatorsSovereignAccountOf = ( SiblingParachainConvertsVia, AccountId32Aliases, ParentIsPreset, + GlobalConsensusEthereumConvertsFor, ); /// 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)) } - } -} - -#[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)) +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> xcm::v3::Location { + xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) } } @@ -751,83 +721,96 @@ pub mod bridging { use assets_common::matching; use sp_std::collections::btree_set::BTreeSet; - // common/shared parameters for Wococo/Rococo + // common/shared parameters parameter_types! { - pub SiblingBridgeHubParaId: u32 = match Flavor::get() { - RuntimeFlavor::Rococo => bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - RuntimeFlavor::Wococo => bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, - }; - pub SiblingBridgeHub: MultiLocation = MultiLocation::new(1, X1(Parachain(SiblingBridgeHubParaId::get()))); + /// Base price of every byte of the Rococo -> Westend message. Can be adjusted via + /// governance `set_storage` call. + /// + /// Default value is our estimation of the: + /// + /// 1) an approximate cost of XCM execution (`ExportMessage` and surroundings) at Rococo bridge hub; + /// + /// 2) the approximate cost of Rococo -> Westend message delivery transaction on Westend Bridge Hub, + /// converted into ROCs using 1:1 conversion rate; + /// + /// 3) the approximate cost of Rococo -> Westend message confirmation transaction on Rococo Bridge Hub. + pub storage XcmBridgeHubRouterBaseFee: Balance = + bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get() + .saturating_add(bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get()) + .saturating_add(bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get()); + /// Price of every byte of the Rococo -> Westend message. Can be adjusted via + /// governance `set_storage` call. + pub storage XcmBridgeHubRouterByteFee: Balance = TransactionByteFee::get(); + + pub SiblingBridgeHubParaId: u32 = bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID; + pub SiblingBridgeHub: Location = Location::new(1, [Parachain(SiblingBridgeHubParaId::get())]); /// Router expects payment with this `AssetId`. /// (`AssetId` has to be aligned with `BridgeTable`) pub XcmBridgeHubRouterFeeAssetId: AssetId = TokenLocation::get().into(); - /// Price per byte - can be adjusted via governance `set_storage` call. - pub storage XcmBridgeHubRouterByteFee: Balance = TransactionByteFee::get(); pub BridgeTable: sp_std::vec::Vec = sp_std::vec::Vec::new().into_iter() - .chain(to_wococo::BridgeTable::get()) - .chain(to_rococo::BridgeTable::get()) + .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 mod to_wococo { + pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable; + + pub mod to_westend { use super::*; parameter_types! { - pub SiblingBridgeHubWithBridgeHubWococoInstance: MultiLocation = MultiLocation::new( + pub SiblingBridgeHubWithBridgeHubWestendInstance: Location = Location::new( 1, - X2( + [ Parachain(SiblingBridgeHubParaId::get()), - PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_INDEX) - ) + PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX) + ] ); - pub const WococoNetwork: NetworkId = NetworkId::Wococo; - pub AssetHubWococo: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(WococoNetwork::get()), Parachain(bp_asset_hub_wococo::ASSET_HUB_WOCOCO_PARACHAIN_ID))); - pub WocLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(WococoNetwork::get()))); + pub const WestendNetwork: NetworkId = NetworkId::Westend; + pub AssetHubWestend: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get()), Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)]); + pub WndLocation: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get())]); - pub WocFromAssetHubWococo: (MultiAssetFilter, MultiLocation) = ( - Wild(AllOf { fun: WildFungible, id: Concrete(WocLocation::get()) }), - AssetHubWococo::get() + pub WndFromAssetHubWestend: (AssetFilter, Location) = ( + Wild(AllOf { fun: WildFungible, id: AssetId(WndLocation::get()) }), + AssetHubWestend::get() ); /// Set up exporters configuration. - /// `Option` represents static "base fee" which is used for total delivery fee calculation. + /// `Option` represents static "base fee" which is used for total delivery fee calculation. pub BridgeTable: sp_std::vec::Vec = sp_std::vec![ NetworkExportTableItem::new( - WococoNetwork::get(), + WestendNetwork::get(), Some(sp_std::vec![ - AssetHubWococo::get().interior.split_global().expect("invalid configuration for AssetHubWococo").1, + AssetHubWestend::get().interior.split_global().expect("invalid configuration for AssetHubWestend").1, ]), SiblingBridgeHub::get(), // base delivery fee to local `BridgeHub` Some(( XcmBridgeHubRouterFeeAssetId::get(), - bp_asset_hub_rococo::BridgeHubRococoBaseFeeInRocs::get(), + XcmBridgeHubRouterBaseFee::get(), ).into()) ) ]; - /// Allowed assets for reserve transfer to `AssetHubWococo`. - pub AllowedReserveTransferAssetsToAssetHubWococo: sp_std::vec::Vec = sp_std::vec![ - // allow send only ROC - Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }), - // and nothing else - ]; - /// Universal aliases - pub UniversalAliases: BTreeSet<(MultiLocation, Junction)> = BTreeSet::from_iter( + pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( sp_std::vec![ - (SiblingBridgeHubWithBridgeHubWococoInstance::get(), GlobalConsensus(WococoNetwork::get())) + (SiblingBridgeHubWithBridgeHubWestendInstance::get(), GlobalConsensus(WestendNetwork::get())) ] ); } - impl Contains<(MultiLocation, Junction)> for UniversalAliases { - fn contains(alias: &(MultiLocation, Junction)) -> bool { + impl Contains<(Location, Junction)> for UniversalAliases { + fn contains(alias: &(Location, Junction)) -> bool { UniversalAliases::get().contains(alias) } } @@ -838,23 +821,17 @@ pub mod bridging { matching::IsTrustedBridgedReserveLocationForConcreteAsset< UniversalLocation, ( - // allow receive WOC from AssetHubWococo - xcm_builder::Case, + // allow receive WND from AssetHubWestend + xcm_builder::Case, // and nothing else ), >; - /// Allows to reserve transfer assets to `AssetHubWococo`. - pub type AllowedReserveTransferAssets = LocationWithAssetFilters< - Equals, - AllowedReserveTransferAssetsToAssetHubWococo, - >; - - impl Contains for ToWococoXcmRouter { + impl Contains for ToWestendXcmRouter { fn contains(call: &RuntimeCall) -> bool { matches!( call, - RuntimeCall::ToWococoXcmRouter( + RuntimeCall::ToWestendXcmRouter( pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } ) ) @@ -862,91 +839,52 @@ pub mod bridging { } } - pub mod to_rococo { + pub mod to_ethereum { use super::*; parameter_types! { - pub SiblingBridgeHubWithBridgeHubRococoInstance: MultiLocation = MultiLocation::new( + /// 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: Location = Location::new( 1, - X2( + [ Parachain(SiblingBridgeHubParaId::get()), - PalletInstance(bp_bridge_hub_wococo::WITH_BRIDGE_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_INDEX) - ) - ); - - pub const RococoNetwork: NetworkId = NetworkId::Rococo; - pub AssetHubRococo: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(RococoNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID))); - pub RocLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(RococoNetwork::get()))); - - pub RocFromAssetHubRococo: (MultiAssetFilter, MultiLocation) = ( - Wild(AllOf { fun: WildFungible, id: Concrete(RocLocation::get()) }), - AssetHubRococo::get() + PalletInstance(INBOUND_QUEUE_PALLET_INDEX) + ] ); /// Set up exporters configuration. - /// `Option` represents static "base fee" which is used for total delivery fee calculation. + /// `Option` represents static "base fee" which is used for total delivery fee calculation. pub BridgeTable: sp_std::vec::Vec = sp_std::vec![ NetworkExportTableItem::new( - RococoNetwork::get(), - Some(sp_std::vec![ - AssetHubRococo::get().interior.split_global().expect("invalid configuration for AssetHubRococo").1, - ]), + EthereumNetwork::get(), + Some(sp_std::vec![Junctions::Here]), SiblingBridgeHub::get(), - // base delivery fee to local `BridgeHub` Some(( XcmBridgeHubRouterFeeAssetId::get(), - bp_asset_hub_wococo::BridgeHubWococoBaseFeeInWocs::get(), + BridgeHubEthereumBaseFee::get(), ).into()) - ) - ]; - - /// Allowed assets for reserve transfer to `AssetHubWococo`. - pub AllowedReserveTransferAssetsToAssetHubRococo: sp_std::vec::Vec = sp_std::vec![ - // allow send only WOC - Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }), - // and nothing else + ), ]; /// Universal aliases - pub UniversalAliases: BTreeSet<(MultiLocation, Junction)> = BTreeSet::from_iter( + pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( sp_std::vec![ - (SiblingBridgeHubWithBridgeHubRococoInstance::get(), GlobalConsensus(RococoNetwork::get())) + (SiblingBridgeHubWithEthereumInboundQueueInstance::get(), GlobalConsensus(EthereumNetwork::get())), ] ); } - impl Contains<(MultiLocation, Junction)> for UniversalAliases { - fn contains(alias: &(MultiLocation, Junction)) -> bool { - UniversalAliases::get().contains(alias) - } - } + pub type IsTrustedBridgedReserveLocationForForeignAsset = + matching::IsForeignConcreteAsset>; - /// Reserve locations filter for `xcm_executor::Config::IsReserve`. - /// Locations from which the runtime accepts reserved assets. - pub type IsTrustedBridgedReserveLocationForConcreteAsset = - matching::IsTrustedBridgedReserveLocationForConcreteAsset< - UniversalLocation, - ( - // allow receive ROC from AssetHubRococo - xcm_builder::Case, - // and nothing else - ), - >; - - /// Allows to reserve transfer assets to `AssetHubRococo`. - pub type AllowedReserveTransferAssets = LocationWithAssetFilters< - Equals, - AllowedReserveTransferAssetsToAssetHubRococo, - >; - - impl Contains for ToRococoXcmRouter { - fn contains(call: &RuntimeCall) -> bool { - matches!( - call, - RuntimeCall::ToRococoXcmRouter( - pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } - ) - ) + impl Contains<(Location, Junction)> for UniversalAliases { + fn contains(alias: &(Location, Junction)) -> bool { + UniversalAliases::get().contains(alias) } } } @@ -957,17 +895,19 @@ pub mod bridging { #[cfg(feature = "runtime-benchmarks")] impl BridgingBenchmarksHelper { - pub fn prepare_universal_alias() -> Option<(MultiLocation, Junction)> { + pub fn prepare_universal_alias() -> Option<(Location, Junction)> { let alias = - to_wococo::UniversalAliases::get().into_iter().find_map(|(location, junction)| { - match to_wococo::SiblingBridgeHubWithBridgeHubWococoInstance::get() - .eq(&location) - { - true => Some((location, junction)), - false => None, - } - }); - assert!(alias.is_some(), "we expect here BridgeHubRococo to Polkadot mapping at least"); + to_westend::UniversalAliases::get() + .into_iter() + .find_map(|(location, junction)| { + match to_westend::SiblingBridgeHubWithBridgeHubWestendInstance::get() + .eq(&location) + { + true => Some((location, junction)), + false => None, + } + }); + assert!(alias.is_some(), "we expect here BridgeHubRococo to Westend mapping at least"); Some(alias.unwrap()) } } 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 a763382f90541f32d7abbdcb5eb202ea2090173c..5fa7455ad2a0b5620a6f6934b87acfe3b85d4f57 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -17,40 +17,53 @@ //! Tests for the Rococo Assets Hub chain. -use asset_hub_rococo_runtime::xcm_config::{ - AssetFeeAsExistentialDepositMultiplierFeeCharger, TokenLocation, - TrustBackedAssetsPalletLocation, -}; -pub use asset_hub_rococo_runtime::{ +use asset_hub_rococo_runtime::{ + xcm_config, xcm_config::{ - self, bridging, CheckingAccount, ForeignCreatorsSovereignAccountOf, LocationToAccountId, - XcmConfig, + bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, + LocationToAccountId, StakingPot, TokenLocation, TokenLocationV3, + TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig, }, - AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, RuntimeFlavor, SessionKeys, System, ToRococoXcmRouterInstance, - ToWococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, + AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, + ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, + MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, +}; +use asset_test_utils::{ + test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, + ExtBuilder, SlotDurations, }; -use asset_test_utils::{CollatorSessionKey, CollatorSessionKeys, ExtBuilder}; use codec::{Decode, Encode}; use cumulus_primitives_utility::ChargeWeightInFungibles; use frame_support::{ assert_noop, assert_ok, - traits::{fungibles::InspectEnumerable, Contains}, + traits::{ + fungible::{Inspect, Mutate}, + fungibles::{ + Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate, + }, + }, weights::{Weight, WeightToFee as WeightToFeeT}, }; -use parachains_common::{ - rococo::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, -}; +use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; +use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; -use xcm::latest::prelude::*; -use xcm_executor::traits::{Identity, JustTry, WeightTrader}; +use sp_std::ops::Mul; +use std::convert::Into; +use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; +use xcm::latest::prelude::{Assets as XcmAssets, *}; +use xcm_builder::V4V3LocationConverter; +use xcm_executor::traits::{JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; + assets_common::AssetIdForTrustBackedAssetsConvert; + +type AssetIdForTrustBackedAssetsConvertLatest = + assets_common::AssetIdForTrustBackedAssetsConvertLatest; type RuntimeHelper = asset_test_utils::RuntimeHelper; @@ -66,8 +79,332 @@ fn collator_session_keys() -> CollatorSessionKeys { CollatorSessionKeys::default().add(collator_session_key(ALICE)) } +fn slot_durations() -> SlotDurations { + SlotDurations { + relay: SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS.into()), + para: SlotDuration::from_millis(SLOT_DURATION), + } +} + +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] -fn test_asset_xcm_trader() { +fn test_buy_and_refund_weight_in_native() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let native_location = TokenLocation::get(); + let initial_balance = 200 * UNITS; + + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + // keep initial total issuance to assert later. + let total_issuance = Balances::total_issuance(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (native_location.clone(), fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = + unused_asset.fungible.get(&native_location.clone().into()).map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Balances::total_issuance(), total_issuance); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (native_location, refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!(Balances::total_issuance(), total_issuance + fee - refund); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let asset_1: u32 = 1; + let native_location = TokenLocationV3::get(); + let asset_1_location = + AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap(); + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create(asset_1, bob.clone(), true, 10)); + + assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = Assets::total_issuance(asset_1); + let native_total_issuance = Balances::total_issuance(); + + let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&asset_1_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + Assets::total_issuance(asset_1), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let native_location = TokenLocationV3::get(); + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create( + foreign_location, + bob.clone(), + true, + 10 + )); + + assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let native_total_issuance = Balances::total_issuance(); + + let foreign_location_latest: Location = foreign_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (foreign_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&foreign_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee + ); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, foreign_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (foreign_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_asset_xcm_take_first_trader() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -96,9 +433,9 @@ fn test_asset_xcm_trader() { minimum_asset_balance )); - // get asset id as multilocation - let asset_multilocation = - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); + // get asset id as location + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); @@ -116,8 +453,8 @@ fn test_asset_xcm_trader() { // Lets pay with: asset_amount_needed + asset_amount_extra let asset_amount_extra = 100_u128; - let asset: MultiAsset = - (asset_multilocation, asset_amount_needed + asset_amount_extra).into(); + let asset: Asset = + (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); let mut trader = ::Trader::new(); let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; @@ -125,9 +462,7 @@ fn test_asset_xcm_trader() { // Lets buy_weight and make sure buy_weight does not return an error let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); // Check whether a correct amount of unused assets is returned - assert_ok!( - unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into()) - ); + assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); // Drop trader drop(trader); @@ -147,7 +482,92 @@ fn test_asset_xcm_trader() { } #[test] -fn test_asset_xcm_trader_with_refund() { +fn test_foreign_asset_xcm_take_first_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + let minimum_asset_balance = 3333333_u128; + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + assert_ok!(ForeignAssets::force_create( + RuntimeHelper::root_origin(), + foreign_location.into(), + AccountId::from(ALICE).into(), + true, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(ForeignAssets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + foreign_location.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + let asset_location_v4: Location = foreign_location.try_into().unwrap(); + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 4e9 weight + let bought = Weight::from_parts(4_000_000_000u64, 0); + + // Lets calculate amount needed + let asset_amount_needed + = ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( + foreign_location, + bought + ) + .expect("failed to compute"); + + // Lets pay with: asset_amount_needed + asset_amount_extra + let asset_amount_extra = 100_u128; + let asset: Asset = + (asset_location_v4.clone(), asset_amount_needed + asset_amount_extra).into(); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Lets buy_weight and make sure buy_weight does not return an error + let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); + // Check whether a correct amount of unused assets is returned + assert_ok!( + unused_assets.ensure_contains(&(asset_location_v4, asset_amount_extra).into()) + ); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!( + ForeignAssets::balance(foreign_location, AccountId::from(ALICE)), + minimum_asset_balance + asset_amount_needed + ); + + // We also need to ensure the total supply increased + assert_eq!( + ForeignAssets::total_supply(foreign_location), + minimum_asset_balance + asset_amount_needed + ); + }); +} + +#[test] +fn test_asset_xcm_take_first_trader_with_refund() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -184,12 +604,13 @@ fn test_asset_xcm_trader_with_refund() { // We are going to buy 4e9 weight let bought = Weight::from_parts(4_000_000_000u64, 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); // lets calculate amount needed let amount_bought = WeightToFee::weight_to_fee(&bought); - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location.clone(), amount_bought).into(); // Make sure buy_weight does not return an error assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); @@ -207,7 +628,7 @@ fn test_asset_xcm_trader_with_refund() { assert_eq!( trader.refund_weight(bought - weight_used, &ctx), - Some((asset_multilocation, amount_refunded).into()) + Some((asset_location, amount_refunded).into()) ); // Drop trader @@ -227,7 +648,7 @@ fn test_asset_xcm_trader_with_refund() { } #[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { +fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_ed() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -256,7 +677,8 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { // We are going to buy small amount let bought = Weight::from_parts(500_000_000u64, 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -265,7 +687,7 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { "we are testing what happens when the amount does not exceed ED" ); - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location, amount_bought).into(); // Buy weight should return an error assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); @@ -279,7 +701,7 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { } #[test] -fn test_that_buying_ed_refund_does_not_refund() { +fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -308,7 +730,8 @@ fn test_that_buying_ed_refund_does_not_refund() { // We are gonna buy ED let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -319,11 +742,11 @@ fn test_that_buying_ed_refund_does_not_refund() { // We know we will have to buy at least ED, so lets make sure first it will // fail with a payment of less than ED - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location.clone(), amount_bought).into(); assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); // Now lets buy ED at least - let asset: MultiAsset = (asset_multilocation, ExistentialDeposit::get()).into(); + let asset: Asset = (asset_location, ExistentialDeposit::get()).into(); // Buy weight should work assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); @@ -384,9 +807,10 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { // lets calculate amount needed let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - let asset: MultiAsset = (asset_multilocation, asset_amount_needed).into(); + let asset: Asset = (asset_location, asset_amount_needed).into(); // Make sure again buy_weight does return an error assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); @@ -416,19 +840,21 @@ fn test_assets_balances_api_works() { .build() .execute_with(|| { let local_asset_id = 1; - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) }; + let foreign_asset_id_location = xcm::v3::Location::new( + 1, + [xcm::v3::Junction::Parachain(1234), xcm::v3::Junction::GeneralIndex(12345)], + ); // check before assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), 0 ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); assert!(Runtime::query_account_balances(AccountId::from(ALICE)) .unwrap() - .try_as::() + .try_as::() .unwrap() .is_none()); @@ -459,7 +885,7 @@ fn test_assets_balances_api_works() { let foreign_asset_minimum_asset_balance = 3333333_u128; assert_ok!(ForeignAssets::force_create( RuntimeHelper::root_origin(), - foreign_asset_id_multilocation, + foreign_asset_id_location, AccountId::from(SOME_ASSET_ADMIN).into(), false, foreign_asset_minimum_asset_balance @@ -468,7 +894,7 @@ fn test_assets_balances_api_works() { // We first mint enough asset for the account to exist for assets assert_ok!(ForeignAssets::mint( RuntimeHelper::origin_of(AccountId::from(SOME_ASSET_ADMIN)), - foreign_asset_id_multilocation, + foreign_asset_id_location, AccountId::from(ALICE).into(), 6 * foreign_asset_minimum_asset_balance )); @@ -479,12 +905,12 @@ fn test_assets_balances_api_works() { minimum_asset_balance ); assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), 6 * minimum_asset_balance ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); - let result: MultiAssets = Runtime::query_account_balances(AccountId::from(ALICE)) + let result: XcmAssets = Runtime::query_account_balances(AccountId::from(ALICE)) .unwrap() .try_into() .unwrap(); @@ -499,13 +925,13 @@ fn test_assets_balances_api_works() { ))); // check trusted asset assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(), minimum_asset_balance ) .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - Identity::convert_back(&foreign_asset_id_multilocation).unwrap(), + V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(), 6 * foreign_asset_minimum_asset_balance ) .into()))); @@ -520,6 +946,7 @@ asset_test_utils::include_teleports_for_native_asset_works!( WeightToFee, ParachainSystem, collator_session_keys(), + slot_durations(), ExistentialDeposit::get(), Box::new(|runtime_event_encoded: Vec| { match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { @@ -527,12 +954,6 @@ asset_test_utils::include_teleports_for_native_asset_works!( _ => None, } }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), 1000 ); @@ -546,6 +967,7 @@ asset_test_utils::include_teleports_for_foreign_assets_works!( ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, collator_session_keys(), + slot_durations(), ExistentialDeposit::get(), Box::new(|runtime_event_encoded: Vec| { match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { @@ -582,7 +1004,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ XcmConfig, TrustBackedAssetsInstance, AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvertLatest, collator_session_keys(), ExistentialDeposit::get(), 12345, @@ -599,11 +1021,14 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ Runtime, XcmConfig, ForeignAssetsInstance, - MultiLocation, + xcm::v3::Location, JustTry, collator_session_keys(), ExistentialDeposit::get(), - MultiLocation { parents: 1, interior: X2(Parachain(1313), GeneralIndex(12345)) }, + xcm::v3::Location::new( + 1, + [xcm::v3::Junction::Parachain(1313), xcm::v3::Junction::GeneralIndex(12345)] + ), Box::new(|| { assert!(Assets::asset_ids().collect::>().is_empty()); }), @@ -618,8 +1043,8 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p WeightToFee, ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, - MultiLocation, - JustTry, + xcm::v3::Location, + V4V3LocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), @@ -642,104 +1067,211 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p }) ); +fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works( + bridging_configuration: fn() -> TestBridgingConfig, +) { + asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + slot_durations(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_configuration, + WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + Some(xcm_config::TreasuryAccount::get()), + ) +} + mod asset_hub_rococo_tests { use super::*; - - fn bridging_to_asset_hub_wococo() -> asset_test_utils::test_cases_over_bridge::TestBridgingConfig - { - asset_test_utils::test_cases_over_bridge::TestBridgingConfig { - bridged_network: bridging::to_wococo::WococoNetwork::get(), + use asset_hub_rococo_runtime::PolkadotXcm; + use xcm_executor::traits::ConvertLocation; + + fn bridging_to_asset_hub_westend() -> 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(), - bridged_target_location: bridging::to_wococo::AssetHubWococo::get(), + bridged_target_location: bridging::to_westend::AssetHubWestend::get(), } } #[test] - fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { - asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_westend_works() { + limited_reserve_transfer_assets_for_native_asset_over_bridge_works( + bridging_to_asset_hub_westend, + ) + } + + #[test] + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_pool_swap_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - ParachainSystem, - XcmpQueue, - LocationToAccountId, + ForeignAssetsInstance, >( - collator_session_keys(), + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), - AccountId::from(ALICE), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - bridging_to_asset_hub_wococo, - WeightLimit::Unlimited, - Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + AccountId::from([73; 32]), + block_author_account, + // receiving WNDs + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_westend() + }, + ( + [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Westend), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } ) } #[test] - fn receive_reserve_asset_deposited_woc_from_asset_hub_wococo_works() { + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_sufficient_asset_works( + ) { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsInstance, >( collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), - // receiving WOCs - (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Wococo)) }, 1000000000000, 1_000_000_000), - bridging_to_asset_hub_wococo, + block_author_account.clone(), + // receiving WNDs + foreign_asset_create_params, + 1000000000000, + bridging_to_asset_hub_westend, ( - X1(PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_INDEX)), - GlobalConsensus(Wococo), - X1(Parachain(1000)) - ) + [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Westend), + [Parachain(1000)].into() + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } ) } #[test] - fn report_bridge_status_from_xcm_bridge_router_works() { + fn report_bridge_status_from_xcm_bridge_router_for_westend_works() { asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - ParachainSystem, - XcmpQueue, LocationToAccountId, - ToWococoXcmRouterInstance, + ToWestendXcmRouterInstance, >( collator_session_keys(), - ExistentialDeposit::get(), - AccountId::from(ALICE), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - bridging_to_asset_hub_wococo, - WeightLimit::Unlimited, - Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + bridging_to_asset_hub_westend, || { sp_std::vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, @@ -747,7 +1279,7 @@ mod asset_hub_rococo_tests { origin_kind: OriginKind::Xcm, require_weight_at_most: bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), - call: bp_asset_hub_rococo::Call::ToWococoXcmRouter( + call: bp_asset_hub_rococo::Call::ToWestendXcmRouter( bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { bridge_id: Default::default(), is_congested: true, @@ -766,7 +1298,7 @@ mod asset_hub_rococo_tests { origin_kind: OriginKind::Xcm, require_weight_at_most: bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), - call: bp_asset_hub_rococo::Call::ToWococoXcmRouter( + call: bp_asset_hub_rococo::Call::ToWestendXcmRouter( bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { bridge_id: Default::default(), is_congested: false, @@ -785,28 +1317,28 @@ mod asset_hub_rococo_tests { fn test_report_bridge_status_call_compatibility() { // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding assert_eq!( - RuntimeCall::ToWococoXcmRouter( + RuntimeCall::ToWestendXcmRouter( pallet_xcm_bridge_hub_router::Call::report_bridge_status { bridge_id: Default::default(), is_congested: true, } ) .encode(), - bp_asset_hub_rococo::Call::ToWococoXcmRouter( + bp_asset_hub_rococo::Call::ToWestendXcmRouter( bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { bridge_id: Default::default(), is_congested: true, } ) .encode() - ) + ); } #[test] - fn check_sane_weight_report_bridge_status() { + fn check_sane_weight_report_bridge_status_for_westend() { use pallet_xcm_bridge_hub_router::WeightInfo; let actual = >::WeightInfo::report_bridge_status(); let max_weight = bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(); assert!( @@ -816,53 +1348,10 @@ mod asset_hub_rococo_tests { actual ); } -} - -mod asset_hub_wococo_tests { - use super::*; - - fn bridging_to_asset_hub_rococo() -> asset_test_utils::test_cases_over_bridge::TestBridgingConfig - { - asset_test_utils::test_cases_over_bridge::TestBridgingConfig { - bridged_network: bridging::to_rococo::RococoNetwork::get(), - local_bridge_hub_para_id: bridging::SiblingBridgeHubParaId::get(), - local_bridge_hub_location: bridging::SiblingBridgeHub::get(), - bridged_target_location: bridging::to_rococo::AssetHubRococo::get(), - } - } - - pub(crate) fn set_wococo_flavor() { - let flavor_key = xcm_config::Flavor::key().to_vec(); - let flavor = RuntimeFlavor::Wococo; - - // encode `set_storage` call - let set_storage_call = RuntimeCall::System(frame_system::Call::::set_storage { - items: vec![(flavor_key, flavor.encode())], - }) - .encode(); - - // estimate - storing just 1 value - use frame_system::WeightInfo; - let require_weight_at_most = - ::SystemWeightInfo::set_storage(1); - - // execute XCM with Transact to `set_storage` as governance does - assert_ok!(RuntimeHelper::execute_as_governance(set_storage_call, require_weight_at_most) - .ensure_complete()); - - // check if stored - assert_eq!(flavor, xcm_config::Flavor::get()); - } - - fn with_wococo_flavor_bridging_to_asset_hub_rococo( - ) -> asset_test_utils::test_cases_over_bridge::TestBridgingConfig { - set_wococo_flavor(); - bridging_to_asset_hub_rococo() - } #[test] - fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works() { - asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + fn reserve_transfer_native_asset_to_non_teleport_para_works() { + asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, @@ -871,6 +1360,7 @@ mod asset_hub_wococo_tests { LocationToAccountId, >( collator_session_keys(), + slot_durations(), ExistentialDeposit::get(), AccountId::from(ALICE), Box::new(|runtime_event_encoded: Vec| { @@ -885,265 +1375,89 @@ mod asset_hub_wococo_tests { _ => None, } }), - with_wococo_flavor_bridging_to_asset_hub_rococo, WeightLimit::Unlimited, - Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), - ) + ); } +} - #[test] - fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_works() { - const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; - asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - LocationToAccountId, - ForeignAssetsInstance, - >( - collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), - ExistentialDeposit::get(), - AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), - // receiving ROCs - (MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }, 1000000000000, 1_000_000_000), - with_wococo_flavor_bridging_to_asset_hub_rococo, +#[test] +fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterByteFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { ( - X1(PalletInstance(bp_bridge_hub_wococo::WITH_BRIDGE_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_INDEX)), - GlobalConsensus(Rococo), - X1(Parachain(1000)) - ) - ) - } - - #[test] - fn report_bridge_status_from_xcm_bridge_router_works() { - asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - XcmpQueue, - LocationToAccountId, - ToRococoXcmRouterInstance, - >( - collator_session_keys(), - ExistentialDeposit::get(), - AccountId::from(ALICE), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - with_wococo_flavor_bridging_to_asset_hub_rococo, - WeightLimit::Unlimited, - Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), - || { - sp_std::vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { - origin_kind: OriginKind::Xcm, - require_weight_at_most: - bp_asset_hub_wococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), - call: bp_asset_hub_wococo::Call::ToRococoXcmRouter( - bp_asset_hub_wococo::XcmBridgeHubRouterCall::report_bridge_status { - bridge_id: Default::default(), - is_congested: true, - } - ) - .encode() - .into(), - } - ] - .into() - }, - || { - sp_std::vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { - origin_kind: OriginKind::Xcm, - require_weight_at_most: - bp_asset_hub_wococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), - call: bp_asset_hub_wococo::Call::ToRococoXcmRouter( - bp_asset_hub_wococo::XcmBridgeHubRouterCall::report_bridge_status { - bridge_id: Default::default(), - is_congested: false, - } - ) - .encode() - .into(), - } - ] - .into() - }, - ) - } - - #[test] - fn test_report_bridge_status_call_compatibility() { - // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding - assert_eq!( - RuntimeCall::ToRococoXcmRouter( - pallet_xcm_bridge_hub_router::Call::report_bridge_status { - bridge_id: Default::default(), - is_congested: true, - } - ) - .encode(), - bp_asset_hub_wococo::Call::ToRococoXcmRouter( - bp_asset_hub_wococo::XcmBridgeHubRouterCall::report_bridge_status { - bridge_id: Default::default(), - is_congested: true, - } + bridging::XcmBridgeHubRouterByteFee::key().to_vec(), + bridging::XcmBridgeHubRouterByteFee::get(), ) - .encode() - ) - } - - #[test] - fn check_sane_weight_report_bridge_status() { - use pallet_xcm_bridge_hub_router::WeightInfo; - let actual = >::WeightInfo::report_bridge_status(); - let max_weight = bp_asset_hub_wococo::XcmBridgeHubRouterTransactCallMaxWeight::get(); - assert!( - actual.all_lte(max_weight), - "max_weight: {:?} should be adjusted to actual {:?}", - max_weight, - actual - ); - } + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) } -/// Tests expected configuration of isolated `pallet_xcm::Config::XcmReserveTransferFilter`. #[test] -fn xcm_reserve_transfer_filter_works() { - // prepare assets - let only_native_assets = || vec![MultiAsset::from((TokenLocation::get(), 1000))]; - let only_trust_backed_assets = || { - vec![MultiAsset::from(( - AssetIdForTrustBackedAssetsConvert::convert_back(&12345).unwrap(), - 2000, - ))] - }; - let only_sibling_foreign_assets = - || vec![MultiAsset::from((MultiLocation::new(1, X1(Parachain(12345))), 3000))]; - let only_different_global_consensus_foreign_assets = || { - vec![MultiAsset::from(( - MultiLocation::new(2, X2(GlobalConsensus(Wococo), Parachain(12345))), - 4000, - ))] - }; - - // prepare destinations - let relaychain = MultiLocation::parent(); - let sibling_parachain = MultiLocation::new(1, X1(Parachain(54321))); - let different_global_consensus_parachain_other_than_asset_hub_wococo = - MultiLocation::new(2, X2(GlobalConsensus(Kusama), Parachain(12345))); - let bridged_asset_hub_wococo = bridging::to_wococo::AssetHubWococo::get(); - let bridged_asset_hub_rococo = bridging::to_rococo::AssetHubRococo::get(); - - // prepare expected test data sets: (destination, assets, expected_result) - let test_data = vec![ - (relaychain, only_native_assets(), true), - (relaychain, only_trust_backed_assets(), true), - (relaychain, only_sibling_foreign_assets(), true), - (relaychain, only_different_global_consensus_foreign_assets(), true), - (sibling_parachain, only_native_assets(), true), - (sibling_parachain, only_trust_backed_assets(), true), - (sibling_parachain, only_sibling_foreign_assets(), true), - (sibling_parachain, only_different_global_consensus_foreign_assets(), true), - ( - different_global_consensus_parachain_other_than_asset_hub_wococo, - only_native_assets(), - false, - ), - ( - different_global_consensus_parachain_other_than_asset_hub_wococo, - only_trust_backed_assets(), - false, - ), - ( - different_global_consensus_parachain_other_than_asset_hub_wococo, - only_sibling_foreign_assets(), - false, - ), - ( - different_global_consensus_parachain_other_than_asset_hub_wococo, - only_different_global_consensus_foreign_assets(), - false, - ), - ]; - - let additional_test_data_for_rococo_flavor = vec![ - (bridged_asset_hub_wococo, only_native_assets(), true), - (bridged_asset_hub_wococo, only_trust_backed_assets(), false), - (bridged_asset_hub_wococo, only_sibling_foreign_assets(), false), - (bridged_asset_hub_wococo, only_different_global_consensus_foreign_assets(), false), - ]; - let additional_test_data_for_wococo_flavor = vec![ - (bridged_asset_hub_rococo, only_native_assets(), true), - (bridged_asset_hub_rococo, only_trust_backed_assets(), false), - (bridged_asset_hub_rococo, only_sibling_foreign_assets(), false), - (bridged_asset_hub_rococo, only_different_global_consensus_foreign_assets(), false), - ]; - - // lets test filter with test data - ExtBuilder::::default() - .with_collators(collator_session_keys().collators()) - .with_session_keys(collator_session_keys().session_keys()) - .with_tracing() - .build() - .execute_with(|| { - type XcmReserveTransferFilter = - ::XcmReserveTransferFilter; - - fn do_test(data: Vec<(MultiLocation, Vec, bool)>) { - for (dest, assets, expected_result) in data { - assert_eq!( - expected_result, - XcmReserveTransferFilter::contains(&(dest, assets.clone())), - "expected_result: {} for dest: {:?} and assets: {:?}", - expected_result, - dest, - assets - ); - } +fn change_xcm_bridge_hub_router_base_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterBaseFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); + ( + bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), + bridging::XcmBridgeHubRouterBaseFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() } - - // check for Rococo flavor - do_test(test_data.clone()); - do_test(additional_test_data_for_rococo_flavor); - - // check for Wococo flavor - asset_hub_wococo_tests::set_wococo_flavor(); - do_test(test_data); - do_test(additional_test_data_for_wococo_flavor); - }) + }, + ) } #[test] -fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { +fn change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() { asset_test_utils::test_cases::change_storage_constant_by_governance_works::< Runtime, - bridging::XcmBridgeHubRouterByteFee, + bridging::to_ethereum::BridgeHubEthereumBaseFee, Balance, >( collator_session_keys(), 1000, Box::new(|call| RuntimeCall::System(call).encode()), || { + log::error!( + target: "bridges::estimate", + "`bridging::BridgeHubEthereumBaseFee` actual value: {} for runtime: {}", + bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), + ::Version::get(), + ); ( - bridging::XcmBridgeHubRouterByteFee::key().to_vec(), - bridging::XcmBridgeHubRouterByteFee::get(), + bridging::to_ethereum::BridgeHubEthereumBaseFee::key().to_vec(), + bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), ) }, |old_value| { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 7a523f8bfb6649e1c0f4c9d14891c1231d1fc0da..950c6e62d72fa0c23544456fce76b49b1d5d3766 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -1,98 +1,109 @@ [package] name = "asset-hub-westend-runtime" -version = "0.9.420" +version = "0.15.0" authors.workspace = true 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 } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" # 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-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false} -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-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false} -pallet-nft-fractionalization = { path = "../../../../../substrate/frame/nft-fractionalization", default-features = false} -pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false} -pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false} -pallet-proxy = { path = "../../../../../substrate/frame/proxy", 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-uniques = { path = "../../../../../substrate/frame/uniques", 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} +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-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } +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-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } +pallet-nft-fractionalization = { path = "../../../../../substrate/frame/nft-fractionalization", default-features = false } +pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false } +pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false } +pallet-proxy = { path = "../../../../../substrate/frame/proxy", 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-uniques = { path = "../../../../../substrate/frame/uniques", 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} +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 } # num-traits feature needed for dex integer sq root: -primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "num-traits"] } +primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } # Polkadot -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", 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} -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} +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} +pallet-message-queue = { path = "../../../../../substrate/frame/message-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-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } assets-common = { path = "../common", default-features = false } +# Bridges +pallet-xcm-bridge-hub-router = { path = "../../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } +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 } + [dev-dependencies] hex-literal = "0.4.1" asset-test-utils = { path = "../test-utils" } -sp-io = { path = "../../../../../substrate/primitives/io" } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "assets-common/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", @@ -103,6 +114,7 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nft-fractionalization/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", @@ -111,6 +123,7 @@ runtime-benchmarks = [ "pallet-uniques/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm-bridge-hub-router/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -121,7 +134,6 @@ 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", @@ -136,6 +148,7 @@ 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-nft-fractionalization/try-runtime", "pallet-nfts/try-runtime", @@ -145,6 +158,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-uniques/try-runtime", "pallet-utility/try-runtime", + "pallet-xcm-bridge-hub-router/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", @@ -152,13 +166,17 @@ try-runtime = [ ] std = [ "assets-common/std", + "bp-asset-hub-rococo/std", + "bp-asset-hub-westend/std", + "bp-bridge-hub-rococo/std", + "bp-bridge-hub-westend/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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -176,6 +194,7 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-collator-selection/std", + "pallet-message-queue/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", "pallet-nfts-runtime-api/std", @@ -188,12 +207,13 @@ std = [ "pallet-uniques/std", "pallet-utility/std", "pallet-xcm-benchmarks?/std", + "pallet-xcm-bridge-hub-router/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", + "primitive-types/std", "scale-info/std", "sp-api/std", "sp-block-builder/std", @@ -209,10 +229,14 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] -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/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index a7dc3a84777e787b5ef24e0188bc1e8d82eb56b1..711fadff92a0c2f0cf898414ae871015bbf2b386 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -27,23 +27,23 @@ 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}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, ord_parameter_types, parameter_types, traits::{ - tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, - ConstU64, ConstU8, InstanceFilter, + fungible, fungibles, + tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, + InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, @@ -53,43 +53,45 @@ use frame_system::{ EnsureRoot, EnsureSigned, EnsureSignedBy, }; use pallet_asset_conversion_tx_payment::AssetConversionAdapter; -use pallet_nfts::PalletFeatures; -pub use parachains_common as common; +use pallet_nfts::{DestroyWitness, PalletFeatures}; +use pallet_xcm::EnsureXcm; use parachains_common::{ - impls::DealWithFees, - westend::{consensus::*, currency::*, fee::WeightToFee}, - AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, Hash, Header, Nonce, - Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, - NORMAL_DISPATCH_RATIO, SLOT_DURATION, + impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, + BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, + NORMAL_DISPATCH_RATIO, }; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Verify}, + traits::{AccountIdConversion, BlakeTwo256, Block as BlockT, Saturating, Verify}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Permill, RuntimeDebug, + ApplyExtrinsicResult, Perbill, Permill, RuntimeDebug, }; use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use xcm::opaque::v3::MultiLocation; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, WestendLocation, XcmConfig, - XcmOriginToTransactDispatchOrigin, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocationV3, WestendLocation, + WestendLocationV3, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -use assets_common::{ - foreign_creators::ForeignCreators, matching::FromSiblingParachain, MultiLocationForAssetId, -}; +use assets_common::{foreign_creators::ForeignCreators, matching::FromSiblingParachain}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; -use xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; +// We exclude `Assets` since it's the name of a pallet +use xcm::latest::prelude::AssetId; + +#[cfg(feature = "runtime-benchmarks")] +use xcm::latest::prelude::{ + Asset, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, NetworkId, + NonFungible, Parent, ParentThen, Response, XCM_VERSION, +}; use crate::xcm_config::ForeignCreatorsSovereignAccountOf; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -108,10 +110,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 0, }; @@ -147,25 +149,17 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; type SystemWeightInfo = weights::frame_system::WeightInfo; type SS58Prefix = SS58Prefix; @@ -177,7 +171,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -203,10 +197,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - // We allow each account to have holds on it from: - // - `NftFractionalization`: 1 - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<0>; } @@ -268,8 +260,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); } @@ -303,35 +293,55 @@ 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, + xcm::v3::Location, + >, + xcm::v3::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. +pub type NativeAndAssets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + xcm::v3::Location, + 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 PoolAssets = PoolAssets; + type AssetKind = xcm::v3::Location; + type Assets = NativeAndAssets; + 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 = WestendLocationV3; + 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< + WestendLocationV3, + parachain_info::Pallet, + xcm_config::TrustBackedAssetsPalletIndex, + xcm::v3::Location, + >; } parameter_types! { @@ -352,13 +362,14 @@ 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 AssetId = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< - (FromSiblingParachain>,), + FromSiblingParachain, xcm::v3::Location>, ForeignCreatorsSovereignAccountOf, AccountId, + xcm::v3::Location, >; type ForceOrigin = AssetsForceOrigin; type AssetDeposit = ForeignAssetsAssetDeposit; @@ -584,30 +595,59 @@ parameter_types! { } 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 DmpMessageHandler = DmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} +parameter_types! { + 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! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(xcm_config::WestendLocation::get()); + pub FeeAssetId: AssetId = AssetId(xcm_config::WestendLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -621,20 +661,19 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } parameter_types! { @@ -660,9 +699,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -690,12 +728,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! { @@ -708,8 +743,8 @@ parameter_types! { impl pallet_uniques::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; + type CollectionId = CollectionId; + type ItemId = ItemId; type Currency = Balances; type ForceOrigin = AssetsForceOrigin; type CollectionDeposit = UniquesCollectionDeposit; @@ -766,8 +801,8 @@ parameter_types! { impl pallet_nfts::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type CollectionId = u32; - type ItemId = u32; + type CollectionId = CollectionId; + type ItemId = ItemId; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = AssetsForceOrigin; @@ -793,51 +828,83 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } +/// XCM router instance to BridgeHub with bridging capabilities for `Rococo` global +/// consensus with dynamic fees and back-pressure. +pub type ToRococoXcmRouterInstance = pallet_xcm_bridge_hub_router::Instance1; +impl pallet_xcm_bridge_hub_router::Config for Runtime { + type WeightInfo = weights::pallet_xcm_bridge_hub_router::WeightInfo; + + 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>; + #[cfg(feature = "runtime-benchmarks")] + type BridgeHubOrigin = frame_support::traits::EitherOfDiverse< + // for running benchmarks + EnsureRoot, + // for running tests with `--feature runtime-benchmarks` + EnsureXcm>, + >; + + type ToBridgeHubSender = XcmpQueue; + type WithBridgeHubChannel = + cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider< + xcm_config::bridging::SiblingBridgeHubParaId, + Runtime, + >; + + type ByteFee = xcm_config::bridging::XcmBridgeHubRouterByteFee; + type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; +} + // 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, // RandomnessCollectiveFlip = 2 removed - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, + Timestamp: pallet_timestamp = 3, + ParachainInfo: parachain_info = 4, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - // AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event} = 12, - AssetTxPayment: pallet_asset_conversion_tx_payment::{Pallet, Event} = 13, + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + // AssetTxPayment: pallet_asset_tx_payment = 12, + AssetTxPayment: pallet_asset_conversion_tx_payment = 13, // 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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, - PolkadotXcm: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + // Bridge utilities. + ToRococoXcmRouter: pallet_xcm_bridge_hub_router:: = 34, + MessageQueue: pallet_message_queue = 35, // Handy utilities. - Utility: pallet_utility::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, // The main stage. - Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, - Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, - ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, - NftFractionalization: pallet_nft_fractionalization::{Pallet, Call, Storage, Event, HoldReason} = 54, - PoolAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 55, - AssetConversion: pallet_asset_conversion::{Pallet, Call, Storage, Event} = 56, + Assets: pallet_assets:: = 50, + Uniques: pallet_uniques = 51, + Nfts: pallet_nfts = 52, + ForeignAssets: pallet_assets:: = 53, + NftFractionalization: pallet_nft_fractionalization = 54, + PoolAssets: pallet_assets:: = 55, + AssetConversion: pallet_asset_conversion = 56, } ); @@ -871,13 +938,86 @@ pub type Migrations = ( // unreleased pallet_collator_selection::migration::v1::MigrateToV1, // unreleased - migrations::NativeAssetParents0ToParents1Migration, - // unreleased pallet_multisig::migrations::v1::MigrateToV1, // unreleased InitStorageVersions, + // unreleased + DeleteUndecodableStorage, + // unreleased + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); +/// Asset Hub Westend has some undecodable storage, delete it. +/// See for more info. +/// +/// First we remove the bad Hold, then the bad NFT collection. +pub struct DeleteUndecodableStorage; + +impl frame_support::traits::OnRuntimeUpgrade for DeleteUndecodableStorage { + fn on_runtime_upgrade() -> Weight { + use sp_core::crypto::Ss58Codec; + + let mut writes = 0; + + // Remove Holds for account with undecodable hold + // Westend doesn't have any HoldReasons implemented yet, so it's safe to just blanket remove + // any for this account. + match AccountId::from_ss58check("5GCCJthVSwNXRpbeg44gysJUx9vzjdGdfWhioeM7gCg6VyXf") { + Ok(a) => { + log::info!("Removing holds for account with bad hold"); + pallet_balances::Holds::::remove(a); + writes.saturating_inc(); + }, + Err(_) => { + log::error!("CleanupUndecodableStorage: Somehow failed to convert valid SS58 address into an AccountId!"); + }, + }; + + // Destroy undecodable NFT item 1 + writes.saturating_inc(); + match pallet_nfts::Pallet::::do_burn(3, 1, |_| Ok(())) { + Ok(_) => { + log::info!("Destroyed undecodable NFT item 1"); + }, + Err(e) => { + log::error!("Failed to destroy undecodable NFT item: {:?}", e); + return ::DbWeight::get().reads_writes(0, writes); + }, + } + + // Destroy undecodable NFT item 2 + writes.saturating_inc(); + match pallet_nfts::Pallet::::do_burn(3, 2, |_| Ok(())) { + Ok(_) => { + log::info!("Destroyed undecodable NFT item 2"); + }, + Err(e) => { + log::error!("Failed to destroy undecodable NFT item: {:?}", e); + return ::DbWeight::get().reads_writes(0, writes); + }, + } + + // Finally, we can destroy the collection + writes.saturating_inc(); + match pallet_nfts::Pallet::::do_destroy_collection( + 3, + DestroyWitness { attributes: 0, item_metadatas: 1, item_configs: 0 }, + None, + ) { + Ok(_) => { + log::info!("Destroyed undecodable NFT collection"); + }, + Err(e) => { + log::error!("Failed to destroy undecodable NFT collection: {:?}", e); + }, + }; + + ::DbWeight::get().reads_writes(0, writes) + } +} + /// Migration to initialize storage versions for pallets added after genesis. /// /// Ideally this would be done automatically (see @@ -889,22 +1029,21 @@ pub struct InitStorageVersions; impl frame_support::traits::OnRuntimeUpgrade for InitStorageVersions { fn on_runtime_upgrade() -> Weight { use frame_support::traits::{GetStorageVersion, StorageVersion}; - use sp_runtime::traits::Saturating; let mut writes = 0; if PolkadotXcm::on_chain_storage_version() == StorageVersion::new(0) { - PolkadotXcm::current_storage_version().put::(); + PolkadotXcm::in_code_storage_version().put::(); writes.saturating_inc(); } if ForeignAssets::on_chain_storage_version() == StorageVersion::new(0) { - ForeignAssets::current_storage_version().put::(); + ForeignAssets::in_code_storage_version().put::(); writes.saturating_inc(); } if PoolAssets::on_chain_storage_version() == StorageVersion::new(0) { - PoolAssets::current_storage_version().put::(); + PoolAssets::in_code_storage_version().put::(); writes.saturating_inc(); } @@ -922,19 +1061,16 @@ pub type Executive = frame_executive::Executive< Migrations, >; -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_assets, Local] [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_nft_fractionalization, NftFractionalization] [pallet_nfts, Nfts] @@ -944,9 +1080,11 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_xcm_bridge_hub_router, ToRococo] // XCM - [pallet_xcm, PolkadotXcm] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] @@ -956,11 +1094,20 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -973,7 +1120,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -1080,10 +1227,10 @@ impl_runtime_apis! { fn system_attribute( collection: u32, - item: u32, + item: Option, key: Vec, ) -> Option> { - >::system_attribute(&collection, &item, &key) + >::system_attribute(&collection, item.as_ref(), &key) } fn collection_attribute(collection: u32, key: Vec) -> Option> { @@ -1094,20 +1241,19 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + xcm::v3::Location, > 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: xcm::v3::Location, asset2: xcm::v3::Location, 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: xcm::v3::Location, asset2: xcm::v3::Location, 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: xcm::v3::Location, asset2: xcm::v3::Location) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -1160,7 +1306,7 @@ impl_runtime_apis! { AccountId, > for Runtime { - fn query_account_balances(account: AccountId) -> Result { + fn query_account_balances(account: AccountId) -> Result { use assets_common::fungible_conversion::{convert, convert_balance}; Ok([ // collect pallet_balance @@ -1230,6 +1376,8 @@ impl_runtime_apis! { 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; + use pallet_xcm_bridge_hub_router::benchmarking::Pallet as XcmBridgeHubRouterBench; // This is defined once again in dispatch_benchmark, because list_benchmarks! // and add_benchmarks! are macros exported by define_benchmarks! macros and those types @@ -1245,6 +1393,8 @@ impl_runtime_apis! { type Foreign = pallet_assets::Pallet::; type Pool = pallet_assets::Pallet::; + type ToRococo = XcmBridgeHubRouterBench; + let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1273,49 +1423,183 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} - use xcm::latest::prelude::*; - use xcm_config::{MaxAssetsIntoHolding, WestendLocation}; - use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( + pub ExistentialDepositAsset: Option = Some(( WestendLocation::get(), ExistentialDeposit::get() ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), + )) + } + + fn set_up_complex_asset_transfer( + ) -> Option<(xcm::v4::Assets, u32, Location, 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: Asset = (Location::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 = Location::new( + 0, + [PalletInstance(50), GeneralIndex(u32::from(asset_id).into())] + ); + let transfer_asset: Asset = (asset_location, asset_amount).into(); + + let assets: xcm::v4::Assets = 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)) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } } + use pallet_xcm_bridge_hub_router::benchmarking::{ + Pallet as XcmBridgeHubRouterBench, + Config as XcmBridgeHubRouterConfig, + }; + + impl XcmBridgeHubRouterConfig for Runtime { + fn make_congested() { + cumulus_pallet_xcmp_queue::bridging::suspend_channel_for_benchmarks::( + xcm_config::bridging::SiblingBridgeHubParaId::get().into() + ); + } + fn ensure_bridged_target_destination() -> Result { + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( + xcm_config::bridging::SiblingBridgeHubParaId::get().into() + ); + let bridged_asset_hub = xcm_config::bridging::to_rococo::AssetHubRococo::get(); + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridged_asset_hub.clone()), + 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) + } + } + + use xcm_config::{MaxAssetsIntoHolding, WestendLocation}; + use pallet_xcm_benchmarks::asset_instance_from; + impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - XcmConfig, - ExistentialDepositMultiAsset, + xcm_config::XcmConfig, + ExistentialDepositAsset, xcm_config::PriceForParentDelivery, >; - fn valid_destination() -> Result { + fn valid_destination() -> Result { Ok(WestendLocation::get()) } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { + fn worst_case_holding(depositable_count: u32) -> xcm::v4::Assets { // A mix of fungible, non-fungible, and concrete assets. let holding_non_fungibles = MaxAssetsIntoHolding::get() / 2 - depositable_count; let holding_fungibles = holding_non_fungibles - 1; let fungibles_amount: u128 = 100; let mut assets = (0..holding_fungibles) .map(|i| { - MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + Asset { + id: AssetId(GeneralIndex(i as u128).into()), fun: Fungible(fungibles_amount * i as u128), } }) - .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..holding_non_fungibles).map(|i| MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + .chain(core::iter::once(Asset { id: AssetId(Here.into()), fun: Fungible(u128::MAX) })) + .chain((0..holding_non_fungibles).map(|i| Asset { + id: AssetId(GeneralIndex(i as u128).into()), fun: NonFungible(asset_instance_from(i)), })) .collect::>(); - assets.push(MultiAsset { - id: Concrete(WestendLocation::get()), + assets.push(Asset { + id: AssetId(WestendLocation::get()), fun: Fungible(1_000_000 * UNITS), }); assets.into() @@ -1323,12 +1607,18 @@ impl_runtime_apis! { } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( WestendLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(WestendLocation::get()) }, + Asset { fun: Fungible(UNITS), id: AssetId(WestendLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + // AssetHubWestend trusts AssetHubRococo as reserve for ROCs + pub TrustedReserve: Option<(Location, Asset)> = Some( + ( + xcm_config::bridging::to_rococo::AssetHubRococo::get(), + Asset::from((xcm_config::bridging::to_rococo::RocLocation::get(), 1000000000000 as u128)) + ) + ); } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -1338,9 +1628,9 @@ impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(WestendLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(WestendLocation::get()), fun: Fungible(UNITS), } } @@ -1354,39 +1644,49 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(xcm::v4::Assets, xcm::v4::Assets), BenchmarkError> { Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { - Err(BenchmarkError::Skip) + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + match xcm_config::bridging::BridgingBenchmarksHelper::prepare_universal_alias() { + Some(alias) => Ok(alias), + None => Err(BenchmarkError::Skip) + } } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { Ok((WestendLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(WestendLocation::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + fn claimable_asset() -> Result<(Location, Location, xcm::v4::Assets), BenchmarkError> { let origin = WestendLocation::get(); - let assets: MultiAssets = (Concrete(WestendLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + let assets: xcm::v4::Assets = (AssetId(WestendLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(WestendLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { Err(BenchmarkError::Skip) } } @@ -1398,6 +1698,8 @@ impl_runtime_apis! { type Foreign = pallet_assets::Pallet::; type Pool = pallet_assets::Pallet::; + type ToRococo = XcmBridgeHubRouterBench; + let whitelist: Vec = vec![ // Block Number hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), @@ -1436,120 +1738,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/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1e5c6a74293995b6e3702f19b8b3750c34b192f --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,80 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// statemine-dev +// --pallet +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/assets/statemine/src/weights +// --steps +// 50 +// --repeat +// 20 + +#![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/assets/asset-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs index cda66f6ea7eee25ece39733cf266dbf16c5c0338..b34c959a355e84e0c7773ef92d12aa215cb5eeee 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -1,43 +1,38 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 `cumulus_pallet_xcmp_queue` //! //! 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-09-15, 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("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// ./target/release/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-westend-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// asset-hub-westend-dev +// --output +// cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs +// --extrinsic +// #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,22 +51,98 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 5_109_000 picoseconds. - Weight::from_parts(5_324_000, 0) + // 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:1) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { + /// 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: `118` + // Estimated: `3517` + // Minimum execution time: 15_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: 5_183_000 picoseconds. - Weight::from_parts(5_408_000, 0) + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(4_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: `65747` + // Estimated: `69212` + // Minimum execution time: 66_000_000 picoseconds. + Weight::from_parts(68_000_000, 0) + .saturating_add(Weight::from_parts(0, 69212)) + .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: 43_000_000 picoseconds. + Weight::from_parts(44_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/assets/asset-hub-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system.rs index c70ea9d58b2649c5f6fbf53cf620757c4aa19f1b..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 @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -152,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 281c013b3372f57a24b57812de91e684ffabd0c9..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 @@ -1,20 +1,20 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 block_weights; +pub mod cumulus_pallet_parachain_system; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; @@ -24,6 +24,7 @@ pub mod pallet_assets_local; pub mod pallet_assets_pool; pub mod pallet_balances; pub mod pallet_collator_selection; +pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_nft_fractionalization; pub mod pallet_nfts; @@ -33,11 +34,11 @@ pub mod pallet_timestamp; pub mod pallet_uniques; pub mod pallet_utility; pub mod pallet_xcm; +pub mod pallet_xcm_bridge_hub_router; 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 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 2f39df65403043dad536ccb180c396108c73f487..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 @@ -1,43 +1,38 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for `pallet_asset_conversion` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-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)] @@ -52,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`) @@ -68,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) @@ -92,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) @@ -128,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`) @@ -144,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_assets_foreign.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_foreign.rs index 5be1319b10dfb221230a66429c578248fc30a9d0..52ba2fd6c40fcfea552b181bcb05cfecab75f6b4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_foreign.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_foreign.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_assets` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_local.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_local.rs index aa09be829c85c6e19d252ad03dd584e34024abc4..e78366b91cbe8100c2a46c47a8708c3f1aed918d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_local.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_local.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_assets` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_pool.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_pool.rs index bfe73e1cfafa5dda9690f4b57f07e08c6ea3b740..65cae81069c40f7369d31d8411bf882521b71e11 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_pool.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_assets_pool.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_assets` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_balances.rs index f6bf09d63bae20a899301bda372a48ad389c935c..68aceca14c1589825e588e4a5491529120da01f5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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: `runner-8idpd4bs-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 +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-westend-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 54_422_000 picoseconds. - Weight::from_parts(55_477_000, 0) + // Minimum execution time: 43_122_000 picoseconds. + Weight::from_parts(43_640_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 39_850_000 picoseconds. - Weight::from_parts(41_026_000, 0) + // Minimum execution time: 33_636_000 picoseconds. + Weight::from_parts(34_571_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_554_000 picoseconds. - Weight::from_parts(14_800_000, 0) + // Minimum execution time: 12_101_000 picoseconds. + Weight::from_parts(12_511_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 21_586_000 picoseconds. - Weight::from_parts(22_297_000, 0) + // Minimum execution time: 17_077_000 picoseconds. + Weight::from_parts(17_362_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_042_000 picoseconds. - Weight::from_parts(58_251_000, 0) + // Minimum execution time: 44_352_000 picoseconds. + Weight::from_parts(45_045_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 51_587_000 picoseconds. - Weight::from_parts(52_275_000, 0) + // Minimum execution time: 41_836_000 picoseconds. + Weight::from_parts(43_201_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 17_201_000 picoseconds. - Weight::from_parts(17_613_000, 0) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(14_743_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_608_000 picoseconds. - Weight::from_parts(16_808_000, 0) + // Minimum execution time: 14_542_000 picoseconds. + Weight::from_parts(14_731_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 15_291 - .saturating_add(Weight::from_parts(15_154_407, 0).saturating_mul(u.into())) + // Standard Error: 11_213 + .saturating_add(Weight::from_parts(13_160_721, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_208_000 picoseconds. + Weight::from_parts(5_619_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_collator_selection.rs index 2473c58e45818145674d62c31f6c6a07b6f6794f..1fac2d59ab9609a0220a8d39a3a711dd8bd98437 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_collator_selection.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -124,7 +123,7 @@ impl pallet_collator_selection::WeightInfo for WeightIn } /// 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() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -178,6 +177,30 @@ impl pallet_collator_selection::WeightInfo for WeightIn .saturating_add(T::DbWeight::get().reads(2)) .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) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..45531ccfa797c52ead00833f765347f1282816c7 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-24, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// statemine-dev +// --pallet +// pallet_message_queue +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/assets/statemine/src/weights + +#![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/assets/asset-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs index 107d78c98f978f6500f721193fc81ec009204b98..27687e10751b3ad052ddd6a9269cdcc415ef563d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nft_fractionalization.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nft_fractionalization.rs index e155e1bada3abc8f959404b112d7d393c40c0a61..601b47227c5840f373414bdad25c5956cbe05569 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nft_fractionalization.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nft_fractionalization.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for `pallet_nft_fractionalization` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nfts.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nfts.rs index 687dfa07f75148de35519507e1721f61dcf7cee8..fa7085c7344f91a2e16cf64e4efff73829f92512 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nfts.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_nfts.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_nfts` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_proxy.rs index 657bd2764cf2d88bdb22d322fc7a67f881c9f3be..d0042419719ae57792fa7218f0fe654dbfb5e846 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_proxy.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_proxy.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_proxy` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_session.rs index 926021917441904d3553f996e3fe9074ba1fe97a..2f9a39d2f801568835e5f85a8e9b29cbd3565937 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_session.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_timestamp.rs index 13f18861d37e8d715e0fb19d71bd60af8dcb8321..85207bc7c7e580d42d0cb4e7b6cdf13a4088f97d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_timestamp.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_uniques.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_uniques.rs index 53bead08f5d4cd9ed4388555a58eca1ae183a2ab..8c20f1601541e604c941f7cad3c39a3000a9ac95 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_uniques.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_uniques.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_uniques` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_utility.rs index 7db443ebbf19967d00676518a53909130ae77a86..a7952d6da00eaade8d02d4f44cf809c48ae4989c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_utility.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! 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 1cc4c2d0e24cf205e5ad33bf40e93dd22f864d80..71facff87d35f350114850653acfbebeb5c29529 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 @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-westend-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/assets/asset-hub-westend/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-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) @@ -64,40 +64,102 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_833_000 picoseconds. - Weight::from_parts(30_472_000, 0) + // Minimum execution time: 21_630_000 picoseconds. + Weight::from_parts(22_306_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(5)) + .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: 22_922_000 picoseconds. - Weight::from_parts(23_650_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 91_802_000 picoseconds. + Weight::from_parts(93_672_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: 17_468_000 picoseconds. - Weight::from_parts(18_068_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `367` + // Estimated: `6196` + // Minimum execution time: 118_930_000 picoseconds. + Weight::from_parts(122_306_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: 140_527_000 picoseconds. + Weight::from_parts(144_501_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: 8_780_000 picoseconds. - Weight::from_parts(9_201_000, 0) + // Minimum execution time: 7_556_000 picoseconds. + Weight::from_parts(7_798_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: 8_886_000 picoseconds. - Weight::from_parts(9_102_000, 0) + // Minimum execution time: 6_373_000 picoseconds. + Weight::from_parts(6_603_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_665_000 picoseconds. - Weight::from_parts(2_884_000, 0) + // Minimum execution time: 1_941_000 picoseconds. + Weight::from_parts(2_088_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -126,6 +188,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) @@ -142,14 +206,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 34_513_000 picoseconds. - Weight::from_parts(36_207_000, 0) + // Minimum execution time: 27_080_000 picoseconds. + Weight::from_parts(27_820_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(7)) + .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) @@ -166,10 +232,10 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 35_770_000 picoseconds. - Weight::from_parts(36_462_000, 0) + // Minimum execution time: 28_850_000 picoseconds. + Weight::from_parts(29_506_000, 0) .saturating_add(Weight::from_parts(0, 3828)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) @@ -178,48 +244,50 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_763_000 picoseconds. - Weight::from_parts(3_079_000, 0) + // Minimum execution time: 2_033_000 picoseconds. + Weight::from_parts(2_201_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `162` - // Estimated: `11052` - // Minimum execution time: 17_170_000 picoseconds. - Weight::from_parts(17_674_000, 0) - .saturating_add(Weight::from_parts(0, 11052)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `159` + // Estimated: `13524` + // Minimum execution time: 18_844_000 picoseconds. + Weight::from_parts(19_197_000, 0) + .saturating_add(Weight::from_parts(0, 13524)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `166` - // Estimated: `11056` - // Minimum execution time: 16_857_000 picoseconds. - Weight::from_parts(17_407_000, 0) - .saturating_add(Weight::from_parts(0, 11056)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `163` + // Estimated: `13528` + // Minimum execution time: 18_940_000 picoseconds. + Weight::from_parts(19_450_000, 0) + .saturating_add(Weight::from_parts(0, 13528)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `13538` - // Minimum execution time: 19_040_000 picoseconds. - Weight::from_parts(19_550_000, 0) - .saturating_add(Weight::from_parts(0, 13538)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16013` + // Minimum execution time: 20_521_000 picoseconds. + Weight::from_parts(21_076_000, 0) + .saturating_add(Weight::from_parts(0, 16013)) + .saturating_add(T::DbWeight::get().reads(6)) } /// 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) @@ -234,37 +302,39 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 31_623_000 picoseconds. - Weight::from_parts(32_646_000, 0) + // Minimum execution time: 26_007_000 picoseconds. + Weight::from_parts(26_448_000, 0) .saturating_add(Weight::from_parts(0, 6152)) - .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:3 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `206` - // Estimated: `8621` - // Minimum execution time: 9_148_000 picoseconds. - Weight::from_parts(9_402_000, 0) - .saturating_add(Weight::from_parts(0, 8621)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11096` + // Minimum execution time: 11_584_000 picoseconds. + Weight::from_parts(12_080_000, 0) + .saturating_add(Weight::from_parts(0, 11096)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `11063` - // Minimum execution time: 17_630_000 picoseconds. - Weight::from_parts(17_941_000, 0) - .saturating_add(Weight::from_parts(0, 11063)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `170` + // Estimated: `13535` + // Minimum execution time: 19_157_000 picoseconds. + Weight::from_parts(19_513_000, 0) + .saturating_add(Weight::from_parts(0, 13535)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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) @@ -277,12 +347,50 @@ 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: `215` - // Estimated: `11105` - // Minimum execution time: 38_425_000 picoseconds. - Weight::from_parts(39_219_000, 0) - .saturating_add(Weight::from_parts(0, 11105)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `212` + // Estimated: `13577` + // Minimum execution time: 34_878_000 picoseconds. + Weight::from_parts(35_623_000, 0) + .saturating_add(Weight::from_parts(0, 13577)) + .saturating_add(T::DbWeight::get().reads(11)) .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: 3_900_000 picoseconds. + Weight::from_parts(4_161_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: 25_731_000 picoseconds. + Weight::from_parts(26_160_000, 0) + .saturating_add(Weight::from_parts(0, 11205)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `3625` + // Minimum execution time: 37_251_000 picoseconds. + Weight::from_parts(38_075_000, 0) + .saturating_add(Weight::from_parts(0, 3625)) + .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_to_rococo.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs similarity index 70% rename from cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router_to_rococo.rs rename to cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs index 3ce9e4e7d319c09b74559e7e72a8b9df44bb1031..84d717b0283c764cac14cce63ca34f81c9f58e8c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router_to_rococo.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,10 +17,10 @@ //! 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-09-19, 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-guclnr1q-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-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("asset-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_xcm_bridge_hub_router -// --chain=asset-hub-rococo-dev +// --chain=asset-hub-westend-dev // --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,35 +48,33 @@ 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: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) - /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `232` - // Estimated: `3697` - // Minimum execution time: 10_861_000 picoseconds. - Weight::from_parts(11_253_000, 0) - .saturating_add(Weight::from_parts(0, 3697)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `193` + // Estimated: `1678` + // 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: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `169` - // Estimated: `3634` - // Minimum execution time: 5_807_000 picoseconds. - Weight::from_parts(6_018_000, 0) - .saturating_add(Weight::from_parts(0, 3634)) + // Measured: `111` + // Estimated: `1596` + // 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)) } /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) @@ -85,22 +83,24 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `117` // Estimated: `1502` - // Minimum execution time: 11_479_000 picoseconds. - Weight::from_parts(11_831_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: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) + /// 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: `PolkadotXcm::SupportedVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `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::VersionDiscoveryQueue` (r:1 w:1) /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) @@ -109,16 +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::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: `445` - // Estimated: `3910` - // Minimum execution time: 52_670_000 picoseconds. - Weight::from_parts(54_368_000, 0) - .saturating_add(Weight::from_parts(0, 3910)) - .saturating_add(T::DbWeight::get().reads(9)) + // 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/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index 3e47cf077a292c21e94404a5b11ec4203ed8ee81..8c77774da2dd747f4c3321ae9e2dfb3984cedb0f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; @@ -24,14 +23,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,40 +49,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } pub struct AssetHubWestendXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for AssetHubWestendXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -111,44 +106,36 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -163,7 +150,7 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -175,13 +162,13 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -209,21 +196,21 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - Weight::MAX + XcmGeneric::::universal_origin() } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -235,11 +222,11 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index f482064e84e9c5b918882ed47ae1afd471f3422c..eaf07aac52cefa88f524e6f3a2180ab9faf2b088 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -1,25 +1,24 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! 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: @@ -54,8 +53,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 25_407_000 picoseconds. - Weight::from_parts(25_949_000, 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)) } @@ -65,15 +64,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 51_335_000 picoseconds. - Weight::from_parts(52_090_000, 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:2 w: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) @@ -87,48 +88,53 @@ impl WeightInfo { pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 74_312_000 picoseconds. - Weight::from_parts(76_725_000, 6196) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) + // 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: `Benchmark::Override` (r:0 w:0) - // Proof: `Benchmark::Override` (`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`) pub fn reserve_asset_deposited() -> 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) + // 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: `145` - // Estimated: `3610` - // Minimum execution time: 446_848_000 picoseconds. - Weight::from_parts(466_251_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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_602_000 picoseconds. - Weight::from_parts(3_844_000, 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`) @@ -136,15 +142,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 25_480_000 picoseconds. - Weight::from_parts(26_142_000, 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:1 w: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) @@ -158,20 +166,24 @@ impl WeightInfo { pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 51_540_000 picoseconds. - Weight::from_parts(53_744_000, 3610) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // 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) @@ -180,9 +192,9 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 32_279_000 picoseconds. - Weight::from_parts(33_176_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index e776529eb7f8e0cfdc70dcc76200e1ec21de7aeb..fc196abea0f5e61d746760e2b2bf5a7d8d0a476b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -1,44 +1,41 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-26, 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("asset-hub-westend-dev"), DB CACHE: 1024 +//! 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 +// target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=asset-hub-westend-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::generic -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --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)] @@ -52,31 +49,35 @@ 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: `145` - // Estimated: `3610` - // Minimum execution time: 405_795_000 picoseconds. - Weight::from_parts(421_225_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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: 4_021_000 picoseconds. - Weight::from_parts(4_234_000, 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`) @@ -84,79 +85,83 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3568` - // Minimum execution time: 11_004_000 picoseconds. - Weight::from_parts(11_217_000, 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: 12_888_000 picoseconds. - Weight::from_parts(13_249_000, 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: 4_504_000 picoseconds. - Weight::from_parts(4_984_000, 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: 2_733_000 picoseconds. - Weight::from_parts(2_887_000, 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: 2_753_000 picoseconds. - Weight::from_parts(2_844_000, 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: 2_741_000 picoseconds. - Weight::from_parts(2_826_000, 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: 3_417_000 picoseconds. - Weight::from_parts(3_525_000, 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: 2_775_000 picoseconds. - Weight::from_parts(2_853_000, 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: `145` - // Estimated: `3610` - // Minimum execution time: 27_035_000 picoseconds. - Weight::from_parts(27_734_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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`) @@ -164,8 +169,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 15_728_000 picoseconds. - Weight::from_parts(16_145_000, 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)) } @@ -173,11 +178,13 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_630_000 picoseconds. - Weight::from_parts(2_700_000, 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) @@ -192,9 +199,9 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_996_000 picoseconds. - Weight::from_parts(30_620_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) + // 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) @@ -203,127 +210,145 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_629_000 picoseconds. - Weight::from_parts(4_861_000, 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: 135_145_000 picoseconds. - Weight::from_parts(142_115_000, 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_948_000 picoseconds. - Weight::from_parts(12_160_000, 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: 2_718_000 picoseconds. - Weight::from_parts(2_794_000, 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: 2_590_000 picoseconds. - Weight::from_parts(2_674_000, 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_858_000 picoseconds. - Weight::from_parts(2_939_000, 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: `145` - // Estimated: `3610` - // Minimum execution time: 30_652_000 picoseconds. - Weight::from_parts(31_552_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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_408_000 picoseconds. - Weight::from_parts(5_597_000, 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: `145` - // Estimated: `3610` - // Minimum execution time: 27_144_000 picoseconds. - Weight::from_parts(27_736_000, 3610) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // 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: 2_696_000 picoseconds. - Weight::from_parts(2_802_000, 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: 2_655_000 picoseconds. - Weight::from_parts(2_720_000, 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: 2_599_000 picoseconds. - Weight::from_parts(2_723_000, 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: 2_630_000 picoseconds. - Weight::from_parts(2_728_000, 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: 2_797_000 picoseconds. - Weight::from_parts(2_928_000, 0) + // Minimum execution time: 1_990_000 picoseconds. + Weight::from_parts(2_076_000, 0) } } 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 f14b410327c91df30249f03a7a3aec335fd5eec5..c993d61545a66c24826c0ab66e20546e3fa752b0 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 @@ -15,68 +15,79 @@ use super::{ AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BaseDeliveryFee, - FeeAssetId, ParachainInfo, ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, TransactionByteFee, TrustBackedAssetsInstance, WeightToFee, + CollatorSelection, FeeAssetId, ForeignAssets, ForeignAssetsInstance, ParachainInfo, + ParachainSystem, PolkadotXcm, PoolAssets, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + ToRococoXcmRouter, TransactionByteFee, TrustBackedAssetsInstance, Uniques, WeightToFee, XcmpQueue, }; -use crate::ForeignAssets; use assets_common::{ - local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, matching::{FromSiblingParachain, IsForeignConcreteAsset}, + TrustBackedAssetsAsLocation, }; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, + parameter_types, + traits::{ + tokens::imbalance::ResolveAssetTo, ConstU32, Contains, Equals, Everything, Nothing, + PalletInfoAccess, + }, }; use frame_system::EnsureRoot; 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::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, - EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeesToAccount, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; -#[cfg(feature = "runtime-benchmarks")] -use {cumulus_primitives_core::ParaId, sp_core::Get}; - parameter_types! { - pub const WestendLocation: MultiLocation = MultiLocation::parent(); + pub const WestendLocation: Location = Location::parent(); + pub const WestendLocationV3: xcm::v3::Location = xcm::v3::Location::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 UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); - pub TrustBackedAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); - pub ForeignAssetsPalletLocation: MultiLocation = + pub TrustBackedAssetsPalletLocation: Location = + PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); + pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); + pub ForeignAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); - pub PoolAssetsPalletLocation: MultiLocation = + pub PoolAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); + pub UniquesPalletLocation: Location = + PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); - pub TreasuryAccount: Option = Some(TREASURY_PALLET_ID.into_account_truncating()); + pub StakingPot: AccountId = CollatorSelection::account_id(); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -89,15 +100,18 @@ pub type LocationToAccountId = ( // Foreign chain account alias into local accounts according to a hash of their standard // description. HashedDescription>, + // Different global consensus parachain sovereign account. + // (Used for over-bridge transfers and reserve processing) + GlobalConsensusParachainConvertsFor, ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -105,7 +119,7 @@ pub type CurrencyTransactor = CurrencyAdapter< (), >; -/// `AssetId/Balance` converter for `TrustBackedAssets` +/// `AssetId`/`Balance` converter for `TrustBackedAssets`. pub type TrustBackedAssetsConvertedConcreteId = assets_common::TrustBackedAssetsConvertedConcreteId; @@ -115,7 +129,7 @@ pub type FungiblesTransactor = FungiblesAdapter< Assets, // Use this currency when it is a fungible asset matching the given location or name: TrustBackedAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -126,14 +140,34 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; -/// `AssetId/Balance` converter for `TrustBackedAssets` +/// Matcher for converting `ClassId`/`InstanceId` into a uniques asset. +pub type UniquesConvertedConcreteId = + assets_common::UniquesConvertedConcreteId; + +/// Means for transacting unique assets. +pub type UniquesTransactor = NonFungiblesAdapter< + // Use this non-fungibles implementation: + Uniques, + // This adapter will handle any non-fungible asset from the uniques pallet. + UniquesConvertedConcreteId, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // Does not check teleports. + NoChecking, + // The account to use for tracking teleports. + CheckingAccount, +>; + +/// `AssetId`/`Balance` converter for `ForeignAssets`. pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConvertedConcreteId< ( // Ignore `TrustBackedAssets` explicitly StartsWith, // Ignore asset which starts explicitly with our `GlobalConsensus(NetworkId)`, means: - // - foreign assets from our consensus should be: `MultiLocation {parents: 1, - // X*(Parachain(xyz), ..)} + // - foreign assets from our consensus should be: `Location {parents: 1, X*(Parachain(xyz), + // ..)} // - foreign assets outside our consensus with the same `GlobalConsensus(NetworkId)` wont // be accepted here StartsWithExplicitGlobalConsensus, @@ -147,7 +181,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< ForeignAssets, // Use this currency when it is a fungible asset matching the given location or name: ForeignAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -157,7 +191,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< CheckingAccount, >; -/// `AssetId/Balance` converter for `PoolAssets` +/// `AssetId`/`Balance` converter for `PoolAssets`. pub type PoolAssetsConvertedConcreteId = assets_common::PoolAssetsConvertedConcreteId; @@ -167,7 +201,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< PoolAssets, // Use this currency when it is a fungible asset matching the given location or name: PoolAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -179,27 +213,13 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< >; /// Means for transacting assets on this chain. -pub type AssetTransactors = - (CurrencyTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor); - -/// Simple `MultiLocation` matcher for Local and Foreign asset `MultiLocation`. -pub struct LocalAndForeignAssetsMultiLocationMatcher; -impl MatchesLocalAndForeignAssetsMultiLocation for LocalAndForeignAssetsMultiLocationMatcher { - fn is_local(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - TrustBackedAssetsConvertedConcreteId::contains(location) - } - - fn is_foreign(location: &MultiLocation) -> bool { - use assets_common::fungible_conversion::MatchesMultiLocation; - ForeignAssetsConvertedConcreteId::contains(location) - } -} -impl Contains for LocalAndForeignAssetsMultiLocationMatcher { - fn contains(location: &MultiLocation) -> bool { - Self::is_local(location) || Self::is_foreign(location) - } -} +pub type AssetTransactors = ( + FungibleTransactor, + FungiblesTransactor, + ForeignFungiblesTransactor, + PoolFungiblesTransactor, + UniquesTransactor, +); /// 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 @@ -231,14 +251,30 @@ parameter_types! { pub XcmAssetFeesReceiver: Option = Authorship::author(); } -match_types! { - pub type ParentOrParentsPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { .. }) } - }; - pub type TreasuryPallet: impl Contains = { - MultiLocation { parents: 1, interior: X1(PalletInstance(37)) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } +} + +pub struct FellowshipEntities; +impl Contains for FellowshipEntities { + fn contains(location: &Location) -> bool { + matches!( + location.unpack(), + (1, [Parachain(1001), Plurality { id: BodyId::Technical, .. }]) | + (1, [Parachain(1001), PalletInstance(64)]) | + (1, [Parachain(1001), PalletInstance(65)]) + ) + } +} + +pub struct AmbassadorEntities; +impl Contains for AmbassadorEntities { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [Parachain(1001), PalletInstance(74)])) + } } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -258,28 +294,36 @@ 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())) => + 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::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( - 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::DmpQueue(..) | + RuntimeCall::MessageQueue(..) | RuntimeCall::Assets( pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } | @@ -447,7 +491,9 @@ impl Contains for SafeCallFilter { pallet_uniques::Call::set_accept_ownership { .. } | pallet_uniques::Call::set_collection_max_supply { .. } | pallet_uniques::Call::set_price { .. } | - pallet_uniques::Call::buy_item { .. }, + pallet_uniques::Call::buy_item { .. } + ) | RuntimeCall::ToRococoXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } ) ) } @@ -466,9 +512,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 treasury pallet get - // free execution. - AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, TreasuryPallet)>, + // Parent, its pluralities (i.e. governance bodies), relay treasury pallet and + // BridgeHub get free execution. + AllowExplicitUnpaidExecutionFrom<( + ParentOrParentsPlurality, + Equals, + Equals, + FellowshipEntities, + AmbassadorEntities, + )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, ), @@ -491,35 +543,24 @@ pub type AssetFeeAsExistentialDepositMultiplierFeeCharger = AssetFeeAsExistentia TrustBackedAssetsInstance, >; -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 - )), - } - }; -} - -parameter_types! { - pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); -} - -pub struct RelayTreasury; -impl Contains for RelayTreasury { - fn contains(location: &MultiLocation) -> bool { - let relay_treasury_location = RelayTreasuryLocation::get(); - *location == relay_treasury_location - } -} +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. +pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = + AssetFeeAsExistentialDepositMultiplier< + Runtime, + WeightToFee, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, + >; /// 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, RelayTreasury); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, + FellowshipEntities, + AmbassadorEntities, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// @@ -536,10 +577,11 @@ impl xcm_executor::Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Asset Hub Westend does not recognize a reserve location for any asset. This does not prevent - // Asset Hub acting _as_ a reserve location for WND and assets created under `pallet-assets`. - // For WND, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); + // Asset Hub trusts only particular, pre-configured bridged locations from a different consensus + // 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 WND and assets created + // under `pallet-assets`. Users must use teleport where allowed (e.g. WND with the Relay Chain). + type IsReserve = (bridging::to_rococo::IsTrustedBridgedReserveLocationForConcreteAsset,); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; @@ -550,6 +592,20 @@ impl xcm_executor::Config for XcmConfig { >; type Trader = ( UsingComponents>, + cumulus_primitives_utility::SwapFirstAssetTrader< + WestendLocationV3, + crate::AssetConversion, + WeightToFee, + crate::NativeAndAssets, + ( + TrustBackedAssetsAsLocation, + ForeignAssetsConvertedConcreteId, + ), + ResolveAssetTo, + AccountId, + >, + // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated + // `pallet_assets` instance - `Assets`. cumulus_primitives_utility::TakeFirstAssetTrader< AccountId, AssetFeeAsExistentialDepositMultiplierFeeCharger, @@ -561,6 +617,19 @@ impl xcm_executor::Config for XcmConfig { XcmAssetFeesReceiver, >, >, + // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated + // `pallet_assets` instance - `ForeignAssets`. + cumulus_primitives_utility::TakeFirstAssetTrader< + AccountId, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, + ForeignAssetsConvertedConcreteId, + ForeignAssets, + cumulus_primitives_utility::XcmFeesTo32ByteAccount< + ForeignFungiblesTransactor, + AccountId, + XcmAssetFeesReceiver, + >, + >, ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; @@ -570,12 +639,19 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); - type FeeManager = XcmFeesToAccount; + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); - type UniversalAliases = Nothing; + type UniversalAliases = (bridging::to_rococo::UniversalAliases,); type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } /// Local origins on this chain are allowed to dispatch XCM sends/executions. @@ -584,19 +660,22 @@ 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<( +/// For routing XCM messages which do not cross local consensus boundary. +type LocalXcmRouter = ( // Two routers - use UMP to communicate with the relay chain: cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, -)>; +); -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + LocalXcmRouter, + // Router which wraps and sends xcm to BridgeHub to be delivered to the Rococo + // GlobalConsensus + ToRococoXcmRouter, +)>; impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -623,8 +702,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -644,36 +721,146 @@ pub type ForeignCreatorsSovereignAccountOf = ( /// 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)) } +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> xcm::v3::Location { + xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) } } -#[cfg(feature = "runtime-benchmarks")] -pub struct BenchmarkMultiLocationConverter { - _phantom: sp_std::marker::PhantomData, -} +/// All configuration related to bridging +pub mod bridging { + use super::*; + use assets_common::matching; + use sp_std::collections::btree_set::BTreeSet; + + parameter_types! { + /// Base price of every byte of the Westend -> Rococo message. Can be adjusted via + /// governance `set_storage` call. + /// + /// Default value is our estimation of the: + /// + /// 1) an approximate cost of XCM execution (`ExportMessage` and surroundings) at Westend bridge hub; + /// + /// 2) the approximate cost of Westend -> Rococo message delivery transaction on Rococo Bridge Hub, + /// converted into WNDs using 1:1 conversion rate; + /// + /// 3) the approximate cost of Westend -> Rococo message confirmation transaction on Westend Bridge Hub. + pub storage XcmBridgeHubRouterBaseFee: Balance = + bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get() + .saturating_add(bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get()) + .saturating_add(bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get()); + /// Price of every byte of the Westend -> Rococo message. Can be adjusted via + /// governance `set_storage` call. + pub storage XcmBridgeHubRouterByteFee: Balance = TransactionByteFee::get(); + + pub SiblingBridgeHubParaId: u32 = bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID; + pub SiblingBridgeHub: Location = Location::new(1, [Parachain(SiblingBridgeHubParaId::get())]); + /// Router expects payment with this `AssetId`. + /// (`AssetId` has to be aligned with `BridgeTable`) + pub XcmBridgeHubRouterFeeAssetId: AssetId = WestendLocation::get().into(); + + pub BridgeTable: sp_std::vec::Vec = + sp_std::vec::Vec::new().into_iter() + .chain(to_rococo::BridgeTable::get()) + .collect(); + } -#[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()), - ), + pub type NetworkExportTable = xcm_builder::NetworkExportTable; + + pub mod to_rococo { + use super::*; + + parameter_types! { + pub SiblingBridgeHubWithBridgeHubRococoInstance: Location = Location::new( + 1, + [ + Parachain(SiblingBridgeHubParaId::get()), + PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX) + ] + ); + + pub const RococoNetwork: NetworkId = NetworkId::Rococo; + pub AssetHubRococo: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)]); + pub RocLocation: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get())]); + + pub RocFromAssetHubRococo: (AssetFilter, Location) = ( + Wild(AllOf { fun: WildFungible, id: AssetId(RocLocation::get()) }), + AssetHubRococo::get() + ); + + /// 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( + RococoNetwork::get(), + Some(sp_std::vec![ + AssetHubRococo::get().interior.split_global().expect("invalid configuration for AssetHubRococo").1, + ]), + SiblingBridgeHub::get(), + // base delivery fee to local `BridgeHub` + Some(( + XcmBridgeHubRouterFeeAssetId::get(), + XcmBridgeHubRouterBaseFee::get(), + ).into()) + ) + ]; + + /// Universal aliases + pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( + sp_std::vec![ + (SiblingBridgeHubWithBridgeHubRococoInstance::get(), GlobalConsensus(RococoNetwork::get())) + ] + ); + } + + impl Contains<(Location, Junction)> for UniversalAliases { + fn contains(alias: &(Location, Junction)) -> bool { + UniversalAliases::get().contains(alias) + } + } + + /// Reserve locations filter for `xcm_executor::Config::IsReserve`. + /// Locations from which the runtime accepts reserved assets. + pub type IsTrustedBridgedReserveLocationForConcreteAsset = + matching::IsTrustedBridgedReserveLocationForConcreteAsset< + UniversalLocation, + ( + // allow receive ROC from AssetHubRococo + xcm_builder::Case, + // and nothing else + ), + >; + + impl Contains for ToRococoXcmRouter { + fn contains(call: &RuntimeCall) -> bool { + matches!( + call, + RuntimeCall::ToRococoXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { .. } + ) + ) + } } } - fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box { - sp_std::boxed::Box::new(Self::asset_id(asset_id)) + /// Benchmarks helper for bridging configuration. + #[cfg(feature = "runtime-benchmarks")] + pub struct BridgingBenchmarksHelper; + + #[cfg(feature = "runtime-benchmarks")] + impl BridgingBenchmarksHelper { + pub fn prepare_universal_alias() -> Option<(Location, Junction)> { + let alias = + to_rococo::UniversalAliases::get().into_iter().find_map(|(location, junction)| { + match to_rococo::SiblingBridgeHubWithBridgeHubRococoInstance::get() + .eq(&location) + { + true => Some((location, junction)), + false => None, + } + }); + assert!(alias.is_some(), "we expect here BridgeHubWestend to Rococo mapping at least"); + Some(alias.unwrap()) + } } } 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 599ff90e254aa5f4fb745e153b8a3e9b9addb0e8..3ba9b9587d888bd1821751053ecaa19caacf814d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -18,57 +18,393 @@ //! Tests for the Westmint (Westend Assets Hub) chain. use asset_hub_westend_runtime::{ + xcm_config, xcm_config::{ - AssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - WestendLocation, + bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, + LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, + TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, XcmConfig, }, - AllPalletsWithoutSystem, MetadataDepositBase, MetadataDepositPerByte, RuntimeCall, - RuntimeEvent, + AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, + ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, + PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; -pub use asset_hub_westend_runtime::{ - xcm_config::{CheckingAccount, TrustBackedAssetsPalletLocation, XcmConfig}, - AllowMultiAssetPools, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System, - TrustBackedAssetsInstance, +pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System}; +use asset_test_utils::{ + test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, + ExtBuilder, SlotDurations, }; -use asset_test_utils::{CollatorSessionKeys, ExtBuilder, XcmReceivedFrom}; -use codec::{Decode, DecodeLimit, Encode}; +use codec::{Decode, Encode}; use cumulus_primitives_utility::ChargeWeightInFungibles; use frame_support::{ assert_noop, assert_ok, - traits::fungibles::InspectEnumerable, + traits::{ + fungible::{Inspect, Mutate}, + fungibles::{ + Create, Inspect as FungiblesInspect, InspectEnumerable, Mutate as FungiblesMutate, + }, + }, weights::{Weight, WeightToFee as WeightToFeeT}, }; -use parachains_common::{ - westend::fee::WeightToFee, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, -}; -use sp_io; +use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; +use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; -use std::convert::Into; -use xcm::{latest::prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH}; -use xcm_executor::{ - traits::{Identity, JustTry, WeightTrader}, - XcmExecutor, -}; +use std::{convert::Into, ops::Mul}; +use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; +use xcm::latest::prelude::{Assets as XcmAssets, *}; +use xcm_builder::V4V3LocationConverter; +use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; type AssetIdForTrustBackedAssetsConvert = - assets_common::AssetIdForTrustBackedAssetsConvert; + assets_common::AssetIdForTrustBackedAssetsConvert; + +type AssetIdForTrustBackedAssetsConvertLatest = + assets_common::AssetIdForTrustBackedAssetsConvertLatest; type RuntimeHelper = asset_test_utils::RuntimeHelper; -fn collator_session_keys() -> CollatorSessionKeys { - CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, +fn collator_session_key(account: [u8; 32]) -> CollatorSessionKey { + CollatorSessionKey::new( + AccountId::from(account), + AccountId::from(account), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(account)) }, ) } +fn collator_session_keys() -> CollatorSessionKeys { + CollatorSessionKeys::default().add(collator_session_key(ALICE)) +} + +fn slot_durations() -> SlotDurations { + SlotDurations { + relay: SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS.into()), + para: SlotDuration::from_millis(SLOT_DURATION), + } +} + +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] -fn test_asset_xcm_trader() { +fn test_buy_and_refund_weight_in_native() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let native_location = WestendLocation::get(); + let initial_balance = 200 * UNITS; + + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + // keep initial total issuance to assert later. + let total_issuance = Balances::total_issuance(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (native_location.clone(), fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = + unused_asset.fungible.get(&native_location.clone().into()).map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Balances::total_issuance(), total_issuance); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (native_location, refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!(Balances::total_issuance(), total_issuance + fee - refund); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let asset_1: u32 = 1; + let native_location = WestendLocationV3::get(); + let asset_1_location = + AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap(); + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create(asset_1, bob.clone(), true, 10)); + + assert_ok!(Assets::mint_into(asset_1, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(asset_1_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = Assets::total_issuance(asset_1); + let native_total_issuance = Balances::total_issuance(); + + let asset_1_location_latest: Location = asset_1_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&asset_1_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, asset_1_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + Assets::total_issuance(asset_1), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + let bob: AccountId = SOME_ASSET_ADMIN.into(); + let staking_pot = CollatorSelection::account_id(); + let native_location = WestendLocationV3::get(); + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + // bob's initial balance for native and `asset1` assets. + let initial_balance = 200 * UNITS; + // liquidity for both arms of (native, asset1) pool. + let pool_liquidity = 100 * UNITS; + + // init asset, balances and pool. + assert_ok!(>::create( + foreign_location, + bob.clone(), + true, + 10 + )); + + assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(Balances::mint_into(&bob, initial_balance)); + assert_ok!(Balances::mint_into(&staking_pot, initial_balance)); + + assert_ok!(AssetConversion::create_pool( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeHelper::origin_of(bob.clone()), + Box::new(native_location), + Box::new(foreign_location), + pool_liquidity, + pool_liquidity, + 1, + 1, + bob, + )); + + // keep initial total issuance to assert later. + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let native_total_issuance = Balances::total_issuance(); + + let foreign_location_latest: Location = foreign_location.try_into().unwrap(); + + // prepare input to buy weight. + let weight = Weight::from_parts(4_000_000_000, 0); + let fee = WeightToFee::weight_to_fee(&weight); + let asset_fee = + AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap(); + let extra_amount = 100; + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + let payment: Asset = (foreign_location_latest.clone(), asset_fee + extra_amount).into(); + + // init trader and buy weight. + let mut trader = ::Trader::new(); + let unused_asset = + trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok"); + + // assert. + let unused_amount = unused_asset + .fungible + .get(&foreign_location_latest.clone().into()) + .map_or(0, |a| *a); + assert_eq!(unused_amount, extra_amount); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee + ); + + // prepare input to refund weight. + let refund_weight = Weight::from_parts(1_000_000_000, 0); + let refund = WeightToFee::weight_to_fee(&refund_weight); + let (reserve1, reserve2) = + AssetConversion::get_reserves(native_location, foreign_location).unwrap(); + let asset_refund = + AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap(); + + // refund. + let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap(); + assert_eq!(actual_refund, (foreign_location_latest, asset_refund).into()); + + // assert. + assert_eq!(Balances::balance(&staking_pot), initial_balance); + // only after `trader` is dropped we expect the fee to be resolved into the treasury + // account. + drop(trader); + assert_eq!(Balances::balance(&staking_pot), initial_balance + fee - refund); + assert_eq!( + ForeignAssets::total_issuance(foreign_location), + asset_total_issuance + asset_fee - asset_refund + ); + assert_eq!(Balances::total_issuance(), native_total_issuance); + }) +} + +#[test] +fn test_asset_xcm_take_first_trader() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -97,9 +433,9 @@ fn test_asset_xcm_trader() { minimum_asset_balance )); - // get asset id as multilocation - let asset_multilocation = - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(); + // get asset id as location + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(); // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); @@ -117,8 +453,8 @@ fn test_asset_xcm_trader() { // Lets pay with: asset_amount_needed + asset_amount_extra let asset_amount_extra = 100_u128; - let asset: MultiAsset = - (asset_multilocation, asset_amount_needed + asset_amount_extra).into(); + let asset: Asset = + (asset_location.clone(), asset_amount_needed + asset_amount_extra).into(); let mut trader = ::Trader::new(); let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; @@ -126,9 +462,7 @@ fn test_asset_xcm_trader() { // Lets buy_weight and make sure buy_weight does not return an error let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); // Check whether a correct amount of unused assets is returned - assert_ok!( - unused_assets.ensure_contains(&(asset_multilocation, asset_amount_extra).into()) - ); + assert_ok!(unused_assets.ensure_contains(&(asset_location, asset_amount_extra).into())); // Drop trader drop(trader); @@ -148,7 +482,88 @@ fn test_asset_xcm_trader() { } #[test] -fn test_asset_xcm_trader_with_refund() { +fn test_foreign_asset_xcm_take_first_trader() { + ExtBuilder::::default() + .with_collators(vec![AccountId::from(ALICE)]) + .with_session_keys(vec![( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, + )]) + .build() + .execute_with(|| { + // We need root origin to create a sufficient asset + let minimum_asset_balance = 3333333_u128; + let foreign_location = xcm::v3::Location { + parents: 1, + interior: ( + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ) + .into(), + }; + assert_ok!(ForeignAssets::force_create( + RuntimeHelper::root_origin(), + foreign_location.into(), + AccountId::from(ALICE).into(), + true, + minimum_asset_balance + )); + + // We first mint enough asset for the account to exist for assets + assert_ok!(ForeignAssets::mint( + RuntimeHelper::origin_of(AccountId::from(ALICE)), + foreign_location.into(), + AccountId::from(ALICE).into(), + minimum_asset_balance + )); + + let asset_location_v4: Location = foreign_location.try_into().unwrap(); + + // Set Alice as block author, who will receive fees + RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); + + // We are going to buy 4e9 weight + let bought = Weight::from_parts(4_000_000_000u64, 0); + + // Lets calculate amount needed + let asset_amount_needed = ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(foreign_location, bought) + .expect("failed to compute"); + + // Lets pay with: asset_amount_needed + asset_amount_extra + let asset_amount_extra = 100_u128; + let asset: Asset = + (asset_location_v4.clone(), asset_amount_needed + asset_amount_extra).into(); + + let mut trader = ::Trader::new(); + let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; + + // Lets buy_weight and make sure buy_weight does not return an error + let unused_assets = trader.buy_weight(bought, asset.into(), &ctx).expect("Expected Ok"); + // Check whether a correct amount of unused assets is returned + assert_ok!( + unused_assets.ensure_contains(&(asset_location_v4, asset_amount_extra).into()) + ); + + // Drop trader + drop(trader); + + // Make sure author(Alice) has received the amount + assert_eq!( + ForeignAssets::balance(foreign_location, AccountId::from(ALICE)), + minimum_asset_balance + asset_amount_needed + ); + + // We also need to ensure the total supply increased + assert_eq!( + ForeignAssets::total_supply(foreign_location), + minimum_asset_balance + asset_amount_needed + ); + }); +} + +#[test] +fn test_asset_xcm_take_first_trader_with_refund() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -184,12 +599,13 @@ fn test_asset_xcm_trader_with_refund() { // We are going to buy 4e9 weight let bought = Weight::from_parts(4_000_000_000u64, 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); // lets calculate amount needed let amount_bought = WeightToFee::weight_to_fee(&bought); - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location.clone(), amount_bought).into(); // Make sure buy_weight does not return an error assert_ok!(trader.buy_weight(bought, asset.clone().into(), &ctx)); @@ -207,7 +623,7 @@ fn test_asset_xcm_trader_with_refund() { assert_eq!( trader.refund_weight(bought - weight_used, &ctx), - Some((asset_multilocation, amount_refunded).into()) + Some((asset_location, amount_refunded).into()) ); // Drop trader @@ -227,7 +643,7 @@ fn test_asset_xcm_trader_with_refund() { } #[test] -fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { +fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_ed() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -253,10 +669,11 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); - // We are going to buy 5e9 weight + // We are going to buy small amount let bought = Weight::from_parts(500_000_000u64, 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -265,7 +682,7 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { "we are testing what happens when the amount does not exceed ED" ); - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location, amount_bought).into(); // Buy weight should return an error assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); @@ -279,7 +696,7 @@ fn test_asset_xcm_trader_refund_not_possible_since_amount_less_than_ed() { } #[test] -fn test_that_buying_ed_refund_does_not_refund() { +fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -307,7 +724,8 @@ fn test_that_buying_ed_refund_does_not_refund() { let bought = Weight::from_parts(500_000_000u64, 0); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); let amount_bought = WeightToFee::weight_to_fee(&bought); @@ -318,11 +736,11 @@ fn test_that_buying_ed_refund_does_not_refund() { // We know we will have to buy at least ED, so lets make sure first it will // fail with a payment of less than ED - let asset: MultiAsset = (asset_multilocation, amount_bought).into(); + let asset: Asset = (asset_location.clone(), amount_bought).into(); assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); // Now lets buy ED at least - let asset: MultiAsset = (asset_multilocation, ExistentialDeposit::get()).into(); + let asset: Asset = (asset_location.clone(), ExistentialDeposit::get()).into(); // Buy weight should work assert_ok!(trader.buy_weight(bought, asset.into(), &ctx)); @@ -343,7 +761,7 @@ fn test_that_buying_ed_refund_does_not_refund() { } #[test] -fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { +fn test_asset_xcm_take_first_trader_not_possible_for_non_sufficient_assets() { ExtBuilder::::default() .with_collators(vec![AccountId::from(ALICE)]) .with_session_keys(vec![( @@ -383,9 +801,10 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() { // lets calculate amount needed let asset_amount_needed = WeightToFee::weight_to_fee(&bought); - let asset_multilocation = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap(); + let asset_location = + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap(); - let asset: MultiAsset = (asset_multilocation, asset_amount_needed).into(); + let asset: Asset = (asset_location, asset_amount_needed).into(); // Make sure again buy_weight does return an error assert_noop!(trader.buy_weight(bought, asset.into(), &ctx), XcmError::TooExpensive); @@ -415,19 +834,25 @@ fn test_assets_balances_api_works() { .build() .execute_with(|| { let local_asset_id = 1; - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(1234), GeneralIndex(12345)) }; + let foreign_asset_id_location = xcm::v3::Location { + parents: 1, + interior: [ + xcm::v3::Junction::Parachain(1234), + xcm::v3::Junction::GeneralIndex(12345), + ] + .into(), + }; // check before assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), 0 ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); assert!(Runtime::query_account_balances(AccountId::from(ALICE)) .unwrap() - .try_as::() + .try_as::() .unwrap() .is_none()); @@ -458,7 +883,7 @@ fn test_assets_balances_api_works() { let foreign_asset_minimum_asset_balance = 3333333_u128; assert_ok!(ForeignAssets::force_create( RuntimeHelper::root_origin(), - foreign_asset_id_multilocation, + foreign_asset_id_location, AccountId::from(SOME_ASSET_ADMIN).into(), false, foreign_asset_minimum_asset_balance @@ -467,7 +892,7 @@ fn test_assets_balances_api_works() { // We first mint enough asset for the account to exist for assets assert_ok!(ForeignAssets::mint( RuntimeHelper::origin_of(AccountId::from(SOME_ASSET_ADMIN)), - foreign_asset_id_multilocation, + foreign_asset_id_location, AccountId::from(ALICE).into(), 6 * foreign_asset_minimum_asset_balance )); @@ -478,12 +903,12 @@ fn test_assets_balances_api_works() { minimum_asset_balance ); assert_eq!( - ForeignAssets::balance(foreign_asset_id_multilocation, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), 6 * minimum_asset_balance ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); - let result: MultiAssets = Runtime::query_account_balances(AccountId::from(ALICE)) + let result: XcmAssets = Runtime::query_account_balances(AccountId::from(ALICE)) .unwrap() .try_into() .unwrap(); @@ -498,13 +923,13 @@ fn test_assets_balances_api_works() { ))); // check trusted asset assert!(result.inner().iter().any(|asset| asset.eq(&( - AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(), + AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(), minimum_asset_balance ) .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - Identity::convert_back(&foreign_asset_id_multilocation).unwrap(), + V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(), 6 * foreign_asset_minimum_asset_balance ) .into()))); @@ -519,6 +944,7 @@ asset_test_utils::include_teleports_for_native_asset_works!( WeightToFee, ParachainSystem, collator_session_keys(), + slot_durations(), ExistentialDeposit::get(), Box::new(|runtime_event_encoded: Vec| { match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { @@ -526,12 +952,6 @@ asset_test_utils::include_teleports_for_native_asset_works!( _ => None, } }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), 1000 ); @@ -545,6 +965,7 @@ asset_test_utils::include_teleports_for_foreign_assets_works!( ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, collator_session_keys(), + slot_durations(), ExistentialDeposit::get(), Box::new(|runtime_event_encoded: Vec| { match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { @@ -581,7 +1002,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ XcmConfig, TrustBackedAssetsInstance, AssetIdForTrustBackedAssets, - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvertLatest, collator_session_keys(), ExistentialDeposit::get(), 12345, @@ -598,11 +1019,15 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ Runtime, XcmConfig, ForeignAssetsInstance, - MultiLocation, + xcm::v3::Location, JustTry, collator_session_keys(), ExistentialDeposit::get(), - MultiLocation { parents: 1, interior: X2(Parachain(1313), GeneralIndex(12345)) }, + xcm::v3::Location { + parents: 1, + interior: [xcm::v3::Junction::Parachain(1313), xcm::v3::Junction::GeneralIndex(12345)] + .into() + }, Box::new(|| { assert!(Assets::asset_ids().collect::>().is_empty()); }), @@ -617,8 +1042,8 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p WeightToFee, ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, - MultiLocation, - JustTry, + xcm::v3::Location, + V4V3LocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), @@ -641,28 +1066,350 @@ 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(), + local_bridge_hub_location: bridging::SiblingBridgeHub::get(), + bridged_target_location: bridging::to_rococo::AssetHubRococo::get(), + } +} + #[test] -fn plain_receive_teleported_asset_works() { - ExtBuilder::::default() - .with_collators(vec![AccountId::from(ALICE)]) - .with_session_keys(vec![( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) }, - )]) - .build() - .execute_with(|| { - let data = hex_literal::hex!("02100204000100000b00a0724e18090a13000100000b00a0724e180901e20f5e480d010004000101001299557001f55815d3fcb53c74463acb0cf6d14d4639b340982c60877f384609").to_vec(); - let message_id = sp_io::hashing::blake2_256(&data); +fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_rococo_works() { + asset_test_utils::test_cases_over_bridge::limited_reserve_transfer_assets_for_native_asset_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + slot_durations(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + bridging_to_asset_hub_rococo, + WeightLimit::Unlimited, + Some(xcm_config::bridging::XcmBridgeHubRouterFeeAssetId::get()), + Some(xcm_config::TreasuryAccount::get()), + ) +} - let maybe_msg = VersionedXcm::::decode_all_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut data.as_ref(), - ) - .map(xcm::v3::Xcm::::try_from).expect("failed").expect("failed"); +#[test] +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_swap_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving ROCs + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_rococo() + }, + ( + [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Rococo), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } + ) +} + +#[test] +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_sufficient_asset_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving ROCs + foreign_asset_create_params, + 1000000000000, + bridging_to_asset_hub_rococo, + ( + [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Rococo), + [Parachain(1000)].into() + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } + ) +} + +#[test] +fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ToRococoXcmRouterInstance, + >( + collator_session_keys(), + bridging_to_asset_hub_rococo, + || { + sp_std::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_westend::Call::ToRococoXcmRouter( + bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + .into(), + } + ] + .into() + }, + || { + sp_std::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_westend::Call::ToRococoXcmRouter( + bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: false, + } + ) + .encode() + .into(), + } + ] + .into() + }, + ) +} - let outcome = - XcmExecutor::::execute_xcm(Parent, maybe_msg, message_id, RuntimeHelper::xcm_max_weight(XcmReceivedFrom::Parent)); - assert_eq!(outcome.ensure_complete(), Ok(())); +#[test] +fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding + assert_eq!( + RuntimeCall::ToRococoXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, }) + .encode(), + bp_asset_hub_westend::Call::ToRococoXcmRouter( + bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ) +} + +#[test] +fn check_sane_weight_report_bridge_status() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = >::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); +} + +#[test] +fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterByteFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridging::XcmBridgeHubRouterByteFee::key().to_vec(), + bridging::XcmBridgeHubRouterByteFee::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_router_base_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterBaseFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); + ( + bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), + bridging::XcmBridgeHubRouterBaseFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} + +#[test] +fn reserve_transfer_native_asset_to_non_teleport_para_works() { + asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + >( + collator_session_keys(), + slot_durations(), + ExistentialDeposit::get(), + AccountId::from(ALICE), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + WeightLimit::Unlimited, + ); } diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 56171c7212e6ced94b64156c7d73e23c1823c3a8..c9252375cfbf019c27a1ce0d5dc38f11db30cac5 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -1,29 +1,32 @@ [package] name = "assets-common" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } impl-trait-for-tuples = "0.2.2" # Substrate -frame-support = { path = "../../../../../substrate/frame/support", default-features = false} -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false} -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false} -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false} +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } -pallet-asset-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false } # Polkadot -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false} -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } +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 parachains-common = { path = "../../../common", default-features = false } @@ -33,16 +36,16 @@ cumulus-primitives-core = { path = "../../../../primitives/core", default-featur substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-primitives-core/std", "frame-support/std", "log/std", "pallet-asset-conversion/std", - "pallet-asset-tx-payment/std", "pallet-xcm/std", "parachains-common/std", + "scale-info/std", "sp-api/std", "sp-runtime/std", "sp-std/std", @@ -52,9 +55,9 @@ std = [ ] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", - "pallet-asset-tx-payment/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", 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..44bda1eb3709c74d808e696d5d3728a3354b747a --- /dev/null +++ b/cumulus/parachains/runtimes/assets/common/src/benchmarks.rs @@ -0,0 +1,43 @@ +// 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, L)>, +); +impl, SelfParaId: Get, PalletId: Get, L: TryFrom> + pallet_asset_conversion::BenchmarkHelper for AssetPairFactory +{ + fn create_pair(seed1: u32, seed2: u32) -> (L, L) { + let with_id = Location::new( + 1, + [ + Parachain(SelfParaId::get().into()), + PalletInstance(PalletId::get() as u8), + GeneralIndex(seed2.into()), + ], + ); + if seed1 % 2 == 0 { + (with_id.try_into().map_err(|_| "Something went wrong").unwrap(), Target::get()) + } else { + (Target::get(), with_id.try_into().map_err(|_| "Something went wrong").unwrap()) + } + } +} diff --git a/cumulus/parachains/runtimes/assets/common/src/foreign_creators.rs b/cumulus/parachains/runtimes/assets/common/src/foreign_creators.rs index 1ed7bd0538c287bba5255c41145a1c3fa3a10064..a9fd79bf939f575ac60007cd6920ac6db3ded773 100644 --- a/cumulus/parachains/runtimes/assets/common/src/foreign_creators.rs +++ b/cumulus/parachains/runtimes/assets/common/src/foreign_creators.rs @@ -17,21 +17,21 @@ use frame_support::traits::{ ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, OriginTrait, }; use pallet_xcm::{EnsureXcm, Origin as XcmOrigin}; -use xcm::latest::MultiLocation; +use xcm::latest::Location; use xcm_executor::traits::ConvertLocation; /// `EnsureOriginWithArg` impl for `CreateOrigin` that allows only XCM origins that are locations /// containing the class location. -pub struct ForeignCreators( - sp_std::marker::PhantomData<(IsForeign, AccountOf, AccountId)>, +pub struct ForeignCreators( + sp_std::marker::PhantomData<(IsForeign, AccountOf, AccountId, L)>, ); impl< - IsForeign: ContainsPair, + IsForeign: ContainsPair, AccountOf: ConvertLocation, AccountId: Clone, RuntimeOrigin: From + OriginTrait + Clone, - > EnsureOriginWithArg - for ForeignCreators + L: TryFrom + TryInto + Clone, + > EnsureOriginWithArg for ForeignCreators where RuntimeOrigin::PalletsOrigin: From + TryInto, @@ -40,17 +40,20 @@ where fn try_origin( origin: RuntimeOrigin, - asset_location: &MultiLocation, + asset_location: &L, ) -> sp_std::result::Result { - let origin_location = EnsureXcm::::try_origin(origin.clone())?; + let origin_location = EnsureXcm::::try_origin(origin.clone())?; if !IsForeign::contains(asset_location, &origin_location) { return Err(origin) } - AccountOf::convert_location(&origin_location).ok_or(origin) + let latest_location: Location = + origin_location.clone().try_into().map_err(|_| origin.clone())?; + AccountOf::convert_location(&latest_location).ok_or(origin) } #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin(a: &MultiLocation) -> Result { - Ok(pallet_xcm::Origin::Xcm(*a).into()) + fn try_successful_origin(a: &L) -> Result { + let latest_location: Location = (*a).clone().try_into().map_err(|_| ())?; + Ok(pallet_xcm::Origin::Xcm(latest_location).into()) } } diff --git a/cumulus/parachains/runtimes/assets/common/src/fungible_conversion.rs b/cumulus/parachains/runtimes/assets/common/src/fungible_conversion.rs index 80f8a971d21781004e264600c67a62f9a18c2ce4..e21203485a764c350b3d9890d7dcdaa89110babf 100644 --- a/cumulus/parachains/runtimes/assets/common/src/fungible_conversion.rs +++ b/cumulus/parachains/runtimes/assets/common/src/fungible_conversion.rs @@ -19,52 +19,48 @@ use crate::runtime_api::FungiblesAccessError; use frame_support::traits::Contains; use sp_runtime::traits::MaybeEquivalence; use sp_std::{borrow::Borrow, vec::Vec}; -use xcm::latest::{MultiAsset, MultiLocation}; +use xcm::latest::{Asset, Location}; use xcm_builder::{ConvertedConcreteId, MatchedConvertedConcreteId}; use xcm_executor::traits::MatchesFungibles; -/// Converting any [`(AssetId, Balance)`] to [`MultiAsset`] -pub trait MultiAssetConverter: +/// Converting any [`(AssetId, Balance)`] to [`Asset`] +pub trait AssetConverter: MatchesFungibles where AssetId: Clone, Balance: Clone, - ConvertAssetId: MaybeEquivalence, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, { - fn convert_ref( - value: impl Borrow<(AssetId, Balance)>, - ) -> Result; + fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result; } -/// Checks for `MultiLocation`. -pub trait MatchesMultiLocation: +/// Checks for `Location`. +pub trait MatchesLocation: MatchesFungibles where AssetId: Clone, Balance: Clone, - MatchAssetId: Contains, - ConvertAssetId: MaybeEquivalence, + MatchAssetId: Contains, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, { - fn contains(location: &MultiLocation) -> bool; + fn contains(location: &Location) -> bool; } impl< AssetId: Clone, Balance: Clone, - ConvertAssetId: MaybeEquivalence, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, - > MultiAssetConverter + > AssetConverter for ConvertedConcreteId { - fn convert_ref( - value: impl Borrow<(AssetId, Balance)>, - ) -> Result { + fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result { let (asset_id, balance) = value.borrow(); match ConvertAssetId::convert_back(asset_id) { - Some(asset_id_as_multilocation) => match ConvertBalance::convert_back(balance) { - Some(amount) => Ok((asset_id_as_multilocation, amount).into()), + Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) { + Some(amount) => Ok((asset_id_as_location, amount).into()), None => Err(FungiblesAccessError::AmountToBalanceConversionFailed), }, None => Err(FungiblesAccessError::AssetIdConversionFailed), @@ -75,19 +71,17 @@ impl< impl< AssetId: Clone, Balance: Clone, - MatchAssetId: Contains, - ConvertAssetId: MaybeEquivalence, + MatchAssetId: Contains, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, - > MultiAssetConverter + > AssetConverter for MatchedConvertedConcreteId { - fn convert_ref( - value: impl Borrow<(AssetId, Balance)>, - ) -> Result { + fn convert_ref(value: impl Borrow<(AssetId, Balance)>) -> Result { let (asset_id, balance) = value.borrow(); match ConvertAssetId::convert_back(asset_id) { - Some(asset_id_as_multilocation) => match ConvertBalance::convert_back(balance) { - Some(amount) => Ok((asset_id_as_multilocation, amount).into()), + Some(asset_id_as_location) => match ConvertBalance::convert_back(balance) { + Some(amount) => Ok((asset_id_as_location, amount).into()), None => Err(FungiblesAccessError::AmountToBalanceConversionFailed), }, None => Err(FungiblesAccessError::AssetIdConversionFailed), @@ -98,13 +92,13 @@ impl< impl< AssetId: Clone, Balance: Clone, - MatchAssetId: Contains, - ConvertAssetId: MaybeEquivalence, + MatchAssetId: Contains, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, - > MatchesMultiLocation + > MatchesLocation for MatchedConvertedConcreteId { - fn contains(location: &MultiLocation) -> bool { + fn contains(location: &Location) -> bool { MatchAssetId::contains(location) } } @@ -113,12 +107,12 @@ impl< impl< AssetId: Clone, Balance: Clone, - MatchAssetId: Contains, - ConvertAssetId: MaybeEquivalence, + MatchAssetId: Contains, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, - > MatchesMultiLocation for Tuple + > MatchesLocation for Tuple { - fn contains(location: &MultiLocation) -> bool { + fn contains(location: &Location) -> bool { for_tuples!( #( match Tuple::contains(location) { o @ true => return o, _ => () } )* ); @@ -127,27 +121,24 @@ impl< } } -/// Helper function to convert collections with [`(AssetId, Balance)`] to [`MultiAsset`] +/// Helper function to convert collections with [`(AssetId, Balance)`] to [`Asset`] pub fn convert<'a, AssetId, Balance, ConvertAssetId, ConvertBalance, Converter>( items: impl Iterator, -) -> Result, FungiblesAccessError> +) -> Result, FungiblesAccessError> where AssetId: Clone + 'a, Balance: Clone + 'a, - ConvertAssetId: MaybeEquivalence, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, - Converter: MultiAssetConverter, + Converter: AssetConverter, { items.map(Converter::convert_ref).collect() } -/// Helper function to convert `Balance` with MultiLocation` to `MultiAsset` -pub fn convert_balance< - T: frame_support::pallet_prelude::Get, - Balance: TryInto, ->( +/// Helper function to convert `Balance` with Location` to `Asset` +pub fn convert_balance, Balance: TryInto>( balance: Balance, -) -> Result { +) -> Result { match balance.try_into() { Ok(balance) => Ok((T::get(), balance).into()), Err(_) => Err(FungiblesAccessError::AmountToBalanceConversionFailed), @@ -162,20 +153,20 @@ mod tests { use xcm::latest::prelude::*; use xcm_executor::traits::{Identity, JustTry}; - type Converter = MatchedConvertedConcreteId; + type Converter = MatchedConvertedConcreteId; #[test] fn converted_concrete_id_fungible_multi_asset_conversion_roundtrip_works() { - let location = MultiLocation::new(0, X1(GlobalConsensus(ByGenesis([0; 32])))); + let location = Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]); let amount = 123456_u64; - let expected_multi_asset = MultiAsset { - id: Concrete(MultiLocation::new(0, X1(GlobalConsensus(ByGenesis([0; 32]))))), + let expected_multi_asset = Asset { + id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])), fun: Fungible(123456_u128), }; assert_eq!( Converter::matches_fungibles(&expected_multi_asset).map_err(|_| ()), - Ok((location, amount)) + Ok((location.clone(), amount)) ); assert_eq!(Converter::convert_ref((location, amount)), Ok(expected_multi_asset)); @@ -184,17 +175,17 @@ mod tests { #[test] fn converted_concrete_id_fungible_multi_asset_conversion_collection_works() { let data = vec![ - (MultiLocation::new(0, X1(GlobalConsensus(ByGenesis([0; 32])))), 123456_u64), - (MultiLocation::new(1, X1(GlobalConsensus(ByGenesis([1; 32])))), 654321_u64), + (Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))]), 123456_u64), + (Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))]), 654321_u64), ]; let expected_data = vec![ - MultiAsset { - id: Concrete(MultiLocation::new(0, X1(GlobalConsensus(ByGenesis([0; 32]))))), + Asset { + id: AssetId(Location::new(0, [GlobalConsensus(ByGenesis([0; 32]))])), fun: Fungible(123456_u128), }, - MultiAsset { - id: Concrete(MultiLocation::new(1, X1(GlobalConsensus(ByGenesis([1; 32]))))), + Asset { + id: AssetId(Location::new(1, [GlobalConsensus(ByGenesis([1; 32]))])), fun: Fungible(654321_u128), }, ]; diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index f45c3289aab49c16e2b435671d3625f78692f07d..fa2752179eb6fd238eb8596d8e3ebddf947680d3 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -15,72 +15,104 @@ #![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; pub mod matching; pub mod runtime_api; -use crate::matching::{LocalMultiLocationPattern, ParentLocation}; +use crate::matching::{LocalLocationPattern, ParentLocation}; use frame_support::traits::{Equals, EverythingBut}; -use parachains_common::AssetIdForTrustBackedAssets; -use xcm::prelude::MultiLocation; -use xcm_builder::{AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith}; -use xcm_executor::traits::{Identity, JustTry}; +use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; +use xcm_builder::{ + AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, V4V3LocationConverter, +}; +use xcm_executor::traits::JustTry; -/// `MultiLocation` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` +/// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets` pub type AssetIdForTrustBackedAssetsConvert = + AsPrefixedGeneralIndex< + TrustBackedAssetsPalletLocation, + AssetIdForTrustBackedAssets, + JustTry, + xcm::v3::Location, + >; + +pub type AssetIdForTrustBackedAssetsConvertLatest = AsPrefixedGeneralIndex; +/// `Location` vs `CollectionId` converter for `Uniques` +pub type CollectionIdForUniquesConvert = + AsPrefixedGeneralIndex; + /// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets` pub type TrustBackedAssetsConvertedConcreteId = MatchedConvertedConcreteId< AssetIdForTrustBackedAssets, Balance, StartsWith, - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvertLatest, JustTry, >; -/// AssetId used for identifying assets by MultiLocation. -pub type MultiLocationForAssetId = MultiLocation; +/// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques` +pub type UniquesConvertedConcreteId = MatchedConvertedConcreteId< + CollectionId, + ItemId, + // The asset starts with the uniques pallet. The `CollectionId` of the asset is specified as a + // junction within the pallet itself. + StartsWith, + CollectionIdForUniquesConvert, + JustTry, +>; + +/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `Location`. +pub type LocationConvertedConcreteId = MatchedConvertedConcreteId< + xcm::v3::Location, + Balance, + LocationFilter, + V4V3LocationConverter, + JustTry, +>; -/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `MultiLocation`. -pub type MultiLocationConvertedConcreteId = +/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets` +pub type TrustBackedAssetsAsLocation = MatchedConvertedConcreteId< - MultiLocationForAssetId, + xcm::v3::Location, Balance, - MultiLocationFilter, - Identity, + StartsWith, + V4V3LocationConverter, JustTry, >; /// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as -/// `MultiLocation`. +/// `Location`. /// /// Excludes by default: /// - parent as relay chain -/// - all local MultiLocations +/// - all local Locations /// -/// `AdditionalMultiLocationExclusionFilter` can customize additional excluded MultiLocations -pub type ForeignAssetsConvertedConcreteId = - MultiLocationConvertedConcreteId< +/// `AdditionalLocationExclusionFilter` can customize additional excluded Locations +pub type ForeignAssetsConvertedConcreteId = + LocationConvertedConcreteId< EverythingBut<( // Excludes relay/parent chain currency Equals, // Here we rely on fact that something like this works: - // assert!(MultiLocation::new(1, - // X1(Parachain(100))).starts_with(&MultiLocation::parent())); - // assert!(X1(Parachain(100)).starts_with(&Here)); - StartsWith, + // assert!(Location::new(1, + // [Parachain(100)]).starts_with(&Location::parent())); + // assert!([Parachain(100)].into().starts_with(&Here)); + StartsWith, // Here we can exclude more stuff or leave it as `()` - AdditionalMultiLocationExclusionFilter, + AdditionalLocationExclusionFilter, )>, Balance, >; type AssetIdForPoolAssets = u32; -/// `MultiLocation` vs `AssetIdForPoolAssets` converter for `PoolAssets`. +/// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. pub type AssetIdForPoolAssetsConvert = AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` @@ -97,28 +129,28 @@ pub type PoolAssetsConvertedConcreteId = mod tests { use super::*; use sp_runtime::traits::MaybeEquivalence; - use xcm::latest::prelude::*; + use xcm::prelude::*; use xcm_builder::StartsWithExplicitGlobalConsensus; use xcm_executor::traits::{Error as MatchError, MatchesFungibles}; #[test] fn asset_id_for_trust_backed_assets_convert_works() { frame_support::parameter_types! { - pub TrustBackedAssetsPalletLocation: MultiLocation = MultiLocation::new(5, X1(PalletInstance(13))); + pub TrustBackedAssetsPalletLocation: Location = Location::new(5, [PalletInstance(13)]); } let local_asset_id = 123456789 as AssetIdForTrustBackedAssets; let expected_reverse_ref = - MultiLocation::new(5, X2(PalletInstance(13), GeneralIndex(local_asset_id.into()))); + Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]); assert_eq!( - AssetIdForTrustBackedAssetsConvert::::convert_back( + AssetIdForTrustBackedAssetsConvertLatest::::convert_back( &local_asset_id ) .unwrap(), expected_reverse_ref ); assert_eq!( - AssetIdForTrustBackedAssetsConvert::::convert( + AssetIdForTrustBackedAssetsConvertLatest::::convert( &expected_reverse_ref ) .unwrap(), @@ -129,7 +161,7 @@ mod tests { #[test] fn trust_backed_assets_match_fungibles_works() { frame_support::parameter_types! { - pub TrustBackedAssetsPalletLocation: MultiLocation = MultiLocation::new(0, X1(PalletInstance(13))); + pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]); } // setup convert type TrustBackedAssetsConvert = @@ -137,85 +169,86 @@ mod tests { let test_data = vec![ // missing GeneralIndex - (ma_1000(0, X1(PalletInstance(13))), Err(MatchError::AssetIdConversionFailed)), + (ma_1000(0, [PalletInstance(13)].into()), Err(MatchError::AssetIdConversionFailed)), ( - ma_1000(0, X2(PalletInstance(13), GeneralKey { data: [0; 32], length: 32 })), + ma_1000(0, [PalletInstance(13), GeneralKey { data: [0; 32], length: 32 }].into()), Err(MatchError::AssetIdConversionFailed), ), ( - ma_1000(0, X2(PalletInstance(13), Parachain(1000))), + ma_1000(0, [PalletInstance(13), Parachain(1000)].into()), Err(MatchError::AssetIdConversionFailed), ), // OK - (ma_1000(0, X2(PalletInstance(13), GeneralIndex(1234))), Ok((1234, 1000))), + (ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Ok((1234, 1000))), ( - ma_1000(0, X3(PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222))), + ma_1000(0, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Ok((1234, 1000)), ), ( ma_1000( 0, - X4( + [ PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222), GeneralKey { data: [0; 32], length: 32 }, - ), + ] + .into(), ), Ok((1234, 1000)), ), // wrong pallet instance ( - ma_1000(0, X2(PalletInstance(77), GeneralIndex(1234))), + ma_1000(0, [PalletInstance(77), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(0, X3(PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222))), + ma_1000(0, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // wrong parent ( - ma_1000(1, X2(PalletInstance(13), GeneralIndex(1234))), + ma_1000(1, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(1, X3(PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222))), + ma_1000(1, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(1, X2(PalletInstance(77), GeneralIndex(1234))), + ma_1000(1, [PalletInstance(77), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(1, X3(PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222))), + ma_1000(1, [PalletInstance(77), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // wrong parent ( - ma_1000(2, X2(PalletInstance(13), GeneralIndex(1234))), + ma_1000(2, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(2, X3(PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222))), + ma_1000(2, [PalletInstance(13), GeneralIndex(1234), GeneralIndex(2222)].into()), Err(MatchError::AssetNotHandled), ), // missing GeneralIndex - (ma_1000(0, X1(PalletInstance(77))), Err(MatchError::AssetNotHandled)), - (ma_1000(1, X1(PalletInstance(13))), Err(MatchError::AssetNotHandled)), - (ma_1000(2, X1(PalletInstance(13))), Err(MatchError::AssetNotHandled)), + (ma_1000(0, [PalletInstance(77)].into()), Err(MatchError::AssetNotHandled)), + (ma_1000(1, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)), + (ma_1000(2, [PalletInstance(13)].into()), Err(MatchError::AssetNotHandled)), ]; - for (multi_asset, expected_result) in test_data { + for (asset, expected_result) in test_data { assert_eq!( - >::matches_fungibles(&multi_asset), - expected_result, "multi_asset: {:?}", multi_asset); + >::matches_fungibles(&asset.clone().try_into().unwrap()), + expected_result, "asset: {:?}", asset); } } #[test] - fn multi_location_converted_concrete_id_converter_works() { + fn location_converted_concrete_id_converter_works() { frame_support::parameter_types! { - pub Parachain100Pattern: MultiLocation = MultiLocation::new(1, X1(Parachain(100))); + pub Parachain100Pattern: Location = Location::new(1, [Parachain(100)]); pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]); } @@ -231,95 +264,125 @@ mod tests { let test_data = vec![ // excluded as local (ma_1000(0, Here), Err(MatchError::AssetNotHandled)), - (ma_1000(0, X1(Parachain(100))), Err(MatchError::AssetNotHandled)), + (ma_1000(0, [Parachain(100)].into()), Err(MatchError::AssetNotHandled)), ( - ma_1000(0, X2(PalletInstance(13), GeneralIndex(1234))), + ma_1000(0, [PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), // excluded as parent (ma_1000(1, Here), Err(MatchError::AssetNotHandled)), // excluded as additional filter - Parachain100Pattern - (ma_1000(1, X1(Parachain(100))), Err(MatchError::AssetNotHandled)), - (ma_1000(1, X2(Parachain(100), GeneralIndex(1234))), Err(MatchError::AssetNotHandled)), + (ma_1000(1, [Parachain(100)].into()), Err(MatchError::AssetNotHandled)), + ( + ma_1000(1, [Parachain(100), GeneralIndex(1234)].into()), + Err(MatchError::AssetNotHandled), + ), ( - ma_1000(1, X3(Parachain(100), PalletInstance(13), GeneralIndex(1234))), + ma_1000(1, [Parachain(100), PalletInstance(13), GeneralIndex(1234)].into()), Err(MatchError::AssetNotHandled), ), // excluded as additional filter - StartsWithExplicitGlobalConsensus ( - ma_1000(1, X1(GlobalConsensus(NetworkId::ByGenesis([9; 32])))), + ma_1000(1, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()), Err(MatchError::AssetNotHandled), ), ( - ma_1000(2, X1(GlobalConsensus(NetworkId::ByGenesis([9; 32])))), + ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([9; 32]))].into()), Err(MatchError::AssetNotHandled), ), ( ma_1000( 2, - X3( + [ GlobalConsensus(NetworkId::ByGenesis([9; 32])), Parachain(200), GeneralIndex(1234), - ), + ] + .into(), ), Err(MatchError::AssetNotHandled), ), // ok - (ma_1000(1, X1(Parachain(200))), Ok((MultiLocation::new(1, X1(Parachain(200))), 1000))), - (ma_1000(2, X1(Parachain(200))), Ok((MultiLocation::new(2, X1(Parachain(200))), 1000))), ( - ma_1000(1, X2(Parachain(200), GeneralIndex(1234))), - Ok((MultiLocation::new(1, X2(Parachain(200), GeneralIndex(1234))), 1000)), + ma_1000(1, [Parachain(200)].into()), + Ok((xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(200)]), 1000)), ), ( - ma_1000(2, X2(Parachain(200), GeneralIndex(1234))), - Ok((MultiLocation::new(2, X2(Parachain(200), GeneralIndex(1234))), 1000)), + ma_1000(2, [Parachain(200)].into()), + Ok((xcm::v3::Location::new(2, [xcm::v3::Junction::Parachain(200)]), 1000)), ), ( - ma_1000(2, X1(GlobalConsensus(NetworkId::ByGenesis([7; 32])))), + ma_1000(1, [Parachain(200), GeneralIndex(1234)].into()), Ok(( - MultiLocation::new(2, X1(GlobalConsensus(NetworkId::ByGenesis([7; 32])))), + xcm::v3::Location::new( + 1, + [xcm::v3::Junction::Parachain(200), xcm::v3::Junction::GeneralIndex(1234)], + ), + 1000, + )), + ), + ( + ma_1000(2, [Parachain(200), GeneralIndex(1234)].into()), + Ok(( + xcm::v3::Location::new( + 2, + [xcm::v3::Junction::Parachain(200), xcm::v3::Junction::GeneralIndex(1234)], + ), + 1000, + )), + ), + ( + ma_1000(2, [GlobalConsensus(NetworkId::ByGenesis([7; 32]))].into()), + Ok(( + xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::ByGenesis( + [7; 32], + ))], + ), 1000, )), ), ( ma_1000( 2, - X3( + [ GlobalConsensus(NetworkId::ByGenesis([7; 32])), Parachain(200), GeneralIndex(1234), - ), + ] + .into(), ), Ok(( - MultiLocation::new( + xcm::v3::Location::new( 2, - X3( - GlobalConsensus(NetworkId::ByGenesis([7; 32])), - Parachain(200), - GeneralIndex(1234), - ), + [ + xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::ByGenesis( + [7; 32], + )), + xcm::v3::Junction::Parachain(200), + xcm::v3::Junction::GeneralIndex(1234), + ], ), 1000, )), ), ]; - for (multi_asset, expected_result) in test_data { + for (asset, expected_result) in test_data { assert_eq!( - >::matches_fungibles( - &multi_asset + >::matches_fungibles( + &asset.clone().try_into().unwrap() ), expected_result, - "multi_asset: {:?}", - multi_asset + "asset: {:?}", + asset ); } } - // Create MultiAsset - fn ma_1000(parents: u8, interior: Junctions) -> MultiAsset { - (MultiLocation::new(parents, interior), 1000).into() + // Create Asset + fn ma_1000(parents: u8, interior: Junctions) -> Asset { + (Location::new(parents, interior), 1000).into() } } 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..58f5d2d57a7669b73876897f90e0da3e20730cbb 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,458 +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 xcm::latest::MultiLocation; +use sp_std::marker::PhantomData; +use xcm::latest::Location; -pub struct MultiLocationConverter, MultiLocationMatcher> { - _phantom: PhantomData<(NativeAssetLocation, MultiLocationMatcher)>, -} - -impl - MultiAssetIdConverter, MultiLocation> - for MultiLocationConverter -where - NativeAssetLocation: Get, - MultiLocationMatcher: Contains, -{ - 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()) - } - } -} - -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) - } +/// Converts a given [`Location`] 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<(Target, L)>); +impl, L: PartialEq + Eq> Convert> for TargetFromLeft { + fn convert(l: L) -> Either<(), L> { + Target::get().eq(&l).then(|| Left(())).map_or(Right(l), |n| n) } } -impl Mutate - for LocalAndForeignAssets +/// Converts a given [`Location`] 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, L)>, +); +impl Convert> + for LocalFromLeft where - Assets: Mutate - + Inspect - + Balanced - + PalletInfoAccess, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: Mutate - + Inspect - + Balanced, + Equivalence: MaybeEquivalence, { - /// 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) + fn convert(l: L) -> Either { + match Equivalence::convert(&l) { + Some(id) => Left(id), + None => Right(l), } } } - -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..478bba4565dc1a6d8a45d47b1569b406596b6be7 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -15,62 +15,96 @@ use cumulus_primitives_core::ParaId; use frame_support::{pallet_prelude::Get, traits::ContainsPair}; -use xcm::{ - latest::prelude::{MultiAsset, MultiLocation}, - prelude::*, -}; +use xcm::prelude::*; + use xcm_builder::ensure_is_remote; frame_support::parameter_types! { - pub LocalMultiLocationPattern: MultiLocation = MultiLocation::new(0, Here); - pub ParentLocation: MultiLocation = MultiLocation::parent(); + pub LocalLocationPattern: Location = Location::new(0, Here); + pub ParentLocation: Location = Location::parent(); } /// Accepts an asset if it is from the origin. pub struct IsForeignConcreteAsset(sp_std::marker::PhantomData); -impl> ContainsPair +impl> ContainsPair for IsForeignConcreteAsset { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + fn contains(asset: &Asset, origin: &Location) -> bool { log::trace!(target: "xcm::contains", "IsForeignConcreteAsset asset: {:?}, origin: {:?}", asset, origin); - matches!(asset.id, Concrete(ref id) if IsForeign::contains(id, origin)) + matches!(asset.id, AssetId(ref id) if IsForeign::contains(id, origin)) } } -/// Checks if `a` is from sibling location `b`. Checks that `MultiLocation-a` starts with -/// `MultiLocation-b`, and that the `ParaId` of `b` is not equal to `a`. -pub struct FromSiblingParachain(sp_std::marker::PhantomData); -impl> ContainsPair - for FromSiblingParachain +/// Checks if `a` is from sibling location `b`. Checks that `Location-a` starts with +/// `Location-b`, and that the `ParaId` of `b` is not equal to `a`. +pub struct FromSiblingParachain( + sp_std::marker::PhantomData<(SelfParaId, L)>, +); +impl, L: TryFrom + TryInto + Clone> ContainsPair + for FromSiblingParachain { - fn contains(&a: &MultiLocation, b: &MultiLocation) -> bool { - // `a` needs to be from `b` at least - if !a.starts_with(b) { - return false - } + fn contains(a: &L, b: &L) -> bool { + // We convert locations to latest + let a = match ((*a).clone().try_into(), (*b).clone().try_into()) { + (Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least + _ => return false, + }; // here we check if sibling - match a { - MultiLocation { parents: 1, interior } => + match a.unpack() { + (1, interior) => matches!(interior.first(), Some(Parachain(sibling_para_id)) if sibling_para_id.ne(&u32::from(SelfParaId::get()))), _ => false, } } } -/// Adapter verifies if it is allowed to receive `MultiAsset` from `MultiLocation`. +/// Checks if `a` is from the expected global consensus network. Checks that `Location-a` +/// starts with `Location-b`, and that network is a foreign consensus system. +pub struct FromNetwork( + sp_std::marker::PhantomData<(UniversalLocation, ExpectedNetworkId, L)>, +); +impl< + UniversalLocation: Get, + ExpectedNetworkId: Get, + L: TryFrom + TryInto + Clone, + > ContainsPair for FromNetwork +{ + fn contains(a: &L, b: &L) -> bool { + // We convert locations to latest + let a = match ((*a).clone().try_into(), (*b).clone().try_into()) { + (Ok(a), Ok(b)) if a.starts_with(&b) => a, // `a` needs to be from `b` at least + _ => return false, + }; + + let universal_source = UniversalLocation::get(); + + // ensure that `a` is remote and from the expected network + match ensure_is_remote(universal_source.clone(), a.clone()) { + 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 `Asset` from `Location`. /// -/// Note: `MultiLocation` has to be from a different global consensus. +/// Note: `Location` has to be from a different global consensus. pub struct IsTrustedBridgedReserveLocationForConcreteAsset( sp_std::marker::PhantomData<(UniversalLocation, Reserves)>, ); -impl< - UniversalLocation: Get, - Reserves: ContainsPair, - > ContainsPair +impl, Reserves: ContainsPair> + ContainsPair for IsTrustedBridgedReserveLocationForConcreteAsset { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + fn contains(asset: &Asset, origin: &Location) -> bool { let universal_source = UniversalLocation::get(); log::trace!( target: "xcm::contains", @@ -79,7 +113,7 @@ impl< ); // check remote origin - let _ = match ensure_is_remote(universal_source, *origin) { + let _ = match ensure_is_remote(universal_source.clone(), origin.clone()) { Ok(devolved) => devolved, Err(_) => { log::trace!( @@ -95,3 +129,87 @@ impl< Reserves::contains(asset, origin) } } + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::parameter_types; + + parameter_types! { + pub UniversalLocation: InteriorLocation = [GlobalConsensus(Rococo), Parachain(1000)].into(); + pub ExpectedNetworkId: NetworkId = Wococo; + } + + #[test] + fn from_network_contains_works() { + // asset and origin from foreign consensus works + let asset: Location = ( + Parent, + Parent, + GlobalConsensus(Wococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: Location = (Parent, Parent, GlobalConsensus(Wococo), Parachain(1000)).into(); + assert!(FromNetwork::::contains(&asset, &origin)); + + // asset and origin from local consensus fails + let asset: Location = ( + Parent, + Parent, + GlobalConsensus(Rococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: Location = (Parent, Parent, GlobalConsensus(Rococo), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset and origin from here fails + let asset: Location = (PalletInstance(1), GeneralIndex(1)).into(); + let origin: Location = Here.into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset from different consensus fails + let asset: Location = ( + Parent, + Parent, + GlobalConsensus(Polkadot), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: Location = (Parent, Parent, GlobalConsensus(Wococo), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // origin from different consensus fails + let asset: Location = ( + Parent, + Parent, + GlobalConsensus(Wococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset and origin from unexpected consensus fails + let asset: Location = ( + Parent, + Parent, + GlobalConsensus(Polkadot), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: Location = (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + } +} diff --git a/cumulus/parachains/runtimes/assets/common/src/runtime_api.rs b/cumulus/parachains/runtimes/assets/common/src/runtime_api.rs index 0a166a048193998fe15b1f35f3e667a76d71540b..19977cbedab07b638f71f32470d3768cabeba322 100644 --- a/cumulus/parachains/runtimes/assets/common/src/runtime_api.rs +++ b/cumulus/parachains/runtimes/assets/common/src/runtime_api.rs @@ -18,12 +18,12 @@ use codec::{Codec, Decode, Encode}; use sp_runtime::RuntimeDebug; #[cfg(feature = "std")] -use {sp_std::vec::Vec, xcm::latest::MultiAsset}; +use {sp_std::vec::Vec, xcm::latest::Asset}; /// The possible errors that can happen querying the storage of assets. #[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] pub enum FungiblesAccessError { - /// `MultiLocation` to `AssetId`/`ClassId` conversion failed. + /// `Location` to `AssetId`/`ClassId` conversion failed. AssetIdConversionFailed, /// `u128` amount to currency `Balance` conversion failed. AmountToBalanceConversionFailed, @@ -36,11 +36,11 @@ sp_api::decl_runtime_apis! { where AccountId: Codec, { - /// Returns the list of all [`MultiAsset`] that an `AccountId` has. + /// Returns the list of all [`Asset`] that an `AccountId` has. #[changed_in(2)] - fn query_account_balances(account: AccountId) -> Result, FungiblesAccessError>; + fn query_account_balances(account: AccountId) -> Result, FungiblesAccessError>; - /// Returns the list of all [`MultiAsset`] that an `AccountId` has. - fn query_account_balances(account: AccountId) -> Result; + /// Returns the list of all [`Asset`] that an `AccountId` has. + fn query_account_balances(account: AccountId) -> Result; } } diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index d8b5ca5c8e596c4dcfabc35e1491097a41031acf..883c93c97b4de6774e86ee83b84d246dc1427f7f 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -1,36 +1,35 @@ [package] name = "asset-test-utils" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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"] } # Substrate -frame-support = { path = "../../../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../../../substrate/frame/system", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false} -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false} -sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", default-features = false} -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false} -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false} +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } +pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } +sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } # Cumulus -cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook",] } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } -cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } parachains-common = { path = "../../../common", default-features = false } -assets-common = { path = "../common", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } -cumulus-primitives-parachain-inherent = { path = "../../../../primitives/parachain-inherent", default-features = false } -cumulus-test-relay-sproof-builder = { path = "../../../../test/relay-sproof-builder", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-runtimes-test-utils = { path = "../../test-utils", default-features = false } # Polkadot @@ -38,7 +37,6 @@ xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-f xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } # Bridges pallet-xcm-bridge-hub-router = { path = "../../../../../bridges/modules/xcm-bridge-hub-router", default-features = false } @@ -50,29 +48,24 @@ hex-literal = "0.4.1" substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ - "assets-common/std", - "cumulus-pallet-dmp-queue/std", + "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-core/std", - "cumulus-primitives-parachain-inherent/std", - "cumulus-test-relay-sproof-builder/std", "frame-support/std", "frame-system/std", "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm-bridge-hub-router/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", "parachains-runtimes-test-utils/std", - "polkadot-parachain-primitives/std", - "sp-consensus-aura/std", - "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs index e0f05fa7b0a4c3bb7ec5bdcf7b26201efbb8e0ee..877d61780ce71c3d82361ccbfa03f2e3dde67d3c 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/lib.rs @@ -19,4 +19,63 @@ pub mod test_cases; pub mod test_cases_over_bridge; pub mod xcm_helpers; + +use frame_support::traits::ProcessMessageError; pub use parachains_runtimes_test_utils::*; +use std::fmt::Debug; + +use xcm::latest::prelude::*; +use xcm_builder::{CreateMatcher, MatchXcm}; + +/// Given a message, a sender, and a destination, it returns the delivery fees +fn get_fungible_delivery_fees(destination: Location, message: Xcm<()>) -> u128 { + let Ok((_, delivery_fees)) = validate_send::(destination, message) else { + unreachable!("message can be sent; qed") + }; + if let Some(delivery_fee) = delivery_fees.inner().first() { + let Fungible(delivery_fee_amount) = delivery_fee.fun else { + unreachable!("asset is fungible; qed"); + }; + delivery_fee_amount + } else { + 0 + } +} + +/// Helper function to verify `xcm` contains all relevant instructions expected on destination +/// chain as part of a reserve-asset-transfer. +pub(crate) fn assert_matches_reserve_asset_deposited_instructions( + xcm: &mut Xcm, + expected_reserve_assets_deposited: &Assets, + expected_beneficiary: &Location, +) { + let _ = xcm + .0 + .matcher() + .skip_inst_while(|inst| !matches!(inst, ReserveAssetDeposited(..))) + .expect("no instruction ReserveAssetDeposited?") + .match_next_inst(|instr| match instr { + ReserveAssetDeposited(reserve_assets) => { + assert_eq!(reserve_assets, expected_reserve_assets_deposited); + Ok(()) + }, + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction ReserveAssetDeposited") + .match_next_inst(|instr| match instr { + ClearOrigin => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction ClearOrigin") + .match_next_inst(|instr| match instr { + BuyExecution { .. } => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction BuyExecution") + .match_next_inst(|instr| match instr { + DepositAsset { assets: _, beneficiary } if beneficiary == expected_beneficiary => + Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("expected instruction DepositAsset"); +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index b0616acb1a4d504e94d70f019f9b74031d954744..53e10956bd0d1d233555a8e4df1ae23fe351c678 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -16,25 +16,28 @@ //! Module contains predefined test-case scenarios for `Runtime` with various assets. use super::xcm_helpers; +use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees}; use codec::Encode; +use cumulus_primitives_core::XcmpMessageSource; use frame_support::{ assert_noop, assert_ok, traits::{ - fungible::Mutate, fungibles::InspectEnumerable, Get, OnFinalize, OnInitialize, OriginTrait, + fungible::Mutate, fungibles::InspectEnumerable, Currency, Get, OnFinalize, OnInitialize, + OriginTrait, }, weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; use parachains_common::{AccountId, Balance}; use parachains_runtimes_test_utils::{ - assert_metadata, assert_total, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, - ValidatorIdOf, XcmReceivedFrom, + assert_metadata, assert_total, mock_open_hrmp_channel, AccountIdOf, BalanceOf, + CollatorSessionKeys, ExtBuilder, SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use sp_runtime::{ traits::{MaybeEquivalence, StaticLookup, Zero}, DispatchError, Saturating, }; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*, VersionedAssets}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; type RuntimeHelper = @@ -43,8 +46,8 @@ type 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 receive native asset from relay chain -/// and can teleport it back and to the other parachains +/// Test-case makes sure that `Runtime` can receive native asset from relay chain and can teleport +/// it back pub fn teleports_for_native_asset_works< Runtime, AllPalletsWithoutSystem, @@ -54,12 +57,10 @@ pub fn teleports_for_native_asset_works< HrmpChannelOpener, >( collator_session_keys: CollatorSessionKeys, + slot_durations: SlotDurations, existential_deposit: BalanceOf, target_account: AccountIdOf, unwrap_pallet_xcm_event: Box) -> Option>>, - unwrap_xcmp_queue_event: Box< - dyn Fn(Vec) -> Option>, - >, runtime_para_id: u32, ) where Runtime: frame_system::Config @@ -69,7 +70,8 @@ pub fn teleports_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -93,6 +95,7 @@ pub fn teleports_for_native_asset_works< .with_session_keys(collator_session_keys.session_keys()) .with_safe_xcm_version(XCM_VERSION) .with_para_id(runtime_para_id.into()) + .with_tracing() .build() .execute_with(|| { let mut alice = [0u8; 32]; @@ -109,49 +112,51 @@ pub fn teleports_for_native_asset_works< 0.into() ); - let native_asset_id = MultiLocation::parent(); + let native_asset_id = Location::parent(); let buy_execution_fee_amount_eta = - WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0)); + WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 1024)); let native_asset_amount_unit = existential_deposit; let native_asset_amount_received = native_asset_amount_unit * 10.into() + buy_execution_fee_amount_eta.into(); // 1. process received teleported assets from relaychain let xcm = Xcm(vec![ - ReceiveTeleportedAsset(MultiAssets::from(vec![MultiAsset { - id: Concrete(native_asset_id), + ReceiveTeleportedAsset(Assets::from(vec![Asset { + id: AssetId(native_asset_id.clone()), fun: Fungible(native_asset_amount_received.into()), }])), ClearOrigin, BuyExecution { - fees: MultiAsset { - id: Concrete(native_asset_id), + fees: Asset { + id: AssetId(native_asset_id.clone()), fun: Fungible(buy_execution_fee_amount_eta), }, - weight_limit: Limited(Weight::from_parts(303531000, 65536)), + weight_limit: Limited(Weight::from_parts(3035310000, 65536)), }, DepositAsset { assets: Wild(AllCounted(1)), - beneficiary: MultiLocation { + beneficiary: Location { parents: 0, - interior: X1(AccountId32 { + interior: [AccountId32 { network: None, id: target_account.clone().into(), - }), + }] + .into(), }, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); - let outcome = XcmExecutor::::execute_xcm( + let outcome = XcmExecutor::::prepare_and_execute( Parent, xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Parent), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); // check Balances after assert_ne!(>::free_balance(&target_account), 0.into()); @@ -162,13 +167,14 @@ pub fn teleports_for_native_asset_works< // 2. try to teleport asset back to the relaychain { - let dest = MultiLocation::parent(); - let dest_beneficiary = MultiLocation::parent() + let dest = Location::parent(); + let mut dest_beneficiary = Location::parent() .appended_with(AccountId32 { network: None, id: sp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); + dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::free_balance(&target_account); @@ -181,11 +187,11 @@ pub fn teleports_for_native_asset_works< // Mint funds into account to ensure it has enough balance to pay delivery fees let delivery_fees = xcm_helpers::transfer_assets_delivery_fees::( - (native_asset_id, native_asset_to_teleport_away.into()).into(), + (native_asset_id.clone(), native_asset_to_teleport_away.into()).into(), 0, Unlimited, - dest_beneficiary, - dest, + dest_beneficiary.clone(), + dest.clone(), ); >::mint_into( &target_account, @@ -197,10 +203,11 @@ pub fn teleports_for_native_asset_works< RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, - (native_asset_id, native_asset_to_teleport_away.into()), + (native_asset_id.clone(), native_asset_to_teleport_away.into()), None, included_head.clone(), &alice, + &slot_durations, )); // check balances @@ -222,65 +229,55 @@ pub fn teleports_for_native_asset_works< ); } - // 3. try to teleport asset away to other parachain (1234) + // 3. try to teleport assets away to other parachain (2345): should not work as we don't + // trust `IsTeleporter` for `(relay-native-asset, para(2345))` pair { - let other_para_id = 1234; - let dest = MultiLocation::new(1, X1(Parachain(other_para_id))); - let dest_beneficiary = MultiLocation::new(1, X1(Parachain(other_para_id))) + let other_para_id = 2345; + let dest = Location::new(1, [Parachain(other_para_id)]); + let mut dest_beneficiary = Location::new(1, [Parachain(other_para_id)]) .appended_with(AccountId32 { network: None, id: sp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); + dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::free_balance(&target_account); + let native_asset_to_teleport_away = native_asset_amount_unit * 3.into(); assert!( native_asset_to_teleport_away < target_account_balance_before_teleport - existential_deposit ); - assert_ok!(RuntimeHelper::::do_teleport_assets::( - RuntimeHelper::::origin_of(target_account.clone()), - dest, - dest_beneficiary, - (native_asset_id, native_asset_to_teleport_away.into()), - Some((runtime_para_id, other_para_id)), - included_head, - &alice, - )); - - let delivery_fees = - xcm_helpers::transfer_assets_delivery_fees::( - (native_asset_id, native_asset_to_teleport_away.into()).into(), - 0, - Unlimited, - dest_beneficiary, + assert_eq!( + RuntimeHelper::::do_teleport_assets::( + RuntimeHelper::::origin_of(target_account.clone()), dest, - ); + dest_beneficiary, + (native_asset_id, native_asset_to_teleport_away.into()), + Some((runtime_para_id, other_para_id)), + included_head, + &alice, + &slot_durations, + ), + Err(DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0,], + message: Some("Filtered",), + },),) + ); // check balances assert_eq!( >::free_balance(&target_account), - target_account_balance_before_teleport - - native_asset_to_teleport_away - - delivery_fees.into() + target_account_balance_before_teleport ); assert_eq!( >::free_balance(&CheckingAccount::get()), 0.into() ); - - // check events - RuntimeHelper::::assert_pallet_xcm_event_outcome( - &unwrap_pallet_xcm_event, - |outcome| { - assert_ok!(outcome.ensure_complete()); - }, - ); - assert!(RuntimeHelper::::xcmp_queue_message_sent(unwrap_xcmp_queue_event) - .is_some()); } }) } @@ -295,9 +292,9 @@ macro_rules! include_teleports_for_native_asset_works( $weight_to_fee:path, $hrmp_channel_opener:path, $collator_session_key:expr, + $slot_durations:expr, $existential_deposit:expr, $unwrap_pallet_xcm_event:expr, - $unwrap_xcmp_queue_event:expr, $runtime_para_id:expr ) => { #[test] @@ -314,18 +311,18 @@ macro_rules! include_teleports_for_native_asset_works( $hrmp_channel_opener >( $collator_session_key, + $slot_durations, $existential_deposit, target_account, $unwrap_pallet_xcm_event, - $unwrap_xcmp_queue_event, $runtime_para_id ) } } ); -/// Test-case makes sure that `Runtime` can receive teleported assets from sibling parachain relay -/// chain +/// Test-case makes sure that `Runtime` can receive teleported assets from sibling parachain, and +/// can teleport it back pub fn teleports_for_foreign_assets_works< Runtime, AllPalletsWithoutSystem, @@ -337,6 +334,7 @@ pub fn teleports_for_foreign_assets_works< ForeignAssetsPalletInstance, >( collator_session_keys: CollatorSessionKeys, + slot_durations: SlotDurations, target_account: AccountIdOf, existential_deposit: BalanceOf, asset_owner: AccountIdOf, @@ -353,7 +351,8 @@ pub fn teleports_for_foreign_assets_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -368,9 +367,9 @@ pub fn teleports_for_foreign_assets_works< ::Balance: From + Into, SovereignAccountOf: ConvertLocation>, >::AssetId: - From + Into, + From + Into, >::AssetIdParameter: - From + Into, + From + Into, >::Balance: From + Into, ::AccountId: @@ -380,25 +379,27 @@ pub fn teleports_for_foreign_assets_works< ::AccountId: From, ForeignAssetsPalletInstance: 'static, { - // foreign parachain with the same consenus currency as asset + // foreign parachain with the same consensus currency as asset let foreign_para_id = 2222; - let foreign_asset_id_multilocation = MultiLocation { + let foreign_asset_id_location = xcm::v3::Location { parents: 1, - interior: X2(Parachain(foreign_para_id), GeneralIndex(1234567)), + interior: [ + xcm::v3::Junction::Parachain(foreign_para_id), + xcm::v3::Junction::GeneralIndex(1234567), + ] + .into(), }; // foreign creator, which can be sibling parachain to match ForeignCreators - let foreign_creator = MultiLocation { parents: 1, interior: X1(Parachain(foreign_para_id)) }; + let foreign_creator = Location { parents: 1, interior: [Parachain(foreign_para_id)].into() }; let foreign_creator_as_account_id = SovereignAccountOf::convert_location(&foreign_creator).expect(""); // we want to buy execution with local relay chain currency let buy_execution_fee_amount = WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0)); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation::parent()), - fun: Fungible(buy_execution_fee_amount), - }; + let buy_execution_fee = + Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) }; let teleported_foreign_asset_amount = 10_000_000_000_000; let runtime_para_id = 1000; @@ -437,14 +438,14 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account ), 0.into() ); assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &CheckingAccount::get() ), 0.into() @@ -453,14 +454,14 @@ pub fn teleports_for_foreign_assets_works< assert_total::< pallet_assets::Pallet, AccountIdOf, - >(foreign_asset_id_multilocation, 0, 0); + >(foreign_asset_id_location, 0, 0); // create foreign asset (0 total issuance) let asset_minimum_asset_balance = 3333333_u128; assert_ok!( >::force_create( RuntimeHelper::::root_origin(), - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), asset_owner.into(), false, asset_minimum_asset_balance.into() @@ -469,49 +470,54 @@ pub fn teleports_for_foreign_assets_works< assert_total::< pallet_assets::Pallet, AccountIdOf, - >(foreign_asset_id_multilocation, 0, 0); + >(foreign_asset_id_location, 0, 0); assert!(teleported_foreign_asset_amount > asset_minimum_asset_balance); - // 1. process received teleported assets from relaychain + let foreign_asset_id_location_latest: Location = + foreign_asset_id_location.try_into().unwrap(); + + // 1. process received teleported assets from sibling parachain (foreign_para_id) let xcm = Xcm(vec![ // BuyExecution with relaychain native token WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { - fees: MultiAsset { - id: Concrete(MultiLocation::parent()), + fees: Asset { + id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount), }, weight_limit: Limited(Weight::from_parts(403531000, 65536)), }, // Process teleported asset - ReceiveTeleportedAsset(MultiAssets::from(vec![MultiAsset { - id: Concrete(foreign_asset_id_multilocation), + ReceiveTeleportedAsset(Assets::from(vec![Asset { + id: AssetId(foreign_asset_id_location_latest.clone()), fun: Fungible(teleported_foreign_asset_amount), }])), DepositAsset { assets: Wild(AllOf { - id: Concrete(foreign_asset_id_multilocation), + id: AssetId(foreign_asset_id_location_latest.clone()), fun: WildFungibility::Fungible, }), - beneficiary: MultiLocation { + beneficiary: Location { parents: 0, - interior: X1(AccountId32 { + interior: [AccountId32 { network: None, id: target_account.clone().into(), - }), + }] + .into(), }, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); - let outcome = XcmExecutor::::execute_xcm( + let outcome = XcmExecutor::::prepare_and_execute( foreign_creator, xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); // checks target_account after assert_eq!( @@ -520,7 +526,7 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account ), teleported_foreign_asset_amount.into() @@ -532,7 +538,7 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &CheckingAccount::get() ), 0.into() @@ -542,24 +548,25 @@ pub fn teleports_for_foreign_assets_works< pallet_assets::Pallet, AccountIdOf, >( - foreign_asset_id_multilocation, + foreign_asset_id_location, teleported_foreign_asset_amount, teleported_foreign_asset_amount, ); // 2. try to teleport asset back to source parachain (foreign_para_id) { - let dest = MultiLocation::new(1, X1(Parachain(foreign_para_id))); - let dest_beneficiary = MultiLocation::new(1, X1(Parachain(foreign_para_id))) + let dest = Location::new(1, [Parachain(foreign_para_id)]); + let mut dest_beneficiary = Location::new(1, [Parachain(foreign_para_id)]) .appended_with(AccountId32 { network: None, id: sp_runtime::AccountId32::new([3; 32]).into(), }) .unwrap(); + dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); let target_account_balance_before_teleport = >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account, ); let asset_to_teleport_away = asset_minimum_asset_balance * 3; @@ -573,11 +580,11 @@ pub fn teleports_for_foreign_assets_works< // Make sure the target account has enough native asset to pay for delivery fees let delivery_fees = xcm_helpers::transfer_assets_delivery_fees::( - (foreign_asset_id_multilocation, asset_to_teleport_away).into(), + (foreign_asset_id_location_latest.clone(), asset_to_teleport_away).into(), 0, Unlimited, - dest_beneficiary, - dest, + dest_beneficiary.clone(), + dest.clone(), ); >::mint_into( &target_account, @@ -589,23 +596,24 @@ pub fn teleports_for_foreign_assets_works< RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, - (foreign_asset_id_multilocation, asset_to_teleport_away), + (foreign_asset_id_location_latest.clone(), asset_to_teleport_away), Some((runtime_para_id, foreign_para_id)), included_head, &alice, + &slot_durations, )); // check balances assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account ), (target_account_balance_before_teleport - asset_to_teleport_away.into()) ); assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &CheckingAccount::get() ), 0.into() @@ -615,7 +623,7 @@ pub fn teleports_for_foreign_assets_works< pallet_assets::Pallet, AccountIdOf, >( - foreign_asset_id_multilocation, + foreign_asset_id_location, teleported_foreign_asset_amount - asset_to_teleport_away, teleported_foreign_asset_amount - asset_to_teleport_away, ); @@ -645,6 +653,7 @@ macro_rules! include_teleports_for_foreign_assets_works( $sovereign_account_of:path, $assets_pallet_instance:path, $collator_session_key:expr, + $slot_durations:expr, $existential_deposit:expr, $unwrap_pallet_xcm_event:expr, $unwrap_xcmp_queue_event:expr @@ -667,6 +676,7 @@ macro_rules! include_teleports_for_foreign_assets_works( $assets_pallet_instance >( $collator_session_key, + $slot_durations, target_account, $existential_deposit, asset_owner, @@ -693,7 +703,8 @@ pub fn asset_transactor_transfer_with_local_consensus_currency_works: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -728,17 +739,19 @@ pub fn asset_transactor_transfer_with_local_consensus_currency_works BOB let _ = RuntimeHelper::::do_transfer( - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { network: None, id: source_account.clone().into() }), + interior: [AccountId32 { network: None, id: source_account.clone().into() }] + .into(), }, - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { network: None, id: target_account.clone().into() }), + interior: [AccountId32 { network: None, id: target_account.clone().into() }] + .into(), }, // local_consensus_currency_asset, e.g.: relaychain token (KSM, DOT, ...) ( - MultiLocation { parents: 1, interior: Here }, + Location { parents: 1, interior: Here }, (BalanceOf::::from(1_u128) * unit).into(), ), ) @@ -790,7 +803,7 @@ macro_rules! include_asset_transactor_transfer_with_local_consensus_currency_wor } ); -///Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain +/// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain /// currency pub fn asset_transactor_transfer_with_pallet_assets_instance_works< Runtime, @@ -816,7 +829,8 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -831,8 +845,8 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< <::Lookup as StaticLookup>::Source: From<::AccountId>, AssetsPalletInstance: 'static, - AssetId: Clone + Copy, - AssetIdConverter: MaybeEquivalence, + AssetId: Clone, + AssetIdConverter: MaybeEquivalence, { ExtBuilder::::default() .with_collators(collator_session_keys.collators()) @@ -847,10 +861,10 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< .execute_with(|| { // create some asset class let asset_minimum_asset_balance = 3333333_u128; - let asset_id_as_multilocation = AssetIdConverter::convert_back(&asset_id).unwrap(); + let asset_id_as_location = AssetIdConverter::convert_back(&asset_id).unwrap(); assert_ok!(>::force_create( RuntimeHelper::::root_origin(), - asset_id.into(), + asset_id.clone().into(), asset_owner.clone().into(), false, asset_minimum_asset_balance.into() @@ -859,7 +873,7 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< // We first mint enough asset for the account to exist for assets assert_ok!(>::mint( RuntimeHelper::::origin_of(asset_owner.clone()), - asset_id.into(), + asset_id.clone().into(), alice_account.clone().into(), (6 * asset_minimum_asset_balance).into() )); @@ -867,28 +881,28 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< // check Assets before assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &alice_account ), (6 * asset_minimum_asset_balance).into() ); assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &bob_account ), 0.into() ); assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &charlie_account ), 0.into() ); assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &asset_owner ), 0.into() @@ -915,21 +929,20 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< // ExistentialDeposit) assert_noop!( RuntimeHelper::::do_transfer( - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { - network: None, - id: alice_account.clone().into() - }), + interior: [AccountId32 { network: None, id: alice_account.clone().into() }] + .into(), }, - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { + interior: [AccountId32 { network: None, id: charlie_account.clone().into() - }), + }] + .into(), }, - (asset_id_as_multilocation, asset_minimum_asset_balance), + (asset_id_as_location.clone(), asset_minimum_asset_balance), ), XcmError::FailedToTransactAsset(Into::<&str>::into( sp_runtime::TokenError::CannotCreate @@ -939,18 +952,17 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< // transfer_asset (deposit/withdraw) ALICE -> BOB (ok - has ExistentialDeposit) assert!(matches!( RuntimeHelper::::do_transfer( - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { - network: None, - id: alice_account.clone().into() - }), + interior: [AccountId32 { network: None, id: alice_account.clone().into() }] + .into(), }, - MultiLocation { + Location { parents: 0, - interior: X1(AccountId32 { network: None, id: bob_account.clone().into() }), + interior: [AccountId32 { network: None, id: bob_account.clone().into() }] + .into(), }, - (asset_id_as_multilocation, asset_minimum_asset_balance), + (asset_id_as_location, asset_minimum_asset_balance), ), Ok(_) )); @@ -958,21 +970,21 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< // check Assets after assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &alice_account ), (5 * asset_minimum_asset_balance).into() ); assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &bob_account ), asset_minimum_asset_balance.into() ); assert_eq!( >::balance( - asset_id.into(), + asset_id.clone().into(), &charlie_account ), 0.into() @@ -1085,7 +1097,8 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -1104,26 +1117,23 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor <::Lookup as StaticLookup>::Source: From<::AccountId>, ForeignAssetsPalletInstance: 'static, - AssetId: Clone + Copy, - AssetIdConverter: MaybeEquivalence, + AssetId: Clone, + AssetIdConverter: MaybeEquivalence, { // foreign parachain with the same consenus currency as asset - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(2222), GeneralIndex(1234567)) }; - let asset_id = AssetIdConverter::convert(&foreign_asset_id_multilocation).unwrap(); + let foreign_asset_id_location = Location::new(1, [Parachain(2222), GeneralIndex(1234567)]); + let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap(); // foreign creator, which can be sibling parachain to match ForeignCreators - let foreign_creator = MultiLocation { parents: 1, interior: X1(Parachain(2222)) }; + let foreign_creator = Location { parents: 1, interior: [Parachain(2222)].into() }; let foreign_creator_as_account_id = SovereignAccountOf::convert_location(&foreign_creator).expect(""); // we want to buy execution with local relay chain currency let buy_execution_fee_amount = WeightToFee::weight_to_fee(&Weight::from_parts(90_000_000_000, 0)); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation::parent()), - fun: Fungible(buy_execution_fee_amount), - }; + let buy_execution_fee = + Asset { id: AssetId(Location::parent()), fun: Fungible(buy_execution_fee_amount) }; const ASSET_NAME: &str = "My super coin"; const ASSET_SYMBOL: &str = "MY_S_COIN"; @@ -1163,7 +1173,7 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor Runtime, ForeignAssetsPalletInstance, >::create { - id: asset_id.into(), + id: asset_id.clone().into(), // admin as sovereign_account admin: foreign_creator_as_account_id.clone().into(), min_balance: 1.into(), @@ -1173,7 +1183,7 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor Runtime, ForeignAssetsPalletInstance, >::set_metadata { - id: asset_id.into(), + id: asset_id.clone().into(), name: Vec::from(ASSET_NAME), symbol: Vec::from(ASSET_SYMBOL), decimals: 12, @@ -1183,7 +1193,7 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor Runtime, ForeignAssetsPalletInstance, >::set_team { - id: asset_id.into(), + id: asset_id.clone().into(), issuer: foreign_creator_as_account_id.clone().into(), admin: foreign_creator_as_account_id.clone().into(), freezer: bob_account.clone().into(), @@ -1213,16 +1223,17 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor ]); // messages with different consensus should go through the local bridge-hub - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do - let outcome = XcmExecutor::::execute_xcm( - foreign_creator, + let outcome = XcmExecutor::::prepare_and_execute( + foreign_creator.clone(), xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); // check events let mut events = >::events() @@ -1241,25 +1252,25 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor use frame_support::traits::fungibles::roles::Inspect as InspectRoles; assert_eq!( >::owner( - asset_id.into() + asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::admin( - asset_id.into() + asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::issuer( - asset_id.into() + asset_id.clone().into() ), Some(foreign_creator_as_account_id.clone()) ); assert_eq!( >::freezer( - asset_id.into() + asset_id.clone().into() ), Some(bob_account.clone()) ); @@ -1273,13 +1284,13 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor assert_metadata::< pallet_assets::Pallet, AccountIdOf, - >(asset_id, ASSET_NAME, ASSET_SYMBOL, 12); + >(asset_id.clone(), ASSET_NAME, ASSET_SYMBOL, 12); // check if changed freezer, can freeze assert_noop!( >::freeze( RuntimeHelper::::origin_of(bob_account), - asset_id.into(), + asset_id.clone().into(), alice_account.clone().into() ), pallet_assets::Error::::NoAccount @@ -1295,9 +1306,9 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor // lets try create asset for different parachain(3333) (foreign_creator(2222) can create // just his assets) - let foreign_asset_id_multilocation = - MultiLocation { parents: 1, interior: X2(Parachain(3333), GeneralIndex(1234567)) }; - let asset_id = AssetIdConverter::convert(&foreign_asset_id_multilocation).unwrap(); + let foreign_asset_id_location = + Location { parents: 1, interior: [Parachain(3333), GeneralIndex(1234567)].into() }; + let asset_id = AssetIdConverter::convert(&foreign_asset_id_location).unwrap(); // prepare data for xcm::Transact(create) let foreign_asset_create = runtime_call_encode(pallet_assets::Call::< @@ -1321,16 +1332,17 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor ]); // messages with different consensus should go through the local bridge-hub - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do - let outcome = XcmExecutor::::execute_xcm( + let outcome = XcmExecutor::::prepare_and_execute( foreign_creator, xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); additional_checks_after(); }) @@ -1387,3 +1399,203 @@ macro_rules! include_create_and_manage_foreign_assets_for_local_consensus_parach } } ); + +/// Test-case makes sure that `Runtime` can reserve-transfer asset to other parachains (where +/// teleport is not trusted) +pub fn reserve_transfer_native_asset_to_non_teleport_para_works< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, +>( + collator_session_keys: CollatorSessionKeys, + slot_durations: SlotDurations, + existential_deposit: BalanceOf, + alice_account: AccountIdOf, + unwrap_pallet_xcm_event: Box) -> Option>>, + unwrap_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, + weight_limit: WeightLimit, +) 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_timestamp::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::Balance: From + Into, + XcmConfig: xcm_executor::Config, + LocationToAccountId: ConvertLocation>, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + ::AccountId: From, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + HrmpChannelSource: XcmpMessageSource, +{ + let runtime_para_id = 1000; + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .with_para_id(runtime_para_id.into()) + .build() + .execute_with(|| { + let mut alice = [0u8; 32]; + alice[0] = 1; + let included_head = RuntimeHelper::::run_to_block( + 2, + AccountId::from(alice).into(), + ); + + // reserve-transfer native asset with local reserve to remote parachain (2345) + + let other_para_id = 2345; + let native_asset = Location::parent(); + let dest = Location::new(1, [Parachain(other_para_id)]); + let mut dest_beneficiary = Location::new(1, [Parachain(other_para_id)]) + .appended_with(AccountId32 { + network: None, + id: sp_runtime::AccountId32::new([3; 32]).into(), + }) + .unwrap(); + dest_beneficiary.reanchor(&dest, &XcmConfig::UniversalLocation::get()).unwrap(); + + let reserve_account = LocationToAccountId::convert_location(&dest) + .expect("Sovereign account for reserves"); + let balance_to_transfer = 1_000_000_000_000_u128; + + // open HRMP to other parachain + mock_open_hrmp_channel::( + runtime_para_id.into(), + other_para_id.into(), + included_head, + &alice, + &slot_durations, + ); + + // we calculate exact delivery fees _after_ sending the message by weighing the sent + // xcm, and this delivery fee varies for different runtimes, so just add enough buffer, + // then verify the arithmetics check out on final balance. + let delivery_fees_buffer = 40_000_000_000u128; + // drip 2xED + transfer_amount + delivery_fees_buffer to Alice account + let alice_account_init_balance = existential_deposit.saturating_mul(2.into()) + + balance_to_transfer.into() + + delivery_fees_buffer.into(); + let _ = >::deposit_creating( + &alice_account, + alice_account_init_balance, + ); + // SA of target location needs to have at least ED, otherwise making reserve fails + let _ = >::deposit_creating( + &reserve_account, + existential_deposit, + ); + + // we just check here, that user retains enough balance after withdrawal + // and also we check if `balance_to_transfer` is more than `existential_deposit`, + assert!( + (>::free_balance(&alice_account) - + balance_to_transfer.into()) >= + existential_deposit + ); + // SA has just ED + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit + ); + + // local native asset (pallet_balances) + let asset_to_transfer = + Asset { fun: Fungible(balance_to_transfer.into()), id: AssetId(native_asset) }; + + // pallet_xcm call reserve transfer + assert_ok!(>::limited_reserve_transfer_assets( + RuntimeHelper::::origin_of(alice_account.clone()), + Box::new(dest.clone().into_versioned()), + Box::new(dest_beneficiary.clone().into_versioned()), + Box::new(VersionedAssets::from(Assets::from(asset_to_transfer))), + 0, + weight_limit, + )); + + // check events + // check pallet_xcm attempted + RuntimeHelper::::assert_pallet_xcm_event_outcome( + &unwrap_pallet_xcm_event, + |outcome| { + assert_ok!(outcome.ensure_complete()); + }, + ); + + // check that xcm was sent + let xcm_sent_message_hash = >::events() + .into_iter() + .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode())) + .find_map(|e| match e { + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => + Some(message_hash), + _ => None, + }); + + // read xcm + let xcm_sent = RuntimeHelper::::take_xcm( + other_para_id.into(), + ) + .unwrap(); + + let v4_xcm: Xcm<()> = xcm_sent.clone().try_into().unwrap(); + dbg!(&v4_xcm); + + let delivery_fees = get_fungible_delivery_fees::< + ::XcmSender, + >(dest.clone(), Xcm::try_from(xcm_sent.clone()).unwrap()); + + assert_eq!( + xcm_sent_message_hash, + Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256)) + ); + let mut xcm_sent: Xcm<()> = xcm_sent.try_into().expect("versioned xcm"); + + // check sent XCM Program to other parachain + println!("reserve_transfer_native_asset_works sent xcm: {:?}", xcm_sent); + let reserve_assets_deposited = Assets::from(vec![Asset { + id: AssetId(Location { parents: 1, interior: Here }), + fun: Fungible(1000000000000), + }]); + + assert_matches_reserve_asset_deposited_instructions( + &mut xcm_sent, + &reserve_assets_deposited, + &dest_beneficiary, + ); + + // check alice account decreased by balance_to_transfer ( + delivery_fees) + assert_eq!( + >::free_balance(&alice_account), + alice_account_init_balance - balance_to_transfer.into() - delivery_fees.into() + ); + + // check reserve account + // check reserve account increased by balance_to_transfer + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit + balance_to_transfer.into() + ); + }) +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 9852453d283bb3649b5454addfa5b501baddd957..1cce3b647cf0446a2246417b5383594fb501e600 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -16,30 +16,29 @@ //! Module contains predefined test-case scenarios for `Runtime` with various assets transferred //! over a bridge. +use crate::{assert_matches_reserve_asset_deposited_instructions, get_fungible_delivery_fees}; use codec::Encode; use cumulus_primitives_core::XcmpMessageSource; use frame_support::{ assert_ok, - traits::{ - fungible::Mutate, Currency, OnFinalize, OnInitialize, OriginTrait, ProcessMessageError, - }, + traits::{Currency, Get, OnFinalize, OnInitialize, OriginTrait, ProcessMessageError}, }; use frame_system::pallet_prelude::BlockNumberFor; use parachains_common::{AccountId, Balance}; use parachains_runtimes_test_utils::{ mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, RuntimeHelper, - ValidatorIdOf, XcmReceivedFrom, + SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; -use sp_runtime::traits::StaticLookup; -use xcm::{latest::prelude::*, VersionedMultiAssets}; +use sp_runtime::{traits::StaticLookup, Saturating}; +use xcm::{latest::prelude::*, VersionedAssets}; use xcm_builder::{CreateMatcher, MatchXcm}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; pub struct TestBridgingConfig { pub bridged_network: NetworkId, pub local_bridge_hub_para_id: u32, - pub local_bridge_hub_location: MultiLocation, - pub bridged_target_location: MultiLocation, + pub local_bridge_hub_location: Location, + pub bridged_target_location: Location, } /// Test-case makes sure that `Runtime` can initiate **reserve transfer assets** over bridge. @@ -52,6 +51,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< LocationToAccountId, >( collator_session_keys: CollatorSessionKeys, + slot_durations: SlotDurations, existential_deposit: BalanceOf, alice_account: AccountIdOf, unwrap_pallet_xcm_event: Box) -> Option>>, @@ -61,6 +61,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< prepare_configuration: fn() -> TestBridgingConfig, weight_limit: WeightLimit, maybe_paid_export_message: Option, + delivery_fees_account: Option>, ) where Runtime: frame_system::Config + pallet_balances::Config @@ -69,7 +70,8 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -116,7 +118,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< LocationToAccountId::convert_location(&target_location_from_different_consensus) .expect("Sovereign account for reserves"); let balance_to_transfer = 1_000_000_000_000_u128; - let native_asset = MultiLocation::parent(); + let native_asset = Location::parent(); // open HRMP to bridge hub mock_open_hrmp_channel::( @@ -124,10 +126,16 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< local_bridge_hub_para_id.into(), included_head, &alice, + &slot_durations, ); - // drip ED to account - let alice_account_init_balance = existential_deposit + balance_to_transfer.into(); + // we calculate exact delivery fees _after_ sending the message by weighing the sent + // xcm, and this delivery fee varies for different runtimes, so just add enough buffer, + // then verify the arithmetics check out on final balance. + let delivery_fees_buffer = 8_000_000_000_000u128; + // drip ED + transfer_amount + delivery_fees_buffer to Alice account + let alice_account_init_balance = + existential_deposit + balance_to_transfer.into() + delivery_fees_buffer.into(); let _ = >::deposit_creating( &alice_account, alice_account_init_balance, @@ -151,51 +159,43 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< existential_deposit ); + let delivery_fees_account_balance_before = delivery_fees_account + .as_ref() + .map(|dfa| >::free_balance(dfa)) + .unwrap_or(0.into()); + // local native asset (pallet_balances) - let asset_to_transfer = MultiAsset { - fun: Fungible(balance_to_transfer.into()), - id: Concrete(native_asset), - }; + let asset_to_transfer = + Asset { fun: Fungible(balance_to_transfer.into()), id: native_asset.into() }; // destination is (some) account relative to the destination different consensus - let target_destination_account = MultiLocation { - parents: 0, - interior: X1(AccountId32 { + let target_destination_account = Location::new( + 0, + [AccountId32 { network: Some(bridged_network), id: sp_runtime::AccountId32::new([3; 32]).into(), - }), - }; + }], + ); - // Make sure sender has enough funds for paying delivery fees - // TODO: Get this fee via weighing the corresponding message - let delivery_fees = 1324039894; - >::mint_into(&alice_account, delivery_fees.into()) + let assets_to_transfer = Assets::from(asset_to_transfer); + let mut expected_assets = assets_to_transfer.clone(); + let context = XcmConfig::UniversalLocation::get(); + expected_assets + .reanchor(&target_location_from_different_consensus, &context) .unwrap(); + let expected_beneficiary = target_destination_account.clone(); + // do pallet_xcm call reserve transfer assert_ok!(>::limited_reserve_transfer_assets( RuntimeHelper::::origin_of(alice_account.clone()), - Box::new(target_location_from_different_consensus.into_versioned()), + Box::new(target_location_from_different_consensus.clone().into_versioned()), Box::new(target_destination_account.into_versioned()), - Box::new(VersionedMultiAssets::from(MultiAssets::from(asset_to_transfer))), + Box::new(VersionedAssets::from(assets_to_transfer)), 0, weight_limit, )); - // check alice account decreased by balance_to_transfer - // TODO:check-parameter: change and assert in tests when (https://github.com/paritytech/polkadot-sdk/pull/1234) merged - assert_eq!( - >::free_balance(&alice_account), - alice_account_init_balance - balance_to_transfer.into() - ); - - // check reserve account - // check reserve account increased by balance_to_transfer - assert_eq!( - >::free_balance(&reserve_account), - existential_deposit + balance_to_transfer.into() - ); - // check events // check pallet_xcm attempted RuntimeHelper::::assert_pallet_xcm_event_outcome( @@ -220,7 +220,6 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< local_bridge_hub_para_id.into(), ) .unwrap(); - assert_eq!( xcm_sent_message_hash, Some(xcm_sent.using_encoded(sp_io::hashing::blake2_256)) @@ -229,6 +228,7 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< // check sent XCM ExportMessage to BridgeHub + let mut delivery_fees = 0; // 1. check paid or unpaid if let Some(expected_fee_asset_id) = maybe_paid_export_message { xcm_sent @@ -244,6 +244,11 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< _ => Err(ProcessMessageError::BadFormat), }) .expect("contains BuyExecution") + .match_next_inst(|instr| match instr { + SetAppendix(_) => Ok(()), + _ => Err(ProcessMessageError::BadFormat), + }) + .expect("contains SetAppendix") } else { xcm_sent .0 @@ -266,15 +271,51 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< let (_, target_location_junctions_without_global_consensus) = target_location_from_different_consensus .interior + .clone() .split_global() .expect("split works"); assert_eq!(destination, &target_location_junctions_without_global_consensus); - assert_matches_pallet_xcm_reserve_transfer_assets_instructions(inner_xcm); + // Call `SendXcm::validate` to get delivery fees. + delivery_fees = get_fungible_delivery_fees::< + ::XcmSender, + >( + target_location_from_different_consensus.clone(), + inner_xcm.clone(), + ); + assert_matches_reserve_asset_deposited_instructions( + inner_xcm, + &expected_assets, + &expected_beneficiary, + ); Ok(()) }, _ => Err(ProcessMessageError::BadFormat), }) .expect("contains ExportMessage"); + + // check alice account decreased by balance_to_transfer + assert_eq!( + >::free_balance(&alice_account), + alice_account_init_balance + .saturating_sub(balance_to_transfer.into()) + .saturating_sub(delivery_fees.into()) + ); + + // check reserve account increased by balance_to_transfer + assert_eq!( + >::free_balance(&reserve_account), + existential_deposit + balance_to_transfer.into() + ); + + // check dedicated account increased by delivery fees (if configured) + if let Some(delivery_fees_account) = delivery_fees_account { + let delivery_fees_account_balance_after = + >::free_balance(&delivery_fees_account); + assert!( + delivery_fees_account_balance_after - delivery_fees.into() >= + delivery_fees_account_balance_before + ); + } }) } @@ -282,20 +323,22 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsPalletInstance, >( collator_session_keys: CollatorSessionKeys, existential_deposit: BalanceOf, target_account: AccountIdOf, block_author_account: AccountIdOf, - ( - foreign_asset_id_multilocation, - transfered_foreign_asset_id_amount, - foreign_asset_id_minimum_balance, - ): (MultiLocation, u128, u128), - prepare_configuration: fn() -> TestBridgingConfig, + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountIdOf, + xcm::v3::Location, + u128, + ), + foreign_asset_id_amount_to_transfer: u128, + prepare_configuration: impl FnOnce() -> TestBridgingConfig, (bridge_instance, universal_origin, descend_origin): (Junctions, Junction, Junctions), /* bridge adds origin manipulation on the way */ + additional_checks_before: impl FnOnce(), + additional_checks_after: impl FnOnce(), ) where Runtime: frame_system::Config + pallet_balances::Config @@ -305,18 +348,18 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, - AccountIdOf: Into<[u8; 32]>, + AccountIdOf: Into<[u8; 32]> + From<[u8; 32]>, ValidatorIdOf: From>, - BalanceOf: From, + BalanceOf: From + Into, XcmConfig: xcm_executor::Config, - LocationToAccountId: ConvertLocation>, >::AssetId: - From + Into, + From + Into, >::AssetIdParameter: - From + Into, + From + Into, >::Balance: From + Into + From, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId> @@ -337,73 +380,54 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< block_author_account.clone().into(), ); - // prepare bridge config - let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); - // drip 'ED' user target account let _ = >::deposit_creating( &target_account, existential_deposit, ); - // sovereign account as foreign asset owner (can be whoever for this scenario, doesnt - // matter) - let sovereign_account_as_owner_of_foreign_asset = - LocationToAccountId::convert_location(&MultiLocation::parent()).unwrap(); - - // staking pot account for collecting local native fees from `BuyExecution` - let staking_pot = >::account_id(); - let _ = >::deposit_creating( - &staking_pot, - existential_deposit, - ); - // create foreign asset for wrapped/derivated representation assert_ok!( >::force_create( RuntimeHelper::::root_origin(), - foreign_asset_id_multilocation.into(), - sovereign_account_as_owner_of_foreign_asset.clone().into(), + foreign_asset_id_location.into(), + foreign_asset_owner.into(), true, // is_sufficient=true foreign_asset_id_minimum_balance.into() ) ); + // prepare bridge config + let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); + // Balances before assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); - assert_eq!( - >::free_balance(&staking_pot), - existential_deposit.clone() - ); // ForeignAssets balances before assert_eq!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account ), 0.into() ); - assert_eq!( - >::balance( - foreign_asset_id_multilocation.into(), - &block_author_account - ), - 0.into() - ); - assert_eq!( - >::balance( - foreign_asset_id_multilocation.into(), - &staking_pot - ), - 0.into() + + // additional check before + additional_checks_before(); + + let foreign_asset_id_location_latest: Location = + foreign_asset_id_location.try_into().unwrap(); + + let expected_assets = Assets::from(vec![Asset { + id: AssetId(foreign_asset_id_location_latest.clone()), + fun: Fungible(foreign_asset_id_amount_to_transfer), + }]); + let expected_beneficiary = Location::new( + 0, + [AccountId32 { network: None, id: target_account.clone().into() }], ); // Call received XCM execution @@ -411,143 +435,72 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< DescendOrigin(bridge_instance), UniversalOrigin(universal_origin), DescendOrigin(descend_origin), - ReserveAssetDeposited(MultiAssets::from(vec![MultiAsset { - id: Concrete(foreign_asset_id_multilocation), - fun: Fungible(transfered_foreign_asset_id_amount), - }])), + ReserveAssetDeposited(expected_assets.clone()), ClearOrigin, BuyExecution { - fees: MultiAsset { - id: Concrete(foreign_asset_id_multilocation), - fun: Fungible(transfered_foreign_asset_id_amount), + fees: Asset { + id: AssetId(foreign_asset_id_location_latest.clone()), + fun: Fungible(foreign_asset_id_amount_to_transfer), }, weight_limit: Unlimited, }, DepositAsset { assets: Wild(AllCounted(1)), - beneficiary: MultiLocation { - parents: 0, - interior: X1(AccountId32 { - network: None, - id: target_account.clone().into(), - }), - }, + beneficiary: expected_beneficiary.clone(), }, SetTopic([ 220, 188, 144, 32, 213, 83, 111, 175, 44, 210, 111, 19, 90, 165, 191, 112, 140, 247, 192, 124, 42, 17, 153, 141, 114, 34, 189, 20, 83, 69, 237, 173, ]), ]); - assert_matches_pallet_xcm_reserve_transfer_assets_instructions(&mut xcm.clone()); + assert_matches_reserve_asset_deposited_instructions( + &mut xcm.clone(), + &expected_assets, + &expected_beneficiary, + ); - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do - let outcome = XcmExecutor::::execute_xcm( + let outcome = XcmExecutor::::prepare_and_execute( local_bridge_hub_location, xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight( XcmReceivedFrom::Sibling, ), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); - // author actual balance after (received fees from Trader for ForeignAssets) - let author_received_fees = - >::balance( - foreign_asset_id_multilocation.into(), - &block_author_account, - ); - - // Balances after (untouched) + // Balances after assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); - assert_eq!( - >::free_balance(&staking_pot), - existential_deposit.clone() - ); // ForeignAssets balances after - assert_eq!( + assert!( >::balance( - foreign_asset_id_multilocation.into(), + foreign_asset_id_location.into(), &target_account - ), - (transfered_foreign_asset_id_amount - author_received_fees.into()).into() - ); - assert_eq!( - >::balance( - foreign_asset_id_multilocation.into(), - &block_author_account - ), - author_received_fees - ); - assert_eq!( - >::balance( - foreign_asset_id_multilocation.into(), - &staking_pot - ), - 0.into() + ) > 0.into() ); - }) -} -fn assert_matches_pallet_xcm_reserve_transfer_assets_instructions( - xcm: &mut Xcm, -) { - let _ = xcm - .0 - .matcher() - .skip_inst_while(|inst| !matches!(inst, ReserveAssetDeposited(..))) - .expect("no instruction ReserveAssetDeposited?") - .match_next_inst(|instr| match instr { - ReserveAssetDeposited(..) => Ok(()), - _ => Err(ProcessMessageError::BadFormat), + // additional check after + additional_checks_after(); }) - .expect("expected instruction ReserveAssetDeposited") - .match_next_inst(|instr| match instr { - ClearOrigin => Ok(()), - _ => Err(ProcessMessageError::BadFormat), - }) - .expect("expected instruction ClearOrigin") - .match_next_inst(|instr| match instr { - BuyExecution { .. } => Ok(()), - _ => Err(ProcessMessageError::BadFormat), - }) - .expect("expected instruction BuyExecution") - .match_next_inst(|instr| match instr { - DepositAsset { .. } => Ok(()), - _ => Err(ProcessMessageError::BadFormat), - }) - .expect("expected instruction DepositAsset"); } pub fn report_bridge_status_from_xcm_bridge_router_works< Runtime, AllPalletsWithoutSystem, XcmConfig, - HrmpChannelOpener, - HrmpChannelSource, LocationToAccountId, XcmBridgeHubRouterInstance, >( collator_session_keys: CollatorSessionKeys, - existential_deposit: BalanceOf, - alice_account: AccountIdOf, - unwrap_pallet_xcm_event: Box) -> Option>>, - unwrap_xcmp_queue_event: Box< - dyn Fn(Vec) -> Option>, - >, prepare_configuration: fn() -> TestBridgingConfig, - weight_limit: WeightLimit, - maybe_paid_export_message: Option, congested_message: fn() -> Xcm, uncongested_message: fn() -> Xcm, ) where @@ -559,7 +512,8 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_xcm_bridge_hub_router::Config, + + pallet_xcm_bridge_hub_router::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -573,10 +527,6 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< <::Lookup as StaticLookup>::Source: From<::AccountId>, ::AccountId: From, - HrmpChannelOpener: frame_support::inherent::ProvideInherent< - Call = cumulus_pallet_parachain_system::Call, - >, - HrmpChannelSource: XcmpMessageSource, XcmBridgeHubRouterInstance: 'static, { ExtBuilder::::default() @@ -585,41 +535,23 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< .with_tracing() .build() .execute_with(|| { - // check transfer works - limited_reserve_transfer_assets_for_native_asset_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - HrmpChannelOpener, - HrmpChannelSource, - LocationToAccountId, - >( - collator_session_keys, - existential_deposit, - alice_account, - unwrap_pallet_xcm_event, - unwrap_xcmp_queue_event, - prepare_configuration, - weight_limit, - maybe_paid_export_message, - ); - let report_bridge_status = |is_congested: bool| { // prepare bridge config let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); // Call received XCM execution let xcm = if is_congested { congested_message() } else { uncongested_message() }; - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); // execute xcm as XcmpQueue would do - let outcome = XcmExecutor::::execute_xcm( + let outcome = XcmExecutor::::prepare_and_execute( local_bridge_hub_location, xcm, - hash, + &mut hash, RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), ); - assert_eq!(outcome.ensure_complete(), Ok(())); + assert_ok!(outcome.ensure_complete()); assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::::bridge().is_congested); }; diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs index 0aebe38fef539db79c14f2d29053fc006565da79..f509a3a8acaad9ea2f499c2fa48ca72e0b0882d9 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs @@ -23,11 +23,11 @@ use xcm::latest::prelude::*; /// Because it returns only a `u128`, it assumes delivery fees are only paid /// in one asset and that asset is known. pub fn transfer_assets_delivery_fees( - assets: MultiAssets, + assets: Assets, fee_asset_item: u32, weight_limit: WeightLimit, - beneficiary: MultiLocation, - destination: MultiLocation, + beneficiary: Location, + destination: Location, ) -> u128 { let message = teleport_assets_dummy_message(assets, fee_asset_item, weight_limit, beneficiary); get_fungible_delivery_fees::(destination, message) @@ -35,7 +35,7 @@ pub fn transfer_assets_delivery_fees( /// Returns the delivery fees amount for a query response as a result of the execution /// of a `ExpectError` instruction with no error. -pub fn query_response_delivery_fees(querier: MultiLocation) -> u128 { +pub fn query_response_delivery_fees(querier: Location) -> u128 { // Message to calculate delivery fees, it's encoded size is what's important. // This message reports that there was no error, if an error is reported, the encoded size would // be different. @@ -45,7 +45,7 @@ pub fn query_response_delivery_fees(querier: MultiLocation) -> u128 query_id: 0, // Dummy query id response: Response::ExecutionResult(None), max_weight: Weight::zero(), - querier: Some(querier), + querier: Some(querier.clone()), }, SetTopic([0u8; 32]), // Dummy topic ]); @@ -55,9 +55,9 @@ pub fn query_response_delivery_fees(querier: MultiLocation) -> u128 /// Returns the delivery fees amount for the execution of `PayOverXcm` pub fn pay_over_xcm_delivery_fees( interior: Junctions, - destination: MultiLocation, - beneficiary: MultiLocation, - asset: MultiAsset, + destination: Location, + beneficiary: Location, + asset: Asset, ) -> u128 { // This is a dummy message. // The encoded size is all that matters for delivery fees. @@ -66,7 +66,11 @@ pub fn pay_over_xcm_delivery_fees( UnpaidExecution { weight_limit: Unlimited, check_origin: None }, SetAppendix(Xcm(vec![ SetFeesMode { jit_withdraw: true }, - ReportError(QueryResponseInfo { destination, query_id: 0, max_weight: Weight::zero() }), + ReportError(QueryResponseInfo { + destination: destination.clone(), + query_id: 0, + max_weight: Weight::zero(), + }), ])), TransferAsset { beneficiary, assets: vec![asset].into() }, ]); @@ -78,10 +82,10 @@ pub fn pay_over_xcm_delivery_fees( /// However, it should have the same encoded size, which is what matters for delivery fees. /// Also has same encoded size as the one created by the reserve transfer assets extrinsic. fn teleport_assets_dummy_message( - assets: MultiAssets, + assets: Assets, fee_asset_item: u32, weight_limit: WeightLimit, - beneficiary: MultiLocation, + beneficiary: Location, ) -> Xcm<()> { Xcm(vec![ ReceiveTeleportedAsset(assets.clone()), // Same encoded size as `ReserveAssetDeposited` @@ -93,7 +97,7 @@ fn teleport_assets_dummy_message( } /// Given a message, a sender, and a destination, it returns the delivery fees -fn get_fungible_delivery_fees(destination: MultiLocation, message: Xcm<()>) -> u128 { +fn get_fungible_delivery_fees(destination: Location, message: Xcm<()>) -> u128 { let Ok((_, delivery_fees)) = validate_send::(destination, message) else { unreachable!("message can be sent; qed") }; diff --git a/cumulus/parachains/runtimes/bridge-hubs/README.md b/cumulus/parachains/runtimes/bridge-hubs/README.md index ef1f837a7e108a92c1219f5cfcec921b3c764233..c858532295ddce7ad1fd9a57a19d752201b78abd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/README.md +++ b/cumulus/parachains/runtimes/bridge-hubs/README.md @@ -1,14 +1,14 @@ - [Bridge-hub Parachains](#bridge-hub-parachains) - [Requirements for local run/testing](#requirements-for-local-runtesting) - - [How to test local Rococo <-> Wococo bridge](#how-to-test-local-rococo---wococo-bridge) - - [Run Rococo/Wococo chains with zombienet](#run-rococowococo-chains-with-zombienet) - - [Init bridge and run relayer between BridgeHubRococo and - BridgeHubWococo](#init-bridge-and-run-relayer-between-bridgehubrococo-and-bridgehubwococo) - - [Initialize configuration for transfer asset over bridge - (ROCs/WOCs)](#initialize-configuration-for-transfer-asset-over-bridge-rocswocs) - - [Send messages - transfer asset over bridge (ROCs/WOCs)](#send-messages---transfer-asset-over-bridge-rocswocs) - - [Claim relayer's rewards on BridgeHubRococo and - BridgeHubWococo](#claim-relayers-rewards-on-bridgehubrococo-and-bridgehubwococo) + - [How to test local Rococo <-> Westend bridge](#how-to-test-local-rococo---westend-bridge) + - [Run Rococo/Westend chains with zombienet](#run-rococowestend-chains-with-zombienet) + - [Init bridge and run relayer between BridgeHubRococo and + BridgeHubWestend](#init-bridge-and-run-relayer-between-bridgehubrococo-and-bridgehubwestend) + - [Initialize configuration for transfer asset over bridge + (ROCs/WNDs)](#initialize-configuration-for-transfer-asset-over-bridge-rocswnds) + - [Send messages - transfer asset over bridge (ROCs/WNDs)](#send-messages---transfer-asset-over-bridge-rocswnds) + - [Claim relayer's rewards on BridgeHubRococo and + BridgeHubWestend](#claim-relayers-rewards-on-bridgehubrococo-and-bridgehubwestend) - [How to test local BridgeHubKusama/BridgeHubPolkadot](#how-to-test-local-bridgehubkusamabridgehubpolkadot) # Bridge-hub Parachains @@ -44,17 +44,7 @@ Copy the apropriate binary (zombienet-linux) from the latest release to ~/local_ --- # 2. Build polkadot binary -# If you want to test Kusama/Polkadot bridge, we need "sudo pallet + fast-runtime", -# so we need to use sudofi in polkadot directory. -# -# Install sudofi: (skip if already installed) -# cd -# git clone https://github.com/paritytech/parachain-utils.git -# cd parachain-utils # -> this is -# cargo build --release --bin sudofi -# -# cd /polkadot -# /target/release/sudofi +We need polkadot binary with "fast-runtime" feature: cd cargo build --release --features fast-runtime --bin polkadot @@ -91,32 +81,29 @@ cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-paracha cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-parachain-asset-hub ``` +## How to test local Rococo <-> Westend bridge -## How to test local Rococo <-> Wococo bridge - -### Run Rococo/Wococo chains with zombienet +### Run Rococo/Westend chains with zombienet ``` cd # Rococo + BridgeHubRococo + AssetHub for Rococo (mirroring Kusama) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml ``` ``` cd -# Wococo + BridgeHubWococo + AssetHub for Wococo (mirroring Polkadot) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WOCOCO=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_wococo_local_network.toml +# Westend + BridgeHubWestend + AssetHub for Westend (mirroring Polkadot) +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml ``` -### Init bridge and run relayer between BridgeHubRococo and BridgeHubWococo +### Init bridge and run relayer between BridgeHubRococo and BridgeHubWestend **Accounts of BridgeHub parachains:** - `Bob` is pallet owner of all bridge pallets @@ -125,77 +112,91 @@ POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WOCOCO=~/local_bridge_testing/bin/p ``` cd -./cumulus/scripts/bridges_rococo_wococo.sh run-relay +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh run-relay ``` **Check relay-chain headers relaying:** - Rococo parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8943#/chainstate - Pallet: - **bridgeWococoGrandpa** - Keys: **bestFinalized()** -- Wococo parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate - Pallet: - **bridgeRococoGrandpa** - Keys: **bestFinalized()** + **bridgeWestendGrandpa** - Keys: **bestFinalized()** +- Westend parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate - Pallet: + **bridgeRococoGrandpa** - Keys: **bestFinalized()** **Check parachain headers relaying:** - Rococo parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8943#/chainstate - Pallet: - **bridgeWococoParachain** - Keys: **parasInfo(None)** -- Wococo parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate - Pallet: - **bridgeRococoParachain** - Keys: **parasInfo(None)** + **bridgeWestendParachains** - Keys: **parasInfo(None)** +- Westend parachain: - https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A8945#/chainstate - Pallet: + **bridgeRococoParachains** - Keys: **parasInfo(None)** -### Initialize configuration for transfer asset over bridge (ROCs/WOCs) +### Initialize configuration for transfer asset over bridge (ROCs/WNDs) This initialization does several things: -- creates `ForeignAssets` for wrappedROCs/wrappedWOCs -- drips SA for AssetHubRococo on AssetHubWococo (and vice versa) which holds reserved assets on source chains +- creates `ForeignAssets` for wrappedROCs/wrappedWNDs +- drips SA for AssetHubRococo on AssetHubWestend (and vice versa) which holds reserved assets on source chains ``` cd -./cumulus/scripts/bridges_rococo_wococo.sh init-asset-hub-rococo-local -./cumulus/scripts/bridges_rococo_wococo.sh init-bridge-hub-rococo-local -./cumulus/scripts/bridges_rococo_wococo.sh init-asset-hub-wococo-local -./cumulus/scripts/bridges_rococo_wococo.sh init-bridge-hub-wococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-westend-local ``` -### Send messages - transfer asset over bridge (ROCs/WOCs) +### Send messages - transfer asset over bridge (ROCs/WNDs) -Do (asset) transfers: +Do reserve-backed transfers: ``` cd -# ROCs from Rococo's Asset Hub to Wococo's. -./cumulus/scripts/bridges_rococo_wococo.sh reserve-transfer-assets-from-asset-hub-rococo-local +# ROCs from Rococo's Asset Hub to Westend's. +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-rococo-local ``` ``` cd -# WOCs from Wococo's Asset Hub to Rococo's. -./cumulus/scripts/bridges_rococo_wococo.sh reserve-transfer-assets-from-asset-hub-wococo-local +# WNDs from Westend's Asset Hub to Rococo's. +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-westend-local ``` - open explorers: (see zombienets) - AssetHubRococo (see events `xcmpQueue.XcmpMessageSent`, `polkadotXcm.Attempted`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9910#/explorer - - BridgeHubRococo (see `bridgeRococoToWococoMessages.MessageAccepted`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer - - BridgeHubWococo (see `bridgeWococoToRococoMessages.MessagesReceived`, `xcmpQueue.XcmpMessageSent`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer - - AssetHubWococo (see `foreignAssets.Issued`, `xcmpQueue.Success`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer - - BridgeHubRocococ (see `bridgeRococoToWococoMessages.MessagesDelivered`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer + - BridgeHubRococo (see `bridgeWestendMessages.MessageAccepted`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer + - BridgeHubWestend (see `bridgeRococoMessages.MessagesReceived`, `xcmpQueue.XcmpMessageSent`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer + - AssetHubWestend (see `foreignAssets.Issued`, `xcmpQueue.Success`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9010#/explorer + - BridgeHubRocococ (see `bridgeWestendMessages.MessagesDelivered`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer + +Do reserve withdraw transfers: (when previous is finished) +``` +cd + +# wrappedWNDs from Rococo's Asset Hub to Westend's. +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-rococo-local +``` +``` +cd + +# wrappedROCs from Westend's Asset Hub to Rococo's. +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-westend-local +``` -### Claim relayer's rewards on BridgeHubRococo and BridgeHubWococo +### Claim relayer's rewards on BridgeHubRococo and BridgeHubWestend **Accounts of BridgeHub parachains:** - `//Charlie` is relayer account on BridgeHubRococo -- `//Charlie` is relayer account on BridgeHubWococo +- `//Charlie` is relayer account on BridgeHubWestend ``` cd -# Claim rewards on BridgeHubWococo: -./cumulus/scripts/bridges_rococo_wococo.sh claim-rewards-bridge-hub-rococo-local +# Claim rewards on BridgeHubWestend: +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-rococo-local -# Claim rewards on BridgeHubWococo: -./cumulus/scripts/bridges_rococo_wococo.sh claim-rewards-bridge-hub-wococo-local +# Claim rewards on BridgeHubWestend: +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-westend-local ``` - open explorers: (see zombienets) - BridgeHubRococo (see 2x `bridgeRelayers.RewardPaid`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8943#/explorer - - BridgeHubWococo (see 2x `bridgeRelayers.RewardPaid`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer + - BridgeHubWestend (see 2x `bridgeRelayers.RewardPaid`) https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:8945#/explorer ## How to test local BridgeHubKusama/BridgeHubPolkadot diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs deleted file mode 100644 index 991cba573bf2a64bf917f5c50a246354c7f82355..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/cumulus_pallet_xcmp_queue.rs +++ /dev/null @@ -1,77 +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 . - -//! Autogenerated weights for `cumulus_pallet_xcmp_queue` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/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: 5_129_000 picoseconds. - Weight::from_parts(5_367_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:1) - /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 5_050_000 picoseconds. - Weight::from_parts(5_565_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_multisig.rs deleted file mode 100644 index 96b2d859ed864d8a368bc258038890825c8b4d36..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_multisig.rs +++ /dev/null @@ -1,165 +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 . - -//! Autogenerated weights for `pallet_multisig` -//! -//! 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: `[]` -//! 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-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/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_602_000 picoseconds. - Weight::from_parts(14_565_036, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 8 - .saturating_add(Weight::from_parts(518, 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: 46_075_000 picoseconds. - Weight::from_parts(33_730_493, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_049 - .saturating_add(Weight::from_parts(134_211, 0).saturating_mul(s.into())) - // Standard Error: 10 - .saturating_add(Weight::from_parts(1_448, 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: 29_389_000 picoseconds. - Weight::from_parts(19_639_583, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 976 - .saturating_add(Weight::from_parts(106_598, 0).saturating_mul(s.into())) - // Standard Error: 9 - .saturating_add(Weight::from_parts(1_457, 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: 50_438_000 picoseconds. - Weight::from_parts(36_195_308, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_689 - .saturating_add(Weight::from_parts(176_067, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_545, 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_134_000 picoseconds. - Weight::from_parts(32_149_785, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_082 - .saturating_add(Weight::from_parts(145_390, 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: 17_560_000 picoseconds. - Weight::from_parts(18_144_079, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 763 - .saturating_add(Weight::from_parts(114_298, 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_360_000 picoseconds. - Weight::from_parts(33_566_579, 0) - .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_314 - .saturating_add(Weight::from_parts(126_583, 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/bridge-hubs/bridge-hub-kusama/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/tests/tests.rs deleted file mode 100644 index 893524e12f66230f5de2a48966344b8b93f08489..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/tests/tests.rs +++ /dev/null @@ -1,57 +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 . - -pub use bridge_hub_kusama_runtime::{ - xcm_config::XcmConfig, AllPalletsWithoutSystem, Balances, ExistentialDeposit, ParachainSystem, - PolkadotXcm, Runtime, RuntimeEvent, SessionKeys, -}; -use codec::Decode; -use frame_support::parameter_types; -use parachains_common::{kusama::fee::WeightToFee, AccountId, AuraId}; - -const ALICE: [u8; 32] = [1u8; 32]; - -parameter_types! { - pub CheckingAccount: AccountId = PolkadotXcm::check_account(); -} - -bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - CheckingAccount, - WeightToFee, - ParachainSystem, - bridge_hub_test_utils::CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } - ), - ExistentialDeposit::get(), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - 1002 -); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs deleted file mode 100644 index 98834cc44e880e432c13c4b1b3d8a5dfe3cbf624..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs +++ /dev/null @@ -1,77 +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 . - -//! Autogenerated weights for `cumulus_pallet_xcmp_queue` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/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: 5_043_000 picoseconds. - Weight::from_parts(5_211_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:1) - /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 5_011_000 picoseconds. - Weight::from_parts(5_171_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_collator_selection.rs deleted file mode 100644 index e0f4156fe4d4c4d3f31229bdaa6308456a5cabb7..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_collator_selection.rs +++ /dev/null @@ -1,225 +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 . - -//! Autogenerated weights for `pallet_collator_selection` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_collator_selection -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/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: `196 + b * (79 ±0)` - // Estimated: `1187 + b * (2555 ±0)` - // Minimum execution time: 14_735_000 picoseconds. - Weight::from_parts(11_846_916, 0) - .saturating_add(Weight::from_parts(0, 1187)) - // Standard Error: 8_592 - .saturating_add(Weight::from_parts(3_270_517, 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, 2555).saturating_mul(b.into())) - } - /// 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::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: 48_332_000 picoseconds. - Weight::from_parts(46_158_586, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 13_938 - .saturating_add(Weight::from_parts(174_493, 0).saturating_mul(b.into())) - // Standard Error: 2_642 - .saturating_add(Weight::from_parts(196_691, 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::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`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: 15_323_000 picoseconds. - Weight::from_parts(15_016_873, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_970 - .saturating_add(Weight::from_parts(199_160, 0).saturating_mul(b.into())) - .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: 7_393_000 picoseconds. - Weight::from_parts(7_723_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() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_426_000 picoseconds. - Weight::from_parts(7_783_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(4802), added: 5297, 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(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: `740 + c * (52 ±0)` - // Estimated: `6287 + c * (54 ±0)` - // Minimum execution time: 41_040_000 picoseconds. - Weight::from_parts(43_902_200, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_360 - .saturating_add(Weight::from_parts(211_897, 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, 54).saturating_mul(c.into())) - } - /// Storage: `CollatorSelection::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`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 `[3, 100]`. - fn leave_intent(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `334 + c * (49 ±0)` - // Estimated: `6287` - // Minimum execution time: 33_429_000 picoseconds. - Weight::from_parts(36_413_045, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_947 - .saturating_add(Weight::from_parts(177_461, 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: 45_300_000 picoseconds. - Weight::from_parts(46_280_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::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`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: `System::BlockWeight` (r:1 w:1) - /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:97 w:97) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `r` is `[1, 100]`. - /// The range of component `c` is `[1, 100]`. - fn new_session(r: u32, c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `2263 + c * (97 ±0) + r * (115 ±0)` - // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` - // Minimum execution time: 17_524_000 picoseconds. - Weight::from_parts(17_590_000, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 354_091 - .saturating_add(Weight::from_parts(15_829_767, 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, 2603).saturating_mul(r.into())) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_session.rs deleted file mode 100644 index 29bc576ebc80d13e02f52837a5eae57436f278ff..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_session.rs +++ /dev/null @@ -1,81 +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 . - -//! Autogenerated weights for `pallet_session` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_session -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/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: `297` - // Estimated: `3762` - // Minimum execution time: 16_905_000 picoseconds. - Weight::from_parts(17_310_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: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Session::KeyOwner` (r:0 w:1) - /// Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn purge_keys() -> Weight { - // Proof Size summary in bytes: - // Measured: `279` - // Estimated: `3744` - // Minimum execution time: 12_511_000 picoseconds. - Weight::from_parts(13_055_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/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_utility.rs deleted file mode 100644 index 5205e9fff8593dda91a9f4b1433c1c5ff4fb3e55..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_utility.rs +++ /dev/null @@ -1,102 +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 . - -//! Autogenerated weights for `pallet_utility` -//! -//! 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: `[]` -//! 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-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_utility -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/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: 6_810_000 picoseconds. - Weight::from_parts(6_290_871, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_678 - .saturating_add(Weight::from_parts(5_193_419, 0).saturating_mul(c.into())) - } - fn as_derivative() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 4_753_000 picoseconds. - Weight::from_parts(4_890_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_873_000 picoseconds. - Weight::from_parts(9_780_422, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_035 - .saturating_add(Weight::from_parts(5_473_943, 0).saturating_mul(c.into())) - } - fn dispatch_as() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 8_443_000 picoseconds. - Weight::from_parts(8_904_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_820_000 picoseconds. - Weight::from_parts(8_206_355, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_327 - .saturating_add(Weight::from_parts(5_187_839, 0).saturating_mul(c.into())) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs deleted file mode 100644 index f45f3936365281c8d592c4a79d8974fb8d468a90..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ /dev/null @@ -1,188 +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 . - -//! Autogenerated weights for `pallet_xcm_benchmarks::fungible` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-polkadot-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=bridge-hub-polkadot-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/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: 24_237_000 picoseconds. - Weight::from_parts(24_697_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: 52_269_000 picoseconds. - Weight::from_parts(53_848_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: `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: `223` - // Estimated: `6196` - // Minimum execution time: 77_611_000 picoseconds. - Weight::from_parts(82_634_000, 6196) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: `Benchmark::Override` (r:0 w:0) - // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) - pub fn reserve_asset_deposited() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `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 initiate_reserve_withdraw() -> Weight { - // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_506_000 picoseconds. - Weight::from_parts(30_269_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } - pub fn receive_teleported_asset() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_541_000 picoseconds. - Weight::from_parts(3_629_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_651_000 picoseconds. - Weight::from_parts(26_078_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: `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: `122` - // Estimated: `3593` - // Minimum execution time: 52_050_000 picoseconds. - Weight::from_parts(53_293_000, 3593) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `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 initiate_teleport() -> Weight { - // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 30_009_000 picoseconds. - Weight::from_parts(30_540_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/tests/tests.rs deleted file mode 100644 index 0be87bd46facfc079cda4a1ea4cf6b7a34114d75..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/tests/tests.rs +++ /dev/null @@ -1,57 +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 . - -pub use bridge_hub_polkadot_runtime::{ - xcm_config::XcmConfig, AllPalletsWithoutSystem, Balances, ExistentialDeposit, ParachainSystem, - PolkadotXcm, Runtime, RuntimeEvent, SessionKeys, -}; -use codec::Decode; -use frame_support::parameter_types; -use parachains_common::{polkadot::fee::WeightToFee, AccountId, AuraId}; - -const ALICE: [u8; 32] = [1u8; 32]; - -parameter_types! { - pub CheckingAccount: AccountId = PolkadotXcm::check_account(); -} - -bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - CheckingAccount, - WeightToFee, - ParachainSystem, - bridge_hub_test_utils::CollatorSessionKeys::new( - AccountId::from(ALICE), - AccountId::from(ALICE), - SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } - ), - ExistentialDeposit::get(), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - 1002 -); 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 5befb21c891165c78fb21b7fa166c7f04ce2fc0d..8a6823ea3ee834e8588d7262500aabd733bcbec2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -1,125 +1,160 @@ [package] name = "bridge-hub-rococo-runtime" -version = "0.1.0" +version = "0.5.0" authors.workspace = true 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 } [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +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.188", optional = true, features = ["derive"] } -smallvec = "1.11.0" +log = { workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # 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-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} +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-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-multisig = { path = "../../../../../substrate/frame/multisig", 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} +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} +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} +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 = "../../../../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-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, features = ["bridging"] } +cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = [ + "bridging", +] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } # Bridges bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hub-rococo", default-features = false } -bp-asset-hub-wococo = { path = "../../../../../bridges/primitives/chain-asset-hub-wococo", 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-wococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-wococo", 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 } bp-rococo = { path = "../../../../../bridges/primitives/chain-rococo", default-features = false } -bp-wococo = { path = "../../../../../bridges/primitives/chain-wococo", default-features = false } +bp-westend = { path = "../../../../../bridges/primitives/chain-westend", default-features = false } pallet-bridge-grandpa = { path = "../../../../../bridges/modules/grandpa", default-features = false } 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/primitives/beacon", default-features = false } +snowbridge-pallet-system = { path = "../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-system-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/system/runtime-api", default-features = false } +snowbridge-core = { path = "../../../../../bridges/snowbridge/primitives/core", default-features = false } +snowbridge-pallet-ethereum-client = { path = "../../../../../bridges/snowbridge/pallets/ethereum-client", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-outbound-queue-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-runtime-common = { path = "../../../../../bridges/snowbridge/runtime/runtime-common", default-features = false } + +bridge-hub-common = { path = "../common", default-features = false } + [dev-dependencies] static_assertions = "1.1" bridge-hub-test-utils = { path = "../test-utils" } -bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", features = ["integrity-test"] } +bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", features = [ + "integrity-test", +] } sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +snowbridge-runtime-test-common = { path = "../../../../../bridges/snowbridge/runtime/test-common" } [features] -default = [ "std" ] +default = ["std"] std = [ "bp-asset-hub-rococo/std", - "bp-asset-hub-wococo/std", + "bp-asset-hub-westend/std", + "bp-bridge-hub-polkadot/std", "bp-bridge-hub-rococo/std", - "bp-bridge-hub-wococo/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-wococo/std", + "bp-westend/std", + "bridge-hub-common/std", "bridge-runtime-common/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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", @@ -138,6 +173,7 @@ std = [ "pallet-bridge-parachains/std", "pallet-bridge-relayers/std", "pallet-collator-selection/std", + "pallet-message-queue/std", "pallet-multisig/std", "pallet-session/std", "pallet-timestamp/std", @@ -145,15 +181,25 @@ 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", - "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-outbound-queue-runtime-api/std", + "snowbridge-pallet-ethereum-client/std", + "snowbridge-pallet-inbound-queue/std", + "snowbridge-pallet-outbound-queue/std", + "snowbridge-pallet-system/std", + "snowbridge-router-primitives/std", + "snowbridge-runtime-common/std", + "snowbridge-system-runtime-api/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", @@ -169,16 +215,19 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/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", "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", @@ -190,14 +239,24 @@ runtime-benchmarks = [ "pallet-bridge-parachains/runtime-benchmarks", "pallet-bridge-relayers/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-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-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue/runtime-benchmarks", + "snowbridge-pallet-outbound-queue/runtime-benchmarks", + "snowbridge-pallet-system/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "snowbridge-runtime-common/runtime-benchmarks", + "snowbridge-runtime-test-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", @@ -205,7 +264,6 @@ 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", @@ -221,15 +279,26 @@ try-runtime = [ "pallet-bridge-parachains/try-runtime", "pallet-bridge-relayers/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-bridge-hub/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", + "snowbridge-pallet-ethereum-client/try-runtime", + "snowbridge-pallet-inbound-queue/try-runtime", + "snowbridge-pallet-outbound-queue/try-runtime", + "snowbridge-pallet-system/try-runtime", "sp-runtime/try-runtime", ] -experimental = [ "pallet-aura/experimental" ] +fast-runtime = [] + +# 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/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 a1fded8470c4c163d58f29cb592a66fc44696878..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,18 +21,21 @@ //! 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 RococoBridgeParachainPalletName: &'static str = "Paras"; - pub const MaxRococoParaHeadDataSize: u32 = bp_rococo::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE; - pub const WococoBridgeParachainPalletName: &'static str = "Paras"; - pub const MaxWococoParaHeadDataSize: u32 = bp_wococo::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE; + 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; pub const RelayerStakeLease: u32 = 8; @@ -41,50 +44,27 @@ parameter_types! { pub storage DeliveryRewardInBalance: u64 = 1_000_000; } -/// Add GRANDPA bridge pallet to track Wococo relay chain. -pub type BridgeGrandpaWococoInstance = pallet_bridge_grandpa::Instance1; -impl pallet_bridge_grandpa::Config for Runtime { +/// Add GRANDPA bridge pallet to track Westend relay chain. +pub type BridgeGrandpaWestendInstance = pallet_bridge_grandpa::Instance3; +impl pallet_bridge_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type BridgedChain = bp_wococo::Wococo; + type BridgedChain = bp_westend::Westend; type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; type HeadersToKeep = RelayChainHeadersToKeep; - type WeightInfo = weights::pallet_bridge_grandpa_wococo_finality::WeightInfo; + type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } -/// Add parachain bridge pallet to track Wococo BridgeHub parachain -pub type BridgeParachainWococoInstance = pallet_bridge_parachains::Instance1; -impl pallet_bridge_parachains::Config for Runtime { +/// Add parachain bridge pallet to track Westend BridgeHub parachain +pub type BridgeParachainWestendInstance = pallet_bridge_parachains::Instance3; +impl pallet_bridge_parachains::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_bridge_parachains_within_wococo::WeightInfo; - type BridgesGrandpaPalletInstance = BridgeGrandpaWococoInstance; - type ParasPalletName = WococoBridgeParachainPalletName; + type WeightInfo = weights::pallet_bridge_parachains::WeightInfo; + type BridgesGrandpaPalletInstance = BridgeGrandpaWestendInstance; + type ParasPalletName = WestendBridgeParachainPalletName; type ParaStoredHeaderDataBuilder = - SingleParaStoredHeaderDataBuilder; + SingleParaStoredHeaderDataBuilder; type HeadsToKeep = ParachainHeadsToKeep; - type MaxParaHeadDataSize = MaxWococoParaHeadDataSize; -} - -/// Add GRANDPA bridge pallet to track Rococo relay chain. -pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance2; -impl pallet_bridge_grandpa::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type BridgedChain = bp_rococo::Rococo; - type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; - type HeadersToKeep = RelayChainHeadersToKeep; - type WeightInfo = weights::pallet_bridge_grandpa_rococo_finality::WeightInfo; -} - -/// Add parachain bridge pallet to track Rococo BridgeHub parachain -pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance2; -impl pallet_bridge_parachains::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_bridge_parachains_within_rococo::WeightInfo; - type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; - type ParasPalletName = RococoBridgeParachainPalletName; - type ParaStoredHeaderDataBuilder = - SingleParaStoredHeaderDataBuilder; - type HeadsToKeep = ParachainHeadsToKeep; - type MaxParaHeadDataSize = MaxRococoParaHeadDataSize; + type MaxParaHeadDataSize = MaxWestendParaHeadDataSize; } /// Allows collect and claim rewards for relayers @@ -103,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_hub_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs deleted file mode 100644 index a02d8ad88e74706cd2672f9efb9b4552b4c8e201..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_rococo_config.rs +++ /dev/null @@ -1,317 +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 . - -//! Bridge definitions used on BridgeHub with the Rococo flavor. - -use crate::{ - bridge_common_config::{BridgeParachainWococoInstance, DeliveryRewardInBalance}, - weights, AccountId, BridgeRococoToWococoMessages, ParachainInfo, Runtime, RuntimeEvent, - RuntimeOrigin, XcmRouter, -}; -use bp_messages::LaneId; -use bridge_runtime_common::{ - messages, - messages::{ - source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, - target::{FromBridgedChainMessagesProof, SourceHeaderChainAdapter}, - MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, - }, - messages_xcm_extension::{ - SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, - XcmBlobMessageDispatch, - }, - refund_relayer_extension::{ - ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, - RefundableMessagesLane, RefundableParachain, - }, -}; - -use codec::Encode; -use frame_support::{parameter_types, traits::PalletInfoAccess}; -use sp_runtime::RuntimeDebug; -use xcm::{ - latest::prelude::*, - prelude::{InteriorMultiLocation, NetworkId}, -}; -use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; - -parameter_types! { - pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; - pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; - pub const BridgeHubWococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; - pub BridgeRococoToWococoMessagesPalletInstance: InteriorMultiLocation = X1(PalletInstance(::index() as u8)); - pub BridgeHubRococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(ParachainInfo::parachain_id().into())); - pub WococoGlobalConsensusNetwork: NetworkId = NetworkId::Wococo; - pub ActiveOutboundLanesToBridgeHubWococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO]; - // 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 FromAssetHubRococoToAssetHubWococoRoute: SenderAndLane = SenderAndLane::new( - ParentThen(X1(Parachain(AssetHubRococoParaId::get().into()))).into(), - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, - ); - - pub CongestedMessage: Xcm<()> = build_congestion_message(true).into(); - - pub UncongestedMessage: Xcm<()> = build_congestion_message(false).into(); -} - -fn build_congestion_message(is_congested: bool) -> sp_std::vec::Vec> { - sp_std::vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { - origin_kind: OriginKind::Xcm, - require_weight_at_most: - bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), - call: bp_asset_hub_rococo::Call::ToWococoXcmRouter( - bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { - bridge_id: Default::default(), - is_congested, - } - ) - .encode() - .into(), - } - ] -} - -/// Proof of messages, coming from Wococo. -pub type FromWococoBridgeHubMessagesProof = - FromBridgedChainMessagesProof; -/// Messages delivery proof for Rococo Bridge Hub -> Wococo Bridge Hub messages. -pub type ToWococoBridgeHubMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; - -/// Dispatches received XCM messages from other bridge -pub type OnBridgeHubRococoBlobDispatcher = BridgeBlobDispatcher< - XcmRouter, - BridgeHubRococoUniversalLocation, - BridgeRococoToWococoMessagesPalletInstance, ->; - -/// Export XCM messages to be relayed to the other side -pub type ToBridgeHubWococoHaulBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, - WococoGlobalConsensusNetwork, - (), ->; -pub struct ToBridgeHubWococoXcmBlobHauler; -impl XcmBlobHauler for ToBridgeHubWococoXcmBlobHauler { - type Runtime = Runtime; - type MessagesInstance = WithBridgeHubWococoMessagesInstance; - type SenderAndLane = FromAssetHubRococoToAssetHubWococoRoute; - - type ToSourceChainSender = crate::XcmRouter; - type CongestedMessage = CongestedMessage; - type UncongestedMessage = UncongestedMessage; -} -pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO: LaneId = LaneId([0, 0, 0, 1]); - -/// On messages delivered callback. -pub type OnMessagesDelivered = XcmBlobHaulerAdapter; - -/// Messaging Bridge configuration for BridgeHubRococo -> BridgeHubWococo -pub struct WithBridgeHubWococoMessageBridge; -impl MessageBridge for WithBridgeHubWococoMessageBridge { - const BRIDGED_MESSAGES_PALLET_NAME: &'static str = - bp_bridge_hub_rococo::WITH_BRIDGE_HUB_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_NAME; - type ThisChain = BridgeHubRococo; - type BridgedChain = BridgeHubWococo; - type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< - Runtime, - BridgeParachainWococoInstance, - bp_bridge_hub_wococo::BridgeHubWococo, - >; -} - -/// Message verifier for BridgeHubWococo messages sent from BridgeHubRococo -pub type ToBridgeHubWococoMessageVerifier = - messages::source::FromThisChainMessageVerifier; - -/// Maximal outbound payload size of BridgeHubRococo -> BridgeHubWococo messages. -pub type ToBridgeHubWococoMaximalOutboundPayloadSize = - messages::source::FromThisChainMaximalOutboundPayloadSize; - -/// BridgeHubWococo chain from message lane point of view. -#[derive(RuntimeDebug, Clone, Copy)] -pub struct BridgeHubWococo; - -impl UnderlyingChainProvider for BridgeHubWococo { - type Chain = bp_bridge_hub_wococo::BridgeHubWococo; -} - -impl messages::BridgedChainWithMessages for BridgeHubWococo {} - -/// 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 Wococo parachain. -pub type BridgeRefundBridgeHubWococoMessages = RefundSignedExtensionAdapter< - RefundBridgedParachainMessages< - Runtime, - RefundableParachain, - RefundableMessagesLane, - ActualFeeRefund, - PriorityBoostPerMessage, - StrBridgeRefundBridgeHubWococoMessages, - >, ->; -bp_runtime::generate_static_str_provider!(BridgeRefundBridgeHubWococoMessages); - -parameter_types! { - pub const BridgeHubWococoMessagesLane: bp_messages::LaneId = DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO; -} - -/// Add XCM messages support for BridgeHubRococo to support Rococo->Wococo XCM messages -pub type WithBridgeHubWococoMessagesInstance = pallet_bridge_messages::Instance1; -impl pallet_bridge_messages::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_bridge_messages_rococo_to_wococo::WeightInfo; - type BridgedChainId = BridgeHubWococoChainId; - type ActiveOutboundLanes = ActiveOutboundLanesToBridgeHubWococo; - type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; - type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; - - type MaximalOutboundPayloadSize = ToBridgeHubWococoMaximalOutboundPayloadSize; - type OutboundPayload = XcmAsPlainPayload; - - type InboundPayload = XcmAsPlainPayload; - type InboundRelayer = AccountId; - type DeliveryPayments = (); - - type TargetHeaderChain = TargetHeaderChainAdapter; - type LaneMessageVerifier = ToBridgeHubWococoMessageVerifier; - type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< - Runtime, - WithBridgeHubWococoMessagesInstance, - DeliveryRewardInBalance, - >; - - type SourceHeaderChain = SourceHeaderChainAdapter; - type MessageDispatch = XcmBlobMessageDispatch< - OnBridgeHubRococoBlobDispatcher, - Self::WeightInfo, - cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider< - AssetHubRococoParaId, - Runtime, - >, - >; - type OnMessagesDelivered = OnMessagesDelivered; -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::bridge_common_config::BridgeGrandpaWococoInstance; - use bridge_runtime_common::{ - assert_complete_bridge_types, - integrity::{ - assert_complete_bridge_constants, check_message_lane_weights, - AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, - AssertCompleteBridgeConstants, - }, - }; - 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, - WithBridgeHubWococoMessagesInstance, - >( - bp_bridge_hub_wococo::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: BridgeGrandpaWococoInstance, - with_bridged_chain_messages_instance: WithBridgeHubWococoMessagesInstance, - bridge: WithBridgeHubWococoMessageBridge, - this_chain: bp_rococo::Rococo, - bridged_chain: bp_wococo::Wococo, - ); - - assert_complete_bridge_constants::< - Runtime, - BridgeGrandpaWococoInstance, - WithBridgeHubWococoMessagesInstance, - WithBridgeHubWococoMessageBridge, - >(AssertCompleteBridgeConstants { - this_chain_constants: AssertChainConstants { - block_length: bp_bridge_hub_rococo::BlockLength::get(), - block_weights: bp_bridge_hub_rococo::BlockWeights::get(), - }, - messages_pallet_constants: AssertBridgeMessagesPalletConstants { - max_unrewarded_relayers_in_bridged_confirmation_tx: - bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, - max_unconfirmed_messages_in_bridged_confirmation_tx: - bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, - bridged_chain_id: bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID, - }, - pallet_names: AssertBridgePalletNames { - with_this_chain_messages_pallet_name: - bp_bridge_hub_rococo::WITH_BRIDGE_HUB_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_NAME, - with_bridged_chain_grandpa_pallet_name: bp_wococo::WITH_WOCOCO_GRANDPA_PALLET_NAME, - with_bridged_chain_messages_pallet_name: - bp_bridge_hub_wococo::WITH_BRIDGE_HUB_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_NAME, - }, - }); - - bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< - Runtime, - WithBridgeHubWococoMessagesInstance, - PriorityBoostPerMessage, - >(FEE_BOOST_PER_MESSAGE); - - assert_eq!( - BridgeRococoToWococoMessagesPalletInstance::get(), - X1(PalletInstance( - bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_INDEX - )) - ); - } -} 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..6dbf96edc2ab0360385b8e04bf1dc52732abd9ca --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -0,0 +1,289 @@ +// 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 bp_runtime::Chain; +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::{InteriorLocation, 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_polkadot_bulletin::PolkadotBulletin::ID; + /// Interior location (relative to this runtime) of the with-RococoBulletin messages pallet. + pub BridgeRococoToRococoBulletinMessagesPalletInstance: InteriorLocation = [ + PalletInstance(::index() as u8) + ].into(); + /// Rococo Bulletin Network identifier. + pub RococoBulletinGlobalConsensusNetwork: NetworkId = NetworkId::PolkadotBulletin; + /// Relative location of the Rococo Bulletin chain. + pub RococoBulletinGlobalConsensusNetworkLocation: Location = Location::new( + 2, + [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(Parachain(RococoPeopleParaId::get().into()).into()).into(), + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + ); + /// All active routes and their destinations. + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = 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; +} + +/// 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 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::Balance; + use testnet_parachains_constants::rococo; + + /// 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); + + let expected: InteriorLocation = PalletInstance( + bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX, + ) + .into(); + + assert_eq!(BridgeRococoToRococoBulletinMessagesPalletInstance::get(), expected,); + } +} diff --git a/cumulus/test/service/src/genesis.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs similarity index 56% rename from cumulus/test/service/src/genesis.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 9dba57ba9fbc1ff00059284ed1c92e1c7669d208..01a762d4b99f94f39e7a6050ff13a720002063bd 100644 --- a/cumulus/test/service/src/genesis.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -14,17 +14,14 @@ // You should have received a copy of the GNU General Public License // 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 crate::{xcm_config::UniversalLocation, Runtime}; +use snowbridge_router_primitives::outbound::EthereumBlobExporter; +use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; -/// Returns the initial head data for a parachain ID. -pub fn initial_head_data(para_id: ParaId) -> HeadData { - let spec = crate::chain_spec::get_chain_spec(para_id); - let block: Block = generate_genesis_block(&spec, sp_runtime::StateVersion::V1).unwrap(); - let genesis_state = block.header().encode(); - genesis_state.into() -} +/// Exports message to the Ethereum Gateway contract. +pub type SnowbridgeExporter = EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + snowbridge_pallet_outbound_queue::Pallet, + snowbridge_core::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 new file mode 100644 index 0000000000000000000000000000000000000000..5d55d7afbacfdb22f6939c88e87eaf64321945ff --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -0,0 +1,334 @@ +// 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 BridgeHubWestend. + +use crate::{ + bridge_common_config::{ + BridgeHubRococo, BridgeParachainWestendInstance, DeliveryRewardInBalance, + }, + weights, + xcm_config::UniversalLocation, + AccountId, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, XcmOverBridgeHubWestend, + XcmRouter, +}; +use bp_messages::LaneId; +use bp_runtime::Chain; +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, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, RefundableParachain, + }, +}; + +use codec::Encode; +use frame_support::{parameter_types, traits::PalletInfoAccess}; +use sp_runtime::RuntimeDebug; +use xcm::{ + latest::prelude::*, + prelude::{InteriorLocation, NetworkId}, +}; +use xcm_builder::BridgeBlobDispatcher; + +parameter_types! { + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const BridgeHubWestendChainId: bp_runtime::ChainId = BridgeHubWestend::ID; + pub BridgeRococoToWestendMessagesPalletInstance: InteriorLocation = [PalletInstance(::index() as u8)].into(); + pub WestendGlobalConsensusNetwork: NetworkId = NetworkId::Westend; + pub WestendGlobalConsensusNetworkLocation: Location = Location::new( + 2, + [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([Parachain(AssetHubRococoParaId::get().into())].into()).into(), + XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, + ); + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![ + ( + FromAssetHubRococoToAssetHubWestendRoute::get(), + (WestendGlobalConsensusNetwork::get(), [Parachain(AssetHubWestendParaId::get().into())].into()) + ) + ]; + + pub CongestedMessage: Xcm<()> = build_congestion_message(true).into(); + pub UncongestedMessage: Xcm<()> = build_congestion_message(false).into(); + + pub BridgeHubWestendLocation: Location = Location::new( + 2, + [ + GlobalConsensus(WestendGlobalConsensusNetwork::get()), + Parachain(::PARACHAIN_ID) + ] + ); +} +pub const XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND: LaneId = LaneId([0, 0, 0, 2]); + +fn build_congestion_message(is_congested: bool) -> sp_std::vec::Vec> { + sp_std::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: + bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(), + call: bp_asset_hub_rococo::Call::ToWestendXcmRouter( + bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested, + } + ) + .encode() + .into(), + } + ] +} + +/// Proof of messages, coming from Westend. +pub type FromWestendBridgeHubMessagesProof = + FromBridgedChainMessagesProof; +/// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. +pub type ToWestendBridgeHubMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof; + +/// Dispatches received XCM messages from other bridge +type FromWestendMessageBlobDispatcher = + BridgeBlobDispatcher; + +/// Export XCM messages to be relayed to the other side +pub type ToBridgeHubWestendHaulBlobExporter = XcmOverBridgeHubWestend; + +pub struct ToBridgeHubWestendXcmBlobHauler; +impl XcmBlobHauler for ToBridgeHubWestendXcmBlobHauler { + type Runtime = Runtime; + type MessagesInstance = WithBridgeHubWestendMessagesInstance; + type ToSourceChainSender = XcmRouter; + type CongestedMessage = CongestedMessage; + type UncongestedMessage = UncongestedMessage; +} + +/// On messages delivered callback. +type OnMessagesDeliveredFromWestend = + XcmBlobHaulerAdapter; + +/// Messaging Bridge configuration for BridgeHubRococo -> BridgeHubWestend +pub struct WithBridgeHubWestendMessageBridge; +impl MessageBridge for WithBridgeHubWestendMessageBridge { + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME; + type ThisChain = BridgeHubRococo; + type BridgedChain = BridgeHubWestend; + type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< + Runtime, + BridgeParachainWestendInstance, + bp_bridge_hub_westend::BridgeHubWestend, + >; +} + +/// Maximal outbound payload size of BridgeHubRococo -> BridgeHubWestend messages. +pub type ToBridgeHubWestendMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// BridgeHubWestend chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubWestend; + +impl UnderlyingChainProvider for BridgeHubWestend { + type Chain = bp_bridge_hub_westend::BridgeHubWestend; +} + +impl messages::BridgedChainWithMessages for BridgeHubWestend {} + +/// Signed extension that refunds relayers that are delivering messages from the Westend parachain. +pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = RefundSignedExtensionAdapter< + RefundBridgedParachainMessages< + Runtime, + RefundableParachain< + BridgeParachainWestendInstance, + bp_bridge_hub_westend::BridgeHubWestend, + >, + RefundableMessagesLane< + WithBridgeHubWestendMessagesInstance, + AssetHubRococoToAssetHubWestendMessagesLane, + >, + ActualFeeRefund, + PriorityBoostPerMessage, + StrOnBridgeHubRococoRefundBridgeHubWestendMessages, + >, +>; +bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundBridgeHubWestendMessages); + +/// Add XCM messages support for BridgeHubRococo to support Rococo->Westend XCM messages +pub type WithBridgeHubWestendMessagesInstance = pallet_bridge_messages::Instance3; +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = weights::pallet_bridge_messages_rococo_to_westend::WeightInfo; + type BridgedChainId = BridgeHubWestendChainId; + type ActiveOutboundLanes = ActiveOutboundLanesToBridgeHubWestend; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = ToBridgeHubWestendMaximalOutboundPayloadSize; + type OutboundPayload = XcmAsPlainPayload; + + type InboundPayload = XcmAsPlainPayload; + type InboundRelayer = AccountId; + type DeliveryPayments = (); + + type TargetHeaderChain = TargetHeaderChainAdapter; + type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< + Runtime, + WithBridgeHubWestendMessagesInstance, + DeliveryRewardInBalance, + >; + + type SourceHeaderChain = SourceHeaderChainAdapter; + type MessageDispatch = XcmBlobMessageDispatch< + FromWestendMessageBlobDispatcher, + Self::WeightInfo, + cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider< + AssetHubRococoParaId, + Runtime, + >, + >; + 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::*; + use crate::bridge_common_config::BridgeGrandpaWestendInstance; + use bridge_runtime_common::{ + assert_complete_bridge_types, + integrity::{ + assert_complete_bridge_constants, check_message_lane_weights, + AssertBridgeMessagesPalletConstants, AssertBridgePalletNames, AssertChainConstants, + AssertCompleteBridgeConstants, + }, + }; + use parachains_common::Balance; + use testnet_parachains_constants::rococo; + + /// 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, + WithBridgeHubWestendMessagesInstance, + >( + bp_bridge_hub_westend::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: BridgeGrandpaWestendInstance, + with_bridged_chain_messages_instance: WithBridgeHubWestendMessagesInstance, + bridge: WithBridgeHubWestendMessageBridge, + this_chain: bp_rococo::Rococo, + bridged_chain: bp_westend::Westend, + ); + + assert_complete_bridge_constants::< + Runtime, + BridgeGrandpaWestendInstance, + WithBridgeHubWestendMessagesInstance, + WithBridgeHubWestendMessageBridge, + >(AssertCompleteBridgeConstants { + this_chain_constants: AssertChainConstants { + block_length: bp_bridge_hub_rococo::BlockLength::get(), + block_weights: bp_bridge_hub_rococo::BlockWeightsForAsyncBacking::get(), + }, + messages_pallet_constants: AssertBridgeMessagesPalletConstants { + max_unrewarded_relayers_in_bridged_confirmation_tx: + bp_bridge_hub_westend::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + max_unconfirmed_messages_in_bridged_confirmation_tx: + bp_bridge_hub_westend::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + bridged_chain_id: BridgeHubWestend::ID, + }, + pallet_names: AssertBridgePalletNames { + with_this_chain_messages_pallet_name: + bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME, + with_bridged_chain_grandpa_pallet_name: + bp_westend::WITH_WESTEND_GRANDPA_PALLET_NAME, + with_bridged_chain_messages_pallet_name: + bp_bridge_hub_westend::WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME, + }, + }); + + bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + Runtime, + WithBridgeHubWestendMessagesInstance, + PriorityBoostPerMessage, + >(FEE_BOOST_PER_MESSAGE); + + let expected: InteriorLocation = [PalletInstance( + bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX, + )] + .into(); + + assert_eq!(BridgeRococoToWestendMessagesPalletInstance::get(), expected,); + } +} 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 5973801d37c4b2f4dd6e734cc84fdbdc0424b6df..bbafcd3c7ddd3d84ab2eae9fc4ec05e93e654262 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 @@ -16,11 +16,9 @@ //! # Bridge Hub Rococo Runtime //! -//! This runtime is also used for Bridge Hub Wococo. But we dont want to create another exact copy -//! of Bridge Hub Rococo, so we injected some tweaks backed by `RuntimeFlavor` and `pub storage -//! Flavor: RuntimeFlavor`. (For example this is needed for successful asset transfer between Asset -//! Hub Rococo and Asset Hub Wococo, where we need to have correct `xcm_config::UniversalLocation` -//! with correct `GlobalConsensus`. +//! 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. @@ -31,20 +29,25 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod bridge_common_config; -pub mod bridge_hub_rococo_config; -pub mod bridge_hub_wococo_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 codec::{Decode, Encode}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_core::{ + gwei, meth, outbound::Message, AgentId, AllowSiblingsOnly, PricingParameters, Rewards, +}; +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::{AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::{Block as BlockT, Keccak256}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, FixedU128, }; use sp_std::prelude::*; @@ -52,12 +55,13 @@ use sp_std::prelude::*; use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use cumulus_primitives_core::ParaId; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, Everything}, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -65,40 +69,39 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; -use xcm_config::{XcmConfig, XcmOriginToTransactDispatchOrigin}; +use testnet_parachains_constants::rococo::{ + consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, time::*, +}; use bp_runtime::HeaderId; +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::VersionedLocation; +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}; -use crate::{ - bridge_hub_rococo_config::BridgeRefundBridgeHubWococoMessages, - bridge_hub_wococo_config::BridgeRefundBridgeHubRococoMessages, xcm_config::XcmRouter, -}; use parachains_common::{ - impls::DealWithFees, - 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, + impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; -use xcm_executor::XcmExecutor; - -/// Enum for handling differences in the runtime configuration for BridgeHubRococo vs -/// BridgeHubWococo. -#[derive(Default, Eq, PartialEq, Debug, Clone, Copy, Decode, Encode)] -pub enum RuntimeFlavor { - #[default] - Rococo, - Wococo, -} + +use polkadot_runtime_common::prod_or_fast; + +#[cfg(feature = "runtime-benchmarks")] +use benchmark_helpers::DoNothingRouter; /// The address format for describing accounts. pub type Address = MultiAddress; @@ -123,7 +126,10 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, BridgeRejectObsoleteHeadersAndMessages, - (BridgeRefundBridgeHubRococoMessages, BridgeRefundBridgeHubWococoMessages), + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, + ), ); /// Unchecked extrinsic type as expected by this runtime. @@ -135,6 +141,15 @@ pub type Migrations = ( pallet_collator_selection::migration::v1::MigrateToV1, pallet_multisig::migrations::v1::MigrateToV1, InitStorageVersions, + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // unreleased + snowbridge_pallet_system::migration::v0::InitializeOnUpgrade< + Runtime, + ConstU32, + ConstU32, + >, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. @@ -153,12 +168,12 @@ impl frame_support::traits::OnRuntimeUpgrade for InitStorageVersions { let mut writes = 0; if PolkadotXcm::on_chain_storage_version() == StorageVersion::new(0) { - PolkadotXcm::current_storage_version().put::(); + PolkadotXcm::in_code_storage_version().put::(); writes.saturating_inc(); } if Balances::on_chain_storage_version() == StorageVersion::new(0) { - Balances::current_storage_version().put::(); + Balances::in_code_storage_version().put::(); writes.saturating_inc(); } @@ -187,10 +202,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: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 3, + transaction_version: 4, state_version: 1, }; @@ -227,41 +242,24 @@ parameter_types! { // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; /// The index type for storing how many extrinsics an account has signed. type Nonce = Nonce; /// The type for hashing blocks and tries. type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; /// The block type. type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; /// Runtime version. type Version = Version; - /// Converts a module to an index of this module in the runtime. - type PalletInfo = PalletInfo; /// The data to be stored in an account. type AccountData = pallet_balances::AccountData; - /// What to do if a new account is created. - type OnNewAccount = (); - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); /// The weight of database operations that the runtime can invoke. type DbWeight = RocksDbWeight; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; /// Weight information for the extrinsics of this pallet. type SystemWeightInfo = weights::frame_system::WeightInfo; /// Block & extrinsics weights: base values and limits. @@ -279,7 +277,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -305,8 +303,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -331,30 +329,69 @@ parameter_types! { } 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 OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; + 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 CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} +parameter_types! { + /// 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; + // Use the NoopMessageProcessor exclusively for benchmarks, not for tests with the + // runtime-benchmarks feature as tests require the BridgeHubMessageRouter to process messages. + // The "test" feature flag doesn't work, hence the reliance on the "std" feature, which is + // enabled during tests. + #[cfg(all(not(feature = "std"), feature = "runtime-benchmarks"))] + type MessageProcessor = + pallet_message_queue::mock_helpers::NoopMessageProcessor; + #[cfg(not(all(not(feature = "std"), feature = "runtime-benchmarks")))] + type MessageProcessor = bridge_hub_common::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: + 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! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(xcm_config::TokenLocation::get()); + pub FeeAssetId: AssetId = AssetId(xcm_config::TokenLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -368,20 +405,19 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } pub const PERIOD: u32 = 6 * HOURS; @@ -405,9 +441,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -457,108 +492,314 @@ impl pallet_utility::Config for Runtime { type WeightInfo = weights::pallet_utility::WeightInfo; } +// Ethereum Bridge +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 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")] +pub mod benchmark_helpers { + use crate::{EthereumBeaconClient, Runtime, RuntimeOrigin}; + use codec::Encode; + use snowbridge_beacon_primitives::CompactExecutionHeader; + use snowbridge_pallet_inbound_queue::BenchmarkHelper; + use sp_core::H256; + use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; + + impl BenchmarkHelper for Runtime { + fn initialize_storage(block_hash: H256, header: CompactExecutionHeader) { + EthereumBeaconClient::store_execution_header(block_hash, header, 0, H256::default()) + } + } + + pub struct DoNothingRouter; + impl SendXcm for DoNothingRouter { + type Ticket = Xcm<()>; + + fn validate( + _dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + Ok((xcm.clone().unwrap(), Assets::new())) + } + fn deliver(xcm: Xcm<()>) -> Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } + } + + impl snowbridge_pallet_system::BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } + } +} + +impl snowbridge_pallet_inbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_pallet_ethereum_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, + ConstU8, + AccountId, + Balance, + >; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type MaxMessageSize = ConstU32<2048>; + type WeightInfo = weights::snowbridge_pallet_inbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type AssetTransactor = ::AssetTransactor; +} + +impl snowbridge_pallet_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_pallet_outbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type Channels = EthereumSystem; +} + +#[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [0, 0, 0, 0], // 0x00000000 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 0], // 0x01000000 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 0], // 0x02000000 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 0], // 0x03000000 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 0], // 0x04000000 + epoch: 0, + } + }; +} + +#[cfg(not(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test)))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [144, 0, 0, 111], // 0x90000069 + epoch: 0, + }, + altair: Fork { + version: [144, 0, 0, 112], // 0x90000070 + epoch: 50, + }, + bellatrix: Fork { + version: [144, 0, 0, 113], // 0x90000071 + epoch: 100, + }, + capella: Fork { + version: [144, 0, 0, 114], // 0x90000072 + epoch: 56832, + }, + deneb: Fork { + version: [144, 0, 0, 115], // 0x90000073 + epoch: 132608, + }, + }; +} + +parameter_types! { + pub const MaxExecutionHeadersToKeep: u32 = prod_or_fast!(8192 * 2, 1000); +} + +impl snowbridge_pallet_ethereum_client::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type MaxExecutionHeadersToKeep = MaxExecutionHeadersToKeep; + type WeightInfo = weights::snowbridge_pallet_ethereum_client::WeightInfo; +} + +impl snowbridge_pallet_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = EthereumOutboundQueue; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type TreasuryAccount = TreasuryAccount; + type Token = Balances; + type WeightInfo = weights::snowbridge_pallet_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 { // 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 2, + ParachainInfo: parachain_info = 3, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, // Collator support. The order of these 4 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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 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, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, // Handy utilities. - Utility: pallet_utility::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 36, - - // Rococo and Wococo BridgeHubs are sharing the runtime, so this runtime has two sets of - // bridge pallets. Both are deployed at both runtimes, but only one set is actually used - // at particular runtime. - - // With-Wococo bridge modules that are active (used) at Rococo BridgeHub runtime. - BridgeWococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Event, Config} = 41, - BridgeWococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 42, - BridgeRococoToWococoMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 46, - - // With-Rococo bridge modules that are active (used) at Wococo BridgeHub runtime. - BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Event, Config} = 43, - BridgeRococoParachain: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 44, - BridgeWococoToRococoMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 45, - - BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event} = 47, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 36, + + // Bridge relayers pallet, used by several bridges here. + BridgeRelayers: pallet_bridge_relayers = 47, + + // With-Westend GRANDPA bridge module. + BridgeWestendGrandpa: pallet_bridge_grandpa:: = 48, + // With-Westend parachain bridge module. + BridgeWestendParachains: pallet_bridge_parachains:: = 49, + // With-Westend messaging bridge module. + BridgeWestendMessages: pallet_bridge_messages:: = 51, + // With-Westend bridge hub pallet. + XcmOverBridgeHubWestend: pallet_xcm_bridge_hub:: = 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:: = 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:: = 61, + // With-Rococo Bulletin bridge hub pallet. + XcmOverPolkadotBulletin: pallet_xcm_bridge_hub:: = 62, + + EthereumInboundQueue: snowbridge_pallet_inbound_queue = 80, + EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, + EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, + EthereumSystem: snowbridge_pallet_system = 83, + + // Message Queue. Importantly, is registered last so that messages are processed after + // the `on_initialize` hooks of bridging pallets. + MessageQueue: pallet_message_queue = 175, } ); +/// 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 - BridgeRococoGrandpa, BridgeWococoGrandpa, + BridgeWestendGrandpa, + BridgeRococoBulletinGrandpa, // Parachains - BridgeRococoParachain, BridgeWococoParachain, + BridgeWestendParachains, // Messages - BridgeWococoToRococoMessages, BridgeRococoToWococoMessages + BridgeWestendMessages, + BridgeRococoBulletinMessages } -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] // XCM - [pallet_xcm, PolkadotXcm] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] - // Bridge pallets at Rococo - [pallet_bridge_grandpa, WococoFinality] - [pallet_bridge_parachains, WithinWococo] - [pallet_bridge_messages, RococoToWococo] - // Bridge pallets at Wococo - [pallet_bridge_grandpa, RococoFinality] - [pallet_bridge_parachains, WithinRococo] - [pallet_bridge_messages, WococoToRococo] - // Bridge relayer pallets + // Bridge pallets + [pallet_bridge_grandpa, WestendFinality] + [pallet_bridge_parachains, WithinWestend] + [pallet_bridge_messages, RococoToWestend] + [pallet_bridge_messages, RococoToRococoBulletin] [pallet_bridge_relayers, BridgeRelayersBench::] + // Ethereum Bridge + [snowbridge_pallet_inbound_queue, EthereumInboundQueue] + [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] + [snowbridge_pallet_system, EthereumSystem] + [snowbridge_pallet_ethereum_client, EthereumBeaconClient] ); } impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -571,7 +812,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -695,58 +936,39 @@ impl_runtime_apis! { } } - impl bp_rococo::RococoFinalityApi for Runtime { - fn best_finalized() -> Option> { - BridgeRococoGrandpa::best_finalized() + impl bp_westend::WestendFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeWestendGrandpa::best_finalized() } fn synced_headers_grandpa_info( - ) -> Vec> { - BridgeRococoGrandpa::synced_headers_grandpa_info() + ) -> Vec> { + BridgeWestendGrandpa::synced_headers_grandpa_info() } } - impl bp_wococo::WococoFinalityApi for Runtime { - fn best_finalized() -> Option> { - BridgeWococoGrandpa::best_finalized() - } - fn synced_headers_grandpa_info( - ) -> Vec> { - BridgeWococoGrandpa::synced_headers_grandpa_info() - } - } - - - impl bp_bridge_hub_rococo::BridgeHubRococoFinalityApi for Runtime { + impl bp_bridge_hub_westend::BridgeHubWestendFinalityApi for Runtime { fn best_finalized() -> Option> { - BridgeRococoParachain::best_parachain_head_id::< - bp_bridge_hub_rococo::BridgeHubRococo - >().unwrap_or(None) - } - } - - impl bp_bridge_hub_wococo::BridgeHubWococoFinalityApi for Runtime { - fn best_finalized() -> Option> { - BridgeWococoParachain::best_parachain_head_id::< - bp_bridge_hub_wococo::BridgeHubWococo + BridgeWestendParachains::best_parachain_head_id::< + bp_bridge_hub_westend::BridgeHubWestend >().unwrap_or(None) } } // This is exposed by BridgeHubRococo - impl bp_bridge_hub_wococo::FromBridgeHubWococoInboundLaneApi for Runtime { + impl bp_bridge_hub_westend::FromBridgeHubWestendInboundLaneApi 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_hub_rococo_config::WithBridgeHubWococoMessagesInstance, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, >(lane, messages) } } // This is exposed by BridgeHubRococo - impl bp_bridge_hub_wococo::ToBridgeHubWococoOutboundLaneApi for Runtime { + impl bp_bridge_hub_westend::ToBridgeHubWestendOutboundLaneApi for Runtime { fn message_details( lane: bp_messages::LaneId, begin: bp_messages::MessageNonce, @@ -754,26 +976,35 @@ impl_runtime_apis! { ) -> Vec { bridge_runtime_common::messages_api::outbound_message_details::< Runtime, - bridge_hub_rococo_config::WithBridgeHubWococoMessagesInstance, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, >(lane, begin, end) } } - // This is exposed by BridgeHubWococo - impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi for Runtime { + 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_hub_wococo_config::WithBridgeHubRococoMessagesInstance, + bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, >(lane, messages) } } - // This is exposed by BridgeHubWococo - impl bp_bridge_hub_rococo::ToBridgeHubRococoOutboundLaneApi for Runtime { + impl bp_polkadot_bulletin::ToPolkadotBulletinOutboundLaneApi for Runtime { fn message_details( lane: bp_messages::LaneId, begin: bp_messages::MessageNonce, @@ -781,11 +1012,27 @@ impl_runtime_apis! { ) -> Vec { bridge_runtime_common::messages_api::outbound_message_details::< Runtime, - bridge_hub_wococo_config::WithBridgeHubRococoMessagesInstance, + 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_pallet_outbound_queue::api::prove_message::(leaf_index) + } + + fn calculate_fee(message: Message) -> Option { + snowbridge_pallet_outbound_queue::api::calculate_fee::(message) + } + } + + impl snowbridge_system_runtime_api::ControlApi for Runtime { + fn agent_id(location: VersionedLocation) -> Option { + snowbridge_pallet_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) { @@ -815,6 +1062,7 @@ impl_runtime_apis! { 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 @@ -824,12 +1072,10 @@ impl_runtime_apis! { use pallet_bridge_relayers::benchmarking::Pallet as BridgeRelayersBench; // Change weight file names. - type WococoFinality = BridgeWococoGrandpa; - type RococoFinality = BridgeRococoGrandpa; - type WithinWococo = pallet_bridge_parachains::benchmarking::Pallet::; - type WithinRococo = pallet_bridge_parachains::benchmarking::Pallet::; - type RococoToWococo = pallet_bridge_messages::benchmarking::Pallet ::; - type WococoToRococo = pallet_bridge_messages::benchmarking::Pallet ::; + 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); @@ -859,11 +1105,59 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between BH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled on BH. + None + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + use xcm::latest::prelude::*; use xcm_config::TokenLocation; parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( + pub ExistentialDepositAsset: Option = Some(( TokenLocation::get(), ExistentialDeposit::get() ).into()); @@ -873,18 +1167,18 @@ impl_runtime_apis! { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - XcmConfig, - ExistentialDepositMultiAsset, + xcm_config::XcmConfig, + ExistentialDepositAsset, xcm_config::PriceForParentDelivery, >; - fn valid_destination() -> Result { + fn valid_destination() -> Result { Ok(TokenLocation::get()) } - fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> Assets { // just concrete assets according to relay chain. - let assets: Vec = vec![ - MultiAsset { - id: Concrete(TokenLocation::get()), + let assets: Vec = vec![ + Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), } ]; @@ -893,12 +1187,12 @@ impl_runtime_apis! { } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( TokenLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(TokenLocation::get()) }, + Asset { fun: Fungible(UNITS), id: AssetId(TokenLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -908,9 +1202,9 @@ impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(TokenLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(UNITS), } } @@ -924,39 +1218,67 @@ impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { Err(BenchmarkError::Skip) } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { Ok((TokenLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(TokenLocation::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { let origin = TokenLocation::get(); - let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + let assets: Assets = (AssetId(TokenLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(TokenLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { - Ok((TokenLocation::get(), NetworkId::Wococo, X1(Parachain(100)))) + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + // 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, + [Parachain(bridge_to_westend_config::AssetHubWestendParaId::get().into())].into() + ) + ) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { Err(BenchmarkError::Skip) } } @@ -964,15 +1286,15 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; - type WococoFinality = BridgeWococoGrandpa; - type RococoFinality = BridgeRococoGrandpa; - type WithinWococo = pallet_bridge_parachains::benchmarking::Pallet::; - type WithinRococo = pallet_bridge_parachains::benchmarking::Pallet::; - type RococoToWococo = pallet_bridge_messages::benchmarking::Pallet ::; - type WococoToRococo = pallet_bridge_messages::benchmarking::Pallet ::; + 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, }; @@ -982,10 +1304,10 @@ impl_runtime_apis! { MessageProofParams, }; - impl BridgeMessagesConfig for Runtime { + impl BridgeMessagesConfig for Runtime { fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { - let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_runtime::BRIDGE_HUB_WOCOCO_CHAIN_ID; + let bench_lane_id = >::bench_lane_id(); + let bridged_chain_id = bridge_to_westend_config::BridgeHubWestendChainId::get(); pallet_bridge_relayers::Pallet::::relayer_reward( relayer, bp_relayers::RewardsAccountParams::new( @@ -998,24 +1320,24 @@ impl_runtime_apis! { fn prepare_message_proof( params: MessageProofParams, - ) -> (bridge_hub_rococo_config::FromWococoBridgeHubMessagesProof, Weight) { + ) -> (bridge_to_westend_config::FromWestendBridgeHubMessagesProof, 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_parachain::< Runtime, - bridge_common_config::BridgeGrandpaWococoInstance, - bridge_hub_rococo_config::WithBridgeHubWococoMessageBridge, - >(params, generate_xcm_builder_bridge_message_sample(X2(GlobalConsensus(Rococo), Parachain(42)))) + bridge_common_config::BridgeGrandpaWestendInstance, + bridge_to_westend_config::WithBridgeHubWestendMessageBridge, + >(params, generate_xcm_builder_bridge_message_sample([GlobalConsensus(Rococo), Parachain(42)].into())) } fn prepare_message_delivery_proof( params: MessageDeliveryProofParams, - ) -> bridge_hub_rococo_config::ToWococoBridgeHubMessagesDeliveryProof { + ) -> bridge_to_westend_config::ToWestendBridgeHubMessagesDeliveryProof { prepare_message_delivery_proof_from_parachain::< Runtime, - bridge_common_config::BridgeGrandpaWococoInstance, - bridge_hub_rococo_config::WithBridgeHubWococoMessageBridge, + bridge_common_config::BridgeGrandpaWestendInstance, + bridge_to_westend_config::WithBridgeHubWestendMessageBridge, >(params) } @@ -1025,40 +1347,32 @@ impl_runtime_apis! { } } - impl BridgeMessagesConfig for Runtime { - fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { - let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; - pallet_bridge_relayers::Pallet::::relayer_reward( - relayer, - bp_relayers::RewardsAccountParams::new( - bench_lane_id, - bridged_chain_id, - bp_relayers::RewardsAccountOwner::BridgedChain - ) - ).is_some() + 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_hub_wococo_config::FromRococoBridgeHubMessagesProof, Weight) { + ) -> (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_parachain::< + prepare_message_proof_from_grandpa_chain::< Runtime, - bridge_common_config::BridgeGrandpaRococoInstance, - bridge_hub_wococo_config::WithBridgeHubRococoMessageBridge, - >(params, generate_xcm_builder_bridge_message_sample(X2(GlobalConsensus(Wococo), Parachain(42)))) + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::WithRococoBulletinMessageBridge, + >(params, generate_xcm_builder_bridge_message_sample([GlobalConsensus(Rococo), Parachain(42)].into())) } fn prepare_message_delivery_proof( params: MessageDeliveryProofParams, - ) -> bridge_hub_wococo_config::ToRococoBridgeHubMessagesDeliveryProof { - prepare_message_delivery_proof_from_parachain::< + ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { + prepare_message_delivery_proof_from_grandpa_chain::< Runtime, - bridge_common_config::BridgeGrandpaRococoInstance, - bridge_hub_wococo_config::WithBridgeHubRococoMessageBridge, + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::WithRococoBulletinMessageBridge, >(params) } @@ -1075,34 +1389,10 @@ impl_runtime_apis! { Config as BridgeRelayersConfig, }; - impl BridgeParachainsConfig for Runtime { - fn parachains() -> Vec { - use bp_runtime::Parachain; - vec![bp_polkadot_core::parachains::ParaId(bp_bridge_hub_wococo::BridgeHubWococo::PARACHAIN_ID)] - } - - fn prepare_parachain_heads_proof( - parachains: &[bp_polkadot_core::parachains::ParaId], - parachain_head_size: u32, - proof_size: bp_runtime::StorageProofSize, - ) -> ( - pallet_bridge_parachains::RelayBlockNumber, - pallet_bridge_parachains::RelayBlockHash, - bp_polkadot_core::parachains::ParaHeadsProof, - Vec<(bp_polkadot_core::parachains::ParaId, bp_polkadot_core::parachains::ParaHash)>, - ) { - prepare_parachain_heads_proof::( - parachains, - parachain_head_size, - proof_size, - ) - } - } - - impl BridgeParachainsConfig for Runtime { + impl BridgeParachainsConfig for Runtime { fn parachains() -> Vec { use bp_runtime::Parachain; - vec![bp_polkadot_core::parachains::ParaId(bp_bridge_hub_rococo::BridgeHubRococo::PARACHAIN_ID)] + vec![bp_polkadot_core::parachains::ParaId(bp_bridge_hub_westend::BridgeHubWestend::PARACHAIN_ID)] } fn prepare_parachain_heads_proof( @@ -1115,7 +1405,7 @@ impl_runtime_apis! { bp_polkadot_core::parachains::ParaHeadsProof, Vec<(bp_polkadot_core::parachains::ParaId, bp_polkadot_core::parachains::ParaHash)>, ) { - prepare_parachain_heads_proof::( + prepare_parachain_heads_proof::( parachains, parachain_head_size, proof_size, @@ -1204,11 +1494,12 @@ mod tests { pallet_transaction_payment::ChargeTransactionPayment::from(10), BridgeRejectObsoleteHeadersAndMessages, ( - BridgeRefundBridgeHubRococoMessages::default(), - BridgeRefundBridgeHubWococoMessages::default(), - ), + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), + ) ); + // for BridgeHubRococo { let bhr_indirect_payload = bp_bridge_hub_rococo::SignedExtension::from_params( VERSION.spec_version, @@ -1225,23 +1516,6 @@ mod tests { bhr_indirect_payload.additional_signed().unwrap().encode() ) } - - { - let bhw_indirect_payload = bp_bridge_hub_rococo::SignedExtension::from_params( - VERSION.spec_version, - VERSION.transaction_version, - bp_runtime::TransactionEra::Immortal, - System::block_hash(BlockNumber::zero()), - 10, - 10, - (((), ()), ((), ())), - ); - assert_eq!(payload.encode(), bhw_indirect_payload.encode()); - assert_eq!( - payload.additional_signed().unwrap().encode(), - bhw_indirect_payload.additional_signed().unwrap().encode() - ) - } }); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc480c391636a92aad6e303a515524b5ce7ff2b1 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,80 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: 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 +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights +// --steps +// 50 +// --repeat +// 20 + +#![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_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)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs index 0106d6398f45938ae89603622db23abf300ec3ca..70fc3617bce93e2fa9246a649cd3adf81c1cda23 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -1,43 +1,38 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 `cumulus_pallet_xcmp_queue` //! //! 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-09-15, 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: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// ./target/release/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// bridge-hub-rococo-dev +// --output +// cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs +// --extrinsic +// #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,22 +51,98 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 4_930_000 picoseconds. - Weight::from_parts(5_292_000, 0) + // 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:1) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { + /// 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: 5_012_000 picoseconds. - Weight::from_parts(5_282_000, 0) + // 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/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 3dec4cc7f182c9aede28084122747dca63b24431..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 @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -152,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 c41900097a1b64120cd5a71ea87373abc212fd3c..aac39a4564fb600d9c4f623aa3ba27c78fc8f5fc 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 @@ -17,19 +17,22 @@ //! Expose the auto generated weight files. +use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; +use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; + 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_bridge_grandpa_rococo_finality; -pub mod pallet_bridge_grandpa_wococo_finality; -pub mod pallet_bridge_messages_rococo_to_wococo; -pub mod pallet_bridge_messages_wococo_to_rococo; -pub mod pallet_bridge_parachains_within_rococo; -pub mod pallet_bridge_parachains_within_wococo; +pub mod pallet_bridge_grandpa; +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; +pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_session; pub mod pallet_timestamp; @@ -37,11 +40,14 @@ pub mod pallet_utility; pub mod pallet_xcm; pub mod paritydb_weights; pub mod rocksdb_weights; +pub mod snowbridge_pallet_ethereum_client; +pub mod snowbridge_pallet_inbound_queue; +pub mod snowbridge_pallet_outbound_queue; +pub mod snowbridge_pallet_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; @@ -50,11 +56,11 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; -impl pallet_bridge_messages::WeightInfoExt - for pallet_bridge_messages_wococo_to_rococo::WeightInfo +impl MessagesWeightInfoExt + for pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo { fn expected_extra_storage_proof_size() -> u32 { - bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE + bp_polkadot_bulletin::EXTRA_STORAGE_PROOF_SIZE } fn receive_messages_proof_overhead_from_runtime() -> Weight { @@ -67,11 +73,11 @@ impl pallet_bridge_messages::WeightInfoExt } } -impl pallet_bridge_messages::WeightInfoExt - for pallet_bridge_messages_rococo_to_wococo::WeightInfo +impl MessagesWeightInfoExt + for pallet_bridge_messages_rococo_to_westend::WeightInfo { fn expected_extra_storage_proof_size() -> u32 { - bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE + bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE } fn receive_messages_proof_overhead_from_runtime() -> Weight { @@ -84,18 +90,8 @@ impl pallet_bridge_messages::WeightInfoExt } } -impl pallet_bridge_parachains::WeightInfoExt - for pallet_bridge_parachains_within_rococo::WeightInfo -{ - fn expected_extra_storage_proof_size() -> u32 { - bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE - } -} - -impl pallet_bridge_parachains::WeightInfoExt - for pallet_bridge_parachains_within_wococo::WeightInfo -{ +impl ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo { fn expected_extra_storage_proof_size() -> u32 { - bp_bridge_hub_wococo::EXTRA_STORAGE_PROOF_SIZE + bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs index 26a188a98610a7ecc822eb5e7e448ad7f1867eb8..861ccfc51fd8e9f7bf8a1367d4ab4ddf459891a0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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-8idpd4bs-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 +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --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_balances +// --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)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 56_219_000 picoseconds. - Weight::from_parts(56_763_000, 0) + // Minimum execution time: 41_696_000 picoseconds. + Weight::from_parts(42_201_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_515_000 picoseconds. - Weight::from_parts(42_186_000, 0) + // Minimum execution time: 32_855_000 picoseconds. + Weight::from_parts(33_554_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 16_274_000 picoseconds. - Weight::from_parts(16_898_000, 0) + // Minimum execution time: 12_977_000 picoseconds. + Weight::from_parts(13_473_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 23_847_000 picoseconds. - Weight::from_parts(24_343_000, 0) + // Minimum execution time: 17_617_000 picoseconds. + Weight::from_parts(18_234_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_564_000 picoseconds. - Weight::from_parts(58_172_000, 0) + // Minimum execution time: 43_174_000 picoseconds. + Weight::from_parts(43_685_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 52_131_000 picoseconds. - Weight::from_parts(52_662_000, 0) + // Minimum execution time: 41_125_000 picoseconds. + Weight::from_parts(41_636_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 19_005_000 picoseconds. - Weight::from_parts(19_594_000, 0) + // Minimum execution time: 15_749_000 picoseconds. + Weight::from_parts(16_163_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_275_000 picoseconds. - Weight::from_parts(17_901_000, 0) + // Minimum execution time: 14_238_000 picoseconds. + Weight::from_parts(14_469_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 15_775 - .saturating_add(Weight::from_parts(15_448_147, 0).saturating_mul(u.into())) + // Standard Error: 11_818 + .saturating_add(Weight::from_parts(12_621_051, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 4_904_000 picoseconds. + Weight::from_parts(5_459_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } 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 5fbe2da8eaa31ae2689a389709fa468ee2d975a7..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,25 +17,25 @@ //! Autogenerated weights for `pallet_bridge_grandpa` //! //! 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: `[]` +//! DATE: 2023-12-12, 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("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-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_bridge_grandpa -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa.rs +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_bridge_grandpa +// --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)] @@ -48,33 +48,31 @@ use core::marker::PhantomData; /// Weight functions for `pallet_bridge_grandpa`. pub struct WeightInfo(PhantomData); impl pallet_bridge_grandpa::WeightInfo for WeightInfo { - /// Storage: BridgeRococoGrandpa PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoGrandpa PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: BridgeRococoGrandpa BestFinalized (r:1 w:1) - /// Proof: BridgeRococoGrandpa BestFinalized (max_values: Some(1), max_size: Some(36), added: 531, mode: MaxEncodedLen) - /// Storage: BridgeRococoGrandpa CurrentAuthoritySet (r:1 w:0) - /// Proof: BridgeRococoGrandpa CurrentAuthoritySet (max_values: Some(1), max_size: Some(50250), added: 50745, mode: MaxEncodedLen) - /// Storage: BridgeRococoGrandpa ImportedHashesPointer (r:1 w:1) - /// Proof: BridgeRococoGrandpa ImportedHashesPointer (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: BridgeRococoGrandpa ImportedHashes (r:1 w:1) - /// Proof: BridgeRococoGrandpa ImportedHashes (max_values: Some(1024), max_size: Some(36), added: 1521, mode: MaxEncodedLen) - /// Storage: BridgeRococoGrandpa ImportedHeaders (r:0 w:2) - /// Proof: BridgeRococoGrandpa ImportedHeaders (max_values: Some(1024), max_size: Some(68), added: 1553, mode: MaxEncodedLen) - /// The range of component `p` is `[1, 838]`. - /// The range of component `v` is `[50, 100]`. + /// Storage: `BridgeWestendGrandpa::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendGrandpa::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::BestFinalized` (r:1 w:1) + /// Proof: `BridgeWestendGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36), added: 531, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::CurrentAuthoritySet` (r:1 w:0) + /// Proof: `BridgeWestendGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`: Some(50250), added: 50745, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHashesPointer` (r:1 w:1) + /// Proof: `BridgeWestendGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHashes` (r:1 w:1) + /// Proof: `BridgeWestendGrandpa::ImportedHashes` (`max_values`: Some(1024), `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHeaders` (r:0 w:2) + /// Proof: `BridgeWestendGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) /// The range of component `p` is `[1, 838]`. /// The range of component `v` is `[50, 100]`. fn submit_finality_proof(p: u32, v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `231 + p * (60 ±0)` + // Measured: `335 + p * (60 ±0)` // Estimated: `51735` - // Minimum execution time: 241_332_000 picoseconds. - Weight::from_parts(69_790_821, 0) + // Minimum execution time: 310_124_000 picoseconds. + Weight::from_parts(18_294_977, 0) .saturating_add(Weight::from_parts(0, 51735)) - // Standard Error: 6_013 - .saturating_add(Weight::from_parts(47_580_554, 0).saturating_mul(p.into())) - // Standard Error: 100_298 - .saturating_add(Weight::from_parts(1_213_475, 0).saturating_mul(v.into())) + // Standard Error: 5_665 + .saturating_add(Weight::from_parts(55_380_719, 0).saturating_mul(p.into())) + // Standard Error: 94_494 + .saturating_add(Weight::from_parts(2_765_959, 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-rococo/src/weights/pallet_bridge_grandpa_wococo_finality.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa_wococo_finality.rs deleted file mode 100644 index 7769112027bea37f85a05520659b6361824906d9..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa_wococo_finality.rs +++ /dev/null @@ -1,81 +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 . - -//! Autogenerated weights for `pallet_bridge_grandpa` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-t2sp1qqs-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_grandpa -// --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_grandpa`. -pub struct WeightInfo(PhantomData); -impl pallet_bridge_grandpa::WeightInfo for WeightInfo { - /// Storage: `BridgeWococoGrandpa::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoGrandpa::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::BestFinalized` (r:1 w:1) - /// Proof: `BridgeWococoGrandpa::BestFinalized` (`max_values`: Some(1), `max_size`: Some(36), added: 531, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::CurrentAuthoritySet` (r:1 w:0) - /// Proof: `BridgeWococoGrandpa::CurrentAuthoritySet` (`max_values`: Some(1), `max_size`: Some(50250), added: 50745, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHashesPointer` (r:1 w:1) - /// Proof: `BridgeWococoGrandpa::ImportedHashesPointer` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHashes` (r:1 w:1) - /// Proof: `BridgeWococoGrandpa::ImportedHashes` (`max_values`: Some(1024), `max_size`: Some(36), added: 1521, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHeaders` (r:0 w:2) - /// Proof: `BridgeWococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 838]`. - /// The range of component `v` is `[50, 100]`. - /// The range of component `p` is `[1, 838]`. - /// The range of component `v` is `[50, 100]`. - fn submit_finality_proof(p: u32, v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `268 + p * (60 ±0)` - // Estimated: `51735` - // Minimum execution time: 294_211_000 picoseconds. - Weight::from_parts(43_087_768, 0) - .saturating_add(Weight::from_parts(0, 51735)) - // Standard Error: 5_610 - .saturating_add(Weight::from_parts(55_863_061, 0).saturating_mul(p.into())) - // Standard Error: 93_579 - .saturating_add(Weight::from_parts(2_409_156, 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-rococo/src/weights/pallet_bridge_messages.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.rs deleted file mode 100644 index ec40615dc133949d8c880e00c687cb106d55cdc3..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.rs +++ /dev/null @@ -1,231 +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 . - -//! Autogenerated weights for `pallet_bridge_messages` -//! -//! 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("bridge-hub-rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// ./artifacts/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-rococo-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_bridge_messages -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.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_bridge_messages`. -pub struct WeightInfo(PhantomData); -impl pallet_bridge_messages::WeightInfo for WeightInfo { - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: `367` - // Estimated: `52645` - // Minimum execution time: 43_187_000 picoseconds. - Weight::from_parts(43_681_000, 0) - .saturating_add(Weight::from_parts(0, 52645)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: `367` - // Estimated: `52645` - // Minimum execution time: 54_131_000 picoseconds. - Weight::from_parts(54_813_000, 0) - .saturating_add(Weight::from_parts(0, 52645)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: `367` - // Estimated: `52645` - // Minimum execution time: 48_120_000 picoseconds. - Weight::from_parts(48_733_000, 0) - .saturating_add(Weight::from_parts(0, 52645)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: `335` - // Estimated: `52645` - // Minimum execution time: 41_028_000 picoseconds. - Weight::from_parts(41_635_000, 0) - .saturating_add(Weight::from_parts(0, 52645)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: `335` - // Estimated: `52645` - // Minimum execution time: 68_499_000 picoseconds. - Weight::from_parts(69_263_000, 0) - .saturating_add(Weight::from_parts(0, 52645)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: BridgeRococoMessages OutboundLanes (r:1 w:1) - /// Proof: BridgeRococoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: 539, mode: MaxEncodedLen) - /// Storage: unknown `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) - /// Proof Skipped: unknown `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: `339` - // Estimated: `3804` - // Minimum execution time: 32_277_000 picoseconds. - Weight::from_parts(32_880_000, 0) - .saturating_add(Weight::from_parts(0, 3804)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: BridgeRococoMessages OutboundLanes (r:1 w:1) - /// Proof: BridgeRococoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: 539, mode: MaxEncodedLen) - /// Storage: unknown `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) - /// Proof Skipped: unknown `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: `339` - // Estimated: `3804` - // Minimum execution time: 32_504_000 picoseconds. - Weight::from_parts(33_085_000, 0) - .saturating_add(Weight::from_parts(0, 3804)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: BridgeRococoMessages OutboundLanes (r:1 w:1) - /// Proof: BridgeRococoMessages OutboundLanes (max_values: Some(1), max_size: Some(44), added: 539, mode: MaxEncodedLen) - /// Storage: unknown `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) - /// Proof Skipped: unknown `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: `339` - // Estimated: `6086` - // Minimum execution time: 34_963_000 picoseconds. - Weight::from_parts(35_473_000, 0) - .saturating_add(Weight::from_parts(0, 6086)) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: BridgeRococoMessages PalletOperatingMode (r:1 w:0) - /// Proof: BridgeRococoMessages 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: 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: 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 RelevantMessagingState (r:1 w:0) - /// Proof Skipped: ParachainSystem RelevantMessagingState (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: XcmpQueue OutboundXcmpStatus (r:1 w:1) - /// Proof Skipped: XcmpQueue OutboundXcmpStatus (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: XcmpQueue OutboundXcmpMessages (r:0 w:1) - /// Proof Skipped: 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: `635` - // Estimated: `52645` - // Minimum execution time: 129_978_000 picoseconds. - Weight::from_parts(98_246_356, 0) - .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 2_554 - .saturating_add(Weight::from_parts(544_728, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3255ab3875dd32e36958757685299800daf67f2 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_rococo_bulletin.rs @@ -0,0 +1,221 @@ +// 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_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_rococo_to_wococo.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs similarity index 53% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_wococo.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs index 83e9596fb3f364cbf5e3f6f9ee4886b029a30b6a..30ea9eed4a5b4f187ea76633400cff8c39991b46 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_wococo.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-10-22, 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-t2sp1qqs-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: @@ -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: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `504` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 42_043_000 picoseconds. - Weight::from_parts(43_557_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `504` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 53_080_000 picoseconds. - Weight::from_parts(55_107_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `504` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 47_757_000 picoseconds. - Weight::from_parts(49_024_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// 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: `472` + // Measured: `573` // Estimated: `52645` - // Minimum execution time: 41_434_000 picoseconds. - Weight::from_parts(42_468_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// 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: `472` + // Measured: `573` // Estimated: `52645` - // Minimum execution time: 76_285_000 picoseconds. - Weight::from_parts(77_717_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `376` - // Estimated: `3841` - // Minimum execution time: 31_296_000 picoseconds. - Weight::from_parts(32_147_000, 0) - .saturating_add(Weight::from_parts(0, 3841)) + // 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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `376` - // Estimated: `3841` - // Minimum execution time: 31_114_000 picoseconds. - Weight::from_parts(31_937_000, 0) - .saturating_add(Weight::from_parts(0, 3841)) + // 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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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: `376` + // Measured: `447` // Estimated: `6086` - // Minimum execution time: 33_620_000 picoseconds. - Weight::from_parts(34_604_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)) } - /// Storage: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendMessages::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: `BridgeWococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeWococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeWestendMessages::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) @@ -230,13 +230,13 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// The range of component `i` is `[128, 2048]`. fn receive_single_message_proof_with_dispatch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `672` + // Measured: `736` // Estimated: `52645` - // Minimum execution time: 63_809_000 picoseconds. - Weight::from_parts(65_441_614, 0) + // Minimum execution time: 65_934_000 picoseconds. + Weight::from_parts(67_915_916, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 61 - .saturating_add(Weight::from_parts(6_729, 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 c9f1d7e05d34e00fcb1f7b28939e93a01dece2de..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,25 +17,25 @@ //! Autogenerated weights for `pallet_bridge_parachains` //! //! 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: `[]` +//! DATE: 2023-12-12, 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("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-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_bridge_parachains -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains.rs +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_bridge_parachains +// --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)] @@ -48,64 +48,65 @@ use core::marker::PhantomData; /// Weight functions for `pallet_bridge_parachains`. pub struct WeightInfo(PhantomData); impl pallet_bridge_parachains::WeightInfo for WeightInfo { - /// Storage: BridgeWococoParachain PalletOperatingMode (r:1 w:0) - /// Proof: BridgeWococoParachain PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: BridgeWococoGrandpa ImportedHeaders (r:1 w:0) - /// Proof: BridgeWococoGrandpa ImportedHeaders (max_values: Some(1024), max_size: Some(68), added: 1553, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ParasInfo (r:1 w:1) - /// Proof: BridgeWococoParachain ParasInfo (max_values: Some(1), max_size: Some(60), added: 555, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHashes (r:1 w:1) - /// Proof: BridgeWococoParachain ImportedParaHashes (max_values: Some(64), max_size: Some(64), added: 1054, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHeads (r:0 w:1) - /// Proof: BridgeWococoParachain ImportedParaHeads (max_values: Some(64), max_size: Some(196), added: 1186, mode: MaxEncodedLen) - /// The range of component `p` is `[1, 2]`. + /// Storage: `BridgeWestendParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgeWestendGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// 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: `367` + // Measured: `434` // Estimated: `2543` - // Minimum execution time: 34_759_000 picoseconds. - Weight::from_parts(35_709_034, 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)) } - /// Storage: BridgeWococoParachain PalletOperatingMode (r:1 w:0) - /// Proof: BridgeWococoParachain PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: BridgeWococoGrandpa ImportedHeaders (r:1 w:0) - /// Proof: BridgeWococoGrandpa ImportedHeaders (max_values: Some(1024), max_size: Some(68), added: 1553, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ParasInfo (r:1 w:1) - /// Proof: BridgeWococoParachain ParasInfo (max_values: Some(1), max_size: Some(60), added: 555, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHashes (r:1 w:1) - /// Proof: BridgeWococoParachain ImportedParaHashes (max_values: Some(64), max_size: Some(64), added: 1054, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHeads (r:0 w:1) - /// Proof: BridgeWococoParachain ImportedParaHeads (max_values: Some(64), max_size: Some(196), added: 1186, mode: MaxEncodedLen) + /// Storage: `BridgeWestendParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgeWestendGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:0 w:1) + /// Proof: `BridgeWestendParachains::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: `434` // Estimated: `2543` - // Minimum execution time: 36_005_000 picoseconds. - Weight::from_parts(36_492_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)) } - /// Storage: BridgeWococoParachain PalletOperatingMode (r:1 w:0) - /// Proof: BridgeWococoParachain PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: BridgeWococoGrandpa ImportedHeaders (r:1 w:0) - /// Proof: BridgeWococoGrandpa ImportedHeaders (max_values: Some(1024), max_size: Some(68), added: 1553, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ParasInfo (r:1 w:1) - /// Proof: BridgeWococoParachain ParasInfo (max_values: Some(1), max_size: Some(60), added: 555, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHashes (r:1 w:1) - /// Proof: BridgeWococoParachain ImportedParaHashes (max_values: Some(64), max_size: Some(64), added: 1054, mode: MaxEncodedLen) - /// Storage: BridgeWococoParachain ImportedParaHeads (r:0 w:1) - /// Proof: BridgeWococoParachain ImportedParaHeads (max_values: Some(64), max_size: Some(196), added: 1186, mode: MaxEncodedLen) + /// Storage: `BridgeWestendParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeWestendParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgeWestendGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeWestendParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:0 w:1) + /// Proof: `BridgeWestendParachains::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: `434` // Estimated: `2543` - // Minimum execution time: 62_374_000 picoseconds. - Weight::from_parts(62_977_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_parachains_within_wococo.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains_within_wococo.rs deleted file mode 100644 index 02c350a0408790a7bda63412d9c7e5ea7354c6ee..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains_within_wococo.rs +++ /dev/null @@ -1,113 +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 . - -//! Autogenerated weights for `pallet_bridge_parachains` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-t2sp1qqs-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_parachains -// --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_parachains`. -pub struct WeightInfo(PhantomData); -impl pallet_bridge_parachains::WeightInfo for WeightInfo { - /// Storage: `BridgeWococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHeaders` (r:1 w:0) - /// Proof: `BridgeWococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeWococoParachain::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]`. - fn submit_parachain_heads_with_n_parachains(_p: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `367` - // Estimated: `2543` - // Minimum execution time: 32_627_000 picoseconds. - Weight::from_parts(33_806_957, 0) - .saturating_add(Weight::from_parts(0, 2543)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `BridgeWococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHeaders` (r:1 w:0) - /// Proof: `BridgeWococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeWococoParachain::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` - // Estimated: `2543` - // Minimum execution time: 34_360_000 picoseconds. - Weight::from_parts(35_212_000, 0) - .saturating_add(Weight::from_parts(0, 2543)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: `BridgeWococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoGrandpa::ImportedHeaders` (r:1 w:0) - /// Proof: `BridgeWococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeWococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeWococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeWococoParachain::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` - // Estimated: `2543` - // Minimum execution time: 66_678_000 picoseconds. - Weight::from_parts(67_571_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 0ae6e5eff2a87fee75a6538b0a14884cba1596e1..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-10-23, 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-t2sp1qqs-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_239_000 picoseconds. - Weight::from_parts(47_442_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: 23_977_000 picoseconds. - Weight::from_parts(24_837_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: 25_798_000 picoseconds. - Weight::from_parts(26_495_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_382_000 picoseconds. - Weight::from_parts(27_936_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: 2_944_000 picoseconds. - Weight::from_parts(3_093_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_collator_selection.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_collator_selection.rs index 9cbfa6ce80e3e4b45338786c3705500bf27a220f..f7e233189abb4443320c7bee6f28ee4856302452 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_collator_selection.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -124,7 +123,7 @@ impl pallet_collator_selection::WeightInfo for WeightIn } /// 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() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -178,6 +177,30 @@ impl pallet_collator_selection::WeightInfo for WeightIn .saturating_add(T::DbWeight::get().reads(2)) .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) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..2fcd573ceb277116bda67180da8e0701593ab453 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-24, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! 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 +// pallet_message_queue +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights + +#![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: 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)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs index 91840ae0c6d77cfb4507d5f7d7b8f2aca7c84b35..832380d3876bc3d19339779c83bba031304d17db 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_session.rs index c9d04f9c6df65892243220531ac9d42dc885702d..06b2f3bcd2718107ac30dd9c78299e963002ae28 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_session.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_timestamp.rs index 0a5bf9b9f9c1f8c483c2e2f86ae471b28e7f609a..1c54e9519b3c4b7e175f72a577ba6de5b67556dc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_timestamp.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_utility.rs index 44cd0cf91e7998cb504587c481d13b110c9db072..d96b9e88840fd39e8553275ffd5d1a01cf310a0e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_utility.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! 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 72bdb282585dec0533662bad5620f2434bcc15b1..a732e1a573439c4b658191697024ac3c396c9de5 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 @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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-bn-ce5rx-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 +// 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-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)] @@ -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: 18_513_000 picoseconds. + Weight::from_parts(19_156_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: 88_096_000 picoseconds. + Weight::from_parts(89_732_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: 88_239_000 picoseconds. + Weight::from_parts(89_729_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: 5_955_000 picoseconds. + Weight::from_parts(6_266_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: 1_868_000 picoseconds. + Weight::from_parts(1_961_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: 24_388_000 picoseconds. + Weight::from_parts(25_072_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: 26_762_000 picoseconds. + Weight::from_parts(27_631_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,48 +224,50 @@ 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: 1_856_000 picoseconds. + Weight::from_parts(2_033_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `187` - // Estimated: `11077` - // Minimum execution time: 17_649_000 picoseconds. - Weight::from_parts(17_964_000, 0) - .saturating_add(Weight::from_parts(0, 11077)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 17_718_000 picoseconds. + Weight::from_parts(18_208_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `191` - // Estimated: `11081` - // Minimum execution time: 17_551_000 picoseconds. - Weight::from_parts(18_176_000, 0) - .saturating_add(Weight::from_parts(0, 11081)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 17_597_000 picoseconds. + Weight::from_parts(18_090_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `13563` - // Minimum execution time: 19_261_000 picoseconds. - Weight::from_parts(19_714_000, 0) - .saturating_add(Weight::from_parts(0, 13563)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `106` + // Estimated: `15946` + // Minimum execution time: 19_533_000 picoseconds. + Weight::from_parts(20_164_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } /// 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: 24_958_000 picoseconds. + Weight::from_parts(25_628_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) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_218_000 picoseconds. - Weight::from_parts(9_558_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `136` + // Estimated: `11026` + // Minimum execution time: 12_209_000 picoseconds. + Weight::from_parts(12_612_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `11088` - // Minimum execution time: 18_133_000 picoseconds. - Weight::from_parts(18_663_000, 0) - .saturating_add(Weight::from_parts(0, 11088)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 17_844_000 picoseconds. + Weight::from_parts(18_266_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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,50 @@ 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: `106` + // Estimated: `13471` + // Minimum execution time: 34_131_000 picoseconds. + Weight::from_parts(34_766_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(11)) .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: 3_525_000 picoseconds. + Weight::from_parts(3_724_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: 24_975_000 picoseconds. + Weight::from_parts(25_517_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 33_761_000 picoseconds. + Weight::from_parts(34_674_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .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_pallet_ethereum_client.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_client.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d5f29c6ff2f21165e45649848bd24664acd7e19 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_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_pallet_ethereum_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_pallet_ethereum_client`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_ethereum_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_pallet_inbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_inbound_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..faf404f90cb34dd3825df585bb3221031147bb47 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_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_pallet_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_pallet_inbound_queue`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_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_pallet_outbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_outbound_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..8adcef076e00add856e387b1a875116f5e8f0208 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_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_pallet_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_pallet_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..c6c188e323af84d11ba396cb9ab4e97983bac33c --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_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_pallet_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_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_pallet_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/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs index 78a0eed91740d645e0c292d86ae1f5909fa395c9..4f5bae0fe597b88b1c23fa4ab806cec98bf7d746 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; @@ -25,14 +24,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -51,40 +50,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } pub struct BridgeHubRococoXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for BridgeHubRococoXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -112,44 +107,36 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -164,7 +151,7 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -176,13 +163,13 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -216,16 +203,16 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { let inner_encoded_len = inner.encode().len() as u32; XcmGeneric::::export_message(inner_encoded_len) } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -237,11 +224,11 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index cd1a673cb53979843ecf191e636865b54b035a3d..d7e8c41ff8ac41acfeb60f30774e8282939b6c1c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-yprdrvc7-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,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 23_601_000 picoseconds. - Weight::from_parts(24_226_000, 3593) + // Minimum execution time: 19_610_000 picoseconds. + Weight::from_parts(19_980_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,15 +65,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `153` // Estimated: `6196` - // Minimum execution time: 51_043_000 picoseconds. - Weight::from_parts(52_326_000, 6196) + // Minimum execution time: 44_411_000 picoseconds. + Weight::from_parts(45_110_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:2 w: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) @@ -86,12 +88,12 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `260` - // Estimated: `6196` - // Minimum execution time: 75_639_000 picoseconds. - Weight::from_parts(76_736_000, 6196) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `223` + // Estimated: `8799` + // Minimum execution time: 89_739_000 picoseconds. + Weight::from_parts(91_256_000, 8799) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: `Benchmark::Override` (r:0 w:0) // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -104,31 +106,35 @@ 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 initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `107` - // Estimated: `3572` - // Minimum execution time: 31_190_000 picoseconds. - Weight::from_parts(32_150_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `171` + // Estimated: `6196` + // Minimum execution time: 60_045_000 picoseconds. + Weight::from_parts(60_710_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_603_000 picoseconds. - Weight::from_parts(3_721_000, 0) + // Minimum execution time: 3_257_000 picoseconds. + Weight::from_parts(3_392_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -136,15 +142,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3593` - // Minimum execution time: 24_265_000 picoseconds. - Weight::from_parts(25_004_000, 3593) + // Minimum execution time: 19_423_000 picoseconds. + Weight::from_parts(19_823_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:1 w: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) @@ -157,32 +165,36 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `159` - // Estimated: `3624` - // Minimum execution time: 51_882_000 picoseconds. - Weight::from_parts(53_228_000, 3624) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `122` + // Estimated: `6196` + // Minimum execution time: 60_484_000 picoseconds. + Weight::from_parts(61_634_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: `107` - // Estimated: `3572` - // Minimum execution time: 32_195_000 picoseconds. - Weight::from_parts(33_206_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 44_863_000 picoseconds. + Weight::from_parts(45_549_000, 3593) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } } 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 da3404909f31ca9418a9f2d21117178833bb1a11..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,28 +17,26 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! 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-12, 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-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 +// target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::generic -// --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/xcm/pallet_xcm_benchmarks_generic.rs +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=bridge-hub-rococo-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/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,120 +50,128 @@ 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: `107` - // Estimated: `3572` - // Minimum execution time: 37_350_000 picoseconds. - Weight::from_parts(38_105_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `171` + // Estimated: `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)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_042_000 picoseconds. - Weight::from_parts(3_117_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`) pub fn query_response() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `3534` - // Minimum execution time: 11_037_000 picoseconds. - Weight::from_parts(11_465_000, 3534) + // Measured: `32` + // Estimated: `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: 12_359_000 picoseconds. - Weight::from_parts(12_741_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: 3_165_000 picoseconds. - Weight::from_parts(3_295_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_847_000 picoseconds. - Weight::from_parts(2_893_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_847_000 picoseconds. - Weight::from_parts(2_936_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_856_000 picoseconds. - Weight::from_parts(2_933_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: 3_635_000 picoseconds. - Weight::from_parts(3_710_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_822_000 picoseconds. - Weight::from_parts(2_899_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`) + // 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: `107` - // Estimated: `3572` - // Minimum execution time: 29_399_000 picoseconds. - Weight::from_parts(30_284_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `171` + // Estimated: `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)) } // 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: `126` - // Estimated: `3591` - // Minimum execution time: 16_173_000 picoseconds. - Weight::from_parts(16_576_000, 3591) + // Measured: `90` + // Estimated: `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)) } @@ -173,11 +179,13 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_882_000 picoseconds. - Weight::from_parts(3_017_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`) + // 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) @@ -190,11 +198,11 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 29_839_000 picoseconds. - Weight::from_parts(30_519_000, 3540) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `38` + // Estimated: `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)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) @@ -203,147 +211,159 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_806_000 picoseconds. - Weight::from_parts(5_042_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: 4_407_000 picoseconds. - Weight::from_parts(4_548_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_930_000 picoseconds. - Weight::from_parts(3_042_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_915_000 picoseconds. - Weight::from_parts(3_052_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_823_000 picoseconds. - Weight::from_parts(2_912_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: 3_119_000 picoseconds. - Weight::from_parts(3_205_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`) + // 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: `107` - // Estimated: `3572` - // Minimum execution time: 33_394_000 picoseconds. - Weight::from_parts(34_497_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `171` + // Estimated: `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)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_471_000 picoseconds. - Weight::from_parts(5_640_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`) + // 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: `107` - // Estimated: `3572` - // Minimum execution time: 29_932_000 picoseconds. - Weight::from_parts(30_478_000, 3572) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `171` + // Estimated: `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)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_807_000 picoseconds. - Weight::from_parts(2_941_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_785_000 picoseconds. - Weight::from_parts(2_894_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_844_000 picoseconds. - Weight::from_parts(2_943_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: `BridgeWococoMessages::PalletOperatingMode` (r:1 w:0) - // Proof: `BridgeWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - // Storage: `BridgeWococoMessages::OutboundLanes` (r:1 w:1) - // Proof: `BridgeWococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) - // Storage: `BridgeWococoMessages::OutboundMessages` (r:0 w:1) - // Proof: `BridgeWococoMessages::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: `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) + // Proof: `BridgeWestendMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + // Storage: `BridgeWestendMessages::OutboundLanesCongestedSignals` (r:1 w:0) + // Proof: `BridgeWestendMessages::OutboundLanesCongestedSignals` (`max_values`: Some(1), `max_size`: Some(21), added: 516, mode: `MaxEncodedLen`) + // Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:1) + // Proof: `BridgeWestendMessages::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: `95` - // Estimated: `1529` - // Minimum execution time: 28_427_000 picoseconds. - Weight::from_parts(28_755_860, 1529) - // Standard Error: 383 - .saturating_add(Weight::from_parts(393_744, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(3)) + // 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_781_000 picoseconds. - Weight::from_parts(2_907_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: 3_001_000 picoseconds. - Weight::from_parts(3_117_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 703acfa44174d12eaf9a433f8a5cf89ef7bedb5e..8934ff9b22729da0ea322792837d2a08d5829763 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 @@ -16,76 +16,72 @@ use super::{ AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeFlavor, RuntimeOrigin, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; use crate::{ bridge_common_config::{ - BridgeGrandpaRococoInstance, BridgeGrandpaWococoInstance, DeliveryRewardInBalance, - RequiredStakeForStakeAndSlash, + BridgeGrandpaRococoBulletinInstance, BridgeGrandpaWestendInstance, + BridgeParachainWestendInstance, DeliveryRewardInBalance, RequiredStakeForStakeAndSlash, }, - bridge_hub_rococo_config::ToBridgeHubWococoHaulBlobExporter, - bridge_hub_wococo_config::ToBridgeHubRococoHaulBlobExporter, + bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, + EthereumGatewayAddress, }; +use bp_messages::LaneId; +use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::ChainId; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing}, + parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, + StoragePrefixedMap, }; 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::SystemParachains; +use snowbridge_runtime_common::XcmExportFeeToSibling; use sp_core::Get; use sp_runtime::traits::AccountIdConversion; +use sp_std::marker::PhantomData; +use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeesToAccount, + deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeToAccount, }; use xcm_executor::{ - traits::{ExportXcm, WithOriginFilter}, + traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset, WithOriginFilter}, XcmExecutor, }; parameter_types! { - pub storage Flavor: RuntimeFlavor = RuntimeFlavor::default(); - pub const TokenLocation: MultiLocation = MultiLocation::parent(); + pub const TokenLocation: Location = Location::parent(); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = - X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); + pub RelayNetwork: NetworkId = NetworkId::Rococo; + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; - pub TreasuryAccount: Option = Some(TREASURY_PALLET_ID.into_account_truncating()); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); + pub SiblingPeople: Location = (Parent, Parachain(rococo_runtime_constants::system_parachain::PEOPLE_ID)).into(); } -/// Adapter for resolving `NetworkId` based on `pub storage Flavor: RuntimeFlavor`. -pub struct RelayNetwork; -impl Get> for RelayNetwork { - fn get() -> Option { - Some(Self::get()) - } -} -impl Get for RelayNetwork { - fn get() -> NetworkId { - match Flavor::get() { - RuntimeFlavor::Rococo => NetworkId::Rococo, - RuntimeFlavor::Wococo => NetworkId::Wococo, - } - } -} - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -98,12 +94,12 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + // Do a simple punn to convert an AccountId32 Location into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -135,15 +131,11 @@ pub type XcmOriginToTransactDispatchOrigin = ( XcmPassthrough, ); -match_types! { - 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(_) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -167,9 +159,12 @@ impl Contains for SafeCallFilter { match call { RuntimeCall::System(frame_system::Call::set_storage { items }) if items.iter().all(|(k, _)| { - k.eq(&DeliveryRewardInBalance::key()) | - k.eq(&RequiredStakeForStakeAndSlash::key()) | - k.eq(&Flavor::key()) + k.eq(&DeliveryRewardInBalance::key()) || + k.eq(&RequiredStakeForStakeAndSlash::key()) || + k.eq(&EthereumGatewayAddress::key()) || + // Allow resetting of Ethereum nonces in Rococo only. + k.starts_with(&snowbridge_pallet_inbound_queue::Nonce::::final_prefix()) || + k.starts_with(&snowbridge_pallet_outbound_queue::Nonce::::final_prefix()) }) => return true, _ => (), @@ -177,34 +172,66 @@ impl Contains for SafeCallFilter { 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::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( - 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::DmpQueue(..) | - RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< + RuntimeCall::MessageQueue(..) | + RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaWestendInstance, + >::initialize { .. }) | + RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaWestendInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgeWestendParachains(pallet_bridge_parachains::Call::< Runtime, - BridgeGrandpaRococoInstance, + 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::BridgeWococoGrandpa(pallet_bridge_grandpa::Call::< + RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgePolkadotBulletinMessages(pallet_bridge_messages::Call::< Runtime, - BridgeGrandpaWococoInstance, - >::initialize { .. }) + WithRococoBulletinMessagesInstance, + >::set_operating_mode { .. }) | + RuntimeCall::EthereumBeaconClient( + snowbridge_pallet_ethereum_client::Call::force_checkpoint { .. } | + snowbridge_pallet_ethereum_client::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumInboundQueue( + snowbridge_pallet_inbound_queue::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumOutboundQueue( + snowbridge_pallet_outbound_queue::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumSystem( + snowbridge_pallet_system::Call::upgrade { .. } | + snowbridge_pallet_system::Call::set_operating_mode { .. } | + snowbridge_pallet_system::Call::set_pricing_parameters { .. } | + snowbridge_pallet_system::Call::force_update_channel { .. } | + snowbridge_pallet_system::Call::force_transfer_native_from_agent { .. } | + snowbridge_pallet_system::Call::set_token_transfer_fees { .. }, + ) ) } } @@ -222,10 +249,15 @@ pub type Barrier = TrailingSetTopicAsId< // If the message is one that immediately attempts to pay for execution, then // allow it. AllowTopLevelPaidExecutionFrom, - // Parent and its pluralities (i.e. governance bodies) get free execution. - AllowExplicitUnpaidExecutionFrom, + // 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>, @@ -234,22 +266,13 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -parameter_types! { - pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); -} - -pub struct RelayTreasury; -impl Contains for RelayTreasury { - fn contains(location: &MultiLocation) -> bool { - let relay_treasury_location = RelayTreasuryLocation::get(); - *location == relay_treasury_location - } -} - /// 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, RelayTreasury); +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. @@ -259,7 +282,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native // token where allowed (e.g. with the Relay Chain). @@ -282,18 +305,46 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeesToAccount; - type MessageExporter = BridgeHubRococoOrBridgeHubWococoSwitchExporter; + type FeeManager = XcmFeeManagerFromComponentsBridgeHub< + WaivedLocations, + ( + XcmExportFeeToRelayerRewardAccounts< + Self::AssetTransactor, + crate::bridge_to_westend_config::WestendGlobalConsensusNetwork, + crate::bridge_to_westend_config::AssetHubWestendParaId, + 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, + crate::bridge_to_bulletin_config::ToRococoBulletinHaulBlobExporter, + crate::bridge_to_ethereum_config::SnowbridgeExporter, + ); type UniversalAliases = Nothing; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type PriceForParentDelivery = ExponentialPrice; -/// Converts a local signed origin into an XCM multilocation. +/// Converts a local signed origin into an XCM location. /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; @@ -306,11 +357,6 @@ pub type XcmRouter = WithUniqueTopic<( XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmRouter = XcmRouter; @@ -338,8 +384,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -350,46 +394,110 @@ impl cumulus_pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; } -/// Hacky switch implementation, because we have just one runtime for Rococo and Wococo BridgeHub, -/// so it means we have just one XcmConfig -pub struct BridgeHubRococoOrBridgeHubWococoSwitchExporter; -impl ExportXcm for BridgeHubRococoOrBridgeHubWococoSwitchExporter { - type Ticket = (NetworkId, (sp_std::prelude::Vec, XcmHash)); +/// 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. +pub struct XcmExportFeeToRelayerRewardAccounts< + AssetTransactor, + DestNetwork, + DestParaId, + DestBridgedChainId, + BridgeLaneId, +>(PhantomData<(AssetTransactor, DestNetwork, DestParaId, DestBridgedChainId, BridgeLaneId)>); - fn validate( - network: NetworkId, - channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - match network { - Rococo => ToBridgeHubRococoHaulBlobExporter::validate( - network, - channel, - universal_source, - destination, - message, - ) - .map(|result| ((Rococo, result.0), result.1)), - Wococo => ToBridgeHubWococoHaulBlobExporter::validate( - network, - channel, - universal_source, - destination, - message, - ) - .map(|result| ((Wococo, result.0), result.1)), - _ => unimplemented!("Unsupported network: {:?}", network), +impl< + AssetTransactor: TransactAsset, + DestNetwork: Get, + DestParaId: Get, + DestBridgedChainId: Get, + BridgeLaneId: Get, + > HandleFee + for XcmExportFeeToRelayerRewardAccounts< + AssetTransactor, + DestNetwork, + DestParaId, + DestBridgedChainId, + BridgeLaneId, + > +{ + fn handle_fee(fee: Assets, maybe_context: Option<&XcmContext>, reason: FeeReason) -> Assets { + if matches!(reason, FeeReason::Export { network: bridged_network, destination } + if bridged_network == DestNetwork::get() && + destination == [Parachain(DestParaId::get().into())]) + { + // We have 2 relayer rewards accounts: + // - the SA of the source parachain on this BH: this pays the relayers for delivering + // Source para -> Target Para message delivery confirmations + // - the SA of the destination parachain on this BH: this pays the relayers for + // delivering Target para -> Source Para messages + // We split the `ExportMessage` fee between these 2 accounts. + let source_para_account = PayRewardFromAccount::< + pallet_balances::Pallet, + AccountId, + >::rewards_account(RewardsAccountParams::new( + BridgeLaneId::get(), + DestBridgedChainId::get(), + RewardsAccountOwner::ThisChain, + )); + + let dest_para_account = PayRewardFromAccount::< + pallet_balances::Pallet, + AccountId, + >::rewards_account(RewardsAccountParams::new( + BridgeLaneId::get(), + DestBridgedChainId::get(), + RewardsAccountOwner::BridgedChain, + )); + + for asset in fee.into_inner() { + match asset.fun { + Fungible(total_fee) => { + let source_fee = total_fee / 2; + deposit_or_burn_fee::( + Asset { id: asset.id.clone(), fun: Fungible(source_fee) }.into(), + maybe_context, + source_para_account.clone(), + ); + + let dest_fee = total_fee - source_fee; + deposit_or_burn_fee::( + Asset { id: asset.id, fun: Fungible(dest_fee) }.into(), + maybe_context, + dest_para_account.clone(), + ); + }, + NonFungible(_) => { + deposit_or_burn_fee::( + asset.into(), + maybe_context, + source_para_account.clone(), + ); + }, + } + } + + return Assets::new() } + + fee } +} - fn deliver(ticket: Self::Ticket) -> Result { - let (network, ticket) = ticket; - match network { - Rococo => ToBridgeHubRococoHaulBlobExporter::deliver(ticket), - Wococo => ToBridgeHubWococoHaulBlobExporter::deliver(ticket), - _ => unimplemented!("Unsupported network: {:?}", network), +pub struct XcmFeeManagerFromComponentsBridgeHub( + PhantomData<(WaivedLocations, HandleFee)>, +); +impl, FeeHandler: HandleFee> FeeManager + for XcmFeeManagerFromComponentsBridgeHub +{ + fn is_waived(origin: Option<&Location>, 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: Assets, context: Option<&XcmContext>, reason: FeeReason) { + FeeHandler::handle_fee(fee, context, reason); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs new file mode 100644 index 0000000000000000000000000000000000000000..239bd946e759b1c8aa1e892912b5bce8093bde00 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -0,0 +1,208 @@ +// 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 . + +#![cfg(test)] + +use bp_polkadot_core::Signature; +use bridge_hub_rococo_runtime::{ + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, + xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, + Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, + SignedExtra, UncheckedExtrinsic, +}; +use codec::{Decode, Encode}; +use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; +use frame_support::parameter_types; +use parachains_common::{AccountId, AuraId, Balance}; +use snowbridge_pallet_ethereum_client::WeightInfo; +use sp_core::H160; +use sp_keyring::AccountKeyring::Alice; +use sp_runtime::{ + generic::{Era, SignedPayload}, + AccountId32, +}; + +parameter_types! { + pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; +} + +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() { + snowbridge_runtime_test_common::send_transfer_token_message_success::( + 11155111, + 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() { + snowbridge_runtime_test_common::send_unpaid_transfer_token_message::( + 11155111, + collator_session_keys(), + 1013, + 1000, + H160::random(), + H160::random(), + ) +} + +#[test] +pub fn transfer_token_to_ethereum_fee_not_enough() { + snowbridge_runtime_test_common::send_transfer_token_message_failure::( + 11155111, + 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() { + snowbridge_runtime_test_common::send_transfer_token_message_failure::( + 11155111, + collator_session_keys(), + 1013, + 1000, + 1_000_000_000, + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + FailedToTransactAsset("Funds are unavailable"), + ) +} + +#[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); +} + +#[test] +fn ethereum_client_consensus_extrinsics_work() { + snowbridge_runtime_test_common::ethereum_extrinsic( + collator_session_keys(), + 1013, + construct_and_apply_extrinsic, + ); +} + +#[test] +fn ethereum_to_polkadot_message_extrinsics_work() { + snowbridge_runtime_test_common::ethereum_to_polkadot_message_extrinsics_work( + collator_session_keys(), + 1013, + construct_and_apply_extrinsic, + ); +} + +/// Tests that the digest items are as expected when a Ethereum Outbound message is received. +/// If the MessageQueue pallet is configured before (i.e. the MessageQueue pallet is listed before +/// the EthereumOutboundQueue in the construct_runtime macro) the EthereumOutboundQueue, this test +/// will fail. +#[test] +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { + snowbridge_runtime_test_common::ethereum_outbound_queue_processes_messages_before_message_queue_works::< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, + >( + 11155111, + collator_session_keys(), + 1013, + 1000, + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::EthereumOutboundQueue(event)) => Some(event), + _ => None, + } + }), + ) +} + +fn construct_extrinsic( + sender: sp_keyring::AccountKeyring, + call: RuntimeCall, +) -> 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( + frame_system::Pallet::::account(&account_id).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + BridgeRejectObsoleteHeadersAndMessages::default(), + ( + OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + OnBridgeHubRococoRefundRococoBulletinMessages::default(), + ), + ); + let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap(); + let signature = payload.using_encoded(|e| sender.sign(e)); + UncheckedExtrinsic::new_signed( + call, + account_id.into(), + Signature::Sr25519(signature.clone()), + extra, + ) +} + +fn construct_and_apply_extrinsic( + origin: sp_keyring::AccountKeyring, + call: RuntimeCall, +) -> sp_runtime::DispatchOutcome { + let xt = construct_extrinsic(origin, call); + let r = Executive::apply_extrinsic(xt); + r.unwrap() +} 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 0109e4de4a9a4c1c1bd844196cfcc5925d37e476..f11954cf165fdae2ee36377456b2bc4b2d6755a6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -18,26 +18,27 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ - bridge_common_config, bridge_hub_rococo_config, bridge_hub_wococo_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, - UncheckedExtrinsic, + AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, EthereumGatewayAddress, + Executive, ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, SessionKeys, SignedExtra, TransactionPayment, UncheckedExtrinsic, }; +use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; -use frame_support::parameter_types; -use frame_system::pallet_prelude::HeaderFor; -use parachains_common::{rococo::fee::WeightToFee, AccountId, AuraId, Balance}; +use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; +use parachains_common::{AccountId, AuraId, Balance}; +use snowbridge_core::ChannelId; +use sp_consensus_aura::SlotDuration; +use sp_core::H160; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, - AccountId32, + AccountId32, Perbill, }; +use testnet_parachains_constants::rococo::{consensus::*, fee::WeightToFee}; 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,26 +47,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_hub_wococo_config::BridgeRefundBridgeHubRococoMessages::default(), - bridge_hub_rococo_config::BridgeRefundBridgeHubWococoMessages::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, ) @@ -73,16 +77,18 @@ 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() } -fn executive_init_block(header: &HeaderFor) { - Executive::initialize_block(header) +fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { + let batch_call = RuntimeCall::Utility(batch); + let batch_info = batch_call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, batch_call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { @@ -93,125 +99,250 @@ fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys SlotDurations { + SlotDurations { + relay: SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS.into()), + para: SlotDuration::from_millis(SLOT_DURATION), + } +} + +bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + CheckingAccount, + WeightToFee, + ParachainSystem, + collator_session_keys(), + slot_durations(), + 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 +); + +#[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::{ - BridgeGrandpaWococoInstance, BridgeParachainWococoInstance, DeliveryRewardInBalance, - RequiredStakeForStakeAndSlash, + BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, DeliveryRewardInBalance, }; - use bridge_hub_rococo_config::{ - WithBridgeHubWococoMessageBridge, WithBridgeHubWococoMessagesInstance, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, + use bridge_hub_test_utils::test_cases::from_parachain; + use bridge_to_westend_config::{ + 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, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(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 RococoBulletin finality bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::< Runtime, - BridgeGrandpaWococoInstance, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::BridgeWococoGrandpa(call).encode()), - ) + BridgeGrandpaWestendInstance, + >(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_ethereum_nonces_by_governance_works() { + let channel_id_one: ChannelId = [1; 32].into(); + let channel_id_two: ChannelId = [2; 32].into(); + let nonce = 42; + + // Reset a single inbound channel + bridge_hub_test_utils::test_cases::set_storage_keys_by_governance_works::( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + Box::new(|call| RuntimeCall::System(call).encode()), + vec![ + (snowbridge_pallet_outbound_queue::Nonce::::hashed_key_for::( + channel_id_one, + ) + .to_vec(), 0u64.encode()), + (snowbridge_pallet_inbound_queue::Nonce::::hashed_key_for::( + channel_id_one, + ) + .to_vec(), 0u64.encode()), + ], + || { + // Outbound + snowbridge_pallet_outbound_queue::Nonce::::insert::( + channel_id_one, + nonce, + ); + snowbridge_pallet_outbound_queue::Nonce::::insert::( + channel_id_two, + nonce, + ); + + // Inbound + snowbridge_pallet_inbound_queue::Nonce::::insert::( + channel_id_one, + nonce, + ); + snowbridge_pallet_inbound_queue::Nonce::::insert::( + channel_id_two, + nonce, + ); + }, + || { + // Outbound + assert_eq!( + snowbridge_pallet_outbound_queue::Nonce::::get(channel_id_one), + 0 + ); + assert_eq!( + snowbridge_pallet_outbound_queue::Nonce::::get(channel_id_two), + nonce + ); + + // Inbound + assert_eq!( + snowbridge_pallet_inbound_queue::Nonce::::get(channel_id_one), + 0 + ); + assert_eq!( + snowbridge_pallet_inbound_queue::Nonce::::get(channel_id_two), + nonce + ); + }, + ); + } + + #[test] + 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(), ) } #[test] fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { + // for Westend bridge_hub_test_utils::test_cases::handle_export_message_from_system_parachain_to_outbound_queue_works::< Runtime, XcmConfig, - WithBridgeHubWococoMessagesInstance, + WithBridgeHubWestendMessagesInstance, >( 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::BridgeRococoToWococoMessages(event)) => Some(event), + Ok(RuntimeEvent::BridgeWestendMessages(event)) => Some(event), _ => None, } }), - || ExportMessage { network: Wococo, destination: X1(Parachain(1234)), xcm: Xcm(vec![]) }, - bridge_hub_rococo_config::DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, + || ExportMessage { network: Westend, destination: [Parachain(bridge_to_westend_config::AssetHubWestendParaId::get().into())].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_asset_hub_rococo::BridgeHubRococoBaseFeeInRocs::get()).into()), - || (), + 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!"), ) } #[test] fn message_dispatch_routing_works() { + // from Westend bridge_hub_test_utils::test_cases::message_dispatch_routing_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, ParachainSystem, - WithBridgeHubWococoMessagesInstance, + WithBridgeHubWestendMessagesInstance, RelayNetwork, - bridge_hub_rococo_config::WococoGlobalConsensusNetwork, + WestendGlobalConsensusNetwork, + ConstU8<2>, >( collator_session_keys(), + slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, SIBLING_PARACHAIN_ID, Box::new(|runtime_event_encoded: Vec| { @@ -226,225 +357,199 @@ mod bridge_hub_rococo_tests { _ => None, } }), - bridge_hub_rococo_config::DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, + XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, || (), ) } #[test] fn relayed_incoming_message_works() { - bridge_hub_test_utils::test_cases::relayed_incoming_message_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - BridgeGrandpaWococoInstance, - BridgeParachainWococoInstance, - WithBridgeHubWococoMessagesInstance, - WithBridgeHubWococoMessageBridge, - >( + // from Westend + from_parachain::relayed_incoming_message_works::( collator_session_keys(), + slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + BridgeHubWestendChainId::get(), SIBLING_PARACHAIN_ID, Rococo, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, + XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, || (), + 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, - BridgeGrandpaWococoInstance, - BridgeParachainWococoInstance, - WithBridgeHubWococoMessagesInstance, - WithBridgeHubWococoMessageBridge, - >( + // for Westend + from_parachain::complex_relay_extrinsic_works::( collator_session_keys(), + slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, SIBLING_PARACHAIN_ID, - bridge_hub_rococo_config::BridgeHubWococoChainId::get(), + BridgeHubWestendChainId::get(), Rococo, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_WOCOCO, - ExistentialDeposit::get(), - executive_init_block, - construct_and_apply_extrinsic, + XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, || (), + 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_asset_hub_rococo::BridgeHubRococoBaseFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_asset_hub_rococo::BridgeHubRococoBaseFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + Runtime, + XcmConfig, + WeightToFee, + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) + } + + #[test] + pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) + } + + #[test] + pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } -mod bridge_hub_wococo_tests { +mod bridge_hub_bulletin_tests { use super::*; - use bridge_common_config::{ - BridgeGrandpaRococoInstance, BridgeParachainRococoInstance, DeliveryRewardInBalance, - RequiredStakeForStakeAndSlash, + 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, }; - use bridge_hub_rococo_runtime::{xcm_config, AllPalletsWithoutSystem, RuntimeFlavor}; - use bridge_hub_wococo_config::{ - WithBridgeHubRococoMessageBridge, WithBridgeHubRococoMessagesInstance, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, - }; - use frame_support::assert_ok; - - type RuntimeHelper = bridge_hub_test_utils::RuntimeHelper; - pub(crate) fn set_wococo_flavor() { - let flavor_key = xcm_config::Flavor::key().to_vec(); - let flavor = RuntimeFlavor::Wococo; - - // encode `set_storage` call - let set_storage_call = RuntimeCall::System(frame_system::Call::::set_storage { - items: vec![(flavor_key, flavor.encode())], - }) - .encode(); - - // estimate - storing just 1 value - use frame_system::WeightInfo; - let require_weight_at_most = - ::SystemWeightInfo::set_storage(1); - - // execute XCM with Transact to `set_storage` as governance does - assert_ok!(RuntimeHelper::execute_as_governance(set_storage_call, require_weight_at_most) - .ensure_complete()); - - // check if stored - assert_eq!(flavor, xcm_config::Flavor::get()); - } + // Para id of sibling chain used in tests. + pub const SIBLING_PARACHAIN_ID: u32 = rococo_runtime_constants::system_parachain::PEOPLE_ID; - bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( + // Runtime from tests PoV + type RuntimeTestsAdapter = from_grandpa_chain::WithRemoteGrandpaChainHelperAdapter< 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, - } - }), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), - _ => None, - } - }), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID - ); + 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, - BridgeGrandpaRococoInstance, - >( - collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::BridgeRococoGrandpa(call).encode()), - ) + BridgeGrandpaRococoBulletinInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) } #[test] - fn change_delivery_reward_by_governance_works() { - bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< + 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, - DeliveryRewardInBalance, - u64, - >( - collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::System(call).encode()), - || (DeliveryRewardInBalance::key().to_vec(), DeliveryRewardInBalance::get()), - |old_value| old_value.checked_mul(2).unwrap(), - ) + BridgeGrandpaRococoBulletinInstance, + >(collator_session_keys(), 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::< + 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, - RequiredStakeForStakeAndSlash, - Balance, - >( - collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::System(call).encode()), - || { - ( - RequiredStakeForStakeAndSlash::key().to_vec(), - RequiredStakeForStakeAndSlash::get(), - ) - }, - |old_value| old_value.checked_mul(2).unwrap(), - ) + 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, - WithBridgeHubRococoMessagesInstance, + WithRococoBulletinMessagesInstance, >( collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + 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::BridgeWococoToRococoMessages(event)) => Some(event), + Ok(RuntimeEvent::BridgePolkadotBulletinMessages(event)) => Some(event), _ => None, } }), - || ExportMessage { network: Rococo, destination: X1(Parachain(4321)), xcm: Xcm(vec![]) }, - bridge_hub_wococo_config::DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, + || ExportMessage { + network: RococoBulletinGlobalConsensusNetwork::get(), + destination: Here, + xcm: Xcm(vec![]), + }, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, 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_asset_hub_wococo::BridgeHubWococoBaseFeeInWocs::get()).into()), - set_wococo_flavor, + 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, - WithBridgeHubRococoMessagesInstance, + WithRococoBulletinMessagesInstance, RelayNetwork, - bridge_hub_wococo_config::RococoGlobalConsensusNetwork, + RococoBulletinGlobalConsensusNetwork, + ConstU8<2>, >( collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + slot_durations(), + 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[..]) { @@ -458,74 +563,80 @@ mod bridge_hub_wococo_tests { _ => None, } }), - bridge_hub_wococo_config::DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, - set_wococo_flavor, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), ) } #[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 Bulletin + from_grandpa_chain::relayed_incoming_message_works::( collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + RococoBulletinChainId::get(), SIBLING_PARACHAIN_ID, - Wococo, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, - set_wococo_flavor, + Rococo, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), + 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, - >( + // for Bulletin + from_grandpa_chain::complex_relay_extrinsic_works::( collator_session_keys(), - bp_bridge_hub_wococo::BRIDGE_HUB_WOCOCO_PARACHAIN_ID, + slot_durations(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, SIBLING_PARACHAIN_ID, - bridge_hub_wococo_config::BridgeHubRococoChainId::get(), - Wococo, - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, - ExistentialDeposit::get(), - executive_init_block, + RococoBulletinChainId::get(), + Rococo, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), construct_and_apply_extrinsic, - set_wococo_flavor, ); } #[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_asset_hub_wococo::BridgeHubWococoBaseFeeInWocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_asset_hub_wococo::BridgeHubWococoBaseFeeInWocs` value", - estimated, - max_expected - ); + pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) + } + + #[test] + pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml similarity index 52% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index dc401e3d8cd31a711a1ab1e3b92301d73244a397..4eb201eedc115edcdb337534c18a04ac5379ae80 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -1,175 +1,135 @@ [package] -name = "asset-hub-kusama-runtime" -version = "0.9.420" +name = "bridge-hub-westend-runtime" +version = "0.2.0" authors.workspace = true edition.workspace = true -description = "Kusama variant of Asset Hub parachain runtime" +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 } [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # 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-asset-conversion-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false} -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-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false} -pallet-nft-fractionalization = { path = "../../../../../substrate/frame/nft-fractionalization", default-features = false} -pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false} -pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false} -pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false} -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false} -pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false, optional = true } -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-uniques = { path = "../../../../../substrate/frame/uniques", 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} +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-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-multisig = { path = "../../../../../substrate/frame/multisig", 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} -sp-weights = { path = "../../../../../substrate/primitives/weights", default-features = false} -# num-traits feature needed for dex integer sq root: -primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info", "num-traits"] } +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 -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false} +westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/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} +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 = "../../../../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-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-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", 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 = "../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } -[dev-dependencies] -asset-test-utils = { path = "../test-utils" } +# 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 } +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-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 } +bp-rococo = { path = "../../../../../bridges/primitives/chain-rococo", default-features = false } +bp-westend = { path = "../../../../../bridges/primitives/chain-westend", default-features = false } +pallet-bridge-grandpa = { path = "../../../../../bridges/modules/grandpa", default-features = false } +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 } -[build-dependencies] -substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } +[dev-dependencies] +static_assertions = "1.1" +bridge-hub-test-utils = { path = "../test-utils" } +bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", features = ["integrity-test"] } +sp-keyring = { path = "../../../../../substrate/primitives/keyring" } [features] -default = [ "std" ] -# When enabled the `state_version` is set to `1`. -# This means that the chain will start using the new state format. The migration is lazy, so -# it requires to write a storage value to use the new state format. To migrate all the other -# storage values that aren't touched the state migration pallet is added as well. -# This pallet will migrate the entire state, controlled through some account. -# -# This feature should be removed when the main-net will be migrated. -state-trie-version-1 = [ "pallet-state-trie-migration" ] -runtime-benchmarks = [ - "assets-common/runtime-benchmarks", - "cumulus-pallet-parachain-system/runtime-benchmarks", - "cumulus-pallet-session-benchmarking/runtime-benchmarks", - "cumulus-pallet-xcmp-queue/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-asset-conversion/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-collator-selection/runtime-benchmarks", - "pallet-multisig/runtime-benchmarks", - "pallet-nft-fractionalization/runtime-benchmarks", - "pallet-nfts/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", - "pallet-state-trie-migration/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "pallet-uniques/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-asset-conversion-tx-payment/try-runtime", - "pallet-asset-conversion/try-runtime", - "pallet-assets/try-runtime", - "pallet-aura/try-runtime", - "pallet-authorship/try-runtime", - "pallet-balances/try-runtime", - "pallet-collator-selection/try-runtime", - "pallet-multisig/try-runtime", - "pallet-nft-fractionalization/try-runtime", - "pallet-nfts/try-runtime", - "pallet-proxy/try-runtime", - "pallet-session/try-runtime", - "pallet-state-trie-migration/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "pallet-uniques/try-runtime", - "pallet-utility/try-runtime", - "pallet-xcm/try-runtime", - "parachain-info/try-runtime", - "polkadot-runtime-common/try-runtime", - "sp-runtime/try-runtime", -] +default = ["std"] std = [ - "assets-common/std", + "bp-asset-hub-rococo/std", + "bp-asset-hub-westend/std", + "bp-bridge-hub-rococo/std", + "bp-bridge-hub-westend/std", + "bp-header-chain/std", + "bp-messages/std", + "bp-parachains/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", - "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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", - "frame-benchmarking?/std", + "frame-benchmarking/std", "frame-executive/std", "frame-support/std", "frame-system-benchmarking?/std", @@ -177,39 +137,37 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", - "pallet-asset-conversion-tx-payment/std", - "pallet-asset-conversion/std", - "pallet-assets/std", "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-bridge-grandpa/std", + "pallet-bridge-messages/std", + "pallet-bridge-parachains/std", + "pallet-bridge-relayers/std", "pallet-collator-selection/std", + "pallet-message-queue/std", "pallet-multisig/std", - "pallet-nft-fractionalization/std", - "pallet-nfts-runtime-api/std", - "pallet-nfts/std", - "pallet-proxy/std", "pallet-session/std", - "pallet-state-trie-migration/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", - "pallet-uniques/std", "pallet-utility/std", "pallet-xcm-benchmarks?/std", + "pallet-xcm-bridge-hub/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-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", @@ -217,11 +175,78 @@ std = [ "sp-storage/std", "sp-transaction-pool/std", "sp-version/std", - "sp-weights/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", + "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] -experimental = [ "pallet-aura/experimental" ] +runtime-benchmarks = [ + "bridge-hub-common/runtime-benchmarks", + "bridge-runtime-common/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-bridge-grandpa/runtime-benchmarks", + "pallet-bridge-messages/runtime-benchmarks", + "pallet-bridge-parachains/runtime-benchmarks", + "pallet-bridge-relayers/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-bridge-hub/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-bridge-grandpa/try-runtime", + "pallet-bridge-messages/try-runtime", + "pallet-bridge-parachains/try-runtime", + "pallet-bridge-relayers/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-bridge-hub/try-runtime", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +# 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/cumulus/parachains/runtimes/assets/asset-hub-kusama/build.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/build.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/build.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/build.rs diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..9bae106395a62a9d1cb6861ed3099b5f3bf963cc --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs @@ -0,0 +1,50 @@ +// 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 that can be used by multiple BridgeHub flavors. +//! All configurations here should be dedicated to a single chain; in other words, we don't need two +//! chains for a single pallet configuration. +//! +//! 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 frame_support::parameter_types; + +parameter_types! { + pub storage RequiredStakeForStakeAndSlash: Balance = 1_000_000; + pub const RelayerStakeLease: u32 = 8; + pub const RelayerStakeReserveId: [u8; 8] = *b"brdgrlrs"; + + pub storage DeliveryRewardInBalance: u64 = 1_000_000; +} + +/// Allows collect and claim rewards for relayers +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = + bp_relayers::PayRewardFromAccount, AccountId>; + type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< + AccountId, + BlockNumber, + Balances, + RelayerStakeReserveId, + RequiredStakeForStakeAndSlash, + RelayerStakeLease, + >; + type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs similarity index 52% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index d09ee24976db05a6ec21ef5ba4fed8fae89519d7..bce722aa5f87d006af0ec71429d6c84eeab4972d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_hub_wococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -14,14 +14,16 @@ // 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 Wococo flavor. +//! Bridge definitions used on BridgeHub with the Westend flavor. use crate::{ - bridge_common_config::{BridgeParachainRococoInstance, DeliveryRewardInBalance}, - weights, AccountId, BridgeWococoToRococoMessages, 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; +use bp_runtime::Chain; use bridge_runtime_common::{ messages, messages::{ @@ -31,7 +33,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, - XcmBlobMessageDispatch, + XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, refund_relayer_extension::{ ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, @@ -39,38 +41,67 @@ use bridge_runtime_common::{ }, }; use codec::Encode; -use frame_support::{parameter_types, traits::PalletInfoAccess}; +use frame_support::{ + parameter_types, + traits::{ConstU32, PalletInfoAccess}, +}; use sp_runtime::RuntimeDebug; use xcm::{ latest::prelude::*, - prelude::{InteriorMultiLocation, NetworkId}, + prelude::{InteriorLocation, NetworkId}, }; -use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; +use xcm_builder::BridgeBlobDispatcher; parameter_types! { + pub const RelayChainHeadersToKeep: u32 = 1024; + pub const ParachainHeadsToKeep: u32 = 64; + + pub const RococoBridgeParachainPalletName: &'static str = "Paras"; + pub const MaxRococoParaHeadDataSize: u32 = bp_rococo::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE; + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + bp_bridge_hub_westend::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = - bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; - pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; - pub BridgeHubWococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Wococo), Parachain(ParachainInfo::parachain_id().into())); - pub BridgeWococoToRococoMessagesPalletInstance: InteriorMultiLocation = X1(PalletInstance(::index() as u8)); + bp_bridge_hub_westend::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + pub const BridgeHubRococoChainId: bp_runtime::ChainId = BridgeHubRococo::ID; + pub BridgeWestendToRococoMessagesPalletInstance: InteriorLocation = [PalletInstance(::index() as u8)].into(); pub RococoGlobalConsensusNetwork: NetworkId = NetworkId::Rococo; - pub ActiveOutboundLanesToBridgeHubRococo: &'static [bp_messages::LaneId] = &[DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO]; + pub RococoGlobalConsensusNetworkLocation: Location = Location::new( + 2, + [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 AssetHubWococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_wococo::ASSET_HUB_WOCOCO_PARACHAIN_ID.into(); + 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(); - pub FromAssetHubWococoToAssetHubRococoRoute: SenderAndLane = SenderAndLane::new( - ParentThen(X1(Parachain(AssetHubWococoParaId::get().into()))).into(), - DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO, + // 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([Parachain(AssetHubWestendParaId::get().into())].into()).into(), + XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, ); + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorLocation))> = sp_std::vec![ + ( + FromAssetHubWestendToAssetHubRococoRoute::get(), + (RococoGlobalConsensusNetwork::get(), [Parachain(AssetHubRococoParaId::get().into())].into()) + ) + ]; pub CongestedMessage: Xcm<()> = build_congestion_message(true).into(); - pub UncongestedMessage: Xcm<()> = build_congestion_message(false).into(); + + pub BridgeHubRococoLocation: Location = Location::new( + 2, + [ + GlobalConsensus(RococoGlobalConsensusNetwork::get()), + Parachain(::PARACHAIN_ID) + ] + ); } +pub const XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO: LaneId = LaneId([0, 0, 0, 2]); fn build_congestion_message(is_congested: bool) -> sp_std::vec::Vec> { sp_std::vec![ @@ -78,9 +109,9 @@ fn build_congestion_message(is_congested: bool) -> sp_std::vec::Vec(is_congested: bool) -> sp_std::vec::Vec; -/// Messages delivery proof for RococoBridge Hub -> Wococo BridgeHub messages. +/// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. pub type ToRococoBridgeHubMessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof; /// Dispatches received XCM messages from other bridge -pub type OnBridgeHubWococoBlobDispatcher = BridgeBlobDispatcher< - XcmRouter, - BridgeHubWococoUniversalLocation, - BridgeWococoToRococoMessagesPalletInstance, ->; +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 = FromAssetHubWococoToAssetHubRococoRoute; - type ToSourceChainSender = crate::XcmRouter; + type ToSourceChainSender = XcmRouter; type CongestedMessage = CongestedMessage; type UncongestedMessage = UncongestedMessage; } -pub const DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO: LaneId = LaneId([0, 0, 0, 1]); /// On messages delivered callback. -pub type OnMessagesDelivered = XcmBlobHaulerAdapter; +type OnMessagesDelivered = XcmBlobHaulerAdapter; -/// Messaging Bridge configuration for BridgeHubWococo -> BridgeHubRococo +/// Messaging Bridge configuration for BridgeHubWestend -> BridgeHubRococo pub struct WithBridgeHubRococoMessageBridge; impl MessageBridge for WithBridgeHubRococoMessageBridge { const BRIDGED_MESSAGES_PALLET_NAME: &'static str = - bp_bridge_hub_wococo::WITH_BRIDGE_HUB_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_NAME; - type ThisChain = BridgeHubWococo; + bp_bridge_hub_westend::WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME; + type ThisChain = BridgeHubWestend; type BridgedChain = BridgeHubRococo; type BridgedHeaderChain = pallet_bridge_parachains::ParachainHeaders< Runtime, @@ -140,12 +163,8 @@ impl MessageBridge for WithBridgeHubRococoMessageBridge { >; } -/// Message verifier for BridgeHubRococo messages sent from BridgeHubWococo -pub type ToBridgeHubRococoMessageVerifier = - messages::source::FromThisChainMessageVerifier; - -/// Maximal outbound payload size of BridgeHubWococo -> BridgeHubRococo messages. -pub type ToBridgeHubRococoMaximalOutboundPayloadSize = +/// Maximal outbound payload size of BridgeHubWestend -> BridgeHubRococo messages. +type ToBridgeHubRococoMaximalOutboundPayloadSize = messages::source::FromThisChainMaximalOutboundPayloadSize; /// BridgeHubRococo chain from message lane point of view. @@ -158,40 +177,62 @@ impl UnderlyingChainProvider for BridgeHubRococo { impl messages::BridgedChainWithMessages for BridgeHubRococo {} -/// BridgeHubWococo chain from message lane point of view. +/// BridgeHubWestend chain from message lane point of view. #[derive(RuntimeDebug, Clone, Copy)] -pub struct BridgeHubWococo; +pub struct BridgeHubWestend; -impl UnderlyingChainProvider for BridgeHubWococo { - type Chain = bp_bridge_hub_wococo::BridgeHubWococo; +impl UnderlyingChainProvider for BridgeHubWestend { + type Chain = bp_bridge_hub_westend::BridgeHubWestend; } -impl ThisChainWithMessages for BridgeHubWococo { +impl ThisChainWithMessages for BridgeHubWestend { type RuntimeOrigin = RuntimeOrigin; } /// Signed extension that refunds relayers that are delivering messages from the Rococo parachain. -pub type BridgeRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter< +pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = RefundSignedExtensionAdapter< RefundBridgedParachainMessages< Runtime, RefundableParachain, - RefundableMessagesLane, + RefundableMessagesLane< + WithBridgeHubRococoMessagesInstance, + AssetHubWestendToAssetHubRococoMessagesLane, + >, ActualFeeRefund, PriorityBoostPerMessage, - StrBridgeRefundBridgeHubRococoMessages, + StrOnBridgeHubWestendRefundBridgeHubRococoMessages, >, >; -bp_runtime::generate_static_str_provider!(BridgeRefundBridgeHubRococoMessages); +bp_runtime::generate_static_str_provider!(OnBridgeHubWestendRefundBridgeHubRococoMessages); -parameter_types! { - pub const BridgeHubRococoMessagesLane: bp_messages::LaneId = DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO; +/// Add GRANDPA bridge pallet to track Rococo relay chain. +pub type BridgeGrandpaRococoInstance = pallet_bridge_grandpa::Instance1; +impl pallet_bridge_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BridgedChain = bp_rococo::Rococo; + type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type HeadersToKeep = RelayChainHeadersToKeep; + type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; } -/// Add XCM messages support for BridgeHubWococo to support Wococo->Rococo XCM messages -pub type WithBridgeHubRococoMessagesInstance = pallet_bridge_messages::Instance2; +/// Add parachain bridge pallet to track Rococo BridgeHub parachain +pub type BridgeParachainRococoInstance = pallet_bridge_parachains::Instance1; +impl pallet_bridge_parachains::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = weights::pallet_bridge_parachains::WeightInfo; + type BridgesGrandpaPalletInstance = BridgeGrandpaRococoInstance; + type ParasPalletName = RococoBridgeParachainPalletName; + type ParaStoredHeaderDataBuilder = + SingleParaStoredHeaderDataBuilder; + type HeadsToKeep = ParachainHeadsToKeep; + type MaxParaHeadDataSize = MaxRococoParaHeadDataSize; +} + +/// Add XCM messages support for BridgeHubWestend to support Westend->Rococo XCM messages +pub type WithBridgeHubRococoMessagesInstance = pallet_bridge_messages::Instance1; impl pallet_bridge_messages::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_bridge_messages_wococo_to_rococo::WeightInfo; + type WeightInfo = weights::pallet_bridge_messages::WeightInfo; type BridgedChainId = BridgeHubRococoChainId; type ActiveOutboundLanes = ActiveOutboundLanesToBridgeHubRococo; type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; @@ -205,7 +246,6 @@ impl pallet_bridge_messages::Config for Run type DeliveryPayments = (); type TargetHeaderChain = TargetHeaderChainAdapter; - type LaneMessageVerifier = ToBridgeHubRococoMessageVerifier; type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubRococoMessagesInstance, @@ -214,20 +254,31 @@ impl pallet_bridge_messages::Config for Run type SourceHeaderChain = SourceHeaderChainAdapter; type MessageDispatch = XcmBlobMessageDispatch< - OnBridgeHubWococoBlobDispatcher, + FromRococoMessageBlobDispatcher, Self::WeightInfo, cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider< - AssetHubWococoParaId, + AssetHubWestendParaId, Runtime, >, >; 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::*; - use crate::bridge_common_config::BridgeGrandpaRococoInstance; use bridge_runtime_common::{ assert_complete_bridge_types, integrity::{ @@ -236,7 +287,8 @@ mod tests { AssertCompleteBridgeConstants, }, }; - use parachains_common::{wococo, Balance}; + use parachains_common::Balance; + use testnet_parachains_constants::westend; /// 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 @@ -247,18 +299,18 @@ mod tests { /// /// 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 * wococo::currency::UNITS; + const FEE_BOOST_PER_MESSAGE: Balance = 2 * westend::currency::UNITS; #[test] - fn ensure_bridge_hub_wococo_message_lane_weights_are_correct() { + fn ensure_bridge_hub_westend_message_lane_weights_are_correct() { check_message_lane_weights::< - bp_bridge_hub_wococo::BridgeHubWococo, + bp_bridge_hub_westend::BridgeHubWestend, Runtime, WithBridgeHubRococoMessagesInstance, >( bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE, - bp_bridge_hub_wococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, - bp_bridge_hub_wococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + bp_bridge_hub_westend::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + bp_bridge_hub_westend::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, true, ); } @@ -270,7 +322,7 @@ mod tests { with_bridged_chain_grandpa_instance: BridgeGrandpaRococoInstance, with_bridged_chain_messages_instance: WithBridgeHubRococoMessagesInstance, bridge: WithBridgeHubRococoMessageBridge, - this_chain: bp_wococo::Wococo, + this_chain: bp_westend::Westend, bridged_chain: bp_rococo::Rococo, ); @@ -281,22 +333,22 @@ mod tests { WithBridgeHubRococoMessageBridge, >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { - block_length: bp_bridge_hub_wococo::BlockLength::get(), - block_weights: bp_bridge_hub_wococo::BlockWeights::get(), + block_length: bp_bridge_hub_westend::BlockLength::get(), + block_weights: bp_bridge_hub_westend::BlockWeightsForAsyncBacking::get(), }, messages_pallet_constants: AssertBridgeMessagesPalletConstants { max_unrewarded_relayers_in_bridged_confirmation_tx: bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, max_unconfirmed_messages_in_bridged_confirmation_tx: bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, - bridged_chain_id: bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID, + bridged_chain_id: BridgeHubRococo::ID, }, pallet_names: AssertBridgePalletNames { with_this_chain_messages_pallet_name: - bp_bridge_hub_wococo::WITH_BRIDGE_HUB_ROCOCO_TO_WOCOCO_MESSAGES_PALLET_NAME, + bp_bridge_hub_westend::WITH_BRIDGE_HUB_WESTEND_MESSAGES_PALLET_NAME, with_bridged_chain_grandpa_pallet_name: bp_rococo::WITH_ROCOCO_GRANDPA_PALLET_NAME, with_bridged_chain_messages_pallet_name: - bp_bridge_hub_rococo::WITH_BRIDGE_HUB_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_NAME, + bp_bridge_hub_rococo::WITH_BRIDGE_HUB_ROCOCO_MESSAGES_PALLET_NAME, }, }); @@ -307,10 +359,10 @@ mod tests { >(FEE_BOOST_PER_MESSAGE); assert_eq!( - BridgeWococoToRococoMessagesPalletInstance::get(), - X1(PalletInstance( - bp_bridge_hub_wococo::WITH_BRIDGE_WOCOCO_TO_ROCOCO_MESSAGES_PALLET_INDEX - )) + BridgeWestendToRococoMessagesPalletInstance::get(), + [PalletInstance( + bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX + )] ); } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..11ab9aecc61748c2c96136e796e727c53a9120d9 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -0,0 +1,1176 @@ +// 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 Hub Westend Runtime +//! +//! This runtime currently supports bridging between: +//! - Rococo <> Westend + +#![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")); + +pub mod bridge_common_config; +pub mod bridge_to_rococo_config; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::ParaId; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; + +use sp_std::prelude::*; +#[cfg(feature = "std")] +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, + genesis_builder_helper::{build_config, create_default_config}, + parameter_types, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, TransformOrigin}, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +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(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; + +use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use xcm::latest::prelude::*; + +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; + +use parachains_common::{ + impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, +}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; + +/// 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, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +/// Migrations to apply on runtime upgrade. +pub type Migrations = ( + pallet_collator_selection::migration::v1::MigrateToV1, + pallet_multisig::migrations::v1::MigrateToV1, + InitStorageVersions, + // unreleased + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); + +/// Migration to initialize storage versions for pallets added after genesis. +/// +/// Ideally this would be done automatically (see +/// ), but it probably won't be ready for some +/// time and it's beneficial to get try-runtime-cli on-runtime-upgrade checks into the CI, so we're +/// doing it manually. +pub struct InitStorageVersions; + +impl frame_support::traits::OnRuntimeUpgrade for InitStorageVersions { + fn on_runtime_upgrade() -> Weight { + use frame_support::traits::{GetStorageVersion, StorageVersion}; + use sp_runtime::traits::Saturating; + + let mut writes = 0; + + if PolkadotXcm::on_chain_storage_version() == StorageVersion::new(0) { + PolkadotXcm::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + if Balances::on_chain_storage_version() == StorageVersion::new(0) { + Balances::in_code_storage_version().put::(); + writes.saturating_inc(); + } + + ::DbWeight::get().reads_writes(2, writes) + } +} + +/// 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!("bridge-hub-westend"), + impl_name: create_runtime_str!("bridge-hub-westend"), + authoring_version: 1, + spec_version: 1_008_000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 4, + 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: u16 = 42; +} + +// Configure FRAME pallets to include in runtime. + +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The index type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The 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; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<0>; + 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 { + /// The type for recording an account's balance. + type Balance = Balance; + type DustRemoval = (); + /// The ubiquitous event type. + 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 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); +} + +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 OutboundXcmpMessageSource = XcmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + type ReservedDmpWeight = ReservedDmpWeight; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +impl parachain_info::Config for Runtime {} + +parameter_types! { + 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; + #[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! { + /// The asset ID for the asset that we use to pay for message delivery fees. + pub FeeAssetId: AssetId = AssetId(xcm_config::WestendLocation::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 = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; +} + +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +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; + type SlotDuration = ConstU64; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; +} + +pub type CollatorSelectionUpdateOrigin = EnsureRoot; + +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; +} + +// 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 = 2, + ParachainInfo: parachain_info = 3, + + // Monetary stuff. + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + + // Collator support. The order of these 4 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 helpers. + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + + // Handy utilities. + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 36, + + // Bridging stuff. + BridgeRelayers: pallet_bridge_relayers = 41, + BridgeRococoGrandpa: pallet_bridge_grandpa:: = 42, + BridgeRococoParachains: pallet_bridge_parachains:: = 43, + BridgeRococoMessages: pallet_bridge_messages:: = 44, + XcmOverBridgeHubRococo: pallet_xcm_bridge_hub:: = 45, + + // Message Queue. Importantly, is registered last so that messages are processed after + // the `on_initialize` hooks of bridging pallets. + MessageQueue: pallet_message_queue = 250, + } +); + +bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { + RuntimeCall, AccountId, + // Grandpa + BridgeRococoGrandpa, + // Parachains + BridgeRococoParachains, + // Messages + BridgeRococoMessages +} + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + frame_benchmarking::define_benchmarks!( + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] + [pallet_multisig, Multisig] + [pallet_session, SessionBench::] + [pallet_utility, Utility] + [pallet_timestamp, Timestamp] + [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] + [cumulus_pallet_xcmp_queue, XcmpQueue] + // XCM + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + // Bridge pallets + [pallet_bridge_relayers, BridgeRelayersBench::] + [pallet_bridge_grandpa, RococoFinality] + [pallet_bridge_parachains, WithinRococo] + [pallet_bridge_messages, WestendToRococo] + ); +} + +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) + } + + fn authorities() -> Vec { + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_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) + } + } + + impl bp_rococo::RococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeRococoGrandpa::best_finalized() + } + fn synced_headers_grandpa_info( + ) -> Vec> { + BridgeRococoGrandpa::synced_headers_grandpa_info() + } + } + + impl bp_bridge_hub_rococo::BridgeHubRococoFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgeRococoParachains::best_parachain_head_id::< + bp_bridge_hub_rococo::BridgeHubRococo + >().unwrap_or(None) + } + } + + impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi 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_rococo_config::WithBridgeHubRococoMessagesInstance, + >(lane, messages) + } + } + + impl bp_bridge_hub_rococo::ToBridgeHubRococoOutboundLaneApi 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_rococo_config::WithBridgeHubRococoMessagesInstance, + >(lane, begin, end) + } + } + + #[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::; + + use pallet_bridge_relayers::benchmarking::Pallet as BridgeRelayersBench; + // Change weight file names. + type RococoFinality = BridgeRococoGrandpa; + type WithinRococo = pallet_bridge_parachains::benchmarking::Pallet::; + type WestendToRococo = pallet_bridge_messages::benchmarking::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 PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between BH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled on BH. + None + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + + use xcm::latest::prelude::*; + use xcm_config::WestendLocation; + + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + WestendLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(WestendLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just assets according to relay chain. + let assets: Vec = vec![ + Asset { + id: AssetId(WestendLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + WestendLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(WestendLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(WestendLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type TransactAsset = Balances; + type RuntimeCall = RuntimeCall; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((WestendLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(WestendLocation::get()) + } + + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = WestendLocation::get(); + let assets: Assets = (AssetId(WestendLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(WestendLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + // 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, + [Parachain(bridge_to_rococo_config::AssetHubRococoParaId::get().into())].into() + ) + ) + } + + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + type RococoFinality = BridgeRococoGrandpa; + type WithinRococo = pallet_bridge_parachains::benchmarking::Pallet::; + type WestendToRococo = pallet_bridge_messages::benchmarking::Pallet ::; + + use bridge_runtime_common::messages_benchmarking::{ + prepare_message_delivery_proof_from_parachain, + prepare_message_proof_from_parachain, + generate_xcm_builder_bridge_message_sample, + }; + use pallet_bridge_messages::benchmarking::{ + Config as BridgeMessagesConfig, + MessageDeliveryProofParams, + MessageProofParams, + }; + + impl BridgeMessagesConfig for Runtime { + fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { + let bench_lane_id = >::bench_lane_id(); + let bridged_chain_id = bridge_to_rococo_config::BridgeHubRococoChainId::get(); + pallet_bridge_relayers::Pallet::::relayer_reward( + relayer, + bp_relayers::RewardsAccountParams::new( + bench_lane_id, + bridged_chain_id, + bp_relayers::RewardsAccountOwner::BridgedChain + ) + ).is_some() + } + + fn prepare_message_proof( + params: MessageProofParams, + ) -> (bridge_to_rococo_config::FromRococoBridgeHubMessagesProof, 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_parachain::< + Runtime, + bridge_to_rococo_config::BridgeGrandpaRococoInstance, + bridge_to_rococo_config::WithBridgeHubRococoMessageBridge, + >(params, generate_xcm_builder_bridge_message_sample([GlobalConsensus(Westend), Parachain(42)].into())) + } + + fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams, + ) -> bridge_to_rococo_config::ToRococoBridgeHubMessagesDeliveryProof { + prepare_message_delivery_proof_from_parachain::< + Runtime, + bridge_to_rococo_config::BridgeGrandpaRococoInstance, + bridge_to_rococo_config::WithBridgeHubRococoMessageBridge, + >(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::{ + Pallet as BridgeRelayersBench, + Config as BridgeRelayersConfig, + }; + + impl BridgeParachainsConfig for Runtime { + fn parachains() -> Vec { + use bp_runtime::Parachain; + vec![bp_polkadot_core::parachains::ParaId(bp_bridge_hub_rococo::BridgeHubRococo::PARACHAIN_ID)] + } + + fn prepare_parachain_heads_proof( + parachains: &[bp_polkadot_core::parachains::ParaId], + parachain_head_size: u32, + proof_size: bp_runtime::StorageProofSize, + ) -> ( + pallet_bridge_parachains::RelayBlockNumber, + pallet_bridge_parachains::RelayBlockHash, + bp_polkadot_core::parachains::ParaHeadsProof, + Vec<(bp_polkadot_core::parachains::ParaId, bp_polkadot_core::parachains::ParaHash)>, + ) { + prepare_parachain_heads_proof::( + parachains, + parachain_head_size, + proof_size, + ) + } + } + + impl BridgeRelayersConfig for Runtime { + fn prepare_rewards_account( + account_params: bp_relayers::RewardsAccountParams, + reward: Balance, + ) { + let rewards_account = bp_relayers::PayRewardFromAccount::< + Balances, + AccountId + >::rewards_account(account_params); + Self::deposit_account(rewards_account, reward); + } + + fn deposit_account(account: AccountId, balance: Balance) { + use frame_support::traits::fungible::Mutate; + Balances::mint_into(&account, balance.saturating_add(ExistentialDeposit::get())).unwrap(); + } + } + + 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::, +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::Encode; + use sp_runtime::{ + generic::Era, + traits::{SignedExtension, Zero}, + }; + + #[test] + fn ensure_signed_extension_definition_is_compatible_with_relay() { + use bp_polkadot_core::SuffixedCommonSignedExtensionExt; + + sp_io::TestExternalities::default().execute_with(|| { + frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); + let payload: 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(10), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(10), + BridgeRejectObsoleteHeadersAndMessages, + ( + bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(), + ), + ); + + { + let bh_indirect_payload = bp_bridge_hub_westend::SignedExtension::from_params( + VERSION.spec_version, + VERSION.transaction_version, + bp_runtime::TransactionEra::Immortal, + System::block_hash(BlockNumber::zero()), + 10, + 10, + (((), ()), ((), ())), + ); + assert_eq!(payload.encode(), bh_indirect_payload.encode()); + assert_eq!( + payload.additional_signed().unwrap().encode(), + bh_indirect_payload.additional_signed().unwrap().encode() + ) + } + }); + } +} diff --git a/cumulus/parachain-template/runtime/src/weights/block_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs similarity index 100% rename from cumulus/parachain-template/runtime/src/weights/block_weights.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/block_weights.rs diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc480c391636a92aad6e303a515524b5ce7ff2b1 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,80 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: 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 +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights +// --steps +// 50 +// --repeat +// 20 + +#![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_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)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..70fc3617bce93e2fa9246a649cd3adf81c1cda23 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,148 @@ +// 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 `cumulus_pallet_xcmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// bridge-hub-rococo-dev +// --output +// cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_xcmp_queue.rs +// --extrinsic +// + +#![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/parachain-template/runtime/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs similarity index 100% rename from cumulus/parachain-template/runtime/src/weights/extrinsic_weights.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/extrinsic_weights.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/frame_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs similarity index 68% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/frame_system.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs index 713c33d34f77f5cc3a5e659a2b31dbbfd5721931..7db371d6af93068467cbeafaa454330325da7791 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=frame_system // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,7 +56,7 @@ impl frame_system::WeightInfo for WeightInfo { // Measured: `0` // Estimated: `0` // Minimum execution time: 1_956_000 picoseconds. - Weight::from_parts(3_441_280, 0) + Weight::from_parts(2_974_450, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 .saturating_add(Weight::from_parts(388, 0).saturating_mul(b.into())) @@ -66,11 +66,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_267_000 picoseconds. - Weight::from_parts(7_462_000, 0) + // Minimum execution time: 7_432_000 picoseconds. + Weight::from_parts(7_686_000, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_816, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(1_767, 0).saturating_mul(b.into())) } /// Storage: `System::Digest` (r:1 w:1) /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -80,8 +80,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 3_757_000 picoseconds. - Weight::from_parts(4_021_000, 0) + // Minimum execution time: 3_715_000 picoseconds. + Weight::from_parts(3_983_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -102,8 +102,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `119` // Estimated: `1604` - // Minimum execution time: 97_958_650_000 picoseconds. - Weight::from_parts(102_129_539_000, 0) + // Minimum execution time: 99_688_458_000 picoseconds. + Weight::from_parts(103_623_061_000, 0) .saturating_add(Weight::from_parts(0, 1604)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -115,11 +115,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_327_000 picoseconds. - Weight::from_parts(2_511_000, 0) + // Minimum execution time: 2_318_000 picoseconds. + Weight::from_parts(2_421_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_186 - .saturating_add(Weight::from_parts(755_085, 0).saturating_mul(i.into())) + // Standard Error: 2_168 + .saturating_add(Weight::from_parts(765_555, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -129,11 +129,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_114_000 picoseconds. - Weight::from_parts(2_177_000, 0) + // Minimum execution time: 2_162_000 picoseconds. + Weight::from_parts(2_228_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_174 - .saturating_add(Weight::from_parts(584_644, 0).saturating_mul(i.into())) + // Standard Error: 951 + .saturating_add(Weight::from_parts(569_773, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -141,15 +141,42 @@ impl frame_system::WeightInfo for WeightInfo { /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `84 + p * (69 ±0)` - // Estimated: `77 + p * (70 ±0)` - // Minimum execution time: 3_799_000 picoseconds. - Weight::from_parts(3_910_000, 0) - .saturating_add(Weight::from_parts(0, 77)) - // Standard Error: 1_968 - .saturating_add(Weight::from_parts(1_220_745, 0).saturating_mul(p.into())) + // Measured: `68 + p * (69 ±0)` + // Estimated: `71 + p * (70 ±0)` + // Minimum execution time: 3_795_000 picoseconds. + Weight::from_parts(3_895_000, 0) + .saturating_add(Weight::from_parts(0, 71)) + // Standard Error: 1_869 + .saturating_add(Weight::from_parts(1_209_251, 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/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a65ee31d3e55ff8135fdd7dec35120e0a463409b --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -0,0 +1,73 @@ +// 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. + +//! Expose the auto generated weight files. + +use ::pallet_bridge_messages::WeightInfoExt as MessagesWeightInfoExt; +use ::pallet_bridge_parachains::WeightInfoExt as ParachainsWeightInfoExt; + +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_bridge_grandpa; +pub mod pallet_bridge_messages; +pub mod pallet_bridge_parachains; +pub mod pallet_bridge_relayers; +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; + +use crate::Runtime; +use frame_support::weights::Weight; + +// import trait from dependency module +use ::pallet_bridge_relayers::WeightInfoExt as _; + +impl MessagesWeightInfoExt for pallet_bridge_messages::WeightInfo { + fn expected_extra_storage_proof_size() -> u32 { + bp_bridge_hub_rococo::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 ParachainsWeightInfoExt for pallet_bridge_parachains::WeightInfo { + fn expected_extra_storage_proof_size() -> u32 { + bp_bridge_hub_rococo::EXTRA_STORAGE_PROOF_SIZE + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_balances.rs similarity index 73% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_balances.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_balances.rs index 5abe64bb411750f714e65038a2c750932240f2f7..3afef6564bdb8fec4e435948cf8cadb928bbf773 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-8idpd4bs-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-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --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-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --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)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 54_518_000 picoseconds. - Weight::from_parts(55_244_000, 0) + // Minimum execution time: 42_912_000 picoseconds. + Weight::from_parts(43_690_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_152_000 picoseconds. - Weight::from_parts(41_084_000, 0) + // Minimum execution time: 33_823_000 picoseconds. + Weight::from_parts(34_415_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 15_234_000 picoseconds. - Weight::from_parts(15_576_000, 0) + // Minimum execution time: 13_226_000 picoseconds. + Weight::from_parts(13_557_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 22_173_000 picoseconds. - Weight::from_parts(22_964_000, 0) + // Minimum execution time: 18_055_000 picoseconds. + Weight::from_parts(18_407_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 56_636_000 picoseconds. - Weight::from_parts(57_316_000, 0) + // Minimum execution time: 44_442_000 picoseconds. + Weight::from_parts(45_101_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 50_829_000 picoseconds. - Weight::from_parts(51_264_000, 0) + // Minimum execution time: 42_485_000 picoseconds. + Weight::from_parts(43_157_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 17_887_000 picoseconds. - Weight::from_parts(18_365_000, 0) + // Minimum execution time: 16_002_000 picoseconds. + Weight::from_parts(16_425_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_754_000 picoseconds. - Weight::from_parts(17_237_000, 0) + // Minimum execution time: 14_526_000 picoseconds. + Weight::from_parts(14_825_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 15_088 - .saturating_add(Weight::from_parts(15_392_959, 0).saturating_mul(u.into())) + // Standard Error: 10_967 + .saturating_add(Weight::from_parts(13_376_293, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_151_000 picoseconds. + Weight::from_parts(5_419_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa_rococo_finality.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs similarity index 83% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa_rococo_finality.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs index e6d7cf53dc9b556aef71a47f1fff85d8dedbdb43..e87ed668dfc7acb1a92a7535d92392a272370277 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa_rococo_finality.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-22, 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-t2sp1qqs-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,19 +62,17 @@ impl pallet_bridge_grandpa::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: // Measured: `231 + p * (60 ±0)` // Estimated: `51735` - // Minimum execution time: 292_425_000 picoseconds. - Weight::from_parts(294_025_000, 0) + // Minimum execution time: 303_549_000 picoseconds. + Weight::from_parts(306_232_000, 0) .saturating_add(Weight::from_parts(0, 51735)) - // Standard Error: 3_450 - .saturating_add(Weight::from_parts(55_743_036, 0).saturating_mul(p.into())) - // Standard Error: 26_622 - .saturating_add(Weight::from_parts(327_706, 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-rococo/src/weights/pallet_bridge_messages_wococo_to_rococo.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs similarity index 76% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_wococo_to_rococo.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs index 12015b962fe6da22bbc46688a29907e3207ad90e..305a8726fa1bb67da8ac239d9f2b66e795582fe5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_wococo_to_rococo.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-22, 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-t2sp1qqs-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)] @@ -52,18 +52,18 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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: `467` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 41_613_000 picoseconds. - Weight::from_parts(42_942_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)) @@ -72,18 +72,18 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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: `467` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 52_657_000 picoseconds. - Weight::from_parts(54_020_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)) @@ -92,18 +92,18 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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: `467` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 47_484_000 picoseconds. - Weight::from_parts(48_318_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)) @@ -112,16 +112,16 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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: `435` + // Measured: `433` // Estimated: `52645` - // Minimum execution time: 40_860_000 picoseconds. - Weight::from_parts(41_720_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)) @@ -130,24 +130,24 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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: `435` + // Measured: `433` // Estimated: `52645` - // Minimum execution time: 75_743_000 picoseconds. - Weight::from_parts(76_862_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: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::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: `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) @@ -156,18 +156,18 @@ 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: `339` - // Estimated: `3804` - // Minimum execution time: 30_195_000 picoseconds. - Weight::from_parts(31_047_000, 0) - .saturating_add(Weight::from_parts(0, 3804)) + // 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: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::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: `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) @@ -176,18 +176,18 @@ 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: `339` - // Estimated: `3804` - // Minimum execution time: 30_410_000 picoseconds. - Weight::from_parts(31_057_000, 0) - .saturating_add(Weight::from_parts(0, 3804)) + // 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: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::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: `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) @@ -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: `339` + // Measured: `337` // Estimated: `6086` - // Minimum execution time: 33_096_000 picoseconds. - Weight::from_parts(33_710_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)) @@ -208,8 +208,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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: `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) @@ -227,16 +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]`. fn receive_single_message_proof_with_dispatch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `635` + // Measured: `633` // Estimated: `52645` - // Minimum execution time: 63_158_000 picoseconds. - Weight::from_parts(63_769_302, 0) + // Minimum execution time: 67_047_000 picoseconds. + Weight::from_parts(68_717_105, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 35 - .saturating_add(Weight::from_parts(6_896, 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-rococo/src/weights/pallet_bridge_parachains_within_rococo.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs similarity index 50% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains_within_rococo.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs index 1a125f4cd0e3597f38a0b03638209640fec34959..9819bd4065411bec6799de3f2aa41c318f53a122 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains_within_rococo.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-22, 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-t2sp1qqs-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,64 +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]`. + /// 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: `294` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 31_409_000 picoseconds. - Weight::from_parts(32_561_631, 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: `294` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 32_828_000 picoseconds. - Weight::from_parts(33_681_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: `294` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 65_531_000 picoseconds. - Weight::from_parts(66_418_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 new file mode 100644 index 0000000000000000000000000000000000000000..ed96f0cd87c9e73ee8c842ab9f4f5d60bf81c2ac --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_relayers.rs @@ -0,0 +1,123 @@ +// 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_bridge_relayers` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-12, 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-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_bridge_relayers +// --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)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_bridge_relayers`. +pub struct WeightInfo(PhantomData); +impl pallet_bridge_relayers::WeightInfo for WeightInfo { + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) + /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn claim_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `207` + // Estimated: `3593` + // 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)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x1e8445dc201eeb8560e5579a5dd54655` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x1e8445dc201eeb8560e5579a5dd54655` (r:1 w:0) + /// Storage: `Balances::Reserves` (r:1 w:1) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + fn register() -> Weight { + // Proof Size summary in bytes: + // Measured: `61` + // Estimated: `4714` + // 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)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Reserves` (r:1 w:1) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + fn deregister() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `4714` + // 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)) + } + /// Storage: `BridgeRelayers::RegisteredRelayers` (r:1 w:1) + /// Proof: `BridgeRelayers::RegisteredRelayers` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Reserves` (r:1 w:1) + /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn slash_and_deregister() -> Weight { + // Proof Size summary in bytes: + // Measured: `263` + // Estimated: `4714` + // 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)) + } + /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) + /// 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` + // Estimated: `3538` + // 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-kusama/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_collator_selection.rs similarity index 79% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_collator_selection.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_collator_selection.rs index fa0ac199ca2c06d07a757e191d9a00e9e1f9cb7e..9dcee77082b99f586707a77a540cf6b13bd2be16 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_collator_selection.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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-kusama-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=pallet_collator_selection // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -59,11 +59,11 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `196 + b * (79 ±0)` // Estimated: `1187 + b * (2555 ±0)` - // Minimum execution time: 14_329_000 picoseconds. - Weight::from_parts(11_605_842, 0) + // Minimum execution time: 14_728_000 picoseconds. + Weight::from_parts(11_562_750, 0) .saturating_add(Weight::from_parts(0, 1187)) - // Standard Error: 4_784 - .saturating_add(Weight::from_parts(3_297_183, 0).saturating_mul(b.into())) + // Standard Error: 7_121 + .saturating_add(Weight::from_parts(3_300_884, 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, 2555).saturating_mul(b.into())) @@ -82,13 +82,13 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `757 + b * (32 ±0) + c * (53 ±0)` // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` - // Minimum execution time: 47_110_000 picoseconds. - Weight::from_parts(45_234_418, 0) + // Minimum execution time: 47_549_000 picoseconds. + Weight::from_parts(45_432_273, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 14_452 - .saturating_add(Weight::from_parts(156_031, 0).saturating_mul(b.into())) - // Standard Error: 2_739 - .saturating_add(Weight::from_parts(216_162, 0).saturating_mul(c.into())) + // Standard Error: 11_457 + .saturating_add(Weight::from_parts(216_469, 0).saturating_mul(b.into())) + // Standard Error: 2_171 + .saturating_add(Weight::from_parts(197_614, 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())) @@ -103,11 +103,11 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `82 + b * (32 ±0)` // Estimated: `6287` - // Minimum execution time: 15_326_000 picoseconds. - Weight::from_parts(14_914_611, 0) + // Minimum execution time: 15_417_000 picoseconds. + Weight::from_parts(15_357_487, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_274 - .saturating_add(Weight::from_parts(201_234, 0).saturating_mul(b.into())) + // Standard Error: 4_074 + .saturating_add(Weight::from_parts(187_410, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,19 +117,19 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_288_000 picoseconds. - Weight::from_parts(7_472_000, 0) + // Minimum execution time: 7_407_000 picoseconds. + Weight::from_parts(7_657_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() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_137_000 picoseconds. - Weight::from_parts(7_374_000, 0) + // Minimum execution time: 7_514_000 picoseconds. + Weight::from_parts(7_695_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -150,11 +150,11 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `740 + c * (52 ±0)` // Estimated: `6287 + c * (54 ±0)` - // Minimum execution time: 40_718_000 picoseconds. - Weight::from_parts(43_911_837, 0) + // Minimum execution time: 41_711_000 picoseconds. + Weight::from_parts(45_690_780, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 3_053 - .saturating_add(Weight::from_parts(229_337, 0).saturating_mul(c.into())) + // Standard Error: 2_800 + .saturating_add(Weight::from_parts(194_907, 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, 54).saturating_mul(c.into())) @@ -170,11 +170,35 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `334 + c * (49 ±0)` // Estimated: `6287` - // Minimum execution time: 32_953_000 picoseconds. - Weight::from_parts(34_817_275, 0) + // Minimum execution time: 33_901_000 picoseconds. + Weight::from_parts(35_875_905, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_476 - .saturating_add(Weight::from_parts(198_023, 0).saturating_mul(c.into())) + // Standard Error: 1_968 + .saturating_add(Weight::from_parts(200_283, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .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)) } @@ -188,8 +212,8 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `155` // Estimated: `6196` - // Minimum execution time: 45_130_000 picoseconds. - Weight::from_parts(46_733_000, 0) + // Minimum execution time: 47_475_000 picoseconds. + Weight::from_parts(48_265_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) @@ -210,11 +234,11 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `2263 + c * (97 ±0) + r * (115 ±0)` // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` - // Minimum execution time: 16_690_000 picoseconds. - Weight::from_parts(17_188_000, 0) + // Minimum execution time: 16_907_000 picoseconds. + Weight::from_parts(17_203_000, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 345_320 - .saturating_add(Weight::from_parts(15_166_422, 0).saturating_mul(c.into())) + // Standard Error: 354_098 + .saturating_add(Weight::from_parts(15_341_462, 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)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..2fcd573ceb277116bda67180da8e0701593ab453 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-24, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! 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 +// pallet_message_queue +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights + +#![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: 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)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs similarity index 76% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_multisig.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs index 4625c4f474ea2f807652e549898a0f38be510554..91840ae0c6d77cfb4507d5f7d7b8f2aca7c84b35 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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-polkadot-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-polkadot-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=pallet_multisig // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_284_000 picoseconds. - Weight::from_parts(14_761_699, 0) + // Minimum execution time: 13_958_000 picoseconds. + Weight::from_parts(14_501_711, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7 - .saturating_add(Weight::from_parts(491, 0).saturating_mul(z.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(626, 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`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_043_000 picoseconds. - Weight::from_parts(32_303_705, 0) + // Minimum execution time: 44_067_000 picoseconds. + Weight::from_parts(33_432_998, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_280 - .saturating_add(Weight::from_parts(133_233, 0).saturating_mul(s.into())) + // Standard Error: 1_250 + .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) // Standard Error: 12 - .saturating_add(Weight::from_parts(1_467, 0).saturating_mul(z.into())) + .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 28_494_000 picoseconds. - Weight::from_parts(19_053_318, 0) + // Minimum execution time: 29_373_000 picoseconds. + Weight::from_parts(19_409_201, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 791 - .saturating_add(Weight::from_parts(112_935, 0).saturating_mul(s.into())) + // Standard Error: 725 + .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) // Standard Error: 7 - .saturating_add(Weight::from_parts(1_427, 0).saturating_mul(z.into())) + .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,13 +107,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_505_000 picoseconds. - Weight::from_parts(36_407_515, 0) + // Minimum execution time: 49_724_000 picoseconds. + Weight::from_parts(34_153_321, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_595 - .saturating_add(Weight::from_parts(166_201, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_481, 0).saturating_mul(z.into())) + // Standard Error: 1_376 + .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -124,11 +124,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_977_000 picoseconds. - Weight::from_parts(32_222_158, 0) + // Minimum execution time: 31_081_000 picoseconds. + Weight::from_parts(31_552_702, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_872 - .saturating_add(Weight::from_parts(125_197, 0).saturating_mul(s.into())) + // Standard Error: 1_066 + .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +139,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 17_351_000 picoseconds. - Weight::from_parts(18_130_793, 0) + // Minimum execution time: 17_807_000 picoseconds. + Weight::from_parts(18_241_044, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 902 - .saturating_add(Weight::from_parts(109_485, 0).saturating_mul(s.into())) + // Standard Error: 768 + .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +154,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_554_000 picoseconds. - Weight::from_parts(33_116_785, 0) + // Minimum execution time: 32_421_000 picoseconds. + Weight::from_parts(32_554_061, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 882 - .saturating_add(Weight::from_parts(119_357, 0).saturating_mul(s.into())) + // Standard Error: 1_157 + .saturating_add(Weight::from_parts(141_221, 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/bridge-hubs/bridge-hub-kusama/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_session.rs similarity index 90% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_session.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_session.rs index cc1b4aeb0ddb6718b57ebfd65c985fc4071867b1..c9d04f9c6df65892243220531ac9d42dc885702d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_session.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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-kusama-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=pallet_session // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,8 +58,8 @@ impl pallet_session::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `297` // Estimated: `3762` - // Minimum execution time: 17_170_000 picoseconds. - Weight::from_parts(17_523_000, 0) + // Minimum execution time: 16_965_000 picoseconds. + Weight::from_parts(17_384_000, 0) .saturating_add(Weight::from_parts(0, 3762)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -72,8 +72,8 @@ impl pallet_session::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `279` // Estimated: `3744` - // Minimum execution time: 13_273_000 picoseconds. - Weight::from_parts(14_200_000, 0) + // Minimum execution time: 12_444_000 picoseconds. + Weight::from_parts(12_832_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/bridge-hubs/bridge-hub-kusama/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_timestamp.rs similarity index 87% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_timestamp.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_timestamp.rs index 32f6e4a6b43598cf3b3b6a6df7a571bd38f44697..0a5bf9b9f9c1f8c483c2e2f86ae471b28e7f609a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_timestamp.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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-kusama-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=pallet_timestamp // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,20 +56,20 @@ impl pallet_timestamp::WeightInfo for WeightInfo { /// Proof: `Aura::CurrentSlot` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn set() -> Weight { // Proof Size summary in bytes: - // Measured: `49` + // Measured: `85` // Estimated: `1493` - // Minimum execution time: 7_794_000 picoseconds. - Weight::from_parts(8_075_000, 0) + // Minimum execution time: 9_231_000 picoseconds. + Weight::from_parts(9_595_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` + // Measured: `94` // Estimated: `0` - // Minimum execution time: 3_338_000 picoseconds. - Weight::from_parts(3_471_000, 0) + // Minimum execution time: 3_869_000 picoseconds. + Weight::from_parts(4_041_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_utility.rs similarity index 76% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_utility.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_utility.rs index 15b06676cd364efcd9d80361f7f7437cab032efe..44cd0cf91e7998cb504587c481d13b110c9db072 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_utility.rs @@ -20,13 +20,13 @@ //! DATE: 2023-07-31, 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-kusama-dev")`, DB CACHE: 1024 +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-dev +// --chain=bridge-hub-rococo-dev // --wasm-execution=compiled // --pallet=pallet_utility // --no-storage-info @@ -37,7 +37,7 @@ // --repeat=20 // --json // --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/ +// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,18 +55,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_641_000 picoseconds. - Weight::from_parts(7_103_558, 0) + // Minimum execution time: 6_831_000 picoseconds. + Weight::from_parts(12_945_569, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_172 - .saturating_add(Weight::from_parts(4_907_384, 0).saturating_mul(c.into())) + // Standard Error: 1_949 + .saturating_add(Weight::from_parts(5_125_189, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_741_000 picoseconds. - Weight::from_parts(4_870_000, 0) + // Minimum execution time: 4_790_000 picoseconds. + Weight::from_parts(5_063_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -74,18 +74,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_561_000 picoseconds. - Weight::from_parts(12_252_064, 0) + // Minimum execution time: 6_894_000 picoseconds. + Weight::from_parts(14_201_341, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_750 - .saturating_add(Weight::from_parts(5_193_404, 0).saturating_mul(c.into())) + // Standard Error: 2_501 + .saturating_add(Weight::from_parts(5_466_047, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_646_000 picoseconds. - Weight::from_parts(8_927_000, 0) + // Minimum execution time: 8_624_000 picoseconds. + Weight::from_parts(9_064_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -93,10 +93,10 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_726_000 picoseconds. - Weight::from_parts(8_025_954, 0) + // Minimum execution time: 6_912_000 picoseconds. + Weight::from_parts(9_228_121, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_746 - .saturating_add(Weight::from_parts(4_936_537, 0).saturating_mul(c.into())) + // Standard Error: 1_601 + .saturating_add(Weight::from_parts(5_138_293, 0).saturating_mul(c.into())) } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..a78ff2355efaf06562e44828a8df0730481d4098 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -0,0 +1,376 @@ +// 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 32.0.0 +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-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 +// --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)] +#![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: `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 send() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 19_527_000 picoseconds. + Weight::from_parts(19_839_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: `107` + // Estimated: `3593` + // Minimum execution time: 90_938_000 picoseconds. + Weight::from_parts(92_822_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 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: `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: `107` + // Estimated: `3593` + // Minimum execution time: 90_133_000 picoseconds. + Weight::from_parts(92_308_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 { + // 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: 6_205_000 picoseconds. + Weight::from_parts(6_595_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: 1_927_000 picoseconds. + Weight::from_parts(2_062_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: `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`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_subscribe_version_notify() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 25_078_000 picoseconds. + Weight::from_parts(25_782_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) + /// 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: `255` + // Estimated: `3720` + // Minimum execution time: 28_188_000 picoseconds. + Weight::from_parts(28_826_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) + /// 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: 1_886_000 picoseconds. + Weight::from_parts(1_991_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn migrate_supported_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 17_443_000 picoseconds. + Weight::from_parts(17_964_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn migrate_version_notifiers() -> Weight { + // Proof Size summary in bytes: + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 17_357_000 picoseconds. + Weight::from_parts(18_006_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn already_notified_target() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `15946` + // Minimum execution time: 18_838_000 picoseconds. + Weight::from_parts(19_688_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) + } + /// 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) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn notify_current_targets() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 25_517_000 picoseconds. + Weight::from_parts(26_131_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:4 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn notify_target_migration_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `11026` + // Minimum execution time: 11_587_000 picoseconds. + Weight::from_parts(11_963_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn migrate_version_notify_targets() -> Weight { + // Proof Size summary in bytes: + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 17_490_000 picoseconds. + Weight::from_parts(18_160_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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) + /// 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: `106` + // Estimated: `13471` + // Minimum execution time: 34_088_000 picoseconds. + Weight::from_parts(34_598_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(11)) + .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: 3_566_000 picoseconds. + Weight::from_parts(3_754_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: 25_078_000 picoseconds. + Weight::from_parts(25_477_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 34_661_000 picoseconds. + Weight::from_parts(35_411_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachain-template/runtime/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs similarity index 100% rename from cumulus/parachain-template/runtime/src/weights/paritydb_weights.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/paritydb_weights.rs diff --git a/cumulus/parachain-template/runtime/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs similarity index 100% rename from cumulus/parachain-template/runtime/src/weights/rocksdb_weights.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/rocksdb_weights.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs similarity index 63% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs index eb140c4bf32383697ca6c27c913e56edd2f100c4..e8950678b40fd7b4e7afca8c998bc20c619e65ef 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs @@ -18,20 +18,21 @@ mod pallet_xcm_benchmarks_fungible; mod pallet_xcm_benchmarks_generic; use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use codec::Encode; 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,40 +51,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } -pub struct AssetHubPolkadotXcmWeight(core::marker::PhantomData); -impl XcmWeightInfo for AssetHubPolkadotXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) +pub struct BridgeHubWestendXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for BridgeHubWestendXcmWeight { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -111,44 +108,36 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -163,7 +152,7 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -175,13 +164,13 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -211,19 +200,20 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { fn universal_origin(_: &Junction) -> Weight { Weight::MAX } - fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { - Weight::MAX + fn export_message(_: &NetworkId, _: &Junctions, inner: &Xcm<()>) -> Weight { + let inner_encoded_len = inner.encode().len() as u32; + XcmGeneric::::export_message(inner_encoded_len) } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -235,11 +225,11 @@ impl XcmWeightInfo for AssetHubPolkadotXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs similarity index 72% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 17ee5cb6a8dca0fbebefec1c817f16a275489260..295abd481d7dc6decd39032bf6b0972cdc7ccf20 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-kusama-dev"), DB CACHE: 1024 +//! 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 // 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::fungible -// --chain=bridge-hub-kusama-dev +// --chain=bridge-hub-rococo-dev // --header=./cumulus/file_header.txt // --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 25_447_000 picoseconds. - Weight::from_parts(25_810_000, 3593) + // Minimum execution time: 19_037_000 picoseconds. + Weight::from_parts(19_602_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,15 +65,19 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `153` // Estimated: `6196` - // Minimum execution time: 53_908_000 picoseconds. - Weight::from_parts(54_568_000, 6196) + // Minimum execution time: 43_115_000 picoseconds. + Weight::from_parts(43_897_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:2 w:2) + // Storage: `System::Account` (r:3 w:3) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // 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) @@ -86,12 +90,12 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `6196` - // Minimum execution time: 79_923_000 picoseconds. - Weight::from_parts(80_790_000, 6196) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `294` + // Estimated: `8799` + // Minimum execution time: 90_267_000 picoseconds. + Weight::from_parts(91_460_000, 8799) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(5)) } // Storage: `Benchmark::Override` (r:0 w:0) // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -102,33 +106,39 @@ impl WeightInfo { // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. Weight::from_parts(18_446_744_073_709_551_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) + // 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: `70` - // Estimated: `3535` - // Minimum execution time: 31_923_000 picoseconds. - Weight::from_parts(32_499_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `242` + // Estimated: `6196` + // Minimum execution time: 60_477_000 picoseconds. + Weight::from_parts(61_314_000, 6196) + .saturating_add(T::DbWeight::get().reads(10)) + .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_903_000 picoseconds. - Weight::from_parts(4_065_000, 0) + // Minimum execution time: 2_996_000 picoseconds. + Weight::from_parts(3_107_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -136,15 +146,19 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3593` - // Minimum execution time: 26_987_000 picoseconds. - Weight::from_parts(27_486_000, 3593) + // Minimum execution time: 18_907_000 picoseconds. + Weight::from_parts(19_475_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:1 w:1) + // Storage: `System::Account` (r:2 w:2) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // 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) @@ -157,32 +171,38 @@ impl WeightInfo { // Proof: `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: 56_012_000 picoseconds. - Weight::from_parts(58_067_000, 3593) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `193` + // Estimated: `6196` + // Minimum execution time: 59_143_000 picoseconds. + Weight::from_parts(60_316_000, 6196) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(4)) } + // 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`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 32_350_000 picoseconds. - Weight::from_parts(33_403_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `141` + // Estimated: `3606` + // Minimum execution time: 44_459_000 picoseconds. + Weight::from_parts(45_365_000, 3606) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(3)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/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 similarity index 59% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 625549ecfccfa348e11debd99325f7ac41646445..9281a880c7e1266d65d29436ca88e51e896c0363 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/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,28 +17,26 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! 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-12, 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("asset-hub-kusama-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 +// target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm_benchmarks::generic -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --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-westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,120 +50,128 @@ 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: `109` - // Estimated: `3574` - // Minimum execution time: 432_196_000 picoseconds. - Weight::from_parts(438_017_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `208` + // Estimated: `6196` + // 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: 4_223_000 picoseconds. - Weight::from_parts(4_412_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: 11_582_000 picoseconds. - Weight::from_parts(11_830_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: 13_955_000 picoseconds. - Weight::from_parts(14_320_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: 4_423_000 picoseconds. - Weight::from_parts(4_709_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: 3_028_000 picoseconds. - Weight::from_parts(3_151_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: 2_966_000 picoseconds. - Weight::from_parts(3_076_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: 2_971_000 picoseconds. - Weight::from_parts(3_119_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: 3_772_000 picoseconds. - Weight::from_parts(3_853_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: 2_940_000 picoseconds. - Weight::from_parts(3_050_000, 0) + // Minimum execution time: 1_838_000 picoseconds. + Weight::from_parts(1_894_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: `109` - // Estimated: `3574` - // Minimum execution time: 27_734_000 picoseconds. - Weight::from_parts(28_351_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `208` + // Estimated: `6196` + // 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: 16_456_000 picoseconds. - Weight::from_parts(16_846_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)) } @@ -173,11 +179,13 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_974_000 picoseconds. - Weight::from_parts(3_108_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`) + // 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) @@ -190,11 +198,11 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 29_823_000 picoseconds. - Weight::from_parts(30_776_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) + // 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)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) @@ -203,127 +211,159 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_966_000 picoseconds. - Weight::from_parts(5_157_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: 141_875_000 picoseconds. - Weight::from_parts(144_925_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: 13_147_000 picoseconds. - Weight::from_parts(13_420_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: 3_050_000 picoseconds. - Weight::from_parts(3_161_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: 2_930_000 picoseconds. - Weight::from_parts(3_077_000, 0) + // Minimum execution time: 1_837_000 picoseconds. + 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: 3_188_000 picoseconds. - Weight::from_parts(3_299_000, 0) + // Minimum execution time: 2_052_000 picoseconds. + Weight::from_parts(2_120_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: `109` - // Estimated: `3574` - // Minimum execution time: 31_678_000 picoseconds. - Weight::from_parts(32_462_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `208` + // Estimated: `6196` + // 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: 5_638_000 picoseconds. - Weight::from_parts(5_756_000, 0) + // Minimum execution time: 4_570_000 picoseconds. + Weight::from_parts(4_707_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: `109` - // Estimated: `3574` - // Minimum execution time: 27_556_000 picoseconds. - Weight::from_parts(28_240_000, 3574) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `208` + // Estimated: `6196` + // 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: 2_932_000 picoseconds. - Weight::from_parts(3_097_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: 2_860_000 picoseconds. - Weight::from_parts(2_957_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: 2_886_000 picoseconds. - Weight::from_parts(3_015_000, 0) + // Minimum execution time: 1_847_000 picoseconds. + Weight::from_parts(1_900_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: `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: `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: 2_874_000 picoseconds. - Weight::from_parts(3_060_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: 3_029_000 picoseconds. - Weight::from_parts(3_158_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-polkadot/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs similarity index 60% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/xcm_config.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index 7378961f576006d484b1ad00fb854d113100da73..840d0c9af0e5886eef81ea9bbc542279ff7a106f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -15,43 +15,54 @@ // along with Cumulus. If not, see . use super::{ - AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, - CENTS, + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, WeightToFee, XcmpQueue, }; +use crate::bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing}, + parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; -use parachains_common::{impls::ToStakingPot, xcm_config::ConcreteAssetFromSystem}; +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::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - pub const DotRelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: Option = Some(NetworkId::Polkadot); + pub const WestendLocation: Location = Location::parent(); + pub const RelayNetwork: NetworkId = 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 UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].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(); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -64,12 +75,12 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + IsConcrete, + // Do a simple punn to convert an AccountId32 Location into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -85,7 +96,7 @@ pub type XcmOriginToTransactDispatchOrigin = ( // 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 which they control. SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when + // 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 @@ -101,19 +112,13 @@ pub type XcmOriginToTransactDispatchOrigin = ( XcmPassthrough, ); -match_types! { - 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(_) } - }; - pub type FellowsPlurality: impl Contains = { - MultiLocation { parents: 1, interior: X2(Parachain(1001), Plurality { id: BodyId::Technical, ..}) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } + /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -131,28 +136,52 @@ 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(&DeliveryRewardInBalance::key()) | + k.eq(&RequiredStakeForStakeAndSlash::key()) + }) => + 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::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( - 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::DmpQueue(..) + RuntimeCall::MessageQueue(..) | + RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance, + >::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 { .. }) ) } } @@ -170,11 +199,14 @@ 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 the Fellows plurality + // Parent, its pluralities (i.e. governance bodies) and relay treasury pallet // get free execution. - AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, + AllowExplicitUnpaidExecutionFrom<( + ParentOrParentsPlurality, + Equals, + )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -183,59 +215,67 @@ pub type Barrier = TrailingSetTopicAsId< >, >; +/// 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, +); + /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: -/// - DOT with the parent Relay Chain and sibling parachains. -pub type TrustedTeleporters = ConcreteAssetFromSystem; +/// - NativeToken with the parent Relay Chain and sibling parachains. +pub type TrustedTeleporters = ConcreteAssetFromSystem; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // BridgeHub does not recognize a reserve location for any asset. Users must teleport DOT - // where allowed (e.g. with the Relay Chain). + // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native + // token where allowed (e.g. with the Relay Chain). type IsReserve = (); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< - crate::weights::xcm::BridgeHubPolkadotXcmWeight, + crate::weights::xcm::BridgeHubWestendXcmWeight, RuntimeCall, MaxInstructions, >; type Trader = - UsingComponents>; + UsingComponents>; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; + type AssetLocker = (); + type AssetExchanger = (); type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - type MessageExporter = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; + type MessageExporter = (crate::bridge_to_rococo_config::ToBridgeHubRococoHaulBlobExporter,); 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; - -parameter_types! { - /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(DotRelayLocation::get()); - /// The base fee for the message delivery fees. - pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type PriceForParentDelivery = ExponentialPrice; +/// Converts a local signed origin into an XCM location. +/// 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<( @@ -245,25 +285,19 @@ pub type XcmRouter = WithUniqueTopic<( XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type XcmRouter = XcmRouter; // 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::BridgeHubPolkadotXcmWeight, + crate::weights::xcm::BridgeHubWestendXcmWeight, RuntimeCall, MaxInstructions, >; @@ -278,8 +312,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); 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 new file mode 100644 index 0000000000000000000000000000000000000000..149a3bbeb75d82ba7c35641428c7677d18297f8c --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -0,0 +1,351 @@ +// 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 . + +#![cfg(test)] + +use bp_polkadot_core::Signature; +use bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; +use bridge_hub_test_utils::{test_cases::from_parachain, SlotDurations}; +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, RuntimeOrigin, SessionKeys, + SignedExtra, TransactionPayment, UncheckedExtrinsic, +}; +use bridge_to_rococo_config::{ + 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, traits::ConstU8}; +use parachains_common::{AccountId, AuraId, Balance}; +use sp_consensus_aura::SlotDuration; +use sp_keyring::AccountKeyring::Alice; +use sp_runtime::{ + generic::{Era, SignedPayload}, + AccountId32, Perbill, +}; +use testnet_parachains_constants::westend::{consensus::*, fee::WeightToFee}; +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(); +} + +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( + frame_system::Pallet::::account(&account_id).nonce, + ), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + BridgeRejectObsoleteHeadersAndMessages::default(), + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),), + ); + let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap(); + let signature = payload.using_encoded(|e| sender.sign(e)); + UncheckedExtrinsic::new_signed( + call, + account_id.into(), + Signature::Sr25519(signature.clone()), + extra, + ) +} + +fn construct_and_apply_extrinsic( + relayer_at_target: sp_keyring::AccountKeyring, + call: RuntimeCall, +) -> sp_runtime::DispatchOutcome { + let xt = construct_extrinsic(relayer_at_target, call); + let r = Executive::apply_extrinsic(xt); + r.unwrap() +} + +fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> Balance { + let batch_call = RuntimeCall::Utility(batch); + let batch_info = batch_call.get_dispatch_info(); + let xt = construct_extrinsic(Alice, batch_call); + TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) +} + +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()) }, + ) +} + +fn slot_durations() -> SlotDurations { + SlotDurations { + relay: SlotDuration::from_millis(RELAY_CHAIN_SLOT_DURATION_MILLIS.into()), + para: SlotDuration::from_millis(SLOT_DURATION), + } +} + +bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + CheckingAccount, + WeightToFee, + ParachainSystem, + collator_session_keys(), + slot_durations(), + 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_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID +); + +#[test] +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) +} + +#[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] +fn change_delivery_reward_by_governance_works() { + bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + DeliveryRewardInBalance, + u64, + >( + collator_session_keys(), + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + Box::new(|call| RuntimeCall::System(call).encode()), + || (DeliveryRewardInBalance::key().to_vec(), DeliveryRewardInBalance::get()), + |old_value| old_value.checked_mul(2).unwrap(), + ) +} + +#[test] +fn change_required_stake_by_governance_works() { + bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + RequiredStakeForStakeAndSlash, + Balance, + >( + collator_session_keys(), + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + Box::new(|call| RuntimeCall::System(call).encode()), + || (RequiredStakeForStakeAndSlash::key().to_vec(), RequiredStakeForStakeAndSlash::get()), + |old_value| old_value.checked_mul(2).unwrap(), + ) +} + +#[test] +fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { + bridge_hub_test_utils::test_cases::handle_export_message_from_system_parachain_to_outbound_queue_works::< + Runtime, + XcmConfig, + WithBridgeHubRococoMessagesInstance, + >( + collator_session_keys(), + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + SIBLING_PARACHAIN_ID, + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::BridgeRococoMessages(event)) => Some(event), + _ => None, + } + }), + || ExportMessage { network: Rococo, destination: [Parachain(bridge_to_rococo_config::AssetHubRococoParaId::get().into())].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!"), + ) +} + +#[test] +fn message_dispatch_routing_works() { + bridge_hub_test_utils::test_cases::message_dispatch_routing_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + WithBridgeHubRococoMessagesInstance, + RelayNetwork, + bridge_to_rococo_config::RococoGlobalConsensusNetwork, + ConstU8<2>, + >( + collator_session_keys(), + slot_durations(), + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_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_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, + || (), + ) +} + +#[test] +fn relayed_incoming_message_works() { + from_parachain::relayed_incoming_message_works::( + collator_session_keys(), + slot_durations(), + 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() { + from_parachain::complex_relay_extrinsic_works::( + collator_session_keys(), + slot_durations(), + bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + SIBLING_PARACHAIN_ID, + BridgeHubRococoChainId::get(), + Westend, + XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, + || (), + construct_and_apply_extrinsic, + ); +} + +#[test] +pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + Runtime, + XcmConfig, + WeightToFee, + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) +} + +#[test] +pub fn can_calculate_fee_for_complex_message_delivery_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) +} + +#[test] +pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a4dcd19dc9e8675599eaad9c2d340eca5874e63b --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "bridge-hub-common" +version = "0.0.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/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..c1bba65b0abc3c6949f94e9e904a5649f1a9d285 --- /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::v4::{Junction, Location}; + +/// 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 Location { + fn from(origin: AggregateMessageOrigin) -> Self { + use AggregateMessageOrigin::*; + match origin { + Here => Location::here(), + Parent => Location::parent(), + Sibling(id) => Location::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(_) => Location::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 2ad79bb24886f26760a3b107b979506877b0225a..5f2a6e050d83c3db662f8ff4896d32dc8a28fde3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -1,51 +1,47 @@ [package] name = "bridge-hub-test-utils" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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"] } -log = { version = "0.4.20", default-features = false } +impl-trait-for-tuples = "0.2" +log = { workspace = true } # 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} -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false} -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false} +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +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-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} -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false} +pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } # Cumulus asset-test-utils = { path = "../../assets/test-utils" } -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-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } -pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } parachains-runtimes-test-utils = { path = "../../test-utils", 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 } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } # Bridges -bp-bridge-hub-rococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } -bp-bridge-hub-wococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-wococo", 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-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 } @@ -57,25 +53,19 @@ pallet-bridge-relayers = { path = "../../../../../bridges/modules/relayers", def bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "asset-test-utils/std", - "bp-bridge-hub-rococo/std", - "bp-bridge-hub-wococo/std", "bp-header-chain/std", "bp-messages/std", - "bp-parachains/std", "bp-polkadot-core/std", "bp-relayers/std", "bp-runtime/std", "bp-test-utils/std", "bridge-runtime-common/std", "codec/std", - "cumulus-pallet-dmp-queue/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", - "frame-benchmarking/std", - "frame-executive/std", "frame-support/std", "frame-system/std", "log/std", @@ -84,17 +74,14 @@ std = [ "pallet-bridge-messages/std", "pallet-bridge-parachains/std", "pallet-bridge-relayers/std", - "pallet-collator-selection/std", - "pallet-session/std", + "pallet-timestamp/std", "pallet-utility/std", - "pallet-xcm-benchmarks?/std", - "pallet-xcm/std", - "parachain-info/std", "parachains-common/std", "parachains-runtimes-test-utils/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..1874f38de2df17e85c1f49723271d090e962eb70 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,64 @@ //! 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::*; +use sp_runtime::Perbill; + +/// A helper function for comparing the actual value of a fee constant with its estimated value. The +/// estimated value can be overestimated (`overestimate_in_percent`), and if the difference to the +/// actual value is below `margin_overestimate_diff_in_percent_for_lowering`, we should lower the +/// actual value. +pub fn check_sane_fees_values( + const_name: &str, + actual: u128, + calculate_estimated_fee: fn() -> u128, + overestimate_in_percent: Perbill, + margin_overestimate_diff_in_percent_for_lowering: Option, + label: &str, +) { + let estimated = calculate_estimated_fee(); + let estimated_plus_overestimate = estimated + (overestimate_in_percent * estimated); + let diff_to_estimated = diff_as_percent(actual, estimated); + let diff_to_estimated_plus_overestimate = diff_as_percent(actual, estimated_plus_overestimate); + + sp_tracing::try_init_simple(); + log::error!( + target: "bridges::estimate", + "{label}:\nconstant: {const_name}\n[+] actual: {actual}\n[+] estimated: {estimated} ({diff_to_estimated:.2?})\n[+] estimated(+33%): {estimated_plus_overestimate} ({diff_to_estimated_plus_overestimate:.2?})", + ); + + // check if estimated value is sane + assert!( + estimated <= actual, + "estimated: {estimated}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + assert!( + estimated_plus_overestimate <= actual, + "estimated_plus_overestimate: {estimated_plus_overestimate}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + + if let Some(margin_overestimate_diff_in_percent_for_lowering) = + margin_overestimate_diff_in_percent_for_lowering + { + assert!( + diff_to_estimated_plus_overestimate > margin_overestimate_diff_in_percent_for_lowering as f64, + "diff_to_estimated_plus_overestimate: {diff_to_estimated_plus_overestimate:.2}, overestimate_diff_in_percent_for_lowering: {margin_overestimate_diff_in_percent_for_lowering}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + } +} + +pub fn diff_as_percent(left: u128, right: u128) -> f64 { + let left = left as f64; + let right = right as f64; + ((left - right).abs() / left) * 100f64 * (if left >= right { -1 } else { 1 }) as f64 +} + +#[test] +fn diff_as_percent_works() { + assert_eq!(-20_f64, diff_as_percent(100, 80)); + assert_eq!(25_f64, diff_as_percent(80, 100)); + assert_eq!(33_f64, diff_as_percent(13351000000, 17756830000)); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs deleted file mode 100644 index e77af189b4fdb5b4366f40bb6647a99c0072109c..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs +++ /dev/null @@ -1,1140 +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::{ - target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch, SourceHeaderChain}, - LaneId, MessageKey, OutboundLaneData, 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::{ - target::FromBridgedChainMessagesProof, BridgedChain as MessageBridgedChain, MessageBridge, - }, - messages_generation::{encode_all_messages, encode_lane_data, 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_dmp_queue::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_dmp_queue::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_dmp_queue::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_dmp_queue::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, - 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_proofs::, 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_dmp_queue::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, - 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_proofs::, MB, ()>( - lane_id, - xcm.clone().into(), - message_nonce, - message_destination, - para_header_number, - relay_header_number, - bridged_para_id, - ); - - let submit_grandpa = - pallet_bridge_grandpa::Call::::submit_finality_proof { - finality_target: Box::new(relay_chain_header.clone()), - justification: grandpa_justification, - }; - let submit_para_head = - pallet_bridge_parachains::Call::::submit_parachain_heads { - at_relay_block: (relay_header_number, 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_on_source.into(), - proof: message_proof.into(), - messages_count: 1, - dispatch_weight: Weight::from_parts(1000000000, 0), - }; - let batch = pallet_utility::Call::::batch_all { - calls: vec![submit_grandpa.into(), submit_para_head.into(), submit_message.into()], - }; - - // 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 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, - ]), - ]), - }, - RefundSurplus, - 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() -} - -pub mod test_data { - use super::*; - use bp_header_chain::justification::GrandpaJustification; - use bp_messages::MessageNonce; - use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; - use bp_runtime::BasicOperatingMode; - use bp_test_utils::authority_list; - 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() - } - - pub fn make_complex_relayer_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, - ) -> ( - BridgedRelayHeader, - GrandpaJustification, - ParaHead, - Vec<(ParaId, ParaHash)>, - ParaHeadsProof, - FromBridgedChainMessagesProof, - ) - where - BridgedRelayHeader: HeaderT, - ::Hash: From, - MB: MessageBridge, - ::BridgedChain: 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 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); - - 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, - }; - - // import bridged relay chain block#1 with state root containing head#5 of bridged parachain - let relay_chain_header: BridgedRelayHeader = bp_test_utils::test_header_with_root( - relay_header_number.into(), - relay_state_root.into(), - ); - let justification = make_default_justification(&relay_chain_header); - ( - relay_chain_header, - justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - message_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..8aaaa4f59d7884ff211855a925638317a3b722ea --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -0,0 +1,425 @@ +// 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::{OnFinalize, OnInitialize}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_runtimes_test_utils::{ + AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, +}; +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, + slot_durations: SlotDurations, + 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, + slot_durations, + 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, + slot_durations: SlotDurations, + 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, + slot_durations, + 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![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 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::(), + ); + + compute_extrinsic_fee(batch) + }) +} + +/// 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, + ); + + compute_extrinsic_fee(batch) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs new file mode 100644 index 0000000000000000000000000000000000000000..72ec0718acf7759aedb02e91356fea73ee73e7e7 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -0,0 +1,529 @@ +// 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::{OnFinalize, OnInitialize}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_runtimes_test_utils::{ + AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, +}; +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, + slot_durations: SlotDurations, + 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, + slot_durations, + 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, + slot_durations: SlotDurations, + 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, + slot_durations, + 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![Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + [GlobalConsensus(Polkadot), Parachain(1_000)].into(), + 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::(), + ); + + compute_extrinsic_fee(batch) + }) +} + +/// 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, + ); + + compute_extrinsic_fee(batch) + }) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..2b48f2e3d515f625532d9c5f50fabadb9a89517a --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -0,0 +1,349 @@ +// 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, SlotDurations, +}; +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 + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, +{ + 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, + slot_durations: SlotDurations, + 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, + InteriorLocation, + 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, + &slot_durations, + ); + + // set up relayer details and proofs + + let message_destination: InteriorLocation = + [GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)].into(); + // some random numbers (checked by test) + let message_nonce = 1; + + let xcm = vec![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([PalletInstance( + as PalletInfoAccess>::index() + as u8, + )].into()), + ); + 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..bc1c7ec5e032c08fb36b3005d4abcaf24bb43ff4 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -0,0 +1,645 @@ +// 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, + SlotDurations, 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, set_storage_keys_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 = Location::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(Assets::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 mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + assert_ok!(XcmExecutor::::prepare_and_execute( + sibling_parachain_location, + xcm, + &mut hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + Weight::zero(), + ) + .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, + slot_durations: SlotDurations, + 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() -> Location { + Location::new(C::get(), [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(), [Parachain(sibling_parachain_id)].into())); + + // 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, + &slot_durations, + ); + 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(Assets::from(vec![Asset { + id: AssetId(Location::new(1, [])), + fun: Fungible(34333299), + }])), + BuyExecution { + fees: Asset { id: AssetId(Location::new(1, [])), fun: Fungible(34333299) }, + weight_limit: Unlimited, + }, + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Location::new(1, [Parachain(1000)]), + }])), + ExportMessage { + network: Polkadot, + destination: [Parachain(1000)].into(), + xcm: Xcm(vec![ + ReserveAssetDeposited(Assets::from(vec![Asset { + id: AssetId(Location::new(2, [GlobalConsensus(Kusama)])), + fun: Fungible(1000000000000), + }])), + ClearOrigin, + BuyExecution { + fees: Asset { + id: AssetId(Location::new(2, [GlobalConsensus(Kusama)])), + fun: Fungible(1000000000000), + }, + weight_limit: Unlimited, + }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Location::new( + 0, + [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, + ]), + ]), + }, + 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()); + + 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..9285a1e7ad4500a4c2c7db73d9966dd711d852be --- /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: InteriorLocation, +) -> Vec { + let location = xcm::VersionedInteriorLocation::V4(destination); + let xcm = xcm::VersionedXcm::::V4(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: Junctions = + [GlobalConsensus(SourceNetwork::get()), Parachain(5678)].into(); + 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-polkadot/src/fellowship/migration.rs b/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/migration.rs deleted file mode 100644 index 9350d03a2c9fbdd0806a80d94f2ccfa080e34848..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/migration.rs +++ /dev/null @@ -1,261 +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 . - -//! Migrations. - -use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade, weights::Weight}; -use log; - -/// Initial import of the Kusama Technical Fellowship. -pub(crate) mod import_kusama_fellowship { - use super::*; - use frame_support::{parameter_types, traits::RankedMembers}; - use pallet_ranked_collective::{Config, MemberCount, Pallet as RankedCollective, Rank}; - #[cfg(feature = "try-runtime")] - use sp_std::vec::Vec; - - const TARGET: &str = "runtime::migration::import_fellowship"; - - parameter_types! { - // The Fellowship addresses from Kusama state. - pub const FellowshipAddresses: [(Rank, [u8; 32]); 47] = [ - (6, hex_literal::hex!("f0673d30606ee26672707e4fd2bc8b58d3becb7aba2d5f60add64abb5fea4710"),), - (6, hex_literal::hex!("3c235e80e35082b668682531b9b062fda39a46edb94f884d9122d86885fd5f1b"),), - (6, hex_literal::hex!("7628a5be63c4d3c8dbb96c2904b1a9682e02831a1af836c7efc808020b92fa63"),), - (5, hex_literal::hex!("9c84f75e0b1b92f6b003bde6212a8b2c9b776f3720f942b33fed8709f103a268"),), - (5, hex_literal::hex!("bc64065524532ed9e805fb0d39a5c0199216b52871168e5e4d0ab612f8797d61"),), - (5, hex_literal::hex!("2e1884c53071526483b14004e894415f02b55fc2e2aef8e1df8ccf7ce5bd5570"),), - (5, hex_literal::hex!("5c5062779d44ea2ab0469e155b8cf3e004fce71b3b3d38263cd9fa9478f12f28"),), - (4, hex_literal::hex!("4adf51a47b72795366d52285e329229c836ea7bbfe139dbe8fa0700c4f86fc56"),), - (4, hex_literal::hex!("1c90e3dabd3fd0f6bc648045018f78fcee8fe24122c22d8d2a14e9905073d10f"),), - (4, hex_literal::hex!("8e851ed992228f2268ee8c614fe6075d3800060ae14098e0309413a0a81c4470"),), - (3, hex_literal::hex!("720d807d46b941703ffe0278e8b173dc6738c5af8af812ceffc90c69390bbf1f"),), - (3, hex_literal::hex!("c4965f7fe7be8174717a24ffddf684986d122c7e293ddf875cdf9700a07b6812"),), - (3, hex_literal::hex!("beae5bcad1a8c156291b7ddf46b38b0c61a6aaacebd57b21c75627bfe7f9ab71"),), - (3, hex_literal::hex!("ccd87fa65729f7bdaa8305581a7a499aa24c118e83f5714152c0e22617c6fc63"),), - (3, hex_literal::hex!("e0f0f94962fc0a8c1a0f0527dc8e592c67939c46c903b6016cc0a8515da0044d"),), - (3, hex_literal::hex!("984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b5413"),), - (3, hex_literal::hex!("44a3efb5bfa9023d4ef27b7d31d76f531b4d7772b1679b7fb32b6263ac39100e"),), - (2, hex_literal::hex!("2eba9a39dbfdd5f3cba964355d45e27319f0271023c0353d97dc6df2401b0e3d"),), - (2, hex_literal::hex!("ba3e9b87792bcfcc237fa8181185b8883c77f3e24f45e4a92ab31d07a4703520"),), - (2, hex_literal::hex!("9e6eb74b0a6b39de36fb58d1fab20bc2b3fea96023ce5a47941c20480d99f92e"),), - (2, hex_literal::hex!("ee3d9d8c48ee88dce78fd7bafe3ce2052900eb465085b9324d4f5da26b145f2b"),), - (2, hex_literal::hex!("d8290537d6e31fe1ff165eaa62b63f6f3556dcc720b0d3a6d7eab96275617304"),), - (2, hex_literal::hex!("5a090c88f0438b46b451026597cee760a7bac9d396c9c7b529b68fb78aec5f43"),), - (2, hex_literal::hex!("18d30040a8245c5ff17afc9a8169d7d0771fe7ab4135a64a022c254117340720"),), - (1, hex_literal::hex!("b4f7f03bebc56ebe96bc52ea5ed3159d45a0ce3a8d7f082983c33ef133274747"),), - (1, hex_literal::hex!("caafae0aaa6333fcf4dc193146945fe8e4da74aa6c16d481eef0ca35b8279d73"),), - (1, hex_literal::hex!("a66e0f4e1a121cc83fddf3096e8ec8c9e9c85989f276e39e951fb0e4a5398763"),), - (1, hex_literal::hex!("f65f3cade8f68e8f34c6266b0d37e58a754059ca96816e964f98e17c79505073"),), - (1, hex_literal::hex!("8c232c91ef2a9983ba65c4b75bb86fcbae4d909900ea8aa06c3644ca1161db48"),), - (1, hex_literal::hex!("78e4813814891bd48bc745b79254a978833d41fbe0f387df93cd87eae2468926"),), - (1, hex_literal::hex!("d44824ac8d1edecca67639ca74d208bd2044a10e67c9677e288080191e3fec13"),), - (1, hex_literal::hex!("585e982d74da4f4290d20a73800cfd705cf59e1f5880aaee5506b5eaaf544f49"),), - (1, hex_literal::hex!("d851f44a6f0d0d2f3439a51f2f75f66f4ea1a8e6c33c32f9af75fc188afb7546"),), - (1, hex_literal::hex!("dca89b135d1a6aee0a498610a70eeaed056727c8a4d220da245842e540a54a74"),), - (1, hex_literal::hex!("aa91fc0201f26b713a018669bcd269babf25368eee2493323b1ce0190a178a27"),), - (1, hex_literal::hex!("dc20836f2e4b88c1858d1e3f918e7358043b4a8abcd2874e74d91d26c52eca2a"),), - (1, hex_literal::hex!("145d6c503d0cf97f4c7725ca773741bd02e1760bfb52e021af5a9f2de283012c"),), - (1, hex_literal::hex!("307183930b2264c5165f4a210a99520c5f1672b0413d57769fabc19e6866fb25"),), - (1, hex_literal::hex!("6201961514cf5ad87f1c4dd0c392ee28231f805f77975147bf2c33bd671b9822"),), - (1, hex_literal::hex!("c6f57237cd4abfbeed99171495fc784e45a9d5d2814d435de40de00991a73c06"),), - (1, hex_literal::hex!("c1df5c7e8ca56037450c58734326ebe34aec8f7d1928322a12164856365fea73"),), - (1, hex_literal::hex!("12c039004da5e1e846aae808277098c719cef1f4985aed00161a42ac4f0e002f"),), - (1, hex_literal::hex!("7460ac178015d2a7c289bb68ef9fdaac071596ab4425c276a0040aaac7055566"),), - (1, hex_literal::hex!("eec4bd650a277342ebba0954ac786df2623bd6a9d6d3e69b484482336c549f79"),), - (1, hex_literal::hex!("e287c7494655d636a846f5c3347ad2cb3c462a8d46e0832be70fcc0ab54ee62d"),), - (1, hex_literal::hex!("82bf733f44a840f0a5c1935a002d4e541d81298fad6d1da8124073485983860e"),), - (1, hex_literal::hex!("d5b89078eed9b9dfec5c7d8413bac0b720bad3bd4078c4d8c894325713192502"),), - ]; - } - - /// Implements `OnRuntimeUpgrade` trait. - pub struct Migration(PhantomData<(T, I)>); - - impl, I: 'static> OnRuntimeUpgrade for Migration - where - ::AccountId: From<[u8; 32]>, - { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - let onchain_version = RankedCollective::::on_chain_storage_version(); - ensure!(onchain_version == 0, "the storage version must be 0."); - let member_count = MemberCount::::get(0); - ensure!(member_count == 0, "the collective must be uninitialized."); - - Ok(Vec::new()) - } - - fn on_runtime_upgrade() -> Weight { - let current_version = RankedCollective::::current_storage_version(); - let onchain_version = RankedCollective::::on_chain_storage_version(); - let mut weight = T::DbWeight::get().reads(1); - log::info!( - target: TARGET, - "running migration with current storage version {:?} / onchain {:?}.", - current_version, - onchain_version - ); - if onchain_version != 0 { - log::warn!( - target: TARGET, - "unsupported storage version, skipping import_fellowship migration." - ); - return weight - } - let member_count = MemberCount::::get(0); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if member_count != 0 { - log::warn!( - target: TARGET, - "the collective already initialized, skipping import_fellowship migration." - ); - return weight - } - - for (rank, account_id32) in FellowshipAddresses::get() { - let who: T::AccountId = account_id32.into(); - let _ = as RankedMembers>::induct(&who); - for _ in 0..rank { - let _ = as RankedMembers>::promote(&who); - // 1 write to `IdToIndex` and `IndexToId` per member on each rank. - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - // 1 write to `IdToIndex` and `IndexToId` per member on each rank. - weight.saturating_accrue(T::DbWeight::get().writes(2)); - // 1 read and 1 write to `Members` and `MemberCount` per member. - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - weight - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - ensure!(MemberCount::::get(0) == 47, "invalid members count at rank 0."); - ensure!(MemberCount::::get(1) == 47, "invalid members count at rank 1."); - ensure!(MemberCount::::get(2) == 24, "invalid members count at rank 2."); - ensure!(MemberCount::::get(3) == 17, "invalid members count at rank 3."); - ensure!(MemberCount::::get(4) == 10, "invalid members count at rank 4."); - ensure!(MemberCount::::get(5) == 7, "invalid members count at rank 5."); - ensure!(MemberCount::::get(6) == 3, "invalid members count at rank 6."); - ensure!(MemberCount::::get(7) == 0, "invalid members count at rank 7."); - Ok(()) - } - } -} - -#[cfg(test)] -pub mod tests { - use super::import_kusama_fellowship::FellowshipAddresses; - use crate::{FellowshipCollectiveInstance as Fellowship, Runtime, System}; - use frame_support::traits::OnRuntimeUpgrade; - use pallet_ranked_collective::Rank; - use parachains_common::AccountId; - use sp_core::crypto::Ss58Codec; - use sp_runtime::{AccountId32, BuildStorage}; - - #[test] - fn check_fellowship_addresses() { - let fellowship_addresses = FellowshipAddresses::get(); - let kusama_fellowship_ss58: [(Rank, _); 47] = [ - (6, "16SDAKg9N6kKAbhgDyxBXdHEwpwHUHs2CNEiLNGeZV55qHna"), /* proof https://kusama.subscan.io/extrinsic/16832707-4 */ - (6, "12MrP337azmkTdfCUKe5XLnSQrbgEKqqfZ4PQC7CZTJKAWR3"), /* proof https://kusama.subscan.io/extrinsic/16967809-2 */ - (6, "FFFF3gBSSDFSvK2HBq4qgLH75DHqXWPHeCnR1BSksAMacBs"), - (5, "G7YVCdxZb8JLpAm9WMnJdNuojNT84AzU62zmvx5P1FMNtg2"), - (5, "15G1iXDLgFyfnJ51FKq1ts44TduMyUtekvzQi9my4hgYt2hs"), /* proof https://kusama.subscan.io/extrinsic/16917610-2 */ - (5, "Dcm1BqR4N7nHuV43TXdET7pNibt1Nzm42FggPHpxKRven53"), - (5, "1363HWTPzDrzAQ6ChFiMU6mP4b6jmQid2ae55JQcKtZnpLGv"), /* proof https://kusama.subscan.io/extrinsic/16961180-2 */ - (4, "EGVQCe73TpFyAZx5uKfE1222XfkT3BSKozjgcqzLBnc5eYo"), - (4, "1eTPAR2TuqLyidmPT9rMmuycHVm9s9czu78sePqg2KHMDrE"), /* proof https://kusama.subscan.io/extrinsic/16921712-3 */ - (4, "14DsLzVyTUTDMm2eP3czwPbH53KgqnQRp3CJJZS9GR7yxGDP"), /* proof https://kusama.subscan.io/extrinsic/16917519-2 */ - (3, "13aYUFHB3umoPoxBEAHSv451iR3RpsNi3t5yBZjX2trCtTp6"), /* proof https://kusama.subscan.io/extrinsic/16917832-3 */ - (3, "H25aCspunTUqAt4D1gC776vKZ8FX3MvQJ3Jde6qDXPQaFxk"), - (3, "GtLQoW4ZqcjExMPq6qB22bYc6NaX1yMzRuGWpSRiHqnzRb9"), - (3, "15db5ksZgmhWE9U8MDq4wLKUdFivLVBybztWV8nmaJvv3NU1"), /* proof https://kusama.subscan.io/extrinsic/16876631-2 */ - (3, "HfFpz4QUxfbocHudf8UU7cMgHqkHpf855Me5X846PZAsAYE"), - (3, "14ShUZUYUR35RBZW6uVVt1zXDxmSQddkeDdXf1JkMA6P721N"), /* proof https://kusama.subscan.io/extrinsic/16918890-8 */ - (3, "12YzxR5TvGzfMVZNnhAJ5Hwi5zExpRWMKv2MuMwZTrddvgoi"), /* proof https://kusama.subscan.io/extrinsic/16924324-3 */ - (2, "Ddb9puChKMHq4gM6o47E551wAmaNeu6kHngX1jzNNqAw782"), - (2, "15DCWHQknBjc5YPFoVj8Pn2KoqrqYywJJ95BYNYJ4Fj3NLqz"), /* proof https://kusama.subscan.io/extrinsic/16834952-2 */ - (2, "14ajTQdrtCA8wZmC4PgD8Y1B2Gy8L4Z3oi2fodxq9FehcFrM"), /* proof https://kusama.subscan.io/extrinsic/16944257-2 */ - (2, "HxhDbS3grLurk1dhDgPiuDaRowHY1xHCU8Vu8on3fdg85tx"), - (2, "HTk3eccL7WBkiyxz1gBcqQRghsJigoDMD7mnQaz1UAbMpQV"), - (2, "EcNWrSPSDcVBRymwr26kk4JVFg92PdoU5Xwp87W2FgFSt9c"), - (2, "D8sM6vKjWaeKy2zCPYWGkLLbWdUtWQrXBTQqr4dSYnVQo21"), - (1, "GfbnnEgRU94n9ed4RFZ6Z9dBAWs5obykigJSwXKU9hsT2uU"), - (1, "HA5NtttvyZsxo4wGxGoJJSMaWtdEFZAuGUMFHVWD7fgenPv"), - (1, "14mDeKZ7qp9hqBjjDg51c8BFrf9o69om8piSSRwj2fT5Yb1i"), /* proof https://kusama.subscan.io/extrinsic/16919020-4 */ - (1, "16a357f5Sxab3V2ne4emGQvqJaCLeYpTMx3TCjnQhmJQ71DX"), /* proof https://kusama.subscan.io/extrinsic/16836396-5 */ - (1, "14Ak9rrF6RKHHoLLRUYMnzcvvi1t8E1yAMa7tcmiwUfaqzYK"), /* proof https://kusama.subscan.io/extrinsic/16921990-3 */ - (1, "FJq9JpA9P7EXbmfsN9YiewJaDbQyL6vQyksGtJvzfbn6zf8"), - (1, "15oLanodWWweiZJSoDTEBtrX7oGfq6e8ct5y5E6fVRDPhUgj"), /* proof https://kusama.subscan.io/extrinsic/16876423-7 */ - (1, "EaBqDJJNsZmYdQ4xn1vomPJVNh7fjA6UztZeEjn7ZzdeT7V"), - (1, "HTxCvXKVvUZ7PQq175kCRRLu7XkGfTfErrdNXr1ZuuwVZWv"), - (1, "HZe91A6a1xqbKaw6ofx3GFepJjhVXHrwHEwn6YUDDFphpX9"), - (1, "GRy2P3kBEzSHCbmDJfquku1cyUyhZaAqojRcNE4A4U3MnLd"), - (1, "HYwiBo7Mcv7uUDg4MUoKm2fxzv4dMLAtmmNfzHV8qcQJpAE"), - (1, "1ThiBx5DDxFhoD9GY6tz5Fp4Y7Xn1xfLmDddcoFQghDvvjg"), /* proof https://kusama.subscan.io/extrinsic/16918130-2 */ - (1, "DfqY6XQUSETTszBQ1juocTcG9iiDoXhvq1CoVadBSUqTGJS"), - (1, "EnpgVWGGQVrFdSB2qeXRVdtccV6U5ZscNELBoERbkFD8Wi6"), - (1, "H5BuqCmucJhUUuvjAzPazeVwVCtUSXVQdc5Dnx2q5zD7rVn"), - (1, "GxX7S1pTDdeaGUjpEPPF2we6tgHDhbatFG25pVmVFtGHLH6"), - (1, "CzuUtvKhZNZBjyAXeYviaRXwrLhVrsupJ9PrWmdq7BJTjGR"), - (1, "FCunn2Rx8JqfT5g6noUKKazph4jLDba5rUee7o3ZmJ362Ju"), - (1, "HyPMjWRHCpJS7x2SZ2R6M2XG5ZiCiZag4U4r7gBHRsE5mTc"), - (1, "1682A5hxfiS1Kn1jrUnMYv14T9EuEnsgnBbujGfYbeEbSK3w"), /* proof https://kusama.subscan.io/extrinsic/16919077-2 */ - (1, "13xS6fK6MHjApLnjdX7TJYw1niZmiXasSN91bNtiXQjgEtNx"), /* proof https://kusama.subscan.io/extrinsic/16918212-7 */ - (1, "15qE2YAQCs5Y962RHE7RzNjQxU6Pei21nhkkSM9Sojq1hHps"), /* https://kusama.subscan.io/extrinsic/17352973-2 */ - ]; - - for (index, val) in kusama_fellowship_ss58.iter().enumerate() { - let account: AccountId32 = ::from_string(val.1).unwrap(); - let account32: [u8; 32] = account.clone().into(); - assert_eq!( - fellowship_addresses[index].0, kusama_fellowship_ss58[index].0, - "ranks must be equal." - ); - assert_eq!(fellowship_addresses[index].1, account32, "accounts must be equal."); - } - } - - #[test] - fn test_fellowship_import() { - use super::import_kusama_fellowship::Migration; - use pallet_ranked_collective::{IdToIndex, IndexToId, MemberCount, MemberRecord, Members}; - - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext.execute_with(|| { - assert_eq!(MemberCount::::get(0), 0); - Migration::::on_runtime_upgrade(); - assert_eq!(MemberCount::::get(0), 47); - assert_eq!(MemberCount::::get(6), 3); - assert_eq!(MemberCount::::get(7), 0); - for (rank, account_id32) in FellowshipAddresses::get() { - let who = ::AccountId::from(account_id32); - assert!(IdToIndex::::get(0, &who).is_some()); - assert!(IdToIndex::::get(rank + 1, &who).is_none()); - let index = IdToIndex::::get(rank, &who).unwrap(); - assert_eq!(IndexToId::::get(rank, index).unwrap(), who); - assert_eq!( - Members::::get(&who).unwrap(), - MemberRecord::new(rank) - ); - } - }); - } -} diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs deleted file mode 100644 index ccd9478bf10eab85eef8b80c0354b59e33e08d3d..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs +++ /dev/null @@ -1,77 +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 . - -//! Autogenerated weights for `cumulus_pallet_xcmp_queue` -//! -//! 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: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=cumulus_pallet_xcmp_queue -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/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: `142` - // Estimated: `1627` - // Minimum execution time: 5_136_000 picoseconds. - Weight::from_parts(5_399_000, 0) - .saturating_add(Weight::from_parts(0, 1627)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `XcmpQueue::QueueConfig` (r:1 w:1) - /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_config_with_weight() -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `1627` - // Minimum execution time: 5_056_000 picoseconds. - Weight::from_parts(5_301_000, 0) - .saturating_add(Weight::from_parts(0, 1627)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_scheduler.rs b/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_scheduler.rs deleted file mode 100644 index d30ac82bf059bc8a2c75b1e8bce75024da38bea3..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_scheduler.rs +++ /dev/null @@ -1,207 +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 . - -//! Autogenerated weights for `pallet_scheduler` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_scheduler -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ - -#![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_scheduler`. -pub struct WeightInfo(PhantomData); -impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) - /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - fn service_agendas_base() -> Weight { - // Proof Size summary in bytes: - // Measured: `31` - // Estimated: `1489` - // Minimum execution time: 3_441_000 picoseconds. - Weight::from_parts(3_604_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) - /// The range of component `s` is `[0, 200]`. - fn service_agenda_base(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `77 + s * (177 ±0)` - // Estimated: `159279` - // Minimum execution time: 2_879_000 picoseconds. - Weight::from_parts(2_963_000, 0) - .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 3_764 - .saturating_add(Weight::from_parts(909_557, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - fn service_task_base() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_172_000 picoseconds. - Weight::from_parts(5_294_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - /// Storage: `Preimage::PreimageFor` (r:1 w:1) - /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) - /// Storage: `Preimage::StatusFor` (r:1 w:1) - /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) - /// The range of component `s` is `[128, 4194304]`. - fn service_task_fetched(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `213 + s * (1 ±0)` - // Estimated: `3678 + s * (1 ±0)` - // Minimum execution time: 19_704_000 picoseconds. - Weight::from_parts(19_903_000, 0) - .saturating_add(Weight::from_parts(0, 3678)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_394, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) - } - /// Storage: `Scheduler::Lookup` (r:0 w:1) - /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - fn service_task_named() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_359_000 picoseconds. - Weight::from_parts(6_599_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) - } - fn service_task_periodic() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_217_000 picoseconds. - Weight::from_parts(5_333_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - fn execute_dispatch_signed() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_406_000 picoseconds. - Weight::from_parts(2_541_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - fn execute_dispatch_unsigned() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_370_000 picoseconds. - Weight::from_parts(2_561_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) - /// The range of component `s` is `[0, 199]`. - fn schedule(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `77 + s * (177 ±0)` - // Estimated: `159279` - // Minimum execution time: 11_784_000 picoseconds. - Weight::from_parts(5_574_404, 0) - .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_217 - .saturating_add(Weight::from_parts(1_035_248, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) - /// Storage: `Scheduler::Lookup` (r:0 w:1) - /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// The range of component `s` is `[1, 200]`. - fn cancel(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `77 + s * (177 ±0)` - // Estimated: `159279` - // Minimum execution time: 16_373_000 picoseconds. - Weight::from_parts(3_088_135, 0) - .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_095 - .saturating_add(Weight::from_parts(1_745_270, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Scheduler::Lookup` (r:1 w:1) - /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) - /// The range of component `s` is `[0, 199]`. - fn schedule_named(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `468 + s * (179 ±0)` - // Estimated: `159279` - // Minimum execution time: 14_822_000 picoseconds. - Weight::from_parts(9_591_402, 0) - .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_151 - .saturating_add(Weight::from_parts(1_058_408, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `Scheduler::Lookup` (r:1 w:1) - /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) - /// Storage: `Scheduler::Agenda` (r:1 w:1) - /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) - /// The range of component `s` is `[1, 200]`. - fn cancel_named(s: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `509 + s * (179 ±0)` - // Estimated: `159279` - // Minimum execution time: 18_541_000 picoseconds. - Weight::from_parts(6_522_239, 0) - .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 8_349 - .saturating_add(Weight::from_parts(1_760_431, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } -} diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml similarity index 72% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/Cargo.toml rename to cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 73d787caf864f0ea87f71a36455db51c93343647..4224b3971398ed87c56aa90fe8f82d9f07e0e4bd 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -1,104 +1,114 @@ [package] -name = "collectives-polkadot-runtime" -version = "1.0.0" +name = "collectives-westend-runtime" +version = "3.0.0" authors.workspace = true edition.workspace = true -description = "Polkadot Collectives Parachain Runtime" +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" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" # 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-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} -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false} -pallet-collective = { path = "../../../../../substrate/frame/collective", default-features = false} -pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false} +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } +frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-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 } +pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-collective = { path = "../../../../../substrate/frame/collective", default-features = false } +pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } pallet-preimage = { path = "../../../../../substrate/frame/preimage", default-features = false } -pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false} +pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false } pallet-scheduler = { path = "../../../../../substrate/frame/scheduler", 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} -pallet-referenda = { path = "../../../../../substrate/frame/referenda", default-features = false} -pallet-ranked-collective = { path = "../../../../../substrate/frame/ranked-collective", default-features = false} -pallet-core-fellowship = { path = "../../../../../substrate/frame/core-fellowship", default-features = false} -pallet-salary = { path = "../../../../../substrate/frame/salary", default-features = false} -sp-api = { path = "../../../../../substrate/primitives/api", 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-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 } +pallet-core-fellowship = { path = "../../../../../substrate/frame/core-fellowship", default-features = false } +pallet-salary = { path = "../../../../../substrate/frame/salary", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", 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-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} +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} -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false} -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false} -polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false} -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } +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 } +westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants", 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} +pallet-message-queue = { path = "../../../../../substrate/frame/message-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-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } pallet-collective-content = { path = "../../../pallets/collective-content", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [dev-dependencies] -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false} +sp-io = { path = "../../../../../substrate/primitives/io", features = ["std"] } [features] -default = [ "std" ] +default = ["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-alliance/runtime-benchmarks", + "pallet-asset-rate/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-collective-content/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-core-fellowship/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", @@ -107,6 +117,7 @@ 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", @@ -118,7 +129,6 @@ 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", @@ -127,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", @@ -134,6 +145,7 @@ try-runtime = [ "pallet-collective-content/try-runtime", "pallet-collective/try-runtime", "pallet-core-fellowship/try-runtime", + "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", @@ -144,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", @@ -153,11 +166,11 @@ try-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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -169,6 +182,7 @@ std = [ "frame-try-runtime?/std", "log/std", "pallet-alliance/std", + "pallet-asset-rate/std", "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", @@ -176,6 +190,7 @@ std = [ "pallet-collective-content/std", "pallet-collective/std", "pallet-core-fellowship/std", + "pallet-message-queue/std", "pallet-multisig/std", "pallet-preimage/std", "pallet-proxy/std", @@ -187,11 +202,11 @@ 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", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "scale-info/std", @@ -202,7 +217,6 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", - "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", @@ -211,9 +225,14 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", + "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] -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/cumulus/parachains/runtimes/assets/asset-hub-polkadot/build.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/build.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/build.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/build.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs similarity index 86% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/mod.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs index b055ffc8abf1337e1260156cd149fe21b0573ba0..0c9f428c1396bede97a67002d0554d98d62dbc39 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs @@ -32,15 +32,15 @@ pub mod origins; mod tracks; use super::*; -use crate::xcm_config::{DotAssetHub, FellowshipAdminBodyId}; +use crate::xcm_config::{FellowshipAdminBodyId, LocationToAccountId, WndAssetHub}; use frame_support::traits::{EitherOf, MapSuccess, TryMapSuccess}; +use frame_system::EnsureRootWithSuccess; 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 sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace, ReplaceWithDefault}; use xcm::prelude::*; use xcm_builder::{AliasesIntoAccountId32, PayOverXcm}; @@ -66,7 +66,7 @@ impl pallet_ambassador_origins::Config for Runtime {} pub type AmbassadorCollectiveInstance = pallet_ranked_collective::Instance2; /// Demotion is by any of: -/// - Root can promote arbitrarily. +/// - Root can demote arbitrarily. /// - the FellowshipAdmin origin (i.e. token holder referendum); /// - a senior members vote by the rank two above the current rank. pub type DemoteOrigin = EitherOf< @@ -100,22 +100,31 @@ pub type PromoteOrigin = EitherOf< >, >; +/// Exchange is by any of: +/// - Root can exchange arbitrarily. +/// - the Fellows origin +pub type ExchangeOrigin = EitherOf>, Fellows>; + impl pallet_ranked_collective::Config for Runtime { type WeightInfo = weights::pallet_ranked_collective_ambassador_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + type AddOrigin = MapSuccess>; type PromoteOrigin = PromoteOrigin; type DemoteOrigin = DemoteOrigin; + type RemoveOrigin = Self::DemoteOrigin; + type ExchangeOrigin = ExchangeOrigin; type Polls = AmbassadorReferenda; type MinRankOfClass = sp_runtime::traits::Identity; + type MemberSwappedHandler = (crate::AmbassadorCore, crate::AmbassadorSalary); type VoteWeight = pallet_ranked_collective::Linear; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkSetup = (crate::AmbassadorCore, crate::AmbassadorSalary); } 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 temporarily 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; @@ -135,7 +144,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; @@ -183,8 +192,11 @@ impl pallet_core_fellowship::Config for Runtime { // - the FellowshipAdmin origin (i.e. token holder referendum); // - a vote among all Head Ambassadors. type ParamsOrigin = EitherOfDiverse< - EnsureXcm>, - EnsureHeadAmbassadorsVoice, + EnsureRoot, + EitherOfDiverse< + EnsureXcm>, + EnsureHeadAmbassadorsVoice, + >, >; // Induction (creating a candidate) is by any of: // - Root; @@ -192,14 +204,17 @@ impl pallet_core_fellowship::Config for Runtime { // - a single Head Ambassador; // - a vote among all senior members. type InductOrigin = EitherOfDiverse< - EnsureXcm>, + EnsureRoot, EitherOfDiverse< - pallet_ranked_collective::EnsureMember< - Runtime, - AmbassadorCollectiveInstance, - { ranks::HEAD_AMBASSADOR_TIER_5 }, + EnsureXcm>, + EitherOfDiverse< + pallet_ranked_collective::EnsureMember< + Runtime, + AmbassadorCollectiveInstance, + { ranks::HEAD_AMBASSADOR_TIER_5 }, + >, + EnsureAmbassadorsVoiceFrom>, >, - EnsureAmbassadorsVoiceFrom>, >, >; type ApproveOrigin = PromoteOrigin; @@ -212,10 +227,10 @@ pub type AmbassadorSalaryInstance = pallet_salary::Instance2; parameter_types! { // The interior location on AssetHub for the paying account. This is the Ambassador Salary // pallet instance (which sits at index 74). This sovereign account will need funding. - pub AmbassadorSalaryLocation: InteriorMultiLocation = PalletInstance(74).into(); + pub AmbassadorSalaryLocation: InteriorLocation = PalletInstance(74).into(); } -/// [`PayOverXcm`] setup to pay the Ambassador salary on the AssetHub in DOT. +/// [`PayOverXcm`] setup to pay the Ambassador salary on the AssetHub in WND. pub type AmbassadorSalaryPaymaster = PayOverXcm< AmbassadorSalaryLocation, crate::xcm_config::XcmRouter, @@ -223,7 +238,7 @@ pub type AmbassadorSalaryPaymaster = PayOverXcm< ConstU32<{ 6 * HOURS }>, AccountId, (), - ConvertToValue, + ConvertToValue, AliasesIntoAccountId32<(), AccountId>, >; diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/origins.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/origins.rs similarity index 100% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/origins.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/origins.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/tracks.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/tracks.rs similarity index 97% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/tracks.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/tracks.rs index 073d8e6ee362ad97f9fa66c612fca5e1a8123ab2..d4a2d3bbf1c7becba8ea6ed560496a0a8114b185 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/ambassador/tracks.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/tracks.rs @@ -56,7 +56,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -78,7 +78,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -100,7 +100,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -122,7 +122,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -144,7 +144,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -166,7 +166,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -188,7 +188,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -210,7 +210,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -232,7 +232,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 24 * HOURS, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 24 * HOURS, min_enactment_period: 1 * HOURS, min_approval: pallet_referenda::Curve::LinearDecreasing { diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs similarity index 58% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/mod.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs index 2a2757ea5cebc28a1f85bbc28d50e0c523b54d90..3816d2ed848ed51740283ffea31e9f7e53c01f1a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -14,36 +14,46 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -//! The Polkadot Technical Fellowship. +//! The Westend Technical Fellowship. -pub(crate) mod migration; mod origins; mod tracks; use crate::{ - impls::ToParentTreasury, weights, - xcm_config::{FellowshipAdminBodyId, UsdtAssetHub}, - AccountId, Balance, Balances, FellowshipReferenda, GovernanceLocation, PolkadotTreasuryAccount, - Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Scheduler, 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; +use polkadot_runtime_common::impls::{ + LocatableAssetConverter, VersionedLocatableAsset, VersionedLocationConverter, +}; +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 testnet_parachains_constants::westend::{account, currency::GRAND}; +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 testnet_parachains_constants::westend::currency::DOLLARS; /// The Fellowship members' ranks. pub mod ranks { @@ -60,11 +70,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; @@ -91,7 +96,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>; @@ -108,12 +113,17 @@ impl pallet_ranked_collective::Config for Runtime type WeightInfo = weights::pallet_ranked_collective_fellowship_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; - #[cfg(not(feature = "runtime-benchmarks"))] // Promotions and the induction of new members are serviced by `FellowshipCore` pallet instance. - type PromoteOrigin = frame_system::EnsureNever; + #[cfg(not(feature = "runtime-benchmarks"))] + type AddOrigin = frame_system::EnsureNever<()>; #[cfg(feature = "runtime-benchmarks")] + type AddOrigin = frame_system::EnsureRoot; + // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + #[cfg(not(feature = "runtime-benchmarks"))] + type PromoteOrigin = frame_system::EnsureNever; + #[cfg(feature = "runtime-benchmarks")] type PromoteOrigin = EnsureRootWithSuccess>; // Demotion is by any of: @@ -122,6 +132,7 @@ impl pallet_ranked_collective::Config for Runtime // // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + type RemoveOrigin = Self::DemoteOrigin; type DemoteOrigin = EitherOf< EnsureRootWithSuccess>, MapSuccess< @@ -129,9 +140,17 @@ impl pallet_ranked_collective::Config for Runtime Replace>, >, >; + // Exchange is by any of: + // - Root can exchange arbitrarily. + // - the Fellows origin + type ExchangeOrigin = + EitherOf>, Fellows>; type Polls = FellowshipReferenda; type MinRankOfClass = tracks::MinRankOfClass; + type MemberSwappedHandler = (crate::FellowshipCore, crate::FellowshipSalary); type VoteWeight = pallet_ranked_collective::Geometric; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkSetup = (crate::FellowshipCore, crate::FellowshipSalary); } pub type FellowshipCoreInstance = pallet_core_fellowship::Instance1; @@ -192,12 +211,10 @@ 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. - pub Interior: InteriorMultiLocation = PalletInstance(64).into(); + pub Interior: InteriorLocation = PalletInstance(64).into(); } const USDT_UNITS: u128 = 1_000_000; @@ -237,3 +254,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: InteriorLocation = 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 }>, + VersionedLocation, + VersionedLocatableAsset, + LocatableAssetConverter, + VersionedLocationConverter, +>; + +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 = VersionedLocation; + 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-polkadot/src/fellowship/origins.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/origins.rs similarity index 100% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/origins.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/origins.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/tracks.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/tracks.rs similarity index 98% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/tracks.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/tracks.rs index f4ba4e05ec166d9d9b7e5e5ed9b679042067ba13..099bdf4cf7539ed27c2c5af4178113d8cbbd2d1e 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/fellowship/tracks.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/tracks.rs @@ -124,7 +124,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -146,7 +146,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -168,7 +168,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -190,7 +190,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -212,7 +212,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -234,7 +234,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -256,7 +256,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -278,7 +278,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { @@ -300,7 +300,7 @@ impl pallet_referenda::TracksInfo for TracksInfo { max_deciding: 10, decision_deposit: 5 * DOLLARS, prepare_period: 30 * MINUTES, - decision_period: 7 * DAYS, + decision_period: 1 * DAYS, confirm_period: 30 * MINUTES, min_enactment_period: 5 * MINUTES, min_approval: pallet_referenda::Curve::LinearDecreasing { diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs similarity index 75% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/impls.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs index 9f4c2a6a4c94a5d28fce09dbe800b2d7226723cf..e5b176fc77873805fb0e4ed6dba74d720ea3479a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/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)>); @@ -129,7 +81,7 @@ where } fn proposal_of(proposal_hash: HashOf) -> Option> { - pallet_collective::Pallet::::proposal_of(proposal_hash) + pallet_collective::ProposalOf::::get(proposal_hash) } } @@ -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-polkadot/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs similarity index 77% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index cec4152bcc314d0c2621901f2b92a28a50a5b6ec..55269283e081240e26f7d0b5ea91d2d126b59b95 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -15,13 +15,13 @@ //! # Collectives Parachain //! -//! This parachain is for collectives that serve the Polkadot network. +//! This parachain is for collectives that serve the Westend network. //! Each collective is defined by a specialized (possibly instanced) pallet. //! //! ### Governance //! //! As a system parachain, Collectives defers its governance (namely, its `Root` origin), to -//! its Relay Chain parent, Polkadot. +//! its Relay Chain parent, Westend. //! //! ### Collator Selection //! @@ -44,18 +44,14 @@ pub mod xcm_config; pub mod fellowship; pub use ambassador::pallet_ambassador_origins; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; -use fellowship::{ - migration::import_kusama_fellowship, pallet_fellowship_origins, Fellows, - FellowshipCollectiveInstance, -}; -use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp, ToParentTreasury}; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use fellowship::{pallet_fellowship_origins, Fellows}; +use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::{AccountIdConversion, BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, Perbill, }; @@ -66,14 +62,15 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use codec::{Decode, Encode, MaxEncodedLen}; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ fungible::HoldConsideration, ConstBool, ConstU16, ConstU32, ConstU64, ConstU8, - EitherOfDiverse, InstanceFilter, LinearStoragePrice, + EitherOfDiverse, InstanceFilter, LinearStoragePrice, TransformOrigin, }, weights::{ConstantMultiplier, Weight}, PalletId, @@ -84,23 +81,28 @@ use frame_system::{ }; pub use parachains_common as common; use parachains_common::{ - impls::DealWithFees, - polkadot::{account::*, consensus::*, currency::*, fee::WeightToFee}, + impls::{DealWithFees, ToParentTreasury}, + message_queue::*, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use sp_runtime::RuntimeDebug; -use xcm_config::{GovernanceLocation, XcmConfig, XcmOriginToTransactDispatchOrigin}; +use testnet_parachains_constants::westend::{ + account::*, consensus::*, currency::*, fee::WeightToFee, time::*, +}; +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 xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; +use polkadot_runtime_common::{ + impls::VersionedLocatableAsset, BlockHashCount, SlowAdjustingFeeUpdate, +}; +use xcm::latest::{prelude::*, BodyId}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -112,10 +114,10 @@ impl_opaque_keys! { #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("collectives"), - impl_name: create_runtime_str!("collectives"), + spec_name: create_runtime_str!("collectives-westend"), + impl_name: create_runtime_str!("collectives-westend"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, @@ -159,25 +161,18 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type AccountId = AccountId; type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; type SystemWeightInfo = weights::frame_system::WeightInfo; type SS58Prefix = ConstU16<0>; @@ -189,7 +184,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -215,8 +210,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<0>; } @@ -334,6 +329,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::FellowshipReferenda { .. } | RuntimeCall::FellowshipCore { .. } | RuntimeCall::FellowshipSalary { .. } | + RuntimeCall::FellowshipTreasury { .. } | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), @@ -381,30 +377,59 @@ parameter_types! { } 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 DmpMessageHandler = DmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} +parameter_types! { + 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! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(xcm_config::DotLocation::get()); + pub FeeAssetId: AssetId = AssetId(xcm_config::WndLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -418,21 +443,19 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EitherOfDiverse, Fellows>; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; - type PriceForSiblingDelivery = - polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } pub const PERIOD: u32 = 6 * HOURS; @@ -456,9 +479,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -517,11 +539,8 @@ pub const MAX_FELLOWS: u32 = ALLIANCE_MAX_MEMBERS; pub const MAX_ALLIES: u32 = 100; parameter_types! { - pub const AllyDeposit: Balance = 1_000 * UNITS; // 1,000 DOT 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 PolkadotTreasuryAccount: AccountId = POLKADOT_TREASURY_PALLET_ID.into_account_truncating(); + pub const AllyDeposit: Balance = 1_000 * UNITS; // 1,000 WND bond to join as an Ally + 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. pub const AllianceRetirementPeriod: BlockNumber = (90 * DAYS) + ALLIANCE_MOTION_DURATION; @@ -534,7 +553,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; @@ -597,66 +616,82 @@ 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 { // 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 2, + ParachainInfo: parachain_info = 3, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + 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::{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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, - PolkadotXcm: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + 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::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, - 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, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + Proxy: pallet_proxy = 42, + Preimage: pallet_preimage = 43, + Scheduler: pallet_scheduler = 44, + AssetRate: pallet_asset_rate = 45, // The main stage. // The Alliance. - Alliance: pallet_alliance::{Pallet, Call, Storage, Event, Config} = 50, - AllianceMotion: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config} = 51, + Alliance: pallet_alliance = 50, + AllianceMotion: pallet_collective:: = 51, // The Fellowship. // pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; - FellowshipCollective: pallet_ranked_collective::::{Pallet, Call, Storage, Event} = 60, + FellowshipCollective: pallet_ranked_collective:: = 60, // pub type FellowshipReferendaInstance = pallet_referenda::Instance1; - FellowshipReferenda: pallet_referenda::::{Pallet, Call, Storage, Event} = 61, - FellowshipOrigins: pallet_fellowship_origins::{Origin} = 62, + FellowshipReferenda: pallet_referenda:: = 61, + FellowshipOrigins: pallet_fellowship_origins = 62, // pub type FellowshipCoreInstance = pallet_core_fellowship::Instance1; - FellowshipCore: pallet_core_fellowship::::{Pallet, Call, Storage, Event} = 63, + FellowshipCore: pallet_core_fellowship:: = 63, // pub type FellowshipSalaryInstance = pallet_salary::Instance1; - FellowshipSalary: pallet_salary::::{Pallet, Call, Storage, Event} = 64, + FellowshipSalary: pallet_salary:: = 64, + // pub type FellowshipTreasuryInstance = pallet_treasury::Instance1; + FellowshipTreasury: pallet_treasury:: = 65, // Ambassador Program. - AmbassadorCollective: pallet_ranked_collective::::{Pallet, Call, Storage, Event} = 70, - AmbassadorReferenda: pallet_referenda::::{Pallet, Call, Storage, Event} = 71, - AmbassadorOrigins: pallet_ambassador_origins::{Origin} = 72, - AmbassadorCore: pallet_core_fellowship::::{Pallet, Call, Storage, Event} = 73, - AmbassadorSalary: pallet_salary::::{Pallet, Call, Storage, Event} = 74, - AmbassadorContent: pallet_collective_content::::{Pallet, Call, Storage, Event} = 75, + AmbassadorCollective: pallet_ranked_collective:: = 70, + AmbassadorReferenda: pallet_referenda:: = 71, + AmbassadorOrigins: pallet_ambassador_origins = 72, + AmbassadorCore: pallet_core_fellowship:: = 73, + AmbassadorSalary: pallet_salary:: = 74, + AmbassadorContent: pallet_collective_content:: = 75, } ); @@ -684,10 +719,12 @@ pub type UncheckedExtrinsic = /// All migrations executed on runtime upgrade as a nested tuple of types implementing /// `OnRuntimeUpgrade`. Included migrations must be idempotent. type Migrations = ( - // v9420 - import_kusama_fellowship::Migration, // unreleased pallet_collator_selection::migration::v1::MigrateToV1, + // unreleased + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Executive: handles dispatch to the various modules. @@ -700,25 +737,23 @@ pub type Executive = frame_executive::Executive< Migrations, >; -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_proxy, Proxy] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_alliance, Alliance] [pallet_collective, AllianceMotion] - [pallet_xcm, PolkadotXcm] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] [pallet_referenda, FellowshipReferenda] @@ -730,17 +765,28 @@ mod benches { [pallet_collective_content, AmbassadorContent] [pallet_core_fellowship, AmbassadorCore] [pallet_salary, AmbassadorSalary] + [pallet_treasury, FellowshipTreasury] + [pallet_asset_rate, AssetRate] ); } impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -753,7 +799,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -906,6 +952,7 @@ impl_runtime_apis! { 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; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -935,6 +982,61 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::WndLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between Collectives and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }.into(), + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled on Collectives. + None + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + let whitelist: Vec = vec![ // Block Number hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/block_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/block_weights.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/block_weights.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..0b7a2fc21cde4f12c821a0e89982db4813f3f832 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,80 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("collectives-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// collectives-polkadot-dev +// --pallet +// cumulus_pallet_parachain_system +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/collectives/collectives-polkadot/src/weights +// --steps +// 50 +// --repeat +// 20 + +#![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: `48` + // Estimated: `8121` + // Minimum execution time: 1_988_000 picoseconds. + Weight::from_parts(2_039_000, 0) + .saturating_add(Weight::from_parts(0, 8121)) + // Standard Error: 30_660 + .saturating_add(Weight::from_parts(24_419_204, 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/collectives/collectives-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..e68c075bffc63e46668ffa84ce37143e848439fb --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,148 @@ +// 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 `cumulus_pallet_xcmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Olivers-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --pallet +// cumulus-pallet-xcmp-queue +// --chain +// collectives-polkadot-dev +// --output +// cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/cumulus_pallet_xcmp_queue.rs +// --extrinsic +// + +#![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: `142` + // Estimated: `1627` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(Weight::from_parts(0, 1627)) + .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: `148` + // Estimated: `3517` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_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: `142` + // Estimated: `1627` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 1627)) + .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: `177` + // Estimated: `1662` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(5_000_000, 0) + .saturating_add(Weight::from_parts(0, 1662)) + .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: `65777` + // Estimated: `69242` + // Minimum execution time: 60_000_000 picoseconds. + Weight::from_parts(63_000_000, 0) + .saturating_add(Weight::from_parts(0, 69242)) + .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: `65776` + // Estimated: `69241` + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(43_000_000, 0) + .saturating_add(Weight::from_parts(0, 69241)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/extrinsic_weights.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/extrinsic_weights.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/frame_system.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs similarity index 76% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/frame_system.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs index 31cd502d1927b0cfeac64774567c937d7ee33050..f43c5e0a40b6356f3caee233ad1af7289a95a7df 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -152,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-polkadot/src/weights/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs similarity index 60% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/mod.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs index b5a3a892f79a56f034b60cbb3c4e142a5ff6ef16..a9a298e547edb49738b9a0612d04d4141301d4ba 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs @@ -1,30 +1,32 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 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_alliance; +pub mod pallet_asset_rate; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_collective; pub mod pallet_collective_content; pub mod pallet_core_fellowship_ambassador_core; pub mod pallet_core_fellowship_fellowship_core; +pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_preimage; pub mod pallet_proxy; @@ -37,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; @@ -44,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-polkadot/src/weights/pallet_alliance.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_alliance.rs similarity index 97% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_alliance.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_alliance.rs index 9e3acac46a4c9e7bc39d55cc5d928150aab3aaad..d8ede609a67c6df5c707504206b72e7382d2f7b4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_alliance.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_alliance.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for `pallet_alliance` //! 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/assets/asset-hub-polkadot/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_balances.rs similarity index 73% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_balances.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_balances.rs index a7f02ba24fd13ff409e599b5344d8c6a512aa9c9..602e7ca50c136c3c862bbf2f43fa452b9518116e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 56_173_000 picoseconds. - Weight::from_parts(57_097_000, 0) + // Minimum execution time: 45_085_000 picoseconds. + Weight::from_parts(45_772_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_470_000 picoseconds. - Weight::from_parts(42_051_000, 0) + // Minimum execution time: 35_447_000 picoseconds. + Weight::from_parts(36_143_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_771_000 picoseconds. - Weight::from_parts(15_125_000, 0) + // Minimum execution time: 12_314_000 picoseconds. + Weight::from_parts(12_679_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 22_210_000 picoseconds. - Weight::from_parts(22_712_000, 0) + // Minimum execution time: 17_455_000 picoseconds. + Weight::from_parts(17_902_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_475_000 picoseconds. - Weight::from_parts(58_343_000, 0) + // Minimum execution time: 46_785_000 picoseconds. + Weight::from_parts(47_436_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 52_139_000 picoseconds. - Weight::from_parts(52_601_000, 0) + // Minimum execution time: 43_948_000 picoseconds. + Weight::from_parts(44_680_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 17_372_000 picoseconds. - Weight::from_parts(17_978_000, 0) + // Minimum execution time: 15_267_000 picoseconds. + Weight::from_parts(15_499_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_143_000 picoseconds. - Weight::from_parts(17_475_000, 0) + // Minimum execution time: 14_817_000 picoseconds. + Weight::from_parts(15_287_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 16_909 - .saturating_add(Weight::from_parts(15_474_628, 0).saturating_mul(u.into())) + // Standard Error: 11_738 + .saturating_add(Weight::from_parts(13_511_800, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_382_000 picoseconds. + Weight::from_parts(5_768_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collator_selection.rs similarity index 87% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collator_selection.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collator_selection.rs index ea237d602a9b5a1190b89aaf4fdf955bdbc9df58..03f3ff602a5b3e91c4e2ff90a4a3433d513079a1 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collator_selection.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! @@ -122,7 +121,7 @@ impl pallet_collator_selection::WeightInfo for WeightIn } /// 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() -> Weight { + fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -176,6 +175,30 @@ impl pallet_collator_selection::WeightInfo for WeightIn .saturating_add(T::DbWeight::get().reads(2)) .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) diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective.rs similarity index 95% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective.rs index 2d344ad0db7ba9e7b98303624e4f3f4a11c4087a..9133baa6120cff6f13bf3a3f45ec5db4b3fe5f3f 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_collective` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective_content.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective_content.rs similarity index 84% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective_content.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective_content.rs index e66907b9453060a29b5e6635fa03a31e0dc782ad..6be94db22db9f88db97f60db4d850fcd4179aa26 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_collective_content.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_collective_content.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_collective_content` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_core_fellowship_ambassador_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs similarity index 100% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_core_fellowship_ambassador_core.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_core_fellowship_fellowship_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs similarity index 94% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_core_fellowship_fellowship_core.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs index 434986b03bba84c8ef43250c2192a946de09b286..471ee82ead729ea5abff616f0c9fe3a86704fd91 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_core_fellowship_fellowship_core.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for `pallet_core_fellowship` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..4bd71c4e7d497b84f9a5b910015e439b69e41ef9 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-24, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("collectives-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// collectives-polkadot-dev +// --pallet +// pallet_message_queue +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --output +// parachains/runtimes/collectives/collectives-polkadot/src/weights + +#![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: 11_440_000 picoseconds. + Weight::from_parts(11_440_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_077_000 picoseconds. + Weight::from_parts(11_077_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: 3_977_000 picoseconds. + Weight::from_parts(3_977_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: 4_831_000 picoseconds. + Weight::from_parts(4_831_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: 5_192_000 picoseconds. + Weight::from_parts(5_192_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: 58_750_000 picoseconds. + Weight::from_parts(58_750_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: 5_107_000 picoseconds. + Weight::from_parts(5_107_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: 46_814_000 picoseconds. + Weight::from_parts(46_814_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: 52_510_000 picoseconds. + Weight::from_parts(52_510_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: 71_930_000 picoseconds. + Weight::from_parts(71_930_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/collectives/collectives-polkadot/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs similarity index 90% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_multisig.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs index a8dd58320cc204382c0160f9629e386ddd7851fb..a7827b7200906370039d245b02fcaec6d22430d2 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_preimage.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_preimage.rs similarity index 93% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_preimage.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_preimage.rs index e9f565d9387da08f35419baa196ce7ef77e265be..9b45c8758186d5b36ae5af158b35e4349cb7a1ed 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_preimage.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_preimage.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_preimage` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_proxy.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_proxy.rs similarity index 93% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_proxy.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_proxy.rs index faf100d23bb0d5c68b1da4d4421b05315ddfd3de..59d9f912bf11d92a9c214e9ff83aa880482d8faa 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_proxy.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_proxy.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_proxy` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_ambassador_collective.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_ambassador_collective.rs similarity index 99% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_ambassador_collective.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_ambassador_collective.rs index a6372c4b89dc222ca6a5a6acd36306d79ea3ffb2..8f6aa583a41c1685ab2d46e14ef1266a79452291 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_ambassador_collective.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_ambassador_collective.rs @@ -174,4 +174,7 @@ impl pallet_ranked_collective::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2550).saturating_mul(n.into())) } + fn exchange_member() -> Weight { + todo!() + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_fellowship_collective.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_fellowship_collective.rs similarity index 93% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_fellowship_collective.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_fellowship_collective.rs index 7515aecbb525a4b4de59bd05e8329166a5515ff1..6dfe9b88ff63356123e7c7faf7fbd2c2e4b81dee 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_ranked_collective_fellowship_collective.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_ranked_collective_fellowship_collective.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_ranked_collective` //! @@ -174,4 +173,7 @@ impl pallet_ranked_collective::WeightInfo for WeightInf .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2550).saturating_mul(n.into())) } + fn exchange_member() -> Weight { + todo!() + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_referenda_ambassador_referenda.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_referenda_ambassador_referenda.rs similarity index 100% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_referenda_ambassador_referenda.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_referenda_ambassador_referenda.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_referenda_fellowship_referenda.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_referenda_fellowship_referenda.rs similarity index 97% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_referenda_fellowship_referenda.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_referenda_fellowship_referenda.rs index 5b4aed06899fcca022895488477ffb058ae76399..63f68833795f20023d8e9040f105d98bc161ef4d 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_referenda_fellowship_referenda.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_referenda_fellowship_referenda.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_referenda` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_salary_ambassador_salary.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_salary_ambassador_salary.rs similarity index 100% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_salary_ambassador_salary.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_salary_ambassador_salary.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_salary_fellowship_salary.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_salary_fellowship_salary.rs similarity index 93% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_salary_fellowship_salary.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_salary_fellowship_salary.rs index 9bb7e68d3145c339505f9c6a729eca1217c02537..37680b4e5df719e61f174c3260bde023fd685623 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_salary_fellowship_salary.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_salary_fellowship_salary.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for `pallet_salary` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs new file mode 100644 index 0000000000000000000000000000000000000000..42e37b967e4c88acccd13c8cb71bd22b1bc2d3dd --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs @@ -0,0 +1,286 @@ +// 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_scheduler` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// 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_scheduler +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_scheduler`. +pub struct WeightInfo(PhantomData); +impl pallet_scheduler::WeightInfo for WeightInfo { + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn service_agendas_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `31` + // Estimated: `1489` + // Minimum execution time: 2_475_000 picoseconds. + Weight::from_parts(2_644_000, 0) + .saturating_add(Weight::from_parts(0, 1489)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 200]`. + fn service_agenda_base(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 2_898_000 picoseconds. + Weight::from_parts(1_532_342, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 4_736 + .saturating_add(Weight::from_parts(412_374, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_task_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_171_000 picoseconds. + Weight::from_parts(3_349_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// The range of component `s` is `[128, 4194304]`. + fn service_task_fetched(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_329_000 picoseconds. + Weight::from_parts(17_604_000, 0) + .saturating_add(Weight::from_parts(0, 3711)) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_256, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) + } + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn service_task_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_503_000 picoseconds. + Weight::from_parts(4_677_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_task_periodic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_145_000 picoseconds. + Weight::from_parts(3_252_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn execute_dispatch_signed() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_804_000 picoseconds. + Weight::from_parts(1_891_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn execute_dispatch_unsigned() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_776_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 199]`. + fn schedule(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 8_629_000 picoseconds. + Weight::from_parts(6_707_232, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 5_580 + .saturating_add(Weight::from_parts(471_827, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 200]`. + fn cancel(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 12_675_000 picoseconds. + Weight::from_parts(7_791_682, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 5_381 + .saturating_add(Weight::from_parts(653_023, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 199]`. + fn schedule_named(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `468 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_908_000 picoseconds. + Weight::from_parts(11_833_059, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 5_662 + .saturating_add(Weight::from_parts(482_816, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 200]`. + fn cancel_named(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `509 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 15_506_000 picoseconds. + Weight::from_parts(11_372_975, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 5_765 + .saturating_add(Weight::from_parts(656_322, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 200]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `159279` + // Minimum execution time: 14_069_000 picoseconds. + Weight::from_parts(14_868_345, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 425 + .saturating_add(Weight::from_parts(33_468, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_session.rs similarity index 79% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_session.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_session.rs index 2af8ce29a196ef21d8f203ccb5d62a1bfeb9a6b6..2ac0804df8903dd5ab4184e21b49cb13b32b48dc 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_session.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_timestamp.rs similarity index 76% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_timestamp.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_timestamp.rs index bc149ec63a176f09e226988d3344f29f9cf3e530..ca06f43f92e425b8d93b8bd394f0897f71627252 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_timestamp.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! 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-polkadot/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_utility.rs similarity index 81% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_utility.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_utility.rs index 5d6b0cb8285c91a98c99e97c7eb948086d1fb2b8..c60a79d91da32c863604b1d220b2e6c8c4159063 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_utility.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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` //! diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs similarity index 54% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index c2ed67d2f5d45196894955e3e2fbe41e175454c0..5d427d850046ff030c6c5b6247426849227e7ea1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-kusama-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/assets/asset-hub-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -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,35 +62,75 @@ 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: `109` - // Estimated: `3574` - // Minimum execution time: 30_015_000 picoseconds. - Weight::from_parts(30_576_000, 0) - .saturating_add(Weight::from_parts(0, 3574)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 21_813_000 picoseconds. + Weight::from_parts(22_332_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: `214` + // Estimated: `3679` + // Minimum execution time: 93_243_000 picoseconds. + Weight::from_parts(95_650_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 reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 24_785_000 picoseconds. - Weight::from_parts(25_097_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) + .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`) - fn reserve_transfer_assets() -> Weight { + /// 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: `0` - // Estimated: `1489` - // Minimum execution time: 18_561_000 picoseconds. - Weight::from_parts(19_121_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 96_199_000 picoseconds. + Weight::from_parts(98_620_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`) @@ -108,8 +148,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_298_000 picoseconds. - Weight::from_parts(9_721_000, 0) + // Minimum execution time: 6_442_000 picoseconds. + Weight::from_parts(6_682_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -119,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_912_000 picoseconds. - Weight::from_parts(3_262_000, 0) + // Minimum execution time: 1_833_000 picoseconds. + Weight::from_parts(1_973_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -128,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) @@ -142,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: `109` - // Estimated: `3574` - // Minimum execution time: 35_127_000 picoseconds. - Weight::from_parts(36_317_000, 0) - .saturating_add(Weight::from_parts(0, 3574)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 27_318_000 picoseconds. + Weight::from_parts(28_224_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .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) @@ -166,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: `326` - // Estimated: `3791` - // Minimum execution time: 36_634_000 picoseconds. - Weight::from_parts(37_983_000, 0) - .saturating_add(Weight::from_parts(0, 3791)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `363` + // Estimated: `3828` + // Minimum execution time: 29_070_000 picoseconds. + Weight::from_parts(30_205_000, 0) + .saturating_add(Weight::from_parts(0, 3828)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) @@ -180,48 +224,50 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_940_000 picoseconds. - Weight::from_parts(3_085_000, 0) + // Minimum execution time: 1_904_000 picoseconds. + Weight::from_parts(2_033_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `162` - // Estimated: `11052` - // Minimum execution time: 17_400_000 picoseconds. - Weight::from_parts(17_759_000, 0) - .saturating_add(Weight::from_parts(0, 11052)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `159` + // Estimated: `13524` + // Minimum execution time: 18_348_000 picoseconds. + Weight::from_parts(18_853_000, 0) + .saturating_add(Weight::from_parts(0, 13524)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `166` - // Estimated: `11056` - // Minimum execution time: 17_287_000 picoseconds. - Weight::from_parts(17_678_000, 0) - .saturating_add(Weight::from_parts(0, 11056)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `163` + // Estimated: `13528` + // Minimum execution time: 17_964_000 picoseconds. + Weight::from_parts(18_548_000, 0) + .saturating_add(Weight::from_parts(0, 13528)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `13538` - // Minimum execution time: 18_941_000 picoseconds. - Weight::from_parts(19_285_000, 0) - .saturating_add(Weight::from_parts(0, 13538)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16013` + // Minimum execution time: 19_708_000 picoseconds. + Weight::from_parts(20_157_000, 0) + .saturating_add(Weight::from_parts(0, 16013)) + .saturating_add(T::DbWeight::get().reads(6)) } /// 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) @@ -234,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: `176` - // Estimated: `6116` - // Minimum execution time: 32_668_000 picoseconds. - Weight::from_parts(33_533_000, 0) - .saturating_add(Weight::from_parts(0, 6116)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `212` + // Estimated: `6152` + // Minimum execution time: 26_632_000 picoseconds. + Weight::from_parts(27_314_000, 0) + .saturating_add(Weight::from_parts(0, 6152)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:3 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `206` - // Estimated: `8621` - // Minimum execution time: 9_182_000 picoseconds. - Weight::from_parts(9_498_000, 0) - .saturating_add(Weight::from_parts(0, 8621)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11096` + // Minimum execution time: 11_929_000 picoseconds. + Weight::from_parts(12_304_000, 0) + .saturating_add(Weight::from_parts(0, 11096)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `11063` - // Minimum execution time: 17_519_000 picoseconds. - Weight::from_parts(17_943_000, 0) - .saturating_add(Weight::from_parts(0, 11063)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `170` + // Estimated: `13535` + // Minimum execution time: 18_599_000 picoseconds. + Weight::from_parts(19_195_000, 0) + .saturating_add(Weight::from_parts(0, 13535)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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) @@ -279,12 +327,50 @@ 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: `179` - // Estimated: `11069` - // Minimum execution time: 38_680_000 picoseconds. - Weight::from_parts(39_984_000, 0) - .saturating_add(Weight::from_parts(0, 11069)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `212` + // Estimated: `13577` + // Minimum execution time: 35_524_000 picoseconds. + Weight::from_parts(36_272_000, 0) + .saturating_add(Weight::from_parts(0, 13577)) + .saturating_add(T::DbWeight::get().reads(11)) .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: 4_044_000 picoseconds. + Weight::from_parts(4_238_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: 25_741_000 picoseconds. + Weight::from_parts(26_301_000, 0) + .saturating_add(Weight::from_parts(0, 11205)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `3625` + // Minimum execution time: 35_925_000 picoseconds. + Weight::from_parts(36_978_000, 0) + .saturating_add(Weight::from_parts(0, 3625)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/paritydb_weights.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/paritydb_weights.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs similarity index 100% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/rocksdb_weights.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/rocksdb_weights.rs diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs similarity index 77% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs rename to cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index c64d688e5f1bc69f1d7e4c4170cac61869ff30dd..b83106a5828402390d1bd237ceb510d78b020b6d 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -16,54 +16,62 @@ use super::{ AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, Fellows, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TransactionByteFee, WeightToFee, XcmpQueue, + TransactionByteFee, WeightToFee, WestendTreasuryAccount, XcmpQueue, }; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing}, + parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; -use parachains_common::{impls::ToStakingPot, xcm_config::ConcreteAssetFromSystem}; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, +}; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; +use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, - LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + 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 DotLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: Option = Some(NetworkId::Polkadot); + pub const WndLocation: Location = Location::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 UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub RelayTreasuryLocation: Location = (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 AssetHub: MultiLocation = (Parent, Parachain(1000)).into(); + pub const GovernanceLocation: Location = Location::parent(); + pub const FellowshipAdminBodyId: BodyId = BodyId::Index(xcm_constants::body::FELLOWSHIP_ADMIN_INDEX); + pub AssetHub: Location = (Parent, Parachain(1000)).into(); + pub const TreasurerBodyId: BodyId = BodyId::Treasury; pub AssetHubUsdtId: AssetId = (PalletInstance(50), GeneralIndex(1984)).into(); pub UsdtAssetHub: LocatableAssetId = LocatableAssetId { location: AssetHub::get(), asset_id: AssetHubUsdtId::get(), }; - pub DotAssetHub: LocatableAssetId = LocatableAssetId { + pub WndAssetHub: LocatableAssetId = LocatableAssetId { location: AssetHub::get(), - asset_id: DotLocation::get().into(), + asset_id: WndLocation::get().into(), }; } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -75,13 +83,13 @@ pub type LocationToAccountId = ( AccountId32Aliases, ); -/// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +/// Means for transacting the native currency on this chain.#[allow(deprecated)] +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Convert an XCM MultiLocation into a local account id: + IsConcrete, + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -125,15 +133,11 @@ parameter_types! { pub const FellowsBodyId: BodyId = BodyId::Technical; } -match_types! { - 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(_) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -159,22 +163,19 @@ 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::PolkadotXcm(pallet_xcm::Call::force_xcm_version { .. }) | - RuntimeCall::XcmpQueue(..) | - RuntimeCall::DmpQueue(..) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::PolkadotXcm( + pallet_xcm::Call::force_xcm_version { .. } | + pallet_xcm::Call::force_default_xcm_version { .. } + ) | RuntimeCall::XcmpQueue(..) | + RuntimeCall::MessageQueue(..) | RuntimeCall::Alliance( // `init_members` accepts unbounded vecs as arguments, // but the call can be initiated only by root origin. @@ -233,7 +234,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>, @@ -242,17 +243,25 @@ pub type Barrier = TrailingSetTopicAsId< >, >; +/// 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, +); + /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// - DOT with the parent Relay Chain and sibling parachains. -pub type TrustedTeleporters = ConcreteAssetFromSystem; +pub type TrustedTeleporters = ConcreteAssetFromSystem; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Collectives does not recognize a reserve location for any asset. Users must teleport DOT + // Collectives does not recognize a reserve location for any asset. Users must teleport WND // where allowed (e.g. with the Relay Chain). type IsReserve = (); type IsTeleporter = TrustedTeleporters; @@ -260,7 +269,7 @@ impl xcm_executor::Config for XcmConfig { type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = - UsingComponents>; + UsingComponents>; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; @@ -269,15 +278,22 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); - type FeeManager = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } -/// Converts a local signed origin into an XCM multilocation. +/// Converts a local signed origin into an XCM location. /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; @@ -295,10 +311,10 @@ pub type XcmRouter = WithUniqueTopic<( #[cfg(feature = "runtime-benchmarks")] parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); + pub ReachableDest: Option = Some(Parent.into()); } -/// Type to convert the Fellows origin to a Plurality `MultiLocation` value. +/// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice; impl pallet_xcm::Config for Runtime { @@ -325,8 +341,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..561e8276b5f0543001e10fd21345ea5d3a65fee5 --- /dev/null +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "testnet-parachains-constants" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +description = "Common constants for Testnet Parachains runtimes" +license = "Apache-2.0" + +[lints] +workspace = true + +[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 +polkadot-core-primitives = { path = "../../../../polkadot/core-primitives", default-features = false } +rococo-runtime-constants = { path = "../../../../polkadot/runtime/rococo/constants", default-features = false, optional = true } +westend-runtime-constants = { path = "../../../../polkadot/runtime/westend/constants", default-features = false, optional = true } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } + +# Cumulus +cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } + +[features] +default = ["std"] +std = [ + "cumulus-primitives-core/std", + "frame-support/std", + "polkadot-core-primitives/std", + "rococo-runtime-constants?/std", + "sp-runtime/std", + "westend-runtime-constants?/std", + "xcm/std", +] + +# Test runtimes specific features. +rococo = ["rococo-runtime-constants"] +westend = ["westend-runtime-constants"] diff --git a/cumulus/parachains/runtimes/constants/src/lib.rs b/cumulus/parachains/runtimes/constants/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..40d799bac7bd354cb9f593a5c0340f691194f371 --- /dev/null +++ b/cumulus/parachains/runtimes/constants/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)] + +#[cfg(feature = "rococo")] +pub mod rococo; +#[cfg(feature = "westend")] +pub mod westend; diff --git a/cumulus/parachains/common/src/rococo.rs b/cumulus/parachains/runtimes/constants/src/rococo.rs similarity index 71% rename from cumulus/parachains/common/src/rococo.rs rename to cumulus/parachains/runtimes/constants/src/rococo.rs index 6e31def4b55b923f1596793e6cb114163551c017..d10b5e7d3af4368d9bd50664b8be029a3f812795 100644 --- a/cumulus/parachains/common/src/rococo.rs +++ b/cumulus/parachains/runtimes/constants/src/rococo.rs @@ -108,12 +108,56 @@ pub mod fee { /// Consensus-related. pub mod consensus { + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the /// number of blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} + +pub mod snowbridge { + use frame_support::parameter_types; + use xcm::opaque::lts::NetworkId; + + /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. + pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; + + parameter_types! { + /// Network and location for the Ethereum chain. On Rococo, the Ethereum chain bridged + /// to is the Sepolia Ethereum testnet, with chain ID 11155111. + /// + /// + pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + } } diff --git a/cumulus/parachains/common/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs similarity index 63% rename from cumulus/parachains/common/src/westend.rs rename to cumulus/parachains/runtimes/constants/src/westend.rs index 9d3e0bd1a0e2a51d2dde47b5be329be0bc8b5d38..607d91e8808d7d9c0aacaa8d1f4056ba5ff06821 100644 --- a/cumulus/parachains/common/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/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,12 +131,40 @@ pub mod fee { /// 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. + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the + /// relay chain. + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; + /// How many parachain blocks are processed by the relay chain per parent. Limits the number of + /// blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index a020b66baae631e5f10eeef6e4b7cc615a9d9f25..e4ac2016a726c4ba9ad63af2ddd332cc6dab415f 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "contracts-rococo-runtime" -version = "0.2.0" +version = "0.8.0" description = "Parachain testnet runtime for FRAME Contracts pallet." authors.workspace = true edition.workspace = true +license = "Apache-2.0" + +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -14,48 +18,45 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" # Substrate -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-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} -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true} -frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", 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} -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false} -pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false} -pallet-insecure-randomness-collective-flip = { path = "../../../../../substrate/frame/insecure-randomness-collective-flip", default-features = false} -pallet-balances = { path = "../../../../../substrate/frame/balances", 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} -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false} -pallet-contracts = { path = "../../../../../substrate/frame/contracts", default-features = false} -pallet-contracts-primitives = { path = "../../../../../substrate/frame/contracts/primitives", 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 } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", 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 } +pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } +pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } +pallet-insecure-randomness-collective-flip = { path = "../../../../../substrate/frame/insecure-randomness-collective-flip", default-features = false } +pallet-balances = { path = "../../../../../substrate/frame/balances", 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 } +pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } +pallet-contracts = { path = "../../../../../substrate/frame/contracts", default-features = false } # Polkadot pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } -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 } @@ -65,27 +66,29 @@ xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkad # 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",] } +pallet-message-queue = { path = "../../../../../substrate/frame/message-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-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } [features] -default = [ "std" ] +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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -100,9 +103,9 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-collator-selection/std", - "pallet-contracts-primitives/std", "pallet-contracts/std", "pallet-insecure-randomness-collective-flip/std", + "pallet-message-queue/std", "pallet-multisig/std", "pallet-session/std", "pallet-sudo/std", @@ -113,7 +116,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "rococo-runtime-constants/std", @@ -132,6 +134,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -141,6 +144,7 @@ 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", @@ -150,6 +154,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -165,7 +170,6 @@ 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", @@ -179,6 +183,7 @@ try-runtime = [ "pallet-collator-selection/try-runtime", "pallet-contracts/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", + "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", @@ -191,4 +196,7 @@ try-runtime = [ "sp-runtime/try-runtime", ] -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/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index 1c99393d5e52fccf427b40f232d55a833d082c9e..171ac6a9528f134d9c22548500805ef36e9504f9 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -21,14 +21,13 @@ use frame_support::{ parameter_types, traits::{ConstBool, ConstU32, Nothing}, }; +use frame_system::EnsureSigned; use pallet_contracts::{ - migration::{v12, v13, v14, v15}, - weights::SubstrateWeight, - Config, DebugInfo, DefaultAddressGenerator, Frame, Schedule, + weights::SubstrateWeight, Config, DebugInfo, DefaultAddressGenerator, Frame, Schedule, }; use sp_runtime::Perbill; -pub use parachains_common::{rococo::currency::deposit, AVERAGE_ON_INITIALIZE_RATIO}; +use testnet_parachains_constants::rococo::currency::deposit; // Prints debug output of the `contracts` pallet to stdout if the node is // started with `-lruntime::contracts=debug`. @@ -67,16 +66,15 @@ impl Config for Runtime { type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; type MaxDelegateDependencies = ConstU32<32>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Migrations = ( - v12::Migration, - v13::Migration, - v14::Migration, - v15::Migration, - ); + type Migrations = (); type RuntimeHoldReason = RuntimeHoldReason; type Debug = (); type Environment = (); + type ApiVersion = (); + type Xcm = pallet_xcm::Pallet; } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 1ea3eaa2e4785cbcdf99432710eb4d0780f8c3fa..17bb45f6b1b89c807f5f61859e2d30d05819c5c5 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -29,14 +29,15 @@ mod contracts; mod weights; mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::AggregateMessageOrigin; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::Block as BlockT, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, Perbill, }; use sp_std::prelude::*; @@ -45,23 +46,22 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, Everything}, + traits::{ConstBool, ConstU16, ConstU32, ConstU64, ConstU8}, weights::{ConstantMultiplier, Weight}, PalletId, }; use frame_system::limits::{BlockLength, BlockWeights}; pub use parachains_common as common; use parachains_common::{ - impls::DealWithFees, - rococo::{consensus::*, currency::*, fee::WeightToFee}, - AccountId, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + impls::DealWithFees, message_queue::*, AccountId, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; pub use parachains_common::{AuraId, Balance}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::CollatorSelectionUpdateOrigin; #[cfg(any(feature = "std", test))] @@ -97,10 +97,14 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( - cumulus_pallet_dmp_queue::migration::Migration, cumulus_pallet_parachain_system::migration::Migration, - cumulus_pallet_xcmp_queue::migration::Migration, + cumulus_pallet_xcmp_queue::migration::v2::MigrationToV2, + cumulus_pallet_xcmp_queue::migration::v3::MigrationToV3, pallet_contracts::Migration, + // unreleased + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); type EventRecord = frame_system::EventRecord< @@ -129,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: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -167,25 +171,17 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = Version; - type PalletInfo = PalletInfo; - type OnNewAccount = (); - type OnKilledAccount = (); type AccountData = pallet_balances::AccountData; type SystemWeightInfo = frame_system::weights::SubstrateWeight; type SS58Prefix = ConstU16<42>; @@ -197,7 +193,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } @@ -206,6 +202,10 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (CollatorSelection,); } +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + impl pallet_balances::Config for Runtime { type MaxLocks = ConstU32<50>; /// The type for recording an account's balance. @@ -213,14 +213,14 @@ impl pallet_balances::Config for Runtime { /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU128; + type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<0>; } @@ -266,30 +266,60 @@ impl pallet_utility::Config for Runtime { parameter_types! { pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; - type DmpMessageHandler = DmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl pallet_insecure_randomness_collective_flip::Config for Runtime {} impl parachain_info::Config for Runtime {} +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 = (); + #[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! { @@ -315,9 +345,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -351,71 +380,76 @@ 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, - RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip::{Pallet, Storage} = 2, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 4, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip = 2, + Timestamp: pallet_timestamp = 3, + ParachainInfo: parachain_info = 4, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + 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::{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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, - PolkadotXcm: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 31, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + MessageQueue: pallet_message_queue = 34, // Smart Contracts. - Contracts: pallet_contracts::{Pallet, Call, Storage, Event, HoldReason} = 40, + Contracts: pallet_contracts = 40, // Handy utilities. - Utility: pallet_utility::{Pallet, Call, Event} = 50, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 51, + Utility: pallet_utility = 50, + Multisig: pallet_multisig = 51, // Sudo - Sudo: pallet_sudo::{Pallet, Call, Config, Event, Storage} = 100, + Sudo: pallet_sudo = 100, } ); -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_sudo, Sudo] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [pallet_contracts, Contracts] - [pallet_xcm, PolkadotXcm] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] ); } impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -428,7 +462,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -560,7 +594,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_contracts_primitives::ContractExecResult { + ) -> pallet_contracts::ContractExecResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_call( origin, @@ -580,10 +614,10 @@ impl_runtime_apis! { value: Balance, gas_limit: Option, storage_deposit_limit: Option, - code: pallet_contracts_primitives::Code, + code: pallet_contracts::Code, data: Vec, salt: Vec, - ) -> pallet_contracts_primitives::ContractInstantiateResult { + ) -> pallet_contracts::ContractInstantiateResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_instantiate( origin, @@ -603,7 +637,7 @@ impl_runtime_apis! { code: Vec, storage_deposit_limit: Option, determinism: pallet_contracts::Determinism, - ) -> pallet_contracts_primitives::CodeUploadResult { + ) -> pallet_contracts::CodeUploadResult { Contracts::bare_upload_code( origin, code, @@ -615,7 +649,7 @@ impl_runtime_apis! { fn get_storage( address: AccountId, key: Vec, - ) -> pallet_contracts_primitives::GetStorageResult { + ) -> pallet_contracts::GetStorageResult { Contracts::get_storage(address, key) } } @@ -649,6 +683,7 @@ impl_runtime_apis! { 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; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -678,6 +713,62 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::RelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + use xcm::latest::prelude::*; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between Contracts-System-Para and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled on Contracts-System-Para. + None + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(EXISTENTIAL_DEPOSIT), + } + } + } + let whitelist: Vec = vec![ // Block Number hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), 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 71a789e3e25a4e305544d32472b3b882220cc73c..ac6fe634662f9789e6e6d8e4750779517036ee82 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -17,41 +17,46 @@ use super::{ AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; -use crate::common::rococo::currency::CENTS; +use cumulus_primitives_core::AggregateMessageOrigin; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, EitherOfDiverse, Everything, Nothing}, + parameter_types, + traits::{ConstU32, Contains, EitherOfDiverse, Equals, Everything, Nothing}, weights::Weight, }; 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::SystemParachains; use sp_runtime::traits::AccountIdConversion; +use testnet_parachains_constants::rococo::currency::CENTS; use xcm::latest::prelude::*; 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, XcmFeesToAccount, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayLocation: Location = Location::parent(); pub const RelayNetwork: Option = None; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const ExecutiveBody: BodyId = BodyId::Executive; - pub TreasuryAccount: Option = Some(TREASURY_PALLET_ID.into_account_truncating()); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); } /// We allow root and the Relay Chain council to execute privileged collator selection operations. @@ -60,7 +65,7 @@ pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< EnsureXcm>, >; -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -73,12 +78,12 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -116,15 +121,11 @@ parameter_types! { pub const MaxInstructions: u32 = 100; } -match_types! { - 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(_) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } pub type Barrier = TrailingSetTopicAsId< @@ -140,10 +141,14 @@ pub type Barrier = TrailingSetTopicAsId< // If the message is one that immediately attempts to pay for execution, then // allow it. AllowTopLevelPaidExecutionFrom, - // Parent and its pluralities (i.e. governance bodies) get free execution. - AllowExplicitUnpaidExecutionFrom, + // Parent, its pluralities (i.e. governance bodies) and relay treasury pallet + // get free execution. + AllowExplicitUnpaidExecutionFrom<( + ParentOrParentsPlurality, + Equals, + )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -152,6 +157,14 @@ pub type Barrier = TrailingSetTopicAsId< >, >; +/// 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 TrustedTeleporter = ConcreteAssetFromSystem; pub struct XcmConfig; @@ -174,20 +187,22 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = ConstU32<8>; type AssetLocker = (); type AssetExchanger = (); - type FeeManager = XcmFeesToAccount< - Self, - RelayOrOtherSystemParachains, - AccountId, - TreasuryAccount, + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } -/// Converts a local signed origin into an XCM multilocation. +/// Converts a local signed origin into an XCM location. /// Forms the basis for local origins sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; @@ -203,11 +218,6 @@ pub type XcmRouter = WithUniqueTopic<( XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // We want to disallow users sending (arbitrary) XCMs from this chain. @@ -234,8 +244,6 @@ impl pallet_xcm::Config for Runtime { type MaxLockers = ConstU32<8>; // FIXME: Replace with benchmarked weight info type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -248,7 +256,7 @@ impl cumulus_pallet_xcm::Config for Runtime { parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(RelayLocation::get()); + pub FeeAssetId: AssetId = AssetId(RelayLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -262,10 +270,19 @@ pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender: impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + #[cfg(feature = "runtime-benchmarks")] + type XcmpQueue = (); + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmpQueue = frame_support::traits::TransformOrigin< + crate::MessageQueue, + AggregateMessageOrigin, + cumulus_primitives_core::ParaId, + parachains_common::message_queue::ParaIdToSibling, + >; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EitherOfDiverse< EnsureRoot, EnsureXcm>, @@ -275,8 +292,6 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime { type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; +parameter_types! { + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } 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/assets/asset-hub-polkadot/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml similarity index 65% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml rename to cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index db400f2977f459068ad0ea4e014d8060a9cc647e..eb92afc431147ef4fa7b64164c0bc5309a465bb7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -1,156 +1,92 @@ [package] -name = "asset-hub-polkadot-runtime" -version = "0.9.420" +name = "coretime-rococo-runtime" +version = "0.1.0" authors.workspace = true edition.workspace = true -description = "Asset Hub Polkadot parachain runtime" +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", "max-encoded-len"] } -hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -smallvec = "1.11.0" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex-literal = "0.4.1" +log = { workspace = true } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # 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-asset-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false } -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -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-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false} -pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features = false} -pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false} -pallet-proxy = { path = "../../../../../substrate/frame/proxy", 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-uniques = { path = "../../../../../substrate/frame/uniques", 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} +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-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} -sp-weights = { path = "../../../../../substrate/primitives/weights", 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 = { 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} +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-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-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", 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 = "../common", default-features = false } - -[dev-dependencies] -hex-literal = "0.4.1" -asset-test-utils = { path = "../test-utils" } - -[build-dependencies] -substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } [features] -default = [ "std" ] -runtime-benchmarks = [ - "assets-common/runtime-benchmarks", - "cumulus-pallet-parachain-system/runtime-benchmarks", - "cumulus-pallet-session-benchmarking/runtime-benchmarks", - "cumulus-pallet-xcmp-queue/runtime-benchmarks", - "cumulus-primitives-utility/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "hex-literal", - "pallet-asset-tx-payment/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-collator-selection/runtime-benchmarks", - "pallet-multisig/runtime-benchmarks", - "pallet-nfts/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "pallet-uniques/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-asset-tx-payment/try-runtime", - "pallet-assets/try-runtime", - "pallet-aura/try-runtime", - "pallet-authorship/try-runtime", - "pallet-balances/try-runtime", - "pallet-collator-selection/try-runtime", - "pallet-multisig/try-runtime", - "pallet-nfts/try-runtime", - "pallet-proxy/try-runtime", - "pallet-session/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "pallet-uniques/try-runtime", - "pallet-utility/try-runtime", - "pallet-xcm/try-runtime", - "parachain-info/try-runtime", - "polkadot-runtime-common/try-runtime", - "sp-runtime/try-runtime", -] +default = ["std"] std = [ - "assets-common/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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -161,30 +97,28 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "log/std", - "pallet-asset-tx-payment/std", - "pallet-assets/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-nfts-runtime-api/std", - "pallet-nfts/std", - "pallet-proxy/std", "pallet-session/std", + "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", - "pallet-uniques/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", @@ -198,11 +132,66 @@ std = [ "sp-storage/std", "sp-transaction-pool/std", "sp-version/std", - "sp-weights/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] -experimental = [ "pallet-aura/experimental" ] +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", +] + +fast-runtime = [] diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/build.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs similarity index 79% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/build.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..28dacd20cf305ebdbc57eb2a30e3c98e4f8853d9 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/build.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs @@ -19,7 +19,15 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() - .build() + .build(); + + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .import_memory() + .export_heap_base() + .build(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..742dd50f6fa1f421d6ce4abf221e05f6902cc2ae --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -0,0 +1,236 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::*; +use codec::{Decode, Encode}; +use cumulus_pallet_parachain_system::RelaychainDataProvider; +use cumulus_primitives_core::relay_chain; +use frame_support::{ + parameter_types, + traits::{ + fungible::{Balanced, Credit}, + OnUnbalanced, + }, +}; +use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, RCBlockNumberOf}; +use parachains_common::{AccountId, Balance, BlockNumber}; +use xcm::latest::prelude::*; + +pub struct CreditToCollatorPot; +impl OnUnbalanced> for CreditToCollatorPot { + fn on_nonzero_unbalanced(credit: Credit) { + let staking_pot = CollatorSelection::account_id(); + let _ = >::resolve(&staking_pot, credit); + } +} + +/// A type containing the encoding of the coretime pallet in the Relay chain runtime. Used to +/// construct any remote calls. The codec index must correspond to the index of `Coretime` in the +/// `construct_runtime` of the Relay chain. +#[derive(Encode, Decode)] +enum RelayRuntimePallets { + #[codec(index = 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(relay_chain::BlockNumber), + #[codec(index = 3)] + CreditAccount(AccountId, Balance), + #[codec(index = 4)] + AssignCore( + CoreIndex, + relay_chain::BlockNumber, + Vec<(CoreAssignment, PartsOf57600)>, + Option, + ), +} + +parameter_types! { + pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); +} + +parameter_types! { + pub storage CoreCount: Option = None; + pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; +} + +/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate +/// from the parachain context. That is, the parachain provides a market (broker) for the sale of +/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores. +pub struct CoretimeAllocator; +impl CoretimeInterface for CoretimeAllocator { + type AccountId = AccountId; + type Balance = Balance; + type RelayChainBlockNumberProvider = RelaychainDataProvider; + + fn request_core_count(count: CoreIndex) { + use crate::coretime::CoretimeProviderCalls::RequestCoreCount; + let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + + 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, Location::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, Location::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, Location::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(1_000_000_000, 200000), + call: assign_core_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::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; + #[cfg(feature = "fast-runtime")] + type TimeslicePeriod = ConstU32<10>; + #[cfg(not(feature = "fast-runtime"))] + 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..26add2ee942497c8420ff85b4e3bc9da4e084393 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -0,0 +1,892 @@ +// 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")); + +/// Provides the `WASM_BINARY` build with `fast-runtime` feature enabled. +/// +/// This is for example useful for local test chains. +#[cfg(feature = "std")] +pub mod fast_runtime_binary { + include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs")); +} + +mod coretime; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +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}, + AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, +}; +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 testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; +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, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); + +/// 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_008_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)] +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<0>; + 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 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 = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +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 = AssetId(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; + type SlotDuration = ConstU64; +} + +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(SLOT_DURATION) + } + + fn authorities() -> Vec { + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_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 { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled + None + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + + parameter_types! { + pub ExistentialDepositAsset: 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, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + type AccountIdConverter = xcm_config::LocationToAccountId; + fn valid_destination() -> Result { + Ok(RocRelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + Asset { + id: AssetId(RocRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + RocRelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(RocRelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(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<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, 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<(Location, Location, Assets), BenchmarkError> { + let origin = RocRelayLocation::get(); + let assets: Assets = (AssetId(RocRelayLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(RocRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(Location, Location), 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/collectives/collectives-polkadot/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/block_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs index e7fdb2aae2a01ec06076de83d94817e540e205dd..b2092d875c8328210667da4cbb95de0642e60ae3 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); 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..139e37c544898e27e218619918c212742635d97e --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,77 @@ +// 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: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-j8vvqcjr-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 +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_parachain_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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: `48` + // Estimated: `3517` + // Minimum execution time: 2_067_000 picoseconds. + Weight::from_parts(2_151_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + // Standard Error: 32_757 + .saturating_add(Weight::from_parts(204_001_420, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } +} 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..efbe7980de281184ecac4c9baf33ac165d41e575 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,155 @@ +// 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: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-j8vvqcjr-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 +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_xcmp_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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: 3_935_000 picoseconds. + Weight::from_parts(4_188_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: 10_252_000 picoseconds. + Weight::from_parts(10_551_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: 2_294_000 picoseconds. + Weight::from_parts(2_477_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: 3_068_000 picoseconds. + Weight::from_parts(3_204_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: 68_610_000 picoseconds. + Weight::from_parts(68_800_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: 125_878_000 picoseconds. + Weight::from_parts(127_632_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: 54_918_000 picoseconds. + Weight::from_parts(56_246_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/bridge-hubs/bridge-hub-kusama/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/extrinsic_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs index 1a4adb968bb7195428ea00d59cd92dcd3b6eea5f..332c3b324bb9c1b386257bf7953d37aba8f5af13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs similarity index 58% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/frame_system.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs index 4aeb4660d8786b81100e26f1309dc2be8898e874..428976e3e036e5b85a9ac216ffd985b0a2f28692 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `frame_system` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=bridge-hub-polkadot-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=frame_system // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,22 +55,22 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_047_000 picoseconds. - Weight::from_parts(2_087_000, 0) + // Minimum execution time: 1_760_000 picoseconds. + Weight::from_parts(6_086_623, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(390, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(430, 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_335_000 picoseconds. - Weight::from_parts(7_507_000, 0) + // Minimum execution time: 5_315_000 picoseconds. + Weight::from_parts(20_446_491, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_751, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(1_725, 0).saturating_mul(b.into())) } /// Storage: `System::Digest` (r:1 w:1) /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -80,8 +80,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 3_673_000 picoseconds. - Weight::from_parts(3_953_000, 0) + // Minimum execution time: 3_046_000 picoseconds. + Weight::from_parts(3_249_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -100,11 +100,11 @@ impl frame_system::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `119` - // Estimated: `1604` - // Minimum execution time: 98_791_992_000 picoseconds. - Weight::from_parts(101_799_041_000, 0) - .saturating_add(Weight::from_parts(0, 1604)) + // Measured: `164` + // Estimated: `1649` + // Minimum execution time: 108_366_941_000 picoseconds. + Weight::from_parts(111_101_742_000, 0) + .saturating_add(Weight::from_parts(0, 1649)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -115,11 +115,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_144_000 picoseconds. - Weight::from_parts(2_206_000, 0) + // Minimum execution time: 1_877_000 picoseconds. + Weight::from_parts(1_947_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_254 - .saturating_add(Weight::from_parts(740_881, 0).saturating_mul(i.into())) + // Standard Error: 2_035 + .saturating_add(Weight::from_parts(763_800, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -129,11 +129,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_117_000 picoseconds. - Weight::from_parts(2_192_000, 0) + // Minimum execution time: 1_847_000 picoseconds. + Weight::from_parts(1_931_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_024 - .saturating_add(Weight::from_parts(558_397, 0).saturating_mul(i.into())) + // Standard Error: 932 + .saturating_add(Weight::from_parts(565_066, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -141,15 +141,50 @@ impl frame_system::WeightInfo for WeightInfo { /// 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: 3_907_000 picoseconds. - Weight::from_parts(4_050_000, 0) - .saturating_add(Weight::from_parts(0, 66)) - // Standard Error: 2_228 - .saturating_add(Weight::from_parts(1_212_760, 0).saturating_mul(p.into())) + // Measured: `71 + p * (69 ±0)` + // Estimated: `72 + p * (70 ±0)` + // Minimum execution time: 3_587_000 picoseconds. + Weight::from_parts(3_654_000, 0) + .saturating_add(Weight::from_parts(0, 72)) + // Standard Error: 1_468 + .saturating_add(Weight::from_parts(1_170_655, 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: 9_701_000 picoseconds. + Weight::from_parts(10_142_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: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn apply_authorized_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `186` + // Estimated: `1671` + // Minimum execution time: 113_812_980_000 picoseconds. + Weight::from_parts(115_758_263_000, 0) + .saturating_add(Weight::from_parts(0, 1671)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs similarity index 89% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/mod.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs index e226021e77ab58d51bda2eaa23ff732d53e7f4cb..f1050b3ae636261ff21674c3bb34c05bf6d232c5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,14 @@ //! 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; @@ -34,5 +37,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-kusama/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs similarity index 73% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_balances.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs index be1ac3011f79e72f852010f8fbdbaf19c4776b8b..aac7e10936615fddf39f9c306121dd9eda826ec6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! 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-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-8idpd4bs-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 +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --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)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 55_040_000 picoseconds. - Weight::from_parts(56_106_000, 0) + // Minimum execution time: 41_557_000 picoseconds. + Weight::from_parts(42_618_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_342_000 picoseconds. - Weight::from_parts(41_890_000, 0) + // Minimum execution time: 33_046_000 picoseconds. + Weight::from_parts(33_550_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_723_000 picoseconds. - Weight::from_parts(15_182_000, 0) + // Minimum execution time: 11_804_000 picoseconds. + Weight::from_parts(12_007_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 22_073_000 picoseconds. - Weight::from_parts(22_638_000, 0) + // Minimum execution time: 16_261_000 picoseconds. + Weight::from_parts(16_655_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_265_000 picoseconds. - Weight::from_parts(58_222_000, 0) + // Minimum execution time: 42_967_000 picoseconds. + Weight::from_parts(43_870_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 51_485_000 picoseconds. - Weight::from_parts(52_003_000, 0) + // Minimum execution time: 41_022_000 picoseconds. + Weight::from_parts(41_475_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 17_460_000 picoseconds. - Weight::from_parts(17_849_000, 0) + // Minimum execution time: 14_339_000 picoseconds. + Weight::from_parts(14_641_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +139,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_259_000 picoseconds. - Weight::from_parts(17_478_000, 0) + // Minimum execution time: 14_241_000 picoseconds. + Weight::from_parts(14_463_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 16_756 - .saturating_add(Weight::from_parts(15_291_954, 0).saturating_mul(u.into())) + // Standard Error: 12_290 + .saturating_add(Weight::from_parts(12_903_900, 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())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_116_000 picoseconds. + Weight::from_parts(5_345_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } 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..2d30ddc612cb9544291b90ea9456e392ab3451d4 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -0,0 +1,518 @@ +// 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: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-j8vvqcjr-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 +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_broker +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_462_000 picoseconds. + Weight::from_parts(2_552_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: 25_494_000 picoseconds. + Weight::from_parts(26_063_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: 22_299_000 picoseconds. + Weight::from_parts(22_911_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`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `466` + // Estimated: `1951` + // Minimum execution time: 11_590_000 picoseconds. + Weight::from_parts(12_007_000, 0) + .saturating_add(Weight::from_parts(0, 1951)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(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: 120_928_000 picoseconds. + Weight::from_parts(124_947_252, 0) + .saturating_add(Weight::from_parts(0, 14052)) + // Standard Error: 435 + .saturating_add(Weight::from_parts(1_246, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .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_826_000 picoseconds. + Weight::from_parts(33_889_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:2) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn renew() -> Weight { + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `4698` + // Minimum execution time: 57_362_000 picoseconds. + Weight::from_parts(58_994_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_982_000 picoseconds. + Weight::from_parts(14_447_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: 15_070_000 picoseconds. + Weight::from_parts(15_735_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Regions` (r:1 w:3) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn interlace() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 16_527_000 picoseconds. + Weight::from_parts(16_894_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn assign() -> Weight { + // Proof Size summary in bytes: + // Measured: `936` + // Estimated: `4681` + // Minimum execution time: 25_493_000 picoseconds. + Weight::from_parts(26_091_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_498_000 picoseconds. + Weight::from_parts(32_560_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: 57_183_000 picoseconds. + Weight::from_parts(58_024_898, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 35_831 + .saturating_add(Weight::from_parts(1_384_446, 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: 59_762_000 picoseconds. + Weight::from_parts(61_114_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: 41_473_000 picoseconds. + Weight::from_parts(44_155_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: 56_672_000 picoseconds. + Weight::from_parts(58_086_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: 64_460_000 picoseconds. + Weight::from_parts(65_894_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: 37_447_000 picoseconds. + Weight::from_parts(42_318_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: 21_219_000 picoseconds. + Weight::from_parts(22_084_648, 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:1 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn process_core_count(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `266` + // Estimated: `1487` + // Minimum execution time: 5_792_000 picoseconds. + Weight::from_parts(6_358_588, 0) + .saturating_add(Weight::from_parts(0, 1487)) + // Standard Error: 20 + .saturating_add(Weight::from_parts(26, 0).saturating_mul(n.into())) + .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_690_000 picoseconds. + Weight::from_parts(39_706_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: 93_531_000 picoseconds. + Weight::from_parts(95_836_318, 0) + .saturating_add(Weight::from_parts(0, 13506)) + // Standard Error: 113 + .saturating_add(Weight::from_parts(329, 0).saturating_mul(n.into())) + .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_506_000 picoseconds. + Weight::from_parts(6_783_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: 31_927_000 picoseconds. + Weight::from_parts(32_748_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: 15_682_000 picoseconds. + Weight::from_parts(16_012_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:0 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + fn notify_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_147_000 picoseconds. + Weight::from_parts(2_281_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::CoreCountInbox` (r:1 w:0) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn do_tick_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3863` + // Minimum execution time: 12_015_000 picoseconds. + Weight::from_parts(12_619_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/assets/asset-hub-kusama/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_collator_selection.rs similarity index 54% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_collator_selection.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_collator_selection.rs index 7fe56ac31f7aa11c8eb497dbacdabef522ccb294..b62a6c2fce5b83d45d8c60264c0a62421fd445dd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_collator_selection.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `pallet_collator_selection` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=asset-hub-kusama-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_collator_selection // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,13 +57,13 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `b` is `[1, 20]`. fn set_invulnerables(b: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `163 + b * (79 ±0)` - // Estimated: `1154 + b * (2555 ±0)` - // Minimum execution time: 15_408_000 picoseconds. - Weight::from_parts(13_068_592, 0) - .saturating_add(Weight::from_parts(0, 1154)) - // Standard Error: 7_395 - .saturating_add(Weight::from_parts(3_219_916, 0).saturating_mul(b.into())) + // Measured: `164 + b * (79 ±0)` + // Estimated: `1155 + b * (2555 ±0)` + // Minimum execution time: 11_551_000 picoseconds. + Weight::from_parts(8_982_740, 0) + .saturating_add(Weight::from_parts(0, 1155)) + // Standard Error: 6_117 + .saturating_add(Weight::from_parts(3_093_494, 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, 2555).saturating_mul(b.into())) @@ -72,42 +72,42 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// 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::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, 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: `756 + b * (32 ±0) + c * (53 ±0)` + // Measured: `720 + b * (32 ±0) + c * (53 ±0)` // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` - // Minimum execution time: 49_692_000 picoseconds. - Weight::from_parts(51_768_986, 0) + // Minimum execution time: 38_580_000 picoseconds. + Weight::from_parts(39_137_598, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 18_404 - .saturating_add(Weight::from_parts(55_676, 0).saturating_mul(b.into())) - // Standard Error: 3_488 - .saturating_add(Weight::from_parts(184_343, 0).saturating_mul(c.into())) + // Standard Error: 6_413 + .saturating_add(Weight::from_parts(119_463, 0).saturating_mul(b.into())) + // Standard Error: 1_215 + .saturating_add(Weight::from_parts(120_116, 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::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// 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: `119 + b * (32 ±0)` + // Measured: `82 + b * (32 ±0)` // Estimated: `6287` - // Minimum execution time: 16_486_000 picoseconds. - Weight::from_parts(16_646_017, 0) + // Minimum execution time: 11_347_000 picoseconds. + Weight::from_parts(11_332_550, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 3_230 - .saturating_add(Weight::from_parts(148_941, 0).saturating_mul(b.into())) + // Standard Error: 2_287 + .saturating_add(Weight::from_parts(134_624, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,26 +117,58 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_806_000 picoseconds. - Weight::from_parts(8_002_000, 0) + // Minimum execution time: 4_883_000 picoseconds. + Weight::from_parts(5_141_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CollatorSelection::CandidacyBond` (r:0 w:1) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:1) /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - fn set_candidacy_bond() -> Weight { + /// 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` - // Estimated: `0` - // Minimum execution time: 7_937_000 picoseconds. - Weight::from_parts(8_161_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `0 + c * (180 ±0) + k * (112 ±0)` + // Estimated: `6287 + c * (901 ±29) + k * (901 ±29)` + // Minimum execution time: 8_661_000 picoseconds. + Weight::from_parts(8_852_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 159_154 + .saturating_add(Weight::from_parts(5_352_946, 0).saturating_mul(c.into())) + // Standard Error: 159_154 + .saturating_add(Weight::from_parts(5_075_906, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(k.into())) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, 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::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: `250 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 23_840_000 picoseconds. + Weight::from_parts(26_343_302, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 1_743 + .saturating_add(Weight::from_parts(118_295, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) /// Storage: `Session::NextKeys` (r:1 w:0) @@ -148,19 +180,45 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[1, 99]`. fn register_as_candidate(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `736 + c * (52 ±0)` + // Measured: `687 + c * (52 ±0)` // Estimated: `6287 + c * (54 ±0)` - // Minimum execution time: 42_805_000 picoseconds. - Weight::from_parts(45_979_502, 0) + // Minimum execution time: 31_637_000 picoseconds. + Weight::from_parts(35_792_418, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_336 - .saturating_add(Weight::from_parts(221_049, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) + // Standard Error: 2_274 + .saturating_add(Weight::from_parts(146_163, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 54).saturating_mul(c.into())) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`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::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: `855 + c * (52 ±0)` + // Estimated: `6287 + c * (55 ±0)` + // Minimum execution time: 47_931_000 picoseconds. + Weight::from_parts(52_506_905, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_696 + .saturating_add(Weight::from_parts(149_395, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 55).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) @@ -168,13 +226,13 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[4, 100]`. fn leave_intent(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `277 + c * (48 ±0)` // Estimated: `6287` - // Minimum execution time: 34_814_000 picoseconds. - Weight::from_parts(36_371_520, 0) + // Minimum execution time: 27_658_000 picoseconds. + Weight::from_parts(30_896_953, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_391 - .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + // Standard Error: 2_038 + .saturating_add(Weight::from_parts(120_980, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -188,18 +246,20 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 46_989_000 picoseconds. - Weight::from_parts(48_151_000, 0) + // Minimum execution time: 37_700_000 picoseconds. + Weight::from_parts(38_497_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::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::LastAuthoredBlock` (r:100 w:0) /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::DesiredCandidates` (r:1 w:0) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:97 w:97) @@ -208,14 +268,14 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[1, 100]`. fn new_session(r: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2243 + c * (97 ±0) + r * (112 ±0)` + // Measured: `2143 + c * (97 ±0) + r * (112 ±0)` // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` - // Minimum execution time: 17_547_000 picoseconds. - Weight::from_parts(17_854_000, 0) + // Minimum execution time: 16_077_000 picoseconds. + Weight::from_parts(16_274_000, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 370_637 - .saturating_add(Weight::from_parts(15_798_857, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Standard Error: 283_859 + .saturating_add(Weight::from_parts(12_293_155, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(c.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) 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..934ab627bc8835f40c53c47c6ec5b3c2ec72320e --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_message_queue.rs @@ -0,0 +1,186 @@ +// 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: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-j8vvqcjr-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 +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_message_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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: 11_120_000 picoseconds. + Weight::from_parts(11_605_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: 9_795_000 picoseconds. + Weight::from_parts(10_300_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: 3_277_000 picoseconds. + Weight::from_parts(3_426_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_016_000 picoseconds. + Weight::from_parts(5_237_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: 5_118_000 picoseconds. + Weight::from_parts(5_347_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MessageQueue::BookStateFor` (r:0 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn service_page_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 175_756_000 picoseconds. + Weight::from_parts(177_423_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:0) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn bump_service_head() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `3517` + // Minimum execution time: 6_515_000 picoseconds. + Weight::from_parts(6_953_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: 57_649_000 picoseconds. + Weight::from_parts(59_093_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: 73_366_000 picoseconds. + Weight::from_parts(74_402_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: 116_063_000 picoseconds. + Weight::from_parts(117_532_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/assets/asset-hub-polkadot/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs similarity index 72% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_multisig.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs index 705aca9e1a4e78222f231a11ca44dd27ac20c70e..8e010d768f643ceb55fd233b3a60e3b8e3c2c945 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=asset-hub-polkadot-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_multisig // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_710_000 picoseconds. - Weight::from_parts(14_702_959, 0) + // Minimum execution time: 12_905_000 picoseconds. + Weight::from_parts(13_544_225, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7 - .saturating_add(Weight::from_parts(568, 0).saturating_mul(z.into())) + // Standard Error: 2 + .saturating_add(Weight::from_parts(596, 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`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 45_518_000 picoseconds. - Weight::from_parts(35_243_068, 0) + // Minimum execution time: 38_729_000 picoseconds. + Weight::from_parts(27_942_442, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_634 - .saturating_add(Weight::from_parts(116_658, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_444, 0).saturating_mul(z.into())) + // Standard Error: 648 + .saturating_add(Weight::from_parts(120_340, 0).saturating_mul(s.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(1_578, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 29_590_000 picoseconds. - Weight::from_parts(21_574_604, 0) + // Minimum execution time: 25_936_000 picoseconds. + Weight::from_parts(16_537_903, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_622 - .saturating_add(Weight::from_parts(95_669, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 412 + .saturating_add(Weight::from_parts(105_835, 0).saturating_mul(s.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,13 +107,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 51_056_000 picoseconds. - Weight::from_parts(35_799_301, 0) + // Minimum execution time: 45_291_000 picoseconds. + Weight::from_parts(31_294_385, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_629 - .saturating_add(Weight::from_parts(183_343, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_686, 0).saturating_mul(z.into())) + // Standard Error: 816 + .saturating_add(Weight::from_parts(152_838, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_638, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -124,11 +124,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_910_000 picoseconds. - Weight::from_parts(32_413_023, 0) + // Minimum execution time: 26_585_000 picoseconds. + Weight::from_parts(27_424_168, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_346 - .saturating_add(Weight::from_parts(128_779, 0).saturating_mul(s.into())) + // Standard Error: 732 + .saturating_add(Weight::from_parts(123_460, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +139,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 17_926_000 picoseconds. - Weight::from_parts(18_477_305, 0) + // Minimum execution time: 15_228_000 picoseconds. + Weight::from_parts(15_568_631, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_367 - .saturating_add(Weight::from_parts(113_018, 0).saturating_mul(s.into())) + // Standard Error: 441 + .saturating_add(Weight::from_parts(107_463, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +154,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_232_000 picoseconds. - Weight::from_parts(33_724_753, 0) + // Minimum execution time: 28_033_000 picoseconds. + Weight::from_parts(29_228_827, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_192 - .saturating_add(Weight::from_parts(121_574, 0).saturating_mul(s.into())) + // Standard Error: 748 + .saturating_add(Weight::from_parts(117_495, 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/assets/asset-hub-kusama/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs similarity index 76% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_session.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs index 56982f565ac478cc59ae9db6864e78e27a795191..409d92be4fcb2eca5d7d9740ef990f2cbf03e79f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `pallet_session` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=asset-hub-kusama-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_session // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,11 +56,11 @@ impl pallet_session::WeightInfo for WeightInfo { /// 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: 16_932_000 picoseconds. - Weight::from_parts(17_357_000, 0) - .saturating_add(Weight::from_parts(0, 3735)) + // Measured: `271` + // Estimated: `3736` + // Minimum execution time: 15_924_000 picoseconds. + Weight::from_parts(16_586_000, 0) + .saturating_add(Weight::from_parts(0, 3736)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -70,11 +70,11 @@ impl pallet_session::WeightInfo for WeightInfo { /// 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: 12_157_000 picoseconds. - Weight::from_parts(12_770_000, 0) - .saturating_add(Weight::from_parts(0, 3707)) + // Measured: `243` + // Estimated: `3708` + // Minimum execution time: 11_218_000 picoseconds. + Weight::from_parts(11_587_000, 0) + .saturating_add(Weight::from_parts(0, 3708)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs similarity index 80% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_timestamp.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs index 94914eefba04db2a068767adeb7657e328613460..c171353404e0d8f213a17a8bad2100ec9d85280b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `pallet_timestamp` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=asset-hub-kusama-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_timestamp // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,8 +58,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `86` // Estimated: `1493` - // Minimum execution time: 9_313_000 picoseconds. - Weight::from_parts(9_775_000, 0) + // Minimum execution time: 5_979_000 picoseconds. + Weight::from_parts(6_115_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +68,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `57` // Estimated: `0` - // Minimum execution time: 3_322_000 picoseconds. - Weight::from_parts(3_577_000, 0) + // Minimum execution time: 2_830_000 picoseconds. + Weight::from_parts(2_988_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs similarity index 70% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_utility.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs index 680e65a2dcf90271faf4f25e1f02656ded9d6881..84eb97838680cfcb2c3aaf24bd90694f60da835d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs @@ -17,16 +17,16 @@ //! Autogenerated weights for `pallet_utility` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --chain=asset-hub-kusama-dev +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_utility // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,18 +55,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_103_000 picoseconds. - Weight::from_parts(7_226_000, 0) + // Minimum execution time: 4_434_000 picoseconds. + Weight::from_parts(2_232_360, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_732 - .saturating_add(Weight::from_parts(6_560_347, 0).saturating_mul(c.into())) + // Standard Error: 3_409 + .saturating_add(Weight::from_parts(3_308_287, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_208_000 picoseconds. - Weight::from_parts(5_480_000, 0) + // Minimum execution time: 4_455_000 picoseconds. + Weight::from_parts(4_561_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -74,18 +74,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_070_000 picoseconds. - Weight::from_parts(1_321_270, 0) + // Minimum execution time: 4_304_000 picoseconds. + Weight::from_parts(4_146_029, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_454 - .saturating_add(Weight::from_parts(6_864_640, 0).saturating_mul(c.into())) + // Standard Error: 3_128 + .saturating_add(Weight::from_parts(3_581_489, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_255_000 picoseconds. - Weight::from_parts(9_683_000, 0) + // Minimum execution time: 6_531_000 picoseconds. + Weight::from_parts(6_805_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -93,10 +93,10 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_852_000 picoseconds. - Weight::from_parts(7_007_000, 0) + // Minimum execution time: 4_412_000 picoseconds. + Weight::from_parts(4_498_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_745 - .saturating_add(Weight::from_parts(6_562_902, 0).saturating_mul(c.into())) + // Standard Error: 1_621 + .saturating_add(Weight::from_parts(3_312_302, 0).saturating_mul(c.into())) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs similarity index 63% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_xcm.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs index 738742b6108b79bb11520fdca1d191735cee1a0c..c5d315467c1ed8b2aabf7ac18abe10931a02951b 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-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 +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-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/collectives/collectives-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --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)] @@ -62,24 +60,35 @@ 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: `111` - // Estimated: `3576` - // Minimum execution time: 27_795_000 picoseconds. - Weight::from_parts(28_215_000, 0) - .saturating_add(Weight::from_parts(0, 3576)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 35_051_000 picoseconds. + Weight::from_parts(35_200_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: `32` - // Estimated: `1489` - // Minimum execution time: 23_847_000 picoseconds. - Weight::from_parts(24_332_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 56_235_000 picoseconds. + Weight::from_parts(58_178_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`) @@ -93,6 +102,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// 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` @@ -107,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_885_000 picoseconds. - Weight::from_parts(9_128_000, 0) + // Minimum execution time: 6_226_000 picoseconds. + Weight::from_parts(6_403_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_670_000 picoseconds. - Weight::from_parts(2_815_000, 0) + // Minimum execution time: 2_020_000 picoseconds. + Weight::from_parts(2_100_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -141,11 +160,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: `111` - // Estimated: `3576` - // Minimum execution time: 32_214_000 picoseconds. - Weight::from_parts(32_989_000, 0) - .saturating_add(Weight::from_parts(0, 3576)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 24_387_000 picoseconds. + Weight::from_parts(24_814_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -165,11 +184,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: `294` - // Estimated: `3759` - // Minimum execution time: 33_638_000 picoseconds. - Weight::from_parts(34_206_000, 0) - .saturating_add(Weight::from_parts(0, 3759)) + // Measured: `292` + // Estimated: `3757` + // Minimum execution time: 27_039_000 picoseconds. + Weight::from_parts(27_693_000, 0) + .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -179,45 +198,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_602_000 picoseconds. - Weight::from_parts(2_730_000, 0) + // Minimum execution time: 1_920_000 picoseconds. + Weight::from_parts(2_082_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `129` - // Estimated: `11019` - // Minimum execution time: 16_199_000 picoseconds. - Weight::from_parts(16_833_000, 0) - .saturating_add(Weight::from_parts(0, 11019)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 17_141_000 picoseconds. + Weight::from_parts(17_500_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `133` - // Estimated: `11023` - // Minimum execution time: 16_561_000 picoseconds. - Weight::from_parts(16_872_000, 0) - .saturating_add(Weight::from_parts(0, 11023)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 17_074_000 picoseconds. + Weight::from_parts(17_431_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `13505` - // Minimum execution time: 17_812_000 picoseconds. - Weight::from_parts(20_036_000, 0) - .saturating_add(Weight::from_parts(0, 13505)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `106` + // Estimated: `15946` + // Minimum execution time: 19_139_000 picoseconds. + Weight::from_parts(19_474_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -233,38 +252,38 @@ 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: `178` - // Estimated: `6118` - // Minimum execution time: 30_153_000 picoseconds. - Weight::from_parts(31_366_000, 0) - .saturating_add(Weight::from_parts(0, 6118)) + // Measured: `142` + // Estimated: `6082` + // Minimum execution time: 24_346_000 picoseconds. + Weight::from_parts(25_318_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) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_465_000 picoseconds. - Weight::from_parts(9_743_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `136` + // Estimated: `11026` + // Minimum execution time: 11_777_000 picoseconds. + Weight::from_parts(12_051_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `11030` - // Minimum execution time: 16_954_000 picoseconds. - Weight::from_parts(19_772_000, 0) - .saturating_add(Weight::from_parts(0, 11030)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 17_538_000 picoseconds. + Weight::from_parts(17_832_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -278,12 +297,50 @@ 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: `182` - // Estimated: `11072` - // Minimum execution time: 37_302_000 picoseconds. - Weight::from_parts(38_124_000, 0) - .saturating_add(Weight::from_parts(0, 11072)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `142` + // Estimated: `13507` + // Minimum execution time: 33_623_000 picoseconds. + Weight::from_parts(34_186_000, 0) + .saturating_add(Weight::from_parts(0, 13507)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// 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: 3_363_000 picoseconds. + Weight::from_parts(3_511_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: 23_969_000 picoseconds. + Weight::from_parts(24_347_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 34_071_000 picoseconds. + Weight::from_parts(35_031_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/paritydb_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs index 25679703831a13b8d1bb7fb7dd4d92fa84b1f255..4338d928d807a41cc60ec91d86e91c81bb253631 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/rocksdb_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs index 3dd817aa6f137085b0e5fdf2b11b7f50e5c8b002..1d115d963facb39fe29d6258918fda3bc8d94900 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs similarity index 64% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index ce6e92065156ea474afcaf004798b527c1c1f7e0..9f79cea831aed66a0d073109233731751cdf99ed 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -24,14 +24,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,40 +50,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } -pub struct AssetHubKusamaXcmWeight(core::marker::PhantomData); -impl XcmWeightInfo for AssetHubKusamaXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) +pub struct CoretimeRococoXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for CoretimeRococoXcmWeight { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -111,44 +107,35 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -163,7 +150,7 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -175,13 +162,13 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -214,16 +201,16 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -235,11 +222,11 @@ impl XcmWeightInfo for AssetHubKusamaXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs similarity index 85% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 96d86ec423f20e1923cf0842830a6e12ad81d436..ec71a87b5a753a879a8157f094693140316ca792 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-polkadot-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-rococo-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::fungible -// --chain=asset-hub-polkadot-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 25_903_000 picoseconds. - Weight::from_parts(26_768_000, 3593) + // Minimum execution time: 19_199_000 picoseconds. + Weight::from_parts(19_784_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +67,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 51_042_000 picoseconds. - Weight::from_parts(51_939_000, 6196) + // Minimum execution time: 42_601_000 picoseconds. + Weight::from_parts(43_296_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -86,10 +88,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `176` + // Measured: `207` // Estimated: `6196` - // Minimum execution time: 74_626_000 picoseconds. - Weight::from_parts(75_963_000, 6196) + // Minimum execution time: 62_463_000 picoseconds. + Weight::from_parts(64_142_000, 6196) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -116,10 +118,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 480_030_000 picoseconds. - Weight::from_parts(486_039_000, 3540) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 31_417_000 picoseconds. + Weight::from_parts(32_153_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -127,8 +129,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_936_000 picoseconds. - Weight::from_parts(4_033_000, 0) + // Minimum execution time: 3_235_000 picoseconds. + Weight::from_parts(3_331_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -136,8 +138,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 26_274_000 picoseconds. - Weight::from_parts(26_609_000, 3593) + // Minimum execution time: 17_701_000 picoseconds. + Weight::from_parts(18_219_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -157,10 +159,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `75` + // Measured: `106` // Estimated: `3593` - // Minimum execution time: 52_888_000 picoseconds. - Weight::from_parts(53_835_000, 3593) + // Minimum execution time: 41_748_000 picoseconds. + Weight::from_parts(42_401_000, 3593) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -178,10 +180,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 33_395_000 picoseconds. - Weight::from_parts(33_827_000, 3540) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 27_455_000 picoseconds. + Weight::from_parts(27_976_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs similarity index 77% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index b1e8107b30bbddb9e30f0bf3c648627993fa4448..719e7543e8886a803f773126eafbc77f34749ddb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,17 +17,17 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-12, 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-kusama-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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 -// --template=./templates/xcm-bench-template.hbs -// --chain=bridge-hub-kusama-dev +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-rococo-dev // --wasm-execution=compiled // --pallet=pallet_xcm_benchmarks::generic // --no-storage-info @@ -37,8 +37,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,10 +64,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 33_141_000 picoseconds. - Weight::from_parts(34_380_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 35_477_000 picoseconds. + Weight::from_parts(36_129_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -75,8 +75,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_803_000 picoseconds. - Weight::from_parts(2_904_000, 0) + // Minimum execution time: 2_243_000 picoseconds. + Weight::from_parts(2_329_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -84,58 +84,58 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 10_308_000 picoseconds. - Weight::from_parts(10_753_000, 3497) + // Minimum execution time: 8_112_000 picoseconds. + Weight::from_parts(8_275_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_499_000 picoseconds. - Weight::from_parts(11_786_000, 0) + // Minimum execution time: 8_960_000 picoseconds. + Weight::from_parts(9_253_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_102_000 picoseconds. - Weight::from_parts(3_161_000, 0) + // Minimum execution time: 2_332_000 picoseconds. + Weight::from_parts(2_438_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_678_000 picoseconds. - Weight::from_parts(2_795_000, 0) + // Minimum execution time: 2_054_000 picoseconds. + Weight::from_parts(2_119_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_685_000 picoseconds. - Weight::from_parts(2_758_000, 0) + // Minimum execution time: 2_061_000 picoseconds. + Weight::from_parts(2_133_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_590_000 picoseconds. - Weight::from_parts(2_754_000, 0) + // Minimum execution time: 2_054_000 picoseconds. + Weight::from_parts(2_128_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_297_000 picoseconds. - Weight::from_parts(3_419_000, 0) + // Minimum execution time: 2_791_000 picoseconds. + Weight::from_parts(2_903_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_606_000 picoseconds. - Weight::from_parts(2_717_000, 0) + // Minimum execution time: 2_088_000 picoseconds. + Weight::from_parts(2_153_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -151,10 +151,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 26_242_000 picoseconds. - Weight::from_parts(29_220_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 27_721_000 picoseconds. + Weight::from_parts(28_602_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -164,8 +164,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 14_106_000 picoseconds. - Weight::from_parts(14_535_000, 3555) + // Minimum execution time: 11_468_000 picoseconds. + Weight::from_parts(11_866_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -173,8 +173,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_634_000 picoseconds. - Weight::from_parts(2_763_000, 0) + // Minimum execution time: 2_125_000 picoseconds. + Weight::from_parts(2_167_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -190,10 +190,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: `38` - // Estimated: `3503` - // Minimum execution time: 27_802_000 picoseconds. - Weight::from_parts(28_495_000, 3503) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 22_422_000 picoseconds. + Weight::from_parts(22_924_000, 3539) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -203,44 +203,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_683_000 picoseconds. - Weight::from_parts(4_907_000, 0) + // Minimum execution time: 3_880_000 picoseconds. + Weight::from_parts(4_050_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_941_000 picoseconds. - Weight::from_parts(4_080_000, 0) + // Minimum execution time: 3_432_000 picoseconds. + Weight::from_parts(3_536_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_775_000 picoseconds. - Weight::from_parts(2_908_000, 0) + // Minimum execution time: 2_213_000 picoseconds. + Weight::from_parts(2_286_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_743_000 picoseconds. - Weight::from_parts(2_863_000, 0) + // Minimum execution time: 2_155_000 picoseconds. + Weight::from_parts(2_239_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_641_000 picoseconds. - Weight::from_parts(2_771_000, 0) + // Minimum execution time: 2_093_000 picoseconds. + Weight::from_parts(2_139_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_838_000 picoseconds. - Weight::from_parts(2_950_000, 0) + // Minimum execution time: 2_345_000 picoseconds. + Weight::from_parts(2_378_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -256,10 +256,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 29_284_000 picoseconds. - Weight::from_parts(29_867_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 31_543_000 picoseconds. + Weight::from_parts(32_075_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -267,8 +267,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_734_000 picoseconds. - Weight::from_parts(4_876_000, 0) + // Minimum execution time: 4_416_000 picoseconds. + Weight::from_parts(4_613_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -284,10 +284,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 26_154_000 picoseconds. - Weight::from_parts(26_851_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 28_050_000 picoseconds. + Weight::from_parts(28_755_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -295,35 +295,35 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_678_000 picoseconds. - Weight::from_parts(2_748_000, 0) + // Minimum execution time: 2_073_000 picoseconds. + Weight::from_parts(2_181_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_585_000 picoseconds. - Weight::from_parts(2_697_000, 0) + // Minimum execution time: 2_049_000 picoseconds. + Weight::from_parts(2_137_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_576_000 picoseconds. - Weight::from_parts(2_701_000, 0) + // Minimum execution time: 2_082_000 picoseconds. + Weight::from_parts(2_144_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_597_000 picoseconds. - Weight::from_parts(2_735_000, 0) + // Minimum execution time: 2_043_000 picoseconds. + Weight::from_parts(2_151_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_744_000 picoseconds. - Weight::from_parts(2_809_000, 0) + // Minimum execution time: 2_197_000 picoseconds. + Weight::from_parts(2_293_000, 0) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs similarity index 59% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/xcm_config.rs rename to cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs index 85b983a6ab98890d2418bbbe778c02ebbe0747cd..955f2eeba339c0e99a8364377dfc27ccc7557e80 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -1,4 +1,4 @@ -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright 2023 Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -15,43 +15,56 @@ // along with Cumulus. If not, see . use super::{ - AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, - CENTS, + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, Broker, FeeAssetId, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, WeightToFee, XcmpQueue, }; use frame_support::{ - match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing}, + pallet_prelude::PalletInfoAccess, + parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; -use parachains_common::{impls::ToStakingPot, xcm_config::ConcreteAssetFromSystem}; +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::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - pub const KsmRelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: Option = Some(NetworkId::Kusama); + pub const RocRelayLocation: Location = Location::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 UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub BrokerPalletLocation: Location = + PalletInstance(::index() as u8).into(); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; - pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); - pub const FellowshipLocation: MultiLocation = MultiLocation::parent(); + pub const GovernanceLocation: Location = Location::parent(); + pub const FellowshipLocation: Location = Location::parent(); } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -64,28 +77,46 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + IsConcrete, + // Do a simple punn to convert an `AccountId32` `Location` into a native chain + // `AccountId`: LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): + // Our chain's `AccountId` type (we can't get away without mentioning it explicitly): AccountId, // We don't track any teleports of `Balances`. (), >; +/// Means for transacting coretime regions on this chain. +pub type RegionTransactor = NonFungibleAdapter< + // Use this non-fungible implementation: + Broker, + // This adapter will handle coretime regions from the broker pallet. + IsConcrete, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = (CurrencyTransactor, RegionTransactor); + /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. +/// 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 which they control. + // 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 converts to a `Relay` origin when + // 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 @@ -97,20 +128,17 @@ pub type XcmOriginToTransactDispatchOrigin = ( // 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. + // 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 ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } } + /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -130,26 +158,26 @@ impl Contains for SafeCallFilter { matches!( call, - RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_xcm_version { .. }) | - RuntimeCall::System( - frame_system::Call::set_heap_pages { .. } | + 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::kill_prefix { .. }, - ) | RuntimeCall::ParachainSystem(..) | + frame_system::Call::authorize_upgrade { .. } | + frame_system::Call::authorize_upgrade_without_checks { .. } | + frame_system::Call::kill_prefix { .. } | + // Should not be in Polkadot/Kusama. Here in order to speed up testing. + frame_system::Call::set_storage { .. }, + ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - 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::Sudo(..) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::XcmpQueue(..) | - RuntimeCall::DmpQueue(..) + RuntimeCall::Broker(..) ) } } @@ -170,7 +198,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>, @@ -179,29 +207,38 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -/// Cases where a remote origin is accepted as trusted Teleporter for a given asset: -/// - KSM with the parent Relay Chain and sibling parachains. -pub type TrustedTeleporters = ConcreteAssetFromSystem; +parameter_types! { + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: Location = (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 AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // BridgeHub does not recognize a reserve location for any asset. Users must teleport KSM + // 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 = (); - type IsTeleporter = TrustedTeleporters; + /// Only allow teleportation of ROC. + type IsTeleporter = ConcreteAssetFromSystem; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< - crate::weights::xcm::BridgeHubKusamaXcmWeight, + crate::weights::xcm::CoretimeRococoXcmWeight, RuntimeCall, MaxInstructions, >; type Trader = - UsingComponents>; + UsingComponents>; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; @@ -210,25 +247,25 @@ impl xcm_executor::Config for XcmConfig { type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type AssetLocker = (); type AssetExchanger = (); - type FeeManager = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } -/// Converts a local signed origin into an XCM multilocation. -/// Forms the basis for local origins sending/executing XCMs. +/// Converts a local signed origin into an XCM location. Forms the basis for local origins +/// sending/executing XCMs. pub type LocalOriginToLocation = SignedToAccountId32; -parameter_types! { - /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(KsmRelayLocation::get()); - /// The base fee for the message delivery fees. - pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); -} - pub type PriceForParentDelivery = ExponentialPrice; @@ -236,19 +273,14 @@ pub type PriceForParentDelivery = /// queues. pub type XcmRouter = WithUniqueTopic<( // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, + cumulus_primitives_utility::ParentAsUmp, // ..and XCMP to communicate with the sibling chains. XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; - // We want to disallow users sending (arbitrary) XCMs from this chain. + // 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... @@ -259,7 +291,7 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. type Weigher = WeightInfoBounds< - crate::weights::xcm::BridgeHubKusamaXcmWeight, + crate::weights::xcm::CoretimeRococoXcmWeight, RuntimeCall, MaxInstructions, >; @@ -274,8 +306,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); 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..b8efecffc5075be04fb3a52410ae86410a01e48d --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -0,0 +1,193 @@ +[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 = { workspace = true } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } + +# 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-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-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-aura = { path = "../../../../primitives/aura", default-features = false } +cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } +cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } +pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } + +[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-aura/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-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-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", + "testnet-parachains-constants/std", + "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-broker/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", + "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-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", +] + +fast-runtime = [] 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..28dacd20cf305ebdbc57eb2a30e3c98e4f8853d9 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/build.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. + +#[cfg(feature = "std")] +fn main() { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .import_memory() + .export_heap_base() + .build(); +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..41cbc62fa2115ff3828e6910b750622a91ff0251 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs @@ -0,0 +1,249 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::*; +use codec::{Decode, Encode}; +use cumulus_pallet_parachain_system::RelaychainDataProvider; +use cumulus_primitives_core::relay_chain; +use frame_support::{ + parameter_types, + traits::{ + fungible::{Balanced, Credit}, + OnUnbalanced, + }, +}; +use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, RCBlockNumberOf}; +use parachains_common::{AccountId, Balance, BlockNumber}; +use xcm::latest::prelude::*; + +pub struct CreditToCollatorPot; +impl OnUnbalanced> for CreditToCollatorPot { + fn on_nonzero_unbalanced(credit: Credit) { + let staking_pot = CollatorSelection::account_id(); + let _ = >::resolve(&staking_pot, credit); + } +} + +/// A type containing the encoding of the coretime pallet in the Relay chain runtime. Used to +/// construct any remote calls. The codec index must correspond to the index of `Coretime` in the +/// `construct_runtime` of the Relay chain. +#[derive(Encode, Decode)] +enum RelayRuntimePallets { + #[codec(index = 66)] + Coretime(CoretimeProviderCalls), +} + +/// Call encoding for the calls needed from the relay coretime pallet. +#[derive(Encode, Decode)] +enum CoretimeProviderCalls { + #[codec(index = 1)] + RequestCoreCount(CoreIndex), + #[codec(index = 2)] + RequestRevenueInfoAt(relay_chain::BlockNumber), + #[codec(index = 3)] + CreditAccount(AccountId, Balance), + #[codec(index = 4)] + AssignCore( + CoreIndex, + relay_chain::BlockNumber, + Vec<(CoreAssignment, PartsOf57600)>, + Option, + ), +} + +parameter_types! { + pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); +} + +parameter_types! { + pub storage CoreCount: Option = None; + pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; +} + +/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate +/// from the parachain context. That is, the parachain provides a market (broker) for the sale of +/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores. +pub struct CoretimeAllocator; +impl CoretimeInterface for CoretimeAllocator { + type AccountId = AccountId; + type Balance = Balance; + type RelayChainBlockNumberProvider = RelaychainDataProvider; + + fn request_core_count(count: CoreIndex) { + use crate::coretime::CoretimeProviderCalls::RequestCoreCount; + let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + + // Weight for `request_core_count` from westend benchmarks: + // `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000 + // `proof_size` = 1636 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(190_000_000, 1700); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: request_core_count_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request to update schedulable cores sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Failed to send request to update schedulable cores: {:?}", + e + ), + } + } + + fn request_revenue_info_at(when: RCBlockNumberOf) { + use crate::coretime::CoretimeProviderCalls::RequestRevenueInfoAt; + let request_revenue_info_at_call = + RelayRuntimePallets::Coretime(RequestRevenueInfoAt(when)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: request_revenue_info_at_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request for revenue information sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Request for revenue information failed to send: {:?}", + e + ), + } + } + + fn credit_account(who: Self::AccountId, amount: Self::Balance) { + use crate::coretime::CoretimeProviderCalls::CreditAccount; + let credit_account_call = RelayRuntimePallets::Coretime(CreditAccount(who, amount)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: credit_account_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Instruction to credit account sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Instruction to credit account failed to send: {:?}", + e + ), + } + } + + fn assign_core( + core: CoreIndex, + begin: RCBlockNumberOf, + assignment: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) { + use crate::coretime::CoretimeProviderCalls::AssignCore; + let assign_core_call = + RelayRuntimePallets::Coretime(AssignCore(core, begin, assignment, end_hint)); + + // Weight for `assign_core` from westend benchmarks: + // `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315 + // `proof_size` = 3612 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(980_000_000, 3800); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: assign_core_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Core assignment sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Core assignment failed to send: {:?}", + e + ), + } + } + + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)> { + let revenue = CoretimeRevenue::get(); + CoretimeRevenue::set(&None); + revenue + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_notify_revenue_info(when: RCBlockNumberOf, revenue: Self::Balance) { + CoretimeRevenue::set(&Some((when, revenue))); + } +} + +impl pallet_broker::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type OnRevenue = CreditToCollatorPot; + #[cfg(feature = "fast-runtime")] + type TimeslicePeriod = ConstU32<10>; + #[cfg(not(feature = "fast-runtime"))] + type TimeslicePeriod = ConstU32<80>; + // We don't actually need any leases at launch but set to 10 in case we want to sudo some in. + type MaxLeasedCores = ConstU32<10>; + type MaxReservedCores = ConstU32<10>; + type Coretime = CoretimeAllocator; + type ConvertBalance = sp_runtime::traits::Identity; + type WeightInfo = weights::pallet_broker::WeightInfo; + type PalletId = BrokerPalletId; + type AdminOrigin = EnsureRoot; + type PriceAdapter = pallet_broker::Linear; +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..76732c3fccc7434ecd667fb4f4dc52c5e41fb227 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -0,0 +1,883 @@ +// 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")); + +/// Provides the `WASM_BINARY` build with `fast-runtime` feature enabled. +/// +/// This is for example useful for local test chains. +#[cfg(feature = "std")] +pub mod fast_runtime_binary { + include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs")); +} + +mod coretime; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +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}, + AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, +}; +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 testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::*; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, TokenRelayLocation, 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, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); + +/// 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_008_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)] +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<0>; + 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 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 = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +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 = AssetId(TokenRelayLocation::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; + type SlotDuration = ConstU64; +} + +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; +} + +// 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, + } +); + +#[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(SLOT_DURATION) + } + + fn authorities() -> Vec { + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_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::TokenRelayLocation; + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Reserve transfers are disabled + None + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenRelayLocation::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, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + type AccountIdConverter = xcm_config::LocationToAccountId; + fn valid_destination() -> Result { + Ok(TokenRelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + Asset { + id: AssetId(TokenRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + TokenRelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(TokenRelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(TokenRelayLocation::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<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((TokenRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(TokenRelayLocation::get()) + } + + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = TokenRelayLocation::get(); + let assets: Assets = (AssetId(TokenRelayLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(TokenRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(Location, Location), 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/bridge-hubs/bridge-hub-kusama/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/block_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs index e7fdb2aae2a01ec06076de83d94817e540e205dd..2bd7975bf98c36996520716c9dc11822d8287234 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); 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..1c9119361985cd541ca42dce6eb8d2f355a29c6c --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,77 @@ +// 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 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_parachain_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![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: `48` + // Estimated: `3517` + // Minimum execution time: 2_080_000 picoseconds. + Weight::from_parts(2_157_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + // Standard Error: 33_906 + .saturating_add(Weight::from_parts(196_603_239, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } +} 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..0b0524339aa761053a7c559a3a2ab725c0cb6c22 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,155 @@ +// 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 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_xcmp_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![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: 3_796_000 picoseconds. + Weight::from_parts(4_027_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: 9_990_000 picoseconds. + Weight::from_parts(10_439_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// 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: 2_394_000 picoseconds. + Weight::from_parts(2_493_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: 3_283_000 picoseconds. + Weight::from_parts(3_388_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn take_first_concatenated_xcm() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_974_000 picoseconds. + Weight::from_parts(6_166_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) + /// Storage: 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: 117_856_000 picoseconds. + Weight::from_parts(119_808_000, 0) + .saturating_add(Weight::from_parts(0, 69176)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) + /// 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: 52_555_000 picoseconds. + Weight::from_parts(54_052_000, 0) + .saturating_add(Weight::from_parts(0, 69175)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/extrinsic_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs index 1a4adb968bb7195428ea00d59cd92dcd3b6eea5f..898d72ec5b19519a77ec0b75bb65d757213b35d4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs similarity index 58% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/frame_system.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs index 5a0a60cc9952dc8f7eb98458a848b24b9c166f3a..b4b7cbf05a5ec757bc3a60e08ef62678029b47f2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `frame_system` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=frame_system // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,22 +55,22 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_985_000 picoseconds. - Weight::from_parts(2_177_341, 0) + // Minimum execution time: 1_584_000 picoseconds. + Weight::from_parts(2_117_975, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(386, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(384, 0).saturating_mul(b.into())) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_288_000 picoseconds. - Weight::from_parts(23_888_468, 0) + // Minimum execution time: 4_607_000 picoseconds. + Weight::from_parts(14_948_582, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_718, 0).saturating_mul(b.into())) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_673, 0).saturating_mul(b.into())) } /// Storage: `System::Digest` (r:1 w:1) /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -80,8 +80,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 3_700_000 picoseconds. - Weight::from_parts(3_867_000, 0) + // Minimum execution time: 2_681_000 picoseconds. + Weight::from_parts(2_877_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -100,11 +100,11 @@ impl frame_system::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `119` - // Estimated: `1604` - // Minimum execution time: 100_298_586_000 picoseconds. - Weight::from_parts(101_869_369_000, 0) - .saturating_add(Weight::from_parts(0, 1604)) + // Measured: `164` + // Estimated: `1649` + // Minimum execution time: 95_893_701_000 picoseconds. + Weight::from_parts(98_086_094_000, 0) + .saturating_add(Weight::from_parts(0, 1649)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -115,11 +115,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_052_000 picoseconds. - Weight::from_parts(2_115_000, 0) + // Minimum execution time: 1_597_000 picoseconds. + Weight::from_parts(1_660_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_048 - .saturating_add(Weight::from_parts(755_436, 0).saturating_mul(i.into())) + // Standard Error: 1_871 + .saturating_add(Weight::from_parts(748_346, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -129,11 +129,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_044_000 picoseconds. - Weight::from_parts(2_110_000, 0) + // Minimum execution time: 1_625_000 picoseconds. + Weight::from_parts(1_669_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_011 - .saturating_add(Weight::from_parts(569_993, 0).saturating_mul(i.into())) + // Standard Error: 903 + .saturating_add(Weight::from_parts(561_709, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -141,15 +141,50 @@ impl frame_system::WeightInfo for WeightInfo { /// 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: 3_741_000 picoseconds. - Weight::from_parts(3_838_000, 0) - .saturating_add(Weight::from_parts(0, 66)) - // Standard Error: 2_455 - .saturating_add(Weight::from_parts(1_216_154, 0).saturating_mul(p.into())) + // Measured: `71 + p * (69 ±0)` + // Estimated: `72 + p * (70 ±0)` + // Minimum execution time: 3_306_000 picoseconds. + Weight::from_parts(3_412_000, 0) + .saturating_add(Weight::from_parts(0, 72)) + // Standard Error: 1_366 + .saturating_add(Weight::from_parts(1_138_953, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// 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: 7_834_000 picoseconds. + Weight::from_parts(8_344_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::AuthorizedUpgrade` (r:1 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn apply_authorized_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `186` + // Estimated: `1671` + // Minimum execution time: 98_682_277_000 picoseconds. + Weight::from_parts(101_609_257_000, 0) + .saturating_add(Weight::from_parts(0, 1671)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs similarity index 89% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/mod.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index e226021e77ab58d51bda2eaa23ff732d53e7f4cb..f1050b3ae636261ff21674c3bb34c05bf6d232c5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,14 @@ //! 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; @@ -34,5 +37,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/collectives/collectives-polkadot/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs similarity index 75% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_balances.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs index dd0c02ab87306ac8d311c223a0c4b297b67552c4..c4770a7c94381cfca9404a6577c703164f215918 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_balances` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_balances // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +56,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 55_696_000 picoseconds. - Weight::from_parts(56_582_000, 0) + // Minimum execution time: 42_773_000 picoseconds. + Weight::from_parts(43_292_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +68,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_885_000 picoseconds. - Weight::from_parts(41_993_000, 0) + // Minimum execution time: 34_023_000 picoseconds. + Weight::from_parts(34_513_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +80,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_565_000 picoseconds. - Weight::from_parts(15_080_000, 0) + // Minimum execution time: 11_685_000 picoseconds. + Weight::from_parts(12_103_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +92,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 22_158_000 picoseconds. - Weight::from_parts(22_715_000, 0) + // Minimum execution time: 16_233_000 picoseconds. + Weight::from_parts(16_706_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +104,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_957_000 picoseconds. - Weight::from_parts(58_618_000, 0) + // Minimum execution time: 43_909_000 picoseconds. + Weight::from_parts(44_683_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +116,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 52_018_000 picoseconds. - Weight::from_parts(52_795_000, 0) + // Minimum execution time: 42_081_000 picoseconds. + Weight::from_parts(42_553_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +128,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 17_469_000 picoseconds. - Weight::from_parts(18_030_000, 0) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(14_827_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,13 +141,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_223_000 picoseconds. - Weight::from_parts(17_587_000, 0) + // Minimum execution time: 14_189_000 picoseconds. + Weight::from_parts(14_587_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 16_201 - .saturating_add(Weight::from_parts(15_360_967, 0).saturating_mul(u.into())) + // Standard Error: 10_909 + .saturating_add(Weight::from_parts(13_040_864, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) } + /// Storage: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_218_000 picoseconds. + Weight::from_parts(5_562_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs new file mode 100644 index 0000000000000000000000000000000000000000..8727b9633b1f0eaca2bcaa5f7a43d832f6abbe9b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -0,0 +1,516 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_broker` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_broker +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_broker`. +pub struct WeightInfo(PhantomData); +impl pallet_broker::WeightInfo for WeightInfo { + /// Storage: `Broker::Configuration` (r:0 w:1) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + fn configure() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_944_000 picoseconds. + Weight::from_parts(2_045_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `10888` + // Estimated: `13506` + // Minimum execution time: 21_158_000 picoseconds. + Weight::from_parts(21_572_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `12090` + // Estimated: `13506` + // Minimum execution time: 20_497_000 picoseconds. + Weight::from_parts(20_995_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `146` + // Estimated: `1631` + // Minimum execution time: 10_280_000 picoseconds. + Weight::from_parts(10_686_000, 0) + .saturating_add(Weight::from_parts(0, 1631)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:0 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn start_sales(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12247` + // Estimated: `13732` + // Minimum execution time: 61_020_000 picoseconds. + Weight::from_parts(63_240_622, 0) + .saturating_add(Weight::from_parts(0, 13732)) + // Standard Error: 102 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(26)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:0 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn purchase() -> Weight { + // Proof Size summary in bytes: + // Measured: `316` + // Estimated: `3593` + // Minimum execution time: 30_627_000 picoseconds. + Weight::from_parts(31_648_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:2) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn renew() -> Weight { + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `4698` + // Minimum execution time: 57_701_000 picoseconds. + Weight::from_parts(59_825_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 12_898_000 picoseconds. + Weight::from_parts(13_506_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Regions` (r:1 w:2) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn partition() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 14_284_000 picoseconds. + Weight::from_parts(14_791_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Regions` (r:1 w:3) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn interlace() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 15_570_000 picoseconds. + Weight::from_parts(16_158_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn assign() -> Weight { + // Proof Size summary in bytes: + // Measured: `735` + // Estimated: `4681` + // Minimum execution time: 23_329_000 picoseconds. + Weight::from_parts(24_196_000, 0) + .saturating_add(Weight::from_parts(0, 4681)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolIo` (r:2 w:2) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:0 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `801` + // Estimated: `5996` + // Minimum execution time: 29_288_000 picoseconds. + Weight::from_parts(30_066_000, 0) + .saturating_add(Weight::from_parts(0, 5996)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:3 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `m` is `[1, 3]`. + fn claim_revenue(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `652` + // Estimated: `6196 + m * (2520 ±0)` + // Minimum execution time: 54_833_000 picoseconds. + Weight::from_parts(55_577_423, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 35_105 + .saturating_add(Weight::from_parts(1_267_911, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(m.into())) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn purchase_credit() -> Weight { + // Proof Size summary in bytes: + // Measured: `215` + // Estimated: `3680` + // Minimum execution time: 55_289_000 picoseconds. + Weight::from_parts(56_552_000, 0) + .saturating_add(Weight::from_parts(0, 3680)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn drop_region() -> Weight { + // Proof Size summary in bytes: + // Measured: `465` + // Estimated: `3550` + // Minimum execution time: 39_736_000 picoseconds. + Weight::from_parts(41_346_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn drop_contribution() -> Weight { + // Proof Size summary in bytes: + // Measured: `463` + // Estimated: `3533` + // Minimum execution time: 57_319_000 picoseconds. + Weight::from_parts(60_204_000, 0) + .saturating_add(Weight::from_parts(0, 3533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn drop_history() -> Weight { + // Proof Size summary in bytes: + // Measured: `857` + // Estimated: `3593` + // Minimum execution time: 85_216_000 picoseconds. + Weight::from_parts(91_144_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:1) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + fn drop_renewal() -> Weight { + // Proof Size summary in bytes: + // Measured: `556` + // Estimated: `4698` + // Minimum execution time: 32_331_000 picoseconds. + Weight::from_parts(39_877_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 1000]`. + fn request_core_count(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 18_128_000 picoseconds. + Weight::from_parts(19_061_234, 0) + .saturating_add(Weight::from_parts(0, 3539)) + // Standard Error: 48 + .saturating_add(Weight::from_parts(141, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:1 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn process_core_count(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `266` + // Estimated: `1487` + // Minimum execution time: 5_368_000 picoseconds. + Weight::from_parts(5_837_005, 0) + .saturating_add(Weight::from_parts(0, 1487)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn process_revenue() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6196` + // Minimum execution time: 36_047_000 picoseconds. + Weight::from_parts(37_101_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn rotate_sale(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12194` + // Estimated: `13506` + // Minimum execution time: 48_158_000 picoseconds. + Weight::from_parts(49_891_920, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(25)) + } + /// Storage: `Broker::InstaPoolIo` (r:1 w:0) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:0 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + fn process_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3493` + // Minimum execution time: 5_911_000 picoseconds. + Weight::from_parts(6_173_000, 0) + .saturating_add(Weight::from_parts(0, 3493)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workload` (r:1 w:1) + /// Proof: `Broker::Workload` (`max_values`: None, `max_size`: Some(1212), added: 3687, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_core_schedule() -> Weight { + // Proof Size summary in bytes: + // Measured: `1321` + // Estimated: `4786` + // Minimum execution time: 30_140_000 picoseconds. + Weight::from_parts(30_912_000, 0) + .saturating_add(Weight::from_parts(0, 4786)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn request_revenue_info_at() -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 13_684_000 picoseconds. + Weight::from_parts(14_252_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:0 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + fn notify_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_718_000 picoseconds. + Weight::from_parts(1_843_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::CoreCountInbox` (r:1 w:0) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn do_tick_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3863` + // Minimum execution time: 11_771_000 picoseconds. + Weight::from_parts(12_120_000, 0) + .saturating_add(Weight::from_parts(0, 3863)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs similarity index 54% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_collator_selection.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs index 53efb218440a87dbd5557708a42c92ef39add826..2341890c314ec422aebf30b27111502a01909c25 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_collator_selection` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_collator_selection // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,13 +57,13 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `b` is `[1, 20]`. fn set_invulnerables(b: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `163 + b * (79 ±0)` - // Estimated: `1154 + b * (2555 ±0)` - // Minimum execution time: 14_882_000 picoseconds. - Weight::from_parts(12_290_529, 0) - .saturating_add(Weight::from_parts(0, 1154)) - // Standard Error: 6_842 - .saturating_add(Weight::from_parts(3_189_571, 0).saturating_mul(b.into())) + // Measured: `164 + b * (79 ±0)` + // Estimated: `1155 + b * (2555 ±0)` + // Minimum execution time: 11_038_000 picoseconds. + Weight::from_parts(8_347_616, 0) + .saturating_add(Weight::from_parts(0, 1155)) + // Standard Error: 5_166 + .saturating_add(Weight::from_parts(3_025_311, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 2555).saturating_mul(b.into())) @@ -72,40 +72,42 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// 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::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, 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: `756 + b * (32 ±0) + c * (53 ±0)` + // Measured: `720 + b * (32 ±0) + c * (53 ±0)` // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` - // Minimum execution time: 48_113_000 picoseconds. - Weight::from_parts(49_767_909, 0) + // Minimum execution time: 36_983_000 picoseconds. + Weight::from_parts(37_900_558, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_725 - .saturating_add(Weight::from_parts(232_655, 0).saturating_mul(c.into())) + // Standard Error: 6_860 + .saturating_add(Weight::from_parts(94_160, 0).saturating_mul(b.into())) + // Standard Error: 1_300 + .saturating_add(Weight::from_parts(119_010, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into())) .saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into())) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// 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: `119 + b * (32 ±0)` + // Measured: `82 + b * (32 ±0)` // Estimated: `6287` - // Minimum execution time: 16_228_000 picoseconds. - Weight::from_parts(16_351_387, 0) + // Minimum execution time: 10_432_000 picoseconds. + Weight::from_parts(10_460_489, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_953 - .saturating_add(Weight::from_parts(140_754, 0).saturating_mul(b.into())) + // Standard Error: 2_803 + .saturating_add(Weight::from_parts(143_162, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,26 +117,58 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_541_000 picoseconds. - Weight::from_parts(7_720_000, 0) + // Minimum execution time: 4_302_000 picoseconds. + Weight::from_parts(4_508_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CollatorSelection::CandidacyBond` (r:0 w:1) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:1) /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - fn set_candidacy_bond() -> Weight { + /// 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` - // Estimated: `0` - // Minimum execution time: 7_402_000 picoseconds. - Weight::from_parts(7_729_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `0 + c * (180 ±0) + k * (112 ±0)` + // Estimated: `6287 + c * (901 ±29) + k * (901 ±29)` + // Minimum execution time: 7_712_000 picoseconds. + Weight::from_parts(7_935_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(5_173_626, 0).saturating_mul(c.into())) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(4_883_030, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(k.into())) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, 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::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: `250 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 22_767_000 picoseconds. + Weight::from_parts(25_594_856, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(110_451, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) /// Storage: `Session::NextKeys` (r:1 w:0) @@ -146,19 +180,45 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[1, 99]`. fn register_as_candidate(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `736 + c * (52 ±0)` + // Measured: `687 + c * (52 ±0)` // Estimated: `6287 + c * (54 ±0)` - // Minimum execution time: 41_874_000 picoseconds. - Weight::from_parts(45_654_015, 0) + // Minimum execution time: 30_792_000 picoseconds. + Weight::from_parts(34_485_582, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_317 - .saturating_add(Weight::from_parts(221_237, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) + // Standard Error: 2_421 + .saturating_add(Weight::from_parts(152_013, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 54).saturating_mul(c.into())) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:1) - /// Proof: `CollatorSelection::Candidates` (`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::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: `855 + c * (52 ±0)` + // Estimated: `6287 + c * (55 ±0)` + // Minimum execution time: 45_538_000 picoseconds. + Weight::from_parts(50_758_223, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_779 + .saturating_add(Weight::from_parts(149_419, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 55).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) @@ -166,13 +226,13 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[4, 100]`. fn leave_intent(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `277 + c * (48 ±0)` // Estimated: `6287` - // Minimum execution time: 33_693_000 picoseconds. - Weight::from_parts(37_321_527, 0) + // Minimum execution time: 26_356_000 picoseconds. + Weight::from_parts(29_910_328, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 3_499 - .saturating_add(Weight::from_parts(182_068, 0).saturating_mul(c.into())) + // Standard Error: 2_159 + .saturating_add(Weight::from_parts(123_421, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -186,18 +246,20 @@ impl pallet_collator_selection::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 44_412_000 picoseconds. - Weight::from_parts(45_196_000, 0) + // Minimum execution time: 36_377_000 picoseconds. + Weight::from_parts(37_121_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: `CollatorSelection::Candidates` (r:1 w:0) - /// Proof: `CollatorSelection::Candidates` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::LastAuthoredBlock` (r:100 w:0) /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::DesiredCandidates` (r:1 w:0) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:97 w:97) @@ -206,14 +268,14 @@ impl pallet_collator_selection::WeightInfo for WeightIn /// The range of component `c` is `[1, 100]`. fn new_session(r: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2243 + c * (97 ±0) + r * (112 ±0)` + // Measured: `2143 + c * (97 ±0) + r * (112 ±0)` // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` - // Minimum execution time: 17_360_000 picoseconds. - Weight::from_parts(17_599_000, 0) + // Minimum execution time: 15_761_000 picoseconds. + Weight::from_parts(16_078_000, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 350_829 - .saturating_add(Weight::from_parts(15_375_949, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Standard Error: 270_522 + .saturating_add(Weight::from_parts(11_903_266, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(c.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) 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..e9cdc2478766de88bfd3cc8765ba472bac61db30 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,186 @@ +// 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 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_message_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![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: 10_918_000 picoseconds. + Weight::from_parts(11_224_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `MessageQueue::BookStateFor` (r:2 w:2) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + fn ready_ring_unknit() -> Weight { + // Proof Size summary in bytes: + // Measured: `218` + // Estimated: `6044` + // Minimum execution time: 9_649_000 picoseconds. + Weight::from_parts(10_056_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn service_queue_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `6` + // Estimated: `3517` + // Minimum execution time: 3_134_000 picoseconds. + Weight::from_parts(3_197_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn service_page_base_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 4_915_000 picoseconds. + Weight::from_parts(5_127_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn service_page_base_no_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 5_011_000 picoseconds. + Weight::from_parts(5_150_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MessageQueue::BookStateFor` (r:0 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn service_page_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 168_806_000 picoseconds. + Weight::from_parts(170_795_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:0) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn bump_service_head() -> Weight { + // Proof Size summary in bytes: + // Measured: `171` + // Estimated: `3517` + // Minimum execution time: 6_413_000 picoseconds. + Weight::from_parts(6_797_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn reap_page() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 52_734_000 picoseconds. + Weight::from_parts(54_106_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn execute_overweight_page_removed() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 68_400_000 picoseconds. + Weight::from_parts(70_336_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn execute_overweight_page_updated() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 109_496_000 picoseconds. + Weight::from_parts(111_632_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs similarity index 73% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_multisig.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index ee7b70736414d86fc270d08777fe606ba76c0b34..1aaf3f4a6fb9dd88f83042915080b08bb735e664 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_multisig` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-kusama-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_multisig // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_714_000 picoseconds. - Weight::from_parts(14_440_231, 0) + // Minimum execution time: 11_938_000 picoseconds. + Weight::from_parts(13_021_007, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(598, 0).saturating_mul(z.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_768_000 picoseconds. - Weight::from_parts(33_662_218, 0) + // Minimum execution time: 37_643_000 picoseconds. + Weight::from_parts(27_088_068, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_633 - .saturating_add(Weight::from_parts(128_927, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_543, 0).saturating_mul(z.into())) + // Standard Error: 828 + .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 29_745_000 picoseconds. - Weight::from_parts(20_559_891, 0) + // Minimum execution time: 25_825_000 picoseconds. + Weight::from_parts(15_698_835, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 914 - .saturating_add(Weight::from_parts(103_601, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(z.into())) + // Standard Error: 568 + .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,13 +107,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 51_506_000 picoseconds. - Weight::from_parts(36_510_777, 0) + // Minimum execution time: 43_587_000 picoseconds. + Weight::from_parts(29_740_539, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_183 - .saturating_add(Weight::from_parts(183_764, 0).saturating_mul(s.into())) - // Standard Error: 21 - .saturating_add(Weight::from_parts(1_653, 0).saturating_mul(z.into())) + // Standard Error: 771 + .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -124,11 +124,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_072_000 picoseconds. - Weight::from_parts(32_408_621, 0) + // Minimum execution time: 24_966_000 picoseconds. + Weight::from_parts(25_879_458, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 913 - .saturating_add(Weight::from_parts(121_410, 0).saturating_mul(s.into())) + // Standard Error: 777 + .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +139,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_301_000 picoseconds. - Weight::from_parts(18_223_547, 0) + // Minimum execution time: 14_450_000 picoseconds. + Weight::from_parts(14_607_858, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 747 - .saturating_add(Weight::from_parts(114_584, 0).saturating_mul(s.into())) + // Standard Error: 471 + .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +154,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_107_000 picoseconds. - Weight::from_parts(33_674_827, 0) + // Minimum execution time: 26_861_000 picoseconds. + Weight::from_parts(27_846_825, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_220 - .saturating_add(Weight::from_parts(122_011, 0).saturating_mul(s.into())) + // Standard Error: 714 + .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs similarity index 75% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_session.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs index 8a6943d53048047641132a70d6cade4983e503b6..b0d993d68a6a4e824c6050a3edef87870438e6b2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_session` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_session // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,11 +56,11 @@ impl pallet_session::WeightInfo for WeightInfo { /// 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: 16_684_000 picoseconds. - Weight::from_parts(17_167_000, 0) - .saturating_add(Weight::from_parts(0, 3735)) + // Measured: `271` + // Estimated: `3736` + // Minimum execution time: 15_149_000 picoseconds. + Weight::from_parts(16_053_000, 0) + .saturating_add(Weight::from_parts(0, 3736)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -70,11 +70,11 @@ impl pallet_session::WeightInfo for WeightInfo { /// 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: 11_692_000 picoseconds. - Weight::from_parts(12_248_000, 0) - .saturating_add(Weight::from_parts(0, 3707)) + // Measured: `243` + // Estimated: `3708` + // Minimum execution time: 11_159_000 picoseconds. + Weight::from_parts(11_504_000, 0) + .saturating_add(Weight::from_parts(0, 3708)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs similarity index 79% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_timestamp.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs index 8c6a2b5505e56afe4b3f32c06d2a3ccb59bc36f0..ad8924d9ce7374c285ddc662c59d860707443bfc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_timestamp` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_timestamp // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,8 +58,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `86` // Estimated: `1493` - // Minimum execution time: 9_214_000 picoseconds. - Weight::from_parts(9_535_000, 0) + // Minimum execution time: 5_552_000 picoseconds. + Weight::from_parts(5_821_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +68,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `57` // Estimated: `0` - // Minimum execution time: 3_269_000 picoseconds. - Weight::from_parts(3_458_000, 0) + // Minimum execution time: 2_848_000 picoseconds. + Weight::from_parts(2_953_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs similarity index 69% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_utility.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs index c6fc093cc4b44e1396d49b4a74e34eb99f07bd3c..0f5340843bd6481da855cb403b2b7d6442ed0a52 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs @@ -16,17 +16,17 @@ //! Autogenerated weights for `pallet_utility` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-dev +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_utility // --no-storage-info @@ -36,8 +36,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-polkadot/src/weights/ +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,18 +55,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_918_000 picoseconds. - Weight::from_parts(2_421_521, 0) + // Minimum execution time: 3_721_000 picoseconds. + Weight::from_parts(7_071_852, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_252 - .saturating_add(Weight::from_parts(6_625_635, 0).saturating_mul(c.into())) + // Standard Error: 746 + .saturating_add(Weight::from_parts(2_767_352, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_304_000 picoseconds. - Weight::from_parts(5_546_000, 0) + // Minimum execution time: 3_631_000 picoseconds. + Weight::from_parts(3_836_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -74,18 +74,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_847_000 picoseconds. - Weight::from_parts(1_224_975, 0) + // Minimum execution time: 3_817_000 picoseconds. + Weight::from_parts(2_683_003, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_818 - .saturating_add(Weight::from_parts(6_891_149, 0).saturating_mul(c.into())) + // Standard Error: 782 + .saturating_add(Weight::from_parts(3_059_987, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_269_000 picoseconds. - Weight::from_parts(9_604_000, 0) + // Minimum execution time: 5_463_000 picoseconds. + Weight::from_parts(5_701_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -93,10 +93,10 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_855_000 picoseconds. - Weight::from_parts(6_965_000, 0) + // Minimum execution time: 3_771_000 picoseconds. + Weight::from_parts(5_714_929, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_631 - .saturating_add(Weight::from_parts(6_545_496, 0).saturating_mul(c.into())) + // Standard Error: 740 + .saturating_add(Weight::from_parts(2_800_888, 0).saturating_mul(c.into())) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs similarity index 62% rename from cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index bd7615895e2e96fa95d45389e3296d988fc7057e..0082db3099d029c976779af8600bcaf4410e8a2f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-polkadot/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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("asset-hub-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=asset-hub-polkadot-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/assets/asset-hub-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=coretime-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,35 +60,55 @@ 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: 28_284_000 picoseconds. - Weight::from_parts(29_186_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 18_410_000 picoseconds. + Weight::from_parts(18_657_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: `0` - // Estimated: `1489` - // Minimum execution time: 24_830_000 picoseconds. - Weight::from_parts(26_312_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 56_616_000 picoseconds. + Weight::from_parts(57_751_000, 0) + .saturating_add(Weight::from_parts(0, 3571)) + .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: `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: `1489` - // Minimum execution time: 18_584_000 picoseconds. - Weight::from_parts(19_083_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) + .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`) @@ -108,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_415_000 picoseconds. - Weight::from_parts(9_821_000, 0) + // Minimum execution time: 6_014_000 picoseconds. + Weight::from_parts(6_412_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -119,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_902_000 picoseconds. - Weight::from_parts(3_377_000, 0) + // Minimum execution time: 1_844_000 picoseconds. + Weight::from_parts(1_957_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -142,11 +160,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: 32_730_000 picoseconds. - Weight::from_parts(33_879_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 24_067_000 picoseconds. + Weight::from_parts(24_553_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -166,11 +184,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: `257` - // Estimated: `3722` - // Minimum execution time: 34_053_000 picoseconds. - Weight::from_parts(34_506_000, 0) - .saturating_add(Weight::from_parts(0, 3722)) + // Measured: `292` + // Estimated: `3757` + // Minimum execution time: 27_023_000 picoseconds. + Weight::from_parts(27_620_000, 0) + .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -180,45 +198,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_824_000 picoseconds. - Weight::from_parts(2_986_000, 0) + // Minimum execution time: 1_866_000 picoseconds. + Weight::from_parts(1_984_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `129` - // Estimated: `11019` - // Minimum execution time: 17_011_000 picoseconds. - Weight::from_parts(17_488_000, 0) - .saturating_add(Weight::from_parts(0, 11019)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 16_425_000 picoseconds. + Weight::from_parts(16_680_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `133` - // Estimated: `11023` - // Minimum execution time: 17_191_000 picoseconds. - Weight::from_parts(17_784_000, 0) - .saturating_add(Weight::from_parts(0, 11023)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 16_171_000 picoseconds. + Weight::from_parts(16_564_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `13505` - // Minimum execution time: 18_625_000 picoseconds. - Weight::from_parts(19_177_000, 0) - .saturating_add(Weight::from_parts(0, 13505)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `106` + // Estimated: `15946` + // Minimum execution time: 17_785_000 picoseconds. + Weight::from_parts(18_123_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -236,36 +254,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 30_762_000 picoseconds. - Weight::from_parts(31_481_000, 0) + // Minimum execution time: 23_903_000 picoseconds. + Weight::from_parts(24_769_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) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_025_000 picoseconds. - Weight::from_parts(9_423_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `136` + // Estimated: `11026` + // Minimum execution time: 10_617_000 picoseconds. + Weight::from_parts(10_843_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `11030` - // Minimum execution time: 17_550_000 picoseconds. - Weight::from_parts(17_939_000, 0) - .saturating_add(Weight::from_parts(0, 11030)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 16_656_000 picoseconds. + Weight::from_parts(17_106_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -279,12 +297,50 @@ 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: `146` - // Estimated: `11036` - // Minimum execution time: 36_922_000 picoseconds. - Weight::from_parts(37_709_000, 0) - .saturating_add(Weight::from_parts(0, 11036)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `142` + // Estimated: `13507` + // Minimum execution time: 31_721_000 picoseconds. + Weight::from_parts(32_547_000, 0) + .saturating_add(Weight::from_parts(0, 13507)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// 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: 3_439_000 picoseconds. + Weight::from_parts(3_619_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: 24_657_000 picoseconds. + Weight::from_parts(24_971_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 34_028_000 picoseconds. + Weight::from_parts(34_697_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/paritydb_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs index 25679703831a13b8d1bb7fb7dd4d92fa84b1f255..1c6d2ebe568cc81e91167ec723102eebde49259c 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-polkadot/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/rocksdb_weights.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs index 3dd817aa6f137085b0e5fdf2b11b7f50e5c8b002..aa0cb2b4bc377bae5bce9b18cbb78820c91f344d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs similarity index 56% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/mod.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index ded5dc6702e60f4ddea9583d4b66c893f6d38b04..99af88812da2be05bd9273585ea7d186be9f8b90 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; @@ -24,14 +23,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,40 +49,36 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } -pub struct BridgeHubKusamaXcmWeight(core::marker::PhantomData); -impl XcmWeightInfo for BridgeHubKusamaXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) +pub struct CoretimeWestendXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for CoretimeWestendXcmWeight { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -111,44 +106,36 @@ impl XcmWeightInfo for BridgeHubKusamaXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -163,7 +150,7 @@ impl XcmWeightInfo for BridgeHubKusamaXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -175,13 +162,13 @@ impl XcmWeightInfo for BridgeHubKusamaXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -214,16 +201,16 @@ impl XcmWeightInfo for BridgeHubKusamaXcmWeight { fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -235,11 +222,11 @@ impl XcmWeightInfo for BridgeHubKusamaXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs similarity index 85% rename from cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 9b8611fd6637aab79237ba23883b6497e091b53b..6f5a52de98c39fb7ff5de96c41652be063f56a74 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -16,27 +16,29 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-kusama-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::fungible -// --chain=asset-hub-kusama-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-kusama/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 25_602_000 picoseconds. - Weight::from_parts(26_312_000, 3593) + // Minimum execution time: 19_401_000 picoseconds. + Weight::from_parts(19_768_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +67,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 51_173_000 picoseconds. - Weight::from_parts(52_221_000, 6196) + // Minimum execution time: 42_452_000 picoseconds. + Weight::from_parts(43_126_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -86,10 +88,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `246` + // Measured: `207` // Estimated: `6196` - // Minimum execution time: 74_651_000 picoseconds. - Weight::from_parts(76_500_000, 6196) + // Minimum execution time: 58_090_000 picoseconds. + Weight::from_parts(59_502_000, 6196) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -116,10 +118,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 458_666_000 picoseconds. - Weight::from_parts(470_470_000, 3610) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 23_569_000 picoseconds. + Weight::from_parts(24_598_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -127,8 +129,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_701_000 picoseconds. - Weight::from_parts(3_887_000, 0) + // Minimum execution time: 2_546_000 picoseconds. + Weight::from_parts(2_674_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -136,31 +138,31 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 25_709_000 picoseconds. - Weight::from_parts(26_320_000, 3593) + // Minimum execution time: 16_889_000 picoseconds. + Weight::from_parts(17_350_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r: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: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 51_663_000 picoseconds. - Weight::from_parts(52_538_000, 3610) + // Measured: `106` + // Estimated: `3593` + // Minimum execution time: 43_964_000 picoseconds. + Weight::from_parts(45_293_000, 3593) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -178,10 +180,10 @@ impl WeightInfo { // 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: 31_972_000 picoseconds. - Weight::from_parts(32_834_000, 3610) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_704_000 picoseconds. + Weight::from_parts(21_266_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs similarity index 76% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs rename to cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 7968649d143c1ca54d05285d0958379a2124fe7a..74254814bcafc7f67dd20d8fa5dcd8da4337f782 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -16,18 +16,18 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-polkadot-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: // ./target/production/polkadot-parachain // benchmark // pallet -// --template=./templates/xcm-bench-template.hbs -// --chain=bridge-hub-polkadot-dev +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled // --pallet=pallet_xcm_benchmarks::generic // --no-storage-info @@ -37,8 +37,8 @@ // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,10 +64,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 30_923_000 picoseconds. - Weight::from_parts(31_653_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_424_000 picoseconds. + Weight::from_parts(23_208_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -75,8 +75,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_837_000 picoseconds. - Weight::from_parts(2_932_000, 0) + // Minimum execution time: 1_194_000 picoseconds. + Weight::from_parts(1_306_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -84,58 +84,58 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 10_319_000 picoseconds. - Weight::from_parts(10_614_000, 3497) + // Minimum execution time: 6_359_000 picoseconds. + Weight::from_parts(6_585_000, 3497) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_466_000 picoseconds. - Weight::from_parts(12_005_000, 0) + // Minimum execution time: 6_297_000 picoseconds. + Weight::from_parts(6_661_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_039_000 picoseconds. - Weight::from_parts(3_125_000, 0) + // Minimum execution time: 1_778_000 picoseconds. + Weight::from_parts(1_923_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_655_000 picoseconds. - Weight::from_parts(2_717_000, 0) + // Minimum execution time: 1_212_000 picoseconds. + Weight::from_parts(1_314_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_655_000 picoseconds. - Weight::from_parts(2_695_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_247_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_612_000 picoseconds. - Weight::from_parts(2_685_000, 0) + // Minimum execution time: 1_173_000 picoseconds. + Weight::from_parts(1_275_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_286_000 picoseconds. - Weight::from_parts(3_425_000, 0) + // Minimum execution time: 1_247_000 picoseconds. + Weight::from_parts(1_332_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_613_000 picoseconds. - Weight::from_parts(2_699_000, 0) + // Minimum execution time: 1_170_000 picoseconds. + Weight::from_parts(1_237_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -151,10 +151,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 24_616_000 picoseconds. - Weight::from_parts(25_147_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 19_872_000 picoseconds. + Weight::from_parts(20_453_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -164,8 +164,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 14_511_000 picoseconds. - Weight::from_parts(14_831_000, 3555) + // Minimum execution time: 9_105_000 picoseconds. + Weight::from_parts(9_365_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -173,8 +173,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_640_000 picoseconds. - Weight::from_parts(2_702_000, 0) + // Minimum execution time: 1_228_000 picoseconds. + Weight::from_parts(1_293_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -190,10 +190,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: `38` - // Estimated: `3503` - // Minimum execution time: 26_044_000 picoseconds. - Weight::from_parts(26_561_000, 3503) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 19_535_000 picoseconds. + Weight::from_parts(20_139_000, 3539) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -203,44 +203,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_568_000 picoseconds. - Weight::from_parts(4_764_000, 0) + // Minimum execution time: 3_158_000 picoseconds. + Weight::from_parts(3_275_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_953_000 picoseconds. - Weight::from_parts(4_079_000, 0) + // Minimum execution time: 1_539_000 picoseconds. + Weight::from_parts(1_607_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_793_000 picoseconds. - Weight::from_parts(2_914_000, 0) + // Minimum execution time: 1_317_000 picoseconds. + Weight::from_parts(1_427_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_719_000 picoseconds. - Weight::from_parts(2_829_000, 0) + // Minimum execution time: 1_176_000 picoseconds. + Weight::from_parts(1_250_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_710_000 picoseconds. - Weight::from_parts(2_824_000, 0) + // Minimum execution time: 1_202_000 picoseconds. + Weight::from_parts(1_279_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_941_000 picoseconds. - Weight::from_parts(3_201_000, 0) + // Minimum execution time: 1_411_000 picoseconds. + Weight::from_parts(1_463_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -256,10 +256,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 28_080_000 picoseconds. - Weight::from_parts(28_920_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_991_000 picoseconds. + Weight::from_parts(23_820_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -267,8 +267,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_752_000 picoseconds. - Weight::from_parts(4_982_000, 0) + // Minimum execution time: 3_534_000 picoseconds. + Weight::from_parts(3_708_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -284,10 +284,10 @@ 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: `70` - // Estimated: `3535` - // Minimum execution time: 24_810_000 picoseconds. - Weight::from_parts(25_270_000, 3535) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_025_000 picoseconds. + Weight::from_parts(20_463_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -295,35 +295,35 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_676_000 picoseconds. - Weight::from_parts(2_780_000, 0) + // Minimum execution time: 1_213_000 picoseconds. + Weight::from_parts(1_290_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_624_000 picoseconds. - Weight::from_parts(2_710_000, 0) + // Minimum execution time: 1_207_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_611_000 picoseconds. - Weight::from_parts(2_707_000, 0) + // Minimum execution time: 1_195_000 picoseconds. + Weight::from_parts(1_231_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_653_000 picoseconds. - Weight::from_parts(2_740_000, 0) + // Minimum execution time: 1_182_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_821_000 picoseconds. - Weight::from_parts(2_874_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_252_000, 0) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc7d36a8ba1854f1a52b7630d98b545a28868c74 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -0,0 +1,329 @@ +// 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, Broker, FeeAssetId, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, WeightToFee, XcmpQueue, +}; +use frame_support::{ + pallet_prelude::PalletInfoAccess, + parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, +}; +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::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, +}; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; + +parameter_types! { + pub const TokenRelayLocation: Location = Location::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Westend); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub BrokerPalletLocation: Location = + PalletInstance(::index() as u8).into(); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub FellowshipLocation: Location = Location::new(1, Parachain(1001)); + pub const GovernanceLocation: Location = Location::parent(); +} + +/// Type for specifying how a `Location` 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. +pub type FungibleTransactor = FungibleAdapter< + // 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` `Location` 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`. + (), +>; + +/// Means for transacting coretime regions on this chain. +pub type RegionTransactor = NonFungibleAdapter< + // Use this non-fungible implementation: + Broker, + // This adapter will handle coretime regions from the broker pallet. + IsConcrete, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = (FungibleTransactor, RegionTransactor); + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with XCM's `Transact`. There is an `OriginKind` that can +/// bias the kind of local `Origin` it will become. +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, +); + +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } +} + +pub struct FellowsPlurality; +impl Contains for FellowsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [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 { .. } | + // Should not be in Polkadot/Kusama. Here in order to speed up testing. + frame_system::Call::set_storage { .. }, + ) | RuntimeCall::ParachainSystem(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Balances(..) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::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 attempts 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: Location = (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 = AssetTransactors; + 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 WND. + type IsTeleporter = ConcreteAssetFromSystem; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CoretimeWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = UsingComponents< + WeightToFee, + TokenRelayLocation, + AccountId, + Balances, + ToStakingPot, + >; + 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; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} + +/// Converts a local signed origin into an XCM location. 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-kusama/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/pallet_timestamp.rs deleted file mode 100644 index 8edae065f1b9173a3767a037c167f05420b95a70..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/pallet_timestamp.rs +++ /dev/null @@ -1,75 +0,0 @@ -// 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-07-31, 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("asset-hub-kusama-dev")`, DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot-parachain -// benchmark -// pallet -// --chain=asset-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_timestamp -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-kusama/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: `86` - // Estimated: `1493` - // Minimum execution time: 9_313_000 picoseconds. - Weight::from_parts(9_775_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_322_000 picoseconds. - Weight::from_parts(3_577_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } -} diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml similarity index 72% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/Cargo.toml rename to cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index e8abc61311c280b7c1655043e6a1339070c874c4..a357bf519e40fcb7b14ae0b2c4e92b0eb6fbb94f 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -1,68 +1,76 @@ [package] -name = "glutton-runtime" -version = "1.0.0" -description = "Glutton parachain runtime." +name = "glutton-westend-runtime" +version = "3.0.0" authors.workspace = true 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"] } # 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-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false} -frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true} -frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true} -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false} -pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false, optional = true} -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false, optional = true} +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-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } +pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false, optional = true } +pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false, optional = true } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false} -sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", 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-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} +sp-inherents = { path = "../../../../../substrate/primitives/inherents", default-features = false } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", 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 -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } # 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-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-glutton/runtime-benchmarks", - "pallet-sudo?/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "parachains-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -86,6 +94,7 @@ std = [ "frame-try-runtime?/std", "pallet-aura/std", "pallet-glutton/std", + "pallet-message-queue/std", "pallet-sudo/std", "pallet-timestamp/std", "parachain-info/std", @@ -104,6 +113,7 @@ std = [ "sp-storage/std", "sp-transaction-pool/std", "sp-version/std", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -118,10 +128,14 @@ try-runtime = [ "frame-try-runtime/try-runtime", "pallet-aura/try-runtime", "pallet-glutton/try-runtime", + "pallet-message-queue/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "parachain-info/try-runtime", "sp-runtime/try-runtime", ] -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/cumulus/parachains/runtimes/glutton/glutton-kusama/build.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/build.rs similarity index 100% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/build.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/build.rs diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs similarity index 81% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/src/lib.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index f5d52239e54374a0274289a896a5c3c472c4e615..fe18f48fbb3c20b294d1efee8048ca75ae76f784 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -13,13 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Glutton Runtime +//! # Glutton Westend Runtime //! //! The purpose of the Glutton parachain is to do stress testing on the Kusama -//! network. +//! network. This runtime targets the Westend runtime to allow development +//! separate to the Kusama runtime. //! //! There may be multiple instances of the Glutton parachain deployed and -//! connected to Kusama. +//! connected to its parent relay chain. //! //! These parachains are not holding any real value. Their purpose is to stress //! test the network. @@ -27,14 +28,14 @@ //! ### Governance //! //! Glutton defers its governance (namely, its `Root` origin), to its Relay -//! Chain parent, Kusama. +//! Chain parent, Kusama (or Westend for development purposes). //! //! ### XCM //! //! Since the main goal of Glutton is solely stress testing, the parachain will -//! only be able receive XCM messages from Kusama via DMP. This way the Glutton -//! parachains will be able to listen for upgrades that are coming from the -//! Relay chain. +//! only be able receive XCM messages from the Relay Chain via DMP. This way the +//! Glutton parachains will be able to listen for upgrades that are coming from +//! the Relay chain. #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] @@ -52,7 +53,7 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::{BlakeTwo256, Block as BlockT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; @@ -61,8 +62,9 @@ use sp_std::prelude::*; use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use cumulus_primitives_core::AggregateMessageOrigin; pub use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, @@ -85,6 +87,7 @@ use parachains_common::{AccountId, Signature}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; +use testnet_parachains_constants::westend::consensus::*; impl_opaque_keys! { pub struct SessionKeys { @@ -94,10 +97,10 @@ impl_opaque_keys! { #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("glutton"), - impl_name: create_runtime_str!("glutton"), + spec_name: create_runtime_str!("glutton-westend"), + impl_name: create_runtime_str!("glutton-westend"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -116,29 +119,6 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for .5 seconds of compute with a 12 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; -/// How many parachain blocks are processed by the relay chain per parent. Limits the -/// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 2; -/// Relay chain slot duration, in milliseconds. -const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; parameter_types! { pub const BlockHashCount: BlockNumber = 4096; @@ -163,28 +143,17 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const SS58Prefix: u8 = 2; + pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { type AccountId = AccountId; - type RuntimeCall = RuntimeCall; - type Lookup = AccountIdLookup; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; type BlockHashCount = BlockHashCount; type Version = Version; - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type SS58Prefix = SS58Prefix; @@ -195,6 +164,7 @@ impl frame_system::Config for Runtime { parameter_types! { // We do anything the parent chain tells us in this runtime. pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(2); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< @@ -208,13 +178,40 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type OutboundXcmpMessageSource = (); - type DmpMessageHandler = cumulus_pallet_xcm::UnlimitedDmpExecution; type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = (); type ReservedXcmpWeight = (); type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; type ConsensusHook = ConsensusHook; + type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; +} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(80) * + 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; + type QueueChangeHandler = (); + type QueuePausedQuery = (); // No XCMP queue pallet deployed. + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; } impl parachain_info::Config for Runtime {} @@ -233,7 +230,6 @@ impl pallet_aura::Config for Runtime { type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; type AllowMultipleBlocksPerSlot = ConstBool; - #[cfg(feature = "experimental")] type SlotDuration = ConstU64; } @@ -252,25 +248,24 @@ impl pallet_sudo::Config for Runtime { construct_runtime! { pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, - ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, - } = 1, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 2, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 3, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + ParachainInfo: parachain_info = 2, + Timestamp: pallet_timestamp = 3, // DMP handler. - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Storage, Event, Origin} = 10, + CumulusXcm: cumulus_pallet_xcm = 10, + MessageQueue: pallet_message_queue = 11, // The main stage. - Glutton: pallet_glutton::{Pallet, Call, Storage, Event, Config} = 20, + Glutton: pallet_glutton = 20, // Collator support - Aura: pallet_aura::{Pallet, Storage, Config} = 30, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 31, + Aura: pallet_aura = 30, + AuraExt: cumulus_pallet_aura_ext = 31, // Sudo. - Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config} = 255, + Sudo: pallet_sudo = 255, } } @@ -312,15 +307,14 @@ pub type Executive = frame_executive::Executive< AllPalletsWithSystem, >; -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( + [cumulus_pallet_parachain_system, ParachainSystem] [frame_system, SystemBench::] [pallet_glutton, Glutton] + [pallet_message_queue, MessageQueue] + [pallet_timestamp, Timestamp] ); } @@ -334,7 +328,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -355,11 +349,11 @@ 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 { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() } } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc8299ab1bd678e58a7909c1de4b0b55f4e1bedf --- /dev/null +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,75 @@ +// 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-11-07, 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("glutton-westend-dev-1300")`, 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=cumulus_pallet_parachain_system +// --chain=glutton-westend-dev-1300 +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-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 `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: `12` + // Estimated: `3517` + // Minimum execution time: 1_745_000 picoseconds. + Weight::from_parts(1_859_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + // Standard Error: 53_384 + .saturating_add(Weight::from_parts(196_309_089, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } +} diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/frame_system.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs similarity index 63% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/frame_system.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs index 36c4abc400634fd9ec857675031b11b3c33d3fd2..b68f16c9865894170b4e8aba3e524e4771fde2fc 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `frame_system` //! //! 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-11-07, 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("glutton-kusama-dev-1300")`, DB CACHE: 1024 +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("glutton-westend-dev-1300")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=glutton-kusama-dev-1300 -// --wasm-execution=compiled -// --pallet=frame_system -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/glutton/glutton-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=frame_system +// --chain=glutton-westend-dev-1300 +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,8 +53,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_717_000 picoseconds. - Weight::from_parts(1_782_325, 0) + // Minimum execution time: 1_570_000 picoseconds. + Weight::from_parts(1_626_000, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 .saturating_add(Weight::from_parts(387, 0).saturating_mul(b.into())) @@ -66,11 +64,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_089_000 picoseconds. - Weight::from_parts(6_353_000, 0) + // Minimum execution time: 4_200_000 picoseconds. + Weight::from_parts(4_262_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_788, 0).saturating_mul(b.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(1_791, 0).saturating_mul(b.into())) } /// Storage: `System::Digest` (r:1 w:1) /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -80,8 +78,8 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 3_389_000 picoseconds. - Weight::from_parts(3_605_000, 0) + // Minimum execution time: 2_680_000 picoseconds. + Weight::from_parts(2_936_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -100,11 +98,11 @@ impl frame_system::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `119` - // Estimated: `1604` - // Minimum execution time: 97_701_839_000 picoseconds. - Weight::from_parts(100_104_315_000, 0) - .saturating_add(Weight::from_parts(0, 1604)) + // Measured: `127` + // Estimated: `1612` + // Minimum execution time: 119_097_302_000 picoseconds. + Weight::from_parts(120_914_576_000, 0) + .saturating_add(Weight::from_parts(0, 1612)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -115,11 +113,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_638_000 picoseconds. - Weight::from_parts(1_726_000, 0) + // Minimum execution time: 1_606_000 picoseconds. + Weight::from_parts(1_704_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_886 - .saturating_add(Weight::from_parts(809_561, 0).saturating_mul(i.into())) + // Standard Error: 2_090 + .saturating_add(Weight::from_parts(765_829, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -129,11 +127,11 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_569_000 picoseconds. - Weight::from_parts(1_690_000, 0) + // Minimum execution time: 1_646_000 picoseconds. + Weight::from_parts(1_719_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 963 - .saturating_add(Weight::from_parts(580_145, 0).saturating_mul(i.into())) + // Standard Error: 1_067 + .saturating_add(Weight::from_parts(578_598, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -141,15 +139,42 @@ impl frame_system::WeightInfo for WeightInfo { /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `52 + p * (69 ±0)` - // Estimated: `46 + p * (70 ±0)` - // Minimum execution time: 3_039_000 picoseconds. - Weight::from_parts(3_090_000, 0) - .saturating_add(Weight::from_parts(0, 46)) - // Standard Error: 2_007 - .saturating_add(Weight::from_parts(1_269_045, 0).saturating_mul(p.into())) + // Measured: `58 + p * (69 ±0)` + // Estimated: `53 + p * (70 ±0)` + // Minimum execution time: 2_933_000 picoseconds. + Weight::from_parts(3_069_000, 0) + .saturating_add(Weight::from_parts(0, 53)) + // Standard Error: 1_844 + .saturating_add(Weight::from_parts(1_214_377, 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/glutton/glutton-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..47f9d1ee105e42fb61afcc5b4cb02a9dcda8e3ca --- /dev/null +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/mod.rs @@ -0,0 +1,19 @@ +// 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 cumulus_pallet_parachain_system; +pub mod pallet_glutton; +pub mod pallet_message_queue; +pub mod pallet_timestamp; diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/pallet_glutton.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_glutton.rs similarity index 79% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/pallet_glutton.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_glutton.rs index f278d246b33b1b4f0854b943fef8a1da22122e93..9345458a704af3dd86b77b0030ae6861b8b2ed23 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/pallet_glutton.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_glutton.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_glutton` //! //! 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-11-07, 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("glutton-kusama-dev-1300")`, DB CACHE: 1024 +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("glutton-westend-dev-1300")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=glutton-kusama-dev-1300 -// --wasm-execution=compiled -// --pallet=pallet_glutton -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/glutton/glutton-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_glutton +// --chain=glutton-westend-dev-1300 +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -59,11 +57,11 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `87` // Estimated: `1489` - // Minimum execution time: 8_925_000 picoseconds. - Weight::from_parts(9_186_000, 0) + // Minimum execution time: 6_453_000 picoseconds. + Weight::from_parts(6_629_000, 0) .saturating_add(Weight::from_parts(0, 1489)) - // Standard Error: 3_091 - .saturating_add(Weight::from_parts(9_666_196, 0).saturating_mul(n.into())) + // Standard Error: 3_416 + .saturating_add(Weight::from_parts(9_938_610, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) @@ -77,11 +75,11 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `120` // Estimated: `1489` - // Minimum execution time: 8_924_000 picoseconds. - Weight::from_parts(8_963_000, 0) + // Minimum execution time: 6_456_000 picoseconds. + Weight::from_parts(6_564_000, 0) .saturating_add(Weight::from_parts(0, 1489)) - // Standard Error: 1_202 - .saturating_add(Weight::from_parts(1_139_080, 0).saturating_mul(n.into())) + // Standard Error: 1_336 + .saturating_add(Weight::from_parts(1_141_705, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) @@ -91,11 +89,11 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 708_000 picoseconds. - Weight::from_parts(1_698_031, 0) + // Minimum execution time: 679_000 picoseconds. + Weight::from_parts(3_310_101, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 12 - .saturating_add(Weight::from_parts(106_500, 0).saturating_mul(i.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(103_703, 0).saturating_mul(i.into())) } /// Storage: `Glutton::TrashData` (r:5000 w:0) /// Proof: `Glutton::TrashData` (`max_values`: Some(65000), `max_size`: Some(1036), added: 3016, mode: `MaxEncodedLen`) @@ -104,11 +102,11 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `119115 + i * (1022 ±0)` // Estimated: `990 + i * (3016 ±0)` - // Minimum execution time: 698_000 picoseconds. - Weight::from_parts(970_000, 0) + // Minimum execution time: 765_000 picoseconds. + Weight::from_parts(1_004_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 4_022 - .saturating_add(Weight::from_parts(6_320_519, 0).saturating_mul(i.into())) + // Standard Error: 4_008 + .saturating_add(Weight::from_parts(6_130_770, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3016).saturating_mul(i.into())) } @@ -122,8 +120,8 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1900498` // Estimated: `5239782` - // Minimum execution time: 100_079_897_000 picoseconds. - Weight::from_parts(100_515_306_000, 0) + // Minimum execution time: 97_248_614_000 picoseconds. + Weight::from_parts(97_728_420_000, 0) .saturating_add(Weight::from_parts(0, 5239782)) .saturating_add(T::DbWeight::get().reads(1739)) } @@ -137,8 +135,8 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `9548` // Estimated: `16070` - // Minimum execution time: 100_237_009_000 picoseconds. - Weight::from_parts(100_472_213_000, 0) + // Minimum execution time: 97_305_112_000 picoseconds. + Weight::from_parts(97_427_728_000, 0) .saturating_add(Weight::from_parts(0, 16070)) .saturating_add(T::DbWeight::get().reads(7)) } @@ -150,8 +148,8 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `87` // Estimated: `1493` - // Minimum execution time: 5_120_000 picoseconds. - Weight::from_parts(5_262_000, 0) + // Minimum execution time: 4_125_000 picoseconds. + Weight::from_parts(4_339_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) } @@ -161,8 +159,8 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_947_000 picoseconds. - Weight::from_parts(6_171_000, 0) + // Minimum execution time: 3_879_000 picoseconds. + Weight::from_parts(4_211_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -172,8 +170,8 @@ impl pallet_glutton::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_964_000 picoseconds. - Weight::from_parts(6_166_000, 0) + // Minimum execution time: 3_920_000 picoseconds. + Weight::from_parts(4_081_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..eab6c15a40d28019967dd3112b1b2bc037b93106 --- /dev/null +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,179 @@ +// 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-11-07, 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("glutton-westend-dev-1300")`, 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_message_queue +// --chain=glutton-westend-dev-1300 +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-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_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: 10_833_000 picoseconds. + Weight::from_parts(11_237_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: 9_399_000 picoseconds. + Weight::from_parts(9_773_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: 3_277_000 picoseconds. + Weight::from_parts(3_358_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_429_000 picoseconds. + Weight::from_parts(5_667_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: 5_538_000 picoseconds. + Weight::from_parts(5_803_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: 89_888_000 picoseconds. + Weight::from_parts(90_929_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: 6_129_000 picoseconds. + Weight::from_parts(6_414_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: 52_366_000 picoseconds. + Weight::from_parts(53_500_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: 67_848_000 picoseconds. + Weight::from_parts(68_910_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: 107_564_000 picoseconds. + Weight::from_parts(109_377_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/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_timestamp.rs similarity index 75% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_timestamp.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_timestamp.rs index 8252834cc11e3cebb131332a92ffc80d25921dd4..4218dcc73f4e608e8b40ae5c4fcdd927747a5084 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/pallet_timestamp.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_timestamp` //! //! 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-11-07, 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-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("glutton-westend-dev-1300")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_timestamp -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_timestamp +// --chain=glutton-westend-dev-1300 +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,10 +54,10 @@ impl pallet_timestamp::WeightInfo for WeightInfo { /// Proof: `Aura::CurrentSlot` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn set() -> Weight { // Proof Size summary in bytes: - // Measured: `49` + // Measured: `86` // Estimated: `1493` - // Minimum execution time: 7_675_000 picoseconds. - Weight::from_parts(7_947_000, 0) + // Minimum execution time: 6_306_000 picoseconds. + Weight::from_parts(6_592_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `57` // Estimated: `0` - // Minimum execution time: 3_342_000 picoseconds. - Weight::from_parts(3_443_000, 0) + // Minimum execution time: 2_900_000 picoseconds. + Weight::from_parts(3_030_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/xcm_config.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs similarity index 80% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/src/xcm_config.rs rename to cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs index fb7b78b79d2a808021c5d0a8494e760c66417057..15bb519e115c5c2eaca25053a3d3e47c30fb21e0 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/xcm_config.rs @@ -18,20 +18,20 @@ use super::{ RuntimeOrigin, }; use frame_support::{ - match_types, parameter_types, - traits::{Everything, Nothing}, + parameter_types, + traits::{Contains, Everything, Nothing}, weights::Weight, }; use xcm::latest::prelude::*; use xcm_builder::{ - AllowExplicitUnpaidExecutionFrom, FixedWeightBounds, ParentAsSuperuser, ParentIsPreset, - SovereignSignedViaLocation, + AllowExplicitUnpaidExecutionFrom, FixedWeightBounds, FrameTransactionalProcessor, + ParentAsSuperuser, ParentIsPreset, SovereignSignedViaLocation, }; parameter_types! { - pub const KusamaLocation: MultiLocation = MultiLocation::parent(); - pub const KusamaNetwork: Option = Some(NetworkId::Kusama); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub const WestendLocation: Location = Location::parent(); + pub const WestendNetwork: Option = Some(NetworkId::Westend); + pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); } /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, @@ -47,8 +47,11 @@ pub type XcmOriginToTransactDispatchOrigin = ( ParentAsSuperuser, ); -match_types! { - pub type JustTheParent: impl Contains = { MultiLocation { parents:1, interior: Here } }; +pub struct JustTheParent; +impl Contains for JustTheParent { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [])) + } } parameter_types! { @@ -84,6 +87,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } impl cumulus_pallet_xcm::Config for Runtime { 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/bridge-hubs/bridge-hub-kusama/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml similarity index 71% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/Cargo.toml rename to cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 603e74850cc74aece8bda3b3adc1606205bcc1ba..eebd662c3fd5728f18b4dfc620d2b8f4c707f130 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -1,91 +1,92 @@ [package] -name = "bridge-hub-kusama-runtime" +name = "people-rococo-runtime" version = "0.1.0" authors.workspace = true edition.workspace = true -description = "Kusama's BridgeHub parachain runtime" +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.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", optional = true, features = ["derive"] } -smallvec = "1.11.0" +log = { workspace = true } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # 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-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} +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-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} +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 = { 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} +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-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-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } - -[dev-dependencies] -bridge-hub-test-utils = { path = "../test-utils" } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } [features] -default = [ "std" ] +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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", + "enumflags2/std", "frame-benchmarking?/std", "frame-executive/std", "frame-support/std", @@ -98,6 +99,8 @@ 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", @@ -108,9 +111,9 @@ 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", @@ -119,7 +122,6 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", - "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", @@ -128,6 +130,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -137,6 +140,7 @@ 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", @@ -144,6 +148,8 @@ 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", @@ -159,7 +165,6 @@ 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", @@ -171,6 +176,8 @@ 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", @@ -181,5 +188,3 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/build.rs b/cumulus/parachains/runtimes/people/people-rococo/build.rs similarity index 100% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/build.rs rename to cumulus/parachains/runtimes/people/people-rococo/build.rs diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs similarity index 70% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/lib.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 190987b1cfebad68feb20406a226e71e76dbed51..2bb5641b4af072d6d400b80bcf090826441fc0fa 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -1,53 +1,37 @@ // 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 . +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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")); +pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; -use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; - -use sp_std::prelude::*; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything}, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, TransformOrigin, + }, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -56,37 +40,44 @@ use frame_system::{ EnsureRoot, }; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; -use xcm_config::{ - FellowshipLocation, GovernanceLocation, XcmConfig, XcmOriginToTransactDispatchOrigin, +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, + NORMAL_DISPATCH_RATIO, }; - +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 polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; - +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 testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - -use parachains_common::{ - impls::DealWithFees, - kusama::{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 xcm::latest::prelude::BodyId; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, PriceForSiblingParachainDelivery, XcmConfig, + XcmOriginToTransactDispatchOrigin, }; -// XCM Imports -use xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; - /// 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 +/// A Block signed with an [`sp_runtime::Justification`]. pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. @@ -109,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -129,13 +123,13 @@ impl_opaque_keys! { #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("bridge-hub-kusama"), - impl_name: create_runtime_str!("bridge-hub-kusama"), + spec_name: create_runtime_str!("people-rococo"), + impl_name: create_runtime_str!("people-rococo"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 3, + transaction_version: 0, state_version: 1, }; @@ -167,54 +161,24 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const SS58Prefix: u8 = 2; + pub const SS58Prefix: u8 = 42; } -// Configure FRAME pallets to include in runtime. - +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - /// The identifier used to distinguish between accounts. + type BaseCallFilter = Everything; + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// The index type for storing how many extrinsics an account has signed. type Nonce = Nonce; - /// The type for hashing blocks and tries. type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The block type. type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; - /// Runtime version. + type DbWeight = RocksDbWeight; type Version = Version; - /// Converts a module to an index of this module in the runtime. - type PalletInfo = PalletInfo; - /// The data to be stored in an account. type AccountData = pallet_balances::AccountData; - /// What to do if a new account is created. - type OnNewAccount = (); - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); - /// The weight of database operations that the runtime can invoke. - type DbWeight = RocksDbWeight; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; - /// 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>; } @@ -223,7 +187,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -237,10 +201,8 @@ parameter_types! { } impl pallet_balances::Config for Runtime { - /// The type for recording an account's balance. type Balance = Balance; type DustRemoval = (); - /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; @@ -248,14 +210,14 @@ impl pallet_balances::Config for Runtime { 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 + /// Relay Chain `TransactionByteFee` / 10. pub const TransactionByteFee: Balance = MILLICENTS; } @@ -272,6 +234,7 @@ impl pallet_transaction_payment::Config for Runtime { 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 { @@ -279,17 +242,47 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; + 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 CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; + type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +parameter_types! { + pub MessageQueueServiceWeight: Weight = + Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +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 {} @@ -309,20 +302,14 @@ pub type RootOrFellows = EitherOfDiverse< impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + 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; - type PriceForSiblingDelivery = NoPriceForMessageDelivery; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; } pub const PERIOD: u32 = 6 * HOURS; @@ -346,9 +333,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -404,59 +390,73 @@ impl pallet_utility::Config for Runtime { 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 2, + ParachainInfo: parachain_info = 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 4 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, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + 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::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + + // The main stage. + Identity: pallet_identity = 50, + + // To migrate deposits + IdentityMigrator: identity_migrator = 248, } ); -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( + // Substrate [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] - [pallet_collator_selection, CollatorSelection] + // Polkadot + [polkadot_runtime_common::identity_migrator, IdentityMigrator] + // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_collator_selection, CollatorSelection] // XCM - [pallet_xcm, PolkadotXcm] - // NOTE: Make sure you point to the individual modules below. + [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] ); @@ -465,11 +465,20 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -482,7 +491,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -635,6 +644,7 @@ impl_runtime_apis! { 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 @@ -670,32 +680,67 @@ impl_runtime_apis! { 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 { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between People and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + None + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + use xcm::latest::prelude::*; - use xcm_config::KsmRelayLocation; + use xcm_config::{PriceForParentDelivery, RelayLocation}; parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( - xcm_config::KsmRelayLocation::get(), + pub ExistentialDepositAsset: Option = Some(( + RelayLocation::get(), ExistentialDeposit::get() ).into()); } impl pallet_xcm_benchmarks::Config for Runtime { - type XcmConfig = xcm_config::XcmConfig; + type XcmConfig = XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< XcmConfig, - ExistentialDepositMultiAsset, - xcm_config::PriceForParentDelivery, + ExistentialDepositAsset, + PriceForParentDelivery, >; - fn valid_destination() -> Result { - Ok(KsmRelayLocation::get()) + fn valid_destination() -> Result { + Ok(RelayLocation::get()) } - fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> Assets { // just concrete assets according to relay chain. - let assets: Vec = vec![ - MultiAsset { - id: Concrete(KsmRelayLocation::get()), + let assets: Vec = vec![ + Asset { + id: AssetId(RelayLocation::get()), fun: Fungible(1_000_000 * UNITS), } ]; @@ -704,12 +749,12 @@ impl_runtime_apis! { } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( - KsmRelayLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(KsmRelayLocation::get()) }, + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + RelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(RelayLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -719,55 +764,62 @@ impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(KsmRelayLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(RelayLocation::get()), fun: Fungible(UNITS), } } } impl pallet_xcm_benchmarks::generic::Config for Runtime { - type TransactAsset = Balances; 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> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { Err(BenchmarkError::Skip) } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { - Ok((KsmRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((RelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { - Ok(KsmRelayLocation::get()) + fn subscribe_origin() -> Result { + Ok(RelayLocation::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { - let origin = KsmRelayLocation::get(); - let assets: MultiAssets = (Concrete(KsmRelayLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = RelayLocation::get(); + let assets: Assets = (AssetId(RelayLocation::get()), 1_000 * UNITS).into(); + let ticket = Location::new(0, []); Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(RelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { Err(BenchmarkError::Skip) } } 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..88a89711019d59436c1ca270bbf19781f6dc77f0 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/people.rs @@ -0,0 +1,230 @@ +// 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, DAYS}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AccountIdConversion, Verify}, + 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 OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; + 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())] +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 + } +} + +/// A `Default` identity. This is given to users who get a username but have not set an identity. +impl Default for IdentityInfo { + fn default() -> Self { + IdentityInfo { + display: Data::None, + legal: Data::None, + web: Data::None, + matrix: Data::None, + email: Data::None, + pgp_fingerprint: None, + image: Data::None, + twitter: Data::None, + github: Data::None, + discord: Data::None, + } + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/block_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/block_weights.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs index e7fdb2aae2a01ec06076de83d94817e540e205dd..b2092d875c8328210667da4cbb95de0642e60ae3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/block_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); 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/bridge-hubs/bridge-hub-polkadot/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs similarity index 96% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/extrinsic_weights.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs index 1a4adb968bb7195428ea00d59cd92dcd3b6eea5f..332c3b324bb9c1b386257bf7953d37aba8f5af13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/extrinsic_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); 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..126d816afcdb551e31adc38aaeb54ad3af5b228c --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_balances.rs @@ -0,0 +1,158 @@ +// 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: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_132_000 picoseconds. + Weight::from_parts(5_467_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } + 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..1e8ba87e25101ae3104dcf71db8d3872cc22392e --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs @@ -0,0 +1,409 @@ +// 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)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn add_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_873_000 picoseconds. + Weight::from_parts(13_873_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn remove_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_653_000 picoseconds. + Weight::from_parts(10_653_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:1 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_username_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `11037` + // Minimum execution time: 75_928_000 picoseconds. + Weight::from_parts(75_928_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:0 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn accept_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `11037` + // Minimum execution time: 38_157_000 picoseconds. + Weight::from_parts(38_157_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + fn remove_expired_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3542` + // Minimum execution time: 46_821_000 picoseconds. + Weight::from_parts(46_821_000, 0) + .saturating_add(Weight::from_parts(0, 3542)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:0) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_primary_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `247` + // Estimated: `11037` + // Minimum execution time: 22_515_000 picoseconds. + Weight::from_parts(22_515_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:0) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn remove_dangling_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `126` + // Estimated: `11037` + // Minimum execution time: 15_997_000 picoseconds. + Weight::from_parts(15_997_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} 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/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs similarity index 65% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_xcm.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs index ffc5fa2fc23ed2c4cb5a0624866dc800ab5ae1e3..fabce29b5fd9451f27364c38481d2dac98c430d8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-polkadot-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-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=people-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,22 +62,33 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 25_510_000 picoseconds. - Weight::from_parts(25_755_000, 0) + // Minimum execution time: 17_830_000 picoseconds. + Weight::from_parts(18_411_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`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 24_125_000 picoseconds. - Weight::from_parts(25_559_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 55_456_000 picoseconds. + Weight::from_parts(56_808_000, 0) + .saturating_add(Weight::from_parts(0, 3535)) + .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`) @@ -93,6 +102,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// 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` @@ -107,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_625_000 picoseconds. - Weight::from_parts(9_232_000, 0) + // Minimum execution time: 5_996_000 picoseconds. + Weight::from_parts(6_154_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_690_000 picoseconds. - Weight::from_parts(2_906_000, 0) + // Minimum execution time: 1_768_000 picoseconds. + Weight::from_parts(1_914_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 30_131_000 picoseconds. - Weight::from_parts(31_138_000, 0) + // Minimum execution time: 24_120_000 picoseconds. + Weight::from_parts(24_745_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -165,11 +184,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: `220` - // Estimated: `3685` - // Minimum execution time: 32_411_000 picoseconds. - Weight::from_parts(33_009_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 26_630_000 picoseconds. + Weight::from_parts(27_289_000, 0) + .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -179,45 +198,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_548_000 picoseconds. - Weight::from_parts(2_727_000, 0) + // Minimum execution time: 1_821_000 picoseconds. + Weight::from_parts(1_946_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `95` - // Estimated: `10985` - // Minimum execution time: 15_298_000 picoseconds. - Weight::from_parts(15_964_000, 0) - .saturating_add(Weight::from_parts(0, 10985)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 16_586_000 picoseconds. + Weight::from_parts(16_977_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `10989` - // Minimum execution time: 14_927_000 picoseconds. - Weight::from_parts(15_528_000, 0) - .saturating_add(Weight::from_parts(0, 10989)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 16_923_000 picoseconds. + Weight::from_parts(17_415_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 16_409_000 picoseconds. - Weight::from_parts(16_960_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15946` + // Minimum execution time: 18_596_000 picoseconds. + Weight::from_parts(18_823_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -235,36 +254,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 28_204_000 picoseconds. - Weight::from_parts(28_641_000, 0) + // Minimum execution time: 23_817_000 picoseconds. + Weight::from_parts(24_520_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) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `136` - // Estimated: `8551` - // Minimum execution time: 8_576_000 picoseconds. - Weight::from_parts(8_895_000, 0) - .saturating_add(Weight::from_parts(0, 8551)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11026` + // Minimum execution time: 11_042_000 picoseconds. + Weight::from_parts(11_578_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `10996` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(15_726_000, 0) - .saturating_add(Weight::from_parts(0, 10996)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 17_306_000 picoseconds. + Weight::from_parts(17_817_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -278,12 +297,50 @@ 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: `112` - // Estimated: `11002` - // Minimum execution time: 34_186_000 picoseconds. - Weight::from_parts(35_204_000, 0) - .saturating_add(Weight::from_parts(0, 11002)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 32_141_000 picoseconds. + Weight::from_parts(32_954_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(10)) .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: 3_410_000 picoseconds. + Weight::from_parts(3_556_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: 25_021_000 picoseconds. + Weight::from_parts(25_240_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 33_801_000 picoseconds. + Weight::from_parts(34_655_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/paritydb_weights.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs index 25679703831a13b8d1bb7fb7dd4d92fa84b1f255..4338d928d807a41cc60ec91d86e91c81bb253631 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/paritydb_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); 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/bridge-hubs/bridge-hub-polkadot/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs similarity index 97% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/rocksdb_weights.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs index 3dd817aa6f137085b0e5fdf2b11b7f50e5c8b002..1d115d963facb39fe29d6258918fda3bc8d94900 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/rocksdb_weights.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs similarity index 56% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/mod.rs rename to cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs index 7e9f21842725ecef79df3305b9f708d3516d8faf..4afd65bdcfea18208046edb4cfe693207cada3bb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs @@ -1,18 +1,17 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// SPDX-License-Identifier: Apache-2.0 -// 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 . +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; @@ -24,14 +23,14 @@ 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; +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; } const MAX_ASSETS: u64 = 100; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { match self { Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), Self::Wild(asset) => match asset { @@ -50,40 +49,38 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { weight.saturating_mul(self.inner().iter().count() as u64) } } -pub struct BridgeHubPolkadotXcmWeight(core::marker::PhantomData); -impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) +pub struct PeopleRococoXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for PeopleRococoXcmWeight { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + // Currently there is no trusted reserve + fn reserve_asset_deposited(_assets: &Assets) -> 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 receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } fn transact( _origin_type: &OriginKind, @@ -111,44 +108,39 @@ impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_parts(1_000_000_000_u64, 0); + let weight = assets.weigh_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 deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmGeneric::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -163,7 +155,7 @@ impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -175,13 +167,13 @@ impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -214,16 +206,16 @@ impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { Weight::MAX } fn set_fees_mode(_: &bool) -> Weight { @@ -235,11 +227,11 @@ impl XcmWeightInfo for BridgeHubPolkadotXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + 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..a10333fdb62651df1d8500f28379710da318d279 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -0,0 +1,326 @@ +// 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::{ + 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 sp_runtime::traits::AccountIdConversion; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, +}; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; + +parameter_types! { + pub const RootLocation: Location = Location::here(); + pub const RelayLocation: Location = Location::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Rococo); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub const GovernanceLocation: Location = Location::parent(); + pub const FellowshipLocation: Location = Location::parent(); + /// The asset ID for the asset that we use to pay for message delivery fees. Just ROC. + pub FeeAssetId: AssetId = AssetId(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: Location = + (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 `Location` 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. +pub type FungibleTransactor = FungibleAdapter< + // 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` `Location` 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, +); + +pub struct LocalPlurality; +impl Contains for LocalPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Plurality { .. }])) + } +} + +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [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 { .. }, + ) | 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 attempts 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 = FungibleTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + // People chain does not recognize a reserve location for any asset. Users must teleport ROC + // where allowed (e.g. with the Relay Chain). + 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; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} + +/// Converts a local signed origin into an XCM location. 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/bridge-hubs/bridge-hub-polkadot/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml similarity index 71% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/Cargo.toml rename to cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 535a0516997733c3849e42ffa9ed4dfa0a78d02e..39cb69e679cc26f2d074626757864cb078d0ded8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -1,91 +1,92 @@ [package] -name = "bridge-hub-polkadot-runtime" +name = "people-westend-runtime" version = "0.1.0" authors.workspace = true edition.workspace = true -description = "Polkadot's BridgeHub parachain runtime" +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.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", optional = true, features = ["derive"] } -smallvec = "1.11.0" +log = { workspace = true } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # 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-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} +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-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} +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 = { 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} +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-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-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } - -[dev-dependencies] -bridge-hub-test-utils = { path = "../test-utils" } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["westend"] } [features] -default = [ "std" ] +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-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", + "enumflags2/std", "frame-benchmarking?/std", "frame-executive/std", "frame-support/std", @@ -98,6 +99,8 @@ 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", @@ -108,7 +111,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "scale-info/std", @@ -119,7 +121,6 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", - "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", @@ -128,6 +129,8 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", + "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -137,6 +140,7 @@ 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", @@ -144,6 +148,8 @@ 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", @@ -159,7 +165,6 @@ 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", @@ -171,6 +176,8 @@ 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", @@ -181,5 +188,3 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/build.rs b/cumulus/parachains/runtimes/people/people-westend/build.rs similarity index 100% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/build.rs rename to cumulus/parachains/runtimes/people/people-westend/build.rs diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs similarity index 70% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/lib.rs rename to cumulus/parachains/runtimes/people/people-westend/src/lib.rs index dc23135f05d7e794a871244fa4f739e38d1f1aa5..b81f7a9c9695d10fa04a8673675ebb52bb7afd22 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -1,53 +1,37 @@ // 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 . +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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")); +pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; -use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; - -use sp_std::prelude::*; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything}, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, TransformOrigin, + }, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -56,29 +40,36 @@ use frame_system::{ EnsureRoot, }; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; -use xcm_config::{ - FellowshipLocation, GovernanceLocation, XcmConfig, XcmOriginToTransactDispatchOrigin, +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, + NORMAL_DISPATCH_RATIO, }; - +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; - -// Polkadot imports -use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; - +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 testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - -use parachains_common::{ - impls::DealWithFees, - polkadot::{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 xcm::latest::prelude::BodyId; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, PriceForSiblingParachainDelivery, XcmConfig, + XcmOriginToTransactDispatchOrigin, }; -// XCM Imports -use xcm::latest::prelude::*; -use xcm_executor::XcmExecutor; /// The address format for describing accounts. pub type Address = MultiAddress; @@ -86,7 +77,7 @@ pub type Address = MultiAddress; /// Block type as expected by this runtime. pub type Block = generic::Block; -/// A Block signed with a Justification +/// A Block signed with an [`sp_runtime::Justification`]. pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. @@ -109,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (pallet_collator_selection::migration::v1::MigrateToV1,); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -129,13 +123,13 @@ impl_opaque_keys! { #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("bridge-hub-polkadot"), - impl_name: create_runtime_str!("bridge-hub-polkadot"), + spec_name: create_runtime_str!("people-westend"), + impl_name: create_runtime_str!("people-westend"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 2, + transaction_version: 0, state_version: 1, }; @@ -167,54 +161,24 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); - pub const SS58Prefix: u8 = 0; + pub const SS58Prefix: u8 = 42; } -// Configure FRAME pallets to include in runtime. - +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { - /// The identifier used to distinguish between accounts. + type BaseCallFilter = Everything; + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// The index type for storing how many extrinsics an account has signed. type Nonce = Nonce; - /// The type for hashing blocks and tries. type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The block type. type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; - /// Runtime version. + type DbWeight = RocksDbWeight; type Version = Version; - /// Converts a module to an index of this module in the runtime. - type PalletInfo = PalletInfo; - /// The data to be stored in an account. type AccountData = pallet_balances::AccountData; - /// What to do if a new account is created. - type OnNewAccount = (); - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); - /// The weight of database operations that the runtime can invoke. - type DbWeight = RocksDbWeight; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; - /// 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>; } @@ -223,7 +187,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -237,10 +201,8 @@ parameter_types! { } impl pallet_balances::Config for Runtime { - /// The type for recording an account's balance. type Balance = Balance; type DustRemoval = (); - /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; @@ -248,14 +210,14 @@ impl pallet_balances::Config for Runtime { 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 + /// Relay Chain `TransactionByteFee` / 10. pub const TransactionByteFee: Balance = MILLICENTS; } @@ -272,6 +234,7 @@ impl pallet_transaction_payment::Config for Runtime { 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 { @@ -279,17 +242,47 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; + 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 CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; + type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; +} + +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + +parameter_types! { + pub MessageQueueServiceWeight: Weight = + Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +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 {} @@ -309,20 +302,14 @@ pub type RootOrFellows = EitherOfDiverse< impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + 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 = NoPriceForMessageDelivery; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; } pub const PERIOD: u32 = 6 * HOURS; @@ -346,9 +333,8 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } parameter_types! { @@ -358,7 +344,7 @@ parameter_types! { pub const StakingAdminBodyId: BodyId = BodyId::Defense; } -/// We allow root, the StakingAdmin to execute privileged collator selection operations. +/// We allow Root and the `StakingAdmi` to execute privileged collator selection operations. pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< EnsureRoot, EnsureXcm>, @@ -404,59 +390,73 @@ impl pallet_utility::Config for Runtime { 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 2, + ParachainInfo: parachain_info = 3, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, - // Collator support. The order of these 4 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, + // 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 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, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + 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::{Pallet, Call, Event} = 40, - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + + // The main stage. + Identity: pallet_identity = 50, + + // To migrate deposits + IdentityMigrator: identity_migrator = 248, } ); -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( + // Substrate [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] [pallet_timestamp, Timestamp] - [pallet_collator_selection, CollatorSelection] + // Polkadot + [polkadot_runtime_common::identity_migrator, IdentityMigrator] + // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_collator_selection, CollatorSelection] // XCM - [pallet_xcm, PolkadotXcm] - // NOTE: Make sure you point to the individual modules below. + [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] ); @@ -465,11 +465,20 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) } } @@ -482,7 +491,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -635,6 +644,7 @@ impl_runtime_apis! { 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 @@ -670,32 +680,67 @@ impl_runtime_apis! { 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 { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported between People and Relay. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + None + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::parent()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } + use xcm::latest::prelude::*; - use xcm_config::DotRelayLocation; + use xcm_config::{PriceForParentDelivery, RelayLocation}; parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( - xcm_config::DotRelayLocation::get(), + pub ExistentialDepositAsset: Option = Some(( + RelayLocation::get(), ExistentialDeposit::get() ).into()); } impl pallet_xcm_benchmarks::Config for Runtime { - type XcmConfig = xcm_config::XcmConfig; + type XcmConfig = XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< XcmConfig, - ExistentialDepositMultiAsset, - xcm_config::PriceForParentDelivery, + ExistentialDepositAsset, + PriceForParentDelivery, >; - fn valid_destination() -> Result { - Ok(DotRelayLocation::get()) + fn valid_destination() -> Result { + Ok(RelayLocation::get()) } - fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> Assets { // just concrete assets according to relay chain. - let assets: Vec = vec![ - MultiAsset { - id: Concrete(DotRelayLocation::get()), + let assets: Vec = vec![ + Asset { + id: AssetId(RelayLocation::get()), fun: Fungible(1_000_000 * UNITS), } ]; @@ -704,12 +749,12 @@ impl_runtime_apis! { } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( - DotRelayLocation::get(), - MultiAsset { fun: Fungible(UNITS), id: Concrete(DotRelayLocation::get()) }, + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + RelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(RelayLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -719,55 +764,62 @@ impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(DotRelayLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(RelayLocation::get()), fun: Fungible(UNITS), } } } impl pallet_xcm_benchmarks::generic::Config for Runtime { - type TransactAsset = Balances; 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> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { Err(BenchmarkError::Skip) } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { - Ok((DotRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((RelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { - Ok(DotRelayLocation::get()) + fn subscribe_origin() -> Result { + Ok(RelayLocation::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { - let origin = DotRelayLocation::get(); - let assets: MultiAssets = (Concrete(DotRelayLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = RelayLocation::get(); + let assets: Assets = (AssetId(RelayLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(RelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { Err(BenchmarkError::Skip) } } 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..a5c0e66a3f882df14cbbd8dba51572834738015e --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/people.rs @@ -0,0 +1,230 @@ +// 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, DAYS}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AccountIdConversion, Verify}, + 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 OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; + 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())] +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 + } +} + +/// A `Default` identity. This is given to users who get a username but have not set an identity. +impl Default for IdentityInfo { + fn default() -> Self { + IdentityInfo { + display: Data::None, + legal: Data::None, + web: Data::None, + matrix: Data::None, + email: Data::None, + pgp_fingerprint: None, + image: Data::None, + twitter: Data::None, + github: Data::None, + discord: Data::None, + } + } +} 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..1a3df158a0d0742d6d721c66f9e6545608e0c88f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_balances.rs @@ -0,0 +1,158 @@ +// 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: `Balances::InactiveIssuance` (r:1 w:0) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1501` + // Minimum execution time: 5_132_000 picoseconds. + Weight::from_parts(5_467_000, 0) + .saturating_add(Weight::from_parts(0, 1501)) + .saturating_add(T::DbWeight::get().reads(1)) + } + 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..1e8ba87e25101ae3104dcf71db8d3872cc22392e --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs @@ -0,0 +1,409 @@ +// 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)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn add_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_873_000 picoseconds. + Weight::from_parts(13_873_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn remove_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_653_000 picoseconds. + Weight::from_parts(10_653_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:1 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_username_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `11037` + // Minimum execution time: 75_928_000 picoseconds. + Weight::from_parts(75_928_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:0 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn accept_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `11037` + // Minimum execution time: 38_157_000 picoseconds. + Weight::from_parts(38_157_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + fn remove_expired_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3542` + // Minimum execution time: 46_821_000 picoseconds. + Weight::from_parts(46_821_000, 0) + .saturating_add(Weight::from_parts(0, 3542)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:0) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_primary_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `247` + // Estimated: `11037` + // Minimum execution time: 22_515_000 picoseconds. + Weight::from_parts(22_515_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:0) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn remove_dangling_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `126` + // Estimated: `11037` + // Minimum execution time: 15_997_000 picoseconds. + Weight::from_parts(15_997_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} 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/bridge-hubs/bridge-hub-kusama/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs similarity index 66% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_xcm.rs rename to cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs index 71bc583077100ecb9edfbca039a9c7b539ff6d66..c337289243b748d721b60421bccf3349044ef194 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs @@ -16,28 +16,26 @@ //! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, 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-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-kusama-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-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=people-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,22 +62,33 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 27_523_000 picoseconds. - Weight::from_parts(28_238_000, 0) + // Minimum execution time: 17_856_000 picoseconds. + Weight::from_parts(18_473_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`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 24_139_000 picoseconds. - Weight::from_parts(24_806_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 56_112_000 picoseconds. + Weight::from_parts(57_287_000, 0) + .saturating_add(Weight::from_parts(0, 3535)) + .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`) @@ -93,6 +102,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// 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` @@ -107,8 +126,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_988_000 picoseconds. - Weight::from_parts(9_227_000, 0) + // Minimum execution time: 6_186_000 picoseconds. + Weight::from_parts(6_420_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +137,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_571_000 picoseconds. - Weight::from_parts(2_667_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_999_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +162,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 33_194_000 picoseconds. - Weight::from_parts(34_089_000, 0) + // Minimum execution time: 23_833_000 picoseconds. + Weight::from_parts(24_636_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -167,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 35_413_000 picoseconds. - Weight::from_parts(36_359_000, 0) + // Minimum execution time: 26_557_000 picoseconds. + Weight::from_parts(27_275_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -179,45 +198,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_679_000 picoseconds. - Weight::from_parts(2_823_000, 0) + // Minimum execution time: 1_921_000 picoseconds. + Weight::from_parts(2_040_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:4 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `95` - // Estimated: `10985` - // Minimum execution time: 15_117_000 picoseconds. - Weight::from_parts(15_603_000, 0) - .saturating_add(Weight::from_parts(0, 10985)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 16_832_000 picoseconds. + Weight::from_parts(17_312_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `10989` - // Minimum execution time: 14_978_000 picoseconds. - Weight::from_parts(15_370_000, 0) - .saturating_add(Weight::from_parts(0, 10989)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 16_687_000 picoseconds. + Weight::from_parts(17_123_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 16_549_000 picoseconds. - Weight::from_parts(16_944_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15946` + // Minimum execution time: 18_164_000 picoseconds. + Weight::from_parts(18_580_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -235,36 +254,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 30_111_000 picoseconds. - Weight::from_parts(30_795_000, 0) + // Minimum execution time: 23_577_000 picoseconds. + Weight::from_parts(24_324_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) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `136` - // Estimated: `8551` - // Minimum execution time: 8_622_000 picoseconds. - Weight::from_parts(8_865_000, 0) - .saturating_add(Weight::from_parts(0, 8551)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11026` + // Minimum execution time: 11_014_000 picoseconds. + Weight::from_parts(11_223_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `10996` - // Minimum execution time: 15_194_000 picoseconds. - Weight::from_parts(15_646_000, 0) - .saturating_add(Weight::from_parts(0, 10996)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 16_887_000 picoseconds. + Weight::from_parts(17_361_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -278,12 +297,50 @@ 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: `112` - // Estimated: `11002` - // Minimum execution time: 36_625_000 picoseconds. - Weight::from_parts(37_571_000, 0) - .saturating_add(Weight::from_parts(0, 11002)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 31_705_000 picoseconds. + Weight::from_parts(32_166_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(10)) .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: 3_568_000 picoseconds. + Weight::from_parts(3_669_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: 24_823_000 picoseconds. + Weight::from_parts(25_344_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 34_516_000 picoseconds. + Weight::from_parts(35_478_000, 0) + .saturating_add(Weight::from_parts(0, 3555)) + .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..b2579230c9ed7afaf9d187a9e783992723444b3a --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -0,0 +1,240 @@ +// 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 WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; +} + +const MAX_ASSETS: u64 = 100; + +impl WeighAssets for AssetFilter { + fn weigh_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 WeighAssets for Assets { + fn weigh_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: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) + } + // Currently there is no trusted reserve + fn reserve_asset_deposited(_assets: &Assets) -> 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: &Assets) -> Weight { + assets.weigh_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: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_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: &InteriorLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + + fn deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_parts(1_000_000_000_u64, 0); + let weight = assets.weigh_assets(XcmFungibleWeight::::deposit_asset()); + hardcoded_weight.min(weight) + } + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &AssetFilter, + _reserve: &Location, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_assets(XcmGeneric::::initiate_reserve_withdraw()) + } + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _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_assets(XcmFungibleWeight::::initiate_teleport()); + hardcoded_weight.min(weight) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &Asset, _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: &Assets, _ticket: &Location) -> 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: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_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(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn request_unlock(_: &Asset, _: &Location) -> 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(_: &Location) -> 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..fee2f5684ac309c623a3914835e0d755953ca52f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -0,0 +1,334 @@ +// 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::{ + 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 sp_runtime::traits::AccountIdConversion; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, +}; +use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; + +parameter_types! { + pub const RootLocation: Location = Location::here(); + pub const RelayLocation: Location = Location::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Westend); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub FellowshipLocation: Location = Location::new(1, Parachain(1001)); + pub const GovernanceLocation: Location = Location::parent(); + /// The asset ID for the asset that we use to pay for message delivery fees. Just WND. + pub FeeAssetId: AssetId = AssetId(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: Location = + (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 `Location` 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. +pub type FungibleTransactor = FungibleAdapter< + // 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` `Location` 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, +); + +pub struct LocalPlurality; +impl Contains for LocalPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Plurality { .. }])) + } +} + +pub struct ParentOrParentsPlurality; +impl Contains for ParentOrParentsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { .. }])) + } +} + +pub struct FellowsPlurality; +impl Contains for FellowsPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [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( + 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 attempts 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 = FungibleTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + // People does not recognize a reserve location for any asset. Users must teleport WND + // where allowed (e.g. with the Relay Chain). + 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; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); +} + +/// Converts a local signed origin into an XCM location. 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 18bee9982d0f62705908c73d06271ad8b4613958..9f08fdf59437cc03336eee449e042eca90ec549a 100644 --- a/cumulus/parachains/runtimes/starters/seedling/Cargo.toml +++ b/cumulus/parachains/runtimes/starters/seedling/Cargo.toml @@ -1,49 +1,53 @@ [package] name = "seedling-runtime" -version = "0.1.0" +version = "0.7.0" description = "Seedling parachain runtime. A starter runtime for solochain to parachain migration." 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"] } # Substrate -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} -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false} -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false} -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false} +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 } +pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } +pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false} -sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", 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-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-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false} -sp-version = { path = "../../../../../substrate/primitives/version", 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-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../../../substrate/primitives/version", 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-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-solo-to-para = { path = "../../../../pallets/solo-to-para", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-timestamp = { path = "../../../../primitives/timestamp", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-aura-ext/std", @@ -75,5 +79,3 @@ std = [ "sp-version/std", "substrate-wasm-builder", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs index c2bcaf8a1266702a0d5c8d6fbbd6463a5d46dc34..2f4f762408febe0e1176cc1d68dd3d314102a082 100644 --- a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs @@ -44,7 +44,7 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, @@ -75,7 +75,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("seedling"), impl_name: create_runtime_str!("seedling"), authoring_version: 1, - spec_version: 10000, + spec_version: 1, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -134,6 +134,8 @@ parameter_types! { .build_or_panic(); pub const SS58Prefix: u8 = 42; } + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; @@ -183,11 +185,13 @@ impl cumulus_pallet_solo_to_para::Config for Runtime { } impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = cumulus_pallet_solo_to_para::Pallet; type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = (); - type DmpMessageHandler = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; type ReservedDmpWeight = (); type XcmpMessageHandler = (); type ReservedXcmpWeight = (); @@ -209,7 +213,6 @@ impl pallet_aura::Config for Runtime { type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; type AllowMultipleBlocksPerSlot = ConstBool; - #[cfg(feature = "experimental")] type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; } @@ -223,17 +226,15 @@ impl pallet_timestamp::Config for Runtime { construct_runtime! { pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - - ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, - }, - ParachainInfo: parachain_info::{Pallet, Storage, Config}, - SoloToPara: cumulus_pallet_solo_to_para::{Pallet, Call, Storage, Event}, - Aura: pallet_aura::{Pallet, Storage, Config}, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config}, + System: frame_system, + Sudo: pallet_sudo, + Timestamp: pallet_timestamp, + + ParachainSystem: cumulus_pallet_parachain_system, + ParachainInfo: parachain_info, + SoloToPara: cumulus_pallet_solo_to_para, + Aura: pallet_aura, + AuraExt: cumulus_pallet_aura_ext, } } @@ -282,7 +283,7 @@ impl_runtime_apis! { } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() } } @@ -295,7 +296,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } diff --git a/cumulus/parachains/runtimes/starters/shell/Cargo.toml b/cumulus/parachains/runtimes/starters/shell/Cargo.toml index ef4b62f985dba6ff2b9cc04774078d2955d63e4a..2f82547afe9a02d4f1b01699869e35aecb526433 100644 --- a/cumulus/parachains/runtimes/starters/shell/Cargo.toml +++ b/cumulus/parachains/runtimes/starters/shell/Cargo.toml @@ -1,52 +1,57 @@ [package] name = "shell-runtime" -version = "0.1.0" +version = "0.7.0" description = "A minimal runtime to test Relay Chain consensus." 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"] } # Substrate -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-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-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false} +pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false} -sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", 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-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-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false} -sp-version = { path = "../../../../../substrate/primitives/version", 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-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../../../substrate/primitives/version", default-features = false } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } # Polkadot -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } # 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-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-aura-ext/std", @@ -58,6 +63,7 @@ std = [ "frame-system/std", "frame-try-runtime?/std", "pallet-aura/std", + "pallet-message-queue/std", "pallet-timestamp/std", "parachain-info/std", "parachains-common/std", @@ -88,9 +94,8 @@ try-runtime = [ "frame-system/try-runtime", "frame-try-runtime/try-runtime", "pallet-aura/try-runtime", + "pallet-message-queue/try-runtime", "pallet-timestamp/try-runtime", "parachain-info/try-runtime", "sp-runtime/try-runtime", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs index 4aad553e6a3baf78667cad1271965d223ca3ea2d..0f4957fd802b441e17fe05f791a7e1dbd047a29e 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs @@ -33,6 +33,7 @@ pub mod xcm_config; use codec::{Decode, Encode}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::AggregateMessageOrigin; use frame_support::unsigned::TransactionValidityError; use scale_info::TypeInfo; use sp_api::impl_runtime_apis; @@ -51,7 +52,7 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, @@ -142,6 +143,7 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; @@ -181,16 +183,17 @@ impl frame_system::Config for Runtime { } parameter_types! { - // We do anything the parent chain tells us in this runtime. - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(2); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); } impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = (); - type DmpMessageHandler = cumulus_pallet_xcm::UnlimitedDmpExecution; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = (); type ReservedXcmpWeight = (); @@ -205,6 +208,32 @@ 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; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type 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; + // These need to be configured to the XCMP pallet - if it is deployed. + type QueueChangeHandler = (); + type QueuePausedQuery = (); + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; +} + impl cumulus_pallet_aura_ext::Config for Runtime {} impl pallet_aura::Config for Runtime { @@ -212,7 +241,6 @@ impl pallet_aura::Config for Runtime { type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; type AllowMultipleBlocksPerSlot = ConstBool; - #[cfg(feature = "experimental")] type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; } @@ -226,19 +254,17 @@ impl pallet_timestamp::Config for Runtime { construct_runtime! { pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + System: frame_system, + Timestamp: pallet_timestamp, - ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, - }, - ParachainInfo: parachain_info::{Pallet, Storage, Config}, + ParachainSystem: cumulus_pallet_parachain_system, + ParachainInfo: parachain_info, - // DMP handler. - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + CumulusXcm: cumulus_pallet_xcm, + MessageQueue: pallet_message_queue, - Aura: pallet_aura::{Pallet, Storage, Config}, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config}, + Aura: pallet_aura, + AuraExt: cumulus_pallet_aura_ext, } } @@ -314,7 +340,7 @@ impl_runtime_apis! { } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() } } @@ -327,7 +353,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } diff --git a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs index ff773ca781612df1e757e343bff49facdad02f41..df89158729cd9935911df3a1bff76557c6d5c900 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs @@ -18,8 +18,8 @@ use super::{ RuntimeOrigin, }; use frame_support::{ - match_types, parameter_types, - traits::{Everything, Nothing}, + parameter_types, + traits::{Contains, Everything, Nothing}, weights::Weight, }; use xcm::latest::prelude::*; @@ -29,9 +29,9 @@ use xcm_builder::{ }; parameter_types! { - pub const RococoLocation: MultiLocation = MultiLocation::parent(); + pub const RococoLocation: Location = Location::parent(); pub const RococoNetwork: Option = Some(NetworkId::Rococo); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); } /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, @@ -47,8 +47,11 @@ pub type XcmOriginToTransactDispatchOrigin = ( ParentAsSuperuser, ); -match_types! { - pub type JustTheParent: impl Contains = { MultiLocation { parents:1, interior: Here } }; +pub struct JustTheParent; +impl Contains for JustTheParent { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [])) + } } parameter_types! { @@ -84,6 +87,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = (); + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } impl cumulus_pallet_xcm::Config for Runtime { diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index 1ecbb212a0e6bdd5a99a31000ff5a09c0dc9e0fd..eda88beb7dabb41bd4075ec5ab6bf8ec2f42d3c8 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -1,43 +1,44 @@ [package] name = "parachains-runtimes-test-utils" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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"] } # Substrate -frame-support = { path = "../../../../substrate/frame/support", default-features = false} -frame-system = { path = "../../../../substrate/frame/system", default-features = false} -pallet-assets = { path = "../../../../substrate/frame/assets", default-features = false} -pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false} -pallet-session = { path = "../../../../substrate/frame/session", default-features = false} -sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false} -sp-io = { path = "../../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../../substrate/primitives/std", default-features = false} +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } +sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } sp-tracing = { path = "../../../../substrate/primitives/tracing" } -sp-core = { path = "../../../../substrate/primitives/core", default-features = false} +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } # Cumulus -cumulus-pallet-parachain-system = { path = "../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook",] } +cumulus-pallet-parachain-system = { path = "../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcmp-queue = { path = "../../../pallets/xcmp-queue", default-features = false } -cumulus-pallet-dmp-queue = { path = "../../../pallets/dmp-queue", default-features = false } pallet-collator-selection = { path = "../../../pallets/collator-selection", default-features = false } -parachains-common = { path = "../../common", default-features = false } -parachain-info = { path = "../../pallets/parachain-info", default-features = false } -assets-common = { path = "../assets/common", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../pallets/parachain-info", default-features = false } cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } cumulus-primitives-parachain-inherent = { path = "../../../primitives/parachain-inherent", default-features = false } cumulus-test-relay-sproof-builder = { path = "../../../test/relay-sproof-builder", default-features = false } # Polkadot -xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false} -pallet-xcm = { path = "../../../../polkadot/xcm/pallet-xcm", default-features = false} -polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false} +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } +pallet-xcm = { path = "../../../../polkadot/xcm/pallet-xcm", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } [dev-dependencies] hex-literal = "0.4.1" @@ -46,10 +47,9 @@ hex-literal = "0.4.1" substrate-wasm-builder = { path = "../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ - "assets-common/std", - "cumulus-pallet-dmp-queue/std", + "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-core/std", @@ -57,13 +57,12 @@ std = [ "cumulus-test-relay-sproof-builder/std", "frame-support/std", "frame-system/std", - "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm/std", "parachain-info/std", - "parachains-common/std", "polkadot-parachain-primitives/std", "sp-consensus-aura/std", "sp-core/std", diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 3215242464aac5177c9931f9974095cdbcee1bd1..e62daa16a1256fa993a97d82d4f6df96f261018b 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -29,24 +29,24 @@ use frame_support::{ weights::Weight, }; use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; -use parachains_common::SLOT_DURATION; use polkadot_parachain_primitives::primitives::{ HeadData, HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat, }; use sp_consensus_aura::{SlotDuration, AURA_ENGINE_ID}; -use sp_core::Encode; -use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem}; +use sp_core::{Encode, U256}; +use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem, SaturatedConversion}; use xcm::{ - latest::{MultiAsset, MultiLocation, XcmContext, XcmHash}, + latest::{Asset, Location, XcmContext, XcmHash}, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, }; -use xcm_executor::{traits::TransactAsset, Assets}; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; 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 +114,55 @@ 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 + + pallet_timestamp::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 +175,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 +211,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:: { @@ -253,8 +261,10 @@ pub struct RuntimeHelper( ); /// Utility function that advances the chain to the desired block number. /// If an author is provided, that author information is injected to all the blocks in the meantime. -impl - RuntimeHelper +impl< + Runtime: frame_system::Config + cumulus_pallet_parachain_system::Config + pallet_timestamp::Config, + AllPalletsWithoutSystem, + > RuntimeHelper where AccountIdOf: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, @@ -290,10 +300,73 @@ where last_header.expect("run_to_block empty block range") } + pub fn run_to_block_with_finalize(n: u32) -> HeaderFor { + let mut last_header = None; + loop { + let block_number = frame_system::Pallet::::block_number(); + if block_number >= n.into() { + break + } + // Set the new block number and author + let header = frame_system::Pallet::::finalize(); + + let pre_digest = Digest { + logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())], + }; + frame_system::Pallet::::reset_events(); + + let next_block_number = block_number + 1u32.into(); + frame_system::Pallet::::initialize( + &next_block_number, + &header.hash(), + &pre_digest, + ); + AllPalletsWithoutSystem::on_initialize(next_block_number); + + let parent_head = HeadData(header.encode()); + let sproof_builder = RelayStateSproofBuilder { + para_id: ::SelfParaId::get(), + included_para_head: parent_head.clone().into(), + ..Default::default() + }; + + let (relay_parent_storage_root, relay_chain_state) = + sproof_builder.into_state_root_and_proof(); + let inherent_data = ParachainInherentData { + validation_data: PersistedValidationData { + parent_head, + relay_parent_number: (block_number.saturated_into::() * 2 + 1).into(), + relay_parent_storage_root, + max_pov_size: 100_000_000, + }, + relay_chain_state, + downward_messages: Default::default(), + horizontal_messages: Default::default(), + }; + + let _ = cumulus_pallet_parachain_system::Pallet::::set_validation_data( + Runtime::RuntimeOrigin::none(), + inherent_data, + ); + let _ = pallet_timestamp::Pallet::::set( + Runtime::RuntimeOrigin::none(), + 300_u32.into(), + ); + AllPalletsWithoutSystem::on_finalize(next_block_number); + let header = frame_system::Pallet::::finalize(); + last_header = Some(header); + } + last_header.expect("run_to_block empty block range") + } + pub fn root_origin() -> ::RuntimeOrigin { ::RuntimeOrigin::root() } + pub fn block_number() -> U256 { + frame_system::Pallet::::block_number().into() + } + pub fn origin_of( account_id: AccountIdOf, ) -> ::RuntimeOrigin { @@ -305,12 +378,12 @@ impl RuntimeHelper { pub fn do_transfer( - from: MultiLocation, - to: MultiLocation, - (asset, amount): (MultiLocation, u128), - ) -> Result { + from: Location, + to: Location, + (asset, amount): (Location, u128), + ) -> Result { ::transfer_asset( - &MultiAsset { id: Concrete(asset), fun: Fungible(amount) }, + &Asset { id: AssetId(asset), fun: Fungible(amount) }, &from, &to, // We aren't able to track the XCM that initiated the fee deposit, so we create a @@ -327,12 +400,13 @@ impl< { pub fn do_teleport_assets( origin: ::RuntimeOrigin, - dest: MultiLocation, - beneficiary: MultiLocation, - (asset, amount): (MultiLocation, u128), + dest: Location, + beneficiary: Location, + (asset, amount): (Location, u128), open_hrmp_channel: Option<(u32, u32)>, included_head: HeaderFor, slot_digest: &[u8], + slot_durations: &SlotDurations, ) -> DispatchResult where HrmpChannelOpener: frame_support::inherent::ProvideInherent< @@ -346,6 +420,7 @@ impl< target_para_id.into(), included_head, slot_digest, + slot_durations, ); } @@ -354,14 +429,14 @@ impl< origin, Box::new(dest.into()), Box::new(beneficiary.into()), - Box::new((Concrete(asset), amount).into()), + Box::new((AssetId(asset), amount).into()), 0, ) } } impl< - Runtime: cumulus_pallet_dmp_queue::Config + cumulus_pallet_parachain_system::Config, + Runtime: cumulus_pallet_parachain_system::Config + pallet_xcm::Config, AllPalletsWithoutSystem, > RuntimeHelper { @@ -377,12 +452,13 @@ impl< ]); // execute xcm as parent origin - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - <::XcmExecutor>::execute_xcm( - MultiLocation::parent(), + let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); + <::XcmExecutor>::prepare_and_execute( + Location::parent(), xcm, - hash, + &mut hash, Self::xcm_max_weight(XcmReceivedFrom::Parent), + Weight::zero(), ) } } @@ -449,7 +525,7 @@ impl< } pub fn assert_metadata( - asset_id: impl Into + Copy, + asset_id: impl Into + Clone, expected_name: &str, expected_symbol: &str, expected_decimals: u8, @@ -457,20 +533,20 @@ pub fn assert_metadata( Fungibles: frame_support::traits::fungibles::metadata::Inspect + frame_support::traits::fungibles::Inspect, { - assert_eq!(Fungibles::name(asset_id.into()), Vec::from(expected_name),); - assert_eq!(Fungibles::symbol(asset_id.into()), Vec::from(expected_symbol),); + assert_eq!(Fungibles::name(asset_id.clone().into()), Vec::from(expected_name),); + assert_eq!(Fungibles::symbol(asset_id.clone().into()), Vec::from(expected_symbol),); assert_eq!(Fungibles::decimals(asset_id.into()), expected_decimals); } pub fn assert_total( - asset_id: impl Into + Copy, + asset_id: impl Into + Clone, expected_total_issuance: impl Into, expected_active_issuance: impl Into, ) where Fungibles: frame_support::traits::fungibles::metadata::Inspect + frame_support::traits::fungibles::Inspect, { - assert_eq!(Fungibles::total_issuance(asset_id.into()), expected_total_issuance.into()); + assert_eq!(Fungibles::total_issuance(asset_id.clone().into()), expected_total_issuance.into()); assert_eq!(Fungibles::active_issuance(asset_id.into()), expected_active_issuance.into()); } @@ -490,12 +566,12 @@ pub fn mock_open_hrmp_channel< recipient: ParaId, included_head: HeaderFor, mut slot_digest: &[u8], + slot_durations: &SlotDurations, ) { - const RELAY_CHAIN_SLOT_DURATION: SlotDuration = SlotDuration::from_millis(6000); let slot = Slot::decode(&mut slot_digest).expect("failed to decode digest"); // Convert para slot to relay chain. - let timestamp = slot.saturating_mul(SLOT_DURATION); - let relay_slot = Slot::from_timestamp(timestamp.into(), RELAY_CHAIN_SLOT_DURATION); + let timestamp = slot.saturating_mul(slot_durations.para.as_millis()); + let relay_slot = Slot::from_timestamp(timestamp.into(), slot_durations.relay); let n = 1_u32; let mut sproof_builder = RelayStateSproofBuilder { diff --git a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs index 4ccb54496b3b1e303e3f4e4b80f4f572202e4dda..1c58df189b673af60bc3e4a9ae7391294287e2aa 100644 --- a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs @@ -37,8 +37,8 @@ pub fn change_storage_constant_by_governance_works: From>, StorageConstant: Get, StorageConstantType: Encode + PartialEq + std::fmt::Debug, @@ -92,3 +92,56 @@ pub fn change_storage_constant_by_governance_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + runtime_call_encode: Box) -> Vec>, + storage_items: Vec<(Vec, Vec)>, + initialize_storage: impl FnOnce() -> (), + assert_storage: impl FnOnce() -> (), +) 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_timestamp::Config, + ValidatorIdOf: From>, +{ + let mut runtime = 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(); + runtime.execute_with(|| { + initialize_storage(); + }); + runtime.execute_with(|| { + // encode `kill_storage` call + let kill_storage_call = runtime_call_encode(frame_system::Call::::set_storage { + items: storage_items.clone(), + }); + + // estimate - storing just 1 value + use frame_system::WeightInfo; + let require_weight_at_most = + ::SystemWeightInfo::set_storage( + storage_items.len().try_into().unwrap(), + ); + + // execute XCM with Transact to `set_storage` as governance does + assert_ok!(RuntimeHelper::::execute_as_governance( + kill_storage_call, + require_weight_at_most + ) + .ensure_complete()); + }); + runtime.execute_with(|| { + assert_storage(); + }); +} diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 13e52f8a3ba624e06fcb04613d576d016db155a1..c18f6571f416beae3b65ffe9b8263696b2ec227c 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "penpal-runtime" -version = "0.9.27" +version = "0.14.0" authors = ["Anonymous"] description = "A parachain for communication back and forth with XCM of assets and uniques." license = "Unlicense" @@ -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"] @@ -17,70 +20,71 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } smallvec = "1.11.0" # 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-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-asset-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", 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} +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-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-asset-tx-payment = { path = "../../../../../substrate/frame/transaction-payment/asset-tx-payment", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", 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} +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 -polkadot-primitives = { path = "../../../../../polkadot/primitives", default-features = false} -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", 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} +polkadot-primitives = { path = "../../../../../polkadot/primitives", default-features = false } +pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", 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 = "../../../../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} +pallet-message-queue = { path = "../../../../../substrate/frame/message-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 = { path = "../../../pallets/parachain-info", 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 } [features] -default = [ "std" ] +default = ["std"] std = [ + "assets-common/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", @@ -101,6 +105,7 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-collator-selection/std", + "pallet-message-queue/std", "pallet-session/std", "pallet-sudo/std", "pallet-timestamp/std", @@ -133,9 +138,11 @@ std = [ ] runtime-benchmarks = [ + "assets-common/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", @@ -146,6 +153,7 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", @@ -160,7 +168,6 @@ 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", @@ -174,6 +181,7 @@ try-runtime = [ "pallet-authorship/try-runtime", "pallet-balances/try-runtime", "pallet-collator-selection/try-runtime", + "pallet-message-queue/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", @@ -183,5 +191,3 @@ try-runtime = [ "polkadot-runtime-common/try-runtime", "sp-runtime/try-runtime", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 86389425eb5e4e89fe9207ced770c68f1ed11507..1e6d485d148d356920c3069fc1017b5f5d703d28 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -33,14 +33,16 @@ mod weights; pub mod xcm_config; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, pallet_prelude::Weight, parameter_types, - traits::{AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything}, + traits::{ + AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Everything, TransformOrigin, + }, weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, FeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, @@ -51,6 +53,10 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, EnsureSigned, }; +use parachains_common::{ + impls::{AssetsToBlockAuthor, NonZeroIssuance}, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, +}; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; use smallvec::smallvec; use sp_api::impl_runtime_apis; @@ -67,7 +73,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use xcm_config::{AssetsToBlockAuthor, XcmConfig, XcmOriginToTransactDispatchOrigin}; +use xcm_config::XcmOriginToTransactDispatchOrigin; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -80,7 +86,6 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; // XCM Imports use parachains_common::{AccountId, Signature}; use xcm::latest::prelude::BodyId; -use xcm_executor::XcmExecutor; /// Balance of an account. pub type Balance = u128; @@ -228,7 +233,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("penpal-parachain"), impl_name: create_runtime_str!("penpal-parachain"), authoring_version: 1, - spec_version: 10000, + spec_version: 1, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -321,6 +326,7 @@ parameter_types! { // Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; @@ -399,8 +405,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -431,7 +437,7 @@ parameter_types! { // pub type AssetsForceOrigin = // EnsureOneOf, EnsureXcm>>; -impl pallet_assets::Config for Runtime { +impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type AssetId = AssetId; @@ -454,16 +460,53 @@ 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 = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; + 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); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; } impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; - type DmpMessageHandler = DmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; type ReservedDmpWeight = ReservedDmpWeight; type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; @@ -479,26 +522,42 @@ 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; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + 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 {} impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = PolkadotXcm; - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = (); type PriceForSiblingDelivery = NoPriceForMessageDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = EnsureRoot; -} - parameter_types! { pub const Period: u32 = 6 * HOURS; pub const Offset: u32 = 0; @@ -523,7 +582,6 @@ impl pallet_aura::Config for Runtime { type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; type AllowMultipleBlocksPerSlot = ConstBool; - #[cfg(feature = "experimental")] type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; } @@ -556,8 +614,13 @@ impl pallet_asset_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Fungibles = Assets; type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< - pallet_assets::BalanceToAssetBalance, - AssetsToBlockAuthor, + pallet_assets::BalanceToAssetBalance< + Balances, + Runtime, + ConvertInto, + pallet_assets::Instance1, + >, + AssetsToBlockAuthor, >; } @@ -572,51 +635,48 @@ 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, + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 2, + ParachainInfo: parachain_info = 3, // Monetary stuff. - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, - AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event} = 12, + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + AssetTxPayment: pallet_asset_tx_payment = 12, // Collator support. The order of these 4 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, + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 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, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 33, + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + MessageQueue: pallet_message_queue = 34, // The main stage. - Assets: pallet_assets::{Pallet, Call, Storage, Event} = 50, + Assets: pallet_assets:: = 50, + ForeignAssets: pallet_assets:: = 51, - Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config} = 255, + Sudo: pallet_sudo = 255, } ); -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - #[cfg(feature = "runtime-benchmarks")] mod benches { - define_benchmarks!( + frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_session, SessionBench::] [pallet_sudo, Sudo] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] ); } @@ -628,7 +688,7 @@ impl_runtime_apis! { } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() } } @@ -641,7 +701,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } 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 710dfd79877cb1e26e50a228584791d3caa21165..d83a877c2f89b27c783bd19f19d75736a6dd0022 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -23,45 +23,45 @@ //! `ReserveAssetTransferDeposited` message but that will but the intension will be to support this //! soon. use super::{ - AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Balance, Balances, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Authorship, Balance, + Balances, ForeignAssets, ForeignAssetsInstance, NonZeroIssuance, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, + XcmpQueue, }; use core::marker::PhantomData; use frame_support::{ - match_types, parameter_types, - traits::{ - fungibles::{self, Balanced, Credit}, - ConstU32, Contains, ContainsPair, Everything, Get, Nothing, - }, + parameter_types, + traits::{ConstU32, Contains, ContainsPair, Everything, EverythingBut, Get, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; -use pallet_asset_tx_payment::HandleCredit; use pallet_xcm::XcmPassthrough; +use parachains_common::xcm_config::AssetFeeAsExistentialDepositMultiplier; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; -use sp_runtime::traits::Zero; +use sp_runtime::traits::ConvertInto; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, - EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, EnsureXcmOrigin, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, + LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::JustTry, XcmExecutor}; parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayLocation: Location = Location::parent(); + // Local native currency which is stored in `pallet_balances`` + pub const PenpalNativeCurrency: Location = Location::here(); pub const RelayNetwork: Option = None; pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -74,12 +74,12 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + IsConcrete, + // Do a simple punn to convert an AccountId32 Location into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -92,13 +92,25 @@ 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, - >, - // Convert an XCM MultiLocation into a local account id: + ( + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex, + JustTry, + >, + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex< + SystemAssetHubAssetsPalletLocation, + AssetIdPalletAssets, + JustTry, + >, + JustTry, + >, + ), + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -109,8 +121,35 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; +pub type ForeignAssetsConvertedConcreteId = assets_common::LocationConvertedConcreteId< + EverythingBut<( + // Here we rely on fact that something like this works: + // assert!(Location::new(1, + // [Parachain(100)]).starts_with(&Location::parent())); + // assert!([Parachain(100)].into().starts_with(&Here)); + StartsWith, + )>, + 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 Location 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 @@ -126,6 +165,9 @@ pub type XcmOriginToTransactDispatchOrigin = ( // 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, @@ -138,137 +180,119 @@ parameter_types! { pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; + pub XcmAssetFeesReceiver: Option = Authorship::author(); } -match_types! { - pub type ParentOrParentsExecutivePlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Executive, .. }) } - }; - pub type CommonGoodAssetsParachain: impl Contains = { - MultiLocation { parents: 1, interior: X1(Parachain(1000)) } - }; +pub struct ParentOrParentsExecutivePlurality; +impl Contains for ParentOrParentsExecutivePlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { id: BodyId::Executive, .. }])) + } } -pub type Barrier = TrailingSetTopicAsId< - DenyThenTry< - DenyReserveTransferToRelayChain, +pub type Barrier = TrailingSetTopicAsId<( + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + // Allow XCMs with some computed origins to pass through. + WithComputedOrigin< ( - TakeWeightCredit, - // Expected responses are OK. - AllowKnownQueryResponses, - // Allow XCMs with some computed origins to pass through. - WithComputedOrigin< - ( - // If the message is one that immediately attempts to pay for execution, then - // allow it. - AllowTopLevelPaidExecutionFrom, - // System Assets parachain, parent and its exec plurality get free - // execution - AllowExplicitUnpaidExecutionFrom<( - CommonGoodAssetsParachain, - ParentOrParentsExecutivePlurality, - )>, - // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, - ), - UniversalLocation, - ConstU32<8>, - >, + // If the message is one that immediately attempts to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, ), + UniversalLocation, + ConstU32<8>, >, ->; +)>; /// Type alias to conveniently refer to `frame_system`'s `Config::AccountId`. pub type AccountIdOf = ::AccountId; -/// Asset filter that allows all assets from a certain location. -pub struct AssetsFrom(PhantomData); -impl> ContainsPair for AssetsFrom { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { - let loc = T::get(); - &loc == origin && - matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } - if asset_loc.match_and_split(&loc).is_some()) - } -} - -/// Allow checking in assets that have issuance > 0. -pub struct NonZeroIssuance(PhantomData<(AccountId, Assets)>); -impl Contains<>::AssetId> - for NonZeroIssuance +/// Asset filter that allows all assets from a certain location matching asset id. +pub struct AssetPrefixFrom(PhantomData<(Prefix, Origin)>); +impl ContainsPair for AssetPrefixFrom where - Assets: fungibles::Inspect, + Prefix: Get, + Origin: Get, { - fn contains(id: &>::AssetId) -> bool { - !Assets::total_issuance(id.clone()).is_zero() - } -} - -/// A `HandleCredit` implementation that naively transfers the fees to the block author. -/// Will drop and burn the assets in case the transfer fails. -pub struct AssetsToBlockAuthor(PhantomData); -impl HandleCredit, pallet_assets::Pallet> for AssetsToBlockAuthor -where - R: pallet_authorship::Config + pallet_assets::Config, - AccountIdOf: From + Into, -{ - fn handle_credit(credit: Credit, pallet_assets::Pallet>) { - if let Some(author) = pallet_authorship::Pallet::::author() { - // In case of error: Will drop the result triggering the `OnDrop` of the imbalance. - let _ = pallet_assets::Pallet::::resolve(&author, credit); - } + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = Origin::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if asset_loc.starts_with(&Prefix::get())) } } -pub trait Reserve { - /// Returns assets reserve location. - fn reserve(&self) -> Option; -} +type AssetsFrom = AssetPrefixFrom; -// Takes the chain part of a MultiAsset -impl Reserve for MultiAsset { - fn reserve(&self) -> Option { - if let AssetId::Concrete(location) = self.id { - let first_interior = location.first_interior(); - let parents = location.parent_count(); - match (parents, first_interior) { - (0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))), - (1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))), - (1, _) => Some(MultiLocation::parent()), - _ => None, - } - } else { - None - } +/// Asset filter that allows native/relay asset if coming from a certain location. +pub struct NativeAssetFrom(PhantomData); +impl> ContainsPair for NativeAssetFrom { + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = T::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if *asset_loc == Location::from(Parent)) } } -/// A `FilterAssetLocation` implementation. Filters multi native assets whose -/// reserve is same with `origin`. -pub struct MultiNativeAsset; -impl ContainsPair for MultiNativeAsset { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { - if let Some(ref reserve) = asset.reserve() { - if reserve == origin { - return true - } - } - false - } -} +// This asset can be added to AH as Asset and reserved transfer between Penpal and AH +pub const RESERVABLE_ASSET_ID: u32 = 1; +// This asset can be added to AH as ForeignAsset and teleported between Penpal and AH +pub const TELEPORTABLE_ASSET_ID: u32 = 2; + +pub const ASSETS_PALLET_ID: u8 = 50; +pub const ASSET_HUB_ID: u32 = 1000; parameter_types! { /// The location that this chain recognizes as the Relay network's Asset Hub. - pub SystemAssetHubLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000))); - // ALWAYS ensure that the index in PalletInstance stays up-to-date with + pub SystemAssetHubLocation: Location = Location::new(1, [Parachain(ASSET_HUB_ID)]); // the Relay Chain's Asset Hub's Assets pallet index - pub SystemAssetHubAssetsPalletLocation: MultiLocation = - MultiLocation::new(1, X2(Parachain(1000), PalletInstance(50))); + pub SystemAssetHubAssetsPalletLocation: Location = + Location::new(1, [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID)]); + pub AssetsPalletLocation: Location = + Location::new(0, [PalletInstance(ASSETS_PALLET_ID)]); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub LocalTeleportableToAssetHub: Location = Location::new( + 0, + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(TELEPORTABLE_ASSET_ID.into())] + ); + pub LocalReservableFromAssetHub: Location = Location::new( + 1, + [Parachain(ASSET_HUB_ID), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())] + ); + + /// The Penpal runtime is utilized for testing with various environment setups. + /// This storage item provides the opportunity to customize testing scenarios + /// by configuring the trusted asset from the `SystemAssetHub`. + /// + /// By default, it is configured as a `SystemAssetHubLocation` and can be modified using `System::set_storage`. + pub storage CustomizableAssetFromSystemAssetHub: Location = SystemAssetHubLocation::get(); } -pub type Reserves = (NativeAsset, AssetsFrom); +/// 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: &Asset, origin: &Location) -> bool { + log::trace!(target: "xcm::contains", "AssetFromChain asset: {:?}, origin: {:?}", asset, origin); + *origin == Origin::get() && + matches!(asset.id.clone(), AssetId(id) if id == AssetLocation::get()) + } +} + +pub type TrustedReserves = ( + NativeAsset, + AssetsFrom, + NativeAssetFrom, + AssetPrefixFrom, +); +pub type TrustedTeleporters = + (AssetFromChain,); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -277,13 +301,28 @@ impl xcm_executor::Config for XcmConfig { // How to withdraw and deposit an asset. type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = MultiNativeAsset; // TODO: maybe needed to be replaced by Reserves - type IsTeleporter = NativeAsset; + type IsReserve = TrustedReserves; + // no teleport trust established with other chains + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; - type Trader = - UsingComponents>; + type Trader = ( + UsingComponents>, + // This trader allows to pay with `is_sufficient=true` "Foreign" assets from dedicated + // `pallet_assets` instance - `ForeignAssets`. + cumulus_primitives_utility::TakeFirstAssetTrader< + AccountId, + ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, + ForeignAssetsConvertedConcreteId, + ForeignAssets, + cumulus_primitives_utility::XcmFeesTo32ByteAccount< + ForeignFungiblesTransactor, + AccountId, + XcmAssetFeesReceiver, + >, + >, + ); type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; @@ -298,8 +337,21 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } +/// Multiplier used for dedicated `TakeFirstAssetTrader` with `ForeignAssets` instance. +pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = + AssetFeeAsExistentialDepositMultiplier< + Runtime, + WeightToFee, + pallet_assets::BalanceToAssetBalance, + ForeignAssetsInstance, + >; + /// No local origins on this chain are allowed to dispatch XCM sends/executions. pub type LocalOriginToLocation = SignedToAccountId32; @@ -312,11 +364,6 @@ pub type XcmRouter = WithUniqueTopic<( XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -342,8 +389,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -353,3 +398,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) -> xcm::v3::Location { + xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) + } +} diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index 616d92b6940a6ca92ebe2813b984105cd39e42fb..577ed749167f0129100a586610658701a3442eab 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -1,74 +1,79 @@ [package] name = "rococo-parachain-runtime" -version = "0.1.0" +version = "0.6.0" authors.workspace = true edition.workspace = true description = "Simple runtime used by the rococo parachain(s)" -publish = false +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"] } # 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-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false} -pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false} -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false} -pallet-balances = { path = "../../../../../substrate/frame/balances", 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} -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} +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-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } +pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } +pallet-balances = { path = "../../../../../substrate/frame/balances", 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 } +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-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false} -sp-version = { path = "../../../../../substrate/primitives/version", 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-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} -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} -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false} +pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } +polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", 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",] } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } cumulus-ping = { path = "../../../pallets/ping", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } parachains-common = { path = "../../../common", default-features = false } -parachain-info = { path = "../../../pallets/parachain-info", default-features = false } +testnet-parachains-constants = { path = "../../constants", default-features = false, features = ["rococo"] } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-aura-ext/std", - "cumulus-pallet-dmp-queue/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-ping/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -79,6 +84,7 @@ std = [ "pallet-assets/std", "pallet-aura/std", "pallet-balances/std", + "pallet-message-queue/std", "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", @@ -102,6 +108,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "testnet-parachains-constants/std", "xcm-builder/std", "xcm-executor/std", "xcm/std", @@ -109,12 +116,14 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/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/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", @@ -125,5 +134,3 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] - -experimental = [ "pallet-aura/experimental" ] diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index dcea349f3a0e797293fbe2a884e4ec696485caba..1b7efa6f400a8cd0f71397ddbe2113de3290fcca 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -22,14 +22,13 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::ParaId; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; use sp_api::impl_runtime_apis; use sp_core::OpaqueMetadata; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, Hash as HashT}, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; @@ -40,13 +39,13 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, - match_types, parameter_types, + parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Everything, - IsInVec, Nothing, Randomness, + AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse, + Everything, IsInVec, Nothing, Randomness, }, weights::{ constants::{ @@ -67,13 +66,17 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::traits::TransformOrigin; use parachains_common::{ impls::{AssetsFrom, NonZeroIssuance}, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AssetIdForTrustBackedAssets, Signature, }; use xcm_builder::{ AllowKnownQueryResponses, AllowSubscriptionsFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, - FungiblesAdapter, LocalMint, TrailingSetTopicAsId, WithUniqueTopic, + FrameTransactionalProcessor, FungiblesAdapter, LocalMint, TrailingSetTopicAsId, + WithUniqueTopic, }; use xcm_executor::traits::JustTry; @@ -83,7 +86,7 @@ use polkadot_parachain_primitives::primitives::Sibling; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, - CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, + EnsureXcmOrigin, FixedWeightBounds, FungibleAdapter, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, @@ -104,14 +107,14 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("test-parachain"), impl_name: create_runtime_str!("test-parachain"), authoring_version: 1, - spec_version: 10000, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, state_version: 0, }; -pub const MILLISECS_PER_BLOCK: u64 = 12000; +pub const MILLISECS_PER_BLOCK: u64 = 6000; pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; @@ -141,18 +144,18 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for .5 seconds of compute with a 12 second average block time. +/// We allow for 2 seconds of compute with a 6 second average block time. const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_div(2), + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, ); /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included /// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; +const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the /// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 1; +const BLOCK_PROCESSING_VELOCITY: u32 = 2; /// Relay chain slot duration, in milliseconds. const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; @@ -182,6 +185,7 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; @@ -224,7 +228,7 @@ 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 MinimumPeriod = ConstU64<0>; type WeightInfo = (); } @@ -248,8 +252,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -271,39 +275,64 @@ impl pallet_sudo::Config for Runtime { 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; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type SelfParaId = parachain_info::Pallet; type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; + 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 CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } 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 = (); + 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! { - pub const RocLocation: MultiLocation = MultiLocation::parent(); + pub const RocLocation: Location = Location::parent(); pub const RococoNetwork: Option = Some(NetworkId::Rococo); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UniversalLocation: InteriorMultiLocation = X1(Parachain(ParachainInfo::parachain_id().into())); + pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// Type for specifying how a `Location` 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 = ( @@ -316,12 +345,12 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: + // Do a simple punn to convert an AccountId32 Location into a native chain account ID: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -344,7 +373,7 @@ pub type FungiblesTransactor = FungiblesAdapter< >, JustTry, >, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -385,20 +414,22 @@ parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); // One ROC buys 1 second of weight. - pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), ROC); + pub const WeightPrice: (Location, u128) = (Location::parent(), ROC); pub const MaxInstructions: u32 = 100; } -match_types! { - // The parent or the parent's unit plurality. - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } - }; - // The location recognized as the Relay network's Asset Hub. - pub type AssetHub: impl Contains = { - MultiLocation { parents: 1, interior: X1(Parachain(1000)) } - }; +pub struct ParentOrParentsUnitPlurality; +impl Contains for ParentOrParentsUnitPlurality { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, []) | (1, [Plurality { id: BodyId::Unit, .. }])) + } +} + +pub struct AssetHub; +impl Contains for AssetHub { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (1, [Parachain(1000)])) + } } pub type Barrier = TrailingSetTopicAsId<( @@ -416,11 +447,11 @@ pub type Barrier = TrailingSetTopicAsId<( parameter_types! { pub MaxAssetsIntoHolding: u32 = 64; - pub SystemAssetHubLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000))); + pub SystemAssetHubLocation: Location = Location::new(1, [Parachain(1000)]); // ALWAYS ensure that the index in PalletInstance stays up-to-date with // the Relay Chain's Asset Hub's Assets pallet index - pub SystemAssetHubAssetsPalletLocation: MultiLocation = - MultiLocation::new(1, X2(Parachain(1000), PalletInstance(50))); + pub SystemAssetHubAssetsPalletLocation: Location = + Location::new(1, [Parachain(1000), PalletInstance(50)]); } pub type Reserves = (NativeAsset, AssetsFrom); @@ -452,6 +483,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } /// Local origins on this chain are allowed to dispatch XCM sends/executions. @@ -466,11 +501,6 @@ pub type XcmRouter = WithUniqueTopic<( XcmpQueue, )>; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -492,8 +522,6 @@ impl pallet_xcm::Config for Runtime { type SovereignAccountOf = LocationToAccountId; type MaxLockers = ConstU32<8>; type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); @@ -506,22 +534,17 @@ impl cumulus_pallet_xcm::Config for Runtime { impl cumulus_pallet_xcmp_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; type ChannelInfo = ParachainSystem; type VersionWrapper = (); - type ExecuteOverweightOrigin = EnsureRoot; + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; type ControllerOrigin = EnsureRoot; type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; type WeightInfo = cumulus_pallet_xcmp_queue::weights::SubstrateWeight; type PriceForSiblingDelivery = NoPriceForMessageDelivery; } -impl cumulus_pallet_dmp_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; -} - impl cumulus_ping::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeOrigin = RuntimeOrigin; @@ -570,37 +593,35 @@ 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; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; } construct_runtime! { pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + System: frame_system, + Timestamp: pallet_timestamp, + Sudo: pallet_sudo, + TransactionPayment: pallet_transaction_payment, - ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, - } = 20, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, + ParachainSystem: cumulus_pallet_parachain_system = 20, + ParachainInfo: parachain_info = 21, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, - Assets: pallet_assets::{Pallet, Call, Storage, Event} = 31, + Balances: pallet_balances = 30, + Assets: pallet_assets = 31, - Aura: pallet_aura::{Pallet, Config}, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Config}, + Aura: pallet_aura, + AuraExt: cumulus_pallet_aura_ext, // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin, Config} = 51, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, + XcmpQueue: cumulus_pallet_xcmp_queue = 50, + PolkadotXcm: pallet_xcm = 51, + CumulusXcm: cumulus_pallet_xcm = 52, + // RIP DmpQueue 53 + MessageQueue: pallet_message_queue = 54, - Spambot: cumulus_ping::{Pallet, Call, Storage, Event} = 99, + Spambot: cumulus_ping = 99, } } @@ -609,7 +630,7 @@ pub type Balance = u128; /// Index of a transaction in the chain. pub type Nonce = u32; /// A hash of some data used by the chain. -pub type Hash = sp_core::H256; +pub type Hash = ::Output; /// An index to a block. pub type BlockNumber = u32; /// The address format for describing accounts. @@ -667,7 +688,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -740,7 +761,7 @@ impl_runtime_apis! { } fn authorities() -> Vec { - Aura::authorities().into_inner() + pallet_aura::Authorities::::get().into_inner() } } @@ -809,6 +830,15 @@ impl_runtime_apis! { build_config::(config) } } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } } cumulus_pallet_parachain_system::register_validate_block! { diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 3c2069c81ef4f58e8bd4747fbec917f17bd34a56..70423856d34748df76f98d58749d6a27c2599c48 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -1,51 +1,60 @@ [package] name = "polkadot-parachain-bin" -version = "1.1.0" +version = "4.0.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.6", features = ["derive"] } +async-trait = "0.1.74" +clap = { version = "4.5.3", 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.188", features = ["derive"] } -serde_json = "1.0.107" +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } # Local rococo-parachain-runtime = { path = "../parachains/runtimes/testing/rococo-parachain" } shell-runtime = { path = "../parachains/runtimes/starters/shell" } -glutton-runtime = { path = "../parachains/runtimes/glutton/glutton-kusama" } +glutton-westend-runtime = { path = "../parachains/runtimes/glutton/glutton-westend" } seedling-runtime = { path = "../parachains/runtimes/starters/seedling" } -asset-hub-polkadot-runtime = { path = "../parachains/runtimes/assets/asset-hub-polkadot" } -asset-hub-kusama-runtime = { path = "../parachains/runtimes/assets/asset-hub-kusama" } asset-hub-rococo-runtime = { path = "../parachains/runtimes/assets/asset-hub-rococo" } asset-hub-westend-runtime = { path = "../parachains/runtimes/assets/asset-hub-westend" } -collectives-polkadot-runtime = { path = "../parachains/runtimes/collectives/collectives-polkadot" } +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" } -bridge-hub-kusama-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-kusama" } -bridge-hub-polkadot-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-polkadot" } +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" } -jsonrpsee = { version = "0.16.2", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } +people-rococo-runtime = { path = "../parachains/runtimes/people/people-rococo" } +people-westend-runtime = { path = "../parachains/runtimes/people/people-westend" } parachains-common = { path = "../parachains/common" } +testnet-parachains-constants = { path = "../parachains/runtimes/constants", default-features = false, features = ["rococo", "westend"] } # Substrate frame-benchmarking = { path = "../../substrate/frame/benchmarking" } frame-benchmarking-cli = { path = "../../substrate/utils/frame/benchmarking-cli" } -sp-runtime = { path = "../../substrate/primitives/runtime", default-features = false} +sp-runtime = { path = "../../substrate/primitives/runtime", default-features = false } sp-io = { path = "../../substrate/primitives/io" } sp-core = { path = "../../substrate/primitives/core" } sp-session = { path = "../../substrate/primitives/session" } +frame-try-runtime = { path = "../../substrate/frame/try-runtime", optional = true } sc-consensus = { path = "../../substrate/client/consensus/common" } +sp-tracing = { path = "../../substrate/primitives/tracing" } +frame-support = { path = "../../substrate/frame/support" } sc-cli = { path = "../../substrate/client/cli" } sc-client-api = { path = "../../substrate/client/api" } sc-executor = { path = "../../substrate/client/executor" } @@ -58,12 +67,19 @@ sc-network-sync = { path = "../../substrate/client/network/sync" } sc-basic-authorship = { path = "../../substrate/client/basic-authorship" } sp-timestamp = { path = "../../substrate/primitives/timestamp" } sp-blockchain = { path = "../../substrate/primitives/blockchain" } +sp-genesis-builder = { path = "../../substrate/primitives/genesis-builder", default-features = false } sp-block-builder = { path = "../../substrate/primitives/block-builder" } sp-keystore = { path = "../../substrate/primitives/keystore" } sc-chain-spec = { path = "../../substrate/client/chain-spec" } sc-rpc = { path = "../../substrate/client/rpc" } +sp-version = { path = "../../substrate/primitives/version" } sc-tracing = { path = "../../substrate/client/tracing" } sp-offchain = { path = "../../substrate/primitives/offchain" } +frame-system-rpc-runtime-api = { path = "../../substrate/frame/system/rpc/runtime-api" } +pallet-transaction-payment = { path = "../../substrate/frame/transaction-payment" } +pallet-transaction-payment-rpc-runtime-api = { path = "../../substrate/frame/transaction-payment/rpc/runtime-api" } +sp-std = { path = "../../substrate/primitives/std" } +sp-inherents = { path = "../../substrate/primitives/inherents" } sp-api = { path = "../../substrate/primitives/api" } sp-consensus-aura = { path = "../../substrate/primitives/consensus/aura" } sc-sysinfo = { path = "../../substrate/client/sysinfo" } @@ -87,10 +103,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" @@ -101,26 +117,29 @@ substrate-build-script-utils = { path = "../../substrate/utils/build-script-util assert_cmd = "2.0" nix = { version = "0.26.1", features = ["signal"] } tempfile = "3.8.0" -tokio = { version = "1.32.0", features = ["macros", "time", "parking_lot"] } +tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] } wait-timeout = "0.2" [features] default = [] runtime-benchmarks = [ - "asset-hub-kusama-runtime/runtime-benchmarks", - "asset-hub-polkadot-runtime/runtime-benchmarks", "asset-hub-rococo-runtime/runtime-benchmarks", "asset-hub-westend-runtime/runtime-benchmarks", - "bridge-hub-kusama-runtime/runtime-benchmarks", - "bridge-hub-polkadot-runtime/runtime-benchmarks", "bridge-hub-rococo-runtime/runtime-benchmarks", - "collectives-polkadot-runtime/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", - "glutton-runtime/runtime-benchmarks", + "frame-support/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", @@ -129,19 +148,26 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "asset-hub-kusama-runtime/try-runtime", - "asset-hub-polkadot-runtime/try-runtime", "asset-hub-rococo-runtime/try-runtime", "asset-hub-westend-runtime/try-runtime", - "bridge-hub-kusama-runtime/try-runtime", - "bridge-hub-polkadot-runtime/try-runtime", "bridge-hub-rococo-runtime/try-runtime", - "collectives-polkadot-runtime/try-runtime", + "bridge-hub-westend-runtime/try-runtime", + "collectives-westend-runtime/try-runtime", "contracts-rococo-runtime/try-runtime", - "glutton-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", "sp-runtime/try-runtime", ] +fast-runtime = [ + "bridge-hub-rococo-runtime/fast-runtime", +] diff --git a/cumulus/polkadot-parachain/chain-specs/asset-hub-wococo.json b/cumulus/polkadot-parachain/chain-specs/asset-hub-wococo.json deleted file mode 120000 index 30a63a9fc786bd542e38c2dd2cf769dc4ebde3bf..0000000000000000000000000000000000000000 --- a/cumulus/polkadot-parachain/chain-specs/asset-hub-wococo.json +++ /dev/null @@ -1 +0,0 @@ -../../parachains/chain-specs/asset-hub-wococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/bridge-hub-wococo.json b/cumulus/polkadot-parachain/chain-specs/bridge-hub-wococo.json deleted file mode 120000 index e13ab77265d59d31fcf822420413d3b5240bf574..0000000000000000000000000000000000000000 --- a/cumulus/polkadot-parachain/chain-specs/bridge-hub-wococo.json +++ /dev/null @@ -1 +0,0 @@ -../../parachains/chain-specs/bridge-hub-wococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json new file mode 120000 index 0000000000000000000000000000000000000000..6e47a6ad08cc787e2a66f6426b1516769779e995 --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-westend.json b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json new file mode 120000 index 0000000000000000000000000000000000000000..80dd771db54fc6067f19fadd883dd2274fb21f9d --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-westend.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/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/asset_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs index 636488cbfe2cd6f7f510e65bedbb473e920c7126..45920cdb6146b01765fb568506ef861c220b5792 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs @@ -15,49 +15,17 @@ // along with Cumulus. If not, see . use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, }; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::{AccountId, AssetHubPolkadotAuraId, AuraId, Balance as AssetHubBalance}; +use parachains_common::{AccountId, AuraId, Balance as AssetHubBalance}; use sc_service::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; -/// Specialized `ChainSpec` for the normal parachain runtime. -pub type AssetHubPolkadotChainSpec = - sc_service::GenericChainSpec; -pub type AssetHubKusamaChainSpec = - sc_service::GenericChainSpec; -pub type AssetHubWestendChainSpec = - sc_service::GenericChainSpec; -pub type AssetHubRococoChainSpec = - sc_service::GenericChainSpec; -pub type AssetHubWococoChainSpec = AssetHubRococoChainSpec; - -const ASSET_HUB_POLKADOT_ED: AssetHubBalance = - parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; -const ASSET_HUB_KUSAMA_ED: AssetHubBalance = - parachains_common::kusama::currency::EXISTENTIAL_DEPOSIT; -const ASSET_HUB_WESTEND_ED: AssetHubBalance = - parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; -const ASSET_HUB_ROCOCO_ED: AssetHubBalance = - parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn asset_hub_polkadot_session_keys( - keys: AssetHubPolkadotAuraId, -) -> asset_hub_polkadot_runtime::SessionKeys { - asset_hub_polkadot_runtime::SessionKeys { aura: keys } -} - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn asset_hub_kusama_session_keys(keys: AuraId) -> asset_hub_kusama_runtime::SessionKeys { - asset_hub_kusama_runtime::SessionKeys { aura: keys } -} +const ASSET_HUB_WESTEND_ED: AssetHubBalance = asset_hub_westend_runtime::ExistentialDeposit::get(); +const ASSET_HUB_ROCOCO_ED: AssetHubBalance = asset_hub_rococo_runtime::ExistentialDeposit::get(); /// Generate the session keys from individual elements. /// @@ -73,567 +41,152 @@ pub fn asset_hub_westend_session_keys(keys: AuraId) -> asset_hub_westend_runtime asset_hub_westend_runtime::SessionKeys { aura: keys } } -pub fn asset_hub_polkadot_development_config() -> AssetHubPolkadotChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); - - AssetHubPolkadotChainSpec::from_genesis( - // Name - "Polkadot Asset Hub Development", - // ID - "asset-hub-polkadot-dev", - ChainType::Local, - move || { - asset_hub_polkadot_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "polkadot-dev".into(), para_id: 1000 }, - ) -} - -pub fn asset_hub_polkadot_local_config() -> AssetHubPolkadotChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); - - AssetHubPolkadotChainSpec::from_genesis( - // Name - "Polkadot Asset Hub Local", - // ID - "asset-hub-polkadot-local", - ChainType::Local, - move || { - asset_hub_polkadot_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "polkadot-local".into(), para_id: 1000 }, - ) -} - -// Not used for syncing, but just to determine the genesis values set for the upgrade from shell. -pub fn asset_hub_polkadot_config() -> AssetHubPolkadotChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); - - AssetHubPolkadotChainSpec::from_genesis( - // Name - "Polkadot Asset Hub", - // ID - "asset-hub-polkadot", - ChainType::Live, - move || { - asset_hub_polkadot_genesis( - // initial collators. - vec![ - ( - hex!("4c3d674d2a01060f0ded218e5dcc6f90c1726f43df79885eb3e22d97a20d5421") - .into(), - hex!("4c3d674d2a01060f0ded218e5dcc6f90c1726f43df79885eb3e22d97a20d5421") - .unchecked_into(), - ), - ( - hex!("c7d7d38d16bc23c6321152c50306212dc22c0efc04a2e52b5cccfc31ab3d7811") - .into(), - hex!("c7d7d38d16bc23c6321152c50306212dc22c0efc04a2e52b5cccfc31ab3d7811") - .unchecked_into(), - ), - ( - hex!("c5c07ba203d7375675f5c1ebe70f0a5bb729ae57b48bcc877fcc2ab21309b762") - .into(), - hex!("c5c07ba203d7375675f5c1ebe70f0a5bb729ae57b48bcc877fcc2ab21309b762") - .unchecked_into(), - ), - ( - hex!("0b2d0013fb974794bd7aa452465b567d48ef70373fe231a637c1fb7c547e85b3") - .into(), - hex!("0b2d0013fb974794bd7aa452465b567d48ef70373fe231a637c1fb7c547e85b3") - .unchecked_into(), - ), - ], - vec![], - 1000u32.into(), - ) - }, - vec![ - "/ip4/34.65.251.121/tcp/30334/p2p/12D3KooWG3GrM6XKMM4gp3cvemdwUvu96ziYoJmqmetLZBXE8bSa".parse().unwrap(), - "/ip4/34.65.35.228/tcp/30334/p2p/12D3KooWMRyTLrCEPcAQD6c4EnudL3vVzg9zji3whvsMYPUYevpq".parse().unwrap(), - "/ip4/34.83.247.146/tcp/30334/p2p/12D3KooWE4jFh5FpJDkWVZhnWtFnbSqRhdjvC7Dp9b8b3FTuubQC".parse().unwrap(), - "/ip4/104.199.117.230/tcp/30334/p2p/12D3KooWG9R8pVXKumVo2rdkeVD4j5PVhRTqmYgLHY3a4yPYgLqM".parse().unwrap(), - ], - None, - None, - None, - Some(properties), - Extensions { relay_chain: "polkadot".into(), para_id: 1000 }, - ) -} - -fn asset_hub_polkadot_genesis( - invulnerables: Vec<(AccountId, AssetHubPolkadotAuraId)>, - endowed_accounts: Vec, - id: ParaId, -) -> asset_hub_polkadot_runtime::RuntimeGenesisConfig { - asset_hub_polkadot_runtime::RuntimeGenesisConfig { - system: asset_hub_polkadot_runtime::SystemConfig { - code: asset_hub_polkadot_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_polkadot_runtime::BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, ASSET_HUB_POLKADOT_ED * 4096)) - .collect(), - }, - parachain_info: asset_hub_polkadot_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() - }, - collator_selection: asset_hub_polkadot_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: ASSET_HUB_POLKADOT_ED * 16, - ..Default::default() - }, - session: asset_hub_polkadot_runtime::SessionConfig { - keys: invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_polkadot_session_keys(aura), // session keys - ) - }) - .collect(), - }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: asset_hub_polkadot_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - } -} - -pub fn asset_hub_kusama_development_config() -> AssetHubKusamaChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 2.into()); - properties.insert("tokenSymbol".into(), "KSM".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - AssetHubKusamaChainSpec::from_genesis( - // Name - "Kusama Asset Hub Development", - // ID - "asset-hub-kusama-dev", - ChainType::Local, - move || { - asset_hub_kusama_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "kusama-dev".into(), para_id: 1000 }, - ) -} - -pub fn asset_hub_kusama_local_config() -> AssetHubKusamaChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 2.into()); - properties.insert("tokenSymbol".into(), "KSM".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - AssetHubKusamaChainSpec::from_genesis( - // Name - "Kusama Asset Hub Local", - // ID - "asset-hub-kusama-local", - ChainType::Local, - move || { - asset_hub_kusama_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "kusama-local".into(), para_id: 1000 }, - ) -} - -pub fn asset_hub_kusama_config() -> AssetHubKusamaChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 2.into()); - properties.insert("tokenSymbol".into(), "KSM".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - AssetHubKusamaChainSpec::from_genesis( - // Name - "Kusama Asset Hub", - // ID - "asset-hub-kusama", - ChainType::Live, - move || { - asset_hub_kusama_genesis( - // initial collators. - vec![ - ( - hex!("50673d59020488a4ffc9d8c6de3062a65977046e6990915617f85fef6d349730") - .into(), - hex!("50673d59020488a4ffc9d8c6de3062a65977046e6990915617f85fef6d349730") - .unchecked_into(), - ), - ( - hex!("fe8102dbc244e7ea2babd9f53236d67403b046154370da5c3ea99def0bd0747a") - .into(), - hex!("fe8102dbc244e7ea2babd9f53236d67403b046154370da5c3ea99def0bd0747a") - .unchecked_into(), - ), - ( - hex!("38144b5398e5d0da5ec936a3af23f5a96e782f676ab19d45f29075ee92eca76a") - .into(), - hex!("38144b5398e5d0da5ec936a3af23f5a96e782f676ab19d45f29075ee92eca76a") - .unchecked_into(), - ), - ( - hex!("3253947640e309120ae70fa458dcacb915e2ddd78f930f52bd3679ec63fc4415") - .into(), - hex!("3253947640e309120ae70fa458dcacb915e2ddd78f930f52bd3679ec63fc4415") - .unchecked_into(), - ), - ], - Vec::new(), - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "kusama".into(), para_id: 1000 }, - ) -} - -fn asset_hub_kusama_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, -) -> asset_hub_kusama_runtime::RuntimeGenesisConfig { - asset_hub_kusama_runtime::RuntimeGenesisConfig { - system: asset_hub_kusama_runtime::SystemConfig { - code: asset_hub_kusama_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_kusama_runtime::BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, ASSET_HUB_KUSAMA_ED * 524_288)) - .collect(), - }, - parachain_info: asset_hub_kusama_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() - }, - collator_selection: asset_hub_kusama_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: ASSET_HUB_KUSAMA_ED * 16, - ..Default::default() - }, - session: asset_hub_kusama_runtime::SessionConfig { - keys: invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - asset_hub_kusama_session_keys(aura), // session keys - ) - }) - .collect(), - }, - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: asset_hub_kusama_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - } -} - -pub fn asset_hub_westend_development_config() -> AssetHubWestendChainSpec { +pub fn asset_hub_westend_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "WND".into()); properties.insert("tokenDecimals".into(), 12.into()); - AssetHubWestendChainSpec::from_genesis( - // Name - "Westend Asset Hub Development", - // ID - "asset-hub-westend-dev", - ChainType::Local, - move || { - asset_hub_westend_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + asset_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "westend".into(), para_id: 1000 }, ) + .with_name("Westend Asset Hub Development") + .with_id("asset-hub-westend-dev") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(asset_hub_westend_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"), + ], + testnet_parachains_constants::westend::currency::UNITS * 1_000_000, + 1000.into(), + )) + .with_properties(properties) + .build() } -pub fn asset_hub_westend_local_config() -> AssetHubWestendChainSpec { +pub fn asset_hub_westend_local_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "WND".into()); properties.insert("tokenDecimals".into(), 12.into()); - AssetHubWestendChainSpec::from_genesis( - // Name - "Westend Asset Hub Local", - // ID - "asset-hub-westend-local", - ChainType::Local, - move || { - asset_hub_westend_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + asset_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "westend-local".into(), para_id: 1000 }, ) + .with_name("Westend Asset Hub Local") + .with_id("asset-hub-westend-local") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(asset_hub_westend_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"), + ], + testnet_parachains_constants::westend::currency::UNITS * 1_000_000, + 1000.into(), + )) + .with_properties(properties) + .build() } -pub fn asset_hub_westend_config() -> AssetHubWestendChainSpec { +pub fn asset_hub_westend_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "WND".into()); properties.insert("tokenDecimals".into(), 12.into()); - AssetHubWestendChainSpec::from_genesis( - // Name - "Westend Asset Hub", - // ID - "asset-hub-westend", - ChainType::Live, - move || { - asset_hub_westend_genesis( - // initial collators. - vec![ - ( - hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325") - .into(), - hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325") - .unchecked_into(), - ), - ( - hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876") - .into(), - hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876") - .unchecked_into(), - ), - ( - hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f") - .into(), - hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f") - .unchecked_into(), - ), - ( - hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322") - .into(), - hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322") - .unchecked_into(), - ), - ], - Vec::new(), - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + asset_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "westend".into(), para_id: 1000 }, ) + .with_name("Westend Asset Hub") + .with_id("asset-hub-westend") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(asset_hub_westend_genesis( + // initial collators. + vec![ + ( + hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325").into(), + hex!("9cfd429fa002114f33c1d3e211501d62830c9868228eb3b4b8ae15a83de04325") + .unchecked_into(), + ), + ( + hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876").into(), + hex!("12a03fb4e7bda6c9a07ec0a11d03c24746943e054ff0bb04938970104c783876") + .unchecked_into(), + ), + ( + hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f").into(), + hex!("1256436307dfde969324e95b8c62cb9101f520a39435e6af0f7ac07b34e1931f") + .unchecked_into(), + ), + ( + hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322").into(), + hex!("98102b7bca3f070f9aa19f58feed2c0a4e107d203396028ec17a47e1ed80e322") + .unchecked_into(), + ), + ], + Vec::new(), + ASSET_HUB_WESTEND_ED * 4096, + 1000.into(), + )) + .with_properties(properties) + .build() } fn asset_hub_westend_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, + endowment: AssetHubBalance, id: ParaId, -) -> asset_hub_westend_runtime::RuntimeGenesisConfig { - asset_hub_westend_runtime::RuntimeGenesisConfig { - system: asset_hub_westend_runtime::SystemConfig { - code: asset_hub_westend_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_westend_runtime::BalancesConfig { - balances: endowed_accounts +) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts .iter() .cloned() - .map(|k| (k, ASSET_HUB_WESTEND_ED * 4096)) - .collect(), + .map(|k| (k, endowment)) + .collect::>(), }, - parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: asset_hub_westend_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: ASSET_HUB_WESTEND_ED * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": ASSET_HUB_WESTEND_ED * 16, }, - session: asset_hub_westend_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( @@ -642,21 +195,15 @@ fn asset_hub_westend_genesis( asset_hub_westend_session_keys(aura), // session keys ) }) - .collect(), + .collect::>(), }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: asset_hub_westend_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - } + }) } -pub fn asset_hub_rococo_development_config() -> AssetHubRococoChainSpec { +pub fn asset_hub_rococo_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), 42.into()); properties.insert("tokenSymbol".into(), "ROC".into()); @@ -669,57 +216,39 @@ pub fn asset_hub_rococo_development_config() -> AssetHubRococoChainSpec { ) } -pub fn asset_hub_wococo_development_config() -> AssetHubWococoChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 42.into()); - properties.insert("tokenSymbol".into(), "WOC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - asset_hub_rococo_like_development_config( - properties, - "Wococo Asset Hub Development", - "asset-hub-wococo-dev", - 1000, - ) -} - fn asset_hub_rococo_like_development_config( properties: sc_chain_spec::Properties, name: &str, chain_id: &str, para_id: u32, -) -> AssetHubRococoChainSpec { - AssetHubRococoChainSpec::from_genesis( - // Name - name, - // ID - chain_id, - ChainType::Local, - move || { - asset_hub_rococo_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.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), +) -> GenericChainSpec { + GenericChainSpec::builder( + asset_hub_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-dev".into(), para_id }, ) + .with_name(name) + .with_id(chain_id) + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(asset_hub_rococo_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"), + ], + testnet_parachains_constants::rococo::currency::UNITS * 1_000_000, + para_id.into(), + )) + .with_properties(properties) + .build() } -pub fn asset_hub_rococo_local_config() -> AssetHubRococoChainSpec { +pub fn asset_hub_rococo_local_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), 42.into()); properties.insert("tokenSymbol".into(), "ROC".into()); @@ -732,214 +261,124 @@ pub fn asset_hub_rococo_local_config() -> AssetHubRococoChainSpec { ) } -pub fn asset_hub_wococo_local_config() -> AssetHubWococoChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 42.into()); - properties.insert("tokenSymbol".into(), "WOC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - asset_hub_rococo_like_local_config( - properties, - "Wococo Asset Hub Local", - "asset-hub-wococo-local", - 1000, - ) -} - fn asset_hub_rococo_like_local_config( properties: sc_chain_spec::Properties, name: &str, chain_id: &str, para_id: u32, -) -> AssetHubRococoChainSpec { - AssetHubRococoChainSpec::from_genesis( - // Name - name, - // ID - chain_id, - ChainType::Local, - move || { - asset_hub_rococo_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.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), +) -> GenericChainSpec { + GenericChainSpec::builder( + asset_hub_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-local".into(), para_id }, ) + .with_name(name) + .with_id(chain_id) + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(asset_hub_rococo_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"), + ], + testnet_parachains_constants::rococo::currency::UNITS * 1_000_000, + para_id.into(), + )) + .with_properties(properties) + .build() } -pub fn asset_hub_rococo_genesis_config() -> AssetHubRococoChainSpec { +pub fn asset_hub_rococo_genesis_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "ROC".into()); properties.insert("tokenDecimals".into(), 12.into()); let para_id = 1000; - AssetHubRococoChainSpec::from_genesis( - // Name - "Rococo Asset Hub", - // ID - "asset-hub-rococo", - ChainType::Live, - move || { - asset_hub_rococo_genesis( - // initial collators. - vec![ - // E8XC6rTJRsioKCp6KMy6zd24ykj4gWsusZ3AkSeyavpVBAG - ( - hex!("44cb62d1d6cdd2fff2a5ef3bb7ef827be5b3e117a394ecaa634d8dd9809d5608") - .into(), - hex!("44cb62d1d6cdd2fff2a5ef3bb7ef827be5b3e117a394ecaa634d8dd9809d5608") - .unchecked_into(), - ), - // G28iWEybndgGRbhfx83t7Q42YhMPByHpyqWDUgeyoGF94ri - ( - hex!("9864b85e23aa4506643db9879c3dbbeabaa94d269693a4447f537dd6b5893944") - .into(), - hex!("9864b85e23aa4506643db9879c3dbbeabaa94d269693a4447f537dd6b5893944") - .unchecked_into(), - ), - // G839e2eMiq7UXbConsY6DS1XDAYG2XnQxAmLuRLGGQ3Px9c - ( - hex!("9ce5741ee2f1ac3bdedbde9f3339048f4da2cb88ddf33a0977fa0b4cf86e2948") - .into(), - hex!("9ce5741ee2f1ac3bdedbde9f3339048f4da2cb88ddf33a0977fa0b4cf86e2948") - .unchecked_into(), - ), - // GLao4ukFUW6qhexuZowdFrKa2NLCfnEjZMftSXXfvGv1vvt - ( - hex!("a676ed15f5a325eab49ed8d5f8c00f3f814b19bb58cda14ad10894c078dd337f") - .into(), - hex!("a676ed15f5a325eab49ed8d5f8c00f3f814b19bb58cda14ad10894c078dd337f") - .unchecked_into(), - ), - ], - Vec::new(), - para_id.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + asset_hub_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo".into(), para_id }, ) -} - -pub fn asset_hub_wococo_genesis_config() -> AssetHubWococoChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 42.into()); - properties.insert("tokenSymbol".into(), "WOC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - let para_id = 1000; - AssetHubWococoChainSpec::from_genesis( - // Name - "Wococo Asset Hub", - // ID - "asset-hub-wococo", - ChainType::Live, - move || { - asset_hub_rococo_genesis( - // initial collators. - vec![ - // 5C8RGkS8t5K93fB2hkgKbvSYs5iG6AknJMuQmbBDeazon9Lj - ( - hex!("02d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534") - .into(), - hex!("02d526f43cf27e94f478f9db785dc86052a77c695e7c855211839d3fde3ce534") - .unchecked_into(), - ), - // 5GePeDZQeBagXH7kH5QPKnQKi39Z5hoYFB5FmUtEvc4yxKej - ( - hex!("caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814") - .into(), - hex!("caa1f623ca183296c4521b56cc29c484ca017830f8cb538f30f2d4664d631814") - .unchecked_into(), - ), - // 5CfnTTb9NMJDNKDntA83mHKoedZ7wjDC8ypLCTDd4NwUx3zv - ( - hex!("1ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965") - .into(), - hex!("1ac112d635db2bd34e79ae2b99486cf7c0b71a928668e4feb3dc4633d368f965") - .unchecked_into(), - ), - // 5EqheiwiG22gvGpN7cvrbeaQzhg7rzsYYVkYK4yj5vRrTQRQ - ( - hex!("7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66") - .into(), - hex!("7ac9d11be07334cd27e9eb849f5fc7677a10ad36b6ab38b377d3c8b2c0b08b66") - .unchecked_into(), - ), - ], - Vec::new(), - para_id.into(), - ) - }, + .with_name("Rococo Asset Hub") + .with_id("asset-hub-rococo") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(asset_hub_rococo_genesis( + // initial collators. + vec![ + // E8XC6rTJRsioKCp6KMy6zd24ykj4gWsusZ3AkSeyavpVBAG + ( + hex!("44cb62d1d6cdd2fff2a5ef3bb7ef827be5b3e117a394ecaa634d8dd9809d5608").into(), + hex!("44cb62d1d6cdd2fff2a5ef3bb7ef827be5b3e117a394ecaa634d8dd9809d5608") + .unchecked_into(), + ), + // G28iWEybndgGRbhfx83t7Q42YhMPByHpyqWDUgeyoGF94ri + ( + hex!("9864b85e23aa4506643db9879c3dbbeabaa94d269693a4447f537dd6b5893944").into(), + hex!("9864b85e23aa4506643db9879c3dbbeabaa94d269693a4447f537dd6b5893944") + .unchecked_into(), + ), + // G839e2eMiq7UXbConsY6DS1XDAYG2XnQxAmLuRLGGQ3Px9c + ( + hex!("9ce5741ee2f1ac3bdedbde9f3339048f4da2cb88ddf33a0977fa0b4cf86e2948").into(), + hex!("9ce5741ee2f1ac3bdedbde9f3339048f4da2cb88ddf33a0977fa0b4cf86e2948") + .unchecked_into(), + ), + // GLao4ukFUW6qhexuZowdFrKa2NLCfnEjZMftSXXfvGv1vvt + ( + hex!("a676ed15f5a325eab49ed8d5f8c00f3f814b19bb58cda14ad10894c078dd337f").into(), + hex!("a676ed15f5a325eab49ed8d5f8c00f3f814b19bb58cda14ad10894c078dd337f") + .unchecked_into(), + ), + ], Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "wococo".into(), para_id }, - ) + ASSET_HUB_ROCOCO_ED * 524_288, + para_id.into(), + )) + .with_properties(properties) + .build() } fn asset_hub_rococo_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, + endowment: AssetHubBalance, id: ParaId, -) -> asset_hub_rococo_runtime::RuntimeGenesisConfig { - asset_hub_rococo_runtime::RuntimeGenesisConfig { - system: asset_hub_rococo_runtime::SystemConfig { - code: asset_hub_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: asset_hub_rococo_runtime::BalancesConfig { +) -> serde_json::Value { + serde_json::json!({ + "balances": asset_hub_rococo_runtime::BalancesConfig { balances: endowed_accounts .iter() .cloned() - .map(|k| (k, ASSET_HUB_ROCOCO_ED * 524_288)) + .map(|k| (k, endowment)) .collect(), }, - parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { + "parachainInfo": asset_hub_rococo_runtime::ParachainInfoConfig { parachain_id: id, ..Default::default() }, - collator_selection: asset_hub_rococo_runtime::CollatorSelectionConfig { + "collatorSelection": asset_hub_rococo_runtime::CollatorSelectionConfig { invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), candidacy_bond: ASSET_HUB_ROCOCO_ED * 16, ..Default::default() }, - session: asset_hub_rococo_runtime::SessionConfig { + "session": asset_hub_rococo_runtime::SessionConfig { keys: invulnerables .into_iter() .map(|(acc, aura)| { @@ -951,12 +390,9 @@ fn asset_hub_rococo_genesis( }) .collect(), }, - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: asset_hub_rococo_runtime::PolkadotXcmConfig { + "polkadotXcm": asset_hub_rococo_runtime::PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION), ..Default::default() - }, - } + } + }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index ca5583fe2e53d8c76f2a53e78ed7817e9450a99d..15e8a1bf11a055e6c5b4950ad07d78cad72f81a8 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -14,36 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed}; +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, GenericChainSpec}; use cumulus_primitives_core::ParaId; use parachains_common::Balance as BridgeHubBalance; use sc_chain_spec::ChainSpec; use sp_core::sr25519; -use std::{path::PathBuf, str::FromStr}; +use std::str::FromStr; /// Collects all supported BridgeHub configurations #[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { - Rococo, - RococoLocal, - // used by benchmarks - RococoDevelopment, - - Wococo, - WococoLocal, - Kusama, KusamaLocal, - // used by benchmarks - KusamaDevelopment, Polkadot, PolkadotLocal, + + Rococo, + RococoLocal, // used by benchmarks - PolkadotDevelopment, + RococoDevelopment, - // used with kusama runtime Westend, + WestendLocal, + // used by benchmarks + WestendDevelopment, } impl FromStr for BridgeHubRuntimeType { @@ -53,17 +48,14 @@ impl FromStr for BridgeHubRuntimeType { match value { polkadot::BRIDGE_HUB_POLKADOT => Ok(BridgeHubRuntimeType::Polkadot), polkadot::BRIDGE_HUB_POLKADOT_LOCAL => Ok(BridgeHubRuntimeType::PolkadotLocal), - polkadot::BRIDGE_HUB_POLKADOT_DEVELOPMENT => - Ok(BridgeHubRuntimeType::PolkadotDevelopment), kusama::BRIDGE_HUB_KUSAMA => Ok(BridgeHubRuntimeType::Kusama), kusama::BRIDGE_HUB_KUSAMA_LOCAL => Ok(BridgeHubRuntimeType::KusamaLocal), - kusama::BRIDGE_HUB_KUSAMA_DEVELOPMENT => Ok(BridgeHubRuntimeType::KusamaDevelopment), westend::BRIDGE_HUB_WESTEND => Ok(BridgeHubRuntimeType::Westend), + westend::BRIDGE_HUB_WESTEND_LOCAL => Ok(BridgeHubRuntimeType::WestendLocal), + westend::BRIDGE_HUB_WESTEND_DEVELOPMENT => Ok(BridgeHubRuntimeType::WestendDevelopment), rococo::BRIDGE_HUB_ROCOCO => Ok(BridgeHubRuntimeType::Rococo), rococo::BRIDGE_HUB_ROCOCO_LOCAL => Ok(BridgeHubRuntimeType::RococoLocal), rococo::BRIDGE_HUB_ROCOCO_DEVELOPMENT => Ok(BridgeHubRuntimeType::RococoDevelopment), - wococo::BRIDGE_HUB_WOCOCO => Ok(BridgeHubRuntimeType::Wococo), - wococo::BRIDGE_HUB_WOCOCO_LOCAL => Ok(BridgeHubRuntimeType::WococoLocal), _ => Err(format!("Value '{}' is not configured yet", value)), } } @@ -72,69 +64,34 @@ impl FromStr for BridgeHubRuntimeType { impl BridgeHubRuntimeType { pub const ID_PREFIX: &'static str = "bridge-hub"; - pub fn chain_spec_from_json_file(&self, path: PathBuf) -> Result, String> { - match self { - BridgeHubRuntimeType::Polkadot | - BridgeHubRuntimeType::PolkadotLocal | - BridgeHubRuntimeType::PolkadotDevelopment => - Ok(Box::new(polkadot::BridgeHubChainSpec::from_json_file(path)?)), - BridgeHubRuntimeType::Kusama | - BridgeHubRuntimeType::KusamaLocal | - BridgeHubRuntimeType::KusamaDevelopment => - Ok(Box::new(kusama::BridgeHubChainSpec::from_json_file(path)?)), - BridgeHubRuntimeType::Westend => - Ok(Box::new(westend::BridgeHubChainSpec::from_json_file(path)?)), - BridgeHubRuntimeType::Rococo | - BridgeHubRuntimeType::RococoLocal | - BridgeHubRuntimeType::RococoDevelopment => - Ok(Box::new(rococo::BridgeHubChainSpec::from_json_file(path)?)), - BridgeHubRuntimeType::Wococo | BridgeHubRuntimeType::WococoLocal => - Ok(Box::new(wococo::BridgeHubChainSpec::from_json_file(path)?)), - } - } - pub fn load_config(&self) -> Result, String> { match self { - BridgeHubRuntimeType::Polkadot => - Ok(Box::new(polkadot::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/bridge-hub-polkadot.json")[..], - )?)), - BridgeHubRuntimeType::PolkadotLocal => Ok(Box::new(polkadot::local_config( - polkadot::BRIDGE_HUB_POLKADOT_LOCAL, - "Polkadot BridgeHub Local", - "polkadot-local", + BridgeHubRuntimeType::Polkadot => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/bridge-hub-polkadot.json")[..], + )?)), + BridgeHubRuntimeType::Kusama => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/bridge-hub-kusama.json")[..], + )?)), + BridgeHubRuntimeType::Westend => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/bridge-hub-westend.json")[..], + )?)), + BridgeHubRuntimeType::WestendLocal => Ok(Box::new(westend::local_config( + westend::BRIDGE_HUB_WESTEND_LOCAL, + "Westend BridgeHub Local", + "westend-local", ParaId::new(1002), + Some("Bob".to_string()), ))), - BridgeHubRuntimeType::PolkadotDevelopment => Ok(Box::new(polkadot::local_config( - polkadot::BRIDGE_HUB_POLKADOT_DEVELOPMENT, - "Polkadot BridgeHub Development", - "polkadot-dev", + BridgeHubRuntimeType::WestendDevelopment => Ok(Box::new(westend::local_config( + westend::BRIDGE_HUB_WESTEND_DEVELOPMENT, + "Westend BridgeHub Development", + "westend-dev", ParaId::new(1002), + Some("Bob".to_string()), ))), - BridgeHubRuntimeType::Kusama => - Ok(Box::new(kusama::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/bridge-hub-kusama.json")[..], - )?)), - BridgeHubRuntimeType::KusamaLocal => Ok(Box::new(kusama::local_config( - kusama::BRIDGE_HUB_KUSAMA_LOCAL, - "Kusama BridgeHub Local", - "kusama-local", - ParaId::new(1003), - ))), - BridgeHubRuntimeType::KusamaDevelopment => Ok(Box::new(kusama::local_config( - kusama::BRIDGE_HUB_KUSAMA_DEVELOPMENT, - "Kusama BridgeHub Development", - "kusama-dev", - ParaId::new(1003), - ))), - BridgeHubRuntimeType::Westend => - Ok(Box::new(westend::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/bridge-hub-westend.json")[..], - )?)), - BridgeHubRuntimeType::Rococo => - Ok(Box::new(rococo::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/bridge-hub-rococo.json")[..], - )?)), + BridgeHubRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/bridge-hub-rococo.json")[..], + )?)), BridgeHubRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config( rococo::BRIDGE_HUB_ROCOCO_LOCAL, "Rococo BridgeHub Local", @@ -151,17 +108,7 @@ impl BridgeHubRuntimeType { Some("Bob".to_string()), |_| (), ))), - BridgeHubRuntimeType::Wococo => - Ok(Box::new(wococo::BridgeHubChainSpec::from_json_bytes( - &include_bytes!("../../chain-specs/bridge-hub-wococo.json")[..], - )?)), - BridgeHubRuntimeType::WococoLocal => Ok(Box::new(wococo::local_config( - wococo::BRIDGE_HUB_WOCOCO_LOCAL, - "Wococo BridgeHub Local", - "wococo-local", - ParaId::new(1014), - Some("Bob".to_string()), - ))), + other => Err(std::format!("No default config present for {:?}", other)), } } } @@ -182,7 +129,7 @@ fn ensure_id(id: &str) -> Result<&str, String> { /// Sub-module for Rococo setup pub mod rococo { use super::{get_account_id_from_seed, get_collator_keys_from_seed, sr25519, ParaId}; - use crate::chain_spec::{Extensions, SAFE_XCM_VERSION}; + use crate::chain_spec::{Extensions, GenericChainSpec, SAFE_XCM_VERSION}; use parachains_common::{AccountId, AuraId}; use sc_chain_spec::ChainType; @@ -192,13 +139,7 @@ pub mod rococo { pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; pub(crate) const BRIDGE_HUB_ROCOCO_DEVELOPMENT: &str = "bridge-hub-rococo-dev"; const BRIDGE_HUB_ROCOCO_ED: BridgeHubBalance = - parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; - - /// Specialized `ChainSpec` for the normal parachain runtime. - pub type BridgeHubChainSpec = - sc_service::GenericChainSpec; - - pub type RuntimeApi = bridge_hub_rococo_runtime::RuntimeApi; + bridge_hub_rococo_runtime::ExistentialDeposit::get(); pub fn local_config( id: &str, @@ -207,7 +148,7 @@ pub mod rococo { para_id: ParaId, bridges_pallet_owner_seed: Option, modify_props: ModifyProperties, - ) -> BridgeHubChainSpec { + ) -> GenericChainSpec { // Rococo defaults let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), 42.into()); @@ -215,52 +156,47 @@ pub mod rococo { properties.insert("tokenDecimals".into(), 12.into()); modify_props(&mut properties); - BridgeHubChainSpec::from_genesis( - // Name - chain_name, - // ID - super::ensure_id(id).expect("invalid id"), - ChainType::Local, - move || { - 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, - bridges_pallet_owner_seed - .as_ref() - .map(|seed| get_account_id_from_seed::(seed)), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + bridge_hub_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, + bridges_pallet_owner_seed + .as_ref() + .map(|seed| get_account_id_from_seed::(seed)), + )) + .with_properties(properties) + .build() } fn genesis( @@ -268,28 +204,20 @@ pub mod rococo { endowed_accounts: Vec, id: ParaId, bridges_pallet_owner: Option, - ) -> bridge_hub_rococo_runtime::RuntimeGenesisConfig { - bridge_hub_rococo_runtime::RuntimeGenesisConfig { - system: bridge_hub_rococo_runtime::SystemConfig { - code: bridge_hub_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: bridge_hub_rococo_runtime::BalancesConfig { - balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), }, - parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: bridge_hub_rococo_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: BRIDGE_HUB_ROCOCO_ED * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": BRIDGE_HUB_ROCOCO_ED * 16, }, - session: bridge_hub_rococo_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( @@ -298,329 +226,144 @@ pub mod rococo { bridge_hub_rococo_runtime::SessionKeys { aura }, // session keys ) }) - .collect(), + .collect::>(), }, - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: bridge_hub_rococo_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - bridge_wococo_grandpa: bridge_hub_rococo_runtime::BridgeWococoGrandpaConfig { - owner: bridges_pallet_owner.clone(), - ..Default::default() + "bridgeWestendGrandpa": { + "owner": bridges_pallet_owner.clone(), }, - bridge_rococo_grandpa: bridge_hub_rococo_runtime::BridgeRococoGrandpaConfig { - owner: bridges_pallet_owner.clone(), - ..Default::default() + "bridgeWestendMessages": { + "owner": bridges_pallet_owner.clone(), }, - bridge_wococo_to_rococo_messages: - bridge_hub_rococo_runtime::BridgeWococoToRococoMessagesConfig { - owner: bridges_pallet_owner.clone(), - ..Default::default() - }, - bridge_rococo_to_wococo_messages: - bridge_hub_rococo_runtime::BridgeRococoToWococoMessagesConfig { - owner: bridges_pallet_owner, - ..Default::default() - }, - } - } -} - -/// Sub-module for Wococo setup (reuses stuff from Rococo) -pub mod wococo { - use super::ParaId; - use crate::chain_spec::bridge_hubs::rococo; - - pub(crate) const BRIDGE_HUB_WOCOCO: &str = "bridge-hub-wococo"; - pub(crate) const BRIDGE_HUB_WOCOCO_LOCAL: &str = "bridge-hub-wococo-local"; - - pub type BridgeHubChainSpec = rococo::BridgeHubChainSpec; - pub type RuntimeApi = rococo::RuntimeApi; - - pub fn local_config( - id: &str, - chain_name: &str, - relay_chain: &str, - para_id: ParaId, - bridges_pallet_owner_seed: Option, - ) -> BridgeHubChainSpec { - rococo::local_config( - id, - chain_name, - relay_chain, - para_id, - bridges_pallet_owner_seed, - |properties| { - properties.insert("tokenSymbol".into(), "WOOK".into()); - }, - ) + "ethereumSystem": { + "paraId": id, + "assetHubParaId": 1000 + } + }) } } /// Sub-module for Kusama setup pub mod kusama { - use super::{BridgeHubBalance, ParaId}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, - }; + pub(crate) const BRIDGE_HUB_KUSAMA: &str = "bridge-hub-kusama"; + pub(crate) const BRIDGE_HUB_KUSAMA_LOCAL: &str = "bridge-hub-kusama-local"; +} + +/// Sub-module for Westend setup. +pub mod westend { + use super::{get_account_id_from_seed, get_collator_keys_from_seed, sr25519, ParaId}; + use crate::chain_spec::{Extensions, GenericChainSpec, SAFE_XCM_VERSION}; use parachains_common::{AccountId, AuraId}; use sc_chain_spec::ChainType; - use sp_core::sr25519; - pub(crate) const BRIDGE_HUB_KUSAMA: &str = "bridge-hub-kusama"; - pub(crate) const BRIDGE_HUB_KUSAMA_LOCAL: &str = "bridge-hub-kusama-local"; - pub(crate) const BRIDGE_HUB_KUSAMA_DEVELOPMENT: &str = "bridge-hub-kusama-dev"; - const BRIDGE_HUB_KUSAMA_ED: BridgeHubBalance = - parachains_common::kusama::currency::EXISTENTIAL_DEPOSIT; + use super::BridgeHubBalance; - /// Specialized `ChainSpec` for the normal parachain runtime. - pub type BridgeHubChainSpec = - sc_service::GenericChainSpec; - pub type RuntimeApi = bridge_hub_kusama_runtime::RuntimeApi; + pub(crate) const BRIDGE_HUB_WESTEND: &str = "bridge-hub-westend"; + pub(crate) const BRIDGE_HUB_WESTEND_LOCAL: &str = "bridge-hub-westend-local"; + pub(crate) const BRIDGE_HUB_WESTEND_DEVELOPMENT: &str = "bridge-hub-westend-dev"; + const BRIDGE_HUB_WESTEND_ED: BridgeHubBalance = + bridge_hub_westend_runtime::ExistentialDeposit::get(); pub fn local_config( id: &str, chain_name: &str, relay_chain: &str, para_id: ParaId, - ) -> BridgeHubChainSpec { + bridges_pallet_owner_seed: Option, + ) -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 2.into()); - properties.insert("tokenSymbol".into(), "KSM".into()); + properties.insert("tokenSymbol".into(), "WND".into()); properties.insert("tokenDecimals".into(), 12.into()); - BridgeHubChainSpec::from_genesis( - // Name - chain_name, - // ID - super::ensure_id(id).expect("invalid id"), - ChainType::Local, - move || { - 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, - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), + GenericChainSpec::builder( + bridge_hub_westend_runtime::WASM_BINARY + .expect("WASM binary was not build, 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, + bridges_pallet_owner_seed + .as_ref() + .map(|seed| get_account_id_from_seed::(seed)), + )) + .with_properties(properties) + .build() } fn genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, - ) -> bridge_hub_kusama_runtime::RuntimeGenesisConfig { - bridge_hub_kusama_runtime::RuntimeGenesisConfig { - system: bridge_hub_kusama_runtime::SystemConfig { - code: bridge_hub_kusama_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: bridge_hub_kusama_runtime::BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, BRIDGE_HUB_KUSAMA_ED * 524_288)) - .collect(), + bridges_pallet_owner: Option, + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), }, - parachain_info: bridge_hub_kusama_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: bridge_hub_kusama_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: BRIDGE_HUB_KUSAMA_ED * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": BRIDGE_HUB_WESTEND_ED * 16, }, - session: bridge_hub_kusama_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( - acc.clone(), // account id - acc, // validator id - bridge_hub_kusama_runtime::SessionKeys { aura }, // session keys + acc.clone(), // account id + acc, // validator id + bridge_hub_westend_runtime::SessionKeys { aura }, // session keys ) }) - .collect(), + .collect::>(), }, - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: bridge_hub_kusama_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - } + "bridgeRococoGrandpa": { + "owner": bridges_pallet_owner.clone(), + }, + "bridgeRococoMessages": { + "owner": bridges_pallet_owner.clone(), + } + }) } } -/// Sub-module for Westend setup (uses Kusama runtime) -pub mod westend { - use crate::chain_spec::bridge_hubs::kusama; - - pub(crate) const BRIDGE_HUB_WESTEND: &str = "bridge-hub-westend"; - pub type BridgeHubChainSpec = kusama::BridgeHubChainSpec; - pub type RuntimeApi = bridge_hub_kusama_runtime::RuntimeApi; -} - /// Sub-module for Polkadot setup pub mod polkadot { - use super::{BridgeHubBalance, ParaId}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, - }; - use parachains_common::{AccountId, AuraId}; - use sc_chain_spec::ChainType; - use sp_core::sr25519; - pub(crate) const BRIDGE_HUB_POLKADOT: &str = "bridge-hub-polkadot"; pub(crate) const BRIDGE_HUB_POLKADOT_LOCAL: &str = "bridge-hub-polkadot-local"; - pub(crate) const BRIDGE_HUB_POLKADOT_DEVELOPMENT: &str = "bridge-hub-polkadot-dev"; - const BRIDGE_HUB_POLKADOT_ED: BridgeHubBalance = - parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; - - /// Specialized `ChainSpec` for the normal parachain runtime. - pub type BridgeHubChainSpec = - sc_service::GenericChainSpec; - pub type RuntimeApi = bridge_hub_polkadot_runtime::RuntimeApi; - - pub fn local_config( - id: &str, - chain_name: &str, - relay_chain: &str, - para_id: ParaId, - ) -> BridgeHubChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); - - BridgeHubChainSpec::from_genesis( - // Name - chain_name, - // ID - super::ensure_id(id).expect("invalid id"), - ChainType::Local, - move || { - 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, - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, - ) - } - - fn genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - id: ParaId, - ) -> bridge_hub_polkadot_runtime::RuntimeGenesisConfig { - bridge_hub_polkadot_runtime::RuntimeGenesisConfig { - system: bridge_hub_polkadot_runtime::SystemConfig { - code: bridge_hub_polkadot_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: bridge_hub_polkadot_runtime::BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, BRIDGE_HUB_POLKADOT_ED * 4096)) - .collect(), - }, - parachain_info: bridge_hub_polkadot_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() - }, - collator_selection: bridge_hub_polkadot_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: BRIDGE_HUB_POLKADOT_ED * 16, - ..Default::default() - }, - session: bridge_hub_polkadot_runtime::SessionConfig { - keys: invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - bridge_hub_polkadot_runtime::SessionKeys { aura }, // session keys - ) - }) - .collect(), - }, - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: bridge_hub_polkadot_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() - }, - } - } } diff --git a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs index fbf49b9535a42f632df29931e7515745414eb9c6..c0a9f195d89bc1a3b56001e8e60ad1834660959c 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs @@ -15,169 +15,143 @@ // along with Cumulus. If not, see . use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, }; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId, Balance as CollectivesBalance}; use sc_service::ChainType; use sp_core::sr25519; -pub type CollectivesPolkadotChainSpec = - sc_service::GenericChainSpec; - -const COLLECTIVES_POLKADOT_ED: CollectivesBalance = - parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; +const COLLECTIVES_WESTEND_ED: CollectivesBalance = + collectives_westend_runtime::ExistentialDeposit::get(); /// Generate the session keys from individual elements. /// /// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn collectives_polkadot_session_keys( - keys: AuraId, -) -> collectives_polkadot_runtime::SessionKeys { - collectives_polkadot_runtime::SessionKeys { aura: keys } +pub fn collectives_westend_session_keys(keys: AuraId) -> collectives_westend_runtime::SessionKeys { + collectives_westend_runtime::SessionKeys { aura: keys } } -pub fn collectives_polkadot_development_config() -> CollectivesPolkadotChainSpec { +pub fn collectives_westend_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "WND".into()); + properties.insert("tokenDecimals".into(), 12.into()); - CollectivesPolkadotChainSpec::from_genesis( - // Name - "Polkadot Collectives Development", - // ID - "collectives_polkadot_dev", - ChainType::Local, - move || { - collectives_polkadot_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"), - ], - // 1002 avoids a potential collision with Kusama-1001 (Encointer) should there ever - // be a collective para on Kusama. - 1002.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "polkadot-dev".into(), para_id: 1002 }, + GenericChainSpec::builder( + collectives_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "westend-dev".into(), para_id: 1002 }, ) + .with_name("Westend Collectives Development") + .with_id("collectives_westend_dev") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(collectives_westend_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"), + ], + // 1002 avoids a potential collision with Kusama-1001 (Encointer) should there ever + // be a collective para on Kusama. + 1002.into(), + )) + .with_boot_nodes(Vec::new()) + .with_properties(properties) + .build() } -/// Collectives Polkadot Local Config. -pub fn collectives_polkadot_local_config() -> CollectivesPolkadotChainSpec { +/// Collectives Westend Local Config. +pub fn collectives_westend_local_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 0.into()); - properties.insert("tokenSymbol".into(), "DOT".into()); - properties.insert("tokenDecimals".into(), 10.into()); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "WND".into()); + properties.insert("tokenDecimals".into(), 12.into()); - CollectivesPolkadotChainSpec::from_genesis( - // Name - "Polkadot Collectives Local", - // ID - "collectives_polkadot_local", - ChainType::Local, - move || { - collectives_polkadot_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"), - ], - 1002.into(), - ) - }, - Vec::new(), - None, - None, - None, - Some(properties), - Extensions { relay_chain: "polkadot-local".into(), para_id: 1002 }, + GenericChainSpec::builder( + collectives_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "westend-local".into(), para_id: 1002 }, ) + .with_name("Westend Collectives Local") + .with_id("collectives_westend_local") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(collectives_westend_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"), + ], + 1002.into(), + )) + .with_boot_nodes(Vec::new()) + .with_properties(properties) + .build() } -fn collectives_polkadot_genesis( +fn collectives_westend_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, -) -> collectives_polkadot_runtime::RuntimeGenesisConfig { - collectives_polkadot_runtime::RuntimeGenesisConfig { - system: collectives_polkadot_runtime::SystemConfig { - code: collectives_polkadot_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: collectives_polkadot_runtime::BalancesConfig { - balances: endowed_accounts +) -> serde_json::Value { + serde_json::json!( { + "balances": { + "balances": endowed_accounts .iter() .cloned() - .map(|k| (k, COLLECTIVES_POLKADOT_ED * 4096)) - .collect(), + .map(|k| (k, COLLECTIVES_WESTEND_ED * 4096)) + .collect::>(), }, - parachain_info: collectives_polkadot_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: collectives_polkadot_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: COLLECTIVES_POLKADOT_ED * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": COLLECTIVES_WESTEND_ED * 16, }, - session: collectives_polkadot_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( acc.clone(), // account id acc, // validator id - collectives_polkadot_session_keys(aura), // session keys + collectives_westend_session_keys(aura), // session keys ) }) - .collect(), + .collect::>(), }, // no need to pass anything to aura, in fact it will panic if we do. Session will take care // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: collectives_polkadot_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - alliance: Default::default(), - alliance_motion: Default::default(), - } + }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs b/cumulus/polkadot-parachain/src/chain_spec/contracts.rs index 0d5012858edc6f81a1554996fbafe4a1f6973072..4e89b81d1be40fb5b0bc5d5f689a8883ba612fa1 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/contracts.rs @@ -15,7 +15,8 @@ // along with Cumulus. If not, see . use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, }; use cumulus_primitives_core::ParaId; use hex_literal::hex; @@ -23,239 +24,199 @@ use parachains_common::{AccountId, AuraId}; use sc_service::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; -pub type ContractsRococoChainSpec = - sc_service::GenericChainSpec; - /// No relay chain suffix because the id is the same over all relay chains. const CONTRACTS_PARACHAIN_ID: u32 = 1002; /// The existential deposit is determined by the runtime "contracts-rococo". const CONTRACTS_ROCOCO_ED: contracts_rococo_runtime::Balance = - parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; + testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; -pub fn contracts_rococo_development_config() -> ContractsRococoChainSpec { +pub fn contracts_rococo_development_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "ROC".into()); properties.insert("tokenDecimals".into(), 12.into()); - ContractsRococoChainSpec::from_genesis( - // Name - "Contracts on Rococo Development", - // ID - "contracts-rococo-dev", - ChainType::Development, - move || { - contracts_rococo_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"), - ], - CONTRACTS_PARACHAIN_ID.into(), - ) - }, - Vec::new(), - None, - None, - None, - None, + GenericChainSpec::builder( + contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-local".into(), // You MUST set this to the correct network! para_id: CONTRACTS_PARACHAIN_ID, }, ) + .with_name("Contracts on Rococo Development") + .with_id("contracts-rococo-dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(contracts_rococo_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"), + ], + CONTRACTS_PARACHAIN_ID.into(), + )) + .with_boot_nodes(Vec::new()) + .build() } -pub fn contracts_rococo_local_config() -> ContractsRococoChainSpec { +pub fn contracts_rococo_local_config() -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "ROC".into()); properties.insert("tokenDecimals".into(), 12.into()); - ContractsRococoChainSpec::from_genesis( - // Name - "Contracts on Rococo", - // ID - "contracts-rococo-local", - ChainType::Local, - move || { - contracts_rococo_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"), - ], - CONTRACTS_PARACHAIN_ID.into(), - ) - }, - // Bootnodes - Vec::new(), - // Telemetry - None, - // Protocol ID - None, - // Fork ID - None, - // Properties - Some(properties), - // Extensions + GenericChainSpec::builder( + contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-local".into(), // You MUST set this to the correct network! para_id: CONTRACTS_PARACHAIN_ID, }, ) + .with_name("Contracts on Rococo") + .with_id("contracts-rococo-local") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(contracts_rococo_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"), + ], + CONTRACTS_PARACHAIN_ID.into(), + )) + .with_properties(properties) + .build() } -pub fn contracts_rococo_config() -> ContractsRococoChainSpec { +pub fn contracts_rococo_config() -> GenericChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "ROC".into()); properties.insert("tokenDecimals".into(), 12.into()); - ContractsRococoChainSpec::from_genesis( - // Name - "Contracts on Rococo", - // ID - "contracts-rococo", - ChainType::Live, - move || { - contracts_rococo_genesis( - vec![ - // 5GKFbTTgrVS4Vz1UWWHPqMZQNFWZtqo7H2KpCDyYhEL3aS26 - ( - hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] - .into(), - hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] - .unchecked_into(), - ), - // 5EPRJHm2GpABVWcwnAujcrhnrjFZyDGd5TwKFzkBoGgdRyv2 - ( - hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] - .into(), - hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] - .unchecked_into(), - ), - // 5GH62vrJrVZxLREcHzm2PR5uTLAT5RQMJitoztCGyaP4o3uM - ( - hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] - .into(), - hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] - .unchecked_into(), - ), - // 5FHfoJDLdjRYX5KXLRqMDYBbWrwHLMtti21uK4QByUoUAbJF - ( - hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] - .into(), - hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] - .unchecked_into(), - ), - ], - // Warning: The configuration for a production chain should not contain - // any endowed accounts here, otherwise it'll be minting extra native tokens - // from the relay chain on the parachain. - vec![ - // NOTE: Remove endowed accounts if deployed on other relay chains. - // Endowed accounts - hex!["baa78c7154c7f82d6d377177e20bcab65d327eca0086513f9964f5a0f6bdad56"].into(), - // AccountId of an account which `ink-waterfall` uses for automated testing - hex!["0e47e2344d523c3cc5c34394b0d58b9a4200e813a038e6c5a6163cc07d70b069"].into(), - ], - CONTRACTS_PARACHAIN_ID.into(), - ) - }, - // Bootnodes - vec![ + GenericChainSpec::builder( + contracts_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "rococo".into(), para_id: CONTRACTS_PARACHAIN_ID } + ) + .with_name("Contracts on Rococo") + .with_id("contracts-rococo") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(contracts_rococo_genesis( + vec![ + // 5GKFbTTgrVS4Vz1UWWHPqMZQNFWZtqo7H2KpCDyYhEL3aS26 + ( + hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] + .into(), + hex!["bc09354c12c054c8f6b3da208485eacec4ac648bad348895273b37bab5a0937c"] + .unchecked_into(), + ), + // 5EPRJHm2GpABVWcwnAujcrhnrjFZyDGd5TwKFzkBoGgdRyv2 + ( + hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] + .into(), + hex!["66be63b7bcbfb91040e5248e2d1ceb822cf219c57848c5924ffa3a1f8e67ba72"] + .unchecked_into(), + ), + // 5GH62vrJrVZxLREcHzm2PR5uTLAT5RQMJitoztCGyaP4o3uM + ( + hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] + .into(), + hex!["ba62886472a0a9f66b5e39f1469ce1c5b3d8cad6be39078daf16f111e89d1e44"] + .unchecked_into(), + ), + // 5FHfoJDLdjRYX5KXLRqMDYBbWrwHLMtti21uK4QByUoUAbJF + ( + hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] + .into(), + hex!["8e97f65cda001976311df9bed39e8d0c956089093e94a75ef76fe9347a0eda7b"] + .unchecked_into(), + ), + ], + // Warning: The configuration for a production chain should not contain + // any endowed accounts here, otherwise it'll be minting extra native tokens + // from the relay chain on the parachain. + vec![ + // NOTE: Remove endowed accounts if deployed on other relay chains. + // Endowed accounts + hex!["baa78c7154c7f82d6d377177e20bcab65d327eca0086513f9964f5a0f6bdad56"].into(), + // AccountId of an account which `ink-waterfall` uses for automated testing + hex!["0e47e2344d523c3cc5c34394b0d58b9a4200e813a038e6c5a6163cc07d70b069"].into(), + ], + CONTRACTS_PARACHAIN_ID.into(), + )) + .with_boot_nodes(vec![ "/dns/contracts-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWKg3Rpxcr9oJ8n6khoxpGKWztCZydtUZk2cojHqnfLrpj" - .parse() - .expect("MultiaddrWithPeerId"), + .parse() + .expect("MultiaddrWithPeerId"), "/dns/contracts-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh" - .parse() - .expect("MultiaddrWithPeerId"), + .parse() + .expect("MultiaddrWithPeerId"), "/dns/contracts-collator-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWEVU8AFNary4nP4qEnEcwJaRuy59Wefekzdu9pKbnVEhk" - .parse() - .expect("MultiaddrWithPeerId"), + .parse() + .expect("MultiaddrWithPeerId"), "/dns/contracts-collator-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP6pV3ZmcXzGDjv8ZMgA6nZxfAKDxSz4VNiLx6vVCQgJX" - .parse() - .expect("MultiaddrWithPeerId"), - ], - // Telemetry - None, - // Protocol ID - None, - // Fork ID - None, - // Properties - Some(properties), - // Extensions - Extensions { relay_chain: "rococo".into(), para_id: CONTRACTS_PARACHAIN_ID }, - ) + .parse() + .expect("MultiaddrWithPeerId"), + ]) + .with_properties(properties) + .build() } fn contracts_rococo_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, -) -> contracts_rococo_runtime::RuntimeGenesisConfig { - contracts_rococo_runtime::RuntimeGenesisConfig { - system: contracts_rococo_runtime::SystemConfig { - code: contracts_rococo_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: contracts_rococo_runtime::BalancesConfig { - balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), +) -> serde_json::Value { + serde_json::json!( { + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), }, - parachain_info: contracts_rococo_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: contracts_rococo_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: CONTRACTS_ROCOCO_ED * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": CONTRACTS_ROCOCO_ED * 16, }, - session: contracts_rococo_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( @@ -264,21 +225,17 @@ fn contracts_rococo_genesis( contracts_rococo_runtime::SessionKeys { aura }, // session keys ) }) - .collect(), + .collect::>(), }, // no need to pass anything to aura, in fact it will panic if we do. Session will take care // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: contracts_rococo_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - sudo: contracts_rococo_runtime::SudoConfig { - key: Some( - hex!["2681a28014e7d3a5bfb32a003b3571f53c408acbc28d351d6bf58f5028c4ef14"].into(), - ), + "sudo": { + "key": Some(sp_runtime::AccountId32::from(hex![ + "2681a28014e7d3a5bfb32a003b3571f53c408acbc28d351d6bf58f5028c4ef14" + ])), }, - } + }) } 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..42d56fc80eb3eee2810da1a414a13e346d6f8d17 --- /dev/null +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -0,0 +1,298 @@ +// 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, Copy)] +pub enum CoretimeRuntimeType { + // Live + Rococo, + // Local + RococoLocal, + // Benchmarks + RococoDevelopment, + + // Live + Westend, + // 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 => Ok(CoretimeRuntimeType::Westend), + westend::CORETIME_WESTEND_LOCAL => Ok(CoretimeRuntimeType::WestendLocal), + westend::CORETIME_WESTEND_DEVELOPMENT => Ok(CoretimeRuntimeType::WestendDevelopment), + _ => Err(format!("Value '{}' is not configured yet", value)), + } + } +} + +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::Westend => westend::CORETIME_WESTEND, + 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 | CoretimeRuntimeType::Westend => 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!("../../chain-specs/coretime-rococo.json")[..], + )?)), + CoretimeRuntimeType::RococoLocal => + Ok(Box::new(rococo::local_config(*self, "rococo-local"))), + CoretimeRuntimeType::RococoDevelopment => + Ok(Box::new(rococo::local_config(*self, "rococo-dev"))), + CoretimeRuntimeType::Westend => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/coretime-westend.json")[..], + )?)), + CoretimeRuntimeType::WestendLocal => + Ok(Box::new(westend::local_config(*self, "westend-local"))), + CoretimeRuntimeType::WestendDevelopment => + 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 sc_chain_spec::ChainType; + 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 = coretime_rococo_runtime::ExistentialDeposit::get(); + + 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.into(); + let chain_name = format!("Coretime Rococo {}", chain_type_name(&chain_type)); + let para_id = super::CORETIME_PARA_ID; + + let wasm_binary = if matches!(chain_type, ChainType::Local | ChainType::Development) { + coretime_rococo_runtime::fast_runtime_binary::WASM_BINARY + .expect("WASM binary was not built, please build it!") + } else { + coretime_rococo_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!") + }; + + GenericChainSpec::builder( + wasm_binary, + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + .with_name(&chain_name) + .with_id(runtime_type.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: &str = "coretime-westend"; + pub(crate) const CORETIME_WESTEND_LOCAL: &str = "coretime-westend-local"; + pub(crate) const CORETIME_WESTEND_DEVELOPMENT: &str = "coretime-westend-dev"; + const CORETIME_WESTEND_ED: Balance = coretime_westend_runtime::ExistentialDeposit::get(); + + 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.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.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/glutton.rs b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs index 881fae398827a6adc1fd6195f9d69997a3a6e6af..77a4123b13ee11b0f158332c26a1695cfe37a668 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, Extensions}; +use crate::chain_spec::{get_account_id_from_seed, Extensions, GenericChainSpec}; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; use sc_service::ChainType; @@ -22,104 +22,82 @@ use sp_core::sr25519; use super::get_collator_keys_from_seed; -/// Specialized `ChainSpec` for the Glutton parachain runtime. -pub type GluttonChainSpec = - sc_service::GenericChainSpec; +fn glutton_genesis(parachain_id: ParaId, collators: Vec) -> serde_json::Value { + serde_json::json!( { + "parachainInfo": { + "parachainId": parachain_id + }, + "sudo": { + "key": Some(get_account_id_from_seed::("Alice")), + }, + "aura": { "authorities": collators }, + }) +} -pub fn glutton_development_config(para_id: ParaId) -> GluttonChainSpec { - GluttonChainSpec::from_genesis( - // Name - "Glutton Development", - // ID - "glutton_dev", - ChainType::Local, - move || glutton_genesis(para_id, vec![get_collator_keys_from_seed::("Alice")]), - Vec::new(), - None, - None, - None, - None, - Extensions { relay_chain: "kusama-dev".into(), para_id: para_id.into() }, +pub fn glutton_westend_development_config(para_id: ParaId) -> GenericChainSpec { + GenericChainSpec::builder( + glutton_westend_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "westend-dev".into(), para_id: para_id.into() }, ) + .with_name("Glutton Development") + .with_id("glutton_westend_dev") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(glutton_genesis( + para_id, + vec![get_collator_keys_from_seed::("Alice")], + )) + .build() } -pub fn glutton_local_config(para_id: ParaId) -> GluttonChainSpec { - GluttonChainSpec::from_genesis( - // Name - "Glutton Local", - // ID - "glutton_local", - ChainType::Local, - move || { - glutton_genesis( - para_id, - vec![ - get_collator_keys_from_seed::("Alice"), - get_collator_keys_from_seed::("Bob"), - ], - ) - }, - Vec::new(), - None, - None, - None, - None, - Extensions { relay_chain: "kusama-local".into(), para_id: para_id.into() }, +pub fn glutton_westend_local_config(para_id: ParaId) -> GenericChainSpec { + GenericChainSpec::builder( + glutton_westend_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "westend-local".into(), para_id: para_id.into() }, ) + .with_name("Glutton Local") + .with_id("glutton_westend_local") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(glutton_genesis( + para_id, + vec![ + get_collator_keys_from_seed::("Alice"), + get_collator_keys_from_seed::("Bob"), + ], + )) + .build() } -pub fn glutton_config(para_id: ParaId) -> GluttonChainSpec { +pub fn glutton_westend_config(para_id: ParaId) -> GenericChainSpec { let mut properties = sc_chain_spec::Properties::new(); - properties.insert("ss58Format".into(), 2.into()); + properties.insert("ss58Format".into(), 42.into()); - GluttonChainSpec::from_genesis( - // Name - format!("Glutton {}", para_id).as_str(), - // ID - format!("glutton-kusama-{}", para_id).as_str(), - ChainType::Live, - move || { - glutton_genesis( - para_id, - vec![ - get_collator_keys_from_seed::("Alice"), - get_collator_keys_from_seed::("Bob"), - ], - ) - }, - Vec::new(), - None, - // Protocol ID - Some(format!("glutton-kusama-{}", para_id).as_str()), - None, - Some(properties), - Extensions { relay_chain: "kusama".into(), para_id: para_id.into() }, + GenericChainSpec::builder( + glutton_westend_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: "westend".into(), para_id: para_id.into() }, ) + .with_name(format!("Glutton {}", para_id).as_str()) + .with_id(format!("glutton-westend-{}", para_id).as_str()) + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(glutton_westend_genesis( + para_id, + vec![ + get_collator_keys_from_seed::("Alice"), + get_collator_keys_from_seed::("Bob"), + ], + )) + .with_protocol_id(format!("glutton-westend-{}", para_id).as_str()) + .with_properties(properties) + .build() } -fn glutton_genesis( - parachain_id: ParaId, - collators: Vec, -) -> glutton_runtime::RuntimeGenesisConfig { - glutton_runtime::RuntimeGenesisConfig { - system: glutton_runtime::SystemConfig { - code: glutton_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - parachain_info: glutton_runtime::ParachainInfoConfig { parachain_id, ..Default::default() }, - parachain_system: Default::default(), - glutton: glutton_runtime::GluttonConfig { - compute: Default::default(), - storage: Default::default(), - trash_data_count: Default::default(), - ..Default::default() +fn glutton_westend_genesis(parachain_id: ParaId, collators: Vec) -> serde_json::Value { + serde_json::json!( { + "parachainInfo": { + "parachainId": parachain_id }, - aura: glutton_runtime::AuraConfig { authorities: collators }, - aura_ext: Default::default(), - sudo: glutton_runtime::SudoConfig { - key: Some(get_account_id_from_seed::("Alice")), + "sudo": { + "key": Some(get_account_id_from_seed::("Alice")), }, - } + "aura": { "authorities": collators }, + }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index 9cd0a37ad633ba069cdf8beae71d3324af4485c5..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; @@ -50,6 +52,9 @@ impl Extensions { } } +/// Generic chain spec for all polkadot-parachain runtimes +pub type GenericChainSpec = sc_service::GenericChainSpec<(), Extensions>; + /// Helper function to generate a crypto pair from seed pub fn get_from_seed(seed: &str) -> ::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) diff --git a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs index 479d1701e379ea4b2ce9d3520205365c1b07201f..cb1cb632d63843681a121261be4b83bf5fb88e32 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs @@ -15,101 +15,84 @@ // along with Cumulus. If not, see . use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, }; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; use sc_service::ChainType; use sp_core::sr25519; -/// Specialized `ChainSpec` for the normal parachain runtime. -pub type PenpalChainSpec = - sc_service::GenericChainSpec; -pub fn get_penpal_chain_spec(id: ParaId, relay_chain: &str) -> PenpalChainSpec { +pub fn get_penpal_chain_spec(id: ParaId, relay_chain: &str) -> GenericChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "UNIT".into()); properties.insert("tokenDecimals".into(), 12u32.into()); properties.insert("ss58Format".into(), 42u32.into()); - PenpalChainSpec::from_genesis( - // Name - "Penpal Parachain", - // ID - &format!("penpal-{}", relay_chain.replace("-local", "")), - ChainType::Development, - move || { - penpal_testnet_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"), - ], - id, - ) - }, - Vec::new(), - None, - None, - None, - None, + GenericChainSpec::builder( + penpal_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: relay_chain.into(), // You MUST set this to the correct network! para_id: id.into(), }, ) + .with_name("Penpal Parachain") + .with_id(&format!("penpal-{}", relay_chain.replace("-local", ""))) + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(penpal_testnet_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"), + ], + id, + )) + .build() } fn penpal_testnet_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, id: ParaId, -) -> penpal_runtime::RuntimeGenesisConfig { - penpal_runtime::RuntimeGenesisConfig { - system: penpal_runtime::SystemConfig { - code: penpal_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: penpal_runtime::BalancesConfig { - balances: endowed_accounts +) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts .iter() .cloned() .map(|k| (k, penpal_runtime::EXISTENTIAL_DEPOSIT * 4096)) - .collect(), + .collect::>(), }, - parachain_info: penpal_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "parachainInfo": { + "parachainId": id, }, - collator_selection: penpal_runtime::CollatorSelectionConfig { - invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect(), - candidacy_bond: penpal_runtime::EXISTENTIAL_DEPOSIT * 16, - ..Default::default() + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": penpal_runtime::EXISTENTIAL_DEPOSIT * 16, }, - session: penpal_runtime::SessionConfig { - keys: invulnerables + "session": { + "keys": invulnerables .into_iter() .map(|(acc, aura)| { ( @@ -118,21 +101,15 @@ fn penpal_testnet_genesis( penpal_session_keys(aura), // session keys ) }) - .collect(), + .collect::>(), }, - // no need to pass anything to aura, in fact it will panic if we do. Session will take care - // of this. - aura: Default::default(), - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: penpal_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - sudo: penpal_runtime::SudoConfig { - key: Some(get_account_id_from_seed::("Alice")), + "sudo": { + "key": Some(get_account_id_from_seed::("Alice")), }, - } + }) } /// Generate the session keys from individual elements. 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..67786e17205d86e5108721c6a6ef7209fe9297f2 --- /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::{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-dev"; + const PEOPLE_ROCOCO_ED: PeopleBalance = people_rococo_runtime::ExistentialDeposit::get(); + + 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::{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-dev"; + const PEOPLE_WESTEND_ED: PeopleBalance = people_westend_runtime::ExistentialDeposit::get(); + + 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/chain_spec/rococo_parachain.rs b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs index fb66efbf9ae1c68eedcc42b8ef6436b979885272..0434e5f7be8fb3ffb5f93cbb639f5c90469cf763 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs @@ -16,7 +16,7 @@ //! ChainSpecs dedicated to Rococo parachain setups (for testing and example purposes) -use crate::chain_spec::{get_from_seed, Extensions, SAFE_XCM_VERSION}; +use crate::chain_spec::{get_from_seed, Extensions, GenericChainSpec, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use hex_literal::hex; use parachains_common::AccountId; @@ -25,73 +25,59 @@ use rococo_parachain_runtime::AuraId; use sc_chain_spec::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; -pub type RococoParachainChainSpec = - sc_service::GenericChainSpec; - -pub fn rococo_parachain_local_config() -> RococoParachainChainSpec { - RococoParachainChainSpec::from_genesis( - "Rococo Parachain Local", - "local_testnet", - ChainType::Local, - move || { - testnet_genesis( - get_account_id_from_seed::("Alice"), - vec![get_from_seed::("Alice"), get_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"), - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - None, +pub fn rococo_parachain_local_config() -> GenericChainSpec { + GenericChainSpec::builder( + rococo_parachain_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-local".into(), para_id: 1000 }, ) + .with_name("Rococo Parachain Local") + .with_id("local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(testnet_genesis( + get_account_id_from_seed::("Alice"), + vec![get_from_seed::("Alice"), get_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"), + ], + 1000.into(), + )) + .build() } -pub fn staging_rococo_parachain_local_config() -> RococoParachainChainSpec { - RococoParachainChainSpec::from_genesis( - "Staging Rococo Parachain Local", - "staging_testnet", - ChainType::Live, - move || { - testnet_genesis( - hex!["9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00"].into(), - vec![ - // $secret//one - hex!["aad9fa2249f87a210a0f93400b7f90e47b810c6d65caa0ca3f5af982904c2a33"] - .unchecked_into(), - // $secret//two - hex!["d47753f0cca9dd8da00c70e82ec4fc5501a69c49a5952a643d18802837c88212"] - .unchecked_into(), - ], - vec![ - hex!["9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00"].into() - ], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - None, +pub fn staging_rococo_parachain_local_config() -> GenericChainSpec { + #[allow(deprecated)] + GenericChainSpec::builder( + rococo_parachain_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "rococo-local".into(), para_id: 1000 }, ) + .with_name("Staging Rococo Parachain Local") + .with_id("staging_testnet") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(testnet_genesis( + hex!["9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00"].into(), + vec![ + // $secret//one + hex!["aad9fa2249f87a210a0f93400b7f90e47b810c6d65caa0ca3f5af982904c2a33"] + .unchecked_into(), + // $secret//two + hex!["d47753f0cca9dd8da00c70e82ec4fc5501a69c49a5952a643d18802837c88212"] + .unchecked_into(), + ], + vec![hex!["9ed7705e3c7da027ba0583a22a3212042f7e715d3c168ba14f1424e2bc111d00"].into()], + 1000.into(), + )) + .build() } pub(crate) fn testnet_genesis( @@ -99,28 +85,18 @@ pub(crate) fn testnet_genesis( initial_authorities: Vec, endowed_accounts: Vec, id: ParaId, -) -> rococo_parachain_runtime::RuntimeGenesisConfig { - rococo_parachain_runtime::RuntimeGenesisConfig { - system: rococo_parachain_runtime::SystemConfig { - code: rococo_parachain_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - balances: rococo_parachain_runtime::BalancesConfig { - balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), +) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), }, - sudo: rococo_parachain_runtime::SudoConfig { key: Some(root_key) }, - parachain_info: rococo_parachain_runtime::ParachainInfoConfig { - parachain_id: id, - ..Default::default() + "sudo": { "key": Some(root_key) }, + "parachainInfo": { + "parachainId": id, }, - aura: rococo_parachain_runtime::AuraConfig { authorities: initial_authorities }, - aura_ext: Default::default(), - parachain_system: Default::default(), - polkadot_xcm: rococo_parachain_runtime::PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION), - ..Default::default() + "aura": { "authorities": initial_authorities }, + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), }, - } + }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs b/cumulus/polkadot-parachain/src/chain_spec/seedling.rs index 6a5842320976c3794557a38deffe4105af4245c5..32d51622054575d103cb5c3684a216a9dbde6556 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/seedling.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, Extensions}; +use crate::chain_spec::{get_account_id_from_seed, Extensions, GenericChainSpec}; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; use sc_service::ChainType; @@ -22,50 +22,33 @@ use sp_core::sr25519; use super::get_collator_keys_from_seed; -/// Specialized `ChainSpec` for the seedling parachain runtime. -pub type SeedlingChainSpec = - sc_service::GenericChainSpec; - -pub fn get_seedling_chain_spec() -> SeedlingChainSpec { - SeedlingChainSpec::from_genesis( - "Seedling Local Testnet", - "seedling_local_testnet", - ChainType::Local, - move || { - seedling_testnet_genesis( - get_account_id_from_seed::("Alice"), - 2000.into(), - vec![get_collator_keys_from_seed::("Alice")], - ) - }, - Vec::new(), - None, - None, - None, - None, +pub fn get_seedling_chain_spec() -> GenericChainSpec { + GenericChainSpec::builder( + seedling_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "westend".into(), para_id: 2000 }, ) + .with_name("Seedling Local Testnet") + .with_id("seedling_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(seedling_testnet_genesis( + get_account_id_from_seed::("Alice"), + 2000.into(), + vec![get_collator_keys_from_seed::("Alice")], + )) + .with_boot_nodes(Vec::new()) + .build() } fn seedling_testnet_genesis( root_key: AccountId, parachain_id: ParaId, collators: Vec, -) -> seedling_runtime::RuntimeGenesisConfig { - seedling_runtime::RuntimeGenesisConfig { - system: seedling_runtime::SystemConfig { - code: seedling_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - sudo: seedling_runtime::SudoConfig { key: Some(root_key) }, - parachain_info: seedling_runtime::ParachainInfoConfig { - parachain_id, - ..Default::default() +) -> serde_json::Value { + serde_json::json!({ + "sudo": { "key": Some(root_key) }, + "parachainInfo": { + "parachainId": parachain_id, }, - parachain_system: Default::default(), - aura: seedling_runtime::AuraConfig { authorities: collators }, - aura_ext: Default::default(), - } + "aura": { "authorities": collators }, + }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/shell.rs b/cumulus/polkadot-parachain/src/chain_spec/shell.rs index 0899c1af3b4d238262191436c0bacf80f557882b..e0a9875fb96f28870a726073d3c8b8cb249628a9 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/shell.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/shell.rs @@ -14,48 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::Extensions; +use crate::chain_spec::{Extensions, GenericChainSpec}; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; use sc_service::ChainType; use super::get_collator_keys_from_seed; -/// Specialized `ChainSpec` for the shell parachain runtime. -pub type ShellChainSpec = - sc_service::GenericChainSpec; - -pub fn get_shell_chain_spec() -> ShellChainSpec { - ShellChainSpec::from_genesis( - "Shell Local Testnet", - "shell_local_testnet", - ChainType::Local, - move || { - shell_testnet_genesis(1000.into(), vec![get_collator_keys_from_seed::("Alice")]) - }, - Vec::new(), - None, - None, - None, - None, +pub fn get_shell_chain_spec() -> GenericChainSpec { + GenericChainSpec::builder( + shell_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), Extensions { relay_chain: "westend".into(), para_id: 1000 }, ) + .with_name("Shell Local Testnet") + .with_id("shell_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(shell_testnet_genesis( + 1000.into(), + vec![get_collator_keys_from_seed::("Alice")], + )) + .with_boot_nodes(Vec::new()) + .build() } -fn shell_testnet_genesis( - parachain_id: ParaId, - collators: Vec, -) -> shell_runtime::RuntimeGenesisConfig { - shell_runtime::RuntimeGenesisConfig { - system: shell_runtime::SystemConfig { - code: shell_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - parachain_info: shell_runtime::ParachainInfoConfig { parachain_id, ..Default::default() }, - parachain_system: Default::default(), - aura: shell_runtime::AuraConfig { authorities: collators }, - aura_ext: Default::default(), - } +fn shell_testnet_genesis(parachain_id: ParaId, collators: Vec) -> serde_json::Value { + serde_json::json!({ + "parachainInfo": { "parachainId": parachain_id}, + "aura": { "authorities": collators }, + }) } 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 870e45e1d55141a778add519893765d7c59fe3cc..9ba7b7876b3ee06f0d61c626d4fa3c234e313ff9 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -16,9 +16,14 @@ use crate::{ chain_spec, + chain_spec::GenericChainSpec, cli::{Cli, RelayChainCli, Subcommand}, + fake_runtime_api::{ + asset_hub_polkadot_aura::RuntimeApi as AssetHubPolkadotRuntimeApi, aura::RuntimeApi, + }, service::{new_partial, Block}, }; +use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; @@ -43,40 +48,42 @@ enum Runtime { AssetHubPolkadot, AssetHubKusama, AssetHubRococo, - AssetHubWococo, AssetHubWestend, Penpal(ParaId), ContractsRococo, CollectivesPolkadot, CollectivesWestend, Glutton, + GluttonWestend, BridgeHub(chain_spec::bridge_hubs::BridgeHubRuntimeType), + Coretime(chain_spec::coretime::CoretimeRuntimeType), + People(chain_spec::people::PeopleRuntimeType), } trait RuntimeResolver { - fn runtime(&self) -> Runtime; + fn runtime(&self) -> Result; } impl RuntimeResolver for dyn ChainSpec { - fn runtime(&self) -> Runtime { - runtime(self.id()) + fn runtime(&self) -> Result { + Ok(runtime(self.id())) } } /// Implementation, that can resolve [`Runtime`] from any json configuration file impl RuntimeResolver for PathBuf { - fn runtime(&self) -> Runtime { + fn runtime(&self) -> Result { #[derive(Debug, serde::Deserialize)] struct EmptyChainSpecWithId { id: String, } - let file = std::fs::File::open(self).expect("Failed to open file"); + let file = std::fs::File::open(self)?; let reader = std::io::BufReader::new(file); - let chain_spec: EmptyChainSpecWithId = serde_json::from_reader(reader) - .expect("Failed to read 'json' file with ChainSpec configuration"); + let chain_spec: EmptyChainSpecWithId = + serde_json::from_reader(reader).map_err(|e| sc_cli::Error::Application(Box::new(e)))?; - runtime(&chain_spec.id) + Ok(runtime(&chain_spec.id)) } } @@ -94,8 +101,6 @@ fn runtime(id: &str) -> Runtime { Runtime::AssetHubKusama } else if id.starts_with("asset-hub-rococo") { Runtime::AssetHubRococo - } else if id.starts_with("asset-hub-wococo") { - Runtime::AssetHubWococo } else if id.starts_with("asset-hub-westend") | id.starts_with("westmint") { Runtime::AssetHubWestend } else if id.starts_with("penpal") { @@ -111,8 +116,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() @@ -125,50 +138,29 @@ fn load_spec(id: &str) -> std::result::Result, String> { // - Defaul-like "staging" => Box::new(chain_spec::rococo_parachain::staging_rococo_parachain_local_config()), - "tick" => - Box::new(chain_spec::rococo_parachain::RococoParachainChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/tick.json")[..], - )?), - "trick" => - Box::new(chain_spec::rococo_parachain::RococoParachainChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/trick.json")[..], - )?), - "track" => - Box::new(chain_spec::rococo_parachain::RococoParachainChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/track.json")[..], - )?), + "tick" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/tick.json")[..], + )?), + "trick" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/trick.json")[..], + )?), + "track" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/track.json")[..], + )?), // -- Starters "shell" => Box::new(chain_spec::shell::get_shell_chain_spec()), "seedling" => Box::new(chain_spec::seedling::get_seedling_chain_spec()), // -- Asset Hub Polkadot - "asset-hub-polkadot-dev" | "statemint-dev" => - Box::new(chain_spec::asset_hubs::asset_hub_polkadot_development_config()), - "asset-hub-polkadot-local" | "statemint-local" => - Box::new(chain_spec::asset_hubs::asset_hub_polkadot_local_config()), - // the chain spec as used for generating the upgrade genesis values - "asset-hub-polkadot-genesis" | "statemint-genesis" => - Box::new(chain_spec::asset_hubs::asset_hub_polkadot_config()), - // the shell-based chain spec as used for syncing - "asset-hub-polkadot" | "statemint" => - Box::new(chain_spec::asset_hubs::AssetHubPolkadotChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-polkadot.json")[..], - )?), + "asset-hub-polkadot" | "statemint" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/asset-hub-polkadot.json")[..], + )?), // -- Asset Hub Kusama - "asset-hub-kusama-dev" | "statemine-dev" => - Box::new(chain_spec::asset_hubs::asset_hub_kusama_development_config()), - "asset-hub-kusama-local" | "statemine-local" => - Box::new(chain_spec::asset_hubs::asset_hub_kusama_local_config()), - // the chain spec as used for generating the upgrade genesis values - "asset-hub-kusama-genesis" | "statemine-genesis" => - Box::new(chain_spec::asset_hubs::asset_hub_kusama_config()), - // the shell-based chain spec as used for syncing - "asset-hub-kusama" | "statemine" => - Box::new(chain_spec::asset_hubs::AssetHubKusamaChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-kusama.json")[..], - )?), + "asset-hub-kusama" | "statemine" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/asset-hub-kusama.json")[..], + )?), // -- Asset Hub Rococo "asset-hub-rococo-dev" => @@ -178,23 +170,9 @@ fn load_spec(id: &str) -> std::result::Result, String> { // the chain spec as used for generating the upgrade genesis values "asset-hub-rococo-genesis" => Box::new(chain_spec::asset_hubs::asset_hub_rococo_genesis_config()), - "asset-hub-rococo" => - Box::new(chain_spec::asset_hubs::AssetHubRococoChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-rococo.json")[..], - )?), - - // -- Asset Hub Wococo - "asset-hub-wococo-dev" => - Box::new(chain_spec::asset_hubs::asset_hub_wococo_development_config()), - "asset-hub-wococo-local" => - Box::new(chain_spec::asset_hubs::asset_hub_wococo_local_config()), - // the chain spec as used for generating the upgrade genesis values - "asset-hub-wococo-genesis" => - Box::new(chain_spec::asset_hubs::asset_hub_wococo_genesis_config()), - "asset-hub-wococo" => - Box::new(chain_spec::asset_hubs::AssetHubWococoChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-wococo.json")[..], - )?), + "asset-hub-rococo" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/asset-hub-rococo.json")[..], + )?), // -- Asset Hub Westend "asset-hub-westend-dev" | "westmint-dev" => @@ -205,24 +183,23 @@ fn load_spec(id: &str) -> std::result::Result, String> { "asset-hub-westend-genesis" | "westmint-genesis" => Box::new(chain_spec::asset_hubs::asset_hub_westend_config()), // the shell-based chain spec as used for syncing - "asset-hub-westend" | "westmint" => - Box::new(chain_spec::asset_hubs::AssetHubWestendChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-westend.json")[..], - )?), + "asset-hub-westend" | "westmint" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/asset-hub-westend.json")[..], + )?), // -- Polkadot Collectives - "collectives-polkadot-dev" => - Box::new(chain_spec::collectives::collectives_polkadot_development_config()), - "collectives-polkadot-local" => - Box::new(chain_spec::collectives::collectives_polkadot_local_config()), - "collectives-polkadot" => - Box::new(chain_spec::collectives::CollectivesPolkadotChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/collectives-polkadot.json")[..], - )?), - "collectives-westend" => - Box::new(chain_spec::collectives::CollectivesPolkadotChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/collectives-westend.json")[..], - )?), + "collectives-polkadot" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/collectives-polkadot.json")[..], + )?), + + // -- Westend Collectives + "collectives-westend-dev" => + Box::new(chain_spec::collectives::collectives_westend_development_config()), + "collectives-westend-local" => + Box::new(chain_spec::collectives::collectives_westend_local_config()), + "collectives-westend" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/collectives-westend.json")[..], + )?), // -- Contracts on Rococo "contracts-rococo-dev" => @@ -230,10 +207,9 @@ fn load_spec(id: &str) -> std::result::Result, String> { "contracts-rococo-local" => Box::new(chain_spec::contracts::contracts_rococo_local_config()), "contracts-rococo-genesis" => Box::new(chain_spec::contracts::contracts_rococo_config()), - "contracts-rococo" => - Box::new(chain_spec::contracts::ContractsRococoChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/contracts-rococo.json")[..], - )?), + "contracts-rococo" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../chain-specs/contracts-rococo.json")[..], + )?), // -- BridgeHub bridge_like_id @@ -244,28 +220,45 @@ fn load_spec(id: &str) -> std::result::Result, String> { .expect("invalid value") .load_config()?, - // -- Penpall - "penpal-kusama" => Box::new(chain_spec::penpal::get_penpal_chain_spec( + // -- 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"), - "kusama-local", + "rococo-local", )), - "penpal-polkadot" => Box::new(chain_spec::penpal::get_penpal_chain_spec( + "penpal-westend" => Box::new(chain_spec::penpal::get_penpal_chain_spec( para_id.expect("Must specify parachain id"), - "polkadot-local", + "westend-local", )), - // -- Glutton - "glutton-kusama-dev" => Box::new(chain_spec::glutton::glutton_development_config( + // -- Glutton Westend + "glutton-westend-dev" => Box::new(chain_spec::glutton::glutton_westend_development_config( para_id.expect("Must specify parachain id"), )), - "glutton-kusama-local" => Box::new(chain_spec::glutton::glutton_local_config( + "glutton-westend-local" => Box::new(chain_spec::glutton::glutton_westend_local_config( para_id.expect("Must specify parachain id"), )), // the chain spec as used for generating the upgrade genesis values - "glutton-kusama-genesis" => Box::new(chain_spec::glutton::glutton_config( + "glutton-westend-genesis" => Box::new(chain_spec::glutton::glutton_westend_config( 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"); @@ -273,41 +266,7 @@ fn load_spec(id: &str) -> std::result::Result, String> { }, // -- Loading a specific spec from disk - path => { - let path: PathBuf = path.into(); - match path.runtime() { - Runtime::AssetHubPolkadot => Box::new( - chain_spec::asset_hubs::AssetHubPolkadotChainSpec::from_json_file(path)?, - ), - Runtime::AssetHubKusama => - Box::new(chain_spec::asset_hubs::AssetHubKusamaChainSpec::from_json_file(path)?), - Runtime::AssetHubRococo => - Box::new(chain_spec::asset_hubs::AssetHubRococoChainSpec::from_json_file(path)?), - Runtime::AssetHubWococo => - Box::new(chain_spec::asset_hubs::AssetHubWococoChainSpec::from_json_file(path)?), - Runtime::AssetHubWestend => Box::new( - chain_spec::asset_hubs::AssetHubWestendChainSpec::from_json_file(path)?, - ), - Runtime::CollectivesPolkadot | Runtime::CollectivesWestend => Box::new( - chain_spec::collectives::CollectivesPolkadotChainSpec::from_json_file(path)?, - ), - Runtime::Shell => - Box::new(chain_spec::shell::ShellChainSpec::from_json_file(path)?), - Runtime::Seedling => - Box::new(chain_spec::seedling::SeedlingChainSpec::from_json_file(path)?), - Runtime::ContractsRococo => - Box::new(chain_spec::contracts::ContractsRococoChainSpec::from_json_file(path)?), - Runtime::BridgeHub(bridge_hub_runtime_type) => - bridge_hub_runtime_type.chain_spec_from_json_file(path)?, - Runtime::Penpal(_para_id) => - Box::new(chain_spec::penpal::PenpalChainSpec::from_json_file(path)?), - Runtime::Glutton => - Box::new(chain_spec::glutton::GluttonChainSpec::from_json_file(path)?), - Runtime::Default => Box::new( - chain_spec::rococo_parachain::RococoParachainChainSpec::from_json_file(path)?, - ), - } - }, + path => Box::new(GenericChainSpec::from_json_file(path.into())?), }) } @@ -315,6 +274,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-"; @@ -322,7 +282,14 @@ fn extract_parachain_id(id: &str) -> (&str, &str, Option) { const GLUTTON_PARA_LOCAL_PREFIX: &str = "glutton-kusama-local-"; const GLUTTON_PARA_GENESIS_PREFIX: &str = "glutton-kusama-genesis-"; - let (norm_id, orig_id, para) = if let Some(suffix) = id.strip_prefix(KUSAMA_TEST_PARA_PREFIX) { + const GLUTTON_WESTEND_PARA_DEV_PREFIX: &str = "glutton-westend-dev-"; + 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(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) { @@ -337,6 +304,15 @@ fn extract_parachain_id(id: &str) -> (&str, &str, Option) { } else if let Some(suffix) = id.strip_prefix(GLUTTON_PARA_GENESIS_PREFIX) { let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); (&id[..GLUTTON_PARA_GENESIS_PREFIX.len() - 1], id, Some(para_id)) + } else if let Some(suffix) = id.strip_prefix(GLUTTON_WESTEND_PARA_DEV_PREFIX) { + let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); + (&id[..GLUTTON_WESTEND_PARA_DEV_PREFIX.len() - 1], id, Some(para_id)) + } else if let Some(suffix) = id.strip_prefix(GLUTTON_WESTEND_PARA_LOCAL_PREFIX) { + let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); + (&id[..GLUTTON_WESTEND_PARA_LOCAL_PREFIX.len() - 1], id, Some(para_id)) + } else if let Some(suffix) = id.strip_prefix(GLUTTON_WESTEND_PARA_GENESIS_PREFIX) { + let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); + (&id[..GLUTTON_WESTEND_PARA_GENESIS_PREFIX.len() - 1], id, Some(para_id)) } else { (id, id, None) }; @@ -419,118 +395,39 @@ impl SubstrateCli for RelayChainCli { /// Creates partial components for the runtimes that are supported by the benchmarks. macro_rules! construct_partials { ($config:expr, |$partials:ident| $code:expr) => { - match $config.chain_spec.runtime() { - Runtime::AssetHubKusama => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - Runtime::AssetHubRococo | Runtime::AssetHubWococo => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - Runtime::AssetHubWestend => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, + match $config.chain_spec.runtime()? { Runtime::AssetHubPolkadot => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AssetHubPolkadotAuraId>, - )?; - $code - }, - Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotDevelopment => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaDevelopment => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | - chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoDevelopment => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | - chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - }, - Runtime::CollectivesPolkadot | Runtime::CollectivesWestend => { - let $partials = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - $code - }, - Runtime::Shell => { - let $partials = new_partial::( - &$config, - crate::service::shell_build_import_queue, - )?; - $code - }, - Runtime::Seedling => { - let $partials = new_partial::( + let $partials = new_partial::( &$config, - crate::service::shell_build_import_queue, + crate::service::build_relay_to_aura_import_queue::<_, AssetHubPolkadotAuraId>, )?; $code }, - Runtime::ContractsRococo => { - let $partials = new_partial::( + Runtime::AssetHubKusama | + Runtime::AssetHubRococo | + Runtime::AssetHubWestend | + Runtime::BridgeHub(_) | + Runtime::CollectivesPolkadot | + Runtime::CollectivesWestend | + Runtime::Coretime(_) | + Runtime::People(_) => { + let $partials = new_partial::( &$config, - crate::service::contracts_rococo_build_import_queue, + crate::service::build_relay_to_aura_import_queue::<_, AuraId>, )?; $code }, - Runtime::Penpal(_) | Runtime::Default => { - let $partials = new_partial::( + Runtime::GluttonWestend | Runtime::Glutton | Runtime::Shell | Runtime::Seedling => { + let $partials = new_partial::( &$config, - crate::service::rococo_parachain_build_import_queue, + crate::service::build_shell_import_queue, )?; $code }, - Runtime::Glutton => { - let $partials = new_partial::( + Runtime::ContractsRococo | Runtime::Penpal(_) | Runtime::Default => { + let $partials = new_partial::( &$config, - crate::service::shell_build_import_queue, + crate::service::build_aura_import_queue, )?; $code }, @@ -541,182 +438,67 @@ macro_rules! construct_partials { macro_rules! construct_async_run { (|$components:ident, $cli:ident, $cmd:ident, $config:ident| $( $code:tt )* ) => {{ let runner = $cli.create_runner($cmd)?; - match runner.config().chain_spec.runtime() { - Runtime::AssetHubWestend => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - Runtime::AssetHubRococo | Runtime::AssetHubWococo => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - Runtime::AssetHubKusama => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, + match runner.config().chain_spec.runtime()? { Runtime::AssetHubPolkadot => { runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AssetHubPolkadotAuraId>, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - Runtime::CollectivesPolkadot | Runtime::CollectivesWestend => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - Runtime::Shell => { - runner.async_run(|$config| { - let $components = new_partial::( + let $components = new_partial::( &$config, - crate::service::shell_build_import_queue, + crate::service::build_relay_to_aura_import_queue::<_, AssetHubPolkadotAuraId>, )?; let task_manager = $components.task_manager; { $( $code )* }.map(|v| (v, task_manager)) }) }, - Runtime::Seedling => { + Runtime::AssetHubKusama | + Runtime::AssetHubRococo | + Runtime::AssetHubWestend | + Runtime::BridgeHub(_) | + Runtime::CollectivesPolkadot | + Runtime::CollectivesWestend | + Runtime::Coretime(_) | + Runtime::People(_) => { runner.async_run(|$config| { - let $components = new_partial::( + let $components = new_partial::( &$config, - crate::service::shell_build_import_queue, + crate::service::build_relay_to_aura_import_queue::<_, AuraId>, )?; let task_manager = $components.task_manager; { $( $code )* }.map(|v| (v, task_manager)) }) }, - Runtime::ContractsRococo => { + Runtime::Shell | + Runtime::Seedling | + Runtime::GluttonWestend | + Runtime::Glutton => { runner.async_run(|$config| { - let $components = new_partial::( + let $components = new_partial::( &$config, - crate::service::contracts_rococo_build_import_queue, + crate::service::build_shell_import_queue, )?; let task_manager = $components.task_manager; { $( $code )* }.map(|v| (v, task_manager)) }) - }, - Runtime::BridgeHub(bridge_hub_runtime_type) => { - match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotDevelopment => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaDevelopment => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | - chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoDevelopment => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - }, - chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | - chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::aura_build_import_queue::<_, AuraId>, - )?; - - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - } - } - }, - Runtime::Penpal(_) | Runtime::Default => { + } + Runtime::ContractsRococo | Runtime::Penpal(_) | Runtime::Default => { runner.async_run(|$config| { let $components = new_partial::< - rococo_parachain_runtime::RuntimeApi, + RuntimeApi, _, >( &$config, - crate::service::rococo_parachain_build_import_queue, + crate::service::build_aura_import_queue, )?; let task_manager = $components.task_manager; { $( $code )* }.map(|v| (v, task_manager)) }) }, - Runtime::Glutton => { - runner.async_run(|$config| { - let $components = new_partial::( - &$config, - crate::service::shell_build_import_queue, - )?; - let task_manager = $components.task_manager; - { $( $code )* }.map(|v| (v, task_manager)) - }) - } } }} } /// Parse command line arguments into service configuration. pub fn run() -> Result<()> { + use Runtime::*; let cli = Cli::from_args(); match &cli.subcommand { @@ -766,10 +548,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)) => { @@ -786,7 +568,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::, ReclaimHostFunctions>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." @@ -832,31 +614,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( @@ -888,66 +670,58 @@ pub fn run() -> Result<()> { info!("Parachain Account: {}", parachain_account); info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); - match config.chain_spec.runtime() { - Runtime::AssetHubPolkadot => crate::service::start_asset_hub_node::< - asset_hub_polkadot_runtime::RuntimeApi, + match config.chain_spec.runtime()? { + 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::< - asset_hub_kusama_runtime::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::AssetHubRococo | Runtime::AssetHubWococo => crate::service::start_asset_hub_node::< - asset_hub_rococo_runtime::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + + 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::AssetHubWestend => crate::service::start_asset_hub_node::< - asset_hub_westend_runtime::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::CollectivesPolkadot | Runtime::CollectivesWestend => - crate::service::start_generic_aura_node::< - collectives_polkadot_runtime::RuntimeApi, + + AssetHubRococo | AssetHubWestend => + crate::service::start_asset_hub_lookahead_node::< + RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0) .map_err(Into::into), - Runtime::Shell => - crate::service::start_shell_node::( - config, - polkadot_config, - collator_options, - id, - hwbench, - ) + + CollectivesPolkadot => + crate::service::start_generic_aura_node(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0) + .map_err(Into::into), + + CollectivesWestend => + crate::service::start_generic_aura_lookahead_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0) .map_err(Into::into), - Runtime::Seedling => - crate::service::start_shell_node::( + + Seedling | Shell => + crate::service::start_shell_node( config, polkadot_config, collator_options, id, - hwbench + 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, @@ -957,52 +731,47 @@ 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 | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotDevelopment => - crate::service::start_generic_aura_node::< - chain_spec::bridge_hubs::polkadot::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal => + crate::service::start_generic_aura_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal | - chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaDevelopment => - crate::service::start_generic_aura_node::< - chain_spec::bridge_hubs::kusama::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal => + crate::service::start_generic_aura_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), - chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend => - crate::service::start_generic_aura_node::< - chain_spec::bridge_hubs::westend::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend | + chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendLocal | + chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendDevelopment => + crate::service::start_generic_aura_lookahead_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoDevelopment => - crate::service::start_generic_aura_node::< - chain_spec::bridge_hubs::rococo::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + crate::service::start_generic_aura_lookahead_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), - chain_spec::bridge_hubs::BridgeHubRuntimeType::Wococo | - chain_spec::bridge_hubs::BridgeHubRuntimeType::WococoLocal => - crate::service::start_generic_aura_node::< - chain_spec::bridge_hubs::wococo::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + } + .map_err(Into::into), + + 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::Westend | + chain_spec::coretime::CoretimeRuntimeType::WestendLocal | + chain_spec::coretime::CoretimeRuntimeType::WestendDevelopment => + crate::service::start_generic_aura_lookahead_node(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), } .map_err(Into::into), - Runtime::Penpal(_) | Runtime::Default => + + Penpal(_) | Default => crate::service::start_rococo_parachain_node( config, polkadot_config, @@ -1013,14 +782,25 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), - Runtime::Glutton => - crate::service::start_basic_lookahead_node::< - glutton_runtime::RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) + + Glutton | GluttonWestend => + crate::service::start_basic_lookahead_node(config, polkadot_config, collator_options, id, hwbench) .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_lookahead_node(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0), + } + .map_err(Into::into), } }) }, @@ -1194,35 +974,30 @@ mod tests { cfg_file_path } - pub type DummyChainSpec = - sc_service::GenericChainSpec; + pub type DummyChainSpec = sc_service::GenericChainSpec<(), E>; pub fn create_default_with_extensions( id: &str, extension: E, ) -> DummyChainSpec { - DummyChainSpec::from_genesis( - "Dummy local testnet", - id, - ChainType::Local, - move || { - crate::chain_spec::rococo_parachain::testnet_genesis( - get_account_id_from_seed::("Alice"), - vec![ - get_from_seed::("Alice"), - get_from_seed::("Bob"), - ], - vec![get_account_id_from_seed::("Alice")], - 1000.into(), - ) - }, - Vec::new(), - None, - None, - None, - None, + DummyChainSpec::builder( + rococo_parachain_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), extension, ) + .with_name("Dummy local testnet") + .with_id(id) + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(crate::chain_spec::rococo_parachain::testnet_genesis( + get_account_id_from_seed::("Alice"), + vec![ + get_from_seed::("Alice"), + get_from_seed::("Bob"), + ], + vec![get_account_id_from_seed::("Alice")], + 1000.into(), + )) + .build() } #[test] @@ -1233,36 +1008,30 @@ mod tests { &temp_dir, Box::new(create_default_with_extensions("shell-1", Extensions1::default())), ); - assert_eq!(Runtime::Shell, path.runtime()); + assert_eq!(Runtime::Shell, path.runtime().unwrap()); let path = store_configuration( &temp_dir, Box::new(create_default_with_extensions("shell-2", Extensions2::default())), ); - assert_eq!(Runtime::Shell, path.runtime()); + assert_eq!(Runtime::Shell, path.runtime().unwrap()); let path = store_configuration( &temp_dir, Box::new(create_default_with_extensions("seedling", Extensions2::default())), ); - assert_eq!(Runtime::Seedling, path.runtime()); + assert_eq!(Runtime::Seedling, path.runtime().unwrap()); let path = store_configuration( &temp_dir, Box::new(crate::chain_spec::rococo_parachain::rococo_parachain_local_config()), ); - assert_eq!(Runtime::Default, path.runtime()); - - let path = store_configuration( - &temp_dir, - Box::new(crate::chain_spec::asset_hubs::asset_hub_kusama_local_config()), - ); - assert_eq!(Runtime::AssetHubKusama, path.runtime()); + assert_eq!(Runtime::Default, path.runtime().unwrap()); let path = store_configuration( &temp_dir, Box::new(crate::chain_spec::contracts::contracts_rococo_local_config()), ); - assert_eq!(Runtime::ContractsRococo, path.runtime()); + assert_eq!(Runtime::ContractsRococo, path.runtime().unwrap()); } } diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs new file mode 100644 index 0000000000000000000000000000000000000000..7778d1bf7d2dc0187fe6a0684023a9a4648be596 --- /dev/null +++ b/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs @@ -0,0 +1,200 @@ +// 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 . + +//! These are used to provide a type that implements these runtime APIs without requiring to import +//! the native runtimes. + +use frame_support::weights::Weight; +use parachains_common::{AccountId, AssetHubPolkadotAuraId, Balance, Nonce}; +use polkadot_primitives::Block; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; + +pub struct Runtime; + +sp_api::impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + + fn execute_block(_: Block) { + unimplemented!() + } + + fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + unimplemented!() + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + unimplemented!() + } + + fn metadata_at_version(_: u32) -> Option { + unimplemented!() + } + + fn metadata_versions() -> sp_std::vec::Vec { + unimplemented!() + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + unimplemented!() + } + + fn authorities() -> Vec { + unimplemented!() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + _: ::Hash, + _: cumulus_primitives_aura::Slot, + ) -> bool { + unimplemented!() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(_: ::Extrinsic) -> ApplyExtrinsicResult { + unimplemented!() + } + + fn finalize_block() -> ::Header { + unimplemented!() + } + + fn inherent_extrinsics(_: sp_inherents::InherentData) -> Vec<::Extrinsic> { + unimplemented!() + } + + fn check_inherents(_: Block, _: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { + unimplemented!() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + _: TransactionSource, + _: ::Extrinsic, + _: ::Hash, + ) -> TransactionValidity { + unimplemented!() + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(_: &::Header) { + unimplemented!() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(_: Option>) -> Vec { + unimplemented!() + } + + fn decode_session_keys( + _: Vec, + ) -> Option, KeyTypeId)>> { + unimplemented!() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + _: ::Extrinsic, + _: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unimplemented!() + } + fn query_fee_details( + _: ::Extrinsic, + _: u32, + ) -> pallet_transaction_payment::FeeDetails { + unimplemented!() + } + fn query_weight_to_fee(_: Weight) -> Balance { + unimplemented!() + } + fn query_length_to_fee(_: u32) -> Balance { + unimplemented!() + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(_: &::Header) -> cumulus_primitives_core::CollationInfo { + unimplemented!() + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(_: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + unimplemented!() + } + + fn execute_block( + _: Block, + _: bool, + _: bool, + _: frame_try_runtime::TryStateSelect, + ) -> Weight { + unimplemented!() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(_: AccountId) -> Nonce { + unimplemented!() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(_: bool) -> ( + Vec, + Vec, + ) { + unimplemented!() + } + + fn dispatch_benchmark( + _: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + unimplemented!() + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + unimplemented!() + } + + fn build_config(_: Vec) -> sp_genesis_builder::Result { + unimplemented!() + } + } +} diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs new file mode 100644 index 0000000000000000000000000000000000000000..880f5d760c74559db87597bce158f8f5e62dbd82 --- /dev/null +++ b/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs @@ -0,0 +1,200 @@ +// 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 . + +//! These are used to provide a type that implements these runtime APIs without requiring to import +//! the native runtimes. + +use frame_support::weights::Weight; +use parachains_common::{AccountId, AuraId, Balance, Nonce}; +use polkadot_primitives::Block; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; + +pub struct Runtime; + +sp_api::impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + + fn execute_block(_: Block) { + unimplemented!() + } + + fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + unimplemented!() + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + unimplemented!() + } + + fn metadata_at_version(_: u32) -> Option { + unimplemented!() + } + + fn metadata_versions() -> sp_std::vec::Vec { + unimplemented!() + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + unimplemented!() + } + + fn authorities() -> Vec { + unimplemented!() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + _: ::Hash, + _: cumulus_primitives_aura::Slot, + ) -> bool { + unimplemented!() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(_: ::Extrinsic) -> ApplyExtrinsicResult { + unimplemented!() + } + + fn finalize_block() -> ::Header { + unimplemented!() + } + + fn inherent_extrinsics(_: sp_inherents::InherentData) -> Vec<::Extrinsic> { + unimplemented!() + } + + fn check_inherents(_: Block, _: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { + unimplemented!() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + _: TransactionSource, + _: ::Extrinsic, + _: ::Hash, + ) -> TransactionValidity { + unimplemented!() + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(_: &::Header) { + unimplemented!() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(_: Option>) -> Vec { + unimplemented!() + } + + fn decode_session_keys( + _: Vec, + ) -> Option, KeyTypeId)>> { + unimplemented!() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + _: ::Extrinsic, + _: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unimplemented!() + } + fn query_fee_details( + _: ::Extrinsic, + _: u32, + ) -> pallet_transaction_payment::FeeDetails { + unimplemented!() + } + fn query_weight_to_fee(_: Weight) -> Balance { + unimplemented!() + } + fn query_length_to_fee(_: u32) -> Balance { + unimplemented!() + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(_: &::Header) -> cumulus_primitives_core::CollationInfo { + unimplemented!() + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(_: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + unimplemented!() + } + + fn execute_block( + _: Block, + _: bool, + _: bool, + _: frame_try_runtime::TryStateSelect, + ) -> Weight { + unimplemented!() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(_: AccountId) -> Nonce { + unimplemented!() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(_: bool) -> ( + Vec, + Vec, + ) { + unimplemented!() + } + + fn dispatch_benchmark( + _: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + unimplemented!() + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + unimplemented!() + } + + fn build_config(_: Vec) -> sp_genesis_builder::Result { + unimplemented!() + } + } +} diff --git a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/mod.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs similarity index 76% rename from cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/mod.rs rename to cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs index 955010505d310cffe1660fcec0dace4c2d613181..29e2736b06ff3a1092211224abd73092bd7ee46c 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-kusama/src/weights/mod.rs +++ b/cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs @@ -14,5 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -pub mod pallet_glutton; -pub mod pallet_timestamp; +//! In an ideal world this would be one runtime which would simplify the code massively. +//! This is not an ideal world - Polkadot Asset Hub has a different key type. + +pub mod asset_hub_polkadot_aura; +pub mod aura; diff --git a/cumulus/polkadot-parachain/src/main.rs b/cumulus/polkadot-parachain/src/main.rs index e40af8128f7166697a7e4f7571dd541a11d626a8..0757bea84aae83b64ec24982874c28f095057e75 100644 --- a/cumulus/polkadot-parachain/src/main.rs +++ b/cumulus/polkadot-parachain/src/main.rs @@ -20,11 +20,11 @@ #![warn(unused_extern_crates)] mod chain_spec; -#[macro_use] -mod service; mod cli; mod command; +mod fake_runtime_api; mod rpc; +mod service; fn main() -> sc_cli::Result<()> { command::run() 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 438d09a4c7771a248c99944a91cc0fd703746511..ddf595ca70c1d675aa66a94daf21036cc8a3295e 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -36,12 +36,13 @@ use cumulus_primitives_core::{ ParaId, }; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use sc_rpc::DenyUnsafe; use sp_core::Pair; use jsonrpsee::RpcModule; -use crate::rpc; -pub use parachains_common::{AccountId, Balance, Block, BlockNumber, Hash, Header, Nonce}; +use crate::{fake_runtime_api::aura::RuntimeApi as FakeRuntimeApi, rpc}; +pub use parachains_common::{AccountId, AuraId, Balance, Block, Hash, Header, Nonce}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; use futures::{lock::Mutex, prelude::*}; @@ -68,11 +69,15 @@ use substrate_prometheus_endpoint::Registry; use polkadot_primitives::CollatorPair; #[cfg(not(feature = "runtime-benchmarks"))] -type HostFunctions = sp_io::SubstrateHostFunctions; +type HostFunctions = + (sp_io::SubstrateHostFunctions, cumulus_client_service::storage_proof_size::HostFunctions); #[cfg(feature = "runtime-benchmarks")] -type HostFunctions = - (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); +type HostFunctions = ( + sp_io::SubstrateHostFunctions, + cumulus_client_service::storage_proof_size::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); type ParachainClient = TFullClient>; @@ -81,155 +86,15 @@ type ParachainBackend = TFullBackend; type ParachainBlockImport = TParachainBlockImport>, ParachainBackend>; -/// Native executor instance. -pub struct ShellRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for ShellRuntimeExecutor { - type ExtendHostFunctions = (); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - shell_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - shell_runtime::native_version() - } -} - -/// Native Asset Hub Polkadot (Statemint) executor instance. -pub struct AssetHubPolkadotRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for AssetHubPolkadotRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - asset_hub_polkadot_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - asset_hub_polkadot_runtime::native_version() - } -} - -/// Native Asset Hub Kusama (Statemine) executor instance. -pub struct AssetHubKusamaExecutor; - -impl sc_executor::NativeExecutionDispatch for AssetHubKusamaExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - asset_hub_kusama_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - asset_hub_kusama_runtime::native_version() - } -} - -/// Native Asset Hub Westend (Westmint) executor instance. -pub struct AssetHubWestendExecutor; - -impl sc_executor::NativeExecutionDispatch for AssetHubWestendExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - asset_hub_westend_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - asset_hub_westend_runtime::native_version() - } -} - -/// Native Polkadot Collectives executor instance. -pub struct CollectivesPolkadotRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for CollectivesPolkadotRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - collectives_polkadot_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - collectives_polkadot_runtime::native_version() - } -} - -/// Native BridgeHubPolkadot executor instance. -pub struct BridgeHubPolkadotRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for BridgeHubPolkadotRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - bridge_hub_polkadot_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - bridge_hub_polkadot_runtime::native_version() - } -} - -/// Native BridgeHubKusama executor instance. -pub struct BridgeHubKusamaRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for BridgeHubKusamaRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - bridge_hub_kusama_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - bridge_hub_kusama_runtime::native_version() - } -} - -/// Native BridgeHubRococo executor instance. -pub struct BridgeHubRococoRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for BridgeHubRococoRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - bridge_hub_rococo_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - bridge_hub_rococo_runtime::native_version() - } -} - -/// Native contracts executor instance. -pub struct ContractsRococoRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for ContractsRococoRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - contracts_rococo_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - contracts_rococo_runtime::native_version() - } -} - -/// Native Glutton executor instance. -pub struct GluttonRuntimeExecutor; - -impl sc_executor::NativeExecutionDispatch for GluttonRuntimeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; - - fn dispatch(method: &str, data: &[u8]) -> Option> { - shell_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - shell_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. /// @@ -238,17 +103,7 @@ impl sc_executor::NativeExecutionDispatch for GluttonRuntimeExecutor { 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 @@ -289,10 +144,11 @@ where .build(); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts_record_import::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + true, )?; let client = Arc::new(client); @@ -333,12 +189,11 @@ where }) } -/// Start a shell node with the given parachain `Configuration` and relay chain `Configuration`. +/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. /// -/// This is the actual implementation that is abstract over the executor and the runtime api for -/// shell nodes. +/// This is the actual implementation that is abstract over the executor and the runtime api. #[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_shell_node_impl( +async fn start_node_impl( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, @@ -357,8 +212,15 @@ where + sp_api::ApiExt + sp_offchain::OffchainWorkerApi + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo, - RB: Fn(Arc>) -> Result, sc_service::Error> + + cumulus_primitives_core::CollectCollationInfo + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + frame_rpc_system::AccountNonceApi, + RB: Fn( + DenyUnsafe, + Arc>, + Arc, + Arc>>, + ) -> Result, sc_service::Error> + 'static, BIQ: FnOnce( Arc>, @@ -382,6 +244,7 @@ where CollatorPair, OverseerHandle, Arc>) + Send + Sync>, + Arc, ) -> Result<(), sc_service::Error>, { let parachain_config = prepare_node_config(parachain_config); @@ -393,7 +256,6 @@ where let backend = params.backend.clone(); let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( polkadot_config, ¶chain_config, @@ -425,8 +287,20 @@ where }) .await?; - let rpc_client = client.clone(); - let rpc_builder = Box::new(move |_, _| rpc_ext_builder(rpc_client.clone())); + let rpc_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |deny_unsafe, _| { + rpc_ext_builder( + deny_unsafe, + client.clone(), + backend_for_rpc.clone(), + transaction_pool.clone(), + ) + }) + }; sc_service::spawn_tasks(sc_service::SpawnTasksParams { rpc_builder, @@ -503,6 +377,7 @@ where collator_key.expect("Command line arguments do not allow this. qed"), overseer_handle, announce_block, + backend.clone(), )?; } @@ -511,647 +386,152 @@ where Ok((task_manager, client)) } -/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. -/// -/// This is the actual implementation that is abstract over the executor and the runtime api. -#[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_node_impl( +/// Build the import queue for Aura-based runtimes. +pub fn build_aura_import_queue( + client: Arc>, + block_import: ParachainBlockImport, + config: &Configuration, + telemetry: Option, + task_manager: &TaskManager, +) -> Result, sc_service::Error> { + let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + + cumulus_client_consensus_aura::import_queue::< + sp_consensus_aura::sr25519::AuthorityPair, + _, + _, + _, + _, + _, + >(cumulus_client_consensus_aura::ImportQueueParams { + block_import, + client, + create_inherent_data_providers: move |_, _| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + registry: config.prometheus_registry(), + spawner: &task_manager.spawn_essential_handle(), + telemetry, + }) + .map_err(Into::into) +} + +/// Start a rococo parachain node. +pub async fn start_rococo_parachain_node( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - sybil_resistance_level: CollatorSybilResistance, para_id: ParaId, - _rpc_ext_builder: RB, - build_import_queue: BIQ, - start_consensus: SC, hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + build_parachain_rpc_extensions::, + build_aura_import_queue, + start_lookahead_aura_consensus, + hwbench, + ) + .await +} + +/// Build the import queue for the shell runtime. +pub fn build_shell_import_queue( + client: Arc>, + block_import: ParachainBlockImport, + config: &Configuration, + _: Option, + task_manager: &TaskManager, +) -> Result, sc_service::Error> { + cumulus_client_consensus_relay_chain::import_queue( + client, + block_import, + |_, _| async { Ok(()) }, + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + ) + .map_err(Into::into) +} + +fn build_parachain_rpc_extensions( + deny_unsafe: sc_rpc::DenyUnsafe, + client: Arc>, + backend: Arc, + pool: Arc>>, +) -> Result, sc_service::Error> where RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + frame_rpc_system::AccountNonceApi, - RB: Fn(Arc>) -> Result, sc_service::Error>, - BIQ: FnOnce( - Arc>, - ParachainBlockImport, - &Configuration, - Option, - &TaskManager, - ) -> Result, sc_service::Error>, - SC: FnOnce( - Arc>, - ParachainBlockImport, - Option<&Registry>, - Option, - &TaskManager, - Arc, - Arc>>, - Arc>, - KeystorePtr, - Duration, - ParaId, - CollatorPair, - OverseerHandle, - Arc>) + Send + Sync>, - ) -> Result<(), sc_service::Error>, { - let parachain_config = prepare_node_config(parachain_config); + let deps = rpc::FullDeps { client, pool, deny_unsafe }; - let params = new_partial::(¶chain_config, build_import_queue)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; + rpc::create_full(deps, backend).map_err(Into::into) +} - let client = params.client.clone(); - let backend = params.backend.clone(); +fn build_contracts_rpc_extensions( + deny_unsafe: sc_rpc::DenyUnsafe, + client: Arc>, + _backend: Arc, + pool: Arc>>, +) -> Result, sc_service::Error> { + let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; - let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( + crate::rpc::create_contracts_rococo(deps).map_err(Into::into) +} + +/// Start a polkadot-shell parachain node. +pub async fn start_shell_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( + parachain_config, polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), + collator_options, + CollatorSybilResistance::Unresistant, // free-for-all consensus + para_id, + |_, _, _, _| Ok(RpcModule::new(())), + build_shell_import_queue, + start_relay_chain_consensus, + hwbench, ) .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::new(¶chain_config.network); +} - let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - para_id, - spawn_handle: task_manager.spawn_handle(), - relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level, - }) - .await?; +enum BuildOnAccess { + Uninitialized(Option R + Send + Sync>>), + Initialized(R), +} - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - - let backend_for_rpc = backend.clone(); - Box::new(move |deny_unsafe, _| { - let deps = rpc::FullDeps { - client: client.clone(), - pool: transaction_pool.clone(), - deny_unsafe, - }; - - rpc::create_full(deps, backend_for_rpc.clone()).map_err(Into::into) - }) - }; - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); - if validator { - warn_if_slow_hardware(&hwbench); - } - - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), - ); - } - } - - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), - para_id, - relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service: sync_service.clone(), - })?; - - if validator { - start_consensus( - client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - sync_service.clone(), - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - )?; - } - - start_network.start_network(); - - Ok((task_manager, client)) -} - -/// Start a node with the given parachain `Configuration` and relay chain `Configuration`. -/// -/// This is the actual implementation that is abstract over the executor and the runtime api. -/// -/// This node is basic in the sense that it doesn't support functionality like transaction -/// payment. Intended to replace start_shell_node in use for glutton, shell, and seedling. -#[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_basic_lookahead_node_impl( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - sybil_resistance_level: CollatorSybilResistance, - para_id: ParaId, - rpc_ext_builder: RB, - build_import_queue: BIQ, - start_consensus: SC, - hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo - + frame_rpc_system::AccountNonceApi, - RB: Fn(Arc>) -> Result, sc_service::Error> - + 'static, - BIQ: FnOnce( - Arc>, - ParachainBlockImport, - &Configuration, - Option, - &TaskManager, - ) -> Result, sc_service::Error>, - SC: FnOnce( - Arc>, - ParachainBlockImport, - Option<&Registry>, - Option, - &TaskManager, - Arc, - Arc>>, - Arc>, - KeystorePtr, - Duration, - ParaId, - CollatorPair, - OverseerHandle, - Arc>) + Send + Sync>, - Arc, - ) -> Result<(), sc_service::Error>, -{ - let parachain_config = prepare_node_config(parachain_config); - - let params = new_partial::(¶chain_config, build_import_queue)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; - - let client = params.client.clone(); - let backend = params.backend.clone(); - - let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( - polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), - ) - .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::new(¶chain_config.network); - - let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - para_id, - spawn_handle: task_manager.spawn_handle(), - relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level, - }) - .await?; - - let rpc_client = client.clone(); - let rpc_builder = Box::new(move |_, _| rpc_ext_builder(rpc_client.clone())); - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); - if validator { - warn_if_slow_hardware(&hwbench); - } - - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), - ); - } - } - - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), - para_id, - relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service: sync_service.clone(), - })?; - - if validator { - start_consensus( - client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - sync_service.clone(), - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - backend.clone(), - )?; - } - - start_network.start_network(); - - Ok((task_manager, client)) -} - -/// Build the import queue for the rococo parachain runtime. -pub fn rococo_parachain_build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - telemetry: Option, - task_manager: &TaskManager, -) -> Result, sc_service::Error> { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - - cumulus_client_consensus_aura::import_queue::< - sp_consensus_aura::sr25519::AuthorityPair, - _, - _, - _, - _, - _, - >(cumulus_client_consensus_aura::ImportQueueParams { - block_import, - client, - create_inherent_data_providers: move |_, _| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, - slot_duration, - ); - - Ok((slot, timestamp)) - }, - registry: config.prometheus_registry(), - spawner: &task_manager.spawn_essential_handle(), - telemetry, - }) - .map_err(Into::into) -} - -/// Start a rococo parachain node. -pub async fn start_rococo_parachain_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, -) -> sc_service::error::Result<( - TaskManager, - Arc>, -)> { - start_node_impl::( - parachain_config, - polkadot_config, - collator_options, - CollatorSybilResistance::Resistant, // Aura - para_id, - |_| Ok(RpcModule::new(())), - rococo_parachain_build_import_queue, - |client, - block_import, - prometheus_registry, - telemetry, - task_manager, - relay_chain_interface, - transaction_pool, - sync_oracle, - keystore, - relay_chain_slot_duration, - para_id, - collator_key, - overseer_handle, - announce_block| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), - client.clone(), - transaction_pool, - prometheus_registry, - telemetry.clone(), - ); - let proposer = Proposer::new(proposer_factory); - - let collator_service = CollatorService::new( - client.clone(), - Arc::new(task_manager.spawn_handle()), - announce_block, - client.clone(), - ); - - let params = BasicAuraParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client, - relay_client: relay_chain_interface, - sync_oracle, - keystore, - collator_key, - para_id, - overseer_handle, - slot_duration, - relay_chain_slot_duration, - proposer, - collator_service, - // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: None, - }; - - let fut = basic_aura::run::< - Block, - sp_consensus_aura::sr25519::AuthorityPair, - _, - _, - _, - _, - _, - _, - _, - >(params); - task_manager.spawn_essential_handle().spawn("aura", None, fut); - - Ok(()) - }, - hwbench, - ) - .await -} - -/// Build the import queue for the shell runtime. -pub fn shell_build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - _: Option, - task_manager: &TaskManager, -) -> Result, sc_service::Error> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder, -{ - cumulus_client_consensus_relay_chain::import_queue( - client, - block_import, - |_, _| async { Ok(()) }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - ) - .map_err(Into::into) -} - -/// Start a polkadot-shell parachain node. -pub async fn start_shell_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo, -{ - start_shell_node_impl::( - parachain_config, - polkadot_config, - collator_options, - CollatorSybilResistance::Unresistant, // free-for-all consensus - para_id, - |_| Ok(RpcModule::new(())), - shell_build_import_queue, - |client, - block_import, - prometheus_registry, - telemetry, - task_manager, - relay_chain_interface, - transaction_pool, - _sync_oracle, - _keystore, - _relay_chain_slot_duration, - para_id, - collator_key, - overseer_handle, - announce_block| { - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), - client.clone(), - transaction_pool, - prometheus_registry, - telemetry, - ); - - let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( - cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { - para_id, - proposer_factory, - block_import, - relay_chain_interface: relay_chain_interface.clone(), - create_inherent_data_providers: move |_, (relay_parent, validation_data)| { - let relay_chain_interface = relay_chain_interface.clone(); - async move { - let parachain_inherent = - cumulus_primitives_parachain_inherent::ParachainInherentData::create_at( - relay_parent, - &relay_chain_interface, - &validation_data, - para_id, - ).await; - let parachain_inherent = parachain_inherent.ok_or_else(|| { - Box::::from( - "Failed to create parachain inherent", - ) - })?; - Ok(parachain_inherent) - } - }, - }, - ); - - let spawner = task_manager.spawn_handle(); - - // Required for free-for-all consensus - #[allow(deprecated)] - old_consensus::start_collator_sync(old_consensus::StartCollatorParams { - para_id, - block_status: client.clone(), - announce_block, - overseer_handle, - spawner, - key: collator_key, - parachain_consensus: free_for_all, - runtime_api: client.clone(), - }); - - Ok(()) - }, - hwbench, - ) - .await -} - -enum BuildOnAccess { - Uninitialized(Option R + Send + Sync>>), - Initialized(R), -} - -impl BuildOnAccess { - fn get_mut(&mut self) -> &mut R { - loop { - match self { - Self::Uninitialized(f) => { - *self = Self::Initialized((f.take().unwrap())()); - }, - Self::Initialized(ref mut r) => return r, - } - } - } -} +impl BuildOnAccess { + fn get_mut(&mut self) -> &mut R { + loop { + match self { + Self::Uninitialized(f) => { + *self = Self::Initialized((f.take().unwrap())()); + }, + Self::Initialized(ref mut r) => return r, + } + } + } +} /// Special [`ParachainConsensus`] implementation that waits for the upgrade from /// shell to a parachain runtime that implements Aura. @@ -1239,8 +619,9 @@ where } } -/// Build the import queue for Aura-based runtimes. -pub fn aura_build_import_queue( +/// Build the import queue for parachain runtimes that started with relay chain consensus and +/// switched to aura. +pub fn build_relay_to_aura_import_queue( client: Arc>, block_import: ParachainBlockImport, config: &Configuration, @@ -1259,28 +640,33 @@ where <::Pair as Pair>::Signature: TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, { - let client2 = client.clone(); + let verifier_client = client.clone(); let aura_verifier = move || { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client2).unwrap(); - Box::new(cumulus_client_consensus_aura::build_verifier::< ::Pair, _, _, _, >(cumulus_client_consensus_aura::BuildVerifierParams { - client: client2.clone(), - create_inherent_data_providers: move |_, _| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, - slot_duration, - ); - - Ok((slot, timestamp)) + client: verifier_client.clone(), + create_inherent_data_providers: move |parent_hash, _| { + let cidp_client = verifier_client.clone(); + async move { + let slot_duration = cumulus_client_consensus_aura::slot_duration_at( + &*cidp_client, + parent_hash, + )?; + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + } }, telemetry: telemetry_handle, })) as Box<_> @@ -1302,276 +688,22 @@ where Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } -/// Start an aura powered parachain node. Asset Hub and Collectives use this. -pub async fn start_generic_aura_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo - + sp_consensus_aura::AuraApi::Pair as Pair>::Public> - + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + frame_rpc_system::AccountNonceApi, - <::Pair as Pair>::Signature: - TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, -{ - start_node_impl::( - parachain_config, - polkadot_config, - collator_options, - CollatorSybilResistance::Resistant, // Aura - para_id, - |_| Ok(RpcModule::new(())), - aura_build_import_queue::<_, AuraId>, - |client, - block_import, - prometheus_registry, - telemetry, - task_manager, - relay_chain_interface, - transaction_pool, - sync_oracle, - keystore, - relay_chain_slot_duration, - para_id, - collator_key, - overseer_handle, - announce_block| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), - client.clone(), - transaction_pool, - prometheus_registry, - telemetry.clone(), - ); - let proposer = Proposer::new(proposer_factory); - - let collator_service = CollatorService::new( - client.clone(), - Arc::new(task_manager.spawn_handle()), - announce_block, - client.clone(), - ); - - let params = BasicAuraParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client, - relay_client: relay_chain_interface, - sync_oracle, - keystore, - collator_key, - para_id, - overseer_handle, - slot_duration, - relay_chain_slot_duration, - proposer, - collator_service, - // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: None, - }; - - let fut = - basic_aura::run::::Pair, _, _, _, _, _, _, _>(params); - task_manager.spawn_essential_handle().spawn("aura", None, fut); - - Ok(()) - }, - hwbench, - ) - .await -} - -/// Start a shell node which should later transition into an Aura powered parachain node. Asset Hub -/// uses this because at genesis, Asset Hub was on the `shell` runtime which didn't have Aura and -/// needs to sync and upgrade before it can run `AuraApi` functions. -pub async fn start_asset_hub_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo - + sp_consensus_aura::AuraApi::Pair as Pair>::Public> - + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + frame_rpc_system::AccountNonceApi, - <::Pair as Pair>::Signature: - TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, -{ - start_node_impl::( - parachain_config, - polkadot_config, - collator_options, - CollatorSybilResistance::Resistant, // Aura - para_id, - |_| Ok(RpcModule::new(())), - aura_build_import_queue::<_, AuraId>, - |client, - block_import, - prometheus_registry, - telemetry, - task_manager, - relay_chain_interface, - transaction_pool, - sync_oracle, - keystore, - relay_chain_slot_duration, - para_id, - collator_key, - overseer_handle, - announce_block| { - let relay_chain_interface2 = relay_chain_interface.clone(); - - let collator_service = CollatorService::new( - client.clone(), - Arc::new(task_manager.spawn_handle()), - announce_block, - client.clone(), - ); - - let spawner = task_manager.spawn_handle(); - - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - spawner, - client.clone(), - transaction_pool, - prometheus_registry, - telemetry.clone(), - ); - - let collation_future = Box::pin(async move { - // Start collating with the `shell` runtime while waiting for an upgrade to an Aura - // compatible runtime. - let mut request_stream = cumulus_client_collator::relay_chain_driven::init( - collator_key.clone(), - para_id, - overseer_handle.clone(), - ) - .await; - while let Some(request) = request_stream.next().await { - let pvd = request.persisted_validation_data().clone(); - let last_head_hash = - match ::Header::decode(&mut &pvd.parent_head.0[..]) { - Ok(header) => header.hash(), - Err(e) => { - log::error!("Could not decode the head data: {e}"); - request.complete(None); - continue - }, - }; - - // Check if we have upgraded to an Aura compatible runtime and transition if - // necessary. - if client - .runtime_api() - .has_api::>(last_head_hash) - .unwrap_or(false) - { - // Respond to this request before transitioning to Aura. - request.complete(None); - break - } - } - - // Move to Aura consensus. - let slot_duration = match cumulus_client_consensus_aura::slot_duration(&*client) { - Ok(d) => d, - Err(e) => { - log::error!("Could not get Aura slot duration: {e}"); - return - }, - }; - - let proposer = Proposer::new(proposer_factory); - - let params = BasicAuraParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client, - relay_client: relay_chain_interface2, - sync_oracle, - keystore, - collator_key, - para_id, - overseer_handle, - slot_duration, - relay_chain_slot_duration, - proposer, - collator_service, - // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: Some(request_stream), - }; - - basic_aura::run::::Pair, _, _, _, _, _, _, _>(params) - .await - }); - - let spawner = task_manager.spawn_essential_handle(); - spawner.spawn_essential("cumulus-asset-hub-collator", None, collation_future); - - Ok(()) - }, - hwbench, - ) - .await -} - -/// Start an aura powered parachain node which uses the lookahead collator to support async backing. -/// This node is basic in the sense that its runtime api doesn't include common contents such as -/// transaction payment. Used for aura glutton. -pub async fn start_basic_lookahead_node( +/// Start an aura powered parachain node. Some system chains use this. +pub async fn start_generic_aura_node( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, -) -> sc_service::error::Result<(TaskManager, Arc>)> -where - RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue - + sp_api::Metadata - + sp_session::SessionKeys - + sp_api::ApiExt - + sp_offchain::OffchainWorkerApi - + sp_block_builder::BlockBuilder - + cumulus_primitives_core::CollectCollationInfo - + sp_consensus_aura::AuraApi::Pair as Pair>::Public> - + frame_rpc_system::AccountNonceApi - + cumulus_primitives_aura::AuraUnincludedSegmentApi, - <::Pair as Pair>::Signature: - TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, -{ - start_basic_lookahead_node_impl::( +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura para_id, - |_| Ok(RpcModule::new(())), - aura_build_import_queue::<_, AuraId>, + build_parachain_rpc_extensions::, + build_relay_to_aura_import_queue::<_, AuraId>, |client, block_import, prometheus_registry, @@ -1586,7 +718,7 @@ where collator_key, overseer_handle, announce_block, - backend| { + _backend| { let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( @@ -1605,15 +737,11 @@ where client.clone(), ); - let params = AuraParams { + let params = BasicAuraParams { create_inherent_data_providers: move |_, ()| async move { Ok(()) }, block_import, - para_client: client.clone(), - para_backend: backend.clone(), + para_client: client, relay_client: relay_chain_interface, - code_hash_provider: move |block_hash| { - client.code_at(block_hash).ok().map(ValidationCode).map(|c| c.hash()) - }, sync_oracle, keystore, collator_key, @@ -1623,11 +751,13 @@ where relay_chain_slot_duration, proposer, collator_service, - authoring_duration: Duration::from_millis(1500), + // Very limited proposal time. + authoring_duration: Duration::from_millis(500), + collation_request_receiver: None, }; let fut = - aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params); + basic_aura::run::::Pair, _, _, _, _, _, _, _>(params); task_manager.spawn_essential_handle().spawn("aura", None, fut); Ok(()) @@ -1637,16 +767,38 @@ where .await } -#[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_contracts_rococo_node_impl( +/// Uses the lookahead collator to support async backing. +/// +/// Start an aura powered parachain node. Some system chains use this. +pub async fn start_generic_aura_lookahead_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + build_parachain_rpc_extensions::, + build_relay_to_aura_import_queue::<_, AuraId>, + start_lookahead_aura_consensus, + hwbench, + ) + .await +} + +/// Start a shell node which should later transition into an Aura powered parachain node. Asset Hub +/// uses this because at genesis, Asset Hub was on the `shell` runtime which didn't have Aura and +/// needs to sync and upgrade before it can run `AuraApi` functions. +pub async fn start_asset_hub_node( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - sybil_resistance_level: CollatorSybilResistance, para_id: ParaId, - _rpc_ext_builder: RB, - build_import_queue: BIQ, - start_consensus: SC, hwbench: Option, ) -> sc_service::error::Result<(TaskManager, Arc>)> where @@ -1658,228 +810,169 @@ where + sp_offchain::OffchainWorkerApi + sp_block_builder::BlockBuilder + cumulus_primitives_core::CollectCollationInfo + + sp_consensus_aura::AuraApi::Pair as Pair>::Public> + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + frame_rpc_system::AccountNonceApi, - RB: Fn(Arc>) -> Result, sc_service::Error>, - BIQ: FnOnce( - Arc>, - ParachainBlockImport, - &Configuration, - Option, - &TaskManager, - ) -> Result, sc_service::Error>, - SC: FnOnce( - Arc>, - ParachainBlockImport, - Option<&Registry>, - Option, - &TaskManager, - Arc, - Arc>>, - Arc>, - KeystorePtr, - Duration, - ParaId, - CollatorPair, - OverseerHandle, - Arc>) + Send + Sync>, - ) -> Result<(), sc_service::Error>, + <::Pair as Pair>::Signature: + TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, { - let parachain_config = prepare_node_config(parachain_config); - - let params = new_partial::(¶chain_config, build_import_queue)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; - - let client = params.client.clone(); - let backend = params.backend.clone(); - let mut task_manager = params.task_manager; - - let (relay_chain_interface, collator_key) = build_relay_chain_interface( + start_node_impl::( + parachain_config, polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), - ) - .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::new(¶chain_config.network); - - let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - para_id, - spawn_handle: task_manager.spawn_handle(), - relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level, - }) - .await?; - - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - - Box::new(move |deny_unsafe, _| { - let deps = crate::rpc::FullDeps { - client: client.clone(), - pool: transaction_pool.clone(), - deny_unsafe, - }; - - crate::rpc::create_contracts_rococo(deps).map_err(Into::into) - }) - }; - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); - if validator { - warn_if_slow_hardware(&hwbench); - } + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + build_parachain_rpc_extensions::, + build_relay_to_aura_import_queue::<_, AuraId>, + |client, + block_import, + prometheus_registry, + telemetry, + task_manager, + relay_chain_interface, + transaction_pool, + sync_oracle, + keystore, + relay_chain_slot_duration, + para_id, + collator_key, + overseer_handle, + announce_block, + _backend| { + let relay_chain_interface2 = relay_chain_interface.clone(); - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + let collator_service = CollatorService::new( + client.clone(), + Arc::new(task_manager.spawn_handle()), + announce_block, + client.clone(), ); - } - } - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), - para_id, - relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service: sync_service.clone(), - })?; + let spawner = task_manager.spawn_handle(); - if validator { - start_consensus( - client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - sync_service.clone(), - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - )?; - } + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + spawner, + client.clone(), + transaction_pool, + prometheus_registry, + telemetry.clone(), + ); - start_network.start_network(); + let collation_future = Box::pin(async move { + // Start collating with the `shell` runtime while waiting for an upgrade to an Aura + // compatible runtime. + let mut request_stream = cumulus_client_collator::relay_chain_driven::init( + collator_key.clone(), + para_id, + overseer_handle.clone(), + ) + .await; + while let Some(request) = request_stream.next().await { + let pvd = request.persisted_validation_data().clone(); + let last_head_hash = + match ::Header::decode(&mut &pvd.parent_head.0[..]) { + Ok(header) => header.hash(), + Err(e) => { + log::error!("Could not decode the head data: {e}"); + request.complete(None); + continue + }, + }; - Ok((task_manager, client)) -} + // Check if we have upgraded to an Aura compatible runtime and transition if + // necessary. + if client + .runtime_api() + .has_api::>(last_head_hash) + .unwrap_or(false) + { + // Respond to this request before transitioning to Aura. + request.complete(None); + break + } + } -#[allow(clippy::type_complexity)] -pub fn contracts_rococo_build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - telemetry: Option, - task_manager: &TaskManager, -) -> Result, sc_service::Error> { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + // Move to Aura consensus. + let slot_duration = match cumulus_client_consensus_aura::slot_duration(&*client) { + Ok(d) => d, + Err(e) => { + log::error!("Could not get Aura slot duration: {e}"); + return + }, + }; - cumulus_client_consensus_aura::import_queue::< - sp_consensus_aura::sr25519::AuthorityPair, - _, - _, - _, - _, - _, - >(cumulus_client_consensus_aura::ImportQueueParams { - block_import, - client, - create_inherent_data_providers: move |_, _| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + let proposer = Proposer::new(proposer_factory); - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, + let params = BasicAuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client, + relay_client: relay_chain_interface2, + sync_oracle, + keystore, + collator_key, + para_id, + overseer_handle, slot_duration, - ); + relay_chain_slot_duration, + proposer, + collator_service, + // Very limited proposal time. + authoring_duration: Duration::from_millis(500), + collation_request_receiver: Some(request_stream), + }; - Ok((slot, timestamp)) + basic_aura::run::::Pair, _, _, _, _, _, _, _>(params) + .await + }); + + let spawner = task_manager.spawn_essential_handle(); + spawner.spawn_essential("cumulus-asset-hub-collator", None, collation_future); + + Ok(()) }, - registry: config.prometheus_registry(), - spawner: &task_manager.spawn_essential_handle(), - telemetry, - }) - .map_err(Into::into) + hwbench, + ) + .await } -/// Start a parachain node. -pub async fn start_contracts_rococo_node( +/// Start a shell node which should later transition into an Aura powered parachain node. Asset Hub +/// uses this because at genesis, Asset Hub was on the `shell` runtime which didn't have Aura and +/// needs to sync and upgrade before it can run `AuraApi` functions. +/// +/// Uses the lookahead collator to support async backing. +#[sc_tracing::logging::prefix_logs_with("Parachain")] +pub async fn start_asset_hub_lookahead_node( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, -) -> sc_service::error::Result<( - TaskManager, - Arc>, -)> { - start_contracts_rococo_node_impl::( +) -> sc_service::error::Result<(TaskManager, Arc>)> +where + RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, + RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue + + sp_api::Metadata + + sp_session::SessionKeys + + sp_api::ApiExt + + sp_offchain::OffchainWorkerApi + + sp_block_builder::BlockBuilder + + cumulus_primitives_core::CollectCollationInfo + + sp_consensus_aura::AuraApi::Pair as Pair>::Public> + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + frame_rpc_system::AccountNonceApi + + cumulus_primitives_aura::AuraUnincludedSegmentApi, + <::Pair as Pair>::Signature: + TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, +{ + start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura para_id, - |_| Ok(RpcModule::new(())), - contracts_rococo_build_import_queue, + build_parachain_rpc_extensions::, + build_relay_to_aura_import_queue::<_, AuraId>, |client, block_import, prometheus_registry, @@ -1893,56 +986,92 @@ pub async fn start_contracts_rococo_node( para_id, collator_key, overseer_handle, - announce_block| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + announce_block, + backend| { + let relay_chain_interface2 = relay_chain_interface.clone(); + + let collator_service = CollatorService::new( + client.clone(), + Arc::new(task_manager.spawn_handle()), + announce_block, + client.clone(), + ); + + let spawner = task_manager.spawn_handle(); let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), + spawner, client.clone(), transaction_pool, prometheus_registry, telemetry.clone(), ); - let proposer = Proposer::new(proposer_factory); - let collator_service = CollatorService::new( - client.clone(), - Arc::new(task_manager.spawn_handle()), - announce_block, - client.clone(), - ); + let collation_future = Box::pin(async move { + // Start collating with the `shell` runtime while waiting for an upgrade to an Aura + // compatible runtime. + let mut request_stream = cumulus_client_collator::relay_chain_driven::init( + collator_key.clone(), + para_id, + overseer_handle.clone(), + ) + .await; + while let Some(request) = request_stream.next().await { + let pvd = request.persisted_validation_data().clone(); + let last_head_hash = + match ::Header::decode(&mut &pvd.parent_head.0[..]) { + Ok(header) => header.hash(), + Err(e) => { + log::error!("Could not decode the head data: {e}"); + request.complete(None); + continue + }, + }; - let params = BasicAuraParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client, - relay_client: relay_chain_interface, - sync_oracle, - keystore, - collator_key, - para_id, - overseer_handle, - slot_duration, - relay_chain_slot_duration, - proposer, - collator_service, - // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: None, - }; + // Check if we have upgraded to an Aura compatible runtime and transition if + // necessary. + if client + .runtime_api() + .has_api::>(last_head_hash) + .unwrap_or(false) + { + // Respond to this request before transitioning to Aura. + request.complete(None); + break + } + } - let fut = basic_aura::run::< - Block, - sp_consensus_aura::sr25519::AuthorityPair, - _, - _, - _, - _, - _, - _, - _, - >(params); - task_manager.spawn_essential_handle().spawn("aura", None, fut); + // Move to Aura consensus. + let proposer = Proposer::new(proposer_factory); + + let params = AuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend, + relay_client: relay_chain_interface2, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, + sync_oracle, + keystore, + collator_key, + para_id, + overseer_handle, + relay_chain_slot_duration, + proposer, + collator_service, + authoring_duration: Duration::from_millis(1500), + reinitialize: true, /* we need to always re-initialize for asset-hub moving + * to aura */ + }; + + aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params) + .await + }); + + let spawner = task_manager.spawn_essential_handle(); + spawner.spawn_essential("cumulus-asset-hub-collator", None, collation_future); Ok(()) }, @@ -1951,14 +1080,193 @@ pub async fn start_contracts_rococo_node( .await } +/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain +/// decides what is backed and included. +fn start_relay_chain_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + _sync_oracle: Arc>, + _keystore: KeystorePtr, + _relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + _backend: Arc, +) -> Result<(), sc_service::Error> { + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry, + ); + + let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( + cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { + para_id, + proposer_factory, + block_import, + relay_chain_interface: relay_chain_interface.clone(), + create_inherent_data_providers: move |_, (relay_parent, validation_data)| { + let relay_chain_interface = relay_chain_interface.clone(); + async move { + let parachain_inherent = + cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( + relay_parent, + &relay_chain_interface, + &validation_data, + para_id, + ).await; + let parachain_inherent = parachain_inherent.ok_or_else(|| { + Box::::from( + "Failed to create parachain inherent", + ) + })?; + Ok(parachain_inherent) + } + }, + }, + ); + + let spawner = task_manager.spawn_handle(); + + // Required for free-for-all consensus + #[allow(deprecated)] + old_consensus::start_collator_sync(old_consensus::StartCollatorParams { + para_id, + block_status: client.clone(), + announce_block, + overseer_handle, + spawner, + key: collator_key, + parachain_consensus: free_for_all, + runtime_api: client.clone(), + }); + + Ok(()) +} + +/// Start consensus using the lookahead aura collator. +fn start_lookahead_aura_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + sync_oracle: Arc>, + keystore: KeystorePtr, + relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + backend: Arc, +) -> Result<(), sc_service::Error> { + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry.clone(), + ); + + let collator_service = CollatorService::new( + client.clone(), + Arc::new(task_manager.spawn_handle()), + announce_block, + client.clone(), + ); + + let params = AuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend, + relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, + sync_oracle, + keystore, + collator_key, + para_id, + overseer_handle, + relay_chain_slot_duration, + proposer: Proposer::new(proposer_factory), + collator_service, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, + }; + + let fut = aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params); + task_manager.spawn_essential_handle().spawn("aura", None, fut); + + Ok(()) +} + +/// Start an aura powered parachain node which uses the lookahead collator to support async backing. +/// This node is basic in the sense that its runtime api doesn't include common contents such as +/// transaction payment. Used for aura glutton. +pub async fn start_basic_lookahead_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + |_, _, _, _| Ok(RpcModule::new(())), + build_relay_to_aura_import_queue::<_, AuraId>, + start_lookahead_aura_consensus, + hwbench, + ) + .await +} + +/// Start a parachain node for Rococo Contracts. +pub async fn start_contracts_rococo_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> { + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + build_contracts_rpc_extensions, + build_aura_import_queue, + start_lookahead_aura_consensus, + hwbench, + ) + .await +} + /// Checks that the hardware meets the requirements and print a warning otherwise. fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { // Polkadot para-chains should generally use these requirements to ensure that the relay-chain // will not take longer than expected to import its blocks. - if !frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) { + if let Err(err) = frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) { log::warn!( - "⚠️ The hardware does not meet the minimal requirements for role 'Authority' find out more at:\n\ - https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware" + "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ + https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", + err ); } } diff --git a/cumulus/polkadot-parachain/tests/benchmark_storage_works.rs b/cumulus/polkadot-parachain/tests/benchmark_storage_works.rs index c2850b64e458adca9bb0918667cf912eb16796df..c554b5b3d6be5602d84aa33c5d89020af26be74b 100644 --- a/cumulus/polkadot-parachain/tests/benchmark_storage_works.rs +++ b/cumulus/polkadot-parachain/tests/benchmark_storage_works.rs @@ -24,7 +24,7 @@ use std::{ use tempfile::tempdir; /// The runtimes that this command supports. -static RUNTIMES: [&str; 3] = ["asset-hub-westend", "asset-hub-kusama", "asset-hub-polkadot"]; +static RUNTIMES: [&str; 1] = ["asset-hub-westend"]; /// The `benchmark storage` command works for the dev runtimes. #[test] diff --git a/cumulus/primitives/aura/Cargo.toml b/cumulus/primitives/aura/Cargo.toml index 791ec17378a803b4ef5945082a2333a3d7df7952..21c06ef22d9a13bf8361156ea5e1af1216aa3e28 100644 --- a/cumulus/primitives/aura/Cargo.toml +++ b/cumulus/primitives/aura/Cargo.toml @@ -1,24 +1,29 @@ [package] name = "cumulus-primitives-aura" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } # Substrate -sp-api = { path = "../../../substrate/primitives/api", default-features = false} -sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +sp-api = { path = "../../../substrate/primitives/api", default-features = false } +sp-consensus-aura = { path = "../../../substrate/primitives/consensus/aura", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } # Polkadot -polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false} -polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false} +polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false } +polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "polkadot-core-primitives/std", diff --git a/cumulus/primitives/core/Cargo.toml b/cumulus/primitives/core/Cargo.toml index 3ce7b1da4a6cba6230d622934a18d35d819a982b..32c5054f359c4102a0c0a5ffdcb128bdc1ffd725 100644 --- a/cumulus/primitives/core/Cargo.toml +++ b/cumulus/primitives/core/Cargo.toml @@ -1,27 +1,32 @@ [package] name = "cumulus-primitives-core" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +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"] } # Substrate -sp-api = { path = "../../../substrate/primitives/api", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false} +sp-api = { path = "../../../substrate/primitives/api", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } # Polkadot -polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false} -polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false} -polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false} -xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false} +polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false } +polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false } +polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } +xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "polkadot-core-primitives/std", @@ -34,3 +39,8 @@ std = [ "sp-trie/std", "xcm/std", ] +runtime-benchmarks = [ + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index f216af2aaee7199fdee701ee89a5b8071d3a41f5..7f7353685657e7bf6bfb2c05faba32315bbbb706 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use polkadot_parachain_primitives::primitives::HeadData; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -78,6 +78,42 @@ impl From for &'static str { } } +/// The origin of an inbound message. +#[derive(Encode, Decode, 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), +} + +impl From for Location { + fn from(origin: AggregateMessageOrigin) -> Self { + match origin { + AggregateMessageOrigin::Here => Location::here(), + AggregateMessageOrigin::Parent => Location::parent(), + AggregateMessageOrigin::Sibling(id) => Location::new(1, Junction::Parachain(id.into())), + } + } +} + +#[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)), + } + } +} + /// Information about an XCMP channel. pub struct ChannelInfo { /// The maximum number of messages that can be pending in the channel at once. diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index 026d5a61bc8b0a0e1ff13767fb6826cd62023d4a..f434305a0ce013ae048087bf9a75413efacc1fff 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -1,48 +1,39 @@ [package] name = "cumulus-primitives-parachain-inherent" -version = "0.1.0" +version = "0.7.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" + +[lints] +workspace = true [dependencies] -async-trait = { version = "0.1.73", optional = true } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive" ] } +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} +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-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" ] +default = ["std"] 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 new file mode 100644 index 0000000000000000000000000000000000000000..dd584ce86b2e3172563848f028730709e1b1600d --- /dev/null +++ b/cumulus/primitives/proof-size-hostfunction/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "cumulus-primitives-proof-size-hostfunction" +version = "0.2.0" +authors.workspace = true +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 } +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } + +[dev-dependencies] +sp-state-machine = { path = "../../../substrate/primitives/state-machine" } +sp-core = { path = "../../../substrate/primitives/core" } +sp-io = { path = "../../../substrate/primitives/io" } + +[features] +default = ["std"] +std = ["sp-externalities/std", "sp-runtime-interface/std", "sp-trie/std"] diff --git a/cumulus/primitives/proof-size-hostfunction/src/lib.rs b/cumulus/primitives/proof-size-hostfunction/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..8ebc58ea450d4aea023f2a3af218bbd9eb3546e9 --- /dev/null +++ b/cumulus/primitives/proof-size-hostfunction/src/lib.rs @@ -0,0 +1,109 @@ +// 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. + +// 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 Cumulus. If not, see . + +//! Tools for reclaiming PoV weight in parachain runtimes. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +use sp_externalities::ExternalitiesExt; + +use sp_runtime_interface::runtime_interface; + +#[cfg(feature = "std")] +use sp_trie::proof_size_extension::ProofSizeExt; + +pub const PROOF_RECORDING_DISABLED: u64 = u64::MAX; + +/// Interface that provides access to the current storage proof size. +/// +/// Should return the current storage proof size if [`ProofSizeExt`] is registered. Otherwise, needs +/// to return u64::MAX. +#[runtime_interface] +pub trait StorageProofSize { + /// Returns the current storage proof size. + fn storage_proof_size(&mut self) -> u64 { + self.extension::() + .map_or(PROOF_RECORDING_DISABLED, |e| e.storage_proof_size()) + } +} + +#[cfg(test)] +mod tests { + use sp_core::Blake2Hasher; + use sp_state_machine::TestExternalities; + use sp_trie::{ + proof_size_extension::ProofSizeExt, recorder::Recorder, LayoutV1, PrefixedMemoryDB, + TrieDBMutBuilder, TrieMut, + }; + + use crate::{storage_proof_size, PROOF_RECORDING_DISABLED}; + + const TEST_DATA: &[(&[u8], &[u8])] = &[(b"key1", &[1; 64]), (b"key2", &[2; 64])]; + + type TestLayout = LayoutV1; + + fn get_prepared_test_externalities() -> (TestExternalities, Recorder) + { + let mut db = PrefixedMemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilder::::new(&mut db, &mut root).build(); + for (k, v) in TEST_DATA { + trie.insert(k, v).expect("Inserts data"); + } + } + + let recorder: sp_trie::recorder::Recorder = Default::default(); + let trie_backend = sp_state_machine::TrieBackendBuilder::new(db, root) + .with_recorder(recorder.clone()) + .build(); + + let mut ext: TestExternalities = TestExternalities::default(); + ext.backend = trie_backend; + (ext, recorder) + } + + #[test] + fn host_function_returns_size_from_recorder() { + let (mut ext, recorder) = get_prepared_test_externalities(); + ext.register_extension(ProofSizeExt::new(recorder)); + + ext.execute_with(|| { + assert_eq!(storage_proof_size::storage_proof_size(), 0); + sp_io::storage::get(b"key1"); + assert_eq!(storage_proof_size::storage_proof_size(), 175); + sp_io::storage::get(b"key2"); + assert_eq!(storage_proof_size::storage_proof_size(), 275); + sp_io::storage::get(b"key2"); + assert_eq!(storage_proof_size::storage_proof_size(), 275); + }); + } + + #[test] + fn host_function_returns_max_without_extension() { + let (mut ext, _) = get_prepared_test_externalities(); + + ext.execute_with(|| { + assert_eq!(storage_proof_size::storage_proof_size(), PROOF_RECORDING_DISABLED); + sp_io::storage::get(b"key1"); + assert_eq!(storage_proof_size::storage_proof_size(), PROOF_RECORDING_DISABLED); + sp_io::storage::get(b"key2"); + assert_eq!(storage_proof_size::storage_proof_size(), PROOF_RECORDING_DISABLED); + }); + } +} diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4835fb5192b88165c1e7912862492086ddd0853e --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +description = "Utilities to reclaim storage weight." +license = "Apache-2.0" + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } + +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } + +cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction", default-features = false } +docify = "0.2.7" + +[dev-dependencies] +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +cumulus-test-runtime = { path = "../../test/runtime" } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-core/std", + "cumulus-primitives-proof-size-hostfunction/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dddc92e395574dbb36538f3799c0d21b22a3939 --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -0,0 +1,663 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mechanism to reclaim PoV proof size weight after an extrinsic has been applied. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use cumulus_primitives_core::Weight; +use cumulus_primitives_proof_size_hostfunction::{ + storage_proof_size::storage_proof_size, PROOF_RECORDING_DISABLED, +}; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + weights::WeightMeter, +}; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, + DispatchResult, +}; +use sp_std::marker::PhantomData; + +const LOG_TARGET: &'static str = "runtime::storage_reclaim"; + +/// `StorageWeightReclaimer` is a mechanism for manually reclaiming storage weight. +/// +/// It internally keeps track of the proof size and storage weight at initialization time. At +/// reclaim it computes the real consumed storage weight and refunds excess weight. +/// +/// # Example +#[doc = docify::embed!("src/lib.rs", simple_reclaimer_example)] +pub struct StorageWeightReclaimer { + previous_remaining_proof_size: u64, + previous_reported_proof_size: Option, +} + +impl StorageWeightReclaimer { + /// Creates a new `StorageWeightReclaimer` instance and initializes it with the storage + /// size provided by `weight_meter` and reported proof size from the node. + #[must_use = "Must call `reclaim_with_meter` to reclaim the weight"] + pub fn new(weight_meter: &WeightMeter) -> StorageWeightReclaimer { + let previous_remaining_proof_size = weight_meter.remaining().proof_size(); + let previous_reported_proof_size = get_proof_size(); + Self { previous_remaining_proof_size, previous_reported_proof_size } + } + + /// Check the consumed storage weight and calculate the consumed excess weight. + fn reclaim(&mut self, remaining_weight: Weight) -> Option { + let current_remaining_weight = remaining_weight.proof_size(); + let current_storage_proof_size = get_proof_size()?; + let previous_storage_proof_size = self.previous_reported_proof_size?; + let used_weight = + self.previous_remaining_proof_size.saturating_sub(current_remaining_weight); + let reported_used_size = + current_storage_proof_size.saturating_sub(previous_storage_proof_size); + let reclaimable = used_weight.saturating_sub(reported_used_size); + log::trace!( + target: LOG_TARGET, + "Found reclaimable storage weight. benchmarked: {used_weight}, consumed: {reported_used_size}" + ); + + self.previous_remaining_proof_size = current_remaining_weight.saturating_add(reclaimable); + self.previous_reported_proof_size = Some(current_storage_proof_size); + Some(Weight::from_parts(0, reclaimable)) + } + + /// Check the consumed storage weight and add the reclaimed + /// weight budget back to `weight_meter`. + pub fn reclaim_with_meter(&mut self, weight_meter: &mut WeightMeter) -> Option { + let reclaimed = self.reclaim(weight_meter.remaining())?; + weight_meter.reclaim_proof_size(reclaimed.proof_size()); + Some(reclaimed) + } +} + +/// Returns the current storage proof size from the host side. +/// +/// Returns `None` if proof recording is disabled on the host. +pub fn get_proof_size() -> Option { + let proof_size = storage_proof_size(); + (proof_size != PROOF_RECORDING_DISABLED).then_some(proof_size) +} + +/// Storage weight reclaim mechanism. +/// +/// This extension checks the size of the node-side storage proof +/// before and after executing a given extrinsic. The difference between +/// benchmarked and spent weight can be reclaimed. +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct StorageWeightReclaim(PhantomData); + +impl StorageWeightReclaim { + /// Create a new `StorageWeightReclaim` instance. + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl core::fmt::Debug for StorageWeightReclaim { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let _ = write!(f, "StorageWeightReclaim"); + Ok(()) + } +} + +impl SignedExtension for StorageWeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "StorageWeightReclaim"; + + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = Option; + + fn additional_signed( + &self, + ) -> Result + { + Ok(()) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(get_proof_size()) + } + + fn post_dispatch( + pre: Option, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + let Some(Some(pre_dispatch_proof_size)) = pre else { + return Ok(()); + }; + + let Some(post_dispatch_proof_size) = get_proof_size() else { + log::debug!( + target: LOG_TARGET, + "Proof recording enabled during pre-dispatch, now disabled. This should not happen." + ); + return Ok(()) + }; + let benchmarked_weight = info.weight.proof_size(); + let consumed_weight = post_dispatch_proof_size.saturating_sub(pre_dispatch_proof_size); + + // Unspent weight according to the `actual_weight` from `PostDispatchInfo` + // This unspent weight will be refunded by the `CheckWeight` extension, so we need to + // account for that. + let unspent = post_info.calc_unspent(info).proof_size(); + let storage_size_diff = + benchmarked_weight.saturating_sub(unspent).abs_diff(consumed_weight as u64); + + // This value will be reclaimed by [`frame_system::CheckWeight`], so we need to calculate + // that in. + frame_system::BlockWeight::::mutate(|current| { + if consumed_weight > benchmarked_weight { + log::error!( + target: LOG_TARGET, + "Benchmarked storage weight smaller than consumed storage weight. benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}" + ); + current.accrue(Weight::from_parts(0, storage_size_diff), info.class) + } else { + log::trace!( + target: LOG_TARGET, + "Reclaiming storage weight. benchmarked: {benchmarked_weight}, consumed: {consumed_weight} unspent: {unspent}" + ); + current.reduce(Weight::from_parts(0, storage_size_diff), info.class) + } + }); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_ok, + dispatch::DispatchClass, + weights::{Weight, WeightMeter}, + }; + use frame_system::{BlockWeight, CheckWeight}; + use sp_runtime::{AccountId32, BuildStorage}; + use sp_std::marker::PhantomData; + use sp_trie::proof_size_extension::ProofSizeExt; + + type Test = cumulus_test_runtime::Runtime; + const CALL: &::RuntimeCall = + &cumulus_test_runtime::RuntimeCall::System(frame_system::Call::set_heap_pages { + pages: 0u64, + }); + const ALICE: AccountId32 = AccountId32::new([1u8; 32]); + const LEN: usize = 0; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let ext: sp_io::TestExternalities = cumulus_test_runtime::RuntimeGenesisConfig::default() + .build_storage() + .unwrap() + .into(); + ext + } + + struct TestRecorder { + return_values: Box<[usize]>, + counter: std::sync::atomic::AtomicUsize, + } + + impl TestRecorder { + fn new(values: &[usize]) -> Self { + TestRecorder { return_values: values.into(), counter: Default::default() } + } + } + + impl sp_trie::ProofSizeProvider for TestRecorder { + fn estimate_encoded_size(&self) -> usize { + let counter = self.counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + self.return_values[counter] + } + } + + fn setup_test_externalities(proof_values: &[usize]) -> sp_io::TestExternalities { + let mut test_ext = new_test_ext(); + let test_recorder = TestRecorder::new(proof_values); + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + test_ext + } + + fn set_current_storage_weight(new_weight: u64) { + BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::from_parts(0, new_weight), DispatchClass::Normal); + }); + } + + #[test] + fn basic_refund() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + // We expect a refund of 400 + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 600); + }) + } + + #[test] + fn does_nothing_without_extension() { + let mut test_ext = new_test_ext(); + + // Proof size extension not registered + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, None); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1000); + }) + } + + #[test] + fn negative_refund_is_added_to_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 100 + let info = DispatchInfo { weight: Weight::from_parts(0, 100), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // We expect no refund + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1100); + }) + } + + #[test] + fn test_zero_proof_size() { + let mut test_ext = setup_test_externalities(&[0, 0]); + + test_ext.execute_with(|| { + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 0); + }); + } + + #[test] + fn test_larger_pre_dispatch_proof_size() { + let mut test_ext = setup_test_externalities(&[300, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1300); + + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(300)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 800); + }); + } + + #[test] + fn test_incorporates_check_weight_unspent_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn storage_size_reported_correctly() { + let mut test_ext = setup_test_externalities(&[1000]); + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(1000)); + }); + + let mut test_ext = new_test_ext(); + + let test_recorder = TestRecorder::new(&[0]); + + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(0)); + }); + } + + #[test] + fn storage_size_disabled_reported_correctly() { + let mut test_ext = setup_test_externalities(&[PROOF_RECORDING_DISABLED as usize]); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), None); + }); + } + + #[test] + fn test_reclaim_helper() { + let mut test_ext = setup_test_externalities(&[1000, 1300, 1800]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 2000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 500)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 200))); + + remaining_weight_meter.consume(Weight::from_parts(0, 800)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + assert_eq!(reclaimed, Some(Weight::from_parts(0, 300))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1200)); + }); + } + + #[test] + fn test_reclaim_helper_does_not_reclaim_negative() { + // Benchmarked weight does not change at all + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1000)); + }); + + // Benchmarked weight increases less than storage proof consumes + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 0)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + }); + } + + /// Just here for doc purposes + fn get_benched_weight() -> Weight { + Weight::from_parts(0, 5) + } + + /// Just here for doc purposes + fn do_work() {} + + #[docify::export_content(simple_reclaimer_example)] + fn reclaim_with_weight_meter() { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(10, 10)); + + let benched_weight = get_benched_weight(); + + // It is important to instantiate the `StorageWeightReclaimer` before we consume the weight + // for a piece of work from the weight meter. + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + + if remaining_weight_meter.try_consume(benched_weight).is_ok() { + // Perform some work that takes has `benched_weight` storage weight. + do_work(); + + // Reclaimer will detect that we only consumed 2 bytes, so 3 bytes are reclaimed. + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + // We reclaimed 3 bytes of storage size! + assert_eq!(reclaimed, Some(Weight::from_parts(0, 3))); + assert_eq!(BlockWeight::::get().total().proof_size(), 10); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(10, 8)); + } + } + + #[test] + fn test_reclaim_helper_works_with_meter() { + // The node will report 12 - 10 = 2 consumed storage size between the calls. + let mut test_ext = setup_test_externalities(&[10, 12]); + + test_ext.execute_with(|| { + // Initial storage size is 10. + set_current_storage_weight(10); + reclaim_with_weight_meter(); + }); + } +} diff --git a/cumulus/primitives/timestamp/Cargo.toml b/cumulus/primitives/timestamp/Cargo.toml index 6b0d3d4a4dc6b3b755f0a822ecb0fa1062f1fc9a..59f327b2642a292db56708f5770ebb35b1b82d1d 100644 --- a/cumulus/primitives/timestamp/Cargo.toml +++ b/cumulus/primitives/timestamp/Cargo.toml @@ -1,25 +1,30 @@ [package] name = "cumulus-primitives-timestamp" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures = "0.3.28" # Substrate -sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} -sp-timestamp = { path = "../../../substrate/primitives/timestamp", default-features = false} +sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } +sp-timestamp = { path = "../../../substrate/primitives/timestamp", default-features = false } # Cumulus cumulus-primitives-core = { path = "../core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ + "codec/std", "cumulus-primitives-core/std", "sp-inherents/std", "sp-std/std", diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 691a4599b2c4fb0331b1a420474a6b8c0e860080..1e2c300b9ba257d2c8fb998689ae45847099dd63 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -1,37 +1,43 @@ [package] name = "cumulus-primitives-utility" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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 } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { workspace = true } # Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false} -sp-io = { path = "../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +frame-support = { path = "../../../substrate/frame/support", default-features = false } +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 } +pallet-asset-conversion = { path = "../../../substrate/frame/asset-conversion", default-features = false } # Polkadot polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false } polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false} -xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false} -pallet-xcm-benchmarks = { path = "../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } # Cumulus cumulus-primitives-core = { path = "../core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-primitives-core/std", "frame-support/std", - "pallet-xcm-benchmarks/std", + "log/std", + "pallet-asset-conversion/std", "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "sp-io/std", @@ -43,8 +49,9 @@ std = [ ] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index c4ce671948557742016266e5fb6c32d6c01b965d..abc391bdcb8ed6ac3682c761e6a0a8d2d0e7f14b 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -22,18 +22,27 @@ use codec::Encode; use cumulus_primitives_core::{MessageSendError, UpwardMessageSender}; use frame_support::{ - traits::{ - tokens::{fungibles, fungibles::Inspect}, - Get, - }, - weights::Weight, + defensive, + traits::{tokens::fungibles, Get, OnUnbalanced as OnUnbalancedT}, + weights::{Weight, WeightToFee as WeightToFeeT}, + CloneNoBound, }; +use pallet_asset_conversion::SwapCredit as SwapCreditT; use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery; -use sp_runtime::{traits::Saturating, SaturatedConversion}; +use sp_runtime::{ + traits::{Saturating, Zero}, + SaturatedConversion, +}; use sp_std::{marker::PhantomData, prelude::*}; use xcm::{latest::prelude::*, WrapVersion}; use xcm_builder::TakeRevenue; -use xcm_executor::traits::{MatchesFungibles, TransactAsset, WeightTrader}; +use xcm_executor::{ + traits::{MatchesFungibles, TransactAsset, WeightTrader}, + AssetsInHolding, +}; + +#[cfg(test)] +mod tests; /// Xcm router which recognises the `Parent` destination and handles it by sending the message into /// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an @@ -51,10 +60,7 @@ where { type Ticket = Vec; - fn validate( - dest: &mut Option, - msg: &mut Option>, - ) -> SendResult> { + fn validate(dest: &mut Option, msg: &mut Option>) -> SendResult> { let d = dest.take().ok_or(SendError::MissingArgument)?; if d.contains_parents_only(1) { @@ -90,19 +96,20 @@ struct AssetTraderRefunder { // The amount of weight bought minus the weigh already refunded weight_outstanding: Weight, // The concrete asset containing the asset location and outstanding balance - outstanding_concrete_asset: MultiAsset, + outstanding_concrete_asset: Asset, } -/// Charges for execution in the first multiasset of those selected for fee payment +/// Charges for execution in the first asset of those selected for fee payment /// Only succeeds for Concrete Fungible Assets -/// First tries to convert the this MultiAsset into a local assetId +/// First tries to convert the this Asset into a local assetId /// Then charges for this assetId as described by FeeCharger -/// Weight, paid balance, local asset Id and the multilocation is stored for +/// Weight, paid balance, local asset Id and the location is stored for /// later refund purposes /// Important: Errors if the Trader is being called twice by 2 BuyExecution instructions /// Alternatively we could just return payment in the aforementioned case +#[derive(CloneNoBound)] pub struct TakeFirstAssetTrader< - AccountId, + AccountId: Eq, FeeCharger: ChargeWeightInFungibles, Matcher: MatchesFungibles, ConcreteAssets: fungibles::Mutate + fungibles::Balanced, @@ -112,7 +119,7 @@ pub struct TakeFirstAssetTrader< PhantomData<(AccountId, FeeCharger, Matcher, ConcreteAssets, HandleRefund)>, ); impl< - AccountId, + AccountId: Eq, FeeCharger: ChargeWeightInFungibles, Matcher: MatchesFungibles, ConcreteAssets: fungibles::Mutate + fungibles::Balanced, @@ -123,15 +130,15 @@ impl< fn new() -> Self { Self(None, PhantomData) } - // We take first multiasset + // We take first asset // Check whether we can convert fee to asset_fee (is_sufficient, min_deposit) // If everything goes well, we charge. fn buy_weight( &mut self, weight: Weight, - payment: xcm_executor::Assets, + payment: xcm_executor::AssetsInHolding, context: &XcmContext, - ) -> Result { + ) -> Result { log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context); // Make sure we dont enter twice @@ -139,12 +146,12 @@ impl< return Err(XcmError::NotWithdrawable) } - // We take the very first multiasset from payment + // We take the very first asset from payment // (assets are sorted by fungibility/amount after this conversion) - let multiassets: MultiAssets = payment.clone().into(); + let assets: Assets = payment.clone().into(); - // Take the first multiasset from the selected MultiAssets - let first = multiassets.get(0).ok_or(XcmError::AssetNotFound)?; + // Take the first asset from the selected Assets + let first = assets.get(0).ok_or(XcmError::AssetNotFound)?; // Get the local asset id in which we can pay for fees let (local_asset_id, _) = @@ -166,13 +173,13 @@ impl< .try_into() .map_err(|_| XcmError::Overflow)?; - // Convert to the same kind of multiasset, with the required fungible balance - let required = first.id.into_multiasset(asset_balance.into()); + // Convert to the same kind of asset, with the required fungible balance + let required = first.id.clone().into_asset(asset_balance.into()); // Substract payment let unused = payment.checked_sub(required.clone()).map_err(|_| XcmError::TooExpensive)?; - // record weight and multiasset + // record weight and asset self.0 = Some(AssetTraderRefunder { weight_outstanding: weight, outstanding_concrete_asset: required, @@ -181,16 +188,16 @@ impl< Ok(unused) } - fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::refund_weight weight: {:?}, context: {:?}", weight, context); if let Some(AssetTraderRefunder { mut weight_outstanding, - outstanding_concrete_asset: MultiAsset { id, fun }, + outstanding_concrete_asset: Asset { id, fun }, }) = self.0.clone() { // Get the local asset id in which we can refund fees let (local_asset_id, outstanding_balance) = - Matcher::matches_fungibles(&(id, fun).into()).ok()?; + Matcher::matches_fungibles(&(id.clone(), fun).into()).ok()?; let minimum_balance = ConcreteAssets::minimum_balance(local_asset_id.clone()); @@ -220,7 +227,8 @@ impl< // Construct outstanding_concrete_asset with the same location id and substracted // balance - let outstanding_concrete_asset: MultiAsset = (id, outstanding_minus_substracted).into(); + let outstanding_concrete_asset: Asset = + (id.clone(), outstanding_minus_substracted).into(); // Substract from existing weight and balance weight_outstanding = weight_outstanding.saturating_sub(weight); @@ -241,7 +249,7 @@ impl< } impl< - AccountId, + AccountId: Eq, FeeCharger: ChargeWeightInFungibles, Matcher: MatchesFungibles, ConcreteAssets: fungibles::Mutate + fungibles::Balanced, @@ -267,11 +275,11 @@ impl< ReceiverAccount: Get>, > TakeRevenue for XcmFeesTo32ByteAccount { - fn take_revenue(revenue: MultiAsset) { + fn take_revenue(revenue: Asset) { if let Some(receiver) = ReceiverAccount::get() { let ok = FungiblesMutateAdapter::deposit_asset( &revenue, - &(X1(AccountId32 { network: None, id: receiver.into() }).into()), + &([AccountId32 { network: None, id: receiver.into() }].into()), None, ) .is_ok(); @@ -286,34 +294,249 @@ impl< /// in such assetId for that amount of weight pub trait ChargeWeightInFungibles> { fn charge_weight_in_fungibles( - asset_id: >::AssetId, + asset_id: >::AssetId, weight: Weight, - ) -> Result<>::Balance, XcmError>; + ) -> Result<>::Balance, XcmError>; +} + +/// Provides an implementation of [`WeightTrader`] to charge for weight using the first asset +/// specified in the `payment` argument. +/// +/// The asset used to pay for the weight must differ from the `Target` asset and be exchangeable for +/// the same `Target` asset through `SwapCredit`. +/// +/// ### Parameters: +/// - `Target`: the asset into which the user's payment will be exchanged using `SwapCredit`. +/// - `SwapCredit`: mechanism used for the exchange of the user's payment asset into the `Target`. +/// - `WeightToFee`: weight to the `Target` asset fee calculator. +/// - `Fungibles`: registry of fungible assets. +/// - `FungiblesAssetMatcher`: utility for mapping [`Asset`] to `Fungibles::AssetId` and +/// `Fungibles::Balance`. +/// - `OnUnbalanced`: handler for the fee payment. +/// - `AccountId`: the account identifier type. +pub struct SwapFirstAssetTrader< + Target: Get, + SwapCredit: SwapCreditT< + AccountId, + Balance = Fungibles::Balance, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + >, + WeightToFee: WeightToFeeT, + Fungibles: fungibles::Balanced, + FungiblesAssetMatcher: MatchesFungibles, + OnUnbalanced: OnUnbalancedT>, + AccountId, +> where + Fungibles::Balance: Into, +{ + /// Accumulated fee paid for XCM execution. + total_fee: fungibles::Credit, + /// Last asset utilized by a client to settle a fee. + last_fee_asset: Option, + _phantom_data: PhantomData<( + Target, + SwapCredit, + WeightToFee, + Fungibles, + FungiblesAssetMatcher, + OnUnbalanced, + AccountId, + )>, +} + +impl< + Target: Get, + SwapCredit: SwapCreditT< + AccountId, + Balance = Fungibles::Balance, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + >, + WeightToFee: WeightToFeeT, + Fungibles: fungibles::Balanced, + FungiblesAssetMatcher: MatchesFungibles, + OnUnbalanced: OnUnbalancedT>, + AccountId, + > WeightTrader + for SwapFirstAssetTrader< + Target, + SwapCredit, + WeightToFee, + Fungibles, + FungiblesAssetMatcher, + OnUnbalanced, + AccountId, + > where + Fungibles::Balance: Into, +{ + fn new() -> Self { + Self { + total_fee: fungibles::Credit::::zero(Target::get()), + last_fee_asset: None, + _phantom_data: PhantomData, + } + } + + fn buy_weight( + &mut self, + weight: Weight, + mut payment: AssetsInHolding, + _context: &XcmContext, + ) -> Result { + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}", + weight, + payment, + ); + let first_asset: Asset = + payment.fungible.pop_first().ok_or(XcmError::AssetNotFound)?.into(); + let (fungibles_asset, balance) = FungiblesAssetMatcher::matches_fungibles(&first_asset) + .map_err(|_| XcmError::AssetNotFound)?; + + let swap_asset = fungibles_asset.clone().into(); + if Target::get().eq(&swap_asset) { + // current trader is not applicable. + return Err(XcmError::FeesNotMet) + } + + let credit_in = Fungibles::issue(fungibles_asset, balance); + let fee = WeightToFee::weight_to_fee(&weight); + + // swap the user's asset for the `Target` asset. + let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens( + vec![swap_asset, Target::get()], + credit_in, + fee, + ) + .map_err(|(credit_in, _)| { + drop(credit_in); + XcmError::FeesNotMet + })?; + + match self.total_fee.subsume(credit_out) { + Err(credit_out) => { + // error may occur if `total_fee.asset` differs from `credit_out.asset`, which does + // not apply in this context. + defensive!( + "`total_fee.asset` must be equal to `credit_out.asset`", + (self.total_fee.asset(), credit_out.asset()) + ); + return Err(XcmError::FeesNotMet) + }, + _ => (), + }; + self.last_fee_asset = Some(first_asset.id.clone()); + + payment.fungible.insert(first_asset.id, credit_change.peek().into()); + drop(credit_change); + Ok(payment) + } + + fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option { + log::trace!( + target: "xcm::weight", + "SwapFirstAssetTrader::refund_weight weight: {:?}, self.total_fee: {:?}", + weight, + self.total_fee, + ); + if self.total_fee.peek().is_zero() { + // noting yet paid to refund. + return None + } + let mut refund_asset = if let Some(asset) = &self.last_fee_asset { + // create an initial zero refund in the asset used in the last `buy_weight`. + (asset.clone(), Fungible(0)).into() + } else { + return None + }; + let refund_amount = WeightToFee::weight_to_fee(&weight); + if refund_amount >= self.total_fee.peek() { + // not enough was paid to refund the `weight`. + return None + } + + let refund_swap_asset = FungiblesAssetMatcher::matches_fungibles(&refund_asset) + .map(|(a, _)| a.into()) + .ok()?; + + let refund = self.total_fee.extract(refund_amount); + let refund = match SwapCredit::swap_exact_tokens_for_tokens( + vec![Target::get(), refund_swap_asset], + refund, + None, + ) { + Ok(refund_in_target) => refund_in_target, + Err((refund, _)) => { + // return an attempted refund back to the `total_fee`. + let _ = self.total_fee.subsume(refund).map_err(|refund| { + // error may occur if `total_fee.asset` differs from `refund.asset`, which does + // not apply in this context. + defensive!( + "`total_fee.asset` must be equal to `refund.asset`", + (self.total_fee.asset(), refund.asset()) + ); + }); + return None + }, + }; + + refund_asset.fun = refund.peek().into().into(); + drop(refund); + Some(refund_asset) + } +} + +impl< + Target: Get, + SwapCredit: SwapCreditT< + AccountId, + Balance = Fungibles::Balance, + AssetKind = Fungibles::AssetId, + Credit = fungibles::Credit, + >, + WeightToFee: WeightToFeeT, + Fungibles: fungibles::Balanced, + FungiblesAssetMatcher: MatchesFungibles, + OnUnbalanced: OnUnbalancedT>, + AccountId, + > Drop + for SwapFirstAssetTrader< + Target, + SwapCredit, + WeightToFee, + Fungibles, + FungiblesAssetMatcher, + OnUnbalanced, + AccountId, + > where + Fungibles::Balance: Into, +{ + fn drop(&mut self) { + if self.total_fee.peek().is_zero() { + return + } + let total_fee = self.total_fee.extract(self.total_fee.peek()); + OnUnbalanced::on_unbalanced(total_fee); + } } #[cfg(test)] -mod tests { +mod test_xcm_router { use super::*; use cumulus_primitives_core::UpwardMessage; - use frame_support::{ - assert_ok, - traits::tokens::{ - DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence, - }, - }; - use sp_runtime::DispatchError; - use xcm_executor::{traits::Error, Assets}; /// Validates [`validate`] for required Some(destination) and Some(message) struct OkFixedXcmHashWithAssertingRequiredInputsSender; impl OkFixedXcmHashWithAssertingRequiredInputsSender { const FIXED_XCM_HASH: [u8; 32] = [9; 32]; - fn fixed_delivery_asset() -> MultiAssets { - MultiAssets::new() + fn fixed_delivery_asset() -> Assets { + Assets::new() } - fn expected_delivery_result() -> Result<(XcmHash, MultiAssets), SendError> { + fn expected_delivery_result() -> Result<(XcmHash, Assets), SendError> { Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset())) } } @@ -321,7 +544,7 @@ mod tests { type Ticket = (); fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult { assert!(destination.is_some()); @@ -377,7 +600,7 @@ mod tests { // ParentAsUmp - check dest/msg is valid let dest = (Parent, Here); - let mut dest_wrapper = Some(dest.into()); + let mut dest_wrapper = Some(dest.clone().into()); let mut msg_wrapper = Some(message.clone()); assert!( as SendXcm>::validate( &mut dest_wrapper, @@ -398,6 +621,18 @@ mod tests { )>(dest.into(), message) ); } +} +#[cfg(test)] +mod test_trader { + use super::*; + use frame_support::{ + assert_ok, + traits::tokens::{ + DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence, + }, + }; + use sp_runtime::DispatchError; + use xcm_executor::{traits::Error, AssetsInHolding}; #[test] fn take_first_asset_trader_buy_weight_called_twice_throws_error() { @@ -409,9 +644,9 @@ mod tests { type TestBalance = u128; struct TestAssets; impl MatchesFungibles for TestAssets { - fn matches_fungibles(a: &MultiAsset) -> Result<(TestAssetId, TestBalance), Error> { + fn matches_fungibles(a: &Asset) -> Result<(TestAssetId, TestBalance), Error> { match a { - MultiAsset { fun: Fungible(amount), id: Concrete(_id) } => Ok((1, *amount)), + Asset { fun: Fungible(amount), id: AssetId(_id) } => Ok((1, *amount)), _ => Err(Error::AssetNotHandled), } } @@ -491,14 +726,14 @@ mod tests { struct FeeChargerAssetsHandleRefund; impl ChargeWeightInFungibles for FeeChargerAssetsHandleRefund { fn charge_weight_in_fungibles( - _: >::AssetId, + _: >::AssetId, _: Weight, - ) -> Result<>::Balance, XcmError> { + ) -> Result<>::Balance, XcmError> { Ok(AMOUNT) } } impl TakeRevenue for FeeChargerAssetsHandleRefund { - fn take_revenue(_: MultiAsset) {} + fn take_revenue(_: Asset) {} } // create new instance @@ -513,8 +748,8 @@ mod tests { let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None }; // prepare test data - let asset: MultiAsset = (Here, AMOUNT).into(); - let payment = Assets::from(asset); + let asset: Asset = (Here, AMOUNT).into(); + let payment = AssetsInHolding::from(asset); let weight_to_buy = Weight::from_parts(1_000, 1_000); // lets do first call (success) @@ -525,7 +760,7 @@ mod tests { } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// parent relay chain. Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -537,19 +772,24 @@ pub struct ToParentDeliveryHelper>, + ExistentialDeposit: Get>, PriceForDelivery: PriceForMessageDelivery, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParentDeliveryHelper { fn ensure_successful_delivery( - origin_ref: &MultiLocation, - _dest: &MultiLocation, + origin_ref: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, - ) -> (Option, Option) { - use xcm::latest::{MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_MULTIASSETS}; + ) -> (Option, Option) { + use xcm::latest::{MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_ASSETS}; use xcm_executor::{traits::FeeManager, FeesMode}; + // check if the destination is relay/parent + if dest.ne(&Location::parent()) { + return (None, None); + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees @@ -560,8 +800,8 @@ impl< } // overestimate delivery fee - let mut max_assets: Vec = Vec::new(); - for i in 0..MAX_ITEMS_IN_MULTIASSETS { + let mut max_assets: Vec = Vec::new(); + for i in 0..MAX_ITEMS_IN_ASSETS { max_assets.push((GeneralIndex(i as u128), 100u128).into()); } let overestimated_xcm = diff --git a/cumulus/primitives/utility/src/tests/mod.rs b/cumulus/primitives/utility/src/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0ad8718b89ed38f686f3c854adf9cd185aeb272 --- /dev/null +++ b/cumulus/primitives/utility/src/tests/mod.rs @@ -0,0 +1,17 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 swap_first; diff --git a/cumulus/primitives/utility/src/tests/swap_first.rs b/cumulus/primitives/utility/src/tests/swap_first.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e19db498816bc02c65f278a7f874bc9671051e0 --- /dev/null +++ b/cumulus/primitives/utility/src/tests/swap_first.rs @@ -0,0 +1,551 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 frame_support::{parameter_types, traits::fungibles::Inspect}; +use mock::{setup_pool, AccountId, AssetId, Balance, Fungibles}; +use xcm::latest::AssetId as XcmAssetId; +use xcm_executor::AssetsInHolding; + +fn create_holding_asset(asset_id: AssetId, amount: Balance) -> AssetsInHolding { + create_asset(asset_id, amount).into() +} + +fn create_asset(asset_id: AssetId, amount: Balance) -> Asset { + Asset { id: create_asset_id(asset_id), fun: Fungible(amount) } +} + +fn create_asset_id(asset_id: AssetId) -> XcmAssetId { + AssetId(Location::new(0, [GeneralIndex(asset_id.into())])) +} + +fn xcm_context() -> XcmContext { + XcmContext { origin: None, message_id: [0u8; 32], topic: None } +} + +fn weight_worth_of(fee: Balance) -> Weight { + Weight::from_parts(fee.try_into().unwrap(), 0) +} + +const TARGET_ASSET: AssetId = 1; +const CLIENT_ASSET: AssetId = 2; +const CLIENT_ASSET_2: AssetId = 3; + +parameter_types! { + pub const TargetAsset: AssetId = TARGET_ASSET; +} + +pub type Trader = SwapFirstAssetTrader< + TargetAsset, + mock::Swap, + mock::WeightToFee, + mock::Fungibles, + mock::FungiblesMatcher, + (), + AccountId, +>; + +#[test] +fn holding_asset_swap_for_target() { + let client_asset_total = 15; + let fee = 5; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(), + holding_change + ); + + assert_eq!(trader.total_fee.peek(), fee); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee); +} + +#[test] +fn holding_asset_swap_for_target_twice() { + let client_asset_total = 20; + let fee1 = 5; + let fee2 = 6; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_change1 = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1); + let holding_change2 = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1 - fee2); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader.buy_weight(weight_worth_of(fee1), holding_asset, &xcm_context()).unwrap(), + holding_change1 + ); + assert_eq!( + trader + .buy_weight(weight_worth_of(fee2), holding_change1, &xcm_context()) + .unwrap(), + holding_change2 + ); + + assert_eq!(trader.total_fee.peek(), fee1 + fee2); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee1 + fee2); +} + +#[test] +fn buy_and_refund_twice_for_target() { + let client_asset_total = 15; + let fee = 5; + let refund1 = 4; + let refund2 = 2; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + // create pool for refund swap. + setup_pool(TARGET_ASSET, 1000, CLIENT_ASSET, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee); + let refund_asset = create_asset(CLIENT_ASSET, refund1); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(), + holding_change + ); + + assert_eq!(trader.total_fee.peek(), fee); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(trader.refund_weight(weight_worth_of(refund1), &xcm_context()), Some(refund_asset)); + + assert_eq!(trader.total_fee.peek(), fee - refund1); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(trader.refund_weight(weight_worth_of(refund2), &xcm_context()), None); + + assert_eq!(trader.total_fee.peek(), fee - refund1); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee - refund1); +} + +#[test] +fn buy_with_various_assets_and_refund_for_target() { + let client_asset_total = 10; + let client_asset_2_total = 15; + let fee1 = 5; + let fee2 = 6; + let refund1 = 6; + let refund2 = 4; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + setup_pool(CLIENT_ASSET_2, 1000, TARGET_ASSET, 1000); + // create pool for refund swap. + setup_pool(TARGET_ASSET, 1000, CLIENT_ASSET_2, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_asset_2 = create_holding_asset(CLIENT_ASSET_2, client_asset_2_total); + let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee1); + let holding_change_2 = create_holding_asset(CLIENT_ASSET_2, client_asset_2_total - fee2); + // both refunds in the latest buy asset (`CLIENT_ASSET_2`). + let refund_asset = create_asset(CLIENT_ASSET_2, refund1); + let refund_asset_2 = create_asset(CLIENT_ASSET_2, refund2); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + let client_total_2 = Fungibles::total_issuance(CLIENT_ASSET_2); + + let mut trader = Trader::new(); + // first purchase with `CLIENT_ASSET`. + assert_eq!( + trader.buy_weight(weight_worth_of(fee1), holding_asset, &xcm_context()).unwrap(), + holding_change + ); + + assert_eq!(trader.total_fee.peek(), fee1); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + // second purchase with `CLIENT_ASSET_2`. + assert_eq!( + trader + .buy_weight(weight_worth_of(fee2), holding_asset_2, &xcm_context()) + .unwrap(), + holding_change_2 + ); + + assert_eq!(trader.total_fee.peek(), fee1 + fee2); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2))); + + // first refund in the last asset used with `buy_weight`. + assert_eq!(trader.refund_weight(weight_worth_of(refund1), &xcm_context()), Some(refund_asset)); + + assert_eq!(trader.total_fee.peek(), fee1 + fee2 - refund1); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2))); + + // second refund in the last asset used with `buy_weight`. + assert_eq!( + trader.refund_weight(weight_worth_of(refund2), &xcm_context()), + Some(refund_asset_2) + ); + + assert_eq!(trader.total_fee.peek(), fee1 + fee2 - refund1 - refund2); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET_2))); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee1); + assert_eq!( + Fungibles::total_issuance(CLIENT_ASSET_2), + client_total_2 + fee2 - refund1 - refund2 + ); +} + +#[test] +fn not_enough_to_refund() { + let client_asset_total = 15; + let fee = 5; + let refund = 6; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(), + holding_change + ); + + assert_eq!(trader.total_fee.peek(), fee); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(trader.refund_weight(weight_worth_of(refund), &xcm_context()), None); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee); +} + +#[test] +fn not_exchangeable_to_refund() { + let client_asset_total = 15; + let fee = 5; + let refund = 1; + + setup_pool(CLIENT_ASSET, 1000, TARGET_ASSET, 1000); + + let holding_asset = create_holding_asset(CLIENT_ASSET, client_asset_total); + let holding_change = create_holding_asset(CLIENT_ASSET, client_asset_total - fee); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader.buy_weight(weight_worth_of(fee), holding_asset, &xcm_context()).unwrap(), + holding_change + ); + + assert_eq!(trader.total_fee.peek(), fee); + assert_eq!(trader.last_fee_asset, Some(create_asset_id(CLIENT_ASSET))); + + assert_eq!(trader.refund_weight(weight_worth_of(refund), &xcm_context()), None); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total + fee); +} + +#[test] +fn nothing_to_refund() { + let fee = 5; + + let mut trader = Trader::new(); + assert_eq!(trader.refund_weight(weight_worth_of(fee), &xcm_context()), None); +} + +#[test] +fn holding_asset_not_exchangeable_for_target() { + let holding_asset = create_holding_asset(CLIENT_ASSET, 10); + + let target_total = Fungibles::total_issuance(TARGET_ASSET); + let client_total = Fungibles::total_issuance(CLIENT_ASSET); + + let mut trader = Trader::new(); + assert_eq!( + trader + .buy_weight(Weight::from_all(10), holding_asset, &xcm_context()) + .unwrap_err(), + XcmError::FeesNotMet + ); + + assert_eq!(Fungibles::total_issuance(TARGET_ASSET), target_total); + assert_eq!(Fungibles::total_issuance(CLIENT_ASSET), client_total); +} + +#[test] +fn empty_holding_asset() { + let mut trader = Trader::new(); + assert_eq!( + trader + .buy_weight(Weight::from_all(10), AssetsInHolding::new(), &xcm_context()) + .unwrap_err(), + XcmError::AssetNotFound + ); +} + +#[test] +fn fails_to_match_holding_asset() { + let mut trader = Trader::new(); + let holding_asset = Asset { id: AssetId(Location::new(1, [Parachain(1)])), fun: Fungible(10) }; + assert_eq!( + trader + .buy_weight(Weight::from_all(10), holding_asset.into(), &xcm_context()) + .unwrap_err(), + XcmError::AssetNotFound + ); +} + +#[test] +fn holding_asset_equal_to_target_asset() { + let mut trader = Trader::new(); + let holding_asset = create_holding_asset(TargetAsset::get(), 10); + assert_eq!( + trader + .buy_weight(Weight::from_all(10), holding_asset, &xcm_context()) + .unwrap_err(), + XcmError::FeesNotMet + ); +} + +pub mod mock { + use crate::*; + use core::cell::RefCell; + use frame_support::{ + ensure, + traits::{ + fungibles::{Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Unbalanced}, + tokens::{ + DepositConsequence, Fortitude, Fortitude::Polite, Precision::Exact, Preservation, + Preservation::Preserve, Provenance, WithdrawConsequence, + }, + }, + }; + use sp_runtime::{traits::One, DispatchError}; + use std::collections::HashMap; + use xcm::latest::Junction; + + pub type AccountId = u64; + pub type AssetId = u32; + pub type Balance = u128; + pub type Credit = fungibles::Credit; + + thread_local! { + pub static TOTAL_ISSUANCE: RefCell> = RefCell::new(HashMap::new()); + pub static ACCOUNT: RefCell> = RefCell::new(HashMap::new()); + pub static SWAP: RefCell> = RefCell::new(HashMap::new()); + } + + pub struct Swap {} + impl SwapCreditT for Swap { + type Balance = Balance; + type AssetKind = AssetId; + type Credit = Credit; + fn max_path_len() -> u32 { + 2 + } + fn swap_exact_tokens_for_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out_min: Option, + ) -> Result { + ensure!(2 == path.len(), (credit_in, DispatchError::Unavailable)); + ensure!( + credit_in.peek() >= amount_out_min.unwrap_or(Self::Balance::zero()), + (credit_in, DispatchError::Unavailable) + ); + let swap_res = SWAP.with(|b| b.borrow().get(&(path[0], path[1])).map(|v| *v)); + let pool_account = match swap_res { + Some(a) => a, + None => return Err((credit_in, DispatchError::Unavailable)), + }; + let credit_out = match Fungibles::withdraw( + path[1], + &pool_account, + credit_in.peek(), + Exact, + Preserve, + Polite, + ) { + Ok(c) => c, + Err(_) => return Err((credit_in, DispatchError::Unavailable)), + }; + let _ = Fungibles::resolve(&pool_account, credit_in) + .map_err(|c| (c, DispatchError::Unavailable))?; + Ok(credit_out) + } + fn swap_tokens_for_exact_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out: Self::Balance, + ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> { + ensure!(2 == path.len(), (credit_in, DispatchError::Unavailable)); + ensure!(credit_in.peek() >= amount_out, (credit_in, DispatchError::Unavailable)); + let swap_res = SWAP.with(|b| b.borrow().get(&(path[0], path[1])).map(|v| *v)); + let pool_account = match swap_res { + Some(a) => a, + None => return Err((credit_in, DispatchError::Unavailable)), + }; + let credit_out = match Fungibles::withdraw( + path[1], + &pool_account, + amount_out, + Exact, + Preserve, + Polite, + ) { + Ok(c) => c, + Err(_) => return Err((credit_in, DispatchError::Unavailable)), + }; + let (credit_in, change) = credit_in.split(amount_out); + let _ = Fungibles::resolve(&pool_account, credit_in) + .map_err(|c| (c, DispatchError::Unavailable))?; + Ok((credit_out, change)) + } + } + + pub fn pool_account(asset1: AssetId, asset2: AssetId) -> AccountId { + (1000 + asset1 * 10 + asset2 * 100).into() + } + + pub fn setup_pool(asset1: AssetId, liquidity1: Balance, asset2: AssetId, liquidity2: Balance) { + let account = pool_account(asset1, asset2); + SWAP.with(|b| b.borrow_mut().insert((asset1, asset2), account)); + let debt1 = Fungibles::deposit(asset1, &account, liquidity1, Exact); + let debt2 = Fungibles::deposit(asset2, &account, liquidity2, Exact); + drop(debt1); + drop(debt2); + } + + pub struct WeightToFee; + impl WeightToFeeT for WeightToFee { + type Balance = Balance; + fn weight_to_fee(weight: &Weight) -> Self::Balance { + (weight.ref_time() + weight.proof_size()).into() + } + } + + pub struct Fungibles {} + impl Inspect for Fungibles { + type AssetId = AssetId; + type Balance = Balance; + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + TOTAL_ISSUANCE.with(|b| b.borrow().get(&asset).map_or(Self::Balance::zero(), |b| *b)) + } + fn minimum_balance(_: Self::AssetId) -> Self::Balance { + Self::Balance::one() + } + fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b)) + } + fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b)) + } + fn reducible_balance( + asset: Self::AssetId, + who: &AccountId, + _: Preservation, + _: Fortitude, + ) -> Self::Balance { + ACCOUNT.with(|b| b.borrow().get(&(asset, *who)).map_or(Self::Balance::zero(), |b| *b)) + } + fn can_deposit( + _: Self::AssetId, + _: &AccountId, + _: Self::Balance, + _: Provenance, + ) -> DepositConsequence { + unimplemented!() + } + fn can_withdraw( + _: Self::AssetId, + _: &AccountId, + _: Self::Balance, + ) -> WithdrawConsequence { + unimplemented!() + } + fn asset_exists(_: Self::AssetId) -> bool { + unimplemented!() + } + } + + impl Unbalanced for Fungibles { + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) { + TOTAL_ISSUANCE.with(|b| b.borrow_mut().insert(asset, amount)); + } + fn handle_dust(_: Dust) { + unimplemented!() + } + fn write_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let _ = ACCOUNT.with(|b| b.borrow_mut().insert((asset, *who), amount)); + Ok(None) + } + } + + impl Balanced for Fungibles { + type OnDropCredit = DecreaseIssuance; + type OnDropDebt = IncreaseIssuance; + } + + pub struct FungiblesMatcher; + impl MatchesFungibles for FungiblesMatcher { + fn matches_fungibles( + a: &Asset, + ) -> core::result::Result<(AssetId, Balance), xcm_executor::traits::Error> { + match a { + Asset { fun: Fungible(amount), id: AssetId(inner_location) } => + match inner_location.unpack() { + (0, [Junction::GeneralIndex(id)]) => + Ok(((*id).try_into().unwrap(), *amount)), + _ => Err(xcm_executor::traits::Error::AssetNotHandled), + }, + _ => Err(xcm_executor::traits::Error::AssetNotHandled), + } + } + } +} diff --git a/cumulus/scripts/benchmarks.sh b/cumulus/scripts/benchmarks.sh index 29d0690592583512e7923f92b9d81d46913d04e9..58b8419bf4ae7ef1db1de785ed8ebac137063556 100755 --- a/cumulus/scripts/benchmarks.sh +++ b/cumulus/scripts/benchmarks.sh @@ -6,14 +6,7 @@ repeat=${3:-20} __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -${__dir}/benchmarks-ci.sh collectives collectives-polkadot target/$target $steps $repeat - -${__dir}/benchmarks-ci.sh assets asset-hub-kusama target/$target $steps $repeat -${__dir}/benchmarks-ci.sh assets asset-hub-polkadot target/$target $steps $repeat +${__dir}/benchmarks-ci.sh collectives collectives-westend target/$target $steps $repeat ${__dir}/benchmarks-ci.sh assets asset-hub-westend target/$target $steps $repeat - -${__dir}/benchmarks-ci.sh bridge-hubs bridge-hub-polkadot target/$target $steps $repeat -${__dir}/benchmarks-ci.sh bridge-hubs bridge-hub-kusama target/$target $steps $repeat ${__dir}/benchmarks-ci.sh bridge-hubs bridge-hub-rococo target/$target $steps $repeat - -${__dir}/benchmarks-ci.sh glutton glutton-kusama target/$target $steps $repeat +${__dir}/benchmarks-ci.sh glutton glutton-westend target/$target $steps $repeat diff --git a/cumulus/scripts/bridges_rococo_wococo.sh b/cumulus/scripts/bridges_rococo_wococo.sh deleted file mode 100755 index 4211a37226dcfabe958c47fe6d15971630345f8e..0000000000000000000000000000000000000000 --- a/cumulus/scripts/bridges_rococo_wococo.sh +++ /dev/null @@ -1,384 +0,0 @@ -#!/bin/bash - -# import common functions -source "$(dirname "$0")"/bridges_common.sh - -# Expected sovereign accounts. -# -# Generated by: -# -# #[test] -# fn generate_sovereign_accounts() { -# use sp_core::crypto::Ss58Codec; -# use polkadot_parachain_primitives::primitives::Sibling; -# -# parameter_types! { -# pub UniversalLocationAHR: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(1000)); -# pub UniversalLocationAHW: InteriorMultiLocation = X2(GlobalConsensus(Wococo), Parachain(1000)); -# } -# -# // SS58=42 -# println!("GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# GlobalConsensusConvertsFor::::convert_location( -# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Rococo)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# println!("GLOBAL_CONSENSUS_ROCOCO_ASSET_HUB_ROCOCO_1000_SOVEREIGN_ACCOUNT=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# GlobalConsensusParachainConvertsFor::::convert_location( -# &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Rococo), Parachain(1000)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# println!("ASSET_HUB_WOCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WOCOCO=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# SiblingParachainConvertsVia::::convert_location( -# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# -# // SS58=42 -# println!("GLOBAL_CONSENSUS_WOCOCO_SOVEREIGN_ACCOUNT=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# GlobalConsensusConvertsFor::::convert_location( -# &MultiLocation { parents: 2, interior: X1(GlobalConsensus(Wococo)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# println!("GLOBAL_CONSENSUS_WOCOCO_ASSET_HUB_WOCOCO_1000_SOVEREIGN_ACCOUNT=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# GlobalConsensusParachainConvertsFor::::convert_location( -# &MultiLocation { parents: 2, interior: X2(GlobalConsensus(Wococo), Parachain(1000)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# println!("ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# SiblingParachainConvertsVia::::convert_location( -# &MultiLocation { parents: 1, interior: X1(Parachain(1000)) }).unwrap() -# ).to_ss58check_with_version(42_u16.into()) -# ); -# } -GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT="5GxRGwT8bU1JeBPTUXc7LEjZMxNrK8MyL2NJnkWFQJTQ4sii" -GLOBAL_CONSENSUS_ROCOCO_ASSET_HUB_ROCOCO_1000_SOVEREIGN_ACCOUNT="5CfNu7eH3SJvqqPt3aJh38T8dcFvhGzEohp9tsd41ANhXDnQ" -ASSET_HUB_WOCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WOCOCO="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV" -GLOBAL_CONSENSUS_WOCOCO_SOVEREIGN_ACCOUNT="5EWw2NzfPr2DCahourp33cya6bGWEJViTnJN6Z2ruFevpJML" -GLOBAL_CONSENSUS_WOCOCO_ASSET_HUB_WOCOCO_1000_SOVEREIGN_ACCOUNT="5EJX8L4dwGyYnCsjZ91LfWAsm3rCN8vY2AYvT4mauMEjsrQz" -ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZhYtddZQSQUqvYJ1f2mLtinVhV" - -# Expected sovereign accounts for rewards on BridgeHubs. -# -# Generated by: -#[test] -#fn generate_sovereign_accounts_for_rewards() { -# use sp_core::crypto::Ss58Codec; -# -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 1]), -# *b"bhwo", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 1]), -# *b"bhwo", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 1]), -# *b"bhro", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 1]), -# *b"bhro", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -#} -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_ThisChain="5EHnXaT5BhiS8YRPMeHi97YHofTtNx4pLNb8wR8TwjVq1gzU" -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_BridgedChain="5EHnXaT5BhiS8YRPMeHyt95svA95qWAh53XeVMpJQZNZHAzj" -ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_ThisChain="5EHnXaT5BhiS8YRNuCukWXTQdAqARjjXmpjehjx1YZNE5keZ" -ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_BridgedChain="5EHnXaT5BhiS8YRNuCv2FYzzjfWMtHqQWVgAFgdr1PExMN94" - -LANE_ID="00000001" - -function init_ro_wo() { - ensure_relayer - - RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ - ~/local_bridge_testing/bin/substrate-relay init-bridge rococo-to-bridge-hub-wococo \ - --source-host localhost \ - --source-port 9942 \ - --source-version-mode Auto \ - --target-host localhost \ - --target-port 8945 \ - --target-version-mode Auto \ - --target-signer //Bob -} - -function init_wo_ro() { - ensure_relayer - - RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ - ~/local_bridge_testing/bin/substrate-relay init-bridge wococo-to-bridge-hub-rococo \ - --source-host localhost \ - --source-port 9945 \ - --source-version-mode Auto \ - --target-host localhost \ - --target-port 8943 \ - --target-version-mode Auto \ - --target-signer //Bob -} - -function run_relay() { - ensure_relayer - - RUST_LOG=runtime=trace,rpc=trace,bridge=trace \ - ~/local_bridge_testing/bin/substrate-relay relay-headers-and-messages bridge-hub-rococo-bridge-hub-wococo \ - --rococo-host localhost \ - --rococo-port 9942 \ - --rococo-version-mode Auto \ - --bridge-hub-rococo-host localhost \ - --bridge-hub-rococo-port 8943 \ - --bridge-hub-rococo-version-mode Auto \ - --bridge-hub-rococo-signer //Charlie \ - --wococo-headers-to-bridge-hub-rococo-signer //Bob \ - --wococo-parachains-to-bridge-hub-rococo-signer //Bob \ - --bridge-hub-rococo-transactions-mortality 4 \ - --wococo-host localhost \ - --wococo-port 9945 \ - --wococo-version-mode Auto \ - --bridge-hub-wococo-host localhost \ - --bridge-hub-wococo-port 8945 \ - --bridge-hub-wococo-version-mode Auto \ - --bridge-hub-wococo-signer //Charlie \ - --rococo-headers-to-bridge-hub-wococo-signer //Bob \ - --rococo-parachains-to-bridge-hub-wococo-signer //Bob \ - --bridge-hub-wococo-transactions-mortality 4 \ - --lane "${LANE_ID}" -} - -case "$1" in - run-relay) - init_ro_wo - init_wo_ro - run_relay - ;; - init-asset-hub-rococo-local) - ensure_polkadot_js_api - # create foreign assets for native Wococo token (governance call on Rococo) - force_create_foreign_asset \ - "ws://127.0.0.1:9942" \ - "//Alice" \ - 1000 \ - "ws://127.0.0.1:9910" \ - "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Wococo" } } }')" \ - "$GLOBAL_CONSENSUS_WOCOCO_SOVEREIGN_ACCOUNT" \ - 10000000000 \ - true - # drip SA which holds reserves - transfer_balance \ - "ws://127.0.0.1:9910" \ - "//Alice" \ - "$GLOBAL_CONSENSUS_WOCOCO_ASSET_HUB_WOCOCO_1000_SOVEREIGN_ACCOUNT" \ - $((1000000000 + 50000000000 * 20)) - # HRMP - open_hrmp_channels \ - "ws://127.0.0.1:9942" \ - "//Alice" \ - 1000 1013 4 524288 - open_hrmp_channels \ - "ws://127.0.0.1:9942" \ - "//Alice" \ - 1013 1000 4 524288 - ;; - init-bridge-hub-rococo-local) - ensure_polkadot_js_api - # SA of sibling asset hub pays for the execution - transfer_balance \ - "ws://127.0.0.1:8943" \ - "//Alice" \ - "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \ - $((1000000000 + 50000000000 * 20)) - # drip SA of lane dedicated to asset hub for paying rewards for delivery - transfer_balance \ - "ws://127.0.0.1:8943" \ - "//Alice" \ - "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_ThisChain" \ - $((1000000000 + 2000000000000)) - # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation - transfer_balance \ - "ws://127.0.0.1:8943" \ - "//Alice" \ - "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhwo_BridgedChain" \ - $((1000000000 + 2000000000000)) - ;; - init-asset-hub-wococo-local) - ensure_polkadot_js_api - # set Wococo flavor - set_storage with: - # - `key` is `HexDisplay::from(&asset_hub_rococo_runtime::xcm_config::Flavor::key())` - # - `value` is `HexDisplay::from(&asset_hub_rococo_runtime::RuntimeFlavor::Wococo.encode())` - set_storage \ - "ws://127.0.0.1:9945" \ - "//Alice" \ - 1000 \ - "ws://127.0.0.1:9010" \ - "$(jq --null-input '[["0x48297505634037ef48c848c99c0b1f1b", "0x01"]]')" - # create foreign assets for native Rococo token (governance call on Wococo) - force_create_foreign_asset \ - "ws://127.0.0.1:9945" \ - "//Alice" \ - 1000 \ - "ws://127.0.0.1:9010" \ - "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } }')" \ - "$GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT" \ - 10000000000 \ - true - # drip SA which holds reserves - transfer_balance \ - "ws://127.0.0.1:9010" \ - "//Alice" \ - "$GLOBAL_CONSENSUS_ROCOCO_ASSET_HUB_ROCOCO_1000_SOVEREIGN_ACCOUNT" \ - $((1000000000 + 50000000000 * 20)) - # HRMP - open_hrmp_channels \ - "ws://127.0.0.1:9945" \ - "//Alice" \ - 1000 1014 4 524288 - open_hrmp_channels \ - "ws://127.0.0.1:9945" \ - "//Alice" \ - 1014 1000 4 524288 - ;; - init-bridge-hub-wococo-local) - # set Wococo flavor - set_storage with: - # - `key` is `HexDisplay::from(&bridge_hub_rococo_runtime::xcm_config::Flavor::key())` - # - `value` is `HexDisplay::from(&bridge_hub_rococo_runtime::RuntimeFlavor::Wococo.encode())` - set_storage \ - "ws://127.0.0.1:9945" \ - "//Alice" \ - 1014 \ - "ws://127.0.0.1:8945" \ - "$(jq --null-input '[["0x48297505634037ef48c848c99c0b1f1b", "0x01"]]')" - # SA of sibling asset hub pays for the execution - transfer_balance \ - "ws://127.0.0.1:8945" \ - "//Alice" \ - "$ASSET_HUB_WOCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WOCOCO" \ - $((1000000000 + 50000000000 * 20)) - # drip SA of lane dedicated to asset hub for paying rewards for delivery - transfer_balance \ - "ws://127.0.0.1:8945" \ - "//Alice" \ - "$ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_ThisChain" \ - $((1000000000 + 2000000000000)) - # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation - transfer_balance \ - "ws://127.0.0.1:8945" \ - "//Alice" \ - "$ON_BRIDGE_HUB_WOCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000001_bhro_BridgedChain" \ - $((1000000000 + 2000000000000)) - ;; - reserve-transfer-assets-from-asset-hub-rococo-local) - ensure_polkadot_js_api - # send ROCs to Alice account on AHW - limited_reserve_transfer_assets \ - "ws://127.0.0.1:9910" \ - "//Alice" \ - "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Wococo" }, { "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 } } ] }')" \ - 0 \ - "Unlimited" - ;; - reserve-transfer-assets-from-asset-hub-wococo-local) - ensure_polkadot_js_api - # send WOCs to Alice account on AHR - limited_reserve_transfer_assets \ - "ws://127.0.0.1:9010" \ - "//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 } } ] }')" \ - 0 \ - "Unlimited" - ;; - claim-rewards-bridge-hub-rococo-local) - ensure_polkadot_js_api - # bhwo -> [62, 68, 77, 6f] -> 0x6268776f - claim_rewards \ - "ws://127.0.0.1:8943" \ - "//Charlie" \ - "0x${LANE_ID}" \ - "0x6268776f" \ - "ThisChain" - claim_rewards \ - "ws://127.0.0.1:8943" \ - "//Charlie" \ - "0x${LANE_ID}" \ - "0x6268776f" \ - "BridgedChain" - ;; - claim-rewards-bridge-hub-wococo-local) - # bhro -> [62, 68, 72, 6f] -> 0x6268726f - claim_rewards \ - "ws://127.0.0.1:8945" \ - "//Charlie" \ - "0x${LANE_ID}" \ - "0x6268726f" \ - "ThisChain" - claim_rewards \ - "ws://127.0.0.1:8945" \ - "//Charlie" \ - "0x${LANE_ID}" \ - "0x6268726f" \ - "BridgedChain" - ;; - stop) - pkill -f polkadot - pkill -f parachain - ;; - import) - # to avoid trigger anything here - ;; - *) - echo "A command is require. Supported commands for: - Local (zombienet) run: - - run-relay - - init-asset-hub-rococo-local - - init-bridge-hub-rococo-local - - init-asset-hub-wococo-local - - init-bridge-hub-wococo-local - - reserve-transfer-assets-from-asset-hub-rococo-local - - reserve-transfer-assets-from-asset-hub-wococo-local - - claim-rewards-bridge-hub-rococo-local - - claim-rewards-bridge-hub-wococo-local"; - exit 1 - ;; -esac diff --git a/cumulus/scripts/create_bridge_hub_kusama_spec.sh b/cumulus/scripts/create_bridge_hub_kusama_spec.sh deleted file mode 100755 index 813921b079a8c64f8b12646570ffca587b0fa718..0000000000000000000000000000000000000000 --- a/cumulus/scripts/create_bridge_hub_kusama_spec.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash - -usage() { - echo Usage: - echo "$1 " - echo "$2 " - echo "e.g.: ./scripts/create_bridge_hub_kusama_spec.sh ./target/release/wbuild/bridge-hub-kusama-runtime/bridge_hub_kusama_runtime.compact.compressed.wasm 1002" - 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 bridge-hub-kusama-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 -cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtime.system.code = ("0x" + $code)' \ - | jq '.name = "Kusama BridgeHub"' \ - | jq '.id = "bridge-hub-kusama"' \ - | jq '.chainType = "Live"' \ - | jq '.bootNodes = [ - "/dns/kusama-bridge-hub-collator-ew1-0.polkadot.io/tcp/30334/p2p/12D3KooWP2Gngt4tt2sz5BgDaAbMTxasPWk3V2Z99bQTmFcAorqa", - "/dns/kusama-bridge-hub-collator-ew1-1.polkadot.io/tcp/30334/p2p/12D3KooWMmL3FQuYmruBui1sbY4MwNmvicinrePi1Yq4QMRSYHoR", - "/dns/kusama-bridge-hub-collator-ue4-0.polkadot.io/tcp/30334/p2p/12D3KooWQpTocTck1tNBzMNTHJ3kSv4vzv8Yf9FpVkfGnungbez4", - "/dns/kusama-bridge-hub-collator-ue4-1.polkadot.io/tcp/30334/p2p/12D3KooWRgtJqKEaMi7hkU4VMiGhpHTJeL8N7JgL7d9gwooPv4eW", - - "/dns/kusama-bridge-hub-connect-ew1-0.polkadot.io/tcp/30334/p2p/12D3KooWPQQPivrqQ51kRTDc2R1mtqwKT4GGtk2rapkY4FrwHrEp", - "/dns/kusama-bridge-hub-connect-ew1-1.polkadot.io/tcp/30334/p2p/12D3KooWPcF9Yk4gYrMju9CyWCV69hAFXbYsnxCLogwLGu9QFTRn", - "/dns/kusama-bridge-hub-connect-ue4-0.polkadot.io/tcp/30334/p2p/12D3KooWMf1sVnJDTkKWtaThqvrgcSPLbfGXttSqbwhM2DJp9BUG", - "/dns/kusama-bridge-hub-connect-ue4-1.polkadot.io/tcp/30334/p2p/12D3KooWQaV7wMfNVKy2aMz4Lds3TTxgSDyZAUEnbAZMfD8rW3ow", - - "/dns/kusama-bridge-hub-connect-ew1-0.polkadot.io/tcp/443/wss/p2p/12D3KooWPQQPivrqQ51kRTDc2R1mtqwKT4GGtk2rapkY4FrwHrEp", - "/dns/kusama-bridge-hub-connect-ew1-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPcF9Yk4gYrMju9CyWCV69hAFXbYsnxCLogwLGu9QFTRn", - "/dns/kusama-bridge-hub-connect-ue4-0.polkadot.io/tcp/443/wss/p2p/12D3KooWMf1sVnJDTkKWtaThqvrgcSPLbfGXttSqbwhM2DJp9BUG", - "/dns/kusama-bridge-hub-connect-ue4-1.polkadot.io/tcp/443/wss/p2p/12D3KooWQaV7wMfNVKy2aMz4Lds3TTxgSDyZAUEnbAZMfD8rW3ow" - - ]' \ - | jq '.relay_chain = "kusama"' \ - | jq --argjson para_id $para_id '.para_id = $para_id' \ - | jq --argjson para_id $para_id '.genesis.runtime.parachainInfo.parachainId = $para_id' \ - | jq '.genesis.runtime.balances.balances = []' \ - | jq '.genesis.runtime.collatorSelection.invulnerables = [ - "DQkekNBt8g6D7bPUEqhgfujADxzzfivr1qQZJkeGzAqnEzF", - "HbUc5qrLtKAZvasioiTSf1CunaN2SyEwvfsgMuYQjXA5sfk", - "JEe4NcVyuWFEwZe4WLfRtynDswyKgvLS8H8r4Wo9d3t61g1", - "FAe4DGhQHKTm35n5MgBFNBZvyEJcm7QAwgnVNQU8KXP2ixn" - ]' \ - | jq '.genesis.runtime.session.keys = [ - [ - "DQkekNBt8g6D7bPUEqhgfujADxzzfivr1qQZJkeGzAqnEzF", - "DQkekNBt8g6D7bPUEqhgfujADxzzfivr1qQZJkeGzAqnEzF", - { - "aura": "5E7AiV9ygGUcfdK3XVoJsew7fsu18uvKQHYhksE5PXDNfRL9" - } - ], - [ - "HbUc5qrLtKAZvasioiTSf1CunaN2SyEwvfsgMuYQjXA5sfk", - "HbUc5qrLtKAZvasioiTSf1CunaN2SyEwvfsgMuYQjXA5sfk", - { - "aura": "5CyXoMh8cA2MSk55JASpCfhCg44iSG5fBwmhvSfXUUS3uhPR" - } - ], - [ - "JEe4NcVyuWFEwZe4WLfRtynDswyKgvLS8H8r4Wo9d3t61g1", - "JEe4NcVyuWFEwZe4WLfRtynDswyKgvLS8H8r4Wo9d3t61g1", - { - "aura": "5Grj5pN52kKU61qK9qP5cf9ADuyowe2WVvYWxMNK1QqAM8qf" - } - ], - [ - "FAe4DGhQHKTm35n5MgBFNBZvyEJcm7QAwgnVNQU8KXP2ixn", - "FAe4DGhQHKTm35n5MgBFNBZvyEJcm7QAwgnVNQU8KXP2ixn", - { - "aura": "5EHTyftGjcHfe71VVuZqCeLbHNf4ptYzgdAMMyqpTNbs5Rrp" - } - ] - ]' \ - > 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 bridge-hub-kusama-spec.json -cp chain-spec-raw.json ./parachains/chain-specs/bridge-hub-kusama.json -cp chain-spec-raw.json bridge-hub-kusama-spec-raw.json - -# build genesis data -$binary export-genesis-state --chain chain-spec-raw.json > bridge-hub-kusama-genesis-head-data - -# build genesis wasm -$binary export-genesis-wasm --chain chain-spec-raw.json > bridge-hub-kusama-wasm diff --git a/cumulus/scripts/create_bridge_hub_polkadot_spec.sh b/cumulus/scripts/create_bridge_hub_polkadot_spec.sh deleted file mode 100755 index 49bc9cee692bc077cdef4d61991e33c2b3db5d9c..0000000000000000000000000000000000000000 --- a/cumulus/scripts/create_bridge_hub_polkadot_spec.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash - -usage() { - echo Usage: - echo "$1 " - echo "$2 " - echo "e.g.: ./scripts/create_bridge_hub_polkadot_spec.sh ./target/release/wbuild/bridge-hub-polkadot-runtime/bridge_hub_polkadot_runtime.compact.compressed.wasm 1002" - 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 bridge-hub-polkadot-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 -cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtime.system.code = ("0x" + $code)' \ - | jq '.name = "Polkadot BridgeHub"' \ - | jq '.id = "bridge-hub-polkadot"' \ - | jq '.chainType = "Live"' \ - | jq '.bootNodes = [ - "/dns/polkadot-bridge-hub-connect-a-0.polkadot.io/tcp/30334/p2p/12D3KooWAVQMhkXmc5ueSYasdsRWQbKus2YGZ6HDZUB4ViJMCxXy", - "/dns/polkadot-bridge-hub-connect-a-1.polkadot.io/tcp/30334/p2p/12D3KooWG4ypDHLKGCv4BZ6PuaGUwQHKAH6p2D6arR2uQ1eiR1T3", - "/dns/polkadot-bridge-hub-connect-b-0.polkadot.io/tcp/30334/p2p/12D3KooWCwGKxjpJXnx1mwXKvaxGQm769EM3b6Pg5vbU33wbhsNw", - "/dns/polkadot-bridge-hub-connect-b-1.polkadot.io/tcp/30334/p2p/12D3KooWLiSEdhriJUPdZKFtAjZrQncxN2ssEoDKVrt5mGM4Qu4J", - - "/dns/polkadot-bridge-hub-connect-a-0.polkadot.io/tcp/443/wss/p2p/12D3KooWAVQMhkXmc5ueSYasdsRWQbKus2YGZ6HDZUB4ViJMCxXy", - "/dns/polkadot-bridge-hub-connect-a-1.polkadot.io/tcp/443/wss/p2p/12D3KooWG4ypDHLKGCv4BZ6PuaGUwQHKAH6p2D6arR2uQ1eiR1T3", - "/dns/polkadot-bridge-hub-connect-b-0.polkadot.io/tcp/443/wss/p2p/12D3KooWCwGKxjpJXnx1mwXKvaxGQm769EM3b6Pg5vbU33wbhsNw", - "/dns/polkadot-bridge-hub-connect-b-1.polkadot.io/tcp/443/wss/p2p/12D3KooWLiSEdhriJUPdZKFtAjZrQncxN2ssEoDKVrt5mGM4Qu4J" - ]' \ - | jq '.relay_chain = "polkadot"' \ - | jq --argjson para_id $para_id '.para_id = $para_id' \ - | jq --argjson para_id $para_id '.genesis.runtime.parachainInfo.parachainId = $para_id' \ - | jq '.genesis.runtime.balances.balances = []' \ - | jq '.genesis.runtime.collatorSelection.invulnerables = [ - "134AK3RiMA97Fx9dLj1CvuLJUa8Yo93EeLA1TkP6CCGnWMSd", - "15dU8Tt7kde2diuHzijGbKGPU5K8BPzrFJfYFozvrS1DdE21", - "1vXMKM8SctM28AQw1wSpd7p9yCUWn1uhbbKSVTuznsw8Q2x", - "15mCQcaj3QP1UdxBF82JRd9v3riZJcVNVEmx8xkFp7DSYR4Y" - ]' \ - | jq '.genesis.runtime.session.keys = [ - [ - "134AK3RiMA97Fx9dLj1CvuLJUa8Yo93EeLA1TkP6CCGnWMSd", - "134AK3RiMA97Fx9dLj1CvuLJUa8Yo93EeLA1TkP6CCGnWMSd", - { - "aura": "5EX6AnyuSPEFQ7HAPjRgzqk1sxgh8cyacGimwJ16y1nJ2w7g" - } - ], - [ - "15dU8Tt7kde2diuHzijGbKGPU5K8BPzrFJfYFozvrS1DdE21", - "15dU8Tt7kde2diuHzijGbKGPU5K8BPzrFJfYFozvrS1DdE21", - { - "aura": "5DZN8UhaJftvKhMMARmJBwrwzuEDpoUzzBvvWMbFXYsJ4CmK" - } - ], - [ - "1vXMKM8SctM28AQw1wSpd7p9yCUWn1uhbbKSVTuznsw8Q2x", - "1vXMKM8SctM28AQw1wSpd7p9yCUWn1uhbbKSVTuznsw8Q2x", - { - "aura": "5FKsn83rXQQiw7HwoeYoLMoYS5GP9YVNHZiCHwA4DSwDcPVa" - } - ], - [ - "15mCQcaj3QP1UdxBF82JRd9v3riZJcVNVEmx8xkFp7DSYR4Y", - "15mCQcaj3QP1UdxBF82JRd9v3riZJcVNVEmx8xkFp7DSYR4Y", - { - "aura": "5DCg19ckcJz4m52Th4o1LcSRK3H7NsUcQsRbu7pTDM3mZ26v" - } - ] - ]' \ - > 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 bridge-hub-polkadot-spec.json -cp chain-spec-raw.json ./parachains/chain-specs/bridge-hub-polkadot.json -cp chain-spec-raw.json bridge-hub-polkadot-spec-raw.json - -# build genesis data -$binary export-genesis-state --chain chain-spec-raw.json > bridge-hub-polkadot-genesis-head-data - -# build genesis wasm -$binary export-genesis-wasm --chain chain-spec-raw.json > bridge-hub-polkadot-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_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..516dc06ac6660c0c1f9d565d88a9ae7d132ade9e --- /dev/null +++ b/cumulus/scripts/create_coretime_westend_spec.sh @@ -0,0 +1,86 @@ +#!/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" + ]' \ + | 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" + ]' \ + | jq '.genesis.runtimeGenesis.patch.session.keys = [ + [ + "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", + "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", + { + "aura": "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi" + } + ], + [ + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", + { + "aura": "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG" + } + ] + ]' \ + > 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_glutton_spec.sh b/cumulus/scripts/create_glutton_spec.sh index c5158392f529eddf462e25e7754ec413748fdb92..78aafda3bd08ac226ce3561241599389c3503f7b 100755 --- a/cumulus/scripts/create_glutton_spec.sh +++ b/cumulus/scripts/create_glutton_spec.sh @@ -55,7 +55,7 @@ for (( para_id=$from_para_id; para_id<=$to_para_id; para_id++ )); do fi # build the chain spec we'll manipulate - $binary_path build-spec --disable-default-bootnode --chain "glutton-kusama-genesis-$para_id" > "$output_para_dir/plain-glutton-$relay_chain-$para_id-spec.json" + $binary_path build-spec --disable-default-bootnode --chain "glutton-westend-genesis-$para_id" > "$output_para_dir/plain-glutton-$relay_chain-$para_id-spec.json" id="glutton-$relay_chain-$para_id" protocol_id="glutton-$relay_chain-$para_id" 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/package-lock.json b/cumulus/scripts/generate_hex_encoded_call/package-lock.json deleted file mode 100644 index 3383265e7796e19f3e0162ade9883144ff686a66..0000000000000000000000000000000000000000 --- a/cumulus/scripts/generate_hex_encoded_call/package-lock.json +++ /dev/null @@ -1,1213 +0,0 @@ -{ - "name": "y", - "version": "y", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "y", - "version": "y", - "license": "MIT", - "dependencies": { - "@polkadot/api": "^6.5.2", - "@polkadot/util": "^7.6.1" - } - }, - "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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" - }, - "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" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/util": "8.7.1", - "@polkadot/util-crypto": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "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": "*" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/util": "8.7.1", - "@substrate/ss58-registry": "^1.17.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "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/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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "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" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/networks": "^8.1.2", - "@polkadot/types": "6.12.1", - "@polkadot/util": "^8.1.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "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-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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "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_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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@polkadot/util": "8.7.1" - } - }, - "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==", - "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/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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "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_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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/wasm-crypto-asmjs": "^5.1.1", - "@polkadot/wasm-crypto-wasm": "^5.1.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=14.0.0" - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=14.0.0" - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1", - "@types/node-fetch": "^2.6.1", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/x-global": "7.9.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.16.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/x-global": "7.9.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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/@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==", - "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" - } - }, - "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/ss58-registry": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.38.0.tgz", - "integrity": "sha512-sHiVRWekGMRZAjPukN9/W166NM6D5wtHcK6RVyLy66kg3CHNZ1BXfpXcjOiXSwhbd7guQFDEwnOVaDrbk1XL1g==" - }, - "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==", - "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==", - "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": "*" - } - }, - "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==" - }, - "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" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "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==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "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_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "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==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ed2curve": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", - "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", - "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" - }, - "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_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/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/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/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "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_modules/is-typedarray": { - "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" - }, - "engines": { - "node": ">= 0.6" - } - }, - "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==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "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/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==", - "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==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "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/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/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" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "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==", - "engines": { - "node": ">=0.10.32" - } - } - } -} diff --git a/cumulus/scripts/parachains_integration_tests.sh b/cumulus/scripts/parachains_integration_tests.sh deleted file mode 100755 index 2a06b930e22ff0fef6fbc5769ead258e78ac8864..0000000000000000000000000000000000000000 --- a/cumulus/scripts/parachains_integration_tests.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -tests=( - asset-hub-kusama - asset-hub-polkadot -) - -rm -R logs &> /dev/null - -for t in ${tests[@]} -do - printf "\n🔍 Running $t tests...\n\n" - - mkdir -p logs/$t - - parachains-integration-tests \ - -m zombienet \ - -c ./parachains/integration-tests/$t/config.toml \ - -cl ./logs/$t/chains.log 2> /dev/null & - - parachains-integration-tests \ - -m test \ - -t ./parachains/integration-tests/$t \ - -tl ./logs/$t/tests.log & tests=$! - - wait $tests - - pkill -f polkadot - pkill -f parachain - - printf "\n🎉 $t integration tests finished! \n\n" -done diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 290cfd7e4d899a84dc3d4392f4c2c1e6ae671b18..028733ce23554967ee46e5ae8f2adebf6da70eb6 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -5,8 +5,11 @@ 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } # Substrate sc-service = { path = "../../../substrate/client/service" } @@ -36,4 +39,18 @@ cumulus-test-runtime = { path = "../runtime" } cumulus-test-service = { path = "../service" } cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-primitives-core = { path = "../../primitives/core" } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction" } cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } + +[features] +runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-test-service/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "sc-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/cumulus/test/client/src/block_builder.rs b/cumulus/test/client/src/block_builder.rs index 2d930d1be59774daf6ab1cd97ef62be88bf1bfe2..929f9f5f48c4d8d97526f97d8ac59cea08797aa8 100644 --- a/cumulus/test/client/src/block_builder.rs +++ b/cumulus/test/client/src/block_builder.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::{Backend, Client}; +use crate::Client; use cumulus_primitives_core::{ParachainBlockData, PersistedValidationData}; use cumulus_primitives_parachain_inherent::{ParachainInherentData, INHERENT_IDENTIFIER}; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use cumulus_test_runtime::{Block, GetLastTimestamp, Hash, Header}; use polkadot_primitives::{BlockNumber as PBlockNumber, Hash as PHash}; -use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; +use sc_block_builder::{BlockBuilder, BlockBuilderBuilder}; use sp_api::ProvideRuntimeApi; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; @@ -37,7 +37,7 @@ pub trait InitBlockBuilder { &self, validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, - ) -> sc_block_builder::BlockBuilder; + ) -> sc_block_builder::BlockBuilder; /// Init a specific block builder at a specific block that works for the test runtime. /// @@ -48,7 +48,7 @@ pub trait InitBlockBuilder { at: Hash, validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, - ) -> sc_block_builder::BlockBuilder; + ) -> sc_block_builder::BlockBuilder; /// Init a specific block builder that works for the test runtime. /// @@ -61,7 +61,7 @@ pub trait InitBlockBuilder { validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, timestamp: u64, - ) -> sc_block_builder::BlockBuilder; + ) -> sc_block_builder::BlockBuilder; } fn init_block_builder( @@ -70,9 +70,13 @@ fn init_block_builder( validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, timestamp: u64, -) -> BlockBuilder<'_, Block, Client, Backend> { - let mut block_builder = client - .new_block_at(at, Default::default(), true) +) -> BlockBuilder<'_, Block, Client> { + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(at) + .fetch_parent_block_number(client) + .unwrap() + .enable_proof_recording() + .build() .expect("Creates new block builder for test runtime"); let mut inherent_data = sp_inherents::InherentData::new(); @@ -118,7 +122,7 @@ impl InitBlockBuilder for Client { &self, validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, - ) -> BlockBuilder { + ) -> BlockBuilder { let chain_info = self.chain_info(); self.init_block_builder_at(chain_info.best_hash, validation_data, relay_sproof_builder) } @@ -128,7 +132,7 @@ impl InitBlockBuilder for Client { at: Hash, validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, - ) -> BlockBuilder { + ) -> BlockBuilder { let last_timestamp = self.runtime_api().get_last_timestamp(at).expect("Get last timestamp"); let timestamp = last_timestamp + cumulus_test_runtime::MinimumPeriod::get(); @@ -142,7 +146,7 @@ impl InitBlockBuilder for Client { validation_data: Option>, relay_sproof_builder: RelayStateSproofBuilder, timestamp: u64, - ) -> sc_block_builder::BlockBuilder { + ) -> sc_block_builder::BlockBuilder { init_block_builder(self, at, validation_data, relay_sproof_builder, timestamp) } } @@ -155,7 +159,7 @@ pub trait BuildParachainBlockData { fn build_parachain_block(self, parent_state_root: Hash) -> ParachainBlockData; } -impl<'a> BuildParachainBlockData for sc_block_builder::BlockBuilder<'a, Block, Client, Backend> { +impl<'a> BuildParachainBlockData for sc_block_builder::BlockBuilder<'a, Block, Client> { fn build_parachain_block(self, parent_state_root: Hash) -> ParachainBlockData { let built_block = self.build().expect("Builds the block"); diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index 61249bdb066ad06a0dfbecfa09ac046aca2b0889..c46f4da7f6788cf53dfdd6d7be5c221d81562e70 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -19,13 +19,13 @@ mod block_builder; use codec::{Decode, Encode}; use runtime::{ - Balance, Block, BlockHashCount, Runtime, RuntimeCall, RuntimeGenesisConfig, Signature, - SignedExtra, SignedPayload, UncheckedExtrinsic, VERSION, + Balance, Block, BlockHashCount, Runtime, RuntimeCall, Signature, SignedExtra, SignedPayload, + UncheckedExtrinsic, VERSION, }; use sc_executor::HeapAllocStrategy; use sc_executor_common::runtime_blob::RuntimeBlob; use sp_blockchain::HeaderBackend; -use sp_core::{sr25519, Pair}; +use sp_core::Pair; use sp_io::TestExternalities; use sp_runtime::{generic::Era, BuildStorage, SaturatedConversion}; @@ -44,7 +44,8 @@ mod local_executor { pub struct LocalExecutor; impl sc_executor::NativeExecutionDispatch for LocalExecutor { - type ExtendHostFunctions = (); + type ExtendHostFunctions = + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { cumulus_test_runtime::api::dispatch(method, data) @@ -84,16 +85,12 @@ pub struct GenesisParameters { impl substrate_test_client::GenesisInit for GenesisParameters { fn genesis_storage(&self) -> Storage { - if self.endowed_accounts.is_empty() { - genesis_config().build_storage().unwrap() - } else { - cumulus_test_service::testnet_genesis( - cumulus_test_service::get_account_id_from_seed::("Alice"), - self.endowed_accounts.clone(), - ) - .build_storage() - .unwrap() - } + cumulus_test_service::chain_spec::get_chain_spec_with_extra_endowed( + None, + self.endowed_accounts.clone(), + ) + .build_storage() + .expect("Builds test runtime genesis storage") } } @@ -126,10 +123,6 @@ impl DefaultTestClientBuilderExt for TestClientBuilder { } } -fn genesis_config() -> RuntimeGenesisConfig { - cumulus_test_service::testnet_genesis_with_default_endowed(Default::default()) -} - /// Create an unsigned extrinsic from a runtime call. pub fn generate_unsigned(function: impl Into) -> UncheckedExtrinsic { UncheckedExtrinsic::new_unsigned(function.into()) @@ -158,6 +151,7 @@ pub fn generate_extrinsic_with_pair( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let function = function.into(); @@ -165,7 +159,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), extra.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); @@ -210,13 +204,16 @@ pub fn validate_block( let mut ext_ext = ext.ext(); let heap_pages = HeapAllocStrategy::Static { extra_pages: 1024 }; - let executor = WasmExecutor::::builder() - .with_execution_method(WasmExecutionMethod::default()) - .with_max_runtime_instances(1) - .with_runtime_cache_size(2) - .with_onchain_heap_alloc_strategy(heap_pages) - .with_offchain_heap_alloc_strategy(heap_pages) - .build(); + let executor = WasmExecutor::<( + sp_io::SubstrateHostFunctions, + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions, + )>::builder() + .with_execution_method(WasmExecutionMethod::default()) + .with_max_runtime_instances(1) + .with_runtime_cache_size(2) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build(); executor .uncached_call( diff --git a/cumulus/test/relay-sproof-builder/Cargo.toml b/cumulus/test/relay-sproof-builder/Cargo.toml index e044b92f7c4af4d297c8996c404a479e2d73ec8b..ff5c4bd66b9742383bcd170f0012695d7f0c72d2 100644 --- a/cumulus/test/relay-sproof-builder/Cargo.toml +++ b/cumulus/test/relay-sproof-builder/Cargo.toml @@ -1,26 +1,31 @@ [package] name = "cumulus-test-relay-sproof-builder" -version = "0.1.0" +version = "0.7.0" authors.workspace = true 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" ] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } # Substrate -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-state-machine = { path = "../../../substrate/primitives/state-machine", default-features = false} -sp-trie = { path = "../../../substrate/primitives/trie", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-state-machine = { path = "../../../substrate/primitives/state-machine", default-features = false } +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } # Polkadot -polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false} +polkadot-primitives = { path = "../../../polkadot/primitives", default-features = false } # Cumulus cumulus-primitives-core = { path = "../../primitives/core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-primitives-core/std", diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 445de32863910a65454d4a10160b4f3a6a86f881..449a8b819bc074e0c891d99d6fa2f42480f56ce6 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -5,51 +5,59 @@ 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"] } # Substrate -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-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false} -pallet-balances = { path = "../../../substrate/frame/balances", default-features = false} -pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false} -pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-features = false} -pallet-glutton = { path = "../../../substrate/frame/glutton", default-features = false} -pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false} -sp-api = { path = "../../../substrate/primitives/api", default-features = false} -sp-block-builder = { path = "../../../substrate/primitives/block-builder", default-features = false} -sp-core = { path = "../../../substrate/primitives/core", 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-transaction-pool = { path = "../../../substrate/primitives/transaction-pool", default-features = false} -sp-version = { path = "../../../substrate/primitives/version", default-features = false} +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-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false } +pallet-balances = { path = "../../../substrate/frame/balances", default-features = false } +pallet-message-queue = { path = "../../../substrate/frame/message-queue", default-features = false } +pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false } +pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-features = false } +pallet-glutton = { path = "../../../substrate/frame/glutton", default-features = false } +pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } +sp-api = { path = "../../../substrate/primitives/api", default-features = false } +sp-block-builder = { path = "../../../substrate/primitives/block-builder", 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-transaction-pool = { path = "../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../substrate/primitives/version", default-features = false } # Cumulus -cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook",] } +cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", "pallet-balances/std", "pallet-glutton/std", + "pallet-message-queue/std", "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment/std", @@ -57,6 +65,7 @@ std = [ "sp-api/std", "sp-block-builder/std", "sp-core/std", + "sp-genesis-builder/std", "sp-inherents/std", "sp-io/std", "sp-offchain/std", diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index ccf624c0ffa4ef85307fcd3c8a0ff24621b5bf5c..8e3569b02a1594c7043e7d6aebee50e7feaa6a5b 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -29,7 +29,7 @@ pub mod wasm_spec_version_incremented { mod test_pallet; -use frame_support::traits::OnRuntimeUpgrade; +use frame_support::{derive_impl, traits::OnRuntimeUpgrade}; use sp_api::{decl_runtime_apis, impl_runtime_apis}; use sp_core::{ConstU32, OpaqueMetadata}; use sp_runtime::{ @@ -47,6 +47,7 @@ use sp_version::RuntimeVersion; pub use frame_support::{ construct_runtime, dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ConstU8, Randomness}, weights::{ @@ -76,10 +77,6 @@ impl_opaque_keys! { pub struct SessionKeys {} } -/// Some key that we set in genesis and only read in [`TestOnRuntimeUpgrade`] to ensure that -/// [`OnRuntimeUpgrade`] works as expected. -pub const TEST_RUNTIME_UPGRADE_KEY: &[u8] = b"+test_runtime_upgrade_key+"; - /// The para-id used in this runtime. pub const PARACHAIN_ID: u32 = 100; @@ -180,36 +177,23 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { /// The identifier used to distinguish between accounts. type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. type Lookup = IdentityLookup; /// The index type for storing how many extrinsics an account has signed. type Nonce = Nonce; /// The type for hashing blocks and tries. type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; /// The block type. type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). type BlockHashCount = BlockHashCount; /// Runtime version. type Version = Version; - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type SS58Prefix = SS58Prefix; @@ -250,8 +234,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -277,11 +261,13 @@ impl pallet_glutton::Config for Runtime { } impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); type SelfParaId = ParachainId; type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); type OutboundXcmpMessageSource = (); - type DmpMessageHandler = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; type ReservedDmpWeight = (); type XcmpMessageHandler = (); type ReservedXcmpWeight = (); @@ -290,6 +276,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { } parameter_types! { + // will be set by test_pallet during genesis init pub storage ParachainId: cumulus_primitives_core::ParaId = PARACHAIN_ID.into(); } @@ -344,6 +331,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -364,7 +352,10 @@ pub struct TestOnRuntimeUpgrade; impl OnRuntimeUpgrade for TestOnRuntimeUpgrade { fn on_runtime_upgrade() -> frame_support::weights::Weight { - assert_eq!(sp_io::storage::get(TEST_RUNTIME_UPGRADE_KEY), Some(vec![1, 2, 3, 4].into())); + assert_eq!( + sp_io::storage::get(test_pallet::TEST_RUNTIME_UPGRADE_KEY), + Some(vec![1, 2, 3, 4].into()) + ); Weight::from_parts(1, 0) } } @@ -386,7 +377,7 @@ impl_runtime_apis! { Executive::execute_block(block) } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -470,6 +461,16 @@ impl_runtime_apis! { ParachainSystem::collect_collation_info(header) } } + + 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! { diff --git a/cumulus/test/runtime/src/test_pallet.rs b/cumulus/test/runtime/src/test_pallet.rs index 0af23797dad6dd13561584f9d1d608f23f1a6bc2..5d11b7f490c68351ea0612e809872a0f217681d3 100644 --- a/cumulus/test/runtime/src/test_pallet.rs +++ b/cumulus/test/runtime/src/test_pallet.rs @@ -17,8 +17,13 @@ /// A special pallet that exposes dispatchables that are only useful for testing. pub use pallet::*; +/// Some key that we set in genesis and only read in [`TestOnRuntimeUpgrade`] to ensure that +/// [`OnRuntimeUpgrade`] works as expected. +pub const TEST_RUNTIME_UPGRADE_KEY: &[u8] = b"+test_runtime_upgrade_key+"; + #[frame_support::pallet(dev_mode)] pub mod pallet { + use crate::test_pallet::TEST_RUNTIME_UPGRADE_KEY; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -44,5 +49,47 @@ pub mod pallet { ); Ok(()) } + + /// A dispatchable that first reads two values from two different child tries, asserts they + /// are the expected values (if the values exist in the state) and then writes two different + /// values to these child tries. + #[pallet::weight(0)] + pub fn read_and_write_child_tries(_: OriginFor) -> DispatchResult { + let key = &b"hello"[..]; + let first_trie = &b"first"[..]; + let second_trie = &b"second"[..]; + let first_value = "world1".encode(); + let second_value = "world2".encode(); + + if let Some(res) = sp_io::default_child_storage::get(first_trie, key) { + assert_eq!(first_value, res); + } + if let Some(res) = sp_io::default_child_storage::get(second_trie, key) { + assert_eq!(second_value, res); + } + + sp_io::default_child_storage::set(first_trie, key, &first_value); + sp_io::default_child_storage::set(second_trie, key, &second_value); + + Ok(()) + } + } + + #[derive(frame_support::DefaultNoBound)] + #[pallet::genesis_config] + pub struct GenesisConfig { + pub self_para_id: Option, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + sp_io::storage::set(TEST_RUNTIME_UPGRADE_KEY, &[1, 2, 3, 4]); + self.self_para_id.map(|para_id| { + crate::ParachainId::set(¶_id); + }); + } } } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index c996a01a12ed13003939f38b106a0a2f56c57716..45e21432f5b82e58d208088842c813986c94dc88 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -5,18 +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.6", features = ["derive"] } +async-trait = "0.1.74" +clap = { version = "4.5.3", 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"] } +criterion = { version = "0.5.1", features = ["async_tokio"] } +jsonrpsee = { version = "0.22", features = ["server"] } rand = "0.8.5" -serde = { version = "1.0.188", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } tokio = { version = "1.32.0", features = ["macros"] } tracing = "0.1.37" url = "2.4.0" @@ -43,7 +47,7 @@ sp-core = { path = "../../../substrate/primitives/core" } sp-io = { path = "../../../substrate/primitives/io" } sp-api = { path = "../../../substrate/primitives/api" } sp-keyring = { path = "../../../substrate/primitives/keyring" } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } sp-state-machine = { path = "../../../substrate/primitives/state-machine" } sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-timestamp = { path = "../../../substrate/primitives/timestamp" } @@ -67,16 +71,17 @@ 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" } cumulus-relay-chain-minimal-node = { path = "../../client/relay-chain-minimal-node" } cumulus-client-pov-recovery = { path = "../../client/pov-recovery" } cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } -cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook",] } +cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } pallet-timestamp = { path = "../../../substrate/frame/timestamp" } [dev-dependencies] @@ -98,6 +103,8 @@ substrate-test-utils = { path = "../../../substrate/test-utils" } [features] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-test-client/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", diff --git a/cumulus/test/service/benches/block_import.rs b/cumulus/test/service/benches/block_import.rs index b79598b1530246670de717b2b9479d26bc691361..9d6485d74c5948079102556ee6b56d40b75d5cc1 100644 --- a/cumulus/test/service/benches/block_import.rs +++ b/cumulus/test/service/benches/block_import.rs @@ -17,14 +17,14 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; +use sc_block_builder::BlockBuilderBuilder; use sc_client_api::UsageProvider; use core::time::Duration; use cumulus_primitives_core::ParaId; -use sc_block_builder::{BlockBuilderProvider, RecordProof}; use sp_api::{Core, ProvideRuntimeApi}; -use sp_keyring::Sr25519Keyring::Alice; +use sp_keyring::Sr25519Keyring::{Alice, Bob}; use cumulus_test_service::bench_utils as utils; @@ -32,47 +32,69 @@ fn benchmark_block_import(c: &mut Criterion) { sp_tracing::try_init_simple(); let runtime = tokio::runtime::Runtime::new().expect("creating tokio runtime doesn't fail; qed"); - let para_id = ParaId::from(100); + + let para_id = ParaId::from(cumulus_test_runtime::PARACHAIN_ID); let tokio_handle = runtime.handle(); // Create enough accounts to fill the block with transactions. // Each account should only be included in one transfer. let (src_accounts, dst_accounts, account_ids) = utils::create_benchmark_accounts(); - let alice = runtime.block_on( - cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Alice) + for bench_parameters in &[(true, Alice), (false, Bob)] { + let node = runtime.block_on( + cumulus_test_service::TestNodeBuilder::new( + para_id, + tokio_handle.clone(), + bench_parameters.1, + ) // Preload all accounts with funds for the transfers - .endowed_accounts(account_ids) + .endowed_accounts(account_ids.clone()) + .import_proof_recording(bench_parameters.0) .build(), - ); - - let client = alice.client; - - let (max_transfer_count, extrinsics) = - utils::create_benchmarking_transfer_extrinsics(&client, &src_accounts, &dst_accounts); - - let parent_hash = client.usage_info().chain.best_hash; - let mut block_builder = - client.new_block_at(parent_hash, Default::default(), RecordProof::No).unwrap(); - for extrinsic in extrinsics { - block_builder.push(extrinsic).unwrap(); - } - let benchmark_block = block_builder.build().unwrap(); - - let mut group = c.benchmark_group("Block import"); - group.sample_size(20); - group.measurement_time(Duration::from_secs(120)); - group.throughput(Throughput::Elements(max_transfer_count as u64)); - - group.bench_function(format!("(transfers = {}) block import", max_transfer_count), |b| { - b.iter_batched( - || benchmark_block.block.clone(), - |block| { - client.runtime_api().execute_block(parent_hash, block).unwrap(); + ); + + let client = node.client; + let backend = node.backend; + + let (max_transfer_count, extrinsics) = + utils::create_benchmarking_transfer_extrinsics(&client, &src_accounts, &dst_accounts); + + let parent_hash = client.usage_info().chain.best_hash; + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(parent_hash) + .fetch_parent_block_number(&*client) + .unwrap() + .build() + .unwrap(); + for extrinsic in extrinsics { + block_builder.push(extrinsic).unwrap(); + } + let benchmark_block = block_builder.build().unwrap(); + + let mut group = c.benchmark_group("Block import"); + group.sample_size(20); + group.measurement_time(Duration::from_secs(120)); + group.throughput(Throughput::Elements(max_transfer_count as u64)); + + group.bench_function( + format!( + "(transfers = {max_transfer_count}, proof_recording = {}) block import", + bench_parameters.0 + ), + |b| { + b.iter_batched( + || { + backend.reset_trie_cache(); + benchmark_block.block.clone() + }, + |block| { + client.runtime_api().execute_block(parent_hash, block).unwrap(); + }, + BatchSize::SmallInput, + ) }, - BatchSize::SmallInput, - ) - }); + ); + } } criterion_group!(benches, benchmark_block_import); diff --git a/cumulus/test/service/benches/block_import_glutton.rs b/cumulus/test/service/benches/block_import_glutton.rs index b49db9f449e971946c9b13086c7d9e89b544e64a..6295fd68286bd8d4f4b48f305cf95cc544cce019 100644 --- a/cumulus/test/service/benches/block_import_glutton.rs +++ b/cumulus/test/service/benches/block_import_glutton.rs @@ -17,7 +17,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use sc_client_api::UsageProvider; use sp_api::{Core, ProvideRuntimeApi}; use sp_arithmetic::{ traits::{One, Zero}, @@ -27,8 +26,8 @@ use sp_arithmetic::{ use core::time::Duration; use cumulus_primitives_core::ParaId; -use sc_block_builder::{BlockBuilderProvider, RecordProof}; -use sp_keyring::Sr25519Keyring::Alice; +use sc_block_builder::BlockBuilderBuilder; +use sp_keyring::Sr25519Keyring::{Alice, Bob, Charlie, Ferdie}; use cumulus_test_service::bench_utils as utils; @@ -39,17 +38,29 @@ fn benchmark_block_import(c: &mut Criterion) { let para_id = ParaId::from(100); let tokio_handle = runtime.handle(); - let alice = runtime.block_on( - cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Alice).build(), - ); - let client = alice.client; + let mut initialize_glutton_pallet = true; + for (compute_ratio, storage_ratio, proof_on_import, keyring_identity) in &[ + (One::one(), Zero::zero(), true, Alice), + (One::one(), One::one(), true, Bob), + (One::one(), Zero::zero(), false, Charlie), + (One::one(), One::one(), false, Ferdie), + ] { + let node = runtime.block_on( + cumulus_test_service::TestNodeBuilder::new( + para_id, + tokio_handle.clone(), + *keyring_identity, + ) + .import_proof_recording(*proof_on_import) + .build(), + ); + let client = node.client; + let backend = node.backend; - let mut group = c.benchmark_group("Block import"); - group.sample_size(20); - group.measurement_time(Duration::from_secs(120)); + let mut group = c.benchmark_group("Block import"); + group.sample_size(20); + group.measurement_time(Duration::from_secs(120)); - let mut initialize_glutton_pallet = true; - for (compute_ratio, storage_ratio) in &[(One::one(), Zero::zero()), (One::one(), One::one())] { let block = utils::set_glutton_parameters( &client, initialize_glutton_pallet, @@ -61,10 +72,14 @@ fn benchmark_block_import(c: &mut Criterion) { runtime.block_on(utils::import_block(&client, &block, false)); // Build the block we will use for benchmarking - let parent_hash = client.usage_info().chain.best_hash; + let parent_hash = client.chain_info().best_hash; let parent_header = client.header(parent_hash).expect("Just fetched this hash.").unwrap(); - let mut block_builder = - client.new_block_at(parent_hash, Default::default(), RecordProof::No).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(parent_hash) + .fetch_parent_block_number(&*client) + .unwrap() + .build() + .unwrap(); block_builder .push(utils::extrinsic_set_validation_data(parent_header.clone()).clone()) .unwrap(); @@ -79,7 +94,10 @@ fn benchmark_block_import(c: &mut Criterion) { ), |b| { b.iter_batched( - || benchmark_block.block.clone(), + || { + backend.reset_trie_cache(); + benchmark_block.block.clone() + }, |block| { client.runtime_api().execute_block(parent_hash, block).unwrap(); }, diff --git a/cumulus/test/service/benches/block_production.rs b/cumulus/test/service/benches/block_production.rs index 1b868d73630280297b87701d871c02ad08acab7b..3b0db578041f04a582b7acae81b553d52a24df9e 100644 --- a/cumulus/test/service/benches/block_production.rs +++ b/cumulus/test/service/benches/block_production.rs @@ -21,7 +21,7 @@ use sc_client_api::UsageProvider; use core::time::Duration; use cumulus_primitives_core::ParaId; -use sc_block_builder::{BlockBuilderProvider, RecordProof}; +use sc_block_builder::BlockBuilderBuilder; use sp_keyring::Sr25519Keyring::Alice; @@ -50,7 +50,11 @@ fn benchmark_block_production(c: &mut Criterion) { let parent_header = client.header(parent_hash).expect("Just fetched this hash.").unwrap(); let set_validation_data_extrinsic = utils::extrinsic_set_validation_data(parent_header); - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap(); block_builder.push(utils::extrinsic_set_time(&client)).unwrap(); block_builder.push(set_validation_data_extrinsic).unwrap(); let built_block = block_builder.build().unwrap(); @@ -66,7 +70,7 @@ fn benchmark_block_production(c: &mut Criterion) { group.measurement_time(Duration::from_secs(120)); group.throughput(Throughput::Elements(max_transfer_count as u64)); - let best_hash = client.chain_info().best_hash; + let chain = client.chain_info(); group.bench_function( format!("(proof = true, transfers = {}) block production", max_transfer_count), @@ -74,9 +78,13 @@ fn benchmark_block_production(c: &mut Criterion) { b.iter_batched( || extrinsics.clone(), |extrinsics| { - let mut block_builder = client - .new_block_at(best_hash, Default::default(), RecordProof::Yes) + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(chain.best_hash) + .with_parent_block_number(chain.best_number) + .enable_proof_recording() + .build() .unwrap(); + for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } @@ -93,9 +101,12 @@ fn benchmark_block_production(c: &mut Criterion) { b.iter_batched( || extrinsics.clone(), |extrinsics| { - let mut block_builder = client - .new_block_at(best_hash, Default::default(), RecordProof::No) + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(chain.best_hash) + .with_parent_block_number(chain.best_number) + .build() .unwrap(); + for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } diff --git a/cumulus/test/service/benches/block_production_glutton.rs b/cumulus/test/service/benches/block_production_glutton.rs index 92a368c88c8d81189c48ce8a1360b54110555548..6ab2c0e56bd188f9b7fa9456db8efc2c5a88adab 100644 --- a/cumulus/test/service/benches/block_production_glutton.rs +++ b/cumulus/test/service/benches/block_production_glutton.rs @@ -17,7 +17,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; -use sc_client_api::UsageProvider; use sp_arithmetic::{ traits::{One, Zero}, FixedPointNumber, @@ -26,7 +25,7 @@ use sp_arithmetic::{ use core::time::Duration; use cumulus_primitives_core::ParaId; -use sc_block_builder::{BlockBuilderProvider, RecordProof}; +use sc_block_builder::BlockBuilderBuilder; use sp_keyring::Sr25519Keyring::Alice; @@ -60,11 +59,11 @@ fn benchmark_block_production_compute(c: &mut Criterion) { runtime.block_on(utils::import_block(&client, &block, false)); initialize_glutton_pallet = false; - let parent_hash = client.usage_info().chain.best_hash; - let parent_header = client.header(parent_hash).expect("Just fetched this hash.").unwrap(); + let best_hash = client.chain_info().best_hash; + let best_number = client.chain_info().best_number; + let parent_header = client.header(best_hash).expect("Just fetched this hash.").unwrap(); let set_validation_data_extrinsic = utils::extrinsic_set_validation_data(parent_header); let set_time_extrinsic = utils::extrinsic_set_time(&client); - let best_hash = client.chain_info().best_hash; group.bench_function( format!( @@ -76,8 +75,11 @@ fn benchmark_block_production_compute(c: &mut Criterion) { b.iter_batched( || (set_validation_data_extrinsic.clone(), set_time_extrinsic.clone()), |(validation_data, time)| { - let mut block_builder = client - .new_block_at(best_hash, Default::default(), RecordProof::Yes) + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(best_hash) + .with_parent_block_number(best_number) + .enable_proof_recording() + .build() .unwrap(); block_builder.push(validation_data).unwrap(); block_builder.push(time).unwrap(); @@ -98,8 +100,10 @@ fn benchmark_block_production_compute(c: &mut Criterion) { b.iter_batched( || (set_validation_data_extrinsic.clone(), set_time_extrinsic.clone()), |(validation_data, time)| { - let mut block_builder = client - .new_block_at(best_hash, Default::default(), RecordProof::No) + let mut block_builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(best_hash) + .with_parent_block_number(best_number) + .build() .unwrap(); block_builder.push(validation_data).unwrap(); block_builder.push(time).unwrap(); diff --git a/cumulus/test/service/benches/transaction_throughput.rs b/cumulus/test/service/benches/transaction_throughput.rs index 81ecc84db7bf211d5d684f7c24bfd9a4abf10c6e..011eb4c7d50e3eb54506108ad4ed97cec5a04041 100644 --- a/cumulus/test/service/benches/transaction_throughput.rs +++ b/cumulus/test/service/benches/transaction_throughput.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; +use cumulus_client_cli::get_raw_genesis_header; use cumulus_test_runtime::{AccountId, BalancesCall, ExistentialDeposit, SudoCall}; use futures::{future, StreamExt}; use sc_transaction_pool_api::{TransactionPool as _, TransactionSource, TransactionStatus}; @@ -24,9 +25,8 @@ use sp_core::{crypto::Pair, sr25519}; use sp_runtime::OpaqueExtrinsic; use cumulus_primitives_core::ParaId; -use cumulus_test_service::{ - construct_extrinsic, fetch_nonce, initial_head_data, Client, Keyring::*, TransactionPool, -}; +use cumulus_test_service::{construct_extrinsic, fetch_nonce, Client, Keyring::*, TransactionPool}; +use polkadot_primitives::HeadData; fn create_accounts(num: usize) -> Vec { (0..num) @@ -159,6 +159,13 @@ fn transaction_throughput_benchmarks(c: &mut Criterion) { None, ); + // Run charlie as parachain collator + let charlie = runtime.block_on( + cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Charlie) + .enable_collator() + .connect_to_relay_chain_nodes(vec![&alice, &bob]) + .build(), + ); // Register parachain runtime .block_on( @@ -167,19 +174,14 @@ fn transaction_throughput_benchmarks(c: &mut Criterion) { cumulus_test_service::runtime::WASM_BINARY .expect("You need to build the WASM binary to run this test!") .to_vec(), - initial_head_data(para_id), + HeadData( + get_raw_genesis_header(charlie.client.clone()) + .expect("Unable to get genesis HeadData."), + ), ), ) .unwrap(); - // Run charlie as parachain collator - let charlie = runtime.block_on( - cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Charlie) - .enable_collator() - .connect_to_relay_chain_nodes(vec![&alice, &bob]) - .build(), - ); - // Run dave as parachain collator let dave = runtime.block_on( cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Dave) diff --git a/cumulus/test/service/benches/validate_block.rs b/cumulus/test/service/benches/validate_block.rs index f3b4d0b12144286ff24fc94ee9ba8169c33fddd0..a614863803e09e89ff9671dd6916ccbfeb657278 100644 --- a/cumulus/test/service/benches/validate_block.rs +++ b/cumulus/test/service/benches/validate_block.rs @@ -18,7 +18,9 @@ use codec::{Decode, Encode}; use core::time::Duration; use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; -use cumulus_primitives_core::{relay_chain::AccountId, PersistedValidationData, ValidationParams}; +use cumulus_primitives_core::{ + relay_chain::AccountId, ParaId, PersistedValidationData, ValidationParams, +}; use cumulus_test_client::{ generate_extrinsic_with_pair, BuildParachainBlockData, InitBlockBuilder, TestClientBuilder, ValidationResult, @@ -27,7 +29,7 @@ use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use cumulus_test_runtime::{BalancesCall, Block, Header, UncheckedExtrinsic}; use cumulus_test_service::bench_utils as utils; use polkadot_primitives::HeadData; -use sc_block_builder::BlockBuilderProvider; +use sc_block_builder::BlockBuilderBuilder; use sc_client_api::UsageProvider; use sc_executor_common::wasm_runtime::WasmModule; @@ -46,7 +48,11 @@ fn create_extrinsics( dst_accounts: &[sr25519::Pair], ) -> (usize, Vec) { // Add as many tranfer extrinsics as possible into a single block. - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap(); let mut max_transfer_count = 0; let mut extrinsics = Vec::new(); @@ -79,6 +85,7 @@ fn benchmark_block_validation(c: &mut Criterion) { // Each account should only be included in one transfer. let (src_accounts, dst_accounts, account_ids) = utils::create_benchmark_accounts(); + let para_id = ParaId::from(cumulus_test_runtime::PARACHAIN_ID); let mut test_client_builder = TestClientBuilder::with_default_backend(); let genesis_init = test_client_builder.genesis_init_mut(); *genesis_init = cumulus_test_client::GenesisParameters { endowed_accounts: account_ids }; @@ -94,7 +101,14 @@ fn benchmark_block_validation(c: &mut Criterion) { ..Default::default() }; - let mut block_builder = client.init_block_builder(Some(validation_data), Default::default()); + let sproof_builder = RelayStateSproofBuilder { + included_para_head: Some(parent_header.clone().encode().into()), + para_id, + ..Default::default() + }; + + let mut block_builder = + client.init_block_builder(Some(validation_data), sproof_builder.clone()); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } @@ -104,7 +118,6 @@ fn benchmark_block_validation(c: &mut Criterion) { let proof_size_in_kb = parachain_block.storage_proof().encode().len() as f64 / 1024f64; let runtime = utils::get_wasm_module(); - let sproof_builder: RelayStateSproofBuilder = Default::default(); let (relay_parent_storage_root, _) = sproof_builder.into_state_root_and_proof(); let encoded_params = ValidationParams { block_data: cumulus_test_client::BlockData(parachain_block.encode()), diff --git a/cumulus/test/service/src/bench_utils.rs b/cumulus/test/service/src/bench_utils.rs index 172c9e504196dd5567e225df7dd32a5055f08c08..4ace894b392aa2393741572249ed35f0a7101845 100644 --- a/cumulus/test/service/src/bench_utils.rs +++ b/cumulus/test/service/src/bench_utils.rs @@ -16,17 +16,17 @@ // limitations under the License. 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, }; use frame_system_rpc_runtime_api::AccountNonceApi; use polkadot_primitives::HeadData; -use sc_block_builder::BlockBuilderProvider; use sc_client_api::UsageProvider; use sc_consensus::{ block_import::{BlockImportParams, ForkChoiceStrategy}, @@ -81,8 +81,13 @@ pub fn extrinsic_set_time(client: &TestClient) -> OpaqueExtrinsic { pub fn extrinsic_set_validation_data( parent_header: cumulus_test_runtime::Header, ) -> OpaqueExtrinsic { - let sproof_builder = RelayStateSproofBuilder { para_id: 100.into(), ..Default::default() }; let parent_head = HeadData(parent_header.encode()); + let sproof_builder = RelayStateSproofBuilder { + para_id: cumulus_test_runtime::PARACHAIN_ID.into(), + included_para_head: parent_head.clone().into(), + ..Default::default() + }; + let (relay_parent_storage_root, relay_chain_state) = sproof_builder.into_state_root_and_proof(); let data = ParachainInherentData { validation_data: PersistedValidationData { @@ -126,8 +131,13 @@ pub fn create_benchmarking_transfer_extrinsics( src_accounts: &[sr25519::Pair], dst_accounts: &[sr25519::Pair], ) -> (usize, Vec) { + let chain = client.usage_info().chain; // Add as many transfer extrinsics as possible into a single block. - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(chain.best_hash) + .with_parent_block_number(chain.best_number) + .build() + .expect("Creates block builder"); let mut max_transfer_count = 0; let mut extrinsics = Vec::new(); // Every block needs one timestamp extrinsic. @@ -248,8 +258,13 @@ pub fn set_glutton_parameters( Some(last_nonce), ); extrinsics.push(set_storage); + let chain = client.usage_info().chain; - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(chain.best_hash) + .with_parent_block_number(chain.best_number) + .build() + .unwrap(); block_builder.push(extrinsic_set_time(client)).unwrap(); block_builder.push(extrinsic_set_validation_data(parent_header)).unwrap(); for extrinsic in extrinsics { diff --git a/cumulus/test/service/src/chain_spec.rs b/cumulus/test/service/src/chain_spec.rs index 7c8c984c2b207bb1ae86bad5042ee469bc622d4f..61bbf755d890aab92c0c714663a7e24c1c286696 100644 --- a/cumulus/test/service/src/chain_spec.rs +++ b/cumulus/test/service/src/chain_spec.rs @@ -17,7 +17,7 @@ #![allow(missing_docs)] use cumulus_primitives_core::ParaId; -use cumulus_test_runtime::{AccountId, Signature}; +use cumulus_test_runtime::{AccountId, RuntimeGenesisConfig, Signature}; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; @@ -25,27 +25,7 @@ use sp_core::{sr25519, Pair, Public}; use sp_runtime::traits::{IdentifyAccount, Verify}; /// Specialized `ChainSpec` for the normal parachain runtime. -pub type ChainSpec = sc_service::GenericChainSpec; - -/// Extension for the genesis config to add custom keys easily. -#[derive(serde::Serialize, serde::Deserialize)] -pub struct GenesisExt { - /// The runtime genesis config. - runtime_genesis_config: cumulus_test_runtime::RuntimeGenesisConfig, - /// The parachain id. - para_id: ParaId, -} - -impl sp_runtime::BuildStorage for GenesisExt { - fn assimilate_storage(&self, storage: &mut sp_core::storage::Storage) -> Result<(), String> { - sp_state_machine::BasicExternalities::execute_with_storage(storage, || { - sp_io::storage::set(cumulus_test_runtime::TEST_RUNTIME_UPGRADE_KEY, &[1, 2, 3, 4]); - cumulus_test_runtime::ParachainId::set(&self.para_id); - }); - - self.runtime_genesis_config.assimilate_storage(storage) - } -} +pub type ChainSpec = sc_service::GenericChainSpec; /// Helper function to generate a crypto pair from seed pub fn get_from_seed(seed: &str) -> ::Public { @@ -83,37 +63,33 @@ where /// The given accounts are initialized with funds in addition /// to the default known accounts. pub fn get_chain_spec_with_extra_endowed( - id: ParaId, + id: Option, extra_endowed_accounts: Vec, ) -> ChainSpec { - ChainSpec::from_genesis( - "Local Testnet", - "local_testnet", - ChainType::Local, - move || GenesisExt { - runtime_genesis_config: testnet_genesis_with_default_endowed( - extra_endowed_accounts.clone(), - ), - para_id: id, - }, - Vec::new(), - None, - None, - None, - None, - Extensions { para_id: id.into() }, + ChainSpec::builder( + cumulus_test_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + Extensions { para_id: id.unwrap_or(cumulus_test_runtime::PARACHAIN_ID.into()).into() }, ) + .with_name("Local Testnet") + .with_id("local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(testnet_genesis_with_default_endowed( + extra_endowed_accounts.clone(), + id, + )) + .build() } /// Get the chain spec for a specific parachain ID. -pub fn get_chain_spec(id: ParaId) -> ChainSpec { +pub fn get_chain_spec(id: Option) -> ChainSpec { get_chain_spec_with_extra_endowed(id, Default::default()) } /// Local testnet genesis for testing. pub fn testnet_genesis_with_default_endowed( mut extra_endowed_accounts: Vec, -) -> cumulus_test_runtime::RuntimeGenesisConfig { + self_para_id: Option, +) -> serde_json::Value { let mut endowed = vec![ get_account_id_from_seed::("Alice"), get_account_id_from_seed::("Bob"), @@ -130,27 +106,20 @@ pub fn testnet_genesis_with_default_endowed( ]; endowed.append(&mut extra_endowed_accounts); - testnet_genesis(get_account_id_from_seed::("Alice"), endowed) + testnet_genesis(get_account_id_from_seed::("Alice"), endowed, self_para_id) } /// Creates a local testnet genesis with endowed accounts. pub fn testnet_genesis( root_key: AccountId, endowed_accounts: Vec, -) -> cumulus_test_runtime::RuntimeGenesisConfig { - cumulus_test_runtime::RuntimeGenesisConfig { - system: cumulus_test_runtime::SystemConfig { - code: cumulus_test_runtime::WASM_BINARY - .expect("WASM binary was not build, please build it!") - .to_vec(), - ..Default::default() - }, - glutton: Default::default(), - parachain_system: Default::default(), - balances: cumulus_test_runtime::BalancesConfig { + self_para_id: Option, +) -> serde_json::Value { + serde_json::json!({ + "balances": cumulus_test_runtime::BalancesConfig { balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), }, - sudo: cumulus_test_runtime::SudoConfig { key: Some(root_key) }, - transaction_payment: Default::default(), - } + "sudo": cumulus_test_runtime::SudoConfig { key: Some(root_key) }, + "testPallet": cumulus_test_runtime::TestPalletConfig { self_para_id, ..Default::default() } + }) } diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index 9235a0c45c58638f3966c28b031c39382d157396..87d1d4af8a95e0edf12efc454d5505a6c1ad7544 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, @@ -37,9 +38,6 @@ pub struct TestCollatorCli { #[command(flatten)] pub run: cumulus_client_cli::RunCmd, - #[arg(default_value_t = 2000u32)] - pub parachain_id: u32, - /// Relay chain arguments #[arg(raw = true)] pub relaychain_args: Vec, @@ -60,45 +58,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. @@ -287,8 +253,8 @@ impl SubstrateCli for TestCollatorCli { fn load_spec(&self, id: &str) -> std::result::Result, String> { Ok(match id { - "" => Box::new(cumulus_test_service::get_chain_spec(ParaId::from(self.parachain_id))) - as Box<_>, + "" => + Box::new(cumulus_test_service::get_chain_spec(Some(ParaId::from(2000)))) as Box<_>, path => { let chain_spec = cumulus_test_service::chain_spec::ChainSpec::from_json_file(path.into())?; diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index a721645546af7417200a7768242573abbe252397..3554a383f219e7465f5df875bdf6463b93119f3b 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -22,11 +22,11 @@ pub mod bench_utils; pub mod chain_spec; -mod genesis; use runtime::AccountId; use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; use std::{ + collections::HashSet, future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, time::Duration, @@ -57,7 +57,7 @@ use cumulus_test_runtime::{Hash, Header, NodeBlock as Block, RuntimeApi}; use frame_system_rpc_runtime_api::AccountNonceApi; use polkadot_node_subsystem::{errors::RecoveryError, messages::AvailabilityRecoveryMessage}; use polkadot_overseer::Handle as OverseerHandle; -use polkadot_primitives::{CollatorPair, Hash as PHash, PersistedValidationData}; +use polkadot_primitives::{CandidateHash, CollatorPair, Hash as PHash, PersistedValidationData}; use polkadot_service::ProvideRuntimeApi; use sc_consensus::ImportQueue; use sc_network::{ @@ -67,7 +67,7 @@ use sc_network::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration, - OffchainWorkerConfig, PruningMode, WasmExecutionMethod, + OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, }, BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, @@ -85,7 +85,6 @@ use substrate_test_client::{ pub use chain_spec::*; pub use cumulus_test_runtime as runtime; -pub use genesis::*; pub use sp_keyring::Sr25519Keyring as Keyring; const LOG_TARGET: &str = "cumulus-test-service"; @@ -113,7 +112,7 @@ pub type AnnounceBlockFn = Arc>) + Send + Sync>; pub struct RuntimeExecutor; impl sc_executor::NativeExecutionDispatch for RuntimeExecutor { - type ExtendHostFunctions = (); + type ExtendHostFunctions = cumulus_client_service::storage_proof_size::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { cumulus_test_runtime::api::dispatch(method, data) @@ -144,12 +143,13 @@ pub type TransactionPool = Arc>; pub struct FailingRecoveryHandle { overseer_handle: OverseerHandle, counter: u32, + failed_hashes: HashSet, } impl FailingRecoveryHandle { /// Create a new FailingRecoveryHandle pub fn new(overseer_handle: OverseerHandle) -> Self { - Self { overseer_handle, counter: 0 } + Self { overseer_handle, counter: 0, failed_hashes: Default::default() } } } @@ -160,11 +160,15 @@ impl RecoveryHandle for FailingRecoveryHandle { message: AvailabilityRecoveryMessage, origin: &'static str, ) { - // For every 5th block we immediately signal unavailability to trigger - // a retry. - if self.counter % 5 == 0 { + let AvailabilityRecoveryMessage::RecoverAvailableData(ref receipt, _, _, _) = message; + let candidate_hash = receipt.hash(); + + // For every 3rd block we immediately signal unavailability to trigger + // a retry. The same candidate is never failed multiple times to ensure progress. + if self.counter % 3 == 0 && self.failed_hashes.insert(candidate_hash) { + tracing::info!(target: LOG_TARGET, ?candidate_hash, "Failing pov recovery."); + let AvailabilityRecoveryMessage::RecoverAvailableData(_, _, _, back_sender) = message; - tracing::info!(target: LOG_TARGET, "Failing pov recovery."); back_sender .send(Err(RecoveryError::Unavailable)) .expect("Return channel should work here."); @@ -175,23 +179,24 @@ 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 /// be able to perform chain operations. pub fn new_partial( config: &mut Configuration, -) -> Result< - PartialComponents< - Client, - Backend, - (), - sc_consensus::import_queue::BasicQueue, - sc_transaction_pool::FullPool, - ParachainBlockImport, - >, - sc_service::Error, -> { + enable_import_proof_record: bool, +) -> Result { let heap_pages = config .default_heap_pages .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ }); @@ -208,10 +213,16 @@ pub fn new_partial( sc_executor::NativeElseWasmExecutor::::new_with_wasm_executor(wasm); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::(config, None, executor)?; + sc_service::new_full_parts_record_import::( + config, + None, + executor, + enable_import_proof_record, + )?; let client = Arc::new(client); - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let block_import = + ParachainBlockImport::new_with_delayed_best_block(client.clone(), backend.clone()); let registry = config.prometheus_registry(); @@ -260,6 +271,7 @@ async fn build_relay_chain_interface( polkadot_service::IsParachainNode::Collator(CollatorPair::generate().0) }, None, + polkadot_service::CollatorOverseerGen, ) .map_err(|e| RelayChainError::Application(Box::new(e) as Box<_>))?, cumulus_client_cli::RelayChainMode::ExternalRpc(rpc_target_urls) => @@ -302,19 +314,21 @@ pub async fn start_node_impl( rpc_ext_builder: RB, consensus: Consensus, collator_options: CollatorOptions, + proof_recording_during_import: bool, ) -> sc_service::error::Result<( TaskManager, Arc, Arc>, RpcHandlers, TransactionPool, + Arc, )> where RB: Fn(Arc) -> Result, sc_service::Error> + Send + 'static, { let mut parachain_config = prepare_node_config(parachain_config); - let params = new_partial(&mut parachain_config)?; + let params = new_partial(&mut parachain_config, proof_recording_during_import)?; let transaction_pool = params.transaction_pool.clone(); let mut task_manager = params.task_manager; @@ -430,7 +444,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, @@ -470,7 +484,7 @@ where start_network.start_network(); - Ok((task_manager, client, network, rpc_handlers, transaction_pool)) + Ok((task_manager, client, network, rpc_handlers, transaction_pool, backend)) } /// A Cumulus test node instance used for testing. @@ -488,6 +502,8 @@ pub struct TestNode { pub rpc_handlers: RpcHandlers, /// Node's transaction pool pub transaction_pool: TransactionPool, + /// Node's backend + pub backend: Arc, } #[allow(missing_docs)] @@ -513,6 +529,7 @@ pub struct TestNodeBuilder { consensus: Consensus, relay_chain_mode: RelayChainMode, endowed_accounts: Vec, + record_proof_during_import: bool, } impl TestNodeBuilder { @@ -537,6 +554,7 @@ impl TestNodeBuilder { consensus: Consensus::RelayChain, endowed_accounts: Default::default(), relay_chain_mode: RelayChainMode::Embedded, + record_proof_during_import: true, } } @@ -649,6 +667,12 @@ impl TestNodeBuilder { self } + /// Record proofs during import. + pub fn import_proof_recording(mut self, should_record_proof: bool) -> TestNodeBuilder { + self.record_proof_during_import = should_record_proof; + self + } + /// Build the [`TestNode`]. pub async fn build(self) -> TestNode { let parachain_config = node_config( @@ -677,24 +701,26 @@ impl TestNodeBuilder { format!("{} (relay chain)", relay_chain_config.network.node_name); let multiaddr = parachain_config.network.listen_addresses[0].clone(); - let (task_manager, client, network, rpc_handlers, transaction_pool) = start_node_impl( - parachain_config, - self.collator_key, - relay_chain_config, - self.para_id, - self.wrap_announce_block, - false, - |_| Ok(jsonrpsee::RpcModule::new(())), - self.consensus, - collator_options, - ) - .await - .expect("could not create Cumulus test service"); + let (task_manager, client, network, rpc_handlers, transaction_pool, backend) = + start_node_impl( + parachain_config, + self.collator_key, + relay_chain_config, + self.para_id, + self.wrap_announce_block, + false, + |_| Ok(jsonrpsee::RpcModule::new(())), + self.consensus, + collator_options, + self.record_proof_during_import, + ) + .await + .expect("could not create Cumulus test service"); let peer_id = network.local_peer_id(); let addr = MultiaddrWithPeerId { multiaddr, peer_id }; - TestNode { task_manager, client, network, addr, rpc_handlers, transaction_pool } + TestNode { task_manager, client, network, addr, rpc_handlers, transaction_pool, backend } } } @@ -719,7 +745,7 @@ pub fn node_config( let role = if is_collator { Role::Authority } else { Role::Full }; let key_seed = key.to_seed(); let mut spec = - Box::new(chain_spec::get_chain_spec_with_extra_endowed(para_id, endowed_accounts)); + Box::new(chain_spec::get_chain_spec_with_extra_endowed(Some(para_id), endowed_accounts)); let mut storage = spec.as_storage_builder().build_storage().expect("could not build storage"); @@ -774,6 +800,9 @@ pub fn node_config( rpc_id_provider: None, rpc_max_subs_per_conn: Default::default(), rpc_port: 9945, + rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -865,11 +894,12 @@ pub fn construct_extrinsic( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let raw_payload = runtime::SignedPayload::from_raw( function.clone(), extra.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( @@ -893,7 +923,7 @@ pub fn run_relay_chain_validator_node( ) -> polkadot_test_service::PolkadotTestNode { let mut config = polkadot_test_service::node_config( storage_update_func, - tokio_handle, + tokio_handle.clone(), key, boot_nodes, true, @@ -907,5 +937,7 @@ pub fn run_relay_chain_validator_node( workers_path.pop(); workers_path.pop(); - polkadot_test_service::run_validator_node(config, Some(workers_path)) + tokio_handle.block_on(async move { + polkadot_test_service::run_validator_node(config, Some(workers_path)) + }) } diff --git a/cumulus/test/service/src/main.rs b/cumulus/test/service/src/main.rs index 5946e9cc3506e220672cb58561d52b93bcad2d8a..69a71a15389a58002e3ad3ac26495a48a0a0cd40 100644 --- a/cumulus/test/service/src/main.rs +++ b/cumulus/test/service/src/main.rs @@ -16,16 +16,13 @@ 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 polkadot_service::runtime_traits::AccountIdConversion; +use cumulus_primitives_core::relay_chain::CollatorPair; +use cumulus_test_service::{chain_spec, new_partial, AnnounceBlockFn}; 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 +41,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(parachain_id); - cmd.base.run(&spec) - }) + runner.sync_run(|config| cmd.run(&*config.chain_spec)) }, None => { let log_filters = cli.run.normalize().log_filters(); @@ -92,24 +67,21 @@ fn main() -> Result<(), sc_cli::Error> { .create_configuration(&cli, tokio_handle.clone()) .expect("Should be able to generate config"); - let parachain_id = ParaId::from(cli.parachain_id); let polkadot_cli = RelayChainCli::new( &config, [RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()), ); - let parachain_account = - AccountIdConversion::::into_account_truncating( - ¶chain_id, - ); - let tokio_handle = config.tokio_handle.clone(); let polkadot_config = SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle) .map_err(|err| format!("Relay chain argument error: {}", err))?; + let parachain_id = chain_spec::Extensions::try_get(&*config.chain_spec) + .map(|e| e.para_id) + .ok_or("Could not find parachain extension in chain-spec.")?; + tracing::info!("Parachain id: {:?}", parachain_id); - tracing::info!("Parachain Account: {}", parachain_account); tracing::info!( "Is collating: {}", if config.role.is_authority() { "yes" } else { "no" } @@ -128,17 +100,18 @@ fn main() -> Result<(), sc_cli::Error> { }) .unwrap_or(cumulus_test_service::Consensus::RelayChain); - let (mut task_manager, _, _, _, _) = tokio_runtime + let (mut task_manager, _, _, _, _, _) = tokio_runtime .block_on(cumulus_test_service::start_node_impl( config, collator_key, polkadot_config, - parachain_id, + parachain_id.into(), cli.disable_block_announcements.then(wrap_announce_block), cli.fail_pov_recovery, |_| Ok(jsonrpsee::RpcModule::new(())), consensus, collator_options, + true, )) .expect("could not create Cumulus test service"); diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index c77d350bdfef6b81532496d1d3a8878b66c3aae5..6b45770a8e3df47cb083dba5a8a0eeed1759e338 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "xcm-emulator" description = "Test kit to emulate XCM program execution." -version = "0.1.0" +version = "0.5.0" 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" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } lazy_static = "1.4.0" impl-trait-for-tuples = "0.2.2" @@ -17,6 +21,7 @@ frame-support = { path = "../../../substrate/frame/support" } frame-system = { path = "../../../substrate/frame/system" } sp-io = { path = "../../../substrate/primitives/io" } sp-core = { path = "../../../substrate/primitives/core" } +sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } sp-std = { path = "../../../substrate/primitives/std" } sp-runtime = { path = "../../../substrate/primitives/runtime" } sp-arithmetic = { path = "../../../substrate/primitives/arithmetic" } @@ -26,6 +31,7 @@ pallet-message-queue = { path = "../../../substrate/frame/message-queue" } # Cumulus cumulus-primitives-core = { path = "../../primitives/core" } +cumulus-pallet-xcmp-queue = { path = "../../pallets/xcmp-queue" } cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system" } cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } cumulus-test-relay-sproof-builder = { path = "../../test/relay-sproof-builder" } diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index 3da7814bec352f49058ed60da0d5fbac4998bd10..babb318a99500932dd8a2e42a2b443944751d286 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; @@ -24,19 +24,24 @@ pub use std::{ }; // Substrate +pub use cumulus_primitives_core::AggregateMessageOrigin as CumulusAggregateMessageOrigin; pub use frame_support::{ assert_ok, sp_runtime::{traits::Header as HeaderT, DispatchResult}, traits::{ - EnqueueMessage, Get, Hooks, OriginTrait, ProcessMessage, ProcessMessageError, ServiceQueues, + EnqueueMessage, ExecuteOverweightError, Get, Hooks, OnInitialize, OriginTrait, + ProcessMessage, ProcessMessageError, ServiceQueues, }, weights::{Weight, WeightMeter}, }; pub use frame_system::{Config as SystemConfig, Pallet as SystemPallet}; pub use pallet_balances::AccountData; +pub use pallet_message_queue; pub use sp_arithmetic::traits::Bounded; -pub use sp_core::{blake2_256, parameter_types, sr25519, storage::Storage, Pair}; +pub use sp_core::{parameter_types, sr25519, storage::Storage, Pair}; +pub use sp_crypto_hashing::blake2_256; pub use sp_io::TestExternalities; +pub use sp_runtime::BoundedSlice; pub use sp_std::{cell::RefCell, collections::vec_deque::VecDeque, fmt::Debug}; pub use sp_tracing; @@ -55,9 +60,10 @@ 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::latest::prelude::{ + AccountId32 as AccountId32Junction, Ancestor, Assets, Here, Location, + Parachain as ParachainJunction, Parent, WeightLimit, XcmHash, }; pub use xcm_executor::traits::ConvertLocation; @@ -166,30 +172,21 @@ pub trait Network { relay_parent_number: u32, parent_head_data: HeadData, ) -> ParachainInherentData; -} - -pub trait NetworkComponent { - type Network: Network; - fn send_horizontal_messages)>>( to_para_id: u32, iter: I, ) { HORIZONTAL_MESSAGES.with(|b| { b.borrow_mut() - .get_mut(Self::Network::name()) + .get_mut(Self::name()) .unwrap() .push_back((to_para_id, iter.collect())) }); } fn send_upward_message(from_para_id: u32, msg: Vec) { - UPWARD_MESSAGES.with(|b| { - b.borrow_mut() - .get_mut(Self::Network::name()) - .unwrap() - .push_back((from_para_id, msg)) - }); + UPWARD_MESSAGES + .with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((from_para_id, msg))); } fn send_downward_messages( @@ -198,19 +195,19 @@ pub trait NetworkComponent { ) { DOWNWARD_MESSAGES.with(|b| { b.borrow_mut() - .get_mut(Self::Network::name()) + .get_mut(Self::name()) .unwrap() .push_back((to_para_id, iter.collect())) }); } fn send_bridged_messages(msg: BridgeMessage) { - BRIDGED_MESSAGES - .with(|b| b.borrow_mut().get_mut(Self::Network::name()).unwrap().push_back(msg)); + BRIDGED_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back(msg)); } } -pub trait Chain: TestExt + NetworkComponent { +pub trait Chain: TestExt { + type Network: Network; type Runtime: SystemConfig; type RuntimeCall; type RuntimeOrigin; @@ -227,14 +224,16 @@ pub trait Chain: TestExt + NetworkComponent { } pub trait RelayChain: Chain { - type MessageProcessor: ProcessMessage; type SovereignAccountOf: ConvertLocation>; + type MessageProcessor: ProcessMessage + ServiceQueues; + + fn init(); - fn child_location_of(id: ParaId) -> MultiLocation { + fn child_location_of(id: ParaId) -> Location { (Ancestor(0), ParachainJunction(id.into())).into() } - fn sovereign_account_id_of(location: MultiLocation) -> AccountIdOf { + fn sovereign_account_id_of(location: Location) -> AccountIdOf { Self::SovereignAccountOf::convert_location(&location).unwrap() } @@ -245,10 +244,10 @@ pub trait RelayChain: Chain { pub trait Parachain: Chain { type XcmpMessageHandler: XcmpMessageHandler; - type DmpMessageHandler: DmpMessageHandler; type LocationToAccountId: ConvertLocation>; type ParachainInfo: Get; type ParachainSystem; + type MessageProcessor: ProcessMessage + ServiceQueues; fn init(); @@ -262,15 +261,15 @@ pub trait Parachain: Chain { Self::ext_wrapper(|| Self::ParachainInfo::get()) } - fn parent_location() -> MultiLocation { + fn parent_location() -> Location { (Parent).into() } - fn sibling_location_of(para_id: ParaId) -> MultiLocation { - (Parent, X1(ParachainJunction(para_id.into()))).into() + fn sibling_location_of(para_id: ParaId) -> Location { + (Parent, ParachainJunction(para_id.into())).into() } - fn sovereign_account_id_of(location: MultiLocation) -> AccountIdOf { + fn sovereign_account_id_of(location: Location) -> AccountIdOf { Self::LocationToAccountId::convert_location(&location).unwrap() } } @@ -344,9 +343,7 @@ macro_rules! decl_test_relay_chains { on_init = $on_init:expr, runtime = $runtime:ident, core = { - MessageProcessor: $mp:path, SovereignAccountOf: $sovereign_acc_of:path, - }, pallets = { $($pallet_name:ident: $pallet_path:path,)* @@ -358,9 +355,10 @@ macro_rules! decl_test_relay_chains { ) => { $( #[derive(Clone)] - pub struct $name; + pub struct $name($crate::PhantomData); - impl $crate::Chain for $name { + impl $crate::Chain for $name { + type Network = N; type Runtime = $runtime::Runtime; type RuntimeCall = $runtime::RuntimeCall; type RuntimeOrigin = $runtime::RuntimeOrigin; @@ -379,27 +377,35 @@ macro_rules! decl_test_relay_chains { } } - impl $crate::RelayChain for $name { + impl $crate::RelayChain for $name { type SovereignAccountOf = $sovereign_acc_of; - type MessageProcessor = $mp; + type MessageProcessor = $crate::DefaultRelayMessageProcessor<$name>; + + fn init() { + use $crate::TestExt; + // Initialize the thread local variable + $crate::paste::paste! { + [].with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); + } + } } $crate::paste::paste! { - pub trait [<$name Pallet>] { + pub trait [<$name RelayPallet>] { $( type $pallet_name; )? } - impl [<$name Pallet>] for $name { + impl [<$name RelayPallet>] for $name { $( type $pallet_name = $pallet_path; )? } } - $crate::__impl_test_ext_for_relay_chain!($name, $genesis, $on_init, $api_version); - $crate::__impl_check_assertion!($name); + $crate::__impl_test_ext_for_relay_chain!($name, N, $genesis, $on_init, $api_version); + $crate::__impl_check_assertion!($name, N); )+ }; } @@ -407,10 +413,11 @@ macro_rules! decl_test_relay_chains { #[macro_export] macro_rules! __impl_test_ext_for_relay_chain { // entry point: generate ext name - ($name:ident, $genesis:expr, $on_init:expr, $api_version:tt) => { + ($name:ident, $network:ident, $genesis:expr, $on_init:expr, $api_version:tt) => { $crate::paste::paste! { $crate::__impl_test_ext_for_relay_chain!( @impl $name, + $network, $genesis, $on_init, [], @@ -420,10 +427,10 @@ macro_rules! __impl_test_ext_for_relay_chain { } }; // impl - (@impl $name:ident, $genesis:expr, $on_init:expr, $api_version:ident, $local_ext:ident, $global_ext:ident) => { + (@impl $name:ident, $network:ident, $genesis:expr, $on_init:expr, $api_version:ident, $local_ext:ident, $global_ext:ident) => { thread_local! { pub static $local_ext: $crate::RefCell<$crate::TestExternalities> - = $crate::RefCell::new(<$name as $crate::TestExt>::build_new_ext($genesis)); + = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } $crate::lazy_static! { @@ -431,9 +438,9 @@ macro_rules! __impl_test_ext_for_relay_chain { = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); } - impl $crate::TestExt for $name { + impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { - use $crate::{sp_tracing, NetworkComponent, Network, Chain, TestExternalities}; + use $crate::{sp_tracing, Network, Chain, TestExternalities}; let mut ext = TestExternalities::new(storage); @@ -450,7 +457,7 @@ macro_rules! __impl_test_ext_for_relay_chain { } fn new_ext() -> $crate::TestExternalities { - <$name>::build_new_ext($genesis) + Self::build_new_ext($genesis) } fn move_ext_out(id: &'static str) { @@ -501,13 +508,13 @@ macro_rules! __impl_test_ext_for_relay_chain { } fn reset_ext() { - $local_ext.with(|v| *v.borrow_mut() = <$name>::build_new_ext($genesis)); + $local_ext.with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } fn execute_with(execute: impl FnOnce() -> R) -> R { - use $crate::{Chain, NetworkComponent, Network}; + use $crate::{Chain, Network}; // Make sure the Network is initialized - <$name as NetworkComponent>::Network::init(); + <$network>::init(); // Execute let r = $local_ext.with(|v| v.borrow_mut().execute_with(execute)); @@ -518,7 +525,7 @@ macro_rules! __impl_test_ext_for_relay_chain { use $crate::polkadot_primitives::runtime_api::runtime_decl_for_parachain_host::$api_version; //TODO: mark sent count & filter out sent msg - for para_id in<$name as NetworkComponent>::Network::para_ids() { + for para_id in <$network>::para_ids() { // downward messages let downward_messages = ::Runtime::dmq_contents(para_id.into()) .into_iter() @@ -526,7 +533,7 @@ macro_rules! __impl_test_ext_for_relay_chain { if downward_messages.len() == 0 { continue; } - <$name>::send_downward_messages(para_id, downward_messages.into_iter()); + <$network>::send_downward_messages(para_id, downward_messages.into_iter()); // Note: no need to handle horizontal messages, as the // simulator directly sends them to dest (not relayed). @@ -542,7 +549,7 @@ macro_rules! __impl_test_ext_for_relay_chain { }) }); - <$name as NetworkComponent>::Network::process_messages(); + <$network>::process_messages(); r } @@ -569,9 +576,9 @@ macro_rules! decl_test_parachains { runtime = $runtime:ident, core = { XcmpMessageHandler: $xcmp_message_handler:path, - DmpMessageHandler: $dmp_message_handler:path, LocationToAccountId: $location_to_account:path, ParachainInfo: $parachain_info:path, + MessageOrigin: $message_origin:path, }, pallets = { $($pallet_name:ident: $pallet_path:path,)* @@ -583,14 +590,15 @@ macro_rules! decl_test_parachains { ) => { $( #[derive(Clone)] - pub struct $name; + pub struct $name($crate::PhantomData); - impl $crate::Chain for $name { + impl $crate::Chain for $name { type Runtime = $runtime::Runtime; type RuntimeCall = $runtime::RuntimeCall; type RuntimeOrigin = $runtime::RuntimeOrigin; type RuntimeEvent = $runtime::RuntimeEvent; type System = $crate::SystemPallet::; + type Network = N; fn account_data_of(account: $crate::AccountIdOf) -> $crate::AccountData<$crate::Balance> { ::ext_wrapper(|| $crate::SystemPallet::::account(account).data.into()) @@ -604,17 +612,21 @@ macro_rules! decl_test_parachains { } } - impl $crate::Parachain for $name { + impl $crate::Parachain for $name { type XcmpMessageHandler = $xcmp_message_handler; - type DmpMessageHandler = $dmp_message_handler; type LocationToAccountId = $location_to_account; type ParachainSystem = $crate::ParachainSystemPallet<::Runtime>; type ParachainInfo = $parachain_info; + 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 fn init() { - use $crate::{Chain, HeadData, Network, NetworkComponent, Hooks, Encode, Parachain, TestExt}; + use $crate::{Chain, HeadData, Network, Hooks, Encode, Parachain, TestExt}; + // Initialize the thread local variable + $crate::paste::paste! { + [].with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); + } // Set the last block head for later use in the next block Self::set_last_head(); // Initialize a new block @@ -624,21 +636,21 @@ macro_rules! decl_test_parachains { } fn new_block() { - use $crate::{Chain, HeadData, Network, NetworkComponent, Hooks, Encode, Parachain, TestExt}; + use $crate::{Chain, HeadData, Network, Hooks, Encode, Parachain, TestExt}; let para_id = Self::para_id().into(); Self::ext_wrapper(|| { // Increase Relay Chain block number - let mut relay_block_number = <$name as NetworkComponent>::Network::relay_block_number(); + let mut relay_block_number = N::relay_block_number(); relay_block_number += 1; - <$name as NetworkComponent>::Network::set_relay_block_number(relay_block_number); + N::set_relay_block_number(relay_block_number); // Initialize a new Parachain block let mut block_number = ::System::block_number(); block_number += 1; let parent_head_data = $crate::LAST_HEAD.with(|b| b.borrow_mut() - .get_mut(::Network::name()) + .get_mut(N::name()) .expect("network not initialized?") .get(¶_id) .expect("network not initialized?") @@ -649,13 +661,13 @@ macro_rules! decl_test_parachains { let _ = ::ParachainSystem::set_validation_data( ::RuntimeOrigin::none(), - <$name as NetworkComponent>::Network::hrmp_channel_parachain_inherent_data(para_id, relay_block_number, parent_head_data), + N::hrmp_channel_parachain_inherent_data(para_id, relay_block_number, parent_head_data), ); }); } fn finalize_block() { - use $crate::{Chain, Encode, Hooks, Network, NetworkComponent, Parachain, TestExt}; + use $crate::{Chain, Encode, Hooks, Network, Parachain, TestExt}; Self::ext_wrapper(|| { let block_number = ::System::block_number(); @@ -667,7 +679,7 @@ macro_rules! decl_test_parachains { fn set_last_head() { - use $crate::{Chain, Encode, HeadData, Network, NetworkComponent, Parachain, TestExt}; + use $crate::{Chain, Encode, HeadData, Network, Parachain, TestExt}; let para_id = Self::para_id().into(); @@ -675,7 +687,7 @@ macro_rules! decl_test_parachains { // Store parent head data for use later. let created_header = ::System::finalize(); $crate::LAST_HEAD.with(|b| b.borrow_mut() - .get_mut(::Network::name()) + .get_mut(N::name()) .expect("network not initialized?") .insert(para_id, HeadData(created_header.encode())) ); @@ -684,21 +696,21 @@ macro_rules! decl_test_parachains { } $crate::paste::paste! { - pub trait [<$name Pallet>] { + pub trait [<$name ParaPallet>] { $( type $pallet_name; )* } - impl [<$name Pallet>] for $name { + impl [<$name ParaPallet>] for $name { $( type $pallet_name = $pallet_path; )* } } - $crate::__impl_test_ext_for_parachain!($name, $genesis, $on_init); - $crate::__impl_check_assertion!($name); + $crate::__impl_test_ext_for_parachain!($name, N, $genesis, $on_init); + $crate::__impl_check_assertion!($name, N); )+ }; } @@ -706,16 +718,16 @@ macro_rules! decl_test_parachains { #[macro_export] macro_rules! __impl_test_ext_for_parachain { // entry point: generate ext name - ($name:ident, $genesis:expr, $on_init:expr) => { + ($name:ident, $network:ident, $genesis:expr, $on_init:expr) => { $crate::paste::paste! { - $crate::__impl_test_ext_for_parachain!(@impl $name, $genesis, $on_init, [], []); + $crate::__impl_test_ext_for_parachain!(@impl $name, $network, $genesis, $on_init, [], []); } }; // impl - (@impl $name:ident, $genesis:expr, $on_init:expr, $local_ext:ident, $global_ext:ident) => { + (@impl $name:ident, $network:ident, $genesis:expr, $on_init:expr, $local_ext:ident, $global_ext:ident) => { thread_local! { pub static $local_ext: $crate::RefCell<$crate::TestExternalities> - = $crate::RefCell::new(<$name as $crate::TestExt>::build_new_ext($genesis)); + = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } $crate::lazy_static! { @@ -723,7 +735,7 @@ macro_rules! __impl_test_ext_for_parachain { = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); } - impl $crate::TestExt for $name { + impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { let mut ext = $crate::TestExternalities::new(storage); @@ -740,7 +752,7 @@ macro_rules! __impl_test_ext_for_parachain { } fn new_ext() -> $crate::TestExternalities { - <$name>::build_new_ext($genesis) + Self::build_new_ext($genesis) } fn move_ext_out(id: &'static str) { @@ -791,14 +803,14 @@ macro_rules! __impl_test_ext_for_parachain { } fn reset_ext() { - $local_ext.with(|v| *v.borrow_mut() = <$name>::build_new_ext($genesis)); + $local_ext.with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } fn execute_with(execute: impl FnOnce() -> R) -> R { - use $crate::{Chain, Get, Hooks, NetworkComponent, Network, Parachain, Encode}; + use $crate::{Chain, Get, Hooks, Network, Parachain, Encode}; // Make sure the Network is initialized - <$name as NetworkComponent>::Network::init(); + <$network>::init(); // Initialize a new block Self::new_block(); @@ -809,7 +821,7 @@ macro_rules! __impl_test_ext_for_parachain { // Finalize the block Self::finalize_block(); - let para_id = <$name>::para_id().into(); + let para_id = Self::para_id().into(); // Send messages if needed $local_ext.with(|v| { @@ -825,27 +837,27 @@ macro_rules! __impl_test_ext_for_parachain { let collation_info = ::ParachainSystem::collect_collation_info(&mock_header); // send upward messages - let relay_block_number = <$name as NetworkComponent>::Network::relay_block_number(); + let relay_block_number = <$network>::relay_block_number(); for msg in collation_info.upward_messages.clone() { - <$name>::send_upward_message(para_id, msg); + <$network>::send_upward_message(para_id, msg); } // send horizontal messages for msg in collation_info.horizontal_messages { - <$name>::send_horizontal_messages( + <$network>::send_horizontal_messages( msg.recipient.into(), vec![(para_id.into(), relay_block_number, msg.data)].into_iter(), ); } // get bridge messages - type NetworkBridge = <<$name as NetworkComponent>::Network as $crate::Network>::Bridge; + type NetworkBridge<$network> = <$network as $crate::Network>::Bridge; - let bridge_messages = <::Handler as $crate::BridgeMessageHandler>::get_source_outbound_messages(); + let bridge_messages = < as $crate::Bridge>::Handler as $crate::BridgeMessageHandler>::get_source_outbound_messages(); // send bridged messages for msg in bridge_messages { - <$name>::send_bridged_messages(msg); + <$network>::send_bridged_messages(msg); } // log events @@ -861,7 +873,7 @@ macro_rules! __impl_test_ext_for_parachain { // provide inbound DMP/HRMP messages through a side-channel. // normally this would come through the `set_validation_data`, // but we go around that. - <$name as NetworkComponent>::Network::process_messages(); + <$network>::process_messages(); r } @@ -883,8 +895,8 @@ macro_rules! decl_test_networks { ( $( pub struct $name:ident { - relay_chain = $relay_chain:ty, - parachains = vec![ $( $parachain:ty, )* ], + relay_chain = $relay_chain:ident, + parachains = vec![ $( $parachain:ident, )* ], bridge = $bridge:ty } ), @@ -892,10 +904,11 @@ macro_rules! decl_test_networks { $(,)? ) => { $( + #[derive(Clone)] pub struct $name; impl $crate::Network for $name { - type Relay = $relay_chain; + type Relay = $relay_chain; type Bridge = $bridge; fn name() -> &'static str { @@ -913,8 +926,8 @@ macro_rules! decl_test_networks { $crate::BRIDGED_MESSAGES.with(|b| b.borrow_mut().remove(Self::name())); $crate::LAST_HEAD.with(|b| b.borrow_mut().remove(Self::name())); - <$relay_chain>::reset_ext(); - $( <$parachain>::reset_ext(); )* + <$relay_chain>::reset_ext(); + $( <$parachain>::reset_ext(); )* } fn init() { @@ -929,13 +942,14 @@ macro_rules! decl_test_networks { $crate::PARA_IDS.with(|b| b.borrow_mut().insert(Self::name().to_string(), Self::para_ids())); $crate::LAST_HEAD.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::HashMap::new())); - $( <$parachain as $crate::Parachain>::init(); )* + <$relay_chain as $crate::RelayChain>::init(); + $( <$parachain as $crate::Parachain>::init(); )* } } fn para_ids() -> Vec { vec![$( - <$parachain as $crate::Parachain>::para_id().into(), + <$parachain as $crate::Parachain>::para_id().into(), )*] } @@ -968,12 +982,12 @@ macro_rules! decl_test_networks { } fn process_downward_messages() { - use $crate::{DmpMessageHandler, Bounded, Parachain, RelayChainBlockNumber, TestExt}; + use $crate::{DmpMessageHandler, Bounded, Parachain, RelayChainBlockNumber, TestExt, Encode}; while let Some((to_para_id, messages)) = $crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { $( - let para_id: u32 = <$parachain>::para_id().into(); + let para_id: u32 = <$parachain>::para_id().into(); if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().contains(&to_para_id)) && para_id == to_para_id { let mut msg_dedup: Vec<(RelayChainBlockNumber, Vec)> = Vec::new(); @@ -983,16 +997,25 @@ macro_rules! decl_test_networks { msg_dedup.dedup(); let msgs = msg_dedup.clone().into_iter().filter(|m| { - !$crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap_or(&mut $crate::VecDeque::new()).contains(&(to_para_id, m.0, m.1.clone()))) + !$crate::DMP_DONE.with(|b| b.borrow().get(Self::name()) + .unwrap_or(&mut $crate::VecDeque::new()) + .contains(&(to_para_id, m.0, m.1.clone())) + ) }).collect::)>>(); - if msgs.len() != 0 { - <$parachain>::ext_wrapper(|| { - <$parachain as Parachain>::DmpMessageHandler::handle_dmp_messages(msgs.clone().into_iter(), $crate::Weight::max_value()); + + use $crate::{ProcessMessage, CumulusAggregateMessageOrigin, BoundedSlice, WeightMeter}; + for (block, msg) in msgs.clone().into_iter() { + let mut weight_meter = WeightMeter::new(); + <$parachain>::ext_wrapper(|| { + let _ = <$parachain as Parachain>::MessageProcessor::process_message( + &msg[..], + $crate::CumulusAggregateMessageOrigin::Parent.into(), + &mut weight_meter, + &mut msg.using_encoded($crate::blake2_256), + ); }); $crate::log::debug!(target: concat!("dmp::", stringify!($name)) , "DMP messages processed {:?} to para_id {:?}", msgs.clone(), &to_para_id); - for m in msgs { - $crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((to_para_id, m.0, m.1))); - } + $crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((to_para_id, block, msg))); } } )* @@ -1000,17 +1023,19 @@ macro_rules! decl_test_networks { } fn process_horizontal_messages() { - use $crate::{XcmpMessageHandler, Bounded, Parachain, TestExt}; + use $crate::{XcmpMessageHandler, ServiceQueues, Bounded, Parachain, TestExt}; while let Some((to_para_id, messages)) = $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { let iter = messages.iter().map(|(p, b, m)| (*p, *b, &m[..])).collect::>().into_iter(); $( - let para_id: u32 = <$parachain>::para_id().into(); + let para_id: u32 = <$parachain>::para_id().into(); if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().contains(&to_para_id)) && para_id == to_para_id { - <$parachain>::ext_wrapper(|| { - <$parachain as Parachain>::XcmpMessageHandler::handle_xcmp_messages(iter.clone(), $crate::Weight::max_value()); + <$parachain>::ext_wrapper(|| { + <$parachain as Parachain>::XcmpMessageHandler::handle_xcmp_messages(iter.clone(), $crate::Weight::MAX); + // Nudge the MQ pallet to process immediately instead of in the next block. + let _ = <$parachain as Parachain>::MessageProcessor::service_queues($crate::Weight::MAX); }); $crate::log::debug!(target: concat!("hrmp::", stringify!($name)) , "HRMP messages processed {:?} to para_id {:?}", &messages, &to_para_id); } @@ -1019,12 +1044,12 @@ macro_rules! decl_test_networks { } fn process_upward_messages() { - use $crate::{Encode, ProcessMessage, TestExt}; + use $crate::{Encode, ProcessMessage, TestExt, WeightMeter}; while let Some((from_para_id, msg)) = $crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { - let mut weight_meter = $crate::WeightMeter::new(); - <$relay_chain>::ext_wrapper(|| { - let _ = <$relay_chain as $crate::RelayChain>::MessageProcessor::process_message( + let mut weight_meter = WeightMeter::new(); + <$relay_chain>::ext_wrapper(|| { + let _ = <$relay_chain as $crate::RelayChain>::MessageProcessor::process_message( &msg[..], from_para_id.into(), &mut weight_meter, @@ -1107,13 +1132,13 @@ macro_rules! decl_test_networks { } } - impl $crate::NetworkComponent for $relay_chain { - type Network = $name; + $crate::paste::paste! { + pub type [<$relay_chain Relay>] = $relay_chain<$name>; } $( - impl $crate::NetworkComponent for $parachain { - type Network = $name; + $crate::paste::paste! { + pub type [<$parachain Para>] = $parachain<$name>; } )* )+ @@ -1125,9 +1150,9 @@ macro_rules! decl_test_bridges { ( $( pub struct $name:ident { - source = $source:ty, - target = $target:ty, - handler = $handler:ty + source = $source:ident, + target = $target:ident, + handler = $handler:ident } ), + @@ -1143,10 +1168,10 @@ macro_rules! decl_test_bridges { type Handler = $handler; fn init() { - use $crate::{NetworkComponent, Network}; + use $crate::{Network, Parachain}; // Make sure source and target `Network` have been initialized - <$source as NetworkComponent>::Network::init(); - <$target as NetworkComponent>::Network::init(); + <$source as Chain>::Network::init(); + <$target as Chain>::Network::init(); } } )+ @@ -1155,10 +1180,11 @@ macro_rules! decl_test_bridges { #[macro_export] macro_rules! __impl_check_assertion { - ($chain:ident) => { - impl - $crate::CheckAssertion for $chain + ($chain:ident, $network:ident) => { + impl<$network, Origin, Destination, Hops, Args> + $crate::CheckAssertion for $chain<$network> where + $network: $crate::Network, Origin: $crate::Chain + Clone, Destination: $crate::Chain + Clone, Origin::RuntimeOrigin: @@ -1171,9 +1197,9 @@ macro_rules! __impl_check_assertion { fn check_assertion(test: $crate::Test) { use $crate::TestExt; - let chain_name = std::any::type_name::<$chain>(); + let chain_name = std::any::type_name::<$chain<$network>>(); - <$chain>::execute_with(|| { + <$chain<$network>>::execute_with(|| { if let Some(dispatchable) = test.hops_dispatchable.get(chain_name) { $crate::assert_ok!(dispatchable(test.clone())); } @@ -1244,7 +1270,14 @@ macro_rules! assert_expected_events { ) ); } else if !event_received { - message.push(format!("\n\n{}::\x1b[31m{}\x1b[0m was never received", stringify!($chain), stringify!($event_pat))); + message.push( + format!( + "\n\n{}::\x1b[31m{}\x1b[0m was never received. All events:\n{:#?}", + stringify!($chain), + stringify!($event_pat), + <$chain as $crate::Chain>::events(), + ) + ); } else { // If we find a perfect match we remove the event to avoid being potentially assessed multiple times events.remove(index_match); @@ -1282,10 +1315,66 @@ macro_rules! decl_test_sender_receiver_accounts_parameter_types { }; } -pub struct DefaultMessageProcessor(PhantomData); -impl ProcessMessage for DefaultMessageProcessor +pub struct DefaultParaMessageProcessor(PhantomData<(T, M)>); +// Process HRMP messages from sibling paraids +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, +{ + type Origin = M; + + fn process_message( + msg: &[u8], + orig: Self::Origin, + _meter: &mut WeightMeter, + _id: &mut XcmHash, + ) -> Result { + MessageQueuePallet::::enqueue_message( + msg.try_into().expect("Message too long"), + orig.clone(), + ); + MessageQueuePallet::::service_queues(Weight::MAX); + + Ok(true) + } +} +impl ServiceQueues for DefaultParaMessageProcessor +where + M: MaxEncodedLen, + T: Parachain, + T::Runtime: MessageQueueConfig, + <::MessageProcessor as ProcessMessage>::Origin: PartialEq, + MessageQueuePallet: EnqueueMessage + ServiceQueues, +{ + type OverweightMessageAddress = (); + + fn service_queues(weight_limit: Weight) -> Weight { + MessageQueuePallet::::service_queues(weight_limit) + } + + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + unimplemented!() + } +} + +pub struct DefaultRelayMessageProcessor(PhantomData); +// Process UMP messages on the relay +impl ProcessMessage for DefaultRelayMessageProcessor where - T: Chain + RelayChain, + T: RelayChain, T::Runtime: MessageQueueConfig, <::MessageProcessor as ProcessMessage>::Origin: PartialEq, @@ -1309,6 +1398,28 @@ where } } +impl ServiceQueues for DefaultRelayMessageProcessor +where + T: RelayChain, + T::Runtime: MessageQueueConfig, + <::MessageProcessor as ProcessMessage>::Origin: + PartialEq, + MessageQueuePallet: EnqueueMessage + ServiceQueues, +{ + type OverweightMessageAddress = (); + + fn service_queues(weight_limit: Weight) -> Weight { + MessageQueuePallet::::service_queues(weight_limit) + } + + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + unimplemented!() + } +} + /// Struct that keeps account's id and balance #[derive(Clone)] pub struct TestAccount { @@ -1319,15 +1430,50 @@ pub struct TestAccount { /// Default `Args` provided by xcm-emulator to be stored in a `Test` instance #[derive(Clone)] pub struct TestArgs { - pub dest: MultiLocation, - pub beneficiary: MultiLocation, + pub dest: Location, + pub beneficiary: Location, pub amount: Balance, - pub assets: MultiAssets, + pub assets: Assets, pub asset_id: Option, pub fee_asset_item: u32, 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: Location, 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: Location, + beneficiary_id: AccountId32, + amount: Balance, + assets: Assets, + 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, @@ -1340,9 +1486,9 @@ pub struct TestContext { /// These arguments can be easily reused and shared between the assertion functions /// and dispatchable functions, which are also stored in `Test`. /// `Origin` corresponds to the chain where the XCM interaction starts with an initial execution. -/// `Destination` corresponds to the last chain where an effect of the intial execution is expected -/// happen. `Hops` refer all the ordered intermediary chains an initial XCM execution can provoke -/// some effect. +/// `Destination` corresponds to the last chain where an effect of the initial execution is expected +/// to happen. `Hops` refer to all the ordered intermediary chains an initial XCM execution can +/// provoke some effect on. #[derive(Clone)] pub struct Test where @@ -1396,7 +1542,7 @@ where let chain_name = std::any::type_name::(); self.hops_assertion.insert(chain_name.to_string(), assertion); } - /// Stores an assertion in a particular Chain + /// Stores a dispatchable in a particular Chain pub fn set_dispatchable(&mut self, dispatchable: fn(Self) -> DispatchResult) { let chain_name = std::any::type_name::(); self.hops_dispatchable.insert(chain_name.to_string(), dispatchable); diff --git a/cumulus/zombienet/examples/statemine_kusama_local_network.toml b/cumulus/zombienet/examples/asset_hub_westend_local_network.toml similarity index 71% rename from cumulus/zombienet/examples/statemine_kusama_local_network.toml rename to cumulus/zombienet/examples/asset_hub_westend_local_network.toml index 1f3debfb9d29cd4381eb376af1d23aefc1815723..5b0ac1f17e8b74cd9c2a89e623e97f634eb54ef4 100644 --- a/cumulus/zombienet/examples/statemine_kusama_local_network.toml +++ b/cumulus/zombienet/examples/asset_hub_westend_local_network.toml @@ -1,7 +1,7 @@ [relaychain] -default_command = "../polkadot/target/release/polkadot" +default_command = "../../target/release/polkadot" default_args = [ "-lparachain=debug" ] -chain = "kusama-local" +chain = "westend-local" [[relaychain.nodes]] name = "alice" @@ -21,47 +21,47 @@ chain = "kusama-local" [[parachains]] id = 1000 -chain = "asset-hub-kusama-local" +chain = "asset-hub-westend-local" cumulus_based = true # run alice as parachain collator [[parachains.collators]] name = "alice" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] # run bob as parachain collator [[parachains.collators]] name = "bob" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] # run charlie as parachain collator [[parachains.collators]] name = "charlie" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] # run dave as parachain collator [[parachains.collators]] name = "dave" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] # run eve as parachain collator [[parachains.collators]] name = "eve" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] # run ferdie as parachain collator [[parachains.collators]] name = "ferdie" validator = true - command = "./target/release/polkadot-parachain" + command = "../../target/release/polkadot-parachain" args = ["-lparachain=debug"] diff --git a/cumulus/zombienet/examples/bridge_hub_kusama_local_network.toml b/cumulus/zombienet/examples/bridge_hub_kusama_local_network.toml deleted file mode 100644 index ae8ae07a75ce84f074837ba1125554622a426e9e..0000000000000000000000000000000000000000 --- a/cumulus/zombienet/examples/bridge_hub_kusama_local_network.toml +++ /dev/null @@ -1,67 +0,0 @@ -[relaychain] -default_command = "../polkadot/target/release/polkadot" -default_args = [ "-lparachain=debug" ] -chain = "kusama-local" - - [[relaychain.nodes]] - name = "alice" - validator = true - - [[relaychain.nodes]] - name = "bob" - validator = true - - [[relaychain.nodes]] - name = "charlie" - validator = true - - [[relaychain.nodes]] - name = "dave" - validator = true - -[[parachains]] -id = 1003 -chain = "bridge-hub-kusama-local" -cumulus_based = true - - # run alice as parachain collator - [[parachains.collators]] - name = "alice" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run bob as parachain collator - [[parachains.collators]] - name = "bob" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run charlie as parachain collator - [[parachains.collators]] - name = "charlie" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run dave as parachain collator - [[parachains.collators]] - name = "dave" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run eve as parachain collator - [[parachains.collators]] - name = "eve" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run ferdie as parachain collator - [[parachains.collators]] - name = "ferdie" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] diff --git a/cumulus/zombienet/examples/bridge_hub_polkadot_local_network.toml b/cumulus/zombienet/examples/bridge_hub_polkadot_local_network.toml deleted file mode 100644 index 564fece7cae7781d06415221a879de0c24380e38..0000000000000000000000000000000000000000 --- a/cumulus/zombienet/examples/bridge_hub_polkadot_local_network.toml +++ /dev/null @@ -1,67 +0,0 @@ -[relaychain] -default_command = "../polkadot/target/release/polkadot" -default_args = [ "-lparachain=debug" ] -chain = "polkadot-local" - - [[relaychain.nodes]] - name = "alice" - validator = true - - [[relaychain.nodes]] - name = "bob" - validator = true - - [[relaychain.nodes]] - name = "charlie" - validator = true - - [[relaychain.nodes]] - name = "dave" - validator = true - -[[parachains]] -id = 1003 -chain = "bridge-hub-polkadot-local" -cumulus_based = true - - # run alice as parachain collator - [[parachains.collators]] - name = "alice" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run bob as parachain collator - [[parachains.collators]] - name = "bob" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run charlie as parachain collator - [[parachains.collators]] - name = "charlie" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run dave as parachain collator - [[parachains.collators]] - name = "dave" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run eve as parachain collator - [[parachains.collators]] - name = "eve" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] - - # run ferdie as parachain collator - [[parachains.collators]] - name = "ferdie" - validator = true - command = "./target/release/polkadot-parachain" - args = ["-lparachain=debug"] diff --git a/cumulus/zombienet/examples/small_network.toml b/cumulus/zombienet/examples/small_network.toml index 06ac0d0e5e78781710eb134eae7c7dd8233cfce9..ab7265712308f86a6a3b9d2d78dfacfa3c40abd2 100644 --- a/cumulus/zombienet/examples/small_network.toml +++ b/cumulus/zombienet/examples/small_network.toml @@ -14,7 +14,7 @@ chain = "rococo-local" [[parachains]] id = 2000 cumulus_based = true -chain = "asset-hub-kusama-local" +chain = "asset-hub-rococo-local" # run charlie as parachain collator [[parachains.collators]] diff --git a/cumulus/zombienet/tests/0002-pov_recovery.toml b/cumulus/zombienet/tests/0002-pov_recovery.toml index 7c74a74a75027dd29e67b256698dcfa0892492d5..15a61eba2a0342bce55f805473d2c3ba9b51c7f8 100644 --- a/cumulus/zombienet/tests/0002-pov_recovery.toml +++ b/cumulus/zombienet/tests/0002-pov_recovery.toml @@ -4,7 +4,7 @@ default_command = "polkadot" chain = "rococo-local" -[relaychain.genesis.runtime.runtime_genesis_config.configuration.config] +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] # set parameters such that collators only connect to 1 validator as a backing group max_validators_per_core = 1 group_rotation_frequency = 100 # 10 mins @@ -34,13 +34,12 @@ add_to_genesis = false args = ["--disable-block-announcements"] # run 'alice' as a parachain collator who does not produce blocks - # 'alice' is a bootnode for 'bob' and 'charlie' [[parachains.collators]] name = "alice" validator = true # collator image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--in-peers 0", "--out-peers 0", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] # run 'charlie' as a parachain full node [[parachains.collators]] @@ -48,7 +47,7 @@ add_to_genesis = false validator = false # full node image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}","--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--in-peers 0", "--out-peers 0", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] # we fail recovery for 'eve' from time to time to test retries [[parachains.collators]] @@ -56,7 +55,7 @@ add_to_genesis = false validator = true # collator image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--fail-pov-recovery", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--fail-pov-recovery", "--in-peers 0", "--out-peers 0", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] # run 'one' as a RPC collator who does not produce blocks [[parachains.collators]] @@ -64,7 +63,7 @@ add_to_genesis = false validator = true # collator image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--use-null-consensus", "--in-peers 0", "--out-peers 0", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] # run 'two' as a RPC parachain full node [[parachains.collators]] @@ -72,7 +71,7 @@ add_to_genesis = false validator = false # full node image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--in-peers 0", "--out-peers 0", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-rpc-url {{'ferdie'|zombie('wsUri')}}", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] # run 'three' with light client [[parachains.collators]] @@ -80,4 +79,4 @@ add_to_genesis = false validator = false # full node image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-light-client", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] + args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--in-peers 0", "--out-peers 0", "--bootnodes {{'bob'|zombie('multiAddress')}}", "--relay-chain-light-client", "--", "--reserved-only", "--reserved-nodes {{'ferdie'|zombie('multiAddress')}}"] diff --git a/cumulus/zombienet/tests/0002-pov_recovery.zndsl b/cumulus/zombienet/tests/0002-pov_recovery.zndsl index 12ff00210f33c0918402b02845325739bc3b8ae2..b05285c87bff5a69312552d13b9b652bbd9d1bc1 100644 --- a/cumulus/zombienet/tests/0002-pov_recovery.zndsl +++ b/cumulus/zombienet/tests/0002-pov_recovery.zndsl @@ -13,5 +13,13 @@ alice: reports block height is at least 20 within 600 seconds charlie: reports block height is at least 20 within 600 seconds one: reports block height is at least 20 within 800 seconds two: reports block height is at least 20 within 800 seconds -three: reports block height is at least 20 within 800 seconds +# Re-enable once we upgraded from smoldot 0.11.0 and https://github.com/paritytech/polkadot-sdk/pull/1631 is merged +# three: reports block height is at least 20 within 800 seconds eve: reports block height is at least 20 within 800 seconds + +one: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds +two: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds +three: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds +eve: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds +charlie: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds +alice: count of log lines containing "Importing block retrieved using pov_recovery" is greater than 19 within 10 seconds diff --git a/cumulus/zombienet/tests/0005-migrate_solo_to_para.toml b/cumulus/zombienet/tests/0005-migrate_solo_to_para.toml index f98c0e6f2592914fe690803a28f05abd1501229c..180ed8bf7b99bc673e76e6b26c19a86c7b103879 100644 --- a/cumulus/zombienet/tests/0005-migrate_solo_to_para.toml +++ b/cumulus/zombienet/tests/0005-migrate_solo_to_para.toml @@ -32,7 +32,7 @@ cumulus_based = true add_to_genesis = false register_para = false # Set some random value in the genesis state to create a different genesis hash. -[parachains.genesis.runtime.runtime_genesis_config.sudo] +[parachains.genesis.runtimeGenesis.patch.sudo] key = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" # run the parachain that will be used to return the header of the solo chain. diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4bfb73acda05880ef49570594d0769d1e5e4b147 --- /dev/null +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -0,0 +1,58 @@ +# this image is built on top of existing Zombienet image +ARG ZOMBIENET_IMAGE +# this image uses substrate-relay image built elsewhere +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v2023-11-07-rococo-westend-initial-relayer + +# metadata +ARG VCS_REF +ARG BUILD_DATE +ARG IMAGE_NAME + +# we need `substrate-relay` binary, built elsewhere +FROM ${SUBSTRATE_RELAY_IMAGE} as relay-builder + +# the base image is the zombienet image - we are planning to run zombienet tests using native +# provider here +FROM ${ZOMBIENET_IMAGE} + +LABEL io.parity.image.authors="devops-team@parity.io" \ + io.parity.image.vendor="Parity Technologies" \ + io.parity.image.title="${IMAGE_NAME}" \ + io.parity.image.description="Bridges Zombienet tests." \ + io.parity.image.source="https://github.com/paritytech/polkadot-sdk/blob/${VCS_REF}/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" \ + io.parity.image.revision="${VCS_REF}" \ + io.parity.image.created="${BUILD_DATE}" \ + io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/bridges/testing" + +# show backtraces +ENV RUST_BACKTRACE 1 +USER root + +# for native provider to work (TODO: fix in zn docker?) +RUN apt-get update && apt-get install -y procps sudo +RUN yarn global add @polkadot/api-cli + +# add polkadot binary to the docker image +COPY ./artifacts/polkadot /usr/local/bin/ +COPY ./artifacts/polkadot-execute-worker /usr/local/bin/ +COPY ./artifacts/polkadot-prepare-worker /usr/local/bin/ +# add polkadot-parachain binary to the docker image +COPY ./artifacts/polkadot-parachain /usr/local/bin +# copy substrate-relay to the docker image +COPY --from=relay-builder /home/user/substrate-relay /usr/local/bin/ +# we need bridges zombienet runner and tests +RUN mkdir -p /home/nonroot/bridges-polkadot-sdk +COPY ./artifacts/bridges-polkadot-sdk /home/nonroot/bridges-polkadot-sdk +# also prepare `generate_hex_encoded_call` for running +RUN set -eux; \ + cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ + npm install + +# check if executable works in this container +USER nonroot +RUN /usr/local/bin/polkadot --version +RUN /usr/local/bin/polkadot-parachain --version +RUN /usr/local/bin/substrate-relay --version + +# https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:{PORT}#/explorer +EXPOSE 9942 9910 8943 9945 9010 8945 diff --git a/docs/AUDIT.md b/docs/AUDIT.md new file mode 100644 index 0000000000000000000000000000000000000000..008600fd369865fc246466e8d1aff2cd858751d3 --- /dev/null +++ b/docs/AUDIT.md @@ -0,0 +1,22 @@ +# Audit + +Audits are conducted to ensure the absence of severe or exploitable bugs. Pull Requests are generally merged into the +`master` branch without audit. The `audited` tag is used to track the latest audited commit of the `master` branch. This +means that audits need to happen in order of being merged. +This is an optimistic approach that lets us develop with greater speed, while requiring (possibly) large refactors in +the failure case. + +Audits can be deferred if the logic is gated by an `experimental` feature or marked as "Not Production Ready" within the +first line of doc. Such changes should be queued manually before these warnings are removed. + +## General Guidelines for what to Audit + +There is no single one-fits-all rule. Generally we should audit important logic that could immediately be used on +production networks. If in doubt, ask in chat or in the Merge Request. + +## Requesting an Audit + +1. Add the PR to the project `Security Audit (PRs) - SRLabs` +2. Set status to Backlog +3. Assign priority, considering the universe of PRs currently in the backlog +4. Add the component diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000000000000000000000000000000000000..e73be2779a99426203e209da846f938c0f73cceb --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,168 @@ +# Release + +The outputs of a release are the `polkadot` and `polkadot-parachain` node binaries, the runtimes for Westend & Rococo +and their system parachains, and new crate versions published to `crates.io`. + +# Setup + +We have two branches: `master` and `stable`. `master` is the main development branch where normal Pull Requests are +opened. Developers need to mostly only care about this branch. +The `stable` branch contains a version of the code that is ready to be released. Its contents are always audited. +Merging to it is restricted to [Backports](#backports). + +# Versioning + +We are releasing multiple different things from this repository in one release, but we don't want to use the same +version for everything. Thus, in the following we explain the versioning story for the crates, node and Westend & +Rococo. To easily refer to a release, it shall be named by its date in the form `stableYYMMDD`. + +## Crate + +We try to follow [SemVer 2.0.0](https://semver.org/) as best as possible for versioning our crates. The definitions of +`major`, `minor` and `patch` version for Rust crates are slightly altered from their standard for pre `1.0.0` versions. +Quoting [rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html): + +>Initial development releases starting with “0.y.z” can treat changes in “y” as a major release, and “z” as a minor +release. “0.0.z” releases are always major changes. This is because Cargo uses the convention that only changes in the +left-most non-zero component are considered incompatible. + +SemVer requires a piece of software to first declare a public API. The public API of the Polkadot SDK +is hereby declared as the sum of all crates' public APIs. + +Inductively, the public API of our library crates is declared as all public items that are neither: +- Inside a `__private` module +- Documented as "unstable" or "experimental" in the first line of docs +- Bear `unstable` or `experimental` in their absolute path + +## Node + +The versioning of the Polkadot node is done most of the time by only incrementing the `minor` version. The `major` +version is only bumped for special releases and the `patch` can be used for an out of band release that fixes some +critical bug. The node version is not following SemVer. This means that the version doesn't express if there are any +breaking changes in the CLI interface or similar. The node version is declared in the +[`NODE_VERSION`](https://paritytech.github.io/polkadot-sdk/master/polkadot_node_primitives/constant.NODE_VERSION.html) +variable. + +## Westend & Rococo + +For the these networks, in addition to incrementing the `Cargo.toml` version we also increment the `spec_version` and +sometimes the `transaction_version`. The spec version is also following the node version. Its schema is: `M_mmm_ppp` and +for example `1_002_000` is the node release `1.2.0`. This versioning has no further meaning, and is only done to map +from an on chain `spec_version` easily to the release in this repository. +The Westend testnet will be updated to a new runtime every two weeks with the latest `nightly` release. + +# Backports + +**From `master` to `stable`** + +Backports in this direction can be anything that is audited and either a `minor` or a `patch` bump. [Security +fixes](#bug-and-security-fix) should be prioritized over additions or improvements. Crates that are declared as internal +API can also have `major` version bumps through backports. + +**From `stable` to `master`** + +Should not be needed since all changes first get merged into `master`. The `stable` branch can get out of sync and will +be synced with the [Clobbering](#clobbering) process. + +# Processes + +The following processes are necessary to actualize our releases. Each process has a *Cadence* on which it must execute +and a *Responsible* that is responsible for autonomously doing so and reporting back any error in the *RelEng: Polkadot +Release Coordination* Matrix channel. All processes should be automated as much as possible. + +## Crate Bumping + +Cadence: (possibly) each Pull Request. Responsible: Developer that opened the Pull Request. + +Following SemVer isn't easy, but there exists [a guide](https://doc.rust-lang.org/cargo/reference/semver.html) in the +Rust documentation that explains the small details on when to bump what. This process is supported with a CI check that +utilizes [`cargo-semver-checks`](https://github.com/obi1kenobi/cargo-semver-checks). + +### Steps + +1. Developer opens a Pull Request with changed crates against `master`. +1. They bump all changed crates according to SemVer. Note that this includes any crates that expose the changed + behaviour in their *public API* and also transitive dependencies for whom the same rule applies. + +## Stable Release + +Cadence: every two weeks. Responsible: Release Team. + +This process aims to release the `stable` branch as a *Stable* release every two weeks. + +### Steps + +1. Check and execute process [Clobbering](#clobbering), if needed. +2. Check if there were any changes since the last release and abort, if not. +3. Check out the latest commit of `stable`. +4. Update the `CHANGELOG.md` version, date and compile the content using the PrDoc files. +5. Open a Pull Request against `stable` for visibility of the release happening. +6. Internal QA from the release team can happen here. +7. Do a dry-run release to ensure that it *should* work. +8. Comment in the Pull Request that a *Stable* release will happen from the merged commit hash. +9. Release all changed crates to crates.io. +10. Create the release `stableYYYYMMDD` on GitHub. Note that the Fellowship has a streamlined process that combines the + two last steps. A similar approach should be taken here. + +## Nightly Release + +Cadence: every day at 00:00 UTC+1. Responsible: Release Team + +This process aims to release the `master` branch as a *Nightly* release. The process can start at 00:00 UTC+1 and should +automatically do the following steps. + +1. Check out the latest commit of branch `master`. +2. Compare this commit to the latest `nightly*` tag and abort if there are no changes detected. +3. Set the version of all crates that changed to `major.0.0-nightlyYYMMDD` where `major` is the last released `major` + version of that crate plus one. +4. Patch the dependencies of the changed crates to point to the newest version of the dependency. +5. Tag this commit as `nightlyYYMMDD`. +6. Do a dry-run release to ensure that it *should* work. +7. Push this tag (the commit will not belong to any branch). +8. Release all crates that had changed to crates.io. + +## Clobbering + +Cadence: every 6th release (~3 months). Responsible: Release Team + +This process aims to bring branch `stable` in sync with the latest audited commit of `master`. It is not done via a Pull +Request but rather by just copying files. It should be automated. +The following script is provided to do the clobbering. Note that it keeps the complete history of all past clobbering +processes. + +```bash +# Ensure we have the latest remote data +git fetch +# Switch to the stable branch +git checkout stable + +# Delete all tracked files in the working directory +git ls-files -z | xargs -0 rm -f +# Find and delete any empty directories +find . -type d -empty -delete + +# Get the last audited commit +AUDITED=$(git rev-parse --short=10 origin/audited) +# Grab the files from the commit +git checkout $AUDITED -- . + +# Stage, commit, and push the working directory which now matches 'audited' 1:1 +git add . +git commit -m "Clobbering with audited ($AUDITED)" +git push +``` + +## Bug and Security Fix + +Cadence: n.a. Responsible: Developer + +Describes how developers should merge bug and security fixes. + +### Steps + +1. Developer opens a Pull Request with a bug or security fix. +2. The Pull Request is marked as priority fix. +3. Audit happens with priority. +4. It is merged into `master`. +5. It is automatically back-ported to `stable`. +6. The fix will be released in the next *Stable* release. In urgent cases, a release can happen earlier. diff --git a/docs/CODE_OF_CONDUCT.md b/docs/contributor/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to docs/contributor/CODE_OF_CONDUCT.md diff --git a/docs/CONTRIBUTING.md b/docs/contributor/CONTRIBUTING.md similarity index 90% rename from docs/CONTRIBUTING.md rename to docs/contributor/CONTRIBUTING.md index 1e05755a9b8338c5769a48c5e8f477670eb92e25..96dc86e9780561e33e24fbc6f0346572d26598b7 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/contributor/CONTRIBUTING.md @@ -93,22 +93,12 @@ The reviewers are also responsible to check: All Pull Requests must contain proper title & description. -Some Pull Requests can be exempt of `prdoc` documentation, those -must be labelled with +Some Pull Requests can be exempt of `prdoc` documentation, those must be labelled with [`R0-silent`](https://github.com/paritytech/labels/blob/main/ruled_labels/specs_polkadot-sdk.yaml#L89-L91). Non "silent" PRs must come with documentation in the form of a `.prdoc` file. -A `.prdoc` documentation is made of a text file (YAML) named `/prdoc/pr_NNNN.prdoc` where `NNNN` is the PR number. -For convenience, those file can also contain a short description/title: `/prdoc/pr_NNNN_pr-foobar.prdoc`. -The CI automation checks for the presence and validity of a `prdoc` in the `/prdoc` folder. -Those files need to comply with a specific [schema](https://github.com/paritytech/prdoc/blob/master/schema_user.json). It -is highly recommended to [make your editor aware](https://github.com/paritytech/prdoc#schemas) of the schema as it is -self-described and will assist you in writing correct content. - -This schema is also embedded in the -[prdoc](https://github.com/paritytech/prdoc) utility that can also be used to generate and check the validity of a -`prdoc` locally. +See more about `prdoc` [here](./prdoc.md) ## Helping out @@ -153,8 +143,18 @@ Or if you have opened PR and you're member of `paritytech` - you can use command - `bot update-ui latest -v CMD_IMAGE=paritytech/ci-unified:bullseye-1.70.0-2023-05-23 --rust_version=1.70.0` - will run the tests for the specified rust version and specified image +## Feature Propagation + +We use [zepter](https://github.com/ggwpez/zepter) to enforce features are propagated between crates correctly. + ## Command Bot 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 new file mode 100644 index 0000000000000000000000000000000000000000..687c0a7cd7da040f40c120d00d75069db76ba27e --- /dev/null +++ b/docs/contributor/DEPRECATION_CHECKLIST.md @@ -0,0 +1,77 @@ +# Deprecation Checklist + +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. + +To start a deprecation process, a new issue with the label `T15-deprecation` needs to be created for correct tracking. +Then these are the actions to take: + +## Hard deprecate by adding a warning message + +The warning message shall include a removal month and year, which is suggested to be 6 months after the deprecation +notice is released. +This means that the code will be removed in a release within that month (or after, but never before). Please use this +template, doing so makes it easy to search through the code base: + +```rust +#[deprecated(note = "[DEPRECATED] will be removed after [DATE]. [ALTERNATIVE]")] +``` +`[ALTERNATIVE]` won't always be possible but offer it if it is. + +E.g. +```rust +#[deprecated(note = "`GenesisConfig` will be removed after December 2023. Use `RuntimeGenesisConfig` instead.")] +``` + +Some pieces of code cannot be labeled as deprecated, like [reexports](https://github.com/rust-lang/rust/issues/30827) +or [dispatchables](https://github.com/paritytech/polkadot-sdk/issues/182#issuecomment-1691684159), for instance. +In cases like that we can only make a visible enough comment, and make sure that we [announce the deprecation properly](#announce-the-deprecation-and-removal). + +## Remove usage of the deprecated code in the code base + +Just make sure that we are not using the deprecated code ourselves. +If you added the deprecation warning from the previous step, this can be done by making sure that warning is not shown +when building the code. + +## Update examples and tutorials + +Make sure that the rust docs are updated. +We also need [https://docs.substrate.io/](https://docs.substrate.io/) to be updated accordingly. The repo behind it is +[https://github.com/substrate-developer-hub/substrate-docs](https://github.com/substrate-developer-hub/substrate-docs). + +## Announce the deprecation and removal + +**At minimum they should be noted in the release log.** Please see how to document a PR [here](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#documentation). +There you can give instructions based on the audience and tell them what they need to do to upgrade the code. + +Some breaking changes have a bigger impact than others. When the impact is big the release note is not enough, though +it should still be the primary place for the notice. You can link back to the changelog files in other channels if you +want to announce it somewhere else. +Make sure you are as loud as you need to be for the magnitude of the breaking change. + +## Removal version is planned + +Depending on the removal date indicated in the deprecation warning in the [first step](#hard-deprecate-by-adding-a-warning-message), +the nature and the importance of the change, it might make sense to coordinate the release with other developers and +with the Release team. + +## Deprecated code is removed + +The deprecated code finally gets removed. +Don’t forget to [announce this accordingly](#announce-the-deprecation-and-removal). + +✅ In order to not forget any of these steps, consider using this template in your deprecation issue: + +```markdown +### Tasks + +- [ ] Deprecate code by adding a warning message +- [ ] Remove usage of the deprecated code in the code base +- [ ] Update examples and tutorials +- [ ] Announce code deprecation +- [ ] Plan removal version +- [ ] Announce code removal +- [ ] Remove deprecated code +``` diff --git a/docs/DOCUMENTATION_GUIDELINES.md b/docs/contributor/DOCUMENTATION_GUIDELINES.md similarity index 99% rename from docs/DOCUMENTATION_GUIDELINES.md rename to docs/contributor/DOCUMENTATION_GUIDELINES.md index 5d1164e8ca89f8eefea71ad96318b9347ca29c89..96811a2772d775f92ce4524416c295a689df1790 100644 --- a/docs/DOCUMENTATION_GUIDELINES.md +++ b/docs/contributor/DOCUMENTATION_GUIDELINES.md @@ -225,7 +225,7 @@ For the top-level pallet docs, consider the following template: //! //! ## Pallet API //! -//! //! //! See the [`pallet`] module for more information about the interfaces this pallet exposes, including its @@ -349,3 +349,7 @@ Consider the fact that, similar to dispatchables, these docs will be part of the and might be used by wallets and explorers. Specifically for `error`, explain why the error has happened, and what can be done in order to avoid it. + +## Documenting Changes/PR + +See [PRDoc](./prdoc.md). diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/contributor/PULL_REQUEST_TEMPLATE.md similarity index 96% rename from docs/PULL_REQUEST_TEMPLATE.md rename to docs/contributor/PULL_REQUEST_TEMPLATE.md index c93ac90c7e32e1c6c33d4409920eaa85a1aea814..79a036a235ad92a2cfceeba9b8fb66a44d163dfd 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/contributor/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ ✄ ----------------------------------------------------------------------------- Thank you for your Pull Request! 🙏 Please make sure it follows the contribution guidelines outlined in -[this document](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CONTRIBUTING.md) and fill +[this document](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and fill out the sections below. Once you're ready to submit your PR for review, please delete this section and leave only the text under the "Description" heading. diff --git a/docs/SECURITY.md b/docs/contributor/SECURITY.md similarity index 100% rename from docs/SECURITY.md rename to docs/contributor/SECURITY.md diff --git a/docs/STYLE_GUIDE.md b/docs/contributor/STYLE_GUIDE.md similarity index 84% rename from docs/STYLE_GUIDE.md rename to docs/contributor/STYLE_GUIDE.md index a9ac4a5910b3547c44612313bbc9745e02febf1d..400d9f477bc82b902b720d17f8e001be44f51fdc 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/contributor/STYLE_GUIDE.md @@ -2,9 +2,11 @@ title: Style Guide for Rust in the Polkadot-SDK --- -Where possible these styles are enforced by settings in `rustfmt.toml` so if you run `cargo fmt` +Where possible these styles are enforced by settings in `rustfmt.toml` so if you run `cargo +nightly fmt` then you will adhere to most of these style guidelines automatically. +To see exactly which nightly version is used, check our CI job logs. + # Formatting - Indent using tabs. @@ -150,31 +152,13 @@ let mut target_path = # Manifest Formatting -> **TLDR** -> You can use the CLI tool [Zepter](https://crates.io/crates/zepter) to -> format the files: `zepter format features` +We use [taplo](https://taplo.tamasfe.dev/) to enforce consistent TOML formatting. -Rust `Cargo.toml` files need to respect certain formatting rules. All entries -need to be alphabetically sorted. This makes it easier to read them and insert -new entries. The exhaustive list of rules is enforced by the CI. The general -format looks like this: +You can install it with `cargo install taplo-cli` and format your code with `taplo format --config .config/taplo.toml`. -- The feature is written as a single line if it fits within 80 chars: +See the config file for the exact rules. -```toml -[features] -default = [ "std" ] -``` +You may find useful -- Otherwise the feature is broken down into multiple lines with one entry per - line. Each line is padded with one tab and no trailing spaces but a trailing - comma. - -```toml -[features] -default = [ - "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong", - # Comments go here as well ;) - "std", -] -``` +- [Taplo VSCode extension](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) +- For NeoVim, [taplo is available with Mason](https://github.com/williamboman/mason-lspconfig.nvim#available-lsp-servers) diff --git a/docs/container.md b/docs/contributor/container.md similarity index 100% rename from docs/container.md rename to docs/contributor/container.md diff --git a/docs/docker.md b/docs/contributor/docker.md similarity index 100% rename from docs/docker.md rename to docs/contributor/docker.md diff --git a/docs/markdown_linting.md b/docs/contributor/markdown_linting.md similarity index 100% rename from docs/markdown_linting.md rename to docs/contributor/markdown_linting.md diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md new file mode 100644 index 0000000000000000000000000000000000000000..af0ede5107a6cb1ea31d8b81fe16b06553ef64ac --- /dev/null +++ b/docs/contributor/prdoc.md @@ -0,0 +1,71 @@ +# PRDoc + +## Intro + +With the merge of [PR #1946](https://github.com/paritytech/polkadot-sdk/pull/1946), a new method for +documenting changes has been introduced: `prdoc`. The [prdoc repository](https://github.com/paritytech/prdoc) +contains more documentation and tooling. + +The current document describes how to quickly get started authoring `PRDoc` files. + +## Requirements + +When creating a PR, the author needs to decides with the `R0` label whether the change (PR) should +appear in the release notes or not. + +Labelling a PR with `R0` means that no `PRDoc` is required. + +A PR without the `R0` label **does** require a valid `PRDoc` file to be introduced in the PR. + +## PRDoc how-to + +A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). + +For significant changes, a `.prdoc` file is mandatory and the file must meet the following +requirements: +- file named `pr_NNNN.prdoc` where `NNNN` is the PR number. + For convenience, those file can also contain a short description: `pr_NNNN_foobar.prdoc`. +- located under the [`prdoc` folder](https://github.com/paritytech/polkadot-sdk/tree/master/prdoc) of the repository +- compliant with the [JSON schema](https://json-schema.org/) defined in `prdoc/schema_user.json` + +Those requirements can be fulfilled manually without any tooling but a text editor. + +## Tooling + +Users might find the following helpers convenient: +- Setup VSCode to be aware of the prdoc schema: see [using VSCode](https://github.com/paritytech/prdoc#using-vscode) +- Using the `prdoc` cli to: + - generate a `PRDoc` file from a [template defined in the Polkadot SDK + repo](https://github.com/paritytech/polkadot-sdk/blob/master/prdoc/.template.prdoc) simply providing a PR number + - check the validity of one or more `PRDoc` files + +## `prdoc` cli usage + +The `prdoc` cli documentation can be found at https://github.com/paritytech/prdoc#prdoc + +tldr: +- `prdoc generate ` +- `prdoc check -n ` + +where is the PR number. + +## Pick an audience + +While describing a PR, the author needs to consider which audience(s) need to be addressed. +The list of valid audiences is described and documented in the JSON schema as follow: + +- `Node Dev`: Those who build around the client side code. Alternative client builders, SMOLDOT, those who consume RPCs. + These are people who are oblivious to the runtime changes. They only care about the meta-protocol, not the protocol + itself. + +- `Runtime Dev`: All of those who rely on the runtime. A parachain team that is using a pallet. A DApp that is using a + pallet. These are people who care about the protocol (WASM), not the meta-protocol (client). + +- `Node Operator`: Those who don't write any code and only run code. + +- `Runtime User`: Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain. + +## Tips + +The PRDoc schema is defined in each repo and usually is quite restrictive. +You cannot simply add a new property to a `PRDoc` file unless the Schema allows it. diff --git a/docs/mermaid/IA.mmd b/docs/mermaid/IA.mmd new file mode 100644 index 0000000000000000000000000000000000000000..4eb50bcf96a8932de3fa90748fcfeb3ca7f02a5f --- /dev/null +++ b/docs/mermaid/IA.mmd @@ -0,0 +1,14 @@ +flowchart + parity[paritytech.github.io] --> devhub[polkadot_sdk_docs] + + devhub --> polkadot_sdk + devhub --> reference_docs + devhub --> guides + + polkadot_sdk --> substrate + polkadot_sdk --> frame + polkadot_sdk --> cumulus + polkadot_sdk --> polkadot + polkadot_sdk --> xcm + + diff --git a/docs/mermaid/extrinsics.mmd b/docs/mermaid/extrinsics.mmd new file mode 100644 index 0000000000000000000000000000000000000000..4afd4ab8f755d818f9bb0ccf6488610dcc6638f9 --- /dev/null +++ b/docs/mermaid/extrinsics.mmd @@ -0,0 +1,5 @@ +flowchart TD + E(Extrinsic) ---> I(Inherent); + E --> T(Transaction) + T --> ST("Signed (aka. Transaction)") + T --> UT(Unsigned) diff --git a/docs/mermaid/outer_runtime_types.mmd b/docs/mermaid/outer_runtime_types.mmd new file mode 100644 index 0000000000000000000000000000000000000000..c909df16af1ffbb697e3d363634fa218005a9c32 --- /dev/null +++ b/docs/mermaid/outer_runtime_types.mmd @@ -0,0 +1,3 @@ +flowchart LR + RuntimeCall --"TryInto"--> PalletCall + PalletCall --"Into"--> RuntimeCall diff --git a/docs/mermaid/polkadot_sdk_parachain.mmd b/docs/mermaid/polkadot_sdk_parachain.mmd new file mode 100644 index 0000000000000000000000000000000000000000..4cee54ba3f45e5dc08c0e120a742c496a246aa93 --- /dev/null +++ b/docs/mermaid/polkadot_sdk_parachain.mmd @@ -0,0 +1,11 @@ +flowchart LR + subgraph Parachain[A Polkadot Parachain] + ParachainNode[Parachain Node] + ParachainRuntime[Parachain Runtime] + end + + FRAME -.-> ParachainRuntime + Substrate[Substrate Node Libraries] -.-> ParachainNode + + CumulusC[Cumulus Node Libraries] -.-> ParachainNode + CumulusR[Cumulus Runtime Libraries] -.-> ParachainRuntime diff --git a/docs/mermaid/polkadot_sdk_polkadot.mmd b/docs/mermaid/polkadot_sdk_polkadot.mmd new file mode 100644 index 0000000000000000000000000000000000000000..3326cc59383926779e044f0e3c8a4837306b4f98 --- /dev/null +++ b/docs/mermaid/polkadot_sdk_polkadot.mmd @@ -0,0 +1,10 @@ +flowchart LR + + subgraph Polkadot[The Polkadot Relay Chain] + PolkadotNode[Polkadot Node] + PolkadotRuntime[Polkadot Runtime] + end + + FRAME -.-> PolkadotRuntime + Substrate[Substrate Node Libraries] -.-> PolkadotNode + diff --git a/docs/mermaid/polkadot_sdk_substrate.mmd b/docs/mermaid/polkadot_sdk_substrate.mmd new file mode 100644 index 0000000000000000000000000000000000000000..dfaf20d241f81b006a2c4979ebb7c3a0811bd208 --- /dev/null +++ b/docs/mermaid/polkadot_sdk_substrate.mmd @@ -0,0 +1,8 @@ +flowchart LR + subgraph SubstrateChain[A Substrate-based blockchain] + Node + Runtime + end + + FRAME -.-> Runtime + Substrate[Substrate Node Libraries] -.-> Node diff --git a/docs/mermaid/state.mmd b/docs/mermaid/state.mmd new file mode 100644 index 0000000000000000000000000000000000000000..c72ecbfd1568b2ee753bf2e58a9aebcad10af164 --- /dev/null +++ b/docs/mermaid/state.mmd @@ -0,0 +1,16 @@ +flowchart TB + subgraph Node[Node's View Of The State 🙈] + direction LR + 0x1234 --> 0x2345 + 0x3456 --> 0x4567 + 0x5678 --> 0x6789 + :code --> code[wasm code] + end + + subgraph Runtime[Runtime's View Of The State 🙉] + direction LR + ab[alice's balance] --> abv[known value] + bb[bob's balance] --> bbv[known value] + cb[charlie's balance] --> cbv[known value] + c2[:code] --> c22[wasm code] + end diff --git a/docs/mermaid/stf.mmd b/docs/mermaid/stf.mmd new file mode 100644 index 0000000000000000000000000000000000000000..dd6c7c36de66fe7d2dbfd77f23a1362878c7a7e5 --- /dev/null +++ b/docs/mermaid/stf.mmd @@ -0,0 +1,21 @@ +flowchart LR + %%{init: {'flowchart' : {'curve' : 'linear'}}}%% + subgraph BData[Blockchain Database] + direction LR + BN[Block N] -.-> BN1[Block N+1] + end + + subgraph SData[State Database] + direction LR + SN[State N] -.-> SN1[State N+1] -.-> SN2[State N+2] + end + + BN --> STFN[STF] + SN --> STFN[STF] + STFN[STF] --> SN1 + + BN1 --> STFN1[STF] + SN1 --> STFN1[STF] + STFN1[STF] --> SN2 + + diff --git a/docs/mermaid/stf_simple.mmd b/docs/mermaid/stf_simple.mmd new file mode 100644 index 0000000000000000000000000000000000000000..5db20cf6156c8c6b9817eb07de07e43d6b7a1abd --- /dev/null +++ b/docs/mermaid/stf_simple.mmd @@ -0,0 +1,4 @@ +flowchart LR + B[Block] --> STF + S[State] --> STF + STF --> NS[New State] diff --git a/docs/mermaid/substrate_client_runtime.mmd b/docs/mermaid/substrate_client_runtime.mmd new file mode 100644 index 0000000000000000000000000000000000000000..caab2b6230283f866730fa389efd2934a970dadf --- /dev/null +++ b/docs/mermaid/substrate_client_runtime.mmd @@ -0,0 +1,12 @@ +graph TB +subgraph Substrate + direction LR + subgraph Node + end + + subgraph Runtime + end + + Node --runtime-api--> Runtime + Runtime --host-functions--> Node +end diff --git a/docs/mermaid/substrate_dev.mmd b/docs/mermaid/substrate_dev.mmd new file mode 100644 index 0000000000000000000000000000000000000000..fc331ce311fea6519957f88deb4a289974d2a98c --- /dev/null +++ b/docs/mermaid/substrate_dev.mmd @@ -0,0 +1,2 @@ +flowchart LR + T[Using a Template] --> P[Writing Your Own FRAME-Based Pallet] --> C[Custom Node] diff --git a/docs/mermaid/substrate_simple.mmd b/docs/mermaid/substrate_simple.mmd new file mode 100644 index 0000000000000000000000000000000000000000..a752eaba625fb1d1a27a91413266bb6fd30c75c0 --- /dev/null +++ b/docs/mermaid/substrate_simple.mmd @@ -0,0 +1,8 @@ +graph TB +subgraph Substrate + direction LR + subgraph Node + end + subgraph Runtime + end +end diff --git a/docs/mermaid/substrate_with_frame.mmd b/docs/mermaid/substrate_with_frame.mmd new file mode 100644 index 0000000000000000000000000000000000000000..173c1757b955f3157d8a38a12b03d0e3157c34e5 --- /dev/null +++ b/docs/mermaid/substrate_with_frame.mmd @@ -0,0 +1,20 @@ +graph TB +subgraph Substrate + direction LR + subgraph Node + Database + Networking + Consensus + end + subgraph Runtime + subgraph FRAME + direction LR + Governance + Currency + Staking + Identity + end + end + Node --runtime-api--> Runtime + Runtime --host-functions--> Node +end diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..3f40a950c2867e1c2c7c9d7d10e6b33e6511692c --- /dev/null +++ b/docs/sdk/Cargo.toml @@ -0,0 +1,88 @@ +[package] +name = "polkadot-sdk-docs" +description = "The one stop shop for developers of the polakdot-sdk" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "paritytech.github.io" +repository.workspace = true +authors.workspace = true +edition.workspace = true +# This crate is not publish-able to crates.io for now because of docify. +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 } +scale-info = { version = "2.6.0", default-features = false } +frame = { path = "../../substrate/frame", features = [ + "experimental", + "runtime", +] } +pallet-examples = { path = "../../substrate/frame/examples" } +pallet-default-config-example = { path = "../../substrate/frame/examples/default-config" } +pallet-example-offchain-worker = { path = "../../substrate/frame/examples/offchain-worker" } + +# How we build docs in rust-docs +simple-mermaid = "0.1.1" +docify = "0.2.7" + +# Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. +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" } +subkey = { path = "../../substrate/bin/utils/subkey" } +frame-system = { path = "../../substrate/frame/system", default-features = false } +frame-support = { path = "../../substrate/frame/support", default-features = false } +frame-executive = { path = "../../substrate/frame/executive", default-features = false } +pallet-example-single-block-migrations = { path = "../../substrate/frame/examples/single-block-migrations" } + +# Substrate Client +sc-network = { path = "../../substrate/client/network" } +sc-rpc-api = { path = "../../substrate/client/rpc-api" } +sc-rpc = { path = "../../substrate/client/rpc" } +sc-client-db = { path = "../../substrate/client/db" } +sc-cli = { path = "../../substrate/client/cli" } +sc-consensus-aura = { path = "../../substrate/client/consensus/aura" } +sc-consensus-babe = { path = "../../substrate/client/consensus/babe" } +sc-consensus-grandpa = { path = "../../substrate/client/consensus/grandpa" } +sc-consensus-beefy = { path = "../../substrate/client/consensus/beefy" } +sc-consensus-manual-seal = { path = "../../substrate/client/consensus/manual-seal" } +sc-consensus-pow = { path = "../../substrate/client/consensus/pow" } + +substrate-wasm-builder = { path = "../../substrate/utils/wasm-builder" } + +# Cumulus +cumulus-pallet-aura-ext = { path = "../../cumulus/pallets/aura-ext" } +cumulus-pallet-parachain-system = { path = "../../cumulus/pallets/parachain-system", features = [ + "parameterized-consensus-hook", +] } +parachain-info = { package = "staging-parachain-info", path = "../../cumulus/parachains/pallets/parachain-info" } +pallet-aura = { path = "../../substrate/frame/aura", default-features = false } + +# Pallets and FRAME internals +pallet-timestamp = { path = "../../substrate/frame/timestamp" } +pallet-balances = { path = "../../substrate/frame/balances" } +pallet-assets = { path = "../../substrate/frame/assets" } +pallet-transaction-payment = { path = "../../substrate/frame/transaction-payment" } +pallet-utility = { path = "../../substrate/frame/utility" } +pallet-multisig = { path = "../../substrate/frame/multisig" } +pallet-proxy = { path = "../../substrate/frame/proxy" } +pallet-authorship = { path = "../../substrate/frame/authorship" } +pallet-collective = { path = "../../substrate/frame/collective" } +pallet-democracy = { path = "../../substrate/frame/democracy" } +pallet-scheduler = { path = "../../substrate/frame/scheduler" } + +# Primitives +sp-io = { path = "../../substrate/primitives/io" } +sp-api = { path = "../../substrate/primitives/api" } +sp-core = { path = "../../substrate/primitives/core" } +sp-keyring = { path = "../../substrate/primitives/keyring" } +sp-runtime = { path = "../../substrate/primitives/runtime" } +sp-offchain = { path = "../../substrate/primitives/offchain" } +sp-version = { path = "../../substrate/primitives/version" } + +# XCM +xcm = { package = "staging-xcm", path = "../../polkadot/xcm" } diff --git a/docs/sdk/headers/header.html b/docs/sdk/headers/header.html new file mode 100644 index 0000000000000000000000000000000000000000..e28458c4ccc791d9a72613ffb530b685828ea828 --- /dev/null +++ b/docs/sdk/headers/header.html @@ -0,0 +1,144 @@ + + + diff --git a/docs/sdk/headers/theme.css b/docs/sdk/headers/theme.css new file mode 100644 index 0000000000000000000000000000000000000000..a488e15c36b70ee76b14f429434daf0717bc0320 --- /dev/null +++ b/docs/sdk/headers/theme.css @@ -0,0 +1,17 @@ +:root { + --polkadot-pink: #E6007A; + --polkadot-green: #56F39A; + --polkadot-lime: #D3FF33; + --polkadot-cyan: #00B2FF; + --polkadot-purple: #552BBF; +} + +body.sdk-docs { + nav.sidebar>div.sidebar-crate>a>img { + width: 190px; + } + + nav.sidebar { + flex: 0 0 250px; + } +} diff --git a/docs/sdk/src/guides/changing_consensus.rs b/docs/sdk/src/guides/changing_consensus.rs new file mode 100644 index 0000000000000000000000000000000000000000..7ba742f10723d22a0ae58b48b3fa35770b75b331 --- /dev/null +++ b/docs/sdk/src/guides/changing_consensus.rs @@ -0,0 +1 @@ +//! # Changing Consensus diff --git a/docs/sdk/src/guides/cumulus_enabled_parachain.rs b/docs/sdk/src/guides/cumulus_enabled_parachain.rs new file mode 100644 index 0000000000000000000000000000000000000000..fafd97feb8291b34947e0a779fc9dda754dec3f1 --- /dev/null +++ b/docs/sdk/src/guides/cumulus_enabled_parachain.rs @@ -0,0 +1 @@ +//! # Cumulus Enabled Parachain diff --git a/docs/sdk/src/guides/mod.rs b/docs/sdk/src/guides/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3120f25331099263087d3804d324e71555c0813d --- /dev/null +++ b/docs/sdk/src/guides/mod.rs @@ -0,0 +1,25 @@ +//! # Polkadot SDK Docs Guides +//! +//! This crate contains a collection of guides that are foundational to the developers of +//! 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; + +/// Writing your first real [runtime](`crate::reference_docs::wasm_meta_protocol`), and successfully +/// compiling it to [WASM](crate::polkadot_sdk::substrate#wasm-build). +pub mod your_first_runtime; + +/// Running the given runtime with a node. No specific consensus mechanism is used at this stage. +pub mod your_first_node; + +/// How to change the consensus engine of both the node and the runtime. +pub mod changing_consensus; + +/// How to enhance a given runtime and node to be cumulus-enabled, run it as a parachain and connect +/// it to a relay-chain. +pub mod cumulus_enabled_parachain; + +/// How to make a given runtime XCM-enabled, capable of sending messages (`Transact`) between itself +/// and the relay chain to which it is connected. +pub mod xcm_enabled_parachain; diff --git a/docs/sdk/src/guides/xcm_enabled_parachain.rs b/docs/sdk/src/guides/xcm_enabled_parachain.rs new file mode 100644 index 0000000000000000000000000000000000000000..4518cab934215c8de7d6ebdedabb8f4ba2670e2b --- /dev/null +++ b/docs/sdk/src/guides/xcm_enabled_parachain.rs @@ -0,0 +1 @@ +//! # XCM Enabled Parachain diff --git a/docs/sdk/src/guides/your_first_node.rs b/docs/sdk/src/guides/your_first_node.rs new file mode 100644 index 0000000000000000000000000000000000000000..d12349c990632deb03bf24006ffc63b493347715 --- /dev/null +++ b/docs/sdk/src/guides/your_first_node.rs @@ -0,0 +1 @@ +//! # Your first Node diff --git a/docs/sdk/src/guides/your_first_pallet/mod.rs b/docs/sdk/src/guides/your_first_pallet/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c633c0a69edea39c925fa63dd8fbad495d632d78 --- /dev/null +++ b/docs/sdk/src/guides/your_first_pallet/mod.rs @@ -0,0 +1,757 @@ +//! # Currency Pallet +//! +//! By the end of this guide, you will write a small FRAME pallet (see +//! [`crate::polkadot_sdk::frame_runtime`]) that is capable of handling a simple crypto-currency. +//! This pallet will: +//! +//! 1. Allow anyone to mint new tokens into accounts (which is obviously not a great idea for a real +//! system). +//! 2. Allow any user that owns tokens to transfer them to others. +//! 3. Track the total issuance of all tokens at all times. +//! +//! > 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 advanced FRAME related topics are discussed in [`crate::reference_docs`]. +//! +//! ## Topics Covered +//! +//! The following FRAME topics are covered in this guide: +//! +//! - [Storage](frame::pallet_macros::storage) +//! - [Call](frame::pallet_macros::call) +//! - [Event](frame::pallet_macros::event) +//! - [Error](frame::pallet_macros::error) +//! - Basics of testing a pallet +//! - [Constructing a runtime](frame::runtime::prelude::construct_runtime) +//! +//! ## Writing Your First Pallet +//! +//! You should have studied the following modules as a prelude to this guide: +//! +//! - [`crate::reference_docs::blockchain_state_machines`] +//! - [`crate::reference_docs::trait_based_programming`] +//! - [`crate::polkadot_sdk::frame_runtime`] +//! +//! ### Shell Pallet +//! +//! Consider the following as a "shell pallet". We continue building the rest of this pallet based +//! on this template. +//! +//! [`pallet::config`](frame::pallet_macros::config) and +//! [`pallet::pallet`](frame::pallet_macros::pallet) are both mandatory parts of any pallet. Refer +//! to the documentation of each to get an overview of what they do. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", shell_pallet)] +//! +//! ### Storage +//! +//! First, we will need to create two onchain storage declarations. +//! +//! 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 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, +//! is as follows: +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", TotalIssuance)] +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Balances)] +//! +//! ### Dispatchables +//! +//! Next, we will define the dispatchable functions. As per [`frame::pallet_macros::call`], these +//! will be defined as normal `fn`s attached to `struct Pallet`. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_pallet)] +//! +//! The logic of the functions is self-explanatory. Instead, we will focus on the FRAME-related +//! details: +//! +//! - Where do `T::AccountId` and `T::RuntimeOrigin` come from? These are both defined in +//! [`frame::prelude::frame_system::Config`], therefore we can access them in `T`. +//! - What is `ensure_signed`, and what does it do with the aforementioned `T::RuntimeOrigin`? This +//! is outside the scope of this guide, and you can learn more about it in the origin reference +//! document ([`crate::reference_docs::frame_origin`]). For now, you should only know the +//! signature of the function: it takes a generic `T::RuntimeOrigin` and returns a +//! `Result`. So by the end of this function call, we know that this dispatchable +//! was signed by `who`. +#![doc = docify::embed!("../../substrate/frame/system/src/lib.rs", ensure_signed)] +//! +//! +//! - Where does `mutate`, `get` and `insert` and other storage APIs come from? All of them are +//! explained in the corresponding `type`, for example, for `Balances::::insert`, you can look +//! into [`frame::prelude::StorageMap::insert`]. +//! +//! - The return type of all dispatchable functions is [`frame::prelude::DispatchResult`]: +#![doc = docify::embed!("../../substrate/frame/support/src/dispatch.rs", DispatchResult)] +//! +//! Which is more or less a normal Rust `Result`, with a custom [`frame::prelude::DispatchError`] as +//! the `Err` variant. We won't cover this error in detail here, but importantly you should know +//! that there is an `impl From<&'static string> for DispatchError` provided (see +//! [here](`frame::prelude::DispatchError#impl-From<%26'static+str>-for-DispatchError`)). Therefore, +//! we can use basic string literals as our error type and `.into()` them into `DispatchError`. +//! +//! - Why are all `get` and `mutate` functions returning an `Option`? This is the default behavior +//! of FRAME storage APIs. You can learn more about how to override this by looking into +//! [`frame::pallet_macros::storage`], and +//! [`frame::prelude::ValueQuery`]/[`frame::prelude::OptionQuery`] +//! +//! ### Improving Errors +//! +//! How we handle error in the above snippets is fairly rudimentary. Let's look at how this can be +//! improved. First, we can use [`frame::prelude::ensure`] to express the error slightly better. +//! This macro will call `.into()` under the hood. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_better)] +//! +//! Moreover, you will learn in the [Safe Defensive Programming +//! section](crate::reference_docs::safe_defensive_programming) that it is always recommended to use +//! safe arithmetic operations in your runtime. By using [`frame::traits::CheckedSub`], we can not +//! only take a step in that direction, but also improve the error handing and make it slightly more +//! ergonomic. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_better_checked)] +//! +//! This is more or less all the logic that there is this basic currency pallet! +//! +//! ### Your First (Test) Runtime +//! +//! Next, we create a "test runtime" in order to test our pallet. Recall from +//! [`crate::polkadot_sdk::frame_runtime`] that a runtime is a collection of pallets, expressed +//! through [`frame::runtime::prelude::construct_runtime`]. All runtimes also have to include +//! [`frame::prelude::frame_system`]. So we expect to see a runtime with two pallet, `frame_system` +//! and the one we just wrote. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", runtime)] +//! +//! > [`frame::pallet_macros::derive_impl`] is a FRAME feature that enables developers to have +//! > defaults for associated types. +//! +//! Recall that within our pallet, (almost) all blocks of code are generic over ``. And, +//! because `trait Config: frame_system::Config`, we can get access to all items in `Config` (or +//! `frame_system::Config`) using `T::NameOfItem`. This is all within the boundaries of how +//! Rust traits and generics work. If unfamiliar with this pattern, read +//! [`crate::reference_docs::trait_based_programming`] before going further. +//! +//! Crucially, a typical FRAME runtime contains a `struct Runtime`. The main role of this `struct` +//! is to implement the `trait Config` of all pallets. That is, anywhere within your pallet code +//! where you see `` (read: *"some type `T` that implements `Config`"*), in the runtime, +//! it can be replaced with ``, because `Runtime` implements `Config` of all pallets, as we +//! see above. +//! +//! Another way to think about this is that within a pallet, a lot of types are "unknown" and, we +//! only know that they will be provided at some later point. For example, when you write +//! `T::AccountId` (which is short for `::AccountId`) in your pallet, +//! you are in fact saying "*Some type `AccountId` that will be known later*". That "later" is in +//! fact when you specify these types when you implement all `Config` traits for `Runtime`. +//! +//! As you see above, `frame_system::Config` is setting the `AccountId` to `u64`. Of course, a real +//! runtime will not use this type, and instead reside to a proper type like a 32-byte standard +//! public key. This is a HUGE benefit that FRAME developers can tap into: through the framework +//! being so generic, different types can always be customized to simple things when needed. +//! +//! > Imagine how hard it would have been if all tests had to use a real 32-byte account id, as +//! > opposed to just a u64 number 🙈. +//! +//! ### Your First Test +//! +//! The above is all you need to execute the dispatchables of your pallet. The last thing you need +//! to learn is that all of your pallet testing code should be wrapped in +//! [`frame::testing_prelude::TestState`]. This is a type that provides access to an in-memory state +//! to be used in our tests. +#![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 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(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. +//! +//! ## Improving the Currency Pallet +//! +//! ### Better Test Setup +//! +//! Idiomatic FRAME pallets often use Builder pattern to define their initial state. +//! +//! > The Polkadot Blockchain Academy's Rust entrance exam has a +//! > [section](https://github.com/Polkadot-Blockchain-Academy/pba-qualifier-exam/blob/main/src/m_builder.rs) +//! > on this that you can use to learn the Builder Pattern. +//! +//! Let's see how we can implement a better test setup using this pattern. First, we define a +//! `struct StateBuilder`. +#![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![(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 +//! properties. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_state_builder_add)] +//! +//! Finally --the useful part-- we write our own custom `build_and_execute` function on +//! this type. This function will do multiple things: +//! +//! 1. It would consume `self` to produce our `TestState` based on the properties that we attached +//! to `self`. +//! 2. It would execute any test function that we pass in as closure. +//! 3. A nifty trick, this allows our test setup to have some code that is executed both before and +//! after each test. For example, in this test, we do some additional checking about the +//! correctness of the `TotalIssuance`. We leave it up to you as an exercise to learn why the +//! assertion should always hold, and how it is checked. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", impl_state_builder_build)] +//! +//! We can write tests that specifically check the initial state, and making sure our `StateBuilder` +//! is working exactly as intended. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", state_builder_works)] +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", state_builder_add_balance)] +//! +//! ### More Tests +//! +//! Now that we have a more ergonomic test setup, let's see how a well written test for transfer and +//! mint would look like. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_works)] +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", mint_works)] +//! +//! It is always a good idea to build a mental model where you write *at least* one test for each +//! "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 the `InsufficientBalance` error. +//! +//! ### Event and Error +//! +//! Our pallet is mainly missing two parts that are common in most FRAME pallets: Events, and +//! 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. 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 extra +//! [`#[frame::event]`](frame::pallet_macros::event) and +//! [`#[frame::error]`](frame::pallet_macros::error) attributes attached. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Event)] +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Error)] +//! +//! One slightly custom part of this is the [`#[pallet::generate_deposit(pub(super) fn +//! deposit_event)]`](frame::pallet_macros::generate_deposit) part. Without going into too +//! much detail, in order for a pallet to emit events to the rest of the system, it needs to do two +//! things: +//! +//! 1. Declare a type in its `Config` that refers to the overarching event type of the runtime. In +//! short, by doing this, the pallet is expressing an important bound: `type RuntimeEvent: +//! From>`. Read: a `RuntimeEvent` exists, and it can be created from the local `enum +//! Event` of this pallet. This enables the pallet to convert its `Event` into `RuntimeEvent`, and +//! store it where needed. +//! +//! 2. But, doing this conversion and storing is too much to expect each pallet to define. FRAME +//! provides a default way of storing events, and this is what +//! [`pallet::generate_deposit`](frame::pallet_macros::generate_deposit) is doing. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", config_v2)] +//! +//! > These `Runtime*` types are better explained in +//! > [`crate::reference_docs::frame_runtime_types`]. +//! +//! Then, we can rewrite the `transfer` dispatchable as such: +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_v2)] +//! +//! Then, notice how now we would need to provide this `type RuntimeEvent` in our test runtime +//! setup. +#![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", runtime_v2)] +//! +//! In this snippet, the actual `RuntimeEvent` type (right hand side of `type RuntimeEvent = +//! RuntimeEvent`) is generated by +//! [`construct_runtime`](frame::runtime::prelude::construct_runtime). An interesting way to inspect +//! this type is to see its definition in rust-docs: +//! [`crate::guides::your_first_pallet::pallet_v2::tests::runtime_v2::RuntimeEvent`]. +//! +//! +//! ## What Next? +//! +//! The following topics where used in this guide, but not covered in depth. It is suggested to +//! study them subsequently: +//! +//! - [`crate::reference_docs::safe_defensive_programming`]. +//! - [`crate::reference_docs::frame_origin`]. +//! - [`crate::reference_docs::frame_runtime_types`]. +//! - The pallet we wrote in this guide was using `dev_mode`, learn more in +//! [`frame::pallet_macros::config`]. +//! - Learn more about the individual pallet items/macros, such as event and errors and call, in +//! [`frame::pallet_macros`]. + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod shell_pallet { + use frame::prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); +} + +#[frame::pallet(dev_mode)] +pub mod pallet { + use frame::prelude::*; + + #[docify::export] + pub type Balance = u128; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export] + /// Single storage item, of type `Balance`. + #[pallet::storage] + pub type TotalIssuance = StorageValue<_, Balance>; + + #[docify::export] + /// A mapping from `T::AccountId` to `Balance` + #[pallet::storage] + pub type Balances = StorageMap<_, _, T::AccountId, Balance>; + + #[docify::export(impl_pallet)] + #[pallet::call] + impl Pallet { + /// An unsafe mint that can be called by anyone. Not a great idea. + pub fn mint_unsafe( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + // ensure that this is a signed account, but we don't really check `_anyone`. + let _anyone = ensure_signed(origin)?; + + // update the balances map. Notice how all `` remains as ``. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + // update total issuance. + TotalIssuance::::mutate(|t| *t = Some(t.unwrap_or(0) + amount)); + + Ok(()) + } + + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // ensure sender has enough balance, and if so, calculate what is left after `amount`. + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + if sender_balance < amount { + return Err("InsufficientBalance".into()) + } + let reminder = sender_balance - amount; + + // update sender and dest balances. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + Balances::::insert(&sender, reminder); + + Ok(()) + } + } + + #[allow(unused)] + impl Pallet { + #[docify::export] + pub fn transfer_better( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + ensure!(sender_balance >= amount, "InsufficientBalance"); + let reminder = sender_balance - amount; + + // .. snip + Ok(()) + } + + #[docify::export] + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer_better_checked( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + let reminder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?; + + // .. snip + Ok(()) + } + } + + #[cfg(any(test, doc))] + 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 { + use super::*; + // we need to reference our `mod pallet` as an identifier to pass to + // `construct_runtime`. + use crate::guides::your_first_pallet::pallet as pallet_currency; + + construct_runtime!( + pub enum Runtime { + // ---^^^^^^ This is where `enum Runtime` is defined. + System: frame_system, + Currency: pallet_currency, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + // within pallet we just said `::AccountId`, now we + // finally specified it. + type AccountId = u64; + } + + // our simple pallet has nothing to be configured. + impl pallet_currency::Config for Runtime {} + } + + pub(crate) use runtime::*; + + #[allow(unused)] + #[docify::export] + fn new_test_state_basic() -> TestState { + let mut state = TestState::new_empty(); + let accounts = vec![(ALICE, 100), (BOB, 100)]; + state.execute_with(|| { + for (who, amount) in &accounts { + Balances::::insert(who, amount); + TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); + } + }); + + state + } + + #[docify::export] + pub(crate) struct StateBuilder { + balances: Vec<(::AccountId, Balance)>, + } + + #[docify::export(default_state_builder)] + impl Default for StateBuilder { + fn default() -> Self { + Self { balances: vec![(ALICE, 100), (BOB, 100)] } + } + } + + #[docify::export(impl_state_builder_add)] + impl StateBuilder { + fn add_balance( + mut self, + who: ::AccountId, + amount: Balance, + ) -> Self { + self.balances.push((who, amount)); + self + } + } + + #[docify::export(impl_state_builder_build)] + impl StateBuilder { + pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = TestState::new_empty(); + ext.execute_with(|| { + for (who, amount) in &self.balances { + Balances::::insert(who, amount); + TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); + } + }); + + ext.execute_with(test); + + // assertions that must always hold + ext.execute_with(|| { + assert_eq!( + Balances::::iter().map(|(_, x)| x).sum::(), + TotalIssuance::::get().unwrap_or_default() + ); + }) + } + } + + #[docify::export] + #[test] + fn first_test() { + TestState::new_empty().execute_with(|| { + // We expect Alice's account to have no funds. + assert_eq!(Balances::::get(&ALICE), None); + assert_eq!(TotalIssuance::::get(), None); + + // 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(&ALICE), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(100)); + }) + } + + #[docify::export] + #[test] + fn state_builder_works() { + StateBuilder::default().build_and_execute(|| { + 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)); + }); + } + + #[docify::export] + #[test] + fn state_builder_add_balance() { + StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| { + assert_eq!(Balances::::get(&CHARLIE), Some(42)); + assert_eq!(TotalIssuance::::get(), Some(242)); + }) + } + + #[test] + #[should_panic] + fn state_builder_duplicate_genesis_fails() { + StateBuilder::default() + .add_balance(CHARLIE, 42) + .add_balance(CHARLIE, 43) + .build_and_execute(|| { + assert_eq!(Balances::::get(&CHARLIE), None); + assert_eq!(TotalIssuance::::get(), Some(242)); + }) + } + + #[docify::export] + #[test] + fn mint_works() { + StateBuilder::default().build_and_execute(|| { + // given the initial state, when: + assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100)); + + // then: + assert_eq!(Balances::::get(&BOB), Some(200)); + assert_eq!(TotalIssuance::::get(), Some(300)); + + // given: + assert_ok!(Pallet::::mint_unsafe( + RuntimeOrigin::signed(ALICE), + CHARLIE, + 100 + )); + + // then: + assert_eq!(Balances::::get(&CHARLIE), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(400)); + }); + } + + #[docify::export] + #[test] + fn transfer_works() { + StateBuilder::default().build_and_execute(|| { + // given the the initial state, when: + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); + + // then: + 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(BOB), ALICE, 50)); + + // then: + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(TotalIssuance::::get(), Some(200)); + }); + } + + #[docify::export] + #[test] + fn transfer_from_non_existent_fails() { + StateBuilder::default().build_and_execute(|| { + // given the the initial state, when: + assert_err!( + Pallet::::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10), + "NonExistentAccount" + ); + + // then nothing has changed. + 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)); + }); + } + } +} + +#[frame::pallet(dev_mode)] +pub mod pallet_v2 { + use super::pallet::Balance; + use frame::prelude::*; + + #[docify::export(config_v2)] + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type of the runtime. + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type Balances = StorageMap<_, _, T::AccountId, Balance>; + + #[pallet::storage] + pub type TotalIssuance = StorageValue<_, Balance>; + + #[docify::export] + #[pallet::error] + pub enum Error { + /// Account does not exist. + NonExistentAccount, + /// Account does not have enough balance. + InsufficientBalance, + } + + #[docify::export] + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transfer succeeded. + Transferred { from: T::AccountId, to: T::AccountId, amount: Balance }, + } + + #[pallet::call] + impl Pallet { + #[docify::export(transfer_v2)] + pub fn transfer( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // ensure sender has enough balance, and if so, calculate what is left after `amount`. + let sender_balance = + Balances::::get(&sender).ok_or(Error::::NonExistentAccount)?; + let reminder = + sender_balance.checked_sub(amount).ok_or(Error::::InsufficientBalance)?; + + Balances::::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount)); + Balances::::insert(&sender, reminder); + + Self::deposit_event(Event::::Transferred { from: sender, to: dest, amount }); + + Ok(()) + } + } + + #[cfg(any(test, doc))] + 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 { + use super::*; + use crate::guides::your_first_pallet::pallet_v2 as pallet_currency; + + construct_runtime!( + pub enum Runtime { + System: frame_system, + Currency: pallet_currency, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + type AccountId = u64; + } + + impl pallet_currency::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + } + } + + pub(crate) use runtime_v2::*; + + #[docify::export(transfer_works_v2)] + #[test] + fn transfer_works() { + 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(ALICE); + + // given the the initial state, when: + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); + + // then: + 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: ALICE, to: BOB, amount: 50 }] + ); + }); + } + } +} diff --git a/docs/sdk/src/guides/your_first_pallet/with_event.rs b/docs/sdk/src/guides/your_first_pallet/with_event.rs new file mode 100644 index 0000000000000000000000000000000000000000..a65aac324f07f6bff9fa85c9b9a2849188f3c4f2 --- /dev/null +++ b/docs/sdk/src/guides/your_first_pallet/with_event.rs @@ -0,0 +1,101 @@ +#[frame::pallet(dev_mode)] +pub mod pallet { + use frame::prelude::*; + + #[docify::export] + pub type Balance = u128; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export] + /// Single storage item, of type `Balance`. + #[pallet::storage] + pub type TotalIssuance = StorageValue<_, Balance>; + + #[docify::export] + /// A mapping from `T::AccountId` to `Balance` + #[pallet::storage] + pub type Balances = StorageMap<_, _, T::AccountId, Balance>; + + #[docify::export(impl_pallet)] + #[pallet::call] + impl Pallet { + /// An unsafe mint that can be called by anyone. Not a great idea. + pub fn mint_unsafe( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + // ensure that this is a signed account, but we don't really check `_anyone`. + let _anyone = ensure_signed(origin)?; + + // update the balances map. Notice how all `` remains as ``. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + // update total issuance. + TotalIssuance::::mutate(|t| *t = Some(t.unwrap_or(0) + amount)); + + Ok(()) + } + + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // ensure sender has enough balance, and if so, calculate what is left after `amount`. + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + if sender_balance < amount { + return Err("NotEnoughBalance".into()) + } + let reminder = sender_balance - amount; + + // update sender and dest balances. + Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); + Balances::::insert(&sender, reminder); + + Ok(()) + } + } + + #[allow(unused)] + impl Pallet { + #[docify::export] + pub fn transfer_better( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + ensure!(sender_balance >= amount, "NotEnoughBalance"); + let reminder = sender_balance - amount; + + // .. snip + Ok(()) + } + + #[docify::export] + /// Transfer `amount` from `origin` to `dest`. + pub fn transfer_better_checked( + origin: T::RuntimeOrigin, + dest: T::AccountId, + amount: Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; + let reminder = sender_balance.checked_sub(amount).ok_or("NotEnoughBalance")?; + + // .. snip + Ok(()) + } + } +} diff --git a/docs/sdk/src/guides/your_first_runtime.rs b/docs/sdk/src/guides/your_first_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e02ef1b1b28eedb7cea0ce38fb8b372a0caaf60 --- /dev/null +++ b/docs/sdk/src/guides/your_first_runtime.rs @@ -0,0 +1 @@ +//! # Your first Runtime diff --git a/docs/sdk/src/lib.rs b/docs/sdk/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e211476d2514419a3d0889ef426817342155c178 --- /dev/null +++ b/docs/sdk/src/lib.rs @@ -0,0 +1,46 @@ +//! # Polkadot SDK Docs +//! +//! The Polkadot SDK Developer Documentation. +//! +//! This crate is a *minimal*, but *always-accurate* source of information for those wishing to +//! build on the Polkadot SDK. +//! +//! > **Work in Progress**: This crate is under heavy development. Expect content to be moved and +//! > changed. Do not use links to this crate yet. See [`meta_contributing`] for more information. +//! +//! ## Getting Started +//! +//! We suggest the following reading sequence: +//! +//! - Start by learning about the the [`polkadot_sdk`], its structure and context. +//! - Then, head over the [`guides`]. This modules contains in-depth guides about the most important +//! user-journeys of the Polkadot SDK. +//! - Whilst reading the guides, you might find back-links to [`reference_docs`]. +//! - Finally, is the parent website of this crate that contains the +//! list of further tools related to the Polkadot SDK. +//! +//! ## Information Architecture +//! +//! This section paints a picture over the high-level information architecture of this crate. +#![doc = simple_mermaid::mermaid!("../../mermaid/IA.mmd")] +#![warn(rustdoc::broken_intra_doc_links)] +#![warn(rustdoc::private_intra_doc_links)] +#![doc(html_favicon_url = "https://polkadot.network/favicon-32x32.png")] +#![doc( + html_logo_url = "https://europe1.discourse-cdn.com/standard21/uploads/polkadot2/original/1X/eb57081e2bb7c39e5fcb1a98b443e423fa4448ae.svg" +)] +#![doc(issue_tracker_base_url = "https://github.com/paritytech/polkadot-sdk/issues")] + +/// Meta information about this crate, how it is built, what principles dictates its evolution and +/// how one can contribute to it. +pub mod meta_contributing; + +/// In-depth guides about the most common components of the Polkadot SDK. They are slightly more +/// high level and broad than reference docs. +pub mod guides; +/// An introduction to the Polkadot SDK. Read this module to learn about the structure of the SDK, +/// the tools that are provided as a part of it, and to gain a high level understanding of each. +pub mod polkadot_sdk; +/// Reference documents covering in-depth topics across the Polkadot SDK. It is suggested to read +/// these on-demand, while you are going through the [`guides`] or other content. +pub mod reference_docs; diff --git a/docs/sdk/src/meta_contributing.rs b/docs/sdk/src/meta_contributing.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcdcea9934bb6b8cf7ee6ec090ebd0939888238c --- /dev/null +++ b/docs/sdk/src/meta_contributing.rs @@ -0,0 +1,149 @@ +//! # Contribution +//! +//! The following sections cover more detailed information about this crate and how it should be +//! maintained. +//! +//! ## Why Rust Docs? +//! +//! We acknowledge that blockchain based systems, particularly a cutting-edge one like Polkadot SDK +//! is a software artifact that is complex, and rapidly evolving. This makes the task of documenting +//! it externally extremely difficult, especially with regards to making sure it is up-to-date. +//! +//! Consequently, we argue that the best hedge against this is to move as much of the documentation +//! near the source code as possible. This would further incentivize developers to keep the +//! documentation up-to-date, as the overhead is reduced by making sure everything is in one +//! repository, and everything being in `.rs` files. +//! +//! > This is not say that a more visually appealing version of this crate (for example as an +//! > `md-book`) cannot exist, but it would be outside the scope of this crate. +//! +//! Moreover, we acknowledge that a major pain point has been not only outdated *concepts*, but also +//! *outdated code*. For this, we commit to making sure no code-snippet in this crate is left as +//! `///ignore` or `///no_compile`, making sure all code snippets are self-contained, compile-able, +//! and correct at every single revision of the entire repository. +//! +//! > This also allows us to have a clear versioning on the entire content of this crate. For every +//! commit of the Polkadot SDK, there would be one version of this crate that is guaranteed to be +//! correct. +//! +//! > To achieve this, we often use [`docify`](https://github.com/sam0x17/docify), a nifty invention +//! > of `@sam0x17`. +//! +//! Also see: . +//! +//! ## Scope +//! +//! The above would NOT be attainable if we don't acknowledge that the scope of this crate MUST be +//! limited, or else its maintenance burden would be infeasible or not worthwhile. In short, future +//! maintainers should always strive to keep the content of this repository as minimal as possible. +//! Some of the following principles are specifically there to be the guidance for this. +//! +//! ## Principles +//! +//! The following guidelines are meant to be the guiding torch of those who contribute to this +//! crate. +//! +//! 1. 🔺 Ground Up: Information should be laid out in the most ground-up fashion. The lowest level +//! (i.e. "ground") is Rust-docs. The highest level (i.e. "up") is "outside of this crate". In +//! between lies [`reference_docs`] and [`guides`], from low to high. The point of this principle +//! is to document as much of the information as possible in the lower level media, as it is +//! easier to maintain and more reachable. Then, use excessive linking to back-link when writing +//! in a more high level. +//! +//! > A prime example of this, the details of the FRAME storage APIs should NOT be explained in a +//! > high level tutorial. They should be explained in the rust-doc of the corresponding type or +//! > macro. +//! +//! 2. 🧘 Less is More: For reasons mentioned [above](#why-rust-docs), the more concise this crate +//! is, the better. +//! 3. √ Don’t Repeat Yourself – DRY: A summary of the above two points. Authors should always +//! strive to avoid any duplicate information. Every concept should ideally be documented in +//! *ONE* place and one place only. This makes the task of maintaining topics significantly +//! easier. +//! +//! > A prime example of this, the list of CLI arguments of a particular binary should not be +//! > documented in multiple places across this crate. It should be only be documented in the +//! > corresponding crate (e.g. `sc_cli`). +//! +//! > Moreover, this means that as a contributor, **it is your responsibility to have a grasp over +//! > what topics are already covered in this crate, and how you can build on top of the information +//! > that they already pose, rather than repeating yourself**. +//! +//! For more details see the [latest documenting guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/DOCUMENTATION_GUIDELINES.md). +//! +//! #### Example: Explaining `#[pallet::call]` +//! +//!

+//! +//! Let's consider the seemingly simple example of explaining to someone dead-simple code of a FRAME +//! call and see how we can use the above principles. +//! +//! +//! +//! ``` +//! #[frame::pallet(dev_mode)] +//! pub mod pallet { +//! # use frame::prelude::*; +//! # #[pallet::config] +//! # pub trait Config: frame_system::Config {} +//! # #[pallet::pallet] +//! # pub struct Pallet(_); +//! #[pallet::call] +//! impl Pallet { +//! pub fn a_simple_call(origin: OriginFor, data: u32) -> DispatchResult { +//! ensure!(data > 10, "SomeStaticString"); +//! todo!(); +//! } +//! } +//! } +//! ``` +//! +//! * Before even getting started, what is with all of this ``? We link to +//! [`crate::reference_docs::trait_based_programming`]. +//! * First, the name. Why is this called `pallet::call`? This goes back to `enum Call`, which is +//! explained in [`crate::reference_docs::frame_runtime_types`]. Build on top of this! +//! * Then, what is `origin`? Just an account id? [`crate::reference_docs::frame_origin`]. +//! * Then, what is `DispatchResult`? Why is this called *dispatch*? Probably something that can be +//! explained in the documentation of [`frame::prelude::DispatchResult`]. +//! * Why is `"SomeStaticString"` a valid error? Because there is implementation for it that you can +//! see [here](frame::prelude::DispatchError#impl-From<%26'static+str>-for-DispatchError). +//! +//! +//! All of these are examples of underlying information that a contributor should: +//! +//! 1. Try and create and they are going along. +//! 2. Back-link to if they already exist. +//! +//! Of course, all of this is not set in stone as a either/or rule. Sometimes, it is necessary to +//! rephrase a concept in a new context. +//! +//!
+//! +//! ## `docs.substrate.io` +//! +//! This crate is meant to gradually replace `docs.substrate.io`. As any content is added here, the +//! corresponding counter-part should be marked as deprecated, as described +//! [here](https://github.com/paritytech/polkadot-sdk-docs/issues/26). +//! +//! ## `crates.io` and Publishing +//! +//! As it stands now, this crate cannot be published to crates.io because of its use of +//! [workspace-level `docify`](https://github.com/sam0x17/docify/issues/22). For now, we accept this +//! compromise, but in the long term, we should work towards finding a way to maintain different +//! revisions of this crate. +//! +//! ## How to Develop Locally +//! +//! To view the docs specific [`crate`] locally for development, including the correct HTML headers +//! injected, run: +//! +//! ```sh +//! SKIP_WASM_BUILD=1 \ +//! RUSTDOCFLAGS="--html-in-header $(pwd)/docs/sdk/headers/header.html --extend-css $(pwd)/docs/sdk/headers/theme.css --default-theme=ayu" \ +//! cargo doc -p polkadot-sdk-docs --no-deps --open +//! ``` +//! +//! If even faster build time for docs is needed, you can temporarily remove most of the +//! substrate/cumulus dependencies that are only used for linking purposes. +//! +//! For more on local development, see [`crate::reference_docs::development_environment_advice`]. diff --git a/docs/sdk/src/polkadot_sdk/cumulus.rs b/docs/sdk/src/polkadot_sdk/cumulus.rs new file mode 100644 index 0000000000000000000000000000000000000000..9bd957c7c1c07fec93b330ce8a8e7ec8118d31c1 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/cumulus.rs @@ -0,0 +1,129 @@ +//! # Cumulus +//! +//! Substrate provides a framework ([FRAME]) through which a blockchain node and runtime can easily +//! be created. Cumulus aims to extend the same approach to creation of Polkadot parachains. +//! +//! > Cumulus clouds are shaped sort of like dots; together they form a system that is intricate, +//! > beautiful and functional. +//! +//! ## Example: Runtime +//! +//! A Cumulus-based runtime is fairly similar to other [FRAME]-based runtimes. Most notably, the +//! following changes are applied to a normal FRAME-based runtime to make it a Cumulus-based +//! runtime: +//! +//! #### Cumulus Pallets +//! +//! A parachain runtime should use a number of pallets that are provided by Cumulus and Substrate. +//! Notably: +//! +//! - [`frame-system`](frame::prelude::frame_system), like all FRAME-based runtimes. +//! - [`cumulus_pallet_parachain_system`] +//! - [`parachain_info`] +#![doc = docify::embed!("./src/polkadot_sdk/cumulus.rs", system_pallets)] +//! +//! Given that all Cumulus-based runtimes use a simple Aura-based consensus mechanism, the following +//! pallets also need to be added: +//! +//! - [`pallet_timestamp`] +//! - [`pallet_aura`] +//! - [`cumulus_pallet_aura_ext`] +#![doc = docify::embed!("./src/polkadot_sdk/cumulus.rs", consensus_pallets)] +//! +//! +//! Finally, a separate macro, similar to +//! [`impl_runtime_api`](frame::runtime::prelude::impl_runtime_apis), which creates the default set +//! of runtime APIs, will generate the parachain runtime's validation runtime API, also known as +//! parachain validation function (PVF). Without this API, the relay chain is unable to validate +//! blocks produced by our parachain. +#![doc = docify::embed!("./src/polkadot_sdk/cumulus.rs", validate_block)] +//! +//! --- +//! +//! [FRAME]: crate::polkadot_sdk::frame_runtime + +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] + +#[cfg(test)] +mod tests { + mod runtime { + pub use frame::{ + deps::sp_consensus_aura::sr25519::AuthorityId as AuraId, prelude::*, + runtime::prelude::*, testing_prelude::*, + }; + + #[docify::export(CR)] + construct_runtime!( + pub enum Runtime { + // system-level pallets. + System: frame_system, + Timestamp: pallet_timestamp, + ParachainSystem: cumulus_pallet_parachain_system, + ParachainInfo: parachain_info, + + // parachain consensus support -- mandatory. + Aura: pallet_aura, + AuraExt: cumulus_pallet_aura_ext, + } + ); + + #[docify::export] + mod system_pallets { + use super::*; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + } + + impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = (); + type XcmpMessageHandler = (); + type ReservedDmpWeight = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = + cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; + type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + 6000, // relay chain block time + 1, + 1, + >; + type WeightInfo = (); + type DmpQueue = frame::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; + } + + impl parachain_info::Config for Runtime {} + } + + #[docify::export] + mod consensus_pallets { + use super::*; + + impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + } + + #[docify::export(timestamp)] + #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] + impl pallet_timestamp::Config for Runtime {} + + impl cumulus_pallet_aura_ext::Config for Runtime {} + } + + #[docify::export(validate_block)] + cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, + } + } +} diff --git a/docs/sdk/src/polkadot_sdk/frame_runtime.rs b/docs/sdk/src/polkadot_sdk/frame_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9b8a381365c4b3675485f3f32cb0cf751138275 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/frame_runtime.rs @@ -0,0 +1,175 @@ +//! # FRAME +//! +//! ```no_compile +//! ______ ______ ________ ___ __ __ ______ +//! /_____/\ /_____/\ /_______/\ /__//_//_/\ /_____/\ +//! \::::_\/_\:::_ \ \ \::: _ \ \\::\| \| \ \\::::_\/_ +//! \:\/___/\\:(_) ) )_\::(_) \ \\:. \ \\:\/___/\ +//! \:::._\/ \: __ `\ \\:: __ \ \\:.\-/\ \ \\::___\/_ +//! \:\ \ \ \ `\ \ \\:.\ \ \ \\. \ \ \ \\:\____/\ +//! \_\/ \_\/ \_\/ \__\/\__\/ \__\/ \__\/ \_____\/ +//! ``` +//! +//! > **F**ramework for **R**untime **A**ggregation of **M**odularized **E**ntities: Substrate's +//! > State Transition Function (Runtime) Framework. +//! +//! ## Introduction +//! +//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level Substrate-based +//! blockchains are composed of two parts: +//! +//! 1. A *runtime* which represents the state transition function (i.e. "Business Logic") of a +//! blockchain, and is encoded as a WASM blob. +//! 2. A node whose primary purpose is to execute the given runtime. +#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_simple.mmd")] +//! +//! *FRAME is the Substrate's framework of choice to build a runtime.* +//! +//! FRAME is composed of two major components, **pallets** and a **runtime**. +//! +//! ## Pallets +//! +//! A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be +//! linked to other pallets. In order to be reusable, pallets shipped with FRAME strive to only care +//! about its own responsibilities and make as few assumptions about the general runtime as +//! possible. A pallet is analogous to a _module_ in the runtime. +//! +//! A pallet is defined as a `mod pallet` wrapped by the [`frame::pallet`] macro. Within this macro, +//! pallet components/parts can be defined. Most notable of these parts are: +//! +//! - [Config](frame::pallet_macros::config), allowing a pallet to make itself configurable and +//! generic over types, values and such. +//! - [Storage](frame::pallet_macros::storage), allowing a pallet to define onchain storage. +//! - [Dispatchable function](frame::pallet_macros::call), allowing a pallet to define extrinsics +//! that are callable by end users, from the outer world. +//! - [Events](frame::pallet_macros::event), allowing a pallet to emit events. +//! - [Errors](frame::pallet_macros::error), allowing a pallet to emit well-formed errors. +//! +//! Some of these pallet components resemble the building blocks of a smart contract. While both +//! models are programming state transition functions of blockchains, there are crucial differences +//! between the two. See [`crate::reference_docs::runtime_vs_smart_contract`] for more. +//! +//! Most of these components are defined using macros, the full list of which can be found in +//! [`frame::pallet_macros`]. +//! +//! ### Example +//! +//! The following examples showcases a minimal pallet. +#![doc = docify::embed!("src/polkadot_sdk/frame_runtime.rs", pallet)] +//! +//! +//! A runtime is a collection of pallets that are amalgamated together. Each pallet typically has +//! some configurations (exposed as a `trait Config`) that needs to be *specified* in the runtime. +//! This is done with [`frame::runtime::prelude::construct_runtime`]. +//! +//! A (real) runtime that actually wishes to compile to WASM needs to also implement a set of +//! runtime-apis. These implementation can be specified using the +//! [`frame::runtime::prelude::impl_runtime_apis`] macro. +//! +//! ### Example +//! +//! The following example shows a (test) runtime that is composing the pallet demonstrated above, +//! next to the [`frame::prelude::frame_system`] pallet, into a runtime. +#![doc = docify::embed!("src/polkadot_sdk/frame_runtime.rs", runtime)] +//! +//! ## More Examples +//! +//! You can find more FRAME examples that revolve around specific features at [`pallet_examples`]. +//! +//! ## Alternatives 🌈 +//! +//! There is nothing in the Substrate's node side code-base that mandates the use of FRAME. While +//! FRAME makes it very simple to write Substrate-based runtimes, it is by no means intended to be +//! the only one. At the end of the day, any WASM blob that exposes the right set of runtime APIs is +//! a valid Runtime form the point of view of a Substrate client (see +//! [`crate::reference_docs::wasm_meta_protocol`]). Notable examples are: +//! +//! * writing a runtime in pure Rust, as done in [this template](https://github.com/JoshOrndorff/frameless-node-template). +//! * writing a runtime in AssemblyScript,as explored in [this project](https://github.com/LimeChain/subsembly). + +use frame::prelude::*; + +/// A FRAME based pallet. This `mod` is the entry point for everything else. All +/// `#[pallet::xxx]` macros must be defined in this `mod`. Although, frame also provides an +/// experimental feature to break these parts into different `mod`s. See [`pallet_examples`] for +/// more. +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod pallet { + use super::*; + + /// The configuration trait of a pallet. Mandatory. Allows a pallet to receive types at a + /// later point from the runtime that wishes to contain it. It allows the pallet to be + /// parameterized over both types and values. + #[pallet::config] + pub trait Config: frame_system::Config { + /// A type that is not known now, but the runtime that will contain this pallet will + /// know it later, therefore we define it here as an associated type. + type RuntimeEvent: IsType<::RuntimeEvent> + From>; + + /// A parameterize-able value that we receive later via the `Get<_>` trait. + type ValueParameter: Get; + + /// Similar to [`Config::ValueParameter`], but using `const`. Both are functionally + /// equal, but offer different tradeoffs. + const ANOTHER_VALUE_PARAMETER: u32; + } + + /// A mandatory struct in each pallet. All functions callable by external users (aka. + /// transactions) must be attached to this type (see [`frame::pallet_macros::call`]). For + /// convenience, internal (private) functions can also be attached to this type. + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// The events tha this pallet can emit. + #[pallet::event] + pub enum Event {} + + /// A storage item that this pallet contains. This will be part of the state root trie/root + /// of the blockchain. + #[pallet::storage] + pub type Value = StorageValue; + + /// All *dispatchable* call functions (aka. transactions) are attached to `Pallet` in a + /// `impl` block. + #[pallet::call] + impl Pallet { + /// This will be callable by external users, and has two u32s as a parameter. + pub fn some_dispatchable( + _origin: OriginFor, + _param: u32, + _other_para: u32, + ) -> DispatchResult { + Ok(()) + } + } +} + +/// A simple runtime that contains the above pallet and `frame_system`, the mandatory pallet of +/// all runtimes. This runtime is for testing, but it shares a lot of similarities with a *real* +/// runtime. +#[docify::export] +pub mod runtime { + use super::pallet as pallet_example; + use frame::{prelude::*, testing_prelude::*}; + + // The major macro that amalgamates pallets into `enum Runtime` + construct_runtime!( + pub enum Runtime { + System: frame_system, + Example: pallet_example, + } + ); + + // These `impl` blocks specify the parameters of each pallet's `trait Config`. + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + impl pallet_example::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValueParameter = ConstU32<42>; + const ANOTHER_VALUE_PARAMETER: u32 = 42; + } +} diff --git a/docs/sdk/src/polkadot_sdk/mod.rs b/docs/sdk/src/polkadot_sdk/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..124d391421b9049dd5865fae0ac9e739e3f46cce --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/mod.rs @@ -0,0 +1,134 @@ +//! # Polkadot SDK +//! +//! [Polkadot SDK](https://github.com/paritytech/polkadot-sdk) provides the main resources needed to +//! start building on the [Polkadot network](https://polkadot.network), a scalable, multi-chain +//! blockchain platform that enables different blockchains to securely interoperate. +//! +//! [![StackExchange](https://img.shields.io/badge/StackExchange-Polkadot%20and%20Substrate-222222?logo=stackexchange)](https://substrate.stackexchange.com/) +//! +//! [![awesomeDot](https://img.shields.io/badge/polkadot-awesome-e6007a?logo=polkadot)](https://github.com/Awsmdot/awesome-dot) +//! [![wiki](https://img.shields.io/badge/polkadot-wiki-e6007a?logo=polkadot)](https://wiki.polkadot.network/) +//! [![forum](https://img.shields.io/badge/polkadot-forum-e6007a?logo=polkadot)](https://forum.polkadot.network/) +//! +//! [![RFCs](https://img.shields.io/badge/fellowship-RFCs-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/rfcs) +//! [![Runtime](https://img.shields.io/badge/fellowship-runtimes-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/runtimes) +//! [![Manifesto](https://img.shields.io/badge/fellowship-manifesto-e6007a?logo=polkadot)](https://github.com/polkadot-fellows/manifesto) +//! +//! ## Getting Started +//! +//! The primary way to get started with the Polkadot SDK is to start writing a FRAME-based runtime. +//! See: +//! +//! * [`polkadot`], to understand what is Polkadot as a development platform. +//! * [`substrate`], for an overview of what Substrate as the main blockchain framework of Polkadot +//! SDK. +//! * [`frame`], to learn about how to write blockchain applications aka. "App Chains". +//! * Continue with the [`polkadot_sdk_docs`'s "getting started"](crate#getting-started). +//! +//! ## Components +//! +//! #### Substrate +//! +//! [![Substrate-license](https://img.shields.io/badge/License-GPL3%2FApache2.0-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/LICENSE-APACHE2) +//! [![GitHub +//! Repo](https://img.shields.io/badge/github-substrate-2324CC85)](https://github.com/paritytech/polkadot-sdk/blob/master/substrate) +//! +//! [`substrate`] is the base blockchain framework used to power the Polkadot SDK. It is a full +//! toolkit to create sovereign blockchains, including but not limited to those who connect to +//! Polkadot as parachains. +//! +//! #### FRAME +//! +//! [![Substrate-license](https://img.shields.io/badge/License-Apache2.0-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/LICENSE-APACHE2) +//! [![GitHub +//! Repo](https://img.shields.io/badge/github-frame-2324CC85)](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame) +//! +//! [`frame`] is the framework used to create Substrate-based application logic, aka. runtimes. +//! Learn more about the distinction of a runtime and node in +//! [`reference_docs::wasm_meta_protocol`]. +//! +//! #### Cumulus +//! +//! [![Cumulus-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/cumulus/LICENSE) +//! [![GitHub +//! Repo](https://img.shields.io/badge/github-cumulus-white)](https://github.com/paritytech/polkadot-sdk/blob/master/cumulus) +//! +//! [`cumulus`] transforms FRAME-based runtimes into Polkadot-compatible parachain runtimes, and +//! Substrate-based nodes into Polkadot/Parachain-compatible nodes. +//! +//! #### XCM +//! +//! [![XCM-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/LICENSE) +//! [![GitHub +//! Repo](https://img.shields.io/badge/github-XCM-e6007a?logo=polkadot)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm) +//! +//! [`xcm`], short for "cross consensus message", is the primary format that is used for +//! communication between parachains, but is intended to be extensible to other use cases as well. +//! +//! #### Polkadot +//! +//! [![Polkadot-license](https://img.shields.io/badge/License-GPL3-blue)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/LICENSE) +//! [![GitHub +//! Repo](https://img.shields.io/badge/github-polkadot-e6007a?logo=polkadot)](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot) +//! +//! [`polkadot`] is an implementation of a Polkadot node in Rust, by `@paritytech`. The Polkadot +//! runtimes are located under the +//! [`polkadot-fellows/runtimes`](https://github.com/polkadot-fellows/runtimes) repository. +//! +//! ### Summary +//! +//! The following diagram summarizes how some of the components of Polkadot SDK work together: +#![doc = simple_mermaid::mermaid!("../../../mermaid/polkadot_sdk_substrate.mmd")] +//! +//! A Substrate-based chain is a blockchain composed of a runtime and a node. As noted above, the +//! runtime is the application logic of the blockchain, and the node is everything else. +//! See [`crate::reference_docs::wasm_meta_protocol`] for an in-depth explanation of this. The +//! former is built with [`frame`], and the latter is built with rest of Substrate. +//! +//! > You can think of a Substrate-based chain as a white-labeled blockchain. +#![doc = simple_mermaid::mermaid!("../../../mermaid/polkadot_sdk_polkadot.mmd")] +//! Polkadot is itself a Substrate-based chain, composed of the exact same two components. It has +//! specialized logic in both the node and the runtime side, but it is not "special" in any way. +//! +//! A parachain is a "special" Substrate-based chain, whereby both the node and the runtime +//! components have became "Polkadot-aware" using Cumulus. +#![doc = simple_mermaid::mermaid!("../../../mermaid/polkadot_sdk_parachain.mmd")] +//! +//! ## Notable Upstream Crates +//! +//! - [`parity-scale-codec`](https://github.com/paritytech/parity-scale-codec) +//! - [`parity-db`](https://github.com/paritytech/parity-db) +//! - [`trie`](https://github.com/paritytech/trie) +//! - [`parity-common`](https://github.com/paritytech/parity-common) +//! +//! ## Trophy Section: Notable Downstream Projects +//! +//! A list of projects and tools in the blockchain ecosystem that one way or another parts of the +//! Polkadot SDK: +//! +//! * [Polygon's spin-off, Avail](https://github.com/availproject/avail) +//! * [Cardano Partner Chains](https://iohk.io/en/blog/posts/2023/11/03/partner-chains-are-coming-to-cardano/) +//! * [Starknet's Madara Sequencer](https://github.com/keep-starknet-strange/madara) +//! +//! [`substrate`]: crate::polkadot_sdk::substrate +//! [`frame`]: crate::polkadot_sdk::frame_runtime +//! [`cumulus`]: crate::polkadot_sdk::cumulus +//! [`polkadot`]: crate::polkadot_sdk::polkadot +//! [`xcm`]: crate::polkadot_sdk::xcm + +/// Lean about Cumulus, the framework that transforms [`substrate`]-based chains into +/// [`polkadot`]-enabled parachains. +pub mod cumulus; +/// Learn about FRAME, the framework used to build Substrate runtimes. +pub mod frame_runtime; +/// Learn about Polkadot as a platform. +pub mod polkadot; +/// Learn about different ways through which smart contracts can be utilized on top of Substrate, +/// and in the Polkadot ecosystem. +pub mod smart_contracts; +/// Learn about Substrate, the main blockchain framework used in the Polkadot ecosystem. +pub mod substrate; +/// Index of all the templates that can act as first scaffold for a new project. +pub mod templates; +/// Learn about XCM, the de-facto communication language between different consensus systems. +pub mod xcm; diff --git a/docs/sdk/src/polkadot_sdk/polkadot.rs b/docs/sdk/src/polkadot_sdk/polkadot.rs new file mode 100644 index 0000000000000000000000000000000000000000..61a6877696cb9298ed831562923d96d4a701bcc4 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/polkadot.rs @@ -0,0 +1,94 @@ +//! # Polkadot +//! +//! Implementation of the Polkadot node/host in Rust. +//! +//! ## Learn More and Get Involved +//! +//! - [Polkadot Forum](https://forum.polkadot.network/) +//! - [Polkadot Parachains](https://parachains.info/) +//! - [Polkadot (multi-chain) Explorer](https://subscan.io/) +//! - Polkadot Fellowship +//! - [Manifesto](https://github.com/polkadot-fellows/manifesto) +//! - [Runtimes](https://github.com/polkadot-fellows/runtimes) +//! - [RFCs](https://github.com/polkadot-fellows/rfcs) +//! - [Polkadot Specs](spec.polkadot.network) +//! - [The Polkadot Parachain Host Implementers' Guide](https://paritytech.github.io/polkadot-sdk/book/) +//! - [Whitepaper](https://www.polkadot.network/whitepaper/) +//! +//! ## Alternative Node Implementations 🌈 +//! +//! - [Smoldot](https://crates.io/crates/smoldot-light). Polkadot light node/client. +//! - [KAGOME](https://github.com/qdrvm/kagome). C++ implementation of the Polkadot host. +//! - [Gossamer](https://github.com/ChainSafe/gossamer). Golang implementation of the Polkadot host. +//! +//! ## Platform +//! +//! In this section, we examine what platform Polkadot exactly provides to developers. +//! +//! ### Polkadot White Paper +//! +//! The original vision of Polkadot (everything in the whitepaper, which was eventually called +//! **Polkadot 1.0**) revolves around the following arguments: +//! +//! * Future is multi-chain, because we need different chains with different specialization to +//! achieve widespread goals. +//! * In other words, no single chain is good enough to achieve all goals. +//! * A multi-chain future will inadvertently suffer from fragmentation of economic security. +//! * This stake fragmentation will make communication over consensus system with varying security +//! levels inherently unsafe. +//! +//! Polkadot's answer to the above is: +//! +//! > The chains of the future must have a way to share their economic security, whilst maintaining +//! > their execution and governance sovereignty. These chains are called "Parachains". +//! +//! * Shared Security: The idea of shared economic security sits at the core of Polkadot. Polkadot +//! enables different parachains* to pool their economic security from Polkadot (i.e. "*Relay +//! 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 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). +//! * A communication language between blockchains: In order for these blockchains to communicate, +//! they need a shared language. [XCM](crate::polkadot_sdk::xcm) is one such language, and the one +//! that is most endorsed in the Polkadot ecosystem. +//! +//! > 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. +//! > 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 +//! architecture similar to that of a computer. Polkadot Relay Chain has a number of "cores". Each +//! core is (in simple terms) capable of progressing 1 parachain at a time. For example, a parachain +//! can schedule itself on a single core for 5 relay chain blocks. +//! +//! Within the scope of Polkadot 1.x, two main scheduling ways have been considered: +//! +//! * Long term Parachains, obtained through locking a sum of DOT in an auction system. +//! * on-demand Parachains, purchased through paying DOT to the relay-chain whenever needed. +//! +//! ### The Future +//! +//! After delivering Polkadot 1.x, the future of Polkadot as a protocol and platform is in the hands +//! of the community and the fellowship. This is happening most notable through the RFC process. +//! Some of the RFCs that do alter Polkadot as a platform and have already passed are as follows: +//! +//! - RFC#1: [Agile-coretime](https://github.com/polkadot-fellows/RFCs/blob/main/text/0001-agile-coretime.md): +//! 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. +//! +//! 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 new file mode 100644 index 0000000000000000000000000000000000000000..4052c785f417b83ce5f7149aea38c2f72689a442 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/smart_contracts.rs @@ -0,0 +1,9 @@ +//! # Smart Contracts +//! +//! 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`]. +//! - diff --git a/docs/sdk/src/polkadot_sdk/substrate.rs b/docs/sdk/src/polkadot_sdk/substrate.rs new file mode 100644 index 0000000000000000000000000000000000000000..5021c55e581f3d9fcd06f8578998ab60db0abb71 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/substrate.rs @@ -0,0 +1,151 @@ +//! # Substrate +//! +//! Substrate is a Rust framework for building blockchains in a modular and extensible way. While in +//! itself un-opinionated, it is the main engine behind the Polkadot ecosystem. +//! +//! ## Overview, Philosophy +//! +//! Substrate approaches blockchain development with an acknowledgement of a few self-evident +//! truths: +//! +//! 1. Society and technology evolves. +//! 2. Humans are fallible. +//! +//! This, makes the task of designing a correct, safe and long-lasting blockchain system hard. +//! +//! Nonetheless, in strive towards achieve this goal, Substrate embraces the following: +//! +//! 1. Use of **Rust** as a modern and safe programming language, which limits human error through +//! various means, most notably memory and type safety. +//! 2. Substrate is written from the ground-up with a *generic, modular and extensible* design. This +//! ensures that software components can be easily swapped and upgraded. Examples of this is +//! multiple consensus mechanisms provided by Substrate, as listed below. +//! 3. Lastly, the final blockchain system created with the above properties needs to be +//! upgradeable. In order to achieve this, Substrate is designed as a meta-protocol, whereby the +//! application logic of the blockchain (called "Runtime") is encoded as a WASM blob, and is +//! stored in the state. The rest of the system (called "node") acts as the executor of the WASM +//! blob. +//! +//! In essence, the meta-protocol of all Substrate based chains is the "Runtime as WASM blob" +//! accord. This enables the Runtime to become inherently upgradeable, crucially without forks. The +//! upgrade is merely a matter of the WASM blob being changed in the state, which is, in principle, +//! same as updating an account's balance. Learn more about this in detail in +//! [`crate::reference_docs::wasm_meta_protocol`]. +//! +//! > A great analogy for substrate is the following: Substrate node is a gaming console, and a WASM +//! > runtime, possibly created with FRAME is the game being inserted into the console. +//! +//! [`frame`], Substrate's default runtime development library, takes the above safety practices +//! even further by embracing a declarative programming model whereby correctness is enhanced and +//! the system is highly configurable through parameterization. Learn more about this in +//! [`crate::reference_docs::trait_based_programming`]. +//! +//! ## How to Get Started +//! +//! Substrate offers different options at the spectrum of technical freedom <-> development ease. +//! +//! * The easiest way to use Substrate is to use one of the templates (some of which listed at +//! [`crate::polkadot_sdk::templates`]) and only tweak the parameters of the runtime or node. This +//! allows you to launch a blockchain in minutes, but is limited in technical freedom. +//! * Next, most developers wish to develop their custom runtime modules, for which the de-facto way +//! is [`frame`](crate::polkadot_sdk::frame_runtime). +//! * Finally, Substrate is highly configurable at the node side as well, but this is the most +//! technically demanding. +//! +//! > A notable Substrate-based blockchain that has built both custom FRAME pallets and custom +//! > node-side components is . +#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_dev.mmd")] +//! +//! ## Structure +//! +//! Substrate contains a large number of crates, therefore it is useful to have an overview of what +//! they are, and how they are organized. In broad terms, these crates are divided into three +//! categories: +//! +//! * `sc-*` (short for *Substrate-client*) crates, located under `./client` folder. These are all +//! the crates that lead to the node software. Notable examples [`sc_network`], various consensus +//! crates, RPC ([`sc_rpc_api`]) and database ([`sc_client_db`]), all of which are expected to +//! reside in the node side. +//! * `sp-*` (short for *substrate-primitives*) crates, located under `./primitives` folder. These +//! are crates that facilitate both the node and the runtime, but are not opinionated about what +//! framework is using for building the runtime. Notable examples are [`sp_api`] and [`sp_io`], +//! which form the communication bridge between the node and runtime. +//! * `pallet-*` and `frame-*` crates, located under `./frame` folder. These are the crates related +//! to FRAME. See [`frame`] for more information. +//! +//! ### WASM Build +//! +//! Many of the Substrate crates, such as entire `sp-*`, need to compile to both WASM (when a WASM +//! runtime is being generated) and native (for example, when testing). To achieve this, Substrate +//! follows the convention of the Rust community, and uses a `feature = "std"` to signify that a +//! crate is being built with the standard library, and is built for native. Otherwise, it is built +//! for `no_std`. +//! +//! This can be summarized in `#![cfg_attr(not(feature = "std"), no_std)]`, which you can often find +//! in any Substrate-based runtime. +//! +//! Substrate-based runtimes use [`substrate_wasm_builder`] in their `build.rs` to automatically +//! build their WASM files as a part of normal build command (e.g. `cargo build`). Once built, the +//! wasm file is placed in `./target/{debug|release}/wbuild/{runtime_name}.wasm`. +//! +//! ### Binaries +//! +//! Multiple binaries are shipped with substrate, the most important of which are located in the +//! [`./bin`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/bin) folder. +//! +//! * [`node_cli`] is an extensive substrate node that contains the superset of all runtime and node +//! side features. The corresponding runtime, called [`kitchensink_runtime`] contains all of the +//! modules that are provided with `FRAME`. This node and runtime is only used for testing and +//! demonstration. +//! * [`chain_spec_builder`]: Utility to build more detailed chain-specs for the aforementioned +//! node. Other projects typically contain a `build-spec` subcommand that does the same. +//! * [`node_template`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/bin/node-template): +//! a template node that contains a minimal set of features and can act as a starting point of a +//! project. +//! * [`subkey`]: Substrate's key management utility. +//! +//! ### Anatomy of a Binary Crate +//! +//! From the above, [`node_cli`]/[`kitchensink_runtime`] and `node-template` are essentially +//! blueprints of a Substrate-based project, as the name of the latter is implying. Each +//! Substrate-based project typically contains the following: +//! +//! * Under `./runtime`, a `./runtime/src/lib.rs` which is the top level runtime amalgamator file. +//! This file typically contains the [`frame::runtime::prelude::construct_runtime`] and +//! [`frame::runtime::prelude::impl_runtime_apis`] macro calls, which is the final definition of a +//! runtime. +//! +//! * Under `./node`, a `main.rs`, which is the starting point, and a `./service.rs`, which contains +//! all the node side components. Skimming this file yields an overview of the networking, +//! database, consensus and similar node side components. +//! +//! > The above two are conventions, not rules. +//! +//! > See for an update on how the node side +//! > components are being amalgamated. +//! +//! ## Parachain? +//! +//! As noted above, Substrate is the main engine behind the Polkadot ecosystem. One of the ways +//! through which Polkadot can be utilized is by building "parachains", blockchains that are +//! connected to Polkadot's shared security. +//! +//! To build a parachain, one could use [Cumulus](crate::polkadot_sdk::cumulus), the library on +//! top of Substrate, empowering any substrate-based chain to be a Polkadot parachain. +//! +//! ## Where To Go Next? +//! +//! Additional noteworthy crates within substrate: +//! +//! - RPC APIs of a Substrate node: [`sc_rpc_api`]/[`sc_rpc`] +//! - CLI Options of a Substrate node: [`sc_cli`] +//! - All of the consensus related crates provided by Substrate: +//! - [`sc_consensus_aura`] +//! - [`sc_consensus_babe`] +//! - [`sc_consensus_grandpa`] +//! - [`sc_consensus_beefy`] (TODO: @adrian, add some high level docs ) +//! - [`sc_consensus_manual_seal`] +//! - [`sc_consensus_pow`] + +#[doc(hidden)] +pub use crate::polkadot_sdk; diff --git a/docs/sdk/src/polkadot_sdk/templates.rs b/docs/sdk/src/polkadot_sdk/templates.rs new file mode 100644 index 0000000000000000000000000000000000000000..f60c75b8f219334ccc52a9ea8441d21a2dbede8d --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/templates.rs @@ -0,0 +1,45 @@ +//! # Templates +//! +//! ### Internal +//! +//! The following templates are maintained as a part of the `polkadot-sdk` repository: +//! +//! - classic [`substrate-node-template`]: is a white-labeled substrate-based blockchain with a +//! moderate amount of features. It can act as a great starting point for those who want to learn +//! Substrate/FRAME and want to have a template that is already doing something. +//! - [`substrate-minimal-template`]: Same as the above, but it contains the least amount of code in +//! both the node and runtime. It is a great starting point for those who want to deeply learn +//! Substrate and FRAME. +//! - classic [`cumulus-parachain-template`], which is the de-facto parachain template shipped with +//! Cumulus. It is the parachain-enabled version of [`substrate-node-template`]. +//! +//! ### External Templates +//! +//! Noteworthy templates outside of this repository. +//! +//! - [`extended-parachain-template`](https://github.com/paritytech/extended-parachain-template): A +//! parachain template that contains more built-in functionality such as assets and NFTs. +//! - [`frontier-parachain-template`](https://github.com/paritytech/frontier-parachain-template): A +//! parachain template for launching EVM-compatible parachains. +//! +//! [`substrate-node-template`]: https://github.com/paritytech/polkadot-sdk/blob/master/substrate/bin/node-template/ +//! [`substrate-minimal-template`]: https://github.com/paritytech/polkadot-sdk/blob/master/substrate/bin/minimal/ +//! [`cumulus-parachain-template`]: https://github.com/paritytech/polkadot-sdk/blob/master/cumulus/parachain-template/ + +// TODO: in general, we need to make a deliberate choice here of moving a few key templates to this +// repo (nothing stays in `substrate-developer-hub`) and the everything else should be community +// maintained. https://github.com/paritytech/polkadot-sdk-docs/issues/67 + +// TODO: we should rename `substrate-node-template` to `substrate-basic-template`, +// `substrate-blockchain-template`. `node` is confusing in the name. +// `substrate-blockchain-template` and `cumulus-parachain-template` go well together 🤝. https://github.com/paritytech/polkadot-sdk-docs/issues/67 + +// NOTE: a super important detail that I am looking forward to here is +// and +// . Meaning that I would not spend time on +// teaching someone too much detail about the ugly thing we call "node" nowadays. In the future, I +// am sure we will either have a better "node-builder" code that can actually be tested, or an +// "omni-node" that can run (almost) any wasm file. We should already build tutorials in this +// direction IMO. This also affects all the templates. If we have a good neat runtime file, which we +// are moving toward, and a good node-builder, we don't need all of these damn templates. These +// templates are only there because the boilerplate is super horrible atm. diff --git a/docs/sdk/src/polkadot_sdk/xcm.rs b/docs/sdk/src/polkadot_sdk/xcm.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dcdc9e1de076c4507cb64517360df73e6733dc7 --- /dev/null +++ b/docs/sdk/src/polkadot_sdk/xcm.rs @@ -0,0 +1,71 @@ +//! # XCM +//! +//! XCM, or Cross-Consensus Messaging, is a **language** to communicate **intentions** between +//! **consensus systems**. +//! +//! ## Overview +//! +//! XCM is a standard, whose specification lives in the [xcm format repo](https://github.com/paritytech/xcm-format). +//! It's agnostic both in programming language and blockchain platform, which means it could be used +//! in Rust in Polkadot, or in Go or C++ in any other platform like Cosmos or Ethereum. +//! +//! It enables different consensus systems to communicate with each other in an expressive manner. +//! Consensus systems include blockchains, smart contracts, and any other state machine that +//! achieves consensus in some way. +//! +//! XCM is executed on a virtual machine called the XCVM. +//! Scripts can be written with the XCM language, which are often called XCMs, messages or XCM +//! programs. Each program is a series of instructions, which get executed one after the other by +//! the virtual machine. These instructions aim to encompass all major things users typically do in +//! consensus systems. There are instructions on asset transferring, teleporting, locking, among +//! others. New instructions are added and changes to the XCVM are made via the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md). +//! +//! ## In Polkadot SDK +//! +//! The Polkadot SDK allows for easily deploying sovereign blockchains from scratch, all very +//! customizable. Dealing with many heterogeneous blockchains can be cumbersome. +//! XCM allows all these blockchains to communicate with an agreed-upon language. +//! As long as an implementation of the XCVM is implemented, the same XCM program can be executed in +//! all blockchains and perform the same task. +//! +//! ## Implementation +//! +//! A ready-to-use Rust implementation lives in the [polkadot-sdk repo](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/xcm), +//! but will be moved to its own repo in the future. +//! +//! Its main components are: +//! - `src`: the definition of the basic types and instructions +//! - [`xcm-executor`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_executor/struct.XcmExecutor.html): +//! an implementation of the virtual machine to execute instructions +//! - `pallet-xcm`: A FRAME pallet for interacting with the executor +//! - `xcm-builder`: a collection of types to configure the executor +//! - `xcm-simulator`: a playground for trying out different XCM programs and executor +//! configurations +//! +//! ## Example +//! +//! To perform the very usual operation of transferring assets, the following XCM program can be +//! used: +#![doc = docify::embed!("src/polkadot_sdk/xcm.rs", example_transfer)] +//! +//! ## Get started +//! +//! To learn how it works and to get started, go to the [XCM docs](https://paritytech.github.io/xcm-docs/). + +#[cfg(test)] +mod tests { + use xcm::latest::prelude::*; + + #[docify::export] + #[test] + fn example_transfer() { + let _transfer_program = Xcm::<()>(vec![ + WithdrawAsset((Here, 100u128).into()), + BuyExecution { fees: (Here, 100u128).into(), weight_limit: Unlimited }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: [0u8; 32].into(), network: None }.into(), + }, + ]); + } +} diff --git a/docs/sdk/src/reference_docs/blockchain_scalibility.rs b/docs/sdk/src/reference_docs/blockchain_scalibility.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/docs/sdk/src/reference_docs/blockchain_state_machines.rs b/docs/sdk/src/reference_docs/blockchain_state_machines.rs new file mode 100644 index 0000000000000000000000000000000000000000..0d1aefcc52770b70baf284335816f013cbfbe5ed --- /dev/null +++ b/docs/sdk/src/reference_docs/blockchain_state_machines.rs @@ -0,0 +1,29 @@ +//! # State Transition Function +//! +//! This document briefly explains how in the context of Substrate-based blockchains, we view the +//! blockchain as a **decentralized state transition function**. +//! +//! Recall that a blockchain's main purpose is to help a permissionless set of entities to agree on +//! a shared data-set, and how it evolves. This is called the **State**, also referred to as +//! "onchain" data, or *Storage* in the context of FRAME. The state is where the account balance of +//! each user is, for example, stored, and there is a canonical version of it that everyone agrees +//! upon. +//! +//! Then, recall that a typical blockchain system will alter its state through execution of blocks. +//! *The component that dictates how this state alteration can happen is called the state transition +//! function*. +#![doc = simple_mermaid::mermaid!("../../../mermaid/stf_simple.mmd")] +//! +//! In Substrate-based blockchains, the state transition function is called the *Runtime*. This is +//! explained further in [`crate::reference_docs::wasm_meta_protocol`]. +//! +//! With this in mind, we can paint a complete picture of a blockchain as a state machine: +#![doc = simple_mermaid::mermaid!("../../../mermaid/stf.mmd")] +//! +//! In essence, the state of the blockchain at block N is the outcome of applying the state +//! transition function to the the previous state, and the current block as input. This can be +//! mathematically represented as: +//! +//! ```math +//! STF = F(State_N, Block_N) -> State_{N+1} +//! ``` diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..2ac51a91f2de78cf10262e5b38c2ebf482c15183 --- /dev/null +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -0,0 +1,4 @@ +//! Chain spec and genesis build. +//! +//! What is chain-spec. +//! What is genesis state and how to build it. diff --git a/docs/sdk/src/reference_docs/cli.rs b/docs/sdk/src/reference_docs/cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..5779e0f8d04954e9fe1d245e0af4761fb0246805 --- /dev/null +++ b/docs/sdk/src/reference_docs/cli.rs @@ -0,0 +1,104 @@ +//! # 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. +//! +//! #### Checking the available CLI arguments +//! ```bash +//! ./target/debug/node-template --help +//! ``` +//! - `--help`: Displays the available CLI arguments. +//! +//! #### 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/consensus_swapping.rs b/docs/sdk/src/reference_docs/consensus_swapping.rs new file mode 100644 index 0000000000000000000000000000000000000000..e639761ee97b42fa68e1ba77250d490e28277e24 --- /dev/null +++ b/docs/sdk/src/reference_docs/consensus_swapping.rs @@ -0,0 +1,6 @@ +//! Consensus Swapping +//! +//! Notes: +//! +//! - The typical workshop done by Joshy in some places where he swaps out the consensus to be PoW. +//! - This could also be a tutorial rather than a ref doc, depending on the size. 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..21bbe78836c44b8afd70cab68e4b9b2f929fb4a0 --- /dev/null +++ b/docs/sdk/src/reference_docs/development_environment_advice.rs @@ -0,0 +1,184 @@ +//! # 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-2024-01-22"], +//! } +//! ``` +//! +//! 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-2024-01-22" }, +//! }, +//! }, +//! ``` +//! +//! 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`. +//! +//! When using `cargo-remote`, you can configure your editor to perform the the typical +//! "check-on-save" remotely as well. The configuration for VSCode is as follows: +//! +//! ```json +//! { +//! "rust-analyzer.cargo.buildScripts.overrideCommand": [ +//! "cargo", +//! "remote", +//! "--build-env", +//! "SKIP_WASM_BUILD=1", +//! "--", +//! "check", +//! "--message-format=json", +//! "--all-targets", +//! "--all-features", +//! "--target-dir=target/rust-analyzer" +//! ], +//! "rust-analyzer.check.overrideCommand": [ +//! "cargo", +//! "remote", +//! "--build-env", +//! "SKIP_WASM_BUILD=1", +//! "--", +//! "check", +//! "--workspace", +//! "--message-format=json", +//! "--all-targets", +//! "--all-features", +//! "--target-dir=target/rust-analyzer" +//! ], +//! } +//! ``` +//! +//! //! and the same in Lua for `neovim/nvim-lspconfig`: +//! +//! ```lua +//! ["rust-analyzer"] = { +//! cargo = { +//! buildScripts = { +//! overrideCommand = { +//! "cargo", +//! "remote", +//! "--build-env", +//! "SKIP_WASM_BUILD=1", +//! "--", +//! "check", +//! "--message-format=json", +//! "--all-targets", +//! "--all-features", +//! "--target-dir=target/rust-analyzer" +//! }, +//! }, +//! check = { +//! overrideCommand = { +//! "cargo", +//! "remote", +//! "--build-env", +//! "SKIP_WASM_BUILD=1", +//! "--", +//! "check", +//! "--workspace", +//! "--message-format=json", +//! "--all-targets", +//! "--all-features", +//! "--target-dir=target/rust-analyzer" +//! }, +//! }, +//! }, +//! }, +//! ``` diff --git a/docs/sdk/src/reference_docs/extrinsic_encoding.rs b/docs/sdk/src/reference_docs/extrinsic_encoding.rs new file mode 100644 index 0000000000000000000000000000000000000000..8c8568a228fad140115e68feb3eec717ef30170c --- /dev/null +++ b/docs/sdk/src/reference_docs/extrinsic_encoding.rs @@ -0,0 +1,277 @@ +//! # Constructing and Signing Extrinsics +//! +//! Extrinsics are payloads that are stored in blocks which are responsible for altering the state +//! of a blockchain via the [_state transition +//! function_][crate::reference_docs::blockchain_state_machines]. +//! +//! Substrate is configurable enough that extrinsics can take any format. In practice, runtimes +//! tend to use our [`sp_runtime::generic::UncheckedExtrinsic`] type to represent extrinsics, +//! because it's generic enough to cater for most (if not all) use cases. In Polkadot, this is +//! configured [here](https://github.com/polkadot-fellows/runtimes/blob/94b2798b69ba6779764e20a50f056e48db78ebef/relay/polkadot/src/lib.rs#L1478) +//! at the time of writing. +//! +//! What follows is a description of how extrinsics based on this +//! [`sp_runtime::generic::UncheckedExtrinsic`] type are encoded into bytes. Specifically, we are +//! looking at how extrinsics with a format version of 4 are encoded. This version is itself a part +//! of the payload, and if it changes, it indicates that something about the encoding may have +//! changed. +//! +//! # Encoding an Extrinsic +//! +//! At a high level, all extrinsics compatible with [`sp_runtime::generic::UncheckedExtrinsic`] +//! are formed from concatenating some details together, as in the following pseudo-code: +//! +//! ```text +//! extrinsic_bytes = concat( +//! compact_encoded_length, +//! version_and_maybe_signature, +//! call_data +//! ) +//! ``` +//! +//! For clarity, the actual implementation in Substrate looks like this: +#![doc = docify::embed!("../../substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs", unchecked_extrinsic_encode_impl)] +//! +//! Let's look at how each of these details is constructed: +//! +//! ## compact_encoded_length +//! +//! This is a [SCALE compact encoded][frame::deps::codec::Compact] integer which is equal to the +//! length, in bytes, of the rest of the extrinsic details. +//! +//! To obtain this value, we must encode and concatenate together the rest of the extrinsic details +//! first, and then obtain the byte length of these. We can then compact encode that length, and +//! prepend it to the rest of the details. +//! +//! ## version_and_maybe_signature +//! +//! If the extrinsic is _unsigned_, then `version_and_maybe_signature` will be just one byte +//! denoting the _transaction protocol version_, which is 4 (or `0b0000_0100`). +//! +//! If the extrinsic is _signed_ (all extrinsics submitted from users must be signed), then +//! `version_and_maybe_signature` is obtained by concatenating some details together, ie: +//! +//! ```text +//! version_and_maybe_signature = concat( +//! version_and_signed, +//! from_address, +//! signature, +//! signed_extensions_extra, +//! ) +//! ``` +//! +//! Each of the details to be concatenated together is explained below: +//! +//! ### version_and_signed +//! +//! This is one byte, equal to `0x84` or `0b1000_0100` (i.e. an upper 1 bit to denote that it is +//! signed, and then the transaction version, 4, in the lower bits). +//! +//! ### from_address +//! +//! This is the [SCALE encoded][frame::deps::codec] address of the sender of the extrinsic. The +//! address is the first generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`], and so +//! can vary from chain to chain. +//! +//! The address type used on the Polkadot relay chain is [`sp_runtime::MultiAddress`], +//! where `AccountId32` is defined [here][`sp_core::crypto::AccountId32`]. When constructing a +//! signed extrinsic to be submitted to a Polkadot node, you'll always use the +//! [`sp_runtime::MultiAddress::Id`] variant to wrap your `AccountId32`. +//! +//! ### signature +//! +//! This is the [SCALE encoded][frame::deps::codec] signature. The signature type is configured via +//! the third generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`], which determines the +//! shape of the signature and signing algorithm that should be used. +//! +//! The signature is obtained by signing the _signed payload_ bytes (see below on how this is +//! constructed) using the private key associated with the address and correct algorithm. +//! +//! The signature type used on the Polkadot relay chain is [`sp_runtime::MultiSignature`]; the +//! variants there are the types of signature that can be provided. +//! +//! ### signed_extensions_extra +//! +//! This is the concatenation of the [SCALE encoded][frame::deps::codec] bytes representing each of +//! the [_signed extensions_][sp_runtime::traits::SignedExtension], and are configured by the +//! fourth generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`]. Learn more about +//! signed extensions [here][crate::reference_docs::signed_extensions]. +//! +//! When it comes to constructing an extrinsic, each signed extension has two things that we are +//! interested in here: +//! +//! - The actual SCALE encoding of the signed extension type itself; this is what will form our +//! `signed_extensions_extra` bytes. +//! - An `AdditionalSigned` type. This is SCALE encoded into the `signed_extensions_additional` data +//! of the _signed payload_ (see below). +//! +//! Either (or both) of these can encode to zero bytes. +//! +//! Each chain configures the set of signed extensions that it uses in its runtime configuration. +//! At the time of writing, Polkadot configures them +//! [here](https://github.com/polkadot-fellows/runtimes/blob/1dc04eb954eadf8aadb5d83990b89662dbb5a074/relay/polkadot/src/lib.rs#L1432C25-L1432C25). +//! Some of the common signed extensions are defined +//! [here][frame::deps::frame_system#signed-extensions]. +//! +//! Information about exactly which signed extensions are present on a chain and in what order is +//! also a part of the metadata for the chain. For V15 metadata, it can be +//! [found here][frame::deps::frame_support::__private::metadata::v15::ExtrinsicMetadata]. +//! +//! ## call_data +//! +//! This is the main payload of the extrinsic, which is used to determine how the chain's state is +//! altered. This is defined by the second generic parameter of +//! [`sp_runtime::generic::UncheckedExtrinsic`]. +//! +//! A call can be anything that implements [`Encode`][frame::deps::codec::Encode]. In FRAME-based +//! runtimes, a call is represented as an enum of enums, where the outer enum represents the FRAME +//! pallet being called, and the inner enum represents the call being made within that pallet, and +//! any arguments to it. Read more about the call enum +//! [here][crate::reference_docs::frame_runtime_types]. +//! +//! FRAME `Call` enums are automatically generated, and end up looking something like this: +#![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", call_data)] +//! +//! In pseudo-code, this `Call` enum encodes equivalently to: +//! +//! ```text +//! call_data = concat( +//! pallet_index, +//! call_index, +//! call_args +//! ) +//! ``` +//! +//! - `pallet_index` is a single byte denoting the index of the pallet that we are calling into, and +//! is what the tag of the outermost enum will encode to. +//! - `call_index` is a single byte denoting the index of the call that we are making the pallet, +//! and is what the tag of the inner enum will encode to. +//! - `call_args` are the SCALE encoded bytes for each of the arguments that the call expects, and +//! are typically provided as values to the inner enum. +//! +//! Information about the pallets that exist for a chain (including their indexes), the calls +//! available in each pallet (including their indexes), and the arguments required for each call +//! can be found in the metadata for the chain. For V15 metadata, this information +//! [is here][frame::deps::frame_support::__private::metadata::v15::PalletMetadata]. +//! +//! # The Signed Payload Format +//! +//! All extrinsics submitted to a node from the outside world (also known as _transactions_) need to +//! be _signed_. The data that needs to be signed for some extrinsic is called the _signed payload_, +//! and its shape is described by the following pseudo-code: +//! +//! ```text +//! signed_payload = concat( +//! call_data, +//! signed_extensions_extra, +//! signed_extensions_additional, +//! ) +//! +//! if length(signed_payload) > 256 { +//! signed_payload = blake2_256(signed_payload) +//! } +//! ``` +//! +//! The bytes representing `call_data` and `signed_extensions_extra` can be obtained as described +//! above. `signed_extensions_additional` is constructed by SCALE encoding the +//! ["additional signed" data][sp_runtime::traits::SignedExtension::AdditionalSigned] for each +//! signed extension that the chain is using, in order. +//! +//! Once we've concatenated those together, we hash the result if it's greater than 256 bytes in +//! length using a Blake2 256bit hasher. +//! +//! The [`sp_runtime::generic::SignedPayload`] type takes care of assembling the correct payload +//! for us, given `call_data` and a tuple of signed extensions. +//! +//! # Example Encoding +//! +//! Using [`sp_runtime::generic::UncheckedExtrinsic`], we can construct and encode an extrinsic +//! as follows: +#![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", encoding_example)] + +#[docify::export] +pub mod call_data { + use parity_scale_codec::{Decode, Encode}; + + // The outer enum composes calls within + // different pallets together. We have two + // pallets, "PalletA" and "PalletB". + #[derive(Encode, Decode)] + pub enum Call { + #[codec(index = 0)] + PalletA(PalletACall), + #[codec(index = 7)] + PalletB(PalletBCall), + } + + // An inner enum represents the calls within + // a specific pallet. "PalletA" has one call, + // "Foo". + #[derive(Encode, Decode)] + pub enum PalletACall { + #[codec(index = 0)] + Foo(String), + } + + #[derive(Encode, Decode)] + pub enum PalletBCall { + #[codec(index = 0)] + Bar(String), + } +} + +#[docify::export] +pub mod encoding_example { + use super::call_data::{Call, PalletACall}; + use crate::reference_docs::signed_extensions::signed_extensions_example; + use parity_scale_codec::Encode; + use sp_core::crypto::AccountId32; + use sp_keyring::sr25519::Keyring; + use sp_runtime::{ + generic::{SignedPayload, UncheckedExtrinsic}, + MultiAddress, MultiSignature, + }; + + // Define some signed extensions to use. We'll use a couple of examples + // from the signed extensions reference doc. + type SignedExtensions = + (signed_extensions_example::AddToPayload, signed_extensions_example::AddToSignaturePayload); + + // We'll use `UncheckedExtrinsic` to encode our extrinsic for us. We set + // the address and signature type to those used on Polkadot, use our custom + // `Call` type, and use our custom set of `SignedExtensions`. + type Extrinsic = + UncheckedExtrinsic, Call, MultiSignature, SignedExtensions>; + + pub fn encode_demo_extrinsic() -> Vec { + // The "from" address will be our Alice dev account. + let from_address = MultiAddress::::Id(Keyring::Alice.to_account_id()); + + // We provide some values for our expected signed extensions. + let signed_extensions = ( + signed_extensions_example::AddToPayload(1), + signed_extensions_example::AddToSignaturePayload, + ); + + // Construct our call data: + let call_data = Call::PalletA(PalletACall::Foo("Hello".to_string())); + + // The signed payload. This takes care of encoding the call_data, + // signed_extensions_extra and signed_extensions_additional, and hashing + // the result if it's > 256 bytes: + let signed_payload = SignedPayload::new(&call_data, signed_extensions.clone()); + + // Sign the signed payload with our Alice dev account's private key, + // and wrap the signature into the expected type: + let signature = { + let sig = Keyring::Alice.sign(&signed_payload.encode()); + MultiSignature::Sr25519(sig) + }; + + // Now, we can build and encode our extrinsic: + let ext = Extrinsic::new_signed(call_data, from_address, signature, signed_extensions); + + let encoded_ext = ext.encode(); + encoded_ext + } +} diff --git a/docs/sdk/src/reference_docs/fee_less_runtime.rs b/docs/sdk/src/reference_docs/fee_less_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..1213c26282537fdee2d04ebd452289f0e6bc759f --- /dev/null +++ b/docs/sdk/src/reference_docs/fee_less_runtime.rs @@ -0,0 +1,12 @@ +//! # Fee-Less Runtime +//! +//! +//! Notes: +//! +//! - An extension of [`runtime_vs_smart_contract`], showcasing the tools needed to build a safe +//! runtime that is fee-less. +//! - Would need to use unsigned origins, custom validate_unsigned, check the existence of some NFT +//! 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 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 new file mode 100644 index 0000000000000000000000000000000000000000..db77547a4bf0fe0a6d24f8ffc80cdda206d576b4 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs @@ -0,0 +1,23 @@ +//! # FRAME Benchmarking and Weights. +//! +//! Notes: +//! +//! On Weight as a concept. +//! +//! - Why we need it. Super important. People hate this. We need to argue why it is worth it. +//! - Axis of weight: PoV + Time. +//! - pre dispatch weight vs. metering and post dispatch correction. +//! - mention that we will do this for PoV +//! - you can manually refund using `DispatchResultWithPostInfo`. +//! - Technically you can have weights with any benchmarking framework. You just need one number to +//! be computed pre-dispatch. But FRAME gives you a framework for this. +//! - improve documentation of `#[weight = ..]` and `#[pallet::weight(..)]`. All syntax variation +//! should be covered. +//! +//! on FRAME benchmarking machinery: +//! +//! - component analysis, why everything must be linear. +//! - how to write benchmarks, how you must think of worst case. +//! - how to run benchmarks. +//! +//! - diff --git a/docs/sdk/src/reference_docs/frame_currency.rs b/docs/sdk/src/reference_docs/frame_currency.rs new file mode 100644 index 0000000000000000000000000000000000000000..6987d51aec824d370fb4706ae7d5b942a2667a64 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_currency.rs @@ -0,0 +1,8 @@ +//! FRAME Currency Abstractions and Traits +//! +//! Notes: +//! +//! - History, `Currency` trait. +//! - `Hold` and `Freeze` with diagram. +//! - `HoldReason` and `FreezeReason` +//! - This footgun: diff --git a/docs/sdk/src/reference_docs/frame_offchain_workers.rs b/docs/sdk/src/reference_docs/frame_offchain_workers.rs new file mode 100644 index 0000000000000000000000000000000000000000..7999707e5ee018c4bb7634e7a506ff8fee8fa8ac --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_offchain_workers.rs @@ -0,0 +1,115 @@ +//! # Offchain Workers +//! +//! This reference document explains how offchain workers work in Substrate and FRAME. The main +//! focus is upon FRAME's implementation of this functionality. Nonetheless, offchain workers are a +//! Substrate-provided feature and can be used with possible alternatives to [`frame`] as well. +//! +//! Offchain workers are a commonly misunderstood topic, therefore we explain them bottom-up, +//! starting at the fundamentals and then describing the developer interface. +//! +//! ## Context +//! +//! Recall from [`crate::reference_docs::wasm_meta_protocol`] that the node and the runtime +//! communicate with one another via host functions and runtime APIs. Many of these interactions +//! contribute to the actual state transition of the blockchain. For example [`sp_api::Core`] is the +//! main runtime API that is called to execute new blocks. +//! +//! Offchain workers are in principle not different in any way: It is a runtime API exposed by the +//! wasm blob ([`sp_offchain::OffchainWorkerApi`]), and the node software calls into it when it +//! deems fit. But, crucially, this API call is different in that: +//! +//! 1. It can have no impact on the state ie. it is _OFF (the) CHAIN_. If any state is altered +//! during the execution of this API call, it is discarded. +//! 2. It has access to an extended set of host functions that allow the wasm blob to do more. For +//! example, call into HTTP requests. +//! +//! > The main way through which an offchain worker can interact with the state is by submitting an +//! > extrinsic to the chain. This is the ONLY way to alter the state from an offchain worker. +//! > [`pallet_example_offchain_worker`] provides an example of this. +//! +//! +//! Given the "Off Chain" nature of this API, it is important to remember that calling this API is +//! entirely optional. Some nodes might call into it, some might not, and it would have no impact on +//! the execution of your blockchain because no state is altered no matter the execution of the +//! offchain worker API. +//! +//! Substrate's CLI allows some degree of configuration about this, allowing node operators to +//! specify when they want to run the offchain worker API. See +//! [`sc_cli::RunCmd::offchain_worker_params`]. +//! +//! ## Nondeterministic Execution +//! +//! Needless to say, given the above description, the code in your offchain worker API can be +//! nondeterministic, as it is not part of the blockchain's STF, so it can be executed at unknown +//! times, by unknown nodes, and has no impact on the state. This is why an HTTP +//! ([`sp_runtime::offchain::http`]) API is readily provided to the offchain worker APIs. Because +//! there is no need for determinism in this context. +//! +//! > A common mistake here is for novice developers to see this HTTP API, and imagine that +//! > `polkadot-sdk` somehow magically solved the determinism in blockchains, and now a blockchain +//! > can make HTTP calls and it will all work. This is absolutely NOT the case. An HTTP call made +//! > by the offchain worker is non-deterministic by design. Blockchains can't and always won't be +//! > able to perform non-deterministic operations such as making HTTP calls to a foreign server. +//! +//! ## FRAME's API +//! +//! [`frame`] provides a simple API through which pallets can define offchain worker functions. This +//! is part of [`frame::traits::Hooks`], which is implemented as a part of +//! [`frame::pallet_macros::hooks`]. +//! +//! ``` +//! +//! #[frame::pallet] +//! pub mod pallet { +//! use frame::prelude::*; +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config {} +//! +//! #[pallet::pallet] +//! pub struct Pallet(_); +//! +//! #[pallet::hooks] +//! impl Hooks> for Pallet { +//! fn offchain_worker(block_number: BlockNumberFor) { +//! // ... +//! } +//! } +//! } +//! ``` +//! +//! Additionally, [`sp_runtime::offchain`] provides a set of utilities that can be used to moderate +//! the execution of offchain workers. +//! +//! ## Think Twice: Why Use Substrate's Offchain Workers? +//! +//! Consider the fact that in principle, an offchain worker code written using the above API is no +//! different than an equivalent written with an _actual offchain interaction library_, such as +//! [Polkadot-JS](https://polkadot.js.org/docs/), or any of the other ones listed [here](https://github.com/substrate-developer-hub/awesome-substrate?tab=readme-ov-file#client-libraries). +//! +//! They can both read from the state, and have no means of updating the state, other than the route +//! of submitting an extrinsic to the chain. Therefore, it is worth thinking twice before embedding +//! a logic as a part of Substrate's offchain worker API. Does it have to be there? can it not be a +//! simple, actual offchain application that lives outside of the chain's WASM blob? +//! +//! Some of the reasons why you might want to do the opposite, and actually embed an offchain worker +//! API into the WASM blob are: +//! +//! * Accessing the state is easier within the `offchain_worker` function, as it is already a part +//! of the runtime, and [`frame::pallet_macros::storage`] provides all the tools needed to read +//! the state. Other client libraries might provide varying degrees of capability here. +//! * It will be updated in synchrony with the runtime. A Substrate's offchain application is part +//! of the same WASM blob, and is therefore guaranteed to be up to date. +//! +//! For example, imagine you have modified a storage item to have a new type. This will possibly +//! require a [`crate::reference_docs::frame_runtime_upgrades_and_migrations`], and any offchain +//! code, such as a Polkadot-JS application, will have to be updated to reflect this change. Whereas +//! the WASM offchain worker code is guaranteed to already be updated, or else the runtime code will +//! not even compile. +//! +//! +//! ## Further References +//! +//! - +//! - +//! - [Offchain worker example](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/examples/offchain-worker) diff --git a/docs/sdk/src/reference_docs/frame_origin.rs b/docs/sdk/src/reference_docs/frame_origin.rs new file mode 100644 index 0000000000000000000000000000000000000000..a2aac7dd3554fb614658f71a2bf88573379bbf9d --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_origin.rs @@ -0,0 +1,260 @@ +//! # FRAME Origin +//! +//! Let's start by clarifying a common wrong assumption about Origin: +//! +//! **ORIGIN IS NOT AN ACCOUNT ID**. +//! +//! FRAME's origin abstractions allow you to convey meanings far beyond just an account-id being the +//! caller of an extrinsic. Nonetheless, an account-id having signed an extrinsic is one of the +//! meanings that an origin can convey. This is the commonly used [`frame_system::ensure_signed`], +//! where the return value happens to be an account-id. +//! +//! Instead, let's establish the following as the correct definition of an origin: +//! +//! > The origin type represents the privilege level of the caller of an extrinsic. +//! +//! That is, an extrinsic, through checking the origin, can *express what privilege level it wishes +//! to impose on the caller of the extrinsic*. One of those checks can be as simple as "*any account +//! that has signed a statement can pass*". +//! +//! But the origin system can also express more abstract and complicated privilege levels. For +//! example: +//! +//! * If the majority of token holders agreed upon this. This is more or less what the +//! [`pallet_democracy`] does under the hood ([reference](https://github.com/paritytech/polkadot-sdk/blob/edd95b3749754d2ed0c5738588e872c87be91624/substrate/frame/democracy/src/lib.rs#L1603-L1633)). +//! * If a specific ratio of an instance of [`pallet_collective`]/DAO agrees upon this. +//! * If another consensus system, for example a bridged network or a parachain, agrees upon this. +//! * If the majority of validator/authority set agrees upon this[^1]. +//! * If caller holds a particular NFT. +//! +//! and many more. +//! +//! ## Context +//! +//! First, let's look at where the `origin` type is encountered in a typical pallet. The `origin: +//! OriginFor` has to be the first argument of any given callable extrinsic in FRAME: +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", call_simple)] +//! +//! Typically, the code of an extrinsic starts with an origin check, such as +//! [`frame_system::ensure_signed`]. +//! +//! Note that [`OriginFor`](frame_system::pallet_prelude::OriginFor) is merely a shorthand for +//! [`frame_system::Config::RuntimeOrigin`]. Given the name prefix `Runtime`, we can learn that +//! `RuntimeOrigin` is similar to `RuntimeCall` and others, a runtime composite enum that is +//! amalgamated at the runtime level. Read [`crate::reference_docs::frame_runtime_types`] to +//! familiarize yourself with these types. +//! +//! To understand this better, we will next create a pallet with a custom origin, which will add a +//! new variant to `RuntimeOrigin`. +//! +//! ## Adding Custom Pallet Origin to the Runtime +//! +//! For example, given a pallet that defines the following custom origin: +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin)] +//! +//! And a runtime with the following pallets: +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", runtime_exp)] +//! +//! The type [`crate::reference_docs::frame_origin::runtime_for_origin::RuntimeOrigin`] is expanded. +//! This `RuntimeOrigin` contains a variant for the [`frame_system::RawOrigin`] and the custom +//! origin of the pallet. +//! +//! > Notice how the [`frame_system::ensure_signed`] is nothing more than a `match` statement. If +//! > you want to know where the actual origin of an extrinsic is set (and the signature +//! > verification happens, if any), see +//! > [`sp_runtime::generic::CheckedExtrinsic#trait-implementations`], specifically +//! > [`sp_runtime::traits::Applyable`]'s implementation. +//! +//! ## Asserting on a Custom Internal Origin +//! +//! In order to assert on a custom origin that is defined within your pallet, we need a way to first +//! convert the `::RuntimeOrigin` into the local `enum Origin` of the +//! current pallet. This is a common process that is explained in +//! [`crate::reference_docs::frame_runtime_types# +//! adding-further-constraints-to-runtime-composite-enums`]. +//! +//! We use the same process here to express that `RuntimeOrigin` has a number of additional bounds, +//! as follows. +//! +//! 1. Defining a custom `RuntimeOrigin` with further bounds in the pallet. +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_bound)] +//! +//! 2. Using it in the pallet. +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_usage)] +//! +//! ## Asserting on a Custom External Origin +//! +//! Very often, a pallet wants to have a parameterized origin that is **NOT** defined within the +//! pallet. In other words, a pallet wants to delegate an origin check to something that is +//! specified later at the runtime level. Like many other parameterizations in FRAME, this implies +//! adding a new associated type to `trait Config`. +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_def)] +//! +//! Then, within the pallet, we can simply use this "unknown" origin check type: +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_usage)] +//! +//! Finally, at the runtime, any implementation of [`frame::traits::EnsureOrigin`] can be passed. +#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_provide)] +//! +//! Indeed, some of these implementations of [`frame::traits::EnsureOrigin`] are similar to the ones +//! that we know about: [`frame::runtime::prelude::EnsureSigned`], +//! [`frame::runtime::prelude::EnsureSignedBy`], [`frame::runtime::prelude::EnsureRoot`], +//! [`frame::runtime::prelude::EnsureNone`], etc. But, there are also many more that are not known +//! to us, and are defined in other pallets. +//! +//! For example, [`pallet_collective`] defines [`pallet_collective::EnsureMember`] and +//! [`pallet_collective::EnsureProportionMoreThan`] and many more, which is exactly what we alluded +//! to earlier in this document. +//! +//! Make sure to check the full list of [implementors of +//! `EnsureOrigin`](frame::traits::EnsureOrigin#implementors) for more inspiration. +//! +//! ## Obtaining Abstract Origins +//! +//! So far we have learned that FRAME pallets can assert on custom and abstract origin types, +//! whether they are defined within the pallet or not. But how can we obtain these abstract origins? +//! +//! > All extrinsics that come from the outer world can generally only be obtained as either +//! > `signed` or `none` origin. +//! +//! Generally, these abstract origins are only obtained within the runtime, when a call is +//! dispatched within the runtime. +//! +//! ## Further References +//! +//! - [Gavin Wood's speech about FRAME features at Protocol Berg 2023.](https://youtu.be/j7b8Upipmeg?si=83_XUgYuJxMwWX4g&t=195) +//! - [A related StackExchange question.](https://substrate.stackexchange.com/questions/10992/how-do-you-find-the-public-key-for-the-medium-spender-track-origin) +//! +//! [^1]: Inherents are essentially unsigned extrinsics that need an [`frame_system::ensure_none`] +//! origin check, and through the virtue of being an inherent, are agreed upon by all validators. + +use frame::prelude::*; + +#[frame::pallet(dev_mode)] +pub mod pallet_for_origin { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export(call_simple)] + #[pallet::call] + impl Pallet { + pub fn do_something(_origin: OriginFor) -> DispatchResult { + // ^^^^^^^^^^^^^^^^^^^^^ + todo!(); + } + } +} + +#[frame::pallet(dev_mode)] +pub mod pallet_with_custom_origin { + use super::*; + + #[docify::export(custom_origin_bound)] + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeOrigin: From<::RuntimeOrigin> + + Into::RuntimeOrigin>>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export(custom_origin)] + /// A dummy custom origin. + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] + pub enum Origin { + /// If all holders of a particular NFT have agreed upon this. + AllNftHolders, + /// If all validators have agreed upon this. + ValidatorSet, + } + + #[docify::export(custom_origin_usage)] + #[pallet::call] + impl Pallet { + pub fn only_validators(origin: OriginFor) -> DispatchResult { + // first, we convert from `::RuntimeOrigin` to `::RuntimeOrigin` + let local_runtime_origin = <::RuntimeOrigin as From< + ::RuntimeOrigin, + >>::from(origin); + // then we convert to `origin`, if possible + let local_origin = + local_runtime_origin.into().map_err(|_| "invalid origin type provided")?; + ensure!(matches!(local_origin, Origin::ValidatorSet), "Not authorized"); + todo!(); + } + } +} + +pub mod runtime_for_origin { + use super::pallet_with_custom_origin; + use frame::{runtime::prelude::*, testing_prelude::*}; + + #[docify::export(runtime_exp)] + construct_runtime!( + pub struct Runtime { + System: frame_system, + PalletWithCustomOrigin: pallet_with_custom_origin, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + impl pallet_with_custom_origin::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + } +} + +#[frame::pallet(dev_mode)] +pub mod pallet_with_external_origin { + use super::*; + #[docify::export(external_origin_def)] + #[pallet::config] + pub trait Config: frame_system::Config { + type ExternalOrigin: EnsureOrigin; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export(external_origin_usage)] + #[pallet::call] + impl Pallet { + pub fn externally_checked_ext(origin: OriginFor) -> DispatchResult { + let _ = T::ExternalOrigin::ensure_origin(origin)?; + todo!(); + } + } +} + +pub mod runtime_for_external_origin { + use super::*; + use frame::{runtime::prelude::*, testing_prelude::*}; + + construct_runtime!( + pub struct Runtime { + System: frame_system, + PalletWithExternalOrigin: pallet_with_external_origin, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + #[docify::export(external_origin_provide)] + impl pallet_with_external_origin::Config for Runtime { + type ExternalOrigin = EnsureSigned<::AccountId>; + } +} diff --git a/docs/sdk/src/reference_docs/frame_pallet_coupling.rs b/docs/sdk/src/reference_docs/frame_pallet_coupling.rs new file mode 100644 index 0000000000000000000000000000000000000000..cca7f9feb3f40ca9f933e9e89ed8739fa2a79b45 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_pallet_coupling.rs @@ -0,0 +1,296 @@ +//! # FRAME Pallet Coupling +//! +//! This reference document explains how FRAME pallets can be combined to interact together. +//! +//! It is suggested to re-read [`crate::polkadot_sdk::frame_runtime`], notably the information +//! around [`frame::pallet_macros::config`]. Recall that: +//! +//! > Configuration trait of a pallet: It allows a pallet to receive types at a later +//! > point from the runtime that wishes to contain it. It allows the pallet to be parameterized +//! > over both types and values. +//! +//! ## Context, Background +//! +//! FRAME pallets, as per described in [`crate::polkadot_sdk::frame_runtime`] are: +//! +//! > A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be +//! linked to other pallets. +//! +//! That is to say: +//! +//! * *encapsulated*: Ideally, a FRAME pallet contains encapsulated logic which has clear +//! boundaries. It is generally a bad idea to build a single monolithic pallet that does multiple +//! things, such as handling currencies, identities and staking all at the same time. +//! * *linked to other pallets*: But, adhering extensively to the above also hinders the ability to +//! write useful applications. Pallets often need to work with each other, communicate and use +//! each other's functionalities. +//! +//! The broad principle that allows pallets to be linked together is the same way through which a +//! pallet uses its `Config` trait to receive types and values from the runtime that contains it. +//! +//! There are generally two ways to achieve this: +//! +//! 1. Tight coupling pallets +//! 2. Loose coupling pallets +//! +//! To explain the difference between the two, consider two pallets, `A` and `B`. In both cases, `A` +//! wants to use some functionality exposed by `B`. +//! +//! When tightly coupling pallets, `A` can only exist in a runtime if `B` is also present in the +//! same runtime. That is, `A` is expressing that can only work if `B` is present. +//! +//! This translates to the following Rust code: +//! +//! ``` +//! trait Pallet_B_Config {} +//! trait Pallet_A_Config: Pallet_B_Config {} +//! ``` +//! +//! Contrary, when pallets are loosely coupled, `A` expresses that some functionality, expressed via +//! a trait `F`, needs to be fulfilled. This trait is then implemented by `B`, and the two pallets +//! are linked together at the runtime level. This means that `A` only relies on the implementation +//! of `F`, which may be `B`, or another implementation of `F`. +//! +//! This translates to the following Rust code: +//! +//! ``` +//! trait F {} +//! trait Pallet_A_Config { +//! type F: F; +//! } +//! // Pallet_B will implement and fulfill `F`. +//! ``` +//! +//! ## Example +//! +//! Consider the following example, in which `pallet-foo` needs another pallet to provide the block +//! author to it, and `pallet-author` which has access to this information. +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_foo)] +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author)] +//! +//! ### Tight Coupling Pallets +//! +//! To tightly couple `pallet-foo` and `pallet-author`, we use Rust's supertrait system. When a +//! pallet makes its own `trait Config` be bounded by another pallet's `trait Config`, it is +//! expressing two things: +//! +//! 1. that it can only exist in a runtime if the other pallet is also present. +//! 2. that it can use the other pallet's functionality. +//! +//! `pallet-foo`'s `Config` would then look like: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)] +//! +//! And `pallet-foo` can use the method exposed by `pallet_author::Pallet` directly: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)] +//! +//! +//! ### Loosely Coupling Pallets +//! +//! If `pallet-foo` wants to *not* rely on `pallet-author` directly, it can leverage its +//! `Config`'s associated types. First, we need a trait to express the functionality that +//! `pallet-foo` wants to obtain: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)] +//! +//! > We sometimes refer to such traits that help two pallets interact as "glue traits". +//! +//! Next, `pallet-foo` states that it needs this trait to be provided to it, at the runtime level, +//! via an associated type: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)] +//! +//! Then, `pallet-foo` can use this trait to obtain the block author, without knowing where it comes +//! from: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)] +//! +//! Then, if `pallet-author` implements this glue-trait: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author_provider)] +//! +//! And upon the creation of the runtime, the two pallets are linked together as such: +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)] +//! +//! Crucially, when using loose coupling, we gain the flexibility of providing different +//! implementations of `AuthorProvider`, such that different users of a `pallet-foo` can use +//! different ones, without any code change being needed. For example, in the code snippets of this +//! module, you can fund [`OtherAuthorProvider`] which is an alternative implementation of +//! [`AuthorProvider`]. +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)] +//! +//! A common pattern in polkadot-sdk is to provide an implementation of such glu traits for the unit +//! type as a "default/test behavior". +#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)] +//! +//! ## Frame System +//! +//! With the above information in context, we can conclude that **`frame_system` is a special pallet +//! that is tightly coupled with every other pallet**. This is because it provides the fundamental +//! system functionality that every pallet needs, such as some types like +//! [`frame::prelude::frame_system::Config::AccountId`], +//! [`frame::prelude::frame_system::Config::Hash`], and some functionality such as block number, +//! etc. +//! +//! ## Recap +//! +//! To recap, consider the following rules of thumb: +//! +//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In +//! general, it is easier to argue about multiple pallet if they only communicate together via a +//! known trait, rather than having access to all of each others public items, such as storage and +//! dispatchables. +//! * If a group of pallets are meant to work together, and but are not foreseen to be generalized, +//! or used by others, consider tightly coupling pallets, *if it simplifies the development*. +//! * If a pallet needs a functionality provided by another pallet, but multiple implementations can +//! be foreseen, consider loosely coupling pallets. +//! +//! For example, all pallets in `polkadot-sdk` that needed to work with currencies could have been +//! tightly coupled with [`pallet_balances`]. But, `polkadot-sdk` also provides [`pallet_assets`] +//! (and more implementations by the community), therefore all pallets use traits to loosely couple +//! with balances or assets pallet. More on this in [`crate::reference_docs::frame_currency`]. +//! +//! ## Further References +//! +//! - +//! - +//! +//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider +//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider + +#![allow(unused)] + +use frame::prelude::*; + +#[docify::export] +#[frame::pallet] +pub mod pallet_foo { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + impl Pallet { + fn do_stuff_with_author() { + // needs block author here + } + } +} + +#[docify::export] +#[frame::pallet] +pub mod pallet_author { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + impl Pallet { + pub fn author() -> T::AccountId { + todo!("somehow has access to the block author and can return it here") + } + } +} + +#[frame::pallet] +pub mod pallet_foo_tight { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export(tight_config)] + /// This pallet can only live in a runtime that has both `frame_system` and `pallet_author`. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_author::Config {} + + #[docify::export(tight_usage)] + impl Pallet { + // anywhere in `pallet-foo`, we can call into `pallet-author` directly, namely because + // `T: pallet_author::Config` + fn do_stuff_with_author() { + let _ = pallet_author::Pallet::::author(); + } + } +} + +#[docify::export] +/// Abstraction over "something that can provide the block author". +pub trait AuthorProvider { + fn author() -> AccountId; +} + +#[frame::pallet] +pub mod pallet_foo_loose { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[docify::export(loose_config)] + #[pallet::config] + pub trait Config: frame_system::Config { + /// This pallet relies on the existence of something that implements [`AuthorProvider`], + /// which may or may not be `pallet-author`. + type AuthorProvider: AuthorProvider; + } + + #[docify::export(loose_usage)] + impl Pallet { + fn do_stuff_with_author() { + let _ = T::AuthorProvider::author(); + } + } +} + +#[docify::export(pallet_author_provider)] +impl AuthorProvider for pallet_author::Pallet { + fn author() -> T::AccountId { + pallet_author::Pallet::::author() + } +} + +pub struct OtherAuthorProvider; + +#[docify::export(other_author_provider)] +impl AuthorProvider for OtherAuthorProvider { + fn author() -> AccountId { + todo!("somehow get the block author here") + } +} + +#[docify::export(unit_author_provider)] +impl AuthorProvider for () { + fn author() -> AccountId { + todo!("somehow get the block author here") + } +} + +pub mod runtime { + use super::*; + use cumulus_pallet_aura_ext::pallet; + use frame::{runtime::prelude::*, testing_prelude::*}; + + construct_runtime!( + pub struct Runtime { + System: frame_system, + PalletFoo: pallet_foo_loose, + PalletAuthor: pallet_author, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + impl pallet_author::Config for Runtime {} + + #[docify::export(runtime_author_provider)] + impl pallet_foo_loose::Config for Runtime { + type AuthorProvider = pallet_author::Pallet; + // which is also equivalent to + // type AuthorProvider = PalletAuthor; + } +} diff --git a/docs/sdk/src/reference_docs/frame_runtime_types.rs b/docs/sdk/src/reference_docs/frame_runtime_types.rs new file mode 100644 index 0000000000000000000000000000000000000000..32cda5bc5345d7eee43efa9e2b99ac831c8fb3a8 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_runtime_types.rs @@ -0,0 +1,306 @@ +//! # FRAME Runtime Types +//! +//! This reference document briefly explores the idea around types generated at the runtime level by +//! the FRAME macros. +//! +//! > As of now, many of these important types are generated within the internals of +//! > [`construct_runtime`], and there is no easy way for you to visually know they exist. +//! > [#polkadot-sdk#1378](https://github.com/paritytech/polkadot-sdk/pull/1378) is meant to +//! > significantly improve this. Exploring the rust-docs of a runtime, such as [`runtime`] which is +//! > defined in this module is as of now the best way to learn about these types. +//! +//! ## Composite Enums +//! +//! Many types within a FRAME runtime follow the following structure: +//! +//! * Each individual pallet defines a type, for example `Foo`. +//! * At the runtime level, these types are amalgamated into a single type, for example +//! `RuntimeFoo`. +//! +//! As the names suggest, all composite enums in a FRAME runtime start their name with `Runtime`. +//! For example, `RuntimeCall` is a representation of the most high level `Call`-able type in the +//! runtime. +//! +//! Composite enums are generally convertible to their individual parts as such: +#![doc = simple_mermaid::mermaid!("../../../mermaid/outer_runtime_types.mmd")] +//! +//! In that one can always convert from the inner type into the outer type, but not vice versa. This +//! is usually expressed by implementing `From`, `TryFrom`, `From>` and similar traits. +//! +//! ### Example +//! +//! We provide the following two pallets: [`pallet_foo`] and [`pallet_bar`]. Each define a +//! dispatchable, and `Foo` also defines a custom origin. Lastly, `Bar` defines an additional +//! `GenesisConfig`. +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_foo)] +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_bar)] +//! +//! Let's explore how each of these affect the [`RuntimeCall`], [`RuntimeOrigin`] and +//! [`RuntimeGenesisConfig`] generated in [`runtime`] by respectively. +//! +//! As observed, [`RuntimeCall`] has 3 variants, one for each pallet and one for `frame_system`. If +//! you explore further, you will soon realize that each variant is merely a pointer to the `Call` +//! type in each pallet, for example [`pallet_foo::Call`]. +//! +//! [`RuntimeOrigin`]'s [`OriginCaller`] has two variants, one for system, and one for `pallet_foo` +//! which utilized [`frame::pallet_macros::origin`]. +//! +//! Finally, [`RuntimeGenesisConfig`] is composed of `frame_system` and a variant for `pallet_bar`'s +//! [`pallet_bar::GenesisConfig`]. +//! +//! You can find other composite enums by scanning [`runtime`] for other types who's name starts +//! with `Runtime`. Some of the more noteworthy ones are: +//! +//! - [`RuntimeEvent`] +//! - [`RuntimeError`] +//! - [`RuntimeHoldReason`] +//! +//! ### Adding Further Constraints to Runtime Composite Enums +//! +//! This section explores a common scenario where a pallet has access to one of these runtime +//! composite enums, but it wishes to further specify it by adding more trait bounds to it. +//! +//! Let's take the example of `RuntimeCall`. This is an associated type in +//! [`frame_system::Config::RuntimeCall`], and all pallets have access to this type, because they +//! have access to [`frame_system::Config`]. Finally, this type is meant to be set to outer call of +//! the entire runtime. +//! +//! But, let's not forget that this is information that *we know*, and the Rust compiler does not. +//! All that the rust compiler knows about this type is *ONLY* what the trait bounds of +//! [`frame_system::Config::RuntimeCall`] are specifying: +#![doc = docify::embed!("../../substrate/frame/system/src/lib.rs", system_runtime_call)] +//! +//! So, when at a given pallet, one accesses `::RuntimeCall`, the type is +//! extremely opaque from the perspective of the Rust compiler. +//! +//! How can a pallet access the `RuntimeCall` type with further constraints? For example, each +//! pallet has its own `enum Call`, and knows that its local `Call` is a part of `RuntimeCall`, +//! therefore there should be a `impl From> for RuntimeCall`. +//! +//! The only way to express this using Rust's associated types is for the pallet to **define its own +//! associated type `RuntimeCall`, and further specify what it thinks `RuntimeCall` should be**. +//! +//! In this case, we will want to assert the existence of [`frame::traits::IsSubType`], which is +//! very similar to [`TryFrom`]. +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", custom_runtime_call)] +//! +//! And indeed, at the runtime level, this associated type would be the same `RuntimeCall` that is +//! passed to `frame_system`. +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", pallet_with_specific_runtime_call_impl)] +//! +//! > In other words, the degree of specificity that [`frame_system::Config::RuntimeCall`] has is +//! > not enough for the pallet to work with. Therefore, the pallet has to define its own associated +//! > type representing `RuntimeCall`. +//! +//! Another way to look at this is: +//! +//! `pallet_with_specific_runtime_call::Config::RuntimeCall` and `frame_system::Config::RuntimeCall` +//! are two different representations of the same concrete type that is only known when the runtime +//! is being constructed. +//! +//! Now, within this pallet, this new `RuntimeCall` can be used, and it can use its new trait +//! bounds, such as being [`frame::traits::IsSubType`]: +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", custom_runtime_call_usages)] +//! +//! ### Asserting Equality of Multiple Runtime Composite Enums +//! +//! Recall that in the above example, `::RuntimeCall` and `::RuntimeCall` are expected to be equal types, but at the compile-time we +//! have to represent them with two different associated types with different bounds. Would it not +//! be cool if we had a test to make sure they actually resolve to the same concrete type once the +//! runtime is constructed? The following snippet exactly does that: +#![doc = docify::embed!("./src/reference_docs/frame_runtime_types.rs", assert_equality)] +//! +//! We leave it to the reader to further explore what [`frame::traits::Hooks::integrity_test`] is, +//! and what [`core::any::TypeId`] is. Another way to assert this is using +//! [`frame::traits::IsType`]. +//! +//! ## Type Aliases +//! +//! A number of type aliases are generated by the `construct_runtime` which are also noteworthy: +//! +//! * [`runtime::PalletFoo`] is an alias to [`pallet_foo::Pallet`]. Same for `PalletBar`, and +//! `System` +//! * [`runtime::AllPalletsWithSystem`] is an alias for a tuple of all of the above. This type is +//! important to FRAME internals such as `executive`, as it implements traits such as +//! [`frame::traits::Hooks`]. +//! +//! ## Further Details +//! +//! * [`crate::reference_docs::frame_origin`] explores further details about the usage of +//! `RuntimeOrigin`. +//! * [`RuntimeCall`] is a particularly interesting composite enum as it dictates the encoding of an +//! extrinsic. See [`crate::reference_docs::signed_extensions`] for more information. +//! * See the documentation of [`construct_runtime`]. +//! * See the corresponding lecture in the [pba-book](https://polkadot-blockchain-academy.github.io/pba-book/frame/outer-enum/page.html). +//! +//! +//! [`construct_runtime`]: frame::runtime::prelude::construct_runtime +//! [`runtime::PalletFoo`]: crate::reference_docs::frame_runtime_types::runtime::PalletFoo +//! [`runtime::AllPalletsWithSystem`]: crate::reference_docs::frame_runtime_types::runtime::AllPalletsWithSystem +//! [`runtime`]: crate::reference_docs::frame_runtime_types::runtime +//! [`pallet_foo`]: crate::reference_docs::frame_runtime_types::pallet_foo +//! [`pallet_foo::Call`]: crate::reference_docs::frame_runtime_types::pallet_foo::Call +//! [`pallet_foo::Pallet`]: crate::reference_docs::frame_runtime_types::pallet_foo::Pallet +//! [`pallet_bar`]: crate::reference_docs::frame_runtime_types::pallet_bar +//! [`pallet_bar::GenesisConfig`]: crate::reference_docs::frame_runtime_types::pallet_bar::GenesisConfig +//! [`RuntimeEvent`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeEvent +//! [`RuntimeGenesisConfig`]: +//! crate::reference_docs::frame_runtime_types::runtime::RuntimeGenesisConfig +//! [`RuntimeOrigin`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeOrigin +//! [`OriginCaller`]: crate::reference_docs::frame_runtime_types::runtime::OriginCaller +//! [`RuntimeError`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeError +//! [`RuntimeCall`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeCall +//! [`RuntimeHoldReason`]: crate::reference_docs::frame_runtime_types::runtime::RuntimeHoldReason + +use frame::prelude::*; + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod pallet_foo { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] + pub enum Origin { + A, + B, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + pub fn foo(_origin: OriginFor) -> DispatchResult { + todo!(); + } + + pub fn other(_origin: OriginFor) -> DispatchResult { + todo!(); + } + } +} + +#[docify::export] +#[frame::pallet(dev_mode)] +pub mod pallet_bar { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub initial_account: Option, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } + + #[pallet::call] + impl Pallet { + pub fn bar(_origin: OriginFor) -> DispatchResult { + todo!(); + } + } +} + +pub mod runtime { + use super::{pallet_bar, pallet_foo}; + use frame::{runtime::prelude::*, testing_prelude::*}; + + #[docify::export(runtime_exp)] + construct_runtime!( + pub struct Runtime { + System: frame_system, + PalletFoo: pallet_foo, + PalletBar: pallet_bar, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + impl pallet_foo::Config for Runtime {} + impl pallet_bar::Config for Runtime {} +} + +#[frame::pallet(dev_mode)] +pub mod pallet_with_specific_runtime_call { + use super::*; + use frame::traits::IsSubType; + + #[docify::export(custom_runtime_call)] + /// A pallet that wants to further narrow down what `RuntimeCall` is. + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeCall: IsSubType>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // note that this pallet needs some `call` to have a `enum Call`. + #[pallet::call] + impl Pallet { + pub fn foo(_origin: OriginFor) -> DispatchResult { + todo!(); + } + } + + #[docify::export(custom_runtime_call_usages)] + impl Pallet { + fn _do_something_useful_with_runtime_call(call: ::RuntimeCall) { + // check if the runtime call given is of this pallet's variant. + let _maybe_my_call: Option<&Call> = call.is_sub_type(); + todo!(); + } + } + + #[docify::export(assert_equality)] + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + use core::any::TypeId; + assert_eq!( + TypeId::of::<::RuntimeCall>(), + TypeId::of::<::RuntimeCall>() + ); + } + } +} + +pub mod runtime_with_specific_runtime_call { + use super::pallet_with_specific_runtime_call; + use frame::{runtime::prelude::*, testing_prelude::*}; + + construct_runtime!( + pub struct Runtime { + System: frame_system, + PalletWithSpecificRuntimeCall: pallet_with_specific_runtime_call, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + type Block = MockBlock; + } + + #[docify::export(pallet_with_specific_runtime_call_impl)] + impl pallet_with_specific_runtime_call::Config for Runtime { + // an implementation of `IsSubType` is provided by `construct_runtime`. + type RuntimeCall = RuntimeCall; + } +} diff --git a/docs/sdk/src/reference_docs/frame_runtime_upgrades_and_migrations.rs b/docs/sdk/src/reference_docs/frame_runtime_upgrades_and_migrations.rs new file mode 100644 index 0000000000000000000000000000000000000000..7d870b432218e9e9a06c25f4f19e1f07ca72c6c6 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_runtime_upgrades_and_migrations.rs @@ -0,0 +1,138 @@ +//! # Runtime Upgrades +//! +//! At their core, blockchain logic consists of +//! +//! 1. on-chain state and +//! 2. a state transition function +//! +//! In Substrate-based blockchains, state transition functions are referred to as +//! [runtimes](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/blockchain_state_machines/index.html). +//! +//! Traditionally, before Substrate, upgrading state transition functions required node +//! operators to download new software and restart their nodes in a process called +//! [forking](https://en.wikipedia.org/wiki/Fork_(blockchain)). +//! +//! Substrate-based blockchains do not require forking, and instead upgrade runtimes +//! in a process called "Runtime Upgrades". +//! +//! Forkless runtime upgrades are a defining feature of the Substrate framework. Updating the +//! runtime logic without forking the code base enables your blockchain to seemlessly evolve +//! over time in a deterministic, rules-based manner. It also removes ambiguity for node operators +//! and other participants in the network about what is the canonical runtime. +//! +//! This capability is possible due to the runtime of a blockchain existing in on-chain storage. +//! +//! ## Performing a Runtime Upgrade +//! +//! To upgrade a runtime, an [`Origin`](frame_system::RawOrigin) with the necesarry permissions +//! (usually via governance) changes the `:code` storage. Usually, this is performed via a call to +//! [`set_code`] (or [`set_code_without_checks`]) with the desired new runtime blob, scheduled +//! using [`pallet_scheduler`]. +//! +//! Prior to building the new runtime, don't forget to update the +//! [`RuntimeVersion`](sp_version::RuntimeVersion). +//! +//! # Migrations +//! +//! It is often desirable to define logic to execute immediately after runtime upgrades (see +//! [this diagram](frame::traits::Hooks)). +//! +//! Self-contained pieces of logic that execute after a runtime upgrade are called "Migrations". +//! +//! The typical use case of a migration is to 'migrate' pallet storage from one layout to another, +//! for example when the encoding of a storage item is changed. However, they can also execute +//! arbitary logic such as: +//! +//! - Calling arbitrary pallet methods +//! - Mutating arbitrary on-chain state +//! - Cleaning up some old storage items that are no longer needed +//! +//! ## Single Block Migrations +//! +//! - Execute immediately and entirely at the beginning of the block following +//! a runtime upgrade. +//! - Are suitable for migrations which are guaranteed to not exceed the block weight. +//! - Are simply implementations of [`OnRuntimeUpgrade`]. +//! +//! To learn best practices for writing single block pallet storage migrations, see the +//! [Single Block Migration Example Pallet](pallet_example_single_block_migrations). +//! +//! ### Scheduling the Single Block Migrations to Run Next Runtime Upgrade +//! +//! Schedule migrations to run next runtime upgrade passing them as a generic parameter to your +//! [`Executive`](frame_executive) pallet: +//! +//! ```ignore +//! /// Tuple of migrations (structs that implement `OnRuntimeUpgrade`) +//! type Migrations = ( +//! pallet_example_storage_migration::migrations::v1::versioned::MigrateV0ToV1, +//! MyCustomMigration, +//! // ...more migrations here +//! ); +//! pub type Executive = frame_executive::Executive< +//! Runtime, +//! Block, +//! frame_system::ChainContext, +//! Runtime, +//! AllPalletsWithSystem, +//! Migrations, // <-- pass your migrations to Executive here +//! >; +//! ``` +//! +//! ### Ensuring Single Block Migration Safety +//! +//! "My migration unit tests pass, so it should be safe to deploy right?" +//! +//! No! Unit tests execute the migration in a very simple test environment, and cannot account +//! for the complexities of a real runtime or real on-chain state. +//! +//! Prior to deploying migrations, it is critical to perform additional checks to ensure that when +//! run in our real runtime they will not brick the chain due to: +//! - Panicing +//! - Touching too many storage keys and resulting in an excessively large PoV +//! - Taking too long to execute +//! +//! [`try-runtime-cli`](https://github.com/paritytech/try-runtime-cli) has a sub-command +//! [`on-runtime-upgrade`](https://paritytech.github.io/try-runtime-cli/try_runtime_core/commands/enum.Action.html#variant.OnRuntimeUpgrade) +//! which is designed to help with exactly this. +//! +//! Developers MUST run this command before deploying migrations to ensure they will not +//! inadvertently result in a bricked chain. +//! +//! It is recommended to run as part of your CI pipeline. See the +//! [polkadot-sdk check-runtime-migration job](https://github.com/paritytech/polkadot-sdk/blob/4a293bc5a25be637c06ce950a34490706597615b/.gitlab/pipeline/check.yml#L103-L124) +//! for an example of how to configure this. +//! +//! ### Note on the Manipulability of PoV Size and Execution Time +//! +//! While [`try-runtime-cli`](https://github.com/paritytech/try-runtime-cli) can help ensure with +//! very high certainty that a migration will succeed given **existing** on-chain state, it cannot +//! prevent a malicious actor from manipulating state in a way that will cause the migration to take +//! longer or produce a PoV much larger than previously measured. +//! +//! Therefore, it is important to write migrations in such a way that the execution time or PoV size +//! it adds to the block cannot be easily manipulated. e.g., do not iterate over storage that can +//! quickly or cheaply be bloated. +//! +//! If writing your migration in such a way is not possible, a multi block migration should be used +//! instead. +//! +//! ### Other useful tools +//! +//! [`Chopsticks`](https://github.com/AcalaNetwork/chopsticks) is another tool in the Substrate +//! ecosystem which developers may find useful to use in addition to `try-runtime-cli` when testing +//! their single block migrations. +//! +//! ## Multi Block Migrations +//! +//! Safely and easily execute long-running migrations across multiple blocks. +//! +//! Suitable for migrations which could use arbitrary amounts of block weight. +//! +//! TODO: Link to multi block migration example/s once PR is merged (). +//! +//! [`GetStorageVersion`]: frame_support::traits::GetStorageVersion +//! [`OnRuntimeUpgrade`]: frame_support::traits::OnRuntimeUpgrade +//! [`StorageVersion`]: frame_support::traits::StorageVersion +//! [`set_code`]: frame_system::Call::set_code +//! [`set_code_without_checks`]: frame_system::Call::set_code_without_checks diff --git a/docs/sdk/src/reference_docs/frame_system_accounts.rs b/docs/sdk/src/reference_docs/frame_system_accounts.rs new file mode 100644 index 0000000000000000000000000000000000000000..ae9d2c9e0cb3ca7a694d9e5330363c05f6f78cb0 --- /dev/null +++ b/docs/sdk/src/reference_docs/frame_system_accounts.rs @@ -0,0 +1,8 @@ +//! # FRAME Accounts +//! +//! How `frame_system` handles accountIds. Nonce. Consumers and Providers, reference counting. + +// - poorly understood topics, needs one great article to rul them all. +// - https://github.com/paritytech/substrate/issues/14425 +// - https://github.com/paritytech/substrate/pull/12951 +// - https://substrate.stackexchange.com/questions/263/what-is-the-meaning-of-the-account-provider-sufficients-and-consumer diff --git a/docs/sdk/src/reference_docs/glossary.rs b/docs/sdk/src/reference_docs/glossary.rs new file mode 100644 index 0000000000000000000000000000000000000000..56f5ef5aeb58dbea251d6829bfc17fc11c70d088 --- /dev/null +++ b/docs/sdk/src/reference_docs/glossary.rs @@ -0,0 +1,120 @@ +//! # Glossary +//! +//! #### State +//! +//! The data around which the blockchain network wishes to come to consensus. Also +//! referred to as "onchain data", "onchain storage" or sometimes just "storage". In UTXO based +//! blockchains, is referred to as "ledger". +//! +//! **Synonyms**: Onchain data, Onchain storage, Storage, Ledger +//! +//! #### State Transition Function +//! +//! The WASM Blob that dictates how the blockchain should transition its state upon encountering new +//! blocks. +//! +//! #### Host +//! +//! The environment that hosts and executes the [state transition function's WASM +//! blob](#state-transition-function). +//! +//! #### Node +//! +//! The full software artifact that contains the [host](#host), but importantly also all the other +//! modules needed to be part of a blockchain network, such as peer-to-peer networking, database and +//! such. +//! +//! **Synonyms**: Client +//! +//! #### Light Node +//! +//! Same as [node](#nodes), but when capable of following the network only through listening to +//! block headers. Usually capable of running in more constrained environments, such as an embedded +//! device, phone, or a web browser. +//! +//! **Synonyms**: Light Client +//! +//! #### Offchain +//! +//! Refers to operations conducted outside the blockchain's consensus mechanism. They are essential +//! for enhancing scalability and efficiency, enabling activities like data fetching and computation +//! without bloating the blockchain state. +//! +//! #### Host Functions: +//! +//! Host functions are the node's API, these are functions provided by the runtime environment (the +//! [host](#host)) to the Wasm runtime. These functions allow the Wasm code to interact with and +//! perform operations on the [node](#node), like accessing the blockchain state. +//! +//! #### Runtime API: +//! +//! This is the API of the runtime, it acts as a communication bridge between the runtime and the +//! node, serving as the exposed interface that facilitates their interactions. +//! +//! #### Dispatchable: +//! +//! Dispatchables are [function objects](https://en.wikipedia.org/wiki/Function_object) that act as +//! the entry points in [FRAME](frame) pallets. They can be called by internal or external entities +//! to interact with the blockchain's state. They are a core aspect of the runtime logic, handling +//! transactions and other state-changing operations. +//! +//! **Synonyms**: Callable +//! +//! #### Extrinsic +//! +//! An extrinsic is a general term for a piece of data that is originated outside of the runtime, +//! included into a block and leads to some action. This includes user-initiated transactions as +//! well as inherents which are placed into the block by the block-builder. +//! +//! #### Pallet +//! +//! Similar to software modules in traditional programming, [FRAME](frame) pallets in Substrate are +//! modular components that encapsulate distinct functionalities or business logic. Just as +//! libraries or modules are used to build and extend the capabilities of a software application, +//! pallets are the foundational building blocks for constructing a blockchain's runtime with frame. +//! They enable the creation of customizable and upgradeable networks, offering a composable +//! framework for a Substrate-based blockchain. Each pallet can be thought of as a plug-and-play +//! module, enhancing the blockchain's functionality in a cohesive and integrated manner. +//! +//! #### Full Node +//! +//! It is a node that prunes historical states, keeping only recent finalized block states to reduce +//! storage needs. Full nodes provide current chain state access and allow direct submission and +//! validation of extrinsics, maintaining network decentralization. +//! +//! #### Archive Node +//! +//! An archive node is a specialized node that maintains a complete history of all block states and +//! transactions. Unlike a full node, it does not prune historical data, ensuring full access to the +//! entire blockchain history. This makes it essential for detailed blockchain analysis and +//! historical queries, but requires significantly more storage capacity. +//! +//! #### Validator +//! +//! A validator is a node that participates in the consensus mechanism of the network. +//! Its role includes block production, transaction validation, network integrity and security +//! maintenance. +//! +//! #### Collator +//! +//! A collator is a node that is responsible for producing candidate blocks for the validators. +//! Collators are similar to validators on any other blockchain but, they do not need to provide +//! security guarantees as the Relay Chain handles this. +//! +//! #### Parachain +//! +//! Short for "parallelized chain" a parachain is a specialized blockchain that runs in parallel to +//! the Relay Chain (Polkadot, Kusama, etc.), benefiting from the shared security and +//! interoperability features of it. +//! +//! **Synonyms**: AppChain +//! +//! #### PVF +//! The Parachain Validation Function (PVF) is the current runtime Wasm for a parachain that is +//! stored on the Relay chain. It is an essential component in the Polkadot ecosystem, encapsulating +//! the validation logic for each parachain. The PVF is executed by validators to verify the +//! correctness of parachain blocks. This is critical for ensuring that each block follows the logic +//! set by its respective parachain, thus maintaining the integrity and security of the entire +//! network. +//! +//! **Synonyms**: Parachain Validation Function diff --git a/docs/sdk/src/reference_docs/light_nodes.rs b/docs/sdk/src/reference_docs/light_nodes.rs new file mode 100644 index 0000000000000000000000000000000000000000..d6670bf03ab1a8e36ef1d8b80717d1b3833daff5 --- /dev/null +++ b/docs/sdk/src/reference_docs/light_nodes.rs @@ -0,0 +1,7 @@ +//! # Light Clients +//! +//! +//! Notes: should contain only high level information about light clients, then link to how to set +//! it up in PAPI and SubXT +//! +//! diff --git a/docs/sdk/src/reference_docs/metadata.rs b/docs/sdk/src/reference_docs/metadata.rs new file mode 100644 index 0000000000000000000000000000000000000000..702c1c30fd9cf2d09082b39fecf880065ebd5375 --- /dev/null +++ b/docs/sdk/src/reference_docs/metadata.rs @@ -0,0 +1 @@ +//! # Metadata diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..de0b012bb1268537a0caa7f1f027f53fd887f6c0 --- /dev/null +++ b/docs/sdk/src/reference_docs/mod.rs @@ -0,0 +1,109 @@ +//! # Polkadot SDK Reference Docs. +//! +//! This is the entry point for all reference documents that enhance one's learning experience in +//! the Polkadot SDK. +//! +//! Note that this module also contains the [glossary](crate::reference_docs::glossary). +//! +//! ## What is a "reference document"? +//! +//! First, see [why we use rust-docs for everything](crate#why-rust-docs) and our documentation +//! [principles](crate#principles). We acknowledge that as much of the crucial information should be +//! embedded in the low level rust-docs. Then, high level scenarios should be covered in +//! [`crate::guides`]. Finally, we acknowledge that there is a category of information that is: +//! +//! 1. crucial to know. +//! 2. is too high level to be in the rust-doc of any one `type`, `trait` or `fn`. +//! 3. is too low level to be encompassed in a [`crate::guides`]. +//! +//! We call this class of documents "reference documents". Our goal should be to minimize the number +//! of "reference" docs, as they incur maintenance burden. + +/// Learn how Substrate and FRAME use traits and associated types to make modules generic in a +/// type-safe manner. +pub mod trait_based_programming; + +/// Learn about the way Substrate and FRAME view their blockchains as state machines. +pub mod blockchain_state_machines; + +/// The glossary. +pub mod glossary; + +/// Learn about the WASM meta-protocol of all Substrate-based chains. +pub mod wasm_meta_protocol; + +/// Learn about the differences between smart contracts and a FRAME-based runtime. They are both +/// "code stored onchain", but how do they differ? +pub mod runtime_vs_smart_contract; + +/// Learn about how extrinsics are encoded to be transmitted to a node and stored in blocks. +pub mod extrinsic_encoding; + +/// Learn about the signed extensions that form a part of extrinsics. +// TODO: @jsdw https://github.com/paritytech/polkadot-sdk-docs/issues/42 +pub mod signed_extensions; + +/// Learn about *Origins*, a topic in FRAME that enables complex account abstractions to be built. +pub mod frame_origin; + +/// Learn about how to write safe and defensive code in your FRAME runtime. +// TODO: @CrackTheCode016 https://github.com/paritytech/polkadot-sdk-docs/issues/44 +pub mod safe_defensive_programming; + +/// Learn about composite enums and other runtime level types, such as "RuntimeEvent" and +/// "RuntimeCall". +pub mod frame_runtime_types; + +/// Learn about how to make a pallet/runtime that is fee-less and instead uses another mechanism to +/// control usage and sybil attacks. +pub mod fee_less_runtime; + +/// Learn about metadata, the main means through which an upgradeable runtime communicates its +/// properties to the outside world. +// TODO: @jsdw https://github.com/paritytech/polkadot-sdk-docs/issues/47 +pub mod metadata; + +/// Learn about how frame-system handles `account-ids`, nonces, consumers and providers. +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; + +/// Learn about chain specification file and the genesis state of the blockchain. +// TODO: @michalkucharczyk https://github.com/paritytech/polkadot-sdk-docs/issues/51 +pub mod chain_spec_genesis; + +/// Learn about all the memory limitations of the WASM runtime when it comes to memory usage. +// TODO: @kianenigma https://github.com/paritytech/polkadot-sdk-docs/issues/52 +pub mod wasm_memory; + +/// Learn about Substrate's CLI, and how it can be extended. +// TODO: @kianenigma https://github.com/paritytech/polkadot-sdk-docs/issues/53 +pub mod cli; + +/// Learn about Substrate's consensus algorithms, and how you can switch between two. +// TODO: @JoshOrndorff @kianenigma https://github.com/paritytech/polkadot-sdk-docs/issues/54 +pub mod consensus_swapping; + +/// Learn about Runtime Upgrades and best practices for writing Migrations. +pub mod frame_runtime_upgrades_and_migrations; + +/// Learn about light nodes, how they function, and how Substrate-based chains come +/// light-node-first out of the box. +// TODO: @jsdw @josepot https://github.com/paritytech/polkadot-sdk-docs/issues/68 +pub mod light_nodes; + +/// Learn about the offchain workers, how they function, and how to use them, as provided by the +/// [`frame`] APIs. +pub mod frame_offchain_workers; + +/// Learn about the different ways through which multiple [`frame`] pallets can be combined to work +/// together. +pub mod frame_pallet_coupling; diff --git a/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs new file mode 100644 index 0000000000000000000000000000000000000000..099512cf4ee1254b9b1e522395b9cff9783d3e3d --- /dev/null +++ b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs @@ -0,0 +1,216 @@ +//! # Runtime vs. Smart Contracts +//! +//! *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. +//! +//! 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/docs/sdk/src/reference_docs/safe_defensive_programming.rs b/docs/sdk/src/reference_docs/safe_defensive_programming.rs new file mode 100644 index 0000000000000000000000000000000000000000..9d0f028e570d42d223e656cb7f46798d956fd173 --- /dev/null +++ b/docs/sdk/src/reference_docs/safe_defensive_programming.rs @@ -0,0 +1 @@ +//! diff --git a/docs/sdk/src/reference_docs/signed_extensions.rs b/docs/sdk/src/reference_docs/signed_extensions.rs new file mode 100644 index 0000000000000000000000000000000000000000..28b1426536bcdf371865db74a104a27d51869c86 --- /dev/null +++ b/docs/sdk/src/reference_docs/signed_extensions.rs @@ -0,0 +1,79 @@ +//! Signed extensions are, briefly, a means for different chains to extend the "basic" extrinsic +//! format with custom data that can be checked by the runtime. +//! +//! # Example +//! +//! Defining a couple of very simple signed extensions looks like the following: +#![doc = docify::embed!("./src/reference_docs/signed_extensions.rs", signed_extensions_example)] + +#[docify::export] +pub mod signed_extensions_example { + use parity_scale_codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::traits::SignedExtension; + + // This doesn't actually check anything, but simply allows + // some arbitrary `u32` to be added to the extrinsic payload + #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] + pub struct AddToPayload(pub u32); + + impl SignedExtension for AddToPayload { + const IDENTIFIER: &'static str = "AddToPayload"; + type AccountId = (); + type Call = (); + type AdditionalSigned = (); + type Pre = (); + + fn additional_signed( + &self, + ) -> Result< + Self::AdditionalSigned, + sp_runtime::transaction_validity::TransactionValidityError, + > { + Ok(()) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(()) + } + } + + // This is the opposite; nothing will be added to the extrinsic payload, + // but the AdditionalSigned type (`1234u32`) will be added to the + // payload to be signed. + #[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] + pub struct AddToSignaturePayload; + + impl SignedExtension for AddToSignaturePayload { + const IDENTIFIER: &'static str = "AddToSignaturePayload"; + type AccountId = (); + type Call = (); + type AdditionalSigned = u32; + type Pre = (); + + fn additional_signed( + &self, + ) -> Result< + Self::AdditionalSigned, + sp_runtime::transaction_validity::TransactionValidityError, + > { + Ok(1234) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(()) + } + } +} diff --git a/docs/sdk/src/reference_docs/trait_based_programming.rs b/docs/sdk/src/reference_docs/trait_based_programming.rs new file mode 100644 index 0000000000000000000000000000000000000000..ace3138807071a35dfaf4ea75e3321960f05137e --- /dev/null +++ b/docs/sdk/src/reference_docs/trait_based_programming.rs @@ -0,0 +1,229 @@ +//! # Trait-based Programming +//! +//! This document walks you over a peculiar way of using Rust's `trait` items. This pattern is +//! abundantly used within [`frame`] and is therefore paramount important for a smooth transition +//! into it. +//! +//! The rest of this document assumes familiarity with the +//! [Rust book's Advanced Traits](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html) +//! section. +//! Moreover, we use the [`frame::traits::Get`]. +//! +//! First, imagine we are writing a FRAME pallet. We represent this pallet with a `struct Pallet`, +//! and this pallet wants to implement the functionalities of that pallet, for example a simple +//! `transfer` function. For the sake of education, we are interested in having a `MinTransfer` +//! amount, expressed as a [`frame::traits::Get`], which will dictate what is the minimum amount +//! that can be transferred. +//! +//! We can foremost write this as simple as the following snippet: +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", basic)] +//! +//! +//! In this example, we use arbitrary choices for `AccountId`, `Balance` and the `MinTransfer` type. +//! This works great for **one team's purposes** but we have to remember that Substrate and FRAME +//! are written as generic frameworks, intended to be highly configurable. +//! +//! In a broad sense, there are two avenues in exposing configurability: +//! +//! 1. For *values* that need to be generic, for example `MinTransfer`, we attach them to the +//! `Pallet` struct as fields: +//! +//! ``` +//! struct Pallet { +//! min_transfer: u128, +//! } +//! ``` +//! +//! 2. For *types* that need to be generic, we would have to use generic or associated types, such +//! as: +//! +//! ``` +//! struct Pallet { +//! min_transfer: u128, +//! _marker: std::marker::PhantomData, +//! } +//! ``` +//! +//! Substrate and FRAME, for various reasons (performance, correctness, type safety) has opted to +//! use *types* to declare both *values* and *types* as generic. This is the essence of why the +//! `Get` trait exists. +//! +//! This would bring us to the second iteration of the pallet, which would look like: +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", generic)] +//! +//! In this example, we managed to make all 3 of our types generic. Taking the example of the +//! `AccountId`, one should read the above as following: +//! +//! > The `Pallet` does not know what type `AccountId` concretely is, but it knows that it is +//! > something that adheres to being `From<[u8; 32]>`. +//! +//! This method would work, but it suffers from two downsides: +//! +//! 1. It is verbose, each `impl` block would have to reiterate all of the trait bounds. +//! 2. It cannot easily share/inherit generic types. Imagine multiple pallets wanting to be generic +//! over a single `AccountId`. There is no easy way to express that in this model. +//! +//! Finally, this brings us to using traits and associated types on traits to express the above. +//! Trait associated types have the benefit of: +//! +//! 1. Being less verbose, as in effect they can *group multiple `type`s together*. +//! 2. Can inherit from one another by declaring +//! [supertraits](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html). +//! +//! > Interestingly, one downside of associated types is that declaring defaults on them is not +//! > stable yet. In the meantime, we have built our own custom mechanics around declaring defaults +//! for associated types, see [`pallet_default_config_example`]. +//! +//! The last iteration of our code would look like this: +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", trait_based)] +//! +//! Notice how instead of having multiple generics, everything is generic over a single ``, and all types are fetched through `T`, for example `T::AccountId`, `T::MinTransfer`. +//! +//! Finally, imagine all pallets wanting to be generic over `AccountId`. This can be achieved by +//! having individual `trait Configs` declare a shared `trait SystemConfig` as their +//! [supertrait](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html). +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", with_system)] +//! In FRAME, this shared supertrait is [`frame::prelude::frame_system`]. +//! +//! Notice how this made no difference in the syntax of the rest of the code. `T::AccountId` is +//! still a valid type, since `T` implements `Config` and `Config` implies `SystemConfig`, which +//! has a `type AccountId`. +//! +//! Note, in some instances one would need to use what is known as the fully-qualified-syntax to +//! access a type to help the Rust compiler disambiguate. +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified)] +//! +//! This syntax can sometimes become more complicated when you are dealing with nested traits. +//! Consider the following example, in which we fetch the `type Balance` from another trait +//! `CurrencyTrait`. +#![doc = docify::embed!("./src/reference_docs/trait_based_programming.rs", fully_qualified_complicated)] +//! +//! Notice the final `type BalanceOf` and how it is defined. Using such aliases to shorten the +//! length of fully qualified syntax is a common pattern in FRAME. +//! +//! The above example is almost identical to the well-known (and somewhat notorious) `type +//! BalanceOf` that is often used in the context of [`frame::traits::fungible`]. +#![doc = docify::embed!("../../substrate/frame/fast-unstake/src/types.rs", BalanceOf)] +//! +//! ## Additional Resources +//! +//! - +//! - [Substrate Seminar - Traits and Generic Types](https://www.youtube.com/watch?v=6cp10jVWNl4) +//! - +#![allow(unused)] + +use frame::traits::Get; + +#[docify::export] +mod basic { + struct Pallet; + + type AccountId = frame::deps::sp_runtime::AccountId32; + type Balance = u128; + type MinTransfer = frame::traits::ConstU128<10>; + + impl Pallet { + fn transfer(_from: AccountId, _to: AccountId, _amount: Balance) { + todo!() + } + } +} + +#[docify::export] +mod generic { + use super::*; + + struct Pallet { + _marker: std::marker::PhantomData<(AccountId, Balance, MinTransfer)>, + } + + impl Pallet + where + Balance: frame::traits::AtLeast32BitUnsigned, + MinTransfer: frame::traits::Get, + AccountId: From<[u8; 32]>, + { + fn transfer(_from: AccountId, _to: AccountId, amount: Balance) { + assert!(amount >= MinTransfer::get()); + unimplemented!(); + } + } +} + +#[docify::export] +mod trait_based { + use super::*; + + trait Config { + type AccountId: From<[u8; 32]>; + type Balance: frame::traits::AtLeast32BitUnsigned; + type MinTransfer: frame::traits::Get; + } + + struct Pallet(std::marker::PhantomData); + impl Pallet { + fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) { + assert!(amount >= T::MinTransfer::get()); + unimplemented!(); + } + } +} + +#[docify::export] +mod with_system { + use super::*; + + pub trait SystemConfig { + type AccountId: From<[u8; 32]>; + } + + pub trait Config: SystemConfig { + type Balance: frame::traits::AtLeast32BitUnsigned; + type MinTransfer: frame::traits::Get; + } + + pub struct Pallet(std::marker::PhantomData); + impl Pallet { + fn transfer(_from: T::AccountId, _to: T::AccountId, amount: T::Balance) { + assert!(amount >= T::MinTransfer::get()); + unimplemented!(); + } + } +} + +#[docify::export] +mod fully_qualified { + use super::with_system::*; + + // Simple of using fully qualified syntax. + type AccountIdOf = ::AccountId; +} + +#[docify::export] +mod fully_qualified_complicated { + use super::with_system::*; + + trait CurrencyTrait { + type Balance: frame::traits::AtLeast32BitUnsigned; + fn more_stuff() {} + } + + trait Config: SystemConfig { + type Currency: CurrencyTrait; + } + + struct Pallet(std::marker::PhantomData); + impl Pallet { + fn transfer( + _from: T::AccountId, + _to: T::AccountId, + _amount: <::Currency as CurrencyTrait>::Balance, + ) { + unimplemented!(); + } + } + + /// A common pattern in FRAME. + type BalanceOf = <::Currency as CurrencyTrait>::Balance; +} diff --git a/docs/sdk/src/reference_docs/wasm_memory.rs b/docs/sdk/src/reference_docs/wasm_memory.rs new file mode 100644 index 0000000000000000000000000000000000000000..4f4cda31094e40e8f308dbb06de4ab0ecd828777 --- /dev/null +++ b/docs/sdk/src/reference_docs/wasm_memory.rs @@ -0,0 +1,7 @@ +//! # WASM Memory Limitations. +//! +//! Notes: +//! +//! - Stack: Need to use `Box<_>` +//! - Heap: Substrate imposes a limit. PvF execution has its own limits +//! - Heap: There is also a maximum amount that a single allocation can have. diff --git a/docs/sdk/src/reference_docs/wasm_meta_protocol.rs b/docs/sdk/src/reference_docs/wasm_meta_protocol.rs new file mode 100644 index 0000000000000000000000000000000000000000..37d1460f0e1a3737217ac0c80ec41de769db4c1a --- /dev/null +++ b/docs/sdk/src/reference_docs/wasm_meta_protocol.rs @@ -0,0 +1,113 @@ +//! # WASM Meta Protocol +//! +//! All Substrate based chains adhere to a unique architectural design novel to the Polkadot +//! ecosystem. We refer to this design as the "WASM Meta Protocol". +//! +//! Consider the fact that a traditional blockchain software is usually a monolithic artifact. +//! Upgrading any part of the system implies upgrading the entire system. This has historically led +//! to cumbersome forkful upgrades to be the status quo in the blockchain ecosystem. +//! +//! Moreover, the idea of "storing code in the state" is explored in the context of smart contracts +//! platforms, but has not been expanded further. +//! +//! Substrate mixes these two ideas together, and takes the novel approach of storing the +//! blockchain's main "state transition function" in the main blockchain state, in the same fashion +//! that a smart contract platform stores the code of individual contracts in its state. As noted in +//! [`crate::reference_docs::blockchain_state_machines`], this state transition function is called +//! the **Runtime**, and WASM is chosen as the bytecode. The Runtime is stored under a special key +//! in the state (see +//! [`sp_core::storage::well_known_keys`](../../../sp_core/index.html)) and can be +//! updated as a part of the state transition function's execution, just like a user's account +//! balance can be updated. +//! +//! > Note that while we drew an analogy between smart contracts and runtimes in the above, there +//! > are fundamental differences between the two, explained in +//! > [`crate::reference_docs::runtime_vs_smart_contract`]. +//! +//! The rest of the system that is NOT the state transition function is called the **node**, and +//! is a normal binary that is compiled from Rust to different hardware targets. +//! +//! This design enables all Substrate-based chains to be fork-less-ly upgradeable, because the +//! Runtime can be updates on the fly, within the execution of a block, and the node is (for the +//! most part) oblivious to the change that is happening. +//! +//! Therefore, the high-level architecture of a any Substrate-based chain can be demonstrated as +//! follows: +#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_simple.mmd")] +//! +//! The node and the runtime need to communicate. This is done through two concepts: +//! +//! 1. **Host functions**: a way for the (WASM) runtime to talk to the node. All host functions are +//! defined in [`sp_io`]. For example, [`sp_io::storage`] are the set of host functions that +//! allow the runtime to read and write data to the on-chain state. +//! 2. **Runtime APIs**: a way for the node to talk to the WASM runtime. Runtime APIs are defined +//! using macros and utilities in [`sp_api`]. For example, [`sp_api::Core`] is the most +//! fundamental runtime API that any blockchain must implement in order to be able to (re) +//! execute blocks. +#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_client_runtime.mmd")] +//! +//! A runtime must have a set of runtime APIs in order to have any meaningful blockchain +//! functionality, but it can also expose more APIs. See TODO as an example of how to add custom +//! runtime APIs to your FRAME-based runtime. +//! +//! Similarly, for a runtime to be "compatible" with a node, the node must implement the full set of +//! host functions that the runtime at any point in time requires. Given the fact that a runtime can +//! evolve in time, and a blockchain node (typically) wishes to be capable of re-executing all the +//! previous blocks, this means that a node must always maintain support for the old host functions. +//! This also implies that adding a new host function is a big commitment and should be done with +//! care. This is why, for example, adding a new host function to Polkadot always requires an RFC. +//! +//! ## Node vs. Runtime +//! +//! A common question is: which components of the system end up being part of the node, and which +//! ones of the runtime? +//! +//! Recall from [`crate::reference_docs::blockchain_state_machines`] that the runtime is the state +//! transition function. Anything that needs to influence how your blockchain's state is updated, +//! should be a part of the runtime. For example, the logic around currency, governance, identity or +//! any other application-specific logic that has to do with the state is part of the runtime. +//! +//! Anything that does not have to do with the state-transition function and will only +//! facilitate/enable it is part of the node. For example, the database, networking, and even +//! consensus algorithm are all node-side components. +//! +//! > The consensus is to your runtime what HTTP is to a web-application. It is the underlying +//! > engine that enables trustless execution of the runtime in a distributed manner whilst +//! > maintaining a canonical outcome of that execution. +#![doc = simple_mermaid::mermaid!("../../../mermaid/substrate_with_frame.mmd")] +//! +//! ## State +//! +//! From the previous sections, we know that the a database component is part of the node, not the +//! runtime. We also hinted that a set of host functions ([`sp_io::storage`]) are how the runtime +//! issues commands to the node to read/write to the state. Let's dive deeper into this. +//! +//! The state of the blockchain, what we seek to come to consensus about, is indeed *kept* in the +//! node side. Nonetheless, the runtime is the only component that: +//! +//! 1. Can update the state. +//! 2. Can fully interpret the state. +//! +//! In fact, [`sp_core::storage::well_known_keys`] are the only state keys that the node side is +//! aware of. The rest of the state, including what logic the runtime has, what balance each user +//! has and such are all only comprehensible to the runtime. +#![doc = simple_mermaid::mermaid!("../../../mermaid/state.mmd")] +//! +//! In the above diagram, all of the state keys and values are opaque bytes to the node. The node +//! does not know what they mean, and it does not now what is the type of the corresponding value +//! (e.g. if it is a number of a vector). Contrary, the runtime knows both the meaning of their +//! keys, and the type of the values. +//! +//! This opaque-ness is the fundamental reason why Substrate-based chains can fork-less-ly upgrade: +//! because the node side code is kept oblivious to all of the details of the state transition +//! function. Therefore, the state transition function can freely upgrade without the node needing +//! to know. +//! +//! ## Native Runtime +//! +//! TODO +//! +//! +//! ## Example: Block Execution. +//! +//! TODO diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml index 6e82cb69f6ecd6c4db535c9797a4ea7fde9d67c1..b0d71a18eaa13f8e7d188ca6420effa9dde4bb6b 100644 --- a/polkadot/Cargo.toml +++ b/polkadot/Cargo.toml @@ -18,15 +18,18 @@ rust-version = "1.64.0" readme = "README.md" authors.workspace = true edition.workspace = true -version = "1.1.0" +version = "6.0.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 } +tikv-jemallocator = { version = "0.5.0", optional = true, features = ["unprefixed_malloc_on_supported_platforms"] } # Crates in our workspace, defined as dependencies so we can pass them feature flags. -polkadot-cli = { path = "cli", features = [ "westend-native", "rococo-native" ] } +polkadot-cli = { path = "cli", features = ["rococo-native", "westend-native"] } polkadot-node-core-pvf = { path = "node/core/pvf" } polkadot-node-core-pvf-prepare-worker = { path = "node/core/pvf/prepare-worker" } polkadot-overseer = { path = "node/overseer" } @@ -36,7 +39,7 @@ polkadot-node-core-pvf-common = { path = "node/core/pvf/common" } polkadot-node-core-pvf-execute-worker = { path = "node/core/pvf/execute-worker" } [target.'cfg(target_os = "linux")'.dependencies] -tikv-jemallocator = "0.5.0" +tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"] } [dev-dependencies] assert_cmd = "2.0.4" @@ -53,11 +56,11 @@ substrate-build-script-utils = { path = "../substrate/utils/build-script-utils" maintenance = { status = "actively-developed" } [features] -runtime-benchmarks = [ "polkadot-cli/runtime-benchmarks" ] -try-runtime = [ "polkadot-cli/try-runtime" ] -fast-runtime = [ "polkadot-cli/fast-runtime" ] -runtime-metrics = [ "polkadot-cli/runtime-metrics" ] -pyroscope = [ "polkadot-cli/pyroscope" ] +runtime-benchmarks = ["polkadot-cli/runtime-benchmarks"] +try-runtime = ["polkadot-cli/try-runtime"] +fast-runtime = ["polkadot-cli/fast-runtime"] +runtime-metrics = ["polkadot-cli/runtime-metrics"] +pyroscope = ["polkadot-cli/pyroscope"] jemalloc-allocator = [ "dep:tikv-jemallocator", "polkadot-node-core-pvf-prepare-worker/jemalloc-allocator", @@ -65,9 +68,10 @@ jemalloc-allocator = [ "polkadot-overseer/jemalloc-allocator", ] + # Enables timeout-based tests supposed to be run only in CI environment as they may be flaky # when run locally depending on system load -ci-only-tests = [ "polkadot-node-core-pvf/ci-only-tests" ] +ci-only-tests = ["polkadot-node-core-pvf/ci-only-tests"] # Configuration for building a .deb package - for use with `cargo-deb` [package.metadata.deb] @@ -87,12 +91,12 @@ assets = [ [ "target/release/polkadot-prepare-worker", "/usr/lib/polkadot/", - "755" + "755", ], [ "target/release/polkadot-execute-worker", "/usr/lib/polkadot/", - "755" + "755", ], [ "scripts/packaging/polkadot.service", diff --git a/polkadot/README.md b/polkadot/README.md index 3c234bb8e3f4d3bed19331047f42c9afa2658053..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) @@ -63,7 +65,6 @@ directory of the repo: ```bash git checkout -./scripts/init.sh cargo build --release ``` @@ -213,7 +214,7 @@ that we currently maintain. ### Using Docker -[Using Docker](../docs/docker.md) +[Using Docker](../docs/contributor/docker.md) ### Shell Completion @@ -223,11 +224,11 @@ that we currently maintain. ### Contributing Guidelines -[Contribution Guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CONTRIBUTING.md) +[Contribution Guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) ### Contributor Code of Conduct -[Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CODE_OF_CONDUCT.md) +[Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md) ## License diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 9cad7e88dfe92fd5587b71ffc030ffa56c5cf007..f57efa7ba4361ecdcfa40eb1d63dabb4a0343767 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-cli" description = "Polkadot Relay-chain Client Node" -version = "1.1.0" +version = "7.0.0" 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. @@ -15,9 +18,10 @@ wasm-opt = false crate-type = ["cdylib", "rlib"] [dependencies] -clap = { version = "4.4.6", features = ["derive"], optional = true } -log = "0.4.17" -thiserror = "1.0.48" +cfg-if = "1.0" +clap = { version = "4.5.3", features = ["derive"], optional = true } +log = { workspace = true, default-features = true } +thiserror = { workspace = true } futures = "0.3.21" pyro = { package = "pyroscope", version = "0.5.3", optional = true } pyroscope_pprofrs = { version = "0.2", optional = true } @@ -38,13 +42,14 @@ sc-tracing = { path = "../../substrate/client/tracing", optional = true } sc-sysinfo = { path = "../../substrate/client/sysinfo" } sc-executor = { path = "../../substrate/client/executor" } sc-storage-monitor = { path = "../../substrate/client/storage-monitor" } +sp-runtime = { path = "../../substrate/primitives/runtime" } [build-dependencies] substrate-build-script-utils = { path = "../../substrate/utils/build-script-utils" } [features] -default = [ "cli", "db", "full-node" ] -db = [ "service/db" ] +default = ["cli", "db", "full-node"] +db = ["service/db"] cli = [ "clap", "frame-benchmarking-cli", @@ -59,17 +64,22 @@ runtime-benchmarks = [ "polkadot-node-metrics/runtime-benchmarks", "sc-service?/runtime-benchmarks", "service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +full-node = ["service/full-node"] +try-runtime = [ + "service/try-runtime", + "sp-runtime/try-runtime", + "try-runtime-cli/try-runtime", ] -full-node = [ "service/full-node" ] -try-runtime = [ "service/try-runtime", "try-runtime-cli/try-runtime" ] -fast-runtime = [ "service/fast-runtime" ] -pyroscope = [ "pyro", "pyroscope_pprofrs" ] +fast-runtime = ["service/fast-runtime"] +pyroscope = ["pyro", "pyroscope_pprofrs"] # Configure the native runtimes to use. -westend-native = [ "service/westend-native" ] -rococo-native = [ "service/rococo-native" ] +westend-native = ["service/westend-native"] +rococo-native = ["service/rococo-native"] -malus = [ "full-node", "service/malus" ] +malus = ["full-node", "service/malus"] runtime-metrics = [ "polkadot-node-metrics/runtime-metrics", "service/runtime-metrics", diff --git a/polkadot/cli/src/cli.rs b/polkadot/cli/src/cli.rs index bc060c21fba70b55e0652bb853b8e40d50f15a52..e1bc2309a942a8255dd5c99a0db0725aa3b1b37d 100644 --- a/polkadot/cli/src/cli.rs +++ b/polkadot/cli/src/cli.rs @@ -52,7 +52,7 @@ pub enum Subcommand { /// Try-runtime has migrated to a standalone CLI /// (). The subcommand exists as a stub and - /// deprecation notice. It will be removed entirely some time after Janurary 2024. + /// deprecation notice. It will be removed entirely some time after January 2024. TryRuntime, /// Key management CLI utilities @@ -82,31 +82,31 @@ pub struct RunCmd { #[arg(long = "force-rococo")] pub force_rococo: bool, - /// Setup a GRANDPA scheduled voting pause. + /// Disable the BEEFY gadget. /// - /// This parameter takes two values, namely a block number and a delay (in - /// blocks). After the given block number is finalized the GRANDPA voter - /// will temporarily stop voting for new blocks until the given delay has - /// elapsed (i.e. until a block at height `pause_block + delay` is imported). - #[arg(long = "grandpa-pause", num_args = 2)] - pub grandpa_pause: Vec, - - /// Disable the BEEFY gadget - /// (currently enabled by default on Rococo, Wococo and Versi). + /// Currently enabled by default on 'Rococo', 'Wococo' and 'Versi'. #[arg(long)] pub no_beefy: bool, - /// Add the destination address to the jaeger agent. + /// Allows a validator to run insecurely outside of Secure Validator Mode. Security features + /// are still enabled on a best-effort basis, but missing features are no longer required. For + /// more information see . + #[arg(long = "insecure-validator-i-know-what-i-do", requires = "validator")] + pub insecure_validator: bool, + + /// Enable the block authoring backoff that is triggered when finality is lagging. + #[arg(long)] + pub force_authoring_backoff: bool, + + /// Add the destination address to the 'Jaeger' agent. /// - /// Must be valid socket address, of format `IP:Port` - /// commonly `127.0.0.1:6831`. + /// Must be valid socket address, of format `IP:Port` (commonly `127.0.0.1:6831`). #[arg(long)] pub jaeger_agent: Option, /// Add the destination address to the `pyroscope` agent. /// - /// Must be valid socket address, of format `IP:Port` - /// commonly `127.0.0.1:4040`. + /// Must be valid socket address, of format `IP:Port` (commonly `127.0.0.1:4040`). #[arg(long)] pub pyroscope_server: Option, @@ -126,10 +126,13 @@ pub struct RunCmd { #[arg(long)] pub overseer_channel_capacity_override: Option, - /// Path to the directory where auxiliary worker binaries reside. If not specified, the main - /// binary's directory is searched first, then `/usr/lib/polkadot` is searched. TESTING ONLY: - /// if the path points to an executable rather then directory, that executable is used both as - /// preparation and execution worker. + /// Path to the directory where auxiliary worker binaries reside. + /// + /// If not specified, the main binary's directory is searched first, then + /// `/usr/lib/polkadot` is searched. + /// + /// TESTING ONLY: if the path points to an executable rather then directory, + /// that executable is used both as preparation and execution worker. #[arg(long, value_name = "PATH")] pub workers_path: Option, diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 3da6e54b812b2ab31c0d34bd46ee32b2f2f7b937..f71891ecde34c0caea9f0b0372f2eb7a1d648e7a 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, @@ -50,7 +50,7 @@ impl SubstrateCli for Cli { fn impl_version() -> String { let commit_hash = env!("SUBSTRATE_CLI_COMMIT_HASH"); - format!("{NODE_VERSION}-{commit_hash}") + format!("{}-{commit_hash}", NODE_VERSION) } fn description() -> String { @@ -91,6 +91,7 @@ impl SubstrateCli for Cli { "polkadot" => Box::new(service::chain_spec::polkadot_config()?), name if name.starts_with("polkadot-") && !name.ends_with(".json") => Err(format!("`{name}` is not supported anymore as the polkadot native runtime no longer part of the node."))?, + "paseo" => Box::new(service::chain_spec::paseo_config()?), "rococo" => Box::new(service::chain_spec::rococo_config()?), #[cfg(feature = "rococo-native")] "dev" | "rococo-dev" => Box::new(service::chain_spec::rococo_development_config()?), @@ -196,31 +197,10 @@ 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); - let grandpa_pause = if cli.run.grandpa_pause.is_empty() { - None - } else { - Some((cli.run.grandpa_pause[0], cli.run.grandpa_pause[1])) - }; - if chain_spec.is_kusama() { info!("----------------------------"); info!("This chain is not in any way"); @@ -244,6 +224,8 @@ where let node_version = if cli.run.disable_worker_version_check { None } else { Some(NODE_VERSION.to_string()) }; + let secure_validator_mode = cli.run.base.validator && !cli.run.insecure_validator; + runner.run_node_until_exit(move |config| async move { let hwbench = (!cli.run.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { @@ -257,11 +239,12 @@ where config, service::NewFullParams { is_parachain_node: service::IsParachainNode::No, - grandpa_pause, enable_beefy, + force_authoring_backoff: cli.run.force_authoring_backoff, jaeger_agent, telemetry_worker_handle: None, node_version, + secure_validator_mode, workers_path: cli.run.workers_path, workers_names: None, overseer_gen, @@ -274,11 +257,13 @@ where ) .map(|full| full.task_manager)?; - sc_storage_monitor::StorageMonitorService::try_spawn( - cli.storage_monitor, - database_source, - &task_manager.spawn_essential_handle(), - )?; + if let Some(path) = database_source.path() { + sc_storage_monitor::StorageMonitorService::try_spawn( + cli.storage_monitor, + path.to_path_buf(), + &task_manager.spawn_essential_handle(), + )?; + } Ok(task_manager) }) @@ -315,7 +300,7 @@ pub fn run() -> Result<()> { match &cli.subcommand { None => run_node_inner( cli, - service::RealOverseerGen, + service::ValidatorOverseerGen, None, polkadot_node_metrics::logger_hook(), ), @@ -466,7 +451,7 @@ pub fn run() -> Result<()> { if cfg!(feature = "runtime-benchmarks") { runner.sync_run(|config| { - cmd.run::(config) + cmd.run::, ()>(config) .map_err(|e| Error::SubstrateCli(e)) }) } else { diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs index 35a467146b428e60462ef36f86d65651b03c40ed..4bb0dfb7583543937380d21da249bc612b842aba 100644 --- a/polkadot/cli/src/lib.rs +++ b/polkadot/cli/src/lib.rs @@ -29,7 +29,7 @@ mod error; pub use service::{self, Block, CoreApi, IdentifyVariant, ProvideRuntimeApi, TFullClient}; #[cfg(feature = "malus")] -pub use service::overseer::prepared_overseer_builder; +pub use service::overseer::validator_overseer_builder; #[cfg(feature = "cli")] pub use cli::*; diff --git a/polkadot/core-primitives/Cargo.toml b/polkadot/core-primitives/Cargo.toml index f843ec17943cbaddc886f73b33fc84bf53bb644e..d3aef89cb74d01cfe3293c500d981a38b09b6a8d 100644 --- a/polkadot/core-primitives/Cargo.toml +++ b/polkadot/core-primitives/Cargo.toml @@ -1,20 +1,23 @@ [package] name = "polkadot-core-primitives" -version = "1.0.0" +version = "7.0.0" description = "Core Polkadot types used by Relay Chains and parachains." 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 } sp-runtime = { path = "../../substrate/primitives/runtime", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -parity-scale-codec = { version = "3.6.1", default-features = false, features = [ "derive" ] } +parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } [features] -default = [ "std" ] +default = ["std"] std = [ "parity-scale-codec/std", "scale-info/std", diff --git a/polkadot/doc/testing.md b/polkadot/doc/testing.md deleted file mode 100644 index 1045303baf0df1e757cefa374ca2396b99984504..0000000000000000000000000000000000000000 --- a/polkadot/doc/testing.md +++ /dev/null @@ -1,255 +0,0 @@ -# Testing - -Automated testing is an essential tool to assure correctness. - -## Scopes - -The testing strategy for Polkadot is 4-fold: - -### Unit testing (1) - -Boring, small scale correctness tests of individual functions. - -### Integration tests - -There are two variants of integration tests: - -#### Subsystem tests (2) - -One particular subsystem (subsystem under test) interacts with a mocked overseer that is made to assert incoming and -outgoing messages of the subsystem under test. This is largely present today, but has some fragmentation in the evolved -integration test implementation. A `proc-macro`/`macro_rules` would allow for more consistent implementation and -structure. - -#### Behavior tests (3) - -Launching small scale networks, with multiple adversarial nodes without any further tooling required. This should -include tests around the thresholds in order to evaluate the error handling once certain assumed invariants fail. - -For this purpose based on `AllSubsystems` and `proc-macro` `AllSubsystemsGen`. - -This assumes a simplistic test runtime. - -#### Testing at scale (4) - -Launching many nodes with configurable network speed and node features in a cluster of nodes. At this scale the -[Simnet][simnet] comes into play which launches a full cluster of nodes. The scale is handled by spawning a kubernetes -cluster and the meta description is covered by [Gurke][Gurke]. Asserts are made using Grafana rules, based on the -existing prometheus metrics. This can be extended by adding an additional service translating `jaeger` spans into -addition prometheus avoiding additional Polkadot source changes. - -_Behavior tests_ and _testing at scale_ have naturally soft boundary. The most significant difference is the presence of -a real network and the number of nodes, since a single host often not capable to run multiple nodes at once. - ---- - -## Coverage - -Coverage gives a _hint_ of the actually covered source lines by tests and test applications. - -The state of the art is currently [tarpaulin][tarpaulin] which unfortunately yields a lot of false negatives. Lines that -are in fact covered, marked as uncovered due to a mere linebreak in a statement can cause these artifacts. This leads to -lower coverage percentages than there actually is. - -Since late 2020 rust has gained [MIR based coverage tooling]( -https://blog.rust-lang.org/inside-rust/2020/11/12/source-based-code-coverage.html). - -```sh -# setup -rustup component add llvm-tools-preview -cargo install grcov miniserve - -export CARGO_INCREMENTAL=0 -# wasm is not happy with the instrumentation -export SKIP_BUILD_WASM=true -export BUILD_DUMMY_WASM_BINARY=true -# the actully collected coverage data -export LLVM_PROFILE_FILE="llvmcoveragedata-%p-%m.profraw" -# build wasm without instrumentation -export WASM_TARGET_DIRECTORY=/tmp/wasm -cargo +nightly build -# required rust flags -export RUSTFLAGS="-Zinstrument-coverage" -# assure target dir is clean -rm -r target/{debug,tests} -# run tests to get coverage data -cargo +nightly test --all - -# create the *html* report out of all the test binaries -# mostly useful for local inspection -grcov . --binary-path ./target/debug -s . -t html --branch --ignore-not-existing -o ./coverage/ -miniserve -r ./coverage - -# create a *codecov* compatible report -grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info -``` - -The test coverage in `lcov` can the be published to . - -```sh -bash <(curl -s https://codecov.io/bash) -f lcov.info -``` - -or just printed as part of the PR using a github action i.e. -[`jest-lcov-reporter`](https://github.com/marketplace/actions/jest-lcov-reporter). - -For full examples on how to use [`grcov` /w Polkadot specifics see the github -repo](https://github.com/mozilla/grcov#coverallscodecov-output). - -## Fuzzing - -Fuzzing is an approach to verify correctness against arbitrary or partially structured inputs. - -Currently implemented fuzzing targets: - -* `erasure-coding` - -The tooling of choice here is `honggfuzz-rs` as it allows _fastest_ coverage according to "some paper" which is a -positive feature when run as part of PRs. - -Fuzzing is generally not applicable for data secured by cryptographic hashes or signatures. Either the input has to be -specifically crafted, such that the discarded input percentage stays in an acceptable range. System level fuzzing is -hence simply not feasible due to the amount of state that is required. - -Other candidates to implement fuzzing are: - -* `rpc` -* ... - -## Performance metrics - -There are various ways of performance metrics. - -* timing with `criterion` -* cache hits/misses w/ `iai` harness or `criterion-perf` -* `coz` a performance based compiler - -Most of them are standard tools to aid in the creation of statistical tests regarding change in time of certain unit -tests. - -`coz` is meant for runtime. In our case, the system is far too large to yield a sufficient number of measurements in -finite time. An alternative approach could be to record incoming package streams per subsystem and store dumps of them, -which in return could be replayed repeatedly at an accelerated speed, with which enough metrics could be obtained to -yield information on which areas would improve the metrics. This unfortunately will not yield much information, since -most if not all of the subsystem code is linear based on the input to generate one or multiple output messages, it is -unlikely to get any useful metrics without mocking a sufficiently large part of the other subsystem which overlaps with -[#Integration tests] which is unfortunately not repeatable as of now. As such the effort gain seems low and this is not -pursued at the current time. - -## Writing small scope integration tests with preconfigured workers - -Requirements: - -* spawn nodes with preconfigured behaviors -* allow multiple types of configuration to be specified -* allow extendability via external crates -* ... - ---- - -## Implementation of different behavior strain nodes - -### Goals - -The main goals are is to allow creating a test node which exhibits a certain behavior by utilizing a subset of _wrapped_ -or _replaced_ subsystems easily. The runtime must not matter at all for these tests and should be simplistic. The -execution must be fast, this mostly means to assure a close to zero network latency as well as shorting the block time -and epoch times down to a few `100ms` and a few dozend blocks per epoch. - -### Approach - -#### MVP - -A simple small scale builder pattern would suffice for stage one implementation of allowing to replace individual -subsystems. An alternative would be to harness the existing `AllSubsystems` type and replace the subsystems as needed. - -#### Full `proc-macro` implementation - -`Overseer` is a common pattern. It could be extracted as `proc` macro and generative `proc-macro`. This would replace -the `AllSubsystems` type as well as implicitly create the `AllMessages` enum as `AllSubsystemsGen` does today. - -The implementation is yet to be completed, see the [implementation PR](https://github.com/paritytech/polkadot/pull/2962) -for details. - -##### Declare an overseer implementation - -```rust -struct BehaveMaleficient; - -impl OverseerGen for BehaveMaleficient { - fn generate<'a, Spawner, RuntimeClient>( - &self, - args: OverseerGenArgs<'a, Spawner, RuntimeClient>, - ) -> Result<(Overseer>, OverseerHandler), Error> - where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, - Spawner: 'static + overseer::gen::Spawner + Clone + Unpin, - { - let spawner = args.spawner.clone(); - let leaves = args.leaves.clone(); - let runtime_client = args.runtime_client.clone(); - let registry = args.registry.clone(); - let candidate_validation_config = args.candidate_validation_config.clone(); - // modify the subsystem(s) as needed: - let all_subsystems = create_default_subsystems(args)?. - // or spawn an entirely new set - - replace_candidate_validation( - // create the filtered subsystem - FilteredSubsystem::new( - CandidateValidationSubsystem::with_config( - candidate_validation_config, - Metrics::register(registry)?, - ), - // an implementation of - Skippy::default(), - ), - ); - - Overseer::new(leaves, all_subsystems, registry, runtime_client, spawner) - .map_err(|e| e.into()) - - // A builder pattern will simplify this further - // WIP https://github.com/paritytech/polkadot/pull/2962 - } -} - -fn main() -> eyre::Result<()> { - color_eyre::install()?; - let cli = Cli::from_args(); - assert_matches::assert_matches!(cli.subcommand, None); - polkadot_cli::run_node(cli, BehaveMaleficient)?; - Ok(()) -} -``` - -[`variant-a`](../node/malus/src/variant-a.rs) is a fully working example. - -#### Simnet - -Spawn a kubernetes cluster based on a meta description using [Gurke] with the [Simnet] scripts. - -Coordinated attacks of multiple nodes or subsystems must be made possible via a side-channel, that is out of scope for -this document. - -The individual node configurations are done as targets with a particular builder configuration. - -#### Behavior tests w/o Simnet - -Commonly this will require multiple nodes, and most machines are limited to running two or three nodes concurrently. -Hence, this is not the common case and is just an implementation _idea_. - -```rust -behavior_testcase!{ -"TestRuntime" => -"Alice": , -"Bob": , -"Charles": Default, -"David": "Charles", -"Eve": "Bob", -} -``` - -[Gurke]: https://github.com/paritytech/gurke -[simnet]: https://github.com/paritytech/simnet_scripts diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index d07b77ec4ddf5574bf29cef0113c1dc1ebf8b32d..677f15c4b9a1446c7828c78f92933f7811733ee8 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "polkadot-erasure-coding" -version = "1.0.0" +version = "7.0.0" +description = "Erasure coding used for Polkadot's availability system" 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" } -novelpoly = { package = "reed-solomon-novelpoly", version = "1.0.0" } -parity-scale-codec = { version = "3.6.1", default-features = false, features = ["std", "derive"] } +novelpoly = { package = "reed-solomon-novelpoly", version = "2.0.0" } +parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "std"] } sp-core = { path = "../../substrate/primitives/core" } sp-trie = { path = "../../substrate/primitives/trie" } -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } diff --git a/polkadot/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/erasure-coding/src/lib.rs b/polkadot/erasure-coding/src/lib.rs index 36847b46371533833c7de5a36e7c9e858ec1116c..e5155df4beba95aa9e7944e3e9a67d4e55a1db9c 100644 --- a/polkadot/erasure-coding/src/lib.rs +++ b/polkadot/erasure-coding/src/lib.rs @@ -83,6 +83,20 @@ pub enum Error { UnknownCodeParam, } +impl From for Error { + fn from(error: novelpoly::Error) -> Self { + match error { + novelpoly::Error::NeedMoreShards { .. } => Self::NotEnoughChunks, + novelpoly::Error::ParamterMustBePowerOf2 { .. } => Self::UnevenLength, + novelpoly::Error::WantedShardCountTooHigh(_) => Self::TooManyValidators, + novelpoly::Error::WantedShardCountTooLow(_) => Self::NotEnoughValidators, + novelpoly::Error::PayloadSizeIsZero { .. } => Self::BadPayload, + novelpoly::Error::InconsistentShardLengths { .. } => Self::NonUniformChunks, + _ => Self::UnknownReconstruction, + } + } +} + /// Obtain a threshold of chunks that should be enough to recover the data. pub const fn recovery_threshold(n_validators: usize) -> Result { if n_validators > MAX_VALIDATORS { @@ -166,42 +180,17 @@ where { let params = code_params(n_validators)?; let mut received_shards: Vec> = vec![None; n_validators]; - let mut shard_len = None; for (chunk_data, chunk_idx) in chunks.into_iter().take(n_validators) { - if chunk_idx >= n_validators { - return Err(Error::ChunkIndexOutOfBounds { chunk_index: chunk_idx, n_validators }) - } - - let shard_len = shard_len.get_or_insert_with(|| chunk_data.len()); - - if *shard_len % 2 != 0 { + if chunk_data.len() % 2 != 0 { return Err(Error::UnevenLength) } - if *shard_len != chunk_data.len() || *shard_len == 0 { - return Err(Error::NonUniformChunks) - } - received_shards[chunk_idx] = Some(WrappedShard::new(chunk_data.to_vec())); } - let res = params.make_encoder().reconstruct(received_shards); - - let payload_bytes = match res { - Err(e) => match e { - novelpoly::Error::NeedMoreShards { .. } => return Err(Error::NotEnoughChunks), - novelpoly::Error::ParamterMustBePowerOf2 { .. } => return Err(Error::UnevenLength), - novelpoly::Error::WantedShardCountTooHigh(_) => return Err(Error::TooManyValidators), - novelpoly::Error::WantedShardCountTooLow(_) => return Err(Error::NotEnoughValidators), - novelpoly::Error::PayloadSizeIsZero { .. } => return Err(Error::BadPayload), - novelpoly::Error::InconsistentShardLengths { .. } => - return Err(Error::NonUniformChunks), - _ => return Err(Error::UnknownReconstruction), - }, - Ok(payload_bytes) => payload_bytes, - }; - - Decode::decode(&mut &payload_bytes[..]).or_else(|_e| Err(Error::BadPayload)) + let payload_bytes = params.make_encoder().reconstruct(received_shards)?; + + Decode::decode(&mut &payload_bytes[..]).map_err(|_| Error::BadPayload) } /// An iterator that yields merkle branches and chunk data for all chunks to @@ -294,56 +283,6 @@ pub fn branch_hash(root: &H256, branch_nodes: &Proof, index: usize) -> Result

{ - remaining_len: usize, - shards: I, - cur_shard: Option<(&'a [u8], usize)>, -} - -impl<'a, I: Iterator> parity_scale_codec::Input for ShardInput<'a, I> { - fn remaining_len(&mut self) -> Result, parity_scale_codec::Error> { - Ok(Some(self.remaining_len)) - } - - fn read(&mut self, into: &mut [u8]) -> Result<(), parity_scale_codec::Error> { - let mut read_bytes = 0; - - loop { - if read_bytes == into.len() { - break - } - - let cur_shard = self.cur_shard.take().or_else(|| self.shards.next().map(|s| (s, 0))); - let (active_shard, mut in_shard) = match cur_shard { - Some((s, i)) => (s, i), - None => break, - }; - - if in_shard >= active_shard.len() { - continue - } - - let remaining_len_out = into.len() - read_bytes; - let remaining_len_shard = active_shard.len() - in_shard; - - let write_len = std::cmp::min(remaining_len_out, remaining_len_shard); - into[read_bytes..][..write_len].copy_from_slice(&active_shard[in_shard..][..write_len]); - - in_shard += write_len; - read_bytes += write_len; - self.cur_shard = Some((active_shard, in_shard)) - } - - self.remaining_len -= read_bytes; - if read_bytes == into.len() { - Ok(()) - } else { - Err("slice provided too big for input".into()) - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index b110540140f958b0624738c38fde3aad417bc85e..8df0c2b1edae47cad98881e7243eb1dc68449e9e 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "polkadot-node-collation-generation" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" @@ -14,8 +18,8 @@ polkadot-node-subsystem = { path = "../subsystem" } polkadot-node-subsystem-util = { path = "../subsystem-util" } polkadot-primitives = { path = "../../primitives" } sp-core = { path = "../../../substrate/primitives/core" } -sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } -thiserror = "1.0.48" +sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } [dev-dependencies] diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index b8c9c1a36e46b9f4b2e7fcc7d40e2ea68ce8cc7e..a89351628a08f74b82459e7153f0df48bd139238 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -143,6 +143,16 @@ impl CollationGenerationSubsystem { } false }, + Ok(FromOrchestra::Communication { + msg: CollationGenerationMessage::Reinitialize(config), + }) => { + if self.config.is_none() { + gum::error!(target: LOG_TARGET, "no initial initialization"); + } else { + self.config = Some(Arc::new(config)); + } + false + }, Ok(FromOrchestra::Communication { msg: CollationGenerationMessage::SubmitCollation(params), }) => { @@ -456,6 +466,7 @@ async fn construct_and_distribute_receipt( } = collation; let persisted_validation_data_hash = validation_data.hash(); + let parent_head_data = validation_data.parent_head.clone(); let parent_head_data_hash = validation_data.parent_head.hash(); // Apply compression to the block data. @@ -541,12 +552,13 @@ async fn construct_and_distribute_receipt( metrics.on_collation_generated(); sender - .send_message(CollatorProtocolMessage::DistributeCollation( - ccr, + .send_message(CollatorProtocolMessage::DistributeCollation { + candidate_receipt: ccr, parent_head_data_hash, pov, + parent_head_data, result_sender, - )) + }) .await; } diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index 9094f40cca8419b608d451c1dc6f09854f53f07c..eb0ede6ef6b121fe655fbbe8331df1c8b645e2be 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -390,11 +390,11 @@ fn sends_distribute_collation_message() { assert_eq!(to_collator_protocol.len(), 1); match AllMessages::from(to_collator_protocol.pop().unwrap()) { - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation( - CandidateReceipt { descriptor, .. }, - _pov, - .., - )) => { + AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { + candidate_receipt, + .. + }) => { + let CandidateReceipt { descriptor, .. } = candidate_receipt; // signature generation is non-deterministic, so we can't just assert that the // expected descriptor is correct. What we can do is validate that the produced // descriptor has a valid signature, then just copy in the generated signature @@ -529,11 +529,11 @@ fn fallback_when_no_validation_code_hash_api() { assert_eq!(to_collator_protocol.len(), 1); match &to_collator_protocol[0] { - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation( - CandidateReceipt { descriptor, .. }, - _pov, - .., - )) => { + AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { + candidate_receipt, + .. + }) => { + let CandidateReceipt { descriptor, .. } = candidate_receipt; assert_eq!(expect_validation_code_hash, descriptor.validation_code_hash); }, _ => panic!("received wrong message type"), @@ -619,15 +619,16 @@ fn submit_collation_leads_to_distribution() { assert_matches!( overseer_recv(&mut virtual_overseer).await, - AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation( - ccr, + AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation { + candidate_receipt, parent_head_data_hash, .. - )) => { + }) => { + let CandidateReceipt { descriptor, .. } = candidate_receipt; assert_eq!(parent_head_data_hash, parent_head.hash()); - assert_eq!(ccr.descriptor().persisted_validation_data_hash, expected_pvd.hash()); - assert_eq!(ccr.descriptor().para_head, dummy_head_data().hash()); - assert_eq!(ccr.descriptor().validation_code_hash, validation_code_hash); + assert_eq!(descriptor.persisted_validation_data_hash, expected_pvd.hash()); + assert_eq!(descriptor.para_head, dummy_head_data().hash()); + assert_eq!(descriptor.validation_code_hash, validation_code_hash); } ); diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index acad0d1fa4e4f5c6286fde17181d582298ed2922..2a5b6198b9a827788696bdf5e1eeeb5829a9da6d 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "polkadot-node-core-approval-voting" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +description = "Approval Voting Subsystem of the Polkadot node" + +[lints] +workspace = true [dependencies] futures = "0.3.21" @@ -12,11 +16,12 @@ 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" +thiserror = { workspace = true } +itertools = "0.10.5" polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } @@ -30,11 +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 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" -parking_lot = "0.12.0" -rand_core = "0.5.1" # should match schnorrkel +async-trait = "0.1.74" +parking_lot = "0.12.1" sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } sp-core = { path = "../../../../substrate/primitives/core" } @@ -43,3 +51,5 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } +log = { workspace = true, default-features = true } +env_logger = "0.9.0" diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs index f345b57029b5a63f1f171578d6b2cb45d703a263..0aa6102fbd6d243b793e889fefb80297a7bb1d89 100644 --- a/polkadot/node/core/approval-voting/src/approval_checking.rs +++ b/polkadot/node/core/approval-voting/src/approval_checking.rs @@ -17,7 +17,7 @@ //! Utilities for checking whether a candidate has been approved under a given block. use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice}; -use polkadot_node_primitives::approval::DelayTranche; +use polkadot_node_primitives::approval::v1::DelayTranche; use polkadot_primitives::ValidatorIndex; use crate::{ @@ -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,17 +488,19 @@ 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::v1::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: BitVec::default(), + assigned_validators: BitVec::default(), our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -497,34 +523,36 @@ 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::v1::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 0, assignments: (0..2).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 2, assignments: (5..10).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, ], - assignments: bitvec![u8, BitOrderLsb0; 1; 10], + assigned_validators: bitvec![u8, BitOrderLsb0; 1; 10], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -569,34 +597,36 @@ 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::v1::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 0, assignments: (0..4).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (4..6).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 2, assignments: (6..10).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, ], - assignments: bitvec![u8, BitOrderLsb0; 1; 10], + assigned_validators: bitvec![u8, BitOrderLsb0; 1; 10], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -647,9 +677,9 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; 5], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; 5], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -675,7 +705,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 1, tolerated_missing: 0, @@ -691,9 +722,9 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; 10], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -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,9 +763,9 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; 10], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -759,7 +791,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 11, next_no_show: None, @@ -776,9 +809,9 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; n_validators], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -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,9 +878,9 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; n_validators], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -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,9 +972,9 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), - assignments: bitvec![u8, BitOrderLsb0; 0; n_validators], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -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,27 +1070,29 @@ 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::v1::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ // Assignments with invalid validator indexes. - approval_db::v1::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, ], - assignments: bitvec![u8, BitOrderLsb0; 1; 3], + assigned_validators: bitvec![u8, BitOrderLsb0; 1; 3], our_assignment: None, our_approval_sig: None, backing_group: GroupIndex(0), @@ -1068,7 +1111,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 10, next_no_show: None, @@ -1094,12 +1138,12 @@ mod tests { ]; for test_tranche in test_tranches { - let mut approval_entry: ApprovalEntry = approval_db::v1::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), backing_group: GroupIndex(0), our_assignment: None, our_approval_sig: None, - assignments: bitvec![u8, BitOrderLsb0; 0; 3], + assigned_validators: bitvec![u8, BitOrderLsb0; 0; 3], approved: false, } .into(); @@ -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 f0ace95613da8d39165813e728804605e27cd373..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,4 +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/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs index c31389269d2ef2093ffb355738ec0f998505b347..011d0a559c022d2294acf0cdcc22364bc301a8dd 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v1/mod.rs @@ -23,144 +23,15 @@ //! require a db migration (check `node/service/src/parachains_db/upgrade.rs`). use parity_scale_codec::{Decode, Encode}; -use polkadot_node_primitives::approval::{AssignmentCert, DelayTranche}; -use polkadot_node_subsystem::{SubsystemError, SubsystemResult}; -use polkadot_node_subsystem_util::database::{DBTransaction, Database}; +use polkadot_node_primitives::approval::v1::{AssignmentCert, DelayTranche}; use polkadot_primitives::{ BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex, ValidatorIndex, ValidatorSignature, }; use sp_consensus_slots::Slot; +use std::collections::BTreeMap; -use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; -use std::{collections::BTreeMap, sync::Arc}; - -use crate::{ - backend::{Backend, BackendWriteOp}, - persisted_entries, -}; - -const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; - -#[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 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)] -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, -} +use super::v2::Bitfield; /// Details pertaining to our assignment on a block. #[derive(Encode, Decode, Debug, Clone, PartialEq)] @@ -171,15 +42,7 @@ pub struct OurAssignment { // Whether the assignment has been triggered already. pub triggered: bool, } - -/// Metadata regarding a specific tranche of assignments for a specific candidate. -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -pub struct TrancheEntry { - pub tranche: DelayTranche, - // Assigned validators, and the instant we received their assignment, rounded - // to the nearest tick. - pub assignments: Vec<(ValidatorIndex, Tick)>, -} +use super::v2::TrancheEntry; /// Metadata regarding approval of a particular candidate within the context of some /// particular block. @@ -226,126 +89,3 @@ pub struct BlockEntry { pub approved_bitfield: Bitfield, pub children: Vec, } - -impl From for Tick { - fn from(tick: crate::Tick) -> Tick { - Tick(tick) - } -} - -impl From for crate::Tick { - fn from(tick: Tick) -> crate::Tick { - tick.0 - } -} - -/// 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), -} - -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. -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/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 new file mode 100644 index 0000000000000000000000000000000000000000..df6e4754dbd63ba81e736495297bfaf017a4e3dc --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/migration_helpers.rs @@ -0,0 +1,202 @@ +// 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::{ + 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 make_block_entry_v1( + block_hash: Hash, + parent_hash: Hash, + block_number: BlockNumber, + candidates: Vec<(CoreIndex, CandidateHash)>, +) -> crate::approval_db::v1::BlockEntry { + crate::approval_db::v1::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(), + } +} + +/// 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_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_v1(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_v1(&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(()) +} + +// Fills the db with dummy data in v1 scheme. +pub fn v1_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_v1( + relay_hash, + Default::default(), + relay_number, + vec![(assignment_core_index, candidate_hash)], + ); + + let dummy_assignment = crate::approval_db::v1::OurAssignment { + cert: dummy_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }).into(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + }; + + let candidate_entry = crate::approval_db::v1::CandidateEntry { + candidate, + session: 123, + block_assignments: vec![( + relay_hash, + crate::approval_db::v1::ApprovalEntry { + tranches: Vec::new(), + backing_group: GroupIndex(1), + our_assignment: Some(dummy_assignment), + our_approval_sig: None, + assignments: Default::default(), + approved: false, + }, + )] + .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_v1(candidate_entry, config)).unwrap(); + db.write(write_block_entry_v1(block_entry, config)).unwrap(); + } + + let write_ops = overlay_db.into_write_ops(); + backend.write(write_ops).unwrap(); + + Ok(expected_candidates) +} + +// Low level DB helper to write a candidate entry in v1 scheme. +fn write_candidate_entry_v1( + candidate_entry: crate::approval_db::v1::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_v1( + block_entry: crate::approval_db::v1::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/v2/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..da42fc5be485caabdd8f8428bdf15c9bf6eb08e9 --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs @@ -0,0 +1,153 @@ +// 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 2 of the DB schema. + +use parity_scale_codec::{Decode, Encode}; +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, 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; + +use crate::backend::V1ReadBackend; + +use super::common::{block_entry_key, candidate_entry_key, load_decode, Config}; + +pub mod migration_helpers; +#[cfg(test)] +pub mod tests; + +// slot_duration * 2 + DelayTranche gives the number of delay tranches since the +// unix epoch. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq)] +pub struct Tick(u64); + +/// Convenience type definition +pub type Bitfield = BitVec; + +/// Details pertaining to our assignment on a block. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct OurAssignment { + /// Our assignment certificate. + pub cert: AssignmentCertV2, + /// The tranche for which the assignment refers to. + pub tranche: DelayTranche, + /// Our validator index for the session in which the candidates were included. + pub validator_index: ValidatorIndex, + /// Whether the assignment has been triggered already. + pub triggered: bool, +} + +/// Metadata regarding a specific tranche of assignments for a specific candidate. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct TrancheEntry { + pub tranche: DelayTranche, + // Assigned validators, and the instant we received their assignment, rounded + // to the nearest tick. + pub assignments: Vec<(ValidatorIndex, Tick)>, +} + +/// 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, + // 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, +} + +impl From for Tick { + fn from(tick: crate::Tick) -> Tick { + Tick(tick) + } +} + +impl From for crate::Tick { + fn from(tick: Tick) -> crate::Tick { + tick.0 + } +} + +/// Load a candidate entry from the aux store in v1 format. +pub fn load_candidate_entry_v1( + 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 v1 format. +pub fn load_block_entry_v1( + 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/v2/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..6021b44c2765ff12a03e1ad85bfb8117c9fcfb03 --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs @@ -0,0 +1,582 @@ +// 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, *}, + 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}; + +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(), + 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(crate::persisted_entries::CandidateEntry::from_v2( + candidate_entry.clone(), + 0, + )); + + 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_v2(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(), + Some(block_entry.into()) + ); + assert_eq!( + load_candidate_entry_v2(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_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()); +} + +#[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_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + Some(block_entry_a.into()) + ); + assert_eq!( + 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_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_v2(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_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + Some(block_entry_a.into()) + ); + assert_eq!( + load_block_entry_v2(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_v2(store.as_ref(), &TEST_CONFIG, &c_hash) + .unwrap() + .is_none()); + continue + }, + Some(i) => ( + load_candidate_entry_v2(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_v2(store.as_ref(), &TEST_CONFIG, &hash) + .unwrap() + .is_none()); + continue + }, + Some(i) => + (load_block_entry_v2(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_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a,) + .unwrap() + .unwrap() + .approved_bitfield + .all()); + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b,) + .unwrap() + .unwrap() + .approved_bitfield + .all()); + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_c,) + .unwrap() + .unwrap() + .approved_bitfield + .not_any()); + assert!(load_block_entry_v2(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/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 87d67c52c467a2079044a3c86179ce162dd0fc43..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::v1::StoredBlockRange, + approval_db::common::StoredBlockRange, persisted_entries::{BlockEntry, CandidateEntry}, }; @@ -44,6 +44,7 @@ pub enum BackendWriteOp { } /// An abstraction over backend storage for the logic of this subsystem. +/// Implementation must always target latest storage version. pub trait Backend { /// Load a block entry from the DB. fn load_block_entry(&self, hash: &Hash) -> SubsystemResult>; @@ -52,6 +53,7 @@ pub trait Backend { &self, candidate_hash: &CandidateHash, ) -> SubsystemResult>; + /// Load all blocks at a specific height. fn load_blocks_at_height(&self, height: &BlockNumber) -> SubsystemResult>; /// Load all block from the DB. @@ -64,6 +66,32 @@ pub trait Backend { I: IntoIterator; } +/// A read only backend to enable db migration from version 1 of DB. +pub trait V1ReadBackend: Backend { + /// Load a candidate entry from the DB with scheme version 1. + 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 0e1d18198c210fa14629a7343087c508a9ccc2ca..1ebea2641b62198c291951448c8c552d9cfb34cf 100644 --- a/polkadot/node/core/approval-voting/src/criteria.rs +++ b/polkadot/node/core/approval-voting/src/criteria.rs @@ -16,28 +16,38 @@ //! Assignment criteria VRF generation and checking. +use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; use polkadot_node_primitives::approval::{ - self as approval_types, AssignmentCert, AssignmentCertKind, DelayTranche, RelayVRFStory, + self as approval_types, + v1::{AssignmentCert, AssignmentCertKind, DelayTranche, RelayVRFStory}, + v2::{ + AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, VrfPreOutput, VrfProof, VrfSignature, + }, }; use polkadot_primitives::{ AssignmentId, AssignmentPair, CandidateHash, CoreIndex, GroupIndex, IndexedVec, SessionInfo, ValidatorIndex, }; +use rand::{seq::SliceRandom, SeedableRng}; +use rand_chacha::ChaCha20Rng; use sc_keystore::LocalKeystore; use sp_application_crypto::ByteArray; use merlin::Transcript; use schnorrkel::vrf::VRFInOut; -use std::collections::{hash_map::Entry, HashMap}; +use std::{ + cmp::min, + collections::{hash_map::Entry, HashMap}, +}; use super::LOG_TARGET; /// Details pertaining to our assignment on a block. #[derive(Debug, Clone, Encode, Decode, PartialEq)] pub struct OurAssignment { - cert: AssignmentCert, + cert: AssignmentCertV2, tranche: DelayTranche, validator_index: ValidatorIndex, // Whether the assignment has been triggered already. @@ -45,11 +55,11 @@ pub struct OurAssignment { } impl OurAssignment { - pub(crate) fn cert(&self) -> &AssignmentCert { + pub fn cert(&self) -> &AssignmentCertV2 { &self.cert } - pub(crate) fn tranche(&self) -> DelayTranche { + pub fn tranche(&self) -> DelayTranche { self.tranche } @@ -66,8 +76,8 @@ impl OurAssignment { } } -impl From for OurAssignment { - fn from(entry: crate::approval_db::v1::OurAssignment) -> Self { +impl From for OurAssignment { + fn from(entry: crate::approval_db::v2::OurAssignment) -> Self { OurAssignment { cert: entry.cert, tranche: entry.tranche, @@ -77,7 +87,7 @@ impl From for OurAssignment { } } -impl From for crate::approval_db::v1::OurAssignment { +impl From for crate::approval_db::v2::OurAssignment { fn from(entry: OurAssignment) -> Self { Self { cert: entry.cert, @@ -88,17 +98,97 @@ impl From for crate::approval_db::v1::OurAssignment { } } -fn relay_vrf_modulo_transcript(relay_vrf_story: RelayVRFStory, sample: u32) -> Transcript { - // combine the relay VRF story with a sample number. - let mut t = Transcript::new(approval_types::RELAY_VRF_MODULO_CONTEXT); - t.append_message(b"RC-VRF", &relay_vrf_story.0); - sample.using_encoded(|s| t.append_message(b"sample", s)); +// Combines the relay VRF story with a sample number if any. +fn relay_vrf_modulo_transcript_inner( + mut transcript: Transcript, + relay_vrf_story: RelayVRFStory, + sample: Option, +) -> Transcript { + transcript.append_message(b"RC-VRF", &relay_vrf_story.0); - t + if let Some(sample) = sample { + sample.using_encoded(|s| transcript.append_message(b"sample", s)); + } + + transcript +} + +fn relay_vrf_modulo_transcript_v1(relay_vrf_story: RelayVRFStory, sample: u32) -> Transcript { + relay_vrf_modulo_transcript_inner( + Transcript::new(approval_types::v1::RELAY_VRF_MODULO_CONTEXT), + relay_vrf_story, + Some(sample), + ) +} + +fn relay_vrf_modulo_transcript_v2(relay_vrf_story: RelayVRFStory) -> Transcript { + relay_vrf_modulo_transcript_inner( + Transcript::new(approval_types::v2::RELAY_VRF_MODULO_CONTEXT), + relay_vrf_story, + None, + ) +} + +/// A hard upper bound on num_cores * target_checkers / num_validators +const MAX_MODULO_SAMPLES: usize = 40; + +/// Takes the VRF output as input and returns a Vec of cores the validator is assigned +/// to as a tranche0 checker. +fn relay_vrf_modulo_cores( + vrf_in_out: &VRFInOut, + // Configuration - `relay_vrf_modulo_samples`. + num_samples: u32, + // Configuration - `n_cores`. + max_cores: u32, +) -> Vec { + let rand_chacha = + ChaCha20Rng::from_seed(vrf_in_out.make_bytes::<::Seed>( + approval_types::v2::CORE_RANDOMNESS_CONTEXT, + )); + generate_samples(rand_chacha, num_samples as usize, max_cores as usize) +} + +/// Generates `num_sumples` randomly from (0..max_cores) range +/// +/// Note! The algorithm can't change because validators on the other +/// side won't be able to check the assignments until they update. +/// This invariant is tested with `generate_samples_invariant`, so the +/// tests will catch any subtle changes in the implementation of this function +/// and its dependencies. +fn generate_samples( + mut rand_chacha: ChaCha20Rng, + num_samples: usize, + max_cores: usize, +) -> Vec { + if num_samples as usize > MAX_MODULO_SAMPLES { + gum::warn!( + target: LOG_TARGET, + n_cores = max_cores, + num_samples, + max_modulo_samples = MAX_MODULO_SAMPLES, + "`num_samples` is greater than `MAX_MODULO_SAMPLES`", + ); + } + + if 2 * num_samples > max_cores { + gum::debug!( + target: LOG_TARGET, + n_cores = max_cores, + num_samples, + max_modulo_samples = MAX_MODULO_SAMPLES, + "Suboptimal configuration `num_samples` should be less than `n_cores` / 2", + ); + } + + let num_samples = min(MAX_MODULO_SAMPLES, min(num_samples, max_cores)); + + let mut random_cores = (0..max_cores as u32).map(|val| val.into()).collect::>(); + let (samples, _) = random_cores.partial_shuffle(&mut rand_chacha, num_samples as usize); + samples.into_iter().map(|val| *val).collect_vec() } fn relay_vrf_modulo_core(vrf_in_out: &VRFInOut, n_cores: u32) -> CoreIndex { - let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::CORE_RANDOMNESS_CONTEXT); + let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::CORE_RANDOMNESS_CONTEXT); // interpret as little-endian u32. let random_core = u32::from_le_bytes(bytes) % n_cores; @@ -106,7 +196,7 @@ fn relay_vrf_modulo_core(vrf_in_out: &VRFInOut, n_cores: u32) -> CoreIndex { } fn relay_vrf_delay_transcript(relay_vrf_story: RelayVRFStory, core_index: CoreIndex) -> Transcript { - let mut t = Transcript::new(approval_types::RELAY_VRF_DELAY_CONTEXT); + let mut t = Transcript::new(approval_types::v1::RELAY_VRF_DELAY_CONTEXT); t.append_message(b"RC-VRF", &relay_vrf_story.0); core_index.0.using_encoded(|s| t.append_message(b"core", s)); t @@ -117,7 +207,7 @@ fn relay_vrf_delay_tranche( num_delay_tranches: u32, zeroth_delay_tranche_width: u32, ) -> DelayTranche { - let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::TRANCHE_RANDOMNESS_CONTEXT); + let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::v1::TRANCHE_RANDOMNESS_CONTEXT); // interpret as little-endian u32 and reduce by the number of tranches. let wide_tranche = @@ -128,14 +218,14 @@ fn relay_vrf_delay_tranche( } fn assigned_core_transcript(core_index: CoreIndex) -> Transcript { - let mut t = Transcript::new(approval_types::ASSIGNED_CORE_CONTEXT); + let mut t = Transcript::new(approval_types::v1::ASSIGNED_CORE_CONTEXT); core_index.0.using_encoded(|s| t.append_message(b"core", s)); t } /// Information about the world assignments are being produced in. -#[derive(Clone)] -pub(crate) struct Config { +#[derive(Clone, Debug)] +pub struct Config { /// The assignment public keys for validators. assignment_keys: Vec, /// The groups of validators assigned to each core. @@ -171,16 +261,18 @@ 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( &self, - claimed_core_index: CoreIndex, + claimed_core_bitfield: CoreBitfield, validator_index: ValidatorIndex, config: &Config, relay_vrf_story: RelayVRFStory, - assignment: &AssignmentCert, - backing_group: GroupIndex, + assignment: &AssignmentCertV2, + // Backing groups for each "leaving core". + backing_groups: Vec, ) -> Result; } @@ -193,26 +285,27 @@ 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) + compute_assignments(keystore, relay_vrf_story, config, leaving_cores, enable_v2_assignments) } fn check_assignment_cert( &self, - claimed_core_index: CoreIndex, + claimed_core_bitfield: CoreBitfield, validator_index: ValidatorIndex, config: &Config, relay_vrf_story: RelayVRFStory, - assignment: &AssignmentCert, - backing_group: GroupIndex, + assignment: &AssignmentCertV2, + backing_groups: Vec, ) -> Result { check_assignment_cert( - claimed_core_index, + claimed_core_bitfield, validator_index, config, relay_vrf_story, assignment, - backing_group, + backing_groups, ) } } @@ -228,11 +321,12 @@ impl AssignmentCriteria for RealAssignmentCriteria { /// different times. The idea is that most assignments are never triggered and fall by the wayside. /// /// This will not assign to anything the local validator was part of the backing group for. -pub(crate) fn compute_assignments( +pub fn compute_assignments( keystore: &LocalKeystore, relay_vrf_story: RelayVRFStory, config: &Config, leaving_cores: impl IntoIterator + Clone, + enable_v2_assignments: bool, ) -> HashMap { if config.n_cores == 0 || config.assignment_keys.is_empty() || @@ -291,14 +385,25 @@ pub(crate) fn compute_assignments( let mut assignments = HashMap::new(); // First run `RelayVRFModulo` for each sample. - compute_relay_vrf_modulo_assignments( - &assignments_key, - index, - config, - relay_vrf_story.clone(), - leaving_cores.iter().cloned(), - &mut assignments, - ); + if enable_v2_assignments { + compute_relay_vrf_modulo_assignments_v2( + &assignments_key, + index, + config, + relay_vrf_story.clone(), + leaving_cores.clone(), + &mut assignments, + ); + } else { + compute_relay_vrf_modulo_assignments_v1( + &assignments_key, + index, + config, + relay_vrf_story.clone(), + leaving_cores.clone(), + &mut assignments, + ); + } // Then run `RelayVRFDelay` once for the whole block. compute_relay_vrf_delay_assignments( @@ -313,7 +418,7 @@ pub(crate) fn compute_assignments( assignments } -fn compute_relay_vrf_modulo_assignments( +fn compute_relay_vrf_modulo_assignments_v1( assignments_key: &schnorrkel::Keypair, validator_index: ValidatorIndex, config: &Config, @@ -329,7 +434,7 @@ fn compute_relay_vrf_modulo_assignments( // into closure. let core = &mut core; assignments_key.vrf_sign_extra_after_check( - relay_vrf_modulo_transcript(relay_vrf_story.clone(), rvm_sample), + relay_vrf_modulo_transcript_v1(relay_vrf_story.clone(), rvm_sample), |vrf_in_out| { *core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores); if let Some((candidate_hash, _)) = @@ -357,15 +462,15 @@ fn compute_relay_vrf_modulo_assignments( // has been executed. let cert = AssignmentCert { kind: AssignmentCertKind::RelayVRFModulo { sample: rvm_sample }, - vrf: approval_types::VrfSignature { - output: approval_types::VrfOutput(vrf_in_out.to_output()), - proof: approval_types::VrfProof(vrf_proof), + vrf: VrfSignature { + pre_output: VrfPreOutput(vrf_in_out.to_preout()), + proof: VrfProof(vrf_proof), }, }; // All assignments of type RelayVRFModulo have tranche 0. assignments.entry(core).or_insert(OurAssignment { - cert, + cert: cert.into(), tranche: 0, validator_index, triggered: false, @@ -374,6 +479,84 @@ fn compute_relay_vrf_modulo_assignments( } } +fn assigned_cores_transcript(core_bitfield: &CoreBitfield) -> Transcript { + let mut t = Transcript::new(approval_types::v2::ASSIGNED_CORE_CONTEXT); + core_bitfield.using_encoded(|s| t.append_message(b"cores", s)); + t +} + +fn compute_relay_vrf_modulo_assignments_v2( + assignments_key: &schnorrkel::Keypair, + validator_index: ValidatorIndex, + config: &Config, + relay_vrf_story: RelayVRFStory, + leaving_cores: Vec<(CandidateHash, CoreIndex)>, + assignments: &mut HashMap, +) { + let mut assigned_cores = Vec::new(); + let leaving_cores = leaving_cores.iter().map(|(_, core)| core).collect::>(); + + let maybe_assignment = { + let assigned_cores = &mut assigned_cores; + assignments_key.vrf_sign_extra_after_check( + relay_vrf_modulo_transcript_v2(relay_vrf_story.clone()), + |vrf_in_out| { + *assigned_cores = relay_vrf_modulo_cores( + &vrf_in_out, + config.relay_vrf_modulo_samples, + config.n_cores, + ) + .into_iter() + .filter(|core| leaving_cores.contains(&core)) + .collect::>(); + + if !assigned_cores.is_empty() { + gum::trace!( + target: LOG_TARGET, + ?assigned_cores, + ?validator_index, + tranche = 0, + "RelayVRFModuloCompact Assignment." + ); + + let assignment_bitfield: CoreBitfield = assigned_cores + .clone() + .try_into() + .expect("Just checked `!assigned_cores.is_empty()`; qed"); + + Some(assigned_cores_transcript(&assignment_bitfield)) + } else { + None + } + }, + ) + }; + + if let Some(assignment) = maybe_assignment.map(|(vrf_in_out, vrf_proof, _)| { + let assignment_bitfield: CoreBitfield = assigned_cores + .clone() + .try_into() + .expect("Just checked `!assigned_cores.is_empty()`; qed"); + + let cert = AssignmentCertV2 { + kind: AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: assignment_bitfield.clone(), + }, + vrf: VrfSignature { + pre_output: VrfPreOutput(vrf_in_out.to_preout()), + proof: VrfProof(vrf_proof), + }, + }; + + // All assignments of type RelayVRFModulo have tranche 0. + OurAssignment { cert, tranche: 0, validator_index, triggered: false } + }) { + for core_index in assigned_cores { + assignments.insert(core_index, assignment.clone()); + } + } +} + fn compute_relay_vrf_delay_assignments( assignments_key: &schnorrkel::Keypair, validator_index: ValidatorIndex, @@ -392,11 +575,11 @@ fn compute_relay_vrf_delay_assignments( config.zeroth_delay_tranche_width, ); - let cert = AssignmentCert { - kind: AssignmentCertKind::RelayVRFDelay { core_index: core }, - vrf: approval_types::VrfSignature { - output: approval_types::VrfOutput(vrf_in_out.to_output()), - proof: approval_types::VrfProof(vrf_proof), + let cert = AssignmentCertV2 { + kind: AssignmentCertKindV2::RelayVRFDelay { core_index: core }, + vrf: VrfSignature { + pre_output: VrfPreOutput(vrf_in_out.to_preout()), + proof: VrfProof(vrf_proof), }, }; @@ -453,12 +636,15 @@ pub(crate) enum InvalidAssignmentReason { VRFModuloOutputMismatch, VRFDelayCoreIndexMismatch, VRFDelayOutputMismatch, + InvalidArguments, + /// Assignment vrf check resulted in 0 assigned cores. + NullAssignment, } /// Checks the crypto of an assignment cert. Failure conditions: /// * Validator index out of bounds /// * VRF signature check fails -/// * VRF output doesn't match assigned core +/// * VRF output doesn't match assigned cores /// * Core is not covered by extra data in signature /// * Core index out of bounds /// * Sample is out of bounds @@ -467,12 +653,12 @@ pub(crate) enum InvalidAssignmentReason { /// This function does not check whether the core is actually a valid assignment or not. That should /// be done outside the scope of this function. pub(crate) fn check_assignment_cert( - claimed_core_index: CoreIndex, + claimed_core_indices: CoreBitfield, validator_index: ValidatorIndex, config: &Config, relay_vrf_story: RelayVRFStory, - assignment: &AssignmentCert, - backing_group: GroupIndex, + assignment: &AssignmentCertV2, + backing_groups: Vec, ) -> Result { use InvalidAssignmentReason as Reason; @@ -484,52 +670,133 @@ pub(crate) fn check_assignment_cert( let public = schnorrkel::PublicKey::from_bytes(validator_public.as_slice()) .map_err(|_| InvalidAssignment(Reason::InvalidAssignmentKey))?; - if claimed_core_index.0 >= config.n_cores { - return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds)) + // Check that we have all backing groups for claimed cores. + if claimed_core_indices.count_ones() == 0 || + claimed_core_indices.count_ones() != backing_groups.len() + { + return Err(InvalidAssignment(Reason::InvalidArguments)) } // Check that the validator was not part of the backing group // and not already assigned. - let is_in_backing = - is_in_backing_group(&config.validator_groups, validator_index, backing_group); + for (claimed_core, backing_group) in claimed_core_indices.iter_ones().zip(backing_groups.iter()) + { + if claimed_core >= config.n_cores as usize { + return Err(InvalidAssignment(Reason::CoreIndexOutOfBounds)) + } + + let is_in_backing = + is_in_backing_group(&config.validator_groups, validator_index, *backing_group); - if is_in_backing { - return Err(InvalidAssignment(Reason::IsInBackingGroup)) + if is_in_backing { + return Err(InvalidAssignment(Reason::IsInBackingGroup)) + } } - let vrf_signature = &assignment.vrf; - match assignment.kind { - AssignmentCertKind::RelayVRFModulo { sample } => { - if sample >= config.relay_vrf_modulo_samples { + 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; + + match &assignment.kind { + AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => { + // Check that claimed core bitfield match the one from certificate. + if &claimed_core_indices != core_bitfield { + return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch)) + } + + let (vrf_in_out, _) = public + .vrf_verify_extra( + relay_vrf_modulo_transcript_v2(relay_vrf_story), + &vrf_pre_output.0, + &vrf_proof.0, + assigned_cores_transcript(core_bitfield), + ) + .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?; + + let resulting_cores = relay_vrf_modulo_cores( + &vrf_in_out, + config.relay_vrf_modulo_samples, + config.n_cores, + ); + + // Currently validators can opt out of checking specific cores. + // This is the same issue to how validator can opt out and not send their assignments in + // the first place. Ensure that the `vrf_in_out` actually includes all of the claimed + // cores. + for claimed_core_index in claimed_core_indices.iter_ones() { + if !resulting_cores.contains(&CoreIndex(claimed_core_index as u32)) { + gum::debug!( + target: LOG_TARGET, + ?resulting_cores, + ?claimed_core_indices, + vrf_modulo_cores = ?resulting_cores, + "Assignment claimed cores mismatch", + ); + return Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch)) + } + } + + Ok(0) + }, + AssignmentCertKindV2::RelayVRFModulo { sample } => { + if *sample >= config.relay_vrf_modulo_samples { return Err(InvalidAssignment(Reason::SampleOutOfBounds)) } + // Enforce claimed candidates is 1. + if claimed_core_indices.count_ones() != 1 { + gum::warn!( + target: LOG_TARGET, + ?claimed_core_indices, + "`RelayVRFModulo` assignment must always claim 1 core", + ); + return Err(InvalidAssignment(Reason::InvalidArguments)) + } + let (vrf_in_out, _) = public .vrf_verify_extra( - relay_vrf_modulo_transcript(relay_vrf_story, sample), - &vrf_signature.output.0, - &vrf_signature.proof.0, - assigned_core_transcript(claimed_core_index), + relay_vrf_modulo_transcript_v1(relay_vrf_story, *sample), + &vrf_pre_output.0, + &vrf_proof.0, + assigned_core_transcript(CoreIndex(first_claimed_core_index)), ) .map_err(|_| InvalidAssignment(Reason::VRFModuloOutputMismatch))?; + let core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores); // ensure that the `vrf_in_out` actually gives us the claimed core. - if relay_vrf_modulo_core(&vrf_in_out, config.n_cores) == claimed_core_index { + if core.0 == first_claimed_core_index { Ok(0) } else { + gum::debug!( + target: LOG_TARGET, + ?core, + ?claimed_core_indices, + "Assignment claimed cores mismatch", + ); Err(InvalidAssignment(Reason::VRFModuloCoreIndexMismatch)) } }, - AssignmentCertKind::RelayVRFDelay { core_index } => { - if core_index != claimed_core_index { + AssignmentCertKindV2::RelayVRFDelay { core_index } => { + // Enforce claimed candidates is 1. + if claimed_core_indices.count_ones() != 1 { + gum::debug!( + target: LOG_TARGET, + ?claimed_core_indices, + "`RelayVRFDelay` assignment must always claim 1 core", + ); + return Err(InvalidAssignment(Reason::InvalidArguments)) + } + + if core_index.0 != first_claimed_core_index { return Err(InvalidAssignment(Reason::VRFDelayCoreIndexMismatch)) } let (vrf_in_out, _) = public .vrf_verify( - relay_vrf_delay_transcript(relay_vrf_story, core_index), - &vrf_signature.output.0, - &vrf_signature.proof.0, + relay_vrf_delay_transcript(relay_vrf_story, *core_index), + &vrf_pre_output.0, + &vrf_proof.0, ) .map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?; @@ -550,6 +817,19 @@ fn is_in_backing_group( validator_groups.get(group).map_or(false, |g| g.contains(&validator)) } +/// Migration helpers. +impl From for OurAssignment { + fn from(value: crate::approval_db::v1::OurAssignment) -> Self { + Self { + cert: value.cert.into(), + tranche: value.tranche, + validator_index: value.validator_index, + // Whether the assignment has been triggered already. + triggered: value.triggered, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -630,10 +910,11 @@ mod tests { ]), n_cores: 2, zeroth_delay_tranche_width: 10, - relay_vrf_modulo_samples: 3, + relay_vrf_modulo_samples: 10, n_delay_tranches: 40, }, vec![(c_a, CoreIndex(0), GroupIndex(1)), (c_b, CoreIndex(1), GroupIndex(0))], + false, ); // Note that alice is in group 0, which was the backing group for core 1. @@ -665,10 +946,11 @@ mod tests { ]), n_cores: 2, zeroth_delay_tranche_width: 10, - relay_vrf_modulo_samples: 3, + relay_vrf_modulo_samples: 10, n_delay_tranches: 40, }, vec![(c_a, CoreIndex(0), GroupIndex(0)), (c_b, CoreIndex(1), GroupIndex(1))], + false, ); assert_eq!(assignments.len(), 1); @@ -692,19 +974,21 @@ mod tests { validator_groups: Default::default(), n_cores: 0, zeroth_delay_tranche_width: 10, - relay_vrf_modulo_samples: 3, + relay_vrf_modulo_samples: 10, n_delay_tranches: 40, }, vec![], + false, ); assert!(assignments.is_empty()); } + #[derive(Debug)] struct MutatedAssignment { - core: CoreIndex, - cert: AssignmentCert, - group: GroupIndex, + cores: CoreBitfield, + cert: AssignmentCertV2, + groups: Vec, own_group: GroupIndex, val_index: ValidatorIndex, config: Config, @@ -729,12 +1013,12 @@ mod tests { validator_groups: basic_groups(n_validators, n_cores), n_cores: n_cores as u32, zeroth_delay_tranche_width: 10, - relay_vrf_modulo_samples: 3, + relay_vrf_modulo_samples: 15, n_delay_tranches: 40, }; let relay_vrf_story = RelayVRFStory([42u8; 32]); - let assignments = compute_assignments( + let mut assignments = compute_assignments( &keystore, relay_vrf_story.clone(), &config, @@ -747,19 +1031,42 @@ mod tests { ) }) .collect::>(), + false, ); + // Extend with v2 assignments as well + assignments.extend(compute_assignments( + &keystore, + relay_vrf_story.clone(), + &config, + (0..n_cores) + .map(|i| { + ( + CandidateHash(Hash::repeat_byte(i as u8)), + CoreIndex(i as u32), + group_for_core(i), + ) + }) + .collect::>(), + true, + )); + let mut counted = 0; for (core, assignment) in assignments { + let cores = match assignment.cert.kind.clone() { + AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield, + AssignmentCertKindV2::RelayVRFModulo { sample: _ } => core.into(), + AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.into(), + }; + let mut mutated = MutatedAssignment { - core, - group: group_for_core(core.0 as _), + cores: cores.clone(), + groups: cores.iter_ones().map(|core| group_for_core(core)).collect(), cert: assignment.cert, own_group: GroupIndex(0), val_index: ValidatorIndex(0), config: config.clone(), }; - let expected = match f(&mut mutated) { None => continue, Some(e) => e, @@ -768,16 +1075,16 @@ mod tests { counted += 1; let is_good = check_assignment_cert( - mutated.core, + mutated.cores, mutated.val_index, &mutated.config, relay_vrf_story.clone(), &mutated.cert, - mutated.group, + mutated.groups, ) .is_ok(); - assert_eq!(expected, is_good) + assert_eq!(expected, is_good); } assert!(counted > 0); @@ -791,7 +1098,7 @@ mod tests { #[test] fn check_rejects_claimed_core_out_of_bounds() { check_mutated_assignments(200, 100, 25, |m| { - m.core.0 += 100; + m.cores = CoreIndex(100).into(); Some(false) }); } @@ -799,7 +1106,7 @@ mod tests { #[test] fn check_rejects_in_backing_group() { check_mutated_assignments(200, 100, 25, |m| { - m.group = m.own_group; + m.groups[0] = m.own_group; Some(false) }); } @@ -814,10 +1121,11 @@ mod tests { #[test] fn check_rejects_delay_bad_vrf() { - check_mutated_assignments(40, 10, 8, |m| { + check_mutated_assignments(40, 100, 8, |m| { + let vrf_signature = garbage_vrf_signature(); match m.cert.kind.clone() { - AssignmentCertKind::RelayVRFDelay { .. } => { - m.cert.vrf = garbage_vrf_signature(); + AssignmentCertKindV2::RelayVRFDelay { .. } => { + m.cert.vrf = vrf_signature; Some(false) }, _ => None, // skip everything else. @@ -828,9 +1136,14 @@ mod tests { #[test] fn check_rejects_modulo_bad_vrf() { check_mutated_assignments(200, 100, 25, |m| { + let vrf_signature = garbage_vrf_signature(); match m.cert.kind.clone() { - AssignmentCertKind::RelayVRFModulo { .. } => { - m.cert.vrf = garbage_vrf_signature(); + AssignmentCertKindV2::RelayVRFModulo { .. } => { + m.cert.vrf = vrf_signature; + Some(false) + }, + AssignmentCertKindV2::RelayVRFModuloCompact { .. } => { + m.cert.vrf = vrf_signature; Some(false) }, _ => None, // skip everything else. @@ -842,10 +1155,11 @@ mod tests { fn check_rejects_modulo_sample_out_of_bounds() { check_mutated_assignments(200, 100, 25, |m| { match m.cert.kind.clone() { - AssignmentCertKind::RelayVRFModulo { sample } => { + AssignmentCertKindV2::RelayVRFModulo { sample } => { m.config.relay_vrf_modulo_samples = sample; Some(false) }, + AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield: _ } => Some(true), _ => None, // skip everything else. } }); @@ -855,8 +1169,11 @@ mod tests { fn check_rejects_delay_claimed_core_wrong() { check_mutated_assignments(200, 100, 25, |m| { match m.cert.kind.clone() { - AssignmentCertKind::RelayVRFDelay { .. } => { - m.core = CoreIndex((m.core.0 + 1) % 100); + AssignmentCertKindV2::RelayVRFDelay { .. } => { + // for core in &mut m.cores { + // core.0 = (core.0 + 1) % 100; + // } + m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into(); Some(false) }, _ => None, // skip everything else. @@ -868,12 +1185,53 @@ mod tests { fn check_rejects_modulo_core_wrong() { check_mutated_assignments(200, 100, 25, |m| { match m.cert.kind.clone() { - AssignmentCertKind::RelayVRFModulo { .. } => { - m.core = CoreIndex((m.core.0 + 1) % 100); + AssignmentCertKindV2::RelayVRFModulo { .. } | + AssignmentCertKindV2::RelayVRFModuloCompact { .. } => { + m.cores = CoreIndex((m.cores.first_one().unwrap() + 1) as u32 % 100).into(); + Some(false) }, _ => None, // skip everything else. } }); } + + #[test] + fn generate_samples_invariant() { + let seed = [ + 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0, + 0, 0, 0, 2, 92, + ]; + let rand_chacha = ChaCha20Rng::from_seed(seed); + + let samples = generate_samples(rand_chacha.clone(), 6, 100); + let expected = vec![19, 79, 17, 75, 66, 30].into_iter().map(Into::into).collect_vec(); + assert_eq!(samples, expected); + + let samples = generate_samples(rand_chacha.clone(), 6, 7); + let expected = vec![0, 3, 6, 5, 4, 2].into_iter().map(Into::into).collect_vec(); + assert_eq!(samples, expected); + + let samples = generate_samples(rand_chacha.clone(), 6, 12); + let expected = vec![2, 4, 7, 5, 11, 3].into_iter().map(Into::into).collect_vec(); + assert_eq!(samples, expected); + + let samples = generate_samples(rand_chacha.clone(), 1, 100); + let expected = vec![30].into_iter().map(Into::into).collect_vec(); + assert_eq!(samples, expected); + + let samples = generate_samples(rand_chacha.clone(), 0, 100); + let expected = vec![]; + assert_eq!(samples, expected); + + let samples = generate_samples(rand_chacha, MAX_MODULO_SAMPLES + 1, 100); + let expected = vec![ + 42, 54, 55, 93, 64, 27, 49, 15, 83, 71, 62, 1, 43, 77, 97, 41, 7, 69, 0, 88, 59, 14, + 23, 87, 47, 4, 51, 12, 74, 56, 50, 44, 9, 82, 19, 79, 17, 75, 66, 30, + ] + .into_iter() + .map(Into::into) + .collect_vec(); + assert_eq!(samples, expected); + } } diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index df712fc45a53d4cd21266e348247ec7f99ec09b8..7a56e9fd11293d1a96debd5e98b719a19c48045f 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -30,7 +30,10 @@ use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ - approval::{self as approval_types, BlockApprovalMeta, RelayVRFStory}, + approval::{ + self as approval_types, + v1::{BlockApprovalMeta, RelayVRFStory}, + }, MAX_FINALITY_LAG, }; use polkadot_node_subsystem::{ @@ -42,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; @@ -53,11 +56,11 @@ use futures::{channel::oneshot, prelude::*}; use std::collections::HashMap; -use super::approval_db::v1; +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}, }; @@ -92,7 +95,7 @@ enum ImportedBlockInfoError { FutureCancelled(&'static str, futures::channel::oneshot::Canceled), #[error(transparent)] - ApprovalError(approval_types::ApprovalError), + ApprovalError(approval_types::v1::ApprovalError), #[error("block is already finalized")] BlockAlreadyFinalized, @@ -211,12 +214,23 @@ 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::babe_unsafe_vrf_info(&block_header); + let unsafe_vrf = approval_types::v1::babe_unsafe_vrf_info(&block_header); match unsafe_vrf { Some(unsafe_vrf) => { @@ -236,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) @@ -497,7 +512,7 @@ pub(crate) async fn handle_new_head( ctx.send_message(ChainSelectionMessage::Approved(block_hash)).await; } - let block_entry = v1::BlockEntry { + let block_entry = v3::BlockEntry { block_hash, parent_hash: block_header.parent_hash, block_number: block_header.number, @@ -510,6 +525,8 @@ pub(crate) async fn handle_new_head( .collect(), approved_bitfield, children: Vec::new(), + candidates_pending_signature: Default::default(), + distributed_assignments: Default::default(), }; gum::trace!( @@ -588,17 +605,21 @@ pub(crate) async fn handle_new_head( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::{approval_db::v1::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::{ - approval::{VrfSignature, VrfTranscript}, + approval::v1::{VrfSignature, VrfTranscript}, DISPUTE_WINDOW, }; use polkadot_node_subsystem::messages::{AllMessages, ApprovalVotingMessage}; 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::{ @@ -610,7 +631,7 @@ pub(crate) mod tests { pub(crate) use sp_runtime::{Digest, DigestItem}; use std::{pin::Pin, sync::Arc}; - use crate::{approval_db::v1::Config as DatabaseConfig, criteria, BlockEntry}; + use crate::{approval_db::common::Config as DatabaseConfig, criteria, BlockEntry}; const DATA_COL: u32 = 0; @@ -635,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(), } } @@ -650,32 +671,38 @@ pub(crate) mod tests { ) } - struct MockAssignmentCriteria; + #[derive(Default)] + struct MockAssignmentCriteria { + enable_v2: bool, + } impl AssignmentCriteria for MockAssignmentCriteria { fn compute_assignments( &self, _keystore: &LocalKeystore, - _relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory, + _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _config: &criteria::Config, _leaving_cores: Vec<( CandidateHash, polkadot_primitives::CoreIndex, polkadot_primitives::GroupIndex, )>, + enable_assignments_v2: bool, ) -> HashMap { + assert_eq!(enable_assignments_v2, self.enable_v2); HashMap::new() } fn check_assignment_cert( &self, - _claimed_core_index: polkadot_primitives::CoreIndex, + _claimed_core_bitfield: polkadot_node_primitives::approval::v2::CoreBitfield, _validator_index: polkadot_primitives::ValidatorIndex, _config: &criteria::Config, - _relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory, - _assignment: &polkadot_node_primitives::approval::AssignmentCert, - _backing_group: polkadot_primitives::GroupIndex, - ) -> Result { + _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, + _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, + _backing_groups: Vec, + ) -> Result + { Ok(0) } } @@ -706,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] @@ -901,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(), }; @@ -982,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)); @@ -1031,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(), }; @@ -1129,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(), }; @@ -1216,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)); @@ -1296,7 +1351,7 @@ pub(crate) mod tests { let (state, mut session_info_provider) = single_session_state(); overlay_db.write_block_entry( - v1::BlockEntry { + v3::BlockEntry { block_hash: parent_hash, parent_hash: Default::default(), block_number: 4, @@ -1306,6 +1361,8 @@ pub(crate) mod tests { candidates: Vec::new(), approved_bitfield: Default::default(), children: Vec::new(), + candidates_pending_signature: Default::default(), + distributed_assignments: Default::default(), } .into(), ); @@ -1337,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 = - v1::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)); }) @@ -1432,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 ddef736feab7c9cdc5b79e3508efef47e7bf76af..8cc16a6e1ec199776003204e8d9c870fb0f62114 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,11 +21,16 @@ //! 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::{ - BlockApprovalMeta, DelayTranche, IndirectAssignmentCert, IndirectSignedApprovalVote, + v1::{BlockApprovalMeta, DelayTranche}, + v2::{ + AssignmentCertKindV2, BitfieldError, CandidateBitfield, CoreBitfield, + IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2, + }, }, ValidationResult, DISPUTE_WINDOW, }; @@ -49,9 +54,10 @@ use polkadot_node_subsystem_util::{ TimeoutExt, }; use polkadot_primitives::{ - ApprovalVote, BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, DisputeStatement, - ExecutorParams, GroupIndex, Hash, PvfExecTimeoutKind, 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; @@ -63,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, }, @@ -76,22 +84,26 @@ use std::{ use schnellru::{ByLength, LruMap}; 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; -mod approval_db; +pub mod approval_db; mod backend; -mod criteria; +pub mod criteria; mod import; mod ops; mod persisted_entries; -mod time; +pub mod time; use crate::{ - approval_db::v1::{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)] @@ -107,7 +119,10 @@ const APPROVAL_CACHE_SIZE: u32 = 1024; const TICK_TOO_FAR_IN_FUTURE: Tick = 20; // 10 seconds. const APPROVAL_DELAY: Tick = 2; -const LOG_TARGET: &str = "parachain::approval-voting"; +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)] @@ -144,6 +159,7 @@ pub struct ApprovalVotingSubsystem { db: Arc, mode: Mode, metrics: Metrics, + clock: Box, } #[derive(Clone)] @@ -152,7 +168,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, @@ -178,6 +201,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() @@ -214,6 +253,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(); @@ -291,6 +342,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", @@ -307,6 +365,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( @@ -362,6 +445,25 @@ impl ApprovalVotingSubsystem { keystore: Arc, sync_oracle: Box, metrics: Metrics, + ) -> Self { + ApprovalVotingSubsystem::with_config_and_clock( + config, + db, + keystore, + sync_oracle, + metrics, + Box::new(SystemClock {}), + ) + } + + /// Create a new approval voting subsystem with the given keystore, config, and database. + pub fn with_config_and_clock( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + clock: Box, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -370,6 +472,7 @@ impl ApprovalVotingSubsystem { db_config: DatabaseConfig { col_approval_data: config.col_approval_data }, mode: Mode::Syncing(sync_oracle), metrics, + clock, } } @@ -377,8 +480,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::v1::Config { col_approval_data: self.db_config.col_approval_data }; - let mut backend = approval_db::v1::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)?; @@ -411,15 +514,10 @@ fn db_sanity_check(db: Arc, config: DatabaseConfig) -> SubsystemRe impl ApprovalVotingSubsystem { fn start(self, ctx: Context) -> SpawnedSubsystem { let backend = DbBackend::new(self.db.clone(), self.db_config); - let future = run::( - ctx, - self, - Box::new(SystemClock), - Box::new(RealAssignmentCriteria), - backend, - ) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) - .boxed(); + let future = + run::(ctx, self, Box::new(RealAssignmentCriteria), backend) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) + .boxed(); SpawnedSubsystem { name: "approval-voting-subsystem", future } } @@ -553,6 +651,7 @@ struct ApprovalStatus { required_tranches: RequiredTranches, tranche_now: DelayTranche, block_tick: Tick, + last_no_shows: usize, } #[derive(Copy, Clone)] @@ -727,22 +826,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)] @@ -754,15 +904,16 @@ enum Action { tick: Tick, }, LaunchApproval { + claimed_candidate_indices: CandidateBitfield, candidate_hash: CandidateHash, - indirect_cert: IndirectAssignmentCert, + indirect_cert: IndirectAssignmentCertV2, assignment_tranche: DelayTranche, relay_block_hash: Hash, - candidate_index: CandidateIndex, session: SessionIndex, executor_params: ExecutorParams, candidate: CandidateReceipt, backing_group: GroupIndex, + distribute_assignment: bool, }, NoteApprovedInChainSelection(Hash), IssueApproval(CandidateHash, ApprovalVoteRequest), @@ -774,7 +925,6 @@ enum Action { async fn run( mut ctx: Context, mut subsystem: ApprovalVotingSubsystem, - clock: Box, assignment_criteria: Box, mut backend: B, ) -> SubsystemResult<()> @@ -788,7 +938,7 @@ where let mut state = State { keystore: subsystem.keystore, slot_duration_millis: subsystem.slot_duration_millis, - clock, + clock: subsystem.clock, assignment_criteria, spans: HashMap::new(), }; @@ -800,6 +950,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 = { @@ -878,17 +1029,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, @@ -930,12 +1113,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, @@ -966,6 +1150,7 @@ async fn handle_actions( session_info_provider, metrics, candidate_hash, + delayed_approvals_timers, approval_request, ) .await? @@ -977,15 +1162,16 @@ async fn handle_actions( actions_iter = next_actions.into_iter(); }, Action::LaunchApproval { + claimed_candidate_indices, candidate_hash, indirect_cert, assignment_tranche, relay_block_hash, - candidate_index, session, executor_params, candidate, backing_group, + distribute_assignment, } => { // Don't launch approval work if the node is syncing. if let Mode::Syncing(_) = *mode { @@ -1006,10 +1192,12 @@ async fn handle_actions( launch_approval_span.add_string_tag("block-hash", format!("{:?}", block_hash)); let validator_index = indirect_cert.validator; - ctx.send_unbounded_message(ApprovalDistributionMessage::DistributeAssignment( - indirect_cert, - candidate_index, - )); + if distribute_assignment { + ctx.send_unbounded_message(ApprovalDistributionMessage::DistributeAssignment( + indirect_cert, + claimed_candidate_indices, + )); + } match approvals_cache.get(&candidate_hash) { Some(ApprovalOutcome::Approved) => { @@ -1065,9 +1253,20 @@ async fn handle_actions( Action::BecomeActive => { *mode = Mode::Active; - let messages = distribution_messages_for_activation(overlayed_db, state)?; + let (messages, next_actions) = distribution_messages_for_activation( + ctx, + overlayed_db, + state, + delayed_approvals_timers, + session_info_provider, + ) + .await?; ctx.send_messages(messages.into_iter()).await; + let next_actions: Vec = + next_actions.into_iter().map(|v| v.clone()).chain(actions_iter).collect(); + + actions_iter = next_actions.into_iter(); }, Action::Conclude => { conclude = true; @@ -1078,14 +1277,62 @@ async fn handle_actions( Ok(conclude) } -fn distribution_messages_for_activation( +fn cores_to_candidate_indices( + core_indices: &CoreBitfield, + block_entry: &BlockEntry, +) -> Result { + let mut candidate_indices = Vec::new(); + + // Map from core index to candidate index. + for claimed_core_index in core_indices.iter_ones() { + if let Some(candidate_index) = block_entry + .candidates() + .iter() + .position(|(core_index, _)| core_index.0 == claimed_core_index as u32) + { + candidate_indices.push(candidate_index as _); + } + } + + CandidateBitfield::try_from(candidate_indices) +} + +// Returns the claimed core bitfield from the assignment cert, the candidate hash and a +// `BlockEntry`. Can fail only for VRF Delay assignments for which we cannot find the candidate hash +// in the block entry which indicates a bug or corrupted storage. +fn get_assignment_core_indices( + assignment: &AssignmentCertKindV2, + candidate_hash: &CandidateHash, + block_entry: &BlockEntry, +) -> Option { + match &assignment { + AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => + Some(core_bitfield.clone()), + AssignmentCertKindV2::RelayVRFModulo { sample: _ } => block_entry + .candidates() + .iter() + .find(|(_core_index, h)| candidate_hash == h) + .map(|(core_index, _candidate_hash)| { + CoreBitfield::try_from(vec![*core_index]).expect("Not an empty vec; qed") + }), + AssignmentCertKindV2::RelayVRFDelay { core_index } => + Some(CoreBitfield::try_from(vec![*core_index]).expect("Not an empty vec; qed")), + } +} + +#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] +async fn distribution_messages_for_activation( + ctx: &mut Context, db: &OverlayedBackend<'_, impl Backend>, state: &State, -) -> SubsystemResult> { + delayed_approvals_timers: &mut DelayedApprovalTimer, + session_info_provider: &mut RuntimeInfo, +) -> SubsystemResult<(Vec, Vec)> { let all_blocks: Vec = db.load_all_blocks()?; let mut approval_meta = Vec::with_capacity(all_blocks.len()); let mut messages = Vec::new(); + let mut actions = Vec::new(); messages.push(ApprovalDistributionMessage::NewBlocks(Vec::new())); // dummy value. @@ -1119,8 +1366,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)? { @@ -1142,33 +1389,152 @@ fn distribution_messages_for_activation( match approval_entry.local_statements() { (None, None) | (None, Some(_)) => {}, // second is impossible case. (Some(assignment), None) => { - messages.push(ApprovalDistributionMessage::DistributeAssignment( - IndirectAssignmentCert { - block_hash, - validator: assignment.validator_index(), - cert: assignment.cert().clone(), - }, - i as _, - )); + if let Some(claimed_core_indices) = get_assignment_core_indices( + &assignment.cert().kind, + &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, + ) { + Ok(bitfield) => { + gum::debug!( + target: LOG_TARGET, + candidate_hash = ?candidate_entry.candidate_receipt().hash(), + ?block_hash, + "Discovered, triggered assignment, not approved yet", + ); + + let indirect_cert = IndirectAssignmentCertV2 { + block_hash, + validator: assignment.validator_index(), + cert: assignment.cert().clone(), + }; + messages.push( + ApprovalDistributionMessage::DistributeAssignment( + indirect_cert.clone(), + bitfield.clone(), + ), + ); + + if !block_entry + .candidate_is_pending_signature(*candidate_hash) + { + let ExtendedSessionInfo { ref executor_params, .. } = + match get_extended_session_info( + session_info_provider, + ctx.sender(), + block_entry.block_hash(), + block_entry.session(), + ) + .await + { + Some(i) => i, + None => continue, + }; + + actions.push(Action::LaunchApproval { + claimed_candidate_indices: bitfield, + candidate_hash: candidate_entry + .candidate_receipt() + .hash(), + indirect_cert, + assignment_tranche: assignment.tranche(), + relay_block_hash: block_hash, + session: block_entry.session(), + executor_params: executor_params.clone(), + candidate: candidate_entry + .candidate_receipt() + .clone(), + backing_group: approval_entry.backing_group(), + distribute_assignment: false, + }); + } + }, + Err(err) => { + // Should never happen. If we fail here it means the + // assignment is null (no cores claimed). + gum::warn!( + target: LOG_TARGET, + ?block_hash, + ?candidate_hash, + ?err, + "Failed to create assignment bitfield", + ); + }, + } + } else { + gum::warn!( + target: LOG_TARGET, + ?block_hash, + ?candidate_hash, + "Cannot get assignment claimed core indices", + ); + } }, (Some(assignment), Some(approval_sig)) => { - messages.push(ApprovalDistributionMessage::DistributeAssignment( - IndirectAssignmentCert { - block_hash, - validator: assignment.validator_index(), - cert: assignment.cert().clone(), - }, - i as _, - )); - - messages.push(ApprovalDistributionMessage::DistributeApproval( - IndirectSignedApprovalVote { - block_hash, - candidate_index: i as _, - validator: assignment.validator_index(), - signature: approval_sig, - }, - )) + if let Some(claimed_core_indices) = get_assignment_core_indices( + &assignment.cert().kind, + &candidate_hash, + &block_entry, + ) { + match cores_to_candidate_indices( + &claimed_core_indices, + &block_entry, + ) { + Ok(bitfield) => messages.push( + ApprovalDistributionMessage::DistributeAssignment( + IndirectAssignmentCertV2 { + block_hash, + validator: assignment.validator_index(), + cert: assignment.cert().clone(), + }, + bitfield, + ), + ), + Err(err) => { + gum::warn!( + target: LOG_TARGET, + ?block_hash, + ?candidate_hash, + ?err, + "Failed to create assignment bitfield", + ); + // If we didn't send assignment, we don't send approval. + continue + }, + } + 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, + ?block_hash, + ?candidate_hash, + "Cannot get assignment claimed core indices", + ); + } }, } }, @@ -1185,7 +1551,7 @@ fn distribution_messages_for_activation( } messages[0] = ApprovalDistributionMessage::NewBlocks(approval_meta); - Ok(messages) + Ok((messages, actions)) } // Handle an incoming signal from the overseer. Returns true if execution should conclude. @@ -1288,14 +1654,14 @@ async fn handle_from_overseer( vec![Action::Conclude] }, FromOrchestra::Communication { msg } => match msg { - ApprovalVotingMessage::CheckAndImportAssignment(a, claimed_core, res) => { + ApprovalVotingMessage::CheckAndImportAssignment(a, claimed_cores, res) => { let (check_outcome, actions) = check_and_import_assignment( ctx.sender(), state, db, session_info_provider, a, - claimed_core, + claimed_cores, ) .await?; let _ = res.send(check_outcome); @@ -1366,7 +1732,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) { @@ -1392,6 +1758,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)? { @@ -1409,8 +1780,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); } } @@ -1435,7 +1809,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) + }, } }; @@ -1465,7 +1887,6 @@ async fn handle_approved_ancestor( let mut span = span .child("handle-approved-ancestor") .with_stage(jaeger::Stage::ApprovalChecking); - use bitvec::{order::Lsb0, vec::BitVec}; let mut all_approved_max = None; @@ -1804,8 +2225,8 @@ async fn check_and_import_assignment( state: &State, db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, - assignment: IndirectAssignmentCert, - candidate_index: CandidateIndex, + assignment: IndirectAssignmentCertV2, + candidate_indices: CandidateBitfield, ) -> SubsystemResult<(AssignmentCheckResult, Vec)> where Sender: SubsystemSender, @@ -1818,9 +2239,12 @@ where .map(|span| span.child("check-and-import-assignment")) .unwrap_or_else(|| jaeger::Span::new(assignment.block_hash, "check-and-import-assignment")) .with_relay_parent(assignment.block_hash) - .with_uint_tag("candidate-index", candidate_index as u64) .with_stage(jaeger::Stage::ApprovalChecking); + for candidate_index in candidate_indices.iter_ones() { + check_and_import_assignment_span.add_uint_tag("candidate-index", candidate_index as u64); + } + let block_entry = match db.load_block_entry(&assignment.block_hash)? { Some(b) => b, None => @@ -1850,39 +2274,64 @@ where )), }; - let (claimed_core_index, assigned_candidate_hash) = - match block_entry.candidate(candidate_index as usize) { - Some((c, h)) => (*c, *h), + let n_cores = session_info.n_cores as usize; + + // Early check the candidate bitfield and core bitfields lengths < `n_cores`. + // Core bitfield length is checked later in `check_assignment_cert`. + if candidate_indices.len() > n_cores { + gum::debug!( + target: LOG_TARGET, + validator = assignment.validator.0, + n_cores, + candidate_bitfield_len = ?candidate_indices.len(), + "Oversized bitfield", + ); + + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield( + candidate_indices.len(), + )), + Vec::new(), + )) + } + + // The Compact VRF modulo assignment cert has multiple core assignments. + let mut backing_groups = Vec::new(); + let mut claimed_core_indices = Vec::new(); + let mut assigned_candidate_hashes = Vec::new(); + + for candidate_index in candidate_indices.iter_ones() { + let (claimed_core_index, assigned_candidate_hash) = + match block_entry.candidate(candidate_index) { + Some((c, h)) => (*c, *h), + None => + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex( + candidate_index as _, + )), + Vec::new(), + )), // no candidate at core. + }; + + let mut candidate_entry = match db.load_candidate_entry(&assigned_candidate_hash)? { + Some(c) => c, None => return Ok(( - AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex( - candidate_index, + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidate( + candidate_index as _, + assigned_candidate_hash, )), Vec::new(), )), // no candidate at core. }; - check_and_import_assignment_span - .add_string_tag("candidate-hash", format!("{:?}", assigned_candidate_hash)); - check_and_import_assignment_span.add_string_tag( - "traceID", - format!("{:?}", jaeger::hash_to_trace_identifier(assigned_candidate_hash.0)), - ); - - let mut candidate_entry = match db.load_candidate_entry(&assigned_candidate_hash)? { - Some(c) => c, - None => - return Ok(( - AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidate( - candidate_index, - assigned_candidate_hash, - )), - Vec::new(), - )), - }; + check_and_import_assignment_span + .add_string_tag("candidate-hash", format!("{:?}", assigned_candidate_hash)); + check_and_import_assignment_span.add_string_tag( + "traceID", + format!("{:?}", jaeger::hash_to_trace_identifier(assigned_candidate_hash.0)), + ); - let res = { - // import the assignment. let approval_entry = match candidate_entry.approval_entry_mut(&assignment.block_hash) { Some(a) => a, None => @@ -1895,79 +2344,144 @@ where )), }; - let res = state.assignment_criteria.check_assignment_cert( - claimed_core_index, - assignment.validator, - &criteria::Config::from(session_info), - block_entry.relay_vrf_story(), - &assignment.cert, - approval_entry.backing_group(), - ); + backing_groups.push(approval_entry.backing_group()); + claimed_core_indices.push(claimed_core_index); + assigned_candidate_hashes.push(assigned_candidate_hash); + } - let tranche = match res { - Err(crate::criteria::InvalidAssignment(reason)) => - return Ok(( - AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert( - assignment.validator, - format!("{:?}", reason), - )), - Vec::new(), + // Error on null assignments. + if claimed_core_indices.is_empty() { + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert( + assignment.validator, + format!("{:?}", InvalidAssignmentReason::NullAssignment), + )), + Vec::new(), + )) + } + + // Check the assignment certificate. + let res = state.assignment_criteria.check_assignment_cert( + claimed_core_indices + .clone() + .try_into() + .expect("Checked for null assignment above; qed"), + assignment.validator, + &criteria::Config::from(session_info), + block_entry.relay_vrf_story(), + &assignment.cert, + backing_groups, + ); + + let tranche = match res { + Err(crate::criteria::InvalidAssignment(reason)) => + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert( + assignment.validator, + format!("{:?}", reason), )), - Ok(tranche) => { - let current_tranche = - state.clock.tranche_now(state.slot_duration_millis, block_entry.slot()); + Vec::new(), + )), + Ok(tranche) => { + let current_tranche = + state.clock.tranche_now(state.slot_duration_millis, block_entry.slot()); - let too_far_in_future = current_tranche + TICK_TOO_FAR_IN_FUTURE as DelayTranche; + let too_far_in_future = current_tranche + TICK_TOO_FAR_IN_FUTURE as DelayTranche; - if tranche >= too_far_in_future { - return Ok((AssignmentCheckResult::TooFarInFuture, Vec::new())) - } + if tranche >= too_far_in_future { + return Ok((AssignmentCheckResult::TooFarInFuture, Vec::new())) + } - tranche - }, - }; + tranche + }, + }; + + let mut actions = Vec::new(); + let res = { + let mut is_duplicate = true; + // Import the assignments for all cores in the cert. + for (assigned_candidate_hash, candidate_index) in + assigned_candidate_hashes.iter().zip(candidate_indices.iter_ones()) + { + let mut candidate_entry = match db.load_candidate_entry(&assigned_candidate_hash)? { + Some(c) => c, + None => + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidate( + candidate_index as _, + *assigned_candidate_hash, + )), + Vec::new(), + )), + }; - check_and_import_assignment_span.add_uint_tag("tranche", tranche as u64); + let approval_entry = match candidate_entry.approval_entry_mut(&assignment.block_hash) { + Some(a) => a, + None => + return Ok(( + AssignmentCheckResult::Bad(AssignmentCheckError::Internal( + assignment.block_hash, + *assigned_candidate_hash, + )), + Vec::new(), + )), + }; + is_duplicate &= approval_entry.is_assigned(assignment.validator); + approval_entry.import_assignment(tranche, assignment.validator, tick_now); + check_and_import_assignment_span.add_uint_tag("tranche", tranche as u64); + + // We've imported a new assignment, so we need to schedule a wake-up for when that might + // no-show. + if let Some((approval_entry, status)) = state + .approval_status(sender, session_info_provider, &block_entry, &candidate_entry) + .await + { + actions.extend(schedule_wakeup_action( + approval_entry, + block_entry.block_hash(), + block_entry.block_number(), + *assigned_candidate_hash, + status.block_tick, + tick_now, + status.required_tranches, + )); + } - let is_duplicate = approval_entry.is_assigned(assignment.validator); - approval_entry.import_assignment(tranche, assignment.validator, tick_now); + // We also write the candidate entry as it now contains the new candidate. + db.write_candidate_entry(candidate_entry.into()); + } + // Since we don't account for tranche in distribution message fingerprinting, some + // validators can be assigned to the same core (VRF modulo vs VRF delay). These can be + // safely ignored. However, if an assignment is for multiple cores (these are only + // tranche0), we cannot ignore it, because it would mean ignoring other non duplicate + // assignments. if is_duplicate { AssignmentCheckResult::AcceptedDuplicate + } else if candidate_indices.count_ones() > 1 { + gum::trace!( + target: LOG_TARGET, + validator = assignment.validator.0, + candidate_hashes = ?assigned_candidate_hashes, + assigned_cores = ?claimed_core_indices, + ?tranche, + "Imported assignments for multiple cores.", + ); + + AssignmentCheckResult::Accepted } else { gum::trace!( target: LOG_TARGET, validator = assignment.validator.0, - candidate_hash = ?assigned_candidate_hash, - para_id = ?candidate_entry.candidate_receipt().descriptor.para_id, - "Imported assignment.", + candidate_hashes = ?assigned_candidate_hashes, + assigned_cores = ?claimed_core_indices, + "Imported assignment for a single core.", ); AssignmentCheckResult::Accepted } }; - let mut actions = Vec::new(); - - // We've imported a new approval, so we need to schedule a wake-up for when that might no-show. - if let Some((approval_entry, status)) = state - .approval_status(sender, session_info_provider, &block_entry, &candidate_entry) - .await - { - actions.extend(schedule_wakeup_action( - approval_entry, - block_entry.block_hash(), - block_entry.block_number(), - assigned_candidate_hash, - status.block_tick, - tick_now, - status.required_tranches, - )); - } - - // We also write the candidate entry as it now contains the new candidate. - db.write_candidate_entry(candidate_entry.into()); - Ok((res, actions)) } @@ -1977,7 +2491,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 @@ -1989,13 +2503,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); @@ -2008,105 +2521,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::trace!( + 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)) } @@ -2114,7 +2685,7 @@ where #[derive(Debug)] enum ApprovalStateTransition { RemoteApproval(ValidatorIndex), - LocalApproval(ValidatorIndex, ValidatorSignature), + LocalApproval(ValidatorIndex), WakeupProcessed, } @@ -2122,7 +2693,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, } } @@ -2130,7 +2701,7 @@ impl ApprovalStateTransition { fn is_local_approval(&self) -> bool { match *self { ApprovalStateTransition::RemoteApproval(_) => false, - ApprovalStateTransition::LocalApproval(_, _) => true, + ApprovalStateTransition::LocalApproval(_) => true, ApprovalStateTransition::WakeupProcessed => false, } } @@ -2197,7 +2768,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::trace!( + 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, @@ -2215,6 +2795,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 _); @@ -2223,6 +2809,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()); } @@ -2251,10 +2841,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, @@ -2341,7 +2927,7 @@ async fn process_wakeup( let candidate_entry = db.load_candidate_entry(&candidate_hash)?; // If either is not present, we have nothing to wakeup. Might have lost a race with finality - let (block_entry, mut candidate_entry) = match (block_entry, candidate_entry) { + let (mut block_entry, mut candidate_entry) = match (block_entry, candidate_entry) { (Some(b), Some(c)) => (b, c), _ => return Ok(Vec::new()), }; @@ -2392,7 +2978,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, ); @@ -2422,32 +3008,59 @@ async fn process_wakeup( if let Some((cert, val_index, tranche)) = maybe_cert { let indirect_cert = - IndirectAssignmentCert { block_hash: relay_block, validator: val_index, cert }; + IndirectAssignmentCertV2 { block_hash: relay_block, validator: val_index, cert }; - let index_in_candidate = - block_entry.candidates().iter().position(|(_, h)| &candidate_hash == h); + gum::trace!( + target: LOG_TARGET, + ?candidate_hash, + para_id = ?candidate_receipt.descriptor.para_id, + block_hash = ?relay_block, + "Launching approval work.", + ); - if let Some(i) = index_in_candidate { - gum::trace!( + if let Some(claimed_core_indices) = + get_assignment_core_indices(&indirect_cert.cert.kind, &candidate_hash, &block_entry) + { + match cores_to_candidate_indices(&claimed_core_indices, &block_entry) { + Ok(claimed_candidate_indices) => { + // Ensure we distribute multiple core assignments just once. + let distribute_assignment = if claimed_candidate_indices.count_ones() > 1 { + !block_entry.mark_assignment_distributed(claimed_candidate_indices.clone()) + } else { + true + }; + db.write_block_entry(block_entry.clone()); + + actions.push(Action::LaunchApproval { + claimed_candidate_indices, + candidate_hash, + indirect_cert, + assignment_tranche: tranche, + relay_block_hash: relay_block, + session: block_entry.session(), + executor_params: executor_params.clone(), + candidate: candidate_receipt, + backing_group, + distribute_assignment, + }); + }, + Err(err) => { + // Never happens, it should only happen if no cores are claimed, which is a bug. + gum::warn!( + target: LOG_TARGET, + block_hash = ?relay_block, + ?err, + "Failed to create assignment bitfield" + ); + }, + }; + } else { + gum::warn!( target: LOG_TARGET, - ?candidate_hash, - para_id = ?candidate_receipt.descriptor.para_id, block_hash = ?relay_block, - "Launching approval work.", + ?candidate_hash, + "Cannot get assignment claimed core indices", ); - - // sanity: should always be present. - actions.push(Action::LaunchApproval { - candidate_hash, - indirect_cert, - assignment_tranche: tranche, - relay_block_hash: relay_block, - candidate_index: i as _, - session: block_entry.session(), - executor_params: executor_params.clone(), - candidate: candidate_receipt, - backing_group, - }); } } // Although we checked approval earlier in this function, @@ -2627,15 +3240,15 @@ async fn launch_approval( let (val_tx, val_rx) = oneshot::channel(); sender - .send_message(CandidateValidationMessage::ValidateFromExhaustive( - available_data.validation_data, + .send_message(CandidateValidationMessage::ValidateFromExhaustive { + validation_data: available_data.validation_data, validation_code, - candidate.clone(), - available_data.pov, + candidate_receipt: candidate.clone(), + pov: available_data.pov, executor_params, - PvfExecTimeoutKind::Approval, - val_tx, - )) + exec_kind: PvfExecKind::Approval, + response_sender: val_tx, + }) .await; match val_rx.await { @@ -2690,11 +3303,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 @@ -2708,7 +3322,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. @@ -2734,21 +3348,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 => { @@ -2779,10 +3378,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::debug!( + 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, @@ -2790,72 +3528,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[..])) } @@ -2885,3 +3640,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 6f57b2f80e8a47facdc664a874d9ee0879889189..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::v1::{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 9b6592220275dedcde80e902c65a8d19d1b47ce2..b924a1b52ccf49a242adecfd534b498004ec3d87 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -20,16 +20,22 @@ //! Within that context, things are plain-old-data. Within this module, //! data and logic are intertwined. -use polkadot_node_primitives::approval::{AssignmentCert, DelayTranche, RelayVRFStory}; +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; -use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice, vec::BitVec}; +use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice}; use std::collections::BTreeMap; +use crate::approval_db::v2::Bitfield; + use super::{criteria::OurAssignment, time::Tick}; /// Metadata regarding a specific tranche of assignments for a specific candidate. @@ -53,8 +59,8 @@ impl TrancheEntry { } } -impl From for TrancheEntry { - fn from(entry: crate::approval_db::v1::TrancheEntry) -> Self { +impl From for TrancheEntry { + fn from(entry: crate::approval_db::v2::TrancheEntry) -> Self { TrancheEntry { tranche: entry.tranche, assignments: entry.assignments.into_iter().map(|(v, t)| (v, t.into())).collect(), @@ -62,7 +68,7 @@ impl From for TrancheEntry { } } -impl From for crate::approval_db::v1::TrancheEntry { +impl From for crate::approval_db::v2::TrancheEntry { fn from(entry: TrancheEntry) -> Self { Self { tranche: entry.tranche, @@ -71,6 +77,45 @@ impl From for crate::approval_db::v1::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)] @@ -78,9 +123,9 @@ pub struct ApprovalEntry { tranches: Vec, backing_group: GroupIndex, our_assignment: Option, - our_approval_sig: Option, + our_approval_sig: Option, // `n_validators` bits. - assignments: BitVec, + assigned_validators: Bitfield, approved: bool, } @@ -90,12 +135,19 @@ impl ApprovalEntry { tranches: Vec, backing_group: GroupIndex, our_assignment: Option, - our_approval_sig: Option, + our_approval_sig: Option, // `n_validators` bits. - assignments: BitVec, + assigned_validators: Bitfield, approved: bool, ) -> Self { - Self { tranches, backing_group, our_assignment, our_approval_sig, assignments, approved } + Self { + tranches, + backing_group, + our_assignment, + our_approval_sig, + assigned_validators, + approved, + } } // Access our assignment for this approval entry. @@ -107,7 +159,7 @@ impl ApprovalEntry { pub fn trigger_our_assignment( &mut self, tick_now: Tick, - ) -> Option<(AssignmentCert, ValidatorIndex, DelayTranche)> { + ) -> Option<(AssignmentCertV2, ValidatorIndex, DelayTranche)> { let our = self.our_assignment.as_mut().and_then(|a| { if a.triggered() { return None @@ -125,13 +177,16 @@ 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); } /// Whether a validator is already assigned. pub fn is_assigned(&self, validator_index: ValidatorIndex) -> bool { - self.assignments.get(validator_index.0 as usize).map(|b| *b).unwrap_or(false) + self.assigned_validators + .get(validator_index.0 as usize) + .map(|b| *b) + .unwrap_or(false) } /// Import an assignment. No-op if already assigned on the same tranche. @@ -158,14 +213,14 @@ impl ApprovalEntry { }; self.tranches[idx].assignments.push((validator_index, tick_now)); - self.assignments.set(validator_index.0 as _, true); + self.assigned_validators.set(validator_index.0 as _, true); } // Produce a bitvec indicating the assignments of all validators up to and // including `tranche`. - pub fn assignments_up_to(&self, tranche: DelayTranche) -> BitVec { + pub fn assignments_up_to(&self, tranche: DelayTranche) -> Bitfield { self.tranches.iter().take_while(|e| e.tranche <= tranche).fold( - bitvec::bitvec![u8, BitOrderLsb0; 0; self.assignments.len()], + bitvec::bitvec![u8, BitOrderLsb0; 0; self.assigned_validators.len()], |mut a, e| { for &(v, _) in &e.assignments { a.set(v.0 as _, true); @@ -193,12 +248,12 @@ impl ApprovalEntry { /// Get the number of validators in this approval entry. pub fn n_validators(&self) -> usize { - self.assignments.len() + self.assigned_validators.len() } /// Get the number of assignments by validators, including the local validator. pub fn n_assignments(&self) -> usize { - self.assignments.count_ones() + self.assigned_validators.count_ones() } /// Get the backing group index of the approval entry. @@ -209,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) @@ -217,29 +272,63 @@ 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::v1::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, our_assignment: entry.our_assignment.map(Into::into), our_approval_sig: entry.our_approval_sig.map(Into::into), - assignments: entry.assignments, + assigned_validators: entry.assigned_validators, approved: entry.approved, } } } -impl From for crate::approval_db::v1::ApprovalEntry { +impl From for crate::approval_db::v3::ApprovalEntry { fn from(entry: ApprovalEntry) -> Self { Self { tranches: entry.tranches.into_iter().map(Into::into).collect(), backing_group: entry.backing_group, our_assignment: entry.our_assignment.map(Into::into), our_approval_sig: entry.our_approval_sig.map(Into::into), - assignments: entry.assignments, + assigned_validators: entry.assigned_validators, approved: entry.approved, } } @@ -253,7 +342,7 @@ pub struct CandidateEntry { // 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: BitVec, + pub approvals: Bitfield, } impl CandidateEntry { @@ -288,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::v1::CandidateEntry) -> Self { +impl From for CandidateEntry { + fn from(entry: crate::approval_db::v3::CandidateEntry) -> Self { CandidateEntry { candidate: entry.candidate, session: entry.session, @@ -305,7 +428,7 @@ impl From for CandidateEntry { } } -impl From for crate::approval_db::v1::CandidateEntry { +impl From for crate::approval_db::v3::CandidateEntry { fn from(entry: CandidateEntry) -> Self { Self { candidate: entry.candidate, @@ -336,8 +459,21 @@ pub struct BlockEntry { // 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: BitVec, + 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 { @@ -412,6 +548,138 @@ impl BlockEntry { pub fn parent_hash(&self) -> Hash { self.parent_hash } + + /// Mark distributed assignment for many candidate indices. + /// Returns `true` if an assignment was already distributed for the `candidates`. + pub fn mark_assignment_distributed(&mut self, candidates: CandidateBitfield) -> bool { + let bitfield = candidates.into_inner(); + let total_one_bits = self.distributed_assignments.count_ones(); + + let new_len = std::cmp::max(self.distributed_assignments.len(), bitfield.len()); + self.distributed_assignments.resize(new_len, false); + self.distributed_assignments |= bitfield; + + // If the an operation did not change our current bitfied, we return true. + let distributed = total_one_bits == self.distributed_assignments.count_ones(); + + 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() + } + + /// Returns true if candidate hash is in the queue for a signature. + pub fn candidate_is_pending_signature(&self, candidate_hash: CandidateHash) -> bool { + self.candidates_pending_signature + .values() + .any(|context| context.candidate_hash == candidate_hash) + } + + /// 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::v3::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, + 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, + } + } } impl From for BlockEntry { @@ -426,11 +694,31 @@ impl From for BlockEntry { candidates: entry.candidates, approved_bitfield: entry.approved_bitfield, children: entry.children, + distributed_assignments: Default::default(), + candidates_pending_signature: Default::default(), + } + } +} + +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::v1::BlockEntry { +impl From for crate::approval_db::v3::BlockEntry { fn from(entry: BlockEntry) -> Self { Self { block_hash: entry.block_hash, @@ -442,6 +730,30 @@ impl From for crate::approval_db::v1::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, + } + } +} + +impl From for CandidateSigningContext { + fn from(signing_context: crate::approval_db::v3::CandidateSigningContext) -> Self { + Self { + candidate_hash: signing_context.candidate_hash, + sign_no_later_than_tick: signing_context.sign_no_later_than_tick.into(), + } + } +} + +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 0b98f28fbbf0439cd0e9eee4aae537a6956a7cab..c9053232a4c8752f03134047dbc64b020377740a 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -16,10 +16,14 @@ use self::test_helpers::mock::new_leaf; use super::*; +use crate::backend::V1ReadBackend; use polkadot_node_primitives::{ approval::{ - AssignmentCert, AssignmentCertKind, DelayTranche, VrfOutput, VrfProof, VrfSignature, - RELAY_VRF_MODULO_CONTEXT, + v1::{ + AssignmentCert, AssignmentCertKind, DelayTranche, VrfPreOutput, VrfProof, VrfSignature, + RELAY_VRF_MODULO_CONTEXT, + }, + v2::{AssignmentCertKindV2, AssignmentCertV2}, }, AvailableData, BlockData, PoV, }; @@ -33,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; @@ -52,7 +56,7 @@ use std::{ }; use super::{ - approval_db::v1::StoredBlockRange, + approval_db::common::StoredBlockRange, backend::BackendWriteOp, import::tests::{ garbage_vrf_signature, AllowedSlots, BabeEpoch, BabeEpochConfiguration, @@ -74,6 +78,7 @@ struct TestSyncOracle { struct TestSyncOracleHandle { done_syncing_receiver: oneshot::Receiver<()>, + is_major_syncing: Arc, } impl TestSyncOracleHandle { @@ -104,15 +109,16 @@ impl SyncOracle for TestSyncOracle { fn make_sync_oracle(val: bool) -> (Box, TestSyncOracleHandle) { let (tx, rx) = oneshot::channel(); let flag = Arc::new(AtomicBool::new(val)); - let oracle = TestSyncOracle { flag, done_syncing_sender: Arc::new(Mutex::new(Some(tx))) }; - let handle = TestSyncOracleHandle { done_syncing_receiver: rx }; + let oracle = + TestSyncOracle { flag: flag.clone(), done_syncing_sender: Arc::new(Mutex::new(Some(tx))) }; + let handle = TestSyncOracleHandle { done_syncing_receiver: rx, is_major_syncing: flag }; (Box::new(oracle), handle) } #[cfg(test)] pub mod test_constants { - use crate::approval_db::v1::Config as DatabaseConfig; + use crate::approval_db::common::Config as DatabaseConfig; const DATA_COL: u32 = 0; pub(crate) const NUM_COLUMNS: u32 = 1; @@ -162,7 +168,7 @@ impl Clock for MockClock { // This mock clock allows us to manipulate the time and // be notified when wakeups have been triggered. -#[derive(Default)] +#[derive(Default, Debug)] struct MockClockInner { tick: Tick, wakeups: Vec<(Tick, oneshot::Sender<()>)>, @@ -232,26 +238,27 @@ where fn compute_assignments( &self, _keystore: &LocalKeystore, - _relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory, + _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _config: &criteria::Config, _leaving_cores: Vec<( CandidateHash, polkadot_primitives::CoreIndex, polkadot_primitives::GroupIndex, )>, + _enable_assignments_v2: bool, ) -> HashMap { self.0() } fn check_assignment_cert( &self, - _claimed_core_index: polkadot_primitives::CoreIndex, + _claimed_core_bitfield: polkadot_node_primitives::approval::v2::CoreBitfield, validator_index: ValidatorIndex, _config: &criteria::Config, - _relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory, - _assignment: &polkadot_node_primitives::approval::AssignmentCert, - _backing_group: polkadot_primitives::GroupIndex, - ) -> Result { + _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, + _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, + _backing_groups: Vec, + ) -> Result { self.1(validator_index) } } @@ -272,6 +279,19 @@ struct TestStoreInner { candidate_entries: HashMap, } +impl V1ReadBackend for TestStoreInner { + fn load_candidate_entry_v1( + &self, + candidate_hash: &CandidateHash, + _candidate_index: CandidateIndex, + ) -> SubsystemResult> { + self.load_candidate_entry(candidate_hash) + } + fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult> { + self.load_block_entry(block_hash) + } +} + impl Backend for TestStoreInner { fn load_block_entry(&self, block_hash: &Hash) -> SubsystemResult> { Ok(self.block_entries.get(block_hash).cloned()) @@ -343,6 +363,19 @@ pub struct TestStore { store: Arc>, } +impl V1ReadBackend for TestStore { + fn load_candidate_entry_v1( + &self, + candidate_hash: &CandidateHash, + _candidate_index: CandidateIndex, + ) -> SubsystemResult> { + self.load_candidate_entry(candidate_hash) + } + fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult> { + self.load_block_entry(block_hash) + } +} + impl Backend for TestStore { fn load_block_entry(&self, block_hash: &Hash) -> SubsystemResult> { let store = self.store.lock(); @@ -387,9 +420,26 @@ 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 { + 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(); + + AssignmentCertV2 { + kind, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + } } fn sign_approval( @@ -400,6 +450,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)] @@ -408,6 +467,7 @@ struct HarnessConfigBuilder { clock: Option, backend: Option, assignment_criteria: Option>, + major_syncing: bool, } impl HarnessConfigBuilder { @@ -419,9 +479,19 @@ impl HarnessConfigBuilder { self } + pub fn major_syncing(&mut self, value: bool) -> &mut Self { + self.major_syncing = value; + self + } + + pub fn backend(&mut self, store: TestStore) -> &mut Self { + self.backend = Some(store); + self + } + pub fn build(&mut self) -> HarnessConfig { let (sync_oracle, sync_oracle_handle) = - self.sync_oracle.take().unwrap_or_else(|| make_sync_oracle(false)); + self.sync_oracle.take().unwrap_or_else(|| make_sync_oracle(self.major_syncing)); let assignment_criteria = self .assignment_criteria @@ -468,6 +538,12 @@ fn test_harness>( config: HarnessConfig, test: impl FnOnce(TestHarness) -> T, ) { + let _ = env_logger::builder() + .is_test(true) + .filter(Some("polkadot_node_core_approval_voting"), log::LevelFilter::Trace) + .filter(Some(LOG_TARGET), log::LevelFilter::Trace) + .try_init(); + let HarnessConfig { sync_oracle, sync_oracle_handle, clock, backend, assignment_criteria } = config; @@ -486,7 +562,7 @@ fn test_harness>( let subsystem = run( context, - ApprovalVotingSubsystem::with_config( + ApprovalVotingSubsystem::with_config_and_clock( Config { col_approval_data: test_constants::TEST_CONFIG.col_approval_data, slot_duration_millis: SLOT_DURATION_MILLIS, @@ -495,8 +571,8 @@ fn test_harness>( Arc::new(keystore), sync_oracle, Metrics::default(), + clock.clone(), ), - clock.clone(), assignment_criteria, backend, ); @@ -589,7 +665,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, ), }, @@ -617,12 +698,13 @@ async fn check_and_import_assignment( overseer, FromOrchestra::Communication { msg: ApprovalVotingMessage::CheckAndImportAssignment( - IndirectAssignmentCert { + IndirectAssignmentCertV2 { block_hash, validator, - cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }), + cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }) + .into(), }, - candidate_index, + candidate_index.into(), tx, ), }, @@ -631,15 +713,49 @@ async fn check_and_import_assignment( rx } +async fn check_and_import_assignment_v2( + overseer: &mut VirtualOverseer, + block_hash: Hash, + core_indices: Vec, + validator: ValidatorIndex, +) -> oneshot::Receiver { + let (tx, rx) = oneshot::channel(); + overseer_send( + overseer, + FromOrchestra::Communication { + msg: ApprovalVotingMessage::CheckAndImportAssignment( + IndirectAssignmentCertV2 { + block_hash, + validator, + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: core_indices + .clone() + .into_iter() + .map(|c| CoreIndex(c)) + .collect::>() + .try_into() + .unwrap(), + }), + }, + core_indices.try_into().unwrap(), + tx, + ), + }, + ) + .await; + rx +} struct BlockConfig { slot: Slot, candidates: Option>, session_info: Option, + end_syncing: bool, } struct ChainBuilder { blocks_by_hash: HashMap, blocks_at_height: BTreeMap>, + is_major_syncing: Arc, } impl ChainBuilder { @@ -647,16 +763,28 @@ impl ChainBuilder { const GENESIS_PARENT_HASH: Hash = Hash::repeat_byte(0x00); pub fn new() -> Self { - let mut builder = - Self { blocks_by_hash: HashMap::new(), blocks_at_height: BTreeMap::new() }; + let mut builder = Self { + blocks_by_hash: HashMap::new(), + blocks_at_height: BTreeMap::new(), + is_major_syncing: Arc::new(AtomicBool::new(false)), + }; builder.add_block_inner( Self::GENESIS_HASH, Self::GENESIS_PARENT_HASH, 0, - BlockConfig { slot: Slot::from(0), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(0), + candidates: None, + session_info: None, + end_syncing: false, + }, ); builder } + pub fn major_syncing(&mut self, major_syncing: Arc) -> &mut Self { + self.is_major_syncing = major_syncing; + self + } pub fn add_block( &mut self, @@ -707,8 +835,16 @@ impl ChainBuilder { } ancestry.reverse(); - import_block(overseer, ancestry.as_ref(), *number, block_config, false, i > 0) - .await; + import_block( + overseer, + ancestry.as_ref(), + *number, + block_config, + false, + i > 0, + self.is_major_syncing.clone(), + ) + .await; let _: Option<()> = future::pending().timeout(Duration::from_millis(100)).await; } } @@ -743,7 +879,7 @@ fn session_info(keys: &[Sr25519Keyring]) -> SessionInfo { vec![ValidatorIndex(0)], vec![ValidatorIndex(1)], ]), - n_cores: keys.len() as _, + n_cores: 10, needed_approvals: 2, zeroth_delay_tranche_width: 5, relay_vrf_modulo_samples: 3, @@ -762,6 +898,7 @@ async fn import_block( config: &BlockConfig, gap: bool, fork: bool, + major_syncing: Arc, ) { let (new_head, new_header) = &hashes[hashes.len() - 1]; let candidates = config.candidates.clone().unwrap_or(vec![( @@ -790,6 +927,12 @@ async fn import_block( h_tx.send(Ok(Some(new_header.clone()))).unwrap(); } ); + + let is_major_syncing = major_syncing.load(Ordering::SeqCst); + if config.end_syncing { + major_syncing.store(false, Ordering::SeqCst); + } + if !fork { let mut _ancestry_step = 0; if gap { @@ -830,7 +973,7 @@ async fn import_block( } } - if number > 0 { + if number > 0 && !is_major_syncing { assert_matches!( overseer_recv(overseer).await, AllMessages::RuntimeApi( @@ -843,7 +986,6 @@ async fn import_block( c_tx.send(Ok(inclusion_events)).unwrap(); } ); - assert_matches!( overseer_recv(overseer).await, AllMessages::RuntimeApi( @@ -883,14 +1025,14 @@ async fn import_block( ); } - if number == 0 { + if number == 0 && !is_major_syncing { assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks(v)) => { assert_eq!(v.len(), 0usize); } ); - } else { + } else if number > 0 && !is_major_syncing { if !fork { // SessionInfo won't be called for forks - it's already cached assert_matches!( @@ -919,22 +1061,34 @@ 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!( - overseer_recv(overseer).await, - AllMessages::ApprovalDistribution( - ApprovalDistributionMessage::NewBlocks(mut approval_vec) - ) => { - assert_eq!(approval_vec.len(), 1); - let metadata = approval_vec.pop().unwrap(); - let hash = &hashes[number as usize]; - let parent_hash = &hashes[(number - 1) as usize]; - assert_eq!(metadata.hash, hash.0.clone()); - assert_eq!(metadata.parent_hash, parent_hash.0.clone()); - assert_eq!(metadata.slot, config.slot); - } - ); + if !is_major_syncing { + assert_matches!( + overseer_recv(overseer).await, + + AllMessages::ApprovalDistribution( + ApprovalDistributionMessage::NewBlocks(mut approval_vec) + ) => { + assert_eq!(approval_vec.len(), 1); + let metadata = approval_vec.pop().unwrap(); + let hash = &hashes[number as usize]; + let parent_hash = &hashes[(number - 1) as usize]; + assert_eq!(metadata.hash, hash.0.clone()); + assert_eq!(metadata.parent_hash, parent_hash.0.clone()); + assert_eq!(metadata.slot, config.slot); + } + ); + } } } @@ -962,7 +1116,7 @@ fn subsystem_rejects_bad_assignment_ok_criteria() { block_hash, head, 1, - BlockConfig { slot, candidates: None, session_info: None }, + BlockConfig { slot, candidates: None, session_info: None, end_syncing: false }, ); builder.build(&mut virtual_overseer).await; @@ -1025,7 +1179,7 @@ fn subsystem_rejects_bad_assignment_err_criteria() { block_hash, head, 1, - BlockConfig { slot, candidates: None, session_info: None }, + BlockConfig { slot, candidates: None, session_info: None, end_syncing: false }, ); builder.build(&mut virtual_overseer).await; @@ -1068,14 +1222,15 @@ fn blank_subsystem_act_on_bad_block() { &mut virtual_overseer, FromOrchestra::Communication { msg: ApprovalVotingMessage::CheckAndImportAssignment( - IndirectAssignmentCert { + IndirectAssignmentCertV2 { block_hash: bad_block_hash, validator: 0u32.into(), cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0, - }), + }) + .into(), }, - 0u32, + 0u32.into(), tx, ), }, @@ -1129,6 +1284,7 @@ fn subsystem_rejects_approval_if_no_candidate_entry() { slot, candidates: Some(vec![(candidate_descriptor, CoreIndex(1), GroupIndex(1))]), session_info: None, + end_syncing: false, }, ); builder.build(&mut virtual_overseer).await; @@ -1234,7 +1390,12 @@ fn subsystem_rejects_approval_before_assignment() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1287,7 +1448,12 @@ fn subsystem_rejects_assignment_in_future() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(0), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(0), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1331,9 +1497,22 @@ fn subsystem_accepts_duplicate_assignment() { } ); - let block_hash = Hash::repeat_byte(0x01); - let candidate_index = 0; let validator = ValidatorIndex(0); + let candidate_index = 0; + let block_hash = Hash::repeat_byte(0x01); + + let candidate_receipt1 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(1_u32); + receipt + }; + let candidate_receipt2 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(2_u32); + receipt + }; + let candidate_index1 = 0; + let candidate_index2 = 1; // Add block hash 00. ChainBuilder::new() @@ -1341,21 +1520,31 @@ fn subsystem_accepts_duplicate_assignment() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: Some(vec![ + (candidate_receipt1, CoreIndex(0), GroupIndex(1)), + (candidate_receipt2, CoreIndex(1), GroupIndex(1)), + ]), + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; - let rx = check_and_import_assignment( + // Initial assignment. + let rx = check_and_import_assignment_v2( &mut virtual_overseer, block_hash, - candidate_index, + vec![candidate_index1, candidate_index2], validator, ) .await; assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted)); + // Test with single assigned core. let rx = check_and_import_assignment( &mut virtual_overseer, block_hash, @@ -1366,6 +1555,18 @@ fn subsystem_accepts_duplicate_assignment() { assert_eq!(rx.await, Ok(AssignmentCheckResult::AcceptedDuplicate)); + // Test with multiple assigned cores. This cannot happen in practice, as tranche0 + // assignments are sent first, but we should still ensure correct behavior. + let rx = check_and_import_assignment_v2( + &mut virtual_overseer, + block_hash, + vec![candidate_index1, candidate_index2], + validator, + ) + .await; + + assert_eq!(rx.await, Ok(AssignmentCheckResult::AcceptedDuplicate)); + virtual_overseer }); } @@ -1392,7 +1593,12 @@ fn subsystem_rejects_assignment_with_unknown_candidate() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1416,6 +1622,68 @@ fn subsystem_rejects_assignment_with_unknown_candidate() { }); } +#[test] +fn subsystem_rejects_oversized_bitfields() { + test_harness(HarnessConfig::default(), |test_harness| async move { + let TestHarness { mut virtual_overseer, 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_index = 10; + let validator = ValidatorIndex(0); + + ChainBuilder::new() + .add_block( + block_hash, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, + ) + .build(&mut virtual_overseer) + .await; + + let rx = check_and_import_assignment( + &mut virtual_overseer, + block_hash, + candidate_index, + validator, + ) + .await; + + assert_eq!( + rx.await, + Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield( + candidate_index as usize + 1 + ))), + ); + + let rx = check_and_import_assignment_v2( + &mut virtual_overseer, + block_hash, + vec![1, 2, 10, 50], + validator, + ) + .await; + + assert_eq!( + rx.await, + Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield(51))), + ); + virtual_overseer + }); +} + #[test] fn subsystem_accepts_and_imports_approval_after_assignment() { test_harness(HarnessConfig::default(), |test_harness| async move { @@ -1448,7 +1716,12 @@ fn subsystem_accepts_and_imports_approval_after_assignment() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1539,6 +1812,7 @@ fn subsystem_second_approval_import_only_schedules_wakeups() { slot: Slot::from(0), candidates: None, session_info: Some(session_info), + end_syncing: false, }, ) .build(&mut virtual_overseer) @@ -1622,7 +1896,12 @@ fn subsystem_assignment_import_updates_candidate_entry_and_schedules_wakeup() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1671,7 +1950,12 @@ fn subsystem_process_wakeup_schedules_wakeup() { block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot: Slot::from(1), candidates: None, session_info: None }, + BlockConfig { + slot: Slot::from(1), + candidates: None, + session_info: None, + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -1723,7 +2007,7 @@ fn linear_import_act_on_leaf() { hash, head, i, - BlockConfig { slot, candidates: None, session_info: None }, + BlockConfig { slot, candidates: None, session_info: None, end_syncing: false }, ); head = hash; } @@ -1736,14 +2020,15 @@ fn linear_import_act_on_leaf() { &mut virtual_overseer, FromOrchestra::Communication { msg: ApprovalVotingMessage::CheckAndImportAssignment( - IndirectAssignmentCert { + IndirectAssignmentCertV2 { block_hash: head, validator: 0u32.into(), cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0, - }), + }) + .into(), }, - 0u32, + 0u32.into(), tx, ), }, @@ -1780,7 +2065,7 @@ fn forkful_import_at_same_height_act_on_leaf() { hash, head, i, - BlockConfig { slot, candidates: None, session_info: None }, + BlockConfig { slot, candidates: None, session_info: None, end_syncing: false }, ); head = hash; } @@ -1794,7 +2079,7 @@ fn forkful_import_at_same_height_act_on_leaf() { hash, head, session, - BlockConfig { slot, candidates: None, session_info: None }, + BlockConfig { slot, candidates: None, session_info: None, end_syncing: false }, ); } builder.build(&mut virtual_overseer).await; @@ -1806,14 +2091,15 @@ fn forkful_import_at_same_height_act_on_leaf() { &mut virtual_overseer, FromOrchestra::Communication { msg: ApprovalVotingMessage::CheckAndImportAssignment( - IndirectAssignmentCert { + IndirectAssignmentCertV2 { block_hash: head, validator: 0u32.into(), cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0, - }), + }) + .into(), }, - 0u32, + 0u32.into(), tx, ), }, @@ -1826,6 +2112,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(); @@ -1879,6 +2250,7 @@ fn import_checked_approval_updates_entries_and_schedules() { slot, candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]), session_info: Some(session_info), + end_syncing: false, }, ); builder.build(&mut virtual_overseer).await; @@ -2034,6 +2406,7 @@ fn subsystem_import_checked_approval_sets_one_block_bit_at_a_time() { (candidate_receipt2, CoreIndex(1), GroupIndex(1)), ]), session_info: Some(session_info), + end_syncing: false, }, ) .build(&mut virtual_overseer) @@ -2156,6 +2529,7 @@ fn approved_ancestor_test( slot: Slot::from(i as u64), candidates: Some(vec![(candidate_receipt, CoreIndex(0), GroupIndex(0))]), session_info: None, + end_syncing: false, }, ); } @@ -2257,8 +2631,24 @@ fn subsystem_validate_approvals_cache() { let mut assignments = HashMap::new(); let _ = assignments.insert( CoreIndex(0), - approval_db::v1::OurAssignment { - cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 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.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)] + .try_into() + .unwrap(), + }), tranche: 0, validator_index: ValidatorIndex(0), triggered: false, @@ -2318,13 +2708,19 @@ fn subsystem_validate_approvals_cache() { slot, candidates: candidates.clone(), session_info: Some(session_info.clone()), + end_syncing: false, }, ) .add_block( fork_block_hash, ChainBuilder::GENESIS_HASH, 1, - BlockConfig { slot, candidates, session_info: Some(session_info) }, + BlockConfig { + slot, + candidates, + session_info: Some(session_info), + end_syncing: false, + }, ) .build(&mut virtual_overseer) .await; @@ -2355,18 +2751,150 @@ fn subsystem_validate_approvals_cache() { }); } -/// Ensure that when two assignments are imported, only one triggers the Approval Checking work -async fn handle_double_assignment_import( - virtual_overseer: &mut VirtualOverseer, +#[test] +fn subsystem_doesnt_distribute_duplicate_compact_assignments() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + let cert = garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1)].try_into().unwrap(), + }); + + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: cert.clone(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert, + 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, + sync_oracle_handle: _sync_oracle_handle, + clock, + .. + } = 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_receipt1 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(1_u32); + receipt + }; + let candidate_receipt2 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(2_u32); + receipt + }; + let candidate_index1 = 0; + let candidate_index2 = 1; + + // Add block hash 00. + ChainBuilder::new() + .add_block( + block_hash, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot: Slot::from(0), + candidates: Some(vec![ + (candidate_receipt1.clone(), CoreIndex(0), GroupIndex(1)), + (candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)), + ]), + session_info: None, + end_syncing: false, + }, + ) + .build(&mut virtual_overseer) + .await; + + // Activate the wakeup present above, and sleep to allow process_wakeups to execute.. + assert_eq!(Some(2), clock.inner.lock().next_wakeup()); + gum::trace!("clock \n{:?}\n", clock.inner.lock()); + + clock.inner.lock().wakeup_all(100); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(100)).await; + + // Assignment is distributed only once from `approval-voting` + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + c_indices, + )) => { + assert_eq!(c_indices, vec![candidate_index1, candidate_index2].try_into().unwrap()); + } + ); + + // Candidate 1 + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + // Candidate 2 + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + // Check if assignment was triggered for candidate 1. + let candidate_entry = + store.load_candidate_entry(&candidate_receipt1.hash()).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + + // Check if assignment was triggered for candidate 2. + let candidate_entry = + store.load_candidate_entry(&candidate_receipt2.hash()).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + virtual_overseer + }); +} + +/// Ensure that when two assignments are imported, only one triggers the Approval Checking work +async fn handle_double_assignment_import( + virtual_overseer: &mut VirtualOverseer, candidate_index: CandidateIndex, ) { assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( _, - c_index, + c_indices, )) => { - assert_eq!(candidate_index, c_index); + assert_eq!(Into::::into(candidate_index), c_indices); } ); @@ -2379,21 +2907,29 @@ async fn handle_double_assignment_import( _, c_index )) => { - assert_eq!(candidate_index, c_index); + assert_eq!(Into::::into(candidate_index), c_index); } ); assert_matches!( overseer_recv(virtual_overseer).await, - AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, _, timeout, tx)) if timeout == PvfExecTimeoutKind::Approval => { - tx.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + 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::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } ); assert_matches!( @@ -2401,6 +2937,19 @@ async fn handle_double_assignment_import( 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(_)) + ); // Assert that there are no more messages being sent by the subsystem assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); } @@ -2475,8 +3024,9 @@ where let mut assignments = HashMap::new(); let _ = assignments.insert( CoreIndex(0), - approval_db::v1::OurAssignment { - cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }) + .into(), tranche: our_assigned_tranche, validator_index: ValidatorIndex(0), triggered: false, @@ -2539,6 +3089,7 @@ where slot, candidates: Some(vec![(candidate_receipt, CoreIndex(0), GroupIndex(2))]), session_info: Some(session_info), + end_syncing: false, }, ) .build(&mut virtual_overseer) @@ -2610,14 +3161,12 @@ async fn step_until_done(clock: &MockClock) { futures_timer::Delay::new(Duration::from_millis(200)).await; let mut clock = clock.inner.lock(); if let Some(tick) = clock.next_wakeup() { - println!("TICK: {:?}", tick); relevant_ticks.push(tick); clock.set_tick(tick); } else { break } } - println!("relevant_ticks: {:?}", relevant_ticks); } #[test] @@ -2858,6 +3407,7 @@ fn pre_covers_dont_stall_approval() { slot, candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]), session_info: Some(session_info), + end_syncing: false, }, ); builder.build(&mut virtual_overseer).await; @@ -3035,6 +3585,7 @@ fn waits_until_approving_assignments_are_old_enough() { slot, candidates: Some(vec![(candidate_descriptor, CoreIndex(0), GroupIndex(0))]), session_info: Some(session_info), + end_syncing: false, }, ); builder.build(&mut virtual_overseer).await; @@ -3132,3 +3683,1022 @@ 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()), + end_syncing: false, + }, + ) + .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: _ } = 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()), + end_syncing: false, + }, + ) + .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 + }); +} + +// Builds a chain with a fork where both relay blocks include the same candidate. +async fn build_chain_with_two_blocks_with_one_candidate_each( + block_hash1: Hash, + block_hash2: Hash, + slot: Slot, + sync_oracle_handle: TestSyncOracleHandle, + candidate_receipt: CandidateReceipt, +) -> (ChainBuilder, SessionInfo) { + 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_receipt.clone(), CoreIndex(0), GroupIndex(0))]); + let mut chain_builder = ChainBuilder::new(); + + chain_builder + .major_syncing(sync_oracle_handle.is_major_syncing.clone()) + .add_block( + block_hash1, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates: candidates.clone(), + session_info: Some(session_info.clone()), + end_syncing: false, + }, + ) + .add_block( + block_hash2, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates, + session_info: Some(session_info.clone()), + end_syncing: true, + }, + ); + (chain_builder, session_info) +} + +async fn setup_overseer_with_two_blocks_each_with_one_assignment_triggered( + virtual_overseer: &mut VirtualOverseer, + store: TestStore, + clock: &Box, + sync_oracle_handle: TestSyncOracleHandle, +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + chain_builder.build(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_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); +} + +// Tests that for candidates that we did not approve yet, for which we triggered the assignment and +// the approval work we restart the work to approve it. +#[test] +fn subsystem_relaunches_approval_work_on_restart() { + 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.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)] + .try_into() + .unwrap(), + }), + 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(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_two_blocks_each_with_one_assignment_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + // Bail early after the assignment has been distributed but before we answer with the mocked + // approval from CandidateValidation. + virtual_overseer + }); + + // Restart a new approval voting subsystem with the same database and major syncing true untill + // the last leaf. + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, 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 fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let slot = Slot::from(1); + clock.inner.lock().set_tick(slot_to_tick(slot + 2)); + let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionInfo(_, si_tx), + ) + ) => { + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionExecutorParams(_, si_tx), + ) + ) => { + // Make sure all SessionExecutorParams calls are not made for the leaf (but for its relay parent) + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + + // On major syncing ending Approval voting should send all the necessary messages for a + // candidate to be approved. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + // Guarantees the approval work has been relaunched. + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut 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(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + +// Test that cached approvals, which are candidates that we approved but we didn't issue +// the signature yet because we want to coalesce it with more candidate are sent after restart. +#[test] +fn subsystem_sends_pending_approvals_on_approval_restart() { + 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.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)] + .try_into() + .unwrap(), + }), + 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(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_two_blocks_each_with_one_assignment_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut 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(); + } + ); + + // Configure a big coalesce number, so that the signature is cached instead of being sent to + // approval-distribution. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 6, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 6, + })); + } + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); + + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + // On restart signatures should be sent to approval-distribution without relaunching the + // approval work. + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, 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 fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let slot = Slot::from(1); + + clock.inner.lock().set_tick(slot_to_tick(slot + 2)); + let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionInfo(_, si_tx), + ) + ) => { + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionExecutorParams(_, si_tx), + ) + ) => { + // Make sure all SessionExecutorParams calls are not made for the leaf (but for its relay parent) + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} diff --git a/polkadot/node/core/approval-voting/src/time.rs b/polkadot/node/core/approval-voting/src/time.rs index 34132dc22b2338c168dd18a21b31fd812d49229e..99dfbe07678f2f0956565e92ffaf196b6f482af9 100644 --- a/polkadot/node/core/approval-voting/src/time.rs +++ b/polkadot/node/core/approval-voting/src/time.rs @@ -16,22 +16,31 @@ //! Time utilities for approval voting. -use futures::prelude::*; -use polkadot_node_primitives::approval::DelayTranche; +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}, }; -const TICK_DURATION_MILLIS: u64 = 500; +use polkadot_primitives::{Hash, ValidatorIndex}; +pub const TICK_DURATION_MILLIS: u64 = 500; /// A base unit of time, starting from the Unix epoch, split into half-second intervals. -pub(crate) type Tick = u64; +pub type Tick = u64; /// A clock which allows querying of the current tick as well as /// waiting for a tick to be reached. -pub(crate) trait Clock { +pub trait Clock { /// Yields the current tick. fn tick_now(&self) -> Tick; @@ -40,7 +49,7 @@ pub(crate) trait Clock { } /// Extension methods for clocks. -pub(crate) trait ClockExt { +pub trait ClockExt { fn tranche_now(&self, slot_duration_millis: u64, base_slot: Slot) -> DelayTranche; } @@ -52,7 +61,8 @@ impl ClockExt for C { } /// A clock which uses the actual underlying system clock. -pub(crate) struct SystemClock; +#[derive(Clone)] +pub struct SystemClock; impl Clock for SystemClock { /// Yields the current tick. @@ -84,7 +94,172 @@ fn tick_to_time(tick: Tick) -> SystemTime { } /// assumes `slot_duration_millis` evenly divided by tick duration. -pub(crate) fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { +pub fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; u64::from(slot) * ticks_per_slot } + +/// Converts a tick to the slot number. +pub fn tick_to_slot_number(slot_duration_millis: u64, tick: Tick) -> Slot { + let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; + (tick / ticks_per_slot).into() +} + +/// Converts a tranche from a slot to the tick number. +pub fn tranche_to_tick(slot_duration_millis: u64, slot: Slot, tranche: u32) -> Tick { + slot_number_to_tick(slot_duration_millis, slot) + tranche as u64 +} + +/// A list of delayed futures that gets triggered when the waiting time has expired and it is +/// time to sign the candidate. +/// We have a timer per relay-chain block. +#[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 955fe37d7c39e0cc4a840fe7bdd8e2a98be364db..05212da7479848830a8fb70ee4c38d449e9a6a7e 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -1,15 +1,19 @@ [package] name = "polkadot-node-core-av-store" -version = "1.0.0" +description = "The Availability Store subsystem. Wrapper over the DB that stores availability data and chunks." +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } bitvec = "1.0.0" @@ -24,7 +28,7 @@ sp-consensus = { path = "../../../../substrate/primitives/consensus/common", def polkadot-node-jaeger = { path = "../../jaeger" } [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" @@ -33,5 +37,5 @@ sp-core = { path = "../../../../substrate/primitives/core" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } -parking_lot = "0.12.0" +parking_lot = "0.12.1" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index e7e6358e8a46ea546fe1f97d4e01a094db7fce63..d0c1f9aa4832dfe629e1fb20d3082f27c260a7d5 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "polkadot-node-core-backing" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" @@ -16,8 +20,9 @@ erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" +schnellru = "0.2.1" [dev-dependencies] sp-core = { path = "../../../../substrate/primitives/core" } @@ -27,5 +32,6 @@ sc-keystore = { path = "../../../../substrate/client/keystore" } sp-tracing = { path = "../../../../substrate/primitives/tracing" } futures = { version = "0.3.21", features = ["thread-pool"] } assert_matches = "1.4.0" +rstest = "0.18.2" polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } diff --git a/polkadot/node/core/backing/src/error.rs b/polkadot/node/core/backing/src/error.rs index 1b00a62510b7c634b3135a56ca6412c0948ef6a0..64955a393962076bd7b202dd31a7d69d46a4d1b9 100644 --- a/polkadot/node/core/backing/src/error.rs +++ b/polkadot/node/core/backing/src/error.rs @@ -48,6 +48,9 @@ pub enum Error { #[error("Candidate is not found")] CandidateNotFound, + #[error("CoreIndex cannot be determined for a candidate")] + CoreIndexUnavailable, + #[error("Signature is invalid")] InvalidSignature, diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 27b5972d98274ddf6b5532aa8ebba0996c49069a..69bf2e956a0f6bb7f3b9f4a19446e5e7d2c125bb 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -77,6 +77,7 @@ use futures::{ stream::FuturesOrdered, FutureExt, SinkExt, StreamExt, TryFutureExt, }; +use schnellru::{ByLength, LruMap}; use error::{Error, FatalResult}; use polkadot_node_primitives::{ @@ -104,10 +105,12 @@ use polkadot_node_subsystem_util::{ Validator, }; use polkadot_primitives::{ + vstaging::{node_features::FeatureIndex, NodeFeatures}, BackedCandidate, CandidateCommitments, CandidateHash, CandidateReceipt, - CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, Hash, Id as ParaId, - PersistedValidationData, PvfExecTimeoutKind, SigningContext, ValidationCode, ValidatorId, - ValidatorIndex, ValidatorSignature, ValidityAttestation, + CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, GroupIndex, GroupRotationInfo, + Hash, Id as ParaId, IndexedVec, PersistedValidationData, PvfExecKind, SessionIndex, + SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, + ValidityAttestation, }; use sp_keystore::KeystorePtr; use statement_table::{ @@ -118,6 +121,7 @@ use statement_table::{ }, Config as TableConfig, Context as TableContextTrait, Table, }; +use util::{runtime::request_node_features, vstaging::get_disabled_validators_with_fallback}; mod error; @@ -208,7 +212,9 @@ struct PerRelayParentState { /// The hash of the relay parent on top of which this job is doing it's work. parent: Hash, /// The `ParaId` assigned to the local validator at this relay parent. - assignment: Option, + assigned_para: Option, + /// The `CoreIndex` assigned to the local validator at this relay parent. + assigned_core: Option, /// The candidates that are backed by enough validators in their group, by hash. backed: HashSet, /// The table of candidates and statements under this relay-parent. @@ -223,6 +229,15 @@ struct PerRelayParentState { fallbacks: HashMap, /// The minimum backing votes threshold. minimum_backing_votes: u32, + /// If true, we're appending extra bits in the BackedCandidate validator indices bitfield, + /// which represent the assigned core index. True if ElasticScalingMVP is enabled. + inject_core_index: bool, + /// The core states for all cores. + cores: Vec, + /// The validator index -> group mapping at this relay parent. + validator_to_group: Arc>>, + /// The associated group rotation information. + group_rotation_info: GroupRotationInfo, } struct PerCandidateState { @@ -274,6 +289,9 @@ struct State { /// This is guaranteed to have an entry for each candidate with a relay parent in the implicit /// or explicit view for which a `Seconded` statement has been successfully imported. per_candidate: HashMap, + /// Cache the per-session Validator->Group mapping. + validator_to_group_cache: + LruMap>>>, /// A cloneable sender which is dispatched to background candidate validation tasks to inform /// the main task of the result. background_validation_tx: mpsc::Sender<(Hash, ValidatedCandidateCommand)>, @@ -291,6 +309,7 @@ impl State { per_leaf: HashMap::default(), per_relay_parent: HashMap::default(), per_candidate: HashMap::new(), + validator_to_group_cache: LruMap::new(ByLength::new(2)), background_validation_tx, keystore, } @@ -378,17 +397,32 @@ struct AttestingData { backing: Vec, } -#[derive(Default)] +#[derive(Default, Debug)] struct TableContext { validator: Option, - groups: HashMap>, + groups: HashMap>, validators: Vec, + disabled_validators: Vec, +} + +impl TableContext { + // Returns `true` if the provided `ValidatorIndex` is in the disabled validators list + pub fn validator_is_disabled(&self, validator_idx: &ValidatorIndex) -> bool { + self.disabled_validators + .iter() + .any(|disabled_val_idx| *disabled_val_idx == *validator_idx) + } + + // Returns `true` if the local validator is in the disabled validators list + pub fn local_validator_is_disabled(&self) -> Option { + self.validator.as_ref().map(|v| v.disabled()) + } } impl TableContextTrait for TableContext { type AuthorityId = ValidatorIndex; type Digest = CandidateHash; - type GroupId = ParaId; + type GroupId = CoreIndex; type Signature = ValidatorSignature; type Candidate = CommittedCandidateReceipt; @@ -396,15 +430,11 @@ impl TableContextTrait for TableContext { candidate.hash() } - fn candidate_group(candidate: &CommittedCandidateReceipt) -> ParaId { - candidate.descriptor().para_id - } - - fn is_member_of(&self, authority: &ValidatorIndex, group: &ParaId) -> bool { - self.groups.get(group).map_or(false, |g| g.iter().any(|a| a == authority)) + fn is_member_of(&self, authority: &ValidatorIndex, core: &CoreIndex) -> bool { + self.groups.get(core).map_or(false, |g| g.iter().any(|a| a == authority)) } - fn get_group_size(&self, group: &ParaId) -> Option { + fn get_group_size(&self, group: &CoreIndex) -> Option { self.groups.get(group).map(|g| g.len()) } } @@ -426,19 +456,20 @@ fn primitive_statement_to_table(s: &SignedFullStatementWithPVD) -> TableSignedSt fn table_attested_to_backed( attested: TableAttestedCandidate< - ParaId, + CoreIndex, CommittedCandidateReceipt, ValidatorIndex, ValidatorSignature, >, table_context: &TableContext, + inject_core_index: bool, ) -> Option { - let TableAttestedCandidate { candidate, validity_votes, group_id: para_id } = attested; + let TableAttestedCandidate { candidate, validity_votes, group_id: core_index } = attested; let (ids, validity_votes): (Vec<_>, Vec) = validity_votes.into_iter().map(|(id, vote)| (id, vote.into())).unzip(); - let group = table_context.groups.get(¶_id)?; + let group = table_context.groups.get(&core_index)?; let mut validator_indices = BitVec::with_capacity(group.len()); @@ -463,14 +494,15 @@ fn table_attested_to_backed( } vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group); - Some(BackedCandidate { + Some(BackedCandidate::new( candidate, - validity_votes: vote_positions + vote_positions .into_iter() .map(|(pos_in_votes, _pos_in_group)| validity_votes[pos_in_votes].clone()) .collect(), validator_indices, - }) + inject_core_index.then_some(core_index), + )) } async fn store_available_data( @@ -551,8 +583,8 @@ async fn request_pov( async fn request_candidate_validation( sender: &mut impl overseer::CandidateBackingSenderTrait, - pvd: PersistedValidationData, - code: ValidationCode, + validation_data: PersistedValidationData, + validation_code: ValidationCode, candidate_receipt: CandidateReceipt, pov: Arc, executor_params: ExecutorParams, @@ -560,15 +592,15 @@ async fn request_candidate_validation( let (tx, rx) = oneshot::channel(); sender - .send_message(CandidateValidationMessage::ValidateFromExhaustive( - pvd, - code, + .send_message(CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, pov, executor_params, - PvfExecTimeoutKind::Backing, - tx, - )) + exec_kind: PvfExecKind::Backing, + response_sender: tx, + }) .await; match rx.await { @@ -955,7 +987,14 @@ async fn handle_active_leaves_update( // construct a `PerRelayParent` from the runtime API // and insert it. - let per = construct_per_relay_parent_state(ctx, maybe_new, &state.keystore, mode).await?; + let per = construct_per_relay_parent_state( + ctx, + maybe_new, + &state.keystore, + &mut state.validator_to_group_cache, + mode, + ) + .await?; if let Some(per) = per { state.per_relay_parent.insert(maybe_new, per); @@ -965,31 +1004,112 @@ async fn handle_active_leaves_update( Ok(()) } +macro_rules! try_runtime_api { + ($x: expr) => { + match $x { + Ok(x) => x, + Err(err) => { + // Only bubble up fatal errors. + error::log_error(Err(Into::::into(err).into()))?; + + // We can't do candidate validation work if we don't have the + // requisite runtime API data. But these errors should not take + // down the node. + return Ok(None) + }, + } + }; +} + +fn core_index_from_statement( + validator_to_group: &IndexedVec>, + group_rotation_info: &GroupRotationInfo, + cores: &[CoreState], + statement: &SignedFullStatementWithPVD, +) -> Option { + let compact_statement = statement.as_unchecked(); + let candidate_hash = CandidateHash(*compact_statement.unchecked_payload().candidate_hash()); + + let n_cores = cores.len(); + + gum::trace!( + target:LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len(), + ?candidate_hash, + "Extracting core index from statement" + ); + + let statement_validator_index = statement.validator_index(); + let Some(Some(group_index)) = validator_to_group.get(statement_validator_index) else { + gum::debug!( + target: LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len() , + ?candidate_hash, + "Invalid validator index: {:?}", + statement_validator_index + ); + return None + }; + + // First check if the statement para id matches the core assignment. + let core_index = group_rotation_info.core_for_group(*group_index, n_cores); + + if core_index.0 as usize > n_cores { + gum::warn!(target: LOG_TARGET, ?candidate_hash, ?core_index, n_cores, "Invalid CoreIndex"); + return None + } + + if let StatementWithPVD::Seconded(candidate, _pvd) = statement.payload() { + let candidate_para_id = candidate.descriptor.para_id; + let assigned_para_id = match &cores[core_index.0 as usize] { + CoreState::Free => { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Invalid CoreIndex, core is not assigned to any para_id"); + return None + }, + CoreState::Occupied(occupied) => + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + return None + }, + CoreState::Scheduled(scheduled) => scheduled.para_id, + }; + + if assigned_para_id != candidate_para_id { + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?core_index, + ?assigned_para_id, + ?candidate_para_id, + "Invalid CoreIndex, core is assigned to a different para_id" + ); + return None + } + return Some(core_index) + } else { + return Some(core_index) + } +} + /// Load the data necessary to do backing work on top of a relay-parent. #[overseer::contextbounds(CandidateBacking, prefix = self::overseer)] async fn construct_per_relay_parent_state( ctx: &mut Context, relay_parent: Hash, keystore: &KeystorePtr, + validator_to_group_cache: &mut LruMap< + SessionIndex, + Arc>>, + >, mode: ProspectiveParachainsMode, ) -> Result, Error> { - macro_rules! try_runtime_api { - ($x: expr) => { - match $x { - Ok(x) => x, - Err(err) => { - // Only bubble up fatal errors. - error::log_error(Err(Into::::into(err).into()))?; - - // We can't do candidate validation work if we don't have the - // requisite runtime API data. But these errors should not take - // down the node. - return Ok(None) - }, - } - }; - } - let parent = relay_parent; let (session_index, validators, groups, cores) = futures::try_join!( @@ -1004,40 +1124,69 @@ async fn construct_per_relay_parent_state( .map_err(Error::JoinMultiple)?; let session_index = try_runtime_api!(session_index); + + let inject_core_index = request_node_features(parent, session_index, ctx.sender()) + .await? + .unwrap_or(NodeFeatures::EMPTY) + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + gum::debug!(target: LOG_TARGET, inject_core_index, ?parent, "New state"); + let validators: Vec<_> = try_runtime_api!(validators); let (validator_groups, group_rotation_info) = try_runtime_api!(groups); let cores = try_runtime_api!(cores); let minimum_backing_votes = try_runtime_api!(request_min_backing_votes(parent, session_index, ctx.sender()).await); + // TODO: https://github.com/paritytech/polkadot-sdk/issues/1940 + // Once runtime ver `DISABLED_VALIDATORS_RUNTIME_REQUIREMENT` is released remove this call to + // `get_disabled_validators_with_fallback`, add `request_disabled_validators` call to the + // `try_join!` above and use `try_runtime_api!` to get `disabled_validators` + let disabled_validators = + get_disabled_validators_with_fallback(ctx.sender(), parent).await.map_err(|e| { + Error::UtilError(TryFrom::try_from(e).expect("the conversion is infallible; qed")) + })?; + let signing_context = SigningContext { parent_hash: parent, session_index }; - let validator = - match Validator::construct(&validators, signing_context.clone(), keystore.clone()) { - Ok(v) => Some(v), - Err(util::Error::NotAValidator) => None, - Err(e) => { - gum::warn!( - target: LOG_TARGET, - err = ?e, - "Cannot participate in candidate backing", - ); + let validator = match Validator::construct( + &validators, + &disabled_validators, + signing_context.clone(), + keystore.clone(), + ) { + Ok(v) => Some(v), + Err(util::Error::NotAValidator) => None, + Err(e) => { + gum::warn!( + target: LOG_TARGET, + err = ?e, + "Cannot participate in candidate backing", + ); - return Ok(None) - }, - }; + return Ok(None) + }, + }; - let mut groups = HashMap::new(); let n_cores = cores.len(); - let mut assignment = None; - for (idx, core) in cores.into_iter().enumerate() { + let mut groups = HashMap::>::new(); + let mut assigned_core = None; + let mut assigned_para = None; + + for (idx, core) in cores.iter().enumerate() { let core_para_id = match core { CoreState::Scheduled(scheduled) => scheduled.para_id, CoreState::Occupied(occupied) => if mode.is_enabled() { // Async backing makes it legal to build on top of // occupied core. - occupied.candidate_descriptor.para_id + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + continue + } } else { continue }, @@ -1048,13 +1197,29 @@ async fn construct_per_relay_parent_state( let group_index = group_rotation_info.group_for_core(core_index, n_cores); if let Some(g) = validator_groups.get(group_index.0 as usize) { if validator.as_ref().map_or(false, |v| g.contains(&v.index())) { - assignment = Some(core_para_id); + assigned_para = Some(core_para_id); + assigned_core = Some(core_index); } - groups.insert(core_para_id, g.clone()); + groups.insert(core_index, g.clone()); } } + gum::debug!(target: LOG_TARGET, ?groups, "TableContext"); + + let validator_to_group = validator_to_group_cache + .get_or_insert(session_index, || { + let mut vector = vec![None; validators.len()]; - let table_context = TableContext { groups, validators, validator }; + for (group_idx, validator_group) in validator_groups.iter().enumerate() { + for validator in validator_group { + vector[validator.0 as usize] = Some(GroupIndex(group_idx as u32)); + } + } + + Arc::new(IndexedVec::<_, _>::from(vector)) + }) + .expect("Just inserted"); + + let table_context = TableContext { validator, groups, validators, disabled_validators }; let table_config = TableConfig { allow_multiple_seconded: match mode { ProspectiveParachainsMode::Enabled { .. } => true, @@ -1065,7 +1230,8 @@ async fn construct_per_relay_parent_state( Ok(Some(PerRelayParentState { prospective_parachains_mode: mode, parent, - assignment, + assigned_core, + assigned_para, backed: HashSet::new(), table: Table::new(table_config), table_context, @@ -1073,6 +1239,10 @@ async fn construct_per_relay_parent_state( awaiting_validation: HashSet::new(), fallbacks: HashMap::new(), minimum_backing_votes, + inject_core_index, + cores, + validator_to_group: validator_to_group.clone(), + group_rotation_info, })) } @@ -1490,15 +1660,16 @@ async fn import_statement( per_candidate: &mut HashMap, statement: &SignedFullStatementWithPVD, ) -> Result, Error> { + let candidate_hash = statement.payload().candidate_hash(); + gum::debug!( target: LOG_TARGET, statement = ?statement.payload().to_compact(), validator_index = statement.validator_index().0, + ?candidate_hash, "Importing statement", ); - let candidate_hash = statement.payload().candidate_hash(); - // If this is a new candidate (statement is 'seconded' and candidate is unknown), // we need to create an entry in the `PerCandidateState` map. // @@ -1564,7 +1735,15 @@ async fn import_statement( let stmt = primitive_statement_to_table(statement); - Ok(rp_state.table.import_statement(&rp_state.table_context, stmt)) + let core = core_index_from_statement( + &rp_state.validator_to_group, + &rp_state.group_rotation_info, + &rp_state.cores, + statement, + ) + .ok_or(Error::CoreIndexUnavailable)?; + + Ok(rp_state.table.import_statement(&rp_state.table_context, core, stmt)) } /// Handles a summary received from [`import_statement`] and dispatches `Backed` notifications and @@ -1586,8 +1765,12 @@ async fn post_import_statement_actions( // `HashSet::insert` returns true if the thing wasn't in there already. if rp_state.backed.insert(candidate_hash) { - if let Some(backed) = table_attested_to_backed(attested, &rp_state.table_context) { - let para_id = backed.candidate.descriptor.para_id; + if let Some(backed) = table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) { + let para_id = backed.candidate().descriptor.para_id; gum::debug!( target: LOG_TARGET, candidate_hash = ?candidate_hash, @@ -1608,7 +1791,7 @@ async fn post_import_statement_actions( // notify collator protocol. ctx.send_message(CollatorProtocolMessage::Backed { para_id, - para_head: backed.candidate.descriptor.para_head, + para_head: backed.candidate().descriptor.para_head, }) .await; // Notify statement distribution of backed candidate. @@ -1625,8 +1808,14 @@ async fn post_import_statement_actions( ); ctx.send_unbounded_message(message); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Cannot get BackedCandidate"); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate already known"); } + } else { + gum::debug!(target: LOG_TARGET, "No attested candidate"); } issue_new_misbehaviors(ctx, rp_state.parent, &mut rp_state.table); @@ -1693,18 +1882,20 @@ async fn background_validate_and_make_available( if rp_state.awaiting_validation.insert(candidate_hash) { // spawn background task. let bg = async move { - if let Err(e) = validate_and_make_available(params).await { - if let Error::BackgroundValidationMpsc(error) = e { + if let Err(error) = validate_and_make_available(params).await { + if let Error::BackgroundValidationMpsc(error) = error { gum::debug!( target: LOG_TARGET, + ?candidate_hash, ?error, "Mpsc background validation mpsc died during validation- leaf no longer active?" ); } else { gum::error!( target: LOG_TARGET, - "Failed to validate and make available: {:?}", - e + ?candidate_hash, + ?error, + "Failed to validate and make available", ); } } @@ -1726,6 +1917,19 @@ async fn kick_off_validation_work( background_validation_tx: &mpsc::Sender<(Hash, ValidatedCandidateCommand)>, attesting: AttestingData, ) -> Result<(), Error> { + // Do nothing if the local validator is disabled or not a validator at all + match rp_state.table_context.local_validator_is_disabled() { + Some(true) => { + gum::info!(target: LOG_TARGET, "We are disabled - don't kick off validation"); + return Ok(()) + }, + Some(false) => {}, // we are not disabled - move on + None => { + gum::debug!(target: LOG_TARGET, "We are not a validator - don't kick off validation"); + return Ok(()) + }, + } + let candidate_hash = attesting.candidate.hash(); if rp_state.issued_statements.contains(&candidate_hash) { return Ok(()) @@ -1783,6 +1987,16 @@ async fn maybe_validate_and_import( }, }; + // Don't import statement if the sender is disabled + if rp_state.table_context.validator_is_disabled(&statement.validator_index()) { + gum::debug!( + target: LOG_TARGET, + sender_validator_idx = ?statement.validator_index(), + "Not importing statement because the sender is disabled" + ); + return Ok(()) + } + let res = import_statement(ctx, rp_state, &mut state.per_candidate, &statement).await; // if we get an Error::RejectedByProspectiveParachains, @@ -1807,9 +2021,10 @@ async fn maybe_validate_and_import( let candidate_hash = summary.candidate; - if Some(summary.group_id) != rp_state.assignment { + if Some(summary.group_id) != rp_state.assigned_core { return Ok(()) } + let attesting = match statement.payload() { StatementWithPVD::Seconded(receipt, _) => { let attesting = AttestingData { @@ -1944,11 +2159,19 @@ async fn handle_second_message( Some(r) => r, }; + // Just return if the local validator is disabled. If we are here the local node should be a + // validator but defensively use `unwrap_or(false)` to continue processing in this case. + if rp_state.table_context.local_validator_is_disabled().unwrap_or(false) { + gum::warn!(target: LOG_TARGET, "Local validator is disabled. Don't validate and second"); + return Ok(()) + } + // Sanity check that candidate is from our assignment. - if Some(candidate.descriptor().para_id) != rp_state.assignment { + if Some(candidate.descriptor().para_id) != rp_state.assigned_para { gum::debug!( target: LOG_TARGET, - our_assignment = ?rp_state.assignment, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, collation = ?candidate.descriptor().para_id, "Subsystem asked to second for para outside of our assignment", ); @@ -1956,6 +2179,14 @@ async fn handle_second_message( return Ok(()) } + gum::debug!( + target: LOG_TARGET, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, + collation = ?candidate.descriptor().para_id, + "Current assignments vs collation", + ); + // If the message is a `CandidateBackingMessage::Second`, sign and dispatch a // Seconded statement only if we have not signed a Valid statement for the requested candidate. // @@ -1990,6 +2221,7 @@ async fn handle_statement_message( ) -> Result<(), Error> { let _timer = metrics.time_process_statement(); + // Validator disabling is handled in `maybe_validate_and_import` match maybe_validate_and_import(ctx, state, relay_parent, statement).await { Err(Error::ValidationFailed(_)) => Ok(()), Err(e) => Err(e), @@ -2027,7 +2259,13 @@ fn handle_get_backed_candidates_message( &rp_state.table_context, rp_state.minimum_backing_votes, ) - .and_then(|attested| table_attested_to_backed(attested, &rp_state.table_context)) + .and_then(|attested| { + table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) + }) }) .collect(); diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 35d17f3d905e4f342d32cbf195a61cc6feb371fd..e3cc5727435a3fef3532a9de342cd194ecd4fe3a 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -33,15 +33,16 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ - CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, PvfExecTimeoutKind, - ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, + vstaging::node_features, CandidateDescriptor, GroupRotationInfo, HeadData, + PersistedValidationData, PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; +use rstest::rstest; use sp_application_crypto::AppCrypto; use sp_keyring::Sr25519Keyring; use sp_keystore::Keystore; use sp_tracing as _; use statement_table::v2::Misbehavior; -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; mod prospective_parachains; @@ -65,18 +66,21 @@ fn dummy_pvd() -> PersistedValidationData { } } -struct TestState { +pub(crate) struct TestState { chain_ids: Vec, keystore: KeystorePtr, validators: Vec, validator_public: Vec, validation_data: PersistedValidationData, validator_groups: (Vec>, GroupRotationInfo), + validator_to_group: IndexedVec>, availability_cores: Vec, head_data: HashMap, signing_context: SigningContext, relay_parent: Hash, minimum_backing_votes: u32, + disabled_validators: Vec, + node_features: NodeFeatures, } impl TestState { @@ -113,6 +117,11 @@ impl Default for TestState { .into_iter() .map(|g| g.into_iter().map(ValidatorIndex).collect()) .collect(); + let validator_to_group: IndexedVec<_, _> = + vec![Some(0), Some(1), Some(0), Some(0), None, Some(0)] + .into_iter() + .map(|x| x.map(|x| GroupIndex(x))) + .collect(); let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 100, now: 1 }; @@ -142,12 +151,15 @@ impl Default for TestState { validators, validator_public, validator_groups: (validator_groups, group_rotation_info), + validator_to_group, availability_cores, head_data, validation_data, signing_context, relay_parent, minimum_backing_votes: LEGACY_MIN_BACKING_VOTES, + disabled_validators: Vec::new(), + node_features: Default::default(), } } } @@ -283,6 +295,16 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) => { + tx.send(Ok(test_state.node_features.clone())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -293,6 +315,26 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); } ); + + // Check that subsystem job issues a request for the runtime version. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::Version(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)).unwrap(); + } + ); + + // Check that subsystem job issues a request for the disabled validators. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::DisabledValidators(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.disabled_validators.clone())).unwrap(); + } + ); } async fn assert_validation_requests( @@ -329,32 +371,32 @@ async fn assert_validation_requests( async fn assert_validate_from_exhaustive( virtual_overseer: &mut VirtualOverseer, - pvd: &PersistedValidationData, - pov: &PoV, - validation_code: &ValidationCode, - candidate: &CommittedCandidateReceipt, + assert_pvd: &PersistedValidationData, + assert_pov: &PoV, + assert_validation_code: &ValidationCode, + assert_candidate: &CommittedCandidateReceipt, expected_head_data: &HeadData, result_validation_data: PersistedValidationData, ) { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + pov, + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == *pvd && - _validation_code == *validation_code && - *_pov == *pov && &candidate_receipt.descriptor == candidate.descriptor() && - timeout == PvfExecTimeoutKind::Backing && - candidate.commitments.hash() == candidate_receipt.commitments_hash => + exec_kind, + response_sender, + .. + }, + ) if validation_data == *assert_pvd && + validation_code == *assert_validation_code && + *pov == *assert_pov && &candidate_receipt.descriptor == assert_candidate.descriptor() && + exec_kind == PvfExecKind::Backing && + candidate_receipt.commitments_hash == assert_candidate.commitments.hash() => { - tx.send(Ok(ValidationResult::Valid( + response_sender.send(Ok(ValidationResult::Valid( CandidateCommitments { head_data: expected_head_data.clone(), horizontal_messages: Default::default(), @@ -455,17 +497,28 @@ fn backing_second_works() { } // Test that the candidate reaches quorum successfully. -#[test] -fn backing_works() { - let test_state = TestState::default(); +#[rstest] +#[case(true)] +#[case(false)] +fn backing_works(#[case] elastic_scaling_mvp: bool) { + let mut test_state = TestState::default(); + if elastic_scaling_mvp { + test_state + .node_features + .resize((node_features::FeatureIndex::ElasticScalingMVP as u8 + 1) as usize, false); + test_state + .node_features + .set(node_features::FeatureIndex::ElasticScalingMVP as u8 as usize, true); + } + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![1, 2, 3]) }; - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_ab = PoV { block_data: BlockData(vec![1, 2, 3]) }; + let pvd_ab = dummy_pvd(); + let validation_code_ab = ValidationCode(vec![1, 2, 3]); - let pov_hash = pov.hash(); + let pov_hash = pov_ab.hash(); let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); @@ -474,8 +527,8 @@ fn backing_works() { relay_parent: test_state.relay_parent, pov_hash, head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), - validation_code: validation_code.0.clone(), + erasure_root: make_erasure_root(&test_state, pov_ab.clone(), pvd_ab.clone()), + validation_code: validation_code_ab.0.clone(), ..Default::default() } .build(); @@ -498,7 +551,7 @@ fn backing_works() { let signed_a = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate_a.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate_a.clone(), pvd_ab.clone()), &test_state.signing_context, ValidatorIndex(2), &public2.into(), @@ -523,7 +576,7 @@ fn backing_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_ab.clone()).await; // Sending a `Statement::Seconded` for our assignment will start // validation process. The first thing requested is the PoV. @@ -536,7 +589,7 @@ fn backing_works() { .. } ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_ab.clone()).unwrap(); } ); @@ -545,22 +598,22 @@ fn backing_works() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate_a.descriptor() && - timeout == PvfExecTimeoutKind::Backing && - candidate_a_commitments_hash == candidate_receipt.commitments_hash => + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_ab && + validation_code == validation_code_ab && + *pov == pov_ab && &candidate_receipt.descriptor == candidate_a.descriptor() && + exec_kind == PvfExecKind::Backing && + candidate_receipt.commitments_hash == candidate_a_commitments_hash => { - tx.send(Ok( + response_sender.send(Ok( ValidationResult::Valid(CandidateCommitments { head_data: expected_head_data.clone(), upward_messages: Default::default(), @@ -608,6 +661,31 @@ fn backing_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; + let (tx, rx) = oneshot::channel(); + let msg = CandidateBackingMessage::GetBackedCandidates( + vec![(candidate_a_hash, test_state.relay_parent)], + tx, + ); + + virtual_overseer.send(FromOrchestra::Communication { msg }).await; + + let candidates = rx.await.unwrap(); + assert_eq!(1, candidates.len()); + assert_eq!(candidates[0].validity_votes().len(), 3); + + let (validator_indices, maybe_core_index) = + candidates[0].validator_indices_and_core_index(elastic_scaling_mvp); + if elastic_scaling_mvp { + assert_eq!(maybe_core_index.unwrap(), CoreIndex(0)); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!( + validator_indices, + bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 1, 0, 1].as_bitslice() + ); + virtual_overseer .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( ActiveLeavesUpdate::stop_work(test_state.relay_parent), @@ -617,17 +695,118 @@ fn backing_works() { }); } +#[test] +fn extract_core_index_from_statement_works() { + let test_state = TestState::default(); + + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); + + let pov_hash = pov_a.hash(); + + let mut candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + persisted_validation_data_hash: pvd_a.hash(), + validation_code: validation_code_a.0.clone(), + ..Default::default() + } + .build(); + + let public2 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_1 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let public1 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[1].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_2 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + candidate.descriptor.para_id = test_state.chain_ids[1]; + + let signed_statement_3 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate, pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let core_index_1 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_1, + ) + .unwrap(); + + assert_eq!(core_index_1, CoreIndex(0)); + + let core_index_2 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_2, + ); + + // Must be none, para_id in descriptor is different than para assigned to core + assert_eq!(core_index_2, None); + + let core_index_3 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_3, + ) + .unwrap(); + + assert_eq!(core_index_3, CoreIndex(1)); +} + #[test] fn backing_works_while_validation_ongoing() { let test_state = TestState::default(); test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![1, 2, 3]) }; - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_abc = PoV { block_data: BlockData(vec![1, 2, 3]) }; + let pvd_abc = dummy_pvd(); + let validation_code_abc = ValidationCode(vec![1, 2, 3]); - let pov_hash = pov.hash(); + let pov_hash = pov_abc.hash(); let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); @@ -636,8 +815,8 @@ fn backing_works_while_validation_ongoing() { relay_parent: test_state.relay_parent, pov_hash, head_data: expected_head_data.clone(), - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), - validation_code: validation_code.0.clone(), + erasure_root: make_erasure_root(&test_state, pov_abc.clone(), pvd_abc.clone()), + validation_code: validation_code_abc.0.clone(), ..Default::default() } .build(); @@ -666,7 +845,7 @@ fn backing_works_while_validation_ongoing() { let signed_a = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate_a.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate_a.clone(), pvd_abc.clone()), &test_state.signing_context, ValidatorIndex(2), &public2.into(), @@ -701,7 +880,7 @@ fn backing_works_while_validation_ongoing() { CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_abc.clone()).await; // Sending a `Statement::Seconded` for our assignment will start // validation process. The first thing requested is PoV from the @@ -715,7 +894,7 @@ fn backing_works_while_validation_ongoing() { .. } ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_abc.clone()).unwrap(); } ); @@ -724,24 +903,24 @@ fn backing_works_while_validation_ongoing() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate_a.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_abc && + validation_code == validation_code_abc && + *pov == pov_abc && &candidate_receipt.descriptor == candidate_a.descriptor() && + exec_kind == PvfExecKind::Backing && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { // we never validate the candidate. our local node // shouldn't issue any statements. - std::mem::forget(tx); + std::mem::forget(response_sender); } ); @@ -779,20 +958,20 @@ fn backing_works_while_validation_ongoing() { let candidates = rx.await.unwrap(); assert_eq!(1, candidates.len()); - assert_eq!(candidates[0].validity_votes.len(), 3); + assert_eq!(candidates[0].validity_votes().len(), 3); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Implicit(signed_a.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_b.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_c.signature().clone()))); assert_eq!( - candidates[0].validator_indices, - bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1], + candidates[0].validator_indices_and_core_index(false), + (bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1].as_bitslice(), None) ); virtual_overseer @@ -812,11 +991,11 @@ fn backing_misbehavior_works() { test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![1, 2, 3]) }; + let pov_a = PoV { block_data: BlockData(vec![1, 2, 3]) }; - let pov_hash = pov.hash(); - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_hash = pov_a.hash(); + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); @@ -824,9 +1003,9 @@ fn backing_misbehavior_works() { para_id: test_state.chain_ids[0], relay_parent: test_state.relay_parent, pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), head_data: expected_head_data.clone(), - validation_code: validation_code.0.clone(), + validation_code: validation_code_a.0.clone(), ..Default::default() } .build(); @@ -842,7 +1021,7 @@ fn backing_misbehavior_works() { .expect("Insert key into keystore"); let seconded_2 = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate_a.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate_a.clone(), pvd_a.clone()), &test_state.signing_context, ValidatorIndex(2), &public2.into(), @@ -867,7 +1046,7 @@ fn backing_misbehavior_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_a.clone()).await; assert_matches!( virtual_overseer.recv().await, @@ -878,29 +1057,29 @@ fn backing_misbehavior_works() { .. } ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_a.clone()).unwrap(); } ); assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate_a.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_a && + validation_code == validation_code_a && + *pov == pov_a && &candidate_receipt.descriptor == candidate_a.descriptor() && + exec_kind == PvfExecKind::Backing && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { - tx.send(Ok( + response_sender.send(Ok( ValidationResult::Valid(CandidateCommitments { head_data: expected_head_data.clone(), upward_messages: Default::default(), @@ -1052,22 +1231,22 @@ fn backing_dont_second_invalid() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd_a && - _validation_code == validation_code_a && - *_pov == pov_block_a && &candidate_receipt.descriptor == candidate_a.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_a && + validation_code == validation_code_a && + *pov == pov_block_a && &candidate_receipt.descriptor == candidate_a.descriptor() && + exec_kind == PvfExecKind::Backing && candidate_a.commitments.hash() == candidate_receipt.commitments_hash => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); + response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); } ); @@ -1092,22 +1271,22 @@ fn backing_dont_second_invalid() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if pvd == pvd_b && - _validation_code == validation_code_b && - *_pov == pov_block_b && &candidate_receipt.descriptor == candidate_b.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_b && + validation_code == validation_code_b && + *pov == pov_block_b && &candidate_receipt.descriptor == candidate_b.descriptor() && + exec_kind == PvfExecKind::Backing && candidate_b.commitments.hash() == candidate_receipt.commitments_hash => { - tx.send(Ok( + response_sender.send(Ok( ValidationResult::Valid(CandidateCommitments { head_data: expected_head_data.clone(), upward_messages: Default::default(), @@ -1158,19 +1337,19 @@ fn backing_second_after_first_fails_works() { test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); - let pov_hash = pov.hash(); + let pov_hash = pov_a.hash(); let candidate = TestCandidateBuilder { para_id: test_state.chain_ids[0], relay_parent: test_state.relay_parent, pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), - persisted_validation_data_hash: pvd.hash(), - validation_code: validation_code.0.clone(), + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + persisted_validation_data_hash: pvd_a.hash(), + validation_code: validation_code_a.0.clone(), ..Default::default() } .build(); @@ -1184,7 +1363,7 @@ fn backing_second_after_first_fails_works() { let signed_a = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), &test_state.signing_context, ValidatorIndex(2), &validator2.into(), @@ -1199,7 +1378,7 @@ fn backing_second_after_first_fails_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_a.clone()).await; // Subsystem requests PoV and requests validation. assert_matches!( @@ -1211,7 +1390,7 @@ fn backing_second_after_first_fails_works() { .. } ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_a.clone()).unwrap(); } ); @@ -1219,22 +1398,22 @@ fn backing_second_after_first_fails_works() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_a && + validation_code == validation_code_a && + *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && + exec_kind == PvfExecKind::Backing && candidate.commitments.hash() == candidate_receipt.commitments_hash => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); + response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); } ); @@ -1243,8 +1422,8 @@ fn backing_second_after_first_fails_works() { let second = CandidateBackingMessage::Second( test_state.relay_parent, candidate.to_plain(), - pvd.clone(), - pov.clone(), + pvd_a.clone(), + pov_a.clone(), ); virtual_overseer.send(FromOrchestra::Communication { msg: second }).await; @@ -1287,7 +1466,7 @@ fn backing_second_after_first_fails_works() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(_, _, _, pov, ..), + CandidateValidationMessage::ValidateFromExhaustive { pov, .. }, ) => { assert_eq!(&*pov, &pov_to_second); } @@ -1304,18 +1483,18 @@ fn backing_works_after_failed_validation() { test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); - let pov_hash = pov.hash(); + let pov_hash = pov_a.hash(); let candidate = TestCandidateBuilder { para_id: test_state.chain_ids[0], relay_parent: test_state.relay_parent, pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), - validation_code: validation_code.0.clone(), + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + validation_code: validation_code_a.0.clone(), ..Default::default() } .build(); @@ -1328,7 +1507,7 @@ fn backing_works_after_failed_validation() { .expect("Insert key into keystore"); let signed_a = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), &test_state.signing_context, ValidatorIndex(2), &public2.into(), @@ -1343,7 +1522,7 @@ fn backing_works_after_failed_validation() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_a.clone()).await; // Subsystem requests PoV and requests validation. assert_matches!( @@ -1355,7 +1534,7 @@ fn backing_works_after_failed_validation() { .. } ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_a.clone()).unwrap(); } ); @@ -1363,22 +1542,22 @@ fn backing_works_after_failed_validation() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + pov, + exec_kind, + response_sender, + .. + }, + ) if validation_data == pvd_a && + validation_code == validation_code_a && + *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && + exec_kind == PvfExecKind::Backing && candidate.commitments.hash() == candidate_receipt.commitments_hash => { - tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap(); + response_sender.send(Err(ValidationFailed("Internal test error".into()))).unwrap(); } ); @@ -1400,7 +1579,7 @@ fn backing_works_after_failed_validation() { fn candidate_backing_reorders_votes() { use sp_core::Encode; - let para_id = ParaId::from(10); + let core_idx = CoreIndex(10); let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, @@ -1414,12 +1593,13 @@ fn candidate_backing_reorders_votes() { let validator_groups = { let mut validator_groups = HashMap::new(); validator_groups - .insert(para_id, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); + .insert(core_idx, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); validator_groups }; let table_context = TableContext { validator: None, + disabled_validators: Vec::new(), groups: validator_groups, validators: validator_public.clone(), }; @@ -1443,10 +1623,10 @@ fn candidate_backing_reorders_votes() { (ValidatorIndex(3), fake_attestation(3)), (ValidatorIndex(1), fake_attestation(1)), ], - group_id: para_id, + group_id: core_idx, }; - let backed = table_attested_to_backed(attested, &table_context).unwrap(); + let backed = table_attested_to_backed(attested, &table_context, false).unwrap(); let expected_bitvec = { let mut validator_indices = BitVec::::with_capacity(6); @@ -1463,8 +1643,11 @@ fn candidate_backing_reorders_votes() { let expected_attestations = vec![fake_attestation(1).into(), fake_attestation(3).into(), fake_attestation(5).into()]; - assert_eq!(backed.validator_indices, expected_bitvec); - assert_eq!(backed.validity_votes, expected_attestations); + assert_eq!( + backed.validator_indices_and_core_index(false), + (expected_bitvec.as_bitslice(), None) + ); + assert_eq!(backed.validity_votes(), expected_attestations); } // Test whether we retry on failed PoV fetching. @@ -1475,19 +1658,19 @@ fn retry_works() { test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; - let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; - let pvd = dummy_pvd(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); - let pov_hash = pov.hash(); + let pov_hash = pov_a.hash(); let candidate = TestCandidateBuilder { para_id: test_state.chain_ids[0], relay_parent: test_state.relay_parent, pov_hash, - erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), - persisted_validation_data_hash: pvd.hash(), - validation_code: validation_code.0.clone(), + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + persisted_validation_data_hash: pvd_a.hash(), + validation_code: validation_code_a.0.clone(), ..Default::default() } .build(); @@ -1512,7 +1695,7 @@ fn retry_works() { .expect("Insert key into keystore"); let signed_a = SignedFullStatementWithPVD::sign( &test_state.keystore, - StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), &test_state.signing_context, ValidatorIndex(2), &public2.into(), @@ -1546,7 +1729,7 @@ fn retry_works() { CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_a.clone()).await; // Subsystem requests PoV and requests validation. // We cancel - should mean retry on next backing statement. @@ -1584,8 +1767,8 @@ fn retry_works() { AllMessages::RuntimeApi(RuntimeApiMessage::Request( _, RuntimeApiRequest::ValidationCodeByHash(hash, tx), - )) if hash == validation_code.hash() => { - tx.send(Ok(Some(validation_code.clone()))).unwrap(); + )) if hash == validation_code_a.hash() => { + tx.send(Ok(Some(validation_code_a.clone()))).unwrap(); }, AllMessages::RuntimeApi(RuntimeApiMessage::Request( _, @@ -1609,7 +1792,7 @@ fn retry_works() { CandidateBackingMessage::Statement(test_state.relay_parent, signed_c.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; - assert_validation_requests(&mut virtual_overseer, validation_code.clone()).await; + assert_validation_requests(&mut virtual_overseer, validation_code_a.clone()).await; assert_matches!( virtual_overseer.recv().await, @@ -1622,26 +1805,25 @@ fn retry_works() { // Subsystem requests PoV and requests validation. // Now we pass. ) if relay_parent == test_state.relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(pov_a.clone()).unwrap(); } ); assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, + pov, + exec_kind, .. - ), - ) if _pvd == pvd && - _validation_code == validation_code && - *_pov == pov && &candidate_receipt.descriptor == candidate.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + }, + ) if validation_data == pvd_a && + validation_code == validation_code_a && + *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && + exec_kind == PvfExecKind::Backing && candidate.commitments.hash() == candidate_receipt.commitments_hash ); virtual_overseer @@ -1863,9 +2045,9 @@ fn cannot_second_multiple_candidates_per_parent() { assert_matches!( virtual_overseer.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(.., tx), + CandidateValidationMessage::ValidateFromExhaustive { response_sender, .. }, ) => { - tx.send(Ok(ValidationResult::Valid( + response_sender.send(Ok(ValidationResult::Valid( CandidateCommitments { head_data: expected_head_data.clone(), horizontal_messages: Default::default(), @@ -1958,3 +2140,307 @@ fn new_leaf_view_doesnt_clobber_old() { virtual_overseer }); } + +// Test that a disabled local validator doesn't do any work on `CandidateBackingMessage::Second` +#[test] +fn disabled_validator_doesnt_distribute_statement_on_receiving_second() { + let mut test_state = TestState::default(); + test_state.disabled_validators.push(ValidatorIndex(0)); + + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd = dummy_pvd(); + let validation_code = ValidationCode(vec![1, 2, 3]); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let pov_hash = pov.hash(); + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), + persisted_validation_data_hash: pvd.hash(), + validation_code: validation_code.0.clone(), + } + .build(); + + let second = CandidateBackingMessage::Second( + test_state.relay_parent, + candidate.to_plain(), + pvd.clone(), + pov.clone(), + ); + + virtual_overseer.send(FromOrchestra::Communication { msg: second }).await; + + // Ensure backing subsystem is not doing any work + assert_matches!(virtual_overseer.recv().timeout(Duration::from_secs(1)).await, None); + + virtual_overseer + .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::stop_work(test_state.relay_parent), + ))) + .await; + virtual_overseer + }); +} + +// Test that a disabled local validator doesn't do any work on `CandidateBackingMessage::Statement` +#[test] +fn disabled_validator_doesnt_distribute_statement_on_receiving_statement() { + let mut test_state = TestState::default(); + test_state.disabled_validators.push(ValidatorIndex(0)); + + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd = dummy_pvd(); + let validation_code = ValidationCode(vec![1, 2, 3]); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let pov_hash = pov.hash(); + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), + persisted_validation_data_hash: pvd.hash(), + validation_code: validation_code.0.clone(), + } + .build(); + + let public2 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed.clone()); + + virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; + + // Ensure backing subsystem is not doing any work + assert_matches!(virtual_overseer.recv().timeout(Duration::from_secs(1)).await, None); + + virtual_overseer + .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::stop_work(test_state.relay_parent), + ))) + .await; + virtual_overseer + }); +} + +// Test that a validator doesn't do any work on receiving a `CandidateBackingMessage::Statement` +// from a disabled validator +#[test] +fn validator_ignores_statements_from_disabled_validators() { + let mut test_state = TestState::default(); + test_state.disabled_validators.push(ValidatorIndex(2)); + + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { + test_startup(&mut virtual_overseer, &test_state).await; + + let pov = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd = dummy_pvd(); + let validation_code = ValidationCode(vec![1, 2, 3]); + + let expected_head_data = test_state.head_data.get(&test_state.chain_ids[0]).unwrap(); + + let pov_hash = pov.hash(); + let candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + head_data: expected_head_data.clone(), + erasure_root: make_erasure_root(&test_state, pov.clone(), pvd.clone()), + persisted_validation_data_hash: pvd.hash(), + validation_code: validation_code.0.clone(), + } + .build(); + let candidate_commitments_hash = candidate.commitments.hash(); + + let public2 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_2 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let statement_2 = + CandidateBackingMessage::Statement(test_state.relay_parent, signed_2.clone()); + + virtual_overseer.send(FromOrchestra::Communication { msg: statement_2 }).await; + + // Ensure backing subsystem is not doing any work + assert_matches!(virtual_overseer.recv().timeout(Duration::from_secs(1)).await, None); + + // Now send a statement from a honest validator and make sure it gets processed + let public3 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[3].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_3 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd.clone()), + &test_state.signing_context, + ValidatorIndex(3), + &public3.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let statement_3 = + CandidateBackingMessage::Statement(test_state.relay_parent, signed_3.clone()); + + virtual_overseer.send(FromOrchestra::Communication { msg: statement_3 }).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::ValidationCodeByHash(hash, tx)) + ) if hash == validation_code.hash() => { + tx.send(Ok(Some(validation_code.clone()))).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionIndexForChild(tx)) + ) => { + tx.send(Ok(1u32.into())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::SessionExecutorParams(sess_idx, tx)) + ) if sess_idx == 1 => { + tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + // Sending a `Statement::Seconded` for our assignment will start + // validation process. The first thing requested is the PoV. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityDistribution( + AvailabilityDistributionMessage::FetchPoV { + relay_parent, + tx, + .. + } + ) if relay_parent == test_state.relay_parent => { + tx.send(pov.clone()).unwrap(); + } + ); + + // The next step is the actual request to Validation subsystem + // to validate the `Seconded` candidate. + let expected_pov = pov; + let expected_validation_code = validation_code; + assert_matches!( + virtual_overseer.recv().await, + AllMessages::CandidateValidation( + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, + candidate_receipt, + pov, + executor_params: _, + exec_kind, + response_sender, + } + ) if validation_data == pvd && + validation_code == expected_validation_code && + *pov == expected_pov && &candidate_receipt.descriptor == candidate.descriptor() && + exec_kind == PvfExecKind::Backing && + candidate_commitments_hash == candidate_receipt.commitments_hash => + { + response_sender.send(Ok( + ValidationResult::Valid(CandidateCommitments { + head_data: expected_head_data.clone(), + upward_messages: Default::default(), + horizontal_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, test_state.validation_data.clone()), + )).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData { candidate_hash, tx, .. } + ) if candidate_hash == candidate.hash() => { + tx.send(Ok(())).unwrap(); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::StatementDistribution( + StatementDistributionMessage::Share(hash, _stmt) + ) => { + assert_eq!(test_state.relay_parent, hash); + } + ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::Provisioner( + ProvisionerMessage::ProvisionableData( + _, + ProvisionableData::BackedCandidate(candidate_receipt) + ) + ) => { + assert_eq!(candidate_receipt, candidate.to_plain()); + } + ); + + virtual_overseer + .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( + ActiveLeavesUpdate::stop_work(test_state.relay_parent), + ))) + .await; + virtual_overseer + }); +} diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index b79515ed37a6ef590680fb834402f6ed3bef73be..94310d2aa164650db84b78ddf361a9f465ac207d 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -185,6 +185,16 @@ async fn activate_leaf( } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) if parent == hash => { + tx.send(Ok(Default::default())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -195,6 +205,26 @@ async fn activate_leaf( tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); } ); + + // Check that subsystem job issues a request for the runtime version. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::Version(tx)) + ) if parent == hash => { + tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)).unwrap(); + } + ); + + // Check that the subsystem job issues a request for the disabled validators. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::DisabledValidators(tx)) + ) if parent == hash => { + tx.send(Ok(Vec::new())).unwrap(); + } + ); } } @@ -202,13 +232,13 @@ async fn assert_validate_seconded_candidate( virtual_overseer: &mut VirtualOverseer, relay_parent: Hash, candidate: &CommittedCandidateReceipt, - pov: &PoV, - pvd: &PersistedValidationData, - validation_code: &ValidationCode, + assert_pov: &PoV, + assert_pvd: &PersistedValidationData, + assert_validation_code: &ValidationCode, expected_head_data: &HeadData, fetch_pov: bool, ) { - assert_validation_requests(virtual_overseer, validation_code.clone()).await; + assert_validation_requests(virtual_overseer, assert_validation_code.clone()).await; if fetch_pov { assert_matches!( @@ -220,29 +250,29 @@ async fn assert_validate_seconded_candidate( .. } ) if hash == relay_parent => { - tx.send(pov.clone()).unwrap(); + tx.send(assert_pov.clone()).unwrap(); } ); } assert_matches!( virtual_overseer.recv().await, - AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive( - _pvd, - _validation_code, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, candidate_receipt, - _pov, - _, - timeout, - tx, - )) if &_pvd == pvd && - &_validation_code == validation_code && - &*_pov == pov && + pov, + exec_kind, + response_sender, + .. + }) if &validation_data == assert_pvd && + &validation_code == assert_validation_code && + &*pov == assert_pov && &candidate_receipt.descriptor == candidate.descriptor() && - timeout == PvfExecTimeoutKind::Backing && + exec_kind == PvfExecKind::Backing && candidate.commitments.hash() == candidate_receipt.commitments_hash => { - tx.send(Ok(ValidationResult::Valid( + response_sender.send(Ok(ValidationResult::Valid( CandidateCommitments { head_data: expected_head_data.clone(), horizontal_messages: Default::default(), @@ -251,7 +281,7 @@ async fn assert_validate_seconded_candidate( processed_downward_messages: 0, hrmp_watermark: 0, }, - pvd.clone(), + assert_pvd.clone(), ))) .unwrap(); } @@ -285,10 +315,11 @@ async fn assert_hypothetical_frontier_requests( ) => { let idx = match expected_requests.iter().position(|r| r.0 == request) { Some(idx) => idx, - None => panic!( + None => + panic!( "unexpected hypothetical frontier request, no match found for {:?}", request - ), + ), }; let resp = std::mem::take(&mut expected_requests[idx].1); tx.send(resp).unwrap(); @@ -1248,6 +1279,7 @@ fn concurrent_dependent_candidates() { let statement_b = CandidateBackingMessage::Statement(leaf_parent, signed_b.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement_a }).await; + // At this point the subsystem waits for response, the previous message is received, // send a second one without blocking. let _ = virtual_overseer @@ -1293,9 +1325,13 @@ fn concurrent_dependent_candidates() { tx.send(pov.clone()).unwrap(); }, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(.., candidate, _, _, _, tx), + CandidateValidationMessage::ValidateFromExhaustive { + candidate_receipt, + response_sender, + .. + }, ) => { - let candidate_hash = candidate.hash(); + let candidate_hash = candidate_receipt.hash(); let (head_data, pvd) = if candidate_hash == candidate_a_hash { (&head_data[1], &pvd_a) } else if candidate_hash == candidate_b_hash { @@ -1303,18 +1339,19 @@ fn concurrent_dependent_candidates() { } else { panic!("unknown candidate hash") }; - tx.send(Ok(ValidationResult::Valid( - CandidateCommitments { - head_data: head_data.clone(), - horizontal_messages: Default::default(), - upward_messages: Default::default(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }, - pvd.clone(), - ))) - .unwrap(); + response_sender + .send(Ok(ValidationResult::Valid( + CandidateCommitments { + head_data: head_data.clone(), + horizontal_messages: Default::default(), + upward_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }, + pvd.clone(), + ))) + .unwrap(); }, AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreAvailableData { tx, @@ -1363,7 +1400,19 @@ fn concurrent_dependent_candidates() { assert_eq!(sess_idx, 1); tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::ValidatorGroups(tx), + )) => { + tx.send(Ok(test_state.validator_groups.clone())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::AvailabilityCores(tx), + )) => { + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + }, _ => panic!("unexpected message received from overseer: {:?}", msg), } } @@ -1394,7 +1443,6 @@ fn seconding_sanity_check_occupy_same_depth() { let leaf_parent = get_parent_hash(leaf_hash); let activated = new_leaf(leaf_hash, LEAF_BLOCK_NUMBER); - let min_block_number = LEAF_BLOCK_NUMBER - LEAF_ANCESTRY_LEN; let min_relay_parents = vec![(para_id_a, min_block_number), (para_id_b, min_block_number)]; let test_leaf_a = TestLeaf { activated, min_relay_parents }; @@ -1530,13 +1578,14 @@ fn occupied_core_assignment() { const LEAF_A_BLOCK_NUMBER: BlockNumber = 100; const LEAF_A_ANCESTRY_LEN: BlockNumber = 3; let para_id = test_state.chain_ids[0]; + let previous_para_id = test_state.chain_ids[1]; // Set the core state to occupied. let mut candidate_descriptor = ::test_helpers::dummy_candidate_descriptor(Hash::zero()); - candidate_descriptor.para_id = para_id; + candidate_descriptor.para_id = previous_para_id; test_state.availability_cores[0] = CoreState::Occupied(OccupiedCore { group_responsible: Default::default(), - next_up_on_available: None, + next_up_on_available: Some(ScheduledCore { para_id, collator: None }), occupied_since: 100_u32, time_out_at: 200_u32, next_up_on_time_out: None, diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index de38d18d970623dd6dc14fdcecb5dd4447232481..6ecfffd7249b9213cc4ffb86f84a38e1cdb9babf 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "polkadot-node-core-bitfield-signing" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +description = "Bitfield signing subsystem for the Polkadot node" + +[lints] +workspace = true [dependencies] futures = "0.3.21" @@ -13,7 +17,7 @@ polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } wasm-timer = "0.2.5" -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index a2e88778532f754cd429c5c156fe27cf5dd88d75..15fc8c940d338c9aab1eb4d23e98427a597a2df1 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "polkadot-node-core-candidate-validation" description = "Polkadot crate that implements the Candidate Validation subsystem. Handles requests to validate candidates according to a PVF." -version = "1.0.0" +version = "7.0.0" 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 21a7121d47bd005b428a4b969f535e0c9f1c7c04..8237137fdca015ace87248d062431230a2ad47d4 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -24,8 +24,8 @@ #![warn(missing_docs)] use polkadot_node_core_pvf::{ - InternalValidationError, InvalidCandidate as WasmInvalidCandidate, PrepareError, - PrepareJobKind, PrepareStats, PvfPrepData, ValidationError, ValidationHost, + InternalValidationError, InvalidCandidate as WasmInvalidCandidate, PossiblyInvalidError, + PrepareError, PrepareJobKind, PvfPrepData, ValidationError, ValidationHost, }; use polkadot_node_primitives::{ BlockData, InvalidCandidate, PoV, ValidationResult, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT, @@ -49,16 +49,17 @@ use polkadot_primitives::{ DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT, }, CandidateCommitments, CandidateDescriptor, CandidateReceipt, ExecutorParams, Hash, - OccupiedCoreAssumption, PersistedValidationData, PvfExecTimeoutKind, PvfPrepTimeoutKind, - ValidationCode, ValidationCodeHash, + OccupiedCoreAssumption, PersistedValidationData, PvfExecKind, PvfPrepKind, ValidationCode, + ValidationCodeHash, }; use parity_scale_codec::Encode; -use futures::{channel::oneshot, prelude::*}; +use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered}; use std::{ path::PathBuf, + pin::Pin, sync::Arc, time::{Duration, Instant}, }; @@ -73,12 +74,6 @@ mod tests; const LOG_TARGET: &'static str = "parachain::candidate-validation"; -/// The amount of time to wait before retrying after a retry-able backing validation error. We use a -/// lower value for the backing case, to fit within the lower backing timeout. -#[cfg(not(test))] -const PVF_BACKING_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(500); -#[cfg(test)] -const PVF_BACKING_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(200); /// The amount of time to wait before retrying after a retry-able approval validation error. We use /// a higher value for the approval case since we have more time, and if we wait longer it is more /// likely that transient conditions will resolve. @@ -87,6 +82,11 @@ const PVF_APPROVAL_EXECUTION_RETRY_DELAY: Duration = Duration::from_secs(3); #[cfg(test)] const PVF_APPROVAL_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(200); +// The task queue size is chosen to be somewhat bigger than the PVF host incoming queue size +// to allow exhaustive validation messages to fall through in case the tasks are clogged with +// `ValidateFromChainState` messages awaiting data from the runtime +const TASK_LIMIT: usize = 30; + /// Configuration for the candidate validation subsystem #[derive(Clone)] pub struct Config { @@ -94,6 +94,8 @@ pub struct Config { pub artifacts_cache_path: PathBuf, /// The version of the node. `None` can be passed to skip the version check (only for tests). pub node_version: Option, + /// Whether the node is attempting to run as a secure validator. + pub secure_validator_mode: bool, /// Path to the preparation worker binary pub prep_worker_path: PathBuf, /// Path to the execution worker binary @@ -134,121 +136,151 @@ impl CandidateValidationSubsystem { } } +fn handle_validation_message( + mut sender: S, + validation_host: ValidationHost, + metrics: Metrics, + msg: CandidateValidationMessage, +) -> Pin + Send>> +where + S: SubsystemSender, +{ + match msg { + CandidateValidationMessage::ValidateFromChainState { + candidate_receipt, + pov, + executor_params, + exec_kind, + response_sender, + .. + } => async move { + let _timer = metrics.time_validate_from_chain_state(); + let res = validate_from_chain_state( + &mut sender, + validation_host, + candidate_receipt, + pov, + executor_params, + exec_kind, + &metrics, + ) + .await; + + metrics.on_validation_event(&res); + let _ = response_sender.send(res); + } + .boxed(), + CandidateValidationMessage::ValidateFromExhaustive { + validation_data, + validation_code, + candidate_receipt, + pov, + executor_params, + exec_kind, + response_sender, + .. + } => async move { + let _timer = metrics.time_validate_from_exhaustive(); + let res = validate_candidate_exhaustive( + validation_host, + validation_data, + validation_code, + candidate_receipt, + pov, + executor_params, + exec_kind, + &metrics, + ) + .await; + + metrics.on_validation_event(&res); + let _ = response_sender.send(res); + } + .boxed(), + CandidateValidationMessage::PreCheck { + relay_parent, + validation_code_hash, + response_sender, + .. + } => async move { + let precheck_result = + precheck_pvf(&mut sender, validation_host, relay_parent, validation_code_hash) + .await; + + let _ = response_sender.send(precheck_result); + } + .boxed(), + } +} + #[overseer::contextbounds(CandidateValidation, prefix = self::overseer)] async fn run( mut ctx: Context, metrics: Metrics, pvf_metrics: polkadot_node_core_pvf::Metrics, - Config { artifacts_cache_path, node_version, prep_worker_path, exec_worker_path }: Config, + Config { + artifacts_cache_path, + node_version, + secure_validator_mode, + prep_worker_path, + exec_worker_path, + }: Config, ) -> SubsystemResult<()> { let (validation_host, task) = polkadot_node_core_pvf::start( polkadot_node_core_pvf::Config::new( artifacts_cache_path, node_version, + secure_validator_mode, prep_worker_path, exec_worker_path, ), pvf_metrics, - ); + ) + .await?; ctx.spawn_blocking("pvf-validation-host", task.boxed())?; + let mut tasks = FuturesUnordered::new(); + loop { - match ctx.recv().await? { - FromOrchestra::Signal(OverseerSignal::ActiveLeaves(_)) => {}, - FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {}, - FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), - FromOrchestra::Communication { msg } => match msg { - CandidateValidationMessage::ValidateFromChainState( - candidate_receipt, - pov, - executor_params, - timeout, - response_sender, - ) => { - let bg = { - let mut sender = ctx.sender().clone(); - let metrics = metrics.clone(); - let validation_host = validation_host.clone(); - - async move { - let _timer = metrics.time_validate_from_chain_state(); - let res = validate_from_chain_state( - &mut sender, - validation_host, - candidate_receipt, - pov, - executor_params, - timeout, - &metrics, - ) - .await; - - metrics.on_validation_event(&res); - let _ = response_sender.send(res); - } - }; - - ctx.spawn("validate-from-chain-state", bg.boxed())?; - }, - CandidateValidationMessage::ValidateFromExhaustive( - persisted_validation_data, - validation_code, - candidate_receipt, - pov, - executor_params, - timeout, - response_sender, - ) => { - let bg = { - let metrics = metrics.clone(); - let validation_host = validation_host.clone(); - - async move { - let _timer = metrics.time_validate_from_exhaustive(); - let res = validate_candidate_exhaustive( - validation_host, - persisted_validation_data, - validation_code, - candidate_receipt, - pov, - executor_params, - timeout, - &metrics, - ) - .await; - - metrics.on_validation_event(&res); - let _ = response_sender.send(res); - } - }; - - ctx.spawn("validate-from-exhaustive", bg.boxed())?; + loop { + futures::select! { + comm = ctx.recv().fuse() => { + match comm { + Ok(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(_))) => {}, + Ok(FromOrchestra::Signal(OverseerSignal::BlockFinalized(..))) => {}, + Ok(FromOrchestra::Signal(OverseerSignal::Conclude)) => return Ok(()), + Ok(FromOrchestra::Communication { msg }) => { + let task = handle_validation_message(ctx.sender().clone(), validation_host.clone(), metrics.clone(), msg); + tasks.push(task); + if tasks.len() >= TASK_LIMIT { + break + } + }, + Err(e) => return Err(SubsystemError::from(e)), + } }, - CandidateValidationMessage::PreCheck( - relay_parent, - validation_code_hash, - response_sender, - ) => { - let bg = { - let mut sender = ctx.sender().clone(); - let validation_host = validation_host.clone(); - - async move { - let precheck_result = precheck_pvf( - &mut sender, - validation_host, - relay_parent, - validation_code_hash, - ) - .await; - - let _ = response_sender.send(precheck_result); - } - }; - - ctx.spawn("candidate-validation-pre-check", bg.boxed())?; + _ = tasks.select_next_some() => () + } + } + + gum::debug!(target: LOG_TARGET, "Validation task limit hit"); + + loop { + futures::select! { + signal = ctx.recv_signal().fuse() => { + match signal { + Ok(OverseerSignal::ActiveLeaves(_)) => {}, + Ok(OverseerSignal::BlockFinalized(..)) => {}, + Ok(OverseerSignal::Conclude) => return Ok(()), + Err(e) => return Err(SubsystemError::from(e)), + } }, - }, + _ = tasks.select_next_some() => { + if tasks.len() < TASK_LIMIT { + break + } + } + } } } } @@ -353,7 +385,7 @@ where return PreCheckOutcome::Invalid }; - let timeout = pvf_prep_timeout(&executor_params, PvfPrepTimeoutKind::Precheck); + let timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Precheck); let pvf = match sp_maybe_compressed_blob::decompress( &validation_code.0, @@ -497,7 +529,7 @@ async fn validate_from_chain_state( candidate_receipt: CandidateReceipt, pov: Arc, executor_params: ExecutorParams, - exec_timeout_kind: PvfExecTimeoutKind, + exec_kind: PvfExecKind, metrics: &Metrics, ) -> Result where @@ -517,7 +549,7 @@ where candidate_receipt.clone(), pov, executor_params, - exec_timeout_kind, + exec_kind, metrics, ) .await; @@ -553,7 +585,7 @@ async fn validate_candidate_exhaustive( candidate_receipt: CandidateReceipt, pov: Arc, executor_params: ExecutorParams, - exec_timeout_kind: PvfExecTimeoutKind, + exec_kind: PvfExecKind, metrics: &Metrics, ) -> Result { let _timer = metrics.time_validate_candidate_exhaustive(); @@ -612,22 +644,39 @@ async fn validate_candidate_exhaustive( relay_parent_storage_root: persisted_validation_data.relay_parent_storage_root, }; - let result = validation_backend - .validate_candidate_with_retry( - raw_validation_code.to_vec(), - pvf_exec_timeout(&executor_params, exec_timeout_kind), - exec_timeout_kind, - params, - executor_params, - ) - .await; + let result = match exec_kind { + // Retry is disabled to reduce the chance of nondeterministic blocks getting backed and + // honest backers getting slashed. + PvfExecKind::Backing => { + let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Prepare); + let exec_timeout = pvf_exec_timeout(&executor_params, exec_kind); + let pvf = PvfPrepData::from_code( + raw_validation_code.to_vec(), + executor_params, + prep_timeout, + PrepareJobKind::Compilation, + ); + + validation_backend.validate_candidate(pvf, exec_timeout, params.encode()).await + }, + PvfExecKind::Approval => + validation_backend + .validate_candidate_with_retry( + raw_validation_code.to_vec(), + pvf_exec_timeout(&executor_params, exec_kind), + params, + executor_params, + PVF_APPROVAL_EXECUTION_RETRY_DELAY, + ) + .await, + }; if let Err(ref error) = result { gum::info!(target: LOG_TARGET, ?para_id, ?error, "Failed to validate candidate"); } match result { - Err(ValidationError::InternalError(e)) => { + Err(ValidationError::Internal(e)) => { gum::warn!( target: LOG_TARGET, ?para_id, @@ -636,29 +685,31 @@ async fn validate_candidate_exhaustive( ); Err(ValidationFailed(e.to_string())) }, - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)) => + Err(ValidationError::Invalid(WasmInvalidCandidate::HardTimeout)) => Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)), - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::WorkerReportedError(e))) => + Err(ValidationError::Invalid(WasmInvalidCandidate::WorkerReportedInvalid(e))) => Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e))), - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)) => + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) => Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError( "ambiguous worker death".to_string(), ))), - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Panic(err))) => + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(err))) => Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(err))), - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::PrepareError(e))) => { - // In principle if preparation of the `WASM` fails, the current candidate can not be the - // reason for that. So we can't say whether it is invalid or not. In addition, with - // pre-checking enabled only valid runtimes should ever get enacted, so we can be - // reasonably sure that this is some local problem on the current node. However, as this - // particular error *seems* to indicate a deterministic error, we raise a warning. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::RuntimeConstruction(err))) => + Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(err))), + + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousJobDeath(err))) => + Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(format!( + "ambiguous job death: {err}" + )))), + Err(ValidationError::Preparation(e)) => { gum::warn!( target: LOG_TARGET, ?para_id, ?e, "Deterministic error occurred during preparation (should have been ruled out by pre-checking phase)", ); - Err(ValidationFailed(e)) + Err(ValidationFailed(e.to_string())) }, Ok(res) => if res.head_data.hash() != candidate_receipt.descriptor.para_head { @@ -700,8 +751,8 @@ trait ValidationBackend { encoded_params: Vec, ) -> Result; - /// Tries executing a PVF. Will retry once if an error is encountered that may have been - /// transient. + /// Tries executing a PVF for the approval subsystem. Will retry once if an error is encountered + /// that may have been transient. /// /// NOTE: Should retry only on errors that are a result of execution itself, and not of /// preparation. @@ -709,11 +760,11 @@ trait ValidationBackend { &mut self, raw_validation_code: Vec, exec_timeout: Duration, - exec_timeout_kind: PvfExecTimeoutKind, params: ValidationParams, executor_params: ExecutorParams, + retry_delay: Duration, ) -> Result { - let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepTimeoutKind::Lenient); + let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Prepare); // Construct the PVF a single time, since it is an expensive operation. Cloning it is cheap. let pvf = PvfPrepData::from_code( raw_validation_code, @@ -731,38 +782,62 @@ trait ValidationBackend { return validation_result } - let retry_delay = match exec_timeout_kind { - PvfExecTimeoutKind::Backing => PVF_BACKING_EXECUTION_RETRY_DELAY, - PvfExecTimeoutKind::Approval => PVF_APPROVAL_EXECUTION_RETRY_DELAY, - }; + macro_rules! break_if_no_retries_left { + ($counter:ident) => { + if $counter > 0 { + $counter -= 1; + } else { + break + } + }; + } // Allow limited retries for each kind of error. + let mut num_death_retries_left = 1; + let mut num_job_error_retries_left = 1; let mut num_internal_retries_left = 1; - let mut num_awd_retries_left = 1; - let mut num_panic_retries_left = 1; + let mut num_runtime_construction_retries_left = 1; loop { // Stop retrying if we exceeded the timeout. if total_time_start.elapsed() + retry_delay > exec_timeout { break } - + let mut retry_immediately = false; match validation_result { - Err(ValidationError::InvalidCandidate( - WasmInvalidCandidate::AmbiguousWorkerDeath, - )) if num_awd_retries_left > 0 => num_awd_retries_left -= 1, - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Panic(_))) - if num_panic_retries_left > 0 => - num_panic_retries_left -= 1, - Err(ValidationError::InternalError(_)) if num_internal_retries_left > 0 => - num_internal_retries_left -= 1, - _ => break, + Err(ValidationError::PossiblyInvalid( + PossiblyInvalidError::AmbiguousWorkerDeath | + PossiblyInvalidError::AmbiguousJobDeath(_), + )) => break_if_no_retries_left!(num_death_retries_left), + + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(_))) => + break_if_no_retries_left!(num_job_error_retries_left), + + Err(ValidationError::Internal(_)) => + break_if_no_retries_left!(num_internal_retries_left), + + Err(ValidationError::PossiblyInvalid( + PossiblyInvalidError::RuntimeConstruction(_), + )) => { + break_if_no_retries_left!(num_runtime_construction_retries_left); + self.precheck_pvf(pvf.clone()).await?; + // In this case the error is deterministic + // And a retry forces the ValidationBackend + // to re-prepare the artifact so + // there is no need to wait before the retry + retry_immediately = true; + }, + + Ok(_) | Err(ValidationError::Invalid(_) | ValidationError::Preparation(_)) => break, } // If we got a possibly transient error, retry once after a brief delay, on the // assumption that the conditions that caused this error may have resolved on their own. { - // Wait a brief delay before retrying. - futures_timer::Delay::new(retry_delay).await; + // In case of many transient errors it is necessary to wait a little bit + // for the error to be probably resolved + if !retry_immediately { + futures_timer::Delay::new(retry_delay).await; + } let new_timeout = exec_timeout.saturating_sub(total_time_start.elapsed()); @@ -784,7 +859,7 @@ trait ValidationBackend { validation_result } - async fn precheck_pvf(&mut self, pvf: PvfPrepData) -> Result; + async fn precheck_pvf(&mut self, pvf: PvfPrepData) -> Result<(), PrepareError>; } #[async_trait] @@ -814,7 +889,7 @@ impl ValidationBackend for ValidationHost { })? } - async fn precheck_pvf(&mut self, pvf: PvfPrepData) -> Result { + async fn precheck_pvf(&mut self, pvf: PvfPrepData) -> Result<(), PrepareError> { let (tx, rx) = oneshot::channel(); if let Err(err) = self.precheck_pvf(pvf, tx).await { // Return an IO error if there was an error communicating with the host. @@ -857,22 +932,41 @@ fn perform_basic_checks( Ok(()) } -fn pvf_prep_timeout(executor_params: &ExecutorParams, kind: PvfPrepTimeoutKind) -> Duration { +/// To determine the amount of timeout time for the pvf execution. +/// +/// Precheck +/// The time period after which the preparation worker is considered +/// unresponsive and will be killed. +/// +/// Prepare +///The time period after which the preparation worker is considered +/// unresponsive and will be killed. +fn pvf_prep_timeout(executor_params: &ExecutorParams, kind: PvfPrepKind) -> Duration { if let Some(timeout) = executor_params.pvf_prep_timeout(kind) { return timeout } match kind { - PvfPrepTimeoutKind::Precheck => DEFAULT_PRECHECK_PREPARATION_TIMEOUT, - PvfPrepTimeoutKind::Lenient => DEFAULT_LENIENT_PREPARATION_TIMEOUT, + PvfPrepKind::Precheck => DEFAULT_PRECHECK_PREPARATION_TIMEOUT, + PvfPrepKind::Prepare => DEFAULT_LENIENT_PREPARATION_TIMEOUT, } } -fn pvf_exec_timeout(executor_params: &ExecutorParams, kind: PvfExecTimeoutKind) -> Duration { +/// To determine the amount of timeout time for the pvf execution. +/// +/// Backing subsystem +/// The amount of time to spend on execution during backing. +/// +/// Approval subsystem +/// The amount of time to spend on execution during approval or disputes. +/// This should be much longer than the backing execution timeout to ensure that in the +/// absence of extremely large disparities between hardware, blocks that pass backing are +/// considered executable by approval checkers or dispute participants. +fn pvf_exec_timeout(executor_params: &ExecutorParams, kind: PvfExecKind) -> Duration { if let Some(timeout) = executor_params.pvf_exec_timeout(kind) { return timeout } match kind { - PvfExecTimeoutKind::Backing => DEFAULT_BACKING_EXECUTION_TIMEOUT, - PvfExecTimeoutKind::Approval => DEFAULT_APPROVAL_EXECUTION_TIMEOUT, + PvfExecKind::Backing => DEFAULT_BACKING_EXECUTION_TIMEOUT, + PvfExecKind::Approval => DEFAULT_APPROVAL_EXECUTION_TIMEOUT, } } diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index af530a20c4e0ca61fcf44f4ce0e3121ffeab1363..f646f8535495b74128f829359214018c4fb01ac2 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -377,7 +377,7 @@ impl ValidationBackend for MockValidateCandidateBackend { result } - async fn precheck_pvf(&mut self, _pvf: PvfPrepData) -> Result { + async fn precheck_pvf(&mut self, _pvf: PvfPrepData) -> Result<(), PrepareError> { unreachable!() } } @@ -436,7 +436,7 @@ fn candidate_validation_ok_is_ok() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )) .unwrap(); @@ -480,15 +480,15 @@ fn candidate_validation_bad_return_is_invalid() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( - MockValidateCandidateBackend::with_hardcoded_result(Err( - ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout), - )), + MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( + WasmInvalidCandidate::HardTimeout, + ))), validation_data, validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )) .unwrap(); @@ -496,23 +496,20 @@ fn candidate_validation_bad_return_is_invalid() { assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::Timeout)); } -// Test that we vote valid if we get `AmbiguousWorkerDeath`, retry, and then succeed. -#[test] -fn candidate_validation_one_ambiguous_error_is_valid() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let head_data = HeadData(vec![1, 1, 1]); - let validation_code = ValidationCode(vec![2; 16]); - +fn perform_basic_checks_on_valid_candidate( + pov: &PoV, + validation_code: &ValidationCode, + validation_data: &PersistedValidationData, + head_data_hash: Hash, +) -> CandidateDescriptor { let descriptor = make_valid_candidate_descriptor( ParaId::from(1_u32), dummy_hash(), validation_data.hash(), pov.hash(), validation_code.hash(), - head_data.hash(), - dummy_hash(), + head_data_hash, + head_data_hash, Sr25519Keyring::Alice, ); @@ -523,6 +520,24 @@ fn candidate_validation_one_ambiguous_error_is_valid() { &validation_code.hash(), ); assert!(check.is_ok()); + descriptor +} + +// Test that we vote valid if we get `AmbiguousWorkerDeath`, retry, and then succeed. +#[test] +fn candidate_validation_one_ambiguous_error_is_valid() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let head_data = HeadData(vec![1, 1, 1]); + let validation_code = ValidationCode(vec![2; 16]); + + let descriptor = perform_basic_checks_on_valid_candidate( + &pov, + &validation_code, + &validation_data, + head_data.hash(), + ); let validation_result = WasmValidationResult { head_data, @@ -546,7 +561,7 @@ fn candidate_validation_one_ambiguous_error_is_valid() { let v = executor::block_on(validate_candidate_exhaustive( MockValidateCandidateBackend::with_hardcoded_result_list(vec![ - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), Ok(validation_result), ]), validation_data.clone(), @@ -554,7 +569,7 @@ fn candidate_validation_one_ambiguous_error_is_valid() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Approval, &Default::default(), )) .unwrap(); @@ -576,38 +591,26 @@ fn candidate_validation_multiple_ambiguous_errors_is_invalid() { let pov = PoV { block_data: BlockData(vec![1; 32]) }; let validation_code = ValidationCode(vec![2; 16]); - let descriptor = make_valid_candidate_descriptor( - ParaId::from(1_u32), - dummy_hash(), - validation_data.hash(), - pov.hash(), - validation_code.hash(), - dummy_hash(), - dummy_hash(), - Sr25519Keyring::Alice, - ); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, + let descriptor = perform_basic_checks_on_valid_candidate( &pov, - &validation_code.hash(), + &validation_code, + &validation_data, + dummy_hash(), ); - assert!(check.is_ok()); let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( MockValidateCandidateBackend::with_hardcoded_result_list(vec![ - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)), - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), ]), validation_data, validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Approval, &Default::default(), )) .unwrap(); @@ -615,58 +618,79 @@ fn candidate_validation_multiple_ambiguous_errors_is_invalid() { assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::ExecutionError(_))); } -// Test that we retry on internal errors. +// Test that we retry for approval on internal errors. #[test] fn candidate_validation_retry_internal_errors() { - let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; - - let pov = PoV { block_data: BlockData(vec![1; 32]) }; - let validation_code = ValidationCode(vec![2; 16]); - - let descriptor = make_valid_candidate_descriptor( - ParaId::from(1_u32), - dummy_hash(), - validation_data.hash(), - pov.hash(), - validation_code.hash(), - dummy_hash(), - dummy_hash(), - Sr25519Keyring::Alice, - ); - - let check = perform_basic_checks( - &descriptor, - validation_data.max_pov_size, - &pov, - &validation_code.hash(), + let v = candidate_validation_retry_on_error_helper( + PvfExecKind::Approval, + vec![ + Err(InternalValidationError::HostCommunication("foo".into()).into()), + // Throw an AJD error, we should still retry again. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousJobDeath( + "baz".into(), + ))), + // Throw another internal error. + Err(InternalValidationError::HostCommunication("bar".into()).into()), + ], ); - assert!(check.is_ok()); - - let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; + assert_matches!(v, Err(ValidationFailed(s)) if s.contains("bar")); +} - let v = executor::block_on(validate_candidate_exhaustive( - MockValidateCandidateBackend::with_hardcoded_result_list(vec![ +// Test that we don't retry for backing on internal errors. +#[test] +fn candidate_validation_dont_retry_internal_errors() { + let v = candidate_validation_retry_on_error_helper( + PvfExecKind::Backing, + vec![ Err(InternalValidationError::HostCommunication("foo".into()).into()), // Throw an AWD error, we should still retry again. - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), // Throw another internal error. Err(InternalValidationError::HostCommunication("bar".into()).into()), - ]), - validation_data, - validation_code, - candidate_receipt, - Arc::new(pov), - ExecutorParams::default(), - PvfExecTimeoutKind::Backing, - &Default::default(), - )); + ], + ); - assert_matches!(v, Err(ValidationFailed(s)) if s.contains("bar")); + assert_matches!(v, Err(ValidationFailed(s)) if s.contains("foo")); } -// Test that we retry on panic errors. +// Test that we retry for approval on panic errors. #[test] fn candidate_validation_retry_panic_errors() { + let v = candidate_validation_retry_on_error_helper( + PvfExecKind::Approval, + vec![ + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError("foo".into()))), + // Throw an AWD error, we should still retry again. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), + // Throw another panic error. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError("bar".into()))), + ], + ); + + assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(s))) if s == "bar".to_string()); +} + +// Test that we don't retry for backing on panic errors. +#[test] +fn candidate_validation_dont_retry_panic_errors() { + let v = candidate_validation_retry_on_error_helper( + PvfExecKind::Backing, + vec![ + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError("foo".into()))), + // Throw an AWD error, we should still retry again. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), + // Throw another panic error. + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError("bar".into()))), + ], + ); + + assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(s))) if s == "foo".to_string()); +} + +fn candidate_validation_retry_on_error_helper( + exec_kind: PvfExecKind, + mock_errors: Vec>, +) -> Result { let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; let pov = PoV { block_data: BlockData(vec![1; 32]) }; @@ -693,24 +717,16 @@ fn candidate_validation_retry_panic_errors() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; - let v = executor::block_on(validate_candidate_exhaustive( - MockValidateCandidateBackend::with_hardcoded_result_list(vec![ - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Panic("foo".into()))), - // Throw an AWD error, we should still retry again. - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath)), - // Throw another panic error. - Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::Panic("bar".into()))), - ]), + return executor::block_on(validate_candidate_exhaustive( + MockValidateCandidateBackend::with_hardcoded_result_list(mock_errors), validation_data, validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + exec_kind, &Default::default(), - )); - - assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(s))) if s == "bar".to_string()); + )) } #[test] @@ -742,15 +758,15 @@ fn candidate_validation_timeout_is_internal_error() { let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() }; let v = executor::block_on(validate_candidate_exhaustive( - MockValidateCandidateBackend::with_hardcoded_result(Err( - ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout), - )), + MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( + WasmInvalidCandidate::HardTimeout, + ))), validation_data, validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )); @@ -795,7 +811,7 @@ fn candidate_validation_commitment_hash_mismatch_is_invalid() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )) .unwrap(); @@ -836,15 +852,15 @@ fn candidate_validation_code_mismatch_is_invalid() { let (_ctx, _ctx_handle) = test_helpers::make_subsystem_context::(pool.clone()); let v = executor::block_on(validate_candidate_exhaustive( - MockValidateCandidateBackend::with_hardcoded_result(Err( - ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout), - )), + MockValidateCandidateBackend::with_hardcoded_result(Err(ValidationError::Invalid( + WasmInvalidCandidate::HardTimeout, + ))), validation_data, validation_code, candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )) .unwrap(); @@ -901,7 +917,7 @@ fn compressed_code_works() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )); @@ -952,7 +968,7 @@ fn code_decompression_failure_is_error() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )); @@ -1004,7 +1020,7 @@ fn pov_decompression_failure_is_invalid() { candidate_receipt, Arc::new(pov), ExecutorParams::default(), - PvfExecTimeoutKind::Backing, + PvfExecKind::Backing, &Default::default(), )); @@ -1012,11 +1028,11 @@ fn pov_decompression_failure_is_invalid() { } struct MockPreCheckBackend { - result: Result, + result: Result<(), PrepareError>, } impl MockPreCheckBackend { - fn with_hardcoded_result(result: Result) -> Self { + fn with_hardcoded_result(result: Result<(), PrepareError>) -> Self { Self { result } } } @@ -1032,7 +1048,7 @@ impl ValidationBackend for MockPreCheckBackend { unreachable!() } - async fn precheck_pvf(&mut self, _pvf: PvfPrepData) -> Result { + async fn precheck_pvf(&mut self, _pvf: PvfPrepData) -> Result<(), PrepareError> { self.result.clone() } } @@ -1049,7 +1065,7 @@ fn precheck_works() { let (check_fut, check_result) = precheck_pvf( ctx.sender(), - MockPreCheckBackend::with_hardcoded_result(Ok(PrepareStats::default())), + MockPreCheckBackend::with_hardcoded_result(Ok(())), relay_parent, validation_code_hash, ) @@ -1111,7 +1127,7 @@ fn precheck_invalid_pvf_blob_compression() { let (check_fut, check_result) = precheck_pvf( ctx.sender(), - MockPreCheckBackend::with_hardcoded_result(Ok(PrepareStats::default())), + MockPreCheckBackend::with_hardcoded_result(Ok(())), relay_parent, validation_code_hash, ) @@ -1216,7 +1232,7 @@ fn precheck_properly_classifies_outcomes() { inner(Err(PrepareError::Prevalidation("foo".to_owned())), PreCheckOutcome::Invalid); inner(Err(PrepareError::Preparation("bar".to_owned())), PreCheckOutcome::Invalid); - inner(Err(PrepareError::Panic("baz".to_owned())), PreCheckOutcome::Invalid); + inner(Err(PrepareError::JobError("baz".to_owned())), PreCheckOutcome::Invalid); inner(Err(PrepareError::TimedOut), PreCheckOutcome::Failed); inner(Err(PrepareError::IoErr("fizz".to_owned())), PreCheckOutcome::Failed); diff --git a/polkadot/node/core/chain-api/Cargo.toml b/polkadot/node/core/chain-api/Cargo.toml index 2178be028bd408297613b75730f35fde7277603a..9aa017ecba3d27c8f85c8055a1fc784d38efbfbd 100644 --- a/polkadot/node/core/chain-api/Cargo.toml +++ b/polkadot/node/core/chain-api/Cargo.toml @@ -1,17 +1,20 @@ [package] name = "polkadot-node-core-chain-api" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" } -sp-blockchain = { path = "../../../../substrate/primitives/blockchain" } -polkadot-primitives = { path = "../../../primitives" } polkadot-node-metrics = { path = "../../metrics" } polkadot-node-subsystem = { path = "../../subsystem" } +polkadot-node-subsystem-types = { path = "../../subsystem-types" } sc-client-api = { path = "../../../../substrate/client/api" } sc-consensus-babe = { path = "../../../../substrate/client/consensus/babe" } @@ -20,5 +23,7 @@ futures = { version = "0.3.21", features = ["thread-pool"] } maplit = "1.0.2" parity-scale-codec = "3.6.1" polkadot-node-primitives = { path = "../../primitives" } +polkadot-primitives = { path = "../../../primitives" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } sp-core = { path = "../../../../substrate/primitives/core" } +sp-blockchain = { path = "../../../../substrate/primitives/blockchain" } diff --git a/polkadot/node/core/chain-api/src/lib.rs b/polkadot/node/core/chain-api/src/lib.rs index 9b25481d71860bbc76f19840397360b8e3ee102b..7fd5166310fec2641af2edaeb8a7580caf9512ad 100644 --- a/polkadot/node/core/chain-api/src/lib.rs +++ b/polkadot/node/core/chain-api/src/lib.rs @@ -35,13 +35,13 @@ use std::sync::Arc; use futures::prelude::*; use sc_client_api::AuxStore; -use sp_blockchain::HeaderBackend; +use futures::stream::StreamExt; use polkadot_node_subsystem::{ messages::ChainApiMessage, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult, }; -use polkadot_primitives::Block; +use polkadot_node_subsystem_types::ChainApiBackend; mod metrics; use self::metrics::Metrics; @@ -67,7 +67,7 @@ impl ChainApiSubsystem { #[overseer::subsystem(ChainApi, error = SubsystemError, prefix = self::overseer)] impl ChainApiSubsystem where - Client: HeaderBackend + AuxStore + 'static, + Client: ChainApiBackend + AuxStore + 'static, { fn start(self, ctx: Context) -> SpawnedSubsystem { let future = run::(ctx, self) @@ -83,7 +83,7 @@ async fn run( subsystem: ChainApiSubsystem, ) -> SubsystemResult<()> where - Client: HeaderBackend + AuxStore, + Client: ChainApiBackend + AuxStore, { loop { match ctx.recv().await? { @@ -93,13 +93,15 @@ where FromOrchestra::Communication { msg } => match msg { ChainApiMessage::BlockNumber(hash, response_channel) => { let _timer = subsystem.metrics.time_block_number(); - let result = subsystem.client.number(hash).map_err(|e| e.to_string().into()); + let result = + subsystem.client.number(hash).await.map_err(|e| e.to_string().into()); subsystem.metrics.on_request(result.is_ok()); let _ = response_channel.send(result); }, ChainApiMessage::BlockHeader(hash, response_channel) => { let _timer = subsystem.metrics.time_block_header(); - let result = subsystem.client.header(hash).map_err(|e| e.to_string().into()); + let result = + subsystem.client.header(hash).await.map_err(|e| e.to_string().into()); subsystem.metrics.on_request(result.is_ok()); let _ = response_channel.send(result); }, @@ -113,46 +115,51 @@ where ChainApiMessage::FinalizedBlockHash(number, response_channel) => { let _timer = subsystem.metrics.time_finalized_block_hash(); // Note: we don't verify it's finalized - let result = subsystem.client.hash(number).map_err(|e| e.to_string().into()); + let result = + subsystem.client.hash(number).await.map_err(|e| e.to_string().into()); subsystem.metrics.on_request(result.is_ok()); let _ = response_channel.send(result); }, ChainApiMessage::FinalizedBlockNumber(response_channel) => { let _timer = subsystem.metrics.time_finalized_block_number(); - let result = subsystem.client.info().finalized_number; - // always succeeds - subsystem.metrics.on_request(true); - let _ = response_channel.send(Ok(result)); + let result = subsystem + .client + .info() + .await + .map_err(|e| e.to_string().into()) + .map(|info| info.finalized_number); + subsystem.metrics.on_request(result.is_ok()); + let _ = response_channel.send(result); }, ChainApiMessage::Ancestors { hash, k, response_channel } => { let _timer = subsystem.metrics.time_ancestors(); gum::trace!(target: LOG_TARGET, hash=%hash, k=k, "ChainApiMessage::Ancestors"); - let mut hash = hash; - - let next_parent = core::iter::from_fn(|| { - let maybe_header = subsystem.client.header(hash); - match maybe_header { - // propagate the error - Err(e) => { - let e = e.to_string().into(); - Some(Err(e)) - }, - // fewer than `k` ancestors are available - Ok(None) => None, - Ok(Some(header)) => { - // stop at the genesis header. - if header.number == 0 { - None - } else { - hash = header.parent_hash; - Some(Ok(hash)) - } - }, - } - }); - - let result = next_parent.take(k).collect::, _>>(); + let next_parent_stream = futures::stream::unfold( + (hash, subsystem.client.clone()), + |(hash, client)| async move { + let maybe_header = client.header(hash).await; + match maybe_header { + // propagate the error + Err(e) => { + let e = e.to_string().into(); + Some((Err(e), (hash, client))) + }, + // fewer than `k` ancestors are available + Ok(None) => None, + Ok(Some(header)) => { + // stop at the genesis header. + if header.number == 0 { + None + } else { + Some((Ok(header.parent_hash), (header.parent_hash, client))) + } + }, + } + }, + ); + + let result = next_parent_stream.take(k).try_collect().await; subsystem.metrics.on_request(result.is_ok()); let _ = response_channel.send(result); }, diff --git a/polkadot/node/core/chain-api/src/tests.rs b/polkadot/node/core/chain-api/src/tests.rs index 331a4f9ba820a85307659494e3c2a14f4fee52f9..eae8f6fa4ac591c565930047e4dcf590c914947f 100644 --- a/polkadot/node/core/chain-api/src/tests.rs +++ b/polkadot/node/core/chain-api/src/tests.rs @@ -22,7 +22,8 @@ use std::collections::BTreeMap; use polkadot_node_primitives::BlockWeight; use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle}; -use polkadot_primitives::{BlockNumber, Hash, Header}; +use polkadot_node_subsystem_types::ChainApiBackend; +use polkadot_primitives::{Block, BlockNumber, Hash, Header}; use sp_blockchain::Info as BlockInfo; use sp_core::testing::TaskExecutor; @@ -110,7 +111,7 @@ fn last_key_value(map: &BTreeMap) -> (K, V) { map.iter().last().map(|(k, v)| (k.clone(), v.clone())).unwrap() } -impl HeaderBackend for TestClient { +impl sp_blockchain::HeaderBackend for TestClient { fn info(&self) -> BlockInfo { let genesis_hash = self.blocks.iter().next().map(|(h, _)| *h).unwrap(); let (best_hash, best_number) = last_key_value(&self.blocks); @@ -191,8 +192,8 @@ fn request_block_number() { async move { let zero = Hash::zero(); let test_cases = [ - (TWO, client.number(TWO).unwrap()), - (zero, client.number(zero).unwrap()), // not here + (TWO, client.number(TWO).await.unwrap()), + (zero, client.number(zero).await.unwrap()), // not here ]; for (hash, expected) in &test_cases { let (tx, rx) = oneshot::channel(); @@ -217,8 +218,10 @@ fn request_block_header() { test_harness(|client, mut sender| { async move { const NOT_HERE: Hash = Hash::repeat_byte(0x5); - let test_cases = - [(TWO, client.header(TWO).unwrap()), (NOT_HERE, client.header(NOT_HERE).unwrap())]; + let test_cases = [ + (TWO, client.header(TWO).await.unwrap()), + (NOT_HERE, client.header(NOT_HERE).await.unwrap()), + ]; for (hash, expected) in &test_cases { let (tx, rx) = oneshot::channel(); @@ -270,8 +273,8 @@ fn request_finalized_hash() { test_harness(|client, mut sender| { async move { let test_cases = [ - (1, client.hash(1).unwrap()), // not here - (2, client.hash(2).unwrap()), + (1, client.hash(1).await.unwrap()), // not here + (2, client.hash(2).await.unwrap()), ]; for (number, expected) in &test_cases { let (tx, rx) = oneshot::channel(); @@ -297,7 +300,7 @@ fn request_last_finalized_number() { async move { let (tx, rx) = oneshot::channel(); - let expected = client.info().finalized_number; + let expected = client.info().await.unwrap().finalized_number; sender .send(FromOrchestra::Communication { msg: ChainApiMessage::FinalizedBlockNumber(tx), diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 7678379870e0371de28eeb3d3807b90820504b87..96fd42785cdd84ee463146f095b06ea01cc7b491 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-node-core-chain-selection" description = "Chain Selection Subsystem" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3" @@ -15,12 +18,12 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } parity-scale-codec = "3.6.1" [dev-dependencies] polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } sp-core = { path = "../../../../substrate/primitives/core" } -parking_lot = "0.12.0" +parking_lot = "0.12.1" assert_matches = "1" kvdb-memorydb = "0.13.0" diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 6061c52b7e810e75cd810dd76287ea1337166ed2..1fff0a77170669753fa3fd8d852f936446fe7264 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -1,16 +1,20 @@ [package] name = "polkadot-node-core-dispute-coordinator" -version = "1.0.0" +version = "7.0.0" +description = "The node-side components that participate in disputes" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } parity-scale-codec = "3.6.1" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } schnellru = "0.2.1" fatality = "0.0.6" diff --git a/polkadot/node/core/dispute-coordinator/src/import.rs b/polkadot/node/core/dispute-coordinator/src/import.rs index cf51ae4d3655e935859878fbc67564dfafbf1a9e..d3a4625f0d24b2ff0fc3bcc914d629e604909193 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; @@ -52,6 +52,9 @@ pub struct CandidateEnvironment<'a> { executor_params: &'a ExecutorParams, /// Validator indices controlled by this node. controlled_indices: HashSet, + /// Indices of on-chain disabled validators at the `relay_parent` combined + /// with the off-chain state. + disabled_indices: HashSet, } #[overseer::contextbounds(DisputeCoordinator, prefix = self::overseer)] @@ -65,7 +68,16 @@ impl<'a> CandidateEnvironment<'a> { runtime_info: &'a mut RuntimeInfo, session_index: SessionIndex, relay_parent: Hash, + disabled_offchain: impl IntoIterator, ) -> Option> { + let disabled_onchain = runtime_info + .get_disabled_validators(ctx.sender(), relay_parent) + .await + .unwrap_or_else(|err| { + gum::info!(target: LOG_TARGET, ?err, "Failed to get disabled validators"); + Vec::new() + }); + let (session, executor_params) = match runtime_info .get_session_info_by_index(ctx.sender(), relay_parent, session_index) .await @@ -75,8 +87,26 @@ impl<'a> CandidateEnvironment<'a> { Err(_) => return None, }; + let n_validators = session.validators.len(); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + // combine on-chain with off-chain disabled validators + // process disabled validators in the following order: + // - on-chain disabled validators + // - prioritized order of off-chain disabled validators + // deduplicate the list and take at most `byzantine_threshold` validators + let disabled_indices = { + let mut d: HashSet = HashSet::new(); + for v in disabled_onchain.into_iter().chain(disabled_offchain.into_iter()) { + if d.len() == byzantine_threshold { + break + } + d.insert(v); + } + d + }; + let controlled_indices = find_controlled_validator_indices(keystore, &session.validators); - Some(Self { session_index, session, executor_params, controlled_indices }) + Some(Self { session_index, session, executor_params, controlled_indices, disabled_indices }) } /// Validators in the candidate's session. @@ -103,6 +133,11 @@ impl<'a> CandidateEnvironment<'a> { pub fn controlled_indices(&'a self) -> &'a HashSet { &self.controlled_indices } + + /// Indices of off-chain and on-chain disabled validators. + pub fn disabled_indices(&'a self) -> &'a HashSet { + &self.disabled_indices + } } /// Whether or not we already issued some statement about a candidate. @@ -117,7 +152,7 @@ pub enum OwnVoteState { } impl OwnVoteState { - fn new(votes: &CandidateVotes, env: &CandidateEnvironment<'_>) -> Self { + fn new(votes: &CandidateVotes, env: &CandidateEnvironment) -> Self { let controlled_indices = env.controlled_indices(); if controlled_indices.is_empty() { return Self::CannotVote @@ -126,7 +161,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)) @@ -218,13 +255,19 @@ impl CandidateVoteState { let supermajority_threshold = polkadot_primitives::supermajority_threshold(n_validators); - // We have a dispute, if we have votes on both sides: - let is_disputed = !votes.invalid.is_empty() && !votes.valid.raw().is_empty(); + // We have a dispute, if we have votes on both sides, with at least one invalid vote + // from non-disabled validator or with votes on both sides and confirmed. + let has_non_disabled_invalid_votes = + votes.invalid.keys().any(|i| !env.disabled_indices().contains(i)); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + let votes_on_both_sides = !votes.valid.raw().is_empty() && !votes.invalid.is_empty(); + let is_confirmed = + votes_on_both_sides && (votes.voted_indices().len() > byzantine_threshold); + let is_disputed = + is_confirmed || (has_non_disabled_invalid_votes && !votes.valid.raw().is_empty()); let (dispute_status, byzantine_threshold_against) = if is_disputed { let mut status = DisputeStatus::active(); - let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); - let is_confirmed = votes.voted_indices().len() > byzantine_threshold; if is_confirmed { status = status.confirm(); }; @@ -305,7 +348,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 { @@ -342,6 +385,14 @@ impl CandidateVoteState { &self.votes.candidate_receipt } + /// Returns true if all the invalid votes are from disabled validators. + pub fn invalid_votes_all_disabled( + &self, + mut is_disabled: impl FnMut(&ValidatorIndex) -> bool, + ) -> bool { + self.votes.invalid.keys().all(|i| is_disabled(i)) + } + /// Extract `CandidateVotes` for handling import of new statements. fn into_old_state(self) -> (CandidateVotes, CandidateVoteState<()>) { let CandidateVoteState { votes, own_vote, dispute_status, byzantine_threshold_against } = @@ -511,7 +562,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 +576,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..5f86da87f21ca060cfe14de536b39e652d12b7b1 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -47,6 +47,7 @@ use polkadot_primitives::{ DisputeStatementSet, Hash, ScrapedOnChainVotes, SessionIndex, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, }; +use schnellru::{LruMap, UnlimitedCompact}; use crate::{ db, @@ -92,10 +93,13 @@ pub struct InitialData { pub(crate) struct Initialized { keystore: Arc, runtime_info: RuntimeInfo, + /// We have the onchain state of disabled validators as well as the offchain + /// state that is based on the lost disputes. + offchain_disabled_validators: OffchainDisabledValidators, /// This is the highest `SessionIndex` seen via `ActiveLeavesUpdate`. It doesn't matter if it /// was cached successfully or not. It is used to detect ancient disputes. highest_session_seen: SessionIndex, - /// Will be set to `true` if an error occured during the last caching attempt + /// Will be set to `true` if an error occurred during the last caching attempt gaps_in_cache: bool, spam_slots: SpamSlots, participation: Participation, @@ -130,10 +134,12 @@ impl Initialized { let (participation_sender, participation_receiver) = mpsc::channel(1); let participation = Participation::new(participation_sender, metrics.clone()); + let offchain_disabled_validators = OffchainDisabledValidators::default(); Self { keystore, runtime_info, + offchain_disabled_validators, highest_session_seen, gaps_in_cache, spam_slots, @@ -319,13 +325,16 @@ impl Initialized { self.runtime_info.pin_block(session_idx, new_leaf.unpin_handle); // Fetch the last `DISPUTE_WINDOW` number of sessions unless there are no gaps // in cache and we are not missing too many `SessionInfo`s - let mut lower_bound = session_idx.saturating_sub(DISPUTE_WINDOW.get() - 1); - if !self.gaps_in_cache && self.highest_session_seen > lower_bound { - lower_bound = self.highest_session_seen + 1 - } + let prune_up_to = session_idx.saturating_sub(DISPUTE_WINDOW.get() - 1); + let fetch_lower_bound = + if !self.gaps_in_cache && self.highest_session_seen > prune_up_to { + self.highest_session_seen + 1 + } else { + prune_up_to + }; // There is a new session. Perform a dummy fetch to cache it. - for idx in lower_bound..=session_idx { + for idx in fetch_lower_bound..=session_idx { if let Err(err) = self .runtime_info .get_session_info_by_index(ctx.sender(), new_leaf.hash, idx) @@ -344,11 +353,9 @@ impl Initialized { self.highest_session_seen = session_idx; - db::v1::note_earliest_session( - overlay_db, - session_idx.saturating_sub(DISPUTE_WINDOW.get() - 1), - )?; - self.spam_slots.prune_old(session_idx.saturating_sub(DISPUTE_WINDOW.get() - 1)); + db::v1::note_earliest_session(overlay_db, prune_up_to)?; + self.spam_slots.prune_old(prune_up_to); + self.offchain_disabled_validators.prune_old(prune_up_to); }, Ok(_) => { /* no new session => nothing to cache */ }, Err(err) => { @@ -642,7 +649,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 +663,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, @@ -963,6 +970,7 @@ impl Initialized { &mut self.runtime_info, session, relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { @@ -978,11 +986,13 @@ impl Initialized { Some(env) => env, }; + let n_validators = env.validators().len(); + gum::trace!( target: LOG_TARGET, ?candidate_hash, ?session, - num_validators = ?env.session_info().validators.len(), + ?n_validators, "Number of validators" ); @@ -1084,9 +1094,10 @@ impl Initialized { target: LOG_TARGET, ?candidate_hash, ?session, - num_validators = ?env.session_info().validators.len(), + ?n_validators, "Import result ready" ); + let new_state = import_result.new_state(); let is_included = self.scraper.is_candidate_included(&candidate_hash); @@ -1094,8 +1105,9 @@ impl Initialized { let own_vote_missing = new_state.own_vote_missing(); let is_disputed = new_state.is_disputed(); let is_confirmed = new_state.is_confirmed(); - let potential_spam = is_potential_spam(&self.scraper, &new_state, &candidate_hash); - // We participate only in disputes which are not potential spam. + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&self.scraper, &new_state, &candidate_hash, is_disabled); let allow_participation = !potential_spam; gum::trace!( @@ -1106,6 +1118,7 @@ impl Initialized { ?candidate_hash, confirmed = ?new_state.is_confirmed(), has_invalid_voters = ?!import_result.new_invalid_voters().is_empty(), + n_disabled_validators = ?env.disabled_indices().len(), "Is spam?" ); @@ -1337,6 +1350,10 @@ impl Initialized { ); } } + for validator_index in new_state.votes().invalid.keys() { + self.offchain_disabled_validators + .insert_against_valid(session, *validator_index); + } self.metrics.on_concluded_valid(); } if import_result.is_freshly_concluded_against() { @@ -1356,6 +1373,14 @@ impl Initialized { ); } } + for (validator_index, (kind, _sig)) in new_state.votes().valid.raw() { + let is_backer = kind.is_backing(); + self.offchain_disabled_validators.insert_for_invalid( + session, + *validator_index, + is_backer, + ); + } self.metrics.on_concluded_invalid(); } @@ -1393,6 +1418,7 @@ impl Initialized { &mut self.runtime_info, session, candidate_receipt.descriptor.relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { @@ -1591,3 +1617,82 @@ fn determine_undisputed_chain( Ok(last) } + +#[derive(Default)] +struct OffchainDisabledValidators { + // Ideally, we want to use the top `byzantine_threshold` offenders here based on the amount of + // stake slashed. However, given that slashing might be applied with a delay, we want to have + // some list of offenders as soon as disputes conclude offchain. This list only approximates + // the top offenders and only accounts for lost disputes. But that should be good enough to + // prevent spam attacks. + per_session: BTreeMap, +} + +struct LostSessionDisputes { + // We separate lost disputes to prioritize "for invalid" offenders. And among those, we + // prioritize backing votes the most. There's no need to limit the size of these sets, as they + // are already limited by the number of validators in the session. We use `LruMap` to ensure + // the iteration order prioritizes most recently disputes lost over older ones in case we reach + // the limit. + backers_for_invalid: LruMap, + for_invalid: LruMap, + against_valid: LruMap, +} + +impl Default for LostSessionDisputes { + fn default() -> Self { + Self { + backers_for_invalid: LruMap::new(UnlimitedCompact), + for_invalid: LruMap::new(UnlimitedCompact), + against_valid: LruMap::new(UnlimitedCompact), + } + } +} + +impl OffchainDisabledValidators { + fn prune_old(&mut self, up_to_excluding: SessionIndex) { + // split_off returns everything after the given key, including the key. + let mut relevant = self.per_session.split_off(&up_to_excluding); + std::mem::swap(&mut relevant, &mut self.per_session); + } + + fn insert_for_invalid( + &mut self, + session_index: SessionIndex, + validator_index: ValidatorIndex, + is_backer: bool, + ) { + let entry = self.per_session.entry(session_index).or_default(); + if is_backer { + entry.backers_for_invalid.insert(validator_index, ()); + } else { + entry.for_invalid.insert(validator_index, ()); + } + } + + fn insert_against_valid( + &mut self, + session_index: SessionIndex, + validator_index: ValidatorIndex, + ) { + self.per_session + .entry(session_index) + .or_default() + .against_valid + .insert(validator_index, ()); + } + + /// Iterate over all validators that are offchain disabled. + /// The order of iteration prioritizes `for_invalid` offenders (and backers among those) over + /// `against_valid` offenders. And most recently lost disputes over older ones. + /// NOTE: the iterator might contain duplicates. + fn iter(&self, session_index: SessionIndex) -> impl Iterator + '_ { + self.per_session.get(&session_index).into_iter().flat_map(|e| { + e.backers_for_invalid + .iter() + .chain(e.for_invalid.iter()) + .chain(e.against_valid.iter()) + .map(|(i, _)| *i) + }) + } +} diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index 3f0fd4013141925fb57e462934b0312698c89291..4b511e7430af655303a7bb6e5ca0e86d0f8e8c7b 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -27,6 +27,7 @@ use std::sync::Arc; +use error::FatalError; use futures::FutureExt; use gum::CandidateHash; @@ -340,6 +341,8 @@ impl DisputeCoordinatorSubsystem { runtime_info, highest_session, leaf_hash, + // on startup we don't have any off-chain disabled state + std::iter::empty(), ) .await { @@ -369,8 +372,9 @@ impl DisputeCoordinatorSubsystem { }, }; let vote_state = CandidateVoteState::new(votes, &env, now); - - let potential_spam = is_potential_spam(&scraper, &vote_state, candidate_hash); + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&scraper, &vote_state, candidate_hash, is_disabled); let is_included = scraper.is_candidate_included(&vote_state.votes().candidate_receipt.hash()); @@ -431,7 +435,7 @@ impl DisputeCoordinatorSubsystem { #[overseer::contextbounds(DisputeCoordinator, prefix = self::overseer)] async fn wait_for_first_leaf(ctx: &mut Context) -> Result> { loop { - match ctx.recv().await? { + match ctx.recv().await.map_err(FatalError::SubsystemReceive)? { FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(None), FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => { if let Some(activated) = update.activated { @@ -461,17 +465,20 @@ async fn wait_for_first_leaf(ctx: &mut Context) -> Result( +pub fn is_potential_spam( scraper: &ChainScraper, - vote_state: &CandidateVoteState, + vote_state: &CandidateVoteState, candidate_hash: &CandidateHash, + is_disabled: impl FnMut(&ValidatorIndex) -> bool, ) -> bool { let is_disputed = vote_state.is_disputed(); let is_included = scraper.is_candidate_included(candidate_hash); let is_backed = scraper.is_candidate_backed(candidate_hash); let is_confirmed = vote_state.is_confirmed(); + let all_invalid_votes_disabled = vote_state.invalid_votes_all_disabled(is_disabled); + let ignore_disabled = !is_confirmed && all_invalid_votes_disabled; - is_disputed && !is_included && !is_backed && !is_confirmed + (is_disputed && !is_included && !is_backed && !is_confirmed) || ignore_disabled } /// Tell dispute-distribution to send all our votes. @@ -575,7 +582,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/participation/mod.rs b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs index 35d07b411e8f644ed5491a2e6344fd8f99a71c60..05ea7323af1419d770cb7b68e15ee7887ceeaab8 100644 --- a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs +++ b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs @@ -32,7 +32,7 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_util::runtime::get_validation_code_by_hash; use polkadot_primitives::{ - BlockNumber, CandidateHash, CandidateReceipt, Hash, PvfExecTimeoutKind, SessionIndex, + BlockNumber, CandidateHash, CandidateReceipt, Hash, PvfExecKind, SessionIndex, }; use crate::LOG_TARGET; @@ -380,15 +380,15 @@ async fn participate( // same level of leeway. let (validation_tx, validation_rx) = oneshot::channel(); sender - .send_message(CandidateValidationMessage::ValidateFromExhaustive( - available_data.validation_data, + .send_message(CandidateValidationMessage::ValidateFromExhaustive { + validation_data: available_data.validation_data, validation_code, - req.candidate_receipt().clone(), - available_data.pov, - req.executor_params(), - PvfExecTimeoutKind::Approval, - validation_tx, - )) + candidate_receipt: req.candidate_receipt().clone(), + pov: available_data.pov, + executor_params: req.executor_params(), + exec_kind: PvfExecKind::Approval, + response_sender: validation_tx, + }) .await; // we cast votes (either positive or negative) depending on the outcome of diff --git a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs index ee6b950720e5f672d2e33a1f22e367b08d829d6f..367454115f0be8e9aaccaf73b13e721a585c9dd7 100644 --- a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs @@ -115,12 +115,12 @@ pub async fn participation_full_happy_path( assert_matches!( ctx_handle.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(_, _, candidate_receipt, _, _, timeout, tx) - ) if timeout == PvfExecTimeoutKind::Approval => { + CandidateValidationMessage::ValidateFromExhaustive { candidate_receipt, exec_kind, response_sender, .. } + ) if exec_kind == PvfExecKind::Approval => { if expected_commitments_hash != candidate_receipt.commitments_hash { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); + response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); } else { - tx.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap(); + response_sender.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap(); } }, "overseer did not receive candidate validation message", @@ -372,7 +372,6 @@ fn cannot_participate_if_cannot_recover_validation_code() { let mut participation = Participation::new(sender, Metrics::default()); activate_leaf(&mut ctx, &mut participation, 10).await.unwrap(); participate(&mut ctx, &mut participation).await.unwrap(); - recover_available_data(&mut ctx_handle).await; assert_matches!( @@ -450,9 +449,9 @@ fn cast_invalid_vote_if_validation_fails_or_is_invalid() { assert_matches!( ctx_handle.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, _, timeout, tx) - ) if timeout == PvfExecTimeoutKind::Approval => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))).unwrap(); + CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } + ) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))).unwrap(); }, "overseer did not receive candidate validation message", ); @@ -487,9 +486,9 @@ fn cast_invalid_vote_if_commitments_dont_match() { assert_matches!( ctx_handle.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, _, timeout, tx) - ) if timeout == PvfExecTimeoutKind::Approval => { - tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); + CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } + ) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); }, "overseer did not receive candidate validation message", ); @@ -524,9 +523,9 @@ fn cast_valid_vote_if_validation_passes() { assert_matches!( ctx_handle.recv().await, AllMessages::CandidateValidation( - CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, _, timeout, tx) - ) if timeout == PvfExecTimeoutKind::Approval => { - tx.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap(); + CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } + ) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap(); }, "overseer did not receive candidate validation message", ); diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index 9254c2a851cea502ed8daccf434a68f48863de94..0360e357bee4ca70fe9a6ab3a9454d611ddc93fd 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::{ @@ -256,7 +257,7 @@ impl TestState { session: SessionIndex, block_number: BlockNumber, candidate_events: Vec, - ) { + ) -> Hash { assert!(block_number > 0); let block_header = Header { @@ -281,6 +282,8 @@ impl TestState { self.handle_sync_queries(virtual_overseer, block_hash, session, candidate_events) .await; + + block_hash } /// Returns any sent `DisputeMessage`s. @@ -352,6 +355,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(); + } + ); } } @@ -396,6 +408,19 @@ impl TestState { )) => { tx.send(Ok(Vec::new())).unwrap(); }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _new_leaf, + RuntimeApiRequest::Version(tx), + )) => { + tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)) + .unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _new_leaf, + RuntimeApiRequest::DisabledValidators(tx), + )) => { + tx.send(Ok(Vec::new())).unwrap(); + }, AllMessages::ChainApi(ChainApiMessage::Ancestors { hash, k, response_channel }) => { let target_header = self .headers @@ -618,15 +643,19 @@ async fn participation_with_distribution( } fn make_valid_candidate_receipt() -> CandidateReceipt { - let mut candidate_receipt = dummy_candidate_receipt_bad_sig(dummy_hash(), dummy_hash()); - candidate_receipt.commitments_hash = CandidateCommitments::default().hash(); - candidate_receipt + make_another_valid_candidate_receipt(dummy_hash()) } fn make_invalid_candidate_receipt() -> CandidateReceipt { dummy_candidate_receipt_bad_sig(Default::default(), Some(Default::default())) } +fn make_another_valid_candidate_receipt(relay_parent: Hash) -> CandidateReceipt { + let mut candidate_receipt = dummy_candidate_receipt_bad_sig(relay_parent, dummy_hash()); + candidate_receipt.commitments_hash = CandidateCommitments::default().hash(); + candidate_receipt +} + // Generate a `CandidateBacked` event from a `CandidateReceipt`. The rest is dummy data. fn make_candidate_backed_event(candidate_receipt: CandidateReceipt) -> CandidateEvent { CandidateEvent::CandidateBacked( @@ -651,7 +680,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, @@ -730,6 +759,7 @@ fn too_many_unconfirmed_statements_are_considered_spam() { .await; gum::trace!("After sending `ImportStatements`"); + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, HashMap::new()) .await; @@ -858,10 +888,14 @@ 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_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, approval_votes) .await; @@ -969,6 +1003,7 @@ fn dispute_gets_confirmed_via_participation() { }) .await; gum::debug!("After First import!"); + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, HashMap::new()) .await; @@ -1118,6 +1153,7 @@ fn dispute_gets_confirmed_at_byzantine_threshold() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, HashMap::new()) .await; @@ -1242,6 +1278,7 @@ fn backing_statements_import_works_and_no_spam() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); { @@ -1374,6 +1411,7 @@ fn conflicting_votes_lead_to_dispute_participation() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -1493,6 +1531,7 @@ fn positive_votes_dont_trigger_participation() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; { let (tx, rx) = oneshot::channel(); @@ -1603,6 +1642,7 @@ fn wrong_validator_index_is_ignored() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; { let (tx, rx) = oneshot::channel(); @@ -1680,6 +1720,7 @@ fn finality_votes_ignore_disputed_candidates() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -1756,14 +1797,10 @@ fn supermajority_valid_dispute_may_be_finalized() { let candidate_receipt = make_valid_candidate_receipt(); let candidate_hash = candidate_receipt.hash(); + let candidate_events = vec![make_candidate_backed_event(candidate_receipt.clone())]; test_state - .activate_leaf_at_session( - &mut virtual_overseer, - session, - 1, - vec![make_candidate_backed_event(candidate_receipt.clone())], - ) + .activate_leaf_at_session(&mut virtual_overseer, session, 1, candidate_events) .await; let supermajority_threshold = @@ -1792,6 +1829,7 @@ fn supermajority_valid_dispute_may_be_finalized() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -1929,6 +1967,7 @@ fn concluded_supermajority_for_non_active_after_time() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -2045,6 +2084,7 @@ fn concluded_supermajority_against_non_active_after_time() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; assert_matches!(confirmation_rx.await.unwrap(), @@ -2160,6 +2200,7 @@ fn resume_dispute_without_local_statement() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -2204,13 +2245,23 @@ fn resume_dispute_without_local_statement() { let candidate_receipt = make_valid_candidate_receipt(); let candidate_hash = candidate_receipt.hash(); - participation_with_distribution( + participation_full_happy_path( &mut virtual_overseer, - &candidate_hash, candidate_receipt.commitments_hash, ) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::DisputeDistribution( + DisputeDistributionMessage::SendDispute(msg) + ) => { + assert_eq!(msg.candidate_receipt().hash(), candidate_hash); + } + ); + let mut statements = Vec::new(); // Getting votes for supermajority. Should already have two valid votes. for i in vec![3, 4, 5, 6, 7] { @@ -2315,6 +2366,7 @@ fn resume_dispute_with_local_statement() { }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -2412,6 +2464,7 @@ fn resume_dispute_without_local_statement_or_local_key() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request( &mut virtual_overseer, &candidate_hash, @@ -2503,6 +2556,7 @@ fn issue_local_statement_does_cause_distribution_but_not_duplicate_participation }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // Initiate dispute locally: @@ -2543,7 +2597,7 @@ fn issue_local_statement_does_cause_distribution_but_not_duplicate_participation } #[test] -fn own_approval_vote_gets_distributed_on_dispute() { +fn participation_with_onchain_disabling_unconfirmed() { test_harness(|mut test_state, mut virtual_overseer| { Box::pin(async move { let session = 1; @@ -2552,73 +2606,125 @@ fn own_approval_vote_gets_distributed_on_dispute() { let candidate_receipt = make_valid_candidate_receipt(); let candidate_hash = candidate_receipt.hash(); + let events = vec![make_candidate_included_event(candidate_receipt.clone())]; test_state - .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .activate_leaf_at_session(&mut virtual_overseer, session, 1, events) .await; - let statement = test_state.issue_approval_vote_with_index( - ValidatorIndex(0), + let backer_index = ValidatorIndex(1); + let disabled_index = ValidatorIndex(2); + + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + backer_index, + disabled_index, candidate_hash, session, - ); + VoteType::Backing, + ) + .await; - // Import our approval vote: + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + + // Scenario 1: unconfirmed dispute with onchain disabled validator against. + // Expectation: we import the vote, but do not participate. virtual_overseer .send(FromOrchestra::Communication { msg: DisputeCoordinatorMessage::ImportStatements { candidate_receipt: candidate_receipt.clone(), session, - statements: vec![(statement, ValidatorIndex(0))], - pending_confirmation: None, + statements: vec![ + (valid_vote, backer_index), + (invalid_vote, disabled_index), + ], + pending_confirmation, }, }) .await; - // Trigger dispute: - let (valid_vote, invalid_vote) = generate_opposing_votes_pair( - &test_state, - ValidatorIndex(2), - ValidatorIndex(1), + handle_disabled_validators_queries(&mut virtual_overseer, vec![disabled_index]).await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + // we should not participate due to disabled indices on chain + assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + + { + // make sure the dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 0); + } + + // Scenario 2: unconfirmed dispute with non-disabled validator against. + // Expectation: even if the dispute is unconfirmed, we should participate + // once we receive an invalid vote from a non-disabled validator. + let non_disabled_index = ValidatorIndex(3); + let vote = test_state.issue_explicit_statement_with_index( + non_disabled_index, candidate_hash, session, - VoteType::Explicit, - ) - .await; + false, + ); + let statements = vec![(vote, non_disabled_index)]; let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + virtual_overseer .send(FromOrchestra::Communication { msg: DisputeCoordinatorMessage::ImportStatements { candidate_receipt: candidate_receipt.clone(), session, - statements: vec![ - (invalid_vote, ValidatorIndex(1)), - (valid_vote, ValidatorIndex(2)), - ], - pending_confirmation: Some(pending_confirmation), + statements, + pending_confirmation, }, }) .await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); - // Dispute distribution should get notified now (without participation, as we already - // have an approval vote): - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::DisputeDistribution( - DisputeDistributionMessage::SendDispute(msg) - ) => { - assert_eq!(msg.session_index(), session); - assert_eq!(msg.candidate_receipt(), &candidate_receipt); - } - ); + participation_with_distribution( + &mut virtual_overseer, + &candidate_hash, + candidate_receipt.commitments_hash, + ) + .await; - // No participation should occur: - assert_matches!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await, None); + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 2); // 1+1 => we have participated + assert_eq!(votes.invalid.len(), 2); + } virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; assert!(virtual_overseer.try_recv().await.is_none()); @@ -2629,49 +2735,107 @@ fn own_approval_vote_gets_distributed_on_dispute() { } #[test] -fn negative_issue_local_statement_only_triggers_import() { +fn participation_with_onchain_disabling_confirmed() { test_harness(|mut test_state, mut virtual_overseer| { Box::pin(async move { let session = 1; test_state.handle_resume_sync(&mut virtual_overseer, session).await; - let candidate_receipt = make_invalid_candidate_receipt(); + let candidate_receipt = make_valid_candidate_receipt(); let candidate_hash = candidate_receipt.hash(); + let events = vec![make_candidate_included_event(candidate_receipt.clone())]; test_state - .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .activate_leaf_at_session(&mut virtual_overseer, session, 1, events) .await; + let backer_index = ValidatorIndex(1); + let disabled_index = ValidatorIndex(2); + + // Scenario 1: confirmed dispute with disabled validator + // Expectation: we import the vote and participate. + let mut statements = Vec::new(); + + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + backer_index, + disabled_index, + candidate_hash, + session, + VoteType::Backing, + ) + .await; + + statements.push((valid_vote, backer_index)); + statements.push((invalid_vote, disabled_index)); + + // now import enough votes for dispute confirmation + for i in vec![3, 4] { + let vote = test_state.issue_explicit_statement_with_index( + ValidatorIndex(i), + candidate_hash, + session, + true, + ); + + statements.push((vote, ValidatorIndex(i as _))); + } + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + virtual_overseer .send(FromOrchestra::Communication { - msg: DisputeCoordinatorMessage::IssueLocalStatement( + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: candidate_receipt.clone(), session, - candidate_hash, - candidate_receipt.clone(), - false, - ), + statements, + pending_confirmation, + }, }) .await; - // Assert that subsystem is not participating. - assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + handle_disabled_validators_queries(&mut virtual_overseer, vec![]).await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); - virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; - assert!(virtual_overseer.try_recv().await.is_none()); + participation_with_distribution( + &mut virtual_overseer, + &candidate_hash, + candidate_receipt.commitments_hash, + ) + .await; - let backend = DbBackend::new( - test_state.db.clone(), - test_state.config.column_config(), - Metrics::default(), - ); + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; - let votes = backend.load_candidate_votes(session, &candidate_hash).unwrap().unwrap(); - assert_eq!(votes.invalid.len(), 1); - assert_eq!(votes.valid.len(), 0); + assert_eq!(rx.await.unwrap().len(), 1); - let disputes = backend.load_recent_disputes().unwrap(); - assert_eq!(disputes, None); + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 4); // 3+1 => we have participated + assert_eq!(votes.invalid.len(), 1); + } + + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + assert!(virtual_overseer.try_recv().await.is_none()); test_state }) @@ -2679,7 +2843,8 @@ fn negative_issue_local_statement_only_triggers_import() { } #[test] -fn redundant_votes_ignored() { +fn participation_with_offchain_disabling() { + sp_tracing::init_for_tests(); test_harness(|mut test_state, mut virtual_overseer| { Box::pin(async move { let session = 1; @@ -2688,37 +2853,599 @@ fn redundant_votes_ignored() { let candidate_receipt = make_valid_candidate_receipt(); let candidate_hash = candidate_receipt.hash(); + let events = vec![make_candidate_included_event(candidate_receipt.clone())]; + + let block_hash = test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 3, events) + .await; + + let another_candidate_receipt = make_another_valid_candidate_receipt(block_hash); + let another_candidate_hash = another_candidate_receipt.hash(); + let another_events = + vec![make_candidate_included_event(another_candidate_receipt.clone())]; test_state - .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .activate_leaf_at_session(&mut virtual_overseer, session, 4, another_events) .await; - let valid_vote = test_state.issue_backing_statement_with_index( + // import enough votes for supermajority to conclude the dispute + let mut statements = Vec::new(); + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, ValidatorIndex(1), + ValidatorIndex(2), candidate_hash, session, - ); + VoteType::Backing, + ) + .await; - let valid_vote_2 = test_state.issue_backing_statement_with_index( - ValidatorIndex(1), - candidate_hash, - session, - ); + statements.push((valid_vote, ValidatorIndex(1))); + statements.push((invalid_vote, ValidatorIndex(2))); - assert!(valid_vote.validator_signature() != valid_vote_2.validator_signature()); + for i in vec![3, 4, 5, 6, 7, 8] { + let vote = test_state.issue_explicit_statement_with_index( + ValidatorIndex(i), + candidate_hash, + session, + true, + ); + + statements.push((vote, ValidatorIndex(i as _))); + } + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); - let (tx, rx) = oneshot::channel(); virtual_overseer .send(FromOrchestra::Communication { msg: DisputeCoordinatorMessage::ImportStatements { candidate_receipt: candidate_receipt.clone(), session, - statements: vec![(valid_vote.clone(), ValidatorIndex(1))], - pending_confirmation: Some(tx), + statements, + pending_confirmation, }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, vec![]).await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + participation_with_distribution( + &mut virtual_overseer, + &candidate_hash, + candidate_receipt.commitments_hash, + ) + .await; + + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 8); // 8 => we have participated + assert_eq!(votes.invalid.len(), 1); + } + + // now create another dispute + // Validator 2 should be disabled offchain now + + let mut statements = Vec::new(); + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + ValidatorIndex(1), + ValidatorIndex(2), + another_candidate_hash, + session, + VoteType::Backing, + ) + .await; + + statements.push((valid_vote, ValidatorIndex(1))); + statements.push((invalid_vote, ValidatorIndex(2))); + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: another_candidate_receipt.clone(), + session, + statements, + pending_confirmation, + }, + }) + .await; + + // let's disable validators 3, 4 on chain, but this should not affect this import + let disabled_validators = vec![ValidatorIndex(3), ValidatorIndex(4)]; + handle_disabled_validators_queries(&mut virtual_overseer, disabled_validators).await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + // we should not participate since due to offchain disabling + assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + + { + // make sure the new dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + } + + // now import enough votes for dispute confirmation + // even though all of these votes are from (on chain) disabled validators + let mut statements = Vec::new(); + for i in vec![3, 4] { + let vote = test_state.issue_explicit_statement_with_index( + ValidatorIndex(i), + another_candidate_hash, + session, + true, + ); + + statements.push((vote, ValidatorIndex(i as _))); + } + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: another_candidate_receipt.clone(), + session, + statements, + pending_confirmation, + }, + }) + .await; + + handle_approval_vote_request( + &mut virtual_overseer, + &another_candidate_hash, + HashMap::new(), + ) + .await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + participation_with_distribution( + &mut virtual_overseer, + &another_candidate_hash, + another_candidate_receipt.commitments_hash, + ) + .await; + + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 2); + + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, another_candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 4); // 3+1 => we have participated + assert_eq!(votes.invalid.len(), 1); + } + + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + assert!(virtual_overseer.try_recv().await.is_none()); + + test_state + }) + }); +} + +// Once the onchain disabling reaches the byzantine threshold, +// offchain disabling will no longer take any effect. +#[test] +fn participation_with_disabling_limits() { + test_harness(|mut test_state, mut virtual_overseer| { + Box::pin(async move { + let session = 1; + + test_state.handle_resume_sync(&mut virtual_overseer, session).await; + + let candidate_receipt = make_valid_candidate_receipt(); + let candidate_hash = candidate_receipt.hash(); + let events = vec![make_candidate_included_event(candidate_receipt.clone())]; + + let block_hash = test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 3, events) + .await; + + let another_candidate_receipt = make_another_valid_candidate_receipt(block_hash); + let another_candidate_hash = another_candidate_receipt.hash(); + let another_events = + vec![make_candidate_included_event(another_candidate_receipt.clone())]; + + test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 4, another_events) + .await; + + // import enough votes for supermajority to conclude the dispute + let mut statements = Vec::new(); + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + ValidatorIndex(1), + ValidatorIndex(2), + candidate_hash, + session, + VoteType::Backing, + ) + .await; + + statements.push((valid_vote, ValidatorIndex(1))); + statements.push((invalid_vote, ValidatorIndex(2))); + + for i in vec![3, 4, 5, 6, 7, 8] { + let vote = test_state.issue_explicit_statement_with_index( + ValidatorIndex(i), + candidate_hash, + session, + true, + ); + + statements.push((vote, ValidatorIndex(i as _))); + } + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: candidate_receipt.clone(), + session, + statements, + pending_confirmation, + }, + }) + .await; + + handle_disabled_validators_queries(&mut virtual_overseer, vec![]).await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + participation_with_distribution( + &mut virtual_overseer, + &candidate_hash, + candidate_receipt.commitments_hash, + ) + .await; + + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 8); // 8 => we have participated + assert_eq!(votes.invalid.len(), 1); + } + + // now create another dispute + // validator 2 should be disabled offchain now + // but due to the byzantine threshold of onchain disabling + // this validator will be considered enabled + + let mut statements = Vec::new(); + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + ValidatorIndex(1), + ValidatorIndex(2), + another_candidate_hash, + session, + VoteType::Backing, + ) + .await; + + statements.push((valid_vote, ValidatorIndex(1))); + statements.push((invalid_vote, ValidatorIndex(2))); + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + let pending_confirmation = Some(pending_confirmation); + + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: another_candidate_receipt.clone(), + session, + statements, + pending_confirmation, + }, + }) + .await; + + // let's disable validators 3, 4, 5 on chain, reaching the byzantine threshold + let disabled_validators = vec![ValidatorIndex(3), ValidatorIndex(4), ValidatorIndex(5)]; + handle_disabled_validators_queries(&mut virtual_overseer, disabled_validators).await; + handle_approval_vote_request( + &mut virtual_overseer, + &another_candidate_hash, + HashMap::new(), + ) + .await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + participation_with_distribution( + &mut virtual_overseer, + &another_candidate_hash, + another_candidate_receipt.commitments_hash, + ) + .await; + + { + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 2); + + // check if we have participated (cast a vote) + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::QueryCandidateVotes( + vec![(session, another_candidate_hash)], + tx, + ), + }) + .await; + + let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); + assert_eq!(votes.valid.raw().len(), 2); // 2 => we have participated + assert_eq!(votes.invalid.len(), 1); + } + + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + assert!(virtual_overseer.try_recv().await.is_none()); + + test_state + }) + }); +} + +#[test] +fn own_approval_vote_gets_distributed_on_dispute() { + test_harness(|mut test_state, mut virtual_overseer| { + Box::pin(async move { + let session = 1; + + test_state.handle_resume_sync(&mut virtual_overseer, session).await; + + let candidate_receipt = make_valid_candidate_receipt(); + let candidate_hash = candidate_receipt.hash(); + + test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .await; + + let statement = test_state.issue_approval_vote_with_index( + ValidatorIndex(0), + candidate_hash, + session, + ); + + // Import our approval vote: + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: candidate_receipt.clone(), + session, + statements: vec![(statement, ValidatorIndex(0))], + pending_confirmation: None, + }, + }) + .await; + + // Trigger dispute: + let (valid_vote, invalid_vote) = generate_opposing_votes_pair( + &test_state, + ValidatorIndex(2), + ValidatorIndex(1), + candidate_hash, + session, + VoteType::Explicit, + ) + .await; + + let (pending_confirmation, confirmation_rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: candidate_receipt.clone(), + session, + statements: vec![ + (invalid_vote, ValidatorIndex(1)), + (valid_vote, ValidatorIndex(2)), + ], + pending_confirmation: Some(pending_confirmation), + }, + }) + .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); + + // Dispute distribution should get notified now (without participation, as we already + // have an approval vote): + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::DisputeDistribution( + DisputeDistributionMessage::SendDispute(msg) + ) => { + assert_eq!(msg.session_index(), session); + assert_eq!(msg.candidate_receipt(), &candidate_receipt); + } + ); + + // No participation should occur: + assert_matches!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await, None); + + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + assert!(virtual_overseer.try_recv().await.is_none()); + + test_state + }) + }); +} + +#[test] +fn negative_issue_local_statement_only_triggers_import() { + test_harness(|mut test_state, mut virtual_overseer| { + Box::pin(async move { + let session = 1; + + test_state.handle_resume_sync(&mut virtual_overseer, session).await; + + let candidate_receipt = make_invalid_candidate_receipt(); + let candidate_hash = candidate_receipt.hash(); + + test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .await; + + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::IssueLocalStatement( + session, + candidate_hash, + candidate_receipt.clone(), + false, + ), + }) + .await; + + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; + // Assert that subsystem is not participating. + assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + + virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await; + assert!(virtual_overseer.try_recv().await.is_none()); + + let backend = DbBackend::new( + test_state.db.clone(), + test_state.config.column_config(), + Metrics::default(), + ); + + let votes = backend.load_candidate_votes(session, &candidate_hash).unwrap().unwrap(); + assert_eq!(votes.invalid.len(), 1); + assert_eq!(votes.valid.len(), 0); + + let disputes = backend.load_recent_disputes().unwrap(); + assert_eq!(disputes, None); + + test_state + }) + }); +} + +#[test] +fn redundant_votes_ignored() { + test_harness(|mut test_state, mut virtual_overseer| { + Box::pin(async move { + let session = 1; + + test_state.handle_resume_sync(&mut virtual_overseer, session).await; + + let candidate_receipt = make_valid_candidate_receipt(); + let candidate_hash = candidate_receipt.hash(); + + test_state + .activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new()) + .await; + + let valid_vote = test_state.issue_backing_statement_with_index( + ValidatorIndex(1), + candidate_hash, + session, + ); + + let valid_vote_2 = test_state.issue_backing_statement_with_index( + ValidatorIndex(1), + candidate_hash, + session, + ); + + assert!(valid_vote.validator_signature() != valid_vote_2.validator_signature()); + + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ImportStatements { + candidate_receipt: candidate_receipt.clone(), + session, + statements: vec![(valid_vote.clone(), ValidatorIndex(1))], + pending_confirmation: Some(tx), + }, + }) + .await; + + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; rx.await.unwrap(); let (tx, rx) = oneshot::channel(); @@ -2793,6 +3520,7 @@ fn no_onesided_disputes() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // We should not have any active disputes now. @@ -2856,6 +3584,7 @@ fn refrain_from_participation() { }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -2948,6 +3677,7 @@ fn participation_for_included_candidates() { }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -3036,6 +3766,7 @@ fn local_participation_in_dispute_for_backed_candidate() { }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; @@ -3177,6 +3908,7 @@ fn participation_requests_reprioritized_for_newly_included() { }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; // Handle corresponding messages to unblock import // we need to handle `ApprovalVotingMessage::GetApprovalSignaturesForCandidate` for // import @@ -3330,6 +4062,7 @@ fn informs_chain_selection_when_dispute_concluded_against() { }, }) .await; + handle_disabled_validators_queries(&mut virtual_overseer, Vec::new()).await; handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) .await; assert_matches!(confirmation_rx.await.unwrap(), @@ -3492,6 +4225,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 +4293,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,8 +4362,40 @@ 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 }) }); } + +async fn handle_disabled_validators_queries( + virtual_overseer: &mut VirtualOverseer, + disabled_validators: Vec, +) { + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _new_leaf, + RuntimeApiRequest::Version(tx), + )) => { + tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)).unwrap(); + } + ); + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _new_leaf, + RuntimeApiRequest::DisabledValidators(tx), + )) => { + tx.send(Ok(disabled_validators)).unwrap(); + } + ); +} diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index 18d91dcfb565abcc0011777d18b881966013701a..24da4dc1e316f8469704ab57e4742dbc07197992 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -1,16 +1,20 @@ [package] name = "polkadot-node-core-parachains-inherent" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" +thiserror = { workspace = true } +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 77a59d87f3fa77a42347550e7538d21b8ceda5b0..f66a66e859ec0a139e31068f78225597d577d1a9 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -1,15 +1,19 @@ [package] name = "polkadot-node-core-prospective-parachains" -version = "1.0.0" +version = "6.0.0" authors.workspace = true 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" } parity-scale-codec = "3.6.4" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" @@ -19,6 +23,7 @@ polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } [dev-dependencies] +rstest = "0.18.2" assert_matches = "1" polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-node-subsystem-types = { path = "../../subsystem-types" } diff --git a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs index 292e4ebe5282b4344853ca5f3247caf1d9af6cc7..8061dc82d8358ae9e7c741e3f785634601eb5d03 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs @@ -96,6 +96,7 @@ use std::{ use super::LOG_TARGET; use bitvec::prelude::*; +use polkadot_node_subsystem::messages::Ancestors; use polkadot_node_subsystem_util::inclusion_emulator::{ ConstraintModifications, Constraints, Fragment, ProspectiveCandidate, RelayChainBlockInfo, }; @@ -202,7 +203,10 @@ impl CandidateStorage { /// Note that an existing candidate has been backed. pub fn mark_backed(&mut self, candidate_hash: &CandidateHash) { if let Some(entry) = self.by_candidate_hash.get_mut(candidate_hash) { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate marked as backed"); entry.state = CandidateState::Backed; + } else { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate not found while marking as backed"); } } @@ -753,52 +757,217 @@ impl FragmentTree { depths.iter_ones().collect() } - /// Select a candidate after the given `required_path` which passes - /// the predicate. - /// - /// If there are multiple possibilities, this will select the first one. + /// Select `count` candidates after the given `ancestors` which pass + /// the predicate and have not already been backed on chain. /// - /// This returns `None` if there is no candidate meeting those criteria. + /// Does an exhaustive search into the tree after traversing the ancestors path. + /// If the ancestors draw out a path that can be traversed in multiple ways, no + /// candidates will be returned. + /// If the ancestors do not draw out a full path (the path contains holes), candidates will be + /// suggested that may fill these holes. + /// If the ancestors don't draw out a valid path, no candidates will be returned. If there are + /// multiple possibilities of the same size, this will select the first one. If there is no + /// chain of size `count` that matches the criteria, this will return the largest chain it could + /// find with the criteria. If there are no candidates meeting those criteria, returns an empty + /// `Vec`. + /// Cycles are accepted, but this code expects that the runtime will deduplicate + /// identical candidates when occupying the cores (when proposing to back A->B->A, only A will + /// be backed on chain). /// - /// The intention of the `required_path` is to allow queries on the basis of + /// The intention of the `ancestors` is to allow queries on the basis of /// one or more candidates which were previously pending availability becoming - /// available and opening up more room on the core. - pub(crate) fn select_child( + /// available or candidates timing out. + pub(crate) fn find_backable_chain( &self, - required_path: &[CandidateHash], + ancestors: Ancestors, + count: u32, pred: impl Fn(&CandidateHash) -> bool, - ) -> Option { - let base_node = { - // traverse the required path. - let mut node = NodePointer::Root; - for required_step in required_path { - node = self.node_candidate_child(node, &required_step)?; - } + ) -> Vec { + if count == 0 { + return vec![] + } + // First, we need to order the ancestors. + // The node returned is the one from which we can start finding new backable candidates. + let Some(base_node) = self.find_ancestor_path(ancestors) else { return vec![] }; + + self.find_backable_chain_inner( + base_node, + count, + count, + &pred, + &mut Vec::with_capacity(count as usize), + ) + } - node - }; + // Try finding a candidate chain starting from `base_node` of length `expected_count`. + // If not possible, return the longest one we could find. + // Does a depth-first search, since we're optimistic that there won't be more than one such + // chains (parachains shouldn't usually have forks). So in the usual case, this will conclude + // in `O(expected_count)`. + // Cycles are accepted, but this doesn't allow for infinite execution time, because the maximum + // depth we'll reach is `expected_count`. + // + // Worst case performance is `O(num_forks ^ expected_count)`, the same as populating the tree. + // Although an exponential function, this is actually a constant that can only be altered via + // sudo/governance, because: + // 1. `num_forks` at a given level is at most `max_candidate_depth * max_validators_per_core` + // (because each validator in the assigned group can second `max_candidate_depth` + // candidates). The prospective-parachains subsystem assumes that the number of para forks is + // limited by collator-protocol and backing subsystems. In practice, this is a constant which + // can only be altered by sudo or governance. + // 2. `expected_count` is equal to the number of cores a para is scheduled on (in an elastic + // scaling scenario). For non-elastic-scaling, this is just 1. In practice, this should be a + // small number (1-3), capped by the total number of available cores (a constant alterable + // only via governance/sudo). + fn find_backable_chain_inner( + &self, + base_node: NodePointer, + expected_count: u32, + remaining_count: u32, + pred: &dyn Fn(&CandidateHash) -> bool, + accumulator: &mut Vec, + ) -> Vec { + if remaining_count == 0 { + // The best option is the chain we've accumulated so far. + return accumulator.to_vec(); + } - // TODO [now]: taking the first selection might introduce bias - // or become gameable. - // - // For plausibly unique parachains, this shouldn't matter much. - // figure out alternative selection criteria? - match base_node { + let children: Vec<_> = match base_node { NodePointer::Root => self .nodes .iter() - .take_while(|n| n.parent == NodePointer::Root) - .filter(|n| self.scope.get_pending_availability(&n.candidate_hash).is_none()) - .filter(|n| pred(&n.candidate_hash)) - .map(|n| n.candidate_hash) - .next(), - NodePointer::Storage(ptr) => self.nodes[ptr] - .children - .iter() - .filter(|n| self.scope.get_pending_availability(&n.1).is_none()) - .filter(|n| pred(&n.1)) - .map(|n| n.1) - .next(), + .enumerate() + .take_while(|(_, n)| n.parent == NodePointer::Root) + .filter(|(_, n)| self.scope.get_pending_availability(&n.candidate_hash).is_none()) + .filter(|(_, n)| pred(&n.candidate_hash)) + .map(|(ptr, n)| (NodePointer::Storage(ptr), n.candidate_hash)) + .collect(), + NodePointer::Storage(base_node_ptr) => { + let base_node = &self.nodes[base_node_ptr]; + + base_node + .children + .iter() + .filter(|(_, hash)| self.scope.get_pending_availability(&hash).is_none()) + .filter(|(_, hash)| pred(&hash)) + .map(|(ptr, hash)| (*ptr, *hash)) + .collect() + }, + }; + + let mut best_result = accumulator.clone(); + for (child_ptr, child_hash) in children { + accumulator.push(child_hash); + + let result = self.find_backable_chain_inner( + child_ptr, + expected_count, + remaining_count - 1, + &pred, + accumulator, + ); + + accumulator.pop(); + + // Short-circuit the search if we've found the right length. Otherwise, we'll + // search for a max. + // Taking the first best selection doesn't introduce bias or become gameable, + // because `find_ancestor_path` uses a `HashSet` to track the ancestors, which + // makes the order in which ancestors are visited non-deterministic. + if result.len() == expected_count as usize { + return result + } else if best_result.len() < result.len() { + best_result = result; + } + } + + best_result + } + + // Orders the ancestors into a viable path from root to the last one. + // Returns a pointer to the last node in the path. + // We assume that the ancestors form a chain (that the + // av-cores do not back parachain forks), None is returned otherwise. + // If we cannot use all ancestors, stop at the first found hole in the chain. This usually + // translates to a timed out candidate. + fn find_ancestor_path(&self, mut ancestors: Ancestors) -> Option { + // The number of elements in the path we've processed so far. + let mut depth = 0; + let mut last_node = NodePointer::Root; + let mut next_node: Option = Some(NodePointer::Root); + + while let Some(node) = next_node { + if depth > self.scope.max_depth { + return None; + } + + last_node = node; + + next_node = match node { + NodePointer::Root => { + let children = self + .nodes + .iter() + .enumerate() + .take_while(|n| n.1.parent == NodePointer::Root) + .map(|(index, node)| (NodePointer::Storage(index), node.candidate_hash)) + .collect::>(); + + self.find_valid_child(&mut ancestors, children.iter()).ok()? + }, + NodePointer::Storage(ptr) => { + let children = self.nodes.get(ptr).and_then(|n| Some(n.children.iter())); + if let Some(children) = children { + self.find_valid_child(&mut ancestors, children).ok()? + } else { + None + } + }, + }; + + depth += 1; + } + + Some(last_node) + } + + // Find a node from the given iterator which is present in the ancestors + // collection. If there are multiple such nodes, return an error and log a warning. We don't + // accept forks in a parachain to be backed. The supplied ancestors should all form a chain. + // If there is no such node, return None. + fn find_valid_child<'a>( + &self, + ancestors: &'a mut Ancestors, + nodes: impl Iterator + 'a, + ) -> Result, ()> { + let mut possible_children = + nodes.filter_map(|(node_ptr, hash)| match ancestors.remove(&hash) { + true => Some(node_ptr), + false => None, + }); + + // We don't accept forks in a parachain to be backed. The supplied ancestors + // should all form a chain. + let next = possible_children.next(); + if let Some(second_child) = possible_children.next() { + if let (Some(NodePointer::Storage(first_child)), NodePointer::Storage(second_child)) = + (next, second_child) + { + gum::error!( + target: LOG_TARGET, + para_id = ?self.scope.para, + relay_parent = ?self.scope.relay_parent, + "Trying to find new backable candidates for a parachain for which we've backed a fork.\ + This is a bug and the runtime should not have allowed it.\n\ + Backed candidates with the same parent: {}, {}", + self.nodes[*first_child].candidate_hash, + self.nodes[*second_child].candidate_hash, + ); + } + + Err(()) + } else { + Ok(next.copied()) } } @@ -984,6 +1153,17 @@ mod tests { use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations; use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData}; use polkadot_primitives_test_helpers as test_helpers; + use rstest::rstest; + use std::iter; + + impl NodePointer { + fn unwrap_idx(self) -> usize { + match self { + NodePointer::Root => panic!("Unexpected root"), + NodePointer::Storage(index) => index, + } + } + } fn make_constraints( min_relay_parent_number: BlockNumber, @@ -1468,6 +1648,373 @@ mod tests { assert_eq!(tree.nodes[1].parent, NodePointer::Storage(0)); } + #[test] + fn test_find_ancestor_path_and_find_backable_chain_empty_tree() { + let para_id = ParaId::from(5u32); + let relay_parent = Hash::repeat_byte(1); + let required_parent: HeadData = vec![0xff].into(); + let max_depth = 10; + + // Empty tree + let storage = CandidateStorage::new(); + let base_constraints = make_constraints(0, vec![0], required_parent.clone()); + + let relay_parent_info = + RelayChainBlockInfo { number: 0, hash: relay_parent, storage_root: Hash::zero() }; + + let scope = Scope::with_ancestors( + para_id, + relay_parent_info, + base_constraints, + vec![], + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + assert_eq!(tree.candidates().collect::>().len(), 0); + assert_eq!(tree.nodes.len(), 0); + + assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); + assert_eq!(tree.find_backable_chain(Ancestors::new(), 2, |_| true), vec![]); + // Invalid candidate. + let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()), Some(NodePointer::Root)); + assert_eq!(tree.find_backable_chain(ancestors, 2, |_| true), vec![]); + } + + #[rstest] + #[case(true, 13)] + #[case(false, 8)] + // The tree with no cycles looks like: + // Make a tree that looks like this (note that there's no cycle): + // +-(root)-+ + // | | + // +----0---+ 7 + // | | + // 1----+ 5 + // | | + // | | + // 2 6 + // | + // 3 + // | + // 4 + // + // The tree with cycles is the same as the first but has a cycle from 4 back to the state + // produced by 0 (It's bounded by the max_depth + 1). + // +-(root)-+ + // | | + // +----0---+ 7 + // | | + // 1----+ 5 + // | | + // | | + // 2 6 + // | + // 3 + // | + // 4---+ + // | | + // 1 5 + // | + // 2 + // | + // 3 + fn test_find_ancestor_path_and_find_backable_chain( + #[case] has_cycle: bool, + #[case] expected_node_count: usize, + ) { + let para_id = ParaId::from(5u32); + let relay_parent = Hash::repeat_byte(1); + let required_parent: HeadData = vec![0xff].into(); + let max_depth = 7; + let relay_parent_number = 0; + let relay_parent_storage_root = Hash::repeat_byte(69); + + let mut candidates = vec![]; + + // Candidate 0 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + required_parent.clone(), + vec![0].into(), + 0, + )); + // Candidate 1 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![0].into(), + vec![1].into(), + 0, + )); + // Candidate 2 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![1].into(), + vec![2].into(), + 0, + )); + // Candidate 3 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![2].into(), + vec![3].into(), + 0, + )); + // Candidate 4 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![3].into(), + vec![4].into(), + 0, + )); + // Candidate 5 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![0].into(), + vec![5].into(), + 0, + )); + // Candidate 6 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + vec![1].into(), + vec![6].into(), + 0, + )); + // Candidate 7 + candidates.push(make_committed_candidate( + para_id, + relay_parent, + 0, + required_parent.clone(), + vec![7].into(), + 0, + )); + + if has_cycle { + candidates[4] = make_committed_candidate( + para_id, + relay_parent, + 0, + vec![3].into(), + vec![0].into(), // put the cycle here back to the output state of 0. + 0, + ); + } + + let base_constraints = make_constraints(0, vec![0], required_parent.clone()); + let mut storage = CandidateStorage::new(); + + let relay_parent_info = RelayChainBlockInfo { + number: relay_parent_number, + hash: relay_parent, + storage_root: relay_parent_storage_root, + }; + + for (pvd, candidate) in candidates.iter() { + storage.add_candidate(candidate.clone(), pvd.clone()).unwrap(); + } + let candidates = + candidates.into_iter().map(|(_pvd, candidate)| candidate).collect::>(); + let scope = Scope::with_ancestors( + para_id, + relay_parent_info, + base_constraints, + vec![], + max_depth, + vec![], + ) + .unwrap(); + let tree = FragmentTree::populate(scope, &storage); + + assert_eq!(tree.candidates().collect::>().len(), candidates.len()); + assert_eq!(tree.nodes.len(), expected_node_count); + + // Do some common tests on both trees. + { + // No ancestors supplied. + assert_eq!(tree.find_ancestor_path(Ancestors::new()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + // Ancestor which is not part of the tree. Will be ignored. + let ancestors: Ancestors = [CandidateHash::default()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + // A chain fork. + let ancestors: Ancestors = + [(candidates[0].hash()), (candidates[7].hash())].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()), None); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + + // Ancestors which are part of the tree but don't form a path. Will be ignored. + let ancestors: Ancestors = + [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); + assert_eq!(tree.find_ancestor_path(ancestors.clone()).unwrap(), NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Valid ancestors. + let ancestors: Ancestors = [candidates[7].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[7].hash()); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[2].hash()); + assert_eq!( + tree.find_backable_chain(ancestors.clone(), 2, |_| true), + [3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Valid ancestors with candidates which have been omitted due to timeouts + let ancestors: Ancestors = + [candidates[0].hash(), candidates[2].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[0].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 3, |_| true), + [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash(), candidates[3].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[1].hash()); + if has_cycle { + assert_eq!( + tree.find_backable_chain(ancestors, 2, |_| true), + [2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + } else { + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [2, 3, 4].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + } + + let ancestors: Ancestors = + [candidates[1].hash(), candidates[2].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + assert_eq!(res, NodePointer::Root); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [0, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // Requested count is 0. + assert_eq!(tree.find_backable_chain(Ancestors::new(), 0, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash(), candidates[1].hash()] + .into_iter() + .collect(); + assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); + + let ancestors: Ancestors = + [candidates[2].hash(), candidates[0].hash()].into_iter().collect(); + assert_eq!(tree.find_backable_chain(ancestors, 0, |_| true), vec![]); + } + + // Now do some tests only on the tree with cycles + if has_cycle { + // Exceeds the maximum tree depth. 0-1-2-3-4-1-2-3-4, when the tree stops at + // 0-1-2-3-4-1-2-3. + let ancestors: Ancestors = [ + candidates[0].hash(), + candidates[1].hash(), + candidates[2].hash(), + candidates[3].hash(), + candidates[4].hash(), + ] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[4].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 4, |_| true), + [1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // 0-1-2. + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash(), candidates[2].hash()] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[2].hash()); + assert_eq!( + tree.find_backable_chain(ancestors.clone(), 1, |_| true), + [3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + assert_eq!( + tree.find_backable_chain(ancestors, 5, |_| true), + [3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>() + ); + + // 0-1 + let ancestors: Ancestors = + [candidates[0].hash(), candidates[1].hash()].into_iter().collect(); + let res = tree.find_ancestor_path(ancestors.clone()).unwrap(); + let candidate = &tree.nodes[res.unwrap_idx()]; + assert_eq!(candidate.candidate_hash, candidates[1].hash()); + assert_eq!( + tree.find_backable_chain(ancestors, 6, |_| true), + [2, 3, 4, 1, 2, 3].into_iter().map(|i| candidates[i].hash()).collect::>(), + ); + + // For 0-1-2-3-4-5, there's more than 1 way of finding this path in + // the tree. `None` should be returned. The runtime should not have accepted this. + let ancestors: Ancestors = [ + candidates[0].hash(), + candidates[1].hash(), + candidates[2].hash(), + candidates[3].hash(), + candidates[4].hash(), + candidates[5].hash(), + ] + .into_iter() + .collect(); + let res = tree.find_ancestor_path(ancestors.clone()); + assert_eq!(res, None); + assert_eq!(tree.find_backable_chain(ancestors, 1, |_| true), vec![]); + } + } + #[test] fn graceful_cycle_of_0() { let mut storage = CandidateStorage::new(); @@ -1521,6 +2068,25 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + for count in 1..10 { + assert_eq!( + tree.find_backable_chain(Ancestors::new(), count, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize, max_depth + 1)) + .collect::>() + ); + assert_eq!( + tree.find_backable_chain( + [candidate_a_hash].into_iter().collect(), + count - 1, + |_| true + ), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize - 1, max_depth)) + .collect::>() + ); + } } #[test] @@ -1588,6 +2154,42 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + assert_eq!(tree.find_backable_chain(Ancestors::new(), 1, |_| true), vec![candidate_a_hash],); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 2, |_| true), + vec![candidate_a_hash, candidate_b_hash], + ); + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 3, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + assert_eq!( + tree.find_backable_chain([candidate_a_hash].into_iter().collect(), 2, |_| true), + vec![candidate_b_hash, candidate_a_hash], + ); + + assert_eq!( + tree.find_backable_chain(Ancestors::new(), 6, |_| true), + vec![ + candidate_a_hash, + candidate_b_hash, + candidate_a_hash, + candidate_b_hash, + candidate_a_hash + ], + ); + + for count in 3..7 { + assert_eq!( + tree.find_backable_chain( + [candidate_a_hash, candidate_b_hash].into_iter().collect(), + count, + |_| true + ), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + } } #[test] diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index dabcfb80e02ee7943885888aef183a327579e3bd..f5d50fb74faca5fec78c47aa6a653a1a9fe48699 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -35,9 +35,10 @@ use futures::{channel::oneshot, prelude::*}; use polkadot_node_subsystem::{ messages::{ - ChainApiMessage, FragmentTreeMembership, HypotheticalCandidate, - HypotheticalFrontierRequest, IntroduceCandidateRequest, ProspectiveParachainsMessage, - ProspectiveValidationDataRequest, RuntimeApiMessage, RuntimeApiRequest, + Ancestors, ChainApiMessage, FragmentTreeMembership, HypotheticalCandidate, + HypotheticalFrontierRequest, IntroduceCandidateRequest, ParentHeadData, + ProspectiveParachainsMessage, ProspectiveValidationDataRequest, RuntimeApiMessage, + RuntimeApiRequest, }, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, }; @@ -146,12 +147,13 @@ async fn run_iteration( handle_candidate_seconded(view, para, candidate_hash), ProspectiveParachainsMessage::CandidateBacked(para, candidate_hash) => handle_candidate_backed(&mut *ctx, view, para, candidate_hash).await?, - ProspectiveParachainsMessage::GetBackableCandidate( + ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para, - required_path, + count, + ancestors, tx, - ) => answer_get_backable_candidate(&view, relay_parent, para, required_path, tx), + ) => answer_get_backable_candidates(&view, relay_parent, para, count, ancestors, tx), ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx) => answer_hypothetical_frontier_request(&view, request, tx), ProspectiveParachainsMessage::GetTreeMembership(para, candidate, tx) => @@ -290,7 +292,7 @@ async fn handle_active_leaves_update( ) .expect("ancestors are provided in reverse order and correctly; qed"); - gum::debug!( + gum::trace!( target: LOG_TARGET, relay_parent = ?hash, min_relay_parent = scope.earliest_relay_parent().number, @@ -552,12 +554,13 @@ async fn handle_candidate_backed( Ok(()) } -fn answer_get_backable_candidate( +fn answer_get_backable_candidates( view: &View, relay_parent: Hash, para: ParaId, - required_path: Vec, - tx: oneshot::Sender>, + count: u32, + ancestors: Ancestors, + tx: oneshot::Sender>, ) { let data = match view.active_leaves.get(&relay_parent) { None => { @@ -568,7 +571,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive relay-parent." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(d) => d, @@ -583,7 +586,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive para." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(tree) => tree, @@ -598,30 +601,49 @@ fn answer_get_backable_candidate( "No candidate storage for active para", ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(s) => s, }; - let Some(child_hash) = - tree.select_child(&required_path, |candidate| storage.is_backed(candidate)) - else { - let _ = tx.send(None); - return - }; - let Some(candidate_relay_parent) = storage.relay_parent_by_candidate_hash(&child_hash) else { - gum::error!( + let backable_candidates: Vec<_> = tree + .find_backable_chain(ancestors.clone(), count, |candidate| storage.is_backed(candidate)) + .into_iter() + .filter_map(|child_hash| { + storage.relay_parent_by_candidate_hash(&child_hash).map_or_else( + || { + gum::error!( + target: LOG_TARGET, + ?child_hash, + para_id = ?para, + "Candidate is present in fragment tree but not in candidate's storage!", + ); + None + }, + |parent_hash| Some((child_hash, parent_hash)), + ) + }) + .collect(); + + if backable_candidates.is_empty() { + gum::trace!( target: LOG_TARGET, - ?child_hash, + ?ancestors, para_id = ?para, - "Candidate is present in fragment tree but not in candidate's storage!", + %relay_parent, + "Could not find any backable candidate", ); - let _ = tx.send(None); - return - }; + } else { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?backable_candidates, + "Found backable candidates", + ); + } - let _ = tx.send(Some((child_hash, candidate_relay_parent))); + let _ = tx.send(backable_candidates); } fn answer_hypothetical_frontier_request( @@ -743,8 +765,14 @@ fn answer_prospective_validation_data_request( Some(s) => s, }; - let mut head_data = - storage.head_data_by_hash(&request.parent_head_data_hash).map(|x| x.clone()); + let (mut head_data, parent_head_data_hash) = match request.parent_head_data { + ParentHeadData::OnlyHash(parent_head_data_hash) => ( + storage.head_data_by_hash(&parent_head_data_hash).map(|x| x.clone()), + parent_head_data_hash, + ), + ParentHeadData::WithData { head_data, hash } => (Some(head_data), hash), + }; + let mut relay_parent_info = None; let mut max_pov_size = None; @@ -762,7 +790,7 @@ fn answer_prospective_validation_data_request( } if head_data.is_none() { let required_parent = &fragment_tree.scope().base_constraints().required_parent; - if required_parent.hash() == request.parent_head_data_hash { + if required_parent.hash() == parent_head_data_hash { head_data = Some(required_parent.clone()); } } diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index d2cd23fe95fc1d8638238c8e30b5227da7e883a0..0e0079c02bbea27be88fc2dc9e030cf1a3311e89 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -19,7 +19,7 @@ use assert_matches::assert_matches; use polkadot_node_subsystem::{ errors::RuntimeApiError, messages::{ - AllMessages, HypotheticalFrontierRequest, ProspectiveParachainsMessage, + AllMessages, HypotheticalFrontierRequest, ParentHeadData, ProspectiveParachainsMessage, ProspectiveValidationDataRequest, }, }; @@ -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 @@ -406,25 +403,24 @@ async fn get_membership( assert_eq!(resp, expected_membership_response); } -async fn get_backable_candidate( +async fn get_backable_candidates( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, para_id: ParaId, - required_path: Vec, - expected_result: Option<(CandidateHash, Hash)>, + ancestors: Ancestors, + count: u32, + expected_result: Vec<(CandidateHash, Hash)>, ) { let (tx, rx) = oneshot::channel(); virtual_overseer .send(overseer::FromOrchestra::Communication { - msg: ProspectiveParachainsMessage::GetBackableCandidate( - leaf.hash, - para_id, - required_path, - tx, + msg: ProspectiveParachainsMessage::GetBackableCandidates( + leaf.hash, para_id, count, ancestors, tx, ), }) .await; let resp = rx.await.unwrap(); + assert_eq!(resp.len(), expected_result.len()); assert_eq!(resp, expected_result); } @@ -472,7 +468,7 @@ async fn get_pvd( let request = ProspectiveValidationDataRequest { para_id, candidate_relay_parent, - parent_head_data_hash: parent_head_data.hash(), + parent_head_data: ParentHeadData::OnlyHash(parent_head_data.hash()), }; let (tx, rx) = oneshot::channel(); virtual_overseer @@ -852,9 +848,9 @@ fn check_candidate_on_multiple_forks() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } -// Backs some candidates and tests `GetBackableCandidate`. +// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate. #[test] -fn check_backable_query() { +fn check_backable_query_single_candidate() { let test_state = TestState::default(); let view = test_harness(|mut virtual_overseer| async move { // Leaf A @@ -899,12 +895,31 @@ fn check_backable_query() { introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![], + ) + .await; + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), - vec![candidate_hash_a], - None, + vec![candidate_hash_a].into_iter().collect(), + 0, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::new(), + 0, + vec![], ) .await; @@ -913,12 +928,13 @@ fn check_backable_query() { second_candidate(&mut virtual_overseer, candidate_b.clone()).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), - vec![candidate_hash_a], - None, + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![], ) .await; @@ -926,31 +942,54 @@ fn check_backable_query() { back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; + // Should not get any backable candidates for the other para. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + Ancestors::new(), + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![], + ) + .await; + // Get backable candidate. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), - vec![], - Some((candidate_hash_a, leaf_a.hash)), + Ancestors::new(), + 1, + vec![(candidate_hash_a, leaf_a.hash)], ) .await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), - vec![candidate_hash_a], - Some((candidate_hash_b, leaf_a.hash)), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![(candidate_hash_b, leaf_a.hash)], ) .await; - // Should not get anything at the wrong path. - get_backable_candidate( + // Wrong path + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), - vec![candidate_hash_b], - None, + vec![candidate_hash_b].into_iter().collect(), + 1, + vec![(candidate_hash_a, leaf_a.hash)], ) .await; @@ -964,6 +1003,391 @@ fn check_backable_query() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } +// Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. +#[test] +fn check_backable_query_multiple_candidates() { + macro_rules! make_and_back_candidate { + ($test_state:ident, $virtual_overseer:ident, $leaf:ident, $parent:expr, $index:expr) => {{ + let (mut candidate, pvd) = make_candidate( + $leaf.hash, + $leaf.number, + 1.into(), + $parent.commitments.head_data.clone(), + HeadData(vec![$index]), + $test_state.validation_code_hash, + ); + // Set a field to make this candidate unique. + candidate.descriptor.para_head = Hash::from_low_u64_le($index); + let candidate_hash = candidate.hash(); + introduce_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await; + second_candidate(&mut $virtual_overseer, candidate.clone()).await; + back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await; + + (candidate, candidate_hash) + }}; + } + + // Parachain 1 looks like this: + // +---A----+ + // | | + // +----B---+ C + // | | | | + // D E F H + // | | + // G I + // | + // J + { + let test_state = TestState::default(); + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Hash::from_low_u64_be(130), + para_data: vec![ + (1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate A + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + let candidate_hash_a = candidate_a.hash(); + introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; + second_candidate(&mut virtual_overseer, candidate_a.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; + + let (candidate_b, candidate_hash_b) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 2); + let (candidate_c, candidate_hash_c) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 3); + let (_candidate_d, candidate_hash_d) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4); + let (_candidate_e, candidate_hash_e) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5); + let (candidate_f, candidate_hash_f) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6); + let (_candidate_g, candidate_hash_g) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7); + let (candidate_h, candidate_hash_h) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8); + let (candidate_i, candidate_hash_i) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9); + let (_candidate_j, candidate_hash_j) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + Ancestors::new(), + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + Ancestors::new(), + 5, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![], + ) + .await; + + // Test various scenarios with various counts. + + // empty required_path + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::new(), + 1, + vec![(candidate_hash_a, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::new(), + 4, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + } + + // required path of 1 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![(candidate_hash_b, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a].into_iter().collect(), + 3, + vec![ + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 5..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a].into_iter().collect(), + count, + vec![ + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // required path of 2 and higher + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_i, candidate_hash_h, candidate_hash_c] + .into_iter() + .collect(), + 1, + vec![(candidate_hash_j, leaf_a.hash)], + ) + .await; + + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b].into_iter().collect(), + 1, + vec![(candidate_hash_d, leaf_a.hash)], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 4..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c].into_iter().collect(), + count, + vec![ + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // No more candidates in any chain. + { + for count in 1..4 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b, candidate_hash_e] + .into_iter() + .collect(), + count, + vec![], + ) + .await; + + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![ + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + ] + .into_iter() + .collect(), + count, + vec![], + ) + .await; + } + } + + // Wrong paths. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b].into_iter().collect(), + 1, + vec![(candidate_hash_a, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_f].into_iter().collect(), + 3, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_b, leaf_a.hash), + (candidate_hash_d, leaf_a.hash), + ], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_h].into_iter().collect(), + 4, + vec![ + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_e, candidate_hash_h].into_iter().collect(), + 2, + vec![(candidate_hash_a, leaf_a.hash), (candidate_hash_b, leaf_a.hash)], + ) + .await; + + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c, candidate_hash_d].into_iter().collect(), + 2, + vec![(candidate_hash_h, leaf_a.hash), (candidate_hash_i, leaf_a.hash)], + ) + .await; + + // Parachain fork. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b, candidate_hash_c].into_iter().collect(), + 1, + vec![], + ) + .await; + + // Non-existent candidate. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, CandidateHash(Hash::from_low_u64_be(100))] + .into_iter() + .collect(), + 2, + vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)], + ) + .await; + + // Requested count is zero. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::new(), + 0, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a].into_iter().collect(), + 0, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b].into_iter().collect(), + 0, + vec![], + ) + .await; + + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); + assert_eq!(view.candidate_storage.len(), 2); + // 10 candidates and 7 parents on para 1. + assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10)); + assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); + } +} + // Test depth query. #[test] fn check_hypothetical_frontier_query() { @@ -1260,8 +1684,8 @@ fn check_pvd_query() { assert_eq!(view.candidate_storage.len(), 2); } -// Test simultaneously activating and deactivating leaves, and simultaneously deactivating multiple -// leaves. +// Test simultaneously activating and deactivating leaves, and simultaneously deactivating +// multiple leaves. #[test] fn correctly_updates_leaves() { let test_state = TestState::default(); @@ -1451,12 +1875,13 @@ fn persists_pending_availability_candidate() { second_candidate(&mut virtual_overseer, candidate_b.clone()).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_b, para_id, - vec![candidate_hash_a], - Some((candidate_hash_b, leaf_b_hash)), + vec![candidate_hash_a].into_iter().collect(), + 1, + vec![(candidate_hash_b, leaf_b_hash)], ) .await; @@ -1515,12 +1940,13 @@ fn backwards_compatible() { second_candidate(&mut virtual_overseer, candidate_a.clone()).await; back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, para_id, - vec![], - Some((candidate_hash_a, candidate_relay_parent)), + Ancestors::new(), + 1, + vec![(candidate_hash_a, candidate_relay_parent)], ) .await; @@ -1540,7 +1966,15 @@ fn backwards_compatible() { ) .await; - get_backable_candidate(&mut virtual_overseer, &leaf_b, para_id, vec![], None).await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_b, + para_id, + Ancestors::new(), + 1, + vec![], + ) + .await; virtual_overseer }); @@ -1567,13 +2001,13 @@ fn uses_ancestry_only_within_session() { .await; assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) - ) if parent == hash => { - tx.send(Ok(AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: ancestry_len })).unwrap(); - } - ); + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) + ) if parent == hash => { + tx.send(Ok(AsyncBackingParams { max_candidate_depth: 0, allowed_ancestry_len: ancestry_len + })).unwrap(); } + ); assert_matches!( virtual_overseer.recv().await, diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 05ea92caa976d99c9274313821cfdce212d74b4b..2a09e2b5b2cc83a0596c6f0ae153e66f8f6a1e66 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -1,24 +1,30 @@ [package] name = "polkadot-node-core-provisioner" -version = "1.0.0" +version = "7.0.0" +description = "Responsible for assembling a relay chain block from a set of available parachain candidates" 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" gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } polkadot-primitives = { path = "../../../primitives" } polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } futures-timer = "3.0.2" fatality = "0.0.6" +schnellru = "0.2.1" [dev-dependencies] sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } +rstest = "0.18.2" 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/provisioner/src/error.rs b/polkadot/node/core/provisioner/src/error.rs index 376d69f276fc892e92828bf994b29f847fae31d0..aae3234c3cc49d19bdaf20b87fa71cf3f3276bfe 100644 --- a/polkadot/node/core/provisioner/src/error.rs +++ b/polkadot/node/core/provisioner/src/error.rs @@ -44,14 +44,17 @@ pub enum Error { #[error("failed to get block number")] CanceledBlockNumber(#[source] oneshot::Canceled), + #[error("failed to get session index")] + CanceledSessionIndex(#[source] oneshot::Canceled), + #[error("failed to get backed candidates")] CanceledBackedCandidates(#[source] oneshot::Canceled), #[error("failed to get votes on dispute")] CanceledCandidateVotes(#[source] oneshot::Canceled), - #[error("failed to get backable candidate from prospective parachains")] - CanceledBackableCandidate(#[source] oneshot::Canceled), + #[error("failed to get backable candidates from prospective parachains")] + CanceledBackableCandidates(#[source] oneshot::Canceled), #[error(transparent)] ChainApi(#[from] ChainApiError), @@ -71,11 +74,6 @@ pub enum Error { #[error("failed to send return message with Inherents")] InherentDataReturnChannel, - #[error( - "backed candidate does not correspond to selected candidate; check logic in provisioner" - )] - BackedCandidateOrderingProblem, - #[fatal] #[error("Failed to spawn background task")] FailedToSpawnBackgroundTask, diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index 8893bdc6549d28c74220950217698d120cf3370e..c9ed873d3c25ae1a0fbe590a51412839e8105634 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -24,26 +24,29 @@ use futures::{ channel::oneshot, future::BoxFuture, prelude::*, stream::FuturesUnordered, FutureExt, }; use futures_timer::Delay; +use schnellru::{ByLength, LruMap}; use polkadot_node_subsystem::{ jaeger, messages::{ - CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, ProvisionableData, - ProvisionerInherentData, ProvisionerMessage, RuntimeApiMessage, RuntimeApiRequest, + Ancestors, CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, + ProvisionableData, ProvisionerInherentData, ProvisionerMessage, RuntimeApiRequest, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan, - RuntimeApiError, SpawnedSubsystem, SubsystemError, + SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_util::{ - request_availability_cores, request_persisted_validation_data, - runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, + has_required_runtime, request_availability_cores, request_persisted_validation_data, + request_session_index_for_child, + runtime::{prospective_parachains_mode, request_node_features, ProspectiveParachainsMode}, TimeoutExt, }; use polkadot_primitives::{ - BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt, CoreState, Hash, Id as ParaId, - OccupiedCoreAssumption, SignedAvailabilityBitfield, ValidatorIndex, + vstaging::{node_features::FeatureIndex, NodeFeatures}, + BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, CoreState, Hash, + Id as ParaId, OccupiedCoreAssumption, SessionIndex, SignedAvailabilityBitfield, ValidatorIndex, }; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; mod disputes; mod error; @@ -77,11 +80,18 @@ impl ProvisionerSubsystem { } } +/// Per-session info we need for the provisioner subsystem. +pub struct PerSession { + prospective_parachains_mode: ProspectiveParachainsMode, + elastic_scaling_mvp: bool, +} + /// A per-relay-parent state for the provisioning subsystem. pub struct PerRelayParent { leaf: ActivatedLeaf, backed_candidates: Vec, prospective_parachains_mode: ProspectiveParachainsMode, + elastic_scaling_mvp: bool, signed_bitfields: Vec, is_inherent_ready: bool, awaiting_inherent: Vec>, @@ -89,13 +99,14 @@ pub struct PerRelayParent { } impl PerRelayParent { - fn new(leaf: ActivatedLeaf, prospective_parachains_mode: ProspectiveParachainsMode) -> Self { + fn new(leaf: ActivatedLeaf, per_session: &PerSession) -> Self { let span = PerLeafSpan::new(leaf.span.clone(), "provisioner"); Self { leaf, backed_candidates: Vec::new(), - prospective_parachains_mode, + prospective_parachains_mode: per_session.prospective_parachains_mode, + elastic_scaling_mvp: per_session.elastic_scaling_mvp, signed_bitfields: Vec::new(), is_inherent_ready: false, awaiting_inherent: Vec::new(), @@ -124,10 +135,17 @@ impl ProvisionerSubsystem { async fn run(mut ctx: Context, metrics: Metrics) -> FatalResult<()> { let mut inherent_delays = InherentDelays::new(); let mut per_relay_parent = HashMap::new(); + let mut per_session = LruMap::new(ByLength::new(2)); loop { - let result = - run_iteration(&mut ctx, &mut per_relay_parent, &mut inherent_delays, &metrics).await; + let result = run_iteration( + &mut ctx, + &mut per_relay_parent, + &mut per_session, + &mut inherent_delays, + &metrics, + ) + .await; match result { Ok(()) => break, @@ -142,6 +160,7 @@ async fn run(mut ctx: Context, metrics: Metrics) -> FatalResult<()> { async fn run_iteration( ctx: &mut Context, per_relay_parent: &mut HashMap, + per_session: &mut LruMap, inherent_delays: &mut InherentDelays, metrics: &Metrics, ) -> Result<(), Error> { @@ -151,7 +170,7 @@ async fn run_iteration( // Map the error to ensure that the subsystem exits when the overseer is gone. match from_overseer.map_err(Error::OverseerExited)? { FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => - handle_active_leaves_update(ctx.sender(), update, per_relay_parent, inherent_delays).await?, + handle_active_leaves_update(ctx.sender(), update, per_relay_parent, per_session, inherent_delays).await?, FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {}, FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), FromOrchestra::Communication { msg } => { @@ -183,6 +202,7 @@ async fn handle_active_leaves_update( sender: &mut impl overseer::ProvisionerSenderTrait, update: ActiveLeavesUpdate, per_relay_parent: &mut HashMap, + per_session: &mut LruMap, inherent_delays: &mut InherentDelays, ) -> Result<(), Error> { gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate"); @@ -191,10 +211,31 @@ async fn handle_active_leaves_update( } if let Some(leaf) = update.activated { + let session_index = request_session_index_for_child(leaf.hash, sender) + .await + .await + .map_err(Error::CanceledSessionIndex)??; + if per_session.get(&session_index).is_none() { + let prospective_parachains_mode = + prospective_parachains_mode(sender, leaf.hash).await?; + let elastic_scaling_mvp = request_node_features(leaf.hash, session_index, sender) + .await? + .unwrap_or(NodeFeatures::EMPTY) + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + per_session.insert( + session_index, + PerSession { prospective_parachains_mode, elastic_scaling_mvp }, + ); + } + + let session_info = per_session.get(&session_index).expect("Just inserted"); + gum::trace!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Adding delay"); - let prospective_parachains_mode = prospective_parachains_mode(sender, leaf.hash).await?; let delay_fut = Delay::new(PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed(); - per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf, prospective_parachains_mode)); + per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf, session_info)); inherent_delays.push(delay_fut); } @@ -253,6 +294,7 @@ async fn send_inherent_data_bg( let signed_bitfields = per_relay_parent.signed_bitfields.clone(); let backed_candidates = per_relay_parent.backed_candidates.clone(); let mode = per_relay_parent.prospective_parachains_mode; + let elastic_scaling_mvp = per_relay_parent.elastic_scaling_mvp; let span = per_relay_parent.span.child("req-inherent-data"); let mut sender = ctx.sender().clone(); @@ -272,6 +314,7 @@ async fn send_inherent_data_bg( &signed_bitfields, &backed_candidates, mode, + elastic_scaling_mvp, return_senders, &mut sender, &metrics, @@ -383,6 +426,7 @@ async fn send_inherent_data( bitfields: &[SignedAvailabilityBitfield], candidates: &[CandidateReceipt], prospective_parachains_mode: ProspectiveParachainsMode, + elastic_scaling_mvp: bool, return_senders: Vec>, from_job: &mut impl overseer::ProvisionerSenderTrait, metrics: &Metrics, @@ -434,6 +478,7 @@ async fn send_inherent_data( &bitfields, candidates, prospective_parachains_mode, + elastic_scaling_mvp, leaf.hash, from_job, ) @@ -558,6 +603,8 @@ async fn select_candidate_hashes_from_tracked( let mut selected_candidates = Vec::with_capacity(candidates.len().min(availability_cores.len())); + let mut selected_parachains = + HashSet::with_capacity(candidates.len().min(availability_cores.len())); gum::debug!( target: LOG_TARGET, @@ -591,6 +638,12 @@ async fn select_candidate_hashes_from_tracked( CoreState::Free => continue, }; + if selected_parachains.contains(&scheduled_core.para_id) { + // We already picked a candidate for this parachain. Elastic scaling only works with + // prospective parachains mode. + continue + } + let validation_data = match request_persisted_validation_data( relay_parent, scheduled_core.para_id, @@ -624,6 +677,7 @@ async fn select_candidate_hashes_from_tracked( "Selected candidate receipt", ); + selected_parachains.insert(candidate.descriptor.para_id); selected_candidates.push((candidate_hash, candidate.descriptor.relay_parent)); } } @@ -637,63 +691,93 @@ async fn select_candidate_hashes_from_tracked( /// Should be called when prospective parachains are enabled. async fn request_backable_candidates( availability_cores: &[CoreState], + elastic_scaling_mvp: bool, bitfields: &[SignedAvailabilityBitfield], relay_parent: Hash, sender: &mut impl overseer::ProvisionerSenderTrait, ) -> Result, Error> { let block_number = get_block_number_under_construction(relay_parent, sender).await?; - let mut selected_candidates = Vec::with_capacity(availability_cores.len()); + // Record how many cores are scheduled for each paraid. Use a BTreeMap because + // we'll need to iterate through them. + let mut scheduled_cores: BTreeMap = BTreeMap::new(); + // The on-chain ancestors of a para present in availability-cores. + let mut ancestors: HashMap = + HashMap::with_capacity(availability_cores.len()); for (core_idx, core) in availability_cores.iter().enumerate() { - let (para_id, required_path) = match core { + let core_idx = CoreIndex(core_idx as u32); + match core { CoreState::Scheduled(scheduled_core) => { - // The core is free, pick the first eligible candidate from - // the fragment tree. - (scheduled_core.para_id, Vec::new()) + *scheduled_cores.entry(scheduled_core.para_id).or_insert(0) += 1; }, CoreState::Occupied(occupied_core) => { - if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability) - { + let is_available = bitfields_indicate_availability( + core_idx.0 as usize, + bitfields, + &occupied_core.availability, + ); + + if is_available { + ancestors + .entry(occupied_core.para_id()) + .or_default() + .insert(occupied_core.candidate_hash); + if let Some(ref scheduled_core) = occupied_core.next_up_on_available { - // The candidate occupying the core is available, choose its - // child in the fragment tree. - // - // TODO: doesn't work for on-demand parachains. We lean hard on the - // assumption that cores are fixed to specific parachains within a session. - // https://github.com/paritytech/polkadot/issues/5492 - (scheduled_core.para_id, vec![occupied_core.candidate_hash]) - } else { - continue - } - } else { - if occupied_core.time_out_at != block_number { - continue + // Request a new backable candidate for the newly scheduled para id. + *scheduled_cores.entry(scheduled_core.para_id).or_insert(0) += 1; } + } else if occupied_core.time_out_at <= block_number { + // Timed out before being available. + if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out { // Candidate's availability timed out, practically same as scheduled. - (scheduled_core.para_id, Vec::new()) - } else { - continue + *scheduled_cores.entry(scheduled_core.para_id).or_insert(0) += 1; } + } else { + // Not timed out and not available. + ancestors + .entry(occupied_core.para_id()) + .or_default() + .insert(occupied_core.candidate_hash); } }, CoreState::Free => continue, }; + } - let response = get_backable_candidate(relay_parent, para_id, required_path, sender).await?; + let mut selected_candidates: Vec<(CandidateHash, Hash)> = + Vec::with_capacity(availability_cores.len()); - match response { - Some((hash, relay_parent)) => selected_candidates.push((hash, relay_parent)), - None => { - gum::debug!( - target: LOG_TARGET, - leaf_hash = ?relay_parent, - core = core_idx, - "No backable candidate returned by prospective parachains", - ); - }, + for (para_id, core_count) in scheduled_cores { + let para_ancestors = ancestors.remove(¶_id).unwrap_or_default(); + + // If elastic scaling MVP is disabled, only allow one candidate per parachain. + if !elastic_scaling_mvp && core_count > 1 { + continue + } + + let response = get_backable_candidates( + relay_parent, + para_id, + para_ancestors, + core_count as u32, + sender, + ) + .await?; + + if response.is_empty() { + gum::debug!( + target: LOG_TARGET, + leaf_hash = ?relay_parent, + ?para_id, + "No backable candidate returned by prospective parachains", + ); + continue } + + selected_candidates.extend(response.into_iter().take(core_count)); } Ok(selected_candidates) @@ -706,6 +790,7 @@ async fn select_candidates( bitfields: &[SignedAvailabilityBitfield], candidates: &[CandidateReceipt], prospective_parachains_mode: ProspectiveParachainsMode, + elastic_scaling_mvp: bool, relay_parent: Hash, sender: &mut impl overseer::ProvisionerSenderTrait, ) -> Result, Error> { @@ -715,7 +800,14 @@ async fn select_candidates( let selected_candidates = match prospective_parachains_mode { ProspectiveParachainsMode::Enabled { .. } => - request_backable_candidates(availability_cores, bitfields, relay_parent, sender).await?, + request_backable_candidates( + availability_cores, + elastic_scaling_mvp, + bitfields, + relay_parent, + sender, + ) + .await?, ProspectiveParachainsMode::Disabled => select_candidate_hashes_from_tracked( availability_cores, @@ -726,6 +818,7 @@ async fn select_candidates( ) .await?, }; + gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates"); // now get the backed candidates corresponding to these candidate receipts let (tx, rx) = oneshot::channel(); @@ -737,28 +830,10 @@ async fn select_candidates( gum::trace!(target: LOG_TARGET, leaf_hash=?relay_parent, "Got {} backed candidates", candidates.len()); - // `selected_candidates` is generated in ascending order by core index, and - // `GetBackedCandidates` _should_ preserve that property, but let's just make sure. - // - // We can't easily map from `BackedCandidate` to `core_idx`, but we know that every selected - // candidate maps to either 0 or 1 backed candidate, and the hashes correspond. Therefore, by - // checking them in order, we can ensure that the backed candidates are also in order. - let mut backed_idx = 0; - for selected in selected_candidates { - if selected.0 == - candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash() - { - backed_idx += 1; - } - } - if candidates.len() != backed_idx { - Err(Error::BackedCandidateOrderingProblem)?; - } - // keep only one candidate with validation code. let mut with_validation_code = false; candidates.retain(|c| { - if c.candidate.commitments.new_validation_code.is_some() { + if c.candidate().commitments.new_validation_code.is_some() { if with_validation_code { return false } @@ -796,25 +871,27 @@ async fn get_block_number_under_construction( } } -/// Requests backable candidate from Prospective Parachains based on -/// the given path in the fragment tree. -async fn get_backable_candidate( +/// Requests backable candidates from Prospective Parachains based on +/// the given ancestors in the fragment tree. The ancestors may not be ordered. +async fn get_backable_candidates( relay_parent: Hash, para_id: ParaId, - required_path: Vec, + ancestors: Ancestors, + count: u32, sender: &mut impl overseer::ProvisionerSenderTrait, -) -> Result, Error> { +) -> Result, Error> { let (tx, rx) = oneshot::channel(); sender - .send_message(ProspectiveParachainsMessage::GetBackableCandidate( + .send_message(ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para_id, - required_path, + count, + ancestors, tx, )) .await; - rx.await.map_err(Error::CanceledBackableCandidate) + rx.await.map_err(Error::CanceledBackableCandidates) } /// The availability bitfield for a given core is the transpose @@ -856,56 +933,3 @@ fn bitfields_indicate_availability( 3 * availability.count_ones() >= 2 * availability.len() } - -// If we have to be absolutely precise here, this method gets the version of the `ParachainHost` -// api. For brevity we'll just call it 'runtime version'. -async fn has_required_runtime( - sender: &mut impl overseer::ProvisionerSenderTrait, - relay_parent: Hash, - required_runtime_version: u32, -) -> bool { - gum::trace!(target: LOG_TARGET, ?relay_parent, "Fetching ParachainHost runtime api version"); - - let (tx, rx) = oneshot::channel(); - sender - .send_message(RuntimeApiMessage::Request(relay_parent, RuntimeApiRequest::Version(tx))) - .await; - - match rx.await { - Result::Ok(Ok(runtime_version)) => { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?runtime_version, - ?required_runtime_version, - "Fetched ParachainHost runtime api version" - ); - runtime_version >= required_runtime_version - }, - Result::Ok(Err(RuntimeApiError::Execution { source: error, .. })) => { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - ?error, - "Execution error while fetching ParachainHost runtime api version" - ); - false - }, - Result::Ok(Err(RuntimeApiError::NotSupported { .. })) => { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - "NotSupported error while fetching ParachainHost runtime api version" - ); - false - }, - Result::Err(_) => { - gum::trace!( - target: LOG_TARGET, - ?relay_parent, - "Cancelled error while fetching ParachainHost runtime api version" - ); - false - }, - } -} diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs index 1d7bdfcfcb89c251cfdeb9c6669a5b07e8b05985..bdb4f85f4009bdfff879cefcb8928899c5b85c81 100644 --- a/polkadot/node/core/provisioner/src/tests.rs +++ b/polkadot/node/core/provisioner/src/tests.rs @@ -22,6 +22,9 @@ use polkadot_primitives::{OccupiedCore, ScheduledCore}; const MOCK_GROUP_SIZE: usize = 5; pub fn occupied_core(para_id: u32) -> CoreState { + let mut candidate_descriptor = dummy_candidate_descriptor(dummy_hash()); + candidate_descriptor.para_id = para_id.into(); + CoreState::Occupied(OccupiedCore { group_responsible: para_id.into(), next_up_on_available: None, @@ -29,7 +32,7 @@ pub fn occupied_core(para_id: u32) -> CoreState { time_out_at: 200_u32, next_up_on_time_out: None, availability: bitvec![u8, bitvec::order::Lsb0; 0; 32], - candidate_descriptor: dummy_candidate_descriptor(dummy_hash()), + candidate_descriptor, candidate_hash: Default::default(), }) } @@ -254,10 +257,56 @@ mod select_candidates { use polkadot_primitives::{ BlockNumber, CandidateCommitments, CommittedCandidateReceipt, PersistedValidationData, }; + use rstest::rstest; const BLOCK_UNDER_PRODUCTION: BlockNumber = 128; - // For test purposes, we always return this set of availability cores: + fn dummy_candidate_template() -> CandidateReceipt { + let empty_hash = PersistedValidationData::::default().hash(); + + let mut descriptor_template = dummy_candidate_descriptor(dummy_hash()); + descriptor_template.persisted_validation_data_hash = empty_hash; + CandidateReceipt { + descriptor: descriptor_template, + commitments_hash: CandidateCommitments::default().hash(), + } + } + + fn make_candidates( + core_count: usize, + expected_backed_indices: Vec, + ) -> (Vec, Vec) { + let candidate_template = dummy_candidate_template(); + let candidates: Vec<_> = std::iter::repeat(candidate_template) + .take(core_count) + .enumerate() + .map(|(idx, mut candidate)| { + candidate.descriptor.para_id = idx.into(); + candidate + }) + .collect(); + + let expected_backed = expected_backed_indices + .iter() + .map(|&idx| candidates[idx].clone()) + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor.clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) + }) + .collect(); + let candidate_hashes = candidates.into_iter().map(|c| c.hash()).collect(); + + (candidate_hashes, expected_backed) + } + + // For testing only one core assigned to a parachain, we return this set of availability cores: // // [ // 0: Free, @@ -273,7 +322,73 @@ mod select_candidates { // 10: Occupied(both next_up set, not available, timeout), // 11: Occupied(next_up_on_available and available, but different successor para_id) // ] - fn mock_availability_cores() -> Vec { + fn mock_availability_cores_one_per_para() -> Vec { + use std::ops::Not; + use CoreState::{Free, Scheduled}; + + vec![ + // 0: Free, + Free, + // 1: Scheduled(default), + Scheduled(scheduled_core(1)), + // 2: Occupied(no next_up set), + occupied_core(2), + // 3: Occupied(next_up_on_available set but not available), + build_occupied_core(3, |core| { + core.next_up_on_available = Some(scheduled_core(3)); + }), + // 4: Occupied(next_up_on_available set and available), + build_occupied_core(4, |core| { + core.next_up_on_available = Some(scheduled_core(4)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(41)); + }), + // 5: Occupied(next_up_on_time_out set but not timeout), + build_occupied_core(5, |core| { + core.next_up_on_time_out = Some(scheduled_core(5)); + }), + // 6: Occupied(next_up_on_time_out set and timeout but available), + build_occupied_core(6, |core| { + core.next_up_on_time_out = Some(scheduled_core(6)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.availability = core.availability.clone().not(); + }), + // 7: Occupied(next_up_on_time_out set and timeout and not available), + build_occupied_core(7, |core| { + core.next_up_on_time_out = Some(scheduled_core(7)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(71)); + }), + // 8: Occupied(both next_up set, available), + build_occupied_core(8, |core| { + core.next_up_on_available = Some(scheduled_core(8)); + core.next_up_on_time_out = Some(scheduled_core(8)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(81)); + }), + // 9: Occupied(both next_up set, not available, no timeout), + build_occupied_core(9, |core| { + core.next_up_on_available = Some(scheduled_core(9)); + core.next_up_on_time_out = Some(scheduled_core(9)); + }), + // 10: Occupied(both next_up set, not available, timeout), + build_occupied_core(10, |core| { + core.next_up_on_available = Some(scheduled_core(10)); + core.next_up_on_time_out = Some(scheduled_core(10)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(101)); + }), + // 11: Occupied(next_up_on_available and available, but different successor para_id) + build_occupied_core(11, |core| { + core.next_up_on_available = Some(scheduled_core(12)); + core.availability = core.availability.clone().not(); + }), + ] + } + + // For test purposes with multiple possible cores assigned to a para, we always return this set + // of availability cores: + fn mock_availability_cores_multiple_per_para() -> Vec { use std::ops::Not; use CoreState::{Free, Scheduled}; @@ -292,6 +407,7 @@ mod select_candidates { build_occupied_core(4, |core| { core.next_up_on_available = Some(scheduled_core(4)); core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(41)); }), // 5: Occupied(next_up_on_time_out set but not timeout), build_occupied_core(5, |core| { @@ -307,12 +423,14 @@ mod select_candidates { build_occupied_core(7, |core| { core.next_up_on_time_out = Some(scheduled_core(7)); core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(71)); }), // 8: Occupied(both next_up set, available), build_occupied_core(8, |core| { core.next_up_on_available = Some(scheduled_core(8)); core.next_up_on_time_out = Some(scheduled_core(8)); core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(81)); }), // 9: Occupied(both next_up set, not available, no timeout), build_occupied_core(9, |core| { @@ -324,29 +442,133 @@ mod select_candidates { core.next_up_on_available = Some(scheduled_core(10)); core.next_up_on_time_out = Some(scheduled_core(10)); core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(101)); }), // 11: Occupied(next_up_on_available and available, but different successor para_id) build_occupied_core(11, |core| { core.next_up_on_available = Some(scheduled_core(12)); core.availability = core.availability.clone().not(); }), + // 12-14: Occupied(next_up_on_available and available, same para_id). + build_occupied_core(12, |core| { + core.next_up_on_available = Some(scheduled_core(12)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(121)); + }), + build_occupied_core(12, |core| { + core.next_up_on_available = Some(scheduled_core(12)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(122)); + }), + build_occupied_core(12, |core| { + core.next_up_on_available = Some(scheduled_core(12)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(123)); + }), + // 15: Scheduled on same para_id as 12-14. + Scheduled(scheduled_core(12)), + // 16: Occupied(13, no next_up set, not available) + build_occupied_core(13, |core| { + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(131)); + }), + // 17: Occupied(13, no next_up set, available) + build_occupied_core(13, |core| { + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(132)); + }), + // 18: Occupied(13, next_up_on_available set to 13 but not available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(13)); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(133)); + }), + // 19: Occupied(13, next_up_on_available set to 13 and available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(13)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(134)); + }), + // 20: Occupied(13, next_up_on_time_out set to 13 but not timeout) + build_occupied_core(13, |core| { + core.next_up_on_time_out = Some(scheduled_core(13)); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(135)); + }), + // 21: Occupied(13, next_up_on_available set to 14 and available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(14)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(136)); + }), + // 22: Occupied(13, next_up_on_available set to 14 but not available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(14)); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(137)); + }), + // 23: Occupied(13, both next_up set to 14, available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(14)); + core.next_up_on_time_out = Some(scheduled_core(14)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(138)); + }), + // 24: Occupied(13, both next_up set to 14, not available, timeout) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(14)); + core.next_up_on_time_out = Some(scheduled_core(14)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(1399)); + }), + // 25: Occupied(13, next_up_on_available and available, but successor para_id 15) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(15)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(139)); + }), + // 26: Occupied(15, next_up_on_available and available, but successor para_id 13) + build_occupied_core(15, |core| { + core.next_up_on_available = Some(scheduled_core(13)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(151)); + }), + // 27: Occupied(15, both next_up, both available and timed out) + build_occupied_core(15, |core| { + core.next_up_on_available = Some(scheduled_core(15)); + core.availability = core.availability.clone().not(); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(152)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + }), + // 28: Occupied(13, both next_up set to 13, not available) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(13)); + core.next_up_on_time_out = Some(scheduled_core(13)); + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(1398)); + }), + // 29: Occupied(13, both next_up set to 13, not available, timeout) + build_occupied_core(13, |core| { + core.next_up_on_available = Some(scheduled_core(13)); + core.next_up_on_time_out = Some(scheduled_core(13)); + core.time_out_at = BLOCK_UNDER_PRODUCTION; + core.candidate_hash = CandidateHash(Hash::from_low_u64_be(1397)); + }), ] } async fn mock_overseer( mut receiver: mpsc::UnboundedReceiver, - expected: Vec, + mock_availability_cores: Vec, + mut expected: Vec, + mut expected_ancestors: HashMap, Ancestors>, prospective_parachains_mode: ProspectiveParachainsMode, ) { use ChainApiMessage::BlockNumber; use RuntimeApiMessage::Request; + let mut backed_iter = expected.clone().into_iter(); + + expected.sort_by_key(|c| c.candidate().descriptor.para_id); let mut candidates_iter = expected .iter() .map(|candidate| (candidate.hash(), candidate.descriptor().relay_parent)); - let mut backed_iter = expected.clone().into_iter(); - while let Some(from_job) = receiver.next().await { match from_job { AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) => @@ -356,7 +578,7 @@ mod select_candidates { PersistedValidationDataReq(_para_id, _assumption, tx), )) => tx.send(Ok(Some(Default::default()))).unwrap(), AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) => - tx.send(Ok(mock_availability_cores())).unwrap(), + tx.send(Ok(mock_availability_cores.clone())).unwrap(), AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates( hashes, sender, @@ -373,10 +595,36 @@ mod select_candidates { let _ = sender.send(response); }, AllMessages::ProspectiveParachains( - ProspectiveParachainsMessage::GetBackableCandidate(.., tx), + ProspectiveParachainsMessage::GetBackableCandidates( + _, + _para_id, + count, + actual_ancestors, + tx, + ), ) => match prospective_parachains_mode { ProspectiveParachainsMode::Enabled { .. } => { - let _ = tx.send(candidates_iter.next()); + assert!(count > 0); + let candidates = + (&mut candidates_iter).take(count as usize).collect::>(); + assert_eq!(candidates.len(), count as usize); + + if !expected_ancestors.is_empty() { + if let Some(expected_required_ancestors) = expected_ancestors.remove( + &(candidates + .clone() + .into_iter() + .take(actual_ancestors.len()) + .map(|(c_hash, _)| c_hash) + .collect::>()), + ) { + assert_eq!(expected_required_ancestors, actual_ancestors); + } else { + assert_eq!(actual_ancestors.len(), 0); + } + } + + let _ = tx.send(candidates); }, ProspectiveParachainsMode::Disabled => panic!("unexpected prospective parachains request"), @@ -384,19 +632,34 @@ mod select_candidates { _ => panic!("Unexpected message: {:?}", from_job), } } + + if let ProspectiveParachainsMode::Enabled { .. } = prospective_parachains_mode { + assert_eq!(candidates_iter.next(), None); + } + assert_eq!(expected_ancestors.len(), 0); } - #[test] - fn can_succeed() { + #[rstest] + #[case(ProspectiveParachainsMode::Disabled)] + #[case(ProspectiveParachainsMode::Enabled {max_candidate_depth: 0, allowed_ancestry_len: 0})] + fn can_succeed(#[case] prospective_parachains_mode: ProspectiveParachainsMode) { test_harness( - |r| mock_overseer(r, Vec::new(), ProspectiveParachainsMode::Disabled), + |r| { + mock_overseer( + r, + Vec::new(), + Vec::new(), + HashMap::new(), + prospective_parachains_mode, + ) + }, |mut tx: TestSubsystemSender| async move { - let prospective_parachains_mode = ProspectiveParachainsMode::Disabled; select_candidates( &[], &[], &[], prospective_parachains_mode, + false, Default::default(), &mut tx, ) @@ -406,22 +669,22 @@ mod select_candidates { ) } - // this tests that only the appropriate candidates get selected. - // To accomplish this, we supply a candidate list containing one candidate per possible core; - // the candidate selection algorithm must filter them to the appropriate set - #[test] - fn selects_correct_candidates() { - let mock_cores = mock_availability_cores(); - - let empty_hash = PersistedValidationData::::default().hash(); - - let mut descriptor_template = dummy_candidate_descriptor(dummy_hash()); - descriptor_template.persisted_validation_data_hash = empty_hash; - let candidate_template = CandidateReceipt { - descriptor: descriptor_template, - commitments_hash: CandidateCommitments::default().hash(), - }; - + // Test candidate selection when prospective parachains mode is disabled. + // This tests that only the appropriate candidates get selected when prospective parachains mode + // is disabled. To accomplish this, we supply a candidate list containing one candidate per + // possible core; the candidate selection algorithm must filter them to the appropriate set + #[rstest] + // why those particular indices? see the comments on mock_availability_cores_*() functions. + #[case(mock_availability_cores_one_per_para(), vec![1, 4, 7, 8, 10], true)] + #[case(mock_availability_cores_one_per_para(), vec![1, 4, 7, 8, 10], false)] + #[case(mock_availability_cores_multiple_per_para(), vec![1, 4, 7, 8, 10, 12, 13, 14, 15], true)] + #[case(mock_availability_cores_multiple_per_para(), vec![1, 4, 7, 8, 10, 12, 13, 14, 15], false)] + fn test_in_subsystem_selection( + #[case] mock_cores: Vec, + #[case] expected_candidates: Vec, + #[case] elastic_scaling_mvp: bool, + ) { + let candidate_template = dummy_candidate_template(); let candidates: Vec<_> = std::iter::repeat(candidate_template) .take(mock_cores.len()) .enumerate() @@ -448,31 +711,43 @@ mod select_candidates { }) .collect(); - // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = - [1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect(); + expected_candidates.into_iter().map(|idx| candidates[idx].clone()).collect(); let prospective_parachains_mode = ProspectiveParachainsMode::Disabled; let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); + let mock_cores_clone = mock_cores.clone(); test_harness( - |r| mock_overseer(r, expected_backed, prospective_parachains_mode), + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_backed, + HashMap::new(), + prospective_parachains_mode, + ) + }, |mut tx: TestSubsystemSender| async move { - let result = select_candidates( + let result: Vec = select_candidates( &mock_cores, &[], &candidates, prospective_parachains_mode, + elastic_scaling_mvp, Default::default(), &mut tx, ) @@ -481,7 +756,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -490,20 +765,24 @@ mod select_candidates { ) } - #[test] - fn selects_max_one_code_upgrade() { - let mock_cores = mock_availability_cores(); + #[rstest] + #[case(ProspectiveParachainsMode::Disabled)] + #[case(ProspectiveParachainsMode::Enabled {max_candidate_depth: 0, allowed_ancestry_len: 0})] + fn selects_max_one_code_upgrade( + #[case] prospective_parachains_mode: ProspectiveParachainsMode, + ) { + let mock_cores = mock_availability_cores_one_per_para(); let empty_hash = PersistedValidationData::::default().hash(); // why those particular indices? see the comments on mock_availability_cores() - // the first candidate with code is included out of [1, 4, 7, 8, 10]. - let cores = [1, 4, 7, 8, 10]; + // the first candidate with code is included out of [1, 4, 7, 8, 10, 12]. + let cores = [1, 4, 7, 8, 10, 12]; let cores_with_code = [1, 4, 8]; - let expected_cores = [1, 7, 10]; + let expected_cores = [1, 7, 10, 12]; - let committed_receipts: Vec<_> = (0..mock_cores.len()) + let committed_receipts: Vec<_> = (0..=mock_cores.len()) .map(|i| { let mut descriptor = dummy_candidate_descriptor(dummy_hash()); descriptor.para_id = i.into(); @@ -527,10 +806,13 @@ mod select_candidates { // Build possible outputs from select_candidates let backed_candidates: Vec<_> = committed_receipts .iter() - .map(|committed_receipt| BackedCandidate { - candidate: committed_receipt.clone(), - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|committed_receipt| { + BackedCandidate::new( + committed_receipt.clone(), + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -541,27 +823,36 @@ mod select_candidates { let expected_backed_filtered: Vec<_> = expected_cores.iter().map(|&idx| candidates[idx].clone()).collect(); - let prospective_parachains_mode = ProspectiveParachainsMode::Disabled; + let mock_cores_clone = mock_cores.clone(); test_harness( - |r| mock_overseer(r, expected_backed, prospective_parachains_mode), + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_backed, + HashMap::new(), + prospective_parachains_mode, + ) + }, |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], &candidates, prospective_parachains_mode, + false, Default::default(), &mut tx, ) .await .unwrap(); - assert_eq!(result.len(), 3); + assert_eq!(result.len(), 4); result.into_iter().for_each(|c| { assert!( - expected_backed_filtered.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_backed_filtered.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -570,63 +861,214 @@ mod select_candidates { ) } - #[test] - fn request_from_prospective_parachains() { - let mock_cores = mock_availability_cores(); - let empty_hash = PersistedValidationData::::default().hash(); + #[rstest] + #[case(true)] + #[case(false)] + fn request_from_prospective_parachains_one_core_per_para(#[case] elastic_scaling_mvp: bool) { + let mock_cores = mock_availability_cores_one_per_para(); - let mut descriptor_template = dummy_candidate_descriptor(dummy_hash()); - descriptor_template.persisted_validation_data_hash = empty_hash; - let candidate_template = CandidateReceipt { - descriptor: descriptor_template, - commitments_hash: CandidateCommitments::default().hash(), - }; + // why those particular indices? see the comments on mock_availability_cores() + let expected_candidates: Vec<_> = vec![1, 4, 7, 8, 10, 12]; + let (candidates, expected_candidates) = + make_candidates(mock_cores.len() + 1, expected_candidates); - let candidates: Vec<_> = std::iter::repeat(candidate_template) - .take(mock_cores.len()) - .enumerate() - .map(|(idx, mut candidate)| { - candidate.descriptor.para_id = idx.into(); - candidate - }) - .collect(); + // Expect prospective parachains subsystem requests. + let prospective_parachains_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; + + let mut required_ancestors: HashMap, Ancestors> = HashMap::new(); + required_ancestors.insert( + vec![candidates[4]], + vec![CandidateHash(Hash::from_low_u64_be(41))].into_iter().collect(), + ); + required_ancestors.insert( + vec![candidates[8]], + vec![CandidateHash(Hash::from_low_u64_be(81))].into_iter().collect(), + ); + + let mock_cores_clone = mock_cores.clone(); + let expected_candidates_clone = expected_candidates.clone(); + test_harness( + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_candidates_clone, + required_ancestors, + prospective_parachains_mode, + ) + }, + |mut tx: TestSubsystemSender| async move { + let result = select_candidates( + &mock_cores, + &[], + &[], + prospective_parachains_mode, + elastic_scaling_mvp, + Default::default(), + &mut tx, + ) + .await + .unwrap(); + + assert_eq!(result.len(), expected_candidates.len()); + result.into_iter().for_each(|c| { + assert!( + expected_candidates + .iter() + .any(|c2| c.candidate().corresponds_to(&c2.receipt())), + "Failed to find candidate: {:?}", + c, + ) + }); + }, + ) + } + + #[test] + fn request_from_prospective_parachains_multiple_cores_per_para_elastic_scaling_mvp() { + let mock_cores = mock_availability_cores_multiple_per_para(); // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = - [1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect(); + vec![1, 4, 7, 8, 10, 12, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15]; // Expect prospective parachains subsystem requests. let prospective_parachains_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; - let expected_backed = expected_candidates - .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), - }) - .collect(); + let (candidates, expected_candidates) = + make_candidates(mock_cores.len(), expected_candidates); + + let mut required_ancestors: HashMap, Ancestors> = HashMap::new(); + required_ancestors.insert( + vec![candidates[4]], + vec![CandidateHash(Hash::from_low_u64_be(41))].into_iter().collect(), + ); + required_ancestors.insert( + vec![candidates[8]], + vec![CandidateHash(Hash::from_low_u64_be(81))].into_iter().collect(), + ); + required_ancestors.insert( + [12, 12, 12].iter().map(|&idx| candidates[idx]).collect::>(), + vec![ + CandidateHash(Hash::from_low_u64_be(121)), + CandidateHash(Hash::from_low_u64_be(122)), + CandidateHash(Hash::from_low_u64_be(123)), + ] + .into_iter() + .collect(), + ); + required_ancestors.insert( + [13, 13, 13].iter().map(|&idx| candidates[idx]).collect::>(), + (131..=139) + .map(|num| CandidateHash(Hash::from_low_u64_be(num))) + .chain(std::iter::once(CandidateHash(Hash::from_low_u64_be(1398)))) + .collect(), + ); + + required_ancestors.insert( + [15, 15].iter().map(|&idx| candidates[idx]).collect::>(), + vec![ + CandidateHash(Hash::from_low_u64_be(151)), + CandidateHash(Hash::from_low_u64_be(152)), + ] + .into_iter() + .collect(), + ); + + let mock_cores_clone = mock_cores.clone(); + let expected_candidates_clone = expected_candidates.clone(); + test_harness( + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_candidates, + required_ancestors, + prospective_parachains_mode, + ) + }, + |mut tx: TestSubsystemSender| async move { + let result = select_candidates( + &mock_cores, + &[], + &[], + prospective_parachains_mode, + true, + Default::default(), + &mut tx, + ) + .await + .unwrap(); + assert_eq!(result.len(), expected_candidates_clone.len()); + result.into_iter().for_each(|c| { + assert!( + expected_candidates_clone + .iter() + .any(|c2| c.candidate().corresponds_to(&c2.receipt())), + "Failed to find candidate: {:?}", + c, + ) + }); + }, + ) + } + + #[test] + fn request_from_prospective_parachains_multiple_cores_per_para_elastic_scaling_mvp_disabled() { + let mock_cores = mock_availability_cores_multiple_per_para(); + + // why those particular indices? see the comments on mock_availability_cores() + let expected_candidates: Vec<_> = vec![1, 4, 7, 8, 10]; + // Expect prospective parachains subsystem requests. + let prospective_parachains_mode = + ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; + + let (candidates, expected_candidates) = + make_candidates(mock_cores.len(), expected_candidates); + + let mut required_ancestors: HashMap, Ancestors> = HashMap::new(); + required_ancestors.insert( + vec![candidates[4]], + vec![CandidateHash(Hash::from_low_u64_be(41))].into_iter().collect(), + ); + required_ancestors.insert( + vec![candidates[8]], + vec![CandidateHash(Hash::from_low_u64_be(81))].into_iter().collect(), + ); + + let mock_cores_clone = mock_cores.clone(); + let expected_candidates_clone = expected_candidates.clone(); test_harness( - |r| mock_overseer(r, expected_backed, prospective_parachains_mode), + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_candidates, + required_ancestors, + prospective_parachains_mode, + ) + }, |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], &[], prospective_parachains_mode, + false, Default::default(), &mut tx, ) .await .unwrap(); + assert_eq!(result.len(), expected_candidates_clone.len()); result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates_clone + .iter() + .any(|c2| c.candidate().corresponds_to(&c2.receipt())), "Failed to find candidate: {:?}", c, ) @@ -637,18 +1079,11 @@ mod select_candidates { #[test] fn request_receipts_based_on_relay_parent() { - let mock_cores = mock_availability_cores(); - let empty_hash = PersistedValidationData::::default().hash(); - - let mut descriptor_template = dummy_candidate_descriptor(dummy_hash()); - descriptor_template.persisted_validation_data_hash = empty_hash; - let candidate_template = CandidateReceipt { - descriptor: descriptor_template, - commitments_hash: CandidateCommitments::default().hash(), - }; + let mock_cores = mock_availability_cores_one_per_para(); + let candidate_template = dummy_candidate_template(); let candidates: Vec<_> = std::iter::repeat(candidate_template) - .take(mock_cores.len()) + .take(mock_cores.len() + 1) .enumerate() .map(|(idx, mut candidate)| { candidate.descriptor.para_id = idx.into(); @@ -659,31 +1094,44 @@ mod select_candidates { // why those particular indices? see the comments on mock_availability_cores() let expected_candidates: Vec<_> = - [1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect(); + [1, 4, 7, 8, 10, 12].iter().map(|&idx| candidates[idx].clone()).collect(); // Expect prospective parachains subsystem requests. let prospective_parachains_mode = ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 }; let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); + let mock_cores_clone = mock_cores.clone(); test_harness( - |r| mock_overseer(r, expected_backed, prospective_parachains_mode), + |r| { + mock_overseer( + r, + mock_cores_clone, + expected_backed, + HashMap::new(), + prospective_parachains_mode, + ) + }, |mut tx: TestSubsystemSender| async move { let result = select_candidates( &mock_cores, &[], &[], prospective_parachains_mode, + false, Default::default(), &mut tx, ) @@ -692,7 +1140,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 0326a20e5a52e7a07fe96a8c721af1c6ac86e5ef..f4f954e316c0b758a4cb1e94f80b729257349632 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "polkadot-node-core-pvf-checker" description = "Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and voting for PVFs that are pending approval." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } polkadot-node-primitives = { path = "../../primitives" } diff --git a/polkadot/node/core/pvf-checker/src/lib.rs b/polkadot/node/core/pvf-checker/src/lib.rs index 2946f3f78861cccdbac8f94a97a4693fce37a09f..c00ec0d952f158f8df21de87ee9d7460d68218bc 100644 --- a/polkadot/node/core/pvf-checker/src/lib.rs +++ b/polkadot/node/core/pvf-checker/src/lib.rs @@ -49,29 +49,24 @@ use self::{ /// PVF pre-checking subsystem. pub struct PvfCheckerSubsystem { - enabled: bool, keystore: KeystorePtr, metrics: Metrics, } impl PvfCheckerSubsystem { - pub fn new(enabled: bool, keystore: KeystorePtr, metrics: Metrics) -> Self { - PvfCheckerSubsystem { enabled, keystore, metrics } + pub fn new(keystore: KeystorePtr, metrics: Metrics) -> Self { + PvfCheckerSubsystem { keystore, metrics } } } #[overseer::subsystem(PvfChecker, error=SubsystemError, prefix = self::overseer)] impl PvfCheckerSubsystem { fn start(self, ctx: Context) -> SpawnedSubsystem { - if self.enabled { - let future = run(ctx, self.keystore, self.metrics) - .map_err(|e| SubsystemError::with_origin("pvf-checker", e)) - .boxed(); - - SpawnedSubsystem { name: "pvf-checker-subsystem", future } - } else { - polkadot_overseer::DummySubsystem.start(ctx) - } + let future = run(ctx, self.keystore, self.metrics) + .map_err(|e| SubsystemError::with_origin("pvf-checker", e)) + .boxed(); + + SpawnedSubsystem { name: "pvf-checker-subsystem", future } } } @@ -420,7 +415,7 @@ async fn check_signing_credentials( gum::warn!( target: LOG_TARGET, relay_parent = ?leaf, - "error occured during requesting validators: {:?}", + "error occurred during requesting validators: {:?}", e ); return None @@ -513,7 +508,7 @@ async fn sign_and_submit_pvf_check_statement( target: LOG_TARGET, ?relay_parent, ?validation_code_hash, - "error occured during submitting a vote: {:?}", + "error occurred during submitting a vote: {:?}", e, ); }, @@ -535,7 +530,11 @@ async fn initiate_precheck( let (tx, rx) = oneshot::channel(); sender - .send_message(CandidateValidationMessage::PreCheck(relay_parent, validation_code_hash, tx)) + .send_message(CandidateValidationMessage::PreCheck { + relay_parent, + validation_code_hash, + response_sender: tx, + }) .await; let timer = metrics.time_pre_check_judgement(); diff --git a/polkadot/node/core/pvf-checker/src/tests.rs b/polkadot/node/core/pvf-checker/src/tests.rs index 4ac6e5eba31d7a98b24b4acc69d645147f107bc8..b0401ecdc3bd2368e34a263842ea566b712884a0 100644 --- a/polkadot/node/core/pvf-checker/src/tests.rs +++ b/polkadot/node/core/pvf-checker/src/tests.rs @@ -267,11 +267,12 @@ impl TestState { handle: &mut VirtualOverseer, ) -> ExpectCandidatePrecheck { match self.recv_timeout(handle).await.expect("timeout waiting for a message") { - AllMessages::CandidateValidation(CandidateValidationMessage::PreCheck( + AllMessages::CandidateValidation(CandidateValidationMessage::PreCheck { relay_parent, validation_code_hash, - tx, - )) => ExpectCandidatePrecheck { relay_parent, validation_code_hash, tx }, + response_sender, + .. + }) => ExpectCandidatePrecheck { relay_parent, validation_code_hash, tx: response_sender }, msg => panic!("Unexpected message was received: {:#?}", msg), } } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index 27f4df117e578ebb8f86dd5eb6570af0b247492b..9ed64b88ffde857c6abe171a7d1cad7dd66dd509 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -1,22 +1,29 @@ [package] name = "polkadot-node-core-pvf" description = "Polkadot crate that implements the PVF validation host. Responsible for coordinating preparation and execution of PVFs." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] always-assert = "0.1" +array-bytes = "6.1" +blake3 = "1.5" cfg-if = "1.0" futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } -libc = "0.2.139" +is_executable = "1.0.1" +libc = "0.2.152" pin-project = "1.0.9" rand = "0.8.5" slotmap = "1.0" tempfile = "3.3.0" +thiserror = { workspace = true } tokio = { version = "1.24.2", features = ["fs", "process"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } @@ -26,6 +33,7 @@ polkadot-core-primitives = { path = "../../../core-primitives" } polkadot-node-core-pvf-common = { path = "common" } polkadot-node-metrics = { path = "../../metrics" } polkadot-node-primitives = { path = "../../primitives" } +polkadot-node-subsystem = { path = "../../subsystem" } polkadot-primitives = { path = "../../../primitives" } sp-core = { path = "../../../../substrate/primitives/core" } @@ -34,22 +42,32 @@ sp-maybe-compressed-blob = { path = "../../../../substrate/primitives/maybe-comp polkadot-node-core-pvf-prepare-worker = { path = "prepare-worker", optional = true } polkadot-node-core-pvf-execute-worker = { path = "execute-worker", optional = true } -[build-dependencies] -substrate-build-script-utils = { path = "../../../../substrate/utils/build-script-utils" } - [dev-dependencies] assert_matches = "1.4.0" +criterion = { version = "0.4.0", default-features = false, features = ["async_tokio", "cargo_bench_support"] } hex-literal = "0.4.1" + polkadot-node-core-pvf-common = { path = "common", features = ["test-utils"] } -# For the puppet worker, depend on ourselves with the test-utils feature. +# For benches and integration tests, depend on ourselves with the test-utils +# feature. polkadot-node-core-pvf = { path = ".", features = ["test-utils"] } +rococo-runtime = { path = "../../../runtime/rococo" } adder = { package = "test-parachain-adder", path = "../../../parachain/test-parachains/adder" } halt = { package = "test-parachain-halt", path = "../../../parachain/test-parachains/halt" } +[target.'cfg(target_os = "linux")'.dev-dependencies] +procfs = "0.16.0" +rusty-fork = "0.3.0" +sc-sysinfo = { path = "../../../../substrate/client/sysinfo" } + +[[bench]] +name = "host_prepare_rococo_runtime" +harness = false + [features] ci-only-tests = [] -jemalloc-allocator = [ "polkadot-node-core-pvf-common/jemalloc-allocator" ] +jemalloc-allocator = ["polkadot-node-core-pvf-common/jemalloc-allocator"] # This feature is used to export test code to other crates without putting it in the production build. test-utils = [ "polkadot-node-core-pvf-execute-worker", diff --git a/polkadot/node/core/pvf/README.md b/polkadot/node/core/pvf/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5304b0720b2df614c005ff54eabb4d2c74b0bb4b --- /dev/null +++ b/polkadot/node/core/pvf/README.md @@ -0,0 +1,47 @@ +# PVF Host + +This is the PVF host, responsible for responding to requests from Candidate +Validation and spawning worker tasks to fulfill those requests. + +See also: + +- for more information: [the Implementer's Guide][impl-guide] +- for an explanation of terminology: [the Glossary][glossary] + +## Running basic tests + +Running `cargo test` in the `pvf/` directory will run unit and integration +tests. + +**Note:** some tests run only under Linux, x86-64, and/or with the +`ci-only-tests` feature enabled. + +See the general [Testing][testing] instructions for more information on +**running tests** and **observing logs**. + +## Running a test-network with zombienet + +Since this crate is consensus-critical, for major changes it is highly +recommended to run a test-network. See the "Behavior tests" section of the +[Testing][testing] docs for full instructions. + +To run the PVF-specific zombienet test: + +```sh +RUST_LOG=parachain::pvf=trace zombienet --provider=native spawn zombienet_tests/functional/0001-parachains-pvf.toml +``` + +## Testing on Linux + +Some of the PVF functionality, especially related to security, is Linux-only, +and some is x86-64-only. If you touch anything security-related, make sure to +test on Linux x86-64! If you're on a Mac, you can either run a VM or you can hire +a VPS and use the open-source tool [EternalTerminal][et] to connect to it.[^et] + +[^et]: Unlike ssh, ET preserves your session across disconnects, and unlike +another popular persistent shell, mosh, it allows scrollback. + +[impl-guide]: https://paritytech.github.io/polkadot-sdk/book/pvf-prechecking.html#summary +[glossary]: https://paritytech.github.io/polkadot-sdk/book/glossary.html +[testing]: https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/doc/testing.md +[et]: https://github.com/MisterTea/EternalTerminal diff --git a/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..2aea21361a3e8fcb4114eb533539f09b1d660fc7 --- /dev/null +++ b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs @@ -0,0 +1,133 @@ +// 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 . + +//! Benchmarks for preparation through the host. We use a real PVF to get realistic results. + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion, SamplingMode}; +use polkadot_node_core_pvf::{ + start, testing, Config, Metrics, PrepareError, PrepareJobKind, PvfPrepData, ValidationHost, +}; +use polkadot_primitives::ExecutorParams; +use rococo_runtime::WASM_BINARY; +use std::time::Duration; +use tokio::{runtime::Handle, sync::Mutex}; + +const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(30); + +struct TestHost { + // Keep a reference to the tempdir otherwise it gets deleted on drop. + #[allow(dead_code)] + cache_dir: tempfile::TempDir, + host: Mutex, +} + +impl TestHost { + async fn new_with_config(handle: &Handle, f: F) -> Self + where + F: FnOnce(&mut Config), + { + let (prepare_worker_path, execute_worker_path) = testing::build_workers_and_get_paths(); + + let cache_dir = tempfile::tempdir().unwrap(); + let mut config = Config::new( + cache_dir.path().to_owned(), + None, + false, + prepare_worker_path, + execute_worker_path, + ); + f(&mut config); + let (host, task) = start(config, Metrics::default()).await.unwrap(); + let _ = handle.spawn(task); + Self { host: Mutex::new(host), cache_dir } + } + + async fn precheck_pvf( + &self, + code: &[u8], + executor_params: ExecutorParams, + ) -> Result<(), PrepareError> { + let (result_tx, result_rx) = futures::channel::oneshot::channel(); + + let code = sp_maybe_compressed_blob::decompress(code, 16 * 1024 * 1024) + .expect("Compression works"); + + self.host + .lock() + .await + .precheck_pvf( + PvfPrepData::from_code( + code.into(), + executor_params, + TEST_PREPARATION_TIMEOUT, + PrepareJobKind::Prechecking, + ), + result_tx, + ) + .await + .unwrap(); + result_rx.await.unwrap() + } +} + +fn host_prepare_rococo_runtime(c: &mut Criterion) { + polkadot_node_core_pvf_common::sp_tracing::try_init_simple(); + + let rt = tokio::runtime::Runtime::new().unwrap(); + + let blob = WASM_BINARY.expect("You need to build the WASM binaries to run the tests!"); + let pvf = match sp_maybe_compressed_blob::decompress(&blob, 64 * 1024 * 1024) { + Ok(code) => PvfPrepData::from_code( + code.into_owned(), + ExecutorParams::default(), + Duration::from_secs(360), + PrepareJobKind::Compilation, + ), + Err(e) => { + panic!("Cannot decompress blob: {:?}", e); + }, + }; + + let mut group = c.benchmark_group("prepare rococo"); + group.sampling_mode(SamplingMode::Flat); + group.sample_size(20); + group.measurement_time(Duration::from_secs(240)); + group.bench_function("host: prepare Rococo runtime", |b| { + b.to_async(&rt).iter_batched( + || async { + ( + TestHost::new_with_config(rt.handle(), |cfg| { + cfg.prepare_workers_hard_max_num = 1; + }) + .await, + pvf.clone().code(), + ) + }, + |result| async move { + let (host, pvf_code) = result.await; + + // `PvfPrepData` is designed to be cheap to clone, so cloning shouldn't affect the + // benchmark accuracy. + let _stats = host.precheck_pvf(&pvf_code, Default::default()).await.unwrap(); + }, + BatchSize::SmallInput, + ) + }); + group.finish(); +} + +criterion_group!(prepare, host_prepare_rococo_runtime); +criterion_main!(prepare); diff --git a/polkadot/node/core/pvf/build.rs b/polkadot/node/core/pvf/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..e01cc6deecc22c574a929c96f0684d777764149e --- /dev/null +++ b/polkadot/node/core/pvf/build.rs @@ -0,0 +1,21 @@ +// 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 . + +fn main() { + if let Ok(profile) = std::env::var("PROFILE") { + println!(r#"cargo:rustc-cfg=build_type="{}""#, profile); + } +} diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 4bdacca72f4208079bd4f810f71c2af15991cb3a..56bad9792fa0b6accea6460025c64c18e8e1e5d8 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -1,18 +1,21 @@ [package] name = "polkadot-node-core-pvf-common" description = "Polkadot crate that contains functionality related to PVFs that is shared by the PVF host and the PVF workers." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] cfg-if = "1.0" cpu-time = "1.0.0" futures = "0.3.21" gum = { package = "tracing-gum", path = "../../../gum" } -libc = "0.2.139" -tokio = { version = "1.24.2", features = ["fs", "process", "io-util"] } +libc = "0.2.152" +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } @@ -24,12 +27,17 @@ sc-executor-common = { path = "../../../../../substrate/client/executor/common" sc-executor-wasmtime = { path = "../../../../../substrate/client/executor/wasmtime" } sp-core = { path = "../../../../../substrate/primitives/core" } +sp-crypto-hashing = { path = "../../../../../substrate/primitives/crypto/hashing" } sp-externalities = { path = "../../../../../substrate/primitives/externalities" } sp-io = { path = "../../../../../substrate/primitives/io" } sp-tracing = { path = "../../../../../substrate/primitives/tracing" } [target.'cfg(target_os = "linux")'.dependencies] landlock = "0.3.0" +nix = { version = "0.27.1", features = ["sched"] } + +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] +seccompiler = "0.4.0" [dev-dependencies] assert_matches = "1.4.0" @@ -37,6 +45,5 @@ tempfile = "3.3.0" [features] # This feature is used to export test code to other crates without putting it in the production build. -# Also used for building the puppet worker. test-utils = [] jemalloc-allocator = [] diff --git a/polkadot/node/core/pvf/common/src/error.rs b/polkadot/node/core/pvf/common/src/error.rs index 6fdd06057c8b021cc89b29eb0265c78e316eed02..cf274044456f3ea2db6770fbd61b3319f6420996 100644 --- a/polkadot/node/core/pvf/common/src/error.rs +++ b/polkadot/node/core/pvf/common/src/error.rs @@ -14,47 +14,86 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::prepare::PrepareStats; +use crate::prepare::{PrepareSuccess, PrepareWorkerSuccess}; use parity_scale_codec::{Decode, Encode}; -use std::fmt; +pub use sc_executor_common::error::Error as ExecuteError; -/// Result of PVF preparation performed by the validation host. Contains stats about the preparation -/// if successful -pub type PrepareResult = Result; +/// Result of PVF preparation from a worker, with checksum of the compiled PVF and stats of the +/// preparation if successful. +pub type PrepareWorkerResult = Result; + +/// Result of PVF preparation propagated all the way back to the host, with path to the concluded +/// artifact and stats of the preparation if successful. +pub type PrepareResult = Result; + +/// Result of prechecking PVF performed by the validation host. Contains stats about the preparation +/// if successful. +pub type PrecheckResult = Result<(), PrepareError>; /// An error that occurred during the prepare part of the PVF pipeline. -#[derive(Debug, Clone, Encode, Decode)] +// Codec indexes are intended to stabilize pre-encoded payloads (see `OOM_PAYLOAD`) +#[derive(thiserror::Error, Debug, Clone, Encode, Decode)] pub enum PrepareError { /// During the prevalidation stage of preparation an issue was found with the PVF. + #[codec(index = 0)] + #[error("prepare: prevalidation error: {0}")] Prevalidation(String), /// Compilation failed for the given PVF. + #[codec(index = 1)] + #[error("prepare: preparation error: {0}")] Preparation(String), /// Instantiation of the WASM module instance failed. + #[codec(index = 2)] + #[error("prepare: runtime construction: {0}")] RuntimeConstruction(String), - /// An unexpected panic has occurred in the preparation worker. - Panic(String), + /// An unexpected error has occurred in the preparation job. + #[codec(index = 3)] + #[error("prepare: job error: {0}")] + JobError(String), /// Failed to prepare the PVF due to the time limit. + #[codec(index = 4)] + #[error("prepare: timeout")] TimedOut, /// An IO error occurred. This state is reported by either the validation host or by the /// worker. + #[codec(index = 5)] + #[error("prepare: io error while receiving response: {0}")] IoErr(String), /// The temporary file for the artifact could not be created at the given cache path. This /// state is reported by the validation host (not by the worker). - CreateTmpFileErr(String), + #[codec(index = 6)] + #[error("prepare: error creating tmp file: {0}")] + CreateTmpFile(String), /// The response from the worker is received, but the file cannot be renamed (moved) to the /// final destination location. This state is reported by the validation host (not by the /// worker). - RenameTmpFileErr { + #[codec(index = 7)] + #[error("prepare: error renaming tmp file ({src:?} -> {dest:?}): {err}")] + RenameTmpFile { err: String, // Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible // conversion to `Option`. src: Option, dest: Option, }, + /// Memory limit reached + #[codec(index = 8)] + #[error("prepare: out of memory")] + OutOfMemory, /// The response from the worker is received, but the worker cache could not be cleared. The /// worker has to be killed to avoid jobs having access to data from other jobs. This state is /// reported by the validation host (not by the worker). + #[codec(index = 9)] + #[error("prepare: error clearing worker cache: {0}")] ClearWorkerDir(String), + /// The preparation job process died, due to OOM, a seccomp violation, or some other factor. + #[codec(index = 10)] + #[error("prepare: prepare job with pid {job_pid} died: {err}")] + JobDied { err: String, job_pid: i32 }, + /// Some error occurred when interfacing with the kernel. + #[codec(index = 11)] + #[error("prepare: error interfacing with the kernel: {0}")] + Kernel(String), } impl PrepareError { @@ -67,83 +106,48 @@ impl PrepareError { pub fn is_deterministic(&self) -> bool { use PrepareError::*; match self { - Prevalidation(_) | Preparation(_) | Panic(_) => true, - TimedOut | + Prevalidation(_) | Preparation(_) | JobError(_) | OutOfMemory => true, IoErr(_) | - CreateTmpFileErr(_) | - RenameTmpFileErr { .. } | - ClearWorkerDir(_) => false, + JobDied { .. } | + CreateTmpFile(_) | + RenameTmpFile { .. } | + ClearWorkerDir(_) | + Kernel(_) => false, + // Can occur due to issues with the PVF, but also due to factors like local load. + TimedOut => false, // Can occur due to issues with the PVF, but also due to local errors. RuntimeConstruction(_) => false, } } } -impl fmt::Display for PrepareError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use PrepareError::*; - match self { - Prevalidation(err) => write!(f, "prevalidation: {}", err), - Preparation(err) => write!(f, "preparation: {}", err), - RuntimeConstruction(err) => write!(f, "runtime construction: {}", err), - Panic(err) => write!(f, "panic: {}", err), - TimedOut => write!(f, "prepare: timeout"), - IoErr(err) => write!(f, "prepare: io error while receiving response: {}", err), - CreateTmpFileErr(err) => write!(f, "prepare: error creating tmp file: {}", err), - RenameTmpFileErr { err, src, dest } => - write!(f, "prepare: error renaming tmp file ({:?} -> {:?}): {}", src, dest, err), - ClearWorkerDir(err) => write!(f, "prepare: error clearing worker cache: {}", err), - } - } -} - /// Some internal error occurred. /// /// Should only ever be used for validation errors independent of the candidate and PVF, or for /// errors we ruled out during pre-checking (so preparation errors are fine). -#[derive(Debug, Clone, Encode, Decode)] +#[derive(thiserror::Error, Debug, Clone, Encode, Decode)] pub enum InternalValidationError { /// Some communication error occurred with the host. + #[error("validation: some communication error occurred with the host: {0}")] HostCommunication(String), /// Host could not create a hard link to the artifact path. + #[error("validation: host could not create a hard link to the artifact path: {0}")] CouldNotCreateLink(String), /// Could not find or open compiled artifact file. + #[error("validation: could not find or open compiled artifact file: {0}")] CouldNotOpenFile(String), /// Host could not clear the worker cache after a job. + #[error("validation: host could not clear the worker cache ({path:?}) after a job: {err}")] CouldNotClearWorkerDir { err: String, // Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible // conversion to `Option`. path: Option, }, - /// An error occurred in the CPU time monitor thread. Should be totally unrelated to - /// validation. - CpuTimeMonitorThread(String), + /// Some error occurred when interfacing with the kernel. + #[error("validation: error interfacing with the kernel: {0}")] + Kernel(String), /// Some non-deterministic preparation error occurred. + #[error("validation: prepare: {0}")] NonDeterministicPrepareError(PrepareError), } - -impl fmt::Display for InternalValidationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use InternalValidationError::*; - match self { - HostCommunication(err) => - write!(f, "validation: some communication error occurred with the host: {}", err), - CouldNotCreateLink(err) => write!( - f, - "validation: host could not create a hard link to the artifact path: {}", - err - ), - CouldNotOpenFile(err) => - write!(f, "validation: could not find or open compiled artifact file: {}", err), - CouldNotClearWorkerDir { err, path } => write!( - f, - "validation: host could not clear the worker cache ({:?}) after a job: {}", - path, err - ), - CpuTimeMonitorThread(err) => - write!(f, "validation: an error occurred in the CPU time monitor thread: {}", err), - NonDeterministicPrepareError(err) => write!(f, "validation: prepare: {}", err), - } - } -} diff --git a/polkadot/node/core/pvf/common/src/execute.rs b/polkadot/node/core/pvf/common/src/execute.rs index b89ab089af1c02eba401517e8248c292dd7040f8..18c97b03cbcd6fccc03cb27cb9f9e3aba9c7cf2a 100644 --- a/polkadot/node/core/pvf/common/src/execute.rs +++ b/polkadot/node/core/pvf/common/src/execute.rs @@ -28,9 +28,9 @@ pub struct Handshake { pub executor_params: ExecutorParams, } -/// The response from an execution job on the worker. +/// The response from the execution worker. #[derive(Debug, Encode, Decode)] -pub enum Response { +pub enum WorkerResponse { /// The job completed successfully. Ok { /// The result of parachain validation. @@ -40,15 +40,45 @@ pub enum Response { }, /// The candidate is invalid. InvalidCandidate(String), + /// Instantiation of the WASM module instance failed during an execution. + /// Possibly related to local issues or dirty node update. May be retried with re-preparation. + RuntimeConstruction(String), /// The job timed out. - TimedOut, - /// An unexpected panic has occurred in the execution worker. - Panic(String), + JobTimedOut, + /// The job process has died. We must kill the worker just in case. + /// + /// We cannot treat this as an internal error because malicious code may have killed the job. + /// We still retry it, because in the non-malicious case it is likely spurious. + JobDied { err: String, job_pid: i32 }, + /// An unexpected error occurred in the job process, e.g. failing to spawn a thread, panic, + /// etc. + /// + /// Because malicious code can cause a job error, we must not treat it as an internal error. We + /// still retry it, because in the non-malicious case it is likely spurious. + JobError(String), + /// Some internal error occurred. InternalError(InternalValidationError), } -impl Response { +/// The result of a job on the execution worker. +pub type JobResult = Result; + +/// The successful response from a job on the execution worker. +#[derive(Debug, Encode, Decode)] +pub enum JobResponse { + Ok { + /// The result of parachain validation. + result_descriptor: ValidationResult, + }, + /// A possibly transient runtime instantiation error happened during the execution; may be + /// retried with re-preparation + RuntimeConstruction(String), + /// The candidate is invalid. + InvalidCandidate(String), +} + +impl JobResponse { /// Creates an invalid response from a context `ctx` and a message `msg` (which can be empty). pub fn format_invalid(ctx: &'static str, msg: &str) -> Self { if msg.is_empty() { @@ -57,4 +87,31 @@ impl Response { Self::InvalidCandidate(format!("{}: {}", ctx, msg)) } } + + /// Creates a may retry response from a context `ctx` and a message `msg` (which can be empty). + pub fn runtime_construction(ctx: &'static str, msg: &str) -> Self { + if msg.is_empty() { + Self::RuntimeConstruction(ctx.to_string()) + } else { + Self::RuntimeConstruction(format!("{}: {}", ctx, msg)) + } + } +} + +/// An unexpected error occurred in the execution job process. Because this comes from the job, +/// which executes untrusted code, this error must likewise be treated as untrusted. That is, we +/// cannot raise an internal error based on this. +#[derive(thiserror::Error, Debug, Encode, Decode)] +pub enum JobError { + #[error("The job timed out")] + TimedOut, + #[error("An unexpected panic has occurred in the execution job: {0}")] + Panic(String), + /// Some error occurred when interfacing with the kernel. + #[error("Error interfacing with the kernel: {0}")] + Kernel(String), + #[error("Could not spawn the requested thread: {0}")] + CouldNotSpawnThread(String), + #[error("An error occurred in the CPU time monitor thread: {0}")] + CpuTimeMonitorThread(String), } diff --git a/polkadot/node/core/pvf/common/src/executor_intf.rs b/polkadot/node/core/pvf/common/src/executor_interface.rs similarity index 87% rename from polkadot/node/core/pvf/common/src/executor_intf.rs rename to polkadot/node/core/pvf/common/src/executor_interface.rs index 58d2a90371cf42508a98501d11be15453ba52db9..252e611db8a488cff776b5d7863c987318e7e013 100644 --- a/polkadot/node/core/pvf/common/src/executor_intf.rs +++ b/polkadot/node/core/pvf/common/src/executor_interface.rs @@ -16,6 +16,7 @@ //! Interface to the Substrate Executor +use crate::error::ExecuteError; use polkadot_primitives::{ executor_params::{DEFAULT_LOGICAL_STACK_MAX, DEFAULT_NATIVE_STACK_MAX}, ExecutorParam, ExecutorParams, @@ -23,7 +24,7 @@ use polkadot_primitives::{ use sc_executor_common::{ error::WasmError, runtime_blob::RuntimeBlob, - wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmModule as _}, + wasm_runtime::{HeapAllocStrategy, WasmModule as _}, }; use sc_executor_wasmtime::{Config, DeterministicStackLimit, Semantics, WasmtimeRuntime}; use sp_core::storage::{ChildInfo, TrackedStorageKey}; @@ -109,7 +110,7 @@ pub unsafe fn execute_artifact( compiled_artifact_blob: &[u8], executor_params: &ExecutorParams, params: &[u8], -) -> Result, String> { +) -> Result, ExecuteError> { let mut extensions = sp_externalities::Extensions::new(); extensions.register(sp_core::traits::ReadRuntimeVersionExt::new(ReadRuntimeVersion)); @@ -118,12 +119,11 @@ pub unsafe fn execute_artifact( match sc_executor::with_externalities_safe(&mut ext, || { let runtime = create_runtime_from_artifact_bytes(compiled_artifact_blob, executor_params)?; - runtime.new_instance()?.call(InvokeMethod::Export("validate_block"), params) + runtime.new_instance()?.call("validate_block", params) }) { Ok(Ok(ok)) => Ok(ok), Ok(Err(err)) | Err(err) => Err(err), } - .map_err(|err| format!("execute error: {:?}", err)) } /// Constructs the runtime for the given PVF, given the artifact bytes. @@ -140,8 +140,7 @@ pub unsafe fn create_runtime_from_artifact_bytes( executor_params: &ExecutorParams, ) -> Result { let mut config = DEFAULT_CONFIG.clone(); - config.semantics = - params_to_wasmtime_semantics(executor_params).map_err(|err| WasmError::Other(err))?; + config.semantics = params_to_wasmtime_semantics(executor_params).0; sc_executor_wasmtime::create_runtime_from_artifact_bytes::( compiled_artifact_blob, @@ -149,13 +148,15 @@ pub unsafe fn create_runtime_from_artifact_bytes( ) } -pub fn params_to_wasmtime_semantics(par: &ExecutorParams) -> Result { +/// Takes the default config and overwrites any settings with existing executor parameters. +/// +/// Returns the semantics as well as the stack limit (since we are guaranteed to have it). +pub fn params_to_wasmtime_semantics(par: &ExecutorParams) -> (Semantics, DeterministicStackLimit) { let mut sem = DEFAULT_CONFIG.semantics.clone(); - let mut stack_limit = if let Some(stack_limit) = sem.deterministic_stack_limit.clone() { - stack_limit - } else { - return Err("No default stack limit set".to_owned()) - }; + let mut stack_limit = sem + .deterministic_stack_limit + .expect("There is a comment to not change the default stack limit; it should always be available; qed") + .clone(); for p in par.iter() { match p { @@ -166,13 +167,31 @@ pub fn params_to_wasmtime_semantics(par: &ExecutorParams) -> Result stack_limit.logical_max = *slm, ExecutorParam::StackNativeMax(snm) => stack_limit.native_stack_max = *snm, ExecutorParam::WasmExtBulkMemory => sem.wasm_bulk_memory = true, - // TODO: Not implemented yet; . - ExecutorParam::PrecheckingMaxMemory(_) => (), - ExecutorParam::PvfPrepTimeout(_, _) | ExecutorParam::PvfExecTimeout(_, _) => (), /* Not used here */ + ExecutorParam::PrecheckingMaxMemory(_) | + ExecutorParam::PvfPrepTimeout(_, _) | + ExecutorParam::PvfExecTimeout(_, _) => (), /* Not used here */ } } - sem.deterministic_stack_limit = Some(stack_limit); - Ok(sem) + sem.deterministic_stack_limit = Some(stack_limit.clone()); + (sem, stack_limit) +} + +/// Runs the prevalidation on the given code. Returns a [`RuntimeBlob`] if it succeeds. +pub fn prevalidate(code: &[u8]) -> Result { + // Construct the runtime blob and do some basic checks for consistency. + let blob = RuntimeBlob::new(code)?; + // In the future this function should take care of any further prevalidation logic. + Ok(blob) +} + +/// Runs preparation on the given runtime blob. If successful, it returns a serialized compiled +/// artifact which can then be used to pass into `Executor::execute` after writing it to the disk. +pub fn prepare( + blob: RuntimeBlob, + executor_params: &ExecutorParams, +) -> Result, sc_executor_common::error::WasmError> { + let (semantics, _) = params_to_wasmtime_semantics(executor_params); + sc_executor_wasmtime::prepare_runtime_artifact(blob, &semantics) } /// Available host functions. We leave out: diff --git a/polkadot/node/core/pvf/common/src/lib.rs b/polkadot/node/core/pvf/common/src/lib.rs index 53c287ea970907cb8884ec47c74bd4e03e38d7e7..15097dbd3af5c29d1fceabc8d04f6ee9a395ef30 100644 --- a/polkadot/node/core/pvf/common/src/lib.rs +++ b/polkadot/node/core/pvf/common/src/lib.rs @@ -18,7 +18,7 @@ pub mod error; pub mod execute; -pub mod executor_intf; +pub mod executor_interface; pub mod prepare; pub mod pvf; pub mod worker; @@ -31,11 +31,11 @@ pub use sp_tracing; const LOG_TARGET: &str = "parachain::pvf-common"; +use parity_scale_codec::{Decode, Encode}; use std::{ - io::{Read, Write}, + io::{self, Read, Write}, mem, }; -use tokio::io; #[cfg(feature = "test-utils")] pub mod tests { @@ -46,12 +46,25 @@ pub mod tests { } /// Status of security features on the current system. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Encode, Decode)] pub struct SecurityStatus { + /// Whether Secure Validator Mode is enabled. This mode enforces that all required security + /// features are present. All features are enabled on a best-effort basis regardless. + pub secure_validator_mode: bool, /// Whether the landlock features we use are fully available on this system. pub can_enable_landlock: bool, - // Whether we are able to unshare the user namespace and change the filesystem root. + /// Whether the seccomp features we use are fully available on this system. + pub can_enable_seccomp: bool, + /// Whether we are able to unshare the user namespace and change the filesystem root. pub can_unshare_user_namespace_and_change_root: bool, + /// Whether we are able to call `clone` with all sandboxing flags. + pub can_do_secure_clone: bool, +} + +/// A handshake with information for the worker. +#[derive(Debug, Encode, Decode)] +pub struct WorkerHandshake { + pub security_status: SecurityStatus, } /// Write some data prefixed by its length into `w`. Sync version of `framed_send` to avoid @@ -73,3 +86,33 @@ pub fn framed_recv_blocking(r: &mut (impl Read + Unpin)) -> io::Result> r.read_exact(&mut buf)?; Ok(buf) } + +#[cfg(all(test, not(feature = "test-utils")))] +mod tests { + use super::*; + + #[test] + fn default_secure_status() { + let status = SecurityStatus::default(); + assert!( + !status.secure_validator_mode, + "secure_validator_mode is false for default security status" + ); + assert!( + !status.can_enable_landlock, + "can_enable_landlock is false for default security status" + ); + assert!( + !status.can_enable_seccomp, + "can_enable_seccomp is false for default security status" + ); + assert!( + !status.can_unshare_user_namespace_and_change_root, + "can_unshare_user_namespace_and_change_root is false for default security status" + ); + assert!( + !status.can_do_secure_clone, + "can_do_secure_clone is false for default security status" + ); + } +} diff --git a/polkadot/node/core/pvf/common/src/prepare.rs b/polkadot/node/core/pvf/common/src/prepare.rs index c205eddfb8b1fbeaf5842d8b468ca94f4806f23a..28ab682ec136d962364019f2623f80d8e7ad6194 100644 --- a/polkadot/node/core/pvf/common/src/prepare.rs +++ b/polkadot/node/core/pvf/common/src/prepare.rs @@ -15,6 +15,25 @@ // along with Polkadot. If not, see . use parity_scale_codec::{Decode, Encode}; +use std::path::PathBuf; + +/// Result from prepare worker if successful. +#[derive(Debug, Clone, Default, Encode, Decode)] +pub struct PrepareWorkerSuccess { + /// Checksum of the compiled PVF. + pub checksum: String, + /// Stats of the current preparation run. + pub stats: PrepareStats, +} + +/// Result of PVF preparation if successful. +#[derive(Debug, Clone, Default)] +pub struct PrepareSuccess { + /// Canonical path to the compiled artifact. + pub path: PathBuf, + /// Stats of the current preparation run. + pub stats: PrepareStats, +} /// Preparation statistics, including the CPU time and memory taken. #[derive(Debug, Clone, Default, Encode, Decode)] @@ -29,12 +48,14 @@ pub struct PrepareStats { /// supported by the OS, `ru_maxrss`. #[derive(Clone, Debug, Default, Encode, Decode)] pub struct MemoryStats { - /// Memory stats from `tikv_jemalloc_ctl`. + /// Memory stats from `tikv_jemalloc_ctl`, polling-based and not very precise. #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] pub memory_tracker_stats: Option, /// `ru_maxrss` from `getrusage`. `None` if an error occurred. #[cfg(target_os = "linux")] pub max_rss: Option, + /// Peak allocation in bytes measured by tracking allocator + pub peak_tracked_alloc: u64, } /// Statistics of collected memory metrics. diff --git a/polkadot/node/core/pvf/common/src/pvf.rs b/polkadot/node/core/pvf/common/src/pvf.rs index 0cc86434c19526396b67565bee30c3a38dae9ac6..3f5b4d7ca70c14d53770af2dc470e4a2e5e572a4 100644 --- a/polkadot/node/core/pvf/common/src/pvf.rs +++ b/polkadot/node/core/pvf/common/src/pvf.rs @@ -18,7 +18,6 @@ use crate::prepare::PrepareJobKind; use parity_scale_codec::{Decode, Encode}; use polkadot_parachain_primitives::primitives::ValidationCodeHash; use polkadot_primitives::ExecutorParams; -use sp_core::blake2_256; use std::{ cmp::{Eq, PartialEq}, fmt, @@ -53,7 +52,7 @@ impl PvfPrepData { prep_kind: PrepareJobKind, ) -> Self { let code = Arc::new(code); - let code_hash = blake2_256(&code).into(); + let code_hash = sp_crypto_hashing::blake2_256(&code).into(); let executor_params = Arc::new(executor_params); Self { code, code_hash, executor_params, prep_timeout, prep_kind } } @@ -115,7 +114,7 @@ impl fmt::Debug for PvfPrepData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "Pvf {{ code, code_hash: {:?}, executor_params: {:?}, prep_timeout: {:?} }}", + "Pvf {{ code: [...], code_hash: {:?}, executor_params: {:?}, prep_timeout: {:?} }}", self.code_hash, self.executor_params, self.prep_timeout ) } diff --git a/polkadot/node/core/pvf/common/src/worker/mod.rs b/polkadot/node/core/pvf/common/src/worker/mod.rs index d0bd5b6bd7c56495efa8fe64730e9fadba9c066b..d7c95d9e7047f940a236e6d9abf1146ad8073566 100644 --- a/polkadot/node/core/pvf/common/src/worker/mod.rs +++ b/polkadot/node/core/pvf/common/src/worker/mod.rs @@ -18,26 +18,36 @@ pub mod security; -use crate::{SecurityStatus, LOG_TARGET}; +use crate::{framed_recv_blocking, SecurityStatus, WorkerHandshake, LOG_TARGET}; use cpu_time::ProcessTime; use futures::never::Never; +use parity_scale_codec::Decode; use std::{ any::Any, - fmt, - os::unix::net::UnixStream, + fmt::{self}, + fs::File, + io::{self, Read, Write}, + os::{ + fd::{AsRawFd, FromRawFd, RawFd}, + unix::net::UnixStream, + }, path::PathBuf, sync::mpsc::{Receiver, RecvTimeoutError}, time::Duration, }; -use tokio::{io, runtime::Runtime}; /// Use this macro to declare a `fn main() {}` that will create an executable that can be used for /// spawning the desired worker. #[macro_export] macro_rules! decl_worker_main { - ($expected_command:expr, $entrypoint:expr, $worker_version:expr $(,)*) => { + ($expected_command:expr, $entrypoint:expr, $worker_version:expr, $worker_version_hash:expr $(,)*) => { + fn get_full_version() -> String { + format!("{}-{}", $worker_version, $worker_version_hash) + } + fn print_help(expected_command: &str) { println!("{} {}", expected_command, $worker_version); + println!("commit: {}", $worker_version_hash); println!(); println!("PVF worker that is called by polkadot."); } @@ -46,8 +56,6 @@ macro_rules! decl_worker_main { #[cfg(target_os = "linux")] use $crate::worker::security; - // TODO: Remove this dependency, and `pub use sp_tracing` in `lib.rs`. - // See . $crate::sp_tracing::try_init_simple(); let worker_pid = std::process::id(); @@ -67,23 +75,45 @@ macro_rules! decl_worker_main { println!("{}", $worker_version); return }, + // Useful for debugging. --version is used for version checks. + "--full-version" => { + println!("{}", get_full_version()); + return + }, "--check-can-enable-landlock" => { #[cfg(target_os = "linux")] - let status = if security::landlock::check_is_fully_enabled() { 0 } else { -1 }; + let status = if let Err(err) = security::landlock::check_can_fully_enable() { + // Write the error to stderr, log it on the host-side. + eprintln!("{}", err); + -1 + } else { + 0 + }; #[cfg(not(target_os = "linux"))] let status = -1; std::process::exit(status) }, + "--check-can-enable-seccomp" => { + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + let status = if let Err(err) = security::seccomp::check_can_fully_enable() { + // Write the error to stderr, log it on the host-side. + eprintln!("{}", err); + -1 + } else { + 0 + }; + #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))] + let status = -1; + std::process::exit(status) + }, "--check-can-unshare-user-namespace-and-change-root" => { #[cfg(target_os = "linux")] - let status = if let Err(err) = security::unshare_user_namespace_and_change_root( - $crate::worker::WorkerKind::CheckPivotRoot, - worker_pid, - // We're not accessing any files, so we can try to pivot_root in the temp - // dir without conflicts with other processes. - &std::env::temp_dir(), - ) { + let cache_path_tempdir = std::path::Path::new(&args[2]); + #[cfg(target_os = "linux")] + let status = if let Err(err) = + security::change_root::check_can_fully_enable(&cache_path_tempdir) + { // Write the error to stderr, log it on the host-side. eprintln!("{}", err); -1 @@ -91,11 +121,22 @@ macro_rules! decl_worker_main { 0 }; #[cfg(not(target_os = "linux"))] - let status = { + let status = -1; + std::process::exit(status) + }, + "--check-can-do-secure-clone" => { + #[cfg(target_os = "linux")] + // SAFETY: new process is spawned within a single threaded process. This + // invariant is enforced by tests. + let status = if let Err(err) = unsafe { security::clone::check_can_fully_clone() } { // Write the error to stderr, log it on the host-side. - eprintln!("not available on macos"); + eprintln!("{}", err); -1 + } else { + 0 }; + #[cfg(not(target_os = "linux"))] + let status = -1; std::process::exit(status) }, @@ -118,8 +159,6 @@ macro_rules! decl_worker_main { let mut socket_path = None; let mut worker_dir_path = None; let mut node_version = None; - let mut can_enable_landlock = false; - let mut can_unshare_user_namespace_and_change_root = false; let mut i = 2; while i < args.len() { @@ -136,9 +175,6 @@ macro_rules! decl_worker_main { node_version = Some(args[i + 1].as_str()); i += 1 }, - "--can-enable-landlock" => can_enable_landlock = true, - "--can-unshare-user-namespace-and-change-root" => - can_unshare_user_namespace_and_change_root = true, arg => panic!("Unexpected argument found: {}", arg), } i += 1; @@ -149,22 +185,90 @@ macro_rules! decl_worker_main { let socket_path = std::path::Path::new(socket_path).to_owned(); let worker_dir_path = std::path::Path::new(worker_dir_path).to_owned(); - let security_status = $crate::SecurityStatus { - can_enable_landlock, - can_unshare_user_namespace_and_change_root, - }; - - $entrypoint( - socket_path, - worker_dir_path, - node_version, - Some($worker_version), - security_status, - ); + + $entrypoint(socket_path, worker_dir_path, node_version, Some($worker_version)); } }; } +//taken from the os_pipe crate. Copied here to reduce one dependency and +// because its type-safe abstractions do not play well with nix's clone +#[cfg(not(target_os = "macos"))] +pub fn pipe2_cloexec() -> io::Result<(libc::c_int, libc::c_int)> { + let mut fds: [libc::c_int; 2] = [0; 2]; + let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) }; + if res != 0 { + return Err(io::Error::last_os_error()) + } + Ok((fds[0], fds[1])) +} + +#[cfg(target_os = "macos")] +pub fn pipe2_cloexec() -> io::Result<(libc::c_int, libc::c_int)> { + let mut fds: [libc::c_int; 2] = [0; 2]; + let res = unsafe { libc::pipe(fds.as_mut_ptr()) }; + if res != 0 { + return Err(io::Error::last_os_error()) + } + let res = unsafe { libc::fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC) }; + if res != 0 { + return Err(io::Error::last_os_error()) + } + let res = unsafe { libc::fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC) }; + if res != 0 { + return Err(io::Error::last_os_error()) + } + Ok((fds[0], fds[1])) +} + +/// A wrapper around a file descriptor used to encapsulate and restrict +/// functionality for pipe operations. +pub struct PipeFd { + file: File, +} + +impl AsRawFd for PipeFd { + /// Returns the raw file descriptor associated with this `PipeFd` + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + +impl FromRawFd for PipeFd { + /// Creates a new `PipeFd` instance from a raw file descriptor. + /// + /// # Safety + /// + /// The fd passed in must be an owned file descriptor; in particular, it must be open. + unsafe fn from_raw_fd(fd: RawFd) -> Self { + PipeFd { file: File::from_raw_fd(fd) } + } +} + +impl Read for PipeFd { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.file.read(buf) + } + + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + self.file.read_to_end(buf) + } +} + +impl Write for PipeFd { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.file.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.file.write_all(buf) + } +} + /// Some allowed overhead that we account for in the "CPU time monitor" thread's sleeps, on the /// child process. pub const JOB_TIMEOUT_OVERHEAD: Duration = Duration::from_millis(50); @@ -186,94 +290,109 @@ impl fmt::Display for WorkerKind { } } -// The worker version must be passed in so that we accurately get the version of the worker, and not -// the version that this crate was compiled with. -pub fn worker_event_loop( +#[derive(Debug)] +pub struct WorkerInfo { + pub pid: u32, + pub kind: WorkerKind, + pub version: Option, + pub worker_dir_path: PathBuf, +} + +// NOTE: The worker version must be passed in so that we accurately get the version of the worker, +// and not the version that this crate was compiled with. +// +// NOTE: This must not spawn any threads due to safety requirements in `event_loop` and to avoid +// errors in [`security::change_root::try_restrict`]. +// +/// Initializes the worker process, then runs the given event loop, which spawns a new job process +/// to securely handle each incoming request. +pub fn run_worker( worker_kind: WorkerKind, socket_path: PathBuf, - #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] mut worker_dir_path: PathBuf, + worker_dir_path: PathBuf, node_version: Option<&str>, worker_version: Option<&str>, - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] security_status: &SecurityStatus, mut event_loop: F, ) where - F: FnMut(UnixStream, PathBuf) -> Fut, - Fut: futures::Future>, + F: FnMut(UnixStream, &WorkerInfo, SecurityStatus) -> io::Result, { - let worker_pid = std::process::id(); + #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] + let mut worker_info = WorkerInfo { + pid: std::process::id(), + kind: worker_kind, + version: worker_version.map(|v| v.to_string()), + worker_dir_path, + }; gum::debug!( target: LOG_TARGET, - %worker_pid, + ?worker_info, ?socket_path, - ?worker_dir_path, - ?security_status, "starting pvf worker ({})", - worker_kind + worker_info.kind ); // Check for a mismatch between the node and worker versions. - if let (Some(node_version), Some(worker_version)) = (node_version, worker_version) { + if let (Some(node_version), Some(worker_version)) = (node_version, &worker_info.version) { if node_version != worker_version { gum::error!( target: LOG_TARGET, - %worker_kind, - %worker_pid, + ?worker_info, %node_version, - %worker_version, "Node and worker version mismatch, node needs restarting, forcing shutdown", ); kill_parent_node_in_emergency(); - worker_shutdown_message(worker_kind, worker_pid, "Version mismatch"); - return + worker_shutdown(worker_info, "Version mismatch"); } } // Make sure that we can read the worker dir path, and log its contents. - let entries = || -> Result, io::Error> { - std::fs::read_dir(&worker_dir_path)? - .map(|res| res.map(|e| e.file_name())) - .collect() - }(); + let entries: io::Result> = std::fs::read_dir(&worker_info.worker_dir_path) + .and_then(|d| d.map(|res| res.map(|e| e.file_name())).collect()); match entries { Ok(entries) => - gum::trace!(target: LOG_TARGET, %worker_pid, ?worker_dir_path, "content of worker dir: {:?}", entries), + gum::trace!(target: LOG_TARGET, ?worker_info, "content of worker dir: {:?}", entries), Err(err) => { - gum::error!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - ?worker_dir_path, - "Could not read worker dir: {}", - err.to_string() - ); - worker_shutdown_message(worker_kind, worker_pid, &err.to_string()); - return + let err = format!("Could not read worker dir: {}", err.to_string()); + worker_shutdown_error(worker_info, &err); }, } // Connect to the socket. - let stream = || -> std::io::Result { + let stream = || -> io::Result { let stream = UnixStream::connect(&socket_path)?; let _ = std::fs::remove_file(&socket_path); Ok(stream) }(); - let stream = match stream { - Ok(s) => s, - Err(err) => { + let mut stream = match stream { + Ok(ok) => ok, + Err(err) => worker_shutdown_error(worker_info, &err.to_string()), + }; + + let WorkerHandshake { security_status } = match recv_worker_handshake(&mut stream) { + Ok(ok) => ok, + Err(err) => worker_shutdown_error(worker_info, &err.to_string()), + }; + + // Enable some security features. + { + gum::trace!(target: LOG_TARGET, ?security_status, "Enabling security features"); + + // First, make sure env vars were cleared, to match the environment we perform the checks + // within. (In theory, running checks with different env vars could result in different + // outcomes of the checks.) + if !security::check_env_vars_were_cleared(&worker_info) { + let err = "not all env vars were cleared when spawning the process"; gum::error!( target: LOG_TARGET, - %worker_kind, - %worker_pid, + ?worker_info, "{}", err ); - worker_shutdown_message(worker_kind, worker_pid, &err.to_string()); - return - }, - }; + if security_status.secure_validator_mode { + worker_shutdown(worker_info, err); + } + } - // Enable some security features. - { // Call based on whether we can change root. Error out if it should work but fails. // // NOTE: This should not be called in a multi-threaded context (i.e. inside the tokio @@ -282,77 +401,71 @@ pub fn worker_event_loop( // > CLONE_NEWUSER requires that the calling process is not threaded. #[cfg(target_os = "linux")] if security_status.can_unshare_user_namespace_and_change_root { - if let Err(err) = security::unshare_user_namespace_and_change_root( - worker_kind, - worker_pid, - &worker_dir_path, - ) { - // The filesystem may be in an inconsistent state, bail out. - gum::error!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - ?worker_dir_path, - "Could not change root to be the worker cache path: {}", - err - ); - worker_shutdown_message(worker_kind, worker_pid, &err); - return + if let Err(err) = security::change_root::enable_for_worker(&worker_info) { + // The filesystem may be in an inconsistent state, always bail out. + let err = format!("Could not change root to be the worker cache path: {}", err); + worker_shutdown_error(worker_info, &err); } - worker_dir_path = std::path::Path::new("/").to_owned(); + worker_info.worker_dir_path = std::path::Path::new("/").to_owned(); } #[cfg(target_os = "linux")] if security_status.can_enable_landlock { - let landlock_status = - security::landlock::enable_for_worker(worker_kind, worker_pid, &worker_dir_path); - if !matches!(landlock_status, Ok(landlock::RulesetStatus::FullyEnforced)) { - // We previously were able to enable, so this should never happen. - // - // TODO: Make this a real error in secure-mode. See: - // + if let Err(err) = security::landlock::enable_for_worker(&worker_info) { + // We previously were able to enable, so this should never happen. Shutdown if + // running in secure mode. + let err = format!("could not fully enable landlock: {:?}", err); gum::error!( target: LOG_TARGET, - %worker_kind, - %worker_pid, - "could not fully enable landlock: {:?}. This should not happen, please report to the Polkadot devs", - landlock_status + ?worker_info, + "{}. This should not happen, please report an issue", + err ); + if security_status.secure_validator_mode { + worker_shutdown(worker_info, &err); + } } } - if !security::check_env_vars_were_cleared(worker_kind, worker_pid) { - let err = "not all env vars were cleared when spawning the process"; - gum::error!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - "{}", - err - ); - worker_shutdown_message(worker_kind, worker_pid, err); - return + // TODO: We can enable the seccomp networking blacklist on aarch64 as well, but we need a CI + // job to catch regressions. See issue ci_cd/issues/609. + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + if security_status.can_enable_seccomp { + if let Err(err) = security::seccomp::enable_for_worker(&worker_info) { + // We previously were able to enable, so this should never happen. Shutdown if + // running in secure mode. + let err = format!("could not fully enable seccomp: {:?}", err); + gum::error!( + target: LOG_TARGET, + ?worker_info, + "{}. This should not happen, please report an issue", + err + ); + if security_status.secure_validator_mode { + worker_shutdown(worker_info, &err); + } + } } } // Run the main worker loop. - let rt = Runtime::new().expect("Creates tokio runtime. If this panics the worker will die and the host will detect that and deal with it."); - let err = rt - .block_on(event_loop(stream, worker_dir_path)) + let err = event_loop(stream, &worker_info, security_status) // It's never `Ok` because it's `Ok(Never)`. .unwrap_err(); - worker_shutdown_message(worker_kind, worker_pid, &err.to_string()); + worker_shutdown(worker_info, &err.to_string()); +} - // We don't want tokio to wait for the tasks to finish. We want to bring down the worker as fast - // as possible and not wait for stalled validation to finish. This isn't strictly necessary now, - // but may be in the future. - rt.shutdown_background(); +/// Provide a consistent message on unexpected worker shutdown. +fn worker_shutdown(worker_info: WorkerInfo, err: &str) -> ! { + gum::warn!(target: LOG_TARGET, ?worker_info, "quitting pvf worker ({}): {}", worker_info.kind, err); + std::process::exit(1); } -/// Provide a consistent message on worker shutdown. -fn worker_shutdown_message(worker_kind: WorkerKind, worker_pid: u32, err: &str) { - gum::debug!(target: LOG_TARGET, %worker_pid, "quitting pvf worker ({}): {}", worker_kind, err); +/// Provide a consistent error on unexpected worker shutdown. +fn worker_shutdown_error(worker_info: WorkerInfo, err: &str) -> ! { + gum::error!(target: LOG_TARGET, ?worker_info, "quitting pvf worker ({}): {}", worker_info.kind, err); + std::process::exit(1); } /// Loop that runs in the CPU time monitor thread on prepare and execute jobs. Continuously wakes up @@ -423,12 +536,24 @@ fn kill_parent_node_in_emergency() { } } +/// Receives a handshake with information for the worker. +fn recv_worker_handshake(stream: &mut UnixStream) -> io::Result { + let worker_handshake = framed_recv_blocking(stream)?; + let worker_handshake = WorkerHandshake::decode(&mut &worker_handshake[..]).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("recv_worker_handshake: failed to decode WorkerHandshake: {}", e), + ) + })?; + Ok(worker_handshake) +} + /// Functionality related to threads spawned by the workers. /// /// The motivation for this module is to coordinate worker threads without using async Rust. pub mod thread { use std::{ - panic, + io, panic, sync::{Arc, Condvar, Mutex}, thread, time::Duration, @@ -469,7 +594,7 @@ pub mod thread { f: F, cond: Cond, outcome: WaitOutcome, - ) -> std::io::Result> + ) -> io::Result> where F: FnOnce() -> R, F: Send + 'static + panic::UnwindSafe, @@ -487,7 +612,7 @@ pub mod thread { cond: Cond, outcome: WaitOutcome, stack_size: usize, - ) -> std::io::Result> + ) -> io::Result> where F: FnOnce() -> R, F: Send + 'static + panic::UnwindSafe, diff --git a/polkadot/node/core/pvf/common/src/worker/security.rs b/polkadot/node/core/pvf/common/src/worker/security.rs deleted file mode 100644 index 1b7614177448539f4263022c757d3eba4557ecac..0000000000000000000000000000000000000000 --- a/polkadot/node/core/pvf/common/src/worker/security.rs +++ /dev/null @@ -1,512 +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 . - -//! Functionality for securing workers. -//! -//! This is needed because workers are used to compile and execute untrusted code (PVFs). -//! -//! We currently employ the following security measures: -//! -//! - Restrict filesystem -//! - Use Landlock to remove all unnecessary FS access rights. -//! - Unshare the user and mount namespaces. -//! - Change the root directory to a worker-specific temporary directory. -//! - Remove env vars - -use crate::{worker::WorkerKind, LOG_TARGET}; - -/// Unshare the user namespace and change root to be the artifact directory. -/// -/// NOTE: This should not be called in a multi-threaded context. `unshare(2)`: -/// "CLONE_NEWUSER requires that the calling process is not threaded." -#[cfg(target_os = "linux")] -pub fn unshare_user_namespace_and_change_root( - worker_kind: WorkerKind, - worker_pid: u32, - worker_dir_path: &std::path::Path, -) -> Result<(), String> { - use std::{env, ffi::CString, os::unix::ffi::OsStrExt, path::Path, ptr}; - - // The following was copied from the `cstr_core` crate. - // - // TODO: Remove this once this is stable: https://github.com/rust-lang/rust/issues/105723 - #[inline] - #[doc(hidden)] - const fn cstr_is_valid(bytes: &[u8]) -> bool { - if bytes.is_empty() || bytes[bytes.len() - 1] != 0 { - return false - } - - let mut index = 0; - while index < bytes.len() - 1 { - if bytes[index] == 0 { - return false - } - index += 1; - } - true - } - - macro_rules! cstr { - ($e:expr) => {{ - const STR: &[u8] = concat!($e, "\0").as_bytes(); - const STR_VALID: bool = cstr_is_valid(STR); - let _ = [(); 0 - (!(STR_VALID) as usize)]; - #[allow(unused_unsafe)] - unsafe { - core::ffi::CStr::from_bytes_with_nul_unchecked(STR) - } - }} - } - - gum::debug!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - ?worker_dir_path, - "unsharing the user namespace and calling pivot_root", - ); - - let worker_dir_path_c = CString::new(worker_dir_path.as_os_str().as_bytes()) - .expect("on unix; the path will never contain 0 bytes; qed"); - - // Wrapper around all the work to prevent repetitive error handling. - // - // # Errors - // - // It's the caller's responsibility to call `Error::last_os_error`. Note that that alone does - // not give the context of which call failed, so we return a &str error. - || -> Result<(), &'static str> { - // SAFETY: We pass null-terminated C strings and use the APIs as documented. In fact, steps - // (2) and (3) are adapted from the example in pivot_root(2), with the additional - // change described in the `pivot_root(".", ".")` section. - unsafe { - // 1. `unshare` the user and the mount namespaces. - if libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNS) < 0 { - return Err("unshare user and mount namespaces") - } - - // 2. Setup mounts. - // - // Ensure that new root and its parent mount don't have shared propagation (which would - // cause pivot_root() to return an error), and prevent propagation of mount events to - // the initial mount namespace. - if libc::mount( - ptr::null(), - cstr!("/").as_ptr(), - ptr::null(), - libc::MS_REC | libc::MS_PRIVATE, - ptr::null(), - ) < 0 - { - return Err("mount MS_PRIVATE") - } - // Ensure that the new root is a mount point. - let additional_flags = - if let WorkerKind::Execute | WorkerKind::CheckPivotRoot = worker_kind { - libc::MS_RDONLY - } else { - 0 - }; - if libc::mount( - worker_dir_path_c.as_ptr(), - worker_dir_path_c.as_ptr(), - ptr::null(), // ignored when MS_BIND is used - libc::MS_BIND | - libc::MS_REC | libc::MS_NOEXEC | - libc::MS_NODEV | libc::MS_NOSUID | - libc::MS_NOATIME | additional_flags, - ptr::null(), // ignored when MS_BIND is used - ) < 0 - { - return Err("mount MS_BIND") - } - - // 3. `pivot_root` to the artifact directory. - if libc::chdir(worker_dir_path_c.as_ptr()) < 0 { - return Err("chdir to worker dir path") - } - if libc::syscall(libc::SYS_pivot_root, cstr!(".").as_ptr(), cstr!(".").as_ptr()) < 0 { - return Err("pivot_root") - } - if libc::umount2(cstr!(".").as_ptr(), libc::MNT_DETACH) < 0 { - return Err("umount the old root mount point") - } - } - - Ok(()) - }() - .map_err(|err_ctx| { - let err = std::io::Error::last_os_error(); - format!("{}: {}", err_ctx, err) - })?; - - // Do some assertions. - if env::current_dir().map_err(|err| err.to_string())? != Path::new("/") { - return Err("expected current dir after pivot_root to be `/`".into()) - } - env::set_current_dir("..").map_err(|err| err.to_string())?; - if env::current_dir().map_err(|err| err.to_string())? != Path::new("/") { - return Err("expected not to be able to break out of new root by doing `..`".into()) - } - - Ok(()) -} - -/// Require env vars to have been removed when spawning the process, to prevent malicious code from -/// accessing them. -pub fn check_env_vars_were_cleared(worker_kind: WorkerKind, worker_pid: u32) -> bool { - let mut ok = true; - - for (key, value) in std::env::vars_os() { - // TODO: *theoretically* the value (or mere presence) of `RUST_LOG` can be a source of - // randomness for malicious code. In the future we can remove it also and log in the host; - // see . - if key == "RUST_LOG" { - continue - } - // An exception for MacOS. This is not a secure platform anyway, so we let it slide. - #[cfg(target_os = "macos")] - if key == "__CF_USER_TEXT_ENCODING" { - continue - } - - gum::error!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - ?key, - ?value, - "env var was present that should have been removed", - ); - - ok = false; - } - - ok -} - -/// The [landlock] docs say it best: -/// -/// > "Landlock is a security feature available since Linux 5.13. The goal is to enable to restrict -/// ambient rights (e.g., global filesystem access) for a set of processes by creating safe security -/// sandboxes as new security layers in addition to the existing system-wide access-controls. This -/// kind of sandbox is expected to help mitigate the security impact of bugs, unexpected or -/// malicious behaviors in applications. Landlock empowers any process, including unprivileged ones, -/// to securely restrict themselves." -/// -/// [landlock]: https://docs.rs/landlock/latest/landlock/index.html -#[cfg(target_os = "linux")] -pub mod landlock { - pub use landlock::RulesetStatus; - - use crate::{worker::WorkerKind, LOG_TARGET}; - use landlock::*; - use std::{ - fmt, - path::{Path, PathBuf}, - }; - - /// Landlock ABI version. We use ABI V1 because: - /// - /// 1. It is supported by our reference kernel version. - /// 2. Later versions do not (yet) provide additional security that would benefit us. - /// - /// # Versions (as of October 2023) - /// - /// - Polkadot reference kernel version: 5.16+ - /// - /// - ABI V1: kernel 5.13 - Introduces landlock, including full restrictions on file reads. - /// - /// - ABI V2: kernel 5.19 - Adds ability to prevent file renaming. Does not help us. During - /// execution an attacker can only affect the name of a symlinked artifact and not the - /// original one. - /// - /// - ABI V3: kernel 6.2 - Adds ability to prevent file truncation. During execution, can - /// prevent attackers from affecting a symlinked artifact. We don't strictly need this as we - /// plan to check for file integrity anyway; see - /// . - /// - /// # Determinism - /// - /// You may wonder whether we could always use the latest ABI instead of only the ABI supported - /// by the reference kernel version. It seems plausible, since landlock provides a best-effort - /// approach to enabling sandboxing. For example, if the reference version only supported V1 and - /// we were on V2, then landlock would use V2 if it was supported on the current machine, and - /// just fall back to V1 if not. - /// - /// The issue with this is indeterminacy. If half of validators were on V2 and half were on V1, - /// they may have different semantics on some PVFs. So a malicious PVF now has a new attack - /// vector: they can exploit this indeterminism between landlock ABIs! - /// - /// On the other hand we do want validators to be as secure as possible and protect their keys - /// from attackers. And, the risk with indeterminacy is low and there are other indeterminacy - /// vectors anyway. So we will only upgrade to a new ABI if either the reference kernel version - /// supports it or if it introduces some new feature that is beneficial to security. - pub const LANDLOCK_ABI: ABI = ABI::V1; - - #[derive(Debug)] - pub enum TryRestrictError { - InvalidExceptionPath(PathBuf), - RulesetError(RulesetError), - } - - impl From for TryRestrictError { - fn from(err: RulesetError) -> Self { - Self::RulesetError(err) - } - } - - impl fmt::Display for TryRestrictError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidExceptionPath(path) => write!(f, "invalid exception path: {:?}", path), - Self::RulesetError(err) => write!(f, "ruleset error: {}", err.to_string()), - } - } - } - - impl std::error::Error for TryRestrictError {} - - /// Try to enable landlock for the given kind of worker. - pub fn enable_for_worker( - worker_kind: WorkerKind, - worker_pid: u32, - worker_dir_path: &Path, - ) -> Result> { - let exceptions: Vec<(PathBuf, BitFlags)> = match worker_kind { - WorkerKind::Prepare => { - vec![(worker_dir_path.to_owned(), AccessFs::WriteFile.into())] - }, - WorkerKind::Execute => { - vec![(worker_dir_path.to_owned(), AccessFs::ReadFile.into())] - }, - WorkerKind::CheckPivotRoot => - panic!("this should only be passed for checking pivot_root; qed"), - }; - - gum::debug!( - target: LOG_TARGET, - %worker_kind, - %worker_pid, - ?worker_dir_path, - "enabling landlock with exceptions: {:?}", - exceptions, - ); - - Ok(try_restrict(exceptions)?) - } - - // TODO: - /// Runs a check for landlock and returns a single bool indicating whether the given landlock - /// ABI is fully enabled on the current Linux environment. - pub fn check_is_fully_enabled() -> bool { - let status_from_thread: Result> = - match std::thread::spawn(|| try_restrict(std::iter::empty::<(PathBuf, AccessFs)>())) - .join() - { - Ok(Ok(status)) => Ok(status), - Ok(Err(ruleset_err)) => Err(ruleset_err.into()), - Err(_err) => Err("a panic occurred in try_restrict".into()), - }; - - matches!(status_from_thread, Ok(RulesetStatus::FullyEnforced)) - } - - /// Tries to restrict the current thread (should only be called in a process' main thread) with - /// the following landlock access controls: - /// - /// 1. all global filesystem access restricted, with optional exceptions - /// 2. ... more sandbox types (e.g. networking) may be supported in the future. - /// - /// If landlock is not supported in the current environment this is simply a noop. - /// - /// # Returns - /// - /// The status of the restriction (whether it was fully, partially, or not-at-all enforced). - fn try_restrict(fs_exceptions: I) -> Result - where - I: IntoIterator, - P: AsRef, - A: Into>, - { - let mut ruleset = - Ruleset::default().handle_access(AccessFs::from_all(LANDLOCK_ABI))?.create()?; - for (fs_path, access_bits) in fs_exceptions { - let paths = &[fs_path.as_ref().to_owned()]; - let mut rules = path_beneath_rules(paths, access_bits).peekable(); - if rules.peek().is_none() { - // `path_beneath_rules` silently ignores missing paths, so check for it manually. - return Err(TryRestrictError::InvalidExceptionPath(fs_path.as_ref().to_owned())) - } - ruleset = ruleset.add_rules(rules)?; - } - let status = ruleset.restrict_self()?; - Ok(status.ruleset) - } - - #[cfg(test)] - mod tests { - use super::*; - use std::{fs, io::ErrorKind, thread}; - - #[test] - fn restricted_thread_cannot_read_file() { - // TODO: This would be nice: . - if !check_is_fully_enabled() { - return - } - - // Restricted thread cannot read from FS. - let handle = - thread::spawn(|| { - // Create, write, and read two tmp files. This should succeed before any - // landlock restrictions are applied. - const TEXT: &str = "foo"; - let tmpfile1 = tempfile::NamedTempFile::new().unwrap(); - let path1 = tmpfile1.path(); - let tmpfile2 = tempfile::NamedTempFile::new().unwrap(); - let path2 = tmpfile2.path(); - - fs::write(path1, TEXT).unwrap(); - let s = fs::read_to_string(path1).unwrap(); - assert_eq!(s, TEXT); - fs::write(path2, TEXT).unwrap(); - let s = fs::read_to_string(path2).unwrap(); - assert_eq!(s, TEXT); - - // Apply Landlock with a read exception for only one of the files. - let status = try_restrict(vec![(path1, AccessFs::ReadFile)]); - if !matches!(status, Ok(RulesetStatus::FullyEnforced)) { - panic!("Ruleset should be enforced since we checked if landlock is enabled: {:?}", status); - } - - // Try to read from both files, only tmpfile1 should succeed. - let result = fs::read_to_string(path1); - assert!(matches!( - result, - Ok(s) if s == TEXT - )); - let result = fs::read_to_string(path2); - assert!(matches!( - result, - Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) - )); - - // Apply Landlock for all files. - let status = try_restrict(std::iter::empty::<(PathBuf, AccessFs)>()); - if !matches!(status, Ok(RulesetStatus::FullyEnforced)) { - panic!("Ruleset should be enforced since we checked if landlock is enabled: {:?}", status); - } - - // Try to read from tmpfile1 after landlock, it should fail. - let result = fs::read_to_string(path1); - assert!(matches!( - result, - Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) - )); - }); - - assert!(handle.join().is_ok()); - } - - #[test] - fn restricted_thread_cannot_write_file() { - // TODO: This would be nice: . - if !check_is_fully_enabled() { - return - } - - // Restricted thread cannot write to FS. - let handle = - thread::spawn(|| { - // Create and write two tmp files. This should succeed before any landlock - // restrictions are applied. - const TEXT: &str = "foo"; - let tmpfile1 = tempfile::NamedTempFile::new().unwrap(); - let path1 = tmpfile1.path(); - let tmpfile2 = tempfile::NamedTempFile::new().unwrap(); - let path2 = tmpfile2.path(); - - fs::write(path1, TEXT).unwrap(); - fs::write(path2, TEXT).unwrap(); - - // Apply Landlock with a write exception for only one of the files. - let status = try_restrict(vec![(path1, AccessFs::WriteFile)]); - if !matches!(status, Ok(RulesetStatus::FullyEnforced)) { - panic!("Ruleset should be enforced since we checked if landlock is enabled: {:?}", status); - } - - // Try to write to both files, only tmpfile1 should succeed. - let result = fs::write(path1, TEXT); - assert!(matches!(result, Ok(_))); - let result = fs::write(path2, TEXT); - assert!(matches!( - result, - Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) - )); - - // Apply Landlock for all files. - let status = try_restrict(std::iter::empty::<(PathBuf, AccessFs)>()); - if !matches!(status, Ok(RulesetStatus::FullyEnforced)) { - panic!("Ruleset should be enforced since we checked if landlock is enabled: {:?}", status); - } - - // Try to write to tmpfile1 after landlock, it should fail. - let result = fs::write(path1, TEXT); - assert!(matches!( - result, - Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) - )); - }); - - assert!(handle.join().is_ok()); - } - - // Test that checks whether landlock under our ABI version is able to truncate files. - #[test] - fn restricted_thread_can_truncate_file() { - // TODO: This would be nice: . - if !check_is_fully_enabled() { - return - } - - // Restricted thread can truncate file. - let handle = - thread::spawn(|| { - // Create and write a file. This should succeed before any landlock - // restrictions are applied. - const TEXT: &str = "foo"; - let tmpfile = tempfile::NamedTempFile::new().unwrap(); - let path = tmpfile.path(); - - fs::write(path, TEXT).unwrap(); - - // Apply Landlock with all exceptions under the current ABI. - let status = try_restrict(vec![(path, AccessFs::from_all(LANDLOCK_ABI))]); - if !matches!(status, Ok(RulesetStatus::FullyEnforced)) { - panic!("Ruleset should be enforced since we checked if landlock is enabled: {:?}", status); - } - - // Try to truncate the file. - let result = tmpfile.as_file().set_len(0); - assert!(result.is_ok()); - }); - - assert!(handle.join().is_ok()); - } - } -} diff --git a/polkadot/node/core/pvf/common/src/worker/security/change_root.rs b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ec66906819f189910ccec6d61081e4575a17957 --- /dev/null +++ b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs @@ -0,0 +1,165 @@ +// 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 . + +//! Functionality for securing workers by unsharing some namespaces from other processes and +//! changing the root. + +use crate::{ + worker::{WorkerInfo, WorkerKind}, + LOG_TARGET, +}; +use std::{env, ffi::CString, io, os::unix::ffi::OsStrExt, path::Path, ptr}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("{0}")] + OsErrWithContext(String), + #[error(transparent)] + Io(#[from] io::Error), + #[error("assertion failed: {0}")] + AssertionFailed(String), +} + +pub type Result = std::result::Result; + +/// Try to enable for the given kind of worker. +/// +/// NOTE: This should not be called in a multi-threaded context. `unshare(2)`: +/// "CLONE_NEWUSER requires that the calling process is not threaded." +pub fn enable_for_worker(worker_info: &WorkerInfo) -> Result<()> { + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "enabling change-root", + ); + + try_restrict(worker_info) +} + +/// Runs a check for unshare-and-change-root and returns an error indicating whether it can be fully +/// enabled on the current Linux environment. +/// +/// NOTE: This should not be called in a multi-threaded context. `unshare(2)`: +/// "CLONE_NEWUSER requires that the calling process is not threaded." +pub fn check_can_fully_enable(tempdir: &Path) -> Result<()> { + let worker_dir_path = tempdir.to_owned(); + try_restrict(&WorkerInfo { + pid: std::process::id(), + kind: WorkerKind::CheckPivotRoot, + version: None, + worker_dir_path, + }) +} + +/// Unshare the user namespace and change root to be the worker directory. +/// +/// NOTE: This should not be called in a multi-threaded context. `unshare(2)`: +/// "CLONE_NEWUSER requires that the calling process is not threaded." +fn try_restrict(worker_info: &WorkerInfo) -> Result<()> { + // TODO: Remove this once this is stable: https://github.com/rust-lang/rust/issues/105723 + macro_rules! cstr_ptr { + ($e:expr) => { + concat!($e, "\0").as_ptr().cast::() + }; + } + + let worker_dir_path_c = CString::new(worker_info.worker_dir_path.as_os_str().as_bytes()) + .expect("on unix; the path will never contain 0 bytes; qed"); + + // Wrapper around all the work to prevent repetitive error handling. + // + // # Errors + // + // It's the caller's responsibility to call `Error::last_os_error`. Note that that alone does + // not give the context of which call failed, so we return a &str error. + || -> std::result::Result<(), &'static str> { + // SAFETY: We pass null-terminated C strings and use the APIs as documented. In fact, steps + // (2) and (3) are adapted from the example in pivot_root(2), with the additional + // change described in the `pivot_root(".", ".")` section. + unsafe { + // 1. `unshare` the user and the mount namespaces. + if libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNS) < 0 { + return Err("unshare user and mount namespaces") + } + + // 2. Setup mounts. + // + // Ensure that new root and its parent mount don't have shared propagation (which would + // cause pivot_root() to return an error), and prevent propagation of mount events to + // the initial mount namespace. + if libc::mount( + ptr::null(), + cstr_ptr!("/"), + ptr::null(), + libc::MS_REC | libc::MS_PRIVATE, + ptr::null(), + ) < 0 + { + return Err("mount MS_PRIVATE") + } + // Ensure that the new root is a mount point. + let additional_flags = + if let WorkerKind::Execute | WorkerKind::CheckPivotRoot = worker_info.kind { + libc::MS_RDONLY + } else { + 0 + }; + if libc::mount( + worker_dir_path_c.as_ptr(), + worker_dir_path_c.as_ptr(), + ptr::null(), // ignored when MS_BIND is used + libc::MS_BIND | + libc::MS_REC | libc::MS_NOEXEC | + libc::MS_NODEV | libc::MS_NOSUID | + libc::MS_NOATIME | additional_flags, + ptr::null(), // ignored when MS_BIND is used + ) < 0 + { + return Err("mount MS_BIND") + } + + // 3. `pivot_root` to the artifact directory. + if libc::chdir(worker_dir_path_c.as_ptr()) < 0 { + return Err("chdir to worker dir path") + } + if libc::syscall(libc::SYS_pivot_root, cstr_ptr!("."), cstr_ptr!(".")) < 0 { + return Err("pivot_root") + } + if libc::umount2(cstr_ptr!("."), libc::MNT_DETACH) < 0 { + return Err("umount the old root mount point") + } + } + + Ok(()) + }() + .map_err(|err_ctx| { + let err = io::Error::last_os_error(); + Error::OsErrWithContext(format!("{}: {}", err_ctx, err)) + })?; + + // Do some assertions. + if env::current_dir()? != Path::new("/") { + return Err(Error::AssertionFailed("expected current dir after pivot_root to be `/`".into())) + } + env::set_current_dir("..")?; + if env::current_dir()? != Path::new("/") { + return Err(Error::AssertionFailed( + "expected not to be able to break out of new root by doing `..`".into(), + )) + } + + Ok(()) +} diff --git a/polkadot/node/core/pvf/common/src/worker/security/clone.rs b/polkadot/node/core/pvf/common/src/worker/security/clone.rs new file mode 100644 index 0000000000000000000000000000000000000000..707f68d185911d21754bcdf8a3a84e0eabfff79f --- /dev/null +++ b/polkadot/node/core/pvf/common/src/worker/security/clone.rs @@ -0,0 +1,93 @@ +// 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 . + +//! Functionality for securing the job processes spawned by the workers using `clone`. If +//! unsupported, falls back to `fork`. + +use crate::{worker::WorkerInfo, LOG_TARGET}; +use nix::{ + errno::Errno, + sched::{CloneCb, CloneFlags}, + unistd::Pid, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("could not clone, errno: {0}")] + Clone(Errno), +} + +pub type Result = std::result::Result; + +/// Try to run clone(2) on the current worker. +/// +/// SAFETY: new process should be either spawned within a single threaded process, or use only +/// async-signal-safe functions. +pub unsafe fn clone_on_worker( + worker_info: &WorkerInfo, + have_unshare_newuser: bool, + cb: CloneCb, +) -> Result { + let flags = clone_flags(have_unshare_newuser); + + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "calling clone with flags: {:?}", + flags + ); + + try_clone(cb, flags) +} + +/// Runs a check for clone(2) with all sandboxing flags and returns an error indicating whether it +/// can be fully enabled on the current Linux environment. +/// +/// SAFETY: new process should be either spawned within a single threaded process, or use only +/// async-signal-safe functions. +pub unsafe fn check_can_fully_clone() -> Result<()> { + try_clone(Box::new(|| 0), clone_flags(false)).map(|_pid| ()) +} + +/// Runs clone(2) with all sandboxing flags. +/// +/// SAFETY: new process should be either spawned within a single threaded process, or use only +/// async-signal-safe functions. +unsafe fn try_clone(cb: CloneCb, flags: CloneFlags) -> Result { + let mut stack = [0u8; 2 * 1024 * 1024]; + + nix::sched::clone(cb, stack.as_mut_slice(), flags, None).map_err(|errno| Error::Clone(errno)) +} + +/// Returns flags for `clone(2)`, including all the sandbox-related ones. +fn clone_flags(have_unshare_newuser: bool) -> CloneFlags { + // NOTE: CLONE_NEWUSER does not work in `clone` if we previously called `unshare` with this + // flag. On the other hand, if we did not call `unshare` we need this flag for the CAP_SYS_ADMIN + // capability. + let maybe_clone_newuser = + if have_unshare_newuser { CloneFlags::empty() } else { CloneFlags::CLONE_NEWUSER }; + // SIGCHLD flag is used to inform clone that the parent process is + // expecting a child termination signal, without this flag `waitpid` function + // return `ECHILD` error. + maybe_clone_newuser | + CloneFlags::CLONE_NEWCGROUP | + CloneFlags::CLONE_NEWIPC | + CloneFlags::CLONE_NEWNET | + CloneFlags::CLONE_NEWNS | + CloneFlags::CLONE_NEWPID | + CloneFlags::CLONE_NEWUTS | + CloneFlags::from_bits_retain(libc::SIGCHLD) +} diff --git a/polkadot/node/core/pvf/common/src/worker/security/landlock.rs b/polkadot/node/core/pvf/common/src/worker/security/landlock.rs new file mode 100644 index 0000000000000000000000000000000000000000..10d00a0e2c66c229131b43da158527aba27184f1 --- /dev/null +++ b/polkadot/node/core/pvf/common/src/worker/security/landlock.rs @@ -0,0 +1,322 @@ +// 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 [landlock] docs say it best: +//! +//! > "Landlock is a security feature available since Linux 5.13. The goal is to enable to restrict +//! ambient rights (e.g., global filesystem access) for a set of processes by creating safe security +//! sandboxes as new security layers in addition to the existing system-wide access-controls. This +//! kind of sandbox is expected to help mitigate the security impact of bugs, unexpected or +//! malicious behaviors in applications. Landlock empowers any process, including unprivileged ones, +//! to securely restrict themselves." +//! +//! [landlock]: https://docs.rs/landlock/latest/landlock/index.html + +pub use landlock::RulesetStatus; + +use crate::{ + worker::{stringify_panic_payload, WorkerInfo, WorkerKind}, + LOG_TARGET, +}; +use landlock::*; +use std::path::{Path, PathBuf}; + +/// Landlock ABI version. We use ABI V1 because: +/// +/// 1. It is supported by our reference kernel version. +/// 2. Later versions do not (yet) provide additional security that would benefit us. +/// +/// # Versions (as of October 2023) +/// +/// - Polkadot reference kernel version: 5.16+ +/// +/// - ABI V1: kernel 5.13 - Introduces landlock, including full restrictions on file reads. +/// +/// - ABI V2: kernel 5.19 - Adds ability to prevent file renaming. Does not help us. During +/// execution an attacker can only affect the name of a symlinked artifact and not the original +/// one. +/// +/// - ABI V3: kernel 6.2 - Adds ability to prevent file truncation. During execution, can +/// prevent attackers from affecting a symlinked artifact. We don't strictly need this as we +/// plan to check for file integrity anyway; see +/// . +/// +/// # Determinism +/// +/// You may wonder whether we could always use the latest ABI instead of only the ABI supported +/// by the reference kernel version. It seems plausible, since landlock provides a best-effort +/// approach to enabling sandboxing. For example, if the reference version only supported V1 and +/// we were on V2, then landlock would use V2 if it was supported on the current machine, and +/// just fall back to V1 if not. +/// +/// The issue with this is indeterminacy. If half of validators were on V2 and half were on V1, +/// they may have different semantics on some PVFs. So a malicious PVF now has a new attack +/// vector: they can exploit this indeterminism between landlock ABIs! +/// +/// On the other hand we do want validators to be as secure as possible and protect their keys +/// from attackers. And, the risk with indeterminacy is low and there are other indeterminacy +/// vectors anyway. So we will only upgrade to a new ABI if either the reference kernel version +/// supports it or if it introduces some new feature that is beneficial to security. +pub const LANDLOCK_ABI: ABI = ABI::V1; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Could not fully enable: {0:?}")] + NotFullyEnabled(RulesetStatus), + #[error("Invalid exception path: {0:?}")] + InvalidExceptionPath(PathBuf), + #[error(transparent)] + RulesetError(#[from] RulesetError), + #[error("A panic occurred in try_restrict: {0}")] + Panic(String), +} + +pub type Result = std::result::Result; + +/// Try to enable landlock for the given kind of worker. +pub fn enable_for_worker(worker_info: &WorkerInfo) -> Result<()> { + let exceptions: Vec<(PathBuf, BitFlags)> = match worker_info.kind { + WorkerKind::Prepare => { + vec![(worker_info.worker_dir_path.to_owned(), AccessFs::WriteFile.into())] + }, + WorkerKind::Execute => { + vec![(worker_info.worker_dir_path.to_owned(), AccessFs::ReadFile.into())] + }, + WorkerKind::CheckPivotRoot => + panic!("this should only be passed for checking pivot_root; qed"), + }; + + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "enabling landlock with exceptions: {:?}", + exceptions, + ); + + try_restrict(exceptions) +} + +// TODO: +/// Runs a check for landlock in its own thread, and returns an error indicating whether the given +/// landlock ABI is fully enabled on the current Linux environment. +pub fn check_can_fully_enable() -> Result<()> { + match std::thread::spawn(|| try_restrict(std::iter::empty::<(PathBuf, AccessFs)>())).join() { + Ok(Ok(())) => Ok(()), + Ok(Err(err)) => Err(err), + Err(err) => Err(Error::Panic(stringify_panic_payload(err))), + } +} + +/// Tries to restrict the current thread (should only be called in a process' main thread) with +/// the following landlock access controls: +/// +/// 1. all global filesystem access restricted, with optional exceptions +/// 2. ... more sandbox types (e.g. networking) may be supported in the future. +/// +/// If landlock is not supported in the current environment this is simply a noop. +/// +/// # Returns +/// +/// The status of the restriction (whether it was fully, partially, or not-at-all enforced). +fn try_restrict(fs_exceptions: I) -> Result<()> +where + I: IntoIterator, + P: AsRef, + A: Into>, +{ + let mut ruleset = + Ruleset::default().handle_access(AccessFs::from_all(LANDLOCK_ABI))?.create()?; + for (fs_path, access_bits) in fs_exceptions { + let paths = &[fs_path.as_ref().to_owned()]; + let mut rules = path_beneath_rules(paths, access_bits).peekable(); + if rules.peek().is_none() { + // `path_beneath_rules` silently ignores missing paths, so check for it manually. + return Err(Error::InvalidExceptionPath(fs_path.as_ref().to_owned())) + } + ruleset = ruleset.add_rules(rules)?; + } + + let status = ruleset.restrict_self()?; + if !matches!(status.ruleset, RulesetStatus::FullyEnforced) { + return Err(Error::NotFullyEnabled(status.ruleset)) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{fs, io::ErrorKind, thread}; + + #[test] + fn restricted_thread_cannot_read_file() { + // TODO: This would be nice: . + if check_can_fully_enable().is_err() { + return + } + + // Restricted thread cannot read from FS. + let handle = thread::spawn(|| { + // Create, write, and read two tmp files. This should succeed before any + // landlock restrictions are applied. + const TEXT: &str = "foo"; + let tmpfile1 = tempfile::NamedTempFile::new().unwrap(); + let path1 = tmpfile1.path(); + let tmpfile2 = tempfile::NamedTempFile::new().unwrap(); + let path2 = tmpfile2.path(); + + fs::write(path1, TEXT).unwrap(); + let s = fs::read_to_string(path1).unwrap(); + assert_eq!(s, TEXT); + fs::write(path2, TEXT).unwrap(); + let s = fs::read_to_string(path2).unwrap(); + assert_eq!(s, TEXT); + + // Apply Landlock with a read exception for only one of the files. + let status = try_restrict(vec![(path1, AccessFs::ReadFile)]); + if !matches!(status, Ok(())) { + panic!( + "Ruleset should be enforced since we checked if landlock is enabled: {:?}", + status + ); + } + + // Try to read from both files, only tmpfile1 should succeed. + let result = fs::read_to_string(path1); + assert!(matches!( + result, + Ok(s) if s == TEXT + )); + let result = fs::read_to_string(path2); + assert!(matches!( + result, + Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) + )); + + // Apply Landlock for all files. + let status = try_restrict(std::iter::empty::<(PathBuf, AccessFs)>()); + if !matches!(status, Ok(())) { + panic!( + "Ruleset should be enforced since we checked if landlock is enabled: {:?}", + status + ); + } + + // Try to read from tmpfile1 after landlock, it should fail. + let result = fs::read_to_string(path1); + assert!(matches!( + result, + Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) + )); + }); + + assert!(handle.join().is_ok()); + } + + #[test] + fn restricted_thread_cannot_write_file() { + // TODO: This would be nice: . + if check_can_fully_enable().is_err() { + return + } + + // Restricted thread cannot write to FS. + let handle = thread::spawn(|| { + // Create and write two tmp files. This should succeed before any landlock + // restrictions are applied. + const TEXT: &str = "foo"; + let tmpfile1 = tempfile::NamedTempFile::new().unwrap(); + let path1 = tmpfile1.path(); + let tmpfile2 = tempfile::NamedTempFile::new().unwrap(); + let path2 = tmpfile2.path(); + + fs::write(path1, TEXT).unwrap(); + fs::write(path2, TEXT).unwrap(); + + // Apply Landlock with a write exception for only one of the files. + let status = try_restrict(vec![(path1, AccessFs::WriteFile)]); + if !matches!(status, Ok(())) { + panic!( + "Ruleset should be enforced since we checked if landlock is enabled: {:?}", + status + ); + } + + // Try to write to both files, only tmpfile1 should succeed. + let result = fs::write(path1, TEXT); + assert!(matches!(result, Ok(_))); + let result = fs::write(path2, TEXT); + assert!(matches!( + result, + Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) + )); + + // Apply Landlock for all files. + let status = try_restrict(std::iter::empty::<(PathBuf, AccessFs)>()); + if !matches!(status, Ok(())) { + panic!( + "Ruleset should be enforced since we checked if landlock is enabled: {:?}", + status + ); + } + + // Try to write to tmpfile1 after landlock, it should fail. + let result = fs::write(path1, TEXT); + assert!(matches!( + result, + Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) + )); + }); + + assert!(handle.join().is_ok()); + } + + // Test that checks whether landlock under our ABI version is able to truncate files. + #[test] + fn restricted_thread_can_truncate_file() { + // TODO: This would be nice: . + if check_can_fully_enable().is_err() { + return + } + + // Restricted thread can truncate file. + let handle = thread::spawn(|| { + // Create and write a file. This should succeed before any landlock + // restrictions are applied. + const TEXT: &str = "foo"; + let tmpfile = tempfile::NamedTempFile::new().unwrap(); + let path = tmpfile.path(); + + fs::write(path, TEXT).unwrap(); + + // Apply Landlock with all exceptions under the current ABI. + let status = try_restrict(vec![(path, AccessFs::from_all(LANDLOCK_ABI))]); + if !matches!(status, Ok(())) { + panic!( + "Ruleset should be enforced since we checked if landlock is enabled: {:?}", + status + ); + } + + // Try to truncate the file. + let result = tmpfile.as_file().set_len(0); + assert!(result.is_ok()); + }); + + assert!(handle.join().is_ok()); + } +} diff --git a/polkadot/node/core/pvf/common/src/worker/security/mod.rs b/polkadot/node/core/pvf/common/src/worker/security/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..72d47235d47a47cf062192b0d88c79947b9318b1 --- /dev/null +++ b/polkadot/node/core/pvf/common/src/worker/security/mod.rs @@ -0,0 +1,77 @@ +// 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 . + +//! Functionality for securing workers. +//! +//! This is needed because workers are used to compile and execute untrusted code (PVFs). +//! +//! We currently employ the following security measures: +//! +//! - Restrict filesystem +//! - Use Landlock to remove all unnecessary FS access rights. +//! - Unshare the user and mount namespaces. +//! - Change the root directory to a worker-specific temporary directory. +//! - Restrict networking by blocking socket creation and io_uring. +//! - Remove env vars + +#[cfg(target_os = "linux")] +pub mod change_root; +#[cfg(target_os = "linux")] +pub mod clone; +#[cfg(target_os = "linux")] +pub mod landlock; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub mod seccomp; + +use crate::{worker::WorkerInfo, LOG_TARGET}; + +/// Require env vars to have been removed when spawning the process, to prevent malicious code from +/// accessing them. +pub fn check_env_vars_were_cleared(worker_info: &WorkerInfo) -> bool { + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "clearing env vars in worker", + ); + + let mut ok = true; + + for (key, value) in std::env::vars_os() { + // TODO: *theoretically* the value (or mere presence) of `RUST_LOG` can be a source of + // randomness for malicious code. It should be removed in the job process, which does no + // logging. + if key == "RUST_LOG" { + continue + } + // An exception for MacOS. This is not a secure platform anyway, so we let it slide. + #[cfg(target_os = "macos")] + if key == "__CF_USER_TEXT_ENCODING" { + continue + } + + gum::error!( + target: LOG_TARGET, + ?worker_info, + ?key, + ?value, + "env var was present that should have been removed", + ); + + ok = false; + } + + ok +} diff --git a/polkadot/node/core/pvf/common/src/worker/security/seccomp.rs b/polkadot/node/core/pvf/common/src/worker/security/seccomp.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6100d236c8bd3460169cf3c8dc311becdbffa77 --- /dev/null +++ b/polkadot/node/core/pvf/common/src/worker/security/seccomp.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 . + +//! Functionality for sandboxing workers by restricting their capabilities by blocking certain +//! syscalls with seccomp. +//! +//! For security we block the following: +//! +//! - creation of new sockets - these are unneeded in PVF jobs, and we can safely block them without +//! affecting consensus. +//! +//! - `io_uring` - allows for networking and needs to be blocked. See below for a discussion on the +//! safety of doing this. +//! +//! # Safety of blocking io_uring +//! +//! `io_uring` is just a way of issuing system calls in an async manner, and there is nothing +//! stopping wasmtime from legitimately using it. Fortunately, at the moment it does not. Generally, +//! not many applications use `io_uring` in production yet, because of the numerous kernel CVEs +//! discovered. It's still under a lot of development. Android outright banned `io_uring` for these +//! reasons. +//! +//! Considering `io_uring`'s status discussed above, and that it very likely would get detected +//! either by our [static analysis](https://github.com/paritytech/polkadot-sdk/pull/1663) or by +//! testing, we think it is safe to block it. +//! +//! ## Consensus analysis +//! +//! If execution hits an edge case code path unique to a given machine, it's already taken a +//! non-deterministic branch anyway. After all, we just care that the majority of validators reach +//! the same result and preserve consensus. So worst-case scenario, there's a dispute, and we can +//! always admit fault and refund the wrong validator. On the other hand, if all validators take the +//! code path that results in a seccomp violation, then they would all vote against the current +//! candidate, which is also fine. The violation would get logged (in big scary letters) and +//! hopefully some validator reports it to us. +//! +//! Actually, a worst-worse-case scenario is that 50% of validators vote against, so that there is +//! no consensus. But so many things would have to go wrong for that to happen: +//! +//! 1. An update to `wasmtime` is introduced that uses io_uring (unlikely as io_uring is mainly for +//! IO-heavy applications) +//! +//! 2. The new syscall is not detected by our static analysis +//! +//! 3. It is never triggered in any of our tests +//! +//! 4. It then gets triggered on some super edge case in production on 50% of validators causing a +//! stall (bad but very unlikely) +//! +//! 5. Or, it triggers on only a few validators causing a dispute (more likely but not as bad) +//! +//! Considering how many things would have to go wrong here, we believe it's safe to block +//! `io_uring`. +//! +//! # Action on syscall violations +//! +//! When a forbidden syscall is attempted we immediately kill the process in order to prevent the +//! attacker from doing anything else. In execution, this will result in voting against the +//! candidate. + +use crate::{ + worker::{stringify_panic_payload, WorkerInfo}, + LOG_TARGET, +}; +use seccompiler::*; +use std::collections::BTreeMap; + +/// The action to take on caught syscalls. +#[cfg(not(test))] +const CAUGHT_ACTION: SeccompAction = SeccompAction::KillProcess; +/// Don't kill the process when testing. +#[cfg(test)] +const CAUGHT_ACTION: SeccompAction = SeccompAction::Errno(libc::EACCES as u32); + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Seccomp(#[from] seccompiler::Error), + #[error(transparent)] + Backend(#[from] seccompiler::BackendError), + #[error("A panic occurred in try_restrict: {0}")] + Panic(String), +} + +pub type Result = std::result::Result; + +/// Try to enable seccomp for the given kind of worker. +pub fn enable_for_worker(worker_info: &WorkerInfo) -> Result<()> { + gum::trace!( + target: LOG_TARGET, + ?worker_info, + "enabling seccomp", + ); + + try_restrict() +} + +/// Runs a check for seccomp in its own thread, and returns an error indicating whether seccomp with +/// our rules is fully enabled on the current Linux environment. +pub fn check_can_fully_enable() -> Result<()> { + match std::thread::spawn(|| try_restrict()).join() { + Ok(Ok(())) => Ok(()), + Ok(Err(err)) => Err(err), + Err(err) => Err(Error::Panic(stringify_panic_payload(err))), + } +} + +/// Applies a `seccomp` filter to disable networking for the PVF threads. +fn try_restrict() -> Result<()> { + // Build a `seccomp` filter which by default allows all syscalls except those blocked in the + // blacklist. + let mut blacklisted_rules = BTreeMap::default(); + + // Restrict the creation of sockets. + blacklisted_rules.insert(libc::SYS_socketpair, vec![]); + blacklisted_rules.insert(libc::SYS_socket, vec![]); + + // Prevent connecting to sockets for extra safety. + blacklisted_rules.insert(libc::SYS_connect, vec![]); + + // Restrict io_uring. + blacklisted_rules.insert(libc::SYS_io_uring_setup, vec![]); + blacklisted_rules.insert(libc::SYS_io_uring_enter, vec![]); + blacklisted_rules.insert(libc::SYS_io_uring_register, vec![]); + + let filter = SeccompFilter::new( + blacklisted_rules, + // Mismatch action: what to do if not in rule list. + SeccompAction::Allow, + // Match action: what to do if in rule list. + CAUGHT_ACTION, + TargetArch::x86_64, + )?; + + let bpf_prog: BpfProgram = filter.try_into()?; + + // Applies filter (runs seccomp) to the calling thread. + seccompiler::apply_filter(&bpf_prog)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{io::ErrorKind, net::TcpListener, thread}; + + #[test] + fn sandboxed_thread_cannot_use_sockets() { + // TODO: This would be nice: . + if check_can_fully_enable().is_err() { + return + } + + let handle = thread::spawn(|| { + // Open a socket, this should succeed before seccomp is applied. + TcpListener::bind("127.0.0.1:0").unwrap(); + + let status = try_restrict(); + if !matches!(status, Ok(())) { + panic!("Ruleset should be enforced since we checked if seccomp is enabled"); + } + + // Try to open a socket after seccomp. + assert!(matches!( + TcpListener::bind("127.0.0.1:0"), + Err(err) if matches!(err.kind(), ErrorKind::PermissionDenied) + )); + + // Other syscalls should still work. + unsafe { + assert!(libc::getppid() > 0); + } + }); + + assert!(handle.join().is_ok()); + } +} diff --git a/polkadot/node/core/pvf/execute-worker/Cargo.toml b/polkadot/node/core/pvf/execute-worker/Cargo.toml index 23678d95696e7ec10f7ac9a69a6d433b5e9275bb..04a620573b2eceb311bdce67006e282778442191 100644 --- a/polkadot/node/core/pvf/execute-worker/Cargo.toml +++ b/polkadot/node/core/pvf/execute-worker/Cargo.toml @@ -1,17 +1,20 @@ [package] name = "polkadot-node-core-pvf-execute-worker" description = "Polkadot crate that contains the logic for executing PVFs. Used by the polkadot-execute-worker binary." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] cpu-time = "1.0.0" -futures = "0.3.21" gum = { package = "tracing-gum", path = "../../../gum" } -rayon = "1.5.1" -tokio = { version = "1.24.2", features = ["fs", "process"] } +cfg-if = "1.0" +nix = { version = "0.27.1", features = ["process", "resource", "sched"] } +libc = "0.2.152" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } @@ -19,9 +22,5 @@ polkadot-node-core-pvf-common = { path = "../common" } polkadot-parachain-primitives = { path = "../../../../parachain" } polkadot-primitives = { path = "../../../../primitives" } -sp-core = { path = "../../../../../substrate/primitives/core" } -sp-maybe-compressed-blob = { path = "../../../../../substrate/primitives/maybe-compressed-blob" } -sp-tracing = { path = "../../../../../substrate/primitives/tracing" } - [features] builder = [] diff --git a/polkadot/node/core/pvf/execute-worker/src/lib.rs b/polkadot/node/core/pvf/execute-worker/src/lib.rs index af73eb16e685a153330ab81353959ecf07a4a2e8..bd7e76010a6dfedb339fd2fc1c17aa7f4007babe 100644 --- a/polkadot/node/core/pvf/execute-worker/src/lib.rs +++ b/polkadot/node/core/pvf/execute-worker/src/lib.rs @@ -17,7 +17,7 @@ //! Contains the logic for executing PVFs. Used by the polkadot-execute-worker binary. pub use polkadot_node_core_pvf_common::{ - executor_intf::execute_artifact, worker_dir, SecurityStatus, + error::ExecuteError, executor_interface::execute_artifact, }; // NOTE: Initializing logging in e.g. tests will not have an effect in the workers, as they are @@ -25,69 +25,57 @@ pub use polkadot_node_core_pvf_common::{ const LOG_TARGET: &str = "parachain::pvf-execute-worker"; use cpu_time::ProcessTime; +use nix::{ + errno::Errno, + sys::{ + resource::{Usage, UsageWho}, + wait::WaitStatus, + }, + unistd::{ForkResult, Pid}, +}; use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ error::InternalValidationError, - execute::{Handshake, Response}, + execute::{Handshake, JobError, JobResponse, JobResult, WorkerResponse}, + executor_interface::params_to_wasmtime_semantics, framed_recv_blocking, framed_send_blocking, worker::{ - cpu_time_monitor_loop, stringify_panic_payload, + cpu_time_monitor_loop, pipe2_cloexec, run_worker, stringify_panic_payload, thread::{self, WaitOutcome}, - worker_event_loop, WorkerKind, + PipeFd, WorkerInfo, WorkerKind, }, + worker_dir, }; use polkadot_parachain_primitives::primitives::ValidationResult; -use polkadot_primitives::{executor_params::DEFAULT_NATIVE_STACK_MAX, ExecutorParams}; +use polkadot_primitives::ExecutorParams; use std::{ - os::unix::net::UnixStream, + io::{self, Read}, + os::{ + fd::{AsRawFd, FromRawFd}, + unix::net::UnixStream, + }, path::PathBuf, + process, sync::{mpsc::channel, Arc}, time::Duration, }; -use tokio::io; - -// Wasmtime powers the Substrate Executor. It compiles the wasm bytecode into native code. -// That native code does not create any stacks and just reuses the stack of the thread that -// wasmtime was invoked from. -// -// Also, we configure the executor to provide the deterministic stack and that requires -// supplying the amount of the native stack space that wasm is allowed to use. This is -// realized by supplying the limit into `wasmtime::Config::max_wasm_stack`. -// -// There are quirks to that configuration knob: -// -// 1. It only limits the amount of stack space consumed by wasm but does not ensure nor check that -// the stack space is actually available. -// -// That means, if the calling thread has 1 MiB of stack space left and the wasm code consumes -// more, then the wasmtime limit will **not** trigger. Instead, the wasm code will hit the -// guard page and the Rust stack overflow handler will be triggered. That leads to an -// **abort**. -// -// 2. It cannot and does not limit the stack space consumed by Rust code. -// -// Meaning that if the wasm code leaves no stack space for Rust code, then the Rust code -// will abort and that will abort the process as well. -// -// Typically on Linux the main thread gets the stack size specified by the `ulimit` and -// typically it's configured to 8 MiB. Rust's spawned threads are 2 MiB. OTOH, the -// DEFAULT_NATIVE_STACK_MAX is set to 256 MiB. Not nearly enough. -// -// Hence we need to increase it. The simplest way to fix that is to spawn a thread with the desired -// stack limit. -// -// The reasoning why we pick this particular size is: -// -// The default Rust thread stack limit 2 MiB + 256 MiB wasm stack. -/// The stack size for the execute thread. -pub const EXECUTE_THREAD_STACK_SIZE: usize = 2 * 1024 * 1024 + DEFAULT_NATIVE_STACK_MAX as usize; - -fn recv_handshake(stream: &mut UnixStream) -> io::Result { + +/// The number of threads for the child process: +/// 1 - Main thread +/// 2 - Cpu monitor thread +/// 3 - Execute thread +/// +/// NOTE: The correctness of this value is enforced by a test. If the number of threads inside +/// the child process changes in the future, this value must be changed as well. +pub const EXECUTE_WORKER_THREAD_NUMBER: u32 = 3; + +/// Receives a handshake with information specific to the execute worker. +fn recv_execute_handshake(stream: &mut UnixStream) -> io::Result { let handshake_enc = framed_recv_blocking(stream)?; let handshake = Handshake::decode(&mut &handshake_enc[..]).map_err(|_| { io::Error::new( io::ErrorKind::Other, - "execute pvf recv_handshake: failed to decode Handshake".to_owned(), + "execute pvf recv_execute_handshake: failed to decode Handshake".to_owned(), ) })?; Ok(handshake) @@ -105,7 +93,7 @@ fn recv_request(stream: &mut UnixStream) -> io::Result<(Vec, Duration)> { Ok((params, execution_timeout)) } -fn send_response(stream: &mut UnixStream, response: Response) -> io::Result<()> { +fn send_response(stream: &mut UnixStream, response: WorkerResponse) -> io::Result<()> { framed_send_blocking(stream, &response.encode()) } @@ -129,26 +117,27 @@ pub fn worker_entrypoint( worker_dir_path: PathBuf, node_version: Option<&str>, worker_version: Option<&str>, - security_status: SecurityStatus, ) { - worker_event_loop( + run_worker( WorkerKind::Execute, socket_path, worker_dir_path, node_version, worker_version, - &security_status, - |mut stream, worker_dir_path| async move { - let worker_pid = std::process::id(); - let artifact_path = worker_dir::execute_artifact(&worker_dir_path); + |mut stream, worker_info, security_status| { + let artifact_path = worker_dir::execute_artifact(&worker_info.worker_dir_path); - let Handshake { executor_params } = recv_handshake(&mut stream)?; + let Handshake { executor_params } = recv_execute_handshake(&mut stream)?; + + let executor_params: Arc = Arc::new(executor_params); + let execute_thread_stack_size = max_stack_size(&executor_params); loop { let (params, execution_timeout) = recv_request(&mut stream)?; gum::debug!( target: LOG_TARGET, - %worker_pid, + ?worker_info, + ?security_status, "worker: validating artifact {}", artifact_path.display(), ); @@ -157,7 +146,7 @@ pub fn worker_entrypoint( let compiled_artifact_blob = match std::fs::read(&artifact_path) { Ok(bytes) => bytes, Err(err) => { - let response = Response::InternalError( + let response = WorkerResponse::InternalError( InternalValidationError::CouldNotOpenFile(err.to_string()), ); send_response(&mut stream, response)?; @@ -165,91 +154,75 @@ pub fn worker_entrypoint( }, }; - // Conditional variable to notify us when a thread is done. - let condvar = thread::get_condvar(); - - let cpu_time_start = ProcessTime::now(); + let (pipe_read_fd, pipe_write_fd) = pipe2_cloexec()?; - // Spawn a new thread that runs the CPU time monitor. - let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>(); - let cpu_time_monitor_thread = thread::spawn_worker_thread( - "cpu time monitor thread", - move || { - cpu_time_monitor_loop( - cpu_time_start, - execution_timeout, - cpu_time_monitor_rx, - ) + let usage_before = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { + Ok(usage) => usage, + Err(errno) => { + let response = internal_error_from_errno("getrusage before", errno); + send_response(&mut stream, response)?; + continue }, - Arc::clone(&condvar), - WaitOutcome::TimedOut, - )?; - - let executor_params_2 = executor_params.clone(); - let execute_thread = thread::spawn_worker_thread_with_stack_size( - "execute thread", - move || { - validate_using_artifact( + }; + let stream_fd = stream.as_raw_fd(); + + let compiled_artifact_blob = Arc::new(compiled_artifact_blob); + let params = Arc::new(params); + + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + let result = if security_status.can_do_secure_clone { + handle_clone( + pipe_write_fd, + pipe_read_fd, + stream_fd, + &compiled_artifact_blob, + &executor_params, + ¶ms, + execution_timeout, + execute_thread_stack_size, + worker_info, + security_status.can_unshare_user_namespace_and_change_root, + usage_before, + )? + } else { + // Fall back to using fork. + handle_fork( + pipe_write_fd, + pipe_read_fd, + stream_fd, + &compiled_artifact_blob, + &executor_params, + ¶ms, + execution_timeout, + execute_thread_stack_size, + worker_info, + usage_before, + )? + }; + } else { + let result = handle_fork( + pipe_write_fd, + pipe_read_fd, + stream_fd, &compiled_artifact_blob, - &executor_params_2, + &executor_params, ¶ms, - cpu_time_start, - ) - }, - Arc::clone(&condvar), - WaitOutcome::Finished, - EXECUTE_THREAD_STACK_SIZE, - )?; - - let outcome = thread::wait_for_threads(condvar); - - let response = match outcome { - WaitOutcome::Finished => { - let _ = cpu_time_monitor_tx.send(()); - execute_thread - .join() - .unwrap_or_else(|e| Response::Panic(stringify_panic_payload(e))) - }, - // If the CPU thread is not selected, we signal it to end, the join handle is - // dropped and the thread will finish in the background. - WaitOutcome::TimedOut => { - match cpu_time_monitor_thread.join() { - Ok(Some(cpu_time_elapsed)) => { - // Log if we exceed the timeout and the other thread hasn't - // finished. - gum::warn!( - target: LOG_TARGET, - %worker_pid, - "execute job took {}ms cpu time, exceeded execute timeout {}ms", - cpu_time_elapsed.as_millis(), - execution_timeout.as_millis(), - ); - Response::TimedOut - }, - Ok(None) => Response::InternalError( - InternalValidationError::CpuTimeMonitorThread( - "error communicating over finished channel".into(), - ), - ), - Err(e) => Response::InternalError( - InternalValidationError::CpuTimeMonitorThread( - stringify_panic_payload(e), - ), - ), - } - }, - WaitOutcome::Pending => unreachable!( - "we run wait_while until the outcome is no longer pending; qed" - ), - }; + execution_timeout, + execute_thread_stack_size, + worker_info, + usage_before, + )?; + } + } gum::trace!( target: LOG_TARGET, - %worker_pid, - "worker: sending response to host: {:?}", - response + ?worker_info, + "worker: sending result to host: {:?}", + result ); - send_response(&mut stream, response)?; + send_response(&mut stream, result)?; } }, ); @@ -259,27 +232,421 @@ fn validate_using_artifact( compiled_artifact_blob: &[u8], executor_params: &ExecutorParams, params: &[u8], - cpu_time_start: ProcessTime, -) -> Response { +) -> JobResponse { let descriptor_bytes = match unsafe { // SAFETY: this should be safe since the compiled artifact passed here comes from the // file created by the prepare workers. These files are obtained by calling - // [`executor_intf::prepare`]. + // [`executor_interface::prepare`]. execute_artifact(compiled_artifact_blob, executor_params, params) } { - Err(err) => return Response::format_invalid("execute", &err), + Err(ExecuteError::RuntimeConstruction(wasmerr)) => + return JobResponse::runtime_construction("execute", &wasmerr.to_string()), + Err(err) => return JobResponse::format_invalid("execute", &err.to_string()), Ok(d) => d, }; let result_descriptor = match ValidationResult::decode(&mut &descriptor_bytes[..]) { Err(err) => - return Response::format_invalid("validation result decoding failed", &err.to_string()), + return JobResponse::format_invalid( + "validation result decoding failed", + &err.to_string(), + ), Ok(r) => r, }; - // Include the decoding in the measured time, to prevent any potential attacks exploiting some - // bug in decoding. - let duration = cpu_time_start.elapsed(); + JobResponse::Ok { result_descriptor } +} + +#[cfg(target_os = "linux")] +fn handle_clone( + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + compiled_artifact_blob: &Arc>, + executor_params: &Arc, + params: &Arc>, + execution_timeout: Duration, + execute_stack_size: usize, + worker_info: &WorkerInfo, + have_unshare_newuser: bool, + usage_before: Usage, +) -> io::Result { + use polkadot_node_core_pvf_common::worker::security; + + // SAFETY: new process is spawned within a single threaded process. This invariant + // is enforced by tests. Stack size being specified to ensure child doesn't overflow + match unsafe { + security::clone::clone_on_worker( + worker_info, + have_unshare_newuser, + Box::new(|| { + handle_child_process( + pipe_write_fd, + pipe_read_fd, + stream_fd, + Arc::clone(compiled_artifact_blob), + Arc::clone(executor_params), + Arc::clone(params), + execution_timeout, + execute_stack_size, + ) + }), + ) + } { + Ok(child) => handle_parent_process( + pipe_read_fd, + pipe_write_fd, + worker_info, + child, + usage_before, + execution_timeout, + ), + Err(security::clone::Error::Clone(errno)) => Ok(internal_error_from_errno("clone", errno)), + } +} + +fn handle_fork( + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + compiled_artifact_blob: &Arc>, + executor_params: &Arc, + params: &Arc>, + execution_timeout: Duration, + execute_worker_stack_size: usize, + worker_info: &WorkerInfo, + usage_before: Usage, +) -> io::Result { + // SAFETY: new process is spawned within a single threaded process. This invariant + // is enforced by tests. + match unsafe { nix::unistd::fork() } { + Ok(ForkResult::Child) => handle_child_process( + pipe_write_fd, + pipe_read_fd, + stream_fd, + Arc::clone(compiled_artifact_blob), + Arc::clone(executor_params), + Arc::clone(params), + execution_timeout, + execute_worker_stack_size, + ), + Ok(ForkResult::Parent { child }) => handle_parent_process( + pipe_read_fd, + pipe_write_fd, + worker_info, + child, + usage_before, + execution_timeout, + ), + Err(errno) => Ok(internal_error_from_errno("fork", errno)), + } +} + +/// This is used to handle child process during pvf execute worker. +/// It executes the artifact and pipes back the response to the parent process. +/// +/// # Returns +/// +/// - pipe back `JobResponse` to the parent process. +fn handle_child_process( + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + compiled_artifact_blob: Arc>, + executor_params: Arc, + params: Arc>, + execution_timeout: Duration, + execute_thread_stack_size: usize, +) -> ! { + // SAFETY: this is an open and owned file descriptor at this point. + let mut pipe_write = unsafe { PipeFd::from_raw_fd(pipe_write_fd) }; + + // Drop the read end so we don't have too many FDs open. + if let Err(errno) = nix::unistd::close(pipe_read_fd) { + send_child_response(&mut pipe_write, job_error_from_errno("closing pipe", errno)); + } + + // Dropping the stream closes the underlying socket. We want to make sure + // that the sandboxed child can't get any kind of information from the + // outside world. The only IPC it should be able to do is sending its + // response over the pipe. + if let Err(errno) = nix::unistd::close(stream_fd) { + send_child_response(&mut pipe_write, job_error_from_errno("closing stream", errno)); + } + + gum::debug!( + target: LOG_TARGET, + worker_job_pid = %process::id(), + "worker job: executing artifact", + ); + + // Conditional variable to notify us when a thread is done. + let condvar = thread::get_condvar(); + let cpu_time_start = ProcessTime::now(); + + // Spawn a new thread that runs the CPU time monitor. + let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>(); + let cpu_time_monitor_thread = thread::spawn_worker_thread( + "cpu time monitor thread", + move || cpu_time_monitor_loop(cpu_time_start, execution_timeout, cpu_time_monitor_rx), + Arc::clone(&condvar), + WaitOutcome::TimedOut, + ) + .unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(JobError::CouldNotSpawnThread(err.to_string()))) + }); + + let execute_thread = thread::spawn_worker_thread_with_stack_size( + "execute thread", + move || validate_using_artifact(&compiled_artifact_blob, &executor_params, ¶ms), + Arc::clone(&condvar), + WaitOutcome::Finished, + execute_thread_stack_size, + ) + .unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(JobError::CouldNotSpawnThread(err.to_string()))) + }); + + let outcome = thread::wait_for_threads(condvar); + + let response = match outcome { + WaitOutcome::Finished => { + let _ = cpu_time_monitor_tx.send(()); + execute_thread.join().map_err(|e| JobError::Panic(stringify_panic_payload(e))) + }, + // If the CPU thread is not selected, we signal it to end, the join handle is + // dropped and the thread will finish in the background. + WaitOutcome::TimedOut => match cpu_time_monitor_thread.join() { + Ok(Some(_cpu_time_elapsed)) => Err(JobError::TimedOut), + Ok(None) => Err(JobError::CpuTimeMonitorThread( + "error communicating over finished channel".into(), + )), + Err(e) => Err(JobError::CpuTimeMonitorThread(stringify_panic_payload(e))), + }, + WaitOutcome::Pending => + unreachable!("we run wait_while until the outcome is no longer pending; qed"), + }; + + send_child_response(&mut pipe_write, response); +} + +/// Returns stack size based on the number of threads. +/// The stack size is represented by 2MiB * number_of_threads + native stack; +/// +/// # Background +/// +/// Wasmtime powers the Substrate Executor. It compiles the wasm bytecode into native code. +/// That native code does not create any stacks and just reuses the stack of the thread that +/// wasmtime was invoked from. +/// +/// Also, we configure the executor to provide the deterministic stack and that requires +/// supplying the amount of the native stack space that wasm is allowed to use. This is +/// realized by supplying the limit into `wasmtime::Config::max_wasm_stack`. +/// +/// There are quirks to that configuration knob: +/// +/// 1. It only limits the amount of stack space consumed by wasm but does not ensure nor check that +/// the stack space is actually available. +/// +/// That means, if the calling thread has 1 MiB of stack space left and the wasm code consumes +/// more, then the wasmtime limit will **not** trigger. Instead, the wasm code will hit the +/// guard page and the Rust stack overflow handler will be triggered. That leads to an +/// **abort**. +/// +/// 2. It cannot and does not limit the stack space consumed by Rust code. +/// +/// Meaning that if the wasm code leaves no stack space for Rust code, then the Rust code +/// will abort and that will abort the process as well. +/// +/// Typically on Linux the main thread gets the stack size specified by the `ulimit` and +/// typically it's configured to 8 MiB. Rust's spawned threads are 2 MiB. OTOH, the +/// DEFAULT_NATIVE_STACK_MAX is set to 256 MiB. Not nearly enough. +/// +/// Hence we need to increase it. The simplest way to fix that is to spawn an execute thread with +/// the desired stack limit. We must also make sure the job process has enough stack for *all* its +/// threads. This function can be used to get the stack size of either the execute thread or execute +/// job process. +fn max_stack_size(executor_params: &ExecutorParams) -> usize { + let (_sem, deterministic_stack_limit) = params_to_wasmtime_semantics(executor_params); + return (2 * 1024 * 1024 + deterministic_stack_limit.native_stack_max) as usize; +} + +/// Waits for child process to finish and handle child response from pipe. +/// +/// # Returns +/// +/// - The response, either `Ok` or some error state. +fn handle_parent_process( + pipe_read_fd: i32, + pipe_write_fd: i32, + worker_info: &WorkerInfo, + job_pid: Pid, + usage_before: Usage, + timeout: Duration, +) -> io::Result { + // the read end will wait until all write ends have been closed, + // this drop is necessary to avoid deadlock + if let Err(errno) = nix::unistd::close(pipe_write_fd) { + return Ok(internal_error_from_errno("closing pipe write fd", errno)); + }; + + // SAFETY: pipe_read_fd is an open and owned file descriptor at this point. + let mut pipe_read = unsafe { PipeFd::from_raw_fd(pipe_read_fd) }; + + // Read from the child. Don't decode unless the process exited normally, which we check later. + let mut received_data = Vec::new(); + pipe_read + .read_to_end(&mut received_data) + // Could not decode job response. There is either a bug or the job was hijacked. + // Should retry at any rate. + .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?; + + let status = nix::sys::wait::waitpid(job_pid, None); + gum::trace!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "execute worker received wait status from job: {:?}", + status, + ); + + let usage_after = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { + Ok(usage) => usage, + Err(errno) => return Ok(internal_error_from_errno("getrusage after", errno)), + }; + + // Using `getrusage` is needed to check whether child has timedout since we cannot rely on + // child to report its own time. + // As `getrusage` returns resource usage from all terminated child processes, + // it is necessary to subtract the usage before the current child process to isolate its cpu + // time + let cpu_tv = get_total_cpu_usage(usage_after) - get_total_cpu_usage(usage_before); + if cpu_tv >= timeout { + gum::warn!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "execute job took {}ms cpu time, exceeded execute timeout {}ms", + cpu_tv.as_millis(), + timeout.as_millis(), + ); + return Ok(WorkerResponse::JobTimedOut) + } + + match status { + Ok(WaitStatus::Exited(_, exit_status)) => { + let mut reader = io::BufReader::new(received_data.as_slice()); + let result = match recv_child_response(&mut reader) { + Ok(result) => result, + Err(err) => return Ok(WorkerResponse::JobError(err.to_string())), + }; + + match result { + Ok(JobResponse::Ok { result_descriptor }) => { + // The exit status should have been zero if no error occurred. + if exit_status != 0 { + return Ok(WorkerResponse::JobError(format!( + "unexpected exit status: {}", + exit_status + ))) + } + + Ok(WorkerResponse::Ok { result_descriptor, duration: cpu_tv }) + }, + Ok(JobResponse::InvalidCandidate(err)) => Ok(WorkerResponse::InvalidCandidate(err)), + Ok(JobResponse::RuntimeConstruction(err)) => + Ok(WorkerResponse::RuntimeConstruction(err)), + Err(job_error) => { + gum::warn!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "execute job error: {}", + job_error, + ); + if matches!(job_error, JobError::TimedOut) { + Ok(WorkerResponse::JobTimedOut) + } else { + Ok(WorkerResponse::JobError(job_error.to_string())) + } + }, + } + }, + // The job was killed by the given signal. + // + // The job gets SIGSYS on seccomp violations, but this signal may have been sent for some + // other reason, so we still need to check for seccomp violations elsewhere. + Ok(WaitStatus::Signaled(_pid, signal, _core_dump)) => Ok(WorkerResponse::JobDied { + err: format!("received signal: {signal:?}"), + job_pid: job_pid.as_raw(), + }), + Err(errno) => Ok(internal_error_from_errno("waitpid", errno)), + + // It is within an attacker's power to send an unexpected exit status. So we cannot treat + // this as an internal error (which would make us abstain), but must vote against. + Ok(unexpected_wait_status) => Ok(WorkerResponse::JobDied { + err: format!("unexpected status from wait: {unexpected_wait_status:?}"), + job_pid: job_pid.as_raw(), + }), + } +} + +/// Calculate the total CPU time from the given `usage` structure, returned from +/// [`nix::sys::resource::getrusage`], and calculates the total CPU time spent, including both user +/// and system time. +/// +/// # Arguments +/// +/// - `rusage`: Contains resource usage information. +/// +/// # Returns +/// +/// Returns a `Duration` representing the total CPU time. +fn get_total_cpu_usage(rusage: Usage) -> Duration { + let micros = (((rusage.user_time().tv_sec() + rusage.system_time().tv_sec()) * 1_000_000) + + (rusage.system_time().tv_usec() + rusage.user_time().tv_usec()) as i64) as u64; + + return Duration::from_micros(micros) +} + +/// Get a job response. +fn recv_child_response(received_data: &mut io::BufReader<&[u8]>) -> io::Result { + let response_bytes = framed_recv_blocking(received_data)?; + JobResult::decode(&mut response_bytes.as_slice()).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("execute pvf recv_child_response: decode error: {}", e), + ) + }) +} + +/// Write a job response to the pipe and exit process after. +/// +/// # Arguments +/// +/// - `pipe_write`: A `PipeFd` structure, the writing end of a pipe. +/// +/// - `response`: Child process response +fn send_child_response(pipe_write: &mut PipeFd, response: JobResult) -> ! { + framed_send_blocking(pipe_write, response.encode().as_slice()) + .unwrap_or_else(|_| process::exit(libc::EXIT_FAILURE)); + + if response.is_ok() { + process::exit(libc::EXIT_SUCCESS) + } else { + process::exit(libc::EXIT_FAILURE) + } +} + +fn internal_error_from_errno(context: &'static str, errno: Errno) -> WorkerResponse { + WorkerResponse::InternalError(InternalValidationError::Kernel(format!( + "{}: {}: {}", + context, + errno, + io::Error::last_os_error() + ))) +} - Response::Ok { result_descriptor, duration } +fn job_error_from_errno(context: &'static str, errno: Errno) -> JobResult { + Err(JobError::Kernel(format!("{}: {}: {}", context, errno, io::Error::last_os_error()))) } diff --git a/polkadot/node/core/pvf/prepare-worker/Cargo.toml b/polkadot/node/core/pvf/prepare-worker/Cargo.toml index 886209b78c329f062e674a96992509d0390a4220..24efbec4be7d0552abdf9e7a9324636820347950 100644 --- a/polkadot/node/core/pvf/prepare-worker/Cargo.toml +++ b/polkadot/node/core/pvf/prepare-worker/Cargo.toml @@ -1,39 +1,50 @@ [package] name = "polkadot-node-core-pvf-prepare-worker" description = "Polkadot crate that contains the logic for preparing PVFs. Used by the polkadot-prepare-worker binary." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] +blake3 = "1.5" cfg-if = "1.0" -futures = "0.3.21" gum = { package = "tracing-gum", path = "../../../gum" } -libc = "0.2.139" +libc = "0.2.152" rayon = "1.5.1" +tracking-allocator = { package = "staging-tracking-allocator", path = "../../../tracking-allocator" } tikv-jemalloc-ctl = { version = "0.5.0", optional = true } -tokio = { version = "1.24.2", features = ["fs", "process"] } +tikv-jemallocator = { version = "0.5.0", optional = true } +nix = { version = "0.27.1", features = ["process", "resource", "sched"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } polkadot-node-core-pvf-common = { path = "../common" } -polkadot-parachain-primitives = { path = "../../../../parachain" } polkadot-primitives = { path = "../../../../primitives" } -sc-executor = { path = "../../../../../substrate/client/executor" } sc-executor-common = { path = "../../../../../substrate/client/executor/common" } sc-executor-wasmtime = { path = "../../../../../substrate/client/executor/wasmtime" } -sp-io = { path = "../../../../../substrate/primitives/io" } -sp-maybe-compressed-blob = { path = "../../../../../substrate/primitives/maybe-compressed-blob" } -sp-tracing = { path = "../../../../../substrate/primitives/tracing" } [target.'cfg(target_os = "linux")'.dependencies] +tikv-jemallocator = "0.5.0" tikv-jemalloc-ctl = "0.5.0" [features] builder = [] jemalloc-allocator = [ "dep:tikv-jemalloc-ctl", + "dep:tikv-jemallocator", "polkadot-node-core-pvf-common/jemalloc-allocator", ] + +[dev-dependencies] +criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } +rococo-runtime = { path = "../../../../runtime/rococo" } +sp-maybe-compressed-blob = { path = "../../../../../substrate/primitives/maybe-compressed-blob" } + +[[bench]] +name = "prepare_rococo_runtime" +harness = false diff --git a/polkadot/node/core/pvf/prepare-worker/benches/prepare_rococo_runtime.rs b/polkadot/node/core/pvf/prepare-worker/benches/prepare_rococo_runtime.rs new file mode 100644 index 0000000000000000000000000000000000000000..d531c90b64b578e31f42a13f2399b4343469fa6d --- /dev/null +++ b/polkadot/node/core/pvf/prepare-worker/benches/prepare_rococo_runtime.rs @@ -0,0 +1,65 @@ +// 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 criterion::{criterion_group, criterion_main, Criterion, SamplingMode}; +use polkadot_node_core_pvf_common::{ + executor_interface::{prepare, prevalidate}, + prepare::PrepareJobKind, + pvf::PvfPrepData, +}; +use polkadot_primitives::ExecutorParams; +use std::time::Duration; + +fn do_prepare_runtime(pvf: PvfPrepData) { + let blob = match prevalidate(&pvf.code()) { + Err(err) => panic!("{:?}", err), + Ok(b) => b, + }; + + match prepare(blob, &pvf.executor_params()) { + Ok(_) => (), + Err(err) => panic!("{:?}", err), + } +} + +fn prepare_rococo_runtime(c: &mut Criterion) { + let blob = rococo_runtime::WASM_BINARY.unwrap(); + let pvf = match sp_maybe_compressed_blob::decompress(&blob, 64 * 1024 * 1024) { + Ok(code) => PvfPrepData::from_code( + code.into_owned(), + ExecutorParams::default(), + Duration::from_secs(360), + PrepareJobKind::Compilation, + ), + Err(e) => { + panic!("Cannot decompress blob: {:?}", e); + }, + }; + + let mut group = c.benchmark_group("rococo"); + group.sampling_mode(SamplingMode::Flat); + group.sample_size(20); + group.measurement_time(Duration::from_secs(240)); + group.bench_function("prepare Rococo runtime", |b| { + // `PvfPrepData` is designed to be cheap to clone, so cloning shouldn't affect the + // benchmark accuracy + b.iter(|| do_prepare_runtime(pvf.clone())) + }); + group.finish(); +} + +criterion_group!(preparation, prepare_rococo_runtime); +criterion_main!(preparation); diff --git a/polkadot/node/core/pvf/prepare-worker/src/executor_intf.rs b/polkadot/node/core/pvf/prepare-worker/src/executor_intf.rs deleted file mode 100644 index 1f88f6a6dd6e9a07b99c4832bd3f4dda1a71cf20..0000000000000000000000000000000000000000 --- a/polkadot/node/core/pvf/prepare-worker/src/executor_intf.rs +++ /dev/null @@ -1,42 +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 . - -//! Interface to the Substrate Executor - -use polkadot_node_core_pvf_common::executor_intf::params_to_wasmtime_semantics; -use polkadot_primitives::ExecutorParams; -use sc_executor_common::runtime_blob::RuntimeBlob; - -/// Runs the prevalidation on the given code. Returns a [`RuntimeBlob`] if it succeeds. -pub fn prevalidate(code: &[u8]) -> Result { - let blob = RuntimeBlob::new(code)?; - // It's assumed this function will take care of any prevalidation logic - // that needs to be done. - // - // Do nothing for now. - Ok(blob) -} - -/// Runs preparation on the given runtime blob. If successful, it returns a serialized compiled -/// artifact which can then be used to pass into `Executor::execute` after writing it to the disk. -pub fn prepare( - blob: RuntimeBlob, - executor_params: &ExecutorParams, -) -> Result, sc_executor_common::error::WasmError> { - let semantics = params_to_wasmtime_semantics(executor_params) - .map_err(|e| sc_executor_common::error::WasmError::Other(e))?; - sc_executor_wasmtime::prepare_runtime_artifact(blob, &semantics) -} diff --git a/polkadot/node/core/pvf/prepare-worker/src/lib.rs b/polkadot/node/core/pvf/prepare-worker/src/lib.rs index fa5d3656a35e69fb0d738db82ed8f5ca54a75a9e..82a56107ef535743a1838d50358241e3cc64b711 100644 --- a/polkadot/node/core/pvf/prepare-worker/src/lib.rs +++ b/polkadot/node/core/pvf/prepare-worker/src/lib.rs @@ -16,11 +16,8 @@ //! Contains the logic for preparing PVFs. Used by the polkadot-prepare-worker binary. -mod executor_intf; mod memory_stats; -pub use executor_intf::{prepare, prevalidate}; - // NOTE: Initializing logging in e.g. tests will not have an effect in the workers, as they are // separate spawned processes. Run with e.g. `RUST_LOG=parachain::pvf-prepare-worker=trace`. const LOG_TARGET: &str = "parachain::pvf-prepare-worker"; @@ -29,30 +26,70 @@ const LOG_TARGET: &str = "parachain::pvf-prepare-worker"; use crate::memory_stats::max_rss_stat::{extract_max_rss_stat, get_max_rss_thread}; #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] use crate::memory_stats::memory_tracker::{get_memory_tracker_loop_stats, memory_tracker_loop}; +use libc; +use nix::{ + errno::Errno, + sys::{ + resource::{Usage, UsageWho}, + wait::WaitStatus, + }, + unistd::{ForkResult, Pid}, +}; +use polkadot_node_core_pvf_common::{ + executor_interface::{prepare, prevalidate}, + worker::{pipe2_cloexec, PipeFd, WorkerInfo}, +}; + use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ - error::{PrepareError, PrepareResult}, - executor_intf::create_runtime_from_artifact_bytes, + error::{PrepareError, PrepareWorkerResult}, + executor_interface::create_runtime_from_artifact_bytes, framed_recv_blocking, framed_send_blocking, - prepare::{MemoryStats, PrepareJobKind, PrepareStats}, + prepare::{MemoryStats, PrepareJobKind, PrepareStats, PrepareWorkerSuccess}, pvf::PvfPrepData, worker::{ - cpu_time_monitor_loop, stringify_panic_payload, - thread::{self, WaitOutcome}, - worker_event_loop, WorkerKind, + cpu_time_monitor_loop, run_worker, stringify_panic_payload, + thread::{self, spawn_worker_thread, WaitOutcome}, + WorkerKind, }, - worker_dir, ProcessTime, SecurityStatus, + worker_dir, ProcessTime, }; use polkadot_primitives::ExecutorParams; use std::{ - os::unix::net::UnixStream, - path::PathBuf, + fs, + io::{self, Read}, + os::{ + fd::{AsRawFd, FromRawFd, RawFd}, + unix::net::UnixStream, + }, + path::{Path, PathBuf}, + process, sync::{mpsc::channel, Arc}, time::Duration, }; -use tokio::io; +use tracking_allocator::TrackingAllocator; + +#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] +#[global_allocator] +static ALLOC: TrackingAllocator = + TrackingAllocator(tikv_jemallocator::Jemalloc); + +#[cfg(not(any(target_os = "linux", feature = "jemalloc-allocator")))] +#[global_allocator] +static ALLOC: TrackingAllocator = TrackingAllocator(std::alloc::System); + +/// The number of threads for the child process: +/// 1 - Main thread +/// 2 - Cpu monitor thread +/// 3 - Memory tracker thread +/// 4 - Prepare thread +/// +/// NOTE: The correctness of this value is enforced by a test. If the number of threads inside +/// the child process changes in the future, this value must be changed as well. +pub const PREPARE_WORKER_THREAD_NUMBER: u32 = 4; /// Contains the bytes for a successfully compiled artifact. +#[derive(Encode, Decode)] pub struct CompiledArtifact(Vec); impl CompiledArtifact { @@ -68,6 +105,7 @@ impl AsRef<[u8]> for CompiledArtifact { } } +/// Get a worker request. fn recv_request(stream: &mut UnixStream) -> io::Result { let pvf = framed_recv_blocking(stream)?; let pvf = PvfPrepData::decode(&mut &pvf[..]).map_err(|e| { @@ -79,10 +117,53 @@ fn recv_request(stream: &mut UnixStream) -> io::Result { Ok(pvf) } -fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Result<()> { +/// Send a worker response. +fn send_response(stream: &mut UnixStream, result: PrepareWorkerResult) -> io::Result<()> { framed_send_blocking(stream, &result.encode()) } +fn start_memory_tracking(fd: RawFd, limit: Option) { + unsafe { + // SAFETY: Inside the failure handler, the allocator is locked and no allocations or + // deallocations are possible. For Linux, that always holds for the code below, so it's + // safe. For MacOS, that technically holds at the time of writing, but there are no future + // guarantees. + // The arguments of unsafe `libc` calls are valid, the payload validity is covered with + // a test. + ALLOC.start_tracking( + limit, + Some(Box::new(move || { + #[cfg(target_os = "linux")] + { + // Syscalls never allocate or deallocate, so this is safe. + libc::syscall(libc::SYS_write, fd, OOM_PAYLOAD.as_ptr(), OOM_PAYLOAD.len()); + libc::syscall(libc::SYS_close, fd); + // Make sure we exit from all threads. Copied from glibc. + libc::syscall(libc::SYS_exit_group, 1); + loop { + libc::syscall(libc::SYS_exit, 1); + } + } + #[cfg(not(target_os = "linux"))] + { + // Syscalls are not available on MacOS, so we have to use `libc` wrappers. + // Technically, there may be allocations inside, although they shouldn't be + // there. In that case, we'll see deadlocks on MacOS after the OOM condition + // triggered. As we consider running a validator on MacOS unsafe, and this + // code is only run by a validator, it's a lesser evil. + libc::write(fd, OOM_PAYLOAD.as_ptr().cast(), OOM_PAYLOAD.len()); + libc::close(fd); + libc::_exit(1); + } + })), + ); + } +} + +fn end_memory_tracking() -> isize { + ALLOC.end_tracking() +} + /// The entrypoint that the spawned prepare worker should start with. /// /// # Parameters @@ -105,41 +186,41 @@ fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Result<( /// /// 1. Get the code and parameters for preparation from the host. /// -/// 2. Start a memory tracker in a separate thread. +/// 2. Start a new child process /// -/// 3. Start the CPU time monitor loop and the actual preparation in two separate threads. +/// 3. Start the memory tracker and the actual preparation in two separate threads. /// /// 4. Wait on the two threads created in step 3. /// /// 5. Stop the memory tracker and get the stats. /// -/// 6. If compilation succeeded, write the compiled artifact into a temporary file. +/// 6. Pipe the result back to the parent process and exit from child process. +/// +/// 7. If compilation succeeded, write the compiled artifact into a temporary file. /// -/// 7. Send the result of preparation back to the host. If any error occurred in the above steps, we -/// send that in the `PrepareResult`. +/// 8. Send the result of preparation back to the host, including the checksum of the artifact. If +/// any error occurred in the above steps, we send that in the `PrepareWorkerResult`. pub fn worker_entrypoint( socket_path: PathBuf, worker_dir_path: PathBuf, node_version: Option<&str>, worker_version: Option<&str>, - security_status: SecurityStatus, ) { - worker_event_loop( + run_worker( WorkerKind::Prepare, socket_path, worker_dir_path, node_version, worker_version, - &security_status, - |mut stream, worker_dir_path| async move { - let worker_pid = std::process::id(); - let temp_artifact_dest = worker_dir::prepare_tmp_artifact(&worker_dir_path); + |mut stream, worker_info, security_status| { + let temp_artifact_dest = worker_dir::prepare_tmp_artifact(&worker_info.worker_dir_path); loop { let pvf = recv_request(&mut stream)?; gum::debug!( target: LOG_TARGET, - %worker_pid, + ?worker_info, + ?security_status, "worker: preparing artifact", ); @@ -147,152 +228,70 @@ pub fn worker_entrypoint( let prepare_job_kind = pvf.prep_kind(); let executor_params = pvf.executor_params(); - // Conditional variable to notify us when a thread is done. - let condvar = thread::get_condvar(); - - // Run the memory tracker in a regular, non-worker thread. - #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] - let condvar_memory = Arc::clone(&condvar); - #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] - let memory_tracker_thread = std::thread::spawn(|| memory_tracker_loop(condvar_memory)); - - let cpu_time_start = ProcessTime::now(); + let (pipe_read_fd, pipe_write_fd) = pipe2_cloexec()?; - // Spawn a new thread that runs the CPU time monitor. - let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>(); - let cpu_time_monitor_thread = thread::spawn_worker_thread( - "cpu time monitor thread", - move || { - cpu_time_monitor_loop( - cpu_time_start, - preparation_timeout, - cpu_time_monitor_rx, - ) - }, - Arc::clone(&condvar), - WaitOutcome::TimedOut, - )?; - // Spawn another thread for preparation. - let prepare_thread = thread::spawn_worker_thread( - "prepare thread", - move || { - #[allow(unused_mut)] - let mut result = prepare_artifact(pvf, cpu_time_start); - - // Get the `ru_maxrss` stat. If supported, call getrusage for the thread. - #[cfg(target_os = "linux")] - let mut result = result - .map(|(artifact, elapsed)| (artifact, elapsed, get_max_rss_thread())); - - // If we are pre-checking, check for runtime construction errors. - // - // As pre-checking is more strict than just preparation in terms of memory - // and time, it is okay to do extra checks here. This takes negligible time - // anyway. - if let PrepareJobKind::Prechecking = prepare_job_kind { - result = result.and_then(|output| { - runtime_construction_check( - output.0.as_ref(), - executor_params.as_ref(), - )?; - Ok(output) - }); - } - - result - }, - Arc::clone(&condvar), - WaitOutcome::Finished, - )?; - - let outcome = thread::wait_for_threads(condvar); - - let result = match outcome { - WaitOutcome::Finished => { - let _ = cpu_time_monitor_tx.send(()); - - match prepare_thread.join().unwrap_or_else(|err| { - Err(PrepareError::Panic(stringify_panic_payload(err))) - }) { - Err(err) => { - // Serialized error will be written into the socket. - Err(err) - }, - Ok(ok) => { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let (artifact, cpu_time_elapsed, max_rss) = ok; - } else { - let (artifact, cpu_time_elapsed) = ok; - } - } - - // Stop the memory stats worker and get its observed memory stats. - #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] - let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_thread, worker_pid) - .await; - let memory_stats = MemoryStats { - #[cfg(any( - target_os = "linux", - feature = "jemalloc-allocator" - ))] - memory_tracker_stats, - #[cfg(target_os = "linux")] - max_rss: extract_max_rss_stat(max_rss, worker_pid), - }; - - // Write the serialized artifact into a temp file. - // - // PVF host only keeps artifacts statuses in its memory, - // successfully compiled code gets stored on the disk (and - // consequently deserialized by execute-workers). The prepare worker - // is only required to send `Ok` to the pool to indicate the - // success. - - gum::debug!( - target: LOG_TARGET, - %worker_pid, - "worker: writing artifact to {}", - temp_artifact_dest.display(), - ); - tokio::fs::write(&temp_artifact_dest, &artifact).await?; - - Ok(PrepareStats { cpu_time_elapsed, memory_stats }) - }, - } - }, - // If the CPU thread is not selected, we signal it to end, the join handle is - // dropped and the thread will finish in the background. - WaitOutcome::TimedOut => { - match cpu_time_monitor_thread.join() { - Ok(Some(cpu_time_elapsed)) => { - // Log if we exceed the timeout and the other thread hasn't - // finished. - gum::warn!( - target: LOG_TARGET, - %worker_pid, - "prepare job took {}ms cpu time, exceeded prepare timeout {}ms", - cpu_time_elapsed.as_millis(), - preparation_timeout.as_millis(), - ); - Err(PrepareError::TimedOut) - }, - Ok(None) => Err(PrepareError::IoErr( - "error communicating over closed channel".into(), - )), - // Errors in this thread are independent of the PVF. - Err(err) => Err(PrepareError::IoErr(stringify_panic_payload(err))), - } + let usage_before = match nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) { + Ok(usage) => usage, + Err(errno) => { + let result = Err(error_from_errno("getrusage before", errno)); + send_response(&mut stream, result)?; + continue }, - WaitOutcome::Pending => unreachable!( - "we run wait_while until the outcome is no longer pending; qed" - ), }; + let stream_fd = stream.as_raw_fd(); + + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + let result = if security_status.can_do_secure_clone { + handle_clone( + &pvf, + pipe_write_fd, + pipe_read_fd, + stream_fd, + preparation_timeout, + prepare_job_kind, + &executor_params, + worker_info, + security_status.can_unshare_user_namespace_and_change_root, + &temp_artifact_dest, + usage_before, + ) + } else { + // Fall back to using fork. + handle_fork( + &pvf, + pipe_write_fd, + pipe_read_fd, + stream_fd, + preparation_timeout, + prepare_job_kind, + &executor_params, + worker_info, + &temp_artifact_dest, + usage_before, + ) + }; + } else { + let result = handle_fork( + &pvf, + pipe_write_fd, + pipe_read_fd, + stream_fd, + preparation_timeout, + prepare_job_kind, + &executor_params, + worker_info, + &temp_artifact_dest, + usage_before, + ); + } + } + gum::trace!( target: LOG_TARGET, - %worker_pid, - "worker: sending response to host: {:?}", + ?worker_info, + "worker: sending result to host: {:?}", result ); send_response(&mut stream, result)?; @@ -301,10 +300,7 @@ pub fn worker_entrypoint( ); } -fn prepare_artifact( - pvf: PvfPrepData, - cpu_time_start: ProcessTime, -) -> Result<(CompiledArtifact, Duration), PrepareError> { +fn prepare_artifact(pvf: PvfPrepData) -> Result { let blob = match prevalidate(&pvf.code()) { Err(err) => return Err(PrepareError::Prevalidation(format!("{:?}", err))), Ok(b) => b, @@ -314,7 +310,6 @@ fn prepare_artifact( Ok(compiled_artifact) => Ok(CompiledArtifact::new(compiled_artifact)), Err(err) => Err(PrepareError::Preparation(format!("{:?}", err))), } - .map(|artifact| (artifact, cpu_time_start.elapsed())) } /// Try constructing the runtime to catch any instantiation errors during pre-checking. @@ -328,3 +323,472 @@ fn runtime_construction_check( .map(|_runtime| ()) .map_err(|err| PrepareError::RuntimeConstruction(format!("{:?}", err))) } + +#[derive(Encode, Decode)] +struct JobResponse { + artifact: CompiledArtifact, + memory_stats: MemoryStats, +} + +#[cfg(target_os = "linux")] +fn handle_clone( + pvf: &PvfPrepData, + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + preparation_timeout: Duration, + prepare_job_kind: PrepareJobKind, + executor_params: &Arc, + worker_info: &WorkerInfo, + have_unshare_newuser: bool, + temp_artifact_dest: &Path, + usage_before: Usage, +) -> Result { + use polkadot_node_core_pvf_common::worker::security; + + // SAFETY: new process is spawned within a single threaded process. This invariant + // is enforced by tests. Stack size being specified to ensure child doesn't overflow + match unsafe { + security::clone::clone_on_worker( + worker_info, + have_unshare_newuser, + Box::new(|| { + handle_child_process( + pvf.clone(), + pipe_write_fd, + pipe_read_fd, + stream_fd, + preparation_timeout, + prepare_job_kind, + Arc::clone(&executor_params), + ) + }), + ) + } { + Ok(child) => handle_parent_process( + pipe_read_fd, + pipe_write_fd, + worker_info, + child, + temp_artifact_dest, + usage_before, + preparation_timeout, + ), + Err(security::clone::Error::Clone(errno)) => Err(error_from_errno("clone", errno)), + } +} + +fn handle_fork( + pvf: &PvfPrepData, + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + preparation_timeout: Duration, + prepare_job_kind: PrepareJobKind, + executor_params: &Arc, + worker_info: &WorkerInfo, + temp_artifact_dest: &Path, + usage_before: Usage, +) -> Result { + // SAFETY: new process is spawned within a single threaded process. This invariant + // is enforced by tests. + match unsafe { nix::unistd::fork() } { + Ok(ForkResult::Child) => handle_child_process( + pvf.clone(), + pipe_write_fd, + pipe_read_fd, + stream_fd, + preparation_timeout, + prepare_job_kind, + Arc::clone(executor_params), + ), + Ok(ForkResult::Parent { child }) => handle_parent_process( + pipe_read_fd, + pipe_write_fd, + worker_info, + child, + temp_artifact_dest, + usage_before, + preparation_timeout, + ), + Err(errno) => Err(error_from_errno("fork", errno)), + } +} + +/// This is used to handle child process during pvf prepare worker. +/// It prepares the artifact and tracks memory stats during preparation +/// and pipes back the response to the parent process. +/// +/// # Returns +/// +/// - If any error occur, pipe response back with `PrepareError`. +/// +/// - If success, pipe back `JobResponse`. +fn handle_child_process( + pvf: PvfPrepData, + pipe_write_fd: i32, + pipe_read_fd: i32, + stream_fd: i32, + preparation_timeout: Duration, + prepare_job_kind: PrepareJobKind, + executor_params: Arc, +) -> ! { + // SAFETY: pipe_writer is an open and owned file descriptor at this point. + let mut pipe_write = unsafe { PipeFd::from_raw_fd(pipe_write_fd) }; + + // Drop the read end so we don't have too many FDs open. + if let Err(errno) = nix::unistd::close(pipe_read_fd) { + send_child_response( + &mut pipe_write, + JobResult::Err(error_from_errno("closing pipe", errno)), + ); + } + + // Dropping the stream closes the underlying socket. We want to make sure + // that the sandboxed child can't get any kind of information from the + // outside world. The only IPC it should be able to do is sending its + // response over the pipe. + if let Err(errno) = nix::unistd::close(stream_fd) { + send_child_response( + &mut pipe_write, + JobResult::Err(error_from_errno("error closing stream", errno)), + ); + } + + let worker_job_pid = process::id(); + gum::debug!( + target: LOG_TARGET, + %worker_job_pid, + ?prepare_job_kind, + ?preparation_timeout, + "worker job: preparing artifact", + ); + + // Conditional variable to notify us when a thread is done. + let condvar = thread::get_condvar(); + + // Run the memory tracker in a regular, non-worker thread. + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] + let condvar_memory = Arc::clone(&condvar); + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] + let memory_tracker_thread = std::thread::spawn(|| memory_tracker_loop(condvar_memory)); + + start_memory_tracking( + pipe_write.as_raw_fd(), + executor_params.prechecking_max_memory().map(|v| { + v.try_into().unwrap_or_else(|_| { + gum::warn!( + LOG_TARGET, + %worker_job_pid, + "Illegal pre-checking max memory value {} discarded", + v, + ); + 0 + }) + }), + ); + + let cpu_time_start = ProcessTime::now(); + + // Spawn a new thread that runs the CPU time monitor. + let (cpu_time_monitor_tx, cpu_time_monitor_rx) = channel::<()>(); + let cpu_time_monitor_thread = thread::spawn_worker_thread( + "cpu time monitor thread", + move || cpu_time_monitor_loop(cpu_time_start, preparation_timeout, cpu_time_monitor_rx), + Arc::clone(&condvar), + WaitOutcome::TimedOut, + ) + .unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(PrepareError::IoErr(err.to_string()))) + }); + + let prepare_thread = spawn_worker_thread( + "prepare worker", + move || { + #[allow(unused_mut)] + let mut result = prepare_artifact(pvf); + + // Get the `ru_maxrss` stat. If supported, call getrusage for the thread. + #[cfg(target_os = "linux")] + let mut result = result.map(|artifact| (artifact, get_max_rss_thread())); + + // If we are pre-checking, check for runtime construction errors. + // + // As pre-checking is more strict than just preparation in terms of memory + // and time, it is okay to do extra checks here. This takes negligible time + // anyway. + if let PrepareJobKind::Prechecking = prepare_job_kind { + result = result.and_then(|output| { + runtime_construction_check(output.0.as_ref(), &executor_params)?; + Ok(output) + }); + } + result + }, + Arc::clone(&condvar), + WaitOutcome::Finished, + ) + .unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(PrepareError::IoErr(err.to_string()))) + }); + + let outcome = thread::wait_for_threads(condvar); + + let peak_alloc = { + let peak = end_memory_tracking(); + gum::debug!( + target: LOG_TARGET, + %worker_job_pid, + "prepare job peak allocation is {} bytes", + peak, + ); + peak + }; + + let result = match outcome { + WaitOutcome::Finished => { + let _ = cpu_time_monitor_tx.send(()); + + match prepare_thread.join().unwrap_or_else(|err| { + send_child_response( + &mut pipe_write, + Err(PrepareError::JobError(stringify_panic_payload(err))), + ) + }) { + Err(err) => Err(err), + Ok(ok) => { + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + let (artifact, max_rss) = ok; + } else { + let artifact = ok; + } + } + + // Stop the memory stats worker and get its observed memory stats. + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] + let memory_tracker_stats = get_memory_tracker_loop_stats(memory_tracker_thread, process::id()); + + let memory_stats = MemoryStats { + #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] + memory_tracker_stats, + #[cfg(target_os = "linux")] + max_rss: extract_max_rss_stat(max_rss, process::id()), + // Negative peak allocation values are legit; they are narrow + // corner cases and shouldn't affect overall statistics + // significantly + peak_tracked_alloc: if peak_alloc > 0 { peak_alloc as u64 } else { 0u64 }, + }; + + Ok(JobResponse { artifact, memory_stats }) + }, + } + }, + + // If the CPU thread is not selected, we signal it to end, the join handle is + // dropped and the thread will finish in the background. + WaitOutcome::TimedOut => match cpu_time_monitor_thread.join() { + Ok(Some(_cpu_time_elapsed)) => Err(PrepareError::TimedOut), + Ok(None) => Err(PrepareError::IoErr("error communicating over closed channel".into())), + Err(err) => Err(PrepareError::IoErr(stringify_panic_payload(err))), + }, + WaitOutcome::Pending => + unreachable!("we run wait_while until the outcome is no longer pending; qed"), + }; + + send_child_response(&mut pipe_write, result); +} + +/// Waits for child process to finish and handle child response from pipe. +/// +/// # Returns +/// +/// - If the child send response without an error, this function returns `Ok(PrepareStats)` +/// containing memory and CPU usage statistics. +/// +/// - If the child send response with an error, it returns a `PrepareError` with that error. +/// +/// - If the child process timeout, it returns `PrepareError::TimedOut`. +fn handle_parent_process( + pipe_read_fd: i32, + pipe_write_fd: i32, + worker_info: &WorkerInfo, + job_pid: Pid, + temp_artifact_dest: &Path, + usage_before: Usage, + timeout: Duration, +) -> Result { + // the read end will wait until all write ends have been closed, + // this drop is necessary to avoid deadlock + if let Err(errno) = nix::unistd::close(pipe_write_fd) { + return Err(error_from_errno("closing pipe write fd", errno)); + }; + + // SAFETY: this is an open and owned file descriptor at this point. + let mut pipe_read = unsafe { PipeFd::from_raw_fd(pipe_read_fd) }; + + // Read from the child. Don't decode unless the process exited normally, which we check later. + let mut received_data = Vec::new(); + pipe_read + .read_to_end(&mut received_data) + .map_err(|err| PrepareError::IoErr(err.to_string()))?; + + let status = nix::sys::wait::waitpid(job_pid, None); + gum::trace!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "prepare worker received wait status from job: {:?}", + status, + ); + + let usage_after = nix::sys::resource::getrusage(UsageWho::RUSAGE_CHILDREN) + .map_err(|errno| error_from_errno("getrusage after", errno))?; + + // Using `getrusage` is needed to check whether child has timedout since we cannot rely on + // child to report its own time. + // As `getrusage` returns resource usage from all terminated child processes, + // it is necessary to subtract the usage before the current child process to isolate its cpu + // time + let cpu_tv = get_total_cpu_usage(usage_after) - get_total_cpu_usage(usage_before); + if cpu_tv >= timeout { + gum::warn!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "prepare job took {}ms cpu time, exceeded prepare timeout {}ms", + cpu_tv.as_millis(), + timeout.as_millis(), + ); + return Err(PrepareError::TimedOut) + } + + match status { + Ok(WaitStatus::Exited(_pid, exit_status)) => { + let mut reader = io::BufReader::new(received_data.as_slice()); + let result = recv_child_response(&mut reader) + .map_err(|err| PrepareError::JobError(err.to_string()))?; + + match result { + Err(err) => Err(err), + Ok(JobResponse { artifact, memory_stats }) => { + // The exit status should have been zero if no error occurred. + if exit_status != 0 { + return Err(PrepareError::JobError(format!( + "unexpected exit status: {}", + exit_status + ))) + } + + // Write the serialized artifact into a temp file. + // + // PVF host only keeps artifacts statuses in its memory, + // successfully compiled code gets stored on the disk (and + // consequently deserialized by execute-workers). The prepare worker + // is only required to send `Ok` to the pool to indicate the + // success. + gum::debug!( + target: LOG_TARGET, + ?worker_info, + %job_pid, + "worker: writing artifact to {}", + temp_artifact_dest.display(), + ); + // Write to the temp file created by the host. + if let Err(err) = fs::write(temp_artifact_dest, &artifact) { + return Err(PrepareError::IoErr(err.to_string())) + }; + + let checksum = blake3::hash(&artifact.as_ref()).to_hex().to_string(); + Ok(PrepareWorkerSuccess { + checksum, + stats: PrepareStats { memory_stats, cpu_time_elapsed: cpu_tv }, + }) + }, + } + }, + // The job was killed by the given signal. + // + // The job gets SIGSYS on seccomp violations, but this signal may have been sent for some + // other reason, so we still need to check for seccomp violations elsewhere. + Ok(WaitStatus::Signaled(_pid, signal, _core_dump)) => Err(PrepareError::JobDied { + err: format!("received signal: {signal:?}"), + job_pid: job_pid.as_raw(), + }), + Err(errno) => Err(error_from_errno("waitpid", errno)), + + // An attacker can make the child process return any exit status it wants. So we can treat + // all unexpected cases the same way. + Ok(unexpected_wait_status) => Err(PrepareError::JobDied { + err: format!("unexpected status from wait: {unexpected_wait_status:?}"), + job_pid: job_pid.as_raw(), + }), + } +} + +/// Calculate the total CPU time from the given `usage` structure, returned from +/// [`nix::sys::resource::getrusage`], and calculates the total CPU time spent, including both user +/// and system time. +/// +/// # Arguments +/// +/// - `rusage`: Contains resource usage information. +/// +/// # Returns +/// +/// Returns a `Duration` representing the total CPU time. +fn get_total_cpu_usage(rusage: Usage) -> Duration { + let micros = (((rusage.user_time().tv_sec() + rusage.system_time().tv_sec()) * 1_000_000) + + (rusage.system_time().tv_usec() + rusage.user_time().tv_usec()) as i64) as u64; + + return Duration::from_micros(micros) +} + +/// Get a job response. +fn recv_child_response(received_data: &mut io::BufReader<&[u8]>) -> io::Result { + let response_bytes = framed_recv_blocking(received_data)?; + JobResult::decode(&mut response_bytes.as_slice()).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("prepare pvf recv_child_response: decode error: {:?}", e), + ) + }) +} + +/// Write a job response to the pipe and exit process after. +/// +/// # Arguments +/// +/// - `pipe_write`: A `PipeFd` structure, the writing end of a pipe. +/// +/// - `response`: Child process response +fn send_child_response(pipe_write: &mut PipeFd, response: JobResult) -> ! { + framed_send_blocking(pipe_write, response.encode().as_slice()) + .unwrap_or_else(|_| process::exit(libc::EXIT_FAILURE)); + + if response.is_ok() { + process::exit(libc::EXIT_SUCCESS) + } else { + process::exit(libc::EXIT_FAILURE) + } +} + +fn error_from_errno(context: &'static str, errno: Errno) -> PrepareError { + PrepareError::Kernel(format!("{}: {}: {}", context, errno, io::Error::last_os_error())) +} + +type JobResult = Result; + +/// Pre-encoded length-prefixed `JobResult::Err(PrepareError::OutOfMemory)` +const OOM_PAYLOAD: &[u8] = b"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x08"; + +#[test] +fn pre_encoded_payloads() { + // NOTE: This must match the type of `response` in `send_child_response`. + let oom_unencoded: JobResult = JobResult::Err(PrepareError::OutOfMemory); + let oom_encoded = oom_unencoded.encode(); + // The payload is prefixed with its length in `framed_send`. + let mut oom_payload = oom_encoded.len().to_le_bytes().to_vec(); + oom_payload.extend(oom_encoded); + assert_eq!(oom_payload, OOM_PAYLOAD); +} diff --git a/polkadot/node/core/pvf/prepare-worker/src/memory_stats.rs b/polkadot/node/core/pvf/prepare-worker/src/memory_stats.rs index c70ff56fc84dbb00439d0c461179425809749739..5f577b0901c252159aed9035c9f2b0cd7765d3c6 100644 --- a/polkadot/node/core/pvf/prepare-worker/src/memory_stats.rs +++ b/polkadot/node/core/pvf/prepare-worker/src/memory_stats.rs @@ -122,7 +122,7 @@ pub mod memory_tracker { } /// Helper function to get the stats from the memory tracker. Helps isolate this error handling. - pub async fn get_memory_tracker_loop_stats( + pub fn get_memory_tracker_loop_stats( thread: JoinHandle>, worker_pid: u32, ) -> Option { diff --git a/polkadot/node/core/pvf/src/artifacts.rs b/polkadot/node/core/pvf/src/artifacts.rs index d7b15ece7b29457bde9c862f6720a7ad505b6dbe..6288755526d497812027f7bf04e11e5a67ab799e 100644 --- a/polkadot/node/core/pvf/src/artifacts.rs +++ b/polkadot/node/core/pvf/src/artifacts.rs @@ -16,10 +16,9 @@ //! PVF artifacts (final compiled code blobs). //! -//! # Lifecycle of an artifact +//! # Lifecycle of an artifact //! -//! 1. During node start-up, the artifacts cache is cleaned up. This means that all local artifacts -//! stored on-disk are cleared, and we start with an empty [`Artifacts`] table. +//! 1. During node start-up, we prune all the cached artifacts, if any. //! //! 2. In order to be executed, a PVF should be prepared first. This means that artifacts should //! have an [`ArtifactState::Prepared`] entry for that artifact in the table. If not, the @@ -55,18 +54,37 @@ //! older by a predefined parameter. This process is run very rarely (say, once a day). Once the //! artifact is expired it is removed from disk eagerly atomically. -use crate::host::PrepareResultSender; +use crate::{host::PrecheckResultSender, worker_interface::WORKER_DIR_PREFIX}; use always_assert::always; use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats, pvf::PvfPrepData}; -use polkadot_node_primitives::NODE_VERSION; use polkadot_parachain_primitives::primitives::ValidationCodeHash; use polkadot_primitives::ExecutorParamsHash; use std::{ collections::HashMap, + fs, path::{Path, PathBuf}, time::{Duration, SystemTime}, }; +/// The extension to use for cached artifacts. +const ARTIFACT_EXTENSION: &str = "pvf"; + +/// The prefix that artifacts used to start with under the old naming scheme. +const ARTIFACT_OLD_PREFIX: &str = "wasmtime_"; + +pub fn generate_artifact_path(cache_path: &Path) -> PathBuf { + let file_name = { + use array_bytes::Hex; + use rand::RngCore; + let mut bytes = [0u8; 64]; + rand::thread_rng().fill_bytes(&mut bytes); + bytes.hex("0x") + }; + let mut artifact_path = cache_path.join(file_name); + artifact_path.set_extension(ARTIFACT_EXTENSION); + artifact_path +} + /// Identifier of an artifact. Encodes a code hash of the PVF and a hash of executor parameter set. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ArtifactId { @@ -75,9 +93,6 @@ pub struct ArtifactId { } impl ArtifactId { - const PREFIX: &'static str = "wasmtime_"; - const NODE_VERSION_PREFIX: &'static str = "polkadot_v"; - /// Creates a new artifact ID with the given hash. pub fn new(code_hash: ValidationCodeHash, executor_params_hash: ExecutorParamsHash) -> Self { Self { code_hash, executor_params_hash } @@ -87,39 +102,6 @@ impl ArtifactId { pub fn from_pvf_prep_data(pvf: &PvfPrepData) -> Self { Self::new(pvf.code_hash(), pvf.executor_params().hash()) } - - /// Tries to recover the artifact id from the given file name. - #[cfg(test)] - pub fn from_file_name(file_name: &str) -> Option { - use polkadot_core_primitives::Hash; - use std::str::FromStr as _; - - let file_name = - file_name.strip_prefix(Self::PREFIX)?.strip_prefix(Self::NODE_VERSION_PREFIX)?; - - // [ node version | code hash | param hash ] - let parts: Vec<&str> = file_name.split('_').collect(); - let (_node_ver, code_hash_str, executor_params_hash_str) = (parts[0], parts[1], parts[2]); - - let code_hash = Hash::from_str(code_hash_str).ok()?.into(); - let executor_params_hash = - ExecutorParamsHash::from_hash(Hash::from_str(executor_params_hash_str).ok()?); - - Some(Self { code_hash, executor_params_hash }) - } - - /// Returns the expected path to this artifact given the root of the cache. - pub fn path(&self, cache_path: &Path) -> PathBuf { - let file_name = format!( - "{}{}{}_{:#x}_{:#x}", - Self::PREFIX, - Self::NODE_VERSION_PREFIX, - NODE_VERSION, - self.code_hash, - self.executor_params_hash - ); - cache_path.join(file_name) - } } /// A bundle of the artifact ID and the path. @@ -136,17 +118,20 @@ pub struct ArtifactPathId { } impl ArtifactPathId { - pub(crate) fn new(artifact_id: ArtifactId, cache_path: &Path) -> Self { - Self { path: artifact_id.path(cache_path), id: artifact_id } + pub(crate) fn new(artifact_id: ArtifactId, path: &Path) -> Self { + Self { id: artifact_id, path: path.to_owned() } } } +#[derive(Debug)] pub enum ArtifactState { /// The artifact is ready to be used by the executor. /// /// That means that the artifact should be accessible through the path obtained by the artifact /// id (unless, it was removed externally). Prepared { + /// The path of the compiled artifact. + path: PathBuf, /// The time when the artifact was last needed. /// /// This is updated when we get the heads up for this artifact or when we just discover @@ -158,7 +143,7 @@ pub enum ArtifactState { /// A task to prepare this artifact is scheduled. Preparing { /// List of result senders that are waiting for a response. - waiting_for_response: Vec, + waiting_for_response: Vec, /// The number of times this artifact has failed to prepare. num_failures: u32, }, @@ -176,32 +161,46 @@ pub enum ArtifactState { /// A container of all known artifact ids and their states. pub struct Artifacts { - artifacts: HashMap, + inner: HashMap, } impl Artifacts { - /// Initialize a blank cache at the given path. This will clear everything present at the - /// given path, to be populated over time. - /// - /// The recognized artifacts will be filled in the table and unrecognized will be removed. + #[cfg(test)] + pub(crate) fn empty() -> Self { + Self { inner: HashMap::new() } + } + + #[cfg(test)] + fn len(&self) -> usize { + self.inner.len() + } + + /// Create an empty table and the cache directory on-disk if it doesn't exist. pub async fn new(cache_path: &Path) -> Self { - // First delete the entire cache. This includes artifacts and any leftover worker dirs (see - // [`WorkerDir`]). Nodes are long-running so this should populate shortly. - let _ = tokio::fs::remove_dir_all(cache_path).await; // Make sure that the cache path directory and all its parents are created. let _ = tokio::fs::create_dir_all(cache_path).await; - Self { artifacts: HashMap::new() } - } + // Delete any leftover artifacts and worker dirs from previous runs. We don't delete the + // entire cache directory in case the user made a mistake and set it to e.g. their home + // directory. This is a best-effort to do clean-up, so ignore any errors. + for entry in fs::read_dir(cache_path).into_iter().flatten().flatten() { + let path = entry.path(); + let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { continue }; + if path.is_dir() && file_name.starts_with(WORKER_DIR_PREFIX) { + let _ = fs::remove_dir_all(path); + } else if path.extension().map_or(false, |ext| ext == ARTIFACT_EXTENSION) || + file_name.starts_with(ARTIFACT_OLD_PREFIX) + { + let _ = fs::remove_file(path); + } + } - #[cfg(test)] - pub(crate) fn empty() -> Self { - Self { artifacts: HashMap::new() } + Self { inner: HashMap::new() } } /// Returns the state of the given artifact by its ID. pub fn artifact_state_mut(&mut self, artifact_id: &ArtifactId) -> Option<&mut ArtifactState> { - self.artifacts.get_mut(artifact_id) + self.inner.get_mut(artifact_id) } /// Inform the table about the artifact with the given ID. The state will be set to "preparing". @@ -211,53 +210,61 @@ impl Artifacts { pub fn insert_preparing( &mut self, artifact_id: ArtifactId, - waiting_for_response: Vec, + waiting_for_response: Vec, ) { // See the precondition. always!(self - .artifacts + .inner .insert(artifact_id, ArtifactState::Preparing { waiting_for_response, num_failures: 0 }) .is_none()); } /// Insert an artifact with the given ID as "prepared". /// - /// This function must be used only for brand-new artifacts and should never be used for - /// replacing existing ones. + /// This function should only be used to build the artifact table at startup with valid + /// artifact caches. #[cfg(test)] - pub fn insert_prepared( + pub(crate) fn insert_prepared( &mut self, artifact_id: ArtifactId, + path: PathBuf, last_time_needed: SystemTime, prepare_stats: PrepareStats, ) { // See the precondition. always!(self - .artifacts - .insert(artifact_id, ArtifactState::Prepared { last_time_needed, prepare_stats }) + .inner + .insert(artifact_id, ArtifactState::Prepared { path, last_time_needed, prepare_stats }) .is_none()); } - /// Remove and retrieve the artifacts from the table that are older than the supplied - /// Time-To-Live. - pub fn prune(&mut self, artifact_ttl: Duration) -> Vec { + /// Remove artifact by its id. + pub fn remove(&mut self, artifact_id: ArtifactId) -> Option<(ArtifactId, PathBuf)> { + self.inner.remove(&artifact_id).and_then(|state| match state { + ArtifactState::Prepared { path, .. } => Some((artifact_id, path)), + _ => None, + }) + } + + /// Remove artifacts older than the given TTL and return id and path of the removed ones. + pub fn prune(&mut self, artifact_ttl: Duration) -> Vec<(ArtifactId, PathBuf)> { let now = SystemTime::now(); let mut to_remove = vec![]; - for (k, v) in self.artifacts.iter() { - if let ArtifactState::Prepared { last_time_needed, .. } = *v { + for (k, v) in self.inner.iter() { + if let ArtifactState::Prepared { last_time_needed, ref path, .. } = *v { if now .duration_since(last_time_needed) .map(|age| age > artifact_ttl) .unwrap_or(false) { - to_remove.push(k.clone()); + to_remove.push((k.clone(), path.clone())); } } } for artifact in &to_remove { - self.artifacts.remove(artifact); + self.inner.remove(&artifact.0); } to_remove @@ -266,78 +273,33 @@ impl Artifacts { #[cfg(test)] mod tests { - use super::{ArtifactId, Artifacts, NODE_VERSION}; - use polkadot_primitives::ExecutorParamsHash; - use sp_core::H256; - use std::{path::Path, str::FromStr}; - - fn file_name(code_hash: &str, param_hash: &str) -> String { - format!("wasmtime_polkadot_v{}_0x{}_0x{}", NODE_VERSION, code_hash, param_hash) - } - - #[test] - fn from_file_name() { - assert!(ArtifactId::from_file_name("").is_none()); - assert!(ArtifactId::from_file_name("junk").is_none()); - - let file_name = file_name( - "0022800000000000000000000000000000000000000000000000000000000000", - "0033900000000000000000000000000000000000000000000000000000000000", - ); - - assert_eq!( - ArtifactId::from_file_name(&file_name), - Some(ArtifactId::new( - hex_literal::hex![ - "0022800000000000000000000000000000000000000000000000000000000000" - ] - .into(), - ExecutorParamsHash::from_hash(sp_core::H256(hex_literal::hex![ - "0033900000000000000000000000000000000000000000000000000000000000" - ])), - )), - ); - } - - #[test] - fn path() { - let dir = Path::new("/test"); - let code_hash = "1234567890123456789012345678901234567890123456789012345678901234"; - let params_hash = "4321098765432109876543210987654321098765432109876543210987654321"; - let file_name = file_name(code_hash, params_hash); - - let code_hash = H256::from_str(code_hash).unwrap(); - let params_hash = H256::from_str(params_hash).unwrap(); - - assert_eq!( - ArtifactId::new(code_hash.into(), ExecutorParamsHash::from_hash(params_hash)) - .path(dir) - .to_str(), - Some(format!("/test/{}", file_name).as_str()), - ); - } + use super::*; #[tokio::test] - async fn artifacts_removes_cache_on_startup() { - let fake_cache_path = crate::worker_intf::tmppath("test-cache").await.unwrap(); - let fake_artifact_path = { - let mut p = fake_cache_path.clone(); - p.push("wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234"); - p - }; - - // create a tmp cache with 1 artifact. - - std::fs::create_dir_all(&fake_cache_path).unwrap(); - std::fs::File::create(fake_artifact_path).unwrap(); - - // this should remove it and re-create. - - let p = &fake_cache_path; - Artifacts::new(p).await; - - assert_eq!(std::fs::read_dir(&fake_cache_path).unwrap().count(), 0); - - std::fs::remove_dir_all(fake_cache_path).unwrap(); + async fn cache_cleared_on_startup() { + let tempdir = tempfile::tempdir().unwrap(); + let cache_path = tempdir.path(); + + // These should be cleared. + fs::write(cache_path.join("abcd.pvf"), "test").unwrap(); + fs::write(cache_path.join("wasmtime_..."), "test").unwrap(); + fs::create_dir(cache_path.join("worker-dir-prepare-test")).unwrap(); + + // These should not be touched. + fs::write(cache_path.join("abcd.pvfartifact"), "test").unwrap(); + fs::write(cache_path.join("polkadot_..."), "test").unwrap(); + fs::create_dir(cache_path.join("worker-prepare-test")).unwrap(); + + let artifacts = Artifacts::new(cache_path).await; + + let entries: Vec = fs::read_dir(&cache_path) + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .collect(); + assert_eq!(entries.len(), 3); + assert!(entries.contains(&String::from("abcd.pvfartifact"))); + assert!(entries.contains(&String::from("polkadot_..."))); + assert!(entries.contains(&String::from("worker-prepare-test"))); + assert_eq!(artifacts.len(), 0); } } diff --git a/polkadot/node/core/pvf/src/error.rs b/polkadot/node/core/pvf/src/error.rs index 87ef0b54a0406776b865ea143185fd8012ef82e1..8dc96305eadb8f3ced1231889f5fa6c5ffaeac57 100644 --- a/polkadot/node/core/pvf/src/error.rs +++ b/polkadot/node/core/pvf/src/error.rs @@ -17,58 +17,79 @@ use polkadot_node_core_pvf_common::error::{InternalValidationError, PrepareError}; /// A error raised during validation of the candidate. -#[derive(Debug, Clone)] +#[derive(thiserror::Error, Debug, Clone)] pub enum ValidationError { - /// The error was raised because the candidate is invalid. + /// Deterministic preparation issue. In practice, most of the problems should be caught by + /// prechecking, so this may be a sign of internal conditions. /// - /// Whenever we are unsure if the error was due to the candidate or not, we must vote invalid. - InvalidCandidate(InvalidCandidate), - /// Some internal error occurred. - InternalError(InternalValidationError), + /// In principle if preparation of the `WASM` fails, the current candidate cannot be the + /// reason for that. So we can't say whether it is invalid or not. In addition, with + /// pre-checking enabled only valid runtimes should ever get enacted, so we can be + /// reasonably sure that this is some local problem on the current node. However, as this + /// particular error *seems* to indicate a deterministic error, we raise a warning. + #[error("candidate validation: {0}")] + Preparation(PrepareError), + /// The error was raised because the candidate is invalid. Should vote against. + #[error("candidate validation: {0}")] + Invalid(#[from] InvalidCandidate), + /// Possibly transient issue that may resolve after retries. Should vote against when retries + /// fail. + #[error("candidate validation: {0}")] + PossiblyInvalid(#[from] PossiblyInvalidError), + /// Preparation or execution issue caused by an internal condition. Should not vote against. + #[error("candidate validation: internal: {0}")] + Internal(#[from] InternalValidationError), } /// A description of an error raised during executing a PVF and can be attributed to the combination /// of the candidate [`polkadot_parachain_primitives::primitives::ValidationParams`] and the PVF. -#[derive(Debug, Clone)] +#[derive(thiserror::Error, Debug, Clone)] pub enum InvalidCandidate { - /// PVF preparation ended up with a deterministic error. - PrepareError(String), - /// The failure is reported by the execution worker. The string contains the error message. - WorkerReportedError(String), - /// The worker has died during validation of a candidate. That may fall in one of the following - /// categories, which we cannot distinguish programmatically: - /// - /// (a) Some sort of transient glitch caused the worker process to abort. An example would be - /// that the host machine ran out of free memory and the OOM killer started killing the - /// processes, and in order to save the parent it will "sacrifice child" first. - /// - /// (b) The candidate triggered a code path that has lead to the process death. For example, - /// the PVF found a way to consume unbounded amount of resources and then it either - /// exceeded an `rlimit` (if set) or, again, invited OOM killer. Another possibility is a - /// bug in wasmtime allowed the PVF to gain control over the execution worker. - /// - /// We attribute such an event to an *invalid candidate* in either case. - /// - /// The rationale for this is that a glitch may lead to unfair rejecting candidate by a single - /// validator. If the glitch is somewhat more persistent the validator will reject all - /// candidate thrown at it and hopefully the operator notices it by decreased reward - /// performance of the validator. On the other hand, if the worker died because of (b) we would - /// have better chances to stop the attack. - AmbiguousWorkerDeath, + /// The candidate is reported to be invalid by the execution worker. The string contains the + /// error message. + #[error("invalid: worker reported: {0}")] + WorkerReportedInvalid(String), /// PVF execution (compilation is not included) took more time than was allotted. + #[error("invalid: hard timeout")] HardTimeout, - /// A panic occurred and we can't be sure whether the candidate is really invalid or some - /// internal glitch occurred. Whenever we are unsure, we can never treat an error as internal - /// as we would abstain from voting. This is bad because if the issue was due to the candidate, - /// then all validators would abstain, stalling finality on the chain. So we will first retry - /// the candidate, and if the issue persists we are forced to vote invalid. - Panic(String), } -impl From for ValidationError { - fn from(error: InternalValidationError) -> Self { - Self::InternalError(error) - } +/// Possibly transient issue that may resolve after retries. +#[derive(thiserror::Error, Debug, Clone)] +pub enum PossiblyInvalidError { + /// The worker process (not the job) has died during validation of a candidate. + /// + /// It's unlikely that this is caused by malicious code since workers spawn separate job + /// processes, and those job processes are sandboxed. But, it is possible. We retry in this + /// case, and if the error persists, we assume it's caused by the candidate and vote against. + #[error("possibly invalid: ambiguous worker death")] + AmbiguousWorkerDeath, + /// The job process (not the worker) has died for one of the following reasons: + /// + /// (a) A seccomp violation occurred, most likely due to an attempt by malicious code to + /// execute arbitrary code. Note that there is no foolproof way to detect this if the operator + /// has seccomp auditing disabled. + /// + /// (b) The host machine ran out of free memory and the OOM killer started killing the + /// processes, and in order to save the parent it will "sacrifice child" first. + /// + /// (c) Some other reason, perhaps transient or perhaps caused by malicious code. + /// + /// We cannot treat this as an internal error because malicious code may have caused this. + #[error("possibly invalid: ambiguous job death: {0}")] + AmbiguousJobDeath(String), + /// An unexpected error occurred in the job process and we can't be sure whether the candidate + /// is really invalid or some internal glitch occurred. Whenever we are unsure, we can never + /// treat an error as internal as we would abstain from voting. This is bad because if the + /// issue was due to the candidate, then all validators would abstain, stalling finality on the + /// chain. So we will first retry the candidate, and if the issue persists we are forced to + /// vote invalid. + #[error("possibly invalid: job error: {0}")] + JobError(String), + /// Instantiation of the WASM module instance failed during an execution. + /// Possibly related to local issues or dirty node update. May be retried with re-preparation. + #[error("possibly invalid: runtime construction: {0}")] + RuntimeConstruction(String), } impl From for ValidationError { @@ -76,9 +97,9 @@ impl From for ValidationError { // Here we need to classify the errors into two errors: deterministic and non-deterministic. // See [`PrepareError::is_deterministic`]. if error.is_deterministic() { - Self::InvalidCandidate(InvalidCandidate::PrepareError(error.to_string())) + Self::Preparation(error) } else { - Self::InternalError(InternalValidationError::NonDeterministicPrepareError(error)) + Self::Internal(InternalValidationError::NonDeterministicPrepareError(error)) } } } diff --git a/polkadot/node/core/pvf/src/execute/mod.rs b/polkadot/node/core/pvf/src/execute/mod.rs index 669b9dc04d7c518d06791536462b2c8bdbcd91ea..365e98196cae29dcb9c5085708cfaf7ee8b26238 100644 --- a/polkadot/node/core/pvf/src/execute/mod.rs +++ b/polkadot/node/core/pvf/src/execute/mod.rs @@ -21,6 +21,6 @@ //! `polkadot_node_core_pvf_worker::execute_worker_entrypoint`. mod queue; -mod worker_intf; +mod worker_interface; -pub use queue::{start, PendingExecutionRequest, ToQueue}; +pub use queue::{start, FromQueue, PendingExecutionRequest, ToQueue}; diff --git a/polkadot/node/core/pvf/src/execute/queue.rs b/polkadot/node/core/pvf/src/execute/queue.rs index aca604f0de21dd805e10c68c1a8d80c202010e9b..bdc3c7327b06079eaaeebf737ffa7ca2c8c8270c 100644 --- a/polkadot/node/core/pvf/src/execute/queue.rs +++ b/polkadot/node/core/pvf/src/execute/queue.rs @@ -16,16 +16,16 @@ //! A queue that handles requests for PVF execution. -use super::worker_intf::Outcome; +use super::worker_interface::Outcome; use crate::{ artifacts::{ArtifactId, ArtifactPathId}, host::ResultSender, metrics::Metrics, - worker_intf::{IdleWorker, WorkerHandle}, - InvalidCandidate, ValidationError, LOG_TARGET, + worker_interface::{IdleWorker, WorkerHandle}, + InvalidCandidate, PossiblyInvalidError, ValidationError, LOG_TARGET, }; use futures::{ - channel::mpsc, + channel::{mpsc, oneshot}, future::BoxFuture, stream::{FuturesUnordered, StreamExt as _}, Future, FutureExt, @@ -54,6 +54,12 @@ pub enum ToQueue { Enqueue { artifact: ArtifactPathId, pending_execution_request: PendingExecutionRequest }, } +/// A response from queue. +#[derive(Debug)] +pub enum FromQueue { + RemoveArtifact { artifact: ArtifactId, reply_to: oneshot::Sender<()> }, +} + /// An execution request that should execute the PVF (known in the context) and send the results /// to the given result sender. #[derive(Debug)] @@ -137,6 +143,8 @@ struct Queue { /// The receiver that receives messages to the pool. to_queue_rx: mpsc::Receiver, + /// The sender to send messages back to validation host. + from_queue_tx: mpsc::UnboundedSender, // Some variables related to the current session. program_path: PathBuf, @@ -161,6 +169,7 @@ impl Queue { node_version: Option, security_status: SecurityStatus, to_queue_rx: mpsc::Receiver, + from_queue_tx: mpsc::UnboundedSender, ) -> Self { Self { metrics, @@ -170,6 +179,7 @@ impl Queue { node_version, security_status, to_queue_rx, + from_queue_tx, queue: VecDeque::new(), mux: Mux::new(), workers: Workers { @@ -301,7 +311,7 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) { handle_worker_spawned(queue, idle, handle, job); }, QueueEvent::StartWork(worker, outcome, artifact_id, result_tx) => { - handle_job_finish(queue, worker, outcome, artifact_id, result_tx); + handle_job_finish(queue, worker, outcome, artifact_id, result_tx).await; }, } } @@ -327,35 +337,69 @@ fn handle_worker_spawned( /// If there are pending jobs in the queue, schedules the next of them onto the just freed up /// worker. Otherwise, puts back into the available workers list. -fn handle_job_finish( +async fn handle_job_finish( queue: &mut Queue, worker: Worker, outcome: Outcome, artifact_id: ArtifactId, result_tx: ResultSender, ) { - let (idle_worker, result, duration) = match outcome { + let (idle_worker, result, duration, sync_channel) = match outcome { Outcome::Ok { result_descriptor, duration, idle_worker } => { // TODO: propagate the soft timeout - (Some(idle_worker), Ok(result_descriptor), Some(duration)) + (Some(idle_worker), Ok(result_descriptor), Some(duration), None) }, Outcome::InvalidCandidate { err, idle_worker } => ( Some(idle_worker), - Err(ValidationError::InvalidCandidate(InvalidCandidate::WorkerReportedError(err))), + Err(ValidationError::Invalid(InvalidCandidate::WorkerReportedInvalid(err))), + None, None, ), - Outcome::InternalError { err } => (None, Err(ValidationError::InternalError(err)), None), + Outcome::RuntimeConstruction { err, idle_worker } => { + // The task for artifact removal is executed concurrently with + // the message to the host on the execution result. + let (result_tx, result_rx) = oneshot::channel(); + queue + .from_queue_tx + .unbounded_send(FromQueue::RemoveArtifact { + artifact: artifact_id.clone(), + reply_to: result_tx, + }) + .expect("from execute queue receiver is listened by the host; qed"); + ( + Some(idle_worker), + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::RuntimeConstruction( + err, + ))), + None, + Some(result_rx), + ) + }, + Outcome::InternalError { err } => (None, Err(ValidationError::Internal(err)), None, None), + // Either the worker or the job timed out. Kill the worker in either case. Treated as + // definitely-invalid, because if we timed out, there's no time left for a retry. Outcome::HardTimeout => - (None, Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)), None), + (None, Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)), None, None), // "Maybe invalid" errors (will retry). - Outcome::IoErr => ( + Outcome::WorkerIntfErr => ( + None, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)), + None, + None, + ), + Outcome::JobDied { err } => ( + None, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousJobDeath(err))), + None, + None, + ), + Outcome::JobError { err } => ( + None, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(err))), None, - Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath)), None, ), - Outcome::Panic { err } => - (None, Err(ValidationError::InvalidCandidate(InvalidCandidate::Panic(err))), None), }; queue.metrics.execute_finished(); @@ -365,7 +409,7 @@ fn handle_job_finish( ?artifact_id, ?worker, worker_rip = idle_worker.is_none(), - "execution worker concluded, error occurred: {:?}", + "execution worker concluded, error occurred: {}", err ); } else { @@ -379,6 +423,12 @@ fn handle_job_finish( ); } + if let Some(sync_channel) = sync_channel { + // err means the sender is dropped (the artifact is already removed from the cache) + // so that's legitimate to ignore the result + let _ = sync_channel.await; + } + // First we send the result. It may fail due to the other end of the channel being dropped, // that's legitimate and we don't treat that as an error. let _ = result_tx.send(result); @@ -441,7 +491,7 @@ async fn spawn_worker_task( use futures_timer::Delay; loop { - match super::worker_intf::spawn( + match super::worker_interface::spawn( &program_path, &cache_path, job.executor_params.clone(), @@ -493,7 +543,7 @@ fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) { queue.mux.push( async move { let _timer = execution_timer; - let outcome = super::worker_intf::start_work( + let outcome = super::worker_interface::start_work( idle, job.artifact.clone(), job.exec_timeout, @@ -514,8 +564,10 @@ pub fn start( spawn_timeout: Duration, node_version: Option, security_status: SecurityStatus, -) -> (mpsc::Sender, impl Future) { +) -> (mpsc::Sender, mpsc::UnboundedReceiver, impl Future) { let (to_queue_tx, to_queue_rx) = mpsc::channel(20); + let (from_queue_tx, from_queue_rx) = mpsc::unbounded(); + let run = Queue::new( metrics, program_path, @@ -525,7 +577,8 @@ pub fn start( node_version, security_status, to_queue_rx, + from_queue_tx, ) .run(); - (to_queue_tx, run) + (to_queue_tx, from_queue_rx, run) } diff --git a/polkadot/node/core/pvf/src/execute/worker_intf.rs b/polkadot/node/core/pvf/src/execute/worker_interface.rs similarity index 72% rename from polkadot/node/core/pvf/src/execute/worker_intf.rs rename to polkadot/node/core/pvf/src/execute/worker_interface.rs index 783c7c7abbc8c7526819d85e3e06bd1ae6e9209c..db81da118d7b5563ea7b4bec1b6c76bd3bf842e2 100644 --- a/polkadot/node/core/pvf/src/execute/worker_intf.rs +++ b/polkadot/node/core/pvf/src/execute/worker_interface.rs @@ -18,7 +18,7 @@ use crate::{ artifacts::ArtifactPathId, - worker_intf::{ + worker_interface::{ clear_worker_dir_path, framed_recv, framed_send, spawn_with_program_path, IdleWorker, SpawnErr, WorkerDir, WorkerHandle, JOB_TIMEOUT_WALL_CLOCK_FACTOR, }, @@ -29,7 +29,7 @@ use futures_timer::Delay; use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ error::InternalValidationError, - execute::{Handshake, Response}, + execute::{Handshake, WorkerResponse}, worker_dir, SecurityStatus, }; use polkadot_parachain_primitives::primitives::ValidationResult; @@ -62,16 +62,16 @@ pub async fn spawn( security_status, ) .await?; - send_handshake(&mut idle_worker.stream, Handshake { executor_params }) + send_execute_handshake(&mut idle_worker.stream, Handshake { executor_params }) .await .map_err(|error| { + let err = SpawnErr::Handshake { err: error.to_string() }; gum::warn!( target: LOG_TARGET, worker_pid = %idle_worker.pid, - ?error, - "failed to send a handshake to the spawned worker", + %err ); - SpawnErr::Handshake + err })?; Ok((idle_worker, worker_handle)) } @@ -87,26 +87,37 @@ pub enum Outcome { /// a trap. Errors related to the preparation process are not expected to be encountered by the /// execution workers. InvalidCandidate { err: String, idle_worker: IdleWorker }, + /// The error is probably transient. It may be for example + /// because the artifact was prepared with a Wasmtime version different from the version + /// in the current execution environment. + RuntimeConstruction { err: String, idle_worker: IdleWorker }, + /// The execution time exceeded the hard limit. The worker is terminated. + HardTimeout, + /// An I/O error happened during communication with the worker. This may mean that the worker + /// process already died. The token is not returned in any case. + WorkerIntfErr, + /// The job process has died. We must kill the worker just in case. + /// + /// We cannot treat this as an internal error because malicious code may have caused this. + JobDied { err: String }, + /// An unexpected error occurred in the job process. + /// + /// Because malicious code can cause a job error, we must not treat it as an internal error. + JobError { err: String }, + /// An internal error happened during the validation. Such an error is most likely related to /// some transient glitch. /// /// Should only ever be used for errors independent of the candidate and PVF. Therefore it may /// be a problem with the worker, so we terminate it. InternalError { err: InternalValidationError }, - /// The execution time exceeded the hard limit. The worker is terminated. - HardTimeout, - /// An I/O error happened during communication with the worker. This may mean that the worker - /// process already died. The token is not returned in any case. - IoErr, - /// An unexpected panic has occurred in the execution worker. - Panic { err: String }, } /// Given the idle token of a worker and parameters of work, communicates with the worker and /// returns the outcome. /// /// NOTE: Not returning the idle worker token in `Outcome` will trigger the child process being -/// killed. +/// killed, if it's still alive. pub async fn start_work( worker: IdleWorker, artifact: ArtifactPathId, @@ -133,7 +144,7 @@ pub async fn start_work( ?error, "failed to send an execute request", ); - return Outcome::IoErr + return Outcome::WorkerIntfErr } // We use a generous timeout here. This is in addition to the one in the child process, in @@ -145,6 +156,13 @@ pub async fn start_work( let response = futures::select! { response = recv_response(&mut stream).fuse() => { match response { + Ok(response) => + handle_response( + response, + pid, + execution_timeout, + ) + .await, Err(error) => { gum::warn!( target: LOG_TARGET, @@ -153,26 +171,8 @@ pub async fn start_work( ?error, "failed to recv an execute response", ); - return Outcome::IoErr - }, - Ok(response) => { - if let Response::Ok{duration, ..} = response { - if duration > execution_timeout { - // The job didn't complete within the timeout. - gum::warn!( - target: LOG_TARGET, - worker_pid = %pid, - "execute job took {}ms cpu time, exceeded execution timeout {}ms.", - duration.as_millis(), - execution_timeout.as_millis(), - ); - - // Return a timeout error. - return Outcome::HardTimeout; - } - } - response + return Outcome::WorkerIntfErr }, } }, @@ -183,28 +183,62 @@ pub async fn start_work( validation_code_hash = ?artifact.id.code_hash, "execution worker exceeded lenient timeout for execution, child worker likely stalled", ); - Response::TimedOut + WorkerResponse::JobTimedOut }, }; match response { - Response::Ok { result_descriptor, duration } => Outcome::Ok { + WorkerResponse::Ok { result_descriptor, duration } => Outcome::Ok { result_descriptor, duration, idle_worker: IdleWorker { stream, pid, worker_dir }, }, - Response::InvalidCandidate(err) => Outcome::InvalidCandidate { + WorkerResponse::InvalidCandidate(err) => Outcome::InvalidCandidate { err, idle_worker: IdleWorker { stream, pid, worker_dir }, }, - Response::TimedOut => Outcome::HardTimeout, - Response::Panic(err) => Outcome::Panic { err }, - Response::InternalError(err) => Outcome::InternalError { err }, + WorkerResponse::RuntimeConstruction(err) => Outcome::RuntimeConstruction { + err, + idle_worker: IdleWorker { stream, pid, worker_dir }, + }, + WorkerResponse::JobTimedOut => Outcome::HardTimeout, + WorkerResponse::JobDied { err, job_pid: _ } => Outcome::JobDied { err }, + WorkerResponse::JobError(err) => Outcome::JobError { err }, + + WorkerResponse::InternalError(err) => Outcome::InternalError { err }, } }) .await } +/// Handles the case where we successfully received response bytes on the host from the child. +/// +/// Here we know the artifact exists, but is still located in a temporary file which will be cleared +/// by [`with_worker_dir_setup`]. +async fn handle_response( + response: WorkerResponse, + worker_pid: u32, + execution_timeout: Duration, +) -> WorkerResponse { + if let WorkerResponse::Ok { duration, .. } = response { + if duration > execution_timeout { + // The job didn't complete within the timeout. + gum::warn!( + target: LOG_TARGET, + worker_pid, + "execute job took {}ms cpu time, exceeded execution timeout {}ms.", + duration.as_millis(), + execution_timeout.as_millis(), + ); + + // Return a timeout error. + return WorkerResponse::JobTimedOut + } + } + + response +} + /// Create a temporary file for an artifact in the worker cache, execute the given future/closure /// passing the file path in, and clean up the worker cache. /// @@ -223,7 +257,7 @@ where // Cheaply create a hard link to the artifact. The artifact is always at a known location in the // worker cache, and the child can't access any other artifacts or gain any information from the // original filename. - let link_path = worker_dir::execute_artifact(&worker_dir.path); + let link_path = worker_dir::execute_artifact(worker_dir.path()); if let Err(err) = tokio::fs::hard_link(artifact_path, link_path).await { gum::warn!( target: LOG_TARGET, @@ -237,7 +271,7 @@ where } } - let worker_dir_path = worker_dir.path.clone(); + let worker_dir_path = worker_dir.path().to_owned(); let outcome = f(worker_dir).await; // Try to clear the worker dir. @@ -260,7 +294,8 @@ where outcome } -async fn send_handshake(stream: &mut UnixStream, handshake: Handshake) -> io::Result<()> { +/// Sends a handshake with information specific to the execute worker. +async fn send_execute_handshake(stream: &mut UnixStream, handshake: Handshake) -> io::Result<()> { framed_send(stream, &handshake.encode()).await } @@ -273,9 +308,9 @@ async fn send_request( framed_send(stream, &execution_timeout.encode()).await } -async fn recv_response(stream: &mut UnixStream) -> io::Result { +async fn recv_response(stream: &mut UnixStream) -> io::Result { let response_bytes = framed_recv(stream).await?; - Response::decode(&mut &response_bytes[..]).map_err(|e| { + WorkerResponse::decode(&mut response_bytes.as_slice()).map_err(|e| { io::Error::new( io::ErrorKind::Other, format!("execute pvf recv_response: decode error: {:?}", e), diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index 81695829122b9d3ba03307f1c597ac5877dc7023..8ec46f4b08f193082f1fcf8f6a08cae2ef261765 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -24,7 +24,7 @@ use crate::{ artifacts::{ArtifactId, ArtifactPathId, ArtifactState, Artifacts}, execute::{self, PendingExecutionRequest}, metrics::Metrics, - prepare, Priority, ValidationError, LOG_TARGET, + prepare, Priority, SecurityStatus, ValidationError, LOG_TARGET, }; use always_assert::never; use futures::{ @@ -32,14 +32,15 @@ use futures::{ Future, FutureExt, SinkExt, StreamExt, }; use polkadot_node_core_pvf_common::{ - error::{PrepareError, PrepareResult}, + error::{PrecheckResult, PrepareError}, + prepare::PrepareSuccess, pvf::PvfPrepData, - SecurityStatus, }; +use polkadot_node_subsystem::{SubsystemError, SubsystemResult}; use polkadot_parachain_primitives::primitives::ValidationResult; use std::{ collections::HashMap, - path::{Path, PathBuf}, + path::PathBuf, time::{Duration, SystemTime}, }; @@ -59,16 +60,21 @@ pub const PREPARE_BINARY_NAME: &str = "polkadot-prepare-worker"; /// The name of binary spawned to execute a PVF pub const EXECUTE_BINARY_NAME: &str = "polkadot-execute-worker"; +/// The size of incoming message queue +pub const HOST_MESSAGE_QUEUE_SIZE: usize = 10; + /// An alias to not spell the type for the oneshot sender for the PVF execution result. pub(crate) type ResultSender = oneshot::Sender>; /// Transmission end used for sending the PVF preparation result. -pub(crate) type PrepareResultSender = oneshot::Sender; +pub(crate) type PrecheckResultSender = oneshot::Sender; /// A handle to the async process serving the validation host requests. #[derive(Clone)] pub struct ValidationHost { to_host_tx: mpsc::Sender, + /// Available security features, detected by the host during startup. + pub security_status: SecurityStatus, } impl ValidationHost { @@ -83,7 +89,7 @@ impl ValidationHost { pub async fn precheck_pvf( &mut self, pvf: PvfPrepData, - result_tx: PrepareResultSender, + result_tx: PrecheckResultSender, ) -> Result<(), String> { self.to_host_tx .send(ToHost::PrecheckPvf { pvf, result_tx }) @@ -133,7 +139,7 @@ impl ValidationHost { } enum ToHost { - PrecheckPvf { pvf: PvfPrepData, result_tx: PrepareResultSender }, + PrecheckPvf { pvf: PvfPrepData, result_tx: PrecheckResultSender }, ExecutePvf(ExecutePvfInputs), HeadsUp { active_pvfs: Vec }, } @@ -153,6 +159,9 @@ pub struct Config { pub cache_path: PathBuf, /// The version of the node. `None` can be passed to skip the version check (only for tests). pub node_version: Option, + /// Whether the node is attempting to run as a secure validator. + pub secure_validator_mode: bool, + /// The path to the program that can be used to spawn the prepare workers. pub prepare_worker_program_path: PathBuf, /// The time allotted for a prepare worker to spawn and report to the host. @@ -162,6 +171,7 @@ pub struct Config { pub prepare_workers_soft_max_num: usize, /// The absolute number of workers that can be spawned in the prepare pool. pub prepare_workers_hard_max_num: usize, + /// The path to the program that can be used to spawn the execute workers. pub execute_worker_program_path: PathBuf, /// The time allotted for an execute worker to spawn and report to the host. @@ -175,16 +185,20 @@ impl Config { pub fn new( cache_path: PathBuf, node_version: Option, + secure_validator_mode: bool, prepare_worker_program_path: PathBuf, execute_worker_program_path: PathBuf, ) -> Self { Self { cache_path, node_version, + secure_validator_mode, + prepare_worker_program_path, prepare_worker_spawn_timeout: Duration::from_secs(3), prepare_workers_soft_max_num: 1, prepare_workers_hard_max_num: 1, + execute_worker_program_path, execute_worker_spawn_timeout: Duration::from_secs(3), execute_workers_max_num: 2, @@ -200,20 +214,47 @@ impl Config { /// The future should not return normally but if it does then that indicates an unrecoverable error. /// In that case all pending requests will be canceled, dropping the result senders and new ones /// will be rejected. -pub fn start(config: Config, metrics: Metrics) -> (ValidationHost, impl Future) { +pub async fn start( + config: Config, + metrics: Metrics, +) -> SubsystemResult<(ValidationHost, impl Future)> { gum::debug!(target: LOG_TARGET, ?config, "starting PVF validation host"); - // Run checks for supported security features once per host startup. Warn here if not enabled. - let security_status = { - let can_enable_landlock = check_landlock(&config.prepare_worker_program_path); - let can_unshare_user_namespace_and_change_root = - check_can_unshare_user_namespace_and_change_root(&config.prepare_worker_program_path); - SecurityStatus { can_enable_landlock, can_unshare_user_namespace_and_change_root } + // Make sure the cache is initialized before doing anything else. + let artifacts = Artifacts::new(&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. + #[cfg(target_os = "linux")] + let security_status = match crate::security::check_security_status(&config).await { + Ok(ok) => ok, + Err(err) => return Err(SubsystemError::Context(err)), + }; + #[cfg(not(target_os = "linux"))] + let security_status = if config.secure_validator_mode { + gum::error!( + target: LOG_TARGET, + "{}{}{}", + crate::SECURE_MODE_ERROR, + crate::SECURE_LINUX_NOTE, + crate::IGNORE_SECURE_MODE_TIP + ); + return Err(SubsystemError::Context( + "could not enable Secure Validator Mode for non-Linux; check logs".into(), + )); + } else { + gum::warn!( + target: LOG_TARGET, + "{}{}", + crate::SECURE_MODE_WARNING, + crate::SECURE_LINUX_NOTE, + ); + SecurityStatus::default() }; - let (to_host_tx, to_host_rx) = mpsc::channel(10); + let (to_host_tx, to_host_rx) = mpsc::channel(HOST_MESSAGE_QUEUE_SIZE); - let validation_host = ValidationHost { to_host_tx }; + let validation_host = ValidationHost { to_host_tx, security_status: security_status.clone() }; let (to_prepare_pool, from_prepare_pool, run_prepare_pool) = prepare::start_pool( metrics.clone(), @@ -233,7 +274,7 @@ pub fn start(config: Config, metrics: Metrics) -> (ValidationHost, impl Future (ValidationHost, impl Future (ValidationHost, impl Future (ValidationHost, impl Future, to_execute_queue_tx: mpsc::Sender, + from_execute_queue_rx: mpsc::UnboundedReceiver, + to_sweeper_tx: mpsc::Sender, awaiting_prepare: AwaitingPrepare, @@ -315,13 +355,13 @@ struct Fatal; async fn run( Inner { - cache_path, cleanup_pulse_interval, artifact_ttl, mut artifacts, to_host_rx, from_prepare_queue_rx, mut to_prepare_queue_tx, + from_execute_queue_rx, mut to_execute_queue_tx, mut to_sweeper_tx, mut awaiting_prepare, @@ -348,10 +388,21 @@ async fn run( let mut to_host_rx = to_host_rx.fuse(); let mut from_prepare_queue_rx = from_prepare_queue_rx.fuse(); + let mut from_execute_queue_rx = from_execute_queue_rx.fuse(); loop { // biased to make it behave deterministically for tests. futures::select_biased! { + from_execute_queue_rx = from_execute_queue_rx.next() => { + let from_queue = break_if_fatal!(from_execute_queue_rx.ok_or(Fatal)); + let execute::FromQueue::RemoveArtifact { artifact, reply_to } = from_queue; + break_if_fatal!(handle_artifact_removal( + &mut to_sweeper_tx, + &mut artifacts, + artifact, + reply_to, + ).await); + }, () = cleanup_pulse.select_next_some() => { // `select_next_some` because we don't expect this to fail, but if it does, we // still don't fail. The trade-off is that the compiled cache will start growing @@ -359,7 +410,6 @@ async fn run( // will notice it. break_if_fatal!(handle_cleanup_pulse( - &cache_path, &mut to_sweeper_tx, &mut artifacts, artifact_ttl, @@ -378,7 +428,6 @@ async fn run( // If the artifact failed before, it could be re-scheduled for preparation here if // the preparation failure cooldown has elapsed. break_if_fatal!(handle_to_host( - &cache_path, &mut artifacts, &mut to_prepare_queue_tx, &mut to_execute_queue_tx, @@ -400,7 +449,6 @@ async fn run( // We could be eager in terms of reporting and plumb the result from the preparation // worker but we don't for the sake of simplicity. break_if_fatal!(handle_prepare_done( - &cache_path, &mut artifacts, &mut to_execute_queue_tx, &mut awaiting_prepare, @@ -412,7 +460,6 @@ async fn run( } async fn handle_to_host( - cache_path: &Path, artifacts: &mut Artifacts, prepare_queue: &mut mpsc::Sender, execute_queue: &mut mpsc::Sender, @@ -424,15 +471,8 @@ async fn handle_to_host( handle_precheck_pvf(artifacts, prepare_queue, pvf, result_tx).await?; }, ToHost::ExecutePvf(inputs) => { - handle_execute_pvf( - cache_path, - artifacts, - prepare_queue, - execute_queue, - awaiting_prepare, - inputs, - ) - .await?; + handle_execute_pvf(artifacts, prepare_queue, execute_queue, awaiting_prepare, inputs) + .await?; }, ToHost::HeadsUp { active_pvfs } => handle_heads_up(artifacts, prepare_queue, active_pvfs).await?, @@ -446,27 +486,27 @@ async fn handle_to_host( /// This tries to prepare the PVF by compiling the WASM blob within a timeout set in /// `PvfPrepData`. /// -/// If the prepare job failed previously, we may retry it under certain conditions. +/// We don't retry artifacts that previously failed preparation. We don't expect multiple +/// pre-checking requests. async fn handle_precheck_pvf( artifacts: &mut Artifacts, prepare_queue: &mut mpsc::Sender, pvf: PvfPrepData, - result_sender: PrepareResultSender, + result_sender: PrecheckResultSender, ) -> Result<(), Fatal> { let artifact_id = ArtifactId::from_pvf_prep_data(&pvf); if let Some(state) = artifacts.artifact_state_mut(&artifact_id) { match state { - ArtifactState::Prepared { last_time_needed, prepare_stats } => { + ArtifactState::Prepared { last_time_needed, .. } => { *last_time_needed = SystemTime::now(); - let _ = result_sender.send(Ok(prepare_stats.clone())); + let _ = result_sender.send(Ok(())); }, ArtifactState::Preparing { waiting_for_response, num_failures: _ } => waiting_for_response.push(result_sender), ArtifactState::FailedToProcess { error, .. } => { - // Do not retry failed preparation if another pre-check request comes in. We do not - // retry pre-checking, anyway. - let _ = result_sender.send(PrepareResult::Err(error.clone())); + // Do not retry an artifact that previously failed preparation. + let _ = result_sender.send(PrecheckResult::Err(error.clone())); }, } } else { @@ -486,10 +526,10 @@ async fn handle_precheck_pvf( /// /// If the prepare job failed previously, we may retry it under certain conditions. /// -/// When preparing for execution, we use a more lenient timeout ([`LENIENT_PREPARATION_TIMEOUT`]) +/// When preparing for execution, we use a more lenient timeout +/// ([`DEFAULT_LENIENT_PREPARATION_TIMEOUT`](polkadot_primitives::executor_params::DEFAULT_LENIENT_PREPARATION_TIMEOUT)) /// than when prechecking. async fn handle_execute_pvf( - cache_path: &Path, artifacts: &mut Artifacts, prepare_queue: &mut mpsc::Sender, execute_queue: &mut mpsc::Sender, @@ -502,8 +542,8 @@ async fn handle_execute_pvf( if let Some(state) = artifacts.artifact_state_mut(&artifact_id) { match state { - ArtifactState::Prepared { last_time_needed, .. } => { - let file_metadata = std::fs::metadata(artifact_id.path(cache_path)); + ArtifactState::Prepared { ref path, last_time_needed, .. } => { + let file_metadata = std::fs::metadata(path); if file_metadata.is_ok() { *last_time_needed = SystemTime::now(); @@ -512,7 +552,7 @@ async fn handle_execute_pvf( send_execute( execute_queue, execute::ToQueue::Enqueue { - artifact: ArtifactPathId::new(artifact_id, cache_path), + artifact: ArtifactPathId::new(artifact_id, path), pending_execution_request: PendingExecutionRequest { exec_timeout, params, @@ -675,7 +715,6 @@ async fn handle_heads_up( } async fn handle_prepare_done( - cache_path: &Path, artifacts: &mut Artifacts, execute_queue: &mut mpsc::Sender, awaiting_prepare: &mut AwaitingPrepare, @@ -716,7 +755,8 @@ async fn handle_prepare_done( state { for result_sender in waiting_for_response.drain(..) { - let _ = result_sender.send(result.clone()); + let result = result.clone().map(|_| ()); + let _ = result_sender.send(result); } num_failures } else { @@ -736,16 +776,18 @@ async fn handle_prepare_done( continue } - // Don't send failed artifacts to the execution's queue. - if let Err(ref error) = result { - let _ = result_tx.send(Err(ValidationError::from(error.clone()))); - continue - } + let path = match &result { + Ok(success) => success.path.clone(), + Err(error) => { + let _ = result_tx.send(Err(ValidationError::from(error.clone()))); + continue + }, + }; send_execute( execute_queue, execute::ToQueue::Enqueue { - artifact: ArtifactPathId::new(artifact_id.clone(), cache_path), + artifact: ArtifactPathId::new(artifact_id.clone(), &path), pending_execution_request: PendingExecutionRequest { exec_timeout, params, @@ -758,13 +800,13 @@ async fn handle_prepare_done( } *state = match result { - Ok(prepare_stats) => - ArtifactState::Prepared { last_time_needed: SystemTime::now(), prepare_stats }, + Ok(PrepareSuccess { path, stats: prepare_stats }) => + ArtifactState::Prepared { path, last_time_needed: SystemTime::now(), prepare_stats }, Err(error) => { let last_time_failed = SystemTime::now(); let num_failures = *num_failures + 1; - gum::warn!( + gum::error!( target: LOG_TARGET, ?artifact_id, time_failed = ?last_time_failed, @@ -812,7 +854,6 @@ async fn enqueue_prepare_for_execute( } async fn handle_cleanup_pulse( - cache_path: &Path, sweeper_tx: &mut mpsc::Sender, artifacts: &mut Artifacts, artifact_ttl: Duration, @@ -823,19 +864,49 @@ async fn handle_cleanup_pulse( "PVF pruning: {} artifacts reached their end of life", to_remove.len(), ); - for artifact_id in to_remove { + for (artifact_id, path) in to_remove { gum::debug!( target: LOG_TARGET, validation_code_hash = ?artifact_id.code_hash, "pruning artifact", ); - let artifact_path = artifact_id.path(cache_path); - sweeper_tx.send(artifact_path).await.map_err(|_| Fatal)?; + sweeper_tx.send(path).await.map_err(|_| Fatal)?; } Ok(()) } +async fn handle_artifact_removal( + sweeper_tx: &mut mpsc::Sender, + artifacts: &mut Artifacts, + artifact_id: ArtifactId, + reply_to: oneshot::Sender<()>, +) -> Result<(), Fatal> { + let (artifact_id, path) = if let Some(artifact) = artifacts.remove(artifact_id) { + artifact + } else { + // if we haven't found the artifact by its id, + // it has been probably removed + // anyway with the randomness of the artifact name + // it is safe to ignore + return Ok(()); + }; + reply_to + .send(()) + .expect("the execute queue waits for the artifact remove confirmation; qed"); + // Thanks to the randomness of the artifact name (see + // `artifacts::generate_artifact_path`) there is no issue with any name conflict on + // future repreparation. + // So we can confirm the artifact removal already + gum::debug!( + target: LOG_TARGET, + validation_code_hash = ?artifact_id.code_hash, + "PVF pruning: pruning artifact by request from the execute queue", + ); + sweeper_tx.send(path).await.map_err(|_| Fatal)?; + Ok(()) +} + /// A simple task which sole purpose is to delete files thrown at it. async fn sweeper_task(mut sweeper_rx: mpsc::Receiver) { loop { @@ -846,7 +917,7 @@ async fn sweeper_task(mut sweeper_rx: mpsc::Receiver) { gum::trace!( target: LOG_TARGET, ?result, - "Sweeping the artifact file {}", + "Sweeped the artifact file {}", condemned.display(), ); }, @@ -882,112 +953,16 @@ fn pulse_every(interval: std::time::Duration) -> impl futures::Stream .map(|_| ()) } -/// Check if we can sandbox the root and emit a warning if not. -/// -/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible -/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on -/// success and -1 on failure. -fn check_can_unshare_user_namespace_and_change_root( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> bool { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let output = std::process::Command::new(prepare_worker_program_path) - .arg("--check-can-unshare-user-namespace-and-change-root") - .output(); - - match output { - Ok(output) if output.status.success() => true, - Ok(output) => { - let stderr = std::str::from_utf8(&output.stderr) - .expect("child process writes a UTF-8 string to stderr; qed") - .trim(); - gum::warn!( - target: LOG_TARGET, - ?prepare_worker_program_path, - // Docs say to always print status using `Display` implementation. - status = %output.status, - %stderr, - "Cannot unshare user namespace and change root, which are Linux-specific kernel security features. Running validation of malicious PVF code has a higher risk of compromising this machine. Consider running with support for unsharing user namespaces for maximum security." - ); - false - }, - Err(err) => { - gum::warn!( - target: LOG_TARGET, - ?prepare_worker_program_path, - "Could not start child process: {}", - err - ); - false - }, - } - } else { - gum::warn!( - target: LOG_TARGET, - "Cannot unshare user namespace and change root, which are Linux-specific kernel security features. Running validation of malicious PVF code has a higher risk of compromising this machine. Consider running on Linux with support for unsharing user namespaces for maximum security." - ); - false - } - } -} - -/// Check if landlock is supported and emit a warning if not. -/// -/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible -/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on -/// success and -1 on failure. -fn check_landlock( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> bool { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - match std::process::Command::new(prepare_worker_program_path) - .arg("--check-can-enable-landlock") - .status() - { - Ok(status) if status.success() => true, - Ok(status) => { - let abi = - polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; - gum::warn!( - target: LOG_TARGET, - ?prepare_worker_program_path, - ?status, - %abi, - "Cannot fully enable landlock, a Linux-specific kernel security feature. Running validation of malicious PVF code has a higher risk of compromising this machine. Consider upgrading the kernel version for maximum security." - ); - false - }, - Err(err) => { - gum::warn!( - target: LOG_TARGET, - ?prepare_worker_program_path, - "Could not start child process: {}", - err - ); - false - }, - } - } else { - gum::warn!( - target: LOG_TARGET, - "Cannot enable landlock, a Linux-specific kernel security feature. Running validation of malicious PVF code has a higher risk of compromising this machine. Consider running on Linux with landlock support for maximum security." - ); - false - } - } -} - #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::InvalidCandidate; + use crate::{artifacts::generate_artifact_path, PossiblyInvalidError}; use assert_matches::assert_matches; use futures::future::BoxFuture; - use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats}; + use polkadot_node_core_pvf_common::{ + error::PrepareError, + prepare::{PrepareStats, PrepareSuccess}, + }; const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(3); pub(crate) const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(30); @@ -1002,17 +977,13 @@ pub(crate) mod tests { let _ = pulse.next().await.unwrap(); let el = start.elapsed().as_millis(); - assert!(el > 50 && el < 150, "{}", el); + assert!(el > 50 && el < 150, "pulse duration: {}", el); } } /// Creates a new PVF which artifact id can be uniquely identified by the given number. - fn artifact_id(descriminator: u32) -> ArtifactId { - ArtifactId::from_pvf_prep_data(&PvfPrepData::from_discriminator(descriminator)) - } - - fn artifact_path(descriminator: u32) -> PathBuf { - artifact_id(descriminator).path(&PathBuf::from(std::env::temp_dir())).to_owned() + fn artifact_id(discriminator: u32) -> ArtifactId { + ArtifactId::from_pvf_prep_data(&PvfPrepData::from_discriminator(discriminator)) } struct Builder { @@ -1043,6 +1014,8 @@ pub(crate) mod tests { to_prepare_queue_rx: mpsc::Receiver, from_prepare_queue_tx: mpsc::UnboundedSender, to_execute_queue_rx: mpsc::Receiver, + #[allow(unused)] + from_execute_queue_tx: mpsc::UnboundedSender, to_sweeper_rx: mpsc::Receiver, run: BoxFuture<'static, ()>, @@ -1050,16 +1023,14 @@ pub(crate) mod tests { impl Test { fn new(Builder { cleanup_pulse_interval, artifact_ttl, artifacts }: Builder) -> Self { - let cache_path = PathBuf::from(std::env::temp_dir()); - let (to_host_tx, to_host_rx) = mpsc::channel(10); let (to_prepare_queue_tx, to_prepare_queue_rx) = mpsc::channel(10); let (from_prepare_queue_tx, from_prepare_queue_rx) = mpsc::unbounded(); let (to_execute_queue_tx, to_execute_queue_rx) = mpsc::channel(10); + let (from_execute_queue_tx, from_execute_queue_rx) = mpsc::unbounded(); let (to_sweeper_tx, to_sweeper_rx) = mpsc::channel(10); let run = run(Inner { - cache_path, cleanup_pulse_interval, artifact_ttl, artifacts, @@ -1067,6 +1038,7 @@ pub(crate) mod tests { to_prepare_queue_tx, from_prepare_queue_rx, to_execute_queue_tx, + from_execute_queue_rx, to_sweeper_tx, awaiting_prepare: AwaitingPrepare::default(), }) @@ -1077,6 +1049,7 @@ pub(crate) mod tests { to_prepare_queue_rx, from_prepare_queue_tx, to_execute_queue_rx, + from_execute_queue_tx, to_sweeper_rx, run, } @@ -1084,7 +1057,8 @@ pub(crate) mod tests { fn host_handle(&mut self) -> ValidationHost { let to_host_tx = self.to_host_tx.take().unwrap(); - ValidationHost { to_host_tx } + let security_status = Default::default(); + ValidationHost { to_host_tx, security_status } } async fn poll_and_recv_result(&mut self, result_rx: oneshot::Receiver) -> T @@ -1204,16 +1178,26 @@ pub(crate) mod tests { #[tokio::test] async fn pruning() { let mock_now = SystemTime::now() - Duration::from_millis(1000); + let tempdir = tempfile::tempdir().unwrap(); + let cache_path = tempdir.path(); let mut builder = Builder::default(); builder.cleanup_pulse_interval = Duration::from_millis(100); builder.artifact_ttl = Duration::from_millis(500); - builder - .artifacts - .insert_prepared(artifact_id(1), mock_now, PrepareStats::default()); - builder - .artifacts - .insert_prepared(artifact_id(2), mock_now, PrepareStats::default()); + let path1 = generate_artifact_path(cache_path); + let path2 = generate_artifact_path(cache_path); + builder.artifacts.insert_prepared( + artifact_id(1), + path1.clone(), + mock_now, + PrepareStats::default(), + ); + builder.artifacts.insert_prepared( + artifact_id(2), + path2.clone(), + mock_now, + PrepareStats::default(), + ); let mut test = builder.build(); let mut host = test.host_handle(); @@ -1223,7 +1207,7 @@ pub(crate) mod tests { run_until( &mut test.run, async { - assert_eq!(to_sweeper_rx.next().await.unwrap(), artifact_path(2)); + assert_eq!(to_sweeper_rx.next().await.unwrap(), path2); } .boxed(), ) @@ -1285,7 +1269,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(1), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); @@ -1301,7 +1285,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(2), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); @@ -1311,27 +1295,27 @@ pub(crate) mod tests { ); result_tx_pvf_1_1 - .send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath))) + .send(Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath))) .unwrap(); assert_matches!( result_rx_pvf_1_1.now_or_never().unwrap().unwrap(), - Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath)) + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) ); result_tx_pvf_1_2 - .send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath))) + .send(Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath))) .unwrap(); assert_matches!( result_rx_pvf_1_2.now_or_never().unwrap().unwrap(), - Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath)) + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) ); result_tx_pvf_2 - .send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath))) + .send(Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath))) .unwrap(); assert_matches!( result_rx_pvf_2.now_or_never().unwrap().unwrap(), - Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath)) + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) ); } @@ -1355,7 +1339,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(1), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); @@ -1437,7 +1421,7 @@ pub(crate) mod tests { assert_matches!(result_rx.now_or_never().unwrap().unwrap(), Err(PrepareError::TimedOut)); assert_matches!( result_rx_execute.now_or_never().unwrap().unwrap(), - Err(ValidationError::InternalError(_)) + Err(ValidationError::Internal(_)) ); // Reversed case: first send multiple precheck requests, then ask for an execution. @@ -1468,7 +1452,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(2), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); @@ -1579,7 +1563,7 @@ pub(crate) mod tests { // The result should contain the error. let result = test.poll_and_recv_result(result_rx).await; - assert_matches!(result, Err(ValidationError::InternalError(_))); + assert_matches!(result, Err(ValidationError::Internal(_))); // Submit another execute request. We shouldn't try to prepare again, yet. let (result_tx_2, result_rx_2) = oneshot::channel(); @@ -1598,7 +1582,7 @@ pub(crate) mod tests { // The result should contain the original error. let result = test.poll_and_recv_result(result_rx_2).await; - assert_matches!(result, Err(ValidationError::InternalError(_))); + assert_matches!(result, Err(ValidationError::Internal(_))); // Pause for enough time to reset the cooldown for this failed prepare request. futures_timer::Delay::new(PREPARE_FAILURE_COOLDOWN).await; @@ -1624,7 +1608,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(1), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); @@ -1638,11 +1622,11 @@ pub(crate) mod tests { // Send an error for the execution here, just so we can check the result receiver is still // alive. result_tx_3 - .send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath))) + .send(Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath))) .unwrap(); assert_matches!( result_rx_3.now_or_never().unwrap().unwrap(), - Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbiguousWorkerDeath)) + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) ); } @@ -1681,10 +1665,7 @@ pub(crate) mod tests { // The result should contain the error. let result = test.poll_and_recv_result(result_rx).await; - assert_matches!( - result, - Err(ValidationError::InvalidCandidate(InvalidCandidate::PrepareError(_))) - ); + assert_matches!(result, Err(ValidationError::Preparation(_))); // Submit another execute request. let (result_tx_2, result_rx_2) = oneshot::channel(); @@ -1703,10 +1684,7 @@ pub(crate) mod tests { // The result should contain the original error. let result = test.poll_and_recv_result(result_rx_2).await; - assert_matches!( - result, - Err(ValidationError::InvalidCandidate(InvalidCandidate::PrepareError(_))) - ); + assert_matches!(result, Err(ValidationError::Preparation(_))); // Pause for enough time to reset the cooldown for this failed prepare request. futures_timer::Delay::new(PREPARE_FAILURE_COOLDOWN).await; @@ -1728,10 +1706,7 @@ pub(crate) mod tests { // The result should still contain the original error. let result = test.poll_and_recv_result(result_rx_3).await; - assert_matches!( - result, - Err(ValidationError::InvalidCandidate(InvalidCandidate::PrepareError(_))) - ); + assert_matches!(result, Err(ValidationError::Preparation(_))); } // Test that multiple heads-up requests trigger preparation retries if the first one failed. @@ -1800,7 +1775,7 @@ pub(crate) mod tests { test.from_prepare_queue_tx .send(prepare::FromQueue { artifact_id: artifact_id(1), - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }) .await .unwrap(); diff --git a/polkadot/node/core/pvf/src/lib.rs b/polkadot/node/core/pvf/src/lib.rs index e3c6da9c4c6ba80ee94658921e975610d3b2d58f..462498fa8f6b108c6057b378d2a0b07face31ec9 100644 --- a/polkadot/node/core/pvf/src/lib.rs +++ b/polkadot/node/core/pvf/src/lib.rs @@ -84,7 +84,7 @@ //! A pruning task will run at a fixed interval of time. This task will remove all artifacts that //! weren't used or received a heads up signal for a while. //! -//! ## Execution +//! ## Execution //! //! The execute workers will be fed by the requests from the execution queue, which is basically a //! combination of a path to the compiled artifact and the @@ -97,16 +97,21 @@ mod host; mod metrics; mod prepare; mod priority; -mod worker_intf; +#[cfg(target_os = "linux")] +mod security; +mod worker_interface; #[cfg(feature = "test-utils")] pub mod testing; -pub use error::{InvalidCandidate, ValidationError}; -pub use host::{start, Config, ValidationHost, EXECUTE_BINARY_NAME, PREPARE_BINARY_NAME}; +pub use error::{InvalidCandidate, PossiblyInvalidError, ValidationError}; +pub use host::{ + start, Config, ValidationHost, EXECUTE_BINARY_NAME, HOST_MESSAGE_QUEUE_SIZE, + PREPARE_BINARY_NAME, +}; pub use metrics::Metrics; pub use priority::Priority; -pub use worker_intf::{framed_recv, framed_send, JOB_TIMEOUT_WALL_CLOCK_FACTOR}; +pub use worker_interface::{framed_recv, framed_send, JOB_TIMEOUT_WALL_CLOCK_FACTOR}; // Re-export some common types. pub use polkadot_node_core_pvf_common::{ @@ -116,5 +121,37 @@ pub use polkadot_node_core_pvf_common::{ SecurityStatus, }; +use std::{path::Path, process::Command}; + /// The log target for this crate. pub const LOG_TARGET: &str = "parachain::pvf"; + +/// Utility to get the version of a worker, used for version checks. +/// +/// The worker's existence at the given path must be checked separately. +pub fn get_worker_version(worker_path: &Path) -> std::io::Result { + let worker_version = Command::new(worker_path).args(["--version"]).output()?.stdout; + Ok(std::str::from_utf8(&worker_version) + .expect("version is printed as a string; qed") + .trim() + .to_string()) +} + +// Trying to run securely and some mandatory errors occurred. +pub(crate) const SECURE_MODE_ERROR: &'static str = + "🚨 Your system cannot securely run a validator. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Some errors occurred when running insecurely, or some optional errors occurred when running +// securely. +pub(crate) const SECURE_MODE_WARNING: &'static str = "🚨 Some security issues have been detected. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Message to be printed only when running securely and mandatory errors occurred. +pub(crate) const IGNORE_SECURE_MODE_TIP: &'static str = +"\nYou can ignore this error with the `--insecure-validator-i-know-what-i-do` \ +command line argument if you understand and accept the risks of running insecurely. \ +With this flag, security features are enabled on a best-effort basis, but not mandatory. \ +\nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"; +// Only Linux supports security features +#[cfg(not(target_os = "linux"))] +pub(crate) const SECURE_LINUX_NOTE: &'static str = "\nSecure mode is enabled only for Linux \ +\nand a full secure mode is enabled only for Linux x86-64."; diff --git a/polkadot/node/core/pvf/src/metrics.rs b/polkadot/node/core/pvf/src/metrics.rs index 3d792793498b5ef69967a4337c4adc8425623fee..7fd876cf17405f4815173ab289d34f41d7e1e1cc 100644 --- a/polkadot/node/core/pvf/src/metrics.rs +++ b/polkadot/node/core/pvf/src/metrics.rs @@ -93,6 +93,10 @@ impl Metrics { metrics.preparation_max_resident.observe(max_resident_kb); metrics.preparation_max_allocated.observe(max_allocated_kb); } + + metrics + .preparation_peak_tracked_allocation + .observe((memory_stats.peak_tracked_alloc / 1024) as f64); } } } @@ -110,10 +114,14 @@ struct MetricsInner { execution_time: prometheus::Histogram, #[cfg(target_os = "linux")] preparation_max_rss: prometheus::Histogram, + // Max. allocated memory, tracked by Jemallocator, polling-based #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] preparation_max_allocated: prometheus::Histogram, + // Max. resident memory, tracked by Jemallocator, polling-based #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] preparation_max_resident: prometheus::Histogram, + // Peak allocation value, tracked by tracking-allocator + preparation_peak_tracked_allocation: prometheus::Histogram, } impl metrics::Metrics for Metrics { @@ -271,6 +279,18 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + preparation_peak_tracked_allocation: prometheus::register( + prometheus::Histogram::with_opts( + prometheus::HistogramOpts::new( + "polkadot_pvf_preparation_peak_tracked_allocation", + "peak allocation observed for preparation (in kilobytes)", + ).buckets( + prometheus::exponential_buckets(8192.0, 2.0, 10) + .expect("arguments are always valid; qed"), + ), + )?, + registry, + )?, }; Ok(Metrics(Some(inner))) } diff --git a/polkadot/node/core/pvf/src/prepare/mod.rs b/polkadot/node/core/pvf/src/prepare/mod.rs index 580f67f73fa0c126994395ce5a2129234fca51a3..eb88070c2bab253092c9f8a37637702546a6b686 100644 --- a/polkadot/node/core/pvf/src/prepare/mod.rs +++ b/polkadot/node/core/pvf/src/prepare/mod.rs @@ -24,7 +24,7 @@ mod pool; mod queue; -mod worker_intf; +mod worker_interface; pub use pool::start as start_pool; pub use queue::{start as start_queue, FromQueue, ToQueue}; diff --git a/polkadot/node/core/pvf/src/prepare/pool.rs b/polkadot/node/core/pvf/src/prepare/pool.rs index 7933b0319a6f9fcd9fe83cad9f47950827a6ecc9..4e11f977c9e7d82527111cc9b751e15d1dc6c7a8 100644 --- a/polkadot/node/core/pvf/src/prepare/pool.rs +++ b/polkadot/node/core/pvf/src/prepare/pool.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::worker_intf::{self, Outcome}; +use super::worker_interface::{self, Outcome}; use crate::{ metrics::Metrics, - worker_intf::{IdleWorker, WorkerHandle}, + worker_interface::{IdleWorker, WorkerHandle}, LOG_TARGET, }; use always_assert::never; @@ -68,7 +68,7 @@ pub enum ToPool { /// /// In either case, the worker is considered busy and no further `StartWork` messages should be /// sent until either `Concluded` or `Rip` message is received. - StartWork { worker: Worker, pvf: PvfPrepData, artifact_path: PathBuf }, + StartWork { worker: Worker, pvf: PvfPrepData, cache_path: PathBuf }, } /// A message sent from pool to its client. @@ -232,7 +232,7 @@ fn handle_to_pool( .boxed(), ); }, - ToPool::StartWork { worker, pvf, artifact_path } => { + ToPool::StartWork { worker, pvf, cache_path } => { if let Some(data) = spawned.get_mut(worker) { if let Some(idle) = data.idle.take() { let preparation_timer = metrics.time_preparation(); @@ -242,7 +242,7 @@ fn handle_to_pool( worker, idle, pvf, - artifact_path, + cache_path, preparation_timer, ) .boxed(), @@ -278,7 +278,7 @@ async fn spawn_worker_task( use futures_timer::Delay; loop { - match worker_intf::spawn( + match worker_interface::spawn( &program_path, &cache_path, spawn_timeout, @@ -303,10 +303,10 @@ async fn start_work_task( worker: Worker, idle: IdleWorker, pvf: PvfPrepData, - artifact_path: PathBuf, + cache_path: PathBuf, _preparation_timer: Option, ) -> PoolEvent { - let outcome = worker_intf::start_work(&metrics, idle, pvf, artifact_path).await; + let outcome = worker_interface::start_work(&metrics, idle, pvf, cache_path).await; PoolEvent::StartWork(worker, outcome) } @@ -339,17 +339,17 @@ fn handle_mux( spawned, worker, idle, - Err(PrepareError::CreateTmpFileErr(err)), + Err(PrepareError::CreateTmpFile(err)), ), // Return `Concluded`, but do not kill the worker since the error was on the host // side. - Outcome::RenameTmpFileErr { worker: idle, result: _, err, src, dest } => + Outcome::RenameTmpFile { worker: idle, result: _, err, src, dest } => handle_concluded_no_rip( from_pool, spawned, worker, idle, - Err(PrepareError::RenameTmpFileErr { err, src, dest }), + Err(PrepareError::RenameTmpFile { err, src, dest }), ), // Could not clear worker cache. Kill the worker so other jobs can't see the data. Outcome::ClearWorkerDir { err } => { @@ -387,6 +387,21 @@ fn handle_mux( Ok(()) }, + // The worker might still be usable, but we kill it just in case. + Outcome::JobDied { err, job_pid } => { + if attempt_retire(metrics, spawned, worker) { + reply( + from_pool, + FromPool::Concluded { + worker, + rip: true, + result: Err(PrepareError::JobDied { err, job_pid }), + }, + )?; + } + + Ok(()) + }, Outcome::TimedOut => { if attempt_retire(metrics, spawned, worker) { reply( @@ -399,6 +414,20 @@ fn handle_mux( )?; } + Ok(()) + }, + Outcome::OutOfMemory => { + if attempt_retire(metrics, spawned, worker) { + reply( + from_pool, + FromPool::Concluded { + worker, + rip: true, + result: Err(PrepareError::OutOfMemory), + }, + )?; + } + Ok(()) }, } diff --git a/polkadot/node/core/pvf/src/prepare/queue.rs b/polkadot/node/core/pvf/src/prepare/queue.rs index c38012d745482e66fa4d74647e34663447b8f0d5..c7bfa2f3b21ba94084edd3cb397abd5e67213d08 100644 --- a/polkadot/node/core/pvf/src/prepare/queue.rs +++ b/polkadot/node/core/pvf/src/prepare/queue.rs @@ -45,8 +45,8 @@ pub struct FromQueue { /// Identifier of an artifact. pub(crate) artifact_id: ArtifactId, /// Outcome of the PVF processing. [`Ok`] indicates that compiled artifact - /// is successfully stored on disk. Otherwise, an [error](crate::error::PrepareError) - /// is supplied. + /// is successfully stored on disk. Otherwise, an + /// [error](polkadot_node_core_pvf_common::error::PrepareError) is supplied. pub(crate) result: PrepareResult, } @@ -268,12 +268,12 @@ fn find_idle_worker(queue: &mut Queue) -> Option { } async fn handle_from_pool(queue: &mut Queue, from_pool: pool::FromPool) -> Result<(), Fatal> { - use pool::FromPool::*; + use pool::FromPool; match from_pool { - Spawned(worker) => handle_worker_spawned(queue, worker).await?, - Concluded { worker, rip, result } => + FromPool::Spawned(worker) => handle_worker_spawned(queue, worker).await?, + FromPool::Concluded { worker, rip, result } => handle_worker_concluded(queue, worker, rip, result).await?, - Rip(worker) => handle_worker_rip(queue, worker).await?, + FromPool::Rip(worker) => handle_worker_rip(queue, worker).await?, } Ok(()) } @@ -424,17 +424,17 @@ async fn spawn_extra_worker(queue: &mut Queue, critical: bool) -> Result<(), Fat /// Attaches the work to the given worker telling the poll about the job. async fn assign(queue: &mut Queue, worker: Worker, job: Job) -> Result<(), Fatal> { let job_data = &mut queue.jobs[job]; - - let artifact_id = ArtifactId::from_pvf_prep_data(&job_data.pvf); - let artifact_path = artifact_id.path(&queue.cache_path); - job_data.worker = Some(worker); queue.workers[worker].job = Some(job); send_pool( &mut queue.to_pool_tx, - pool::ToPool::StartWork { worker, pvf: job_data.pvf.clone(), artifact_path }, + pool::ToPool::StartWork { + worker, + pvf: job_data.pvf.clone(), + cache_path: queue.cache_path.clone(), + }, ) .await?; @@ -491,7 +491,7 @@ mod tests { use crate::host::tests::TEST_PREPARATION_TIMEOUT; use assert_matches::assert_matches; use futures::{future::BoxFuture, FutureExt}; - use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats}; + use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareSuccess}; use slotmap::SlotMap; use std::task::Poll; @@ -612,7 +612,7 @@ mod tests { test.send_from_pool(pool::FromPool::Concluded { worker: w, rip: false, - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }); assert_eq!( @@ -651,7 +651,7 @@ mod tests { test.send_from_pool(pool::FromPool::Concluded { worker: w1, rip: false, - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }); assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. }); @@ -697,7 +697,7 @@ mod tests { test.send_from_pool(pool::FromPool::Concluded { worker: w1, rip: false, - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }); assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Kill(w1)); } @@ -731,7 +731,7 @@ mod tests { test.send_from_pool(pool::FromPool::Concluded { worker: w1, rip: true, - result: Ok(PrepareStats::default()), + result: Ok(PrepareSuccess::default()), }); // Since there is still work, the queue requested one extra worker to spawn to handle the diff --git a/polkadot/node/core/pvf/src/prepare/worker_intf.rs b/polkadot/node/core/pvf/src/prepare/worker_interface.rs similarity index 80% rename from polkadot/node/core/pvf/src/prepare/worker_intf.rs rename to polkadot/node/core/pvf/src/prepare/worker_interface.rs index b66c3604434363cdffefaaddf9091397c25b49a0..d64ee1510cad79a4ed1df808668d203b5817121b 100644 --- a/polkadot/node/core/pvf/src/prepare/worker_intf.rs +++ b/polkadot/node/core/pvf/src/prepare/worker_interface.rs @@ -17,8 +17,9 @@ //! Host interface to the prepare worker. use crate::{ + artifacts::generate_artifact_path, metrics::Metrics, - worker_intf::{ + worker_interface::{ clear_worker_dir_path, framed_recv, framed_send, spawn_with_program_path, IdleWorker, SpawnErr, WorkerDir, WorkerHandle, JOB_TIMEOUT_WALL_CLOCK_FACTOR, }, @@ -26,8 +27,8 @@ use crate::{ }; use parity_scale_codec::{Decode, Encode}; use polkadot_node_core_pvf_common::{ - error::{PrepareError, PrepareResult}, - prepare::PrepareStats, + error::{PrepareError, PrepareResult, PrepareWorkerResult}, + prepare::{PrepareStats, PrepareSuccess, PrepareWorkerSuccess}, pvf::PvfPrepData, worker_dir, SecurityStatus, }; @@ -78,9 +79,9 @@ pub enum Outcome { CreateTmpFileErr { worker: IdleWorker, err: String }, /// The response from the worker is received, but the tmp file cannot be renamed (moved) to the /// final destination location. - RenameTmpFileErr { + RenameTmpFile { worker: IdleWorker, - result: PrepareResult, + result: PrepareWorkerResult, err: String, // Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible // conversion to `Option`. @@ -97,6 +98,12 @@ pub enum Outcome { /// /// This doesn't return an idle worker instance, thus this worker is no longer usable. IoErr(String), + /// The worker ran out of memory and is aborting. The worker should be ripped. + OutOfMemory, + /// The preparation job process died, due to OOM, a seccomp violation, or some other factor. + /// + /// The worker might still be usable, but we kill it just in case. + JobDied { err: String, job_pid: i32 }, } /// Given the idle token of a worker and parameters of work, communicates with the worker and @@ -108,7 +115,7 @@ pub async fn start_work( metrics: &Metrics, worker: IdleWorker, pvf: PvfPrepData, - artifact_path: PathBuf, + cache_path: PathBuf, ) -> Outcome { let IdleWorker { stream, pid, worker_dir } = worker; @@ -116,8 +123,8 @@ pub async fn start_work( target: LOG_TARGET, worker_pid = %pid, ?worker_dir, - "starting prepare for {}", - artifact_path.display(), + "starting prepare for {:?}", + pvf, ); with_worker_dir_setup( @@ -126,7 +133,8 @@ pub async fn start_work( pid, |tmp_artifact_file, mut stream, worker_dir| async move { let preparation_timeout = pvf.prep_timeout(); - if let Err(err) = send_request(&mut stream, pvf).await { + + if let Err(err) = send_request(&mut stream, &pvf).await { gum::warn!( target: LOG_TARGET, worker_pid = %pid, @@ -150,14 +158,14 @@ pub async fn start_work( match result { // Received bytes from worker within the time limit. - Ok(Ok(prepare_result)) => + Ok(Ok(prepare_worker_result)) => handle_response( metrics, IdleWorker { stream, pid, worker_dir }, - prepare_result, + prepare_worker_result, pid, tmp_artifact_file, - artifact_path, + &cache_path, preparation_timeout, ) .await, @@ -166,7 +174,7 @@ pub async fn start_work( gum::warn!( target: LOG_TARGET, worker_pid = %pid, - "failed to recv a prepare response: {:?}", + "failed to recv a prepare response: {}", err, ); Outcome::IoErr(err.to_string()) @@ -193,17 +201,24 @@ pub async fn start_work( async fn handle_response( metrics: &Metrics, worker: IdleWorker, - result: PrepareResult, + result: PrepareWorkerResult, worker_pid: u32, tmp_file: PathBuf, - artifact_path: PathBuf, + cache_path: &Path, preparation_timeout: Duration, ) -> Outcome { - let PrepareStats { cpu_time_elapsed, memory_stats } = match result.clone() { + // TODO: Add `checksum` to `ArtifactPathId`. See: + // https://github.com/paritytech/polkadot-sdk/issues/2399 + let PrepareWorkerSuccess { + checksum: _, + stats: PrepareStats { cpu_time_elapsed, memory_stats }, + } = match result.clone() { Ok(result) => result, // Timed out on the child. This should already be logged by the child. Err(PrepareError::TimedOut) => return Outcome::TimedOut, - Err(_) => return Outcome::Concluded { worker, result }, + Err(PrepareError::JobDied { err, job_pid }) => return Outcome::JobDied { err, job_pid }, + Err(PrepareError::OutOfMemory) => return Outcome::OutOfMemory, + Err(err) => return Outcome::Concluded { worker, result: Err(err) }, }; if cpu_time_elapsed > preparation_timeout { @@ -219,6 +234,12 @@ async fn handle_response( return Outcome::TimedOut } + // The file name should uniquely identify the artifact even across restarts. In case the cache + // for some reason is not cleared correctly, we cannot + // accidentally execute an artifact compiled under a different wasmtime version, host + // environment, etc. + let artifact_path = generate_artifact_path(cache_path); + gum::debug!( target: LOG_TARGET, %worker_pid, @@ -228,7 +249,13 @@ async fn handle_response( ); let outcome = match tokio::fs::rename(&tmp_file, &artifact_path).await { - Ok(()) => Outcome::Concluded { worker, result }, + Ok(()) => Outcome::Concluded { + worker, + result: Ok(PrepareSuccess { + path: artifact_path, + stats: PrepareStats { cpu_time_elapsed, memory_stats: memory_stats.clone() }, + }), + }, Err(err) => { gum::warn!( target: LOG_TARGET, @@ -238,7 +265,7 @@ async fn handle_response( artifact_path.display(), err, ); - Outcome::RenameTmpFileErr { + Outcome::RenameTmpFile { worker, result, err: format!("{:?}", err), @@ -272,7 +299,7 @@ where { // Create the tmp file here so that the child doesn't need any file creation rights. This will // be cleared at the end of this function. - let tmp_file = worker_dir::prepare_tmp_artifact(&worker_dir.path); + let tmp_file = worker_dir::prepare_tmp_artifact(worker_dir.path()); if let Err(err) = tokio::fs::File::create(&tmp_file).await { gum::warn!( target: LOG_TARGET, @@ -287,7 +314,7 @@ where } }; - let worker_dir_path = worker_dir.path.clone(); + let worker_dir_path = worker_dir.path().to_owned(); let outcome = f(tmp_file, stream, worker_dir).await; // Try to clear the worker dir. @@ -305,14 +332,14 @@ where outcome } -async fn send_request(stream: &mut UnixStream, pvf: PvfPrepData) -> io::Result<()> { +async fn send_request(stream: &mut UnixStream, pvf: &PvfPrepData) -> io::Result<()> { framed_send(stream, &pvf.encode()).await?; Ok(()) } -async fn recv_response(stream: &mut UnixStream, pid: u32) -> io::Result { +async fn recv_response(stream: &mut UnixStream, pid: u32) -> io::Result { let result = framed_recv(stream).await?; - let result = PrepareResult::decode(&mut &result[..]).map_err(|e| { + let result = PrepareWorkerResult::decode(&mut &result[..]).map_err(|e| { // We received invalid bytes from the worker. let bound_bytes = &result[..result.len().min(4)]; gum::warn!( diff --git a/polkadot/node/core/pvf/src/security.rs b/polkadot/node/core/pvf/src/security.rs new file mode 100644 index 0000000000000000000000000000000000000000..733ef18bcadb497020db70f54db72d507a750c58 --- /dev/null +++ b/polkadot/node/core/pvf/src/security.rs @@ -0,0 +1,379 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{Config, SecurityStatus, LOG_TARGET}; +use futures::join; +use std::{fmt, path::Path}; + +/// Run checks for supported security features. +/// +/// # Returns +/// +/// Returns the set of security features that we were able to enable. If an error occurs while +/// enabling a security feature we set the corresponding status to `false`. +/// +/// # Errors +/// +/// Returns an error only if we could not fully enforce the security level required by the current +/// configuration. +pub async fn check_security_status(config: &Config) -> Result { + let Config { prepare_worker_program_path, secure_validator_mode, cache_path, .. } = config; + + let (landlock, seccomp, change_root, secure_clone) = join!( + check_landlock(prepare_worker_program_path), + check_seccomp(prepare_worker_program_path), + check_can_unshare_user_namespace_and_change_root(prepare_worker_program_path, cache_path), + check_can_do_secure_clone(prepare_worker_program_path), + ); + + let full_security_status = FullSecurityStatus::new( + *secure_validator_mode, + landlock, + seccomp, + change_root, + secure_clone, + ); + let security_status = full_security_status.as_partial(); + + if full_security_status.err_occurred() { + print_secure_mode_error_or_warning(&full_security_status); + if !full_security_status.all_errs_allowed() { + return Err("could not enable Secure Validator Mode; check logs".into()) + } + } + + if security_status.secure_validator_mode { + gum::info!( + target: LOG_TARGET, + "👮‍♀️ Running in Secure Validator Mode. \ + It is highly recommended that you operate according to our security guidelines. \ + \nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode" + ); + } + + Ok(security_status) +} + +/// Contains the full security status including error states. +struct FullSecurityStatus { + partial: SecurityStatus, + errs: Vec, +} + +impl FullSecurityStatus { + fn new( + secure_validator_mode: bool, + landlock: SecureModeResult, + seccomp: SecureModeResult, + change_root: SecureModeResult, + secure_clone: SecureModeResult, + ) -> Self { + Self { + partial: SecurityStatus { + secure_validator_mode, + can_enable_landlock: landlock.is_ok(), + can_enable_seccomp: seccomp.is_ok(), + can_unshare_user_namespace_and_change_root: change_root.is_ok(), + can_do_secure_clone: secure_clone.is_ok(), + }, + errs: [landlock, seccomp, change_root, secure_clone] + .into_iter() + .filter_map(|result| result.err()) + .collect(), + } + } + + fn as_partial(&self) -> SecurityStatus { + self.partial.clone() + } + + fn err_occurred(&self) -> bool { + !self.errs.is_empty() + } + + fn all_errs_allowed(&self) -> bool { + !self.partial.secure_validator_mode || + self.errs.iter().all(|err| err.is_allowed_in_secure_mode(&self.partial)) + } + + fn errs_string(&self) -> String { + self.errs + .iter() + .map(|err| { + format!( + "\n - {}{}", + if err.is_allowed_in_secure_mode(&self.partial) { "Optional: " } else { "" }, + err + ) + }) + .collect() + } +} + +type SecureModeResult = std::result::Result<(), SecureModeError>; + +/// Errors related to enabling Secure Validator Mode. +#[derive(Debug)] +enum SecureModeError { + CannotEnableLandlock { err: String, abi: u8 }, + CannotEnableSeccomp(String), + CannotUnshareUserNamespaceAndChangeRoot(String), + CannotDoSecureClone(String), +} + +impl SecureModeError { + /// Whether this error is allowed with Secure Validator Mode enabled. + fn is_allowed_in_secure_mode(&self, security_status: &SecurityStatus) -> bool { + use SecureModeError::*; + match self { + // Landlock is present on relatively recent Linuxes. This is optional if the unshare + // capability is present, providing FS sandboxing a different way. + CannotEnableLandlock { .. } => + security_status.can_unshare_user_namespace_and_change_root, + // seccomp should be present on all modern Linuxes unless it's been disabled. + CannotEnableSeccomp(_) => false, + // Should always be present on modern Linuxes. If not, Landlock also provides FS + // sandboxing, so don't enforce this. + CannotUnshareUserNamespaceAndChangeRoot(_) => security_status.can_enable_landlock, + // We have not determined the kernel requirements for this capability, and it's also not + // necessary for FS or networking restrictions. + CannotDoSecureClone(_) => true, + } + } +} + +impl fmt::Display for SecureModeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use SecureModeError::*; + match self { + CannotEnableLandlock{err, abi} => write!(f, "Cannot enable landlock (ABI {abi}), a Linux 5.13+ kernel security feature: {err}"), + CannotEnableSeccomp(err) => write!(f, "Cannot enable seccomp, a Linux-specific kernel security feature: {err}"), + CannotUnshareUserNamespaceAndChangeRoot(err) => write!(f, "Cannot unshare user namespace and change root, which are Linux-specific kernel security features: {err}"), + CannotDoSecureClone(err) => write!(f, "Cannot call clone with all sandboxing flags, a Linux-specific kernel security features: {err}"), + } + } +} + +/// Print an error if Secure Validator Mode and some mandatory errors occurred, warn otherwise. +fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { + let all_errs_allowed = security_status.all_errs_allowed(); + let errs_string = security_status.errs_string(); + + if all_errs_allowed { + gum::warn!( + target: LOG_TARGET, + "{}{}", + crate::SECURE_MODE_WARNING, + errs_string, + ); + } else { + gum::error!( + target: LOG_TARGET, + "{}{}{}", + crate::SECURE_MODE_ERROR, + errs_string, + crate::IGNORE_SECURE_MODE_TIP + ); + } +} + +/// Check if we can change root to a new, sandboxed root and return an error if not. +/// +/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible +/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on +/// success and -1 on failure. +async fn check_can_unshare_user_namespace_and_change_root( + prepare_worker_program_path: &Path, + cache_path: &Path, +) -> SecureModeResult { + let cache_dir_tempdir = tempfile::Builder::new() + .prefix("check-can-unshare-") + .tempdir_in(cache_path) + .map_err(|err| { + SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(format!( + "could not create a temporary directory in {:?}: {}", + cache_path, err + )) + })?; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-unshare-user-namespace-and-change-root", + &[cache_dir_tempdir.path()], + ) + .await + .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err)) +} + +/// Check if landlock is supported and return an error if not. +/// +/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible +/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on +/// success and -1 on failure. +async fn check_landlock(prepare_worker_program_path: &Path) -> SecureModeResult { + let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-landlock", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableLandlock { err, abi }) +} + +/// Check if seccomp is supported and return an error if not. +/// +/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible +/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on +/// success and -1 on failure. + +#[cfg(target_arch = "x86_64")] +async fn check_seccomp(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-seccomp", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableSeccomp(err)) +} + +#[cfg(not(target_arch = "x86_64"))] +async fn check_seccomp(_: &Path) -> SecureModeResult { + Err(SecureModeError::CannotEnableSeccomp( + "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into(), + )) +} + +/// Check if we can call `clone` with all sandboxing flags, and return an error if not. +/// +/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible +/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on +/// success and -1 on failure. +async fn check_can_do_secure_clone(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-do-secure-clone", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotDoSecureClone(err)) +} + +async fn spawn_process_for_security_check( + prepare_worker_program_path: &Path, + check_arg: &'static str, + extra_args: I, +) -> Result<(), String> +where + I: IntoIterator, + S: AsRef, +{ + let mut command = tokio::process::Command::new(prepare_worker_program_path); + // Clear env vars. (In theory, running checks with different env vars could result in different + // outcomes of the checks.) + command.env_clear(); + // Add back any env vars we want to keep. + if let Ok(value) = std::env::var("RUST_LOG") { + command.env("RUST_LOG", value); + } + + match command.arg(check_arg).args(extra_args).output().await { + Ok(output) if output.status.success() => Ok(()), + Ok(output) => { + let stderr = std::str::from_utf8(&output.stderr) + .expect("child process writes a UTF-8 string to stderr; qed") + .trim(); + if stderr.is_empty() { + Err("not available".into()) + } else { + Err(format!("not available: {}", stderr)) + } + }, + Err(err) => Err(format!("could not start child process: {}", err)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_secure_mode_error_optionality() { + let err = SecureModeError::CannotEnableLandlock { err: String::new(), abi: 3 }; + assert!(err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: false, + can_enable_seccomp: false, + can_unshare_user_namespace_and_change_root: true, + can_do_secure_clone: true, + })); + assert!(!err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: false, + can_enable_seccomp: true, + can_unshare_user_namespace_and_change_root: false, + can_do_secure_clone: false, + })); + + let err = SecureModeError::CannotEnableSeccomp(String::new()); + assert!(!err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: false, + can_enable_seccomp: false, + can_unshare_user_namespace_and_change_root: true, + can_do_secure_clone: true, + })); + assert!(!err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: false, + can_enable_seccomp: true, + can_unshare_user_namespace_and_change_root: false, + can_do_secure_clone: false, + })); + + let err = SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(String::new()); + assert!(err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: true, + can_enable_seccomp: false, + can_unshare_user_namespace_and_change_root: false, + can_do_secure_clone: false, + })); + assert!(!err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: false, + can_enable_seccomp: true, + can_unshare_user_namespace_and_change_root: false, + can_do_secure_clone: false, + })); + + let err = SecureModeError::CannotDoSecureClone(String::new()); + assert!(err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: true, + can_enable_landlock: true, + can_enable_seccomp: true, + can_unshare_user_namespace_and_change_root: true, + can_do_secure_clone: true, + })); + assert!(err.is_allowed_in_secure_mode(&SecurityStatus { + secure_validator_mode: false, + can_enable_landlock: false, + can_enable_seccomp: false, + can_unshare_user_namespace_and_change_root: false, + can_do_secure_clone: false, + })); + } +} diff --git a/polkadot/node/core/pvf/src/testing.rs b/polkadot/node/core/pvf/src/testing.rs index ca69ef9e4d047d61590c3a0ff362ab43d8e4de6f..60b0b4b8d3d0c49199800194570d8ef2783fc67f 100644 --- a/polkadot/node/core/pvf/src/testing.rs +++ b/polkadot/node/core/pvf/src/testing.rs @@ -14,14 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Various things for testing other crates. -//! -//! N.B. This is not guarded with some feature flag. Overexposing items here may affect the final -//! artifact even for production builds. +//! Various utilities for testing. -pub use crate::worker_intf::{spawn_with_program_path, SpawnErr}; +pub use crate::{ + host::{EXECUTE_BINARY_NAME, PREPARE_BINARY_NAME}, + worker_interface::{spawn_with_program_path, SpawnErr}, +}; +use crate::get_worker_version; +use is_executable::IsExecutable; +use polkadot_node_primitives::NODE_VERSION; use polkadot_primitives::ExecutorParams; +use std::{ + path::PathBuf, + sync::{Mutex, OnceLock}, +}; /// A function that emulates the stitches together behaviors of the preparation and the execution /// worker in a single synchronous function. @@ -29,8 +36,8 @@ pub fn validate_candidate( code: &[u8], params: &[u8], ) -> Result, Box> { + use polkadot_node_core_pvf_common::executor_interface::{prepare, prevalidate}; use polkadot_node_core_pvf_execute_worker::execute_artifact; - use polkadot_node_core_pvf_prepare_worker::{prepare, prevalidate}; let code = sp_maybe_compressed_blob::decompress(code, 10 * 1024 * 1024) .expect("Decompressing code failed"); @@ -47,3 +54,75 @@ pub fn validate_candidate( Ok(result) } + +/// Retrieves the worker paths and builds workers as needed. +/// +/// NOTE: This should only be called in dev code (tests, benchmarks) as it relies on the relative +/// paths of the built workers. +pub fn build_workers_and_get_paths() -> (PathBuf, PathBuf) { + // Only needs to be called once for the current process. + static WORKER_PATHS: OnceLock> = OnceLock::new(); + + fn build_workers() { + let mut build_args = vec![ + "build", + "--package=polkadot", + "--bin=polkadot-prepare-worker", + "--bin=polkadot-execute-worker", + ]; + + if cfg!(build_type = "release") { + build_args.push("--release"); + } + + let mut cargo = std::process::Command::new("cargo"); + let cmd = cargo + // wasm runtime not needed + .env("SKIP_WASM_BUILD", "1") + .args(build_args) + .stdout(std::process::Stdio::piped()); + + println!("INFO: calling `{cmd:?}`"); + let exit_status = cmd.status().expect("Failed to run the build program"); + + if !exit_status.success() { + eprintln!("ERROR: Failed to build workers: {}", exit_status.code().unwrap()); + std::process::exit(1); + } + } + + let mutex = WORKER_PATHS.get_or_init(|| { + let mut workers_path = std::env::current_exe().unwrap(); + workers_path.pop(); + workers_path.pop(); + let mut prepare_worker_path = workers_path.clone(); + prepare_worker_path.push(PREPARE_BINARY_NAME); + let mut execute_worker_path = workers_path.clone(); + execute_worker_path.push(EXECUTE_BINARY_NAME); + + // explain why a build happens + if !prepare_worker_path.is_executable() { + println!("WARN: Prepare worker does not exist or is not executable. Workers directory: {:?}", workers_path); + } + if !execute_worker_path.is_executable() { + println!("WARN: Execute worker does not exist or is not executable. Workers directory: {:?}", workers_path); + } + if let Ok(ver) = get_worker_version(&prepare_worker_path) { + if ver != NODE_VERSION { + println!("WARN: Prepare worker version {ver} does not match node version {NODE_VERSION}; worker path: {prepare_worker_path:?}"); + } + } + if let Ok(ver) = get_worker_version(&execute_worker_path) { + if ver != NODE_VERSION { + println!("WARN: Execute worker version {ver} does not match node version {NODE_VERSION}; worker path: {execute_worker_path:?}"); + } + } + + build_workers(); + + Mutex::new((prepare_worker_path, execute_worker_path)) + }); + + let guard = mutex.lock().unwrap(); + (guard.0.clone(), guard.1.clone()) +} diff --git a/polkadot/node/core/pvf/src/worker_intf.rs b/polkadot/node/core/pvf/src/worker_interface.rs similarity index 71% rename from polkadot/node/core/pvf/src/worker_intf.rs rename to polkadot/node/core/pvf/src/worker_interface.rs index bd85d84055ce5e6136a97fac329ee20a39744137..ad9f0294c09400a47f249bdf16855b3c110f155a 100644 --- a/polkadot/node/core/pvf/src/worker_intf.rs +++ b/polkadot/node/core/pvf/src/worker_interface.rs @@ -19,8 +19,9 @@ use crate::LOG_TARGET; use futures::FutureExt as _; use futures_timer::Delay; +use parity_scale_codec::Encode; use pin_project::pin_project; -use polkadot_node_core_pvf_common::SecurityStatus; +use polkadot_node_core_pvf_common::{SecurityStatus, WorkerHandshake}; use rand::Rng; use std::{ fmt, mem, @@ -68,89 +69,95 @@ pub async fn spawn_with_program_path( let program_path = program_path.into(); let worker_dir = WorkerDir::new(debug_id, cache_path).await?; let extra_args: Vec = extra_args.iter().map(|arg| arg.to_string()).collect(); + // Hack the borrow-checker. + let program_path_clone = program_path.clone(); + let worker_dir_clone = worker_dir.path().to_owned(); + let extra_args_clone = extra_args.clone(); with_transient_socket_path(debug_id, |socket_path| { let socket_path = socket_path.to_owned(); async move { - let listener = UnixListener::bind(&socket_path).map_err(|err| { - gum::warn!( - target: LOG_TARGET, - %debug_id, - ?program_path, - ?extra_args, - ?worker_dir, - ?socket_path, - "cannot bind unix socket: {:?}", - err, - ); - SpawnErr::Bind - })?; - - let handle = WorkerHandle::spawn( - &program_path, - &extra_args, - &socket_path, - &worker_dir.path, - security_status, - ) - .map_err(|err| { - gum::warn!( - target: LOG_TARGET, - %debug_id, - ?program_path, - ?extra_args, - ?worker_dir.path, - ?socket_path, - "cannot spawn a worker: {:?}", - err, - ); - SpawnErr::ProcessSpawn - })?; + let listener = match UnixListener::bind(&socket_path) { + Ok(ok) => ok, + Err(err) => return Err(SpawnErr::Bind { socket_path, err: err.to_string() }), + }; + + let handle = + WorkerHandle::spawn(&program_path, &extra_args, &socket_path, &worker_dir.path()) + .map_err(|err| SpawnErr::ProcessSpawn { program_path, err: err.to_string() })?; - let worker_dir_path = worker_dir.path.clone(); futures::select! { accept_result = listener.accept().fuse() => { - let (stream, _) = accept_result.map_err(|err| { - gum::warn!( - target: LOG_TARGET, - %debug_id, - ?program_path, - ?extra_args, - ?worker_dir_path, - ?socket_path, - "cannot accept a worker: {:?}", - err, - ); - SpawnErr::Accept - })?; + let (mut stream, _) = accept_result + .map_err(|err| SpawnErr::Accept { socket_path, err: err.to_string() })?; + send_worker_handshake(&mut stream, WorkerHandshake { security_status }) + .await + .map_err(|err| SpawnErr::Handshake { err: err.to_string() })?; Ok((IdleWorker { stream, pid: handle.id(), worker_dir }, handle)) } - _ = Delay::new(spawn_timeout).fuse() => { - gum::warn!( - target: LOG_TARGET, - %debug_id, - ?program_path, - ?extra_args, - ?worker_dir_path, - ?socket_path, - ?spawn_timeout, - "spawning and connecting to socket timed out", - ); - Err(SpawnErr::AcceptTimeout) - } + _ = Delay::new(spawn_timeout).fuse() => Err(SpawnErr::AcceptTimeout{spawn_timeout}), } } }) .await + .map_err(|err| { + gum::warn!( + target: LOG_TARGET, + %debug_id, + program_path = ?program_path_clone, + extra_args = ?extra_args_clone, + worker_dir = ?worker_dir_clone, + "error spawning worker: {}", + err, + ); + err + }) } +/// A temporary, random, free path that is necessary only to establish socket communications. If a +/// directory exists at the path at the end of this function, it is removed then. async fn with_transient_socket_path(debug_id: &'static str, f: F) -> Result where F: FnOnce(&Path) -> Fut, Fut: futures::Future> + 'static, { - let socket_path = tmppath(&format!("pvf-host-{}", debug_id)) + /// Returns a path under [`std::env::temp_dir`]. The path name will start with the given prefix. + /// + /// There is only a certain number of retries. If exceeded this function will give up and return + /// an error. + pub async fn tmppath(prefix: &str) -> io::Result { + fn make_tmppath(prefix: &str, dir: &Path) -> PathBuf { + use rand::distributions::Alphanumeric; + + const DESCRIMINATOR_LEN: usize = 10; + + let mut buf = Vec::with_capacity(prefix.len() + DESCRIMINATOR_LEN); + buf.extend(prefix.as_bytes()); + buf.extend(rand::thread_rng().sample_iter(&Alphanumeric).take(DESCRIMINATOR_LEN)); + + let s = std::str::from_utf8(&buf) + .expect("the string is collected from a valid utf-8 sequence; qed"); + + let mut path = dir.to_owned(); + path.push(s); + path + } + + const NUM_RETRIES: usize = 50; + + let dir = std::env::temp_dir(); + for _ in 0..NUM_RETRIES { + let tmp_path = make_tmppath(prefix, &dir); + if !tmp_path.exists() { + return Ok(tmp_path) + } + } + + Err(io::Error::new(io::ErrorKind::Other, "failed to create a temporary path")) + } + + let socket_path = tmppath(&format!("pvf-host-{}-", debug_id)) .await .map_err(|_| SpawnErr::TmpPath)?; let result = f(&socket_path).await; @@ -162,46 +169,6 @@ where result } -/// Returns a path under the given `dir`. The path name will start with the given prefix. -/// -/// There is only a certain number of retries. If exceeded this function will give up and return an -/// error. -pub async fn tmppath_in(prefix: &str, dir: &Path) -> io::Result { - fn make_tmppath(prefix: &str, dir: &Path) -> PathBuf { - use rand::distributions::Alphanumeric; - - const DESCRIMINATOR_LEN: usize = 10; - - let mut buf = Vec::with_capacity(prefix.len() + DESCRIMINATOR_LEN); - buf.extend(prefix.as_bytes()); - buf.extend(rand::thread_rng().sample_iter(&Alphanumeric).take(DESCRIMINATOR_LEN)); - - let s = std::str::from_utf8(&buf) - .expect("the string is collected from a valid utf-8 sequence; qed"); - - let mut path = dir.to_owned(); - path.push(s); - path - } - - const NUM_RETRIES: usize = 50; - - for _ in 0..NUM_RETRIES { - let tmp_path = make_tmppath(prefix, dir); - if !tmp_path.exists() { - return Ok(tmp_path) - } - } - - Err(io::Error::new(io::ErrorKind::Other, "failed to create a temporary path")) -} - -/// The same as [`tmppath_in`], but uses [`std::env::temp_dir`] as the directory. -pub async fn tmppath(prefix: &str) -> io::Result { - let temp_dir = PathBuf::from(std::env::temp_dir()); - tmppath_in(prefix, &temp_dir).await -} - /// A struct that represents an idle worker. /// /// This struct is supposed to be used as a token that is passed by move into a subroutine that @@ -219,23 +186,26 @@ pub struct IdleWorker { pub worker_dir: WorkerDir, } +/// This is publicly exposed only for integration tests. +/// /// An error happened during spawning a worker process. -#[derive(Clone, Debug)] +#[derive(thiserror::Error, Clone, Debug)] +#[doc(hidden)] pub enum SpawnErr { - /// Cannot obtain a temporary path location. + #[error("cannot obtain a temporary path location")] TmpPath, - /// An FS error occurred. - Fs(String), - /// Cannot bind the socket to the given path. - Bind, - /// An error happened during accepting a connection to the socket. - Accept, - /// An error happened during spawning the process. - ProcessSpawn, - /// The deadline allotted for the worker spawning and connecting to the socket has elapsed. - AcceptTimeout, - /// Failed to send handshake after successful spawning was signaled - Handshake, + #[error("cannot bind the socket to the given path {socket_path:?}: {err}")] + Bind { socket_path: PathBuf, err: String }, + #[error( + "an error happened during accepting a connection to the socket {socket_path:?}: {err}" + )] + Accept { socket_path: PathBuf, err: String }, + #[error("an error happened during spawning the process at path {program_path:?}: {err}")] + ProcessSpawn { program_path: PathBuf, err: String }, + #[error("the deadline {}ms allotted for the worker spawning and connecting to the socket has elapsed", .spawn_timeout.as_millis())] + AcceptTimeout { spawn_timeout: Duration }, + #[error("failed to send handshake after successful spawning was signaled: {err}")] + Handshake { err: String }, } /// This is a representation of a potentially running worker. Drop it and the process will be @@ -245,7 +215,7 @@ pub enum SpawnErr { /// has been terminated. Since the worker is running in another process it is obviously not /// necessary to poll this future to make the worker run, it's only for termination detection. /// -/// This future relies on the fact that a child process's stdout `fd` is closed upon it's +/// This future relies on the fact that a child process's stdout `fd` is closed upon its /// termination. #[pin_project] pub struct WorkerHandle { @@ -263,19 +233,7 @@ impl WorkerHandle { extra_args: &[String], socket_path: impl AsRef, worker_dir_path: impl AsRef, - security_status: SecurityStatus, ) -> io::Result { - let security_args = { - let mut args = vec![]; - if security_status.can_enable_landlock { - args.push("--can-enable-landlock".to_string()); - } - if security_status.can_unshare_user_namespace_and_change_root { - args.push("--can-unshare-user-namespace-and-change-root".to_string()); - } - args - }; - // Clear all env vars from the spawned process. let mut command = process::Command::new(program.as_ref()); command.env_clear(); @@ -283,13 +241,13 @@ impl WorkerHandle { if let Ok(value) = std::env::var("RUST_LOG") { command.env("RUST_LOG", value); } + let mut child = command .args(extra_args) .arg("--socket-path") .arg(socket_path.as_ref().as_os_str()) .arg("--worker-dir-path") .arg(worker_dir_path.as_ref().as_os_str()) - .args(&security_args) .stdout(std::process::Stdio::piped()) .kill_on_drop(true) .spawn()?; @@ -389,6 +347,14 @@ pub async fn framed_recv(r: &mut (impl AsyncRead + Unpin)) -> io::Result Ok(buf) } +/// Sends a handshake with information for the worker. +async fn send_worker_handshake( + stream: &mut UnixStream, + handshake: WorkerHandshake, +) -> io::Result<()> { + framed_send(stream, &handshake.encode()).await +} + /// A temporary worker dir that contains only files needed by the worker. The worker will change its /// root (the `/` directory) to this directory; it should have access to no other paths on its /// filesystem. @@ -415,33 +381,29 @@ pub async fn framed_recv(r: &mut (impl AsyncRead + Unpin)) -> io::Result /// ``` #[derive(Debug)] pub struct WorkerDir { - pub path: PathBuf, + tempdir: tempfile::TempDir, } +pub const WORKER_DIR_PREFIX: &str = "worker-dir"; + impl WorkerDir { /// Creates a new, empty worker dir with a random name in the given cache dir. pub async fn new(debug_id: &'static str, cache_dir: &Path) -> Result { - let prefix = format!("worker-dir-{}-", debug_id); - let path = tmppath_in(&prefix, cache_dir).await.map_err(|_| SpawnErr::TmpPath)?; - tokio::fs::create_dir(&path) - .await - .map_err(|err| SpawnErr::Fs(err.to_string()))?; - Ok(Self { path }) + let prefix = format!("{WORKER_DIR_PREFIX}-{debug_id}-"); + let tempdir = tempfile::Builder::new() + .prefix(&prefix) + .tempdir_in(cache_dir) + .map_err(|_| SpawnErr::TmpPath)?; + Ok(Self { tempdir }) } -} -// Try to clean up the temporary worker dir at the end of the worker's lifetime. It should be wiped -// on startup, but we make a best effort not to leave it around. -impl Drop for WorkerDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.path); + pub fn path(&self) -> &Path { + self.tempdir.path() } } // Not async since Rust has trouble with async recursion. There should be few files here anyway. // -// TODO: A lingering malicious job can still access future files in this dir. See -// for how to fully secure this. /// Clear the temporary worker dir without deleting it. Not deleting is important because the worker /// has mounted its own separate filesystem here. /// @@ -449,7 +411,7 @@ impl Drop for WorkerDir { /// artifacts from previous jobs. pub fn clear_worker_dir_path(worker_dir_path: &Path) -> io::Result<()> { fn remove_dir_contents(path: &Path) -> io::Result<()> { - for entry in std::fs::read_dir(&path)? { + for entry in std::fs::read_dir(path)? { let entry = entry?; let path = entry.path(); diff --git a/polkadot/node/core/pvf/tests/README.md b/polkadot/node/core/pvf/tests/README.md deleted file mode 100644 index 27385e190250df9baa0d14cea7afc9e00f973a98..0000000000000000000000000000000000000000 --- a/polkadot/node/core/pvf/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# PVF host integration tests - -## Testing - -Before running these tests, make sure the worker binaries are built first. This can be done with: - -```sh -cargo build --bin polkadot-execute-worker --bin polkadot-prepare-worker -``` diff --git a/polkadot/node/core/pvf/tests/it/adder.rs b/polkadot/node/core/pvf/tests/it/adder.rs index 8bdd09db208a92f5510f0c60856bab6a1c3103e0..9a7ddcb408909e6fcac2c812921174e81b7911bb 100644 --- a/polkadot/node/core/pvf/tests/it/adder.rs +++ b/polkadot/node/core/pvf/tests/it/adder.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +//! PVF host integration tests checking the chain production pipeline. + use super::TestHost; use adder::{hash_state, BlockData, HeadData}; use parity_scale_codec::{Decode, Encode}; @@ -28,7 +30,7 @@ async fn execute_good_block_on_parent() { let block_data = BlockData { state: 0, add: 512 }; - let host = TestHost::new(); + let host = TestHost::new().await; let ret = host .validate_candidate( @@ -56,7 +58,7 @@ async fn execute_good_chain_on_parent() { let mut parent_hash = [0; 32]; let mut last_state = 0; - let host = TestHost::new(); + let host = TestHost::new().await; for (number, add) in (0..10).enumerate() { let parent_head = @@ -98,7 +100,7 @@ async fn execute_bad_block_on_parent() { add: 256, }; - let host = TestHost::new(); + let host = TestHost::new().await; let _err = host .validate_candidate( @@ -117,7 +119,7 @@ async fn execute_bad_block_on_parent() { #[tokio::test] async fn stress_spawn() { - let host = std::sync::Arc::new(TestHost::new()); + let host = std::sync::Arc::new(TestHost::new().await); async fn execute(host: std::sync::Arc) { let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) }; @@ -149,9 +151,12 @@ async fn stress_spawn() { // With one worker, run multiple execution jobs serially. They should not conflict. #[tokio::test] async fn execute_can_run_serially() { - let host = std::sync::Arc::new(TestHost::new_with_config(|cfg| { - cfg.execute_workers_max_num = 1; - })); + let host = std::sync::Arc::new( + TestHost::new_with_config(|cfg| { + cfg.execute_workers_max_num = 1; + }) + .await, + ); async fn execute(host: std::sync::Arc) { let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) }; diff --git a/polkadot/node/core/pvf/tests/it/main.rs b/polkadot/node/core/pvf/tests/it/main.rs index f699b5840d8faa7e0a51e859b735caed02f3e621..cdfbcd8e57855121a7128a0ed616df4f8ecb0cbd 100644 --- a/polkadot/node/core/pvf/tests/it/main.rs +++ b/polkadot/node/core/pvf/tests/it/main.rs @@ -14,58 +14,58 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#[cfg(feature = "ci-only-tests")] +//! General PVF host integration tests checking the functionality of the PVF host itself. + use assert_matches::assert_matches; use parity_scale_codec::Encode as _; +#[cfg(all(feature = "ci-only-tests", target_os = "linux"))] +use polkadot_node_core_pvf::SecurityStatus; use polkadot_node_core_pvf::{ - start, Config, InvalidCandidate, Metrics, PrepareError, PrepareJobKind, PrepareStats, - PvfPrepData, ValidationError, ValidationHost, JOB_TIMEOUT_WALL_CLOCK_FACTOR, + start, testing::build_workers_and_get_paths, Config, InvalidCandidate, Metrics, + PossiblyInvalidError, PrepareError, PrepareJobKind, PvfPrepData, ValidationError, + ValidationHost, JOB_TIMEOUT_WALL_CLOCK_FACTOR, }; use polkadot_parachain_primitives::primitives::{BlockData, ValidationParams, ValidationResult}; -use polkadot_primitives::ExecutorParams; - -#[cfg(feature = "ci-only-tests")] -use polkadot_primitives::ExecutorParam; +use polkadot_primitives::{ExecutorParam, ExecutorParams}; -use std::time::Duration; +use std::{io::Write, time::Duration}; use tokio::sync::Mutex; mod adder; +#[cfg(target_os = "linux")] +mod process; mod worker_common; -const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(3); -const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(3); +const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(6); +const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(6); struct TestHost { + // Keep a reference to the tempdir as it gets deleted on drop. cache_dir: tempfile::TempDir, host: Mutex, } impl TestHost { - fn new() -> Self { - Self::new_with_config(|_| ()) + async fn new() -> Self { + Self::new_with_config(|_| ()).await } - fn new_with_config(f: F) -> Self + async fn new_with_config(f: F) -> Self where F: FnOnce(&mut Config), { - let mut workers_path = std::env::current_exe().unwrap(); - workers_path.pop(); - workers_path.pop(); - let mut prepare_worker_path = workers_path.clone(); - prepare_worker_path.push("polkadot-prepare-worker"); - let mut execute_worker_path = workers_path.clone(); - execute_worker_path.push("polkadot-execute-worker"); + let (prepare_worker_path, execute_worker_path) = build_workers_and_get_paths(); + let cache_dir = tempfile::tempdir().unwrap(); let mut config = Config::new( cache_dir.path().to_owned(), None, + false, prepare_worker_path, execute_worker_path, ); f(&mut config); - let (host, task) = start(config, Metrics::default()); + let (host, task) = start(config, Metrics::default()).await.unwrap(); let _ = tokio::task::spawn(task); Self { cache_dir, host: Mutex::new(host) } } @@ -74,7 +74,7 @@ impl TestHost { &self, code: &[u8], executor_params: ExecutorParams, - ) -> Result { + ) -> Result<(), PrepareError> { let (result_tx, result_rx) = futures::channel::oneshot::channel(); let code = sp_maybe_compressed_blob::decompress(code, 16 * 1024 * 1024) @@ -127,11 +127,35 @@ impl TestHost { .unwrap(); result_rx.await.unwrap() } + + #[cfg(all(feature = "ci-only-tests", target_os = "linux"))] + async fn security_status(&self) -> SecurityStatus { + self.host.lock().await.security_status.clone() + } +} + +#[tokio::test] +async fn prepare_job_terminates_on_timeout() { + let host = TestHost::new().await; + + let start = std::time::Instant::now(); + let result = host + .precheck_pvf(rococo_runtime::WASM_BINARY.unwrap(), Default::default()) + .await; + + match result { + Err(PrepareError::TimedOut) => {}, + r => panic!("{:?}", r), + } + + let duration = std::time::Instant::now().duration_since(start); + assert!(duration >= TEST_PREPARATION_TIMEOUT); + assert!(duration < TEST_PREPARATION_TIMEOUT * JOB_TIMEOUT_WALL_CLOCK_FACTOR); } #[tokio::test] -async fn terminates_on_timeout() { - let host = TestHost::new(); +async fn execute_job_terminates_on_timeout() { + let host = TestHost::new().await; let start = std::time::Instant::now(); let result = host @@ -148,7 +172,7 @@ async fn terminates_on_timeout() { .await; match result { - Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) => {}, + Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)) => {}, r => panic!("{:?}", r), } @@ -161,7 +185,7 @@ async fn terminates_on_timeout() { #[tokio::test] async fn ensure_parallel_execution() { // Run some jobs that do not complete, thus timing out. - let host = TestHost::new(); + let host = TestHost::new().await; let execute_pvf_future_1 = host.validate_candidate( halt::wasm_binary_unwrap(), ValidationParams { @@ -188,8 +212,8 @@ async fn ensure_parallel_execution() { assert_matches!( (res1, res2), ( - Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)), - Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) + Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)), + Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)) ) ); @@ -208,7 +232,8 @@ async fn ensure_parallel_execution() { async fn execute_queue_doesnt_stall_if_workers_died() { let host = TestHost::new_with_config(|cfg| { cfg.execute_workers_max_num = 5; - }); + }) + .await; // Here we spawn 8 validation jobs for the `halt` PVF and share those between 5 workers. The // first five jobs should timeout and the workers killed. For the next 3 jobs a new batch of @@ -245,7 +270,8 @@ async fn execute_queue_doesnt_stall_if_workers_died() { async fn execute_queue_doesnt_stall_with_varying_executor_params() { let host = TestHost::new_with_config(|cfg| { cfg.execute_workers_max_num = 2; - }); + }) + .await; let executor_params_1 = ExecutorParams::default(); let executor_params_2 = ExecutorParams::from(&[ExecutorParam::StackLogicalMax(1024)][..]); @@ -293,9 +319,27 @@ async fn execute_queue_doesnt_stall_with_varying_executor_params() { // Test that deleting a prepared artifact does not lead to a dispute when we try to execute it. #[tokio::test] async fn deleting_prepared_artifact_does_not_dispute() { - let host = TestHost::new(); + let host = TestHost::new().await; let cache_dir = host.cache_dir.path(); + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), Default::default()).await.unwrap(); + + // Manually delete the prepared artifact from disk. The in-memory artifacts table won't change. + { + // Get the artifact path (asserting it exists). + let mut cache_dir: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); + // Should contain the artifact and the worker dir. + assert_eq!(cache_dir.len(), 2); + let mut artifact_path = cache_dir.pop().unwrap().unwrap(); + if artifact_path.path().is_dir() { + artifact_path = cache_dir.pop().unwrap().unwrap(); + } + + // Delete the artifact. + std::fs::remove_file(artifact_path.path()).unwrap(); + } + + // Try to validate, artifact should get recreated. let result = host .validate_candidate( halt::wasm_binary_unwrap(), @@ -309,13 +353,20 @@ async fn deleting_prepared_artifact_does_not_dispute() { ) .await; - match result { - Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) => {}, - r => panic!("{:?}", r), - } + assert_matches!(result, Err(ValidationError::Invalid(InvalidCandidate::HardTimeout))); +} - // Delete the prepared artifact. - { +// Test that corruption of a prepared artifact does not lead to a dispute when we try to execute it. +#[tokio::test] +async fn corrupted_prepared_artifact_does_not_dispute() { + let host = TestHost::new().await; + let cache_dir = host.cache_dir.path(); + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), Default::default()).await.unwrap(); + + // Manually corrupting the prepared artifact from disk. The in-memory artifacts table won't + // change. + let artifact_path = { // Get the artifact path (asserting it exists). let mut cache_dir: Vec<_> = std::fs::read_dir(cache_dir).unwrap().collect(); // Should contain the artifact and the worker dir. @@ -325,11 +376,20 @@ async fn deleting_prepared_artifact_does_not_dispute() { artifact_path = cache_dir.pop().unwrap().unwrap(); } - // Delete the artifact. - std::fs::remove_file(artifact_path.path()).unwrap(); - } + // Corrupt the artifact. + let mut f = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(artifact_path.path()) + .unwrap(); + f.write_all(b"corrupted wasm").unwrap(); + f.flush().unwrap(); + artifact_path + }; - // Try to validate again, artifact should get recreated. + assert!(artifact_path.path().exists()); + + // Try to validate, artifact should get removed because of the corruption. let result = host .validate_candidate( halt::wasm_binary_unwrap(), @@ -343,10 +403,86 @@ async fn deleting_prepared_artifact_does_not_dispute() { ) .await; - match result { - Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) => {}, - r => panic!("{:?}", r), + assert_matches!( + result, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::RuntimeConstruction(_))) + ); + + // because of RuntimeConstruction we may retry + host.precheck_pvf(halt::wasm_binary_unwrap(), Default::default()).await.unwrap(); + + // The actual artifact removal is done concurrently + // with sending of the result of the execution + // it is not a problem for further re-preparation as + // artifact filenames are random + for _ in 1..5 { + if !artifact_path.path().exists() { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; } + + assert!( + !artifact_path.path().exists(), + "the corrupted artifact ({}) should be deleted by the host", + artifact_path.path().display() + ); +} + +#[tokio::test] +async fn cache_cleared_on_startup() { + // Don't drop this host, it owns the `TempDir` which gets cleared on drop. + let host = TestHost::new().await; + + let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), Default::default()).await.unwrap(); + + // The cache dir should contain one artifact and one worker dir. + let cache_dir = host.cache_dir.path().to_owned(); + assert_eq!(std::fs::read_dir(&cache_dir).unwrap().count(), 2); + + // Start a new host, previous artifact should be cleared. + let _host = TestHost::new_with_config(|cfg| { + cfg.cache_path = cache_dir.clone(); + }) + .await; + assert_eq!(std::fs::read_dir(&cache_dir).unwrap().count(), 0); +} + +// This test checks if the adder parachain runtime can be prepared with 10Mb preparation memory +// limit enforced. At the moment of writing, the limit if far enough to prepare the PVF. If it +// starts failing, either Wasmtime version has changed, or the PVF code itself has changed, and +// more memory is required now. Multi-threaded preparation, if ever enabled, may also affect +// memory consumption. +#[tokio::test] +async fn prechecking_within_memory_limits() { + let host = TestHost::new().await; + let result = host + .precheck_pvf( + ::adder::wasm_binary_unwrap(), + ExecutorParams::from(&[ExecutorParam::PrecheckingMaxMemory(10 * 1024 * 1024)][..]), + ) + .await; + + assert_matches!(result, Ok(_)); +} + +// This test checks if the adder parachain runtime can be prepared with 512Kb preparation memory +// limit enforced. At the moment of writing, the limit if not enough to prepare the PVF, and the +// preparation is supposed to generate an error. If the test starts failing, either Wasmtime +// version has changed, or the PVF code itself has changed, and less memory is required now. +#[tokio::test] +async fn prechecking_out_of_memory() { + use polkadot_node_core_pvf::PrepareError; + + let host = TestHost::new().await; + let result = host + .precheck_pvf( + ::adder::wasm_binary_unwrap(), + ExecutorParams::from(&[ExecutorParam::PrecheckingMaxMemory(512 * 1024)][..]), + ) + .await; + + assert_matches!(result, Err(PrepareError::OutOfMemory)); } // With one worker, run multiple preparation jobs serially. They should not conflict. @@ -354,7 +490,8 @@ async fn deleting_prepared_artifact_does_not_dispute() { async fn prepare_can_run_serially() { let host = TestHost::new_with_config(|cfg| { cfg.prepare_workers_hard_max_num = 1; - }); + }) + .await; let _stats = host .precheck_pvf(::adder::wasm_binary_unwrap(), Default::default()) @@ -364,3 +501,58 @@ async fn prepare_can_run_serially() { // Prepare a different wasm blob to prevent skipping work. let _stats = host.precheck_pvf(halt::wasm_binary_unwrap(), Default::default()).await.unwrap(); } + +// CI machines should be able to enable all the security features. +#[cfg(all(feature = "ci-only-tests", target_os = "linux"))] +#[tokio::test] +async fn all_security_features_work() { + // Landlock is only available starting Linux 5.13, and we may be testing on an old kernel. + let can_enable_landlock = { + let sysinfo = sc_sysinfo::gather_sysinfo(); + // The version will look something like "5.15.0-87-generic". + let version = sysinfo.linux_kernel.unwrap(); + let version_split: Vec<&str> = version.split(".").collect(); + let major: u32 = version_split[0].parse().unwrap(); + let minor: u32 = version_split[1].parse().unwrap(); + if major >= 6 { + true + } else if major == 5 { + minor >= 13 + } else { + false + } + }; + + let host = TestHost::new().await; + + assert_eq!( + host.security_status().await, + SecurityStatus { + // Disabled in tests to not enforce the presence of security features. This CI-only test + // is the only one that tests them. + secure_validator_mode: false, + can_enable_landlock, + can_enable_seccomp: true, + can_unshare_user_namespace_and_change_root: true, + can_do_secure_clone: true, + } + ); +} + +// 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 new file mode 100644 index 0000000000000000000000000000000000000000..e989eb874ba956da83fccd653f978a041f2411ea --- /dev/null +++ b/polkadot/node/core/pvf/tests/it/process.rs @@ -0,0 +1,378 @@ +// 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 unexpected behaviors of the spawned processes. We test both worker processes (directly +//! 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 as GenericBlockData, HeadData as GenericHeadData, ValidationParams, +}; +use procfs::process; +use rusty_fork::rusty_fork_test; +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"; + +const SIGNAL_KILL: i32 = 9; +const SIGNAL_STOP: i32 = 19; + +fn send_signal_by_sid_and_name( + sid: i32, + exe_name: &'static str, + is_direct_child: bool, + signal: i32, +) { + 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) + .expect("Should have found the expected process"); + process.stat().unwrap().num_threads +} + +fn find_process_by_sid_and_name( + sid: i32, + exe_name: &'static str, + is_direct_child: bool, +) -> Option { + let all_processes: Vec = process::all_processes() + .expect("Can't read /proc") + .filter_map(|p| match p { + Ok(p) => Some(p), // happy path + Err(e) => match e { + // process vanished during iteration, ignore it + procfs::ProcError::NotFound(_) => None, + x => { + panic!("some unknown error: {}", x); + }, + }, + }) + .collect(); + + let mut found = None; + for process in all_processes { + 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 + } + // The workers are direct children of the current process, the worker job processes are not + // (they are children of the workers). + let process_is_direct_child = stat.ppid as u32 == std::process::id(); + if is_direct_child != process_is_direct_child { + continue + } + + if found.is_some() { + panic!("Found more than one process") + } + found = Some(process); + } + found +} + +/// Sets up the test. +/// +/// 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; + }); +} + +// Run these tests in their own processes with rusty-fork. They work by each creating a new session, +// then finding the child process that matches the session ID and expected process name and doing +// something with that child. +rusty_fork_test! { + // Everything succeeds. + #[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() { + 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()), + // Send a stop signal to pause the worker. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, PREPARE_PROCESS_NAME, true, SIGNAL_STOP); + } + ); + + assert_matches!(result, Err(PrepareError::TimedOut)); + }) + } + + // What happens when the execute worker (not the job) times out? + #[test] + fn execute_worker_timeout() { + 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(); + + let (result, _) = futures::join!( + // Choose an job that would normally take the entire timeout. + host.validate_candidate( + binary, + ValidationParams { + block_data: GenericBlockData(Vec::new()), + parent_head: Default::default(), + relay_parent_number: 1, + relay_parent_storage_root: Default::default(), + }, + Default::default(), + ), + // Send a stop signal to pause the worker. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, true, SIGNAL_STOP); + } + ); + + assert_matches!( + result, + Err(ValidationError::Invalid(InvalidCandidate::HardTimeout)) + ); + }) + } + + // What happens when the prepare worker dies in the middle of a job? + #[test] + fn prepare_worker_killed_during_job() { + 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()), + // Run a future that kills the job while it's running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, PREPARE_PROCESS_NAME, true, SIGNAL_KILL); + } + ); + + assert_matches!(result, Err(PrepareError::IoErr(_))); + }) + } + + // What happens when the execute worker dies in the middle of a job? + #[test] + fn execute_worker_killed_during_job() { + 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(); + + let (result, _) = futures::join!( + // Choose an job that would normally take the entire timeout. + host.validate_candidate( + binary, + ValidationParams { + block_data: GenericBlockData(Vec::new()), + parent_head: Default::default(), + relay_parent_number: 1, + relay_parent_storage_root: Default::default(), + }, + Default::default(), + ), + // Run a future that kills the job while it's running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, true, SIGNAL_KILL); + } + ); + + assert_matches!( + result, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousWorkerDeath)) + ); + }) + } + + // What happens when the forked prepare job dies in the middle of its job? + #[test] + fn forked_prepare_job_killed_during_job() { + 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()), + // Run a future that kills the job while it's running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, PREPARE_PROCESS_NAME, false, SIGNAL_KILL); + } + ); + + // Note that we get a more specific error if the job died than if the whole worker died. + assert_matches!( + result, + Err(PrepareError::JobDied{ err, job_pid: _ }) if err == "received signal: SIGKILL" + ); + }) + } + + // What happens when the forked execute job dies in the middle of its job? + #[test] + fn forked_execute_job_killed_during_job() { + 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(); + + let (result, _) = futures::join!( + // Choose a job that would normally take the entire timeout. + host.validate_candidate( + binary, + ValidationParams { + block_data: GenericBlockData(Vec::new()), + parent_head: Default::default(), + relay_parent_number: 1, + relay_parent_storage_root: Default::default(), + }, + Default::default(), + ), + // Run a future that kills the job while it's running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + send_signal_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, false, SIGNAL_KILL); + } + ); + + // Note that we get a more specific error if the job died than if the whole worker died. + assert_matches!( + result, + Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::AmbiguousJobDeath(err))) + if err == "received signal: SIGKILL" + ); + }) + } + + // Ensure that the spawned prepare worker is single-threaded. + // + // See `run_worker` for why we need this invariant. + #[test] + fn ensure_prepare_processes_have_correct_num_threads() { + 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()), + // Run a future that kills the job while it's running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + assert_eq!( + get_num_threads_by_sid_and_name(sid, PREPARE_PROCESS_NAME, true), + 1 + ); + // Child job should have three threads: main thread, execute thread, CPU time + // monitor, and memory tracking. + assert_eq!( + get_num_threads_by_sid_and_name(sid, PREPARE_PROCESS_NAME, false), + polkadot_node_core_pvf_prepare_worker::PREPARE_WORKER_THREAD_NUMBER as i64, + ); + + // End the test. + send_signal_by_sid_and_name(sid, PREPARE_PROCESS_NAME, true, SIGNAL_KILL); + } + ); + }) + } + + // Ensure that the spawned execute worker is single-threaded. + // + // See `run_worker` for why we need this invariant. + #[test] + fn ensure_execute_processes_have_correct_num_threads() { + 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(); + + let _ = futures::join!( + // Choose a job that would normally take the entire timeout. + host.validate_candidate( + binary, + ValidationParams { + block_data: GenericBlockData(Vec::new()), + parent_head: Default::default(), + relay_parent_number: 1, + relay_parent_storage_root: Default::default(), + }, + Default::default(), + ), + // Run a future that tests the thread count while the worker is running. + async { + tokio::time::sleep(Duration::from_secs(1)).await; + assert_eq!( + get_num_threads_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, true), + 1 + ); + // Child job should have three threads: main thread, execute thread, and CPU + // time monitor. + assert_eq!( + get_num_threads_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, false), + polkadot_node_core_pvf_execute_worker::EXECUTE_WORKER_THREAD_NUMBER as i64, + ); + + // End the test. + send_signal_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, true, SIGNAL_KILL); + } + ); + }) + } +} diff --git a/polkadot/node/core/pvf/tests/it/worker_common.rs b/polkadot/node/core/pvf/tests/it/worker_common.rs index 5379d29556c76aff3a489d7d0a967d4b6a503637..2c24a15b682df651e1c033f686f0549488b32ffa 100644 --- a/polkadot/node/core/pvf/tests/it/worker_common.rs +++ b/polkadot/node/core/pvf/tests/it/worker_common.rs @@ -15,55 +15,59 @@ // along with Polkadot. If not, see . use polkadot_node_core_pvf::{ - testing::{spawn_with_program_path, SpawnErr}, + testing::{build_workers_and_get_paths, spawn_with_program_path, SpawnErr}, SecurityStatus, }; use std::{env, time::Duration}; -fn worker_path(name: &str) -> std::path::PathBuf { - let mut worker_path = std::env::current_exe().unwrap(); - worker_path.pop(); - worker_path.pop(); - worker_path.push(name); - worker_path -} - // Test spawning a program that immediately exits with a failure code. #[tokio::test] async fn spawn_immediate_exit() { + let (prepare_worker_path, _) = build_workers_and_get_paths(); + // There's no explicit `exit` subcommand in the worker; it will panic on an unknown // subcommand anyway + let spawn_timeout = Duration::from_secs(2); let result = spawn_with_program_path( "integration-test", - worker_path("polkadot-prepare-worker"), + prepare_worker_path, &env::temp_dir(), &["exit"], Duration::from_secs(2), SecurityStatus::default(), ) .await; - assert!(matches!(result, Err(SpawnErr::AcceptTimeout))); + assert!( + matches!(result, Err(SpawnErr::AcceptTimeout { spawn_timeout: s }) if s == spawn_timeout) + ); } #[tokio::test] async fn spawn_timeout() { + let (_, execute_worker_path) = build_workers_and_get_paths(); + + let spawn_timeout = Duration::from_secs(2); let result = spawn_with_program_path( "integration-test", - worker_path("polkadot-execute-worker"), + execute_worker_path, &env::temp_dir(), &["test-sleep"], - Duration::from_secs(2), + spawn_timeout, SecurityStatus::default(), ) .await; - assert!(matches!(result, Err(SpawnErr::AcceptTimeout))); + assert!( + matches!(result, Err(SpawnErr::AcceptTimeout { spawn_timeout: s }) if s == spawn_timeout) + ); } #[tokio::test] async fn should_connect() { + let (prepare_worker_path, _) = build_workers_and_get_paths(); + let _ = spawn_with_program_path( "integration-test", - worker_path("polkadot-prepare-worker"), + prepare_worker_path, &env::temp_dir(), &["prepare-worker"], Duration::from_secs(2), diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml index b16a501686d3503ae476e807d859f58bdf88fbb5..2de3a6ee325ad6d08ea3377e8fccee56b7e505fa 100644 --- a/polkadot/node/core/runtime-api/Cargo.toml +++ b/polkadot/node/core/runtime-api/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-node-core-runtime-api" -version = "1.0.0" +version = "7.0.0" +description = "Wrapper around the parachain-related runtime APIs" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } @@ -21,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 69eea22b23bda4d8c67543c535c5f80afd81703d..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, 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 @@ -67,6 +68,8 @@ pub(crate) struct RequestResultCache { disabled_validators: LruMap>, para_backing_state: LruMap<(Hash, ParaId), Option>, async_backing_params: LruMap, + node_features: LruMap, + approval_voting_params: LruMap, } impl Default for RequestResultCache { @@ -97,9 +100,11 @@ 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)), + node_features: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), } } } @@ -446,6 +451,21 @@ impl RequestResultCache { self.minimum_backing_votes.insert(session_index, minimum_backing_votes); } + pub(crate) fn node_features( + &mut self, + session_index: SessionIndex, + ) -> Option<&vstaging::NodeFeatures> { + self.node_features.get(&session_index).map(|f| &*f) + } + + pub(crate) fn cache_node_features( + &mut self, + session_index: SessionIndex, + features: vstaging::NodeFeatures, + ) { + self.node_features.insert(session_index, features); + } + pub(crate) fn disabled_validators( &mut self, relay_parent: &Hash, @@ -490,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 { @@ -537,7 +572,9 @@ pub(crate) enum RequestResult { slashing::OpaqueKeyOwnershipProof, Option<()>, ), + ApprovalVotingParams(Hash, SessionIndex, ApprovalVotingParams), DisabledValidators(Hash, Vec), ParaBackingState(Hash, ParaId, Option), AsyncBackingParams(Hash, async_backing::AsyncBackingParams), + NodeFeatures(SessionIndex, vstaging::NodeFeatures), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index bdcca08b10dda6fed122e482e5ac015beb2a29bb..3ff1a35d0687b6c6dc2d0ae70c96caeefc727c8f 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), @@ -173,6 +175,8 @@ where .cache_para_backing_state((relay_parent, para_id), constraints), AsyncBackingParams(relay_parent, params) => self.requests_cache.cache_async_backing_params(relay_parent, params), + NodeFeatures(session_index, params) => + self.requests_cache.cache_node_features(session_index, params), } } @@ -298,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) @@ -313,6 +320,15 @@ where Some(Request::MinimumBackingVotes(index, sender)) } }, + Request::NodeFeatures(index, sender) => { + if let Some(value) = self.requests_cache.node_features(index) { + self.metrics.on_cached_request(); + let _ = sender.send(Ok(value.clone())); + None + } else { + Some(Request::NodeFeatures(index, sender)) + } + }, } } @@ -408,12 +424,16 @@ where macro_rules! query { ($req_variant:ident, $api_name:ident ($($param:expr),*), ver = $version:expr, $sender:expr) => {{ + query!($req_variant, $api_name($($param),*), ver = $version, $sender, result = ( relay_parent $(, $param )* ) ) + }}; + ($req_variant:ident, $api_name:ident ($($param:expr),*), ver = $version:expr, $sender:expr, result = ( $($results:expr),* ) ) => {{ let sender = $sender; let version: u32 = $version; // enforce type for the version expression let runtime_version = client.api_version_parachain_host(relay_parent).await .unwrap_or_else(|e| { gum::warn!( target: LOG_TARGET, + api = ?stringify!($api_name), "cannot query the runtime API version: {}", e, ); @@ -441,7 +461,7 @@ where metrics.on_request(res.is_ok()); let _ = sender.send(res.clone()); - res.ok().map(|res| RequestResult::$req_variant(relay_parent, $( $param, )* res)) + res.ok().map(|res| RequestResult::$req_variant($( $results, )* res)) }} } @@ -557,6 +577,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), @@ -591,5 +619,12 @@ where sender ) }, + Request::NodeFeatures(index, sender) => query!( + NodeFeatures, + node_features(), + ver = Request::NODE_FEATURES_RUNTIME_REQUIREMENT, + sender, + result = (index) + ), } } diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index 979b3587d2692149d0d5927e80fea79f2618b0a0..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, 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()) } @@ -269,6 +279,10 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { todo!("Not required for tests") } + async fn node_features(&self, _: Hash) -> Result { + todo!("Not required for tests") + } + async fn disabled_validators(&self, _: Hash) -> Result, ApiError> { todo!("Not required for tests") } diff --git a/polkadot/node/gum/Cargo.toml b/polkadot/node/gum/Cargo.toml index 01ed34f7a7309e01dd7a836984ddc0871c2f894c..0d887b9be5394c6b36f882d60f57d4ee2d9bf8eb 100644 --- a/polkadot/node/gum/Cargo.toml +++ b/polkadot/node/gum/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "tracing-gum" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" -jaeger = { package = "polkadot-node-jaeger" , path = "../jaeger" } -gum-proc-macro = { package = "tracing-gum-proc-macro" , path = "proc-macro" } +gum-proc-macro = { package = "tracing-gum-proc-macro", path = "proc-macro" } polkadot-primitives = { path = "../../primitives", features = ["std"] } diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index 1ffaf6160ba2be444ab3ac6e2094c2b690fcde12..70126b4f43367ce11a1a23d462392b513ca1c028 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "tracing-gum-proc-macro" -version = "1.0.0" +version = "5.0.0" authors.workspace = true 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.38", features = ["full", "extra-traits"] } -quote = "1.0.28" +syn = { features = ["extra-traits", "full"], workspace = true } +quote = { workspace = true } proc-macro2 = "1.0.56" -proc-macro-crate = "1.1.3" +proc-macro-crate = "3.0.0" expander = "2.0.0" [dev-dependencies] diff --git a/polkadot/node/gum/src/lib.rs b/polkadot/node/gum/src/lib.rs index 1cc4d8dec1cbf7ad477e50edf4f57ac92de3c894..dad5887af224382ec384b4d354fd74fb2d9c31f5 100644 --- a/polkadot/node/gum/src/lib.rs +++ b/polkadot/node/gum/src/lib.rs @@ -105,8 +105,23 @@ pub use tracing::{enabled, event, Level}; -#[doc(hidden)] -pub use jaeger::hash_to_trace_identifier; +// jaeger dependency + +/// Alias for the 16 byte unique identifier used with jaeger. +pub(crate) type TraceIdentifier = u128; + +/// A helper to convert the hash to the fixed size representation +/// needed for jaeger. +#[inline] +pub fn hash_to_trace_identifier(hash: Hash) -> TraceIdentifier { + let mut buf = [0u8; 16]; + buf.copy_from_slice(&hash.as_ref()[0..16]); + // The slice bytes are copied in reading order, so if interpreted + // in string form by a human, that means lower indices have higher + // values and hence corresponds to BIG endian ordering of the individual + // bytes. + u128::from_be_bytes(buf) as TraceIdentifier +} #[doc(hidden)] pub use polkadot_primitives::{CandidateHash, Hash}; diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index fcfbbaec611ef22ae593ad05bcb058a2316a0e9b..23ab8f8421084751fa61d48bd3aa2b346e5a75c1 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -1,20 +1,23 @@ [package] name = "polkadot-node-jaeger" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" -parking_lot = "0.12.0" +parking_lot = "0.12.1" polkadot-primitives = { path = "../../primitives" } polkadot-node-primitives = { path = "../primitives" } sc-network = { path = "../../../substrate/client/network" } sp-core = { path = "../../../substrate/primitives/core" } -thiserror = "1.0.48" +thiserror = { workspace = true } tokio = "1.24.2" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", default-features = false } diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 9ce725f1682211d9b98166e50438e643c942d336..b3eb856f08efec851d9f19a07008e0d6853d81ae 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" @@ -26,7 +29,7 @@ path = "../../src/bin/prepare-worker.rs" doc = false [dependencies] -polkadot-cli = { path = "../../cli", features = [ "malus", "rococo-native", "westend-native" ] } +polkadot-cli = { path = "../../cli", features = ["malus", "rococo-native", "westend-native"] } polkadot-node-subsystem = { path = "../subsystem" } polkadot-node-subsystem-util = { path = "../subsystem-util" } polkadot-node-subsystem-types = { path = "../subsystem-types" } @@ -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.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } @@ -48,7 +51,7 @@ erasure = { package = "polkadot-erasure-coding", path = "../../erasure-coding" } rand = "0.8.5" # Required for worker binaries to build. -polkadot-node-core-pvf-common = { path = "../core/pvf/common", features = ["test-utils"] } +polkadot-node-core-pvf-common = { path = "../core/pvf/common" } polkadot-node-core-pvf-execute-worker = { path = "../core/pvf/execute-worker" } polkadot-node-core-pvf-prepare-worker = { path = "../core/pvf/prepare-worker" } @@ -62,4 +65,4 @@ substrate-build-script-utils = { path = "../../../substrate/utils/build-script-u [features] default = [] -fast-runtime = [ "polkadot-cli/fast-runtime" ] +fast-runtime = ["polkadot-cli/fast-runtime"] diff --git a/polkadot/node/malus/src/interceptor.rs b/polkadot/node/malus/src/interceptor.rs index 04ee0905deeb0758f222fce2cc6d3075b5a6000f..b44ffc8956b52581f0d27eeb7e794ffacc922fb7 100644 --- a/polkadot/node/malus/src/interceptor.rs +++ b/polkadot/node/malus/src/interceptor.rs @@ -21,8 +21,8 @@ //! messages on the overseer level. use polkadot_node_subsystem::*; -pub use polkadot_node_subsystem::{messages, messages::*, overseer, FromOrchestra}; -use std::{future::Future, pin::Pin}; +pub use polkadot_node_subsystem::{messages::*, overseer, FromOrchestra}; +use std::{collections::VecDeque, future::Future, pin::Pin}; /// Filter incoming and outgoing messages. pub trait MessageInterceptor: Send + Sync + Clone + 'static @@ -170,6 +170,7 @@ where inner: Context, message_filter: Fil, sender: InterceptedSender<::Sender, Fil>, + message_buffer: VecDeque::Message>>, } impl InterceptedContext @@ -189,7 +190,7 @@ where inner: inner.sender().clone(), message_filter: message_filter.clone(), }; - Self { inner, message_filter, sender } + Self { inner, message_filter, sender, message_buffer: VecDeque::new() } } } @@ -233,6 +234,9 @@ where } async fn recv(&mut self) -> SubsystemResult> { + if let Some(msg) = self.message_buffer.pop_front() { + return Ok(msg) + } loop { let msg = self.inner.recv().await?; if let Some(msg) = self.message_filter.intercept_incoming(self.inner.sender(), msg) { @@ -241,6 +245,19 @@ where } } + async fn recv_signal(&mut self) -> SubsystemResult { + loop { + let msg = self.inner.recv().await?; + if let Some(msg) = self.message_filter.intercept_incoming(self.inner.sender(), msg) { + if let FromOrchestra::Signal(sig) = msg { + return Ok(sig) + } else { + self.message_buffer.push_back(msg) + } + } + } + } + fn spawn( &mut self, name: &'static str, diff --git a/polkadot/node/malus/src/malus.rs b/polkadot/node/malus/src/malus.rs index 69dd7c869fc0d4720026998db36e59d063cb9bd8..7a9e320e27368e51da0508910fea5b7c54f2d6c7 100644 --- a/polkadot/node/malus/src/malus.rs +++ b/polkadot/node/malus/src/malus.rs @@ -32,10 +32,14 @@ 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. DisputeAncestor(DisputeAncestorOptions), + /// Delayed disputing of finalized candidates. + DisputeFinalizedCandidates(DisputeFinalizedCandidatesOptions), } #[derive(Debug, Parser)] @@ -66,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, @@ -80,6 +89,15 @@ impl MalusCli { finality_delay, )? }, + NemesisVariant::DisputeFinalizedCandidates(opts) => { + let DisputeFinalizedCandidatesOptions { dispute_offset, cli } = opts; + + polkadot_cli::run_node( + cli, + DisputeFinalizedCandidates { dispute_offset }, + finality_delay, + )? + }, } Ok(()) } @@ -184,4 +202,39 @@ mod tests { assert!(run.cli.run.base.bob); }); } + + #[test] + fn dispute_finalized_candidates_works() { + let cli = MalusCli::try_parse_from(IntoIterator::into_iter([ + "malus", + "dispute-finalized-candidates", + "--bob", + ])) + .unwrap(); + assert_matches::assert_matches!(cli, MalusCli { + variant: NemesisVariant::DisputeFinalizedCandidates(run), + .. + } => { + assert!(run.cli.run.base.bob); + }); + } + + #[test] + fn dispute_finalized_offset_value_works() { + let cli = MalusCli::try_parse_from(IntoIterator::into_iter([ + "malus", + "dispute-finalized-candidates", + "--dispute-offset", + "13", + "--bob", + ])) + .unwrap(); + assert_matches::assert_matches!(cli, MalusCli { + variant: NemesisVariant::DisputeFinalizedCandidates(opts), + .. + } => { + assert_eq!(opts.dispute_offset, 13); // This line checks that dispute_offset is correctly set to 13 + assert!(opts.cli.run.base.bob); + }); + } } diff --git a/polkadot/node/malus/src/variants/back_garbage_candidate.rs b/polkadot/node/malus/src/variants/back_garbage_candidate.rs index aa904c37b80a59c9a77dfdb682c3d0e00b7ff833..b939a2151e2359828da74d317c9e2bc0af2c6e0c 100644 --- a/polkadot/node/malus/src/variants/back_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/back_garbage_candidate.rs @@ -19,16 +19,14 @@ //! candidates. use polkadot_cli::{ - prepared_overseer_builder, service::{ - AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer, - OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, - ProvideRuntimeApi, + AuxStore, Error, ExtendedOverseerGenArgs, Overseer, OverseerConnector, OverseerGen, + OverseerGenArgs, OverseerHandle, }, - Cli, + validator_overseer_builder, Cli, }; use polkadot_node_subsystem::SpawnGlue; -use polkadot_node_subsystem_types::DefaultSubsystemClient; +use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient}; use sp_core::traits::SpawnNamed; use crate::{ @@ -63,13 +61,10 @@ impl OverseerGen for BackGarbageCandidates { &self, connector: OverseerConnector, args: OverseerGenArgs<'_, Spawner, RuntimeClient>, - ) -> Result< - (Overseer, Arc>>, OverseerHandle), - Error, - > + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { let spawner = args.spawner.clone(); @@ -80,11 +75,14 @@ impl OverseerGen for BackGarbageCandidates { SpawnGlue(spawner), ); - prepared_overseer_builder(args)? - .replace_candidate_validation(move |cv_subsystem| { - InterceptedSubsystem::new(cv_subsystem, validation_filter) - }) - .build_with_connector(connector) - .map_err(|e| e.into()) + validator_overseer_builder( + args, + ext_args.expect("Extended arguments required to build validator overseer are provided"), + )? + .replace_candidate_validation(move |cv_subsystem| { + InterceptedSubsystem::new(cv_subsystem, validation_filter) + }) + .build_with_connector(connector) + .map_err(|e| e.into()) } } diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs index 365b2f16ac21b67480b2dfc1c49b6092511c998d..eb6988f81811687442a36ae3f8ec225de48dd9df 100644 --- a/polkadot/node/malus/src/variants/common.rs +++ b/polkadot/node/malus/src/variants/common.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Implements common code for nemesis. Currently, only `FakeValidationResult` +//! Implements common code for nemesis. Currently, only `ReplaceValidationResult` //! interceptor is implemented. use crate::{ interceptor::*, @@ -23,14 +23,10 @@ use crate::{ use polkadot_node_core_candidate_validation::find_validation_data; use polkadot_node_primitives::{InvalidCandidate, ValidationResult}; -use polkadot_node_subsystem::{ - messages::{CandidateValidationMessage, ValidationFailed}, - overseer, -}; use polkadot_primitives::{ CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData, - PvfExecTimeoutKind, + PvfExecKind, }; use futures::channel::oneshot; @@ -90,10 +86,10 @@ impl FakeCandidateValidation { } } - fn should_misbehave(&self, timeout: PvfExecTimeoutKind) -> bool { + fn should_misbehave(&self, timeout: PvfExecKind) -> bool { match timeout { - PvfExecTimeoutKind::Backing => self.includes_backing(), - PvfExecTimeoutKind::Approval => self.includes_approval(), + PvfExecKind::Backing => self.includes_backing(), + PvfExecKind::Approval => self.includes_approval(), } } } @@ -273,30 +269,31 @@ where // Message sent by the approval voting subsystem FromOrchestra::Communication { msg: - CandidateValidationMessage::ValidateFromExhaustive( + CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, candidate_receipt, pov, executor_params, - timeout, - sender, - ), + exec_kind, + response_sender, + .. + }, } => { match self.fake_validation { - x if x.misbehaves_valid() && x.should_misbehave(timeout) => { + x if x.misbehaves_valid() && x.should_misbehave(exec_kind) => { // Behave normally if the `PoV` is not known to be malicious. if pov.block_data.0.as_slice() != MALICIOUS_POV { return Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromExhaustive( + msg: CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, candidate_receipt, pov, executor_params, - timeout, - sender, - ), + exec_kind, + response_sender, + }, }) } // Create the fake response with probability `p` if the `PoV` is malicious, @@ -313,7 +310,7 @@ where create_validation_response( validation_data, candidate_receipt.descriptor, - sender, + response_sender, ); None }, @@ -326,20 +323,20 @@ where ); Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromExhaustive( + msg: CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, candidate_receipt, pov, executor_params, - timeout, - sender, - ), + exec_kind, + response_sender, + }, }) }, } }, - x if x.misbehaves_invalid() && x.should_misbehave(timeout) => { + x if x.misbehaves_invalid() && x.should_misbehave(exec_kind) => { // Set the validation result to invalid with probability `p` and trigger a // dispute let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); @@ -358,7 +355,7 @@ where // We're not even checking the candidate, this makes us appear // faster than honest validators. - sender.send(Ok(validation_result)).unwrap(); + response_sender.send(Ok(validation_result)).unwrap(); None }, false => { @@ -366,56 +363,57 @@ where gum::info!(target: MALUS, "😈 'Decided' to not act maliciously.",); Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromExhaustive( + msg: CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, candidate_receipt, pov, executor_params, - timeout, - sender, - ), + exec_kind, + response_sender, + }, }) }, } }, // Handle FakeCandidateValidation::Disabled _ => Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromExhaustive( + msg: CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, candidate_receipt, pov, executor_params, - timeout, - sender, - ), + exec_kind, + response_sender, + }, }), } }, // Behaviour related to the backing subsystem FromOrchestra::Communication { msg: - CandidateValidationMessage::ValidateFromChainState( + CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, executor_params, - timeout, + exec_kind, response_sender, - ), + .. + }, } => { match self.fake_validation { - x if x.misbehaves_valid() && x.should_misbehave(timeout) => { + x if x.misbehaves_valid() && x.should_misbehave(exec_kind) => { // Behave normally if the `PoV` is not known to be malicious. if pov.block_data.0.as_slice() != MALICIOUS_POV { return Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState( + msg: CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, executor_params, - timeout, + exec_kind, response_sender, - ), + }, }) } // If the `PoV` is malicious, back the candidate with some probability `p`, @@ -439,17 +437,17 @@ where // If the `PoV` is malicious, we behave normally with some probability // `(1-p)` false => Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState( + msg: CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, executor_params, - timeout, + exec_kind, response_sender, - ), + }, }), } }, - x if x.misbehaves_invalid() && x.should_misbehave(timeout) => { + x if x.misbehaves_invalid() && x.should_misbehave(exec_kind) => { // Maliciously set the validation result to invalid for a valid candidate // with probability `p` let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); @@ -473,25 +471,25 @@ where gum::info!(target: MALUS, "😈 'Decided' to not act maliciously.",); Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState( + msg: CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, executor_params, - timeout, + exec_kind, response_sender, - ), + }, }) }, } }, _ => Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState( + msg: CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, executor_params, - timeout, + exec_kind, response_sender, - ), + }, }), } }, diff --git a/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a95bdaead26e204bd4cd8b386ae02b5e12297f9 --- /dev/null +++ b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs @@ -0,0 +1,263 @@ +// 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 malicious node variant that attempts to dispute finalized candidates. +//! +//! This malus variant behaves honestly in backing and approval voting. +//! The maliciousness comes from emitting an extra dispute statement on top of the other ones. +//! +//! Some extra quirks which generally should be insignificant: +//! - The malus node will not dispute at session boundaries +//! - The malus node will not dispute blocks it backed itself +//! - Be cautious about the size of the network to make sure disputes are not auto-confirmed +//! (7 validators is the smallest network size as it needs [(7-1)//3]+1 = 3 votes to get +//! confirmed but it only gets 1 from backing and 1 from malus so 2 in total) +//! +//! +//! Attention: For usage with `zombienet` only! + +#![allow(missing_docs)] + +use futures::channel::oneshot; +use polkadot_cli::{ + service::{ + AuxStore, Error, ExtendedOverseerGenArgs, Overseer, OverseerConnector, OverseerGen, + OverseerGenArgs, OverseerHandle, + }, + validator_overseer_builder, Cli, +}; +use polkadot_node_subsystem::SpawnGlue; +use polkadot_node_subsystem_types::{ChainApiBackend, OverseerSignal, RuntimeApiSubsystemClient}; +use polkadot_node_subsystem_util::request_candidate_events; +use polkadot_primitives::CandidateEvent; +use sp_core::traits::SpawnNamed; + +// Filter wrapping related types. +use crate::{interceptor::*, shared::MALUS}; + +use std::sync::Arc; + +/// Wraps around ApprovalVotingSubsystem and replaces it. +/// Listens to finalization messages and if possible triggers disputes for their ancestors. +#[derive(Clone)] +struct AncestorDisputer { + spawner: Spawner, //stores the actual ApprovalVotingSubsystem spawner + dispute_offset: u32, /* relative depth of the disputed block to the finalized block, + * 0=finalized, 1=parent of finalized etc */ +} + +impl MessageInterceptor for AncestorDisputer +where + Sender: overseer::ApprovalVotingSenderTrait + Clone + Send + 'static, + Spawner: overseer::gen::Spawner + Clone + 'static, +{ + type Message = ApprovalVotingMessage; + + /// Intercept incoming `OverseerSignal::BlockFinalized' and pass the rest as normal. + fn intercept_incoming( + &self, + subsystem_sender: &mut Sender, + msg: FromOrchestra, + ) -> Option> { + match msg { + FromOrchestra::Communication { msg } => Some(FromOrchestra::Communication { msg }), + FromOrchestra::Signal(OverseerSignal::BlockFinalized( + finalized_hash, + finalized_height, + )) => { + gum::debug!( + target: MALUS, + "😈 Block Finalization Interception! Block: {:?}", finalized_hash, + ); + + //Ensure that the chain is long enough for the target ancestor to exist + if finalized_height <= self.dispute_offset { + return Some(FromOrchestra::Signal(OverseerSignal::BlockFinalized( + finalized_hash, + finalized_height, + ))) + } + + let dispute_offset = self.dispute_offset; + let mut sender = subsystem_sender.clone(); + self.spawner.spawn_blocking( + "malus-dispute-finalized-block", + Some("malus"), + Box::pin(async move { + // Query chain for the block hash at the target depth + let (tx, rx) = oneshot::channel(); + sender + .send_message(ChainApiMessage::FinalizedBlockHash( + finalized_height - dispute_offset, + tx, + )) + .await; + let disputable_hash = match rx.await { + Ok(Ok(Some(hash))) => { + gum::debug!( + target: MALUS, + "😈 Time to search {:?}`th ancestor! Block: {:?}", dispute_offset, hash, + ); + hash + }, + _ => { + gum::debug!( + target: MALUS, + "😈 Seems the target is not yet finalized! Nothing to dispute." + ); + return // Early return from the async block + }, + }; + + // Fetch all candidate events for the target ancestor + let events = + request_candidate_events(disputable_hash, &mut sender).await.await; + let events = match events { + Ok(Ok(events)) => events, + Ok(Err(e)) => { + gum::error!( + target: MALUS, + "😈 Failed to fetch candidate events: {:?}", e + ); + return // Early return from the async block + }, + Err(e) => { + gum::error!( + target: MALUS, + "😈 Failed to fetch candidate events: {:?}", e + ); + return // Early return from the async block + }, + }; + + // Extract a token candidate from the events to use for disputing + let event = events.iter().find(|event| { + matches!(event, CandidateEvent::CandidateIncluded(_, _, _, _)) + }); + let candidate = match event { + Some(CandidateEvent::CandidateIncluded(candidate, _, _, _)) => + candidate, + _ => { + gum::error!( + target: MALUS, + "😈 No candidate included event found! Nothing to dispute." + ); + return // Early return from the async block + }, + }; + + // Extract the candidate hash from the candidate + let candidate_hash = candidate.hash(); + + // Fetch the session index for the candidate + let (tx, rx) = oneshot::channel(); + sender + .send_message(RuntimeApiMessage::Request( + disputable_hash, + RuntimeApiRequest::SessionIndexForChild(tx), + )) + .await; + let session_index = match rx.await { + Ok(Ok(session_index)) => session_index, + _ => { + gum::error!( + target: MALUS, + "😈 Failed to fetch session index for candidate." + ); + return // Early return from the async block + }, + }; + gum::info!( + target: MALUS, + "😈 Disputing candidate with hash: {:?} in session {:?}", candidate_hash, session_index, + ); + + // Start dispute + sender.send_unbounded_message( + DisputeCoordinatorMessage::IssueLocalStatement( + session_index, + candidate_hash, + candidate.clone(), + false, // indicates candidate is invalid -> dispute starts + ), + ); + }), + ); + + // Passthrough the finalization signal as usual (using it as hook only) + Some(FromOrchestra::Signal(OverseerSignal::BlockFinalized( + finalized_hash, + finalized_height, + ))) + }, + FromOrchestra::Signal(signal) => Some(FromOrchestra::Signal(signal)), + } + } +} + +//---------------------------------------------------------------------------------- + +#[derive(Debug, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct DisputeFinalizedCandidatesOptions { + /// relative depth of the disputed block to the finalized block, 0=finalized, 1=parent of + /// finalized etc + #[clap(long, ignore_case = true, default_value_t = 2, value_parser = clap::value_parser!(u32).range(0..=50))] + pub dispute_offset: u32, + + #[clap(flatten)] + pub cli: Cli, +} + +/// DisputeFinalizedCandidates implementation wrapper which implements `OverseerGen` glue. +pub(crate) struct DisputeFinalizedCandidates { + /// relative depth of the disputed block to the finalized block, 0=finalized, 1=parent of + /// finalized etc + pub dispute_offset: u32, +} + +impl OverseerGen for DisputeFinalizedCandidates { + fn generate( + &self, + connector: OverseerConnector, + args: OverseerGenArgs<'_, Spawner, RuntimeClient>, + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> + where + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin, + { + gum::info!( + target: MALUS, + "😈 Started Malus node that disputes finalized blocks after they are {:?} finalizations deep.", + &self.dispute_offset, + ); + + let ancestor_disputer = AncestorDisputer { + spawner: SpawnGlue(args.spawner.clone()), + dispute_offset: self.dispute_offset, + }; + + validator_overseer_builder( + args, + ext_args.expect("Extended arguments required to build validator overseer are provided"), + )? + .replace_approval_voting(move |cb| InterceptedSubsystem::new(cb, ancestor_disputer)) + .build_with_connector(connector) + .map_err(|e| e.into()) + } +} diff --git a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs index fa3b0c38bc2f456580fdc41822a47c9f395946a0..a50fdce16e4ec41d6b9bea4f0190616f756d9193 100644 --- a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs +++ b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs @@ -23,16 +23,14 @@ #![allow(missing_docs)] use polkadot_cli::{ - prepared_overseer_builder, service::{ - AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer, - OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, - ProvideRuntimeApi, + AuxStore, Error, ExtendedOverseerGenArgs, Overseer, OverseerConnector, OverseerGen, + OverseerGenArgs, OverseerHandle, }, - Cli, + validator_overseer_builder, Cli, }; use polkadot_node_subsystem::SpawnGlue; -use polkadot_node_subsystem_types::DefaultSubsystemClient; +use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient}; use sp_core::traits::SpawnNamed; // Filter wrapping related types. @@ -80,13 +78,10 @@ impl OverseerGen for DisputeValidCandidates { &self, connector: OverseerConnector, args: OverseerGenArgs<'_, Spawner, RuntimeClient>, - ) -> Result< - (Overseer, Arc>>, OverseerHandle), - Error, - > + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { let spawner = args.spawner.clone(); @@ -97,11 +92,14 @@ impl OverseerGen for DisputeValidCandidates { SpawnGlue(spawner.clone()), ); - prepared_overseer_builder(args)? - .replace_candidate_validation(move |cv_subsystem| { - InterceptedSubsystem::new(cv_subsystem, validation_filter) - }) - .build_with_connector(connector) - .map_err(|e| e.into()) + validator_overseer_builder( + args, + ext_args.expect("Extended arguments required to build validator overseer are provided"), + )? + .replace_candidate_validation(move |cv_subsystem| { + InterceptedSubsystem::new(cv_subsystem, validation_filter) + }) + .build_with_connector(connector) + .map_err(|e| e.into()) } } diff --git a/polkadot/node/malus/src/variants/mod.rs b/polkadot/node/malus/src/variants/mod.rs index 3789f33ac98be40d979e331dc67956a48711d4e4..3ca1bf4b4696843476c3de2b6dc441224b0ea9bd 100644 --- a/polkadot/node/malus/src/variants/mod.rs +++ b/polkadot/node/malus/src/variants/mod.rs @@ -18,12 +18,16 @@ mod back_garbage_candidate; 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 cf0ff5f809d8c31df07a13fb3eac0661f8486917..739ed40db362a2ae330f14b7a24cc77c1ab76159 100644 --- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs @@ -22,18 +22,16 @@ #![allow(missing_docs)] +use futures::channel::oneshot; use polkadot_cli::{ - prepared_overseer_builder, service::{ - AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer, - OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, - ProvideRuntimeApi, + AuxStore, Error, ExtendedOverseerGenArgs, Overseer, OverseerConnector, OverseerGen, + OverseerGenArgs, OverseerHandle, }, - Cli, + validator_overseer_builder, Cli, }; -use polkadot_node_core_candidate_validation::find_validation_data; use polkadot_node_primitives::{AvailableData, BlockData, PoV}; -use polkadot_node_subsystem_types::DefaultSubsystemClient; +use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{CandidateDescriptor, CandidateReceipt}; use polkadot_node_subsystem_util::request_validators; @@ -53,7 +51,7 @@ use crate::{ // Import extra types relevant to the particular // subsystem. -use polkadot_node_subsystem::{messages::CandidateBackingMessage, SpawnGlue}; +use polkadot_node_subsystem::SpawnGlue; use std::sync::Arc; @@ -83,7 +81,7 @@ where CandidateBackingMessage::Second( relay_parent, ref candidate, - ref _validation_data, + ref validation_data, ref _pov, ), } => { @@ -113,6 +111,7 @@ where let (sender, receiver) = std::sync::mpsc::channel(); let mut new_sender = subsystem_sender.clone(); let _candidate = candidate.clone(); + let validation_data = validation_data.clone(); self.spawner.spawn_blocking( "malus-get-validation-data", Some("malus"), @@ -125,22 +124,51 @@ where .unwrap() .len(); gum::trace!(target: MALUS, "Validators {}", n_validators); - match find_validation_data(&mut new_sender, &_candidate.descriptor()) - .await - { - Ok(Some((validation_data, validation_code))) => { - sender - .send(Some(( - validation_data, - validation_code, - n_validators, - ))) - .expect("channel is still open"); - }, - _ => { - sender.send(None).expect("channel is still open"); - }, - } + + let validation_code = { + let validation_code_hash = + _candidate.descriptor().validation_code_hash; + let (tx, rx) = oneshot::channel(); + new_sender + .send_message(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::ValidationCodeByHash( + validation_code_hash, + tx, + ), + )) + .await; + + let code = rx.await.expect("Querying the RuntimeApi should work"); + match code { + Err(e) => { + gum::error!( + target: MALUS, + ?validation_code_hash, + error = %e, + "Failed to fetch validation code", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(None) => { + gum::debug!( + target: MALUS, + ?validation_code_hash, + "Could not find validation code on chain", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(Some(c)) => c, + } + }; + + sender + .send(Some((validation_data, validation_code, n_validators))) + .expect("channel is still open"); }), ); @@ -266,13 +294,10 @@ impl OverseerGen for SuggestGarbageCandidates { &self, connector: OverseerConnector, args: OverseerGenArgs<'_, Spawner, RuntimeClient>, - ) -> Result< - (Overseer, Arc>>, OverseerHandle), - Error, - > + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { gum::info!( @@ -293,12 +318,13 @@ impl OverseerGen for SuggestGarbageCandidates { SpawnGlue(args.spawner.clone()), ); - prepared_overseer_builder(args)? - .replace_candidate_backing(move |cb| InterceptedSubsystem::new(cb, note_candidate)) - .replace_candidate_validation(move |cb| { - InterceptedSubsystem::new(cb, validation_filter) - }) - .build_with_connector(connector) - .map_err(|e| e.into()) + validator_overseer_builder( + args, + ext_args.expect("Extended arguments required to build validator overseer are provided"), + )? + .replace_candidate_backing(move |cb| InterceptedSubsystem::new(cb, note_candidate)) + .replace_candidate_validation(move |cb| InterceptedSubsystem::new(cb, validation_filter)) + .build_with_connector(connector) + .map_err(|e| e.into()) } } 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..169c442db25b7426f44cdfbe2c4181233802a914 --- /dev/null +++ b/polkadot/node/malus/src/variants/support_disabled.rs @@ -0,0 +1,96 @@ +// 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::{ + service::{ + AuxStore, Error, ExtendedOverseerGenArgs, Overseer, OverseerConnector, OverseerGen, + OverseerGenArgs, OverseerHandle, + }, + validator_overseer_builder, Cli, +}; +use polkadot_node_subsystem::SpawnGlue; +use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient}; +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>, + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> + where + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin, + { + validator_overseer_builder( + args, + ext_args.expect("Extended arguments required to build validator overseer are provided"), + )? + .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 e13ae63199ff0960af100a34451c14090b23e764..c567278f70ea79740510aea4099170179f7e1f85 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -1,17 +1,20 @@ [package] name = "polkadot-node-metrics" description = "Subsystem metric helpers" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } -metered = { package = "prioritized-metered-channel", version = "0.5.1", default-features = false, features=["futures_channel"] } +metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false, features = ["futures_channel"] } # Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`. sc-service = { path = "../../../substrate/client/service" } sc-cli = { path = "../../../substrate/client/cli" } @@ -21,18 +24,18 @@ sc-tracing = { path = "../../../substrate/client/tracing" } codec = { package = "parity-scale-codec", version = "3.6.1" } primitives = { package = "polkadot-primitives", path = "../../primitives" } bs58 = { version = "0.5.0", features = ["alloc"] } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.4" tempfile = "3.2.0" hyper = { version = "0.14.20", default-features = false, features = ["http1", "tcp"] } tokio = "1.24.2" -polkadot-test-service = { path = "../test/service", features=["runtime-metrics"]} +polkadot-test-service = { path = "../test/service", features = ["runtime-metrics"] } substrate-test-utils = { path = "../../../substrate/test-utils" } sc-service = { path = "../../../substrate/client/service" } sp-keyring = { path = "../../../substrate/primitives/keyring" } -prometheus-parse = {version = "0.2.2"} +prometheus-parse = { version = "0.2.2" } [features] default = [] diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index e19a1b83a62fe33b0db6989a3abadd18d768a29a..2bc09c5f42acad52a8a96dab3916ed6314f27d56 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-approval-distribution" -version = "1.0.0" +version = "7.0.0" +description = "Polkadot Approval Distribution subsystem for the distribution of assignments and approvals for approval checks on candidates over the network." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] polkadot-node-metrics = { path = "../../metrics" } polkadot-node-network-protocol = { path = "../protocol" } @@ -14,10 +18,12 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-primitives = { path = "../../../primitives" } polkadot-node-jaeger = { path = "../../jaeger" } rand = "0.8" +itertools = "0.10.5" futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } +bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } [dev-dependencies] sp-authority-discovery = { path = "../../../../substrate/primitives/authority-discovery" } @@ -27,8 +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 } -rand_core = "0.5.1" # should match schnorrkel +schnorrkel = { version = "0.11.4", default-features = false } +# rand_core should match schnorrkel +rand_core = "0.6.2" rand_chacha = "0.3.1" env_logger = "0.9.0" -log = "0.4.17" +log = { workspace = true, default-features = true } diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index d8949b1c1123c25c00d85e2e9cf9392a6710f552..f4e40270160cbf54cfeb516c5d7deeed59e7373a 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -23,17 +23,26 @@ #![warn(missing_docs)] +use self::metrics::Metrics; use futures::{channel::oneshot, select, FutureExt as _}; +use itertools::Itertools; +use net_protocol::peer_set::{ProtocolVersion, ValidationVersion}; use polkadot_node_jaeger as jaeger; use polkadot_node_network_protocol::{ - self as net_protocol, + self as net_protocol, filter_by_peer_version, grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology}, - peer_set::{ValidationVersion, MAX_NOTIFICATION_SIZE}, - v1 as protocol_v1, v2 as protocol_v2, PeerId, UnifiedReputationChange as Rep, Versioned, - VersionedValidationProtocol, View, + peer_set::MAX_NOTIFICATION_SIZE, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, PeerId, + UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_primitives::approval::{ - AssignmentCert, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote, + v1::{ + AssignmentCertKind, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote, + }, + v2::{ + AsBitIndex, AssignmentCertKindV2, CandidateBitfield, IndirectAssignmentCertV2, + IndirectSignedApprovalVoteV2, + }, }; use polkadot_node_subsystem::{ messages::{ @@ -52,8 +61,6 @@ use std::{ time::Duration, }; -use self::metrics::Metrics; - mod metrics; #[cfg(test)] @@ -67,11 +74,15 @@ const COST_DUPLICATE_MESSAGE: Rep = Rep::CostMinorRepeated("Peer sent identical const COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE: Rep = Rep::CostMinor("The vote was valid but too far in the future"); const COST_INVALID_MESSAGE: Rep = Rep::CostMajor("The vote was bad"); +const COST_OVERSIZED_BITFIELD: Rep = Rep::CostMajor("Oversized certificate or candidate bitfield"); const BENEFIT_VALID_MESSAGE: Rep = Rep::BenefitMinor("Peer sent a valid message"); const BENEFIT_VALID_MESSAGE_FIRST: Rep = Rep::BenefitMinorFirst("Valid message with new information"); +// Maximum valid size for the `CandidateBitfield` in the assignment messages. +const MAX_BITFIELD_SIZE: usize = 500; + /// The Approval Distribution subsystem. pub struct ApprovalDistribution { metrics: Metrics, @@ -100,7 +111,148 @@ impl RecentlyOutdated { } } -// In case the original gtid topology mechanisms don't work on their own, we need to trade bandwidth +// Contains topology routing information for assignments and approvals. +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 +// information. +struct ApprovalEntry { + // The assignment certificate. + assignment: IndirectAssignmentCertV2, + // The candidates claimed by the certificate. A mapping between bit index and candidate index. + assignment_claimed_candidates: CandidateBitfield, + // The approval signatures for each `CandidateIndex` claimed by the assignment certificate. + approvals: HashMap, + // The validator index of the assignment signer. + validator_index: ValidatorIndex, + // Information required for gossiping to other peers using the grid topology. + routing_info: ApprovalRouting, +} + +#[derive(Debug)] +enum ApprovalEntryError { + InvalidValidatorIndex, + CandidateIndexOutOfBounds, + InvalidCandidateIndex, + DuplicateApproval, + UnknownAssignment, + AssignmentsFollowedDifferentPaths(RequiredRouting, RequiredRouting), +} + +impl ApprovalEntry { + pub fn new( + assignment: IndirectAssignmentCertV2, + candidates: CandidateBitfield, + routing_info: ApprovalRouting, + ) -> ApprovalEntry { + Self { + validator_index: assignment.validator, + assignment, + approvals: HashMap::with_capacity(candidates.len()), + assignment_claimed_candidates: candidates, + routing_info, + } + } + + // Create a `MessageSubject` to reference the assignment. + pub fn create_assignment_knowledge(&self, block_hash: Hash) -> (MessageSubject, MessageKind) { + ( + MessageSubject( + block_hash, + self.assignment_claimed_candidates.clone(), + self.validator_index, + ), + MessageKind::Assignment, + ) + } + + // Updates routing information and returns the previous information if any. + pub fn routing_info_mut(&mut self) -> &mut ApprovalRouting { + &mut self.routing_info + } + + // Get the routing information. + pub fn routing_info(&self) -> &ApprovalRouting { + &self.routing_info + } + + // Update routing information. + pub fn update_required_routing(&mut self, required_routing: RequiredRouting) { + 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: IndirectSignedApprovalVoteV2, + ) -> Result<(), ApprovalEntryError> { + // First do some sanity checks: + // - check validator index matches + // - check claimed candidate + // - check for duplicate approval + if self.validator_index != approval.validator { + return Err(ApprovalEntryError::InvalidValidatorIndex) + } + + // 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_indices) { + return Err(ApprovalEntryError::DuplicateApproval) + } + + 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.assignment_claimed_candidates.clone()) + } + + // Get all approvals for all candidates claimed by the assignment. + pub fn approvals(&self) -> Vec { + self.approvals.values().cloned().collect::>() + } + + // Get validator index. + pub fn validator_index(&self) -> ValidatorIndex { + self.validator_index + } +} + +// We keep track of each peer view and protocol version using this struct. +struct PeerEntry { + pub view: View, + pub version: ProtocolVersion, +} + +// In case the original grid topology mechanisms don't work on their own, we need to trade bandwidth // for protocol liveliness by introducing aggression. // // Aggression has 3 levels: @@ -120,7 +272,6 @@ impl RecentlyOutdated { // not to all blocks older than the threshold. Most likely, a few assignments struggle to // be propagated in a single block and this holds up all of its descendants blocks. // Accordingly, we only step on the gas for the block which is most obviously holding up finality. - /// Aggression configuration representation #[derive(Clone)] struct AggressionConfig { @@ -135,12 +286,12 @@ struct AggressionConfig { } impl AggressionConfig { - /// Returns `true` if lag is past threshold depending on the aggression level - fn should_trigger_aggression(&self, approval_checking_lag: BlockNumber) -> bool { + /// Returns `true` if age is past threshold depending on the aggression level + fn should_trigger_aggression(&self, age: BlockNumber) -> bool { if let Some(t) = self.l1_threshold { - approval_checking_lag >= t + age >= t } else if let Some(t) = self.resend_unfinalized_period { - approval_checking_lag > 0 && approval_checking_lag % t == 0 + age > 0 && age % t == 0 } else { false } @@ -150,7 +301,7 @@ impl AggressionConfig { impl Default for AggressionConfig { fn default() -> Self { AggressionConfig { - l1_threshold: Some(13), + l1_threshold: Some(16), l2_threshold: Some(28), resend_unfinalized_period: Some(8), } @@ -163,15 +314,6 @@ enum Resend { No, } -/// Data stored on a per-peer basis. -#[derive(Debug)] -struct PeerData { - /// The peer's view. - view: View, - /// The peer's protocol version. - version: ValidationVersion, -} - /// The [`State`] struct is responsible for tracking the overall state of the subsystem. /// /// It tracks metadata about our view of the unfinalized chain, @@ -192,7 +334,7 @@ struct State { pending_known: HashMap>, /// Peer data is partially stored here, and partially inline within the [`BlockEntry`]s - peer_data: HashMap, + peer_views: HashMap, /// Keeps a topology for various different sessions. topologies: SessionGridTopologies, @@ -200,12 +342,12 @@ struct State { /// Tracks recently finalized blocks. recent_outdated_blocks: RecentlyOutdated, - /// Config for aggression. - aggression_config: AggressionConfig, - - /// `HashMap` from active leaves to spans + /// HashMap from active leaves to spans spans: HashMap, + /// Aggression configuration. + aggression_config: AggressionConfig, + /// Current approval checking finality lag. approval_checking_lag: BlockNumber, @@ -219,8 +361,11 @@ enum MessageKind { Approval, } +// Utility structure to identify assignments and approvals for specific candidates. +// Assignments can span multiple candidates, while approvals refer to only one candidate. +// #[derive(Debug, Clone, Hash, PartialEq, Eq)] -struct MessageSubject(Hash, CandidateIndex, ValidatorIndex); +struct MessageSubject(Hash, pub CandidateBitfield, ValidatorIndex); #[derive(Debug, Clone, Default)] struct Knowledge { @@ -241,9 +386,11 @@ impl Knowledge { } fn insert(&mut self, message: MessageSubject, kind: MessageKind) -> bool { - match self.known_messages.entry(message) { + let mut success = match self.known_messages.entry(message.clone()) { hash_map::Entry::Vacant(vacant) => { vacant.insert(kind); + // If there are multiple candidates assigned in the message, create + // separate entries for each one. true }, hash_map::Entry::Occupied(mut occupied) => match (*occupied.get(), kind) { @@ -255,7 +402,25 @@ impl Knowledge { true }, }, + }; + + // In case of succesful insertion of multiple candidate assignments create additional + // entries for each assigned candidate. This fakes knowledge of individual assignments, but + // we need to share the same `MessageSubject` with the followup approval candidate index. + if kind == MessageKind::Assignment && success && message.1.count_ones() > 1 { + for candidate_index in message.1.iter_ones() { + success = success && + self.insert( + MessageSubject( + message.0, + vec![candidate_index as u32].try_into().expect("Non-empty vec; qed"), + message.2, + ), + kind, + ); + } } + success } } @@ -272,6 +437,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. @@ -289,47 +489,154 @@ struct BlockEntry { candidates: Vec, /// The session index of this block. session: SessionIndex, + /// Approval entries for whole block. These also contain all approvals in the case of multiple + /// candidates being claimed by assignments. + approval_entries: HashMap<(ValidatorIndex, CandidateBitfield), ApprovalEntry>, } -#[derive(Debug)] -enum ApprovalState { - Assigned(AssignmentCert), - Approved(AssignmentCert, ValidatorSignature), -} +impl BlockEntry { + // Returns the peer which currently know this block. + pub fn known_by(&self) -> Vec { + self.known_by.keys().cloned().collect::>() + } -impl ApprovalState { - fn assignment_cert(&self) -> &AssignmentCert { - match *self { - ApprovalState::Assigned(ref cert) => cert, - ApprovalState::Approved(ref cert, _) => cert, + pub fn insert_approval_entry(&mut self, entry: ApprovalEntry) -> &mut ApprovalEntry { + // 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.assignment_claimed_candidates.iter_ones() { + match self.candidates.get_mut(claimed_candidate_index) { + Some(candidate_entry) => { + candidate_entry + .assignments + .entry(entry.validator_index()) + .or_insert(entry.assignment_claimed_candidates.clone()); + }, + None => { + // This should never happen, but if it happens, it means the subsystem is + // broken. + gum::warn!( + target: LOG_TARGET, + hash = ?entry.assignment.block_hash, + ?claimed_candidate_index, + "Missing candidate entry on `import_and_circulate_assignment`", + ); + }, + }; } + + self.approval_entries + .entry((entry.validator_index, entry.assignment_claimed_candidates.clone())) + .or_insert(entry) } - fn approval_signature(&self) -> Option { - match *self { - ApprovalState::Assigned(_) => None, - ApprovalState::Approved(_, ref sig) => Some(sig.clone()), + // 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, + 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(); + + // 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) + } + } + } + + if let Some(required_routing) = required_routing { + Ok((required_routing, peers_randomly_routed_to)) + } else { + Err(ApprovalEntryError::UnknownAssignment) } } -} -// routing state bundled with messages for the candidate. Corresponding assignments -// and approvals are stored together and should be routed in the same way, with -// assignments preceding approvals in all cases. -#[derive(Debug)] -struct MessageState { - required_routing: RequiredRouting, - local: bool, - random_routing: RandomRouting, - approval_state: ApprovalState, + /// 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. -/// In other words, multiple `CandidateEntry`s may exist for the same candidate, -/// if it is included by multiple blocks - this is likely the case when there are forks. +// Information about candidates in the context of a particular block they are included in. +// In other words, multiple `CandidateEntry`s may exist for the same candidate, +// if it is included by multiple blocks - this is likely the case when there are forks. #[derive(Debug, Default)] struct CandidateEntry { - messages: HashMap, + // The value represents part of the lookup key in `approval_entries` to fetch the assignment + // and existing votes. + assignments: HashMap, } #[derive(Debug, Clone, PartialEq)] @@ -348,8 +655,8 @@ impl MessageSource { } enum PendingMessage { - Assignment(IndirectAssignmentCert, CandidateIndex), - Approval(IndirectSignedApprovalVote), + Assignment(IndirectAssignmentCertV2, CandidateBitfield), + Approval(IndirectSignedApprovalVoteV2), } #[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)] @@ -362,30 +669,19 @@ impl State { rng: &mut (impl CryptoRng + Rng), ) { match event { - NetworkBridgeEvent::PeerConnected(peer_id, role, version, _) => { + NetworkBridgeEvent::PeerConnected(peer_id, role, version, authority_ids) => { + gum::trace!(target: LOG_TARGET, ?peer_id, ?role, ?authority_ids, "Peer connected"); + if let Some(authority_ids) = authority_ids { + self.topologies.update_authority_ids(peer_id, &authority_ids); + } // insert a blank view if none already present - gum::trace!(target: LOG_TARGET, ?peer_id, ?role, "Peer connected"); - let version = match ValidationVersion::try_from(version).ok() { - Some(v) => v, - None => { - // sanity: network bridge is supposed to detect this already. - gum::error!( - target: LOG_TARGET, - ?peer_id, - ?version, - "Unsupported protocol version" - ); - return - }, - }; - - self.peer_data + self.peer_views .entry(peer_id) - .or_insert_with(|| PeerData { version, view: Default::default() }); + .or_insert(PeerEntry { view: Default::default(), version }); }, NetworkBridgeEvent::PeerDisconnected(peer_id) => { gum::trace!(target: LOG_TARGET, ?peer_id, "Peer disconnected"); - self.peer_data.remove(&peer_id); + self.peer_views.remove(&peer_id); self.blocks.iter_mut().for_each(|(_hash, entry)| { entry.known_by.remove(&peer_id); }) @@ -422,11 +718,44 @@ impl State { live }); }, - NetworkBridgeEvent::PeerMessage(peer_id, msg) => { - self.process_incoming_peer_message(ctx, metrics, peer_id, msg, rng).await; + NetworkBridgeEvent::PeerMessage(peer_id, message) => { + self.process_incoming_peer_message(ctx, metrics, peer_id, message, rng).await; }, - NetworkBridgeEvent::UpdatedAuthorityIds { .. } => { - // The approval-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s. + NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids) => { + gum::debug!(target: LOG_TARGET, ?peer_id, ?authority_ids, "Update Authority Ids"); + // If we learn about a new PeerId for an authority ids we need to try to route the + // messages that should have sent to that validator according to the topology. + if self.topologies.update_authority_ids(peer_id, &authority_ids) { + if let Some(PeerEntry { view, version }) = self.peer_views.get(&peer_id) { + let intersection = self + .blocks_by_number + .iter() + .filter(|(block_number, _)| *block_number > &view.finalized_number) + .flat_map(|(_, hashes)| { + hashes.iter().filter(|hash| { + self.blocks + .get(&hash) + .map(|block| block.known_by.get(&peer_id).is_some()) + .unwrap_or_default() + }) + }); + let view_intersection = + View::new(intersection.cloned(), view.finalized_number); + Self::unify_with_peer( + ctx.sender(), + metrics, + &mut self.blocks, + &self.topologies, + self.peer_views.len(), + peer_id, + *version, + view_intersection, + rng, + true, + ) + .await; + } + } }, } } @@ -462,6 +791,7 @@ impl State { knowledge: Knowledge::default(), candidates, session: meta.session, + approval_entries: HashMap::new(), }); self.topologies.inc_session_refs(meta.session); @@ -484,20 +814,20 @@ impl State { { let sender = ctx.sender(); - for (peer_id, data) in self.peer_data.iter() { - let intersection = data.view.iter().filter(|h| new_hashes.contains(h)); - let view_intersection = - View::new(intersection.cloned(), data.view.finalized_number); + for (peer_id, PeerEntry { view, version }) in self.peer_views.iter() { + let intersection = view.iter().filter(|h| new_hashes.contains(h)); + let view_intersection = View::new(intersection.cloned(), view.finalized_number); Self::unify_with_peer( sender, metrics, &mut self.blocks, &self.topologies, - self.peer_data.len(), + self.peer_views.len(), *peer_id, - data.version, + *version, view_intersection, rng, + false, ) .await; } @@ -533,13 +863,13 @@ impl State { for (peer_id, message) in to_import { match message { - PendingMessage::Assignment(assignment, claimed_index) => { + PendingMessage::Assignment(assignment, claimed_indices) => { self.import_and_circulate_assignment( ctx, metrics, MessageSource::Peer(peer_id), assignment, - claimed_index, + claimed_indices, rng, ) .await; @@ -578,108 +908,174 @@ impl State { adjust_required_routing_and_propagate( ctx, - &self.peer_data, &mut self.blocks, &self.topologies, |block_entry| block_entry.session == session, |required_routing, local, validator_index| { - if *required_routing == RequiredRouting::PendingTopology { - *required_routing = topology + if required_routing == &RequiredRouting::PendingTopology { + topology .local_grid_neighbors() - .required_routing_by_index(*validator_index, local); + .required_routing_by_index(*validator_index, local) + } else { + *required_routing } }, + &self.peer_views, ) .await; } + async fn process_incoming_assignments( + &mut self, + ctx: &mut Context, + metrics: &Metrics, + peer_id: PeerId, + assignments: Vec<(IndirectAssignmentCertV2, CandidateBitfield)>, + rng: &mut R, + ) where + R: CryptoRng + Rng, + { + for (assignment, claimed_indices) in assignments { + if let Some(pending) = self.pending_known.get_mut(&assignment.block_hash) { + let block_hash = &assignment.block_hash; + let validator_index = assignment.validator; + + gum::trace!( + target: LOG_TARGET, + %peer_id, + ?block_hash, + ?claimed_indices, + ?validator_index, + "Pending assignment", + ); + + pending.push((peer_id, PendingMessage::Assignment(assignment, claimed_indices))); + + continue + } + + self.import_and_circulate_assignment( + ctx, + metrics, + MessageSource::Peer(peer_id), + assignment, + claimed_indices, + rng, + ) + .await; + } + } + + // 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, metrics: &Metrics, peer_id: PeerId, - msg: net_protocol::ApprovalDistributionMessage, + msg: Versioned< + protocol_v1::ApprovalDistributionMessage, + protocol_v2::ApprovalDistributionMessage, + protocol_v3::ApprovalDistributionMessage, + >, rng: &mut R, ) where R: CryptoRng + Rng, { match msg { - Versioned::V1(protocol_v1::ApprovalDistributionMessage::Assignments(assignments)) | - Versioned::V2(protocol_v2::ApprovalDistributionMessage::Assignments(assignments)) => { + Versioned::V3(protocol_v3::ApprovalDistributionMessage::Assignments(assignments)) => { gum::trace!( target: LOG_TARGET, peer_id = %peer_id, num = assignments.len(), "Processing assignments from a peer", ); - for (assignment, claimed_index) in assignments.into_iter() { - if let Some(pending) = self.pending_known.get_mut(&assignment.block_hash) { - let message_subject = MessageSubject( - assignment.block_hash, - claimed_index, - assignment.validator, - ); + let sanitized_assignments = + self.sanitize_v2_assignments(peer_id, ctx.sender(), assignments).await; - gum::trace!( - target: LOG_TARGET, - %peer_id, - ?message_subject, - "Pending assignment", - ); - - pending - .push((peer_id, PendingMessage::Assignment(assignment, claimed_index))); - - continue - } - - self.import_and_circulate_assignment( - ctx, - metrics, - MessageSource::Peer(peer_id), - assignment, - claimed_index, - rng, - ) - .await; - } + self.process_incoming_assignments( + ctx, + metrics, + peer_id, + sanitized_assignments, + rng, + ) + .await; }, - Versioned::V1(protocol_v1::ApprovalDistributionMessage::Approvals(approvals)) | - Versioned::V2(protocol_v2::ApprovalDistributionMessage::Approvals(approvals)) => { + Versioned::V1(protocol_v1::ApprovalDistributionMessage::Assignments(assignments)) | + Versioned::V2(protocol_v2::ApprovalDistributionMessage::Assignments(assignments)) => { gum::trace!( target: LOG_TARGET, peer_id = %peer_id, - num = approvals.len(), - "Processing approvals from a peer", + num = assignments.len(), + "Processing assignments from a peer", ); - for approval_vote in approvals.into_iter() { - if let Some(pending) = self.pending_known.get_mut(&approval_vote.block_hash) { - let message_subject = MessageSubject( - approval_vote.block_hash, - approval_vote.candidate_index, - approval_vote.validator, - ); - - gum::trace!( - target: LOG_TARGET, - %peer_id, - ?message_subject, - "Pending approval", - ); - - pending.push((peer_id, PendingMessage::Approval(approval_vote))); - continue - } + let sanitized_assignments = + self.sanitize_v1_assignments(peer_id, ctx.sender(), assignments).await; - self.import_and_circulate_approval( - ctx, - metrics, - MessageSource::Peer(peer_id), - approval_vote, - ) + self.process_incoming_assignments( + ctx, + metrics, + peer_id, + sanitized_assignments, + rng, + ) + .await; + }, + Versioned::V3(protocol_v3::ApprovalDistributionMessage::Approvals(approvals)) => { + let sanitized_approvals = + self.sanitize_v2_approvals(peer_id, ctx.sender(), approvals).await; + self.process_incoming_approvals(ctx, metrics, peer_id, sanitized_approvals) + .await; + }, + Versioned::V1(protocol_v1::ApprovalDistributionMessage::Approvals(approvals)) | + Versioned::V2(protocol_v2::ApprovalDistributionMessage::Approvals(approvals)) => { + let sanitized_approvals = + self.sanitize_v1_approvals(peer_id, ctx.sender(), approvals).await; + self.process_incoming_approvals(ctx, metrics, peer_id, sanitized_approvals) .await; - } }, } } @@ -698,14 +1094,23 @@ impl State { { gum::trace!(target: LOG_TARGET, ?view, "Peer view change"); let finalized_number = view.finalized_number; - let (peer_protocol_version, old_finalized_number) = match self - .peer_data - .get_mut(&peer_id) - .map(|d| (d.version, std::mem::replace(&mut d.view, view.clone()))) - { - Some((v, view)) => (v, view.finalized_number), - None => return, // unknown peer - }; + + let (old_view, protocol_version) = + if let Some(peer_entry) = self.peer_views.get_mut(&peer_id) { + (Some(std::mem::replace(&mut peer_entry.view, view.clone())), peer_entry.version) + } else { + // This shouldn't happen, but if it does we assume protocol version 1. + gum::warn!( + target: LOG_TARGET, + ?peer_id, + ?view, + "Peer view change for missing `peer_entry`" + ); + + (None, ValidationVersion::V1.into()) + }; + + let old_finalized_number = old_view.map(|v| v.finalized_number).unwrap_or(0); // we want to prune every block known_by peer up to (including) view.finalized_number let blocks = &mut self.blocks; @@ -730,11 +1135,12 @@ impl State { metrics, &mut self.blocks, &self.topologies, - self.peer_data.len(), + self.peer_views.len(), peer_id, - peer_protocol_version, + protocol_version, view, rng, + false, ) .await; } @@ -773,8 +1179,8 @@ impl State { ctx: &mut Context, metrics: &Metrics, source: MessageSource, - assignment: IndirectAssignmentCert, - claimed_candidate_index: CandidateIndex, + assignment: IndirectAssignmentCertV2, + claimed_candidate_indices: CandidateBitfield, rng: &mut R, ) where R: CryptoRng + Rng, @@ -816,15 +1222,20 @@ 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 }, }; - // compute metadata on the assignment. - let message_subject = MessageSubject(block_hash, claimed_candidate_index, validator_index); - let message_kind = MessageKind::Assignment; + // Compute metadata on the assignment. + let (message_subject, message_kind) = ( + MessageSubject(block_hash, claimed_candidate_indices.clone(), validator_index), + MessageKind::Assignment, + ); if let Some(peer_id) = source.peer_id() { // check if our knowledge of the peer already contains this assignment @@ -840,6 +1251,7 @@ impl State { ?message_subject, "Duplicate assignment", ); + modify_reputation( &mut self.reputation, ctx.sender(), @@ -847,6 +1259,16 @@ impl State { COST_DUPLICATE_MESSAGE, ) .await; + metrics.on_assignment_duplicate(); + } else { + gum::trace!( + target: LOG_TARGET, + ?peer_id, + hash = ?block_hash, + ?validator_index, + ?message_subject, + "We sent the message to the peer while peer was sending it to us. Known race condition.", + ); } return } @@ -865,6 +1287,7 @@ impl State { COST_UNEXPECTED_MESSAGE, ) .await; + metrics.on_assignment_out_of_view(); }, } @@ -881,6 +1304,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 } @@ -888,7 +1312,7 @@ impl State { ctx.send_message(ApprovalVotingMessage::CheckAndImportAssignment( assignment.clone(), - claimed_candidate_index, + claimed_candidate_indices.clone(), tx, )) .await; @@ -919,7 +1343,7 @@ impl State { BENEFIT_VALID_MESSAGE_FIRST, ) .await; - entry.knowledge.known_messages.insert(message_subject.clone(), message_kind); + entry.knowledge.insert(message_subject.clone(), message_kind); if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) { peer_knowledge.received.insert(message_subject.clone(), message_kind); } @@ -937,6 +1361,8 @@ impl State { ?peer_id, "Got an `AcceptedDuplicate` assignment", ); + metrics.on_assignment_duplicatevoting(); + return }, AssignmentCheckResult::TooFarInFuture => { @@ -953,6 +1379,8 @@ impl State { COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE, ) .await; + metrics.on_assignment_far(); + return }, AssignmentCheckResult::Bad(error) => { @@ -970,6 +1398,7 @@ impl State { COST_INVALID_MESSAGE, ) .await; + metrics.on_assignment_bad(); return }, } @@ -993,7 +1422,7 @@ impl State { // Invariant: to our knowledge, none of the peers except for the `source` know about the // assignment. - metrics.on_assignment_imported(); + metrics.on_assignment_imported(&assignment.cert.kind); let topology = self.topologies.get_topology(entry.session); let local = source == MessageSource::Local; @@ -1002,28 +1431,19 @@ impl State { t.local_grid_neighbors().required_routing_by_index(validator_index, local) }); - let message_state = match entry.candidates.get_mut(claimed_candidate_index as usize) { - Some(candidate_entry) => { - // set the approval state for validator_index to Assigned - // unless the approval state is set already - candidate_entry.messages.entry(validator_index).or_insert_with(|| MessageState { - required_routing, - local, - random_routing: Default::default(), - approval_state: ApprovalState::Assigned(assignment.cert.clone()), - }) - }, - None => { - gum::warn!( - target: LOG_TARGET, - hash = ?block_hash, - ?claimed_candidate_index, - "Expected a candidate entry on import_and_circulate_assignment", - ); - - return + // All the peers that know the relay chain block. + let peers_to_filter = entry.known_by(); + + let approval_entry = entry.insert_approval_entry(ApprovalEntry::new( + assignment.clone(), + claimed_candidate_indices.clone(), + ApprovalRouting { + required_routing, + local, + random_routing: Default::default(), + peers_randomly_routed: Default::default(), }, - }; + )); // Dispatch the message to all peers in the routing set which // know the block. @@ -1031,83 +1451,151 @@ impl State { // If the topology isn't known yet (race with networking subsystems) // then messages will be sent when we get it. - let assignments = vec![(assignment, claimed_candidate_index)]; - let n_peers_total = self.peer_data.len(); + let assignments = vec![(assignment, claimed_candidate_indices.clone())]; + let n_peers_total = self.peer_views.len(); let source_peer = source.peer_id(); - let mut peer_filter = move |peer| { - if Some(peer) == source_peer.as_ref() { - return false + // Peers that we will send the assignment to. + let mut peers = Vec::new(); + + // Filter destination peers + for peer in peers_to_filter.into_iter() { + if Some(peer) == source_peer { + continue } if let Some(true) = topology .as_ref() - .map(|t| t.local_grid_neighbors().route_to_peer(required_routing, peer)) + .map(|t| t.local_grid_neighbors().route_to_peer(required_routing, &peer)) { - return true + peers.push(peer); + 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. - let route_random = message_state.random_routing.sample(n_peers_total, rng); + let route_random = + approval_entry.routing_info().random_routing.sample(n_peers_total, rng); if route_random { - message_state.random_routing.inc_sent(); + approval_entry.routing_info_mut().mark_randomly_sent(peer); + peers.push(peer); } + } - route_random - }; + // Add the metadata of the assignment to the knowledge of each peer. + for peer in peers.iter() { + // we already filtered peers above, so this should always be Some + if let Some(peer_knowledge) = entry.known_by.get_mut(peer) { + peer_knowledge.sent.insert(message_subject.clone(), message_kind); + } + } - let (v1_peers, v2_peers) = { - let peer_data = &self.peer_data; - let peers = entry - .known_by - .keys() - .filter_map(|p| peer_data.get_key_value(p)) - .filter(|(p, _)| peer_filter(p)) - .map(|(p, peer_data)| (*p, peer_data.version)) + if !peers.is_empty() { + gum::trace!( + target: LOG_TARGET, + ?block_hash, + ?claimed_candidate_indices, + local = source.peer_id().is_none(), + num_peers = peers.len(), + "Sending an assignment to peers", + ); + + let peers = peers + .iter() + .filter_map(|peer_id| { + self.peer_views.get(peer_id).map(|peer_entry| (*peer_id, peer_entry.version)) + }) .collect::>(); - // Add the metadata of the assignment to the knowledge of each peer. - for (peer, _) in peers.iter() { - // we already filtered peers above, so this should always be Some - if let Some(peer_knowledge) = entry.known_by.get_mut(peer) { - peer_knowledge.sent.insert(message_subject.clone(), message_kind); - } - } + send_assignments_batched(ctx.sender(), assignments, &peers).await; + } + } - if !peers.is_empty() { + // 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, - ?block_hash, - ?claimed_candidate_index, - local = source.peer_id().is_none(), - num_peers = peers.len(), - "Sending an assignment to peers", + ?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 } + } - let v1_peers = filter_peers_by_version(&peers, ValidationVersion::V1); - let v2_peers = filter_peers_by_version(&peers, ValidationVersion::V2); - - (v1_peers, v2_peers) - }; + // 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", + ); - if !v1_peers.is_empty() { - ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - v1_peers, - versioned_assignments_packet(ValidationVersion::V1, assignments.clone()), - )) - .await; + 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 !v2_peers.is_empty() { - ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - v2_peers, - versioned_assignments_packet(ValidationVersion::V2, assignments.clone()), - )) - .await; + 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 } } @@ -1116,7 +1604,7 @@ impl State { ctx: &mut Context, metrics: &Metrics, source: MessageSource, - vote: IndirectSignedApprovalVote, + vote: IndirectSignedApprovalVoteV2, ) { let _span = self .spans @@ -1135,13 +1623,20 @@ 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) { + gum::debug!( + target: LOG_TARGET, + ?peer_id, + ?block_hash, + ?validator_index, + ?candidate_indices, + "Approval from a peer is out of view", + ); modify_reputation( &mut self.reputation, ctx.sender(), @@ -1149,6 +1644,9 @@ impl State { COST_UNEXPECTED_MESSAGE, ) .await; + metrics.on_approval_invalid_block(); + } else { + metrics.on_approval_recent_outdated(); } } return @@ -1156,81 +1654,21 @@ impl State { }; // compute metadata on the assignment. - let message_subject = MessageSubject(block_hash, candidate_index, 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 } @@ -1252,8 +1690,8 @@ impl State { gum::trace!( target: LOG_TARGET, ?peer_id, - ?message_subject, ?result, + ?vote, "Checked approval", ); match result { @@ -1266,9 +1704,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) => { @@ -1285,97 +1727,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", ); } } - // Invariant: to our knowledge, none of the peers except for the `source` know about the - // approval. - metrics.on_approval_imported(); - - let required_routing = match entry.candidates.get_mut(candidate_index as usize) { - Some(candidate_entry) => { - // set the approval state for validator_index to Approved - // it should be in assigned state already - match candidate_entry.messages.remove(&validator_index) { - Some(MessageState { - approval_state: ApprovalState::Assigned(cert), - required_routing, - local, - random_routing, - }) => { - candidate_entry.messages.insert( - validator_index, - MessageState { - approval_state: ApprovalState::Approved( - cert, - vote.signature.clone(), - ), - required_routing, - local, - random_routing, - }, - ); - - required_routing - }, - Some(_) => { - unreachable!( - "we only insert it after the metadata, checked the metadata above; qed" - ); - }, - None => { - // this would indicate a bug in approval-voting - gum::warn!( - target: LOG_TARGET, - hash = ?block_hash, - ?candidate_index, - ?validator_index, - "Importing an approval we don't have an assignment for", - ); - - return - }, - } - }, - None => { + 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, hash = ?block_hash, - ?candidate_index, - ?validator_index, - "Expected a candidate entry on import_and_circulate_approval", + validator_index = ?vote.validator, + candidate_bitfield = ?vote.candidate_indices, + ?err, + "Possible bug: Vote import failed", ); - + 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 } @@ -1391,60 +1791,34 @@ 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 (v1_peers, v2_peers) = { - let peer_data = &self.peer_data; - let peers = entry - .known_by - .iter() - .filter_map(|(p, k)| peer_data.get(&p).map(|pd| (p, k, pd.version))) - .filter(|(p, k, _)| peer_filter(p, k)) - .map(|(p, _, v)| (*p, v)) - .collect::>(); - - // Add the metadata of the assignment to the knowledge of each peer. - for (peer, _) in peers.iter() { - // we already filtered peers above, so this should always be Some - if let Some(peer_knowledge) = entry.known_by.get_mut(peer) { - peer_knowledge.sent.insert(message_subject.clone(), message_kind); - } + let peers = entry + .known_by + .iter() + .filter(|(p, _)| peer_filter(p)) + .filter_map(|(p, _)| self.peer_views.get(p).map(|entry| (*p, entry.version))) + .collect::>(); + + // Add the metadata of the assignment to the knowledge of each peer. + 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(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1); } - - if !peers.is_empty() { - gum::trace!( - target: LOG_TARGET, - ?block_hash, - ?candidate_index, - local = source.peer_id().is_none(), - num_peers = peers.len(), - "Sending an approval to peers", - ); - } - - let v1_peers = filter_peers_by_version(&peers, ValidationVersion::V1); - let v2_peers = filter_peers_by_version(&peers, ValidationVersion::V2); - - (v1_peers, v2_peers) - }; - - let approvals = vec![vote]; - - if !v1_peers.is_empty() { - ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - v1_peers, - versioned_approvals_packet(ValidationVersion::V1, approvals.clone()), - )) - .await; } - if !v2_peers.is_empty() { - ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - v2_peers, - versioned_approvals_packet(ValidationVersion::V2, approvals), - )) - .await; + if !peers.is_empty() { + let approvals = vec![vote]; + gum::trace!( + target: LOG_TARGET, + ?block_hash, + local = source.peer_id().is_none(), + num_peers = peers.len(), + "Sending an approval to peers", + ); + send_approvals_batched(ctx.sender(), approvals, &peers).await; } } @@ -1452,7 +1826,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 @@ -1475,25 +1849,20 @@ impl State { Some(e) => e, }; - let candidate_entry = match block_entry.candidates.get(index as usize) { - None => { - gum::debug!( - target: LOG_TARGET, - ?hash, - ?index, - "`get_approval_signatures`: could not find candidate entry for given hash and index!" - ); - continue - }, - Some(e) => e, - }; - let sigs = - candidate_entry.messages.iter().filter_map(|(validator_index, message_state)| { - match &message_state.approval_state { - ApprovalState::Approved(_, sig) => Some((*validator_index, sig.clone())), - ApprovalState::Assigned(_) => None, - } - }); + 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 @@ -1506,9 +1875,10 @@ impl State { topologies: &SessionGridTopologies, total_peers: usize, peer_id: PeerId, - peer_protocol_version: ValidationVersion, + protocol_version: ProtocolVersion, view: View, rng: &mut (impl CryptoRng + Rng), + retry_known_blocks: bool, ) { metrics.on_unify_with_peer(); let _timer = metrics.time_unify_with_peer(); @@ -1519,42 +1889,51 @@ impl State { let view_finalized_number = view.finalized_number; for head in view.into_iter() { let mut block = head; + + // Walk the chain back to last finalized block of the peer view. loop { let entry = match entries.get_mut(&block) { Some(entry) if entry.number > view_finalized_number => entry, _ => break, }; - // Any peer which is in the `known_by` set has already been - // sent all messages it's meant to get for that block and all - // in-scope prior blocks. - if entry.known_by.contains_key(&peer_id) { + // Any peer which is in the `known_by` see and we know its peer_id authorithy id + // mapping has already been sent all messages it's meant to get for that block and + // all in-scope prior blocks. In case, we just learnt about its peer_id + // authorithy-id mapping we have to retry sending the messages that should be sent + // to it for all un-finalized blocks. + if entry.known_by.contains_key(&peer_id) && !retry_known_blocks { break } let peer_knowledge = entry.known_by.entry(peer_id).or_default(); - let topology = topologies.get_topology(entry.session); - // Iterate all messages in all candidates. - for (candidate_index, validator, message_state) in - entry.candidates.iter_mut().enumerate().flat_map(|(c_i, c)| { - c.messages.iter_mut().map(move |(k, v)| (c_i as _, k, v)) - }) { + // 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 random_routing = &mut message_state.random_routing; - let required_routing = message_state.required_routing; + let required_routing = approval_entry.routing_info().required_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 @@ -1566,40 +1945,26 @@ impl State { } } - let message_subject = MessageSubject(block, candidate_index, *validator); + let assignment_message = approval_entry.assignment(); + let approval_messages = approval_entry.approvals(); + let (assignment_knowledge, message_kind) = + approval_entry.create_assignment_knowledge(block); - let assignment_message = ( - IndirectAssignmentCert { - block_hash: block, - validator: *validator, - cert: message_state.approval_state.assignment_cert().clone(), - }, - candidate_index, - ); - - let approval_message = - message_state.approval_state.approval_signature().map(|signature| { - IndirectSignedApprovalVote { - block_hash: block, - validator: *validator, - candidate_index, - signature, - } - }); - - if !peer_knowledge.contains(&message_subject, MessageKind::Assignment) { - peer_knowledge - .sent - .insert(message_subject.clone(), MessageKind::Assignment); + // 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); } - if let Some(approval_message) = approval_message { - if !peer_knowledge.contains(&message_subject, MessageKind::Approval) { - peer_knowledge - .sent - .insert(message_subject.clone(), MessageKind::Approval); + // Filter approval votes. + for approval_message in approval_messages { + let approval_knowledge = + PeerKnowledge::generate_approval_key(&approval_message); + + 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); } } } @@ -1612,26 +1977,43 @@ impl State { gum::trace!( target: LOG_TARGET, ?peer_id, + ?protocol_version, num = assignments_to_send.len(), "Sending assignments to unified peer", ); - send_assignments_batched(sender, assignments_to_send, peer_id, peer_protocol_version) - .await; + send_assignments_batched( + sender, + assignments_to_send, + &vec![(peer_id, protocol_version)], + ) + .await; } if !approvals_to_send.is_empty() { gum::trace!( target: LOG_TARGET, ?peer_id, + ?protocol_version, num = approvals_to_send.len(), "Sending approvals to unified peer", ); - send_approvals_batched(sender, approvals_to_send, peer_id, peer_protocol_version).await; + send_approvals_batched(sender, approvals_to_send, &vec![(peer_id, protocol_version)]) + .await; } } + // It is very important that aggression starts with oldest unfinalized block, rather than oldest + // unapproved block. Using the gossip approach to distribute potentially + // missing votes to validators requires that we always trigger on finality lag, even if + // we have have the approval lag value. The reason for this, is to avoid finality stall + // when more than 1/3 nodes go offline for a period o time. When they come back + // there wouldn't get any of the approvals since the on-line nodes would never trigger + // aggression as they have approved all the candidates and don't detect any approval lag. + // + // In order to switch to using approval lag as a trigger we need a request/response protocol + // to fetch votes from validators rather than use gossip. async fn enable_aggression( &mut self, ctx: &mut Context, @@ -1639,32 +2021,31 @@ impl State { metrics: &Metrics, ) { let config = self.aggression_config.clone(); + let min_age = self.blocks_by_number.iter().next().map(|(num, _)| num); + let max_age = self.blocks_by_number.iter().rev().next().map(|(num, _)| num); - if !self.aggression_config.should_trigger_aggression(self.approval_checking_lag) { + // Return if we don't have at least 1 block. + let (min_age, max_age) = match (min_age, max_age) { + (Some(min), Some(max)) => (*min, *max), + _ => return, // empty. + }; + + let age = max_age.saturating_sub(min_age); + + // Trigger on approval checking lag. + if !self.aggression_config.should_trigger_aggression(age) { gum::trace!( target: LOG_TARGET, approval_checking_lag = self.approval_checking_lag, + age, "Aggression not enabled", ); return } - - let max_age = self.blocks_by_number.iter().rev().next().map(|(num, _)| num); - - let max_age = match max_age { - Some(max) => *max, - _ => return, // empty. - }; - - // Since we have the approval checking lag, we need to set the `min_age` accordingly to - // enable aggresion for the oldest block that is not approved. - let min_age = max_age.saturating_sub(self.approval_checking_lag); - gum::debug!(target: LOG_TARGET, min_age, max_age, "Aggression enabled",); adjust_required_routing_and_propagate( ctx, - &self.peer_data, &mut self.blocks, &self.topologies, |block_entry| { @@ -1686,13 +2067,13 @@ impl State { false } }, - |_, _, _| {}, + |required_routing, _, _| *required_routing, + &self.peer_views, ) .await; adjust_required_routing_and_propagate( ctx, - &self.peer_data, &mut self.blocks, &self.topologies, |block_entry| { @@ -1711,30 +2092,166 @@ impl State { lag = ?self.approval_checking_lag, "Encountered old block pending gossip topology", ); - return + return *required_routing } - if config.l1_threshold.as_ref().map_or(false, |t| &self.approval_checking_lag >= t) - { + let mut new_required_routing = *required_routing; + + if config.l1_threshold.as_ref().map_or(false, |t| &age >= t) { // Message originator sends to everyone. - if local && *required_routing != RequiredRouting::All { + if local && new_required_routing != RequiredRouting::All { metrics.on_aggression_l1(); - *required_routing = RequiredRouting::All; + new_required_routing = RequiredRouting::All; } } - if config.l2_threshold.as_ref().map_or(false, |t| &self.approval_checking_lag >= t) - { + if config.l2_threshold.as_ref().map_or(false, |t| &age >= t) { // Message originator sends to everyone. Everyone else sends to XY. - if !local && *required_routing != RequiredRouting::GridXY { + if !local && new_required_routing != RequiredRouting::GridXY { metrics.on_aggression_l2(); - *required_routing = RequiredRouting::GridXY; + new_required_routing = RequiredRouting::GridXY; } } + new_required_routing }, + &self.peer_views, ) .await; } + + // Filter out invalid candidate index and certificate core bitfields. + // For each invalid assignment we also punish the peer. + async fn sanitize_v1_assignments( + &mut self, + peer_id: PeerId, + sender: &mut impl overseer::ApprovalDistributionSenderTrait, + assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>, + ) -> Vec<(IndirectAssignmentCertV2, CandidateBitfield)> { + let mut sanitized_assignments = Vec::new(); + for (cert, candidate_index) in assignments.into_iter() { + let cert_bitfield_bits = match cert.cert.kind { + AssignmentCertKind::RelayVRFDelay { core_index } => core_index.0 as usize + 1, + // We don't want to run the VRF yet, but the output is always bounded by `n_cores`. + // We assume `candidate_bitfield` length for the core bitfield and we just check + // against `MAX_BITFIELD_SIZE` later. + AssignmentCertKind::RelayVRFModulo { .. } => candidate_index as usize + 1, + }; + + let candidate_bitfield_bits = candidate_index as usize + 1; + + // Ensure bitfields length under hard limit. + if cert_bitfield_bits > MAX_BITFIELD_SIZE || candidate_bitfield_bits > MAX_BITFIELD_SIZE + { + // Punish the peer for the invalid message. + modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD) + .await; + gum::debug!(target: LOG_TARGET, block_hash = ?cert.block_hash, ?candidate_index, validator_index = ?cert.validator, kind = ?cert.cert.kind, "Bad assignment v1, invalid candidate index"); + } else { + sanitized_assignments.push((cert.into(), candidate_index.into())) + } + } + + sanitized_assignments + } + + // Filter out oversized candidate and certificate core bitfields. + // For each invalid assignment we also punish the peer. + async fn sanitize_v2_assignments( + &mut self, + peer_id: PeerId, + sender: &mut impl overseer::ApprovalDistributionSenderTrait, + assignments: Vec<(IndirectAssignmentCertV2, CandidateBitfield)>, + ) -> Vec<(IndirectAssignmentCertV2, CandidateBitfield)> { + let mut sanitized_assignments = Vec::new(); + for (cert, candidate_bitfield) in assignments.into_iter() { + let cert_bitfield_bits = match &cert.cert.kind { + AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.0 as usize + 1, + // We don't want to run the VRF yet, but the output is always bounded by `n_cores`. + // We assume `candidate_bitfield` length for the core bitfield and we just check + // against `MAX_BITFIELD_SIZE` later. + AssignmentCertKindV2::RelayVRFModulo { .. } => candidate_bitfield.len(), + AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => + core_bitfield.len(), + }; + + let candidate_bitfield_bits = candidate_bitfield.len(); + + // Our bitfield has `Lsb0`. + let msb = candidate_bitfield_bits - 1; + + // Ensure bitfields length under hard limit. + if cert_bitfield_bits > MAX_BITFIELD_SIZE + || candidate_bitfield_bits > MAX_BITFIELD_SIZE + // Ensure minimum bitfield size - MSB needs to be one. + || !candidate_bitfield.bit_at(msb.as_bit_index()) + { + // 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::debug!(target: LOG_TARGET, block_hash = ?cert.block_hash, ?candidate_index, validator_index = ?cert.validator, "Bad assignment v2, oversized bitfield"); + } + } else { + sanitized_assignments.push((cert, candidate_bitfield)) + } + } + + sanitized_assignments + } + + // Filter out obviously invalid candidate indicies. + async fn sanitize_v1_approvals( + &mut self, + peer_id: PeerId, + sender: &mut impl overseer::ApprovalDistributionSenderTrait, + approval: Vec, + ) -> Vec { + let mut sanitized_approvals = Vec::new(); + for approval in approval.into_iter() { + if approval.candidate_index as usize > MAX_BITFIELD_SIZE { + // Punish the peer for the invalid message. + modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD) + .await; + gum::debug!( + target: LOG_TARGET, + block_hash = ?approval.block_hash, + candidate_index = ?approval.candidate_index, + "Bad approval v1, invalid candidate index" + ); + } else { + sanitized_approvals.push(approval.into()) + } + } + + sanitized_approvals + } + + // Filter out obviously invalid candidate indicies. + async fn sanitize_v2_approvals( + &mut self, + peer_id: PeerId, + sender: &mut impl overseer::ApprovalDistributionSenderTrait, + approval: Vec, + ) -> Vec { + let mut sanitized_approvals = Vec::new(); + for approval in approval.into_iter() { + if approval.candidate_indices.len() as usize > MAX_BITFIELD_SIZE { + // Punish the peer for the invalid message. + modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD) + .await; + gum::debug!( + target: LOG_TARGET, + block_hash = ?approval.block_hash, + candidate_indices_len = ?approval.candidate_indices.len(), + "Bad approval v2, invalid candidate indices size" + ); + } else { + sanitized_approvals.push(approval) + } + } + + sanitized_approvals + } } // This adjusts the required routing of messages in blocks that pass the block filter @@ -1752,14 +2269,14 @@ impl State { #[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)] async fn adjust_required_routing_and_propagate( ctx: &mut Context, - peer_data: &HashMap, blocks: &mut HashMap, topologies: &SessionGridTopologies, block_filter: BlockFilter, routing_modifier: RoutingModifier, + peer_views: &HashMap, ) where BlockFilter: Fn(&mut BlockEntry) -> bool, - RoutingModifier: Fn(&mut RequiredRouting, bool, &ValidatorIndex), + RoutingModifier: Fn(&RequiredRouting, bool, &ValidatorIndex) -> RequiredRouting, { let mut peer_assignments = HashMap::new(); let mut peer_approvals = HashMap::new(); @@ -1771,64 +2288,54 @@ async fn adjust_required_routing_and_propagate t, + None => continue, + }; - if message_state.required_routing.is_empty() { + // We just need to iterate the `approval_entries` of the block entry as these contain all + // assignments that also link all approval votes. + for approval_entry in block_entry.approval_entries.values_mut() { + let new_required_routing = routing_modifier( + &approval_entry.routing_info().required_routing, + approval_entry.routing_info().local, + &approval_entry.validator_index(), + ); + + approval_entry.update_required_routing(new_required_routing); + + if approval_entry.routing_info().required_routing.is_empty() { continue } - let topology = match topologies.get_topology(block_entry.session) { - Some(t) => t, - None => continue, - }; - - // Propagate the message to all peers in the required routing set. - let message_subject = MessageSubject(*block_hash, candidate_index, *validator); - - let assignment_message = ( - IndirectAssignmentCert { - block_hash: *block_hash, - validator: *validator, - cert: message_state.approval_state.assignment_cert().clone(), - }, - candidate_index, - ); - let approval_message = - message_state.approval_state.approval_signature().map(|signature| { - IndirectSignedApprovalVote { - block_hash: *block_hash, - validator: *validator, - candidate_index, - signature, - } - }); + let assignment_message = approval_entry.assignment(); + let approval_messages = approval_entry.approvals(); + let (assignment_knowledge, message_kind) = + approval_entry.create_assignment_knowledge(*block_hash); for (peer, peer_knowledge) in &mut block_entry.known_by { if !topology .local_grid_neighbors() - .route_to_peer(message_state.required_routing, peer) + .route_to_peer(approval_entry.routing_info().required_routing, peer) { continue } - if !peer_knowledge.contains(&message_subject, MessageKind::Assignment) { - peer_knowledge.sent.insert(message_subject.clone(), MessageKind::Assignment); + // 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.clone(), message_kind); peer_assignments .entry(*peer) .or_insert_with(Vec::new) .push(assignment_message.clone()); } - if let Some(approval_message) = approval_message.as_ref() { - if !peer_knowledge.contains(&message_subject, MessageKind::Approval) { - peer_knowledge.sent.insert(message_subject.clone(), MessageKind::Approval); + // Filter approval votes. + for approval_message in &approval_messages { + let approval_knowledge = PeerKnowledge::generate_approval_key(approval_message); + + if !peer_knowledge.contains(&approval_knowledge.0, approval_knowledge.1) { + peer_knowledge.sent.insert(approval_knowledge.0, approval_knowledge.1); peer_approvals .entry(*peer) .or_insert_with(Vec::new) @@ -1840,24 +2347,32 @@ async fn adjust_required_routing_and_propagate continue, - Some(v) => v, - }; - - send_assignments_batched(ctx.sender(), assignments_packet, peer, peer_protocol_version) + if let Some(peer_view) = peer_views.get(&peer) { + send_assignments_batched( + ctx.sender(), + assignments_packet, + &vec![(peer, peer_view.version)], + ) .await; + } else { + // This should never happen. + gum::warn!(target: LOG_TARGET, ?peer, "Unknown protocol version for peer",); + } } for (peer, approvals_packet) in peer_approvals { - let peer_protocol_version = match peer_data.get(&peer).map(|pd| pd.version) { - None => continue, - Some(v) => v, - }; - - send_approvals_batched(ctx.sender(), approvals_packet, peer, peer_protocol_version).await; + if let Some(peer_view) = peer_views.get(&peer) { + send_approvals_batched( + ctx.sender(), + approvals_packet, + &vec![(peer, peer_view.version)], + ) + .await; + } else { + // This should never happen. + gum::warn!(target: LOG_TARGET, ?peer, "Unknown protocol version for peer",); + } } } @@ -1959,12 +2474,21 @@ impl ApprovalDistribution { ApprovalDistributionMessage::NewBlocks(metas) => { state.handle_new_blocks(ctx, metrics, metas, rng).await; }, - ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index) => { + ApprovalDistributionMessage::DistributeAssignment(cert, candidate_indices) => { + let _span = state + .spans + .get(&cert.block_hash) + .map(|span| span.child("import-and-distribute-assignment")) + .unwrap_or_else(|| jaeger::Span::new(&cert.block_hash, "distribute-assignment")) + .with_string_tag("block-hash", format!("{:?}", cert.block_hash)) + .with_stage(jaeger::Stage::ApprovalDistribution); + gum::debug!( target: LOG_TARGET, - "Distributing our assignment on candidate (block={}, index={})", - cert.block_hash, - candidate_index, + ?candidate_indices, + block_hash = ?cert.block_hash, + assignment_kind = ?cert.cert.kind, + "Distributing our assignment on candidates", ); state @@ -1973,7 +2497,7 @@ impl ApprovalDistribution { &metrics, MessageSource::Local, cert, - candidate_index, + candidate_indices, rng, ) .await; @@ -1981,9 +2505,9 @@ impl ApprovalDistribution { ApprovalDistributionMessage::DistributeApproval(vote) => { 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 @@ -2007,49 +2531,6 @@ impl ApprovalDistribution { } } -fn versioned_approvals_packet( - version: ValidationVersion, - approvals: Vec, -) -> VersionedValidationProtocol { - match version { - ValidationVersion::V1 => - Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( - protocol_v1::ApprovalDistributionMessage::Approvals(approvals), - )), - ValidationVersion::V2 => - Versioned::V2(protocol_v2::ValidationProtocol::ApprovalDistribution( - protocol_v2::ApprovalDistributionMessage::Approvals(approvals), - )), - } -} - -fn versioned_assignments_packet( - version: ValidationVersion, - assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>, -) -> VersionedValidationProtocol { - match version { - ValidationVersion::V1 => - Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( - protocol_v1::ApprovalDistributionMessage::Assignments(assignments), - )), - ValidationVersion::V2 => - Versioned::V2(protocol_v2::ValidationProtocol::ApprovalDistribution( - protocol_v2::ApprovalDistributionMessage::Assignments(assignments), - )), - } -} - -fn filter_peers_by_version( - peers: &[(PeerId, ValidationVersion)], - version: ValidationVersion, -) -> Vec { - peers - .iter() - .filter(|(_, v)| v == &version) - .map(|(peer_id, _)| *peer_id) - .collect() -} - #[overseer::subsystem(ApprovalDistribution, error=SubsystemError, prefix=self::overseer)] impl ApprovalDistribution { fn start(self, ctx: Context) -> SpawnedSubsystem { @@ -2074,15 +2555,66 @@ const fn ensure_size_not_zero(size: usize) -> usize { /// the protocol configuration. pub const MAX_ASSIGNMENT_BATCH_SIZE: usize = ensure_size_not_zero( MAX_NOTIFICATION_SIZE as usize / - std::mem::size_of::<(IndirectAssignmentCert, CandidateIndex)>() / + std::mem::size_of::<(IndirectAssignmentCertV2, CandidateIndex)>() / 3, ); /// 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. +async fn send_assignments_batched_inner( + sender: &mut impl overseer::ApprovalDistributionSenderTrait, + batch: impl IntoIterator, + peers: Vec, + peer_version: ValidationVersion, +) { + if peer_version == ValidationVersion::V3 { + sender + .send_message(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments( + batch.into_iter().collect(), + ), + )), + )) + .await; + } else { + // Create a batch of v1 assignments from v2 assignments that are compatible with v1. + // `IndirectAssignmentCertV2` -> `IndirectAssignmentCert` + let batch = batch + .into_iter() + .filter_map(|(cert, candidates)| { + cert.try_into().ok().map(|cert| { + ( + cert, + // First 1 bit index is the candidate index. + candidates + .first_one() + .map(|index| index as CandidateIndex) + .expect("Assignment was checked for not being empty; qed"), + ) + }) + }) + .collect(); + let message = if peer_version == ValidationVersion::V1 { + Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( + protocol_v1::ApprovalDistributionMessage::Assignments(batch), + )) + } else { + Versioned::V2(protocol_v2::ValidationProtocol::ApprovalDistribution( + protocol_v2::ApprovalDistributionMessage::Assignments(batch), + )) + }; + sender + .send_message(NetworkBridgeTxMessage::SendValidationMessage(peers, message)) + .await; + } +} + /// Send assignments while honoring the `max_notification_size` of the protocol. /// /// Splitting the messages into multiple notifications allows more granular processing at the @@ -2090,37 +2622,119 @@ pub const MAX_APPROVAL_BATCH_SIZE: usize = ensure_size_not_zero( /// of assignments and can `select!` other tasks. pub(crate) async fn send_assignments_batched( sender: &mut impl overseer::ApprovalDistributionSenderTrait, - assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>, - peer: PeerId, - protocol_version: ValidationVersion, + v2_assignments: impl IntoIterator + Clone, + peers: &[(PeerId, ProtocolVersion)], ) { - let mut batches = assignments.into_iter().peekable(); + let v1_peers = filter_by_peer_version(peers, ValidationVersion::V1.into()); + let v2_peers = filter_by_peer_version(peers, ValidationVersion::V2.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. + if !v1_peers.is_empty() || !v2_peers.is_empty() { + // Older peers(v1) do not understand `AssignmentsV2` messages, so we have to filter these + // out. + let v1_assignments = v2_assignments + .clone() + .into_iter() + .filter(|(_, candidates)| candidates.count_ones() == 1); + + let mut v1_batches = v1_assignments.peekable(); + + while v1_batches.peek().is_some() { + let batch: Vec<_> = v1_batches.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect(); + if !v1_peers.is_empty() { + send_assignments_batched_inner( + sender, + batch.clone(), + v1_peers.clone(), + ValidationVersion::V1, + ) + .await; + } + + if !v2_peers.is_empty() { + send_assignments_batched_inner( + sender, + batch, + v2_peers.clone(), + ValidationVersion::V2, + ) + .await; + } + } + } - while batches.peek().is_some() { - let batch: Vec<_> = batches.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect(); - let versioned = versioned_assignments_packet(protocol_version, batch); + if !v3_peers.is_empty() { + let mut v3 = v2_assignments.into_iter().peekable(); - sender - .send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned)) - .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; + } } } -/// Send approvals while honoring the `max_notification_size` of the protocol. +/// 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: Vec, - peer: PeerId, - protocol_version: ValidationVersion, + approvals: impl IntoIterator + Clone, + peers: &[(PeerId, ProtocolVersion)], ) { - let mut batches = approvals.into_iter().peekable(); + let v1_peers = filter_by_peer_version(peers, ValidationVersion::V1.into()); + let v2_peers = filter_by_peer_version(peers, ValidationVersion::V2.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() + .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(); + + if !v1_peers.is_empty() { + sender + .send_message(NetworkBridgeTxMessage::SendValidationMessage( + v1_peers.clone(), + Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( + protocol_v1::ApprovalDistributionMessage::Approvals(batch.clone()), + )), + )) + .await; + } - while batches.peek().is_some() { - let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect(); - let versioned = versioned_approvals_packet(protocol_version, batch); + if !v2_peers.is_empty() { + sender + .send_message(NetworkBridgeTxMessage::SendValidationMessage( + v2_peers.clone(), + Versioned::V2(protocol_v2::ValidationProtocol::ApprovalDistribution( + protocol_v2::ApprovalDistributionMessage::Approvals(batch), + )), + )) + .await; + } + } + } - sender - .send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned)) - .await; + if !v3_peers.is_empty() { + let mut batches = approvals.into_iter().peekable(); + + while batches.peek().is_some() { + let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect(); + + sender + .send_message(NetworkBridgeTxMessage::SendValidationMessage( + 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 896866ce099a18c993540877a73144fc194a9a81..0642b1b2e0cdcea51d4a34263bad87ad6612f035 100644 --- a/polkadot/node/network/approval-distribution/src/metrics.rs +++ b/polkadot/node/network/approval-distribution/src/metrics.rs @@ -15,6 +15,7 @@ // along with Polkadot. If not, see . use polkadot_node_metrics::metrics::{prometheus, Metrics as MetricsTrait}; +use polkadot_node_primitives::approval::v2::AssignmentCertKindV2; /// Approval Distribution metrics. #[derive(Default, Clone)] @@ -22,21 +23,36 @@ pub struct Metrics(Option); #[derive(Clone)] struct MetricsInner { - assignments_imported_total: prometheus::Counter, + assignments_imported_total: prometheus::CounterVec, approvals_imported_total: prometheus::Counter, unified_with_peer_total: prometheus::Counter, aggression_l1_messages_total: prometheus::Counter, aggression_l2_messages_total: prometheus::Counter, - 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 { + fn as_label(&self) -> &str; +} + +impl AsLabel for &AssignmentCertKindV2 { + fn as_label(&self) -> &str { + match self { + AssignmentCertKindV2::RelayVRFDelay { .. } => "VRF Delay", + AssignmentCertKindV2::RelayVRFModulo { .. } => "VRF Modulo", + AssignmentCertKindV2::RelayVRFModuloCompact { .. } => "VRF Modulo Compact", + } + } } impl Metrics { - pub(crate) fn on_assignment_imported(&self) { + pub(crate) fn on_assignment_imported(&self, kind: &AssignmentCertKindV2) { if let Some(metrics) = &self.0 { - metrics.assignments_imported_total.inc(); + metrics.assignments_imported_total.with_label_values(&[kind.as_label()]).inc(); } } @@ -64,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 { @@ -89,9 +231,12 @@ impl MetricsTrait for Metrics { fn try_register(registry: &prometheus::Registry) -> Result { let metrics = MetricsInner { assignments_imported_total: prometheus::register( - prometheus::Counter::new( - "polkadot_parachain_assignments_imported_total", - "Number of valid assignments imported locally or from other peers.", + prometheus::CounterVec::new( + prometheus::Opts::new( + "polkadot_parachain_assignments_imported_total", + "Number of valid assignments imported locally or from other peers.", + ), + &["kind"], )?, registry, )?, @@ -124,10 +269,16 @@ impl MetricsTrait for Metrics { registry, )?, time_unify_with_peer: prometheus::register( - prometheus::Histogram::with_opts(prometheus::HistogramOpts::new( - "polkadot_parachain_time_unify_with_peer", - "Time spent within fn `unify_with_peer`.", - ).buckets(vec![0.000625, 0.00125,0.0025, 0.005, 0.0075, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,]))?, + prometheus::Histogram::with_opts( + prometheus::HistogramOpts::new( + "polkadot_parachain_time_unify_with_peer", + "Time spent within fn `unify_with_peer`.", + ) + .buckets(vec![ + 0.000625, 0.00125, 0.0025, 0.005, 0.0075, 0.01, 0.025, 0.05, 0.1, 0.25, + 0.5, 1.0, 2.5, 5.0, 10.0, + ]), + )?, registry, )?, time_import_pending_now_known: prometheus::register( @@ -144,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 29c7d8aa45daa1f88ec6737c448e9e2d8a493bb5..6c88dd53ad364ab0eabed065c9098c1dfcdd81d9 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -24,20 +24,26 @@ use polkadot_node_network_protocol::{ view, ObservedRole, }; use polkadot_node_primitives::approval::{ - AssignmentCertKind, VrfOutput, VrfProof, VrfSignature, RELAY_VRF_MODULO_CONTEXT, + v1::{ + AssignmentCert, AssignmentCertKind, IndirectAssignmentCert, IndirectSignedApprovalVote, + VrfPreOutput, VrfProof, VrfSignature, + }, + v2::{ + AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, IndirectAssignmentCertV2, + RELAY_VRF_MODULO_CONTEXT, + }, }; use polkadot_node_subsystem::messages::{ network_bridge_event, AllMessages, ApprovalCheckError, ReportPeerMessage, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt as _}; -use polkadot_primitives::{AuthorityDiscoveryId, BlakeTwo256, HashT}; +use polkadot_primitives::{AuthorityDiscoveryId, BlakeTwo256, CoreIndex, HashT}; use polkadot_primitives_test_helpers::dummy_signature; use rand::SeedableRng; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_core::crypto::Pair as PairT; use std::time::Duration; - type VirtualOverseer = test_helpers::TestSubsystemContextHandle; fn test_harness>( @@ -124,17 +130,16 @@ fn make_peers_and_authority_ids(n: usize) -> Vec<(PeerId, AuthorityDiscoveryId)> fn make_gossip_topology( session: SessionIndex, - all_peers: &[(PeerId, AuthorityDiscoveryId)], + all_peers: &[(Option, 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(), @@ -148,7 +153,7 @@ fn make_gossip_topology( assert!(all_peers.len() >= grid_size); let peer_info = |i: usize| TopologyPeerInfo { - peer_ids: vec![all_peers[i].0], + peer_ids: all_peers[i].0.into_iter().collect_vec(), validator_index: ValidatorIndex::from(i as u32), discovery_id: all_peers[i].1.clone(), }; @@ -219,15 +224,15 @@ async fn setup_gossip_topology( async fn setup_peer_with_view( virtual_overseer: &mut VirtualOverseer, peer_id: &PeerId, - validation_version: ValidationVersion, view: View, + version: ValidationVersion, ) { overseer_send( virtual_overseer, ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( *peer_id, ObservedRole::Full, - validation_version.into(), + version.into(), None, )), ) @@ -244,12 +249,43 @@ async fn setup_peer_with_view( async fn send_message_from_peer( virtual_overseer: &mut VirtualOverseer, peer_id: &PeerId, - msg: net_protocol::ApprovalDistributionMessage, + msg: protocol_v1::ApprovalDistributionMessage, +) { + overseer_send( + virtual_overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( + *peer_id, + Versioned::V1(msg), + )), + ) + .await; +} + +async fn send_message_from_peer_v2( + virtual_overseer: &mut VirtualOverseer, + peer_id: &PeerId, + msg: protocol_v2::ApprovalDistributionMessage, +) { + overseer_send( + virtual_overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( + *peer_id, + Versioned::V2(msg), + )), + ) + .await; +} + +async fn send_message_from_peer_v3( + virtual_overseer: &mut VirtualOverseer, + peer_id: &PeerId, + msg: protocol_v3::ApprovalDistributionMessage, ) { overseer_send( virtual_overseer, ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( - *peer_id, msg, + *peer_id, + Versioned::V3(msg), )), ) .await; @@ -261,14 +297,36 @@ 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) }, + }, + } +} + +fn fake_assignment_cert_v2( + block_hash: Hash, + validator: ValidatorIndex, + core_bitfield: CoreBitfield, +) -> IndirectAssignmentCertV2 { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"WhenParachains?"; + 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(); + + IndirectAssignmentCertV2 { + block_hash, + validator, + cert: AssignmentCertV2 { + kind: AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, }, } } @@ -321,19 +379,32 @@ 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); 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, ValidationVersion::V1, view![]).await; - setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await; - setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V1).await; + 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. + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0, 1], &[2, 4], 3), + ) + .await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -353,7 +424,7 @@ fn try_import_the_same_assignment() { let assignments = vec![(cert.clone(), 0u32)]; let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer(overseer, &peer_a, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peer_a, msg).await; expect_reputation_change(overseer, &peer_a, COST_UNEXPECTED_MESSAGE).await; @@ -362,10 +433,11 @@ fn try_import_the_same_assignment() { overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( assignment, - 0u32, + claimed_indices, tx, )) => { - assert_eq!(assignment, cert); + assert_eq!(claimed_indices, 0u32.into()); + assert_eq!(assignment, cert.into()); tx.send(AssignmentCheckResult::Accepted).unwrap(); } ); @@ -385,12 +457,117 @@ fn try_import_the_same_assignment() { } ); - // setup new peer - setup_peer_with_view(overseer, &peer_d, ValidationVersion::V1, view![]).await; + // setup new peer with V2 + 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); - send_message_from_peer(overseer, &peer_d, Versioned::V1(msg)).await; + send_message_from_peer(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; + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent"); + virtual_overseer + }); +} + +/// Just like `try_import_the_same_assignment` but use `VRFModuloCompact` assignments for multiple +/// cores. +#[test] +fn try_import_the_same_assignment_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 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::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. + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0, 1], &[2, 4], 3), + ) + .await; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 2, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // send the assignment related to `hash` + let validator_index = ValidatorIndex(0); + let cores = vec![1, 2, 3, 4]; + let core_bitfield: CoreBitfield = cores + .iter() + .map(|index| CoreIndex(*index)) + .collect::>() + .try_into() + .unwrap(); + + 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_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; + + // send an `Accept` message from the Approval Voting subsystem + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + assignment, + claimed_indices, + tx, + )) => { + assert_eq!(claimed_indices, cores.try_into().unwrap()); + assert_eq!(assignment, cert.into()); + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_a, 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_eq!(peers.len(), 2); + assert_eq!(assignments.len(), 1); + } + ); + + // setup new peer + setup_peer_with_view(overseer, &peer_d, view![], ValidationVersion::V3).await; + + // send the same assignment from peer_d + 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; @@ -413,7 +590,7 @@ fn delay_reputation_change() { let overseer = &mut virtual_overseer; // Setup peers - setup_peer_with_view(overseer, &peer, ValidationVersion::V1, view![]).await; + setup_peer_with_view(overseer, &peer, view![], ValidationVersion::V1).await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -433,17 +610,18 @@ fn delay_reputation_change() { let assignments = vec![(cert.clone(), 0u32)]; let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer(overseer, &peer, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peer, msg).await; // send an `Accept` message from the Approval Voting subsystem assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( assignment, - 0u32, + claimed_candidates, tx, )) => { - assert_eq!(assignment, cert); + assert_eq!(assignment.cert, cert.cert.into()); + assert_eq!(claimed_candidates, vec![0u32].try_into().unwrap()); tx.send(AssignmentCheckResult::Accepted).unwrap(); } ); @@ -474,7 +652,7 @@ fn spam_attack_results_in_negative_reputation_change() { 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, ValidationVersion::V1, view![]).await; + setup_peer_with_view(overseer, peer, view![], ValidationVersion::V1).await; // new block `hash_b` with 20 candidates let candidates_count = 20; @@ -501,7 +679,7 @@ fn spam_attack_results_in_negative_reputation_change() { .collect(); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await; + send_message_from_peer(overseer, peer, msg.clone()).await; for i in 0..candidates_count { expect_reputation_change(overseer, peer, COST_UNEXPECTED_MESSAGE).await; @@ -513,8 +691,8 @@ fn spam_attack_results_in_negative_reputation_change() { claimed_candidate_index, tx, )) => { - assert_eq!(assignment, assignments[i].0); - assert_eq!(claimed_candidate_index, assignments[i].1); + assert_eq!(assignment, assignments[i].0.clone().into()); + assert_eq!(claimed_candidate_index, assignments[i].1.into()); tx.send(AssignmentCheckResult::Accepted).unwrap(); } ); @@ -533,7 +711,7 @@ fn spam_attack_results_in_negative_reputation_change() { .await; // send the assignments again - send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await; + send_message_from_peer(overseer, peer, msg.clone()).await; // each of them will incur `COST_UNEXPECTED_MESSAGE`, not only the first one for _ in 0..candidates_count { @@ -552,13 +730,26 @@ 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, ValidationVersion::V1, view![]).await; + setup_peer_with_view(overseer, peer, view![], ValidationVersion::V1).await; + + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0], &[2], 1), + ) + .await; // new block `hash` with 1 candidates let meta = BlockApprovalMeta { @@ -578,7 +769,10 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { let cert = fake_assignment_cert(hash, validator_index); overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .await; @@ -610,12 +804,12 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { // the peer could send us it as well let assignments = vec![(cert, candidate_index)]; let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); - send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await; + send_message_from_peer(overseer, peer, msg.clone()).await; assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "we should not punish the peer"); // send the assignments again - send_message_from_peer(overseer, peer, Versioned::V1(msg)).await; + send_message_from_peer(overseer, peer, msg).await; // now we should expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; @@ -624,19 +818,21 @@ 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); 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, ValidationVersion::V1, view![]).await; - setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await; - setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await; + // 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::V3).await; + setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V1).await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -650,16 +846,31 @@ fn import_approval_happy_path() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, &[0, 1], &[2, 4], 3), + ) + .await; + // import an assignment related to `hash` locally let validator_index = ValidatorIndex(0); let candidate_index = 0u32; let cert = fake_assignment_cert(hash, validator_index); overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .await; + // 1 peer is v1 assert_matches!( overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( @@ -668,20 +879,35 @@ fn import_approval_happy_path() { protocol_v1::ApprovalDistributionMessage::Assignments(assignments) )) )) => { - assert_eq!(peers.len(), 2); + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + } + ); + + // 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(), 1); assert_eq!(assignments.len(), 1); } ); // 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, Versioned::V1(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, @@ -712,134 +938,626 @@ fn import_approval_happy_path() { }); } +// Test a v2 approval that signs multiple candidate is correctly processed. #[test] -fn import_approval_bad() { - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); +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 - setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await; - setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await; + // 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(); 1], + candidates: vec![Default::default(); 2], slot: 1.into(), session: 1, }; let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, &[0, 1], &[2, 4], 3), + ) + .await; + + // import an assignment related to `hash` locally let validator_index = ValidatorIndex(0); - let candidate_index = 0u32; - let cert = fake_assignment_cert(hash, validator_index); + 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; - // send the an approval from peer_b, we don't have an assignment yet - let approval = IndirectSignedApprovalVote { + // 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_index, + candidate_indices, validator: validator_index, signature: dummy_signature(), }; - let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, &peer_b, Versioned::V1(msg)).await; - - expect_reputation_change(overseer, &peer_b, COST_UNEXPECTED_MESSAGE).await; - - // now import an assignment from peer_b - let assignments = vec![(cert.clone(), candidate_index)]; - let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); - send_message_from_peer(overseer, &peer_b, Versioned::V1(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, - AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( - assignment, - i, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval( + vote, tx, )) => { - assert_eq!(assignment, cert); - assert_eq!(i, candidate_index); - tx.send(AssignmentCheckResult::Accepted).unwrap(); + assert_eq!(vote, approval); + tx.send(ApprovalCheckResult::Accepted).unwrap(); } ); 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, Versioned::V1(msg)).await; - assert_matches!( overseer_recv(overseer).await, - AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval( - vote, - tx, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) + )) )) => { - assert_eq!(vote, approval); - tx.send(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(hash))).unwrap(); + assert_eq!(peers.len(), 1); + assert_eq!(approvals.len(), 1); } ); - - expect_reputation_change(overseer, &peer_b, COST_INVALID_MESSAGE).await; virtual_overseer }); } -/// make sure we clean up the state on block finalized +// Tests that votes that cover multiple assignments candidates are correctly processed on importing #[test] -fn update_our_view() { +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_a = Hash::repeat_byte(0xAA); - let hash_b = Hash::repeat_byte(0xBB); - let hash_c = Hash::repeat_byte(0xCC); + let hash = Hash::repeat_byte(0xAA); - let state = test_harness(State::default(), |mut virtual_overseer| async move { + 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_a = BlockApprovalMeta { - hash: hash_a, + let meta = BlockApprovalMeta { + hash, parent_hash, number: 1, - candidates: vec![Default::default(); 1], - slot: 1.into(), - session: 1, - }; - let meta_b = BlockApprovalMeta { - hash: hash_b, - parent_hash: hash_a, - number: 2, - candidates: vec![Default::default(); 1], - slot: 1.into(), - session: 1, - }; - let meta_c = BlockApprovalMeta { - hash: hash_c, - parent_hash: hash_b, - number: 3, - candidates: vec![Default::default(); 1], + candidates: vec![Default::default(); 2], slot: 1.into(), session: 1, }; - - let msg = ApprovalDistributionMessage::NewBlocks(vec![meta_a, meta_b, meta_c]); + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; - virtual_overseer - }); - assert!(state.blocks_by_number.get(&1).is_some()); - assert!(state.blocks_by_number.get(&2).is_some()); - assert!(state.blocks_by_number.get(&3).is_some()); - assert!(state.blocks.get(&hash_a).is_some()); - assert!(state.blocks.get(&hash_b).is_some()); - assert!(state.blocks.get(&hash_c).is_some()); + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, &[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; + + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, &[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(); + let peer_b = PeerId::random(); + 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::V1).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V1).await; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + let validator_index = ValidatorIndex(0); + let candidate_index = 0u32; + let cert = fake_assignment_cert(hash, validator_index); + + // send the an approval from peer_b, we don't have an assignment yet + let approval = IndirectSignedApprovalVoteV2 { + block_hash: hash, + candidate_indices: candidate_index.into(), + 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; + + expect_reputation_change(overseer, &peer_b, COST_UNEXPECTED_MESSAGE).await; + + // now import an assignment from peer_b + let assignments = vec![(cert.clone(), candidate_index)]; + let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); + send_message_from_peer(overseer, &peer_b, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + assignment, + i, + tx, + )) => { + assert_eq!(assignment, cert.into()); + assert_eq!(i, candidate_index.into()); + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_b, BENEFIT_VALID_MESSAGE_FIRST).await; + + // and try again + 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::Bad(ApprovalCheckError::UnknownBlock(hash))).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_b, COST_INVALID_MESSAGE).await; + virtual_overseer + }); +} + +/// make sure we clean up the state on block finalized +#[test] +fn update_our_view() { + let parent_hash = Hash::repeat_byte(0xFF); + let hash_a = Hash::repeat_byte(0xAA); + let hash_b = Hash::repeat_byte(0xBB); + let hash_c = Hash::repeat_byte(0xCC); + + let state = test_harness(State::default(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // new block `hash_a` with 1 candidates + let meta_a = BlockApprovalMeta { + hash: hash_a, + parent_hash, + number: 1, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let meta_b = BlockApprovalMeta { + hash: hash_b, + parent_hash: hash_a, + number: 2, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let meta_c = BlockApprovalMeta { + hash: hash_c, + parent_hash: hash_b, + number: 3, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta_a, meta_b, meta_c]); + overseer_send(overseer, msg).await; + virtual_overseer + }); + + assert!(state.blocks_by_number.get(&1).is_some()); + assert!(state.blocks_by_number.get(&2).is_some()); + assert!(state.blocks_by_number.get(&3).is_some()); + assert!(state.blocks.get(&hash_a).is_some()); + assert!(state.blocks.get(&hash_b).is_some()); + assert!(state.blocks.get(&hash_c).is_some()); let state = test_harness(state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -874,7 +1592,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 { @@ -908,17 +1627,242 @@ fn update_peer_view() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta_a, meta_b, meta_c]); overseer_send(overseer, msg).await; + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0], &[2], 1), + ) + .await; + let cert_a = fake_assignment_cert(hash_a, ValidatorIndex(0)); let cert_b = fake_assignment_cert(hash_b, ValidatorIndex(0)); - overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_a, 0)).await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment(cert_a.into(), 0.into()), + ) + .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)).await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment(cert_b.into(), 0.into()), + ) + .await; // connect a peer - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash_a]).await; + setup_peer_with_view(overseer, peer, view![hash_a], ValidationVersion::V1).await; + + // we should send relevant assignments to the peer + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( + protocol_v1::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + } + ); + virtual_overseer + }); + + assert_eq!(state.peer_views.get(peer).map(|v| v.view.finalized_number), Some(0)); + assert_eq!( + state + .blocks + .get(&hash_a) + .unwrap() + .known_by + .get(peer) + .unwrap() + .sent + .known_messages + .len(), + 1, + ); + + let state = test_harness(state, |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // update peer's view + overseer_send( + overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( + *peer, + View::new(vec![hash_b, hash_c, hash_d], 2), + )), + ) + .await; + + let cert_c = fake_assignment_cert(hash_c, ValidatorIndex(0)); + + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment(cert_c.clone().into(), 0.into()), + ) + .await; + + // we should send relevant assignments to the peer + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( + protocol_v1::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + assert_eq!(assignments[0].0, cert_c); + } + ); + virtual_overseer + }); + + assert_eq!(state.peer_views.get(peer).map(|v| v.view.finalized_number), Some(2)); + assert_eq!( + state + .blocks + .get(&hash_c) + .unwrap() + .known_by + .get(peer) + .unwrap() + .sent + .known_messages + .len(), + 1, + ); + + let finalized_number = 4_000_000_000; + let state = test_harness(state, |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // update peer's view + overseer_send( + overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( + *peer, + View::with_finalized(finalized_number), + )), + ) + .await; + virtual_overseer + }); + + assert_eq!(state.peer_views.get(peer).map(|v| v.view.finalized_number), Some(finalized_number)); + assert!(state.blocks.get(&hash_c).unwrap().known_by.get(peer).is_none()); +} + +// Tests that updating the known peer_id for a given authorithy updates the topology +// and sends the required messages +#[test] +fn update_peer_authority_id() { + let parent_hash = Hash::repeat_byte(0xFF); + let hash_a = Hash::repeat_byte(0xAA); + let hash_b = Hash::repeat_byte(0xBB); + let hash_c = Hash::repeat_byte(0xCC); + let peers = make_peers_and_authority_ids(8); + let neighbour_x_index = 0; + let neighbour_y_index = 2; + let local_index = 1; + // X neighbour, we simulate that PeerId is not known in the beginining. + let neighbour_x = peers.get(neighbour_x_index).unwrap().0; + // Y neighbour, we simulate that PeerId is not known in the beginining. + let neighbour_y = peers.get(neighbour_y_index).unwrap().0; + + let _state = test_harness(State::default(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // new block `hash_a` with 1 candidates + let meta_a = BlockApprovalMeta { + hash: hash_a, + parent_hash, + number: 1, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let meta_b = BlockApprovalMeta { + hash: hash_b, + parent_hash: hash_a, + number: 2, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + let meta_c = BlockApprovalMeta { + hash: hash_c, + parent_hash: hash_b, + number: 3, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + }; + + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta_a, meta_b, meta_c]); + overseer_send(overseer, msg).await; + + let peers_with_optional_peer_id = peers + .iter() + .enumerate() + .map(|(index, (peer_id, authority))| { + (if index == 0 { None } else { Some(*peer_id) }, authority.clone()) + }) + .collect_vec(); + + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology( + 1, + &peers_with_optional_peer_id, + &[neighbour_x_index], + &[neighbour_y_index], + local_index, + ), + ) + .await; + + let cert_a = fake_assignment_cert(hash_a, ValidatorIndex(local_index as u32)); + let cert_b = fake_assignment_cert(hash_b, ValidatorIndex(local_index as u32)); + + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment(cert_a.into(), 0.into()), + ) + .await; + + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment(cert_b.into(), 0.into()), + ) + .await; + + // connect a peer + setup_peer_with_view(overseer, &neighbour_x, view![hash_a], ValidationVersion::V1).await; + setup_peer_with_view(overseer, &neighbour_y, view![hash_a], ValidationVersion::V1).await; + + setup_peer_with_view(overseer, &neighbour_x, view![hash_b], ValidationVersion::V1).await; + setup_peer_with_view(overseer, &neighbour_y, view![hash_b], ValidationVersion::V1).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( + protocol_v1::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + assert_eq!(peers.get(0), Some(&neighbour_y)); + } + ); - // we should send relevant assignments to the peer assert_matches!( overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( @@ -929,47 +1873,22 @@ fn update_peer_view() { )) => { assert_eq!(peers.len(), 1); assert_eq!(assignments.len(), 1); + assert_eq!(peers.get(0), Some(&neighbour_y)); } ); - virtual_overseer - }); - - assert_eq!(state.peer_data.get(peer).map(|data| data.view.finalized_number), Some(0)); - assert_eq!( - state - .blocks - .get(&hash_a) - .unwrap() - .known_by - .get(peer) - .unwrap() - .sent - .known_messages - .len(), - 1, - ); - - let state = test_harness(state, |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - // update peer's view - overseer_send( - overseer, - ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( - *peer, - View::new(vec![hash_b, hash_c, hash_d], 2), - )), - ) - .await; - - let cert_c = fake_assignment_cert(hash_c, ValidatorIndex(0)); overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert_c.clone(), 0), + ApprovalDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::UpdatedAuthorityIds( + peers[neighbour_x_index].0, + [peers[neighbour_x_index].1.clone()].into_iter().collect(), + ), + ), ) .await; - // we should send relevant assignments to the peer + // we should send relevant assignments to the peer, after we found it's peer id. assert_matches!( overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( @@ -978,49 +1897,42 @@ fn update_peer_view() { protocol_v1::ApprovalDistributionMessage::Assignments(assignments) )) )) => { + gum::info!(target: LOG_TARGET, ?peers, ?assignments); assert_eq!(peers.len(), 1); - assert_eq!(assignments.len(), 1); - assert_eq!(assignments[0].0, cert_c); + assert_eq!(assignments.len(), 2); + assert_eq!(assignments.get(0).unwrap().0.block_hash, hash_a); + assert_eq!(assignments.get(1).unwrap().0.block_hash, hash_b); + assert_eq!(peers.get(0), Some(&neighbour_x)); } ); - virtual_overseer - }); - - assert_eq!(state.peer_data.get(peer).map(|data| data.view.finalized_number), Some(2)); - assert_eq!( - state - .blocks - .get(&hash_c) - .unwrap() - .known_by - .get(peer) - .unwrap() - .sent - .known_messages - .len(), - 1, - ); - let finalized_number = 4_000_000_000; - let state = test_harness(state, |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - // update peer's view overseer_send( overseer, - ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerViewChange( - *peer, - View::with_finalized(finalized_number), - )), + ApprovalDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::UpdatedAuthorityIds( + peers[neighbour_y_index].0, + [peers[neighbour_y_index].1.clone()].into_iter().collect(), + ), + ), + ) + .await; + overseer_send( + overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::UpdatedAuthorityIds( + peers[neighbour_x_index].0, + [peers[neighbour_x_index].1.clone()].into_iter().collect(), + ), + ), ) .await; + assert!( + overseer.recv().timeout(TIMEOUT).await.is_none(), + "no message should be sent peers are already known" + ); + virtual_overseer }); - - assert_eq!( - state.peer_data.get(peer).map(|data| data.view.finalized_number), - Some(finalized_number) - ); - assert!(state.blocks.get(&hash_c).unwrap().known_by.get(peer).is_none()); } /// E.g. if someone copies the keys... @@ -1034,7 +1946,7 @@ fn import_remotely_then_locally() { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; // setup the peer - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -1054,7 +1966,7 @@ fn import_remotely_then_locally() { let cert = fake_assignment_cert(hash, validator_index); let assignments = vec![(cert.clone(), candidate_index)]; let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer(overseer, peer, Versioned::V1(msg)).await; + send_message_from_peer(overseer, peer, msg).await; // send an `Accept` message from the Approval Voting subsystem assert_matches!( @@ -1064,8 +1976,8 @@ fn import_remotely_then_locally() { i, tx, )) => { - assert_eq!(assignment, cert); - assert_eq!(i, candidate_index); + assert_eq!(assignment, cert.clone().into()); + assert_eq!(i, candidate_index.into()); tx.send(AssignmentCheckResult::Accepted).unwrap(); } ); @@ -1075,21 +1987,24 @@ fn import_remotely_then_locally() { // import the same assignment locally overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .await; 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, Versioned::V1(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, @@ -1113,7 +2028,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; @@ -1133,6 +2049,17 @@ fn sends_assignments_even_when_state_is_approved() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0], &[2], 1), + ) + .await; + let validator_index = ValidatorIndex(0); let candidate_index = 0u32; @@ -1147,15 +2074,21 @@ fn sends_assignments_even_when_state_is_approved() { overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .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, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; let assignments = vec![(cert.clone(), candidate_index)]; let approvals = vec![approval.clone()]; @@ -1191,6 +2124,124 @@ fn sends_assignments_even_when_state_is_approved() { }); } +/// Same as `sends_assignments_even_when_state_is_approved_v2` but with `VRFModuloCompact` +/// assignemnts. +#[test] +fn sends_assignments_even_when_state_is_approved_v2() { + 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; + + let _ = test_harness(State::default(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 4], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[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(); + + let core_bitfield: CoreBitfield = cores + .iter() + .map(|index| CoreIndex(*index)) + .collect::>() + .try_into() + .unwrap(); + + let cert = fake_assignment_cert_v2(hash, validator_index, core_bitfield.clone()); + + // Assumes candidate index == core index. + let approvals = cores + .iter() + .map(|core| IndirectSignedApprovalVoteV2 { + block_hash: hash, + candidate_indices: (*core).into(), + validator: validator_index, + signature: dummy_signature(), + }) + .collect::>(); + + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_bitfield.clone(), + ), + ) + .await; + + for approval in &approvals { + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone()), + ) + .await; + } + + // connect the peer. + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V3).await; + + let assignments = vec![(cert.clone(), candidate_bitfield.clone())]; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(sent_assignments) + )) + )) => { + assert_eq!(peers, vec![*peer]); + assert_eq!(sent_assignments, assignments); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + 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_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); + } + ); + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent"); + virtual_overseer + }); +} + /// /// /// 1. Receive remote peer view update with an unknown head @@ -1219,7 +2270,7 @@ fn race_condition_in_local_vs_remote_view_update() { }; // This will send a peer view that is ahead of our view - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash_b]).await; + setup_peer_with_view(overseer, peer, view![hash_b], ValidationVersion::V1).await; // Send our view update to include a new head overseer_send( @@ -1240,7 +2291,7 @@ fn race_condition_in_local_vs_remote_view_update() { .collect(); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await; + send_message_from_peer(overseer, peer, msg.clone()).await; // This will handle pending messages being processed let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); @@ -1257,8 +2308,8 @@ fn race_condition_in_local_vs_remote_view_update() { claimed_candidate_index, tx, )) => { - assert_eq!(assignment, assignments[i].0); - assert_eq!(claimed_candidate_index, assignments[i].1); + assert_eq!(assignment, assignments[i].0.clone().into()); + assert_eq!(claimed_candidate_index, assignments[i].1.into()); tx.send(AssignmentCheckResult::Accepted).unwrap(); } ); @@ -1283,19 +2334,30 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { // Connect all peers. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, + &[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 @@ -1325,12 +2387,18 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .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()]; @@ -1388,13 +2456,24 @@ fn propagates_assignments_along_unshared_dimension() { // Connect all peers. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; @@ -1424,7 +2503,7 @@ fn propagates_assignments_along_unshared_dimension() { // Issuer of the message is important, not the peer we receive from. // 99 deliberately chosen because it's not in X or Y. - send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peers[99].0, msg).await; assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( @@ -1473,7 +2552,7 @@ fn propagates_assignments_along_unshared_dimension() { // Issuer of the message is important, not the peer we receive from. // 99 deliberately chosen because it's not in X or Y. - send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peers[99].0, msg).await; assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( @@ -1530,20 +2609,29 @@ fn propagates_to_required_after_connect() { // Connect all peers except omitted. for (i, (peer, _)) in peers.iter().enumerate() { if !omitted.contains(&i) { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } } - + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[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 @@ -1573,12 +2661,18 @@ fn propagates_to_required_after_connect() { overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .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()]; @@ -1619,7 +2713,7 @@ fn propagates_to_required_after_connect() { ); for i in omitted.iter().copied() { - setup_peer_with_view(overseer, &peers[i].0, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, &peers[i].0, view![hash], ValidationVersion::V1).await; assert_matches!( overseer_recv(overseer).await, @@ -1668,7 +2762,7 @@ fn sends_to_more_peers_after_getting_topology() { // Connect all peers except omitted. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } // new block `hash_a` with 1 candidates @@ -1698,57 +2792,37 @@ fn sends_to_more_peers_after_getting_topology() { overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .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]; + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; @@ -1820,7 +2894,7 @@ fn originator_aggression_l1() { // Connect all peers except omitted. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } // new block `hash_a` with 1 candidates @@ -1847,22 +2921,37 @@ fn originator_aggression_l1() { validator: validator_index, signature: dummy_signature(), }; - + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index), + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_index.into(), + ), ) .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()]; @@ -1979,7 +3068,7 @@ fn non_originator_aggression_l1() { // Connect all peers except omitted. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } // new block `hash_a` with 1 candidates @@ -2000,20 +3089,29 @@ fn non_originator_aggression_l1() { // import an assignment and approval locally. let cert = fake_assignment_cert(hash, validator_index); - + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; - let assignments = vec![(cert.clone(), candidate_index)]; + let assignments = vec![(cert.clone().into(), candidate_index)]; let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); // Issuer of the message is important, not the peer we receive from. // 99 deliberately chosen because it's not in X or Y. - send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peers[99].0, msg).await; assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( @@ -2084,7 +3182,7 @@ fn non_originator_aggression_l2() { // Connect all peers except omitted. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } // new block `hash_a` with 1 candidates @@ -2105,11 +3203,20 @@ fn non_originator_aggression_l2() { // import an assignment and approval locally. let cert = fake_assignment_cert(hash, validator_index); - + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; @@ -2118,7 +3225,7 @@ fn non_originator_aggression_l2() { // Issuer of the message is important, not the peer we receive from. // 99 deliberately chosen because it's not in X or Y. - send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peers[99].0, msg).await; assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( @@ -2249,13 +3356,22 @@ fn resends_messages_periodically() { // Connect all peers. for (peer, _) in &peers { - setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; } - + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); // 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_with_optional_peer_id, + &[0, 10, 20, 30], + &[50, 51, 52, 53], + 1, + ), ) .await; @@ -2284,7 +3400,7 @@ fn resends_messages_periodically() { // Issuer of the message is important, not the peer we receive from. // 99 deliberately chosen because it's not in X or Y. - send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await; + send_message_from_peer(overseer, &peers[99].0, msg).await; assert_matches!( overseer_recv(overseer).await, AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( @@ -2378,19 +3494,32 @@ 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; // All peers are aware of relay parent. - setup_peer_with_view(overseer, &peer_a, ValidationVersion::V2, view![hash]).await; - setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await; - setup_peer_with_view(overseer, &peer_c, ValidationVersion::V2, view![hash]).await; + setup_peer_with_view(overseer, &peer_a, view![hash], ValidationVersion::V2).await; + 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. + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0, 1], &[2, 4], 3), + ) + .await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -2410,7 +3539,7 @@ fn import_versioned_approval() { let cert = fake_assignment_cert(hash, validator_index); overseer_send( overseer, - ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index), + ApprovalDistributionMessage::DistributeAssignment(cert.into(), candidate_index.into()), ) .await; @@ -2451,7 +3580,7 @@ fn import_versioned_approval() { signature: dummy_signature(), }; let msg = protocol_v2::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, &peer_a, Versioned::V2(msg)).await; + send_message_from_peer_v2(overseer, &peer_a, msg).await; assert_matches!( overseer_recv(overseer).await, @@ -2459,7 +3588,7 @@ fn import_versioned_approval() { vote, tx, )) => { - assert_eq!(vote, approval); + assert_eq!(vote, approval.into()); tx.send(ApprovalCheckResult::Accepted).unwrap(); } ); @@ -2479,6 +3608,7 @@ fn import_versioned_approval() { assert_eq!(approvals.len(), 1); } ); + assert_matches!( overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( @@ -2491,6 +3621,33 @@ fn import_versioned_approval() { assert_eq!(approvals.len(), 1); } ); + + // send an obviously invalid approval + let approval = IndirectSignedApprovalVote { + block_hash: hash, + // Invalid candidate index, should not pass sanitization. + candidate_index: 16777284, + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v2::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v2(overseer, &peer_a, msg).await; + + expect_reputation_change(overseer, &peer_a, COST_OVERSIZED_BITFIELD).await; + + // send an obviously invalid approval + let approval = IndirectSignedApprovalVoteV2 { + block_hash: hash, + // Invalid candidates len, should not pass sanitization. + candidate_indices: 16777284.into(), + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_a, msg).await; + + expect_reputation_change(overseer, &peer_a, COST_OVERSIZED_BITFIELD).await; + virtual_overseer }); } @@ -2512,22 +3669,33 @@ fn batch_test_round(message_count: usize) { let validators = 0..message_count; let assignments: Vec<_> = validators .clone() - .map(|index| (fake_assignment_cert(Hash::zero(), ValidatorIndex(index as u32)), 0)) + .map(|index| { + (fake_assignment_cert(Hash::zero(), ValidatorIndex(index as u32)).into(), 0.into()) + }) .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(), }) .collect(); let peer = PeerId::random(); - send_assignments_batched(&mut sender, assignments.clone(), peer, ValidationVersion::V1) - .await; - send_approvals_batched(&mut sender, approvals.clone(), peer, ValidationVersion::V1).await; + send_assignments_batched( + &mut sender, + assignments.clone(), + &vec![(peer, ValidationVersion::V1.into())], + ) + .await; + send_approvals_batched( + &mut sender, + approvals.clone(), + &vec![(peer, ValidationVersion::V1.into())], + ) + .await; // Check expected assignments batches. for assignment_index in (0..assignments.len()).step_by(super::MAX_ASSIGNMENT_BATCH_SIZE) { @@ -2549,7 +3717,7 @@ fn batch_test_round(message_count: usize) { assert_eq!(peers.len(), 1); for (message_index, assignment) in sent_assignments.iter().enumerate() { - assert_eq!(assignment.0, assignments[assignment_index + message_index].0); + assert_eq!(assignment.0, assignments[assignment_index + message_index].0.clone().try_into().unwrap()); assert_eq!(assignment.1, 0); } } @@ -2576,7 +3744,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 c3c7aa4e0ea517b68e6e69d7aef45870d94efb90..182d92cb163179eea85cafeb1c605763ac18cde1 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-availability-distribution" -version = "1.0.0" +description = "The Availability Distribution subsystem. Requests the required availability data. Also distributes availability data and chunks to requesters." +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } @@ -17,7 +21,7 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-primitives = { path = "../../primitives" } sp-core = { path = "../../../../substrate/primitives/core", features = ["std"] } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" derive_more = "0.99.17" schnellru = "0.2.1" @@ -32,3 +36,14 @@ sc-network = { path = "../../../../substrate/client/network" } futures-timer = "3.0.2" assert_matches = "1.4.0" polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } +polkadot-subsystem-bench = { path = "../../subsystem-bench" } + + +[[test]] +name = "availability-distribution-regression-bench" +path = "tests/availability-distribution-regression-bench.rs" +harness = false +required-features = ["subsystem-benchmarks"] + +[features] +subsystem-benchmarks = [] 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..4e23030aa499003a92b0e7c3eab858bfa5a55f2d 100644 --- a/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs @@ -139,6 +139,7 @@ mod tests { use futures::{executor, future}; use parity_scale_codec::Encode; + use sc_network::ProtocolName; use sp_core::testing::TaskExecutor; use polkadot_node_primitives::BlockData; @@ -146,7 +147,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 +217,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, _, @@ -223,7 +232,10 @@ mod tests { Some(Requests::PoVFetchingV1(outgoing)) => {outgoing} ); req.pending_response - .send(Ok(PoVFetchingResponse::PoV(pov.clone()).encode())) + .send(Ok(( + PoVFetchingResponse::PoV(pov.clone()).encode(), + ProtocolName::from(""), + ))) .unwrap(); break }, diff --git a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs index 460f20499ed59b0c7207354067b944759b794bc7..a5a81082e39ad8897845363960120956b1599a95 100644 --- a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs +++ b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs @@ -25,7 +25,7 @@ use futures::{ Future, FutureExt, StreamExt, }; -use sc_network as network; +use sc_network::{self as network, ProtocolName}; use sp_keyring::Sr25519Keyring; use polkadot_node_network_protocol::request_response::{v1, Recipient}; @@ -252,7 +252,7 @@ impl TestRun { } } req.pending_response - .send(response.map(Encode::encode)) + .send(response.map(|r| (r.encode(), ProtocolName::from("")))) .expect("Sending response should succeed"); } return (valid_responses == 0) && self.valid_chunks.is_empty() 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..66a8d8fcdcf9ac4db556d872c121e25832bf21cb 100644 --- a/polkadot/node/network/availability-distribution/src/tests/state.rs +++ b/polkadot/node/network/availability-distribution/src/tests/state.rs @@ -19,6 +19,7 @@ use std::{ time::Duration, }; +use network::ProtocolName; use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; use polkadot_node_subsystem_util::TimeoutExt; @@ -46,8 +47,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 +265,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())) @@ -321,7 +325,11 @@ fn to_incoming_req( let response = rx.await; let payload = response.expect("Unexpected canceled request").result; pending_response - .send(payload.map_err(|_| network::RequestFailure::Refused)) + .send( + payload + .map_err(|_| network::RequestFailure::Refused) + .map(|r| (r, ProtocolName::from(""))), + ) .expect("Sending response is expected to work"); } .boxed(), diff --git a/polkadot/node/network/availability-distribution/tests/availability-distribution-regression-bench.rs b/polkadot/node/network/availability-distribution/tests/availability-distribution-regression-bench.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdab11298d5c257741eecb6bada930b929b110c7 --- /dev/null +++ b/polkadot/node/network/availability-distribution/tests/availability-distribution-regression-bench.rs @@ -0,0 +1,75 @@ +// 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 . + +//! availability-read regression tests +//! +//! Availability read benchmark based on Kusama parameters and scale. +//! +//! Subsystems involved: +//! - availability-distribution +//! - bitfield-distribution +//! - availability-store + +use polkadot_subsystem_bench::{ + availability::{benchmark_availability_write, prepare_test, TestDataAvailability, TestState}, + configuration::TestConfiguration, + utils::{warm_up_and_benchmark, WarmUpOptions}, +}; + +fn main() -> Result<(), String> { + let mut messages = vec![]; + let mut config = TestConfiguration::default(); + // A single node effort roughly n_cores * needed_approvals / n_validators = 60 * 30 / 300 + config.n_cores = 6; + config.num_blocks = 3; + config.generate_pov_sizes(); + + let usage = warm_up_and_benchmark( + WarmUpOptions::new(&[ + "availability-distribution", + "bitfield-distribution", + "availability-store", + ]), + || { + let mut state = TestState::new(&config); + let (mut env, _protocol_config) = + prepare_test(config.clone(), &mut state, TestDataAvailability::Write, false); + env.runtime().block_on(benchmark_availability_write( + "data_availability_write", + &mut env, + state, + )) + }, + )?; + println!("{}", usage); + + messages.extend(usage.check_network_usage(&[ + ("Received from peers", 443.333, 0.05), + ("Sent to peers", 21818.555, 0.05), + ])); + messages.extend(usage.check_cpu_usage(&[ + ("availability-distribution", 0.011, 0.05), + ("bitfield-distribution", 0.029, 0.05), + ("availability-store", 0.232, 0.05), + ])); + + if messages.is_empty() { + Ok(()) + } else { + eprintln!("{}", messages.join("\n")); + Err("Regressions found".to_string()) + } +} diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 42c3abef547b93e4187ed92764a91b4415602f06..23c4148fa858f7530405d9bd08f79625a40eb968 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -1,17 +1,22 @@ [package] name = "polkadot-availability-recovery" -version = "1.0.0" +description = "The Availability Recovery subsystem. Handles requests for recovering the availability data of included candidates." +version = "7.0.0" 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" +thiserror = { workspace = true } +async-trait = "0.1.74" gum = { package = "tracing-gum", path = "../../gum" } polkadot-erasure-coding = { path = "../../../erasure-coding" } @@ -27,7 +32,7 @@ sc-network = { path = "../../../../substrate/client/network" } assert_matches = "1.4.0" env_logger = "0.9.0" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } sp-core = { path = "../../../../substrate/primitives/core" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } @@ -36,3 +41,13 @@ sc-network = { path = "../../../../substrate/client/network" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } +polkadot-subsystem-bench = { path = "../../subsystem-bench" } + +[[bench]] +name = "availability-recovery-regression-bench" +path = "benches/availability-recovery-regression-bench.rs" +harness = false +required-features = ["subsystem-benchmarks"] + +[features] +subsystem-benchmarks = [] diff --git a/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs b/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs new file mode 100644 index 0000000000000000000000000000000000000000..42b1787e04504968c5c379a5e09b5214b760509e --- /dev/null +++ b/polkadot/node/network/availability-recovery/benches/availability-recovery-regression-bench.rs @@ -0,0 +1,69 @@ +// 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 . + +//! availability-write regression tests +//! +//! Availability write benchmark based on Kusama parameters and scale. +//! +//! Subsystems involved: +//! - availability-recovery + +use polkadot_subsystem_bench::{ + availability::{ + benchmark_availability_read, prepare_test, DataAvailabilityReadOptions, + TestDataAvailability, TestState, + }, + configuration::TestConfiguration, + utils::{warm_up_and_benchmark, WarmUpOptions}, +}; + +fn main() -> Result<(), String> { + let mut messages = vec![]; + + let options = DataAvailabilityReadOptions { fetch_from_backers: true }; + let mut config = TestConfiguration::default(); + config.num_blocks = 3; + config.generate_pov_sizes(); + + let usage = warm_up_and_benchmark(WarmUpOptions::new(&["availability-recovery"]), || { + let mut state = TestState::new(&config); + let (mut env, _protocol_config) = prepare_test( + config.clone(), + &mut state, + TestDataAvailability::Read(options.clone()), + false, + ); + env.runtime().block_on(benchmark_availability_read( + "data_availability_read", + &mut env, + state, + )) + })?; + println!("{}", usage); + + messages.extend(usage.check_network_usage(&[ + ("Received from peers", 307200.000, 0.05), + ("Sent to peers", 1.667, 0.05), + ])); + messages.extend(usage.check_cpu_usage(&[("availability-recovery", 11.500, 0.05)])); + + if messages.is_empty() { + Ok(()) + } else { + eprintln!("{}", messages.join("\n")); + Err("Regressions found".to_string()) + } +} diff --git a/polkadot/node/network/availability-recovery/src/lib.rs b/polkadot/node/network/availability-recovery/src/lib.rs index e2146981da926d2c7027c902ba5be4716382f4da..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; @@ -105,6 +105,17 @@ pub struct AvailabilityRecoverySubsystem { req_receiver: IncomingRequestReceiver, /// Metrics for this subsystem. metrics: Metrics, + /// The type of check to perform after available data was recovered. + post_recovery_check: PostRecoveryCheck, +} + +#[derive(Clone, PartialEq, Debug)] +/// The type of check to perform after available data was recovered. +pub enum PostRecoveryCheck { + /// Reencode the data and check erasure root. For validators. + Reencode, + /// Only check the pov hash. For collators only. + PovHash, } /// Expensive erasure coding computations that we want to run on a blocking thread. @@ -344,6 +355,7 @@ async fn launch_recovery_task( metrics: &Metrics, recovery_strategies: VecDeque::Sender>>>, bypass_availability_store: bool, + post_recovery_check: PostRecoveryCheck, ) -> error::Result<()> { let candidate_hash = receipt.hash(); let params = RecoveryParams { @@ -354,6 +366,8 @@ async fn launch_recovery_task( erasure_root: receipt.descriptor.erasure_root, metrics: metrics.clone(), bypass_availability_store, + post_recovery_check, + pov_hash: receipt.descriptor.pov_hash, }; let recovery_task = RecoveryTask::new(ctx.sender().clone(), params, recovery_strategies); @@ -390,6 +404,7 @@ async fn handle_recover( erasure_task_tx: futures::channel::mpsc::Sender, recovery_strategy_kind: RecoveryStrategyKind, bypass_availability_store: bool, + post_recovery_check: PostRecoveryCheck, ) -> error::Result<()> { let candidate_hash = receipt.hash(); @@ -486,6 +501,7 @@ async fn handle_recover( metrics, recovery_strategies, bypass_availability_store, + post_recovery_check, ) .await }, @@ -527,15 +543,17 @@ async fn query_chunk_size( #[overseer::contextbounds(AvailabilityRecovery, prefix = self::overseer)] impl AvailabilityRecoverySubsystem { - /// Create a new instance of `AvailabilityRecoverySubsystem` which never requests the - /// `AvailabilityStoreSubsystem` subsystem. - pub fn with_availability_store_skip( + /// Create a new instance of `AvailabilityRecoverySubsystem` suitable for collator nodes, + /// which never requests the `AvailabilityStoreSubsystem` subsystem and only checks the POV hash + /// instead of reencoding the available data. + pub fn for_collator( req_receiver: IncomingRequestReceiver, metrics: Metrics, ) -> Self { Self { recovery_strategy_kind: RecoveryStrategyKind::BackersFirstIfSizeLower(SMALL_POV_LIMIT), bypass_availability_store: true, + post_recovery_check: PostRecoveryCheck::PovHash, req_receiver, metrics, } @@ -550,6 +568,7 @@ impl AvailabilityRecoverySubsystem { Self { recovery_strategy_kind: RecoveryStrategyKind::BackersFirstAlways, bypass_availability_store: false, + post_recovery_check: PostRecoveryCheck::Reencode, req_receiver, metrics, } @@ -563,6 +582,7 @@ impl AvailabilityRecoverySubsystem { Self { recovery_strategy_kind: RecoveryStrategyKind::ChunksAlways, bypass_availability_store: false, + post_recovery_check: PostRecoveryCheck::Reencode, req_receiver, metrics, } @@ -577,15 +597,22 @@ impl AvailabilityRecoverySubsystem { Self { recovery_strategy_kind: RecoveryStrategyKind::BackersFirstIfSizeLower(SMALL_POV_LIMIT), bypass_availability_store: false, + post_recovery_check: PostRecoveryCheck::Reencode, req_receiver, metrics, } } - 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, metrics, recovery_strategy_kind, bypass_availability_store } = - self; + let Self { + mut req_receiver, + metrics, + recovery_strategy_kind, + bypass_availability_store, + post_recovery_check, + } = self; let (erasure_task_tx, erasure_task_rx) = futures::channel::mpsc::channel(16); let mut erasure_task_rx = erasure_task_rx.fuse(); @@ -655,6 +682,7 @@ impl AvailabilityRecoverySubsystem { &mut state, signal, ).await? { + gum::debug!(target: LOG_TARGET, "subsystem concluded"); return Ok(()); } FromOrchestra::Communication { msg } => { @@ -675,7 +703,8 @@ impl AvailabilityRecoverySubsystem { &metrics, erasure_task_tx.clone(), recovery_strategy_kind.clone(), - bypass_availability_store + bypass_availability_store, + post_recovery_check.clone() ).await { gum::warn!( target: LOG_TARGET, @@ -818,11 +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 d5bc2da84944a3403f9d4a3bf9d5a11b0e772f40..c300c221da5c6da8f40e8a6db3dede59ba207a58 100644 --- a/polkadot/node/network/availability-recovery/src/task.rs +++ b/polkadot/node/network/availability-recovery/src/task.rs @@ -20,9 +20,10 @@ use crate::{ futures_undead::FuturesUndead, is_chunk_valid, is_unavailable, metrics::Metrics, ErasureTask, - LOG_TARGET, + 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::{ @@ -95,6 +96,12 @@ pub struct RecoveryParams { /// Do not request data from availability-store. Useful for collators. pub bypass_availability_store: bool, + + /// The type of check to perform after available data was recovered. + pub post_recovery_check: PostRecoveryCheck, + + /// The blake2-256 hash of the PoV. + pub pov_hash: Hash, } /// Intermediate/common data that must be passed between `RecoveryStrategy`s belonging to the @@ -426,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) }, } @@ -501,39 +508,48 @@ impl RecoveryStrategy match response.await { Ok(req_res::v1::AvailableDataFetchingResponse::AvailableData(data)) => { - let (reencode_tx, reencode_rx) = oneshot::channel(); - self.params - .erasure_task_tx - .send(ErasureTask::Reencode( - common_params.n_validators, - common_params.erasure_root, - data, - reencode_tx, - )) - .await - .map_err(|_| RecoveryError::ChannelClosed)?; - - let reencode_response = - reencode_rx.await.map_err(|_| RecoveryError::ChannelClosed)?; - - if let Some(data) = reencode_response { - gum::trace!( - target: LOG_TARGET, - candidate_hash = ?common_params.candidate_hash, - "Received full data", - ); + let maybe_data = match common_params.post_recovery_check { + PostRecoveryCheck::Reencode => { + let (reencode_tx, reencode_rx) = oneshot::channel(); + self.params + .erasure_task_tx + .send(ErasureTask::Reencode( + common_params.n_validators, + common_params.erasure_root, + data, + reencode_tx, + )) + .await + .map_err(|_| RecoveryError::ChannelClosed)?; + + reencode_rx.await.map_err(|_| RecoveryError::ChannelClosed)? + }, + PostRecoveryCheck::PovHash => + (data.pov.hash() == common_params.pov_hash).then_some(data), + }; - return Ok(data) - } else { - gum::debug!( - target: LOG_TARGET, - candidate_hash = ?common_params.candidate_hash, - ?validator_index, - "Invalid data response", - ); + match maybe_data { + Some(data) => { + gum::trace!( + target: LOG_TARGET, + candidate_hash = ?common_params.candidate_hash, + "Received full data", + ); - // it doesn't help to report the peer with req/res. - } + return Ok(data) + }, + None => { + gum::debug!( + target: LOG_TARGET, + candidate_hash = ?common_params.candidate_hash, + ?validator_index, + "Invalid data response", + ); + + // it doesn't help to report the peer with req/res. + // we'll try the next backer. + }, + }; }, Ok(req_res::v1::AvailableDataFetchingResponse::NoSuchData) => {}, Err(e) => gum::debug!( @@ -647,22 +663,43 @@ impl FetchChunks { match available_data_response { Ok(data) => { - // Send request to re-encode the chunks and check merkle root. - let (reencode_tx, reencode_rx) = oneshot::channel(); - self.erasure_task_tx - .send(ErasureTask::Reencode( - common_params.n_validators, - common_params.erasure_root, - data, - reencode_tx, - )) - .await - .map_err(|_| RecoveryError::ChannelClosed)?; - - let reencode_response = - reencode_rx.await.map_err(|_| RecoveryError::ChannelClosed)?; - - if let Some(data) = reencode_response { + let maybe_data = match common_params.post_recovery_check { + PostRecoveryCheck::Reencode => { + // Send request to re-encode the chunks and check merkle root. + let (reencode_tx, reencode_rx) = oneshot::channel(); + self.erasure_task_tx + .send(ErasureTask::Reencode( + common_params.n_validators, + common_params.erasure_root, + data, + reencode_tx, + )) + .await + .map_err(|_| RecoveryError::ChannelClosed)?; + + reencode_rx.await.map_err(|_| RecoveryError::ChannelClosed)?.or_else(|| { + gum::trace!( + target: LOG_TARGET, + candidate_hash = ?common_params.candidate_hash, + erasure_root = ?common_params.erasure_root, + "Data recovery error - root mismatch", + ); + None + }) + }, + PostRecoveryCheck::PovHash => + (data.pov.hash() == common_params.pov_hash).then_some(data).or_else(|| { + gum::trace!( + target: LOG_TARGET, + candidate_hash = ?common_params.candidate_hash, + pov_hash = ?common_params.pov_hash, + "Data recovery error - PoV hash mismatch", + ); + None + }), + }; + + if let Some(data) = maybe_data { gum::trace!( target: LOG_TARGET, candidate_hash = ?common_params.candidate_hash, @@ -673,12 +710,6 @@ impl FetchChunks { Ok(data) } else { recovery_duration.map(|rd| rd.stop_and_discard()); - gum::trace!( - target: LOG_TARGET, - candidate_hash = ?common_params.candidate_hash, - erasure_root = ?common_params.erasure_root, - "Data recovery error - root mismatch", - ); Err(RecoveryError::Invalid) } diff --git a/polkadot/node/network/availability-recovery/src/tests.rs b/polkadot/node/network/availability-recovery/src/tests.rs index 63ccf0e94f91ebad2a8cba3158d6db24159683c0..f1dc5b98c09b895f987c070fee0cf3cca37e036e 100644 --- a/polkadot/node/network/availability-recovery/src/tests.rs +++ b/polkadot/node/network/availability-recovery/src/tests.rs @@ -22,14 +22,15 @@ use futures_timer::Delay; use parity_scale_codec::Encode; use polkadot_node_network_protocol::request_response::{ - self as req_res, IncomingRequest, Recipient, ReqProtocolNames, Requests, + self as req_res, v1::AvailableDataFetchingRequest, IncomingRequest, Protocol, 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 sc_network::{IfDisconnected, OutboundFailure, ProtocolName, 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, @@ -48,8 +49,18 @@ type VirtualOverseer = TestSubsystemContextHandle; // Deterministic genesis hash for protocol names const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); -fn test_harness_fast_path>( - test: impl FnOnce(VirtualOverseer, RequestResponseConfig) -> T, +fn request_receiver( + req_protocol_names: &ReqProtocolNames, +) -> IncomingRequestReceiver { + let receiver = IncomingRequest::get_config_receiver(req_protocol_names); + // Don't close the sending end of the request protocol. Otherwise, the subsystem will terminate. + std::mem::forget(receiver.1.inbound_queue); + receiver.0 +} + +fn test_harness>( + subsystem: AvailabilityRecoverySubsystem, + test: impl FnOnce(VirtualOverseer) -> T, ) { let _ = env_logger::builder() .is_test(true) @@ -60,101 +71,23 @@ fn test_harness_fast_path>( - test: impl FnOnce(VirtualOverseer, RequestResponseConfig) -> T, -) { - let _ = env_logger::builder() - .is_test(true) - .filter(Some("polkadot_availability_recovery"), log::LevelFilter::Trace) - .try_init(); - - let pool = sp_core::testing::TaskExecutor::new(); - - let (context, virtual_overseer) = make_subsystem_context(pool.clone()); - - let (collation_req_receiver, req_cfg) = - IncomingRequest::get_config_receiver(&ReqProtocolNames::new(&GENESIS_HASH, None)); - let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( - collation_req_receiver, - Metrics::new_dummy(), - ); - let subsystem = subsystem.run(context); - - let test_fut = test(virtual_overseer, req_cfg); - - futures::pin_mut!(test_fut); - futures::pin_mut!(subsystem); - - executor::block_on(future::join( - async move { - let (mut overseer, _req_cfg) = test_fut.await; - overseer_signal(&mut overseer, OverseerSignal::Conclude).await; - }, - subsystem, - )) - .1 - .unwrap(); -} - -fn test_harness_chunks_if_pov_large< - T: Future, ->( - test: impl FnOnce(VirtualOverseer, RequestResponseConfig) -> T, -) { - let _ = env_logger::builder() - .is_test(true) - .filter(Some("polkadot_availability_recovery"), log::LevelFilter::Trace) - .try_init(); - - let pool = sp_core::testing::TaskExecutor::new(); - - let (context, virtual_overseer) = make_subsystem_context(pool.clone()); - - let (collation_req_receiver, req_cfg) = - IncomingRequest::get_config_receiver(&ReqProtocolNames::new(&GENESIS_HASH, None)); - let subsystem = AvailabilityRecoverySubsystem::with_chunks_if_pov_large( - collation_req_receiver, - Metrics::new_dummy(), - ); - let subsystem = subsystem.run(context); - - let test_fut = test(virtual_overseer, req_cfg); + let test_fut = test(virtual_overseer); futures::pin_mut!(test_fut); futures::pin_mut!(subsystem); executor::block_on(future::join( async move { - let (mut overseer, _req_cfg) = test_fut.await; + let mut overseer = test_fut.await; overseer_signal(&mut overseer, OverseerSignal::Conclude).await; }, subsystem, )) .1 - .unwrap(); } const TIMEOUT: Duration = Duration::from_millis(300); @@ -342,11 +275,12 @@ impl TestState { async fn test_chunk_requests( &self, + req_protocol_names: &ReqProtocolNames, candidate_hash: CandidateHash, virtual_overseer: &mut VirtualOverseer, n: usize, who_has: impl Fn(usize) -> Has, - ) -> Vec, RequestFailure>>> { + ) -> Vec, ProtocolName), RequestFailure>>> { // arbitrary order. let mut i = 0; let mut senders = Vec::new(); @@ -380,7 +314,7 @@ impl TestState { let _ = req.pending_response.send( available_data.map(|r| - req_res::v1::ChunkFetchingResponse::from(r).encode() + (req_res::v1::ChunkFetchingResponse::from(r).encode(), req_protocol_names.get_name(Protocol::ChunkFetchingV1)) ) ); } @@ -394,10 +328,11 @@ impl TestState { async fn test_full_data_requests( &self, + req_protocol_names: &ReqProtocolNames, candidate_hash: CandidateHash, virtual_overseer: &mut VirtualOverseer, who_has: impl Fn(usize) -> Has, - ) -> Vec, RequestFailure>>> { + ) -> Vec, ProtocolName), RequestFailure>>> { let mut senders = Vec::new(); for _ in 0..self.validators.len() { // Receive a request for a chunk. @@ -433,9 +368,10 @@ impl TestState { let done = available_data.as_ref().ok().map_or(false, |x| x.is_some()); let _ = req.pending_response.send( - available_data.map(|r| - req_res::v1::AvailableDataFetchingResponse::from(r).encode() - ) + available_data.map(|r|( + req_res::v1::AvailableDataFetchingResponse::from(r).encode(), + req_protocol_names.get_name(Protocol::AvailableDataFetchingV1) + )) ); if done { break } @@ -456,33 +392,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![ @@ -559,8 +468,13 @@ impl Default for TestState { #[test] fn availability_is_recovered_from_chunks_if_no_group_provided() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -592,6 +506,7 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -627,6 +542,7 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() { test_state .test_chunk_requests( + &req_protocol_names, new_candidate.hash(), &mut virtual_overseer, test_state.impossibility_threshold(), @@ -636,15 +552,20 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() { // A request times out with `Unavailable` error. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunks_only() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -676,6 +597,7 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -711,6 +633,7 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk test_state .test_chunk_requests( + &req_protocol_names, new_candidate.hash(), &mut virtual_overseer, test_state.impossibility_threshold(), @@ -720,15 +643,20 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk // A request times out with `Unavailable` error. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn bad_merkle_path_leads_to_recovery_error() { let mut test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -767,6 +695,7 @@ fn bad_merkle_path_leads_to_recovery_error() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.impossibility_threshold(), @@ -776,15 +705,20 @@ fn bad_merkle_path_leads_to_recovery_error() { // A request times out with `Unavailable` error. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn wrong_chunk_index_leads_to_recovery_error() { let mut test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -823,6 +757,7 @@ fn wrong_chunk_index_leads_to_recovery_error() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.impossibility_threshold(), @@ -832,15 +767,20 @@ fn wrong_chunk_index_leads_to_recovery_error() { // A request times out with `Unavailable` error as there are no good peers. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn invalid_erasure_coding_leads_to_invalid_error() { let mut test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { let pov = PoV { block_data: BlockData(vec![69; 64]) }; let (bad_chunks, bad_erasure_root) = derive_erasure_chunks_with_proofs_and_root( @@ -886,6 +826,7 @@ fn invalid_erasure_coding_leads_to_invalid_error() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -895,15 +836,20 @@ fn invalid_erasure_coding_leads_to_invalid_error() { // f+1 'valid' chunks can't produce correct data. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Invalid); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn fast_path_backing_group_recovers() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -938,20 +884,30 @@ fn fast_path_backing_group_recovers() { test_state.respond_to_available_data_query(&mut virtual_overseer, false).await; test_state - .test_full_data_requests(candidate_hash, &mut virtual_overseer, who_has) + .test_full_data_requests( + &req_protocol_names, + candidate_hash, + &mut virtual_overseer, + who_has, + ) .await; // Recovered data should match the original one. assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn recovers_from_only_chunks_if_pov_large() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_if_pov_large( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_if_pov_large(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -992,6 +948,7 @@ fn recovers_from_only_chunks_if_pov_large() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -1036,6 +993,7 @@ fn recovers_from_only_chunks_if_pov_large() { test_state .test_chunk_requests( + &req_protocol_names, new_candidate.hash(), &mut virtual_overseer, test_state.impossibility_threshold(), @@ -1045,15 +1003,20 @@ fn recovers_from_only_chunks_if_pov_large() { // A request times out with `Unavailable` error. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn fast_path_backing_group_recovers_if_pov_small() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_if_pov_large( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_if_pov_large(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1097,20 +1060,30 @@ fn fast_path_backing_group_recovers_if_pov_small() { test_state.respond_to_available_data_query(&mut virtual_overseer, false).await; test_state - .test_full_data_requests(candidate_hash, &mut virtual_overseer, who_has) + .test_full_data_requests( + &req_protocol_names, + candidate_hash, + &mut virtual_overseer, + who_has, + ) .await; // Recovered data should match the original one. assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn no_answers_in_fast_path_causes_chunk_requests() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_fast_path( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_fast_path(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1146,13 +1119,19 @@ fn no_answers_in_fast_path_causes_chunk_requests() { test_state.respond_to_available_data_query(&mut virtual_overseer, false).await; test_state - .test_full_data_requests(candidate_hash, &mut virtual_overseer, who_has) + .test_full_data_requests( + &req_protocol_names, + candidate_hash, + &mut virtual_overseer, + who_has, + ) .await; test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await; test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -1162,15 +1141,20 @@ fn no_answers_in_fast_path_causes_chunk_requests() { // Recovered data should match the original one. assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn task_canceled_when_receivers_dropped() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1197,7 +1181,7 @@ fn task_canceled_when_receivers_dropped() { for _ in 0..test_state.validators.len() { match virtual_overseer.recv().timeout(TIMEOUT).await { - None => return (virtual_overseer, req_cfg), + None => return virtual_overseer, Some(_) => continue, } } @@ -1209,8 +1193,13 @@ fn task_canceled_when_receivers_dropped() { #[test] fn chunks_retry_until_all_nodes_respond() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1242,6 +1231,7 @@ fn chunks_retry_until_all_nodes_respond() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.validators.len() - test_state.threshold(), @@ -1252,6 +1242,7 @@ fn chunks_retry_until_all_nodes_respond() { // we get to go another round! test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.impossibility_threshold(), @@ -1261,15 +1252,20 @@ fn chunks_retry_until_all_nodes_respond() { // Recovered data should match the original one. assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn not_returning_requests_wont_stall_retrieval() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1304,13 +1300,18 @@ fn not_returning_requests_wont_stall_retrieval() { // Not returning senders won't cause the retrieval to stall: let _senders = test_state - .test_chunk_requests(candidate_hash, &mut virtual_overseer, not_returning_count, |_| { - Has::DoesNotReturn - }) + .test_chunk_requests( + &req_protocol_names, + candidate_hash, + &mut virtual_overseer, + not_returning_count, + |_| Has::DoesNotReturn, + ) .await; test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, // Should start over: @@ -1322,6 +1323,7 @@ fn not_returning_requests_wont_stall_retrieval() { // we get to go another round! test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -1331,15 +1333,20 @@ fn not_returning_requests_wont_stall_retrieval() { // Recovered data should match the original one: assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn all_not_returning_requests_still_recovers_on_return() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1371,6 +1378,7 @@ fn all_not_returning_requests_still_recovers_on_return() { let senders = test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.validators.len(), @@ -1385,6 +1393,7 @@ fn all_not_returning_requests_still_recovers_on_return() { std::mem::drop(senders); }, test_state.test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, // Should start over: @@ -1397,6 +1406,7 @@ fn all_not_returning_requests_still_recovers_on_return() { // we get to go another round! test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold(), @@ -1406,15 +1416,20 @@ fn all_not_returning_requests_still_recovers_on_return() { // Recovered data should match the original one: assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn returns_early_if_we_have_the_data() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1441,15 +1456,20 @@ fn returns_early_if_we_have_the_data() { test_state.respond_to_available_data_query(&mut virtual_overseer, true).await; assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn does_not_query_local_validator() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1480,6 +1500,7 @@ fn does_not_query_local_validator() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.validators.len(), @@ -1490,6 +1511,7 @@ fn does_not_query_local_validator() { // second round, make sure it uses the local chunk. test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold() - 1, @@ -1498,15 +1520,20 @@ fn does_not_query_local_validator() { .await; assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } #[test] fn invalid_local_chunk_is_ignored() { let test_state = TestState::default(); + let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); + let subsystem = AvailabilityRecoverySubsystem::with_chunks_only( + request_receiver(&req_protocol_names), + Metrics::new_dummy(), + ); - test_harness_chunks_only(|mut virtual_overseer, req_cfg| async move { + test_harness(subsystem, |mut virtual_overseer| async move { overseer_signal( &mut virtual_overseer, OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( @@ -1539,6 +1566,7 @@ fn invalid_local_chunk_is_ignored() { test_state .test_chunk_requests( + &req_protocol_names, candidate_hash, &mut virtual_overseer, test_state.threshold() - 1, @@ -1547,6 +1575,6 @@ fn invalid_local_chunk_is_ignored() { .await; assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data); - (virtual_overseer, req_cfg) + virtual_overseer }); } diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 32b0ba2545e69159c1abf6cbe336de84218ca79d..0ddb5f643b89d382855de92fe450a5807e6e21ac 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-availability-bitfield-distribution" -version = "1.0.0" +version = "7.0.0" +description = "Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used to compactly determine which backed candidates are available or not based on a 2/3+ quorum." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] always-assert = "0.1" futures = "0.3.21" @@ -25,7 +29,7 @@ sp-authority-discovery = { path = "../../../../substrate/primitives/authority-di sp-keystore = { path = "../../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } maplit = "1.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" rand_chacha = "0.3.1" diff --git a/polkadot/node/network/bitfield-distribution/src/lib.rs b/polkadot/node/network/bitfield-distribution/src/lib.rs index 68e381ab6be51d65e4ed0a45eb971b84089cb62e..029401e0bd51478ad7144e2790c6abe3db52ce3c 100644 --- a/polkadot/node/network/bitfield-distribution/src/lib.rs +++ b/polkadot/node/network/bitfield-distribution/src/lib.rs @@ -25,14 +25,15 @@ use always_assert::never; use futures::{channel::oneshot, FutureExt}; +use net_protocol::filter_by_peer_version; use polkadot_node_network_protocol::{ self as net_protocol, grid_topology::{ GridNeighbors, RandomRouting, RequiredRouting, SessionBoundGridTopologyStorage, }, peer_set::{ProtocolVersion, ValidationVersion}, - v1 as protocol_v1, v2 as protocol_v2, OurView, PeerId, UnifiedReputationChange as Rep, - Versioned, View, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, OurView, PeerId, + UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_subsystem::{ jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan, @@ -101,6 +102,11 @@ impl BitfieldGossipMessage { self.relay_parent, self.signed_availability.into(), )), + Some(ValidationVersion::V3) => + Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield( + self.relay_parent, + self.signed_availability.into(), + )), None => { never!("Peers should only have supported protocol versions."); @@ -131,9 +137,9 @@ pub struct PeerData { /// Data used to track information of peers and relay parents the /// overseer ordered us to work on. -#[derive(Default, Debug)] +#[derive(Default)] struct ProtocolState { - /// Track all active peers and their views + /// Track all active peer views and protocol versions /// to determine what is relevant to them. peer_data: HashMap, @@ -492,17 +498,13 @@ async fn relay_message( } else { let _span = span.child("gossip"); - let filter_by_version = |peers: &[(PeerId, ProtocolVersion)], - version: ValidationVersion| { - peers - .iter() - .filter(|(_, v)| v == &version.into()) - .map(|(peer_id, _)| *peer_id) - .collect::>() - }; + let v1_interested_peers = + filter_by_peer_version(&interested_peers, ValidationVersion::V1.into()); + let v2_interested_peers = + filter_by_peer_version(&interested_peers, ValidationVersion::V2.into()); - let v1_interested_peers = filter_by_version(&interested_peers, ValidationVersion::V1); - let v2_interested_peers = filter_by_version(&interested_peers, ValidationVersion::V2); + let v3_interested_peers = + filter_by_peer_version(&interested_peers, ValidationVersion::V3.into()); if !v1_interested_peers.is_empty() { ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( @@ -515,7 +517,15 @@ async fn relay_message( if !v2_interested_peers.is_empty() { ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( v2_interested_peers, - message.into_validation_protocol(ValidationVersion::V2.into()), + message.clone().into_validation_protocol(ValidationVersion::V2.into()), + )) + .await + } + + if !v3_interested_peers.is_empty() { + ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( + v3_interested_peers, + message.into_validation_protocol(ValidationVersion::V3.into()), )) .await } @@ -540,6 +550,10 @@ async fn process_incoming_peer_message( Versioned::V2(protocol_v2::BitfieldDistributionMessage::Bitfield( relay_parent, bitfield, + )) | + Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield( + relay_parent, + bitfield, )) => (relay_parent, bitfield), }; @@ -774,9 +788,11 @@ async fn handle_network_msg( handle_peer_view_change(ctx, state, new_peer, old_view, rng).await; } }, - NetworkBridgeEvent::PeerViewChange(peerid, new_view) => { - gum::trace!(target: LOG_TARGET, ?peerid, ?new_view, "Peer view change"); - handle_peer_view_change(ctx, state, peerid, new_view, rng).await; + NetworkBridgeEvent::PeerViewChange(peer_id, new_view) => { + gum::trace!(target: LOG_TARGET, ?peer_id, ?new_view, "Peer view change"); + if state.peer_data.get(&peer_id).is_some() { + handle_peer_view_change(ctx, state, peer_id, new_view, rng).await; + } }, NetworkBridgeEvent::OurViewChange(new_view) => { gum::trace!(target: LOG_TARGET, ?new_view, "Our view change"); @@ -784,8 +800,11 @@ async fn handle_network_msg( }, NetworkBridgeEvent::PeerMessage(remote, message) => process_incoming_peer_message(ctx, state, metrics, remote, message, rng).await, - NetworkBridgeEvent::UpdatedAuthorityIds { .. } => { - // The bitfield-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s. + NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids) => { + state + .topologies + .get_current_topology_mut() + .update_authority_ids(peer_id, &authority_ids); }, } } diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index df8e881234dc56dc364478040a87363d104de4c1..2e889fc30eb90063f07c05e34e51d863a3d32f59 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "polkadot-network-bridge" -version = "1.0.0" +version = "7.0.0" +description = "The Network Bridge Subsystem — protocol multiplexer for Polkadot." 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" } @@ -18,10 +22,10 @@ polkadot-node-metrics = { path = "../../metrics" } polkadot-node-network-protocol = { path = "../protocol" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } -parking_lot = "0.12.0" +parking_lot = "0.12.1" bytes = "1" fatality = "0.0.6" -thiserror = "1" +thiserror = { workspace = true } [dev-dependencies] assert_matches = "1.4.0" diff --git a/polkadot/node/network/bridge/src/lib.rs b/polkadot/node/network/bridge/src/lib.rs index 46d4a00faace6344cbfcfd16ea702f1dcf3ee907..ddce99d5c2a8a721164e73ea61ed989b78cdfbdc 100644 --- a/polkadot/node/network/bridge/src/lib.rs +++ b/polkadot/node/network/bridge/src/lib.rs @@ -83,6 +83,7 @@ pub(crate) enum WireMessage { ViewUpdate(View), } +#[derive(Debug)] pub(crate) struct PeerData { /// The Latest view sent by the peer. view: View, diff --git a/polkadot/node/network/bridge/src/network.rs b/polkadot/node/network/bridge/src/network.rs index 823e1254612f8cae6980c518ffd848f0b61f2e88..21bed019256ac7a0e747ac041f8db32609f0d065 100644 --- a/polkadot/node/network/bridge/src/network.rs +++ b/polkadot/node/network/bridge/src/network.rs @@ -14,50 +14,147 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use async_trait::async_trait; -use futures::{prelude::*, stream::BoxStream}; +use parking_lot::Mutex; use parity_scale_codec::Encode; use sc_network::{ - config::parse_addr, multiaddr::Multiaddr, types::ProtocolName, Event as NetworkEvent, - IfDisconnected, NetworkEventStream, NetworkNotification, NetworkPeers, NetworkRequest, - NetworkService, OutboundFailure, ReputationChange, RequestFailure, + config::parse_addr, multiaddr::Multiaddr, types::ProtocolName, IfDisconnected, MessageSink, + NetworkPeers, NetworkRequest, NetworkService, OutboundFailure, ReputationChange, + RequestFailure, }; use polkadot_node_network_protocol::{ - peer_set::{PeerSet, PeerSetProtocolNames, ProtocolVersion}, + peer_set::{CollationVersion, PeerSet, ProtocolVersion, ValidationVersion}, request_response::{OutgoingRequest, Recipient, ReqProtocolNames, Requests}, - PeerId, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, PeerId, }; use polkadot_primitives::{AuthorityDiscoveryId, Block, Hash}; -use crate::validator_discovery::AuthorityDiscovery; +use crate::{metrics::Metrics, validator_discovery::AuthorityDiscovery, WireMessage}; // network bridge network abstraction log target const LOG_TARGET: &'static str = "parachain::network-bridge-net"; -/// Send a message to the network. +// Helper function to send a validation v1 message to a list of peers. +// Messages are always sent via the main protocol, even legacy protocol messages. +pub(crate) fn send_validation_message_v1( + peers: Vec, + message: WireMessage, + metrics: &Metrics, + notification_sinks: &Arc>>>, +) { + gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation v1 message to peers",); + + send_message( + peers, + PeerSet::Validation, + ValidationVersion::V1.into(), + message, + metrics, + notification_sinks, + ); +} + +// 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_v3( + peers: Vec, + message: WireMessage, + metrics: &Metrics, + notification_sinks: &Arc>>>, +) { + gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation v3 message to peers",); + + send_message( + peers, + PeerSet::Validation, + ValidationVersion::V3.into(), + message, + metrics, + notification_sinks, + ); +} + +// Helper function to send a validation v2 message to a list of peers. +// Messages are always sent via the main protocol, even legacy protocol messages. +pub(crate) fn send_validation_message_v2( + peers: Vec, + message: WireMessage, + metrics: &Metrics, + notification_sinks: &Arc>>>, +) { + send_message( + peers, + PeerSet::Validation, + ValidationVersion::V2.into(), + message, + metrics, + notification_sinks, + ); +} + +// Helper function to send a collation v1 message to a list of peers. +// Messages are always sent via the main protocol, even legacy protocol messages. +pub(crate) fn send_collation_message_v1( + peers: Vec, + message: WireMessage, + metrics: &Metrics, + notification_sinks: &Arc>>>, +) { + send_message( + peers, + PeerSet::Collation, + CollationVersion::V1.into(), + message, + metrics, + notification_sinks, + ); +} + +// Helper function to send a collation v2 message to a list of peers. +// Messages are always sent via the main protocol, even legacy protocol messages. +pub(crate) fn send_collation_message_v2( + peers: Vec, + message: WireMessage, + metrics: &Metrics, + notification_sinks: &Arc>>>, +) { + send_message( + peers, + PeerSet::Collation, + CollationVersion::V2.into(), + message, + metrics, + notification_sinks, + ); +} + +/// Lower level function that sends a message to the network using the main protocol version. /// /// This function is only used internally by the network-bridge, which is responsible to only send /// messages that are compatible with the passed peer set, as that is currently not enforced by /// this function. These are messages of type `WireMessage` parameterized on the matching type. -pub(crate) fn send_message( - net: &mut impl Network, +fn send_message( mut peers: Vec, peer_set: PeerSet, version: ProtocolVersion, - protocol_names: &PeerSetProtocolNames, message: M, metrics: &super::Metrics, + network_notification_sinks: &Arc>>>, ) where M: Encode + Clone, { if peers.is_empty() { return } + let message = { let encoded = message.encode(); metrics.on_notification_sent(peer_set, version, encoded.len(), peers.len()); @@ -65,32 +162,40 @@ pub(crate) fn send_message( encoded }; + let notification_sinks = network_notification_sinks.lock(); + + gum::trace!( + target: LOG_TARGET, + ?peers, + ?peer_set, + ?version, + ?message, + "Sending message to peers", + ); + // optimization: avoid cloning the message for the last peer in the // list. The message payload can be quite large. If the underlying // network used `Bytes` this would not be necessary. + // + // peer may have gotten disconnect by the time `send_message()` is called + // at which point the the sink is not available. let last_peer = peers.pop(); - - // We always send messages on the "main" name even when a negotiated - // fallback is used. The libp2p implementation handles the fallback - // under the hood. - let protocol_name = protocol_names.get_main_name(peer_set); peers.into_iter().for_each(|peer| { - net.write_notification(peer, protocol_name.clone(), message.clone()); + if let Some(sink) = notification_sinks.get(&(peer_set, peer)) { + sink.send_sync_notification(message.clone()); + } }); + if let Some(peer) = last_peer { - net.write_notification(peer, protocol_name, message); + if let Some(sink) = notification_sinks.get(&(peer_set, peer)) { + sink.send_sync_notification(message.clone()); + } } } /// An abstraction over networking for the purposes of this subsystem. #[async_trait] pub trait Network: Clone + Send + 'static { - /// Get a stream of all events occurring on the network. This may include events unrelated - /// to the Polkadot protocol - the user of this function should filter only for events related - /// to the [`VALIDATION_PROTOCOL_NAME`](VALIDATION_PROTOCOL_NAME) - /// or [`COLLATION_PROTOCOL_NAME`](COLLATION_PROTOCOL_NAME) - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent>; - /// Ask the network to keep a substream open with these nodes and not disconnect from them /// until removed from the protocol's peer set. /// Note that `out_peers` setting has no effect on this. @@ -122,16 +227,12 @@ pub trait Network: Clone + Send + 'static { /// Disconnect a given peer from the protocol specified without harming reputation. fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); - /// Write a notification to a peer on the given protocol. - fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec); + /// Get peer role. + fn peer_role(&self, who: PeerId, handshake: Vec) -> Option; } #[async_trait] impl Network for Arc> { - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { - NetworkService::event_stream(self, "polkadot-network-bridge").boxed() - } - async fn set_reserved_peers( &mut self, protocol: ProtocolName, @@ -156,10 +257,6 @@ impl Network for Arc> { NetworkService::disconnect_peer(&**self, who, protocol); } - fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec) { - NetworkService::write_notification(&**self, who, protocol, message); - } - async fn start_request( &self, authority_discovery: &mut AD, @@ -167,7 +264,8 @@ impl Network for Arc> { req_protocol_names: &ReqProtocolNames, if_disconnected: IfDisconnected, ) { - let (protocol, OutgoingRequest { peer, payload, pending_response }) = req.encode_request(); + let (protocol, OutgoingRequest { peer, payload, pending_response, fallback_request }) = + req.encode_request(); let peer_id = match peer { Recipient::Peer(peer_id) => Some(peer_id), @@ -218,6 +316,7 @@ impl Network for Arc> { target: LOG_TARGET, %peer_id, protocol = %req_protocol_names.get_name(protocol), + fallback_protocol = ?fallback_request.as_ref().map(|(_, p)| req_protocol_names.get_name(*p)), ?if_disconnected, "Starting request", ); @@ -227,10 +326,15 @@ impl Network for Arc> { peer_id, req_protocol_names.get_name(protocol), payload, + fallback_request.map(|(r, p)| (r, req_protocol_names.get_name(p))), pending_response, if_disconnected, ); } + + fn peer_role(&self, who: PeerId, handshake: Vec) -> Option { + NetworkService::peer_role(self, who, handshake) + } } /// We assume one `peer_id` per `authority_id`. diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 7e86b46a7e0397ed78e5796afd9a9979688350fa..11ac73259e3a178f418f4a6aa316e337acb31322 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -20,10 +20,14 @@ use super::*; use always_assert::never; use bytes::Bytes; -use futures::stream::{BoxStream, StreamExt}; +use net_protocol::filter_by_peer_version; use parity_scale_codec::{Decode, DecodeAll}; +use parking_lot::Mutex; -use sc_network::Event as NetworkEvent; +use sc_network::{ + service::traits::{NotificationEvent, ValidationResult}, + MessageSink, NotificationService, +}; use sp_consensus::SyncOracle; use polkadot_node_network_protocol::{ @@ -33,7 +37,7 @@ use polkadot_node_network_protocol::{ CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion, ValidationVersion, }, - v1 as protocol_v1, v2 as protocol_v2, ObservedRole, OurView, PeerId, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, ObservedRole, OurView, PeerId, UnifiedReputationChange as Rep, View, }; @@ -49,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, @@ -64,9 +63,11 @@ use super::validator_discovery; /// Actual interfacing to the network based on the `Network` trait. /// /// Defines the `Network` trait with an implementation for an `Arc`. -use crate::network::{send_message, Network}; - -use crate::network::get_peer_id_by_authority_id; +use crate::network::{ + send_collation_message_v1, send_collation_message_v2, send_validation_message_v1, + send_validation_message_v2, send_validation_message_v3, Network, +}; +use crate::{network::get_peer_id_by_authority_id, WireMessage}; use super::metrics::Metrics; @@ -85,6 +86,9 @@ pub struct NetworkBridgeRx { shared: Shared, metrics: Metrics, peerset_protocol_names: PeerSetProtocolNames, + validation_service: Box, + collation_service: Box, + notification_sinks: Arc>>>, } impl NetworkBridgeRx { @@ -99,8 +103,18 @@ impl NetworkBridgeRx { sync_oracle: Box, metrics: Metrics, peerset_protocol_names: PeerSetProtocolNames, + mut notification_services: HashMap>, + notification_sinks: Arc>>>, ) -> Self { let shared = Shared::default(); + + let validation_service = notification_services + .remove(&PeerSet::Validation) + .expect("validation protocol was enabled so `NotificationService` must exist; qed"); + let collation_service = notification_services + .remove(&PeerSet::Collation) + .expect("collation protocol was enabled so `NotificationService` must exist; qed"); + Self { network_service, authority_discovery_service, @@ -108,6 +122,9 @@ impl NetworkBridgeRx { shared, metrics, peerset_protocol_names, + validation_service, + collation_service, + notification_sinks, } } } @@ -118,425 +135,562 @@ where Net: Network + Sync, AD: validator_discovery::AuthorityDiscovery + Clone + Sync, { - fn start(mut self, ctx: Context) -> SpawnedSubsystem { - // The stream of networking events has to be created at initialization, otherwise the - // networking might open connections before the stream of events has been grabbed. - let network_stream = self.network_service.event_stream(); - + fn start(self, ctx: Context) -> SpawnedSubsystem { // Swallow error because failure is fatal to the node and we log with more precision // within `run_network`. - let future = run_network_in(self, ctx, network_stream) + let future = run_network_in(self, ctx) .map_err(|e| SubsystemError::with_origin("network-bridge", e)) .boxed(); SpawnedSubsystem { name: "network-bridge-rx-subsystem", future } } } -async fn handle_network_messages( - mut sender: impl overseer::NetworkBridgeRxSenderTrait, - mut network_service: impl Network, - network_stream: BoxStream<'static, NetworkEvent>, - mut authority_discovery_service: AD, - metrics: Metrics, - shared: Shared, - peerset_protocol_names: PeerSetProtocolNames, -) -> Result<(), Error> -where +/// Handle notification event received over the validation protocol. +async fn handle_validation_message( + event: NotificationEvent, + network_service: &mut impl Network, + sender: &mut impl overseer::NetworkBridgeRxSenderTrait, + authority_discovery_service: &mut AD, + metrics: &Metrics, + shared: &Shared, + peerset_protocol_names: &PeerSetProtocolNames, + notification_service: &mut Box, + notification_sinks: &mut Arc>>>, +) where AD: validator_discovery::AuthorityDiscovery + Send, { - let mut network_stream = network_stream.fuse(); - loop { - match network_stream.next().await { - None => return Err(Error::EventStreamConcluded), - Some(NetworkEvent::Dht(_)) => {}, - Some(NetworkEvent::NotificationStreamOpened { - remote: peer, - protocol, - role, - negotiated_fallback, - received_handshake: _, - }) => { - let role = ObservedRole::from(role); - let (peer_set, version) = { - let (peer_set, version) = - match peerset_protocol_names.try_get_protocol(&protocol) { - None => continue, - Some(p) => p, - }; - - if let Some(fallback) = negotiated_fallback { - match peerset_protocol_names.try_get_protocol(&fallback) { - None => { + match event { + NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx, .. } => { + // only accept peers whose role can be determined + let result = network_service + .peer_role(peer, handshake) + .map_or(ValidationResult::Reject, |_| ValidationResult::Accept); + let _ = result_tx.send(result); + }, + NotificationEvent::NotificationStreamOpened { + peer, + handshake, + negotiated_fallback, + .. + } => { + let role = match network_service.peer_role(peer, handshake) { + Some(role) => ObservedRole::from(role), + None => { + gum::debug!( + target: LOG_TARGET, + ?peer, + "Failed to determine peer role for validation protocol", + ); + return + }, + }; + + let (peer_set, version) = { + let (peer_set, version) = + (PeerSet::Validation, PeerSet::Validation.get_main_version()); + + if let Some(fallback) = negotiated_fallback { + match peerset_protocol_names.try_get_protocol(&fallback) { + None => { + gum::debug!( + target: LOG_TARGET, + fallback = &*fallback, + ?peer, + peerset = ?peer_set, + "Unknown fallback", + ); + + return + }, + Some((p2, v2)) => { + if p2 != peer_set { gum::debug!( target: LOG_TARGET, fallback = &*fallback, - ?peer, - ?peer_set, - "Unknown fallback", + fallback_peerset = ?p2, + peerset = ?peer_set, + "Fallback mismatched peer-set", ); - continue - }, - Some((p2, v2)) => { - if p2 != peer_set { - gum::debug!( - target: LOG_TARGET, - fallback = &*fallback, - fallback_peerset = ?p2, - protocol = &*protocol, - peerset = ?peer_set, - "Fallback mismatched peer-set", - ); - - continue - } - - (p2, v2) - }, - } - } else { - (peer_set, version) - } - }; - - gum::debug!( - target: LOG_TARGET, - action = "PeerConnected", - peer_set = ?peer_set, - version = %version, - peer = ?peer, - role = ?role - ); - - let local_view = { - let mut shared = shared.0.lock(); - let peer_map = match peer_set { - PeerSet::Validation => &mut shared.validation_peers, - PeerSet::Collation => &mut shared.collation_peers, - }; + return + } - match peer_map.entry(peer) { - hash_map::Entry::Occupied(_) => continue, - hash_map::Entry::Vacant(vacant) => { - vacant.insert(PeerData { view: View::default(), version }); + (p2, v2) }, } - - metrics.on_peer_connected(peer_set, version); - metrics.note_peer_count(peer_set, version, peer_map.len()); - - shared.local_view.clone().unwrap_or(View::default()) - }; - - let maybe_authority = - authority_discovery_service.get_authority_ids_by_peer_id(peer).await; - - match peer_set { - PeerSet::Validation => { - dispatch_validation_events_to_all( - vec![ - NetworkBridgeEvent::PeerConnected( - peer, - role, - version, - maybe_authority, - ), - NetworkBridgeEvent::PeerViewChange(peer, View::default()), - ], - &mut sender, - &metrics, - ) - .await; - - match ValidationVersion::try_from(version) - .expect("try_get_protocol has already checked version is known; qed") - { - ValidationVersion::V1 => send_message( - &mut network_service, - vec![peer], - PeerSet::Validation, - version, - &peerset_protocol_names, - WireMessage::::ViewUpdate( - local_view, - ), - &metrics, - ), - ValidationVersion::V2 => send_message( - &mut network_service, - vec![peer], - PeerSet::Validation, - version, - &peerset_protocol_names, - WireMessage::::ViewUpdate( - local_view, - ), - &metrics, - ), - } - }, - PeerSet::Collation => { - dispatch_collation_events_to_all( - vec![ - NetworkBridgeEvent::PeerConnected( - peer, - role, - version, - maybe_authority, - ), - NetworkBridgeEvent::PeerViewChange(peer, View::default()), - ], - &mut sender, - ) - .await; - - match CollationVersion::try_from(version) - .expect("try_get_protocol has already checked version is known; qed") - { - CollationVersion::V1 => send_message( - &mut network_service, - vec![peer], - PeerSet::Collation, - version, - &peerset_protocol_names, - WireMessage::::ViewUpdate( - local_view, - ), - &metrics, - ), - CollationVersion::V2 => send_message( - &mut network_service, - vec![peer], - PeerSet::Collation, - version, - &peerset_protocol_names, - WireMessage::::ViewUpdate( - local_view, - ), - &metrics, - ), - } + } else { + (peer_set, version) + } + }; + // store the notification sink to `notification_sinks` so both `NetworkBridgeRx` + // and `NetworkBridgeTx` can send messages to the peer. + match notification_service.message_sink(&peer) { + Some(sink) => { + notification_sinks.lock().insert((peer_set, peer), sink); + }, + None => { + gum::warn!( + target: LOG_TARGET, + peerset = ?peer_set, + version = %version, + ?peer, + ?role, + "Message sink not available for peer", + ); + return + }, + } + + gum::debug!( + target: LOG_TARGET, + action = "PeerConnected", + peer_set = ?peer_set, + version = %version, + peer = ?peer, + role = ?role + ); + + let local_view = { + let mut shared = shared.0.lock(); + let peer_map = &mut shared.validation_peers; + + match peer_map.entry(peer) { + hash_map::Entry::Occupied(_) => return, + hash_map::Entry::Vacant(vacant) => { + vacant.insert(PeerData { view: View::default(), version }); }, } - }, - Some(NetworkEvent::NotificationStreamClosed { remote: peer, protocol }) => { - let (peer_set, version) = match peerset_protocol_names.try_get_protocol(&protocol) { - None => continue, - Some(peer_set) => peer_set, - }; - gum::debug!( - target: LOG_TARGET, - action = "PeerDisconnected", - peer_set = ?peer_set, - peer = ?peer - ); - - let was_connected = { - let mut shared = shared.0.lock(); - let peer_map = match peer_set { - PeerSet::Validation => &mut shared.validation_peers, - PeerSet::Collation => &mut shared.collation_peers, - }; + metrics.on_peer_connected(peer_set, version); + metrics.note_peer_count(peer_set, version, peer_map.len()); - let w = peer_map.remove(&peer).is_some(); + shared.local_view.clone().unwrap_or(View::default()) + }; - metrics.on_peer_disconnected(peer_set, version); - metrics.note_peer_count(peer_set, version, peer_map.len()); + let maybe_authority = + authority_discovery_service.get_authority_ids_by_peer_id(peer).await; - w - }; + dispatch_validation_events_to_all( + vec![ + NetworkBridgeEvent::PeerConnected(peer, role, version, maybe_authority), + NetworkBridgeEvent::PeerViewChange(peer, View::default()), + ], + sender, + &metrics, + ) + .await; - if was_connected && version == peer_set.get_main_version() { - match peer_set { - PeerSet::Validation => - dispatch_validation_event_to_all( - NetworkBridgeEvent::PeerDisconnected(peer), - &mut sender, - &metrics, - ) - .await, - PeerSet::Collation => - dispatch_collation_event_to_all( - NetworkBridgeEvent::PeerDisconnected(peer), - &mut sender, - ) - .await, - } + match ValidationVersion::try_from(version) + .expect("try_get_protocol has already checked version is known; qed") + { + ValidationVersion::V1 => send_validation_message_v1( + vec![peer], + WireMessage::::ViewUpdate(local_view), + metrics, + notification_sinks, + ), + ValidationVersion::V3 => send_validation_message_v3( + vec![peer], + WireMessage::::ViewUpdate(local_view), + metrics, + notification_sinks, + ), + ValidationVersion::V2 => send_validation_message_v2( + vec![peer], + WireMessage::::ViewUpdate(local_view), + metrics, + notification_sinks, + ), + } + }, + NotificationEvent::NotificationStreamClosed { peer } => { + let (peer_set, version) = (PeerSet::Validation, PeerSet::Validation.get_main_version()); + + gum::debug!( + target: LOG_TARGET, + action = "PeerDisconnected", + ?peer_set, + ?peer + ); + + let was_connected = { + let mut shared = shared.0.lock(); + let peer_map = &mut shared.validation_peers; + + let w = peer_map.remove(&peer).is_some(); + + metrics.on_peer_disconnected(peer_set, version); + metrics.note_peer_count(peer_set, version, peer_map.len()); + + w + }; + + notification_sinks.lock().remove(&(peer_set, peer)); + + if was_connected && version == peer_set.get_main_version() { + dispatch_validation_event_to_all( + NetworkBridgeEvent::PeerDisconnected(peer), + sender, + &metrics, + ) + .await; + } + }, + NotificationEvent::NotificationReceived { peer, notification } => { + let expected_versions = { + let mut versions = PerPeerSet::>::default(); + let shared = shared.0.lock(); + + if let Some(peer_data) = shared.validation_peers.get(&peer) { + versions[PeerSet::Validation] = Some(peer_data.version); } - }, - Some(NetworkEvent::NotificationsReceived { remote, messages }) => { - let expected_versions = { - let mut versions = PerPeerSet::>::default(); - let shared = shared.0.lock(); - if let Some(peer_data) = shared.validation_peers.get(&remote) { - versions[PeerSet::Validation] = Some(peer_data.version); - } - if let Some(peer_data) = shared.collation_peers.get(&remote) { - versions[PeerSet::Collation] = Some(peer_data.version); - } + versions + }; + + gum::trace!( + target: LOG_TARGET, + action = "PeerMessage", + peerset = ?PeerSet::Validation, + ?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::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." + ); - versions - }; + never!( + "Only versions 1 and 2 are supported; peer set connection checked above; qed" + ); - // non-decoded, but version-checked validation messages. - let v_messages: Result, _> = messages - .iter() - .filter_map(|(protocol, msg_bytes)| { - // version doesn't matter because we always receive on the 'correct' - // protocol name, not the negotiated fallback. - let (peer_set, _version) = - peerset_protocol_names.try_get_protocol(protocol)?; - if peer_set == PeerSet::Validation { - if expected_versions[PeerSet::Validation].is_none() { - return Some(Err(UNCONNECTED_PEERSET_COST)) - } + // If a peer somehow triggers this, we'll disconnect them + // eventually. + (Vec::new(), vec![UNCONNECTED_PEERSET_COST]) + }; - Some(Ok(msg_bytes.clone())) - } else { - None - } - }) - .collect(); + for report in reports { + network_service.report_peer(peer, report.into()); + } - let v_messages = match v_messages { - Err(rep) => { - gum::debug!(target: LOG_TARGET, action = "ReportPeer"); - network_service.report_peer(remote, rep.into()); + dispatch_validation_events_to_all(events, sender, &metrics).await; + }, + } +} - continue - }, - Ok(v) => v, - }; +/// Handle notification event received over the collation protocol. +async fn handle_collation_message( + event: NotificationEvent, + network_service: &mut impl Network, + sender: &mut impl overseer::NetworkBridgeRxSenderTrait, + authority_discovery_service: &mut AD, + metrics: &Metrics, + shared: &Shared, + peerset_protocol_names: &PeerSetProtocolNames, + notification_service: &mut Box, + notification_sinks: &mut Arc>>>, +) where + AD: validator_discovery::AuthorityDiscovery + Send, +{ + match event { + NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx, .. } => { + // only accept peers whose role can be determined + let result = network_service + .peer_role(peer, handshake) + .map_or(ValidationResult::Reject, |_| ValidationResult::Accept); + let _ = result_tx.send(result); + }, + NotificationEvent::NotificationStreamOpened { + peer, + handshake, + negotiated_fallback, + .. + } => { + let role = match network_service.peer_role(peer, handshake) { + Some(role) => ObservedRole::from(role), + None => { + gum::debug!( + target: LOG_TARGET, + ?peer, + "Failed to determine peer role for validation protocol", + ); + return + }, + }; + + let (peer_set, version) = { + let (peer_set, version) = + (PeerSet::Collation, PeerSet::Collation.get_main_version()); + + if let Some(fallback) = negotiated_fallback { + match peerset_protocol_names.try_get_protocol(&fallback) { + None => { + gum::debug!( + target: LOG_TARGET, + fallback = &*fallback, + ?peer, + ?peer_set, + "Unknown fallback", + ); + + return + }, + Some((p2, v2)) => { + if p2 != peer_set { + gum::debug!( + target: LOG_TARGET, + fallback = &*fallback, + fallback_peerset = ?p2, + peerset = ?peer_set, + "Fallback mismatched peer-set", + ); - // non-decoded, but version-checked collation messages. - let c_messages: Result, _> = messages - .iter() - .filter_map(|(protocol, msg_bytes)| { - // version doesn't matter because we always receive on the 'correct' - // protocol name, not the negotiated fallback. - let (peer_set, _version) = - peerset_protocol_names.try_get_protocol(protocol)?; - - if peer_set == PeerSet::Collation { - if expected_versions[PeerSet::Collation].is_none() { - return Some(Err(UNCONNECTED_PEERSET_COST)) + return } - Some(Ok(msg_bytes.clone())) - } else { - None - } - }) - .collect(); - - let c_messages = match c_messages { - Err(rep) => { - gum::debug!(target: LOG_TARGET, action = "ReportPeer"); - network_service.report_peer(remote, rep.into()); - - continue + (p2, v2) + }, + } + } else { + (peer_set, version) + } + }; + + // store the notification sink to `notification_sinks` so both `NetworkBridgeRx` + // and `NetworkBridgeTx` can send messages to the peer. + match notification_service.message_sink(&peer) { + Some(sink) => { + notification_sinks.lock().insert((peer_set, peer), sink); + }, + None => { + gum::warn!( + target: LOG_TARGET, + peer_set = ?peer_set, + version = %version, + peer = ?peer, + role = ?role, + "Message sink not available for peer", + ); + return + }, + } + + gum::debug!( + target: LOG_TARGET, + action = "PeerConnected", + peer_set = ?peer_set, + version = %version, + peer = ?peer, + role = ?role + ); + + let local_view = { + let mut shared = shared.0.lock(); + let peer_map = &mut shared.collation_peers; + + match peer_map.entry(peer) { + hash_map::Entry::Occupied(_) => return, + hash_map::Entry::Vacant(vacant) => { + vacant.insert(PeerData { view: View::default(), version }); }, - Ok(v) => v, - }; - - if v_messages.is_empty() && c_messages.is_empty() { - continue } - gum::trace!( - target: LOG_TARGET, - action = "PeerMessages", - peer = ?remote, - num_validation_messages = %v_messages.len(), - num_collation_messages = %c_messages.len() - ); + metrics.on_peer_connected(peer_set, version); + metrics.note_peer_count(peer_set, version, peer_map.len()); - if !v_messages.is_empty() { - let (events, reports) = if expected_versions[PeerSet::Validation] == - Some(ValidationVersion::V1.into()) - { - handle_peer_messages::( - remote, - PeerSet::Validation, - &mut shared.0.lock().validation_peers, - v_messages, - &metrics, - ) - } else if expected_versions[PeerSet::Validation] == - Some(ValidationVersion::V2.into()) - { - handle_peer_messages::( - remote, - PeerSet::Validation, - &mut shared.0.lock().validation_peers, - v_messages, - &metrics, - ) - } else { - gum::warn!( - target: LOG_TARGET, - version = ?expected_versions[PeerSet::Validation], - "Major logic bug. Peer somehow has unsupported validation protocol version." - ); + shared.local_view.clone().unwrap_or(View::default()) + }; - 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]) - }; + let maybe_authority = + authority_discovery_service.get_authority_ids_by_peer_id(peer).await; - for report in reports { - network_service.report_peer(remote, report.into()); - } + dispatch_collation_events_to_all( + vec![ + NetworkBridgeEvent::PeerConnected(peer, role, version, maybe_authority), + NetworkBridgeEvent::PeerViewChange(peer, View::default()), + ], + sender, + ) + .await; - dispatch_validation_events_to_all(events, &mut sender, &metrics).await; + match CollationVersion::try_from(version) + .expect("try_get_protocol has already checked version is known; qed") + { + CollationVersion::V1 => send_collation_message_v1( + vec![peer], + WireMessage::::ViewUpdate(local_view), + metrics, + notification_sinks, + ), + CollationVersion::V2 => send_collation_message_v2( + vec![peer], + WireMessage::::ViewUpdate(local_view), + metrics, + notification_sinks, + ), + } + }, + NotificationEvent::NotificationStreamClosed { peer } => { + let (peer_set, version) = (PeerSet::Collation, PeerSet::Collation.get_main_version()); + + gum::debug!( + target: LOG_TARGET, + action = "PeerDisconnected", + ?peer_set, + ?peer + ); + + let was_connected = { + let mut shared = shared.0.lock(); + let peer_map = &mut shared.collation_peers; + + let w = peer_map.remove(&peer).is_some(); + + metrics.on_peer_disconnected(peer_set, version); + metrics.note_peer_count(peer_set, version, peer_map.len()); + + w + }; + + notification_sinks.lock().remove(&(peer_set, peer)); + + if was_connected && version == peer_set.get_main_version() { + dispatch_collation_event_to_all(NetworkBridgeEvent::PeerDisconnected(peer), sender) + .await; + } + }, + NotificationEvent::NotificationReceived { peer, notification } => { + let expected_versions = { + let mut versions = PerPeerSet::>::default(); + let shared = shared.0.lock(); + + if let Some(peer_data) = shared.collation_peers.get(&peer) { + versions[PeerSet::Collation] = Some(peer_data.version); } - if !c_messages.is_empty() { - let (events, reports) = if expected_versions[PeerSet::Collation] == - Some(CollationVersion::V1.into()) - { - handle_peer_messages::( - remote, - PeerSet::Collation, - &mut shared.0.lock().collation_peers, - c_messages, - &metrics, - ) - } else if expected_versions[PeerSet::Collation] == - Some(CollationVersion::V2.into()) - { - handle_peer_messages::( - remote, - PeerSet::Collation, - &mut shared.0.lock().collation_peers, - c_messages, - &metrics, - ) - } else { - gum::warn!( - target: LOG_TARGET, - version = ?expected_versions[PeerSet::Collation], - "Major logic bug. Peer somehow has unsupported collation protocol version." - ); + versions + }; + + gum::trace!( + target: LOG_TARGET, + action = "PeerMessage", + peerset = ?PeerSet::Collation, + ?peer, + ); + + let (events, reports) = + if expected_versions[PeerSet::Collation] == Some(CollationVersion::V1.into()) { + handle_peer_messages::( + peer, + PeerSet::Collation, + &mut shared.0.lock().collation_peers, + vec![notification.into()], + metrics, + ) + } else if expected_versions[PeerSet::Collation] == Some(CollationVersion::V2.into()) + { + handle_peer_messages::( + peer, + PeerSet::Collation, + &mut shared.0.lock().collation_peers, + vec![notification.into()], + metrics, + ) + } else { + gum::warn!( + target: LOG_TARGET, + version = ?expected_versions[PeerSet::Collation], + "Major logic bug. Peer somehow has unsupported collation 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(remote, report.into()); - } + for report in reports { + network_service.report_peer(peer, report.into()); + } - dispatch_collation_events_to_all(events, &mut sender).await; - } + dispatch_collation_events_to_all(events, sender).await; + }, + } +} + +async fn handle_network_messages( + mut sender: impl overseer::NetworkBridgeRxSenderTrait, + mut network_service: impl Network, + mut authority_discovery_service: AD, + metrics: Metrics, + shared: Shared, + peerset_protocol_names: PeerSetProtocolNames, + mut validation_service: Box, + mut collation_service: Box, + mut notification_sinks: Arc>>>, +) -> Result<(), Error> +where + AD: validator_discovery::AuthorityDiscovery + Send, +{ + loop { + futures::select! { + event = validation_service.next_event().fuse() => match event { + Some(event) => handle_validation_message( + event, + &mut network_service, + &mut sender, + &mut authority_discovery_service, + &metrics, + &shared, + &peerset_protocol_names, + &mut validation_service, + &mut notification_sinks, + ).await, + None => return Err(Error::EventStreamConcluded), }, + event = collation_service.next_event().fuse() => match event { + Some(event) => handle_collation_message( + event, + &mut network_service, + &mut sender, + &mut authority_discovery_service, + &metrics, + &shared, + &peerset_protocol_names, + &mut collation_service, + &mut notification_sinks, + ).await, + None => return Err(Error::EventStreamConcluded), + } } } } @@ -551,6 +705,15 @@ where let mut peers = Vec::with_capacity(neighbors.len()); for (discovery_id, validator_index) in neighbors { let addr = get_peer_id_by_authority_id(ads, discovery_id.clone()).await; + if addr.is_none() { + // See on why is not good in https://github.com/paritytech/polkadot-sdk/issues/2138 + gum::debug!( + target: LOG_TARGET, + ?validator_index, + "Could not determine peer_id for validator, let the team know in \n + https://github.com/paritytech/polkadot-sdk/issues/2138" + ) + } peers.push(TopologyPeerInfo { peer_ids: addr.into_iter().collect(), validator_index, @@ -562,17 +725,15 @@ where } #[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)] -async fn run_incoming_orchestra_signals( +async fn run_incoming_orchestra_signals( mut ctx: Context, - mut network_service: N, mut authority_discovery_service: AD, shared: Shared, sync_oracle: Box, metrics: Metrics, - peerset_protocol_names: PeerSetProtocolNames, + notification_sinks: Arc>>>, ) -> Result<(), Error> where - N: Network, AD: validator_discovery::AuthorityDiscovery + Clone, { // This is kept sorted, descending, by block number. @@ -664,13 +825,12 @@ where mode = Mode::Active; update_our_view( - &mut network_service, &mut ctx, &live_heads, &shared, finalized_number, &metrics, - &peerset_protocol_names, + ¬ification_sinks, ); } } @@ -704,7 +864,6 @@ where async fn run_network_in( bridge: NetworkBridgeRx, mut ctx: Context, - network_stream: BoxStream<'static, NetworkEvent>, ) -> Result<(), Error> where N: Network, @@ -717,16 +876,21 @@ where sync_oracle, shared, peerset_protocol_names, + validation_service, + collation_service, + notification_sinks, } = bridge; let (task, network_event_handler) = handle_network_messages( ctx.sender().clone(), network_service.clone(), - network_stream, authority_discovery_service.clone(), metrics.clone(), shared.clone(), peerset_protocol_names.clone(), + validation_service, + collation_service, + notification_sinks.clone(), ) .remote_handle(); @@ -735,12 +899,11 @@ where let orchestra_signal_handler = run_incoming_orchestra_signals( ctx, - network_service, authority_discovery_service, shared, sync_oracle, metrics, - peerset_protocol_names, + notification_sinks, ); futures::pin_mut!(orchestra_signal_handler); @@ -760,17 +923,14 @@ fn construct_view( } #[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)] -fn update_our_view( - net: &mut Net, +fn update_our_view( ctx: &mut Context, live_heads: &[ActivatedLeaf], shared: &Shared, finalized_number: BlockNumber, metrics: &Metrics, - peerset_protocol_names: &PeerSetProtocolNames, -) where - Net: Network, -{ + notification_sinks: &Arc>>>, +) { let new_view = construct_view(live_heads.iter().map(|v| v.hash), finalized_number); let (validation_peers, collation_peers) = { @@ -806,46 +966,50 @@ fn update_our_view( ) }; - let filter_by_version = |peers: &[(PeerId, ProtocolVersion)], version| { - peers.iter().filter(|(_, v)| v == &version).map(|(p, _)| *p).collect::>() - }; + let v1_validation_peers = + filter_by_peer_version(&validation_peers, ValidationVersion::V1.into()); + let v1_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V1.into()); - let v1_validation_peers = filter_by_version(&validation_peers, ValidationVersion::V1.into()); - let v1_collation_peers = filter_by_version(&collation_peers, CollationVersion::V1.into()); + let v2_validation_peers = + filter_by_peer_version(&validation_peers, ValidationVersion::V2.into()); + let v2_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V2.into()); - let v2_validation_peers = filter_by_version(&validation_peers, ValidationVersion::V2.into()); - let v2_collation_peers = filter_by_version(&collation_peers, ValidationVersion::V2.into()); + let v3_validation_peers = + filter_by_peer_version(&validation_peers, ValidationVersion::V3.into()); send_validation_message_v1( - net, v1_validation_peers, - peerset_protocol_names, WireMessage::ViewUpdate(new_view.clone()), metrics, + notification_sinks, ); send_collation_message_v1( - net, v1_collation_peers, - peerset_protocol_names, WireMessage::ViewUpdate(new_view.clone()), metrics, + notification_sinks, ); send_validation_message_v2( - net, v2_validation_peers, - peerset_protocol_names, WireMessage::ViewUpdate(new_view.clone()), metrics, + notification_sinks, ); send_collation_message_v2( - net, v2_collation_peers, - peerset_protocol_names, - WireMessage::ViewUpdate(new_view), + WireMessage::ViewUpdate(new_view.clone()), metrics, + notification_sinks, + ); + + send_validation_message_v3( + v3_validation_peers, + WireMessage::ViewUpdate(new_view.clone()), + metrics, + notification_sinks, ); let our_view = OurView::new( @@ -917,78 +1081,6 @@ fn handle_peer_messages>( (outgoing_events, reports) } -fn send_validation_message_v1( - net: &mut impl Network, - peers: Vec, - peerset_protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Validation, - ValidationVersion::V1.into(), - peerset_protocol_names, - message, - metrics, - ); -} - -fn send_collation_message_v1( - net: &mut impl Network, - peers: Vec, - peerset_protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Collation, - CollationVersion::V1.into(), - peerset_protocol_names, - message, - metrics, - ); -} - -fn send_validation_message_v2( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Validation, - ValidationVersion::V2.into(), - protocol_names, - message, - metrics, - ); -} - -fn send_collation_message_v2( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Collation, - CollationVersion::V2.into(), - protocol_names, - message, - metrics, - ); -} - async fn dispatch_validation_event_to_all( event: NetworkBridgeEvent, ctx: &mut impl overseer::NetworkBridgeRxSenderTrait, diff --git a/polkadot/node/network/bridge/src/rx/tests.rs b/polkadot/node/network/bridge/src/rx/tests.rs index 7c69cce48391b65294970232b1a277d7c261ba05..6847b8a7e24db5b13df2873d1b54f37506e76831 100644 --- a/polkadot/node/network/bridge/src/rx/tests.rs +++ b/polkadot/node/network/bridge/src/rx/tests.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use super::*; -use futures::{channel::oneshot, executor, stream::BoxStream}; +use futures::{channel::oneshot, executor}; use overseer::jaeger; use polkadot_node_network_protocol::{self as net_protocol, OurView}; use polkadot_node_subsystem::messages::NetworkBridgeEvent; @@ -26,10 +26,13 @@ use parking_lot::Mutex; use std::{ collections::HashSet, sync::atomic::{AtomicBool, Ordering}, - task::Poll, }; -use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange}; +use sc_network::{ + service::traits::{Direction, MessageSink, NotificationService}, + IfDisconnected, Multiaddr, ObservedRole as SubstrateObservedRole, ProtocolName, + ReputationChange, Roles, +}; use polkadot_node_network_protocol::{ peer_set::PeerSetProtocolNames, @@ -47,9 +50,8 @@ use polkadot_node_subsystem_test_helpers::{ mock::new_leaf, SingleItemSink, SingleItemStream, TestSubsystemContextHandle, }; use polkadot_node_subsystem_util::metered; -use polkadot_primitives::{AuthorityDiscoveryId, CandidateHash, Hash}; +use polkadot_primitives::{AuthorityDiscoveryId, Hash}; -use sc_network::Multiaddr; use sp_keyring::Sr25519Keyring; use crate::{network::Network, validator_discovery::AuthorityDiscovery}; @@ -64,10 +66,9 @@ pub enum NetworkAction { WriteNotification(PeerId, PeerSet, Vec), } -// The subsystem's view of the network - only supports a single call to `event_stream`. +// The subsystem's view of the network. #[derive(Clone)] struct TestNetwork { - net_events: Arc>>>, action_tx: Arc>>, protocol_names: Arc, } @@ -79,37 +80,42 @@ struct TestAuthorityDiscovery; // of `NetworkAction`s. struct TestNetworkHandle { action_rx: metered::UnboundedMeteredReceiver, - net_tx: SingleItemSink, - protocol_names: PeerSetProtocolNames, + validation_tx: SingleItemSink, + collation_tx: SingleItemSink, } fn new_test_network( protocol_names: PeerSetProtocolNames, -) -> (TestNetwork, TestNetworkHandle, TestAuthorityDiscovery) { - let (net_tx, net_rx) = polkadot_node_subsystem_test_helpers::single_item_sink(); +) -> ( + TestNetwork, + TestNetworkHandle, + TestAuthorityDiscovery, + Box, + Box, +) { let (action_tx, action_rx) = metered::unbounded(); + let (validation_tx, validation_rx) = polkadot_node_subsystem_test_helpers::single_item_sink(); + let (collation_tx, collation_rx) = polkadot_node_subsystem_test_helpers::single_item_sink(); + let action_tx = Arc::new(Mutex::new(action_tx)); ( TestNetwork { - net_events: Arc::new(Mutex::new(Some(net_rx))), - action_tx: Arc::new(Mutex::new(action_tx)), + action_tx: action_tx.clone(), protocol_names: Arc::new(protocol_names.clone()), }, - TestNetworkHandle { action_rx, net_tx, protocol_names }, + TestNetworkHandle { action_rx, validation_tx, collation_tx }, TestAuthorityDiscovery, + Box::new(TestNotificationService::new( + PeerSet::Validation, + action_tx.clone(), + validation_rx, + )), + Box::new(TestNotificationService::new(PeerSet::Collation, action_tx, collation_rx)), ) } #[async_trait] impl Network for TestNetwork { - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { - self.net_events - .lock() - .take() - .expect("Subsystem made more than one call to `event_stream`") - .boxed() - } - async fn set_reserved_peers( &mut self, _protocol: ProtocolName, @@ -143,7 +149,8 @@ impl Network for TestNetwork { } fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { - let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap(); + let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap(); + assert_eq!(version, peer_set.get_main_version()); self.action_tx .lock() @@ -151,13 +158,10 @@ impl Network for TestNetwork { .unwrap(); } - fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec) { - let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap(); - - self.action_tx - .lock() - .unbounded_send(NetworkAction::WriteNotification(who, peer_set, message)) - .unwrap(); + fn peer_role(&self, _peer_id: PeerId, handshake: Vec) -> Option { + Roles::decode_all(&mut &handshake[..]) + .ok() + .and_then(|role| Some(SubstrateObservedRole::from(role))) } } @@ -201,35 +205,85 @@ impl TestNetworkHandle { peer_set: PeerSet, role: ObservedRole, ) { - let protocol_version = ProtocolVersion::from(protocol_version); - self.send_network_event(NetworkEvent::NotificationStreamOpened { - remote: peer, - protocol: self.protocol_names.get_name(peer_set, protocol_version), - negotiated_fallback: None, - role: role.into(), - received_handshake: vec![], - }) - .await; + fn observed_role_to_handshake(role: &ObservedRole) -> Vec { + match role { + &ObservedRole::Light => Roles::LIGHT.encode(), + &ObservedRole::Authority => Roles::AUTHORITY.encode(), + &ObservedRole::Full => Roles::FULL.encode(), + } + } + + // because of how protocol negotiation works, if two peers support at least one common + // protocol, the protocol is negotiated over the main protocol (`ValidationVersion::V2`) but + // if either one of the peers used a fallback protocol for the negotiation (meaning they + // don't support the main protocol but some older version of it ), `negotiated_fallback` is + // set to that protocol. + let negotiated_fallback = match protocol_version { + ValidationVersion::V2 => None, + ValidationVersion::V1 => match peer_set { + PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/1")), + PeerSet::Collation => Some(ProtocolName::from("/polkadot/collation/1")), + }, + ValidationVersion::V3 => match peer_set { + PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/3")), + PeerSet::Collation => unreachable!(), + }, + }; + + match peer_set { + PeerSet::Validation => { + self.validation_tx + .send(NotificationEvent::NotificationStreamOpened { + peer, + direction: Direction::Inbound, + handshake: observed_role_to_handshake(&role), + negotiated_fallback, + }) + .await + .expect("subsystem concluded early"); + }, + PeerSet::Collation => { + self.collation_tx + .send(NotificationEvent::NotificationStreamOpened { + peer, + direction: Direction::Inbound, + handshake: observed_role_to_handshake(&role), + negotiated_fallback, + }) + .await + .expect("subsystem concluded early"); + }, + } } async fn disconnect_peer(&mut self, peer: PeerId, peer_set: PeerSet) { - self.send_network_event(NetworkEvent::NotificationStreamClosed { - remote: peer, - protocol: self.protocol_names.get_main_name(peer_set), - }) - .await; + match peer_set { + PeerSet::Validation => self + .validation_tx + .send(NotificationEvent::NotificationStreamClosed { peer }) + .await + .expect("subsystem concluded early"), + PeerSet::Collation => self + .collation_tx + .send(NotificationEvent::NotificationStreamClosed { peer }) + .await + .expect("subsystem concluded early"), + } } async fn peer_message(&mut self, peer: PeerId, peer_set: PeerSet, message: Vec) { - self.send_network_event(NetworkEvent::NotificationsReceived { - remote: peer, - messages: vec![(self.protocol_names.get_main_name(peer_set), message.into())], - }) - .await; - } - - async fn send_network_event(&mut self, event: NetworkEvent) { - self.net_tx.send(event).await.expect("subsystem concluded early"); + match peer_set { + PeerSet::Validation => self + .validation_tx + .send(NotificationEvent::NotificationReceived { peer, notification: message }) + .await + .expect("subsystem concluded early"), + PeerSet::Collation => self + .collation_tx + .send(NotificationEvent::NotificationReceived { peer, notification: message }) + .await + .expect("subsystem concluded early"), + } } } @@ -240,6 +294,121 @@ fn assert_network_actions_contains(actions: &[NetworkAction], action: &NetworkAc } } +struct TestNotificationService { + peer_set: PeerSet, + action_tx: Arc>>, + rx: SingleItemStream, +} + +impl std::fmt::Debug for TestNotificationService { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +impl TestNotificationService { + pub fn new( + peer_set: PeerSet, + action_tx: Arc>>, + rx: SingleItemStream, + ) -> Self { + Self { peer_set, action_tx, rx } + } +} + +struct TestMessageSink { + peer: PeerId, + peer_set: PeerSet, + action_tx: Arc>>, +} + +impl TestMessageSink { + fn new( + peer: PeerId, + peer_set: PeerSet, + action_tx: Arc>>, + ) -> TestMessageSink { + Self { peer, peer_set, action_tx } + } +} + +#[async_trait::async_trait] +impl MessageSink for TestMessageSink { + fn send_sync_notification(&self, notification: Vec) { + self.action_tx + .lock() + .unbounded_send(NetworkAction::WriteNotification( + self.peer, + self.peer_set, + notification, + )) + .unwrap(); + } + + async fn send_async_notification( + &self, + _notification: Vec, + ) -> Result<(), sc_network::error::Error> { + unimplemented!(); + } +} + +#[async_trait::async_trait] +impl NotificationService for TestNotificationService { + /// Instruct `Notifications` to open a new substream for `peer`. + async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> { + unimplemented!(); + } + + /// Instruct `Notifications` to close substream for `peer`. + async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> { + unimplemented!(); + } + + /// Send synchronous `notification` to `peer`. + fn send_sync_notification(&self, _peer: &PeerId, _notification: Vec) { + unimplemented!(); + } + + /// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure. + async fn send_async_notification( + &self, + _peer: &PeerId, + _notification: Vec, + ) -> Result<(), sc_network::error::Error> { + unimplemented!(); + } + + /// Set handshake for the notification protocol replacing the old handshake. + async fn set_handshake(&mut self, _handshake: Vec) -> Result<(), ()> { + unimplemented!(); + } + + fn try_set_handshake(&mut self, _handshake: Vec) -> Result<(), ()> { + unimplemented!(); + } + + /// Get next event from the `Notifications` event stream. + async fn next_event(&mut self) -> Option { + self.rx.next().await + } + + // Clone [`NotificationService`] + fn clone(&mut self) -> Result, ()> { + unimplemented!(); + } + + /// Get protocol name. + fn protocol(&self) -> &ProtocolName { + unimplemented!(); + } + + /// Get notification sink of the peer. + fn message_sink(&self, peer: &PeerId) -> Option> { + Some(Box::new(TestMessageSink::new(*peer, self.peer_set, self.action_tx.clone()))) + } +} + #[derive(Clone)] struct TestSyncOracle { is_major_syncing: Arc, @@ -335,10 +504,11 @@ fn test_harness>( let peerset_protocol_names = PeerSetProtocolNames::new(genesis_hash, fork_id); let pool = sp_core::testing::TaskExecutor::new(); - let (mut network, network_handle, discovery) = new_test_network(peerset_protocol_names.clone()); + let (network, network_handle, discovery, validation_service, collation_service) = + new_test_network(peerset_protocol_names.clone()); let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); - let network_stream = network.event_stream(); + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); let shared = Shared::default(); let bridge = NetworkBridgeRx { @@ -348,9 +518,12 @@ fn test_harness>( sync_oracle, shared: shared.clone(), peerset_protocol_names, + validation_service, + collation_service, + notification_sinks, }; - let network_bridge = run_network_in(bridge, context, network_stream) + let network_bridge = run_network_in(bridge, context) .map_err(|_| panic!("subsystem execution failed")) .map(|_| ()); @@ -942,8 +1115,6 @@ fn relays_collation_protocol_messages() { .await; } - // peer A gets reported for sending a collation message. - let collator_protocol_message = protocol_v1::CollatorProtocolMessage::Declare( Sr25519Keyring::Alice.public().into(), Default::default(), @@ -953,19 +1124,23 @@ fn relays_collation_protocol_messages() { let message_v1 = protocol_v1::CollationProtocol::CollatorProtocol(collator_protocol_message.clone()); - network_handle - .peer_message( - peer_a, - PeerSet::Collation, - WireMessage::ProtocolMessage(message_v1.clone()).encode(), - ) - .await; - - let actions = network_handle.next_network_actions(3).await; - assert_network_actions_contains( - &actions, - &NetworkAction::ReputationChange(peer_a, UNCONNECTED_PEERSET_COST.into()), - ); + // peer A gets reported for sending a collation message. + // NOTE: this is not possible since peer A cannot send + // a collation message if it has not opened a collation protocol + + // network_handle + // .peer_message( + // peer_a, + // PeerSet::Collation, + // WireMessage::ProtocolMessage(message_v1.clone()).encode(), + // ) + // .await; + + // let actions = network_handle.next_network_actions(3).await; + // assert_network_actions_contains( + // &actions, + // &NetworkAction::ReputationChange(peer_a, UNCONNECTED_PEERSET_COST.into()), + // ); // peer B has the message relayed. @@ -1212,7 +1387,7 @@ fn our_view_updates_decreasing_order_and_limited_to_max() { fn network_protocol_versioning_view_update() { let (oracle, handle) = make_sync_oracle(false); test_harness(Box::new(oracle), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer, .. } = test_harness; + let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness; let peer_ids: Vec<_> = (0..4).map(|_| PeerId::random()).collect(); let peers = [ @@ -1231,12 +1406,22 @@ fn network_protocol_versioning_view_update() { handle.await_mode_switch().await; + let mut total_validation_peers = 0; + let mut total_collation_peers = 0; + for &(peer_id, peer_set, version) in &peers { network_handle .connect_peer(peer_id, version, peer_set, ObservedRole::Full) .await; + + match peer_set { + PeerSet::Validation => total_validation_peers += 1, + PeerSet::Collation => total_collation_peers += 1, + } } + await_peer_connections(&shared, total_validation_peers, total_collation_peers).await; + let view = view![head]; let actions = network_handle.next_network_actions(4).await; @@ -1248,6 +1433,9 @@ fn network_protocol_versioning_view_update() { ValidationVersion::V2 => WireMessage::::ViewUpdate(view.clone()) .encode(), + ValidationVersion::V3 => + WireMessage::::ViewUpdate(view.clone()) + .encode(), }; assert_network_actions_contains( &actions, @@ -1261,15 +1449,19 @@ fn network_protocol_versioning_view_update() { #[test] fn network_protocol_versioning_subsystem_msg() { + use polkadot_primitives::CandidateHash; + use std::task::Poll; + let (oracle, _handle) = make_sync_oracle(false); test_harness(Box::new(oracle), |test_harness| async move { - let TestHarness { mut network_handle, mut virtual_overseer, .. } = test_harness; + let TestHarness { mut network_handle, mut virtual_overseer, shared } = test_harness; let peer = PeerId::random(); network_handle .connect_peer(peer, ValidationVersion::V2, PeerSet::Validation, ObservedRole::Full) .await; + await_peer_connections(&shared, 1, 0).await; // bridge will inform about all connected peers. { @@ -1277,7 +1469,7 @@ fn network_protocol_versioning_subsystem_msg() { NetworkBridgeEvent::PeerConnected( peer, ObservedRole::Full, - ValidationVersion::V2.into(), + ValidationVersion::V3.into(), None, ), &mut virtual_overseer, @@ -1292,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(), ); @@ -1310,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); @@ -1344,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 f15635f1f41c528f6b15a07870f1f348a6b777a3..d5be6f01c33737a2b09bd39f40640d29c99ca94c 100644 --- a/polkadot/node/network/bridge/src/tx/mod.rs +++ b/polkadot/node/network/bridge/src/tx/mod.rs @@ -18,9 +18,7 @@ use super::*; use polkadot_node_network_protocol::{ - peer_set::{CollationVersion, PeerSet, PeerSetProtocolNames, ValidationVersion}, - request_response::ReqProtocolNames, - v1 as protocol_v1, v2 as protocol_v2, PeerId, Versioned, + peer_set::PeerSetProtocolNames, request_response::ReqProtocolNames, Versioned, }; use polkadot_node_subsystem::{ @@ -29,19 +27,18 @@ 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::ReputationChange; +use sc_network::{MessageSink, ReputationChange}; use crate::validator_discovery; /// Actual interfacing to the network based on the `Network` trait. /// /// Defines the `Network` trait with an implementation for an `Arc`. -use crate::network::{send_message, Network}; +use crate::network::{ + send_collation_message_v1, send_collation_message_v2, send_validation_message_v1, + send_validation_message_v2, send_validation_message_v3, Network, +}; use crate::metrics::Metrics; @@ -59,6 +56,7 @@ pub struct NetworkBridgeTx { metrics: Metrics, req_protocol_names: ReqProtocolNames, peerset_protocol_names: PeerSetProtocolNames, + notification_sinks: Arc>>>, } impl NetworkBridgeTx { @@ -73,6 +71,7 @@ impl NetworkBridgeTx { metrics: Metrics, req_protocol_names: ReqProtocolNames, peerset_protocol_names: PeerSetProtocolNames, + notification_sinks: Arc>>>, ) -> Self { Self { network_service, @@ -80,6 +79,7 @@ impl NetworkBridgeTx { metrics, req_protocol_names, peerset_protocol_names, + notification_sinks, } } } @@ -106,6 +106,7 @@ async fn handle_subsystem_messages( metrics: Metrics, req_protocol_names: ReqProtocolNames, peerset_protocol_names: PeerSetProtocolNames, + notification_sinks: Arc>>>, ) -> Result<(), Error> where N: Network, @@ -129,6 +130,7 @@ where &metrics, &req_protocol_names, &peerset_protocol_names, + ¬ification_sinks, ) .await; }, @@ -139,13 +141,14 @@ where #[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)] async fn handle_incoming_subsystem_communication( _ctx: &mut Context, - mut network_service: N, + network_service: N, validator_discovery: &mut validator_discovery::Service, mut authority_discovery_service: AD, msg: NetworkBridgeTxMessage, metrics: &Metrics, req_protocol_names: &ReqProtocolNames, peerset_protocol_names: &PeerSetProtocolNames, + notification_sinks: &Arc>>>, ) -> (N, AD) where N: Network, @@ -187,23 +190,28 @@ where gum::trace!( target: LOG_TARGET, action = "SendValidationMessages", + ?msg, num_messages = 1usize, ); match msg { Versioned::V1(msg) => send_validation_message_v1( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, + ), + Versioned::V3(msg) => send_validation_message_v3( + peers, + WireMessage::ProtocolMessage(msg), + &metrics, + notification_sinks, ), Versioned::V2(msg) => send_validation_message_v2( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), } }, @@ -212,23 +220,28 @@ where target: LOG_TARGET, action = "SendValidationMessages", num_messages = %msgs.len(), + ?msgs, ); for (peers, msg) in msgs { match msg { Versioned::V1(msg) => send_validation_message_v1( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, + ), + Versioned::V3(msg) => send_validation_message_v3( + peers, + WireMessage::ProtocolMessage(msg), + &metrics, + notification_sinks, ), Versioned::V2(msg) => send_validation_message_v2( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), } } @@ -242,18 +255,16 @@ where match msg { Versioned::V1(msg) => send_collation_message_v1( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), - Versioned::V2(msg) => send_collation_message_v2( - &mut network_service, + Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2( peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), } }, @@ -267,18 +278,16 @@ where for (peers, msg) in msgs { match msg { Versioned::V1(msg) => send_collation_message_v1( - &mut network_service, peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), - Versioned::V2(msg) => send_collation_message_v2( - &mut network_service, + Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2( peers, - peerset_protocol_names, WireMessage::ProtocolMessage(msg), &metrics, + notification_sinks, ), } } @@ -372,6 +381,7 @@ where metrics, req_protocol_names, peerset_protocol_names, + notification_sinks, } = bridge; handle_subsystem_messages( @@ -381,80 +391,9 @@ where metrics, req_protocol_names, peerset_protocol_names, + notification_sinks, ) .await?; Ok(()) } - -fn send_validation_message_v1( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Validation, - ValidationVersion::V1.into(), - protocol_names, - message, - metrics, - ); -} - -fn send_collation_message_v1( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Collation, - CollationVersion::V1.into(), - protocol_names, - message, - metrics, - ); -} - -fn send_validation_message_v2( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Validation, - ValidationVersion::V2.into(), - protocol_names, - message, - metrics, - ); -} - -fn send_collation_message_v2( - net: &mut impl Network, - peers: Vec, - protocol_names: &PeerSetProtocolNames, - message: WireMessage, - metrics: &Metrics, -) { - send_message( - net, - peers, - PeerSet::Collation, - CollationVersion::V2.into(), - protocol_names, - message, - metrics, - ); -} diff --git a/polkadot/node/network/bridge/src/tx/tests.rs b/polkadot/node/network/bridge/src/tx/tests.rs index 48287f8b74c91a526263fed1d176f916753ce985..c3cf0f322f681b5cf2bca5c7a8bbadd53553a90d 100644 --- a/polkadot/node/network/bridge/src/tx/tests.rs +++ b/polkadot/node/network/bridge/src/tx/tests.rs @@ -15,19 +15,22 @@ // along with Polkadot. If not, see . use super::*; -use futures::{executor, stream::BoxStream}; +use futures::executor; use polkadot_node_subsystem_util::TimeoutExt; use async_trait::async_trait; use parking_lot::Mutex; use std::collections::HashSet; -use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange}; +use sc_network::{ + IfDisconnected, ObservedRole as SubstrateObservedRole, ProtocolName, ReputationChange, Roles, +}; +use parity_scale_codec::DecodeAll; use polkadot_node_network_protocol::{ - peer_set::PeerSetProtocolNames, + peer_set::{PeerSetProtocolNames, ValidationVersion}, request_response::{outgoing::Requests, ReqProtocolNames}, - ObservedRole, Versioned, + v1 as protocol_v1, v2 as protocol_v2, ObservedRole, Versioned, }; use polkadot_node_subsystem::{FromOrchestra, OverseerSignal}; use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; @@ -51,10 +54,9 @@ pub enum NetworkAction { WriteNotification(PeerId, PeerSet, Vec), } -// The subsystem's view of the network - only supports a single call to `event_stream`. +// The subsystem's view of the network. #[derive(Clone)] struct TestNetwork { - net_events: Arc>>>, action_tx: Arc>>, peerset_protocol_names: Arc, } @@ -66,37 +68,78 @@ struct TestAuthorityDiscovery; // of `NetworkAction`s. struct TestNetworkHandle { action_rx: metered::UnboundedMeteredReceiver, - net_tx: metered::MeteredSender, - peerset_protocol_names: PeerSetProtocolNames, + _peerset_protocol_names: PeerSetProtocolNames, + notification_sinks: Arc>>>, + action_tx: Arc>>, +} + +struct TestMessageSink { + peer: PeerId, + peer_set: PeerSet, + action_tx: Arc>>, +} + +impl TestMessageSink { + fn new( + peer: PeerId, + peer_set: PeerSet, + action_tx: Arc>>, + ) -> TestMessageSink { + Self { peer, peer_set, action_tx } + } +} + +#[async_trait::async_trait] +impl MessageSink for TestMessageSink { + fn send_sync_notification(&self, notification: Vec) { + self.action_tx + .lock() + .unbounded_send(NetworkAction::WriteNotification( + self.peer, + self.peer_set, + notification, + )) + .unwrap(); + } + + async fn send_async_notification( + &self, + _notification: Vec, + ) -> Result<(), sc_network::error::Error> { + unimplemented!(); + } } fn new_test_network( peerset_protocol_names: PeerSetProtocolNames, -) -> (TestNetwork, TestNetworkHandle, TestAuthorityDiscovery) { - let (net_tx, net_rx) = metered::channel(10); +) -> ( + TestNetwork, + TestNetworkHandle, + TestAuthorityDiscovery, + Arc>>>, +) { let (action_tx, action_rx) = metered::unbounded(); + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); + let action_tx = Arc::new(Mutex::new(action_tx)); ( TestNetwork { - net_events: Arc::new(Mutex::new(Some(net_rx))), - action_tx: Arc::new(Mutex::new(action_tx)), + action_tx: action_tx.clone(), peerset_protocol_names: Arc::new(peerset_protocol_names.clone()), }, - TestNetworkHandle { action_rx, net_tx, peerset_protocol_names }, + TestNetworkHandle { + action_rx, + _peerset_protocol_names: peerset_protocol_names, + action_tx, + notification_sinks: notification_sinks.clone(), + }, TestAuthorityDiscovery, + notification_sinks, ) } #[async_trait] impl Network for TestNetwork { - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { - self.net_events - .lock() - .take() - .expect("Subsystem made more than one call to `event_stream`") - .boxed() - } - async fn set_reserved_peers( &mut self, _protocol: ProtocolName, @@ -130,7 +173,8 @@ impl Network for TestNetwork { } fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { - let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap(); + let (peer_set, version) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap(); + assert_eq!(version, peer_set.get_main_version()); self.action_tx .lock() @@ -138,13 +182,10 @@ impl Network for TestNetwork { .unwrap(); } - fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec) { - let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap(); - - self.action_tx - .lock() - .unbounded_send(NetworkAction::WriteNotification(who, peer_set, message)) - .unwrap(); + fn peer_role(&self, _peer_id: PeerId, handshake: Vec) -> Option { + Roles::decode_all(&mut &handshake[..]) + .ok() + .and_then(|role| Some(SubstrateObservedRole::from(role))) } } @@ -174,23 +215,14 @@ impl TestNetworkHandle { async fn connect_peer( &mut self, peer: PeerId, - protocol_version: ValidationVersion, + _protocol_version: ValidationVersion, peer_set: PeerSet, - role: ObservedRole, + _role: ObservedRole, ) { - let protocol_version = ProtocolVersion::from(protocol_version); - self.send_network_event(NetworkEvent::NotificationStreamOpened { - remote: peer, - protocol: self.peerset_protocol_names.get_name(peer_set, protocol_version), - negotiated_fallback: None, - role: role.into(), - received_handshake: vec![], - }) - .await; - } - - async fn send_network_event(&mut self, event: NetworkEvent) { - self.net_tx.send(event).await.expect("subsystem concluded early"); + self.notification_sinks.lock().insert( + (peer_set, peer), + Box::new(TestMessageSink::new(peer, peer_set, self.action_tx.clone())), + ); } } @@ -208,7 +240,8 @@ fn test_harness>(test: impl FnOnce(TestHarne let peerset_protocol_names = PeerSetProtocolNames::new(genesis_hash, fork_id); let pool = sp_core::testing::TaskExecutor::new(); - let (network, network_handle, discovery) = new_test_network(peerset_protocol_names.clone()); + let (network, network_handle, discovery, network_notification_sinks) = + new_test_network(peerset_protocol_names.clone()); let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool); @@ -219,6 +252,7 @@ fn test_harness>(test: impl FnOnce(TestHarne Metrics(None), req_protocol_names, peerset_protocol_names, + network_notification_sinks, ); let network_bridge_out_fut = run_network_out(bridge_out, context) @@ -356,7 +390,6 @@ fn network_protocol_versioning_send() { } // send a validation protocol message. - { let approval_distribution_message = protocol_v2::ApprovalDistributionMessage::Approvals(Vec::new()); @@ -365,9 +398,9 @@ fn network_protocol_versioning_send() { approval_distribution_message.clone(), ); - // Note that bridge doesn't ensure neither peer's protocol version - // or peer set match the message. - let receivers = vec![peer_ids[0], peer_ids[3]]; + // only `peer_ids[0]` opened the validation protocol v2 + // so only they will be sent a notification + let receivers = vec![peer_ids[0]]; virtual_overseer .send(FromOrchestra::Communication { msg: NetworkBridgeTxMessage::SendValidationMessage( @@ -407,7 +440,9 @@ fn network_protocol_versioning_send() { let msg = protocol_v2::CollationProtocol::CollatorProtocol(collator_protocol_message.clone()); - let receivers = vec![peer_ids[1], peer_ids[2]]; + // only `peer_ids[0]` opened the collation protocol v2 + // so only they will be sent a notification + let receivers = vec![peer_ids[1]]; virtual_overseer .send(FromOrchestra::Communication { diff --git a/polkadot/node/network/bridge/src/validator_discovery.rs b/polkadot/node/network/bridge/src/validator_discovery.rs index 86e861fbc5b5c1cc4159a86c47fcf88cc6a6953d..b11af8a8a089c4aa64b26312562636b039068a78 100644 --- a/polkadot/node/network/bridge/src/validator_discovery.rs +++ b/polkadot/node/network/bridge/src/validator_discovery.rs @@ -169,13 +169,12 @@ mod tests { use crate::network::Network; use async_trait::async_trait; - use futures::stream::BoxStream; use polkadot_node_network_protocol::{ request_response::{outgoing::Requests, ReqProtocolNames}, PeerId, }; use polkadot_primitives::Hash; - use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange}; + use sc_network::{IfDisconnected, ProtocolName, ReputationChange}; use sp_keyring::Sr25519Keyring; use std::collections::{HashMap, HashSet}; @@ -224,10 +223,6 @@ mod tests { #[async_trait] impl Network for TestNetwork { - fn event_stream(&mut self) -> BoxStream<'static, NetworkEvent> { - panic!() - } - async fn set_reserved_peers( &mut self, _protocol: ProtocolName, @@ -263,7 +258,11 @@ mod tests { panic!() } - fn write_notification(&self, _: PeerId, _: ProtocolName, _: Vec) { + fn peer_role( + &self, + _peer_id: PeerId, + _handshake: Vec, + ) -> Option { panic!() } } diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index e5328cf16629f4c7bdb2180e90fbe888dbfb8f42..f0f8be0f7bab240a928be9f30a18ca10b14eb16d 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-collator-protocol" -version = "1.0.0" +version = "7.0.0" +description = "Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each other." 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" @@ -21,11 +25,11 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-subsystem = { path = "../../subsystem" } fatality = "0.0.6" -thiserror = "1.0.48" +thiserror = { workspace = true } tokio-util = "0.7.1" [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" diff --git a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs index 53f947142d10d615496b184b22ce7d69d815c977..dc1ee725462f5350ccfaac72e7c85969cdc7998e 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/collation.rs @@ -27,7 +27,7 @@ use polkadot_node_network_protocol::{ PeerId, }; use polkadot_node_primitives::PoV; -use polkadot_primitives::{CandidateHash, CandidateReceipt, Hash, Id as ParaId}; +use polkadot_primitives::{CandidateHash, CandidateReceipt, Hash, HeadData, Id as ParaId}; /// The status of a collation as seen from the collator. pub enum CollationStatus { @@ -63,6 +63,8 @@ pub struct Collation { pub parent_head_data_hash: Hash, /// Proof to verify the state transition of the parachain. pub pov: PoV, + /// Parent head-data needed for elastic scaling. + pub parent_head_data: HeadData, /// Collation status. pub status: CollationStatus, } 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 e4b95f24d5a3c8789174b31346a3b23a1d458422..19cc1eb1a57c80966d2f65bd141fc0be560ccc2e 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -55,7 +55,7 @@ use polkadot_node_subsystem_util::{ }; use polkadot_primitives::{ AuthorityDiscoveryId, CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState, - GroupIndex, Hash, Id as ParaId, SessionIndex, + GroupIndex, Hash, HeadData, Id as ParaId, SessionIndex, }; use super::LOG_TARGET; @@ -347,6 +347,7 @@ async fn distribute_collation( receipt: CandidateReceipt, parent_head_data_hash: Hash, pov: PoV, + parent_head_data: HeadData, result_sender: Option>, ) -> Result<()> { let candidate_relay_parent = receipt.descriptor.relay_parent; @@ -465,7 +466,13 @@ async fn distribute_collation( per_relay_parent.collations.insert( candidate_hash, - Collation { receipt, parent_head_data_hash, pov, status: CollationStatus::Created }, + Collation { + receipt, + parent_head_data_hash, + pov, + parent_head_data, + status: CollationStatus::Created, + }, ); // If prospective parachains are disabled, a leaf should be known to peer. @@ -763,20 +770,26 @@ async fn process_msg( CollateOn(id) => { state.collating_on = Some(id); }, - DistributeCollation(receipt, parent_head_data_hash, pov, result_sender) => { + DistributeCollation { + candidate_receipt, + parent_head_data_hash, + pov, + parent_head_data, + result_sender, + } => { let _span1 = state .span_per_relay_parent - .get(&receipt.descriptor.relay_parent) + .get(&candidate_receipt.descriptor.relay_parent) .map(|s| s.child("distributing-collation")); let _span2 = jaeger::Span::new(&pov, "distributing-collation"); match state.collating_on { - Some(id) if receipt.descriptor.para_id != id => { + Some(id) if candidate_receipt.descriptor.para_id != id => { // If the ParaId of a collation requested to be distributed does not match // the one we expect, we ignore the message. gum::warn!( target: LOG_TARGET, - para_id = %receipt.descriptor.para_id, + para_id = %candidate_receipt.descriptor.para_id, collating_on = %id, "DistributeCollation for unexpected para_id", ); @@ -788,9 +801,10 @@ async fn process_msg( runtime, state, id, - receipt, + candidate_receipt, parent_head_data_hash, pov, + parent_head_data, result_sender, ) .await?; @@ -798,7 +812,7 @@ async fn process_msg( None => { gum::warn!( target: LOG_TARGET, - para_id = %receipt.descriptor.para_id, + para_id = %candidate_receipt.descriptor.para_id, "DistributeCollation message while not collating on any", ); }, @@ -835,6 +849,7 @@ async fn send_collation( request: VersionedCollationRequest, receipt: CandidateReceipt, pov: PoV, + _parent_head_data: HeadData, ) { let (tx, rx) = oneshot::channel(); @@ -842,13 +857,22 @@ async fn send_collation( let peer_id = request.peer_id(); let candidate_hash = receipt.hash(); - // The response payload is the same for both versions of protocol + // The response payload is the same for v1 and v2 versions of protocol // and doesn't have v2 alias for simplicity. - let response = OutgoingResponse { - result: Ok(request_v1::CollationFetchingResponse::Collation(receipt, pov)), - reputation_changes: Vec::new(), - sent_feedback: Some(tx), - }; + // For now, we don't send parent head data to the collation requester. + let result = + // if assigned_multiple_cores { + // Ok(request_v1::CollationFetchingResponse::CollationWithParentHeadData { + // receipt, + // pov, + // parent_head_data, + // }) + // } else { + Ok(request_v1::CollationFetchingResponse::Collation(receipt, pov)) + // } + ; + let response = + OutgoingResponse { result, reputation_changes: Vec::new(), sent_feedback: Some(tx) }; if let Err(_) = request.send_outgoing_response(response) { gum::warn!(target: LOG_TARGET, "Sending collation response failed"); @@ -880,7 +904,9 @@ async fn handle_incoming_peer_message( use protocol_v2::CollatorProtocolMessage as V2; match msg { - Versioned::V1(V1::Declare(..)) | Versioned::V2(V2::Declare(..)) => { + Versioned::V1(V1::Declare(..)) | + Versioned::V2(V2::Declare(..)) | + Versioned::V3(V2::Declare(..)) => { gum::trace!( target: LOG_TARGET, ?origin, @@ -891,7 +917,9 @@ async fn handle_incoming_peer_message( ctx.send_message(NetworkBridgeTxMessage::DisconnectPeer(origin, PeerSet::Collation)) .await; }, - Versioned::V1(V1::AdvertiseCollation(_)) | Versioned::V2(V2::AdvertiseCollation { .. }) => { + Versioned::V1(V1::AdvertiseCollation(_)) | + Versioned::V2(V2::AdvertiseCollation { .. }) | + Versioned::V3(V2::AdvertiseCollation { .. }) => { gum::trace!( target: LOG_TARGET, ?origin, @@ -906,7 +934,8 @@ async fn handle_incoming_peer_message( .await; }, Versioned::V1(V1::CollationSeconded(relay_parent, statement)) | - Versioned::V2(V2::CollationSeconded(relay_parent, statement)) => { + Versioned::V2(V2::CollationSeconded(relay_parent, statement)) | + Versioned::V3(V2::CollationSeconded(relay_parent, statement)) => { if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) { gum::warn!( target: LOG_TARGET, @@ -1022,9 +1051,13 @@ async fn handle_incoming_request( return Ok(()) }, }; - let (receipt, pov) = if let Some(collation) = collation { + let (receipt, pov, parent_head_data) = if let Some(collation) = collation { collation.status.advance_to_requested(); - (collation.receipt.clone(), collation.pov.clone()) + ( + collation.receipt.clone(), + collation.pov.clone(), + collation.parent_head_data.clone(), + ) } else { gum::warn!( target: LOG_TARGET, @@ -1063,7 +1096,7 @@ async fn handle_incoming_request( waiting.collation_fetch_active = true; // Obtain a timer for sending collation let _ = state.metrics.time_collation_distribution("send"); - send_collation(state, req, receipt, pov).await; + send_collation(state, req, receipt, pov, parent_head_data).await; } }, Some(our_para_id) => { @@ -1448,8 +1481,9 @@ async fn run_inner( if let Some(collation) = next_collation { let receipt = collation.receipt.clone(); let pov = collation.pov.clone(); + let parent_head_data = collation.parent_head_data.clone(); - send_collation(&mut state, next, receipt, pov).await; + send_collation(&mut state, next, receipt, pov, parent_head_data).await; } }, (candidate_hash, peer_id) = state.advertisement_timeouts.select_next_some() => { 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..beda941d37abea5d214703847c160f5c11eac24a 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; @@ -355,12 +356,13 @@ async fn distribute_collation_with_receipt( ) -> DistributeCollation { overseer_send( virtual_overseer, - CollatorProtocolMessage::DistributeCollation( - candidate.clone(), + CollatorProtocolMessage::DistributeCollation { + candidate_receipt: candidate.clone(), parent_head_data_hash, - pov.clone(), - None, - ), + pov: pov.clone(), + parent_head_data: HeadData(vec![1, 2, 3]), + result_sender: None, + }, ) .await; @@ -406,7 +408,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), @@ -621,6 +628,18 @@ async fn send_peer_view_change( .await; } +fn decode_collation_response(bytes: &[u8]) -> (CandidateReceipt, PoV) { + let response: request_v1::CollationFetchingResponse = + request_v1::CollationFetchingResponse::decode(&mut &bytes[..]) + .expect("Decoding should work"); + match response { + request_v1::CollationFetchingResponse::Collation(receipt, pov) => (receipt, pov), + request_v1::CollationFetchingResponse::CollationWithParentHeadData { + receipt, pov, .. + } => (receipt, pov), + } +} + #[test] fn advertise_and_send_collation() { let mut test_state = TestState::default(); @@ -730,12 +749,10 @@ fn advertise_and_send_collation() { assert_matches!( rx.await, Ok(full_response) => { - let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse - = request_v1::CollationFetchingResponse::decode( - &mut full_response.result - .expect("We should have a proper answer").as_ref() - ) - .expect("Decoding should work"); + let (receipt, pov) = decode_collation_response( + full_response.result + .expect("We should have a proper answer").as_ref() + ); assert_eq!(receipt, candidate); assert_eq!(pov, pov_block); } @@ -1332,12 +1349,10 @@ where let feedback_tx = assert_matches!( rx.await, Ok(full_response) => { - let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse - = request_v1::CollationFetchingResponse::decode( - &mut full_response.result - .expect("We should have a proper answer").as_ref() - ) - .expect("Decoding should work"); + let (receipt, pov) = decode_collation_response( + full_response.result + .expect("We should have a proper answer").as_ref() + ); assert_eq!(receipt, candidate); assert_eq!(pov, pov_block); @@ -1369,12 +1384,10 @@ where assert_matches!( rx.await, Ok(full_response) => { - let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse - = request_v1::CollationFetchingResponse::decode( - &mut full_response.result - .expect("We should have a proper answer").as_ref() - ) - .expect("Decoding should work"); + let (receipt, pov) = decode_collation_response( + full_response.result + .expect("We should have a proper answer").as_ref() + ); assert_eq!(receipt, candidate); assert_eq!(pov, pov_block); @@ -1463,11 +1476,10 @@ fn connect_to_buffered_groups() { assert_matches!( rx.await, Ok(full_response) => { - let request_v1::CollationFetchingResponse::Collation(..) = - request_v1::CollationFetchingResponse::decode( - &mut full_response.result.expect("We should have a proper answer").as_ref(), - ) - .expect("Decoding should work"); + let _ = decode_collation_response( + full_response.result + .expect("We should have a proper answer").as_ref() + ); } ); diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index fd9d7a746ebe49623ff0feebd48793e8e603807b..2bb7002a4f4710727829a7bed0f064c23d19e2c9 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -271,12 +271,13 @@ fn distribute_collation_from_implicit_view() { .build(); overseer_send( virtual_overseer, - CollatorProtocolMessage::DistributeCollation( - candidate.clone(), + CollatorProtocolMessage::DistributeCollation { + candidate_receipt: candidate.clone(), parent_head_data_hash, - pov.clone(), - None, - ), + pov: pov.clone(), + parent_head_data: HeadData(vec![1, 2, 3]), + result_sender: None, + }, ) .await; @@ -351,12 +352,13 @@ fn distribute_collation_up_to_limit() { .build(); overseer_send( virtual_overseer, - CollatorProtocolMessage::DistributeCollation( - candidate.clone(), + CollatorProtocolMessage::DistributeCollation { + candidate_receipt: candidate.clone(), parent_head_data_hash, - pov.clone(), - None, - ), + pov: pov.clone(), + parent_head_data: HeadData(vec![1, 2, 3]), + result_sender: None, + }, ) .await; @@ -469,12 +471,10 @@ fn advertise_and_send_collation_by_hash() { rx.await, Ok(full_response) => { // Response is the same for v2. - let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse - = request_v1::CollationFetchingResponse::decode( - &mut full_response.result - .expect("We should have a proper answer").as_ref() - ) - .expect("Decoding should work"); + let (receipt, pov) = decode_collation_response( + full_response.result + .expect("We should have a proper answer").as_ref() + ); assert_eq!(receipt, candidate); assert_eq!(pov, pov_block); } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/validators_buffer.rs b/polkadot/node/network/collator-protocol/src/collator_side/validators_buffer.rs index 5b88efc99d8307594c5ac726d1af5ab141e4718d..1533f2eda5a57d332ea899e1dd81764f31c86dad 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/validators_buffer.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/validators_buffer.rs @@ -90,8 +90,7 @@ impl ValidatorGroupsBuffer { } } - /// Returns discovery ids of validators we have at least one advertised-but-not-fetched - /// collation for. + /// Returns discovery ids of validators we are assigned to in this backing group window. pub fn validators_to_connect(&self) -> Vec { let validators_num = self.validators.len(); let bits = self @@ -99,11 +98,22 @@ impl ValidatorGroupsBuffer { .values() .fold(bitvec![0; validators_num], |acc, next| acc | next); - self.validators + let mut should_be_connected: Vec = self + .validators .iter() .enumerate() .filter_map(|(idx, authority_id)| bits[idx].then_some(authority_id.clone())) - .collect() + .collect(); + + if let Some(last_group) = self.group_infos.iter().last() { + for validator in self.validators.iter().rev().take(last_group.len) { + if !should_be_connected.contains(validator) { + should_be_connected.push(validator.clone()); + } + } + } + + should_be_connected } /// Note a new advertisement, marking that we want to be connected to validators @@ -279,7 +289,7 @@ mod tests { assert_eq!(buf.validators_to_connect(), validators[..2].to_vec()); buf.reset_validator_interest(hash_a, &validators[1]); - assert_eq!(buf.validators_to_connect(), vec![validators[0].clone()]); + assert_eq!(buf.validators_to_connect(), validators[0..2].to_vec()); buf.note_collation_advertised(hash_b, 0, GroupIndex(1), &validators[2..]); assert_eq!(buf.validators_to_connect(), validators[2..].to_vec()); @@ -287,7 +297,11 @@ mod tests { for validator in &validators[2..] { buf.reset_validator_interest(hash_b, validator); } - assert!(buf.validators_to_connect().is_empty()); + let mut expected = validators[2..].to_vec(); + expected.sort(); + let mut result = buf.validators_to_connect(); + result.sort(); + assert_eq!(result, expected); } #[test] @@ -320,10 +334,18 @@ mod tests { } buf.reset_validator_interest(hashes[1], &validators[0]); - assert_eq!(buf.validators_to_connect(), validators[..2].to_vec()); + let mut expected: Vec<_> = validators[..4].iter().cloned().collect(); + let mut result = buf.validators_to_connect(); + expected.sort(); + result.sort(); + assert_eq!(result, expected); buf.reset_validator_interest(hashes[0], &validators[0]); - assert_eq!(buf.validators_to_connect(), vec![validators[1].clone()]); + let mut expected: Vec<_> = validators[1..4].iter().cloned().collect(); + expected.sort(); + let mut result = buf.validators_to_connect(); + result.sort(); + assert_eq!(result, expected); buf.note_collation_advertised(hashes[3], 0, GroupIndex(1), &validators[2..4]); buf.note_collation_advertised( diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index 9348198e70853de3ab954dd51d9c17836f8d8dd6..0f5e0699d85c026cb3ad489de919cbb9c82ed50c 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -89,13 +89,21 @@ pub enum SecondingError { #[error("Received duplicate collation from the peer")] Duplicate, + + #[error("The provided parent head data does not match the hash")] + ParentHeadDataMismatch, } impl SecondingError { /// Returns true if an error indicates that a peer is malicious. pub fn is_malicious(&self) -> bool { use SecondingError::*; - matches!(self, PersistedValidationDataMismatch | CandidateHashMismatch | Duplicate) + matches!( + self, + PersistedValidationDataMismatch | + CandidateHashMismatch | + Duplicate | ParentHeadDataMismatch + ) } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index d6f34fc81b82568f77835a31706abeab5ac51d13..8c3889a3554865c919f2eb33a8f86cce15317ff3 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -41,7 +41,8 @@ use polkadot_node_subsystem_util::{ metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode, }; use polkadot_primitives::{ - CandidateHash, CandidateReceipt, CollatorId, Hash, Id as ParaId, PersistedValidationData, + CandidateHash, CandidateReceipt, CollatorId, Hash, HeadData, Id as ParaId, + PersistedValidationData, }; use tokio_util::sync::CancellationToken; @@ -120,7 +121,7 @@ impl PendingCollation { } } -/// v2 advertisement that was rejected by the backing +/// v2 or v3 advertisement that was rejected by the backing /// subsystem. Validator may fetch it later if its fragment /// membership gets recognized before relay parent goes out of view. #[derive(Debug, Clone)] @@ -143,6 +144,7 @@ pub fn fetched_collation_sanity_check( advertised: &PendingCollation, fetched: &CandidateReceipt, persisted_validation_data: &PersistedValidationData, + maybe_parent_head_and_hash: Option<(HeadData, Hash)>, ) -> Result<(), SecondingError> { if persisted_validation_data.hash() != fetched.descriptor().persisted_validation_data_hash { Err(SecondingError::PersistedValidationDataMismatch) @@ -151,6 +153,8 @@ pub fn fetched_collation_sanity_check( .map_or(false, |pc| pc.candidate_hash() != fetched.hash()) { Err(SecondingError::CandidateHashMismatch) + } else if maybe_parent_head_and_hash.map_or(false, |(head, hash)| head.hash() != hash) { + Err(SecondingError::ParentHeadDataMismatch) } else { Ok(()) } @@ -176,6 +180,9 @@ pub struct PendingCollationFetch { pub candidate_receipt: CandidateReceipt, /// Proof of validity. pub pov: PoV, + /// Optional parachain parent head data. + /// Only needed for elastic scaling. + pub maybe_parent_head_data: Option, } /// The status of the collations in [`CollationsPerRelayParent`]. @@ -359,7 +366,7 @@ impl Future for CollationFetchRequest { }); match &res { - Poll::Ready((_, Ok(request_v1::CollationFetchingResponse::Collation(..)))) => { + Poll::Ready((_, Ok(_))) => { self.span.as_mut().map(|s| s.add_string_tag("success", "true")); }, Poll::Ready((_, Err(_))) => { 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 00bf50013a534d7ccbfb4a6f070e974e5322713a..d23279e875419b19052380d8d25a1cda6f5d4dfa 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -44,7 +44,7 @@ use polkadot_node_subsystem::{ jaeger, messages::{ CanSecondRequest, CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected, - NetworkBridgeEvent, NetworkBridgeTxMessage, ProspectiveParachainsMessage, + NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, ProspectiveParachainsMessage, ProspectiveValidationDataRequest, }, overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, PerLeafSpan, @@ -55,7 +55,7 @@ use polkadot_node_subsystem_util::{ runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, }; use polkadot_primitives::{ - CandidateHash, CollatorId, CoreState, Hash, Id as ParaId, OccupiedCoreAssumption, + CandidateHash, CollatorId, CoreState, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, }; @@ -723,7 +723,7 @@ async fn request_collation( pending_collation, collator_id: collator_id.clone(), collator_protocol_version: peer_protocol_version, - from_collator: response_recv.boxed(), + from_collator: response_recv, cancellation_token: cancellation_token.clone(), span: state .span_per_relay_parent @@ -776,7 +776,8 @@ 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::V2(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, @@ -888,11 +889,16 @@ async fn process_incoming_peer_message( modify_reputation(&mut state.reputation, ctx.sender(), origin, rep).await; } }, + Versioned::V3(V2::AdvertiseCollation { + relay_parent, + candidate_hash, + parent_head_data_hash, + }) | Versioned::V2(V2::AdvertiseCollation { relay_parent, candidate_hash, parent_head_data_hash, - }) => + }) => { if let Err(err) = handle_advertisement( ctx.sender(), state, @@ -914,8 +920,11 @@ async fn process_incoming_peer_message( if let Some(rep) = err.reputation_changes() { modify_reputation(&mut state.reputation, ctx.sender(), origin, rep).await; } - }, - Versioned::V1(V1::CollationSeconded(..)) | Versioned::V2(V2::CollationSeconded(..)) => { + } + }, + Versioned::V1(V1::CollationSeconded(..)) | + Versioned::V2(V2::CollationSeconded(..)) | + Versioned::V3(V2::CollationSeconded(..)) => { gum::warn!( target: LOG_TARGET, peer_id = ?origin, @@ -1469,7 +1478,7 @@ async fn process_msg( "CollateOn message is not expected on the validator side of the protocol", ); }, - DistributeCollation(..) => { + DistributeCollation { .. } => { gum::warn!( target: LOG_TARGET, "DistributeCollation message is not expected on the validator side of the protocol", @@ -1768,14 +1777,21 @@ async fn request_prospective_validation_data( candidate_relay_parent: Hash, parent_head_data_hash: Hash, para_id: ParaId, + maybe_parent_head_data: Option, ) -> std::result::Result, SecondingError> where Sender: CollatorProtocolSenderTrait, { let (tx, rx) = oneshot::channel(); + let parent_head_data = if let Some(head_data) = maybe_parent_head_data { + ParentHeadData::WithData { head_data, hash: parent_head_data_hash } + } else { + ParentHeadData::OnlyHash(parent_head_data_hash) + }; + let request = - ProspectiveValidationDataRequest { para_id, candidate_relay_parent, parent_head_data_hash }; + ProspectiveValidationDataRequest { para_id, candidate_relay_parent, parent_head_data }; sender .send_message(ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx)) @@ -1789,7 +1805,7 @@ where async fn kick_off_seconding( ctx: &mut Context, state: &mut State, - PendingCollationFetch { mut collation_event, candidate_receipt, pov }: PendingCollationFetch, + PendingCollationFetch { mut collation_event, candidate_receipt, pov, maybe_parent_head_data }: PendingCollationFetch, ) -> std::result::Result<(), SecondingError> { let pending_collation = collation_event.pending_collation; let relay_parent = pending_collation.relay_parent; @@ -1813,38 +1829,46 @@ async fn kick_off_seconding( collation_event.pending_collation.commitments_hash = Some(candidate_receipt.commitments_hash); - let pvd = match ( + let (maybe_pvd, maybe_parent_head_and_hash) = match ( collation_event.collator_protocol_version, collation_event.pending_collation.prospective_candidate, ) { (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) if per_relay_parent.prospective_parachains_mode.is_enabled() => - request_prospective_validation_data( + { + let pvd = request_prospective_validation_data( ctx.sender(), relay_parent, parent_head_data_hash, pending_collation.para_id, + maybe_parent_head_data.clone(), ) - .await?, + .await?; + + (pvd, maybe_parent_head_data.map(|head_data| (head_data, parent_head_data_hash))) + }, // Support V2 collators without async backing enabled. - (CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => - request_persisted_validation_data( + (CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => { + let pvd = request_persisted_validation_data( ctx.sender(), candidate_receipt.descriptor().relay_parent, candidate_receipt.descriptor().para_id, ) - .await?, + .await?; + (pvd, None) + }, _ => { // `handle_advertisement` checks for protocol mismatch. return Ok(()) }, - } - .ok_or(SecondingError::PersistedValidationDataNotFound)?; + }; + let pvd = maybe_pvd.ok_or(SecondingError::PersistedValidationDataNotFound)?; fetched_collation_sanity_check( &collation_event.pending_collation, &candidate_receipt, &pvd, + maybe_parent_head_and_hash, )?; ctx.send_message(CandidateBackingMessage::Second( @@ -1875,7 +1899,7 @@ async fn disconnect_inactive_peers( ) { for (peer, peer_data) in peers { if peer_data.is_inactive(&eviction_policy) { - gum::trace!(target: LOG_TARGET, "Disconnecting inactive peer"); + gum::trace!(target: LOG_TARGET, ?peer, "Disconnecting inactive peer"); disconnect_peer(sender, *peer).await; } } @@ -1970,9 +1994,10 @@ async fn handle_collation_fetch_response( ); Err(None) }, - Ok(request_v1::CollationFetchingResponse::Collation(receipt, _)) - if receipt.descriptor().para_id != pending_collation.para_id => - { + Ok( + request_v1::CollationFetchingResponse::Collation(receipt, _) | + request_v1::CollationFetchingResponse::CollationWithParentHeadData { receipt, .. }, + ) if receipt.descriptor().para_id != pending_collation.para_id => { gum::debug!( target: LOG_TARGET, expected_para_id = ?pending_collation.para_id, @@ -2002,6 +2027,33 @@ async fn handle_collation_fetch_response( }, candidate_receipt, pov, + maybe_parent_head_data: None, + }) + }, + Ok(request_v2::CollationFetchingResponse::CollationWithParentHeadData { + receipt, + pov, + parent_head_data, + }) => { + gum::debug!( + target: LOG_TARGET, + para_id = %pending_collation.para_id, + hash = ?pending_collation.relay_parent, + candidate_hash = ?receipt.hash(), + "Received collation (v3)", + ); + let _span = jaeger::Span::new(&pov, "received-collation"); + + metrics_result = Ok(()); + Ok(PendingCollationFetch { + collation_event: CollationEvent { + collator_id, + pending_collation, + collator_protocol_version, + }, + candidate_receipt: receipt, + pov, + maybe_parent_head_data: Some(parent_head_data), }) }, }; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 3a9740149948f4796df9d5f9d775861b4fe901de..1ba6389212cc5beffdd6cb94a0fed194dd1cb048 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -17,6 +17,7 @@ use super::*; use assert_matches::assert_matches; use futures::{executor, future, Future}; +use sc_network::ProtocolName; use sp_core::{crypto::Pair, Encode}; use sp_keyring::Sr25519Keyring; use sp_keystore::Keystore; @@ -559,11 +560,11 @@ fn act_on_advertisement_v2() { .await; response_channel - .send(Ok(request_v1::CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); assert_candidate_backing_second( @@ -761,11 +762,11 @@ fn fetch_one_collation_at_a_time() { candidate_a.descriptor.relay_parent = test_state.relay_parent; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel - .send(Ok(request_v1::CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); assert_candidate_backing_second( @@ -885,19 +886,19 @@ fn fetches_next_collation() { // First request finishes now: response_channel_non_exclusive - .send(Ok(request_v1::CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); response_channel - .send(Ok(request_v1::CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); assert_candidate_backing_second( @@ -1023,11 +1024,11 @@ fn fetch_next_collation_on_invalid_collation() { candidate_a.descriptor.relay_parent = test_state.relay_parent; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel - .send(Ok(request_v1::CollationFetchingResponse::Collation( - candidate_a.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v1::CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); let receipt = assert_candidate_backing_second( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index c5236ef3eb211eedd5b95afb57a609c9b082c6d6..eaa725f2642ed38e3a6f222f9624dd6e2bc4fcce 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -314,11 +314,11 @@ fn v1_advertisement_accepted_and_seconded() { let pov = PoV { block_data: BlockData(vec![1]) }; response_channel - .send(Ok(request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); assert_candidate_backing_second( @@ -565,11 +565,14 @@ fn second_multiple_candidates_per_relay_parent() { let pov = PoV { block_data: BlockData(vec![1]) }; response_channel - .send(Ok(request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v2::CollationFetchingResponse::Collation( + candidate.clone(), + pov.clone(), + ) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); assert_candidate_backing_second( @@ -717,11 +720,11 @@ fn fetched_collation_sanity_check() { let pov = PoV { block_data: BlockData(vec![1]) }; response_channel - .send(Ok(request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode())) + .send(Ok(( + request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) .expect("Sending response should succeed"); // PVD request. @@ -751,6 +754,126 @@ fn fetched_collation_sanity_check() { }); } +#[test] +fn sanity_check_invalid_parent_head_data() { + let test_state = TestState::default(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, .. } = test_harness; + + let pair = CollatorPair::generate().0; + + let head_c = Hash::from_low_u64_be(130); + let head_c_num = 3; + + update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; + + let peer_a = PeerId::random(); + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); + candidate.descriptor.para_id = test_state.chain_ids[0]; + + let commitments = CandidateCommitments { + head_data: HeadData(vec![1, 2, 3]), + horizontal_messages: Default::default(), + upward_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + candidate.commitments_hash = commitments.hash(); + + let parent_head_data = HeadData(vec![4, 2, 0]); + let parent_head_data_hash = parent_head_data.hash(); + let wrong_parent_head_data = HeadData(vec![4, 2]); + + let mut pvd = dummy_pvd(); + pvd.parent_head = parent_head_data; + + candidate.descriptor.persisted_validation_data_hash = pvd.hash(); + + let candidate_hash = candidate.hash(); + + advertise_collation( + &mut virtual_overseer, + peer_a, + head_c, + Some((candidate_hash, parent_head_data_hash)), + ) + .await; + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateBacking( + CandidateBackingMessage::CanSecond(request, tx), + ) => { + assert_eq!(request.candidate_hash, candidate_hash); + assert_eq!(request.candidate_para_id, test_state.chain_ids[0]); + assert_eq!(request.parent_head_data_hash, parent_head_data_hash); + tx.send(true).expect("receiving side should be alive"); + } + ); + + let response_channel = assert_fetch_collation_request( + &mut virtual_overseer, + head_c, + test_state.chain_ids[0], + Some(candidate_hash), + ) + .await; + + let pov = PoV { block_data: BlockData(vec![1]) }; + + response_channel + .send(Ok(( + request_v2::CollationFetchingResponse::CollationWithParentHeadData { + receipt: candidate.clone(), + pov: pov.clone(), + parent_head_data: wrong_parent_head_data, + } + .encode(), + ProtocolName::from(""), + ))) + .expect("Sending response should succeed"); + + // PVD request. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ProspectiveParachains( + ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx), + ) => { + assert_eq!(head_c, request.candidate_relay_parent); + assert_eq!(test_state.chain_ids[0], request.para_id); + tx.send(Some(pvd)).unwrap(); + } + ); + + // Reported malicious. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::NetworkBridgeTx( + NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)), + ) => { + assert_eq!(peer_a, peer_id); + assert_eq!(rep.value, COST_REPORT_BAD.cost_or_benefit()); + } + ); + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + #[test] fn advertisement_spam_protection() { let test_state = TestState::default(); diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index 5d8e245d289a7637f9c64c069f6038ab41946c49..14d59d04f2bff7b838717edb927401035537ff4d 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-dispute-distribution" -version = "1.0.0" +version = "7.0.0" +description = "Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware of a dispute and have the relevant votes." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" @@ -20,14 +24,14 @@ polkadot-node-primitives = { path = "../../primitives" } sc-network = { path = "../../../../substrate/client/network" } sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" schnellru = "0.2.1" -indexmap = "1.9.1" +indexmap = "2.0.0" [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..880d1b18032cc67c546d77a4b68ad59af61ee832 100644 --- a/polkadot/node/network/dispute-distribution/src/tests/mod.rs +++ b/polkadot/node/network/dispute-distribution/src/tests/mod.rs @@ -32,7 +32,7 @@ use futures::{ use futures_timer::Delay; use parity_scale_codec::{Decode, Encode}; -use sc_network::config::RequestResponseConfig; +use sc_network::{config::RequestResponseConfig, ProtocolName}; use polkadot_node_network_protocol::{ request_response::{v1::DisputeRequest, IncomingRequest, ReqProtocolNames}, @@ -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!( @@ -814,7 +832,7 @@ async fn check_sent_requests( if confirm_receive { for req in reqs { req.pending_response.send( - Ok(DisputeResponse::Confirmed.encode()) + Ok((DisputeResponse::Confirmed.encode(), ProtocolName::from(""))) ) .expect("Subsystem should be listening for a response."); } diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index a5bfda73833f32d354bd111948104d9aa73e709f..8d0edc206d728b16e0448c3339c1719f26d81413 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -1,14 +1,19 @@ [package] name = "polkadot-gossip-support" -version = "1.0.0" +version = "7.0.0" +description = "Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and issuing a connection request to the relevant validators on every new session." 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" } sp-core = { path = "../../../../substrate/primitives/core" } +sp-crypto-hashing = { path = "../../../../substrate/primitives/crypto/hashing" } sc-network = { path = "../../../../substrate/client/network" } sc-network-common = { path = "../../../../substrate/client/network/common" } @@ -32,5 +37,7 @@ 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" +parking_lot = "0.12.1" lazy_static = "1.4.0" +quickcheck = "1.0.3" diff --git a/polkadot/node/network/gossip-support/src/lib.rs b/polkadot/node/network/gossip-support/src/lib.rs index 2b2b2d0933e6c0808deb2cd59be19c002cff2624..9f33cd5d8a31697f5d42030fc043643ef1ee247f 100644 --- a/polkadot/node/network/gossip-support/src/lib.rs +++ b/polkadot/node/network/gossip-support/src/lib.rs @@ -32,7 +32,7 @@ use std::{ use futures::{channel::oneshot, select, FutureExt as _}; use futures_timer::Delay; -use rand::{seq::SliceRandom as _, SeedableRng}; +use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use sc_network::{config::parse_addr, Multiaddr}; @@ -63,8 +63,12 @@ use metrics::Metrics; const LOG_TARGET: &str = "parachain::gossip-support"; // How much time should we wait to reissue a connection request // since the last authority discovery resolution failure. +#[cfg(not(test))] const BACKOFF_DURATION: Duration = Duration::from_secs(5); +#[cfg(test)] +const BACKOFF_DURATION: Duration = Duration::from_millis(500); + /// Duration after which we consider low connectivity a problem. /// /// Especially at startup low connectivity is expected (authority discovery cache needs to be @@ -270,9 +274,10 @@ where session_index, ) .await?; - - self.update_authority_ids(sender, session_info.discovery_keys).await; } + // authority_discovery is just a cache so let's try every time we try to re-connect + // if new authorities are present. + self.update_authority_ids(sender, session_info.discovery_keys).await; } } Ok(()) @@ -477,6 +482,7 @@ where match message { Versioned::V1(m) => match m {}, Versioned::V2(m) => match m {}, + Versioned::V3(m) => match m {}, } }, } @@ -502,7 +508,7 @@ where ); } let pretty = PrettyAuthorities(unconnected_authorities); - gum::debug!( + gum::info!( target: LOG_TARGET, ?connected_ratio, ?absolute_connected, @@ -592,7 +598,7 @@ async fn update_gossip_topology( let mut subject = [0u8; 40]; subject[..8].copy_from_slice(b"gossipsu"); subject[8..].copy_from_slice(&randomness); - sp_core::blake2_256(&subject) + sp_crypto_hashing::blake2_256(&subject) }; // shuffle the validators and create the index mapping @@ -606,7 +612,7 @@ async fn update_gossip_topology( .map(|(i, a)| (a.clone(), ValidatorIndex(i as _))) .collect(); - canonical_shuffling.shuffle(&mut rng); + fisher_yates_shuffle(&mut rng, &mut canonical_shuffling[..]); for (i, (_, validator_index)) in canonical_shuffling.iter().enumerate() { shuffled_indices[validator_index.0 as usize] = i; } @@ -626,6 +632,16 @@ async fn update_gossip_topology( Ok(()) } +// Durstenfeld algorithm for the Fisher-Yates shuffle +// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm +fn fisher_yates_shuffle(rng: &mut R, items: &mut [T]) { + for i in (1..items.len()).rev() { + // invariant: elements with index > i have been locked in place. + let index = rng.gen_range(0u32..(i as u32 + 1)); + items.swap(i, index as usize); + } +} + #[overseer::subsystem(GossipSupport, error = SubsystemError, prefix = self::overseer)] impl GossipSupport where diff --git a/polkadot/node/network/gossip-support/src/tests.rs b/polkadot/node/network/gossip-support/src/tests.rs index 2e909bb0a67433e667ae87d679005091fe505810..6817c85f98d87c03b3c252783c464efed88dfdb6 100644 --- a/polkadot/node/network/gossip-support/src/tests.rs +++ b/polkadot/node/network/gossip-support/src/tests.rs @@ -22,14 +22,22 @@ use assert_matches::assert_matches; use async_trait::async_trait; use futures::{executor, future, Future}; use lazy_static::lazy_static; +use quickcheck::quickcheck; +use rand::seq::SliceRandom as _; +use parking_lot::Mutex; use sc_network::multiaddr::Protocol; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch}; use sp_core::crypto::Pair as PairT; use sp_keyring::Sr25519Keyring; +use std::sync::Arc; -use polkadot_node_network_protocol::grid_topology::{SessionGridTopology, TopologyPeerInfo}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + peer_set::ValidationVersion, + ObservedRole, +}; use polkadot_node_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt as _; @@ -49,7 +57,6 @@ const AUTHORITY_KEYRINGS: &[Sr25519Keyring] = &[ ]; lazy_static! { - static ref MOCK_AUTHORITY_DISCOVERY: MockAuthorityDiscovery = MockAuthorityDiscovery::new(); static ref AUTHORITIES: Vec = AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect(); @@ -87,17 +94,14 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle>, - authorities: HashMap>, + addrs: Arc>>>, + authorities: Arc>>>, } impl MockAuthorityDiscovery { - fn new() -> Self { - let authorities: HashMap<_, _> = PAST_PRESENT_FUTURE_AUTHORITIES - .clone() - .into_iter() - .map(|a| (PeerId::random(), a)) - .collect(); + fn new(authorities: Vec) -> Self { + let authorities: HashMap<_, _> = + authorities.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); let addrs = authorities .clone() .into_iter() @@ -107,10 +111,37 @@ impl MockAuthorityDiscovery { }) .collect(); Self { - addrs, - authorities: authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + addrs: Arc::new(Mutex::new(addrs)), + authorities: Arc::new(Mutex::new( + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + )), } } + + fn authorities(&self) -> HashMap> { + self.authorities.lock().clone() + } + + fn add_more_authorties( + &self, + new_known: Vec, + ) -> HashMap> { + let authorities: HashMap<_, _> = + new_known.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); + let addrs: HashMap> = authorities + .clone() + .into_iter() + .map(|(p, a)| { + let multiaddr = Multiaddr::empty().with(Protocol::P2p(p.into())); + (a, HashSet::from([multiaddr])) + }) + .collect(); + let authorities: HashMap> = + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(); + self.addrs.as_ref().lock().extend(addrs); + self.authorities.as_ref().lock().extend(authorities.clone()); + authorities + } } #[async_trait] @@ -119,19 +150,23 @@ impl AuthorityDiscovery for MockAuthorityDiscovery { &mut self, authority: polkadot_primitives::AuthorityDiscoveryId, ) -> Option> { - self.addrs.get(&authority).cloned() + self.addrs.lock().get(&authority).cloned() } + async fn get_authority_ids_by_peer_id( &mut self, peer_id: polkadot_node_network_protocol::PeerId, ) -> Option> { - self.authorities.get(&peer_id).cloned() + self.authorities.as_ref().lock().get(&peer_id).cloned() } } -async fn get_multiaddrs(authorities: Vec) -> Vec> { +async fn get_multiaddrs( + authorities: Vec, + mock_authority_discovery: MockAuthorityDiscovery, +) -> Vec> { let mut addrs = Vec::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority).await { addrs.push(addr); @@ -142,9 +177,10 @@ async fn get_multiaddrs(authorities: Vec) -> Vec, + mock_authority_discovery: MockAuthorityDiscovery, ) -> HashMap> { let mut addrs = HashMap::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority.clone()).await { addrs.insert(authority, addr); @@ -153,12 +189,10 @@ async fn get_address_map( addrs } -fn make_subsystem() -> GossipSupport { - GossipSupport::new( - make_ferdie_keystore(), - MOCK_AUTHORITY_DISCOVERY.clone(), - Metrics::new_dummy(), - ) +fn make_subsystem_with_authority_discovery( + mock: MockAuthorityDiscovery, +) -> GossipSupport { + GossipSupport::new(make_ferdie_keystore(), mock, Metrics::new_dummy()) } fn test_harness, AD: AuthorityDiscovery>( @@ -289,59 +323,65 @@ async fn test_neighbors(overseer: &mut VirtualOverseer, expected_session: Sessio #[test] fn issues_a_connection_request_on_new_session() { + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let hash = Hash::repeat_byte(0xAA); - let state = test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + let state = test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone).await); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - test_neighbors(overseer, 1).await; + test_neighbors(overseer, 1).await; - virtual_overseer - }); + virtual_overseer + }, + ); assert_eq!(state.last_session_index, Some(1)); assert!(state.last_failure.is_none()); @@ -361,6 +401,7 @@ fn issues_a_connection_request_on_new_session() { tx.send(Ok(1)).unwrap(); } ); + virtual_overseer }); @@ -412,7 +453,7 @@ fn issues_a_connection_request_on_new_session() { validator_addrs, peer_set, }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await); assert_eq!(peer_set, PeerSet::Validation); } ); @@ -428,125 +469,405 @@ fn issues_a_connection_request_on_new_session() { #[test] fn issues_connection_request_to_past_present_future() { let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, mock_authority_discovery.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + test_neighbors(overseer, 1).await; + + virtual_overseer + }, + ); +} + +// Test we notify peer about learning of the authority ID after session boundary, when we couldn't +// connect to more than 1/3 of the authorities. +#[test] +fn issues_update_authorities_after_session() { + let hash = Hash::repeat_byte(0xAA); + + let mut authorities = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + let unknown_at_session = authorities.split_off(authorities.len() / 3 - 1); + let mut authority_discovery_mock = MockAuthorityDiscovery::new(authorities); + + test_harness( + make_subsystem_with_authority_discovery(authority_discovery_mock.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // 1. Initialize with the first leaf in the session. + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, authority_discovery_mock.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::CurrentBabeEpoch(tx), + )) => { + let _ = tx.send(Ok(BabeEpoch { + epoch_index: 2 as _, + start_slot: 0.into(), + duration: 200, + authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)], + randomness: [0u8; 32], + config: BabeEpochConfiguration { + c: (1, 4), + allowed_slots: AllowedSlots::PrimarySlots, + }, + })).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::NewGossipTopology { + session: _, + local_index: _, + canonical_shuffling: _, + shuffled_indices: _, + }) => { + + } + ); + + // 2. Connect all authorities that are known so far. + let known_authorities = authority_discovery_mock.authorities(); + for (peer_id, _id) in known_authorities.iter() { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); + Delay::new(BACKOFF_DURATION).await; + // 3. Send a new leaf after BACKOFF_DURATION and check UpdateAuthority is emitted for + // all known connected peers. + let hash = Hash::repeat_byte(0xBB); + overseer_signal_active_leaves(overseer, hash).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 0..known_authorities.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_eq!(authority_discovery_mock.get_authority_ids_by_peer_id(peer_id).await.unwrap_or_default(), authority_ids); + } + ); } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + // 4. Connect more authorities except one + let newly_added = authority_discovery_mock.add_more_authorties(unknown_at_session); + let mut newly_added_iter = newly_added.iter(); + let unconnected_at_last_retry = newly_added_iter + .next() + .map(|(peer_id, authority_id)| (*peer_id, authority_id.clone())) + .unwrap(); + for (peer_id, _) in newly_added_iter { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES - .iter() - .cloned() - .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) - .collect(); + // 5. Send a new leaf and check UpdateAuthority is emitted only for the newly connected + // peers. + let hash = Hash::repeat_byte(0xCC); + Delay::new(BACKOFF_DURATION).await; + overseer_signal_active_leaves(overseer, hash).await; - let addrs = get_multiaddrs(all_without_ferdie).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_eq!(validator_addrs, addrs); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); - // Ensure neighbors are unaffected - test_neighbors(overseer, 1).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); - virtual_overseer - }); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 1..newly_added.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_ne!(peer_id, unconnected_at_last_retry.0); + assert_eq!(newly_added.get(&peer_id).cloned().unwrap_or_default(), authority_ids); + } + ); + } + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + virtual_overseer + }, + ); } #[test] fn disconnect_when_not_in_past_present_future() { sp_tracing::try_init_simple(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - let mut heute_leider_nicht = make_session_info(); - heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); - tx.send(Ok(Some(heute_leider_nicht))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut heute_leider_nicht = make_session_info(); + heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); + tx.send(Ok(Some(heute_leider_nicht))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert!(validator_addrs.is_empty()); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert!(validator_addrs.is_empty()); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - virtual_overseer - }); + virtual_overseer + }, + ); } #[test] @@ -577,13 +898,15 @@ fn test_log_output() { #[test] fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { let hash = Hash::repeat_byte(0xAA); - let mut state = make_subsystem(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let state = make_subsystem_with_authority_discovery(mock_authority_discovery.clone()); // There will be two lookup failures: let alice = Sr25519Keyring::Alice.public().into(); let bob = Sr25519Keyring::Bob.public().into(); - let alice_addr = state.authority_discovery.addrs.remove(&alice); - state.authority_discovery.addrs.remove(&bob); - + let alice_addr = state.authority_discovery.addrs.lock().remove(&alice); + state.authority_discovery.addrs.lock().remove(&bob); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let mut state = { let alice = alice.clone(); let bob = bob.clone(); @@ -631,7 +954,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone.clone()).await; expected.remove(&alice); expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); @@ -650,7 +973,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { assert!(state.last_failure.is_some()); state.last_failure = state.last_failure.and_then(|i| i.checked_sub(BACKOFF_DURATION)); // One error less: - state.authority_discovery.addrs.insert(alice, alice_addr.unwrap()); + state.authority_discovery.addrs.lock().insert(alice, alice_addr.unwrap()); let hash = Hash::repeat_byte(0xBB); let state = test_harness(state, |mut virtual_overseer| async move { @@ -696,7 +1019,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await; expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); assert_eq!(validator_addrs.into_iter().flat_map(|v| v.into_iter()).collect::>(), expected); @@ -710,3 +1033,23 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { assert_eq!(state.last_session_index, Some(1)); assert!(state.last_failure.is_none()); } + +// note: this test was added at a time where the default `rand::SliceRandom::shuffle` +// function was used to shuffle authorities for the topology and ensures backwards compatibility. +// +// in the same commit, an explicit fisher-yates implementation was added in place of the unspecified +// behavior of that function. If this test begins to fail at some point in the future, it can simply +// be removed as the desired behavior has been preserved. +quickcheck! { + fn rng_shuffle_equals_fisher_yates(x: Vec, seed_base: u8) -> bool { + let mut rng1: ChaCha20Rng = SeedableRng::from_seed([seed_base; 32]); + let mut rng2: ChaCha20Rng = SeedableRng::from_seed([seed_base; 32]); + + let mut data1 = x.clone(); + let mut data2 = x; + + data1.shuffle(&mut rng1); + crate::fisher_yates_shuffle(&mut rng2, &mut data2[..]); + data1 == data2 + } +} diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index 379334ded24acde67100de5b61069d5cb5909ce4..7efa0b8ca820d651d77a53a95d61e7672c5f9737 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "polkadot-node-network-protocol" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" } @@ -18,7 +21,7 @@ sc-network = { path = "../../../../substrate/client/network" } sc-authority-discovery = { path = "../../../../substrate/client/authority-discovery" } strum = { version = "0.24", features = ["derive"] } futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" rand = "0.8" derive_more = "0.99" diff --git a/polkadot/node/network/protocol/src/grid_topology.rs b/polkadot/node/network/protocol/src/grid_topology.rs index 99dd513c4d7909012162a075f6bbc7ce041377fb..3c4372a27a2c42e20227adbe15d2cc7fcade3d12 100644 --- a/polkadot/node/network/protocol/src/grid_topology.rs +++ b/polkadot/node/network/protocol/src/grid_topology.rs @@ -73,14 +73,42 @@ 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 } } + /// Updates the known peer ids for the passed authorithies ids. + pub fn update_authority_ids( + &mut self, + peer_id: PeerId, + ids: &HashSet, + ) -> bool { + let mut updated = false; + if !self.peer_ids.contains(&peer_id) { + for peer in self + .canonical_shuffling + .iter_mut() + .filter(|peer| ids.contains(&peer.discovery_id)) + { + peer.peer_ids.push(peer_id); + self.peer_ids.insert(peer_id); + updated = true; + } + } + updated + } /// Produces the outgoing routing logic for a particular peer. /// /// Returns `None` if the validator index is out of bounds. @@ -111,6 +139,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 { @@ -256,6 +289,7 @@ impl GridNeighbors { pub struct SessionGridTopologyEntry { topology: SessionGridTopology, local_neighbors: GridNeighbors, + local_index: Option, } impl SessionGridTopologyEntry { @@ -273,6 +307,30 @@ 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) + } + + /// Updates the known peer ids for the passed authorithies ids. + pub fn update_authority_ids( + &mut self, + peer_id: PeerId, + ids: &HashSet, + ) -> bool { + let peer_id_updated = self.topology.update_authority_ids(peer_id, ids); + // If we added a new peer id we need to recompute the grid neighbors, so that + // neighbors_x and neighbors_y reflect the right peer ids. + if peer_id_updated { + if let Some(local_index) = self.local_index.as_ref() { + if let Some(new_grid) = self.topology.compute_grid_neighbors_for(*local_index) { + self.local_neighbors = new_grid; + } + } + } + peer_id_updated + } } /// A set of topologies indexed by session @@ -287,6 +345,20 @@ impl SessionGridTopologies { self.inner.get(&session).and_then(|val| val.0.as_ref()) } + /// Updates the known peer ids for the passed authorithies ids. + pub fn update_authority_ids( + &mut self, + peer_id: PeerId, + ids: &HashSet, + ) -> bool { + self.inner + .iter_mut() + .map(|(_, topology)| { + topology.0.as_mut().map(|topology| topology.update_authority_ids(peer_id, ids)) + }) + .any(|updated| updated.unwrap_or_default()) + } + /// Increase references counter for a specific topology pub fn inc_session_refs(&mut self, session: SessionIndex) { self.inner.entry(session).or_insert((None, 0)).1 += 1; @@ -315,7 +387,7 @@ impl SessionGridTopologies { .and_then(|l| topology.compute_grid_neighbors_for(l)) .unwrap_or_else(GridNeighbors::empty); - entry.0 = Some(SessionGridTopologyEntry { topology, local_neighbors }); + entry.0 = Some(SessionGridTopologyEntry { topology, local_neighbors, local_index }); } } } @@ -347,8 +419,10 @@ impl Default for SessionBoundGridTopologyStorage { topology: SessionGridTopology { shuffled_indices: Vec::new(), canonical_shuffling: Vec::new(), + peer_ids: Default::default(), }, local_neighbors: GridNeighbors::empty(), + local_index: None, }, }, prev_topology: None, @@ -393,7 +467,7 @@ impl SessionBoundGridTopologyStorage { let old_current = std::mem::replace( &mut self.current_topology, GridTopologySessionBound { - entry: SessionGridTopologyEntry { topology, local_neighbors }, + entry: SessionGridTopologyEntry { topology, local_neighbors, local_index }, session_index, }, ); diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index 901ac99b66933a77b95ccb1c55fd22930fc5a34a..7a0ff9f4fa9a26c2ec1f7d97b03d61424e83b98b 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -253,25 +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), + /// V3 type + V3(V3), } -impl Versioned<&'_ V1, &'_ V2> { +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::V3(inner) => Versioned::V3(inner.clone()), } } } /// All supported versions of the validation protocol message. -pub type VersionedValidationProtocol = Versioned; +pub type VersionedValidationProtocol = + Versioned; impl From for VersionedValidationProtocol { fn from(v1: v1::ValidationProtocol) -> Self { @@ -285,6 +289,12 @@ impl From for VersionedValidationProtocol { } } +impl From for VersionedValidationProtocol { + fn from(v3: v3::ValidationProtocol) -> Self { + VersionedValidationProtocol::V3(v3) + } +} + /// All supported versions of the collation protocol message. pub type VersionedCollationProtocol = Versioned; @@ -307,12 +317,12 @@ 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::V3(x) => Versioned::V3(x.into()), } } } }; } - /// Implement `TryFrom` for one versioned enum variant into the inner type. /// `$m_ty::$variant(inner) -> Ok(inner)` macro_rules! impl_versioned_try_from { @@ -320,7 +330,8 @@ macro_rules! impl_versioned_try_from { $from:ty, $out:ty, $v1_pat:pat => $v1_out:expr, - $v2_pat:pat => $v2_out:expr + $v2_pat:pat => $v2_out:expr, + $v3_pat:pat => $v3_out:expr ) => { impl TryFrom<$from> for $out { type Error = crate::WrongVariant; @@ -330,6 +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::V3($v3_pat) => Ok(Versioned::V3($v3_out)), _ => Err(crate::WrongVariant), } } @@ -343,6 +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::V3($v3_pat) => Ok(Versioned::V3($v3_out.clone())), _ => Err(crate::WrongVariant), } } @@ -351,8 +364,11 @@ macro_rules! impl_versioned_try_from { } /// Version-annotated messages used by the bitfield distribution subsystem. -pub type BitfieldDistributionMessage = - Versioned; +pub type BitfieldDistributionMessage = Versioned< + v1::BitfieldDistributionMessage, + v2::BitfieldDistributionMessage, + v3::BitfieldDistributionMessage, +>; impl_versioned_full_protocol_from!( BitfieldDistributionMessage, VersionedValidationProtocol, @@ -362,12 +378,16 @@ impl_versioned_try_from!( VersionedValidationProtocol, BitfieldDistributionMessage, v1::ValidationProtocol::BitfieldDistribution(x) => x, - v2::ValidationProtocol::BitfieldDistribution(x) => x + v2::ValidationProtocol::BitfieldDistribution(x) => x, + v3::ValidationProtocol::BitfieldDistribution(x) => x ); /// Version-annotated messages used by the statement distribution subsystem. -pub type StatementDistributionMessage = - Versioned; +pub type StatementDistributionMessage = Versioned< + v1::StatementDistributionMessage, + v2::StatementDistributionMessage, + v3::StatementDistributionMessage, +>; impl_versioned_full_protocol_from!( StatementDistributionMessage, VersionedValidationProtocol, @@ -377,12 +397,16 @@ impl_versioned_try_from!( VersionedValidationProtocol, StatementDistributionMessage, v1::ValidationProtocol::StatementDistribution(x) => x, - v2::ValidationProtocol::StatementDistribution(x) => x + v2::ValidationProtocol::StatementDistribution(x) => x, + v3::ValidationProtocol::StatementDistribution(x) => x ); /// Version-annotated messages used by the approval distribution subsystem. -pub type ApprovalDistributionMessage = - Versioned; +pub type ApprovalDistributionMessage = Versioned< + v1::ApprovalDistributionMessage, + v2::ApprovalDistributionMessage, + v3::ApprovalDistributionMessage, +>; impl_versioned_full_protocol_from!( ApprovalDistributionMessage, VersionedValidationProtocol, @@ -392,13 +416,18 @@ impl_versioned_try_from!( VersionedValidationProtocol, ApprovalDistributionMessage, v1::ValidationProtocol::ApprovalDistribution(x) => x, - v2::ValidationProtocol::ApprovalDistribution(x) => x + v2::ValidationProtocol::ApprovalDistribution(x) => x, + v3::ValidationProtocol::ApprovalDistribution(x) => x ); /// Version-annotated messages used by the gossip-support subsystem (this is void). -pub type GossipSupportNetworkMessage = - Versioned; +pub type GossipSupportNetworkMessage = Versioned< + v1::GossipSupportNetworkMessage, + v2::GossipSupportNetworkMessage, + v3::GossipSupportNetworkMessage, +>; + // This is a void enum placeholder, so never gets sent over the wire. impl TryFrom for GossipSupportNetworkMessage { type Error = WrongVariant; @@ -426,6 +455,7 @@ impl_versioned_try_from!( VersionedCollationProtocol, CollatorProtocolMessage, v1::CollationProtocol::CollatorProtocol(x) => x, + v2::CollationProtocol::CollatorProtocol(x) => x, v2::CollationProtocol::CollatorProtocol(x) => x ); @@ -439,7 +469,7 @@ pub mod v1 { }; use polkadot_node_primitives::{ - approval::{IndirectAssignmentCert, IndirectSignedApprovalVote}, + approval::v1::{IndirectAssignmentCert, IndirectSignedApprovalVote}, UncheckedSignedFullStatement, }; @@ -598,7 +628,7 @@ pub mod v2 { }; use polkadot_node_primitives::{ - approval::{IndirectAssignmentCert, IndirectSignedApprovalVote}, + approval::v1::{IndirectAssignmentCert, IndirectSignedApprovalVote}, UncheckedSignedFullStatement, }; @@ -839,3 +869,65 @@ pub mod v2 { payload } } + +/// 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::v2::{ + CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2, + }; + + /// 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, + StatementFilter, + }; + + /// Network messages used by the approval distribution subsystem. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub enum ApprovalDistributionMessage { + /// Assignments for candidates in recent, unfinalized blocks. + /// We use a bitfield to reference claimed candidates, where the bit index is equal to + /// candidate index. + /// + /// Actually checking the assignment may yield a different result. + /// + /// TODO at next protocol upgrade opportunity: + /// - remove redundancy `candidate_index` vs `core_index` + /// - `` + #[codec(index = 0)] + Assignments(Vec<(IndirectAssignmentCertV2, CandidateBitfield)>), + /// Approvals for candidates in some recent, unfinalized block. + #[codec(index = 1)] + Approvals(Vec), + } + + /// All network messages on the validation peer-set. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)] + pub enum ValidationProtocol { + /// Bitfield distribution messages + #[codec(index = 1)] + #[from] + BitfieldDistribution(BitfieldDistributionMessage), + /// Statement distribution messages + #[codec(index = 3)] + #[from] + StatementDistribution(StatementDistributionMessage), + /// Approval distribution messages + #[codec(index = 4)] + #[from] + ApprovalDistribution(ApprovalDistributionMessage), + } +} + +/// Returns the subset of `peers` with the specified `version`. +pub fn filter_by_peer_version( + peers: &[(PeerId, peer_set::ProtocolVersion)], + version: peer_set::ProtocolVersion, +) -> Vec { + peers.iter().filter(|(_, v)| v == &version).map(|(p, _)| *p).collect::>() +} diff --git a/polkadot/node/network/protocol/src/peer_set.rs b/polkadot/node/network/protocol/src/peer_set.rs index 8dd68b297e3000ec4fc0847b047a1323aae97353..cb329607ad6127024af9e6d6bc3c75c7e813c0e3 100644 --- a/polkadot/node/network/protocol/src/peer_set.rs +++ b/polkadot/node/network/protocol/src/peer_set.rs @@ -21,6 +21,7 @@ use polkadot_primitives::Hash; use sc_network::{ config::{NonDefaultSetConfig, SetConfig}, types::ProtocolName, + NotificationService, }; use std::{ collections::{hash_map::Entry, HashMap}, @@ -68,47 +69,59 @@ impl PeerSet { self, is_authority: IsAuthority, peerset_protocol_names: &PeerSetProtocolNames, - ) -> NonDefaultSetConfig { + ) -> (NonDefaultSetConfig, (PeerSet, Box)) { // 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 { - PeerSet::Validation => NonDefaultSetConfig { - notifications_protocol: protocol, - fallback_names, - max_notification_size, - handshake: None, - set_config: SetConfig { - // we allow full nodes to connect to validators for gossip - // to ensure any `MIN_GOSSIP_PEERS` always include reserved peers - // we limit the amount of non-reserved slots to be less - // than `MIN_GOSSIP_PEERS` in total - in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1, - out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1, - reserved_nodes: Vec::new(), - non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept, - }, + PeerSet::Validation => { + let (config, notification_service) = NonDefaultSetConfig::new( + protocol, + fallback_names, + max_notification_size, + None, + SetConfig { + // we allow full nodes to connect to validators for gossip + // to ensure any `MIN_GOSSIP_PEERS` always include reserved peers + // we limit the amount of non-reserved slots to be less + // than `MIN_GOSSIP_PEERS` in total + in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1, + out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1, + reserved_nodes: Vec::new(), + non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept, + }, + ); + + (config, (PeerSet::Validation, notification_service)) }, - PeerSet::Collation => NonDefaultSetConfig { - notifications_protocol: protocol, - fallback_names, - max_notification_size, - handshake: None, - set_config: SetConfig { - // Non-authority nodes don't need to accept incoming connections on this peer - // set: - in_peers: if is_authority == IsAuthority::Yes { 100 } else { 0 }, - out_peers: 0, - reserved_nodes: Vec::new(), - non_reserved_mode: if is_authority == IsAuthority::Yes { - sc_network::config::NonReservedPeerMode::Accept - } else { - sc_network::config::NonReservedPeerMode::Deny + PeerSet::Collation => { + let (config, notification_service) = NonDefaultSetConfig::new( + protocol, + fallback_names, + max_notification_size, + None, + SetConfig { + // Non-authority nodes don't need to accept incoming connections on this + // peer set: + in_peers: if is_authority == IsAuthority::Yes { 100 } else { 0 }, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: if is_authority == IsAuthority::Yes { + sc_network::config::NonReservedPeerMode::Accept + } else { + sc_network::config::NonReservedPeerMode::Deny + }, }, - }, + ); + + (config, (PeerSet::Collation, notification_service)) }, } } @@ -119,7 +132,7 @@ impl PeerSet { /// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`]. pub fn get_main_version(self) -> ProtocolVersion { match self { - PeerSet::Validation => ValidationVersion::V2.into(), + PeerSet::Validation => ValidationVersion::V3.into(), PeerSet::Collation => CollationVersion::V2.into(), } } @@ -147,6 +160,8 @@ impl PeerSet { Some("validation/1") } else if version == ValidationVersion::V2.into() { Some("validation/2") + } else if version == ValidationVersion::V3.into() { + Some("validation/3") } else { None }, @@ -195,7 +210,7 @@ impl IndexMut for PerPeerSet { pub fn peer_sets_info( is_authority: IsAuthority, peerset_protocol_names: &PeerSetProtocolNames, -) -> Vec { +) -> Vec<(NonDefaultSetConfig, (PeerSet, Box))> { PeerSet::iter() .map(|s| s.get_info(is_authority, &peerset_protocol_names)) .collect() @@ -218,6 +233,10 @@ pub enum ValidationVersion { V1 = 1, /// The second version. V2 = 2, + /// 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. @@ -274,10 +293,12 @@ impl From for ProtocolVersion { } /// On the wire protocol name to [`PeerSet`] mapping. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct PeerSetProtocolNames { protocols: HashMap, names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>, + genesis_hash: Hash, + fork_id: Option, } impl PeerSetProtocolNames { @@ -312,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. @@ -416,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/protocol/src/request_response/mod.rs b/polkadot/node/network/protocol/src/request_response/mod.rs index 96f7adeb29ba02987857fbac1eeed7b6770d9a35..2fb62f56d104fbab804dc2d64941f06c281efb0f 100644 --- a/polkadot/node/network/protocol/src/request_response/mod.rs +++ b/polkadot/node/network/protocol/src/request_response/mod.rs @@ -30,7 +30,24 @@ //! `trait IsRequest` .... A trait describing a particular request. It is used for gathering meta //! data, like what is the corresponding response type. //! -//! Versioned (v1 module): The actual requests and responses as sent over the network. +//! ## Versioning +//! +//! Versioning for request-response protocols can be done in multiple ways. +//! +//! If you're just changing the protocol name but the binary payloads are the same, just add a new +//! `fallback_name` to the protocol config. +//! +//! One way in which versioning has historically been achieved for req-response protocols is to +//! bundle the new req-resp version with an upgrade of a notifications protocol. The subsystem would +//! then know which request version to use based on stored data about the peer's notifications +//! protocol version. +//! +//! When bumping a notifications protocol version is not needed/desirable, you may add a new +//! req-resp protocol and set the old request as a fallback (see +//! `OutgoingRequest::new_with_fallback`). A request with the new version will be attempted and if +//! the protocol is refused by the peer, the fallback protocol request will be used. +//! Information about the actually used protocol will be returned alongside the raw response, so +//! that you know how to decode it. use std::{collections::HashMap, time::Duration, u64}; @@ -188,11 +205,11 @@ impl Protocol { tx: Option>, ) -> RequestResponseConfig { let name = req_protocol_names.get_name(self); - let fallback_names = self.get_fallback_names(); + let legacy_names = self.get_legacy_name().into_iter().map(Into::into).collect(); match self { Protocol::ChunkFetchingV1 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, max_response_size: POV_RESPONSE_SIZE as u64 * 3, // We are connected to all validators: @@ -202,7 +219,7 @@ impl Protocol { Protocol::CollationFetchingV1 | Protocol::CollationFetchingV2 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, max_response_size: POV_RESPONSE_SIZE, // Taken from initial implementation in collator protocol: @@ -211,7 +228,7 @@ impl Protocol { }, Protocol::PoVFetchingV1 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, max_response_size: POV_RESPONSE_SIZE, request_timeout: POV_REQUEST_TIMEOUT_CONNECTED, @@ -219,7 +236,7 @@ impl Protocol { }, Protocol::AvailableDataFetchingV1 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, // Available data size is dominated by the PoV size. max_response_size: POV_RESPONSE_SIZE, @@ -228,7 +245,7 @@ impl Protocol { }, Protocol::StatementFetchingV1 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, // Available data size is dominated code size. max_response_size: STATEMENT_RESPONSE_SIZE, @@ -246,17 +263,17 @@ impl Protocol { }, Protocol::DisputeSendingV1 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, - /// Responses are just confirmation, in essence not even a bit. So 100 seems - /// plenty. + // Responses are just confirmation, in essence not even a bit. So 100 seems + // plenty. max_response_size: 100, request_timeout: DISPUTE_REQUEST_TIMEOUT, inbound_queue: tx, }, Protocol::AttestedCandidateV2 => RequestResponseConfig { name, - fallback_names, + fallback_names: legacy_names, max_request_size: 1_000, max_response_size: ATTESTED_CANDIDATE_RESPONSE_SIZE, request_timeout: ATTESTED_CANDIDATE_TIMEOUT, @@ -328,12 +345,9 @@ impl Protocol { } } - /// Fallback protocol names of this protocol, as understood by substrate networking. - fn get_fallback_names(self) -> Vec { - self.get_legacy_name().into_iter().map(Into::into).collect() - } - /// Legacy protocol name associated with each peer set, if any. + /// The request will be tried on this legacy protocol name if the remote refuses to speak the + /// protocol. const fn get_legacy_name(self) -> Option<&'static str> { match self { Protocol::ChunkFetchingV1 => Some("/polkadot/req_chunk/1"), @@ -360,6 +374,7 @@ pub trait IsRequest { } /// Type for getting on the wire [`Protocol`] names using genesis hash & fork id. +#[derive(Clone)] pub struct ReqProtocolNames { names: HashMap, } diff --git a/polkadot/node/network/protocol/src/request_response/outgoing.rs b/polkadot/node/network/protocol/src/request_response/outgoing.rs index c613d5778f5eb5d21bbdaecfd8e4493ccac14117..88439ad40367d7303b63a7fa97ebfb6fd9bb89e4 100644 --- a/polkadot/node/network/protocol/src/request_response/outgoing.rs +++ b/polkadot/node/network/protocol/src/request_response/outgoing.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use futures::{channel::oneshot, prelude::Future}; +use futures::{channel::oneshot, prelude::Future, FutureExt}; +use network::ProtocolName; use parity_scale_codec::{Decode, Encode, Error as DecodingError}; use sc_network as network; @@ -49,20 +50,6 @@ pub enum Requests { } impl Requests { - /// Get the protocol this request conforms to. - pub fn get_protocol(&self) -> Protocol { - match self { - Self::ChunkFetchingV1(_) => Protocol::ChunkFetchingV1, - Self::CollationFetchingV1(_) => Protocol::CollationFetchingV1, - Self::CollationFetchingV2(_) => Protocol::CollationFetchingV2, - Self::PoVFetchingV1(_) => Protocol::PoVFetchingV1, - Self::AvailableDataFetchingV1(_) => Protocol::AvailableDataFetchingV1, - Self::StatementFetchingV1(_) => Protocol::StatementFetchingV1, - Self::DisputeSendingV1(_) => Protocol::DisputeSendingV1, - Self::AttestedCandidateV2(_) => Protocol::AttestedCandidateV2, - } - } - /// Encode the request. /// /// The corresponding protocol is returned as well, as we are now leaving typed territory. @@ -85,7 +72,7 @@ impl Requests { } /// Used by the network to send us a response to a request. -pub type ResponseSender = oneshot::Sender, network::RequestFailure>>; +pub type ResponseSender = oneshot::Sender, ProtocolName), network::RequestFailure>>; /// Any error that can occur when sending a request. #[derive(Debug, thiserror::Error)] @@ -128,11 +115,13 @@ impl RequestError { /// When using `Recipient::Authority`, the addresses can be found thanks to the authority /// discovery system. #[derive(Debug)] -pub struct OutgoingRequest { +pub struct OutgoingRequest { /// Intended recipient of this request. pub peer: Recipient, /// The actual request to send over the wire. pub payload: Req, + /// Optional fallback request and protocol. + pub fallback_request: Option<(FallbackReq, Protocol)>, /// Sender which is used by networking to get us back a response. pub pending_response: ResponseSender, } @@ -149,10 +138,12 @@ pub enum Recipient { /// Responses received for an `OutgoingRequest`. pub type OutgoingResult = Result; -impl OutgoingRequest +impl OutgoingRequest where Req: IsRequest + Encode, Req::Response: Decode, + FallbackReq: IsRequest + Encode, + FallbackReq::Response: Decode, { /// Create a new `OutgoingRequest`. /// @@ -163,24 +154,54 @@ where payload: Req, ) -> (Self, impl Future>) { let (tx, rx) = oneshot::channel(); - let r = Self { peer, payload, pending_response: tx }; - (r, receive_response::(rx)) + let r = Self { peer, payload, pending_response: tx, fallback_request: None }; + (r, receive_response::(rx.map(|r| r.map(|r| r.map(|(resp, _)| resp))))) } + /// Create a new `OutgoingRequest` with a fallback in case the remote does not support this + /// protocol. Useful when adding a new version of a req-response protocol, to achieve + /// compatibility with the older version. + /// + /// Returns a raw `Vec` response over the channel. Use the associated `ProtocolName` to know + /// which request was the successful one and appropriately decode the response. + // WARNING: This is commented for now because it's not used yet. + // If you need it, make sure to test it. You may need to enable the V1 substream upgrade + // protocol, unless libp2p was in the meantime updated to a version that fixes the problem + // described in https://github.com/libp2p/rust-libp2p/issues/5074 + // pub fn new_with_fallback( + // peer: Recipient, + // payload: Req, + // fallback_request: FallbackReq, + // ) -> (Self, impl Future, ProtocolName)>>) { + // let (tx, rx) = oneshot::channel(); + // let r = Self { + // peer, + // payload, + // pending_response: tx, + // fallback_request: Some((fallback_request, FallbackReq::PROTOCOL)), + // }; + // (r, async { Ok(rx.await??) }) + // } + /// Encode a request into a `Vec`. /// /// As this throws away type information, we also return the `Protocol` this encoded request /// adheres to. pub fn encode_request(self) -> (Protocol, OutgoingRequest>) { - let OutgoingRequest { peer, payload, pending_response } = self; - let encoded = OutgoingRequest { peer, payload: payload.encode(), pending_response }; + let OutgoingRequest { peer, payload, pending_response, fallback_request } = self; + let encoded = OutgoingRequest { + peer, + payload: payload.encode(), + fallback_request: fallback_request.map(|(r, p)| (r.encode(), p)), + pending_response, + }; (Req::PROTOCOL, encoded) } } /// Future for actually receiving a typed response for an `OutgoingRequest`. async fn receive_response( - rec: oneshot::Receiver, network::RequestFailure>>, + rec: impl Future, network::RequestFailure>, oneshot::Canceled>>, ) -> OutgoingResult where Req: IsRequest, diff --git a/polkadot/node/network/protocol/src/request_response/v1.rs b/polkadot/node/network/protocol/src/request_response/v1.rs index 0832593a6a3d938cc4349c57972b850023ae02f6..ba29b32c4ce036005d691e9e495dc6e42c3f3d81 100644 --- a/polkadot/node/network/protocol/src/request_response/v1.rs +++ b/polkadot/node/network/protocol/src/request_response/v1.rs @@ -22,7 +22,8 @@ use polkadot_node_primitives::{ AvailableData, DisputeMessage, ErasureChunk, PoV, Proof, UncheckedDisputeMessage, }; use polkadot_primitives::{ - CandidateHash, CandidateReceipt, CommittedCandidateReceipt, Hash, Id as ParaId, ValidatorIndex, + CandidateHash, CandidateReceipt, CommittedCandidateReceipt, Hash, HeadData, Id as ParaId, + ValidatorIndex, }; use super::{IsRequest, Protocol}; @@ -103,6 +104,18 @@ pub enum CollationFetchingResponse { /// Deliver requested collation. #[codec(index = 0)] Collation(CandidateReceipt, PoV), + + /// Deliver requested collation along with parent head data. + #[codec(index = 1)] + CollationWithParentHeadData { + /// The receipt of the candidate. + receipt: CandidateReceipt, + /// Candidate's proof of validity. + pov: PoV, + /// The head data of the candidate's parent. + /// This is needed for elastic scaling to work. + parent_head_data: HeadData, + }, } impl IsRequest for CollationFetchingRequest { diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index bf516e7b7ba9b8b89dbea715bef9bf6af2ff5d36..01f7d818c1c52ffd677379e3a0a9d2f543572d92 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-statement-distribution" description = "Statement Distribution Subsystem" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" @@ -16,12 +19,11 @@ sp-keystore = { path = "../../../../substrate/primitives/keystore" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } -polkadot-node-subsystem-types = { path = "../../subsystem-types" } polkadot-node-network-protocol = { path = "../protocol" } arrayvec = "0.7.4" -indexmap = "1.9.1" +indexmap = "2.0.0" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" diff --git a/polkadot/node/network/statement-distribution/src/error.rs b/polkadot/node/network/statement-distribution/src/error.rs index b676e5b6a223540fb3e93abc82dc7a94e861e7e5..a712ab6da436f813d956df38c1cd0da9f02de3be 100644 --- a/polkadot/node/network/statement-distribution/src/error.rs +++ b/polkadot/node/network/statement-distribution/src/error.rs @@ -75,6 +75,9 @@ pub enum Error { #[error("Fetching availability cores failed {0:?}")] FetchAvailabilityCores(RuntimeApiError), + #[error("Fetching disabled validators failed {0:?}")] + FetchDisabledValidators(runtime::Error), + #[error("Fetching validator groups failed {0:?}")] FetchValidatorGroups(RuntimeApiError), 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 fc2aff0da305edbb7f6f39834ffd017e2701a550..e22883f8937606475efc01225fcd6f98e4ee0f08 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use net_protocol::{filter_by_peer_version, peer_set::ProtocolVersion}; use parity_scale_codec::Encode; use polkadot_node_network_protocol::{ @@ -21,7 +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, 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, @@ -1061,7 +1063,7 @@ async fn circulate_statement<'a, Context>( "We filter out duplicates above. qed.", ); - let (v1_peers_to_send, v2_peers_to_send) = peers_to_send + let (v1_peers_to_send, non_v1_peers_to_send) = peers_to_send .into_iter() .map(|peer_id| { let peer_data = @@ -1073,7 +1075,7 @@ async fn circulate_statement<'a, Context>( }) .partition::, _>(|(_, _, version)| match version { ValidationVersion::V1 => true, - ValidationVersion::V2 => 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); @@ -1093,6 +1095,21 @@ async fn circulate_statement<'a, Context>( )) .await; } + + let peers_to_send: Vec<(PeerId, ProtocolVersion)> = non_v1_peers_to_send + .iter() + .map(|(p, _, version)| (*p, (*version).into())) + .collect(); + + let peer_needs_dependent_statement = v1_peers_to_send + .into_iter() + .chain(non_v1_peers_to_send) + .filter_map(|(peer, needs_dependent, _)| if needs_dependent { Some(peer) } else { None }) + .collect(); + + let v2_peers_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V2.into()); + let v3_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V3.into()); + if !v2_peers_to_send.is_empty() { gum::trace!( target: LOG_TARGET, @@ -1102,17 +1119,28 @@ async fn circulate_statement<'a, Context>( "Sending statement to v2 peers", ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - v2_peers_to_send.iter().map(|(p, _, _)| *p).collect(), + v2_peers_to_send, compatible_v1_message(ValidationVersion::V2, payload.clone()).into(), )) .await; } - v1_peers_to_send - .into_iter() - .chain(v2_peers_to_send) - .filter_map(|(peer, needs_dependent, _)| if needs_dependent { Some(peer) } else { None }) - .collect() + if !v3_to_send.is_empty() { + gum::trace!( + target: LOG_TARGET, + ?v3_to_send, + ?relay_parent, + statement = ?stored.statement, + "Sending statement to v3 peers", + ); + ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( + v3_to_send, + compatible_v1_message(ValidationVersion::V3, payload.clone()).into(), + )) + .await; + } + + peer_needs_dependent_statement } /// Send all statements about a given candidate hash to a peer. @@ -1442,8 +1470,9 @@ async fn handle_incoming_message<'a, Context>( let message = match message { Versioned::V1(m) => m, - Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(m)) => m, - Versioned::V2(_) => { + Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(m)) | + 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!( @@ -1863,7 +1892,9 @@ pub(crate) async fn handle_network_update( ?authority_ids, "Updated `AuthorityDiscoveryId`s" ); - + topology_storage + .get_current_topology_mut() + .update_authority_ids(peer, &authority_ids); // Remove the authority IDs which were previously mapped to the peer // but aren't part of the new set. authorities.retain(|a, p| p != &peer || authority_ids.contains(a)); @@ -2169,5 +2200,7 @@ fn compatible_v1_message( ValidationVersion::V1 => Versioned::V1(message), ValidationVersion::V2 => Versioned::V2(protocol_v2::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..2766ec9815af1e87051603c1b45304c4758cf9f9 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs @@ -43,13 +43,14 @@ 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, }; use sc_keystore::LocalKeystore; +use sc_network::ProtocolName; use sp_application_crypto::{sr25519::Pair, AppCrypto, Pair as TraitPair}; use sp_authority_discovery::AuthorityPair; use sp_keyring::Sr25519Keyring; @@ -834,6 +835,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 +1084,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 { @@ -1312,7 +1331,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing( bad }; let response = StatementFetchingResponse::Statement(bad_candidate); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); + outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); } ); @@ -1364,7 +1383,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing( // On retry, we should have reverse order: assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); let response = StatementFetchingResponse::Statement(candidate.clone()); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); + outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); } ); @@ -1604,6 +1623,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 { @@ -1842,7 +1870,7 @@ fn delay_reputation_changes() { bad }; let response = StatementFetchingResponse::Statement(bad_candidate); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); + outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); } ); @@ -1886,7 +1914,7 @@ fn delay_reputation_changes() { // On retry, we should have reverse order: assert_eq!(outgoing.peer, Recipient::Peer(peer_c)); let response = StatementFetchingResponse::Statement(candidate.clone()); - outgoing.pending_response.send(Ok(response.encode())).unwrap(); + outgoing.pending_response.send(Ok((response.encode(), ProtocolName::from("")))).unwrap(); } ); @@ -2084,6 +2112,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 +2443,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 +2677,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 259c8f6a360176fb89e836583b2fbd3013c68840..7e91d2849120283c3fcefc4193a6e3d8bff809a1 100644 --- a/polkadot/node/network/statement-distribution/src/lib.rs +++ b/polkadot/node/network/statement-distribution/src/lib.rs @@ -19,7 +19,7 @@ //! This is responsible for distributing signed statements about candidate //! validity among validators. -// #![deny(unused_crate_dependencies)] +#![deny(unused_crate_dependencies)] #![warn(missing_docs)] use error::{log_error, FatalResult}; @@ -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, Versioned, + v2 as protocol_v2, v3 as protocol_v3, Versioned, }; use polkadot_node_primitives::StatementWithPVD; use polkadot_node_subsystem::{ @@ -319,8 +319,12 @@ impl StatementDistributionSubsystem { if let Some(ref activated) = activated { let mode = prospective_parachains_mode(ctx.sender(), activated.hash).await?; if let ProspectiveParachainsMode::Enabled { .. } = mode { - v2::handle_active_leaves_update(ctx, state, activated, mode).await?; + let res = + v2::handle_active_leaves_update(ctx, state, activated, mode).await; + // Regardless of the result of leaf activation, we always prune before + // handling it to avoid leaks. v2::handle_deactivate_leaves(state, &deactivated); + res?; } else if let ProspectiveParachainsMode::Disabled = mode { for deactivated in &deactivated { crate::legacy_v1::handle_deactivate_leaf(legacy_v1_state, *deactivated); @@ -399,9 +403,12 @@ impl StatementDistributionSubsystem { NetworkBridgeEvent::PeerMessage(_, message) => match message { Versioned::V2( protocol_v2::StatementDistributionMessage::V1Compatibility(_), + ) | + Versioned::V3( + protocol_v3::StatementDistributionMessage::V1Compatibility(_), ) => VersionTarget::Legacy, Versioned::V1(_) => VersionTarget::Legacy, - Versioned::V2(_) => VersionTarget::Current, + Versioned::V2(_) | Versioned::V3(_) => VersionTarget::Current, }, _ => VersionTarget::Both, }; diff --git a/polkadot/node/network/statement-distribution/src/v2/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/cluster.rs index 619114de9670c8aef50ce129b6c26bf6ff206f70..c09916e56201f20a0fbbbac349681b512f1fd5fe 100644 --- a/polkadot/node/network/statement-distribution/src/v2/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/cluster.rs @@ -55,8 +55,9 @@ //! and to keep track of what we have sent to other validators in the group and what we may //! continue to send them. -use polkadot_primitives::{CandidateHash, CompactStatement, ValidatorIndex}; +use polkadot_primitives::{CandidateHash, CompactStatement, Hash, ValidatorIndex}; +use crate::LOG_TARGET; use std::collections::{HashMap, HashSet}; #[derive(Hash, PartialEq, Eq)] @@ -424,6 +425,28 @@ impl ClusterTracker { fn is_in_group(&self, validator: ValidatorIndex) -> bool { self.validators.contains(&validator) } + + /// Dumps pending statement for this cluster. + /// + /// Normally we should not have pending statements to validators in our cluster, + /// but if we do for all validators in our cluster, then we don't participate + /// in backing. Ocasional pending statements are expected if two authorities + /// can't detect each otehr or after restart, where it takes a while to discover + /// the whole network. + + pub fn warn_if_too_many_pending_statements(&self, parent_hash: Hash) { + if self.pending.iter().filter(|pending| !pending.1.is_empty()).count() >= + self.validators.len() + { + gum::warn!( + target: LOG_TARGET, + pending_statements = ?self.pending, + ?parent_hash, + "Cluster has too many pending statements, something wrong with our connection to our group peers \n + Restart might be needed if validator gets 0 backing rewards for more than 3-4 consecutive sessions" + ); + } + } } /// Incoming statement was accepted. diff --git a/polkadot/node/network/statement-distribution/src/v2/grid.rs b/polkadot/node/network/statement-distribution/src/v2/grid.rs index 19bad34c44ff9de34596595480c0b1bb92cd0d3e..24d846c840e00c49446a0525b3768353f56ae524 100644 --- a/polkadot/node/network/statement-distribution/src/v2/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/grid.rs @@ -253,7 +253,9 @@ impl GridTracker { /// This checks whether the peer is allowed to send us manifests /// about this group at this relay-parent. This also does sanity /// checks on the format of the manifest and the amount of votes - /// it contains. It has effects on the stored state only when successful. + /// it contains. It assumes that the votes from disabled validators + /// are already filtered out. + /// It has effects on the stored state only when successful. /// /// This returns a `bool` on success, which if true indicates that an acknowledgement is /// to be sent in response to the received manifest. This only occurs when the @@ -525,12 +527,16 @@ impl GridTracker { } /// Determine the validators which can send a statement to us by direct broadcast. + /// + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. pub fn direct_statement_providers( &self, groups: &Groups, originator: ValidatorIndex, statement: &CompactStatement, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { let (g, c_h, kind, in_group) = match extract_statement_and_group_info(groups, originator, statement) { None => return Vec::new(), @@ -614,12 +620,13 @@ impl GridTracker { originator: ValidatorIndex, counterparty: ValidatorIndex, statement: &CompactStatement, + received: bool, ) { if let Some((_, c_h, kind, in_group)) = extract_statement_and_group_info(groups, originator, statement) { if let Some(known) = self.confirmed_backed.get_mut(&c_h) { - known.sent_or_received_direct_statement(counterparty, in_group, kind); + known.sent_or_received_direct_statement(counterparty, in_group, kind, received); if let Some(pending) = self.pending_statements.get_mut(&counterparty) { pending.remove(&(originator, statement.clone())); @@ -906,6 +913,12 @@ struct MutualKnowledge { /// `Some` only if we have advertised, acknowledged, or requested the candidate /// from them. local_knowledge: Option, + /// Knowledge peer circulated to us, this is different from `local_knowledge` and + /// `remote_knowledge`, through the fact that includes only statements that we received from + /// peer while the other two, after manifest exchange part will include both what we sent to + /// the peer and what we received from peer, see `sent_or_received_direct_statement` for more + /// details. + received_knowledge: Option, } // A utility struct for keeping track of metadata about candidates @@ -931,10 +944,13 @@ impl KnownBackedCandidate { } fn manifest_sent_to(&mut self, validator: ValidatorIndex, local_knowledge: StatementFilter) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); + k.received_knowledge = + Some(StatementFilter::blank(local_knowledge.seconded_in_group.len())); k.local_knowledge = Some(local_knowledge); } @@ -944,20 +960,24 @@ impl KnownBackedCandidate { validator: ValidatorIndex, remote_knowledge: StatementFilter, ) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); k.remote_knowledge = Some(remote_knowledge); } + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. fn direct_statement_senders( &self, group_index: GroupIndex, originator_index_in_group: usize, statement_kind: StatementKind, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { if group_index != self.group_index { return Vec::new() } @@ -966,11 +986,18 @@ impl KnownBackedCandidate { .iter() .filter(|(_, k)| k.remote_knowledge.is_some()) .filter(|(_, k)| { - k.local_knowledge + k.received_knowledge .as_ref() .map_or(false, |r| !r.contains(originator_index_in_group, statement_kind)) }) - .map(|(v, _)| *v) + .map(|(v, k)| { + ( + *v, + k.local_knowledge + .as_ref() + .map_or(false, |r| r.contains(originator_index_in_group, statement_kind)), + ) + }) .collect() } @@ -1012,12 +1039,19 @@ impl KnownBackedCandidate { validator: ValidatorIndex, statement_index_in_group: usize, statement_kind: StatementKind, + received: bool, ) { if let Some(k) = self.mutual_knowledge.get_mut(&validator) { if let (Some(r), Some(l)) = (k.remote_knowledge.as_mut(), k.local_knowledge.as_mut()) { r.set(statement_index_in_group, statement_kind); l.set(statement_index_in_group, statement_kind); } + + if received { + k.received_knowledge + .as_mut() + .map(|knowledge| knowledge.set(statement_index_in_group, statement_kind)); + } } } @@ -2236,6 +2270,7 @@ mod tests { validator_index, counterparty, &statement, + false, ); // There should be no pending statements now (for the counterparty). diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 95104732df02328067a077093114dcdb68d92696..2c9cdba4ea8eb1155f9307eb01460a777ae9a737 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -17,10 +17,11 @@ //! Implementation of the v2 statement distribution protocol, //! designed for asynchronous backing. +use bitvec::prelude::{BitVec, Lsb0}; use polkadot_node_network_protocol::{ - self as net_protocol, + self as net_protocol, filter_by_peer_version, grid_topology::SessionGridTopology, - peer_set::ValidationVersion, + peer_set::{ProtocolVersion, ValidationVersion}, request_response::{ incoming::OutgoingResponse, v2::{AttestedCandidateRequest, AttestedCandidateResponse}, @@ -28,15 +29,16 @@ use polkadot_node_network_protocol::{ MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS, }, v2::{self as protocol_v2, StatementFilter}, - 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, }; use polkadot_node_subsystem::{ messages::{ - CandidateBackingMessage, HypotheticalCandidate, HypotheticalFrontierRequest, - NetworkBridgeEvent, NetworkBridgeTxMessage, ProspectiveParachainsMessage, + network_bridge_event::NewGossipTopology, CandidateBackingMessage, HypotheticalCandidate, + HypotheticalFrontierRequest, NetworkBridgeEvent, NetworkBridgeTxMessage, + ProspectiveParachainsMessage, }, overseer, ActivatedLeaf, }; @@ -63,7 +65,7 @@ use futures::{ use std::{ collections::{ hash_map::{Entry, HashMap}, - HashSet, + BTreeSet, HashSet, }, time::{Duration, Instant}, }; @@ -91,10 +93,23 @@ mod statement_store; #[cfg(test)] mod tests; -const COST_UNEXPECTED_STATEMENT: Rep = Rep::CostMinor("Unexpected Statement"); +const COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR: Rep = + Rep::CostMinor("Unexpected Statement, not a validator"); +const COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND: Rep = + Rep::CostMinor("Unexpected Statement, validator not found"); +const COST_UNEXPECTED_STATEMENT_INVALID_SENDER: Rep = + Rep::CostMinor("Unexpected Statement, invalid sender"); +const COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE: Rep = + Rep::CostMinor("Unexpected Statement, bad advertise"); +const COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED: Rep = + Rep::CostMinor("Unexpected Statement, cluster rejected"); +const COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP: Rep = + Rep::CostMinor("Unexpected Statement, not in group"); + const COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE: Rep = Rep::CostMinor("Unexpected Statement, missing knowledge for relay parent"); const COST_EXCESSIVE_SECONDED: Rep = Rep::CostMinor("Sent Excessive `Seconded` Statements"); +const COST_DISABLED_VALIDATOR: Rep = Rep::CostMinor("Sent a statement from a disabled validator"); const COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE: Rep = Rep::CostMinor("Unexpected Manifest, missing knowlege for relay parent"); @@ -140,8 +155,27 @@ struct PerRelayParentState { session: SessionIndex, } +impl PerRelayParentState { + fn active_validator_state(&self) -> Option<&ActiveValidatorState> { + self.local_validator.as_ref().and_then(|local| local.active.as_ref()) + } + + fn active_validator_state_mut(&mut self) -> Option<&mut ActiveValidatorState> { + self.local_validator.as_mut().and_then(|local| local.active.as_mut()) + } +} + // per-relay-parent local validator state. struct LocalValidatorState { + // the grid-level communication at this relay-parent. + grid_tracker: GridTracker, + // additional fields in case local node is an active validator. + active: Option, + // local index actually exists in case node is inactive validator, however, + // it's not needed outside of `build_session_topology`, where it's known. +} + +struct ActiveValidatorState { // The index of the validator. index: ValidatorIndex, // our validator group @@ -150,8 +184,14 @@ struct LocalValidatorState { assignment: Option, // the 'direct-in-group' communication at this relay-parent. cluster_tracker: ClusterTracker, - // the grid-level communication at this relay-parent. - grid_tracker: GridTracker, +} + +#[derive(Debug, Copy, Clone)] +enum LocalValidatorIndex { + // Local node is an active validator. + Active(ValidatorIndex), + // Local node is not in active validator set. + Inactive, } #[derive(Debug)] @@ -162,7 +202,9 @@ struct PerSessionState { // is only `None` in the time between seeing a session and // getting the topology from the gossip-support subsystem grid_view: Option, - local_validator: Option, + local_validator: Option, + // We store the latest state here based on union of leaves. + disabled_validators: BTreeSet, } impl PerSessionState { @@ -176,14 +218,18 @@ impl PerSessionState { let local_validator = polkadot_node_subsystem_util::signing_key_and_index( session_info.validators.iter(), keystore, - ); + ) + .map(|(_, index)| LocalValidatorIndex::Active(index)); + + let disabled_validators = BTreeSet::new(); PerSessionState { session_info, groups, authority_lookup, grid_view: None, - local_validator: local_validator.map(|(_key, index)| index), + local_validator, + disabled_validators, } } @@ -202,6 +248,50 @@ impl PerSessionState { ); self.grid_view = Some(grid_view); + if local_index.is_some() { + self.local_validator.get_or_insert(LocalValidatorIndex::Inactive); + } + + gum::info!( + target: LOG_TARGET, + index_in_gossip_topology = ?local_index, + index_in_parachain_authorities = ?self.local_validator, + "Node uses the following topology indices" + ); + } + + /// Returns `true` if local is neither active or inactive validator node. + /// + /// `false` is also returned if session topology is not known yet. + fn is_not_validator(&self) -> bool { + self.grid_view.is_some() && self.local_validator.is_none() + } + + /// A convenience function to generate a disabled bitmask for the given backing group. + /// The output bits are set to `true` for validators that are disabled. + /// Returns `None` if the group index is out of bounds. + pub fn disabled_bitmask(&self, group: GroupIndex) -> Option> { + let group = self.groups.get(group)?; + let mask = BitVec::from_iter(group.iter().map(|v| self.is_disabled(v))); + Some(mask) + } + + /// Returns `true` if the given validator is disabled in the current session. + pub fn is_disabled(&self, validator_index: &ValidatorIndex) -> bool { + self.disabled_validators.contains(validator_index) + } + + /// Extend the list of disabled validators. + pub fn extend_disabled_validators( + &mut self, + disabled: impl IntoIterator, + ) { + self.disabled_validators.extend(disabled); + } + + /// Clear the list of disabled validators. + pub fn clear_disabled_validators(&mut self) { + self.disabled_validators.clear(); } } @@ -213,6 +303,10 @@ pub(crate) struct State { candidates: Candidates, per_relay_parent: HashMap, per_session: HashMap, + // Topology might be received before first leaf update, where we + // initialize the per_session_state, so cache it here until we + // are able to use it. + unused_topologies: HashMap, peers: HashMap, keystore: KeystorePtr, authorities: HashMap, @@ -233,6 +327,7 @@ impl State { authorities: HashMap::new(), request_manager: RequestManager::new(), response_manager: ResponseManager::new(), + unused_topologies: HashMap::new(), } } @@ -260,6 +355,7 @@ fn connected_validator_peer( struct PeerState { view: View, + protocol_version: ValidationVersion, implicit_view: HashSet, discovery_ids: Option>, } @@ -332,9 +428,13 @@ pub(crate) async fn handle_network_update( NetworkBridgeEvent::PeerConnected(peer_id, role, protocol_version, mut authority_ids) => { gum::trace!(target: LOG_TARGET, ?peer_id, ?role, ?protocol_version, "Peer connected"); - if protocol_version != ValidationVersion::V2.into() { + let versioned_protocol = if protocol_version != ValidationVersion::V2.into() && + protocol_version != ValidationVersion::V3.into() + { return - } + } else { + protocol_version.try_into().expect("Qed, we checked above") + }; if let Some(ref mut authority_ids) = authority_ids { authority_ids.retain(|a| match state.authorities.entry(a.clone()) { @@ -361,6 +461,7 @@ pub(crate) async fn handle_network_update( PeerState { view: View::default(), implicit_view: HashSet::new(), + protocol_version: versioned_protocol, discovery_ids: authority_ids, }, ); @@ -373,12 +474,14 @@ pub(crate) async fn handle_network_update( } }, NetworkBridgeEvent::NewGossipTopology(topology) => { - let new_session_index = topology.session; - let new_topology = topology.topology; - let local_index = topology.local_index; + let new_session_index = &topology.session; + let new_topology = &topology.topology; + let local_index = &topology.local_index; - if let Some(per_session) = state.per_session.get_mut(&new_session_index) { - per_session.supply_topology(&new_topology, local_index); + if let Some(per_session) = state.per_session.get_mut(new_session_index) { + per_session.supply_topology(new_topology, *local_index); + } else { + state.unused_topologies.insert(*new_session_index, topology); } // TODO [https://github.com/paritytech/polkadot/issues/6194] @@ -393,17 +496,29 @@ pub(crate) async fn handle_network_update( net_protocol::StatementDistributionMessage::V1(_) => return, net_protocol::StatementDistributionMessage::V2( protocol_v2::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::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::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::V3( + protocol_v3::StatementDistributionMessage::BackedCandidateKnown(inner), ) => handle_incoming_acknowledgement(ctx, state, peer_id, inner, reputation).await, }, NetworkBridgeEvent::PeerViewChange(peer_id, view) => @@ -461,13 +576,20 @@ pub(crate) async fn handle_active_leaves_update( let new_relay_parents = state.implicit_view.all_allowed_relay_parents().cloned().collect::>(); - for new_relay_parent in new_relay_parents.iter().cloned() { - if state.per_relay_parent.contains_key(&new_relay_parent) { - continue - } - // New leaf: fetch info from runtime API and initialize - // `per_relay_parent`. + // We clear the list of disabled validators to reset it properly based on union of leaves. + let mut cleared_disabled_validators: BTreeSet = BTreeSet::new(); + + for new_relay_parent in new_relay_parents.iter().cloned() { + // Even if we processed this relay parent before, we need to fetch the list of disabled + // validators based on union of active leaves. + let disabled_validators = + polkadot_node_subsystem_util::vstaging::get_disabled_validators_with_fallback( + ctx.sender(), + new_relay_parent, + ) + .await + .map_err(JfyiError::FetchDisabledValidators)?; let session_index = polkadot_node_subsystem_util::request_session_index_for_child( new_relay_parent, @@ -478,23 +600,6 @@ pub(crate) async fn handle_active_leaves_update( .map_err(JfyiError::RuntimeApiUnavailable)? .map_err(JfyiError::FetchSessionIndex)?; - let availability_cores = polkadot_node_subsystem_util::request_availability_cores( - new_relay_parent, - ctx.sender(), - ) - .await - .await - .map_err(JfyiError::RuntimeApiUnavailable)? - .map_err(JfyiError::FetchAvailabilityCores)?; - - let group_rotation_info = - polkadot_node_subsystem_util::request_validator_groups(new_relay_parent, ctx.sender()) - .await - .await - .map_err(JfyiError::RuntimeApiUnavailable)? - .map_err(JfyiError::FetchValidatorGroups)? - .1; - if !state.per_session.contains_key(&session_index) { let session_info = polkadot_node_subsystem_util::request_session_info( new_relay_parent, @@ -521,26 +626,71 @@ pub(crate) async fn handle_active_leaves_update( let minimum_backing_votes = request_min_backing_votes(new_relay_parent, session_index, ctx.sender()).await?; - - state.per_session.insert( - session_index, - PerSessionState::new(session_info, &state.keystore, minimum_backing_votes), - ); + let mut per_session_state = + PerSessionState::new(session_info, &state.keystore, minimum_backing_votes); + if let Some(toplogy) = state.unused_topologies.remove(&session_index) { + per_session_state.supply_topology(&toplogy.topology, toplogy.local_index); + } + state.per_session.insert(session_index, per_session_state); } let per_session = state .per_session - .get(&session_index) + .get_mut(&session_index) .expect("either existed or just inserted; qed"); + if cleared_disabled_validators.insert(session_index) { + per_session.clear_disabled_validators(); + } + + if !disabled_validators.is_empty() { + gum::debug!( + target: LOG_TARGET, + relay_parent = ?new_relay_parent, + ?session_index, + ?disabled_validators, + "Disabled validators detected" + ); + + per_session.extend_disabled_validators(disabled_validators); + } + + if state.per_relay_parent.contains_key(&new_relay_parent) { + continue + } + + // New leaf: fetch info from runtime API and initialize + // `per_relay_parent`. + + let availability_cores = polkadot_node_subsystem_util::request_availability_cores( + new_relay_parent, + ctx.sender(), + ) + .await + .await + .map_err(JfyiError::RuntimeApiUnavailable)? + .map_err(JfyiError::FetchAvailabilityCores)?; + + let group_rotation_info = + polkadot_node_subsystem_util::request_validator_groups(new_relay_parent, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiUnavailable)? + .map_err(JfyiError::FetchValidatorGroups)? + .1; + let local_validator = per_session.local_validator.and_then(|v| { - find_local_validator_state( - v, - &per_session.groups, - &availability_cores, - &group_rotation_info, - seconding_limit, - ) + if let LocalValidatorIndex::Active(idx) = v { + find_active_validator_state( + idx, + &per_session.groups, + &availability_cores, + &group_rotation_info, + seconding_limit, + ) + } else { + Some(LocalValidatorState { grid_tracker: GridTracker::default(), active: None }) + } }); state.per_relay_parent.insert( @@ -587,7 +737,7 @@ pub(crate) async fn handle_active_leaves_update( Ok(()) } -fn find_local_validator_state( +fn find_active_validator_state( validator_index: ValidatorIndex, groups: &Groups, availability_cores: &[CoreState], @@ -608,11 +758,13 @@ fn find_local_validator_state( let group_validators = groups.get(our_group)?.to_owned(); Some(LocalValidatorState { - index: validator_index, - group: our_group, - assignment: para, - cluster_tracker: ClusterTracker::new(group_validators, seconding_limit) - .expect("group is non-empty because we are in it; qed"), + active: Some(ActiveValidatorState { + index: validator_index, + group: our_group, + assignment: para, + cluster_tracker: ClusterTracker::new(group_validators, seconding_limit) + .expect("group is non-empty because we are in it; qed"), + }), grid_tracker: GridTracker::default(), }) } @@ -623,7 +775,15 @@ pub(crate) fn handle_deactivate_leaves(state: &mut State, leaves: &[Hash]) { let pruned = state.implicit_view.deactivate_leaf(*leaf); for pruned_rp in pruned { // clean up per-relay-parent data based on everything removed. - state.per_relay_parent.remove(&pruned_rp); + state + .per_relay_parent + .remove(&pruned_rp) + .as_ref() + .and_then(|pruned| pruned.active_validator_state()) + .map(|active_state| { + active_state.cluster_tracker.warn_if_too_many_pending_statements(pruned_rp) + }); + // clean up requests related to this relay parent. state.request_manager.remove_by_relay_parent(*leaf); } @@ -636,6 +796,7 @@ pub(crate) fn handle_deactivate_leaves(state: &mut State, leaves: &[Hash]) { // clean up sessions based on everything remaining. let sessions: HashSet<_> = state.per_relay_parent.values().map(|r| r.session).collect(); state.per_session.retain(|s, _| sessions.contains(s)); + state.unused_topologies.retain(|s, _| sessions.contains(s)); } #[overseer::contextbounds(StatementDistribution, prefix=self::overseer)] @@ -705,13 +866,17 @@ async fn send_peer_messages_for_relay_parent( for validator_id in find_validator_ids(peer_data.iter_known_discovery_ids(), |a| { per_session_state.authority_lookup.get(a) }) { - if let Some(local_validator_state) = relay_parent_state.local_validator.as_mut() { + if let Some(active) = relay_parent_state + .local_validator + .as_mut() + .and_then(|local| local.active.as_mut()) + { send_pending_cluster_statements( ctx, relay_parent, - &peer, + &(peer, peer_data.protocol_version), validator_id, - &mut local_validator_state.cluster_tracker, + &mut active.cluster_tracker, &state.candidates, &relay_parent_state.statement_store, ) @@ -721,7 +886,7 @@ async fn send_peer_messages_for_relay_parent( send_pending_grid_messages( ctx, relay_parent, - &peer, + &(peer, peer_data.protocol_version), validator_id, &per_session_state.groups, relay_parent_state, @@ -734,15 +899,34 @@ async fn send_peer_messages_for_relay_parent( fn pending_statement_network_message( statement_store: &StatementStore, relay_parent: Hash, - peer: &PeerId, + peer: &(PeerId, ValidationVersion), originator: ValidatorIndex, compact: CompactStatement, ) -> Option<(Vec, net_protocol::VersionedValidationProtocol)> { - statement_store - .validator_statement(originator, compact) - .map(|s| s.as_unchecked().clone()) - .map(|signed| protocol_v2::StatementDistributionMessage::Statement(relay_parent, signed)) - .map(|msg| (vec![*peer], Versioned::V2(msg).into())) + match peer.1 { + ValidationVersion::V2 => statement_store + .validator_statement(originator, compact) + .map(|s| s.as_unchecked().clone()) + .map(|signed| { + protocol_v2::StatementDistributionMessage::Statement(relay_parent, signed) + }) + .map(|msg| (vec![peer.0], Versioned::V2(msg).into())), + ValidationVersion::V3 => statement_store + .validator_statement(originator, compact) + .map(|s| s.as_unchecked().clone()) + .map(|signed| { + protocol_v3::StatementDistributionMessage::Statement(relay_parent, signed) + }) + .map(|msg| (vec![peer.0], Versioned::V3(msg).into())), + ValidationVersion::V1 => { + gum::error!( + target: LOG_TARGET, + "Bug ValidationVersion::V1 should not be used in statement-distribution v2, + legacy should have handled this" + ); + None + }, + } } /// Send a peer all pending cluster statements for a relay parent. @@ -750,7 +934,7 @@ fn pending_statement_network_message( async fn send_pending_cluster_statements( ctx: &mut Context, relay_parent: Hash, - peer_id: &PeerId, + peer_id: &(PeerId, ValidationVersion), peer_validator_id: ValidatorIndex, cluster_tracker: &mut ClusterTracker, candidates: &Candidates, @@ -794,7 +978,7 @@ async fn send_pending_cluster_statements( async fn send_pending_grid_messages( ctx: &mut Context, relay_parent: Hash, - peer_id: &PeerId, + peer_id: &(PeerId, ValidationVersion), peer_validator_id: ValidatorIndex, groups: &Groups, relay_parent_state: &mut PerRelayParentState, @@ -856,20 +1040,37 @@ async fn send_pending_grid_messages( candidate_hash, local_knowledge.clone(), ); - - messages.push(( - vec![*peer_id], - Versioned::V2( - protocol_v2::StatementDistributionMessage::BackedCandidateManifest( - manifest, - ), - ) - .into(), - )); + match peer_id.1 { + ValidationVersion::V2 => messages.push(( + vec![peer_id.0], + Versioned::V2( + protocol_v2::StatementDistributionMessage::BackedCandidateManifest( + manifest, + ), + ) + .into(), + )), + ValidationVersion::V3 => messages.push(( + vec![peer_id.0], + Versioned::V3( + protocol_v3::StatementDistributionMessage::BackedCandidateManifest( + manifest, + ), + ) + .into(), + )), + ValidationVersion::V1 => { + gum::error!( + target: LOG_TARGET, + "Bug ValidationVersion::V1 should not be used in statement-distribution v2, + legacy should have handled this" + ); + }, + }; }, grid::ManifestKind::Acknowledgement => { messages.extend(acknowledgement_and_statement_messages( - *peer_id, + peer_id, peer_validator_id, groups, relay_parent_state, @@ -912,6 +1113,7 @@ async fn send_pending_grid_messages( originator, peer_validator_id, &compact, + false, ); } @@ -953,7 +1155,7 @@ pub(crate) async fn share_local_statement( }; let (local_index, local_assignment, local_group) = - match per_relay_parent.local_validator.as_ref() { + match per_relay_parent.active_validator_state() { None => return Err(JfyiError::InvalidShare), Some(l) => (l.index, l.assignment, l.group), }; @@ -1030,7 +1232,7 @@ pub(crate) async fn share_local_statement( } { - let l = per_relay_parent.local_validator.as_mut().expect("checked above; qed"); + let l = per_relay_parent.active_validator_state_mut().expect("checked above; qed"); l.cluster_tracker.note_issued(local_index, compact_statement.payload().clone()); } @@ -1117,31 +1319,41 @@ async fn circulate_statement( // We're not meant to circulate statements in the cluster until we have the confirmed // candidate. - let cluster_relevant = Some(local_validator.group) == statement_group; - let cluster_targets = if is_confirmed && cluster_relevant { - Some( - local_validator - .cluster_tracker - .targets() - .iter() - .filter(|&&v| { - local_validator + // + // Cluster is only relevant if local node is an active validator. + let (cluster_relevant, cluster_targets, all_cluster_targets) = local_validator + .active + .as_mut() + .map(|active| { + let cluster_relevant = Some(active.group) == statement_group; + let cluster_targets = if is_confirmed && cluster_relevant { + Some( + active .cluster_tracker - .can_send(v, originator, compact_statement.clone()) - .is_ok() - }) - .filter(|&v| v != &local_validator.index) - .map(|v| (*v, DirectTargetKind::Cluster)), - ) - } else { - None - }; + .targets() + .iter() + .filter(|&&v| { + active + .cluster_tracker + .can_send(v, originator, compact_statement.clone()) + .is_ok() + }) + .filter(|&v| v != &active.index) + .map(|v| (*v, DirectTargetKind::Cluster)), + ) + } else { + None + }; + let all_cluster_targets = active.cluster_tracker.targets(); + (cluster_relevant, cluster_targets, all_cluster_targets) + }) + .unwrap_or((false, None, &[])); let grid_targets = local_validator .grid_tracker .direct_statement_targets(&per_session.groups, originator, &compact_statement) .into_iter() - .filter(|v| !cluster_relevant || !local_validator.cluster_tracker.targets().contains(v)) + .filter(|v| !cluster_relevant || !all_cluster_targets.contains(v)) .map(|v| (v, DirectTargetKind::Grid)); let targets = cluster_targets @@ -1156,54 +1368,67 @@ async fn circulate_statement( (local_validator, targets) }; - let mut statement_to = Vec::new(); + let mut statement_to_peers: Vec<(PeerId, ProtocolVersion)> = Vec::new(); for (target, authority_id, kind) in targets { // Find peer ID based on authority ID, and also filter to connected. - let peer_id: PeerId = match authorities.get(&authority_id) { - Some(p) if peers.get(p).map_or(false, |p| p.knows_relay_parent(&relay_parent)) => *p, + let peer_id: (PeerId, ProtocolVersion) = match authorities.get(&authority_id) { + Some(p) if peers.get(p).map_or(false, |p| p.knows_relay_parent(&relay_parent)) => ( + *p, + peers + .get(p) + .expect("Qed, can't fail because it was checked above") + .protocol_version + .into(), + ), None | Some(_) => continue, }; match kind { DirectTargetKind::Cluster => { + let active = local_validator + .active + .as_mut() + .expect("cluster target means local is active validator; qed"); + // At this point, all peers in the cluster should 'know' // the candidate, so we don't expect for this to fail. - if let Ok(()) = local_validator.cluster_tracker.can_send( - target, - originator, - compact_statement.clone(), - ) { - local_validator.cluster_tracker.note_sent( - target, - originator, - compact_statement.clone(), - ); - statement_to.push(peer_id); + if let Ok(()) = + active.cluster_tracker.can_send(target, originator, compact_statement.clone()) + { + active.cluster_tracker.note_sent(target, originator, compact_statement.clone()); + statement_to_peers.push(peer_id); } }, DirectTargetKind::Grid => { - statement_to.push(peer_id); + statement_to_peers.push(peer_id); local_validator.grid_tracker.sent_or_received_direct_statement( &per_session.groups, originator, target, &compact_statement, + false, ); }, } } + let statement_to_v2_peers = + filter_by_peer_version(&statement_to_peers, ValidationVersion::V2.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.is_empty() { + if !statement_to_v2_peers.is_empty() { gum::debug!( target: LOG_TARGET, ?compact_statement, - n_peers = ?statement_to.len(), - "Sending statement to peers", + n_peers = ?statement_to_v2_peers.len(), + "Sending statement to v2 peers", ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - statement_to, + statement_to_v2_peers, Versioned::V2(protocol_v2::StatementDistributionMessage::Statement( relay_parent, statement.as_unchecked().clone(), @@ -1212,6 +1437,25 @@ async fn circulate_statement( )) .await; } + + if !statement_to_v3_peers.is_empty() { + gum::debug!( + target: LOG_TARGET, + ?compact_statement, + n_peers = ?statement_to_peers.len(), + "Sending statement to v3 peers", + ); + + ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( + statement_to_v3_peers, + Versioned::V3(protocol_v3::StatementDistributionMessage::Statement( + relay_parent, + statement.as_unchecked().clone(), + )) + .into(), + )) + .await; + } } /// Check a statement signature under this parent hash. fn check_statement_signature( @@ -1299,7 +1543,15 @@ async fn handle_incoming_statement( None => { // we shouldn't be receiving statements unless we're a validator // this session. - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + if per_session.is_not_validator() { + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR, + ) + .await; + } return }, Some(l) => l, @@ -1309,31 +1561,57 @@ async fn handle_incoming_statement( match per_session.groups.by_validator_index(statement.unchecked_validator_index()) { Some(g) => g, None => { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND, + ) + .await; return }, }; - let cluster_sender_index = { + if per_session.is_disabled(&statement.unchecked_validator_index()) { + gum::debug!( + target: LOG_TARGET, + ?relay_parent, + validator_index = ?statement.unchecked_validator_index(), + "Ignoring a statement from disabled validator." + ); + modify_reputation(reputation, ctx.sender(), peer, COST_DISABLED_VALIDATOR).await; + return + } + + let (active, cluster_sender_index) = { // This block of code only returns `Some` when both the originator and // the sending peer are in the cluster. + let active = local_validator.active.as_mut(); - let allowed_senders = local_validator - .cluster_tracker - .senders_for_originator(statement.unchecked_validator_index()); + let allowed_senders = active + .as_ref() + .map(|active| { + active + .cluster_tracker + .senders_for_originator(statement.unchecked_validator_index()) + }) + .unwrap_or_default(); - allowed_senders + let idx = allowed_senders .iter() .filter_map(|i| session_info.discovery_keys.get(i.0 as usize).map(|ad| (*i, ad))) .filter(|(_, ad)| peer_state.is_authority(ad)) .map(|(i, _)| i) - .next() + .next(); + (active, idx) }; - let checked_statement = if let Some(cluster_sender_index) = cluster_sender_index { + let checked_statement = if let Some((active, cluster_sender_index)) = + active.zip(cluster_sender_index) + { match handle_cluster_statement( relay_parent, - &mut local_validator.cluster_tracker, + &mut active.cluster_tracker, per_relay_parent.session, &per_session.session_info, statement, @@ -1355,29 +1633,46 @@ async fn handle_incoming_statement( statement.unchecked_payload(), ) .into_iter() - .filter_map(|i| session_info.discovery_keys.get(i.0 as usize).map(|ad| (i, ad))) - .filter(|(_, ad)| peer_state.is_authority(ad)) - .map(|(i, _)| i) + .filter_map(|(i, validator_knows_statement)| { + session_info + .discovery_keys + .get(i.0 as usize) + .map(|ad| (i, ad, validator_knows_statement)) + }) + .filter(|(_, ad, _)| peer_state.is_authority(ad)) + .map(|(i, _, validator_knows_statement)| (i, validator_knows_statement)) .next(); - if let Some(grid_sender_index) = grid_sender_index { - match handle_grid_statement( - relay_parent, - &mut local_validator.grid_tracker, - per_relay_parent.session, - &per_session, - statement, - grid_sender_index, - ) { - Ok(s) => s, - Err(rep) => { - modify_reputation(reputation, ctx.sender(), peer, rep).await; - return - }, + if let Some((grid_sender_index, validator_knows_statement)) = grid_sender_index { + if !validator_knows_statement { + match handle_grid_statement( + relay_parent, + &mut local_validator.grid_tracker, + per_relay_parent.session, + &per_session, + statement, + grid_sender_index, + ) { + Ok(s) => s, + Err(rep) => { + modify_reputation(reputation, ctx.sender(), peer, rep).await; + return + }, + } + } else { + // Reward the peer for sending us the statement + modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT).await; + return; } } else { // Not a cluster or grid peer. - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_INVALID_SENDER, + ) + .await; return } }; @@ -1399,7 +1694,13 @@ async fn handle_incoming_statement( ); if let Err(BadAdvertisement) = res { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE, + ) + .await; return } } @@ -1426,7 +1727,7 @@ async fn handle_incoming_statement( checked_statement.clone(), StatementOrigin::Remote, ) { - Err(statement_store::ValidatorUnknown) => { + Err(statement_store::Error::ValidatorUnknown) => { // sanity: should never happen. gum::warn!( target: LOG_TARGET, @@ -1448,7 +1749,7 @@ async fn handle_incoming_statement( local_validator.grid_tracker.learned_fresh_statement( &per_session.groups, session_topology, - local_validator.index, + originator_index, &statement, ); } @@ -1507,11 +1808,11 @@ fn handle_cluster_statement( Ok(ClusterAccept::WithPrejudice) => false, Err(ClusterRejectIncoming::ExcessiveSeconded) => return Err(COST_EXCESSIVE_SECONDED), Err(ClusterRejectIncoming::CandidateUnknown | ClusterRejectIncoming::Duplicate) => - return Err(COST_UNEXPECTED_STATEMENT), + return Err(COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED), Err(ClusterRejectIncoming::NotInGroup) => { // sanity: shouldn't be possible; we already filtered this // out above. - return Err(COST_UNEXPECTED_STATEMENT) + return Err(COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP) }, } }; @@ -1562,6 +1863,7 @@ fn handle_grid_statement( checked_statement.validator_index(), grid_sender_index, &checked_statement.payload(), + true, ); Ok(checked_statement) @@ -1697,14 +1999,8 @@ async fn provide_candidate_to_grid( statement_knowledge: filter.clone(), }; - let manifest_message = - Versioned::V2(protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest)); - let ack_message = Versioned::V2( - protocol_v2::StatementDistributionMessage::BackedCandidateKnown(acknowledgement), - ); - - let mut manifest_peers = Vec::new(); - let mut ack_peers = Vec::new(); + let mut manifest_peers: Vec<(PeerId, ProtocolVersion)> = Vec::new(); + let mut ack_peers: Vec<(PeerId, ProtocolVersion)> = Vec::new(); let mut post_statements = Vec::new(); for (v, action) in actions { @@ -1712,7 +2008,7 @@ async fn provide_candidate_to_grid( None => continue, Some(p) => if peers.get(&p).map_or(false, |d| d.knows_relay_parent(&relay_parent)) { - p + (p, peers.get(&p).expect("Qed, was checked above").protocol_version.into()) } else { continue }, @@ -1738,44 +2034,92 @@ async fn provide_candidate_to_grid( &per_session.groups, group_index, candidate_hash, + &(p.0, p.1.try_into().expect("Qed, can not fail was checked above")), ) .into_iter() - .map(|m| (vec![p], m)), + .map(|m| (vec![p.0], m)), + ); + } + + let manifest_peers_v2 = filter_by_peer_version(&manifest_peers, ValidationVersion::V2.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, + ?candidate_hash, + local_validator = ?per_session.local_validator, + n_peers = manifest_peers_v2.len(), + "Sending manifest to v2 peers" ); + + ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( + manifest_peers_v2, + Versioned::V2(protocol_v2::StatementDistributionMessage::BackedCandidateManifest( + manifest.clone(), + )) + .into(), + )) + .await; } - if !manifest_peers.is_empty() { + if !manifest_peers_v3.is_empty() { gum::debug!( target: LOG_TARGET, ?candidate_hash, - local_validator = ?local_validator.index, - n_peers = manifest_peers.len(), - "Sending manifest to peers" + local_validator = ?per_session.local_validator, + n_peers = manifest_peers_v3.len(), + "Sending manifest to v3 peers" ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - manifest_peers, - manifest_message.into(), + manifest_peers_v3, + Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateManifest( + manifest, + )) + .into(), )) .await; } - if !ack_peers.is_empty() { + let ack_peers_v2 = filter_by_peer_version(&ack_peers, ValidationVersion::V2.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, ?candidate_hash, - local_validator = ?local_validator.index, - n_peers = ack_peers.len(), - "Sending acknowledgement to peers" + local_validator = ?per_session.local_validator, + n_peers = ack_peers_v2.len(), + "Sending acknowledgement to v2 peers" ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - ack_peers, - ack_message.into(), + ack_peers_v2, + Versioned::V2(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( + acknowledgement.clone(), + )) + .into(), )) .await; } + if !ack_peers_v3.is_empty() { + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + local_validator = ?per_session.local_validator, + n_peers = ack_peers_v3.len(), + "Sending acknowledgement to v3 peers" + ); + + ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( + ack_peers_v3, + Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateKnown( + acknowledgement, + )) + .into(), + )) + .await; + } if !post_statements.is_empty() { ctx.send_message(NetworkBridgeTxMessage::SendValidationMessages(post_statements)) .await; @@ -1922,7 +2266,7 @@ async fn handle_incoming_manifest_common<'a, Context>( candidate_hash: CandidateHash, relay_parent: Hash, para_id: ParaId, - manifest_summary: grid::ManifestSummary, + mut manifest_summary: grid::ManifestSummary, manifest_kind: grid::ManifestKind, reputation: &mut ReputationAggregator, ) -> Option> { @@ -1953,13 +2297,15 @@ async fn handle_incoming_manifest_common<'a, Context>( let local_validator = match relay_parent_state.local_validator.as_mut() { None => { - modify_reputation( - reputation, - ctx.sender(), - peer, - COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE, - ) - .await; + if per_session.is_not_validator() { + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_MANIFEST_MISSING_KNOWLEDGE, + ) + .await; + } return None }, Some(x) => x, @@ -2005,6 +2351,12 @@ async fn handle_incoming_manifest_common<'a, Context>( // 2. sanity checks: peer is validator, bitvec size, import into grid tracker let group_index = manifest_summary.claimed_group_index; let claimed_parent_hash = manifest_summary.claimed_parent_hash; + + // Ignore votes from disabled validators when counting towards the threshold. + let disabled_mask = per_session.disabled_bitmask(group_index).unwrap_or_default(); + manifest_summary.statement_knowledge.mask_seconded(&disabled_mask); + manifest_summary.statement_knowledge.mask_valid(&disabled_mask); + let acknowledge = match local_validator.grid_tracker.import_manifest( grid_topology, &per_session.groups, @@ -2055,7 +2407,7 @@ async fn handle_incoming_manifest_common<'a, Context>( target: LOG_TARGET, ?candidate_hash, from = ?sender_index, - local_index = ?local_validator.index, + local_index = ?per_session.local_validator, ?manifest_kind, "immediate ack, known candidate" ); @@ -2074,6 +2426,7 @@ fn post_acknowledgement_statement_messages( groups: &Groups, group_index: GroupIndex, candidate_hash: CandidateHash, + peer: &(PeerId, ValidationVersion), ) -> Vec { let sending_filter = match grid_tracker.pending_statements_for(recipient, candidate_hash) { None => return Vec::new(), @@ -2089,15 +2442,31 @@ fn post_acknowledgement_statement_messages( statement.validator_index(), recipient, statement.payload(), + false, ); - - messages.push(Versioned::V2( - protocol_v2::StatementDistributionMessage::Statement( - relay_parent, - statement.as_unchecked().clone(), - ) - .into(), - )); + match peer.1.into() { + ValidationVersion::V2 => messages.push(Versioned::V2( + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + statement.as_unchecked().clone(), + ) + .into(), + )), + ValidationVersion::V3 => messages.push(Versioned::V3( + protocol_v3::StatementDistributionMessage::Statement( + relay_parent, + statement.as_unchecked().clone(), + ) + .into(), + )), + ValidationVersion::V1 => { + gum::error!( + target: LOG_TARGET, + "Bug ValidationVersion::V1 should not be used in statement-distribution v2, + legacy should have handled this" + ); + }, + }; } messages @@ -2167,7 +2536,15 @@ async fn handle_incoming_manifest( }; let messages = acknowledgement_and_statement_messages( - peer, + &( + peer, + state + .peers + .get(&peer) + .map(|val| val.protocol_version) + // Assume the latest stable version, if we don't have info about peer version. + .unwrap_or(ValidationVersion::V2), + ), sender_index, &per_session.groups, relay_parent_state, @@ -2198,7 +2575,7 @@ async fn handle_incoming_manifest( /// Produces acknowledgement and statement messages to be sent over the network, /// noting that they have been sent within the grid topology tracker as well. fn acknowledgement_and_statement_messages( - peer: PeerId, + peer: &(PeerId, ValidationVersion), validator_index: ValidatorIndex, groups: &Groups, relay_parent_state: &mut PerRelayParentState, @@ -2217,11 +2594,28 @@ fn acknowledgement_and_statement_messages( statement_knowledge: local_knowledge.clone(), }; - let msg = Versioned::V2(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( - acknowledgement, + let msg_v2 = Versioned::V2(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( + acknowledgement.clone(), )); - let mut messages = vec![(vec![peer], msg.into())]; + let mut messages = match peer.1 { + ValidationVersion::V2 => vec![(vec![peer.0], msg_v2.into())], + ValidationVersion::V3 => vec![( + vec![peer.0], + Versioned::V3(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( + acknowledgement, + )) + .into(), + )], + ValidationVersion::V1 => { + gum::error!( + target: LOG_TARGET, + "Bug ValidationVersion::V1 should not be used in statement-distribution v2, + legacy should have handled this" + ); + return Vec::new() + }, + }; local_validator.grid_tracker.manifest_sent_to( groups, @@ -2238,9 +2632,10 @@ fn acknowledgement_and_statement_messages( &groups, group_index, candidate_hash, + peer, ); - messages.extend(statement_messages.into_iter().map(|m| (vec![peer], m))); + messages.extend(statement_messages.into_iter().map(|m| (vec![peer.0], m))); messages } @@ -2320,6 +2715,15 @@ async fn handle_incoming_acknowledgement( &per_session.groups, group_index, candidate_hash, + &( + peer, + state + .peers + .get(&peer) + .map(|val| val.protocol_version) + // Assume the latest stable version, if we don't have info about peer version. + .unwrap_or(ValidationVersion::V2), + ), ); if !messages.is_empty() { @@ -2409,7 +2813,7 @@ async fn send_cluster_candidate_statements( Some(s) => s, }; - let local_group = match relay_parent_state.local_validator.as_mut() { + let local_group = match relay_parent_state.active_validator_state_mut() { None => return, Some(v) => v.group, }; @@ -2496,11 +2900,10 @@ pub(crate) async fn dispatch_requests(ctx: &mut Context, state: &mut St }) { // For cluster members, they haven't advertised any statements in particular, // but have surely sent us some. - if local_validator - .cluster_tracker - .knows_candidate(validator_id, identifier.candidate_hash) - { - return Some(StatementFilter::blank(local_validator.cluster_tracker.targets().len())) + if let Some(active) = local_validator.active.as_ref() { + if active.cluster_tracker.knows_candidate(validator_id, identifier.candidate_hash) { + return Some(StatementFilter::blank(active.cluster_tracker.targets().len())) + } } let filter = local_validator @@ -2530,17 +2933,28 @@ pub(crate) async fn dispatch_requests(ctx: &mut Context, state: &mut St } } + // Add disabled validators to the unwanted mask. + let disabled_mask = per_session + .disabled_bitmask(group_index) + .expect("group existence checked above; qed"); + unwanted_mask.seconded_in_group |= &disabled_mask; + unwanted_mask.validated_in_group |= &disabled_mask; + // don't require a backing threshold for cluster candidates. - let require_backing = relay_parent_state.local_validator.as_ref()?.group != group_index; + let local_validator = relay_parent_state.local_validator.as_ref()?; + let require_backing = local_validator + .active + .as_ref() + .map_or(true, |active| active.group != group_index); + + let backing_threshold = if require_backing { + let threshold = per_session.groups.get_size_and_backing_threshold(group_index)?.1; + Some(threshold) + } else { + None + }; - Some(RequestProperties { - unwanted_mask, - backing_threshold: if require_backing { - Some(per_session.groups.get_size_and_backing_threshold(group_index)?.1) - } else { - None - }, - }) + Some(RequestProperties { unwanted_mask, backing_threshold }) }; while let Some(request) = state.request_manager.next_request( @@ -2613,6 +3027,10 @@ pub(crate) async fn handle_response( Some(g) => g, }; + let disabled_mask = per_session + .disabled_bitmask(group_index) + .expect("group_index checked above; qed"); + let res = response.validate_response( &mut state.request_manager, group, @@ -2627,6 +3045,7 @@ pub(crate) async fn handle_response( Some(g_index) == expected_group }, + disabled_mask, ); for (peer, rep) in res.reputation_changes { @@ -2724,6 +3143,14 @@ pub(crate) async fn handle_response( // includable. } +/// Returns true if the statement filter meets the backing threshold for grid requests. +pub(crate) fn seconded_and_sufficient( + filter: &StatementFilter, + backing_threshold: Option, +) -> bool { + backing_threshold.map_or(true, |t| filter.has_seconded() && filter.backing_validators() >= t) +} + /// Answer an incoming request for a candidate. pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { let ResponderMessage { request, sent_feedback } = message; @@ -2764,11 +3191,13 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { Some(d) => d, }; - let group_size = per_session + let group_index = confirmed.group_index(); + let group = per_session .groups - .get(confirmed.group_index()) - .expect("group from session's candidate always known; qed") - .len(); + .get(group_index) + .expect("group from session's candidate always known; qed"); + + let group_size = group.len(); // check request bitfields are right size. if mask.seconded_in_group.len() != group_size || mask.validated_in_group.len() != group_size { @@ -2789,7 +3218,11 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { for v in find_validator_ids(peer_data.iter_known_discovery_ids(), |a| { per_session.authority_lookup.get(a) }) { - if local_validator.cluster_tracker.can_request(v, *candidate_hash) { + if local_validator + .active + .as_ref() + .map_or(false, |active| active.cluster_tracker.can_request(v, *candidate_hash)) + { validator_id = Some(v); is_cluster = true; break @@ -2817,31 +3250,79 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { // Transform mask with 'OR' semantics into one with 'AND' semantics for the API used // below. - let and_mask = StatementFilter { + let mut and_mask = StatementFilter { seconded_in_group: !mask.seconded_in_group.clone(), validated_in_group: !mask.validated_in_group.clone(), }; + // Ignore disabled validators from the latest state when sending the response. + let disabled_mask = + per_session.disabled_bitmask(group_index).expect("group existence checked; qed"); + and_mask.mask_seconded(&disabled_mask); + and_mask.mask_valid(&disabled_mask); + + let mut sent_filter = StatementFilter::blank(group_size); let statements: Vec<_> = relay_parent_state .statement_store - .group_statements(&per_session.groups, confirmed.group_index(), *candidate_hash, &and_mask) - .map(|s| s.as_unchecked().clone()) + .group_statements(&per_session.groups, group_index, *candidate_hash, &and_mask) + .map(|s| { + let s = s.as_unchecked().clone(); + let index_in_group = |v: ValidatorIndex| group.iter().position(|x| &v == x); + let Some(i) = index_in_group(s.unchecked_validator_index()) else { return s }; + + match s.unchecked_payload() { + CompactStatement::Seconded(_) => { + sent_filter.seconded_in_group.set(i, true); + }, + CompactStatement::Valid(_) => { + sent_filter.validated_in_group.set(i, true); + }, + } + s + }) .collect(); + // There should be no response at all for grid requests when the + // backing threshold is no longer met as a result of disabled validators. + if !is_cluster { + let threshold = per_session + .groups + .get_size_and_backing_threshold(group_index) + .expect("group existence checked above; qed") + .1; + + if !seconded_and_sufficient(&sent_filter, Some(threshold)) { + gum::info!( + target: LOG_TARGET, + ?candidate_hash, + relay_parent = ?confirmed.relay_parent(), + ?group_index, + "Dropping a request from a grid peer because the backing threshold is no longer met." + ); + return + } + } + // Update bookkeeping about which statements peers have received. for statement in &statements { if is_cluster { - local_validator.cluster_tracker.note_sent( - validator_id, - statement.unchecked_validator_index(), - statement.unchecked_payload().clone(), - ); + local_validator + .active + .as_mut() + .expect("cluster peer means local is active validator; qed") + .cluster_tracker + .note_sent( + validator_id, + statement.unchecked_validator_index(), + statement.unchecked_payload().clone(), + ); } else { local_validator.grid_tracker.sent_or_received_direct_statement( &per_session.groups, statement.unchecked_validator_index(), validator_id, statement.unchecked_payload(), + false, ); } } diff --git a/polkadot/node/network/statement-distribution/src/v2/requests.rs b/polkadot/node/network/statement-distribution/src/v2/requests.rs index 8507a4b827690acb6b7fca2a4e2de427245b437d..bbcb268415e67141ff1c620a134582d06bd8a67f 100644 --- a/polkadot/node/network/statement-distribution/src/v2/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/requests.rs @@ -30,12 +30,13 @@ //! (which requires state not owned by the request manager). use super::{ - BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT, COST_IMPROPERLY_DECODED_RESPONSE, - COST_INVALID_RESPONSE, COST_INVALID_SIGNATURE, COST_UNREQUESTED_RESPONSE_STATEMENT, - REQUEST_RETRY_DELAY, + seconded_and_sufficient, BENEFIT_VALID_RESPONSE, BENEFIT_VALID_STATEMENT, + COST_IMPROPERLY_DECODED_RESPONSE, COST_INVALID_RESPONSE, COST_INVALID_SIGNATURE, + COST_UNREQUESTED_RESPONSE_STATEMENT, REQUEST_RETRY_DELAY, }; use crate::LOG_TARGET; +use bitvec::prelude::{BitVec, Lsb0}; use polkadot_node_network_protocol::{ request_response::{ outgoing::{Recipient as RequestRecipient, RequestError}, @@ -314,7 +315,16 @@ impl RequestManager { request_props: impl Fn(&CandidateIdentifier) -> Option, peer_advertised: impl Fn(&CandidateIdentifier, &PeerId) -> Option, ) -> Option> { - if response_manager.len() >= MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { + // The number of parallel requests a node can answer is limited by + // `MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS`, however there is no + // need for the current node to limit itself to the same amount the + // requests, because the requests are going to different nodes anyways. + // While looking at https://github.com/paritytech/polkadot-sdk/issues/3314, + // found out that this requests take around 100ms to fullfill, so it + // would make sense to try to request things as early as we can, given + // we would need to request it for each candidate, around 25 right now + // on kusama. + if response_manager.len() >= 2 * MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { return None } @@ -495,10 +505,6 @@ fn find_request_target_with_update( } } -fn seconded_and_sufficient(filter: &StatementFilter, backing_threshold: Option) -> bool { - backing_threshold.map_or(true, |t| filter.has_seconded() && filter.backing_validators() >= t) -} - /// A response to a request, which has not yet been handled. pub struct UnhandledResponse { response: TaggedResponse, @@ -542,6 +548,7 @@ impl UnhandledResponse { session: SessionIndex, validator_key_lookup: impl Fn(ValidatorIndex) -> Option, allowed_para_lookup: impl Fn(ParaId, GroupIndex) -> bool, + disabled_mask: BitVec, ) -> ResponseValidationOutput { let UnhandledResponse { response: TaggedResponse { identifier, requested_peer, props, response }, @@ -625,6 +632,7 @@ impl UnhandledResponse { session, validator_key_lookup, allowed_para_lookup, + disabled_mask, ); if let CandidateRequestStatus::Complete { .. } = output.request_status { @@ -644,6 +652,7 @@ fn validate_complete_response( session: SessionIndex, validator_key_lookup: impl Fn(ValidatorIndex) -> Option, allowed_para_lookup: impl Fn(ParaId, GroupIndex) -> bool, + disabled_mask: BitVec, ) -> ResponseValidationOutput { let RequestProperties { backing_threshold, mut unwanted_mask } = props; @@ -751,6 +760,10 @@ fn validate_complete_response( }, } + if disabled_mask.get(i).map_or(false, |x| *x) { + continue + } + let validator_public = match validator_key_lookup(unchecked_statement.unchecked_validator_index()) { None => { @@ -1013,6 +1026,7 @@ mod tests { let group = &[ValidatorIndex(0), ValidatorIndex(1), ValidatorIndex(2)]; let unwanted_mask = StatementFilter::blank(group_size); + let disabled_mask: BitVec = Default::default(); let request_properties = RequestProperties { unwanted_mask, backing_threshold: None }; // Get requests. @@ -1022,6 +1036,7 @@ mod tests { let peer_advertised = |_identifier: &CandidateIdentifier, _peer: &_| { Some(StatementFilter::full(group_size)) }; + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1056,6 +1071,7 @@ mod tests { 0, validator_key_lookup, allowed_para_lookup, + disabled_mask.clone(), ); assert_eq!( output, @@ -1094,6 +1110,7 @@ mod tests { 0, validator_key_lookup, allowed_para_lookup, + disabled_mask, ); assert_eq!( output, @@ -1141,6 +1158,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1167,12 +1185,14 @@ mod tests { }; let validator_key_lookup = |_v| None; let allowed_para_lookup = |_para, _g_index| true; + let disabled_mask: BitVec = Default::default(); let output = response.validate_response( &mut request_manager, group, 0, validator_key_lookup, allowed_para_lookup, + disabled_mask, ); assert_eq!( output, @@ -1221,6 +1241,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1245,12 +1266,14 @@ mod tests { let validator_key_lookup = |_v| None; let allowed_para_lookup = |_para, _g_index| true; let statements = vec![]; + let disabled_mask: BitVec = Default::default(); let output = response.validate_response( &mut request_manager, group, 0, validator_key_lookup, allowed_para_lookup, + disabled_mask, ); assert_eq!( output, diff --git a/polkadot/node/network/statement-distribution/src/v2/statement_store.rs b/polkadot/node/network/statement-distribution/src/v2/statement_store.rs index 96d976e22cd518e350b9c7de19aac127b3eeccc9..022461e55511cbaba8040064408cd5b47a276cfb 100644 --- a/polkadot/node/network/statement-distribution/src/v2/statement_store.rs +++ b/polkadot/node/network/statement-distribution/src/v2/statement_store.rs @@ -97,10 +97,10 @@ impl StatementStore { groups: &Groups, statement: SignedStatement, origin: StatementOrigin, - ) -> Result { + ) -> Result { let validator_index = statement.validator_index(); let validator_meta = match self.validator_meta.get_mut(&validator_index) { - None => return Err(ValidatorUnknown), + None => return Err(Error::ValidatorUnknown), Some(m) => m, }; @@ -134,7 +134,7 @@ impl StatementStore { "groups passed into `insert` differ from those used at store creation" ); - return Err(ValidatorUnknown) + return Err(Error::ValidatorUnknown) }, }; @@ -251,9 +251,12 @@ impl StatementStore { } } -/// Error indicating that the validator was unknown. +/// Error when inserting a statement into the statement store. #[derive(Debug)] -pub struct ValidatorUnknown; +pub enum Error { + /// The validator was unknown. + ValidatorUnknown, +} type Fingerprint = (ValidatorIndex, CompactStatement); diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs index 80dec1d75ab98be43b820bf7382b4436ef87c3b2..a944a9cd6d021364d3b9fa7b349e9a649866cf29 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs @@ -23,7 +23,7 @@ fn share_seconded_circulated_to_cluster() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -34,7 +34,8 @@ fn share_seconded_circulated_to_cluster() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -52,7 +53,7 @@ fn share_seconded_circulated_to_cluster() { // peer B is in group, has no relay parent in view. // peer C is not in group, has relay parent in view. { - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); connect_peer( &mut overseer, @@ -74,15 +75,7 @@ fn share_seconded_circulated_to_cluster() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let full_signed = state .sign_statement( @@ -119,7 +112,7 @@ fn share_seconded_circulated_to_cluster() { // sharing a `Seconded` message confirms a candidate, which leads to new // fragment tree updates. - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; overseer }); @@ -130,7 +123,7 @@ fn cluster_valid_statement_before_seconded_ignored() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -139,12 +132,13 @@ fn cluster_valid_statement_before_seconded_ignored() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); let candidate_hash = CandidateHash(Hash::repeat_byte(42)); let test_leaf = state.make_dummy_leaf(relay_parent); // peer A is in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; connect_peer( &mut overseer, @@ -154,15 +148,7 @@ fn cluster_valid_statement_before_seconded_ignored() { .await; send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let signed_valid = state.sign_statement( v_a, @@ -184,7 +170,7 @@ fn cluster_valid_statement_before_seconded_ignored() { overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) => { assert_eq!(p, peer_a); - assert_eq!(r, COST_UNEXPECTED_STATEMENT.into()); + assert_eq!(r, COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED.into()); } ); @@ -197,7 +183,7 @@ fn cluster_statement_bad_signature() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -206,12 +192,13 @@ fn cluster_statement_bad_signature() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); let candidate_hash = CandidateHash(Hash::repeat_byte(42)); let test_leaf = state.make_dummy_leaf(relay_parent); // peer A is in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; @@ -223,15 +210,7 @@ fn cluster_statement_bad_signature() { .await; send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // sign statements with wrong signing context, leading to bad signature. let statements = vec![ @@ -277,7 +256,7 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -286,13 +265,13 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); let candidate_hash = CandidateHash(Hash::repeat_byte(42)); let test_leaf = state.make_dummy_leaf(relay_parent); // peer A is not in group, has relay parent in view. - let not_our_group = - if local_validator.group_index.0 == 0 { GroupIndex(1) } else { GroupIndex(0) }; + let not_our_group = if local_group_index.0 == 0 { GroupIndex(1) } else { GroupIndex(0) }; let that_group_validators = state.group_validators(not_our_group, false); let v_non = that_group_validators[0]; @@ -305,15 +284,7 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { .await; send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let statement = state .sign_statement( @@ -334,7 +305,7 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer @@ -346,7 +317,7 @@ fn statement_from_non_cluster_originator_unexpected() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -355,26 +326,19 @@ fn statement_from_non_cluster_originator_unexpected() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); let candidate_hash = CandidateHash(Hash::repeat_byte(42)); let test_leaf = state.make_dummy_leaf(relay_parent); // peer A is not in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; connect_peer(&mut overseer, peer_a.clone(), None).await; send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let statement = state .sign_statement( @@ -395,7 +359,7 @@ fn statement_from_non_cluster_originator_unexpected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer @@ -408,7 +372,7 @@ fn seconded_statement_leads_to_request() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -417,7 +381,8 @@ fn seconded_statement_leads_to_request() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -432,7 +397,7 @@ fn seconded_statement_leads_to_request() { let candidate_hash = candidate.hash(); // peer A is in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; connect_peer( @@ -443,15 +408,7 @@ fn seconded_statement_leads_to_request() { .await; send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let statement = state .sign_statement( @@ -492,7 +449,7 @@ fn seconded_statement_leads_to_request() { if p == peer_a && r == BENEFIT_VALID_RESPONSE.into() => { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; overseer }); @@ -503,7 +460,7 @@ fn cluster_statements_shared_seconded_first() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -512,7 +469,8 @@ fn cluster_statements_shared_seconded_first() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -528,7 +486,7 @@ fn cluster_statements_shared_seconded_first() { // peer A is in group, no relay parent in view. { - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); connect_peer( &mut overseer, @@ -538,15 +496,7 @@ fn cluster_statements_shared_seconded_first() { .await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let full_signed = state .sign_statement( @@ -573,7 +523,7 @@ fn cluster_statements_shared_seconded_first() { .await; // result of new confirmed candidate. - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; overseer .send(FromOrchestra::Communication { @@ -624,7 +574,7 @@ fn cluster_accounts_for_implicit_view() { let config = TestConfig { validator_count: 20, group_size: 3, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -634,7 +584,8 @@ fn cluster_accounts_for_implicit_view() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -651,7 +602,7 @@ fn cluster_accounts_for_implicit_view() { // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. { - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); connect_peer( &mut overseer, @@ -670,15 +621,7 @@ fn cluster_accounts_for_implicit_view() { send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let full_signed = state .sign_statement( @@ -715,7 +658,7 @@ fn cluster_accounts_for_implicit_view() { // sharing a `Seconded` message confirms a candidate, which leads to new // fragment tree updates. - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; // activate new leaf, which has relay-parent in implicit view. let next_relay_parent = Hash::repeat_byte(2); @@ -723,15 +666,7 @@ fn cluster_accounts_for_implicit_view() { next_test_leaf.parent_hash = relay_parent; next_test_leaf.number = 2; - activate_leaf(&mut overseer, &next_test_leaf, &state, false).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(next_relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &next_test_leaf, &state, false, vec![]).await; send_peer_view_change(&mut overseer, peer_a.clone(), view![next_relay_parent]).await; send_peer_view_change(&mut overseer, peer_b.clone(), view![next_relay_parent]).await; @@ -775,7 +710,7 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -784,7 +719,8 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -799,7 +735,7 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { let candidate_hash = candidate.hash(); // peer A is in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; { connect_peer( @@ -812,15 +748,7 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer sends `Seconded` statement. { @@ -877,8 +805,6 @@ fn cluster_messages_imported_after_confirmed_candidate_importable_check() { }, vec![(relay_parent, vec![0])], )], - None, - false, ) .await; @@ -907,7 +833,7 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -916,7 +842,8 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -931,7 +858,7 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { let candidate_hash = candidate.hash(); // peer A is in group, has relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; { connect_peer( @@ -944,15 +871,7 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer sends `Seconded` statement. { @@ -999,17 +918,18 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { ); } - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; let next_relay_parent = Hash::repeat_byte(2); let mut next_test_leaf = state.make_dummy_leaf(next_relay_parent); next_test_leaf.parent_hash = relay_parent; next_test_leaf.number = 2; - activate_leaf(&mut overseer, &next_test_leaf, &state, false).await; - - answer_expected_hypothetical_depth_request( + activate_leaf( &mut overseer, + &next_test_leaf, + &state, + false, vec![( HypotheticalCandidate::Complete { candidate_hash, @@ -1018,8 +938,6 @@ fn cluster_messages_imported_after_new_leaf_importable_check() { }, vec![(relay_parent, vec![0])], )], - Some(next_relay_parent), - false, ) .await; @@ -1048,7 +966,7 @@ fn ensure_seconding_limit_is_respected() { let config = TestConfig { validator_count: 20, group_size: 4, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: Some(AsyncBackingParams { max_candidate_depth: 1, allowed_ancestry_len: 3, @@ -1060,7 +978,8 @@ fn ensure_seconding_limit_is_respected() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1092,7 +1011,7 @@ fn ensure_seconding_limit_is_respected() { let candidate_hash_2 = candidate_2.hash(); let candidate_hash_3 = candidate_3.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; // peers A,B,C are in group, have relay parent in view. @@ -1107,15 +1026,7 @@ fn ensure_seconding_limit_is_respected() { send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Confirm the candidates locally so that we don't send out requests. @@ -1142,7 +1053,7 @@ fn ensure_seconding_limit_is_respected() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Candidate 2. @@ -1168,7 +1079,7 @@ fn ensure_seconding_limit_is_respected() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Send first statement from peer A. 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 5b1dabfc8a0e3a9690be131d5abb27cc78750866..38a12cf32e3b37cd111b4404b53015376e749192 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs @@ -29,7 +29,7 @@ fn backed_candidate_leads_to_advertisement() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -41,7 +41,10 @@ fn backed_candidate_leads_to_advertisement() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + + let other_group = next_group_index(local_group_index, validator_count, group_size); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -55,13 +58,12 @@ fn backed_candidate_leads_to_advertisement() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - let target_group_validators = - state.group_validators((local_validator.group_index.0 + 1).into(), true); - let v_a = other_group_validators[0]; - let v_b = other_group_validators[1]; - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let local_group_validators = state.group_validators(local_group_index, true); + let other_group_validators = state.group_validators(other_group, true); + let v_a = local_group_validators[0]; + let v_b = local_group_validators[1]; + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. @@ -100,15 +102,7 @@ fn backed_candidate_leads_to_advertisement() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -135,7 +129,7 @@ fn backed_candidate_leads_to_advertisement() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Send enough statements to make candidate backable, make sure announcements are sent. @@ -219,7 +213,7 @@ fn backed_candidate_leads_to_advertisement() { assert_eq!(manifest, BackedCandidateManifest { relay_parent, candidate_hash, - group_index: local_validator.group_index, + group_index: local_group_index, para_id: local_para, parent_head_data_hash: pvd.parent_head.hash(), statement_knowledge: StatementFilter { @@ -230,7 +224,7 @@ fn backed_candidate_leads_to_advertisement() { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer @@ -244,7 +238,7 @@ fn received_advertisement_before_confirmation_leads_to_request() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -256,9 +250,9 @@ fn received_advertisement_before_confirmation_leads_to_request() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -273,12 +267,12 @@ fn received_advertisement_before_confirmation_leads_to_request() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - let target_group_validators = state.group_validators(other_group, true); - let v_a = other_group_validators[0]; - let v_b = other_group_validators[1]; - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let local_group_validators = state.group_validators(local_group_index, true); + let other_group_validators = state.group_validators(other_group, true); + let v_a = local_group_validators[0]; + let v_b = local_group_validators[1]; + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. @@ -318,15 +312,7 @@ fn received_advertisement_before_confirmation_leads_to_request() { send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -398,7 +384,7 @@ fn received_advertisement_before_confirmation_leads_to_request() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer @@ -424,23 +410,38 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; - let relay_parent = Hash::repeat_byte(1); - let peer_c = PeerId::random(); - let peer_d = PeerId::random(); - let peer_e = PeerId::random(); - test_harness(config, |state, mut overseer| async move { - let local_validator = state.local.clone().unwrap(); - - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); - let other_para = ParaId::from(other_group.0); - - let test_leaf = state.make_dummy_leaf(relay_parent); + let peers_to_connect = [ + TestPeerToConnect { local: true, relay_parent_in_view: false }, + TestPeerToConnect { local: true, relay_parent_in_view: false }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + ]; + + let TestSetupInfo { + other_group, + other_para, + relay_parent, + test_leaf, + peers, + validators, + .. + } = setup_test_and_connect_peers( + &state, + &mut overseer, + validator_count, + group_size, + &peers_to_connect, + false, + ) + .await; + let [_, _, peer_c, peer_d, _] = peers[..] else { panic!() }; + let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() }; let (candidate, pvd) = make_candidate( relay_parent, @@ -452,52 +453,6 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; - let v_e = target_group_validators[2]; - - // Connect C, D, E - { - connect_peer( - &mut overseer, - peer_c.clone(), - Some(vec![state.discovery_id(v_c)].into_iter().collect()), - ) - .await; - - connect_peer( - &mut overseer, - peer_d.clone(), - Some(vec![state.discovery_id(v_d)].into_iter().collect()), - ) - .await; - - connect_peer( - &mut overseer, - peer_e.clone(), - Some(vec![state.discovery_id(v_e)].into_iter().collect()), - ) - .await; - - send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; - send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await; - send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; - } - - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; - - // Send gossip topology. - send_new_topology(&mut overseer, state.make_dummy_topology()).await; - let manifest = BackedCandidateManifest { relay_parent, candidate_hash, @@ -529,14 +484,7 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { // Receive an advertisement from C. { - send_peer_message( - &mut overseer, - peer_c.clone(), - protocol_v2::StatementDistributionMessage::BackedCandidateManifest( - manifest.clone(), - ), - ) - .await; + send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await; // Should send a request to C. let statements = vec![ @@ -562,40 +510,254 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { ) .await; + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + // Receive Backed message. + send_backed_message(&mut overseer, candidate_hash).await; + + // Should send an acknowledgement back to C. + { assert_matches!( overseer.recv().await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_c && r == BENEFIT_VALID_STATEMENT.into() - ); - assert_matches!( - overseer.recv().await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_c && r == BENEFIT_VALID_STATEMENT.into() + AllMessages:: NetworkBridgeTx( + NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V2( + protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack), + ), + ), + ) + ) => { + assert_eq!(peers, vec![peer_c]); + assert_eq!(ack, BackedCandidateAcknowledgement { + candidate_hash, + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }); + } ); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + // Receive a manifest about the same candidate from peer D. + { + send_manifest_from_peer(&mut overseer, peer_d, manifest.clone()).await; + + let expected_ack = BackedCandidateAcknowledgement { + candidate_hash, + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }; + + // Instantaneously acknowledge. assert_matches!( overseer.recv().await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_c && r == BENEFIT_VALID_STATEMENT.into() + AllMessages:: NetworkBridgeTx( + NetworkBridgeTxMessage::SendValidationMessages(messages) + ) => { + assert_eq!(messages.len(), 1); + assert_eq!(messages[0].0, vec![peer_d]); + + assert_matches!( + &messages[0].1, + Versioned::V2(protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack) + )) if *ack == expected_ack + ); + } ); + } + + overseer + }); +} + +#[test] +fn receive_ack_for_unconfirmed_candidate() { + let validator_count = 6; + let group_size = 3; + let config = TestConfig { + validator_count, + group_size, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + test_harness(config, |state, mut overseer| async move { + let peers_to_connect = [ + TestPeerToConnect { local: true, relay_parent_in_view: true }, + TestPeerToConnect { local: true, relay_parent_in_view: false }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: false }, + ]; + let TestSetupInfo { local_para, relay_parent, test_leaf, peers, .. } = + setup_test_and_connect_peers( + &state, + &mut overseer, + validator_count, + group_size, + &peers_to_connect, + false, + ) + .await; + let [_, _, peer_c, _] = peers[..] else { panic!() }; + + let (candidate, _pvd) = make_candidate( + relay_parent, + 1, + local_para, + test_leaf.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + let ack = BackedCandidateAcknowledgement { + candidate_hash, + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }; + + // Receive an acknowledgement from a peer before the candidate is confirmed. + send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await; + assert_peer_reported!( + &mut overseer, + peer_c, + COST_UNEXPECTED_ACKNOWLEDGEMENT_UNKNOWN_CANDIDATE, + ); + + overseer + }); +} + +// Test receiving unexpected and expected acknowledgements for a locally confirmed candidate. +#[test] +fn received_acknowledgements_for_locally_confirmed() { + let validator_count = 6; + let group_size = 3; + let config = TestConfig { + validator_count, + group_size, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + test_harness(config, |state, mut overseer| async move { + let peers_to_connect = [ + TestPeerToConnect { local: true, relay_parent_in_view: true }, + TestPeerToConnect { local: true, relay_parent_in_view: false }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: false }, + ]; + let TestSetupInfo { + local_validator, + local_group, + local_para, + relay_parent, + test_leaf, + peers, + validators, + .. + } = setup_test_and_connect_peers( + &state, + &mut overseer, + validator_count, + group_size, + &peers_to_connect, + true, + ) + .await; + let [peer_a, peer_b, peer_c, peer_d] = peers[..] else { panic!() }; + let [_, v_b, _, _] = validators[..] else { panic!() }; + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + local_para, + test_leaf.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + let ack = BackedCandidateAcknowledgement { + candidate_hash, + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }; + + // Confirm the candidate locally so that we don't send out requests. + { + let statement = state + .sign_full_statement( + local_validator.validator_index, + Statement::Seconded(candidate.clone()), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + pvd.clone(), + ) + .clone(); + + send_share_message(&mut overseer, relay_parent, statement).await; assert_matches!( overseer.recv().await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } - // Receive Backed message. - overseer - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::Backed(candidate_hash), - }) + // Receive an unexpected acknowledgement from peer D. + send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await; + assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED); + + // Send statement from peer B. + { + let statement = state + .sign_statement( + v_b, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + + send_peer_message( + &mut overseer, + peer_b.clone(), + protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement), + ) .await; - // Should send an acknowledgement back to C. + assert_peer_reported!(&mut overseer, peer_b, BENEFIT_VALID_STATEMENT_FIRST); + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] + ); + } + + // Send Backed notification. { + send_backed_message(&mut overseer, candidate_hash).await; + + // We should send out a manifest. assert_matches!( overseer.recv().await, AllMessages:: NetworkBridgeTx( @@ -603,63 +765,178 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() { peers, Versioned::V2( protocol_v2::ValidationProtocol::StatementDistribution( - protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack), + protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest), ), ), ) ) => { assert_eq!(peers, vec![peer_c]); - assert_eq!(ack, BackedCandidateAcknowledgement { + assert_eq!(manifest, BackedCandidateManifest { + relay_parent, candidate_hash, + group_index: local_group, + para_id: local_para, + parent_head_data_hash: pvd.parent_head.hash(), statement_knowledge: StatementFilter { - seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1], validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], }, }); } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; - } + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + // Receive an unexpected acknowledgement from peer D. + // + // It still shouldn't know this manifest. + send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await; + assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED); + + // Receive an acknowledgement from peer C. + // + // It's OK, we know they know it because we sent them a manifest. + send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await; + + // What happens if we get another valid ack? + send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await; + + overseer + }); +} + +// Test receiving unexpected acknowledgements for a candidate confirmed in a different group. +#[test] +fn received_acknowledgements_for_externally_confirmed() { + let validator_count = 6; + let group_size = 3; + let config = TestConfig { + validator_count, + group_size, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + test_harness(config, |state, mut overseer| async move { + let peers_to_connect = [ + TestPeerToConnect { local: true, relay_parent_in_view: true }, + TestPeerToConnect { local: true, relay_parent_in_view: false }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + TestPeerToConnect { local: false, relay_parent_in_view: true }, + ]; + let TestSetupInfo { + other_group, + other_para, + relay_parent, + test_leaf, + peers, + validators, + .. + } = setup_test_and_connect_peers( + &state, + &mut overseer, + validator_count, + group_size, + &peers_to_connect, + false, + ) + .await; + let [peer_a, _, peer_c, peer_d, _] = peers[..] else { panic!() }; + let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() }; + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + other_para, + test_leaf.para_data(other_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + let manifest = BackedCandidateManifest { + relay_parent, + candidate_hash, + group_index: other_group, + para_id: other_para, + parent_head_data_hash: pvd.parent_head.hash(), + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }; + + let statement_c = state + .sign_statement( + v_c, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + let statement_d = state + .sign_statement( + v_d, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); - // Receive a manifest about the same candidate from peer D. + // Receive an advertisement from C, confirming the candidate. { - send_peer_message( + send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await; + + // Should send a request to C. + let statements = vec![ + statement_c.clone(), + statement_d.clone(), + state + .sign_statement( + v_e, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(), + ]; + handle_sent_request( &mut overseer, - peer_d.clone(), - protocol_v2::StatementDistributionMessage::BackedCandidateManifest( - manifest.clone(), - ), + peer_c, + candidate_hash, + StatementFilter::blank(group_size), + candidate.clone(), + pvd.clone(), + statements, ) .await; - let expected_ack = BackedCandidateAcknowledgement { - candidate_hash, - statement_knowledge: StatementFilter { - seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], - validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], - }, - }; - - // Instantaneously acknowledge. - assert_matches!( - overseer.recv().await, - AllMessages:: NetworkBridgeTx( - NetworkBridgeTxMessage::SendValidationMessages(messages) - ) => { - assert_eq!(messages.len(), 1); - assert_eq!(messages[0].0, vec![peer_d]); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT); + assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE); - assert_matches!( - &messages[0].1, - Versioned::V2(protocol_v2::ValidationProtocol::StatementDistribution( - protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack) - )) if *ack == expected_ack - ); - } - ); + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } + let ack = BackedCandidateAcknowledgement { + candidate_hash, + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }; + + // Receive an unexpected acknowledgement from peer D. + send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await; + assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_PEER_UNKNOWN); + + // Receive an unexpected acknowledgement from peer A. + send_ack_from_peer(&mut overseer, peer_a, ack.clone()).await; + assert_peer_reported!(&mut overseer, peer_a, COST_UNEXPECTED_MANIFEST_DISALLOWED); + overseer }); } @@ -672,7 +949,7 @@ fn received_advertisement_after_confirmation_before_backing() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -683,9 +960,9 @@ fn received_advertisement_after_confirmation_before_backing() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -700,10 +977,10 @@ fn received_advertisement_after_confirmation_before_backing() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; - let v_e = target_group_validators[2]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; + let v_e = other_group_validators[2]; // Connect C, D, E { @@ -733,15 +1010,7 @@ fn received_advertisement_after_confirmation_before_backing() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -832,7 +1101,7 @@ fn received_advertisement_after_confirmation_before_backing() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Receive advertisement from peer D (after confirmation but before backing). @@ -858,7 +1127,7 @@ fn additional_statements_are_shared_after_manifest_exchange() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -869,9 +1138,9 @@ fn additional_statements_are_shared_after_manifest_exchange() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -886,10 +1155,10 @@ fn additional_statements_are_shared_after_manifest_exchange() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; - let v_e = target_group_validators[2]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; + let v_e = other_group_validators[2]; // Connect C, D, E { @@ -919,15 +1188,7 @@ fn additional_statements_are_shared_after_manifest_exchange() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -1012,13 +1273,8 @@ fn additional_statements_are_shared_after_manifest_exchange() { persisted_validation_data: pvd.clone(), }; let membership = vec![(relay_parent, vec![0])]; - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![(hypothetical, membership)], - None, - false, - ) - .await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![(hypothetical, membership)]) + .await; // Statements are sent to the Backing subsystem. { @@ -1082,7 +1338,7 @@ fn additional_statements_are_shared_after_manifest_exchange() { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Receive a manifest about the same candidate from peer D. Contains different statements. @@ -1155,7 +1411,7 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1167,7 +1423,8 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1181,13 +1438,12 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - let target_group_validators = - state.group_validators((local_validator.group_index.0 + 1).into(), true); - let v_a = other_group_validators[0]; - let v_b = other_group_validators[1]; - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let local_group_validators = state.group_validators(local_group_index, true); + let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true); + let v_a = local_group_validators[0]; + let v_b = local_group_validators[1]; + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. @@ -1225,17 +1481,8 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; // Confirm the candidate locally so that we don't send out requests. @@ -1260,7 +1507,7 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Send enough statements to make candidate backable, make sure announcements are sent. @@ -1327,7 +1574,7 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { }) .await; - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; // Relay parent enters view of peer C. { @@ -1336,7 +1583,7 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() { let expected_manifest = BackedCandidateManifest { relay_parent, candidate_hash, - group_index: local_validator.group_index, + group_index: local_group_index, para_id: local_para, parent_head_data_hash: pvd.parent_head.hash(), statement_knowledge: StatementFilter { @@ -1377,7 +1624,7 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1389,7 +1636,8 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1403,13 +1651,12 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - let target_group_validators = - state.group_validators((local_validator.group_index.0 + 1).into(), true); - let v_a = other_group_validators[0]; - let v_b = other_group_validators[1]; - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let local_group_validators = state.group_validators(local_group_index, true); + let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true); + let v_a = local_group_validators[0]; + let v_b = local_group_validators[1]; + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer A is in group, has relay parent in view. // peer B is in group, has no relay parent in view. @@ -1448,17 +1695,8 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; // Confirm the candidate locally so that we don't send out requests. @@ -1483,7 +1721,7 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Send enough statements to make candidate backable, make sure announcements are sent. @@ -1567,7 +1805,7 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { assert_eq!(manifest, BackedCandidateManifest { relay_parent, candidate_hash, - group_index: local_validator.group_index, + group_index: local_group_index, para_id: local_para, parent_head_data_hash: pvd.parent_head.hash(), statement_knowledge: StatementFilter { @@ -1578,7 +1816,7 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Peer leaves view. @@ -1599,7 +1837,7 @@ fn grid_statements_imported_to_backing() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1610,9 +1848,9 @@ fn grid_statements_imported_to_backing() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1627,10 +1865,10 @@ fn grid_statements_imported_to_backing() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; - let v_e = target_group_validators[2]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; + let v_e = other_group_validators[2]; // Connect C, D, E { @@ -1660,17 +1898,8 @@ fn grid_statements_imported_to_backing() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; // Receive an advertisement from C. @@ -1753,13 +1982,8 @@ fn grid_statements_imported_to_backing() { persisted_validation_data: pvd.clone(), }; let membership = vec![(relay_parent, vec![0])]; - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![(hypothetical, membership)], - None, - false, - ) - .await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![(hypothetical, membership)]) + .await; // Receive messages from Backing subsystem. { @@ -1803,7 +2027,7 @@ fn advertisements_rejected_from_incorrect_peers() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1815,9 +2039,9 @@ fn advertisements_rejected_from_incorrect_peers() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1832,10 +2056,10 @@ fn advertisements_rejected_from_incorrect_peers() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(local_validator.group_index, true); + let local_group_validators = state.group_validators(local_group_index, true); let other_group_validators = state.group_validators(other_group, true); - let v_a = target_group_validators[0]; - let v_b = target_group_validators[1]; + let v_a = local_group_validators[0]; + let v_b = local_group_validators[1]; let v_c = other_group_validators[0]; let v_d = other_group_validators[1]; @@ -1876,17 +2100,8 @@ fn advertisements_rejected_from_incorrect_peers() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; let manifest = BackedCandidateManifest { @@ -1948,7 +2163,7 @@ fn manifest_rejected_with_unknown_relay_parent() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1959,9 +2174,9 @@ fn manifest_rejected_with_unknown_relay_parent() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1976,9 +2191,9 @@ fn manifest_rejected_with_unknown_relay_parent() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer C is not in group, has relay parent in view. // peer D is not in group, has no relay parent in view. @@ -2000,17 +2215,8 @@ fn manifest_rejected_with_unknown_relay_parent() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; - - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; let manifest = BackedCandidateManifest { @@ -2054,7 +2260,7 @@ fn manifest_rejected_when_not_a_validator() { let config = TestConfig { validator_count, group_size, - local_validator: false, + local_validator: LocalRole::None, async_backing_params: None, }; @@ -2078,9 +2284,9 @@ fn manifest_rejected_when_not_a_validator() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer C is not in group, has relay parent in view. // peer D is not in group, has no relay parent in view. @@ -2102,17 +2308,8 @@ fn manifest_rejected_when_not_a_validator() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; let manifest = BackedCandidateManifest { @@ -2156,7 +2353,7 @@ fn manifest_rejected_when_group_does_not_match_para() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -2166,9 +2363,9 @@ fn manifest_rejected_when_group_does_not_match_para() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); // Create a mismatch between group and para. let other_para = next_group_index(other_group, validator_count, group_size); let other_para = ParaId::from(other_para.0); @@ -2185,9 +2382,9 @@ fn manifest_rejected_when_group_does_not_match_para() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; // peer C is not in group, has relay parent in view. // peer D is not in group, has no relay parent in view. @@ -2209,17 +2406,8 @@ fn manifest_rejected_when_group_does_not_match_para() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; let manifest = BackedCandidateManifest { @@ -2263,7 +2451,7 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -2274,9 +2462,9 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -2291,10 +2479,10 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { ); let candidate_hash = candidate.hash(); - let target_group_validators = state.group_validators(other_group, true); - let v_c = target_group_validators[0]; - let v_d = target_group_validators[1]; - let v_e = target_group_validators[2]; + let other_group_validators = state.group_validators(other_group, true); + let v_c = other_group_validators[0]; + let v_d = other_group_validators[1]; + let v_e = other_group_validators[2]; // Connect C, D, E { @@ -2324,17 +2512,8 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; let manifest = BackedCandidateManifest { @@ -2424,7 +2603,7 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Receive conflicting advertisement from peer C after confirmation. @@ -2454,3 +2633,125 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() { overseer }); } + +#[test] +fn inactive_local_participates_in_grid() { + let validator_count = 11; + let group_size = 3; + let config = TestConfig { + validator_count, + group_size, + local_validator: LocalRole::InactiveValidator, + async_backing_params: None, + }; + + let relay_parent = Hash::repeat_byte(1); + let peer_a = PeerId::random(); + + test_harness(config, |state, mut overseer| async move { + let local_validator = state.local.clone().unwrap(); + assert_eq!(local_validator.validator_index.0, validator_count as u32); + + let group_idx = GroupIndex::from(0); + let para = ParaId::from(0); + + // Dummy leaf is needed to update topology. + let dummy_leaf = state.make_dummy_leaf(Hash::repeat_byte(2)); + let test_leaf = state.make_dummy_leaf(relay_parent); + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + para, + test_leaf.para_data(para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + let first_group = state.group_validators(group_idx, true); + let v_a = first_group.last().unwrap().clone(); + let v_b = first_group.first().unwrap().clone(); + + { + connect_peer( + &mut overseer, + peer_a.clone(), + Some(vec![state.discovery_id(v_a)].into_iter().collect()), + ) + .await; + + send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; + } + + activate_leaf(&mut overseer, &dummy_leaf, &state, true, vec![]).await; + // Send gossip topology. + send_new_topology(&mut overseer, state.make_dummy_topology()).await; + activate_leaf(&mut overseer, &test_leaf, &state, false, vec![]).await; + + // Receive an advertisement from A. + let manifest = BackedCandidateManifest { + relay_parent, + candidate_hash, + group_index: group_idx, + para_id: para, + parent_head_data_hash: pvd.parent_head.hash(), + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 0, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 1, 0, 1], + }, + }; + send_peer_message( + &mut overseer, + peer_a.clone(), + protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest), + ) + .await; + + let statements = vec![ + state + .sign_statement( + v_a, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(), + state + .sign_statement( + v_b, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(), + ]; + // Inactive node requests this candidate. + handle_sent_request( + &mut overseer, + peer_a, + candidate_hash, + StatementFilter::blank(group_size), + candidate.clone(), + pvd.clone(), + statements, + ) + .await; + + for _ in 0..2 { + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_a && r == BENEFIT_VALID_STATEMENT.into() => { } + ); + } + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_a && r == BENEFIT_VALID_RESPONSE.into() => { } + ); + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + + overseer + }); +} diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs index 4150377a0c6c219832104024f6d6c046dccec709..82986a0330ec25d35f3b75a94be67157283bbd9a 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs @@ -21,6 +21,7 @@ use crate::*; use polkadot_node_network_protocol::{ grid_topology::TopologyPeerInfo, request_response::{outgoing::Recipient, ReqProtocolNames}, + v2::{BackedCandidateAcknowledgement, BackedCandidateManifest}, view, ObservedRole, }; use polkadot_node_primitives::Statement; @@ -37,6 +38,7 @@ use polkadot_primitives::{ SessionIndex, SessionInfo, ValidatorPair, }; use sc_keystore::LocalKeystore; +use sc_network::ProtocolName; use sp_application_crypto::Pair as PairT; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_keyring::Sr25519Keyring; @@ -61,19 +63,30 @@ const DEFAULT_ASYNC_BACKING_PARAMETERS: AsyncBackingParams = // Some deterministic genesis hash for req/res protocol names const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); +#[derive(Debug, Copy, Clone)] +enum LocalRole { + /// Active validator. + Validator, + /// Authority, not in active validator set. + InactiveValidator, + /// Not a validator. + None, +} + struct TestConfig { + // number of active validators. validator_count: usize, // how many validators to place in each group. group_size: usize, // whether the local node should be a validator - local_validator: bool, + local_validator: LocalRole, async_backing_params: Option, } #[derive(Debug, Clone)] struct TestLocalValidator { validator_index: ValidatorIndex, - group_index: GroupIndex, + group_index: Option, } struct TestState { @@ -99,7 +112,7 @@ impl TestState { let mut assignment_keys = Vec::new(); let mut validator_groups = Vec::new(); - let local_validator_pos = if config.local_validator { + let local_validator_pos = if let LocalRole::Validator = config.local_validator { // ensure local validator is always in a full group. Some(rng.gen_range(0..config.validator_count).saturating_sub(config.group_size - 1)) } else { @@ -128,13 +141,19 @@ impl TestState { } } - let local = if let Some(local_pos) = local_validator_pos { - Some(TestLocalValidator { + let local = match (config.local_validator, local_validator_pos) { + (LocalRole::Validator, Some(local_pos)) => Some(TestLocalValidator { validator_index: ValidatorIndex(local_pos as _), - group_index: GroupIndex((local_pos / config.group_size) as _), - }) - } else { - None + group_index: Some(GroupIndex((local_pos / config.group_size) as _)), + }), + (LocalRole::InactiveValidator, None) => { + discovery_keys.push(AuthorityDiscoveryPair::generate().0.public()); + Some(TestLocalValidator { + validator_index: ValidatorIndex(config.validator_count as u32), + group_index: None, + }) + }, + _ => None, }; let validator_public = validator_pubkeys(&validators); @@ -169,27 +188,53 @@ impl TestState { collator: None, }) }), + disabled_validators: Default::default(), para_data: (0..self.session_info.validator_groups.len()) .map(|i| (ParaId::from(i as u32), PerParaData::new(1, vec![1, 2, 3].into()))) .collect(), + minimum_backing_votes: 2, } } + fn make_dummy_leaf_with_disabled_validators( + &self, + relay_parent: Hash, + disabled_validators: Vec, + ) -> TestLeaf { + TestLeaf { disabled_validators, ..self.make_dummy_leaf(relay_parent) } + } + + fn make_dummy_leaf_with_min_backing_votes( + &self, + relay_parent: Hash, + minimum_backing_votes: u32, + ) -> TestLeaf { + TestLeaf { minimum_backing_votes, ..self.make_dummy_leaf(relay_parent) } + } + fn make_availability_cores(&self, f: impl Fn(usize) -> CoreState) -> Vec { (0..self.session_info.validator_groups.len()).map(f).collect() } fn make_dummy_topology(&self) -> NewGossipTopology { let validator_count = self.config.validator_count; + let is_local_inactive = matches!(self.config.local_validator, LocalRole::InactiveValidator); + + let mut indices: Vec = (0..validator_count).collect(); + if is_local_inactive { + indices.push(validator_count); + } + NewGossipTopology { session: 1, topology: SessionGridTopology::new( - (0..validator_count).collect(), - (0..validator_count) + indices.clone(), + indices + .into_iter() .map(|i| TopologyPeerInfo { peer_ids: Vec::new(), validator_index: ValidatorIndex(i as u32), - discovery_id: AuthorityDiscoveryPair::generate().0.public(), + discovery_id: self.session_info.discovery_keys[i].clone(), }) .collect(), ), @@ -214,6 +259,19 @@ impl TestState { .collect() } + fn index_within_group( + &self, + group_index: GroupIndex, + validator_index: ValidatorIndex, + ) -> Option { + self.session_info + .validator_groups + .get(group_index) + .unwrap() + .iter() + .position(|&i| i == validator_index) + } + fn discovery_id(&self, validator_index: ValidatorIndex) -> AuthorityDiscoveryId { self.session_info.discovery_keys[validator_index.0 as usize].clone() } @@ -258,7 +316,7 @@ impl TestState { &mut self, peer: PeerId, request: AttestedCandidateRequest, - ) -> impl Future { + ) -> impl Future> { let (tx, rx) = futures::channel::oneshot::channel(); let req = sc_network::config::IncomingRequest { peer, @@ -267,7 +325,7 @@ impl TestState { }; self.req_sender.send(req).await.unwrap(); - rx.map(|r| r.unwrap()) + rx.map(|r| r.ok()) } } @@ -276,7 +334,7 @@ fn test_harness>( test: impl FnOnce(TestState, VirtualOverseer) -> T, ) { let pool = sp_core::testing::TaskExecutor::new(); - let keystore = if config.local_validator { + let keystore = if let LocalRole::Validator = config.local_validator { test_helpers::mock::make_ferdie_keystore() } else { Arc::new(LocalKeystore::in_memory()) as KeystorePtr @@ -340,7 +398,9 @@ struct TestLeaf { parent_hash: Hash, session: SessionIndex, availability_cores: Vec, + disabled_validators: Vec, para_data: Vec<(ParaId, PerParaData)>, + minimum_backing_votes: u32, } impl TestLeaf { @@ -352,11 +412,104 @@ impl TestLeaf { } } +struct TestSetupInfo { + local_validator: TestLocalValidator, + local_group: GroupIndex, + local_para: ParaId, + other_group: GroupIndex, + other_para: ParaId, + relay_parent: Hash, + test_leaf: TestLeaf, + peers: Vec, + validators: Vec, +} + +struct TestPeerToConnect { + local: bool, + relay_parent_in_view: bool, +} + +// TODO: Generalize, use in more places. +/// Sets up some test info that is common to most tests, and connects the requested peers. +async fn setup_test_and_connect_peers( + state: &TestState, + overseer: &mut VirtualOverseer, + validator_count: usize, + group_size: usize, + peers_to_connect: &[TestPeerToConnect], + send_topology_before_leaf: bool, +) -> TestSetupInfo { + let local_validator = state.local.clone().unwrap(); + let local_group = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group.0); + + let other_group = next_group_index(local_group, validator_count, group_size); + let other_para = ParaId::from(other_group.0); + + let relay_parent = Hash::repeat_byte(1); + let test_leaf = state.make_dummy_leaf(relay_parent); + + // Because we are testing grid mod, the "target" group (the one we communicate with) is usually + // other_group, a non-local group. + // + // TODO: change based on `LocalRole`? + let local_group_validators = state.group_validators(local_group, true); + let other_group_validators = state.group_validators(other_group, true); + + let mut peers = vec![]; + let mut validators = vec![]; + let mut local_group_idx = 0; + let mut other_group_idx = 0; + for peer_to_connect in peers_to_connect { + let peer = PeerId::random(); + peers.push(peer); + + let v = if peer_to_connect.local { + let v = local_group_validators[local_group_idx]; + local_group_idx += 1; + v + } else { + let v = other_group_validators[other_group_idx]; + other_group_idx += 1; + v + }; + validators.push(v); + + connect_peer(overseer, peer, Some(vec![state.discovery_id(v)].into_iter().collect())).await; + + if peer_to_connect.relay_parent_in_view { + send_peer_view_change(overseer, peer.clone(), view![relay_parent]).await; + } + } + + // Send gossip topology and activate leaf. + if send_topology_before_leaf { + send_new_topology(overseer, state.make_dummy_topology()).await; + activate_leaf(overseer, &test_leaf, &state, true, vec![]).await; + } else { + activate_leaf(overseer, &test_leaf, &state, true, vec![]).await; + send_new_topology(overseer, state.make_dummy_topology()).await; + } + + TestSetupInfo { + local_validator, + local_group, + local_para, + other_group, + other_para, + test_leaf, + relay_parent, + peers, + validators, + } +} + async fn activate_leaf( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, is_new_session: bool, + hypothetical_frontier: Vec<(HypotheticalCandidate, FragmentTreeMembership)>, ) { let activated = new_leaf(leaf.hash, leaf.number); @@ -366,7 +519,14 @@ async fn activate_leaf( )))) .await; - handle_leaf_activation(virtual_overseer, leaf, test_state, is_new_session).await; + handle_leaf_activation( + virtual_overseer, + leaf, + test_state, + is_new_session, + hypothetical_frontier, + ) + .await; } async fn handle_leaf_activation( @@ -374,8 +534,18 @@ async fn handle_leaf_activation( leaf: &TestLeaf, test_state: &TestState, is_new_session: bool, + hypothetical_frontier: Vec<(HypotheticalCandidate, FragmentTreeMembership)>, ) { - let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf; + let TestLeaf { + number, + hash, + parent_hash, + para_data, + session, + availability_cores, + disabled_validators, + minimum_backing_votes, + } = leaf; assert_matches!( virtual_overseer.recv().await, @@ -415,51 +585,82 @@ async fn handle_leaf_activation( } ); - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))) if parent == *hash => { - tx.send(Ok(*session)).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))) if parent == *hash => { - tx.send(Ok(availability_cores.clone())).unwrap(); - } - ); - - let validator_groups = test_state.session_info.validator_groups.to_vec(); - let group_rotation_info = - GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 12, now: 1 }; - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))) if parent == *hash => { - tx.send(Ok((validator_groups, group_rotation_info))).unwrap(); - } - ); - - if is_new_session { - assert_matches!( - virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionInfo(s, tx))) if parent == *hash && s == *session => { + loop { + match virtual_overseer.recv().await { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::Version(tx), + )) => { + tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT)).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::DisabledValidators(tx), + )) if parent == *hash => { + tx.send(Ok(disabled_validators.clone())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::DisabledValidators(tx), + )) => { + tx.send(Ok(Vec::new())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, // assume all active leaves are in the same session + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + tx.send(Ok(*session)).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) if parent == *hash && s == *session => { + assert!(is_new_session, "only expecting this call in a new session"); tx.send(Ok(Some(test_state.session_info.clone()))).unwrap(); - } - ); - - assert_matches!( - virtual_overseer.recv().await, + }, AllMessages::RuntimeApi(RuntimeApiMessage::Request( parent, RuntimeApiRequest::MinimumBackingVotes(session_index, tx), )) if parent == *hash && session_index == *session => { - tx.send(Ok(2)).unwrap(); - } - ); + assert!(is_new_session, "only expecting this call in a new session"); + tx.send(Ok(*minimum_backing_votes)).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::AvailabilityCores(tx), + )) if parent == *hash => { + tx.send(Ok(availability_cores.clone())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ValidatorGroups(tx), + )) if parent == *hash => { + let validator_groups = test_state.session_info.validator_groups.to_vec(); + let group_rotation_info = GroupRotationInfo { + session_start_block: 1, + group_rotation_frequency: 12, + now: 1, + }; + tx.send(Ok((validator_groups, group_rotation_info))).unwrap(); + }, + AllMessages::ProspectiveParachains( + ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx), + ) => { + assert_eq!(req.fragment_tree_relay_parent, Some(*hash)); + assert!(!req.backed_in_path_only); + for (i, (candidate, _)) in hypothetical_frontier.iter().enumerate() { + assert!( + req.candidates.iter().any(|c| &c == &candidate), + "did not receive request for hypothetical candidate {}", + i, + ); + } + tx.send(hypothetical_frontier).unwrap(); + // this is the last expected runtime api call + break + }, + msg => panic!("unexpected runtime API call: {msg:?}"), + } } } @@ -489,7 +690,7 @@ async fn handle_sent_request( persisted_validation_data, statements, }; - outgoing.pending_response.send(Ok(res.encode())).unwrap(); + outgoing.pending_response.send(Ok((res.encode(), ProtocolName::from("")))).unwrap(); } ); } @@ -499,16 +700,14 @@ async fn handle_sent_request( async fn answer_expected_hypothetical_depth_request( virtual_overseer: &mut VirtualOverseer, responses: Vec<(HypotheticalCandidate, FragmentTreeMembership)>, - expected_leaf_hash: Option, - expected_backed_in_path_only: bool, ) { assert_matches!( virtual_overseer.recv().await, AllMessages::ProspectiveParachains( ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx) ) => { - assert_eq!(req.fragment_tree_relay_parent, expected_leaf_hash); - assert_eq!(req.backed_in_path_only, expected_backed_in_path_only); + assert_eq!(req.fragment_tree_relay_parent, None); + assert!(!req.backed_in_path_only); for (i, (candidate, _)) in responses.iter().enumerate() { assert!( req.candidates.iter().any(|c| &c == &candidate), @@ -522,6 +721,66 @@ async fn answer_expected_hypothetical_depth_request( ) } +#[macro_export] +macro_rules! assert_peer_reported { + ($virtual_overseer:expr, $peer_id:expr, $rep_change:expr $(,)*) => { + assert_matches!( + $virtual_overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == $peer_id && r == $rep_change.into() + ); + } +} + +async fn send_share_message( + virtual_overseer: &mut VirtualOverseer, + relay_parent: Hash, + statement: SignedFullStatementWithPVD, +) { + virtual_overseer + .send(FromOrchestra::Communication { + msg: StatementDistributionMessage::Share(relay_parent, statement), + }) + .await; +} + +async fn send_backed_message( + virtual_overseer: &mut VirtualOverseer, + candidate_hash: CandidateHash, +) { + virtual_overseer + .send(FromOrchestra::Communication { + msg: StatementDistributionMessage::Backed(candidate_hash), + }) + .await; +} + +async fn send_manifest_from_peer( + virtual_overseer: &mut VirtualOverseer, + peer_id: PeerId, + manifest: BackedCandidateManifest, +) { + send_peer_message( + virtual_overseer, + peer_id, + protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest), + ) + .await; +} + +async fn send_ack_from_peer( + virtual_overseer: &mut VirtualOverseer, + peer_id: PeerId, + ack: BackedCandidateAcknowledgement, +) { + send_peer_message( + virtual_overseer, + peer_id, + protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack), + ) + .await; +} + fn validator_pubkeys(val_ids: &[ValidatorPair]) -> IndexedVec { val_ids.iter().map(|v| v.public().into()).collect() } diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs index 4734d7a0f960bf43cd09a50ceb96c29fa0deadf3..dc2c8f55290b43e5dce172730d3c5027dddc4b03 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/requests.rs @@ -22,8 +22,9 @@ use polkadot_node_network_protocol::{ request_response::v2 as request_v2, v2::BackedCandidateManifest, }; use polkadot_primitives_test_helpers::make_candidate; -use sc_network::config::{ - IncomingRequest as RawIncomingRequest, OutgoingResponse as RawOutgoingResponse, +use sc_network::{ + config::{IncomingRequest as RawIncomingRequest, OutgoingResponse as RawOutgoingResponse}, + ProtocolName, }; #[test] @@ -32,7 +33,7 @@ fn cluster_peer_allowed_to_send_incomplete_statements() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -43,7 +44,8 @@ fn cluster_peer_allowed_to_send_incomplete_statements() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -57,7 +59,7 @@ fn cluster_peer_allowed_to_send_incomplete_statements() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; @@ -85,15 +87,7 @@ fn cluster_peer_allowed_to_send_incomplete_statements() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer in cluster sends a statement, triggering a request. { @@ -175,7 +169,7 @@ fn cluster_peer_allowed_to_send_incomplete_statements() { ); } - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; overseer }); @@ -188,7 +182,7 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: Some(AsyncBackingParams { // Makes `seconding_limit: 2` (easier to hit the limit). max_candidate_depth: 1, @@ -203,9 +197,9 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -271,15 +265,7 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -353,7 +339,7 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Peer C advertises candidate 2. @@ -425,7 +411,7 @@ fn peer_reported_for_providing_statements_meant_to_be_masked_out() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Peer C sends an announcement for candidate 3. Should hit seconding limit for validator 1. @@ -475,7 +461,7 @@ fn peer_reported_for_not_enough_statements() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -486,9 +472,9 @@ fn peer_reported_for_not_enough_statements() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -536,15 +522,7 @@ fn peer_reported_for_not_enough_statements() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -656,7 +634,7 @@ fn peer_reported_for_not_enough_statements() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer @@ -670,7 +648,7 @@ fn peer_reported_for_duplicate_statements() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -681,7 +659,8 @@ fn peer_reported_for_duplicate_statements() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -695,7 +674,7 @@ fn peer_reported_for_duplicate_statements() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; @@ -723,15 +702,7 @@ fn peer_reported_for_duplicate_statements() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer in cluster sends a statement, triggering a request. { @@ -818,7 +789,7 @@ fn peer_reported_for_duplicate_statements() { ); } - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; overseer }); @@ -830,7 +801,7 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -841,7 +812,8 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -855,8 +827,8 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - state.group_validators((local_validator.group_index.0 + 1).into(), true); + let other_group_validators = state.group_validators(local_group_index, true); + state.group_validators((local_group_index.0 + 1).into(), true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; @@ -884,15 +856,7 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer in cluster sends a statement, triggering a request. { @@ -955,7 +919,7 @@ fn peer_reported_for_providing_statements_with_invalid_signatures() { if p == peer_a && r == BENEFIT_VALID_RESPONSE.into() => { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer @@ -968,7 +932,7 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { let config = TestConfig { validator_count: 20, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -979,7 +943,8 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -993,9 +958,8 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); - let next_group_validators = - state.group_validators((local_validator.group_index.0 + 1).into(), true); + let other_group_validators = state.group_validators(local_group_index, true); + let next_group_validators = state.group_validators((local_group_index.0 + 1).into(), true); let v_a = other_group_validators[0]; let v_c = next_group_validators[0]; @@ -1023,15 +987,7 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Peer in cluster sends a statement, triggering a request. { @@ -1093,7 +1049,7 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { if p == peer_a && r == BENEFIT_VALID_RESPONSE.into() => { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer @@ -1101,25 +1057,31 @@ fn peer_reported_for_providing_statements_with_wrong_validator_id() { } #[test] -fn local_node_sanity_checks_incoming_requests() { +fn disabled_validators_added_to_unwanted_mask() { + let group_size = 3; let config = TestConfig { validator_count: 20, - group_size: 3, - local_validator: true, + group_size, + local_validator: LocalRole::Validator, async_backing_params: None, }; let relay_parent = Hash::repeat_byte(1); - let peer_a = PeerId::random(); + let peer_disabled = PeerId::random(); let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - let peer_d = PeerId::random(); - test_harness(config, |mut state, mut overseer| async move { + test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + let other_group_validators = state.group_validators(local_group_index, true); + let index_disabled = other_group_validators[0]; + let index_within_group = state.index_within_group(local_group_index, index_disabled); + let index_b = other_group_validators[1]; - let test_leaf = state.make_dummy_leaf(relay_parent); + let disabled_validators = vec![index_disabled]; + let test_leaf = + state.make_dummy_leaf_with_disabled_validators(relay_parent, disabled_validators); let (candidate, pvd) = make_candidate( relay_parent, @@ -1131,199 +1093,164 @@ fn local_node_sanity_checks_incoming_requests() { ); let candidate_hash = candidate.hash(); - // peer A is in group, has relay parent in view. - // peer B is in group, has no relay parent in view. - // peer C is not in group, has relay parent in view. + // peer A is in group, has relay parent in view and disabled. + // peer B is in group, has relay parent in view. { - let other_group_validators = state.group_validators(local_validator.group_index, true); - connect_peer( &mut overseer, - peer_a.clone(), - Some(vec![state.discovery_id(other_group_validators[0])].into_iter().collect()), + peer_disabled.clone(), + Some(vec![state.discovery_id(index_disabled)].into_iter().collect()), ) .await; - connect_peer( &mut overseer, peer_b.clone(), - Some(vec![state.discovery_id(other_group_validators[1])].into_iter().collect()), + Some(vec![state.discovery_id(index_b)].into_iter().collect()), ) .await; - - connect_peer(&mut overseer, peer_c.clone(), None).await; - - send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; + send_peer_view_change(&mut overseer, peer_disabled.clone(), view![relay_parent]).await; + send_peer_view_change(&mut overseer, peer_b.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - let mask = StatementFilter::blank(state.config.group_size); + let seconded_disabled = state + .sign_statement( + index_disabled, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); - // Should drop requests for unknown candidates. + let seconded_b = state + .sign_statement( + index_b, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); { - let (pending_response, rx) = oneshot::channel(); - state - .req_sender - .send(RawIncomingRequest { - // Request from peer that received manifest. - peer: peer_c, - payload: request_v2::AttestedCandidateRequest { - candidate_hash: candidate.hash(), - mask: mask.clone(), - } - .encode(), - pending_response, - }) - .await - .unwrap(); + send_peer_message( + &mut overseer, + peer_disabled.clone(), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + seconded_disabled.clone(), + ), + ) + .await; - assert_matches!(rx.await, Err(oneshot::Canceled)); + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_disabled && r == COST_DISABLED_VALIDATOR.into() => { } + ); } - // Confirm candidate. { - let full_signed = state - .sign_statement( - local_validator.validator_index, - CompactStatement::Seconded(candidate_hash), - &SigningContext { session_index: 1, parent_hash: relay_parent }, - ) - .convert_to_superpayload(StatementWithPVD::Seconded(candidate.clone(), pvd.clone())) - .unwrap(); - - overseer - .send(FromOrchestra::Communication { - msg: StatementDistributionMessage::Share(relay_parent, full_signed), - }) - .await; + send_peer_message( + &mut overseer, + peer_b.clone(), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + seconded_b.clone(), + ), + ) + .await; assert_matches!( overseer.recv().await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( - peers, - Versioned::V2(protocol_v2::ValidationProtocol::StatementDistribution( - protocol_v2::StatementDistributionMessage::Statement( - r, - s, - ) - )) - )) => { - assert_eq!(peers, vec![peer_a.clone()]); - assert_eq!(r, relay_parent); - assert_eq!(s.unchecked_payload(), &CompactStatement::Seconded(candidate_hash)); - assert_eq!(s.unchecked_validator_index(), local_validator.validator_index); - } + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { } ); - - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; } - // Should drop requests from unknown peers. + // Send a request to peer and mock its response with a statement from disabled validator. { - let (pending_response, rx) = oneshot::channel(); - state - .req_sender - .send(RawIncomingRequest { - // Request from peer that received manifest. - peer: peer_d, - payload: request_v2::AttestedCandidateRequest { - candidate_hash: candidate.hash(), - mask: mask.clone(), - } - .encode(), - pending_response, - }) - .await - .unwrap(); + let statements = vec![seconded_disabled]; + let mut mask = StatementFilter::blank(group_size); + let i = index_within_group.unwrap(); + mask.seconded_in_group.set(i, true); + mask.validated_in_group.set(i, true); - assert_matches!(rx.await, Err(oneshot::Canceled)); - } - - // Should drop requests with bitfields of the wrong size. - { - let mask = StatementFilter::blank(state.config.group_size + 1); - let response = state - .send_request( - peer_c, - request_v2::AttestedCandidateRequest { candidate_hash: candidate.hash(), mask }, - ) - .await - .await; + handle_sent_request( + &mut overseer, + peer_b, + candidate_hash, + mask, + candidate.clone(), + pvd.clone(), + statements, + ) + .await; assert_matches!( - response, - RawOutgoingResponse { - result, - reputation_changes, - sent_feedback - } => { - assert_matches!(result, Err(())); - assert_eq!(reputation_changes, vec![COST_INVALID_REQUEST_BITFIELD_SIZE.into()]); - assert_matches!(sent_feedback, None); - } + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == COST_UNREQUESTED_RESPONSE_STATEMENT.into() => { } ); - } - // Local node should reject requests if we did not send a manifest to that peer. - { - let response = state - .send_request( - peer_c, - request_v2::AttestedCandidateRequest { - candidate_hash: candidate.hash(), - mask: mask.clone(), - }, - ) - .await - .await; + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == BENEFIT_VALID_RESPONSE.into() => { } + ); - // Should get `COST_UNEXPECTED_REQUEST` response. assert_matches!( - response, - RawOutgoingResponse { - result, - reputation_changes, - sent_feedback - } => { - assert_matches!(result, Err(())); - assert_eq!(reputation_changes, vec![COST_UNEXPECTED_REQUEST.into()]); - assert_matches!(sent_feedback, None); + overseer.recv().await, + AllMessages:: NetworkBridgeTx( + NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V2( + protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::Statement(hash, statement), + ), + ), + ) + ) => { + assert_eq!(peers, vec![peer_disabled]); + assert_eq!(hash, relay_parent); + assert_eq!(statement, seconded_b); } ); + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer }); } +// We send a request to a peer and after receiving the response +// we learn about a validator being disabled. We should filter out +// the statement from the disabled validator when receiving it. #[test] -fn local_node_checks_that_peer_can_request_before_responding() { +fn when_validator_disabled_after_sending_the_request() { + let group_size = 3; let config = TestConfig { validator_count: 20, - group_size: 3, - local_validator: true, + group_size, + local_validator: LocalRole::Validator, async_backing_params: None, }; let relay_parent = Hash::repeat_byte(1); - let peer_a = PeerId::random(); + let another_relay_parent = Hash::repeat_byte(2); + let peer_disabled_later = PeerId::random(); let peer_b = PeerId::random(); - test_harness(config, |mut state, mut overseer| async move { + test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + let other_group_validators = state.group_validators(local_group_index, true); + let index_disabled = other_group_validators[0]; + let index_b = other_group_validators[1]; - let test_leaf = state.make_dummy_leaf(relay_parent); + let test_leaf = state.make_dummy_leaf_with_disabled_validators(relay_parent, vec![]); + let test_leaf_disabled = state + .make_dummy_leaf_with_disabled_validators(another_relay_parent, vec![index_disabled]); let (candidate, pvd) = make_candidate( relay_parent, @@ -1335,37 +1262,742 @@ fn local_node_checks_that_peer_can_request_before_responding() { ); let candidate_hash = candidate.hash(); - // Peers A and B are in group and have relay parent in view. - let other_group_validators = state.group_validators(local_validator.group_index, true); - - connect_peer( - &mut overseer, - peer_a.clone(), - Some(vec![state.discovery_id(other_group_validators[0])].into_iter().collect()), - ) - .await; + // peer A is in group, has relay parent in view and disabled later. + // peer B is in group, has relay parent in view. + { + connect_peer( + &mut overseer, + peer_disabled_later.clone(), + Some(vec![state.discovery_id(index_disabled)].into_iter().collect()), + ) + .await; + connect_peer( + &mut overseer, + peer_b.clone(), + Some(vec![state.discovery_id(index_b)].into_iter().collect()), + ) + .await; + send_peer_view_change(&mut overseer, peer_disabled_later.clone(), view![relay_parent]) + .await; + send_peer_view_change(&mut overseer, peer_b.clone(), view![relay_parent]).await; + } - connect_peer( - &mut overseer, - peer_b.clone(), - Some(vec![state.discovery_id(other_group_validators[1])].into_iter().collect()), - ) - .await; - let peer_b_index = other_group_validators[1]; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; - send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; - send_peer_view_change(&mut overseer, peer_b.clone(), view![relay_parent]).await; + let seconded_disabled = state + .sign_statement( + index_disabled, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + + let seconded_b = state + .sign_statement( + index_b, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + { + send_peer_message( + &mut overseer, + peer_b.clone(), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + seconded_b.clone(), + ), + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { } + ); + } + + // Send a request to peer and activate leaf when a validator is disabled; + // mock the response with a statement from disabled validator. + { + let statements = vec![seconded_disabled]; + let mask = StatementFilter::blank(group_size); + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests(mut requests, IfDisconnected::ImmediateError)) => { + assert_eq!(requests.len(), 1); + assert_matches!( + requests.pop().unwrap(), + Requests::AttestedCandidateV2(outgoing) => { + assert_eq!(outgoing.peer, Recipient::Peer(peer_b)); + assert_eq!(outgoing.payload.candidate_hash, candidate_hash); + assert_eq!(outgoing.payload.mask, mask); + + activate_leaf(&mut overseer, &test_leaf_disabled, &state, false, vec![]).await; + + let res = AttestedCandidateResponse { + candidate_receipt: candidate, + persisted_validation_data: pvd, + statements, + }; + outgoing.pending_response.send(Ok((res.encode(), ProtocolName::from("")))).unwrap(); + } + ); + } + ); + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == BENEFIT_VALID_RESPONSE.into() => { } + ); + + assert_matches!( + overseer.recv().await, + AllMessages:: NetworkBridgeTx( + NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V2( + protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::Statement(hash, statement), + ), + ), + ) + ) => { + assert_eq!(peers, vec![peer_disabled_later]); + assert_eq!(hash, relay_parent); + assert_eq!(statement, seconded_b); + } + ); + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + overseer + }); +} + +#[test] +fn no_response_for_grid_request_not_meeting_quorum() { + let validator_count = 6; + let group_size = 3; + let config = TestConfig { + validator_count, + group_size, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + let relay_parent = Hash::repeat_byte(1); + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + + test_harness(config, |mut state, mut overseer| async move { + let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + + let test_leaf = state.make_dummy_leaf_with_min_backing_votes(relay_parent, 2); + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + local_para, + test_leaf.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + let other_group_validators = state.group_validators(local_group_index, true); + let target_group_validators = + state.group_validators((local_group_index.0 + 1).into(), true); + let v_a = other_group_validators[0]; + let v_b = other_group_validators[1]; + let v_c = target_group_validators[0]; + + // peer A is in group, has relay parent in view. + // peer B is in group, has no relay parent in view. + // peer C is not in group, has relay parent in view. + { + connect_peer( + &mut overseer, + peer_a.clone(), + Some(vec![state.discovery_id(v_a)].into_iter().collect()), + ) + .await; + + connect_peer( + &mut overseer, + peer_b.clone(), + Some(vec![state.discovery_id(v_b)].into_iter().collect()), + ) + .await; + + connect_peer( + &mut overseer, + peer_c.clone(), + Some(vec![state.discovery_id(v_c)].into_iter().collect()), + ) + .await; + + send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; + send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; + } + + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; + + // Send gossip topology. + send_new_topology(&mut overseer, state.make_dummy_topology()).await; + + // Confirm the candidate locally so that we don't send out requests. + { + let statement = state + .sign_full_statement( + local_validator.validator_index, + Statement::Seconded(candidate.clone()), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + pvd.clone(), + ) + .clone(); + + overseer + .send(FromOrchestra::Communication { + msg: StatementDistributionMessage::Share(relay_parent, statement), + }) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] + ); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + // Send enough statements to make candidate backable, make sure announcements are sent. + + // Send statement from peer A. + { + let statement = state + .sign_statement( + v_a, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + + send_peer_message( + &mut overseer, + peer_a.clone(), + protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement), + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { } + ); + } + + // Send statement from peer B. + let statement_b = state + .sign_statement( + v_b, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); + { + send_peer_message( + &mut overseer, + peer_b.clone(), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + statement_b.clone(), + ), + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_b && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { } + ); + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] + ); + } + + // Send Backed notification. + { + overseer + .send(FromOrchestra::Communication { + msg: StatementDistributionMessage::Backed(candidate_hash), + }) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages:: NetworkBridgeTx( + NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V2( + protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest), + ), + ), + ) + ) => { + assert_eq!(peers, vec![peer_c]); + assert_eq!(manifest, BackedCandidateManifest { + relay_parent, + candidate_hash, + group_index: local_validator.group_index.unwrap(), + para_id: local_para, + parent_head_data_hash: pvd.parent_head.hash(), + statement_knowledge: StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }, + }); + } + ); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + let mask = StatementFilter { + seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 1], + validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], + }; + + let relay_2 = Hash::repeat_byte(2); + let disabled_validators = vec![v_a]; + let leaf_2 = state.make_dummy_leaf_with_disabled_validators(relay_2, disabled_validators); + activate_leaf(&mut overseer, &leaf_2, &state, false, vec![]).await; + + // Incoming request to local node. Local node should not send the response as v_a is + // disabled and hence the quorum is not reached. + { + let response = state + .send_request( + peer_c, + request_v2::AttestedCandidateRequest { candidate_hash: candidate.hash(), mask }, + ) + .await + .await; + + assert!( + response.is_none(), + "We should not send a response as the quorum is not reached yet" + ); + } + + overseer + }); +} + +#[test] +fn disabling_works_from_the_latest_state_not_relay_parent() { + let group_size = 3; + let config = TestConfig { + validator_count: 20, + group_size, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + let relay_1 = Hash::repeat_byte(1); + let relay_2 = Hash::repeat_byte(2); + let peer_disabled = PeerId::random(); + + test_harness(config, |state, mut overseer| async move { + let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + + let other_group_validators = state.group_validators(local_group_index, true); + let index_disabled = other_group_validators[0]; + + let leaf_1 = state.make_dummy_leaf(relay_1); + let disabled_validators = vec![index_disabled]; + let leaf_2 = state.make_dummy_leaf_with_disabled_validators(relay_2, disabled_validators); + + let (candidate_1, pvd_1) = make_candidate( + relay_1, + 1, + local_para, + leaf_1.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_1_hash = candidate_1.hash(); + + let (candidate_2, _) = make_candidate( + relay_1, + 1, + local_para, + leaf_1.para_data(local_para).head_data.clone(), + vec![4, 5, 6, 7].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_2_hash = candidate_2.hash(); + + { + connect_peer( + &mut overseer, + peer_disabled.clone(), + Some(vec![state.discovery_id(index_disabled)].into_iter().collect()), + ) + .await; + send_peer_view_change(&mut overseer, peer_disabled.clone(), view![relay_1]).await; + } + + activate_leaf(&mut overseer, &leaf_1, &state, true, vec![]).await; + + let seconded_1 = state + .sign_statement( + index_disabled, + CompactStatement::Seconded(candidate_1_hash), + &SigningContext { parent_hash: relay_1, session_index: 1 }, + ) + .as_unchecked() + .clone(); + + let seconded_2 = state + .sign_statement( + index_disabled, + CompactStatement::Seconded(candidate_2_hash), + &SigningContext { parent_hash: relay_1, session_index: 1 }, + ) + .as_unchecked() + .clone(); + { + send_peer_message( + &mut overseer, + peer_disabled.clone(), + protocol_v2::StatementDistributionMessage::Statement(relay_1, seconded_1.clone()), + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_disabled && r == BENEFIT_VALID_STATEMENT_FIRST.into() => { } + ); + } + + { + handle_sent_request( + &mut overseer, + peer_disabled, + candidate_1_hash, + StatementFilter::blank(group_size), + candidate_1.clone(), + pvd_1.clone(), + vec![seconded_1.clone()], + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_disabled && r == BENEFIT_VALID_STATEMENT.into() => { } + ); + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_disabled && r == BENEFIT_VALID_RESPONSE.into() => { } + ); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + activate_leaf(&mut overseer, &leaf_2, &state, false, vec![]).await; + + { + send_peer_message( + &mut overseer, + peer_disabled.clone(), + protocol_v2::StatementDistributionMessage::Statement(relay_1, seconded_2.clone()), + ) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) + if p == peer_disabled && r == COST_DISABLED_VALIDATOR.into() => { } + ); + } + + overseer + }); +} + +#[test] +fn local_node_sanity_checks_incoming_requests() { + let config = TestConfig { + validator_count: 20, + group_size: 3, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + let relay_parent = Hash::repeat_byte(1); + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + let peer_c = PeerId::random(); + let peer_d = PeerId::random(); + + test_harness(config, |mut state, mut overseer| async move { + let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + + let test_leaf = state.make_dummy_leaf(relay_parent); + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + local_para, + test_leaf.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); - // Finish setup - activate_leaf(&mut overseer, &test_leaf, &state, true).await; + // peer A is in group, has relay parent in view. + // peer B is in group, has no relay parent in view. + // peer C is not in group, has relay parent in view. + { + let other_group_validators = state.group_validators(local_group_index, true); + + connect_peer( + &mut overseer, + peer_a.clone(), + Some(vec![state.discovery_id(other_group_validators[0])].into_iter().collect()), + ) + .await; + + connect_peer( + &mut overseer, + peer_b.clone(), + Some(vec![state.discovery_id(other_group_validators[1])].into_iter().collect()), + ) + .await; + + connect_peer(&mut overseer, peer_c.clone(), None).await; + + send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; + send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; + } + + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; + + let mask = StatementFilter::blank(state.config.group_size); + + // Should drop requests for unknown candidates. + { + let (pending_response, rx) = oneshot::channel(); + state + .req_sender + .send(RawIncomingRequest { + // Request from peer that received manifest. + peer: peer_c, + payload: request_v2::AttestedCandidateRequest { + candidate_hash: candidate.hash(), + mask: mask.clone(), + } + .encode(), + pending_response, + }) + .await + .unwrap(); + + assert_matches!(rx.await, Err(oneshot::Canceled)); + } + + // Confirm candidate. + { + let full_signed = state + .sign_statement( + local_validator.validator_index, + CompactStatement::Seconded(candidate_hash), + &SigningContext { session_index: 1, parent_hash: relay_parent }, + ) + .convert_to_superpayload(StatementWithPVD::Seconded(candidate.clone(), pvd.clone())) + .unwrap(); + + overseer + .send(FromOrchestra::Communication { + msg: StatementDistributionMessage::Share(relay_parent, full_signed), + }) + .await; + + assert_matches!( + overseer.recv().await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V2(protocol_v2::ValidationProtocol::StatementDistribution( + protocol_v2::StatementDistributionMessage::Statement( + r, + s, + ) + )) + )) => { + assert_eq!(peers, vec![peer_a.clone()]); + assert_eq!(r, relay_parent); + assert_eq!(s.unchecked_payload(), &CompactStatement::Seconded(candidate_hash)); + assert_eq!(s.unchecked_validator_index(), local_validator.validator_index); + } + ); + + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; + } + + // Should drop requests from unknown peers. + { + let (pending_response, rx) = oneshot::channel(); + state + .req_sender + .send(RawIncomingRequest { + // Request from peer that received manifest. + peer: peer_d, + payload: request_v2::AttestedCandidateRequest { + candidate_hash: candidate.hash(), + mask: mask.clone(), + } + .encode(), + pending_response, + }) + .await + .unwrap(); + + assert_matches!(rx.await, Err(oneshot::Canceled)); + } + + // Should drop requests with bitfields of the wrong size. + { + let mask = StatementFilter::blank(state.config.group_size + 1); + let response = state + .send_request( + peer_c, + request_v2::AttestedCandidateRequest { candidate_hash: candidate.hash(), mask }, + ) + .await + .await + .unwrap(); + + assert_matches!( + response, + RawOutgoingResponse { + result, + reputation_changes, + sent_feedback + } => { + assert_matches!(result, Err(())); + assert_eq!(reputation_changes, vec![COST_INVALID_REQUEST_BITFIELD_SIZE.into()]); + assert_matches!(sent_feedback, None); + } + ); + } + + // Local node should reject requests if we did not send a manifest to that peer. + { + let response = state + .send_request( + peer_c, + request_v2::AttestedCandidateRequest { + candidate_hash: candidate.hash(), + mask: mask.clone(), + }, + ) + .await + .await + .unwrap(); + + // Should get `COST_UNEXPECTED_REQUEST` response. + assert_matches!( + response, + RawOutgoingResponse { + result, + reputation_changes, + sent_feedback + } => { + assert_matches!(result, Err(())); + assert_eq!(reputation_changes, vec![COST_UNEXPECTED_REQUEST.into()]); + assert_matches!(sent_feedback, None); + } + ); + } + + overseer + }); +} + +#[test] +fn local_node_checks_that_peer_can_request_before_responding() { + let config = TestConfig { + validator_count: 20, + group_size: 3, + local_validator: LocalRole::Validator, + async_backing_params: None, + }; + + let relay_parent = Hash::repeat_byte(1); + let peer_a = PeerId::random(); + let peer_b = PeerId::random(); + + test_harness(config, |mut state, mut overseer| async move { + let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); + + let test_leaf = state.make_dummy_leaf(relay_parent); + + let (candidate, pvd) = make_candidate( + relay_parent, + 1, + local_para, + test_leaf.para_data(local_para).head_data.clone(), + vec![4, 5, 6].into(), + Hash::repeat_byte(42).into(), + ); + let candidate_hash = candidate.hash(); + + // Peers A and B are in group and have relay parent in view. + let other_group_validators = state.group_validators(local_group_index, true); + + connect_peer( + &mut overseer, + peer_a.clone(), + Some(vec![state.discovery_id(other_group_validators[0])].into_iter().collect()), + ) + .await; - answer_expected_hypothetical_depth_request( + connect_peer( &mut overseer, - vec![], - Some(relay_parent), - false, + peer_b.clone(), + Some(vec![state.discovery_id(other_group_validators[1])].into_iter().collect()), ) .await; + let peer_b_index = other_group_validators[1]; + + send_peer_view_change(&mut overseer, peer_a.clone(), view![relay_parent]).await; + send_peer_view_change(&mut overseer, peer_b.clone(), view![relay_parent]).await; + + // Finish setup + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; let mask = StatementFilter::blank(state.config.group_size); @@ -1404,7 +2036,7 @@ fn local_node_checks_that_peer_can_request_before_responding() { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; // Local node should respond to requests from peers in the same group // which appear to not have already seen the candidate @@ -1419,7 +2051,8 @@ fn local_node_checks_that_peer_can_request_before_responding() { }, ) .await - .await; + .await + .unwrap(); let expected_statements = vec![signed.into_unchecked()]; assert_matches!(response, full_response => { @@ -1468,7 +2101,8 @@ fn local_node_checks_that_peer_can_request_before_responding() { }, ) .await - .await; + .await + .unwrap(); // Peer already knows about this candidate. Should reject. assert_matches!( @@ -1515,7 +2149,7 @@ fn local_node_respects_statement_mask() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1527,9 +2161,10 @@ fn local_node_respects_statement_mask() { test_harness(config, |mut state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); - let local_para = ParaId::from(local_validator.group_index.0); + let local_group_index = local_validator.group_index.unwrap(); + let local_para = ParaId::from(local_group_index.0); - let test_leaf = state.make_dummy_leaf(relay_parent); + let test_leaf = state.make_dummy_leaf_with_min_backing_votes(relay_parent, 2); let (candidate, pvd) = make_candidate( relay_parent, @@ -1541,9 +2176,9 @@ fn local_node_respects_statement_mask() { ); let candidate_hash = candidate.hash(); - let other_group_validators = state.group_validators(local_validator.group_index, true); + let other_group_validators = state.group_validators(local_group_index, true); let target_group_validators = - state.group_validators((local_validator.group_index.0 + 1).into(), true); + state.group_validators((local_group_index.0 + 1).into(), true); let v_a = other_group_validators[0]; let v_b = other_group_validators[1]; let v_c = target_group_validators[0]; @@ -1586,15 +2221,7 @@ fn local_node_respects_statement_mask() { send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -1621,26 +2248,28 @@ fn local_node_respects_statement_mask() { AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a] ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Send enough statements to make candidate backable, make sure announcements are sent. // Send statement from peer A. + let statement_a = state + .sign_statement( + v_a, + CompactStatement::Seconded(candidate_hash), + &SigningContext { parent_hash: relay_parent, session_index: 1 }, + ) + .as_unchecked() + .clone(); { - let statement = state - .sign_statement( - v_a, - CompactStatement::Seconded(candidate_hash), - &SigningContext { parent_hash: relay_parent, session_index: 1 }, - ) - .as_unchecked() - .clone(); - send_peer_message( &mut overseer, peer_a.clone(), - protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement), + protocol_v2::StatementDistributionMessage::Statement( + relay_parent, + statement_a.clone(), + ), ) .await; @@ -1707,7 +2336,7 @@ fn local_node_respects_statement_mask() { assert_eq!(manifest, BackedCandidateManifest { relay_parent, candidate_hash, - group_index: local_validator.group_index, + group_index: local_group_index, para_id: local_para, parent_head_data_hash: pvd.parent_head.hash(), statement_knowledge: StatementFilter { @@ -1718,12 +2347,12 @@ fn local_node_respects_statement_mask() { } ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // `1` indicates statements NOT to request. let mask = StatementFilter { - seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 0, 1], + seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 1], validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0], }; @@ -1735,9 +2364,10 @@ fn local_node_respects_statement_mask() { request_v2::AttestedCandidateRequest { candidate_hash: candidate.hash(), mask }, ) .await - .await; + .await + .unwrap(); - let expected_statements = vec![statement_b]; + let expected_statements = vec![statement_a, statement_b]; assert_matches!(response, full_response => { // Response is the same for v2. let request_v2::AttestedCandidateResponse { candidate_receipt, persisted_validation_data, statements } = @@ -1761,7 +2391,7 @@ fn should_delay_before_retrying_dropped_requests() { let config = TestConfig { validator_count, group_size, - local_validator: true, + local_validator: LocalRole::Validator, async_backing_params: None, }; @@ -1772,9 +2402,9 @@ fn should_delay_before_retrying_dropped_requests() { test_harness(config, |state, mut overseer| async move { let local_validator = state.local.clone().unwrap(); + let local_group_index = local_validator.group_index.unwrap(); - let other_group = - next_group_index(local_validator.group_index, validator_count, group_size); + let other_group = next_group_index(local_group_index, validator_count, group_size); let other_para = ParaId::from(other_group.0); let test_leaf = state.make_dummy_leaf(relay_parent); @@ -1831,15 +2461,7 @@ fn should_delay_before_retrying_dropped_requests() { send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await; } - activate_leaf(&mut overseer, &test_leaf, &state, true).await; - - answer_expected_hypothetical_depth_request( - &mut overseer, - vec![], - Some(relay_parent), - false, - ) - .await; + activate_leaf(&mut overseer, &test_leaf, &state, true, vec![]).await; // Send gossip topology. send_new_topology(&mut overseer, state.make_dummy_topology()).await; @@ -1978,7 +2600,7 @@ fn should_delay_before_retrying_dropped_requests() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } // Sleep for the given amount of time. This should reset the delay for the first candidate. @@ -2045,7 +2667,7 @@ fn should_delay_before_retrying_dropped_requests() { if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() ); - answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await; + answer_expected_hypothetical_depth_request(&mut overseer, vec![]).await; } overseer diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml index 376ebe0375b8e21d02d849784af11f0db6400b0c..f91ec80d944064f5cba28275766c0fbcc9231133 100644 --- a/polkadot/node/overseer/Cargo.toml +++ b/polkadot/node/overseer/Cargo.toml @@ -1,29 +1,33 @@ [package] name = "polkadot-overseer" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" } futures = "0.3.21" futures-timer = "3.0.2" -parking_lot = "0.12.0" +parking_lot = "0.12.1" polkadot-node-network-protocol = { path = "../network/protocol" } polkadot-node-primitives = { path = "../primitives" } polkadot-node-subsystem-types = { path = "../subsystem-types" } polkadot-node-metrics = { path = "../metrics" } polkadot-primitives = { path = "../../primitives" } -orchestra = { version = "0.3.3", default-features = false, features=["futures_channel"] } +orchestra = { version = "0.3.5", 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] -metered = { package = "prioritized-metered-channel", version = "0.5.1", default-features = false, features=["futures_channel"] } +metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false, features = ["futures_channel"] } sp-core = { path = "../../../substrate/primitives/core" } futures = { version = "0.3.21", features = ["thread-pool"] } femme = "2.2.1" @@ -35,8 +39,7 @@ node-test-helpers = { package = "polkadot-node-subsystem-test-helpers", path = " 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" ] +default = ["futures_channel"] +expand = ["orchestra/expand"] +futures_channel = ["metered/futures_channel", "orchestra/futures_channel"] +jemalloc-allocator = ["dep:tikv-jemalloc-ctl"] diff --git a/polkadot/node/overseer/examples/minimal-example.rs b/polkadot/node/overseer/examples/minimal-example.rs index cffdfd9f8aa14851275ef3f41a4ddcb47ccb516f..857cdba673db267d2824d8a995257316fc67225f 100644 --- a/polkadot/node/overseer/examples/minimal-example.rs +++ b/polkadot/node/overseer/examples/minimal-example.rs @@ -32,7 +32,7 @@ use polkadot_overseer::{ gen::{FromOrchestra, SpawnedSubsystem}, HeadSupportsParachains, SubsystemError, }; -use polkadot_primitives::{CandidateReceipt, Hash, PvfExecTimeoutKind}; +use polkadot_primitives::{CandidateReceipt, Hash, PvfExecKind}; struct AlwaysSupportsParachains; @@ -73,13 +73,13 @@ impl Subsystem1 { commitments_hash: Hash::zero(), }; - let msg = CandidateValidationMessage::ValidateFromChainState( + let msg = CandidateValidationMessage::ValidateFromChainState { candidate_receipt, - PoV { block_data: BlockData(Vec::new()) }.into(), - Default::default(), - PvfExecTimeoutKind::Backing, - tx, - ); + pov: PoV { block_data: BlockData(Vec::new()) }.into(), + executor_params: Default::default(), + exec_kind: PvfExecKind::Backing, + response_sender: tx, + }; ctx.send_message(msg).await; } () diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 6802426d3c70d66816ff313fc91a36e6c87f5352..e16a3fd27ab3d920da5c83b3c4ee02ab4ccba2cb 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -87,8 +87,8 @@ use polkadot_node_subsystem_types::messages::{ pub use polkadot_node_subsystem_types::{ errors::{SubsystemError, SubsystemResult}, - jaeger, ActivatedLeaf, ActiveLeavesUpdate, OverseerSignal, RuntimeApiSubsystemClient, - UnpinHandle, + jaeger, ActivatedLeaf, ActiveLeavesUpdate, ChainApiBackend, OverseerSignal, + RuntimeApiSubsystemClient, UnpinHandle, }; pub mod metrics; @@ -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. @@ -463,7 +465,7 @@ pub async fn forward_events>(client: Arc

, mut hand message_capacity=2048, )] pub struct Overseer { - #[subsystem(CandidateValidationMessage, sends: [ + #[subsystem(blocking, CandidateValidationMessage, sends: [ RuntimeApiMessage, ])] candidate_validation: CandidateValidation, @@ -498,7 +500,6 @@ pub struct Overseer { #[subsystem(AvailabilityDistributionMessage, sends: [ AvailabilityStoreMessage, - AvailabilityRecoveryMessage, ChainApiMessage, RuntimeApiMessage, NetworkBridgeTxMessage, diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index c17613fb7ea5dc3d12f04a6d3a9162b7de4dbbe6..0494274367d953146ad06e93c3ee00cfc9c3b983 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -30,7 +30,7 @@ use polkadot_node_subsystem_types::messages::{ }; use polkadot_primitives::{ CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind, - PvfExecTimeoutKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, + PvfExecKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, }; use crate::{ @@ -102,13 +102,13 @@ where }; let (tx, _) = oneshot::channel(); - ctx.send_message(CandidateValidationMessage::ValidateFromChainState( + ctx.send_message(CandidateValidationMessage::ValidateFromChainState { candidate_receipt, - PoV { block_data: BlockData(Vec::new()) }.into(), - Default::default(), - PvfExecTimeoutKind::Backing, - tx, - )) + pov: PoV { block_data: BlockData(Vec::new()) }.into(), + executor_params: Default::default(), + exec_kind: PvfExecKind::Backing, + response_sender: tx, + }) .await; c += 1; continue @@ -793,20 +793,20 @@ where } fn test_candidate_validation_msg() -> CandidateValidationMessage { - let (sender, _) = oneshot::channel(); + let (response_sender, _) = oneshot::channel(); let pov = Arc::new(PoV { block_data: BlockData(Vec::new()) }); let candidate_receipt = CandidateReceipt { descriptor: dummy_candidate_descriptor(dummy_hash()), commitments_hash: Hash::zero(), }; - CandidateValidationMessage::ValidateFromChainState( + CandidateValidationMessage::ValidateFromChainState { candidate_receipt, pov, - Default::default(), - PvfExecTimeoutKind::Backing, - sender, - ) + executor_params: Default::default(), + exec_kind: PvfExecKind::Backing, + response_sender, + } } fn test_candidate_backing_msg() -> CandidateBackingMessage { diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 55dfa67387094198c2126b79b5a31abc23dc2b4e..b4541bcc346c8ce4282947c38e0d9a2591631090 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-node-primitives" description = "Primitives types for the Node-side" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bounded-vec = "0.7" futures = "0.3.21" @@ -18,9 +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" -thiserror = "1.0.48" -serde = { version = "1.0.188", features = ["derive"] } +schnorrkel = "0.11.4" +thiserror = { workspace = true } +bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } +serde = { features = ["derive"], workspace = true, default-features = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/primitives/src/approval.rs b/polkadot/node/primitives/src/approval.rs index 01f45a20787461c2cf6a0f39f88c6d082ac87468..f2a79e025affe3de108f0ce8abd985e53c862a5c 100644 --- a/polkadot/node/primitives/src/approval.rs +++ b/polkadot/node/primitives/src/approval.rs @@ -16,190 +16,570 @@ //! Types relevant for approval. -pub use sp_consensus_babe::{Randomness, Slot, VrfOutput, VrfProof, VrfSignature, VrfTranscript}; - -use parity_scale_codec::{Decode, Encode}; -use polkadot_primitives::{ - BlockNumber, CandidateHash, CandidateIndex, CoreIndex, Hash, Header, SessionIndex, - ValidatorIndex, ValidatorSignature, -}; -use sp_application_crypto::ByteArray; -use sp_consensus_babe as babe_primitives; - -/// Validators assigning to check a particular candidate are split up into tranches. -/// Earlier tranches of validators check first, with later tranches serving as backup. -pub type DelayTranche = u32; - -/// A static context used to compute the Relay VRF story based on the -/// VRF output included in the header-chain. -pub const RELAY_VRF_STORY_CONTEXT: &[u8] = b"A&V RC-VRF"; - -/// A static context used for all relay-vrf-modulo VRFs. -pub const RELAY_VRF_MODULO_CONTEXT: &[u8] = b"A&V MOD"; - -/// A static context used for all relay-vrf-modulo VRFs. -pub const RELAY_VRF_DELAY_CONTEXT: &[u8] = b"A&V DELAY"; - -/// A static context used for transcripts indicating assigned availability core. -pub const ASSIGNED_CORE_CONTEXT: &[u8] = b"A&V ASSIGNED"; - -/// A static context associated with producing randomness for a core. -pub const CORE_RANDOMNESS_CONTEXT: &[u8] = b"A&V CORE"; - -/// A static context associated with producing randomness for a tranche. -pub const TRANCHE_RANDOMNESS_CONTEXT: &[u8] = b"A&V TRANCHE"; - -/// 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. -#[derive(Debug, Clone, Encode, Decode, PartialEq)] -pub struct RelayVRFStory(pub [u8; 32]); - -/// Different kinds of input data or criteria that can prove a validator's assignment -/// to check a particular parachain. -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] -pub enum AssignmentCertKind { - /// An assignment story based on the VRF that authorized the relay-chain block where the - /// candidate was included combined with a sample number. +/// A list of primitives introduced in v1. +pub mod v1 { + use sp_consensus_babe as babe_primitives; + pub use sp_consensus_babe::{ + Randomness, Slot, VrfPreOutput, VrfProof, VrfSignature, VrfTranscript, + }; + + use parity_scale_codec::{Decode, Encode}; + use polkadot_primitives::{ + BlockNumber, CandidateHash, CandidateIndex, CoreIndex, Hash, Header, SessionIndex, + ValidatorIndex, ValidatorSignature, + }; + use sp_application_crypto::ByteArray; + + /// Validators assigning to check a particular candidate are split up into tranches. + /// Earlier tranches of validators check first, with later tranches serving as backup. + pub type DelayTranche = u32; + + /// A static context used to compute the Relay VRF story based on the + /// VRF output included in the header-chain. + pub const RELAY_VRF_STORY_CONTEXT: &[u8] = b"A&V RC-VRF"; + + /// A static context used for all relay-vrf-modulo VRFs. + pub const RELAY_VRF_MODULO_CONTEXT: &[u8] = b"A&V MOD"; + + /// A static context used for all relay-vrf-modulo VRFs. + pub const RELAY_VRF_DELAY_CONTEXT: &[u8] = b"A&V DELAY"; + + /// A static context used for transcripts indicating assigned availability core. + pub const ASSIGNED_CORE_CONTEXT: &[u8] = b"A&V ASSIGNED"; + + /// A static context associated with producing randomness for a core. + pub const CORE_RANDOMNESS_CONTEXT: &[u8] = b"A&V CORE"; + + /// A static context associated with producing randomness for a tranche. + pub const TRANCHE_RANDOMNESS_CONTEXT: &[u8] = b"A&V TRANCHE"; + + /// 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. + #[derive(Debug, Clone, Encode, Decode, PartialEq)] + pub struct RelayVRFStory(pub [u8; 32]); + + /// Different kinds of input data or criteria that can prove a validator's assignment + /// to check a particular parachain. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub enum AssignmentCertKind { + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with a sample number. + /// + /// The context used to produce bytes is [`RELAY_VRF_MODULO_CONTEXT`] + RelayVRFModulo { + /// The sample number used in this cert. + sample: u32, + }, + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with the index of a particular core. + /// + /// The context is [`RELAY_VRF_DELAY_CONTEXT`] + RelayVRFDelay { + /// The core index chosen in this cert. + core_index: CoreIndex, + }, + } + + /// A certification of assignment. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub struct AssignmentCert { + /// The criterion which is claimed to be met by this cert. + pub kind: AssignmentCertKind, + /// The VRF signature showing the criterion is met. + pub vrf: VrfSignature, + } + + /// An assignment criterion which refers to the candidate under which the assignment is + /// relevant by block hash. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub struct IndirectAssignmentCert { + /// A block hash where the candidate appears. + pub block_hash: Hash, + /// The validator index. + pub validator: ValidatorIndex, + /// The cert itself. + pub cert: AssignmentCert, + } + + /// A signed approval vote which references the candidate indirectly via the block. /// - /// The context used to produce bytes is [`RELAY_VRF_MODULO_CONTEXT`] - RelayVRFModulo { - /// The sample number used in this cert. - sample: u32, - }, - /// An assignment story based on the VRF that authorized the relay-chain block where the - /// candidate was included combined with the index of a particular core. + /// 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 IndirectSignedApprovalVote { + /// 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_index: CandidateIndex, + /// The validator index. + pub validator: ValidatorIndex, + /// The signature by the validator. + pub signature: ValidatorSignature, + } + + /// Metadata about a block which is now live in the approval protocol. + #[derive(Debug)] + pub struct BlockApprovalMeta { + /// The hash of the block. + pub hash: Hash, + /// The number of the block. + pub number: BlockNumber, + /// The hash of the parent block. + pub parent_hash: Hash, + /// The candidates included by the block. + /// Note that these are not the same as the candidates that appear within the block body. + pub candidates: Vec, + /// The consensus slot of the block. + pub slot: Slot, + /// The session of the block. + pub session: SessionIndex, + } + + /// Errors that can occur during the approvals protocol. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum ApprovalError { + #[error("Schnorrkel signature error")] + SchnorrkelSignature(schnorrkel::errors::SignatureError), + #[error("Authority index {0} out of bounds")] + AuthorityOutOfBounds(usize), + } + + /// 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 UnsafeVRFPreOutput { + /// Get the slot. + pub fn slot(&self) -> Slot { + self.slot + } + + /// Compute the randomness associated with this VRF output. + pub fn compute_randomness( + self, + authorities: &[(babe_primitives::AuthorityId, babe_primitives::BabeAuthorityWeight)], + randomness: &babe_primitives::Randomness, + epoch_index: u64, + ) -> Result { + let author = match authorities.get(self.authority_index as usize) { + None => return Err(ApprovalError::AuthorityOutOfBounds(self.authority_index as _)), + Some(x) => &x.0, + }; + + let pubkey = schnorrkel::PublicKey::from_bytes(author.as_slice()) + .map_err(ApprovalError::SchnorrkelSignature)?; + + let transcript = + sp_consensus_babe::make_vrf_transcript(randomness, self.slot, epoch_index); + + let inout = self + .vrf_pre_output + .0 + .attach_input_hash(&pubkey, transcript.0) + .map_err(ApprovalError::SchnorrkelSignature)?; + Ok(RelayVRFStory(inout.make_bytes(super::v1::RELAY_VRF_STORY_CONTEXT))) + } + } + + /// Extract the slot number and relay VRF from a header. /// - /// The context is [`RELAY_VRF_DELAY_CONTEXT`] - RelayVRFDelay { - /// The core index chosen in this cert. - core_index: CoreIndex, - }, -} + /// 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 { + use babe_primitives::digests::CompatibleDigestItem; -/// A certification of assignment. -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] -pub struct AssignmentCert { - /// The criterion which is claimed to be met by this cert. - pub kind: AssignmentCertKind, - /// The VRF signature showing the criterion is met. - pub vrf: VrfSignature, -} + for digest in &header.digest.logs { + if let Some(pre) = digest.as_babe_pre_digest() { + let slot = pre.slot(); + let authority_index = pre.authority_index(); -/// An assignment criterion which refers to the candidate under which the assignment is -/// relevant by block hash. -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] -pub struct IndirectAssignmentCert { - /// A block hash where the candidate appears. - pub block_hash: Hash, - /// The validator index. - pub validator: ValidatorIndex, - /// The cert itself. - pub cert: AssignmentCert, -} + return pre.vrf_signature().map(|sig| UnsafeVRFPreOutput { + vrf_pre_output: sig.pre_output.clone(), + slot, + authority_index, + }) + } + } -/// 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 IndirectSignedApprovalVote { - /// 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_index: CandidateIndex, - /// The validator index. - pub validator: ValidatorIndex, - /// The signature by the validator. - pub signature: ValidatorSignature, + None + } } -/// Metadata about a block which is now live in the approval protocol. -#[derive(Debug)] -pub struct BlockApprovalMeta { - /// The hash of the block. - pub hash: Hash, - /// The number of the block. - pub number: BlockNumber, - /// The hash of the parent block. - pub parent_hash: Hash, - /// The candidates included by the block. - /// Note that these are not the same as the candidates that appear within the block body. - pub candidates: Vec, - /// The consensus slot of the block. - pub slot: Slot, - /// The session of the block. - pub session: SessionIndex, -} +/// A list of primitives introduced by v2. +pub mod v2 { + use parity_scale_codec::{Decode, Encode}; + pub use sp_consensus_babe::{ + Randomness, Slot, VrfPreOutput, VrfProof, VrfSignature, VrfTranscript, + }; + use std::ops::BitOr; -/// Errors that can occur during the approvals protocol. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum ApprovalError { - #[error("Schnorrkel signature error")] - SchnorrkelSignature(schnorrkel::errors::SignatureError), - #[error("Authority index {0} out of bounds")] - AuthorityOutOfBounds(usize), -} + use bitvec::{prelude::Lsb0, vec::BitVec}; + use polkadot_primitives::{ + CandidateIndex, CoreIndex, Hash, ValidatorIndex, ValidatorSignature, + }; -/// An unsafe VRF output. Provide BABE Epoch info to create a `RelayVRFStory`. -pub struct UnsafeVRFOutput { - vrf_output: VrfOutput, - slot: Slot, - authority_index: u32, -} + /// A static context associated with producing randomness for a core. + pub const CORE_RANDOMNESS_CONTEXT: &[u8] = b"A&V CORE v2"; + /// A static context associated with producing randomness for v2 multi-core assignments. + pub const ASSIGNED_CORE_CONTEXT: &[u8] = b"A&V ASSIGNED v2"; + /// A static context used for all relay-vrf-modulo VRFs for v2 multi-core assignments. + pub const RELAY_VRF_MODULO_CONTEXT: &[u8] = b"A&V MOD v2"; + /// A read-only bitvec wrapper + #[derive(Clone, Debug, Encode, Decode, Hash, PartialEq, Eq)] + pub struct Bitfield(BitVec, std::marker::PhantomData); -impl UnsafeVRFOutput { - /// Get the slot. - pub fn slot(&self) -> Slot { - self.slot + /// A `read-only`, `non-zero` bitfield. + /// Each 1 bit identifies a candidate by the bitfield bit index. + pub type CandidateBitfield = Bitfield; + /// A bitfield of core assignments. + pub type CoreBitfield = Bitfield; + + /// Errors that can occur when creating and manipulating bitfields. + #[derive(Debug)] + pub enum BitfieldError { + /// All bits are zero. + NullAssignment, } - /// Compute the randomness associated with this VRF output. - pub fn compute_randomness( - self, - authorities: &[(babe_primitives::AuthorityId, babe_primitives::BabeAuthorityWeight)], - randomness: &babe_primitives::Randomness, - epoch_index: u64, - ) -> Result { - let author = match authorities.get(self.authority_index as usize) { - None => return Err(ApprovalError::AuthorityOutOfBounds(self.authority_index as _)), - Some(x) => &x.0, - }; + /// A bit index in `Bitfield`. + #[cfg_attr(test, derive(PartialEq, Clone))] + pub struct BitIndex(pub usize); + + /// Helper trait to convert primitives to `BitIndex`. + pub trait AsBitIndex { + /// Returns the index of the corresponding bit in `Bitfield`. + fn as_bit_index(&self) -> BitIndex; + } + + impl Bitfield { + /// Returns the bit value at specified `index`. If `index` is greater than bitfield size, + /// returns `false`. + pub fn bit_at(&self, index: BitIndex) -> bool { + if self.0.len() <= index.0 { + false + } else { + self.0[index.0] + } + } + + /// Returns number of bits. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns the number of 1 bits. + pub fn count_ones(&self) -> usize { + self.0.count_ones() + } + + /// Returns the index of the first 1 bit. + pub fn first_one(&self) -> Option { + self.0.first_one() + } - let pubkey = schnorrkel::PublicKey::from_bytes(author.as_slice()) - .map_err(ApprovalError::SchnorrkelSignature)?; + /// Returns an iterator over inner bits. + pub fn iter_ones(&self) -> bitvec::slice::IterOnes { + self.0.iter_ones() + } - let transcript = sp_consensus_babe::make_vrf_transcript(randomness, self.slot, epoch_index); + /// For testing purpose, we want a inner mutable ref. + #[cfg(test)] + pub fn inner_mut(&mut self) -> &mut BitVec { + &mut self.0 + } - let inout = self - .vrf_output - .0 - .attach_input_hash(&pubkey, transcript.0) - .map_err(ApprovalError::SchnorrkelSignature)?; - Ok(RelayVRFStory(inout.make_bytes(RELAY_VRF_STORY_CONTEXT))) + /// Returns the inner bitfield and consumes `self`. + pub fn into_inner(self) -> BitVec { + self.0 + } } -} -/// Extract the slot number and relay VRF from a header. -/// -/// 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 { - use babe_primitives::digests::CompatibleDigestItem; - - for digest in &header.digest.logs { - if let Some(pre) = digest.as_babe_pre_digest() { - let slot = pre.slot(); - let authority_index = pre.authority_index(); - - return pre.vrf_signature().map(|sig| UnsafeVRFOutput { - vrf_output: sig.output.clone(), - slot, - authority_index, + impl AsBitIndex for CandidateIndex { + fn as_bit_index(&self) -> BitIndex { + BitIndex(*self as usize) + } + } + + impl AsBitIndex for CoreIndex { + fn as_bit_index(&self) -> BitIndex { + BitIndex(self.0 as usize) + } + } + + impl AsBitIndex for usize { + fn as_bit_index(&self) -> BitIndex { + BitIndex(*self) + } + } + + impl From for Bitfield + where + T: AsBitIndex, + { + fn from(value: T) -> Self { + Self( + { + let mut bv = bitvec::bitvec![u8, Lsb0; 0; value.as_bit_index().0 + 1]; + bv.set(value.as_bit_index().0, true); + bv + }, + Default::default(), + ) + } + } + + impl TryFrom> for Bitfield + where + T: Into>, + { + type Error = BitfieldError; + + fn try_from(mut value: Vec) -> Result { + if value.is_empty() { + return Err(BitfieldError::NullAssignment) + } + + let initial_bitfield = + value.pop().expect("Just checked above it's not empty; qed").into(); + + Ok(Self( + value.into_iter().fold(initial_bitfield.0, |initial_bitfield, element| { + let mut bitfield: Bitfield = element.into(); + bitfield + .0 + .resize(std::cmp::max(initial_bitfield.len(), bitfield.0.len()), false); + bitfield.0.bitor(initial_bitfield) + }), + Default::default(), + )) + } + } + + /// Certificate is changed compared to `AssignmentCertKind`: + /// - introduced RelayVRFModuloCompact + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub enum AssignmentCertKindV2 { + /// Multiple assignment stories based on the VRF that authorized the relay-chain block + /// where the candidates were included. + /// + /// The context is [`super::v2::RELAY_VRF_MODULO_CONTEXT`] + #[codec(index = 0)] + RelayVRFModuloCompact { + /// A bitfield representing the core indices claimed by this assignment. + core_bitfield: CoreBitfield, + }, + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with the index of a particular core. + /// + /// The context is [`super::v1::RELAY_VRF_DELAY_CONTEXT`] + #[codec(index = 1)] + RelayVRFDelay { + /// The core index chosen in this cert. + core_index: CoreIndex, + }, + /// Deprectated assignment. Soon to be removed. + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with a sample number. + /// + /// The context used to produce bytes is [`super::v1::RELAY_VRF_MODULO_CONTEXT`] + #[codec(index = 2)] + RelayVRFModulo { + /// The sample number used in this cert. + sample: u32, + }, + } + + /// A certification of assignment. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub struct AssignmentCertV2 { + /// The criterion which is claimed to be met by this cert. + pub kind: AssignmentCertKindV2, + /// The VRF showing the criterion is met. + pub vrf: VrfSignature, + } + + impl From for AssignmentCertV2 { + fn from(cert: super::v1::AssignmentCert) -> Self { + Self { + kind: match cert.kind { + super::v1::AssignmentCertKind::RelayVRFDelay { core_index } => + AssignmentCertKindV2::RelayVRFDelay { core_index }, + super::v1::AssignmentCertKind::RelayVRFModulo { sample } => + AssignmentCertKindV2::RelayVRFModulo { sample }, + }, + vrf: cert.vrf, + } + } + } + + /// Errors that can occur when trying to convert to/from assignment v1/v2 + #[derive(Debug)] + pub enum AssignmentConversionError { + /// Assignment certificate is not supported in v1. + CertificateNotSupported, + } + + impl TryFrom for super::v1::AssignmentCert { + type Error = AssignmentConversionError; + fn try_from(cert: AssignmentCertV2) -> Result { + Ok(Self { + kind: match cert.kind { + AssignmentCertKindV2::RelayVRFDelay { core_index } => + super::v1::AssignmentCertKind::RelayVRFDelay { core_index }, + AssignmentCertKindV2::RelayVRFModulo { sample } => + super::v1::AssignmentCertKind::RelayVRFModulo { sample }, + // Not supported + _ => return Err(AssignmentConversionError::CertificateNotSupported), + }, + vrf: cert.vrf, }) } } - None + /// An assignment criterion which refers to the candidate under which the assignment is + /// relevant by block hash. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub struct IndirectAssignmentCertV2 { + /// A block hash where the candidate appears. + pub block_hash: Hash, + /// The validator index. + pub validator: ValidatorIndex, + /// The cert itself. + pub cert: AssignmentCertV2, + } + + impl From for IndirectAssignmentCertV2 { + fn from(indirect_cert: super::v1::IndirectAssignmentCert) -> Self { + Self { + block_hash: indirect_cert.block_hash, + validator: indirect_cert.validator, + cert: indirect_cert.cert.into(), + } + } + } + + impl TryFrom for super::v1::IndirectAssignmentCert { + type Error = AssignmentConversionError; + fn try_from( + indirect_cert: IndirectAssignmentCertV2, + ) -> Result { + Ok(Self { + block_hash: indirect_cert.block_hash, + validator: indirect_cert.validator, + cert: indirect_cert.cert.try_into()?, + }) + } + } + + 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)] +mod test { + use super::v2::{BitIndex, Bitfield}; + + use polkadot_primitives::{CandidateIndex, CoreIndex}; + + #[test] + fn test_assignment_bitfield_from_vec() { + let candidate_indices = vec![1u32, 7, 3, 10, 45, 8, 200, 2]; + let max_index = *candidate_indices.iter().max().unwrap(); + let bitfield = Bitfield::try_from(candidate_indices.clone()).unwrap(); + let candidate_indices = + candidate_indices.into_iter().map(|i| BitIndex(i as usize)).collect::>(); + + // Test 1 bits. + for index in candidate_indices.clone() { + assert!(bitfield.bit_at(index)); + } + + // Test 0 bits. + for index in 0..max_index { + if candidate_indices.contains(&BitIndex(index as usize)) { + continue + } + assert!(!bitfield.bit_at(BitIndex(index as usize))); + } + } + + #[test] + fn test_assignment_bitfield_invariant_msb() { + let core_indices = vec![CoreIndex(1), CoreIndex(3), CoreIndex(10), CoreIndex(20)]; + let mut bitfield = Bitfield::try_from(core_indices.clone()).unwrap(); + assert!(bitfield.inner_mut().pop().unwrap()); + + for i in 0..1024 { + assert!(Bitfield::try_from(CoreIndex(i)).unwrap().inner_mut().pop().unwrap()); + assert!(Bitfield::try_from(i).unwrap().inner_mut().pop().unwrap()); + } + } + + #[test] + fn test_assignment_bitfield_basic() { + let bitfield = Bitfield::try_from(CoreIndex(0)).unwrap(); + assert!(bitfield.bit_at(BitIndex(0))); + assert!(!bitfield.bit_at(BitIndex(1))); + assert_eq!(bitfield.len(), 1); + + let mut bitfield = Bitfield::try_from(20 as CandidateIndex).unwrap(); + assert!(bitfield.bit_at(BitIndex(20))); + assert_eq!(bitfield.inner_mut().count_ones(), 1); + assert_eq!(bitfield.len(), 21); + } } 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 dab72bb2a5ed80b1842d4ca341c609109dee8c75..d295c21cce1dc9f609c8068fb806cff69612f7d6 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.8.0"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: @@ -442,7 +442,7 @@ pub struct CollationSecondedSignal { pub relay_parent: Hash, /// The statement about seconding the collation. /// - /// Anything else than [`Statement::Seconded`](Statement::Seconded) is forbidden here. + /// Anything else than [`Statement::Seconded`] is forbidden here. pub statement: SignedFullStatement, } diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 899a95dd3a709e56149b67eb03b6eb1df2059113..734dcbdeb4411023cfbf540dda01a0b8a4799d95 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -1,10 +1,14 @@ [package] name = "polkadot-service" rust-version = "1.60" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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 @@ -74,20 +78,22 @@ frame-benchmarking-cli = { path = "../../../substrate/utils/frame/benchmarking-c frame-benchmarking = { path = "../../../substrate/frame/benchmarking" } # External Crates +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" } -serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.107" -thiserror = "1.0.48" +log = { workspace = true, default-features = true } +schnellru = "0.2.1" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } kvdb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } -parity-db = { version = "0.4.8", optional = true } +parity-db = { version = "0.4.12", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } - -async-trait = "0.1.57" -log = "0.4.17" -is_executable = "1.0.1" +parking_lot = "0.12.1" +bitvec = { version = "1.0.1", optional = true } # Polkadot polkadot-core-primitives = { path = "../../core-primitives" } @@ -139,15 +145,16 @@ polkadot-statement-distribution = { path = "../network/statement-distribution", [dev-dependencies] polkadot-test-client = { path = "../test/client" } polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } +test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } env_logger = "0.9.0" assert_matches = "1.5.0" serial_test = "2.0.0" tempfile = "3.2" [features] -default = [ "db", "full-node" ] +default = ["db", "full-node"] -db = [ "service/rocksdb" ] +db = ["service/rocksdb"] full-node = [ "kvdb-rocksdb", @@ -178,8 +185,8 @@ full-node = [ ] # Configure the native runtimes to use. -westend-native = [ "westend-runtime", "westend-runtime-constants" ] -rococo-native = [ "rococo-runtime", "rococo-runtime-constants" ] +westend-native = ["bitvec", "westend-runtime", "westend-runtime-constants"] +rococo-native = ["bitvec", "rococo-runtime", "rococo-runtime-constants"] runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", @@ -216,7 +223,7 @@ fast-runtime = [ "westend-runtime?/fast-runtime", ] -malus = [ "full-node" ] +malus = ["full-node"] runtime-metrics = [ "polkadot-runtime-parachains/runtime-metrics", "rococo-runtime?/runtime-metrics", diff --git a/polkadot/node/service/chain-specs/kusama.json b/polkadot/node/service/chain-specs/kusama.json index 6676bbe154b0591f4271aa727b64a9bdaf9e82ce..fd60cb8b6c1d443d47a85afcb7b885b4cf0593ae 100644 --- a/polkadot/node/service/chain-specs/kusama.json +++ b/polkadot/node/service/chain-specs/kusama.json @@ -16,8 +16,8 @@ "/dns/boot-node.helikon.io/tcp/7062/wss/p2p/12D3KooWL4KPqfAsPE2aY1g5Zo1CxsDwcdJ7mmAghK7cg6M2fdbD", "/dns/kusama.bootnode.amforc.com/tcp/30333/p2p/12D3KooWLx6nsj6Fpd8biP1VDyuCUjazvRiGWyBam8PsqRJkbUb9", "/dns/kusama.bootnode.amforc.com/tcp/30334/wss/p2p/12D3KooWLx6nsj6Fpd8biP1VDyuCUjazvRiGWyBam8PsqRJkbUb9", - "/dns/kusama-bootnode.polkadotters.com/tcp/30333/p2p/12D3KooWHB5rTeNkQdXNJ9ynvGz8Lpnmsctt7Tvp7mrYv6bcwbPG", - "/dns/kusama-bootnode.polkadotters.com/tcp/30334/wss/p2p/12D3KooWHB5rTeNkQdXNJ9ynvGz8Lpnmsctt7Tvp7mrYv6bcwbPG", + "/dns/kusama.bootnodes.polkadotters.com/tcp/30311/p2p/12D3KooWHB5rTeNkQdXNJ9ynvGz8Lpnmsctt7Tvp7mrYv6bcwbPG", + "/dns/kusama.bootnodes.polkadotters.com/tcp/30313/wss/p2p/12D3KooWHB5rTeNkQdXNJ9ynvGz8Lpnmsctt7Tvp7mrYv6bcwbPG", "/dns/boot-cr.gatotech.network/tcp/33200/p2p/12D3KooWRNZXf99BfzQDE1C8YhuBbuy7Sj18UEf7FNpD8egbURYD", "/dns/boot-cr.gatotech.network/tcp/35200/wss/p2p/12D3KooWRNZXf99BfzQDE1C8YhuBbuy7Sj18UEf7FNpD8egbURYD", "/dns/boot-kusama.metaspan.io/tcp/23012/p2p/12D3KooWE1tq9ZL9AAxMiUBBqy1ENmh5pwfWabnoBPMo8gFPXhn6", @@ -33,7 +33,9 @@ "/dns/ksm-bootnode.stakeworld.io/tcp/30301/ws/p2p/12D3KooWFRin7WWVS6RgUsSpkfUHSv4tfGKnr2zJPmf1pbMv118H", "/dns/ksm-bootnode.stakeworld.io/tcp/30302/wss/p2p/12D3KooWFRin7WWVS6RgUsSpkfUHSv4tfGKnr2zJPmf1pbMv118H", "/dns/ksm14.rotko.net/tcp/35224/wss/p2p/12D3KooWAa5THTw8HPfnhEei23HdL8P9McBXdozG2oTtMMksjZkK", - "/dns/ksm14.rotko.net/tcp/33224/p2p/12D3KooWAa5THTw8HPfnhEei23HdL8P9McBXdozG2oTtMMksjZkK" + "/dns/ksm14.rotko.net/tcp/33224/p2p/12D3KooWAa5THTw8HPfnhEei23HdL8P9McBXdozG2oTtMMksjZkK", + "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30333/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT", + "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30334/wss/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/paseo.json b/polkadot/node/service/chain-specs/paseo.json new file mode 100644 index 0000000000000000000000000000000000000000..2e659716766ede8cf4ec2b183013b5e925dff998 --- /dev/null +++ b/polkadot/node/service/chain-specs/paseo.json @@ -0,0 +1,422 @@ +{ + "name": "Paseo Testnet", + "id": "paseo", + "chainType": "Live", + "bootNodes": [ + "/dns/paseo.bootnode.amforc.com/tcp/30333/wss/p2p/12D3KooWFD81HC9memUwuGMLvhDDEfmXjn6jC4n7zyNs3vToXapS", + "/dns/paseo.bootnode.amforc.com/tcp/30344/p2p/12D3KooWFD81HC9memUwuGMLvhDDEfmXjn6jC4n7zyNs3vToXapS", + "/dns/boot.stake.plus/tcp/43334/wss/p2p/12D3KooWNhgAC3hjZHxaT52EpPFZohkCL1AHFAijqcN8xB9Rwud2", + "/dns/boot.stake.plus/tcp/43333/p2p/12D3KooWNhgAC3hjZHxaT52EpPFZohkCL1AHFAijqcN8xB9Rwud2", + "/dns/boot.metaspan.io/tcp/36017/wss/p2p/12D3KooWSW6nDfM3SS8rUtjMyjdszivK31bu4a1sRngGa2hFETz7", + "/dns/boot.metaspan.io/tcp/36018/p2p/12D3KooWSW6nDfM3SS8rUtjMyjdszivK31bu4a1sRngGa2hFETz7", + "/dns/paseo.bootnodes.polkadotters.com/tcp/30538/p2p/12D3KooWPbbFy4TefEGTRF5eTYhq8LEzc4VAHdNUVCbY4nAnhqPP", + "/dns/paseo.bootnodes.polkadotters.com/tcp/30540/wss/p2p/12D3KooWPbbFy4TefEGTRF5eTYhq8LEzc4VAHdNUVCbY4nAnhqPP", + "/dns/boot-node.helikon.io/tcp/10020/p2p/12D3KooWBetfzZpf6tGihKrqCo5z854Ub4ZNAUUTRT6eYHNh7FYi", + "/dns/boot-node.helikon.io/tcp/10022/wss/p2p/12D3KooWBetfzZpf6tGihKrqCo5z854Ub4ZNAUUTRT6eYHNh7FYi", + "/dns/boot.gatotech.network/tcp/33400/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW", + "/dns/boot.gatotech.network/tcp/35400/wss/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW", + "/dns/paseo-bootnode.turboflakes.io/tcp/30630/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e", + "/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e" + ], + "telemetryEndpoints": null, + "protocolId": "pas", + "properties": { + "ss58Format": 42, + "tokenDecimals": 10, + "tokenSymbol": "PAS" + }, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0595267586b57744927884f519eb81014e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x06de3d8a54d27e44a9d5ce189618f22d4e7b9012096b41c4eb3aaf947f6ea429": "0x0900", + "0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385": "0x0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c9018096980000000000000000000000000005000000140000000400000001000000000006000000640000000200000019000000000000000200000002000000020000000500000002000000", + "0x074b65e262fcd5bd9c785caf7f42e00a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x08c41974a97dbf15cfbec28365bea2da4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x08c41974a97dbf15cfbec28365bea2da5e0621c4869aa60c02be9adcc98a0d1d": "0x380325fc2095902f5fe394f244bce38b0dc3d631cbc05f0b64d5620a71bbf2514f0f03f045328f504c13dac9ddd9b1186098aee7c46cb8d55289dbbf2433bab7a2623903a0af06322d100056125fac1df39d161089b07ae279505aae8731c4d110a54ad703aec8e80ea0375f8669d6e55d7abb6a3117678d7bb851a1bd100a01e52a4fed9003d46c454f9b620603feef8c3a2a5d7098205b8566500fda0fa0b456d6ded54538030e901c390fa37d101ff25d70594acd2df67b4493ee77a73684f25d39313536d702f4f4d0eccb899bf2d611b56e0afec7c740efba404f8d0e82a545f988c45316c402182879ec92e811e2a8cc117f3cde1f61d3cba0093134cfb1ed17a4ef74915d4a03e843f200e30bc5b951c73a96d968db1c0cd05e357d910fce159fc59c40e9d6e20203d51bba2124f480e3507eb1764fc3019ac7abae8ee215683a285078bda7f51d029338ece1c6bc6439dc4d16bfe33f28c9f0af31626bb8142849742b7a624f68070333022898140662dfea847e3cbfe5e989845ac6766e83472f8b0c650d85e77bae03586dafcdab3d4647d4dc68732a9cab8aa34c00c5edd04e65d9dd44c2a1fd21e2029a1eb2e31dcaf468dbb516f9b620fdd7c3f090d58a88e02b51b25255b2182dd1", + "0x08c41974a97dbf15cfbec28365bea2da8f05bccc2f70ec66a32999c5761156be": "0x0000000000000000", + "0x08c41974a97dbf15cfbec28365bea2daaacf00b9b41fda7a9268821c2a2b3e4c": "0x380325fc2095902f5fe394f244bce38b0dc3d631cbc05f0b64d5620a71bbf2514f0f03f045328f504c13dac9ddd9b1186098aee7c46cb8d55289dbbf2433bab7a2623903a0af06322d100056125fac1df39d161089b07ae279505aae8731c4d110a54ad703aec8e80ea0375f8669d6e55d7abb6a3117678d7bb851a1bd100a01e52a4fed9003d46c454f9b620603feef8c3a2a5d7098205b8566500fda0fa0b456d6ded54538030e901c390fa37d101ff25d70594acd2df67b4493ee77a73684f25d39313536d702f4f4d0eccb899bf2d611b56e0afec7c740efba404f8d0e82a545f988c45316c402182879ec92e811e2a8cc117f3cde1f61d3cba0093134cfb1ed17a4ef74915d4a03e843f200e30bc5b951c73a96d968db1c0cd05e357d910fce159fc59c40e9d6e20203d51bba2124f480e3507eb1764fc3019ac7abae8ee215683a285078bda7f51d029338ece1c6bc6439dc4d16bfe33f28c9f0af31626bb8142849742b7a624f68070333022898140662dfea847e3cbfe5e989845ac6766e83472f8b0c650d85e77bae03586dafcdab3d4647d4dc68732a9cab8aa34c00c5edd04e65d9dd44c2a1fd21e2029a1eb2e31dcaf468dbb516f9b620fdd7c3f090d58a88e02b51b25255b2182dd1", + "0x08c41974a97dbf15cfbec28365bea2dac713b7f8b14e2815d297585d3581e774": "0x0101000000", + "0x08c41974a97dbf15cfbec28365bea2dad47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x0f6738a0ee80c8e74cd2c7417c1e25564e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x1405f2411d0af5a7ff397e7c9dc68d194e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x1405f2411d0af5a7ff397e7c9dc68d196323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x196e027349017067f9eb56e2c4d9ded54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x38b07d600e3487e2712dcc3879c7b17c9b29cd2243b45f0d9343c591b89cf82a6501000000000000005440add43e5388a81aef665c9086d386c0be0ce75e4f8a4a3d8168e976ea821f010000000000000074dacbca0cdb5099afef67e622c147614198e669931cebc605629da332632473010000000000000058108e1651614afc6a535c426fc013945e93533faa33819fe4e69423fe323302010000000000000050412bd7d3f1075e8f8e3b682d05ea20b391c287d8c849a0e49a78f568553e690100000000000000c44b3e8efe854419ccd5801a82ada22d39cfccdbcece382304cdfeac81ebe4020100000000000000b2efe7e70daf44b3466c63ccbf4487f42c6a9f6fbb7050b849691e36ce92e3470100000000000000facb2f987caac6c1290a9784b1efdba78343d39aed805addb12945efbe444000010000000000000068a9ec74fa35b3425eaf503dd36294ba8e758e7b8084c4d6bfd547f8c6b5827401000000000000007c8348ec95a0faad6a638ef74864761028c53221bde07e9ff7c81a3f427abf3f0100000000000000ae240842b74e5dd778972e451558134f434c7a1d8a52bc70519f38054e245533010000000000000018bd0f67d77f04f1a92400421813d8927fad109b40a8689254a5f0c8b346857c01000000000000006248d87bd2a640ffe26d6b831735887c24e2076a3a0f3a74f7ae7568c276040801000000000000001ab03b1b3277edfedd24ef3d3359b449b64bd95ed82a04e7f9fbaab7b71dc0150100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x38b07d600e3487e2712dcc3879c7b17c9b29cd2243b45f0d9343c591b89cf82a6501000000000000005440add43e5388a81aef665c9086d386c0be0ce75e4f8a4a3d8168e976ea821f010000000000000074dacbca0cdb5099afef67e622c147614198e669931cebc605629da332632473010000000000000058108e1651614afc6a535c426fc013945e93533faa33819fe4e69423fe323302010000000000000050412bd7d3f1075e8f8e3b682d05ea20b391c287d8c849a0e49a78f568553e690100000000000000c44b3e8efe854419ccd5801a82ada22d39cfccdbcece382304cdfeac81ebe4020100000000000000b2efe7e70daf44b3466c63ccbf4487f42c6a9f6fbb7050b849691e36ce92e3470100000000000000facb2f987caac6c1290a9784b1efdba78343d39aed805addb12945efbe444000010000000000000068a9ec74fa35b3425eaf503dd36294ba8e758e7b8084c4d6bfd547f8c6b5827401000000000000007c8348ec95a0faad6a638ef74864761028c53221bde07e9ff7c81a3f427abf3f0100000000000000ae240842b74e5dd778972e451558134f434c7a1d8a52bc70519f38054e245533010000000000000018bd0f67d77f04f1a92400421813d8927fad109b40a8689254a5f0c8b346857c01000000000000006248d87bd2a640ffe26d6b831735887c24e2076a3a0f3a74f7ae7568c276040801000000000000001ab03b1b3277edfedd24ef3d3359b449b64bd95ed82a04e7f9fbaab7b71dc0150100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000002", + "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x38f89c97bf5b2c07c05c84eebce4ffc7b28766946c03741fd1a71fdae0942e8768cce3ec06252cf7cdad47fe1265047a9bbddb9059ee4bdc6dec83b67249b4a9342ada042fb4bbfd9b6d8c48293ffc4a7722632c843a67e608554c41d06aabc41392cb05c48fc643f057626c669604675c5ad5a836266f260ae7030c6fdc17a5438c15606f4c121376097ff0e96c2a33ea7b024d812b42fe2c741c8b8cee17e63d729053f28155071474b4686323db5f7a318cb3f088b76660cc8ff5e3e11ec32eaa3955187f755708cd6a8104314b962ff5043e36efa3ec5d84df40c58b44222126e2fc857945d01520797a75388c58e710c9fefedd28387af70880f1682be41e64ffc83f4f86cc595e607a00b977eeb6641e02a4e6e556c24ab163aecd7d146c7aeb767131602e6612e607a9eb8e26b4ce4fa4765593d032bc923ce8acadda42763d070989ead31f265b40cc7a0cd29d47799b766d6a7f084e44c82baedfc01e629f9fd0dd7279c7af7470472d1208a13e33239b484974d47cffce4ad4785644047277f22b9ef92a8b99618f4c86c2412f0e3b08a4f965f842775672043d1e259a86227e204a2d003399c2a3b50c2c869c4380c195a014a02f6d2e7048941237", + "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x38f89c97bf5b2c07c05c84eebce4ffc7b28766946c03741fd1a71fdae0942e8768cce3ec06252cf7cdad47fe1265047a9bbddb9059ee4bdc6dec83b67249b4a9342ada042fb4bbfd9b6d8c48293ffc4a7722632c843a67e608554c41d06aabc41392cb05c48fc643f057626c669604675c5ad5a836266f260ae7030c6fdc17a5438c15606f4c121376097ff0e96c2a33ea7b024d812b42fe2c741c8b8cee17e63d729053f28155071474b4686323db5f7a318cb3f088b76660cc8ff5e3e11ec32eaa3955187f755708cd6a8104314b962ff5043e36efa3ec5d84df40c58b44222126e2fc857945d01520797a75388c58e710c9fefedd28387af70880f1682be41e64ffc83f4f86cc595e607a00b977eeb6641e02a4e6e556c24ab163aecd7d146c7aeb767131602e6612e607a9eb8e26b4ce4fa4765593d032bc923ce8acadda42763d070989ead31f265b40cc7a0cd29d47799b766d6a7f084e44c82baedfc01e629f9fd0dd7279c7af7470472d1208a13e33239b484974d47cffce4ad4785644047277f22b9ef92a8b99618f4c86c2412f0e3b08a4f965f842775672043d1e259a86227e204a2d003399c2a3b50c2c869c4380c195a014a02f6d2e7048941237", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x0000000007def964eb114a412100", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da902d496d20c019d22397accfc42b7635d94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da90ffdb9747f7a8bfa200ea2291eea6bdf9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da911fcf3e922de10898cb04273301735e65270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da915fbc5d9b44885a2dce0fbaa3834ba211e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9279effc2672354478fb5e3f7e65726b668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da92ffe7c0dd7d3d8d8485c403a33fb7adb6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9350ea875b4c0459b3527d475e9825c99d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94d176c4abc6d2410c43423c2714b909c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9585bd5ccc1af6cf60c19d791bb091b750efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000": "0x0000000000000000010000000000000000e40b54020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da965bb58c29c9825c1f6727fe8c41650ac82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96deaa967a94fa09152be01a253511479d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9829f3f4d6d7c991993e0c044ae97410b043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9a00c6f5358fa04d76ce7cf995bce2eabe21bb02f2a82cb1113ff10693093377672925b23f047624c0cfa7a24a8609841": "0x000000000000000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ba2db252d1b8c64ec51b04d0f50d7d2b9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9dfc40f350cc4563ad9ddff5ad92944278eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x000000000400000001000000000000000000c16ff28623000000000000000000000000000000000000000000000000000080c6a47e8d0300000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e6fb488a1496189393ed0a95dcf5577e7e939ef17e229e9a29210d95cb0b607e0030d54899c05f791a62d5c6f4557659": "0x00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0xaa183d0014706173656f", + "0x2762c81376aaa894b6f64c67e58cc6504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x380edf2a41cb81178704560b02c35f5e01a5a97a568ebc10c025ade18b6ab2fa1d98aab6f52520022d011a6eba2dca1c6327edbbcd753c170dcf0e9118b5f0f25bb414aa148096a92a1831309f758f944725653363ccbaeb21817b7df5784b8d4674bd654c470ed9b94972c1f997593fab7bdd4d6b85e3cf49401265668142584e94848b8cf2cbc9e6fd72db8d80676591b5be4d1ec68972ada48cf6fd01228712a8a03d86e6c0dbe180cadfc7994121f462b28f7a8cb1be7e0e354147624be734bef47a9e4b47ed57461e1d28cac7da327a52ebcd64d74080d31deb3ac7a7645eca3c2703db1633a27eff681d979967988c3a6752c669fd41f1abde10f3b0544660fcc9d094d21fe17cfb7426501f50cb3d75c4c9395a3140e0f255443f660d3b30c40adee5476157ef3c2a26e10cab95ec7d54b62dd220738f5a474d5f86874e06bd8fd81e50cda2bd67bf6893d921d1aae5cb08409ae43e0bff4d54e1830e58d28145a7cde195a4c834276730d30f074b212a150e770931ee9470e853e7d2247c03ca47a3201455f8f89defda4aa909cb1d25dd9ddb7fd62a940606f79b56632cd12c731d91441f0114b08d314cd3f9a9f7fd0240d467fe54adefbee4d90762", + "0x2ecf93be7260df120a495bd3855c0e600c98535b82c72faf3c64974094af4643": "0x01000000000000000e00000063e7f4c4028d97f9e69689cea528e2d0155ea2b202be0a42381c642ace2e4cf4", + "0x2ecf93be7260df120a495bd3855c0e604e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2ecf93be7260df120a495bd3855c0e60c52aa943bf0908860a3eea0fad707cdc": "0x00000000000000000e00000063e7f4c4028d97f9e69689cea528e2d0155ea2b202be0a42381c642ace2e4cf4", + "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0xe68919a7ceff5bcca24e5051b9ab34b9c9f9e74a925cde4c49570cfed7bf14180e000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0xe68919a7ceff5bcca24e5051b9ab34b9c9f9e74a925cde4c49570cfed7bf14180e000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", + "0x31a3a2ce3603138b8b352e8f192ca55a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058540d061e1308c819541050e938e9005f822fc197252fa2ed5713e0ba46c46f094c0fe2307326fb057d60bbce8bdfcd175f1feb2bc7604d3068f26910799b2f5ebdfc5148bbff93d098c88f368b8ee8661e889a5a2344ad1142f6de72cbb41dfe175118bdb120c23497929fdc3aea901fbf5f6ec5254f62bd59323b7e76fc0f30180c36cf35e4cfafa724fca1e767a5f2247e3bd43a5ec62ddc3617c3ae39462e821cf3b8db150a63cb17f9629075e3b88be0ec6e85aac4324d45eb2b34294e761194ed5dc9ba317da543ad5095086b2a483a9ebf1e0d13163d251face33b2511d6f11c590cb7bd712be4d16c7cfcf8507e99b5e724e8c2c797dfaf4703ff89ac1bb3f6437c14e300b805fae263731bf4f1f773e356fc9ec166e38be7cb73ec2591317a0538ea782e76128cbd5eac1bb3e28798c16556fc11472adcee845f6b99156ecd0fb7de2f032df7f0f7dc027dd963731bf4711b91be8d0591456f2c88a40e53cd6fa13706041acd5e92a22f7e63b0a4e62fb7b6f327b7b2f3376e6df339f6926c75a34a0145c098b59d9d87028ee8b23edbd12bc05167e7629781db1b189b8b5e762e038b62678f1e95249359f34abece1e3d19b6b3af97649915761d3fc41cb71eade151d36f60b8f5f60c0cb32fb3b60fc90d0c6787f345b2b5af97847e88396e3d59c357cb6f60f8de9e81e1326b7bb8cc0a7fe8ede1322b9cd2dbc3eccbac9087de3e2425a8552aaf96cfc0ed0d0c717a9e8b9d066e67e0f67a34cc961fe221a62015d9f2135c66c5af9744d6c9acf8211ee2211ee2211ee2dde1fae4ebd539d2a23482db0b7c1b03c28b0ebfe6b3b81576cde7afb815b29acf07c2add8fc8f5b1b03c28ae6db708b9bbf0c145d214ef3b98d59be8d01a1456f875ff359dc0abbe6f357dc0a59cde703e1566cfec7ad8d016145f36db8c5cd5f068aae10a7f9eb29e00810accff61077dcdaf37787eb9321bd3a4731c61170bb13bef9dc067ddc027dcbacf06bbefc7a49e4a307c30f30180c8896e7b85b6ec9ef27b7e2f7eb6dcce20fb1043b66f15b5f68d3fcf8f9706b3e67c3ad65d69ebfe2566c3e0eb7e4f95cec56dcdae6af273fc4433cc12b396de5a863bd59b275fcd6b1de2c899f8f9fb528b6ac9359fbf592c41ac68e1fe221de1dae4fdeab7324630cc3ed05be8dc1c674b8f5fee3d6c660b0de6fdcda184c4cef3b6ec5de2f03ad2bb4e9fdee6c7df25e9da31891702be4dbc7af895b6ec5de4f6e6defb9d4722bf67eb9c50fb7de4f6e6defb9d4722bf67eb9c50fb7de4f6ec5de73a9e5d6f67eb9c50fb7de4f6ec5efb9e8258975a34a0145c098b5cde7a18023baaccf3617bb08c65e8fcf71b7ab5200116698b52dcfa3d9f8e6e5e3a59724d68d2a05140163d6365f82cb2c794e76118ccd1e7fbd599759e1d6f243bc5e92c92cf9211ee2211ee2e815e0a8f9eb25891fe2213e8a312281db9df02dcf6dd0c72dd0b7cc0abf968f5f2f497cf460f801068301d1f11c77cbadf8fde4167fbfdec62cf9218e60c72cf9d617dab43c7f3edc5a9eb3e1d6326b2fbfe2566c791c6ec5cb73b15b716b5b7ebdf8211ee2095ec9692b47cdf566c9d6fcadb9de2ce1cff3672d8a1deb64d67e3d25f15c437efc100ff1eec8fae4bd3a471ce30db8dd541db39659e1aae3b9de186c4bdb302b7e71b8dd541db39659e1aae3696f0c066b1b66c52f0ec7edaa14408419666d2fb3f6eb2599ccda0ff1e270cb5cd117bf315852efd7e356e88bdfafa9631216860f4edd83ce41abd13a68327a072da6cbe8a5f6a2c7e82c9a8aaea245d04a7d45b3a09f68271a8a5e419ba051d056b414ad82fed253f408da4b9fa053d04d74145d8266a2b168120c1d61480a4346186ac1d00986a0188a62688a2129869e184a1a126208690836c4652888a12c43450c6d1902620889212286b40c393134041235903c61c805487c80240b243d40f2c41009864680440b245420790112162099029215201933c4c51013434b2081028919244e2079634889a118209102923343251892c1501743490c79602836d4012450401285a12c5e38c10b2678a18a17ac7881042f3cf102140190c20b1d7821891738f0c2085ed8f2c2102f20bd20c40b5c0270e6052d2f30f142065e38e2852502308517b2bc1002a12d2f80c0881823608cc8c0481746b83012331203235b18818191258c28612404464060a48b910f18f180910e1849c2080e8cdcc0081246928cb4c048164658606405465460040b23293042032333307285112b8c90c04815464660a40446bc18a1c2c81446a430128511288c3c61c409234a469a30c284111118d18091238c64c04811468c30328411242129084141e80da129084541880d21324241105282d018a11e080d4148084263082141e8084261087d21048690184246106a430808425d08c940088c9009845820c485d017a19810164572504409459650240945d628f28322658a04a188108a00a108128ab051640845d428924611342217518c226414b141911914b9a248098a3051a489221a28e244101482a280248a201b048d0962232808414a415d044111a44610164132080a139482201f04d12068064160826a105446d00e82a20872220809413a08ca411018414b41610489116482a02f415904b1200809a02858420049517900a4045406680aa0250041f11346eb07401c58150174c40f095400c12a06ab180f183c39e02102cf10789cf003e907179e1ef0d48007053c38e0718327093c25e07901cf183c51e0c182c74c4b07ad25211bf0344128072d295046402101750454191796c0c21a3f63fce8c00525fcc8e2c21178622eb481da82c81b2e3c41c409750d17602e34f1d1c085215c08c20511b82044f5810b4bb8805481e08216224aa8648834a10ac10522883cc185225c30c28524178e28f2c505257e3ef04345902578967852306bd03242cb0700d0004bcb4f976f8c1f2454434841e1a7053f4a081102644388078418d1e241ab8d1c6bc82708f940d0195349c816211a10da819023841421444b3542103041dc087283204a0862031e292062e6071541ba207284202fe081823ac6852d41b8f831c50f2f404c0419230810829011b425480c809a20b20422653e190429838514e48b54526a0341c0d049d221426708f0043a45e820059142903782e800080aac20829c2088143f26e0e1e2c7169e323e4af041c20f08843ec003854f0e7cc4f0012386e1f3850f0e7c6ea093e54710d38a59029f297ca41842021f0e0029021022107183e7083fb4b0b0041694d0eac124830519f820a31261853456f8c1102b2a1b1f1741b4f8baf8624162f5072ba011248b6f065f0d5618c3e2c21a6285355cf8000e332b90b1c2182c2282b4aca0031c4ec0e1068e25e068c20a2c5841052b60b1c209523be811420f1b3d6bf4008195a5e707ab2d5a68f08029728691345a40c287831f23f8d1c68f2a3e227466a012824e0c8428810a03480f524008d9006a0c9d1da890e573e2a7043a63a07290030837425c042e2041478c33f121015103e5858e18214ba496a013068f1428307472a0f3058a0b14193a660849ea59d29141ca8c1e3474702064089d1a0871e21b42870b214a3a6ae4f0018f255065f48891a30729188f171e12a49c90638c8e0d3e2656374829e1a70a211950b1117482225d8474d14123a5861134844cc183c42304cf123c43f06c8027032b9cc163c40a3950010c0f123f849848b49cd032f393c50f172a28a1420882941094041e29a800fb89e2a7091e312a00a113849d2738072fa080c9603478d0688520880c74cef0193a3dd04943e70c1d1ee894a143868e0e749674c0d0f142e7063a617468a0f3021d30421ed081818e163a2ea8661089029129d424fc90c2052e3a42d42054346a11826c51d3f001061124b890258819d5093a5b884881c8997a847a860e103969d428e49c51cfe488a96fe4f0a04a21c70c1d580e1a750a150a393dc8514327091d0ee87840a7034156509f20c484da84ca843a835a4695811441ce167507f104750c1f2da82ea82da85dea07aa076a524522080caa0682b8808e01ae01022107991c43e05923958194065e30e22ae019806f0cbd0146017c0268066402e8469035c02404498347093c4cc821424e16392ff021c60520829ce1c38cda868f307c90f1d1031f3bf021860f1df858f2a1860f367cac71b3e5468b0f20f818828f347c10c1c70f7c8ca1f162b6e18307467c80030a3e5cc0e30394192d245011f858c1b7850f27703081c70644924084093dcce078428f347892702841880b585b70a0c079c247042a984027081e58f058010f2b8a3cb182014f153c3c3064044143f06c2124043c2ce03963c71ab3055c021e2b8034b083044259fca000350321483a2dd0d902e502941642885829b10a010b3d70818c1026503400fa802a08a830281c0829037503d45210255a6c14f900aa8b16115035f844e04211827c40052826098480691d61c7085a53b4d270c1084536d073860e135488828519804140f1a025a615041e18fc8015f1800b43f09941cf98a012b4cc407db1a30a1f6078be1072049e32563af8b050418a4f0539d40032a2550625c6f7448f194538803a83052f78beb48a10c4033c34d089f98801cf0a7c6cd11a426b8d9f255a40a061680d641142a490460ca922c719201200788206073436a0a901cd0c50b11f1c4c333c4e309ff0c1802788ef053c598814a18a09c2021e26e0016285c42a690531448cf06941a48d1578b0eac203e3f1658532563063c5019512aa107c6204694155a30e8105848f1c1081828f327c8ce1e38b0f4ced4145c21783cf8c8f8ccf8bef8c0f8d2f8c55078a8cf9be586d60e5812245f896561f5825f1811157f025a1880f8af480474b1129725890b3822152f0bcc1d3021514aa27544ea894544da89810c225f584202f3c64f068d11ad31202cf09789ef0a382203482c4f084d152a3452648972244f894f840f07529e2a59679210531073eda78a10924357081083d6050628286904283c708423f0842c19fd013868fa4a0275464503040bde0052780422089288244d099941b2b2e90d840a806b209159c10c281540f68085a67f04c1174c52a8c1ba41f227862b09a0190078a74e0e70b214de4c47eae6829c132008b8002934a4308172a3ca142132a28adba102ac20c41cb891f1b08492375868f2c7ec2f08c19c2e2678a1f2b86ae98492f7409ea814ce24acc0cb85086b9985c5a55ac64c0c3c62a8c0a5d540862688bd5172b1b04a9c0c80f8468f0a3082148a06c303520991082042439e03933a484a4053c64864c502489151873073c66e8fce0c7892240f020e167053f60a06200d401214fbc80444e0d82aa081a411011281df0986045831f303c9410d281a0229048114404432f08ca80500764077e66200404487080dac150133a63e6153f2ce091411130b80beac40f0d907c21048a1734304df033022149acc07c8919048329a22444cc0b53acbc102202236adc3c010eb1c2811023ac8cf879c190164243042d41052682489033032430f8d1e2c70b940c7e4280440c242e688901444c91a4202e48a658d5e06749880c90a031e4e587091d200cc160471143219847f478a1e38322301f12f85ce163854f1540ca40e245881a42a210d2864e179405130a730a14080a9b676810f38d1905fa654a8166092286e60bca058dd118b89084173e20848a1c19d02d80940002c18f0c78a2f851e2e9426805422ef8f122d482940e84d0f801c48f144265f8504a8991c3450e0d72c0e4d8a08523f050f1d3c5cf133f2910d2418b8c1f43f08c8047053f30f8d9e227c623841f46acb4f889c18f0b7ebef064f153039e19fc40c1a3c68f087e4ef07383203378c204112148084145085a419011a9314156041d91d345912a5e4812f2414e18540b788a60042674c6678b10117eb0e0e1c10b5f5059a888b03ab303881d30006c00001c08a2250897205b88c4887441840b16c06061898530762cedc8c10e318884217203223658018a15a458218aaa4415415da29aa07ef94cf09de0fbb283cc0e22ec18820a6178005104043c82f834f025ad0066051aac30838f042a7ca102182b7460850face00115622a74a10217428620a4084288507150bfa85e0c616288d290265a00a385a516c200bf00c300c1b801c18d123721103286901d0821a34709134ccf125a88410b5cb4104b4de183a5ce083933048821b016d66821082db041640744c41029c327083e647c84e0f3049f28f840017442a2007c0284c18c02dc6226e1d3850f181f19ec18b3c3c54e173b31d00ab004e0152d1461d5849513401e80678066ec88c18ed88e2d7686d8296287081e3178aee099024f10785cc0b3031e28f0fc8007083c3ae0b9014f1a3f88e0698367093c5e0885119a22688d2032738b900a261bd306431b9861cca529c60c33bb784189223a28e2c5ac628e607a1112420804930373035388893487104a825019d904e903e98664824443fe400a412a410641f64002418a91652412e419d20cd98654433a4192916bc82448366411a41939461a411e41f2400e41a621972089506449880c59027985549220904ec8276408e412720a49858c427a911f9052c82e120aa984f440113124103288d8059fe129b014f80d36023381dd6036f8082c045e0297e12230190e02278189c0436025701b8c84173cc038601d7018fc057bc13760308a84c151700c380601f6c22a602a9805bc82225ff009f8056c02c682afe02a780b4e014fc1246017b0146c05b78047c02828b283175410651081107f108310c1884b4422e210718ca883095cc921aac0872844e4126115f8a189952a0f70000e1250800d09880106041ca0061a6658420025321069e167c80b0100400cc20b828bbd710719134b222b4d26c0644a13221e9830e1a2d4210a111326271f9f1c15e800c5890f13b8c9c162898a9a1055c921869d9c140cc9410114284d728002a589004ec0431105a21041f9b244443b309152c4a488070a3821aa404f8e09d60a14274d8a8874604244b4c38f9c12ac941da2386162812a443e5061e2644a174521eac1024d6a78ce154b94431429519834c9218a1403e4d03d72acd0414a0e443ae4e49060ad5880e889150b102d4900985385152703683067042ee478d9c9a1626972a6582996871ca44c21bac98962abf84001273b449192031327539a68024d5c3872a0f891f344508e13ab831326454456aaf890c38e1ca5b542e484024c9c4ce926537488d2a44a0c3e729a28fa8128ca4e0e1316287252450a0f453e34c9c911c15ab14204a547ce1235394a84608ba2f8202507263b342182822307045ba5871e7a982195d345859c0fac0e445128e0c44a141f98e420e58a131fa810b5723cb0454456987460ad38b162811d888826c0c4c994b6c00e4444ae1c39496c911529509854913285a80a0776072952aa4c6132c5090f56aa1045a980142616207a02e66c60ad10f160a509142b4c7420720265ca9426517cc8c94162ad1015f930c58a131d72e424d51c0d2c5195294c88aa38f1810a5115265676b832452727035b1485a807a2a226455676b832859573c45a212a9272850993285270728c581eac34e181890526c0a4082b5088a24469c2430e4ca6f4508507263a44f141ca04a23821a2288a941c9850e9418721d64a0f4daa3499c2a48a14a495b2038f1c21d64a5114a22b44395c96a8a8890e4e7488d2240720395b9648072939f4c8d1b255a4ec6085c84a96b552e4c394221fa4f0e404d1830f56ac34512107886d52d444072651a438d181a8c88a132b5588a6fcc881ad15a22a52726062812227509c4ca00914274dae34b98f336b658a0f52aa5080890f1f53582b55a41459912243932a4d6a58f990c256c0c994ae42f4a48a1406d000fa786389887c20f2e10036e84044010a34b181051f51d82645527ca0c2c44a14264ea63405a23879d2a42801383ea0b0528a983c61897288d2044a9309309152c4c44a51142752767c98592b3a54695201a22a4c2810c5890e5188a6307132a5ad409122a58628529c143589e2a4072906a83e9cb0569ee440c4430e4d582b455188aa10f110c5c97d30218a931d6e7cb8b13938b112c54995a21f88985800ca94294d985421d2018a131f5c4f729002c585e363096b85870f256c1326383e92b03d306952d4448726454433a87c94b98013284d9c143989e204870f24ec931ca440990013a2221f7220fa8188c6c711b68a1401741b89a04ca04951141f76605294035115273a4471c284c98e19704ca17d21e388287a8fb88dc1601d8fe48ecccbe2658953a2367570361c9c6dd3c1d9b66ddb984e9a6db67c62668e94329572aec44199299dcc534a69c3ccbc316e8cbbbc91ee4e9632ca8d4b296566a6bb9b9cbb94e7d2dd38ee4d5c6daea489579bd9e4ac9392661acb93c652ca9394cc268d4eb9f4a3cb1f4fc99327dd9d3c79eeee049a508e73755667714829e54637ba946e31524a4d1ba554a6b4a5da04b5b92a497160c964aea675734e4955544a2a699492b2a473ca18bb9daa8ed967b7ca65cdc98c9200b310639494258d546ab31bc272638e41987723c0922c519297e54e6d71d089929287e70a524aa6a66566a9c91865a7824077dab873eea472b59573abdbf2d87d00b3a4da7633959adcf1d464c7546adaa63ac99407903959f260964c1d207969ec32a9c98d9b940db8214ac92ba92771ec763491294b962629358d6bb4c9cbccbb5152aaa2ecc93853cc92535288fc4e3b94019399b56e4ff3f49d16703a514fdbe44ee66e519379521959c3c13fd68615b0492997999929534a29cf9e7657ceb9bbcd39b739a3d4b4a9f1b6bb4d29e5f4a494524a1a66d624a56b434d0d955ac709c0a194fea09e4480644f4e8d0fb03f9ec73548a621ca3925ff0c611b96924e969272dc9d9e67e35596d5c6f33c03c49a1a49d7666d3c0f07087a2849a5dc4d63c92c375bdee49c34d249299d9c511a59f20c426847a746299d944a0aeef6581e91ce2092f2ae941a53a65372645e9e3c234f9edb4a161740d2c8949952ca319d94d21825dd6c728aeea4cc9a94332ea13c270e3997f2eedc9697256f6a575bde5d4925a574521c934e9f3929a59c993893b4d3648a67c63b774a6dca5d6d93bc0460254c2365197fa4a6e4c98b8399e6b8d965cea2b632f2025919a39492e2c8c19429cd6edc3565d9eeae2639721677974dcb6c32994c6bda35adc9c4cb2b354dfb989775ac8ed52179994a2a7923f326996764a64b99255d96a8ddb18317c53b98e58e1dbc2c70e48f23c78f25cfb81c254f2965a452d33466c9434aa94d9eda5cd6e69474ae9492512bc3c648a5ec2265d6a4a4b4eb54cc1d65eeb8e378a2db89c66da3943b1983a6ed524a25a592eeae64de9dcbddeef2ee6429e5943c254f2979ae9c92275366661824cb6d37c6dd8e57ca8e478c34d24d8b7105f9ff1b9d52ce69a3699ac633351b4d4ba269da00a63605a031338d52b28cbbbb32ee4ac9947799eed24957ee6e94bcccd44577761c378ecc94a9dc58ca4925f3e4c99ab6946edc297929a573cedd49174597eef25c66deb97432f3dc5d9e9a26792eed96e9b26439996564e6aedb9d93796ea4cbbcbbcc74e9b6514a7729a5bbcc734e6669c351ca96cc1c99797799254b5e39a5a4cc2ca5643a44a5ccd1344a376da3cc2a243d2fe4a8664e007a7226ddb64d45e9ea083522448bc46d53c52d6e5145378d529992945219a35c5e4a996e94d2549026a726e9dca4a4bb94f2524ae9c73393737653ce49b7f3a49452d28d9939e32929dda5dcb1ee42e658b64015293d10f59001e940548548872a5298b45c000091932a452bc41e767f88aaec90831428509ae0dc4ce9a1499115a21e9c14ddc7047cb0d2436485490f527488d2a44a455961b5582ee810a50913a22a4da6f42085c807a5490e52a0ac609252d4440727509a5c616245a7072745517ca85275ec50e4430f221e9814457152650a931cac38e90187c80f1580a8f0a34607284e78c8814911141f9aec40a443cf0f443a4471c2248a93263ce4b0c30e5288b47afc20a502453e9e34c92107a2272d1700f01c4100f8610200a2274da04c800905a238e1c14a131e5ea54991951f35567600408a942a3c9ee4d0640250a048a9b2420b4d88aaf0f041f48449d10f44517cf82822a2c08e1c45569cfc4084934971529443132b519a54296252e4a4871ea4b0545800fce06407a22a53aa8efd69420425879b9afd894234654a132644146852454a91151c00e800200a059c580169f6c78a05889e30c9c1099409f830855545051d88ac5821aa32a507293900710b802a4457a4e44054e5e749cb05004cc981c80a901d0bece0a40ad1159d223f2eb800001d54e84154d4a40a5114220a30696245871c889848299a62812a39e898a0105d2182c2e487263de45c89422451fbc3248a142756aa105d61420129509850208a130a50400a1426453f10edb81285480e49b980579b46d2c8c8c8e884c366a6a3686474b47538ec91917674448d4cd2c3e1281e9952464653e270148f5e47f275344d381cc523a3232323898351343a3a9246464646469ad1d1d1d1e4705823a3a3cdc8c8e848ce2323a323a3a368148f1887353232323aa23818c5c5618d8c38e270148d220e6b64746464b446468c83513c8a381c45a3233ed2240e6b6494e160148fe2913ca238ece270148f8ef868e2b047474712873d3a621c8ee251c4618f220e39643888bb1b7bb0d2640352203595545fd9b268645b86dc6166f4c573d1c3f27171c16030d14b4265c2603b666641d1c8d6d868249241a3f71c81d6b53f452313d60bc3a2d1f1dc6adbb66d196f9b8cdb4659a8f86c8c1c32465ef075534411cfc789466223e1f602df3eec3a7e875ba1101b6e853b1defa3e33b301dffed4e9c010505d71b93a24bdbe0702b81728cf0a50d24b634c18b0eb896d8c0075e6030c517d80c5cfbfd67d1f7718ba58035c7b6a9f2c56f6c29e0766c2e7a4926b3e29935cfd5f0ad7de31a16c98e37dd865b5cc665a7530db3d3e5b90c8ccd9dc06556f462e8d19359a66745b24fe7226f94a80cdce68084381d2f8ff375aca1c6c91835be09dc9014519be6ec1194de52d5f4765375ccda6196bcf6334bab0b2e18c6a61257f69e3b5abfcee68dc3adb73c90157ab8d3a748a3e0f66015163666f16d8c044b7484f5ee4e1861cde78094f89e4818f7c6aa78a363c7b3b724c27a6b18612d93cc2f7865f67e824abede4b303c6a397fa3e4d4b35249f57ecbe8211dee990ee579c1f8c5b20bfbe85179cbcb8ac3acf80803838b67fede8659f12b6e85ace687409acfab70d5f140b8c5c31ea7f95184696eaead9b0ebb0e57cddf1d8e1968a36b982c3aa7370645978602893e82b3975bf321abe97770cebdde7cc2970fbf87d9f95c59d627df9442d3059ff0e7a53703cb86e28d0ebfa636210ed727f294be5bc51ae5a3116e39c390def589f20a4ece2f98e619f8f2092c9a2f9f18a3f932353b09862b74bae9d32b00e5cb4f700ee993fce904fe34473bead1204f97dd3bf9d3e99203e34fa17c7ce62d603b96ad62d99e5d3b9775d25bb275d15bc0e6002ed1b51d0b97e8cab263c9aefd86ed58b61bb66b334a1074142dffcabc27926879497f7aa67aacaa676096edd929687472d1ef0a69cfaad1c995d580643aa5607499c02ba8204949f94ee7a2fd0aeeb0938b7e3ef5ed27b480514ecdbd03959c509f1e0cf134dd43f00914eeddbdcb0e8cc2ddfb908ce0360d181eb52a3e95fa07765faf00b1a6aa0f5f8713068b479d036f38cf092f1aec8d2969e18416bdd31b73a28b1348b4e3c01b94e7799ed671e00d87026f38cffb10534ff3967877424c6bde9253cbaf8780756535cb565f47ae53e5a23dba2815485a57b90f35adaf8c5e3365d588d2677f196dcf2ad25dd991ee3255a3ad22dd452b172424ae2d472e7a7a2d935c7da28023fa1b6e1a7ddbebfb81d61bee943ec67ac3d17aa4d44c695dd9b31965bcf580dba8e5dbd8115d9a635398e9ad3bc2ed4e38a4e7394edde806d7b700ef4636bcf2be5ad5dccbbede0dde5760778ebb194ddfdff0c2e2aa36b0eb9bdd7a3660e1f1ee58bef3f87a36f078575fa87ff4ebddf02919d1f24dbcd1f2927a36d45cf50ef48e02b1d41c75ea3960c7bd67a00d3557d517ea38be2b748e76cff90bb5e35e7dd5bc4381eba201b174dff11c10cb8ed71c0788e5d5bde61a181ef552efe88d3531a6b9f59268a7609453d36ba09253d3e338c745cf01a8e3780ac4827aea2af085baea1ff842fdfb7a0ea8f98e0f310ef0957ace3bf0a8b99fce691d5dcf012fd5bdbf52a7f97a36d47c477d7df772402caf97f7ef3767170d88e5f5ead695025fafdfc7552016f0587e2c3efe32c282739ae3f86bf5578ebf6cfe51cf06d455a7de0ddf6beaebc8953a0ff0155da94a411b50f4aa8ae5bbea9cd6496f01dd77542cde51d7bcacda50732cdd535f812fefa86a8445c7736ee86a961def9eaa462797ea1e6874727dbb42b486439a0a24d4bb6a747275cfae0253ff402fba6288dfbede02befaeaee552ea85f91d52750e8b567577d88b91a0e69eda7ca1e0cf1f21a18853efb908c59765c555fd1552b969bfae2f197ea393781db3b30cb8ee75c07f85a5dc7b9ec08a55e3092b223943a3bedd94cc47ac5e8d8c317aec2afe73cc288304dc49a486a02a6b48492154c9461620c134b5d47c19bad2e3175f6d8a7d6c8f4aea191e9ecb2b9d753b26b7416002db1f9832b609d5debac395447c11bfa0dbca155496737f5d6eb2911524617b0ce5312002d31250b4612ea39f5155d2fb0e67c7f3dc7c7578d70eebd4735c2b942fb771c4474bc0a44ba4bf51b10e9aeee29d028751ce7518dbae35055a3d453f7be037c1db96a2a1754bd820a927e126e14126e2c09a48ea7df8eae158874d76b7bfd8574975177ef35d701be7254a3ae3b8ebf8c688ea322dd85ba4d45baaba61ad154a4bbbaca4555b32071a9deddab595267ce9c39e30a379604ac632df26858313ad61e3e303aa4547c6928bbd43f526a53d71df5f995409fdfbc05a09efa2781b2a48eaa48aa7a45f623d7bce615e0a8b34aeb13ad2ae0c8f5b10bf569f46daf08837d3f6cacded0a3cee52ed4b387533aab37b4daf0c58738747cfc4aa054c5e2bdfbfc168dc4facc73c06130180ce5d9d01d555fa87728af33d271b4f1d7020e7c69f953166b7a52ea258ab2cbde5d7a34c89f923a7a06f0e1eb18c1a2afb573270f8638a47f7abdd3b3f0a8e3f6cca3e1f430f693f8d376e2c7f074fef496645e122a617cac9997844a9278ee9cd6450f86989d8bde4df7ce4b349a28534ae9a5e93b641d07c6e6d68be1f41de7c052cb9f7ee37120d6cb6d5df76e33f180c1f3505d8c3d52a91b1f57f2ddf8e8e1c52f9ed7c3f3c01e28eaf5b8a9072a557373d383c7b8b9a19937cff19bd4d7c5689323cde4dcdc809e4ade78d771ef9c07de58c1ad9bd3735fcd131e3f7d48d77830f0f8e9d45b227f3a47d331773d703caf8a92cec7bbef4a52dfdef9e8de7d35a492f2c01e958a51df3c8261d781b1716e6e3ec43d62b397e4e6d2a3813b3b0eb8dd35e56e4e6fbcafee2db366c7e8c110bf7d48ee00c3d83cbcf30077ec00b7573a40da8559f43940e906b33c100c5fad02c3170d18be5060f88ab40bb7a234d3f451bac163343d977527709bf34ef54689fc93cf6b5f4f86cc0a6e79a7e73ccf3b7b8f20d14d6fac8b3038308603653800db40990d8cf990ee7af79f5e5fef9a7ade8798f33a6f49eccd74d44d47a14ecfa11a753aa5fe755d1525a713f7eda7d3e95b0db90e4c65e009dcfe50a813b89d42a14cde1296f778af462b9845ff83a71fb018fdf5b5c27e1004c175c5ed041ebc227bcc3c1aa89764634778e922ee534fc79a69c12c7af66630f5a98615e0170e462f4977a31f3afe603c97bb5ebfd10b8a24da80b9c0c3241a454802e67ad5c2e5ae7fabe1e49e85f1db43ba448ca0e927509e03c31fdaf4f56660b5e94a4e27503ed3825ba6d3902ed1f42690979845c3ae69fcd2d4093d2fc52f3462d1d4a6e9bf7f9b3e5a71fa8e5ff8e9f46c00ff4bcf86ff55ff0a9ed62759ac57c7481eddf00f37c255cbbd1161d615762dff188e5a7bd1bcac6111b792f055e5ca76f9a2f9ece1a481d5a151d3f0870ef71b4825a7b7af4703e52e50d07207daed87787a4964bdb2550da4975c6ae90cf945dc374b9aff0952c9e9f8ea8c3e0b6546f7a0f496b0ae501a4eb08e9764421ebdb12eba34b744b19352cff3f87e8b46b42f2dcf030cb96fbe2b34a453e7d143f86e3ea4c3d8371fe25da19fe61ec2977d4887dcdba3b7e4e621b7173bd630f65643cf83a148effa6c47fce8c170d41978d411bc790a0c799c716678ea3838383812c707ce39d9e1e07c88bfcf870f1fe762f7813e7c9cbd243e5229140a752e762910858452cbcb887a8fe7e8706c8f21ab5b5d666d3fea086e60181fef3d25dbd0b2c767951ad1738b9c9704f514b8fd81db9c8e2e7af12b2e7a06a0b1ca40cf6d6fd01eefd1a3470fda23e7393ddea3474e8f1e38471d0785e2b818050f308c7d73e9e3a8fb088734cec39bf3330f06f99bd83847ede0c17128d9060ac5a150600fd906a3eacd121def711def11720f1e37351c8aeb4193503d7a703d7a805cd7e33deacd92d5b9afce9dc301abccc6f90d8e4d8645eaebcd103b3bad4743cf8ea7bee3a97329903b0eb87554ea70a0dec3ca51a33431b17ba062af97845b82036ec79b6e401e1f923cc0ed1da03606b3e65760f68559f33a406a0418be4e60f8ea30c3e2b5399e7a8eba9961d67caae2009d7cb2434d4ccf6f66343136a0938f5b1b835b31c3a2e72358033af962675fb8950a6952cf73920cb754aad3e9de921f7d22c3add327c7cc614e672fc9e90c865bf1f3dcf77de76217c1efeb0df1776e773049d985318afe2ebd24df23b8ad02b7bfca6098351f953893299a3e4de0b9a3ce2238c4dcb9254222a97d7a634963901aa83786c4d20696bad51bdb4097a5c6c122298aeed11b4bd2d21a70a3717a631a28a3399a4e3bb71cd7adb7d5f0d5db23187e1d0f824877815d04eb0bbcd1c9b5ecc2b69de33a105c5704e7b32d9ace759dc92403e532f0267efba86f966ccf1ec35663186bf8eaf8a38e5e92218e601177bce6d1d0d3b1de2ce19e3af754a546306b729fe7346fc98fe6bc73dc454f49fcf6e829c91ebdc7ac2eb3622de2ee50e7d64bd5d0a853cfc070ebace33db08a12efdb95d0dcfb726bab611525db5557f27dbb57c3ec347599e5dd5b6ea96a4825b6f7ad86cbacec5f0aec2a15a3ed0d0fdc4681546273dc3d70bb0339eede125673f566c9e9a69faa24c3ac79d34f4b70cbf4791ee1f646cf9bc0a8c4ac798e870e5f761d74d0e18bfd44fbf6bcc6c3c9f764fb7628dbe8792e761c8687932f7e6e356aa00f5f177d9d552a90ee7a31164a5f380173194d325c84a00b17160bbc8e5c3b1762888bcb10d715f1d4086ec936b8157b89de0e6599e6303dcf5ee848323a1c865fd00963143d1fbd5406536314fc42fd3c8f1ec217bbf476b8f5fce4d64d762a90ee02bf3759b5e183c160301778eed8f3cb2f809f8fe02a0a89a7208f05124fbd193c1beac14bcf868367ef00ebd2a99fec898fde01d655eb267be2d73bc0bac0bab2277ed627a779d4838520c1c9ef4e1895b865baaccdc617957a7eb778d363353d82db290e49b92d4fe77607790269873a4d7e77e22cfbb6fd74daa84703ab4ff127b07b066a59f613486b763ad77ddb3c18bc9f2ec1ed1dc87d486edc9e03b73dfa3d0a0c63113773cb6d1741ef26f06689bce9d2744e825a0ce9b94ac547cfadd7d5f0d5dde90391eefabefcc2575f46a92fbb70fa6bd905f9eff2f1eb296175fc23c8bd03f91fb8ae147845772e825e0d67c777e076024d1e0cd9e325783a076e1f92a7edf343d2b4cd6011776cfacc4b429f816111478f869ea6a713ec217c51a9b7a352c7d9105390611324c1a9fd8d168cea8d65404b841d41a6776353cc7950e0e3ded8114bbdff38d6ce60383510e92eadd3b4fad26e24a48c2e602e4ddba6b2edc06a2e7651a9885b2ef9d1f2546c1f625ac3a3a60c26a15d2035abe5ace1abe719dc40a4bbb66e43ba2b7b56c3f9c2565fdb8db42fbbc0e70fc9f946cf73dc31b8dfc075ad47836cfe1053908aec59a9c83367ce9c690d5c7621dc9813687411b712b756d2d0d3b24625ae5434233ea29c1d815be696396ebb23c9c3e3f69c16d1041dcff10824ed8d1911c5342289ce7a63461cd15e6fcc883052ad3756c4133a9ecbba05a3139fa9375644998ee7b46ec118c5b7b122c674b8355ff6c68a30a34fbdb122963a7ee3169f5b2da35372dc0db5ebb367265031a9dde1dbd810647a7b7b7ba77c30180c881d62f9e8256135af50ec7d1148fda36516e339dacd15a2bd9735943eacba68958155a8ec99e617699d9d93de92545389675a7e7d42f9ed5cd6cd15e25e25f43306794d86b143f9790dbc5942cfe7fa649ebe336fc98fa675d7477e322b5e825c6aa3b05ac6cfe9d1b0665a9ec128ac9635ae0f3ff392c8478f0656cbbaebc375d3e22401b7cca5e67727dc58338b5b1b4332b361a247837cf6e825d967e0cd3e8b4f7cf141dc0a7158dce21f01c240d1c53fb736568416cd746b187bc52c3e9f822c66cdbad3716b574474e98d1181d46150f3bdded810663a4802ed7012f692ec308bcf2c66f2bdf936dcda4b707b63bd3a4734ca28b8087cf1cb71510660c0883508f3c520be48e54de56bae38e0518c46c2adc30d68720f7fe54fcf4d109c0f670c1bb3f8dc7a0bc85ec3a3963772492f89fc905cef06ed597d1db95ef2ec921af83a72c58e5b93bf12287b58247bd68e597c5a27b3f8377cdbe16cb6c1c8c5e50a2a489a3eba26286b187b9e82a1ecf90cd43e2427b8ad81ec5acf06e98a1f62096e67e0ba26036d2e36e2a3c90c0306ccd3e370ab48b6dc9a8245b295c4f80916c99e31ee80db73935bdbdc6df492c87391ced3cb4b2f89ac145c66499039d949303ec91147e6c26df86d0c894cd3de181252afaa8d38eb567fe00cf0312bc5c5057ceb11e0a8e7ac310ee0052b05c044a2b1bc7caedbb9d8ed9f6ca562d4fb901fb72eb3f68b3121dae88d09e1c51442a9e5e4b838bd021cf5322b9c7dd491935e12ca5e018e5a76193d38bd1862671fe28d5b3730c4ce9e6dc75330f6ee4c19590ace00313e01bb8d18178605faba9ecda576550a20c20cb3b6b3f3b96ecc92979f34e3cb0ff172eb0606bebccc1e3fb915d23f91e7967cf68d5bdc59d7433cc44731c630dc2ee0e35242cc021ccdcf73a9025031eaf9f9f88d3deb04b86529a53c7f4e29a724e2965b1be3f246cbef7e412e3af171bd312e4974fca2b8dde246cf67400b060673ed61bd9f5c6a722bfb3e4befb95d21d439eea83b0adcf5e96ac825a1c21d0357436eeef11d181a75171b155a316aeef3a7ef0a998e5a1f540db9cb7b3781bb3e5e0dbbcbed0852d1ce9c3933859eefc0f0a8b3af0743ecf921e6c0ed13f864d62bb1b32be1ce2ebd25f35cc3a3e66f738f60183b5e7a3064479d036f9474f462e8beeb133f2487b803a9c433bd1d05de78316837d530b6e927308c7d3a978161ececdea747c37cf6f592741f626e89be8d71d1d2f3d9b9e5b85b2f06ef5c0d677392af6c8d3aaa464fc9ecec1cb71eaa86478d7a075ed9ba3b0786b1b9ef0a7535ec0e45a8777db8731f921c28cfc58e03e5d743552ab2b327c9ee8161b6643e5b30b0209a3b7d1fbd18e299eeeaae4fc8552af14c73a75f4f866daeee02315d9ec06d13189f81db1b1861eb43af81213f03b7d7e30ff1912966417097405b52ff57fb961ccfeec3dfb2e3a80791df3244fcea42be1268cb8efaa2b9e4f12d39e7bedf24d096215b747cc7f72b09b425477de5782af51daaa7c0e7f82b477da976bee3af1cef9efa16595fdbf6afbebe6bf22f595f5b1d22feb5ddc755f7beff24d096537d9dce7c6e477dd56b3aff71d6bee32ffe8efaa2f45f7d9dfefdb5e3dcb7ccf3a82f1b9bd3d497bca9be4c07b9bfc073f525e55fdc6f5ef37defea8bd29fea4bd6578f9ffec97f7f75ffea8bd6fc45df7315fefaae427dd9d81c477de1b8a6dd477dd9f8380e0dc755f84bbb0af5e5c3247dfc65daa943b06aeaabe63870fca606078e7a9dd3e93e7a9c4edcc904e2d89143253f8d4da71c72e370d014483bde76a852bc81da27bf938683a35d8e1d393630a5a2279349729f8643d57d394cdd0e95c61b9f5220c521298e1d72fbb1c2c7710ae4ce824a85d38f1f353b3869eae9f98bdfd363ca91a39ef56d67752bacf0d7e92bac006a5aeaf53ddde93d94c5fa0b3ceb076b4be1f85ebfc20ed557380b7fe5380b3d3d7f71ef51a1e72cfc65a23f7e70e0eb5914c759fce3c78f15fed2be427dfdf8a192674105157afcd5bd87e9f53d3ede4327070beff96bc77b582c9eab505faceba8f09ebf4ed7f9ebab3bafa9e11dafffd1a3c7757cf8f8cd7be8bcc75faaf7d8fee340fe4a1d481c827316fe4a9d85b8731d160ee4af1cd7f96b7b0f0b07a71ea7e7387f81c7897f71c7b90a3737d7f1515f3deaabc7757ee3e3377f75bf897fd1ab70d65fdf5991e7c75fda7fd417cf8fd7fae2b90f1f67fd255fff321d487dadb0c277eaeba6be6e5e53739cfae2c1e337f585f39a9adffc85e337f5c5e3e63cfeeaead0f6c4ef9c059d8ee63dea8b477df1788f77ddeb5fa7d7faa2d179fd8bab53b6279ee62cfc75aa3f6c4f3c0bf5b5421d22f67057a1be4ea7dbd497cd3915cefd65aa3770f575e28e535fa777a6777f81b586ed89c7f90b4795617be259f5f503c779eaabd657bd0ae077ea4be7aff5b544d0f2042fce8860091b407281620891441850404208b688e1da027e6847ddb6271eacafd7e7a8bb3df1397565cfde478f1dc7a9af9bd579d4d7d10b5250842531535401c60b5c3b8e9408c20c24bae0c113c434e1daf197aaaeb6277e477dadeaca1e213564f5fe95aadff6c4eba89becd9e7f84b561eb6273e477de1a84344d716d56dea2bf59a3ac410d716799afa52a19eaaaf2329a8900215529871c3084970c93da34c0aa0404209335d3cc1b5453ef5d75783b6273e555fa8bab227480d837affd2ea90ed89f7ea4af6ec5f5c71d89ef8aebeb83a04cbb525fba9be8eb0408b2398c00d1d942181cb5487b86b4b0e174d50410ccce0028522b8b6f0b5bfb6cada9e78adbeb2bab2c7a786437affa2f5db133feb277bf6fb2369d4c33e67b6c096b44471818f7f6eb1b815ce8f5bf23bdc8a2d6359c6b47cc72dd9b20fc4aeb0eb706bf9e5d67e4756e136227d1bcba2d41b0bc28d0e532dcf42cfa265d1a60d5dcab96a69264df0d2fbeb0a1b6e0ef20d8f7373751eb1661d5fc19a7f1dfc98f4ea7572e03c07bf86916c5cf34bb32c23bf34ef388acf3af5e5bdd683f5f5ddd4d710c79d477dcdd8aabea68efa92af32f65a1b9bd3d4d75c924b3975269d56dfea6bfaa8738cec31fd33f5c0a9af2b6cf0749c4f57299dc6347f934acd386ee2f36feacb3b8f8a83ece1e7e0d7d4d710d9c3d7ea8b0b1097f6aebee61759467e913dfc6547e5325fca569e8e9ad57c7f71ea2f3e4a2635272dc174d457f61c35d2d4d714d9c34ab287c7c81e56923dac84a3f2981d534689da643595e62fd35f48445c273e8dd1d497e9aa1a635f7dc52fa9fa8aa8fa9249b287bbca4bb0f86575008327244dce545f5ab874ae539d7c2422ae595fa61ac7c81efe7cc6a7f5c55be558cca030032780a1021c00119762172048218432a8c8a219710db038c106c4784309d7acb14c19a5b994b434631316934af38b2cf3a58cfc9255da658c541aa3442b4d9249f3212f6967ce4c810765dc4089a4592912c7e211766006109a0863866b5d9273f0811db440882f8ce05a97ac14267b981fcdf8e2430a6ba64bbd3a46e051e41c55701b1197961dd7c2a573cdec5c562e7a792e362ed68284e3d282ba162e412ed3a93b17968b3b971dd7a6694162b9b47009727dd7c265888be65c582ed5b9ac5c5ab4a02a97952b752e9f0b754f0b1796abe65ab86c2e9b6bd1c2e573e5b8162e77adce65e5d2711c5ab8dc35f7275ecbae0bf15ab2683b2a9721ae6c7fe2b9b05c74bf027dec4fbc0eb0c7fec4e70071f6271e07c8637fe26dc09bfd89af0173f6279e0644ed4fbc0af4f627fe0353fb139f026bf6271e05d2ec4fbc077edb427c07e6d89f780ec4b13ff127d0667fe24da08efd89dfc0d5b610af81dafec46760b73ff114e4f6277e82a7fd8997e0b63ff10c9af627de6815e506b86c9ecb74d12191e5a4da663ad1ef3b2aa594ac9d1d5c57b207a7878f9c2967e43132e9f5cdd3bc84b58cc15a1eb6ff7cc75fb8d8b7fa92b018c352f5b55d1bd3923e2bd3f2f3afaebeb80c7f9932e433a1945a6ee7bed3524b8f935a761f2fbd38e915975e1b936a78b112a7e23119adaf58e6f14b6a639d8499621ac326ec8b75434c65b42ff32be37d315de3b963beaf8b7bc84aded7b57d95e8c30ff570bf543daa6e4b5e57575cfd38c954bfadae968c6a5c5add5c597d4c5ad7ac2b7b58a68e229fbee076278c65b66b19fdfe12687e4fc14f705e035f38be812f1d37aa71699f7f5d614377edaf8c7bf657d29ca7a8cfd8e67dfb6b658f1235fdf4d715367c37ed189a3a8d4b3fd51cc75f57d840f39a3127d54f7f6d191cf5a5858b97e33ab4202171e13897e87a5d6183cd71fca5a3be72542ed18543dea64698ec91afa9af3846f6c8d3542ed175aaaf21aa3a45f6c89bea0b07d923ff552ed145eb2b2ec91ef954fd0b55b9449757a3d2abab5ca22babaf1a648f3c576392eca14b33a9a992e9618ccda5ec3b6688b6654e0fe39813fdbab2871b3355a31ad7fcbab48711e6daa40666200557b2277e82973d31da1c45d696b8dd09b5322dbf49a0f00b57e14356c7be2af4d9f727d4a5c974aa583cee7c3a71158ba9eb2a9653eab1ebd84725b616667de4d7b5a5fb0ba90557aa62e150154ba76566323312c8ab5b3caf6666648f7c57b7749df61846abe1b9ba85e3eac6620d57a7bae5f42cdbb79cea66217be4670dbf0e3333a6bac5f42c5bc592259e715d31bfae2da61abfc81e0dd6b1af4a767e14f37909e365633608d359b614bd7891405b7d6d5f2490b75258ca4202755b4c02719f7c0c23814ea68a659545562c973df148cab8e429d8b16f9edb1a462af31dfb60d8f5097d427ea88589918c2f92f1694a999950531213bd70875a182d8c048a5e648ffcb67ddf6ad5a116a6a5ec09df61f402d394b4309ad2aed161b69499c99600d06106cbbc6430fa858aa15f6c3a9c6328d21c73ea7026cd2c9268873226c9c82e32c66564973065580916c60b2b99f112463343222cdc7ac50c09b79651cc17cbf862f3c3e845db8e28a38ce076087165916366ce1e4102e2ca22dfe0b14790a68b8f2075387b04c9a6c71e41c2f1b1479076b49a3da2050900ae2ccce7126df6089286638f20a572ec11241d3af60852ce6a8f20fd70c9faca8ed4826b8f6841f27165916660f25c326f8f20316a8f209d527b0489e6db23483c547b04a9d2ec11a4155c5c5ff4482d647b444b16cd8534c4c5e7225d7b0489bae41124cfb4479070b8e411a41e2e7904490597ac2f79a416e21e41da788f207d728f20ad5c7c04e9ae23482c171fe1fa9a37aa71691a67995657947eb2be905a70c953a4165caf1ad7acaf2c521ad5b8905a7071fd8db238635c152977e72eb3180728051cb9f858e4b7c82a0412179fab11c773f18b9bad0438eafd2bbaacb072d4fb2b6a6c61bfa8fd99f34787477433ea0502b89d820b10520617675cfb0952798232aeadc2168ca4f9d0d4f2c77e3d07fce8a80320a00ccc4f701fbd2537d9570bd2e6da6eba06226d2eed0b86463630ccb5dd0472097269374aa252539ad51ab46fdbf0b9b62cab54524d2b15db51cb2109eb4d0357f6845b53e62420262688345df312b4e173cdef4a59a9a49a8a39c1953dcb1c9b5042ca1996104114c204d012df5c29026ef74c022b3a7684b55c1ab8e7655d303ceaa5125b7e376e808b1fe8c01757b2b067904aec573c8a1c008e5702690fbf4cab2b7be457f64cedd9e5794b72e300d3b3dbb05ddb4eab0da667f57505bda95ef6c82a3664dfeaeb933df2f30858d74af6d04c9e5cebd2d69581eb9a60c86ab025cb27b8d8219453cbcfcb1aced34a4536bd18b6a940baeb0a79e92d31f5ac610d2da5a9b98647a1027a63121c95d87cce4a6c537d126fba0ac6e890abc4f5a12fda9aca9ee97e3a71f491c3ecf42670cf2dd17cf735bae34cbc1e87a625f3dda3b7a4bbe9dceca2178376ce04769fde92edf1b1eefac490abe91c18b2e9cad654e4146061513cca221361c6f09b4a69b40a96e8ec72eb6ec021056166d3f81a6e20238f866471eb26fb90a6af815be1ce0ddc0a5938f0c0ad70b5610a30845b1bc3c24bd39d0ebb0e378c4e878bc672eb1d6e1b40d4159ec61863163766dff8a2ac37f60eb768957d51b68449f333fe462031c993cc2bc0573e460c74f617162c469b11c3c097a6bad5507e1f663064974bb28a0564ceea9278fe8a5bdae9713e9b580a943a03697fdcdaec540a539842af776681ec33309ee9f5600b64190644c7b32c63aa823b92fafee396f7bdcd0ae73b2c6e653fea13e85d038f38ee74f90ebcd16eba97d43305b09e379d5ba2d8826f632818d3d9977ade4341999ee7ce9dba0c9ce7b8a3286f3281948b1de5a8c8e652dc9fdc2cd94ebf6da6eacd70da4ca84d3b673a7ddb74aa306837ddd45d036367118613bde974a261fe549fd087f3568e3abbe6d1f024fbfc56c3ec5d353d8b9da540c95c767f5c5a52e7b2fb933dba8664079e2a95977c0a5c17c77529705d27703b3c6a0d5c62aa4fbccf244de6f31e7297537a4ab86735f4ced51beedbe9d953e2d51bae2ed19e9dbe69db136d06a96da61a7260d8fdc6f4bd315518e8b973f50476dc4dd993d3b91ac60e331ab8baeb53a9c433a64b2ef6c904c68e447ac3235b178d3010bd470d09e296348a4727ee86e038198327c3d210b9149096d3b55c2139ce046e8c99e34e762670c77805981e0d725dccd1e5960438d2406f340cd9320618ea2dd5dab94851a15112cb30d4dbd98b214a533152d2754e6b1d7b04386aa2b773b29b1e0c474a4d0587c88c4285464abb40c2ed994780a318648fd7b6eeb4d530ae8b337527705d26705dec29e167353c8546fde54b67ffc0cb2a6b283d1a2615d94fa456b31ae593f89b25a1059abfbd5e12ce989af9d2458629811bf159b69a96659a96655566670c149532b0cad6746620ad38e066311e818b54d504b7e06caf435da14979ddadd0eeeed230614abe2455b6652f3f7af2ae4fa63ddb9f5648abe1a63d5eabf1a78ef4cbd935eefd8906ae9ed68723acf9bb3ecc4034addf0f2d32175c6ce28be7b88d30f8e6a7d411ac4d2317d92b407706e33bf08917033f3e721b53f06dac045ffac485983e75778e935df462d86efa13e618bcf1d63b83dc9ed6cd5d9fd4e919443d0586fc10063eaa86913d18f8df87e407de2ce1a3ce4755f066c9a9f9a7e6ca637da4769640eb9267307594e6d170eaee3c56288cddddac8f7c5779a03ec411d6ebc5106154a83ec49f0452d599aae1d6d99ff04f0ff95b80a3189c4e43cc4d969e12fee9fb04b53ef2a9f591b09eeb235fdf8401d1de3913e7d5d4777d4c5eea281318cae68ee25e9435ca03aba052dd8738d51de5753781616c0e08574d3ff52e5569f56356566bd36a73146b4a70b1891280a09b284112bd3e2ab091edf5428423cca0144f90526e8c746bf8436f3c423e9c0bc6aa01cff3bceedc3dcff3bcef8ec75a388ee3388ee3be3b9c144c269369bb7693c964327d774c4dd0344dd334ed8b4800b9e831189bbfdefad08d59f4f1886f36a5f7562809af1019d30a25915f22dcc62d9fd6dab994bc94524a29378b6163c6ad37ab04c36dc158c4b7b11268697a5696813164533b53914aa550f7de9d7b2a954aa57ac071dce9a66fd7ce711cc78dc1ed0261bd0563586f7db46e7db4473016f165a7e1d69af66e85f8dabd15dad6cee08e66036ee50ea9d91d1cfdb31a76cb272318c2ad25e53ee5d45c8d72e2900f2536a1698d72e2cf1a9ebe9dfa8a359af6e9dcec38eb535d6651309c9d51e99dc04869a56c3a831c8564e766cc98cf25e18d35d770a8f99ca9a320fdc67a635744d1cb45739dccca321318ce0d0cb596d7c0104ad7f45992594d9112c129d13ecfcd6bf39ae9a6f0a8b38dd3a6a681db13d4ce317f7df6c936c3f6eca61a1eb569e3b618e3fc16638cdf267f8b317e63136fbcf1237fa618bc998f9f67f0667e82320c698f1c7771b207039578a64a513745090e0458200b0606dbce6d60387b5e9a6a152a5b0606eb229ade2e6551d7db4f5f8f862927b00a1524cdeb335f94f5e96195d38bb2254cdaf41c2a483aaecffc069aea2e90e9c1409fd5096e1bd8fb2ab36e9a1b929e93e7a247c108c6c5c973dc516f31e46f1294ac5d72a5f25a4945768cdaa9e068e736666da66b60a84d666d37691a95f846cf67a64b3094bff235940903a2c3ed54b4378038d35736fa649ecf5dab1bd8690fb7ad6a0fb753d9b404014457b99275fc15adad1cb5f621a981d937707bc1908a56a6e9b30ff156c43d6fba51126349f8191843edd919bcc2dbb9e9b7af07039557cf2434945103afed23f824560d94bcd5307eaf5d89ecd0ca51c3b0b33e91f5c99e1f332370455fbc92ecd933d37e4d5fd34fdf054fe067caaec4ceb258bf3ecbc54e7b778acd9d3ea44d8f5e9258c3989d3b759ab6243b5753a6472f32b17deb8a597b538dbdd94d3fd5f0a84ff1a60fc9c87127d3b675bc09d4b205c2a77381300cb363a5f26a7913287981f043021cc5a0e302e113e02806bd0b8467cb53912dbf26308ae053f231b8217dfc06dec44a25b65671c0c060cd4273b1db409fde675b7034a9e729fd8f0ee9f636d10a5519d254ec8f36088fdfad59d5c2c5c7951da9c66504b76c7a1e4ad30fe9d0a6b5648f5dbe301ad1f3e1b2d1f3affd277b98b0040b9a60c1973130982b7be432d8244106a008e20918b8b470f171c523d5b8f65ab8b04b3bd20baef80c447ac1f5bac286f8570db2673e2ebdaeb06148f6cc6bf575850dd9637d5df6cc6775c8ca9e582c3edca56d821036a0c2086654e1831cbce73b08dfba8e3ae0050c9860820b312858826b5dfb70880d87c15cd1086efde8199398a7135c0cc1277b725c9184154d68a1509e7f9a9fd3a8e5e31a3d2b0db379ad46c96a981d8a3c4199a635ca4359c35776098291d4197d68d461bca48772b4446b51168ca4a60f2fd09956a3ec1a4db32801d0126b5a97bc6c21a48c2e6af843cbc7fd74c01053f1da1ba5ce9c39b384cb4405acc7115224a154450525138481154dd9359a0a989032ba805d098096d80fb00523c9b50f2fd034885dc365aab05dc3b50f5f4d6b182f5748b8311294a08b625301cba1c609b20c0106736d7ded8da230e1040f80780306736d1516cec7704a4b1b543e50638a296881c15c33d43497809bdf9db94b35d870c3a2d1f29c9628cf0227b3e46f8842e46d60967c0dccaa41029d7be425b3e42ef1b04b2d2fa17caa70c7b47cf82e031f8d70cbefc8b33819576e8f3df94684b9993501ebda3e2f7bce5f6085e6e5719040a7cbf32081a24b7e8a04f22eff4334a25d9ea3bf991e78334fe0cd3c9da26b823570d7e834fd26eb7e93d51aba4d3677aa3774db4cdb844184d12c384a3f6e6ddf7f85342514e4be5e0cf4f334f272b35b2f49bc2909ade151531b908261ec2bb1c37825f69ce778e3f2449dfe245e5ebea867d881374a768de64ecddd70eb29d1c83457370a09b969e495a88d42e8e504a174327b569ff47edc1ad27b1338a47fb2e9281895bef815b336fb697bf4a86c8db33efb0dfc98b5d7c0afcfd6adb3cebadf7c83a3df9d702be3812fdc62316e699fdfc0708b3fbf85e1d6f6f970f3a2e7b928beb40abdb128909a935108117b9e5270ca37e5b76f60625b01e8b7ba858941b69804da92b8677e6eb1271ffd16db9298351fe5a3f54609c3602d6f0483f5931ae2e82b7389869542ac6985427f23c336dd4ebba8978846b6e7a6d4f3dc9cecb4cc4e03b7a49e5b5234c23dbf25c1ad8d450144cf6f5db8257b9e8b92a3443178c3a7020989cbf4d8148cad8137095897e937fc8d4501eb8d4171a695c8a6974d432322ac10fdfcb62481b8cf6f62245074cd6f632410eaf31b996864fb3c97fd86be025e740570d028f0863ebad8c58137b41e603b97e8e2fee8a2600ddd3730acc0179d7d362739ae9ca8f0a86f6a38fd46fb6cd93707f07ea3d51abc9a65cf98317d3677ec2196dd8159a28bab3759a5923ad354201171999e2575660a2e2ed165ba96ed54b36867ce9ce15eaf497c046110bf95e9d531ca8e286b64b4d1e1d6f15b156b747cc73fd89aad0fc7fae4d90acd31554451c51249324ae7e5334a29373beacd249964d682dbce5628da78b111a0d136dd08b068f9ef5ed89d5b1248bb3c101c1c66d9acd6bbd3f2dace3884c95e12a619cdd6474ac1450e7cf3f271031f9f86db22b40db3621fc5e8829c13a48f51a354a3944e9c3884c9f4138c3d9f559c39c1d893990c2e7e5ba158a3a841db304b7e5b21f97d2413d76725d765d626639442c66d85e8f71c95516e97d96576a95d7ecbe465762925a78138ccca380893824bb3c7ec37f3b1b96f261bd9a956719895d5b9adcfc69ee08ee41c70fcb9420cc6bcfca06d628d40e8e3ac4f8c6738866d2af10c8de05c9f98855bfab942d9f7748522488459f4637a08b3e8d70b719a5e86dd105d87c340a7d31f59a1afcf767a20dcfa98b59d3ea867fb0e036ddcb385371d03d970cf76fad5a243224d7fb342b12967027198b55da631c4df2eefc54baf98b5456ffb8d76a369df981731bd312f67b40db3b66754669b2a0eb3b6ba1a48d7677d36f6c623cad204dc52553c0ca766b99239d2c065e7b2e82539bdf34ecf71200c471d763f695fedfb0e0cb5c773b137e6658c5d1f0f0690ef81da4d606c8edbc0edde76dc1a6edf40de1a9ee3ae034ffd132739ef5bc3ad7fd0120410b0aed275958a95a3363dac4014a470a6adec929724886853ed6ab8dc72b4db40e63eb9acdbc0f3a5b7c4abe10fcd7dbbbaa71e0ca7eea7ed27a7ef562aaf0e659fbe9e92adb9fa643b87dbe312fe762af14c9b3e2487d8046e6fdb19b8e0e4e27c01b717f8e6c355d3efdca227c2ad95172b9a1e674853facf664fe937a6852bfa766bb8a5aa8e593657b8d14c05d291cb508317a459c798a7d7e85ba3bfc0973d486b859b0cdc0a3f1cb8a5fd08b7deda6f18a3b543805bb2b515d31a18d4da975b6087ebc510e3700c6a4bc410b4c6bde2d6c6a8e8c271258a4a3cd3fb714b5ebb0db7e635edda7747ab4fb68cf422a5e4cbe3acd0b67c8f1582815b1427e5630d5ab6605163cb52d2961aa8b1866b65cf961603051cf082cc139cc0856b650f7f59352bb358f1e1d621ab977bc2afa750eab9157df4933f845bfc49f146f36d98d57ca4597edd6eaaac76cca2b5eb8d4d9144cf6ac3acc851e06213df64d6c6afea6b09e42bd3f23612a7e51fc0c52d3e36416f6c0b2e9a9be88d69f144b37a632fc88192a22c653a3cd222ebd0072468d0fb54dce2935df4c6b248ea08a637b6022f1a486f4c056e442f86d317639a0a0ebd8fe1548689a66284eab0e8076aac9ae8b0c84b13dc0f3a2c2ae207b4432b3c50010b2e58ead04a164bbddf60b00983f59ee94dbd973be0a569195462cf7313a4b386317a4be4b9863228d3733e3605c3a39b25b48c9ea7657468e5a8e7294865cf9c39c347cdb44c73d1a5c1069be38e82b48c6630a7a317c39b3fc12b38622de2256ffe12f00983618c219e9154d61b25f1f231c4cb5ac475176d741137ad21ff4a6cf9a396604b106c2ee27a7f44233b81138ab03ead90d0a9f7bb46360a994fadd04a01288b3df1b3865b87d9101763accfbc377b8d0c71918315b23d6bc83534ea2bdc14a8123bde6473fab8aedb58fcf560e0e6c7c0cd1cc7651457fef2d5f4c6805044c7313e50c20719f001d298316e488e3b51374a642f2771cb447ce08330bd311f64d1eb0312b4a937e683273e36164c6f6fd017256002892ec840a28c335c14075720e14510471821082e1a7c7143726580db15279725551bb31eeeb0ea63d60dfdc6a6f046cb1b5a61889d49a2e7831848c6f84305b841072c9a596d4c181a415abc3230180ce6ba62636abcd1e12ea9e145878b4673cbde320a386209500143a0012a608831db4533171c0c47b1e5157084d2d1562a321c66f1571268e3ac2ef7ac54298088a43438353ab0a375b85cf4001688480d2bb57b01b4b9f6dc1e7d1b1333a655bdb132dae8f5c27c382194a0fe67f3312bda302bc665630b1b7cc006660c5103194c0604a14b14699c40074e08c335270f54d0f31db7b49e447aceb962c50f68734520dcc3925ca406dfaa01ebad37a6c6111dae3afc9624156662d0d1ab8034cca441268d35d8d41b4be38cde581a63a4116693698049c305d34c1a58f4c6d288a2c3d89b4608daeb8da571c4a46848010d24a03104347a804619687811ebd0f88246157d52f2d048a2eb8da181943273861bfdf5c6ce58438a30bdb133b2e870f69e61459f7a63673cd191a33963899a3392b0a160646fb8604616d101527c1b33a38dad3766c61abd3133c6e870ebfda2696fcc8c301ddf7dabc58c373a068de0dbd81963747c17e3eb3a805ba0efbdb50ea648ea556ca1570753c03a1e08b7b823918e5b871b106b5958a0550d1268ba76998fb4c2163a7e005c838f358408b742283b6d533b0ec1377b6366a4a0571288c53df1317b53130045187d07b42eca5ad6d668460900d0f29bfc3b49111c7c592fb7b2e6adcdf0926d6c8ee176871bb3466f6ccc181db2172dcff5c6c6d0a0d98ba69224462f3e34eae8842fbab13ef26154da317a633cd0a2c3f8844f1e6881a219d343243248b9da0a999140b18ced916779fa90d5d94321bd2b2444b281918af591d7c008c5b2e4658846a25214222fcf2b149f581ff9dd9e504856c345028bd6f05f7d5ce510be9b254ff8cb45286f46521771cf2bf301e8f596d09e55c28428a287c16045746bfe8683205e62074a104d1436e04050b70433efb43cb3c4c922096b40d991629ad019155c964d6e65d1867132117cd951154a3421082cb00111c29c717d1288a703496ce186185e9ac0c2956de716003a3b4b024d57f621b476cc9a67e2db9eef7667b20886e69c2b431007828ec42c0392d5d5764414c2f7d559ad0583e08b603a666d9c530cab8ff47e6885a6ac4f4c2d9121adfa3c94064405c6e68d66fbbe267c41aa8ebbd3f9c49db62f457342bde589e6f22c1aefc4ad040a5ac2172e98961f00b7ba6d6edb2e0f64bbfc10ee92935082fa74e92da189c2061be8d3a98d316b908100b3e29752ac15d17de9cd8e704f967537300b02ccca768dce5ec4055f18912212b7342fdcd2c0d81c05b7b866db4b44257a83045a6d9d984d4c11305f761924d08b7bb2675df6e84696fd066e75cf0e011cb8d5254332825ba7678f49d96312193ffb8b5bf15d7da3b36e677df8145c318b4df1095f7c78a4b3af186873657ff6ad66d7a89ccfbe3b597d12bfd0703fe79c73f78b4e90a19739f0c54309ea5dc934d8ec5aa869d9b5ec4fb48c6e966559986559f669e0c601c9d627d3b4a865f5f1e1bc4689accf3ecbaed52cab5bcd90f8bc70cb2dde5a2720ef7bce047212be09066606429d9347813bdcd2c0706b69bad6312b7aa00df7c45393f9b29b625866c9ba52eb6cd34e6770e7d65df8f5ace12a76df8e111f713c91c0edb6e6946c84f94995d845cc112ce2163fdccfddf0b3ee7a4a260c1665944270374a8a6687db43edd963a57d7aac8ff54609f7f858c31b25b168367dc83f6aaede28e9ce71efbacddbbca50decc00de4328e7bc6d56596090ccf69db766dab32cc01306bb3ec34639e40684f8e54f20ef7911727144edfb7002bf45a9f687ac8eaddc0c48744e404c24010e09e08b4dacf6d15c47d606cd662415a3d224d4df8584086706bbb96ba345d1b3ac2adedc8f6763a55283b4b70b2480210daf45903b3e253208ae5993a30b67703b74caacbd720af0237d9d36d120808f7c86b219196b18d2fdc303214a0061bc8f4e9a6cbdf7002e335309e879d63b21570360c04847be2379e5b9bfc46277ce1ce0908b74cf452d2cbeda6cb533d7df67aa65385d2f566dace52700bf4799f1dc7b4696f030315b10cdcc3f5a237888178e01ed351a96f00dca22b06ba41f6988064903da61731e3dbcec198a58559a60f71111e985ec405eb797cd3390f8c2a8e4bb4e9b1f9218649a01dee317ddbbef0abcb3b6d52c1a2e0469b1ebde01ac3c630dcbae16f8c81e196155a684338b008982fdc2902e6d31e8df0f16456c6d574bab5e906316e702a668e935e12ceab45b3190766f155e01acdffc2a486a0c02260768a80f9f81d08657df83248a03806f7986eda4c8f4c30991e61dc8a373d6a8948310b6e853b6df26205b7be700b0b6e85455a7ba7373d8ae19677d3e318dcea6e7a1ca34d5d8c15ca2eb5576773e0ec133899e5321d481003adcb04a64d5f718bdef44d02c99bfe49a019c79c4ad0f542e96e3029a57477f7457cf16210b13c05224098b505581f1b3a29a51f0476360065e7ccda775b125f7c18d473a565800b17640d7cb18a7cd16cfaad374a369c1d6ead321b4a50f3a12c989e3feaa33e6a2ed29ecba736c1b458cf9a358361d72b969366e16c1a190966c658ea78f6a2e3105fd8f56a1523912140e239a8e3975d42daa5950c412dcf5abeb06b0eea3965aacb17768d624f7693a3a7cca475365dcb6af3e1c469b37528df31cba667cffe98359bdbcd865bb37bb7a5c32cc0510d67cbca427cf147cda912ecb61d4bf9294f1f9f5039ebc6290c616d5a87524a29b3169a8febd3e17c71d29c5b6b5e763f915356c91a0476b240d9e9f92c725b87c383a03daf7949e6b729a59cc2407cf145b3e70e0eb3966a9aa669332c624e0b6ed66de92efd3efec9f225fdc4a1a7200e4d3f410ee28b9f45b4299db34eb9c53e7bdc6c9fed9f649359f24372ca6da50d77e05b1a2311b838679cbbf16836e8978242f8fe84d6d91b13814607e98d95614573a970c5e7d6c603c76b60b89aefc068f2c0e51e6aaadae9cde94f4cdf7e327dbba96e2cad863c347d56b7860ef75056cdff4e2c9562290ef6c58e33c48e5fca5862074b3bd0d21c173bba24360e1df7c21cdff1158ff36836be27aad3702a107513f8c5abdc68d5bb35c818438ee146cb31da6829b36e3d25a87f57729afbf7cca381e62ad54d60b86a93c994aa04386a4ef57d99f5fdbbea26d5516068f33d01723ca502555fcf00b1737c8855377d3ef366409de62a30e4a155cfbc194cffbe0c64f370b379f8b54dbd21c051dbfc4bfd3b8d95a34e551f72daa6de707f92bacdb9d46d9e4adda6b25235e4816553431e9ae634577d48aac0d0e699ead49321768ed38021cd51b5d9f8682a0d39aeaa4f723c559f74577d886d4028ab56fd03439a0a65d539aaa98650563cc070f6ead25be229e9ea6496574fafaae18ebacc5a7d326bf5cdf0d530b68ed77cbd1974bc4607181a758d0a945d738e7626b0e6d19b21a4b90a87b691b2a33743f89de62ad93a6a88a56b4e4fbd19b86b6e9ae13b4db5a9217d91ec9a87935926138e1a1e754dc5718ae3436c0271a8541554351b5f9154fd04164515c3be9b25b1691e9be65ff5cef54649fcf7f8af86a7fec027dcb773d153726a9aad0c237630a6b9e8cdc07ddb408e0644c56f6480e9eddcb9e5689702c355a76e03863cb48d4d7f5f6ee9f84aa5faf79b550dbffe9ee33ac065568e1a7ead7a0da8fa32d0cd73e4a8e1d6aa1ae2b8cd6b7e032ef7d4d470ebaf86dd531f9229706f0366c7018650564d732536cda7a7849be6d15b124ff3ad86dc69684054f40870440372b307034d0d63d31cf51458f30fb4b90a0c57adba4cfdc9779ba7bedbfcbb4d5d66a99e7a4d557d48d280dc2a30ecae35ea3420f70dcd9fa8fe9da63699ef4689eadf557599f555efa9ea29e146d527dc63d85528abe6eaf6fd3a24c243120a6b28abbe123b1e47c7af38c5846c630d08cc33cb27720a0e8da663628c310ee91a9845c5c418a31766ca340f5acff315ad9ff063dd3a49030e29c639c323de18679669ad6519a5fbf821e6ef8ce71863e448797e829c446a91720007b461d68a594af8429c1f191a800cdc0aa1ac5a7e2651b03bf3c96f18cde7ee03cb7ce1cf0c052e6ada762a5a6b463db32ed9e9cc42074be420a9a5a7647a200aa1976790ce24a2107ef492d0ca1e8842f812dc76c54b3e389915199c493f343d83f21c7734bb30eb888274562a5a4b593b666d31fecc8a59166e7722123e286ba63766c569c3ad048c11a659e88de9c08ae652bca56876f679fea1b7d5da744e1a9254b4de8c5aa39fda331085ecb34f709f816c441432b73292066e739cc4acade927c849e090a4a235a5d97c0652baf121d7ed5b9453e07627bcdcb4ae77a2a20d0e17c32f5c3557ab4c28ddbdc81e58d2344dd31e14b4f37daa237b1b9008f7f06b401af0a4ed2ca97842c149e1e353ba4b1d9332f06949f18ddee42e691915a60ff19e977bc2ad358e4908fa3e551130615704ccc7ddf40fdcb4a845ed282d6af7b477da230a0c5721ab8774ac55669fc0f05d34bfc848e08ec6d0bc686dd36078a27dbb06864b669ed36ab8645a7bf8a11eda780f57bd38340fdfbb4305d25d381eb26a1e02e97d38a4379cfd5181e3aa1ab00898cf74d291c34655736669e7d380b306c4398141ccd2ae0364e51842036e9f584324908dea7c9604c271be0a5c314bfb070e314b7b0a1c00b3b4a3401b98a5dd036f6096f60e8400b3b473200eeba31d07b8718fb6124806eed1cebbd41a0cdca72d8452350a4e1aa66870bb33bf44b37a634b6eb41863f48fde981856740e6062acd19cec681453492ec15d5ad33aab4974e5246669217d16cece1e72cdb44b0f86d8279b673544d13cdcbeaa67af5906aa79769b1a16cd4d02d55c16c936ddfbf71a70b927751a70b9e70b8b6473cf52a6d3371b1faa16cdd6ba83cdc6b7d522d95a77afc6f0247eb6569f74559b61d16c6ee32b921dbffcad61fc42db549ff035255c9fc4d3d62857e9a54703ed582bb7765a434eea2c034f52324bee304bb2ba556b8fdcdab34f1e1a4acf2984ef55873c6854626735caaa677d123fabfcfc102fadd1ef8d851184e62c3ef33c1a62493d9b5ba08f633d1b28ab61d16ced337ee91e462c3a6a9f9a119a764e9a5e122d3624a79784665996d4935b948d884616895b19e0246e694966ac359006ae93ccca485c6b153c723a3dab27d0548318892f0cda3616ebd5da275829277106a290f9a9512ab2292d83e2cf0b778408103091e5a46f7c479815631d29ddb64ddbe8b3ce8e749c603a7e88713ac618671db25bc3eff4ee16adfacca2aca659a6d5f0a8837af787ddbad0a6975959982028bbd43316e10b67470d15fed0cce5488ba737e677b8aee3c255d31a9e866a58d43284d2323d9cb29ddbc08e085f10b33ea0e553cd8e308b33133899a56535360581f404c35587abd5fcbe55c755f8eeeb59b7db704bfbf68d8156dbb6d5b941dfece8cd59a1ac7a0532aa20a3a5fc6c596fe48ffaabf9705504cc17ae1956fc0f122875d4c3cd7bf8759fd54c0d1228258f1ab37964be4eee929979b94b605d375ad6100aab23cc4bb2b2fec0ac08852f2ad123139bc4d0b8a465710a9f04c335c3140c174c371bdf82e93022f107beede1c7ffdc685395da4d6095d99c4623b8bd2d180d08d18599a6b3b9cff33b70b9c74c5c323197c165a6efb7cf3967d1909e6094a0e63ec130a86750cf4730dce9c87d192848f670500c0ae223beeefc30a853e76eb3f970563b3d51447ade9ba7a9bacc9aa7a3400f0c8734ad5566f3393094a1e9a597e454b98eab5076fa95451280e065b6095cd60c1ad3ab73848a530c7785f693ecdabbcec47135dc38300c6aee2f6e4a7345cd716038a43766993653f7ee0aed27f4f340d315bf31d0c660309816577c73e7be699d77fe32503cc2170269be693bea7c22dcdace50823af5ed747e0afc98c57d43811db3b87fe08a7bb87be06416770ada308b7bf4927075fb90dcbe659582b376f30afa1997f0853bf4bc4da99db36f91d20a25c8c80867ac60c98b61f21765c934ad4f66dd6a973103c1f159cc9a4066a6adb6ceeba8683584b2d3d969993599456b58447b7bb655d329b89324fb46c5a8e94625369ddde439e352d4aecf3cce3f66cd1a4209ea24eb456a83380429a1ac3ace56b2021955ec00ca6a367f8ae1b6865f6f77fa30fb66a010bef8ec3c39e9ebea3c0d8b66c7ef65c0452cbef85595e96cb8150e1912c4401f62a0bbf8321460856ae016377f7a914aae25d3928fc3e7f385acd0fc69ee7035a4c3aeb9095691af7265eb3d95f715aef79c04630cd69cec26e8a3b9d94d904773b49be0aab9ac9ba08e1ccd6ddd04713467ea2668d3dca99b604d735c37419ae6ba6e823fcd79dd04873487ea26d83597ea26786aeeeb26686a4ed54d306b8ea69b206daea69be06ccea6c3d14d909bcb61538323878e5aa5e86b21fb58ef969940848114d708b7f339dc1818b1ee76b825048c249a83f0faecf912e4f5d973d264667a45a02934328d0ccde4c586dac2e49de0947a82f33ad0c90d781b11a82f60a95313bea90495ca0734338d9a4e0c9b530d70c43a9ecbe1450b72e0b0a9a1517d2994d77127d3a6033524045c5ca3e3f6898f1f7d9abf5e7c69fee4ae11c0c5ef4e945f336474ecdbc7ba20958d1d591d953e1fc584bb346bc8923564f51823be984edfb69b4e9563646ccad0b0991a55163695bbc4ceadbc2ff59540ac146b662fb287b3903d5e207518cbec524fa42454974ee9a45446f59951fd3046a582915121a990caa892544966be2eb2879f458713298c14c270e1502da95435626a6a4ef31c39eab218078eba32d8d8d0d4a149435353836a9ee5ab79548a4a613892d9e298d3b7a8a218edebdaa28a4b1ad8b12f56ed31cc6743b314c38859da92e5bbaabee6355b54cff2d9542c352a558d64ac1c35c7d6875335fcbc1aae3eae863fd57065aa216bab4a1c5b336674d0022742c04612a0782d11b6641143461a3070bdf8096e4c0113023602d74b8291451044007bc20b5c2f0ec3a50d27106184332e19546490a4a04599a42e133695a6171920c1c5ba41970e27120e929a63ac301268c892040a12238186788c048a359f151b237b964c56c3ad0cade1870333b2677bc80b98ecd9fe82629a2db8dd0967acf746352ed55559d0d864f1c5e65f640fd3dca6a66251d5d985662ac52533a90fa9059a313bae834ccd55aa8a85c771dc2655b764497dcb0ae7aaabcedf0d8ec683c7cdf6954035b5e63c4c5f09b4a3c6af6b55b1a86a2a96efa7c7a4d87d2275de27cc435d9a913d66d84c1996316dbbcc62335d7e319d2ebd9c3e64db988c9463e6e9a598ef5952dcd7b5e5ab2b7bb2cbec1c38bbc4a52e4bcde71ec625321d32accc901bc4d88c04622fb287bfa16ab87d5e0dbf5557cd5433cc55f612615ea298ed5b5a4533cd0fd3252e7551ead2a54c98e632cd4bbb2481be94989ad3acc6d05c870e32aae7c8f12c925f58bee3c0f12cb26249dda6be6cce9f5d24d06b556b903df135e74f2509f4d251e369feca515f386afcf7974d8d4f9d3f4402d55496a419126e2a530dbf2f5c694322ade1b7ea708b97357c875ba38e3c56b1c1ed0ec79929135de18ae9fd0646d78ee9dda50ea598e945f6c036a42f693f6112e84aab2e5f12d2b67d52cc94528af9b22481d62566cc24d33cbd7c19239124d0962cf45bb46b5e4c9c1725cea4d4e55b5e482db84ce7ba2449a02dd9b350ad4aa48a257b566592440a2552f3a5981db391f9caaca41909c425ba2e7bf8524c128f21d325298be673920492cc5d9a3f2966c5493bf842f925aeebabf0eb188b64c89489b12fd14c982c604b3146121346f6f06338bd347f7a91406c050dd460230419b8818b3fbf4820d90128bc4145154f74e1e2cf2c2450e452040fce9839b303177fc624d04f0d40f083335cc0b8f8338c048a2c7802079098b001988bbb343fec22815ed1753a9bceac9d1fb398c2f17f9040a72a83ec61315cfc089340a65a83ec617e449240f586f36352c5a2551cb0d0ca83ece1f386e1924831ebc397629a2fa564c9dfa42bfb3257b78aae9abb903ad76a7be2797be22ca31d49fedcf881ea380a96e329a45e8ae3df529bab685ef33076e9fd72cfe66acbcec91a24d00b751da9d473d4e845ccf71d478d5fc6a82a97e852dda6d6542ed115c9d0ec55601cf381510c189740560d3fc81e79ee546f903df21b913626f1207be4c62e739796c2c487512964356bca0f99f334a8bb37743af72cf2f23268db4df7e2bbba7d12e8c5c59feab792402f53fc565797402f2d3eabb4b2644fbcdc5cc0ed4ec8b178f970cd7c3edca559c3ad21abb7866ba6654ce3cbe8b178470de1de05997e7a1895b46fcf22b1d0670f374c8751a94c876bc64c87514902bd5095257bf8de8b7b9deaca1e53fd640fbfb6bab247ab2bd9c3afac86693ead973d652450993264c88c690ea3d28a690ea312c77a758c4e47cc9305dcee8474a9f991075f48bd741809f44add8b04429d4ff9340b09949d4f6312883e05d2ec95792f0f05a2be9e02a47c5d6183fcfc4b96693e928dcb742190b84e2f308b760fcca26560168d8259b4fada2acd7c7c9b4a730cbdacaf59860b10578f0b81c4f5d5d791155d0891aa2f0b6c7101d2096e80c519d74b08242e547d1909610933ba7811c30157162da84ca8542a7335da584f928e318466684613001a63146030482c188dc843025dd4d4071400109fb0545a9f0ac42408720821438c2186800100001800184dc000a600c2c1d94553a2a299258205e45a745b0e0a532622fffa44a7ca3ce7ba36d808847f4fb94025b5514101edfd170a477ddf9bec2a9ff3b0fb520c84ba0a6e9bded66401f6c18b9d2397e62af84663026c1f3971e67e95202bca8faee684fd786305aa7e964d2a75de25ba76109d00aedebd41023dcf25a542524f8ef8db11074156b3eaaf18e433f3703ecd49e04812a5afd8875919c4c18881dd4dd11675ba62bb8b0576cb434b2d679d9430265fe522365e9741fb8f2b283296ae624a086caeb04516e678deefbc62774bd101702884cb21a23d6bfc2236d491e0716f800a0d8ddf2f1f87b9951b81df69dcc02e18c22c5a8afe2f6d62779aa69b59fafac618d224bbfffbc0760baec0050ddf8b682d452c2fce5d1c374e3a38c4f0fe7b94ea55ff4e3f5a69fe5677b39a40bc2b62adc6ec64f2994ec5bd6a646e78d89b02be14389d75a5d8504a423de9e4bb7f230c5d8534dcb9d4bb26214b4481a62aa2567ef300376766e7092216b97eda8b5c1d94cead18e171eacce317f79ab331bc56bf09855e49b85b458a2d938599e876c0d831ca0d289b69e5094a8d887c839b7df2cb755abdd2218c00a84f9699d528b75d481aebd03ba745b1321357205e85964dcce3f32e129e3306e5f0c46b2cef83ace0094861f777a27ed1205cdb559cc43fc404a260cb709773f47e730857001a8c0772b17e40d17ae373ec51f004da9f3f08ca4dbacf07090e2674d08731c485b6f5fb722feaa48248e6a581b194d8e1413f7d145ef591533b1efeeaf7a9eab9aefa69b615223a60f0b961e90206a0b20a1717db7bcdd458871e5b370da4adf45fe95527cfdce421129217fd7534f00b5047bbe274a617d345f81fe991e51d4c2ff500432c36ce02765cc706d28f1339ca8c2fc26b96a99b7bd50bd6679f15ffdecb87154da3e63878ac8963613baf92d77971ce201ad0f435de65b0cf6887558328e437dbf177acc9a1d894e7b39a5838d1a96e6e4393d77aa7cfc0ca570bfe220a4a8d84daa76b56f760748e6c80a734de07bea4fc44ed10cd97cb83783f4c442f5fb3d2e63cd8f93b03849139c5f40929c5c8f8fa8e8c3599b4fbfee879bbec1dcef2631f7843598056e5523c43c8a20f664c64c66eaabda986a8f6ce67fb2c7ffa6f4ee293266f56096292b981958c46c72fdc31af6a595ce4e75c813736124a5cc9840d877480e3b6d2b6b390b84e90ea6cf12e26740d102f8dc75ddef18b4171a7479d6580e7d9758efe5ae5b1a5c25c6e2eb19c680fb003583232b11c45b406ddfb1e2c28d5294862c8ffdfd336425cb80c348f7e34a012b41879f69cb0cbb8856ae9508019bca9af8e2041b6aa5445dd1d8d4ef9b9aa6b4620ef94c61851d4f9d55e866746257ce17729ee96303ea2bdd9bada05f9ca5bba1e707349ab2a14b4369e7aa7b19528a1aae7b3c46aad879ddb4817bf3468d6d395a96f159a23bbde48b47eb6a1c3aaf6280398e27363bdbe45fff1b96f053d687bde70b5a5bf2e4b643ec27dcd1c6558eaf4f982ddf1e0272cc2e7eb1b0af278e421a6676059b102c0ce9f8a36a88ab855dfaf808a4d5623b87be742ed2450cf6b5f707a644d4f4bc8407efafab476d81fed165a8510b251011097deaaedde8ef827dc37250074950b15920df1a8f28908afc9fea0163895a3761ce54d28385d384627ade7f781d819affbb9e3fc0124810da67fa5677500f5fbc7704c145abbd66a047d306e5af07c25847b3e2ac763897d77cac82b8b645c12364aa54e2baa71939cd679faa3130fdefcaf54e53394118f5763c95d7f5d167f8dbbd01c16aaf5543de4c563da6d769a69930ae1a34056dcf8901072ba252d51225cb1a76de7920c2052d00b07c46c31885ab01e8cfb31897ad8132248444b6444a5b62b69b98dc1bab790717917fbb5566789f062ab8eca12945d48ae1e719dc59ce3871cd6b0a72c64da714889571ca5c3c7e8a9e166978394315b7a3674af2c5483441e4834bb2fdadd570c4e71452d9969d502a1c1a3a5271f6ec07ac4d5b4739332d4098efd5cdbebc9c746136295494278ae60cf07768e725a22444e6d523e96f707feb14d76dcc9201cb66049c2b94e0fdf32a57057faeb2f2895aa9c5473ab22814395df5f31166c603b5e3de54c259891e1b283396f1ab1e140cf0439968e66fb364cf33e8328ea7eb97bcf1cdd13f058d750cd5c6e4149f5bd516927805a5700f995a35a3a3189d1a3a0c4c4f667cc4254cf2e0ddcc044cad15397fed22f81e4f7dc2bc1a9295ad0346ae5fc45c5a5fd9b184ee277fa462d59852bc661e5213d11c74e65d2bfd3627ba4c909a50da87690c499d495dc4fd7341c9c0b8bb3e27d0c3b6e70cbd3095259cbed550dfb406133a988a31bea2cbe5c20104d4b922b33719c36fa8c7164c5c65959d4eee0d69b1da935988c77b95e212df9ee886178d68bfa31e59e3f01c3b87d819a1c1210dd4facd2da5d425c48e3423b7b8bf50d9a87d8aeed26ccd27775812e335829d39080a3eb4dcb6fce58a4510e4e81340049ac22576c42a0562f164c880b9f32f6608424768d4491552cef8c307ee04ce8f81362ea88de1595cc00c8d9540006774816f9d7715d1ed55022d8ae809ce2a18c027b23b33b99957af39a766320af27903dd45244e56f12e4eb10dc0a56181a1bd967577b4a32313b23c1e1a030500a77d390e4c29721ebfeac3fe744a2bb91c6b34b328f010f2e2bc82a20ab75623d8e1d7b14018ea3dc0aad1b55a0235a8b1b1891b4343ff1971f9d6062075489b43c4085a4d1466bc4bd599def928bcfa5882a200549945d52a89cb84e6c0410184efd3a525ecaacc1248499fbe7bac0733fdbeded0628bbdde65a44e0f3d2e40a34fe80ee2091c9845b6e06828f9c86239d3c2207bd9ac2c7be7b49061ad5a47f9954c666505ffd4f1b1c7b27f4981790fea1886f4387096be84adce097ba850e9ad0eadff267fc1295cd43ad1464034aec41594704a559a2493324e33cc0df1b47238a81b698c012bd6e8578f6c20685cc6dc80c8598daa46f8da266b4897015ac9cfb8d33317bf8a564d539f8ea87daaf0bf7df89cb5b145f0f431c949429422e6317e586604505cde881aa913e77ad13017daede493cb069361105d735a49cd85ab782425dca4ccc2c500756ba4c1a17e7d763cbc0dd6c00366c6aff0e0c7aa8d4f51131c7372a6466bf93338c76531fb1535b787da4d3440db369302d4c31ddbf3d556558d5cbd91ab91a409b5d5492972fece24cf02e3c9b4fc154a2b9680f6b31e6c47cebcf41bc539efacf12a86a208eb386aa6b3ee2b2b786b09d88f7a542c15fbd6dc250c13730378cbb1be4e3206e5177fe0d43efa29d8eb407177190951563d5e0d67102548b0e9ad5b730a192eea74905d0e79cadbd3a479f8aab92d2709b1ae4b66bdb5ae27081c0fc858200458bff002db2269fb244e088768bca09eb693068363e2e3d3e2621d571b08c85b05e76dd54004099c979af73b74446c1ce5bf634bc144bcabc3887090722f36eff26dbe869fa016fffed61c2c13018a90695cc406e8f5f249b3cb07aacfa922ee5ce86c205239bcbe2b6616f82509a551f0d2b17832c7548531581f3adba1890312874136b7172c823fd47427d794407105c9441bc37e39002fb8196de88795870147e198fcd99d50e32d660b49b5fce164421c98285b55a21ede77da919df09563048614ef921960f4cd94f4b71d350b164ee2c9ebb5fe0484641fcbb87579a764a2c78a30ad507ed1053d5bf083cf206729a5420199cc0663fde2caaa4217fec92eb740038455cc29703794a3717880899667b02e8f5e82f40df4aa07a898ee484ddecad3ecee20f0fff3999327d85e5e2d91da284ff16f0a4eaa5c92cc48c5363e4dd90d2dc80cffa8577447ca50c70c7e2cac82ef7d0868bc3694476d07079b6ec58c93aa81b14f4c1fe8537997ff652dee6a79321c23f44e7f19f663656bd01accf39e0563f2c0bd16709fe01004c314dff27c85f9ee6de1a2a0778ccb566a0cd10b1c77e0b59ff901a8142d2a2273f1a8d24cd2d39b59b3b4d488b24dca424197569d81def01ac52d83986b9a2d4e028c0d507657be5f4942901483ab9cfa883ff864256bc3e7c452fd0b526ac4870b974982251c111412464622d576c3b2e497fb3e9fa4b4b4fe928d02f73f4e16b839bb091ca092159b6f625081fb2f38b7a89f98725cef1884b29b17fb06546441c5fa3aca4f9eee1858e2fe4974e4b5030d22c729b544817db2c9e287ec556a5e310977e1ee6a001c2f49801203be1ea7af19cdb50d920d4599d8096bd974f1dab3d1869d5a1a2b027dd0506700d07e21062b6c8700fab0b2e81d5242d40064e4c85344ea656b0a08f0843aee614d541d49b4d9eccdb6a2e553443a65c1072325e9f2037cd5f61bd4c352e93688348f5b47fb9a1567703f32ce309dd5267824fd7a402eafb8ff297257889f1b626d7449c0365cb3e9841ec278a52817672b9bc8521f859c74ef003fde958d7810516d1a14fad4d69db290d1d3bde7f394f057998148de17c9b704cf18506c187f6d610e6da0a1ac1aab4c1129c53c3e2bfb40fb493fc564ad38b2192ee7f58ffe3a79b17500571087cdf01b18633460f084b54cc30bbdbec173ed94517ccef4873cebc47aa3d02b99e6e190e2617de04f4aa93ba673474444321a7e6af1d474bad78be47abd5addd8dc5137050d353c123934d0926de88000266f9df0e8f5553b6a5e55970ead7d4c10a9bbd6f7930a95467fce07729b1ead57c07a8a5384f8fb58460295701705845c47646d6a7bf7dfb66ab5f3b1cd349a04c78f653e12b3e4692c5ac18526b9d586f27251f09b0ccb1689406b3f69dc52c372be8f53ef41a3d067d8dde47afd1e3d1d7a977c366f05b23b35684fa60de37479658adf4723896505832a0857973d286ed8ef3f536eac6c5f14a064a9a0772b4e323647687f978237d59aef8cdb8085407ddf3af149a5504d6f7bf663ba802dec1a5235403cf043c8c3d8b93101e88511d88b7480bd94e05217d4ad95adde27ca12eee5efecb42c25d8084585b96ba76117919d7864da9604ef33387f1ed823336fc118b210838f4f8ce4f98e172f48070aca1f277078d6a3558a2e8def2eba321bbb94af7908f62f0027650c893adf7761953cea5c4d34c8cb261c889fd7a8fa45358a2596b64e20c6f930433499b67c3f988521cd65bb29a5d9b380aef2aa0554176b211c4edf28bc403154de89b17e219864c17ccaf098c4cf59270ef9535c1bfe21e059be34ed89bf1c13622eae8c07a3d8514ff7f82373adfccd49c2c5c1a2ac85301e41b12a3c871461476979b278b294e4874ac86e70161b8b8daf838727c93d54660031d3b5686361f4be57955604dbf97c400d1452734755cb22f75542e2e8ee6ccc5fdb307476d8e1d7c9a4e0c1a87d79163cbd233ef349fd984fa682e5507b568de97ebae673721a7c87d8ac61787bd039001483d63c1ee981bf8e837a49e3f3f86a5dfd5cd639def58d81093c0f0f1e5efb0ccf9443754a8fae795f2517be6ce86736097e2e505b76b1d6703cf13d0ff360db06d890c3ea7232bc7394f45f446cf4c9d9c4780ec3ff3d6eb18220576d14353456a30eff2fe189ce334c3357ad2d6c7e652dde9e164661267e96435ab984a9c8d5d72732591d31e97f76029c6f7200e02925e975a95459d7dc901a56a53bb783ce74d140e66fd590e0c4843407826829f2dda521fbb9ab5e9d82f0720a542988435eb2607069a2616da17c4d8b5af302383ebf35593266232d222b40f47a7a2200fff6ef8f2ee3ac3a7e02b7d5dba90e13fde67999d916ffb95d111d10ddd853d8b0bb2d72becaf860a167a9d02ae0a66839e67c67ae097b43de063c1e7c0e838234c3b3004660aa6782df3e93561f4930bff0d810e1fc4675c0313f3a131defe4a844efb55e67f24f2cdcb15b16da0e8d569b8d310b26f89e678a2b82c60c7f5a803e742b77e5a30a5a95d9617042071c18a4e10f0f49ff5586cf6312ced0e58cb139f53b1c43878821d96b18395228adaa2241aed62964f5824012fd83a0044941c56a5eb56752121576d5fff7d8c87c82eb326da356af689d0087cd97d3509a7a16a81cea6af8001cff1dc01b565116f04b97535775dea4c607d2b6e24e594f624416bca0f19dbcc85aebdf0a3344400cd6ccf3751e69c236139abcd570515eba886f5ee3b602b1b6a98352366076c83fc72c13fa1c6ca9ca43abb684def62e785863db795ea95e855e11e9d6eb43c421cc51e807dde4a03e3030019b23664bfadcf4ef308c965a0f5b37f134ff449d10eabf6594e3f04814eea9428319087322c62633d093f72955234d19207b564906afc674beed9b0dff19078b17823bb20dc751b220f96329fe6aac63371c47bebd271a58a758aecd988ab4544392d0aac6bd9bc334891197f55b8fab42b401bcd2034e83c8334c35689ad256cf8ad7d7675024f372797e10a48a8f621d840f83247e8bff480b4bb613d0a391ae8573b3276430500cc1dadddff5d8fc584dd13804599f67f2a5cb1a2055000e8a4411afcdf785c109c3f6e3f9c7505f95232f63b2d7a1a748eeca382cc1a2a549d1393c440d5fd78c76513946297838b9a442c8b529c1baf587b6d21feb031d7cc91e863c8ccf86c61477de99b22870b500412de9a07037822327f37ab80aec8255d3725054796cbb537694d4356d81cf8c074f27caef892eb7a39ebbc5f52f8a4038f237aa90a596de82e2b1a2bd8c56cbe6bc8a926066725ada42b0cd139bd1acfebb46a45389902673297a9aded4c2a28fd3035b67115c9d67ee93834f03baa9d35dec59a73abd88de4fdd9619b0624c7971b7e62d32ab56a93a3698c6d25caa9c3b6538b17a13883cf861ecc74169b0a392991084fd74db5155736ba21454303d76e412a5962075c123fd8b94dafb32e30ebcc358084721a1541944c183b1b4e15e910bf4bb40eb395d3956e042cebd5be1ef91b3b626edcf7a2b11167c2c03af0050829686cd38942234a65fc0ea11db8f4f2bd9b80948b66c80e42d2f09d04f16fa2c49d3e154b125fa90d65216550df6a4f738b0b94c75734d54b2e60a1175efa7fc224244221c15334095f5b5d8909f876f8b31d5e6de6571d4301bd4387b3ecbeb2c495a0638db2e8eba1bf8e5eca4a23410387d46406cfeb07ff9aae02179dee9bd2cd8ec925fc04a21bf5d7c34a2fbee2b640a88c191355a0475153d894d65bd49b6d1a6b012551e51689f81862329cba2c47644a538a2dff90a768e9bf1b5b372cc5609af46a883473a1560189a62c284bf9631f78a52a02c0c8f2ff0334ea73e2ab3594f49a05dab44af17a4715bab152edbd1ba97cc33e404a639e53f9d4af792c2347926fc165543c1438453a982ca0c9b828a051efe27bc0df9d17a1602a39860d8b9b677efd588cf7b28ef71657f4e72507af59fcf84221f9e4131bec7a12b0a1cbd8833dce1874dcfde89200cb534c6ef2974a34306ac8668090f98ae4f37064bd1f919f4929fe882e4b13ecdb0a6e362c63aa100f15bc471f52bea26c820f4cbef3a187cce388354acf5cc243f6ab0a584ac796e4cded40a42d595ac4eeaf44b9b787c108359bbe0ed8adf96ef780c4ffc592ad4547db9263b724dd6aa8859f921cdb4545467ef16528fa8c960c15524046709dbe01d5a2b6915ab51943f9e95bc3ceaa4c51d6fe667d783b55bec041e0ef5944a2eec42f75a4da4e130e2dd9ae71de824e6d16bf87ab092506aab4b6537f6f59f3735e17c80271c173d520b12bc96838430e9d74063ae248d576b66c58d0e5479b210d1a3097f4e75be43977f8bd9b984bdb68edf60e76f5a9664a74fcb2d8bccbba37d7b13f1ea4007cb4a24314a5db55204f4c5b1c2f836ebfdf37af0feda52cbae3e4c4627ba5974489de886c4a44db9bafac964041712011cb28af0e5c5f8d25edcf170c8534e56923ce4e061c3556bebdf5ad0d8458843f55ad1fc19a0f4205ac592ba1be3fffdf6b1be62e745ee044e840a15cd80938369c6c7e5dad9e62639da26e60d206880f44d841fdc2abad2c2b576f4caafc1a6d8e8ccfc70dc005bd861d74a1dd0fd26c2294535114f7518ad18df70cff1d34f313408fb8e20875903bf271cfe99236b8a1e9b75931b8974c483c16eb91069e2d14f8e41fca9da15f6dc15868a0df2079f5197623c7758d7ca361026d7275cbb3a0d8046226d72ba19e63f1690d618046eeaf6e0721a9c20d0c39ede1ae2e45ffa28db7628cd5c665525a1969e85f86dca9a93335d1303e4b1a836b6484e4f20e351568738a109311f9e9b35a4cf1c61d2ef2ce22be58e1627b9330c1baa6d02d4e342ecd95c267b94a1ada8c80c3355b9080760bd49ad58c4806d92000792314f1a265412c50b6d411a292684230b94fd00f16af9c69bc5c7907f1055988b9ec9aaae4c6a5c063c016370ca982732ae4bbac36c96c0df0acf0ac2d9e6ec81cc2c13541ebc7f327b625fc38c4318985dc79502d60a4a6510ae81abb9d82332ae16d7a3b2f9238dd5ae29a5662c8d9b7789548c17b6a81ac47a391517370960e51a9ec7d524dd86b2752648aaaf57c305c7ae6e1e695d0455489ea8e28aa59e1ea6dd423f0e4048d4d3d740ef1f2e7942ffaa3046796c5c5ae3abac74cd75884a75bdd6da6e16c69b50cafe85a5a1688d12fae8ea5d3dc6a762cce599c9fa5ae9d4eb15da26127affabab60e7d1f765fb200333989d36786097f086708e35b2ba837ae8d8d20c7827a51143ad73c0203a005bf5819ac9ff8c18e949876d40791193cfe365441a0e6346ca7f0962bd208c3d16566091fb6af22d0a79ba4f81816bbe3d47498c195dfd0a8f4860bd032d6556379026fa53f71037d29918d20595f3a6826872efbf69fffeffffaff2f96461e27a7e7fa5b50205a06d92cfd66dc0b654fccb16f5b871c06467cec8ed5c359e9a63ea40a67924987b63bc777dc68600f2dbc3933b0076307bac4b5c2e58e2ccde36ceba948c6491030a489fe0b149ef9d303f9f72f9b7d409c959bf602fd6756ac5a34fee2b3541b12c97cd64005de2cb87021bdd88653e68b4564c0bd34f9085e120411f43cd6e6d708d6e238e44dfe24450dfdea6273ccd841bc111cdf390d1dab2ee1c2abd392e04f98491a462b3b56a1697ee050efd4c15759b7ae7a43b365a6051bfb173b739c3ac89b9a8df8c47e65ee8f22d258920981de746e780cb8c60d44890882cf30e4fff9019d11f4e482728f96fbfaf2ad2b320a3a43e02ebd04e1d72301f56f402371f86d5a13fe2f5e20446e5af2d0af6978da1e86deeca4253540d8dd3a31ad99e9cc778005fbd55f3183850c2a2262bf39efd2e6ea6fbc8fff02c7a6a342287f3a320897d332a200551b423063ce7f81c75185165d4ac0de2aa6c1bf5bcd6788d1a9961d385b6112880e4e9e334e9139465bd6ff313c42963797178dc1a25df12cd0fec6cb620ba262d81cb278568ace04ed2d63a022272b1565c33aa14c503e5e061032bb25014684eb652c9befd37920852c385a937552af17c79796c809ad25ca21edeefbeb3caec19be603736fa08b827eba1907199d28838706c42d6b34a9a75e28d97cda5a3cada69f78e3ae1c9866a4d060f84fdfb6719d48f0698e1d2d08160d60a3993698df5eb2e260280b5c31e2f010003255fb72e711152f19694db11ee11e522f2c444c280b651d01fead712fe2e0f49c6c467d843713b5dac5e6bdcdba7e09eb592b287b5163e1e80ccddf78d337960bce5c443953270bdaeaa3c03be5e616a87d2b6e5cf649e35e208d829f07100bb79c8545d53910f32cdf6f514c02c21aec8434c4a00ae43adda04e38ee4af64c89a0015db5a8cfcc8563cbc1fdf7945d23483505994698f90969fa8ec2e0d4852c5779ce720d084dcdbb4704a208dbf9025e886cf17e101de33148af81701a36f0eec0b66fb8929ff9909b9c1205c5d4a888674134068ec013c32158238d782ebbba488f2a39bf7b0566b3744e2a40b7b42d0c63ff3d14b5f70100c5686afaf4ebf4406099e42bcde4821b0623023ff06a78db3b4dbf0cab54a149c1d88d6b71fd20a499834ea207e66e5de6bbba14460a287d9a4e3814a1128e8e72988c81f05fbc3718bf90ccb33bc7d3e56bbced2ae6c1c7ab1ca20d572fb5af1320a74dcaecb797d78d035850d1d83c304b85dba4feb6b2172e5fa5722f05855a2841e745b5dd98511c4e44d82381a1658a0e0687c00206ee75ca97c37175a6e117d6a74258848c2b8b061caadc6906cf4e59c82b0c51fc4f06b296943be900401dface811e24cf35eb5d78d338bb1ca94c6328de0f5b0e1c1936a7152aa8b028b4a1071398f912b7f8b55c3612885b07decd99e05faf3eda82462010234cceb44239ffa22371b0ed8a5afdb416c1f136e8b7f4a18d3c9e000ded99c0873bae3e1d0251f1c301d6b1b0d69dc44ca61dccc19589b093a7901bc918ad44c508adc2426010a029df19aeaa4bdc5168e5a511dc7e08e11f74a1569a27ce4aaf1215eba3401020ca6f88c421246f7d7dcf71fe02a0f53ceecaf492f56b874883e299dd4e0f1f220acbebb95f6e97fa5b588e9338a73772750fa88bf7d0d5337440b87e693f8010c0f735d290b4987d66ad4b62bcd36c570f6a411ddd8e3fb5ed99caf5b42e765bd2373143face32ba12078bfd8aec4e07377b560afb020f4f7d983fbc2a083988ddfa978d34b11e774bca35b07cb7deacfa5eafac3625f90e8c766d50c12f83989302dee826d7a0649f1a2f73148d89f134c2443535cb35ce443200eb473708acaf1c10cf70ec87d816c53d7ee26eb123861d834e33e9713e0e42997f14555ff63aba33744820a5209da942470d46c47d782b693e0efd0c1e8aa1619637bf4f41f3e317e6f148000dff08dd4f1339e937e9688f65659eb16f243a0c3339cd47de46e7961cc92677927028121b37059b16bdb25a72878a8147938d9581b8f0b782d297eeabe20a0b3df0bdfa4455f729ae75c8e7a8edb34d14dfa09d4689ec6f9e0d2dd6cd658c20dc70e69ed2e27127ef8ec7e367b0c20ae618239fdc14f91115658e8d866fda06a15dba1ae7613bed668dba260355d15078cf22dd63490cdb8bfdb803d09fec65011c817c910f9a380c7cb8241e99d066efd42eda93c8636033adf8ec2e4e44ac067b80c9eade933bbe42537d33e71e193a908f2729fbaebb48d740ff23cac85d243ca59add0fe6c97b264a1ad3f44a0528eeea81a4e32c81217287800b893f7a1ddaccc19bdc07093cdd7a327beab932eb1962abe27bcf908d4ee3e4178ca2cfe45f5334f2e909d7ba5fc616c8974e0de790268f1a091f6ee9760660b5697efc305935e45e90a1540838b63b4bb05369f4656ab1ebaa388506a153dad0dec203db50d0fdbd49db126e1a5de5fcabfb894aac824f56e58038ab8a0eef60f0238f93f0ab3de711247aae2e5b1001a54690bfec2667b9dde76070ee80d82e9d471cc53a478c6083a5e5fc91219769ed87bc1afc5a417bb3f7d015136c4f997a0501d0da90e5130d8e00ea651c6dcf31198c3202c9bc61548f89d6d91f1ed300767b5811882108dddb0bfb1eb808e0371aa4c6fd3e19be1e4ddc1c266430b62e7e677ea8d27865313848e36ac68423b966bca5468c9126983e933659e4ad83f332caa278b4d42c05a34b3f26f4046cac49c1b47dedfab970dc013c53f2d59145304d108380764712c038cb05155750df8481e7989cd32c22596f3348672254890ab84634918f63347eb09fc3b39d82050a655090f5ce0bac673b5b529be849784f28e907ecaa5085f86f2e85eb212327f50641185ec13b5f43a035cf832e12896d63f24fac224754426230dd10fd69788c4f1c031b26f6e7a789013439f6d9be14ea8946302b0e0694f5c0ffeba4719ede724cafaf606543b07afd028075a08ac631a3921bbca44a824af5270221c024063f201fe1a8df9ef4769023d96b27f14f8b77e604ae5cd2be65e36b24753f0079277aa84b8a87e995349c1f2372a01b5ebe25b284ee825a0df6d4e4134688c58087f6ad9a7365679925c73dcf9c2457a9b1f363ff962f3fbf53ff5552ded9a1d63da4acd5693a853e6b089c2e487f9d7464ff18fe8af1f0ed5753c916853dfe7ac890b659b5ae5aa4874ab1c2349877b6781208eada1da28f75b6fae1dfd8d5605b67d100de821981a0a1047401eb67de32514974e11a12567e9da9c0ab3154e751ff87972d9677d06c73b21dc696d80fb5e0fde12dfe9ea47d816d18335eed2f041e680120e8848515dca74c69f637331cb9f0db443aa3deb69e981f1064618cdb770889eee9a25beb9e0428b9bb1e0194c62a5845944b01ea08e7ce1026a17f0cdc9ebad2a050b3306b4bb3065004f11ff6399307efeaeef2ea77f612c91c9e6b1392aad4205d0f7666b4b24373e5ca910bda3aec141dcfcf60d514efe01016d2f828c1cc01c3a61487c23673907f71bea8965ebd8d140cde667426707228a94d0171a71002b1dc212de099d6908e8c8878a4d8372fa48685d3aa11d785d931edaa552dd7c3687ee8d2c3965562981aab8c2b81258f207c1c8e459c6c65b4da6b8a6be13991a427f5b826f9993d2d9152c7c91b2e9e9475d52da8c2cbc1d40c2d0ff5c0012ae8db1fb9436c74c92f09fa99ff5c105e727fd20532b8dfe91180935558ea44d26b597a6c7c52ab1cb9bc4d47d9344e205471f4e0d46fadd966b2a3132967cd9cdab071e78feb8ef04471642c2761ad032664c0b53400d2caaa050b75e23b3c6391f1e8b82c78c0cc38d3d6374f613d1fea5ff3a98d46da12ff0aecdb5853249ad77ac54944d39ccc087888d8dde2a099c40350cffd0935ba2e1b49dd0290e57f3d6c79df1eecc77a3517745a8a4fca4c6652996406d700d7d17117d755f96e41925811770376c29c1c36de2894e59ea4c38f3e16234cf144a80ae24e11339de07208bbc02b38236b842b131338430eeab02ff9a6fb79deb51d8ea10a48cf5136a3960c2b46e3cdd1aea4f9d202cb82f138addd254505f741a503a8f221166072ee0a214165d6a99024e0b5aa6c3f3f10aa60caf7368950b5be5e9b9cc8c6a8925c6f8b7f30c8ea37aee239aaa248870af9f0c3d9153c08668aba2448754dc003bfded164a207110d23940fa826aece5b39af807b7a4339a3e2052bbb61cf9c61404c93f7a9d10a539dc1f40de3cafc38bae2ad5dcdb30b40abb61c36ac17409deea5da74676bf00c25c45acb9621219abe297451c325fe43f1418b3d26d52134ab0ba3baefbae8afb3b28bed09457b59651a51484e29c8f2fecb57d1bf2072565455e3f899c2df4d09a05d25c3f89f3c9ce75bfc30b1155332abe234987f882d469c0ff1a2d50d622f4154619e825f4491c876985307ad101c496a63767328cdadfe8f34c300f6fa077f41cba7ab69fc95dacfdbdf7b24b61ce901fd5eabbad1ff051cd3573695cad87dc65eab1712032ae72d74457df78771b114e1a61f84394b287594e7eee59fa49ca2f39823df67ad3d4444727ecf6ef509314969a2f14389c1105a5e1451a0f24b37d08ec8d738f89b50d6787400382a4325e5952911147028ce9cc5990691306b8b9c233727a841ddf9f5c82a3a965310d56f833d8d7963be6f292c41d7122930c30b041c16439620380b848d724356c21253100bb1c3292126196f2e0ae81801c8f9d10970e1669dbcc304b0744d8d12145021b4c1fac23620234063944a1a285f40e03c16b66a39913c8d5421983ad2eca9020a2b6594262c92652d4f9833986a4471b6a485f653328b547d105b4bca6e273d2ab7a3877494f24663982252992b5ce69258f836b0b09ceff610a350cce368d824a0fe4294f542b34aebc24ead0fc18ebb68d632b40d9028eccc2466872dabb6d2ac939552b7c296ef2473d3a9dc3bfda868324d6beab3d29e1892ad36c5ff5940e304e798c7ba554a2f1665d1454ac5368d4598f0984ae368327a277b2d010088e375df15a7b965be9413b63ad8a0dd7c42e1a6e1d107b21c2f39ab5005f380f4a3cd8f33019b26cb11b00bc8cd4ffeb87bbe82b524eb3c6509141f797b3d8c280fa26746c7e4b21071ce75bb170150f4fdaccd10e680b4a23ff1e38b565c2c0789a013cf8ad20bd0e1b4e353bb08893e1124150d1dec00943214c11eeb9c520bd4f66daa3ff7cf714c9080a3ff68846ac382793c474a5c57e359556c5f73e9e44c20fba63a236beff3c368ef8e0bba82161d9f59759490275c54ecffd58bbdb8d8afa833b8892af33ecad8e3b663c028b150d4971ee5e2a15c34ceef6f5dedcfa881ac772e38e5820b1bfa9b733bd0241a8a26bc7634512568326d78d8d81f9fbf3a6cfd302a2a6cd0b730032345db65c9e5f5715ad5a049e4f9809103474327b65b91e40fd3a9013f46be2cd0511af37f1a91ec7f1a2066c546644d77b8c520dac1a800075f4d7dc990262dd19aa7abd07333c5a7c50b62ec15c75e92d62848ebedfe91fec81812d937e0874e30fa0feeffa7cafadcf3a840c33599dde4e27bd065a0208b05dca334c85266a50696927164e058db26035fc37b43208254c537dc9d075049fc9192a7707600e05627ceda09d2797f5d9e7853c4e3c1ff0b1b04dfacf1547be49e80734aeb4e9955296d480592140b4b2625195a903f4a6ff37c21650d8172214a54b87c33436d9ba814cc8a7f92b7e00d6cd9dca21ae2f27bae9f784627f1420c1546739e4ead602e3aa183ffaadb26077c54495b4648c0170c2c933fa605e4b31b8c193ee043fde5001e74ff2ceeadb797d8b99b493c31192dc49b6f3a65bca627ef95ee0e80d2004a1f987809bb8043bb9fa293a8809ba369b03a2bfa2854d1624e48bc50f89ed899b79322fbf652a82335e418445588006a690c8310fd413d249f242a3fabb912d47b80f1275ed897ee6b1549bd6b3009483e983c6f2240197a8133d355fe98d552036e6e332f1878190bd9f0c55960bf9a8461efcf50ab6ab075ea82b306a3a726482ec30717cdba75c34827ea2cfd72738deceead79fe0d8e9090baa34e30412cc56786bf20ed46c45ebc5a677932b926fd01bf1372208b7d9068f5d3df0d36448852ecb1ceadab3ced064a6231587856b47e334400413683873187861652d58ed20adcd3b7bfbc199a64c60aa36119f8811a371d00481874ced6c2e5748c8bf3885f863f748e1deb44534125badf88ea4a6eb7e3155b641f4a5a4d38c0d54eff58739eebf32bce4783c0a9d39e2fd2b708c01ab34496f23c1aaa0c8c8c999d355896a22b84365402ac3ae28b4e4bb897fe72133bea0723a7a102523a3052b0780e6f62551fd3c66b45cf05cf6cb9e707fa4c4426974a6db97c355b9857b9b763383c5164644fb91767fb4a6fa731f5a056e8f9948ef5d91e56fdf6a610daa6e5aab976572004530d5b575c111270f9ccad17bec6a5f6bf4c0ccefe3ce79728115c788578ba521af714a923b841e99d08fd48b3570c217a8fa8c922b6c1ab8c05a754a17022de53d3467c5a75c2da6575e48bc3997415e5c307be9a83e5c2530e5080fe8eef911c25e8adb4735fd043fbda5cc3a45e239f8bee642ece317fcb68e7ef7c2d59df1ccc85c66b3b2b8131c37b441d97e4b7d6163348fc0bd0e72f515ae7c7888ec7d08a4bc87145135f58f47566c0d47e2b33b09c566aac2681028c20f6c735b0d0c442c40c1e1ed25a707ab2936dc52911ba73cc1f042e9c31860de16bade416710e36548ad37ba67df0d265b008802d13351f8a82602b6912625324b2e0744702d84f3f96bd4edc419ef00544e8bd49833db5152203e2314c91aefb701e3affee534af229ce0379f9f510d1602869a13dc218f1a87927439eb149efb53ff67ea6c53d8621fd6270c8f6c1bc00fb611a509bcce134f19498c8f985226f2015593f5e2c73c64da9872a822b2a079dcc6822662452c2ad5407bcd1a6c84381f60e9ea7a36b03da2a969d04732cbcbdf7d396d8b682ed66ce325f03f1f5e66be9449db8e0937c8058e4087b89947df90b0215d842f8ae70ed0426431bee794be4a118ea8a794ffbb3ae40528f9ea0431742898e4e9af8e1c48ea4a12c4acdf174d7245d3b9089b842251e7be0a0134ddff33f42d93820828e7d6f8613a8fe5520762d43f69af7d397c14bc55d1a8ad03d89e8774f895eb074cf3560b7758f261354e8cb6db1c3edf237a89c7ee3a515c50d76a1664ae862cbd45d241f066547ba7f962c2a7d62736481ae7630dc454fc4d950c7732e1be44c15d6001e24ba123c165ea4831d929dc65bb4a912afa333b59c229ad44de6fc7c1075d01851d9189ba22383e00675c1812b59f2b1ad540c6c5aeeb9ae9335fd5ca0b8e58b182bf1d458e11911ddb1f04552943483ec2cdc0b4493abc96b4c0db91fece11f4bf6a42231ce68b918386ae8020ca2f19753edde28905a0192c80ea727033af785dbbce56a71560f79277311daa1df66a9dc47825ca4c5100699f9b4aebb438705752600fcafc1a8c08c0004ec1201e2a48f0df19ff5d839abdc53f131a021762c5550ceddb59c5fc49dba4a363414e3b0f6d83c91c7e86f368d2303ae287c1c6e0c969b8ca7287ece25a0dc84f4bbeeb51f7e1073a534f1db5220c36cafc9f0d32b2f396d6359e6102b5c1f7e460d60108632cace2e802cf14eb10c826d081da150792b349560f4989c6b48ac00db2ad048a093bd122a82422a4b57f3be7249cde9dee199493831f0f888393b5671eae062438390dcdd970ea079b4806d175c1110fdb41cb88f217598dac84f624e01d1a2b1ad6400ee2095dabd5631beeca399115cae986fa5640e8e79803533fb9d3279d8ff8c63a5c99998d1ebf844e209960e6041638371fba1746df7778a1e0e513e6bdf0641680cf5c330b55e96377860d1527efda7697c37611a5be64935f0591933d96fa53b313fa43bfd10800a1f09806dab9f5f4db1448c7233b04e0f29d6cbf4f0de9e713cbcd46405702bdbe442758a934f28259899b5e28a0588eef5843f1f0414be0ce49f46b7594a6e030daa7b2c4fb226c057b8e0db8cd284c497670814c129397a39db7058a8f8e187b7f23548f90d1fc93398993807f69958de7d8c07fc7d6c47709e23c84131d33b03c80dd5e480685e55632a96d1136383ef88c2e44b39286fa1e3320c7223a004f9fcf1088037c51ac1058d6c873cca07bf9e0f29ba09609fac47921d4065a24946157a87c7f56b3416fa34fa1a6a1342a0860d13ccaaff835799cd3414cba1f07daaad66cb9dc6ea7dd9ae932389dbbec13ab7ac612855dbf8579e53e3b8dae13f5811c5865e05e772117ab6e9316f1a8e8e348c5ff74b465af58a8ce23bc35802a1ec64acad61f3132627058e68235062f65278d6dac7159bfa0980db6ba32a7066f9181312e4e0f85ed4c6c0c5c418d43cb0a5b8fd4232121e282d3a5ccff670949ddc03bda198dc608c7c87fe0dcc86dac8cada140244443bdc52322b75210488be01e52b7c530337cccb0f89c53d418d00ee3ab4e34be97ae8df7420abe02096e3b319779a982fd716741ab8ceb31b39a7083d3887f38257ef8b18a9020065fe8f3bdcab0bbabad8a07d643546070c34da088523ec9915427aa1a2f0dedd4930df81d1f3851404c5c8e8fb4f1b7139cf599a5e44a930dda8f4894e926e10be980020bca06de2affe6ad35e836526bb274a1c34d434d24dd5e94272100882363d3ca1b6039c530f8fda7ffaa46ca44887482d609e938d1c186929f0dd2c35d1ff8e0184af21996f8ca4cc3b11a04fa4d0581f8a989dafcd51311c29b7872fa708b37f9c2128f4063e433738dc80850cdfa01ce9a1ec87d211cb484f0ae49a0b8c1249d42201d4b1b3653351b25be4f0bbf941b84ff10c8908c85716e6c8af17296be3a892d1c87b2cda242d469a622f78771b962b09c08042b87a506247c19722b9042585375de00ef3f9f6fbe073be1916cd064384eba032a6abd9a087ce8464d25f67e09399e3051cabf10c869e54dca3e774c28e0e580e0d7697db4fbe61c769b8327d28cd595750695d729c5eb86466964c0e5a51c7d6830cff4a5bb2c0db40d37e9a9825b57d1e5a4e68008bb29b06df4e451c3cb608f07278c948c7d059ffc47f4ddd2212248182f6a0909b70e1cb8e157387bdfce15bb532189015256a0a53acc85269792539438872a5c5e992e3eb92b4ea95e45f99ff665cce546b66f08c4013a9575175a2089fe115c50032db4f7629e038b541e4b8af56955dd047b79abf2db6bf6d441ce208a616e7c8b9e44028f893dbaeab6ee88bf11eb5051889f50b5442472eb6e4a1b484f24c5b7b6a250c399d5c0a82c58e6b7942dc0041ca85685f103ef646591f8e249b6750ab6fb9a3cac145e484215285ebe4b1ea11da0a05235f148d646166cd17a534f419ba10f509a48a51b0066b92006da95be120f753a8a2bb606413c688992d78972a3e31e32aba0cb8e672255c0dca33fa158179cfe29e355c62813542474e683274c38debfa5b4146182544961e17f2ddedd491c49aefc53abf61f6a3483a50dc724a602e57b30d781750c8921b5cb2ed844b9e42afe90bc411da21f3154d3214878fb5616db361e74a0519456c927dc6e28e431485e3dd8895dbf86f7923447791d2368199009005cc515a1a1f0b1b81e956144a28fd647d38369c21b7843ebcfc00d05f9474be87aa63a086b3fbf3f931407711944ed1036a72428635e1d03e5f80b9e4145f5d0a73319205cbd5e431824b0e05a55f75fb11b673068e82c245794d5e96c6378d597b9eb0a8778086b20b06853c23433bc610c0ec51785edcbd7001866ffd70b40a31062b0a706215dd32fa97567052fc956dbc13395b57d7fa0d51b2ed35cb1e45fa701150af8c0da68f83123b957e94de757bfe4bc5a92efe21b195ce41dba784817c1ae2a3a1483a464a86c105c2d9047e8168b226b1646718d0d7599bcbe96c795a5496d45e48e150fc6182a4d819eb72080c9200b76525d65aab2bb4d84809342f274db4ccc00bcce8904017201ffedcba0c62611f072718e184411ac999f246b14c0a97cb6c9f4e4845e822e1d1d1d266743908b0263bc69d839e5a48ce9bc679abe5b85f3c10d7fc05a48a0ba0a462e336b79fb2d043a3cf3f22491d5ffa1889befbd3bd0b6046586f00f95a2b502bb9100a6cd6b4e65600fee17991b625087afc690ac27410a359d8d602f0c8c4899b8c872968944414db7a12e719c36f2e9c5f75615e4599946201aecafa34de5030dc70c7e74f67182eddd0b2d64ce5d7b72557e491350c17a8791e99d96710ef1aa1315d3aae40e68f5a4444c556c0d1cb7f3c446d8f9115ff875b7725eb70f689acd81f32d819849f834e3a30ddc55f292bda2f98293ba01bb6e73bd480e53aefb698d2f76507d33a6ead61dee820d181db9c85610c91a826c01dbe73929a0183bd695ec26883151f6017d776b108e26f32b8c14389eade1f9890861510485412e3a684999dc5c058bc09b4e06eaee0b4a24803047226a9b437761902067304c6032fd5555b621ca3990fc894de5aa3348c67d2843766abec6984d207819f890100a12c23ab34002aec42b1fcab63c252a701cdf08c119e3ff9827dbb6414229860ddb1366d02527de8691df076ec49ccbd0efc6005e48dcc79855c0c6c070d92b79cbbaed10cb5d5b5b1fc2c798908ef54f273bb8ee303a3ae7f45afe80a038c1776be59f7c331d3b0d7deaa45302b0e4f6956f4c4134a83a7267807253d72225f117feedc8c5747b4aa6fb1e3d022c2c357a773178f1462274c1d151d2d3a6b28ee80c381719445ab092779b34a9df97daa8857809dd663eb6e84078a023bf2890087fa9934a904a25e377b7ac4c4966c68dd645fa969f30cdca309bff1bb2c6fcd4977709c843d7da6d49ae7610d24823c381b0f512c42211a434a8256882b34575adc93c6634bdbc3ae20640240ad7e7c19e107f3162e671c7a8d4db51979e7a87dd9bac4dff8a7533cac3ca7ec9121a1de6702640171c1c395acfc64a7491ed81812d53460e4d31cc286f24c841442d82711488bbfe9bae6005213442a0424ff8de878c2098b42407c8cddcc583723d81c0a7e24cb558f38c5b88fa1cf2502ae22dff9c32d86b9c70b88839f73d8ccd63d3bca8ecabb8455cf53dd888f73944bdc361e12b6583d954dd12a14f43d71f654d119e4d3fcc2fee2fbfda27e8a30471d388b656b209ac6eb88aa025e9aa7e9e02cbd83a87ba1793e8e3d6cbc99e08f74794a63e94aae0ad927ffd060ae66e27a1a21b1994e19a15038c566cdd303954711ddc5f6cf609e4bd0d86602f1418120d3f213aa6761032b14d9eb242ba1108b33b65c75653236a77e6271161c3e158fa243085f8d61a26618822be0065c89baaecbbb8d122b206956197251738cffb6258beb53448d969b60f6b7dbe3329dc840bc4c578b87209fa33bd24c1991a2f24be0d191b80158b4bac41264a2cfd9e846fb510e6b37fae82fc9d560fabda67e2b29c9bb2a5c6b44afb3c0d28e0efceb9cdb58ebb24196a40aad70433e4b068cc695467ecc1c43d6637532a55f7b42f784a34792bde57242b4ddb72b801efe2663db5aa011de1cafe4332d37c7ee7f5fa4e526fc3fb6b1cd8e6b5c3cb0c7ac7aa789ea9d4a4d0b45c62ff16ed24695a760108496248beefc5f7d2f0513595f5541e0fc402592c93ea6c9a160e38d5009f43417460254670978d967c55e600640dd3568620306f5800b0265510c358c935d68b3a50805be39aeb44fe74bfd7249065c9331d914c6fc0213be24cd549d7769ed68f8ecdaa5c18b9d42559a1290512163b0b414cfc26d08096d582abb8846a191c7035281df2edfb85899594e29af2e28f269f6034783e939aa91ff560a71ddec47e43f2246191344b596edf24c5b3c96135cee105d1b63fe00d5ca88a3e19e49ef0c21e0e5b1477d966a9d3ab09e26b77c1cc2d79602643b4876a493d86e6fe61211e969755461ef008e1814c222b65ee5a6f64959fc5a5271e9871e5cea808fd05bf931791483a2cc86db3d31ef57505e5d293f73fccaf956204d88050023a37215f9fdf7a723bcc3530002b93a323d7c511593685e806a6a82c41758742f132ec3fe46f9b633844bb99531871516213b80f0cd8faab289a228dc90955464056ffae3fda62d7e78261d323c0d010320f84dc8fbcd82a1d66e77002a3b6b04e8652776febcfd400444fefeee08b11a5f691e2a0f10a99b11596c619a6b8799302c1d20fd27f3613d39b029832dbfa83f6e987606d5efa6ff05437d54088ed5e12eb85d190e6fb7491452136c53b39b8f3494bc111be9caeb9043bafa076b6401ea33beafe05e9373337e20cb60365e4644a4dc8fce41851c58a9027afa91e12bc9bd29cb4825c89990e9ef3e3e3c2a0f8875c921d7450736e449b3a4dcf1c6d85b477d5f90605e96f972aaebc740f57c5368b0326ba63a60330be901db8702c496dd55073b3556b49874b95054af146347fb7b0fcfecc5e560dec0a0dad267f82db0abaa2602cc90107972f25bc44d8336e340f68666aa720a2b3473a032642bc565066ed69e48fbb4f1b7b6b25a9209791d014fdab9f644d4929408271fbd39fd6ea8014ce9f8c73cc0d3eed727b58ebd6aeedaeba1439bcbaa1ba087b76ad83152feea531ef7fc04154d52e7ffb84688b8a32703b49497ba8fd3ca0fd935a40e80b4656e8caab2a661f8bf7f64060ea94eb8a017601b6b65a86fbdcaedf9f88cacf6c5664f6cf3d327ef7970da3a408dde698c9adcbf288c758baa3bec6c56e039ebda448a5a740d702cb51c124484e5fe067c9d84ffd7d74fd2c35a1ef9f85bf1ef0b19e9c4e0b7b56a5b0efeff1b811b8184c906e630cd6e3b0cd0803253dc6df6720cd826bc7968a4fcae01ac728500993a9bf43dfab089b957612a60946ff0a054ce8b8a22574a3a2a3741bc15cee76afb1a7c4d2fb30188f19a5a5c97523980a8b036fb7a73ca9a4135da9010e7687344a5696667371872d9dc7b0f573e83a306b25835acbd6d82fd4655f557350a5926aea9bc9001602bfef78219144125ed36dc718d8f68dfbc221e32826e75ff202b509f05d46223414473a1cef9c62401fa050253d9dc427d080e83f6d1043795b280659e014320041c1009b445757de9ae7d05094c793c065b272812ac2882be5d8ef2b24ec7cf5b0b19ebf457f7cccfcc78cf4717036c0c320f1aaf8329e4a1cc9918f774269529194a7c61d4b98b57817c633689d2dafa74b16e83152a5a4710af737b2a8bb218b88b217057be1b99f665954e916a95aa147f1aca11a7bf2a53981c16209a42e43f74ca03f2cd51a5ec3f096fb91637786edf52030715f8bb05e42ab005737f669343987424198f180c1b1d091f19ccb01e1be2c2f4dd81ff156da744c982db9572d101760b06ce8ec117932187011b2870b698e4741a255065a1a7bef743d7851f25ef7af88b05389afb74e4623af4060f27328c60862dd03f60e2e092e75ac8718b39ae3825d8886970e69b46040951da1ba18df44ab2e9184a5d11505e02b57af3764ad41d8baf6a09b418cbf1469c05a3dd4094628866246fc2acd9d71d11b63429fb630121f97290fcddac3ca5a7759b883a6f688c7738120e4b703000d0e90e6d01369fe4d4df01918d5c7ed70ec13e03403569106defd3e231153223bcd86911c860ac127f8c517a5d5e6ffce32ca921e301d2d93c30eb4ecc70dd42f42603ff65f32fa9d0bab10815d7781f08984d94665375e4afd2da520cffdbf405c6ee79e35054cbe1a3055e5d438d5cf0bb58ed416af6180618e2bf6961976a46ecd6767911180faa2fcff356cb86349b2cfb0678aedea53a41da34d30fc3ef8c6aa9a0b3482fc9dbb365d14e1350ffcb6260f91b6e18bbb4a7fd8d5bd2c1976a3e9d88254290ca81d90430be1dfe007c03982d30ca8231433003cdb5e674d10f0ee6df4c22daa4b8f059094114b940b509960be05ca97decc9959e3b919e28140d0770f62becc7820f6f955a57e6a2b222323eb77a54bf3074c0137e8fc4fdcfe08b02caa60af9c5aebcae6a4646b7d19ec3fb6b7a5e4c01935079c8372e129d66ee4f6e6160e04a940e3076cdc3ac60a1be15c7809ce9e1652829c6356d54ebf0481279d654cf244cdf22ec81295709c27b612b382e6a447b2ce161f648ec7a0333da72c7af7446b199c5b78182b65628a394ce4293b6e4b7decb9502ade7a252ffb378d4a9997044ceae7f16600e2a27e5f4cf317bf782698f5e8ab93b5909407df7724b8503ee1412a0eb6f9d54d1db721192f8d547cb61aa55b0473670133b46b2e07450761850c48d3ab4d40a9d43bf9a9100c3b424a3c5dad9900b02c94506a46b95685bb615a42d7de25c5e80f3ee5d2153cbcf14527fc0c603b60107c7a80adf718eb1cc96405113b8d02f07f16796434e9eec4aa4230f17aee99f3f88ac3171217c1d2c5def9b5e5d7a567c314fc5641cde8265150c2d8c396f836540a9631ebeb65158619a6cde19bc7a0917ee95d623130fb4aaefd70e24a0718c88e75711dc19983d1d00a9a1f8a7429072f2575901a56c195d4ae9afc1a0ac0bab0807938b2432fd4d26630230bbcc105aef1ea5c5965e0e537dc385da2381efe09da1cdfe872b3e57ea74be5f811a55a4369038dcb16a1e87122a60b3a5e8a219a6dc41a66abf6af59e4697bf3f7d924ba11bc8dc03614d14bfe6c70f5763f6a9779444a3f196646c1139f1c4b21a813cc9a80d39e61599850d54c002cfab648c852e85f893799f78d5e420e8f2cb312834dd4c82146dc456cb5d07662934dab65dcb35ebf1ca2b567c4940ee564854f6ea25912706d42094d6a23dad6a150ac1884c810154fc633670fa3852988fd6d563440cae2feb35047ba409286e2af782b69c1159c08d523acca4582e26f29043decbde4876a9c3f0c4ee5847bb9bc80d70171717f3a4bf8d005cff35c7080387ec65ec81a24e0edc11b2306a396e93e6441b15697d910d9c20203a970848a938a2ed07c67fb82dc2c4e37312de59aa691eeb67a5a87f531f74dcb516fb3e12c0063ed69b80da8b07a0c61e89e84ff62d2999f32bfab192b4a21b81ce097ee6f0541ac82a0db6cac0e6f0a5e2c8527fb7455af2a2d94f0e53f553ef4ee635f7cded4c485e0657df83ddb26112fd1499e08d8e1337572f972007b30ce47136430653802b04a1868b24be6f07351e026970b573bfcbe4e706c0071073a40add46ba4db1019dba8c3689ef64d6a401cb2a8099acc084652fb0eca614064bf2e79a1148e4bf7c79ba4f47c047f4a704ebd81b954cc8a17caebefc4d765c7febcd1418fcae664eb5b3840acbd326940cf0c1415bf79aaa02b7840a8dd9e18ab513a1cb48a006b46cd79b3ee72a0b3fac7715a40cae505f226d691e5f4b5e12aafc9440c46afa2ea1e0a9ae73c30f32063d42100130b147954068942d49ddf77b3397ded414bf3a1df622a8e4b0fc77dfb4b16f01f52f91e506b30c93433e0b5af633be49c9f40dbbc35bc7887797a99dfebf252680e2b3050277964f1a93a4ff8d35561f84aee99dd92d4087884d8ad1c6ab936c0ffe7cf4a939f736b42efb8186f97fd09a3dc15c02422ce3450ea4c5c191a9f6c4f856487a396b4b6dbb0d18cb939803fb918a757863b972be452cdb00066593b5b55190e52babc19e490cc40b635c5800798abf3a8c8896bac372face9ae3f40857ea06deca74542ec9afc8de3ae3a38e869704671437a91099995d0591845674ab567a6295a78abd8908ae2577df728ca7fa37e12017a474cf94bc48145ea3e87b24b7609101264fbecefa12dec3a92f63425ae08e70fd92063b04490b2dd1b421af24fefe4dbc6c9e0dbf452bbd94744a81d2c4ed63b18a2fbf9c9ee112e5025f13704959f5587398239b3c11f70ae52780d4de66820a366b14af60e580c75b4791b17a737fb79a008c418a93839abbacb3f2198052a2f2e86bb176136274e37291f48aae218477176a085739a15b92faba87af389986eda11e49600205a7219aaedcbd116d1ab4f4883460f344c3d28749520fde3c55ed5936b8e557cd12e74155af547b7f8101a0efa15e43ab49261055766b4d6e090e6709e41b4d62d72470f69456159b5af0b49b6182388fb8cd4337167da559e5e003b1974b01c8a88824f1c8fa4439c0199489878cbcc12147f38a9b9036bc53702c5c32bb3405a4e246433a279b67278ea2d1294b0402692e3ec064f7b24f2019e461e87d315692f0cd7a80ebb3d74f51757d9afc843dff3f4c8168c3edfa1c8b890d305b70a992cd91eb43e630dd916cf8c1ebfb0f1205740be7a7ae3d79d6ce6e6cc5f239ded7a8d1ae01f0517b10d4d7cfb41f5e10b08d88edfb7379cb8b14784d98ba399e24d8500b1eae6afe3b53fb43413624744a163239e73fce9ad202a3d965386babd0b602286480b07e59da8c0d6bcc71a1b0e0271bb596fa5340ee4758d6be1c2791f9d6d6fe169277f74e0af066a59ce4325365cc90df8c6ab7e8bc9300fcf3b9d33334edc38f224723d79a85867081c4ccea2ce550a5eb973c01bf037a69622970c357ead46b867bce4832d39e8344976b0f2902a3b06bd8cea4981ade0c8bded2e235a641a786557648f0b0c4da389a04c8281a556827949d03987eff4c32ba24ea2fd70bae8476580724035d024a5311b54de73e459528d88f20e70a473a38cd48a8fa96b826bd079a2c5910e3c6292cb937bb9804b59b836bcea6cee87cf35d74216702a3c5a90604d971c939da147fbab620600bf43f6336e6260218bbaf62d320ef1ea360df02a7d7df3b0f8c3e472b2912ea52fce0eda3208dcc5256a6dba43664c23abadce7cf6e906d2550c29700ca43d6b23b36845f148051c1e0adf85a178f3c87d6b9d01cdacd7aa8dea001ea9d1ce24591321ab518883b1e107a7b6d55b6bcec93356dd084e879ff22d55866397751386bd01393237825d30d0ce005c230e87e4fd0fe22a6a89eebd7a10d04d780a5624bb2657b5642155789bfdf0397372433b1c519532db2d2046552a13c574e64234e51e14ea2206122a069a638694d0013db363d50e6c5f0858ceaa9fa5797bce9e2fe96b3b07b30d7fc66fedade73a70717977f06947be0c539404daaaf7bf9fc07b24e7654da4951f24ce57a614c23c19f1b5cfcfc62ecae6a7f7b1db80963e802c7754f1532e53b4338ea1eda9e8975684fc2b7b5ef693c0afe49813b5803ff894acc7ac503fced347624653376263120f57147801c487b7d06b4a489c7cbe8a4d257b80e493c7581e0abdd1502d9665f6f7669a059a27fcef78771b153a3ef2cc2aa3c86d8422f9869962c2bfef8d8c35a9ac14fbf08ebb2bd74a1f0e58efd97aef23a37185f048fce21294de9dee17f1844dc39423e8e80fa46ba88aa9031c4b0dc05ae7810e12acac4399974fbcbc870eee1bb8c9737c5bd820c1d627e8152d1dc7c53f2e4305073246a70e38fd52c5dcd7e888a9ce29b4ea6591308ab30bcd61c905873b6ad781edb7c69fc33679f23cacd96f4d93fb543dff517a83452b78d366973dada30c3ef667f3b77180112a03a47ac61aa7561cbb1377b35f944cecbb70f2adf2090fbf36b355a4ba110ecbe8612680c2c4ce1c3b3d108c927a1295739eed73ad2344854d694aae5e3da8616514189cc45afb38c5c9614163f4d3b49381df57c06b1bd6d3b78f95c596eb5b4592e336d87a76d84b365547463bab704720a8b54bdce188f7a350468f80dae2bb90a4f693c8e59c3a06926611a94e6f7caa5c66de3cd960db34a7d250683e3ffb4210f0906b8a05ec02303377e66c236eb2649ce14e95cbf510a951fed229fb1acc07b2cdc8524da3b2c4da53c51c573d340062cf759aaa10af3eec1950d07f86f839e68fe1f2b7b1c4393dbd848c180ea8906036acf219931c7ab4b9537ded65be86525e3f6a2725e17f4d2ce1aca6388905240387c13b69b51c938800743a3328d6842bcb4ebb7694a5f67ad7a103ef716e18229cf8ef83ed27ff1ae9f5246285de14df42e59e92e19b543e9709a00cfcc068ceb545b0c3fe29c4e940e70e0b7c438e7f33c36d0496feea47897890fbfab388102efc713f78388cbdb800c07b27c7b3a4b250565407866b1ee8c1030d7807f21d00226fed60bb6107c39240c6daf0bda5737b0d050dd313c47d6ab2e418f9a858e2aec06d88c17176de2215065eda699bd94f15305e1aac8c737a89c682dad746e7719f6abab762eea51a7738ebc1b0595438f00065c7c0a6e428f3b42b6d5061e80953cc3ea3aba27849349b4d3c59542d59cd099122bfff13ee2fb9a512ac2e1d43c84cfeb06953960e735177d77ae84b3502645e06debd2c75a51fc6f27f0a060ddb6593a6566730f23ef57ccfe40dc8905d40104542a10ae070cf45a44ba4b20a3849f6bd550d52604359c9baa2720ee237bcdb6d8ad87ed0c53615555f5eaee554485dc77716eb237587d8d1624ddf7487e9f21512e5489a024092d35af1d9101f213dd1f4b8349ebcecf88fa8a51c095384b84cb8f951e445c55e931a169de92491f730ff5d7cade355c54ad03d5153e90575a3b9e18760ed877d247458c8395536316a5265b34e475f9fa0d8a3851eea3acf47b322b92d54c66cf7d1da10bc7d1b1b790e68c45cc1cce1f2c66667c603a40aea4d82b6ec9a8eee4f6ab49982971ae5a0d3d51b037102ec1e65cc9fe2d57efa3adefd13858ad4706f0e057abd0292a901850940faba635b0f4601c3cc47b17808fad9531972cc0098aa4159f7cfb67a240b216fa94d404b40624a77f75426cd7838c14c1c660374317245dcff8748cc5504f2082585bdb41e748fcc890100b5b5295a711f1a43b961a3bc63e59ed222f5586313ce2acc0957845b82464d2c530e18e05c0c80d37c83f014a5d2f52d7b3aeadebd85a392a8c47161d255c207447460724d8a09042f2463bfac902c4bb555a0679344246c2a38c06e4c62b3b184338fa586a91cdcfd195f8406ff99378ce481bd3846dec65a3d8984a2152070511d844e1d620d1a2e213822826a5585bc21d0f750e4236f3933616248930e35f2a7f7b5228c440ca9d14ac90aea63eb03259a4085e7944d571b3cf2dca973a37b3514db73b85fa396db54034c93dc7953708368586bf8c995c86cf867411a200bcf0460f7187d1bf4a433bc5bc3f41f1f84d1fe7790f2dda83d32a6ef038253f81fae9a1e1dd5da916ebb7086592ccbc00fe06046d9bcd6da41a414121c1cf135daeb76f1437adba486f9b559f36c90b30fd2cff0e41215c70cc5272d027e429e7bc70399efb7f638e3e91105462e3325afe56ed24f6f80709e5b7312cbc81b1a848d22325b8041185945e6aa623d0c5291d8cebf4197b5c6df2b8d3578dfcbb1b36e40ed9519107cefc1f59977b1a61484d64468935f94086f47638eccf9b8d1a43ea942caccbaf9ad423ff5630d0ebf8a5af5448895653d1b538032a12724b1e05046be492ecf54c70e1347fcceeb7bdfe9d40ffb5062cb125d42e3c8d11d16110b0b3ad5dd90e94e65526ee708da8a5ea4c6e89b52c6736ff2e83572e2679ca786fe1f47bff89e9e089c73d30102783aa15bac1a0c4f7d197ca09d4a03e29995fb331c4a58bc765bd021f5da7004922f647b01599fdcc1295e792d1a49c133dbf3022f4e44c9c460f20c5064fbb843667e2fe8c41a08f1f0c9946cedcbd0983401f7f0c09ed9c993b130681be3f18725a391377260c923efe3164347226eecc0c027dff614868e44cdc4f1864fafac190d0c899dd993348f4f18321a1cd99b89702402176d8ed08cd393737160602b58efb9ea424064588aeb9dcd3d0acefbccf9d2d1cb897efd089b92a9de53513bea44653dbc598a426124a1d28a60a849adcfd98e0eb8723f90948a50bc8a9ca635611b22a08924f28e82fddac4822b1bf4859f3114bc9a3006eb83edac35a1588056d143f7a2b1f182d75337f044500b34ca75f79a9d80aba43dd49789987b8113cbcb6871f8de7647a4dd6632d4c5431d84fef0d755a907ac06a2f112af8a9e4a81327d9a0fbd68d6045bd21bc680b381392c810224615e9cda5fbcdb9e5187ce45553770611a53ebc66adeb05a889cf2bafa71804bcaf0c98025ea2cc2e1ed4c8581f8b5d6cf7b1bf2da2f12f598af4650f1cf73c658f2b3fb0386b39a5b42ff5c7bbb50fd9fdf307f50f9a6fd33b6e3f7ae1a217b6dd2bf422ed17efa92a6014fb251f69c175cfac92c1779a5b878d1290ac9470412f1c197e74e92ba1075ba5cbd8da2a02aa52ab5fc21742d8b78ff2a0ac98b7aac7f0af54c5ebccd96b43c8b8a6e2e8e9c5aaf8cf1a0b47339a5bf4b1b24891607c0efb4af46289dd51fef4f8aa47a29ff177a5975c89270a0e7d3fe61bdc35b535a44d48873fc88ac5e422bfbaeaf32018bc96254e3acf82c67c3b0e86db87d5c868305eba5480a880b39fdb19377727ffdd30a2891ca8d00af120cce40432170e9be084f244cb31749b6e6c2ee08a23521cb7b3103f489afb54c10e0fddcc6e14abc47652331ba3e3fb99426e8c03c7a1f340e8ff2bf29bac21900dc21882e26243d8cb4522af1ca8297a44a715b50a3c6a8a44be3c0ca08a6e1a2c31c50524ac848e7ceed9cb9663a002e8a8316b85dbc59d6bdacd066e42d88b2c87f2dcf0e46d27f659b3b5d9a1f361eef4b6c6ad71d53d1812024e0c3ef54acab21bcffcf3f254962720e36b709857929ea4ecdb0bc2a3337d7abaac8ec8a0bf3b8a969d59c9f60efe919754387c3ec241921f30fb72909846500975bcd80325a73ae87cf35c298bad1899ec899901cd1c43dccecd122f6cd71d01d34b39f59f683b394d536cb5e63944fac5ecea6f48a6963cc43775fa1553b7990de54181f581e897d6fae6259a1af9c50359a0049543c7e8f80e964e0e674214f35978804ed669d8039de79f6b2430d83e91cc6d30555ae1e8b6413454a7d297964c8f32da9780b27f2610a8583dc79492cf66bcc85dca6567376f429f4c7430c64daa2bad8d3e601738c6f970995b126368eedc6500832f164cdc76febc7c70a9cfe1b22d37a9be0d22eba8e30de33f6759006c4049628df72c479c13c301182c9be1ffc85f8cee92d471f20d15e8e58cba5b887b271c2f74d8702a40d5dec0509cee3b491bc689733310fd38caac13b1d46328656a70d50ea709de4eb143e28000130b69b30c081bf6864766003a2149d3443d6a23059a5858e0933a6e2c7301c0b917e6af1eadbd919595e616726f5e3ce2d2ae38973ba31347227db324df30e3d9de8a69fee0c0a4e7c0d30538ead3398ed480636ddfb1f11cdcee240e9eb522a29c7da30140ebbcfe2a80978f048508506848778112bab9e3beee0bb45d8f9240628d74c0cab9a62afc18b87b1070c6473fd4cf7215c64371b1c51367b7fab733e287e634e7e8417514d61dd433334329da8c9f23908647fb819220b9845c2c9310009953f8bd440869605f4278e1fade0fb337a8f90fa5ca5c385efc069f09eaf672b492a3753d9103f7dfa81522a7138a9462dbe4a8493b1d85b0fa7e6576cbe37daac420624159a8280937b6ffd261db124ebb9db61df40f72be264e9697ecfd51a910e8601a0dc9eecf88b8228870f39a6f3262a4668b4774987fbac3111219cc38d179d9e2dc97ccd7d103cc5b9a82a2aac398041a47cc0134242fa21e7145aba04e312ac1673a17a44aa883625155dfd99c7775f06202c5bbb84db5a5126356c7aab42d88e424b1e10212b3c65c09558a1f7617809938f641f8865923d66db25127875d525ae08018351287aef06d30b8f1716242951b520a41e3c3674d78602599d137b6fe08407cc01679dde82bb8a229035c00c1f251c988bd5e26f52438901c9e95c1935a01845e2d030c21553be875b9389710830df98c21ea60a638e290fd674d208a9e08eccfb5953a69fcc31a4a334a142e3d56d6c47cc7ea08aae7a644484b8e7c7486cdbd54417dae90c8bb0cf056b36dd3dffa75d8c6c04690c2b381a7630254882a787ea7ef3fd6c094ec220f28f5a23777aa0f5b52940610ac604483b65d1d51ff78a03e06d2b265b7f422bddc8dc664dbe8bded1dca79075765a342021dd040f115ddd990b11e8ee06782788dea5f2de027116625947b120af12a513e0a71c7a6ac621c905d42e32ea8394e04f9c124bf325325841750f6b2c8c4eb0d46497e899d24f050bbc32648cc5022487dc70cfa02f314afd400dfa4752608c18eb261fa07950aa06457c50f73f072b122b9b4c435f3700b1d475d740b97ec925267e5268e479ec31962ae50780ca522dd44ffc8c58555f729bbe38b920c4c8f12be398773f06692549a0499c82356739b9097f50c4aa6835f2b312e243ddd3c44a7c7bafe1ee8f2e6d7c3202296288a533254bfa68d920d6148a65e7a24147c5e6dd22cf42a1f5809e764d5c699e83b23bec8d26162be87ad2ada6fd18f8dbfb53edb0b20a1dd16c66f713d5a09e1150a4fbcbd09703a511870916832ae9c7450d43cdd0403978f2a22fc0ae4b1f7f0b024bc1a2465708409398407f1bd3f8162b5de7aa20eff2022c0e653166c52b2f056f41ff0d9139e13666e9df3c0a7eaafd9366d3ac8d0f7a3b9cfe3139c710edb9e3843c153105c0669aab3fe5f416ab2e34b486d833c8d0b1b35d3ce46d9192fde2da681d6c866a23ce9341d9658795909ac9f5cba0e09e90420c9425348bbff2a22a1f3fc967e0b1e7de4fd0accaa1338c49e8dcffdab5504c870a6c224b2d9b0f5fab9c8541c836431100b4b9ddadc856ccf0086cf0b4afff256705bcf5f3d780893e6b5408708247cd1108977fbb03891655c26263182cc3672b47f235ee6326440431e347fd06fa85929c805bd7a867a9425e3d3144b4a48231a8db5a23a2c9a46c185ded954a911b2da6fd78e4e3b10a1f6905ede3bca93c9d5f5359ff6fec822102a2c3c00af0bd936e8a8fe16cee6ac8722b1356aa57b52cc2799ab7f8fbfc3dc8b5a4af253a45a077f857e36fb11540c3a58669cbeb476162ae49c54d67626be844425c540b1e83eacc14235201ab2f70404ef53eaafaa7ffa09c398eb8cb2ec6b408c5d19a74033c2d8187fe3ae1757aa901af08b6c62e36d4c6c600c466342462c14af56ecb965b5e8962933c44f84cf5eb54c7c66c367744d25acda01d3851d5884589cb8862d5f55daede91dbcd2108195f54e6c055d47719acf669a1b4c4ca3c92cbc1e573c93edfeb937350ef4605a02b486b3ba1e7a9e0fb6e01a4bce23c3cb434785793a8861019e06fcb200d07f329125265f5f7290bd654ff08a542fcba2255eb82539114f316f03f21bc938530837e75b631a02d6ead629cdbc20bdabfd24a0e174a4f6244f1a9c271e0284f49a6d970790347d8cb7109d1a846ab50d536fd463a47fb991e094680d6912d612afc50da4e360a3a360f53c559bc2d7e54b41602ec08927aed604e736ba57dfa84c1e898583d4aafe1598d809bec11d8fd83bf41f22dd054f928e1e2429f6bc25a5f90d66ed52c692aa7d130becff018460037a55bfb258200c5a351d575f5c2af1d94a6492bf661e4408bcab4105bead218333e18482f41d74f4c30b0a1c865e9bca379d7b5e66a3574ca74b6aad4657b30c276a74be5ebbeb03116804cae3131a55a089b6ddc86e6b7b6f29a59429c80a8e0a3d0c349f5236be42ec4ea45b6f72123d88e653cac6977527f27a10cd9b9c80dcc79b9c7ebcf426a71e977993d3cc476f728a79e84ea4bfbcc909f4a07c03ca252dfb4fa6f964a29e5c2b736d9689649916cb377722b38fb984e57e6552ccb776403dcc62eda07a931398c9da01759cbb7650dde966b176d041f576271b7d618def2b99da975ebe24fa52e84ba02f615fbabe74c302ded28d30ebabdf68df5b0781a99da9f3e92f677ae593487426319fb5d099c27cd2406702f359565c33e17c92d7996e3355b7b56bcd39829ee65bc5d13763571937425fba715d747a03f4a51bd85f7e03e64b37ac8f7e2346bacc6f9433efb9f1e3e334bf1104e42a6e08f912f51b35bf41e44b342fb1b861f31537a82fd5acc4b4b41442084b365faaf952902ff9f8d2cc97ac932ec2cb45a0de7f83fa12a5f95ca279f2e4f96a5b914f225b3ebd36944fa15a3e898054e43308289f351f5a3e7f7cf2499bf5e4b39ce5b32465f98c91f924ade718fa319f309bcf1c438f3dc7d0bff2d9be3f730cbd98cf90870f9758e4c993e7713e730cfdcd67873cbcf51307f836e708fa9a6f44f402192b7dc1c8d74c4462c2017ee84b05f81c34a146625a9a7d8ea0c7484c4bf673d49ae65bc55130846522d82bd762a2b7cb448b94c3e743a41cb00f9172d40f9172900f22e5b80f22e5903d46ca013e46ca513e520eb1916e769f46b2b9ac388af4a2b6cad855061313130e1c82306f1745d84be6ede448ca62de5ef6943ea3793b4df63e34c701fe8fe308faa5d9abf0f61af940ce94e3be0ac7713fc871d07ea9006ff376a22a830987cfd79c0907ec859c2907f836c7517f88890947f91638130ef1573891e3a8fd927d16de7eab329870c89e29477d16b9ba05a6ed22ea2bccdb45d4894cdc45d46de6ab8ba80f996217511732c32ea25e33c12ea2aec224bb887a90397611752013d645d469e6ac8ba8ff98b22ea2ee63c6ba887a8f597611f5d2ece922ea33d3a78ba8cbcca02ea21e3381ba883a69d2ba88fa68fe741175982964d6ba88ba68aae822eaa149d445d441d3d64545d4afb9a28ba85bd30293451751d76101b55de3ad324e1bf6fefab00a0594bf0eeef8f671a427b880eba2c884e38e641eed251d36fa68631dab5183f4a5d9bfbcc642089df45be471c44450f1349f2678582c9f3674487172d6189d2e00d451c3d70cf325fa95dea7394bbe7a38a7ca5797fafaf59fc0f3c4d66ff0ed7b628627f373bc7c75108c89501dac0fd2b7277d30749babd3dfb717dbc69982d5c1bfde4bb307fd0c9d08f339ee8335acfd91c6b08722cc1ea4ed01e5ba7a26fa38ec9918ba2317b41c01c31263c634d169b0d8c069024c1074aa705d7971c920e483eee85edb82940f3cb02902e4863348be00f1c40d35fc600506ae212497929f1eac966871b7a06fdf0aa22e109f889b5e5b2bef08a9652d0657136fa576d65a6bf319c39a49d6cf18474915c7864b882f5fe09aab07aeac3f7141683101c1355c520f825a537cf081a8e6367e98b2810b8614de5c99e2a5c9105a5b58729e184209130d9bd7191859064f19cff6b3c74f1f3f493f657ecefc2cfd8cf909f3f3e5e7e867e87e827e9e95e63f3ed26834ae6ffa306ce554106c744f8f564f4f4f564ffbf9e33e3efebcee8f597d3f26fdd107867d4c6b835ef998f47b4cfabc2eaaa992d755d21f63a4cb7c2449522693c19199f4c9126c0e068bbd5eaf11e6a48f3018cc4792e41169d287e9425ccde65eaf570983c1e8bfdae6428c310882346c0e5b6babeebd5436d74e69bb520efb515000e4afefd80897c71b81f2783b20af23b46f0744df89eaa0d552c912536163d0826649d27cb8c1ea2a8e103e298ee8806272c5051a8e0c61e60913752c135100f1a045c3ddc66fdf124ec8ea66ec5bcd2a4ff1b958a317f551ac97568582f30229970bb41c9147af3a767b77019babd608e6d5cbda0833e92e346dd8dc0b279a5a361242175dcc268c1eee7a37521206820df10573317498188a2efa68365921813201830ebc9d094e4d4e2f87f9989d5e6e82132cf4b05655ab97d9b503ac65839ad5aae26895a76ea3ba8d04d15f2e86a68d84d05f1efacb86db5cad553657eb15ae609c516c24fa75d73eceac68deb12512605efe748c6632d17cfc4533997e68f48a72a13e9f17cdeaaa3c8af5d4cbacf594723d751f38729efacf6b46565026731bcd4b1fd1631ecb26ecac93602321e63dde934dd8dd9e6b7b7209b503ea369acb5cc7aef431997c448f551763a2c7b20dc8676095d503651b3c4af5d469a60da8a72e33a7987e9c698904985bf771a62512b0bfdc271fa03fca75a6129a42071de625348d3f26d38fc7bcc6bc562ab36d8ee631d3e6e3a4ebd88d3f2613902f31916023013bcc61d904d0b1f72ee624d84880f9cc67d9849dcc1bd0bb998f346643e6346623e63f26fd8ed998e5ae1dd09859da7cbcba8e1dcd3c6b95cdfdf8086436a0774fd8613ed21809a0630732fb613ee60a6573d461d946f397d3b0395aa7b26cb46ce3ead8fd983ea60d9dd5719dca2aaa19bb8e1714ed11363f982146bcf7dedb0498bff7de7befb5f736c1c2dfebe2d55f27b3fede7befbd14dfdbc490bfb2bf4d30f97befbdd68295f6ed5b62cacfbe7d4b80017b3873e64c0688bc1004073748d880c399af5d3b4c0933870918282a2c4c905982cd93e2d107a049e41547dbbc6a6e2dde1a67d0b0b6664bcb84116f9928c2ad3dc25ab5183547fd9a3aeb349fb75afd568deeee8512469478aa815446caeb1f2f5788f065413dc96933df47d6e8ee0800049233bf213b8c59b36bef0edc1ad1ad5673d47f48224f1277d6d49288228939be24e6bc0e0fe814db915ed16fe8a059c9ba78620f4fea30ea98b4b6d62e7ae5939eb4e9411fcd137b08559388f36ef5d56b555fbfda1a5b80fd933acc5cb21484eb230e4db2ae50bebc17f072d05ff9c4d8a99275d1c31808972c69610c843037ed7900101ee8f43b06d6c00ee653c93a761ab38e4da5eb57242b8d99ba91824a1c1d5589e2d1086e21b839b97704374bb7aca567e8a2571fbafe7a99f74979b1f8620efa34df1cf8c14c86502b90f1882824a27a37ccae444e9eb746352b670e10394b9c20e785283966fee7db27878610899c2950725c7892d3e36374667c40433c72c91c3529c568f1f98822a20e981436308512375b45a264f8c126492c8aac0c8db25696466865a9287fda28067ed94a228adb8700d03fa02258a475c6cd9d29769ca1a18bf03286891b869022c51a3642c423974c596b52d2ca078c01f2c44490362edc3941159a24a03cbd918202cfe30f777cf8e10e474dca32f4e6fad06f18866158bec0d568255159d6fc881fc01564b1a8b5d10cf1e52d6ab71a91d788fc56731ea26acd6b44332c9803901934daf741cde7a356abc5d039514db446b717ed9b41a3e6b823b4fca9132b9e62fc213b478d74946251a782fc9226a62ca9d7ce30eb2a8e7a086e7f14a3e2e8093cc230745dc51d857eea62541cd5592332d485473c7dd8631786614892d8bcd14a93acd51cca53a73b2269442abe7d472cf9fbed3b82eacfdb9582ea00a4ca57a76960cecf44d1ecf72ae39ea108d7c3bce8a1115b1e2445b37a29afc3407cfd450b1d966d35c05726dd8a4aa1d7588dd0c5df2ac3ac143ae8275971b1048039ccee9928ca087aadca20a2b51171fe3a7623d47c8b6f5f114c7eb4b52ae3ac8e495acd4338ed933d0ff851956b5909f4eaa2d3b07962279d66259cad91a9f2bc4374089616c148c560f1b83d0ff8c1e5c9daf38028ae8aeb1f2b79abed5e6bf4e2d1da47049e9bf524f3c591f2e72dced39fba0bfe8cf194bed9fab37d1de6a95f5b471b0ff801e70765e471d43fdbb7f5d52df501bc8d2d007c6af38c7e51bd4651f69f942a0cd58c8aabce426936edae55e12db7204762dfd0deb993c5420f60b44e406277d6becc834d8466698d54b0c40c912966861831c48e96ad1011f6fdf37143c5c2d77d48f592f8f0f5b80df1e1e5e9e6a34b8f9b1d727db0f0e1f7cfc7923f75595b468ac954354f6dfd3df97e3d76e80991b236df6c73d47f3d1e7fefbd17ac8160d7df1fbb7befc5d9fa8831362a6b8efafd3d35f1f73a79efcd5197567341dfbf2736fffafe3d65fd59faddb2503ca4c0eea2504c8307c403f6d8f261a9d31d61a781dd460f288f9dc4f8621ceaf0edd86f78ebcf178f1d742e6b71fdae9539c4bf16044110f479aa4317b5878de4f31804bb8b28982b985b3473dd56bdf5211fe1f7af4798cff251070300749c0924b69c28e2de3ab54e175947ba6155e822eb95cecd548d74a9bacc7717f9496fb654f6a9f4794b659334525b07a9d1ef38d3392ae377ac695cff297b1bc3f79f3ebf634ce3a807bfa34c6fd0556497ad366d7b94fde311e7cf1f8f257f865f7f3cda7cfdf1b68c7a57fdb703cf53acb73e5a1fef2f8062fd8d8940c52cdd9e7e89e6a158d483feae77cadab7d3288d74833e3d8dd245ed48375ce8a27617dee6259ae7b11ae9ba15f3631ae9a46f33c5eaa2f6315dd44e756824eaed8d64f392db3cd66bff786dfe14af00c6aea0d7ebb69a43a82f7ae84715f412749ae82b6baec11a98abfe32c59aab2e86f7c723f3ddc3770c841b682b3de2fb8e5da7d8fafe7dfba698f2e36c6c6b34b4c2db6990c01a5d6f22efd1db67582398b77fc01a65e0cdc2db56faccdb2f608db277ccdb7420eff612acd18f3706de3dde8eb346427ef3f613582315de2d62b466deb20b87e12f3cc86c917bb7167e81fb9818e45e0e03c7b9ccccb907a3c8839c34cddcbb20bbe92f3383dc2b0af2172e9a2c720f97816be0a169cbbd6cfacd417345eed940b72651ee65a081e77cc8ecad70d059500762e1440ee6a8173b0bbc347b2c72948b5d4fe62c88dce6a3d923ca5140bb9ee844394ab6735f917b435eba90d91b6d0ee434b36703f2153e337b4039caee7a352f57f8cc61331f7d458e2ac2666f96a306b0eb91ce62cc51446ecb51b45d0fbbcd7fcc9e0b18394a68d77b399117e52871d7b36ef36bf6827294cfae17e4e58b1c45773d205fe12e7214b8ebcd7a1ae428dbae07ba2d97a36ebb9e90978e81c770396ab6ebc59c856790a38e76bdd0891c831c05dbf57e7c85f798bd16dea3a76377418eca60d7bb6ef39ca372bb9e0a2f732f7b94d9ebd1d3b1bbe52802ec7aa5b360e1b0dc03f21ecd7b3fdeeb711f6f91a38c763d9813e5dc03bd87bd77bde7668eaabb5e8faf70377bd56db9c722f746efc1bcf7f25ee8a247d19dcd2d307b345f917b517447947ba5f766de93798ff498db726fc87b42deab792fc8550899389b6baf99366cae5d8579029b6b0f326995cdb5039934536773ed3f660936d7ee63529ecd59d95c7b69cecc239b6b979917b0b9f69849a16cae9d34471366ceb0b9f697f9019b9ba25c6cae1d34dde6dab149c3e6daaf49029b6bb726ad26f5d99c4f8aac3fdb8767ea71d857a25efb5a111583f8bd79563084f6751781331b5d44fbf7ad6b233a6573651b512d3b6ba4ea35ac29df510dd75a5ae67d3a658d48d039eb5c535c535c4bf47b67a373d66bb5b2ec1c9d1ad24516f7154e7952adac2afce60e97ef1f2969a391dae6acfabe72a9e6d4af0f59997588f2a45d55c60daf443df4ea144f95712ac9fcc583f9f8443a0d2ae64052330f9a2a5d85911e17f2ea95aaca383b8f8f0f79f5baa4caf8719b535efb6c665d5271d587cc4a558dd429b34a5528b33e999547f1d02e2b213f7df4b326fa98148b8631a9cf8c99940b69c24c9949ab50ab8aa33c154f415233233e4b685e9d42295dcca7d8f3e338d7bc1fc86d2ea13a05aa6f007bf510e0a8de64bd7a87f914c77c85724d451010edc7a7a79cc964a76915cb94ac581557c3545cf5555cf51266caa5e2aa2f81995ed12d15573d04ea22504aa6e2aad737e5ad5da55bb27695ef2bfb2f5ea253bcbd51747dfba290f3e3b72f8a1f9e5a5519f6adad55431466678446a2be347319056aceaa91dac74ff12ccf9a10f6deac14f3985bf0ba0dc3ac04826e45312b85a1db976725d1dbc26059e9f5723b8e590906734b9259691cddd69a954827ddfa8b1bd563b96a559cfdc94a34573c1567dd3a69d62eb3ce31ebcbac5b558e59e354381557db08519ef5eaaaca962b2e5bbc70f17909e3c30a43062b8b925153652cd1ac24cb54cdd32c3ae5496995b74e9fc48a83aa46e45435a2515523a2aa6a74ab39ebf645c559a780ccad5f20e6d63f10ba7512c0dcfa0948b74e79a08bee934ff1e53df92c7df4329fb55916e10c797859a65a15d76689cc74ce9829172cd3ad57a6722acefa9298299c4ce3549cf5eb215010cc14cf5febd7a45d6f4aea27d5fa1e2a123162bf8efd7c113ae8a19fbad7196374988f7e1ec5ce2499cffc6c9f9f1de6c71beb6fd01c488507b90a6fbf425e136d3ee4366f27573891aff0f692855be02cbc9d66babbe9edb5169ebd85b7135de037bfc0db3d03c7c033f0f69bdfc839ce73379668565aa2796eb868b7d14837a82f29d96ca38ba8dbe8a2eaed2e34307115573d67e2cc5bc555cfc0c4c0bcc0bcb530b369d2589816982b4c229b39640a9935156690096456ff31bbe27accd2ac3e336531d2ac3e9e475fc5ea61751d388add707d755c97cd556f6b52d29760337b1d04a6d98c52a5ea7350a97a21c24a954b8d84b952dd428329d5ab5254aa552aaef6a954ad2aae3259bf52e0b1f9ec3c7984f0f2a752adaab83acb6798270f9e57aa612a4ea942a9b8ea5d3deee3e28f877e6d4f0f2d8ba437cc0a1443f2c2ec0b763d7cf1592f10933031bca08bde57ebb3445829d6b0125162c1f2298af92ccb309f354cbe782b2b2b2b2bcab3a23cca6bad1244b06cb1b588284fddd75a15d65a73d52ba8d3e9743a9d8fde2f11c49788927ae9d56b1556e9b22e8fe5b346d2ae7a73d75c8815a7ac5eabac51d75a556b55ad55b55671c92a52d61d2c779edc597247ea0ed4959d2b3b55769614b1236587d785a7cb89abaead6f5f579b2e345d3874c1d015a56b852e245d43ba9eea74d5915367883a5a75c8d4f1d5b9aa53e5eb2ca9c3fbf6d1e1a21387ce0f74d4d02143070a9d25df3e3a52747a747837843153474a0276e4d869f3edb343c60e183b30d0f142670b145c76f0d8e992402dab95552bc52e56abb5b7d62976a4f576bf36d7dce4043ae833ebf5627722aa57b4eef4725cab5edd07dd39817e4317ad3bc168d7ab3b890e8e8e9d606ebdbdf4995dd1ab3b61077b5e1ebad3e8533a8d3eba53473125f301dbc562a537f980ed64590673a7d7d8631dbcee84bd3ad4d06ee64dfe72a7d143f701db95b9070b596e7a855e4577b27e1d6a681723f398bb5eec4ea18bdee4841dc35e59f42627eba23b857ec55ca3bb26a7106c72c2d7c9d6ecb5831e1ace305e4b80269b430ded6c6ebadee403dcd925164e210f3b9b9b7cc876d6a1827677895edc6396076a6867bda9070b1f307c730f164e310ff21eb3a1dd10cda7948d3de48c8816e453cac69f594f2dc88756ca46a09e1f5a10adf4112ff80354ae08a2f9c8dc89f492687c85d89d40b75156d884bc86bd56cb3d58343989309b90903739c1aed8514ced888682683ee5e84ea4cbbcc9076c67cb3d5808d57213d4d04e456e7282795035d387a3fe2b5beb45600fcd8ee15a31687477b5d21878ff6663796434dc2c386ae5f1783c1c954d962c47544b8ffb425689457a69ccf800cb11406e5ab00411a5058b0f505ab090f98105873f7531fcb0f070ac00e4ea8715863c5169c113450c3bac8499618a3ae11437cb04be049162e6496f0923742764edf8d1238a0f688c807185f44a26834f8ca13bfa804914449e88228919b24c3da9818a891b3543523802c48845770485453324ee8645ca12a8ac52fc60068ddf55d7efeac9851b28b8e4b1e2049a205220656b090b555255581883cdc4e815197c9488472f5851229a79216761e19725c8dfd7dfa317c4daf7ef8a1c2c431e0d58c503f7ea77250e9caba5bcf286f6f85d61c3468adac2ad85a32a6dfe88f28af88af867e5ea31c64e9618e32857ae6aaeaf7011cbbfe28b5d29433ec6186317ac58bdaaaca0cad68d72b4c28d8c719434054b4a0f970915377749d9f3fda342e6cf185709152f7f96974a0b54aafec43d19ad3541b7d5c84e5646d06ead5c9dadbddfda4bc95b3b6807c1dc64a501bd33017431f3c26c853a98dd52a56bad8b40fd8e95b420180504b309b8180851dcf451f69b50136dae6957ea6475dc0a9619a9a71182ea85bed3c0c9fa9192d4b435a077369b0dec0de81dce24cca2e08cd4426312b0d398037a877313cee63a1605c94749bfc966e3c9e6aa779e3c361b0fd0b103bdf3e49981d4426973a2cd75efac503f4b6ada44a034db44b04e9d66255b1428df61b7d570db468928383b5919c175ead83b68a73110a8d9151785e7043a0671d417a166a5364a18e17df536c58a1b692cf3ac5c77b262ab71dd319d234244d14ae943b3840f74d004a1050226c4dcb91345431d39547820151fa2958b04ee46015a3a4f5826b0833d0fc3300c432fab8c30bc4fca30a495157793b8f7e22230ee827eaf5ff0a2406a485617569a2937697678d2d56bc38605345ae06f4a1021ade2250b657f79cbdb4479c12a4a596235e527a50cd4cd64c5a9509ea45873d4a5c420a5ca9fb7ac41ba5a4a4529ad4ea9c541bfe65b716dc3510a2dcc698147edef8bd5860b5cae93a2ebee8b2365cfa9b98e82a7e644ef6af4564f35d77f962088b50f410c9c51c20a95540c2870d99db537222ee92d59e20914aaae8ed85171dd11f653f73174f85a49fdfd6b35f5974e6924653fbd2e5cbd5c8872e1e9cf9acf4df540f983b2038a114ba0f83005250c11285ca4a0344922fbfe4199da32f6edd48de0f6e7ed9b5e1a0b4f08b949dd90c5f78f851f0b587fde667842c38ffa64cb094fae9e9e44818259e0f204872747509841e38327bc232b2471e44aad1084d4950a557cff5610b302973f7538c905729321d2171983c9bc7d9499743733e96eec1803cc9883a6e83393ee640710a919336326cc6766ef6426dd55b36d4e141d3b688a7e4d985f30f46bbe9c344ba21387c561df6b15677d88b7c2556d046d2088fd453512e2b21e63067eeb254cbdbdc1b13c282fde925c555e6c15956dd371de3a88ef2dcadb9f512350568f40598dc4dd5841ad1388df4eb8708c5d473a46073423c873c5449d2d4b84817265860d982d3250698102081f175e3538f1d204eb91d4a98108106c9e5838810a4308993a73c40c91ddfd392133a32ddca75f93a7c7e0fbd7448507bf7f4d8aa0b93d6c6b35b5379cf9a2e2e80834aad1dd65057dff986c3191f367abc9a11cddfc9974eb52bed4fca94bfa528266fe4cd2d1508e37fc594be202e64f5d2d862e7f26e970d8f2a72ee90c963f93745aa6fca94be2a1853f93745baafed4258951f26792ee86237fea92c0a0f06792ce27f5a72e49cd097f26e9d030e1f1a72e294b8a3f937463bafed425a579e2cf245d0d4cfca94b2a83c49f493a1de2fca94b82e1cd9f49ba353ffca94bb2a1873f93745825cefa5397e403993f93743360fda94bf212e6cf24ddcff7a72ec98c973f937461b8fca94bea61cb9f493a32577fea9276a8f26792956189d59f6252159740794a4aaa7066a04f7fbcdd702fa807ad03add5889c7ad0583bbef2ca937cb2a4510f3a871d5f79a529520f3acc8ebf7ee9e3fc4894d6ad63ec6fdddfba49bd1d8c81407f6c5066e7f57ac1eebd972ed11748a9122ebf2546b4b2aaadd6caac14943a1803e1d545203e40ffcd9dc54e61a388e216c5916f86016c3e6b6f71eff0d431eea90a5a54447cc76cbe555c9ef26cdf8c0f6820c0563346c551194e085f00a108c14e7a57c993ad49a87ae86a919eeec8ceb8d926917a6badb5fd2a40f1b1ef9f0a72febcc17aa8b824901dfa01a9cc45a1928845cdb622689cd15e21b620d6e303ebfa23885121bdde134f5ea0909381143d5049a105861f5fa03081cd7181073759acb2802409da8245052fbc28587260e1a219d3e48e1a3b4bc6a8c922048a206ea2f0443d099a6d1962840d5a9290d2c47767083a4594990a026a85263e3f346419d224895f90169a006235866a880a3df450d50408e9881454b44c4981b22601509858f383889824779c10dd4033258716be440184120f5678e3c4af0715dedcd08456c28f1e5a5548f020314502d24091022f081d7c1461c2c4023030bcd0c389154cd191c3011b9e9a227200e286c99226a00e2b728cc852044a0a893c3854f12184922b2778c9418993320f0b6cc63c05e1f1b3220122a898045901c91457123b64a42a8c0d73b26c5d71e0c312274a9ce061ce122a5413e01003d49621904ce130048e8fa8272431ac5982aac98c7493268b192e3c3c4ac48ab05242448a26e20481e4678c982c687e49ece092d268d2701541018e0a461c71c4c9e8e44413659eb4f1610a991756000495d6173b56745218ca008829425841e2491e2592f870b3460e9316de5c19b9208a45ad052b3c6a7867a4927852a6c7112867a058028522f8491220c45cd11066892998946922830f45d060040a112742750b92284f576c9454a1800c528e1841650c11572830215b4b4c0b6d8638a2cc8f2c0cb0e242922d2c4b4429990a8bae5000e24a1169aea4f4d8a27acac22407224c0c51d171a85c4922093148764c21ad2912c788ad3149c608e9090286168638e3048a136084aaaef88268c2862e4e82d4747cc963030c2eb0c1610d9808fcba7c3864c5604607a9262da2c4fe7581238313407ee015d16f58e841cad31c1bf01401c4498e7afdb89945e461e287993b4986ac11418b0d46144125874a4914ed3561aab8a9d383480f282228e3cb8282c277e7085413a7400f364ba2d810a78834318c10a9a3250b112b302730914521d9c10a8fad3339b8e9803703151588bc61e3820fdb093c7a5841959625a23cc94eace07bc383181690e8355961f1a8a00c0f5e0d539288c346070b62c844e941091e6ef8ede893bdd3e408560a6a6c5419b6d63627d416afd8556ff5f6f447e5838feb18e31b365f603f6f8fc3f90596cd55a1c27abaa3f2595babb89aa34ed57bfcfda3e2fd59d3adc18285f6f813d7642b0729a286484115d99dfecee369963344b4e441e18cd4133b03e850a5840f4d1a1e4a762fda2421d2840a1148b4b0a3ba17bf6354f76276749541c2871d98aca049a53b7252a0e0a2f543618528bbf6f6808c97835eebab127c24af171a2c776e3093c50e103ba807c11f92141e74d1c38e3556a2d8c2866b07f26c966a84942329b71bc298f919416344cccfc817235ba0fc49fe8c18c1469e605d45e414815324eb69111c7e45b61481f22bc264fc153152048a4817d753274799acbbc856d1174aea422e94fd230227d4c2fa53f47dff885cfd49f68f88d58fdf3f224c3490c2530a556cede4876febdc5dd43f2370be7f465ae0092dd713e0cf881dcb65a9fc84e07051809a32e225ea6e81b2d9662155a0a60871c1c714214e4e980206b9f3b3ef5f90ad3f7557c66e9fef5f10adae7dff82f8dafcfe05711284d744df3fa938a51a502a8c5495506a498b3afefd03b286c5f70f081721250d42ad4aa1ef1f90210f1b654b34cfdb6a8cf66d3e992c0ab6fe50a0a24a7e3fbc70f9fdd8028191ae500ec1f3ed5350df1947fdee229c9a1e71a88a212850994aeb62addc159efe86a4407f437e50934a29fb07c4eabf7f42a678ea24fdf9fe095182fe8400f11261325b0dd8b7d7ef7ca2e07f80d4d629a0cdb58a2bf20427b95144a0ec8ca81f503507a37840cdc050559e6ab6c80d6fdf3fa820a0d4884f63dfa88d9aebfd0125ea85efd8cdb5da87f1f2dafca2e61b91b2fff682a43d7501e4505af0fd83daf2f7fb0775f567adc280020a74abcab046d5c7e3f18cecdabbad111de98c6a04aba231c205420055794d9efc9d70e5cfd63a61eaff6c39cde543cd9f3de76bd7574ae901ee53a392f6f4698cd2de1ad1aece55dad7c69db5273ea3d6a200410a86a2188254cd0e9452300cc1300485e05b1f854b6234d5c208c09192fa2d12c5b22cc273f18b1a0d1874a10343f1f5124310bb4003861af41badd6124a138a391c088a2980d106a25896b59a7b0c32e20ba7810b2f58064e6023d98d84c1b71bd53870516bce3236d205df3493b1db0625072e84b66232f25619b60551ade2c694d4cf9a6c969bd8da1abf7675d1908f59692231d2c2488942d943e9e8f343762359f0eda2589617f0f04323c52a83aed0008592fa59d28088e8100af51774010ab2ed80d5022bfcba65032282540c85d98c8a9a908eaa2654dbe088032ca121156bb2b2c0af642acecc6188c43cd888808e8003870141bb3a65731b94212271d60a0b7ef274757d97290b58f8d4e1e2fa92070befd9c08e9be5195d2ecdef7083071cb8f8af5315c7811b33cfc460fc66cd9916031bfd9cfde9636e5b96b59a3b0ea7d31d1d7d0c36b905d98d24b37e1ee90080fbd37318f79a3ed3620af02f58db576e6bdd71baa2205adcc86ea4581a2a5f6f45746e179046350f9e0317a25896b59ad9e4020c48aef7194aeaa76390016c4d5611960c70641ff1a7b7cde0abc3f9db0cb89cd862ccbec1030e443f5d88b96b59d66aee385c949c06a4188bd21c20516ae0822cc083615896b55acd8e8b175d006fd14618e73a75c37b505d84b92bae7917559f51cd342f487ca62c22db7ecdcf9777066d0482b9623f8d1ef47383b7fa9aab9dba0529a9c320bb91ba08c60b182e34c8c1c06580c105306e2d606418304c87c1c2821544306c432a846a308260c0800144fb81e1d353ce60c862248c11665f30441821886fa595278311ebf7fd1758c8d4fe44de5da68dbbf8af0abe1d7f7b8d7517dddbf9da9d8d2d40d6ddd59a4ce014d003fd2932e1109ff606d871d41e3b9eb246b773422d7c15f9d8b105fed6306953fbf82e39f1de3e554cc580ba0b339eb2b9ea2e7828dbb1f51a0b21006f7dec0a82180441aa42667377d1f55b6554dd30f6ed84f1befa482b14edf2ed974b6751ba3bf3e72bcb5ffd8aa7f975d545d501c0654da820295619b571d81b4c416877d4634ff3c5bb9192a1597754e6d4573f85fec43d46986773f5f26cad86799f2bf0d6f35b6a6537d3b64678d73e86f85619ed22ec8d5fb10334112104e0411fdbbe603058552103737711a630de14235d214c95a6d5db03260a36128e7a4990a93e689e5746750b92167452c495c420e07bad5d6267f6e2b2cab058845b033b76259ccb2cd246ea5ba3467a0394bfbdf0ea97cb574ac3bd8e95b08b37f7ede98a237bd736168215cb8b7337eec40e8aa0025ce3be12e68075ecbf304b1ebb8e1f2f596335ee873e5e7c7102689ebf99886e501d8cdd24cafa2751bf9f2f6ce6a81f2ed9bf0e62b721dc5c823c46031841bfc6aa572a068bc715428823215eb75a4b8a179b295f4fcd94e2533365f8d44c095e33257e33e57d6aa6b44fcd94f5699992fafdb3e7dbaf99923e35538af776db7befb5e6bdf7de7bf1bdf75e7aefbdf78234dacf91bdf786f74d8b5d54efb5a7ee4d49bded12cdd3481463fcba17062bc10c899778b5bafb14c18bbbbbbb1bb4dd4855c4185f9ccf7bef751b8aa01886a6881dc418bfeecd23c6d8cd7bc33ae3dd61879af5eded2188af3d753bec9055713687106431e166ce1b25a4a8080d51658a246bb0a234f9cd99cdd0c50a4e133bbc3077a648b11aa720e22bc20992264776a00d3dba50190206227468e0fc59f365fd29d688fc66b3217aff0895b783f9081bcd271f473bd1a1c4ddeb35ba689a309304b606f4eee54deddba1407414bc9c83bf3cde08d4d6d9c62bdf6e24ea41ad94cab07e99eae3a81f45f4b16f9ab27f3e5f0d5c3e2e35f06c6c84cadbbd1c7c9950e24ef46ea428a29fd6436b5f3ec3becc36cf13fc0541500947fd9b45ff117d4615f34d42abb529c5ef1f982e20c040f10207a60921bf383eae1e92d84cc30d98df839982e436049822486e435c9fef1f981d0ff4fdfbb2f5a72eabccc5572f8a6595715eeb621947bc59a9661291299ddaaeebf88bf301facb2daead395c5b78babcf8bc84f1792183c5c5de8b310886e215455ba98841300c45f10592a128be5e30d8c885bc24692b2545f205838d2349c6e4c8ae4c662b9569c1c891246331996c4683947991f9665ebe7a5992f9ea3d583ed7c7c756ea53a7aa54353255a5ea920a05a74d1c386db6e4685dd12a5bae68152f5f1d73796a457978e813e5e1a15214ea04368b4cb7a16169add6de8bc10b82b65290566befc5180c6f18da4ac38b41300c45f105bb3098ad14168aaf170c368e64ecc662b6d2186c24c9584c269b95b72c6da5654c369b95654f8fcfcffdf9b195fef8fcd068404041412a6ab756b395d67a461fd29a3f443fa435694e23adc9741f680422ad79bef8ea41b103d0c7b58aab4285ad54857873ade2aa038932db8daf722aaefa0130949aab5e83a0644a5ce53aae82a1545cfd1343f9ea953eae5265742c84157fb1a05419d80b00045623017d754cd5487dc50199366daedbdf17327807322af859bb8a47940318c56e51ac06182f1cf1bc57dfde1edaa96f07dbf1b5f5c269b12afdbdeaa2f6ea9817f270a9bd6bbabbdb5c43b1ec45c51a5af0627cdffe5d53c92ba7bc770dbd724a4abf526bc3cb068b7ebbe876cbe8c6d1683f340b9257ebea5056f3ac91b55a59b6d37c5a7f6bfd2d8b57b2e6d5a1a46eadb52279d79cf725b3d578bdf5a69ff1c6eeeeb3bbbbed69adbd6bfa524b7677378f1fbee0f0e53744f9c507c35b17bfccf065862f535e967ce192c3971878e4f0c5ea4b150e3718b4b6142186245956bf35cc50c49fbafba5840161cd04b1e5912ec611172e56678d441b03551575a5489672aa6867b01242f1e84517e348862bf1fa66a0fa5314cb19a6ac87e6ad0b9b2e6afe749f0d23f5919a21c8dee6db45717e3454796bde4a4199ad06fecbf4f637c39cbf1405be2b8229edf7af8b993fef9da1acdfbf2e64ae6b39716f755d74b0ee3bbc75d1b6af4b982e612c132516c11786c130c63886ac570c5a332ac618638c3116b348c600e7318ea188c7b84ba35d22d84a015c3aec5095c5fb614b972bb04b0c3212c2e47027c9133e50d9da4229a59452fa7a1509c3300c43afc160611728252c555851400569626a77d63e8c41ce87610c4b7c08002e6dd25cb103440f2a3b2d40fc7e4794524a29a5e12b9ff47a29c3b0cfa4106788224bb6e8b0a4e90b798428c3a34c992670e0611782385f1aa82e88b7586981610b113cc4f2fa295aa32d3f44e1b894fd341aad36f4fddbe27bf1fbb785cb9fb429196eb37a2f894372528241aa7eb4b7bf46642d9212dc0ba426e5897b8b7b61d5f4ecf0789404bd3d46c559fba2e6ace32ace3a5255d9358b339293b22bae2bae2bae1ad5b2783cde0e4fa23182558c58d60b58b817aefe6c325a2f3c59c797d29b9194945c219cb16f484acaa3b276cfa8b125fab15ae5650ed26a1d69496954e69e34446c7d8f5ffffba535ba80c7cc1bf4635ed22e9091989467f9d5faf878758bddda0c822f1f1f43b2b48ec4a4ac17b04665024aef2973575ce93df99a3de679fdc74bf3bc6e4f1d3ff3d9cff51f9f398dd59865a599fb789995aef7388ed5b8dee334463b40e936cf6633d367a40135fd8c76c5d91fef07b23d3e3371d4efa1f5e4dacf8d2dd1bf31bbd6b18f24098b810096e609661cf57d1ce72841ded628287bae33efab8be1d66846cdd51e25e58d410d1c27e5cccff6a98183d4a43c75d75a70044dec1663876198eb62d4c7b5eaea88c4a414ddf7f2ea1fa8325e3027fd5e985ff33ecc613111689efbd6145be6336c2c9d17a8b4ea570c4339c91845330218000000a316003030140e0a85a22409f354dc0314000e7fa64c504c184a034910a5288ac120c6186008008410608c21c620e6e63a00d02f0f6628fe4f663dcb2cf2b3af3207de05498e9699d9778001f217d830512723901306cb869636d449e149918a201820ed51f456529aff2f2e9f5290f00dbce8b22352950d0786f903d637abb0fff842641e736cf9c0541f3338872af34e0eff60de850722d4407335f3b33cdb2c3bc778338c67c935eb776c5db453fb35d000016ce95456fdffd5d93cfc502cc194e54c3c99fe462027bb2deb6c7778a1a2828144e7e372dc3f9e89d95337101e8989e5f8e6528ef8772343058c6b631c4939edb9cd82f69e977c16860b601860fdf191bc19150a36e6b4b426b8134e0770d4a105ba131dc63560daf28803f90861c106da250a5eba2e71dbbad49c7045176598534010eff2bcbb7e2457df702f5290735373c666246780b6f847b9c22d1902daba78b0bea6bc1402fcafa03c5685da295fc53f958bc41ac7270f4c4d60a6f6e9b4beebba544a86beffb3212d9b9f4c6a3059f1373356c0a4f9f4cd59275e052eae2dc9efb3e76d8a5760797f62d14a3da3372d66a2406b4a7164b917017353b10122efb7aff10b9e8eae5cd6ee2e7961ba5dedb7bc0de788f3581d8defe947f45f453cfeeaf944a6b18e47f92a2ad001837c08db32e49ce746823214562a69a7f0b47f81388fdd242ec3a448ae6d92acea08f7618387d2568bbaa492a74c02eb2415e8561a311e696ba6bf6147481e8c299032e2531f5c3b5aadb86c2991c16c84f0340724c1dfbf5316ee5115341b5779d02b32126e380551b81ce828efb1d3c17f7fa723ea7bd00f7bf02faaea34d4f703026ee48f9be7ccf34bd8b7323a1363c32fe568341c484011bf76e3ea541c44218de3e015976bde6b5fdf43ca3065df4fbd4363c05dbf722fd706ae7555207a36c4a4719bd54e6b33ca7eb6b7df49b7340381fb77542b9a227787d3f02550b5f92df7af1c03104e024394d619844349b440466ea55649444c65a3251f4183153a3212c2b2f131094da15f565078b64dbe27325f840e2057d7809e7d6b6bad08ccd46c5345bc20acf29c701d59c518d84eaa3715136d9dbcd9346dab9ad850d40f04e176fcb0733acaa1d6ceadbbd2f8dea50e52c0e87a6248d233d7ed859cbd6527e0fa25fa0a90dee602e9315036e4590d0253506e33fafcf88c7a2c52be7f6c56d03159464453939c803663336784822c2b88c3ece3b063edbde959a2bc7e5d527288cbeb04e8b44c156681d07b86d708835e24440dd542a403e84becc903d377534d90988224a114b298919ac8660a14a7e9a47ecd4754193268bcb0185db0b4d7bb998973d5841fa1da9316876de27abeb10b4ebc8e9d5e5f582817bebca353a117d3f7f433ef3898ff47be9fbfc6791225a9484f2a0eab9f30b1e7368114a1bec66952054ae7b885ebad8865c4c50557687351ffd59c74f52e93416129ca9805a6746f1690d7ab6e75db42cc610a0660d7032426e0613e6cc1841cd82ba7ef8871d0131649bda1c5e663dbd3123ba9bbb679b3d192875f6d20136e80d4484f04918217ef409f93668b182693e7e068e35e0d5f9ca84fd10355b85182baa184db53465a53241716625bdf3aa5d5955a8627601cdcc7584d56535d4b7b3501e70db0c27977ce1784bab5b7bed15d5253ea77281642d8277940ae65c8f463a5b9e904a7930904fa08dc6015a304c04ad58b37ec9c972dd1f1be3ebf56d21f38b75a3fc5c2a90bf0a509750bd79a99922757362eb8dc1846e52a112520e30882411ed235874bce2b62e1983b39167073a3b7027f9194e0258ef4d8f97779eaa0c097a1c7de58228203031405a5c1c46ddb021469246244f61b7cc41dfb26731496f25b3803ec342d3f0a76ba8457f61cd7aae1ede0e10d61f071ca11c979c76397b7b644081afc3c2a4ffac03cb071a246728972eac0448b8c5b1ce28398461dafae565f67051cb39a73832f6534d3e20632de4a382cd13959bd7579beec91525bbf980fa2fe8ff20021602ce7c9ee03697a4043b6feab85431300c27d7f3b6122ff8ee83365dd25b08849c70a3eaaa350312286a96a1059e358e38346de5e8b3d12350ded24cd734a53b5a207275b0793efa1acfd86db90f9fc890c57aba3b03bcc8f5c019cc5f6bc584230376448292c4a1578735a7bf7dee671bfd1e6a123134823c14064f41c288ff1c360e40e37f54b09f4974ac0652b81635e07c8bf03d7b0a6af84cf07973085d3ba607509e5c6a9b4f3e329f54796efe99a3e3aa1532dfe72676918f4ad1cb688e2d0b51845db1b242137534104326726cebd6cbb9417d1060eab48f952a6dd64a03fe8ae68366a590cbfd810c062678413f97f7899788ef8235396f46a6e10ca01f7ef7f5d05256a7da26317b3dcb258d4b7651c58d6aca61c35d64f163ba9184d95759f57e398d93158683238bcf93a47bc03b08d485baef1688c2c1ecf65ff80a7b65c83bdd0cc83ce9ab8ff2bbc4aad31a7d5639e96165f13e5ec2b0d67d3465cd421b0495d0705dd8921402d0df71332122c71456e819b597409dab82048e684d97820b7299a5971525731726b751263935db05974c933e2828a2ab0b586ce17545df097abb3205dfd6f7408396a6b368a5ca130538465c97d784853686cedd0fdac5cce05a21a16ec0e65545a7a15ba637821fd415cd0fde1045e455a99587c08f3d0600d1ce919f803a243eab7ee18abb58356f4e6c2f76e3ccf8fb9f0442ee1d0ed3df7972fe2d57e6a0862b5bfbad58fe6f90a6a08c3097182a020e2e45ed1f3cd4b40db5bc0fe5264fa938985945db47a243f7cda9015f61e8684ed6cd4a7009dcf2d50583355406a9700790b2b0e8a6ed4ebe8f168570bfb182da75d732e2b7b95b40c21a603012e4595aed63b0821f79dbbe580c7e7f8b1410c8c2a88cb87e1e2f5d5d79eb730834a98892cb9337b4196123cafe97ace8946b06059833815e6029f964304c923f409b51889debe643db117d7e0d073627ad84ef712fe5e6999f54af81cf193371778730027c419117168acd3c691e0ca6f2b42bf651e8ade93a45485d9f378a4cb70eb7219387f9d9c18e2f4eba14b116e603174e3f50e6f19065a2d09aa86583639e3ef842f7c3e41d8500c35fe38ee6c5bad57dc9ad6ba01dee4b0298fcff38feecaff2432d6503e2c5462d1ac0c9d5e34b7ad5810160474034c4f90c163e68ebfea8731f07c9d682ba832250f8798cafd97733b5edd55e3285fed213f50030824ea5329409b9cfa6c2c070c6cad777bfe1f367c89b1855e32896d3a72f731e380bfb598148e9368a0ccf8b871198b3dbe60842d40b2a6d2a181ab8dd9f1faa76516a0eb0ebd06103319f387dff194dfd91743003241bb99f4706feae2826e5f6030b402b3f757c42f2be352566503cef6a0349d9ed96cbd27b02400c5c750e2f9ca8e4727018aff35136c46f07247ecb0ee432d370743d37ee83e37feee20ccd1103231dcf9c313ccc584736bb99bf34f476be212f218041276838459e39d2f87cdcada9d713791d30e83025d761fb882264ec9ca025821d680e6058679540ac983f705230d84a806dddf70d29a6e729c4b9c2dcb6a9cffaa05a8a5961ff85c47736417390f063ecf80721973261e3bc37dcbd5a172f63de00fc10996fac801518eceed60db19a9c9410c0d9a5b544d701d3480dca6bc4be3fc6dd02e866451d89f38eb5f81fd9a1ba3a3ee8d10c03c5c8b8451a0a6a990150cf5b437733549d1a92f3009bba491ac7b05374426b8310809fbbe3b149cb2167f80370f477c3ca4faaa0691ec79b05630d20ef4e0764a81454729a5592881d59b9f9bff01cb169a3203017f7a27ecc9eba90eb82efcd40a134dd521c33c089e76c3afdf057e410286d976df5d78aad852f07ac016c67ca12576bef27d90d7446b38fb424a8953df2fef9e9ac0c6d91f7622fbf6aedf7af15ef859f6eff430b1b8fb6733a0ea40775faae26c48dc9dfa0c89ee0135f99ee290b10043a23e22453ba0a980f9bad6a6995b3a2945ee110b9b1495843f2c5697c61f463e2788e071269c57a7019b31ef05e368e1dceed0ced29d9b561f1ff82043798f6a09c2a4b3f4d7dd94800ff257007bac758f10bd777697632ec439e20fbc6d89b5a7974472964f3f507dc6ac955e58ebdbc6e4bbf1802348fccb1161584fbf5870b38ba403aa5b3fd04e0d753da47d592bc0585c718b968c54b1955bea62ffea0532364677631106dfd7ec4a763069195830cb15a9c991970f37d9a3c9e5c182300500a44f39834512548d64132f40e6dd53840b25552bd7e8d584c6fe2d10b79dbb8935f2833d03f8bf04cac2429e2b8b7346dd5daccd0be49c07cc602fe1d928b2b83f4e44fb310df47d49cd2d6f5da5859cb3aa92812bfa8d3a20bc592a93b6d94b15ff6636026abc06b8184a80bd860efe8561fe79c0044aa92f4605d85ecb82043f5103f707be2b342597de812f54494d5978fff4ef08a00ca78c6f1dac0cd67a58411fa581049a84f78ecbfd24e8be91f92008d90fa0dde13d736cf2b20b14889401cd539b201303366c0145cdbba0d9fdcc936c970f683eaf96ea7acda96cfbf5c74b83c0b7d78420f52b882cf3076a74d653d2be95dbce127607569052c3e7f2ed168a5ed26d80137f5217197688566387fad3db31519ff325aee39e1377865f097edfa63d62e4ee67401abe0fbd05bae4e867434e3e9fae0d3e65c467401abe0fbd05bae4e867434e3e9fae0d3e65c467401abe0fbd05bae4e83d57f49a2e09e47806674424a613d9010ac4d4cabdc100f59428eed12dd8017de607c81373e13d77f1e31d9b390ac7f4ffdcaa5de600d65bb21a4d2d0dd772f9f0a27b1dfefffc9ace53437a82960d734d33385bd2fa6f905a8bc15064879ae49ec1ee127c04fb0e486bf9c13771129be16c24f5801cad86072f972a515ebade409e425b723929a2ed21223e2c37e031fbc319b79b8de88fb6c25fb5f5ff87c1ca18e5a35018d0bbdef2d928a14de10fd1dfef7e5e0d8fece61e6623264bcfcfd2c9591573b9aa931bdabfc9de3c6a5aa847b7a83de9a81d0b50015bf748bbbda368529882df5d67212b4e230cbc00d357a7a3ecb47c6ba2f30a74ea6031da2b10f68e65e8fdb18829a6282bcc18c39d292a0e3145694da2b004662ab84c5cf58accde0f6cfafce3ef3c67bf1dad7c233a324bf0cb1db4900b6fe4b72449c4e537021adb266f08b1f99b42b7313bbcce2a7c0a440d07aa14fbce95ce84c812161a3a66dba03a5b70790a233d5c7691794298eb24927d51d81d91871d7188ca1f429f64fde5ffd97e99e75f8ae98b769966ffbd89e05fdee1fa59da9d9fb12ec319cb784bc109102d72b6fee325aa48c58079227909055aec4f48e0f435590b427be9f4b0ca14ab114047e789ef900ab7adbe2c7163e9f7048de74e2f11cc95b9f7935c1d681da932702d78d2fab830b4152afe907fefd397330ebde9bc15406e9ba140b16ba3212e663b8154dfb49e73fd7ad98089973240ff37a7ce52d2fba9c0b5cbd4bb634bed5da3d44665300df1cfa0c30cbbb0b566df748bcd724655f8810f4ef0f15ef420f9d6b7c2e17b52c00c3acdd424d099374faa549116440bb2d699217d4c703ed4ef74a407b86a60ea9c9c50da93f68fb6dd9144d35fa40ef546ddda07aa4833215a2e062a9d501baf3831bcde74124c4363964caa29cac6f4ead083b2fa88cc9d0f1866f18fe6f134b059d78335684c8057b4251f6ed23cff0a7499c40dc707b165430e7d41a716f2d799095b36e015d5d9fe825cd5682a19a8c4445a13c1441286315c6760a87f8c1bfa590ce53a41c4fd8fcce51eeafbee8ef6fe32bbe4f0219091e7166d961cd4db631bacd8de6e2ff5c8374e2685bc5731db9b81adfa778239d68286db92c7cfa5bb8b3bde5443d9e64113d79a5a823a843f4fed45398c49c2819774497c0f456c7f4151141be7a5e9b6c8bb9c2d8dea9569373cd5b8af20012ec4babcadd72f7f2f4f29646ea2c728b58b792fd769d5f423bfb5afba62aede71a9d6fa20beed612da3e087124006b00638f1fd4b336b0aa87bc234e580b84cbd8d6d8fba5ff7632a933c38d49d6199650c9b0aa42b022fe10a54388bf561ee9b97e727e1aee0eaecb13481aab3394892a997b4d6b5d8ee3c062507522042bae6c18b931eaa330b823b1023d3364c48d054f279bbe9d933e4e705b21e110e005b68e07e610b7881148d6a76ba42d2ddfceaa681b55a80a6b7f70940b6ec0b25d062dcf142dd7a36c909fed63b684801e142a9af3555b7ce585af98cfafe1c77e4bede7db4d342a15e1739b57fc962164d1b1956010392d1e2a10456ae4e7e48f7ab1599dafce20a9ec01da48499a89e12792613e69dc2727993524a1bef2ab81f012fb3107643d51422c202610a1a1cb023730d56b31a37f8c46b6b6129519b45f7cff5c00616e477d0c377f81d55453773e9757635cb90ce52d80a76c39c9553138d7244387db211f55e868fc79da5d6f6fa80d09f561538b1397257216f71a5183621dbea5e35800b0082199a36d1b831f900ae78b61ccbfb32ffd1e6127a992187b2749a38de088f46886e3dfae6b25fc0397361fb16cb2a23df8f30a2c1b07183c4f2d805149de0b8813e41dd59e60b257c927fe5291b0a4d6c0dcbaf1edbcfa2a5e25fa02e9e36fc4f3a85a0b5a3dd73398854c424eb81f62354ebd15e58c381d499c9e533acd86005413568cfa39440e9327fcb797715e2571c2207f362b16c69cd556240e12a8a37a24e94752764fe693d98337c7567fe9c642197e4a37b231fe00fcc4eba8007ad5a3acf4f72634e5c13e6d9240afe1d725e1d2b9ed79f9fbe3170a77b637e0dc54887db3f2e595a30a1b4d7d661c0a89731a7469fc88bf1def88e7eee9761ade6d199233d4f41f609e6f67b96136a652c448f9dce358cbff35369745e6ce6578bffd92168f2ab91e735d2503cfc7b8e1ef3293e0f09d9d2e4c5588987d868031836d2d63e39e8f16a7248c5ac599c9528686a97cf19069f0da8586a9dd02f6e9374f486e4dddac473b3f3231cb1904c89288f366fd3d101108db469ac5b6f64b653079a7c5cfe9bc4b9e5b9fb4f577488048589313e7b94f0f073f452bc9cc93662cd2d7eeaba488d400bfdb09bc2e499779e2bfb2dc35b77ed272b82af7d8412d93aefaaf40d811a01a5ebde3ee68e3adc55c3fe997b9b2f19d87351005449ef769d2874463be90ca73444d45b61f71299742aa18fbd6be8f586554db3cd14668e0fbe2ed54e40635d59c0b99331bacb0aad34073b7eff0601c87b8daeb87bbbf457bfa432c6f4e5e8f0073196d05e89b760a8d46bc554cb547268e7948e5a00196e6588130f6b940093d5fd9c757afa824541e58057e306049da16956e811ee3cf72033d6a7629088571c77d8c629d29ad47a790a230f6106d08c081c76ea46eb692404a17623ddbf264e051056115015591eefae1795176f62afdffe6581c18886d12b014df7de7dbd1a7f88e1fdfb559a04213ce6869b724a3b313752aa012e91cccc02f4575118a4eec20c37ac2cc6752f874374b1c64bac3d0f6e6069f2d3b2a0ecbec6bf2a6da08cbd48f41a6874ba87ed35bdb2aa7c656b3155c8cf28f1265110a34042c3681d0a5ec5d918912f43e5a5a7e9c1a5a6b1956c7818b3fe59404d60038f498b6fdbf084b4681814a4b57b88c6114b9595699582324a12c4dbbc4ede92fbf28fde15cd54b646d68839f9ddd6aa689bb4e1182c335915175830be7f1bc5a1f5e5452d5360c496e75eed11873c54e740f55d479c5a3241dd74840d31f427945b7a2451d57086e70b7a701cc4382b332eeb1ef3b8a90e5149d310a52128e37c0439602a3511368fd84da029b779e455ae05a3343cc5a7b7fbfc2c5f3e4bb95bf13da8bc432306adfd12d56bd60c749c84f1796f472b5c6d106a119136b3f6742e0c90e7b1e1827c9367195b2bcaa83a1573d11dd4c0097d0de395a901f3892aa830589a8ea827f24cb094c5559afb57f3ea626f7f239c500b10d09a777522ee1ed1f7ccaf6930692abd3a349d50f819df1cc7958f406ae5fb3738ce0390cd763e1f79d4d251b0d851386becbf156508ac3da591b5a9a58c176a518694db4d3c5780f2b98afedb95a87565a37813a19523efb56510838f2c14d9ffb444c07a06211e5cfe7024199e2337a0a6cdc54c42d3a2a666646cb05a0745cd4747225bb60e87e6c6b773eed0591cd3ee82b09309a218f37f2834b6e119e2a55b561c880a251af4b759d7602b420a8662a8257c74adb2937022f1c69bd45ade89c480511389549cdb7d053e14e0afaa5576b5266f939cb607c6374e7fe839ae069cc3c669a8ae589d040c74cffac629f6c55467282e349107ee50678af8b3558e930248b591f9348ad7db9e56f059a4e0639920f80d90004e2860deb94ec34f09d3ba343dadd2f99c5861e654e147624ff9d6d844e444b6656dfb12f14ef52fa429224171b918c8ef2f6011775002b316ee4b9997f330355231ec4f73bed5b502c2743603b108be41f27000f874c79a3d784b819e8351c7fba711f6fbddc54aea1c4f53d063ceb6e21c4966e5cd86f125bc2273aae138f812b9d1f36dcd8b72382404e03c18fa0a400e00a9691a2a66e16a38e8ee063887afbcd4b22623124f3a8aedff1bfbc6527c50dc24f0042963d789e085a6c579107f646958f94e4c74ac76fa56a5969f7b415d157afa3dd653bc1d01f91f142c8157c666caef210f84adfa3dd9bda8cf807741aba9489bae6af0a91efe7c4911df6132b9284f863acc4bb22adfe1a433e6a685b2e6de274c25f97b031b5c15697bca983c24d9a913f6103b907f635b1a98d425c4e81f8cf10acc7814800eb17b408d44ad89e36d9ee144e5f000d62f1bce6dfcfb4ceff98b07cdf4c287f4d0d9cf08a34771c5f9b1d8dfca3804bbf58665370ce801dbf78abac39a19947a45ffbb6dd4ba30df162878d27212970c9281115211996b755f37581177bcec14d4735eeea98f55c3392b4cff31428d6732872717b365ad1b58d94a9fb9745768d10a435755090d5e14e82e08e3c72ce8b29be8b626a1b8b87aabe309f77eb23a76794bfec276ebc8176e6631ad018f1f2faca2712de2bc9ed099ee66ccc622f0f6f51a0919db2ca45aa1a390a78562a3d8d8904d37a45b22afcec0e66db00a95d934d7d2a562e438308892e8b45eafb1e2e6f9648291c3072f3b93d6f8cd2f13479f835057e36b4d9d11ca4273624528c69ae125a8e6630507b4ce2f2fb3d729eb23eeccf86ed201671c6b729424bf2e57b07b9df97cf6215a688ed4bda14424cf3c2048144aca11fb0e59d113e5887e9f271ac949cc6db545d7ba5726d6119ad5409f3db2c0ec4edadc68b6239a576afdeb8cc1a72cd40d971b74e4667e8c6a2b0072f9a90c1581f7f8e2e4b2894a2fa046d9fa83b01c932aa74469133912351be123e01a1f0494e4fad5a390a48c3fd3680e9f5e4f95ea7cd1669ed305020012bb9d18c22bd8a57aba0de504627a981452b1966962486ed70f9544214dd7c7a23c837f44aa3d9ed0f876b75a45bc04af9b803e3bf2e5ef7eb7dbdbacdb079461afcef21db2b2df3350959e04b8e7d8bc24f22797029581ee414cf6d94bb8a1b814fd162d2726bc519e91931b7fc61a2fd5b3226b88aa8f40e6888ee0b7d20736b156a4d44274e33e2df66f02025e35c8b6d109abb67ee256d2f0a5e2ca6304a1f236135a66fedb098c13a04b7b781bced541b00ce90ed63b709fc6639ce69e96181c5d9b0d592fa1d10ed14487b4763684802e9eac833ad59e3c3718afcb0fadb13b5afe657b355032c900e7d8cee78e62e1834d98d59ac63476d7afe1f8d4eeda2ffde00997aa45e0c0f2190be32840203913b6e44c9270a2d54fa00fb980dbc1e5658a53ca289e79b9c09d22b74ec16f5d3337220a867a5a0d01a14f068cb110abf3455b4bf62f464c3c15d1e183e161077ca7c8dcb27f80f209204582ebc22c948ea1a55c92e21fdff9dea713d3688dc42253ba8b06bb24d39e70cfaffda9b1a7a6efebb39f6d09a9fbefbfd01c787e751afb75566deaf26101daffa5061735f7fda381836484cf4853f511172d071ee874594bf113cac49474cbb76d20db4aa5a658a4fac38aaaa142ade4f6a2b0a03e5d6cd0400cb65e3af497761e4caf434c259c509115f4bf26ba7c7096b72af27f5bdec0021fd36a8c4ffaff9a3c74a35bba24c826fe9ad61b60402ef010787c82f8f19903508153e9c142a053601c859a491a3722e362dd9798263a1c4422dfe7186c4cbd1b3a63083ca0075f04bd6317a310f00957701bd1978607f627c6fd5d4beb7b1514956448a602a6e0f576209692753b0c06407613b7cf5df9447ea90665b091ed93741d0ee345662aea3762aa64bc07a64aafdccd96641cf50b2ebb23389dc1c801672ec7c3f6e292eee072750a2d968e40aa20c5bcb93e10d6857b0b17301bb1613d61576e67beabd5aee4258242045ecf4a9afd956b1b97e11f1e6485e0bf5e575f5d5b48884566f7b5236a649e0ecbd5a433c3576d8583e1129f22f6eecd77b2e3c4db57eb59608ddb5e4a507bffdf214a566e8986667a59db08596c4d036d95ed29542c642442e231a7a0a709f557f760e48116d436c6206e396e4df8848e3005faed501f3d0f262d3bcc9802bf03fef9eafc4d6e0352542d473bc87990d21403466978ffd29e800e8479e2886790df18f5d37f60b3c36b747b7312b7c44247ca05968e155c400211662cf3fc7c19dc27f9e15fdd65d19a991c319b4ae4ed7e918c0c6a100c328cadb2627a7944318bf732c4fd410fbd93d8a084d2066148b5a22cd466d0f0815412ac986bc89feebb14c80bb7aa06bc33c82a76e1a76ce00f3bc1b213c14b86ef367f0a92940e0aa61ee18615316faa81205278291bb828dd2933a0681d2fcfee6bad02e612013620cc6b289d7977f387e70ee5834e11de3029dfa50d4df6f0f185e70e69740f2477e5a0ee247ab7164e04e0d59651edd9a68d6e6c9def6e94285dd651d120feaeec55925f93bf0137f77bdd126efc9da4a21415497f87a317a202f7f7f14ba642ba1c0900cffefe7f351087d4d4cd0fe4536620f48a55bb535a7411873cf4d6cf40e24a81a616448127bb69d0d46748c6274420a89ed01013281152e09b8997ec2dacef645edaa6ae8ca360dc7fbf1002e60fc6b944b6d0e24df45ffb975250390ab53a412928a714f40601b53ccf4ba4beeecbbaca0d31f39dd8dbe5fc4d1f078644e3f9d6a7a612868ee1460746b4f69192a18abb1f7403efb1fc9db91d373f2f516335612e91a05bc563f51b8afd11c88b6d22fc668e62bd107abfb1144761b2348cae47c0042803bba41538d1907db12166888414c214a03d9f00679fe527ad525fbc0b7710d18ab696afc4d6ef95bba2af4a57f1f563e5aed0d6b2556cfd5e7157346b69155f7dae9c15cd5a5a89ad5f2b7745bb2aade2d7cfca5d29ba64591efa1bcabf3de860641f7a9727a973f674048a1b8abcb2863883358e096e5a3e602bbde2d2bdb8018c0eaab4720bb6908d7cff3835c98951863d3585d6f88da1447c0a8926a5cd559720a8d4b169755d8722f0d7e5762a21ef3b1d19508a6da712f1b5f38652346f8f730379f1d1406efd209cc55ea1d3e406ecd22beb1214122b0e564d04127df2722aaab0fadb96fc2313a5ed5189fc3eced08c787b5c8d11cc5b193210b615a0872362f17175622347c2e64806c6b20abaedb0df33b11bb71dcac47f28051913ca378612f929709453da5c71095eac0cdfc195566f07078a229babae5c68d56563d5dda4c572485ddba984e48f48a4650f2bb12cfa2d0d64059591981c3a1c7b2652abee88236fdad16d11d62afd1c7a4af1898df4eaba341c81655db7530979df4191f1536a3b9510dfe980a0787365dd1c618b4d89b60a89a4392b1ae1909c0457ee2da23beb94eace459aa024ee5745acecd1485ee23e6c3e6a91a2c9602a979a1b502d3fe369cc2c8dc8a0368a9a1f947de0ccd9bae7b73ce6bc7f34dd943c46d45fa6786ff485c6ea76e90b436ce4e0dcb620b28c8ed6de48fa7421a4b4296c54284b9f9a9c537827211a334ff7503ce126f236405ea0cec6d5f1d333504e656cee795a7a6b7c521834c2badf2af2ec144d7b86c81aa9ea262455d7cbadeab1195618725048cb61b03a9329866eac87a40c557918937326fa353ae4882cb5ad7a2eb81f83d7d992c0301fa97baab9c008b54e8abcdb539e16525d864f0960f133d4cba702f0ff0cd00981105e93ec297d80ed17125fd55f9b7fb16dc99a2515e8d0b47349896f103d238ae7a6e5fd1ec7a3dc43239afc0dd3347c5ca643aad40c5d07e46fa6722cc172a127d2ea60afeb235d7982b9a94509db7899b9a96ce9d66dfa0209b23f892945f598776d116ce2af837851cc595ce1f70bdba864289c44d6d3838baa063c874d5e43b64c77e59cf4b55205713827825b5846fb8cb75e803d5eae7da2f882bed0831180fe0d8380c01dc0df8819d665338ede81a086a0a8f94241682560ce5dc4a0ae044fe037ff188688d182a9b725091828ff9b1fa7993d8dc2551080a1492ccbf434cffe0ca1986012ebd2df789a02d0b2d8ec912171beff3fd73290697f020b2b6e7229d52fbd473939d3170d1012edf1d9ef16e92c6826b4362867c1355d4e94749055673302ac4fc53e8fa368bc5f42e24cf503f6c6734bb0054678f3334fbb4ad60672328601f22689550675f06f512df89aa1d18537e014886fd4f1a5aed69ec39d3010f39475875f8d2c63129928fb272befe097e71fc33738c775fba03fc096cc1c3a8021ddb1e3cb2d5e982b433fb03b99d8786f5b6a0ac3015999250cadfa87c3efdd16b4a55b945faffe7a98af6663c3e50052c4670e514e3ed404e77a333d9c6027d82436b854b113eca247d6e313c1402dbc54820329b881d88b9b32880f68810a6247be716b9ffdee1b9516bb0cf4b6b85f81d80e6898f6e4a61f9ee0db6810a9d8bad70065b699b07bf7600fadb4ddd2dec7721634f8015033cc1dae7c8683a72dd8d331d9debe06a2043efdf6372e272b6bd730bd37c8285a17b11be044affdf5904fd3bb3b8dc358a05bf53bbb3674f28a67c503d5e1c352a3a1a8d210708ecb4e1f3a982687bb92adff297c5092306b2187deb2381a6f5d47f061a91127e12473e3b415da5b5a0a1065cd332bd9904d60ce4e13778b1d096cd1747ee0abd474136e9bc967df0cbdb7c513f1f6c6123111a13eb79071ed03fd960be57b8a403e8eb11a8261cefd89b240663907364d866a1082cf08c7ab5f707f6d82a0fdcad678a7ce7ef174987d50cb80158c2ba2f06d2ddd68c040ce935ef855d33b661269949cc2ea799e47265eb389953d1ebe2e692075a57ecd26f13230d165419f077b3a88cf442494122c9929d1ed4b2aeb42846865977d7b13ef4cc917429f95155b6135e0692afee83000804c6e81d75881e52d9c0c482f165ef7b898373306f4fd4f6608ffc84c148592540065d4d6e3d5edc6a7ca2da8690db0f2921e827713b4ed4ad3e178d0b76db942d1474f366ef04c91b4cf8be26ff565dd66a18abbe31b9582a7c69466dbac961aada20979b3183139e7e43329219eb3575fde2f87d87148dcf188053315188556808f62f286795f0e0174dfb2c214282483111874a90368210fd62e16aad17db3ee0333fc420659e8afec3c0d10a45c212f424a5e47408892b7bf1c58d1831427276a0c9ea3dab34ec1542938c7a2badd253910b2218038ac46a00a23d97004e2a14d91c72af1c9671a2f39e31bb08f6465af25253d968522a5c645a4d121e120b1c057eab4e9dfd0e805d77b3aec96912526bc4677687e516522d353a56a62b768c650f6105b4925de0cfaff9143f17b2e4afb767147b8ba3a45c8469c23af2060bf212dba1a011be12f3eb161ad4180fb85686f55964284ea52a2ff03fb40c81953cf434ac082764f16d2570e3ed71aefcd423f7edc4d76c4615005b6e8f62fa9290cb054624bf58e0206a83b60231e6807e39eb65809f9f0a2e965587f8a61d1cb4043e7ce9458873a02635457daad1ef2cb99cb0c5935492fe90e25cafcd8b60a3116f0d5dae8ee56a02a9b76a9da32c702a60b78ba33809a2b56fad85b90b5966db8b17e7d86c7c3878b8ef450052aaa4c94c144074556a347b0d93c7f59e36b802c4976b04087a75759b55393a92c080c06be5e7ead132b56efa8385ab76a97e818d15275a8fa713a847b152bfb78a9aa149559e051dd417409e8f04c708c1d682b315aef29dfb38fb37ebe48653bf42dd0e55c708b6f415f8e9b43e74b7862fc7f03accd0c499c9e90d60ca24b1a8ded878157ea9d416e0abafd3c31568da53f979ced027e97d4be281958bb2acf534c4e95741b4330e93e86c2f272adb1d46ec9eb4eb2b064cc67cff0aecfab24ac7c9330fcc8abc26156340fd22d31cc8e34d4d68fa5ed306752af2a6c47c4df21e0fd370ae202336f331d9b3da7773699e68ab9bb28fb5d652ea603307625147875eeace403d1a4178b8971e7989a6e375d565eb0b27dfe9125f004e149955966fd4406cc2c50931564120cc8ec20a850d78ce8ffb8bde1d520d17820018e8d788fed8ebe72ac857a489f5fc022564736ceecfb5f9b0a47f2d9fca4c0101ac3bdfeccb3912dd2e3d9f0d4ec4003181029fd92892fc24c0961a8227c19c33ece671533f74e7d805337801b1078604535833f2bd7dc214e9d112a8544c644a786482365d1faee01a0d870357a4aaa92b757c2c2850358d3f34365eea5185de32b4c4fa8f35c7aede0a8e2828b51fd1085e21d8266569cccdd030ac50fe51240a7ab04e0aa4231fa1c0ea6a69dc4de1ac7b654e829117d250074395e0245fc5000f3f6ac2a02610ad9e21cfbde0cbd7aaa0725316cebcd9ee380123bb7e8d3a2bc6606c8fd03133f9af47cbe5b4af5fc1bdf7cfb4f41b9fe1f56fac7882d75dead1d1d60f7d65943f27bcb54fe22514b2654400e45a5009fd43fbcfc9c18be576e485e9fe9878ad6ab0c84147c3f919e2150f56fa10439fe736a48b0129f7a23e56c0eb70434b10e2980998fcf4e921a3f702482af9b3189efb8d740554290df97f12b444ffb7ee3533149197e8b741b82011fa6f73de4a15613de757890003e58d16dab096d4ae19e1d9535ba9bab4a94c6fd42d3a1d840e6c69cccf6d342c10c90ba2bba71566b91f4fcdc1f9c687d3ee922d11f0449ed25027206b215d0d4c8dbc32d255bf93148d3cb6f05da190d22df8cacb3dc1a0473bf33b968bf46f507ba71e76be282f15078159db617d11126b0f002bb268fb617f6fd18a535b5925c1a82b0da5818bf025aa2592ea2af10e29afcde00df0f82eff30566e916469772a27572231571d4574b330434fa1c01dfaa5d737b8719ed9693a5c03196e1f24219982664139bc8898961b80d4e8690a1f9ac52e24a62c9e1b4b096f65c3483dcdb5d9aeac09f1cfb52aea76bdfca0d491dd80e84f37bfb7dfb0464e6cec380b028f50379fd7300df4fa3786a96d564b39659b9b1955bd14eb785716196cf05c6dc93ed5f7712a73a2d14f8fc19a1e28ac475cf9829c3b4bae78e980b71f6d9e2167b1f2fbaa8cb7a507551730298c34caa1850cd9607c45055922ed0354663efd8be50aaa3989d49fbc90b93a31d78466b3d1cc18bfac9a771e9cc9d529df4348f875e86ece6cb21ae772e1d15f62d52316fa7f49ad89817401939582c771a461d888297ed76f78e33eeec23d03e049760023bf747a2010d9fb1a295c4328019093c478662446bafed8895449215e39824a570e11119c02d4bc1cdcc9de08c4951f27392453e0709a52e2e424bfa0b452626f9114830a176652a4ad445d6a776d0629113135050c34a89c35fa0f3d858daa9fbae007ddaa558217f262a633eed0298dae1b054f8f76e162f8fd7f84d98256c9f272052189475e1137253bef54105e5b268492d61fb2abfac306c870df004ef9e40e5b6c8f10716e1be7f0285a399c0c24f3684b44617933afb6615bf7f55ba30c2568d18309c2464b66a372b19053e0577eb502d83c15451bc7e2e5ac4825b7943eabfb5af27b428a35a8ced9414b65e53c10b6d6621949951bce2692323950811262d0632e4ad82391d92eb0da73062efb7a2e092790dc7a3bd2c07174554a279770a1ab819e81401069d60142d2d2ca6ec3623d92d354781e59636bbb0cfd39b5443dc51428b0136faa94530200f62683788f170d16ae765735a1af3373dddeee6e3f42f91528040ac8c7dcd3ffd06147a16d6bf877f01d50066ae04bc11a0251982b49aa31533bed7821f8ac87f0794c0fcb792d4ec086c21e9bd4cf69365f6c5c5acb825d12f3db7c7ef5ef22ff5c448642e8ed6e3faf2a51eacc5c7f788e1750659ac83ae9bc1605b7d3024800286d6d88f66950b0cf6eb9f9de8c63060cf804065fd4b0c76c1c59a7dd16cea365525244148a34688341c42f795ce19fa39ef8488d946caf8209a75b01a86277cd82c0b2b1c8d8bfb821a0622a68b892a16074f0ce0f0d80bb8b0c0cc00673e18bdd807e42767c3cb768e34ba5d283c0b1c53f46299d31af341d3e6b68258dd803e30c5fc24b7fafd0b706fe5c556e48a59dcc7e21711b37fcec6999e1b500a4e9f9b1b7e8bfa6c6cbe2d1a1b36c2525eb672af9f20e1aca8a7021649a4c6e78b4b859123021c570a921a4a9698d21d364ba2eb1b74e298c4458added19a2b1414e1d68d7d8c202d419e5367c25c736e038a9848bac0c94110e6b5cdac3430a5d6b3db1c8d917dba6897c233fcfba7e7bce0059c6239caf5bd34f3b0fa5cab70497d27ee137bfcf44c2799c7507867621b31abd02d4e45529d1e5dcfb3854569c22d4bbe0b678380608c5224b19b55f2a23db995a763886ee2a8abc4444f79401ede8a3dd3fc9fba246de4bd13f01499dbab03e568e3fbfd3d7ab55bb1458a7b9df77a1bccacbf27617d1d3c83531f0c2e9e50438dfc61e5352abecadfb111549c2e46778baf3f38621226ba56a12192cc72aa52a187c1f07c89c4192a5b778e33ac8729632d93350490e30179bf78eb12e8c75e6fac119dfddf827c86f00a4bd1c70fdd3d118929d07b192f10395240400e6d19381fe87a67b59a7143c768669f376fbf7b136e11db8861e904690f009cd8a2bcab93ef3479b0246a5f8b3ca5eebd079d962b29527965c66df3d6e085b69b55f3c25a56ebcbaf7fc97892719be30ef30caf0c4fe7c4dba4a0182c76175329ec66fc0b186e24deb63adae5f3c914c8dc032bab5cff962cc41024f26a7a58bc6202fe0fff9f88c21d576660c53499531744bd522e3ab84f95973abe4e392209f344a660397cc8817ed76bdc79c277374560a5c9c88a87143a3414e66a332d9986b17ad0828e62f6717288ef88e036ef075061e147be73e829ccf4abe3120e4089a6ea46dc15fee2d291e463bb3e035ade4a5057256e6bfc274595da425b3b1946b228f5bbac30fd3019cda9411d5086c967c24331300d7f3beddbb5527433540c2b83d7f834a85d7840362e61641687b06ecc7ffeef6216e7c18c92bf36b347463951265963f00657a8ffdc688306dfca6784699df62d5581dc9cc461027b20d0a4d3e1dc8b8a5bfc5b20351fd2c066a8f4785ace1a6e57a76c1e26bbda1fd9d2a19ab0f313f9027356c942e48b90eb1bdfdadc58b6bfdeaad6bb76bd72ef89c2a0750105a97ae7b358a117a3a397934947519a296b272e271d24a7962fcaf06f35cf8939655c6376a6dce9ed4a7399348b4dc669fdb21a499aeb8550e898322d0729339f831f325944c60945d4efc78764f74837c4ee21a43787cac3922a8b3f0018c2d952dc3c30dbb7499c6cdd1667d5c398798ea55bcd339e20c35ab615791009b15ea92966547c4a5cb1ea2967b097a8af72c2896d8bccb4376f9420fbeb16d854a48b4647a19908f812d5a33ae8499d35c1a1628c06f5130675bfdada9f46dc28491ee9fcf1db17056ce9a1742fda3ab481a038bd4c8242ffc1f7fe05ffe512eb16c0382a997485a8372f5faeed5e2e0ab3501f93d309d1c698105706af0acc9ec8e0a053964fbb62857860f2d7e7c2efd074b60e812baa4662287438a1fe78ff70f65ad3fb4a7cd4bce398e661354946f9379cd0a3d8ae0b88aa3ecd2137d57dd2007474334813bdfb6fe6ab7fb33e029790dff61c8bd6ba338ad0240084fc0b7e5f28c8278f02da342ac7daeab0b7f5e2a17052def621378d635d0647d8a84d6634c68ea7c25435a6067ece4c1c3d84a90e43a5fbd85bcec8a9a4d633d7d9d0ad1bbf2d0f6095051ecc6d28b3a333711ca9121b491986743e693196547abd14dfca34588776f21f198f63f0d1c6c23be0f2bc4ae733dac024c330713cfa39e0855192f00919d0129a95a766485258bfe1e9f276adf26ca9b359ac62d2b328ed60fb34cc6b92450e5d5a0fab559c131f081d6c8cd8315c9a75797e06edeee4c9e0a46e07444401151bf4ecd13e6da91c4553ec2265f2a917b69ffef2f1e641fbfbf018f6a1b8583300359c5e9e90755d3ebff63ce37289cc11738cf26249328887186eba367416141fbb4f11308b849b51e630710328f526d363f6183c20a2f4a67abbe170e343b08961cd9ad8f32be54942a3b0aaaf44bdd4ae5b0b179706f4d3242112436fd7022ba8a50997ccb191aa2a87b09f9ec01e341084a3c50ac81b1a1a583508b5ed344326622a6ec07be86d97b0c75e49788efc6b163cd6c851d9de03e2544401ef05a29416e75649c018b5074758ece25bf659d86110f72692f989763bee194b7f456cbdaf00652e4c089b02c0215b205507ef9c9771a6a5db52cbec5734c60fc4f957496297d663afc4941def1293cb36f29c517cf3be6795d3cf18d15f4676190aca741ca97972c0f22c24a44190bf11ca06bba843aaa9222ea9ee235b696dd28f380f07a0ef945502f9d898c21cc25689b8d6b524acd5e5ab1d4abd05941a3a6ffd217279a1ce48a6581d684924826a3b5d82696fc59b10c37c7d96376e7e37b6dd09db9472c086ea53bcc63681bb7beb931c37f350773a22ce4b6d68c80177ce51f8449aa88e69cef2d5e0bf83a1703ea6b617fcedb70018370a0833029779d0f33ca0221ec69b43a53139ffb865c0e5d6cff2783247446616274ee44989e4cc4be241631efd54aae245feeaec2213cb0f489e6724b8149db159a0918820dec1233149b7f5499b78eb1aeca4bdf1d971f798ba50445add82123267a85a1d6d2923fde7f6cb15e077c42fe2881de0ad6922a78412aeaf04e9e464e4a185e1ada36cabbfe391eee12d4969cca96284500a6c9a9cd7f2e45e6f20439a6a4acf7e97f240b5a686547cb80066e8e5997c5d586069e56a187356430706f0770c0d870a2eb25944351904ad6e2918022199d8243e3c89cdc168ce433d91814ca9c319efbaca316365d6a06dc250d6f9f49103ed158491243a4d0a60d8063c46265a14c061bd70051c97ac6373812b08f7f924797be2b72e1530d1c8e5621d1645489e06cbff39c42abec17181ee3f329124cfa9265887c99e44f06a119be82c9673b56f8a8208fba88de8a0876cc673e04dd825cab732c1f3ab9fd9e8d5a9192c7e28f6b99ecca73af7c9829f2b50b20a4325253f0463e415b7e3ec8c49f7368e308f46869ca416ca3541af0b506b76d64ea8fe0bd329b23838bfb44a66404726d4ac2666da55f2c09c3c8a1680577e80a2ca7fe2fea21c960adae0a4ac02413e4bbef228c3d6e6e69bd673f2979305bc0c554932e224758870671448811def58d9c57679043ae142fd859b824f96de33ffacee2a8f17931455a0b7ff98e50e8fc8e2e124d427460a60a877e441cbc837cff803fc2e5ead135a34af7c6063a16f356b35407ca40e1e4acdf2b0050ea3cb1b7a144c814c72e81b86255b2762e031aa60d5ad39af9aeed75869476735a999e9ad0ddd87cc342f783cee2ab638a54c1fc2d575b4f5fa1811eed27d71426bbb592a5a986c384ea591e6b2f294890081b38890f4e34ffadd19eb69b313f43d8a615860963d8852a21162d10b99fdca8c895eb510608c4b7e7673866e9a5960bc9620471f0708b5c328f2da64b0631322c6d14eec3584bd3162cc3309fd4c1de9964d53b64ab6b7d1e16418e438348466f62845b75c687662fb18ff5cb0d57b0fa7dee88e14c62f629315159a393c0810c7f2867cd25de292e34879818bb1db0538491579f658e887d6598ee6d2d31cf7d78d2707bb052036f1eb6c5a34f56c0b7a263723a5438abe766601fad94a2519e3ebd84c0b4e84df4cab5402edfcf6dc92bde6d123b18da1117622bbc3f4d8edd586709bb26146b740569944591c6b67b14d92712aaca06594faa0f17ee2bd5ba41e75bc062771cdfb85f5d068a9d9cb519c2320d35fe6ff5d392c07d64adbf5cccf499a5d1de7df9df42c8e39efc19d0389b94f4b0c74a87e81adcd4231f295639c518f9b9a08793bbd7634b5c0416fc027cc612e7d96bead6f2700bbde923eb5d4326b2fcfa807512faa44012646872377c16e977fe4c32516a11ec7ac66a77659993a43a871d7a6125c7cc29e4a650872903f0e703afb0f10c04b05a9cf60765eb4a6c0cb6876247c13c9ccd10b10a18d68f16c0584b94568c687cc0cd4ee6c92e7cc661d0e2fcd32f6d6d0f39086023da07ad58baabfd465143549e2f8f0c1a998410182cd21db5c63a77dd34cb9bc2f43c8b63d11f642377af89f2e662d76772ce121b112b8244285feb47d931c845d1ebc5cb6134dfc17e8bf38574c4436364508eb1ca9dcf81e62328ea0c11c24c97f0210e0086fa32f12b8fcab7fd11d44e890bfa8dbae9cbca7f1aec51aae008b2344ebe861ab0ec222388975dd5832be4e6ed03544f1ed43245789777d595dc3015044a09587c57d3425201a0990c0807e13828e350a1b27a22aa452e05eaa1b8d0e4aefb52b8c8a169896666ab22ccc96b967738b18d6557644ab1cf0c5519056f87b8dc7fc009b0e3949b41606fab20fc3b4e67844a7d6b35c782d6c515dafd12967a9dce80a8409c3eecd55036079a4d1795cb414a737cd8be5f4baa7700b7d405e5c488bc133c50326c66478ee288ef1b7d31779684d7861cfbe625dbe690ae06428627cf907209b35abe88e09b556f9dcfb1dbcc5b3439420aade1d0c0ddbb4e60795815a647486464af4a2356690e50406c1aad249f7120e785a631637759fe115f9db290079398d73e40eefd33a0b9d2e169b731516a19a65d35e15f31b4f6761c366ea0f077c2997980beeabab4f9dfc5d0421b79e1777340460990473b9f4084923008ff4932dabfe1838eafb806b6d5b01ce51bd57fa60fb374537e83fa1f597f05c118c67f946454d8d6fab9084e12f93d93900b191f8b34b0c38b34ac9a2b4e417726da23a2f1346fb192b9309a293ef30dd2c278fb85fb1ac5d1251fec9e5eb84fe14a404930d0ea6b473bf3ffb5af1e3ddd32a0230e90fc245c30130b9303d2b96b7c4663473d88073aa8b3dcd5b21a96115042779250274ce4f9f9cc08dddbfda35326130d16bfaa88dfb9aab99cd4f8673ad69ca7cf43eddc9978d06ef16307ef1c2f61334a80c7e9877a33401daac6b89479a0a99d6444de12a9fcb58fde7c702de0682c796a57731a26ecae483b4d890bbc8850bca88aea65bcaa20ef6cb93f8c05f28549022153c488cac41ef27ce575ad1048b543d14b086ffcc2ec798168b662c9e99c917d99128da979e88697f915550d5e142b61a651b1bee501ce9327a7fb0231dc1663fd11554f4521e3f05c2efcdc3d10462e94ec1d688be57f2719e689efad090f4f00a2189c40a6d4bc8296d1b50f1d6815a7e90f1d183fc119e054a8aa82e0f18d8cb30b7efd2f9b2f2d4de2147ae004f9834837066712e4feb4deaf485fb1390b7cb3b2fa703049c391a2b4b4b9165e9d687c211edc94f1899a498c4a9d9a63e86c808bbb99fbc5a66c08ea83945434b464f482a2044d45533b20ae4a3904c82544cb9f226a16ed72c78c7df5d7b1cc8214a840bac78218768fbe0a89520357880b0208d0d9072439960c52bb06d26c7e6b8380e8f28faf38d5ea7794f83858f81c878680096b1f074c3195d9b824122f89fa981b9d826a35db6d01cd22bb29600d51ef231b4c87f43c2d683ac059cc50918a28350310cf65c891310d6f67586540104aacd341c0ed299b52ea12dced2ba63f552b914e9c1878d0d348af31d5d2d38869ee621c327ba93284bf1b1ca2bd62f614f4688576144aa53f3f756de315f563676d80eb36d22b75d233da7cf556fb58c01adeddfa0bbb263be29b4d8a295e4919af0f9567191048cb57920e06011df526bf12a64e8154a4565dee2fe8cf23db4a694972697e9da1c3b5ebfbad712e5bba2e3ca54ccc96ccdc77bd9d57ffaf8d5bfc7eca9579e8ef92f8f7d88282f3ebf30c9c0dca655643589c015369ac5cbcb25a14b19666349dda0e5bf484a777cd48d8901701056d3ae90662914073a26471d0139790d3cc6a86dc59069a92139ad874832bde631b6af2d167060cffe11f4372c27364445527ce3fa04eaecb83007681e56935ca8d9d3f6b8fb9cb206524f187849747ec117e6740937f80f91b6659b0aec951259a28373af2e4c1b76616204d9b845c97496621266071212f7fd4a9b44ccf1cca7938f98e4478289cfc8f66e626aa2b7bdbb54fa94cbdc551938419e28272600a26ef42729623c87c5363294daeff93339e7a3f15cea2e5f11aa4b904de41457812501038e36e3a00a07167ad8106a071e75a8196c65deecb6ccc3257c3b8cbdd65b79af50731b5c145c58416cb91327acd49c96a7814f8d9db8de8c644fe6cff7b3b8afef4e093f6b7dd6eb4c9de5b4ab9035e113812a6131fe857bb7ef388dc0bce9ceb93e39dfee94eb7d3ed743be14e3d77ba668f684e2802fdd3d30254888b833ad1faeff6b1eb290d3a52c8c9716745121e2fd603b7e6c60ab2869e125e0e75ee4ce1e1e24c09592f067bf3a28d881917093e6f56e365e7012960011904d6ce41cec46a8f0591e465e79d79582c6aec393d5305188da746859e3da256ad79b21ca42c08d574329db0630fa84e9cc0aee984dd359d54ddba476037cde1f69d2c88fcb9b7477c8e05d1b6400ac8fde90b30775bf7d8b7ee74de9ab1a9f64e16c429547726de657da2694eddf5504f51e10ea360048a533318d6b4661990aa322d65191064595695c99abc0781e9a71436851ba8f5d6be53ad3fe8533b6fa7aea2a708d6cff936bb3ceaf879e8e938b79e5e9442dd50e3dcad4e7f32c869f1b9ae2c367306304560a2b6602061566f05628fa69b017d79d479ed9abadb3cebb5fa5cd9e3e9ff5b73503a613552710eadad8c936855fa025c77d1aaf4c46825ae398f562189e05ab4416185c2a222ad2e960aeb118fd357a299eaae4c73842bd413d50286df6e924e1fd54ada6cf3a6a9e679172efc567afce7929a3f75d24ae4a32aad50cb0b6b54dad2eab45487a617a6d5e5716258cd2c95a5f679d4e6319b3535cf6b1af13ac3ea04a5694d7aa2ba439c569d7ba45de73cdfb9475ac8c1ab13561973510f4fd7b9bc2ae0ab73ded3badbbd59d5be3a776239c8bdc9029dce874bec0596b802fba8c5d50da3612d6a726e58935eb49ecb4148dcf19489856950f0d42cb573ce7bf83944d3b0a86cb39b2aa6c1f3526e71e53057d027845b08660766312a69080cae1a0d8f679e61b4373d9fe5b4375da3e5b3b565f144d35c9a5375aa6ec73377bc9535dd5cd91ed0dad3ea71bde9405b5a6f7a6bba3b7bbdd9f566ceca43d3dfe0defac0bd652aa4db9818d998d8991918c6bac239b0f015d67ad3d1acc7c9bca6d9d6dedc4288f210651671ce5a9f278ab3d6e789a2e989a6692ea7aaba9caad3ed762c088cd71d6f5d59b6d70372b78a57dc629f6fc53fd374b76a4e308786569308bb4179885d21ecee1304c66d1e9ad8f1670a4f3d78542d90c703c2599f680a946341a4e7ab6bab638f27e69979b76341e45e359d4c732db2c4d5a278050a0d81b4d94d7607a69576bde94b4d5ad33f68734551b45928a4e65254886d85565d9587da4a6b9d85a9934044aab590edb1479ebb851ccd189b67cf349db2a3d84f3f75f11c7ad15c16a372ce49f94d09465513d5eb118805b1f4aae9d4b6c75f50109ad143948976143a9b3061682825222242894e9d3191b9f6b6083ef7b736659cf367d76d107b14ca39e70d50144dd19ac209194f30cf9ce7170499ae9010d0501e1ac2262ad4b6137ca6bb5b20500381198827b0470f6a1e169dbda3ec43434444c3a66311447ea1a2d6c8e8e828ca584442d219eb88888040e3d1431e9a5ec482c01f659a46461b1c3bc843d38f5810f983410aec71e8a3cdc2b4259af2cbcbc4c249b7b710661d3425991939c4d166e1c67dde1456608fc03c743597a2a76b77f78ccd1ed09bf35d4e28020b5ea051903d2e2169338a4d4c34dd748b8c888e82199bee5e4d779b6d47ec00cef628040207d902e0230a2e1285d5741d45d19ac2b642057737bbd875cdb4425b81e501adb147d39d0581dd56686d9376b459a8959494b2921256327dbddd825785162c242529295db8609a4b4ba98524ac942404c4c26190360b93580e942eb047736969099f6793c69031e025f6083414ee10eb635e7e37e7ac3dffe996007f0662628f39da2cdc2b860c4decd1893db64f3807d41e872c88fcbefe897637e9f33776c163e15190890212135305676262728d98988a9898746a2e45999a46782c7c49c825c07f9a4e3ca004e857f3d0ccf2994d40eeb1b1ca04f2359b8023adc20df9279b00a2c742f5b1708f809ee799d1f33c518da6e7899ee7b077b22086804cc081ee85a0ee80de8b1e4b80f3fc0ef1da07e034c200ce87c06be7edb22ef7f608de336fd519c5a69a4b575dcf74fc3956658f2ac61c985e02ec59e717fc693a611f52753e9bf04cad31d6da4d1745ae6606a7e90404638d3106a14108fd888cf1162ffd83e1fc79cc260008bb8ec3f3994d40fb5878f401d07bfaee3766c1ab9a4ed82f3c0f88c29cb83f7fb209307a2c544d27ddaba6d3f3726c02d0df23bc8ad901e85e553111bf6a3a9d2ff893031e900938c83d760ef263a1da53d95e2fedf572bd3597cba1bd1cefcced74cee57239accbe572b95ecf6452df14ee1184f2d0fc17e01139e73b3c6cfacc3cd4a7d01c71f6c13678f8cd93172604c6d19b61569652a6177bf42d2ea01c596d320f3f64aade0afafc42b8e54258d37d90876a0766312a28692884271c5156b8bad65a6bcda2beb225c0ad109887a7b9cb4d104bb3a7bd2ce4f3f97cbd2bedce66d92ca3e5b42015f8d3193f685785db58cc09f7f027ec097dc25618e46e0f592845955e26101858bb3118d81899188516e8c3a2072385e09191fb109b197fe7a488b48b6bcbe79aad0728edc65d5dca2fafa131aa7814f53b27d7e62c3d6865e5e8b9a6cfb89c6367b95caa69b3d4c8e5524abb71d6169756f0c8c88751c5e091511136dbd82c83adcd69e363c4ba4afe5cb31715a5dd3899c4ca7175e4c3e25153539415aec254e6af18d66e8cc4485c85a98e5aa0370df3501f6923ddb640a03b5cb1cf0a724d53da6dba916005228800027d689e1e040aad6ad26f6ca6f672b2e9cd2d84a99379785e81485d0bd1aa76e7248f1f49cd74f2783d374d1ebb735fce640fb04355b4598629a5cd610d732a6732b1b90b7738c1886fd7292093e9e7ebefc7fe4e1d17f7adf003269e1ee484f904f2d668acdde6f24f0fc1032134f0c1c88b7cabc0244cd4be733d0fb591f034b3eebcfa9387e9aa3be7f8d379eece44420cd9d642c8aea96ae14e7acdb22ccbb22ccbb22ccbb22ccbb22ccbb22ccbb22ccbb22ccbb22ccbb26c199028e998a89da7834442afcd74f0774e86484a43fbc0ccbe5c2e97cb255cbbf3d26de7245bdb5a9113f95683c03c648b8880ace72d11a819590741ae6906f3b4407315b2bedb55e8373633831e825cd39380bbf5011e0095ad1090a7a77cb70e87bb35e7d379e0434f1aea7cee0946bccf8335b7aeab4f888489a7bb9ef2c0f4e2070f3ec8439d04d3c9a11c5327b119910b6122eb3cb7cd439648686626d685acab6608a031aa689a424843304848ea6bb7c544d6f5145078662676ab392b8450d33069826bb6c3377acacc4cea8ad3449c1e040e0d0dcfd379bc74c52c8820d681c00c16b01893e9f49d7bed4741e614506bad7db74240de07cd747ecb73206f7bfe33d903f03ae860dd44af739e939ae9f401cd303184f0b57fd06e07695aeddb04db40d881cf8139a9a7804c268d3bacc2ad6aed3ed761b3138c38fa7cc295e540c7c5fd4ee89878fa073d85cd7ec29e10a881420f74f24f3fcf3cd05379295c32b968735696da45cf91a54ecf5779e94556bb2a26b2a8af6e12c835f71ae4ee10ab1e72859f3ff5092e565dfcba9f8bbf87df39507d1502b3d4cef06cebdbc2b3314ca7d377ef593f32a7f2d2814ca99eb31e644e1139eb2beea09398783aeb2ccbb24565cbb242605eea296ca6994e3fbd156ed5e4399070f9a7eb183e3f5d2771070f30f104f29d755cdceb5d01e840e654d16fca8875ed466ece6462fde76a73c87a910b6432b10e74596f590762855b04faac037906f2cc1e807520e19a875ae801ee00e427d05bf70977fe6d9af99c47e4ab603a015d2093493b906b9a44ed446eced4e630e7acbb0a75ed6f6ca69eae33b5ce0aca58858e104d7fbaea66f174ac37e0154de08877b7235ed379b915f2f4b76eb6ee069949473d2adc22d23f5de7bcfc28fa6183475fb04db7d7792e013eae6ccff770174df5c39fabcf47bc2cebad7fc0aeee138ae0f3d547bc3b9eb7cef3d67bee11f69f8f7881dc232c3c520090f7fc0320ef39f69e50842258f841cf7f42118eb0b79a29f5116fcf4538c242137cf0735533a5de7a90500411823ce837e205f211af9bafcf77fba94f68be2dea29eb9aefea9a2fcf1df1eedc112fea6e17917e1676e022bd68b52adc200f732e928ca29eda2033a5befe873c4c3d4f8bfae9a6d999d5ad2214386e03d689c79ce2d6447dad6538535314064b54d91886ad2bc21e567a70527c5a33003b374d0d7e9ba669f2d622391e7e6bd77af57d7a7a9ee7e9a85063605465ecadedd52905b42a7951a1da15438c73a8f4b5445c3b958256618a24a1b57843bd306185ca221c5add3124a2f568c74901b812b9048b56263723a315ca895a74b4b654c0e04aec43446185423301698d2aa30a55589dd482dcf858638d35d6588305e041604a0d79bdbaab9bc513aeee97cec2ea142597b4267da9a8d2ea6e9d17560b46b4d65a2fad403e6a1eae2e981dd35ae4e4c418d6a4a4d65aeb0c1884ae5718e129dce08d5dbd885c54954f3f857b1dc099334e7e36c1f9a759e74fa18833ab23fe09799e290ff866d09bbe375bdef3526c6674cd6e06b42231b935bb515cdaadaac05335a66b769372297ae6741cdadd9ad7ec865d697b11f9cff5793da0e7f9d4df1aa4e6527500bacfaeb6bde9277b0afd08bc4314b56b1c22bf3dce28ea1915669c5da5b376abd93de240ff9987ed9a5d2528be35bb4436ed3e7fbb3d7af4794226385f443ac1839c6b78fcbca13567f3343178a3d49b1c9e67eaf3e803f44d3dffc62f24158af082a8e705add92de268b79a5d282ded56b3eb6669b79adda3ac769fd9257669b79a5d263aed56b30bf5d56ef5c8349db686379de94d2cbf4f73cd9bfbc80353336179a53fe220ebde34d59e879a4e476f2d364da1065eed6ef7081f5cb30ba5d6eef3b31b65a6dd6a769dd2b45bcdaed9e579ae3e0f57795318621d91872e5ef3669bcb82c05f021cf73ce19a5d2729ed56b39b04258987d1a301e4ec4e781ece6b7637faeef34e9df1b9d7c7d9cc4a6b764190faee95d0576253cfae7ea554a8e6e75d58b30bf4d36e35bb1bbb26385f049a5bb4b747fc395cb35bf4a4dd6776f739807d9a239e9743ac26383f04f04473a6dadb235ee561a6e1d205a5240b1884ae57c88044c1348347464540222727f54fe11e017decaa3f94254ddd4c324d8df3f1d9daa407f4d49b4cdab1b87531c7b8cff6c174d22cd0f2d0cc56583b5f0358b0e5a1f9db04337d6757d5b6cdc847851d98ee870df2503bd00c292277da7da618f59dce59b0739f7abaf93a4f8f54a1073b13ecd50981c3f3ad3d85bbf866978e88b15577a7eeb13c20b4d5f972bf20a1094344c022a3a32005a40aee0a2db8c91e93fc435e3fe42fb138abefdc10a698ba0af101a5a0a2e6658a7d7d30c1e858f09d2b224db27003684b44dab5818736d783d0d5eaf9aac47ce4f0a0d58191ad82d12be86093468c6580a83914a29028f85883d0b6a4f9d4315514b6f2a0f8e6a8183c1ac29b400218125650fc54f841939ca226a322203b221f6f464234bd81307c231a028970e88ac8d824f9d4430831649b40129b09494313b6d4d43f0891941682bb05f9ce15810a05fdc87590871a08d6c9f92e4d859e873908ccd1ee24dff9ce933230e8abb30548c21243ebd39debc10d11fc54e801a01b1df07d3e1f5055733a9fa3ec5157f4e97cc610427650dfb92184be6d5ba14fcfb3fd9d70a7aef356e8db24281b646cbe45db2612b47078e70301a9eb8af2805cb3479e88f38180dcf345ac6d7abe7343accfb2ecce55dc41c5c4b4a7037e2a24b4d7dfa9bf7aaf042fc8ecb117627d96e7ae6c014c30248021e1d91e2b5cc1ef3c4db2725a18d2ee356903f599aeaaa3ebef7cafbf3a6fc544e14e1dfd546b2d14a15f5bc0422a3cd3711ee2b54e6bfa6fd14f698ad35c2ec5c00d33f4e9049c22aa6adb3af939d126cfe554b56d814027689aa1985375bb9d4ecda55fced25c2ecda93a9d9a4bb5a4699a53753bdebaf2763a35970ad991a6692e979690a6384d711c8cb3a48891e672692e979a698d934ed2e454dd8ec7dbe9d45c3a254dd39caadbf1d695b7d3a9b9b4469aa63955b7e3ad2b6fa75373e9952b655ac8942672aa6ec7e3ed746a2edd93a638cda9badd4ea7e6527c264dd39caadbf1d695b7d3a9b9940c4e539c53753b1e6fa75373299a3b698ad35c2ec53a9e2c6992a638cda93a9d9a4b7190347d93e672694e5573690e3bc54aa93e7b8a647bb4cf67479154cbdf28169adfe899dfa8197a86a6a16b4ddc7ea36cb9df68db0acef8dcb86a132546dbbbd12a1031ded020b3268e899877a3492024570e2d778e4489f16e34cba8ca8b2873a6b8e082f56e74cb032d6043926895f892e2dda8f2b3bb28a17401848a1a9777a3609f6798443c23ee731f8c7562ac13639de77972719d7df61399a5ccb026794e33bf4facae734cc4151a293ac0d8a851f6eeb32a45e364cc16728e8e2aef3e9321d071b4268176d36e703a6dedb36b329ca990e76f4d95f65b636d5de6a9a88173040f932158efd66692a0d02211e7c58dcbbb75d20340d6cc5021e39aacbd5b2bf7993d2aa0b069c146f96e0dd60093e8c70413f739f70121b3a165ec14d7c1c92462dfd8cfe72266c36e54bf331599df196bccef8ca6bed9339ace5f6470254984bdc96af2ee5cf55c6cc869eba28c11c5cabbb308686b3b966530b0c87877ced282026d3944451d1fefce5b0e4803260f1017795468bd3b2b7f042a8e582569d18602c6bb33d8e33e1b9ffdd4061b7d9c7d04b3036a12b1636933c66b050d275578bc1b2731b88073a27c7246ebeddd42c44f14b7253d62087937d663a7c656122d3d7f72bc1b6781e551f3b2a704197d8ebc1b83bd73bdf9520349d6931eefc64a31bff1589807f3bf3195d56717a17553b77936957d021a087332e3733efbf2d84b131f33246825d5e0c4bd398c5b111d52be9099a97877fbc6214e7e20290b23f6e2cd42fa8461d901870515b3d7a8a7462861c18385932279aca8bc1690418584305d7f926479817f5ad74dab8146bbd9b1c7be0106ebf5d83724dbcd663d7636c926c572eb97df429f73d3c4e7ae7c8e8b90b5cf51c9f2395fd9630ed8a4cf638f52b67bcd20d6ee95cbd7632fdad2ee75ebb1475d59af1e7b949576afc8c78ec147db63c7d0a3dd3cb4c72ebcc1137bec421bede6753d76a19176f3908fbd35d2ee1ddb632f5ad3ee9dd9632f6ad3ee9df2b1177bb47b97f5d895aeb47b57f5188b5a9247db4751f7a4a61ca5a2edd48837e73cf6985377ec11351a21dbad737bec4637daad437becad8b76ebc01e3b062aedd66d3df6968476eb928f5d89acdd2adce3f55333bf839f76e971e5d3339fba0ae453b339699f76f181e4983a0617668f1d438b76abcac7aec444d57aec4a4bdaad261f3b51adcf63272edb9d637becc2b1b3c72ebcd2ee1cd8632fd2c81567b43b0765250725d6eeb44ada634fcd52333fa88eb3573aa24733d2afc77e74d5ee74ebb113b3a4578f9d3886f7fc793ad093cef374f5dcf2c50cc3a39e9332c190f0a9a7ec313d9e462826688f1d4a49bb51b0c70ee407e57aec407dda8d623d76b44a4fb1cb13adc2d143b2cb3ca5b250696f8f1daa4cbbcfe5638f0243f6d8a3ceda7d2acd9c5a8fdd29addda79d769fefe4d66e6df6d8dd1cfaebb1bb38daadb71ebbdba4751a83dd6948bb4fec4e5bedcecac7eecec15e84a6b57aec4567daa425ed3eb12729d1f35a3b50bb27e5a9acbd294ba1aebda855ededeb2d8f6678d44ff6889a53f98d4a1933e47bba6a4e99ef6904d353ec123b919576b3cbc7be59b8c7cec2b17df2544b3683eb85ec92658362e37aec506d5ae09d769fd8816fadeba52d5ad2eef38b94b44926da7d7e528976ab8f01f27bc9372d8cd000673179d434158ef082346796c8b908454480be909c30e72f30d11ca142be2a7e4dcfb9684e7f54c030daa0277de820557ba1265d219322c785451a0506ca24854eba526445063a3008f4c122e94a98170f1349574edd9b615e347e17cd766a3c33ec8a92573f0b81cc26eca6ab7b1d9416ff067aec26909981d9009e99b4b105e126c15be009e0fd0d1f3404a0a80111004f1e9c3a34659001030c46a6a9a1d41207cf9e01ee7021cab36bf001778032c5bcbec2daecabf24b00efbba7bab642c0a0270d9b8a51460f1b34f47e434648c20b189e36486570fe070d1a00c043870c30304e49718892327ac01d36f8565793363bcbd5f51f02e09eeada0a01839e346c2a461935147bbf212324e1050c4f1ba47a01d00000450fc40e4f193861d064cc3085418a89c3306a09cab36fc01d2e6cf0ec5246dc4183871c5a5cdaec40595a01d00000eea9aead1030e849c3a6625491d8fb0d1921092f6078dae0a200f040cc0003e394148728a80d4fc528dca1684c624969b3b7575800f0d0c13dd5b51502063d69d854243ef57e434648c20b189eb2ebeb9001064f46a7a926a90c1c304431410d372c69f0ec1870870b45cffe54c41d88519f1d53593d69b3ffaaac3a6480817baa6b2b040c7ad2b0e9c9a9f71b3242125ec09062607492e21005b541439198f4e4d9578091696df62011301203e3947baa6b2b040c7ad2d0a9a9f71b3242125e109a92e2d01495010ac306260dc3e212d1b30b71870b4f9efdc21077701a6a1179a8279040c4941407f754d7560818f4a4a60cbddf90119230c7212ac3060d45e2139227650fc103c52e6df6210d78804314947baa6b2b040c7a060cbddf9011527689a03668c05064220e9f969c3cbb11ee70a1c9b323e10e4a193e9800f14a9b1d08c204a03668704f756d8580410c4cbddf905176550d45a627a7a62160307b071e6469b31719c0030d45a27baa6b2b04641af67e4346c427a761d35206cffe131a02e20e4cc1cfee6183296df6a30f1b109f9cdc535d5ba1e152efa7736a5ac2d06b858ac6246df66094d1a929837baa6bbb74a1975d0a1930787675873b5c187af65e8b3b2c8510faec494d606d76a46153060c4ceea9ae1776ec9add9d9bb8039367cfb8c3d0d51577b800f4256df60a41671a2eb9a79abe2f79f61477b8a024e4a4cdeeb6424bae8989d92fb81913b32bb9272666a1d0b39fb8839267612e4269b367a1aae49a9868a2295a8dfcf97a165ca1ebba5bc1457229b841f7c835727791bbcd312277c89da05dc8dde6db6b0f727fda7d3a99e3b5f7b4b3eec65baf7dd5ced31a58ec2084152433deca53e7dbeeb611cc44cb09b7c9e6960a3746566d8c750a37eec2cac74d633c338929461bd447ad3e85e7e968c6214c31ecf5e5a12822ddeaf2f0a7a7450fafb556d4d081218c388995e6284c9587b8d894e4c5156064d39f9e0e9b42e8b724608861086c491fe8341d54cd827f7a9a5bf50c117ced28f0755643008bc610422b78114942adf67cba88758bf07acdc3f5b5d0791a1979694d4cdc4329cc9256332908cc435cf490a5b6f1b5632f4609915d688069f33a1b0285069c8a2c2f6eca0841abe395eb52802b55945084845511a3c345cb49091f584aea14c999996e41e03861134be304c6082c3a2948dc11c34126a74e901c972fcaaeb01d53ec64a1657169513b03c4ca15313c725b8c4ab4d83242040d8c4f1bae132e5ca83111454f1dac5c590c3733625e5a902a7ef876e489ab31040d9f248c1c199a5686cdc91732435a14608f1f3b2bc8901c695167454e0b87195e71ca00b91a4b0209230b53478d8f11b43a5459be989c2dc9a2a3849517126ccc117b6202caf20f960f9298a86a41830e95da0677bc689cd07203881bb8b1203194907d9dd9b242c7d4a26cbba186c5151f66ea5c19d1040905c68e2c269c6819d1a46a0912155b5472badaac4c515bc1c72a8d8bb72156865cf9b3b5a5146353252bc04d0c6bcbcd881a31261f1c27aed658e1d155652996dbb2068e103960f60819c164cc59982f6caec66890a1f25aeb2cece1f5c6ac871cac2a291d59632f54a49153c2abcf5ebf3aecb5d6c53ca59a6a3be1cd30213f7e0385d2903c39c295c48894183c58250f56098f558cc7fac6637dc283cde2a182871a1ecbcffa44d39caadbf156b607e43380071e08415595a5f59587bc7ca6ea6eedb5269045638f382d0ff35b1e62b8b13c85dfde5e7fe5298c9699b2a3897d76dd95a7f097569ec25958790a5359e5290f648dcc5306c84cd9bf94595c98ea8a4c2b4b6d65ae2ca5cc62594a97a5789e3da765b6fc96bd54dff96bc87706dbf9ce6344be33d9e77c06f49dd13ee765916f53ec739b91efecf63bf29dfb7cf6252fedd967cf39c4f05512e7ace1d6977667e4db67cf56596a8b483508e62aac9cd36ebc86e6e6816dad0d280a9f76e3b1af3323d998590f6cd96ecc95a58ceae2f26299ec6937a6faec3847115fe124c65a9dacb5db7c6b5a81081178c3af76494cbb430c5f4895bfc109640d829e1d8410b25490670feed86310abd59e3bd61472e4167a1ce9d8a3cf4fbb0a31720b3d8c54f608b492d00ab53d574851518e3db24cda608e05ba42806ea1079037a34d4ad99d2b84c82df42042d9a38e2dd1360d850c0dad5f6d5451c88409ac93d683d11522e4c6d7ca4c396729db0d7ce72c0b3d5abd7cd3c20b74237ed937863f40cfc2fe84f961fba5ab3609187460d0f310b3958193e345989cab38644c6039a184a3e2a14647074ac7c786292b94e461f1c5cd8a341e392a30b13228b0ccf071d312e4762600606af4c829f103cb8f2c6e7c54a890b9519239d9e18604727be1418a04808091518e596d112bc01c1d5d5e36ecf4f19ae3a43767cc18c5429c2b2eecfcf0b224cc065a8444012386c714397d74734e708002743933b64ce993474cd90a33ce11232d94c0d8fab1bc726ad1f065ec2097cba93937ad3e97b32247c6477d0923314c5d8a01047a95b367df0cd3b2ba4286f787a793399904539319d8c2b901972d5a8bc7dc345376a05bce7552277512ec05606e6e6e6e38eb054b33333324b2ebec6acde588ac3387dd50ece6a66be3a6f65bddd2b82b89e5810e861809e7b644332b1a8140cdb6443353e3e13e8f273c77a7db1b9e69aa5abbaf1d83407b2e87e2ac9c737aa6a9a340a0aaa2e6f2cd344d4dd3d4a88f48711a8767e2676bb567f6d9daecf8c46606e7b3b56606fad95a3383fc6cad990106619a692e97cb9d39d46c7b5b84cfeea75d2a6bf19b53c498e4e1faa2dd182c4c88159d36480f863c2dd09bacb69776a3fdc2a2dd1e1664ecb79a21471b1684ecb7faabc9e735f14b2371dbc7492412d9e3c518638c3146c34b074b5d2d30de6c7529b3d02a9c6c3116f26473830b418fa966681ff2392c1a1dafeb893cd7e292166bacb13e7750d43ee56130531d6bcdfcd13498442249a8a58eb3305632758cc4c8104c93cd7413c27d6a9a996340635b1caabbfd6082b929ccb1c714ab7d969ed254398b994cd981cab64b536d01b1da645e53d95ab10f7a2a6b994c39539978cd64ca0e446bcd80c6b6b887409d70035b55b8dbcf685aebb3a74966644903a695a53497ca1ed18caa5a5a60bc51fbada9307ba6ae838ddaefbca6638125cced771653a19071f33b6be5d8236a8524a2222ccdef4c759ecbd4ce89b536bff1da67cfda51d600233e4575175018cf4cc2261846eabc6ae3a42e04d5b37fc8797687537d9b6c40b4d66c9b6343e3a3a703816d0b06b6cb7ac1128934978f8ec8423f9ceedafd35063be3a89eaa69eaa9eb5c751172aecb7ebe92c8d77342144d3361d44c336134a7407866d246978fe320df0483c3652edf046bcb7a9d3433d060a6135e407eb5327769b7aac419bee439d3857cb117f5d40829d85780d45319dcc8b205867cf18817e317f7ac78c99244be5d7aca24036286ccca8cac826212844fd78b3d636533cc86f35263c76ed8f15617567a404f69a566c2ae7ceca1058db1173b08217cd0531aa999b08f3d76a39ec24a33532735524f99afd9a2ca53a88559882e5df0cca4ad9147baecd81d70ba168e201c61849e67e1799e193d8b9ae9ddc09e500b4510a1c89107da1760be1a887507a47034e0e8d0318644be2300093f68a6b4272481e9cf060bb3f0147a7b3a906f172277d97711157a00260c41339dc2118a7c80baf611902f1c01156236d8738a855988939a293d5da7cecb4059d9518f40765478a473d4cd3702d88184473ae191024e47dd01a7a3c2118a7c805dfb08d94f77801ee1fd9095780c2b7b6ebe63471fb08e3a2a14a1c98b88da9315235e56847e07b0ac10a369a6d4311b46c3277498d042b2e349ab35d1d8c484e1b942919a294de1f230f50cb45afc1bc31d29e0f4ec0ed01e50b1ee0839a99952df79f9c882d3c68544be996b2b2b79f4818797750f2feb3b677deabc9e9bbb3453ea0fa735dfb14f3d8369a6d477eeca600e30ef5c818302f9a2ac5b91304e9e2491c897c7ba457667bbaabb00405cdefca07339dfe9783b9d4e98d3d0f29075cdd76473c27cb601acf5d4773efbd4b39b9e1281153715627dcada58bca908a658b1b31ce25582c49b6a305ac47c41165153f6a69ed9f4d4882bb3336331962471e2e5d5c8158d977a707afe23f5338b56e7aa9ba6699aa689d903e49eedb1e918638c716641e43ea7b11efbd639e7ac5910e9e7fc88b2c0a66badf5c982403f07f7d8cff3445910e7e7d823ca023b8aa62c08fdb96da2a5391644fe9cf3d435c71e5116edea3c5779bad57577c20d823f95a9ab9f9e4a10d8abde83eca80bc9cecb790842359dcc73d56c4ee75217820a4da190537814c7df99c59845323197cbfbb498799f3b7bf4a96f6ef039a144e9448bccbbdb575531afaa612bc1caca0e9f207d5e550b9857bd027b548dbe75b3c8b78ec41e5b35789405caa35a5864f1f1a853608fa8bf0dc173a7cb94386752d2304ff4c28a903d60618c6079d329596c7cea41f698fa117bcc2dc11ee8078a077223f60894e5e47beaf7c07ccf7dd8ebf950f1bda05e3eebf57c02bdb59e8f3bdff3f5f2979ee3a49e52df9e63ac2c657e0fed159148a4899eab7aca449b8125eec41f206a60de5e1b384a966b548c69827c77fbbd3278762cb9b30409901e1e2641aeac9589238145cfcd9ef7cc1c8fa2a8b73eaa3cea45ec11056acdf4da5b38afc37a70bdce0a7bed40f6a83d4dd3d45327628fa985cf65d5f89c0fb1c7dc04f6d833c79ee5a1e55917628fecdaafab975fddf594faaeab3765a9d4572f6a230faa5f3dac0e4296d2befa04cc35b65f9d043c9efcbabab971d5af8e93abe3ab15e7c059bf36e972560687ebaa04dbbb1a695164cd09581a20ef6ec15637b709f72b46fe1ac603c6abaa07b14775c40c307ed888093344cfdabbdb47d12e8fc68051e64d0a9a2661d05ef4ca9a47fdc71ed1a2cfc1f99cfbd8634ed5a3c3ca8d071821a7d96b192bd494e420014b9adbab9d4cca6b6fa5923edd01f6a903b1c7b4c71ed72df6782e5db68f7b7a07dbf841ef5c9d50d743e621d60959b83cc42e15d7de31de31c6fd36b2704616cec8c2198d4afa18b9fc366ef96d2cfb6d341a8dcb0c2471d165cc971f5761de6d6c3206cdd5112a5c92bcdbb82487d80e2d232f4860bddb78012f67cca4ade1b2e5c6bb8d137041470b8e14b73950de6d2c22c3890a275a64a0bddb6866cacc14cef27b6a39c536b59c82fbec53c8a9a9a9a9a9a9a92a5255a4b0fc9662c7a4d83129764c4a6a899514155697949494d4528749d411f799039bc581cde2c06671e0c08103599e62bd582f0e1c5e7020fbcd018b8396df1cc64694b0268d913125d09480f66e0e40f0c8b142430d161d5adecd21f9e78819202a98a8197b3707a5dad9128ec98f353defe6c0b688625b44adf91dc526a3dc3e47c17df6286454cef23b2a63f91d95c77e474545452db9fa1dc516d596b4b241c7445b111639de1d55658466c72d88893b3aaabc3b2a096587891f59ac7419797754d61bb82e7660c0c9e2f2eea82d1c65695bb29c68b2e6dd51ca3d6e2eb660787d817b77d41aa835506bbfa1d8a0d8a0d8a0a0a0a0faf4813afbec504b74405df90d65e5375495df50634df894f0e1b5b5e6c9bba17a79a8f09825c58455bd1b6a859e3b39943ad0e859f36ea82c5646520b8796650b2aef86da22010f9c1336555890bc1b4a7925c7878f3d2d5ca8bd1baa6c43d90635bf37986d30db60b601eeb36f18b241ecf7062abf3784fddeb061c3862587222adac0b42cfd1c79f786aa2d6dacd4d49accc91af2ee0d490b98b8b3a5c6c8599e1fefde9055420c956411146a3cdebd616b6dc5d50e1b5e7e2cdfbd61ce1d175556dec0d1f1ee0d2b1093b80289fbac61ddd2b06e6958b73468d0a0812c4fad5feb97060d256898f25b03d86f0d527e6b18ebd204cd94235762c478b7861e5baa9c8179e9ea7ab786e408f22c51c459ab62f6e3dd1ab23a50c34d5a1b347edadadead616bc50db12329809c71f26e0dca3765255879c930637bb7063f453fc5b1dfc515595c91c515592c168b64796abd5aaf8ac527c528bf8b507e17bf7e17c730d861832d39c3099f16ef2e569d58a621221686c657dabb8bc9f6091f367ee4b03823e6ddc5acfca38e161a5866d0e4797771ab4617136e41d04ca4787771c411382e4b5270ad2821cbbb8b604c2298b8cf4434221a118d482412dbda88679f9db8840af1c96fa293dfc426bf89639fc17cc9621624cc1334ef2656ed21e1860a2a1540acde4d4c52f95263e72ccc16ac77132ff0756487d51c2079dcbc9b3800146f66bcbec43da1f36ee29b316fccd87a542961c7bb89459e8a3c59f9fdc4eb7ae2753df1ba9e9e9e9ec8f2144f8c27f6f4b464ec49f9fb89c9efa725bf9fc6dcde28c981c3c81be5bb9faa3858d3838e551bdc9bd7bb9f922576f850210547189f773fbd6c4c7839e1a1054894773f1dc08513a4da9d196adedefdb4858d46f28d9d253262bcfb8967c324f26cc47d06fbedc4cbe1d4f6bc1c4e6f9fdda9cbd06f272aac2e2727a7a5139b1f93e827ee73130fd9c44336f1904d4d6d9a94fc6e6af2faddd4d4d44445d56645cf9f363ed8bcbba9ca03578b1779500069d3f6eea62490b41245b2909133e6dd4d4a3b4a963d5a78f940f2eea63526714ddce70c3bb60c3bb60c3bb60c1996bc6548f23b4309bf3320f99d2143860c567c6cb418c246824bf2dd19d8d8f073450c450d14efce00e58a4e9a145f5e5829f2ee0c59187c2d6133068a98225befceb09580b715253801334386cbbb332885cc6833c3f2058c8a7767002bc350860100bf31986130c3608601eeb36360420203060c183060c0a0c4242a89fbcca46452322999e03e3b13932453d76fa623bf998cfc666262625a5ac1b9d3c4879d3f6aef66328183c5902f5a65cc20dfcd5425cccd1909aa2246e8bc9b8958b6359694422287897733996e7c669c25bdf0f06af16ea6186e5f5fe2caa290e07a3713d84ec770a76398c1efe12e6bb8cb1aeeb286c3ab21d570381c0e97bb1326717722eef3d2ae6a6957b5b4ab5a5aaab154e4f7d2d2d2d2d2d2920232c2e2e0a9837687eddd4b5560b26eb40842840cc5bb9792714c629cb8cf17dc2eb85d70bb00f7d92f30f17181ebf70522bf2f90f0fbc2d867bf6076e142da85b55f2691209532c628bfde7da1ca4c0d1424eeb0513363e7dd17925bb4e2d468ab8387cfbb2f643db92ae3032b2bc9d6bb2f6c6da06dc50e356282fc81f3ee0bba38ca3a7161ec8cdd78f705304a6094a67e2ba129a129a129c17d7625264294a8b0ba9494949496504c2294b8cf49604960496049495e9f9386fc4e4adafa9d949494b4a461c2cbc48e323375de9d54b5470a1b3e38565ccd797752f2f3579d396a5daa78517977d286137ad66c59058181e2dd49402c00b1c0e1b7852d0b5b16b62cc07d760b4cc02c08f96d21c86f0b407e5bb060c1c252049858193a606af448bedb4255da18db962d30c844b1f16e0bc927634adc108223c78a3cefb69005656d0c4d97d80eb2775bd8fa123882d0f852e3c5bb2d2811804589999610e51c23efb600a66b21d4b51046fd16ea92425d52a84b0ae13ebbb04ca8f55b88d525140a85cb11379ea4a9f19ab2a68b89770be1f82023b2162606981cef1626ef98c43b719f5d3817ce8573e13ebbcb04cdfdf1dbc572b38fdfaeebbacbb2b2a49698382a59deed5691019b9104a245898977bb491467fa2ca9111763cfbb5d655b92ac2536a0cc38f16e174ccdd57cc36feff3ea5a6e7b75cddf3ebb3359e354585dee6859ca7c4ffbece6f6a5b3a94a2aa84a2aec0aaab282aaaca02a2b54a850812c4fa966aa59850a4ceef07e57e8f1bb42d6ef0a3c7e5760c01a267046ba945e24bcbb8201a49c81b1870c0d3a6cde5d818b9693b63437616aefae904565060d9a8e1f634ade5d814415ade5913d26d2b4bcbbc20f2b355c34d0fc20a27c7705b50592da026923a949a424929a4442424222cb53aad6463afbec4848a0df483b7e23e9f88d94e337d298903a29e4d4a87273e2dd4855cb352a4e9c6023c7eddd48c93b61ca12fa20a77c7937d288ae3137d494fd21f3d5f56ea4173fc82cd9b1670c9b9b77238da0ec08133e6152ca28df8db4c624ae89fb4c818d021b05360a142850e893eb43e1ecb3534052c0f19bc28ddf146cfca6306707d8d90aaa1443b4de4da12a831a2c303360a0b921f26e0a2d701343e6cb189b3c5dde4d418d989a373b5c288152f66e0a28dabca1b193a74b0e907753d8000b09144468ac0951f36e0a504c2294b8cfc11c58300716cc810583c1e0d95930a8c2e87710eb77b0c6ef208ddfc1b6b2146368725e14117b77b0ea8e8a651cd59430b57707934f040a181c3a30eef87977300b0f17b41650ca66c8787770cbacc2a64a18395fec907877500964b365c9e3c90d3159de1dcccd3089b919719f8f72584739aca31cd6d1d1d111599eca71e5b88e8e943fd8df4779c6efa37cf5fb28cbf87de4228a9d256653e67821f1eea3ad3870662eb8cc4c59f2eea364708c891c36c4800511f3eea3ac0d48d4dec468f225c7eadd475b31d6d67e5475a18d79f791f22a0755123f6124c8de7d14c724c689fb6ce466e466e4666464b4f6d98dc8f2548e2a476564d46514e3b7118cdf46c9df466344ceb0d4d24c904979b75195084e2489f3670e9a182cef364a6270828d4d58094b5cb8bddb284beb0d0aae3c7fa85ebcdbe845580f1e59d4d4497bb791f2023ec698114266c713b2771b9599c4b2b8cf45a9599119dc17a515ed22b6b6cf5e94965654a42c7af1bbc8c5efa216bf8bc66898a5415251c951e2f6eea22a0274894341024e9d296ade5d941ca1420c16166971b06e58bdbb28eb4fda893a5ec4bc64bdbba8c893b82376e6bce500f2ee22351751e2ca6c4499e2f5eea21488494c81c47d066e01b7805b402010f8f5050482a1bf8156bf812c7e0357fc068ea500881c18666d905adcde0dacfabca38433234fc8aec87937d00cdbf1828d96b7256bf6bc1b78c5c55595236b26a4c47837300e181e267aead0a991e6dd40250b2a3d5ec9333ba0bc1be8c724fa89fb4c9422895224518a2422222222cb53e9557a4544a4ac42a4e23751d56fa214bf89d2deeae820fb91d6f56ea2aa2a1245ce3c9901e4c8bb89929fd3b8e08c51e58081e7dd44599fa9d6ecb03376660a8c77136d7d7e83a6051925494ebc9b48f919c98c25666d64baf8bc9bc88d497413f779086d1b6afbb6a1b7cf3e543684e2f7d089df4354bf87c63efb90d9d050dad01a9d2524d27461f3c647cfbb87f6121d2548f8919c23e3dd433f2a2cb4f21c712b8184770f1129e2a2ac4e1a9a9f2aef1e5a81111b7d84e8d9b365f5ee213e6ec6789918f34325ebdd436028940928940945bf27a0601350b00928d88409132690e529140d459b30417936219bf83d2197f83d2193f83de16843c5933c6247aacade3d21051a3f4e58b85993e5cabb27246fa4d811a60a991695774f48337685c5978c2b3d72bc7b029aac2142e82c8b5c9d79f70405a81053640a9bb0af32ef9e30c324ce88fb2c846209a15842289690909010599e42b9b6d0d967174296f9ec4219f95b28eeb7d09fdf4264525ec0b833a6c34f9b770b554d51f9402ba3452dabccbb859247154ed2b4e29ce13af16e21245a676f5cb0b8a0b9f16ea1addd1d3841eac8b9520295770b29bdec8003054a1c1d2cef1602434f04f9f913719f83d0aaa01d04f7d983907e7e07f5f91dc4e777505050d052a9f63b882da8adcbb28a932b3f926d38de1d84c1922b78e2b2dc19e3e4dd4120e8326749458d09d7977707b939f2e6cd969c2b5669de1df40124708e587ded90f2f6ee202c33e47cede0f1860bdabb83cc984433719f7fe7f2f73b97bfdfef4796a7ceb7f3edf753d2f9edf9fdd3f3fb07f7fb7767ed2b081f2f5d5633defd83000940622cd798ddc8f1eedf08ac272cceb8bde1a952f3ee5f1293225f56c07003a27cf70f03a41de59fad2fb418effe69e15ab2a6c6441c1a5ceffe2931894ae23efb943ea54fe9f3f97c64643e9f728f2fcf6f1f9edfbe3bbf7d639fbdaaacd1da4182489e77fb4e40a70d123a7a528cf178b72f4bd89cb73e507c3c897bb72f8b429a3831b6a0d07245e4ddbead26557ece44994135e6dd3e73cada1b1a6edcd4f048f36edfd9c2249e2de23eb767b26dcf64dbb62d599e3ab54eadb65dd1befd6eedfc6eebfc6ec7248085042467705c7e66bcbbad223249d145d985230bd5bbdb640867c4c879f3c20a9533ef6ed98021b388294de0f9f1ee76ab03111c493a5fc2aa687977bbe683471adb9eb8175adedd6a200db4813450dbf7017afbec40318080808080808096406c6526b12cee73cfac67d633ebf57a3d36b6ded967ef69f5e8fceeb9fdeecdf9dd1bfb1431b8c8f4b9ba62f5eede8b124aa873c5cacd32f9ee9e0d2c7e9428911104489b77f7b2726481b186e32d0856d8bb7bc82a1438e2c250f9f3e2dd3de51e374d76a801c391f6ee1e10930824ee33bbc56eb15b2c9bc6ae7d76962c4fe9afcc845d7fb1679f9de562e5fc66e3fc66db7eb3639f7dd850d2d002e3aaebdd6cd5672e2c2bbce8c9c1f6e3dd6cb20538d8c68018f10292e2dd6cd6671663b2a898b4483e79379b876a4814384f867089f16e9645d7971a619adcf0827c37ab5b9844dd22eed7bdb2fdbac27df6b5499215ceeff5cdefd5cdef755d572956c4906c0943333bf1eeb52a4a143145be909044acc4bbd7e4e7ae3d389225ecc191e4dd6bd602667cc5b982a4aa079e77af5bca3c5957e62851d2c4eaddabf28d15b3b12835c40079f7aac724ea89fbec9bd7a74f66c2be796ddf87f7f6d9794e7854583c1e8fb7e4b199318966e23eef96bbe56eb9db89eda8d87eef76bbdd6e99254689064d4e172037debdcb425ad1852d2d8db20bdcbb775b4a4ca292b8d76d1d9b5297953a9d4e474676f6d975481d559bdf3a2c36bf756b7eebc61eecca8a85962b2ee0bc5b579585c8d7da95126469dead4b7e56c1a50d45dc902c2fdead03c18d961f3564e840e2e7dd3a07f020434369620f9a12deadf32165c68a226162dcd0f16e1d58d66112b38eb8cf2adc6757c976bf552aacaecfaababc93f37327ce7fe7fac0e5e0727070b9b4b5cf9e23cb53d92a3361dfb9b3b2b42cf563969aa566709f3d5d4bcfcc8024dfe04923e184c8bb735b9fddd69475b4c84811f2ee5cd6daef5c57eef73b970bf3699f74edd334287150503f60e846e1fe09d90e3f35486cc5887b775aa5b45c3384c4993044c4de9da2658921c18a4c942f26de9dda3112864c8f903327c2bc3bdd42011991396b685a56dade9d2a392831a3081824da9a14ef4ec1d4fc4ec7d2fc4ed53e24deaddba07ceb370df7a79fc8377f9e547f9e55565739fef4334bc89fa7d69fe7d6c9f5a7d79f5f7ffa09d6d6e6cf53eccf73ec24fbf33c43fbd34daf4742724c027b446253f2c1603018247a30e8c6ad0fae411c4107214b69f0a04f208814743318f4a08d3b5780488141624a7087561a9b3a24d4e013448283e406a9cdef28b62d8fe42792ab6e863cd29bab477ae3e79190e04c7924c743d82392f17c0d1aa6b065d96afb2db4fc1d7ca29f44221b374f6c03e6896c823cd1b110f6480cc21e8339c0fccf7fbf9f2735652929ff79511b61fc6fe887f5fbf9047e1d7ebfdfef47bca2f0fbfd7e3f0dfefb59f8fd7ebfdfeff7fbe968f316dc826320ecd182060d1a346870acc51e3550d021e42938fec11e29a8442291e8c3e93bac7d07c73dd8630724242424242424242424c759ec11492a87899772cc833d4a05836a7e5489d5592ea7a40583fac6078316f852a3b3c44a8c1d3f6f30ee838e77b0c7a00ef6f8c3a1e37d3e6de37deef3249f476529f37d6e14f23e9f0fcbe72064a9a0fb7c02bed3e7731244f1215de5d07a9fe32d31ef0bf2f91c8f6529a2fb1c93f9cede9786d462461a1f6ae0b0a8c1c5c99012249655d0d4787d5f57f88c91b9d146c2279547cd4419931c5dbcf87c3e9f9b3e2776792291e840a2e31cec918884e4aa124730180c0683c16030e8f8067b0cea1f8e1bfffb85e1a8f13fc736d8e30f8b3dfaa0b04e3c94e31aec114a7572a0933b7952767272d5c9e1de69acf83fc734d8e3cfe79bf23ec733d8a3ef8a3d3a199bdc04fb26c732d8639315af7727bcebbaeb49ae17dd2b45de756bb80e42964272d727e0fe5cd775d7c49c1324e08ed80013e39a91f18227c518d79531ee94d9ff7eaeb678fee738067bfc019190683c92ab4856b03c926318ec11290ca829eaf4b94ae27dde52799f037de8b3827cdfd7fb1c27d9a3cf189cf141578362ca0f3a7ec11e83ee823dba67c17ff2334b5198a1e59f1cc8e39f8cfc93e316ecf1c9fdeadddde7ee6faebe941183c682cccdeb674dac70a9696952e7dded8c1fef6e9a799f9f3ec756ecd1c7823d3a13993acfe478057b64dac244c6a3eeeea817b5f1cea3a89b2084e084ba4982268f9a6f5549ac271475bca5e3d11daa63a0a86333241475bc4483280c649672a97c28ea190b7542d1a0a3e80f454df8e8a166cb09103938de1047c0f859834384eb883966c259a1c2468c093c6d666f18182dd420b7d0507b37567ee8b1024cab4509384f5053ec4916da924a5814258aa2fefbfd7ebfdfeff7fbfd7ebfdfefe758057bfc0575f2b7f183242ce3a04902c5cb8d77b71fe483e64d588f2c7d9ed479f7f0836dde2469d322636122187cf1c1a0922d4be5972548dcdede60d071157b0c8251a1221646c517e68bf2f97c667adee7f3f9be50bdcfe7f30f675adee71da4789f7b80e57d8e53b047df97d589286464b8ba5e9cda05524a191638deb69ef4795d3785bceb18057b74d747427215c523597924c727d82352fbc4a5ea9f1c53b1c7270d5bc4dec9b109f6e8b4e5eadd7109f6e82a09f68866b3a2d1187ce36e7ae3efa8371a036034ba0a43cc1bbd05a678a37b0cb237faf0c51bbd28a3cc1bdd680cbbba7aa39b35dee818c91e8d4918df7adbe2dbb66d97ad1b5ba7164eeb206429a3b73e015fdb3a09dad6aa75bd757cd5b66d180c15dff65a6ca255dfd6b1599632bf0db6adabadb7ad970e2d566a98f028d31239d2a556030957d5bad9626552cd850ffa08f6180c4b6abdc075731bdf75dddcc3775dd7755dd747608faecfe7f3f97c3e9fcfe7f3f97c3e9f8bc01e7d409a0509da50014911c50c1e2f3f2d3ec080bda11225460594ac9db9e9dacae2c668d42bde68c4aa9325e52a47923e715ea3d15dc01e8d577eaffffbb9b983ffe3d2f43fffb9fae77f3f07baa8f23ff718ff0b7ba116d6e2c8ffbc054516b0c776c9cd37bfe42b608f4b450716dd87c522162f5f041655148bc5b65874b5380114784ca0c1c17903576c83c22b0f15b32865452c2dbe8825ebdbd655c01e5b742ccca39e02f688a2803d164d37f795d2f0782517c11e95ca64f934adfa344d5d4fd334e4a7bd34459aa6c1343563e6d35eaa51a4be2b639a2eb57cbaa63f4fd3629a3a5eaaf874283df11999a9d236dd3999010b295db880a541f9f3a64a4f82f001e2c64b9e3da90a301746c274ac9dbd306a7ca9922cd345459a162547d61b332ac4de6dc22d2551335206450f3457efc6c854c8981a39697cc01832e7dd98edd31e40be14f101c625f66eecf669dbfe0e367d1bb52cf36debaa1a8e6fd5c87cebdefab0f562eb27608f6daa4da0e80944dd51ff80ba3a6529e65137c1162af33ba8562c9e49f24507a279f14577346fbe6806cf17bd04ecb138443595e9e709d80917407ce8e19282c9132d5e574dc448f95d79439219533623cccdef67e27fee74fe77268c89305a5c606f30de9f1938ff7312b0c7dffa46a319ab37ba59c21b7d04ecd148a44bb0f94b1a8ef7b9ea4b73f13e17017bf495f1f2410f017b0caa6e1921ef7a08f6e882803da619cb5fb8e6cf0b7db347a19a14afaabac4abaafa73555513f62aab6a12aaea211455d54950e55527155725b3547b954305e3e7d5a0aa91aacf5515557117128964a16ae00758da18166a6c8479d52c7073b2d4b021117353a5a498f11137e5480e1f6a9f3a3666bcb519a35255d5e8a50459d698376486a84cd610794282102e3578de8d95afa2c124b1280f554c2aa87966c6e4513f5157d58079d4dbaf471dd8c5cfa3ee5ec01ef5e1dad5a35efc62e551379e553dea1fd6d01ef50e743cea1eac593eea1f608f28da8aa7f87c6644dee753c3e57dee01f6e8d3e0f71a80df42c19d14f6bb29ea530f6a983f9f7afbf5a903c5f0f9d41dedeb531f92f874ca182a9f92a1fad4c9a7de01f69802c5e130338282c61b197ca8acb87144c58934ed081d2e5068d9914c9695b5bf6fff7ceb4ee4db13a059a1d24406922f4bde764a181bdf3a07d8635b5473f545df007b2cfed258f99f6b803dfed40cb04715832aaec7c031c01e318803e3b5f6f35a1f19e720739fdf208410d453dcb84c3ebf31c6147ce31c386bab4e98a2df986bcfe3319d6a241279423b5edea981f520331592ef6c95b1f6e41caf9168cfca2cd5baf6fc55c17716cb6625f259667bedd92dc36529f3dd77eea3abe4bcd6567a2b4bf95cbbe6f2adbd5ebba95d83e9d1a1c5e07e6bb4d7ae97594ae8daf59a05dfba4dbbbd760d87e2b5ee93e7c43ab3f6a9756ebdf6b36b8f9c138ff6d32c4b29b9f6f3ec82ef334dfbc9c6270fd2efb3edce6f145995a596ac86bed12b14ebb5a35960a8d6db6f54f9da51b02cc5e4da51310cbe5132d4ecb5a3687e7ebc46d3ecfc46dd5e3b0a9722b394f9197ca754dad36492f4aa8e6e72eda95776f29d7e694fc7ac52b2d7e9f2b5a76c59eae7dad3b627dfe95b0ee9274745477b2e2bb795a5cc27face7175bdf69c126927f7e5f63b67f6da736859aae8da73691a7ce7d6726caf3de74602c8ebdcdb9cdf6ad56b57932ad606df6a8eacd7ae6e916023faad72c9f9ad82bd76752c4b41b976952ccab77aa6a2bd76756902c76b752d8e761d525795a5cce7e05b67957ced3a2c5d8eb6dfbaaed7ae53662929d7aefb9af2ad13d38dbd769d19d5db6bdd199cdf3ab6d7ae73cb5246d7ae7bc3c0b7aecfeb1db2cac584df3babd75bb4efb6765d19f8de79bdde295ffb0e0c8599d73bb137bf7768af7db7cc521d5cfb6ecd83ef5ddbceedb5efe0525c11fabdebe3e6372f89950500df3cadadd7ceebb2f19ae7f59aed374f3b0f4d03dfbcb4d75a8f0e145b42f8608933e5d54cb2a2f01823c184d5bb5b2a624ab47113848e1822ef36d9f0b0a3f6440a999b8d779ba6951b6060b8f8c95286e6dd269c9196da8d3148382a66bc1b1b60085b9a263c944ab377e3ae2b35155d6968e0e053e2dd784b959f382c207ae2eebc1bb3bdae1164481f27684594bc792ba3e5a54e6aacd75e3bb0b419437691f26e3df63a4d91316bc01c5111e6dddaec75559f3229967825f3bcfb44be0ef183aa4b8c136b5b62e833509a6c9992a4488b779f76e2fab859bb82c46dcebb4fb0d75b636eb4889b530289779f6e4954e0d972d6674e5c9c779f70232852c2d24e16b82247de8d6ea526be662873a0d5817b37daa5a48dcd2c4d151a1e6ade8d2e5f6310a38d0e9d332c9466de8db2bd365156c4080b257edabc3bc57a6d9665cbca1f2e593cde9d66bd4ecd5ea768af1370434d091f52ae3c81f3ee5cd56b1b3de2f498c5e9d1f3ee5c7284b2b7125925a81841f1ee1cd86b23ca3657702ce15202f9aaf598c1640dc98d3f64de9d837bed315abc91f9596ba37ab78a7c4d45cc941676dabc797bb7daf5fa4b169d1f79be94bd5b55565892c46a471435b6b477ab6cafe39ac41992a78e8cab33ef56dd5e57b9b2634e96e59e34efd665bdce52a5c41a38246dcedeaddb7a0d825615b03eca12c8b4bd5b87f6fa05132743ae9cb8f0f26eddf2b59b7b977ced3b2c13e4593333a35366cfbb77634f6061aaaa2455acc1f2ee9d590466e821010d173c70babc9b877cad33e1e6678d95951927efe655bdae9396640b7945c69977f394af8dd4a8a3a44a0e153eefe681a528f31af899a534780dfc02ec5103f504008a390f000f813d0240c5e03d68bd07b7007bf4a076e8d0a143188a1fdfc12bc01e3b38007eaf9f0195cfc0d52e9fc109179f8153803d66d056c000033f317015cc63e02d0661542c1e039f007bc420c8d8e6b7d1c9c6123025b4b84832e3dded1bcbc8a8a9c127489994770fdf28268f1c3034780881d2e5ddc5371ad9bcd11dce1b159034c1a2889d2c6e5d5ee39f37ba04d8a371cacf2c05f55353ca9ff208b0c7a9b67d2929074ab92a156682eba51c02ec516a7d0e7e72082b01e639c43d0707813d72689f3ecac947f903d863940a05050555e4a1dc01ec110ae937849148f11bbc01ec7183aa4183da6bd0a0210c19e335f807ec5103b0582c168bc562b1587406b0c72291482412e3823cd117c01e894f4ffef4f4f487ec9f5c01ecf109a8fbadf96dfc5f89226d6be43829f1eb61768686972d4ac83079f7f07f6a304ef4c0fab106e7ddc5fffdd6fef787caff866ed069c3c26dc7932aefef8f94ffb907ecf1e7e434f54e4edec679a725efe409608f4ec0a6a6a6263f48bec911c01e9bc83e431f319fc10fc01e33a84b8fc1c6637003b0470c5b4e097ce06459e531f3eef69998d43c93ab7a4a045a9890807351a4edc4cbd467c6337901d823930f877cbefc70cf0f9d00ec71d82e2d2d2d2df900d8e3d2853d3cfe8277c01e2fa84a4a4a4a7b68bc9283608f4aaee593f43cf924e7803d2661b120e62dc09dbd853c7ede821fd9a305a1502814fa06ec51e80abd1b7cd775d5f5d675a09bc7c6bb2e00f6e8bacfe7f3f97c3e9fcfe72f7bf4b97b78776ff16c79c713e3dd9f3d3ab0ca57b8a3e42bf807f65841cdb569da766c992306093b597ade3dfcd64d3d05a5e6070c1f6b5aa4a1b56d7bc7c8b76deb01608fed9ae5b7d02321b96ac7ed91bc7d2b7b243b341ec935608f48c025f58caa0e5f5555b5ce9c5755d501c01e55a92e177e277d4a86ce904fbd4de9a0f9b44e971b974fdd037b4c87bbacf636c17283c4923353bec0a8e4c811850b1bda5b5619143c546eac968fba57298195294d4e98585375e445dda23cea1dd8236aeef529380557cd3c852973924fc133608f145ab4dfeb07a37e0b7dd0cfa0ab6f1f9443f6c138383ee818b0c760f08fdaeefc911bd9e3d1147bd46c46635c6fe452ecd1482d72605151515191ab45ed988e2f82f3e886479d037b44b5d65a7b147bd4ce1e8b8c402b0ff40dec11a80a3d5146f3444ee44944445ed4533889442255bc44aeeaa90e596eea2c6ba85953f112695952d1d2244b169b77b74fe466094fe463ba54f8ddf4dbc3339530529143c59d15565efbd0c66b2fb250bef60f562a5ebb06f6a8c7720e839ef96da235800d9989342b4cb8d29ca199f32626264f1d36ef36c73420068f0c2b3442dcd478b76996663355781ef50f55708fe2205c32b8d8a0eae1f6a2556e8f7a913da24b227b24f2fdd0d0d0d0d0d0508d203f94e6513f517f628f288d32afdd893d6a6f628f431afc841b577e8267608f136c50bd90900385843c290b09b9d1869917f22024e4131012bab1e4858484841c5f09090909090909e5f900bccf31b0479f0e4551144551398fa2a833b1471400dfba99f5ad0fd9639b9a73f6f08bafb576bff13acc868cd7bea43279d52fb04755556321c30d182e1a7dc4741a148fad297cc27ce401f2a6585e9fba127b4c3d893d0aa57d909b5c1fe416d863d08e329f734f6a2aea9c1baf7cae9723937310f494fae63c8436977312707d2e2897c7e454cfe57cb99ce3ad2c654e29d3f1b91c5acef11826fbe5d03e77a4cc8a0c102625743979737ac020011245d9a5c49bf35c87ad3827bc547cd92acb8d808b131a62d6cc70e1c8958591c94203cf94322cefc65d9f9b42d6450915321849726e8efdcfcf9f0bd9a61da854289b89a37916635231c86600800273170030281c0e8a666194045914e30714001666a65a60561b08c469940331638c21861103000000000046461b0220aaed11c9557d1adeab008fc3230558a7a4264c52638115267ff94379473bd865f49c0f6976ac4eb694c7408077ac2ec38d8356af4b5499c5de89d6e9885e320458ea9010ca51b79f439e442d39881526d8a8a45b03a202b927ed25934db68358e7b6e585775113b7a76d40d5a1ebbd88111c226fcea58435b183911804b450ec404054ef77ac76b53a4ae27f442b933d95f57dec830328128d993ae997b1314926fab47417b3507d7941211642c8cdb21f87bdf9b825cbb8d7030d9551d956f9723c5e10c2969073f383f12ae64200d226271a138c435dc02d9c4cd0f58732071c13f1b3486e776291599d71d545cc021a7e2584d24ca3c60b814dd268a084801abc2fbac9136db0c66676a67038c37421bc72053566811a31a0c1afd21871b1fcdb985ffefdd7204ecd821f9b534fbda8d79d422e46e1a4c88e942ec7cc974878f7f2f98500269d50b023d0f471aadd00935249d27ed3260acef98362f68058fae17160241621c5baa04c6199f3218e33ba2b76ad5e5374a4b1766b36040201d0f2cc989f27b4579b07d7f70e08010ce04a7845bc11180c4a0eb1f131deed03c90fee1198d2f3179ab88163aa0502982330135c7204be17f3fc73e30394bb7562a7da9aa1fde770bc2b45e2e73bd056c0e080b804761b01940240bbffbe21788006ab2a9f74504dffbfe46c4239b2585f5760161c4ae71d9ec637c4bf36694a08ac7ee8e9129abe06c21514538f3b82bb4f3878992ae1e24e5c471db1cc2edf9c993bdce6488cac5b92918fef94ca7986855e8323e2e4c183d6f676a928df1b813d35f6729e9ae5a1fe1a36edde2bd6cbcebf5b0bd95469b83c09d92a5c8634baf15a8ebcbb1d2342c331249e70f8584f20ea459dd84125e83d21265e0e468d0f514e79e9e056d763947b1fa21c75609350a017785c7f5389ccecd3110bb637d8e7bb69576411233177644b859a97419e645d867ebf13d1cc4bfc54d16c81f635aa276a09572946de08af912fb76af9c940f086077caf0096e5e026acdb9c50fd9504c292d811bffe6fdb45ccbc96db4d584cce9c7b17331a792d297438c044636742a7723e7cfeb0591b473c6db8a630ac5ff95ccf364bcadb78528603d46e59d542a077e0dbf483cd69b1e8261502c845107e55e8d5c872a4b551ba8589698b805aba57c1bd5008bd13f78f43fd432f5cafb021859489057593183cd8d9e8bc3c306f0c35662312925cb0ceb44305072b0be7ac8db6b261154268a3338ac2662e2d95446770126e436777347571a710fe1987afd6d89deb436e7d04a186b23b2fd24637436431b491b515d90a816e33eb04d64be007243bc209ff012f1a0bb9830e4360fd1be2e311ba47463335a5730382adb522dc8df38ac4dde8962e6e1ab7859eb203fea233956dac77d8df2a09aa7ab184fff3554f611d69cdaa98012a1c26b13202bb627a11d8152ed204606e13a2d48f2e9d7b3eaa2da05b4b0a5ca0f5a5ee15f46a36c8fd5d4a2e6988511f160d50b31d0881766125f4e550594e2a0e4ad7a0e66b49db982bed80300058caff381c6086b7bce60135508037d567ca87b30154de23b55ec714cccaa4602af577fe3905800bddbb74208888fc0c0569114bc51977bc9b60c6c992457bad9c280a6d4ebc8ba14453d97c66b6629d0ba917cce8f045e5aa65f109502a91f1a4a9cf68d656ca9528c5b4448f7d17793c9d078c21fa21567175ef0f8cdcc8f1acf7cc2547488be12b70ff645d9f5321e4b32865a05169497150b73eba763a0df89c1e5aac82948bb741a360c97bd5a1034a39fc276fdc25c0807edcec56b32f4f81c8ec82c5a3812115de8d3387269abcf81be9cddf3fbd2475a43f3025625244fafd85d577ed18b6d2cb42249bd7965de60efe7ad4c77ec85a50079b6cb1cb7e5644154b28e6964ae5fc1f4c618cb332823bbe6bbccfab6f800fb50f867752b8324b72e819a8d4e675025c864faace2a0b0adc45b0c697df0b3ab6d28ce1faf1dc9805e476d2f9acd5431e4141fc5a5905a58c1649b35803b8984aa9070c697d638acd865a97610275ac57d1c4c5feb602fd085dd2e9cc57d1e32cff3c5c015ca958563c7fc3c53dda808bf0bdbc2ed47865e6d8463abc392c40b62cff8b738dc978f58161b028ce172b06bd24cd40901ac80ec4212fedd031fb1a80f010891a27cb30349ec1641ddf04067a62e06ab3a21d78cc364f1bc2a69c3a9f009968949c30fa6ac06714919fac86014f260bf1fdd28e3e6d09f981f9b3a61b3f12f9c34b905d9434237860a857d35ba985f6fb6bafff05b19899e34192aa3202b4cac692add4c488f14c1cf26cc9c58480f2a4dbf125e2537e8c98b05ee6b8a37881ba4d2d7976e10185211e7596c0ea87df736364fd38b8e89d7945859a91ecef9ead1981b50b359d743db1e51072104071302aab3aa6a6f9530b609dea9541cb1e53fe07bf42a7650e654eda5f11db55e891bb580e31e891e469f3a182633c06954a60aa8cefd85aff8156aee406e4137077f38509c96f608337e2a45d5084c589a839eb881f5b277ecca7ee9986c6085b357a23d07cff492702c8c9d1685cb8fb7d50b7d152199b548da2fd3475dbbce8d2140c73d41cd66816933a75f65664d21f6086f031de4fe5a70632c18fc6fcc5a7f9e09e600b072a6598c1d9ac22707323389a8d6b2143cd4cd00114de82904c18c9cea36bd52daee9a09a3858c16a545199f3e3bfc5392b87cd9ee1aa4587ec81d6dd240985068a0c6b9b5318ccd2a8316d3b08eb2dd0f85cb2b9f9510aa9b0cba8ba4566ff9a9e8950e9ee1d5b223f849979205dcb7c32dc469d8fa8737538574be9a648d1e60e41f7e77b5a02d28f160b97eec5c623d1d009cfdc82a91e8a8f66ef336d9c2fca84887a4b8d790d44b4343e52070f4279f82c48e84fea5ddc1618bf8d4971e189ac45d8cc6aa09a3b6410ed0fd54378ec14182b179fc4070048c9d3a6c022dc3d99bdceec148b83421353f40e909320b250fa00c567728d507700e0510b4daa1f325ce96f44bf77d1f332a721db8f51b358a5e51c7fd623ef3ce1ff89711dcf034902fe948c8eab202091f596c6444d77926e6b302cdddfd223097d9ea83502faf1a863de7c075efead3092db949d104ee633b5fab0021d05152e8161a0a5f40ffd47b66c8d70d2944a4e1867e3128c9d58f0a027750394a251c0d273a27d279bef8db0d9c9408967126a00774c0cb5bf905edc88f2998018108b688a52a722e37542717ec26a4a5f17fa3bba68f2a3e8b6be86bf8b58599f38b54e8e453a99c7b7b062d29f424b27893819c047294b196b3d4842c06cf6617b1498b48eeadefa58049f2d0430155bf22542f9aeeaa3e4ce8995d185c8b46b2a5693963c664ea98127d79aea5bf72e4e7779f1583442ef9bd3b901cac0e6965f8e0e35d02817503af74137581e4df7e157f6cfaff6f86c72712a9ddc8fc0e47d06e9bc85119ca963a37863e0882c32a7c7c5b29b54516f5c8447b490f8c2419ca13e6f329f742f853b802e0b7a2d3d3e6882be60a6fc161fcce9fde0fad4c581296805d69f7ada3e08a2f4fdf3fd855e5718dd08935d25b39e871ef5a7a097e1e6c6d2cbd1a2f9bf0dde18ac71a39c5494a6e86dece599fb5be2386205edf527ca7806811416fa19165aeeed14ff569f7a75613373ad34f9f45aa935d24ffddf39f86d92c608be41df6ee3b695db40b3579d88bd5c741eb4779bf7da2ea60ba450d8cc931a162ca5e9a87aa587254a5fc4b392252130c14407366209c65d0918879e086574abf0777dc04a0bdad365966a0011d87acc4280b3e57b9aef02f8f835f074018e9045882a7192139920764a58e5003a11fac16af287c1f0d6d0e38eba3885034fe1e676c57be1f71e8742c077630c098177cf66185c4226068a96e8b64bef8f07d7f81adf4d115f4deafb710060481e2a397ee0fb2a82649da69a820dd154b9fb182f0dcd4d9b811e53191317328bbfc50c0ea0162cd272743221be3009bfef7e76e588c6f58053d3443b55c375e391a280af63c3694044ae91fcc6fd20b5637361c6b5fc759cc2112d3ca118fdae049c8dfea75bd7103c705618d868ad1d04ad6607d1740b36580b2063d9820ed0651ddfc309714cc16b6c1cdfdae7f72063b767477863ce0a931fcfd236309f708fe7759034135c58fb7c0163f411dec380c506dd9652c181b65a0e42c5665c10cf8b4f3d50051714cccc46d637d514c3038ebfb4e543af61dff41c743b12e1144d1865664a8f89d5807e2c7bef4c64bf3aea8b6143f56e793ae6f11a0e1b472c3c278443f731402d65585d450953490a97878c1c02cc4e3afbea16b5d89ddcf0e63b5209a6086dc20624149317c3020f8f2f475c1d6b4e8d88fa681af6a8940acbf73d91d85b70d189e7abf1a5c7821c3407a2a5d4da5b4f9815a425eceda52d23510d081ee826fad14bd639e1b77a0f45658a8e640f4879a38bcce71c5fc664cb837d9c4c2ebdba479ef90ffbdd33a71de2ccb35387de76fa586f8b8a6379ed86dc116724274e026ac292d83e8e47e9773bd4b7e42619c900f5659c98922d041a7247c90732ba2724715abcbd80695964cab26b681ea91516df9fc823e227917f9b95c403ab15906a25284e1471c7adee8920fbfe06770c14f093733f3f8206d3983cc885724c084d514ff38453403120d18b9ef25a9207f29bc9727451f9bec1a838949eb2058f722f643416833515200b96a2bf7b87f57cec5e2c1ef4e10783a90e5886c0b9a2ab4404621d1e0c5e424ca488d705304a5d11692ff10f39936a71aba390aa7474e1e7f47c67744eb4356f65696a96cfa14a97e45e846309e9f0450c7004d5e6b2bb9b3c59dd3bbf5df08288818c0f9c55dd05063702e71e03c9d60964ae1820558d020830a664a1aa99271ec6d46e70dfa9781399d14aaa1c6d772752f65723e216a2e605e523f9473f3a92f304daa1e2f10e78e91457f167bdeb2be15c386f9dd3930bbc4e923850ce3d49c577a0e7b8f5c0831416607d93ed1c0d3c4a417e010de9e08823bead6943e2b4bb311c983cd737857044665f2dbf38389aff0f713665244b43c82be774bbc450a5da28f82601772378f0ca922d4480112e61d998dece437efcb38d9328610183c688410abea5e4dccc574db467c6741f62fcd8d37d0da2d6482c80c94a029d2822c5232535906511182b566e46a7874c8a55443f46e09dc36e1d59cd3cc35f61278907f430eb3fb2097a31306c637a5805495abc4f4c71245a368b3def961e1c70304a88530635e83bf276ae2024b5b3268b52c826a5710fe16f087ac69b3d44cccc8ab98595943156a7a86c8188ca5e5716c47b8d42c2868aa0103e2e10755b06450ec7e56e79ea9e11dc25c078df8a8b071f92faa292590012329d13794daf84249a2b553a462ea27ed1311122f8cd977d19943e92d9956c64ace13e67891b830042542a50e981f9ba6d3ad6862d514609bf21e3e5445f343c688b40b15e001d1588a7b3a92dc8348d196b5b7d79f561123bd130279dc9ab4fcb3493e597ad7528919e915a59a840eb600cf0818eb7d36ae55f9066f015f027e20799f081573ba2a8532740d11a7c6ad0bddb0dfc22b90db046a7173ba223a01cff8ee832f828e938eeb6629be25684ce8ef0fe82c0535bba2380c3c1f3736d41cc56b14df69cb04cd97737d17eb813d2f32dd5bdd57942aa90436e54a9191c25d8d27bbabcd6d2ec873ffbf67bf16b79d3e3f57d026fc1e0fb2850a14355585cfe67899b3991466bb68c1fc723c10b205f50323dfa86132e5955cbb183b54c582d2117d98551a05c3bf91feedf69411f9bad53c348c51b5fc0c7896b4d76701a0356b910b3958f6867896dec68a5483de32abeaf4630b43c6b05f49caed7bb4b308fb72399905aa478ce1a1338dc1e6e6fb80d12f1386d9904f4c39ad36686006aa25a01617edcb598794ac15dbaea2f12eb809560c1ba4bb382db68ae2bea91cc28283771c6c81ed4f86a42d9c7549466f571de7f2c6faea2b702608ad8308175ac2ed4de24e3893e612c8566cdb4fb99423206dfd303f83212df12df15004d1a3ac04be53fc92c96f592942754092ea2e9576a0c349c1c1481e341b16ad2bc4525ef4029dcf9b444a3e9283ba4ea0ca4196e3b396afd4391ecf221286c56fa6da07f6411c48dcd6ca9b37d9d2c5c59ec6502de29cc5a661806f4cfe0dfb4197b21b7ddada80de407ad6106181c61734136e75de6bc718eadd425685eb22d77234e1f5914ee5acac9d607f0325726818d2289e2c87bc1a368993af2c0b3909349c8cb96d5ba613cfaf9adab44551a4671a0d94ca426f539525e3314e58cc570bf7fbd3ae20c29a10fb1fa1ab3e52a5a2e475c1e3ac558ddca62ca40689f1941ee64a02478a17387ec47e8ea1c249a84f4f727fbc9534a7903e213e25cc489f138c49d6edff297116dc5d5927c9bf3e7f3b1fee180b5062762ee25fac08b3cb2a88e7e0ec36de7674e757f4f33eef5d38a6490ef61c354e86a143566cb41b09ccaebba22304690d50cf0d2b2dc943d750ac86e631209e005943c16907ca4ced6a6ff3c5e481e8a2e97b3177c48160b89989615f325abf4f1387b0de0cbbc0225d76eaa1d70d2b2a8a175592300fb0856101eed9673f7c0cc5d771613970ef203620f4b82577292419673e897ea31cbab1c721825bef367fe80490208c6c0931359f10ce9b6daa3e68ead12946262396bda0414c453e71621438d2c120b92427038f60aa74a53fa2e0bba59efabad83345ac8516cae55c2aeceb4c2aea7afadc86356eb5dda22c1e7970d6097bbe3feba034d934e985eb9a48b2109fab92b314d978e4c68f6d08796479f40821152dcf9bb7c7f5ad0db8c81dfbed7509eb04136c3c62914696a12e311c592572564f856e7629445a3cd773c91f857855d52688a3045ec7e53c2d0c4ecf4a4c4f8ccb9024017860e3e8e15f8934b4a93fa8c4a96bf081ca684d285f254ff4fb3bb337162d26aa5fbd7372fa01a0839a3d052332f15e7412c56651d9e91b897c124af94bb8803319d71e3bbcd711051de0ba36249ce892b2ac30caf7248076e5c67f18cc976942149fa8f7151196229be224b2a8ff32988271855dcb8a892c89b694428980de8a6c95609268ec4c03d6a10bd46943fc940433bdd67886219d1578aa9587e7c1d3710558cf252d8900b923dca7fc215c1ba006c3a98eddc5ba46240b374a1b6cf8a7129cddda826707388fea06e0642652f399325f49f0f4613e4c7e032c28e55e7c339386ad82c2abaaa007c2b19ba7b8516db3011e5340fca5666c7add659d2910af25349c51dbd6d322f1faa3be927b1057df640527af940c4362fd46d6d2452eaea02dce8e89ee925657234565acdbc236e032be56012b422204930f184d1f3c78251bed095b66014b1d40c56efe8816c78a3dc185ce5ddae8490c1093c9c4e6596d7f0fcd03045c430479047c1b849409e7677195f84d1041ccb821f7472db7edcb0509e1b6a1a7e138239c69230add20bd19f2f2b2069dcf8a07ea63bd85265b391b819d9bd55c4eb4aad4d587395802c6f5a5ad6e2a044500aadf6d59908a302aef926d9e0bce28c10f1c52087359d79fbe6cfd4cda070eff0e796650616f5f886a3160243b5bb0655ccf21e241e6aae9234f024f8f0923852672880b096ba468d194fc68853ab6b9157b74b5b4c528612b9cb8365d25fb5008ca963c8c1a09d953777e07d46b870456b6c8e3960933e75882de6485547024dadcfbe6ad0b9ec94f9df90b59e72276ec2a89576a2b822ef65d2a1e08a6e32ebe0a4c6b6082e0cd0014b1de9ac9e7b83874903b41436f33f15e31f592c0cd53e05ca364b35443975d8ef55ed84d04df5469823a81dc2bf64b681ca50df1f543c109ee2ed5d89c43d03067e2b949c802820a3e4f41e9c597f341a7d1c723085c0b144b84e1089000e2d2405c430421bfaaae8338f5ab49a7be6829df9d0405478b484bc7f916b0914342c2d039ed86690e2d55316e921e88f97723a582a0ec809be9bf47627e1af6723da952eb513afa1a27c4c44576aa12e50293a12255a72d54037797d87a4c6c0f008ea349e5034207ba4d9a0d79d243894eb4f30866ee8a39fee985a686e0db3a7327a91eba588bbc45767d59bbda81f8a8738d16ac024142fae4ea6c9612fe0b1ac0de951e19799d42478ac8dcc787abe05854a43320e2b2bb67fefd6553f33c87712322d7d2276efc313da859d134766790d6d832c398ff764d6db617a28d07e1b58e8a4f8b073cf331aa87e2cb01aaee4e4bef5f47791e08f0b118735059a7f453f2fb9679eb6bdf1d7e380a4f4d67bd44f20a368257beacb6a8e420aa2c01f080b133e124680ed7885a0ff0619b140a7a98320e4e4b5b8f62f292441eec41a6e9a41940fe57c518444a14e131162879c9ba27e250adf8c81c61c2a4bb888cc1f6e1e0363e91e035d761bd104b4ac64860663a4654938cfd53fb8513c5270393eca75b08d2b985e9a6bd06b71d9433dc9b5f531d099356026c05ab20a84fb7b6fc4d84d8a45df89f62a3198d70d8ac9d0452b2d91df5ba7b68d18ae429f3dba08f3aa8d00f8563578fe01380b00f08366a601c24cce7aa5dca2b49829a21b65cce9156789e41dd735ef99f4fa1f149eefb32f9e94c9987aac5fe442c708fc0c152e56d8a51f56cc44009aad093a36dcb44b092fb98b68a52bd9fc778d821003cad0c3f55770495b78aa2ca7f605cf5b77c39f1d12f1e36033445c00c081dfabba904a9cceaa3616249cc5876ebe660eab27f1f51864018d8ba41f3411a324c168c739848ad475a4637fb5464fac78f8496bb0a9402bf7ec4c5342312bd9922b2550df3826b7648cb480cbe21ea763fc3d48bdc3ce9963c308e037d8e4660dd95433e94ffb5b017a974aa246f217afe19f241b7f001ee7692d8e1e99b648cb8c7c3ed9a7106094c03db9298217213883bad9928f1eaa7d58e44d6df4e06bd521f1db4324df63c33c7ab8a8ac00add8d3ab1940594189f142391e51d0cf169324e000e3c93ec20dfa637f135dc63967f44268ad5655a37ff9ad9811b4373ed81c4899ff09f8319209caf2be0d10b36c8403e5305bd3d6aca075ea511f3ac2d68c2d91693ae81a12586e9e3b5075ea5f5c9c6b8917459f6703c774fc7620165776d7708505d46e1577cec9a6935963895b7637907fdb5f1b29abd952803e59ae9cd3761d402f759492f425df8d28d0db700016b99f22284ac81b3b3234ed0fd2d626501e7bf552e11f71bd43e0e38964c4c0801cc7616cc4c1a093c49bcda61bf7713a25be6bc17c299b22fb12b8a28c5c85b6b9cc88cf605921252e9c815c85a47ccee784679573b245da23c0f9e982ad417aed7f023d8c05251988f239d44b108598df12022cd8de8efb8dd824731697e0af87fd8fc3d2c9c3252526cc41230d9a70583c630d02fcf239109c795c02728525d4ab6d63339d605877f4990e1d657a6a9ca9fa62bc17d6421f59b1b8f2842f00aa7e99ba608a8b02e7c7710f9d2823b25a7a5d5c7e3eb0defdddaf87ebdb8d2f9fe01239041eb43ab99491020a0fcb60da7ac902c7cb58e43b8ed6075c07c1f2281ccd6bd074c2b733ffb73678d45b779b94adb93d7f7750e28cfbd20abe5e8d71691369367e72f4718b04353698582d1d612a091d3fa53da3f12a24aca13312dbe46e1612cdeff3d51d3ef48549dc425655e22a5a3bbf4b1c843ecaa6277c8e648ec2cf88726a0f98bd293e0c6add597bcd79aee9cf57a9758fe39d8727bb3ba4d807d091a25c0ae47584f3ab48e1c05157d0712f8b424e23d5067910371dd29c683b10e948e9c776f1e1dc0e0518a91001a30a0c80d5895492360786b138bbd2bc0169a9aa884989837cc8e6fc4ccc946851405def2851a4dc11fd7fdcf455086ea84a97ebe0dd467f260ecad51da8b5de3f149a7b5defe8aaa1822553dc25bd85d754c3017b04e2ec06687511a0fccf4242ad872e1d2078545ff97a78ae5440bdf36c0eb481f11cfc843f51d30d664f69727ef530e246e036a224e7277c54d32b2f7b01e61b74f24deaff65437d12fe3c7ce57241c1a6e458f0effb72d7a697363b256831fc2ee0aa11dca8b0b6d6e08d2cce5c44766d8cb1890a7174d82b1e2dcd9ac2d31df0e0d928b6f062ae2e693ed2a6ba8f20210304fb615fa5b12de22951a5e6b94ff2521760b85f31a65d863689720572f26b94fc2f4d78a88567df6eb6e0bec6b76d72ca35660bbe17dfe2502cacf6358b5debea86cc2f4c5aa8d650d3ef919d8ec24557e1b65cede2c4798d08552238dae89bc7cb494f08b7121d7dac0ab159ed6605a433f9d176084f9c364bef6960086b9c647e121485381e9feb4c77636059e4e07e835ee0a25d9610225a4b5c403575207408e4c65cc6ea6eb43dd319c76a80e7d639a3aed75a596cd49b9ea4d3e85e85ecb115876601f5503c48eed6937fd63e2d4b0f1c2a7762911e238cb96d2c5624bed1c78a3ac4d313df2ee46f9ce4bf0954851e4e41f193531b1e67937131b0c0a7daf115dcb5c1cf9de10af1edbe905afd94dc63c31beeaaa00796dee01393f8f0b977f3a8e83fd8e2131a705d5c6d53ca8542605b93221e291ab46dd3fed7c7e500ee4651aad014965b0127176dcf6471e08cea59f0dfc72465a28fdce6f8112f8632c20a7b7ef6f28ded4a0fb458649e90b8852414b0341de418011a289d345425c5b4da2f77ea5bf2cc2395f242edca700b212550ac5051c429876ccd5a8834b1ccbc6b50ca2774b33b063a6df9eb7e48149626c4edaf85b8474012f4903a6ce4a0379004f256fab66decc9637e52dc53512b5831f382751fccb0f4016771753cfe8a14b0609821016369acd263a9ae3f05dc3ac447b65cc1f914dc0d8bb724deab16666b6c6b329c956de5c4ab6bc4d0c646ad24b2c0f4d43d399c59529c88c8673cc77286558ff2e3371c37a2c5251f7a592bf350fa1a3d05a71baa86df1275a22718567fd05683199635719980e44d27932a3cd255cb535cf45ab36d9e09e24d3a22e2fb946097d91f052dc6e717346372ff0fa7cb590ac1a0d8c45bc1de6731c4110b1be8ca7e52b99f38271b99da0074f6607ee9ef9010fd2a60be785f595e38e59f0d88722543c2a410819b5168c96dd9785c11906f518b253c600a112447d436b4a82de12d7e2dd9fee34647955e482d664b7366d76e692c97aff602eaa50bea40c75b5d3e9eb93f7570f0d75112872cca092b46852185c73a8f3f121b56b6d1042bc67ff06ea6bb9c874bbf725ab6de1580863f05201ab48a0055f7616596423e66cb4319d323334fa6b56352090fd1dff001c45939a280a875fe65f45c0d0c4b2b0e099f4b6bc73d1791c161b7de5fa9c8cede97c837b9caeb318d404a0fbc8606e0d29fdb9b7819dac810aeea4317feb87066561d526812a05f59162871818b1af1db96453a22cdb7786e82dc98627fa0dbe41137abf012ee78436fa3d9a478aa00577c71a719380a13262d4e9d177d5efdb8bb952abf55346068a791bfe7793a839c08ff879ade277085d31db1db1b2cf93d72c35447c91318e110fb0d2d030ee339d60dc7f8107100aa40e47ccb8fe33160cc71ef38802e6c1e2c7984b41ded5ba4bb49523acc7ec3dba1a2956e5a3425fc4670d0007f6f25abd680592326089adfe0dc4f8a56a2a294dd519e4a17a68f141125944f0a041610236d81120cf81e47a5e3398c64096230b49ef19fd8d894d7065a68ed1855224cf540f5e3689955686db3d507cebe413554212dabbfd0517d4a6920605708a0b483af84261c7436388f63553c3f261c73ff6d1400f318d3203dda378b01ecd9b3c367eab7dc75da75342305af508a0665de0e1ff420859252bf9dbd6cd8177e6ffdd7864e215e3de7ef847890c1186be03161d8c63cfb02e9da888a6032bfffb14adce3bcc4cdcc632a22522cde4d98704008347c1e6750a829199212aea8b78689ec803e9291528302b708305fb3d790d9d852c8cc5e25a114e9c7e4a48224a6b0506a3ec08233fda0969ac9e64f75ed9495a50ef71e92c0104db383a12bb6fc3bc8b6c5275081c664c96eb83e33a112e9c09c7a972b747790449bf8e9494a910e56e6653422637dd339aee8b01114d8f9083f7f4b63a0552ff5998b0b4d40176a411326421a21c7a807095dd1dca12b1cb2ff44cd45bdc67266eb2782e0d26768eec38cb4d303cad38ff48e388e0ee3cf7a0fbd43efd03bf4827a7f6c9934f0cc92c9cc9769e6cb24f36792f933cddc7f66eeff676c9a919b6c11cc8550f2a21d4895e61df66a3ba1515a9b6e620d44a50dbb44ae20739ff075ecf4434a12806ad338e006176d273c550a66688cd096c21a6cac494b52f0a37a7e47c99ec5b76d7be04ac9a2dd0b2a897376f750305706815a08bbf31c11cb5f8afbcf72898a93e1ed2bbcec2962d5e6fa22258bde9aa4238ddc4562983ec8cbe2ab7a50339d8184d09137ce67076e913f45a31124b10357113095b7c460a5a1575837e348ef9362ab5e5bc83fd09764f2017f33da2f1d17d9e8e58dc669d09b3ebeca276e97502ae6f085bedab9306ea2ecd4cb0627552ff71d8c86bc30d04d618ea47e665cb8c4d0677a0d8c07ae96c9eba995e84ebdcd57f90440b2a4b4c54729b050874b6236c8c290df153b8790e1150f8cb938eb885de75e8a4c3423882e5a87df0a024b35abeeefe0e0aac5e8bacc29f5dcdea8f99c959c6e55a7ff2492a352313e5dc314debd485441c2219c3398cb27d53c78c9db093b0e36a21de32cd2115c17709bed8ce9b14b1ecb8684cf29533bbf901775ba5062a811a81cc054392590bf3bc6ef06f56794f9a46ad035084706e3db436e9a4319b11ff3ce3e3a0de45fb45c62b6169c89a2b711a79c5b3a6c0b8ee02ccdd5484df38ee090712f8390a033b5c622ecfa1f5e89dea9b40b03f4508bd37f8fd9e0486b3bc6c0dc4609cb3f17130d56767612190f9dc5ad740da851012212d08613f2239380dad0d55e458944a3ea4b5e161a245f71d83cd5f223d382535dff81644a11c117da4e6916953fec4ccc8c292294e93b51ba166412075fb6293df585c753c9f4fae18510c1041c0155614e5cca6ea673e364c2b4671fe8b3749be23e6ec339f9b579198b8c9e348b8caf8020686676803a0f73252ba23e14db12473d53976623953ae5c8dd18aa89d1614d8a5d39b9f4ef0c9b5e1896e04a58d25df73f5f25619dd0dff3d69cbb4b83647bde837f5195514b9dd42486028ac8807cd005e9cb1e4ad9f2498317a2acbf14e63563ff4d31521e6a2fa7c0b3836f2fd60624e6162954124523a3e9a97bf856df747079e00595fa88f14679a16462c960a1684c1a25634064347ba7aeb7f2156ebc0b4f4e9c425548c28c6e853ab0363fe23042d6f540637f2f17ac90fbd675efd5c09a926490f909499d45ebf4fbe817f53e63c638d1527cf04142c442e1d5dd5c5e0cb9b77274739b9827fa6520b438693ff2fc6a441f64821bb1ed444ace94a932ebe81e0e63897d2d2f91969280263ea207847af03adf85121c4075de74d60866ae467d9cb194b8307ebb20c1bbaf0b62950f40ce8070f375d62832f16f01c709ef8d29bfc3f8efee9affc1b9feb4a4ef323ac05b2771fddeb61f3d40d264453b64c5e27d4679f0100a8b0a38d0819f27c471a03d819abe7d2f0d86523d644b858b1b61eba899ab6506ce2678a4cae056ef3c7adbdfa72bc39a671dd63ed376468b63ab2653388d19794b6928bc4ce7c55cb3503d67c26b0b06593705d86254eac1a8ce892c8a56363a71ed4137d823230a13e64e13e015753f80d64de27f8717648e7742e9c046f426c1e477778c6d40e691eb70bc88eeb7168e44123f74ca238791eae40dc3ccd88691146d0864e57241847d4f5967936aae19202c752594886d2c501f43ab92a0336cf6e3a1cb2b72c381c650c12fcb65a5ea899bc0d2a7b1c3b41465a57908a4f06071d0677499a55e59f0a66b3ca0e37cf13136a9afc6c1805cba610ff10bf1e50b29c9386b4b11a5bb8e92260cd2b1defec783893e7fa59af4d325c1523dad75b4193461c500afff8238c60d76d52e93a6e34d94341e41b7fbd1c84628dde500c969e682ce428c99c3d4ac674375eee73e780eda124ee6b1f246a0ab6f1456f5ae075ca7de0a27717d3ff46b35a3c8b922bb866d79fbb0424796044b2586800f5d2bec6b3e7ab859eb46881cb6574470798c0a1dc616247644b597f14a1c3fb68a20755e57689360a3ae732742e48789d8cab27fea48c99c0c565b655c3d7e92be7bda3c12fdf1af302bd20cf779de2fe369399e47ef46aa89dd98902d0ce6282c585e7e81557218371abaeb038c3d8c841131acea719a819498155cd740795642c4a33fdeca6d3dcf4220639846eca18a214d2e31a5c6a34f5130529d19b1481481ca323c68e8f8b3eea3b05487f25065da5cdd84d8e873320db517a35eaa3337ec53bbfa3a7802360d653714e3fa721dc97595b7de1dc97feebf2c02e842b776e2ea9581b72076f87add3671e00f2bd6af8c348c2b5be1dd64a4de0b23ebc7c494636cd49148fde0cbc0ec11db419166ec43fd903fb8f3a578ba0941886ad9ad7eabdc4a92d63d1aea547d02d9796c44649f0b5fbb9758483fb4c2f043b12446cfc065c1f43071455c7e6d434aa6578a4f2ca618b096aa2f86f5d3f068336bcd5f8ce8651e28c89dbe147f0b1c1fb0faa003fe873734a027ee86cf873b5375245b9f42ee790279ad0bc7e152d0848fd3669bafdedee9c3f2a5cdb838aa0c357ba787dda554ec8e20342cd4fa19608a4864b2e949c3aab9ab0ad617d71bef6c5554ccfc03b38cb69a2bf7d605d95c46a4558fe6510d44db28a8cca0f5fc6f21f4099b5d38ed84935a74cf009b3c49a35bc91247cfed0c9cc4648f7d29658900fb36ef846fecd46f178bb95a64834e6d84f7a5785cf741263c13a016ebf49afbc90d0dd586643590c795190e2212707c785fa417144dbc9fa510174845d1c5eff259fca49f7eb7ac3bb774ee8357155dc35f067ae31feb51fda0f6363ef2cd922d016addceb1185d4bc138e92dc99a2f25a9f2cc04e990096a15317655c0cb7b4d8a167ccabd598e243f306d66161fd4baed11d47031cd6b4634342c9bca1150953ce702dadd083f115e809f84a6fba02c8ada3683493909a79b4209da91bb12cb441aaf14c366f8e2f6628b81b1b4e44a824311a7b87722323c723be5d874e13ba47a70e5568dabba48c81f4e67e3b4290e9461b809e9c4cb980f484757ddf454efa86a97d96f379967911e6a904115266c28da27fd471afc2941ecbf440f89975ed38eeb81a43f6874d52b63973a19f8dc0709e13468c819bc46c7ae45906c49dc4a8cbfad2a663395a0f71abd4c3a289c239d1f4643c7c28d6b34be4df8df18aa7d63b358c931328a4e338bbaa99434ea05d29db25853a69c5b1160b9c03ffebfa484290312d747c932ed129f6b39e83b9661015c742266ebfa61fab639cc4ddcc80ba8eec34db93c07d99e58388ac81036970324c22e31e20fc2dedbee00a7d7ca00b7ce7105482e01eeea6ec60ff3ce5883f1311871254f3a91304eb5ffb621363b9ac55af773cb59e14ef54ec0ee82927ba1df5c242cd7453012b03a9b60b915e36ec319ec83f65caea46d7001156c6c7f86eeb271653544c43658bdbdcae623c02faf8c788a1051b7ee0c30a3e21ee2cd1465be31d491c68d8a30a6f62196347be4acb356d8ecae73967136c1b258d275a01f954508ad58c5cd741a6b1c896c1334468e7dbcfaef757c3ea314a81c818026a084d1a2b76b9f5e2346f3ff11911788f83c71b98e59db85c3c3b62da6742d72f13fc4f9c1339ec745540f16cea6416a2ab45f37ceb971a607f6e22f87a18c1479f2063d1cd8c63ab7c1e0bf98e68b59356f42c5c4b4ba4bc41722cee69a4c66756f4f2df6c915c6c24c50472385d13515660c3eee88fb162039eec0445741c423ac616b0364fb942374ee7b866fa7edc4ad31b91a25e51e0e8b08dd36f456e807a9dd7ca0ca3f23398f14b4e979eecbec72d73ecbe3fc5157abb1c3ab514ed4b8ecd4b708e62bd4020028976ef425fbabdd8d2bee63b36b319d4437753a952b684e0f129c09acb1238e9ccfcfa2acb575d4be3d78bfda28c25d408a0deef4bd3491ef476c1a6a70b4cdb1ef5f941c3e6301ec95243eee3122c0e5b9007d86d503cdf4e693b2555e9b6e40d1b3cbd3f0291b402a482fcfb0a8a2d421f213b3e5e436bedce40cb1afed6cebde59e30803c84d35a08f1c70b3817d147de168089c0b8bc02184271d990c580f781c0a8f41580462272a13dbc623435f9eec665a7814502c0809bf0ab29f9607af6be7cecb9496506ef3101064e46b0c527f06c4d86583732b0649523b172ab2069f6e696bfc3de24b0ab2627eb67aae9d8f4462653dc943974154f7fc646c836efcefd9413a7d655195930de3604fab02a2eca1bfc45c9a6e48c97b8b8de289525c2326e8c3d0a483e5b15487f2da2e2b99f62fa752f7b6ad3e4bf3bb4e63346a2b4d4e6ac773a15352b803d9196f72332a1ea9cd4a98b4b26ecc56ec998b3a9b1a79f79ed1cb9a4e2ac6f780b41e6cabe870dc0ace0ef71358078a58dafe7fd8e851159de221c0ff02fcd6f65b85c10345c18d30970478d9d55faab1bdd569d1855077d7e94a7f37550ebe3fac23afbcb63177455bb56f57022dc148d2607ab64ee116bd45857f17ca94d08bfa27572e0433aa8704d99813ce07bb681f02ec350d5c254d01ec989d9f095dd7f8821a245421c82448738a7f10aa5d374d1a081c1d13f48686212aae66a767fb83165f278258ac6782813eb18980138ca3f0e2fe3bfd43b997c21bde9369bd53ac8dcad7c18498a9d7c024ad5d760c59d164a3d07a6df33df891b12b24d1d52716ffe3909057a7e734a62e1326268c38d96a4b162d16e9078b4298376f13819471d421b8ea9283ae96923966db1ca226e0353d4f4100ef9eb907a65f8ded0a353de0c157b105e248c26dd818e2196e05192cfb309ea58e7a8098333f93ca9eeb392543cd215f8c12a49619461d83b4e29c9194dc41a8e47622d045b67bb7d5d5d5cbf425cfd2d55c03fe607470321c19ddd21c11b0783bc38316c0f7bc11fd6f7307b671d04cf2b5a3f517079d28c6eff84742439c629f4f42e575cfb7a267fc2f9d16ad903029205aa06ee2fcce0f768a2017cede35e944416351e2e9e14850510cb473a1ba1619015404daccdce24a3404bdd2441ac8f5e1c0b8c1846bdecd682356531b72ea24a6c20430f28301060834d79ce098f1a150e5ae2c28c7f819db1db5c1e555f6c67fa7da3922e45c10b1b1d52ec700be3a4fc6503ec9d2b96814281ca4c4c79dbb88910613f324593d7f365447885a51687e2462ba4dac516d646d188d0936ae12cb8453c176228d239bdf152c223ca54a87231aec3e7311114d7bb0ebd6f501fb61afa1e52fa1d322312112f1b904b1d732bbaf77b8137d95953dbbf4e844e8e1d05b1cd34a297557a48692a855236edfd75eae9ef9bd8af414c1eb1d2e6f79aebaa6ac7144728eeb8f84a4deab89f4e76a21c52e29a972d9d9ea7852891570facd3fc33bf0ff28fdde5f61d50788c2b1e429e95b3efa2cbe1ea98f2ff692d82fb299bc08cfbe1ef2301da15f3d241a156945c676a8a71bf1436c0b090d143717b627186934b995e6ffdf6db86937a79a5a5557eb5ebb455e9930aa2e76b8050814803d349df5734957249448f12109110a88958a5e4dc5677525ec0340ee75e7a210c4d9a277efa725f07f6b70c0a0323354849193f85dfa39f930e3947395ea7fa042a3f97050730181d00631a753d3b26d73a4dc066f0d78ff9a2701ba36cffd7edd3cc50b80f3ec558fbb1dd2811320d384425b12ede53ad42a6b11d3bfc33d1ab589e65a34cf365626df5c5083158b802980305f731266b5292ac8989f06be43910f1a40978e2e0b28996e39be5ac602d55bf0c12f754e346881e680b63e528f31e836ba016af1e23e482b849c3c39993a9b039961bc675483184b5525e3f0f10d3cedf7e3cbc09b2fc26500d9050061597f73ca553711667ec9df5c4abb519c5605e0d896061743e0d6c0b9eceb8cf52fa833e35a9d52363425e30eaad62f5a81e1bebfffb8bc648e144302a60d4553a81124e7e003b8f63d0430a8472c86279d8eea8fe9b80fb00567e153ad6e8a6b623b85218340e8033802e766a87b53d0a52001133d4350eeb91d3041efc1d4dab7f09427605545f68bb48238783da2a7c0ed752cea01f94d558b47dc51010215a158c934b95359ec1da07c18ea002c8fad2903562202a82a78ac6579d9f54007b9fb82d6161753b4a70ce162a1142b0238385ae957f6bc0482f9a72efac0b7268b82e6f2c7aa019a847c3c5e21d3656a5e82853c420f7ec58b14f3f7f254650b30721da0f4c70b58674fad814a98a089ba7372f575ee77cacb618e1795a19bdf537a8ffd6ccc047da690167f841161890ae8a44232d767eccf538d8949e253ac26d8f2dd03712e62be88a117667401975bc29e03e5cab2fb9f42c5eac607723acfc644426ea09ed67a71e505457ada4cc4ec315c2fa880b5bef5a18cceb33d5197204ccd50a819d2ad5a746c1dd1c30cfa2cf66b34864836cf48b303564521a8be50091a0b15a198bb237118ba199e230b6d1056d2a1f2f4aa5b3940b68d18be643cc5cce552a6afef9a9e5c06f1f6fba97d93323b567e6a44058c8738839748d0fbcf338fa85c0d378cddf31b9093e07e5c43a46f830138f8398a93dfbfdcf49fd136726b1271884f5ca13983db6815313e6de49928608ab23967558ee2129914e037b7f42202b8ffa03b72b0abb3ca9755d177da097706c6631143f6e00dd82d7f4e3bb633c7c824269e0b2ded6f4d46f521b828ed3b0a10ac31ad26aba789743e9f2b8174b2299c3992b2225acba2424e3ff5d79a4d9f8590992911425a2c7b6c7df8f050bdab7c233a5a79f212e8c4da866cad637e6c341276750725f4a38fb80b9c3dbca2c0769467c036204c9d326167a3f354e388e29ad847e5fddb338c5d7bc9473873aad5aa1b2a1b6f49ab723600fbf0a8944ea1cda3de29f21acc4c109667fda338deb46a8f885b5fe6de90f02d9f1c5c44414807618fba68ff3da1603a0b025360558aa0566a9d03e0241b0a993f904dcc3aaca145670a03ac42ba5e70e423eae93940156393d5e3f4459101d1303464f9d28d05704fb4d0bcfa4b95ee90cd50186ad0fba0a767e04b4a4da214997200829ff7758c93e28fd9b71518a4a64f98104dc21dbd887b6c04565647dc5a6cad0b6587ce117a1e105edbd1aaeeb7ff091ba02bbd4e11040a5505bbd319235ff5056ed74e2b4543ca1ad7f9b94578cfdc8163b8f8322833971a56118d22c29ae796d354167a9a2263632a607b367de66324730deb3cb0af01316909f0dc7fe9c8c2ce78dd4de75370b392469acd17acfa462f5b887e4dedd24ce787e35c50bd6ca51d303836e5a39d9f470897ea19998b9e6b8a1d320807038f65598f45e329c3e2b719b14c62545d7072be81bec79f232631b5d7944d7699ce7d12fa6f93676e5e609a9c0926d37f638d0b891e3ff884fc0b72fb70e4a5865db8f8c3abbf238cb0e0bff6e5b84e0449c957a484ad3899e419558cf162ea76da74de927e6f4aec994bffabb1c5d43a249c91c19acd7071defaa3b45df9e6e6a4e26084cc8331cfeefe06dd7c8bc4f5f75c443350d7c5a2b5ca0877c5ba74489583888e5a3289d064bf61fe134444a08990f19d954ad072309ed1a0b6172b28cbea90960a75cc48892cf895ae5548e056a19ebb3d0fd53180e941eb4f3cdf087030de653a930b04551acdb9785d431748f7f62bbfc43adf982a55d51f4ae57cffadbf1249a545db35550f73033248ae6763d7151f8c510ba45daf0921f56e35e7367eff6278f0a80b59b5be4f1f1cffdcab5bb39d15e9a34110b884a978305bd80a4889fcbed67303e6f4d4b51a04ae15d7a901252e429d4f4830b39675773bb85d9a29ba0b9d79240c4b2590bf6d1d12b3fc4394f94d1f29eb47fb2bcfde00256f75da6f82d876d51c7713ad6357cf4815037f6bdf78e476893f076eb60a1eec66c833de97c0096a1f456633100555a789f17bb38e7ecf0b3cce2b480004faf23a18283d3a19b8ccf4ffd4011f0b9919038f618f1b1859b78ec6db4a3075fa0d9f84da750aaee3498584cecdc3da5fd6e6df52a7866630a4137f28ecb4c136f3885462acdd0a517fa3f30126a6dbbc28c3fc7f92b375dfe3c5bd0525d285656012aab798d00050236d476f571d5a6a2f74e6dbb37606a936dec68c87ebde78bc015d89bc9cd1e0f496319002c3a76d5f56250cc23b9514fe225747739a0e76cb628d71daf5019f1b1dad99df682e04b0819f3cdeb5217a8ebdb51e302b8a78b6cf1ba8f7a5523fdb276d45db631fa941af7e49bfe31e74b25501dd8045eeb901c84c59d106fdef78616d8774ee0623aa4e69a78702b903d9dbf00dacee17d244dc22c90102f61a8715722bcfcd64e3303956b50b4ddc8f756c0a4511d8cb206a331dabc82d6810e81a7c2d75ccd6f51414050fab644dca41fc50a1d7be7d33f4b4ceefa882b21cb6e6a62268acb2082f7c3cd5c6de7357fce74acf07bff79fd8f9d88e267ca3257ae06493afd7ab8bb5238422dc4d55fb64791f547f17407fb1e190703abcab819f12da3ef1a8924295763fb08432011bba42a983be6518bf266ba7ed2ac55b443c968161301b160c93d1a962bc84ca4ad98b7e3031320f84979586cdef9546808df05a5096f8e3072fa8a7ec53697488bb638c70f823ab7cf7c89742f65941cfcf07cb8d657cae622e744e1bbe16930d0f732e2a7ef26fda0acc668146e0d6bd5c4c7365772be7e850f8b63fe7c8e627d5c43bd1230b471f3337f97200b6fa4ef239a237762276867eb01fd04ffada2ed19acbffd6275892875e3ee87d74d826cc31656b84d83b53f1037135d33eb28849feda35ba86a3c05ec1899ae20b2e5cc6a6e9636a11f22bcd2a0135482130d9fa2e648b6a93e4e7b0d0eea204db28c432cdd11d0370eec50ad6799ed83241719aa8b2adb4e29d861dcf041b814a92fdb91885bc316c679a5f4271cf1dab3456085e95e8add95d3224cb689744b1cfd8c6ae9a3784d400c199ffc125a3863ea659e705aea822ccf914a6d305daa9d613077b9f17128c1f277771c86e5d41c526e0bf07d8312906ae0d3c8245537dc61f1c9c5972d9e66d5ef964537b88580fe445ef641b497371b6c589f5ce2b33c3c1b7bea9f8a136b17f231763c4cc5b433a00ac7f01e40dd14d78c5838c293941d2fcb98b69b6691bc46d9c628ff7047626bf472019a63ed4611ddfc4d94c7a2045cb43688375f548fcb9c86dc44e5a4edf7f058bb7b5c31120ee99d79e58ffe8fcc2e413d035fc9508feb3811f9b33d7c24d07022131fcdd0f3b90af9eea506f2388eec402e02ea5e69318bd8cfe28cd6a7d514e3c93c187302086375b203a430df22b6f139e66bd9620ee056881fdddbf7a01ebee2f9cdeb8c84223d463d46209ad264a44daf757d2eb3496d0ce8a1adaba4aed5ce0d8ad282daec30c28ded196f0c6be9e787f33bd1899d165f6809c71e3f19673b33461bd018524014f79de637ec6f1cd0a1798fe3e25b4bbdc3a08cebd12ebe31ff43ab9f01d64141a6bfc30159470f37a576184ed5e9b65612a799d0a5cc63ff8bf48d035c78226b70ea3a85b2769cd72ff96106e5da42f73abc159c0d6eff79659e7dde1267f6a4555fa22f3b3c562a0a538951d26cfac031a487dc8804f90e370561638b8fce47309d067bec80236ca2aae4b1a02380e3757a31b49057488e4528d6e34788f8caf7116d45ade38bac93039bd484ab10cc6d680cd111138543960feec38e2876e3e20efb3e7d63f4f44bdd269241780e608df3a06b392c81416c45e8f16c9b7c389920fbe9044b5e505172a45884a203f7d812064d191bb20bd78d98ff7615b99817a79457f7fb6ef5f2621b72cfe546aecc8fd2a0b8802e4d110e2a5852d60ef15a64f6443e1882ad3c721d93be1c8f6b400bd47dcc57f27350205aa9f4e689f2b1e33c66ed481f472e033c48860f52a15b145ffcf0027cda9848472a8543c15580c6f912dcdaf977e27a2c2bdc013fd25a7c3eeef874b0e0df4706c0b0cc3be4927dfb7ed3dc25e795eeb0d87bcb7dd620c264ed45c09b8b569d26b38c2d3bbaca1d7506a161fadda2dac7ef8c4d3ae1a2ca1bdeecc1f105b019f3a8b2c380408ce6f4a1f6eab5b290a17375b40bff549713cb6333bb873226759c16c67b98f66e4883b8586f3101e22eeb21401fe13c2cc657a553051ff7c741bb4eb73cd0f5dafcb732d062f0f3f6973a98fa895ddf983f3a726cb1763ed2975d3b58bc83bac937f96032c73f4904b16a395989a036c8ccb44570beac0863828ee35dbb7b83c433d111ac8da7e3eea41d960fc5fd826247bad5fbf241313e643891f4bc9883e3ec309964ccfd12c221bd4dd530ec333e401087392a2eb04ba93a675e25b5ba82bb3dec875b6e3439d146c51f4df859c47092b905cf05cfa474fa598fb163d66e360954adf3147ca1340d3ba412f34ae9e8a0c495fcd31386989b2c35b40e72026b149c04f267473648d39728b49ed54dda17bda716106f9fa949257e824d6558a52bc4f6a8cc987698c1ee9dff02cc9356d5c1f66a37b0a05fe535ed30557a8b9ca304386ca606e887b53b792b7347a26f69407f51ef9fddc6ec8452ac7316762e78ded474849c5e265ecb8890414da9132aa58c3196f24d81d770feae1d8d02d74e368af7783f79409e79a05998f598fad93eec867181601aee4025504ca776eff79489eeba834f590b53b11cf9cf241b8443e351b51eff901dae346d90dec496c1c5bb4201f32090815b61184b510e2bc0e07aa8baa8f0cd222436ff4aff7792c0c96a62ab658dc41d399b804c65a403954aab10025112bc1997534f3c3a3ebe6a3b673949a7237c8981b5872ef00f5f5366c7c6a05e97fae62487531e06d2245ff8d221f8e229052a4ac36c22f4e14a53a917308c31d7076d2a849bd6fd486c1d1fdb574f42748606da2492b4703617d5d9d7653222e525d81b4b5dbaaf812b05121535fd83d4ed0b1a8f11ba3ff22eaf4e93be37faa8d560ecfbff2af973770279f6ad8c2e97f5dd83833141b9b07f22fa657eacb365b94329b73522639d98bda31b514ab174994bf906bb7aa79e0b6e3ba45fc2ba1bc1029255a04ec87d18094e28f2e119d89add3c8df9f3d92e1118470e7049d08ac573d84be47e07d13ce9cfa091755110e434d83a2c61a3637e52ffe9ebd53e6e609760e211eb0c8be42f57f572f795004fe1b75ec11dcb09633377aa77420aa05435aae574c9cddecc08470f22d25e447c25b77e93409723157cdb35239a825ccbeea699e3b7f7c40a4c61b5175f819f99a8ac0b82140b9d4c9508898d3197c54720d8ce097f2f8850d81d28424819c0c3bee386dee91e24acc61b261f91c54566bcbd63f833d0786539dcf4e51c1c6042eaba0fe51681ada03247e61ca2614abf2cd74c2d38cce4dd0b78d162fb40029e033b8b8fd33cd8a7a6816558745c7630baa4c255982dfd20052ea05006cbe74c3d018000ae0597f567679d8665905842f307664a83862b39d6bdd9a51b906f71d9bae9c06650e9b52d688ab8101d9703077632017dc702e737a7c2fa9bf012dba320e73c34b9cba13fbf8078292b2882fee0014b84b0c0a3aa04b88f8cc7e7016b1217798c35c8bff9422c423d703b996d13ab934d74b6816f09d71a168f860ae7114905a98bef343cbd46fdb4b8698e1ad1d76ecd19cd658c7fac99586fe4ccbbc7d1cc36f606878d1be673dcd70a460830cd949b9a5ac9143697cd987659133305b0963c36dda85d726e4c1e5e8dca64044aa1d6da3eb0e0dacca948f6c52322e6704a1c00ff8baa6e9b63a4984a2d5be1468fd26c2aaa6c0b0913e131b02e8c56ff2fff94c5c4236f9bb725b347841fe6d71d97be95d5d9072dac89ae92e68cd1e3602ba9d109dc40cb9667c682f547b026dc8c720f365ba6eaa1d4f5f5d271c5b4d50b3b7a344dafceed8a68783f6fe6e564e04699cb44ad607cf80fd8215125bfe511070919a07111f20e69094c508686104e380c12766c1acc924202d253e7348bd9896425c96deb25343f826e2fa7f666f2ebaa9169759f76d0d7d7b82eddb865257a747fa479cefd8619c7d6d2c4dca9827509832574c11c6ff347384af967c4bd522a2b7a7ff06b081022c51886d3a732a2056550b99d8167256c285a48bfb8ac90ba22b96ea08f87a7b147c4ab2b5db9767ac8e57a88a222e29c0e8bc1a81914b978eaa9cf77148e66b8ca0e8ae671f057a25ca3c01568558d279cbd9602725a354e5338a5999fa3f57abca0900530bf1ecf59efceb7763838286440e02e930ddde321d812fe97969ed38de228dc6865241764afe740ca0b59fc5b1e09c3101e132b80ca66621d3df6982d6f0e364375002fe945ae6f886d98e20da9fa7003795d70efa0ace45606a56846861b6c2ce66868f533875db4a2c8bd99a7db3b7fcc1d1a6ab9269ff4815279f3ba0072ee595d4ae9a7827e22b32f72fa47adc4b21631d7008e853d01bbc338b36e080d7223ca09c40dfa5d6e216db0c230328d215fed59da0f574bb53707b12c769940994210f36cba7e4e16589616956a7388bd840b2db541c54fc5609a9905dca554966d4ef9a931fd400cd2c3bf9a02ff85da6e20cea059f95935fc402cdd6ac7e6bb96d54e3666167c18906f101cd6cbb81d0d647402271378d20de5adc0d20a0b5e918c3b028967fcceda928555850c8c5282d44dd0eaf853786b678ca0a9c15a66ce55ad86763f7f82bfc4dc011795e1393e388f87038880f9fe7e78cdf4ffe5cdf2d16fa2dc27d17fa30e2fee973b7046d11f0d602ecbe8a71516481f6c56bf92653ae50682cedc3b8d001849f17f5923b3aeeb0aa5ec81ace7224bbe204137319e897a429849347ef17ed3320d2d0fba33b2d0f326dcda1d3f80f5e151f6a5687453fc6457a06cf0a716618dda400cda78cc7f0932664756d02a444509ad83d4844adca85b787d7e7aa7b48ee05dc894e163ebef76b82d4a0ec177137d412b7b38b2974d74847c467c431d94f7e9f04f9c47e0b1480df63a3045a8c7560d0d2152b0a820acf4700e8ca884526b51540dc5f9ac21722e5e76d0e50a35e603d66ef6a5507a8d06a077e50051f7fa417fcd527ae91e69ea45f8b85fddd941ec049a597b54932f7eafb6281d472b9802230d47f1a4ead80372f68875e60005bd967062b44f6164fb3d6a201a33594ce037243005533946894678f0969e5223fe7f07147c2e249d58e39671ac56bc553a0a3ba3530e7311afe3e0da38fa4735c118a0ed8ec1a0b8186c4128dd9f26440fbedb1412e976ad51e55915bf4056c9f642c85942c7d1942c38c31175b86adbdb0b3213754ee015089dd53573597e5958cbbc4e60b60647174bd11ce6620fe9f51bac81d193daed36145e3b034930e02771a19a0c4434a088f3338796b25e3cb389a104b07a4c0f3d096f3476bd198407ed08ace09a645f30eee1ac53bfefb3f388f0ae00a54f2008087b95b6e4f176a8edb8c49623293b010196c4ffc4c7ef8cb0e9de83c7d4401b8649992bcc1b89eaf82c86eda14517bf7a5a1585f603f1f4681026c0b3260ce95fb88daf287ccea9a36394acc10b3984dc9a78f789d79355b65121df43a87646e806afb54cb642930e8b1c784c39a12a0ae7c417ab17decbe4cecb019f457a86cb896a5d062685bf112e01da507880620d60b81df79c053ed4e656c4c52e13d0beaccf2f30b44f91f447d9c395370cb1e6b9fa11e27424b0a66c4bc51841b0ff2024eeb093177a01816226662e85699d74c7200469e15d533a23d390754515715f603902cce8265fe4970c33cf8b25ccfc5b95d4b17eb778c355bd07d32b3386786f336b03bc2cecea2609559c0ba38d2d3ff7d79046bfb3c4f10f55844691c49fa4175c57df3b49b4a37d329e8642c52c8c19871c8deba48db52f8caff1b3a06cfc027f72045a13b85db6d1858c6b10cfc59a0d9fe9a8579f81a379d01d214c80a2770fd4185c4f22bfdfb64a709cc6a41cb639f3a91a3b3eddde0b5b0bffd59991fd5d34d1f11099c9aee4e60731b89ce799a1551651d4ae222469eb8459f13495ce90d652342222b78427e4c562488e328c437bffc65053a1bdd01eaaeafda8fc395dca9435cdbc6dbfb17ff391b2e304687ed336aeb09f0af4c5e2682a57ee75b8039f4d6e79b4b0c39bd04df84723a92d37941742e20f8083fcfc77939be849ff1a37c9c3fe14978323e0af9e62225f3f7453fbd7ec25590091ef055fda0d578ea3205482f253112fd07425621905b9f9469d69515e4c868296e41b8166d921850b79d4c2c52e83c34ef3dd3cb62805b93bb6ed5fca37b0deb0a624237804c12f4ee5a31512858a2b6ad958a01e216cb35b8f651ccbee816aacd558589076e8b7bd047ff1a6efcf00c6018220989036e9b7b5259752057dbd60bc5007123a871cb43c930c0e51cc9987810976382315c31511327bb016f2d7d5231406e810f41d52856945da800d159985f85dd069043080ef992a5341be64a51859a1afe21ed41c6b7497e9a367b75be6a725c11df1f2051fbb6f0d4a3fbebc46f633c75232ca0e117f5f6f3cd6b0e1fa9febb37b182a7fdf22068f7ad0f32889b95e06b48f8b7e7f5f6e0b04147a64cf3ebcc227df5b9883feed8960dec068c4e259fb1fda3ef81fc0813a26c188d3e495ce5394f073bd1a5d0822031491d7ea87c15321a4386cc2615e90616194092b82e63057c0104972b7641702a7eff811277b8f74da2449d323336b0400eb7812fadf98654fb63add759ca35937e4a541c6019f14a3ddfd429183759e40b180a75d007f22ae09bb8a638055343712ce5bc244fefe616c2a1ebe3527319e6d320cf73e7858638c678f249136896cf663a1557ce1fa82a06a51c7c252efe7721815d554e5b1982d1d39a66046283757cee56cb045725b49ae4a584dad68791041e4f10115fb5c8775bef0af5159496a1695541b5f645ccb43e93b160bff3215ffbac485e8bb11d028e373af25186707c684a2dff8d00dfc6ce3166905f5940979180a38c98cda9d4147aede7d82b83c89df1ca0be5351596990fb602b657b20416c5f3a016a5ea21ab4cda265738ac357f1244e5755f1bddf75552e858ee1beff11714b2bb98701b80d28c3f3177f346ff4dfb47c6116c3c5f7a87489fd3d3c14dc88bb622998cc6498d1d2b2d1fd7fbfd4c198a7927ae775c6923f2d382787aaabb1816402a697c4c49e303e5e37a742d3000893482f263091e93f55550eac408bd26759f0790ef3094cf7693d1bd143c21245ac802e98f597c9873279655db9565daed00e69973d41c796122965ea717b61249bc4dad0c011d34f4131a1f2b3dfcb4100ebe7a93c60548251a1e286bfc00f9bb130c7ee526080b50251b1d55d2e881f2395d02d45514bec96eacac4c8a64532163501302cae4c68e959edf45b76dfcaa7df1d537765049c30384bf3ac1f42f3683b080efb892c6c74a1a61fee027dd188acb990a08d90f52b6d04f68f048e9f92cbd4c0d85316513ba89e78fd2eb067e03416d9f7d2babf375a8acf181e7990e73f07f298802b0eee048a5fd13f809cc68022a05ae745630cc249e03330468f56fa1ddf1259837991668d6e5f97785bdfb627b2ecd7fd70fcdbc332ee493aa38ab8a30490b183eba4e35046755c42bd110f40d7266339020aea480d1da4f891fb160eafa2db2b79432a594640adb06d50677072dae3fd389e07abdbd36747d130b5c4f36c1f5654f0aae2f42747d5ad4c5f566d00aae87b22faecfb383713d92bd0b5c2f16e37a39dc5a703d94a38d1d8e777ddd2ebade88d1f54eec645cff7cceb8fe093bf45f9b0bae871b4261a233721aec30b8fe2e7f68695c8fe7a347d7cfcdc8d5b8fe6de79ad4bbcf2114263a23f775eab56f31703d5b7f6d3e77ea1b76555e10d6f2203dfb9c9ee6bd37f1e5a7abfff5dbc881f08617086824f71ac98d21ac0099daa555a66ca4d65aeb9c73d659679d39d02f6bce55ce55cef7de5be7af89daf4e3fddf3a07faa94fa77822bb24165d3efe699db1d8ede69ca6693af30a917ec678e8a7a9cf0de0481559e1857b8ee41e5961efdff106ee71b000123ec61b3ce8e92f70e867bf81431844fbb3071ae50febdccf9ec589414cf8d90b87bc0799eb4820152211ed8d733662239b8f3963228be460ff75d32ab79f77aa91da2c0076ef9ceed4a835d5d71d8348fdde2a8c5a21074a53ed4be065554e361a39d04ed32b15e45218989d5d56dfccb021b1c7c9491285bcf98a3184646faee09ec856abc597d518427a87c2ec0aa91279f10502dcab5e3586b08bb2903c2d6366d9217971176521719044cea08f08d741c9ead674b1aeeff1db7a732ef4902ba0a044057b7d215790f611bfbea9015a0242b62ef14de6ae13e358d8220658fd852166df0a5d19bcab8f975ecf9f1a4190a1e260a2bf5b82eb777038d1a185cc100537c649079d0c4f1cbcbdb82c7c195cb9c0edf0d3e1cc424b11a7c6d4157e42e5a4cc32ba8e92485eace942d251f53c91bc38b33de419b94a61aca01285e445163664bb36c582cc90bcf8a2876c59774bc1624242f222cc0eb2fdfd58de125f8e4b49149217db22c8d6bb58df8480f9d49c3c902bc8435548ee43903036865c415009921b71e6001761b6ea542861768716634a646b74b1be7949bd9eea53f244663826232fa931ad8187ac6166809a71659eba8c0c0c2ed6370448cab547328d8b91d8f7340021a8267ac4984b2257003c1b42ea78410076fb2987b36512d91e5d991944a6feee8f818b41a9779d833cb28a71aab5d6e9fbc1d0c8e6232641fa63e27a93cecf39bf198ad537331aba271261f211ab9919d8213714c9f281fbd663dffed8969fba00714dea81be4ddd0f0402811adc28be007df6f9bbc517a00ffa8d64d1d9bb3e457aa06f62f0ae4cf09ce871820a32bd6bc223e9a987935b1732f5363840a06f5b230b90d5fe57dc3e074770e307c7144765bffa7711db68b0e592e178d225e5f4ac5da06209da911b932a2782451c5d937ab76853f4006f48bd0650baff695b524f0b28d024676a48100cf151914591bab144a649d69438a488d11921d32b393a92927cce6a0421dbccedd83617d7e27bfcab36fe8a5e9358bdea3dd06c91fbf5f8b7d6b99880125f1b3f387eee35ff38a835b0d1871f2fa9ecfebb7b166bb1c73369dac1c5506bad3108ecb377c11fdc7ccc427ccc43114503e00dd90b9907c0c7ec626308bc8106adbffe67aebfdf0fdcaf436010c05d45ddbedd5eafec0fe8dfe36f8d694eaed91e08737c71d4633aa30672cdf6db5883d4df740928dd2910f73bd57eb73a3822fd18d0376eaed97ec8c274a206cd3ba4bc84ce9b459b6e20a3f6695104c577f8b99a163568d16f5e989fb3cf2c51fa8b93375dea695af4d463a59fa61e0683c039fbb7ff14df9c6049b706858c098909327b22bec155cc7c9ec8284a319502993dc6370a8806cd152599c49ec8ec597c630266d19a143ca70e0f64ce416d1ec65a07db352023b1aef12087201f53bd37c6183bf5ec658eba703ccb1271ecbc12da9f9fb5b1c3891ef310f46f96ed9d65896c67bb678947622be7fcfb47591c36fa59cffab4e9b3d1bf89ecde29ce670190fc2c0bd04f7d9a020720b88739a41e07a9acf84825969234b97043db295ac2454a15424f50aad0a890597b58ca34352662d829d5a3c712a69426c8377d267d8d4484f1141f4260496b70744e1a1084891d123bb81452382a994cbe40e6d5f8d8638c86c6b4842485244a847cfdc7744a3d407445763a9a486c63021c32c8c0c0a0f0889006d037b82ded6ecfe90614a92f244e3bd156e40991125f091f203faece945544f690d8a7391fc0f91ef885808210e40f9ac6c7471f7b0fb0d1c7be639fcaad40c964120bfb17dbbbdb0ff387a0df9e987dd73e6af8db8307623a7f3b319804ffe2320ca4f8dbed6fff62393a0bfc8bf3f66458a53913fc9bea08fd6d34e3ef250afe4d9ba088fe4da9b64f95cdbf53a68b7f5327d8df15b64fa36d7f07f78e4a269345c837a5fb3bcd4bd2f87bebf418fd9b8586947fefa60c75c1bf992a1369b17306a3f16f2edb3e47c9805b2ed3fd9df3debcf7b7d749a4bf75cedf5ae7d53d35fed542db6ba4185d7ae96fdd04f577067bb7db09a6c1bfba0cfab7d7d11b7f0bb796fb3bf998b6d90d5babcb123f2384e53976110a7409b2d39c6756e49c73aee0d28529059ff6cb09f541c8df680810a71c8b3738d5d4fc8dec2fa7586780c283900c49facbf2230eff720b769583969cd31403591cf6afbe8048ecbfff8f0e9a1ad558fe41c8bf0311a8040dc6df0ef7d0655f38f1b7fcebfdbd60509ce11b33b1f3b7adebba41966030e8b6c05f905d555ef33b74007eecf597c1b8033ff0bf2040ffec5f4864a2fad5dfea9d62bc7556598838e786b87d10f2cf7e4290040b933925bf75d95f04e91511fc6cc45d980966e4c2feb682de5cfddd2f1d7eb129f3d35f560219c9a6e90765d62aff6d8af7d67be7546bdc5936defac5bfe1ad8f80ff0ff0b3f85b969711f0ffc1f1f361fa31fc0b3fcbfcacc138e33f56fe565ddd5cdcfff8db9f9b8b361f6fe8f07191dfb66e2ecefcd66d81c01fbbba2ae7dc6d61c15b6faafdd20721b37c6c7ca9f92c5659b6f04dff8c8e85980b605e2e547099215290e94263d16348310dcbcb0168b2f11dc12f91d39636e090970bb607b8d7fbc74babb7174cca54fcf7f8cc693be47535ddc5ed99b2ea57555d55954db9067eef09c20f35fede4c7b0539ef26298ce8ae520e3f4d27a06fb6d1461f3db1ab2ccbfbb681423ffb6d50f434e7654e4ef8411c8dc2c0cb514f932618fe344db7cf46af895d6b9f72a87992e6d4488b673af89973ce7ff6af0be71cd7e41bad8d1edc68ffe26c4fec2ccbaebdab4422f77e1f8f78cf0317ab9e46a55963b5420c8275530c82e5479bcecf5bb6fd45f8d8eb2d3837c520f857ddfee57aeadb3558805f041442e1c79ea79e25480283087eb9ded246f835614800a67ea842876a30f56eea23a418c4faf19b15918eaebb2048e69038d8008c27057502c7d363b90489fbda7bdb79effd87d30c5485db2cc34e7fb7db161c0677fdd5500cf0faab819a403078d2df0d6d5ec5e0534d821f6f89596540d15f2cd75cca60a3bf5bd93c6ad15fad1462c24a7f798a268ca2081f2f9b28aa60cff251c959f36e74277decdb8bfeeabdcf5cd3d9ce1aeac74b2674afb14e09cd171958e981019854fa89b9f407f0f192cbedbf4820014eb49c27766aa5aa1ab9b3d58085918ad8d6df6d618ee5aebf6c06b822517f3771aebfecc74776c518ab18638c31d619638c719a3d5b3b0210d8bfa37eea5b0d74fabb53e3547ff7c720145fb01fc10b369260f9d8afbffd20b0ff8584fed888dd6f41ae3b002337926b1628fd657786b7fe6e0e827af6fa261be59a6ca4a3fdb1457f5d239c937f1b65547496070b90ff7aa52ff9b39128a7f2755df79aae352caf0a7efe396a9f68d5d234e56a4a33ce384d35f62f5adfa45c33fba7de00543ff5387d2ef5acd164329944eac04b1b62fff5b0647e0ca67331023b6ac508ec443fa436820548af609c66af7fee92c9d1dfb6938fb7dec7dbcac75edf74a8bbe48f2076466498ef4eea47d18f281d71298822113a8b6212a995301acbb2b29b2ba2e3844cdd3a60dfee125a543e57ce1b575d6d2282aa7ef8403d71f83051597295fd385908b97441213b6bdbc3bb824cbdd6421f0319a1a0f988bd0b507f37d8c77e26a9cb40e91d90f4772b3ff6c29cfe6ae5c75e835c7f798a8fbd87b7fe6eaa8f7d06293dea63bf9bdafabb8d3ef6febffd7b7d83f7e084c4b9452da5b140a6fea86f42a8533225ebab2c4d0959626789f2e7c69a8b2b360b00b9b165649582eb0532f51694f477037dec35cc75ec65eafabb733ece79a8ecdafb994ef440e7a22eeabd7cc4792789665dc6adbf9aee635505b085eb0364871a112454666c35f654a9ce9554a1ee52dda595b9b81bfb69d241939e92cabd3d1a6a1d0b208ece2eb146a054b0be4e0177949d957395fa6af71362db7e4c4531829092fd27c4c275f7c8cc49dde93f29608ece6601933ae7e250facff9f6abeb1b8833e95c49d5a104faf8215991d8a675a7a5a13ab567eddaeab4fe00c698fa1d01f27062d77ecebe42ad4bfa3ab5ea7496690398d6237cbcc432fb4df9b56895542220d5d24be957c483871b7c2362c5045089ab070f1d74a25f3f5e7aed78dae52e81dd1cabbafd6f57d7f78399df81d1c38facaa0a30abf39ff54b88e9e3c88a139814968f2c57860f2ef5c987332bee848f3c1c7d03f1bddeca688d3a57628574976a05a26c7fc25c145650aed6db8f97567a6c544ad8acfe4bb6f50a3bc0283d68c3adb3516bd8d56f46e74aa1ce36815a7d0d637ffa5ab622e96adaead57f4a78aea0a8517aff787945a56d7696c5cad3d9a5150b0baafdd1d7a9df92ca4a5dd1593936acaf1f2fb1ec5aa9fefb78892505a8a3efe40f4767ffdb5b2afdf75fac647774f5e3e50f18ffed9f55f9d61f8c342e965d7cff3d7ee098b2cdc7f9245ecd47f147a07ec9cbb2abebf7c29e3fd733f15256216deb7abaa1470bbade8e8ec5f5635be8fa28b4bbdead0d6470604f1aa95053cb5bfeba62326d55b5f055555555762b8598c879ba4a2372e2e24a8ea5233c24cf153e655f1d73c223c29ecd31b709b6d4f388202892b687b3e922d9b6aa95d6227e9e4ca1a490450ff1da11e492953f162e7e4a751dfaeb1ad5b597f35527a91222de6b9a0d51ebb1c4c6095cb60364c59415a4675d9218560f9e1c2a253c787c7c0da564930a0d233cda30af918418239251450b8fcbf973922494dcc1c3680b8cc4909caa925d690b7e74cc886a7a2b92e25382e22b8fac876f2fc78aafea2f4c62f9cc31c683e8ed6e6c0a5757934eb8c7ae40f9188a1aadd0445c9a634cca829a46564af8d041778346598c2a492467ee25837ac8504fc9247888ea6a386f40796d6d3931825326b19155e5cad9b48690223705698a532ba5341c3123a9aa6a8acd5c55c522904411bf0a1b7f5d7df7d2572c9af2afccb8236f483f5288a879691075a5141d02c3cb62a2a56061a65d65e862c2e0b1eb5aba7ac539fb39e79c8b50b1e7b21bada719e5042487847c89ff4452d5d1f13994831f764bbc1c4796d468b65055555555d589c46ece1cd2f40680165d3a38e79c73ce7908194e97e503c239e751fae4104786ac381f6e1fce394fa1e3b22a9c5a058f11ef31b222cae7dee59c0f87c3fd83e1852f07900e95d692a0b6383a232e3c2b396c435a290b9aba5a78354531d98b3bca18a231a5b4d493e39363f5f97175555555555555114aa2ece69ae1e4e9abdeed790da94d00337097978ee657b355cf4e459f216b0ce8af2eb992a0ba226a25f99c37935a9f73dff39bd5eeb2ca6ac96e7d5dd7352ad760e494ab5c83ffcb762b438055e4dcbc8c997c57d86595156b8a82f3486596374de268006183eaac81e0450fa0b13214534cae1f99e55f99cba5bfae5f51ecf814aacff9ce1a4388e305454721e76062e1560100414d1637d2bcd29804c0a90f8fca468c154026fcfcd5bb5b3c4c1138e79c730cd4889166250c2c4c6b03a72c1e153e685e5fc89ea0600f8d16ad446b39e7bcf905c49eb59c7d4ba2e333b60605fd7a52327d9e9c3ca1d4d85c3e4012be16c2133681ac2ebfbc4909b5cdf0d1f32a8183a9432fab8e0d2a88abc7aee591026ad109470c3da35b3b537850986ae8205f6291dd57d5222a5f2d56f57d2d1541029484d44170082bb908f888d2f10971f2e502f912bfd69a569b85cfd7849edc9c4f2c21231ac8052a2aae8a7c15f938510157d199dd93b1233a905c3dfa5c9d85471c191af6755dd7755d4b31e7c6c664c9c98c11594d781481564187348f20f5e6734ff8834b858ee61aa2e166b5ce91b4882b251794ca3a4588908985f4390f72ce57960b3fe79cf311543abfdf5a6497624b954bea08e79cb39d757b92260cb7cf391ffaddc3fed6097f5dd7750d51d5c4395bc43c3e96885e3083902ff107116309a2c6723169d38b4665fd406a7d83c9cf5a0f0ebd173c902f310a5d320a5f16989c9025a190a9962bb5073121412868a539d0d556b537b4f343a985138a243eccce52e40809922014b4d20203a102cecad1dd971e5e0917615128526cd1274c419c44087265820ff68e056bef868a3c262234b439235f2f54e080c252a2975676acba6ae5d94bbeb220168ea825b135253423a0bab3a58d3126cca5be255996cac5c2ab6b2abf9ddcdda5b1154941ba3253a737e3ac89ab68acd9f3424c4f4169585e31c16aa9a3d70ed357ed7252533a5634c5270849919c0847509aaacc50265bf4384a45404199507d2d53c78e8e8a1b6107c4acf2f3d5267346cfd1b7259859cb619941f267dfa61e5c76d2ac370002015e7d9d8109bd35d0d03b91e8fdf168638383c37a08b27907e19210c9c38fd1c11b32a071d5c605aaae1cc09cced5c373cd1f32f8d8973db313fbdb6054fa01357429609bc1a5aecd1f308099612ba0708335598dcd470cbc822c478ec10b16210d9d9c5ccb052d61c79039c4f5960baaf1d9126186edc0c3cc0cccf0d73b91e8fdf168638383b36d8c224890112af3114cc2d058a5f835b4a0e8c63f8b37bcc8b06082172af10623132858184af82cde50934721062b3bb0f170d43003e3c644f58d360a951efecb6af82f3bf35f16e6bfecf0bfacfb5f560b63a25a1813d5c298a816c644b530266a63f3f30641882e1780f5142e1560d4a050a152d770b052e1e5c52d1aed5fa84c396cb0e4a5c2c77e2b7b5883b94078f5d97c6cbb2086481ce219bc618394180addb5b1b14f85745fb4c0c3e4e3062445e3b06583d2887d8598688c5b162af58d3676ed5f0b463ffbd782d0cf167a54ff5ac8f939c6bf31793fc7d0099d2c2b540a9542256785ca97152a5f9cff0a95acae71512283f387081f7b3528d4822f54ce68cd5c68c9c7a70b9d3dcee01b16ebe21906617789430f739cb1d1e0a1039605d2530e58c062b6501913152a6784ca9ff3070e8e34182eccd0c8188b9ec6024d0c0d0dcd0534343034341368541a0b86126822b474210d4bd0a561571abe696858bc217b9a20fe38c862103883649a3f00b52e2c760627de3c68b965486b73411be2a188f3c968a34f0dc0c5ae830a3938cecd010642c70b426838c85d301699b139c2b041186b2c82c5bc0ced0b3890b130357abbc2065a2e5d0591431f850dda580b3cb00ddd7aea256cd8738b4bbbe55c1f53af8dd9983d0ef968ca3a345ad5da10584e9ce492203d494b50a20811538594ad2d39573c6b7a6c616cba6f5dd7755df594ba06b31b376310116a362faac90cbc2e27998da4b6271e54583672f0924d3c167fb5a2b73434ca51be7833ca5b10ef070645c91c6e4a36a4d5ba21d1cc993566371b95f64eec4322e744f5d9d2442e4719b122bc9c1e500ee92cb574b08010d9012472f583af2aa9e307c78e94aec2b99044da15129fe65ac63361148f5647e936c51a23eba8be32a12939e79c7b71ce3906241617f28b0b802a68e077e4b4cb5795428153ea523ad519e9d9925992e2fb7962f2a449f3d1c0f024774188c98b49c857e51c60a72407457464a52fc31b19d4d294269f1395a3f5a84918e20e4965459848aebf169330a1a3769900fa6aabaaaaaa8eb062c76e5555d5561c31eb510666a5fcbaab9b32b28516036e6bcadb0eb9e2109f996ab906bbd1fd4830f388bea5eb9ddff317c147a975b04910331532964cdd95a8f2993284c9d68c2515abf7d6755db78442d6301fba75dd54a99d9be4a77f743366b06956c45d57b424a111b9156a0763421d7659f9d14ba56875d947f753d323960c31c0079228a3a2a11d566a06585cd591f1e89194a3a53c81847832fbb0ebfca47efe2ae684ca6a3fcf7770a0e46226df089e456df0a2a3c1236585d21021695c4894a5c885d65752db1226407414eeda907811a5b560a4507012bc8eae2ca4772e3f4efd0b8a8a0892eb2774ac423f5bcb2899d6755d63ece91acc027f1540f2646c4d2bc60e2b1b08c264c95c185a5edc0d21d58c5e84bc1b55a8f4709ca81353a263a4026d48df1eb4452bab256f840a1745a07665307040493a5740b102857536f78bd21a6f35abc039e77a8b734e5c9f0ce51a8cf4a4a913b5d4a2d035a1b1e24a4e9c06aad47eb49892452cf45d920e61638808468b9efa82ba2d77185901079d6ad2493d7774884e008fa21a0ad152d02f55855d2b0f904ea6aaaaaa467f3f16362b39224ba743e77e7aebd0ee00492ca21cac2234641cc08a330c480aaea792a88e8bbf6abbcea13689264fee0ec958bb9c446118f668556f31a9467b739f73ce39e7219e20fd7e6a5d8bb44b1679d188cba1b4421504f912bf5af6d724123b1e70625d65c83a352bf75adbd2900acb27bdf3356489e47e4774c11954d8a191bb534582dee2d941b175cbef26137e552c1bf3ec4eb6c2cd820fa70dc4ea274c47af242f995ca35555b5695dd775ddee66dd2969694b533774ec9031f5465454c3a70c4a3b39ca3b4a167c411aab6e9ec8fba551cdbb1fba870f0ee116e57048c0ac3d34caaedafa33528b49e79a2ca8ea63432ddac674f2fad292cc8006d9d0ad552a8a17909113cf3c253aad6c9a58fe38e711deb20836e8baaeeb5af64d409901d1e36164eec7f53472050a2a055da6d22497d08152650a89cfd29914852a623f4f369660cb287433a36a314c68e0353bb5c5534e18d56c016da36c3c3726d175d5a50c767585b5e8b9b4078d0d78ae5af0883e25e72d1d98817579c56863526a3732aa2044694355b4dc2a46e61a896a9b5c96888d1e329e7cae98a0827c899fab458c15779f7bd7c934c4b682a7e9461cdf493134fa49434093ab05205c534878910512668134ec3555c08e98181224ce0af225fe3bde6421c753438f2d67c86c091c1d930cbb2a9e24455ea44559477a7c62e84c81295151f444d06feb2a952fc068d2a9881aca8dd71f52544a43d9124f4ea6b0a51c3c33921c4063aac80e8ed9c64246f2c794b103e3c64492fba92272aeb8cee22c51214c20ad3472189928c2c4bb71a285f0c4515491b32a2baec85b0cdd66021ae1869c067a0926d12339e7b254f657bff52fafeb5aa53ec9017f57cc9d1b3d6b1e50fab09a5cb4704271a00d8d4d2809f484a9e3f3d389a1a4074ed5c96189cac261f34f4caaaaaa3c3b538e4ca0b0cbe103f729d17252b239b7aaa235ba94c35ec249c76a0998912410d195ce24f2534287d511ac1e41ceb7156c48cfa825689126460f484bdb1b7bce4d9960aab15d6b9948a80f8f2e08b46a860a5ef8480b7ad1e42468089a508e42120a4cc5e9c1d5f0da49f204e36a4ccfca8d3a3eb51c620a2b254bef6b68d446799ec4817326e3685a8138541e50555535441fbb79ebe502c593ab98a7270b2d26fc8a00fd70b88e8435c3645c794282a82448663479d4fa984da6d993d5696c67dd7ea6a36b301faeab50c310664884c9c70d4d723e8449bec4786a8dc3451285dd13d8724995c4886965e588099cdc5aa5860548f41b7001508ce4780a515a3d594a325376f53cb15a8611b156a22cdabaa5e6b4e40ee088d2978625e3ccaa04d0195971369c9a74ccb827130ec85fa1ebbaaeee1913352df183ae07c555901d38a910e0008a33e265a4f404c9741a02c452e2ea33a91440aaaaaaaa17dc006cd090e961a28fabe9a96345280a05a6ba68482df17a426126a28993ab2d2eab7457744a358ab38d4bbd6ef0f9d42c15117d72ce5b968c2d79229bab73022d11c447550b2a4a446e43f4913d44a8aaaa1a426988ca57552e9d265f557559700d8ab1380312a1808263c9cb0595285328ae10617b63a103b9ae6b27fbe0a5e4a32637a5cf24073ef2a09c6e705154205fe25ff1038271058656d0d6d99214521b31a1dcfafaf12605047481246f506761513f448a4cf56611ae1b5da48f6baf62ed1131f68c3ef79b7331bf7952b1ad1d78673df0f87e6ca802f684892f965da8846c776639aa0cb248200020800083170000280c0c88235118a6599ad91e14800e43a85260542610c96381401486310c02311000010000200040210cc3801c8a74ee099c04023e740ba02d7477029d8106b027b3d2c49c1a08ef0c828d294f402640101423ce81da13b2e24dd0f7ac279f7660aeb7cc62cf4c1769484e3f435b0317489b959c4f3a65ac74feca770db9ec1e40de4e1e7c8ba1e05148dec5263105e540600e8316ffdbec659f3c04cc4b4184c3687e1b9100646f638608dffc4ac0e98196bc21c97ca1c8035de0907dcfcdf0ce192e3f0e439324e7f1ec54bfe88e76b5c4e99cb850793cb6ac3c719d9f48d0c78b405a4a56ca4d772927ca75f26a8ce2455ef3e2edf74591c87a19f05902ff5404be8cb991c51b5d281644e9f86769b1599123d504ba3daf8b3a153fb37763f60cba5a0a24cdebd29890040af1571a92737404b3a36eac8ef82921e49c4ef4df427004c48d643b5af78680de0e68f7008a0e0052a3029b7688c9258422c6392238bdfa8c4e9091dbb938600c10a647b40695a011e0bd9431b8e967cd7c0a256583583b37576d08ed9203e316c929784b213ae0d528382400b9970035576535095fd01caf711fd8403610288ab5994f5591de604a9dd8ef491892b83bf31964c91302ef064ef0d4269fe954e4903f27f8e3f070369223803ab40ff5429e156b953d2fce96b85c1352d2b1d550cf1b4a4398d29a60413f1593f5ba2febd8dfc8badbc517df90adc3f5a0c6dbb448f1d3d50ce15c41db4a15f11e38115e5534b636e93b211aedec2fd83030a153cf2f7fa9273bdc97118142074252115828fc65c5287e1d2bf97105ccf2e23667dbd23b9067913b89314dd35a77b2074cd38c57ec99f183a17d4d75ffb9436a4b3aea963a478a3b32bd08da87c66422c75ddc24331e38170232ba4c9a09e028e4e93063e033cb095b64742901131771798eb7ce16f25d7b57389b54428b1981e0b516787854f284357e4b0351609b34d0717f84a3a1e22a393308cfbec2d337f032095c3c9a99ddaca693bbb3c8295e127af5dfeefc43cca13a6ca28628a50ee5b20da45fe90a1020d8670bd750782d46a08135e98e3851863f898bf16d1ed7afdfb0b2b27126a5dc4dbe9c75a6704b449df76918cbce615fec354b342f9480cd10fd108c21be0f3df42f7d49069bc594e2ddc66ce37538539ec783af3f6543627683154fb445041cc03b987525eb7885539696e6e8306f0593c5e262d98d465d0576639617518225d34406d1d29624528ee3f99388592c58e1042035a0c4390c5f1c49ce04395f5612511d0271d6167fd7f92ff1c6cd4445705264a38c7bfb13eb58a79fc455a5cca6ab3ef207821f9014b58ee3e39716e76c4342b86e956a1dc3165e54dd5e982628bf6ed4859d579ee387032255588717465a0757dd46769e959415c0e9c9aa3350b06ea1c70a63a021e0f4a47a22319cda3b0505884e4b2a763846bd0c429be5199ff92bc0d5a78be9c9b599fe045897e6e53a145d65e989418c3378be13ddf909c3b40a40c041f80b79a53d32f2c8137b98bd4e48b3dd74004a086b6c7432e8d04d6e2a885f2fad0b02a070a680b8abf52d9daee51dacd9bc6970fb1719a2df53d7b4437970e65c95253afeb82e568ce7b1e761ff4fe4bd3b7a5f14e58c629ab3a22479a0714d05904101489eba24a0398d116c8a6d7bf7de68931014a42038c804114aa97ac3840edd9ea3448a389ef29bbadbe0d6895e3ad7c982838eac638620f8cdf41bec9ba2e2668c0ef72022ca1da33914fb5817eb8780794e3b069bdd289f10370fd63b90c38ad752ee6e1cda39b4b39320cbc5fd7e8636b3c292ed435dba68ca0567727701251d33f2bfeadc843737a7b1b339b7d75f58729297099a334b3697f41762dd3816ae5cc817affab829c6df686394fa6fe7fee0fc085d9828f8ce9dd0560c0cfeeb4ce0a7dca1919016b4a6178829b8f3d7658b1d3c33832b07fe001f5e147ff2441ff9f79ef53140b1c2c039d9fe091c33809c4d962481a2253c1b00f2f32b8d94130852c4aaa1156a2c93d21ffc6e5e3c77df90688d75ec2f320331f0b044b65c9901d419c7c7f73baafa180d6fbf9225c2a506e6a230919a5be9576e809afc03adc15d833976c70b713c21f51ddaaa659c53df6d842a84855ae61f2c7038937557cdc49f83cecea08ae288d0cae750a00c22832f7be3974b971c4814f570ea006f370227dc550d60d2ed35c4b8bf361b1f9411a53696ab70b7449f13721e2bbd3fe5f0f66caf628dfd28d1103be3e227608c7f378c2b0da0e66f8b5d8e8543bd31aac3f34c20eb6c6283644f9cc64a97392b5aaf77146882811c6944c8f1bc98d96108ce719a551236a24f30d7c8aec3dc6d503ccf322fff67a64d86432995c15e6ed6c8031b59a1d689ac9a0416cd1d51274a2742de84428e6465880fc3d0c91fe79157b7ba54f99c852adffd15ac30d08a4fbd98b3cfe4c57d5e43ddc8b00fbf1d1fd3ba13b3065e55d948da094018f2a509e9b68b86eddbe206940b4a93d96cb005913882491ea0c69510b78332e8d44fb304e3c7fd7686d340a1fcdf0c145fc4c0cea3f3239a660ab8405c83506bf5637bbcf7ea80cef058968c6ca4cea422f2a3df95e2b76c546e120a1b95f67a28c9ec3a4d0afd363ae28b86451d3a13383407ac0c647b48f4a855c3b53e3f8607e9614ca87288cc78a9ee4406b0e47f5bd05864fc33d94279221a753ca209d000f8d924d40b67f02346540c821631a1b9ec38f654bb35594ab0339bcf2a77508be235ec77a6eacd25629b36c94c377417b46530973d362f47b9ef6303c5a91430442c52fc6fc533fb52479a7c1ecbabbc334bee909b4ff901463c794bf4f689d1374424cf0562de6cb05994c655ce41117a519461a26050f24cc40596118d35e94fe12e81cba4afa000d8bfa26aeb8b07792b8ae14278294121c742809930969aac872aa977b2aa2092d4861e737a20cffd5a6338e79db4a949ea615cd4292c3226e9b4ffa8c086d2df4d4c43892c6826baa890c697fab37a0ed853e45bbcbd046bf94546b1f906a40eb3a8b5bf1f9453ac0fab3799d39c7c830859e7361fef2cc0e678b6c6b94a9ae50b67ae59695f0517ac55b60a2e5f1662a58603b0cb23fdb06df44dfde859e6b7556aef7732d2cece5ea31e0941409afc2509005774fcbbe08ad888c89c50fab922566651e4ab2b07664b7bfb1f057cba0049b419b6b4ea8e7cafebb8943393de443685924cc72425389a7e6a86bf5af3727521f8465c0c5d065ac4694a5ca1fc4dc06d999d1d14a72c97faa5e353ced3a00eafc7cd40be28e4cb2bd600c38a7568280a811b4ffade3d6c87dac54d9e7144c080e01e4dc3a066cc368052b1423be208bffe3bc701a12338eef7dc496f051f8a90aa413e1121d26f81165b62e8ab066bc8260d9ce5aa9938c190dae2c2f9f31d70665a20c60dfb41bb85254360f84a09d20f3f1c2e86fa34bf46c30c2c9b8d1bea8df76ec37744e9c230382988b6207e8962f22bbd84d25cf898017c51620c97c516f225cb54d0a13b9c610b70ec101747d5907546d9a2a2f46a12d8b411c77e43cc8c162540cfe91a6397b68fab1116b7c7c173314c3cd8af051fe6d5f1f3e0cb2cb6b91f4730417df311013a600646b35b322210d3f7e262ff9b635dfc587bf7ea17c839f66cd05164a2ea3bbf1c809ad3000c63fb3bc35b8ae4310372f18e07d05d4deef8c807fc59408635cc6322b2b911f72a76d4574968c20f5ac3e2729150e889739435497dfe004e5282a1344bbd0e47119fb671200b6ee08978ae7c0b1e3a0ef728d383f6a441ad5a64b3adfe60e22639defd82a8e6c57d87ff16429eddcce5fb0b4c263d179c4728166bf83ce7244e23a76a28c2bd98cb1bde56c001c5bc4ac32f3065ee1364f6511b69f0fd7d7982e0fd69139ac429d46b1a86fed3bdf6c403de2c41674656e9b8f694e77d92a503cc86c36f5bd9298bbff1c08a9ff49801643449a8f27dc87879fa96298f4556568463621aa1e829e7968e1417552f5f79e0bbc191480eb1ed1126ab5209b56b7c2f8a3de6e5c5938c7584a0f35477a1ed498d38bc377d20211d159209d861fc971a63c5f53342ce81eef79462ccdd12d802cd956cd1df881ef86493a82776585b8ea160a68e94171f79c13898fd843aa48c73ed4448e285a30e67866982c7fd2cc6e2130d2ae12dac7b1282c06f538bae2553ef4c4844dad9bc73f2374cb6e876c191804aec326e7f487ce59e9d90ac00b8cea24945dd05a83a89eb66ff25ceb483ea340db81d988337c45ff0206480d95b2e1c79d9d533acbcd0cac6073490f6373b5b5dba6406da5f4e04bd41fb10106a2f81973ff4b2a0bf4f9639e1c72ea02cde605bc5ef5fd7f6355b5aac77bfd7b4f7b6c2d3a443bd5c99d88b04e2a23d5cfaea32fda8ea7cd54ce4ce32d95f010ccb47fbc0dfbcdf51ece489d1214d767a2251c481f0f1955263b44032be5448b1dda3d70a923762a64422b54d2ddd3004aec9986cdb28697ebf0a7c504e692eff9792932a30b9f319e4f413a98d508f233e55c93d7d1179aa91081098a41e66c4da0cfd878516b1ade6cf41641eb4e75d0c3f3147153376180081588ccc7bb11b44e7f0b9ff99de76a9ab2d7211fde321a7c6d332165334d3c83f8d8876c63e7aac7e71ea299ad58a3569cdc31b72f0a30abe2ed31a5997b5b4369bac38d18823e0f121d91bfb1fb1d5c4a05c377971f333e207c6475bcc22ed2609435df72303e9a323c97d5683883bf4caddb32105afb2d111459dc602ad2e26e32e3269bd8cc01b2a33c972da6d14f497e303859c4e9775bf9e07f20da772f449fd8080cd1e4b1c87a416eba517d01beb062b0c8c02d35124724525f0774acb3e0086079fd922afee1ec3355187725d4ae9122506dcb8efe4cae810fe1d9015f1e26101fefda08c9d9cea1dce89f87349eef6a92841ecdc5d6054a7522a24d667ef96ddaafdad2461e39e9326c912ce6fd35198fee79e30e97efd4ceb455409b135bd0934e173a056ebc8515a623d8b149819758725c2e5e474fffcba471737f1c616d0f4be1d4c6fce5f4405cd4bb327bf9073282b0f515156c050eb81a43f59318e98aa3a9d13b681d4742e7f0e3dfdaaff0e8262c731b4ef3daaa34342d45c83d2cd4478a11315538cbecbf01dcb2348a69941f5a76597164efbdfcfbb7188d7c1a01e60ce453258918b2419ed295ec09c0dc8886d00c9057651c284314e26beb8dfadad2e15b5973b9b4a97f6866fdc6fb46930cd3c82cd6403429ce9758d12d2ebf780ab872a0e3f86fe49d3f26c7a21dbb17b2b8b5b6575d09538d33fa146a93faf3bfcabae8d6304bf67d23fbc3cf6ed2fa0ccd826be035e352b3e71241290e6f2e894b8519cabb38e35c2f110a2f2e3f5647c62c476bb78f935034751cf6e96acea5e2e6d914482bf6c7c27d66e3c7c0cb527edb8d476280ec1725fbca6187339104240a535901e0b7a5e95b2fd901923ceb19cb7e381b43aefcebfe0c3add476a00aebcc447b1b84ace25321d85f688e0ff50dd6b40dc2fa77ff281ecf52cb35735204a643a970e2babb69c19e0ea92a4b0427660ec632e46d3a24515cbfa87784b69b846f4e0be4ebbc11c9b3e49bc99a4e8b94ee48e859817797c695e82cde551c3901789a1b6c0cead10621c73ad2869d9568710ab109856113ac2e74b63a00cb1c31bb6614c52ba9652876c98a6e60385bb1cb5e4663477093a6c5e6777d9631a5381c6a9da5f528566277dd3a2f1d40402cadd2f7444d87047477a6ddf8a37034914f7899c832e861383813ab7053caa06bdeeed2873e7a5ae87d1625bc0f49eb24ebb6e776eb3c07f3196ea4f1495163a3bd702716e87bad465fa02477ce4000e92f16343ab18402daf8dbda70ad1aeafc05bc185b8af7089fa4e04e296ab93000acef50e6c52c2a25af13b5aa2414a9c8da4e5da0346825ef9dfc24b15fd6f9da04dfbab52daca8f681d0730df784d6b3ed0eb3b281626c966a8d9ca09667dfb5dd172bea0fd872fde9131d896277e24e81728d2c551e328c99728038bee1400af2114b9eef0134cd67a081b3c81295c763b5a87069db37cefa83893c86a4eced2c8b3b464351828e1562f5cd19f26f9099c7bacb415fc4245260abd7a83758a37208e305d20b6baabae7b1bfdd3d918db915185cc066f68ef3f037d790c61f2716487637f8e1bc465b7bdef133c191dbe6cfad55da8d0467e4268ddb5775a5015ba65556b6d2a03a7b1366f52da5a4ecdecfd7e11c5ea72a4f4c1c69359bb25bd0f2efc97bb4f77c87189bde59697965c62567f726d68b3bfca034abbeaf2f544e36f8051c41a1509e0c8ae69bcd01410266065b36a4b1462324b976a170010351175351cae0438877ce207e518b96d752c5d9aa81c20114b232a642c804653a9b14149574dd5742c5e2631fb987334404fa842044ac91130ce12dbd39377311e2964f32798d44155188c2720d480107643d7c0f41055008d228fc149426b8a4fbc414825c4cced3799c49c036b136236f9c67ad8cfbab537c4528b236ddabf457e54698cc81208f4ea0aeba1a4af172e0ed918c98ac045be0cd7cc2a1058b65c782ac117ab63db596e400948ce63f604ed2667805430fc00b5877a0f671ab3ad98149e2af8c8e173a31eebb7d7248a24e1895f0f689e8bd04a959e0ff11b0355c0460a6ede5b344f98d0610cb44a89ac1dbfa02aad4486fb371d2926b84d99afeee58f0a5e36ee104ec1374c0056e0f5709408dad78a613f336158e54c0685b875d8caefdf9e2409eb49dd742c7047402e5d74b2152be20007b9a80489fd2370306214825ce6b96c0127cb744eb7184dea121091de869c35f2f2600eacdaf20af049ef5cb7ae1f4b467023b9cdefb520b09fae1bd1009df78a272ca58ca6419303a803806650c57a79a998ee9cd50d6820bfa3047cc8c83b77414fcd865a8f2782c94ceb6d6c68f5449e4835f9ea7450221dd03263bdd266dd7b76362e0ebb0e8c894a89fe82eb259ba0bbbd0fce9fd8b3817353f938a9bcbf4b3f22bb3b64a4d7fe18d63ab9e41f20a0449d5441545a623c6f093222de8d455c60b70e39de811e9e90ba0b9e00b385abbd6b1ee597208d0eeb94b44c1061eceb528260ece507d41ebe087f08ec8a935ac443651bfef5deab2044d09f9bf74de4ca273abd7149198550fbbe5563ce471fc86fb7c13ebfc08e30ec454915aa777bb27907786ec86300316febc93fa07c86e64f21a78042cdcb2941bd85af3df1cc9d4eb0daef49f006347904925318621dc021a7f570d92abc99926db61cd434d00186b888f1263fc1f84e693587063363210b37550e0db9b72af461822a2ba80daadff3525148afeb5f1d1ae30c6b710ce90603284ad4433d65c47fe469431461a838dda6197688beda6155231bebc8d08b7f38d334dbb14ec44364142c54e06697680efefc177c12af44e6c775af32c200e68e0cfa14b0871e7ad7a121f45041e8fa63a198550175ada68521a640cbfe6cc0d1f5940ba262dca7ba188806705b9d9eded26905f1f62053b24a9b2b50d3c2f910806f9b2380c5d70a2c99b5a2c1a41805f788f9499f6c9412b88e9be32eb0ed0aa22e8080bdb45945ca22c5369b36a0acfd64cf33f4c2e54ea4a5dde32c5b9cdb3f2fe9a3f6f3b04be7587670ec6bed52740a15943c885b9cd2701db582af88560a7853861615221108793d878315fd49d00c47afbebc453807a316e5aff08417f3ba6141911fe323567036c37488296006d0ff943199c92e42fd7423845a6bffb80adf92a7856d490038e516e9c992933dd7fb80bdc4249725e40f16422fd38d4f9374adb927918166b21c74913eca3c74118e662160e8347da56b9fd6fe99dac63a96a0a01ae6a1e381a73bb124869710279ba323503676bcc1251e1e6703ec9e85c3635beb7bcdc045ee51c0e001181ae700d668647b6772ca0fc950bde1b962190cc935143a145fd755054bc1231edd493643126580b08a2fb2b781cdb8b9b954e31d1ce1c7ddf6c30b665a4685f187f814055395df50122db27e4ecc482f40bf1a58f5a8d5cdcbaca3be12b637b71e41037ad6a6ca7edbfca626d742e8891ad6d1c3c2c7e9a2c98349921753e78288e20def0576a1888ffd59fc6c4e57364c43020bb373d0445fe14d9322e23a1de58080f9632ceb57d1e0beda4898317d5db2d6641dc9af10da1e78c2751ba5b51b0aa7ee1496dc41fa1a268a481980aa251bde11cd56fa14ff24ced58ddbe33e54674333b69a4b2e23c9db94018dd14a61d055c4b683c155337ee6fc380db507178b5febed59a05be00c3ffd0148b69e2d69d8c99dd9acdafa878c2cd000370d09efceefab9f075ede159485d3536658ada15ad3ad11ee57f01c96709ee02b26accd699faa321889d175a6741a58020bd8a9cb7a63ed6ae125965ffc97057efad0e551107f492356cb8ab212e7d3084b295525c3a8692ce943171eba2855e50b981e9cb30ce8ab22b072f62df474aff1a5b44e928fea3cfddbc292dc91a996867a23a6e3cff947668978535062b33701771aa87aca7bb1b6e7280b90822f361366ceae45c504d8d30e2198193b01f5ebea5abd19d7d79c4dfc1b7369fcb14f1ae05f5ac14b0cbb3d8dd5add0f506bf0bd0d60bde5ab470a871c717046500a45f4fc885017434a2c42708306e6b7d14e92af65a11e257da280bb41fd154793c903b392a3ce12a9853c02c3b5cad3019d32d71093197da5b6653bb7dba90d9e27f62ff4ff58c790dbe8f8e8e68c90b260c670a2bc01b19cab53f94a91f0556d832f5a7454c5cd5d1e401feece89ea046c5fc2b20865c1b6a32d9000a286db83bf8ca4a35189f2b3c2af2b31aa24fe5d71f3506817ee551475c9cabe668fde3e28e1c91d910ea08163a3276bf5591fe294f9a36206d8ed6a123378bf7228a79c05b1448b286856025f091183f41507208abaebac280e6edf828429a1fe7e5a889c1930481571678b82e15ba1554f71a25151591aa8a0578f06e732a7f20982598c06cfd2958252b9025174ac986cfaf35c193c74dc3ba523cd0e72a4d36def94af62d42da754996a823ebd6aa22bbf47927c2a6e60a98144eb23797c0073d9e9ea5ac1ddc2d0a2838b8912e82242e77450a8b79aba01394dc4126e948bc808aff7c07c081ff28bf821fd19a660db88e2ac754b755dece8cd67330c6e0a1d634f2445d3f4b078ad614d960796ea12ccbf8d752a89e9777bfbeaab41fade4fa759601712182c4d10a2a9031ea36a57ac146b2272cba79857ab8a0e032ef2c0d52a8633304ce5905dacdf0a18bfea83e781e6ee1a1328103aeaa2645dd8a4016283cb1227bc5fc958ea68a874e90089218de4db70f60931d8a2c46c79f5bf04d217a766cef59f59c29f8c3ec3527cc80c71a2ea7f2c133e297d5b440fce28d7fe66393f9e0611a32203e194a8cfb2b19c16fbc024e58f9b8971a26b36074ab85c1053b686756eda4cae85874b9df112160f640571cc8341fc96c3c501c9fb1c280ffb0fa3ec36286916aedf61b3dae4b459a978e510127898157b3c86d209fa6d12f73c88ec06f07c8391c2f1879263d09c7ec6b2caec6f04edce1edd409e0dc594e730ac6776da81d4b054abc94b5beea8d751336caa5b636ab8e40d31176b2f2f85012efc47e20baeffca04c62decc2698e1dac14f3e0a58d7456e3d2db393efb2f6ae286c0bbe089cccbd0c7499d6a49345e7cdae5f466423a497c3c3d7212c68603d5213fd255be703fac3119d031f7bcb70eaa3278c7a3cec8e4cdff640d0a5212feb5d175a7aca6c3042e71ed4d0d219e6570a901226c1b0a3bc25de01886cfa482cf470879557fd06700c1f3d4e90b0f8984eea3d9127d6f9e7d5f783068d7108296329b04914a537eb6f428c8b599e19387f919c79f0f8e01661eed53fa799f75dc99bfe64d5908b689bb3daa03bdc5f1dab4b9baf80d0d22ede36b4be93e1e724d56cb0a185045484bd0a351d80e4e400c40195d287a5bc368b1eea1622f43836d4029f31ca23ce27567b4d82605da006220026456352a1304663036517514eff8d336a3530f25bd5a3a532ad61c32ba8639eaa93e6eaef8973ef3b20583c9dec9bbbe4cac2379095b36c79d55f964f7c1a2928e9982fb5dfd21d5ddc649e55d79323889609ce3ab9892269de9a0c922782c7b13d032f406202b124b2d0a1bd694169f1971725787a658713f1d5d0c4d52df66a5f0901af0603f49013c4b1cf69e4c8db9c06cfc0664b3bd01812d76f10a98f0c144525b11784c9cf9a0cd243606fbc68ef5f5a285da380460d8f4787bcd9cd82df8f539cc574356c4b1ebefda246e3425d58b29872ba4c9ba1a0b602c16d63a75cb84a88605a05f5cf3a9b21119596031a4e24bc7e33021295ba6c022222f31a0d48646c3cddf27be23a9c89c994d03021d0016372dd6998b260a636ba4ac6eb1ef2fd6329da4ddef69ab42c3c5de12f2a5a35c801dceaa38ac8a18065c49f7dafd29bc1e2d49441c54f4fc0258249c250eed3acee1958b8abeee64573738e96d0b7b69c2a7c0fcc89bb69538277c9ec76c7c723c7cf1c90075f3578a25e20bfc4c27593ce4687554189806accbfe8669dcaafa871072ac84d3918dfee84b878a79aef7ee9210c565d62ad48a0e4dafae0136abbdf4a48b083d82e9ab4245e1136eb026cc1e9e784dad0933340b75ee4579fe94432fd659107acae17337454e1488ef103defd2eb4ca4e6ff9d6165eba53d3c48415fe2c6a2d5d507fa53beded45dc65585a74ef4b6451a051d8cad7d865192064d289743083a224bff64702fcc323ad716257328b172861b9ec86e1eaa20f212e31def9be0e79757ed75609add853775890b62b3f89f8ac55a7b254462e26bd293b00549f7c5c4fb360cad29da9d2ba39b6bef041bcb224cb923638f7a9bcb0048225382bdda6d4fa8fd0238e6ba2a7561d273b12aa770d5b930c7e7ea549524bcc899af2aa243232df7608e712ff6ec7121645272618d67bf99ab7f26f2459e1e017fa96c46c4669ddbe6153e72d56d4f6a4bfa81b7b20954f9f1fcdfd588e5bc75ecd35c70398ddc5fa0352041d48dba7580d711fcc9024cd1d9db7a8dc997ab2a054fab250fbdc115bafb5f43a10beb16657dd69efec0d760a3dc82656bc361aaacbfb7c5325bd078eb09d7bacbbba8f472dff24999f73ef32d5f8f61b67ef0fd000bc525f38d22f2571a7780bb7c37d5781d65204a8757e9b6ec05f95aecc5abc9beaf948af45909a9a3858be4e5e67f198b995a03cce8163f55708856ffa4f64cb19f698d1adcd7b315831bc9ba17bd7a4ca5801b982e679b905026b4dda98fcf8f657a44e5fff7ad33b6a985d6c96a6f0c25aea32200ea1722eb116cda658a1f5a3e325b4fc65c08984b886f5d822d60b3bb17dfe97309842c97c6d5f388ee7f8c9b2768a997855c73bfc1b3f570447a9536339fd2ee17946e6bef6d430989a04125232a5ff3b7ffc648953122a1d64d7c0964263e26845c36080b7b8a5c8bc9ef1ed30b0d2992100e34530c690459381c67dd0504a2abded4c39703796568179fed1d35f8e1ec067a61a7f58ec0c69d172795fc2259fba7da4509503a0fc83b07f96c7b8b564b5d9505a152eef1ccb9f1e9ac56a4e2935306cdce0d0e18ea00fe86b952f5cf262111cf2412b5e430a27fc63722544f32147dfdc8b4c1144db3d82fe6abf7384bdd829599c3db011690420ea4d947b602593907a0761cb886c3f4b232790aaa274d18205f9a4a2ebe7ebea660715320a126d1a78244ec9c8b486b9d73209bd863fefa91ea32b85a9650ba3347ef040bedc277e6bc5a63fcea24df3d6c96fbe613da1ad6cf77765d162cda83a989820b18a39435ac11190a04fbf683294fe06fe7b450daa622d8180e67e7d7ee09f52d2754e2866f091488560b7b404eefa9b32da9f23b3e7d151b22a01832363623100f7d043f2a0ac418d84dc0a7cd450622ddca11f9bfba739309b1ca97c935badd37fa7c1de84ab752dcdda39a37cef2c8c9c625b1481ba7d5666526ffbaf08344cc43a26273b4a92bd1392af9bc42cd14efd58dc91d6cc6f5514dff15c5a2a93bbe59cd003a4bb0c267b8a11e1ed8561f16cf1f94057296f811501c6707e690afa07b61a3201210a7afcd0db04777338635c84679e262169563dc14bcfc4b7205731e98287b1d0053df4a54a1b94c3e5abc5adb881a61e8296aaed357feeab82a44ac596f521b06e8fb52425528672e08e9bd9cc1aa8f534106335b15687b853fc15cad6cfe6271379f952f41059773330e8b1884439ea519ea0323007bcde51d36b70b94c910f1705277d2acd16ec81cf3717d247d842a48929df91ddf3c925ec5fcd57555d0af1c22e787dffc7ff6f0235c4ec496e9d4c859d88911bf8c1322f5c29aa3ce851d3ad7d850437c16fd65747e2fed6285aa68519b9bd3d070e3dc48ebb4bbd11faf191bac8501789088f27541bb7958d004775f5f6e4b2f507082915b0812fb71a8342f5332daf767033a75d3e10a7c8939c77266757c8f22fb33903658e03833f5cd51fac21b51ba1429d32a4d1f1aaa9d28d54c3ad5b5172e06b150312d3a8efb5b228877a30f7f76a965f28f39ba8f1472a14c4d0b9a6fbd0bc4226c7f864da90e51ef1a285bb0de66b30bdee34e89021b905ab48d26590fa6fa532215779c5a45710596e9f09feae82796c505ecc3c30b6f8fdfbc73ee568c01d201a2a2384ac2a35fb32c5696f97ae08cbf5e77a0fa1a5dafab6950649990555e33159e25a5739ea086b324d39eec76f24ad4c6068c684feebc15e2791df19521b02cdd59408e59e8a35956d063a295bbb200c448d6371ec41777a80cda9874dd4c74e9dd01dda875a53789f78733852936c33f689be47692b805b8698241fea8c4d00abb14150a212a2b687a4c0ad46a8efc4880a54fbb8480a9f06dd6dbb01cefdabe1e508b1e3b898451225c49819cbce815c42451f83e503784e83d678ab777214661d26a279927acfc9988d2e18fd95d1aee8c573d417ae262a20f2327ca127968489ee581818e493f93903d7c7112af476c1d48f673359deb96288b0642d841f8ae88406b2a2e05b8de44733fe56125e26cdeb9550e2f5089b64a7be809032684304d85c6aa6bd0a19511cac79a0a5d96ce006c0c044f16dff59fc8e19f5cf559f0248d031c0b5e47a499a41fc8ce33b131b78500cb9358ba7b5de3af38955ec3cea2836887ed673732ac16f007d13568d9e9332e75b0913185a6f4ad48d0aa4814b60df013eae193509cd60fa783426785b4255e98d7e928cd8b41e189fd5f2c4bb8cc7b497ec286ebb8f7832274086bdeea8d66990ae9de9cee2b3e0769ff8762ff32796bc96b2fcd41a2f5a49068684fd530bb0a08b428b11c4e3addabdfd5b258a04bfe51eb2336a9f3030ae47714bcac26c20480d97915a705673c46de58ce8783a6a4c38510d6bab4957e434ec350ecc1763710e8be460351c4049bfe876e9a9408515f8347c8e39351f5575e00e13ed4f893925c65064fe260710066dc1bf020f74597a1e2de87648896db04bb969a5a9fcfbaa9b62cf8879bc56c9a30d96ba7950fc940ec9914260291eac414a0d5b8b5176a7ae2efaf7be17f065649ef6823c8a6cd5233515bb4c474d3ec4add7cd42c22870f5537a190a660328b13bd83f93ebc9f379b25c3f743a5cfd557930d45e03f8f426833a85c36cd5e2cd37dbab582d08b8b8f28648310b4b47c084128f4e2f30168b27485813f69798482ceb9e08c2a62511a88cecd8aeecc1b06115b2d1293d609319e511a99ec607fa256a2c9206114075d82b3f10a2ba4c0dbbb24c95a415e986c383b5a608f15494222a8550e5656fd36c2fdc69822469e154bc4a565d5c39a3f3868b89f0cda1172982e1784144651a7439ae32a3ba980def3d86d57452f112484a6c860cfc147de5d2932923792fc19bd464b0ae89021d9b8c9c1b95381e8d2f35c8ec5a53c82c60573f93f64cf51957f029f3d99ff94f7393a1225e482082ea1cfeef3aae9938a23ded96953eee4f3868c33f06ff8813c495158a6b5385c9999e06a9d391ca392bd20e1e88d689a634491a7151956064cd7cee4bf9ecefe6f8f5648ae7cec2278ae76ad6a1492fc222e0077412459d18d6533f9281970cc220b150ca0e6547ac85cdc133296c35e624d114a8f1f44d9ff47905e1164aa6fb54b2827dc9d40b18b84d0b4dd9a56cd6bd0f012f462a9f76e78f16a29840db216d268314088496a4f4890885423c88224d47cccfc7e43a7317a5b2abae7d603bdffe232fbfbfef23c24d6a9143203628537a75123e908011a53239e1942189434342882eaa8165e753cbfee86c63208d7eae1003a2ac323154dd735ac83391fe2aed361bfe591227ce8629cdf2b0722df0b00afe8df8ad7098e31438dd3e7b9a6169302a3e728b62273abb8d202d690f8cb2900d9e59f85588ed4294e1e32e98479f14138be9b61ded19e57ceca597e67735619dc0550e6fef43d2b9354f99ab88c0a6ff93daa29731247a9474b08ac7be663f74b7725fc352a7ed757e8c9d1f867fdc248aa3952eddc547318ed2d6449587ffadd8bb2fa42d7875ddcd3750f120f0cd43e0d6a85472a7504083a310b2f6e9c65af60d66bcb7ab17ab2edacda3a2ed8c405b30c8591845baf43f01ffb10f9f7bd745c3ed8aa7ee140232bdbe48fc4af335413d93e649b14963c532de96c03e1a79dad415ca8907eefcefa7ab15740104b78b538ec5bb2243b7ed26b9889f08e535492d1aaf5aa7b2a90baca078fcb84557a539edf5e54c7141e0312736237c151653897ee481fce1f548e5e580879377abecc18c882697a5775c0e914b55e9f7b9e6f75b46dca64070d75e83a31a83e11b5bef63a0fd59e81b56cc80038ffa18747ce862cc3e532953196d233642f0f296f0574d83b4b4ae39404321df1f47325e84f7444ce86b222dd1da34c1b2a6fe83e48508f58a3f5e14675be470a2deb4db01553c84688bd904b4e74ed59b33f8bfa9ca6c267080286c306720f3c1f3e92288b3faaf171af1701b323e28565e26df74a42301a8be568dc0d0166a47704fd78f6d5eb64b85a0655d6f1cdfed809f56ff91496fe3d19fba1195f1d05d2e15d09af310f5c849c9f4c06c2d42c84c9f0f3f08ee073007bf370b13e1b432f3884e55a254c980b06f0ffc1545414b907c3ba80c1adc2305dc4d13ee8c51c509807630e695a02bac5093d13d69757fb99f8dd7c48ef108d550f6e757c6bf67b28be7f518c1b201334712a85aec3471e08fbca221372186c7945f0561d1a32f6e3d34b5dd4e79f9e9d52607d24b6b0833589c7c2087178b226224642908c4691b3da4efeb56b16c586154e5a1a95ea1958c0da2ad59a77349eafbd1c317e280a43f35eb07186eb86024c31ddcd162e364f373b8cfa0136538fc436149166b8bd504805dd7967840063871072f8f258301e4f3cd6fd3b8c9588fee3fbb91c008d27c536c9cb7052cd26fdf018274452e490d7149446fe9a58ccdf39d49cc2a16756370bda5cd3d9c3e0228c2e17c948dc5f49b3d81f1ffdc159985c45d7debc7970f1a00fb20073ce364333167dcce5f3a8c2ee5a1d7e94e69798d47851a7e5b197c32ad35be097168be7b946fe03d30b3990abf889c101e17055d8c6374809bf16b45a0f7678729dcc5ee905004cfd4d860c9a954d4f20a53a8959f8b3c44866b858e6c5881386b94a53ef33b432ce82955a355ea0b36b329ae2e731ed6cd5f9cd74973fe4bcc76fe94a542d006e0ffc993bf76c49ca199ab855e2c450e9b5a88090645fb5cb5ee130a3e82ff523266ea88267a8249a933f025f010b0780ea7f4bbae8ac62fa6eb94455879b3436674c2a0dc69962c71de2f0e5c742ec903d73d15e312880cd03da7890b7c68c2e6395011c48c70e07fa2193230d3a565f663f18caa574625fe8a7e3a584133e04cfd1dbf49b4a1dfe6666f3348e0172d852f97c66583b1ab8fdf9a38f1297eb0d393ba52bfa0107f7f58a59524acb4cb240f4d3f5c8393946e94e0402e2c4b92593b2fbaa0a427cb56b56d10af30d6f094eed88f727ea13249d0465711664810a60e01a5e7d62a6d7baa0e465e157a7d82f46c650ab85ea5b11242dad7a674e871aaaa0983530700a6356b7fa636404109292e6db063900df1d2d308de276ce8516db2c8602305c587f8f89678127e7f8be4e01f8a1b09217b958d9d6b6035a25bc5579d090d4c62516f0b66f39888909b325b9213c3dd113b917617c5aa81f0d68e47251dbfcc0021bbbb910686861063e9469d3ec11ea5cd27fe82750129a30c2cf74e48e96f3313ba9a95a9374be37a95edaff2c4ba3fb7ebf1022f734f5b5f4830b292a748ca41266e9ab0e90cb4ea4c0fee740bb44b9f27772115cd7398b3e10c52bd8c392ff15f5677ad1ded937d9be5d4118323a9b7549498c585ed84c7979db0d4657235afd23b412754c3f4d1b15c50a7ef978318dd64945db8a194b73115fa7e6df7866954600b279d588b771e0dc68358a03bc5460130211ee2ed69c92f23e9c3e0488e3d11dc52d5ecb23687a8a6cca36e9b3503c1266bf8d42189a2f9c84dc14078544f86873ed86c3c0d31cb233cfa80e6a54ec32399d33fbfb7b9aceb7bf0ddc94bf80556dccd23b423ea33d62e31a429879699de2c61eb4074b3332207653a9a8804329e150890987fe373fd0e01b2de141483fdf1a1e3e11bf0147e7a87fa0df77035fa8eddc524d3a451ea013345a02a055c406ad1a324634cf1486218218bd4b3c4886f49c15a8402e1c1a5a0a2a6f67d46c9ce42317c6f3f1ea7c08b552862a149d9d5917ca64ff13c66e85704fe6de7e07932fe6d4fa6d58c8cfcb00c52b9cea4c73334730420e05001d7e4ccccec47dcb10baf928765d17ab1c4a806a0450cc3b194cd391c739bc4bc0f19782f47f29622cd9f95b2da1e3ad8a0052bf4f0013cc3cb99eb21716007abb27327ac665889f3eb124692c802c64e9f10f293d9c620fe01e4af983183dbd55817d2cdf7ec5137dcc115939b3ccac642f0a0af5b4f41e02e3b4c419196464da4142b1193f057d59d13750742d1436be8053882651abe9d8ba68f3149330c0482e743dcb23972de8644a83d736cc15f99f8da99f2379d8648f2b53c719610178479f0bf0c5829e67e22350d153bd7b1c1e6e6cfb6d4a5b81cf3cf7492ddb9ab11ad0ed3aa8b6adc4bebc7608eb3cb79f4c03cc36012ac6602d546010a9e9e73900081921e3ffa53201989880240a83e913ca1520fcbb611a191018a10010fe6fdb4040a40cde4efc41b22b6db85186bf68179d6e8752f887e4c1741f87a6ea27b4c9a6a5399fe786b69449eeed16bb02ca02c102dd0ac43e19cad8ec2c84c645a1870eec8cf6303a1083ddc09509c1b94a99db9b32aa43dcc5c163508fe958120799fbffeff4ca2036f06dc419fe908233498ef1586fdb9c7327eb3f39feff4f6245b8a66f9c73cd763ceecb0fd5b5edb787ecbd7739aba3c3d721f1e5165f7b1761dd5e03645459708c2d57cf2aafb4caab4b85eb6c39c42cdac004526cb76d9ebbd1db916bf6aecbcd1ecb4fc922b1f42d2746ca21ac24675eb2ca1c12265a615c5e30b9c85b0877a9e33669e306f4ad3fe79c732d65105b9386ea8e3e5c3fb032c8f3b8b2138020dfe8de7bef1fdb6b13291226e53d1d0ad31eaa6a6134e2342c8714bddd73f7ef8099af0861f7250966ed71def1ff0f04bedd649c35d5e8e4e008811e19f16c108ded2022e79c97dd80315039a35fa127e0af8594a69ac95368269479ea64a4db846b7b18690fe31130ba9c467db84c8d21c5cd18584fd410262b7138c46067101b4d3dfa74fe5984907db933173b3e23a611933555935adb1ba92710ca9fe7746d49f853b54adbd8263ef07f7e95438642e9f0b89f1a75e9ff316b650f261f31da023f5051b26f18f2b9245a7174220a7f4abbe4f8ff3f223d92a1fab21225529631a73334556a16553581c26cc6a9c6db24ff7c3be28476816b350ecd7059512602d712ca8aee11699232f46ab8edcbfeff7f97b76ff3f6857ea8e9ffff8f857268f0dcedbd77edd10c62a3754bf557a7627979948bf053b7280cecc54f082a414c35c1b29619bbc48ce51c4b002b4e55071373185106b18d842169dd9887109fa3e98e73ce39a777103a161344554336030dca9345b80c1b9b41b4e3bbc769b6cd8d9919a3222388cbbb13e56524a55d6a614c01258b7049ebc6bcb69b1ebadcccd63a50d57e775d0e216689db1d17a1414e6bbab802d69c680731b187cc14033119003cefd5439785fb769fdc35b757881d4b7bdb7bef3da4b8fd97b97137f4de67310d02325b6606df6e26fea3fedd01e630ff7e02873c85b3c78d7ab8e3bf9b9ae8facf4310bfb6e69dbc8a1d7bef3d3b83d87aef58c994df1dd93b27ebf36df39a0878d487563e549a456840be8d4f9d8c7bef5d0c4a06b18959d1ba11c084a25f51d3a8b037ab41f245d3c422b48d244e5b9a003e4203db09f0d5789dc8c8de7bef50539b29ec889dda2316d39a8fea8b2f9518261d6db530f1ba136bd79d5db862d5f99493e7979f9517740cebf312ad951e108027ab864c926525280684b5074326c191738e3678cc938b050ca5ac284b48d88576672bec2df1ac31383b05ceab08956b35c0aa3e9717469f1d1f4277fca3c6e96a0d4f88905036b1f7843153c29d15e61496d2ecc5d6d50f698b6cc7374c9c150014d90ef3290a9545a521b9ebcb27d3891331c0fd32361ca48c5dde7e78d472d2ba3c5fdf162e3125358e8c50c5330e54a7148eedb26dd8bfd6563612407ceb8170467e323bfc8067189aaaa185c74a5a48cd2cbc20665031a5d4955552bb74768b4c0405f667a1e26114d9126e1b53b42b8a880a4a43c760a6901595d2ba596162c3db2c25b2bbde37c400e7743d8654ddf49a89b819d8ff3fd8f676fbfff898e48a19e09c734e026b7bbbd5b0e09e852d558f5c5be38e57b73adba79427211ca6a5bd92a4ceb30f0aa1f12834b4b17d9a6ca93009e37d9faae8f2d8c3e34c5c63f9ffdf849d416c34ff4de227bb9535d4945c4e665a0cd611ab066b46f70575cce7c016763022e75bade01514ab157a8145b9d07a368f532654aead625b0983f43e80627cc5bf7c414e602b54ca70172b065153ce958a8d828673ce7d7a223e401deeffff6b5aaae685ac71e2fac698923afd7e4968bf1fefa75ad0ddefa00d4c83da04c3d0b07d999e55777d58c27559b39a32b9bc9d325bd93483804ca8c05793a0aaa26b0a9f13c1b59f7d62c48c89183ee215f68584a798cc928f876c660c66a3c8f5128e11c55a124e540821a18bd9114c8f0e2f5cc9434657c60c1c95d763d93a02be110bfdffff0067101b3d74d3c1d351d5e2c805cb128a198322ad52e33e19317572c8af19d3c06d6ed79bae434e96ba5967c7246400516002b14133195e972951c95fa5d810c3ca6ab1120dd93037ceffff9f00630cec598442339a1ad282b1e29712a55a3ed22db794bbdd76cd7228887c6989c27281a9c9f21aac7ad815541c8a028b12db4a9e69950601d9c939e755d9db4dd6729d386bd12a22cedab0b98e30232f2c49eb6afaffff49e862cc831c61053e777c4c3e2ad73e62b0052c33265634dfa9aa0b788aaa50907c952cf78dadd9b19447ca809cf3ad98012e5895d70b48a5528267870b2b48050a74db1c6d59c8ace58e4fb372f8ffffff8767101bdd3fc7c3144096c65664c75c25d4a648b52e1dae947afe79928cdd7fee2c443c837b39feff85353df59afd8fc5b19a92e6cfaa1931deb65c69447befbd932d9f47d6d4e59a20e4162386cb598b73edd4d1da4b3245d4c98385d3a43ed14adc48ede4d39d73cebb5a64109b31a9e3c3a293e89af6be819754ada3699aa6b91c7665629242caa2c1f4fc14457f9e485ecc5c5df5ea55b763d7e8bec606586fcd8aab24d85480d6040a78689d91c6e352e78e6f9aae02acab37f1f725e2909fbdd97bdf39b9a6f73322626e06295f309c911f624b4f4e463b62f74f4b5da5b0fc4b9072369580cda445b5e677e924054544eab055fca8348a385ed99573126f0357dfa49ea41419a813e03e49015123a160c9d9aa2ad1b4914fb34c2e698ea7ab6d04092aedbd07771d00b294183516a592e9c609a8c6d7e3ee9c18410d6b2899dc9ef9a02faf51f6850c3c2bf9008148624316651b77dcf85ebfe69a03ec3af7a8ebad8bec2ebbcd2eb47bd4bd76b174efbd772f052ac239acc63786ea6a4739add8a7c79504388a852899bd840b2410bc4086e9dc1aa893e7e66e6645a2c81ac686507a3e1e27a4ee620015487e6a30365c9dcb9d44d23378dbc07ac789b1c99a39063d535690667f325a4889cb4d1403151dedf8c7e6263439498dbe5fca359907eac66946486d6a0f7d301cca3f1e50b25a572b8b5636e243a3ac820cca6bdc2a3962a5a39b16d5b0e1bffed8311e5f40dc83b33db66ac61cac9f55b59ed2c692552a74f94cf4d0444956783b4c670b6de4703ee122205c3316116efbffd756b14c0e6a29a2652b77756ac051aa649520d9b9f628739e5cd0823c36f416838c802aa55d79aaa1a9b2e2f42b533712925c67dd24199e951df47151194f55c7a0bce22ace39e7f4ae8ac3ce5e7d4111aa85f4ff9fade9ffff3fe1b0d9889f6b2b4e06588ac150dcfcffbfac6914a654890c16d78e782b7cd8852d77d895530f56f14c39c3db405fd6c4950d6d6812819c73ce390f7981d2bb82a8710acda854f185016fc0a4d5625adc9cf00b116aff6485676f4701c21def92531a48e2b2114bef61562e6e0da085227befdc2662d48c6aeafd0049a95c6d4970005391cbd912571a0c6ffb346baccbb319af700827b0693b5098b52523a8519480fd9c91b82d18284e843a69e1133fc3ef0ae32bcd9835b1c3fb2504db199440d8fcff9760f7ffff5ffdffffdf374321e218ca6138b7f49fdb5b7ac300ddd2a19ab24123d20c954bb26030480310ba6d4942a22e69a8d6d5d4241fde7be75b9cc80f392f6531a1d8762ad6dd787a9de687dd12e7a39b7d2f21b8ced68308b7a3b46137e32dd2ffd3ffff5c2adc1c45af9245d48428fc43031bd3dee781856f761b85a1d104b35770dd9cd3f9ffffff08145bd32b33b83ac5a636f4927191c8bc6a5ec8b2524a757550bc78704ec0790538cb0f04c3fca3eef9e63f6e9ea5e49aeebd6efa62e8103b1fc0326b7140b32682c7e683e27c6a1c34cfa917369a345b74578a26bd37450553b44d45dfbdc39c6d337f8a82d83a5539b96f9501760e876d03608e76ddd3c652eff1776fef018d21a8d48f767d3300c403b318080400c44018c9034d11e50314800315b488ac843438303c3c240e06c36040101006080001200008060340814020082687c88a81d9004b7892e68b6ff32ca233a401574e9cbdb25ac87aac7a9c21a03282d12944e83a9fc24a51229be433fce0d89646b189f012a5a2f16c622ced673669784eec24c0b1b6bde025317cf510454dcaaae98b684312a9dd408b9448143ca452b3c0d2f4c66ce153d40b5b1c328c1544349f36478e889c4f550b351d0b1d0506b1a2a0a23b5bf079f7294e80ab1375bff77fead98d6fbbd32785fb34c1c88cb1d7832b8586bced0411dd7c0cca60a1c6e007e8a5a22e5a6326973b47854815fd6a5de5202ffc56c363f431f6ab1ce4895a02312848138f9362e39ccda71b15bee048c3a8130b3c73c5d84656ebec0431ad06dc409fca8aba16e5ce3c68dd0466ff8d747c1c2f243b9b423401a6e5290728d174f7d1a1bebd90a06fe82816f2dd31a5be458573851e407c60da24a33a3921cfd8d621fd7efb74b1fbfb2f6a2f092f0b2229e14831a9d21d242f8b4805b17e2767a9de813c8d1c4910c5692f9fe23a189f24a22a913bd8837d59ba9f8f9b97e5b955bf9c9734442ea94c84a79b9045b8d82da8a0dd2d820905a278f718bf2c3f0a985df55480699d0401a021b34ba93e84f7c06ab3faba66f71835b81cf6b2b30ccb6183712b44998c9f6b4233a114c2cc7acc9d4150d413abf13900808a8ad5b86a946040ad26a9974db9c26500a338fd221a9f1e2243a35ea4a56645642638ce0e4721f55c5e648e3e05289f5844c926364a16dca6ac4b1900e253e5cdb1c854c4dca11bbc85bcc20b82f9bb3a3778714cacbedd8dc8420d747e034b44f60f2a72e61079bf7e1da036530929299839dc8dff3f4f84b572fb991bf5dc2a5b146bdfc4d8523e418256ca1ee5f1862950e6c896a609e2f9a36f31b9448433e0fe43e83a20b709f0d8e37a57de666a4c73a3419a0319e6499ddcd5590c2bfa8bc7ee611046539002dc5ef79911baa20a8e2770fd54897d4413bba0112bba4cf35a652fb31c8594d8f068df5c64bea435cabee2dd01ce1cc825e2a1c1ca1a2bca4073db489e81231fd43fe4449f74fc1974b8806dca4c153541741bc2edaa5081fd46d1a1431950d2011d54cef26693fbac08ecb4d310f14e09f73cbd26adb9f28d5172ddec7614d23cd22da423a8ad1eb1aaa62c21bc1528eff46bb4fd396a3d3f53e70922844c025322d57d3b4a14859812c9651148e2d2b0133df606ee2f3ed7c48046816aef724632035e48f3f25823ff1068664881129da7009641796fe6edc395bb75289c2643ea60e2d501fd66b91198b8b14771e2cd93e5e493c4f8a7fa00299259edaf1c1062a37d234d00d2f5a6164610175eed87d9b5e157b306b8da8badc48f10032805e6970bc654692380a23719e4d2acaebed1fb8c9b0cb236d4a8365ad56072d74ab6baa84183d17455ebe50dbf2e111fab084ddb7968cd8a5a0699070748f4372493366f2b3aeab52aa514c04807fd32cd11294c3e42c43b03640ad413bd0aa6a21bf8a3a6feb2cbe05458a57aae6eaaa26c962aaf62d273122f5a18a2ef8be1988174de4ddf22c6bbe7919ee1a07b3361a4f2c64b22d972659b2d30750de21e2f47870b818a0db48d6b892fb7aa8db9b17019b72ae2f9869558c1ab69f1fa868d44a5c227e4e3a07cd8c191c85f5a264c9c41694f4b36a69ef0264e818c62e5c5ce4bdcc27b2e60e67aff0d17d17649fa2a7af302ac8f15afc9cc14009ee9a649ea5b81e2b8fe2410c8fbd71c03102491ad601e17b02b658e19e1ec11e0a822d1aca9d5d3fdbf94c5bb0e4f92b8132208fbada6b6ace7bb449f1aea33989aece1f28e521df3fd4a44f4595fc34dd82e17f54f27c7b291aa8f82b4bc9fec76db016ce9a6e4869b5a566c900c45953ac70e8b87791e413632cd8762fa5adda1862f78c35c8a3d33936845825454be6a77fafb61a922ac44f0b4eac4333962705fa1932ab3e6929b3659d95c127582d703fe67218d371981cf59b6f162c18ba2be9fe18797081de33e5e9cea3cc7c4f600f9b9710174368fbd3762d3bcf5596c10cf45ffa128887041c16273274a8745bb6fe8a9e6cc44fa70091629e3104ac06a3ea93d52833e358594ee14b4b82ee07d5f4cf963c5bb397c655e132c3ac6c30f792e999962bdc10c684862d560f43eae627b43d9245645a77fd48c98256d0cac7ee83d5e2a2b1d10653763c9f6c82cb807845ceb8bb9d4ac4f5753d2a6ed4111b1bf1dc871403868fc66e13041f2324114e33c8b5913a109b2872ca18a6c17018bbebf6bdf94b3cd73ee83c2427ac3adcedd417bcda125b429465ca2e63b8a81e8f97ec8bd84327768883e1187ff9dfcb55e04c66333407868cd9ced33b215ca31af490eec94634c8e5b8ebdbba5f573fc5fb5727334938cf78850c60a2c3312f7b9a9ddb0cd75c836d077a5b172890ca787a88dbdaee82e60b0c332ae8333df9259ff8819a61990ac8e716f5c2804d814815b7667b8ed87173e77fca0e1748907e272ba3236f21fd1c093ce7b9e351c4054bffc1c90ffbce9a85dd6062af5c75c67cff1a77d9a1db7ccb8aa048463a75c7b89469a4c62cbd82c3c4752d0b1b1826955801c56d2459996982720df1b1d75eb9d267753d23ede4789f04b2b9af258021a1ad2184ca83755186364fdaf9b2ebbcb52d40b4d10608be2327c2a34a6e04e862f7ac2ac6944e9b9e66d42e1564a4d615179f5d04adae7d27760b8cbf23b9b65648e162aa3b76db3cb72f9d9ead24c42dd61763f86f399e5413d4405a2c82c6383e823f1cc4f5f2c9cff3e3c51dec51a9da30adbfe5c19b208d0700ca87b2ec40377493826d42d858e274d3886136e9195636ab26b822e2015ed57c25a791ec35fb66d588ebb6c3df99693065e1e8430929ad402fe22ef6643f1d1890f628d152d7a3e0c655ca6a6e1e28e6adaefe6601e7a373dc2624ed5d9441d7d4a4a6a891dec10177150413c33e21739b4d75458134df60d1c453064988d065dd5fc4fb83cb0f59d3d024f33c01cb50b26980963212aec4a9208b0103e0c6630f81aae9937a8a6dda8a2a18cdb3b04913fa97f0f9358c0719ff577650a563026120629ebc5b679239b8e6bffebd45cea0ff9a0cf7edb56366e5e52eebec1e4e3c3456d4a91bbb617d5fd21f71354b409f9e00a0954249793522e7742f168b73bc0ad4a346eaf573086a56de51369c51f2b090d466d5cc0ad8d40c0487e8743238ed29830e4f30581a863a2e0843d160d04ed0c5fe7d5540879d1ce333a6d509dd9b944d6cad4e4f7557323642287a78a27f621ac3a105f4e8f052743923345f6281e41400a5240b6340a8724e1af63824044501854d62c560d6f842d03c630cebf84633c63f451d8fc3d1c7bb8db5d033c93e2863a22792c800116dc1efa1449d6e952f18a0ccd9c2e62b21dc2c70d03379d00f0cfd64f37eaf765ddec429d031dc5cb0272043a9b9b872f76d8b226028d069520495a64e08aa87a922a86bc299df72e7befe7eb6165217e2d3ff1da59fd13e16005889234a480ba3a83cf241b49a219a308bf7a790100e6a4eb72a41b779aad4699bea290f5e6e4a1f60f270f4feb4c8f54b353b807bc551a99600b28e648c7f41c8167afc60b2bcc99b5337f5f538dea09f239c2a25257a0a9410371bdb522f57d7882e91ee74ad01d6d1d0a13d552c7d111ce38878bd86156c674592d5c14aecf1cc6a99af9064ee5c448a49242fc27047e3ae0998518ea66c6ecb25827f3e3d7befdc64b016c1f1f4488cf1c93515513a78c7cf0c23139cb482da0b9f8ae47cdc188801f031fd857eac932da7dcd7715cbb562493554a7282c711cfc3a6de89994c58ec948222d1d9bfae130f4df8c0ae96509099db8e881aa3489d392fe1578acf5e7d7cebf6f5263d85203fd072a352e8cb2fe8995b56e65598b54eb20d55a25e699f86d0cb3313d04105c54208020ed87e2d8c35240913628f8b206046924def1c11683c69b5ac7203871a9c6602a9da37f7790365cbcf13bb5c583277a23ac39a849bdef6b1b9499842b2d116e8348e38b961d2ed4fb3871cf4292b8a43d3da9df6a6293ab8cb46f24e05f988ea42aff9aa350b27e9b978b04fb173f8a44cef6c1749fb26a3a18e98d27e1c1b798db7926e643dbdd8e31f7e74bb3296e8a435cb81d1a036cca8d04eeb0d62af2426e64543586409037a53888af18f69b50e0d41ae959b9d2f098fcfb5a6502454112890d078baa5d68bb4a8b8227d0f6aa32f6245725380722065413f7318089c8c7f5ca16d0a4dffec0a88b65e203cc72940d2ad5e7c016b6061ee0731e10e34d27cd85e31f4784ffd6450b839fca3cc5a79b1c29ebcb0427655c074a7912d30b153959a311a1ed3f1d71d321d781ba911c865c7fae6427b5e38b0b70204538d439f22476080aa8c0a5c1e4b7e73fa085ccbc639294ec49123e529592d8b291a09022f003f7c85d9185e407ea848fc87501aa13ca4261d832f4052c4f9fe3d9296adc931660964580978fcc17e8c7f492de27c668ec7e9192f11eb47fe239be02a7e185ad48a30254954e0203c39a06028ffae69a0065c8109cb04d90dbf94564917efd4f249c687d37f321bb8e89cf04727c33580ff74c394e6616e7ae9890c918dea8e7c250b2c85ef70d443f24cc630bb4b9061a8fbf2a19cb89911ad099d6ed4555926ed648b5d117592b2246ac6b0833b14840b763b6d2e49ec5641cbd58f6e63de7dbcd55fa49322df99798b66811dbd3f39b80089bdc4f7ed898c120300cd65b098cc361b4a28f8595ed45e88cc0b882b4601848dfca70fa7c7d9249d689a7177b3fe20a00cabe1c2ceef3c2b0261b30d620b79adc79dcfa48866bd521890f83cc1a5a0cb0d4319bdfe854bb12efa800cced284419099dff07840a2e6e584f11c1ffc6220a59bb9dc3e1a2b4ebd9de3e922c2e7147776f1af89929c9a88fdf896fae040063b1f3c6c3e4702e31b32e64b078282084e6ccfce4d904299089c532db8c7740119a459aed7870e9f5f3f2558c8e5c0b80b47838cf848ece8971347e651193a7d446c4b879deedfdf6ec08f25677a304724ae3e3d5c6ff94af6e562bb2dc4eb2ad6e1028d3442d93aaceb4a523061a8751c49f81f06caa1986a78d50039adf280218213d2850bc6f363d21c0cbfb70cd4db61ae90a7ea263e3694ed65b92d977c7355be5f4064c82ce2f42f103aded94785ee716bc3eca69b86ce396f045465bb5783019ef9dd325e18bb61271b0517368ad976953199a9cce42fe5e220761d0046eb2e7f877b9515250ebb8c7d558d929dc3c60ca0fa4d58cc4cbf58d785beb2780c97622b0040d565fea91fb14e70e380740219360c0680779f25a8fc139c298425dc96ebc3336476818252df5a72648f4ca153c40ce2ba749ecb0a2bef726250149327e6439f221b69a32ba1c33b24281c6f4413766da06907243a4df10ec811700aa5fb2bbed239a13557555f9ee168950821658bf84502d1a9c6d7c597b2d510bd35c7a2eb2b2cd19461ee13a176fb133400b4dfc6bb13d4443886c0222081f168048d3e3a3a2fd2da4374bf2c63a550725227c0409ff68451562a289aa812fbc310969460486254d68649c08f40befe4faff6c22c18464ed5d20b84534da15ad70be6c810b6dee0afbedd72ca9589c6c5f1074a3809b6f828f5ee50f5f89a8a14014c44d91b186d097c6f6db83d2884b07ebf6615b60ce4a7da53c2aa173867c3f01fcadb3698db343681b765f58fbd2e7d635270a6431cb893c7fdc01b824c38586090ac14da51ae45548979b92494190604b6a2a60d2ca31985c6c7fe39791a04fae0205ed93582369ba480b9c030ca53fd38e53b02dbd5438cca5b221578f949921a2adf3f0401c4ef34cca520c2925cf1fb52e22e737f426ce5488c4cc4d88671485455d03ddca93e0d947598a29e34af95b1ffa59909ab00819292c4222e2ef84726d815e410a50bfc2af8ec196a026c1012b16397a74498df975c013d956550f45c6d481f311db6140b0173b4b24ad46638f4c381871c7cbaa179b8b161baecc81774a8e726660e57977cf1d9889dae0ee14a772add93c4bf4188c76f88c5c6d0244602831308ccb708dcd177c5064c1e758126dc211484ce6bc7d66464b1c51afbfacb297ec8b4819fa768db583a72c32d678bd5c71fbcf191325fde71ba728c795634695ee646a7a366270d0aece502c23c9fee9b3412487d0b6ad7791a9819a2ce6da9233a20b44919346f7de9a0326a41e2b44ab6b53006b3deaae56db9d5126dd428f42e23f4429c3a17cadc115216761e240b0eb1eaa8200e93e7e8d4aae4e2cb92776c27f07b541b4fb8be97baf4f770e9e28b298ff02540d74aacb5bcbf4310094996e864bd69d919f86c90f9b39a3aac584b3202474846769378b85918b2d3c9b963c75197cfd9c94a4899d2ab54c61b58102c0455d02ae14de2df6a4b16157b3ef040355fc2176f86c2568a96b406696f308e8ac4b81e9d247b3f1d02a5df64dcbae3fbae5c022aac8892290dde1813ab4f74f196ff5f242243690f05d0c09223f03491c0bf391339f695e23981140fb157a4b7a4de2db870e3038f87b8e3da61ce1877c4aad8b52d8b031cd628909eaaca0a575ba97011f8d2e8581b5f626ad5a333da22a3d56c7a4985e7bc4748c548f2c6e70823f617c6400b9329a87df522a1eb71f6a3542e3cb71b233af4d3cf2749702a7ff40d64b21ba55b2850af5a649f67f6df8c93efa433092b421f2cedf4afd141dd21a445ac9ce2244d5e31303b556c1a02e646b0812f0db173ba4824865c695fd67b478c73c0acd5689ec03ac538448b36856e077b848740d72bf8edacb3d73d79fec4839d1998bf656e923ab70088318af0aec1672a5baece9738e830df0f7d8b2294dc7780234916b76992386d8e65e1e8be7314b8f2efde416b322241b59a3bbea4e8ba3da242c7df30aa876a5163029e0f227122723897344b1912e6dd3072c07ed0e624e23cf83f478ff02d81632b1c4ccceec7d05d0dd8d9fd3782cd9c599aaee6664ac4d2b2c9155a5c4280f518e7427b7b9b99e4b1deaab88ff4ea48658ee24f3bea8ec01683ca8e78456901ab1c746fea953cf46e1442b553359d21f97bce1e4de7efe00ab363751c7480ed133c67b43303f8d10e0c744f3758e345ad9e81a7339df68124a9e183242420372e30841d64687745b4934e1df6260a9184d2ebbe2561940da2c08c16cec52024a7937e5372d996a4729095c01f2fc0c02a10328bfbeb2c2150e44fc8bd8f683778704ddcd0481bd5dd33b745b0bea50cd07510483671b2c6a7b1cb2f8d6e201609d4879fc305b7c9bd869fc80b190c7df345c767907e6bc165bbe38473b8cc326d32d0df37172b50dcdff74377cd7d462e8e36b520dd00d9fd7303f3bf398e422318ae80635c98461488b727080cb42376a55270e61285e7cbc1a6e458b9e3bd4198b30f124a939113216c53d7765b298b70563c9de88b37adc6c24f1a186b5f1acde3dc673bd27e644689961226b0c2148a94404b2cf4d9dcd9db2dc26d15273dab6969a16d5acb99b019d56e90b1d3c0f6597cf4c39ee39ac71bb62eada7d56093636bc0a2f4eed30d8d6b5e33f37b93a7ff29fec7c0f66257ab170011f5f4913b5818dcafaa85da21ff1c3e4bc0e9c0282619e662007b26af72dc8faee68b64574fab6b57094d540896b51ce1193adbe85688938a36f56b48b2e49f496481668051e391635981dfdee04c39320262e0e76186783e5e4cd8771cad996a20e2f529fdcc94886820d4397bdf72ec43e28a842472385c0e3ebec541b7a716d3f2d66b760872ec9278814620360ad8211b7c43ea1ca6e480c8e62000d9252667d05e0ca53e3a8e05832025849e193b3194dbca864f66828fac6d182151a40a4cab1a25ae48c327f3795d115c001c3175856ef1260b9c8f7d24927f72ddceca2190b4b055a2c11390bb4ce8a34a91b6e63e364dbe65e6331d810edecba1ce1047cfd3ba5a05f18a7e40ff0296627b64cfddb1550e76857fe675f97e738a0d3cdb27c746c08a8342885c0bcf324126465bab36dd92e8341c091b4c5371e0341a3d98e561e47a4bce390c7130f0bd812687042874551f1593a4604c31d1311c093f19b59c4e7f4a49b010a163652c73492ebc02f30b5969bc3948e9120061d3ee4e69cb3a2e3895f0052820449408019de71f33b9000d2604398859d7db099ce0a8f96d44065af280ee09a0e8a49d9956bef3f49b9b1675cd77611806d655400a3b9f46c0fd75a00efe2f1cf5498067d550a2c88b716e96700f504bf6ed8494317fe28af0daab259e4cf13a30605c8d1dd04b2c1019d8461c4fa537f1d1e5c65d77f30d9be688d11375dd9a552fbf32b51a485248b84e28ad4723488c5ad945be417721d1531294cc4d7cc17de8db8c344f522b7d0fe3cb15ffb9f988a908724a39b35277394355c70ba937bd116e39e89932e352763135b427145a20ab2f0d882a0e5f349c70e99428a5e2c00ba48730c23b08e80404b145e696e41796262c1b3afb05372df764eb110df660c8ad1a3a54f9338eea2aac89c7cfca7a635842c9c7c692be25e0ebf56dbf8df28cd90a4b84a866a183ba5c556c07b4c623a6613b47274866ae12d62023d472dee8158907a47199f1391c1dd7ccff60281ecd88b5e8eb8e8234b2e4cae2c52d2ebdddbfd14bb5293903c63b5541f6c864ba5214b52ba97555980d4430cac5b4c4672a28e8cb0baaaf8766187b0a92b04029df9c076ad25b338e0a80f18a9792f1713b8e81d75e7f85a60faf267837baa2c61a981bcad46dd2f3d85893f57133130089f64c1437e398357a6a01436b74712a2e0843b9af0636a41901b02177045019ebb954fa5bb4848e1ad4db26c15fa84b2de35c454d279ada346aea4dacf10c3809a4d5a1124b09f762e235c6868aa500e2b35f5fe7d4aab3df3ec343907c659a10c22cb743830d9137fde32842102674991424187d59b2eff88d175e4d9d9136e48f5583ea5f0584917e30f4bbaef97b98e5601996589b74e49a687d90acd469a198f136f4b511e79bb292fde23aeb86b98f4cbe404776b5760894c8e61e112d22bbd9c901958306829af23ccba61090509489f9f74fcbd24d5d35b988cd3ffae2663fb7a2d8c8754ba491850fe46432eeac7600b254e93e26591a0217d95c8ab08f923c91e8e1b614fde1cadd45e1fa98fe1e4fcde0a321721ff55ff36556a1ca017a5c67e464bac34f1ad414968f847eda2f793d6d4c8e0a60558a2854acd31d9da0063824210bea28ce0ab7492a86feed13d6f56eca94814c6cac92d1d2ef25422703eb715d9db18ac7d4274906b0fa1d5f96888422118bb78d48a3585857ea51631122620d74290d969040980a42b11a5e75378ca1cbd88b3a71237583a6486488d6791246526638a904cedc548bd90f3f9cdc38be18fa617b5797bd6ad3291f92676f74dc098d39d15b74b823f3ddbe86c9d567ee1b52cdc45d48a9369c2a2eeb933046c30e705c872c3921b11685021aff86fa4ac98b0e8f12e1eea081edc51e6a53c345c9b3e4045c00ea3361ab0058031996935e20ba25627338f69c5cd3438a291e9901a4cc2ff939942fa8fece8d2bdcafdd537cbb5e9250fa85c31b5c0e48bd0fd34d28091d1082e59d89392438910b85e1ceda1c867c5930a48e832abd31c8af87738b1cc55af8df9ee173595d2b69ed76e26ea7c75e528886f6aed2403d86017e6cc023babd01eb30eff338a4e237ad03aba699e400aa9eb55d1411d93f270c2d2070c974e50c8509b398835c74a6235379c3849cc5cbfb89039922847c72c721068d6ce2c9500580a4320d181910912ae0b7a1fa36121ad21359c2101daa37c2552704c6d751430a4432c81e1274c3608672689c4270a4214e175bf05579c84f441749b403b290023511bd8b5640185c594a80d964c3c363c5e5a74507693df5c4f943cd2b928927bc69d65fe76cb94a4dc7b074e024a02330227482cb09f251ccd97a67a5d8ec3bdf756669116d6f1f4f5de7193b064e91967a020ff934926b2a498a8532cae495128792ec2944aa329238aa89e82feffff3fac816c2d18562a1db2520a8a555d13992630ab2ae755960de42d2d06c8c9f2a553e870e038dddf83a4ee0a3d04cd683ab1431842ebe7bde05a931315224a3145d0a1a10371dcc212a1f41b728246e1e69593fc9ab430a5c85db128b312ab45151c17665b28304678b440f4090e0e2fcb4c818409fa21dc2d8ebf3c259ac00805c388a5cb057577af2263d8ddddf3cbe10cf9c6bca6b4cc38a794a823600f1f19e4c1cca0bca9b0aa93ca2554e27f2669dac8eeee4b310cf55b825270ffbd1bdc9029dc9470eba1b8d9f6de9b4ccfcb52845b4ef1d697c6677c1105a84a6103418007aa3da4fbff0fb451666baec0476a802377772b3686bd497724a13856429cfa43c2ee6dc20abf7b3bc0d386c0db7300518c0372de9168b1c22759d86982b030db116b910d4a8e492dc471098308e343c7c61789bd3714442713fa8975181e82fbbd83d9a36ab916e2de7ddc61b519f28dc5d6504c8c3a09c66870879d8c80110fbc344249c8ffff7797d1ad5b181b9886355d0b0b0be3bab300c5cad972a41a4bca3211674509a34a30b2341d81b022fd6000756375aa613a99666d7164adb80015f38157800779672b7b73307bd7d68595151e1a3107e9830fac9f5cb4ec5efb0e62bcd970dd81e94445b3c5b0102f5e9473c0360ff74db8af5eed9012987413d0ffff7fa26c1151a8c153da58105d1ccc882912d5a9b0a7251214cbc6e96d8b55aab923b9e3dc5c360b160444231f4cd010b451e7d4b013c0e24a7125e38461624477edfdea90872ab0d2816de3650124f982ba37fdf86bc528695a9e8cdaf5c94caa7a9dbd58d1bd68ae9d973ad0dd3d1ac35238cf41c1b4bbb751322c8509511f6e8c8ce7ff0350d541c9431ce4645e9525d521d58a7856c6ba3e584be8a815e84300502812e55541f4b2b7935c865821da9c4595e85c04b15c4d477ecf67436f15fdffaf80fa7c667a0db956c2ddb57223500434c0ab6440e6859816cecd8f870f8ccb4c510b1b21ad950374e6d566f35789dea6f28150dd5270e362ca921247edb0e2e1408f5d1d53da55d26a242ba9d391130532f394cd27e7d616d2c48954a2c179d5916039fa32b40a1d9b20905ad5a605e7a7aeadcb59d5c633d3d61494cccaf8d058754b2804dc17cb7f1f559d943b1dca46a3bb09ef53aefaebeeee7145df48dc5188b73bfcb64c33bbca8d0e6d04e8ab1c64081cccf3fcfffffffd5fcab648f46d506aea9639f5fd8640a4c6debf3723b59709a975223ad1fc5ca4285331f86eb062ff7f811a22d587a611c4ed221212934f5f73753bf5a4cb5228ff3a83e8594d8c5e15f3274cc58a4a686af584d664f53339a226bd198f592b7c9c80645ac4489d6ab04d153494c90cfa70f7feff7d1b88c09703dc21cc160d6114dd2697facd7adc1cb3b5edaa92745dabbea8d02702284585372b934caf2861324040c714a314ea14a6aa69d8d11995261b333b9c115df52f0a6ad587788f4bf223d4ed8f5047ce3e563907ea826be5d0eeee2561c8185e0ac394c3268cbfbb7b490d3686d95c76ec882750acaa4e684cc9cc990478c6b51752371ec6f074d99b464472a4158579efed91e2e2e592e3150804eebd7716f98ab4ca91ec12a923e7a510a2bc1ff2d2b743b8f7f0e568aaba06667702b33e1a74a14a6a422d6a50d020a1931c1dad00c114dd8f1816bac4a4c9bcc405fbcd4c4107ecc2dbe1ff5feaf3810dbfd64ef470efbdb77e5e91b668eb483841b7315298855af1fbd5105f4b87f0840f08dcdbd7625f9fc6a4e6294602c9650dcfa8fd3a5aab47b83916bdb968063d561454ea0c1b1c518d27a2f2764098b8a804f554c94dc5103b52d052571acc2e06a87ccdfcfba6ba84eeea1a5e72303b435f36503acaab3717d91d63cb887bd4438be5a87b0b77bf90db600e4acbe6507fd518e2eefe5ad257a4ad5e9f440bfbef83e3bdf7dedea4b69544fa90e584a6f7c9ac3b2789272c1f1dc6e1568da7ffff5fa7353384d22664d7c15a98de4b51cd0b9906cbf1f93cdf78614785cb079298f7964060cb5bf3fffff215699bce6788f446cb6465fcd537bb9796827b67bcd720d01de8127df90de18ce689864dc76a0409475b62331f52ec14f184889515f417480d345b59aa46993d652c025f777726640c67ff8d687d657dae12f8ee0ddcfed36f03758f83ec1270d57e90af938ec6026960d79c59ea8e1667b1b684e9e8fe5a41bea310354d59314069332649a5f2e6c9a84dda53651366c10b76515ca1d4b901d8ffff95a0af481b9556af254ad8dd80317677f7bdb770a76cbc2d3fbbb8f305aa6691160ebaa930ccfe4d1c40eeae3db21f978cfb11eab6e8c179f722d9e2166d04b9fac90c10380c6f11263afeffdaba23328e6d963bc09c24cba8307682e33473cb03b35085a54375c4bca9c91982936c1fdabaf74be67c39d4d5218b2be43a987150a40a81e512a7b4a3b26878ad26a47bb2bea51790d798a18cc40d4c1596aa6204bd9b131c1dae3c2a9d96f31efff7da976072a8a747a716352b981db9f770ef8cdf684407264129fcdcdd4540635844d69da6eb48aa4bce9984e7a537745606b6c774ae8db8160163eb152f7a593774aa33eb893b8b4bd9208d57ca13cb94d3224bbbc624878eeca6655c1a5ea331ddeb34e8ee4077f766166961f796a88203f12b465da6224e7f99834aafc7a55bbe8937e381ee06b9e6feffffb7c39582e6b71ac365174b24c1168570fbec292ac82eca6d4f6fd39430c22c9d00a1ab3c4cdf9138f451dd51dddc16332532d1b702a2c6c2975b6595fb90ee49b44657ad160d6331c7281e4b1c2a5e54622c9541a11b58763d2c2a467366672f86a87ee6fccd29dbd234fc3b958927ae1a5e479de1cab1dcc130e74dcaace1cf2cd2920193be0960925834435e8e109db585782eb86bcb5f100e06774c8f6c497f5dd70d6dd692e77ee68a60610cbbbbbbbb270518b2c21093b1b04b4c057492b7cbb468c8dd9dae18fffeffbf18ccedd43309ffff3f9cf793579477d421d47b19be47c1517777777f3ba38f807a089fbf35a7c206cb523c9b52c731ae1aaf180f78d434b1144dddf900a126cbe335364555d92b827bb366ad30987316f47479008ff24d2eddab6ee3ec58edd370f303c615ec8f044f269bab5499d74b8c96a8c41038450291ec08a31808040d0451248ef348d0391400020eb478bc78302c383c342c200a87816020300c1480c10000000c068383813020440e9556223b03e123c2daa70b1357dea207bfe8115cf0e05991b2004ddef243c08ad2566aa0e788c1f03c23a59aac2edca5a0dc78b97d6331b10a4e8970aab7ed65fc71db12d4ad783a1177eb172f2f3a1e190ec404a9e64dd93443ddb31370aa378502ec08da93a43309ee4e829b753b90d439241ad13ccd7139a4c6fe50796352db5ef47c17f262d26b3ec7f588fbfbf51e869224184b2496a014ef86b5209e3b56e844b1f74b7910ad4f1881d15469959588e43458aa58b3d5a40d3d7557d32767c070f751af6188e7c7505a1f0cd48c1dabad8fe2ab776ea515de522483434491468eca381bccda871885913f280f3cadf518ed0d23d0759197a9b213542b6eede1a8c3b6000d569758c848370bf64812bb5908203149205cfdb95fd98535dd86f18c19d779674397868b8c5aeccd78f6ba70eda73293a95e689430af3b447a0276a9fe124ee6b83f44cee75490fe5f425a194e196a6a580d2d4b654105649481a81f5843b6e656e719b8f018774c50d3476cd5672a0a1bf56b75f3d68867d69f989368f483fa26caaa4189ed9c7396e4070e8568c513b5785ccf0f1c0c14816c9099fbe10bc9767acb0e530b89728f4b2c2270d8078786042bf03a759dfe20fa311a2b6eb001f63ea618e47262f84a75b71cbb471fa8ecd0982bb8452f3c2b90139dfd51bdf0f18e3232c4be073c6a5693f3364a1d332abf5a902d0ba3e070302592c6326c7ac60baa671b169f9db2dac29f60131ca109d58e4ea2013e3b335ecc9acbeb084a938cc5230e33075b9229bd699948cb85c7f4821710868fa1e42d6744dd743e32e9c97f2dcabb4e02f663853045019284c9e160fbae6fad8c8b3e8c97b432ebe34e5652e659ed8f907d4fd623306edb507890245a46acce424cdc8f32bfd768c84ebc23a8e6c692465e69fefd74c170e5ee35f2f8d433a00870ba6ca2818312c604c6824b3e48004cd96b18d1b6a359c8d793559a3829e032a2c59d55e25a8514531c9cf9d69202c682a9d591126f1e5106f80a77fbd9c0fe9c89e5dabeec70e587f2a5e41f90918b92c831d0b22e56675ffecd713ec4a0061d16ed84bd00906e2368b219b2eee5bfa990b8f4f81565122e566811f1d747f3d00c20a6fc2d08786e70c1a71388069453738439f125461db5321bb2216448fc29c9e7fd284956171c3e408b4007a9bb9a4ed9c8084df003c028a9115c1b39ad79411364bd8c053e82db0de4c522491bf9eb6e92f32a8295e7d0cde0d24db5c734c16e6c8e42180ef4da844a72d4ab717fd90f820dfc305349f94a5fd2e7223d303f7ed023fc8f562082c6c3af042b628935add9d3e4b938dd2c11468cc0adc423cbf05b253d6e70f6b1771157785f929e2682873114a1bb6b52e3b810c360026f310b5db744dd3c16367d4015448bfc216db1375bc4e283171d082c90a64664042d5dcd5e442489820c0cce1065771d573e6f135cb23f5ff43f769803285488385edeff25319fce93b4e6b28e78ebc630370079ef10a68e498b236efe1f11843e4e4310b91c16a73b9a7b6d0479fe11eb11b2b420ccadaf5302359542fc6c62cd248bf34c936a0d6ea3c952638b63227f4facb8ef2ebb23fbc5a1158eb5944932992382f0129a9af435459d296f2b484c915956eb8f5497a3f765bbf3c1a64fe97bf93dafab974a82978ac5f0464a14c6dd3e2302fcf4bd79b985172bcfb94d0fc50f05adf5c66cead0127c2c2287e3ee063ae23b36900afffb9b2c46207d2916ef72ded728f6bd68e04f2ace81c518d6f44cbeebe4319840d3724a258fcfd398b056c85a420b617b252eb00bc55525e650aaa7c361ee6729f9cb0b992c096f62916b838ce1c7272c4ac6913aa9d32140cfc4999626f5ce2116c1a4898881619635013afb274d1ea535be1484e60e9648515282960bdee6d648cbac63f6ba05ba8b372e95329f8c5884c6103546528c4f1eb45e62048d1845684c4b7aa0b259c6fc53a619ecc11fe1003d2204a38c79f71e08aecb0c18b886fdc3d4420f02741d6cc7db41475eb5e67aa8dde1444c2301e60d14d7e0c04ca524010a582e9351e416a19315fa79f3c20b71e96a02094989ed832caad3be3382e6b5071a2f344359cdc0e65e6b3ce991407d099d1070dd1b5ad2c755e0a0bcac5a1c18eb5f2af84c5d39959e057c997a4d1a18f490155d55f2a55f793e64c30e1f88842db2d5fb32f3f6fc282d80b405fff9a1428961dbb302c940f774d6f03431f575fc8dfb5876c4840dfb46e4f5627bc3f9b8ee9ff1a06f814d00d1c4ef757850e40519d68e5639022ed609f024059be167b5e909fb978099c7f2613f8883e7a02cd466fbc443dc9ab4fb3bc7982644f6660e960895fc505274f579ad05b8b9107c2a10c6c11ee824aeea0629bd244ffc77bed7367ef435534c5c578923db279d0878be2493f9398839df092123df615ab25d6ac34a3efff966e9ec0e498a0dc71899594f7ab9613d8449ee63120fd38d0b25a7496fa5fa9334b3e418e581150eb0c74c1cdb918b782a04b34cbf9c54bf8866f84f01e9f427ff319c52944b6f3455d8501bdc3cc52cda05cea0255ce2c0c74e4000e4ed402aba001220bf3ae1adbe9d086ff6aa6698441e593789170bfb5990a2116a1a4ec732f7bb89a9f3304c086e4ef70953830870dce8da135fa79121d19e4cb100a601723f0b0e5d88fa5752b5133fed1006440fbb87fea2d33d8403fcd8512a863120dde812edc6eb1afa50fc4e941581a9d9fb323e77557baef5ecb7142a87ad8073c7c23e52240e256389303870ebd9e7a21a2faf44004927b9c898f8d063fa1e14b72b3fd081fd076ace5b665abeb5a0b1df67071f3ba4dd1877ade381015e8570fba5088a99ca75ba85239af944fe27e844608565b81093d41828d448ba2c3d768f85aa2cd31d1b30a26f22884770e47fb2c44067c28c4b6c1be924be2cce91bf6294df2aa7a2c8979969a69b7c8f24347b783dc8bb62a7a1ceaa980b485254506fb6357f3b59eaa6519466b3bd240e0b367ce4eaa0897f7464f9b60c084c6b43b43bb0dd3a190d5a72d95481855784c730ce63ee508344b4395b5759eeec4811a24b05ba535ba6f0b44389f1a93b8d95a6ee87a7fa479aa74e37557d6d60bbcfcc516e003039922ad670d58a9e6d2306c20ac8567f0cf863dd0dcd0981b98658724e72b51e6e21204c6ba91f77dc6b8f30adb4969adef72583dc85aeba0c25546df9e36be311ad5f474300cf3a1426d183f8bdc45662408571747818552c1710bd695f20506c18a312616fb6c7a2b6746fbf52b9b6eaf6199c277c934118e820c8317d0a1ac3d17891b360364ee7a8b4349e80713876ead37276449e9c8e30bbe4c5a8844ab83957e42ca7b9a2290c4d697c61b4be4e8ac47befaba086f6551e371cf2b6bdb02cd267c544e67b9a35bde49d3cdc4208e78e06ce07ce543d31adbf44247b3743269c8ffefb392b40509d768588051474d13803bf907382ca91aae5fe6b55b2960f4b49c820899494e8163fca3fb7531186991c47943a55fdc34c5612f9da7fc3f338c7777dcb503541219c78c9ba3e3fc840205e8447bd1ed10feccd2334d4ab5e901ee5631c24fc7f37264ff92c87239018fb30a47e26f70aeefda49bba9ba951faee6020b65cd297dcbc209dde1e908180279d38d962a4d3382a4d58afef8dc8dcacc597bc7d2227da090a6df5481c5d2d1b01cd9ba4c54647c41254e469a31ebd96f99c1e78296c2f6df4f19951571a070c2dbdd1377f67e863b178cff157cf0e03beefaa4ce04628a515d35bc631e36f54f4a0482cf3f3fa13f905c1f585f2bc4d2a8c37a660e3f3707e389480b456d3759ce253e9708946e44a0ea62a89c9469ad999f1f5cd48feb6be225e73dd4c12c8347e3548a095573bf961f5f3054b6c5818417fb21d337451cab517a20665aa7795d0537d07ec738dc8e97e7ce38af1b3cd7f524af8b2c8a62b2703e2c2a2b562190f368b5d68d4f60780e628496c219f6bbdea6ef71f1125350c4356c70fde225a73817e8e99a10d3d190b6abebeb5068cbcc3682cfdff96416dab13305641c5a0377960ed32a836b1500029c7368a5149e741b5b19683e5798aa0306ecf251a6394d5459d97a3e8601b4580d74c3755e0d3231718a99c6c4c5cab58d1b7bd50765726e0937e6355db47b487aa7076e4994e00c30f94bc33108f03b0e85d619fa6909a18f273f172ec2f22b0eacb50444b4163e629b988fe236a38ea11135a525c3802736dfc24890e68774a80014f7795bde717ca12b07c0d382278f858f64fd384fe652db83a5809004a6b5ecaca0ec85a26143fa3f24ea6af618fce73dfb62dee545ef25bc087c40fb5c337633df50b006b2c91921b8c411e4fd4b6be920f94c6c86ab331271bd6316dee1f8bad07e4c1aab89fa666724191d8611fbe27ba5aadc7398533a8c8e587d20596fca24043222ce4a9b1c68a91164ea433e8c9f690300c7b769935b0310439d67ba44e52e0fa8f15f69861a717ad7c50071762f67db6abe82cc2efb3a9da702609070922d13c309022faf3f2e4e601aeb38c2e34e86f3da2c682646421e167a0f990a27e67c7799c103338457fac27f8eb5171c574ab3d3989c3514b1b885f10c221acb742e7cba15a005432383653675501d4e5c0f05b2b78dc94e06d911d791c3597a4878f1f2466bd36144e5e3188cc3ec3c9570f81a8b962c73e94e6a764ecb0323e4c090e09612de265e9b9e395d86fbc824cb25576f46f444a1033be12e9594cfc76c132a82ec6e98ceb821e611291bb3dd645b367449d29bfa85dee45389881a02efb454e8c9869b0c5ce49512b24ac3f73a6a612104d4193950ca81c46d46553bc043697c59665b185ec23be33e72f1b6820f6e1d895e0289cf2c7771136c122021c51b59ce9431a35c074a23026edff523d82c9bb19c5544e9c77ac13741adc452a5b7b23e0d4b6171faf60ca252b1ecf5d728ffa7e707bc78d4760175fc270b07dd41b88e237a10ca041e299d4879072596f7c3a78e1c12688085e66b93e2d3cc89619c99b28a65a46933d0e62e9fe422b70530a67086bfd4451f6a898212296b666ccecd5500f4106daa05e3e7c1f4ca40dac84c8c5fb65a3116803a651a2b761113e24608411a33e221681464a6b72225aca4876de70cad5f671588a0e32a87b1195c9d9af7cdb5346394616aab76b201e5b33402660ad77a6e00f20392aa46500d12933bcd96df3448b7fec3d08004b231d2ea70ae863340e2140829176564ad2ea98054236e6c8acca6e1500014903b137ccd06e3e8a710fbf9abebb274bc69daac9a39b32faa2ee64e5733bc04df589e286b84bc8d1b87a1274d60e132390080ba37dcb0b8f1f67654b345429d84b136055310a565a2a6af2daab7b4a3bdd6264232a969ccaf63b7b7e2d61c70e166b7acc79fa4c380cc8dd2f97abd212ed20262a849652fc6519493791998cb3941ad710ca8c584279966e508c3dc210ca55e7d052eb07ed9856e49b403a2641708f2b3863565c514a98bb9af7902171c55a405772ee57dbccddc7c22e99cc940f8308a178c055718db6adca7dd24eec6875e42417a4190d914d119555941270da2bd320c1a54c090bb23269396b4af4c2c3183330c5b51469840126444952b7f873532769a116ce7ddda9d47356167235e6834ef76a8a509b5612b951eb98dce0107db6414ac723c383fe8c6d9633afde69af1c639d2c6f257061d92b0f0e820b78e8b70fd01f3b561e3536e776e4abc26ef97690366724996c113a24858aae813a2a130bd44fbebe8f9819dd0c1245c872d682096a4cdb644dbcea80aa29d8586bfd494c7cc6aa0d97c16d681faba15cb5d7c12a7feb82e236fc8ffcb335928cbff6ec9e9170e3a239f95478430fe52fe3bb3d8248b1a74432274f3e667447e9e234ae1e014863c5a21f6cf4e6cace9aff2f36aec04b407b1214943147fe0631a93c58d7329c7f145862eeb663bab852019a4e67979c0aaa63eed49178549c3c21b45c306bc215b000b977255f8a422e1b1e01316a4249bfa844267133f6a2d296285f1f1666c93a45edec20bb76a944516b5785b1543e49083a84570606b258bd36b7f1d1da64fda5a456538d69920c9078775422822898db91a42c7b8aeb0eb9232257871472796ff42cbd10bd25410929841e43a26bb8c6e977c54b6c41c2ce025269c3ed734d6505e1e1761820bb0859f1117d29718216a26d3ead6add9b5bc25024b5f63dfc10baca5ada81598b100343b3caa751a24879216646069b5e742b1692292c3e8de194ca6deccbc1581abe04003d916bae9376d1df6f81757ae077fb11a866fc60b9e80a0f3d6589de3efda9606636f893b39beb0d0e5dd615aef66db70d91ca71c2c219c2886e6838c0c80cdb46d4df12bd481af94a7ec0d3c2294cc80a4778263fde87ce7baac11fc37ae724d6344f7e55148f35c3414654de8e06e80c29ff94eb821177347932ea528bad86f0157c3c1e9fb6e1da16be60425abba56e730bfa2504a62c42001272d80f10e158851c416cc8695121014a71eb6c1313758549da66121071193c9d8431c00929726f82d846ca4e0c4f7608bd39e38e65fb2ff379a36307b7cce32c9e2ec61ab10782e53228ef99f9c520a5b016cff2c0701083455c977b8057557507cd4fd508a077ec0651f5dd527548e2939be298e6233bfb26376c89a96e6f22a8ea659c682347a0c96edc9244a2249a3c341be432346a66f68f93dcc3e3cb8f408780f11d6b8b5e9866ce75770bac55a49f4fbbe6106c76679409f273890655ec021735f93ea7c525b924924470d2a380b0ac35e8b02823271a863f97b38fd1068b6a8cd67e6655c8cfdb4b5e55bc5755c39a482af539c64814363a1b0804e0c77ea50206a3b1ad62960232bec88a22f24e83a116c05f04dd6dc0aaf554980244e92392e9abf9f76620215cd42c8f2430a4879a539f3ca08b7fae4debc3fbe51e5fe9cefb25373e785072b0d86da235cb28e161c2eed08deaea9c7a7576b11d6731113ceab7be9aacc82efaeeb0c0a71b334c8c2360a0cb99b0e4266c7edcc8c361ac529562e89be663820500e3ce64bce23cc3bd425f13b0507759a439631d011d6cc21eceb002c7e148db683e692b90d0651c89ca6fd153836a0604656a8dc0084eb3ac39742d7c121c39b373ab569bcabbcbb2a7511045c8504188e31f1fc20b72090e8e8b73d2afa4f49a2c7118d6650ab6c3b104c5d9b0975c04e1e774e61eba0cff4ca81370e99bba06adc66e81dac3862e66fe4be49282805dc655be0cc71b9f828723bf0c9c656297ad67ffc75084b9c1192e7d086dbbf9eea58f1f513409734909009091c97843223ae655a0b72132c5f7bcd136024a12e1bb8974de4b0160ea4a6f6cf5bd5bbf8f713089cbb082d1041b9d0aa836636938ff239debd63173ffc0d89f6b2520b175456b3a71130c704e6155e00962ef8bcc39c729576120945970ca367056e64595534e0c821d84c5ed7e3bfd26b4d3461b5ebc8cfe7fc3db5e164c0baf26d86eb5fd9b8dcb4d4f25b3b968478f03af5392f801af3723a608762ea7482b09d3a56552beb117c52aaef5b944f6383e5650af8ee68ba0a4f934ba1113c22fecb2769220576e05bffc5131ddc9e854c699eae93eb6208451fdb82713783a72eac079aea6977b035961f3641c07386646f98bfadc789822138beaf44531fdef08ab2a55cf5d03258e0853f7c4c49e260026fb95a41ed4c5a2f85fc7679eb46312132d6aab80b6163e141f6a2b48b319ba83d08aaf8888ff874e87e9691f18760ebf156e403a031a425978603e046d829c12c06b445c6499bec40f0f88c859b334d0db2686230790f8d94bb080f417583e51172a77aee6cc33e2ff002c6d0422e4fdbd62c22a32ff7da4087026154d884dbbceadad2603df4197e74c912921db3f4d90277ab4783ce71108c0671167e5c93eab4be09030802a336db68dd5c26fcb183d157fd8d5c0ed2d14388b8ac2872482ef5ecc7cb36105a1cc645109837d91872b0b8ce364dece481888fd1b7213f702ccbc17e91497e8ae268679ccd640d5b509bf1a030c9128802c66da888b7a46b1708e605591909321386185b2184361a60fea2ed267491289dae241a5216559cd5eba06085b5cb61f684a231bd1bf8ff352892224b93224a104f16fc221e9fb2c2ed5fd9938349bb5a2e929a47f3614033d5a392e331653cb253343c652eca24bf503822ead70e33e6ffcbccbc608dcdca64bdc0d025031959b2cd132cf049d672b2fbef33117522fbd938b482af261f52c5e72ce54f4fbfb2d6b2669ac223109f4bca1c2ba614eab7c080a57635dfc64bab893d1cfc3ac94b7597f1bcae2859f82a8b632b10aea13b787089a1995c635a015d0186cfa10f1369f7a7feed0228a4d208aa23171877d24b77ffb440bfc87995c631b461bcb1f9bc42228c00d0c40fcac7db415cefb58152c20ab941a765eec0302444b526da15121e6ccbf526d64ad5d88af0ea7c299ff98d9139df7371cc1ceda08b6c125c851b494b2aed0d28f8c1ed0a432c1c24d2a60481196eb8fc6df315828fa7bb3089d0359aff30a28012b83c44dc5ade7010a501fe8b3b81b8d079099a71070b594693ae2f8dd6cb41c19a0d1154dbeff1468f22d84e7a55d94528bf6089cfaa447667e3f4e3634ffb45c39a4d54793273b36d4ae76cde828207e3a3677b0b6d68a6cb7d95aefc2ed8d426772d4d96ba346896c343ee4dea8e87d8972d6877bf6c3c08f018eb0bd9b02e729500efb742915aead894974820d0c5d32e3f18fab0846f4f91f9e938a76b0e80621772c6ac849eeb7d0078c932f03f0142256433c1a53d213dba6354f7e749aef9119337ab0bd279d39814fdd2a2505e2a4ddd75955e419132534930ce026fbbf88c25d9bc68f200a45fe7f4c1a314e6d9276574f552badc3164cd8004a4b8aed5f36993f15b3ebe4d12ca36f3c31acb9b6fe1674db82708bf3363400a1d16010e8068f4bd0337dec292d383ff7e481e9e34859bcb144ca6c39c974ba0921061d88312120e125321fe7643740d3e2a1a665e8d461db94a28e1a05e67a6e73424c50102e034d0de3be44d7fc09cf991426a8ff3942e408193f367b63833e15aa98f5ce0aeadf7557d6da4989d42f42260870a3c546aa35346bc4e53b30e0e7187e491fc9288b8df562cdd01e2022f8453e6e5dcbdeb8d0791378f3af4bcc2b7759815e95c4b6fca31733ee7283d6d8202dbc3abd05ab5fa0782f45ec962b0ced590d2822504ecf18f232a36c1d86ae97a46b345f935269dc7671097d4e42e3b145db93ffd2ae132e447c6383f97978a5ca6e13b00f25ae1326fd00ab536215427dc5c5aa4902b80f8512841df5630394543acbadc8ee1547e9261949db0ef008dbb145f442da081518cbceab58fbfbe5ab2281a340bc43657cc42b2947d8940495c86f06a290e3dec6393e41fd0ae6c99f052d4a5df0e4bc8686d419ca198e62e5d9dded41dcf345560a6f0d3f081192683c46d0dbd88e9eea0642136a15757985133286fe988d4435966be4b82b17ee90ff08549b48d815e9dc01f4d4024a023e61594056daa4030568cf1d9112804cf091fa34b5b068932f27d255b900e20fc1b61beb49f6349b1e72e3030f1719bc40d73374cf46a2b9a10cb4f42a523587f6dbe20529d77037d0b25c2dca989da7d14a42b6dc7b4b99529252ca7606ea05b00548d8739e758ffe80aa29de92ed0a698c89998df163308a8db00669a828e5142aa7971646e1a4a8ed88c9223de5782b5b96a8c73cc8eb8a320f6be6f3975c7835d633134199ad0a42f7dc39e79ce3f80e8bd70379b19960a187897da3e1afb012116afd8bc01c1f85091346dae0f2b913d2e353799f91a2b17caef4b060969991cbf6f3f4613999190bbeae2a503aaef05b93add18e13632c343635bded220f086c9633f65b6db9324c42fa104bd863f9f9e99042dbcb1a0601a158864f288639bc335e0df479602a34027b7c007d1a4208a14fa9041f9813ac57d8a48520509803ec7951a638813d3ee450a27ba173e012363c2147609830b6067f1efc49e457ae067f7eb6865461c2d88b64c912cb95fd2b77af41516b3c9865bd4a22365fd6dc1c0636a9aeeb753fe7ee76efd9df8da48ccc2ad4f4f1e554ab9ef1e5b37523a16496d6cd7bd87a30db8a848d750c41f999ef63c26ea8076e0c2616fe6b3ce0f318d4d5e0ac1be219a8a0897443b6c1a12c3e329699e57c35193fcbec65315be1066d440603323e4343ed5b427f96405852938ff4e1e6934563393439380439a564ee6766e6e6414eca5f5e115c47e6265472c18f38ebdfd8f5321adef68d57666666669679999466c7ccfdae2f924c73c91a51359c5dbfbfb9172692edbe42fbbb351a7f0af30c4c586b3de2b325845a89c921ee65eec9d9b11a2159f7e824cb353ef8a4c976d98e3b63bb5db7a43105db31b627db6a6c6c281b528e1cf6da58af570f1b305683b372d21999238a208419e93a0c212925a5b731c1b61f2ca914725221d8f8d7b1cb92a12d5752e3017ff4822b62b7ad9c94bf4c61fc3044fb225483217f422517fc1d762489ed775d32327d71cd431af8ed959152970b3c1a2090063e9c2cf032f8330a755006d431f62deb7ea600dbf840207d2e620979ebb535065c249952a90d391719e363e1df7cd1c1ba6f3ad63548e3e69c57ec2f59498832146a40c7106b858541dddf2aa8b907e4c5e0ac1bc7e0d30bbbf16aa4bfb89e16dc60bbfb4b8c8f0ffd8ef432c8ddff6e1cc6ffb02349acb347dc25d3d72523c3af26c9f4fdb3deab3199fe7b5992cccba010b75fbe7b8ed27e413bbab50025aeeb64167a68597fbf1c7ed61018c284096303bf93aa9fabec49141e004aaf9348f7ac9756f592a87a7faa40b2de52df0644a368ec5e7688e4e6e8f800b65d570c1cc0ea3018d0cdd6a4578234f0bdde9cce6037ff9bea933596667e401bcbb6a181600fd5c8c6f2fc5bb29c597e12fdc6f2b95e230d083241f99b15848eeab52995f8ad7a33fe59ef57157aed2b56891214d65ae65fc109ad1b6676b66be397c1d9ac2337d3cbf0ac2130b075aa5e24b6df75e51fe84bec87261f03ad127834f001016842dd0381dd806617525cc038843cae186840965f26643d8fc2423d8e04194190c1240464ec40869306c6086ad0030e5da000e8084c04fe616e5c8064163b58022125d18bd4fd32064f081fd865441df1857ac26405a74513e10b04ba6289185da4cc408a1fa4502268b2a1620a25784045075f5cc1627012767aa62980785d8ee86e4f810b0971e7e0eeee3b474876bfa1bbfb67ca257ab096c841774b1a3b0d27fb544fbe8c62acbe7d28c2105e125c05a5a57b727777c99e8319d3093de9ee6e1d749964d00117f4044da028610b21aed8210b332c1268e1431155c4a8010f1cc67309bc988e80af0750670aea490320a2602c4480450e4d2fea63c4cea275627abe70a482070731880098285828e14a0aa6a8428c2abc38420a9c0822038c18b8149f254c469c800026d8c1871b489d1bc824ae274c0508312a09a63d11aa557ac240020e54ec7006efe0c99719214f84d982090f3e2461081e42777777379992cf78c2518085055488f0ae544834594118b1db251943287046e8c2c104e0b0e47a620306602295ca9b881d22fc528d485480464ec8bdbb1b0848d324cf1451c830234a16504411ad38c3860376a8006db1224c9428f88b6e17d48472720d35092239f1815edc508c311e0169da45db083176cc8b4af468e131c6b804d234e7eea28c2ecef04204a48b32e10535acb0a87725c61843f0b18c2d95114a10c6155ec0507152f2928326549084162652f0206710f325c618c798f2689a12318c7185c40d325551e76283fe9080255fcc20020ebe5801451548dc2084df20e5d241145caa0ced4c414116f622c618b5e0e91b2d80a04488f9a131ced643131b53254690439fd2d34e069e206f3e3a30c1142e3ce862045660f0138130450f152a50bc6009260083bbe8eeee36e5c7ddfa6fb9f6b384b9f010750a53c0dd554ad9ddddddcdcc2ca8e0e24b4eca7d6551a3a3d1b0161d37be571a00175096936160e73c23265a507e968fc91d59f84f07d6f00ea4812f9989c6037eef3031f10eeff08eb39799783a4c60139684714c2cf3b0c0c7012cbb607987e5bfd0f2cfb4d99a0ff92f3cc0f5f8d962f300f7027c34f069bc6d8ba46750e81ccb206e91341ef0cfb0f0278c02455e06bd4aa0f180a51004715103cbe97e6cec7b1bfbdee5bbaf66d53de52789aad44e5f9aa5aa1481408eef51d6463d16a3ba2e67f6e7cfbd3266ddc8382d118ba23b24d0f932684d7f595153b3ec2e928c9c5eb44193517a932c851acb29ce67d21664993967ceeb5a0011d886e655c4a4d705200da4b982c2e616545151658716e47ca014cc5d45776c010cea8953d5f3673b5ecd649692f2bfc0a3c97618b19961014243948750fb8d26938094f2b9114bbabbbb9b999999a96867b6b18c820a5a37ddddd463eab1bbbbbbbb9b7dfa79faaa557176733777bb47ef89a393005a37efab6639b981221331324602a78e20e865cb7cdd81dc657e05dee3788080f09e09831ed25104e57f172ee1570616949fe51b824b465e3496af8c2714c408f895d1434d22bf6392fda954d3d432ff6669c0764f1b702d73ff80edb6fd1537f744536d8d87747666eb30b8ff7baf36c02db2a822e5373619c20cd658dfdf24ac29d9f42895a6ca31fe0a73315818d668fa270d89d4020df46215108259fe8a6368e951bed611927236a0b6c53514b2ceb9243ad625d1c1ba2349ba61f2b1d77b4d497b2027bd177d19b45e1d989927a86b0560ae81ed7572521622e3ba785eccdfdddd8cd9d0b17de26e4b085bc76c78b15e25925fd6cebae1fee9b9716666253d96954cb1fc38467e05bc9a2290063e84b09a3efc210d8482ca592403b086b690a4246a88662539344464d402dab648a0e6e9e2121e6666ee2e979c94b95dc9c21c9b99f95f6466666666eeeeee1e4034cf10f1780750b316099778d690185f3eeb86618396eb0d4e369f16b3924d8e03814a797d562ad9d8c4d0a84104d65cb5068944a3d6699ae2bb295ee923b75c45afc99033d9cccc8e1df2e9f3d1333baecc643bb067fdc0420d4833d90e49faa26a336fbeeabdf71e5f231710d8d527ebdae533e9c8f6c8de8c0527575f72527e97e51e2a08d9bae7b70485dfd57e41a17b10c710f8fc5c711872a9d7c8b7fbec7bf79a1b7f63b225ad2227e9e272c94999a77d11543ca79224acc727ec254df2c7c6b885c618493fb329026b622ab5b119954aa22cb3fe27895451da2e10ebefd3594360b01e9b93ee5dc6cf7cdbfa63f7039627bd7e444f8e28c28fe5ca135f0b61f6bc2bf51a7940de3cd63d087aac03c194f75a07db6f5e2f83eeeaaf91cf95f480b8e0d745620de5d1c06f9e2d949ba75db1b779ba5818bf79b8799aa7791e94c805d56407eafe524d2c8ccd33e9f580200e39ecf0039a3d4c989ef925ac8939fdc48449ec590d8831c44a8234f04b2260399d5bc2ef71b1a143f90871292a289f1faa098a8ad20e1d289fb6c51338503e3674d8411dc9133edd01114803ab499660f35b82128132ba642ff2b6959e2aa258ffdb562cfc16ba7b2c33618509cbcd3d961a1202c7e96fb4f1afdbf8b76d7c6e3fac8fb0fda01fdfb51fa38fffda0fd1c718b111e61a8f7e11f61a8f7e0be3c6a37f6215d64fb96d1e37daf6ee697b8a1c3311ca6c322a5ebfeeb7d9d125cb2c8e218c836348bf3f89ab8c652fa46cd079bbf742dd7b6deeb9da2fa8fbc6ae9fab39611c176666666666e6eed6c14333932c123a87b534b611093b07a2e7709368894634a6923ee6d3e8041543ebfd6c546f26a256bd3693086c94d60bc4fa4ff54a29296dad46fe636f45ac4becbe7b741e39bfc192c62dca89aae60c364dccd07eda4ffb693fed676a3169636943030a144e6c96b013efab9110ef1be4aecd61ef9bfdf75c85ce3da123bb27aa9a16911cf767c9cccccc369679d0cc3a15104f25192bdd0d05b74c172e9ab9bb703c724a495b9776c5892b5d2ea08b74e58ae361d2584eca2d45c78e9cecdd3d432e27aa02b2e30839bbbbbb7bd03b5098411939d8c134702cde9c67440cf0bbbb1b7ab7373fb8850ff898beecb90af9212491de7bd2be27851f14ede57c159bfd5776a52f931632cb4983b8eff9bbce775c2f08216ce6e607c1cc18089c80e7ddc7fa167e3ff7f365d773f5a2ee65ab91ef9abdd31a32b2ee1f5b3771c224d620bd5cbd976d35852ce89312c257711e08f7c386d1debcb96e6fefcb6e09de5cab934ec7f14aa3c705287197394cd5a463f362db7cc89faefb5da52f6bcd820de89d9649a4bf72a075bf8037065565c0c25613bf2bc571ad71bb7fecc57b8637ec5858df37ec5666e66feb86ffb9f658c44a1c063123ed8b755b6efcedee97b5fbc2b5756f491b540512009e475600eb10605dbdfdd44b4cbed72f3ffe9d9e7ac9f21f0eeb5af3d13f6192e3bfcad175cd62dbbbf2b2f6a31dfafe0109c16facb91eef7141ed75df5ef6d8ba7f38eef9658f051b482c66acfc4a3e1faede29b68ae34ca8acee63956fdd7beec9971969571e0d84b21999cba6bbbb999979c70f3a2bc306d6888890d223e0c2c420aa2d349a36401069e3a8bd5dbeb6f24bda3bebf83d57f80e0a61f82369ccf78db6dd76dbf90d473f44dfffda0febfb49fdc48b4cb167ddb08bfc2dffe95ff95393bf8ccc0169bd73b4f5b80a33337337172ce7735e989999594eba855e2c67bbf023c4cccc2c677777fba0594ea0eeee66660e02cbf90333f3b39c4098b4b1e4e25c73ce39d78d3b4adadc84107657503d2c5bc7f2573aa450156c8f9df3760f43587eeef125ad63450f16d2252bd8aeecd378c04701073a84bdb459078b1721d0a17a9ea0735eb2d136311bdf1d9b59904824ac02d8a4971bc16c87608809cc7205426376f7be5d1521f81bbfbffbe68fcddf6c377333d7799164dce342ce6454352d51cf8a12593f64006828034343436644eae48ff8fedc7c341f7356b16478363e63d637563db46ea6af22864306db53e34159f5da609b8d3df1062cbf831f68d7b6b96ad79e487a4f2c5e56a0a308dadfdddd73c4c5f4d95872bf32da097e7a3d7ed7ddd33e53a1f40494e5642c26aa9a33b890849cf4875e2ca70b3582b404c18515382092c452aa2845275a2a954a2ca70332c33d3ec2ec830a6335c43f32f9e4ee2e7bbc24ef07a1eeee6666662f8de5135353dddddd56b25053777777b793d2df795b49810c000f94257377773777773777777777b733bbbbb396eeaede91b9c1781302c3c02b90cccc3cdf647e5f2cf3401a58e10f6cc205f248c84372c8635fd0b31a0063a0ea1b8234f07958417a609a68897243132adddddd72522ef4623979c71035d498999959ce2d266d2cbb2483c81200b6df7bedc7f434c618e5aca8f67732b09dfe5943a46dd9eecd4c03283dae59c80959fe796d31e92564619003e3c0f8c0304ea8fb0b8360100c82611c98492f27c4503881e284dae705ddefb9bbbbfb1d49c28dbb4652a1a44703ff7a01cbd956d89b64794563497a6198999999bbbbbbbbab682f424d46395136fe2cbbbbb334960ce6e57b4b24b38a32c6e891a3fb835154cdb9eeeeeeeeeeeeeeee66669f6eddc8772dad85bd230b7a7a8f11632dd424a5fc67b5c6839da5fb3f2b880ee912daa047961e59584ee61f2b1cc85d26874f3098e11231b22381ce9004ddcc3cfb5d4bf3d0dddddd2d279d825e2c2763e00054a094040066099631b3edee9eeda0a2da5dbac7ae0c952099a9ffd1f797a474d9484622f6b6b0d6deedae243e4602ca14ef4142e66ff0619f192b58cee746504103586fc13a8d75cc7a0ad663ac4fad86481614fe45f5d21a55bd998d9fea2d9df05ceb093636366a6b34b7d468b562a726e4cb6870fcd49ba618fb45d9ac581f0935c10403cd8a8ef5bc898a7d6666878c5389bba710f128ad1f2496d580480472a006ca66b21d4a30bb5fb91c78afcdc7002dd0448abd4dbbe2840dacf12ab2e8a415a5a8dba902793db248a005fd19cf72f23811eeef9d75436af759abda45acbba95eb6cdde39bab99211e444b53b3333cf04f10c8442b66099c63256cda9d5d01f4e849c5a0f7ba985d6eb0c8d077c1f415017d3b713e8bb6ae37abdceb28288bc7d047a3eac1ba7ac9b5855a3776cf41426820f637c20298afa988592188ef54968707c29df291a35a216f702f54e23cab7e255a3de67adbf2290f577ce558b72f2ad778f42944f551cd6c31fb9bf3ba4dc7aa1112802595690eb7d5ed07ff4707455b152315e237f389ae412d96e2dac59e9eed05d0992183f66f18fb18bbd23d127b1d0521f7f9e8fd1c38f593c6eb1771481bc277a1781461fb58ca818eb8d4076f4145571fc451f5b14b6ff4eed478d8ff51a19895e747346ee6f89461fb3b81efe6e05113df523cc9df03530fa2860369e06367a91086bf187a3702ce61d8da930ca7af735c6596ec8f431e6632a0ef9a377e70327b63f6a712f60d60b1dbdff84d68d5ef4d787ecfc1ed5eb78b2a3bfce07b69f62b2f118fd09588334231ba2a825be55a316d7e3276a8111889ab066a35bd9013b7d0567d4028d5c230dc8883f252bcef4f1a9970c881456e5cb7cc84e1fb1698a51cbe3a1cfc61730f66dc332441096882fb6641b16228eb0433cb102b00d4b14285decb439fec732145b00dbb04c09c3629992c556d6fd8ee5aa3d1fd4a2be7d35e1cff97ac9137a4f64d150596759416e3c7c9e9a9d7476a675f37ea07533d522d0468dcfaa0abbf1a4136c7c8d8f79eee1f4ac03a70702a79c87e18c2699afa68975a6af1e94a97e7a20efb91c5fd5fbece87354de81f0afab1527fee8497fe5097fa90d068cea745fc5317a5804d2f4f70a9fc23f0ad88b70fc45830142dcbf770f7faa1e08b45ee8f40effc6b482e4f857e5805f4485bfd2e2f8f66a7060f5bffe0af5cab7029ca8a9e6f8a934bdcfb1820a5584424d41fec4b623b7a3db01f7defbaa55eda6c94dcf3a4c0cc55eff9c8773e393d8875d685da5f37ce8e05e700f2f3fb1d727208f2d1aeeb337deedd80b9fadfe4e53bdbc63bdc6e47eaa38ef6ffc346b4c35268710362137fedd9c86d875f606db88bd77160d6ddf8f6ee8e07ab88f56901b5fbd63ef65b0f90e73eb6f60cde2c0ee63cdeabcac3f857a25a95e8942bdf4847aa9a85e928d7a4957bd5fa3de2ca6de8cd24855d439e762c5a17e7a473d6d817be1ba7bf08dde1b559ceae34fef5f41e71cf5137d87d18a437efcfafd45604dad7d5b1000fe7efd4b62e12f9df9ae4ee767aa6c166a83b53638bdb375d5c76abdd0eb3f3f5a41dce354a3778b8669dd3b9deadbd53baa42d6bfabca5f875be05e68767ef30ec6cd4e58bb95b70396fa8ae7344d0ca86ae51e7ca2fb014bfd845154956c25e6b805ae8777c5713c2a3c15a218c1c57bfd9e115b8c08c288277600b661a1f2e5bdf7a2f7a27ed17bd11375fc07ada82d1994587b5bbdeca2f62217895cd45ee4a2f6221cb6ee0efda1a81e81f3afe8db379148d43deb15bdc8fafe9bd376f48ee32fbd8657d2a5fb5f3ebd1c5551a53c06a358836d67bd4aa86b8a9e8401123e3600b6614142075b8ddaf3317dc5cc38d71f7e12f667aef8ab23d05e675dd47fe18e7df2813ce8e4a7afaaf9b1aa6255dde92786437db30ebbd0c6b72e5b37fd8c3f2f7c62eff439efa6aa47a0ad6e128b339ffa2bed1dbdcb69e76abc73f5beea6f4ebbbfb01a188534ee69383789eecb71b4e63c5b517f4735e7d9e85e54af55739e9d554eff9ec2a013d763aab15e776f93b092d18b3b5482a8f4d86ac44060c3dee73ceb2a874d51c266b66199a203151eabc3362c4580b155bbb46129e28b25806d588a9862ab56f1bcae5e254892bcef0b02ebbe6b831662384790d8fe661b76211421d0f2bb23d0769b6acebb70c75a51fe55effcea658cf16b60edaaf16d6014d244d15b396ff4570dac411a1a58830d62ed65a36ad587592f74d6fe5751ef4160c5a1ea757d73daba7f93fc9bf31e17d6897b21561cf7fefd5eeba4615aaed73dacd089ebf1ea95a4958c9efb5041eed2e213db2fad2c68b316161487bfeda5d33a713da2cb9f813bf6e63c1ba47980362c5280480d8b941febec834e5c8f7e67c50a75b027c4962b7402ed0c2a1ced71c8e0acab479c75f6d991d5ea03aa3673a96b3f468dc77b3743d59a8ff70c5f90aab51af85a6bef5e92e726f0de7b0f42c8950ae62e0d039d73a8146d5aa872a788060000080083160000180808844282f19024a931e37d14000d6a8a465c4830140c23922409521845318c32c6104000208610631052101d07f36a525cda76f6833a87e4ff3e405b5ca6ff8147162a2ae847b235868e6c6b3c1e80bd0d9551084c2dfc1cadb00faa9cbc93a82e8d640c66d260e544d97e88ba32fee1282b491ed31a7046de09a098fa83c3d12b752b570acdced491e333bba026762e6b2d7cc199d0f835432041aab2dddb6363fa72796451bdebb1a5b6dd99421f5c81c9636cb3b2ef44df5ca0652487ff57872bcdae7df5cdebf645e8e456c4bde46234b1e3098df2a7b80b88b5013bd1e1de6c4e50ec8b913c809b53d2ce29be28072133f8d08c4f9381335253108b05f15a8ce01b5c53210b41334c2be26b4e194f268c7694b1b04e986496e801b1a53d705ed47eda370ee10c71a7fe1234089ea5c849768a1fed418288c2f352a4bd4c52710b2b97625d8195b0732fd8dbb166f82a700867f721b89b14d988d9c5c5c5a07a15abffe51b5153ff64fd98f5183e04bfdf77ac527b3497e25ec2eaff51f851c07f52f42124e4546358f5f2fb62aca0133a0028ce3cebd5f063b21d5b9377d62ea89f1d8a01a5e8b66cf205fb605cf4323b3247a157d3eeff2b698bb8f7da9fd1748d56ee0d435042eb1089e57f57030d9ed5d86551c1b2e1dd7bc1d3cc5966748f4d4cfe51fcb078adae8b757d0d8c5a32a547454467a1b45443bb7216172eceb8645e7d6f7d51c438553cc978cc33b6468a5240e9fb55cee34a6153e8c689eed18ecff4298cbc7073644fc32febb0fbdeafa4cf9883b5c1d4c01022561aefab1057dce53a1b69d97d8393e86a48e6fc444562a4deb8e14ddb078f08964e5ffd37629c75c25dd98838d50f075ab48cf2bcfcb5e5cc7380e0d818f132b1471fff63b8c86373cf3a74851801d82c0f2f8a9bb7268d18b5645861edf1425a3758b934bbc483ed352c89fff1426f117be779b1c8cd30e863912b36b0ee17ca5d483198eca501289f0438649ca5a12431aed0a1e8abc1a4c72b997c2b3cb2ef7336459568d36d77c7fd8036713dbcaddd545657c227b23730011fc3c5b2d8e094fbf3931b086aca6901670218a0884968dcf3fd3d70bf4e6a481de3a57cd411ba3b08693f42f169a2104da1a807e1714aadfe6f295c9adbb118a7fbd294f7d0de8fb6d771af8e04753dd1d5f375539a41ec4d0729044912f432caab7a5ee893024806b72dd43bd7e14b40aebc2b1906d2e0e14ee06a6156690062be4733c6ef525e632f8030435f2b0a49bc91e46a8100448a180a3166a708d664f5b0a6e824b2c351e5d8e70f07ff9e0fcb9667d41647d92bf3d528eabc4ef69b80a1b855bfa367fb765e50985d29d6a9f640c50a7e3b0d503c9aacbeac4e17cc85c5659821ccdafd12de63abf43397b70dfce2595f1dec2854a1ff1651ecf8bada494025434ac41922ea61584c37d134951cc715f96b56b8f8693abb8976080701043ba38f5a1094ea8b9771b1b9bc0496db7d6fdb16464bfbba1c80c6ad1db4e263d6a8d60c6fe1169a3dd716df6654ab6c194315148984b25f9358211c9676ec116f873d0a2c153502dc2a162d1e0f0482a796db49ef52d8069d6970a1b543c9bcf8cbf90dc20b6a3f3307a98ed7058a1e9ac1820be1821171e0c6fcd0772362aa8a8276f9d6b3da1901bb9328918202511b8fd59f51a7b6a7f38bc6b02fa3c69c3a8fea3eef508c7a5eb86e343a4c463d9042eaaccc493a872648bc18ec398e5614ad811f0db7e87cb574548787b29c6743045900694e24c117884bfa6fddd5de2e0378fa7d88d028a87be7e228ecf6b2a39aa5c12e911020589db15f8be64db95b71bf0c4756775b239b787ecba9a8371518442569b505e63227f5db5eb0cd2916573ddcd7de0afd6902ad3ae8aad4dc33350eca218be0a4a30bed231e88011a2c3e0f35c53276818983c688d2a8ba498f3c1d11b93a237ff16d955edc002526428031d2f689c4e22cb8533c929dbeee9f5b88101c6331f2cb986b24ced3444dc88a24677661af615482a63d6917951b1c08c0ac9120fa09b32e161ba8eaed15c7e3d4b3521b02476805a23df89bb8b703cf47f0e5d0a8a417f76c41191ec6f8a340a2e4df51084aebe051df8d9d4bb5f44e2eb3518e7c2030ede6c2ecc3a0da05eb38b467f22d1f9b381a237c37243b7b1dc0759f317588e823e994e5151a7c0768fbe8870ff0ba8e0fe6961ddbaf1f118332fb5a9de6e5671f87c24cc1bc73dacbc0bab5b6a1dc2bda45759de22e5d10cd6fa78e2ded304e2a288387ed3b51c95178cc0a06fde85150036640200007c581cfcc9eb68f704155bfc832a3b462e94076370f2799d75a8c917700194dc5b184644aa8f091c417e7d0771758b685621f7e516596840e8b836e8453e8f8c376d660c0bdd0d2706085750a2229e966b3f36ea71afe0f6eaf2b50a6564b9397e4cd32a8ded66d89a17b9e601fe43568b1faee63f128281c49c6b3caaaab67b276173d058e578ccbfb3384b9597123aeea4592d85e3fb83b7d5b54f614a89d279fbeee00ab515c144b08e29337cf51f3e275641363bb1c4738919945332bfaf2ed61a03186876713f02ada1df41d798ab793bd2541b52d41a009862b53767ead53e6eee18535c65d01d1422f6509a599a72ac7ed80c94dbbd73930859e187aa9c1a4403a338064758b76241388eb7619c352fa0ce999c0d62db27fe2961c306c1422f17eaf7166e91355e2aab9e68fe25d04bfc9591db373fc12e55ed99dbd6c433dc7c170db92f65c8ed2c540438f26b6a77b7f7a049b63366d78af1bff0f418f1105bdae699ae84c2254af9d81671595f23bf62a54eff2b40075df837807c8f428705332427c5cad915d60e03c1b9f0d57f6a5ade305add67c02116bcc39be75e09fa0a8fdee7a050901c1bdc18b5c188236a3fad2656926754e89ae2d4077f54faadd29b25eca800052d284a8b314e652c4fb0fc130c7c8c8603edc9d5790aceb3f64ccab86772401a019b7de4524e654aa8289d77a9faa0e2a0873b4d1bb6d4bb2b152b6e963f24e0700fa62743fcda5a5742ec6407ec4614a292bce57b02a4608ecb483ec38d243d99e095872297af9e19a9046da6ef416c6d4ce7ab494e18eec3b8f9360e1bd4062a343dfda09445323dd4cea18b92f462b38fb3f039b8a6494e040c83cbf6229be1d58148c641ae960f18969dfa0dd463e8f44716af7642d7d72b1f3423aee8faeb671e61d49e57c084e3035b3ecb05be6077cd748b4eb0273c0b04140d10ee120ea5ee235f21c01f84b89a1a18baead5d875f149d70484f01257dc522cc7172d185341dc5205c9150efd1154f529166b8a2b5363a8ee430cf8bc509116a26d0a1171d157488e6d58a43e3101a948f37469414839154473bd45c251b1e7f6b7b64534aa41f34a01d11e36f7c9531a11445511cb41481ab85bd6e9ca83cd0aeb5502aa2ca0c80b4adc38a4ebff4d33d827824b84a58c606f62dd38aeee14bf1f452449a6e3d8ef3f321f141af4c26332964d95daa64d2c82ca8ff919aae5527ab394ddfbd455806614a91c37727f0f35a34880d8e0c11c54a6e72bdfb68b975fe241cc40a704c4dfbe11784d0812837666a8fcca22e63e9068e9034f7651a7fcbac79782e3ed79ac6e0715e4fbc1dbc67e81c94e6522692dfedbd3dd3ba712e5aa7501957891354df1bb7a045ec0463dccc23f8086bf46b08f12fe18b9b5b042b2989bf4a37e5c73a25ff4e59e91a66a665573506196329ae472bc9cc032dcb2eaab4d1994a8308520c535fcd2a345285bb25c184fc6aed6824b1a0bee446b92ad25f44c055c38f74e486c32f83bca7c809710575fd882517cf70c09c9ff99b017e50a2670c84e068fda2b5bb93e89875a53411e2254d940d074ee1ea507cd6ec02fc0e073bbaa4fc32f4e8eb25bd5bf394b671d8aa74094c505e686bbfaf9bd0f13ccaffd1c1e7c8cb08ebcdde8663882675c9a9c2434b48fe43914c702f800ae8cb50fe13f107ec9901bb23b3182e8842fda6b972b8ae095e89bec34b68d7a0588a98e26f2b2cbd536449e9af0debec463133e1f1f90d800758a32618a339b081bf973ec0e908aaa2633ec07092d9baf9875fcd019aaf7052e79ae491d25ad053dc1a959ba65e2b928d30e5c6207d0cfd496eb76297a6b216d4980d119d6ce31306015006521ba7a108b068811db4c1de0f1bee778780a3b80a270e8e28cfdf2c8c827ff9a2b6f52b9ebab5a8be4af5c45460da3cba7e25002123cfb522631a8dc828b39790f0019d7169d90a1cf1caa7b853a0e880337172bb00598977e1a13180e7f418336b5cb7eae541e8cf1b7540d7686139a439793c57400703fa3fb3238bd7302b6e4b52d55e3f0416edc6faa116f2beb75d17aa7e694fdeddbd7e3c16ea6385f50d08894783faae27826e84e076e43c858d8643740dab7f70f00bbd608d8aae40121e81e34840f40067a244b4d2712848d039275771790f17fb2330e80522c1a1ab40ee43762c4c2ba387da1ea2146dcfe5e091902af75988bc1907596c8b59363b848cbd999f7ffc7a7d98bdb46e87d54467c6615f1078e4a2b78a151c420d727c7e6579c2fecfd9990e04c106c60dac3cfde03a27036d0d1858715fc6ea581c75c53ed7105d4a6ff260d88f21a03b4a9ed3361549ec19d0cccb93e87a04ad0fb188a35b3ba25d8b700b4861a6af32f6f7bf2015d5fdb506eb9484aef16f08a1f3bc881ed03122968fa130dc73ac4fd123457307e84c94346c5c6e18947c14924a1a6e5ef303bfee0634445e08d90acb6de5e5f5412284f568653e46fad9449c64fcd295a166fbda95d7eb408653ae6ee44b1ade89a0279bbef752eb7fd47d2568a19c2ebdeaf4c0fa8f6b76edbee727df7818a4a743a7649d8ffb7b9fa33fd3de70499c9da35250315bac60c9a298e76069b9c2e928b2d875e6122400323df30533f432077ce8db4e502b560c1e46b36a7604a180817918294a9c7515915326a77be53932edeedb8157cf08b53b60c5f8d27701e6fa53eb3d98e4c17640db818d0606e5a2725a7f56b19428151f6c9a51b8531e9088f38329973c6e6dc3a8115ea8860ce2e6523403e6335985de31dfc63957c57743372f0de9826906f1c94dbc3d9642d8e06cecebc6436622a50fdb5bde07c00025f03b98f00aaa16e8f153839caced58e9a28bdb6271cbe0c2ae33454b7992c8ac11f88bb91959077d6f3bfa863fcbe86ca6467585024322802b461aa671adc8e159f88c0d622b12a40a0c2d540abb620e7dc5fd8b11405af21ae7d065d1ae8f8f7ac64d9d3bc28a96174d34db59709d6f17b260d0e0a0ea2259cb9a8f91f396caefaaa1c12337b30a66c7b30fdacde1a3e2f859e2c452b8e860935a2da7bebd0323074ffda3a0a28a270e760d9312a0a0e6e19bef5681ae3cec3d24e33641896e08283e6eb707d2bd33f128cefd6730020cc68f9e47b405f2a282df012bd10fcbc67066d5ec9076d9b507371f63f52395cc6577c4cfeeac4c8adddfcb6c10bffa85989a2de370c9ecf2f8a1228d750da8fd45b1666c0311ec948245154b5185d6e8d8f18c68cbecaec76613e670db01854518e7476f1505cd128d5b44d811c5613a80c16ad1af20fdb098e94466b47e414f0fa7d486a7d183cc49b53e66e9a06dc7d2098ab08506626a19c027f076fe4cf5bab14556b56011ff18ffbf0489cae60323a49ae2ff2a4a5672f2f2c04d72e7a45ec30fc9523cbac4e57459668e870f9a91d0c3e6fced15f5e9dcd843a4d32cf0790497df230c05ff39b1ffe8662cdd9376644d3a18c272c13682d06af5b4e6922b9e97fe16a39efd120fc285da3602a7cc2d4cad7e186c829ad945452c859b9940a805fd6d63d8e592508e35c14a55f3dc10fd44659488f972ba50ca4d4fd99a1b55bff427c8c9fdc8c805f9048b15fe79c79054e54de102f1808a8e0c2139ac649b38505acecb79903d1b327573d2ec021f1f7c4e78c4dd8174315e9a8d00ef90ce45a8b014a5899a782b7be3857f07cd7fb6994a3987c0d452fb63a3d42b71dfbbbebbe394b90dd8ddba65c3f0b00a2ae8ab4a9ede564f69ee85c300e68b92d3313d5af261be446292850446af2ad9f2418ffb6ba3a268fccb564efec9bf416b74d0f49a42a355b200567bb0e7af7b0341a888813fd30a44d183a7b5afb869652de574777a52147fe3254592f4b5424f9015992612ab933a447964a71a897c1677a0d5c030d10481b210ddf1f5790f88fdea3fe1b1c964d8642d90ac67ffd613075b0511bfa65ad0ac8572578e706aab1d0a85802de6f2bd181d55d91abeaa54e6009f1129f2123f530111ebc4f0cfee23bc5d1e9b85ca335a024687db174893d47a3ab5cbc63ba63de460983b0c72a09e937d8603ec2034827caeb01ada8bf68e0fb7552b5c1f4966fba2c4c7aa4016fb50ac7eae5a59453d7296e75e9054ae38f3755110c338d109434d86c36dc8dfea4029488ebd06b6ee7a7cf40a53b06fd785ee7109059a31ace039ccf5e440b7b52a5695f006c8c95c5362788e62adbb8f105205ad8703ab3debe163f4a8a61902be91354a625124cffcb22ca090593f5e08c937c1ecc4e1bdb78f5504cc4f934bb7b78195ef0aa07c19287e8435a56bfcc115f78692e4bc790275fbdee1c8ab3ec7b98861b6700a0591899ece597fad9b871cc252dea684d7a6801b81d1100c091e2330a9e2b0d460dbc0cc612cd519c0ed87ee541e29a5c3ddb142903909d36c172d85268fcbaf1468fd27db30b882ddfb06a50d75c1e351ac13282a97d2b8d258f28cd7d1c0a202a1716578c1516b25050eba36d163f6a3590f01533856e5a66fe4a208571d2dc2c774711be4557608746e56a105571409ec112c0f13608010d7ba789695e795196959968c8cfeac8efff2625bc3a17ca33b0811511aaf213ae513a2a554496b9918a76c13d9dcf93fbc30ad034e4073138e84870fe6cc5372e485cf9e7c8c5c0ef114e28b0959e206df908cb2f69166ca623a540092220e9354a886e2cb0440208abf8c4be2791cc491fd8785c622832daa6cb207c6f520d05dc82c1a92ad18af803c1530e3bb0d65488e2f2ab3b9a33868a82e949bbe378a939ccd7f16289127b5a81c29ff88cda2d91b8f923f8c908042faccdbf3efd7f13adbe18e505b945d2d23a6b0bc23a63020c920d5ce36e6609190151a7c901ff7f8ad3ed744ebb8118a353378b959f025cf7abab48e81f83c2971b97d418672df9ba9f0b6c8d8d34fd285f8d256d22ac660e0081639c1133ea7e7d3d76ddbe8c36ef3684165b4db8674b7698a376a20795401e2e141f884712a7102fa553545c73bba36991a7afe5012d858d6a7fcee55c7068161de500f384f6330abf0de7adcf927ea3bf090978e55045eb9e5bc1fe394ec0af31788c1f210d880a94abf08dbf30de99aff1e6d10f2153cda92741af7cd0c569a2c1d7c7556b42496c5a543751dcfa8c2f8d801436380769fd453b084882ed318562347717072c68e0434dd84e42776d8424bb3a83a49831c26b0f65a32d13f1030ce083f557447735f572825476ab04d48bc55ead286d3279521079879d2e5f29d8196c0a97014fad16ee4940fe71afb19f11e01a8df3ea2265fe42dbfdda51c7d08e48c0bcefd3f42405f8e087875e40738c1a6ce6b95648a120e03b21cd5944d019e1032d915cb838a58c7ae0e41d6201c1593d8be5fcd1a07d043b763b0493d22108f758c8540d2f7a3d04cd151fc51b281a3a1cf57a7080947f10773351ca0aa6818a48a40cb1d9db4c20e0413b10c4a7bc3b31f218d5ee0c4e8764c7ed3d0623f8e6542d0d92b4c27e67e043d0a143c8b0fdc1260b6b9f4631c39ad48eec35318ba0781321cc449bbef00db253054e397e62961c03b03a619a8c27a4147219e0c19b359f1ed06457f20e7e57e1e881bc47a289163ae63053d1aa1f624bc094238b96dfe4a6a36ebef02cc51a4412480b81718d48466d44c1d284166bbe35803581a449696b1db2eb05ac1adb83a41e1d4c475b7b2e9e4600d366166568cfe21072d2c9ea363555395913ddb9c2f63aa5e2321aaf71db0db4afc5c71402cd29d4ff7ac5d959848ea858090a0b72c70702a37727d6573d6960c928ed0135146f6c1da3b1aa7bde82235db7125d4114efd6dff72ef9b560940cf2f6711666a8614ef573572ab3c90d2d7ff761156bbaa6cb1820f03baa2bfdbb9d33ab69c8d3eb7a50c862b88d1de38ecda5c06de0e75cc40adbc9d47140fff4c6b7594c2aa7a89a4e207fa4c1eaa7b2934d68ecbf17d17bd0cb51bea793ae65af27a97c01b5a45ef8cd7e0160b56dc6a7f7b9d5b29b72d06c54eb0fababa570fd7f41aa60174f6d6350239240216488dc756124b6f2ae1e0729e3aa9726943b286b6a6331763865a54e11c48ccf85a26002070a90b4f506c16851cb76a30cc969a4088c1d2a63378a1f7155b2d2cadd1b559780e70a3f4fa1a170d828edbd6661db80e17524eb5d253cb800dbbcc021b1ad225c30a6ccce4e2ae72cb3088b686741f0dc3d673131336d45d172c161722b0a3dd0aa912bf8cb009ba09ea1a81087f25a796db497c6d6ba2d91de941c2b9992d07f274a36d5c40fa2f0e0e69d34520ebe13d27b2a17d9de424493e4edc8a2e4767a78a4e227c0c99679b0f9d63926f6090732ac2c7edddb4ca8d3f5b41d9489884cf79d059ee24001c5860810da49194d8879c047bd606c3c7f70813c9333f3a618c74cee5078e250586b6933da4377f0b51704ae16762ddf52592dbca0f62ff6b80a03b0ffebd7a39472af8af96feeb628e56f2a6ab2bf71f9c750a6b96b49f410d043d9da4d596ea6ac0592f076a5c5181d7443c39ef045476f74d07960593883107f036c09f10383b98b3cb00fff8bd0b6ef8343d3581dcc45f56b1ea1e54f5aef1073995a02a69de1820e06494bcc9f9554ba5a469b3fa1c00b77b0839ac08cf52c3de4a4ef9844bdd7a342d27854cb20d67b03041c7f79fc649b0f5a81c083ff586cac7237e3ebe035f5a9103ef10bfa09e81ff832b80a4479056154e8cc9b06493cec914cdb66833028fff31de86b834545fef14271e3d30c489cc83218381c5d672fa4714a8b1e380e947d1cd64814238ec862a870bd8de5223979bba0c74e485d9d11a3752fc5b9e1fe207b41eccc46acd9f97cb0561896df97217ec8bf8d750a38e7f1ab0057a5c62eaf2e1eb4103d90608ff34945ed7a6e941516d277e278bfaff87f41646ec0cabb95257980cded5ebbb8cfe734da1cd8bfa5999c43352248933eb67368d690655091c746ea757305f6c764e01f5b6788661c58400ef3597952438547f91576800f023289fe0985e874c7900356b7984d26f21e11e11f47352d564c806d75cde7beab408554de0f29b9acea0dd9cb71609c986d3807e87eaacfcb4ede9e7d737e24b6e03c1b0f754e041f5629500f40e5cddc836c4af7383540c7d29eae388b721b15d490e72cf9464c90cb353f3eea0f8fb8c1bb52affdff3e06f10dacd014a78c8fac9cb862899edf663514e8106f001fd3ef92a58c6c09564c15a07e3e0cfc896659f04656014760d787547446bc56ba2930145b1779611fe55588ca1414db3c828936004fc4884788685694b6e745b89bb499f8e4042b7f9d5827a547614efbbabcb51bbc26008fadd7507d3a35ad6bec76b246796795a0715ecd445207c29233d25fce3aa8f51bd706fb728180980d8f017028b2e8b1cad70c5d1f1782e3605cd88476736e168a04e1be77268116d10e7b2f53c1157bbe4b7829ef86f21f916942e98fce7d12f7afbc80fb515d7acaf775cba1bddb5c4e951e9bd8a8e9afcb618a379e19ae92359088cb7236955cd20cab06c41f8cc46d79fb0fb3371b1daf7a70f9785921d31b11e50331eb2444afa7740e0211c22c67fb48421eb5a319f301a152825eb06a0ad218d460c27bdd93a7776297afe638cd49200244cd10faca433ead78af044a6047e2f6ee9c876b25a041b84f5c0e288fadc99417fbf536af3394bd6f325d6d9901b16dde08d732efe5e174b018729fdcbab7253fb7fbe09b0090f6c9f8739a21e0f42b4a5af8a11c52306c8304657bd7312a4cdfc46eb2ab0f5f6bf761556bbfda86837fc3bb999ab89f10a8be58dbcc8e794d37ca91a8008093c4efc05f8fe491cdbfd7a1b62dcfddbaecc291661a4576f1cf4e97d700c1161e9ab2e310a570bdee4ddeca2954a58ee905bfeb8a90fe1f6b639e1bbff8cdacb0f8155a1b9fca187e44416df75f94879f8f27e9d9a308d2d06f08a41cb15ec96ca623ab094c2e9531802d101c4d48f63033f347ca3656c0129bb3ea60850dfa9e411ad362284f75225f881794bb663beb9bfa4b980019963dee1d6efa6fdf1cd86f136d80dea0c9d0a939518a330c60461e590186c57b51fde283d30f9b42b1b14146d11eba2846f6d3fe13e32585260f10ad3ef77dbe51d947696f3721df4f52f34b930defe50347d6f16f8131445a92091389aaf760e1811d94e43617c298fccf263580dd5de2ca5cfdf2f52a2b6330626e63afaaa8bd1a38cfc5070678e6e4a8dcf776de8ae6c9e7a053b5710ba205c37a1d1209d492868048541da5b7d6b5595720bf7147106931a4b8e818215010976bf6f52c3a65e83e1258601b9dc19692188537683066c410b2243a0ccbea0953b7866df2341fdc2edb6028d14d1fcc29270a1f5ac2f15ea40c20f33013681f75f05a5bf86c902570c2a2acba15de8875fcd397a0c7011cf5ebae802fbc59f278b7f27b96e5126af88c5914806d579075b4f61a2315ef495d46c496379e4486e07686554bac8c5efcf7eafaeb32d09efa08ac83adf7fdac520ff349e235f2de6ed6d3650f6390e74de79130a562fa53ef23e771d3b3249ab51cad6832c45a04ec50f362dc00ae353d4722e06d8f86b258b7b0f8c48a523f60185eea692386209d523e64c50a2bd6389c00a695d37c94fbea37c194e8c646e65c87721ca64ab01209b0404df438f2b535b972e4b2bee3233e4800db3f690a5af630861578a96ade9ff28e5e0379348bf1b78e7963cf9badc6082215c9b90ea2c4dd65e7c146cc0020990102b4df4c28e2e903a87c94573914e4c1542624b08b9850c24f4fa04dba9921a48876592800db17bfc93328636ba95934e96545b528a099dbd90d98aad25b7d55d0027cb12c3fd860514096bd3fd10924eb9ebc76c5a0d89a0c3a59573a74a9358d311f3fc18e794220762e197edf43f5b55a08502945e22bb64a04677abab6742ec045c7eef5d9f023f79b0ed73dc6a3eb3c9ee994fd582f0c0e2cda8af7780901bf89460109c957527d60321bd690a7aa6866b6a7d17a74f2737fd4b3e4b9be6593278b6087d8c5991dc35f0430d49ca006534c2de610eccec78a00f0dfea3121cc91fc8833bad69e9ea11bca6e281e4b00fb28f3e386cf9e4ca9bc324d5976d732d068662525f1a59d467ad62401a65a9d6033bf29d6fb57f67b19003a03f815f95ba582890589ea5c2603b13d5610857a66031fd12c2c16f405e5c22e16f669d98827ee5df5bd474f5ccaf70f0abc1aeb55b1b5cbb231a0ba8f4662b6d622d30de2eb3919d6e8a542276444b674b14da542d9f0ca5fda484c1d65479a001317a0667d130d74ad35f1a6994d96ee4f6521b408b858d7faa33479272b9a7bfe952da009e55fb246f4ccc91a290f824e4007d71906885e7767c918983dc9121a8c7fa31df3fe7134f4c574769aa92a228a161c47f52977cf7d43a0f4b8418c20b2057dad18f7e73a6006e45f95ee8f0668294a9ac8b71d51a1f2d8763bb89029996c0baf47e1df20e67034bb1358088ab0b2af0e816229fd51d36f64bd11f2810d8182b7c59137329efeffdf268cf93bf5c7563a0227a1fd2a141e332a78d58d18a71ef8308dfb8e04a14d4c768602f50171e2631bc000bd3fc0dd56c5b7c879e0c4c0c14d7b928417dc87e0be4aa34a4461f0294cbb70d9e75a26c8bbabfe9cb745522c7355f0672b9067c8789f66048b88e13e5d833869e07c18f837057228c1f121dd22ef7f9cee33c6d52ddf13133c82ef89fbe658706d071f612cfeac7993de28053d9aedb529d1c54342374be4506fb4a9f1b7d3af71425206860023a3a6e3f280aba3fec42b5ab10831205d0246c1cf0ebf039d4d1aee40c6802ba34f558627d6af9ff5bbca31014e0b48183f1ac40feb5fcd63b6e6ef70ca8c4ec4ab6a7d28432237fdca6e426b5a787eb9069dd98440bec27d06f0cde99ead295ed872659e3fe3db8b3a8fc425fc557b112b11552728481c52603aed4929d2b1706144d0028b73d0cd98183c09b6e31b027aba0f1c6a593a4e4d46ade17b664c0d826094cefbe99276ec92ff721524ae689a933ac4014a2fb89c4f92f3f633d4e5bf4238a7bea76622778a97638d08009020152c0e290819d42118310030090b1f8c8eac8476f445f72c09b82043eec9b81b4faa8d838c06ab9c4e7578513f7c203781edccb25d8948dcc5d5165c9ee90cf4b0c6a8238f4efd5711392a97fa1125aee05909f80c5ed94c251052c816334e58a5cdbf67d8118f49c15bfb00803a0694cde08d29931797dcfa09d4cbcc393011af2c37ce134bbe8daef2096d19a8002f86e823720fb200b69af40735f588b42c85d2cecd4dbacd25a124f166f142d94cb2a600b9202c1686e8c28e57884d268209f185d9134ab9a1afce693dbc58585abf5d6435c98b4c4a172ae313dc3f5dd606a927ce3091f08530b0da68c5f1b8de90c0c5fe1c27fec2722a5d546511b394232e74046a0500b31aa438ba4f23cb48df590d388c2ab0e2d1fbedbad31d29d5e77a832f6bb222dee79fbe81e8c95c3ea6f917f0429e611dd89aa4c0735673bb68b264fdf180f2f0bdf50100956d3d071c4f0ebd9d68157ade97eb697244020570248b2c48f0ef9077715302f8014f85c8614b8fb3a5edc56d1aaf05064e69b43eab7138241103cd529800e9909c12ffc1fa82af7bd764964d2376acc8eb4b3e32ce0c35d714aa28c09535c527aea7581514cb2120719af385e7654061b365c796df8e714d4b8b8d79c9db5e2701478f4fad6580d5843ca7429d652e753d0c1344fb19e5b422bf7adfebf626a9c65e800ad1e6548284b096c692ed1144c5d88c663143a320dfb59856559a9384b2fcfac1b73dcf580082cbbe7917c5b935ded98d45404c4a0c6ee93f470fc2a2bb9c6248e0509771798cdd023b1ed2bfe02dfa31ad1e70881f3c7209404f6d4245f2f39dd71c95b9a11e659e4d7bd6984021b21c950a8f4890cf689dfcad99257750be0b9fbb6a127d001493cb959c7ecd80f54e37484f0da000bb3cda89f59bc80968339b3015fb23413abe72b6747c87d86c98d9a995b438796d1e0ba4992bfd887397032c81ebb3eb4fa977ac3bd06f60586e1c8ffba0e933835603f91d326264ecd35c0a1f591a0743b88e08be2944e8231139f0ef65b962a50a59138453be4a73e945b53cf63140f4918fe81f15bdf352446d9628f81ec80a66755f45df32e2ae8684f190762ca45f2b9b001570f6854696d8b9955775003fd619c3698a3fc30418e0271868c4d34456aabe2bbad348f3ca38009687130149990e3477c27c97c93dae522f911f6a674cf7de3f8bb635b7d2b76b0a78537e86a03aae1705ffcfdccc562b2ff9fc47a7962ca24a328dee7c49076a34fc36bb7348bed581a94bf29896d018a39979905590a69cc57bdc8ea2d3dbb027670f35237267aab63e664fd09945b4353985c7731177eb5cf7357296c3e10e6f2120cfd9307ae06344c53fb8d1209a91c81a60fc591c4bf211d23c80f6cb22099a92ec438cb41f36bbdb0b1a8001576f66aee22a0e69ad535001456eff2d07f846bdccb5158dede05067eba8505bddc7cff614b321398e7f1570e9f27bb65594de3bc238596893d962b7e1d5dc9ff3d47833bd8d8a274c82243e089817176f177737788c61dd6be2c774b2d3beded655f6e9a91dfed754e44cd285c8a0a2722abfd3908440742a04d79ebbeb09271dc9c30be8c55819d972d0c1a4a60d424e1836229241adc7ab572149ac2e5a01417c68168f66b5d254dc9bdc44c75504b0cfdc8c6d188fb8f70b4b0b32a8c54f13a7a5cb30404c84f5e67ed80b615d9fbd27c7e4b0d55ee08d5556ae0cb8502030fb101a7eb7280a5b263c2d94806ed0414b823f2de1ae964a52633303909240520befa1336433b46a24c82b249f5f657785225fed5c35a8b17bde8fa07d6bef2b1c2945aeffcbe25c6516ae10353f8fe54b1289d1634146a856623b894616873fefc5a0a42b167d1148eee22acc72927cd98d2278eda90a8e57f62e407b47d3f2866f2c57e93a9682f575c2924803470eeb19a36f71bd9d6caeb174b9bfd31afba3343adf594f9145b5dd00448820924ecbe6a6cd90a78bdc26776aeb90a7669416725d00593ec32e5d12fec7e6003939543f870ea04efd4ce321eb2bf65b1a5e4c19090c00e3d24c5d4ce3262df2d6668bd69177a0d6764ddaf371c6fdfd1ffc914890e2b62eca127b5d57c45bf323012c544e6886a40bf425c04b344f9146711ce120d7859f6d3e7bf95ac2c0a269ac69a7b0fb7f81aed86cf0d154d43690fa8b56a5a322b0041bf7f5b62fb239c4279f211fb54a868f3e7c825b383bfe660b4fcd73fea5428913133853fe98363006651a1f3f7d1b1ae30ee909f8c3e71b26add2174f164046257f19c3cd1011b60135dd615e39712ebaefccc04254ce28c7ac189d493ccd4c708554b9fd1c04885abdbf29f6c101ea21f44f3594082fdd2af92707611c01a32f443b07fe90552143124364482fdb696311198c20286d3c843eda4e55109f81cde1f4b87ba51e1d53b19f0a84081d2743c364c1044b7f175116f825d9ec4d0ecae0b58f3f255c439d0358415150b5c0c250b8f641433454dd9b801255512a1b550b5fb24cd38f430b6c58709759209003025236ee189a4cab30ccfd93f9d1616717505e6cc9ede3a4605f3426c426b07a38924dc526b56b38923fc4c74a4a0b6708c9d5d6be24d46430cd3e5a0350266b4fdb5947939c48bdf495f1b7f09f315d1040e2f0b7e974c027267cb40b8d97b7a9b36948424c01fec371b081e82b4fc2510c1c78c70167a6cde6fe5edb12ac03ec2825bcab0165680ec59136270e26b7f4b91c5f88b45dfaf97fc54d23b6bd615969acb7483eee8464afc294f24815301eaae2b10d035268379d61051ea874201c9d9c513a7c9651bd72d8851e68fe2b17941eacd8ce7bc56ccda8554f546c56d2bf6bc84bea7c338322d2dc316eba31e1e92ffbe8be45491f51e08dded902d9489ca8ada5f49086493d5b92484b1384682f69982bf14976eaa953c884c39498c7bf43bea8fbcdc77841a487948c56d7a90cbea9914b701a5b36db9cde1d5593ff6ceb3606da2c05ff6d6973f545c911796f2c8944cd5b34a9fbbdb411d40d1944f8e01ad03e31291de9f2324c7896b064fc143f7e1e4611216d3416a28708d9091891dcf1001f76754416944b532ef29e6df27aecdd6e517f1884286afa80f3f23d8618801acf14987152a1238583ac0c48b2fc974703c7a4267e815bf7479c635b54c66be3b4d0a2912ac4f961107e062d41f31ac1b6530b907bbd7da073b011c93702575e0916bd3d6219fa7645dbe442e8e47b00b55c6bb1c63c5e9427c2cfede5987237138030316f884840c05be7cc558624051009af289e0ecaa5f7ec70b925d80296b05112d71b124669eb27252cbc2ce2a1fedb8b7ea6b966c9a5399dfc44afb646f4cca1862d6f2eafa9b2d4ece360275b7fa38296f6156aceb2341ef33cef30ec6f0ccf260e7d2ccd5c22066611a2b74bb11298094f8825ed71c8cabcbeebee0b52cabb821de6259736e977b4200e6ddafb6be61653375bd0ef6c52267186e62a65d2952887db5728ccf0ff98d2a9fd05742164ed1278ad4daacde6e4e1d9dfdf68fefac97dcf6322fa2dd34b6ceaeceb4180454434d775dbd54f3fa0737ed0ec846194938a01f1edfe282b66d05a019cd293f1f685df5c851258fedeae110bf0d11ee0750fd38466501536bbd7a07184b77d508f81d9383fa1b37d190e7937887aadc603a4432186c5f389b700d548a519b9c39fc3ad97b2e9482f987359a54360bb15e70ed0b7c39b464d5e4a453dcf3f2524710e85339a72404b4880c448ee7fb5ebcecca579ec0920fc3f82480c95712038c23a87961b86fc4659df9ffea56f473089ece0fd572449c045c6e9244a56e6a4ebee5915767b1e41d78e10ea52d67d44177e7957b50d344cbe0302a1c754c348bf5eb8f2ae0ba731cc0a56c7b3d361b017d0f730a841a4d4542eb317c2f690b5e39e5bc2c6ca02b177b0db58d3834c0a8321537eefad0777ffebec418bdb78bc06d0633b1bd7fc01f9eb7d14392a0f144ac4f43cbd8b5dc50dafbc7081fae504813150fdc98b474d5ffc727f3ea8f61b3b0dfee6739b07d3270a0edd59e8ae523c42a92f1032ebbd481017549db0b26976a2d0e55174f8707bf9b71e23d7815c4af21a8aea08bf516365c7e28fb4ce2c34166470a0b7512a2e053c5fa043dfb7eeb3c0fbdcce71a561d3c36996c170f6ad9fa0be23cd1ed24252da3891119f5d85200d5edb2bf208c95d454b48575278c3241b25a04cdee9b16451c585d5c3492510799821e25e1d6730eef477192c5dd9b382c914080050e0ca0ce9043b742e61a7b7960874571e5fd0fb82ac8cb4aebe992e6c33c30250888bcdc0fce2c8ca9f096ff188f12d77dd290ab044242f03e4a0df9cbc5972e2539ccd60395faa26300ecce36a6dc8d7fb86326a57322b99fe28fbe20e566d1eba2af5a162809a2ec6b18521d9291b68e48c59575b9b617aff88e5b239462e0c0e4b6133085e4317dcfe231b1917d462b9a4412d427869e1617f69848d3b1c34ab35df6ea2eb0af028ec9c0936ca0de6be0e6c1021cab7a764ae64ecc48b52b0d5460d707b8fec7fccc05600e3b4b0a8689e8b73bf61c300b062d3aa232e0839aa49be154363c34470b5c90d5c58c711447d8d4f726b401146e39e06ea3eb27021634058e450648f98ef730f2e4fc89340718a7a4d2b4a25149755429f8cd7e9593cd47b78870359ce6a6608578d927b5e5c10734be9165ec4457d11d19719eca517fdd7795490effdd5891d35221390f649adf71741b37751422ecbe4ed1696bd2900535f08783953b9bf0c16e6d4813c2bae02543b105888686f8a48c81f8625c61d07b783126262fde875f00ea00245e13561ef031d41f12440ebeebb0e4fb4f560a03074701d490ef3d5b662c639edef8375ea5269e328c3e9f4c0928aef10f8139a792aac67ce1817d6e26ab00371101884d75e4f60146f1ab45a9500a2e470ec6ae39c01ac441c52040b1414999b555477617fd9d01c92b2a48d2a479ccfe38e27d0ac191c1c07c326a09c0c05bf0dbf6b1898ce37f0472c45b6f8232d03c4cb2838dcdc4d6e413d3b6780528cfb0ddcb18d9789bdfed9e79fd2c3059bb82967be020e566c68f80acf212396da752e0599230e4de788249eee0f098696881e887dcc9bd8acf9fcb1cb90fb361e839b84ea38b793263e797a2051732dde839658438d6f326a9c86e9ab13209775415fef87de7d3e95b9ec5cc14aa3638a249031411380251feb66526664fa35c06e212dd3ff0c007a5d1655589ec010459a86d33d4517df62604b025b9e1d4e3bac73a74251ae617bc23f4bb482f79c65c355e220fa45f4b737ad5584c44f1f4f347d75000b738e631474f079d98f904b1f3fc44f5ebd1e7242e095402ad59348b5b6e4a818f222a7aa91b312155dd8f4c369f874137ed0e26331d3b064918ba427d3a4f6b5a7805e76f395b1f8fe6ed0c9b7e895c251bfdf98fe024a1941ae6664adc18a7e15be210d4898eade7c6162c5016ac51e31f93f2c3c660fcfca0c351ee88221237589def032be44b25489830aefc2107c7e57e81af3fd4c42fc064fc0d1bacc4fdbf9ebdad5afd80b35ba97e67e370f60d83b73e6453834fced0fdbaf26f497c9eb9c5f202e0840de55ddaaea95070d8753cf3f71c13d76a7f21d609234319579d97d9d37b9683ff5f147fd5e1aa96bb4a00c64f8f909b246c3bbc1427ce5633bb9c676b37d212673b5e94da0790c3e379a031bed2bccc8dc4fcdc33fad9ab7308034394df9dbc860db70ae5a909dbd858f5b3ca58617620f6d46823783903068574bce3859c3f5ecde8c856b0ac16e1125ca25feeffcb8ce75196489363c6fa810019ddac7a22a3cb74af4ada7b070998d1a1b2142b47c220315974b21bc0b698c5fcbf792b6351762638be3e631ec2079dc29689d1c590f0e37fd2606c69a216c96376aa6074e376a63911f723115f657186f330ff1f2bbd3002a288a7192dd698bf1d7f63c0d02cfb67dffd2b764ce387a5be7633314ba9b97dc5aa53c28f83e81341f1bfbb1a49de69225f0b5339e1775494b5c62072d0116a4f495de4cb77d256740fb60f69a8338776958b9b1b1d3a8c4f842632c25e063bc826012f73d7acee438d42f9d8b26823e5e9a043bea0380a186de162ef8cf4774d46a9987f9e18c5c31fe204fbbee3f89338c555c89cfcd2e074224c00f4de4fcbeeab83297565b4410b6907974fd08a4ca440fd8e639263d60faf48b793c82fce79d78f6b4294d7cc1c6cd817facfa1b0957ecd84baa2211d6d067bf80772ff5ee0918def767e0e81a603f65f79c340f39d2846aaef8e004a9425d16aef38d1790b3a1bce27ecd0c59dab8118caaadcfcb12ec1fe222dca5f967bf164ae70134f1a73e35fc14226af4599b72f028d6a105c02e8c6bc27311581dc5668cf6e8de3d9ee0431385458450396aae22de8e16cff61cca3d9282aba7af182a9c60134e7653dbaa5cc00e387e82c6dc0a560601cfa921851fd11d4fb58495dfc2b9c1e9f5dd4f618aa274ebb113f4239e1bc55ba616910ba45b9a90a3dddaf686f874b1d29dc4398bbf860da27e8c2d92bbb505ac70bcbba7038fdee22733e925f4bc04187dc9020151ce20a9b849e981804b0c41ec9d0295adeafe159ccf52147aac1f8c508bcf278b14ae74cf3a2bdca28d38dd904b14acfeca6df13867f0c5267ae008f2dbc2346b63108c4222c2828c923f3fb98efeb487025a2fd9f6d10b7e47ede102c12041565d5ad8497c19c4467f9ceba4ed1c28d562bd353c3a63e702ecb94e2dc2f3a952ea5306f346dd00f21e3c488a567e7af2d01aa6c25e4bace1088ab416bdd2d3c30ec34c4c1bac4346f31ec0327bc2873a68cfbcc2edecea080f57b8491fc3e9213a0b692dc065f18f593e0519c9cda90bbae747db901b7c600f954da873fdae73391e3528ed4473fceafc557e5e8080a51d43f1d63c862aa0b9f322f4f1f100238c157d3fad8a5ca0046a3359f7ecafb1751e5af381c106f42298452fe9b8a07e34bba11fa2f8658c45f14a4ef7f5f3b9c33a236c7d4592a82a85fe4f6940f08fe19454b4026cf79d34ced981ca3a3d297dc95e599492e27b7eda4460f26d6a98c9536fbbe3c8b9d1f3f6221d2d39f2e573a83640de541df1ed3a0ca6557bd4b591541058a409d2fd80ce2078a9b1f261c68ce16342596cb66407881087d53ff3e39e27b5cbed175f2da66114e5cf4c7b507cc23d14a7c6945076dea6e78eb16cde15652099c572b903f45d1197735057978ee58d45b566b4cf94c42b21c97ab94c9f2afc56238f9ac9b4cc82027b6c58a39e5593dc829351bb04a508614072d727229ae15cd1813497270822b834f6a6eeb3286e6edc250f382600c69751cbc7b6b4194bda9afcba18a752aa70407fc26fc6a803b92f380f0baa4f0479f244f044e68b96d882b7cf9b9ab01f35d2b5f227b1907ce3e0d422dbb1ca5eff2ce16325bc348a18ccad2699a1557b79b5479d40c876fffee443cec9551012bd18abfed611d47e4e1428df0179a933ff275e625a76911312ca07f0ba9723c3561916a7f108d7356366f3e9469fa95859a94dd58f649620a7749533697a0e32e0d39ba182b4630295c190269e3a6972dfd9c04b92878a73a5c8580c6fcddde569b8b122f7b45c968eda2729f0fc878744490876e76922e284b1b4d30b83d0d017688cba93e06c0513d203577aac687b4d05ce454b6e7a5db41251680865a75fcf6ea4ae54270fff9481f052ade9ff2b20ec22bef9f363c4d0e1b54fa4e8defc3537b3b8844ea37195222bdd0dda06389b477f5ac815cbf9af74ac44ab40b38604e55fd40ab41c4094869ed5f2dfafc2e398402b2e48519732d01e46f15391f947d8601960d1ba4a2c85bc9429be9d96c8e79aa0e5e1d685fcb7c20571a505e5ac49ec63d75c1b628eb53489cf188e293db78d160d3538626c2aad4cb8596366bd36d9bf84753f138ac50d0d2dd09e850bebed26d97fec199850f847356cfa4d1b5e7117b8b4eb33027c621cccd68ceb94b92e12203c9b99591b71fa12bbe87541aec143e56c763dc3aa65503552e70b83f0c01b77406f536914a48610c0d62d182c045637486ab9c884d3db11646b7601112892d3a04c04bbc33b755d6b6faba642d8c5f11a680c7be8ced62d8534d18e83e97c998f9f0e8aad6dfcf4a9a2320d4149d6f5892bad5d4361cc2b0f442666a06766ea7646a6dcb234ddf0d5a4a620d6749be239cc26e6a198d9caf873b9ca46c95f66cd8c032c10e71caeae035581964d7754f160fcdcca0166f439f9877b7b99095c19054e8349a8d46cf20b3ab92b36286e34b9520065c3ad670abeed8784c262b37a5d809282abe4cca798c5d4d4bb45aa7a95079f4c771f0c0546cbe86b63b39a333b38a2942654ab03c9134385bd83412ce3455a311aa41dcea6fb6341fcfc29b53f4bae30d4fcaa085a254a0d007770be24cca00eb702d28fa0545cd8214cbd7c328c4bc6360b6b293954fd9dea9517fa61be42a3bd61330d5cd20ae716f21392f3cc2f2733e1aa59b3d5403afc04c26ecbb43e89442282540508f0c92770def2056ff8b8c873863f525bcfb21324f6c31787756a891583fadb0f52581b454fc20603dc3c91e74a9903cffa48b50441e209a9ae399eb997ace63a0952e140978f6ce0621f75e9a190f6bac6c05d5a79a272bd273bc27f178cf1a958f572f3cf929e2debeb5ba7d65fa6d05950210dd1f33a21b3b59c41c8a69ee42343e31998af52ed399528a2d911edf1cf3c93745818c398f5ea4d825c42c24cc87e2a3d147b7f12750a8d6673938f1e3061700f019c68872b8836409e97df44f4784e6a4e71d3c222505c64692b1718fab92eace095038516b7bf326a17266ad5dd85e5910d11c485a7011f344ac838942c5074cf33de88981930a22a70f0b817c40bce24f133569851c54034fc8fafaafa2ee96cf47e736c6af5ad883c1fe2856e2238d05f9ac6c88c16e4bd2af9a2c2cdef2011133dfa0c224c62e854df310ab939e8ba4dfc0c84def387d043e65fe1058c53e3d5752227cfe460dd2b228d4ee226f1f8f84423121fee033162c2da585a819dbda929f0226c532c4fbc391a6e19a18a1cb609374aa794bb1fbb741c8b64aed42d5cd8948b6c712aa13d10fdfe34aac230d53c1c054dfed1c8526aef10ea93de0a8b7d2e8c32e02e571db7b8b3490168a60b89e67091aa6775788d5b6babce36478c522f15bee6e69de2dc9abbbd369e47f02718241124a6193eec11385093e5eea094b2049f015c9b837583d413b922dc43892b9818f2307c1e700c7f203f9b1c05f775979890e958d7c7503037ac91e2df6cc776bfa27de419a8ff61e064cb235cbe4976822fee08db83100380ed9b4daed97184f1ee484852c68ff07b3d327e261183eb158c82315903f2516d5b1ca19798d4c4bbb3cf37038e85c68645d3fc18e9858a9fb0739f2496cd7cac4b4a50e2d8dbe8144370cdee3c4cae6996373410d26e5f503ad2b4a3498799eb91a256045086cd2d15db9008b08d5f3146d5cd1235b5e595e7a1435e7fafaa17ac2f18830426f3822c83a201c500408b25e024e1e98f10f20486b89a6f861c2e4d26c3b6fd488caa2ac8620a3bcb44c21cbbe72a14067b74603454a2d5d184449b35b20fa31bdb1dd240dd2bb7b522584db49b943096a6440e4fe63d23abbeffbe08ab278d376f30054744c55fd7a75aa2e132346249506ed15c2e6d2e2fd35b9b1e844dcbd11472cb2470b5644ef7290295a4090d358653d70d8e1e50b569b05f13b1b67378ca21fe2419bb8ab7f78bbae92b5ab56722f332d888031776e94fd8b0115876288669021a23bb9cc0ac118652f1117c60e0b04ca59b8514c32f3194788d0ddc4feedc5ea0715e6f85bd663a56e681904a976398b8369cd8052baa1caa8b2ed5dd8933ac15cc6a010252614ffc5c3f155a6966c22fedccfa83ff9891e16bf0edbde97148dbc1002891220e63c7f1f994574fefb20108fe46fcde97d7365e631f1f8037aecbef1288571acb04d67b336057ddb9a4971dc3e28ebbf7d2e59c95355f07f8831887174d8b0445b835533de205285f0082fe8054c200e8acb6645806f3d8c0b46ac93e37459a974654d2e19171d7efd6185ae4ba459678fc025ec8d3f26d884ac32ce8cfa5f2aee3e107306d9abc502664c3878924ca720decdca8ff531ac0b8dbee3107844e5ee0dae52dd243e21900b0db14157bdf01730f28208ccd4bb68304cd052c030a53c97a5bbedc69c857cf213f1677e74aa0c0e727ce57a2c959521b104fb3300c801e75902caa34f0ee3293c9bde3a49c407f85cdf1c59a2827364d7d6ea56609cb9842f8451f4b44d2acc1d2523f4201b0a81ee1dd91d63caaa59be02cd30c6951148f4003cccaca01369e52a8559b137cffdb6cc3428d9db8c8a9de9cd34205858f9335b847cdecf36708f4a991d41b0b33a825eccb0e2dd6b6adb43a4948a68751860fc0973eca05b6824e07a9aef0f6f184566ff532316b399f0caa21808ba50383f28098026e20f57149722c03055cfead293c94a93fd55774004a9cd32f795fbb2590fd749d9ecca7c2c5c27d2465d2114023d899a39bd0ef10d9e28d2e31612c7ca599e5bc56370e109f9279e32ccceb5c002177c11c9c545a4ef667ae411b3a44e7db4f26372a8ddcc7098d30b3b41c5eef0c9d8855bf9529a58230d64553dc28d772e9410d5d711d4c2d0683e04629a2198b24d43a8055f819202838ea1834d8be1f259e5cccfc90d548a3e39e39083bcb158189a9a23c9fd463586e2a4778e9d319c98c6e88d462eb797fe32a6244d402f204c459e751a2f241af609c3621dc34059065fef41159d2d1e58682641a450e1209a72fc990df9c9d874367f2e4d9d211527515282ec23b306b29148a0486fbdd92607e2d6bed4676871aeb9a37f29fe55838fafdb95c7960add5c1c57936d3e2761a056ae92bf2ef076191d160d9da16d9b683d3e851c4cdc5806c23cc6ba89455f3fe5fd0d229f3d8ad7cd0a571cf40a5285ef51820c56cb86a23ff5c2dd85f5e0f6c7279199805bcb9d59c007983f2ff929139507d42ad5bdf7dcbb938fe6bdd18828e98de5c67686cfdb204f4d3762101f3b939f6ce2a0e0fe507ec583931823885d42b0969eaca1409eddd9a37f34bcd742098171bbabc998e2150b6b89bfc390b4839ad2ed4772d314b5679347ce93cc6f31ce6631714ad41045a95e48f0c002cb1bf3db8c6d487964d93cbc016ea2048fc55179ec031f8f83eca2bb42a2c287d99078fecb9ecdcb001f5dd40d1bb355c8c29304a73e8d7a9e73cfc980e8f8f4d09d9ea930f87fd153c8054e883f24634af512fd97d4e11f9494f744f4c33c52edb604682036f117ff9160334e624ebbfd019744cddee1dd440708c7c8e6126d2211cd668c79e68d4d27707a9f33b2e92272061ee2d23fbd2de3ca407bcf941d454bb712434c5cfe037af10a895a801615251dc336f09eaeffb4f0b08228e0b4134705dce0a6b58445a34df8bfb045ca637248d771504b8192450097d3e628ce85bce37c0cf8fb5103276f55c17f62baf2c10837330f2dfae2ffd65a2384ecbdc9de9b6c22a524010f0852088e084de3ae711ff728dbbcce735eaf73349b495558a3a11ff7f876894559caa698dee6e52b288b745eafbfa855d19e3245a6eca42142e752364505d58d97b482b2168a14be2e85d3a3607b9fb0f768e6269ab2264db31a25306c1dd96bc9deab30cea365897c3c8723b4ae0cd47c5e6213f00c03bb854df0641aa3c43490ca9516f0b361c0cfd0f50c6430a2e6847019e43be552bf2094175c0821a493fa75f83a2fda2c140855e18c5afab93eff264b19f237af919af966f9c4b2265cf5dd0aa12accbc0199dc2c6c59ad83ba286a32370c7e9f58482349e6f7391f4b8de610cebf47eaccefd6b75254f2d467ca9acd3487d4a569189acb3f4bc246de30914fd8e71d6c73a8d1702aec433dcae4e470260dc55ff391e5dbbdfa44fb52f95edf8c879f8f93433fd4f70ffe7db1ebef96e61f6c7e049b17c1a22e99f7cc8de6da5b6718ebcd6259dd22231527fc5613ab2a2c5f8c147c177754d0cd319c4c889cee7372873fd83a77d3da9a5f67dabd0266458c8ce3e60a1b79e4798cbd7cd7eb425597e85ed1a7b604b5310088e712675a8256e6ad914cac6e586666e6f8eadcccccccfc806adf74e9a784c863605e01435132ee23558f6c4c0c17d90e806b666666e67e88721d801868bb3939462c51028bb21857c0f057c0ac80d920788429e615309b8267b65926db186a3ff2be013eaf53d6af0d867723f1dad9dfdfc800d8fbbd1c4b6c0ccc9a7005006c9e629b6398e6c7ae93c0400e03667d04f6711158e71f8c73c626f991911557c8bad3107e314c513ca9578a233c877cb190bdd8bd5adaf3201f9d3d275a96a2016c0dee216aba8c9410b329dcc6acc9f4f9151bb3329219c2e884004e45ca10728c00b0a844a3ec11c28eed1d55bb4ceef1710a42c6110f323e40465280cd2b0a767222a5e593766113eb781904db7cc2aa3bcc723c3527625a3d9f4fb49280ce390e7514e21c04090939bb6fff0302211fb4324f520642dd344d137c7f2299a6a999de807d421e5fbd6b7ea21dcc7bd03ad7afe22d6cca090199713c0e7d2202235484d6cb43a7571f692ab27d0f5f8389c4a69b531160fed464fc8ba9e7fd6453c5d83b604808166510c6951619c0610f5a1fb25a4d7c9ee2419b821f7b46b5cee2d776c96af5934d115fedd2da2e59f5fe56e86868baf5e58f490445b6a08dac4f8ca164150cb140d6dd2b8cafa241b0cd3f30cd3b58bc8545d9c4ac28025dd27ef52b89ac92488ab37904fa040e5dd2ce21e1a968f55caeba38bbbb5b55bbfbe8644d38af6845c938f4f370e813245dd2eef15c1749fb49757d625975dd7a45c9e23758140d60afa91eba6075926e1410ab28d5c99a4c8f8f4e62bb38aa4a13c4324843af3760d93ec618e3de80a16427213bc2040c776671802ad777cbdb90b37bc344336cb88d711e691ea364e6bec44233ea30cb90377768487e76f6c6a6e0f7079410a4034217c7bea2c896f6b976641f865015fecc3f4208bfae99abe7d339988c13022e7ef3bd5141aa878684c087d8823f83447acbb99d91534ab7d2225a1bbb8310476268637f989a399cc3c5b13736858baff37d5a97df4d2b281bc97a70deef1b18c0f972aaa47272d6c69a40cefb937b07b30e1babcee1605176ad3cf56be5ee0d48eb0d5266f1fc7996f3716f48d97583bc418232376fcd4b5036ed8d49f7c69a4cdf1bce2d8e2a9a506252b637da4a673f3e00f2f71886e43cce392fcd9368ddead2a5d7a28c61b089264d1a1a8d62f5181f8d2b2ddea57d975eb491a47d94e07e34786bf3b7a19533788691eddfa355a525abbe7d4fa22178ebefeddbc7ba74f7defb4a2dc60623935e817c2308563793f6b58348af4912bd300d06f21e6ad2a315036fd2302184ef9d5df00e8f79c8aabbb4efb9e01d36c5bbc4ae451a5ae9e34fba6baebdc5b746db5ba735e941ebddcd1aaac73b6cff81cd7730cd37d875c642de98e70f7357d1ec82493812fbc04759ac96b35b715e6c8a6d4bdd23a41ad210f527c5e67c310efdc06feece2f36c5bba3d6dde73dd8e62158d4259b77775d83494c72d1aad2aee217efd5e7a461c63665f0f5897efc039ee49e94c147f9c59a385eb9b8f3811d9a1d97a24d3155a6499abe6362e6697a556577777ad235df487dda14dbf81a5a1fcf69d269da214992248364a9044bf9e951a6a1bddc14d69f54d9ec493a49d3a6d8e59ddeb4fbf64dd3f46953eceebea95a0b08b8bd838e914919c3c42ae4f1129be08e111d16f268e423c2c5c8b15d5c4307ef46c4503b91f6380fad9cb56704ad2adc2cf3c1d1f40747d32cddf1d355f4897749fbc8474474aec2f0293e44ce1ae039c228e29b9183300c74448423a008faa12334f60eba3b324cc3689750886398f88611bb05867c3b867bed8e914d97878e42da5485655c48480ce910da2c0d23e44e0493f760d5179be6434218e620f45d1e66966916669669967676af2c9359f06e722a9ac5d166619866e918149cdac88adc40b648062593b2ea9d922aca7ece1ae437ca5edb3d8a9c34eca421928f71626656e1090bc5c8629e367f6cd1d0f468bd9dff9ac6a2953346c9de86aa7055e1484fd6e47d7af4134f5d6a5f5a19f0a57835975e5132ea1293b306f9a52189d5cda89f540d55e14c7329a959437b4843ebc2d6f6f24d1a28992c4ed3eeb7adb478efa047bb4a57e1104fad51076d8a760ac59b6868cf0d25ab2a279b82fa7b7c959f93c6d09eff5e2555a16aa42acc72f791e9c99a80d6a4ddcd1ae4df256c7f61d42d6cbe61d255b4c4f66453540cb1cff4288b74abaca29fe9d4a54b2cca4e3645bbf4912a65d42726693c9fb4c7533fd914118592496dcaa6934d51975b03daf749ee0d738e6653a3276b22596bf9a0f372c89edb913d763ad8041002efbd5e12ad9eec6dae397f70fed1c12226e1d0ca321bcd6555521f8662bf599aa5595e38ec6248af77575abcf7b5f1a4d76d7a6d5a1c59bff73af5418955149ccb47eb0d32bb5e69bec1a4aba21c2ce2928a56b44a93ca9044eb0d0ddbfb7b63533cf7c94d928627104430d234b198a41e908a60a6b7e51e69056555251d6d4923a4deaf14a4292929296d05124a14a3685a69214520a9b3698a5e26944699a669faa31e2fb1884922ad5246d12a4d34d4f4e4e484e34a3ade88f7b2a98a968481c42bcf7453524a8aa274c8ddc7795848c423e5bb1443e2619e3d2c428d78221f31972f45e48499e645a849844a28a0e647e4f2ee2ae429ea3d271ff246848c903c425252d2aa225192fa444d54d3112a2dcb9a22225894cd29298aa25cc8a328e9a81712e271f2ee5194a37aa27aa27aa2288aa26ea2266aa2284a4e11f7ee30be07b3de180c9730125fcc556e9aa6698a2fca4743f1948c4e4a4e4e475114255d3c75d10ffcbcc7735dd4dd47dea45913dcad93c06078f47d929fe424279526a1e0d62518120aa4aa6590c2a60c5214453da9d21c85c5484928983c3cd243253b0633d386ddce757777bb6676aeefdc748eeb88b7e3f6c4c89abae866dd6619d1f413efba1d64ee118e5d8fa0aebbbbbbbb439e81ac596a18dddd6037d80df2f6430edf215f3963f327acadaab7b5d69a6546ddda6e6bf2d674d339e7e6ce0846643d308801b621b227af237bd9a68abb38b2218268ebd225edbdb4497047dc938686bb282289119f91190212a3831072e88a0ea193f03424ddf77c710c31341dd976bf5fe1b157778c5686e918e4aff7a36ba45b0f24a45be23088d08878403addef86dd0d4f2286770cd093595766d59759b700704aab871ea3f52ad19f6278e59778659818282f8d41c532ecc7a011510286470d004ae96234c636449bb2abbbe179078d0821129263de4103028973d3f1a6239bdeecb9b4ba6fb7edb6dd56d2509fc4f73552953677ee6498f72e0690784312e7eeee5f2468ebc10e48dc1d8b67125894599b03092c6a2f4c02031d41eb1341af8c8ed0eac99a8786d04a92813e5e50649b8d3bbf6e3c926e5a0f9a87c898caf3eb6cd9f46cbe587574433fef120588e49b439fd8972e6947016a92cc73bdacf5c0bdee2d49b3265c8eaab42cb4328a5a0f36856c3d5893e9d1e36977abada3eda5f520c6a6a39a2b69eb01b6580e627c40bc1ef8c5aab60508183b60e7c0259099d9e361e629468e5c1e8dd7039b8871d210a9b42f1cec82ed226fe48e7f2edcf1234af18ef247bc0a7e34ba690f19f76643bf1cd91836c366d80c9b61336c86bd3424cf79c7eeeeee0770f8f1f3f17d0003facc1b5f0ff5c959433c4f08d8107948bc5e11bca6a2a8a04a525f3d10320c1b1d356b880f393821e0d2671712148611f036348cb4624004ad286805f547682511a1f51e5a3dcdb0aa1e101f1fccbc2b2de2737933e2b311ba2418e3737933f6f900fbf59055157e34faf590c56f3651a545860c193264c89021ff7af40071eda84d471637f1bdebf2787e12920a82af28e27556995a7e5cf4d17833d6a4391dac7a73b0f9ab462fd10bbc983e1798cc7214b4266a3d1acdcc557b99a25ceb11abd6c2f489685f3c2d170c76810205084442f27b3cd7d5fe688cc8d7f692393a4dd57b1e496b7bc9a6dbf068bc428102048a1106e6d3737934aaeaf5302347d1892823a4e0a3f19245005811b125b6d0d299899b4abeaa3cf3fefa84c81fadf241f978864dc816f8e49bdc841042159e58e88a3810cacf29218786acc93c7627cf64921bce57f84e474eebd72b94927eaec3bb3faa82a19cb3f3e0f7bd7ebc0369d684ebd58f0921adf0e38356d8999007acaa16cbb2a09430f22c2385806f4af9f7dce1596653bc43eee1e47ccc434a5a792673737ed23adf79adde91af15959772c267b1cc9a3c0e569d319037167209f33cccc16c22f681affb6b63ed37afbb791bfa81bf345894318fcf8b664db8a4ec229eebee219ad93c26651e6be25e9d619847f59d34543392c7a638f7abfc94b210e51e157958cc4981067e767c5c46663e2d6da487d3c07523331f191f1736e1dad5a683e6ae7bfbe38373ce7d5a3685036976eee3926decee8f0c07d2b48ef4e8761f978f4bd6d7388da6379b35e16a9043b89acf8e766e64e6d3e23e3eb4732333fd4f4b7f66b88f4b6b5ee3c7259b3eef2eef689d9cc6715cbb911efc6971b2baa64ad906d38cccfc20a95f9571e0701f191d3fff9131d2837f683a64ed1f198c83718cf4c8a48338b27a03e3f8c8f8c81899f9c8f8b88cf4c828f891b126138ddc0b8d96cbb5ec0c75e72877d5889961131445a34861974461f5caa87594a3dc791d5551ee517f9d2f573454bd5357c11cf766e51c5d577dde3d60561dcaf3fcc93947bdd7f9c4bf30396bb8be3474617533e6cef5b136dc75e82e29b9a5aa2a5e21424f923bf5a05edd99798aa275672877bd53afd35114f5aa9acf5da738987c631f973010775d9a578da6aa621ec7769a2f5669e8c75515c87551573937e320a4163a8b3922e0c854f20b431b420eccd68090575ac0ad0143f119c8e05c9890afe39e8fcd71c6edf9787817a7a39f08b235984338cc811b07391cce2104a19c39a7ebde1a3041b084706bc0cc4866991890d7386583448979da9811466e8d98bda683cc713ec65d8bc9296fd97031315b6363a494591b319ced7881332d8c4c92348f110b286513289b68caa680d3c4024e2936b009d8139c24c8dddd2acc58c845b39b99d3136ea6e99a1755c1f01c5a3987af9c7fbcf63f6087ef5e3994a6ac09fc04a769fa3e6201bbfb2d420e07ab9bc5a76c8aadfdce445598f38ffe47d3cef410a7a7a76c8a89560cbc494384c18e7982109e058c7a64b55d050b49699ae2596c8ae9319269b02398acaa70caa6e8b71829b46ed10a6fb1bbe05b9413e4c68ec59a4cf00ddb6fb0f80bb33eb1ea13d6022f41348e1c2f3f348e1c2f41e4681c393cd8e6d025240878dd4d752380a4bb9b860878badb43a32e897abb5f72bce478c9f192e325c7077f0c21ba96ad076946bb300f992449b463c8f8fd34d10632894cbfae4f22467ce4ef9ff779e72244bcf31198a7f3771158e71f2c8a46b00ead2a4d048ba29237ddd3b907c47378a96520af2acd499732101a7a076149a16b99ebd0d03b08031e05597a5592a3cd32f5547daad27777ab1d642c13817454340bc3348c8e21758c666118e99b437bdeb04150b8371665f57aafcf9e0b2ee9bf47146ce28174dab60ec8bb8a76b7e1d4a3ae7e7db37f5c1b05c075d125fdbe28f5501730f41ecf75b9809feebe2edebb2e4f7d4651ccc0247d817dcd02c21f4a9066260424e9e8dc98adb1305b636376a6656b6c8cb4353666675a36666b48ef715ee10a4d6f59bf472692453eb0c18e96c95e598fe4eeb057337630eb1cec7a8c6224a55983fcbcc320a783ed54c0e3dcc23a57d11e0e167507036570f36b730ea6e9682a40d2b90870af9bbe2e8187d8fbe5711076f5824aca47730523a07a47ef257a639a84458e53902c796acf05ef10a7e31d56ca85352a39636549ca5955e417fc825ff00bc993163e2d5d4d1aea7d5a28d7de9a57296935d2432ef58f8c8f8c8f8c8f8c17a7c365cbb699a4a464bb545252d4ac34575f4b7187396cf1c64dcd9234724aeacdd22ccd62044d6b6f8626724dd3ae71d3d368de0ccdb3319f86be477dbe4f6a6a36b3331fe5a676c99c8d4676e7ccd950d7b84fedbd1b8d55c9ce7ca498c31bd6f0c5164ffa2ee9a3f1683c1a8f069cc1c2f06c578c0b7349077f5d516ae24c9322e7f17a5591e226ceb51135f01bb3311bb331d31522080402bd440974721223287a89668ca038e725ad28d5c9c949d6ba202549622125295dcef6565ab0887a8428179db3dcad95d75570dc285f2d9857d0379a6fac6fce9187b5a25313af9d935699929292b226bcc2d94907936cad85aabb14b96e979ca921754af387d56ba585fcdaa03458283e8a9ee65d43ed8c7f9beb92b2646b5e67c8e4ad4716651415e5c84b4eeafd88ed8d35a1e0a3b54ff0d6da14a9f7f1da31565a304cc3b03496d530dca5b92aa763b410908de54236b47a3eb6635d1b0d5561cbbd599aa5599aa559fe7a900f3ab7d2626decc06afb03c8ebe2581c7b63a545b436e2dab0362fdb4cb73aaf3364d637b48232eb92d450107a636fec8dbdd138eb9c60b9e01d98074bb2a44641078361f00b7e5159960bde8179a8b0d0a4288a7a355d15ecbe4a2e5a43b3d968f805bfe017fc825f3820348c9e21350f9ad72c16c3340ceba2a85bd665715e5fb669699d0f9090ea21201f1d4bb39ccaa23497d52ccdd22ccdd22c1990562e764f883aedd9e8ec9bd1793dbc1e1e8dabb3ef74ae8f4ee7b95c7f33aee7c3d509b90afeb8426ab3a616f26a518bd6f632d21664d7932d49b6a06ca7b790eb55f4de87e4c9a4952a2893f6b2e8a3f1683c1a8fc6a311bd503934f99c73eed3e21e1bf5e919c8dcc6d8eeb0fa99110f5f3f2e1f19779d9516d63f2dd5bd8385aa78c5e8de89ef38ab73157c55d5e307e79683af6d7aeb58b73e6249f0d56a1f5485b30ead1858a91c7ab9f891b1266eca482b056987493e32a6266dd61981aaa494124a591d442359514e4ed6445e5a2b2daa83e4a3abaaac8364754dd7452b4a2635933c591319a9e9e4e4e4da4908d64a0b163dac0852947516562413c1443029299b9516967516d686aa2ceb14457d63559546f30afa47e775f30f4b433bad4a4949494959e9ac8b61031db27d91453ea8811059ed7ff87f8cd54a8be994349d122254e15b04294a9a5c0fcafd7a92dc98c74855d5c2549c285b8ba2389c692bce55b0b59fd5add7cd2b5855b4aa7040fe515daf95c4b92caa524d8f818f76be316bc26def74cc6e76ce39762b2d9cfbf8c0cccc9f19ce398692efce705e7146aeced31ae911619242d791ce0aeda268b389369b68b399369bcd4a8bcd66e3e222b7d9dcb986d1d266b3e9189bcd66a2dee69be9b539df402755960b712097fbb05ca772d6ab0e6773bd83455109cb58d36d18be90a80886df4a0bea2ca847453da8179da2288aa21e45bd57b9a9f7defb837456af904638271a23f54a4aca2929292973dd8b76dd2b2d26e8e07bd14a8b686decc045ce39c95d6a895a1c0e4228a977fd72af34bf5e1de5a83527ad8e35f492a6db447a83cc28ba373af262d7857f9aeedce42637b9c9496e7a8fdefbdbbddddbbda35f45bf2f61a1f62873fffbd22fb514457777ee6fe71cf5a81f92f8f4f7e38bc4f9d85ca0ccd1aae9589c8fcd554d08b2b99c7320ee9d8dbbe6729a5fbfdb74403c23227e182e803f007f0c7f891f003ffd69254155a6c34047d08ac28d50115a493c21b4de75369a8b56f5728dea48123c9dc0efb5f7de7b2e7887c73cbcc73b708cf7de7bef8f9a9ad7786d3493dc4cd754d2252d8aaa7406695de1179dc92e093c679b73aeb498738598937bcc3ae7cecc39e3946faf5c7dbef86855e9e9d7545d98452385ad8d37d18de9c8c90e09cc5253d1b53d8a2250048a4030828f32d810468710be400842012b840c217c87f15052af32d2bad2aa949d9c74e454035723731f9791994fcbc787f79e3544e6dca3fc9239ec7d8a6f29bdfb1f191f191f191f19ae46db42b7eb7dec9e565a3c97a8a347d973d1ebe151d7dd2f9b8fc81d9037adf2ee83d68b3bcca1d5a3a1f55c3187702c1bba6903221aa2ed888278b446333f76cea3e804fdc7a3e804004a5ce4517422867f1e452702f011ef241e002c8a4a62c0a2a8a404f60e002c8a4a2886615154722c8a4a1ead2ad3495018684531825610e8434568fd432808ad9e0fdaa1f5e2d08bf6b99a91721da25a7b91f6d178341e0d1838428161b00bde817958d7ce39f96769a8cbcd4777cd34fd72b1a4aafc45aa2265ed0c1fe517fc825f486e4c68cded4c738e9d5b219a9be99da175b264755db4aa486b63cab8362489633666637ac5e206334daff624bdd730daa563c4f75ec8eb03f978f5c843c969fea8e43d4443eb0b4cd22dddd20e3dd9b66992a4f648147c550c2eee9631b8f82dbd26751462c4120a0a8a949d804e669c74504e50665c4f5e3fc4a844057164830d0d24710a8b12222a1774522e175cfc2a65d293120f7e517486e118866b4cebf223f30b17bc03bf2061926eaa6a01bfe0170ca305173f925efb418c4ad4e6f2c226963deee3f271a1ed6e1a99f9b47c640051e5f591213f326070f123e9b5b6ad87189568925a8843b74c2b3c31bf761851d49f631889bd4ba433263d562cb8a437cb12602f5d327fdbd666f7885970496f471b2236ef71688b639aa47349a3fb6c6fd85893a5a0869d49ba049734777ea2450572c821871c10e08123606f2c01305a573269ca405d69342e6ed7f4901023d536203dceb4a34f76c151cd2d09aeba2e2e374483f2d46e7b33d6c46d738f46af93680b26c9617568c126a4b83a4492eb82e4dcf3c179605d7041073631c4b52e1a0cb83cac0b9b888fc69ac0d78383aec7bad81d36c6a3f166b4f85c5c4407752c1094f4ba3059b7695df47bd7e5f1fc24241534b9c8685054885189ea5e64a4238b2e42561a5838a304576d30b2e0e23c6c3b3f2d23331f1994816012253010ac0436216122a9551a8832a97d6674e09a0e044c16ae8dd15a1a0fcda5cd683db009f991f171a90e08198c16a3f1f091f1699123330d061059c368ad32101f19233ddc0e58044952e2728d465295438b10c655a3976c5ae10ab9371390ba1e772fd8b65d45af8562612866b06688aa1c2ea9b5105cd32bca0c56b836539180053820021458d1316aa404150960130b04ca5956bbaea9d23c50878867ed8c7bb1858df0c515ae908b77909a3162f37061d664b786c43c52c406aee9d116a416d363039494f00a949431338d6a2f21430c8c935ba3c92649d59336666dbcc01cc4a884ec5c273cfd28e985853f5c1d115c1574097185a8b7ac82320862132ecea022010ba80ee9daa0e8de982548649832445e8832a9ed0f20b8a6d7123208712d0845450216b0381ad580004299616facc9ae0d17cd1932da286e9072c715247c3b7c61a5cc0e55b8422fe551540a8b94aaaa3850555f905a6b1cb826d75e55515570ed55d553d6e4d197f2288baa4a717f92a650efd0d1870309db08b4b012451b0115ae108471ca865d52d09a2ca427dda8899a1cd5282d480d45d3e19a5c8b1405a9e05aa4a8933589b49d80d644a2a44d511b3d91b20743c4b34b3e7ac7131960772e8da4e6c2524b2b96abbe679461e10abdb7ac3ea3ce261caca8f741282828ac728f264579075e3a318f88ce14c1265a244d5c90a689634c2eaef6ed889858b836ab25486400a1b00969be695d79d633bc840cfc6287375d482f5628452b95aca93d29e8906159e4031ac0d4f0a145d1b328f26cb730c9d2a5d23cc2b5547a6c42468ea612a24c72fbea03d7f4cdea252712aea5e2fb2f4112e94a77bf331fcd481bc69a4812045a6681fec21724a9c226767549bbc4fe9e44214f4b7839793979397939b1765f4eac5b8fe65a56cb4b8f5ea401e6f42a65271236814c4852925e2c0974a22208e9c4b2ac130b6c26dd77c21c664d8d08d28e0602ebd8aca79e3e725d7a577ed3260fa30134ad28599d7f7c8f957adfc2a247da688b5ae593e9278e86dac99a48d494eeda49afc3a48671b6b386f7e913b60d93a2014c1357350319759f307815fd9e8b06f072f272f272f272f27252adbc91ad588b56955ef1152bda8a15323156440dc6f99c3ad28a152b56ac58d1de3587914c3641687d6a958e91d6a944d2780c3e823434ef260dedad6d8e924f522b22a3163e36095f612fd86c3a99fb355f705d05b73901cd39ef4983bbc65d45c7495ad874179d9cd8744a029a732e6c1329cc7de74435924ba83b78ea13ab9d8c3af5893d26a1bec124438e0e3290c17b345f70dd6134444cb222287871c94ed6dc207613420aa27bf0ccd46f3fbd4fb44a779fd1a757a0ecd4f9b0f2a747d8235a418eaa701645547ebebe4fd7955c4a55389b769a5a94b1987ab6364dedd324b5d63a4ab1a1a7d6d19be48c7f7cd8f49def8ad8be3d62fb87b5b7af06977d625885cd9e0a707f190dcfb5a052775dd9683649ee04c24be38234440bc141c8cc3c78cd6c5e1f888c3a7fff7807c1a2a8e471b00d959a936888a2926bd7a5c30b46c83c669887c433dbc2332d140ae6c132cc83675a9807cb300f9e69611ecc03459fc0215228ae78450688b2f678ae85d4151960b3fe290edd8f8ffa849cdfeb51d4e0540075771caccf47f04e63555301d4271cfa443f3944a43dbd47452f7a4c69b2feb63033f30cf3d8ccb4b4b4b4b496d65a6b992c3a5a7474668b8e96161d5884009610ab2448d860110220f61ecf6bb0c725cea9689e0a781a8cf30a03f9857d9070b08f974847b4a38379cec1460e8285fc031389cd730d36f20a0bf985892c0246443c214cc2c11e97c07f601e2e8120d8e62a1804abfe815dd76c1e0805c50c4cb2d76017f62ed95b2d2d2d2c7325f28c11a53684244407c13d5686499c35b7be5fa736d13b07d3bc4fa7aa45f7d461559168083e421a726f59a41bf322b8321b333549461e11d2eb90dc50014f27ab3774245a424646464646c63d42402606739489212313039361aa326420cd1057bfca9035f32f13c1586c25e1a4b5d640281bc99c804e50da49039da09cb4d61c15a4934b7a50b2ce3b9955a455aa41d2ea2a3a9a559aef11eebcbc8a9ed87b282727d0876885ba39ec0e6cc2440cf76e591d9a45a3d16834dbe16c56c5e4d63596d168341acd898f7fd00f904fb4be2e5910caa19b6f0e0f5225b2386ba87eddc2368b1eaca21c38932bd12eb39643bbe29bf50bab5ec3a6ac5b58844924328050f6d76ed8ea41ac424a8b6026c992a248b2a2261966214a4d082988eec1999336a33654f2edeeb5baa3358299f6f1b95b8f4ed11af1427e5ccaa291a22148cfafd599abaa3d1aaa2e2b4953aa943591a87bca9a383751555555d5231611cca6442c526ca852aa884594624355a52840debd611ddfeb92ae01dbac61138d98c4bd5f70d1884b2605bc68bbabf80953d1f249114cca353df05e2382b4a381c03a7e681c3fbc745ae7e55d8f11c38ec8f4918a29a4233a9d0e841d08691475206c1dd8ddb9ee2e2a7540dea9ae815c029168e7903a47bfe4782f395e72bce478c9f19243922f5995bebb2bfba583f054951c8de387c69123ac38a8e4ec198a6aa968400000000033160000400c0a060363d14494043983071480147a9e50685298c7142589410a19840821040c1010000100121200000db1da1b6931cf903fc47be11cf835387576b7ac08ffeee476bd26af6401ed9f15bc28a363a326ca55347ce4bf19c54351c88dd2cbcdfcb1adfc022a5e35bf64fcb1998a91447fb9bc3c943705b2c06a15cc963626b6325392fc1b62409129c8f4a37ef507dd4086cb28876bc38e875f653ef9b39c3febefdc876815249d5e4f52bb64d4517bc9afdd006a6330c02b136548463291d4951f9126ce87a2662fac01f0fc812a370b94268685396f934703239b70d785d4ca92dbc6f2320e909e3447ff3eeed5ca276ee4ad780d8a447751815527468cb0edfe99ab51a14c68cd5a843600a1c5841970109a9d45843c3206ab4d7e3fe61a1429e731945940d99249294407e1e6d25c1a7e8551e5a1c00705107a36b1a87d14af5d1be2e1cc0210d55c1b446c83e61476edacd7291d609b2f90162f47967465d36b399f77d5219f88c17ee28d07d1fecc578e1ca8729550bc8d1a15272fb26405669e321927ac869d77e16443897755427f5c5a6c0a8461517bb06d0ced04dfb456c5cdeb7c3518bf01b39cc1d26ba5b00e4391843de8d8019f0546ddfb1572194d6310e0fb7ad988cac250ba7cd384f612ef67a40fb21c1c659f39587dee1732a735f28f89dc4274bec86097d90af6f8c3930c0ad9382378b4d273c34d370c7ce3a77198bc268bf3cc053293c0e4a98cb89799f42724d6adeb0651b7273b8d3de36e163d282b1e56987a8331d2b4067059088b1c60004eddec21f5f1eb586a9dc1da8cb06d37b4e686d69bcdeb1e49ff685efccfd64ba74c9a1e2b5ddeec4844ed499667ba016cbdd2dc31558edbe681a577c28066546560a3983b77aad7ef7f9f47b75ec6195377a73e7e6168f63601a5f83c44f81f946f770b663875683fe5f4682025ce60f61159c5cd1c5b891069fc5361375d8d2606d357be57c062ea4cacc3986c98f939dbf1030c2ea0bcd6a7a94548e2b90be6a26725b1b90110ccc49cc6cc4fdc6dee4a2f98f6bca912937412e5f2606858eee0d44ef21fd0b6a885fdab10db6ec3ffc27f4b5afad5e25bfa924fe1d1eeb25ad6fd3785666d12626d93d6f3ef2ae12abc9bb2c9a0036b7731ca82b7c93e4e0489273c1a69fffcb41a2bbb9769bc9ecbf96c3d1b96e76db1982cf7a6df66efa6c4ed9a4049b1930d1fd307e74729f621711e7b0e44e329a77318a1f77cbea58361b6e2740eaecc1c419bfd5ba474aa6bf0a354d32e53c8b94083b0a2e23be2c0ac5b7686269730bedeed8123474aed9a732c87af4230fab334a7747618b3fd3803e8c72a827a93e803617f5353a83df5be1f14e6d7756b1f3a40f4fe0f283ca3fc61a5e468ba54fcaccf29a4a4364bd3417a813bf869cdb61b627a448378b4e517f0b2a12e461b6912067928433c51d60ee8f58728c6e241b99e634bae4c87cc946fb8e5dd9db83cb14878a889236e2ecb4cd78d669ee25fa3583774ed41239805c4b3f683793a0af6d44f6dff93d9f38f9009192cecca2fa680c6362aa92a9502e1a2f22ece5806f2026093c2e5640102ec0a1a3cca301307130497a3cd3ec8207861efa00739d99474a72613014fc23c199d44411426cb1d839273f023dc5c52bfc84ffa94f800146a11c4172683270df33cba893d842a6e3f5c76ac309fee12eb5778d59133985ddc7ceccac7738e6a5885f19647c8834b382d98292cc0d220efd235b0c9e5eed18448a71ad4387a39c5330a8b4b60fabe7ee5bf571dc58f7e834c409525ce4f42667da7c27221a762cf92a24297a361b9a1c08984916838e2b90ae59912e0328bd177f85cc6c0105fa358b5cc3c3ec7c7ca4cf5594470de845c2a0bda887bf06b102ab4d4546bad86e73ea22a8ad22bffec041e056ab617c9d38c9b9ba05f25711c0113483b8499465f30afd72d24562e5f06ff0c18076db2e1625fd1dcb10c47150ded4e39c172dd361629b7fb8eca0b11d6ff5f753a012b07c06140bb8a494fba4189c3b5554bee81c706bc071d086eb3a708003e46027566d7a510c4a32d350a32c38d2f46aa1d0d2f1bc643b728d2625b8e8dbf60560289cce997c8ce84195fde66df9c44e86bf465662a5e14dc459dc4bfe52e5e35a71acb79068789269c5d9a6bd569b9e2fa954bbfda3b7e92a3c0a0888860d87e82ab987299250a6ed9f803c918914ee886524999c89e62111445b8d7faab19c590f20924bb086bcc8fc13925662c0885885f90b4e01d46d27e9b9728930040804427acdc74a4a000565c9b193cd6016b1c729c20253a5604fa73414a179d42a910790d78f302bd44c1c0cb72dc1dd8735cdafcc34ed50086570360f1d14a8e1a859fff88a7a67277153464311d8a2e75c261d5dfae5a72f5c5ad1a1d43f59606ed283536914ecf2db04056886f6e4bd3408392849fb84901059003a3b4459ba2c28939b05730cc5f4436058318e6172691532d400f6b62feaf20f6a76d4f2f3845f8210b41a24c9ab3a482590bc51a84a846114b3c18152967b7270101bbbf62f1971d416f15e3ac9d585c195d5087ed86ac123629cfbc3ca195da92d4994a981dc975b6d16e641b2f571d5b54e4c1a6173eacab89b6092256cb950aaed94828a0c12ec871fde102cf8e24691eb3b8d0ca0eee9928a4a15c1367c289b0c113d447a10e2ee0e174d840631d8f7e397af6e3474cd373ea18c56549b6b02339a2ff69a0a75ec7d4d6ace58921fb64d6edf0cdcedc22e1b83b591c4b07921c0a8fa5461b164ad7fc22b7a5ae0544a41ce377521e928572437ca884d9b81cf2b3ff2f30666aa34d8a5950ed879546469101a2802699c83c203327e98a64aaed438903521d9fd124d025592c9f489c1b5e4c747e2443adfe1250764ee434648c0229ac589924c9f210d2464df0499c20c2834095e9163e1108858b7d31591892b7099f16914ecc032871a3032640934c2eba4a3edc7cc1dc220ab421e3ad17b432ed53cbd0e2fdbac8dbaa3e7a2cf272c99c8e07143c01fe0701551001935d972a825781d8785c1d1346a9aad4bcb500d13ba40c456624b6c004107b369808193e73f39c78c46dd03938e0059787ba87e86b6d564c748e68c2f56f14343c6280b8f1f0f27ac36be9810d3f0d58457020c8dfef9eb903865bec315982a9d5b46f6b8e4925da7a352aa46743e069e32d3c411bd5047814482e8fde00644d8d760ed233383ae1917581a52b07dc8a6580d3209abcedb4f17c283a1624acba2be17705ff70708b9529187591b4a322bbc550a84255a1221dae3a5b04d1b96638a8ce029b034b28b178fef4e96d81b1fbbd76f382d6e44afb3d61183f5ffb4234462a1f8cebcd048ae1440d04cdb6839b90fe044a95adb1cec8cf40c57e6bd84988c2eacec3d2704744f756958a6d09ad633787d7c0ff13f3848b33feea64d5008f18df8d9390dc066319800e12aa7bd3e7ba36dc7551d27f98f6245b9c9ed67278d90083ca3f84a37c100bc90261720e2137a575eddcae26cc9d63d26c31b75235e251eadd563e185f179270e2c18d9a0e8a8a79b03f30f52d51dc338456cd42609998c83eb81ee6476438ff07be23f18e6254cf8ce2a6c5cccf15f0822815983d2ba8f2224715e930ce1a20afb895a5f6b02f1a792c4e93e2bff8c1630255bd8792d24e16b35470c5980919a716af5ad41678280d5dc21345bd0a12d072f99022275f5d0aecc225e592a7be5269d3bc4545fac58c2c61aafa205a0feb619aba5f6885dae185ec3929bce58376b47e323d290e2fd4a16bb5b6fa341986ae029465dd54a1b9a6da4435c59f6dba278f1c8318c7aa3dab486e8bde7e6d5aa62aba853d1cfce5769103ff4bed30b55017e65fc350b60f9180091a9b0422cabc2b89d1e77ad433a05f82dd98e331327455878ba59b60c0fc29df10b2b9024beb82fe4c47769ba127d6477dc5f0e8e7580da6a7d61419224b441f12ecc69a907371e27f55df27a1e81a6f077fdb99ae83221c297ba593c5e6c175e98359ca45c18fa911d3c898a14bbd217c094512a437616ee7c77711cab3b778609bdedd1b4bfca9f4d6e33d9a4b01e05e9826289872ea41f9ddf3d6aa5be24a94e0cb1b4a2efcb9b298fbcd9f1b249ddde7507897253fc4607f593d0fb20ebe865f04a4b417a9b7609480e6e2582fba781871718ef5fccd0d5b18811f2b8746f55080ad0e246a06c8a9ac496bcf994da5369d84ceb95cb9c6bad4dc07d33d2126a90e47b8c015fc0dcce634985574f7c7078ac611c52343ecc1657b53a4449e2c8c3bc55153d0b320f2730214b20a3e7531d4a965dc101eb0ee38142c69af2ac49db06938061b9a8707df3d95123c3645b126a170452a013ae318a164a835b904248d66a1d856048807d598c67f4046736356df1632c45421fa8907b2205a9173f6b684427325ca9aba322b8e45700d98c1465fe81aa5d22834cd799c463f9c2cb7f11ec7035aa591d2b01d0ed9bb4741a1508fcdd3e649cfbb94cc6a4603231dd2cd87552aff2e8ed4ff0554948be6e9bf892c5afad1f81144e0192d50de89ef12172f3dad380d4c36643769470c167b20110d0490ddfcb68b37ad2e0cc7be9ea015b2ab10cdfde5f918568704cf6420c3be754030a82e44e9cd779b5143ce2b7b97df0ef24a39dacdbbe5db7e2875c0a63f4a0e9246c19b5e2a0bc1e6768d902f5ac759b94b8bc628aa2fd7a0e27f91e7e28a3f57472c42568174ed3aea5466ba0d141aafbd08fb97f33d1c1bd6b078906206371e73945e973074b2ec2e704d9636e939e86f59d4786d70b95d3aa7a717601a646954c6fa29bccbaf461c871374295fa69b0fc6481dc209fc97e95c3123d8efbc529411260adc03e0795102dcc59d4c42a968fc44cecf7b10b7d4b6108e859658964e79c271d4e80d8c44d085fe2b412ebd911effa28dfc176a3480f946030c1a1130db3060e8a5cbd6ccb77dc180a599aec5c42fb9e79ff8f55f96aa54d10fbf64301c9785bfe05cca19cf6777d5cc024f66a4e0ae9ebffbfee8ac50b0e26286f757f2cd690f8bccdd9dbf1a521152cc37844cbdaa379e2eeb9219d5bd12aedf7de2f1628f7817ddf5e5ebf7652c45ca59c35c1523ef77b56c5a63316364c4cee28639c0855f32e25af5b01726e83062019fd603bea6dc99461ef15fbf0042c2a20fcafb254dac172d63c07af42277d31de8a50580c0a9477252910b4fc2dd04a4f40d1a97d7ca346097c75e63cb9e22110b482ce3532d7f065fe78c6b0d9f3aedc819a82ce65ac62c305d66f884908de6b50be2bb6c4529a7a7e1c60c29872d564b1f4bb973e00512f7b5187d604e74f5fd2172cd3b614995e3ab85131f7d4a730b5ccda2aceda7616dd062fef87526df3efff82233a8034702f07a86206dc521052016d236353802e0f5110bb16d345cc04aa921731c7d4302c098b30568419a273fd769a2d2c3b2e00eb671605c735ceaba0609f9e2920c37a366d4c2913673d03ecdc010ada0f9fa9210625d90bbe4d6bf514c50eec9c764287f1d6cb51c125ee247720402991129e918ee5df442e6ed64bb0480977a6a481fbcb865eb8b22066406ee7993fc4b9cb55c88e597d64358dce73541d5c60598c7ebc0f66deb1adbcaaffe3413001a5b8cd927140646e0305940e87a98a96195ae6a0cf2d2f1cdffef1a5c5853e4472731e67e129078b778fa65a362a443369bec1e28286e1c1497a69159243b5c8431079357e5eef6529c85009648ee58bb520caab9a10ff5977df54d995ca6fbdf9c98525781428c597510856bc8a7f4854634d97064a8ab2635e1996ec44258963c6a2d061bed92d12d0a75e8b705e8a45686a377212fc1d0d81f4c0568e3076326f02cde46b1197130a175395c4af1ded2685c9227043a3742132ef9edd7a893f7eaea605048b086f37e296080984a44de5c391766cca99e37cb05be387647c007ac8cb2d8b78a5083b947aaab72986680c42b41a5fa4fe9aeb04d37cfd9cfd216f005e2aeeba6a5a5b72acb98cd66293f03ab5effc98f70d5a114348f58499d4e99fd4485e2d20df0204e0a85ab63366f5a2026bcf5fe6a0bad5e00ac2b49aed0350c9900faf24452c6fab8034e71a54a430614428b080f70528346456c4d3e22663907e95c2f8dd76f1038bcfa802fd109d202d1aade0e3ab940f488830c97e17524d568c782c0956259a7b84ead1d1c6698b67fc001b1b721409fe8c35ed5b21f8d9cb71ed728f68ef883bb7f225aef60a343aa106c73acc476722a20e371b85c4ee4b360ba5731a559108a58f2d1cd2c51d7f9e493708981fc2bf29fb70518c63c133dabd53630dc7695d4fe5e13ce216789f2055cb33e43a335a6e624ca2de3c8700ea9463922eab08590c281c517d9e73e37cedf497776e0dfc8b2374decca073d9f5da7bbad3c8b45a6b21b65b79b35a8d15c7b06c961e822639200c2925dfdb168b5813bc251f22e6ee530eb95cd10a295ce5c9cfedc1f2e2c718f3b45d82b97cf9db3e9751817977466ba131e37112964c00f6cda91ff4cad0cf45a813d412c2d39c128380683a40f2761dfdf5fadc4e17e47f876bdbffd1a92764051bf0d32c0f4f1f635eaf3caf0d2e81ccc26780b9e57443b8914360ba4c9700d7788b67d8c346ac2940e3413162cf431837799e80f2a61d141c2fc41beced29b0f3dfa8337e18e7a74b89f809ba6336df23a650a539cd9c41f8269461a3633c1bb15a8d9d05775aec94441586b7ab41e34d51cd809ee39547ace9e502a2d298e79583d984bcbc871fb9a15e797bdf081ee3ffca60e816d07f52478df161781dfa57db579425c161dda48f37a824b9c841b1eed767f0f72d81c8b5155a68c26abddd9a3eb59deb68aef2564120887b70a276d0abca538efdf01bd10b2df9359b47c8188f4743b35264202fab56348ddfb6e53023d254120fa423bac7b932233e04d81987ecd36240f8b13a51ba5a5cf90ca1da5d2db859b72b5851a69d5cfc796b5f3128a35dc5eeb4f7cdaffdcaa2a67753cc62a724499c7f30bdb5998531235a2f28fb7e82aca7cf2f6f66963663a0f0bb3aa6967da43284be65bb67b459355d505361b56f5de41c00d6bc2c0e1e50f2e69670c6c2510b0b78fb2de505d14552718de048154e25f15ca5d601d4e5709152f21442aea85d9f9ecce541070319701486a5b6de6d0b68fc019f4dacc341944d01881f35dfbb5db15d5d536407cdd48a00a08f6722fee9da62ca7d5eb1f79c062e37e6043ebb0d574a2157687047865819842d0d54b9755ae819d94f21a74d49a11cbf1fa7f46c7e4a240790f0838fa277ee526300a42bd639211c5c897e27d3dd62f0810419ff91d736c7705543aa6970b5906a00f0528c331ecac3d65132252e5cfb738d22fd61abe59fdecb47f2625f71c0a7a4e8f2a35ba319710dc1bbc91e45b3cae514b1d35e92474ba5d00cbf58ac81b316c92c046a737ab77ba3ae5f427f5f99875a840600f3a251889c06c69b35dd93c0748abf2d840221e175b59575e24a0502379a81d1993f2385c22cac6ad0352c5a7b4569fbc8f24e33068b2069ca51260bc83841394c7c70377419c695a2ea57830fa920697fe773af9386104b5867e9fe77dc031d23d324f5f103f8918cc0870c65745e92450363c9441d1358a09e980a3406b492e8b56034ea45fbda1d2a39d662d89f30571482774596511a76a216c787d110b0818357eba85301346025a85bc7510f7db4326f8147dd948b7990e1c4d0f417a6773cac38759b84ba54571363b141db3f00ef6391e817846440e79bf8b6a345e785ee7c1c970c7faa7a83f169aaae45778fa236f0910ff93a9ab30b450b06b17c4be38edf28ed54beb2dd8b400f4d7149ed2491f086bc73bf4ca10b2ddbc54e0bb7a7da0f31ac09ef3918709c175e124808a3999ae3068595913533fc596adc97de00fb0fc60127713d3a753a259385247cbb4a8d2f3e9e6337e04743302cdc05c3ff120ba03b172d83bad44bf9d0c43e2d14fa5a4dbe26bf3edb7567b46439cd5af8941ca941c9f00a78bcf12fa5ab04a7155f7362bb1d7d70881aa6c49d6a84fd9d7bed25cf12b675a580188b96d7f4148c032769bafcf4488e9a22aee8cb2b223aaecb817e44fcc186499da0b442d045c74977a0182d657194a57d0b79022689d5c4032cef9c47233adf373a370598b61083019a09061e14381e7b5de3675e5951c2284b91f1d536c01a96b80d681bfe1c7342cf8aa13c58f7ee588261cc62d72ba03608d4a6c09017ce6178f4d71cabe992f1720ca6ad41ee7e1a471fec179d122fe70ffb2ae91b7122f7fd03a19eaf171852a7f2f01df6aec0cd211715f88ee26c417bd4f755bcdeb6bb8af7f11e21d23cf1412cc40fdc5249fb91718c328fd791f021a569ef2a0b4360acf1cb30477152e6e8960e73215062a5469d683e17d5c9dd428e7617b9f2b286d873a30aefa9616cd6a50eb45923b145d2dc181656dddf352848363793099ae70b7d148f076f75c876e663d67aec832da9acf9cc4e6ff47042b61f25341ea74cf75e05d6f6fe1b2c57714a9ebc86bb83785c384ba7f6ffa5ad508bd56d661077b46b122abee5efc7d17928b7d8c9f438d6348344fa852fc7c6ebeae4ecf73f21ceeace43f09bded1111ab9310ea9d110c7aac70c95693cbd679cccf2f415fa98a68d0b977d788049a2acd1b32492184c7307c59bd4ceb7938e29e7f2228d807cffe217e8f8425042bb7b8fd1d39c1d104835810ee85d9d002691ba31b315d7dbda83c2488e9998caebb5a8214d5686eb343db95293110d5a403316c62a68805c4b110a7a0f58cfefa6795f55139d461eed8380395a36a9a81b5bca5745838ad69c8a773c5dc9bbe52cf4f41d968357383b1011eb6c8dc9206c453698d6eee7df569dd3062ed606c41201d3c9cd5fafac35c041c6dba1639f679ada6a6050387bf92da6283f65a6b492ad7352d0d6e60c07e82d965416bc58bc7fb9c28b32c53c142aeb741583ad852539fc8ca7dcb6aab73d58048aa53799b4e7347ab61b99437722dd893b3ab8537192d0b48e1a6ea5ce035161fbae233b09ab3d7709a692144b7afc5c086b75c710890652102ea8103c36832e3af03f1a3675ed4f468492037845fe500071d6c78854ce9e5035cf0908d42af389a7279857025223b5129734086d9fa073e819f579612be1088baa747ad6a04482593c80d7b9c94fb329b6b584a22124b0b8f4c8f1754d2b62787c9d45b2802e99503ece3524a8e6c937ec76378eeb205aef19180759a8ca585e650cbcad607ecd1e2852e64c944555872de4aa72ecdcf75e070ad5036d5869c2dd6e69d3a49d1abea0ad63e0da33874de31e7be95287e8445ca7d1f21662b2b521284aaea96060b7dffb2c53d4b48169ec0ad79ae4ab61903c4b52ca4c9e5b780fdc11436f720199391c2a152609634fddad933ba2e5d9a5cb2b9c74df4bb3d24614c10bedf6054156908f7ce0a1734fbe00f94669951cedd5f840e9e35115584c20f83b5c32564db895c83cec66dba0e755ddf7a879263f8217269f8d9b6dc7a5eb9e036e4aeaf81376c352c9d856b0bd0c5da5e16f240c5397997d0f91c03a11e58117ecc94183a38bd9c499d58963f1ab53ab527f205f30b5a470e9071a60c26fc104025d13332bce4fa8612fa0c08a5ca40c59a974a74b6cf415cdd1ef4d8ad139d6a698140381a0773843e1ee27100ee834271691c36f6e5fbd533154a520ddfcff167b6814c80cdba2a4d5c8046605504caa6e68f58fe91331c6da7f049c47fe801a059685b37a7538468e141101b320aca9b15574e3cb40c0c4f63186251d7b357c14b3939ecee0e8b06093368e31ac552c4beb14926cc567df061d557efffbc3d23fb9252ac00d98eb3e859f0ca224b49332b42e8ad5f48ed5e310018deb12e138838782634b0fc921322584eab99d2b0f64e812cc434af76ee9183d7ae3b2277bd1c3c8e40e38eb47153666be33b598cf20a631cbf7d4a33cae5df5ad6663932200ad1ef8810274f2f74158630f448c0f2784b9ed7d4dbc9246b781d75431ea1d5b24b9d4f06c705e26ac1564fec6a56dbdd40971d2ac5bcb312d1e7df8afc194644949f963aadb5179e486bae8bec3d3cd9e7f34eb9265ba15ba2d418d127d10f5bf6932d9dd772127c402861284d219b9a02b5604c5a63d229ed490d86cb65284701284d66da7ce01061ce67e57c776ace4eae8ce73b12d28c8cb3fe8c68b93760a9f0ddcc57576150cb9f1be5500aa1ff19e29d507c00d304cc259f8f78ca64e2d2a3a6affad81ae29f5e02a4a75b63452a6986e01f6008e8bcd2425c572da25d6c9de12654d30925c0211cce093df0e3306b306cfdee61b9b6563457dc5f544e8a4313ff58442117d00f965f4caa82632ff86ecb928b91b573b826a911268bef3e737767a7481fab72c8847e0c91990a1be7f9026d23a8648d4b292ff5d91fbc31b16447092b4cbdc11869da3331c873b4162775b7bbf8a364dec86255a0898a939af086951ec2de9179b13fb1d9be0c61a2c27f443c80bdc5932ac03d8e54f63a32251a1feb1ec64926eefee9c00047723f0405248aa58a009480bd4fe0789d28fd2c20f73ed4cbabd2cedf5bd6a01b5d872ecd8ee3af1c63aa598aab739ac6aef0a472fc30db39c414fa2dff521daaa0a0f16ce0791eeeb11c2691c14613f13c0ca4180ca0e36a6a241c6bbbe6aebd2969cc32b67a761b52387151423981010abd9a5cd265b48a891ef8d669b78365aa825b50ef0e939e701b2480eb5cdce5d3f6a0dcc5b40b850db4a85734abdd0a9c84638e01c9abbb63ff533bc2c1795616ee0a794693855013eb5267093c36e1a8bb6de3fad2f23a9a4293348659ff86d3485c371d42514626ed59909cd260175163186752c5c96065ff548d6872f9ffd3938d5920a0f9f62478ee723664e3e991c8214584d5497ec0680e9a1054c7202dd8a91af6639130b17b8287509d6b2b67a4d58a99e06ca0f8477d7a58369728625b20a2d9b34c75e2325199e5a1c95832279ac6afaa0b26fdda0a29c844e387a124e0bed0ff36ca615e4c943b08dac0b6d81b052e61c2689410f71d1a73aa3a0d98b497f3ed46398ab9e50f117f6c14186930c421faa6c36e027e783b7eda66e846c1c1bee85bf4bbecec6edde2252172e9eec97e1a0b4778979e17bd7cf3bd7ebd22c7cf6f141bebc51966ca6b614356e2fb14f68344ae0b2c0a6b63567476ae568ca02352381a4215cceb990014507c8d8fdb72395c10c2241fbea96ee2217e1c84d02ec5b9ddc3152516ee5e8d5db8f8912d878d80be9dd40996af48fce439c096b3f8a98243e3fc5a4dc5e3de5e395c07dc97f1aac471e035d8e2bfe15050b82d429cfb94ba3159fab44c6ab47da0a85250d7c744106e30173eb1999f7c0aa744e1be5c48bf9a7382252131af09378e7b30fa0dc931b038640ec914560898d67672be58016b46744c26f40665f62e3df19d8e06c678596e003970d8ef27d4ccad6606c604fa35ecc1307b6636dbb9e2e0bb2655a2e6853da0405b46b34c8dd896efbfa8232fb76ca72e9f81019cd43b90f17258c83b2647cff6edc9482c2bc70f8f06f2b5f85ada409645c2004e4467e860bcb94d9515ad6ec6bdc5805e64fb61d370c78d8521810c4beeace647044caaa1029895bc081fbf144429cd43511a617168e6c284f59637c3e5a4f5a54026c7b41fc033ccc4a5eb3f5e755bc53c78a242485470f89a75d3886e3f3084e2fac63687d909301750909e50ce1d032517ff3cf74394c0745fb55b9eacaea755b49ce3d7ffbb5006e772e44c515634ed070b3d17b12ed00bc804250795e366acff3eaca9fd10253d85a7e42d910a04aa4a9f6e4b444ae9ad38e8aa55c7c98cd28a62d77d6c2f8e11e68256145969a64064afba6907db245101e8697c7ea13ddf006ba009a0317ad1f13b9a8b8e093295b36c4c60b63f69201bc9f80b0dee545dd1424b7b5c6b9e86083d9944eb657fb52d3f31b9cd685fd0dc7b1569d32363972af0fa79f9eb594703db03666a0df809543a49cbafff6ecca44f7c4b060b0b0e26e82a14450e01fdf93721f4426f6f0112b8c6f142a8e2b36ce592b71722872b97f087e6cd032de86d7d37ad077239ab59cc4fc1fab31b1707b3475dae35a549257f9ffca6c91803436d1578ea785983009fb0ed6a260a78a369808e7d9443ea72db6064ad3d81dc570c439a75053eca6c2818f64de745bd038118e9f8ad1d9c121d6b9692ec03c357664a032601f14977cd42fdf80b1ad8acaa4c2fef26abdcdfce576767b43392acc642596ac0d703a36f703bce422a882f09530e5fabe2fab72e532ef80b191d9544c2ba5945c7e64bfef8380d8443a19134da2ec89c6ae51d0a3958137b6d373281bf2f32295307b6910cca3cd1a5017e41c3aaf4c86755dfbdaa48c63bdfe69c9cc5c19f4f2ffa2ca9a13e0bdbf65915d7dc51f9915df6cbaa72f5b7ac449d7631252862e91acc5bdd8e2c06a45add2858dd1ffaf624fc6dd02a309d596aee95761ae64656e98223cdc0a058aed3088c39825e494cf737f1b08f64eb123768dc17677965a736bf361b6db278bb5b94670f5b80ae3d92b718fb3257f07746ec6b973a5b9ac9145aa4865755890c7341e800c2bef8f1e94018eac82ecf5714797b646ac8d641daab482d364b1d96ec139d535fbafe990274e5c0906d33fe54891fc8dbb44ff5c0a70d115368b53bbb8bd97ca1526e082e4ff9fb2ebd9c69453fac817ad70a4e6d6fe3695678bba6b33bcd554e122530a042b391a5aaa8b7d842fd5cf6c85a3a48b8079ea9f921872464c996acf9291a799a62506181441d713e408fc49afa6cf72a5bea0bbeb9eb092994f6f2b7b61258c81d96b3a88e3c40159c42fdaafe69f74a1270464f13b9895f265ea42135da7d756f5a0192c29fe74a98f30f14e80a86f84bde46224561f4c6376010a0921e57ba5ae7bd5cc91f91e90255533da8b6ce3e53c0f6b61d4f8e97b29f10f32940890ab7eba61b8d8e5e9750c7ac36c947e5a273d44911cca3855801e7aa0587fd597e32fd84d509a7263cd8b877eab153a602ee62862bb0bfe81df77db6c25b1af86906910bcaefd65e5910a14f04694e004afb59283cacf30e35b8502fa462c6332bf3b8eacca80600a2f5c6f9816f267960f456a5cab919703c4a90e7fb79fb246cbb2d306a94705fa00caf84fe8f9027d24656a9ab0f5f9a8d4964c463a0af65943ad80b8da53136f2ba348a1e889df41c6192d228c1f7dd862d0cd2a88314ffbb0b4883176391d5db90df09755b1a78ee44e57cd62778b075977a671130c4c442f075c55d3d4661056262633fa54aef6e3211cddddd7f3c05d1dd7ecb42eb713723133807f5f0ccd79c24e2a83f2c61c42e6887d4ede88567403e3fde8731127c1c40114013f38e7471b56192a7351237a6cb9a8f5bed52c507d1dec36719f7610f6cece89b34b29b29f469a8bc064aed9028b4edbaf02cd3f67e8df868b0128c721e38a1b878f6c111a2fbd5c6eb20860ccac7d72f6e4522f76c5b03d5f83a46813febad77e7fd3a03f24791edef1224963674b91729ef6fa7a65f234048f206f0f5efbd71e447881bb6ccfe1a153f2cbfee84d7c0e48bd015f95d2a8faf1f31fe37e185c6ba7ea31488f8cc2c5339f144c02c5c022462aca0aae16111cf4ab7bc363f25a16829483a562f48d811320305c2a702bd85d25e0ed2ca9e55adf352f9c45bc1eec570d7ab822484329406b8cd544ba7f6164b817e25f476c54206218db376b3d2a59bf7ca72bfb6223900c96ed8f764596fba8e3b03ded4f924626b7a8612765af59ecaad4d60e18766a408d48b8750e0012f6f5a9b552063dd685012b59fe8d470a059b4755db3f25634f0a438082a947cb09b1ca8005ef53bfb2c673ff9c91bccb6071c4d96b1aee2b2f8fcb05e6223520844b0f92114109b492f0f798345417a3ba0108b57da5f5025de22c44c2bd53e8fa66e8776e99aae07ab72fddf0aed78381e39dd60b3d5bc140022629c4150e5f2c9bd4f5e0ff5d308a36b6bf91809741b0b246f9784768b071ae995f18f5524645ce2e3388b61d7f457f1ea0fbb74b829aca1c82f47e2c1b746477f3752e24a1ec4d3fec588af7437cbc54e4f15d30e55df5344fc100bd01c72e47b383c16e097bcb6974ca55f163dac2fca4689e64d06002e64c3b85ca910541237653b1681805065f43c77ec9d58fdc3e328057a4e098a932877650c890c6537aa2d8ae022dfc7075232a87fb999dfca52d325340f62a9f6825cf2f57634eb1f2484b10d72f200cfae32bf77c95d2940048b5f89dbf461c5b0ab32b936dc8e76c354cb8bff1bb089dd4cb71fc78a25afd5c9b8194ba7cfd68af8251276571fab9a2a214409fbf8c4f9f62a87e9925e049594af8c21e92509189156c2628074bd2d516c83bd4c4329754cd0487cdf6ed75498e7bdbedc859a730a7f7f132646d50e024c7ce42491062123ab455145f3900626a70c65e9f71e78104c5177332a35d4abc3d3ca7dbb9c09967e326be7bf06889699c3f08f2cc57f313398a37072d7b775f3f907158cd7cf26e81020d1b79d8931741a107853033d403217af97f8643a4472b41e4124d7d2dd66e9257952e44c62b97a6a0a41018fc15bebe7eb92c6253f3d10249e4e8762d80598af47692f0408d88142698d222e0b7ea34fb48c73e554814f7f9c305f996d5cc9d74b3a5c7d221f468a5f6b427b9cd618792a25f97e520e6c2d9ff1ce8c6028991b3cf79ed0daaf5880af5a54ce2b5180b596cb2cb56703e5b373c704b8f813a66db036a332e2da00d2fe3965a789789704436f755ee31a2f7245368a0bee5a17a62a02b325cc0b1ae6119998d9a3fdb2804e401333484e4b67c64758bdcb45c67a1111226e1959b538b30b3e0a87a12d72e35d59f0893901dc1826385fdc6ba7b628137397a6a9304c74784252a6b688a83c70841333358e07454d202f735bef6d4ceb38b744aa22a1ae634dd144bb1d448d325aa4260ea681735afc3ca06d71ea5d44c9ed22f3dcfd797fff7441fb8a2bf7740d2a5b6dc4c7f31a5f0d12cf13095cba14982e2cdddfd3f2a76b98816ca788465737183214acb6ea27dcb421ae7fe3bcee22c953cde01c3f3bb3f2e6033c7fd4eb766c9248758cbe7446d78d7e73a10f144f84c6611104e4401f4414f9c2857da3acad1be29c1d53ff1c1532d70f60401f0c0be24f0ab2790dc11e4ddc24b34add8a1c1e4435f90db714d19fa5a09aae39b1ce2572d09e836e3f5461383abb633e12b9d2ef7b41688c59065e8503409531f40d1796593b6285dfc8eb796c680427881a3d1f3e708efba17a82072f08e100029d60020c1959abf46fec4d1e09e81266c34bf2c7a868e188b43e06cd1d00ba7b0445830c5a7157084772bd1d78a6f288d50770585704e608884f81084a1314006c7065e25d5115e19b2aa477f93c7c3556741b841365718e0fdf95a9e359d1815586752d249d50de19d180262c0586532741a2a5b23157cf22ec71f6434f390cc0bd9b964d5c4d4f771cfabba4835700121850592177247c56f333d78eb708a9ee8ef1db354817688a2d390f8174d9b25c900ed96c4bad95ad289899722f83f0c67524c93728eb379dda7975af1556017c247e5aad3200ed05abdb845cdb14b5c8d8a3209ac3abacf3b876c4330fcad27f6e38047284c8d36e442e887a87bc6e043cb40bfab7e74585fb28096dd12c42b09eb6f9aedb0656e75d33f5adaae573fe2df8af5b8ee3f9aa74358729ac1ef63b31217e589b53f3fd86c6e8280ca412ae09278de009871e1241406138638e82558efcc1aa810a86489bb6a459d46e00e8b47c182b42d536489f049499daab56448c33c9454c05a9bde55a23da8b2b27c01a900588b311db1f700106c6f33d05e8eaf5ca2af61efc0270ccb94882fbeacfdfce810ea082adedadc1468baac0b6bb1a08108ba165bc4f71707edd2448e3bf2c011fdcfa770dfc9851a347f8367394d507ba466f6b64562178af752408f4081c3a998c9c8f810caaba2cba021783b75df7237ad0bb4abb161403e741e6259ca8c83d6106b0f007ad5818999b45377e258539507504c0f8454a97748e950b6fba1e08626aa0d79286d44831da1d0c7c12cca8ce0dfe0fd426922c3df06a6d6aef202480f38aee00a8a8eaf49d5cf199a59b5cd3f9e30e23e806c22bbfc339c64e1f2790eb708f49720d0af75c330751384268f5b6e712ba0ee2a4288beef7aab2469336714dd765193976ae746218e6fc2b75a3f413b2468a60238c6929a5e9b1fe24813f95b8f03de93c79a522d4d9261830ca04cb7e35317044af356e6940161decf98494cd3d82a23cd4113e7c98b8cb6c9ed36c24546c1eadac466c83010cca630e1fda2b81446e70ea1effb5c114c93e88151203f4bb4e4c2e895dd47310c80b93c7d8db44d9d41860072c71219e7651bbf5d3f5cf6ca4d9c871de7e50431f87fb2380690704fd11ddefa4ce3044a57ab90e9bfd7426c61195644ff724004b871ea141c721a6ee154744cfcc36c85774c32196df919d473c7e6ae145ca47f29c38519553efc5386ed7139d30d2765a2d691b5e7e932ee224d24d1abf58ec8154682e1a245a3adb6fb24ab5c453385a4e3a63d46a2ed7eb923a86df782c74e3716f87812da472c91e04852b7f7002ada2cfa32c61561112ab25b45918a7c6c161ed42dc5625ff670e765e643267aa1e886971a9bf70c18df3c73bc1cf864321685047cbb565659a208c7a349398edb48dc8844d9f5088fb1d0a60a51e1d9596980b9fdb1ff807c03ec024c387bbf8d8c86d4ff4f51dce3507172c8b1c4244e16340560465bc75062ad451ee64ec290abb8919c0e257c886e8500b0d3042eb4bf07b45bcd25d5d79691469522728064267fb81d18eeebc05e5a0df6712cd2c6881f9f23ca4ef946af89882cd13daa03f30e669e1bb2d48289bc3796271efc21dec004c2984b3e3deb33a0b4a3bb574b7d300f7bd89b6d01c72e7d2d3b391f0c9209bc6ed207eb8fb03323d9777f1b4228e95d9572670ce165e71363d040bcea587ed209270461dc84439a8f8d6225f1eedaf5222f5e4d4342aae9993f1aa64e6c30dce482aa7582278cfb66b4eb071f258f163371561a1693b0866fd0a6bd1a87699bdeb4b3787d7fc76fb09b6608fde1886d0a35488b0866f224f6f1a694d48b3d2281f42700f1d847070f1186bc846c1e737d99d51b3a38778b9f417e2823f1702806fbb555899912ed4e5a0ca8147efe442f4dac3832683da8d87230e33e095f21d6265894f002272419cdd48d6c9809b48bdab975f0a93382c230d244cce03181b63590d6cb61785ecd01b5b3487db762865583ea813c696a0ef9718e79c7c0caaa6488a45a2ac8032c179d105fbe85606ddf96e8861a48f57866b6ac0ee0b04729811c33220a6a4e25f28209a2e29ab4aeb30172de98395774f1556ac756b86348dcd37f86915b4bc7ced33bcb30b56066ef96e6f4bd650775617a210074fce1adb1b480bfcfa40b680a2ce011676e4f34c15c16d205affc2115b4a1e58ab201264870a5fea983eababbe62e5818b757e1c7c03b3e71ac8d702ea4a24f4952b485f8f6bf21be08e3a3ce0ab90daa8bca833cf1eaf140880c6543daebb9fca75efba097f7cc69e12480f2f02b195a438f412ad4a64ce71f623c5c7fd4bef74793e3c43c7768101dfcbc81eebbddb6da996b69332edf90b2c20c1a8486145951f07e3572d1d749e93c3df877168d646ba15e596dc0b0a5b3b29052cfe8bc97130370f225706c6e634f56d78f48aa84be647ffb24a8884e7ef32f5e4362b2a12a173507a6c0d8e7a5d13a654ed687836489320109aa34fb96b7895f7cfd36845c1d4d740d7f7e3d97e383f6d1662ceb9fc3040d9657dc39ec6ba8e5865bc4902ec3ea1ded22f605687fda7c41a9d32c64d34c739fe709ff85b4c6c29209199951d6938d3accda101d5b98ef332a495f532ea1f948cfac70b35a97c6a9de484ca9f88753bbb6330ce796402b7963c2a0e19d89265961644fac5c4657a9c5a2f57f1bb797caf7fb8a93cbf7c36fabd577281f04aca21bb4fa03f3fb44b6df8f0d073835c4b9c00e783e61b27d983d1c0e3d98b69f0ccb3435403a4672fa9815626259326b14e2196b9be3c9f065c5e541bfc5baf006694fdbc4471707e5cf9563e8811c800f0fe1826b0edb1dffd7eac5c01d223446aaa790dfc1de8fa379035e60a7a4d3f0eafb19cf858ae34fbfa7c6c177a45bdd92df8b8aa1365c1602a810c60592ec3ca42cede09233a03bb118ed1dc8ada103b7c11ce0f5bc52d965a67cdbf566e8e48bfc5d5309ffa6c2b445d401fde66abcc420e08b80842014a027348ec764003d4d5a571f92859aa2d3cd92c24852ec8d30268466950ee32de8676981d1cd7f10306381c17facf004a75e3c8f990af28d3c93949e026bd5d0e106a5e22d300c300d6ba3341fce88aba86545339c439533635d23d7f366bb3c30dedd3c5a9f0cf582af0dd58ae02d42020efbc573e56566b5082dc896301f0865891a696e940a5290ca8ae72a64ef7b2f68e0579afb751e86b8081081d6572704ca4547c142950c4b799671e9406b26aa120593df506b429097b17bd14f69d4237c239106aa023271626005749a69420b8c1e4d1a64af4586598243e364acf86a96f2dc482727ec959787ef8709c37f67fe7047a27f03f821ce91c4ae6f8a9e976eda6481d3d334a65f214b12565f4e44b4dd9ca99e61c592ee194b5bdccbea48990d4a3e6b1812c7f4db00c7d220d7488276622a16c4bb8959d211efae018b3b150145347b0076c2bc2d440aee2643401e32175e1ac0d46e03c180288a30cf18d084c5ea95568eab186fd3076156fcfcda8c61d95a6fd5fa5158c4f468c4bdd8a0cdf3f5dadd98fd4cf56d990882ac2659adc70eea0a515b8352e55cbc216cfd3f2f3d07ed9d3961a8d2514c12d8424da5f64ec041ecfa86314066f4cb822b84038bb6e9c1e8e981cc9c32d80201f60b395fd2adfb9a4e7c865729f8aa2e945982d83d76699ac55674f7a45de18a48f7f79f02c95d05ed06a880df3aafef0096336ec9a4023fc518486b068ef272345e24b35a85bf250ebfb159a51a66c975b6cf99f146b54480cbb642ccb54be44fda05c0db63a35f5d1da873dd522cbdeaf0e8110cfd342fe6a04f76d9b94ca8e12525f52fafca091fb3baabe52364f19f4d8238de9aa34453cd16c97b9e6a85e295b0df0bdde411765423e54f4923462bc4b91cf6d5e746d19cd23fc15d63fd5becf50072c54214f43817e6e4d957f054cac0e9c24b93b665820ac208c3fd388df7ec19510e4884fe23f319ee2265aacd350ee937c72b738741737cc06ccc86eedbd1c77ea9679f9342a259a07934a5e0053225f32400cc6bc542b21965957e4edd091a5a2d3275be4da9cd22b0794ef65f2ab0fb34fec7a42f5da64f2540a3f0baf66f29fbdda4293ffe0bbdd6999689edc50911d3dac3c03950230fbf34d84d98e9e8f00b849bf9f7f9a78cba53988f3cfa3c97ffe0dafb10ff114d6a7e53f9fe64b370a7d690e4c5ff90eb69f72332aeb73e0018dbf32d7ec1e453867fa810dd089d743714388d1cdbf555dd6a7be07461baa16fb81914dd4a7a629c65fcc69623701afa5151ced1eb80420a903921115c9111adcac37c4d8940052bb647f4228481cadd93b6b7f226c9008a1a0139403b5a84a4c89076782c4468b19dd9fc87d9bf411fb3562d5429d18e0fc3a46a58629ed7870008e8a277d306fe67528c16dc4111aeb0f67130652e430cd5c9a035fa86f94a5a7c8b4e6401f1cfe55acc9219bcf9be5a485521116897f5cf538d06983ac1ca10008b0c457824952aad8e46e923e1b183d83890eaa5fe593930de4f9219cb9a84997d39e04ae69a126436918823a273b8b4293eaf1e7cc31ec558e252c7bae682db0a0e0a8d4aed7f591e90937a22dc916850669e32686883f18b0ebe11dd21a988200ca1d3bd2f2d4d805e35e61534ecb9338cc45df7fe7f1a7872d1400121f4405505bf9f1ba2deccae23f2015de635718b8ec8d9af27e676178fce24d6343f8020903380c11db623b88179a723e3df799ce186e592f7ab0154b063e27533a2ffce79a4d3bb7f044956152a0277ee2deacbd5786c55cc0cafa4e0c21b6b3ae23cc57b30a6ad04a9c78f733d4236c846ea0e5d34be3798a345fb23ed6ce9c41509f1fb2c130fe383cd424d21289cdb16f7543b0fddfbf68e4f619f70b6db8d77d89c83e547503d00ec32d0b5f74a2f127cf14280451e6c2f3d50330ef64c18ab92e5d4dc3a05dea43d2a172b1a6d369f9249f0dc34b890eefe7a89e50de7c1337e7dab467d0c7c4e7eb519234bdc7c39e30c573e0c2fc9e2497ed1e4c5a608f571d9b49d02f223e75b0614a352e3e8500821c15e2a8169d75aa17f7d49e3dcf5c6cd99ed7dbe311efb4f823baf60c463f6541d0c1cb0cc3992ed7b35cb3bd9b8eea10efb54baab041c39c95f533198792ff53f161f970d9e7d763272c9768f3ea7dc3fdaf6700b93ca58d107ac1224e2d71982e6a7d4f3bff62055321b123cb052d433e0ed0682e3d6935688a344f6e987156fe23d46968f26e32fd3164afbe1747852442f85e41358fc41f07651d6b8dd4714adc6ba495ca2a8086c375ea30ffa28c0a4cf8fd0c70b8affbc42e88d82f8a9eb559644b097c876cf11f01b472b9dca501f4dc945f82806ac739b8df1a3ade6ad9bfe9258e92fcc5aad4899ae86b495646f40259a0a1c890968fc9ec0442fe42b466297790252475725727b13587f50ed0d85c28641f235aa690641b15e9aff757eeaeec12eca4bf33e753fb84e3046dde71ee355074ee7bcbf3c8a9bd6af28d6f446b80b7abb929e4b800a261f399beb1eb5a1cbd44767a2914798755fd611f5afc6667d0b9a20b6b7739bd46dbcadc42719b5a72861a5a041f50584eb392151473dad296466827eb8d00f970c7185c426b5bcf52033fecf822b0dad49b93b04baa7fe58514108c2c0fdf49880dc0200e584061337111a2b37e70cb6981605a2ed8e898c4ebc2455832df02ae46c161c74086feb8faa3f87010b6f696fc375b88b6b72c63da9edcd2aee19d29c70ca5a4bab65d7343c6ad3c6be93ba709cf8a45bc393b5801e12a188e23bc74f71cc7dd25414758ef11791771781f8f3b08f20a84d32d76d6e9035626f2b2fe85ca921f9b489eb1e3b72df0133c002dea815fd4ba796172967230242c8c6ea20575b3c31de623cd6a279a8905ea59cbf00314298a72623f4c4ab7cb9119dfd96dff7f8545e75f6aa99e835022a6c01706a8a306e6b18776d5963de4bd0c61785b84cc160bea3562c2e0360960bad3689d5981db4c93de2cddead492351a3aa84ff36dac7583580527e9de4b58d89bd8664b5a7247a0c44ed38cef910576e4a10c1abe656755221cde6565d20a819d6067a1cc81b4c8762c70d2646039005f96bea7e60988d99f96a7e771a0eb411b9cc5c4037bce68d08c476f8fcb5d67c71124854b27742304a5177e9ecb39851fc845a956572b7ab20e141888e60e8a9051bb5af1fb8ac8a855a1509b9b84edc3ca8a3147d67ec02e6c42aab232a9156a78ab16f50999e079246c2aea5f63e31ece0438eb2f86ad7d62d8ce13ef3c9bbe56ecda1f151a56427e93baf301beb005450451ac890f5547f80de79a715d514ac155ccc2822869c8f5aa1bc40948551143745907b6f508090100b041d5ba2385be169dfdc9e1714cf5247cb76c506e7ff9a4c5feb6f88b8c1f8de44feb61d495871cc08d821219127d1164e3846c56f7fb89fe37952f3729ba436612eb3d43378662a05a3e7c65dc1cbc44acb179e667a99c850b94e65779db71f2c2b64649408d686b50b8e0d85700db58d1e8a7711638aa3f9e12a89dc61408dc52f128c09bf390a7b47c42423b3a839a23be96e606c959f6b849dcb95324612fa29dae4227b259131cca219bc82e8a6f83bd1270293e5591dbc1ca8e727350f51d361bfa9bcdde56792c1812a62f0eab2dd069cfbe9e480a02cbc52806b08e4d216ea057eca2cd43ea0e99838a982f65030a81b4610a2687bb66f959d5382646228089231892973019ac82e42de86d414879004f5f2382f2cbbf922341f41f1e28ac565bc455c9ac9603df8b93b670b0506576f4e5aea5f499d8b0ac7da7c46cea32b2383042e4290d1f6e7d900502f960188dbedde27027a0d2a88332c2b8451ef2545f003aa03c3f865b28703509fd6f5da87fb022d2db44cfa5bf2132a8fbed6588259607ddea8d401d667fa0ff16a8969fa16b527c796921935e11ab9cdc276c417762a655b66a1a24915089733a5db389b41adeb15549cb13ed77446cc81433fc56dc7b8d7215841981a8304434423e2b09855c11db6d85c0ee20ab8939ecafcbfcfd617d64236ebe561efd5ef17c91b10b72a06952a9ec717dc88c488b5426477f32e2a7b13321f49df60d61a016c42691f6815adce1bd0f3b42f3f71ce07cae44bc143fcafbf13656a9f299e358fe82df016496877e3bcd459de7b87815e9f88d5f7c09acf6cfe8ac973fae0530e45dee997a80af2fd0418bd9fab6e0279c16cfa4e0faa9db0bbd585f58a6042ffc700f9a77c8e0c3520f5e6c073bb426a195e67721be4dc61ace8ced95d7be69d4bd59f445788b4012c2aa77e3c76c1878b6624b40ea7ca969ca0570d5f528798182a2c5543025d91a5f91865556561898358e7b3b567242d5718f4a4b836afb11ea1b05981d463015f96e293e846038cec21a893b2e0535b18473627161e53c67378614ba7e0c16a2d434840c2a1db315d94100b552253249cfa9d944899004160db803aa86c92bc07670fd0f82f6ef2f855ebd9eb287042758827c8390a8739091dc50c28f401c686435ba6324294feca9249e75f2f86c09e77c12daf6c9d0cb8c625108408794d8230a2d7ed032694b4536c4c0954603d1f22a8af9232277573d12f29898aec7584c8c7dd574b7e6c5f32556c56db57f5f54ebda419ec1d00e4b38de54403e71cd560754ceb93a98f7f0f8e1e355f9798e7f5c75627b0f4342a660584255e7175061d637dc185e0520559d8ba899311c521dfa0cd338e50bf32ec50015b9fe7aaaf26fdfe03c20442e77de90398dbf8a9152990968cc23ab344012bdae77346a6ddd4c2c4342c3f759ee8b22910dcfb713a9f978d7a8a8896dee51464762cba19456ecd68e42cf0e3f9edb34be38d098e1c6ae79a90cda1cc35df06ca4a777b4f99d88fb062a7d29079f803a241ea39e86d67d7847e21b4eb2f28e04f075fdf8c60f4ac20dcb69770911f17a27ee66089f5b9ce5848b28139cd60ace09a77a5e1345542a4b08b8c9f8af41354c806155e7c49e98255733f24f0c039b24a4af454984788548e74d8c45850aa5d3ab8c4681f3f3e1f074032580f9164288f160c069a356276e74e122aa4bdc9a5a353480582c02485a86fee9852802d02c4e3f9a92620b65ec9d6c88225276642cf6501dd69bd898ddec94671976d740b448cb9a68184de73d71eeac2ff1d2c2bd03a3513199738095f404a5de21a9314ab2b5b56def8ffad7136c56e6e0ade750cb7019ca7c38f8e12024c179599d01497d6b683a1ea029b381cb905033b3b741a6536bfacd648e91956139d4fc3c3e162d75b087de5111936ecc2721efb2e1018b577fbf6d340793efff042f45e564aa71415dcc43dd8eea0a603f9549372b77ea3f37e8bae26ee8cd1b03a8111e7eee398553df187740d7800e2387eabd03bd56d422d1cc5640a854d2d09c33c5b41484d1f92c00974c79c871c74b26e98d944df014215ee8b0c368d7fcbc6eb1b38e97e46cb715d12ec6320e021c95c2d11a51ad287c2f5335027138b18c635c1d05815585bbd900a95134321588bb6334912f3c61a079bcbdd57876fbe74b98577cb89ac88764586d4f27aad7286cb061d95c358048e37d54cfe004dd7872551003dfb87bc498db894834cf6f984bcc2c1c3c10a5fb833339be96e880ae009ba05160a4715cb58bb690333a4107ccf11f1d169bb3518c523e8704634ee4d97b8e54376b5d9430029ca07b81bf2fd41d0afe6883461ac54117ee313bec9a588c2c76c168f6b125183088f880f91cf212f87b52634a0a8a4f4df997657d283e1356c8953a6e14326d5568a5b7beb61aaae162c335165b84fd43da6286a558c33082df6d6473209e5bec819141e17a16a7ede2f5a16931c74189f0937b164ead88f6c5a23153184a26605eeea3b522b1a2612bd867b90582577844b518ad65d3fe7737094ae6ccdbac1312b920139c7d2f8ab687643ddc2f1c1e40363e0eb613273a4548318ae8d46b08d06ddc5014a3f6344dd608d724135d8a473b806114e7c7927568358473dab37d3a01711a6d2af82cc7f4cf4bed760c16500bd56201cf6b956d773f586b28be21f1e3cbdff8847efcbb587f1d5dfd62e6ed8ada613cd0a6b9bb1d3d77fefc81de6030c6e7fac164815a6f46535dc3d35ea8f52c88d7fb124c4d6028327576b976c186c725762fe3da8ff3c5b4edca130546b6b90205265641b514b7c8720cb3d393a799a41b7a7df37178dae1e5583c3ed9dd419823a1c3b015e7d969dfa87e90dd06f8a00e343bcd969a3250780e8ed2d0cb11cfc5d079fe169aefe60bbb1c258a33aa77dcab34070e49c5a3b6ba5d793703b2be43971a9b5ef1d21f0bf18c48a4104838d1e009a9803c2e84a9a7c6f3cf9efe86c773b5e60681b6f430552ec87c1fd5ab34ce44e3bb23cdeac0c44b0fb0155aa649f50f604c10f924d4b4b99230af81aef428c125ec0f593ab73415b1e1ea9e080501f21eda37715a05231e15b94f3a75ec9e7d9103d23fc5889bac737b1fc8e25ddf475ac05fd388d189846a14eaf18b3eb7f64a98dc0e47305b2660c91763e290e43f9487e4c2a6916348c2b1ea16ea2665d411938e399b63ac8b7f98f7ac2047928096d8f47af9dd0808b7564f161a33bed9672b09739294508e9805673fcbb3075cc48ce410b9588f1cb181b047fbfbb94f6704853dc6ea969d495333ce9e549b297c900b8248027def361ddb84f318b5484a2713cc4c2ef3aeb792c7c0274e69e8b42b68d8a479fd0433931702bf5493f35d20482b6a950822dccaaa0bdce7d44dc9bcb501e1c87885642bb69de76abd57978c7fae1ce09792103ebd972739700ef805c29f45b92ccbf22f42a9a260d96398825e70c0c2a9798acbf548e6333438a0c4220696058ab0329d9498a019f79544ab79d46a1aff90f8ab7da8d1230bcf1d01c844e63459f2a14244b531bd37987b87d065a3fdefffba6cf010cc5cd5daf91211a03df5f0d58f3c8e47e34ad9b34d0ae2598c3583cf0aced862bb3a6bb022337f6503049b38ac1d8805eb75d5fd2f81439b00c4a6078d2829313306ecbeafd4e34c5a0cb4c0a33c64da91845883a36fa0c3da5b9909585f84ead6c60390789f9e160b09a469091fde228f07cb7ba48a59d4e1e6b202c6868b0a8998bf021e0e433cb2209c9a325c012b9545cfab8da300b51d771f35360d06b0efc8e41baa5c9931a4f1ae7710177516a491f566dc7f6ea6197740547968c56ffa4dc65b4fa594b8cdc8a39b72216a33ed41c0c6011976808f3e52a59ecb0e49aeb220a6f70094a4b80738fa0005cdc47ba6fe53cffa942c03be48884014a480361cdf9bab9dd21e948c600208a59e1a98796af00c22b12461afc11bac615f70f87b3909773b87938eb6e650ed8b553436acb8e39552aad97ce25e97e937f6fcf60f1217425556031c875e4333c9a79279aa67f246fd9afeb9b1bbd2af95dd18be074bc008feaf1ffa298bfd7280889819769f20a8fb19a65f096042a0ad2f7c7cbded4eadc018b67328d1a994e16e8fe56e651806e586ae5b6956eaf646205b2b798370387ce4b6af691490f287992ad4b65f6fe03d09c087a8e0d4e74aa324b5bd45992315b018376eef09a8805836a53491542b2dd96635c56441a70ef476ee0d598e550925353a565459ede45ea52698dce3dbb4344b24509dc9c6f9317c8317b2b7aa41e33ef3c4ba05d278dd41726278e4dd0c5c0aeac6490efe4454954904871d508b27869545ae56fffe3ac1d6c201b2b40c48de0465fffad239ea660f40d0c3fbcd732399c28840d3da4e0ebd65d8f3f1560cffbe65eabb539dc19aa9743dbafb1aade030fc5a39ef220c439194d8f42b9be8be8f3a241b8db5d9509b579911b34f7110d8d33cf98598fbd09cb3341cd9cc3a6647ba8185929d7dcd34321af605eb8eda08ceb006550c30b9f6aefceae20d61c27622c3dedb861b3ecc50119b34bc50f8bfb3c178a0c2b91df8bca15f269e8562f1388d0d4384c5e03af3f011ec666e540af879f11080a9cb540500e7472a639464d9b14ec360f6b7542c700da44ffdd20d07a62e107123031fb56cc3edcd0da6bdeaa4e5342bda43b1d9b032db54b0b6ed68eb6c4f75986bbf7e5bd6183a65c49233ea6f76a5ffa68fdc9181dbf558ba158a9c74bf9555be17b47085dd4c8424db1fcf060c0c6b3211eb993015a635bbe1a40690ca4a216c0df8641c76e62a093644309222942edcabc3b4b9b6558d29d23d8f7b260b0435d31dc7efbf9585316119f8b2bf7410ddd37aeaffa137628786f492572748221b102de55610a09d194191948f32beed9ef51e28e0e8428d558b7ff630e604ae34501150609b3fc26b3d8fb9567d447dfb4ad5aab6c3f949c898591db5f95b7cb41e6565a53273d054a8ac6fb57724740b9090c37da165c7047b1e57b67f4c04e708198cd7ab052b5107f95ad8572b4934459dc58d589393a2a600af0c810ef03dcaaf15dbc7cd32685c01c2105430611cb92466291caba299b3acb998589342f1de55ab04ebce3ac9b88ed6ef1e22c3e373b50a19babb773c6eb0d0abe4c00a974f8ebeb8c8256121a9703ed659ffc759c8bd1423d6460881f84156095e3672db55e58052622951ac52cf24c141fda3e45c9b2f71e0afe8951bba2ba0b74fd6f135efbe28c9114594afb84a678d10dc33c45ad3c8f112ec3ab791df744d0f79a64cc3b05b38ce84053b35873bb73546f6691f0a72d2748c3ecef9118247f88ee8f7e868c7195c1bb14190bcd96cecabcea97c9dacec40212ee0a6505a4fbb62bd2ad6e24017a863b40ef3d5404bf189880d74ca42aef662769403ceb83bb409cb4f08b7234828b93dfb19cce424b7a8e0528fe6f65e8a4f74f78665a11fd57c4d9e4f13f38bae878a1887aa43fc41b190ce9a7822520747abe2531666e9d40d85b8efb3191d9d10c375f729c619a331e4caa851a197b860efdbc64db3048f58d796d97e82389e40f3b8a8d4367196e3d4e8a614075e87afdae2738c13dc35e807efd3b2a27b2106158828765cb50fed8eb3f49deb0d22dea1298f69a8733299976dfb8804226c794551d72cdd5af8bd4b93d3dc2e85079d4729a01763ca2e5a3b291f7c87810e0e57f32e4f0afd7338f130d45cfbc2f8c670674f79f30e7689d798e3d80a44254aebc81372bd4794c67a0ae6cda1c808b2e8deaf5dbfe1c18ba77cf0d111a86b0e4eb5d0599376f94adcd64a0e9c6218f87b750f8628813813c82eaf4aaf9b826ec39f0b4d34435a1aafa73c343376fc66234df4e4d7e8dd9e9b132498e36384d2d87ae08894027be94521de3b174f99fa98728377c49a5e201134470481fd411b72b5ce56533c43e005840d4300d8305715b27497195c23020ae989445360b88a2f135758df50793faf77edbc2aa35b007e2536eea3699f593787e9a740cf72b24fd0cb37a1d10220c7227e96df98a325ae2a65d705ab449682c1520e516326a88349caa752bac30d81892fe490833294df5b22692952bae30c3110a5832f7792ed010f39b90ca85a86cabc316ef5844ab3f06311a8d17dfbb340650c26b89124c90c5ff85a612e265b0f570224ed07daa685156ae5f782fc81f18221360cbe47dcd2b221ad969fa16756dc953aaed0d51742b30f6d3e8a5d13edcea43182f926f97cc5465d6ece253381011d75f476c4feeac01a6eb39b1cb4c75b505539d3947a2d2074c8ee08365f49e29faa7eb6c3435235c7383753ba320efc81a3bd8f29f17b824ec40cf11ad090e66f700d2eb21b6ee947f6206fc038e89bd20d871fcc7909d9a93d7fec29bd82bcc5f75a806fb849deaab841fe8beee9a6907850c0e18656aed070d34e98e6dcd432966259a4684eb46a7f90e70f457eba8a04521c3862629e8e46896e920c658e721a2e8beca34c8820c391fbb764b980811b553c6f8fb90a1927151bd4e0b63bf970c2751834e9f415411d8845291c3ec6f4fd643bc4c164105a2826269c07c4a86b5872d51d4294202ebad99ef7fb7b61ac2e8a4d6801107f0e249570e698556277c9b732f1faa90af9d913e539525728f8a919ea6a6efca00cb1ad9a7bf819052d8a730fd6609d63c3ab44633145ca2a86eeef57ac0ffa96b4e1a7e154ddd80c2a2fd0e9276160c0b0156c28345a462b30b27c169cca40f52f67f1b6f704fbce0bac49a910c30acd6762dd0254e960109d47ce79eedb45f9317bb8420b86efff6d85ef82e66f1dc7e62afe91461fd9cc3a300624307c5894e2dccd1d7c6be045f94df96b23ce022f60ece3311d022f4f405fee02af5baaf7a536ff67299d151696eca5d7d1b55d6f01090ee691b7c33c1f36bf12948e5f64fc7f3a5925b26f2b802dc6dc48d4a62b9f85ade5ca1808d320bb95703b845480a6bc13e46f48b15429e9bc97540e96d68d8594a3f03740d9386e0ced4a346370ea7409ff0ab1233b38e677daaa31c16c573176c2e0c57a1e1bb522d8d80e00b6fd82790b608885720e1937d0c3d275c235b48c50ad2ddc8c70a753212b49d1bedc487f86993185e0d00b85dc5920a62ec9d8591e7c9b770ac9d540aaff0cd83156c9f448cde38efe9e5ee6b356c485622f1c2058e8bf1bbb7f6c30ce29fb1d608c833d883ac017e764d5f5072dcc53ffcc338e90c2cb5769e5510bd99b16a99a6725b43644aa37324cf60df7ff07fa3e90feed231369d7c9c7b4a74eae3cbc2e269aad4c5198f6a1de7cf9b3267467e3526454bf95dfeffda0298c97d3a28852f9b04fa74554957aea3200a84363ae645b3f5a28f17f1993fb007df79cecdfb0a1319255ebccd5115e9b4ab85fce317e912f9cc4d618aa558ec3d4d26cb9003ed008a99feda309629af55440a26d320701589c3ab4c9ccd9fa01d763c21c1cccc25a5e7aaac806a2ffb5091b0549796075148ac7edfc9588cdec24dba4b93daf6b2919119fd74a0def7f1693b58157f4ebdec4035d6e28172772af1d19a18f9cb8cad148c223fe9749519dc78c9d1f03bbad70c8a97fe79d8cc888fc898a9ceb0e5527bb8a2311d06cd4afa51f6c16efa3cb1386a150355e7178964dc3238d21fe9bb2df2f8034308b85e549fb741c74496fefb4d936a3ff6e161eff437676f8e39ad449311edc5310c9cbe66e63ad804d201a4fb20a5c74b3f4933c29a30adb127c46d6f0f8bf3ed7b17a0a0a67111c3e1d5f4fd495ee85e1d201d257519dd721727ed424623f1fff1e0835b910657d30c778feae630eecd1250b31838a329d5d476d9227cf92c4979668f351799792616d904620058f579cb9f81391adda937600f4a58c8cfae428a4e22d076d0f467f8d22df75800767d240f063868a1a0f02f9550a76ede5fa3d874108626ac07e3f6e87bf0b616f99a9fee2765b50bd4c069e477db200de6fdde3c9e6d88c4a3a738c880bc3b448d3445f3a797dcf0f7f28251186da79018d9c70c62d8b102a3d9281bd578d4d398e18ac4857be3307ed37592837a21491649fe500732ee5c720c513e0f30a8ed0d11c4501009fa74cc14c5aa9de24c9522b20ac139e004b176576cc5ebe8153cfc1a215cd6920415822e97cd7e36609ccb795e98547d458eb19f5dfe7429cc698993de36d030eaceeff63ec1be8af7b415bde80106c968e0095cb57f61be53374b770b63696fa376de105977a411fa39f450b02c78967ed4e4de52e4919d64d188458cda89c3a7adac8f3870b394138656497fcc52d88356ee3735324ef6afca03d405ed87162fabaeea47e5dea5a8b4075888a6c89da771af5bb8f53cc8fa8f5e7e8551757fedf58777d6f7bff89fffea055a0d27d6f8d341e314a9b48b832e2516dc069a6e57672bd110a0dc1648b7cd42dc710ce1a82dc0a6704e93edc6d5df686aa2bb14f011d6f22d94c6fab38800eed191cafb554afe73769fcd14bdfad2f907fca4a80dc6d1412612f083003a670c16376e183cac597b774b05fa4182e6c4f489afe77ad0c6b599d852d9f22feaf4e5e91eb8d452dc5956b7fc5b1874dd15659fae186debf29ce5d1a204125aea3d806e48053f8b3807048e36ceee745883549953f06411d3c363578d1bbbff77027e31fd74a3df182b31392334ba8f2d3248818d8d1e55272a63194850e30a6ac44adc3c83874033e52782227e9f516cb53e2ab5b47816ffa5d7c7cf001ef6a150ee43ed03d17c5e489a9e8bd9ea088074d07ea197b648835fa8f8fbe6c03375a0ac9506f4c920e91bcdbe96c312b94bca04856a1e72a289d1261a5647a18ef91dcd7de532a3172ad468941ec2a7170812fab196547f10c8e3923b4fc13658903600c57fa59ace928f6cd9a5e1ab4c342699739a087e79874ab29d16aca42cba088a0233cb264551416a0b2f3019a75c525840a8f0511a060932679801e34e646938af8ae2d99162517e301bceed4c31b1475ac1f2aaa84843c3fa69d58d1ae7f3806594020d6b5ceba95219dd4c3448c4dce336d561fa5d10c097d1ab0bf43eac41365dcf3e460ad1b3e5131a61270fc2154ea455c94806f2b7e64ea1d8bc4b772c82434ef0a4f4341ca1131926824fdbffa90d634b096d02c2e8a959a5c8675dde1b08426ef988d3a6a495d3cfff33913bab086cdd0b152b96b6cca79559e9a59a936f69933acfef18bd016d7a8030c0800c8c6f6841f7c3040a16e42b19db86c854e41cd844527ad2a1256cafbd7fca4fbae8aeffd3b4050bd568bc38c45cf7a3ee903c2356a71f4156329074399c28a1cdf6652beccd40ad19016091c64ccbba18be77644ddf3fe2992a49365f78dce2b0e9de3cc393e454dccc4a4b6ec4ec5942f2f3001249fd8f68e8ac966be8453d804dc994f347b23598a76dbc9ac43f372376220497bb0d9e8ce3d8ce4e5aadf27e121ab693e581c3a34269b25a3ae2155346c5ec4e4f901b4090b2379b0491be512238dfe0505475ea7250023fd6ee73a11a236684721646f86367bc1cd91a8bf1aa1ecd5d919dae8348a285873d6841d3b297fd95c0f8139ee38577399f07829d77660b0f7d2a037604c38d271617974e4066540a382ead132105f0d50c69e7e4c16746ed2cf0f570dca216fdd7e9b1edcfddf432ba3cec537392df37f0dd464189974efa5bd74fd77848725b0ca992e6886ce6a718e1ed00c040050c9eca061c09996a066151b541b2fcdf63ecee7ee06748a6e4c190c5d2ee038dfcd7ecd08d93732f4879a33c322830bdbc8255d5d22643b61205c95e29cfc18d90a270c6774b51c1a68b64c52f5d39adb85c277805d97ad3ca94f3aa14f9b71357e2f459eb1359cf2d6b0cc2f0ecbc041dd9efdeeea63850ef7ff77d7b08dfee9205fcc0f67f1e159ad114d83dcaa4b922ae4f661be14f51360339432fa2c0bbf80d47e3ebf4756408971c0c1d6076189e8f3f3ede59075a50d9d1089b1e1eff662694eaa492756a71005a883c3363d4c594c13a8669593373fe011745028758f5e4795b5bde3c7a90487d4b6e1d860be6ab62e32c90b4a6814cd55dc73c2c29557dd2f06c954715c1128f1333bf9dc7dfa3dae02a4cc6872f19c2d21a6a1c62bff49fe195f6a369e17292289bdfe4372e82da2efbb6169d64b844cf2954a2ee9661beb8129a5a7a43133e8c03eb04c12e17999ab4308a4db67787c464550d4fe2e4dba0c0cd6db66270a62ef9230eff0dcc66299c11abe69d8fe3a50e29749a1f992bf3f7e8e9e25559b1597d2ba5dc32167e000c905ecc6671b561b241beb9bb003b57dd00cdd41c724776b70e57b995dd5bc7c26d1040e9ad221ac096cb6e75b414f72e4f6cab26d0d695cd7862bfb0d976a097f5f1009a12b0d6944d5a4f33c6b472c90087baa37b2502aaf8dd26f5405722e546ae01f2d12278c54b6bfe8ae2392953e61fd39faea2adef2cb7c3de965adeeebd9c83db8061adc851e706186c8f7767be31ed4147f910993ce7efa51fc164209b4306ed7b2722f30292abe3fd2c86540f6ed78d8168586510dc14088c7eac6b9bb5a863736b112f55dff93116812a4477a4b33526606e58d68c1e232c79a540175b8f021905a1616030008fda52ef258a7a3efdcb5fa014f66481b4afaf445c4f588c28eb62fef15c4c92e00b0807abe92edf500b99613d34664808f1efd162845b00a2357bec38a28aecefab7c7614423fefedbe6f125788c2421c1074b1ef6a680b9edab258f8a17503a4c40364d3bf40820d4746a3704f0615180f9e49d77dc37f8807b83dd1121f095300f0463d2e55d680fd49cc390ba09f667a0b1e3c4b5456f157972012ccec0db1705dc46e9d2da009261fedb9a173864dee2b63f009c253c25fa4038affedd379a16bc5e0e4c020e66e6d1b849a0ee3bcdc58426146b025ac21cc98648eb19983792fbbf6a6420cefc0b32f065d763c05ace59d13e6fd02f09c2becd2d78ea1f919650e90155719190377755148bd81ca1f8806d7b71b51afbd388a881b4a1913b873f2db80ae5ca065a76ce55d045a61ce15bd4a438a210fc29707318ac4cd2c27c3074f0a3dd237fc1573d03c65e08d7cc510e47eebe326501ce9bc7153250baf15eb5d9fc6ee4e6ac816af919ba262cde569c950214e76c82742082704f8741a45ef2a42097d0c0de681e9d57b86212e5751045c87522551f7361169418efc47a807c1238611c9f530000210b2ac7e68786355ab34c390681dfed5f7edae65e11b0a08a84784c51ea26379dd8e7516c1961d530049a20300373266cc04048ea964699170f18a2c424e50daa7079d30b0c6455e8671f201cc4ef60ccf5a193d61d8015a2d58b536d1b417f0439eae7601277dfc8f75116754949fc439d6fb2640d4813cc8922a314230aadac86c2c8181e72f5e5bce4ef04de320d35b59457060324de8000862aa6dcb9e59a86f7488b010953abaf7133cef51eb55cd054dea86615fcb1e65d76b0393343263fb4264d2693d6ddeff27659c9b6c7123cf61efeac10f4665eecb9ec1918a20c1d055a175ce015e2a58621be766d0756ccfd080943909ec5d87b748dd37e8f26560c642c3fed849929cb2a1dac6ba1a0feefbd6cce3e51fb5cc3ab352d8efb5466fd4591038ebd238dd7b7bea6116ada4d7a3f40abefbd6e7f7816f898195189797343edf92c1ce3fb2e1ee3de904919f191cd66fda77b0d7a37bebdf3ca88f871d90058cbb8cf047f8011669de472c64e786f196265281bd2e71426c4ce531ccd4e1e206d787cbf424df1d3bb76f118fe862fca54f7b9f7e77c1313955b6bf922c79e5985e0b33487f8ad5b2881fba4452a25764064d5ab6c6bdd9d9d3c761213dc231c79d70afef8dd6aa86b502b3cfd10693dd595acfdece9b1dad5c386a6c3b38d5cf55e6ffd1853680c8d046ad69bc55620103b4a0702359eda575e176e9f6709e78c135b30b4e2883dcd189c9d2fe0b9ede1fa6afcf521a19531eab28aed581093d93647ca3764780f4c79ca08e451839c6a34ab7b691f160ef6ba4942b6dc7bcb2da59449ca380caf0c4f0c0d5e2da956a72eb7482e5af5cc35dbee9b90cfdcc2e24f7c81e1a1c76b4d34c5557c8d4312260bf9ac8bf2c153c97cfb64ac412bb960d728cfa4a16bf8ae67fbacccef249706ad5f4f6e69d0bae6859d63471226770ee443b9e5ed9406ed0b1068f0ba6bc66cd08dc555fc139e80211491c47f0206505c4520afd39907c4afa7f9e645d1fefa149bf90b0d4e69d03a0b29671068d06e5e0caca752657bedebc0f63714f2617da15df2657d4a11fb519149170d178bdcecdb3c95f493c717e7a150d84f1f0d27c7d9e38b06ce3b5fc0e6f9a301e59b779bd360e39cc7577c71d8bb4f25bdf39e170f76d4c739ca5de01ce5d2519b771f0fca37e7b171ce7b5e3dafd8f18839d755d27b5ed853c92f0617b0e79b6a21be4e9e3f1e9e987b5e361ecf17176c1cf5f17c09bba07c01520d27ae80bd501f0ce5a8af061ab0e7e9d8bf3857f77a984bb8ce2ae3f4dd47a373ec9dd7f3c25ecfcba5178228d39330ecddc7c3336586b99a1e430bf165a5850e7b286171a9933226816241b17e893d94731cf5e41702f9d435cf4a3f7594278bba185c40f9e9e3e139f9e9934353129d501e4a94ce44c996afd8c90f27e6dccc331575b9d54f45bf2874cad15d22cfe999f530cbb28cc812d4299125eecd6d836a6ba594d26a29b5153f51049d5448d22f4ce13ca39db95b1267b6e2f48d119c734e6be328a2592c16ab084a299d55055c2cc2554f291d2549892e2973d59e3262b198e4c88a2e168eae3cbd196468b01d2365da60337dbb949ed43a6b66b67a1f2562442a9573dca1e7ecaef25dc95570be22252949a196f330955d6b3d1c5bc4b1e224b2c4c6598f11e765902238aea41f79e9ddb2737a159cafdf7dcfcec8134eb08fdeb149bb3c217e74b0e2574e05f1092ee1925b91cc1472fc4f57a15d60ec3c1bd7ee95b2e699aba84732512c7d2453f4c4d7ee89e62afa917929e62b67b1ec76b2d83de9bfe192c35804c618c19d1d493904c1d8c4faf4556c527dba91e89a4afe7ad8e45d609427dcef6aedd9678106dbaf37a81373a65bef63cef4eabd62ce7470ba081057d3e3163fbd4c1c459a091dd99f5e716df03407afb86a39e33c853d37888b21faf5582464bdfcebd4d2eeae5570f4b878ad6bf1a3a5b3c198736223478faec612d7566d460790e520577e59fb11470a60e06588312c388342153368e2287683d10daf36c11b15f6dd9def96ad3e9221d3c5134f918c7c246374c39f969041cc645cf098881366b54d534c50d021839453f9c84732465ea230c1876014edb1488f5a6b8c35463a298d39e2e8c5673eadcb9f5d141f6fbf50049f519f5cbd3de69c37b3746adaa6695e7c75eee288a9c7da757a2aedaa3295ed5a29ad5405653a9d1e9d4ee7accd029d597b8247f79899adb3efd5ee7512e7784aa72d720a45f0f4cbe0681abb7bae6c2dac94b46f7738333bfb5eed5e54fcdb2d948c3d3d4eda73564ae7a45305e59bce399d91d218959c4211484d561bb40cad4feaa92c9d9e6ad36e2629f5a27d2a67f418bde716ea5546da196756da9e1751214afad453d9aed6869e6ad3ae6c6badd796cf49e7384cd570715e99f46a890972640f97fc7423b72360be4e62e4f8b18b277942c35059e67efa49294737418ddc2e3d36d97e3ab7033c02d636ee97d5e4305037a920293276b8b1830d9bdc2ddc753bb4bd193dbc9d93a8e6a95b354f274ee724fb54516cf624bad1371d0bd633df2e3e8512f8ccb9585b4eea9dc29e243ea36e2beda480cddc2ea7a7eada2de5cd2c9db3e79c737e9785adca4945c4b5568f2d7b7a19f5785599749595aea2b2c6e94d3ca58c229db2305d7a6ca7d265678018aff75c3995accf8be5898c9c41d9faa35ee684faeca73dc2777bec6fc69fde1d6d6b9db6a79a6de7a435f6f4acf0b4152f5a9ca3d3e8338aa6336d7c4a5d7606b853eb58984f58df9fd7cbc2fca8262d15dce98b7b9f4432de8e99d6a3fd3c5231404f95120cc7717d3b0fcb0c6280bd5fb16b407c751f0dd8e3eb155f9d979aabe95a3421354598f93446e6322cbb273cd4afac2dc4ce05ec3629c3ce69586a639c8c70bf50ca6b1d0bd5ad632f94f2d7b90d53b72afa7642d158f88b73966556ce38382d09e36cdd4efe22c78faf76e9dd2b668e73ed76b22ca31927559aaa3357ddcc5ae1792adfd24cd228d5a9731dc6de9776ce53b5f7c060f1c5cdce4924238ef9ccbb7332c26f99f5ac7bc2aa747b833b25652a6f14ed6ff52cf3ec37024e8590537d4005ead60b103f30030886bcfda8707e4f65e4ea5236bfce79a1e655b6e79c935d94ec492ca30921ccac10cb68428832cf6ddb6b9b5b16ae77d36e3a6dedc6f9e6a938bb499ee734155555d5556576e33cf95b2633b799ebf4cd5cfae44229f68b2cdc6e65b1e53c850af84c665cb8ca99a06a1f023bf648dd524f3e3733b719956d3d49a9ab2e95d28b99b432e35bfa7515e85912027f47748ee90e95d54d80234b0f2ff690e81c5311c2b84598a69761be6d306090e809c34c1b58d0308aa580bdbb02f45020c6980276e23f603f100c79ea3406d36391cad33d595335d871eed6c9b13d792a8b4f217decd50b3bd73aec2a0ebb6ac3aed2f0e9ab159eb727ebd8da479dc2c854b0532a377621e6b922eb787da95ec3f68a525b10e2353d762ffc30534613429829a389a017ea94740223e348c6c7e0df51c14e9d43fdc971a6ddcea97a2aa9d513cf9fb017721e6e1e5aaf9e8a536d2aabb94a6af5e4c9af9dfcd8759f0a0af6eb18bbf58bb73fd52f94f29dfc10fb46c53a75f93d9bb471709b73b85b9c6f1bb785163e1cd530c7d5df7c763b1be7859d73aa6e5accf39d4f8f8b1e520f4faa5645159dae3a4ddb795678de5a99d7332b5f769cd72e0aca9dc498d339e7a1be50869c57b79cd37047eb9c70de8e372af75345c99cba5baf93350d773fd38f5dacaeaaef5ea8796855dae764fbe8aafb39d954d9e7647b95fd9c6c217d9d29ab5b5141a94ee5f75cd934a0756ca08b34b420c3670c184478600ea135ca98a2680a19b841c787b18887337438020526282222871dd62c051104c5134a3485b1e80da7830cccc477d7e143a5638d1d5f4c6aea018d177c01050eb017a02022882ac42cf102a63aae9ce00862089d23051556901733fdac5112871b5bbe6cd14112610861b18231ac38e328a9891ec422560fe4f830164dc102209a804881183578854c84a0c3cc17305003cc165ebca4df8f9d7c434abf52b248d04ec0299c71584b983428e1c84116a20794c584b504378b0539a698e3092c583831a6670bd10c9ad62893c31b4d3449b13559d1230e8cc122381947d6122628744b5e8c83f347325a78f161ea430c068e68c5098ab0388104130db0e880e6a80a2e5600461725b6203383684ae1064a3c71c49a1d90f0e2d5832586268e2622bcc08e5ed253d2a3f42b3d5585bc22a55fe929232a86d10e8cdee8410a3396c2a84087184c213d4abf6e248139c28833d0e8208bf902328d86f640288d0e55a441861a46f8d00507242272b8d2841839309a56488f577aaa93aa986d503746c8010837d6a891861a59f4b0800746a851820ea216f0a093820a0d4a37e0f8763acd7cfff8b64276b78c356831744cd15da463b3b44023fb8b325d7cfbd562ccfbf848c6065e6857f418d22f655ac096164c916608a51e76b8c01452c8e0cc173f24bda0bf901eaff4140fc8b4c0c31847cce1461354ecd0460a311042820932603ac43040d9d0c670c30d1fbc70b1820b5a82971ea5dfb803a39f1f6cc00108c6032358193556ac81454c163e6b3c2009130c41041d68ac01224b8fd26f155d80a0838b3137f8f9c9d274090a175e9ac8c227082176f08a1e3380a2d54ecfbcbbb36806893276a083a12c68de7855b96105455a9811a444183fd8204b799072385029a4322a603f5bcabb334fc3b6f1dd73e8bbbbbbbbafecf6c8c537f74d833ac2f8eeeece6ec6f22057629d145458a15bb20893ef322c9928d4e8b44ea73c4526066b5ef5914c1465de7e2413c5984f2ac39da0c8d163b6f342831202ce43fe74a4ab209d259d89741007593afe9ec423337eea25c51195c5efceb9f8a954887ffd49fcfbf57c9649eab5877e12e8a95292894c8280dbd432b560ae3b95dab414cc4899ae22d914c2d1a2458b162d5a84b6b64953ba253d7e335ca106b32bd4605669ad538eee0fbfd47b64d4534ddda74be5b3e7864f99e2ecf83c9e3cf6173289947a7bf5bcbe5c2412ead2250b570989847e12d63e3f5d1e4dd7945ecde9f25e4d07f285b12995fac2b8d4633a8ff9dd7ca9d317fa077ac8ba9f4ba0155c2570ae122bb8386b678a529c9254da362ac14831e4904c92626c6017298652175b69edfa6536a765175b1b7186eb6196413a95732b6f46cd552c92df8e183395a76ef3f517defc65d4b703c2b7e3c4f293f85646b19f4e8ff9e6389c3232fdb04bcfd395b51ed690c1184d00c9c1f492fec27445a31ad471821fa22b7840c58b0b3092484a020d3264b0a507054234a9a1c1115f986e7849e7a8f5f627f60be9670f490251d4c783f2fcf1648f47414f3d297bf145bdf6ccbe70d2c828cfae33c34c5fa8d9f2d4da1047a9f5d88452b75f343a22f645624eec6ea7dfec12c4569474ea3418e9a45ec1cb6292722a29fde2edfe6eed6d882cda5c238eabcd35ac0002f5918c153e6c2469379e89455c1a5ab7866626751453836ed6773c325ad21a8d8e111c49698c90ee12178b843f927e3a041a9cb5be50450330743b28e7783a7af24950e2dd8a45b0d329db525fbe10a7b5763bd9162d68e9c9cd98603e945ed8fd7170ba90b86421f96427759a9f309fec3a0d648d0ca48e0d26a5b6466b6a7066e992c32c68c6a62b894797d1e5d04f07e2d153d15241ba75728f2a8fceb557796807e2d1873d434f453aca69ed197a94a3aa0c51da97a9bc6ca903aca7729d7ea1f4b00aeb35bfda1776f5aa0cf23b10931ee5d50b973cebc39ed85b91df01d6a3be50cad78f8a74cd4329afa19cf552fa75edbb59ec7e5950d6e30a0aa40b1339bc423fb464290024b215ca30bfcb8fbf90b8f4a90489b771e9d5b779318245a63070881c5ed8756db1c53c0d164299108e72b8c5c22c4c4861af01a51f43c8ef49fcea2e3f55fdae50835e1a9c242891fd7fa42067433f82c886a8caa7f6851b989fce79192a93430dc605c8123954b9a47d42e9f4855dd810fbd8656c658edf0099d30669f8e884f6a365d180342d5a90b6a5c1b9c309a32b84d228fd5083d12db60935788532237ab3d04c7919a2f2ea5529f241be0d48e56d3f5ae4cf6d7cfbc0dd2913ea1a1287cdea1ff8100f7b39aea4c719648b4dd517b2c02de6110422d3069d5417e24ebb9d0f847c55e2d1bb676afc03ff3226232dc8dfa7e26c3c53f3020724896c8fc840cec8d4a53402030f332ef77b125faae4a735d56f8335f803881c6eb11f0d662687599812ea08a9979f99fcb2a031d93f7a21441b434c52f810c41a6c5c01630634b89245186abca6f7b08117b06661504e9f876b1f9789c9c4047989633457558a3498892113e667511600334c4b1bd28fd560b189f4e99a0dda0df284ab65e91bd7e92c4c51833353235bca98bcaca957537e991a2f4b23e64ce7e2102143ee10a7a793e7443e108a00c0b334621335b1c9e9a7733d98c8e1f643c59841cc8626493ea4cf24892cbb871199ba01a8e3f430410ef37b2a484df9705fe621f22624bf2df6136a30a6ed4788991c6e3f4d5f2505d29a7ca69a42a82278ec76565e2b087a1b503d528fd46fcb72e4db7e402ffecabb47d74c83d35b4864f0a6e9d574d0d3981a9c2ee9cbf8f5bb6bac68f279113e1a9d71e2b9c66a94d21485210233e4e5c88c918646436a503ba36dd182342d1a909645fbe982c4b9d8b5108bae0c200986e4db6273158489ac39f2d3372eb1c998d8e403393e42f0e1c363f4e160b551c00e3f0dc60777f8903526946b30d027e8391fe8216830d035a66eb54faff55e4ffba9f5d6fb693e0d4e0db67169359137210d06248d4caf9aacdbb94a6990d0983922023314e61af9eed2656af0c70fdb505193fc8d8b90976e5127dac2748b7e97c90be7cb207f8bc59ce935899833ddc81459f584aaa0f4873d2a2731474c51984bd4e0bc601a1c1202264be7a4a73deda27460be2a8a74eaf20bc17ceaf17310d78b616713da912e8250ae397cc5e173fcbafc362e5b0c05997acdf96ed10d1344a6df057387ec1b29ca424cd4ab12958c4491a5c37e6eb14d75bf2d264489e80f19f4106eb76878ffc71502f23357d37f08cdd57420ce5d6f2bfad9b274cb884fdf80bab5ca39e222f874b99d20d7d9edac7c7656768cb813f937478423ee447e8eebb42a8af4117c848fca57afdec625c7db848e88708dacbc2d0ceadb62d247296b3f9f066b7047960c7e217ecd43f8c223af79a8c136a406a703c0dbb834381d046ff3e26d431b9806a713f136a206a77fdee6799b9806a7bbb71935387d88b71d09f1b63342c0e4fe3053f3c117cbed455cc32acdf17d556664f4d3815c18ce8717261443fd1e5e789f8774cdb3a26e85c9c474cb5351af1ed33ceaab52e4b924e9dd8da7a2be29fd0e4f453f27fd5b4c8369405a9006d37e342f0b924f3f744fe24d869a29cc90aafb87d5ed3a5d6ba54d60692a25614912bf41d392c45351b93424a74b094b4a29599e4a52cf85258ec4b94befad26c0681b3eb7d22cd6e0cd8c342d9422f17848f0251f3857499cf5f128f1241feb4b5642a45a00126739eba3810447f28149bc251e3d2126d9d02246846e853408088b166a5a7eba9452762f207196c76e014b3e3af4d1a0b9f228516cae2609dea6c48b7395054dd716bb468832259aabd58449a5b99a1a6c22792acee5739e3d15e7f17109b0edcc6f48a725f9c25f241e5e968739898729252490e0c55e29f19278acd8ab29471aac1ebaf0557e457d52a9c19ebfae2cf216f3e45252b7ac926d9aae243e3dfb99aea8c443bb84c4c30cf61910eca3128f1acdd5f4241e3df22812cf88975cf4f7362d015a2368f322251dfa49345d5f86a64b83cdd5346a707a51130cd6ad3033f2e956f5a194d2cca85be11d7a170883c16c5882c15ed3ef50b78a60b02701895e2f1acc434cb4f43325a7f4d343cc35fd749a4493daa50446992aac6601f3d36358b5fc54439baa4f0dd260dd0a31914fb7ea950a94c32d4695681a255fe41c0431a44412b138a21a441c91830e40ace162044d43951a4c52c2c59431b4f802c98927704064c21a686039c2044068b8206ac1c5193b24f150c70db4c42b7af0218e1c52e0458c8bd774eaa55baaf82834383f8916262b1bbc800131268b1338b14501451731639cf18312d0d24f12160b766613035bd260d34542bd61ba90f8f40b9b2e257ec3879489064d170febe361790d9a0acaac61f4fa723d894fa7b1e952f2f120f97890b81227e15321896724065989f7a50ed52f8c627e8b19d1816b3c43833c0d522aa5f6cdf049989aab25b196d0eb55f3ac4825b99492414192c8fda12bf1e44b76d7b758b724de62dd524191b77ffafda634e8bdd0e0a41489a77201c916eb12a623f19e170a8d1c6e3196a77281e5493cc9c793c459def3da62dd4ae2f1e02e6480b8e255438f1e72c0f48a5d42d8699278b28b0d329843e8a5339178f1c5f2e24b7a1a6870b2bcf842423f18380a9b2e9ad4ab7961ddf269577c252971230d20242fde48438c1444a8408e13fca4f1821b7ee8e2d5f373c4450e673c61822b4dcc90868e1bd830e38727aebca6f7e022cba60fb7981a2f5b6cbaa450afa60f5db911659cd2cfa5a69934cfb40b174d9f683ad6ada02906cb6bfa34d347d345c5f8027b2975d274ddd7f4e9a5c1b44b13030683c1c2b44ba6595a82bd310419b0d7f409d442334bb7d4b42bbe846233c8a888680e31fd5c337d7efa168b2f5d091a9ed1152596f0d20333c0f46059430d9fa3352710428828460a30571869f146181c72e0c0e18730aa9001134bbca6534f8a0b0d4edf86a66f44d3b7581b398c4d5bac69ba3623e26afa123017fc9c1b143fbd69bae20bf6d3405b8c2e4d170758f082344d148105961d5ed369d374ad90450fca3405e1c68f0c5ed369d274aa349dab5b6c8bb5cbc8d2f44d084cb722fde99b191ec07e86db999fb8c8875bece766f433dc8e7ece57b821fdf44a93ba155e21aad4ad5083fd52b7c20eeaa06ec9a506d3744b760b06fc214dc2f9904a4a95a62be5b109bff023e9484b1e7e5a4aaeaff4109cc816ef745f74d612ac751c87bdda9b6912a8814e4c99d3a2bd20e37b3333324ece524b83411268892c53d50a2e1609e912d0c7403feca1a11eeaa31e6a1708404dd462221935621da68dfa4c2375520cd6b476a5a442d3c57a62882c3028811534587a692a4dbb4e8dba25d42e000cb5d3a130dd3a436368ea501a653ea460be5164a870e385d218fa909a0159f215d2a46fd7aea4b16e8553e9b974abdaf02d5551faa53fe99753e9de9c5329f71e9a4a2df46b1acd55fba90e494474d116c4557bd315c8557b3b5d816cb140bab6eb2185410e1f765dfbe635981873861a6ca7238753e97b2a754b4d3883c4338da669fe24bee63d7fbdea85f8eb0caf94c9a899e3a7f4c4f08638930234ffe3e3991414dd4091e5091b6c471a597e5d542ea0eaa8a38e3a5ed3759a873a0d374ca79f6a01d5a9d38f06eaf5fba2baffa25f9e2be9b29332bf3b572fa925fb87398ba32f6d30c1030e57dcf06a07a7abc7cb14368062091b8c41dae2d54127f082cb1a659c51e225821d04f1c311195000ad79b5b364770e5c3c3232a2e9e527c3b48928fce0305f5fe417f65ccfb1c9132eba329097d6a38c2e949f3cd4320f6d3e7e29c4953d7d666e04c378e685e99244c495751bca2da1dce2c4d20c62d3181816f2066fb794602972094cafc25c31d94ceacc4b342f3dac60882a50afb25c2d79cb4b2e2f6198ae8eb03b8f905e1ebd741a4487664c88e8e52b898c97f22553a9d71cfa5738895ebaa43f2f815efa646a977cc9352f612fbd12b52bbec0800153b5b42b4a2984c1e98a2f59372cb89934d3203518d11c7577d3331fcd34185d32d1222ac6cc51d114038648c8cb161a036a30522d47dd1dbbbbbbbd5a91df718506a3ebf834487f9ad67c9c430f66aa6930cea59e2a2d8fa4b7ce65de49294bef9c93a9286577dab4ec5e89ba281bd4457d594bc949e1acd05c5997351a0093966b8a0f6fc30a1989b14836c9e6b40011867cb0a305154e0f1e37794b9a2e0660d70b4b9aa6ab014a5c0e60014dd70346d8d22e08ac20345d1110e29200ca052db33ab04b3f52f71a05010225052ffaf9f9f9f9f93132d2604b9c9a6c6340ff09bfb0a401668ede3a18cbe10e336f8b1ab43bc4287100eb013e02047c8508ec18d24502ce7599808348641f539cd368b773e39c8683add174f188f5359aae9ba36b748daed135ba46466ffdfe4cd70ea5fb737feecffdb93ff7e7e7ad14eb5607b175c34444ead68d508336c90a4d178fa299f4f2f022c0c37c002bf4d699b43ca61d03f8f01a3c8aa6eb001f8fa246c0d7af2fbc79073d6e72b5c1998ff3e1a5bca6e6e7a48d7d0a6b7817be4cc614dbf970cdf3e1239fc123c6834bbb26f0c55e9d2e66c2d205719dee8adea67e781d11100f33290d3161c26281a07b2a9583f490c2c50fc575fae668ba70d35c59ef84582c10744fdd20bd85f2e1a51248c0e334ee632744f38408f130131043bcd8e0fd3ef076c0de7a0b3e6f5de5c506c3eea713e1c34b396cdae33562ceb6134704b8155cf3565821bf01019c7dec46709d1e61842b0fc04d6f9debecc78ee53acd7558f3581ebb1d162c3be0a404cd898794e2c2110faf4c506072a3c20a46dacd0d8fac75d549a148bbc959db01dbe123c5eb32ee8b291648a2e011f0050820b196b01ab0a40b1e12061996f24c054969a08a52f622fe828691784898f48161d7fa399bcd8b6fe784bbd5cec0a3752e5feba2f8f8ee0b45f0786a5d330ed7690bb00971beb3e1a668ba30d39ab71ede18bd75db39a18fca321e52fe64e93c9315673bd9e97463b7736383eaee3de5939f9cbb38778bf322b55ef40dbddee9c4692e9fd3ba9c73c631d33c73dbed64f79e344f75d2b44c3b69a7d377d24e278ece586b9f18e061a65ed9fecec34c9b8d17376072183992c182e9a3512ccdc7e7bc7eda65526a9a952925aa3b6d9e159e5741916e65ba76513bdc7bb3c3bebbfe363a882e2dc9a6a95bd7ef186fd1781bdab4f1d6b310b1b5c11accd2aeed27c7ba15c5ad672edd5ac1ad67a1e962805bcfd633986e05c0ad67a26ea1e0d673986e9de0d67351b79ab8f52ca65b26b8f56cd4ad216e3d9be99613b79e8fba5500b79ecf740b8a5bcf48dd52805bcf68ba85e3d67352b74470eb394db78cb8f5acd42d1e6e3dabe9d6086e3d2f752bc7ad67a66ef570ebb9a95b2cb79ed7740b895b1f6eddc6a75b4adcbacd4fb792b8759b2c3640dd5ae2d66db4748b04b76e13d4ad1f6edd664bb75a6edd26d6ad12dcba0d976e0171eb3642dd62e2d66dbc74cbe5d66d86ba15c4addb80e9d600dcba0d51b704e0d66dc2d814d988b131ea9610b76e63a65b29b8759ba36e11c0addb9c894d566edde606f10415dcba0d0e62eb895bcf41b1c90eb79e61dd02dd7af6e9d68e5bcf3fdd3ad2daae44d07a98b7bc96b7be0529bdf55352b712e0d64f69bae5825b3fc5a68b1b3a0575eb03b77edad2ad772648ac23d068a868e84434741a1a127aeb5dd274714b46d38585d0744bc7ad7748ddc2ce020af1929f60a9a6bca4f46177f4d6f152b7f27461a2d90d75cb006ebd03d3ad03b8f58ea85b2cb8f52e4cb7a4b8f5aea85b0870eb9d986eb5d0398b09c87230e539b57442234b4fc526a1ec3ccd7144c0f10fc8ecf5c72e868ec7083cfc03f2513d583dfc0332cbaa4839719e655996659914e74e0638c093feecd787121ffe0119bb187652fe01f9f8470bfc0184c9ca9fc407e23acdc56ea789ab027044156400ef4fe2c76ec7052b4e3ccbb29bcc9fc4d8edecb877b743c45b73bca3715eb839b7c5d738ce6f76efb65d6d43656210010367c3381c2782173f7622b84e779e13fa1e650547793b3c7878ec7646701b6fa7470f8fdd0ecba578ec7610e09cb7e3c3a5548949bfb914d4f685fd9b77ce79a12c7a0ecc735c447a8edba478d7420baed39b15f92db460008fddce015ca77d2871eceda4dc63b7b3c4416fe787732d5f793b409cdb61e2dc9113bcbb9d267ec48bffde4e100f80eb343700e712e0ddedb8e04e766e5e0e229177bc1d21ce09f1d8eda4e01c0f5c00280af03a588fcf5d48c00e180c0683c176c08838e703ef50e252f8a6c8f3bc05af13d3a07504785d5183d6a5785d9806adb3e075440d5a3f80d7813180d70d1d11b93922727344e4e688c8cd11919b2322cefdc00cf0ba15bc2e4b83d6a378dd0f130070eb374220b8f59ba1225ec48de0a00f717ede7ab805bd756e68ba76c0e6cafabd39a752ee20c862ed80bde586de3ab7345d3b9426169aae9ba2b943895bdaa1c42ded50e2967628714b3b94b8a51d4adcd20d167aebdb951b261e134dd7cdd15cc1ba75231441ebd6b79fe9ba196a2d69ba78c46e4e397873f41613dd0cbdf57b734a4b0281c3b8008ebb1d284ec48bb27bc271274ca338615fead68d5bc74cdd7ab975dcd4ad935be750f8c6068c2728ee0607719dbee97654cef9c03df0102f7fe01ff887c76e27a5e254d887c7ee7e4efaf307d8c693ff01965d0f4779f26df0ea773218fe09e976709c0b01731f5e79788d98635d050fb32191587fc22346c46d2ae56126c223f6f623c2a3082fc51ca510564694e9d58e134ffe7bf28f78f2579e7cd0938f51f23727de0d7192e73062141b1ae23abde344c09d5cc64a048d0660119c82a7390144c0428464348e60215e3c624414425ca73910f00b37d56ee7c606871c7ce3c59cfa914c1754dc7cf09ebb27fd1f3cd0089d0b3e709dd67154f7a41f67c9e190a03e1a197104f601789a0b00090e12440b0b07616991431095178de000789aa3708287d734f13a98d7f934683fbcf493939099420e95916e47fbb21c2178f259ddaab3223f04e744c04c3ccd5d4a301020b90908093b3e1a19f103e4086e799a974002fef1c3e7c7122323603f72f0124f731296e054ca86540945a8498d8095789a272901fbf081a6857db4e0f0d1c2d9ebce7d744ffaf3d77998e90b97518c8dccc22c4f73242edca347173d98dc8f646e00eb91048fe0699ec304f3e0816402e6e14513b28f648ed6f0709de64cc03b3cfa448ef88fee09fd1d5f078b39d68f787829074de847a322da78224e280958044f73231e6e6a8271703ebc64e345ceb813308e179f8827ff644696aed3dc0958f3b218d97e348aa979221ececd89c71431c73ac8238b71e15658f3788869904751ccb15eafc8b8c9431e456f7dd539e9c74b98a941eb26c8917d098733627383b604754ee21c4ebee79cdb3ccd35cdb76e475b610f6d1cdba03ad035075720f7a9a0701a8742a15cfab471f9d38643390a9583f36b3bdaf524909528e732ceb911f0e669ce69db46679c93fbb2f82baf1ff4fab1d71f82179f02c00be783f0859d0d6fbd88170325e22dbdf5cf0bbba2b7ee796167f4d6dd1be2f50bf1e27fe0f507f1e2abbcf840bcf8b18717bf7f0714cd37e9dcc9b7546c723a75ce79fd98f3ce65b7d36d7e551ba77956e6f3fce68536aeb24175270ebbe6d835d79edbb20acae69a6b7743a1b0c78e05cdb1ebf4e661bf19b2d2732a3649c51c1c1c9d566e5e4e9357d3501e761e9e3cdc9c73e9a950dd69cba4d60377de6907391a1511fb900718b47312e5f84d07775ed7fc7ea1945ebc9ea671dc178ae0b7eb996b1ffd78703d525aeaf005a7886638611adcbcd42ca3db903d218738611e45d4adae3a7c3b6a48e9db5160ba25bf5161be1d270c188cda21f7b05e2855d55e8a3de43c547598abf6d24bbdfadd503be44a838c2bd037ddb634b587156813dabc6c431b182d9db9fceea2f8f8ec0b4560b72c0df606f46d5a42d426f48d42e990dbc32dcbb787a8a16fdfb26c409b9676e5867d6ff9768aa325f60cc528db63b7f7bc2a0af8ed2d8768908bf227472314acf9688402352fc247231dd2fc4d7aff681454f4dccca44f6e62bf2a28d46386abe717d72c73eab81e207e3be9d84159bd94609476fb8159b858c42828f6361f8d82b23c277167d6ab7399a779cd2147e7248eaeaa4e5d551dfccc6dfda2809fe94c2f7329dd3af8f4a36bb8a270c8d56328ab8720033d1f5f7650eea5623f95f5ea5465bf28e05707bf7e6083311609e5d0cb09460cb18b3448acc0082213d8200b971258a1822e6de04046092aae2852b3b0987467403f4e5cb921290d2f4cfc840182074f98c00b2d5a9ca14676861bad5a82254c5000f2918c165bb4e0e1816068d0e808498632ac5be2f4c9d23d15140a83c1b0f868e6630745c2607fc5112b7eca2c8b17191d2155a121a22d5ee5bb9265ad48494a52faca182d4b1eb1d9adb5d60cdf9b69b5fa90a48f9e752745acd382896c0ecef842c59ce1023a7900f3edb4c374517714636c0eb208e37b4716519ce13854f44a51a7ce4326f1d334edb331327d8c696c98658c6b3cede981d3e2694f0b7ee62461017b20c415a53909f809c2037e7a04c3fa94043f5dd29f1ec37033906ccce83166769b51070f7925856e491556c842a847163ebccd473259c0d0483a9bce59bdaffe546acd497a98924adddbb3d2eed94f701d593863a6657b6da6b6518371ce8bd44773ce39abfd12c5a452da3a6f967977aee49cb251e0f86b29a753fa517c69c5b3d3542963adb54a4a294d7537f8b160f6d79eb5571ccf1aec0eab809d71c0b9c3911966ba94525a6b9b075c8c2594c1328b34d618e7cf8c69add44bdd3461b7dbe9c984098b0582dd03a0f48752a323a4a44c68a8bba8b3ac6b96654a46f2288946876920ae2cd06043e08adcdd32ce53a4e16f7ae028c69194a398d654663db670a5e1e421de0cd30ca76badf5cb33e3746bd669bbd2254f6f984de7ec76e8a4f35e2abf3b69537ab34a7d6a6165a419b5b6d2ccdacc567abbe532e644ea74465aadacb5ab6c154c195053e8cc1abdd4fc22d8aaa0d8fa55c1b3082ebaf28e974fa76d239c54e304411206270641f2210e52def121ce7bc76030d80faf7982b83243444412cd9820aee2eff81a3f9c20f40b32abc4100789474ea968d974c545c69ff5a67c66b1a595524c69cfeb34a59489a462f3b1ab36762a0bba108b5ef7f3b992b98da034a3d7d6fbdd23567a2d12579ae6deb3e9165cb76d3aa3adb5566bad4bbfd37677ada8cb4d3a27fdb20696e6fcc25a5f48fa486d5561aea267de0c0d46d78278f91a87e9f4a61fd2a792468cbb453bced3749da6b2e2eba2bd923e35ede5773735d9b1e9697e3dc5d4d8444a9a533127bab59987d6663d1fd2d983569a691aeca40671183166ced82d5cbcd09a65599665a9d4bd2864d99af6e92cad8566dd68d2a861f221613eba986be6cc47efceb22d718df49159a8ec8e34ab99cd325b977c741a64ce944d0f95d6196348e367a404b96b77cf2ffcc254df2cbba9eda66d69a5edd9a75a4a2995a19b055e69189d46b79e6b74ff2f080ece4fc7b552cf7f4b0dfb696d2991489c18e7a45efea6f58ba1c7cb507fce9e2fa8d6668c837132c6c1c138631c8c536666367af6e1dca0f530c6186908627266b3cc33cbfa6bd31eadac3293d2d66ce2b6dabd338bf5d35aa3f87ead699bf751ad6a56d3b2fa31cd4be974bae2110c06f3e1155b521a942e43cc913e6db2228a32a66f85e72f7d1cfb5501ff0bcd34bab744871cd210aac8b87ea12ad259269323c09129f6b075e9619659cfac53c93e9c06a9758a735be1d568e6797a997c51b75eef871b5c82ade70627cecc387a094839c45d1539c46f371ce73cbcb7fabd1f4ec655f0636b8b7c54156930fa1115141bd20f65c0a1def3b58b4b37f770f330a5dbe6746edbb67da1f661cfa8a535bae86bd28e437e9657de0fd3d487f4335ba374cdf9c5336836457eb4d65ab194d265a8b8521d709c27514239c619299d9f9c7376e1ba5cb7de025ed785d8182594a97352b69cb3ce8f31c7528f42d0c8d9cd19dfdccaf7e757c7ddbab9c6a01b93434ce91453c6cf868f5e5190716aa52eabb40ddf4a747e58ca0fa7c19e6d6b566fa532469b1b67ce8943299d4e854edcad5a67d3302a9df299d75abfb849a933334f86fab2bea4d3059d14e3c4534a4a29a5544ada30fc516a60aeda979462d39284c5a52897967e989ae4921b59f6eace7035a0a4a494a4032efacb4c8232294a59ab9452ca1865edf0db7962cad14309e4dd8a2f7c07a5d65b6fbdf58b111604173d19eac7482fa5f3de54ca87f8b044872ca991121c993670b75b32ce8c02e72ad2e90ddecf67ed56419329634c399125cd6a66b3cc566a468ca9318e0518b240f8424a69cb3aedd82476bb9c5e3799ddb2e79c937ac7e3834a2b75d9d309fdfe32a9ede003476393fa418c5ea32b82105d278f391fc312360f03c07918c7600f23169d87f1061fbdf8e827afc3e0f5c2509e385dee5cf44f023f7f7632e6549f14cb8e05af1d144ba3a4f494d5b9432516d9ae5966b4318bb15614a898b984b21a8c4eab9dd99c5fadd72dcd94b6aacbae421a8d317e617bf79d3326344454ad56b5feb90d94a3df8fce3953a939bf5073fa454dd3b448354dd39294969ae69c3395a2b118a3cb59358dd27b959672fda6fce8446d91cde563df2e93baa5ce238e1863ec21f821eb1bcba2798dbee5918cc1a65ca121a21fa0a01c97523a0d9b11e9c82815d440dd5c421a4e1edb87863532cd965884bf507241f3934a9faf6bb64431618a7c8540d6c9ec081d1d9d6e49d796b9d53c6e9e05a6b020969a79aeda897461cd55bbf5ba8044ba64e93a5dd433c82ee3ab697ba43dbbc418bf9ed6e56d0f73a594c65863ad2d1b6bf59942aba98e95b4886b189e8a0c1453b13edd0af57086cfbe10fcf87befbdf7b35281d88c91a3c7ee5a91d6fbba157a8e84d967857a68a54f9742ddfaf4ec7eec5a882ff9c5b80aed67a57e5da4f758399be0c2ed0b6fecbcc3e95628037d2ad8e3dfd377e574aa7e1bb4f8b3a279c89dfc7a3d9d4e1f96ec1df759d11c86cfbe2e28cfa84f8753d7755dd7755d773a9d4ea7d3e9e4359cbaaeebbaaeebaad3ee743a9d4ea753d7755dad5e6f6dd1902ebfbba5ec9eddb34a99654ea775dc6037cfe334d85a72c8faf6cd711a6c4781355d77aedabbdb639742019143563b4bd64a83d0a86599a69a4eb5ec88c69a5e9110a41e16f93088cba52ab314ec23cf22cad2290070c8451a948dd3411a0a2e4f17fd4218be37a79aadb5d65a6bdd3eceb50f06549e2e5b6badb5d69a655996655956c3193ec412e34a7a6c1d7cbcb0d450b32ccb28a58e039317752c3cd3adb5d65a6be987857af6ddb98ad65a5b6b751c98bcea87a5bae54a2821e4f9507ac7488274eac59824ecf9f08b318974d93d91df5ee3ab86882434079633696e40c79717cf97f63c57d17b640db7372f38ea5ea87de195dd8e148ee7c3c845dcd8797a5ed8b9eedb60fda8c8407ffbac84607e9711e0d849c1df951ed8eb0a4f0d754801060fde90638e3a5ed8b1f4bcf087a5f3f859096178fbe12ed83baf06092b8a01fdf8dc60c3eb4aace3f5f3e3853f1db063a9014bcf8b73cce485bd06fce5b99ace711ce7281f72fb7461a921470a3296a0010fb43802e9c5b94fcf8f8f8fcf0f9317f61ff085dde727f5c2fe735fd82b11ae3e30d80b7b10b017fe6ae8e982831be26022052bb0c1062f9f4b85101d5c5e3e3e38dcd71554130c967a5dc9d6c0602fce7d70005f57261db017e7383079711f962245e4408b9f2f597cd8e2f5a56fb28831262905539421e635b9f8d4c0e708336bc6387a4d1444b1050f6240d1850eaf944d818f7c135d3ab248d5ad25e5ee231933b025717c777777dfa08c3f9229a3c6b7cef8f679b5639c6e7d61952259b3954e9d14545841e654e63e922903c68720124958a42d7c7a90e50d159c404889962b5e008b030e2038b8c091a948420755dda1e22acbe4a0a84c99bff948a60c9126c4419975d0f1566697301f9deb80182058f3ed610864d02da554720a7b843431d14c16df552c7dbb93c88619b18c30df0e46115fc851860c221a2fdf5d7c116b00c53716597c7b175e7c1187956804b3e16598517062f085096098b043add9298432c68888450d90d40064e3db3f31862801ae01044b0d6c503024893805cc8697a171bebfd0036728eb14c220854d10ad2fbe65f78453f3eddd39b95ccced1482e0281be3db67e7c49ee0fb491d621215c06ab937f87e32b9503a29a5744e4ae9a4b206a7f0e713df0540157d37f9febe67ca2c0917503be43012f6f2c99411fa48e68a18928f64ac58fa0aa65bddcdaf82a9b5123558adc815a85b6105f312f69212f63aadd341d3c59a49d2a99ad824be7c8553493a3dd32efa6a2e625e7a0cdbcc4b8f619f79e9316c342fc356f392e9e7254543d3c8353edd59b4bcdcf23294a1540aa823a8840c7765490c32a5660680000008002315002020100c09c582e178344d04c5e20714800e7a984a74529989a44910c4384a19638c31c000000110019a196d132af7690120ee74a835c5af9be19f9ab647821b03d6d8c93d81b6b8d0a8cd4c6d06fbb4a2119cfa6c52f9e3813a1271ef9647d05724ca87f16151dbd7b7c4223a63696629984669c69f38f53d083e156d27c07cf1ba6b95d53be78e79bee2b2fbba80362f430d4e537a93c49ff6ce71cdf2536ddd8d394465f905d88135bc4899a30b62666d361b0df7d8b20c7d9d7e604d99835e21fef701cf4592a79c7a7550aa12ecaf725b5b1990148dbf1cd14390033ad09bfbfbd3845c951a6de8939708953ba52750998ae77c3363dbf1f2a8e9b66abc81b66fc3f89c35b100a504cdaf22490b2fd49351382b9d2a5367e802e0c204041202076b0d740aa0a2427b25af83b6616c3e0f5ed9fe3831f54627a60243204bbe87c0b96b56764e66d2f5bb34fa6f199040c28764f7f35fb2836d16bfe4d90d8aa8682b59a6fb6ec007d3ec2a81d310fd79fa72f3705c7873489e78e4feb410b23e32c4dd5fa10089d3aef07e9bfbdf0af5d2cfcb3d3156276e003b36d60ffc1790ec48f603a3027b4fc10dab4a76ce11c6196c33d258730f982c59f912161f982796517da77221cd58bdc4959def13f8238f2d17af2d758491137dc458c6b324471f094a206f0d69d77c890248013431e872a448c9fce8daa3f103a58925a134a39f51046fe4c9809b1834512982b978f672d4002d18ca576fb014df75328b1c05a223ec62997637942820fd612f9c846968ffe8b9970c908da5a001d724df66ee82f6ae9c349d888dba18850782f2765aa6b694decb1444d696d17ac1f1778b0463bc48c0dc70d48a7179c8452401994164ff82c706441e495ca8f72df575626788b30e498145e43d762eaa2b9d8e24a90176dd16053c79ebd0e64eda77f59cd6f8b7e415d8fd4411c73da76d92def7dd538f09a508dbd347eb7a36da4ba1283f9eca17db05bfda0ca83f5174ec83a1478df6911c362d5a111468a3630229dcca4d6439c8d08649723aaf82c8c5ef77bb7737ec56615eaff9d081144bbad34f46f1a510131f9dd6dd54ade4098d53914e03868f61e4e657a8df940c9dcc7aa1097d2e492afe783402047f3fcd7bb37120492503a0eede32aa9a231809318e137624638a56f4c3d8486df41840852b7ce6d135e143d6d03a4841c8debd347441dcc0c3a01c62f3f41a110ccac370cefc95c89580f1868c07aa9d59ea58e15b456db3e37bdf3929415e405f0a0e26ceda283044a413c53c2f6d8c33de84e25ba8a0b4658d3d7a0cab6f8d82b20983404bd71a76ce813686217ba15196a01c82b9a517a1d1ebc6151fff856ed13595e67117d2bc05afd12a8724f18b7ee874f61bf95741cbfc8f22bdeafed76f2d672521cf64932d487c223badf4e917dad47c5d0844b767d9f6a2628a55577938f18928038828a4a28609adb24ab8a320780015226a798eafb9a769d6bdd287cfac5e0cdbc431e8d116cacc8d3011759d48a46802d84c16fa4570ec5c55e47c61d600f1a7521b5288dd70aece09ef11532042cc9de3fe71af8403dc4381aa0e1c3ce178a9e760d3f9b5ae0f9c6d267593cd9c87ea02414d9c62d09f0b20a073c9cfbc6fe0529743d3f40ccb5421d78e80b49242f4d8cd1698f520e7066ed94a4dad0e44e998afba4542eca6586511c070b32ea39b8f82c55128fc020dfb36d0887fe60a796c88f39c0eaa8ae0124c4dd2255239b84d6922ea65ac4f0f1daa3554d73b4954cf6706f789c5d7aca6a3d2e3ad26432e829ff14176ffeb3913eac1784025a7033c49d027485b7c0e6a83f8a493286164037c23f3e35913c36db6ca084c8430692f9f950eb9930cf0e66096cdc1cdfd68d999b6dd11bf18191dbca081647486c6c41d74ef55c0086abe80e3cf7af17dcd01c8c48a04d2c77117dd4ae09358099789c9a319b24d816e4a55cc9de288879e492d11870b4679b7c71710953c23fbf628dce27a4d67f851ad6ab9273149f52f78f90f4892f9a72958fb1432fc4400e62f589f21e8547cdde573a5273921281a46f2fe2cc9e132445f6553f0daaef0508d4595feb40256447cfca5ca7f608ba108acb9ae03d39face9da4c84bf32fdefbc3fe47e63ba65ce96f839c3b25f55102f805d039a9389d3cf117061cb2bc9fe5678226b5f70a0183cf10f0eef96de4b81b74ed21f46d359b9acd31780b8c9a9f879abd7d2e67569a48a7f1c36443518dc02cc5e05920082a2a8df06c9cc5610abc080fe1dbcd110ac256824b67358ce09a130734c831bdb4ee9815b9b4881d8bb29b06666bdf9c5cb0a999354841eec7d1cdb09777560b07cfee03435c606041af95363db905b029c620a2e1e0720edabb218b9eb3773494605217ca8ec544ab0ed64a9d7d5fbf161f1ab0bad61ff1409b8686439e4d678a866dcf5c95e6520087ca106873465e682d1d45527122dd0c2ee1116b23e1e7b7d60c5a9a7605f5815fb1879a0e1dcbc61327008fb634d82c80a3fcedec5aa513384e66cbc0fdd1bfc804acf24c1852037e66fc080ea22481ad1708272fd01962ff051d6557fc3d2f1a6226d133410c5a903a03f6ec4ee542b27054b4bd2b041a98d81c09b6f0017b0651e328ba7333a32de00de561cfce4eb75e407699ce0aee926f6758bf491b3580ba16dc4bab64fdf641d2a18e68e3aea0d5068f1177c159fdc0ad58f2d0330f133a1793c1c100cd4b1589f172d2d1827b2144d26fa79e8a7a2d33de7702a11427efb04cc60d309743dc7882f1d26049d73fe9b57b39f5dfc9bd503804ce207e29948c464bc469853cdbb5d5aee8991657bee88bce4d3773b33d0faf8c576af72908e1e80b50dc1261c86bb731815f4eb1db39c57850f3f243ee525da8dd5804920ec4efdb35453b01db8b5477c61377a59dfdfc49e377c4590dd024190ac6ffe24727c677023f7f507990843ad5a936440c2874db33c4f90b084806ed1904896bb6b384795ff54fbb186767cb4073b5be25b6c9305902a8d8fc1f90e4a1f29782af8df46815621c486686b8caf86791bf76b036fc484ad0f3f3fff31b9303775e2b6465d9471084a295fe4058d7fd0937fb02ac775e58f28b44fb2af86670bc6447a392d6527492b83ca34affffac9f49a4949c673354605f5beab7b13fb30cae04e4d4c67f80bd0b8b6396d03cb4243cad90a5d76ec6ed8f3e0401fd1bb16140c7bff5d1843047e0d0fb727c8b15f48b768f356abd6767d3a958148198ed40917290359595d8ae6ad1b73eb444fd36f92156f7735f29bb337efae7103b209e690fad4c51ae294ac66032d014498ec7fba5684219b6e4b1df0bb6db8785dbbedec9b3193c8d12baeae4f06e720dae026e61b6d5b68cf282021b50f8685e0a3228f2e6a63c0ad0cbc7bcd705704af91c88393246aa81896955f8e431bf45b2aadd7d40dc188d942c1e7734dcaebcc34e922b1d13edd3d674bcb6f27db6efafbcb088f9b374f329702a38de9a5d68379e9b2c1dd964e7f932604acf72de1cc1bd0b8d5c29d6b7853cd380bf6d4727cf94643f9a44bbd2137b747ca42b71c255ce0f2f483f4506d4c76a5539b905dc8fde9cc5a3a2d40c3e4418013bc935f36a24a50ea45c626d2ee3c399986d10420eae5a5de6c92af31f2366242c9d8e2fb1c600912477390722dd556183be27327492b5befd4744d5a0d8bb5a9aa3466e8c32a418f6aae1bdcbb01a8caae310743c2773badb0ce818808af43da320c1fe1aab66304dd0e2298cc2c43a0070ccb04eb265db263a195f18af9dc2f87411972c4de59d871db004d6c234e0674683a7592ec67c564b04909f6c4bae702dfa7d656ed4ace9b3a0795785f9c45a311c9853020b82d909bdca360d4541f634fe55ec01e0282c11f971c4ff69603370f8f84c2118adbde8ca943595eb69ea300d1388bfd485979623a3846189d8f41447190e5007f86d2f3f9cd9ca7da0ceef708def0bcd75763ae857b560934d3aae1a3d5e5d25546eb4691431961e61fbc670727cc04db429a1cf320e8a2ed92f6caefe2e3b4c8a09be85dd7720d472cb8f935f6b3ccb1e1665efd1333c635a1c56ea545513348ceb295bdeb66288dfccb26c4d714266d9fc883bdf888f22d8bea17c3a5c74106d8a413645827e313463ddc29742216f2d2276dd41eeb6dde40b38353d8ff78216e11acd9c3e290d54bc13034a5e39c9c08fa7669f369b4e39c80d985bc02770bea5f1167ef7da95cc0c175c49a1b6a912c3df508f7ab7c931a4df52570b7bb2dd3abb79b3bcc3a44709459e9cf8fe2129ffc21cfa19216b1a33deb94be114bc67f34011ca99dadcb0c1cf411f10b647003557b382ee99ce1f3b2cc19fba595a24f33bca5e12bdb78bb361f1e1a9607b6acf2d71a1f10b6205211b9724ae51a1b07889606b18e4dd312f3cf6b58c26482286915bfc9041c6f94d7ccfc019134a0644c5c6df12a7728e8c85c24df6aa038f9efa566201f891d0554c78963d3ae0e674145f47d19ca1f9f42ad0ebd529873fd38b240b295400ed2c49b4d22d3a589cf8fbb841aa7caeb0b9c0fd74e30f398d15205d31deaf11f0fbc62fdd326d22da8837c0d8eda902ab2c84b8b75f8bbe9cfca0d5766d534c422158d35a2ebbe16fc49f690289f467a478aba93258b5354d5754b4dbc9b54bee2034b6a0e9f2b24f1ca016d018403415c8c6bb5ba0dcab708fa588a86247e63545fac311529fdd6dbdf769a6fef1426ea94b7d9d8e153c3f0371358b364b4141a7dec54303badd439d3112cfb9bc067adabff78974b4cd78a6176f42415b338c1e7a8513bded7ad95fdd612352dcf06e8b54dabe7fbcab8c1801728c6641f132f1a3f102c936d80b2c488d45b8212a2d20f2f74e3672251b0fbc9d464edf4452692aaa47b1d227ad14823d7a936feb014b9cbc3ef6225467039b4b82f52895ab09734393003a266bf137eb7989c83091f4d9978cc9228fdc959dbee22e3ae038280c960b20887067d3bb6d6dda33ba56e455ccceb4fbf62dd7b0f7bd3bdf5c2739e94e79754b0cb8b68282a30b2ed11b60fd1cb00f2f92b4001a96f170498089cf61e090abc3313325253c3bbd7a71a672d2ddf28493d2d923cea6018c5fb31e756852e285f0e99249ad138e66d23fe102b0296b46eb315a4c5bb3ed4defddcbfcd1b1b2716f525141fe4e405aff7ce31dc60a3aa489bbdb337ae54bc2657b6ca74134cde0277efd6a5278e26cf53fa0227f05840b48070eb8ada4c6e6bb2f0ad5c5f04a8689df889b81517f35668b9fe339bc0bd7db90e8a4b7de8f832d79506fb98a1499b80bbc3613f444920ee2b5125147f0418d2c6c81e5d58322726b39aeba30a3eebe2b13e5b7b741b84926db838402f68998e06e91de091fb037cd66cca79fdf28e70d186b7b04204559a065ea74ac227565cac8d94519649a1f3e794719ec2e5a35687f2c3c28d1b825e9fcbe7c7476b61da07c3c08bc735f4ca199f3f88327bc53a1276eb8ab9902a8cfd166c2e650872b619b70f14b87aaf13efb09c8300127e92178fe41f54a94c24b61fd4ba8aa6889022575d117853126fb0c0918e7223a677652a7b3822159f45924b4209b5d7e52e8448e8bbfafc3485d50e2b4faaafd4d7a44920ac55474bd2043c9dacbbf817dac2ccc008c8c9ca9a07b1ef57afc3a19678d2ac74572e6fc46a5d6c769a3f36a7af78a039d0020ab4e63a0b137d7b3099fec9135247d4424e316afef94f40fc6c331d0a3cafe121c50034d2584028d24dea06e7ab8f4f9fba1f855548b6838f11080832aa540311b90ee97a3204eae1f6c8edae815ef312091a8e3126d88e6bc7d449102a6ece9283800d8310dc789d99282b9e63df4167371d23d27c0089de42a3a398bb44442277aef469420d1fa4b22912247edca2994a0efc8fe4a285e61daa0088a24855ffd75cd38a46e265490d3e2a1fad8fd597a291b8d3dafa7c30f72ef6bdc9e6d64beae386a713bb5f8686dfb9956c5052f04b17990b70ade29c60d88da320915c5d2cb0879765258b5ec304f84cfadd3892da0e8eac99997163964d7c55e9b83a793415638b7391002441b7baaf3a5fa59e188b72856f481e73fb400e50c73e8b5abbcf984932cd4a2bbaed26b24465f9020c3188274fe2afdea5c6dcbd5d351c1e7ccab740e963a3e51e9515b0b55cedb81308ddd46c26245a978a9fc3912f02632526c022e3e4ffcd1ece6fd6d4b1d5207e11fb73b3e428ed7fcdcaba102ed7322f214d6a83da6ca50089546ac1f5effd806b79c542529c93de198298bff596fe3a722bc435f950f3ebcc110e561dadc97ed9a486b18a138d38a848d65256f5fba945b63456b706634a76e9286006829eb4cb11067d0826686b3fab5795d27b102f3faee22c1d169c172dd2f5f85cd75f8041f6a92555941ed226d38be3864297320f9239f77d31fccba73d0c27b870e1660fbebb85c7e47b66cde7a5a5a66937b8bd671c7f362e88973cf62596031a5ff30677202ca1bd00038b97ae1b77d15d22c1851257937c256f41c87f633806c2b9153df6464320987ae2786055320d224f5855741aa3c4cacbc6eb8c15b0a108dd3848ebec19197d3e576c6c78238fa0a3919493f9246ae7e54c43c81024211884c098625ada13addbbb381792a8352c1b4073c8cf0492e1cefaf136999ff7dbfd3d58a6c1ae3bd83c0d2aea164cba0f6ea3161dc97283fde0123967f3f8205d727b2d908b148a3f043b0842b3aab7250c4c90f33726da76180c10114e1030efba4fd58a10adb92e68265e9c13ed1f5e842af22b3ee2efc4f5e0a990f260248169b70a2b99c4d883c8e7c122177a3b85bfb4e7a703f68eb5c77230b38207b826bb9e9bb11047088db8e2d588ebf093c51c82d82783d008c5cb11dc309e7f6e394f3bc98ec192e394f23769ad60e1bf1370dc0787c3692d9080b63ae4bdc36350b64c6738b5a6d53278a928a619cdc9aa8a2e2a465b8dedc2605188a616a6596a537c02c31486348889869f90000e0457bb66c2715fefe69a057681708f4060296fcae9de18c9fd10b1cfd2dc5d16e8fbf3a98718e51070136d283a847aeea6ca7a30c70772cab0407062f1b6480ced76d58eeed07d038c1d72a4b57eea93ec11e47d1b78c46796cfdae727a688e9808f1d32fa10b93934f0e18c54c874956d0f9985b05493069e8145377567e429db321a933cda4df80b80e6c66bf72ff21c3b57da471004e49866c5a195ba8336a239919616f9259dbefeee94a16ffc078b5c6e37764d6c2cd659bc67e2568f36190585bedbc4170acdd50ae05d5a44a0be9dd801b8a2854960839e67da4c8e665ec69e17f9ed54b5b762d03881bb25ee483ff5e0723436a516a65092a18c928b26267e5c86f91807bbb7cf13bdb92b3e47b2ffb424da660644770e6527ed7386752328b29aca840016a929bcc1e2b1b2675ba001627f61863b99a7452d3f83383143807c85375026b93e52ca050c3bdc151f944ff874c400aeba31c43cfc851fe1f3c0d72fb65409b5b95e4619d9fc95fe1c17191fa86411b38b1b246465435499f36d67b415674aebd38d6d41554662ea6fcd88d9f96838df0dc68208af894c8117f46607ca25aa07ecb9be2f208f2e21a3bcad2fbe0fcc11694f89c8c0393722db074b8ec0db63c6719f6f2ab788b2a82288492742ceb883d4f1d425ccfd61d9917d6b3b62d3aab62fb78cd96c8b1a6d2e5a9bf4059196e4941186b7b4674e335139b95c6497ffd885d7eb52b7596d44088a86a066a013b958108a251b98bd636ad100a0cf04540b398d8731a24d2a7629b35e91deb6bd09127214d47806d1695221db08583c55abb7dbeb84a8804cc1b8c606884fba5dcb783e9461e6c49581a24d263070e4ee29c2129da460fe876614748aa9963193cc04c29551825253fcb710b8079c802431eda07ec559a4b9071a5362838c0bc7c2c350430322c4877dfab0a2a1b0462684b4040f5ea1d3fa1aab374927ca5b74d8e9a287ec700d7e2f3a610fc2b83b3dc3e8a090390b25de9ee9633b16c146b11b1129a20801fc78892f2462b3809166c4b38470b320450b7fd7b178e5c83da2d6ac8cb84b34fc2b3c40c858f842b2bb9efdbc8ca2c250b290b5c222a3d47f056d8297f44e1949205685c238c6a984240c7d18b2a8731bc0200a0f46ecf4fba91f499cfff2fd83a82ed2d83b66148e2bb2cbbaf0bbebecf15f85810af2c53f9a37265d60cb11984132b8c9352925f24bf51e7134eb1d39df76dc10fd0c661502cbe42e90f56ec22d68f796a0771caf230a306f1d27712647d0478b156ce46509354663d4cca6aa4659acae27b2a2a78e45de4ca732fd7177f4749dd3e45e2d435dc29100357ed8a07db50a7d8501441b8808558dd29165ca95c8ef7805a8951241f0d5a898f4c6400093ba8e34e5724f515d58aa83a21a8019dac09c94f24254b483ae71445a8e6f8f886cf04383ab3058419217eebc495fc21f5e101f994f512b7c36cdf91b3ee4e7f7877efc5bb3ba610172f419344390f850d87eceab5e14b67bac4542619b30084198d724e589d2d12685aff460a7d3dd08232bf227544aa536021f0bef6e8cb584c1b0b0e35f45f3ff117fe5d00e6ad1cb487f0fe289615e262bb74eb64d1d2f6081e15e4e6b875c2bf8af94f2a68a20e238959fe052eaf8ef5eb98bf607010abcc757b32ef3811fde6fe207f918e88ef397af786ed769b0cb24e94eff0a0e73c97a3540a886b9ea896ec190f5e2f605db1615408ed911b6ad7353919cb3dd99838d4ef22bc4ae30d3aa5bf6bf0a9ad2219fe8ab3b085f29ea23696e27d42f92102007554ac752de923a302822e932b915960e9a6c1403bae166596e724aafedcce84fcfa06ad1f94da719815fab4cc516ad7131fc75508f403ef0d78cb8875c640c433180d6cb43ec011e0fe5bbfcc87ddc6763c067c5664dc773478ab976247eb2a3e0d55bd1da4c1a424927b860f93ff5e66aa4e194bb65c676418e2929280aed8b4a811c8cd1090ebdb6012fc406c0486683434dde646660f5e4b20852c54ba9e7a3dff1e2c499c05b8201219a0dc2e104a63b998a87abbae24b6b76da59a4da5fb2c1c6f62f585f67bf6810f54b245c4db5ab912a494e95205414451bbd5f3f901fe9887529f126618173b04b962227fca7163a09b5401c545eed4b408ae5af042ef8f0c083447c4d7475bfef383a558c212aaeb2a6e816653236bef7f6516e22fbf9a788b89e979544e0ef1b42ddb773c1819e592ba7c06297e22de4a9d9c03fc5d8f52924175fd752b0b726e4d4dc063d371c879759c35e66cdcab8dd38dbc61f83bdadd49a6b563087f38c79260684e5966453fec96d84782fba5829a4b3be0e69ea5b0a1a282be5042268e923b6963d03d6f70bd04e8c08493d7ef03762997adea07a1680f4155c2870ef58600a1c314841f28ba9590f345a15c0b0de396b9630a6426d65ba63920d8d9b774bba71b7445d987d3ab2369e767e8d3a9eb013568358e403834065abd2956de49a18ff0d42200144aef7ca9f59f3bf84eaf7c20f3818d1faac373c99bc9588946df384595109522a2f14b61b65ce12063062b5fa1789237fb10ba4212d0c365ecc227a7c744715d77073ba8438938f6209421a80a1e72580f93fdf6ea55b8ce91111665c65c3f20cb3df35c29511508592360e0cdfba0de6d40967a6e92931841eb65ffcd53e471042251fdba03222f77fbe46030db11434e287b4483a1fa1aff58bc46d79381d59964fa48120720f453eedb3ff7df18aa8ced48fc7087a092f0481df97093a637e978b3eef81d165cbbdd4fbb6aaf48ce45d6f9b2402349d66084ad9063b7372c6f33819c51329f55a13a4b1898776a697d090a4ce4b37e6a62500ddd2c0b103aaf48cf4a15675319f91fe748fda8bb10456278b0d4f6b03ff351b31b6bfbf68adda1ad090057744acc58c2cded3a9c86ca3e4300f590c6f4f373e047278e3c8a2fd195d51a4d6884af682ee289ab2a37dd5df5ae758ca418fdc10d78891798fcf982c75ca8400cc49175bf32eb95e354667a70ae588a4bce6f06c711a01f9495d6319e3e4ecfeb94cdd88b228ad70b79260ac971a6eb571e5c6061fac598090a9b5af2952ce8dc21f7403c10487045c33a55fde73fcdb5a55567addc5281fda44d9b2d819fdffe692cc576a17f83309376d4a79a60499e7771d28588f90b70ce9e2ccceab27110385707fd3a637dab289b593a8ba147480ad7d8ac0664db15f4d68e99d26457fbb31e512ca84a70cf9322c7f0026e12bc3b43e7c36440c22f5bc895153bc6aae9ea360465885af52bbdf92a3c83986867f8e66537a2240bfaf665fae04ef1cc49b70660f5197290dde1ed33255caceb0194905c829566907cd21e6360bf2d681ef45fadc7b4535f65ea3c563b4a726b76e911a87e4576a8a21e2d562b660d6a4e7ed6e5d11238f8e050148df5b4b0760967f3a8911e69183213b87b82eddfd97c14855d8cac0e186a36f83461f6ec09e7d556966e54344b679f92cd8d64e304989e3739ddd13a36468e0cb1493cbb8aabebeef8d070420d5c318542210b35abfce11a119d6add007a778fc998c7cd06ea0e1098ad7cc0ecfb79df8d1e1fc356f17d433e31a886f809f7258a779dda564be03fd9c0f5480dbe3e43719a31ee4bb2f7a775a4fdef485b7e54524d539dada14fffbea9cae32e0237ef987185b6afdc027be89729da8034a4132f31201883360526fb645c68e1ad04ed15da738889832bc4983da197ff7b1e351b503fa61a319eeb348d8bce23478dacd82a1c78673412517de7c6dbecea802430c52fac4ba3a6ccbb755b3258788da50cf139c22abc413448abcaf0977bba9ba3649fa0bd5ac8beb76853dc30b5197a0b5b1943757f8514095eaad7b1ea65af98b4c93c039a69bf44c801ca72d45a1563dc0edffaa956abf126b25432002574334272e4332e637b2cf9e2b150fec429639ee52328de55b6a206b85eeef673ee497e71972b5370326a166a0792d3c7c294a801a8533f89cc5ed0bc9401465c0790c6d9ed358398fec184242339a9d2e6b5f5c15e86bca0c4a482bf894a174d8445abbbd14bd62c1b3eef0ca51a0001536a5c9a596181810c39c4ee0a2e1236d1ed57b809d02370a904fed10bf7dcb63a4dd5701433c9a6ba7ddce5386087feb1931833c23b1fe5146c7a9d1be6ddca03593be8ade5e7747395f8063996feb66cf369e3a1431f77b2442ce82ca6d6edebe0173f67109c73e044118b93e77faa95fa89bee0d16e5000ec26e65e255c454a107e44f950943947aa455d2d48c6fd1e17df870eba241ffca0559e4497d82448b31783aed47b8f5f5581ea45ec75f4cc80905230bcec9b973f3081a5f60e815b328c9327163c380183eb2ed921827e11deac5588655ca22307207a2817cae4bbfdb24f4dbce02a2374e641bcdcc00a2a0aa3521b170a582f510bfa3a949b265ea67c321b276a4e71cc33c5f24e6797ee665fab51e6783a0de5fbba61984bae89a57931abb558b990daaa48d8e5e1f5f7f22fc3ca5492551b3361b31cce1e931e61d9cd755402ae62b5618308b343ad2ee81e305a87d1f255c1fb502d2222f2920853cc54530de502e33a084cb4dc66f4eac7f7eeeeff81efa57c196aa6c514c5d9bb10034e09fa9e4853292ff718724c7c7e43bce5116e0c7d27be3640407e9152a5790ce9dfa90a7de80b26851bae693c91bda9a93189953167543b802d3aead4f771b83d72e902ebb117c04570a47f07033b1726d92cb7625b017513e32312d03e9cf2d211a0e8c0e1e5169f92303c1191cd9872b698ab010229f3f944a97de7f66ff3ebe00da58c5152866d2e46730136fc9896399ae3b48a4d00bdc2f289dcf070d42987658ba4b34b1eb2dbb474aef0098a8a98a7d5e35d0aefa1a6033406627201c0112bc1d49562953de61e06bd6b4b6312e00d6b831863ced0035b6170e081c4c5bcfd76059d76f9a61945aba429ec60cb8101bddaf2b301e449ef452e43e15e2a5ed93667027cf683be5d181811ab9bdf8a20e55203a1ca404de2ac8596d845100c2db1d07ddb955e8c5be5acccfb8bdb0ae6fa73d3669ee7a4877e88cb2bad19446d7c1623574cdde4281cc35fb24a1560558e6009c17a60cb1cddb2c70a167021b8e45a9ec57e8c93a8a7116f58f02f7e4c7c02e8dcd1d7f1bcff997943ea2ab5ec447747ac271632bc1ef16e84797bde38f6ad08faabba60d61057fcf4273ae7dec10457cc47c40eb07e62d0176efce8822914b654f43691300ed12d42d1339d59eb9bf3e0dd620db388482cba7469fc02f016eca6e5d294d4cb5304499f2340ccad60509ceda35aafc66ec2466fa4ff3ff66ef2144b1e0119d73e8e2f18e6dbd74f4eb54548ddc00e545ff20e34204319bfbdd8a497a1925d7f2da10f6da8cfb63d7919f364d24ba5ae6bfbed356ed3eba081f815e57a0bde9991d8df466839b8dfada5822d1102b7c6e17f08f57a677db14894ef3585d8aeb0a2201647228eae687d7240a891faaad596aab5cc4ba1f352e9022e87d17f14001f0c8dd9bde3957d451192323bfe110733e3fd4bc59364817dbd23480eab0ff2d5d485cacb2535e4e110f96134e1136a54df0196c866413ebcf544ae065a4eeb2e1cffcff5f7d18794a2cf4d2c3de22458f3461a9af3dd6e0b4a0da85af499caa9812d45570ec307e6d3c4b0b4b82ae35a28a42a30dfa7342c3c5a6e8770c328f296ee085394925b968d1441c6b8489652921fae941037a5ce843ad351f25db8512ff2d0939696722f35906392af997dec2e67df027e0c9c20189021de533a2d5a65abb80c0ae86e8dc6c258f34030150c273dc4e28a986e93e9907934a1006abfe0409f930de4fa480535403504adc14fdf08989aa0a65ed7e464978f15a3292d7a0a4e3dc392cebd7a83214511a3921916e8ca29c754c01d174ddc5d24faf236007527b1a1657e5aed19a6030ee42f936dcaa54e78652df7256a5f0503a01a6e8306872aa8f4adc8048a2f8c1feb25eea77a29feb6f3d036320711d470bcfc0b522265c2b0e27f4912e53b357ccec905feb266c1478dc7467d85ac233312a917e9586108ae7db9686e60b1179547191e17e9e5b2565e96c3613dcef67b18cbeb734506436c52761ea586a816306de4895c1d72d1fd6a08d31ea50fcb05d4d28924d66beac6727ee8fd5b69fbba12294e87e82296224919e2c2aab48364bc4458e9c32a2a2baa318e0c1fe909dd33f074aae47d55b5820f42a408c6da1d08ecb5929b634d9cb19a3cd4663c1870a56cf4efb86f764e753ee9c97eea38db806b6996aeb8bc375bf8f9dfffdb55b041ea254ca94b771cdbb88ad9b23c3604d0e67c455a9c7b9e6c856a0d71290f008736b428e5ec90bd2efe8aadd5dc035bb49b6166197c8a81f66e66834f1d45cf757d626ab116b075442472c1cd1e4985e4d7b474e5f39a87b2bbf2fa99344f562adc9ea126d0b6b53ee6c92d6a214e33fe483d7a772190e4fcfed5de7058fcbb778b536f2c1dc7150ce7380fc590284f20f5c0529489552d0636e210cf7024bd0563101f9b8e43ca31c1654b53ab0123384d0647302ed108bdc14988a5805d513c6cd2be02b9949fac87064655e6e2d5a907a8b4cf5a013e69e4a324e12d275c96dc8fff666d5ccea35e6519b0790a64dfe7ff07395172b724a04bdb6b1b75f85c3229bf28b7a69044ba08493158131f42b96f2c0c3c63c6218ba52d0408aad955fba0a05afb0ae5140735002050ce28b3a830c2634a3b819e5bcd3d699f13ccec6118f0be5f4a8e04462a46cc1a4b2d284b4bc3b3dc2957e7eacc9ece4328060750b818fb0ee09072a94114731f3c5238a2b96fb5f9c4f78b9139cb21f89216fb5ae556f19d3b32818c3b46f561cc932e75fb30aa314245de5a8cf51bf2ede6f10bc6c0a165717337904dca8fe5d38e865491b2ebe4987735879711d929b83afbfef9154954840364bfc88eb7cdee9da3fe618825e2038af73bcaa3c6d6531842d0508a1e8317ff7003b6a07eedfcc014c50778c0997d06795dfb2a3d19eae0d34c2ebabb707b47722b43a9d15e164efda5e19eb20b441275b9fa5c79b3774419f4f7051a0baf8fada50e224baf6219dd2784276cefced07983838c30b31b2215de615c2b1e41dd8a575f0c4587f4addec0c1127775159303403c2e328415494055a38c860c25bb49f5173924a229a37facd6a813b9b271a96f0388a773654e8822471e4846d02571e10070cc9bf1a3aa32c8f92d318ade89688b0d380002bdbc7dff1dd7dd61c547493388e215a51076233b6946cd235b8ad40d0da77adc288332cac6d8f7816d5f1b00782efed00e973710fcac08ee99b0f90e10e7bd55cf02574538947be99f0d4d084a72d00ad1c99237c59155851d90dc62cd82daedfb261354334610d546f9ab1e83370900f6e8054241f6026d47224372dc60e544e0ef81ad94afef1ab86712d0cf524141a38c109500657b1dfdfc4a2b1c424409870b58817bd7f850cdd68948144142ea037cd37b080aa474d5fe2c268f5bfdeb580335c87725a367ecc5610192c88fd42e36aa56ee091ed6305c6c5dfd1848b0a52a2a051d644e706f644880d3df89e265e080a4f8cf3dbf64ea4790f84238ca73581e8ee206fdb6ee8361d44590a76e67d10cafb6cb0807df95b27d7ed8bd79b23ab56ad5c0d5196a45eaa5487d20e59fe3895afc05f8c78be87c40a68022b5c155f4643d51f67a54095370d12e5522deb08b426d900e91c9b408a529017411c6ed3d62f2d26614ea2077e0584f6e761a92d8ef3b56eac0850686d83912d5d60c4bd0bcdbbbd425656419060c4106a0fc145ac74071bcbde85f0a6ba08fe161abf6906a06c6afc1614e06bf952302b9457077dc9c1033e76780266d3f5788b805404277e585588ea20cec846a99c9fb20fb82329dcdee0c57778cd3b3cebee5f407762682c5c867eb9885b4a0ad441eb48043feb3cc1c04bfde32ec0096525306cef688fc37511c1b16a8bd1df78a8da68467913779aa0f8d9ee0452b06dcb62c8d25e7624262f9b40bdbb4c04e6ed5730a526c060b881dca532088dfba3b43aa12216193cb9afd58a964ab646ecee9998342420ff8886d893c02f648560fe6758e57ee6cf9024117d859ba3e9d7766bbdcc4385347ec0d607d3623a15a1d1739454b4469349daa11b993f8f922f12e3b1b70d14f6e0f974896d5a83aa00e5b8cf48eb6b7b3a961ce71768c711558aa0152e7a405925d625b44146582c2615a739a3546fbeb3128b034ace74127901b2eb69f335a9ae21eea5d660e89c105bc43e172c5b4b741178fde63fce44a78004c29e106fba7dee054fcc8d556e3809227e381633928d1ffb12ceee68c4e0aa403618bd2ac25932dd1c9921ddc6dc16211c558560beede0fb900ab122c2bf65067b4a19923858b3e6c32f64f70c402b2c421eb60b5c02a048114b4005dc28278fdf5db38ab2066debd4ffafcfe59cccdd24f9d5d59ef0e17efd7de66afc62c070c2d2aab84df66311f1a01fddd47a27d9b043fd86bba4ab8787ac6d8dca13399c36f36aa8b7f551d9cd752b81e8df5ca42c42c7becc0198dd97f71868859c7b1fd4a908e3f95c43bc24358077cfca76ba724dafb765f8ba350ad872d6bb81f3712d70324b51a92ca7457f9185aabd35d0c9cd0561516972afc7317c34cbe865eb011dd81c002e8aa4bb6701e4e8888c9d4e1b303d03703874f5406fe1f924e8dbf39dc1de98329f8450e1fa552671b9dbb8e216139f4c3502881dcf352eaf38b014f458c8a7dedf05f9e371bc0d9a280863023d8234805e211df73e70faf1faa6aaa081a7783f7a782fb4e8a55cd7f6951f17ea456471a15a934e765896e0d1707d35c84747c094129ee2109c420e2288c90d19052b4090147462bc845219f9e8194f9da2820cc51f6ca9218ea9a2026b2538b0803c16665f48a8c23c55ba8a43178e2f22deacaf6334199caa8f72c83ead47f7bd9c85efb2873db88e1bec855ac3b1dcccb8fddec90091ee8464cda2dc392c352751290fa830b5a033bd6da14dc894227889591bcaca45ffeef989f5657d1b76721a7372328105b9eec537ab5be8a6d11ea66ac8eb20786e3ee34bf530a33e0152662bdeb1264c5b967d8e05117752797f1f1f5a02518d074a53fd726dedd6346bf8a502d8abe64a2c34c752884cf203c3769b136b356e831530923e9f668879b8dc91887fa614ec4eb15f0eb578739a762da68e17a9bd58c663fdad82b5452566a821aca8d4d64f82ddd98cb62a14068558c7e4426eb973d728d8e89300313bd3b57f87eda94bc7ade937c7f8aa1882927f73fc1d4f1edcca1ea3970903c46162114ce772c2ee4f51c81311d8018138869af3313bf3f9defb11dee59d993dfdb4d2c42f029170425ce101a53c9730c98d83bcb37f261b48e836462fde36a6fd40901db6aafed91c9eed1e347294b80e92a19356397cf56417f2065a9451b4d87d5b1eaf0bf33dd36bfa828ea5a8bcf4d8c6fcec7bfdb2e7c2722ba57cb1f536bcf46c9548ee4621886cab0327148ce22336ad7b9389558c47d16b479128687cc1f1449192a516625c2ba2f884a58a296cdb252a24c7acf62665dc22d5b5d2e10f1f76a7fcfb697c7dcd02012d2b526002a1af26fcb972e70e51103719830fa9e6e41832cfc1d3461dd9d7a720dfee77905448d4d0ce45fe70bbaae09334f855e859c7f5c5b07bc75ea2c12ce11c7f8de0c8729e99fb813d0f7aa98402f23921a31f06e8890f03ec13fa6f09f85e53b0076130abf08f5796fc6468f60ec343ea9044c99f023aeed78e1be38d9d8cbd2286dbe6ce7e59e0f6bcea60af2d3888af0ebc5306adad12b868af7208917e1a2110e891a3fa9d80957423f2845440b908d4bf9db3a49b7d60d78092c5bcddd88d3e2ba58cbd1993fc116360c52a6b97d3527c4365e81a7c94adb92680c3fcab470908b951ab76435726cab7c37eab48e72259f3c2b91a77100756af2b57730454f6c704da5f3350503567d3b8f2a17f1c4b45be8499802fbf06e9c2e442efae4bafc000546290705edb09708047504687de1467c0240e64b7bf24a050e775d062a969bae4ae57c3644e1804ddba60d7afa59e16a4d998054f5b55d167d274a66a95f6fac44f09f6502a1d1ec1139489f98ce1b853b4a705c49b4dfdd8a38e485560412bc87c69ec48a47f59651408d96692df9043b6aff030a39e9b3bdb8708a234d80f698afe0d7fda71531311ff32b533b107d84dca8b1181d3348d1af006337cb49fac0786646b51389bdaf8ba2d8dfc4ebca4dcc7546136b907e034c8076060c30d4db4ee6c3fc663507ffebb939f7a3be06bf7da45bd9cc514c87ca13660c9c59edee3101fa6cfeed58208e23069eab1f64654311b786e58116e9fe9c8f5c42698bdc6dfd8d4dac751018b2ec418df31a109338c1c8fd0f7b8efb125c59c42889a8793b0fbc678101f8db89c753a04caf3370b014f2de400237309c542abfa3412ad89035c1c1bbc687d4f22b38fd2daf44d07b469b1d3502818f4919e675bcf501b71120e807c9a9e2f540a486f8140c7cd15a4c68b7524fbaa92c84912a1878e87a32824dec1e9af3e1c8f70df6058629bf10d9a47be50ca3fe588a02c5c84f06a21048c4e23e0415250a0be221b7e23cd2b0635640639b6970b8d2bc02062b040cd3273c8604f37c203745f8ceef81afd24effd90daacf954013350e504f29a66a0831463e28fe684cf17c38501fabdb8df1f0d8548028d0ddfcd2617d1703ac2d73da00ebdc1c843d18ac355b286702ceaa13a795449cef25373bc4770d100951129390a58ff558d12b31e74a7c66ee07ff9545aa5b9be6706489b03e18b337914da1f91b4c570931755dd0be6411878bdbd8fac7357864d6bddac8832d2056ad5b53eed93d0ae048ddd2b664544617ad267f8c04795fbc1ebdb0e3929a8912e53c216445b00884f21d891bd194d2d22251ac7ef11a6d9376607840682f9a3fd9e6c990b78af8fbca90329d6ef999dc8a1d092487701f553ebbbd2b2423c478e11e0879eb26b82a01fb3eb2735a16fad00484fd0aa25b1386309ef3d73cad932890f445740261a2fb65a6bcfdf2b3f12c571726f2e573f5637deb9a2f14a23f0cbeb53c261f54fc6fcbefadce97ceb29095cc08648b4bbcd2578a9a65b692eca395a48a50395d5fa9167eedb48f7d7b31ea4b183e87e1958b8eac9af05347c4c6b67a73303f15956ad07fab6537f98153a2accddce3270d2c6fbf4db2de80661cac088a708bbf80a39b0297d0eccdf920a7d5cf4ac1fed2a894c49dde6a02377e183c382a61010f3cd96307c0c7ab5b6b70850fb07682da3be1f6201f4b223367ec0b6b46a48d2d98cd425c615515c1e8a114d00e96af0225f214f2786b2a524a49767b0bcb5aacef0d1e296b6e90869e5c89dc949e913c7d5058dc971e78d10cd681522a880cec6a41b28574322b9c9e2a714ae20159c0d57ddd8c4c0c74580fea153c2b085dd264da72755d3c33186bcb2bee2af021ee3957a85c23f77f7b862f278c05699757f739c05ecd78d5739b58c37e61b2c915087924a563d352db8f25a5c4c85700565508363e97ec3c25e152693df70d7979a32a2bb09def87bc9406821d8e53893405f24cfcacbd296b60414463213660dbb201314f439c5501acef7c0e72307d08cfbda5388e9ad1c6818169ff2e0bdf1133c2cc59e7c2f112acab35daed1a22f103ff3c4a49b0a1a2b7fa0544983588d9df3518aef363a6e7791b717c69f679c5d7008e6436f0864f9210f78f4155c0308712a41df17498a96a45a64f172189acb840c606a91ac09b51791b617a60df676523f699fd56ba0fdae09273ee6fdd0a21e32b57f2164a2c547f8b9c612fd1314548d143c3516b074b57919c025a037389b0c949ba970e7d6042ca8266b8993df4132abc031cadd2ee6a6f5a11b703d074172581e2ba3b2e72448ac8044e3106dc17b90d134e0995348935c922535307c581012dc7beb8ad0193d640d5211af1a52d19b26fd7b072116a1013b8758e1793566a409dfb7cce9acf0e92065274ee90db7e7499f67226141822a31673eff2803138e357c33ed93a7bbc1959655ccfe3e2914352a993415c6b86ea76ce9650afdf411911ba6b757364c91225f52a75326f29bab8491aaeed06f6d7b2b380981aed75bbf416183c40cb10cdd518f7338fc0094f77f26a515c155949c7a80e8fca0f62db6e328317c02c78f9b87de0d75ac0bd1d6644161613c1014c96f8881d3999a8faad6ac1598a145ecb96cf3bbe125f54c617a3e32971f098bd8309aba2ada623632828435c1b86a745b08ef787daf349c54262b6d0747b5517cbcd962c9d9fb6cebd485eff458807496368f16507e4e714b2bb1d35ee831ff255c99d23c770aa62869ff003517632fa552cec3105ea04a7b0c01919cb73554739e8ec18ee444e6d639c4723b121b42ccdff9118edf520a26c6cffa10e25dc11ba0550302ebad8eff1218df35b3cf1c10d4fa2e45f0840c32949b49654c94b921d36a4159cb1727b68613d0a73eb18cbea3c13d6863c96583943d94386d5221219943ad6ab8a95c846fbc3c2363e0d3fe14101232d236963b54247a8619806a3fbd1b12e5390cf40bb45c32892811a0f88b617ea52c16a445056a21a1d0d1131c01080d09afed6870779a8d152c0ed0629552e4be780f7612b451b05c8177b6534ac99709f541b081168a9a94db448ced9c8e823b03e77410be720683f3ac40ff39920914f12b80341091d41ad53956d9e4b69d39837c11394f66a7fdc65e31bc184d18d06c958259cc5455390c80fde3b9a7bb743c40458556d6db5695ec26b7d351dc1761965c7433113ea5b3b7b1007ad8a341d41bf223e95ada871e28030d8870d83e8a0a8eb3b9e491bfb6bce3c0cdc78a23bd4c7c7138765cc2ff3c00335184fca90568f975d432c0058db419816ee5d6caf4d7f71ccab01aa5f2b5320bef6c66acd525dd0b0a0f9230f755788bad30d77b13bf2871b26535c02a88130323d469b12ea9b7f9fcc3e4a47d03e19bba35078f37c57004fff591bdf86f6d7b990f5dde86d16eaf89380e0fc75fd50cc5ffa140200b42cdb20204e32bd0894653ed5438c2b9db023359f1b3b84227c660e3d12321f74e23c45d0860f9a33352340455ad70354c638197ce0e2b07b9b348ad190e7ef670fb1cf11c2aaaf73b573b180db98e65949e6009f6d944c8e623005d168e4c34d5b00b6cfe48d4840896862e9dda1fff7236fcf9c207936539b9834a0c602a1d19148faa4a42d6d7e7ccd20805ac611803d2b3de0d6ed03176be670f75e88ffcdde348714d803ec1e8d93395f9a3837d18b26cd40cf813e96d9d670c210ed67d617382423f0a60dcf7eb0bd0ff7814527bb25a905c16c4a097d37c01c81fb16ab4e586eb82350217eb9aeb7056102d8755643148e0f7d46a291004c7df1f466f68dc45eafdbcbead8c680c99309be6a380045101ae0c0fe8759454e77cb930b5c3950ae85daa46c0f022c21493740c89b68505b781eee929862d916025a137ecfc8ae438c035aab9f9a84af1de8c3a8f0c55334d156f250b9d3944c62b39b4df81b0c4f7671c7a4149cc512cc9a0972975d936ae5f7f9566a0156661230d80a4e296e1737af851eeb7d495aef218ab755bbf7a41225265b0ce6ed20b4632f391f8e42e1ad0ac93f107426452d9344e0ba8d4970a1d5631aece246e35851027673577818b6284c905ba159507f0a14eb8d3570051fbdf00cb6383214561cef8c928e8582b27dd8b059a750df9e568740cc5a9caa77b249b00a5ef0eeda6134507b1a9f19c6fe2709a5d49c5a5a484e549f1c2e1bf166cca16437112dea3696936f4490c6b6bd9a7d6b1f53b6c1bb6a52328113e2a86681678b5ad640d3452dc4d2232fd10e7d27235e876634d13528817a21cbf84aca3d91634276a31b1aa01c467141ed38562c47f516cdc6bdccfbeea2bd0fc8886911163575ab7a5ea555b8878c9a4c6f91639c0cc94764a3200990cc5614f77d165c09b21150ecf1b0d819b534ec0d25522bed28cb2d5618cf8220887d7ea420a90f906843d1068391eb3c5982666b03a236300dc604ce9618ad774a54ab55d17355d05bb3123c460c261f390b864f8f3ab0f206e8afa40615e5904f0d4719c0e9a628f8cb83fde4690b751e609939a519c17cee0f8c39939305f5e83ac9d220385a9ac4e72ecf9fc50696250d625481218c5f55130769c83d85c3dc83e0ac0b445ee7bb589238696dc2fc2a12f41dac991dc1b0693001ec0250e2a192aba0a93e0d1e77bcde246a915bff1bfbefc05a511fab98d425055073c3b8b337211d83564b8fbb93351ea3dfe77f70d67426070611f470965d9f3c21ba1255de19e07c3b90ff04f34aa5468ed81573eee83ef05b6e45461783df38691a17ad01ea8703050a007b89e350d920bc796702ad234965c6cd3f04e01ef5872a1cb3a070964ebc4f9fd810c80cfe2b1414aa63c21f0ce4dfa5a36a195cbb3497db7ba6cd162463601b170a3a103259114871224fc078f746941ab0b5060d155e9946684a95ccba37d4f9e9a8c8dd3a8ea8f08b4928bfc0ebac520ec199e5193cf524a533b6acfc83b38400af369c815d3831a941f38b8a86480999b97159df94cc4a8e3579a0a3603d78513c919d6df2d059167597a24633a00214a115fad22b16a03cb5780bacbd4023960c431fadd214ad3d63152a76ac98315147ae924775087c7332f85564a2dd6c4a0c9b8e5a4ea04b5f68271634900eed27170e78496eb2d2ccaf22148200de507de429bf94fa9cef36485a11c8f9e33ef447b309447b1d889c5836e06127a688e6575688e47778a5ee38e1664725ac2d243d0d4f2015e2d31b6f2e9c7a442f73f887e9f7ccfc19ffb1b10df047815a791ad1caf6a957cc892a397e63b9f1ec2427167f2370a7b51d877eb3e18b9481a6fa7278c348bd88bc99f5f07303cbcba09d06a8ccfcedb94c23e5632d63d683a9d72e973684828efceb0fc9040fb7568eb1aea4719d93feed0b02b54652efa696ef98d8560135e5c5324cf641da60e06f1b2e29bd04f97bb4810eab6d2a8c2a0d0664ec5e81e6e95000fe4e894d805fccf811503e6097d5169360cd9b3d8f266d77fc04cf7bc35a355534b1c543331c0d9d99704fdbcf8b7c17440a60f7842a5f62a731aca2dd8682d2ebecc492ff74fd040f09261e6c69eaea70a01cde5b53fce31a9bf2a7380e80713be8989b7bd0c69c3089b68218b570b026191948b6e5cba7feffe0707f8e4723542771dd54c2509a1f0ee916f2b43a52e06faa0edc396a17bc7a0981526f5c58e9a03221ace1ae4e302a33c47e33d72658a96e2591d0c63c10ec0879ff0b2545e446ccfc7e460398b672c077f9c60b5e937f3d0eab5ec8b9114a7f740614f40c59089d19b17e400f2ff59120c17c335be6ce36ce6d701142b8786cba3e5745ac796aa167eaa1ac9e855678c9f856a41e440b9b2fdc671c53c382e3edea8afd10bc5d7c4b679c2f0267e47447a60709f26d53eabc2d9c7413b2ac4795d92660f3edc8db2587814ebf653f337621b71d48a438a1a52f16083fa9446351b2445cae8f949085edcd8304d90a628ea2b17372823d75fa57554d68d3737abae054a55f13d79191f6f25973dd4c092d9dfdf6cacf932879804c50a373db106c09138778db4d70455fd3c7fc5a5032d8105cb40b0cb2bdb5cdfb18b36d0a026ed81d5963d5b7e1ce820c6845c128d7862835d123370867ffc444eae70d695ddb3b5db7ba4ae1463868cd922818098401ddfa7c4537c6bc5d3c5aefd8a8c569c85a3eea46548f71723c1efe67f517a007bfd3da97f04e3c04def39cfc20f781d7c8d0305a322c2820b4af80d13047f9af446dc5e6dd4503edd4c76b09a310f6ac3f281aa3f089237603f2bf13265fcb1f9f2a93e1f7ce57782bada895ebb5ab77edabcf047229910fa9460d6a7a3656df94dafe927496a84429b4da25d03a52f733cb16f962c0bf5536257113dba2621f962bb661f23415ec5cf22031d0dd8cc6498853425bcfb1545d3e3b6a7076a15fd4a4495aa94fdd94219f152ad12ab3546b355fd2469535c41547a4c05998892120ade3c5bba7ea2ec26473cc948d0117a372b0f6bc59a58ad8713413b5c7fd86b909551422537f53de655c9941f4fc800e3fd9f9b062ac3f5677758362623b7e2778b83a9e59182f9c23a5473320f638d2bb320ff329b1d1b828897753a8540c035c9bbfc001fc322c6324f98c2792f620e3f0315a20c7c11e58346b810527c706eb6057b38ba4d419ca2473cb42b69f80242f3092c2f84392e9bdba2c017176ad5206ab0a9990a58e0d02c6e5ea50edc02c1994721e626f4e06cd03f8f2db59f3cba2f40e1e95df322aaef720535fbf51362862556d3cf4cc3d0e2690d8892da54e52a37ac5aefb91c76eacfec7b69354d92bbb51249a111ca58dbd1600a0ce5f9fd86efc7e3766ec221a03fa36e2cb6de37ea4d076b225151e36f7a9536553a1b6428d43361591175b572eef8de9eae8d927f806e5465acd0e168898dba18199909a91c96b23613eb79d96c90371be42dd74bb36f06831c53975f8967831ceb1654641d1e293dc49b6c8b0e679dcee7ee52c1b644944e4f087dec8c5c5cd27997dbf37f7643940454c81847b5ad193572166434727c6aceeb3dca2a001898c691f0065774597567ee2b39bb8686fdd2887c627614cb5ce9df2e7f43f1bad50de049bb5d290df9544a956c290769a5ad94ac1ef1db1ee0714bec59c3ad99f8295952e30a61db3508cbe8d2a7292e7186cdeee52c9d2996bea163290dc11614bfa216d58ca3ce6853f1772f378caae8565cc14b7f7e3221808f8316417a1c84010fe113814acaa516651944fddea5221742ba0816eec085d41ad4b1e9fbfd671f83bcc47a9b78a19a9749cc22d7e4bf6e619a0d5e040614fdf752743480ab25a13bee50011ee1e035462a19223ef3c521276323e1efff70a9f37acc69c1ec18e1ba74e63e8ac56f1a2c5f6cc2287add9c11ef4f7e0c35247d46428c3b23b3211fd8df7154a8d1d09bd050bf6e85722c2217e84e9d75940cce86b4d6d62511ae6b2a9af7e752cb04113979bdf2003f6cb575eb9cdcd4d2e6b5c98768cd2aa2321dbb7b508dac3f1a9af362211298e54108ca5dbc7447388cc268a1010d2cbae40640fb937d65557927fcba4ce2388376d95b93ff7d988d34c69531c1ddc969af4227e0187a142b0817851ecb4a9decf7a69b04fcf9f5a7ed40a0e738c2b45a196dc678a60923cc20f444a10f77715632c74373c5c7c0566948802dbad78995ae372bb9909efb615cad2e1ef140f2c1a15afb616a17db3e64ef07f06776cab33b57362f2853102d758c2d25d051bc990470edf97c8869cb2886e860cc73596130e1905e82168088e54f1450e65a125abe5a7afd6489296711c9c3e35b7a0831a23dde5c787e2c101b9e66bc734c71d1551eff6c298f151c91a23ee4c78838e48012ea19c5627ca4adb5ef0b98307f0436677eb1d0ebd96fbd15ec7989144ebdd4d258221d8ebb8edddeac278a62954034386529c8e2f80d752a30c53e1e63d120ef23b079cc519b3e02916a50a37e1292618b47ac7b0e81983a9f200aa7773ac502ef48598e41fd8bb0982a2bf7907c903ad765aa752f2dbbad340c0ed2f2612495586698066b7211b95314dc6adba2f3fe6e48c829d19658a83ed2cca0abfe5881fcb352c2d5a4020b888b4a9e18fbdb449c2f40b32356e6fcd4c92ef744f3bc0f95cfdab2a54e454f63a41100ae67f753b39843252cd8ff221e9980942a75d966946a2b7a268ddf69153abdcd0ae66082bffb5399cb666b7535c67d4c2809530a39b51b1d7a761cefc9ba19200061de2bb429da1b1d7fce6f1b0b4cf2aae421137f3a79791aa41fa93c70f75ec8c327779c394cce6c0493c65590b3ab55ea40b7963006139a8404e3a3c47ec7ea41d274705647d198024fb1c874e650e67e77194f2a537aea28d96d1d5f4eafd7677d6f4ab031380773be4842d550445050784c1d910a8a45ca5e3ec817ea6beedceaa14ca0cfd923370e297114cb1054b2a82352f412c9ad5e8ecc59f846797f5bd6a16dec1eaf39cae3470dfbc371191852396875c2d33b8f143d8ae1778985012fd66973313d08cd319f2f8233029cf4b5dc1b6088d705ecc45f0a0879763108032ca54a99f7e9b6eb56158b924fca7414adf9dd00d91015c789d5c29f35e00d6ead7efa074a74fc0333fcc0c2bf4f3ba3d60afcc0a2b2d755472813dd4af55d5337c417132d75d7616657dca7efa7913f1d89c141f797a4e31300c647acf9a0446864a5f868a67fa87a90f2b73237adccf6f544a7b9308af27960173efadc1df4dc134199bfc7bb359fc37d4397ebae6404d8b272c6af0f9bc9459b5a2b0dae9bc93db1b330d57dcbf421f28402dfa08e0a456c64672f9e713fdfd46905e4faa09877b1863043136626327a5e742cc0b29093919f9159b5a383d2d8c515055b75563bd49b281ce9ac8675ea8a36e07a542903ad1e4239083ce369f778ffe63a0357c455449c397e135025874f6c099aada8d754363af018779f072f9b23b1dda55579e5d16efdceab115f03c054b34e588c6376d53048763f96b21e24c1515c8cb0a72c09637c85ac805b06287881050aea752e527594c75788707907af7a8b9c3925c888a08773c00c832a96df730c69c771420cd54dbc83e7fbb1069ba86ca625e279df695c3ea214f9ae1a8dd8414b8e9e42692df84fb5c3aa4de428c9a60e9f8efc38c27af1cd39e3284c3ac70f07701c66789bad3b075173c3ba994451c83e3761d8d6936f48e7c2aa6f05609a060525ac94bd73158793efbadd086e8e98c0808dffca1422537c70688c84525590021ec60876239673354931f60b5a8eae939821f69b9289c3fca093edcf7fd55b060e57b2b02cdef3e0236d5a2fde2f6cbd104a148c449d292b99cc1afbcb70f77c2524b97ed5a304db10a1eddfe935048e8449fdb2210bf87dd69bf5a63a0eb53d533062bfccfba79dfce0f222b6a1ceffc617fef2880334f1f3241a09223ee3012e32b1f6ffcef1ad568ece0fce3569f6cf9696d1078d01a3bedc4bf7420243b703faeb87528160e5462b17075db83f53bacead30442bd2fb149b77ff816e4fd3e96674c085e5d790f160371aa1be38cab229d7a10822e15e32d2c476e2e8e832507876ba2f4e0436b4bc9e79b5896deace208f21239b7e67dc653ebda0d107ad8f0a3132e0753411e1a073c030da578a1447cc8ec212447c5d8256c405b2f1755d6103c175b6469bbd0bd32d0dd7c7e17c24216cb752c337bd9a13b67ba181009a72d5d20651b121769d457e470e751a572575c4a5297f9d60cad0b4f408e6c1487e2ddc1f31aa24729fd68a7cdfb9ad269d1caf818a6b11023b7adbfe05e225f468d58ee4600c11ea40fa1220818396de33d24bbde5d09a340c8e1c276230a74cde00e218c81335fe25fc6aec38bc0a27a539196226527ae8530be67cfeeb8c365c7f5c2685005ee3a95208e0e25aa6ec15358a4cbc7ec17d337b1a893f30e9c85286eff545e9c40afe815abaa788e4c3fd11dd917fecbd69813b2a00d991ef52883f17f79161e5617f346868a67a9e53a7dc8eb5df303b22d1b149f1af2aee0344228e46628787d2cbaf699499907c5eda0019664a133cd27b9268e41ffb94cabec9e1fea5a3d603f968a6573783fc6564ea4528db127670e01717212443b50da4e37b5de4511acc403ca60282ef5636b816436cb607d4f2dda2688b0f9dc2cebd3521c206936ba8e69e3732151e5d331bbbf75d029f8a9113da7cbff4988ddc29292afbcc3fcd3c733c480bae4b0432aca9923a295df83f14941570ce9d579b083f0c0b6cb98c7a3d0da7e012e9d7cba9d333cd794c82c04716794a43a4ffb11efb93283a85e903cc9a8f6f547313b56922b5e0613b4a480a0e4ef27bf2432844fd058504b424ffea5fa7f4bc10b9ab909d14edbb999981c3b71e854295745979465e068a06af28fe6c21fbf6bc35b5f6473b076e5e203a57b3fd6be2ef0dd22d9697146a4aad422c9f6d6134852ad8464f32331f8db8dc0dce839f93648f6e7621e775064cc7adf394033b78751388d7d1739cc13accb3f87b03a8b15ced82bbd6339485f20a6b8a3eb629ebc32a96341e52643cb456a41308c8691df54e358b41f1becc6b4c1c94d979253b382ac2a32f3529539acdbc950694b01039446228995b9db4d888221ef6847e05714fe892a99595eba13f599fa2bd26fe7709e15cc51f3c5460cb3ea68a01fa4be64cc627f04c520384dcc7f3d489544231e8e1b93ea12d537ef1acd010ed3fa880f6bcd0cba26e5b44ba6d8644a718a48d914a6b5984754a6a4782723cbde442e37f563a7f48ea0e96acba7aa4a880010f01c258488f6faeda1ea47f0cd196d619e2432f1241b82b77b6839b81c6518092e41e1a8ae0e403843d02151c3ba5c80085931a93786704cad33942350b1d0670109ec03bd5551cc9767ecff7dc055716965db6cbfbd0fc10d094241b98680ba2c305271b0a27bc0569dbf733ac98a20d9e7e42835bc4da05603b3aa5dcffdbea0ccf0eadcfd7abf0e0cf42795f5d2f6c2546f0a2c12141fe19775cdb1c8b758d59cb764e011f3d0e365610fef3b3effa7755c34b854905e1b1c6d98389138ea56e2244d779eea56b124030edcebbd197ebdf145a4f4bd819630236cfd5a0b2e7779770a981d2076040329b23291379630683dbda37b4c88df4393ef27dbadd1affdf0ce7ec9a088c49799c3080b200429570430e4cc358bb22b0aa96549e5b37d1fdad1af4b473c673ae30dba5d83da653212bd82c24fae307c5147a15817498b2cbf5ccc8501c329df3728142032c8240687f471ce4f5b0a7652402bcd363c27302c0f0e71e34300cd0d3c3f7c52086cbef27305919cecd3a8e63604bde1683dc8366ae0800a90c9334edf58d44d5fca73aefa22222434d0e54b42322d0ff4b7cb349633fca6bf657f5d447ad8d88e2f69625bfbfcbdadfdd16a9f84bba1fb1f2eeee378bf75f823793ed1524003ec4981571149e14f0002c9540b82315617416d3d3c753ffcbf16d1201c6dd4e2aee044f56de03ad5a1a323a16f98207703b620d7a2e833dc71b2a3e8d85966c69fb00d5c2ea9f85970ee7360bc2f72141d3bc375478deac24f500779006e7e9b68493b2fe7ccc8c1f68503f11c0901a75f0b535282a8924ad306f1029740e205c80f9a061a21d572216702abe1d10105d1704adad3d96b09722182c25952b48a963c6bdb522da53030008409d644607eac373c126a3cc0868500b41d2c5136fb147ee86e9e18504f803b3df5bd74944b527c581c003eaebff36bdb9e029e4b78aeaba9830a486a4c23464f5de075f504aa749444902e62be651a8c8c70ee5d53e0a24181c7b9f8740863d0018c0212a8c034e4e538706be532bf0988013a4390a076ae87662af24a649a94a4099086cba361288bffa6a5196f2dd2150a0f8c15da5cc36cd8a4b68482785cacace7330f1bf310f4e2a5c09daad65e3c9e8a8a07d80702724aa59fdf66583b4546374bd533b714dfa5d325d24b18af6a09516686cf9a24585ba5334c7b0c894290e6b998a55065a701e32eeff77a477c5c39f9d9d18284b3cd7d0431e068006ba39c21faadcb0a6c1979bfb65839d4fc33730e8694259161b9f520d094032204c61766bce3edc00948d5a06c21e6c4d1ce55660dc630f2bf27aee3468404222d9744a9ba8b915b1c16505fe704135e1b661e6b45f4706f68049a798cf6972dc068671b6d68d9a11ee5e6a2b2da6a2d8195710413544c74abab9b2d47ea0db00ed566262cbefecc40bdf40fdf81c874f933c3b5147d088611c8518e97daeb8d5e98cee6e2da9147befcfdb6d5da8696f34e86e2e425ccdf3b25445bc808ae796f34693bdf98ae2fc55dd92c9adb4994785ddf52001d0f781ad170b33fd871a286c261d77eb1f1f5f9eb4ecd80a4d7e6b2621c16e9c1e0d1f888f0ae37be9c15fb8c1f7e25f1b5707119b0c715c50814145cc3f0a5cf85b98d59d79cbc6b3621d7db2735af2d6b7912722aaabe5c5abff19eac601261663d4f1d461d5a09aa3c069e3985e07870de7f13812e885bb062857219d3196b12ead18921612ac9d729da6f3944bc847b82a92e645e4be94c0c73191de9fe0730d835a68487fae920ea71172263ec0f299c5ecc67dc45575c681d871a0c1d6c59ceae77bb0c9812fd33e9060eb7639934a9e3c8be70590c24e853d73a0aaf9cc8cc2cbab38374859f807dd50509b01b0317f75185aa08ca6b86df69e2dfbc46e0a76483698d650ef3c3d2ea2e6c6d69cda49a2e07d84e5e0f3e4828ff5d2d4b3ed42039e554f702649ab0dca9c223d8c35df2b4e89fcf27cc67e6ee598ffee9f2341e99cc07433eee4db3e56a44c639baef00a6a84850ecf0aa783ffce73d0ce7c06c061299f929d02151e9f8d63d26bbf2c7f542165ef19e876e37f0d8d1f167c531c95e5c9285847b5d0b3894d75d6a378859b9f2596139b824ad6d5add27ff539969c92a3d24fdc1481113d7627a90f9c6bd2591703c0ecd1f15ab631e5a53b15848ea0b3d087584e3551a4ea58d0b08eff90df50fa6ca5560762fed23130aea97fea5be9885c00d2e7a1dd34e0ccc882f75d85d8a664556e56269b18365ad5e6091450fd3e43aadcd4c0d8af4e459c01f47badf539a0699777e7ab802e31788bb3b08ab97c8450f05a50f977969a276dc31c63ba0eb91fe3558327225967f0293f05054bc0ab1c4c92669941e372c0d5662697433351ffcdf2d1317476aedc296a72aa496645c5cb08eaf81e05cee8aa3f4df8b1a6cb0023fb35f77b4d9a6cf2e72221585c11b027b27b87a0bcae4ba87337f5a5604d0b4d5cbff6c5a1db325ed3fb143eae6ce194954a64e3c0f33adc2848cc3c4e212031bb95526dfb1422f5257b49c95c5f93cf923aef5e0cf09aab1487f9ce742139f99601efeb696112a7417f301c390cfa82be550aba7727f303a93bab1d73f2341a920fc89721aad8e9fad099ae10afd624ac55141a58d5d8011157b5ba92ad56807a4b93d88c9af9230ecaef20734cfb67214bf2181fdbb2406f46c4f159fe2d6bb1a1912bec2a6f2ddd6858ada309b65ee30ff7757e5459d35862a5ccea086320c1fa80fed0a1ca75f5eb57f299d449bfcd3067f4a9655f25db0f35f10040ca00e73338006f1842ec1c26cd279866f0f64aaadc8f30b3abe01a585e8202ba2c5f0a14b8c410b36c6273026f8429ffe97a53e7ef3c3fda92c42bc698e09089d97d8e35dbb5095883c7ff0cb55bdc83b14e54f40628f9eb06ea94d21d2c8b686a3ac031967c845e4eceedf7fa2fb40a8e4fcf48c98bc40440f239494ccb3859ead4685e6802819812fbc2c99d5e1689d09fe77d08c8c1990b39e1cad52685dabbb5c5aad63d3386cab88fd6b90bb7bac587c4fabb1f6719db715a8776e48d36a58f07041cf2e024384dc3e9e8dc2f8a3c306d955a9897fd7c11e68dfb3d85b823dd074cc627f239cd31e6a17c7c1b74be7eaf265d718e35baf5dd4069d2049083b396828c6cbb830f7167da17be2528b4f45612aae4d55fa1d1107807647a658d4896d6159237d6a4276569509c4ef3b525d03b5e46ccc3b639db21314860f5c7206e37a840f98802284892209ce430ccef53b2c56143172bfaf0b1a68e808908585ea5d53c25c2072e27e1bf64b145cc46bcb4e43a43184bf8dfd98c443f8539c932024da3f704d874d0a872b81cfd70009be24367598a659a33fe58963af77af1b47bbe4e8de703dcab2a8e968c15eacb08d08c2d860d32f3b7174b418062c8a8f6e5ca4fe2367a175f21051733fad91ea7c19b1f324db54b5dec80b3e7b23eb84ab71088177dfc4ecf5730e78f8406c928d0085ffe06e8447cfdd832cd1078f0d19b8f608344c789908fe2c2ad5bb609b0af29b87f8713004bbc07d090f25fa6ba75cdcf490712520dd9ec99f6d7ae79b1a4ab54e69fb61003c8a58dae8ed3366c841e1dd0a08fdfe47423538e1a32c62d4d5f03da59b6a11d7f6dbacf956e9b50792ce6fae8ff9204a26bcab53d87d36fab885031d8df8ebbd8107e4cfa1cc3a4e615bea2a2e6d0ec503c326480620847e10edd83883a4e16b064e2e2dda1d420d37dca596f989fe058e7a9a16c49dfe4ab02c109b2f1f6c36926c178dd728d87a41be66de0e1c0c1095e90aec3e802a0cbdee2d59b0207409493c1a96694ec15d359ac8e2e032859d4319e41553758286368dc06153fc5380f9c95731e9669bee586e0a2a415cd5496ea211593216aaa70d8a0942a968668e08319335cf4512a430e4c75c185cb294787f81088ef350e00b407dbf77415d493b2e1a2ed429a68596523c186152ed276fb92f010b9f4e788c82401954a9040ac74f989172095c3a8836a1d93be7f2f0c367dac2753d47bd1ea63289b2a528e3b8bdc80a277ee4f6d58c4c8a99de4521761e6c97478edb0ae8db06295231b3c37375fb471c565a301f1ca77083a69b4755ce67282fe639aef7db2e7edbf1293a4f2f8578b289c0bf931c37954d826ecc7e1df63fadd8a6f25851739963f120cb28890c0cbc56086285f8368c8b646bdbd4ba4ccd6ffa0d13394c2451f4e04181ba2be68da784bcb2d50a2353001438ca235407d6a9201e1a9887be296d44d26363d70eb7643ed4f7920f956e6950e869401210c2789b9eb3353f4e2f67e1069ce516a2cda111bc9b3ea9b60af809d932c5622103ede20f634ed0dd6db3be47f38856277a74c457da030357313f218e1ee4becb38747b3d1fa4135520280a7e5df2b34d43695179a485820116559b8209c953eaa7d07b9136e597d5ced59be0d83d6afed47eca031c157f220d6febceca633a7973ec322752988fb6db084bf36e0a025f9aef5d24beaeda03cb9954299450f3e7e1d04592f1957f5740ce940ddeeadbf114ec54ff569baabb2d12169fb8a852315c68f0d7f40f983534c623ac3ce0bb9262d40e4ad120f61b122f686e2851c10d59c59a2bd485ff1dd872b172bd736f974861b3e842ea994792b05a3b70240f56efda2e686774f4640a694630ba149a5d8b5daa3eeafe985ed999faa9880315ef5ba15b90880596bd7395b1a9d7f3e1f428f6feef9ff605e9abf69e48d452b1c18a1b73d0b8cf0cc6b2cf267f9fc7fb0d7e4110a6c8f656a68d1b713b40d8606f5057029b6cecf9949df2991e800450a02ee229700fd93adf38ea44c793cd2929247b2f92e2f4d0136f930313d521b6fa3185a6b2e12cdab53d6eda9febde50a1ebe92278b8a0326cf9efaa2047bbbc72e14438c3af0377b2cbd5b36e9da69624e8a146f8add316a467f1079d83a3d44ac0868cfe1375bcb588fa46658c28bd81569173dc46c7e28a9ef1ab6dd00c521e83a029823e779f14ef818d6b45ae6adf3cf04401d5a8c52345fa29cb8b1706d3c14c81598e9db1f2dfc90cd237673a9e02e59a3211150e25b5bad75a1f30b17637f6e38b23de6af1158ee2e3a317c67090cad7f3313c3aa2c1516dfa556573a2e04c64598809ea67bb54050a21ea6440e31c6cd028facd78147a2d7fb01fe05dda560dad74b1128aaacc875b1a9d37e3811e53401459ce997b10888e551c8cb88a1d04919d645d1dc6433a7cd18847bf551ab68679f5542957993bf2ab8bca4313b19d8a4860b54b2b93e68274d79fdebb9d9e32e35bae079eaaafeb05425a5071ad77c47cca4fd9ca4d4c72f0c6ca4f2765d452c930a230e40aa97a42a2acb8492c02226417715209034bb4092dafb1f206502adf63c02f08df51f83083f131befad2138b60469f3ad3510e2864bd5643a2427bc3f783c844ddbacebd73427430c00251b5c9ead75239454626d7db4addbf0e136acb28201aaf3f8731448ad59c262576c82659e8676a5107bab09cf0da6b0a536280387546f6a0526132e0db582286f9a83076322608f4d8bec94546b203fc0581f94379dcdeb42a9384eef8708861c503285024a728b132ecf7b6ae47cd1e866322c42f1e08b037a49c30a8bfc1f7af4ed87aef77ca050825d36c3391d4794dab2179271b3975ce6098c54069c74fa35814246a943e13024e84b1a1b68e1f422b109cab7b29af34b189be4b0be2649052d701ed7440d32a9e59b339eeb8ac2ec4cbfcc1a2564af823d17cc482af8d7097382459cd14678cbc9c2d51fba31d8d57983c6161949d5d66ed439389813a9d6ad1aea06a2ca7d1d637f5db0e22b825324ccc59dece9c5d752e1257a6634ba8ff0de94dc7df0f05be5ec3bf940d458c3ad56dcfa1110fd2d508c651d9db6d45aaaa7662a292258cfb277d40ca46cb44cf93e10a07ee2d66babe552365762b0b419213679ca101a7b0824d9890423878b5f8c94b65a8e3dbf7e755ae092f7e4b74bae28221197a333c937a203856f15c8037d61730f3519d856a339d001dc311d08fdc83a8f0fb170df6e5951c8061b74594ad238d2161b3ccd71df512c19b493c9c71e66d2e1412e55486b6fabd6a5c9577b352851a4f363230e0547e6c4d59597af8167349cd8e05dab48146b3334609c70dd6debaaf53cfa3cd5232de0003dcc53ce31e12a8ea74366d8c16f1326c886ac69664386d1d0868c64561bb2e22be481127e3a14c6569ea92a1f32af52be0217d52f4661b8465f0841cdd16e611b5482f2d6d2f7e4d00caa1b0168a501db551084af4b9f372c10b741f4a2cc7b52a5636fcbf134a6d1fc2984a0881e536fc2cf90bfa576001a398f7037f1c522721b6444c206b4f74e24732b8018ca4982065591046ad21b61738263c837bd4f307e27a82c04f1f9c941c47151ac8267a8023dfc452cc92e8e61f5034b5ccb5c310a402b910bcc16b9515bf0cc423c482ca8985d7d82f582bd192d64c8c3f36c61ec98d1400dd1d8f2004e25f2a56690bb30324445fcd41e03e6d2bded16b5e90476c66df8bcad67945827d89165513ccfe637a2f2f6ec5f6689f39b3d4517459f4b2ea114267dcbaf8e78a622101ae0a88a939f19dadd7a4807d32c3210df383a25bebaa2583e957792a2e4fcda564e86b7ed3979a808f2bb0f28694f478def40346988dbbfaeedf94439305c3e8e9642b2b6bbefc4e63b58805783ad9b580555f6cc5fc87c31c83caa7adc63a79c9253064dae4b2c3264b13be455ba3adc91352f32b2554d4418f94689ab9ffa3397536cc50f284e80833ccc6b0b1329ef2ad0384bf3e7e1fbaf3dfcfddb157f745b4e0a9357803e7ebae1cb932b8c1dedeaae5d460317f03da0a9389fac945ead1d36e691b5e689870d615bc3224e35e601f7abd7a9b34319995bc8a4f1cacb7bf0843bbc395eb751697a96b34478831b45e3ba6c70e747940d397170d4885850371ee779119ebca1f777d66d8acce3da7d9c700c62de872b3dbd11084b6b0fd3f28701d9e85969fa743c2577fc55486793455b8f83b3200cf38bd5f767ab931bcd3f2bf62c9207e214c5fa31ef4f712f53cc057e84d3bcbae733fa635a47cf593e56237fe9843faa980af2c6df976daa3f6c731f761f985203597cee09b7ce86b0ec42a98d2a01d41e935a9469c257e80a318bac2edfeaa1fe314905f4a8030ad10a925820d03a4e070f470cf6441978ce0061291d62cd0b043060d28d7c9ef31c7c60edb7977f581038dbfa6954361b179d4f83602e40bbee081b180254438fac6147c6685a1eec214f2e64bd1385dea390b6f7de5bca2da54c52065a0910091809dafffae7057ba0a3152978d2f17316502c63dc31022cf4fcf9f3044b628440cf3356f204bd5525ccf49c7fc14043cfc74a2c8561869e33ce982043cf3792421217607057de4d2ede82a787ddf18e2f58a5439f3aaae36f1948e185f9fa3e86f94abdc35a2b772eb4ff9eaf70ee7890241208888118d6bfe1b5370cc33338443776905f4869613114f249eed7df5156b5b87fa1eb09e2a4a82df7983059b1f624f940954739af899e2f2593c874fdf6e004c58a08bac061cd125794c01c808828485b867802c10be0d0a2cc60a71e39a5a69a7a45133c441a4429a3b360d8a0fd7f9c56bf6a3af304573569e9862d87a6898cde5166b96842c38d26186c4d5edc702f873b8ca3e35213154d4be81d6a317a01a9eb47262db1addb053205f6af4b38cfba735927d35b6dd56ab5a17eadb022307d49209ac6dd739cc194220345db1813c66a1923c6d620368c024ec88438020a24941003b45b5f860842fbff17233070822a6040c41059408183ac878e4b5ee2f0420649d232d39b9ce9086f67d25ffc15c500f98f6f97e9ed21c192a04ee5cbbcc888c98f1c698c5f29e34a4f30df914f65e62bbafca23133e6f4c4c43446ba7b931b35791193c7664e57c62895c1227f32bda21b311dcd8c64b248e9e94a19a431464d723b9374a632364a17599fd0f6376fbfed87ac967f2938249fb4cc5bfacfdcd9d1522f49f1e5de7ce08e59f4f72ad5c7ef5adf0153f050d3f11beff87d6b4e928d78a8ed683b9280107f77b071f4dc9f9e5fe36bf533e75e0e30e6915739c69d379282bb76b4f634fa483d59b00220acd0f3e9cef71c8031eecc25f827f729d950b4678fa0fe533dce4f90be056f1eb29a67be0a1cb29a6e19a6fe84b81f926b74f733c78fdf446d03de14f84d621607e3a1fc2dc3d4f767f6314356df21abefa76ea064fef609cd9761ea1b99bc286bba8c316dced8620415359841e5061b8517322eb6a0220d1a65d8acb10110a110372842aac19a2f40c052b003154c4ba801476cca86456c08560d811735a67b09b3c38e5b354b6d7704972d0cb6d171c98b1448e0948e4b5e96d01b6b7aad911492ecc0c20b117a877426bbf152f1d5bab2b5c3be674f65bd0c6183a75af2705679b8059ea23916e0abe392971cf4f628d0ed8109f154dea6e3929798de2c59181a23adb5d2e9458f8773b624a5d3526badf5c6b09032a3153f2d2b0fc368697475338bc5fa11b23c2f58c5c298b6c6e86274554dbb744a3136d9762d32ed2ea34ed4893ac9e6ebde1966726ad27e2fbeff6188449d9034a4f9baf70a56d21ec3a2fdb5d87c59ab55a7f9b2b656d97cd93bab48f36535a71516ac64eb953b069759694fd7e93a5da7eb74efdd2ed26f17090929cae2dcf20ef71683ddd88ddd98939393934c2693215d24a4b7b1f9da6e2ca61f48cbfbd26d8b79e8aeb8c95254743e72884c2c10b3e80da58a26318bf66de72091399ff634e7734e9f5e355da766fda6afb69a553b58f5138beda6af05b52119d7acd1feba067937bd5ae5cc9a5fff4ed3a6e7fd646e4f42fbdf2f683e12775342bbfd905489744589ce26a731655c36639251cb53acc86842b1f2fb40cf6ccdfb7bce2f0b2cefbdf7d6392916d98ca922d54b29a573de8f1ede2b2fecde278fcddbe434a68c53199d51264ae9f7b8f75e2a69934d518c59b66b8379acc0df09493861dbaecfa6ddf952bbfe99da6e322975310e4606587e1ae9cdd975f2fb1e77d118a2502f3dfc7ddd36a5f7f1fbd4cbcce4fbef53a8efb70fe565cafaab9970c4ff0f6f63f31f186103b2fe8d00a38bacbd71de6315f7def7cde9cdf73c4f6219f1dce93c6efbb48f0ec69d5c8cbb521f7f686792fe05f4260fdd994ae04c5a496f67aa3680b153805609b44bd8272c162bb35aeccc72f190c943b75fc034d92b6396b0e14a1057b4ffc6244517598f557152eab436d1669cf3c9c74e937ac803db46bf20ef7ba6acf6765e6ae3e2b7501f5f7edfa7c01f1e3aea0b3dec68b4823b9697c5748f9187fb1f8fdcf6940be232f510b4825ffbae604d6a59e5a115fcf383223018fa4e14acace3e107456047b915d345d64639a181b793f1f7320ec69dc464307a3b19ed5f67dacc070e954b65f2d0ebcc43ff50092cdfc9b84b8bbbb4ffa277f53217a987d3e23bfe5c98be34817112f31c9970a6399987fea933b0fccdc990b4bf87345f28997745bba7a4dd9f93cd172a76f1eac3964cfb7bb1f9fa9c9cbc2227cfc8e948fb774ef3f5c9e68eff1da3fda37f27f3bf33cca4fd3ba4f9faee15ac8445fb7bb2d87c795e93c774a4dd48bbd715c91c2543c5260cd66a85e1ff44c5262a365131fa439d9c9c9c6432990c090909e98b7db12ff6c5b47f03de73f29c3ca7e939698f15e584bf4c5adfddbe7f434117356d8d61f71b1187fa328d3c5dc701bdbd0cbb8edbb64c2b783d1f45fe74a4e34f305525c678efbdf78787f12fbdf56af7e66d6d0fd5d5a1de1ad6743aade6394a9f30f22891e608944cd39433d33891a68d1b3520d0e166166046044f2f400204559238401b4a3138c81c2131454d951b354c50c9824b136d5861a3045bce6862cc0c62d006136fa49122a5258d16a8fac3ea572aa59553c705cdd30d4ac8d65041c59738c6a0c2ca137144d902884bc4aafa0045972b96e0a1862fac8040031770c18229045fc2a0a1e2d184d1be54a7a8c01b812d63c2b18ac3871b9b3360c0f183ea0d1bb8c301850d1c59c0218607471a374c9d84c30a4aa281061642e440022baed0208a119b128233669c40093364a05283538d5677d5ea02c6c700a30b1b2b27d419ae32c325470c66b07d2146cc8c0b18333801105078c1218d356f446941980d099bda039a0f785f6e1c2af080ce35d674c1c28a33639af0024a15613851023262d4d00414606e1c62b82a258e373ce96e8da49064f5ab1b1d564ea915442d2d1eba2395193d30219ecaf3424b14676eb089801b656851461955886164c21561a401c6165600610232506cac2a0f0ec576a6053a54cb8f91948e4b5d54f0c4024d62a0f40194d22942162c7fb7f40fd64f6bad55516aa7101ded4b4b3f7e4ab817541d4e357b39cf7aa8b75f90951d71e5e18bfa68a5e88176fc8f8351bf6491cb1377a96268f3f1a34d0c5ad56159e18f568fbf767c7c181ae0ae1c1fff01eebaf9f831b84bf5f19bb80be7e35b400806b1aa090ee8610ba8c0eb3db901a94d0674ea6568833fdae054ea05df417d81bdfb791e0bb0aabeb081307c411608ac4375802f7c3c60d6fc8fe1fd7861b0f7de7d2f0c1ebe065f70c17b4154f9b64537bd68a5e881b88f8feb476b7fbbf6a3be4f9caad081ae915ba95a80e7df7fe215e0148286d90cf407da2d8ac270427ba006060d5a98d14afe2e79bbc26ba01525790609a4e42b6865499ec181943c05adac906798404a66de7e45c74f9241f040d14df0bc7d091d3de6a1db4051c14160818d008eb31dc0c889a46ba703699b9ac6e5ed7f5fe6edef6fb7b9e948026f07e32e035d8b87977b47e32ed7f51d8cbf3a592de3ae222cd4c88bde3e4657544c0b35cce8ed66b45043a6f78c4d1b749d5174dd12c7137552d1f52f3741c9715ccc3ef3b0fa875ef0762ebad61c24fe928bbbb4afef5fbcc95df6ef77f153019c010a90e759fb9eb3f2650f24e1245cdf7bff9ff94402f265b6e2fdcff4c024cb7150f4e6b87750d3f2b79550b483da6f2093a10cd821abad7d3d6dcc5d5ee4477ea5e6fa29f0c61d7f1488e38e073a185d1fa41a0caebfaa1febdffa3a96f044a2375ecaeeb060c85261cf53e968698385458c7f29e512a64b0890c082044c18e0065684f8c01645557801aa24404117346cf980374bcbd8d272e98c10da4d034519377429cd174f5e687f8a552afa29f898277cd0c19a1f288185b84b3e8c15fa8b02fd78a3072bf4d21b66dc484f45b3ca1535cdb981194fc5351d89da498c74e8c990d5fe5286a93d6ffa50a674d2f9778e81c0df122633cf9f40600ac5e50addc7958e50709c519942ebe838a382a4af87573aab4b3f29c170e80250344bfe0f2dc19f1fb549ef04736e8c3feb103a7e747bed3b2731c6af31339179c8e6cdfdcc4372cd548132994f33d5f170d3d7b28e06640a6c9f32a1afcdd79e097d4bb92dcf3c64f5cabaf64ce84b7f979321abe7123af3ede105d8e59c32473ed552c6d471c04117589a80066f8a753cbc3947d5f287f9bf7fb8e58997c5d331c650b2e2f8183b392b65b98bdb3e7aef2d8129dd401ac3aedbba958e6aaee08da5285780b1850e67d020c011465da678230b188c918226b4287a5423cc701d1724f0b84801126ab248810eab6ef52b379a564e9c957ba7b8d14523811b4b493abcc29f8e4b6eb040ef906905115c451e475a451fa7de6a157d9c6eaa95906b51cb2f484aa1f9f32f93f9dacdd1ce396bfd822aadde25338537eba41b26f098b4263cac6af5c0a2745b0d2a38214595347ec892c31917a4a011031dbe8461021a82f88e5855a41e99755c72a3c8cc13140048026f4b291d97be60f15106c7f7745cfab2441c68663ddc60d6c31a65cc7a689acd7ac07265d64395590f51663dd83093626626458d9994a69994d94c8a1533292b9849499ae9c0349372058592728a60524aa92a877ed59bfef198d7744ee43bb1177ba81afa11863aee1a62f1fc8c5c48e1a42b381423cff65ac83337d6f2d659591bedb7d72cddb4fa3f5e87e8365073b2fd93a9b7cca4fed5b4cec7aa7bad0c536b79d73c64b50a5c3efd4ede8f9167ebec094470fa9e408c3c92462da5bce04a65b19cc0508c3c3c9e877ea3b5916f2398538bd095081dbf9aeb0874ad72b504ab08741dc282f59dc87f327590cc43566726f46f7d19661eb2324c33548cfcfdc3d90f487adf4d312b94cd7c50f22be7474d295d22bf66a7fb48d4324c2d9f06aafa61068dcf6babef4ae9cff92ca4ccc0f2672ba506963f5bb0b0f5e1eaf1ea524a351f3bec4969e4ae08cdb80d1ec5dd5d8c3badd3dddd9f05adfdb7247d4787216807c1a97b6de2d3571d88a2e9bb7f6a0d37e33e72c0f23d07899cf16e51722aba0a4a0d8e3c5349042fefe3bf4fef65f5fec7b5098a00059eda7373ce79bf9343f293461b28a95c0f05d8bb964aa3cf04586584b1a4e23b958dcfeb988b6920a5b660ad1ed8184b460b24289346068b0f1309a992294208171a818c9289f2832cc248a88823c76bb42184101157ba355baab0b618d1d2d4a3c197d62081f6a77e2fa5d2094d9750a3e91b71d7d5f45348e2c20b346a4a29a519fb441986472ff4fe1144fb5facc692f65f19a1fddf9764c0a44614eddea2f2dec614cf35a63b6da9717efddafda9d31be3a512cf977c98874e427a3c9c9f52ea49925ecd2adff157c92b6230d148e948430d182be44c97e30b8a859a996a02b1b53c89fdae745c22830a324ce0441759db46e90334b88cbb3a982ffbd3cdb85359e960d58c8baf74669e4f391f470a2c1f4709cbc7c982e577c0fefd0e24d5ef80f6db772004a6ba0321b0ebf864c4745c1aa3cc4bc7a531a2d0b2cc7cc9d92cf2cc2f81091c3d3c42fab0f52367393487e69c0178cfbd0bde734f9fcb3324d1ec02f75d9e81cb39b4fb193cefb9870274bd19674869cea829010c8c6000259604126a38f104155888019a506ee8218d152c29cd5c60051010142c81822cbc60e28b2320960f587bfbd5eb4008ace958020a300b120d2c5f7e3903d07e7b17b4df720efd0854b30bf66fcea13906200b3895f1d87c2f9a2fcbcc974ff32da88135c0f26d94a6774a9dfc8b29fd5062d54d4bc7ef71a655e386610bfe600fe74b16aa22d0cf7d99ec47f6137de22cd3b30496bf7770aaea86ca48e79c6428bd59aad2c05286dad3ac812aacbad93ee59d2fe3ef39b34499663267adb55aa1239dd1eb2229ad9abd1bd7a540d3760eb07d4854da1ed9db513de9474d9b5fd0dc26cd94bafec008efa3c2deab7d484094d761c18407e2c41dfa1d48b55a2bce7c1c3dbf43a5eec65933342502362777330c6400a8676a73540d5229faf8f8f8f8449f0b024e7cdd596d2e09276469daa2e3efc0228fff8f9ba0c455a99aa8886887c520f175dfb33cf21d8f5768bff2e88322bc285f51ee6c498403e44b1ec91dfff87208f99245534a1268bfeed193b07c58962aec872466b99452242f57cf145b9cde3974fc707a7cc9a3994d60016c5aed6103702013a200b105b03c9af248d54453242a0d14a3165ee818c3c8a229ad42d397300d5667d462e4a1124a964a9aa3b848737dacc1dbd34b5d7000400d38fe9547a6981a036c755c1a438935c6954b63bc38cc1abd574f17a76ec7d91b2dff5e57b560647ea85fca4c7bc1d0278d29caf4e2e43b515f2f637c27ea8a6f4be6175f0f7b31a3ad79958c640be619f6c5e8de2dccd1d17df7ed15f405cbdc89fabe7f41374769e37ff50d63642d07b21e7bf7e255d57109cc18bd5fe65191d9195f2ddf99b1e02ecd3b2ddfa7e0d043d984a56d6679d8748591969b9a2b74b820c09e8e4b56a8d1f2b51f30a7e39215663817584183788f80719a3b516b17d4f11df9958ae003f678f1155c68aae3121820f45ed129a4ebf88f1e774530465588f223488bdc888c2eb24ee001ffffff4a156ffdaddbde73e4781c7048ea1c9d0d7a7a4ca22963a69b6378f880f7535dc1f953f7c0721c070c0ae7998e668e929b5ffdeaa6de804e82c21b473dcedf645645e526f9c5c39aa38732bc5f4339d25df45105ded3e8042938e7b5aa71e6ebe7cefc09c2cdaf6e7e957384a8bec931f2e4448df3db8f74ba1b5007549d20027c73138493551ee264549338a6fa6801a6bfa79151cecf9f54dc75f3f3e7d1ea77bc24d20cfd2595e6cec4517ad60d381d8babfea6db015224d46fa9e45f908ef76f2868c70b49bdcae1c1c10b499df33fee47ee8a3bc01f0fe74b247984bb76fc7c79c55d3a7ebe944fb88b839f3c1e498e0a9989c6c1015921073c563b401828a4e6d714c7ab389287f371b21fbd2a6fefc70b2cf047ab0726043492c23db259cb3f3ca4a0d7827da8e32e241d55bac8eaee7cd990443ff5d13506933acc7514d45e769a77bd97e0d4184c427d77352d9f7027bf2393071c787ba2bfafe0f6b65401f554a39f0af62df800b0011fd8c4d3d7fb54a81986fb50f44d1534c72095c8345a03370251300713bede97c0ad0bf5860cdedf8d3c1e0c30ad4a5ef0507ad3eb07bac8da93e620b19a3ed51f084a40039629aaf00014c3f5a3ded6d071f601244d6591bb6addb2280619658646d7298ba4a7699e4a180b8e1ef67848bfe521fdcdb23a917e4825177d7074ccf1ef893e724be4a95fdf845886ae61e85abdd0f5f7102d6791a7fe0d1b78fe963369c35dc21d8ba63f5ff6c46c0aea51fc15231997b359e6883155a8e86a83ae9b34732fb7619aea38e56cce970748b1588c0630ee1a23cdb82b7641a3a4ab129aa9a4d4869c7d485e3421456fe9a4ebc7275d6bfd29e9c4e2ae3d9ba692bb6297345874fdf984bbea11ba96602a514aff88d4d4a38f0e2c9a72a015ef5347e0ddf30077755f1fe62fefeb1bf11e77b9ecd78f61bebe2c8be64e0583ea49f5a4ee45619c8bbaedef5fddb9379bdc1567344cd1f5271877e9d060cef6dd472d8beadf5b6f6880f76cd25506e66c59c8b5742a1e826ee461fd00ac81bfcaeacf59fda9547f62a93f9b6c7d7965be2417dfa9ffe582c1524cf4c13aca592b094c269495d2af4691be53ea4c1759db22598dd6fbf51b0ada3e1fe32ea959ff7263e8cd7a5cf643c9db4f74e2cb956607522ff43aafe3b87c2b4553634ff489a2f9429f3c747f57525262516991b218f18497b89143582d8a7270340b2d5040a1d8e80eab5fc5dc28630bd31630378b2daa10749cc5bad01ce8388b4131b749a54ffc7bc1c8e3946e795e77a7ddbc52834127bd48a54f39371d796830fac8fc91c7dfa9ffff4fea72a23ef2f8ffabd1d6604dc7598c09ef8722f46e69ad068c759cc57a680d71bbeb7998c6872b7be264f08395c936317ad05aa0745b774b6dc15a3db02aaed83551f84085b260d15ad78ad16cc15a3d30596ceec4bb468a104256c4d4bc118450154d6ac218c198c7debd8e6fbcd436af1a2e523c4fa805314f65eda6398f8e4b5918c9a81b343e718526a50bac40410b9a3569b8a89182468a20ff1ae20be2a0d2258e33347d5aa4b51041d3bf35d03446e1c841d3a7946228b2d878b23366b47f3cf384f68f568bcedf63eef2e934bab6ff8dae3dc3d1b54d70a95eab13a2eca36b83208216d64ea801f043fbb7a26bfb3cecb5f4f83cccb57df29e0fcb7bfe9632ff9fe08ff6d87c2df9105ebe477117de2a1494e419e46f1692a3d0c20af9778f2b390a2fdfaf380a2ba050f4402be4192890926c83924ff2217ce843c763d8635abe94492d72e6ae0f5ebee4e22ed6cb974cd269abc0df2b9dcdf2e077f8a3072644c2f032012f5f78e9c2cb974af3c5c223e0f7aa003236808f3dcce7e5b3e0ae204fe4e55bc05d455ec86fd50a85df3a3fbf59fa7708f4fb8791df3d41bf6142bf85e81d65f27bf9362fffdd85f33c5eaeb48e96bf593b7e6899f31ba6e52b01fd8a9b107f09e857e64ef491b73f113d0cf376a50fe70d45e37790c8c2c02020911d02120189ac068b8044360824b2134c401640de45be43923791670d09f233a58714817dd2cc7bfa804416f649fe30e414f2eefe834c748fe44de439050cf9f2f67957807f12accbdb7b4e01dc0f51401724ef21ef09f02e135d2eefee5909f83e894be5ed4d4fc0fc2447e59dc27f09f8a42eef23cf2580fba4cecb1bf533f5eef3309801880cf9203e79077998cf27c104f0453211d6792379229908b33ee9831f92b7f73e9f1424efef612f044440ee2921b7a089bc109bbc857c909ebc4bf820dd82f64979c82709c95b080a20911df24941fe851c80bc733c98892e097917f9ed00fa266f143ec801827cd2901c79dffc7680edf5017210c95b7f1003dcdcede6b01112c400423e29c84dde01f81c06f8a42d6f125e33c0f6499a4dde395e88cd0741e1871029216fd5e74c7483eecd2715d14f0485bc89fc10143e69882aef9ebf09cabb842f923f097c9db7ea51f8242279dffc90ff010b905b5feb7f26ba403e294847de45fe2713793879ff3c91155a79ff03c944b8e60d6485fa492dbdc20f104864f527117917f208ef4126ba3c79ebf89b89aeb64280bbda9137d0132100914fd223e4bdfa4b804fd291f7fd2002ecf8241d45f20e7a222daceebd3a707ef22ef2445af8f92422abbc47781d2d7cd2cd9be76b0bf9061b922a4ede3afe07e78900bd2ef2dfca441748deabebc127ed0802cabbc8eb4ce4017d92febc5bef4126c2abbc81fc8e4c84ff933cf8a0bc570ff44945f2def1fa8d8003c83ebe470f235fa4878fbc7b3ca59fe443c92719c9db88104864833ea9c8b390c3bc55b04b2c0fa12fb2a4880a7987f03613619d14e66dbf83251d7c52cee7ad63798470edcde191893c2399c833f249453e84bcc3cfc9445827d9bc4378ba42bec17e127d0ef2e6e08d7c128fbc797c914f12ca5be8833291cdb93dae8f9c4ce40965224fe89382fe47de3fde4726c23aa947de3ebe834c84b51525f9861e9fd441924ede3a2ff4493979e77cd0ff4c0180007824e0cea7008af04740105e083ce183c0d71b015d5f0434e181c0135e83afff015d4f0434e151004ff821e0eb8580ae0f029af03ee009393e0c7c65573621f78000f812c09d0f0028c2930082f02d10000f04dcf91140119e0704e17f8000f001ee84a0085574fc1040108e747c16d8e33f003bf80cf67810ece01fec91a3871e801de4e8a14e0e073c563b74e0a86e72d86090d32950db214b9933342a5d96ba7cc08ba227a1168accc430ea18433a02a327ea2e2e41f00e5f98b59a60087ff8a72083dcf717800c2237eea5fcdd81d4e1fd46bdbedf5af7f7b7d32f877787dea1dea1f7d5e0d4b2f280eb4b163cf4aa69da6f22dabfd3fe6a6f25775fdeb71c98c443e76e76f25277dae42a275fa3ded59685bf3f3cbcf287879ec4437fed65b7ef6b6ae09b37145bb7965b39d461ab82add043ffc1b90da36b79f8fec2b3fe76ca1381f84f10480cfd7109dadfb7088368ff1179fcf10df0fca8fc6e7a32d721ba0a0ea1a4770fed2f1fcfef6a8d41734eedce39a7f676ce59516474b4e1ab2925053b2977cdd9ee9cd372dd47ede39cd35aab596ba596e766023318072145baf8d849765ff86ccea64841675396a853aaac749c4da9c1b533d6a84c9440e24b2fd16510ff18444cdf97413497415c172007311248d45ded60a503a5e3c7ad59fa9ba678c0daac71c71d47fbeea1a7bf056504e24fc1198178de364620f76304b2fd2b137ffafae631b06727afedd7a1e84776d6f6a99321b9e6c9d54344f4cd32f2d49f9e0b008ac6c04a3f5955d79ebee61e18b5f701cd0ac27116022d9a7eedba6d69c61148fbd24518a6af69339e1087064a55d4a4a649a9792250d23ba5fd13e2416bb0e2b17dc41ac4870442dd453b6c03bce3171d9d32e0ae1d9f6670575542fb8c0a17e9cf8274b564e8ef32dc01d0fef7028940fc7d35821c413b0923e0323886df23686fc00368e024a5fcd6e8226bd3396b8d1295c6044f98018eb32e8c26b5e27d57ad37411c06c7252a9ef47c79efbfa22ee058aca332d289b2d23d9e0398405d3603472d5f9313533aeba4b5fe9cdbb553abb5524a41406da269bb9012b381dddd316766153da62c9d5e2af53f6e41141916ce9fba362814ca5a6b8352a07dff52788d2e05da8e76ddbd369fc7711b8e9d97023119de87b76fc9177e413865e3c57e29ef46c725309474fc82b0f7e1c7e9eef7e1df42858aeefc46c72d3fa8b9d11d8742d95cacc2dc8d8d4d8edf3ec74d0ecff3beaff33c2f8767f3e1bfdb97a3fbc08802b7a731185339529f4aa5501eca4bfdf6a99b9b8e4b7deaefea0b4a712b2f258b1cf0a0f352ada192525ecf5d3dbcc763738fdd3078febef183d03cd4e76d1c65904ef3d9ba8fd65a8bfa716a93427d9cb49112f51d977a0e0cba8f028fc4d57cbdd0d474af7d1428356a0dcebd14eab34b32305f120b96272c4f118c08447b1b508a1143ed259615c6a9df574b7c032cfff36c9a6653939681a6f8a4b5dfbeaf434d1d9798ae78754b8c8abe6fa78e5b6253b4d5374b270f35d90406bf86bd14eab3f9ed6db00d0a65ad27c9d0da63707b4bb6e7de7edfc671dc97fa0b1e89da3e0a04b95fcd170cb6b2f63bce43a1c8489d0265e79ed4b0f99247b7fb302410ed3950dbb204431ec926f9b45acda6c883ddc058e414d1476a2fe5a80fe5752350d25bae991eb481bded6d0fd4db03bfb8e35ee419e3c92034d69a759115252ae57d1eb7a1502914ca7ea7799fdc40d41af3deaef352286f43f2dd0d7aa9516c78183c75f442a98c9677069afb0be67c1f75a7dd975abbdb6bdf922e771f4a0d6c0646e9b8c485167d41d75f775316c44e58ead805162efaa274ec020b15287d7328e43a856decf5eec5db17646d529f427ddef6d806775de779b7ebba1bef82aeefedeefd4e755da740ba6ddb16b47d41db831645c6756797b878d2516f0fe89bdf438d7506ef95f63ad1476aed579ae7d92a4e7a0391dc5b9487fa3c4e4a19676bc71029cf438ad1c0f4e9c471aad1a1a9adad740ecda07354e85e9b60f53934b5cd41625f7b5b6de5b8aeeb3acaf91744238f9c3ee7b4765a6ba7967317f3aaf667fd6cf5d01f23484069e953bd4dcf46fbc71fdfe675ac9adadf638c3400a8f97d3ce2a7020f5d9f1e69929f905b1da998e950c7a529b2e80eeff85b4e9c3446ee77fded2798d371d5b75ca796df91184714a3e3c08e7b50e52e07631463f336ef3bea7160fdaebed77df71d8969f4f673fb2e4ee979526e997a2a0f23f71e146de379673b9bcf334a0c8edfd195ce4a731d5575bffd37d5f6fe1a13cfaa0ec7a35f90e713d6609c9b4212ed138a6740b169c30336646ccac03a389810989667a8dc50d9e0660365f80945329a83f2947b0fe8f8843ce7a850df7ffbf838df92fabec3a3607dbdb7cd52ab81a80d0589ed6a575e79af9420500ecccaa446ad510f274a0dccc1142947528a78e0e22b3d67cf8a872b7abe4e122d3d3dac2a8a2d776d184c88bb260f4557424a2925a329fdc8a261f0fde18f9fd0ae0381f8e0096ce0e841d383e018c1a8a71ca3f38f2c3c06de57e3f8d38a9eb32854f44c62035dd4d75ddb3d28ca67326394f2e76c4212a6db810cd5b4961951b5b2963a69d2d2353bb9ba8933e9ebe1964863a434461a69a431464a694881f183564df3136fe2e5e829f06ebc31c6e8495974851ec6a714ece2c47463e8f07cc54802a8e51849d8914b6a0abe9f63ffeb9c7326dd9c637fd2777777f707010cc8ff8619b8228c8042881054b10512d00c34e3b92329a594524a3ddf101d6b9aa6699aa6cd1d39e79c73c28066be21face84e21ea8ce05d1e9ac9ac4f88a3ab31b9a7440d271a68391de445cabd4eaf810d37136a58c7ddd017c8f323f7c279402fb0dade0faabe979185d7417074a4de508eda3bbefe8f7f4a3f7fba8e739a59e97f7b7a1e82ba3e6985c24ae75f77589cf9737f7f3e9139abe4b6e9683961cbe68fadef77d20121fcdfdedfed24fe83ecd51e1ef7779fbd7edeebb7d7fdeef3a9049131fcd650af868eeb9ec84fb2757c7c833ffdec8b3fddd40f9bb9ee962173d07a96bafcdf9f96f8deaaf8c962147ca7370ea4f064dbb13e9a465f0b4ff7c943f8e69d732502f556cd04329a92516d352c10d640a4c3fe608008af687a29f487d5993e2e0051b61a4133c3e6cb57492e833b57b5e8668424287c524ab34e0bbc84156e9131b0c51460a96e9adc381e401c7990e4a7aaf3c79a5025dd4917a6963a643472f53a2c8a1a31765de50e9b8044695c8d2c1c405a068fa51d3fe21b937fed6c202e88287f203b0464cbe17c9974f01a142a8142dd784414b15aa1901000240015315000020100c0704e270503424cbd2921e14000d789e486c569e89a34910a3280842c418640c01c010000c18333343b40eaca079ea411373a8415f7a3158055e81c354fe32567f3b997988169e0b612d73fb3b113bf5c122e364fe9743ed6869712f133c18b206823d7a961684d70f9af15f6352d16855c7ff404a7f6921179e806cd671a4c4972cac046321dcafc5b6538a0c3c97f03abc9540d84dafee2866aeeffcf2f159b74fbb4e3b47d19e304d68762a2b94a3415013303cd9c4465387e715bcc4e330bd514014ceb02a80c48c35acdced215636be8ac880cb0ef25963253f0d6bd9b95e6386050438a80eeb426d0775acd1fc8a71002360d1be1a1865a9a5d953ae243df2bf537f3dde6a7cd91993c7a2cc203cf68bd0b50cff10492b61a02df9d5e8e3af1ea9ab1a9d57a6a404347ab0b83e8a3f4a6d255817814e8ba8f1716fad58e0d5dce9174a1b0eb6175b4cdc2138b69918ce3db1d40033487861c215547c9ea5721aa4e39f681666abe3b875376b62b73c0e0607dac702b360e2e1f6d992e67906582360714b0209572c0b91a8f64aa5ae7069cbb5f119ec5d31ad498c93477fe80dceabec21893a24e192fda31b53aa5e2808334d983001c97f25b1a0adad898555b4e2fe8b5e53423276ac10ce2e06787324c55ff7533281c67d7f0111b2f3f596fb5ceb4eb1e326e1ee2f4345073800bb1cc63a8450558c1e46417865bc92d9aadc7e453a4f6895380466be0ef71656f611efb69647032cf4fe2d467f002d6824369cebdbb72ae5cd929486d752c73c417d7890d6682518127a38abe43176a3902b5f8849b1e21555c756c8c443e4b28ac7ae042362677e841d459a3de3506a854b3b211b0ea8bc728f22b65b55f44140c252c0d3bb6e9e4f6d97ae2301fdf8ae81646ed40356ef9ae54c6c9f0dd8530b5ad14c6e7a388a31c8e1f2f1ecf1fb68e7bc5e1a1dc67f162af5c5f5dda8629ec4abb37bde1dc43d18608c29b91edee03d195204f88c5b21fae113da21ec3e6423f6313823073cdc4686eb92d542c5730c8d7a8e8da72808200870641f9b706662ca4d0d5bf3649bfd52e8b51c2b669dc96511c66cc25b1535128d730ef694132d47261fcd35f2aa7a8172c2e937c84bed2c6f639235bc673c0193e0f8fc2e6fbb3b5ff9fa5a84c966bc237932abbd5fc10429b51985037c54237189a71cbe77bfb9e4b1a7d3670c96e5666288786f204ad76e2dfdd1aa393ce047f47c40134c479765a9fa66c867c0d3dd62411ad34b521ad6f511a9c6ae84d64260fd78a718f1937fe9d19b184cafecdf9bb30b8f80c52c432f63427fb97fdefe17310953f4f0055321a9229992de80c6445385d31ef7f3012504a8125b2ff83ebe37beca00a25d5a64ccecfd97ce621d05a25a62eab2e92f01bcbb6be972302eb3bfd71b44ff4698e94ba8030b5505735be55a70959bdbb59600c793ba996ca2d7e294fdb426a2ce41dcf7ab36aee388d2dc6857e904c0a0bd2ad313a90daae5a545763e4dcbe4703c996a06285992e712a6b1f2ba0853af30942350705222f5757a8fa19149209832f6c9e7bd23e73a06499404f7e861af9b72f99770d12c30b839a6306a08ddcc1ae6181a039fb3fd4333f768e3a221a2c565a58fd35afe72142b561fd09dfd232f33b7e842b9dba04c534091b742dd95bb2a576bfe55a4665018f8b198ce6f2431dc35ffb8555522c6a84fa3092169671a0a88328a979394302fca1541a5e760e7fd46b839236e59b207d1b6644284a4312171c9e2c2d524ba38d0ad6baacb4939d05b3b6e060b5d17ebd611daba431b0f4377f126fe76fe1c8eea6a9f7db18a805e2985b409538bfe26c94ac7c850f93f90eecfb6b4cfe0fa6b25d31c838a8a75e2c27fc2700a753ddfe57c76c2cff1fff10329d7ac26280443d67fb0adbfbdefe3f40e3f7ebaee1238a21aa60b7e1494e98a3cc6b0f45b8287eda556d2389db39a51fd2d5ddc830954821c88822b7aaf82ff6ce923c9c0367e40ec4e57387f3936f3f888cfe1b778a3e70fef45a3607be1a801a1e361290c15e9e3a90a7a1c5cc4e84e78b7004da6b8cbcdd89fbdb6e3c2179458003fc5ca2c701f387a173ca5edaed9c0402126ab16796d4b7c22f985ddc1aacec113af10a0fcbec5dc0c610f2b66f4517d3bbdc6598a4cb3ef90917d95f124ac5c6e32144a6fee24fad77211eec626af2b32bc83a7d72bad75286be15dfa4bcfead4fc12becd1e51316fc2bc2c5d4a49af69f42d24e1680ee12e0546a01fc22dd4a3c2b6f9ca4721a17783f446a6d71ac68c2204635482ceba36e95e83b3c11693c2af7f499e9be762817c1f9b08dd065cb2c8dc3618a5ff81ea7f7adb4cf7b9ac54917b8d3201b41e3ed181d9457428c9c16cdb61307dca0e34b7388610bf92d5bad65a1827f840350fdf9a391a32d0ff28f69c04e5dc1bde6e1d799c37fef297f2440ee6093c3382b0a9b38d24f83fc8d0162708b712f9593eb9963e142d784e3ee6c397fb5029b2935022dc3bf53ad74881e7881cf357f7648da321325311e4cd52ad7a40b1dff98e0acda01e450040446fc875314c74d7f502c06d6935f616070e4b96b1bb53e67633bbad763a6b950232de14746ca55684c724cfd5b9d1677075645881f5c344b8f1d4101d5e81c830876218c38af6c17c91fc3ce64964ad588a88e158e541e1387306c99fedceb0d4734221b8caa90a19968740db4deb8b6d6853f123a28dddea9c16c434d0477a0b3f789c3b6b1a1eba64fdac4812488ba88ea2730e1b4b0d62f9405618ead415066c188807809fd21cbe55a2c6a46e37595803e2fd5e4db387c04cd8c77d5053569b8bb374380eb9043a633d632f88158d7216c6e9df7165285e4790f6dab0578a8af8d736e42ec354f0a435535252cbd95dff79220a144925ea8a103ee40b0c8bf84f723dc9acb0859770a3763710aba80695fa2a33255433efaa0a02ded7f5153c7c5e24eaa947622fd18901bb819c5506ee9f7200631ddbdc38e0e83dfed239b8143904721892d49cfb104ed392d141977a073c02e53c19e3cd4b83ee32272eca79d9a1a763c925328e05e7d5914c08883e5625eee4607c567bb336ca26953345f3077a86326271226167843b2055f10cfbc7cc71725b9be7752d6851721ad7ed0523ec5af9c2bfbd848b06884c5442147716dc63cf30096ebc1007108449f4c6988452b087db4a60c81aea93efd6d11eaeee81ea2bb94ec0214e46f150f39199a4f5f28c6abf14198defa338e7b36d29578254d603fa3c9fe289c54fe61e0a833f73d322709b7f30e6871246799c31d39d733e4e108dd3a10a2db989f34684aca9ea90fd9caf51da571c4c528aae66241e0129d3acd15e9da4e164bc378b0f6415b091bd4b17064cc6e91a787aea7fb947e83c75a5fa9b3ae1b34f3abb3ff9592714d134d769aef68050a44a88b2b6521691b9ebef47823b29801654208562299d8295134a017b26210ae481627ace698744ca6e664510260c8b0aac63ccf3f85863ce1f3466131e581446b191fddb4a1052fc08f824298deb67f07e768c2bb7e9b2ad1459ba95a646ef1c64e321982eae620b55fec80c46322eb786546cc5d0653a9f32901abd9cd9adb10dae342cf772a0849d73cb475488991ac5736a2bc669edc4dc8c9440e91a86a4ff52e670655af8efffe8b7405180019f2227508f5b188283a21c778f6f757f5738028ae51adb643da9d5cffd94439e07fcedb0a3b385475fef4764eecd820d68932660fdd6cce3a203e7af54e616938e3b0f6c9a9b62a5081146f0b0cf2ffe952adf3330c608398ec1f0e13e8b5ad890e3c265183a58d8b99daf4324692179ff0f1f3117d8c91a62259d5d67ebc34a5badb33659f1960d472733399da66320447b7f7bd200d12e09e05d409418053f5d91d00d8ea8f9f4674d2d4362a8a45a4c40c31798c2d4097220e3c68226062f145c7f16de80748ffbde87fb8cac7b90208f69181be632ec856ab1d77d06ca0fded02f2d3e9a7288d3dc6e3c6ecb2a6e993b972c193892a83c281a951b3940be08e2609df2290f8a227fabebe47de3415b07887cf6a0c16e3bf0048f8795ed14fa534256be747aa6e2b33247564c8b01f7aedd0dfa4b1f76733f25eb5293f72c40978831e93fe15476dcc8157538828c9e63d76b623f3fdff3c19c793ff1fe86806ec662ab907729b03ce48a8bf44187c905ca59737e8f188fe73acb40c3279123dcc7bc3e3417192710ec71f1a0a79a33c2b037d0ae58622397fc398ba539a5a2125d21884ef3ec4373a95bb1241df5ebf89bb85a8e4091a5766ce9a8d0aa2d4295909f58a2fc9e0cfed8feedaea896534f22c430abf0e0d8c83c5d9e58127aa9aa4b803a22c729049ca98f94f84dc0266a4db194976659ea9985cbdcab1e9313f00784bfea99281c41d5e0874c326d76e30f41dc7f11d72983bbb5a5083618cf036137cdf30c4fdbfd1db0c03407b86188407e7c12b62feadf018bb8fc4b186a91309648ee97604f483f528d4900ad31c59751d3caf4e6aa070715eca56cdc5ec5728e405d2f8529c9354c0c3177232d64e174a43ab9d4b9e1045a5fca2baaf4ab535f5a5b0c077cd5c950b20d07ca8d916a98c296274a2c46cc6dc51d93985acae89a24682c5f2858e4c052bf7c9f2c5f6133e3dff6c51e499686f1092859e474345093482d4611b1cce760dfad2e93c2d186563e1ca6b57d06dc7927161918cc86a8942460700daf1319d1585011792b703b125067f598ade0ce686db2a31855612ae02073d891aa313b97ee34e5d842e05b70e545f600691c87fc864b04c8703f5e5d18ed78f25b85399c505a3ff119588e615f63ebb19ca80df2917cd24e1c88ba3b4dfdc4056fc1d995880ef807a9e2f8b1a39f39d5dce2ea46a5868bb8dd88bfb01aa2b41572b30ff05059b367e8cb37172502edf6b42a8d6c650bb0272ddd16e62f17a8f506080c30247c672461e15e710d1919d9a171e692e1ae67416d2ab0af03e2fdbc9e630030164bb3322dfd39adf5e254e69104fc0a88f7bdbc9cfc926659def2f9d65ca2ec3e47a1312ec03f6148d8515d32f22ec7743fc652f77e006a116c2a5d0981226fddc6f82caa94a301fd35349a8b6c4e6254508da5f20d08de3a883fd100bcbd85607e662e8e63311308ee288950d751e091457652d1307432e81d6e843b02197bf72483e27127b019371b344650dc67b30744035bbccaadbfe11a2da1eadc147c8d42d63fa3becfee78e43ba69a8fbca6437acadb90fb15bf09c6f6deebb2926f2162892091a60ed10d05f9853175027db03e234bb6372a421c24a83b4983d27da6559e8a3604ae5e9c92a7b23b76edcac62e65b2829e0981021e35376af9e09a8ff62966576cc49fea9282185747e538b53f069b4f8d2db3a49aecf95818ad4d02083766680c734aa6071b4fc0d1f971ff3f038da21d415919d41bdd4a1d0659bf515846f11099687aba822f52970fc53e407ff333b5e1ede3a5ec99a548138ae028e0d050be137577b97621018a7a53f0122b9c7ea47a045c04c946327fad53a660d416e913c62bf7bc58ec93f6bdaeb0f068f20200f751dc3812ca003afd185ecce5260a64d7b438704f72e25d2cf2f54858a70b4d1e0eada3480944c772cb25bcd449dcc1a941320f4ab1557e7cd47cb60f9016d49f47529fbe06874e9e6b84d8286804f1dceda9ce718539c09cb654009c70ab09352e15e54532081fdbaba52510c7edfd4915bb61f52cad058320734d24c9534024f6da2beb53c7132ab05204f1aea45ca5f1bade3870f5f950d40f26ebb868d88489f4e340aac51be09423e5de01dea2aa4b1fae23ec0d91ba032352fb179112d6530d22edfa1e8b29cfc46721b3956296f489532f626ace3d138c3dba10f9f6aa2efd2d0766b0c6e59a49a285d9c03217edad4f07ca229f43ea38009228e2c5e3340766990e7de0b22b927eacc100b1e6305a2a34035aaeb286001e2b52500a6e9b8bc24a8a9faa1b8f4946c67e1c3342a9eb08d912fdd9dfec9221a744860553eaa661f25759361fec2210878f2c429969357068328fc918eced434e64cb1cd7ec56bf06d753a369340147eb90647ca9aa4498525da14daa649635ac31ac8f883dbc6611b2e99d1176259b1327d4b6f4e2845a31e52caf2eccc0cb82b5b1d8994203ee28a77116d46e5abd585a47e4dc0a45efd7d070f4754e81a16a66fcd84b1c19e6e6720e8474f7e85b4013932bf0d849e67f13415ef28ed4e7b337957cdfdac8d8dc4c70d0e16fc6800e24a7359f47517f06a8811263264c722b4ccff38c0ea58cb6eec2f1a057552655ef9d01574252a48d33e2d8e8173a2868b2a5e9a7dc8cd469478e30652263b27d0570d91a37d21f06ac3ade92f64efca46daad3e26c4e978316bf23727687257bf49732719408ae446f45f738176e3097908b96f37f779da0e54f4c52483e4276ada9abdb965883ea2acb53edb061efdcd1e5b1f35a07bcdb843d5cf5c61e3dc646c2201927bda56492f0db2e4d0d720d1633691f8b0b1acd1e810963fc954a6fba5e98e7c2e7e1839f75434cf25a5ed46b0e6b70797a0b371c2388663b27061fa2fce8c60c0aed457d668d907707d776193483cff2e143cbb4dceccd85b573ef16e1c4af47abd9e84669e1b7a113cee4191198edd3dba1da8a661539c037eead686a051f2e9310a7bde4eb6170c06165b5304c85dba59d75f8da23736c475239f6c9ae4a2859d8b5afd0909f207b732ec62b9878b62732a8c1e2699ed3c016eb65511fb0abd5bfe6b55d3071f30433d1850e101bc5bf37d8acd90421bfa5d5d74e0b80a1603fc0d30770dd1c5e077e206c93d8af83b351ce878ae241c39cb70f2ac25ac70559fd7cb690e093d67c782ab40ab0f02fc4b67aa406871b95d5175f43ca0c1ed827d9eacdffa715c8053607ec6c42d82bac1d23eff30f39ae21f1074001d3620bc1f8951794f3541c308d6b0bfd8ad841427b9385e6d60da97177ec8ba81320bdf463c2e8a8fa43818b913d7a3132a6c2a116574cff87d7363f935e1fd397157a3f38d06c5725bd20f54cd4375743445997132a22271b083af3da3d500110085d92550dfcc7aa5a2f561573baeb49229c4c16f194e4d52a9516f04041eff082af02a077fad4776091490b9864e0c8be244ebb54fd5de61acda4964a1b0a981b430565346a500df23c133c7a3e25f510b22afcca279dd1ea62d2221377a6c0f3a227b877ebd71da10920df4734d1a8022c6ba266f3e34631e5ac8c24ca3338e706f646f24b3614e5113a1c51595182087d2c905d8e70c5f21ccecb649a5b46965e5557fe42dfe5b2dc3f090ece67d5bc63cc2b3d9408ee5e1ebbe2930d196d65e708e333e9ad06dcbdd1c4889a36b8225b4df7f20223393e26a2b7be90d00e26e078f6692478798fed0c0e400e751c32a56d0a602b948162c165f6f63e5f6fc61b19c41225c8ee1829ca954c2158d3e23c69b2f8b0697863bd898f23e336431f2fdead1473fe0c4c64804619b614a4cd447b9ea41611a7a217fae260457ca33822f0973e60853834d300d238ce205aac40d87cc97570a1a25490644c21201384240a4b8fac43f0a21cd930a11b1886616315fb81df6f0f127709c83acbe4266fe30ed0b7ae1d11b9bdca33e634fabaa4fe34491a5583bb72144e05fd06561cd05cdd3223f95bde352b1d8b37519db577536a4a2eb9722c7b305df63f69213e57c19c413f814cac103f94b970c509ce81de1f55a29edb529dd91c14a83c0d57f683acf620ab6820f5c1a450fc387d5c27d47bf5bc066aba5ab2723d9d6cbde1a713deb0778967e48896ca2333ccba1f026a1a69c09d7ff0db5604be753d2375a4e77d0fe5f7bd2cae027d341beccb9439333eb04314d6b4218708ebddb8ffce4e5121a4055716ad0595b0231cbf7f0081fecb53d99a1ceb9e5201134b43ccd4bfc9fbe88230319fa0d8f65bce32961efe8b910f414042220df178cf1ff27144b6736080d513982a774c5b3b644f28e8a4d06a1831b7aac18c5ff06b04f87a4c68181eb83a164cce65b2649867d9619d15deecdf9eba6dbd9e6794fcd7c18ae69f3a1e57738da40379a56d77ec4856c284294cb677e1c55f988a20d236e2de787d5e5a03d05b2003327ca0116314299dc66c798bc4c817d84b13c65cd6a98c4f67966ab97ae1bcaf136de59cbc9f4a8b63607cb268d29df134e2d7891fa94a65521b6095b26adca8a89281d265b83ec51ccdb0d2149b71a868c587121815538f4a16fb4a58856ffb2260bf0b81611f9fe6427cbbca665dfac3eb4031dce115a0297c69b621814758b5ce0d3495b6f5bdeafa8a9cae189d66831b8c2b4d285e176b0bc02f8184f7ebb9280dea8b64dc1a6edfad3e7a0e930fd6634a1eff84264bd47c1e050d0e7c6ab7a145253e29bf9f42878f0f7186d2ac2e9d919403c85c71411c13db3e9c2a03d34190385a1a636c85f5183f31d9a2455a7ce725cd931c7ea2df366194123f09e3f6b445b320582b2925f5f3c77d9ad941aa63dc8defae621d68729ad9f0d3446d70feccf7baeef73972f39f5ee81519eb089915985d08ea60a166f9aa972c5bea2baea722f4468e85c725b3c20631748adcc1b678fd824cc586e9255173476a642664adff449fdde1516f5c0a8691c4f2d27c695fb262e51b7699d9bd38073bb3e54bb3b7b300bfb15cb4d3ef387ca69b9fea6631f0e0bb4823b5897737eb15f9a799976023cb70cc5cc1419396fad7e6f618bad3fd9d216e8e3c8db18fffb2264c4a7404f1d7d0d1fe3e3b315951972825dbda6e97e26900c9910e2d23c5bcd4f22490e8b7d90444bfd73e9d0f470806a123e947d36553b11b7a2963dbbedaafcce3ed019211760553aa3f06b29e25435afa89f5263233b76b019edfa964a041bffa487cfd1438dfbc92daae2ba88e8a3ec7a413d7422b8240d0819bc3f31f69bbd8715baca7629ff8afb1abc24362c5b50cce633d6ac193ba0de3cd649fc1f981a760b1893b135dd756e9ba404c5d179560f004e27c9c2011ae3c7f08370a55028c43a83ba9057a57de5a5348d326f27d9cd67d40904d3e40f85d9923422049e1b114834464a5411427575d94735912e458099f419b945f8cbd6d0bf827b978f4c2d25f31a42334b92a5ab0e544f0787351120361070a3b1077216381a1a3ce5cd3f8357326fa1322773d81b31696f98b3ed700c5df1f72cb4ec094e4522c65e54b5907023455d6615d67c1b106fdf4946d28e926f031c9cbde98db1e17cab70bffdefdf67f56f6295b45942ab9990ea7439df2d9c181051747c03d4ae6481751f7324e2614a0eed3a7225aaf4db4eced89b453c06e6603075774aee161003fd2b361edc64c566f1159079b09cb426c895f3d685d0400c240f40ba8ad7c95287ac3b8bea0743fd6183d8ca087206f1209a204d574182f9281dfd8a78660f14988f23f85c8bc208bc9480ec46105c06fa1eb27bc6331ac7951aa94aef9ba5adab7e5aecc6e02ec01c54a84f8884072867a8c27ca76b52b18df1b3a1e7fe91430c303b29d75ae9fc07395c3c50d879d286bb585151e3d52676039b2ec1ab1cce3f8900fa89b78ad51f99eeb9cd45c1f9ea625886654a167f7d5b57e14b6bb685186f40438b6a1382def289e9fd2e877033ccc6982233c7087f1471c29046dd26a5f8db2e9ee07fd8bd0668346b003f98bbe9b8d0e2d3f3452d060e9ed77d2b22d1e719d36acbc817675f16cbe8afc3a5f617a9314fdbb504ccac592e671b2f72284078753cda99cd560d55846b89952bb48a969d2d614695dc704d9109f56c6aafdd62eda573e269f713eebdd544f0f015b60e07893b7fa634758fa57c088aa34f4224cc408ffb28fb1ede1dcbba7ba88e740ca186a2d541d11f0f69c27a73536c8c1bf404da8dc875a2e0469166bf25971fbe0aa63aa2781aa37bc0ac26eeee0ba7f7aa88efeaf22b533b0fb59025cdefd2f3d94cf87e7061e90542967f594eb455c14d24350062d6a7a720bb353790227b710d9442d9e2f40da77ad47d844b10f7da5cded178569ceedbd6346197c942ca7c3d6f8f3fc71499bd883980af57edf8381e37a876065d278006b4e4016ba821dc012ced5d88904605a2867a51d82bde3683f91df1135a9ab33e536fd6c8b1208cfd7dc2a86da952849a0bc5b1470af143b60f9dd905ec114263e4086fd5be5df3c5acc49e978b40307725d289242ff52410fca6cbaee4b02eb9b90b50909a84fbdb63661a0e057ad8fa5472a7d08082609d47c285d0986efc639601925379525faab53243550c2d6a5c780f10ccf66354062c7988aa07c092eb7528fa9404cf8fa542cab18fcd4800388219006c29679d2c7f202e581e44acefe237e68c2b5b7157cff28a56bec38035457a09c635bf3896030b27ecb66cc4973de2a93113f56ab71e3afb815d12cc095e777fee1d30aba518fe4f5485186d623d128ac1f2d3e50abd9e178d2f233c9648dfba742c674739522ce5c68822e012cf419c7e73ae2e69b0549915cfe5b478849c2e24057f71f11e5b50e56606ee50fc7ea0a40c8c140c9dc3efb250d2b25615057d9dcb4bed7ec33a3a88dbc013d8433e231735103e9adb319826b6a36c943a2773ece080ec2a0de6618c3cae96d0fc80a9a7ec09e6a3a2bae7718a3d7ebce40ae448590763d13464ec5fb63fe95881fe655cfba41f8b6c8d41dd1a8070d068548555167b8cc80062cba75416e7e6bda10d8ce21dad4d4961cf24110266ddb630a4905e4835bd2b0c049031b92d96acdd7b68fd9576d67f662b60b0399e9d28f1abf07e7d6e801aaaf3f86b2ce5c8290ab14a8af5016032b831ffd3a8c40611b51ef3bc1e02dd4e1f4f240104bf94ee2889307a1fb3f30ce6f36dc4ce9a712630d6a012cd9b835b644d8dbab899f48e1b083e124173ebabe2df4258db5a2a70b3c63d439430224a30ee9073cef827b474b0e08aa7b402098e402414110d1e9585c9ae047f9697bafd00f584d6e0fd899167e5a2951e782d1946f94fc6ce6d01f572581161b08d8c53f6d8a6f2716446baec573d3a4871dd2e5f0632189f8ec73988c7617fa2cac9a80b617fa042c4c4c6abb606b5011b43118a36b9f4aa36ec555849027939d15175ab5082ab118edf1e485a1f671f3aa64ac3b695e82d2137936137d844fc1deb58aecb54f55485bd1417e39ac68a518fa610e99ae440f9099806bd0e21990e1f7b5accbafc76920d0684ce16bdd336665060b7662c929734f706b2a7742cc92499056de43c40ae7d2a64fb5a8d086c72ee43ad4b5d16670d92906de02c0aef00be57cb2f1d99622c3adc41e7c5f33c76813093db2e55e04231d4129adac69b36ccfaad26c49764c854e37833eb5304edd7e37545b30c101777c8e93f4ddc00480b563a4f466e6e2ce3170a46d3dad4e916ac80f1b1aac22b58ec1c8e28f3d20c3cdc173aacea127ff085c942e283c7dba50d56175d5e3eb2acc04fb73cadc500a33e85ff510baa5cf6521596f8036d133e9635788b94dca11b398263ab0f348d1f32a9eab8a38f5b894d537d1d1d917377582bdb1102177a5615f2fc4963bd700f80d533179be02a5b691e8888dcc8d37e8e588336a3fc989bfe892492af2a92e901a093402a2092c21bb919de6d103c1ecf6e109ac5c4ac7a8e66b4a3054db091d41b1ecd4119dc9f40f0d7d00343343035e72b44b100c54878d519abf57ca9154bbdfdaa037921e9eb8e7a8f8512482609dd0ae5a99050017652697a77529e53c334a71f040921bf293600375ecbe547a13c29088e4d4bbd5c53731732801b7298036e30ec4ac7211b36c7f36323cf0ba201f6d4ac50c837e6299bbbaa384f17e3b469fbed4e2425ccc4c7dbed85943256f9d30106d6c99389894aacbb87e184ed9ae612a38f24b6fc8574444ad897b3e98718ed54e5245dd2b14dfb49dc772a73a3e4477a74bc15efd4e88c46608cdfea157c318975b08b499a3c2ec4bb2afb6e2acc09e454b5abdbbf3274c3d8e22d3e5a880bca0cc1975965461bdd513e7d179536408197c10b9be6b4c7cb3fb39c6821bef344b31fec7bb42dfe78bde77e36256f2a14cc744809a6875dd8998e6db09510036c362c2c24e277d1acf0ab72d328158a6f34b9bdbb8a500184cd4614deeac719dccf416838a75dbffa29ee45bd05c505569d8d96cb1c74f866c96da4b0f344c2d71f251104c684cf930b7101a6b10ff6776cfacc49bddde43edb65607d971257bb36d00a67e4cbfe756fbc10c08ffc07cbfb51c56132fb0862b94c37cf1fb4282e36453d1d4ee38a0b94cdab63e0bb9a08956b8cf03da543ad9f866a59b590053a53af404ee35a26683a8811792b5d76df945fec8690100aca8d2d0015b618f9e6c17849fae1b13fa2b7dd1ddf101f3ca6127e647c2bfde3d96121dba7936e031c01c70e64b8cb0c2b04fb4503ff0fb0c58cc0f6579fc5129368156a6bc534624c5d07e4eb20a4ac1e15c468c0eafd8d544d55bd20483b36096c8be5bfb62b03c07e1d5f98ae384a29f60af25893ff80b8814c573ad61ea9f6b8f557294713f0fb22143554280ed3a3b34d4f5d84474939b7d2c9f7cf205c4ef228cd34a68768c3b5017dff75560a52b948d85be50d4978db3323269617e53554e369ebac14441a683f059f055744b6e6170171fbdec7755f7705c79fd7ede47f67a9da7d4c6dffad7ec4f64b83192273203479e42b3bb004c91c19df282f5c57944a3a5cd2722699ca258a156c84a58acbf890ac0fec296328a1d65e18f92229a2ded082312eb29d1dbada4af3f07d66e7670f679d2a2d71a45968611b3d2ee4a530c76ed29d3c5bbb42199fc83824d11ed71f89c38a8e699ca745684628aa2f1980d547d20fb4beaadea7fd83ade331aada570d9201cdf8ff4ccbb7b2cb518fff63b69ea76b957d9bcccae01cf6f64fc403ad6fe91e9b8a766ead699a21694f077cab4e231424be8022c6ba1b2ff52fdee1452ddf9cf9a4fb42483895dc8fbefbf6db171bb9bf4eb0270eabc66b1262495209736bcc67be467896dbc27e95712d9f3f209695271cf6166e3c8b03a6a6ec3e84265098769f1b1bc5c23d67a4b3d34d781ec71f60fd599531c520269cdf52401ebb17499cf9d965af15c4636b260ff6c120b0ba951f82e0b25c94a8088fe014418044b8c8992c757bfb20406d43be1f6707e6bea83a7883cddc26628d136989b63922f3cb25916ec701ab671eb1e91f708bc9f28e2806c8085f4c9c82428f8859900fd55e5af851f9a8a12753942f3f13a07d704f4c5bf0da5ba5a0e523d6c046233762632605fceb6a320c9839714d0a1c950dd31f39dcb8f71d1bdc1041a100ec28a38563492f69399c26fe632b03fd381b7aa2830b6c1210b24d0ad34154b22082b82696a025ef2a14a8784b9cb050d62b1c4bb44e7ae09cd029b7822f792449a2cab0ad0eb80a91ccc70245545de9e2ceba97c50102264833de3498694d931e6628ff6edfdc900f89740aa6c919f8bfa439fd21bdbe73165433673a6616a4d6b40b56688d0e3a0c6e0d4f4070a9337c99b6506e1e46242bb5171fdb6f310415a5bf466f480ea6cc8bb7b220308a472c999b829285afaf33e0ce46d073bd9e3ea117c5e07a70ec21ed7611ef58769c298cb6c2df9b23282b427fcb91fc62fe82e97cf3d7846e5abe50f3c30beed49de6a66c45e42c013168e2743b85d99184f982a2677c699f99f8f9d2704b1324c1b2308cc5b288f86672b1b67c8dfd969ac24c64efd82600158c6b50a4fbeba067f29270155aa2c03fff3e8ddbaa72bd5dc9dbdf634ad63a0ac04daf04dba889a2ff327eeff1d4f697b2a86942736522c03b3238aeedef5f42c6f32010d4d412644c36a8257fa3750322729fd01b6c79a25560443d349934bfe7f44da4a0576929464bb5e356f7f16d113001b9b90ffd61574d0a643197a36d7467afd8a20ac223d0db41dcc1e8991d6f0c969207b593ac3749f89ee61f07a7752fad7962a1a26d622d501cfaf6f29db67244294643cdf47a190d4198279aa348a07c07a1683022c5948488e957244752264b46a813ad6daea984d13ff12948dadb173b2cc3a34012a03d0aa321e420cb1cd8f030e4308583411b885dd73b10ba4e03910db10198f94191d2dd1919c5e9128a800cf050fd8d82db0b3d8d7f7911db4445b3654867a3ab0d0bb2126ba3235e5c1d3a73c32794a999d2c90131c2ef344a23e52ff8ebd7165e2c62214a127fff6d28dcd52c214a33934062114ab12cd7d2a7fa2ff983e900cd626e9f9f15f0127307b02205e4614f9bb7c3bba2b2989e452b53277b1d0b86ee62038cd4c83eaf1f6d1c4ced27dd351db6e0c808c8af606b44c54c6368a627ca0263a700a3dedf47b499d2a99b20d88521039e2ee0eed524eceed01d66814b20cd43612cfe6a388fe223af18c0c28d253ab7cbc109e39acd70ae146145863af286137aa820853277cbc056c75bba48b8fcd64f901027d799d0d52c833c66388acb9e65b2dfa8956599119c064fe85fbd9cf651dd0bd3a5827c5d2f88fa54eec05a2b93f2ce6d547b8c21e916fd7f6512c8eb63b907c37fb9c288e13e604df58aca8628f47b1783c1ea85b8a6887a651810130bd8ed65c1b53bd705e9bd6b72ea7d55938e78fc5d215b9a7d1c9b3237876e4a8551953e636c4e8e94e92d39c2834142eb927a186305977217f157c750fbbbb8fe054d3146d99f883e8695874916cc3b41139ef74fe2e11be3ee34cc9ec7eefb1d19ab4c0e6741e3ad4a0d642e12679b48f907de26af3d160f0c925dbcbca36f8c62425f7bd7ddd549d2b7745582eb00e9c60ec2e5c1b6d44457c6a0e340934f4c90a26df0c179933dfacfe0e8c94aa1cdd7e220324d24bc7439e078efb5b1d529cb6c2d48756e52243bf11d1401c782a1b4d3c100872b32088285c751d8e31588a6b0e48dc91b46efb78324418ed83fc543db089616b6a701d2cfb16c8626997c1662f9c745c1816300822183475551921547609e0d07ad1273886d8b34160a031e628426b1b8cdd96a0349baa81b728e80302d985519847901409e47c6f4470509cdecbd93e3a1bb491ae52685bd0a482a26cf441d4546a7203854c74c5af04e9fbdcb81bed3370b1425bfd5d8addc2b72fda93f08b6681540d71b3265715f88c6a588a12ba0f9681de65fa550007715ce561582271248088c25d1b1428908c61ee7c46118e9524a4a5649c38faf27622ab018812940b57de913bb9c88e070cc3ed39cd7dbd873f6a5b8d39edfaea46033d2538947c9307c0270d4ba493d830cd84112d05a38c1dcf2e231b9d0ac427bb27b16dddf0065b48db0079df21f72559bd0a7379cc6224f35510c1129eebca4e5874e2702323a8a1686f304ec843017de5b44fc6413385847f7b9f162473cd17446583333abb9b946b9779aea95c250fa5dd78174af9c3b8fdb4d18412e09ea4fd29959d16e4e8f240d541d2de6df3153d91abb55af8c8251c212ccc882cb8928096b85fbf4a7412a1d8af7cdf9701e677a28a68ad138313453544ae1335fb34cc9e89a0d9bcf800fd62eb49283127be0c9bd059ad64985f337260d54530576d41131565ce9e8db98f666f5e46553c2b2104ed52901194da3866b3019c968d664a0b8ad064e51614a8089e15759bc81541a6a347216e2a36814260fa66bbf731c4f79521af134d15f01a8e45327eedf90f27f8758969f94a573ead6dbfd69e4906c8309248ba802577c5ab5726ddfce1620ef52230f2b5693b54d28d237f9046f57420dadb5f111f37aced30c4b4d52808af318afa7d29d4de2b0784803760659202c8b378b59efaf5385434199b82fa7609248f7873c51b573ddd96f1b87488fa0cc24ebf2e65dc53a3650ece109045aa9edb601f2b0fbec20bdfd2df163834b11030d2f9e91cb4569485f18be27bab2085f17ab70afea8af990b63118a5483b8614986c0e149502ed955fb858e49407f2681caf6291ad107173e18678b668a3bae97f1e52aab8a6919aaf532c6ae88109114338914c083692a0a71561dbd1414a7cbea0a3f17c7e797f7c215197a3e6d6abf5eb240a2e0565eef22256b13b9b5db65eefce834388e7ff9dca30bbde1880e390d533be1a3044a9d74db52ceaff674841a3be82882c48c6ce3b95c7a5181caabef92b3ef482c1d03ecc70b852a550e621b2bfcee9100485701e9df4b901cb8c0114e0a2d50ea52272fbb2c2694be36d59634aae07ec42afa2acb26624787251d315b8d1aeb5c16662a1629bdae25bbac9cb943eeb27f84bad5636f9bca3aadf8401bf97afedb26315632c9640dae2ca2c88ef20839b8bd7c83efa31a749988ea897842c35ba66a1c4d40a551d652dc900ecefdfe749d033ec45727c9fe52d083403140dc507e7ef8482f1fbbe791004f435edb045d627f86de1bc4474ec18a331b7f18c76554e724fa10ff20a47a3495898be773f4bcdbd5ab44e50bcbaa4aeff036a048a3057936e14f96040656221dab8dddc468b4fb1c2a5689a7fed6466e01ed1f273c92b08d9d8c5efd84a11c5f7d51da960e5466d9abe1ec4988962abd9e5140c966574d7030ab8f68f832a18836c5da52589a493dccd0ac38b77fd806ebf0858db00d891640c4ff201da001147d67ab1cbe8d7a7b9ec8f5f8b66ab857bb7e13d4fb758fef4af9ee804255bafdf7a687b582e278c2061f320a17d4abdb89708169bfd9ad1a45c432fcefa0610e8eccfaab288d2c9153530471e7c350eba6a0c0f807d85deb847df09704b000ee855a52bdb09365999cfdd5631e4c7c329324e29b98c0fc7cb2f45b666b1322789f024a0913a4a98f646c5da44740c8e6d7859701f47a2582c77011fc424c2d5a1a615e2887e4f8792452910143a7eb43dea250436e1d533140da7297a291913135db96a81e7544c7c6ef6ae18cf92c301db82f6fdc52cb19ea77128e237ed22365fceaed0a7256c33c6d4389b51c1158b73bce170cd561876319854e170d4b376e9c5b8c4c2def56c1d8753d053d1ede3a8546c58158c8b47b117c7e02dd151322dcf9d5b2825cb20ebc729d2fdd680152f91d451441775da9174bc8e45924237a28a3426547e028ccf6f7abe9a8c674af3a2b60ea89c77f4e5b7046d567e1a2ce19fe09759b6aa2f0bb4737a6cb1af6a478a9206402bb42ae9907a053bca24c340c26a846143968af0a6085048304d63391ab8c38b18eaf60e5c8690713d0e88f7f32822819fe08242609afb159031d0eee7bc61be5354e3a962f44df86ec5c3374eb336cd8427c831db65084792ba95aab226531d0856f2447f54e1db354ce46f168a69c598d797701a45ee476dc55949c372600ee40cb7634ab61da140b010e61743c81ebcb8bef82d4867110d545f43c584de29c19e53c87f477f22ee2c02b10def93b5bfdc76d3f869c2967ff304f1ca7079184fbe1bf765643e6833e4f2c6e04d8fe2c2c59194e2f18543a8c90aaf3f8a0c747e905b79b7a8f70912b905a5a68c1eff23e8f219bd949d81e67bcf0e1582e5a80ecdcf278c6c878127d7b1cf14fc90fb4a3ed817e1c7b45a6ba452b0d2cf3dc9b6919a4ba206027823abe65887bf5606b814d49848e28f4470322844535be84eebec4803c8c92c49d562858ad9c410330d8fcec67df637c75fbe6efc5625022c008275dbe62e3c6552f33c6b05038451486b1989ac2467c4f13b8261b1dde81c36674dbd5220bdadf24cf619f3601bfc3951d869d505dab7006edd41909ac3996ecbccbf1a0248c8da4d140fd1103ab88732624470b0977d6353b2814e09b97c4020054215dc73ab4d90c9fcec30bfee714e0a7a5e365108deeb9792e09ce8d5fe763495ae979d3140e974003d8f26118f9796a42c4e3da94fe9cd7db82b484f640d315dc1fc21013e9bc3ac2715de4a077d689b73b4236fff681b73d05c25cc1aac5b99bcfa0dd5acc5a4188d91a114e6c01d29628528528ba803b148bfda183df75be8fa81a7bed21d6b6bd9bb9576f4e3de1c423acc4f043e0c4adace62689c0624be3c93d40dd3336c7eb799739a46f1205a954d2dc7589b5b8d3b54559cdeec2d55e70a003f6c0cf02a65452a51162a055e08e87d444cda692e2cc26ef29a94f49fd622a690f3a1b0110cacde6383d76280cb00940a942a76ba10bcb53da2696ee7f0bcbf1ace8a03c743725c2ec7e8e7ed34e926514485ffd4248dce08fa9fb8cf3a4e200d1097b20e1d73c5f817042653f00f39eca6b0eaa4bd69ab7804d59ee0e9a9f76598a010adfc910411b05cc9b8196a1b142433bd6b164a91566ecf1b087eeb6ebd0236b6bf57f3f9d250cbd118888e763c276c3368b040fe9f3fb0f60dc178b36a7f624dc0ff00cdaec6a165563b0278d497d508f94fd697a0ae6f35b7388d30de2d2149967427e123a82804d1d78f6172e7abe0756835893b935c6e81943aaac79a3c800731eb22658096b3d8be5bb8bc987deb3cb24b99609a30766ecc524eeeca67c28a5f95767a8062ee660005fad0f973714cd1cb0f261b62ab7b8f08bd93e05cbdfeba2f2f268d9f14a850335fbad7929b80212cc079867286004039b5d64c066bcf1af5909f46b76d05c2bcf21aa14bc2e034c55e096fb36c101228659ee8c656474ed2516393de250ccb55821ec72759eaaee5e50a0d5c459e653118c07c6728e9cecabb9360b5ffbb0b66dc2fc235925656ec6bfab6a87137ccd4ff36d0ef7bc8880171bb935d22959b9de72da8eea9473c6734cbf236734ec9d3dddf204387186fc0c5b816a423676c8b578c248e492045de3a687508d894c8f4421c75677e9337f94766d7bb26063c6bff59890666afdfc8cfb82b58ba241dd15b6e0b685517984a5ce762c439cde5c2327636385b27ec203039b084f5a30ab0f9512a2f0442639d1f5a258b0225bc3937fc34e32bba3338b270b0539c3341213f3b195bf4ea8bf12e45e77882ef16b1e9983b7051231bb4569a06e9f3528a231b46e59b966ed961262030105c5dad3724e6f4afc030d05867399c6448a1c9c7de5abffbb942c9b4ddaddec2e8f341aa1c539baab28e2e960973db409c1275ed80341266c038096e1bd2c34d34d620c0d4e2e9246ac066ace3a29f684d293bb396009cc0700af0b2cd6068705512bf2d4b80d76264b0e514dd388e5c04f726bccff16157ec35364a093297ec7e56e3dc55d86cda3e44cbc54ee9ff3a8b94b20753678f47f2817f9f0f1578d52cae6850387138774ed190c313ca90acf60d260e2f710fcca03c568ea2ee3560d741500c7ba9ba66c68b171ecfb24ebf98b0e6c845cc22eb4c0805445bb1b8a1895f4f7b8c37086a55c34749edadbcf0d472b57a037dcbe0717b2e99c246a4bcaf4bee1eaab70c72109fcbe9cc67bcda582bd1f5d65583c32b3da4751bc1c3f04d6eb278837b3d066f5d80f4d3866172e4d7993f6a45ee15b55d116b034fa83daaeb57352d52a8e1f43776a060d10b6359b70b691137178fd89ee9e7582d9c44c583540aa82832d09cc096e00949f8e1ce35edbdf95331604d64110d61f46dc9f52c841a07b10e33dc67dd9a25faef39c9eeb701c39cae38f9ccd84ec7d2d897794a6e172907e64b287e0b7cef9a57ac4deda862cc215213acd21d0dd7259ee634232869506b0db9bea7345de9eb90b280b53cc23d57cf3eabe0432d5062253c9db53e11d5317187c86f725f4478368e2f41d4dd54c3e72135e86f33914a1ab23cdd57396ed553028350a3950add4bc30019754b47250744c6e6962453d06865cd854cceaa9ca7eb26709dd24a4e2a118f9680da6f1a7fc4c920a348cdbc43a21161f256dda7fa0fbff9e27f56f806403823a8de2bdcd6a77d08e74f3c1843fe11c9d958ca25d9521ac4ad461b3b3be22106973d0d439f0aae4e3800908d279395dc1ad9008b138962dacce90572cdcc41ccdbd96a5b5de3382ddaf951c7720f34d1e3b792d9054ac1d0da4df0158a3e5b3aaa277d4e3de52de7319778d72471d1b379a83e514e5b7ebc01d349d70029955f958a303506605a5d7d6bcdfd0389a6425db9beccafae4ae7eac5e27ff5756d9158d6b72b828081efb41dc7a71477a8cef2454c133ab2bc469a039d47159a941ef1573ebcb7f04666c0f734cd01b21f2ec9d4e4cce8e02c58c1a6e07f608a76dd11dd7803b3fa6bf3ed0c9402f32d8120c3f6646ceb454293b484a51d5deaacf79722f7a7eedb8cf9ee8264d4f97241261ec3ccf4a7e64fa070afee7dd2663b567455084c13d58be4e7cdeceb658ab5eb7e4575e4e5434e83d098afc293fcad89f2cc6a1ba53c441e48f940cb6211e8604915b5e6d869a41462418a7b2869314fb16f2cd2405291d45845f9fa9666070ca79a6c377418d4a36ed8cd23ba5aa5602c7d1fb7e060d8608200ddb1ee1a1d0b4748a9cc684ccb2450dbdf3c50c1946f4286184d8bb99c6b2e6f6df21ca4aaa4369d48fc1cc58cb7a997d1703f29a1e9a3a4c77e9999d84ef63f12eec118427e0ee572489cc7a851af96b540591fa3548b6a64cd2761df07f870bd60c51e3249cf327be22938778df74498fc9ea5ab39d3a997cd78ae13ae3976e52d63aafb04c0b89f556222d8f38f938ca856e0e23aca78ec01fa148cd0bd91702301679634b2a0fb64a64eabe2437fbc4aba08e8c9d2f839f4b10372b911aba1840bab2d7afdc6f80852b450948f234d0f80c0d62876caf69e730dfa8a46b49c94a92db439642f2aa9209bb82194b39a11b57750da38215f2eb48a7598ae9e0d5bf690865d8b692bc109b07e5104c7a98ae8982d5c8a6e887790ab2ba67201175c3524cf500b5a2d906b7d8a4085497773516ab50a0000df7838f81a6c9814dac5859efd087f0c94bd2c8791e7056641caf72eab597a600fb4eb15c3f85a679b8e370227633873ba1f1b96b040aaa3730ae1e3e76cd46ea3415e03b8c23b7c124bed0116f3e6a16364a051d5553b9bede0726b5b352177c71a59b99f7090dff2850a2eb5263d292b7c96683bf0e244231ac42e6c1dc61729c4f4c2be8647f8fd0a455cdf63d6a00276f002434b110d1218bac2f310ebedac12d9a12e6310dc21b2ed4e6ad60104fcc517091274e952c9ea83858fbd48cce056b22450d347da1cb4c80ec50869abf3a892b4a2344c209e374fbfa7e6e217f6ae43cd05c2cc1351751d8bd92a6b43d8975acb7e323d6a791638e6dd32f864d1b46bf5ea48207c6b1c981e5b1e9d2c1ffa9b1d90c21d57af296537ea2fb2aa096da8fd09772982b25d0967abb1feda34d09c504cba510876f50377e12cadb574ba501447b701a0dcf944455612984dc5c26142a439daa2ee43f70528c55e1c3462f428491280e34d7859ff103f3fd43a5110ad5b5c4f6e732c4929500a4e0204e4e387a095bfb506e616be9502eadd3811f9ab3547284a778f9d630d58324cebeb2128a945fab89f5b88203175ad0bf6e193d1827402c0ba48560b24405f42581132a5e499b2411d8dc17a151275520d0a28a61c5d86abd2ceb0eb70377e953c340d616c183cec0e944624a06b3759e86550c6717fb10dc5dbca1188e4689edd35ba6e17c97fe6a6a496ec8b810d186f8cbec7ffeab18f58fa161e62ca4e1ebc5b8c73e611bc625f95c51af0c88ae3bb5a037c907ea0c4160ead7cbb0e56e741637ea934940f46637686e18aa9afeb06c145f608cdfdf164329ad1f3109e0bcf0cda9a8c1565068d2c3a16e6607a6e9120b88a656b3759599a0a267e658d55470315ec2656e9063d554c84248440e751fe3f070a139f433953d9bef24e11f5753c8765d8c2a08ea4677c75dcab8a5bd12554de8f3c6cc1c1efb96d00f5c3bb09816854b4f648ff2192e05ea2c4a2bae4615efcb12ed67b93493d11a21f4ea9db8b287730e1284f8763a61fd8ad34e711e1c7776283ee49acc11049680308b237dd4d54e7267025a34e42aeaeebabe8b3957eb796de2808a9cc37cdcd6846b7aaf2e4d035697da175c28cb3039e5c6c19c86e93996bee421a2a67d0d6a767b90ae1eacf0905d5e6c8e7c7131732425d080aaa5a10a00480b0d75517c6ad1588938c6aa2d229ac377dfb1074b0c596100b59c3195e55e49f9c833518df9a533269f2b2eb4d3d88fec5b36802828088b650ce4a8d4410626b6d2e96b4a3e7e0ccaa1fd11966a975df49d9ccb11be1d695277051c1f54c14dbf7dd2fcdbbb60d26fde9609b78862e1d9dbb577a2d6931bc6a213492f9b4a0130946b4901396a58cdcd633930c9f88ac1cc7855c86ee235ade11bd9c5fbaa05b2cbd12a1d5dd3fed496793b325bf2fa48118f032e98ca7d3548fc90f3544635d5766c4657be37a2bc41d3e6817257f1ecc1823ab8b9e22808756434788cb186c5c3b64abc2fc197211c97460894f3aeec1eef0cc1054290359d98a001637a3f2cdc25cf7ac5f05fae5a11878bf4f4717b3bebdecacd925e95857764de181f95108d52dcf7ae3c8309669020734eb2c372ef21df6ec68eed31db6f8a351ab7f95c84398dc7f8b3626c4f004cb3a1d4e2b254e4a807ef0610ec8b099c8a27545e57146022206291df2210474339ad58a0101532d1757d16a8a7ded9760370a7b2888fce5f70d94a44da477a07d86556568277e01d1c24a458ff563418b6a5faf59d7c9e62f9b245c167beb5a2a8a16626aec453124a09f9e6ce0993ced93d08f7757394198938a00cb7c3299e75c46880820e23351e3395109644e1304c46c5d661ad105e2954cb7462ea00ddc76a9101dd748cbf3590ad4e6aff51545c4b6eea9e6e684cf3021ad42eb265a620fcd593aeab2988614827774b37c89b24ee5f97e5a103029cb7ebd4b94a085c138b20a031e1f08dc6581ccea4db9750d24751d092a00881a5b4caf770c5181c4110fbe4c5240841794c8c0cef2493e4b9bbe4be35d8f64ca4fc9bb839204ee44b7058035eb6584742dc47bf36b9de1625e4cf170147c768ff73ed0e26026865cee0675a9a7b70353000df263df23a08f1273db4b31df8a95e459bdc1481b181930b2c0243aeb0dbab931048f9166b82f4f2c0264b5a40bc6312c5a7fd4ad75aff1cb25568f62bddcb2aea60bcaf42a67856266e7d4725ef9ffa9463cf8578ef9d4baec3c4431d1d9fe82733a580a479068351c4af2966d4066d3d6b03a8cb5350073f96ed1a3f2a16468195018cdfd4b2b8e07cefbcadc6d7c52c9a44f69df2e7b985b423073bb02f051534923a081940b1b3ebc025c3671ac1a5f9227773e4878cf8d20b0d028a94ad0595e42d57235e19af81ef512855fb6d3409502ec25ba0ac302985daee5592dfe953c32da885ae07e0d0ec7efb4329279a4b25a244999085b8eb4409c8ef6fa552211de46f17edf326fef5344b28022a8c2110390bd9519fec17430271df681dc811dd118f48a8134c22bcc6effe49e1965a39465c8641641b2df61966352f2e3e74f883955d078ef65e2a908d350c3a7ebc7d4124fb79deeaf9784dcf27a2276c84a11789724f76a66f956a89239b9017bc71683ca2a593b6028bbab3c37bf2e11cbc5d5b32a084e16e6035ae6739eb74f365d9abaab9b98ddb4ea8c24976ed6df7e635929ded8ea2d82af3f8a36258820771f4592ce10e1e6cb9a44223c06a99cac9a4307ceb9a3cef5806da1fb9c575f5ca751f3dfb30f7aa445710bc3f22abff51e66ce1ac5ba6a981055bb542066af9a3618317dcb2776da3f4911d41bdedde3658d68791fd1d4a9801d04a81f644b8991bdbf0f9955c861f2b6c58b1e9fb39c6e35b318165722436ac9c68be56240b474e26b9ff8cfcdfefa48be864e23c0adb7782916757cd05881ab73f1f650df36018901838073806e07c708420334467f04710eda98ecc16d0eb027ec2ddacb4c1a5477ccb907f0b2c719bcdc6323b180e9eb2e39bbf19d2fef8167a00306800a652a3b01080369c2546d79e06fe028e4b97b45149d3c18153b649d43df9cfc74b4200c4f5ecdce2596e9c25c53885966cf637fbd2c1f19870b22ccc443a1f0efc7188748ec61092a8c8db62779ff70b24277ade38676e8a3a5c30859a293d4a5be2fd9363970b35268a232c3dc6992bd55b7ad5f4390bdde56516e1355ef179fe15da6162b98c4a6a6aedf5ac8c44d0902cf203332c704af1fb3ee41be46820227fe015831c3d22e484fcf6d38bfa9575e36412b84d4d82a8cdc4209f8a1a0f1c401ac47d83a65a1fe6b0bd8752491c61a2726918d8b02ce0dc053cb396b3c7e4dfa8c9b2b99827f0289a7fc0ad44c49045a7910b7b3e03df6e420ecf9544cb034da3a3cbf7008280a65fcdcefd04a632e80b93b4fe35a429e5ad791f5373e43af9beaf011a588175cf409d91be14b369409476dc604df51d2585839eba3832fc98f663929e0100d59edf24c1acec5ad046ec80946fdd1617d768822f2c8a655c678e338a761f85f29dd5ae340793e9f8d003ff9fa2f5cba2072aef710820a7baea8e0a7560030c701e12510982c7cf0590c6380607542953a9b1d4856c26cd7224e4f9c19c0ebd227aefb02ee66db545841b9e46a3607b912229c791b9a41ae17ec073d5c592188fc82af09aae5d7d3e487714a85ccf6ae4daa405aa60301161171ac209521b2aa123a4e59feb3f23abc461fd0ef02a10da412c76b7998e8cec5e8abd08a07071bc44eec0c53c68aae23b7c289c180ac9c697ca9f9135cc5887f0f9ba8893145467d7aec832bbc741fb02c5589014c38a4feebb769d8c5730e5268845242802d77594bb2bb6557ba362b6eef648137d51b40214ed5d5ead733c99c0bf6f0c7b312c02d0d62ce79e88dacb8a8cbd5e64de26c6325dcdb0ca42f71c17bf41d70a7f0cd33267b49ca7a48bc40bff81f6a2985e6587af0b86f9640348fd21d157a7a10f1fb8a6b7542bd03b719c8ecf4f57ba35507065ef9bb1d707729b3b0a46d782d391f47156603d7f08aa3cf3883d09b4e311881fad1e06ce3a150ccfc47227395ff80214378d681bf2c1511b4f7a01a9f5c2fd7389ab1caff10ae424507b75e7e95a83b07b0b4b93fa051e48ce8312507a47d3d62f3ff8612dbaa529682cac115af1874a193533ad22a60f3126ee6c17e4a505d26f07fef7f1acd6815360ab38f4f7a374b95afc09ba80aed58617476b0a535b845c13f298105216edb737d870c6a7f37ff0c9f13701251924c10272663d0283978d00634da9ebbd4acc1d8a20bd1a324ea623ae25afa2d52406833c67e9734910cb0bf3882ecbdfc86c81ce47c0366fc3ede48c88114ba6c76fc439c5485aecae365c9de62d77d61fd40116e0112636e540c90c031822035c47e104613caa34285c90e3d0b14652e390ecd38e1a94af82ddc07d13f4c860a44385953bebe51643806a16ace5d43b3d72e24f6c126fe9031136ccb3017831892067a53e0e88e8c6bdae0da06a06f13c59b4704a85628973a9b16522cf884647100278f678b9d0679fececcd576d61391f04bf78839e6042859271837e2e4455825cf1b9c105ac8d43d3d165bbf239e0107dec7fd6e9ecd20ad1557d3452a85568d2968fa3491a4030f0ef90eb8a34e17c4df69fca9a7060795cffcf5a5cfc5b4617c4bd79514eadff1871aa8474e266953fb53817bcee77682514852facb589019906b8e870bbc62b8c350a4d46ed550614a43625b02c40ebb14066ed30cca9f651fbeac760338821bb7a977b7c54f97a9e32b57d2ad0feac00b707e298fda3e6650149fd65119b33036dc718ceaf23566aff6904c3551d3fc193bbf4082c9314c8429c023ca19e2043867529a1f2e98e9b244e150b8568e1ae45f6cf5b144325b72a6861111ab3903e37879e060f5f67f88b5c4484dd162fc2a05182370ad0d094463fedf3f51702e2cf9a2379cf8f1103357098b3e984e92c42f813a9acb5f2140076c84314c5b383783bc17553417cee2a700456a0b66770c501ab4a63427cb7e99f296c09a8322fd1a7d7cc7fa34df50ff034f549ba5372e6ea76408f542b354eb0f0e2689f24f96bbdeff96d8508244b41d0fa6a62446dd2b7e1ec75a22371a6167ca6b66c3b5890eccbbeb255b4c554660b897b1a98686db84fd271baac2a14ccfefff541c66819a3a4686889cef325221f99c7ea59e7c36b92018716702fe2f909c98617acc3c18e9b8106120c652985e00acc36fa946d8703b0f4aba35ce2caa56709c09257efc925eaed6cc9c283ac3c712369bd338cc58ba08fb328bb46b67e74e0878dfe5ac82cd9e54cc30306c7258925aa7f5237d3d729b303d4afa799d717c22a09d9d1efe2cb78273d276f91d8cfba78d38fcb4ebf8d52c20811188daf8ff1c79a576190da947d736e0d6160e87102db4c8aa016ccff392b0df52ab645a15f6cc210465286bd4a5f9029b50cbb686f79fa5545ea7f3d18e61e594a5aa56203d99cbe86c57edaa20fae69af552d8a67b6fff4accfcf1dad99b0203808677a682406c5331e4ed916862856151023b93acbcc8867cf333e5ce8f2a52b1cb0e380e646b1cff78f5447cd003b0a0f01fc334d0523a82e40b543eae08a1a8a1ac836f9b53a9d3caef151cb6b35dcad4c686cc994bc641c237f09cb04e33d276b79a5c982e3726438c43b6c4b857161afd452f7853556bacaecdbec355f10f570286dcc730876798d59f138cd05920f680b5feed60757d50e25b435edc80ef485836ff6337037d09540a3757af938f12f140e086eb42041331465ac59be6fe5e5b2367d396f692270d0996c193296b87b268ae5623f28f6c79b4045afd9e8d54d5cd3496a529dc8f05f33f1e4858099b2e854e24a4311341cc10f5d190f2149f265a7891a3f2725096d8363504383677a5036ea8d2f31da5952a4dbb29b8923d76cd830c4fad417fd25b4de8ac12c6bc6d85320f1040501a5535eb772706c6dd6d631dc867caac94fbb9343aa1492df7bb237680d9880bdb01d99b128b8e449a73baa4cfc1b45f0ce75517aebdb31358eb0c6e778c94639f9102670878548e6fabafc29932ca5f928455ec5d7bf467dc45d5762464538e1ab27aff825a4ffa07df6716e2c0a320d5e5448d95a35c43b2b5d7ccd1192d8a1835605b8a1e3306e83b2c115a155e3842d23fe74830f928acb1e98b61cefcdcb783a1120553b675050d11750cfadd13bf5b601ea0f71f84a542d7d6b337502acddd7b4a33b05bf6724fec9af92a5ebf56d89291d99622b071394bc05c9d1ea4a0c61eff09441ae2a43ccbf4382dc9f8166529abf7c91d5e756252c541ea699fe1e0bd70b3bb938c9ad2ee86d50256b6effa9e221c75736b8c86772eb0fdc67d48dcddeb8ccd6bfd0d210bc2d9dd7552b8c75b30b201dcf5d60ba4ffe920cd4385087c91f1733df89339ae313f91847123919b36f45b4c1b817f789112940397852096ebcbe45f801a0c7b0bd718413e4fd2e200525658c5bf49b1404530cf3037efdbd43b70343f6585ac80e0d9bf12c26d4250852d21e3d27db342164dab1c90967b7f584d34c464fe9e2e911da123ad11386a4210246e189a4010c46af321488a1bdbc78d8484ab7bc7e03d5849fc726790482df13ba7b7222cba280ce2b5fd2068f2eb4e6933012ee4d6b3902153b874acb0a46def82344c360dfc72a5a1e0fb09b6c252c397353bc08fb6f3ad1ce43f323c9f25f69dfcdf4f00c06a64fbe38e94fe2ad6a32c0166d0830c59e40ac178dd863667c8d593da4c804d9c7e8d37d21126681becbe56de1cc15013a1e502b0c63deb08278cc38daf1dd1bf70078e2d3e59bef461cf21f3fc84a6effd4a1cdfc5fd9b6866bafe0f48afa90a1b09388cc169ca6e5ffdc050f195c812b03780ba101734003c409e992857be0c9cbcbc2ae575480e6d9ad334e847f6641ada5506fdeb9e225e10ee23fcdc572e6864e0161eef8f5248a29a801af8729639813b12f8a6d04785532144c3daac17eae820476c6100e9ea60eea31b373fd5e894f01b80defc24b6c45a37b9b81eb9aa918ac1b0f83266909a21bf6713ab982e6583c8362e2e853dbb5c7d2d8242f48ec6041f84f97614f0173424090fd0b24ba3a1acb49bac01b3627fa6dcc0c30c5e012ad424d8e6e07eed847cb99cb0f23652e350d105f7716a59450668c1893046e847b403f19d98ff5346207d13843ab27bcfa331c5acc6e174f52e960e664067779bbcaa8b47699336bb514d72f048dc04ab132b536bbe181fea7cb7cdc30e8c4c377b17c8baaaa85ffcc9ccb9d7792e7d06e3ddc484a0230dcbb5c4f89bf212b4eb721e694f17b8f6b43c5b81f815b6036e6d36b8487e30232e5e77f580f989d9988b8843ffb9fb8aab71e98829c26d2808b38993ed66802858636f4e13af5615367ea0c7312003a1af03e093780bf5f511e5719df5bb319cb5debee0278272c1460b069c115bf1b7c1412af8c5dd9d02bcecddf5d68c0401282ef71ca056f23d79c9dc354770194ac709a4a3346cdbb5e3dee121c6b95016a5a2ebdaadc7afd0f519b04027133b3ed3a258425cffd3d93b8f965c610b182a89a87efc04a2360f3e8e915d088442bfbd88977db16612eee5400f3ac543c28460813d2b3f98c7ccfd9565f7c11d29f10151916523039766eebcc19f3f59f5be411027183a1aa9431a4e0f88f8054a198355f1becc27b02ad6b599bb6ee66776b81807368edb64e3038f68da68b4ead5cf2e565fda4b13b91003af528f62ac10c34ee57bb803133ce04a3091a02c3c84cc0e0b915d1127207e29f1cf1634bde55452b2088311bca41c306f53eb608b919ca510ac3b1a909dbb8b3fff5745556e51750579b30ce90eabee6497ff402d641d2e40becc2856bd2b9937528f235c275ef147520c670e400d069243d91fd589d732d0405dc9554ead18b49ba7ec944f87629c486e0616c681e028a03efb2ba762a76eef2d4e67d17abf15bbd03d6fa25cd2a9b13bda55fb4cff8d69cd6b32d82067f19da5d3641072bf83c18da031c3cc0778f8e2aee0970a77f2cf7e0ce5721f25a44479db6e820baf11c384a35760fcba5f25d791d49e94073a75a610e431904b88f00f50882fbd8bf06e51277be9873063430890486d48c39f9c1d02de6538908bada4d5ae7e56eca04740b2ae4c84cbfbe2b4b668c1a01341f024a80ed4a5b505a15a50d39c504355366f199948ea23885f50c9668723cbbd251e5cc8584ceabe00a25cf6c362eab2e6e0d5a55b67b608062089c64c6423555970abadf88a838f5ae51c30362b14516510ffd2a43fb4d6493c84d24b2bb7bd30e6c049e04c20436b896f92f68728c8854234452915a0ccd45ce9f9c5dd49ff3ab7ddbe489b49339d11b8a7632a159c96d2feaa28ae8f3c117bee15a1c976ec0cf5bd0b5175b283f396b179a26c827e55ba4bc8e9759f12c344d90fc3a5e46f42a9a46c7cba8c8e2d3eedbac3eed66363c6d7eaca075bd9d411909cb6af8e793b2822261c876dbbaec0e5e37281ff31af06450321264996fcf176915da3d1a9f0c03226fce880d3740173bedcd1d790e7b3080f0b497a14883a31124c1cc2343086f0fa39c7f0fc6f75e7c8f44720d39ab38f8ae6d031b6622cc88bcc5fd36ba698683cb788c4e678cd5cd56774824820ce373e9c13a27df424dff06f431af445f6882dc7711deab6f408f1f8e94f4589e0dc209cdef49096b9554d62a2b29abf516a5d37dcb04441cd0b25e4a6b2ba6d75f5f7dd259ab6a5e4bab29a7b6de927252907503ca488f4593228c7a624c69bdf0521a4bd8b510466c5575ce29e7b4b2d6789abd41c818669713190a49028f5d3976684d2ed4d8477dbd3531c125ecf311872da6af6c060989ac3f27528b01bfc235d02d7392874bd75b78cbac463643939ef6dec4b0f94f7bb4caff7c26a552425861098adeabf4edc99d5aca39a1db1fed6635eb8510c20927fe146c5515d229259d77ce89b17d6bbf82e6db0987c8d9db8cfd5e7e2f4e7043b5ee70777fdbc35a6b2da85a90b515044ac282acb5d6da7fb8e292ad1ea4dd067d6a0581fe56f75a7bef5b9d92b10b5b6b2f080402555012950402a8aa2a0afaeac7a65f6d37841e3e76f5b4c2409fefc56f2fabaaeeb5aa7b2d7661cbaadee4abafac1bd238e0be6f62dd6befc5185fd7c51883beaa2a7a2f06b29f4fce39672266548c70910b95301deefe26183204f58319d8f8c18d0f144693ef1588101922c48f2142a8605b1f29ac87517d777deebdaecf65bd756ce5e7fa5c178cb884c37e0d6ee380df82f8635e65dfd2fe2caeb2dc44f64f72c4de5b21c1c8c6ae52ccc397e2d2fbbe17ab187e9886cfad744e8284276cac3d7fc52e092aecd85512537862576f73a5f6f9bb360c6cadfa80b04a4f2e2e55d775522bb56acd6e7faadb9662fa57576f7d573b3fb6db17cd2eed36e643c56e1dc24614c51345f15f78957e3276fe96dc84def54de87128a4571ecb7ceb184a0565b3a7b5314fce0fc5256bafe352fe7cdec60bf1e76d57e4083451fb36c11f89ddbf8e987cd529d8bbcada6b551ffcf9bb9d10546a63176b0dbb54e7cfa88c69b6db0951a2091b6f25d96e57c40850d8f42d7633fe9cccbb5d11233cb1e9db4b0317c8e482f087d65b41d704c34c40f7deebb2f7de14f7047f7ed614f7edcf473bebdeb729a5d785d116f0b02841bef7b15c2f5c38841551e728cdce519d726d55297598629c5280e9bed2af9afea42e535da5acc17921046fad9faba7150eb8b34ec9f8da6955d7d7bfbe7eb53eae3ead324f76db2971d3c416d1f0767e9a7c8a12f3223600caf5d7d7e7725d55b6e5d835bb9664f75357f7dd475caab47533916b71ef78b6abaa3cadd729753a5aab55bdf5ae9ace55576bb5eadbea2c2bab545e45d8dc05e84cd80b6b856e9f28b203ae6bfcaae357f2e54b2d0add87f960f9928c6449deeecd56522a2584904629212542b33717fb59e83f5dfa7b8ca116d83c9989218490cab7733a3a350f4f7b3e03596e4096f723e937b8014f06673b6024b8015f38e02cef7dd84f04fbbdf42032920db896b7c3147ae4b4d99b13512bfb4e7830ca272c5eecf7db8a9c85eb6eac5bd97c465f4a64b4ec36abea134af8f213e528bfbed4a59861ad22d7f45a92614662df08be40c059de27d9ef371214b6b3c27eff00d7f2fe866b91414672e369ef311fb20d1b5ee460287c3884676f2e8afd1ed2e7ea7b2fc405218ed8f3dfdbbe3f6b0128c90e9758829043ae11216f23a723041222a89f7b95952c060c13b27b98cde9b28799e8e1743038b89cadc3db381c8a1d71d0a16ef939ccfdf684710cbe6f1c2e8a2876cc82c84113e5d304f1d7a1c177ef2285dd1d88cdb1ddcc2c003f3e8f80a5d13192d1f1321ff0286482a4d050778e9d63bb8f6e01d67daef8723055e790811c3b071c34c776b814c56e61e30abb425f46a41a310320c001eec3fa06c8c2e369b092318f3aeac283d3f88ea78968763fcef7de93fe64b6c392bccd4851c004f93e0dba6d460c84fecfb3f8b419a416c4b7e3f178a6bbe10d39ecf05c248cd5e501b7d7a326d63ccdc7a301dfba94d5d65a6bad94c65adf5a6badb556167b3c0d42b13914faf1781a7cf854bf9e07059c793b3e2fc89b4301c57371f16843737e64a763e231edecba9d8e0965ca79808ee96402b29d8ea9663b13101b6fa763a2d98e8047dfb3ec2fa967af5691e83d0743add9082c243f67d91ad430fcc576f0dfcc8c3f25acbf00a4909fc3253f226f2a2a2ba4183566bcb84c10eb48f0c58180663b1d1d40f6cb763a3fa4b6755cb2f15c66c418f1448ffdbe864391f63bc9c5e5580109978385d47e5fe321b17fbf8fe160e0b6f2785e129450a2081326ba5d11267450224e3dae18a37b8c1e9fe83d27d3f7ec629f33c619618c32468ff2212e418ff4cedf28a55f69e51801a3bd8576b2dba34d33774397cf1963ac2a95d1b2ac65552fbb8aa841581d358561a12cdf39d4761cc83231f881d66893a025c0019e4b7e571fe793c1878ee338f46db6917ae83b7d8aeaa13487441a8d34ed5f34034d94866674e6ed4b3f17b6dc1d6bfab6d22d2a8128facef1475cc2d8e7c2f1c82681a0f69c3990657ead398b44ff9a361a45257bfec47e9340e890a7cd0f69aa93536bce22d1bfa68d46dbccd9f37186049ce48817f6491561222c58b2e963222c70c2c634a33bf00ca5b1ae0a9770cdf8ad09f6b9b0090863ec628cf1a4a04bcfc7f446af7e17c6d87d17068152a0a05cdc8299bd552ac09ad24b48ddba4d10c294e505f8ee8b4a26866fa312ecb6c30ec82173a38d430ae35c22c24a90e16f195a5978d6a9b4a8d9ba2aece2dd5b2f819ed2bf35484fa381827c7f239dc87a6b0d3567faf97c3e1ff328a59f9c93cceaa2689761a13225ca5b2b89b522ed322e740bed3029887a234aa16b5c92c5d4a1bd4ba43090f67dc17741185ba26757fd40263530c9e40221210454df0a973e353ef6b9db8c629a5d4e647a820f5d6267577f3056d6bc18e05ae6a35caa53bc057ad0bb5d99bcc9bb6d750a0dcf6dbb628926ec7a23ba4179abe29ec4d18c94894b01ae09d96fee09a1dfbc07ed37ffc0ca6f0e8293dfdc0530fce630d8307ef39bdfdc072e7e7320fce6426eb88ea37678760072c2c9a9f11ddb4d355e53e3364a70526e8627efeccc9e9a9ad90359660d64c93a86cffae1e03c9c87f37620cb7c3d9065c29a9d9db7e35e53536b6a7626dc812c13f64096f9383b383b383b3bbae618b4db5105cea5fe5481d366cddb812f15b553513bf35f0f7ca1a9d4eb49bd9ed4eba9812f1485aa41d5a06a70e00b3d9d704e38279c3d1feec017ba03e1cece9e0f7be0cb4c411f0e26fffc87d333610fecd95906172fe4b6706ec73168dff461475454ce343b81018676216d85a660b8e0452d8626c3c7bcb8610ee593b7d5fc95af2a19429614a9884e422193d7d5044e9b9fe913ecf9a819ecc943d260229f585a8d5826263aa7f8ac2d3d813c6d3e48cfd7b3e669f3353d61d0d3859e2fb40ea2ad3d0cad8368c3f815ad8368af3ca67510edff4ceb20dad9575a07d106d9d8ecd8449b1e9c8bf6f3e78c8361897586c0b9c0f8f973878389e167ac354f209ccbcacf9f2607f3e2e74f153897ece74f1738977f39236d204b8d8381e1e74f1b07a35fb463338b702ea09fd2c1b8f8297920cb7c51ce39def36a6af6ac3adb7b5f126aad24d626b13789c549ec95c4cecb42fba6299f21732e4345fba6945294542a95aaae088542a154dc0b97688b98035f280a9583ca41e5ecf9a719f8424dd206becc13cf89e7c43333434d33534e69939a2f73e0cb44491ef8324f2edac0179a722cf35139a81c54ce16e52c796acd34355f9e5268df2643a87c4a906233b3a2d2024abc8248d36460d1301ca0a05818876f160fe3619cc40063c58aabf236c5e4a668dfa1172ba7d3e904c3052917c6e13be5adcaa5ba9a5668df263064ba9a9ee69ee8b79c0190b9c7830613d955f28ab72c2ed5ff573f0b16208cc337cadb16976ad05b5cb20ffab800b56811c2387c8bdea6b82ab4ef79822f1525caf994d5d3a5ba456da1b315f20c79ab50a4f6fcaac773d9ea129ae7b2553daa9af9d58e0ac55ce22eb504aecd9f9504ae9ac0b5cc6fa1abc9b5f915957f8a72ce6fb38ae6699347a966158a6a860ad9e7bb50c1387cab785be9556be05ae6db4a753da29e50906b8f141ed5948287ad9f900986d94a5d0a8be1b6187571be054948aa413a39c93e781ac9d3886b99a1b72737d7fa1597aaadbf8d422626b5d66ca29e36ff444ff434ff8230df16f4b6630cbba1266aa2266adaf33f5695822ff5345333b5ffd2156bd326a289ed36095ccb49881a2406f534c49e4fb37adaf33fb8f29842de26aaf2d8f3276a1a91a26b73f6561151bd781008c2ba95ad3a79f59bfb0112560001d86e63d1121b55db39293c1dc8421a59d936d2b47fd1c8079741e95b4f25fc410a3bf2401f7c06e9c365bc278d469af6af13757c90d4f2c8035f68cd5487ea501daa4391b817a94375e08b0402a50f9f4122e132de8bb690abb01f75628bb204a2b39f44626fd2875cf2c312f6469558b287e06414c1e9b89619509d132852a9b6a23287b2a45a0ccdb2e40fd7e2b8247fb89657dfe29b1dfd299dd24cda3cedbd0e7ca1447096f74650024dc2e3da396da349ecf7124891fdfe5122b88cf74ee420a149a043844087407564903749757ee0cff5246cf656d12063eeeed5491309d54a77762d32d4bc4aeb0f36ba751d8eea631e0ef6e9c7bcb90b283e303d762def4550679761882cbaa1d0cbd95b4889ec60721332dcae084e137b3e7cd485f115803304c6874b49885b49aeb62b8283da95ac605555959cd7c6c39e598d8ece3985b0ef3d0d8c60c7856f071fb529ff7e942564f7f006fe25172d58a8a4ac4011a9487112320161f973e16badaad229237c37f063de7b306382cd7492557d5b225186d57b3bf8c0a384488f144da4522929638b91f6953e199dbb6c2d52b8ab34e67db6d50de1abf41d8b9307e38a9311b37f1a8c99b6e98721aeac38edff35389d8200072719b3f25eb06b847348355aa5f896045f6cd8802f375c8b83f5524bbf3a5e3de95fdac948eddb7232766aefe1b675f3f9aabdc78e97b91ed3343a5ee6fa07dd4d19d7d43893efa00ec6a5696f4e032b454fca9aa59c9287685da758f92996fc944a4af7397dc619e743e8526aef61ef68c6c8aa2eab0a7eaa164373298358c10b2c106e32597ce5909cbdf9f5b14fcfe07a9f81282050a8f836274b581b2f94a3a2b382723c970dcf657e7cf9bc7b6ae303389628851a7c01fdfbd7b411696343de8b9b4059356272264c4d60b566a953eae44738ce63813b066089377328432861bd55feca57559da2593fda7120cbfb4bbf93dbc059de9b88e040dc06aebdbf2107fb3d911d42d88f07560d4e27223ff6e654a0369e0b8cf6b1f801424a2db450baffa63e4cb6aae857d423a5558a252774a7d42975890d20afe0811c4b3a50d50a4935ab2aca5955b172277820c713f13db0290db54e1e3ab0e97b759f6bcbb1825d1f42f798122b8fd1b2248455156315530ae02fdf2b5adfab62b6d19945602b0c22b2c826db53c69d9c5a2bbf567c7f817b166e06b77119efddbbaf6af98ee7bb4b49a65fbd755195363e83077132de7b90fdbcb20ebe256e63fd2a1dffdec7a513023cfa281ccdc0de5639630650611faf24a90609b3277e43023b6e7d34d243e79452da702859ab472d86267d56afe3651ce7d128ddca55e002f2e9ac247ed737a0bf380a99d2f8d661f9b69dff6cf419a729803fc631cb4d64cb5e0cfb98274b9685a120bfd84e2705355bea7ce488dbf6e4620dd23441b0fcf288c396d7c0b5f8e9942da812f2b17e22311d3f6bc779daa341ae70cc2e2732b5212f596fb1102342767cabe2c4dac16347c65b498e6f3107310cc3b047df618f3a2771894bd563782bc951668ea3c2e9a36076071e52a4c97b98fc3d951ce35f5c8a37e4f0b4f7aeb32caee304f8c78f568cd5db5535010a5c17684efaf46d8a1bebc3ca666fde8308ed73f8de8c260ee445fdb6ad3456af799a0fafa966742097e2527d3a758a4fe94ee9cf38a7fb74777fea3e8f78da734933fff1b49891f8e9693b2fc99bfff01fcf055af7d621a7bd790f54541ccc8b70bb008a8074a5e460e65f98c6d602512f9c0d29df8fa08df73b5ccaa1523484c88f4a3921a4da4f4f7b514e29b5185a554157f1055ccb2b5da05654cae955cd625681a7bd1d3630e960bf1fd83c41b1768fc6e63bb08115f06470e6181037c01737c206ae6dae83192fc2b53c07e244de9c920a5c007fae3c626faec48a8359713070db97f281cae6de19aec7bfc18f8bf51b0cdbc5707f733e00fde68080fde68498fce690fc6f0194d3a8240d5a286a0a158d0400401100e3154000200c0c08c4c170482810865d6f0714800f6d8c4a6c409b0a64c22487811004419031c6180308018018008c4186868a373ceb889ab62e7f4f3c07477f6c2aeeb7cc32c893a3036b6c62352f04bca0129a77ef0cabf71f802d43394a46efbe749a06bc366826e0d686f21a2b406e1d3ccab70c3c151127c916cf3b8d4825ca1bb11fae046b392cf563366a5db455117661bac5b6ac3ddd3dbaa744009e24489406c87c29e2229b1283235c40cdbf00d95df90ed7001157b4011121876bfabc3ad98811cbb04d5bb0065327b485d198a4e5a9b24185871c0f8e930ae5a2097228a321e8c082d6436a0d0bfdfa0e041b1a9bce2e31462c78e425bfe1e8bd545aa1144f1db7159ff8983626d49337a1a090b2813bad3483e8002fcf8ce3b4e833705d26778b037caa71058d0e33118419c4fd922cf84dfa1163cbc9ad25ce4a15f6780033b133f4f598c2067db1f9358004f6a8e88f09b2627092807604c9e7fc7fc67b5d3be0b696a4d9d708ffef83bda27b3df82a9f60fd03c83a7cb24fabea80e999ce1502998adf61969f98fa9459011cc4a9e24c66cd572f4da29975db435cc4a94422e732b3c0b12ae69ee21d98e5e50ecc14082ff08c3328410ca6f2ed42d7c1dcd650dd25c2dc64e40de4605d86681afaa6fa0bed45f1ed026b274ab37a169c80a1a1049e50f189e75dea84ba4487e2f31a21e07bc0e19281957bcb20d71117ab83c644eea086d73e3f4e754a04f037c5c425a20a4158405e43660eb97e3d5c0149f24554e2b379c70a88a4ff0370a9f5b9e4b1529ec921b0551f503a485a0db694e2ac3d20fe91bb6239be4fb544204ed92f204dc0997b80b4e569a689cef3a963931a7f4efd3ea0d9d83a019314f9011081135c9348bfcf6d907a7b05a2b37cfed4074d5ae5ddb3e1f49c4a0ccb2bdff94bf1c40c1a82b6841f53ff7f4eb138b8a07054ae1b9d829f1db0329764bec06c334e1b5f6dfb9a9446877912a220cf18a8ee39eddeab0fbd1c37547dadaf80fe88652ec950e42aa20d7a72c4e70b65295c9e86f8fed0f0a20534203608a21fe93ffbd436e35b150e431ced0ba822272813d595b30dcf3bd3e9f6ed4381c6149421bf37dd148cace07d2c66a5a82cd51eb5ecd07216a4d85187bb23be0c6926cabe8f80caffd7a7af5af062e93b237901e02b60fb756c5addbe3bea6d8c1e2e95d5f1a1e08ef0824fc40e140d13e78c4a7329ac513e017adc30904607ea2dc5ad315f11daa130e4faaa7fee644c59a19ca17151a913a49d9fe7de933822722d90848c38bab2475bb50941c661c08f6d0bbf73831697b0636339951060e22625d483ea343ce33868322d16ce53d9d596e100c661b8a51923d8236aa45f2688d8d4ec98a8b7d8689caaf837f8781e36fa7387c79099991bf66138d5ee4018a73b6ec84280ebb08f8d6a5f17eca4d1b1f08ce44368e829bde845eac8bdc7497bcb20af037bea923a621aedb60d95c19deae399e99cd6c1dd64997d661c0706b43b1433dbc4cd0cdbff3c2173ae084b7b010bc7118aa108b039dd5b61b695779873c0b8e5319b8ed65c36a00637f123246314e5cedff23659eb47dc9684c868164733f1f96e84d211a88a66d6c7d5ac6db0972be4cf9384dc20b43e738a3e769d0764304be9db95861325240ffe5a01e73c0416d8b53580cd977fd010ea93c427776f1a71fe86c7f9db27d6ba38e30b449c376a3814a5cc0a9f72d1d322f8c9a1439cc418b2d34ada2ea969ff29fd3ec6a84556caa597862a43a5b47439d1b15745715baed5a4708aa1dd5f62532ecf8e22f05bbaac09279982720ca2252fd4d3777ae479ce1da694fe75fb8f98c07a8c3528566869df9b9e5f8c6197d42293a3c305b1da24ba1ab842516b480016070d0c2296024bf3c60b52fc377766c088959cf70db764a2bef9cab5ebd663ab646cc73c8d18cfa647029ad8a7bb51439f039f50efbe1db43533f01f9fe09bf60e90fdcb795e7f3e9bb1743c023d47e9f4afe8c8f12a0cbb5a2c556f3f09dba8370d18c83db9eebcd8b881de6acd0c1c6c3453a7cac00c3526b138e9683351f00d87c1f71ec4b967c181686348bc346138dad3f27edb4d1bd2f267748e6acc96514c5a61743e926e1e933a62bc438ff37dec10bd15c15c6f0d92238c3b3ddf412474c4f1042b6b55e6a0e111f58a8ee214bd96ebcd93f191b5b0fb4ab3a2fd0e813e28a1e94ae822131f2dd48ff6b017579a49405d9d2d4309f845e161714b681b1c37b507cb1332bf3113588fa8f71999fa8b903fb3aedaf66dc0d845f27114eada909106ace09a6b1579704ba14f3c326d9f2e33a0277b2c9ed0755db45635fba783c1c9d432356bf11bd80136c0ae7ed67e1f95f0c4530179ab126305c3957b51b16fa70afe4786c066465f59ff707396e508aeb5b30493036b031f7f125ac0f9658b86e0019cf34078e109192a2a43f3bc64e00db846f7f41651f1a582273625c643f6f292d0a70091c0b923f81ffa14cf83d3187e52bc5419cf8922b2fb1fd72363eebb7486aba1f1fd9b2f7a7d3f96ee746df7486f4b73bce5664083b7df7bd396cd44989559293f2a0734cc3d77804613c828180efbac6f0e69b2adf797fe64710043ce58535fba064bd5c980a5f2c7a750b155149322c6eb361d2957486aa1e94b7044d5510898881ad0228519ba8b808057b38f9e2aa09af7e3d82140ff7de5a56f3379c91a62b5c3e53512a1d3ca26e2aabc21dcee7c2b68935e993b2500784ab651afccdc1ef90f31b3a7c400058e5bf36be02d07f250dcf102bc52fa921377338c048420e4416e40a7f83a7373a2f2ec0205bef7d0b663ececdcd9ae78db71f886757c41f482152d0fb1ce383961c435e035101b998782a7fb2b310463894b02315f1dcce191795f026986d08ff9d415d2dea7223dcedfc0a27dbeabdb9310fb8bb11f9788b43b5c187889705c826a34d8be826b99c1f0ad1b05c155fa63d4d853185abfa69f7907f0979954a83d2d589d73f8980379058cc71be7c2ebbc5f6a183eb5565646e0453dd386a6bcb74ccbfa7b052355b9077e1ed2b58f156fb455691b3693923fe31f365b932b2d847449a3d89c0f589a16eb476cc9de8c09311fc40f988ac8c6d7a87aa8319d41734106a6921bb207363fd9f77057f39052e9f0846ba34e5d37a455e836fa964c1894aa3043ca2041b2f71523648b7283c7ad32a1ca1d010630db6d975063bfbafcae09261544cd3bcb0a4314cce609f42060c052c2141e1d6ac7b1da40549fbf893d07ef27a3285b355e2e1c78cfb8cfc1fbf368538e0d73b567a0e9f1323f244178dcfe53c46a906ed029758c18a6bc5343829c7b354af4cdc9c212e76cb19f871a3432a06f4d47e3406ad98299b60a238ccca64829a9aa0a66d0fd834112a00c0bfdd4efab4c10112121832f79cbf614886e9ff5df8435598dfadb3c551ab4122cb80f250ec86b7517b445fc5b4945eae6ba08a64545370a6e25ee7aa4c51c19c68340ab610cfaca404b4f5b4aa0a032e46d641f36a6233eb0df827c1b9d78904b482096cd9c747ea3a03a1e1ec89613b572fb8ef406704971250f932b8a89d32fe8834f89c22a31745a177ff0eab68eb8154f77a23f4d8f492b5194685237f3e8824cdb901ccb0ca719e40d849ab0bca44c9a8aedbea6b0d480fa23e5d0e8101cd7b06cac46c3e47899dcb8c5d48ea10a9716ec876b848b53615aedbb78caac651c5c80713b9597ec14911516a5701741f2f3a541a8e6799e13f931c159a382fcc914fd5b3160f31ff247d4989a20b8517f0ba342744c17cb4f567e68138e736d0462a1979cb8744398e2f5d81b1a28555847e2661a030e4afe5691480f364ff3992abf4bfcc3fde743c11e35a4cb241f721257437ccd0f05d151bc1becffb0e372723d358448a4ab4eddedd598048aa5893143a122b00a28a6deca0de8023bba1ec1184fd846448ade72335bd62e80f4ffd6f28cc1cb3740fb5a199238ddc09ee424d6b10f4fa9a2f5ab923c927f9c3b3b2b32ffc45418c04b41c5733c61e67b3549697bf3591c67a73e03c6361045427f4acb495433e06889880086e4baf9b44f54b6775a574f68b3ab9189091da6ce59843b416c636e15ce52eedc79afa5778723158ab00447b2d9011bab3a4ceb2e156420c85b7cca6a804640b318f8ca5bedc2ea9959de2710bb67f37dd36078e7be747ff41cd143273818c4d9004b6e15a8d8f513e2621a8729177eb58dd03bcfe7eeb346cc3f20c9cf762df8afcc32afcbfd38aeac00d6bac789f9745d7a12b79bc2cd0986cc6e5a2e8db8a9b143b2898fcced2b83e3c5ca4fad55098207553c0b1f6cce25b0d102fb3ab1b1d339062b090b36a46b22aa1082902057474d86f398c9e49cbff79037a39d398b2d290db17b27911bd880e047eeb49c6f3bddce26510e50a76647b4ce67b897082fb8964ad672455198e9e0ff9a147e29fb1aa88c825e88c40d3a5646d49dae200a24f9e1c37523349d592e7ed226dd8cce74b31a4420d410af150471a475ccb1e244b3767e0d28d0b1f5db56ef34847ba9bb1b035cd6b607e518dfac5fc84160369bfab727af3b19d3a2055959f4271b11f464570441f28c09907862ff087556d78e49758c0fa663c008f9707dfb250c00c3c8c47c77fd920885b1b3638a036e6700e4db1320de0f5aab859c5cbf18b4b77d90c7c53c31760b7c142ef9f1c08768f3768c3520cb8659b7c82734881a73d7e2db4ecd6f611e21196d6098fee9584065e8a3dacd81a0a95ba39553f2ba99aa5e111ab7975a30a066d2203a08076863ec77e65792f2dbcb713185a718bde649c107d178d777d3d78230eb6fcdb205a56cc0b4ea91f86d4361790d13ee3ee647cbe113f23f6083c20cbc6df6a2a464a4b868e469f313d1b4793874903ccb86ae5e593cc6744f5f6920b20f71d6834997eb1d9cc2c89c11444bec61de0c7dc710cefe6909369de4a8a0e43d578d08e7b628c84ddeaf99884b682ed9607d6ea5267e92f1c4273af73ebf119c3cc4d4f010de64180962566c1663ef7c322eb567da0f96e1ed12f243110e1f90bddd08c0d8750e45d37613718171b7e7061730f66c10b64798e1055422065e3a33b791e7d624e5432d21601fc81f61791f027bc0fd10a0a4ac6b3a8809d196261188039169cf35eaaab489284d7cd1ecb14cc19806345c308f532022dc237ad8a7b3bc126f06fb25f2b99348e6aec6183a2c97ceefaa6496d971fff5f2b1940939bf46b06a2cc455e255f9c48ecffeaa87c67480d42541db3c4fcb47136e9c179263c5d0980be2283993c928a05a495c44cc6449338592b5741f6f3c9344156ff1c791d55d766c4724d807e591cb6da38d1cb70366dc4733c91231fbe08a134dd97b36b706d204d45e3252930d636f0789a5148a1679f80a8dbc4f08d926c58964c7e5385c0ac95b63c9196ef1b1c65724aa7bf9e983012a64ee8816cd2f4ab27e5926f392bdf145eac6b5f9d1a8751647f927ea7b2bcb79bd3071b71c13347eb1c09ad8693d6c865dd88fe8cee09d6dd88b7ce38ff246f870049b7c5b5af8c8251b62370088fd8f208d15f9e090ed8e2e822fe037087709c320d8003ffabfed9fa68c2cec12302df112af47fa053844034d811add844d2f7c6614cca61e303173b59eb96dadc3db381bd824464569a69398fe99537bb57f4320fcb6e00747c2c752860fc251701398d37b3e9e8b2918f06583f6bb7bf43d9644706bbb794d40c675b1d6e73e6bd347fd4489ff9c844154c8a63748e0db0b177aededf5629e7b7a6b984ddb319eb83f852c1328ff80bba0fbfa40ce2ecd0d10f80c0c3f31484386b08fe0893d1ffe7dfde3440ea44df8caf88f99fc802349caaa7863b7f11031042a930c0cc11f244de108c559033e0c8cfc4ec7a3af8209f305cb2144ab66316f8e8595659002040b2fd6eb629b051d86645666ccac6866f2bebaefd86a096b3edad27012bda6ebc5d62a6afbfd6540ada5e235721510e34825a8f72d67e23836cf36f505c011f3b5747e1ffb4ed4846d230ec82ec6ba274a11e7180ae1ac3f0173efabb17b86105c52f05ae0e2470208b9ccf444e0e952791956e3ee33ebf1032fd13bf7fba73b0cf57a7757f1f84133f004d229bfdc63b81996839f5b302492eda84056a01a8649f81400d0070e618dd062ca6e15e15b425c2ed0f6b7c16a1c35e8f45f529b0b65695c7a781131d86357924f45ad0ba70c89f5bf9c9de118258d112775b87601a33294b007a28dc4d10f217ce233a90230f72ee3661766054eb2b4e891a13f1e211bea653749d67e4e1e916cabbb19f21961c2c8b77aae9172b0df7e2c96e59cbc70579e442cef270b281a34ea4dcc4d7a6e6dfe7f694c447b6c777e8fddae9f45da0ee07068835c4bba751e8dff57e3c399c13d7f7099fb3f1f6c9f49ee71f6df7279381f9c48822e72d8a11e3930fbc05a27e1c2a9aa952853e5ce6cc520bd4bc1071e821274803a3ff7e04823ba2bd4ef97c4119ced7a551522f3cf6ebe918bea12d05f80763e16e18e15b1812f2348637dd3e6cbb5551d8e43ab12507967561afa534d5093263f612ee03c1ca0ccdca97331b45bfa78135669d4f56efb6d6c5d1f0a2b28437888225c2f2e8d3f320aae8f1715cb7d8957cf8a6f0ccbe7accea70b08f4f3b86a23ccbbd29cbb34086fdbabd0b1c5159a515972c3ed1b33f8557e21a89bc15a9cf05f4a86b47892d7ef40316ed426cde968e788caed2171098f8eccd818b7fffff4644ba0005dcecf0c1c0b19eeac201b42283ba00b1b0e11cbc58b1d462b0f82c92098ae6b1c0025bd567c878eb6d627b7c0182002149e173561d0fd346103366ed8d2442f8229a16f9978876537c37588efb28a4f35bc355a5241bdc69c0699caea0a793227b41548c91d697a505cc29cc258fe06e79713483a94b7579e9606607faac010654efa8b2638f65af4a0af25c43d556e61eccb60f4b775d5e6f2836d6c4e23d33919cdb31270d50792c2de48a4e49cd60c376031e6d3214935f1654e2d48bdc3c05a1f19fbe7656cd4538a944a6651c5f03944de7034f76d96046119e5afcc09373ad7bff9c5bea1e7413c26cb86db3113c048f40f3383c695020950a7394d8276c917157e14db269b110bca63b2a53f16d46efc227ea41706b1115b9766058cdc8fcda5907b1ecc03cd85fc15ad95c357d33cd4a6c9ba7da08d37c280e2d73831e9d75b233cff7071e6e17dbb8a6999029f861aae3bad64edc143a553f8a93e2521d547faa2e1a3a4ec0050dbd03129b27e8216e2170f92acab6f40d55e578916230449616379ff48e93ba2097755bb7ecb55168d02df29ead7dc46400c48673598654c4c2ddffc8be8017b83a264cef84a9e36eb874dd59ccd28ccbe4d8382a8840add548c9ed8464c2a78a82d1d8de07fd504282bf02539faf085d487ea7668085aea8e97342809c3c7dc3d974b4d1eff23cccefa5d06d13eed781079a79581273f7d4b7ac3936cf5e6c66cf005586924c86b43f6f25c7e947a13e154625a11051ad6a0df5f87275cc406b502a5a73d7042258c72f1a105136e51c99d9c2d99a2370298263188f1724406d380a6c12dcb1454785cd67cb5804f07d407f0b66e46f99145eaf37ef114600df27abf703f0ddf2249190f1240b81c48bd7bc7f1da8c82368d810d70e3d5ff3778e21b4711ee3fa5403b214235a663cbb43d0d4581f06b8137aa2029433a3a2483101a708d5922c10b7941755fc67073d64c7f0ad49a4fb42af9242d05196651cd0c8020ec54c40a19330400a199bf867665a14e8bf3c903fe1c672920dcd3c08bb512ac50fe6b0174bf0ff0abe4e3201ac707c619f344d652db3104f3206ab99d6f9cb3808b840651e233381ba9915a7e560f09cdbbc2ec153453ecae8ddd478c2f3166db0facfe5c5511a5956445e756046504bad261c07571cc2e221f50660f84c15193000dd4ce367953668d453aa385921966293a14dc63e0d5a7be42fa549df57f7680c1d110ca435c558b48702dbc83ba44096ead39b5c2f2b77df54d29f88e214b1b3eadbe3dad7cff04af8d0ec031ce0e24bee0132f8e31c1c2d3bc86801180d474c45a4acadfd8e828b17ce6ccb95d8590cfc2b5cca6abc290b2b895776ecae31d9f082a440380a8589dbf21c53498c97ca4409cd8a780a1460244aec8d4215a42a8db6242a80af43b9944db18b5074f11ac54da65824a8f4d16a59605e5c90e1e4ce393fec98dc6ee49f214d638c6147c7c181803fe1c4382526bd6582734d264434176f0fbbd67d22f5f76b74300794ee4fbb0b506f0439aa65bf83b4d0abea59f338d22f809658ab12af9587e4a765e34692f79234c64223806e0e95ef42e149eb15de4ba434cef498b8dd3babe925120fb4f1812a27342c9f76d192885a29bfd164230eca0ec6c04b4dbd6d43857d6a0255b5009201abaf38c09e2b6cb05c4c0ad4a87f53587fac32f19a66ef9841e42d195ed08ca0d931c4c563ecfbf5ba0396a35123b4f29f6c6bcf92deb17813b6011dbc21567eccfba2a53c5e378bd42f33149d69dfaaee0384377726aa0bd87f266dd41b5c9322db62842d30780021855e9638255d979579c765efc104254a9bbf04d1a8a2d1249aca311e20aaef558e5dc6ecdf249988e94a8c8a15827f012151d940a2f1723f16fc6394cca2f420fb27c004a10d129c659e82910058b8ca75cf65863df217665e00ee3052af4effed34ce36a3638d7b7d45b1c1c0a8063f0bdd196194589957147fdcaf41e19b704e88bbca560e6c0a2c3c51148c184a49ac0d87a1229be9bf5801bd57aee47289e78751391f40b74689b9b8e7064d2c4bc337d43f316a9069d5ae65d35041f3d4918845a048c4226182d38dbe05d9c0db78a6772e9cfbcc4b946f578acdbcaaeac0c8bf89e8e355f68c9230610be001b5ebb082eb6e1890e350d45d5868c047a52a6bd338bdec1a41b0f30da0f29087730e8dc2b35c17436d4c97660bbb41481f6aaf0845be71b1603a702c1a7fcb2706ec3a5bd2cb7ad85b7eb7eb39f280d2e7fc1ca497e03a0b63cc9f81a6aea6569128bdc2969a99db0d06a5900ef05b5ec459ef5f3b3d9b5395f219c051fe28b09a86ea992341c7e7d0ce58938ab091ee37b2dc331029b5567bc4ad1e9d139ba1b43ee93fa011650a0a0363d2c6cd0a989bb2056f44bb5de4ead30875a6eec662ec85fb93020d82cdc135a00775769fe8e0e6365234a9c7e37a1df9891a7d838802dffb3c4bcef6e6167ce93e4280ae49cc9bef5983d9bffa43037ebca40b072ffa50810a7814e473b2b6f63d151736a9f9b63f856109e74fd254b863361db2b279437b7c9f5be594f3458eeb9bc3308b66b98f8539cc3a801bc88cc54863eee84a847b046dcfb5ddfb04f76c206eac0ab0cf071e01e9d049433fa23f4c5667d2cae9304763d52063eab41df92b04618a48e957b291a72d21e793eb282a32c32f416242c606ff3046d1a3603ab3c5db715fdb138f1172936e7970a240e4562c1ba186ebf677f35b0214b1b96ef5d7a0f940b38efe025b7bce767c89fa45e05f04b80fa594b2817a7c4dc636fb709cf59b1ab8e1b6068264b5019b21dc76a4c34d0d28be8ec949c5d7824e46f7812ea852a9463618b380c4262949c689afd35dbc5d48949a3a6d72dc98d258181e2347a4822d93b2ce23c944c99ce26a8ec2d23ca25590717809ce943fd4d8a164e19d739492782b6f80a31bccd0bbe3f88b22cf108c5922ee1ee087c057c55725088fe3e73fb6fb152d3581100c14f32614554c69a6a47cc326c11c902149ead27c98dbdadf14ee513345a00849b93181c9a7a7bc4178dab5a97607d962f8cbb0b8b848b270e53b84cd3f1ade4f13daf224361464988de08430cbf16bcf48f0428deb12d31fadd5a3a80904c874a910e4a302550e2b22cb4dd8439f7998470a58b07d758b56c6f25122521f32433370c74b5fd402844279ea1149a65aa5e4cfd8c55048a4b91cbbede05e60aad7fb5a2e069def983afc61467b55e15051c237a1e41b207a618eeecb8f26c248449c56fbbcc871811975aaac57554645eea191b82df5991b5441c4c4234ba4e6a4604fb682b127b49ed46228ff78dc8035c3a71ba87c7947947780630dcd5b59641e3ba4a67803d1191463dd7346f518a077a8c290b9c7821b49233f83ec48f5fe1816c31d839c32bd37ca906945c443c0b358647f9abb779a8d0439eadc95f8cf2e1ee772671c814f0714e9c04ddae173fc7326e9797c42c93eb0f7b7603b6688fabad86680770a4ddd154de100f8dd7d005a361f64f590e671e1e1e6c1a5eb06778970c0da71acbae2c32960f4850fc361027a2a1683fd8c0342682aaf7354af709717b97411089268a0224285ad14eb60b3e2575ebaae0e6b39eef47cf0867c2f3bb1f288a417e722c102a6ee69bdc612cc34ae50444546a1feb8d4312feb81f5170067da4c0a6602e0ca1d3a32a91ca9e4247403242a14718e19b9531410a961f52ea04283ef1312f7116ef3769c5622ee205917b74e963dc75ef34db98db726b3d0c292a50388083174ef261c8ad0ce7ce702fa8f9cc6596b810df4c5e7c198c867312ebedbb4c07c22a4e8f565607328c20fc8b00533e2af267e93cc0d8d6de3a639961d2a7d0a0f5201a3dd578378c0985a6dd73228bc2ee09d46a2dca9def2bdfb12d414488deecc65e9c27eab1da3f8254c31c1004091d900ec3057c3153765c1c43590247cebda1c4717b009343e32da4c89c134f8153ab3df8ed10f030acc4d56e961532a8470b55400cf20068bbaaf5632c4114245aca0ce203a9b416e427575c0b6c8183c4209846c486f63d3f7900090443afb6dbfd5355c48764fd07176e7f0d8dce8636f03fa03ab04ac0a054bc26850497c4b601e1d889ad76bbd27630a914a4c43f7742fefc2f0ad5cb1e93caf1cd324d2a17135cfe360b57411036b6f6c9054951680941a90d95c070caa52ea64556e8324bfda4970c5de48604acbd1f3c73b6975002e3fb757847cc4507d8888df3eb24755bd64ca9c755be3dfce99bb8f7ef5c1cd5af786a163ca146be3b154ce807e77db1e43fb09da87653f11fc5dca7974807a819bd7bd6d45f5caf602b84f82a7b233a3b5739040e3d5cfb5090914661db9a96eb4d584c9bf2f570e1098abd7cc142b679d8790be0277d4d2c9b26bcc4f30ed516750e180cb6279b919b75cf56122a74dfc98a6b1de782ec3856baae08452ba7d23cba17937fd58b7d40039346f83927ed802150757c245e5f25ac167f7ea239c06896c7ab5b11121b06c4c4d32803094d4d5ca4ed57a5684c3119e5ee7ffa48c2c4734274cc116344d90edaf5a3c07fd48d808c1a61f9650429121796b049e735e16385375597df28fc4c54726137af58d2ad9bc403669ef0d55bca87952f13821f71f5853b8ea2d984b76b6d9d4610e8571e40a66493daf581e78c7c21c4e6bb1755cfc51bb1f6345b4833c10d127aaa897b8ed7ca349e79821074ba88700ab49ccc59c76d38d4845af4d5d4ca084c58146535846e96ed071d208c8871161ec4089abd1f27ab48fadb2df76683d79b564822a2b04d162b39de19dafef42a12475d43ca72c42821c10712dc788643d73bcee71175191d01eac2551dc0f52b489278ad8de395be3f6b9d78e68ada487e6b14a1ee143528d44edc57eb881b79c2a7691bfaac26f6825630449058b52571b8a599eaa9ac34b1ac412f8d351423856a89f723ade9dab383c1de5bf93a7f2508cd1d9adece7cde64ef179d759ceb4a5301d7e22e06c205ec89f945dbc5d0b9aee8cc48344a43dcbe2386a6ad65fb01d9f6c964ddacde71356fcb745412a58b2c5f2253607fc53bca00727906b17a3771f879cbc6db2e150e08620aa4d02b5723a3dcb213da62c55da46226a1128373ae7b391ade590b2486cc308bbeae73593e6e63d6d6dad535126aaa1d268838b44f2cb2780c4b2f54ed6db7c1c9457d0da34c2059cb2e2729586c1fbeedb34c234eb7559614aa215c2a63cda0fc99bc275d39590dc6e8c2670554e49c861166c9b99361a0d358456569f8efa10ff52a72c229e5ceffd4c51577bd1b30f994c9c3023c2c2fb620389e7ae1264c758cdb0573a7d8200526e37381e016092314a88f6ec188220d380c32577e1ac499f945f8194097212d800c060328194ec6ed66ebf27f910af889ac4d6fe492b6cafe95305c430d85d29c38c1f65f7ee8941673f39981cf6a27455780a811045e7ce44a029f8c93bbd5b2cd26975995df3382ed2717c993ca1c7854a703fd74d937b0b38028740bb54e273690c9f90311147aad506f3b9d182ef3270563139422246ef70daba1d93358ac50f4aca492adc11e09abd0a48be88e249e946b54c3919636be54241229b08e18917a33868f42f405c7c5e10c7fbe00837ea73ce3705f94b0189d9d280f580838c14746e28a0d350210dfda3f7eda8d395099798df9c38b5df9554a5174bbbf3ae036a01c2c892fe5d59f1f72da94f8f5b717b8fd4b3c98c1ab5869421f7a573f2bf145e6ccdff0465770d236ea52e2bf671b6d59f2668231567a037bd425bfe8003fee661efef2a41c247891688a43071be4ca0c44388a8eecd78eacb8eaba49bca04e05dd0f41df8e7393612033588ae338d13e4f80536b0559236b7606db3022e552f5d4afe27f24cd4103a1849f3f3fdc81ea90ee012a218c9ff49d72a3691cc4bcd1f8e1dfae4770b9b0a25eeead38cf3b7261a9af44b89c6f7b11b8d9a5ee08c50c8cc940dce6dca0b56f0091ea60c091a88e8bbe4bbab21221a8f1981f1641ee216cd361e7ddd8f60bd5a2e503a3d0086d583bd76177d0d4a3404b35e6604793b71a2ba924e543782d64e6853455e1be6253ae9caa71490f98e96bc9e8b1bab77460602a3067fa907c182d6d54104881f5c68b203b2f2082301863169bc1b60f88e945a721a255803d4ecd13411af58c45f377c0b7da53e578ee1f9c6a2af820da589d15eeab56ed20f3a4acfb43547ea8f0fb5acfbb01cefdc417e0dfdbe91839786a760b0035065e4a31c730ecf766f455bbf666ec2a083fd8b10c3500856306ad916d64cb365cf8c1dcdf70fcf4e3f887ffdbccb15e6d45b02c22de847ae2b69878c31c7cb8cffcd8f44852bf397bd00859a83e47e8bd440247a72a7484e02fe9f1494fe2cf1ca021c3c4e01f7f1da4e11e014a22bc849194dee1a8bf64f9f41bfba24cd7926be60253096d699f9829c858a1c886b12925e649607b2758d9af60a412083498b4ffb0b3a751228a10c0ff48d3413653751d1eab6e4f51030c4796e404ddbf6c227432359a4f8b58506d239f8f843955429e82ce6d69fe123ffbd7e9996a598c2358e1b503e0051fd024ee86ce78bcdc84f80f0d82b8b96cdf44968662095f7091579d0a99710c10e5e42d1e3d51f031b343504f6e8bab35273c7c2425ae8b596a899238e062fcc1d993508f4ad5a87a6c439ffe836b7bb1bebc797c91a3ffbc06c30e8f5caa221bcf866731f3aa1d6f96ef8b070c358d69e308c6d0b8525f418b7ecc60c36d06396b07d7c205919cb33084cf95931d222ed7aca250a6c768a7cb0324de10b2a0c44908f4d7412d1882b4ed00695b1d36ce56c52b301377f967f29aef7690af7c6be74402d670f96cd37fb526cc9656340007db472f8d8478c388250c5bdf9f7ec3665d23eab78acbe8b9c812d18b4243866ba0286d6012288f1f7885a43791d206874648aaa78189c16ff07148341e12c7fe6a962736835090b849569b1019a0a7d569d978f304a1d0909b2359eb15fb91cf27932d2bc8da820db5da6f374b0c2d349584a3857bb90671a6fd01f0d77f673442493b54651e53f5c0470559cd7764b827dbe8f2c0776356262986c46ce2756a58e646e71e9736ef344803f304d026807d412c583026e4a7cddfa33f6a58dcd527f130502726d2add09f53ec20bfc4afed58f4af89ec685182ce7b581dd6500d36ab8817d066e81ff87e205382c6337a67254aa45efb9a626317144735f8ae110f493446d1353265e8699c821c73bfcbf41a6d2dbcbfb3c5bb9fe9c1ecb745e20be2a9c074159f43f1ff10edf9b98c5ae58abf1bc87b6f9d1e0130774dc484231d68dc8135246e11111e1541da664c9b0e92c2d7095b9a0e6830ce630416792dc87fee6f8e125771dd2b4998fa1c773eed2cc0ffa15798eb0ef59fedd8a51efa85188d22b22ecd9c5503c51dd7913d4424d2bf82d9037ae83962be5a6c588155796366e0f07f085221e57134fd591c6ffd7234c07493adcadb793e5470197c0d167ce0757664040374f65e34298716ec78c71042654260239bb2152562e21e70fe917397db20c65c23944989d8aacc9812a168b457cd40cfe718b7e45fa6984f277ab739e8c241a2c698228a8f1f2b73c1ab111267be65cbb10fa2dcd073594896c96239e26041610e1559d8ea070944073099580d9de4e18ed705928d5a1e49db585f04acac3c75b8540fe93ce46d8065fb4d92562a8cfef54c4652a44f988149e82452351645f245e610758e00664d1856ccf10af5baaae296ee6e68c8ecdcb8a56a2102fc18fa8a5d77144381925084c8065bddf1df12667d5dd8ff157c61c722117179d748cc341228c96b94a85cf280742e398707d34a812cf093171e25900e6d57881cd90b20b080e09161581c35c4bf57f5deaf3d02867f296809a211c76d9142d43dd297eb33012e59741a3386f7dea106162dcc5d8a641534e6ecce5f98ae889d1c4fa5db53883862342f40cb207a3a7701f9956ad16d2490c91ab1fb45024e12de63e9a9b96105d49ee109aa9f6de0aa006fd12909203b8396ca3eab13aaa6416c3d24fa77dee93126ced603220eb08ec83ab9bf5f05b1f4f177084a86085a8fa109f381af96f5d45371ec77d5a113b63b9165c9b123da4309c8565115b5ae002f61ec9e7e1f6698204a8204d74d90ed26c7be4544bd766150f061f816adca1ef95b158ee1ac3ea372c62be6993db0c5a33d104741308c09e3c33c5ba4e958f7878f3790ab435b3016e4f66a64b2f54c2552c05b22e848ec4341e078b4c3c473e6cbd587e28adbd5565cf4bdd57dcb3356f0660c958c61a330baf02a193733afc4c50862bc8ada4cce5dcfc23d8274421006593f8b308ae64323f6a3acb9f8ab229b1ae64dc24c5c0805dd46a8c7eee3534db20e780e7bcd1d00c570399038b0c933c04f0360242bd5f7057b4da878b6913eb417139275e8267a013e1568ea21ccba77f7003de3b6aba80a49b98a63961b2831a81624103ce20b6b985bed2875424abc416c0c9f0ef06c85222bfe478eadd6f1fbc6c4f23a824861108f589b9765b26500e87db1e4fd7137c794d76f86c14201b194da9cc73cfb1ccd2c9775ddfd5299560134a191cc0fb2603a13d1aa350b2c11e90d3c49b6015e40bde0e0cd296efe443bf097efe1204080aef5fc675e89b0447a647f113086d92c368e1763eaebff1a0d26e346f1f5900f25b8425ba3fdadb1c3bbb252bae8694742c014dd4e7bedb09dfb748a6611a1181b3890a1f11ff1524491c928d5c02fa6e9bd17c34c80e1b0043d13d02f869f74956884bebbe4031a7dd362dfe4448a64de96c8c7844961cfae80f1e52b3e1d930da057763a1eb46b3421fb35e4f09d4958fc111ffd368bfa63e10d59761ee94272dd1878075020b017ec52d5b017ec351a303e2a167d15d486348f72c2d1087683e7d7e52402cd8b046a43d0532e7f9f9ae5503daaa70fd9758e6b8b0147d17bebb8014f880f1dd6f41560c422a327bd10636edc096da433247d26117f0599e86b020962d2c35fcac47c374bc5a0ae06300cc610b2db265013d932bf194cf64a0cbde1a7a9653d5f985395d16b96db8f1cb5b3b6cbb3d9f335f5ebfa2170a057c6476903c3953312a8499e65e6bf55e7c2a75e9ded541bdcf19653ce9935a7a9f9972e55f11d3e71eba664cf8d9bc3f768295293f7e7143ef2eb24e6656eaaf53ad2484407fe974a3356d3382b0d68aaddb437b23338573425e3c94dca8b47c242ba9dd402c22f712c08c54674feece5dea7421a498e9ec4937f54d60ac7126efac65b7284bd297549dadd1839123e474c29d4424b1052cad4baaa4cbaa2971fbda19d3d18b5eb6c23cd93a9cfab399d0ab039dae39c3f7f299602a93b25d9230c2eee7aa39d91a76bf80a572e661f3b87be529ded59fb32483ee97c6edccf44918c477ccebd5e31dd72f308f211b6e21685b0a1ea560a1391ed2127b3c4f63583c17c1eddbc3cda3451a48002fa0cb2e32a3f30d58309afd375d298548b30da6801250cdb82bc926c69b6994b50e50f1054191efb1843a54e32df24ce9d7afa2fc4bd6ff015fc671677b4fe77f4113b5a988f870eb6650401050a847bb7efe4330a2ec7e5973aeaa632d21b11474921a2bd4f836a6f474607a3fcdbc20bde9fe6c509d28f8ad21f73b1e0a9ef6dd6643ddc84bdcc0c3017163892f91eaa7d37217a02f040a27e0e594436b541b516cfa434566e1075d988016b958ccacb70adcd6514babcefd5880e4aa20c291dbdf6f603b247995f524b66aae22d467b7793cd504ca1415fd04428d9e3f6d001522b0d9b761815e393fcda1463693999622e8ac9002370f2155d472f617612524b8b3929c0dd21b551c777719f6c7c9fd241ac91ed2906f29f825fa45874a8801b524ee428960d59225b5599a2c725bce4810a8ea28b41fbb66d13eb7a32420648091d41d14f4d62d90da44324973dbeee1e1abebb4f0d00a10d3af309fdd3fe4e4847f68cdb4644ec86e10f7f2dc64a7ee4445856752a1c43d6daac15a7fb130246a827261606381b43c81bf2a7a389bf837839527f9922ef5f39899b6e907618b780a6277d4bee5b3e0e8b1ec62b1761e2cf6814f6385ec53ebc915e34e40f8d5ac4d1a1a58766361fb37220fc24a4f1a1a5c9f75a4de6a39e56925ebd7f3da6a38300d9b313dad5ade83dd2ae6ec02adf835e85483b5224f66b7c25efac203b062d115e2eaa70abb4e2a0f991dbe1dc7a91880c08a243c699eebb47c7fcb8846f00ef031a2c99d0e87b90737675c4ebc9e27560bf3819602f619d06cf693ff24fa92639298ecf537f51032a8053a1611a651e2af074ddd8fab9f0191f64cce9409ae808033414da46da62389fc708bf15f0a066e8390379d2e21786328695587c2899f1858bce7781adcd77940a19091944850474404a1415940f3d8108f8face2ef22b42bbc7b7a9b46415f888e2aac114d4d79fa317a5e6020bb4004944312191279ffd935ff5a86aeae40ef191e91176bae7993fa10e0dc6898585a71024f0c9168f80d9ae940bc54ec89163a5ce02f19491860bbbd8843769a8bde3861190487da85f41f8abf5625bfea049c4207d553ae114a575f97341ba9b7be28658ce7c1351b238588cf82c70b4e1fd1742c3c9d0684130619c81e93ba34f34b42763dcb1957765c3e03369bb8764c2a6ae64ab9f472b6e66636f1240c3307963acf819dc91173cce1c45350ebc21e2e8aec2550edace2102f6f9ba8dc5a4a240538c0badce0514581aa308d6d4aabd4400662f29e3740bf4db90f81559722ec0588ae7b8c7968851df89bcc0569ec6e3e7ada8c1b734929e7fe0467711513636a9fe9567516712ccf17a7c9fe2216d35b2f69ddc879e9ededab83706402c5acd1d190323fa3cd2dff3a889d5b0f02d9b950a801954bb02ea5bc2795820d855693af915f34d0ed387460acb8585a001dc3f6488f7934dae9c26dfb2d230f044011e3590fbe29f0d4030fafaed3adcfefe9b2ea64d4dd702237cf98d548267b313fa7b79befc65eb6fabb68d9a88d052237bef2d03d9075d07ce0739c8da97ca4003b9848a4ca2d2d9ae03dfada440d4095cf9f695d40a0882281408822b9eb59d65016bf66e675592265e5e969bd7dd9ae5077edf497bcd1fb8d28228f9258190d1b80213985045169678e890052074ab7377cb325d71c5115660748422c4d082229aeee04150fe17085281cee65b5d4686cefb420b983a218a2546cbfb4353c7e57d59248da6cecb1be61df3de797feadcf765125307bf67de34ef9af72593a933e32dbe5b6f1aefcb28a6cecd7b86b78d37ce9b863700de97574c9d1ade97594c9d9cf78df765d3d4015f17f2be8bee0079bf9b936e59e8af7b773bffb4629e764f330057e65316008cf25c99438d2743551baecc286b83b6857655b09e2bb3cad663539900aecc294e009b4a1bc09559d50d80c3472bf6862bf38a7743c7a201cbe6736566f97cbc185cbe32c700f3b705d88d57e6d57d38e53482de17274fbb2d1e01aecc2d21014e2edfcf95d905f513d214c17a775732b35470a05250a8f470e5f8ba400f2a29095c390a092281d40beac3b192f96525876a0bedc2a8e8b832c3b0e85861f1b41b9302ba32c7c4006281812286a7dd1d150e57e69d55a7f274eea728e3f293c13244b06a82ccea2c05b832dfa5002d37c6876325f365e5e0821201cbd32e5e055d99f14b106b85c44ccb876325f30c8c0e2fd8091a17035c9969620c0053f3eeae64ae39408c8cd7dd8ef12670e508e49eae902620f315cdf8876325f38cbbe3d7d3ae18c3e3ca2c621e177bda6dc9ec70656ecdec80691c0157661a340898f1b4ebe3423e6868b6e86209b9bb2da45d9bfbe9983901d8e6460a6abceef615125023c3d3eecd4c8f2bf3cd8c1e325a42ccf0b43b038d02aecc33880a98f1d540f4b46ba3660157661bad0588610f5a9e76716430e0ca8c4383012d1723d0f0b44bc38c065c9969a8d1001a6117353ced0240e4e1ca0c001b1e6ad878daada1e5802b730d370eb00187b8f1b49b43e30157e69c191e7073246ed480c095f9860d086c61c3d32e68f3e188c00b8ed76911b0d11444c6f1baaba3b34c37c3c675de079e42944a4ab5c21263d5e2c27a818991f9c53334353266882d1a356c6e6a8037fa26bb173dc81d8fa75d1fb742174ac013f0083cf7145e2091fb83801743e4fe1eb0fa42eecf016117b93f1e5c8c90fb6b40d883dc1f03be1ae4fe16d0ca3d851584c8fd29000443eeaf87ea07b93f04b4c820f7b7c36a88dc1f8f2e8a727f3b322eb690fb439a400b16b9bf036027727f06582191fbd3610a5c8820f7179455c10a790c597e05b83645159a90fb9381c93d45157098c93d45155600c492a2638b2ae48ea50a096c0105520f5b1481a3850c3fde17045045615c0d21b368e083734f4185a31b68b818408c2a04b0f2440f8b1236b854724fd18424773c0178e7f2b0ac73e50821bcebf2af950d99f5accdf96f7cfb8d9c1b356a0c40f36ee822703b43e0767ec0edec80db9987dbb901b733036ee705dcce0ab89d7bdcce09b8330bdd9911703bef703bf3b89d77dcce13b83d1ee07636c0edacc3ed1c743be7703b17e076c6e17606ba9d75dcce396e6709dc1e71dcce3fb733016ee7f176ceb7b3cfed7cc3ed3c80db5900b773cfed6cc3edcc733b07e07696b99d7bb83dbaaecc6dbac9c8d840a41eb45be33b57bb8c8bd4c3757a38906b9424c302af80052a7021d72889a3c08350e0473efef8eb1a256d13f80526f0231f25402383f7462b47ac6106006464140868a8c1a1b1313303beb91905820edb3c8761a82193c330d088c92810b460c497192c242007af8c7bd725039161810adc9e376a5a68563984321303b3dc957cfca2ca3ddc54ee4f4646e6864025774d513121cce905e4380afc38ea045fee9af6709fb9bd07bca2ad6c0212b83d6f70f865e3b1badc3575e172d7b4e553dcf21796c1c6d058b2154c55535465a264e885148b5727fc0282f09593b31c57f3c88c35c9af1b346bed66b5cc23e26122400912452f2bd58b76d50b0e54ab17d54afc0dc29777d58b17365181054d5f2815e5d70da167c3a963431b53582fb4deaa562c031b765d3a220aadd73c950ebc50e5852f3ae69257ad49342dc114e508a52d3425c9af9e82cb86143b905f301d910455e8a9848859bba7228217aabcf02b1d84afbaf254d5326d4ad08e28c2344dd3340da394524a69d530ad0bd6064644d142925a6ba55c300226842b180981054cb5663f59599b82480b986358f3e9ba688b1e3544f812b97e911e779f49e9edb1cb9c615cb515b514cadbaf4e8d522cf4407ff68952ee6cb5dc69d826b1ec5bb5db0776dc96dd6edccd9e5d1a3e9cbea14749114a10048360973a60cfb06f57669be6e91c85239ad82cd3589acd3aace3366b33db988661dfbfef62c7ada4ac5995a0e536cd76588759387c9fd5b4d76aadb4dca65969b59417a7c6acb5d6269124bf4e799b5996d9ce6131e6819c1da0d267dd6d1bc35e2ffaac6a745a6b3bbfb2fe9c147fde8766f75583d4776eb93151fb2a115e47fb90bd36e885fc76e9e757bf5dedf4aba2a8bdfb4c2ff3e6372390b85c3838a2f8af5661a8dd20d434f1db618edefbe321470f9fb69c085f9e0fe53e10254a7e35175e56aa7622801b99de644a6b9aa679b4d3534caf9a224e13423a5f54ab7b206bf827297c7997c915bf7a08769cb2ced9248a1d2c8f86d2240a5580818820340561ec6e5926b216624ba660d282cada3405d315b9d64f8796b38f4767a10927241d4142055c308dddad7a6a73866db5b3cb1457aaa4eba9c3a24d710e62a6a75894d2a60586248aa4248922050539070f294760db462b0e54c4719d865ebd86bd0e7b7debc86efbd617c9b2fb2a92e5acab41341d341ca4e2202c1cb063580cb64e62143c358f3d9d40b07940f0fb9ae7fb3caf793cafeb9aa7eb38ae79386edb9a67dbac6d1e6b9bc7f2685a96354f966158f36058df64afd5d2e6f94e3f9bc7e36e7968df503a9b677a77f64dd63754f60d9df22a52b7cdc33fb464efd672c71e84db2c6abbe5501c26e28561188661185a7b4ff73524d377f70544a638c8066edbb66d28d4c6a15028d4c66dddf5b31f460ac3300cc3303c9d408628140a8542a1c2300ce9e9cd5e43f23deccba1293ecd18473a9a2048d83d9dfebe63180dafd2615372f8ced29cd139b97bde766c37fbef2584e8f61235c9fde59053b2ddba13a91bfea1256f14079b89d46720883df620f6b4ab5d67ffce62229b15a13c4ae5a22ef6cea7a2dc7d13e21786e93bdafdd4bd9bdea78328d7bb64281de661efbacd039966465d862963ea411c69a31044630a376d514a84665957bbe1a039eab977dcbd0c10657a6b33fc438ba559ea767b766b5f94a6408c1482944aa552a9542a952a954aa552a9d48fb8c6d43d90c2216d49954aa552a9543fe21a555b28a4993e29a57c7f409dbb6d5edcbd1751931c4437c83c0b872dd30bd1f20f94285f21dc2ea764fae1e0b2036853a64c09030e1b7e59a4d4e78774f431853dd04c63120b875cb352177090e5ed174b594a89012d6727caf3c301cc19ce302b87ecf4990e3fc85c2f44cb19c59e54992a35e830d94c197c678e3b91ed2f2032f8dd12e9291949e52ff0dc3bf0725b7c180672e7301083dc7610bc2fece07d2886023111fa1d7ac43e1c20f6d309c33cfc7d860f77a07010a80f118d28ec5d9f751241fcda91c1dbd3e927ac9b64300891edafef3fd08cc401d43b733b50a80b4134a2b010a9871813a1f788c829b98a2364d91afe95a8a754f051cb88c221405d88300cc3300cc3108542a15028d48fb846d43d80c2216dcd300cc3300c7fc4358638b422a43b73ce09ce2e041289e769ed042f6864fc256691026d0303da4676159817944c0ceb7d1f3f9ae754f3196fbd3f334a629af66d1b0c14a47b9fa9ddbf139384afe6e2bb2e67ddef3017e9fee5b6ae511258e3628d1ff948e333ae5152872f03ffc8c799c75ca4a38fadd75ca3242b9e46fc918f33b8fc5b44fb75c145b61c8601fc766d9601d6a071d40964fcb7fb76b52c030fa12d998bd47a87676ecf1b1a0632775acffe63eaa09ebd75c3c8ae17b0560b0fa12df10ecd56f6194675c8143d1c4269392194d6d2dc651c076edbcb8ad3ecbd45b40f6584d06a9ff5f1c5c4645f77edeb0f07f6e19039b86d208eee1a9885bb1d467e38640e8231a8cb097a27103caa2b456029a50cb4427bb285c115f95657e64f7e38b695a45e4b91212db7593899571ecc07838552d6aebcc75d560b6bf5ce5d8643e6daed8ce2503131f7993666d3de8591e587343305164f727f4858a3026ac3c28aac21dd18ef322e9c82cab72258ee33b3eeb86ddb4299235a40cffb50dfb61dad3e73e5d81243951833ef420f5f39ce90814ab5e0772a0f2573b7c75c20a6988560d478dc8c4605d4bd72acb940d056769afb0d41c1cab7bddc77a92e86b75d8f25e6cb21738c772aef7489f44df6956f48df646f3921860cfbc409e66e7fb940a8a8e021458c1072b755b619de8605135965bbacb3b05c99592df8cb2133cb3b176fbbe1637c3886ac1471a1f972c8bcf2aeb9cd7620056256b01d42db0532c51f7d937d061684406457195224f5a332a0209617cf637d3beb85e5e23263b90bd2742d33cfdc1ef1ed31465d79c9a21e53f7e565151438593e3bf367571f8e15c86d2b9f0f161f2ef6fb34cb6d9b64610253eedc31645350c343425ca4b5c80ef02133837d608df7bd00497e49137444acc9f384a6138a27b39a651acdc2dbd88783b336b01f13942a9050a4431561a8dde994e258b815efe48228f965a3080b2c8ed00226412d40aa2e40d28442f5e4cd06a0c58ce3388e8322a90465b697524d7616ab9b05b7efd2d971d672d6a2ec59271668cffa6cb769dbf65dfb770b1434c5ec9b0f11aa8042c18fe3388ee3be5d8e66d409fc3ebbc5269bd80b5eeb4e53e84f07cdb53b8137f74d96bd450a952b47d505a2adeca9bb83b6b2cbcf870856e59d4a154d647b69965f6e2a888842d6be1c5d85dca1b66f7ef2035f4419e8cb31732bb178f6524ef45027f00bbdefa35de7626956b93d56edfbe8ecba8f66ca945f3d053be7e6cdcb85942714089e4ea8d3c9cb49a7d76831eb70f5826a9d7ebdf6524e02f8002d41f16e7891587e6239015aa7032d663a4000751d90380062eac82624132368eebe4bb39d3adbbf0fecc0203ad873ff30fae1f8386e0575fa40ee6167b10fc767b5bac2711cd77520e6bccfda6db3a8d307721cc7711cc7711ccb954a662b7b27514b082bf6811225e5776d9779dfb5ef2facb9cb6ab40e0fe2d459c014b3c6b1d76ecdd7bb3577dfd540d409a45d065a478ea0874c1d7934c54c22914846d034a79e758ba967aa67aa7f2b5836e99b4c264141c1ae5c0a3ee4a175241148a60fba2393d0bb724944eb782167cf8600312ff540f0d34e5b58395db9c4143d2c9348254b487d57fbe77da90c4ab90b4a2c1ee1bd0b85f7be87e043fcf0de5f218b87f7ef49dc87c27b58e83b8887bc7f38f4c29c85b0476efc0ebeb2b89fb6ea89c6f0b238ec8350780f0f85b45589c6f7080621a78c491c9e7d63bfb02a6daf3b7035a161895692fb6ac92f54e6b473df7a671edd364f77ae489683cc572be5b09100042979fbdcbefd5524eb70a284f2892872fda69dbc1bbefbf71eb9ee7b8ff01f3662c4e3be83b8694bbe47162739cce2111e7c8ff020369264c4881ee0432c91d0563d884f5bf33d4eaac94f2589701e8576151e1dd117cea703cc128cbea98f59124e3a2f97eba7039dd247383b6835b959d23a8370b9c3ac1cba6bd7d1e12c73f835ff83b69dbbdb278e0d2b3d31451d5a964ffaa67e5e2ccfa3a227e88e1deba5927cd23c5aae97452f5984c4089924d7375d62462122ccf0d014ab58af72040645d20285ec2708e827a8d63a2bad35abb36dc8548cc0cb2d850e5a905fed248a13356caab5d65ae79c73ce39e704aad507a8e27c4ca19de2ccf9c97985d55a252596dc4b544041a909e342a74416842f9a9c14810a3c50a29282706a35cb5eb3ac52ade7a747abb5d65a6bd5324ce585248ac014250b2665f80cabf5263683052a6900235c4d71aae457731576a6396032ad27caf5062884f206a6509e8a393d3d3f3d3e383f3d3f3d343fb37f7a7c7a7c7a7ee8ecf9712109e72715214dd602480b2d1c30757a7a7a8a10444f0fb60295dc4b54206972e28326273e403f3e4073ce39e794724a2a65954a3f800861526aca09cac9f15152b25628bd5a290b29817e82e69c73ce29817e82e8fc09f2c9f911f404c8b5e3c9132054247d3138d24433715c52bae47b6cea33978feb72d219d2d965d7c7d5e3922e9f29a974f9c81cd1f52ce4a4227cb932b5f1c9a2c7e6278b2c286d572af752116890ab93298b706acfcffccfbc41f3af76320f34451af467aff5b3d65a6bad73ce5ae79c13c814e9a30c993aaf76520488a1a9f39258c815e1abda44e16207e5e2c7121542a832c0e9f9f9097202e40afa0972621314f403e404e827a8d29f209f1c573f79b515289df9d556a880b4a1a58d79fa974d7605ed88e2fa71e2a408443809c5905fed249b828d6bc7a762977fd58abdfe27e7f23e1748ca4c66a7545249e5059a22bdac3407995c7d1a87ac3e40b807e3f850fa3175800861529a732afd4c1d20fa201aa5fa748bbe69f7967b890a2872c54f3c9103da447772666be6d44083509e4ec144965b0a2854802101401898f44113ac9837602288237c10050998204a41156068220a4af8c209162cb0718515b658f204282c5002510e6030022bb6d0228825444939618913453041c1020dc626e4a00627d081133c504209511421b0020eb0e8a288055890d244144b2862a2044c4f2021ca8747e048810c4bc0200462a841113c04c1648215dce003433861ce9b01397db0215f64956de81b99cd39b10f35007d2367005a949f5f569fcd895557dfc849a79497a73b338270c4122a5c30061e1c511c84010517bc78d2832d8cf20a5e30297af282112021490eba60833208f1052ea68cf24d23595091ace45e22c20c686e29769082fcca691aa2698818b99786c0045718a5525969cd04c02aad35579c6bc52af5a918f66cc55c3f53aca712fbd0cb79a0a983ad807c7e5aa9522a7d8070cf14a9085be90710215387a9befa7418bec225224021531ca50b2a71a8d8fd61fa6063bec82aeff40d2542a748c9724a2642a74cc912fba0bd88cd37fa66beabf846e38065792bb3ced8fd61ce2abb8239324b5cbb33965f39d9956d5c53942fec14e760d85f39193bc5b0105b6118ee8942f660393d1447ac79f615425347e6746b7ee6d89c55eea51f4891297e85399ad44430bdba6d9d56b55f0b67cb72cfde53a74eb15ed6532d5b4fdb9e5d95eb69bdf6752fdf7a82a4bf421fed54bb210d4d94d21d7a349a97679a477434808115f54d7fce9f66b0408bfa664e1d30b936e99b29a70930d9042b6a1e9a84fcaa4d34911be72ec2da04264b5c69d842286172ee22a4472b7932cd682194af4db0a22f388079016b324469f082cabee18850b4993bae1c2861bd2a87e10d4d84af9c1d9b2932412795147c11e59cbe99a7776699336d9041615ba2598145a7a4c40aadc31fa0a025254b5db8baccbda48449ee722f2949227bb9979444915f22d3096cb05841a701254e29d3d7d08253cafda61b60ca9a64faab206b92fb9f0e702a865f5e2c8e4f11be6cb0204153b6993aaf1a353e5f46104e99676e9af981ae693a53791dfd7274ed0a2dd5688a529ad26c778c5ea94352eddfc524b6344555941ee05295f2a1da272f65633f6a400aeb31074cd15a6b5f856461ca455b99901f407ecc1d397e394420cbff002244c33ea07e3d4bece3c79190a9f3924640a6cee9d987482317ddf9d1adec7605bf6c9872e54c8a1ca01220822753eccc852a53ad6cf7b6abf272164e31cb56b00aa7f0eb2ab7b73b9683fb77f9e9a057b964e5803a3deab4dafb0af3abb978d58c7aaab99a7f4969b3bc968348edf57a59a6ddfa225816ca300944f342f57828a9b1d03cc543f443f3444d3ad6c86acce2518fbdc79c98d546e62b66f57baceff166791c7997fce545daeacfcbd444f8036e099c4eb84196e5b87ab6c98439953042f684252594a0441319cbbda40413295a5c51832dbed0021464e0852e884ee00525902085214409c210c67e03e93134a1a088221d0c118512c6453642092ee8c20790306232f6db02f26d8940c5d048c822f7d848c8f28d3384bb1dbbd9b1eef56ab7692a541802a6a3332625ae2eed18ce9962cdd18e69b856ad6a5a4f4eadaeaa6119566badb5d65a5f6bc52a56979454e045a70412ec5c6677ba2b3f1c56d629626f0d958010486c73e19c29f6c85b4cb3d6f64c11bbbd3918e6c26ca665d87b300cc3300cc3b00ccbb02b744db365d8e9d06c61ef23681909f034fa761d28b6a1bec1ded57045f89a46598ace349a3a47c78832ed1e7ed9305cad7e511c6a1e3063efa617b3249cb9ebaabd8ef983ccda3fe93df319d237d87de88e1db1030d691e2d63b7985d1d2763328cbec1de7d9b4753c430179969cf82641fca241269238a103332c21ec3b095b5525e8904cbb24b4c66364332772496467892608ad85bca6b1681cd226611d28679ec5484466019966559964924ddc28e61198665f8157e6050fa01648a4aabb92486dd5e3f934c2553e784ba977159b530c9e2e5af69c4ed7115b324c41eb324cc668e4fef4386780657aa7224c3905296d09e8862e2207515251ce4fab9a4752492d94cb25192ea473edebcd2bc7e2ed13c2b97f1eabdb2c82635f8e6af79b444ae46349159a8344d0952e90b02f879d76e912cabde498fe6d278cdb5f9cc6d1ddf1a5fb9da279277913066cdcf9c68545da41997317321fc199a63ecc9a8b98c138d98358f4f341e7d9c81c5e30b891f9ab9f88985661c1fe321fc192752a930d28cd7d49c68a4a1719a0bd138cde7696c5e83876a6ef3219ad338d1882f6b7e86138d33ade30bb58e3f8ff150d2c442353e031ec2aff1a199b74e348af7e61a1d7d9c712953946153e3d2b0ad5bbf72b52cde19771611c1642e41d284eec8a6244bd8ac65d5ed2c9bc8f052589240054b23a5d028579a9ad70fa9a19939fecbe69acbd22ed25c96867d10a279cd6bf0d08a0687e63de2bf441aed3dcefcb50a8f67be7fe6c61cdffbfe40eddaa62df95f991b766b3e861ee6764f5ca728956418539422a314dda9648af52e5736512497b36e53ee22c5f87671cb3dfad8f2550b6e7af43106964c4c96b496c1dbf9b3b91b5d6e8f1d43c37309a515516aaa0821fb084152ca9f29ae40993adc549a3a53cbaa161d2d1e01861b597a6cb93dc2dc1eb5517e3ac01cd437558b5912da9cbacb7bb4fce5a9bb9ce526bdfbc42c1c6612963251ee1eb4a35e86512f9beae593fa956bb47a8cabae11eb2c87b946aaaf787289d55d9d7593b6b3dc231f61be7255f7e823cc3b23a638b398629d5ae079c514ebbf7bd781227cc9a62b5a473671a2c5fa9984eea85e2f9fd01dfcfab9d43ce1eb67135a59c0d7cf24a60eea5532d15613ddb9af32af471f557fcd25725d527a22ff749f2cbb1aac085fb2285f36350f1199a9d03c2ab78b64f9243f918e8eb4824f1769e63567b92bef41f315961b89390d7e12f81e2ca7390d36b27296f7588359f3aaf778f47106d35cf51e3457cddc48ea34ef3173d555d848cb67de63374f278c34f3bbf21e317ecc7bdcaf60232bbf8fc1469284567e6f24e6f83dca5cd6bc8cf7f8cb9a98c5e3af790f99cbc0465a2ef31a6c2449a8e5323792fadf23cd3dfa4873d5353af291e6471f67661220cbbd32d8fe366999e6cedc9984b6eaa5134d9ea03bb38964a23b5309df1eefedf1b74799db234a934c4cb99e45e61a88083f2c9960a984be5e1ed19d98d7cba296942cba96b6ea636ed3d45b58f362cc654dec8390ea2b5fc1432b158eea3db6fc25aa70927d8fd62869ca03a13baad7fba03ba9d70fa9d9bee5ca26da7a22a14c236612534c72535746a1adfa953b8b68ab1ea6fee5ce26b455afba4deb596ed316eb32c9f52eb7692bc60d62c80a4f1dadd35d5fa968b16a99bbdde36a64b93daedc1e55b7c76db4782ef54db5d9c7259b5c2aa2d34481a19fd1f6714db16708217cb9b087452af6bd7b07dfc33b887b7c07ff1d9c180747dac01d47dca8fc0e30bfdd8543e422ec2504a129f7129cc8a2ab6f6903c3afd99f4256f22281d8355bf47473d9ccdd9636aafde69a224d0ab567363d33c32e1f17e683613eaebe3dae79978fec76f934c699e2ccc2aee67401c10679d6fb794d5c0b3a7a6bedc67def4b8efba6b5567bc67df3b1c336b17868cf2ea461234915c3421c16b191ba35cdceeb3aef1a0eedf6ec46be6befedd985b667f8bb867fba63443bf7fe777de896dd5e28bb15b5732b1c6c2449de883ddd31629f5dc83ec3120b6dd770485bb3db6edbcb92d80721fb6d685a8b93e47bcca2d4a9858122c91588293e31451c6d73bdfc42f368afefa695604ca529ce287d534f8da658ef33e911a64553ac3a50d0a28cc9a2790446f380b99e8649c8cadfc1cfbf6811dd69307610852aac308621ba1065ac57698194a9c3bdd6cfa34ea42d7910b3b4a10fb3344d231a59f2e189c6efb224e81df4b40b36f8cf7effb076ef4423a761ed43a2878742a229c1ee1bf9cdaeebfe099e8e3a0985ffc20f7be7b010f8fdbb3e68ab1e7577dcd3e9df392ce43dc443a87ba71f8b07087add245614f8f9fe0e5ee83bf8590f0fe1d056fd879bb666f7d39dd3673deafa049872a8a46b1a277b20640ad10082001040005316002018100e068322a1400e4589acce0e1480137f9a4a5e3c9709e3418e433108a230631021801800003006c0d0c8ccd44a002c65dd041ea83b2308c735f364618eb383e89ce1e02d893be5d87992424f33ec1546d066acc7fdc730d63f792b07f19e486d8f8975b7da02038610a81a703bbbb4b4d09a41550ca7b0f45d61668531d8bd72f1185b83291795d18171c4feacb5519f8307d99d8d003b1a63a47f180f481928379017936a5dd81dddca24755933f9f7cc0084083255281776248218ec974ffbbc99f69bd0b24260dc94ae21e62309a1332ac2a8756b0e2d8957cfc7fb4e0b6e3bd4baa95a77ddec4395a552afb66f7b1d6ce6ba3ae6a93e476eefee046ee93b516f4971ef5bb0eed8e4100f3a2a9469f30b0c0711ff8a482586d9192b6b3101cdc50c8ec2920c2ac8fb7eef6f2a5940491e0fefc1cbf2d753ff08516e6a7b81f112b39980a83a5f10481418d5cdcfef5e34ce826fabba8bfdccbbe20e79d3f1c8a8ae0db05b74b69976ddea87349214b4cdf0887f45342d79998b234b2dec0cc8210d9996d4b6d10fa62502a580c8e7148b259afc9f099669ec19d7d506d137e44a987d455af236bf1bbfec23667169b994bb0b254a228f66422b95ed16658e23ddbf65747592ec2b61895e8ddbaa110410459f827995c8950e025c04175d1ec8ad316ee9b164f9006c81924c91834bc99bd554f0fd5a432d8c76e8ee3091ce99ca0348f15586ecf377c4670c01dec36ca0e8cc0d786db1d34243a1fe854239ad8f5a5d3b32ce656ff466aeac9640081e7b2fb22ba06f3492be069cd984f3e501a26db07f156c8630fe8d9366b6b6debc38add52eee4c3f172765eaeeae385a860b92959483c69beef86170b13402f15219df76f6bc8c8326bd1e6d9fb65a82e05f25e97c2a9691da5408bfa53299f4ba1999765e4e2e3cb339679f2f16587d014a1959249ae3e462072b7148f3ea1b6591de41ee8ebdd282c9ead76c0d6af053ae0a314be355ed18314133fd2af0b9bd4a94fb54b2e9dc7939db1ac95b4fb5106db533859c8699ff9e10dae3d199590877f451dc69411bbbed3b957d8e16f0025d54c1c66b6a36ca200c3c8799ee46c7e337a1101f5e007c49ea6b5d4ccef4d2c51c62bf47e40d00f1be94db968259363f1f7ad6def77808f11a64a888a60c288cf18701aefd8d014e69c8752843a5227b154d27ab9a3a1b70733474562dacc8f76af56b9b15d25f05df375a6d01646adb497dc18c55fe5b5c5f4b4825d1bc0a5b461b7d2a2e2e539cd10d277fe32f3cc81c9fb9e9d77a49d870fa666a1a1c11b43ea520475b3b092a37269c1bfef91159d97c9838e50cf38a56489e7035d48104445702eece41d12e30408be05528da907933717341ccca5af76955d67a67f4077ad30d5250bd5e9583b68b46ec106700ddd5190f8106c5631347a001a02500d225d1db5c4a71f1ad35f8ad939f06d4e6080bd9a8d2bfd92bee95b6e6f20a9299f29dae41256df1544835d85fe6a131c85aae022dd5ccb7a25d026dd393d1a89124a92ba51b560346520a29253fb2c0c224258b1d747a0cc741933ef0d02b3624139907ab89b90baadc0b609364b8ca7c55d7ec80f590948cbb4fe8cccbed92a25ad4f74f520e805b50912847bd324dc882610b25fe772e15e293427e967eac6a27cef7f1f62de1ed22499941e072a50900f6d43dfd210e5c1c91f63593e7c37ecbc03b6b7faeee63ff87d39d44c4c61df86fdde6a71fb2b8e36b150ddfd3d31873fc10e3845c6b4a08c606a8c248a08a67bc3b1995c284665793929aa9ac17deb02a071cbaed4996f12856913475b29072856e168c9dd56f20d2bbcfba1441089afc097deda5e16d4f2cb029338872099b77a099daa446909fbab09f61f9ad0555fc987a7424e669810d633f07ebbf3eaedf83dd92b36588b442786176e45a04ecec797739d0e6e0798c1f825a3d56b2983b590e42d31da286e17ee71324427199f4fe41f938236634ebb96b394398001204a998546eb5eefe6f825a4d5828999c1196889542bc9c30bf1cf224bf5f8a1fc4768f7d6214843d189b918c6e0f7840c5866ef3eadae8317c60afa1fcd82afccee472b2747a2dc4adb666ba92e490c2d50a05823feb90d2e2491c12646bd6579c91162582070123a811078c262839a8603285e2f9d0871218175d13d19c108c9168c6613202f2f52e50545ccfa416c8e27f717b087edcd84ddedca25e4a92e451a1b856f72d15bcf1b0e76a4404017b0fcfbbae4d844e77093b0f807803bd9435bb1346b429e761417f9b21dd4bf916ebebe788684a75190d8ded16b1f6b7ba456455fae21dfc1d355be2ef69db9c3101d9b1a5c29979bb9c7f7e8de125fea325c66ce689555ce6f936f04ed3cc699fd32c432939e4209bc3f3347512c1a7ebcd87e3ef5249fbcd8edcac926425e6a845386f5424ac6426d5b047a62945e4a17a72271fb0f7215e43613840c254a73b974ef3c205eee795ff454085907167e711e8a6dda2fbb9a1b0d6ea24748d8ea0e6c987e730288c81c21f511c0977b4ae550db7915bbf252a7b0de300bc602afc7d5c7a2494dbc39c64486a64a412efedcb37c8f703acf3eb848d5ca42bc13feeb302e5029047bc7a2ba426479c1e1a6fc65fcdf90b8deb9cc199cbdbd98f4c95b861fde76b99eedd482ea3b407acb3dac9c4ca027771729c12ff14699ea8f827dd504bf99e592981916c39b10c7755130f0c5dff71c7420e1a0160d39b7da0b90db76d19315640b40945d4ceb0ecfa8733cf2b18a4d81f20df4bda6411f8cec0c5ef891149aff4327f52dd0d2b44c77932119a38482683cbe195db621e75eca548db2315fc8761643819127b238c5e95957471d9f692e715abf7e66ab77346f1de3c2b28a2224a22695911c1ac251f099519c42ecd6e87136d19d9cc377b0c0a38c3c8d736bd6ebd16f509c4c5e1d6fc12a75ff8a1534a0a717425f53a7161b4700651981349cdfb4383e475bc393e9eedf705b2b5608c261ffeecdd70681b91e146cd890e3d55307e4f48f6fe5de0167cdb49bb87f1a4768838a71a6943e79994f3e6b68f9752194e19c0910e2bd74bbc134abb556a90e12b2abcf962db5f9bca26aaee0233858b52363465525eee825da2dcaaf1ae96b39e6f58d3274f351221e966ec2015ae0cce0341e68efe50037385ce872451ced3bf05dcc647121b5abbb982c51886213acbd1ed18cd8a7b5e64f4246c2e2e63ccaa8a2dcb7e096bf540431547dce602887f3b060d762638e968ab5f294a191e847b16403d7f8abe42e22380f205b1f42aa690dac486aef47090c4ea2e34e82751d970115f7c98e420ac404b2cac3809317f79524286acb16ab6882a7ea00dbfc5b757b98661b20ba6a764636221937d16097eb4907178f532254b19b0df7e981aaa2c364975b579182aaf425f418e9c6a3f9fcb00d7b80186ab3238a61b020cb9937e2a68953007d4a5f249a4fac27669c1742d6ab54c6aadd248462109f7cfd2b6c91f47e9c7dfa04fd644cfd00e0f72cf51440106e3838235d017e33210270c58c6727c1ea419cd7b593aafe7fc224b60d92ce6dbefd4467c14b46445606c6d350d03db69f2ecb133e5e0e75e675249ed783c09bf0f7ec7114c36b6d5010418d07b4c61561078403d05d1172354dbcb54709c2ca970bced56a429def50a3579668743ff8a657509d4d2772a3d2dca74a6f2c090869a09769cf34d6cc9b1f42a2bce795fca00070a600b29d711253f362ac9b491b8bb14c3935851914f9276060fdff20ed1bbe6677655c1e8c25df96149df6cbfa427d108a9cbefa1e0650d56d431d1dcea367df3de3a46e688208054645fa264ffbd5ce4f1e93d324147aeb597e3600988115a6fafdefd3041976ff62248e814edc07e61256251f1a59203abd2f47ca9a305b0a6fbddeb062ed1b48f1f2d688c8782cf6da61065dee82f132fa143f0e499d9825468f0e27aa70c08aefec8fb6eb481166e7de376d16a507820d06545dc62655af9fc2db498522f6a07a33e3200bde0eb94d48ac74b582e5c76891241775aa1b80375d3490ae526ac7fe0d03d4428a6c07f4764507ce14b8da1f00f7763afe0b3f61600eec22b9e75c175efc2d7a9a27401b36faa4026a289014819a61dd847531f95f53d8fd856b4a206db52ec134ad8c7a1872a6dbcde2d8d67631b5ab94331d63f036191402274763449b8b4dd5037d142d838d6207aabbf68546bb9b4d9b42e96311e06627ea809343f6790f6bb2afe5544142b149efb0f1821dc546fca108af61ac02f205206813d6adb1265c6270a78ddfa19fcbf55a84875adb7be3ae07874dc49b386b2fb9c859e6420492709346dc90bc161fa1c5e04aefb4410501427cdfe9f1c1cf530af46cfa7c50d4303bf3f60e57c51efc207dda0dd690f71d30533e8299a1f3fa8d2d9c82eef3b8cb4c9ae130fc21a436b0ecbb02355f16bc250ece3f0d8199f5b5ae1226ac71223c2fb7d24edc80d108027757068e4fc5e4e9c62220ecd0d2365feaafd47568bac33d742a161da93749bb3cc0b8e0dbc6a97876c3412f7774469a3d0067cd91a04c5d935f4f63606b738f854a61ce6308e9f6f0ab4cfb91cff06af9062889b12e8481705c4b1309036e88064188a9235571760d3d9100a2e7496290d2de215909a59d915c80a52d37bb6f5ddb3d206e7bb42d07673559ddd5a8e02c5f955bd035e4eea3d6b6e61b8a9376d3db6d6218d4e8a0c9df1498c0a0bfbf26171c4cfa01de43143549f4865b05aa479ca3a2537390dd465d8fe6a4507c57e08f6ec09dda52460097b008d94e0e5604d48f1ad4a8803d4ff81ab2e560efb95d95c3a5e717bed480591c335a4fbc01e68027c25bc1b80cfe2956e3ccbb8053862e647e8eca6e9048799449bcb356d9538f1e3808d0e4a9d7df6e55a2c6d573e7c68e95968b01e4ff93b100b96745b344610a1d3f1a33e0ea402ff2484a8e42ec66f014502343b4ecd7ea91309dce5d36d1cf00954e3b21218884cefb38e165f8792cf589c8c9eacc9eec721e5f4423506a02f99ea9f3382f2c57799cef7d715270e568276a0f968b6b82b1871acd05a4ef5f4a94598811083bb26b1c7766f08530c496e90635dd045d9b46083636e9d714033bae491840638e5a9f814bb82a807d403daf6939c810d350845e2dd7d3dcdac3a8881af1b9b55e6eb3bbfb941be4fef7b02488a4f21c40179eabcdafcb9d60dcb1492385970f03a03cdf2761a94618e81c633188c6d2c33f88ee94087d874d005c41d3f969747767b0d214c05cf96ab141c2f8fa6e6440f1e0b0c1e4652bde956f88bebc699ded1bc98c03ca9e259b5baf92d96a82557880863928e9292b8c564f586296094396c2f1696c92cf317e9a6171bed72d258eb9a447d636ecd532580693ab721513a37e6ef3a13322e32c88166cbddd36623095f85ff2baa6dc22b3028211b112069323a29a011fada08194fb2bd78fa5be631afc50b8ab33df24796d3a778ce1e9ec99651c515ba2714c16d61b79a2d7cecbf7a7fa4b8ddd243fcad7bbec56ef897308ad3054459ab906adaddcf3696746a52851ace3081c3b136cb7ed7e54708b867a1f72d32b87507a70e1010a4039305b3a196b74a5a079a13af8c7fc2fe48f848fc0464a045c982ea50fcd0248b85087419d6f14bcffbe02c5815612fcd2cf1e586312c33681b2f18576e6b7a28015daf6d5acfa8e44775c97464a633c779f28f9e0fc135d0d15ba63bfa65f1b72f526a690c87d9a1ba47c9f905255b6a022c4268a0ff887979aa2279516d6b7d03a30796d2c89b1d9e8df9b914417932b170499566cb98f3353ea723ebe69b8453e94b802980e2ad5dfe543b24b11d8515e441991b28aa02f6c87ebc52d520709e8cde6ac8b5b5dc70f44a9a9b969488f45a8d3459e4aee655c6cd96a5c023453b24a15e73e85455d1681a6d2ebef04149a6e65000592ef8f06c3f0f1b8a3be05f12653348b44a49f925cb44825bc318e3c9076e544347747899eb2aa23ee7a58b191ad73f03a9e3f25f61190d1746c658d59acd44dc6ef32926f4902290589ca18867c15c217df504d750028638aac618fc8e629571908804f7eba38bf53b5ff21f8d5f1743cfae96127a4d8d249c54811827671d0e00121b11d199b328c53c822169b80d2a3b490579063dab849c70b28b50d8a53cb9185ad547828fcf146e27ec1b3e999f39c8134d67287f930b37f976080379d5c88f3c1bf84465b569013472f600d8ffa9c3dd69923d192fcfd1053e59e7b590cf08a3256f01e4e5e9070fdb329f5b6618c53841e4ce87b15120b1cea3eb7c67668f74e5c96a04d44c5b74b9f9edc434b8dc1d714ba6d358e7ff48a950c1332123e5b2e846571a2faa6d42c3a8edd9609897a04e88d4a19640b746c155e77a332996a08888008b06ad988870fca59fc1802247b25c09b20ffc0fdbce4b13fb548a6c14a68a590be0bc606eecaeeb7bd3cf989fe8d38a5d769b40e8b1ce201c23f17f057c01ea6cb747e2c655d2ae7fe891403d71a68124da323785c4a2a321f9db972200dc5890dfc566b4e83455635abe4f13823f71c80e45dd091aad9341dd899e959c74b9f6d3d3263c251ff92827b6ee24c14ebee653331368d961f0af052183b1354910b7d31ffb76d2897569cab6921599fbc54bc980bb3c7f8420fa4541726621b144660611df8b828e8ced6540c487b0275699e37054c52b3f12be37ee2a825b36f5627c0aa99958a63b7073c49d5da1e0572127c3abd97003e8864465a051fdabb8c38254751bed478073192bde038597b32582874d5aa5935831e1e8a2b561586ca61da547e9f1f35194a7414ad90d34bba681d5e671e1c066fa034a8957d40398b99759657fe67a53c2370fe86cd23448c81a98dc1192a7ea06a9483d9681de3f5a43a10e68307a0b77588fc140aef301c59babf091e21708db785fed0d99363ff69dd4aad16e4494d5696a2eab7910ec70546f75e548990bd769f725339b81e2eb408a0b166601a4f302e25e82db5a831070484e8e9b35df61587561439f8949feb5809ada43b0bf5622052d8068be5cdd20fe0e052f08dc2a37f6a25701ee978364547223b95a87594ba341913162ec5682f874153f43ee93bcdfd5dff376323944de596b6c67cb2c01675c53f2cdf89529af70fa52e171f80b34c7b138f210ff262feebed9a356b55bed9f83d5ce8ad2478345ca8b761bb392d02765f82494406d4d2060ed066072c04f55cac673935c500c49c49bb1ae4423fd9425df2f36105474b7a4c862cfad34246cd299987cab93e61767a2510636ba9e4f59d249bc0dac773f37b04fc14f244e0bd4f30cf314ebbf8e5768ed92c1c7b9e2e42288446e3791bdf2c1fc031a0e3e4ed5b3001fb2211c0f02c399087878eb0c4bbc52bf770034684c002c58caaf75af70564a8b7988299cff2eee35607647fe4d1f087dccd82b2b003f3b38c20ebd11f8f4be5691c9282a2e4a762385a3482b33195d35de78f42bd8c9b2851c5f05e67f305053454f762758727de1cb3f610540efda703328e0013139fb55a01d2912b2bac3ec85b9edddebccc5e5c81d1244ca3d0a0f303988efc28d5a045069ada140ef6065fce485c3a57b2a7ef624b91493ce65b77bb83f1df79e6922541ff6931fb8dccc8c52778b73e2c556829caa5ef396a273670ec86ecf0141aa49eb2eba6338cf56ce53bbc59cdc81dccea8d1236d39696bf8cd781484a7e325cb39b60f4fd81734a3680621aec0f3ec25c337bb34a31ed516c7fc437edd92ae9c2d67ae9cf25fc99f251d7450d7c0d283e9828427d0bdfafa64a8d2bf094907253b64132b77a4e9cda3dadf089e70675da35bff0757578f3349b748d6fb8b61b36ee3e662ab98cbd12792d623f444d9a2e04aad858288b8fdb6527b26e55a2483bfe7fbb0b37f62a83699eedf5703dfac6d0821d878878c2efff78ca4f35aa23a3d5f39ef3ba80fffe756d1b784b667e2a2d72312f5f885a1027b45316902844a45dee201dcdf64285658baa847a25751f3bbb465de7c817064369e36629a9e0203d2bf92e937c05487357c07e5de84dfd50140f3c97e1ab891d33016a6d3ab675f30283498ec5be96e3b73d408c25de19d136b95736b565980081f0b94309f0913707e2339f0148be8634f7dfbb4bdeebb3a08dd88adb915bd26eccde393ef53a758f45424fa172656ca40445f4598fa52f22ad76259dc60b84ae6ddeeee3a3a391e588f8fa7977d702b9dc6e84377b38de729c81a52703db177b5233795fe4047c6cbf0e472641f5a3af955a4362c44226fee3a94b34d9865d1a06d68f0962216d4551759bdc2a3727a50451e87e81fb624cc2792ba6adf222f07cad9e327a549fd03986a069aa16b10cb6cf2d635a137259460f17730f1b8d495c9af34dfc0c00841fd1fc12179a8c3788782ef19b2b0a08dd84b673dabc8d887d9dc0bf1f812a9d979884ae836640ebbf5bf286142555d31ef225cbd0f7ee8007575022a707f6d5e509367d62f1ea34d92a65075dbfb3368705d09d34c9063b9519afa331f02c00ebd6ceda9ed1c92ec490988d26508fc13153f3a9084a593c55ca5135032cebed1beabd130572bb286d33538a0d748dc0a44ed023cf5cb8798f33353f670e72dd6f415c830fadedfd91418180ce0fb931996901cfbfc409192ffd888e78ba20e3efdf681e4399bf5d86fc0fd34be9e06a478f7dc4d3afa58e1ad463bccde5603e992ac5e08161679645f060038188514b73419d8893d31265a3ed0d2499fb5328902b6c2406562a43dc552ec020854e3843303c7299fd70c543b9eedf779452bd89ec89f38e9dfe41fc2b47b1e1108e32a002556523f083362491e47324087422c0c0ae3b90e81d2f683e0e08197117ea7dd5ec9e90e2a1504c9771867f8fdfbaf222162d38ba60ee7f282458cdad3f56e8f9c482629167e64bb4113e970a8568fd1122911d4960c3d08d91216420f72a0348942c986a7c4d24b35f053a53457708854bb95cd10348c5a529a2d53df38dd440a08d57b52c2e67de1aef2d231c0d1fe9bc0d0143b88540a3d3251283e59f3298815d2e1588a90335568a50f85cb51e5c95856702a609fbd417aa311db7b5e50b0d8c8387f4828af2f7d7c92670629e808989a1db3ea050626536b4efa83572dc5c21bdf57fd58a509f845b3193fc9a7756b0816c9ca0422cd58d4050eaee09260f66d46923ebaf037433da44d6a5566ab849fb7c225157bd487aabab056c972cdf787b0c6f6707bc74e66d7e60c0140f7f2b164fb3269bd7b798337a1343c533f0231ae3a105e587b65541831456bc95b5710798ba23f069a25ac08ba7a998312262a70ccccdbe0bb3076ca7d6ac58571aa182e6228769102d8aa1bc2814718130a62648c68bf8a1bf308fa8664fc490eca53742cad34fd8b017b8d0920821ffc94cafae3493158046113810c058058d117c8ccd918d0bd180416fb7f3836ab92282de3f9014847bc53c35e08ace2973d45ed5f219cb8297d2922540090593b3f1165365a47a61505a939e88da5e8ede378ad366fc5906ea395fb72edbdb5315c543f820002ec4016d45fbe48dd6e0f954b408ce48d432799041878b9c0cc0b84f2882c2a720d502b0bedb8310b0bbb8c4c5111f93a299cd433c74945232ca5fc6d666146429cd938487cb6f662d037c8fce78d229be7a0b7de41b2c87e69922614934fbe2841e837691d1198cac4be85e47a27f514746f4387087d9be411b32493a1046e876a57db8a9f4ba7bf2a21b321943a07a000eca90d5ad6e4bd905d428690648596499ac91174e24ced6f4e03948a9a0cf20dea612e664e9c6c7300d4cc680951f3af1adf087b1f0badd715c674e33b90cb48d57565a3626aa905992b7de543d1fbc2d700d519f00374cb9f24f9bc465fbc2f125e4a143f1b5462b934ba3b4eb4c71897eb57990bc4348936069f643bb993c495eb4464e9ce4d307d80b43c3544b6863f384adab62b5a0ce23fe7316934b42e39c7028a469bf09cbe4942ebeecc0e70f8783e85b062681dae7656622e8a415530a57edcbc90610cb8ba51f6c00925972e75087362a78243382a90ceb91ac0defb19bcf033f7404d08cde7638d749877c0dc6e8123c8256953c5b93a083ac074664a3aef32cffc780b154f638b3e4522b05c36e5e3894f20e9b4a8f42f1d95640501cc6a6d18b02f88fb422fdcca874792b3e990289b870c9acdc359a6cd835a531ec32e9ca9a07d6ea8244ba30e32c41978fd11709be308d18efbe258290d1bd1bec064569bd43c233e22f3e7c652c4e38adefa9ef6c3462fea67d40aa19d7c381416d7efc6c68810c291143a4ff17f98c2a88bb64fc881921a3aa982f2048b4adf6bf689114680240665c0b44d9cec6ff97c8f2dfff50cede76026ebd3fde72e46dee52ef645fee00bd00aa239bb6586b6641d44376190b6df1d4e8d324f6bb427e4a7d1a07ac90ded3e4f9d5e4656ee8972560f8c3cd59abdc5d089686dc5124ff3eb58119320642bf7eb3858fdb8906fe222f8190f8c0c1a3605f8892c60e454d79624e7bc0dffcda84b20a3fe0ee56936f2deca16e7bcd081bf3c9e8cd35f54ac4b754d3dd2c8d521fe49611340115b42a3665a4e8d246c4d937918cce40298712df09077432fac8a6b747c89798b0e3fea5efc368f46b43d83413b019c62cb8319c8b4f56d7a0bf3ffa7cebd6202b1da2ca6bdc296a1dcd758f841ce58a04d76049362542e4d21b83093823adb341df7c13ea334cb0177c591a81fb0b0219bc108bfcdf3163fb14834f19c597151f1fc749e17dbe2a2ba38232cce656c4dabc50505c6c1ce8ecbc772eaccb8491f80054bce0ec627de084c7c1d8eed2e027d60b715c567ea37a064a1a219d2af8f55fe6a7664cedc11c25ca8c13690f6d4a62593163cc25de10b3b32a5150e0ee0c628b07649a820eab595003a58ba4ec0c1f73aa4278c79fff99839a59e96e7e6d2f6bb9be2ba2d72075693a99a59555b488778777f5855c8268d4f783155b31224054bef0b3f1c0bcedd00a2caeff16995915465b3d22ce318c367e1ec185ea7faca9ca9326a95999fd967a98a53dc60b8f3be27e2128a947abc3fe1b1b29eae227c18f70c6394290678531a26b0e7328e5292dc471906fba4e2bbbad6e9d831efb5e91968084822334e0e28c63cbd007d66846c72cb6b1d70d4d3e9e69ee70acc37cf0829c5f22f08060145b81c811dad918d3ed001eade417ffbc88f3a9695c068736b6425fdfbcce410e4482c3450945ff9923e5af661ddd249cebfc76426d95e2cc22e3453f14d88954172c731a8347db10cf08f077d81a5a0301224c09f060945b223172c61be93a7a8f503c1ebab3085260e37cf7ab574f821b058b03cd90372bdff837e9b016d9040a037ca1a0d3b51c9a4683e09bb28682b2e2acaf865e413c570aa5d70bef9358f7ee455418111cda2bf3ce15f4037420942d6c0fea3ee610149e426a43c421356430d280d190b6fcf101e1af650aa84ce49d108b6e31a7160bf43f680853460904aff046c9712c642646055e85dbc2ee9099f48db546a600e8cfea1d8f95e3f15c35652f41e261aafd41053abe55f96a2ff901aaa2f733be85eff895ac15c8dbe23f08abad1e3d2b1c535c4d9edc06956ba63aff0533d1f6347927738837d70a83c713b82fd9749ff990d0e72dbbb2f0817bf6dd5019a6daea339ab585aa4b666965f4ca29d416d685278d1d653edf9cf38d2be134c4d5eb3692c1caa167c3f3a591084ac4d352f1c602ec2351b6184953b255b9049ce7bc6bdd44355e042ab8952d503896c0011a3f61012a7b83fbfcc2dc597db5c60b5c3702dca0a4f5d52ab6474e559adf0e1cdd6f74cffc4ff4808432b8868f40c7bb0319b6e622da1d67ddcfee8f88a5c250ce6595eb9aa15e46d8af82c0a7d12ccb4961f71683aad2a1ab1aa5a09e6a3710b0a555870acb2c251cd292bd2ecb35b46e10515996d0783fabb82db59ed2fc45a5f69820087e5d9b2c1d39644121735ebc166ebb09ff7719eec1827a6bf518d62930965c4a66274b79c59624b14d54049a2e5a21c07c777ee968c2d298f0f2dabc43657bf0f4125b610d52e3e5e503156f2875789ed8ee4035b832c9203f78681445895853bf7ba3a506f8605081148d4b0fe803d87ffd669f877cff51036da7273d1ad6df22ea8d13a4408bc20098b7615eeeb968688aa7922f3921367d2288a47db71f694666f361ff40023931d40ab835eaefe46fef5180c9ce155ce39e4aada4115ce7ed23591706b159887b040d60228c0ef718be24af9417ab8a91df82a15083196f93f3a0a9da0159abd7e6b7c56479c9ba0bef692465fdce925124da45e67fe5d19a2860b23478b9b476de912089fceb0b5d39f9c68d85c15fb1b36829801be101156eaef7b2eb1f9ec48ed078475cb0ab92852a0e1f70fd2da22959d571ef4094ed29c319a2833aa1101bd03292d827d62ac80288c2777b9ec3341ed1f001f9c0901aed5ade0629834bfa060d2536c1d84c77ec49fb6517925a8aac57e793cd3b30412e3f38715907d5644ca1a7d617e702a5ff7082a6ac3970938d02c5c3b141ac92a2aee7a7abb4197be4264193042ce5c541391223af08adedcd08463d6b5f39b1065b0dd167d24b62128c966f5fb74b01709d647893bf128116c20a2c077da435317a0359d4127a730c1e93e14ebe5cb9076be301c6ab90cdd6c0a91c8124e385868333884c160734a63944a9e6dc64b7fe2eb71fced92dc9c990809bb052756a66af59248c75a61b28c85bc7e90e78457135a53336271188030d150a239e67e74b3be160a901a73dc564d6ad6146459acbc05e6a13bac9a577347e35fbd484ba3d49805b4e4a1e67b2c897159cd484fefaafd1d136d8e4c9d1ef2c5f0178e9e4ba2e54f0f801cef0a8659ed8c4a269ac3180db58b949249cc523340283ab083458cea6a050ff2c8bd7b5a16507e2fc0d8ed868bb0d8417aea05f65899c92881b3f22a8714e9f317d0aafc01f7d0147c76452c6240fb55de87f147997f64d32809edef7a24eb9b5017d941d8f57216c4d7449800672b2f63fd7f6f2d8aaab0edac40e05a3ce7e2bd6bef3d6cf5c12a6c32c067cd20e7457d884bee1fc63615bf7653c12d1387ae02763146d0a953f71c7bc0bee12ff9f308ae1aa101cbb1a36016ac927811c8be7a7a93ae6866a426d0942ae66a99c023db1b806d424866ad91881bfe29f016701f536f3b2955fc35be3dde5a9e2f0fdb3402cdd7795b0fd09bd2e5b13f24c9674173dd77fc86dc233ff0e97de3e603d2192db3dbc577e03363111c1ea3c08b5147ac37da7ab8d33f3401abc72a77bed5cd48f95322290c4038bac2ecf1afa143b918aa294324d9396e2cb89d13df16474cbb40a23b8eeac420f9e1e23ebe9ba4ca083dc3427099b266a77cac8d21497b5b81a0495aedaab8708c0dde96a0b9ca2dd539320ee96d7fedaba23acc805f12db1fad9083906fb8e2cd0a081bed71fa9d285fcb8d599c5e82a9554fd366936f1036042fab798d2bb2decaf863b8d23672d6802b31da34e502796d153a183b40bededf2de388c844ac415236e01100a98608f0cda0aebecfd68b33841716105b120729df3d0109fefd8ef4d26311b1df4f63fb37927bd0dd191494400c4cc56d18d475a1f201a4e1f6a6c80b0c411cc172e3005732305fc460d333d680d1ea43416aa56d8c13c8edd613af0caa9946fdbec410361ca699faca9a1768ab65ed021d905cdd8f284a1dafba0095e32a100946fcba3903699c1f3b1010e327726aa5971af683ee49b089bee4c5a6a0f786ab52bd8a3b29d559e78a82463af18ada2ac1a29d1fe1d670d476d1473c4404e31bf412eab96340730d2be7392c29ac557050d011ae9976b3e17fad18bcbea0bfdaf36062a36af88e56114f5108cebef2ff71b47f0cde528428cd170237b95d52f3649417eda00d198a9ab376ca406bf8e1802ccdd2f6d799bea21a5161cdc4c0fa880d16fa274eaec35593e780396438d62633a909f91396900b16d4a1610fad1cde0ad2794ee9e2b724e933144cb3e746d0be377e3689e15b0cf22a5cd3b1f102b44946483987bf022c60b61858f08dced8887d28f05569caefe1457e673a77685710c75d0a05ed8073933d8808d61fce4dfb609a08b3c9935a8799c746379e352c07481f3e7aacf401a06d5edb84b6f0660b0da68baf5c0e38942ec217da7e46881bad5ba5caae2aac0555f3b17365b21cfa0315a89d858d8f1b426b2bfb8303d7c18b8f97dd20160916114ed8c5fb36af1b19b9b8f50980a1ff270ac8a15e2f5856afe94845a27243a9be93b471c4f4e4aa1eec402c7e26afc7de69328db10cd9d618e2aeee6336ebedb49bee284cb3a4a91fd248f03b4145f8b667ea705c0f0ddaa0d1af8c2c2ced6a2cfcb77b595b675c5f5b1bbeab2dcda0ef69d20bc4e34e30d367bb6cc4b6456ee23b22fb54fb664b49458589c83d730e2af1f9350ec119c04029152aa698953d4abc94e2b3c2c47899aec4d17e77c86f0a3f8089b675dd60a13f963feb0a4e9a946e3e4fedeff365d3e478c9449fdfc5eb72a5dc936ebca4cdb85b5ec6c0217bb9ed4f21ea778933b573a52e8f7ba97805bb205f632677b75ca19c95256b67149a72e113492f533f721eda144ec1b2be49fd97e79aa12c7706ccb4f74ac6c99667088352a5f98350dfc48d623506bacd18367a2ab973d06e586c7d5eabe61540c9091f1c591cb689da4dc723f75db4e8a9207c83d7f8e05c487b02689a1fc867197a0d1d38ea1ec5f277033961f3658962aa51401b3b2790d2be9d117879431097bec3efd6df76cf06928b000834528a6dbe574e532bb80c8eab0db8e04a8cb9ca3f81dde363d7f20613c7983bd5b6e06c105ae6e2f535624a1f89bfce4312ccf1334e4798e15f0aee3752b9a052bb4e72ab5ce629db24b82fb7da0c7f085606936f01aec1c103bcc7c4043ebb606e55c5d874867b32beded14f55b25fd6b6a9057d16d1e5a02f349962ab256499bd09bab584a4037d6fda634c16add62758c95b9db7d851d8aa4d4ade0bd8760bec1c94e017f49e46e5b8a363c7ae71a9a3ccbf30b6f0542cf8029e94566f42817eb3a0bf06db76c7da1a5c62af838ecf67b6fc6232a378f61856d65bf8aa0acbd6750eacc0044d14b85c831b1b4367a05448790b96cf98f4478398782e1fde71837c881a047e0e872c5e57b3b337c5204e42ef3d2cdafca83de825caa66981b1df67c1b7191524fd0b0d5159a0608baaf5cc5c9b938439cdc8e20d3607b3005f677b01d74023e5aa25df19034eb4d914095ee1a468db53e23dcee53b53ee136425c9332c7cfeef3781078d03fed60596cb77664e4cb7c6279a7fd137fab0406887efeccc08d419825620c30d0d1e0d30c58f8ccfd0c42e6712a82ee6d3dc1cd83162b7769f6a3c9e2161e83901df99befcc576aa2a6e0e17c4ef6fde84d5bc27f03e8f0ca905e09937c95cfd21bc6ed7e43af57c93d08371f8f3f402970414ae23e9cd5d33c5598a6104daf9fa3c262225e4a68bbd30c652560d1a4c5255e3ee9cf5f3095005ceb76c9aaa76ca83652bc4adf0bfe1e64804e11626e1ad83e43e6643546c4f9a95a5ea3c05b029c2f8d9585a86b51dccfb66285a28f06acf295191b4da83beaa53b43221ecc30cc60bd1553aece2e8fca800d01844387c9b98853229eb329a175eb2e6076962f058ca597a85a934e941ca824a7aac76c718163e76c86970a6a938956ac028c270c9dea4088b5a6fb2c65ec14651e330797276e760f009dcc0ffd116982130f566470d44996db7b03dae756d4f341c3c12fea3893aa724ba3d7671758540c8e85082d4c00886fe482ad065d42b1b9560f59e1552eb3b2b800a1c211cc8b9d197a8668d398db495807f5b28db4afcafa29c91be82f269e43167cb1d987c93be826a758ef807387f73e4d279d4de01d87002427d7dea254c1574baa7a2841c99d1ba370112c75ecf29be4edf44627de91265d5237abc86c3585328af84b0211d642efc2c6db71db8addcfba4dd665831de3fa975edb36a28bcf4f73b9b6f1951c2845421aac15e779b1f3be5c60d3391e36c8e827efd5d1c6858caeb93b0f1d14db4262934d14068b31250c3ba83379ff38e29f1834bd458f8639dd8fb3f192503a858689b614d824f647b370d7ef1cc54061ff07d850422a4e5c76854c5dfeb0c9dc1a84415f609085af95d25e905ad9a4cfdd435ae6cde60e67bfcda5eca213be2c327a2dea209eda6b6b64ba99ebd9d85b1accdfa2545f88dcc673ee2fe16dd32a32a4c6f534194b8f24f3dc47f74cf29fa6245cd73ca3d6e9962bb889bcff371209f8a1f3068af50ca672e2e2ba354aeb74ec7415739d16d23ec4f9cf7231ea751e0b583a39edf92f447b3fd90cbf2ff6a3fbb1996bfc71968ecc8bda32559406dfaaa3ac03ad425efa67cfd884007a34723d272ae08af7ec4cbcc94de0064eb6737340be22c3af54bdd3dfd5f8affdbbd94c7add21dd89ab90d3d8fb6a054de0fdfaa844adef5917594c3df70ba36b1e1684751fc97e1ea2a4affe2bdba832b09c0eb90db33a1361e58b390f167fc31a79d46e0ca41348b7dfe23c00bfd8c51af14b12b2cbaabd28f6233e7c2d62a0a48470f746ded6875b18b5f74c33517843d964408a6209d9d1f9af302cc01531a963d29b24cffd7b94937a4255084bc740cc58bee5043832e36152ce41b8ced19a4e08312cdeff31b3d1ab088036ef5a1edcb7187cc6c554c04042cc1588cdd43c6aa77d16b285d8e55b86c6c09bf020d1061cb951a479f2758189a2ba1b3ac30f1332dcdf411bd94630af229c03596fcbbbcdca34514474ddd33a68e3dedbe35a58d95f82a24cd0a5e0fd140ea49f56a2b34fdd50face58aae05d8e2d98088a5b3c934bca3edae61aa93856d4962df7467483793d0f3bd517a2084ad383e3e31faa23861dea44d1a8ab4d6cf56f791721e19cce53a5ded0e272d354dc8d0ca6a17b47414a9dfaebe5c57ced0c60a9d27a9c2962ebfc7c958e3adb3a355c843c401df74fd683061d6aa402f7b5db008d917cac217142f588583aff9cd76b957ff322c839154930c471ad0c907357163cc5641033457ea7e08f90bc6e44e5958fc539be9ab1339ff3f381db7d037e224a601c61c1a41325bf04b1ab0099dd15e224de0cc584ac081c611f333353d68af554ebb394c7322a4875977958f779e709e01ba22f675d2a7a7c8fbe1fb9b5398cee09b3e2a19e55052334ec048df70dbcef2e4a3dbb318388ec47e560bad15b8c1871380e12a74fd73d9e4fd687de7061a4ccd181f7477ce2d052196b72d3008afa94e3114115d01331501e715b9f15726cc42071368649e8751c122303da9a2ca7a8f6f262508adda9647d5275b5c0b11e22e16da65f48fd3859e5029706cdffdb7ca110b8f82727afd3e6b7e579ac39a5fab3c9a838ee61ddb206336be51320fa018b2b9d248b5a0d7417b41e43d3bb4e18a5581f9de36891b45e696d897763efc6b6f12f0fc26b9b66cf0cc9a9f9b12b9bcea97bdd920e02362fc7840549b99052ec9367f5b955d578583e37f414edaaa0493b6635b1491ebed388b1dab61aaebb9235d4c5439a23e7cc4d5dd2a4ad02784c50ccff74fee83fced5710f83e5569dfc905cfecada97ea1acd41fb2818252921e43dc751f99a0c8d0d8fa8638be4cdb2108e812eac6362af6f2f0af4b48f9f29c01161a1f96bd9e52168baf475aff4972794e41e0991b8b1bc974575c6e607661c607983f445e3b93b22fc4479ebb0d17c1385b987a80e86b0e1e64e0eb16d8e07db89f34aac4d7a2c122bea1662bbbab82f5333add2fb1847a41b9e944a0669038d1c136d72461d4d8fe02d9e7930170cfa64ccd22d6c3c465fa419649215069da16fc25ce18a78ff47fd2ab8ecb47baebc96122bd7da2a64826f9319037f566955f2b8b586d363312aeb129464fa2d4f729410da20f5cf64fbd68cf54d3458060e3e0f4d5c88446e0d605756b10975d585fa3b8d8c5fa1af9b80d680914a1e86b24c435e8705a59da6d11259fb17742a60aa3c4520b7e65b7d13e52e2fcdcf7bb8288f528e1cdb4f1265fda6d5dae6bb67d6f6a71c36e6e4b725465c4571a725cf082d5a8b9e07952f4c34d43529322faaa623ad76b673d7b23bd78b7de2547d1175c9359fe2f68786ad351a39d1e78807bbd05d1fb82d3d487ccc57548e2a9520f20034a5d0c408afd21f7fc1c3dfce8a393ed11a38129837bad0c99340a24e744ae27deffe76620b4954192605b2a5f218020eebfdae55ad9198a4fe91fe08e7aaf06b74bca749a5528c568966cd13e092ba2dbe1c02d21464d12f4318c17192884ac2277bfaa5db5b91b60c17ceca47af58e1acd01ba9263518f1f78ac7b3de8f6b2033d095dbde378f30ea86571f6b0958453fbabc95d4b941bd66035813bb9107f23853ab7a73a16c64ec85f2e9aa81bf47e4c53c9c8ceeca4458c45aeb9264f46cec4f47ca7a36d07d871c0115a784a2101f4a338d54d4cee20cbd78a07a7cec348f802bd0b05b3b873e0a08e79f55e873cc3ad1ef47b45253e454568b08428338abcac7dcb19a1c2d62f4988426a64bf82f2830ef428b3308c6a13e96361f369b391eaab083e25cb8304af3ee1d039abf49e748f00a582c29e46804a5aad60421da2c8e9ad21b5c5d6a8caa988f9b050096a24241b6d8eacb41c916563ecae0581693a7662016b929e087b8ddf9d067ad05c0361330c424e0bae5625a42335ff5e417f5537d99a3b0db616d23e657fb6bf842896c3731562364c6849891eed795510875a4b08905a333996475d4d35e89539909d1c49402f1c74febac3055183c59c2d1459e98a8c320e82c0488f496d7536237fcc31d913395bc49fdb0718c0fb00c688a777ae222e4deb7b30f60ecabb2ee6ac41e6113cab25205b186ee94d7568c4b2d17480e1b94a7c0ad9d2774df1543306540e6e00134a42f83f711b283e8661da0ced3038fc2cb9297a25b3ff083c0f4ea2a8276ffb5d6a33dba50573d7d2cc9061cd1fddf895c9f7ade25516667099cdeaafce4827b00f81776c68c2d9f9a57c2e38fca382255b78e116f56142778222aae5efc11ad026963aae8380736688c19a98c0d2f80af9a58682649094e6a12b13fdde99ac447df5e2b3851eea35142fece8f192c3fb226f6d0e0b79874eed9c125a002a34cd8bfbe33261284d3ba119cb34f99b0058a4d28bbab4f3b21d85d0687ee8ad65c3348d28c3413b8264e9f0d31106069fd63511c90682cc75e5d3b6b0a43602f2c6c499796e54e6b0e54417a6a5014ba38e848b9c68be11a323efa403b82a1a652222e63dbf37bd0c889ff8384d05476042a4ed217cb0121f1367df956e509cd93dc76f693dec52abb4680e8dd82089ea47cecf3cbf0c2b4e2a9ff2008dd0ebe706074f69d0d0ef7b1b9046a6c059902385931bc62d30f2e1d42657ac4509d8f568a93431520f773103e86d909a4bf109a11f34f45cc335b82bbed95a81d038c916b745dd76c6712bb78a1601ddb8108cee60f7215de7d1a09caaa8c319005cf8b155988b1d68fbeb0d4f3a63b1eda07b63b07c37aefbe58fdce13e933178958d6b54c43ca0e477c5b5f037a0fefa10dfacb8c3b1342defdb04596c86d027bd457ee61395ce9963039ede4633d158796f64dd3e4751d8acfe38e1323ea8f61661b66d84621920afe1b055d0aeae63f6e96d4b49705e986b74a20b1e8d5db0adbcf3103df7a9bba87bd954848eb0317a1bddcf1b87364555e1c5235b7e26c02812feab65b0de35e2586fa2044561b62a9be3c35713bb1647e0c6ab194527460b5986f56d024ea83fc1729ee2eae0b6aa7337e35c74000159b2e73e100ca0d12bdd04a80998b8142e6146179d49cd1054ba0a2a41fa2abbb0aa73f72f6e36225221f81c4a727ff90a173468b2c236d5049493f5bf5d313a63b292a66721d12007ed8b2951cd0322d1425c2aa3aafa3f09ab0c1b68176aea8a92e615e43463275a88d94033c85b07f9c81d5e7dcee5a0f2cf76199be3405d79de8fc316f54c35ffee6868e221989a9268bee91537487b3fed06e04ea66c90858e7660036f5a3d070a60a1b19d62af2647c6e57594d2e42aaee152234528a87ed620cefd225b1e1d8f1a70642b3814609b14c7da4a132803f76532ec57992843fe7a90662259ef7733e5298de50d64e2698292d8a38515ae08cf2f237fc1f520c4ba20630841a2b2a3f0b8319786375eacaeb60723373e007487c2a3fa422e04c472ee2830480cccca2342f5936851cfe7ca4004a4e5c7d154a5232875610611a813c4a98ce9b698cb50bc2721f08680863d6935b8bc45962b2268f0204ce4c058364797b0a70f60b716d011e4abe7ef60d65cd49d49a77724f279715a0530ac621106aa63d425b3f3936462df70ba25b0dc6ebe4f0d0bce762c70cb2dd84a3111ddbafb631422d7898a097d768c5cdd7c6b4235cef0d6d119fae78300df0c0c03aa1d566a5a4058555107f92bb2e84a0782b915db14579374935010c6388b820e7d29bc8b1e7dc1fc761f3ef00c4431ad76d790bdf2e2e276c2b89875c61be0af819a1e80f691472a8342094f5960a4ddbc8e81e83f05c4ee12cab6a54c29a572067c06a206dc8bb8ee392e94a9dda1666ad350559906fd5d9ef29f28e6efd47f9af5d4cf3d330e0fc6611d28540b90cdaaf8db2c0ba8facb5bd5fde577d2b24143b6594583ee8c3211819a3407ca5ceb1656e5c0cdbf16085b28aa9696abd592e26ab95aef72b55a5a2d2d57cbb502ba5a525c525aad95f3e53308dc8ff2dbddddb8bb312e689793bc32899eb147e886b6eba541f798184364603162620c69896132994c31626486c8c4c4888901e3d2fe1ebf6dd4187222454cafbc86c95931434ca621439ae342e736d480969c20c2f6c4e5a0fbbea830febeeb832d0a663f5cdafe4b4b830de3bbff028381c174183f97af058389d99f93d25d5a7de955d83368b4a5a5a05bdff9dadf5f51046d29d181b613a0e5a0e84e2dbaf22f0b07a2697aabe2bf8209aaa2c4051fe0dfd8a8503e770b143fd0094ab7af73be41b05591fd4c83ad8afc46b5b8d096859483e23a3bbd9a1fe7772f12c97775c772683327b64820a2a3b62c7ede0431685ff2c47da717dd7b0f23f4a2af4654f32386fb7dd584befb4edc475f35dc683f57c44783f241fc758e2c07f4cc3719176b594efbc869103f37620162c93932e69e06a5ac5761f7e0a357e16c9d5e85b287ee216753f267e7744e4e08d5eda4e4d2389238a2266ac512946bb9c5a6488874c78e1dce6e726766c93b5625eea8718aaaa257a7c511ab2108474c36a3c9765cfec5a54dcaa66d907d9d1486a0f2675a95f8279ea92695b02105493cb16212abf1651efb917da47f6a1f71f5022aef2ccb2e0ba932cf35792a7ea76c81c2644bf3d967a0c11935e4f09adb7ce6b4fe390379cd83862334f41957e33b2da2f04045dfbdf771f633fbd9d4be16f49913a97148e5a8c043149ea8fe71dd51281a9ef2024dc5f82832e6469ecf96a59d87d3bce6ce233441a0191a85f3c7dd791a8c338f1ffda656a3e3681e9e6ab5bf69c6de3b734610efc8ea9e471ad51eea33678d7e6e08f47da459a3cf797c38bad06f58f13b12ef88748efaadc607452ef492771a8272fd69c1c3594ea3a0190fe751e3f3a80557fdf3d9aac42f91cd6a7c92110b1cb8b62dca3080eaf19964c422ae8a7f17e2b8a148e2e78f77e4c73aecc324c6f589743528254d7e943722456eee3e357a649a9434dab22cb12c8602132142631a6c59ba4ad04bff9808ca0fa884f1a0ef0403fa53ccb3c4600839b3d80bf4d9f811f6b0e117e8611020088fd7081c8063f60a010182f07881be1a93a7e4f4942440101e2f9115441fe829e9001cb3d77a0af4cdf9e23e1cb86e2485372af7c15c5a9ad6c23edd9c0d1c1423df3a15a5ef4459e59d5eadcea98512ea4e51a38bb8801a472e40b75589a85542ad61597353feaa65e15f623764408d38a0079de6697e31df7d367ecc2fa6fbee3bcddf17eddefb4ef39baff835dc28f7205b83b1082ae43e343f0c3de8e35b08c53db7d08386f4c43929e550a8876f0d16693072219028c4b7663188fb984883311609238e4dc5979978a0a8a02fa4dd4fc0fbd653df04683c155ff4ada7dc7d25497e2a14550e28385c8b5ed0dc9d121908dd8fdfc21c3946ffb8c2bcf139b6377fcb323333f3333f3323c153fb0ba9addbbd110b5b75556e830d687cb6f1e2a49371d039e90c9dd3a393f20d8e8e5eede8da9c71d6f4de9b11474c2e88c8956a55be7cdbc7b51fd2a07c122228db7a551b52a476d7296a1cb920d58ead8afc13113454c38ea9962596f8a0cec5b8d39e6e981ffad017e5d79083fe801ec47d1d6b50da3c274583a0dad6400fb26930860271b3592cc3128f2cf154f9618956e597783625a5113b6838423bc75912a5fd4bef3ba42f7d8ca7f932de8b5e66f45d09758a4ff2fb22a14ef14bbfaf0e2506742b85c1419f747a21fad2c3107da9fb1afa9de28bde7bd1c3e89ee4ab217da7f89de6f742f4dec7743ffa47f981aea75887a724dbc8121e505617a8948f8418c0a076f5975308b1038a1e3528420b3e2ff93b82c04a83f24fa6aaa26355368efcd621bf63f2db46be775e53b94fb52af297a0fca5a03428bf240794524df6c805a61fd425865f26c64caffa6bf54ab732a25b95f82a76a08c8a953bd7b274ad020843c7cf7334e87d1cb120b5f4aa8504041a82f4e3d3f48ae4e3db009765324ad68ee3baef74c3e8e5bb18bdfc1bd5462dfd8e986bfa8d7a102a9ca9f45d4e51de93a0485fab41ff462e7655f6e5a845d7ee73ad4a7c46795d41848a40dc216e7e2e0dc6c8450e55a3c1f82f31ca28638c31461028c61863f497a86a30ae14ae58e39517296031d670893246f985747938e60541da5104ad2526df8d57e90ba7fcc239e917523abfd0a46ab025042a9700d686851caba4394da10f26b180830551c45edc772a42b9861e04b648951face45bf7890a55fc025dcaac90d1485f6030307352fa0e7b81890e833981cd6266b0239a409b49d47069319226b1a05b16ada1726576f7f89cfcf8bfacee638c3554b9af16a1f6e5f2d03a13d46b382bb82df7d9fc7cc63d4b191e37e99ca1b3d9ccc51c4bf6a14163c486bfd7323067b3399bf346d58ab139ee5d4efadd8821e86e27ba65556e5295391ff3f81e98a021f360752c168bd55ebe02c955e8bd565beeac49e9f6d6fe876f6aff04e1e02adbd476516fc9dad2d1d272b572b8dadba78bbbd5e2f296ab7734d8396ab77ac8f1d1b5e6c8d114bbbd5b2dd917840879e9fcbb1313942b9f98a0f2c315f282f203253dc9777bb2d2d2b2663cd4fd084557c59f04a5f2947b9f6b554aeb05545d1f0fc00a287044e662b7db6d865236b98dc3c8d4f866c65994dee82d8891ae413a9aa30d3dd024800409c20654567b51b1daf0f3d26354ac23a7e108e59b04d4c681e3acd2f3ccddeb4e4428a9d2a04455002fa0b1a4464195a657d185fc4c0d9e34c834ab52517e404bbe9a1ffb35f449bef4d59cbcc96f831ec9c7d06f9de4db2fe6e49bae2e79216006943678a3556537c15a324985be4451de0bd37f073a8bbebff7f68b684253f7853e17beefdf4fafc196683618c62f3688faedbe55bd8f280cf8632014fa4b0f6542851db811fa1ec5972889fa2868fcd06320f4fb1808c55104422ffa4cfdbd3f2667cd10cd0b28e839978d705fd234d8b1065b7e4bcc643299686cfc48771a8eb9fcc48c308e7d9a6e6f5a901354effd918d9a440774561d74c3363fd0f9b347e94bd4858623d49f7bc0ac651d1855ddab3fab6059bc3729117210aa3303c1a41ab20d9d41f55fce41e94b3e34b9b4cbb2f0fbb35816a360531e0ad57079f843f901adf95163f2251f7fa0353f624a7ff235256ff235a53ff9f827fe18932ff998932ff1f7c2e44b1e86c967e3474cc9977e3d05e3e4537d36a4133f8660137b957c9465f0a308b1d7be7ec4ef04d53f3c40e537c036804bb00af0e792138f6d4a5019002b06e28b9e3f0423103f7ef75da77a8272157df8282eabe27f5a0195b56b66e98d2250802a7ab6f137996217a50bd2f3f3f70dd28700c9f3bb2079fe505685fe8d136c2bbf8badfcd51efaf4037f43f44fd32bd2b7cbe27de0cb387f2f42f5287d87ea51f2efac928f6ddce69d155ffea0cd113a1ff432f4dcc7360d32ceb373df9c94721fe3b00ddf30c736cd628f363978649cc5369bf25ff724865023dbd05909122c159be25f9495f8940a28bd9af1c4c9dda896a5794e934945e55e81292a15bdea979f424f50c0e92050fc628352862726cfe48386b1ca0fbdb9208732a84f0c6bd4b6e19e3b8b8473f2292d138a6aa565c6136b2024e69c3070829260089cf91d776c240f510879ab52bea36057209b0689b45c96cee166c771c88ee4b4143cda888e2e329b47f0a043de70bc5b0e39aa4daf463cecce911c1d38b319ce461c6779453c233a3c297878477270c86e767066de0d6723fb5015e0cac9a7a85a2d4c823373f4b856bbfb85ba0912b93a358ad1fe96fa81aefd1c6dd47ed912f878410982da2f2510bd76ff1ab35bd2ddbf5d99ad064128130d39a0e14a48d3d2d22c128e0b4c8c19b421b722467ca400adb4ba39d9158a2099555992834a1b892375b8e4f12d0836285fa6f4989e9af547a61bd01004439a1a7fc61eb9b32ca0972f8f884e703855ca77217605da6950be04cd3cb5b32c364438902a7b746e3e40b51d9ea3234551e587f2889c1a8e681cce68b62af21935c22162473c75a757a32316942fbfc7878d3680ca7d249f55915f830f6800153473d62847a7e4efa0f550f361ab72a47303d25365ad86a2994f0b192a7f231a9ca022546955b8a4f24b0dd4e69e6ea005f5937bab59d4c7f52223c3a7653c9e6cd644911a6e6d36859f1a2e1123eb53e57beceeee26017be52f4966de211fde56a8cbbed59d9959f2102768e61218507e67b9ebf2fd02a855d877c178b652eeee7ea174b9ccfc5d5cbb1edd05f3964fe5cecece0e5658bb77777bd7963958af4eeccecea327bbc05e6affe7a6065bac8ca0fb735166a7dcf48386a6ea02a8d139dde4041a9a5a856fe566f56459fc5710a15b435ebfcbcecf05dbd9bacc981f9a18e0ef1932994ca650a8bbdd05db8fc647bbbbbbbb27df6c0fdfdc5477cd4ca639eb68d07974f7f6c6f11107473cf58b3ef453f4d1f0947fe87b4ff977dfcf8d39faf930ba0ffd7aaa6d8cbe53524ab9c33bd179c7e78793a34637e8298973526a32fd833ca30e9007ea5bdb9a487fd7fec29adadf44eda6d71f8c111f40117ba9745e21d18f8eed4707068717fa3c29dbf6d9746c3f3a3622fdc3a2e8fdf2ef18eba08819c85e215531c42b345596a5a18bf4384b04d336ae8ee6a3f040bd1f3dff388bf43efd678bb8cbe929ffc93edce3a9fecef3f8c7597b22828ab8f3189b3992fc1d99ec40bb6156c57f6b4788d86eb69b73cec1dc6c446c362237db2d66fbb9f52aec211efa109db6d96a355bcd660335f5318d405dc029321a74d088458d067dae66fdcd0a3dc74967bdb4fc5ca1f73ac57b84ded51ef23ed4de871e86f721cff3be137de7496f5f3b92b4c1d30a68d730451ee008b41f8492316359e6fb5f60571e58d0bf86284423fad48834687c686868d4f0a1518386064d911a5b84c6c616a1e1e3d3e990040f537662fe7accdf17c85b60ecf2b23cad1793c9647a6917d88bcb8b0bacbf5d7878bc976787129c68b3d96c211b0db1ba8f7fe76e53dd2405547e08a771402f23fad1cb784f3a750875ea2f39cdd30c7d353f62e887be86e4bb87117a9337f96ae8877e5f3e24dfbd0cfdd0e9c5e84b1ec6e84bbe9a1f31a1277d4df79dfa472ffad1c3207de9ab097da7fe4ea0efc5e8451fe33de94d5040e553131b2a3f9ca1ef3e641f12aa6b253ca0ac2eb0adabcdff04a6f4ad7b647c9cce1c91acf2ec4484ad3a086aebf490aeb5ad6b2d6b9b732a55dbbad643bac6c5aeb5ad777c48aff8bbb629ffb95deb5a09d75e918eb198c335a609c9a121d316c522a89ce3d150670dbd877c0bb927649f7effa571d6e8fda4f687a1124aed0f0350736ab15aa36fe85ab34d54cf5915ff8e080a1ac5741f23fa913c0141630d3947a693594f8144df0c4f71557e29f0c1f1635a8d99c63187db1a8d8534e8342ed63c76a4358b5946635aaf8008990d01e20364377e4c8b813ac4c4a92e2d8814dd8d1bb87a0d396801ead2788e509b6dc081967d3d55c38d4a29db69ce30b0179a6486c1b08bc3c4f8f4b8bbbbb36034dad2624b9f968b0bb602a39452988b5d2e306f17ac65013ad09056531025ea7e396ab8b3a8c388279034319d2069e247d0fa81081d422411c40f52e038ae9b5684175020842042f4048104422841841140685c7fd692339abedbf4b329fcdf1c2185eeee96b31f44756941d082b3bbbb33373337eb08d90891820e777bb77bbbbb77777785a0b9a0d5faa1882882e082266e20057fb0bd511a8cdf52ada87ca5d551d5f22f05a657fc31d8fd552a55123ea8a40e1e1dac9c7cbf2a7a7bfb8845af9c749f3cf32cf13f7327d0847023052472b013eb55820722ddcedcfbb55cddea4d71b598f7a3b75cdcaadcc2ccdd6af104a1455237884e13356cfd70abeeeedc7152f5cbda9714c91df2bb3fc9eff462f4dd57d38dbe93ac19bdf779bf2f7f1a343333481e0d9a991923148a8c76ef3dcb422c5b16d1fbb30fcb72c94a3fb32afe1e8a63a18f715ccea2c08d09d5c3bddd6891071777cc70ce39e7bcbd8706fd7907d75cbc36c3b7ee26461300803e19484f325a77932fa133944c9f0ca86df0e433416d83255f0050eb43bfd6f444f30b7f7b22003ab444175bb865556aa8614b334bcfd5356234ee7f59a7f925bfafd3fc4e2f48bef430e8977c35254fbff4d5d49454cbb295e34a95633962c195a0685a96854403c507ca95e4c404ed1ab6d4d824184f45ce7b128aa2485071f4a9568537dc2bd4580aad80224a3925b74c10a8063337ca1941dcce28e3d3e855ac7103e351761d6a41c74372afe86d1fe162f2a7c120e87c297fe4112ed6abf8827bc9174ba186a1464dd1c845d77ea9036551d7d9f48abf3003b2a7f2a3e48fa75cbe767403e334c8bff5847f9ef8f32815e87c297d4202901ff7464ee48ffcf1b813e99dc8b0cb413b1d61974347f5ef72fc38cbf3e4cfcf0b86b8a9a1d7f108e5e852525264cccc2877e3e68cb3e6e7f294ff9c9e02e32cd1fbda5c292929393af50175693d72c414ae614a4aaf28b00af0af61aa15581567c908f7c68d5a906afc66ba177d4de8e7d7742f7a29faee6be687be46f4ddcb17f3430f43d4f17c18a19f9f8d1f31f3450fa37bd1fc4c9ef2ef219e108357878279c910eae682b970f97accffd6dd73c1606c333aa0dc4d25839b99714b99e19a3123e3362363c6cc0c77ce91c1393332e072d6cc4d0855b6d7c3a22c890e0557f653b5548bd2f26da9561a6cd54a6b57d5da1a4a8c4c0cd5eeae6aa685ea5f485542c4aa730c9d1e253067fd9c347f1a53eaad8a77f7b2845810589ab79e07b32ca0ca3caa87ace3257d1249a8f4277fe3e5df6a9d4adf6902257ff231262fc3975e86cfc68fd21753f2279f8d9247f46081d95f858fb8d3f965ce8d73e7c690e39c5b7f62094b80c10da0312114052d488b58571881f6efeff3f42008ffe8ecbf82896e71b97477b75a72880d31a04d114488219c0ec02477f765462fc23c1004412fce18a717bd18bd49631231b6125fd0820d39463e3f9630041138b1d88a1e4ab117d47cc115554bc5324a299da5bb3b0341f973fca0e64ff8200733234fe0cc15466294eeae069bf3158e7294a31ce5382eb6cb05165db0789b7116a514891800b1439c02072b92d048ec90851fa2c5e5e2ad96d4814e1226f8cfa13b53392723b3777b777b93c0bdba2b8e70840f32c7bad8da5e9a0df6e0be87fc960699bfe52cee9b9de297dff69c3d9598824cc28de77edc8d2bbe40bd6166f4e28b078220e8c519e3f4a21713e3f4e83ee8ac18410985b8e38821ac2c41fabe226d77c9e8f2f66e978b373b3b3b278393931545281898883cd8590c34ca96f21b2699d991a04be341edaf3fba6005102c8a52b9ae680245a9fefbbfab5d30be7b7b7ba3fcaf1137d58310410d3f881e31c6821ef0ca4aab65a585b6b2b2049e952b4361552d2d57cb39c535e79cddea96b1dbdb536a7fcbd662734def6eef6e1a10d55b5330dbb0039dedc0ae524d5513343e3333333333d3b450bd85f883e00b09896389442231fb832a6701e01d87ee6bccfa120121b1e6e467f81a19def4a697e16b66f893af31bd0c2f4f7e868f91e14d273fc3c338f9190230c3034086377da7f8fb02c00c9fc90b3b702304abc99b7cc7b5a4d4a4a4c4c444e4795f52a2a865e1e15083569ef466bafd4d01abe21f512aab007f0fa542a5ac8abf0ca813948b50e091104c016fb7db8c0a142244c8cced769b959a0485cec77dbf1510743f3ed713ff8dca1f2a2e8b951be2b7cd0b4f58c1ce11729c607bf5cb38cb460c083728f464e1043a2c14559801e147144d38e10baf0310a1044cfc20d18348175efd3cea01fa9aae9002dd6fe96ad92b3098abbbc505737971cd66feed2c64832e4a998cbfcaa1b521dcc845376a21bba1a672a8294341c83c1cf75cbd61a9108be1a080f8354c957fb02ad1b98f7d5830fefce6deb90f87f93566651f5625be73737e2c3b414056ee656519ebd41b3c18212748167258810940dcb81162930228ec00082e94e0e5322346e8600b498ce106af48811ccca004b49b4c0a9c570c55ee452c3b1121fad0cb88bef54f8686a7e2b38e37e964198d31d2e9cde9d149673de2c57884a7c6e723bde2c9f29b1debbac0370901ee438f001cb26bab57cb32bf189f55a0c829ae3927bb38e98a1edd5d2ed13dba3ba703f7a568848d486ea765ad23931cc7011e031b5916ee8965b50f46506c4446431555eea7a96d306779274e7eacfedd87be43c996cd68b21da6394bf4dcd732d1271275229148f4d588befba8f75d37db71236ca4e2c051d658bbed5d8ea4b0a6caae339fd691edc88cecb876643a3299ce8e6c877380f42a74f7ef1e5ad6b296b5ac65251f507b4ef0edb8a5bb68214b70c3c9691e4105b11aee1154507b686863da4a66318f754fdfbac8caef9c65e9387b9822e7711cc86311e4b5ac45a1d08b3eec5bdfda6f7c87e7901e8dbb8b5ab605ba9e8279ca1f45167d34e81e731cd7e1b11fa8cc53fedfed06f2d8b2748ec7fa4336259ceaa1eb60e1ce9138a788e3f098db78ec6693939393e3361e731c1ef358afb8e831b7e969d0df63319bdb380e9bdfd83c16cbc9f1588ec73c36a35168204080d86a436ab521dd92810001c238a827bda2cb22ab7f8d994d35a7f99c9486be292b8f4c76a0dbe08a2a9d71a41c81986ab55a2d728c9102a9ce3f2cc5890a29a460297ac55ea3b150103a9ceaf2ff1fe51fa64e4f52fd632c0b57ff191901d0a1a28e6b44916a8c3146067de7916ef52571088002da35fc9f2316a467d4c6186344ad93808652472879a851e2ac4afcf842bee8cbd10bf922798aeae9550cbd27039a39c59fa7f8c9c4007df731bc4f865ddd6f078a31039299911112e374c136d8806270323336d0cc8c4c0c9b4c8c1999199b4c8c1919cccc32369b8de2d01e243e0820c18e5c7415fdac4b81931a326d131609bbc4748246517d8aea23166984daef0b67b8c8a81ab2762fbb6f8a6ce081b6b4386e398e736f71de52a95c2e31c618497ca6cefb542fa9cbe21d4e79a5e108a9faf36c59222f517d91507d9b50dd3b76733af8f4b8692e7a8c31c69728528d28ae3c63204c00f1e79d15ccc75dc5e77eae37472cb85768fa741c8a6b8fe2fb88050e1df7a7aee39d66710e35e7ea4bcb81109ef5a821f354f76838f8410db986031dfedc533d6423d53b11c71cc7712199180dfebe6c402537838134e891771af4998cd90e90086416b91d9e6540f28e7c199daba8b46d1815b5546a46000000400043150000280c08064302b17044a06b6a520f14800e7c9c40664a99caa3419643398e72c61863080000000000c0080c0dc0021c90f1e8fee07974591ee883225ee799f6579c7b8f4cdd3b26b935214ff30cebdbd31096a56a1aa7349eac75beaee90146392ff22aebaa3fb1d195164bf88860526aa4538d73c3e63873c6d43e7f12288a3237a1deb9106b0751a21bcbd23d8d87a81b3f2094232001ebc36d346e4a4e9c77334c3748a862a4e81d71c3e7981acfe211d620acf10ea9a9eccb93009a9e9b8b494500a128c366fac18cf497e64e1c983eae22553197621edcb45437d14af7c09b8ea869c0f63a0c0fb35f98b9f5d4f994fbd7f1cf54abd8b108e02f6da714b7be8fb7a74c298c3089c295ad24516212ae91811311d2962245d3803855d56de92ca55a9045ca0990953229b98c0b90823959e4e8512709eb0d762edea7fccb36870d4bb8f8bb82227a8900eb859ab02fbac8c39ac52cea57a805e2c1d6f85ac1d579705649a229141d600b8714e056114968937e1b5335855f43373d883e01390c0cad7b67cf49b9eee7e6cf43017253fce404b22fcc4e4714139424a29097d6db32bcd6739b05a14592b58cc53f56f4e8e259c3a8218b6984b39e813e441972c5b2403ba12c58c7001c9d78b55a9f4090bfcfed6fbc11e023b839fd9622fc8b5985ad396b846e71890acff747859f71ebf20f10592bcf81863c04322f76add098960a5d987e4a165afa31727cd0d23ba617f55699cf01af4bd8f72e15de2aed42b8bb3a464295a5ab0a9659e184f2ba07c8a8820093b0e64a55c25da4d59c84b02a6feaf245c5a67c812735ff179ea41c31a1bc8b3dd3f5d2fdff9a8f9c9239b37f3c0bb32401f04071baf142a03cabf31c97c625d79734c0f682a6294bf45c18b2d40dc478b876d452f55b8e1197929dda8323e66c6f6ecd2181a9adb09b5fb4999a7664c4a1be44300ba51d39a12bc0f862bc7e9288edf3f4cf39f5faa56ef6bd1b47de47e707ea0d3a04aad543444862c18beea801fd19befeb571e64ae0a3695e0ebb8f33f5975493fa35e49ed5bb1726f5890c96e5a25b4aee7115d10a832a36a331e1f961a10bfd7260986cf2d65fa96d21478bbb8c20c74979e300950c3d880c4afb9b214b9e1994bbd09934d01ffcb6c86adc53637058d346bfb1322528647d91ebc32088bbc57e53d28a650af6403b19f5cbd8dc93b637da5317dbecbf3b6d0e657f4063f794726826185a13050c95ed3945be82303b39a304380aef8176751172b05197b633ecf06e49490e82185a2e2101e4a3a97911185366c67ea1388147afbe82db0792a7a9d59416f772532a4dff91ee3715001f3343aba8ea618fda22024fd81002941c5f2fcfd350a64e3a703c8ebe5d435414b8ec405826a13baff13b27e630ee1cca1ec243e59a7f0e617370851348b46f88e4c85e0c82812091443a61727794ec719b1dd01485dbcc39a6d63bc33b17af7a3482bd745bcfa890e4328d6140d01d49398c5cbe41e74ec261789f7a913cd1a0f539e0f2668971006e8d06f8a63a132c965e1e12cf5d0b287faec8ff7a018ee6a2f8185cc6c653a19bc4454bd09ecce347c722aaf54b44feb4afabb8eba769d2fb2bcdff49e65d5400929a7b5b9ca2216edb7c04fd1b6218a948378bd47dcbf8ac447a986720a7916bf120438a57a360db1978318b75eb71dbd94439c8246dad54ad19be49e84bacc038617a21a175a86392e42332e1bd98f7da43ce607ac58801e29fcbddefa926041179abb5343271f5dd9bb590db070e870aef63832b971ddcbef8b82ede2dc74c1afc502e78cb043848560d70abd35af4669e00a4d207c0d69555a714be21c0416e63d2e00876c760611846b0b067085da947e7bf4b9cb3e700134611a8c7f95d5cfe8c019ce36f265a23fa4b18162b9ba37c33ecf9215b91432334c1c5879446144a7b6a6860d2738784423e7eec5b340ba7802ef7d52b57f73f6951a3015dc442dee060770840244805a2764bceafd0c3bb8b78a95882d8287b90fcba423192af9612f52b8731e9ebb2653e7829a067bc0bfa6235194936f36c85d565f81a6a4d15889af37e70c1995b74372c57d881ec7188283068f763cebfbb7744cb5eb87c8c22bc6056e310d4d4b39f5f368a42a0dfb4ff983a793cd6b56147c9dada78a235f7e0e773611ae56ed3dda247a8f1c3932e9497fd3cb4b68e41e1a3b070025a214479da89da37e2f9f762762cbcd8904d6a4dc6c994e7d2a362b1bb2270a1d29f5190d9ab67b06d51f45962bb2da5f1e411b70caa7cc727cbd1c74ff7dc079987d20e07552c75c0878d63a0c32336c313df77f70a48c4b7ad30920a196d4731d368ac6732121be1d58860b650ca905a886245f905e71fbe03e58312f31cb48b235be8fa570b4ef1863e094738419b10e660ec7c0ac6dd508f5a6317a475a5a7a082ede93938377b4f6a4f21e559b01c074243dd802ce9eac4c514438d627b78836068f2c7c61f4bfd82e6c89e19636ba7653463ce79e6f9c645947a71ccde1424666a314ef1fd28157fe9a437c58399e514e7643cf5b2cd8b0647d6e5fdb241756d3c75c2de27eef9300d8385eac217b8e0f1a10b76847dc0560af83f53381962a3fa4996a3294c30d43ddb887da330137b940dcb14bcb1d2145e73e771f101620b9d1eaf2982a918727c1444c555bc7c850e0118bcd0496d1d011df771c2bc65592b5277abaef71cb768d1aee8486a538bcc7146e8c1cce9364d13d58c860514eb7e041e9dd1496b3929daedefea8bb950231387d5eda9cf8d38c916751d7d2ff7d608051728ffc0df8b8801ed507edb02304d5b746d032f69fbfb2cf73b18ea8c6f8b21aecd7a341b76c423d88ab25b596d2a47d4749f155b834d07b7533c039608314c79b9e2edabf1f21432945190c1e0d381123485baf7614a5e97363b9da925a408f0109591c7ece4e721c3331b08891c03caa6bfe5aac7b88b684ce7dc010ba56f647a19f6f3acf8ffbc164b9208a0f0550371ad286a08bc5e93c4ce3fc910927a6820851a09993e480596297132493058c56ebd8f0066298e2d545a5cbe9bdd86371ec1f08337f79047f8c46010acc50769268a7c93d2ce158360465a1c026115aed6a939b0caee1c6f33d6cecfbd0d545a1aa23ba10d2329a4f8d7eb32875f2f19458af25eadc548b3cd170d62035ee17ee30bec2b31dec48a8604de49cccd56fc29cd99e20dcc6620361fc08008c957aaeb27bf9a7f957ca8557bf936329dbae16c52b661c0f1321cdb2b3bc4c4d553d26b7e1390908a9be35e7d248de215eb44ff1a1993c1f7af1b638ff320d4877895e8419bd0e516f383297c4a17147d3a641ce89798738c7b1a75ce5f819cb73ae4ce87022a58d2a6e845843c03e4b49d1512cd5faf8db14ea7137c1521989694fee10e76f070fa9a55186093226045e70ec84064e4eaabc1785272c2a220b34bff4d4babef354b4bf23f8ce4ff87f55d9f53e14621f1d5319309a460257d89d680a5625fa9d4de003b74d6353b05b5d3dd869a8a9dce1f129831c98275d32f170a89d7e43d5fb815b492b0984fd92877b231c5d342ecb37a3df31714d63f187e2ba7f0519729c038ede486528e08c4876d0928cc09e9e60a0b08ae23e3a316575aa56e35f6b2e0ba0e242305f222a834416a8bae459f3cb30607cd6da56d3b8d86a4455f14454d7a5306380892756d142feb31bd934ce097a39485bfc1d6ff64d6c9951d9b16e7b9ce38ded2b9c94de697e69c119940410256b00d1caebaab90eb47fa979035e3c151e15f4b00c91e91eb979f8c06563e1ffcd6a5f4b84af9105f092648ffc6fd0e5acba03408826af7ae784659e1b7c288fac7f2fcdeb809882c5b57e9e58a58792bb421d9db27e95d35631a9f0df826922489f27060df65f74ac80dd62f78dd0f7def8943c12a526af1d23e09e6d44745ae618b4467c749c6673c85dd04ce543455e0c193462248350fa9b669ae16314a3b4f21e0988beb86dc787340d9e4fd0c51dc2c0c5522562e735fbd033d5d917f7bfd371804425a8cf789dea45002fd2c21bb6160a4d0cca9f36d5780dbf29b97555f398c6c730a59de9e0aae89c7e805fd939d0a58ce3935c7dc9c9ed763d570d3dfe42fb679c603011be00c5795343e350b554b061a2034c86cb86edf7ba76d400e460037f2f1653325efb4b2d01b8212dade81b1a2acd3241455efd6691375a91f2efafc527ffe7eab659d861e22f3e614bee6f4338550552fadbfd2939f9610c4430667f721bdfb3a91074b13d1bcbde9a53172eb8e24cf155e643ba3dc04491e42cbca31ddaee51e2454f7313e965b3b60d9e40c574651989765f2e480240ccf9d18e28975a75a0b456e54c13ab16d78f67ae97e519551c5d30d23cb13e92031e37a5530289b610ec811ae9748dea94581cf50022d38d501388915bed35a588dad6e424c4346e19c15d655ccc0e5104d9424815aa0191d75ac1c42c3ab415e884991fa750d61fe836f94dd92d4f83294f41e5874faae3a46fa4df4be90930cf9d10c1685bb3a20cefaf575b18a15e20fa73388d1c22317a99fe3db20f7e508160b369fa0f1f70581823ef13a5f84f15991279d6a591033bb586fa06e8365b94ab9d08942d6e454cefe5d2470d3a07a6f2d19a0aa8e495a8d29ebb4d20b0728af596721011c7cb2d94697bd8e2f09de010ab6021b5802a182a7e8b647e7615c0664f5795ada322b56bac7af01717a47a74b62acf5a0dfb1945e3d68a08445fd04b8c0caac26fc32e518d93d65a6a18315120bccc62b965931ab11cdbeca02135a329c08425e4027411ae65960cee98554a53fe5c1541e8dccd47eeab5f902d54960313bf0c99140e76a0ee668616a1534ff9df555246c10762ec1ccf828dc202ddbae41b248e97a028718bd4ed0e72b3cf9356bb4f7899d0916e3470affd5e924d6b346e4fe88fb512631f4545654263d91cce8677d26b2f54f923a5db8f2093a521da1a108fa569f79e173601128c2646576d835395cee8620cda02a4e2019d38f202e7da348350e5481fd3c3ea3d72bfa18fd37851e07d72980e64de8678a715b38ebe9b747bd236e1019ce3e860b3a2a18375137ff17f921067c4b45225606261ba0e6ce37c6f144e3b306886ce5506f0d2074b029aa1601d08e3f66efb240a56e02ac8484f3d7763ee9047a749ab3ceb6c9033226e392bd01574a0c51cb92cfdedb0f205b46df13f5086c29d8eb623482373f497408f9f35522dd72997e2ecf930cbfab184d49adf211fe5c6496e23a8b0bf12974d231e2015a52d9bf038878a8c22bdac75e71d2c302de5f04e141a6b095dc16264261145ea27a07f6975dc197d17e48d2a6386867679600257a83ce1e50c63d572cb8ef8295e21a0046c45991c9ec13e1bde69a89ad0e584778d7c9825fcf25f1f7bf0d1425607903ab9585bb806bec74e41b790057579ef6067177ce910532364978884f9aecd5aba3035fad31ddc6de8a0304c2f814428128a880a4cf8362a3351b4c48935df994f99cdfc0b476203398c09b7e6c765c3b5852b6bd4660f3ae7f460049be6c8835b962d09972813efdef2c9df450851f57889b5f94d1380dab47a2092d7b3609c386451900f28a376fea555e622292d17d1e4f7ee6c909a7a8c5b2452b165fb634602d0d26fea17516b2c19868d3e12f6f6a98e980e49147d67912bc749858d5745440b899794d178103052c92715f65c3e70459523d118df43f1d3560f511f81e25747bbc5ebca841a479f9826de86285ddf1707e058c8ecc63bed6ba30bf9182f9998399abb5c4ea45b3d2ab3417092df78dc93ff9a574a62fc59056cda0164616870ed6afd2bf2b384e390a7e0f222bef784fa2082e1b4751ac9ba5fb1291db08f2c4052b029ee4fe77d300817571cd067e968c81621f8dbabf4c2a0c7a6a99a71cc74ae64673041891b7ca384cb0aed28c4bb34e69e0c109fb34f2509b371569282b1c1a3812e009c2f33ae5c7aabce350381f15adb4e91e18eb786d1f7fe86f2c5bf059c1fc96930f99b8822fd493c37dfff636474f6ee04e1f63c858614a4332dbfb8522761d0106799ba457570983611bb213cfe71f520428f443cbc750838ae8c5af6ccd41f15ef07fda4591feb7a2bb772a335b627fc5ea94cd674cae8afc06d2c420fefaf9ea5b5dc8902f9370c4f62d90442413473e07e792a0ff6639bb292092a5db3dfb4de2f1050bab78865c9899b3ebc38b8f526aa7460d55765287b72cc8139b3c67ee595a004c769f073575c52eb61236b6c9a3df5149348ca97425011c2db014c7fa91be3e588f801a8900edd4c9f1f8b0c903774f86ff6d29c42d7d37c2edad7bf8ce0827b4fc9c154ce43439ddf95f235b220ef500f345a234c158602732aabc611d396b4dce134b731ec90bdb33bbd17de45724998ae91872ca7311be87b9f33839639ca73626f44c86e4c5bd048b03935ea58a6d6d78a83bf5195d888d77e79e1ef9dbf2ccf89d650135defc71799c324aaa9d84d34c87811478ff157ec001e5070c174ca1aeb58b526bc116df0a27772d810348e8f18f080adce23e6c5c624ad4c37a3b8259f112481ae8bdb92a12c344753ff06cc7087d0e859488b19f060df93260e96ef0103ab392510171e77187d1ab450064e262d9482d5619fd003006a5d874545425c2720206a970b92715fc33a13ad33257e5303eb85a89c62adb16da62ce15ef5a7bbbf122eb76be6a81e0c568df064062310a14db2d2872ccf8380d53fa32d3ca11baf15678d9aa2c4d30717a12dfe811437e315d6490b58219a2dd1e9e07367b46757d9992d7d3ae8c00586a8a188e98a366c242bd3673e2fdb75bd7b636622b8e608b77d09f1407782446696b1529a8b5966adee225d26611ce5fd4ee4527adbcc58f79930ce17ac5f68bf5e6d229f8594177f85954eb520dc9a1c2ab23e1eac89af85a1bdd836f1367e78ea065d22d2c246f5b2abc90f00f326c38a642048928840e6651e1ac2d7cfd3bf1a12a616de8ccd24ab632878df21811454f964c0abb3b321b37326c32f3378ee05ce23af6a19a4ef76324c4b26d6c8c59e9e63e6886896a45dbb8346094fb19a21012e7ec7f115ce8f522b32529cc52517e0ef5f1eda8a9f0f8b83aaed64d4c100484756e0a9785ee5cba47d23574ce6dbcfff9f4e78a7b0d8a4cf45222a3c9b4236a057db11930650634f3f7516a2a1b7f7c81985f6c7357032a38dbc19e3778d1c0e42a84831e48e763e7146e1ef7d188fd29fa01043d52003fb14d5e0acee4c1881cc495c5dfdd85bda79d4f993c4e5a662cb352990adb151761c26e76ed1c7117260b4d77dd607d95ad1f0112d6688d07f2417f015afafd6666e3382b4524c464f0dcc3b35d368d0b86f1027b139b7ae6e2182d84d80d15d7e12cec3bbd8ff3569791efb1ee31c366e5886232ae3e3b907e131cfb60d0b6e23470f67140ca406431194c8526cbcf3ca1b8c14d790f26636bc8c16117fa40b17d732c1d0ccc70e5c29214ede0995e43e61eb9579ab034f135c09a62c3219b6d8db0b1893cb7c8428a117e51f839c6f3a1e3b02de240ca387a556754023c6fad64e920acc285989ff92caa73592fce5ff296d9664a29743680de770d46c9d0d6237b7549d329a938986c9ed5c71d1f9126cea5a3908802c69b676863b9fcc64a72f30882adaf35001e7e379b5d3d7293a0df64529eb16a4ec1fdfbb9d5cf07ff0dfb1943903001d4b4232f623d500f7a9bb7e4a4b73d856c3ceb9a17a2acdf78b564062d6b5b8d9531d1bc68c220f3e56c46a2168fb70be437daebaca75109f277f1de69e39fc3ba0bcbe2d3bc9cbeafa88003db8d870144e049ec05b40679382a1fc206d6d6686806464ccb917d219f131bf63008237ba481692c90129a055e925c62bb932e9cb60b7c5a6542e1e62de9d2c4190e9da70bb112edbb737d6a644be00f1ec90476c2b2a239d0dd66dac72dd29fd607154b06039b59f5598cc4b9f7f175d0f34d6af9a58a0dc00e542aece3870c0b72c05e7e144df84839e7f0077a566f6d05b48f39eb63615ff245114d3f980a2590f3dc0810754a92412dfc8d99a2ef59eadc85c6e59d8ad3b3a498dc2c974392f072a4489d5a03c13f4e1f03f17887aec9200974cafe05d91a1cb3f2aa4cc8af4e22b13f9b233eafdb82c31c33fa34971e8ec0b94355197db40ed217bd1c77419c1bdb772e5a104764f4f63ef011c484ac0792f0fb9399dc48efc9f84ec242c290b8a188bbd66aa0c9407967bdf0660e360dcae56fc88a4d835dc21b5f8c548c80a222b66d518a10e9694368e01f8ae65b7d611afb2d4dd0c1b1af0cb9624031146b7d4694564ccee23104aea52c03727a2821ef78ac153e9914330aaa49887b11be2b74c3c80525dfb3292ba8152ca77e933971698b0ab42f118500eb5c9484cd45722c4cb52d32839fab815a9e4c2465b002c8ee76faaa221a838f5e07c88768f80bfa4f00bae766053c43480682ee1835414721ab6c7bb113e3f58f3d7d779ffceac9c52860f0399bee76aa5f373b52487c97caa98153418fdbdfbf29d80db47789c5caf85a1404d91e8b17ac6fdfa754b3594ab693a9d9ffdb735d59aa2a7907cf2c4eced31cc7d35ba27627e58d4a9e69a01dd5c8ad292dd4d6f84aef7922aad97b4d1771d80fe710b806fb2f957fb2b33f7cf02f109550e6729d5048f318991cae3aa1c5e045fd48a1197b780c87d5a0d94e1fcb15604a74fc31011e1c643da09ed356708f44fbbe4d138c31e03e0796c3b13c77e7b3cbb9aac86eff3eab58b2f203ad6ebb405432abe84a6c075a36018ba0b75d58a3ceb41ffbcc99c191dd575525f2195a6f38c95224560898101d7fbea8cd0ebf844d03d19c49139893c6df14419c3c10a4983511b5c12dee3f5abd0e9527fc8d4f45bb526016624c160ac63861b7123b5965d08b2c4143634307d639eb0d23b093875f1284e0f51e8c8636681492d3e11b6598038cfc2da167e4e8afd61b379127772b71b7714b97a78dcd34fe7d6a107ec05efd3f2d8ee531354780ee2a72afbcf1cb0c93872ccc6b160751e43c2ed4c0e110dd2059352b3ee04b7a57f16a4b7ea86676f5211aeddbb387b018d80c60a17d26d9ed067efe108033e7d0fbc3e0a13ee3deaed2fa9be486043a37431d7e2073e2d8e256ee5ea9bcbcf495a5aecb371faa94f40b96beb7f648fc9245fa7dc3aa7d4100960a25b9b040e2c4224c89e08f5883c41130f3a8de52447e1846470db03b1508bb43ce1862e4ef835f17e3aa87aad222aa01a624cc271af1f8e1171d7414ced8afe5be04a40967b6539ebcc2bd9c10b37fd109a57cc44823dd99529a4affdb545e73c6d2f3f423f123e8533d894e52c27c4738f6c0c845cf4c19ecc15b6f9fabb851e7ed5b83f5ee54b06406d926557c721970dfb0146768df6a083fd7f57e152f887eb3941ec873c14cd6536c09209de6a1241d3783c7af9215f05e811f751c50651b7baef491cf6a73d6a5cd870778f945288d467ff8fd2af42ee0da397fcab7a9ae01909e846cb9545b7a89386b90063878a66ad786a5a9838903e03c359fd7139b4fde5393935a11193a379fa08cbd4b06d44aa3348e769e3456758df8ff24a6f07914a734787f3ab8c7abdc4bdf24dddc58925ed72a322573cac38468718faf90e435ef1107b2eb44355dc31bbbb67a75439ba963202e31c88b76a95d47714210b1e0c9281c9a4afb104e584cae58fa9a5417c83fa23ce084311ff2a540938a10d31711e1289182c20e58448dc97f87499c4184b8fd72e16cf95628bab0998cb938c234f1e55c506cb5e966ee897302dc7b7229629a3ead39c540f4fc103d7b45a2b3f5d4634ddc730277ca6404eeb5da7925769d6e3d36c2e8b972257ae5a48d5b32ad027ee7656f11e8632b9d1d1f378faf5d3aa5e236bcb96c25b0ea400f9d12046a95b67b50a5e0e63dc85d2c234e6fdb147a1e3eaf8bfa5427fdcbfdad59303e4b3560768a3639d91a7f7df901ab13a2c9dfeacfbb34ad953bd144a851a9b45b38e54d1c308ca75bc5afd3a31579c4d945598b8635725397d7d92f2f208a566f1d4ba092aa5eff9635041a31184f1084c010498126fa6a66943ce64f24e603e95663684e3b4df87d0772793a6300d10f7672a0d1f0c386243bc495722d9005401c89fda6711f824437c786b345d8149818ee3fe63df89f50dcd943cb750058dd8ac75378f49b6ef76f6f9acfa36263a0bb529f1f2b76d029216e219b0d8328b14acaad2da8daf8664a8eb63796d6191173fb5f06f51670c6a4614b91aca0cc0fce69078cea471da2c4cb0f04140888426834202d56c4c51e57d94170408629e6db1876027804c2f9de2b6114b1970fe84a1a7459624230b077ccc063f188f6d798d7afb9b9f5ca0e37ba54b8d9bdca9e5ca93d21493301daccdde07d1327073f6acab331593dd29353d8301a076a7a7f6515044590455a50686ac28a9b916faa266dffb2f7d326a0c09621cb22cefa13f9b91e051167b847b1d07d6e7753537a02814b995f3dc69c88b3fb1bb0395d7a6930cebdce54af6f310cbe3b4933579354ff0883c32dbcf9e9c174ec9aa907214e8ff7a61f8549886f32f2edc1546ade10c0317a5019e34d98e3ff247c917f67c1d148ccdfb6246370fab64dc677581485a2f9d2bbc53b73b484e865a1c8914eba1a5ae58a0167cf840e809d12bddc610685b453edd83236b4fab33ebc046ae8ca9475b2162a393ab2b15fa271462f2151cd1a65d8c462ed5114832cd10499301b86e138f7e6ff59f7d786224de16537b3ea87c50cbd6a0f74c1890a503bf82fae9d28d905e8a3580070ddc548e973baee6e244b433e40616b38ed93cb2bfefe65e2e354346c502b47640769022633d38ac59e49b7088df7d62d715ad538c6f6ebd0a32cf2903d04a3ca01fca37d77c9325136f0aca385df6e4f043a3ce0b9c5f1158b38b95ba9556e2482f28d76d3b20e908f948eb915b605eb57ccb6a65f311b5144ff400c767c477c761ca876c6398ac51489b177f0e5f5140f732607e55bf832e93dc7474867964dc788d85d3506342ea9a9cc0abf8476042e17754450ed80af37969a7c390b4bc5cb84a629eba4ce661f7d3b07590f34a85d146d363e717e80cf3740bbbf88483eea031a81da28d2384b152a55b3230b8b44b60bb90cf392157cb3552a999fd1c3ce04252639e831ce98a64cece0233f75c7e80e8f5b09f786c2219c3d997cf34713223f808ba1b84fce73f2b37eb396497872bce81c7ce47dc353700cb4f0b8babc3d758485b9bb96aeb0f094c262392550e52d269c18a9c54355864a9c7f39275f9cd37bbe04f3f7c511ac7b08e81b24ac236404468888e799e3d3efaa301798380a21e60a7eb1822082ba0a3d522781cdc39fb6ab0f3c8167b48a4f146bd187d5cf79bc025d09cab4f5732eafbb9bc235664829b6aed467a7608b1345bd600b9553cc2bfc5f6dc057a56fb0bf60540f3f4e5157fc5b269863399de0ab0073c914d24acbdec43e49bbcf829933593c07e7c475d486965defb71198973a46875a414e643de4b47d76d0922748c5c66d71e7a5c390205aef2a9346e5cf3a32db01a3816a462fe35896cc86ba92869a3a4529461410dc0efa8306e9c421029d63d703a565f1fd42a02dc26959961715d2adac1bad478a10445639d92e8e67e778868ea9b931076827bb3f6f984926b439ba695b6180a59d6584d45dc025692f24c0915af498ef213e6295bad86095a72249c2ef3ac8afa8a1c5b4da62f8f3abc1013da360b06e9085578d758251a4032d461ea717b8b27fadf3410fbf5a8b7834c656805cd3d1cab0b0f72fb37176381927cbfb006571323b5afff363437d62fb8f56477ab7942fe5b4284a048c7d8b42faed9f834c20cbc2dfc794dc10026f7e6b5a551578a5ba310eadfc8dce52eca544e7e76c4b2a4f00d21e42ef516efb0d14a3c6ace7ecc52257444390550daeff823058291a9116b3494c2a03065fb32cac431946e8d1e2ff86258c44a5dae9354eb3503ab8b1882d19c50432b6c7faf466850bc23daef631d9e249afec64767a67c556a6918f17271fe01432444d560a65b07446c1af7cae3c4ee23707ec07fa238bb8e4ec46b69c2e8eb1b205077b2c449e4bd5a340ae6a2ba8ba9e44e72f1c64d38b846ef074cea723326b8ed75ff0a09c9882b725cd3e6e0a8ee265e2fe77de86f9ce8e3268869a00b50e021d2701639a24d64d55bbf45d40088e1a5a8138264a4b7cd0abb773c75cf09e26ccd79682535edcb345669391c58a8ccf34bc37054cfd6a27ca15725023efb3c369bd93262dff7e3438b7158a365c68da10f45bde9c929ae4a60a8c25abc4231f5820858406eaa3f75da66fc58bd2802062bc828ac2b6d8e49cc02c65012c7c449e3d505b14136f624bc83e761f0637b7f429dc97622a48cb91b6ae91b6a485179b9633333ed505e4709ef13f9978991cc6b11318eca9813b750a9c2eade76fdb3d9bacca1b0bbf38ce40ed9ed6a32026271d36afbae4d31fd42856e2437d848ffb85e2f2958b311d6f08cfa56c368cd0d37317cadc65a4601053468f80f00c5e574f66394770cc542c6c38ba53e9945ac4f7ff47f0be05dbf2bdfe1e3ba91ed002b2aa6cdeb86d7eab910fd32d891cb3389d2b4ef7a30ba956ab3fc6fc9bf874a1a916d68987c206a31ee626487c8363be5e08f7b53654eae4421a0695dbd8a5740bb028952f6bc37aef7212154f52ef866e2f13a6057a8f8870c3609a00a4c1fe261be988d6e3d3dc8e1d14051c111af9c1f8c4783e87c85d0f45e146a3a81a0d50145ea87cd0be49748b82c5731097ef17eeddd0e900572cee64a2b1e5976583a2ead3814336326052c460f5f1e617e5e3926cded1548b6d76376f226102e4f1befcf860269d3e3217cb7a9cd5d2176bd84b98e0deadefdcab62c58448ae183784b703bb67012691e526cd1f28d2e8b7ac2eec8816872185a735031b5433fe7d93bae4a00132c561f687c60e311bbfac831e9157281b411bb567e78a38db08294b2bcd6b9645a779e06db5fd361d06978f0c8623e56c5de3a9ffab652774134c0ba6e0675478fbf78e482efddee7c9dc5ba02ac4efb0c49735cc0781af6b9343173cf07745dc591a213fd10b3eaf179be30d023b89c1638b5880064b510922e7300dbb947fd6e90802dfd65194dcbef30bd62a90a9e3c95f86ba13509d5858f8fa12145cca8c6c8429c20b502a29690936c34b1b522071a1bde25c6ce7f563799efeb0623705676d31e54989e6396c0f63984f04b548ddbdb6e62c7df17b8c01fab4b51fc330b756bd8bf9e0e2780090df621f0034a5986ba8ae48ff53069c766febc5a2d80c04567e42b2a37f98439286b02e43e4c43b6823d0997fb0f941376af0157e3e659c1bc364bdaa26f38af055cfde83d20aa3653963fe116108e028f983217198f670f832111196c25884cc5cb153de0d4307e354a4aa0c5cd5460e7075e1971193a63e03e92f8e718905fd93a2c557ba7506eaa91b0d50cb0379708c85a944dc910ef3099a600c08f5ec04a3804bf3092cc0a6176a96612d83de621fbf06b8175861fe81121e41b8bd11c81312f3a720ccd17926ee463d8328d8a9bd131cf22c441090cac99aa101770a93403c4a7aee939fce5cbf9580ba3ca11517259367b1b75a675b8b284c15404b5118473551a89de3bd775b34d8748073878bbc70f47fb07d45f5dedb9833dcaa323b34a8b90436c0392b1e83821f6f13265b86b435e495376683be1f1e357772e4c4935471d5fb5539b39ea38129f095307d679ca6d593105c4ffca1cc532381172ad40756202cffc86405079c452b46c75099b01a4b6711e75edfbd2f3004de3b73668e801057b07c58e24ac347c30db4e59b9d2fffcb84c85a2bb6020f9894c9ec6fa450ce401c0247e1de475f8e91208a86f878239ee948c253cb586aaf9a70b15ab8bc503b40c07c887d9cbf4a8b4373f50e7a85433baf4cba34e439e8d57aba48a3062d1a9fab4ad6d583b10049a0efe2614a858c34b23f233b61b21a62f09d91019cde5a0f9b38fda0689afc7144c54c7e2ec683e27e6008fb4631e202f9e3a02e513568c15500e292014a41c3c48107ad6396e80ac9207323142710cece4a57efb103c2431712c352ea5bf26954769eb61d109a0b48a36d7b1179c57845442923d0f53886845545b03692437cb5b1046b5e0c2b782405ca801ab9ac9d3c879ba5c4a28ce072bc7b29ac22f5e470f6f186fc3af4b1144391fd15b29b9564e4026d01dbd66eec5d241d0b512b4b6feadc25a938f1f37d2b0278f30bc2a4b6ba60d21d6e794c553f736e2a292378490e2e344e38dc7849dfedc1fc41a1658053c795d922e22e8b4c267cbb4f68d4da7baf878c3a404162a6188feb1a4c38bbbf3be855e41e28f9f4745e94c9fc6f8f4f3305d745cf9c06bf8eb6a8c796e8fe98c49f6cd3fd7154cd0a83af3a072b0d9731e78f630761cdd921446145a2b2b0952c90930eb80e2a538f7b9519fed2327c829731067878fd11f6a507f0f1310094a1d13cbfc33007b50c4f357e0daf584227fa366c0024d5be72598f1bcec8fa89f4eacede7e6c6d1a8c157065088ca27c72f197be5e5bec5289989c7465088fa634e2a42b814c52cbf019f7a7998ca31347d42e3e308d06fe8cc5cb958b2def8092ad13febc73b4d62e28d338d4320805be494f927b18037c0c488d587cbb259e852e9d880ce01fa3b0f4797c50be65a07378c5f4ab97897b97a5bf47e71b2d467a68c456dbd4c5caee3c2776d7da4968a1e26db8f7bbd4bd1b5622110fc4e32d9d974a12f91a52c373278e5b1dc68a6dfad218a17c23ad922cfb7f157df28924797e439726b8695482195ce211057a9dd966a48b5fb88b785f400233799f48e1097c76ad773f1e6e118708f3663528a0b7304e11bce009021a806d2fd3f11afa2da6dd167c834b55da3136fc83ccb858eb93ec46a5c2a5183ee0770bc02a4a316d18a05d9c1c2a9e00ff6d00937f54f2a6346255eaae9123a8ab33442558606b17d54656a125da03dd539b8e042301d5150ef32b4df0e99d70b781d697469cc3c878500d8192fb07125750928432d121b02ed2409247553492a74ac7c906ebcb971ee4870fcdfe84497e8fe34c16942c7de52f9e890d8c5e8004aaff43a0496b87b29e95f893aea82542f055fa015238c48bdaeba5453c0c18d9da4b34570a17a0dd9f2e9075f90dbf551a8d98b764e86699542e94b4de2580c7edd29d801164138eeea66f5c82c804b4996b0402892b1701c1abd23d207a345fe11c2a431055b4c2f9c3e69dc64745fc00940ec40e68db90d56e2f1447124a907b5cf5e99ea2cd6aa079904962201a6275961601c5fbd22712d265097fd072f216acd4456947cb15e2a2383477a68391e79f3368208ecb9bf1f1dc2d36dc6316a190dc7e4437861f9449f737de6d7c5caa6a68e5e813179564e1d97beaa94cada7de34d595378e5747940c52120e323e5149c1a5310dcb9e19023b5c2e51b9c35667705bdeaf688029d4a9843797d09bd97277bd7e483fca6d2b0002f71f2c114e6c1b7ab8b544bf8f1479e013bfd762f942013e933955a4ff7a6689bd2b07aef097a77724a3a537a1c23e7ddea4942ad47f9b4474682a2a83685ea095b71a552df8b6b899021aa7921e89484687809f434441d6d8ddb3b159f55707e6259dd621fd38b7a3b0321be76970a281e293e39ee8746c1e56e0c393fb6cba15de64c76e97fa77f68ee47a321456deafb4ae25ea6c1344825d6e5aa2b8553707ca32729c02afa0b432263ee72ea3d8c86c6b596cff4d081db8b85d238262d298404a440bcbf296814c072ded228a1dc762853152c1b7bb83c01a83407c2ce122dfa9f6d2916f0f2bcbfdf1ca5eecb9e8565d10ca096f0fd1d2595d8af1d9befef31ccd0567dc1f8a20141fde7b84124a42d6e6bf83e02b53f9556ec3c086231733ee9edc462e0b412dd42b73658ed290721b4910b9a0764b90fc7b4c584e0a3d3e50319d531e0702b1c5ea7ab0bbb6bad806328c9394048b9468461c24c78f6d562453aef6e121a47e0a416b637d966f57fc42ce0c1be6e9aff70ed7661c0b2b8ac5fe846765b81f9289e58c2b6170b09d6675f19d01db87ff1eb0be4b5c6bde49fe118b6dc13f6e766e72466cf9ce94eef3315b46bc6bfcdf4c59bfbe3da455b901ee00879f13428ba34fb5968a7823af38e7fec2a9925c0c09c96cd6532c211dc9eb0ad455474223bcf2e2dfac8817937003a9eddacab5a215b1f457c9b4ce1370d0b6afca5c1a14eb35fe5ccf399af6921819f304bf35f04dbb65e1a5504e42b6ab38e912852fbcfe92d8097a248cdc67e90629456a9563cd342486f1dd48e52be159bde34ce3a623770c19fcebae6027058e3ad610f11cd1ba04bc919b190b2d7ebd2af4bd64d2306faf0715b918766e6d798f74a9650a035fc0bc9fa892f528650d7df0a47c985906a17b773d20bdee9c69288a1e69389f66a93f74ec1ef8e2269e386556a2c7fba7b5e70f78ca2c18f15a709043ab7f418f5193b4ce6676379b2a59b241606cc57e9bd0c49e68156f04af8a80e5fa32162b3c73f6340eec9d7d54b5c54c54722bce84ce4de6fa1375fc5dbbb988dc7cf31beebbe29d7249cf23ee160d4f526c844295119fc053e6943425c956d30f9ab80e916f641fc4ebbaad676ff829fceb0660fe47feaa4c2d0e8b38bbb99134dabc296ce19c5da630b5afe58969f791a87ef38420518fa8e2223ecd18d0d2cf5374a0becf646b899e60f4e49b78366692130cd5cfecc1dc8870c933bd0b37d8ab4ae7fe6cb2c0414a9d44a41cbd9b347d67197c3467bdc8da351ba0ef19c7d315e732197f0e8e78491b53121fb0d6c4f1407b05c7e9b9b6fbc47c2bbe1d8803048de943c9e45d1514131387a847cfe6d915aa1e098c58891e28dff4eff314f7ec516485bc6b7111be681a085a929817299a8f2dc735fdc18eef7de0befd43096aa504f3b919c47180b9158901bf4a2d91ecc87bfb6f42897141570f4d014908c326beaad8f919b192eb72f1f87ec8c7cf0231932c24a1a94b8012521db5adf04eff27269fa19939c6be411dc23e50513b8fae7b7da464e81b8385b52080fec931fee04c010f489a5de4dd1f329c8a1f4a1dbb0ca41fee8d77f95855fd73040e42692f189378ec816595debb0a10986abeb056cadc819d531e72d51397aee59579fc9e3f0df13760e4c073d93d818d1ddac1cb90b769fa9157586bc249cd0935930132a3faf91daabbb2a8d01181f32ef073e732c6fcfc3020933a86f4082b964beab248c7883644b45263fa61b618a57e2775745812715b1fdd15e6932ad02b2c0a90302482ca2c38ebf15f06de22013918b159198bb2c9311bd22930fe6510f2c3a173c35673b0bf1f06d8ecf6a873f05c5b860b85b55069da36b1206e750ef5a7c06b4d399f871ae70be66b7fc2508dab98c631e66ed37582f35e4eed2ab6242cee46203f35a17171ba09817efe88693dc15980f0e38eaa48332bd0b2eea763b3d5349c58835b826ca37c22ef0084e0d7161eba50a1fe19bf87157fb803cb81c7f8bffe53115e40f4fc19daa81ea10ae58d73672f6052f2191881f41e3f82dce1a2ae1871c32f909a6e7c8cf4e53bce22e38ba5b9b123c56d3737b853497799f60eceefec083cca118a27554c04736acc157f8eff18c41345f956e16bb5b07779952a32fcece1ac76cc90def49afef160b026cbcd34b4f57b9914ecafa8c2b9494395861b164dc27dab3e892526f3305f8f9ecae458cc75179dbb7134006c21f1a829f758624d27bdf96d88342e6500306d26b004c2cc9467e29eddaee7804618927037605bffac69cda460690318e1683cfec2e5a44b3158c539fdc79fc64c23f696bb3bc9a81282f76a70d4da94804cb2b623b19630e61f5317a6d975f2cf9429bcb6d3074c68a9f32cb1588aa46181ba56833dd8b5ff792d428e8cada61fd1796ff407b7d7cb870a37b881a4309867f03dcb1f89523b75e5b6a1374d168a042943b74f6438e0d220b0dae13073914002e049b984b70250cf8a2aed9c9432cee6d520038da9e7d9c74a18d8615a8e72dfd8b54c32645a6acb09c2a02f6768b2f56fc175ad0fc6e41d082514acc334f8fad1a3092a22478f780d3fc3cbc9974407c8cfe65554624abaa02dbe23b3e80295154257ebe0efa91090714bd120464c9d52efffa3421192804e510c2373267d7f7338661bfc125adcc77dfd66f09092f82f43010d88d89fb9ebec18c021aa3bf9688ead928ec17c78132256bb88ca3ff7a2547c1964e73af730cb69ee116937e3a211bd72c91f2c57d8b864fa1aaba099a90368a64d92567a5c35cfaf06e4b9b81812c95a4973cff165cd4f2dfce100dc0ad3f3625532aab05cb29899968cccdd5dcb4782d2d8e3b20295abaaab3776336e8c476a54bf4a32534a118bb25c55b26db6b9b08cde401960cb4b28ae9c0b3da2bc75418e95072332096a0f8a94f146db2121132c82d842c3a1290fceb500d8548218e9875cc98864e261a9f1143812302386030a6f8b6ea8487669f455d0dedd3aab153c169d3b35fdc861dee24cb993e6bff13adcec278db4a390717b997d4db6bb2ddfbc33595989c9f44f49013e49eb093c03100000f47e90901ecb1fc64ab2c9e20f812ccea1f6f3c709729a0a67c3ee061d62e27f4a73a0ec6e624ac1f466b851e3c9b049fbb7c13827f036563d4e991362486581e96f03485209a1d4f2611c77be257a4f8a527d278dde663e8aac5889f919a2d8ed342fca86ace62e41f72f3be6196ce18eb7046682fbfb6e50dda64b916ff71f45a60e498cccc4e63945b5a27a429dadd1ddc9b8494ae9822cda57b8c7996a22a2b7d0f7bcfe3c324706597b087023cf705ab9e38fab9e30c6f3c40f802c421d3a5e2a0a995a40bea71704aec7cf485cbab745b426c1d6056c4299acb50ae409275cc6b368273a38e0fd859f6d54f616bdabab70ec60181ed4634c12065c3d234aaf93b20b03fac204f19998de12bdb52cc00b2bba006572c23889e518f7764c3ad8f97c37647c295f77776fba74745571bfaed8f738385b790d2fba634a30c2b8501cb9869288cd072bfc6a042824b72af9fdb0bb5b9e6f8e62fb4607362665c91680815880300b9f6033043ae76427d878cc994699ddf000e433d069366193673d2d029ed6ef2df3248df53865abbaa76d19ff4b0eb05df444d58f79a89f69b288961463f05aa23932c8782682a02677c9811c04d1419fd475ccde64cb120510ce3310ccacdf6e589da10bea368f02709988015f432e489b802845474b4140cfe4c521fda86ba7f55119162ac1cd46909a65bb9a3890f189438a5fbd5690d6f7d02763b5969c2c52f20591d066b216ca16459be1438f2c87d6b4d7c27198105373d7bff656427742374e9555f59bd5e59853e067ce05ebdb503834e2e61ee4d7bfbdd597ec466c343fb4b75791fb2735d5cae446e27d9509e049e2ddb30a04581d6e4fe01602bebce9aea7d0284e58e714dead15396da25d0d042527c0d1a3445b67f8170848046c7adda93846b866670280a24523933014baf83b5d827d03d40189e3c21ec30a8cd02caa84e108cbc1e6aa18ab53f43fc03cf9c20867482652d2be6004d30305adb7c2b66d949830bbbc762f2692254d0bc754fd477c1fd1c41cb3b87985364b8f401d761fd8e2377069468f19861d91b9ba8c19d8513dccb936e84fad23e84fe5efc7ddb5ac517d0d53bf6d0e3d123855dd2221d6ea0192d33e0c9163c012b03dd174dfbdc691100f7e150f548ed892633ce4337e6dcf8a7fb906e7ed1cdc03c96e8004d784cfbb1bedce2ea08f4de9bee27dc56a1353c72d9cfd2ac513a708c93b1e458cad2971c7e4f545b99f6c6dcd38243a14829940d264eabeeb1481b61625e25b087ecd136513aaab326e82c9edc45fc3c70dc670feb69f5b56fd979bebfc4c27ff8da39c0eec7c8c0aea74ce47944abe3d8a3502a7479c448f30ec87e2c954a3fc5ca9abc2da005fd68518019732cf8e6cced8520a5486b0b98db262886d29e2da08d8937594a565b05016f31b66961972b6a73bc82a0a8ff6e45953ee701000421da47fcd4139e39381bc9edb5152d3ad4384d411a7477fb4101f14ef0b9a5485e7c34b9965faca824c99298a04fff132b885660d76d6affb474ba33c9eea8d9a89068fdb29549e2d401994af6e97e1a47f1c66ff2bac848d642597a02b3dd757107091061bdb2562b3ccca0fd5adad56dee5e74587c553fd35432aad4ddadb4cd554f29ab10980f201e4f2133018dcbd72c86c15711e20077ba25d349d233b18bdb0d8cac531eef12dfa851fcae46bfc54124e17833cf3923048e9e385e53c5fde45cf1b8a04b2530676cb62abc745f17e4b10cc3110c8ea049604ead62be6b98b8ef7dc5931ee391e4cc2b14e616d85d9284a5755348bbbf1e68b8899864ef07ef7957a8af1b1573c607e2a957b0a969b89be352d6848042bd33e7409803af668474164b7b7fd46dd8b6e385d1bebd92cf2d537ee9b4f6d322bde67d4e01c9f34fadb3947da22610e2524045ef245a56a3805271f18779f700d6089020c3cbd027a6119f6853b5c95c58ad0d552802f86509598499b328cad70d4422c414e41db30dc49841e8d0012e0def2ad4233471be412845077bcb522592f249056258ee43c147bec5e56424413dc4f5ea133b16c474c8c6b699db1d3c8134adcd24f6e518854689a5138f71c1732a2696c8ffb09e87ba24aec05a4f149d37b0348bf71d377ddcefff8b3170f1c693e2954ea65d875ae1abcf0c415069e6a508ae78b0dc6aefa5c4100724d49f5bea4039b0a98cffc926cba78df361bd19fb95e650393645279a713d89c604513bce14ce24c08b06570a9e98731174dc2bc52e791faed47fcb9b3c529f07d06e8e3d687aff4120dc0f637b8fab4bde341b58e4dfc70c0b8f634cd22f4b600607a199d1ce1e06ad6771208be15810b75a39dc02ce93e9b6ecc9b3ba00eabb2a60a6de8828d0e758a02a02210d56f9957a5ed573cfd246efe1c2d1e868b5838747c33195758590645fb4a1f6ed056db9d1f2be3f3309113eb667cf571365553f243a5812d47a58396356b69c9f2ea8e7058fd1d926a990ff37d9cc486fc1d8fafe2865c6f0e018da7d8677c6fe87d14fa434b1b59f246e0f289641212f6ee0527913828186c00e1582c52438e447c1457fe5ac8b166ceee0070c58d786c1111410213d5b4858afc5a496c78b8e8bb91566a1ac4029c3a2087709c9178df19da41a958cc003334396d527b9d85bd4721e2bf396209353b2fca04ba124b886b0c70f3eef2ae441479ac1c821ab16f30d4f0f3752341d61f196dc2e4b5d94478fe7bf0cc95ed1b7f5d3281518c1088d42aecb4eba2801c80622fac520f4a556c353b107ab2a27a581f1c2194cd9b0f405dc689b55be4d46f79e82e85d5aa812b11f5d09e4e3a215fbb560f6de903da5ed4436c6ca76800194c0340ba0fdaff4eea81c332a093481f9beb338eeef970ce600bedb83ce11f39f9204ac9a9ee9ef3fa926a3d67d32bde059fd9d120308601d405e9eadb5167fc0bc42e4d10f3ce99f5fa84a0463d5412e7f44c57b63c95b08d2149daf056f3953f02969bfc2bc80dad0bb1d3604dabc03fe5c655ca52ef552939f07df4e1f097824aee755bd1f2f08e67565eba22f12b0fd9d71a5c7aa82b2cb671d0ae6c535303e2cfc9d58b5f1aa144789b295c8ee212f1129a6537f84a8e7ce5400a25e221698c2e06ae4d934ea08b3a1a2dccb5e517b8432e25bfc2f5a5b377ad9397f4d91a4250279da3ac19ed6fb26cdbdbcb635144abe11cb6635fbaaff39bff9b0a818d5e1c98584c6343ea5090e49af7a49a730931b9fa644796195bb013ec8d9a99bbf5384784b91b4a6f65355797a27f7b8856eb8e1855f3c1807a4a9528c0f48f49986b1fc31c01f68f5e4ee268234efa30c2bf7191fe49b8cc80c9c936b4aa04f75b654e1d1ba549f63e6874daa948449cb443e4369cf2eb566495448fa52d3da8468eb0a2974fa99b9345d327bc39dec6fd25450dc24efdc1389246410173735ca780829b2fe88ce06ffc04f423ad607fee7870bc303b64ec0fa828b6cf3d1829903c7382c83b5e04878c34d215db34a83ab2b4619bbdad94313fce97bb62a1864324710515ba2f1a792bf06154d68eae99b0ecc66b1ff55f68ab241381580bc19a73ca1cded0354e42220edec584152b12b098c82128a428ee7885e68f6d66ff73c834971523b6793be2b86cd60ff3a66cea0284d8b972b32db9888887ae444fc396a55cbc259c9cc380c37641fad6dcd9f14310d1d265d37d08eb176e36518b5fbadf63960e2abd901cead710ae5e680e253dce0ce0833475eac3bf67ee2c84e07f5c9972e05a4a82d278d3e288362778e9a730120d19c6180136201b5a83eba1ab4566e96f2e6e18a15ab8e7f443ccee43d462e9f1a9ab190ce9668c5f45f70b2543aaaf00f88a9c61aac89d1519fb4d22a4937909e070da6278a24e23c4e36a12aba73ae110f0273a289b6cef5d380762dac269694964488eed71386e7fae4301ded599bb2f45b828ee40a2245c652f2a7c96d51ef93da0aa48b47180236e30b2d4606e7f155345d8a211a62cd4d275188171adeee6c6bcfeccb092cb8a597cfece96a1d4ec07ce12424887804bf1fd56a285a474384caa322a7c1a7a54bff48a7920a8b179d14022dc8f395971920ca2c35b40b65204d83867f7b38c8a0e842dd16b39743dfd2752657bb08b0af965fff00442b120cd81d3f4d5d2e6d9a14cb8b29c97226188d10332bea092a4174ac7bc2e75fabf87b4be1ad22a2fc76608c1c1fe8d7875822930c407fda01e1bbd1e1022e4c9fbc229c0375d238b08e883854bcf3a64e5c868365dd67199852d579d64816144c83cc5a35a9b91c55b1c634fa4d1a05fa826f4e63440d90dbfe733b41922bcbbd892e999076149b73bc1401388671dd5a2b194f12f9b259f4c27913191c3fbf483f26d26cd20bb40e4f10f120dcb0bf1613a29d665242d314fd71d64252be28681d9752545748ba7806f8459bf04445f1259204d7cd7cc724b1f029c5ea135f3b7dd7f7dfa4cc66fe843bb6216b5163534252d3fdb2ef0a290754d545eb68b69a373cc950aac6617845b7c3892c0d7e6bc2bc09192c4ab2a646f3700edc4dff31921e76e74ef58448a2e3d0826d63b8af95db75704664f97f6a54486df5d8a971724757aa3fcaf3a38aaea79e02a6e2ab28288ccab5c7950cc315e396408dbf1cc338c95faa86620394876d0210f06c70a5d7593087632908c4bae40d80e889f20e7584545394223834ae253327aa8b157f39fd403d2271eba3cf1d9869af49ff7c888d638d09af319e07d698d27c6447563a01c5ca55b61b256d1f54f523a2f7d443397de84dcd3e97d678e95787874a682bca9bbbf127d2df4716e892ceff2656201315c3393b7aac4c9580184d542c7f1ae4802503d8d41ed44009c9585b99eedd18a4b5a8050d4bf9f6f19f00e718949bd53105f651e51b6831b40c92319fb2b8283825c3ae215be1bdcb0500424247e0cc65ae34b677eee7d79e2b640fc14bb731936c6dd34f7fcdc6b0d02ddc3cd7f57aedfcccd58ac9201305b9a0a02e3117e7338cd2d4fd63f1089cbe5b364211a9ae5e236f137fa4fbe5562e22c05a41158a98b7bda158c0785a9499de69cb6518e305d1833c8f07d5bf9f2c845d89f2e167a0aa40ce474b5053647516e2c7c353990fc6611d882b4bb4eaec189e8d2a4473d73bcf588054105c59d67f5a61249efc49ac92831c1531cee0b4ba1e957e036a3e24dd5d54b50188ffa8c214f8f093291b89207a24e8ad72421f85e1bd50811a758c4489a29388ed4f136a65f0ec1f9c1a0a6f7f0e9c4974d0575fb3a70a6f5d6254cba74e92e486edd9c7738db622a6975c3fc0ffd48baafe90e814fd622adf7894500344e046cb7fe5806904a7b0632d4e84be99e826e5272be06213c6cd8dd88ac0cb2587275f6c29da42706850190bd29ffba32ceab097768dd72cc3bda0541dd221cee1609b9ea160525cbc189b220ec8d9b22d2b20b2ab745f4692601758efc6f2b30b99d376f47825b3c7f5b1c5fff064222f0856c9d27d29669d70c1d0b706522982a9b42a7532984b6481e02f204e80db79921e72f4728460c7ac79e5acdeaf2f4e9239760d602a299aa3b592aa97615da8b82a59c2c06232dd12dd7b45a7e03f685f22f17cffd2099a0600eca92967d4a0c6bc81404b1118b1f7abacc5794197ed61ff830cb6864a216d313525c1bbf18526d2de58e3c024bbb501b7804198f168be6ec2094ee02325f9b00179725c4c3f3ad4a0e34dc0fa2f417a4060c26553358114e764c95a78c818559e2481693982a9829d7b5891d7992450b41aa6e7a57d05298571b6e812fe4d5d85a3d10a18437e68517015666d399c576ab8563455ebee5b2339009620a5808b6ea290da2b28b437aaf084e0fb8eb2f289eed640690f2b1867d4d3dff2dcfb0d8676e0134d3ef49a4e2e524582db88cb440f53877cdbb36752e2cd3adf0c3126f889e21f50e0ddedba1550d7df95e2ab5ac55ce1389e9fea99fe5176a587e736603a540fc1d4f8d6b9ea582c7917062454a3febf650412a95149db2d5ba10d4bb475041f3c4f4e3ff684b0c14b1cb51c50b2ac459cc08a788afc17325f79e3aeea46839fbc44809c877e3058acc81bf2b831d164b94d3fc3ef00d8ef76af870f3ebe1d14b0fcd5ffb32e4ef370c499827c76082cb174c3033f3734b5605990b1477e1d9f99ee9e7ce2ba9e906f55736a216904860f3fd2025b01b133bc51d835d0042fec47a0e5e89830d20dc1efeac4fc09eb86bb46828ff809e7d4dbdd8d8d809ab2381290dc6b50a6ddb1dffcc43e00b026401e871c801bbb39278d8e457525f88866d5f50e60a05781ee25f09fbb26a8b75e27ff1d37dcf6fe24191d3c780ff2d5f1b7ee8295f9ba1e0aab96f064c1df0967d43c570622f39a092d256613f69ecac4f0cffa453aec8e0964684c5a369cfd0a3495b1389791a81583e9702abf1082d701b242267f4bee335de714c8b7c1ae3f3f29c6fbf29effb4827ff69d92eee3afd207896125f35527073e11bb8e08390b4469b763aefe12f1b7590bd0ea22191d7d51173764fa8f14e6274f58e13506a0741a6dd8d61e49b40a9ba83795615dd95ade32c80d12158df6964c76eb8e37e2ec52f29f178179c333017ce4f69c06d469a728383c7fbe1b6cdf9d4bd40f0956edb7d0e16c4955686bd60f1c0ff77aaec3c725292797f9d37a4eb0047021c5de3593cc64c761f10d56d12d4c7156077d159e837abfe058b14fbc36c5aba46e8685182633da287167aa288a852e2f2768e9e6e6bc06ac01f1cd382d1298451bf2a539d7c4da6a9881c69bf3a99680ae77f31ffde16ddf74f11481465b0bba76c2d43a5f2fdf329f7a81d076e10c2bad04ffd60a72b5958a0d17074c4c5bf31d02b651bae51d14d18aca473019acc3c2650709c51d2017e29d1b6921804d4f73200ded029d99aaa0528da31ab5a116ecff63735ab51f14e7e2404cf1b7bee886100e1e5657ce7a771aeef51009dd6175bdcc6842f321952901ff6166d42884c5ba693023f0217023abdd44ca14cdca31a19a974dc38803a30db888adcca20a807918f2b908f930f08897af820f271a21e3e7efc273ab55a58ab496c69d775dde944a433cdebaf72afebdeb59df777a889f252a8cbaeeb5ee0598245de7bccc8865f5ebea2f905cdeb603167cbf33ccfb34eca53792a9597f2bc94ca53755caa5b711ce779adab89106575f10ee4aebfa2628c11a654abd4aa634dd55c01cd1b1963645d08152c8c55c27583116f3bba68316adacd0d3c7133d0a433a5539ff2a7979aa913cae35428146a5e50bcf5fdf6c897ef9043c518e3f5bd0ae87ac59bab39638cbef226432ec5c2f30e44a53c957752459597f2bc946aefa938cfeb54abcef33ccfb341490806de81a694a7f254aabc949752fdf25410a23cae9b1e8487107a9e0ae1d9614a92eb0679c995abfb5c2eb6596a59ee569b6db6d92ab5ab5aedf52bb5ba7677777777cbd0414d6be32c76dfaf5fa6582cb35a35b071c067242b58d0b0945c1f58f196244934d52b4df09830250a105a6248c2c41dd51d75959d3b0e85ea4e4143184025140ac54a824245d9218466faf4aaaaaabf4eb1b1a9a2c9bd7e696aa31db2dec4f5708021355cb09c6212ed74f289559a38394175ce4583e5c449eb0c6fe179a10a0a2bb851813a20c38e2c5c98b2101e4278588447090b09033e578152c29627a8805103d6b02cf502e42548c88b096904237d695d3a21840233c5e01ce73293fef91624233119770c61324913156cc88dfec45593884586081e2253da9022e6ac84346848af1a9252caa12021a520a1a1a021a032b42025290f9576ca9c4b9860f105163360d76969751d193bc428729465092fb03461247bb264c507c5a19742851a8410424d91ba703c00ad82a67451814704cdfb4278ecc2300c5b6c775b443e7a2c292d0d5999e23b4c0285a5b76fdb77c17495c76cdbb6c9246ddba4847c94979931fbf7ff9b8a09737201611761be635b03651de6270c7629e6f1e9677f2f3722dafaf0f881f23c364618e79c930af82dbcad8327d82345dbad0807827f973812c0ef52877c0ac2672e1f8e04f02b0af2ad0fefa8b4fdfbff1d4614b6ed1c6f69b5a4759c6a75dd932b05205de9c284866ddb1393cee9b86f759dd4af0c834abbbbcb7128e0a0d530b9ecb2275f6c6c3efbb76ddb66ad5116adc8f499ceb06ddbb6fd833f6bf10149cc10b6c4eefbbeafa3d23ef23aeaa35d47e542d8799d4f5ef420e430130cb8a299d6a839618a858b9d3a6e72d885619e1cc4b0bd969b8be2b8a5a9d42d794a4b124a6e9d384d9266f91dbaaeb561b87cfa1de3a22cfb28cb3a2a919094945a2d28509a6813b5c13e2ac43af621b28eca252b29e793923e97cf98fdfbfffd39670c930edaf80186a88bd0d22ac9832716d7994efe15479aca035cb06d779d942c69dbb66dbb859934da08e3c492521321989ca0464a82972f5d3e5f31cb944386ffff7f0d725d23b661415369ae887669a9b66ddb4ba9fecb79f550b344337d96119ed4b66ddb595e13452f086d92a020dd3384dab66ddbb6b568b66ddb3afcaa2aaf57fdf5ff557f40abd55a86c64867315b0f18b7cc86a80dec62615f5aaa693737520e3dd1ea6cb0a839b191524a4ba57fce29e5e9c4713ebc03e7bc96148c3a09c83a40de81495c109050124a28c618858082928082848084bc99529a4049495aa02439290b04f3f9cd09460b13982cea15370f31d2a5af68fef00e3c5d7f35053ea61f6882e2f365adebf56dd5566cedbfd8ffe7acbc6a72d02e69f90dbbe63c02872e74ded8ae90d2b68989a2454e4e01f6b921c03d36f07469f2399931fbf7ff5b8c19018e6834101cb0503e4b018e6834dafa01530e785d7a5d00e048f07d2bb2e0ffeef76bca4113418e6f8bb5a942530552502bd90a18d1f4feedff121efe3fd4abeddfff6f2946b48883c532f8ccc1e7ef230245d8b68d25e90182bb7ffbb67d39ad00297d30810f34b56ddbb60ddd08a086eb0677f9d4cba72d0d2208f2e910a5d0f51337201efcdc5d0eb7c0185a11aa5bdbdb0264681deaf26dfbc64898e94a98292634cd99fa6f33d05acd667fdba2f81d7e0c9694ea936499b668391be857841394491edff5e14c78d28cc777f3ffa7e1216896b7824791d979dd5ad7ebdbaa554005aed0541a2a5c5deb7a7d5b555555f50ac2f47863f6efff373cbce968618f11da25b4aade6a55952a67acf00a151ac2efbf48944e1d37cfddf397cd4ddf1c77ecb76fdb8f21ac5cc9fdfe7dffffbf5c37b9c9cdfff2dfff0398f1edade03963cb299ebeefeeaee5ee97c49a27046c41a4c7098460d8323bf6dbb7ed5ff9d7c63b31eab84c1c3c6da8dda3b80ed519f5c4b3b8ee818f5766dbf67e77776ddb46038123e662befdfbfffd8502456cdbb66df714b160a24863f6effff7ff1fbad02344969a1d6402449653b8de5dddffff7ff9c4b74d45c6ecf874a4a05a7de3990d4766dbf1e9d0f16d0ee71d95d9b1072ba020f855900dd28a05c40a12621d6a3370a60b29a52a4d9216e129ca296d9a07fc1835ade8e601db11812d9e38fa316b66523555c76cc3545555215408a1c20dc156abaaac91a215715fd4bed130a1c33a970c442adf91f8fedf7049b82f53abda6adb9ac6ec9c8607260c07b0470bf88ca4258c4f39cd60d0b8e1ac0cf213e5b30d67b72022be1a359044f30f0e2981f02094c91899c92885646236990d88cc80384affc848b9d98683c9191c6cc3b9204a192994c94c629b5094325266030200122df0e0c60f488408f98932c88f7f62369c5ded4072041e75004fd75ed7d1d5e83a7264747421ff890c5d46f898e9c278845d100321cd214f27296f4e10c26d8756ad6a21ab23064556095555f5590c361345a24a942059a35476ed36a7a6d1e856248b668e8c9cf78967dd68f246cefbf8878544817f667e66d4e8105e5ac96d24b66d2646488c8e1cc960db323933a7490665bcf8cae818b56dc7c82b47030fae817e5b638416b36dd47270449a08e2860376f7d9d8f1d8a05d62d9b11fc9a2688ee3f837d7b0e6544293a499f5d9b66ddb36a1e5845cef7777d70abc20bce4552f6d4971d0b06848608800470ad062dbde42b07df81fc24308e75c4283039519ee2a1a942a55555555b55aefc11f4a10420855c110c19386a7503201e462fbf7ff3b82d1658d36d3c9e97c2a3b2611e7c28952bf1c1def7847e58561d8628bedb5dbe55a923a4f68453335bd0c82e778474a89a4fb56cad3cadded8ced6a6b23e5fe0b29a1eddfff6feb605a2d0acd644b850a55b3394d39280595097b6cdbb66d4b99d921261b9b8e0b079169a8544e1e758a0600488204331808201008825018c90239d133b107140012455a3852422c342a8d04e44069281a4d411c07510c00210060200618a510724caa3d3b743183769bb60ceeb26d0534b614e6ca17022556e12c87d9abb9a859603f2c4621fd23cdda98cf2b80e0061b73904f2263353836308114d1efd6cdda80f3651cd9aebae0b26375be7fdbbdc7f78c105f917fe8209398c5609d4b273cad4b2ced0aa1fc6fa55748813f872f731939ff76850a762ecab0e0a436cffa1925ee152b521347224774edcf18fbfdabb9f2e7bdbde26728a0168329a5d6af17e96337b1294293f39b456b01d4d281e9b3b8cebd03690b3e6eb93f8fa60195aa2707d881f255387860a9799bfc93650945f25c3adee06393046580c56dc46035365336b49d68f37a8d3e7a56b8311367500494a149a925bd1566d8c0bfad1d69d9c950d8d913b9a4e9b77a781f2a99b4ef35d640c7c8af5c002ddad0bce233ba1a67213ba3622c080147a10cc0c28eaa81ac207bc59ece3a8a80806527b6a750075e65dbc37aa67a25e178dc0211ae40a5ea6aebf8445d53c9fbe961318fb601d831953db8a44104a24ddba21b63dcd2f5f54d7f6800168e744511b8d8f8bdbe2403a157ffee18801cee1ddcf578d3850d7fbe5a0fe8979c25a14fca4635289cca689472d2fe4b0a773467f283600e9924f18c49c1734fcc3683ad723279393d133b2ef01b343599048cdf09fc78da121babf6757a202dfcde9185c1e09806a5d2e782ca3463483f12b43913abcdbf4d82f977917886deb650f50732bd2599e0a5094f449d4a08eab119ca5f3af50b43a90a7b0d535b22709bd693a2db2dccf0011b5d37b2fac0ff1ad644d8d0d2a27b45023f8f28898ee5203a514c629aee94a235c48e81b33c09f17501881ac3cf40ba5ab0a83141b33fb4a237d6ac5f21d5076ccab886c68fd144786fe270356f1c36c4a040be477c27554ee5f7b26cca82baae92f204324419b0b1a909dfdab97591b2ec4f269039111b98f5f57009243175c2a311d2168086f70f132428a72ee0c7a141cc727c236771b14557bd211c800475c8454160cecee0e278311bbb42f2b78971c11158ca0496b433660972bd85ba5080b0983c53cc5239662128c83e48d88d40ce6e139ecd943357fa0743114e7e25321b67d2c23875b4dc5df2a1aee952f35297401614154376d9fc8042a62b47dd86460348b40e00aa9fb058a2bf06a31b27accaab32149789a91e77cbbef41a7b11eb8f3eec4321903764133ab2bc782d43182e2eb5773248ce420498bb7ba003d6069bb820e553d93029ac9bae1ccaf4ed218b8eadd0a34308248b85b88b64cd93d55f65faf8d4ed9369d5a7dfdc7ab1b7935701588d6df8d7c63038dbe138a5acc77ac430e041eecc236118bb20abd6de11a466ce36186cb4a352a65fa33a88bcbe056a371e7a0a9a6a284ab28ab0a402d5fdb08670cd17a5c9d94fbea404755db5c4b49fd78d6b554fdb03b4bdbe0cb494aad3f7d29da7e7cca9713d1de2997c499628ba59b6c29fd20dc2a9e53011b9218e30eac35543d811e65866abb37e178af99a8b5e2b650b4f2b9d9dcccdbf20ada11a7084420d4745418b0285d38dab5abf884affc7893e2415a1dd0424d3499b50f32c47e3a3b03f65a9124311cd74d0defc293d36f70f7b93d4032ca8def9539ced1a33d9ae1c608df450c7b781dfeb698fb275e0fbae56af03bb394d791bef25d7d93f15669a06caca4aca109d1ebba42375da71dc41e1d7214b290093bb6cd49de04582c43a8d7df9d8cabd9964dd8d6f74fb32e5e9487e31c1a5f8531576a7a38330980ff4ea9b5dd330982b094655f67c4cb582b79bd3163350983886c072feedec4f1230575588732ae1602a5179c6cf117713a008f3200fa6bfd35e6b2ccd958edb28e02f7ce1868394cf95808b8fc18e85427afa9920c0cfa41330ed4c7e78fda9d8511e772cf72ca96158ae3d3b28dd2a64d0fc10ebffceb4730d969c425ae140b25a467d865eb2b10c99cb6b998fae8f7930227584332ef1dc085d0f64c63e8ae6f34321f9e1006c264fd877fca626be00032f5ecc40f8505e5e8cdf740badebd5117033a075f6bf0b9994ab21024c1e251b37cbaf31da0c8f05cbc46783a70aff1fdbee7f483c24b973acedc9ddbb7681d71c8fa9326085b4855988375114b292d34263ba087e96e22e4c3d994504212c2d50400ae6e9ab6c3edd115d4232255c881722b911ccc422fa1b0416b148415702a1beb3c4a7e7b6a938cfdf2d869420cc91947d847ce3d5656f6c458bfb74cc77ca247fb10174531dc431c81a2ee112f3e7af059b6103e8dbb73ad494c6cc50ae6fc35bd43163ae8e8b7e9efa9d2f4d1597fc22152d87250645d99843589eda17fb60b4ef935e8cf9e0406dbb7cb31198a41234868a7f7b28816d7783d52ecbb30d041032f3451c8d8ec1976c02d08fc3c958e05ef6c5ad4e9206ab15dc81bb8ec76712f69d3e7b25bc7abc9eecd8ff7f2646985cd1d7327119ae6c6a35fac5bc892ad3c0cb1bc1c6f9a11b3ff9f3fb2fe92ca16dd803aa9c477cc8fbef0e04fbab11133cec3bf71500786d7777e04e61ca4335635afe12d3f2611df379e11fb203a930f3466abd27056a88a5e8b442ac3eaf2044e243118d7a7192919a930c4c294522c90c097659ac5e324e05d5a173a02a2347bfa8befa4419aec6780c7919fdff1d255644ca80cc1c0ee4b569595ba56ccf4ee4fcac6dea1c7cc4567ce7c028607ff81b1df21516a7c83adbc30c03c6c941fb4cd598bd9b88c6442f3dfbbe277116476a60eecb5d0d593cf18edf9bedce6fc91a99c42fbe6e4c58c7afeeef01b41addaa2f569ae1b35aa4d110d2ab04688ac9eaa941dd84dac451c122cbeee5c1a4901714e20c40fcacb0fc34bd8601e5d7ab5f061e8a7833a43eb71198c61e759e2d85eea2b35a79e9257046a8cb47dfb4926c985a2451bdbc6d0f574cb79712685fea63d5393cae17e5ef054e1901876fafb1d444fad7071a369c0a3a6008f96556f50c4c35ecb29de3f0b93ed25e36aad52733f20d64266c11b7e278c4dc4b197b7ddf91c9d476ba9c48b74664e8175f9cca64e59e784b661544f9f5f2bcd8244c44c15b9061988b01f01d81f48ef7523ac4a6c2e6081b03aaa6ad3252e6af69c7b50dfc3da48934ff4b3602051b45b84cf0b66c4d07f93f96db354500470a8584a56f6354c9cb02bb9f1bdd0a2486779e60a576723971e8cfa5ba8b2ff0ea51a454210a1bc73173b130e8442a22939d18ef5595bfc3d7676cd6541f7d0e2ab82342153ac428c80230d2ccd83781acacba99954c63a72b517733b014058dc404321abe93c976e8b98be8c175931d65791cab8109dffaed6963ba3c1b751d51caac761cb660396c7c08b0dc6c30df4dbb60b516045c0ac6ad4de50c57e521b0f5157f44e30e6bf9df49161f1670bd3bd415c260176a3b2a2bf72242b9d5ac06fceffddae16ba49b0237155fcf6d8185143252afc50f8d08de8df4e44b770e6e14894631611b899f039fb8c7907584b06527456c1e69160ff517f85d847d61bab35830900f98568db6593e10f2474f060f034a411942a93441b9221e8bb3a3a5f3c5530bc56191816a47a8865b67a2d3caf5ca611d0f2d271e1cb23ed10b760feeef7960f530b83c2995f9ce870c4bbf8ad980190893f3b163efa746dc5f84e7281153f8667db8d881044d609c901d1ac02b1bb353cdbeecfc2bec312a0cd0940f52b4274ff97d83824cc4340dc26ad01200a90a8cb1b74f70c9f5e3febd05da3440b45484b87e9b48fef10504a44d43a441d4e5b7c848ae48966cd7a81d5325e3bd3c4bf0dbe5b64060cb0c4161054b67dab965e0978067479ae7aeae6b7c5dcabdcc6dddb554a158dda0d6de51e7aac45e8cc171e314067183b5b5956da4c6056983ba3d4824c503d52fb304b30de9eee2a1de79883c8e6132a8989318f538968fb6855bec8e1a85c109b087c43d1bc0699ae5760ee8ce7cf9a3356955c3efee02d4412d0b4929044cf09c41eb407b7c878a527afc37baed98cd04815e314439e587656f19b7903ecc7d813892c05f38b8b820030f785d66449a6ead43401d2961a2b13a4252e1858a58f907ed9a6001b6c9c325f4753dfdb966f4e4edf304ca61605993993b83a2652d9d36914fa763a01d11ea53d4d519b74bbc8f50b3034091558b0cf3724f1b5d5188643177ad6667231c4faacff0e2ac54e4591ac6e9df68820fd575d316f72a7e7a2231eea589c0c5db733171f65f0067a68387d42cab69e63750ee62104c4be27c6b8d297921710990d3ca9dee5239ce6e2351f1210d88c6caef9c9b38582422d70c73b342f30c8aeb94d4325c80344789e837ea337811f131e4cd9a0b3880becf6b1a5df2ac188fc29a04934dec44223aa4c8289a5bd7e051f012112009a128f19d7ec48a6e881ba4a65bc95a19ab647cc8a246be503d7b5b0896f4def0140825fcbb792e6c6a3706d8bc173f6a5c7bc901d002313ca9712a901c1f746261ce549594e6292f87231366da1cf29381a6feaa5d30a7d13ce7c06a65ea790511aa82a617646c99bb0e073489ce6dd6331f14e250b03943b4effdd840c867a32d28ac851ff4c997173608d0f0215169a7a902917081a83175b3d55000421db8f40d75e078d0303cca5a7783673e8450693647f6f213d540a741811b7580ff2188e615645d96fd6f20cf315e4035eeaf8e15afe0557dfba0c1275012b3c993b26456e0134def41f02c42af7e63634d7fd0c4022e030b34bd6c2eba47e1910d451c3e00699b794b05ba1c93f46e174d2371b201f9f99a4ac20b79934391a751c577e531d0c40fdb6292b442fd9d90fabba974242479900a4e7c9feb27aee6df095608d70ea286cffa02f7d902eeb3e401381d13f2f5b55be11a6baacf1aed75c1b47e2e94c7454c290bbdcbef35a977f3a299005a2ed6e292d5f7aeea0ff2f79a8abb7ad4d6d13e72abbcec3ad9f0690369cda670bb43a353c847cdae36a494614e4d6c8023f05362365835207fecfb51b53a47464ccdd2aae0b0aa106ecf33c370ff111ca1710f3b3293c33ce83a9dc8d7f38b549334ebefa4877dc97f7cee578092c1b4faca8932338495805f177b4f1d2c40cf61cd4c5e7e3f5e06d4ec444bdfaec351a98a4ca21ac4194695710eebe0fcb6291aaaeae00d1768186be6f34815047c2e3b9abae599194c6621fe48f25bd2c9d7e5366ebe0360d547543fb96c2680c9fa07d3470efc9b6dcfe2c29c2f4cd78cd846615654e6180cbebb1864f1f60113cf934cde3f2e2551da8214fb664d2900e845e28e9d67fa49f98417674b6527b96959d9c4ba5631c471454b21d598686b2d0407d2927590b27ec88beb57e96065133517e2cd6300f63495ab509070d41dcef79d8a97329a469bfc25cdd2aeee30e24ee4dcff53238717309036bfb869c933a1fd93cb49cf4dcf0e1e464db639ef6ba38ffcc157887d1f2beda59896d79b81e86bdb76b86ae7dae3b12a910b875ba10eef53360988110cbaf0319d0fb03ef2e578143d6c29974b797b4605475020033e46abbd89aaa22d232f65aac96240a6aabdc829bcee4e8167ba70207cd7017645580e06b23391c9b12d3917a8996cd020576e013930038a046a155cd7196e981adcb81d9e623224fd850e6a8339494db256a51740f360966f4c4712981827c7b50780ad356d6df0ee1a1a87eb92c601d1e1090aaa82a0b70bc22fefdd92a80175a47cf7310688a1447ca212ebe508496d854a8886a0d3e518dab2ef7331ec191c29873d5ea9bb36afe05df3705c329b9341c8098a186b9d619c0827104a21084d72b9a4126e51d3c3fd273c127c06366e770f47a4a8fdb9b19b38ca4762ebca8970c819d1f8f3437e4adf64774081db8b3c1e9ebc1ef26b60cd48ba1e56c6eb9055ff03e8751a8701bc6a43e7733eac5a2cffe3824dbf37726178496da00c5235fa1ae27201037aac3548552263e73b342f6eb34a709d5e1f6e1c8f3afbd1e84c4acfced9349978029f677704340bd60a6cdfa7053715c5879d0896af55b36f364f7e244be3359e766d13ba5bebfe9b6ffd9cc12f9a0d6d0c42b851ab735170b97f9ddc1f68acde38c6a542b19ee711d7de94aa003ad27cf7d8b3af9644109b8fda4c21dba6f595ff53ef494bb973780481aa12ade54defcdee97e55c094eb0c9d80d526bb52bf2bf06345091d730ee7aeaeeae7e1e543bd6524081466347b08ea98bd7cf9a26b0989358f520be685e2e93df711ee9a75183bd7b596b0ddf85956197efc9c04122fc80374fc9abb8f96f734feb1c9e05c73c1612127646ee8c6fe4e6c799e2a695161353f8195222675d9896c9dd00d09e738da2a085caa856d5aa52cf047dc38f4f0c95dcd721931ec4e56d98b02c6e1940e78d050596f603ab22c0ce19d015c55cf1a6a9b3c7848cf17b63bd8c1f4bfb438af47d15a3f35785acdc0d771c0bc7ee28b1d6df80040962253ffb3e7a4669b63b2b6f2f7c4a26b13a4dfb30fee23dca204449022227fd81099e3bc70a489809301cce27015e6c33a9dc5678a1f393bdf9f2933db396ee68cf38b3d5714cd39c45b37dac783f14dff0f2bc54adcc546d617bb631aba6b787743e1007c561476cc4e9f735d4af50c031edc5078bc498eae92cd40449145158b9eef94d6ff93eb299ebdb35877341f67ba002c458b5b129466b6382691f55c181f927ba57e910707355210e546c01e8fa93510bc523787b34b65ac0a63a406b1b2feeef7880721c5fcecacbcee3574ea1eab81e10a87cfe1639b156cf40c9815cd71eb0abdfe7739151d960d7b398ca821931a582641ef074bbe961cc7ad07da493b57be7619736b2904c8054c44739090e72ee34be5116277e24d00a86087adadae8449394c4d743b459f38de05cebf0c0c9db6d1925c30bbf95a9d09225560d9c31cb8524f014522abdd64ff6b6c226542e597f8767fc2b266a4d13f4965d920b75b7bbe29b23a5f17268e5149a202c8800f1baa96feeaf186d0ab4390e4bbc97e5c6ac3a4fffe587dca6f56aaa6928db30e8675b03e238872693bf3899ba6da8ca1967df35968e0f2373e19c39b6fb8d0c946fa9fbd172db13f4f90ee9d68850fc8797e7a582931ab4c5ec628d285dc06457bdea10318110e88200445eb794d81374fdf79060c97400770468fc283ca4ed7d3b891281015ba2c3a45ce76463c9cd83324367ac70e36a52ef4ed6ec2d7aa14a5e317ca3b00d99dd6914afe0eb956414937f093549f18598f21560a75c23a7178ae6c4ece7954399ace62ff51b22933c10228d9b32da1a2352283953c731577f1d384d53d9f92b3e85229d33356baa62b813e37f509d1127959c605b0dc4eaa121dc243052144151b6e6225abee6c8be9d277546daf3bc8a8dd7ca442fff8ef355e2a6e7a1fb78124b4e4d0e9ea35a5dfc4824a633eaa405bb13044875c912730e6887e647433cc82459f41b03281fcc627b640d73aa04ba5b46e15892fbde12217fbe835df90ff3a32be3355e993207f82c1c114cd0bc978a161529c5043137787d4b7da193ff87b6eba2b55e1e32e31423e936af5519e7f07a5ffd77ad31cc8b10411ce0bf92a54cc2652e9f410a35a18aa299babefc14613a3f107fbca52c6a76c8be54c4e25b9d7069fca4a7bbd2fea6a01d1cb0ed2f8461559d164c5b9b5ad733fc0a7dbe6778d8a2ebac5eee54ab23f7f0db36b614a3adca4372c08e4c68d9a1ec7b5602572558c9b9de9bd96f0552c5bdfdb8103c83cabaa467e433dfccd83d053ce6ff42d818a4eec2cd89bd57fd4913fe9fc83fdd1b6f90ca244f5062a556e3f37da13b6d396edfe28a1a1722127beb7e10b229649b7ae6b6d7e502e86633882709f09e2d4250efbe4fb42a03e765dccd06094ae7003762e9620c4113d8725522a86847254bac28913d397a8f1f03e2572b0a3dce8703a2ef3b5be8beb930c4266070226b2f83fcdda6b0ba5b31b062f8d7178f12140aacf24c297e51d20f4ad8d08b62164c76a96f3b0a2c3089b057d6820769ff68505acf3a6d0514a16ad91bd43e5a11c312c04e1e11ca2a7494ffc5dd3754afeab1035b2809ede17d0fc4c35296ef70377a91283a17371e4c9c308cc9f130236c8fa647085f14ad0a96016a1fb6cc211b3ca3885cb83f791170767c39e48b5a3ee6f308aceb90a1b4b338140e81e258083a098c1a13352361ddb735191a72cc05ce6dd42eec4589e176dd5330fc140e750b4fe1ebc5b5a7b355e91a519b90865d3aad1fbb34db8c410011122195205e92e58858c8670c4bd75cc9865e756c53163ac70d5863b85dc85c6e9a2385714f7635add098d3a80203fa047d0d5b0f360ca89da93a2f42c8a46585c5eca73e145364327649c97f20bb3d5fe40fec7bdb0be5223b535e1284da0794665663027f7f6400a270b9e0e5d52311b02f8aaa941efd44f7d119d9183cdd0231e27a6cc1dd0879facfae89e45ffa70b64615438aaa8085c2e8d2afe9d6f775fa3ea4d5c6c4accbbd326cd038072b15244d8f2b41ffa025306b675a88d473df8c020a2636a490c58590558aa2358fb03cd53afdc9df51ad5c9fb51e39d4d6dbc25dcc4b46e0a67f3beee0816a078db00c20f64c04eb2a88088b83e4a376b9b693a2633297d309b63d230d464bd35f07897a746917d36598c2b8e8de1df4999209ed2a3a9937b38b24ba52d9a24598b248f55e2e5b4d9bb69dc44a704cb6a92ef5e0399e90528c377e45ff6fa5ec0837414ee335437e12f84cdf879a90686feafbbcebda632d85ef11b5a4505fd6e21333f9bcf9dfb55a4a30d5bf3d9540578b569bc7c077737faaaf622990a21c1c5589f8d5e574ac1188705d10c75841b3e9a8beb8227c68f27b454ea4b3385b780699b68a40f445f89a83a228f5161875ea1e709c24087f541894e8f70e5996ec38ad7ba9407b6633a9978042731cfb9bad3b4b3f4d2812a7ef205b74c3556e6d2e5f5ece6916c9de230c5cb5706ddef4de0d7d18c7ce71f198f8d070482e89c77c82ef50a20e0db637f91a2249cd707e18bb4ef7630a61441f4ae05788d773eb7d55099e07000783dcc6663ceaa24a16a4c0ebfd9995702e45d9504d885ff599848a731a91eb1c4728bb824e6f15201d1b21351b2cbdac2eecae29acb5e04b7da20bbc6d6fb0a61112b742452c17846ed0961ecff2d0507b724a0860fc5d11a1866a5c1a2a2c5372304c7c2d161b14bec99cea6cf4ecae8340ca26611439b6e6cd4d340f70b67fdd78b30af2b87dc080faee824f9e3735aca389ddab03238a51693dcb26198a6281a6ef3d8350052ed549509b4cb8ef11fc776fe0cb2c78dc7683c321f233ef5a53c65a54eafae4133c020b181195dafc7b9a790c2cca1b9c6482a0d9d5cababab85c184490c11504b8221f3e4b55f9aaf062b3315a8cc6642983b4898f64b523166dd766e5fa1309a0ba4d5b1ba4b1c259a52bd8c6eb2bf9854ce6937106b3848d8d776bbd60ffb1801d97576c71fdd5901fb5af0f48bc97755052f498d30aa03bc46e0c8e81c582c8c1dc93209cfe067372472af83fd74d42731eed17ceb0e9ae7de0cba8698eea619416b32b44a1dcda5c0f36ae46fec7704027f08532bc4da5fa9ce003ae93867be82fa5b7fe3869b6bee779ad6ec8507f6ac6488926de9010ed72f9329e1573cf42eb720ce2c55502be1ba15c8510d863e2cdf1b95c111002cfaed922424b99b67e1b8f3fa215580b9b6a85724bf6045924af3e7a1ebd18f233439886ce60d44a2732f95f67a1919aa1751b37378eb64485cfbf6214f70374d88480d3ae7ae39d0560f31fd23e0de2512c2c468114b7403077fad700f59f7a9d932b4cd1a3468bd8bf97741ce528f2310a426f9f344ac9dba51132163d4065a29082e68a822241110ec7053eacbed56cc8a85c885efda8e316a0e31cebd767a1150b6bbdcc8ff400750c9bada2066eb1fd9753b61aa3eeaed879b6c1b99627678042146395dfb022c55fc072f7eb27bf607a69acf4d58f3c09eceeab4ce05a9d799f3725b8f3a5b7425a208b4256bdfa64ca3cf16c7c09eea0ab3c7af9db178b1d6ba879dbd624b1a796ceab66f98e632208a80d5decd66f7b7e234ec85b842d496a23285707ebb6cffcd0ab479555e1298b72432aad10d708ff5ad575a9917b0e251ce5dbad4f9b31e43e953911d747e20cea7455a5dff4beb57b8a1a3f63a7256f62fe7b777c34c19a985a25e2837eb3984449e79beb013a41e4aee965223c925e59b6357df5d8dbce34f012dfe75f87fffd8648bda66e01fca9983cd0f38971dd9b79eda3c253732d8b786e5e094c7da47f57e2b72b3cd3c93140877ddbc99e7837c3959d9bdc2595f0b77062802c071c317953a17e293d72fdf925d5cd442592d8cb7412390c428df0bfb3693ad6ae6ce7bcf69c0d56798e20cf8e95df8be95165baea88c73c62811d41f4ea19dbd16f74729a428b465c255ddc56d03e543d5af4923925041222df388bd882139c47fa5ed4736f0eee2b74c5e7986995e809caafd761d7c39302b215024dd66f7ec060a78e4f77ecfad22eddc54c6a16753a37f3550d920c140c2c4ef42d4a5cce8a1889f4ad9d30925d6791731d4f36d87719944981028c6cde5a0876c316b85d3c7f1daa223ee6c0ad5d8dc60f203a7a3dd35ca52cf3efb6c37751db6f37854436aedea3aabbaedf4949aee82b7984d354a5d7f9b0daa37cd62be4f9431cc75be154a1d4a538b780e28a5549a32ffab9eb4ebf008a331c4f917c16f95d7efac6e633e4b993c0b8607888ca43801d6cfacddcb11bbdf33419243011299021426844cdcb4bbe936655dd342e12eb339c0b508053bd875b36fe494a7f09cad5c9183000478d4bf5639c0b0b04c04c1b003903f993be019e86384999c7a7f9f203a6e31db6cadadf4acf80c3a20e400d7fa596cb6dda90447fe8d721405f8adb5ebbe80da39124a137ef10844ac2e488cd1f6f9a1616b10f08c444ab513f6a6f85c9c23e68ef6ce3efe9078d7830e1a3caafe73b54c17e5aa0a6778c420fc9b26ca3aa6106bdf6eadcdb3e7fb53fa2d7c66a6c60b2fa0b93dcabe532620fc137dbf97438f70c68e4923640b5d647244464e613f30bac374e9fa7fd9a996c10849887da6f952f7b4a09d0861265eed0e6646764d1fdddb532151f105b89b1eb160751465ba8e65a920a5393fb91fbcfa3da53c10fb4460e14c9cb0c08da1bb40298c6dda9521572f0e1d1e1c1e2b05e99a4e0425c72624e538553e20c8ec66bf2f06ab84aceb84875f8b7a13020e144fe4c1f95e09c7240a8191551fc17ca49873709760cf8bc18ad6bf613cf2260f14c60f262b917969ad08267ddc0eebab180538e79257d33a79a48746ecad0799c327af213187e4d189779ca117470d2d7a3b7163936bcf6e6c3e8ce6377ed696b017fbb548d8c464f760aceb231afc54fcbbdf77577294e66b1a82e9722942f677c0c39a3c974509925b183752b7f74c824c48d52b6c26ab11e6bac3cf4407086e703fb84efce4d13b26a64c2fe015c664f77845573b7cd91f3d4deec393d224005d56f47b9b428e09deff4fc842a30bf60bf8e2d16d9ccc72791f42576b025f0ff568eed1c40857e8ef4d55a1e669ce8ba0ad7674774c15e59e802e197ce7f1f0a9402838cda76888b6de83cb2ff6aa43354e50a38dea12ae84593e6e28fe388b495eda62d21850902dae53bfe3b666c57d9dd75399fa64ba3e22233c05bd85dccb6cce610f3e52c2cd8826199f00f6e7ea9e6c4dbc13d8d49eb3b0aefecc094c94fcbd2e554ba41f2143efaaf7f2bb33f3cfbaea76ca696312122e46a80d8f2a42c09e89454aeacbdf30cbb1e5ea4d0eac1a8d8a8cd1cc5d71012f7c0b2c0ef974a71cf15b6675bc62302bf8f4e6671ef019e5fbce26a5c73cfff307f40e1a7980cc4abf3de5aec3cc86100252934cca592620a87f3cb2c7531dd2adb42a5c6ca5062d5c8505167baf4bc1194068f8a12c6f0a60c4831fae4208a2c3e8f0c5b847bd6d94196a613d6101cf9502231cd05dbbfab5812185cdc37abd51407c26a766ace8c46dbc43d287ab0f178e208dd3975853bea23223bf742c49388d42cfff2e7177d28e260cf8378cbad06a75c68e9df0ea6479333f35bb397820ac12047e9394382e89644c51433c921b4bf48e4528dc47f175658402bc3b26c0a553a5a615d6f25970af7debf14b717831479e399a25d88280a2bc9db5693cd1ce74332357af2c2237c6ec71557990cf984b3c76971cfa9919beaaece107140e912c54272fb710e9b81c7e37efd27d4099f15d1ac28d3b69325e40cba3d4a3a19ded3fd2e31dee35fa2c87517f118b8f587fbac4f772092c641183dc1e6d2296de8f9a8736af014ff4bddb1527172af22e564eaeb113534302a9b7c0ea3672d8bbf53f12ad4eb8a4153b7e289ca91a66298d10209dbb47dae92718079103eb5cce02eb53e208b4d3902cefc9f2780f5e85cdd00754dadeaf9df18275c8703a602ef68827b521063381087213eb3b63a77906db67ca0fa797376650e9fa1e66f7802d7529a154de8ba28d7100cdc427b01830401640c63176486803b593a7ca2e95d98cd9b8c3a7f50800de9cf10e1303765e971cab953737ee9457766dc8c98e694fdce30bdf0abec527c21813a065e0fe5f2ad4ff4030bb1a6ef485921cfbac8f109ef58b2d284e97c9872e0606c5319e78a0c8eb2f675b19172b604cfe52f70ddf3a3aa323d1f0438b6475ba88559ad0f26db4f83c318728331d2c0d0f095ec078a498d5e082a34a008e7a47ff515d4d7e88f4270dfb66bf45d013ea6019e1f29ada4893d213aba1fb48c863c5be505e1ab19e29a81bfb9eebe281ac778b41d53019964cf0c8f735a6fc634caf8ce4940dd767ac860a14915b4a4ead0a9d5777742bc2588d92c1a4b0cdfb9bd12e1faf9c3068be90fe3ad020233901a1bbc29f952773838560f78c5dcc6eff2d914aca5cdf0d336444ec1cca5656dad7e2833326098041b28becfa9a6b3c9c9e99191630e885b68cef7dc003db52a670831e20f106e8090a6c942eb6b30eaaeb9ad0f2212d2d4c5fb53f05a1df46968303b8acf55a92b4bb33d4cec275d9ded49f53d76e813dde1d13d861372a85316a2c8f5c791a8a0eb2e04e044a4b35265c7a09a7f3266c05813f8d4162490d1d47c55d3a708120d36001572fabd8c2040043de8de891e086787ed11d7e3118432b81248501c611d2f91c63967a0ff48f96d765449f43c41dc70c2eda1e03796c04aa47b1dd7c64905bd036124b73a1a01942f034a2816705a20cec24a079d0e0e5889797419f8857ce0caafe084fe7efef64f02aeb1a0fcb1bbfb9785e5cc2173fd6bea4a6868dc4970b01b3991e4630360a1a68aa26781203857e92328f8b2732fbfc2892e5a9384ecbd09917b6f99524a017606c6069f07301ab087ed0b3d8c46c71c06fb5dd8af9922ab4648fb2c0c464545a689f0b6e73cee7b37f76eebf6411d2deebd754792adf73850476b2bf2afcc3db3bec592d2ffb503ed64dd386e999979b9df4dcc31eb5e72b3e3e4b6bcedca5d76264f8ad7e2b28cf7babbdb84ca7d3310ae1c87f35c50c7bcf541ff481abf5e823a80d0fa7ddff77d6394202b2716bd1e7ca06e105e0fbefe0b9d66d8fc8d38ca7af3deac7d5a149a821eb984b8c7d98ad439e158cbd97e07f79bc838af818cae55192152570dc41bdc01ce03794e4fe388b38ed1b709698b2042c7663c6fa1bc234884116684b379a8283bd6e2eb15b6de157eebfb40d60ec1016bd6aca9df4356a18ec140e77793e3faa863fe43ce718f21a1a1203ed4c3879b99c160b01b1572daa5a66da0db2d116159a8382e95081d5be25f5187c728c57106b50f6f36e2bc7931c88af66e906f06acfe2c8ddf95f002b8f274c1c2dd19e1ee06ea60673292f1bdc10f073fe3c759c1dfe620dc209c35cfab47a4c609c75ac529ea5e46b8cf20ce7b2cea046de57ec7564eac57b5da85a390da79ce6b50ab5d38a4a96a10226d354774258ee3e277137ec76c1e66f36df3b2b109b7a76108641da72b1c259871c768a7fa6f22344f7f10ebe78f3b25c214833a26d475b0eeb8eb1e369bb339e7d2d2d2d24e69d0e0557bad321fe9139c68121284d5faa2edd71be2faeffb8ffce77b90e82988ffb87ec68ed683398b92415d19f7ca674b73893a42e0f731336f9be46dacd55d21495c24240297f6dbdbdbdb3b7e902b9811d1d155ced1ffff1191d11591d111d1118d2b222b23ab23242ba2abffabbfba6a6af567b5becdbdbf90ff74ef2f9d26cf28deebdedeebeeee2ed4834aa8c790d090081d731ad5ddbdddbdfdfd3bce7b73e2511775ea15cda9fcbf8d5f7120a11b55e576524a29e5a6c43a956ce2254c78093731efaee4f52e2ec5853ee552de45caa7a438eaf0e8ca75abbeb436fa39b19f78894f6dbc2ec5719ce4bc4bc7fab940718182e202d53e7e2309da715f4f61bed257bafb4a3ff5d6577ab7aff4e852cea5a7b47371a9daef5cfae98979093bb804a8b31fea2c8b65ebd741e9b3715f7a72cbb1c96df36df3ddb027078e179879977777993f6e4de16f3b96cbd97706008202712c5227974b8c798cfb421dcec7d61bc12ac76df5852e1708be5cf45f0f12515104855cffbd055e50d5ffdbda2190bdfa00ac8e44d508567d3a4febbfb025c63a26c17069b2f691f102b8f77751d631e9524a96df8345dcb7604644232f77e0bd371ac1aa7f91ffe621b77307fedcc1b666cd237f630be6e8a494524a29e54c4a8ee38d67409c434ac9cc2ca54b295d82e1eec8d9aea71bb57778f5df9ddee716c0b5fbdd9ded8dcaeda2cc9dfd061054b2dcb93d3467b3d96ce5dcf9a3632c472661c666aaa4ff8edcf9bb33da34d55e1b26364c5d68b3c48689894df75db7dc87a92b72d5d77eb67af06b34ae13bb8fdb3eb146db7e47095de7e96c986c98fa6d96b44fd77e1b26edc33d94f378d0e07fcfedf7180079691d03deaf8d5e9b250c6645aeea585bcd5f1b26ab662bae623055555673bbadb2b26152c554bbbbd4fee72aae62ae6271a45572da34b18e903110bd632bf752284804111aa0f4b4d980c93eeb9352e729b57989eb00d4d0a11fdcdcdc1d827d399f96fce8ad92b40db776770f394fe380c201f918faf1a3bd7d0ef5d00fdf6d877eac94de54f287aaaaaab68a2e7933786b02ba4b4421b50498244b44b1c493845aaf4381a5b5742cc04bce2354c4799a4a0f5108159d0974db0bd98488cf494bebbed3d9664645384989938894ba93b6efcda527291d2529b54f7f23b50f735292f75c62ee095bff35eb6b75c3d66f61eb39dc9698c35b16b78502b87f76ace7fc5100cd975efed6221dd67ad0628eb5d1d3c2fe30c08789238ea8e35a8d33080630604e3a6f4441f7d9a348d81f44cb85c8d7776eef4a4d79b7b856f388d971bbbbbb1b14192ccb56e8a945fc2c1652d75638bba05c59217011cb4d8787fff63bfc37f14be0ba7d8be5e489b2f6d39eee39668e7997777799d959dd7d315708e7dd7b55dde7362d74dbe64686762fb9d9719213e3ecb22ad589f540cd12c511c963dc0e94fbc989cde3ef5372322b590fc7a26cb63377776eb9979739e6ee2198a8edde1d7925d5e6bc59adefffd13e45435e0ffe102a3d5eaf44e442eb01abbeb8a9d1ec0d2f5147c8f53a3dc0e787f5ae580436570fc76710f6bfd043d6ef58ab9b8f1f2404394fbf2b5c5f5a2bdcba81e1d2380c8184f56cdae250f71c9d1db7df064d7cb0e14c165b48e0e9f06f875f980026ba28740143112c44b658d17ea491210a244e50c1428ab6bbbf3b2b16c946e06dfdf96744421d5f013150231288c410c8fa5550f0a32cca62ef14e6d36538509721e054ff8fe61dc3f232ffa9c1b0c4952f2be06206633cd1e1b483982564b0c882865653a18a2f4fb0e041064fd4508226222f3c3922451854b419e5a7088d245ad338819600261a441762b6e080e50c184cd0988b344c38a104102b64c181c64ff9616728fdb9aa5123e66ddf7ae0aa2da41631ebbdfe9e7edf16bad5f1ab2d2b8f0c02f7db3196c8485db79caed20cfd3acafc67042838810aa8a622a0b8d0fa480bb8d0c10827507ca8422380132f0051e5042c663852c322832d686090064a6a0b5ab7b02db1de262c42c5c56158cf51fd7ddf5b3575233f75cb49df7fdffff8be0fcd4ec16654169b7317e8fbdfef0b53e78ffe048a5dd09738caea4f234bf94f6c7958cf2c66fd93047538d9bcc7415201a66a807a7dfc7ddf074ae0eb3854c16729a6627e96ea1fd7127701c3cf55e0547e36c3cf67180d3fab797ddff77d3bbe020942bfd7a1f4f59ea57f8a482b1ac8742153f9fda97f6066fc0a97f689ea9f22ae27a929aaf62940e57726da67a768a8a2f27b9583e177ab7e7ddff741d19daa21aa82ff1564a9b5c1ef74f3389628283a293ded46e4a9e6e167d5fe1677e1a98eb1c8524bf83d2f64fde7f1671336d1bef06babd562ed0f50982a4f5078028a48175aeb61fb53c3a2e5892b544e38796181d6faddd6cf6db5586a4a3e5556aa1c35fad3d26b8bb73b5c1b88104e87e33677777777afdc26a5949243e81a0a0f067bd9a572a1f8046842c7492985edc872d088f5cca2a4a6e6d756f8b382ca2fa14770c94356094a406ee309bc4ad9dcebbdb1ec96524a29d5c8624b3ce9aa8eb09a43b7d0f1cbc9c9a111eb01fa1ab31c75939aeaf2514f3ab2b31ef4ce7ad073c2601f4257c55d1b1c182d98923aae733f41959dc81b73d93d1776389a50e57bb79d2f27a5d34f203afc41e0da75864edc3327d203284924a49414f6b3a9e3caa9a0fdc736362908b10a44486d1a474971773717326da9b8ddca92e4a8e6cc82facb2072673b5277777372e5dc29a463dd494a3f545463ae6c6826b01e4c0007116e9aedcc646e1b9a202ba7f49d1cc599c9fcdcfd83a0e91fda37b6aae3a4b02db3937c93ce56a2575757bd396feedefdb3997298ba99e655fbdc52904bce5445d5239bc9a44b297b65cbf6ee656e666ea6f22ec6e872d8a0389396dd76767777e666e66626306a7b671f0005f1524aa95ca00f82a40771d36eee6eee4f06e62a66287a7aa89a28a52abb6c25c7706f5fdf755f77f75d1f327777676e666e9641c6d42d5f61dadddd999b999bc5f897deddcccdcccd4a5d0e5b0e1b949c9c28e666e6e628ff6226c70c6856038837de80663ccb0104d4fc838804a3a195ddddcd4ae29b8cf5b43337b73333333373f3f7ccdc67de2ceb61667ee666e666217677767c7e999b999ba19ab9a7999bff6689282c6f000ba827a530d8733f1537594a4a4a3a41842112a42c555e30959939acc011abd0dd7d3b1c6b9595462f9ccecccccc5eb8340a7b1a2f6d99e536039ab5ec6eef5eb940ebfcdcc44fed33c5dddd9bf9caecefe99939b8e1856ebf1c333333333333d32a951b0c0598e1967f179662f1144b318b99ac074d888efce7fb21b8c0b4502a95a5384b49b94230fcc239596a3d70d120436db8c5023d10f4c0f97d727aa0f716a0a2055e22fdef87bcde0543c3521cc4feb013555cc553e4aad70ff99efe10d7bfdee63dc926943af37bbcde254e80d2773d2854c4c81b112a62848a6fe4e5a22b240a127356622e43688151a92e859754ff25d63318a3b19fc53a62f91fb9b39ce52c3f2232ba22323a6239d191134919491d214931d1552c7615bbba32d3d566db9cf18de2a66fc7519ad6eac9c7faae8be2908feead1a5a1bfdcb8469a8aa4989a9a9a9a949a98ababbbbbb2b24a9c8900f1264b1a029dfb068f367f0c6dbc25148dd18673f676b759b7227168bc5aa96ccaaa9e289a2ac6f89e34769b38f2ae67e4355357c0cfda88afd686a6a6afa31e4a36ac8c78fa11fb2aa213a54d5d454d5545515f56406a7e22691f3152ecb1533504657b25c11b50fd7c9f302dafa11666333250ad1150765cc983ce3f5325e49aa06dfde0fec6703193fceea72fd06212c9c394fff109521ca9b1ba9c1cdb66d9bfb2671723c1cd2547342a4ade2c88e754c36a3fbcd689853c7bde2244cc4f302da12c79c3ac258221b9d214222c7eeaecf7aebadb72322a32b22a3a38de808e88ae8a80a8634447475c5af5c5df12baa9c76f5e01dd7c3711c3767cc0705fe31ffe997f5ca8de5ca1613ee9a767b3d90dfb4eeee0470de0e0e30a107c3190830d8149b2abe48b2aecc49be6c1f9fbc62c454af3fde9f3d62a93ff091a4f657c1576ea8e39734039719a254b0d2967d3622b5dddd7f47573faa4872c78aa9fccbe2ec41c5c5759e6f9e5e2c7e36f00517637e50a3c44c0e34116490020c9e9c5c184384d6dd09e12d6884bfe4c26f19946119ea5cf77d044f32b7cfbdb75dd7dd89e37e095cabfe5be8bf4ef3739a879f73243fed580f3925f0f085081b64a0449627de8a0c284c31b28408121affc73aea0af0882da4d3e34046163a7eed35b46d3ca1f259efcded37bcd0af1068e2c80a585600420b18224823a587291da86471c56374023fd1041a95222307a3055392d12691821924d5297ec00324af45ad47f2cacf7a8f7545b9b65aecbd7b21ccdfdfb13f45afde6cfd0c29d2563b9146c76037b2f4cf723a27e784102011f58220d094b1020a9a16a0b4900393941453b4feef8fcd1cbc0d8810657444924febb675635ffefdd83edd87790e9efc1cf187068df6019db675031441c7a7f265fc33fe8fb511bb321668095e8bbc56e76ddb37df6bb55ce1f69ef75f0886e128a4ee17b63644eafaad19ce1008af59b3664d1d6b3b1dc3d1847e182cb268ea210c2d5b90d0d806344d5e70e5e58a16a0a0f1efe46d89fbf847e0d1cbe258a08ec971dc823f591c377fe4388e9b1cab9bb3637120f8afdf401d2f8f2b70a47a0fba7a74ccfb97f7ae1f5f2fef5fe2f8d5f5bcfb7abdc4d1f5e07b2e7e90ff63ef85da07fcef5de02734f48922748cff8c0f24fe39619b6fe1c8bfa20e6ce44aa363fc3ffca7264586a51f5e4015059927b415e3258a20c8a4a08b2d41d0f885865e4c7df076677bfe20fe23dfdb429dfded972643eee55b60fe268a4358defe707025d603a48495a5de238e5279876c3f5f864358dfc9e71e03db4f2a3db69fa2cefe76e110f99ca8b3535362a254198105c90c34580c473811c55496a81a36c949de720b6f777eb2a0bf6458e92888d1a46f01f92e69cfdfefd109a30fdb96a580fe02a1448b105da6f8f2250bad5df01286852d56d8220634b4fe9d1cd2b5c1b28bc7bf3bbbcb01c93d776d34a089d7d33ef29b71fbd7d33e630ed9e21c620c949835582001838d160c41e405342f8471268c8ef1bb4f9ffdb0feed9f34785e2d567999fbf34f4a969b4b87476571bc409d5478511d7b4971cb6ddb36261cd74d281db08d4b8df23c5007ec0375c068c7c23147060d74a4513aa4ead60c10857654ddb1355597353b6e73b9d8b5325c634d87c78c6a535b1c3950abeafeb6ed6fe08e29ab368f95a3a3037774386e97ba3fbdf975c88271abd5dacdb5dcc148ebb2be1b26ca0277b0f65bddddddb52cbcd4f23e0ffcc0d981dc06822028416f061700aeb104d5c558cf00ced0b1a767264ed5fd761850314945ea6a05161fa05861460c1a922e5005981f946c40e108171afff24f0f8a97b5254fb43843268c24aa2fec059429767082a48b134dde4b4b2dcbd392192c6a564822a586e40a27a92d62c084164c30e1f18eae085042796e800a628a181958e9729454a5062952383c01066f1391ed7fe5d8fe6cdc921953a5091d9a7ce142191d4d5990542951418c20ba703acf3eb785b15026b4dc0d03f7961c6cebe742202600e169002eecc50644f0c3f8772af175abbc3efc5ceb6cc8957a3713c6a16652e69f302b74a4d5a351a37d3a07b6f3de0642d0ad11896246490a5f9cc810d1419a224ab64461815314099e92445d7183164c98a868d2e187309ea4989cf4f69649c117464ca1e0490d5d9ea460050baa389284122b767b9c2a5d1d21f960dd2a501b4183126084704a228ca9143ce143154638a912a6055778bb138018c48821c45598316ad4d440eb2d549254493ac207275a3fed87d552f0c518576bb48872a18b23be70a2a4431659ce3cc10fe35ffe6924db38e92648d92c003228a022713765b438524493224ba664d4d3121ec43c4963c57700013f8c7f8d02a0258c142a294d588185c65310d14107196e1023860f788f04172829446145ca1315377021a50b162c5439020a8d7ff927087a7bc5f840d5a62c59e2062d5fbc508184c62f043106922bae4c4186c64f74b1a4872d569044191a077420a287215cd04313332378dd401ff0d48255172546a831c2a40523a42873050e52a468698a8c918598334f10e1c50601c6952d479ac8600505bc827eeed25dc3c59d5814dd8979b7387906cc56fb3ce55cf6596aca3e6799edee9252dddd5d3064ea7e10333533e128fb41fc6769483dba59a7dbe7fb20f863fcf4841a961fbc3c89210517525c416315d668e1c40718be709ab2544b42010a199ac4acc0c8921d226298a1082d509e589841c50853154d46b48c792985c1a40417279eecc74a388a326286c9551229581ce550068a1454584a03f52f3069a18b28b640edd06450a024c91054656e48a28b24351c69c2821ec2c812e64bd31543249d01631f26822f42f0b0f4e504235fca487169b282253088a85014802c505145a001e2c9548d19b84085252e3481839a08745133450ba817c028e322d2650b31495ee02109c9db9d1b52e853d8cecf80b73bdd228d3acf972d49a89c547171a40c8dc348618111399051d3021affc754b03e6441424a150d2ee49006cb155140d952658c9125bb33fd0067c600428c92169840e28126b808838553920d6368cc636dcb115f5459192106aa0c6d9fa47ce143176028a939523bf2c58814667488a2660a087c214673b382b739e79ccf2cba5027ee079c92a8c9e2b6fd722a7fcf4fe37bfffd3c4f6c85e3c60a61a10ef7b272393424751d763bc9fdb6cfedae381a75dceeee6eb76d5be77289305fd1fbc0b592f56cdf77ad70e6748c5920277e40b62b8ecbbd64715dd775ddfc6eeb58531c856a2716ed17c9e7b871c551268ebb2f7138912bbf9617dd73674ef798b3c3fecdf9b27f766e658923eb65f040a728c59f322350fb6c536edb16823feb77f8d7ea9cf2b9ef6ab5b995a253eb5b22ac73b834deb6beca45f337ecde2bda9145832b771d0b74a4b0f6e11c1a53dc7eacd52eecc465fe183fe587455dada0440b2f62e800c50c3440260a891a94c240620cd7b1621caca1deb736f8773d0e8ca0523fb27938f042f74129b21ea8d78614747c56a8d3fdac21ee58b975415f62bb2d38d5c6c1843907b908f44154f7412c96330deee06954ff01721abc80c951fe63d79146e54dfcb5116b1e5e40b81dccdaaa31b9899db8e2071deb9f951381b06c6278b11a3bfe3dd6aa8f22a8cd108a582c166b6eb5eacfb933bb151878e26824abadefede9f75a40b87adf216f6fc7de09fa39727c3ccef13f719be7f33acf6bbdb3def33ccf33a1cef7c271639a0f84eb94eb846a200aa613d24c09838688da0c2c5cb1548507335167683c0417303b80015565cc0f566871a40a19682073048b9d408045d07f193dc810f6638b300f8b7a47fff61ef6cb20374e375228ffd651264324affddb07997256242fea9723b408ebd8263ee730a6b1b34edbc1b1c28e0c2251377421051a158cd927beec30a6ca1a29860043e397cd2467b5edf7e8d80e756cbfc7898ecbc450057969bb3a1368dac837ce00ed07edcbf667564c54a1a1d3cfb57f96a6d3e20d2ff667fbb117a868480fc012f9fec926fb407f7660bccca596048929c67c21c30a338c41e3c00b0c2a38a1050a0c5068fcb2a82bc87891821037d0f809258c40c19234505a20a1f1376f5b3c6e652c507e0eb61b08fcdb6fdcf672ebba35ddfc1dd299dbbe0bb76d73626db2eec4d1e4fb63dd4df41052eb21a4b6925ba2dbcbbaedcbae464ceee060429f99e592c7624fc7589664ec798e2b92dfcf8d30cad2c89652226dd2fbe5f6344bffe1e62bde7f826401f5c316458e58c1131aa350c4911c7870c116595841e3a7b01bbcddd9b6ffba3ecebf6d329c2f9bedff7e2c0787d2bae0440a1469b834c9c2a5823552699cc68c1185b6fffbb16d2d56e07e7580424340419e8321703be35e5c1407f6047d5e8bee7bae3008e883200ed4b11b080205f1ecb69f9fc85a66392b1c69e8a1bf0cb75f50023a3c44252a340414d48de1bdc422718bf8a558b4bd73918b5fc7645b282bf1605668cf2cc707e2a42c67f375b8dfbab1bef55ef87d32de1bb9e6fcfcdac7f594fecdd3df7a238e37efddc8e081be7ec11df4666c7923787f234e2aee8da820d90cc92b9511ee84b1cfc1019148aa5f77cacb14551cb9de6cf0a3f3b0dffee1e05daf136ef3b87e9b07268eb3e2fc06e1368f4bd4e161f32e7194f1fb337e411034b879ef46748523ad2efa74a76335e88f3b35d631c6619c671c9b1f6f5ec6dbfc5617cebb705ec6cf9811da841a843a2180bf0ffe3e0ef8f4bdfe70c21a3838e288e4b21d282f28819b1b1d1e383feebb1ee7b7d2d75397b81dcb11c76fa76bb86edef52008e2e0e0fc0eef71be566938ca4a5f37378f436f429b5f706b756d5c4f5dff0ac7a3eaea1e0ce7b7c29182f040eeb1c211492e272e8e16d0070a92cd26ac41229ec7157122d2c6ed76ee2292ac45dcbb3fc771727fecdf8ab697dbcbe7c4227f1edbff2616f5fbfbfb161b07b3425f06e4bd42da31df699fad3de04bfcb6822028e6746c0321e83873e6e751013e17debcbe7d3c195c28cef7b76470a1e0f7b7fef3e9e6013ece4d48c3d9b1d74b1cc16f89db311b719c558e00bef75e2a17e654cf1b5f620876b45ec73e0dc7a34adfe33e2e348013753d0d912475895bc4a247bfdfc2223a02158b582cd2e1f15b59dc8e7defbd0e4fdcca5578e158abde37a8c3a3ef51512784d6d3dfd17afab50a86a3aca07c815cab1c3215dcc1f76028c3f1a87eac6f85932580ba853b758452951df316d0af32ff993b950db39c6c5d8d36fdc748cd488d266b349d0970df7d0fce5f480ff99b28a4a1bcdf80887e6eb6473989be2c8aca0a4890ea3f9ad055174711d4ba6e72ca6240b777b1c8fff37fcff9b21591eb3372d5f1ef97bf5289929084152f447a28e2c51ada4e91840bcc34e1248c1c54a05dc089195018430542a4608596e5cb093a8c41c60b2824fedcef906d08951edbcb1fd23d063ab131b089e2d2a89a072f3d90a99dfc8d7b3f43b9f27b7b91fceebb0fe1bd7b2e44f2dac91a1d63b9d301276bb8585305c50a62a0c4b0a58c0d2e88c1520cb2c98e86db3fdcf981b6079dbfa8848ef7a31a2a44368380200123150000180c080503029168401c85a2b47d140010818c426c48164aa46116a3414a39030c01048008008800c8400dd20e70b7cac5c7e5b16ac6b9267016a00a732f52813eefd2ef8b07dc574cb4c391fa7652eb888e1dccee86410de60448850f8985175dd12e9b92d863c5d74038a3537bdc79bbdc00175d456b5c962c5c740fc256a98a8c15a78e5a0a6665e438152e18b110b22dfe9f7bdcc36c5e191552ba22a4f8e30abc8ccc2b929aa3a52e93c5221815b61968575457534155ce6b6986c7e24d483580e06dba886dae928b07cb85c73e17c351cb68c6bff58ab1b177fb2da9041292fee536f41b52e9192b80bd4ba93669322afb5f086f60bdc512aa568180445959940cbeac1d75f984b415832cb4a318b1a36a1b8950aa385262d5133d247f0978febc704a15d5e750a876941850159381d1d3efef4ad6a0d9b3aada1e61c42c6262b6a54c00bfd406bced2630a009b344d20ff4d1b76a5d7abd7c66746458c9da96985dc445f7c5705f19f2c8360586ad890bc0dc93caeb6ca77f42a1ac90502452da5d4a38acc5f32f344dabdd573a257235856139fc90c093ff93e04756c587d817c2fbf52ed2e082360f78336c6379cea95c1338d2e152cef68c081ca9a836cc405fd2dfb2eac3698ed46e448ba232c1aeb372fbb0945f2f3d672f768c49f90715b2abc4a69c621321689bfe6e368c200d6ce8aa0155ffa746137b99a6c356064c46b6d27a7bdf077bd86497bfaaca51adbcbca73713174fdaf03ffe8b164966916c5996cc68e5e5de61fc10237467f7276f4ad2aa7712a703253398c8d8bea52beac0a95ff9f6aab53e49b749453681a2b26f4831510b7104d376c1ce986383287d53ce7228877e5b20365516e53c1ddd56d9ee6e1b9c515276befaaad007a4395968d79b4f8b50cf2ad7c5d522cc45296baf521ec46059c7de6e47bc6fa10cc761e4495df9ae38a6ace7ce312455cdbce62abffc65433b4a28bdd87947bed0a59c53dd9851efdd1e1d2a334af18597d06e1359d32cdd11e5a3c2575f21344b824e0b11fc6cfa45f30a4625a79fd92f7afffba66267c2be148448f11ed9bd0d5095aa7d4f7984e6b306e603a7a6f8048a2ca83152e2eb403418384b089d62889bac320a9401efc6be5f7c693db4687f320ebc3eb40d0bcf5c56c0aca02ca19170e760af31d1d7458db45f22cd77d1d362d4fe459cacaa0a574cb71a82d982ef0174cfe9a43b0b8771947ac4614b9b79d41fb74b50a99fe86189269943ba98cc225de0cd912eca26df426ee1983e10f6cd831a22533f28f9f8249ae60e1cdc843f9252fd484ad406555ada5160c010acd2bba7d030dd784755ca7cf23eda3d704b70e438626cbfbad6b0d216fb5b3da91730bcd37b5d5509c6f209857ef1e3c65cb6e89596efa05633fb585646c731607a6ab107eca266df8e41e27fd113756d124f9984c973ce19247b35c204390d149b5f23b5731e5ae0352a8d466934d519ebe5fd2bdda131d8f7372dc352b6d2bd86d8ca154a2e065a63ca08b72c03a90bb83405ecf71d7b39b78a2b42f92741feec1aa467b597985e28fae05aa84ce1da125d0975a6fc941aa8a9edc4db05c9845655d79e5fbb5fb2ad13086d5d4d412d896e92daf3d735032223365175e7d369fef150c72482f1396ed8231eee73b0d1886d50c8ac8374d9c98e260d85268ab2fe0c5c30ad85d60591f44fbe1232f95308eef0e3882bb8b2926073bffafabc1ac482eb9ab5c44df81a1fd5c16fa649150dfb6e25e00c22d026e284aa7e0d847a4f5db78c0140819539403ce8045b472b61f50557cec9746fdda4b4bf67f09fb61b1f6de43955f3fe58217135a06022dea638442694237e270d3c35f2289f34db282f8a020faafb761e9c10e3717956677eb5ebc5c0d2aca1d100257a471d8ea3c3d2a4164433dc20086eabe5b7aaa751a032c0eacd0167555489c6ca510b8626d40a9a2c143f026de9fa1d71371dfb2ee113894558d828173e6e6fa117cde76fc845a887ea0305f9fe523fda27790da90daafaa911b0d43628bf0381963f446b2a09f21cf819c8c62b79784e4ccea26500c73617f3a2c585f3b52af09a28ae9b21ed24614c2c2093a0b31953cf8c06ab51c6d338bb70313e00a61597d9014f837e464f75efbae1a2167a58cf9f6a92eea369d46483d6fff3f547c611154a7efa8fd4c26a036524175692f533ce980a95b28bc6318d81311de5c116d7d590be9e3337ee3cd2715de86f8426c20b3abe31d5fb47935f2c1dd57a47ff197c0c88fb87340193d564997ebf892bde409615084d87bb88114daed9081e124f70bd316ad41782856e0de16a24296570d33fd016bfd23924cc1a31ffbc8c1f0cadd586ed1ae3e1b8cd7ea1b7bdceca7b886cc8f87fd4f8a9b79ea5d8098e6c5e8f8cae319d92c7ef6e5d9372ce86110f3c63e8a744f1059ab6354eb55cf9456aa01ced2e36303872b00eaa6559472f4f2a6412bb98b75561be11da102114990c1c59356de57ebb9966b22bdbd63ec618d816270de5dbc089a66a859ebf6a65fdac6460f70fc1c6dbdb3cebbc74b42eac7462f6b5ac79769db233923471134404189578cd762e53228d37c11f54e3a51eaf9ed584c7d0fab13e8362e60347544ade70ad32410597e6ad4cef6f9b2d6b9495b6bc51c5bc7d16c0755f0ecef11b5e5a83ae20f39a42f2c61457f8b66037b8695c74b1cb1061b454aa5d0c3f71d1e3d24524a06aad67afd4f1011ef831e7152a1a9342c20ce652fbc1d182d1dca6f6d120af82f115fafdaedad776abcc8e9ae7fdb3fe47778f838ff225166aeeb7b5cdece2b383542a4745e7d9985431270123aba27416bf0c68f4b017e5958cc9597f991f914307bae7624339e87d54d5052634743cee9475694a8b8230fca6b2ac5498c75cd07cf6d7a5e54c3ef528c4687c78dfcce05d0d64abeb4e7aa0b9d60e0c5af20c3b3fdd8be7a73d8cfba11259a6e8748d0333764c0fac2a8e1a7135abd16a4a8b652d10e52c52017fbe5727f86ed8c329f76458943dbc5c6fff8e85e008befc058ce8ebbd56774dd1b8368d11eb05b0b5f70ca0495117b9d34467812b92ba20edda4558ce6b06c94af168c816ae2554c426fe1b7aaa53a7b771a687d2da74351f76a2b712ca6a0065d6c6cbb0674b0b53628877da827c23bb3b48339123d715efc8b062262f76fa70d6d4744ddc82b7d50b3d527b4399d6bbe98689ea7133e2857dd6e97e80e6ab9ad27715e6a9f579d7ecbc848a5b4182d7a2f5aaba71bab1a914846c2ff1a831418b21a9a8e786a8d7e81fef410aeafef6769cde4e9b11b89a68c19c09dc25a1c4a98eb57ecae9addcc2313bcfa9f0dcbb5aef84780074ff76ae6e19a2a79eb75294463ade551610897dac7046a4e36905ae49d9f6d27160becafc1a957a2e2969121b5e4db194003dcbacae428ee4f1cea94768c566e2404ccd188e85f7c9037f71ad0590da8fb0839bbc6c385e77e930cbac9f0ecc026b6db8eb0860fd6ee07f18be63b9001f8e884f1448cded68309de0c507a631bdfa4875b305937b77524dce4b59dbaf827146f0f5b826fe07244a3f9df9e468e531b075c81cd95052d602a958911978c45dfb5d44455bbe02cd1507d49bbf280402e1539cd5ab958f3f006c6115a548d2ad9bc07431d2b518829531afa589f2bb05562e261250ac3432ff0b31db06fe40d4a7172b4383d17ab44f096a5bb6c5523ec5901e2af79a441bba5d4d78a3738c6ba447d60954e00c93ccda65199630675c03100f62ef9c3aa4fb75efff123c0d8194712d8e916d4b1b5367655180661b20770017ccb7658d65054fc6ca689663f19b6bb49ff363e0824db998db9407ecbd8280a710e6dc2efdb4d349dce54c7f36b0425f9b2d3e785e881e6b5007ac28ff51285646b1fa8d53beda19f562cb9a831f1ab567c9e683dec90551e0d0d512039705ab70f7951a15a388bcd552dd94c884ed3ac60bfab60231c6db1dca227d67a2d65624f4db647c2d871dcc6aff4c7a1d9bd71da2d48033dd614912f1a045acf97c6e667414ae7284d7ce5ba9e86965ba83cb25895638c8a56cf823c0581bcbaabea49b7ec5996e2ac9815c8e1a0cc7a8c03f554ec9b28c35697a8036cff7e24b874a973c7970fcc7cf2c0380c9f2be9e5c42a0934945eab2f4479d438c8cf2545b165afa1289943c05a2944d30e65100323ff7f2f355e25fdd2415c0b9a2a13a5e43f83d3302b0fde4d2de851d89e04daa38f4e3b8bc45cb486d1093ea4553bb1d1a7a839bb65dcd7490144120126a4f30f9cb3b270c8ec7aef71630a7138f856cfedf19780983ba585a8e813faa331c91e0adca3a68107d09383b8029684102dd96cae36adbe33af974a0872534088418f979bdecfabec46500ce62d10e50b16da148cf0d4d91220e78b77a5b3ba4e33eec261a8f4a009a1719e468bb4ae27b7ae758d8aa18c8eeb64d379191c50141b16aee47d5b6b7073da95ccda5532e2b6ca5c22c150da580b5a552ab2f7192213c531362e1c57ca6830754f95aea5343456e1f0d629360744917462057bf25b13ff1c404867941de5413dc8d1ec74bb6d152037dbeb07f7a3e0c6e1661edf102a8dbbc1fa28eac2c832f2539831485857be5ced2f6b768cb4e35c70a2553f934c084cedcfec1b396a8fa6d616a0e7360360e4bfdf7f814dc3cf99ebec478a180503098b9c2f6c9117bdff0431f29677db0377b13397b765565044f60da13883f59b15c37f856bd166e8bec06112e2fac46f6c81f89a666fe8835ecee5746950eabfff856c1f98b67ae7d76d0325c637c77f4aeb5078505c5be4e9035c725f64997187c3705e17058a9d4cb2b3f2a759d1e2920a1c6f1e15d052d1c40c25dff5b27198ea7948950f14e648634c7b714c26d0c4dd057091caaab282c1329b63f49d92dcd855ef60fdb4f90321feda8fa9969f5e709e1aabcbc1f898593f64334bb04bc712c55093f749d5df0f5028d0513704cb8091d5b70ff676384108decbf64824792299e97a71915438b889bfa0ed85ba558d9f8142f3dcd0b2506486ba83c8a7c288d2e49b63194ef7ee77f35efc44412c4ce451f29ef0f50c94b95ab1f62a2b4fa35a629a29e070d21b2f4812a7f6db8a1465731e62ccb589eb803b3df9b15f29d684e5cedcb48bec4701c320b776036bbcdbc02b22687bad9585995c396327c62c59968127d6d8b6ea01cb690ad9121842c105cb2747d016fb3c8af87369678bafc0411cdd6af9174153078417ca9ea4ca0fc8d59545d41cec6ccdda3a3a74578c63c192423e8543785c845f1184078685f09888889d7ef351ac70688e539f24bd570202d7fc48851b64e87844959f72b533188211ab6c4478b4a83f8e61c60e1d136f6631acd51c8734dccd221eea809bb3dace0aa92621aef8e459b76d2f2956c63719a60490bae9702f5be136b45e7a42e978cbad7d803a50d53ab66f030bf0903700eaf1c58ed2c7ef6912b0d12ff1b426a8a603dadd78518cd6ffed315171f2bcc054c7cd32f991fd1d8b4973ed1dbcb71992048a51d805c3a32415517c1e1f1c74b0ef3c15772850419ad3ed1f47c28cff7b3800d5799ae91468fa8905b4f4324a78f1a588eb182f3256b80cb92bffb8b8cdd3ade8df81dee1704f56096c765f9dad9c4e0bf5bd37524d71e12d3da133a93fee2064813a05429acc4324390eca194725c3a5e9fe16b3a0c4254c50a74b624c82bc6c944d5d2a13731041ab7e35115910c94658820fe6ab31c25973a6503cedda15710cec55506843213fd3e1f28b3342806dc42415401abd064dd01f6641b62473d60a91c846c39f42333d1560d662a84886a8655a34283ecd6018f106b5219590db645e4c682d694d87c5cf941ff6302cba872506715379c5ce4861dba3767ea247bc6d634ef416d65550c6cc0d90378706d7861a9b73829d7b1574665ef7600a757d2172aecaafc51ef78dddad6e5da2071b32b5f40396eba9ece6700e301d0d01641221746e8f334c6f253f8fc8c0b688cd7c5f73279d7cd22e05264ed424e443f0ceb1f4112fc8db91078ca37194b6224cbb849e38081d58bd221340e06d5912df301ebf093e3057b594477ae9751c4c61358bf526e3fa6126b95549f2d757cba9e257ff83fd9452ad0ae5d05ba2674881443d29bc5ded2070c60e6586e6bc2a7dc5ad437bcc191434eee60e93ad4251f5bafe090c3a1178e1f4f6b4b1368f331ec6cec8e7b6519f57c0f0a585f2e5ddb703fff10bc98a460f8853d08d2f8329cb845d8b9ee58e80f075d6009f9a1f7556522b4049878f52b7ebecaa1c74c1e199d46c91cd98c0f2733c2d6d30cc5713830f70017b77ebc3b078b2b1e53e98f1f157402301b9795d06085f7f77c876bc4316180f6a7189f4611669726b008ee84f197b5c71e7837e62e776e0d86e8999d0f8d3f97ecf91b9712d13a74d90a60eb713ea60a60743f600e2aca1172d0b2910e7091aa494e7ade4fdd6e62eea62576882a702be0b7812873381f9a7359d143bc91dc6ef1e95e89941e2356f1ccfc71c7d67a0b8609d9a247cbb4c1173debfe734b331ee66b5e8095f588dcc7494b61e8f5cfa47bf630cf2ecbc12e12cae4b944b25168214dc248912fde1b204b07c1d54cbfe01d303f5aa4c321cfeb98301334d1e5bedf12cb628f2fdcde27a6778d33f2709c66605bd070343ec047fef84018580692fd814b0c6765760e09f230303ee88996cfa8700a5dc61993e84d554272c676648355b946f3e3c6221213f65d54de6580bbe1163633ed82e20d845a8f90de17084bf1025141e4c0361a41ff0c9b7d463c6610738f2a9d8327dbfee74f2517da95309bf30f184b8e96ef7b835a6d9385d5c7176263a8c7a791d024d3817ac53578ece2d99ca449cad90696231d918d23822eb8310004e28d61eb2186168be09122f981ba491ae97536a7990e4a2c68ad9c9569144fbed18e41ff4fd625e298a4450dbbee6f342b6e9629d442c3214560fabb931cdc51e00f3ecd6ec8326b025439597d0ef4fa15f113af074d81496d96295ce9c6edcc251aeca9ed4e2077be0d215e2cf1370783f8c6edaf0652ff0efc9b7dbeb7e1312a780a35323f76478b676b626fa6f3302aba5222adacf41820f7f01c44e98d42a40ee12b52b475106948c14da5db3b3d591316b1963a4f1e75b00f8e28419263d7de38ca248cca1ee95d8b4775b459029589201a1bbe72402ea84ca1728b623732af6fe01fa0711abde6a650f912d5b0a871bb5f9376b74650937897c944bbc5e6b161ebd869c053a0043e81c755a9b317ddf2b9cb608b67aed16dfc508aa4e9d45e9f97d38c881d82e581534e6ddc6468f3220d75e10e7cfd0f428ca5f0a79ccc7229eac404dc3982eace869197e7540be81743bfc37881555c81e6a11591196a83e2c122d7d197dd0779a9775c45d15d79118c921171f27d4e35aa3333845a31149ca0eca5d11f2a22327863d922e53b3c20cd233fd18f558ca8b25304c4aa801728568d582fb511d6002c418878127010516c7eadc3b49d4e984a9b48557966ef8ef2046af07a7c725c431561812e02912cd09595d9eb11f827fcc0000ca1677fade4f04d8e98c3dc380c3cf32a86394cbed4b94618f6397062e9ef05878183046370fc757967dbc93593024095e2f25c933b0bed52be69cad5080bc2b194415dabd1ae60c5acb1878991f43c5b102c778e7ccb11b9bce5027c319f8851b8e5d7475d7c6ac90a29db7aa19c3efc13114296e3681e9d5a7f1f105caf2cd83ab360f0425520b729212f8c87d70d1c319662e3e74029f21671c6ec55794afbd1a7572cd07b1f7475ade1e927bc0cf402b9b3c09e6ad8b67677891a4533e136ed5303cef7c4a2b82ef00504215d66b1eded2b7365cd81a81a4cf3a7664222a7d51124000b781354d2c9c125c6a919c4ffbc6f0886d3e177327fdcaf59a1c8ecc9b6487f3c0612637b1db608552b169e36b171c09637dcf9729bfd1dc5608806d60a5fed581ef53967d759eaf4289e1779b1b2c111c41d2329dffb52ac6edc516f3af40a935c9fdaf121d8e3d283f6408927c6077ead2b261bb86547122ae558caf4be9575e2d28b35c4114bf8ebc3f9b9f90932f629a095a5989c00e65c3982cbeb1a9337802f4c5844e2990579fcee5224f506f7f02e042a9381a197da2a5799b54dc185d8e882c7a99ec919d0fb4a8b0ed999beba5860c2b335ed909b17b8fcea3fa0bc2278a30c1d96a04bda0d447d198e1a2fc10dd80d327f271564a3382a927bffd2ffae149a5a487dcf38ed3801568382db9867ee564d759abd61d774e77dc2bdd0b25599ec6332a52e53514ef62d304bbf7739d1e66d35f7bd62f444266e98ea5d42819b2d1701f32f138a1a8284c0841d7394d13fcfbd09702aac414c3b396935f8390ce1ed84c357f1820e9f3d7038c5ee8ab2f8367546bb5fc72e0574f43166a16237bda7d322e93fdcb76f995421cb90f8be3459849279e417b68b84bcb2ff2cad267f8dc55c53aae47f4fe0129de12b4e7aad63635d1413d4a121e2a73a2b00674a79a98145be392b625473c7aae2035340c6be23c8f2124e4919bc3c089d924e871fc2754a9abb505f982ff5c974e8a40993c0715f8f8393bfded8bd306640af082a495bef6d0a95e547fbe72ec21a09c60b7df5f1e20c19870337df580474c415b1859964a41d7e607f665c3a68f48a30f530aa06f60a0b32134770b7d95a98def19780656b97df90ee93e837455fb8d530e10632d0ec881a0a4ba143268e8161031a48adbede191824e0fc1676c45e3cd89f04d43b90b21594ec23315fce42d459077bcac9410b8b0a87e4f3399e0294d971a4f67102865f78575ead19425d0780d8651325028fdcc4ee812c9215f8b04868a0d79098d6f6bdb8990a7dec3e2d2416f86dd2b187aff013822effa1f577a041996946002895ffdb07eed1004f8ef247135d33e80794cf1763106c160b3e7411e023bd97ba12ae982c703868dea717ed0281ac8fba7a515fa9e8106450f41bda049d04779be995dfc9057425834bd71b8d15f7afcb47151b569fba9d5ead7f41003097b61ad7742590e4edb6a29f7aaa90f59f6bbdadfa4b74a9bcfcf19082eff6b209f8c7d28cd0a6ccaac2a8ac6915a382421365b642b4ccb53cb04838d8a2d8dfc20c573a56d14cdabfef4820515b830a6616cd29fdb65a711db1ee6710ed441b7cfd0987810d68f3760020fe7e009139ff19240b63c1515f84eeed439cdce057e077ec797a6673c4de376a70a61fe144a3188999f17812a803fdc009f544d7d8a43592bf643eddab61f9a586bca94102e146006b79b8f831856a1c46ddce831faa86210228ef717fdcf8e0693802a9c6eb49977018cb46e4b87cd33f7b740caa5811239b2e5087f20cb649ad30a909218b59d5d9b119e03515269f60ecba28fffef959959f40407b6a8d5a39c2e6875b80d00d85969ada0992ec76f459719e29e4f35e8a83d31409bd811fc9ececd13ee0a31e14535c3b2234d0743b3a331561f8d08a36e45ae81a804be4d75930df7373090744d6952f817146b7ba3a070c9f73baefc6c1d2110ef300529d771c9effff0b1b3b9b7390c1fde8c415d958469dd8d691aaa708dab674d960c88242e23ae9a5884449cb474c54d821ffbd8beba00614d9dfc1e05d2e40d3778b358eb05624ce8adc33a2235ce324503dc74d6bda9bc1ae5b0419639de6e69e86d1d076cd97be88c119e6eca22b4194a25b9c9602002a0d51013b4e64807ef2b90b3d80e84936888fdaeb28a07e8430a6fb97af3b10ea27456ab43c2321d5eb4668129ea7633ff827c4afab6b687e83e82cb361df559588fdecba713ba0a928b2806488641f5eaaa10bfc2ba139bdad94be64a837de738c3839ab14b0a906b67c95741a9ca1d9235d674edfe1d82f150ac0c775e38f53559a6ffdd4375865e47f8933a3fafa58b9e5726388507e21a52a85943e93e68c299d28cc484f07949ee2e06f7b38a8f976100ed65b3290825c1cd041415451dcf26178e3b51acc0bc147ad7a03e4b721bb1e2f47e5943a6ae25a5301c29f494d4ee3d691d0f7673db6235f58c85555f42cece7ed95a926e28c93b6e2524b851c515a11e432f1c4e30d223687a3ff3ef64c46222c5eb20f2a51dfcbc654ec7d7e7a24fd20ce8312429cc10e1fb05ad0fc319ef9afc48b63067c09d8974f0f0ac177088fbcf75092dc222370f2fad0f5fa94504eb69701fd91a0cd43ade34118931daa0da13ba5c05de9ae61165af698cae050011cfe0ef6dd31386d02258963d9e05dca44a765d66990b9f4020f1e63cc6e6b198c0c25f41e43bf99d9ec9e8b7e7353bda953b11de734c6938014d8aa9b9b43f581b2fb7508287f73551d0181a70313526b454a483fa5b1c4d89801b58c791821dfc5e61e6a9494f0fa4e5201b5f5dcd4fce114340ef2c4c31c655c3568d5a4d5560765d5c4c734f81d0f69c744683bb22f9043b12a14a7177dfddb4523f5aa189d43840e9ab39662e35eb6231728cc828374cb6506dcc01497347d7c792c4d17d8337b44661541b03054d002f91d35ae90d04cfbd56aacc9090ca19d597b97da773b9c114baf24ae96c8357b1edbe2a9f4b2a2d6fd0e105887ef6e04557a791ee4fc8dbef76de1a21e3f0d4079b195a33c1210734f04dcfb8d0977a4f02bb5fbc117d4f34a5ae03cc9fc6268aae26946ad51d38401a3c2c45c44219feacfacaea3372860968ec46efae4af8018e7b2417c59d79f62e37bdf2f234ba82b36c6ee974844eac9b3fe0a6c33c7294af51856a6e8b7d7589085dbf345714fcc70bff94300b2c90859270f58c6514f4c51bb630843a978aa7f311b4d007873099675fad1fc02d4d936b4fc5b8716e445ed42fddfe1202326e8809dd553d7945f10c26051660128a6b40be6b032efa012fba4b342eb384156ca83166105b17cab6d617965363321672a46593988864ac6a942d68a04e85a2aa71e8f4a0795609842868dfb97e21282d013445a27580df6e4514a419a8503d68cf466b00998d9abef06f6e742555d88eddb624e4dac7ddd15b8f2d7dd2ff20672d7c00c6956789bf07165d5a76981418775a1da5260b1f5c0be3a2da01a1aa942809beac2c06a7696bc10fb4f438002896b11660d2bff4882b92249bc88d9e012b2c726c972346b79374c79be54fb1da94d1f06f62989a2f22b6fd81f33c9e1877c49adc9f81056dbdc5793fb0a902caf055ea01004d3f563a122dedb44cba1de9577023da1d55456e8ca4a109b063739e9e2eb3ae16859f2b5ca95076c957dc356c94d87d82a4960ec07b6456785a55ca0faef5484be05d88a4a2446f4463c019030f1c2b9836bbc91954e1394cf405a423719600e197d62fc17da0732ee978f577257a0279c4be37e4d098a3c6f4f0bda14244935d709d2670aad028939d70c47fbba12ab0616b42b9d065274270fa225d8dcc671fcf08e80bdb0b548e4cda1f245f59df8d07ba5bb6b38e01a5e2b627b0437f0700cdb89d50b20bc2f3390d1b92dcf0c6bd7dbf2607f28840391d2a306b71bd42ea89b779baccfb93810fbfd7f19585722c613abfce35b4e3b557dee7b5f93c98cfbb6826b183cfa44fbd101bf0d8a563896355b652eff81e7aa2a811ee8de30fed9411b44b887f05a3e91633c29e29dc1cb9bf203c1a2372cef8baabbb98903ddc3c1c25a25c44ea1c2e6fe982ea1a324f0f22256200f5272823198f2ea1cf7378513ef16ac5a2f4ea7b28be02736c1820063071bbe8a109d5b98cf9d81b55ca83d4b45e7cfb194c5af2102103d1ac7113cea1fc6fc47906385d168f1abc346dba98c6d77bd12fbb151991c643ff235508abb4be8c74b91d3fc9f8692dddd72c30114d61e8273353ecaed08383a70dde745b14cf48ba29817b029f4e51cc8ca3aafdbe807a20319c4ee61d05805f6e14e09f1a1d765419f13d4a6a75093e7da94e364f124d534502c0893410b5852779c1c8163c1e2488c0529c7b6cdbdccdb490512f23436d1d8577ba1cb81ab47eb22fc46a69dabf3ae795e1fe4ef344c9bbb4fab17d8d730e59fbb31cb70eb2005ad64b79efb703f69d71a67d1cb63583f101d13aa1b4ac20849a44c663595dcd6877841a40e839705b5cb041c65cecf5222f543b9dbedd4568791535a5a6eccf7c245c706b42e7d2a3dc0396a739a45e870443c9ce0c6abaa52a960ce1598254ead37c6493d30abade5d9425ff810738443e9b5e623d3076ed4cde42ebb96f0af75626b87de20af1d8f59dd2176d4a8a5a752d0c5b4c17754bd7c8df0ff14f57a0c956ec3a54a20517e11ebbc6787d8cc00cf0fed904b25400cb35ef52535af4165fcb61d8aac8567ebdb8e26cc0c376bcf7ce9e1de7ddab3009251d966cf886f192dd229c39c8c669d781a41dea7123f93dae416a24d5bde950ac44d79ca171b5166754edbb9775cae3a605cbe5c4c6479b99fccc5dbe265065c94e1356dfc97f96c718dd584bb433b78a4ee5bfd53e072baeedca7a7b7e858739aef7d5eb0a758f0c5351628ae11235c7023cc5e30567f454bfa35390ea3386833d6a6814f05bb4f02259751ae01252eba0b41b7f6f5ddac1266ef6a4c947e73766506d8ceaf34edeff75353800b071f1afe84aa90f2c2328076e763b5199ed6d44c10e5fc03e094995122257f9f5206d08f81e06b0072358b882dfe958b59aed0582ebbc793cd95983843eda693c7fe37e4037acf3d8544f02c9743f36796e97191d86f0f6b2fdc627311bfb85b62922c6ea815e59a14fcc151fe03f4724e96758902f47670703a651885590b8fe616fbd355a5cd306beaf183214f6170255a1921713541eb436b11514531640d1920bfcee682d6fdc1d681a72e83d2d203b787b16c209cc10ef7ed7663af9315536e0859b0816cd0f9be2f0940f2a5d57bacf7d75505316332aae896f826a10646c6eaad36e6af7f70e548d1fd4944dd6fa77d9c618e6396ed23f59de5bb2805120a0f87907335b5e58674e56e50dd7a7d6688160e2177498f2cf189388566b9ddfc2ec31ea84b612a00e24ddb2c3761e8e216ee72657e6525f88cdfd0b284e9f9b05ae832f8fb4532861b486229d84908dfef4664499692a720517f21b2a91f0f6501442f7224849a9603960f0969f7645267056dcd1ce74172a6b60999bda6cb22383e65ab2ab22b804c1833a89147229b879754094f09d7dd3a88581b4e6d26505627f3356391db00b73ab49ea7722fb8800203f497c1fe9e4211eaaaa0bf5ed88943328ea4a4ecb08ffa77c708016407e8f185283d4b2751dade4d08b6b9943786abb2d1e801d4a0fa851d74c0b87719cf6dc3602e20d2b469776a33fabbdb1368c431596bf8a08ad0c4926c1407bb6146d7cbc908f2099dc9d40d009f982f81a04bc781facd793265a92e8bf95313cc39dd438302621b0a2436ca1ee9cab2d4e36bd8520a13bf0fab1698b29ca1fa0dc031274c9ffb3e1f8302668dac477cc59850ae1828d2be1afd13fda4432c353fc6e189d02bec1d948cba98a391025f9523e269c837ab48ae97c356ac809c41e6400615d778918152da96745cde8d81940817918378a7006653c0bca82d948c0218434aee5f9716da8ff8c3e290681473dc6059c1a485b5b3d08a7a87076aba8365d6ed068e29a761d1380a26695a7b8706994e79e20b32e2f5d1a0fcf71fa2a7d29f3a4a7d27f509d47bb63a8e8eb3f57c69e00f4c47941b17d4483accec9c0fbcb9bee1c860cff582af199587c7b6053dea41799a19e82a4beaf00612b09320a66660776a861613d120a7079b3315737c94dd6b99216dd2c8cc42cd7d3008a2a27ecb0bf2c36c6724e4e0b29a89d1d6c228771c132a94b1bc30e8d585533871d3cba38c1b31639969e584455b5e746409b99b251919ff52380f62939a55d272fee34079ee7fa42fe6bf1313b31853dc0d56747a0b35bd2376880f36a710441b9d0ee1f51b119ca78556b1dbd0c3e1c86be2bf464577d0ecb8294405e9fca377eac589c89b874611415a8a97052b1a2dd0caa360cb2f818ed9749b8469c0d3a78e0d963fea781b8462e51f88de4cd79017268a8e1f63b650e6e262c34727a767a036a995b7adbacaafbf671371b7487c6c09904b52541daad10e43c9a973242dd5205120a07dc509d72d254a42728b0a6621e364c22abb1af64711677e9c51a8b999a4cf4a90419c8a0e5381f9b25be886aa7cdd325313006cb28b78b5fcb879119ccefe5ec75ea80da6f20403d17850fbc2c42e1183b008ad7a39a792c7c852d911f88560b254e0b377bf0eff5c2ab29e567fba6ce5bb2430ba68b23bc63879d649c67677fd866939e6812e8fb96a23a6e293a9f070836b7264f6e0fd746326af849fad800067eb378aadb8a0ad800f8224e7267a5043e6d931126a2de27be6421830d06c4fdf70cd6b6dca018f2b8310b5279d2bff58e320827205bf93abadce2544ef0821accec66667cf30b7470ad23f1aa8b6636cac137d67dd08ebf4175ae4bd05bbd688046975391017b89e8a94fc60a061306621f44d564e8b673330d7a5e0855d77d2e09022e02896b50f99229d3a7a35ea381ee38d5378631383c2e9cc8d95409f21ea287d79c4302aa3ae77320267b20b4e089689c9f1696d45a4b679b4109794ad177421e6da072a6d23ff3cdd93516294ad236ea04af1be64ac7f48354e75827d633ca65be5542d5429c99d66f3f734ce123f03d4f9c62d98de0ce4fd46c0b965e2438f741f59b982f76f84343b0b739be82e9a58c06e89041e179918e811585ba8a34fd63a77850bebb1007d8c713bb183322e066bce3d2a2d2ba06712148fe9452bf040b3c8e03af212ad075e78763131410ed85b5f2e76e5c769165207e1ad9fb4781c10074ec2184446b93f49fcced99593f6cc27cf598d538331c9cba4c76a8117079e90469863e01bc2ca667acc313b46dc8b5acce21d1befa5cf91b86d8b5082ca0017e90c491f0da0c9a3a26b8fdd30e6190a5838e15d0c50788a618100157652a4f11ca0ea48089356a7dfe6044d9ed6b241372a13c0686aa74101134c04c7d4414c9c985d5228f5782d4f871b6605633f0d66486496672eb886d88bc521dc27f66551b70ffd77e0b0a531c36fe532a593840e82d0b71418b31aab8841a7a7e17250af9a908c36923e65ecf10b4734852994b2fc71be0f3d42e5d854de57e8c0b9cfc0ccbf53db059b51df6b6a61fb8b7f5e23a9f91c3340ef66e3f51f8584b16fd526796837da741e7799138d573cc52d4c2e8629dc056ec6dee9301e6422b5f20b45c6cd90dc690b7f13f793799e603aea3a8d05d743ee9a510db0740aaf8cf4b93f10a9606199c175ab1352cb5ed258477c541e8b76fc3d8743706960d12ef6a7dad04ab03cd217bfdd3358072a017465c0fbbd3af26a7abef5befe511056d3ad97e99290e9497e919982615a4f7695f4df53cd02e975395a37a2fae0e2a0bea78458904961dab2b52a8dd2e3f3f9bf9b9ba403ef0a1629d1c1758a0e8061411a71e42e0c5725550b07bafbec1e4b0ffd2eea64de54070e3b9f854de9f5c3b51eb1e050327100c84005117c0a2f3eabfb58e35636a89b4a653075273dc967e031d4db9fb6ef81345825e5a3b399928343d061b5a1981b577cc13af025057d81aa79eb1eadbaf58c3c7323caae34c8fdcf8d6c70260aac18c84d31cf99b9700b755ff945e42bf6af31fd61fa0d727a30552c19d0dbcace52172bc01ccb3074bc79d622656ecdc61dd5958fe7dc366bd06ce46e374a959d9370401b524608b7c8b9fe726c03e07102b72cfd1c8e9e4e34cd405dee8a873364131c80d908b9b5df3c31512786cbd922c31d6d56982b1670415e4a20bbbf755452b62e507f822aa41b5fcebc8272563d3df22dcfd54629d8e2c13b9237fde6e785d84d9d2a2bdcf137459aa6f978706892a3eb59fb4769bf7048c6880382d8134a9edf4367cd8024525f64c69302c7c792609521983cc38dba57b4abe3d01e111f80fa641ab2b9ce20a1cd667b7ed00a4ebc1cc2b2c91866d77e38a4631cd8fe3611e28e015865a71a150acb2c2acc17bc2742f146f8b9714e1629a1e7943997211998f00e7a17fd8593ab901bd01707f68e64326abd67a63d263917b4c62f507c470628afbb66e0f1d4a735b6ef5bea2495c12f6590c8e660a351f80223694405036e4508f88e9301f69d524c9acf6a998513d57b470201fe03dfe2f2279525cab36e5522e44d2fc1cf5eeb8147be1924d6b03ab41a2c97e83bc4415cea546253c62579cfaf0961b80b1be285f92ffbc99027e77054eccb291b8a86b4b28be20436b5fe4be7dcee85a2885784ec68379edeebdf11403220454361228241b2ae957f5fa120c1a2e2f51eff88249a27ac72ed712b7fda64aab4080351b1c19bbbdd1e471bbe6b44767625bce519292bd1f4384b6bb31afeed7fa5161f2fdc47457854aaf1c06b2db1343abb0639dea8cabf6a4c97fc6067b8615fc5eeb765102d03242f412ff0f29d79154888050e7622fc234ecefaf1da232d75b1060f94479ecd7d26d2068aa895a027429d904a82f68252619d4d42ff4f5b4c14b5ef414bc13b5aff3870f8caf723af789d44e1a237250efa0c81fbde9b7459ac58004048bc79abfe86c05b95a2aad4f38a56f7babea0fa02592959b15ce1563a9377f5e61e8ee27a889ba13f63d0d115d34ea13f414d5c6bab253bfdff039eae43292ea240cdb08655b30e78fdc59e868de700ba9461a96076074868880afde3762c91d5ebac59c7f9f73df366cbfc7e2ed43b0521e71fd76e488eac76ad3ca2d5b67cd09adc42f298ab91faed878b4536ab9b5b2265e5fb5a1ba04f4aec3108c09086e50e97fe53eb9a4e73830dd930c26e734816ddbb004570e3293d958131421adaf0672182a90af76130849dcbee5ed4819db49883605fae220057953543c3258530fa85ec6ecf3bbf4352c94055837288ba2d3a650aa560fde30385cfc1bc8501e6990f2d0694ff4605b81acd11542f4fa911d0d629d41a8e0883ef46bcde64409ceaf45be9ab9b1767b8f928bb09f16684dbb57e7f30c76f6257209e577b00fd20f4ef08ca76009e9793c8321c4f45ea407bf0949445b8e73442ca1ef9ab4ad70a81a5c5b2d9e29475d9a7a9b8653f98910ffe8e62d2937f948eb34065f4d7bf7c0a2286d22bc219926d86fb9ee7fa88af7286f803bc90136fd1bc99776f550d501be79400816999e1e3f159ebe022f63f4ad8f8066b073c297e4de78f0d1440488dde5d387dfb3b053042f40d5e2f3c603f733d71296c98d793b9bb479ede71f4816d497e3ba75a4e52560ea5d174dd4b2d9e0229d0caecceace270f22a4335a62dcd3fbeb46137952e8af2fa9b801e2024c31332ec61fcba79e88a2919f8a10650fec1066ddc951e15736d3488a500960769361b3a990c50559f75bbf4dfc4c21379dd6df7e6409919c128aab0185230841627ba188175d3a668d4c2d131b5b2688f06f9621bcab650dcc350066b2b12a9c39f9d8a724ccb320959a0ec978c43c303d91924328c031d67cb3800b03bcee78adb2d01fe180f82593830679c7194f77877cb93033a7821cbf098eae16a01afff29fc2cd7e6326216bed6ed8d76d0278f3f5e78d87d091fa320c9e2f5e10d0c3ffa053993351231cb5a05f795d63e998645c219eca142a7b8bcafec5e7372774d5f8192680305fb4e847f07522ba1bed2a5407409008fad1165b8654b562b41d9e9958a49aa9ac393a6ff74fda4ae1a0b2f07ff662283e1814532a7b87654eea9cefb08b0a01a1ffa09c3f2e190eb235583fa3af816b1a43d1ae1323783ee68588e23323faab1a1e374a9620d2886f3c1d2dace6aee833e8c389f102db71a7c01d86f85f2fd478b0b3fa5996580c632940889c2daab7d051bcece605288e0616c9f0e4fad067e7da55874936bfa5c2bf22602980074ee13a4d3827b3809060fb61a838dbd11b92d1864a6a4bac180d42729c83319a38f716a404241c88802818679bccad1ef2ba024e69e0b5e7dfea0227aa2e068446347fa017c000a3047a71b0dac3ae263b9a7aa6f68b240c84e2ba74d8551813e9820c023fcb4fd8469fd3c91467803724b451d621b919e4a9c1673b5b15e243a63bc9954f64bc3157c9eb8e36cf9b6add7bef3b77332bdab69295138ce68c6ec0a277fb0405d5053c608ba8f81d84f3f43c382b4d71dd4966cbeb0bde3fdc5b9f26486c0e287cc15e6a546c9c612fb4a11c511f34a42dd162b14483a5299bedc465969e4cfdeb2fdae3604e181878d8cf19dfcc6bfb5d32a7b83572d630b45a1e3732fc89d8fd4176ad3d0d34e3db4f330afce9a536289af8151aa2ad6e4cfa4efc62d65f3edcbb031856604d753cae2dadb5ff0d8c313510ae4ea2532cd31b0502a03c2f5d430c91dcef8379cd0af46223e50773b28de32cf08eda958577204a517c829b10ca6643f0a07e225aa5ee4774bdbe8a6ccff95eadfb4c3e27e47a39b2ab7e277616b2e4a92ac0ff165bb6d211d3950d94160595ad65b41c15fbc7bda859511eff2edcbe737ee15900510ac3be64d2ef92856f7c5033776ab9109501421ba255d3854309cb95640daae4da8c8f9762ec5f7f0809aa7faabda4e9d4b86cdfe6ddc4e6c94c9d6b1b85d41f6ca208b71c08704a70c2629ad90c2eb3f207859bec92947ff592f8778d711c346441969c294ba477022d6a4ac9d981183bd2e4a8f01166185b410ff70ae1443464c415613bbe70748197798e21dde6d13e549de481630dc0cb50fec79c1a6eb28aa85ac21a8a842ccb3fa204226b28f89d2d8e9b788a91ef9d842062441e78affb52068214278ad4cd4a9c01d5b1f687dd958c7453bc46d3cc49dec2c0b75e403d75a3d0c1bd3413e687ce1d3e4823ea24d62cc94a419fec122c2d2821e103dcfbc94e42add44e1d905003c0281d7b459ad2384657d2d59e34be1fb6568d203d079778a5cd38ece6fe5fea3f8c0072943b37b638f4c203a2a702e67ff80085d604da00769ca6d029e3cca1dfb09370be3f596efae809c83603cabad359b05f9a1217703de523a75f05fa0490e99220037d90150c1aa78037a9f8da89a74ecba363b8b9c77977066eca2d62b30cb63a83dede5af33d0bd21af7b617af21f6db5e04ac545a3d40168ca63fd3ab5c26e9f02f2ed48898b79348341be501f9c317202dc3d2385319e240dabdf9726a99fa501a1a42312d496b89b0c287562eb7f16eb128b11449f6e83a41e3c07f67437aae25d0871502a0be477abbd34651d7ce76ab7089f7591706c5e215230751bf01bcc36294bf899131d55ed94aab32da5ae1fa41c2b3ca2df5babbe227c62ab48ec8aace8255f1a6616d16cf0ca988f8cd2a53b713cdd73177ba693f15253395d64979e8fa91901fb2cd72b7a534e6fabc1e08cb048f3c789a46e2f6578442a5067cdbc85f7d692f7b8e293a6f88773ef44fd267befc30956c686c60382093687e12b2b15e5b408bc5a389ca7be42a775f46003ddc737d1a266410bd8d0c8e9792dff60c2b5e36e6ee2b315a8405a72ec74be332bce234420cb3a3bae8389affba5be1f93ef9b45668acea1cb6bd12a02e99685625a27e60435ac6cfe6a19afd0f473fb5d9b015face7c11e29f6ddfaaecda8e7db92b7c32dd988af0c1f6c2791b094b3ffb4a289f1e84d2acf685af24ad2a0cb123465c180886ecfa1cf00fe330fbf952c28b176dfdd63e0f21c89a4366bd2fbf10208ff0b2b11f4f2afb4092c54b48a083ba1d0999c9e0baf176de4ca06f07910ba08fce73a13eea8ba653f331636993eeef516e1bd2d69f45c201bbc8f46aa70421e1099d7218b09f7aa7fba1c04af17d1aba5a4b6b19a633c3b547edd0b7151b60a886180edda3757b59db336b9c1ef3c50745b53eb454221dbb49612cc2e5c9df8098ad9e559e966f081a0581b5cd30735e6976dacedaea2d42ba7c8b4d4dced584b6b14b7e457aa453edab51472adf693c0310675b4d52106e0283cfa090d3096d63b52d124133a7e1ff80b442ac64745f6a96270d31b15deef859d126e5c5a22091f0104737edcb6b25dc4354c19400d44b7494108dafcde3d743af8875fae8227ce9161fd5d0e3987054be149c69fe648879bb137c157ca4ca180cdd0b641c0e67aae99fb461b530456e2dc64ef81ef95e29d148a7d7f4dceead5ce1c320e7855a152a1452541a80e5c2ac758d1d6a1d6532be6aca5b642fc15e210c79a9aa5f5c238bb514a3d40b0843da12cc728c89b74b5cee1fa41473d430ff09cbeec2d378014ad5f326741f1a3c1698531b0cf04c8757b45a2022dd9d03d72e494aa702e5cc4184c91b49b4e9cec8cab394531a5d45033a5726442882326c8c75da0aaaa061b4dc1e7ecb5d32a011f19dd260d8b34607163343f53e67705893931c4837465efdf48cc195d60a703a45b88798406d1f4c9d8226751f5e7167a262b1b819cc1bbb3e129dab0fae5c21a9a91c39c71aa52b695955fffaf53fad3e5d9f981b8274b7c951be57c6afc2efaf052a9e52051057f9977840fb1c9963f23a207106cff1b6b0bd01fe910d873703d517d0bf7a0df7d321b1fd046750d1bb39861fe1467186f4596119559fa57c651ffc718c6e063b3434857c0f2ed482cc35a5c1108480efe74bceab60e2550d90906cb53c38ba6178dd6383931d61f4caf440c66ba118f6bb8ce6e1e1982bf2ac85e0aefdfd4756d2de89e87946675d4a5e539072a93a0eddd0b4086a75651e4472e994a31e5cb4179e714ed31547e9007c3dcfddc775bb945978c6b16000d261242f46533d2c50a091c3436a84bcffa683037ba10750ad2d9c65b551fb8d093391e27f84d249af23cea278efb55802342f880365d44484d7d9a1fc8261bd2400de81843d8bda241856cd2952b644acc4c064e64132a7a4ade7571029d3dd39513187451d7c6857d784a0f407bff64abe9041ac81b28756c15ad9b4710ccf57760bafbba11910e52544aa763b530376fdac83733393a9c3c9dde4c4d3839166f36a5bcdea627eaa06b4d4029d1cf5f102598ab6165d41336a1e796dc905d18a2175b89768724ed962f8810a38cd8a9ffd4bac092e2053f69f42357438ef04f85a94196c195e461996fde2a9d00b8347adb3cc97efb27feda1ab28d7178119cdf47c78f863946368f9f0b0f221b378b100bd833a6863eec267763eccb79f65b804cc2395e693c0dff4c41908250299d676731cc554b1bffdefd07a93e30aad0e6827339c210a51202348bd0388afea15fbf62c82a7cd9594617d501e5c0b398861b2d8811da950d7b8521570e4afd4287d2071cbdcfb5caa98e1d892c0a12220535a5f61142f9c1ccb71c525e378bd9ad60d68e90c302d69b42baae3a42271866dc990cb3a0a1488f1d76c3cbffd40e67a20b406704bb34ec9413f124d80037906af94ec16992aff9ea68bc8f1a19a878bfa03f511f9b39423d1d898f858277bb0e1c5517da404ce02f28b9088b9f7e6ca525012bee2baedcb3d802e10fc240361856092a1c49ba0e86ec9d06549ed17b26bb81e8b55939c3408aa8bad846542fb2bd1420093541e99f317a405ba5c109da80c3ae22e2e603552b88223a68a6be7e2f290389a78d66b19d57539ac960c5ee4f27d88afa5a5664bde325ebacbdcb32a20e22cbf30f45148f9b08567e8456f9b3b5668bceba53e45fc9d8e17989de90a976e70a7dde904d6951a7107478dcf2870315bc906344b0962e33f971f119b895aecec8673bc3d0e06d9fc0cd2aee106bbe61a62050a87f1bc4b596eb18b2a50dc2b6b0fd038401bc236ab864e250cbf137d4960ac3e8a6064908939f632b916aabac854a3a0fdf44bb390b96ead9c1d9d204d71fbb8279e523297d07c26ee5f5bfe02f4e229b2c5f08fe8035b6bd12b10a9d2b83e3841007d8b8528aa8e95c6844464ca9f482a6ee00438b509bd3b762a3444279dcde278f4c4e30edec3acb28f8e8a70c6524e1f8f8114b3066958b2d3ede32599f01599340d8c38dc7880f0f81795fc46bb241ff11edbc25bd13ff3cf0da4dcd3e9ec0f1b2e9e1c3ddbfd54e48b7dc11c910377230e6d1438d79f1852c840c7d6a546b3b968eed1b24ffea6f235409f34bbe0dd30e9d62fb9e112820898d288d2addf7c66b47b2659b753316744a215384abed31f08b54b4560d6fb4716be4c331f69f81e55521eb3541df8fa1e5344c90f88e1cbc14106d0a0115930aeb97743821a2be361898a02d8a2904cd52349e5ca2762f537582beb4138231e380bd1e0b2886b7e14581fa5bbab094195e1ddfa87d089cbf558da5494e9e6f07f17af2073593a5cd5f7e827842301a90a5b45dd81dcd1302d926b57347eed2169a3105b995e8f47c9b96181e1b1ebc5b612eea9106155c1b64b09517370d2d9c1ff854869f243f59d678194c0e1500e7087690de76117e947acc999fb4bc91d080a1989f20a78a2383e86e8ad3a7ffee8bfc7ad1118d76796aecc96651b90b045c2c312767a3ba681a63e7a8bedc9f7dd6b2fdfe037ad0b56cb8fcad7cad08eab61a5380b6dcbb56905264707949a6ac10db63fce3d7fba9ce85135c19a017138bf7c90744979c36ebbba72cd7a00237005c919e9a2439b7cf38bdcbf06db4e32793d950b30f45d91fbc796f7c5cb13258d60033cf1b93b643a800c551c57e65e4eb8a059a8ec17f96e15dc637510b0ea4af6cb372920c5776639508f319896d644d316f15a1991a07ae33051031e1438eef593e7b32c4e558d269f0355616407b72b30f9e4fa81250a10cf23d57c20880736345f72a357c191cae15ddc88fa05c70254eae07690830b1d9029393fb2d4e91450260cdb9527000d15879f023758a4dcc294db0a4928878f7c062f7d934de64a9ef568c86f5940cf977e61b53b2a38448a690fd41484626b4e3a34d4284f519cfb3200a82908c54b8b36bb13d80cf4d22b2488fe75484828162ac59a51b198fadc3d5894fe10883f06c8304a32e9b4e39511a65a1caa597ddba78c6110f3ca20c697e8d184122e0d983fbf8bfde2c701f6074cf9b18bdc2d8b6481aeea117e19b8a1ca3423e7aceaceb6d007c3bd7d54968ab990cf25c38b74a194c1b833d014b5165d4b524b30051b9698f51dfe014b27913002b0e207c1cefad5b2638456f0a23c0fc5f25147869b4ec6e3012d9b28d995bbca00e555ea82d198cdccf1c30dc2adb413651366ec5da661ed30b48e0aaa7835379523c0645bd96458eedf255b3b703ab85851e8fccad70add31e431b80087047b85de7e982d8bfb00b249d0fffade98b9315e0883d913b234595ae1696426a869406a0589a34ffe525115da3eff13fe1c586e3924a5dbdbf18b8075bc5b775753a8ef79dcf815524547e52e39529b212079dfbb5eb8592bb7174771cfc3ddca07fca9076ef149a7872511bee599f3028eb08bfbd7668b762030ef45fc21ea73a1bbe55cf8578e0085176c987fd99befc4ec42674fdb3b5ec12f12fd553663740d95a7b0099b228e129b56fee2708048b4c6ada085ad3eff5567502877966bc75620b2fb5c27cf60b56add8d9127b1b0b05f6220c3d749f3671a0fc431eb534423189b3530cc43fd1d10843ac925d8e1128d93328b35f60b5e5cca21fe15ce21f98ec4423e4b9081454fb20d897964ec2fdc5df7787412d75d156f130e56af9580a5e6d5a18dba0abde020bcb5a64cf767022ed865162379976e68bff59dd97711383e38e1c197ffacbbd50e5af407bb921628815d654a377388b58e8990b418a52d94e17036a55f6759545ec8677e10c5bcb7d9af8cb1d4481ec5599205ba8b0ed720be0d965bfedbe6be864ad3ce306248bc1e872c938a158b42c2a65ee666f2c1c405abe2709005156dd707d7a9f5d3a1ac319602eb989b9330a8dc7ef8b5b3ce1bd068ae7e850bef3dbd886544e4ef88258a54d2f1926e1dbdfd60fb10df356092c13f47efeae4139f72b4fc48d306f4708d61c695034c3e15d2683c9fac20b12268d699fdc313099819e35c00ce78933923e674018ce26cc4cc50c52cf0909b38069c08a02aa55e30068d1c0af4b63d069e1320f658a9e7d06fa6fc4e9cb3d9f3a6483c726e0de7c9949ca991e0501c528519ac2be422eaeb7767272d5c4f29c9caeeacfa163a5c774bef65d798ec2af9600d33a31271f7d3a9b4e435ce995184da4b4b0c7e9c1d636fbb610f6e3720637cf8d83430a6d9c11becdb9e01bc681ad5219cfa8eacf46dc1d8cec9ba6ad58f7642610117ec5bf62d5ad6ed1240af0380dc09a93ab7078cae8921f86f5fc01bc9ddc6ae48340c3705f556dc414fab792e0dc5d9df7ac614bf01843e8b20bf68f0b88bc5705ab0d8c5bd9925b71730fa9e3aab241232825ed8c1c52aa5ef6ccaa46cd324fb22e788f8fbc732e0d6cc389a82468204cc867ce9e816a8494d26ac329ad77475519c2ea1ba95acea301c107eb4eb0d7f78217e3ae409e27ccfc88551982de028a601989a9b470fe7181fe4223ba6037260f2fc2ed2f34659a2f5a7b39816e2da3b942c08a8f07c14f85c8a251f09daef01290def624ab9e44a3145d824d4df9a451ea6188715d18bc26618eff3f0f7b0e402cb4635023b01a9724ce5592823c250fcd0aa627721105a1b72d1460b9b25105419abe527ab8b18f5cc9642ead1d3d8b47084d5942b0aba48a030b0531ca537b056caa7e8693d5bdd88a4a2338c02522c16f1bf3be07688df98f5a58562c180d8bd66c2cb4b8b06ce96c8fb92c4122390cb158a5928cfe79ff07e49b91b4bb3a24d09485eb4c7c15c06d94d104ba04179ebd13a3b9385e1710eaa4139e95a0d96a978f9acebbb7953ca4290e07ec08d7b3f08956393d6a29776bd7d07ba4e2d0076e517feba13af3ac9440961ccfb515d64cab4a527cba88b668682d468a5ec612e4c8d76f91a8b6f159244d9dc61a9e3260610c107df1b214f178b77f102740ce050f391ecd24252f371bd5167d6ee1f6b8053130c06d6660d1520f52582c8f2f498a35d1259c5fde573b5f2f64107237940b3edd45950683e022fec38bf210c309d2ff2de25bf1c9e4ab9243f383d82c175b1f49dcd9dd381ca36fb1b14ae078b61347c0502f716702fc4f42e2f8a077c38bd6481978fa8e6bcfd41182e08a8936a1766066e21ccf1ff3893156461be608ce85931bd80fdd1b8200895d5c90400f792f5131d3e8116ad4fc3efc77c8e23457190c3dcc6b53293fffb5b7a794f08de6c0608515667bcc08d481704577aae225034912e9f3b10c047f309a43d7bf8d2f218ef92aece286dbcec82d23e3767a959635038d74188ca9416fbe41c46457c88f87633cdf1375c505101e6a78e075cbd40e170e4d958e0a44bd8e579baef1a9da61dfead9059012d6b9da7ceca8baa4e00adeb1448f3e602e76f8e920893443227e2f2edeed5971163ef0a68a7f6e9fa7fb61e1229f32c341ba6213704ae4f67e7e719e4b9042c50d8d9046735f4db92058328206d2d3737f1bd9602aae85a4afc561a404a95789371bd8571579c0948b09db8283de6d368750b1333979dcf6cfbfe1545c338846b6a51c762e7f2d9a5d7a7bb2416045a9449983f1436f69fa4cc85fb19229cd273ebb645682404780b0090630ca1bc0fe976d5eb09f729394401c2282f5650d058e5cbb0fc6fad1ec8cfc5eb0bd26b25e16df12ea4be23ff7007500b63eb1e0272bba354a5a794286e0e97650c7d9577b9233196e61fbdfb27496f89f0e69745fc2addd12003798e0e0b9377ca19f0ed45a371001bb25201a0ba46993e1ef792b1307cac970c8ea0b6fa51fa611855961bd5d11ca1d8743df4b19fd82ec2e886900b4ad639c2e43b8fecdd0916a25781ed387affd998b95e8fd228129137163786e20346f942462cd9174c4ab6500104c73da75199892eecfd1ced5fc8aa195f8f23258d6cfc465042b3ce75ec423a49ad0562fcc86f2f5bc7a96371ab1ac3a77636c72f75acbdc6bb487e9b9c20ed5353b3d41e1d01e6de48c36046eb0efc907e2fedd4416fc7d5b7608da81c80c332c5e95c070a7c73d118b67e4fb8d30b0d555a96b07babb9f5b838d2b5654a834634afea92d278dd029abe8e13b58c16bb7327e07484e4bd969862dfe2a3666287a9e65998b7bf775b47dc2cd7f105dd0eadedc7c4dc8d20e6d358755b5e7aa38c812f56899dee7d8fc18538b44d83b2aeb968d332c40f7928a1233b5eb2152625a6e02f51ae39b9887d2128a6e668ec99888876e51206d7be608762b0d97ddacb59366d085eedccc38eec938ad44b84286dd479e6675c56c34a11133ef11116f23666800ca9bec9514bcf65851054f7c0c32d6e5ce0ab95873cf35f9dba1d1d26de085cc4ae31b6df5fb99ca45e2e4fb3c5c983b6b524572d3fc406d47d2e67b47a7c55bf41c11fddee634cb88df09cf317e26621aa2699108b6c26a55b7584b98bd19f143db1d7900886e303d56cad45a4db04ce459c58d261c88880104b0de021b60c60b6f18dfa554b0b146d866507815e43434614f935402a8129d0f3534dbf5026942f7711417e28ae5b262d66f101e15425f4f5c53848b9dc32e8a7253426b975ef4ca8fcbe37338981babb66d628180240d249e68d512912097c68bc761d820bd6e65a43c6125d08feef85f4186da636b8c5c410b78c3e09d25a18856c8d2e0d65a0c2c4c03ea63de8456200a693a1d6f4f3550a4a3ab45f9ece5db33d8fb680cf6db769deab9587e43b89582bdba62305cb792d97b8e80800355315cf575d7ebb64518ddcf954e52d52ef46ac7ac9af6dd438f80b3b4192242f6de5b4a99a40cfa075008c108404a68332047ae4be2e7856b6d87dfa63f0ec498c822e1f7f97ce799fa3cf8f1155924fc1df0bd1211f9044979c70ce63469e04b6b1210eb9c4b7ce69c4decf26ff9ff1fdcfd547ba45f57c1d69ff36370808d4bd5b1bdce7383d697f2898dffda265fbac09e01d8da7f8f1eb975df61bfce632d92bec612092a67ce315263afc08d4471eef69aeac818a1daab35ead28ca028b3dc9b2275525f5a844c912d7b05de59bb433e6591ecdf0db4ef9be5395045d63acbedacb56b5c01284baffddd1dbed9e36ab41deb0ca0487f4953e97d4cc21edd5d6a5b3b34eebda8ae3ccd5d4fcae3e9f95cb0f03bcfe5a9e39dbd10427ffa9475729e67d927200bb463978f1a013b7c60ed93b04e3e6f9f8412be34b44e16de5e78fbf3f62f9775027afbb74be86d76a96b74a68bbcc725f6e9ba5d0ffe187ebd4d2cd3705bb21adbfecde14c97787dd8bf33ecd3e5e2ba34b67d139cc984b92bd4807dba56566d46b081609f4a5c0f71964a1353133eb26a143c89703db8fb9494de5c0a6e5ba7da6eef9b5010d28dc8a7aee97adf30a8ab03a9f7ed7661a6fb668fdcef5bd7f60078fd5cf85d3822d79410a2b6d0e331e15acd7df6497b4694e0e9a9ab0bd730cf3c9b44686adaa9f7cd22f98dbb700ef4393ff2bb4675611b8be4bffa7d0b32f4f89714950efd2e9c1ff99b9c14fff5b68d37f4487d98b2696312e180b4e1c2e50b08d90e1368b08061cd7952e462a4c7ca2bd57134b45c1e82b3748f8ccf7cd172634d8b355235a0a48cd9f2a1044a728913b12f263a2a700a702c82a6f37d91259b37c497ece931c7a70dd1a7cd0ec6af3967b3cc399fe745f326cd871a568c1b55618abc819a8a92726683cb13241c15d6f38ccb71ed4603231492295eb6a00d1993854bd23e1679e166fbff038a6fe25e7ce108e3fdf08a44f190659da435f210e29606cc8e9514b24829a610922205102f2e2699a4860b4b0b2bcf3819343c7a2ec993c6c60aaf236d7344c25cada1c1a2ce1b0a395d9acc974eb3c1666595538c8c16d3b671e37ffade8761db23112f44902c76f68388b05b8454a892808fa446283f7fced9be098bb34dee8cd9d458990d40d521ab36a07c531dc11111ce377f04e897df007304e79b7604aa0ee1b7c1b2fc11a73a84d5a460d3904da69f0445c34ef1c794141c583918b0e2c71d326d7c78f570a00812fc4df01fc1ff02fe3abbe733b4e747ea8f3e1e7a10be3b512c7faba3d8e59db3cbf2efdb2edbf67a93bb544bf38136ef85d41568af1fa4fad4f5f4f9d2d49e679aaebedde3f36d1f1f1f9fd5b72ffcfc9aee0b172e00010161f8d5b781327c1666611652d7f34c539f0a026bfc197eeb672faed6b85a6372b549a1cf6fbdc26291275b638edafea66fe026f77aa2b8772a899bdc3aad739d6e4898a56ea4fa57cf4210b801caec59ef320fef4a9eee5d0bd43d10f4d6aff597e5bb99bde84b6eaa3953ed326569de20435242cce4451f52bd38f462d89c276c66aec4702483e8092cbad0e824f15223e86fcf6490f78456bab876def668458b0147f25b9ece4748e8278880d004232698608209845c4808cd0ec40074c1f720fc16910cbdef8d3448f0f7af962f7d08fb3e2eb449d7bcefffd6ffdfef01f01dfcab0fc2ffd5df590ba4d64cae6dffdb1f007e83ba9e7bc3ef3c06b27846e68d07795c239eb5759d95a142adda9a7bef0bfddd3ceb3e5608b5426a07be85ee7d14e08a473433163ccf110d7985507bbe596b6dcae3e9f1763a146a73f80a82d0a0a00acea320f3fa380f879eded1dfe1a3dffb1d879fdd1c903a704042e280d48103070e1c3870f8590b44dff68e5410f216528378b8c29edfa5bc2b74e07d134cf852875f9b8456a0b57604873f52d75348eda0aebe8da21cd4f5ace08f844448ba6f79475f41051fa4aebe0dc277a845ff56a0aebe9d1ea96bba5321753d777a3fbdc5bf197e8e5ee879d01fe250c15f355f4a1d38c441edc03707d428140a515c2359dcb8baaea84dddb1a32fa5fb4a16e3be98c983f73954808ef0a5db665fad26cdbe288abe0777a312ec2335888707e1423f2507dea17be0dd212abc52be84fe915210fa41e81ffd1a1414f408403f03412f941474040ca2206882a01141ea7af4f677475608b542473fbb712f8415ca2024a4167bdea26f1f68d728f809ee8607817f6f7b1e00407d0dbfe141f86ec34fc317fdaed12f3b50c3ff7e1950ffdf7b20a8ff7f7b08f8b7ea0ab555f4effefffdecbe3df577ce537f42aaaa4115fadf61de3f083792e0fe86df8aaa25f07ef56aa957c8865fdeeacfee9fdd451a7e7917a943ef0b22fa5d1f434241bf0a0909fd7a8b4241bf110e1452d7df414240424242ea1af4bedf05fdae90df2d16d1a0103ef4d15e01a036fa4c172ddee25def70f7d7c2dfe209a4fedcd3826a1de8f317bf053f291e2415afdd7387bcbdde61cafb0d57f444cf94778bf76fd7150287e44971c62e280ebc503b0a94e88cbaae343e5d972ea2ba2ecc84aabbd497eec6495952bbad52a8ada7b6f9c6b80ddc564d4a97bdcb3c0b56e9aa8b5bc3469116e9be4e48d100ab6f877b1c94696188db0bc18158bafa6ecf034cb448f785db7cec01fda7ea4191967b50948f24f4d07f41d0d775615dd7b985ecebba1c78df2bfdfbabae6bdfd75a982948ab0a9027288c8c4d09ded75d988902c413be6899b2150349f0be2e6a213d46b3b8735c96f93dff32b02bcbcf0050953484b3b44055d2fbfa774d75bd3d03e83fdf96b683852bcbb22cc3c01139f02621e9c083efe9206028fd19e87de95f26ed9c73068640b0bfb38a2185f4960fc27b97a7ae4d4940f4d43f9ecf77cb1b8c73ff83fb494df9843b5d600f39e77cbe2e9fa735739975ce59c629e745d4e59c73be0f04125924ff0e6fe44cee44d6c9cbed70f595c30e15952ee115189752cf304b221543ed8ab8541604cefd02a8b84026ad68ed98cb70650d303034c9cd0e1dad373bb0eed81f7326e5100c418285c894284c989c56605b6911e2188e3164577c80f11549f31bf98ca67e9a84a0648a1531868d619a920658d68fee82230c941b5c6b4eb2c0e1a2829a15ce8c39520566a3a473ac8186a402e72c8e0ee31c0bc816766638d131943344c336e30d1311266cac9c49ad10e639d272bb85eb419e0922c7828a161a2754b1e5d0f85642e9786104489537534bb458b04a579dd6c681833d48b66c1ca3cb80acb4188af18b56523ad2925ed87e2f58a7aba8206ac86094b541a1820ce3d90f54d559933750b89445bd8d7d01dc5049f912d5560687f19d184626161099f3866ac8962f2419117398020c2770626f725b2c60fcf05941ce2b899d2a29607dc56d9fdb0e67915cbf083aa8551327126006b59ae223ada09b434cbc1a02586866cebc99f34ab32595b146cad6a66d47d6989c565b30e79cb34eff8608331e91b3a548f10d2e5f1853407979c1b1250173a638259873ce39c4080ecce0ce0870be8d9f643552ea980f2c3728d61012a74c9c2d54b67421c58c0665642cc2bec2c84df9945ca4364e595c01e478a3ca1014650b2f3b0c6c4549f1c6880c3a39c2e40c5590d1c21246c6d4f1c204a4cea4a474419372270c123165d28827541953b29002669ec61365c22f618ac4e902b3facef4937c6d1183c71b22374e182b574881a2edf18a8c1833516669b29290bd173ac8ceb478ed8071aad83963f0821627ca97953039c19c73ce5a2ce32c29392e2c65b4acac15e0ec7852272ab8fa4a296793627556006ceaeb881a1634ac385d4d440ad507f9d4d934262965aa525fdd22148478895971720c3bc944ed9c7308881ce370c11882f9ea60f437b3b836ed730831b3664a853227a8c16c658a371590226405982e3749d81045a52b6fbfbba56dda783c7f1a4ca9e40b264a17dd522ec7f01327689964d3dac0d952bcaaa2d675264b14517efc0465f05003244e05acb160ce39e70e160945bafef8f6c718bbf06d3037c9554f7e2099cfd96c5a94a80b62d7e7388d9b9d9f001ff812cea73b14be3e76af06c62f5b389eb06989719ef8c57ca149731a2a589aac708c01e5c7cf9259bafedc34e7c981ad06167df3bd3f6bde4dedfbe69a48abbc9ffbeb4fd254bae6bca6f8ed38d0f5fdbb81ee43ed134b99ee3e60bcdf418b4e3fc67fefbf8d526b63adf5d66f55124359b55a5b67dc5875a3d28bb2d65a4973e661ad15e3e160bb8cc7302fcd8efb76f665adcd6fadb5d65a6badb5f8ed3b12eb2954cb5a1369edb51a6e973f5b5cfedde5afc98ff0557213c2cfc6ef4718e824e5b9f5b21db6faef25da5aa8927be56d1f1ab24ef6c879d9813c3fb1b32fd93fa313662deb24bcd822eb581476e9ce092962273a763626cf6f1fabbb4ceac8730a790aedcf44de3aa7140a216fc429ada25a6fe52dda2e649b6a1bbe11cd38d38e16ba96554de499d4feaadde6ac13ce484bd34a4b4b4b4b1bee58efdc852bd27053a0bfef52be24bc76eebdf7de7befbdf7de8be1afd0dfa1bf70d77783ae4fdd0cf7bd8565ba6b336eecdb75bb6ed755bb6af74db8f7357045b820d8a76b753d34318d48ed930897ca8ffccf332d52d794485d7d6bbab7b5d65a6baded49793c3d9f0b3f16848630bcd9f3a0dc434270f7c96104c1395506d5a730784073185e63fb7bd61a5000c0dcca870e74a29a90ed5d6a16dcca874e74185c3e6ee543273a0c21733cb7f2a1131d46dccead7ce8448721a4c96558247feb5416c9834820c17b404bed4bf0f3a17f8f2a752aab55e4e99ac538d5586b3fb2bfe23b474f39d0be7b0abb0be272d63f346efb69ceaa9f70f736605ccb3a69f8f3aa53d6a97cffdce5eebee6eeeeeeeeeeeeeeae93bda1fbfb77d91142cc2a8fd122f95f00577ad1f67f236e777777771f1ab76b959774a0ff0634e9f5ef1996479d316720199135af3950926b82ab6f6725585fc813c298a489135c9376ce397f3e3dfffb5229dcf9cece5f613be74f31d307ce68115e3161b2c5cd003a946051c58093634b30e79cb3968ecb8255ba46045db34e1ab28a8ddfb1ae05142d29921b0f2d6a3ec834c95a018a140cc02c4329722eacde7035c1d293c93c31f2dc4d55e39cf5e75f0927cc07e1a6699aa6a9ad887e6aa4ebcf8f8e91db34f9b241152f852246b92a4970d45081c95c062a6c564c117e0df101c682b7302ba927708831ec2c89db4121890b82e62b07171b50d25762e7245242142e5e459c34f131a44e12373e16cd0d181629fc4042a69836c4180915171d7147ac1c31c674363edb98b7f18fcc56968db82e2c2e8218638cdd5dcac82c5d5dcaa57cca996e5ad0dfdeb40dc2e1fcef9c2f25b19a33f18c97ed8e31a522ae5b900b8aff1d3a930dba1f8d4d76a4b48b01e8107b826507961460a669ea39db04dbe61320471c1972c2315da438d0ad593ecea6697eb0cd026cb33471ceefc062ba85875b6cf5f8d9f84fde65ba46a1504815b43899218896b129727265c839147894503bbe20e162c3cc9db81c2651cc47d69a2c3bd804d7749b3f82699a72b689246b9b3cdbaca282551c92264474c8099aa6699a9f4dd334cd131de85d002521a9deb82c8903765aa83946c1b2c143cb99e9413129b3238a9c1634295246461484a5ab9111f0931e3d1dcc91a16ee31fd4fc715baaeb6983d889e47d3194176f48a215194582237ca0e318316efc04f025a08d8d71d8b498231775054c10630f67a90739e8deb563b32e42bd972cbd578403adba92d0d65a6bad75fdb784eb2a093e5be9ca5383f88978a97067f4f970917db2d6437eff214a9a92ba5b4b74550d2c124682b100887829132fe23cf6fd9584f3d8ee5452f75bc4480c6216e4187fc199f4bec5d62b83135277777777771f3bb7cd42c449db8f2dbc6d16246774db1e8d30f92a66235988689dbe64ed953e4d3478fe2c3efa597c14b4bd10169f28a1dcafdcce62a6e0f96bd2b6492276f957ffdb775f897fa7f9f6546d26c1aa3708854f64bf5ebe4c3da6b9ba341526fa7defa3b275a2208b3b6292c1b1d2494d1a50a9a2c282cb8d0e30a3c3ca8f386eeec4c151a6c0f9bd19c9c0c238891293ca6817a7b95b53c45878a08e822b34b8ac15665c65955f53903e16582553309ec9e195257fdd0c8c74a46a1cd95c542d29a14c2172c15641c1666c455714b0325d599e928307fb1931695832d820d940115611b2bd99b90263bd9e4ca1f1430a1c154db654a85d5160869a28308a7356b2dc84a52726582773bb2eca8d9a12e55da7bb1bff62e72ccb49e4fb51e291ae3cf7eb1050c1432a2e0c132c686ce9e44c2c851bbee1906306a78d7f6045a7ced61a5b10336557ae937e6087962a517260e25411ed0e1aa8a9cfc8635a6c758624e5dca40df1c0480e35566e70518d9d4d983c41a990081946f1aa82834630276b0ce3921c6181268203fd4564a5abcf976c4e3a9b1e3b5f63c125858218323fcc96e438430b4aec8c4959a203cbf04a052bd2353d9bdcca93ec86142fc06ea040a1460d990a3518441764567429b9949cbde8313403a284c5dc1a152c182672ce9044905ca1f142859fd959a37503adadbff7e3d9bdcddb1af62edb7c3de36cf2f0fcac037b28dbd65a0bc832a51508f7b9779ac784df621545666ba9c89923e279fff16ca535076ab6ad937af888f4abc4ad7f552a6efd4a43252b25a22fa1af5f29872ff1be676bcdc3f3dacd81facd817ad7b583b233eec6eeda5e5395a8e640fde80f5dfba16cbd0ef0eea9122053fd44fa3fccf830b44ee73781edf26d58b5a5222ba32bbc5f8d90bbf745595bff6a8462f78c84bb57f4a6b67bab04c8ddb3b1f5af121c776f2801d86ef7feb44f13c8d8bdcf5d1e766faf3c73bb87dcbd07125a270cbbf74052bb0734b57b2f81d13ae5dd7b251a96c9e747faf56e73404c5714892251356d3ede10b7b5966e6deb74c9adfbb56788dcbad719ad13efb8756fadd31008fb96bff624b7aec8d6f96ef4ccd83ab7b6ad7bef85d001097b15087f4844faa96ae2067b1e2aa85fa9a8244469a844fc61eb51874e076afd4ac3fb64e54b44c3ad5f89789f2690b1f507e2d67b6d3a6ebd0a00b937286efd1750a5bf83b6e2f038c30685478aa07ed40d3341fdf082ec827421a60290a07e14a91f5d1b3a8f7d81c6063ef62a802b5b5f70dbfad70fc5adbfc8ca978e8e0e2cb261978ea858305694e52b7a3ba2f2e1e8e84b1f9ac094869a078505631dc828b2d741d146551ee19794f7631a5aa4a28daaf799864d46eb54b4f57310965edefd1bdb6d6b104ea43873f67a74eca068fbee57a4384d468b84fe6ac1d8d6cff3531a3a50bff73c00da1f8616493f075ad21503b81503380ce0ac9305a09d8191832c6906c60d9cc323b44efe46f67a744491bec4f31a888722d7bcb557a5610732faeb4006dab677faacd3cfd6ff59fb2eb8ad1be2b6fe5337b5fd2fd0b0541786ba164e6e6cfde5a35c7b551a22b736cbae554fad4ac3bd811610d5ce73c6e5eb371a0a416ffc431d6057873a006ebcdd5abf99c76e0e5de990d3068ba4d4933b6da492bc316d5b59d5962a38608eac389edec6b3944bb19c764ca1b7948f614015b86171244ece1492305b47263c5ba7878f355165beb8789059a82ea10a51418e5829b3460e8d2340a2e074b12ab58e45761d9888a922b2266e0d051e951b6788a4a0030d94343f822057a8da96c4d88a43e19823ad4d4a95b63847be9cac2240eefc6b36ee4ca444c38f5c956eb8d010831b52ec2a911c59f45040ea8eb0eaec0d5c9731364166499e309d24064b1595112c35407079c44ccd96592bd3a6c59b2e36362323a516e96ab4dd48073deb8cbc70a7081c206bd8cc61193133eb79211246eb8bd7132c2268723cc93a6de0ba8055b515310b9b33a3a1359c7332b5269873ce59e79cdfac4b775dace8cb961d6fc48808f1824b2266545a59c7e49419526ee0c6d2d51aafc294d1b15036c6a7cb6494792983d860a9c271870d6deb1c92d683ba818c11258e8835034d09861aa43532c94245650e07181642cabc746d6186a898525206488d744db974389776e4b61bbedcb183a6cd6c0bae8d75b932204044927857665d5fde905153074e19a42c47dc0b3a5d6441aa98d508e69c73d6528049227e002942260b96266580944da154544d6688c9f106c50c599c0b2ee642cdc71829b22546d27e90ad918a71e58d155c922c453df668c0b8b10109620687e5e38a768b43e64b0a3314925867e7f62223ac9d59612385c6160b7b02d91417725f900c7db85961692c07b5dc7d8a8b12272d880b2662650f32283c0ac08226ca17911658aec26c5085afaa3239d4de72f020db2a8fb4c048a25d09b3c845e0021649c9ea02d6c91dbfc071642b764a3b4a9ccc2ec850a019637301034e74c4a9985206c7c4164d0bc7b314451c452e532ad0d676816c83c0a3e7aa312b122ce5b1b3f2966339c3cd9dbd48cbe103e92467cc56ee188bf253a56247199d3228bc84cad2d58e89f0e1a31ea90deae4bdf86b0c493a3636448fe53063337ce998d027c582d45a6badb5d65a8f59d24f005fd2b0f5a7fa815a6bfde98fe7ece3a566feb384fb19acd0e36addf861a30952c68f3358a70e312f960c052130846b7351327ebea4ab6fbbcf22292149579fbd5a63e04cd152539124498643cc5886a31e73922dc280a9e0f5363f114301488958a241fada92a5460b285574648901412d1006470b878db9b2a9eb2e2686c5d3629e1493b10b8b1b5aca9ae7640b2c4a49173a67658eb77389d78a3637729c41c172b12435276cbda82aa230c6a5c411c6a6d545c441e58c591c32df91710a782dac0c83ff05cc585119434af1888115b6c4e7490daa2c73595d5d5658d2372a4c4b97613b7caed6b0d5597bdb7242bc01c3020f39315d82a851f380d02a0b0f38584e48814b62180ba82859dae6b418d2c5ce132816732ad4e0e6e6cca84a1b701940e021650487d98c23584e5793b1e5316ee75b64ced8c180850917ec2c66c2ff6b5a423406fd47d42688d1cc7c990afaef022541acb6462413f45f063a8226ee8f1f3fea044fa05758fc80044ff464a52f1853c113570d931e557a6382277e849870925194113ce151c8be6804f1fbcf040f9de0090ac004b19a4b9eff4c10ad099ef8911134e1264a284426c3a0891317c6540f34c1acf23c00f74f5caa38301541574f0fc0fd9de9c6095cd07f1ada04af18211b31e8bfa215823b54c7b3426d0f52354418f49f86a9e0097c3decf24e7ba32ccea5d096d9d7665de5302635beeff7718b70630b1b4305dd480e6ab5a5fe213aa0d3e9be84126bf925987bf73ebc75aa75a0ee6dcf003b95c867b1d0af0f08dcab6f63219614fdfb740acb379fe7c0a635e97a85a5ce58602bb658a77c73f8b5ba5bc49f1f381083497f97bf10cad7ea90ddf9022be1c698e908c517210d322b3596e0bf42fc774a6f7d6267f50216c9ff26d1b89dc54cc124f07d9998afb2551ee62b8c71d63ae78c71d0cc4e4ff9aa87fb438c0f535ad649c3872921bbb92677b7edc9ed6ff55197e9316dd46549daa88ffae846b796706fbb636ef26ad9ab75db4eb875c25869af3983c8399b468c01c64918600c02638cf10e449d9d8934d34c34f368a6a5d6c9cda3893491593b5752c220090325254b42e84d0f610d1185dc4e5fb2426e43451cbbd3f7dfdbfd4ae8f3cc1957e1a1273d89ad4e64a8d3cb333b7b9fee937e7b3d64de79a2399f6b09d7e5f5f0bb1efc7af0d7af73ceba2ccbf75ff90e74cf596ea78531c658ebb73dfc74753de8dfd3d3d081ee9f31c6daf594752a9f9e303eb3fb2d51e81e0c5929061ae99aba181fdbc5e4d8febba4dbb6c7e4da5e39d8fe766cdb63d26adf1e08250c1efb3e10b8ff6d06eefbbd1082d0cc28d9f76f8f77be3f30f5954ca9cfbe5b0bdc2edaf6a805b9773d3ec0d4e76aea4b21b5bc6def8429e2240a0531a499e0f561c7c9141a65aec4a811bc9fdef75d773f628c31d65a6b5d426f2bc281f68fb6a4173fc669979d0811f803cca47bfb9f04c26238672281041edc451f552f0946a3f15a0953cbc367a6404abd01487a9f047bd574a0c2203d1c770aa19ca55992529306676906472c7e792d81c2864c16d9714b982a1f69d47ef891ed17b63daa55a949a51e3c79193dadcfe77cde0f03a8b42c932fe1bf976780f37939e79cf308f4ed900f03a8d61c688fee9fab897d9e2a91aadef5c71aa1e4dd110fdc7d5d9766d38a74b5c702e07f9cfa8eeb276d1109c04c66d04f147d133d81deedbb92964322e8a0ee7daf7707d1a9264e0475af9ea5a6080e54cf5237833a55054bd774631e55d759eed881a8d2f084832a1a8a5a6d831c07549ae0e8284b5a30b4359df179d393c5c74d6adf9a52644c197b98d1b22565bcb2e1c5a5c40757591914d50d322e78b419526709932c2b31266a6538b290956338eb09d79db413904a56a880c54acd97569c189b768c830667cec60daeacaf1b295820b760594992460c5f8cd9cc563032040d2d6ecc15a8e1952e3fd08c4c79890bcb6ff28bb335498997fa988c9a34d8a67ddaf729b04a575ed35e49346dfdf87fb670600d3906c16233125c7d48beae92a80d21c32308b671fe71e07d5e586a7753921fdda7a00afbb6650cb514582d6ae33ff1b957f763163adb4826f4b6d7123e4414721bc2d2a1a4a4b48284d62acb9c9f884f564fc3a7ab27e2131187feb2ccb92c4becdc1fff92ae878bc1f570df04100ebc2056a89db399f1fbd26716493e82042260253082037d04205a7b25a1a73e0c3f507d98fa10e3c3f0c3508ad63affce7c74f76b7e9daacb59f72ba1b7f9ba6cb5de69adcd37558dadb495f3f4256d9ad7d4da44511445d1f3bc497ddb4ecdc468ccdb4ae88dfef9fe3aab33cfb73d54ddd9377f596713131da8b369dedb2bf3cae3a545b93939533dd697fc72e9df5a229be6a9b53eb5763704e0cfd9df4dc88eb3cebd9ba3f8b64eabaef6cbf267ad7dd35afba5fa3bb3f56da5abefb1ea81ddf687ece79f6fd560fb6387db3dfee5ab95cb526b5d9ee763476fdb79a2f887cbfcde0b917399b37efd3bacb5ce658b240a58051021c28fd7910a38bbddcef6cac7bff24cce6d73d8f6a8b664e39ebfc5f857969ff5f07ac0183f93cd7f96bd1037ffcdbc6c5ebd0657e2ec3b92ae497b63bf1934c8192c92ff051a11f5d6990c19ac93f564d6d8eade8bad1c57f90f0f3d89abfe6737b659dfbc06eede22f75eab111118a102463bffadbad84a0749d7a4db66bbcf81f9e805dcc5fe2b5409e676b6fd84cddeb69cf36dcbfa8eb91eac4d5e0f778403af6ebb6d59dfb82b5493bf6f48d28e07e4d99196247ef68831f729b76da6af744dda61b6bf068a49d7240ddce23bbbb3b9e12e819d0a94a0fdf43e01f18fefdd77d7813258a48b811ca8d7c1ddd8f38f45ba3e35da64ca46a150e823e8d8c8df9a1fe4ac6dd6596b6db56bad33564b0861687b60b70f9d32ac17c21f3bc63e80c2524bf742941763a7802d5d7d3b235f5a49e88cdb37d09f43d89d69ea8c3ece9c4b2d27765bff24cd63678d560cb7f19de2f986a4aed3e9743a1ed32d9a40eff06aa9e8799ea5d6ba9d4ea7d3e9743e2129516b4be129075e2cb43b30a6c252f85ae5c070e771678d76bab76fc2697928ba966f5ed2b939309fe7799eda546f7247da76dd920ee48993e227e14bfe4e72ce39a7256dfc249248249148e27277f726242e893bdc9ac41d92b1b1b33bcbb3bfacf496655996abd65a6b9df6ca377ff8b3f9e5af03fbe6979f7ff6f50f3f5ecd6ca6088186a32d3681c45adb069d6fa854ae341631e80cb26800000000a3150000180c08068462b150305023350f3f14800b68903c6c502a134863b1480e23218a82180842180618620842c428e51c232b00d2f69bda36e9e97ff6aa83ef1534edbd7ea56fcff4133fbe27404419cf8bf8e5edef787fe7a78feaf14ed4a25be9dfa51044a9279cfe19240b24f5d4dec95babe6980d56cbb18e46f7922846003927f9dd388836ffa46e5f59b22f019b01d801fc8aa65e39b522097b95f318ed3c7d205725e5a351bd15024ddab68ba475d1513044e9a6ebd55dfd86c085ec5a8ee5548515957a97d12be50233930ecd6ad592cea4c37a7027a2408f012599f7704c6b04ce309512a08816f638e3f007fc9118006b48f6104352f5bfc82945a19cbb8f5d1f3de297f7639076785b2053abbf5ba4fbbd5a1d5e5ba185f89f9ba3b873f11b9e2c9f7c34aa3311fb10f6b334bf2fd59447a337f601f20bf16fb045cbfffa6ed80851b1e70797537a616abfe016c3ba2d5c053c0c1fa1411c470de0404515f212415dc275c6847cc1b8b81bb133b25e1cb2940580be013a8e566c23940c5638a7e2307da34ace83e8bc97127f90656bc9ecaf946b856f2ababc75b177bb7e8976412e02845b5790257e4d5de1b732a84e829141e195fc0ca64b9b6f2a6998133e90298cb2a634f62ab2a432c26188ba4962dd0c8b3631b313e01130aa13d66b0618d98447411530f22e242a23880aae1b9249944f26b84d16608284216df64eda96eee26a064c3166ed845cfcedeba69c6fb77383b2e65fd6c87a89e700e0ff658ad8fdec1a9c3d9235d5ac50d539aa103f0afcb160910050d95500b837550820df01fe604673e5cb2ce7e297cb03a445a30113e0cd7831d769207eac47c5b5bf9ad121d4b263efbd241d6998fd3c5a3741cbb177efc0229ed038486102e42e26dbe3946c9dc6a41b815883a5040e100ea8e46a5d90f2d9280aa5de6bab5eb0713c17221709bb9688669faa42347088b97f95c554696055216067157b84757e7103a37d1d1e98a6ef7785272dc7721ac654a6be96979d26c92c42b170551152263f087f82303df4ab6bfe63c16008b20ff2b7251d64f2cc94e277265b5bbe623ffd9ce7cde5db88963398aaab15748769bd3e7daa178899a72a91da03fdc522e1133a905987b62cfc1c53356b35284ff9334ab314f401c2202f02b8e2a606278076760abc2b32efd2d440e6318cd3d52ed858421ce3674542591405d5704b80686786491ecf9a1ea7bd780bb1abde746e3fe6b904ca3e56604f7eb68371a17c05e40349ae700566b22b3326bbf404438a30ff66b4151b44641aa3bb5248893027a447b64294141caa4d1a7e4f99f4187539c275dc1968bc33f49134ccfa325520bc81ce040de9e5150d02fccdc603fb7d26e4b107b980ec2a79e14d69421898db8f69f0c7cc928ae9e958748ab0e59099a6b7695e71fc9805c6ac81224247d3fb5a25cb58ccbc670eb5e15fabbfd5cf8e261868c2141872970c73f5dba57ebd48f884268259d6fbaaa9567e72fa9fa1384be2f7043b5f87ac7e41f688e60e36979a9774a9d90a8236f09ecdca20776886f8df4da5ca1d11a5af9230794a77d7be06c368549f4b4e58e28b7ec94f1820a2da872cb0278887bb1e42ca61363554cf23a69e0c5591ec0c8ad040ab27f0a4026e9d6c7d5cea7c2d508aac5f460a48d370d1d71fc260a330046b0238680baff0321d9787fa832c50b350fb6eb7d35727a6069ea62330209055adc5ee9d4316cc260df81e5c082d6fad794053edf7d04bb532134f4cf389e5e0906b3146e9ecde89d8d071ff258c44bb72087ca74e6317bef249690509f8807c5b40829592d16d8b29778b8d86f7907f6d4b1e317e121b3e8a714261f59dab011958919d88c3a31e1a86c94b339c5dbacfd4081b60d58e5b9d6aa7a9ad937c5759755af9c931067b622abc0bba016e86c8088dae14ee54dd76fd38b57876ab102a502fc87917d2f5e331eec59964c3791dae1a4e8c5b05951b9b5fe6cf393aeb8c0e70dcfb39d6515070ef5d9dd299372538445c558e80dbf27fa172fdb86b0c57c9869cbd22a4bc15f079dd8024e26c90c229bfcb6b3a4f4b82489ad34e8a43417ec714c5d39445bcb43a725fdb0021e7501bc94155aaf177ad490059c1794e17cdaeef798e902feea89100c39f4c977a8333e1c861675b373ecd135c4992168fdbd51bda4e4d17bf03837ba9ba3a26c7385f674e740b602e79fc2e22f3f5c85b8973ebd8d94bbda7f51f552b658b7f7a31fbb68149c77451c127bc3fdf3ca5ccecfc07576c48f105f9116e824ba4e300d39197c8d009c1d9386e85903f6ec2010805ab997c05272748101e7042e923592725c7228609a33bb213bef9163d4c5e0eb64590d1433b09aa8a9073f921b265bf8a53a2112a36a0250f421d10ae80fe7fd714f33829620038b51c2f16b21241f402ae106dd676302978141818f894c81f4239e0e8c0b038ba5e9f8b95908d4386208c0001547279fa39cc7f654dcaadcd21fe2c69aa9fa3a28b299f23d6ced0afd38d70ad699aead0c361bdbabbfefa1371b6fed32ea051b199b0a7f22e4363d0777e58f5c2d8ff5781ee04ae50e486b15870712de8db36e6ce854a1015c461ece0d8508955bd399e6c441ed6a36016b3710a2fa42bc944f4bbc015c0669e54b4f6c1e57c669e5f1f0901ea14a66656923021e3ecb30ceba4eea20ac5f95616c70e9046b9785f97363b6d7546a4ec459ba5a78f2e07c66cef9e6b2e064f084e019bb00adcb7e43b6928fe6213825e413224a1f31deea484de7a0cccc11f98589e66723df2a345084f014175b045795c25bbec6c3d68a76d9f31bd4fdf9e569762a3e1da9c150ae15ef4eefe29354261177a643de3fb761c7dcb60dbc809f66d9f43a4e2e44c4f4589af5291f584a29bd8db276b51f291fe405112881a159746f8e03901c709fea6499c5dc619295b8e96cbaa180f40b06ec06d8c21638724997aa1fd74c9f1571da51771a0e881285979d55f9e952daaf24ad6033c4f8ac6154dffaec4608b0e3b7226af5cc223feb6a34096c6fffe2cd7fa56eae7934637397347ea22452ec83b3098c7c71e1a98085a689d220a6257e652df1ea95bb0304e9c3a6ec8e80b21f70d0d0fd9131afbb552f0ced40eb37e257c5427fa94912596b100a847a58167e6d18b2aac9fd1388ba7d42eee645badb6d9aa09a0dcc5c956008d8a62bbf5543f6ff1599fbfe2d7cc8b03a3c6be9d68578174f72dd8cf4f66ec078cedd1eb99c5d0798fc71921113f50ae0c4434970698bc52c89e493ae516253181f9c23efec9835d450d717b2ac8be48f49a98c8da3ee7ea48c9b518ab93492053b88c273b0611fd59decd7aa961381edee7cc481c4b9cdea78ecf5d4332c8cd2bcf1544be7ce008de97a87b37db6f08928fafcc8f693e0ceccc61ca68f3fac7e0d2436941b9d3e0fc6c3a3607fdf255cb4b55bba38e43e7545051a0a9e8cd2800339bd747a02c23bcb5ea51cf2c0ba7e19dc50bb3998c7e5d9b11df4f30bd884ff0d7ccb3e7e9cde62d44976abee45a3453c4df47e5e3198536b5b98928c48bccccc259c532fc7342959ad3306174c6ac904b444279de4a5a59a99b7595a4d36d7363c41a2ec4feaae4cc3aa2e6a5665ca0d7e3c4ccc0df2fbd74cb61e901281fcbda60d5e326b69565b32af5afcf6c02974574d0d6ee8c15aba6cd5480c7ccba9a36aff3c31d5b2c28a8944001c40f8da464593c9e0dcd29e394def83993db81de0607ac82752b62daf02b8064439d32ee202884b5081b3de35c50fcd72e70d6d9d86daf46b0192c9bc677cb7a1f322e82d4c1a4135bccd2ee4abd7d7acc5587632aa541c9f142f186e91738d3420aa11283409e347dddd671015daf7d2e187c528e1d980fd3a611f2e0f2e503fdae16abde6df9527b440e373de5b2578c1abdac08c8b81bb6280d7bead2514c7d3caa0d598bbd82da1c7e74102bcc09cd69f944527a30f09b3ab52697a5118cb45db67a897f9fc90afd741ae58c97cca1a06a33eb6aa4b0e2bac05dd7cfe103d215dbfb40a83d11e0ac30a64643eaa000b8476eda0c2dacb1340682ef425b64c12ea9c2998cff586940985518b4fbd9899ec1aabf2e6bd966bbe312b43eb3b5eaae0140bad8b4264a324798259e28ac6599bdd6ac84fe30751dafb53143ff924e14f546788650ab90772846497a650f5e4433f98994e6c2be785eb03f409cc7b80efe5cbce3e3e7935d68f98d8efa689f18eb6ef9453e8516dc5667fbcc07c8c4a58249edca72cb6a2b35b4406206853d2cf8f081d3a9f3a04a5be70e36eee75a0cc179e0341ecadc26984f3eaaec908209268d6c01e81684581030b99b649caecf7c8c71ea62362361081f7e6dc424902b40d9df681c1cf6a0de32aa2347b44301b7f9df5dc07d89c5253444f4a60b98cb2c4a701f010295df27b15214ef975dc851861f4b8389e697e3366a1cf8f05517d67741b923db57ef4f45623f359a359a9b4592a4a0422bdd15673c857225ad41190566b1536a57ad4bb21b4d7b287615a04fedfc85a4a70ece7cec8616d76e9b9a185a5cae0c17081e5d24647601533949f6de5e99ede6839db96017ff20b2b1bed0af5bad5d32348a22c03be8a6f561b5e12ca4d200b57fe4a5ffe866561be26d3ca54c983a48823e2fd90539cbf9310bcafb29631843ec9c0fe9226f19e51bc1b6961ff922412f1acf5812e717d301283707d11a4360b983c5fe0faa73c9ada6ef7b4d8d63bb9fecf81558974b75657928612c6b6d6e0d82737cc55475fc66b1a9550e7a384e9c041423b07de743b0ff94304c45f77e6a91b6bc25e6590ddd4e311747505d73dffe9eb131878a5e568928633a03c240cf0f49fdf6e4258505a24337f650f367a9e8d17fae567fd436ebe3afbc893f0a1086c45c4d48f8588380178d060ef4ee8cabf70aee0e1a2830cae8dffe8fbff830a1831287dffe60a68161c394846ccd499806d8ddf79681f527660655027fdbba9cf01cbf18c6b2ade0e4e88070a465130e72d901d279196787c6e5d68a15286a71bf16586fd8ffe1abcf8f37295c484a77d531548aace9f7541200424dbbe50aeb39e56f819f099d53f17f10a0034c849502f0dda532b4d47c974d2684aed50aeaf2906fa45839d8b8e7c19c27227bcd7456ea5aa636956c5306394408b2e83ffed42a1e63f1265064eb6d740f243f5bc5bf988c23ca2e3eef078ddeb7b17580e74f58ff035275278e05de501e55a03dd67c9dc4b2778ded8296fc14445913a8e35232dec0d5dc48ef9b1dbb537630c9ab05cd90f0d569c25ca5faa9d3fff1d36d9a1a1a755255fee582c2c9df0d4000c1ea813489204d5087fa04ba94607faf297a14ab01e0408851046a94dd9cabe1e5ccf0ed56e749aa4b4517cb67e0defd92f8bcfa897c3835ea8891acfa6320c110fd0b773109f851258518aa96023249719ce01ba2ba1e27f30fe4e655897e2f151306eac37a576c776e011b727d6844cbe9a59820cd044c365c3d7d28040639c869cb77a1d8cd1bc1f9f03e60d317591ff1348677c356fcf5b131663bcd1fe9303bbdad57fcfad84460f3836e35635e2f965b8c6353afcda6128857f5d604ad7b17689cdc74800f819e95a3949f9bfa86849035d72fa3a6ae2b31e9996a4ebfb813d795fcdb42eefca08fc9f9489133514f9a9940ccb9b0d336ec8b98481a21f7d6dfaf1ac45d60eb8fc666353f8c7d5e0c11b00c57f3bbe03bf8902b5ff9e5e64643cdc994cf0eade6375cb31df1ea5ff2ec4bd33e0f4c2cf759cb911f75bf601ec968872e4bdc3f0675cf432b66540d841814eec6e47d638dc89156a9c20afeecb74dfde02f1bc0cdc4e277c74e239055faeb4aa59430c4d709371fc07ee09bfb5c65afa97dc1fa48431426efa2a1b648741df92a87f550e2380f671e89030ef2b9212ccb63bb4380416f1a22f449d12c8232035fb4c03d98fdbc77b285beb6d400b1417d4ca6e9754c92f948d00f61f34ed562f38bdacf438a9c9a4bea63e786290e1841cea579991a9a40c146ef7a2e35487afcb8f8159af90128d75399c271ec6e4c01b55a215cff61b6db91e1496ddbd4da786d2b1b1ec11888e929c52241d394927a122bf20832d9933a62a64f057480e77160d882095ebb334c81ac4fc669ebc96080e77d4efed75e83f2bceb26de1aaa08136b5a5ebd36b26b1622a843377e930c25823fcf065a925d5f11606797105657f9ffe906fb08d58daf6843943755b4e5d60bcceb648ed04d2d9b15be30b7312dc0d3d4d433a9328cbb7552cf8b451f7f50ad4b657c71c47fa704b4b5382621fc6fa0611decaa8e88a64a173e65aca640f60b12032a068808dc7c4b83d009dd7bbe8a0dd8360b819e8f39446c215a580ff1ecbdaa4d0179a0f31cc3e16db203e9e33e8a10353e2142929372ac40320d2b284bdf501ee9c6d294c6845a8b0133f83230c30a50c465256aec27187481c5ab8a1166b746b82c413b83f400dcba44c0733cf9c3acd3946e37a6936c9ac1d9ca4985d7ab6b108220587f19ab7519fbcdba85b459aac42ee355aa62f25b7f4f1ed92ca3bfb48176a9ff5b49df9b8c570683d2366bd3021850844eeecd95f1507c6dd58ec6bd37330bad04efb52a7defaefe4a6b0949d534e7090b334e732ec510ed7d9b7f00b8a86153e4a9298bffe6a5e320abd21084eb01222230e86514b1d8ea4a7c93ebab263c483549e0f63992d2a72604be87379769ea9744493136fd23251b1cd81dbf2a0f3ea51700862a5271af583b2935b30b0ede4b2dd442b68d1065594b2f62183218e6436a94abdf4b0f2b4401121468ff3af6e7ed92377f0edbef340432ae11c63fc11ae266551e2f3615a7c7688a13a7b445fa74e79cdf8b31f6725d200b7dc66980a1c445c6823e2b5c2aa5b535919ba916ca10e1e27fb863955eefbf7a7340d2c7bc7262fb811d7f71646eb656684bcc8e3c87fcffd9661e7a887a8f5f9f1b1328ffc36d9f9f8255774663387f4e6902419a4a1ab3e5f50012b007d8cd4be38d6695c2224ceefe73f6b2121a1c1a5518c39de93360b4c0812d553601cef05a89c64c2b4deb24b0d27f96464f7e31e4074302f95b4a5c2b9c0e485c6bda7d018c93a426fb0e36b81d91c21cfc7b51a42c12098e8ebb802279447c02f66faef14ae0caea5293da252e2f287780fa612b8f6698964f89b88015fc264ad5bed91ecaacd579d039b207c35622ea4de467f4049da37118057e05100406ad9a6bc0e24820434edc94301e245f229211faf5db5356588bc310171090a17a9911e1aa5a20e2110467c36abce85f7ec96aa850abe9f0638fe9ea19d5429beaf250154a546b90ee98e1a9e99d6835618ce0e05e16447438817562ecbde555d3fa0509dff55e6baab98e29c9bb8324aa588fb421145cb2c84482f46e893f8b23825f089c9ec2f382d986e1c4ceb10102325c6191e416541b724d3d7f39de4603e0d0f8830490968341dc3b6d065fbb20bea814c8c7cb152517a0c213e78d26ca967a61a6287d53cc67eb8e7cce51f2a5c794ed5a750243a06d1723e3d11ba2ed6093f3b623d4322464a54be29f7255bd7c2052f5bf6b12c9817e6a48b9c254409d95fd8c653f23a69898ed89a4fa0161beb18a6481bee7fd8c9fa121000303d5e30c76a0503bd8593411cc8ed6ed0881ace465bf30af430c9710c35b3dc790c68ba49031a0c6bcc51ba97d7bfa5f3440fe39b2b2fbff8709d798cce4a9e79ad515bcde669e92c90ba2a06621195d520b16d87177044a32ed703d66b11f1b8b37a84b8505026a49365e40b0bc68c5a4cce0e2ae407f51a4de4d28e8f76bb6ef68497d9c621d2558976b004d66f8e4a5f0d2480ce74b71d14719a5133f553e0aa3e29851143f298412251db983e3a8756063594193472595ee2dcf6d0b8c2a55c7184074fdf070c911ce420245c7f98514fc94ed4794a766827375b8a0c43cc798301ccf982780ad17410e1605cd677e52f23576988317ead5f71327ca7da549b99ef61e83ce1d6029135a38b6ebd3f91ef73617e2f2978a9c75e8b0c530e42e519e9d2791ed4622e8adcb80d17ddb6b94cf870cd27aa80458a86acb944cf00a7a84414814912a58f5d485e4cfc1883ea798352267ff639cb3636b2e4aa2be6c8c63663ae4621c7e3d884b312e6dded3755573aac3c397dd40128c3ace7fd8c1658ef67e7825cd0aec802a471d4cd49463127cea865020737e0a5496bd010648c05c29415964869be13b01e0e15fb5516e0b86bb130a31f5a44762833c705d2b3244e9398741252ac9673e9da982307c4ac45bdefeafc6d544ccf7bec4063f886b44e8fa7069253db855bb1d1d935507c625357118a0e73690533073585686b995da2ae8844d30ce14032a3fff2cd09fadb82d07039207d1554e894099d4caa81d0f49eede612b89665d0854dbf21d08c2243e30b0a2acec7a904722c59c19176f97b55c6fbbfee33c34c298d11a56c341e6d99031c00ff826d1f7029968db2beaa0f17279a65d70b57d991e0d28ea9754b2a7cf79b78bd2c817be699636adbc72163cffd17b48a81a5ef65c55150527fc412c6f6ebf3b60d3970810f3e2f49adf626d0dcd985fd6f283ec28659c55738dbd3eb7ed3ada002cc232fad04855bf8fe6595b71421fc5df6917884f4296f083f0e7401e3c6897ee45fa877d383309013378dcb9f41ed1e5e4b792f5b0cbf2734f753e543b52710b406ede855f4a55e8330fd0d3ab562d71b9da4a104f07aff30423dbc4a23a6dc9aee33b97b84e73c2bfa585e3b5ee154e516a20f78a2d1fe64a7b5c95573033c0206065d5298957aa3931c3ec89f65b651296158f95e2a0ae60aab5f2d410f44943312ebd8b75cab82d1aaf961ca670b48bf82907cbccf909f83279c7fa8de25792fde8222cb0f4b2fc306f77939cdc96375217a543a811581a6d87b9f89f0950abc29d84974f70c2f96fc22962d3927e447a4a30e53a25a53a9c74a2aa8b5aeb585d8ae1daacdfd9299d99c30b224111df1a2280a11417319bc27876db2a26a88b0e3e0c52c88ca66143f4db7933dd1d7a63ecfe705f15ad26c8cd936d3d7e5bed5bf88e7756ecaea205347a877cc2e5c8f255a3578f45315f9b74785859ac74f8f6e49ef692b3ecd49f30d790e3edbbbd206207234b1e35d297323119357608d94315a6894f2cc3460c82908412796fb2b0d071201593a96ba01c07a5c7ea21d11510c9c954df28ee8b3409c019cd5e56ed094b79cfd4051cee78a7a90f3518a7e5785391852797e27bc5c358a4e109dc464f9fe3b712d929df990c4c93b8289ca9c27950355428f3706801f3e43a41d2e0787653fbfd5a7d7cf8d88992694802a522d0ca5cfaf99efa405c93748de63723833c6b18e92c83f7eb3eb87c08b60048f1bd959e611ea75ea18cf2941c995c33927efeac4aa08fca284f7bdcee8c0b6b7f25a7baca1909d56eae6a1b5a135793adb1dd11fcb8f9cf728ac3124722d102673e79ed7eeb43871fa14bcefd00d95252e7fcc4b357c781d643efae1e9a655510a1bf26d1f103dbbb28306e82e9b517dbe21d24da95e048801a180d5dccddc4e7fb31ad0525926da69de554b49901f47605066b8beb16bccc7dc83ac3cc75a12bc8de55df017e9993cc309d9268599132c8cb2c8aa7f3b4936edb2b0c873b5655cdb56d2195efca381d5379c596f6c7509f8c6d2786f7787eb86fc64a23cbd78ec14aacaa056ca3af4782dd533b955afde331b36906bec0984b008c121055993b65a31c5ce4972022d67546d21759c2edb20471f5c79f0b0bbbb3a7be0c48fce7a9edb838fad3697fe1a7ff5a8c45917e6c1bfca202030fa6cdd5844d9c1cae2d699b22eb81b71afb3f20db2498a0751fa4d123e6a30f9192041debbffb427c210d9cb90d290b354a71b19e306ad6f1b1d25837db2f55ea714271a14ae420213bb00b20c9ec04a2281856c6926a236b50971df014a6e9823fd183f817dae50578adae19d0cf7ba66f8e5d5728aad8816d79173640140c35bb636ead955106d81f9b7dd8f8239aedf1524205371f4e34a3f7d391ab14e1dfe7c8c03f7d28f1437c749ad8146f9f899f19445c86e458160fe29c57ad32327ca8d77343168ab8e03aea68bacd803056a7cafcdd4296bd8c5d8c329baaf852502ec5d206a1d6d5207af6c54427f907fbc0b12cce82adb00e278e313abcf4092e4c03b06c8ce93a169cf10a753668407c47e0bc7b83949a52b991ea115fedb74a0984a918b96a9712bc9bd124781db705d3c18d9046cb9924cf9258a8464ce9c77346d763f01f36022685dc386d3bd85757433640c88537ba003ea02453a0f1683fbfee47079ff366835f480789584b01378ef9f07bc7f5e382cf107af825fe9452eb18bdf0fc2dc0b027ef00d9b49bc6e5712c60a2ba74b669a917744a894298c8f9d3f79c59929ac297a22f8719e0b32cede5e8b46e5e6d0a803b497fb7e22e2ec5a1ae74370ca7dde14eda18450b5c7d157cdf03fa0688fdaa6750c3d9eef2d048a39cd98fc6b8e8268b510058cd0800b3f0f158c959dcd68e1d0c53bfedfef9e1d2e487550bdad2191ab4bf424a21b551be3ac5b868d899f58360aea70120ba374007e32f680944aa52049c4e65ade61f1c4584673acbc12bd0494f7e7f7709e03414c66da54d7cc3efb529ba7965f6c4da75554918595f03eba481e0b72865ba3d52d72230cb67b5590c0dbf0ea2060dd351c5ad3bd7d431c971434f1df7a132a4e284f1b52e2565eeff67de9faa7d93c56792ac2b69da92311ff9c76238c868e39df5d1ff61c94ca0e5d64e8fd6f19ed1d9a502a5e64865b18feda86791b6093003f07759b81c4ae371554464175f2be47b9a90760a1bf69736f5ad6d5f433380d56e630a9e46e113fb9b1dd1ba651cb41d3ac969f9f167399712be667dc404f296f2ac4843e3efe03bf4627c74f9ebb8bb1b656168c20bfabe5838771a4822272f68cd5b989fccd26af896c2d5aef0e447522ebc63e63f43c2d950ac83246d6bdc50ddd9a9aa06cf6054b4d11d1c9aba49859d5566d57b9eab81cfc6adedad6e19397c1c7fcaf4087e77d320b632d1c1d43af31bad522a0e6accee80ec35b531f2d7664cd48a2210108cb894d80aae626dc063ec40b45df8eb166f490b3f0293557cd94decddda0b44c92a872370c65c9331ef388b54b47403db17ee30a0b6f97681398e76790dfb2d4d1f6d256b34ee4ee7b6f4094c411cbdc70d18c8df25a1cec1fc4372535df2a69d555c43c32cff64dff5dc2d00b6b84922aeccbe979a1bb22661ec88a93facbc237fd35c1d13795cd5d3ebba6a5a8ca8c9d6f8f62b3e5faae76aa4211242f20f424d8eae014a11215263de1bd14e04d46f1e862e5e48ba355c843082f002c7820e6ebd834a0f33cf627872599eb081914b0ec26b666b7dface2ad41f8824ee36328b1002460ecbf6f4d5affe08b849b48307746913c67e058a655a9aa5b848089d3dcb9e9b644acd038dad392fe89acc480379b1bdc6ae154869b3b4ea47ea5648eed70a86355101928f9292478ff34fd06b33762e2a6de651d6794271f142eba5c8c16e6feb411df35e64de23bd251ca009d0c6bcee8e750388a3603d4743c3c998a11891ddfce1b1974c9a8aae280b1b2a96cc98d471c8a53b83e5370095d1c5ec73e456e427d695646e2754ef807834ede1f16921549e2675b43ebe5919c24b3088c0887ba89fe0f19c8ec4e416de2384cf7c57be610edcded406cc6d337070bbd746c7654095bc04ada2d7679940e439bffd9dc0edba590ed68fcf06ee328baaed9852d19820211f9b278422216672204bfcde7539bb9f31698b959a79854adc2737cb553924f900820bedb22c604f71a02193dc821bc4bb8e00b381757d4520a4247fff15944b16c6e2fb5fa100b7e879566423ca6d55dc272c0e218644d78fe443d4be92b8d765275000366949fa5ec6b682052739274f16e19700d3fef394edcd0a2e3dc506761f68e6439cc9176c62eebabfec879f64557561dba00d6c8b4c163971842aa0e7f5003498a8043a26bd29a4e87661525f5318d80f08e97afc6ca239f2d484f3faea2ed10dd744f07c51354082cedfc2c1f99c8480a940bde7f883c9e053d2986962d6c5aae7e8dded4ba0a52aa5926c54356202020d4f97ed7bfbcbadd84606ff46039dcd8781d5da741926e4439bcaf54a5f3d35816affeca86d8645744e573d30b863bbead3ac28f7a927c87a6b5dff7a0d723d601ed37551fadc30db9d27318a51cd1992ce02e26982cb77153181d7029992aab6139a15ffc3eec640a972a24502f07d931311b3589daca8ddaca8ca53ff8150ad99cc80df1de4fe594e0c76921b1c11e543b96ca7d107e2a5061b76fcef96f47c4a726acbe81ff262357078b0a634fc8b6f62802140f90b44f5be0336cf67a1a20e4db9583b9a41bac7071135005f6cb851be4d2ce173b0669d710802f43780939119ff6dfc469296c7dec8a4f827077f12bd724847d2c366f6d50f3583be400fa514a83f35ab4edd5c239565ddfb9a877e87bac002306d5771a2ea9df8d0a5ab4a1bca523185db467641c8c0fd2f4be1b1c0425a2d8c80963f514f17950c6541fcd9b78c0aa4a9955ae701d6ac16fa665f6571f36a98ceb8390da545c8846121cfa0360348e5c4fced1556e501a41c651dc1fbc7090d5e98ed1fe2fb4e74833c1a9d1c10eec9318c133d293fe44f9d83ff7eebdabc8426fa1e23889594aff6ede906046342078f2b07638c382507515e008ff02dd7afc1d3d53434cf9454030308af7ef48d152bd14032a16f5d3f4d2fc2cd3a5d0a16a902b7fe767926ebe97372b1b93c97acaaa10f4de1c9a4fdc47dab741dd740017bc223c9b1168a809369b129720241741d27edb8de20b4bc07f3102d62cee1ffc664a10d29a50fe4b70b80a34a9f5611cfb013e6eab85fd9ce6fe6bd7f39d2cf8f016f121a993e5ed9072fc9ba7dd66395cf720be830587ab4ba63686b3d29241821729c277a8b2345aac068c11ae7f611c9b4c87f21d9508b9cf6ce3fd3879aef59e3eeeba222c1a5594c0b5174b536d144e67c507f3ce978bc8c4ef000494ca69ab72af3541b68310da348bf09e3f3edd60c81978e9d52a91546d6b49c2c58dac7a204456cea9c64d26049e0af81f6eb2448b5b8e90e455efa434ee71056f0394adc8c1e470e677f56cd4959d4d9e9aa84e0d88e15182d744b6f64662ed6c4e4a7cdde0f1392ef5c9ddb7c0f4a69298d69e1c7cde1d4b744d4ef299ab990c2eb1b1a7ff93e16fbb146941012173f03f988d4fe3481d6022a410131f798ac94448f86280c6e284f14cce178afa1dc5d089d739e64f11077125b496128434345bc195a7e64b2ad17621f32c9f397365fdb274d5a33ff56aa51f9fa24eae98074263adc8d9608bf4498f97ab7fb83198fe298cf8e7fc64367c93b51899f30cfa86c2d415ed8b3e9f41a3af31ff649667656f0ba3ba1248f6406866d669a4cc6cf390a8c937eb255a546f37e5a7318ffd36963ee64e7fdaa2fb999ecf0142b3d5975856e54177ad9c9b042208ff0fb9075cc24374d5b732444ecf8e96b3505d8f6301a4237a08f260819d0ac4afdbb97e873d5f42b4e5284dd71758a642792722592d63634c663cbc23c9688cdc37a13a26af9500cc24627dc06c7959a13fd847fdcdb3890d967399061224ea19a3203304b52d16ba0ae932c30183a089ef350fda86733f958b24f3bb4b847126006b70cf1dfd000a0ef743d3bdec348b5d6f92646511b62b0e5068b8739f7d01b278d080b7b421b064e84343f1e67ae23a2c0fd179271740cebde4464ed0f420d664e99c984dcc47f89f1d55e74fc44746da0ec77703ad06dfc08016a92bc7f228892ff87152731fad85200b2bea75cd3d1f5b1dd420bda641c2f305e0dc0be25b1013d2c9e328f9e0b08064dec0215410c6b52db1553a5ad99c330f41ddb2a3e9d09e533ce1c90da1422c636da61c8e77abc7905f51c77cb5101745a052f6a97b7fb5913346ab7245eae4f3242fed9d61735bc26f5318e7b04dab2640c56af74ccbefdc4c2240580a6a959b25e8b1d74a770e6d1ee0ec941efe0d595c3fd26cb61e55dfde2ab9d86ef190e36b5e7dc7476c22a4d0c342891ffe74678cf1b8f3dd808d1acc42a54e76ccc2233a54b2cbd0b6636621dad79360058fe92f593324442301cbd062a46b036769af911e44371f7d8cf8e168d14021699c7fcb68153c18267a0b144cebd5ac0db96281897dd262e0e4d9d40f891d1fcabf1da1a5ece7b9070c224681b3b99c539ca2aca60d1f55ba8d8d2d1528c83a64bed8a592bd51f6b76391367324686b4eddea1661d070f9546cd69d1af8ee764b5fdd58dc5c4dce097d440b70b0b02f6b5591bfc138785b914ab8cc8235229fba7ac292e27db40b03f9c249ea2d5ecf3201d09364b4b6f55549c93dab4edbcf509fd5a9ad4e1592e9972b3448ea88a6945336559566220bb11828a785e982e80b8a61946c39acb91a9d6454dc37925ebb047e53ffe354b34ba6220b3df753b81fa0af558b3587915d993848b0256cf0a90f64e614a8088122fb59497a39002f463da93dcbb769b4b0720df287d7db3e22f2910b3bb29359e62b8df7f93827a6532833bc5e5c859b403df66531422028976299aa4b71a797dd8edb31bcc599a8e369ff530f4ac7769e24d7e452147cd55835f52155d8fd4fb75620baa3e04a3a049011141fa9e75af926ecca48d6913a152a309926f6a3a06c4778a849c9afddb9223f446ffa161718bf0237126a0ba4be80dac1c93b1a25f1c7b88e99517924457e04aa7c0a59386ab53a13f63e1feacb6edcd41e2b22d54b39dd3f584f57e33bb7ba7bc42aaf0017dbc0567315c86ac28ffe6e798c91b0c6bd8ee9820884432d927dfc2acd87ba20c39322aaff8b11aef8054e4b9eb89d5078a0da13e52c271d31b10d077e3c5ae0a2845754d654b4c8ed6f0168649972f119be129e36d22008319d36b8b081c80a3941dfe08cc876a40bb29f2658006ca1bc70734e04db3aedbdfa3b86a12e4bba870e9a3dc4ae8b7caadebb69b442f8e183e9c19fd35b5bd04e77d8a1a0d8a38b3a20ec2162fd530a8811b15abfbe8363d24bbaa31359c5e251d261a0bf0714cfa3c6039f9aa33059506f11d60b7420e3aa0faa5db2bed6ae00bc17934e95e0d2ae08fe2fddb990bc9d2bd687f558027ac5898e74fc2afb2c204ad1b01345d74fa2a7d0a84343b5bc8555668fea312ed57b8cd3f376e06cbb372df601836bd7c2636d2435432390c5b308ee17613fb5784518025506711449e7c69c72c7e2b46ddf2b35505492d4c007f1ebc0730313ddf08da9e5a18bfa936046530f5a701a16ed068a52c4a6ab23695c5b9a1a2f5118515bfa45c7a93c1646695d4970ed04fb0999f1cda7d7d923790c0e2a0702a8f87d39a5eda9eba61a4dce03aacf8f9b16c1eea53a58bcb9c8a6da0d1b0506d938999badc73f04f6104bc8a70e29cb7811d125608b8f867ed264730ac1ffa4a5798075088092348a08ee948ae1a84b68aa66700f7e4be528dad6b5d912dbc2ca33411b23ef921901e640c75fa12ac4093be2fe4acc254736fa7dbbee92ac9244a01e258f127c8a61d92e2abe4032bf9e292ca76b0f0cb854aa52045b756f442253c03547fc9b2908fe205c344b3009a7495777d365544751fd753c6e7967e16bb4e77fbebb22e403a709d6a348f46a7c2227793a15ea648a3f1b15e1623af0911d8d9f093d3694db00e1cd4a573df5933e27d22f9bb450be8dfe5ae209448eea4186b4b15e91e4dca8d5f25c01a991f086bf2c7c393e3283895445487bccb51b42f83a4c65dcc3a01e50258e3084506faf29b75ac5370ce1c7ed114cff5f69f705961d671c1516261931c6c8afd804b594d242ab1ace0d260582926ad954e45ecb9299fdce92b3df8d40f85cc8c46e2dc0a0a7a29324615075cc01e1d479d60721ec50f217f46e62e15066cba26fcccae93209b5328557a3b8de006dbe7f7f75b530437d86f7ca7c46a247879972b624448698931211c6354117c82cc29251c828017e803382955229243f0a6b5659bca27fdee10a9314e96aaa29efd945ca220d7801e245baf26b234b08b90eebfe66db1378cca05f1221564273f03262375499a9107566bb7f3970d2f2a295067910b8168fb0bb6bd382ee92ffed8065a0d25ba4a6218be9b0a1e362fbea3390c9bb301d66dda3ca33bc0f585e3f6ae1ea09c69378774e0cf4625448a2c08805ff13b2543da75237133d28d0dd53d8a15582bae7542a8c2b7717801bbec88fda388521ad87a61f5ccc8e8fd550865eaa0323c5eaeeb63ee7950e70bd541f97b6dd3f7bbf64e33da629bbf9a4fbe0a32fa5048d12ae9336b3a5a7c302e72408c80eed5d26984ab1dbcc5f90f4e474d2e1f284b9302cdcb377ddb1037497e617bcaba838d9ca96e2970286a2a0f67f58e36f638e03c21fb8f93769cd5cad40d7fd41e2b2ecd64f2a3044f74ae41e3ca0f2cbcb2396f64b1c7dc5e76e2e60b9f8e8d891a2f2b1ceb54b83218989d695effb604cb46a85f1e74125fa3f909fb3e428708c508f60a7f6259391dcfba1cb84bf04cf93c4021402df6e9679194476a6206e0c3e115728fba5ff681ba79e7078113b27313802367b01dd40a69fb334e0898183369c110a38ce50610619646d8a66a51936fa09a3b21115ba041214dff0dc7831940e4aa80f0cd71b6fe2703d75d72d314471661d5de5425039d72c194fc6f847824a031616b4464eec74a624526dbb48439e07e68356c4137fe66414f2c767f6a7824bddb5860e0f6299271b35d5d8ad5f005f3d4fa18919fac4b2adb5e682de0365268345304b2b35eded08c8f31643dca6028b200a5290a2bbede788caaae4c8d84e94d6e81ae23263614188b503c3fc6b1d795b332c14b7c63c218c7dd174b35d6ca3abab323b6e69f7c7d0449d0e554ba00b515b8b8f1c557a3f1721b814ea5e2523f7afdf0b31b6d882bf049d6196d3045ba66255f427a79a3c64546df59854b1fe87d88a8481752b70f847db5da9e125c31a2acfe584d5df41e2b3db2cc9e88a8e4dc1b741eccb872d502e6f238ddd3c19da158628e3a92a222bd66e948259408a4f0b548f87c05393b6b6ddf75b7c82e07838862425c52daa026fe5656cc13cfa7f7c5922df6c9e896d3a4c15e9fed1ecbe39e5b3c607f19dc8eb5170bfaa3ed63714b894a39f372660881f1a195e1b3c8e47e8e41b1d4072429313aa0c8d0900539fe830018161e66cdc79eb74eb6d740a52ab230f71cc0fe1a80be1ac6d27de6e214a2554cb5900efd9fc7b2e576801b0df2c32e014d9307940b551cd988f3fb348e309a70d504e4d49d568347bac6e20e15caad55d037b067c8b5e029d587de825ccd8dce8b96ee9e8d82c7a1dc77ba277a75d93c3c551da74dbac62110d3c151d873bdd926299e56a6b5c06622f2390143e2eb28496227f796551edae14e45b7d07755c7f6ae0f9fa735646ead498d424a46bc89c16814f765a3327ccd2416266aea9a305f9d8c9292a8fd26c9e23a6214a373eb82549a4de269e5fdf0fea2b4cc9bd884b57e68c3d655893fe36b5a57125906145ab940428859e1d275acc38ad2982ebbc93ad7ca571473aff5295bb063cf90f120551423dda82a84a2dc0eb0f10f45b1b58754d53769685f14e538a3e8bc50ae8f92f92e4a2c24f485ad5c7250b9a1cd856dbf57c72ccc4ff145d9cb69f8b5a46c4c7549d0b509f69b847a7d6002f1db9985025d13b26d6340550da09c8bf3e9086eb5b87092e9047923f6b0b44b2b3e24a61f81f1ad3084c3d2bf6658bac443c09c37e31102227ed2bb35a14175818ba73fe90886d21efd49d7cbce2e9dc3ad460abe63c3b6863fa1c0f8ac231c65c6b916e886505490bc39cf33836f3c4f6d9120c93c8da2cb30c3c704acb4106f7d14d306a585d449ec32340c6277dbe5f04d08d0032e073568725e210690963b114c7c5f846157d0a4b3e7f7afc6769f34c1cab04bac40b98a941b9cfe8ede51e7712f06026ea38758196a702107cad2d58fb2c5218f7e7bac0cc16807ac817ca1bfaa86845c12656e2899ca9726fee4efde3a3f875a5947943497da40316465046afa8318430e06500c4f6cf830b46b858521324d133d931e29325c4d132f0f8a618774affb9f752986b155ed2296c999869119b73516eabe1a3b91396ef89d50810e3a27c27b9566833e1717ec08ee8c745812c5ed4f42d519c563a9a8f152a00c9581b788ca3897a8048831628255fab1ae079e9525b594d2ad5f3edefeec8fe8a6684751d7e8244d007ec4d9bb69bb48c2b09a19b2ca28cb15524f57598427f83f8147bf547d871a8f3590f8f9bce7e76708729dfad14e03344fccb0ceb694bda3e010ff86a7a5d01f6637944444452ecd9e7029702fd97141cc4af01c648236987f72a3f29bdee8172b0ab8cba548b104aa3f7dde2253df8e0b8e0293300b8cd0f2c0710a0f99e9172f9f6f0065ad8a8c83633bdfc0f0cebe9e4c8a45778a719624131ee3a6994f71f6fa0ee8a0c7671af1c89773a859d008f5bf1c0a19ea30f55351101e60575fb1c3c9a60756f1a7582bbe3fb82a27b0805add050d2e6056978e72ff0a396f20b253f8558aadb0561bac25118de01e09e96ddf68d05b8fbb884ad24e42070da39539e44225209112096d395bd3bc9cba647f30910c2e3150065adbcebb97164dc219fc23e099e4944134e029477469c29d32e85fa2c523fe5d32f9afbffb0c16c13bfe845e8314c94fd5a76a1f7d9985d09774051f2945fb1f47b5848c26a2f416841ad79bdd821166b8ee63a106859741f60b162fdd12722ffcdd7e089ec182fff838d7d6132b7baee8255c847ee79822c7a43b7bc0cd5edde3dd7ae0263a327416162d448712743618f8ebf0b034cd59b213e71d26d01b2fbd5901d3bff513e09b9d003a9f00b180d9086f043ab79d419d5e9eeea753dca1c4640c5112ca7e955c4c3ce0cc9096a9b1c41893922d60e826fd4aac3ba5a7b49b212a8186914136f98e4c0d3fdfad389745d884c4e0d8452a509f2a39254c683da7718a4b31972283fe22571f8c0b7d857b43f050b7da0d96d72658c2245769d48272b4e3b05d199f67bda37daea8d1af79f1e8d27617db01836b6441c28168342ace009a7b2c190219523b15e1b951d6bdc930a8726605a8528793e36ba6ec3b04ac9c068a37b3cea9e465137bbd1c7ff23b07ff92832a64b37ebd6c4ebc87595f3acb9d21ffdee8ac5ec83d4befc042de62cf37d3e9e1215dbefb95bff57175007b4566ebccf0d4b3011fa4578f0ace77143611c857721a84f6e1ac6697c8c97a2162ab142f4478d7c4988e1f24bedf8e4ba4e62c68d7f6814f96768642ffd9aa5465bfcfd890c48d3cdde8dc9ba215fed86e52adf9fd540fc5aa624e998b8f67a09fd8e77247b26c84bc2e31bb613201c13c0b2de9ef55bdce787ad1a42335b08ccc4bef58935ec6d978bde83395014a2b0359225ea3cbe1e7f325e53519d76474c181d2e505c64c8293d38c105c2d08660462c7ba9f4d518188b9ee105d5a602ab68eda64354022d264b8dadd649927c676703309013bc58056282951c700eb3da1f73de6fea824cae9100fe2a059f2d3c912f0a31f8f3068c372bc9b2fe94c1f05cc6bf834769af7622f0dfb8543d3e0b01da2a7393ede3eab65f5dcb1c2d7b5e4de0eb4735869f7029485d8c7c93fe78ba16f080c40e48f81f781c043609ff4448cac8260df5bebec17a5de992570de7d2bb14fbcf07e8bcd4e48bdc66f03cbf8138ec314b04ebe069d950d34154418347b3706bd45f642d290f02aa430635c8e3b91c8e3c200e4a38536d6d6236446b7f51346f27df28ecbb2937e647482f91e07db04fa3735797413e97b732b4e3d69ece834be42a681922ed406c5f67b5950e2e40b3237b09a827b6af0d4ce51dbbaa4671b3d586ab0f1f4635dd4bbb0bd61966d1bdb9eb25e36a9413f7df3990047c81b3736e049cde12e79dcdc9dacc14b38ef6832a2f8cf9a82b3b66b9f9800fcc95fd3b5e047a09979c76a0cb691dfe9c4dd4bb0cc0f6846bf54f9b666252874bce69fcb99ad7d7a1b8018e18757f91b5be7d50986bbf452b938e119a4189b665c1ecc759afe06e46846550b07add9440e698357188857796734a15838c7430123193ac2374707c3b1b7b61992022c333a4de4b3ca0182195a48da7d57a08a4e6f1524a015723c34b8f07ed21c7e18a547d230083fdcffb0d09a054fe441d475331f1d9e068070db0cd5c6b54b20d81c823bb33ce19477b2d19650f30a1782770ff33c5cd448265da07e0a8c8a007bd5a4f2a227188d382ff1b467346d780634cf0c3087ad0c9f5b14627e044071a41c71aa150d82586fd7e62caf30da62058c1dcff576096f890023a3c7d4716b3eddc368f941b30d6c2fed0719f452e3a09a5134ed0c087f6c4222ebddd115ce668847f107c4d42b1b0e3b22ff60bb86e1d3bd3d13b07771b8d3f7a33dc5459dfd4d77293a66ac3a1248e4a87a19872816b36ecc7a5a6e63630b43f2fc1dab3cffa2ea858de67985214f213644b40728007666851882145803ed21defb3126c5c0de5492deccd2b34be44f1a3a0d0fd04f67c6435bb41d33e8622f4439a088331c91bcc456886cf5d1a544b7bfca7cca52927d43c0f72599e41bcccaf7702bdff63102f6085ca12571e658e51b57cc078df03c67fdf03ec67c5609baf4bc7ce5a27542620bfc650d4d9492bc5b485d533d87f70a9dd174127c94f4eedb1ddb3df7ed5ac5cf6f528ebbddda04102628c7d6004163befd5f9211063c5c1813af98549f8cb475ac18e32aa62417862d385735af4bd843f435ee739317e5c0adffad88e5fa787418dd33ca4b545e75b75cfceb30bf11545381504ef651150e606e00ec210fe0227b45fc4669aee42f51f5a011c28602c5208adc5109587bda4966b19b97d64f91a76b08a366b8d357d9bb44a115862ea810ce4bf5282230842fde044d1879550b8ac0cfb5dca4627947b6d2c0415a56d01d6534834ff923fb7a04d949d358d463a5f0cec78c9f44ad870e92293a707619bc43176e4448facbc47a65b54d4c7eceb97319e0574ffcfc79ff811a41db9669bf2d63b2627bd5cdae254ec317409f9773382c3706725bb2a489daac5cf10a00180a661ebc02dd337f85a9810316c0075c173d0d50bf81b6c3d5ff0a1159077c5a56a50a2c1ec88581c1cb7d3b5591fd6ccb4f5455480bc59dd58a5d3e1df557a910934775307b6f9f0c785b062f774659470dd1ce2d059b87337d26d4a78811193b974296ed8b031f05e3a819fd8d0f7836b84a02f1986d2805f918e56a9119df0b6cc0b0c75c1322beee6d0ceb5a15115f080c29ddbbf027b49702d7b35a2971f87d75b4cac10ddb8da0687b5ea9bd68326c30f901264453ce8bd241c7dbf4f8967fd25f58b9d94037e67b5d09bbe6b64367b99d9ce7298d1e532f03b4301f89ba095c452439b24aa8baf0dcfd3478c6a2d30b6cbb3ce6db65db5fc487fb1d47e951db5617506e092abf06734d244f4266a52472adc07f0de2e58581493cfd5f5854c33ffccfda601a1a9ce10b3d8605e0303fb4448715820eeb24d999f2ceb82dbbf5bf0c57be6f2a00f20597e38f715b7762126b630d4a747b2b55d778d6b34e956d4e9dca1252bc3e5cdb17635c67381b7329ee1c22e2f98599a86531f2d0339ddac19cdd024eb63a4c37a5fe403099245bf3473f4167430b4163943f4b93bef6082e4f2a1906b06e778f383a7d4da702b1c1871db52d3e6c389decc373963a53153dd4bdbae5c4bf2f5e87883669840662d10134477ec1ae32c9a45d00cf7c34bd2c258016a1d4b8c647e0024cc4ec018e115d975f95da706149f7ad3d4549fc0f5c275fc9e7afe6ff39bac0489693a1bd4a57ba7e566d557f8496cc895b4efe234261fe1f519a5c8904543f4db6657b6af538295ba6bf761d600dc409fcbfc12f2f7ed5e83ce21a15512228068fb8454539fc10d80956049649b4a4a34ebe39f15c49200556640798bb1725cc355c61a4e19b47880e4080eac53658046b457278c2a50fc844118ec355c3794cb9134ae19aa2846168847707bbc8049f23ec269d79389d62c19356d93790c090e873090d145350b3725f30474298b8e13df8cce75fe736fdd9ff597e525083d5f10e89ff03fc7b0500abfb8ec40e717972385081b869d1f6503f23883c4e4e81b799648349dbb5936093c8e91970269fbef7c25a06a4c65992cc62f0b6f6b37539dfa193d41f069f55d24b959a8171032f11d4e80c524c171f72baf8428116075e8b0ecfdf19941f4968e339a73c51e6bdb5d8a1769753e04fb990bdea69bac48f99dd06b86413b811c89bd9fae304f57bb2524819e38911f20706ebe9609bb5db4903547edda05802816b72adf97052991fd02ee8a52c2af5833bdc90635bf6cbc473016461bcfbd8d23c5ea91f7c845094d2caed964dd81fa84c0365bf45cd1a4fb310d2baf92d7de3fb6ad67b3d0ac76841fc2657e75c2220520672c7fdd7b07c283cd8c6b4ce4efff751ffb616749a5c572bd77b956e0cfc37f38d3ffb8bbe3b845981af0431b5d62aa84f2b156d01912f465c538624fc401d7dea24f4d82538b8f17b6cce1642d8c9a409f81b526080035e0cc5a9a0e633f5610620f30183d80e9bcff9119e003a6954340dffede51cd032d5ee06769832fc4493af0e97d4361b1ff60fe962701d57e3495ed0e57ba453e3a08b2623c7bac66832cf0ad8ce9a56e359b547ca35d74098ac89541219aa447d368b3136a572cb7bb1a425a3d0e245b5319be360cf88bba0db18249f8be1c1e8cb62a523dfb8c49b5e6f883af47b0d72f540d61cfcd8ccc96667ae1f91c474cef8236a389a9d100d48ccc95ee0b823645f0c5c2456a98b34e351da828f7dc51d535695f4c5a8f201ff3c7796e968257806b2307d35c1b0235ca8c52c55ba3de6b0ba58668fef800999484f7516c7e1c41c69644e3acb772445510e96f9a21ad8d593179c5d3cbbb3c779ca5552c29ab3aa510a9fa295135e880749a391ca36c8d9875031f76db684ccb21601ffbeb8fc4b64d94681f912a09d0080ae50c3a8a08cdc99ceda4d5f5ff431a203fb80c3a0742da698bd663a4b9f757622d3eb6d0b66e3487fc0cb3479f12d1932cc62c7c2ed9cac3339cf1e56152b07513241d7207f8562c7d7ae33758f3185cf870b62232c763a93ec7e14d2e8314a611d947c04df369932ccc4869fb341d452dcd1f618e75b4b5251875ac9a70b19b7e31b6459eee263f81651d9c782cee8215268190da4e59b970d89c56205188266e37f099f9b6d427b78cab992c1d50f78c3eaef8566fca228e6c16a7fbbdce2b8e3c12449153b49db843ae5ea8bd45ff5e4eb876f492a0e15f6a0787b49f170c4717cb92988e42bece588e4ac24485f63e0b3e67acab7b7a1bc5ceb863d3267d05d0606430b280179c580ef468cd002d54c08d189a6c3e74019a299b7b30f81df90fc132fe5051bf1cc1704769dd980d4739e0d09243ee576771d7c90b10fbb26d004076a6d80535e90bc7ba484a7564c08acbf0eaabe6b9d85441f8146c0ee927116c33417f03f480c68d9cb500764ed62a1078d44109b59cc43596e1224a3bfe105223214a381a810c6c7bf5ebeb1b8d1ef008dd71dddc84f416a539d3f0f56c26c91598e3ed1d00d47db09cf0dd9ca4f1d2767864b4e72956d357e485cd0a6ea6923d928adad258cb8fca79c4e63415c6942ac2721a6de4811f7eb6c45990820ff7b9a3f4b1d0118f5d30ea73545ad2c7f8e97cd93fcd8e8572ce9c65371472b2d85ed9eb930b59eae0dd5d6ded3daf8909c759b73714bdddec987dcb9a181c354449503c165a8605db25c519a6a07b19c4f4be80fccc9b96fd00b1442d5493b925725d0e406380f5a61f47a4cc33bf77081a8536d635f6ed380827c292b9ef20fb2e486473709eb975f11522920759692bd42b35cd17a08278c076cf5646664de9a01650d225bdd8ac1a44103b40fe1ee82f5ad01c14919694ab60320512ad2a909995282e18486b031539f50e6081655e634be7e5506351396351448389cc8e8b32dec3dd40c8a6f67d2a710fa9b24bbe6e211790a6ffb60e8c41aa1fb8404e520af1d7f40bc2e0e374c6c98d959c1458357f3ff8d4fb7fcc1cf5cbb4bb0299706d1b3205320a1c2a283207b8776a11b900c16535375bc6e28cddb475f89afd25dd3c159ccfce8593b6fdb3989f5615f60a3054d8cbf863fbba8307ca1aabb7f469baff061141a6be60798c764be691b076d33dd6e3f6e2bb26d5373d4be3196f4bf12cb4104edfdd5b7e166c599c30b56c2322fbbb66c21e9161d143353aae68654d5605f87ba886d1ad7d39e9c48564cc98f4fe86854f81bbb232b996a332048e1fa3b6e6aafb4b740396ca46bfa38f675d6cda008b1d9e23f50031ecdf8e9d427887f8e419798931beb3df7a771963a5511c52f6f439e91048cb9345cc3a47cde3959aa837f98f022f78eaa726d1089b8026fcf2e42b267be6246af8d721fb986618693a15fcd0d4270a91b738f8e540f22221d622778bf20a99184cf6ddf264702c19d46fea7ad6225a18ff6c169344046ef932b062459fa9419a70af58ad979565759a6fe59dc63864ddc5d811b9e93b18c2c7f8549dfc0ba8c18a0ffc093386b8589743c223416473cbb3be5d16b90c8a9f170d1848df34b650747e5802227089052a02e11dbee52bcf723999e1ddc2a4ed7762d42321de8e067d3055e836877e1e8b768b58be8d0f1f6113f5fe434c4f12d39989f37b456a1f71fda15e6f6768b128c8ef4e6d548ccdaba6733ff6f97ad3b4677abd77684a0377c6fce86de6f08289dbd8475768ec2ec0e9ff5782f03ee945ae40f71e4f1ff03838f980cc6ea091e13a7905c269e8bdd7341725ad0ad9cdffa57564f235e391610b6f4c0b85cd3042ae39f17a6ddb2c34991461c56f98900db119e474710ddd7e1bd4cd7f4bf51503849f939c203d5c4926a8a49a9b27db98ff0af7cfc08251470c8e1cfca16490ef644cfcf3ac63bb3ba27ebeb2e9adfb48b24b3bcd0846f302762ef3212f33f59afe1dabae57a3925317237fe34b501526e451d5b8838cebd1b98b9919cacf9782288a1c3c9ed9c015237244bc855e9b9acf9d8dc9dcab85aed56dd71d3e15be15f12f3ebfb3878638a75e422411834fa08ca6a66e6aec30bccd0bf8e13bfa25303134b16cdc0c04ad579f8d7420728131efb0ac977aee8a4e8a2a79d68281f7d38dc4d13a37a8e1f60ca03b68cf98bcfa6d2e693b349ee2e735385cc0f57e9c0ed023aa8bdd9b4fc12671dfcb50484e8abef81f8f16d1a8c8b5c22ae6e3de1f84e4fca2299995a8d4df7b1d584745d7c48b6a8e153be6c533add27a3231e30fd33e3e6b9c0d52e07320a3085fd89c1f63c031014fc5394e83a08f3bc708f917d780b2ce929f8863a88d1f3f31118ea734bcb166944b7a5541dda35e256fca538b3869d6812819bd8001c3e7e01b0b79181b3d2afd45ecb6707921a0187e407574b7cf6e4ba8875bfc4d5a70b113b7a2143c3fb1afca6c92a5c8e522fbf389f0d0b4aff2c8c0536ee8481a59dc981e78706a2cd1e0831ab5ea239c109fe1f33800d13819e18d6650b494f503831f2b5c6157cc8d5546199b3e6ce12c31771928b550245f4da6f021e905273a3b5ddda9989ba9ce6cd4a45dc904e2965ee6c153412ec0a8923ecc66c320cd9b545af167ed88970e644222a2b13e185c9d4b3ee8cb1fd7430576ab1e1b85c6bee88fb2383d26e0d98d9a7e11faf90593594553d6e3189c6c2bc2d914f83dfa78254425542c421d622fcbae093f537d92c988a95098d41a99eddb7a1d32c987e208dfe7b00b43a400ab217e25ae8045776e34b7dbce26b514ecaf8f257680ae2198e20c72850ad6c28465204fcb80e257ff57beae6f33634afebb99b17fee3f062c240fe35bdbaca5a69bcf6e2465291becb26f0c14184df1ac6e5a26f8a2933b080eb4ea55b6043c820289803d0d304419944ed030afb369c5920ccc29a0e7ffb4b1139f89f88ec9015ac404149f11fcdc0f3a9f715ffaa9b8ef6436d01283ab8013b9f0ebe3e786f60f8c522375dc0782bf7bbf858388392bfc74db7a4f0cc8e75df010a55711f21d1dade7bcbbda59449caef099e098a09b4030e4c0dba0318f4b86fc7204d98a8a334b88b27e7fcf482cc0e4ca1cebbd187d6f05dd218847ccf351ec63eb6f6b1f9f868d094a9631ea45cbdc2a60f99ba6ac6e143a65ee6eaa9403fc8fca1ca966ad68ab38015db26d7e4e9f5e74d56040b2e93bf9f37190c209a376d7cc8556fa8b09d13e73750d8ded46240c00b543ede3cb57218b67ac828f22550b794bb9fb63744bc17328d9f36376a389a1b3452307982f3116c6c68bf4879aa36b438a4a21b281b420196f4751ea447757c85508f1eec1e2d589bcea34bad0b203841db6d6951e5c4920bcbdb5dd7757325572d8ffbd54b6cc5d2fbda5388e58cff66538e72524010b6a6290eab2ae378c96dc0e943bbf62c6a8ed39991814f47036d3f0f7cd692364893765695ada760adb5d6ca39b6ad300ca997214e5f8e56967f477076d13167fee868e0ab5358ce06bed209bbdc07bc492bb5b76fade40fb93a668dd124d2ab607bc8b4d6a69535eb9480211fa1e6a7cd4d106038a0210e9abc610989c9e64676c3d41413028430d9da4031210089ca471b1b277a0eb1ad8d12ef082826ebfcb4b5a1e16b927b7edad88461858d14900f3651d84e3a8d7c4fccc588b73f6d6c66f8d255cb015b8c2d959a27784a7d2af5a118727e9a0026c89a25585fce34dc12ed6d6c5a784a9d3cc55ef0a4d336abb4bce7b68306f056f676fc6e0d920e7332d03efcdbfadbf3b7f4c148fe7a2643eaafbb94fc752fa38bbf3eb178320054318babedcbd6d73a46ed6bd50b63b86c548c0795fa238f7665d06cb17d0b8531a52521b3ad1d31b46cd1f196030fc2b0f2f689b76994dea6694a93464d1362d02086156fbd67c5c33e65c17911411146121855bedd463e6a920317525096988c589961986c01c3182688a8e185d8ed0825e0e4a1de6330a27ca3f9f2edae158fce4aa4c135ecc4724547105513aa32689cde9e41f3d677563c2cd995cc770fdf13aabbbb69e02b0515938a2acca4c04c1829c0724687b73ecde0f0d65b2b1e96ac5f0c91c30c9a5b33a8135f86ad76fcc59b6fcf65b07c91c4b7ebac78b45f11e389165964b092056725cb96334852b0c316313b395b11523890e5928c512703854c18df9e6382312978ebd67ac64eb4702693c986e0d8b2e782d98107f38a0562b24cb93085c50bd21499314cc68811d3f4d6592b1eb64609c4ccdeba5dad78586c8414182ac539d65ec30b32be5a314874e1b236b76894174abedd6b77f5936fffa0ad10a5c9643222b1f6136e2b1ef5cb568f8f01bed431afbcc5e203f0d6711ef2d6ba2fbd75b28b29de3a500dd306262acc0e6f6d6150f0d63b2a3b801c174e7cbf517261092d573871a22156e6b77506293e80d800362e7a00f3ed35efb8c965a883dedebea54294d0f2611a5b44f10903de72053be7f82a613a763c1a8d60f600ee5806f9b27d051ef782d7762000998e9d84e97805fb1ea04f9da49e5d4d06ac3e7de8ca01ed75ce497dc6daa7ef9852e4a345b2c2a66fa05434ddc774a02128236c5e4d389f31ec835267c188ae4828f1bb44921c1287ba2d206eaafdc0df0e44fd46c5a697f969fb6943c192072910100a967e9ef969cb82c983363ca10af9a830ea18d3fa260461e1040cac40a85f347fa8ef4033ab42139830ea6398d0d0c8784a29a54d27cc1f4a9dce28d35293b32682b00084f38e930ac202f5133030c28cddd188c62875a9202c9c8001eca56ac0467d0a0540aa7d09362ac68d4634f62a811b41e7dcf86a52c50220d57eae8070def5c4d93991f40adb31c5891b4054698a4d2abe42a04d5e40dc8e23f0a377c01d5f408c6c54ccfa516c4abb09d7ed68f4f2185961b6dd84a10aebeef6221ff50a70fa800250d30dfafc7c66db00af1246f0ba4e634674c626ce952a226a8c40e28490d83ce2c1882190a2c02287375562d3295d89784e994ea98b06b63061d387fcf4223f8fcc2a34674cf367beaee3d7f5797b36bd99a67773b3919a68d974ffd07fb82a6c86e26b041ac33ba6642f6a5a2bc7711cc79529b440914e55d8f44fa45415369dce3c912655580aad150935864758552e5b06820d624809ac3cd7897f53687d259bf46972c6fc89ec3f9b4e99a653194d22c1c6a8acc26ca0b22a6892b400c40c653029e36b0853c585162da4886ada4a4a1732493fcbf8d965cc0c25d82a62af12ae57afe30838bfe3ab89f5195b01b9ce8d46afeca2362a3663affe41dd04273750fa41dd04ea453e80c41d53f20fea26502ff2e13293ab0eeef9bdedd44f4deb262826496d1dd4454c0ac58cb2438b132aa7a9258e28812186202d84ccc8281903cd2c877a636fcfdbed8a474b7962d3443ab6d3fc41937d727152a6cc1151415491431762da1cb181091cbe0c0115e52283fd5e7f62339a5061d729926ebaa49d7494bf48bae99276d2519a664c3b4de49d1d32155590da18e74c674da5a672cce415f0bfda8fbec71e69ec3afd32df42456db014a40a512e1376dd8a317fefbd146afedc59bf89a24b155074a1854decf6586209544d4d981faccc98a4907ede7edab450c288c630b043432e291447a3b0f320efc540855ddf39229714ea557d7e57e713329b164d7f6d5a3885321b172c487f6b779380fd8563af1f9e7ba3118de50d947009cc341559b3c6898b270ac870860c97304eb520305085f028b351b157092378b96218ef98e23a4a42cc932fcc243931839879051432a020328312b42f5624812aecfa8e29a405c090d1a1062eca04f14206a4b8b2660b106eb08cb9de891ed41168b9c28b530b5b940123769d4e55a10b2081c35412a726c86013bb4ea1aed3a8eb265cbf6067bbae76d57e3982ca5a6b81582d45e2072d72423b332acaa68326940dc9426571f313ca56e6465125ca9734cd771ceda64d1b165afe3a5dbade4b2e9d3dfa20b9d47a94ec0891a91117905a782cb035b9a783286d89e06a92d28795917bdaada5e0b4755656b533dabb4b9fef3105f24370d6f1fef4a15c378dd642369c5a8b1569eaec8a215fb693d5153d7cd94f56b0f9b2afacbe6cdb2cdb4b5b0ad690ce0e3c405e276f1d36832e1093b47e671566b91a6ab1e99dd84db35a2fd6d25a9aa4750a579ab45a92c84db38dad858aa50bc796adb75fa6be4f79bc4f55c8424d226ab34286b747319bbf022fb2f5a2be6dc919387b6ba57269a55e69a595565a69a595565a6975ab855ccad42fa55e69a595565a69a595565a69f58ed24a2b6d06f40a07700ae54ee2645f8117b93da90a35adad8b09d35afc30f9caaadf79a994f3cedaaf54ad46fd2269a1d934eace28d49dd1a73ba34e4d43482da53ba34b570bb994dbed0fa82a04649d4c4d49494545d96cb5a156aba7c7a7b4b496551895aab0f6a929aa1e6dd9b4a7499c8c13038b824b2dd6e2c441860f34aa588b946a0c07554011156bf1c524e6519e20a24d911809b292186eb1c748ac8a465489101c9c8891c06373b390614d9418663112d0d0218667ac8a46193c6d998a91c04b32b17013aba2514745c1a485180974c2c4f0ecb86bb175fc257d7be50b13c8622d7ab045ec8892a0bb8bd49e8ca4a7636c9cd27cdd690acbec407d5c7af8ea447cd9a3044d0f6dfe54bfa26da9f6d4546f40cd9fea1b5075778756855e691a56fdc5a609c949e9e9eb52edab23b17de5a1417d75fba26ab2f28839f387e32ebd33731bf8bef49b7dcfb0cd9f06a3020cc54d06a52c5fb2f7f326839030640303e9d3cae10c73191db6c2f0e74d29055ffad4aa28ed31cdf20029272a4e4db208c309342e484106111fd0d4f0a60c236a704fd42f5e130886016b191c55d840dcbe2b4074b9941652a4ef6e3a4ba28a6f77137c7b5114df7ec215df4e6561682011ab336abe582b5dae008b7073bb7946cd19ecf597a20f7c24a093784d383f6f4ab3c73f6f4a49989c445dfff486a21d746353510dac5b6f2926511d1b8cf9631d29770e4eda818e0c8c5ce9ad5d27b91bcb29055561d6738ba9c886aa426d73a67a97f5c6cac4db2aea30936d94266dd7d5da75e375ba4f38aa72799d5ac8761095b57e9deeb55da7fb842f188be7bdf7de3be7b5a30dc725f2f40040c973d4d1443b2b8c6c7b249728ad9aceb324bf9e3a493dbb7c641904544f1dbb7a6e4ab2a7e18b004e1f1c45e4e9e0ce955c9b44fd9c774e39f10584c315463bf105a4738fce55ce6cb2c7578fb9a549f72ae19b5ee25f61d52da250c7570844df4ef4ed28dcded2446645a2afe2dcc284e95b0c42f4d33ff1554238820f8baf12b8199b2b1340efc61790cef38e1b4df83ee7c617100e3b178eed5461d45def409f17eb620280a1d650d43b8a7ad7dad652b72d199eaca41de08495d6a4a8065626111d9b8bf9d3de4438b5c93cbd342c357fb6a022e27bccb7535cabb0663d0e37b9bcb41a6ec297869fe60fe7458d6bd8463ba8de8ecb9844504ca1fe62c2aa680775eca9f10302ca20031c385eaf19335a4a079a5cbdc44d4ecb4ddfdeca21e79f5b9c74f91277ad72decef425f9b88673ae71639555d8cf985ce237df493d3acc789d16e01cca9fd36e6badadd576df0f07193b52447571818b1921ded88022250725901051a2862f3b5772d95ffacb24ea7f0101c1f1052474d0c1d18885e347e30b08e82b5f8d462b07a9e388fd34cb25d0c415461d0794acc37154651d62ae30eaa0083341aeea3438c25035550b1540298818d5d30661fe4fe5fbafea56961cf9b902e17e019482f896fab2bf7c91e5fe52c4162e5c94b2343942060b381c0993030d424ca1c5a8030d51c7b1267b1502ea14c0a1a17e2ed34984bd558031babf0f8b5fb93a817aa8cabef66c204223c2649d9e7a8bca909aa4159c9e83989629b48aea87472a6b92264da82160867cf1af2906272f4364ff8d25d03711a92f2d139128bb24871779de862cf9d232292d3df5f996a983e62de9e9a95b241d44b34b5a68f230c9a5556a0310c92aac90dfea307fa8db2378e99394946ed8b2770582e75f5df1e038da752050bcfaa95cbd498c572090abeaa0adee7935002d7bdf58e676abd4413dd2a4a50ec2a3cba7059e2539741f59fe3e1ea41ead819af428b4770e7e636954f51fce5b1e1f726999aad0caa914c839967d478d5063abb1a32a2872582c5d65957aea9f98424df250f4608526670c526f999e3a759ad441b718a29e52cbd441f5732c7e58fc92be41ff7a889f7dce8baa1d833439cb70c6945c1ec98e5e074f0f795aa7b059618079a230c309740803056cbe300181335f348961e2a48b2ebe3863848c10244d4c64a4a727a2928af082456cfa340098a7d09cba21298a2ca40c518a21832a4c58c1c5ac4c952936b22c31051a318c5cd962819b35557811449a2732e601c03c8bc03ca7bbe6f439f1fc1b48a976dc2d960db96c91d513e0f2f1a3d54f4bd00ee6778d88eb9dbbf097ae0a8e650ae4473db716ea49a072d656d6e6385c811ff5f6dcfcd0973eded2eb2288e088162f9adeced7c0d0dbeb24c5648b985a0000055255d4ca43fb841647a0a5ace432dfdbd47a9fecc18b6bb95aa762e7dc01debc61f3d8cb233c7ae03ef5403f1d51b05e6f1f51abf5b45c3d3a38f0dbd372eda885b75cfa1f00e3307fc9dd5e7c147d4081da7d586bb313398aefb00553ca611ba9d0621cb20d2d8e809413b9ccd6f3fb1d2dfe9c86b3a97b8e02d7f5e0466cab1038b368b9cce50af53dfffc3ff7404f0bddb217e4d61329f53ea72b1edf58afed61c7b142014435d8616be50009b035459edf8222b76cd9628c318d1772254dd8e036062a561d08d7d96a5d9129934ca9ab8500798ce41e0f481fa010891b0c696e309479fa27f014c9a525eb0d02bc3836abe7d94e2877ed1dfdae1367ac1b5763b2ab0a85de6a2e746eb55a3908edab951fbd27862bb1ecc250fc1cf4a23efa6e85fe6ff46c9374d5d129ee9e40a431dc41378b2eba9a74b1a0b87868c8399e1dfbe79d6f364cfdbc2d95f902fcbc2d397997e77849821e3ab52b1e20cef812013b8e83801dc78fde134ba3f79c25aefce83d7185fed548b9a01a805e412faaa1689bfcc610fc7193dfe8238d92a953ceace6868033366badac21f2bc5ce77d843dec796e048e1e38aefc288647ccf2a398e7b30ab11c08e8acd168886ffc4620a0b39c351a81e351cc0bc559419086a018760b63aeeb3a0e7b1e65003931b6b8c72649ce894dc019febc5fac6c7e4965162997f9c3495455a05f65654df3ed354dad92512ba36e386861d16582214edd90cec9bf7ede64b75ffdbcc9a87801821c38c9d44bfa2f22ac8a02b069cf6943e7a777dcb5239d44768573c5096b9c6fafaf21eef3a759dff5415bab1a106c9b26fb3439451a0fd24b45f1c17959d5bb667b6bd33b2fcbbe42e8923a68ad9d1546af7bf68eb8c226753599265b5bc7b906042f11b0bbbb5e2f6abb33d65b1fc499f58e19a4e012c9d45d44ea1160ad5583202cc5f124e3789229b66a32755793d4aaa9b5d66aeb9c3f15066bb9ea9db5d6a78e41340082eb30c6def77918880e7bd85b92a92fa11db73a733def068238b4ab5df1e8b0e77d383838383820d8dd79ce89f3d5964c07c0943c6d48c76e1a34582c9c9c73727466b45a3468ececd4a8e1e25c2ed775b97466b45a04e1aad5e4eceeee6edad5ba5c5c721d694c4aa3eb462312b05a3834728dae5123d3c8d9d199c1c35379789a6787c7468d1a2e170f8f0d1baf574f8f8d232cb0c0e5e2e1b161e3f5eae9b971a3027751aca2288a3caf1b53b4312758e3e5ea693ac5320ca758ce3274e29e5508f6f1fcc93c5a20d01b2b117a2ab8114ed102718a2d529167883cdd330f71ec918ee3e458e5118eaf78547babdfee7e6f52145720d01f71b47c702e58f1e021f75831c00083c600038a010e1f9f0b2ec000030000802461301f9ff0020c2806175c70c1aa477ddc2406380058f198244952cb2a8fdeeac8a03320613932d0f113000d6030b771448e1b15e4c86181e7c89143b49123074f8e1c3910f0049c3e6537d563ceb9ea7342614067073e968a85157c024a28556295dc6b6f951f943851a244c90f4abc900b9076b597ebb0f781d97b8ae2643b88d29ca9439fe85393e410d608671582d599c119e1ac58a84262ada84ca6449398e8aa099d5127b46946ebae1084aa4523b4368526fde9538555e004fa54b9d14ff09d59e3031f50d3ca97f4a9898f264510306146d5982eaa0368a84b2ad2a7096b9ff469feb40f4fbf5a611cf6ac70136ff2603594c4655746ee19265f027d5d78c8210c1e96b00d3cb8e041c91e1b1d3288a66bca393f6f3b48755576884a92c19fb71db65c2b72f7f3b683137709679e1da6c8b003c6c9b543132334a26aec704391d6174d3b3b1099c145158d3274b8806a4d19c931f38587193033b21534642f643a5b9470c8d072aacc5864aa32122356535138487e500ac3d458488ec8008ea9ad9008b9e11b330bab62f0cad0c02f64c065aabe2a96ba2fa83c1da248e26878892297c798aa8e08cba38b280e08df74c081a9020d01713024a65661938d0e5e4c7571248c4c8a225349196797fb04c1d4420e37844866f8a0b2fd796b81880e2d94200d1bcf4cc63f6fb2344964ba3799fb799381c9f1f386a4830cdb4c3fd3263698db427de8c81a0f7a66021c17b63ca9820a9a11555ac43e31d4acd12266e9a685ca7220f29ddb0e2200eea6ac055f86f8cb11a44c935062662af3bcad8e99baf8ea9389962718247670e2c5912a605035fd5147af54a635294678ada724565d0e31be9db34864796b2d0dbaf6891b0e4ebedde205d850440f3330863c01e648072e45543de1c54c0b266254cb116f306daa42ae3a96f85d549e522b6463d4863718f99ee27683986f6fa32444603123c614267a8895407f5402284c9c481195248d1431eb449b2a8c42612d0740e4d2f5d4862a1b5ffb9ac210739cb7ac58027d99c2f7e4791e7ec2584a14294f2ad815ec6d9380f943adcb8531e71dc7715d11c7dd3b663cb1d892e8eecef3075b970b634a622c86b8c3d8e5c263d8e4ed2a57dd76d71f4d7626ad65e1337eef3bccd7aa30b71c3d1b10637a2fe62847db67938dc50df2d4a7c8f3445ec1da2d6c6da7d57a75b9aeb52bdcc775cec979d789a11583a3fc3def9bdefc3c07c1e779dff8dee8d7ef987b2fcf13fb1a81c66685e1eb9605f5d15e2194f46fcedff115c2ebfad101b0c8657ef1ad78002c72b64edf73ed909b73dc643ff7b9b5e1fd3e90e33eeea3d6fa5c71debd4e29a597f3aeabb6e370bd9e42ebef1820772fa594de79c5ae78cc2fa7014a4c2cec7db5fe24bb56ad17f54f215a5368bd4bd934e074ea2cccc98685eeb6b68ecd8325cf7176e5fc35028d814e82377ab649908566aab0aeaa304a65733611965c52aa3473a6d0c2d55961e4f6924e551a9e3af54a25f593ce3a2e67720f793afd79abe10bf8c5f54d864cf51c5bafe23ae6d872e7ab3ab2c64cd724eadcce4934bfdd7610eb4bb6dcb59575e7cf12997a6941e0f9cee7aaa8b995083c970148e09c44b98b5c7fde6ab8f2f4f5f356c30f4f6f46da3c9d44f4a7f28d9fb71a863c75d04edb3aac72856f4702169169fcbc19990a9b6e46aef8c2c8383f6f46a0f812e8399b9126b82319ffbc199992c5885308469a3a2336b860ab2a6c3ebe191162e44b54191c6660923ec8e2a98897d9075f6638bb3e29c21872eba7adcb097ca8613039fc69eb12054ceaf203909bccfdb475a1dd3519ffb47561eac2051408ded6a16300b64cb9cbd93b76d3f9a50169577bb9ee76d872433a3de9bd5c7fb5d1cd9a0745385f347f9432f6a2cc793769b3ee5874e924b245f6032957648b662d6872459368aec6507fb092187c00fe08e357f8ac3ffa1b08d6db6edb2da8a48e9f6a32755c0730459e60141014490cbe7ce9f070f90adffb79391a05ed7a391c2ee7e4ac42cf177b393a2c0f8360f819e5f0e4501d5bf18c165edd7beff57eb0d6c85aeb27f88c704ea256888d66a536ccad496367de11a8d67a2779af570faf4210c15bafb61bc91b827d9006a79382aef676d803c3152be7e8cc6871393a198705b6685cf05bb13e70b56285e02a041aa1fd39a708eef8941c08b9b5e08a87b53dfa819e64ea259038497bc103ec775d2520d6c01d74bd0fd049895a1d040484b1cf0e14bf495a67a9f089dc24ade35508f63f0f80dea4cdf2c63b7fac736f43f0d5297939ced2f20d24d5e39f4075a8d6fad28326bd61d469d862f53b6ab8af970dbef4f99b82bf6eef9d1df177a6c4df2234fc2577bc156ae9a00187afd5290ec0522e7d92f516c994eaed09a82b7938d7384245cfd731bed67a9be1881b0d31bc94255fbb7c75d5315febd7f925c6b14bc34a6e35e532db5a6bad53cf29a0b35e8680e9abdb9ea1459e5e5ada385e6ce87f3e055b4c64cfa9635c2f96efa6ac03fcbeeff3a20eaa53dfae03482a5faf07d04f77555c5723dc18e89ef83deebaaeeb3aaeefd48ebf23074dced022d7f9d362604bbb30c41dc518638c31c63e5b52e48e661d878e3d8c31c61893d83db143d2248e558b31c618638c31b621c61863c718638cb167b177d829c68e31c6187b517352185f7c1dfb2d3389e863171edf368fd53c2eaf99c71f0631873bdce10e77b8eb3a6adf7a8e3dec55d0ab18638cdbd2ae2bae5804ef55fde8abe747ef618c1d638cb1cf954e984cbda871a8c30eedbb16027d724e5403912bd3b08ef538d87061a61037ab30309388ce999aef9c93eaa0dcd4e63b8ee93b2f3933ef2d297247eb40e79c3a24524e524e524e52ddec85fde83b9ad821a1617550a4312cd2073ba62ac44d7533927477b9a46295841a03474e6ac6986c43742173b3ce3b9cbbd9779cd4fc996ab2f31639304eb4036e8ba941e75d17f3a7f33a969c11df2935d9cd9aec92e64fc74935d93927726048b831aeaac23a2f6a28b924af540795e1649844d6bb0be6bb3bf5ddf5fb54712ddb2e140876d851a0cc9feea9619d73454c0d3a9fe5517fb5b80a618c39ceb92823e7347f3aaf6048ae5f724edf757b1ffdabe3a4bef3ce39aaceb9aace39a9ceb9a9cea74f37a2b04303afbc2a1892bbe3bcc4f9673786b38c43da5b07977830bd0d95265152380b973a68f244c9ed208da95c825358defa16aa29de7a0956bd05b378eb65287bf0cafcb1427275100467f3c7963d5c5215ba9cac0a716e5df44255a13b5561d6f385d2f2654f395585423a96b88e535c8c1bc3a40ab351d93a277bebe0ac83acdb57ed2a81d30714c9e490c729f7bd4e238020061b5718b5549a9cd6e23c35393d478c4cb3b5384d15268034158c1ab6468ddab4c6a481591df8866a727ad300dc5001ed30e4192297df0c841bcf39c679c2a182d3548540c771faf9cd683fdd46c7686c257a1396b301cfc18ae74f38c93b59c0f61abdb538b349d4792d594d7c75560b4d91af1e8269a19dd56c124975d0ddb129d556484f014a3a4c2d206c058724394043401c0c5555d86ca24d0e4f4d7c5122d54492a9267ad0a1879359132f20e91144546d8a87592d8b921e4c6a352190f47822abd1d003081a1354497a08c1c48417244e4c3cf9e1072626663cf46892b484191f7a34412d21450f2292968092a4c710b52578b0a1c70f511690f1995962081a2c5730b18291a812442dd7f879ab5203b843ce3f6f55844c41050dc84576fdbc51b1020c93777edea89800f6f326a40440736ac80534b33502e420c242c7942f572421e596c419258e7cbb2bd1e5db492a4fbe9d0544c42cc3a7a80ca608e1d1b9bb6d7737162abe6b7c7777dbaeb466f252a85a6bf5a15a5d5f6badf556ebde8a87a5378b071554504e305192445923fb763fc2f6ed24100ddf3ea4c3b7174939f3ed3e64f8f61f48487dbb0948b8795ae2dbf154186043b1e1e7db718662e5db3b9719b0c70818bedb7136e2886f774121f2ed9c47d1f2ddf3dddd5dfb5a2fa8db5a3d3e3a3870eaa20826be3dbb9c041a9a028b299e54f17d23028ad30cdf5e5d59a868c289986f2b86c84f6ea0c20b21664c641922848c7aa6c9962db60083e444ac3bd60e81eee6f22dc5f6ddddddb5db2e4181401d423cd1e0dbf14d882cbedde544866f274cdf3b687c9e76d4291b30ac6095d42485f2d2dde83ab1a1a29c9a6c6d914bdb64422593cba64175108ef338f59ad4412ea75e953aa88653afb30eda71ead5cbf662c3cb7e6aa15e6a18f5eab9a3b0e92f9fbf9c7aee4ad3653aa586cd1b5eda3975dcda3aa82d15ebc546b135abc542d928fb45e7c68d1b376e2c9165707ec397540025b7acca396fd5903ba90a59a60af5d371ecebb88d1cef115f3a3e7f86bfc4dcf3f9aba7e7250446d14269eddbd6506ba27ccfebfbbeefebe1b9a5916e72699b9c74d08d717ce1f87c9673ce8d2f1cb78fe3158c2fec368a6d7212456c21db70f1153afdd03bf115fafc9583363cdc71def9c7b9df70cee7c4d3e7c60ddb5485ba671ccbdcb429af690ce0fcba0df125c24b041bceb90d9f8fbd3cc51d2796aee7388ee344ec9fc80112ccef460ad524f52bb6272aefa449eaf474c90c5a9e5f36ed8e1b2c61aa2dc04c619104161bde1c1d51c4cb9b29aa195c78a1913ac8c668937844faa549eaae1a228da24e43a4524db644ebd4a4689b66e8e4ecb065d13ac16189d649004956a275b24da0689d8469a27e65c8b752d95af5d37150e54c6697a5fa89e3a8025a011617b64048b4806343288268b3c157e7ac0842c9971eb27738b3f8b2d5748287358d01c495af6e5d6148254cadb5d69a66e08c5475f10515628eb450012b7c8061cd0b61242113731f55080aca542b57a11e1c40e8e8a01ec7e2cbf0bbf58316dfed38e9bb4917df9d1820cd62cbb4049972c09f03f97bece100fb0f54beb44e6523f9a70ea25c144fdd3a75d0bcfde0e4a9db281dd4964a8d7350fc5ae5e53c6793789e73ca83a3ff812429688640419b3350130b1750506dda2c29a18109893775906d2591973c5d8239cec5f992a55698bce472e55ca92a8c7ac7d4a4a565c91c12b7b419497269975e7883256c31b57df7dc73cf7de765d33a913e155500c6e4924ecdf879fb41cb736e83eaa967a6550c2f64fca5fdd9654a565d10c3e64beb54855ed46d53157a753e63d9b5815276d22b5515b2454c18f529b5aa0999bd3da5aa423356ab6449d57730e97eeaee26ff2abeb0d36ccddaac937d1a81c63ca72b8ec3188f25779b9aa46ead9397911b49cf3c20cfa9d353b7316fb43658279d2ba0b5d65a2027c5d43acec338bbfcfaf5f943eb12e844d6f97963f284cff8d34685915a657fc84e239ed28692c96436c4a8630ad69e027ce953ea809a57707c39bdd0a83ed3cc3a4bba84842ee1a1ca572a6a48c2c357f72468be3a497bf2d5815af8ea43455cbeba0fa9af372527d4f0d531d0f4b587325f9d851dbe3a952529f93a6f3ea8f9db122d4c96d04aef121d237a906244c89212c862659e630081e6c815538ac8a214e39098c2aab576d84161fe1a5544919cd0da1c05524f5345357f5c9cc861f1871b55108b9cf86af7fc286634e2d167e5381e3f5c3c5fb21df3fca977c63a2bb618eba80a7392a9d7cacd86aa10c9625d299637618d97ea4a35c8e17e7a2ce60ac3ce89779657e227be383f8ad55a85610fc51737fe40727a18f6a318fe516138f43c6cf7da71518561ffbed108a8c2b0371af568e41586cbeae43e63d8df8b1cfbb0c78d27348931506118fb8e25d95faada2a764f64a1c2308b8cfc02e27963afb50e7a9130634898265518f6d7ebc7e724ccd83796f079936fa44c1586bd5ea11dd0c76e67d5a93e592b846c01159b315a0417225c28d354d2a46bab305c9d1e4f6e46bb636381e69c33db19446bd580cabdd67b1b020f54e6cffc1078883261b6b2013b077b462711920e6aaf0e04f4dd532b0890d4b1d3247be20e917c456fd64526ac03c1731d4d768f9dcec4fed50972d9b39ec9963aa60e2a3b29a98356de792b7550e81d8d6a59077d47b1ee7581e0788e5fcf198d86b823909c1c37b28ee347b1ce7d96e49ecd1f1f213247bbf3ea0b4a9e0124432ed341ed9dd3a91cef9c4a51aacebb889129d413369b442c2722dab123478e71b4614347e7fbebe5c7ead9fce97c2536ad6715d67928760e8a9de3889de74ce99cd2a80efa1ce72794bcf3f339b8923d24589783875cd2272135f9ebfc13a95293484d76ae817923237b6f7cb97a5ce0cf263b7f79939d774eeba69e351394fc7d093963d5778ee32504a9b0ce338e7f7e4215cade395dbd46c8711cf7dcc8fa3796de0804c7f368541d673ca1c23a27754c22cfbb185e85fb2adc177b69fe7478ec2f72775032fe699d049a8412336272a0c02f6aadb596b5d64a6b596bf5a1b83b8b2d67efad96abb5eb5cab75d55abd6abf4c8476b59ebd1f8729f6728e934eb69debbaae92ee6eb177bdcf7e6005c3feaa0bc8dded6857acc9fa3e967b0d24c2416afb030a369d2358dbc75a7beff5796e7cae0ba968829f569e3e948f0f7048451c3f3e7d0ad4e4f49256b7966c4781bb0e02ce73ac4c667dfeab8e3f7ea88e14f470e7794f14cfd25a6bdbed74ebd65afba5b5d6da6bedad160710d65a6bf1dbd9852f25ec64a3575e3164ea969614480710a4d31010955acb82bad5bae5c6177f7e0351afada3ac1cc78de4cc2126cf2d4d66efeaa0b98589ecdb7bb6d0927ec67e7c3e1f48cb62603bcec162128e7dee89482f0cc5be117b128e81a3d77d62d250cc7350447a01c73c4f1a8a7d380cc32b965d8ed3cacf267e7e69bd7ef0bdd7711e08769cce0cb1b438de8e5927b43a1e86f2f101d80a3dc79d7b22de0919d07d07709e738b0237e2ce41803ddb6e058476ab971d7fe0b11b872a8cfb5b7d9aac628d708ae57d1e3ccb23a0db3948559490552e8e13254a94fca084d2a2daa8144d96a5412b95b2190000080315003028100c088522814024ce54552c1f14000c7a98407852990b846912c328c818638c318010620c010680d010d94428106a4a255c887fb0cf0ebb545c860ad3a8aa843968fa20f1c07730e5307ae4c04f208fee18cb16180ab9287cbcdf58368678dc43e092447eaed8450afddc60d9ae429f5825d5c02589fcfcb12b209d4f3da258e9fa248c5ca1e8cf12eb6d81965c97448ae4f513eac9fd837b04daf30a2e0974dbc548562469e446e390fab418ce0aa6e4e1553b57c4f6aa696926836b6d05ba2a33eb6c617115c6026c29bb15ff0e979a982d7e90c9f70af49303c2586a564ef49c551cfa9fe68dafc222f1b76181cff814823d4c30516847349e3dae3255344535835ebae7b68af8d41b277808e440ff283af4ef2455d34415bbfbcc453fe8f69d50e21c255577038dd0bef298b7816359a68e2f486a03e8fa97070bdc93e4fabc1fa5ae26cfa9b4605ce446bd51d58d9439484a8029b1a2cdf7a23574d7b9f09a194cd8fd14de73357d9a3abc9bbd46026a184474d88f156467b4b4cbb4d2224259e1388fb1f12b151a14d200aa1caacddec06b719ed16bf8a2f75bf8e0b386a27ae21cb13ceec03365bc6a4a9fab2ebb64e9473c069bca99472ce7057d7631d517e61191c6a3362ae0cf42b32a327f188025f22b5e716d11397b16e2bfa5798db03c2405ae5afabd44a732ed2849e42ead75e5579ff675edfe7abdbc80c8190919972d1b52ab098451968cd5fa6812f19a9596dc2f2c420853140a8b942203eed13bfb58399636335d215c79aa020c2d8efb35596918a2367aeb896ee71f492bf491d5ec67ba1ff0a6758883e41cf7be05ccc2eeb1a7e185b395127d1996c1cbeff998f0a1c0fa5617d9782cf1de44cda76c7e75bc586f650f2638e10ac9aa5a2b026328cd0f7729b0b84e356a644688d01b3ab8fbc3201bacd876aee39cf1c23211bcfd7e06677cd1bd35f038b11ed42144254fed530378b117c14d05a65b070f15a8c0872bdbd1b8d8a0412fb4fcf6d75d855c92b1d51f72110d854e16e426d05a7bfd878dc8ceced32ffd39c4aca5bad1774880213f695bd0aa8a2f2f9ce8e2e0500ded7e1d0580de6d619a8fd25102550876156951909e3c2e40c4f072d3a2842dac57bcb833e36e87d1bd95e2544df25c25b2e0ea02b38abe31fa0933758d2ec5959c0136b658ea36ee29498b7d266976f92e7d0c00636e2835672f36b2bbb190a2fac46be3289d22fcd4b251a5218845eb8f9388e69c9a20c439de733711c3587d194884902e5ad0af330e52132c7df40548fd36e294cece5932a939daebbee47f2ac3d26cc925182afa87de7385813daa1a5047cf070726ab44e009263b42c11cf98bf443e7e418574b65d1c4e77cedfa31fd3bd706149c4d951559aba57b591a3cda9de7e8205e89c31ca4a3b3c4952e844b43a8e0f3f7f8bf1700efb599e150a786cdba8e72ced6d3e7816e5a614536c0a1eec344cfe6d6000a8a313c7beed013a2f8d7209a1d0dd5d1b98f5c9fd8943ed5bf602533cdee82c44408d1e8ec5bc2ea3084ef613e9f743aa6bb2bfbfd1acb3f5493b8b308b22ec721392a83ed725186a06c66f3b451dd3558a8c81016e18275a4405883f432b5726b32d20af3f43e93915f9220c81ba258ddf7c5eaa38a7ba26a549cc643f6034674d7b3323693d265a83a885a6f784b5eb801ebd8db6f4cce00c8281af8f81a5f6f2f0984b15fb695053bd6d8a205e3b02c138fee08db5035f1627302d60962008252daa3274bba62890f0be715705d9da047d9471a3375a005930e7a303425e608bd6557dc41270295a06882fb0eb2a03c68b658ac4a3bda0814c1f54d85e59b84b6e2e08874e126a54ce0cc887405f13da70aa9e4895ab8be435a5d83a39695dde913c5ea226304b591f75d1b00fd1380e3585fc6d201e072e724ea1cd4c1c6435d9c7a86e02b13c0f08ec1141e277b3cb4b4cb50f832c2c439e975f408f33afa9afde3d528ef3ab631859997ca145220de0c3ed0060c4afd6c97ce9f491ffe3adeed84b99fdc4ed02773c55265a36a09536ae808d6f55053211357bc01bd3887bb215617f9dd6eb0cfd96ec8f6e216abdd10308a9664fd54d6d0f29c6bbeb34bc24074f72494a9cdb3620be99d176d073ddf79c78202058b11303a8743573c365ca5af9bd0c85e4ecfe19dab1aca1d11ef8cc7c43bc79de29de5f51406c51b5daf096c26264e7f32a311bcd00cbc6183b25a8c2de26059438ad2d09e0f4399279877525d37d5b6009f66c16b6804c2e2db79c4e8dd2052d48a4b2a4b5646a44ac5179d045f3f7572dd0b7641d7626e18c4ada231762935923b64759287f892aa5895066d6b63045547c531b0b923268f6d9847c78166c4a6cb8f529ea6804ed1cd960207072d850a13424eb0845526573cca80cbb68e18d0175ccbf3ff8cc980f857b4ac754ff9f200e081526538991d42c42adeb26cb71aa8ba76d1de9288412c6b99d48e305ddfce5af619d60a074b19697dfd50dccb86f245032c01257a31ffe4088a84611f2ef6063a5f5a9b4b5720b8910ab7de49a0989f46dd4396c95101db1401175b8ae5658551b9f295a1f8ad298fcaef33f4e63ead7e1c6232baf748be38632bc32379af2393910ed96aef68e7eb62de52f8d67ba086388dbbbabe5c385827604b3b81e9845f396365c28c75aa6bfac805826fae06ee67fd87e356607db2f90e5eb562997fb0c24a63913a6d8b32f2dc1ce135640d024e2a5aad6d96191f1110f9cc3fc6cfa832b2f3a44a206bb8e14dd8a03065e5e444c673c3ba8eeccb9e6a712ed8c0a105690f344077dddc4dc55f7bb09360d13fe63e833c995b588e756e0121099e0703bd1b3038d3e0d7507bdd2bb86541d9bedbf33f773be7a18f12a6030d5a07a2fbd9d923df4ccdfc740e5cc27341f539803a8ed52a93278ca02f98a3420b825adeaaa6e1935c1685690c2c5bbc88076fa66813d970e4512f10f3b7dc4f3f0e76c2dcafc8d5c20de3ec9a3e118dc76192c0350b4a600f1f8fd5116ef14ddc52fd38db8c6b744bf3bcc9533a6f76064497e960c715f1c841edec7b15d82f641e1dc4ea3a0771a1d4023fd016aac9f189a5ca8829d15eb4f2529fb59784b31b8cca083a249ae1e4a304d41570cd47852a375f1e4185d08d9a6f392ba1922fa9a78a330fd44fd2b53b25c0c1918fd85268da0011b74239650694d3e401770bde933349642206bc41ae7ed5501549d6af20887f301b3c9d9bf0f978f0da9bfe4b0329a4f4a6164eaa1ec7d2f340ef50cd5b0c56057f89d1f38092c8c40bb848d3722ca1dc4414bf21932872906824161ef65019a30c2ddbe1236cce7c9541c409508efb9bf1131b065643314212e9595a04c0f720e603c3153e6df4ae8dfbb1c0f7e61edcafdbd7def87547118eb414fb341ceb68ea6572b907c9f8753400454e4bec2a4a89a72dd70825eedc4062f3a990187785189b291c24de61c4a52894268c0a286a166851ea96cf602c6697e72c3a42d0413a459e2bf90d7bfd35a8654fc87c724eb17f5a8331e02c92c45ced17092deb6a757d1b7e172ad71ffb87c62a5f2193099900369c2e78db59c1b870a8f818d16600091a66b0e28d612bc32b2d710a23b8d744df730a8e5038acaa235b9b74678e7ae364d7202c2acc8d2cf7da5f0af7aa498d2bd04c618e621683a3c3a9be0893414e94c9e065326979776fb81f36e0c3af613951e8225c34e2011fd3c411d4c3b4ce9776579b1a9788a24b095a5712548246d7a0c13a722e0a4a80efab9e0d5d23560c43e7c3c1869cc14217c555c10603ba826b026dd8cc66faa70828b5318371213e878b4f49cb291641251a7083f2d855690852c8b8c905ea85dd1514e31bcf12848135c36e69a111dd3f6fd8f776678563ceb71367a8bbfde26685ba1ce83177ea59dc76b535c82a1e93d4e7dd2f576945b5224fa4f22912d79eb94189c628b71cd96169890734507ac7e865eb0392374d2eaff0070dadf123cec2dcef5334c9c16eb9fc161f53f92a2bebe2ff27749c06e27da799aa9a18061ff02ea69e1c0ead2cded3f700467ce2e49894eaf995112668d988ebceb20435101faf5329be16def2516afdbfc3b0f9a1d7cb393b1a1067aec5585b00a070ed91084bda79ec10812d7dba19a924761c08b3d748422dc961d53268f4bd887a17bc6f9fd572836bc2c54b58e4eae4b1b0feb996a0ed1f7691e1a0ebfd5c9139ccaf7f185033d382715b0dd66f5240c3a751da1dcfbe12da78be81c00b86f0b89f73f2f5dc9a04c25f830553eb8e0954e6aada2dc1b8ad7cab2e157d71b46171eb3b664657519c67dd2196aff766caab64fc788e580606b93ebc5832ef29076d78223f9ba48e0b519e4187f3a4ac124b5c4b7631a9238f7731901799e7e56dcf1317e48cd826307ae254b3d9aafce3ec7e6b3b13f75e110e302a43a6ed8b9992ef1844f34e5c42d7df322dd7af62c439fea08592753218b22a2b5173bac46956aacdfa14efea3f14bb8df39bd1a814ceef2d3bf4cd0f5b05b672f39f90212a2780e754eb6cac5eeddbf46f21bdb75f46c1905ebea3c3084c5fe41802dbe1897bbcc17c1113fe4e351be7069b19074c8da9943768530259f8c21e2f31c23fdc3c09a5d380654a7c00a5624976f62309efe2e1903188b2a30cc19f9a8c7803ca4cd2314b89435022f750701bb4dc010d4189645af599b9980a80c87f4a83fe230c98b5d6c4ba02a92ed6dbd51abea17c0f8dba0b78fbb11befc3d0b18b2dff8b995d7ea498cf398e1c002a80d148977b0612e4a0cf76671404a05ff0bbf1dacb2453eebec041662b2671d3dd19d15220633149699a0dc78ad5c78594d3d5638eab996734f358060099eac2890d8daad07e92a96599548da104bade2f92a62efab17c4d802ecec47a50d74f056d3fc7e48ed4b217c878624df6ba54985829ef36ecfe2811296d8c29dc806193b52cdbd6e675d4cc6fcff5f1f62e244dc70767e1fea5559c57ea569b4fc168f051e0ff7856beb4c4dce5ffa8ec33b10312d4e14c1b57120768336719e4d9376e055cba2405dc9909d2c2fede9749bbe4c6cd551c622533cdc47afd68761451cffd612521ae5668e56c7cd326efd367499667899b4202c5e0d48859a9a8d358cdb7bb6140427f1443e4c60beaa5e58ba1e6c67e6546ebd7ea9191710f4c8ed47eadde5f954cebf83a2dd8942d0573823c2a2f8c364741cb848eeb6876d6c787a5ef3fbe849a5d4f1647e8f1de6e5e4359382ccec07d1a98799a6e4aacc8bad91d9ecc60a6f0b543139c74dc8e272495118bffdb6371dd6c256859b092b282609ebbdeb18ae3044e593a9fd2d29b278c8569536999f6f0e8d6c45c46433d906daa315d7d30be1692d0a2f81ae0d18cd8d7937adc8c4d62a35eb32a665564f73f3b99bd803f88ba497ea4e1354194eac2241617b82de3c7d948898e106bfef06b91410db91162806291c080f215c99d4ba5007f5c6d4d9d6ccf2bc16cc465607b51acdd096b8abfe654df22568daf7e29486c8aa80eae8c6e13b809b6df11311f4cfdd282640ad37baea58fcafbd106e20990b2497888892d34f6808382f11b465bc013b025d16f7f6e65942c80bc8e98d744bfe9e547fa996038dc5aced740b0b6c7004550c494f104779c1c116e59688724231acce1d5bb87042847faa62deac005ecaa153e815c263946bf6a22c41c98d4ed0e34b0f731b08557cd315d8950d62f933da82c95a46fb67bd95d43723ed792c21c0fc09b584d05a2f629dc8a54bd279af7057ad5e3bef80d86ce24091431b9a51707d639509632958ec9368dd93ffc562e00dc9a0f30e0246041f85311d28848fd41c162f3b93ec8c1f473b42991c8e117b87cc259725422a7e84eef33bce0f213ded72ee3e8df65723f163f277f52f4afe03c7773149058d73600d2b279c9f0a1b5866a70ae7f1740f363e5567aec7ca74d83f85ac3216cfadd6b73f4e53533354148831dbdf724be86d1bb2c8c49d59d8d4036b7f61c6ff42578220ac589abf7876bd32025d0e2ac0bfd2406c8ec8c5a81a9d13fcc296d7b1d55ccc5b671eaa9f4eff66b0ed6049055c736f5ffed5e0800b1e7728a96fa2d7db19885972a43d8ee17280d6d531308640f2ea35a66e66895494ff719f03b676a5d406ad3017d177c253c576cd16784a475e7f84e74fddd17414758195a1e75e21c0ff0ff0f1c41bb83f1b0ebacc36ddcc1725353a062a1efb093d862ef035a6d56f2f489280f86932dc81dfd4670c35612eb263c97351f1b8a9b50be7b293f8adf07e3c4549b8fcba031d9060b966c52d6ef19f126509e587f808d6bdf208f2444cf0553605097420a70f3def96992806eebf3e4bd063d5c4ff88aa58be2be3598dbb20fb88a4b7650aeec982a6dde9554d08542522989a450c4ae002047d78ceb4ffcdb10b930ad76746b0d105838e44bfc167fcc757285d29b0d41af7b117705c5bc0ac18f79a0cc03081616df43d80ddefbcddca7a29fc0f382c6988cb9f91e28821044f456ddb18755f3b6946a7f5b485ed6e01f4365983c3c8331d3970be5897b52f8c56490c4a3f313013e71338f9b82d56e58246b69dc8a1e76b36d4387769ce01f5bce79c5e3991c4550051dafbc81773cfec5f17a5b10b6a2e467cd51ab72954bffc76194bc896a40449a6dd018d0e7b94ac835233a3f222b9ae0827140e66ccdd723b19b0d0c1a69885549d2543a424e017c63227121fc5fc82bcf42451f0828fb44a59e25fdd4164779b76d41b997ebb4c4f526b84a34b7bf36aaba28be9cf529dde84fc85a6e0c8251830ae74b903579368182263d446a1cab81e33a45c585651b90d6e77c5526679ff4adba319535d53327b667fc272f2d5442100357da7d6ebf2a3623f059b0812abd98b4d3c713085e6bab22759ac683af9789b8cef20dc7d0ae9d457dde0c667b6e093570899c1b58498e8924751074b7002f2c04f8aa6974c427f2af4f2b6c5ef2aaaaf7d2f04cfa1b6cce7f8f834d7a694a6c3be7372171f18fb973778ec37c2673613adec0811a74fdfd78bd683c81148b1728d6e040a6b909921d36a2420a45402d62fe372bc2eda5527a5e661115d93f8f88527fe6f0ca5a2eba062c1d5d1aff6f001cb05493dafbca39ad28ab50f96442645d424afa3e6ca88af0e5ae403351ab36fa1c1d4413810b491479bd2d28d4e5cd31ac3b1ecb9c6269704af944c8e198f26b336d9108b7edd904c26fca24a908f16f068308dd884261835805ddea978e68adb3850b3c66e5cab0fb422ff3035dbb12ffa1b88480aeae302fecb7d2f92847389a90cf1f6b27af76aa09ebe627ed56633273b9feb945ab049c8004214658834c0e6dfbdb15b55242b7822da763083025fe30bf92709873df1139e1d4579206539ac0a9a9b9eb9905688346f18804c57aa5773ca4ebefe8a996e9a0a6bbe84c83d013e4fbd2d1109a30bf9000fcca8a258a707c7ab56c24698fedb5a12e0cea2db19ac57f9af9e3f5eb2e269a5d5a813913b922a3426312cee950bff0b8a0109e7f3656f315c3711581f0bd2723888e55a9ba6a0c599c184bf89af7685270e72b3ab38b8ce5508a85d4f78fca8558c4718ede1e6e797648dbeca36c68f6824d5c0a01430201bbd570dbeade7a3ed7c61f1ce384aa39997369aa1d769338b88e05fdd0fca7f87598b85141eaf38eb76607d9635c79b0432eb45ed969b5042e5c4e325c7543e7ee24ff5498c40a1f8b43a9baa87c9a9c67667c731b8958f606a98dd6c2b687c980f3341a74cb85de1711ae954e2cbf26bacb09b7e6270e0ee1d183107e55c436a689f62f6f5c8987c944d0ae53b7ec722e93c4aeb3ca0c7c504ba7c9947c2ccb3a5a5819983281d0ec5626f3b854bb94fdeee735915a3b6d36cc4d27a78e9e896926e8b29396397d22b78e7726e699a8d38e5a36664daf76076a264c4d21b74e9e894926e8b0d36562d2446aecb0b93037a59c3addb99805d3cd62ed28815ed2c4dce469d1590a5e2922dfa74d9d4805797754679a269157ac82e97af9b9f638332813ebd5adeac45864963a27eca43bfbd99c2674104be95f1bc7b985c1942297171cfd37757b13b30bd846a186fe146e11d96f86689e41731ea129ec05f28ca8fefaf02b422b41c7b63ff212d4e8f035d715d0c067ae18a74584eb641840dc0786553319404d7165b00423f939adc327a2d3a10249237b7c446e45db03cc0b43d486eb4494c908f7114151163655185ba17f446e652e44d21968f5d02b6142ce2b3e0a29a2a3c926cf65d0190123737ca396a22f30e2bfc7e1ba8c8cc4eda0962e6c9550b0219d14d52ce07a6f583d71e72cdf5ac84c37a205ec55d4ec27d62257786844bea5ce5a2a5ae1b94dfba998a9474fe838e547aefcd2098d03437c8ffc9dfdd2f64fda2495cd86a060e9b17fc3ccee8ade3e52e8c0557811eaef249fa9e8e0d8bed2511eacbcdad200e9cd71bf05f195f7514d8cc2114b53f02d570928f841e2592ee460c5280f4631ab7fd689854eebe8c38cc452421e6466ece862671b2d1e6f5119858adb634993f9186239934897845bcd08ecf4030e771c411a9d22a74119a27c032bfbe0fb0158ec5fec85e6ada58fca550f042b4427238728309ce25280e840b2ee27a478b215ff297d30403d9bc3fbc883a5a17342b1eb1439b2f7f45fde105660a77a7a577b6b579d97aace681b06f6538882823022b2e0cf822855ff5aa2d806e3f3342d118e3a8bc96b65728ef655605fdba1428e8638dd63839c9d08940fb4daeee52c2833773aac613c1a9cf1642f663d6898ba258ae45a0764b8aa91db971da8fff79fe80517698cffb11d90b18b00a70de10585f5d604dcca4eb000db4f8d5f06ad0c4545bb099e154d452ebc9f59e9396c2a3b0d074a67f03d8fc18ab21aa4722f58e0dc10d34f3390639cfe0df26cda27acfddf772e6df4fc241386148437efc4d9623767565d93280ec3d946043e4e8807a8dc207a6c913a323780521163117274a0ba22556f213b183f1d71170734d1456425d1521b4c0ed8a447bdbfa82c5eaa98c188c8c07b7886adaea4dedb1d217c3305e5984d2a5c11846d6faedf58094302cc593011ed9d7b9952f57758d5d778dea0b90bef10084bd16c6a363aefbe0b669bade302c24f8e1d1a367a34d13f00fb7f8a0ab1c67891a8ad359470ac3760350eea386d5e7360ca5023f72d703c3329595664e6fbae6ece273bee67e50e2257e691f82b69d71f67366130e9c41a664440cbcb5e444b59e9c553f8fd5c72ad2bb7145a37237cd9a48e576cb589f8e240d61cef0a303562c493ef0b3a3fb1daf1af2b43b5dfbc545b40ac3423a967df43907ac66c59508bcbae8cb199505200becfb88360105fdb449f2d651c0e6a165f6f53bf9b6001aec9e323550b87c9d31beee459e72ccd09c2f134188d90c5cd1bf41bc66fcc721050990818dfade288b62ff14faee854abe3dd997d9e8836f23cf4b79837e42cfa78613ec29d5c19e9dda41bfa8ff609fd3da1bf999bc511cf6e6eaa2c70dc0d20a3c254c7438cc80bba4e81ca45be86e2922f5758704068913c31c9648b9e3d11ba2e78ad06aa9af6478974ce4b83ad3c1e9195c70a16387d7759c39bc6869b9385ca0cdb8aeb0c8ee509f3c71a2996a7911fac45aa5892cb0de5aeb3d937e4b55724ae29cc3b654f163deb9cd6c3724794506e644bd6ff223a0985f1e25427ef586393de000141d5d4b293192b2338d52f23ee2b9c5067eb5de4f2977a078b9d712e6a210625c8809755bf13462bd0a7a69b812d42ebc50913619c22af86a5e94fb5e00a5491a63e8e2183706b30fc68939ddd123caf387ccd53a758300b4de2ec89fc97a91abf44aba456dd7309be74594d5bb6004bc82272f8dc2219c522039e822e4fe54bc1fe89588b4fae7825aeed4dfd790aa73bc2ef0e90562571c6f9b351d8f17784894692c0e812634f4381fd143f800bf71d45570474de61549bda0f2e22e792d8a3779807fe7f1aaad9ec22310ca23805b45dcf62f43ca8f0cbb3d539daca4d8539deefd97e22ca93296dc9d4d9ff268fb43395216192a8eb8716bf82f0a24686561600e5df760f6a967218a9b4129be9a2775896a20a312dc59cb69d9d0b2048939ad3214692990cf41ffb4d0ee0b61d00b71980592403a0cc647201aee8b12a18d96eff421373432a35b9d820555dcd2a60f8ab1ec4ce8039b5e8f5f0c9ee9bb8eced84e260c02f1fedf6920e51431a30ace0f911a09c04e5fd2fb567eaf9014dde998bd4666e6b08fbd406667a7fe852a3b4218ee7524ac5889ae18bc5d8fe219c554f826a117222617fbc5e81c2663643a0381a58510847e75adb7e4b95a2acde6bacb72793af71ec60022b68a68f062fe42a834451c97b5351548088a2b7c8cb9ac40455321977d4a2c1720bddeaa32f05bb07828755acd6f75aca2fce53704a8d12ef6c171d8f9fab2b09fefb2f6c868f3999f199a3dcb55350bb6400a6e2b9f744a944479f8a6783f6bda75553c6d50a104780180e4826bfd1ed3e52abf172dd10bd4b0d90b90771f331f0904c682a41cb06625c721156b9f3746b56ccb01d78bf62a0e92b696ea0f4e0f9344aa8535aeebcb9dc23f9884bbc01e9b9c2a1e01e1ff2cd2628a21f3cdeaae31feec357d78d00127e2b275b74856dfdf22debad18a625d8599aa5fd8578584d4388722f3a16fcbff1f432037acf75600fca25e2df587032ce9246c85cbaf0c976a5605db108463339275ada0be1aa98c8e59c57d513b3e22903a0664b33d58298546353d9ea058b4bdf4a24d8f152986703727fcf4c9e6ff17108c3af1eb4bdd6309f13afc2035331b242d01c22ceecb33c7d323875271b945cb29aa037231aac0c563c162f57685d894c4be16502bcd99605f54c86eb0c20c9c2ff2a3802797f811e38e4baf451c58ba106a1235118f31ea60ccf0c8a2513a0d52596969b5195ddc574dd13b91e88a3883902491c850a1f5121f5b5858c93379563fdcea0fe4826b73ae8bd69a3c37af0029034df87ff1155b5960967d1b4c53ea6c51c9db23b1a70dbd3d18e6910aa80b26867e5328129b0bfc0ef86d9815ce756c0a6f766f6296fb1ec6c730411b6c74076ff6cb7ef416814f49aff67fd956efe8ba4a65f4222dd4d4588e91a6b61ebfc83870b801b5cbb6cb65c57606c8352dfba89865dd82b6a11afce1c1f6353324919766163a90f6137d71521dd6000c28854069c4cd8b6302e438abb0dcfed13d2781e45c2b411c00a5905b939273d291c2b2f3389b9571f0f54ebebb0349e32032c4737a7c9bbf23d5c43e802c272dcca9c1e382396fd682b77916dc2d2c8aa6d9a941e769b60981ecba0a76ae15c6fa56e01abd322e0abe1d894593decef5f23a61cca2d8d80570b18a5dbbdabd663cd726649d9dee089cf52e0b563838e61b588dddce8553b6a473ce1279476fc18f9fa680c583dc4c3407c14ee3bcd27de1ec5c7b4ec093c24b36ed0495e281554f0a2934c3e301f3f7fcb841960949fa88fd5449f228d303831f6d6dcfd9d94ae035adc03ecc726a2cc12b2376696728f50a5cebe99cd15fea6323800a0613b1f186494dee0d1d4dbdb0e464896de94ca58a6e44d6fdf503a3c5b0c83ea67448a80098ce35809b4efe4436bc64c15c5cc1565100610e8750442265dba381b38a29af159bb5f3bd4a2a268c01881a921daa84181796f7cd2531e46b802969534274dbd134ae91a0f13358d43b97a3c3e7a3c59a07dc2f60f6d72bef582dc602f932bed13d5fe00d9d52763550cb9c474336b53fc287517e81aa8d1a10c9f91f5a53dfd947a89bbec837a811eee62ac7c89206a8edc4721ffb4049f5d03469781050372610f1a337709799bd6fdc5f5c015e86ddcc7fc618f2cb222dc7fab5fd4225417b5d797eb2b891f143909870282f2e8613122aab2ed1bd36c0a17742eebf4e0734fab6b09f93f5b06438b8dc74e24b0581cb9a78ab19bb068a8e5c7d4fa7937dde898f586db2bd425e001aa91058c3d5cd49d3db05867df997655d8176e37286b0247eed9855f39d8ebd26a0463f5694afd68ca8f955a409fa22c96f8c3b77184d9b6eb09ed204b6d59eb5762d48bad9dde94b89875983af96cebfb2041f0ccac300c78146ead2d462ceda09c235187c4be385f6bf716f1762d60bbd4e81a625f7611b5e2a7a7abfca0ad36e61c17158ee47d7198201cd49dbebe1419653d21709ec01dda4a69ea78780c56c8ca6c24e10993bd04a0d258e42ecb0a280b83d8b74e315130aba6a4a064cca4dfe373612c15e4befac27472324a2ab2ea59152162182b17f627ceef0a84dd13ce2fa11b8c0996858d50bb048b28156290654eabf39fa2af7eaada34180755914f20c6d5f0fda33909cf19e177fb54ff32803ea1bf6e928181b884f7395ebec20943ef1da1be1824079da31f82ce92109a201b674de763845cce53bd52706ac1bc3b08df0abf7eb2b5ed1c6689b587bc59db0e827cf64fee229afbbbe13600373a424b42f84eb405590f5a009f1ff59eff78347aaa43db7139093bb58329327d29c073f27630a3d32010e558201654dc2be09009286a4f0c1efa46727f9e33bc8e1e298d3873c3fa6bb3d44824dae5b18bce37de0a4e17dfa2767aaf182079712005702470817efd0f8baa75ba5debdc40d940dc0c99dfbae0e8da094e4b02fea6245cd7027140df3ff0c0b40196a2a0f429d27eeb5eb6c5fd00a2a2e28f22fdce21daa63e61f6392a5c2a285c53a2d28c2d1d6ebb89d8330be4b82dcce9f5abacadb320b6c0d18060d39d68a59b12cf74a9c99f62d8034700c91de30dfc5b73f0c526f70d09e259bf705302db20eb9c57e6b9823ca14de5898bcd7d08d77246670cf2860719669243d2e170d411567b9e8e99e60540152fc280eb8488def9733a7da4226ae1f0e74976f8381b47c49c9bda764f3fc077a138bdbb648a1d6785a28c75debb9e5384636ab330babe919b058e0d05eceb707b860bd116954ef7275dca6406935690dc6557e4501b485d3e09aed589cc15c18b8e1d6067e288af2d8c0e230f6431e1d0dd43f544e0de39cd221f0cd8d247c52de9768c266f2fcc8587c7d12beadad04a4fa58298462d85355a12d6c775f1187b88960609789e40da5fb00956cefb3fb3d0de3b720f9de840837184a0248249579eb6d9c0b387ca3772b6544a96e799129a8c0602421707c856439fd3bb15de65cfcaf161b6445059a6323593bec418a15166aa95d74e29930aa9322463a88fe533b524614517ba14b7da9ed89b76f832232d5f189a4d18563eb1f63182f6b463ccbc35f12c01849ff1865d78f3a2f1532f84bea41637e826c4ab223c7a60b9ef4efcce2435899581d17c4f699b85dbe845f10f97c06923ff5d19e5fc7b6d2e1a1e7f60ed35f81b175a351045473a201636f1c2396c534f884d62b33bdbe28c77646d44d6944e894a49cf07bfcca12fc09b2af606c7cc4f095737d0fbfaf3261ec89a6d64954ee0cb4f2fd739514fd4858da06cbd2ea665027e143412702a5799fe4c20b524ceb45a2154c8309b92a719286dd016f3510e8c470a8fef2fceceb615b3b44fac133ece4fa3a5b8e8b13e1b65984c1b0f432d5b456ab05fa13da06814c1654e904d9c3c32da8acbc712d18bbd2852c5e310c8debb7ab8619a117b55665454e50cd7f5a43331ffd10ef317538abe9a54f3bd44287bc550adfda80f368254b7ec50ab9a234103ad1c5f461857ecc04fc4428c18c35fb5306f39f74018391c8ef12230f8ba20926e9d6db8699da12f8d477f00a4e265b1dcc5ad60e8fb8d519bd6345996c3ddb15d4cf8db07f8f7e3c198135a5518b76c58d4d56bc5bc41a0c926505242d51ca75b2f9b102429bd32e0e91d8f886daf9caafcd9121d1d46a141b6d195a13e1830ba027e22851efce464e77e4350e4da9cc7be0e08f5548fd42c58ef566b8c6a352264ee359a53719e98bec0b2397f60c855e676b58a27dd8d44f4060960fa2fc1544d0784ec5447871681b011e530a44f148a312a41964a0981de7794b9eef78d8fe2ebc90676d1309e85e51c14dcc331743be931c955bd9c14e7fd92df93d3b839e623060de8bd056ed1db03e2fdab36b7415443fa70e6909492c6c0ceaa9325df2e464fa9821805761e7652313b2ba748b5550ee03c80e8ae96f0a6989ab7a49271efa6c0e5f7c44b0c9b3154d2447c55aa4114da4bcc4a2ebf1b2be40ecc4c1de5ea22a1504b24beef086661043441443e4244417bd33aef415c22f2914ccb1905d729b4280e1e7e14eeb278f29cddb68d9f094dfec97d1dc1f327dee87a7f9f4c698d5e91def675a7243ee0f33bb79bbb9d637d4ec5d8c1a72588643ea8e469ccbd6f047bde415fd9f1ff63cb473544ece9ad910b97e793a89e1ef1dae04e7de97b72de7cce341d6229875b00dda95b9a5f7ab8131872478f3322137c7393a3cd6f27b4a3c7cd3c1227a36b435b33033b10f45bed505b54686572cd335612394ca863f9107a593b2ad1efe64394af5eed725ae00b50b45da4ce328ca2ff3ff1a1435a7f17e15bb17c59b6c5521153912300214355504456a0f0481671d745cc79ebfbe2ac3553d66444786202db8106d4d1129cb2861d87aef057ddb1758195f3a82d1bdc061e4c9e8cc5c8d0d63201bceb5dd0f151f018d9c8d2daaf20b11be6f27a8ef7e36cce28c1a60723015a65f07eb716b2fadec22589b5fce71c208175cd65b1eee8479418a5265ec33508eb88e85869490fa9e28fee1975ee5274ab61035494f593e246dbcfa4cfb4aa742a1751f8408a487df19b68d5e07971d312d7783539564911fad73ee47a81e0533efd3d23be0e1ba494829757206d1dc5726f4e443c935ee39991b7b144933fe8543a62f201bfec2df0a1816048f16481a409b05a9b1244901ec84e4fd7fddd53ad2b8851b0616212e7343b17d56d9adc4400c8b2b53d2305e28418ee48d79eff3398e7861ddfb245b40ce34f3bf4ed9c619dfa03ddcf2e74819b71c3b47714c7cf371fc3322fe314dbd8811a722b8e51f1150bdbe172c067327485869fef4814fcb5543e0ac46343fd730002f23b2853233a39d417b35dfdec6ff8148f38d5b35adb736f326118021ac351123a171b06c7379618bbe0f23ea02a34b1a1c8041f31d0b759ba2dc659fd7b0cd5103126562fb5c5856174325d2c7b57c192d1b047e5769648e0948bb9420f68c2a8201c1f064ff6875be55238e402ef08a7c808203ab2aa1bb24dcfb53469ea61abebf8738604d0689b1bcccd98ff42fcaae8b773193015b9c305dc9c8d6ae6ebee154a8a2ed593aa803baa41275a28e787c52e842c49331ca0c10331a478d2a849c3e740b3e5663c6c7ce995ab07865c324a123aaa247d1e435e357c5ce702c6cf37a3edde0e1e92088f5f054c926f47b4c5b3c2a29198aead413922196db265787c6ffdafcc359b0d95dd902b4632b5929f93103edb60906c6e5cd782c841e42a20f441328cafd62d41dd483c83549881dde36d96c7eea9b5152d559d52406768e5c2f758ff9a19b8e32fe0de90cee25246917157fd3e2f863b3109221b0550a6b59e30f9e46d3846702f2c359725cf64b5f9c973531abf692134eb7eeff0020c699d6891316d2be60f0d1a44838342eff6e7f7060f54684f2854472247c97a5308cc3e6d416a1083b16214d93894172c6a2b95339791ed30e7b41ad6d33dd40e1b964f6fbc2a2c439a686f986847e771cd09a46feca3de94e1b39ed2d3f535473fa97b97abb2c56fe2f77d27083dc1142d66c9ecb0ea28c80eea9bdf266736393983590fba2e5354e0d2e2f4b540de0c12fb8d2fffa259791e21023c2988726116b4c9748063a204d91958e3d2c33334a477aa2cbf1b7e27c2dd295d551f127e37b8e765cec48a2b615f0952aa99f8ebc859c68bd32aa19640285c3d9e56015b9b36dc1deb484b28e886afd7a40108f75742451e75c862e92ff213501445f19d15d4802f242a4b9de70f048f504f79d0b6df5ca5899b7bdef16d2f1fae8dd2a5e24418e75d7d0e1517e34eba985c2e03e7649da7f38dabd442f9b94ba06093340428e7f02c6d95ee2ecc503fae5bce11f91281e7c1bacfd9f1500b6688a1bcbf918a73bb10b04add32738971ca65d344dc32229677c2d8958e9d9b79cae63f270e607a1a560e8963efcf5a8dd7cc1f3a28a85d7aa3fb1ac4b5f163348abcd3432cf554d1ef65f085911a9b05d2b1498f2ce1a1ac91f11cb513b4fc5f98c3eb353d627682a0a4a5101061fe39f30861df3b1addcb07299afa13b565e598a1da1ba522794895a841038268b645e2e0c2433560fb2d12af7e69be03e7fe5117cb94888a7d269de1bf03afd688bd0fc99c2f03638bd5f548ec671f4ea185614213f121f3c1e30135e6d8a5967c56ad41b67519e3604de5aa10878b8774de3de2a14bf4c51d10112434128c7b68471338895006474ebdcd4b99604977aaf7cd54742bafc32e1ec5269605a149a0d6461d454949ea1b632d44d5446e12417a16c08b2c2a8a42d447c71a1b9b32d2a10892e4e35024f301cca654fe75e6dde06e71eb3b1a32add011da1fb11f576de5d9a5b3f95dcda833e98a72a03f2947acb9568ffa190d429d233be1ad125a13eba89d619d418fc8440a79648ac6fd0cc63b997e4a0d27ef35e72149ddd8e02f8e518f95733081566da0bfffbbcf91ae0d28c611fc3b7e1981a73b9c8f232843240d2e5b665d8bae85e19d95d615ddda8e8a1e5d4bcfe0a625b4dfce24590be7771a9961e3205260c9ee48346d31f6a0353f5c5b984565d9f4fa73d6cf15b9a2e438d1cbe880155dd4ab20281713a972028fbd2cadb37abc3bbdc9ec01aff65991dc8a2d8fd8d49a04632c4be5a50f50a823671232e7008503fb4dc0377813000e8f74695381e54d154da5b891b48d135b3d5b61a0d106605dd0e21fa405a0ff208bf84eae2d6bf4132bf4445473a9473a3091ff86d3c1ba734c4b7890eec7d134c4a83e87f818a457b88b8da374ad110d1ec80236fec0c087d5e9a8a2fbc4221ec8f898314626387ff65837fe2727dd37f1b1f755951c66fda0207600698d467546f4071741e809d67542d3edba8c238911a73f64590f1e1e4e4dc642093cff7568fad0df9128544c2ccb9653ffc64011ced45ed855b0ae2852e3d3181c1c1e2cb58d2cd7af7d4f00a993b7587eff48500be3af179e7844e969e123aab3a32b5799b48f99c0ca6a7510dfce9e0679ebeedfc164bd1a56024564c14f81c81855bd519b42cf04e11255802780cb6258d87248ee3ee5c5f197218cd07b7af7618afcbba080acafadf492c70fad2e682d7f23135fae38ab6ead387d2ac8f7fbf1e5e71ad6b5fbd4f272a2af1e7378e25a99a2f343baf4637ac9333aed06b470b998ba9c8adf7904c6e71745700bcae8031ff18ef0c53c830235927eaa3a6bdf07b86166e8f5c01f8ff2cc7eb88efb50f28d7313b8be0931cfb940a0c5515635b55346882233ddd254bbac7000aee373c3b773ca50fb68c8dc4294f1741fa3f2bfa6e4beaf803f7b4550e5938f17c25538bbca0f21bc885995146a5564ba560d050e7b6bb1bb40d1fbc549328b47016938de00148792d6f48be26d2f8801e53533c338e0ce93be3427c6213a4507bd01bb9203ad6c2d97662e65bdeb76a89f1a0598c4880af365f50d53a651bf2c531fe009ff6f480aad76f4d58abb8f022c1676b5da7825e908136d288fbc0c954197f2cdb268b414a9bc1f34082b7af971b5999291149cd6318be0af50950a9dd35ba7f85c63430e227d0222c84301480be87fdbc57c247f62e3b5ce21e3ccc5f8d5d32e205d46ce81cd275cc9e4f082bd132954f0c94591fa280e26fbd0beb73ef6ff37aae5acad41efa4c643ff790e0b56d05c277011acccfe0839270c015bf9cf80f946863afda32688243a206c4f698e5c88f428afcad7ec9f60ad17ef711f6d61fd048c25eead90fcef1f4789c4bc4863eb97e0bb9833cadd9ea2ffcc5a45a49b760f4803a6a0e0107ac9a7cf23370a0658832db0ddb914f339a1caeb9cd516bcd75462d2d96ecaae431547affdf04dfccf003f6c784e87ae6ee31884ecc99d6ea332fe4488f72659e0db23868ba8e1901ac37f88b10cbb1fb5b51767a402e40950dc387c9f701dadc89d3505ebc70a994ff1fe5c1e7be076f884239b7883235d87c9778655190aed1a4c14e8b939bcd5890ffd44e2ab0ed8c9cc01dca669448f6588eb790696ad3e397ea6426e8aead32d7da32e54ab572531fbd7f39fd48617ada910f1c7122d21f9c952acbb51387c13c2f522b629968261be8497136feed2973feda54b5e1fb9a571378147b824ec7451c999d3f2ac582553e0492d9fcad5a12591bf6c40e40e843023a982d63faaa91a5e5553cc05ef9b517732df5179e7810c10eeba1269fcb2acd55f78c8d703491f5e2342ccc1b58e42a775a7bee40a706423243304b45794eb5ccf550f4cfdf06bdd5e1e41f98e8911f81adbdd68b3569831a30dfdd072851775f0f7031e14f89ed083f8a8334c2c6fb34b17f6f9b7c6c67835a12e5aa890c4e6826993d067428a8f60ef8ad030739b7fd865d023f21415f3a741c8b42cf3ee80670df9b3f00c3b6d83e86f48d0af8f43ef249b38674a024e7a5f74733be4d2c62a6c43204c0ade1ba9a5fa5b3ff3975ab7c4823a74d8c7ad3c2d08bd19391b520316d15c456a76a9285bb3c867a481a0aec898b4735f3b70dc550aee9cca19fa94269c9b898667ec22ba47a850993257b4a94bc5ec3d895dddbb44e2c74a167d6ba707d7af18ffd264c15589bb31751400a2da30d92239001b7b651616324b37ab99c16a4c00060481bc16e290e6f967854712e5c69c28640d7f0d8c5b5184b4a04f9cc2071a9014e9829a9116b63a389888fe582ccbc706f2280119ebe199a0242126bc056ff78c0e6807ebec489b86a5afb8347bd4442ecbe4efedeabdd2766b4d5295796f9bb5799988beab20e8015d3912460d7465a512a812e47effc090efb6a22b09b10d20b2123b256921dcc0a0f9932ace570d138bb5295e583fc35ed817e99f20af681d73d379f56a1a01489e53bcd3b1bcc63d43d152f0b8605448585a63650bdd5a3852a2972b29fda1271fdbe22323b05508bb3f6d5c272c1815385c8e94a0b1a0f2f69aebb44c1e60c854877200de81d079c0527dff955503b51af4c19ade9bc8f006efda7cc5e3ccd7b4b1c6a305ba08db9f0bcadbef3df2cf0506dade03e719cfdf317e5652c05e9e2b4cf78512a557e71f4684a450a21e41078218e081ec618a74a48018f784eaa873935c95a512bde959f22ef4f11e97afec5fcb40c3bd621a503f6918d17095b10ef285411acacaae7284a5a3b69fdd18a77758549f0a1c63a1f2c63372eeb7124868fb4df1922f65ef3d2b43f411bac190518f3b7010cc901dc114ffc94f92995411d731ae758a48e6649a06a8aa6692ad9289bc659715b56dc024a1c613f2f853b20580dd722c07391fc9adb72a7229345b5e754bf2cb03545b654ab908e28ed8b72eaf69d9c9418e99dbac1bcef06c1558536fb92a52cd139578ba4b1b141c8870f5c8cf212a1abb8329090292fba60abbc4cd85b5a5d412556daa84d74090dfdefd26bb056eb3a4084e57c6dbf4dae0610b1be1e0a79831e700be0d58ae96b6c27645b8aab24fe3caab30a03b6af17267c9970b3c2b1fa16a1b3b6d0485467c6e6b75755658dc9805302c4d9c8438a8418d7fd0f18e1c21c51885101cd549f6d61d410368ab68a005db029d3e70159d0b4f4b0f258b083e5d0e0486184f58be182d8832cf145d14fd842b697034e5b2c12709dcd22514faa9b014aca4246557ebfa73c02c92102b67d96bdd3a986bd5df291661487123ed12054f5a2c3c7064a116e2b8c907edfa4981ac4286a1dbae2d504273e51d3c41205baad4ba98217364701e085bdfeb59325ab68bbb5fd6173a6b80b4114ad63d188f04a1069089c12c092c6dea5abd028d266c8ddd9cb59d32679ddb5024de70b10a772a4c32e789013a766af3b8fa7e7b62af719641a3ca52c16872a8372319ae0cfdf01f521b184c0ba1c35f9710531d7b610c27db4bf710c469a5191121236dd4a5e39c4d584abbeb947ec263510cad32808aef67cb904e77eae7652ce5fa65f6c5bd30450677c20c5b7d181e16a044ed7a712f4b82a1c44363ab587a049ac7a6aaddb37b51baa89b5347b7403050fa90f3ef211415157bec92a385209c8a3a5b28ab7331c1266cbe00dc1b24c68df04d4a6d95c2c080ef4ec2a5f24207b6e55e8b4dbbb8b40c61d9d9b63b16603fdf49bdf564e27866340cf30af5b3626369612250c37b51f0037fd72cd12dbd7f9d106e91f16af44df18e767e25b9c8d23aceefc8355a96740abd36c644dfb3faa0983cc5d3ec52bbe629301af5dfd15390514c0e5eff2080daaa22dbb6b04bef24105819eaef5280ca9de7d88488d9f7394e92073f0203a002771268c666735c1956525dc1a12b7c50751167a5ec9cc6a4cf25a5060ca579822ccec1259518a67821ae35ede1ace053427c1302e0cb5f4dd13171561d0296290d91712d857f1902aa84e311ad5070e806245e217c5fd5a1aa95cfa42f58accaba5a48c61440ba3daecf44afb2b15522b41314d82a4a1dc860213d6fa05bb00096808795b9ac55ce7188f8514001f6d0d6431d88160b7daa963fc4a1be434613ad1d6ed1ea484b1a2c6aa38a72b24b0165042d4f12b881577eb91f5a34818f09f8f7fc225902e058e5cf89906f844cb44a84e26dac457d74410210623693c1dc9dd5d6dfae01b41f9789705940b37f27531aac89b7a014bbac4b4e1b48f0af99a2dc513dae6d13549754fdb9334fe10669330fc78669b9597823f04354c4b366c0eb93658292f88d034fbac561b9547f9e8b5915a08c95dfd3edec9d598be98288f6ac92cc2554c3a3059c547a5d095e4792c28e045c8e91da716501e4a28aa1e124ba03439f6af9f31a096a88aa63a7630468f8ba05191ee9aafb5449eb4be19091fbfbf5a5dc42ff62c203fd8b5387caccdea246b2fccaf97fe51c232984013681ec0108f5bd00463e1b21ab052fd9d5dcb1759e44be8b280c283a76811a08ab146ea5561a76f9cec0cc369715ed86598e3da81e772d70d2cae6880428256082d67342e1653eb4a95d352ae2f7240f97738eff9747c2822ef19a40f4f0922118d9d9b8689f6d20d104b2034a968efa0fc9a58a00d0d421b008e0ac3737c4a07c1ad0c138135013030c17fc4390fbb2cffb9b8dd2de25ecdb89c24d5b94c39b2f16164b145e6583aa76616b79b085b6effc249ab7f7126cef4c06f841f982a2113c2e370bb024d39a90587c52e7b9c8bec24ce8c2a3cda14e556c3ed16c9f1219b225a27e45e19ffc098c12142104e38accd84d0273bec6a74ce111777245e35871aafdca1be13941a86b9e45349e0ea1580ccbfdb3f3bbd9e68e7712c063b6a4e79ca7fdff420610e1fc8e854625026d4cfb23f2621bf54bba2219ba31d0fec9ef41712042c39d62bfe42c95e0ce25fefa69ea5ebf4dddcfef837f7bc0e4dc850cbfdaaad3b45c121ca4edf7cb82bc98e056b0d0d1822200d252e88384d11ea307c62b6b30c54d5180cefcf9732775d59ded3dab09f8ec81825002e15416abce455078453d08d7753901c185cdf8eaf6f8d7466d0beaf83424c7a79bd28b78633b458175564715b1126c5af8b2eb6fe99d60067cbb03298b66562ee06e82a8f8798a2ee662a72df7cde0bb94dd408fd90443097321ee49a6f8920be8a884843ac702abb8c690a85ba4a891b80a4a463036e15f50ade8a47be6019f438c083b5e631cd67c150b784580a02bcba9d442569da632db2a1a02176bd2d42102f15243eb57f5ef2dcd4d15641836ca8751f159e5e3f8befa49340cc27e66c7d53053c005d70df3317e5760a0a195f823d4163a984503493b64327300edc29abd3037e63656a9a7e70834c8cc92fabe1b3e0a9f908210181c448e3824cc649043941bdaa3de1c595ab14587f587899fc8d008216df2c10e979759322dd7f5c14220e97d4f2e5bf3c54955ac8517aa80d340acbb8318f82714681d68e17cb51d2e08548638a5c84ed4388d582a196e93a7fe9bfd1d23bc7f9a752407508cc485b1febdfe487c5856e7170f41b36c817f6c922fc7dbc0b5aa03192eba184a01a159acf7fe2affdbbe1c0c183d536ed1485bfeb8d14f1cfd81c139b7a3f6d5821944da9ac9d62664cec9d8486be8d7794480b4def418694d65f0d1a85176eb19ff0cc1b3a5f4695a7b54e34ab8da7e2b3422b35b19d5b074868e601e14b0ced83093a15cd1d1d7290c8bc0d8b8c52360b715592e34f403f5236e9ccc219ef9a85ceb6637e8aa889e024aa60f9f853c963ff396f20475bde40f3eb82d7b9561cc1a899920705903943828a1373131837d2fed5dcab43e26fa89531f8c3fec33d0618769ccd556f027432bdf375e037df1e709ce3fdcadb905434333601d4e77153f19dfa8018f9dc13a28f033b0f938b4cf4cf10b21e5a92d8bca7b1d73aa6aeb8e7919cb7af258a8c0d7a0a9127b4af4c350b2619d79bebf28b0101448d74fc179edbe2c9ea21d318b436e789f4e658c16a03e52a53accf60b7c8913fd8211fdbc3164c4811c80e1677a18784be18ac962dcc79c3aa2db9bea074c243d4c5d7724d4cbc5e44587e7ea772394aa01140a2191be695aa05e0d6cccc15a3ee1def279fe2fd3d5ef7dca774ffd112e4a1abb315e5b3cfedf32073b9c78e63ad400014429064bd274272fd91c675526ae1b46839e5b3425805e3a61921e6c8fa90de46229f03414b8607204f679751bab5a7e944a7aadf0bc5d35f9546daa029af1cc4135e1e60a0149bdad4527a8d0e97524f430f6e287212abb69f952ba15807811db36313ab00e7d28960b6fade2c417ee1fb8022cf381a358057ee010fa205e095a9f06e26dac320e2b83321a2ae228e979f5b72a0c533d352c5ca83daf3ac9a2cf494fe2c602a6dce0bbba40c23e98abc2e7d3ac628b9302877349a129a440bc18754e0517b80bc52208d09c85075dff8c2e34eadf60701b58d7670c793b35db5b26ea3ca0b5d6bc3443ade064571c33f472b48dcba79f634ff2d9a78c937a7f3eece0c9c44418e826b6d4f793485cd79ba51198b886070d7ac7b6f3290251a5912baf62b426d5a4f323b3213969b77bcfe7bda87dfb878f88bc16dea05e7253fa4a4c1e14a759d2680353cbba0e8e50be696e4018e203b998138400b70f909395742af9911c248e309a03149fd932f6c6f0f2d6ae16eca2f23802a45ff429e7e922f0dc4ea3db9f2f73ddd975507334b46670cb02d3eace0cf93542e3afd40f8969867705e681f6968ef314c2d6e389300abd6ff82e0310e1c993dce44ffe5ec3ca1b0b5447fa71853d60e7a56e033f44a150cac479e57b2482fc274b0a05544b582297c2a527a75e42b4543168e75ad96cb6b824d9a19cf22d1468c61b72b049e2df8178bd3e833799974fdae30bbcc9a7d5ea2558d8d5cb815f3b04e4f35e0b6591a889a2c494a1122846ea9692836d0eeacd25a905b0d68afcb7d444eff1cfb81bd7782a8c20611ab7a4e41ce1b9f30d2390cffb21d75eee24e6f8bcee82412e52f61b21fc569b7f3a17dbb97b4c5daa59f3590d34f1597ced42a41c24b90ddcab646dd1ad759c650571225ad89d69d763870dba1307ad75058721a0a871ecb04cddd0105b96abbd4e53440258351871a41cc9254ba79ac81d436b0af77b468038629808a963f5582dab8ef55d1c60c78ad3223646f2be49eae40c196e6e64adca43dfb16a3ffdbb659663d34c4651c51c335990a5a78d1bd9df15d90053ecdd1d8346d1bb00225f3cc531d4181bc8362f0f8be2b2935259fae58dd972665ceeab8dd3ac6153b41e21ad1f2e22a84fafbe3ec9efb593ceaa959b13039acfc33c38438953a67b78647c75824cedd84a346c53734caec92bc81c304d063aa16abd99b47bd1b394a0e5f64b321cdecd663c70b7572f0e0f5dcf3cebbf927ddc4cbb10abbad8033db244e0fa26a36c3e8665181b63361df2c23cb473fdcec0df7e0baeb0c46e426220a2b638b9e4a1633628c0dacfa641172ed80db384751ce3d0aa8064f840d64a4d16cf185066fe07c46e56d992db667272f69ce5a9f253ddcbc51aabf20296247e9d6f7778584de4a94d3d1971d4f0499545bc7d770f5ed6109e3851a74b56551c1cc5e66f4391355a7c46894acd00aed1449d4a44e60b7ce737034e819ea5e3a2d00318340a0f21600e7a0da0800382186e204e311623d81b99c9cf0f67e3fc107c79d758355fbc0f79240f451fb1e3b2c7b89a4ee7106766ce45184dd1f468d81ea69f5742c7ebe69931e99f2f79ea461108df091fd2c5ff942b0efff41177eee511fe9b146b30e4ad64385400a4e8e34f70075fcdb411459fdf2f61f6bda7d88ab057c974febcafdfda88793b14d53f0b7da900d70fa83a58d430580b6d35e8fb9966ee09d2ecc0f4df50bdd55dd406389674c2c8979a17527a70746ae185669ef224cd3aa17adbd576741634f185e695ad4933d6f8019b937bcc00d1126a5148482b646c3be54feef6565b929b8338eb54358027476b643c29236033a00676740b3debbbaa14dcdb40f75267e24dba38cca4650e9522e96cd146388ecec6f0e9cade0ca26abf4e943f0acfe4700ee38c0c292cedac00a84c4d78da0a1cb5da92b9ce09983b9bc83eac518dd7e29f23968e0b2afd924ebfa8774fdce314634fb830692443eee4b2e727474e4e65008e0b12b4d1f307ff135d8f31df277c6796e85f3bc4057dab665588f94d94c01c3d2278ce0fe31c7226c2e06fd5ea4edb008b897938a3b8a70313e16c215de2638857468c76d4804b4e3aadd00b8755f826b5a3aac350a7fbc8138e0673e0e76cde780ee05a4b837a16b4071b0870d83ec197721af033c7007ba2a48f89ed981dadfbda795a6a8d736e2aac4804070a42cf1e6bd6eb303053a5e0f0a8e276d45902252d5cd4dae7c5dbf39e9221249de65ddfa247a4aa1f69c167b4a3f141d6f6fa73fc19b08a3480956bac18790c35cad0e8e92e4fb72a405a8342ebd079eb6a7edd87dd4754f7be06cff00c182aaddd26611f12282be58fab716f759f8eb410bdb88c68edaec07460a58ede0a3aeaca53454c13a6850c938eec2faa02a5704428a00c3a1671e5d7d42d0e4f6b2e67e36b931574988d9e432c908b1861c8c86a79d86eaf5b7cff15c4abe0cf670d30b7272b112cf3f964ad4b07830d8a1dca0aff227d05053412c371b435ba7af31c2126476579a5d141b5f23a79e36a0207f2e5995292da2921433737b74426b1cd1ad37f142de1e48475075049acd98cdc9df8c3dce187d501c0e7fb22d57656cc33c6858538a156dbf24ceda25ae0b6af456bd7d964bccd1dcd9e5b7ad70b0f4adc02e378c2202f435ed3a3f7da6a3c5b12f8245bf6f0ff634fe0f1956cf00941fd94825a9305441168b7d1f1f1d30e0d1e0b76e0d6a7fb400b89138e8aa8304440090f590b63400e2bde94289e8f8e8a5828a1123f8340db955632ef7bdf4c3dbad4a3380096026c897c3b15fc67e58320f307e518bdc70f29371399c6e441c71b65b074257139ce2b6983f0bd1007a9c74545fdae44eb3fc24a41301f75d48eb3912feb3cc9d033f11e2749fcae8e91e3ed05e7fb0d9669fd23a01569ab54b884edc07fb88a03a0ec1580f2712590929eb7789eda91c49b57e64d3e54fbd778c74a371657e38cf4aeffe579c7e32b31b04d4fa7e994a0eed280df53033ef8d21d60b8517d40ba09097afd3057146a9a7108d1d5494f96b387a6a76bc1944b23f2773df55c07916c3f4710892a2a0ebf0ce4719bcc373d31a0e068013552b0f7075b6eff5980bba3ac25d2c5f98d12948bf63be280cf14dc01d58d22885c2b8abc51cae97c064bcf84350b52dbabfd6471048f54307114f2c98d33230446726b8850aff7ce2b0b41447c757d196f20ca0d3a9139fcc2e2d536c8f8c5101226c288dcfe4b5108e8611f06e2165d1b512e02746426ca8d66c87fd2dc44a1d96ddec9c4c4a2d6ac790ea77c1ed6ea2f481c37b70d6d27bd7fa298e0a8e73343183f85a416e0ee44d9675ec6c1283db0447527a9383ca2629a05498ff913899569edfee763ebcc5b2ac8def28665e63a936176cb1876e640e58d66a312aff98b6af460f91cd739eafa53e825c87720814d9c8d0eabeef12feafd29e6f12a28c43f39f5632890790924871da6f0b2603a7ecb6d49e01865a7e6e37a639be29bf4a428f46aea3934bb82f6aa0d2f4524aa8b5d1f5111bd4fc2f6a34dd494e7a19cac0bbb6beada9be9cff25c92036b9c865cc252c72c2c68fe2a340fd0b3bd02408babd7f2e043496c4c1e157bbf1c7bb0f904c5759fe8b1e34bd65a24dd6c53a2c0fe2114dac0a6598195292c0ce49249a108611c052205c417100612a6d418224723222617267cc752288880a406872a7b019009369a20a2959f842008bc6e7701fc5b81365384ac018ee8990918ec208adf14f0430816555437ffb809bc2d337f6d5657e586fabb953603fcd0b507f57c94bd14ea7fde7966994258ed4a84d20a5102ef51a39afcb7bfa0bc2143db8e2f7913b2b21a2b1c985c63522b16786ce2f03651f35d79087349025683f6f4b9eff3a170902994176ea1eee284d124ec7e0a710d51342bdd1c181a56a0cbdd165f9c3eb2572c83425b343f701207b68b883de259c869ccc601d0e73ef56d76f7496ee073410690c23b6ed325694c1eb437e90424af4586a84a87be4910fe0eed07c4794b50b0ed3927d68d74501b2d1162a6483416bcb14f6f85d393647633d0f3c6c8ea1c5f17376cebd1e762076c97bd43d66e1bf9d212705a5149854232202a3d9a38bd1d3952e9e2400be1d96f7cedb2af84b2f2a9e971daaf63a38b96865cfe04a75296ffa7a6a5d8aaebdb0dc550145b93814459964264c83eb58aa3a58b676ecc2e38bb3e30f02c15dce879363fa86d8faf69b52af83ab70a4ae4a31d3abdaadaeb0f9da450121d19fdf3509027f47e01d33a66ea1f970bedea7294113228a907cef206068fa48a6e00f89f795cd1f1879114b1137effb28788799073198b4179ec84db5880870bc5d36da34fd8e7420821ba73da895a8136a017b35235ee5253afe3b1b638819cc67965bab711d7d2e70a5bc0e45bf0d4ba96b6e7d42a99407f2a4215edd164ae38e2358902c6eb0572b69be44d855f168e19d84a56eb7706fc5a1bfd3a6f340dc40c3d00450b36a8e21174343e58252de017884f1ea8406e45ed71c5216a65a5f2393a9615b1529fbab84f8d794cea865d3998a8aeafe06929eaee487ac2ed2f6944f066fbffa186a328cb8a26f146a2fb10144cb7965ad010116d0b09520148c1da454d9119474165715187f467f0832b8443147d52a4ec45b27596cf67a596c79c121760b866ab989a6a873aea307136a3f6dfcc61e0eb120299373711a5e1f9bab6573a4c7c4ef0b39372642ef768b2d8280c52c68ad0a5b6a0c386c86852ec5f749a12c53b516b107b61d69e5551048ac9b921113d10b7200fa1ee3afdaa8fe750639b60ee2b62c37483dd90f0486ff0856c38493c281cc08a28de2ecd58b09f3b30a91e402173bddbc955523a499927e3a73af96d2a20ba95fc53cd8d40ddb060ea055e04319de4200f323339f14127c687d003cfc1747686bfd39452a1ffea23bfe53341ae754e5bd9d8a1828b2ea146377b0952b2b89ac54aeb3b9da4d29bba3f8e6704aa67c365cc1f1339bfad5edf819eccba82cb7bec6ffcc21f695de61f2160d9c0d9b98f44dde66811dcf51b4cd8fc52c7e27a54d3043aa43ddf784dd0cbdeb873fb053518371ef0221a0d7c97501640ade7c0382aa6f9a4c18af7870368be120d719f65adcf6447f06ff04b2bbb167e8402b2cf5666705b2555cb2943a346b72865f32583700fe72057facbd092f6625363e0e4faa0fde8fe7769bbefa8767f59a142973ce1b610ae2f227884cd2f5e289a07d93019d71789d681b175afcff7f2100fc92e6b953a06adc826a9a893dc2c50521fd73c4f0a65a81298918b1287e73e2aa69ced155ab1269f352867465402ea8c36361358598d03011768b6825f32909eb25327b1f542974e28aed5c5699cd00bdf4bf8c1897afc217f335eb9e46deb5a5445dddb645a1f4c05ae338588c693b6ae74094aab9b0dbb1c75fbaff94be9b214164343384ab6246dac508158a906c8768961a6478465134e05708c8bc9353a1599d4c6952314fd011b35f1750a6a3f0eb849754cc133443d478026f8e1708d9a07d64d6aeb2248cd93ba4e21451f8aeb13488e9736593ba074cd11c27060e51959b5dcdbed415a8b28ee08355587b1ee80d7918df41081c62a754b379000dc610d8d79552e4136586e56e6abf6a0e5f8afed73fdfc1d4d57f58073c051a871daeadc140e70a44dab8f7a0278ada4235331b849c229d94573d5d44e521fe6a712c25bca8ef25baaba6271672f51278ee5756aaddc8716b0a3ac8d7577863851356a9c5b5762c077fab9a7705a1d3bf8c5c742c28360be5d1488efe441b04f48cce67cf44bfa556a1cb15da43cb06a54a1fd15ad464446edca21a3eb280845a642493b4e0841406c1725229438ffe89f1e42293047e27709b916dd16b6d4928c2ef2f960bf411074f1cbead8a65023985ff11eb840760848427bca7b12d4f4c2bb9ff57abaa7e901d46899b57ccfc86fc0b7eda3bd2aa253025c276c41007f081e962faa032319e7ff5c1d45af7b1942f0258fa09587c14e41a5bacf8247d93cc2c93f8bd7c36ba89aa080f088e8d8de75fe183ae603603a183841f8e7a3f28901c1911b1a2ea885f140e4afc1957290a38118aa1188faa7733ac22a40b21f89e3e5b8e2fd263af3eef8a605695014effc9001a6f0de07462052a601b920c0272b7414928626a2bffd7bfec1415eca889d820b7f4f882a0d688fa894720a61fbb51daa8f05127eefad29f8cb55edd1abd14f3c1d1e44577e9b5ca149c3908fa81a3331e7e734e2b770b8bfe1b23401695fdaddc1686ee6672d2ebf43e2742ed03af0a280945de2880a13b3307a2f34603dea01fa530000901a6f29dd3d5793f7d9b14118a9422a855cbe972c9b726adc0d754c0f8259c04922cd771c2d0c6d6ffcca65ffc67ff61dcdaef19fd1c519e323e493a586b22838c20d0ade18d66201a1ca1b34f966770b893b113b45a3578f7f037aaa859d65d1528a4a4a44563b1348f5a015220eaa319936f7601ba1750c09d348292cf3ef8448c20acdb25345ec970d3ef3d7caaff6f30b7c522022f4a848019a9fa16375ce5f0996139579cd984bc38fc541e889d70f7a2f7ecc52314e00f40461c4db3e49b9fa34af3e5568ea067df5e80268016a8f7b27e9e11dcdcf629d027b48fa04ea579c2c246f5f5e4801e0d4a8ab87ce45d6a539797fd87c889293cdb6050e1dfa8a8ca66e5ac38bc73af490febba73f392000777485f67afc9fedec3bd94f1353b5b665510de716e19e27c25964c2a938320956d54c4fab34628b7d42f9fbef8a344d7ccb2cd3e35855e8d2d5aeabffd1e1ddaa19d97796816a9d07c655b555e09696161c7adcffa7afb90bd213d793930984fc83ce5f6ddf9b6c2f7345546a42f4e225b0fa9f3b7ba54fa523a9ac7733dea907649310b5b21ffd258dfafef57f30b3a84ab9ec4654ffda0fd5d1a2b92ececa265335863d402b8cb153634d726341d3b31a2dc1db7e5538cce76031da5af8f01244db702084a7ca68777353c9fc47dc1b5680d1de0365e7b2af7fa43caa4cf8d50225da7a2d9288528581327774e4d49633fb1fb9b5d5b543f0d7e154f00c48050ceb7cbccfacdcf617c63a38f5202dfd11d7b7b27f6234e14bdf92e746fecf3bc5ea437db2a4a612216f09854065b11868fa3b82f691c5709de3e8d7c1225c313d7f1847b3c2abbc2768a08b93443619d3ef88f96c54e0dc53d8382bd84899cfd2ebbd32b95a42b434d2af43b0bdb7cf8c5655b5f9658c1535cb62ce6dd061749ca5055a6b24436687b7b04929d85f06f8af70d25b309ba0e3f608883ea538c41079d61fdae854b26c8a167c99b56bae587b22efefe7bf456478c0a90c4deba949bceb38671d8e77d071f64acfddb870842fd9daa043c120d41d7a91a67953584c0eb0a980e3f8a2e0bbe74fe8c5ae1a09a50acc1473c990d4513b9657203c0635a77bad0c91fbee9c07a56d64c4da129c2fa105110c422c75925fc31c66516f023e71df978db5d7db1328a2ef257aa1920b3a676f7f725ca4fcac5d9214276bb767e1c5fd690e907591711e0ee8fbec8e28ac87e8d877c5a72fa57b7403f8c38276c02d1b4a0e6d73b0e9c2ccc4f5712255b601bcdb1a69f5ebd9f9ba01438f1e2658faffd3cf1f1782c256bc207aa236ef07e09669865078534464536f02d932ea2b9f17b35aaa6912348d74e65614b1a6c70f62203d6ff04e6444070592ed5b78fa0a39f4a4a064a26cf8554f6ea585b5f9756d012e4701746aa7d76a01eeea6b8a30cd37783251d5d35f7016acb7bc138ae77f63a1f1e09357fae0e4ecd05f0b14604e84622bde5cf710f425f22caabc29420c2b1b9e0f4fae5aba3211c0bcf04840fb9f1047c1eb8093810809b521cc69daf9f02c1229258aa410e774b038b52ba88962b6e6bc77c1cee02da87268e88c046c664e210e103db4f59d7cac68cd562ab1462a379f4a076961fcd16d1cccfbaddb58165c6be1790dc29b14c414f3466c5b8b2a58cbad63125b838a4319e077493134b931aef34e2ea66f61247a0705be162f342f893d95b9aba95ffa3e2d6ca582b6eb939972f55997c4c946e5617fb46f92a6605272667fa28559343f9ce21d195920421904c138a6c904e006d52372d69ed4c1ba67ac0278cfb4d936412529c76a9802d86099de8824ae6288282178ff6afe37327c06669c71ca0c91cecdf9b5a3931abad1667c6575cc276c04ab4eccea7f7396d63e1fe5b77616329e406ff6863153823eeed606ad9389aeb73143bacef9175a00ce7e39fc2b5d0ad6125fd95f2584a2c74618b3660489e759956e5af7669853dc3ac6a28429a44aee246f2fd2f78b5cd3514fa257ca9d9c9a11fb75e34c56cfc7714707add3f881eb31dee8f22da9b847cbddaf691892bfb6debed9b75962b92d336e0389dd4ecf2325d4fa035adb2a935920d032930ec46212175c0749db10bd930a96f85e5e0f981f3e1f3085191e325ea7b301be97c42f7a4b11cdcd29ef05e07250805073aaee09c74707f04725166ea4a9630aabadd9b733f0bdc4828f24217b6fb2e5de52ca9464380918093a099af676be8f74b396bee526a77255d79df7ffd6ae5ee2341996653b557778a5a5043a744f94be3892a85c90e4a1b103f7c45257c5c82a29a99810626b013706103d682d58b263d490a43e31b54de5409f28d2ce60fae2c8084f9c7018c189a217e030421353ad296bc410839a86143190a82206241b0d628861090c6af00003152650c8000312150f3ae5800ddab728a8f62a3105e629099ba72a620881fd6910c696d717e44a3c2b601c19c17082fdc13072c0feaebf77ac312ab158b2c1ab355f90e7f882687fd144059e0fda24289a2fc8a98f075d7c91039e7fc12c4ee0f9335fd07c777757e207ec17bbbbbbcb7c414e3d9054b224f1620a3cbfe5d44117319c6f747a40b1f284e5ca1357ae60ffc78204f61fbdb801fbe7c48062be206fc2abbcae4ca1f2822faa1c5539a28ba2a62ebe80c11330e8c20a1254a460f50202152578fefd82e616f04a942d9690b2c51b5c7481fd59f74e0dc134ebc10c365450c414614a8724533cb57184946ccea97191c464ce39e7ea0b9ad96bdd603104167160ff9bc515ec0f3a2161ff9701fb8f5a8091c3056c4da9827d8b2b8ab8a20deca04c19292081110b8cce285ae30a2b5af0030b66479c685b90c0eb971e3aa740c1f32fc805cfbf57c6a81629a4b076b8b9d56ad933df5e2b96b082060c4af104fb6f56a42019b17bd114033b850537a8a86aca89a54988a62398aac0f3e7fb20441c6d38c5d186ab10731928e102ec8f80d8e469b562fd2790d0026d80e7d3f96d8568bda2485501c4184711d4c51c6c5cf932545c39fddba9383a4cca2b1f94728ba7d76b528a375677e2ea6fcb7ec70122b8fb0bb22d76df3d2bfcb19df4cfa70609d335a0d5fb73e210eec2ece1ee57abe7bcff1ec887f0170e71ec89eda47570df85d906730f34554a98e2fe5a4927d6838b7d8b60ebfa187365d5da16ad5ba0ef43b876325037a6c8ed8f99e1b6d014e5094b2c58364529829ba8e882a927f3c3b53237dc6f31b930dfff36e3f7df9797b2961debaf749e8f01447ef8d8268c00e710cc6957b259620babb005be42f9c3c7165fe2f4c764b95942750671cb28dc2f37bf30310df2a3051194dc87d274e85c32a2ccb7be250ac93ccd0f7d63c8845aa22b85d6ca06208021a3f9219a1f92c92266fefe26e66043860a7a67c41f0ea30fe4620b39deb82d13ba6644999f81f921592b74a5d0fa988f11855e68c8845a0f233671bdcc884d642f70bc84f6869e1bd73fc70d36995068c7490f32901111e18836a26434df0a853e1f644462cc90d1884da6f57e25668b5731456e67a3c485f998db6af98faff532392f39aefb99504ad8b63d953bacdf582fa1f45126cc09250a2d1c6ecea11a270e716dbb415862ceecb2fede3027f6c3c797d0568dfbedef775ba8ca89e5e8c47ca4f73731b7e02375c1dd7e04e9fee56f98a910defe4504f29ee1b3c255f8859f7d2f1ce278aac22139b11682f88e0cd2c20f1fe98ba0040ad307a24f02fd1cfa31fa31218cd4cabf1674620e36ae8e0cb59fa1d60d998d6b684b706cc5cd07c09a3876b93fc4b18c99baad229934ae7c192657ead0499b4e7a4f50ce2a6bfd82b6eea9b5dddec77c31fb7f407bc588d1c2944ab976deeac43fa0add5faabfe95a63dc5dd071f7ccbcad7b39aae86dc4bdfb1bf7a6a57331fdb68f52bb1ce7cc70a739ab8ac9f79f9f96089d29b0933f5617fc8488cfb7d6b56594f5a4c6eeea49f57eb98aff2e06dc29fdf4fbe23fefc86923efd2f25005eca57f772be9ce06cbdf41d98673db9b9937e8260cf3c6fbe5703be9c5f1697f5abcf2d64baa43a780b7e370c73e3f0c530b35efc97ca46cc8e55ef81981d8b611876966e3e88bbef40cc2000440ec4e9b2a0982f8d98ef8c9841993cbe8879c45dafd8501267b6ac8fbefac24c716e51e918649f61f6c1265a4f5a624a529a89eeadd1491bafb25fa374524f08ed9cce7a3eb4a759081e42a71c1beb9860c51b31c7c41c1f1df790383506885b5f2c817e8b417ceca7172cdf46895ba42826b34f8395152942c3cbead39023d37efb2232741cb226f369b0324b1fe4ee0c3783f4675e200375f305250e7d1be690304a801c62ba23832045c81f60fa302e7bae6212873e9ddfee6474d2a69345dd4248d8acde8bcbedb05455ea0dc5d2d2142b50547081e294e5298a8b149e4f5dab4a4c949e58dc33899b1b57252bd2e78adc017d7442f2bc2b39b93c0b3d923873723fdf97a63b75cb09dbc41f13c672dabccf558c59ba2f6edcece1f94a4b4a3ece239a4495e8114552fafa5d59f07cbf22c7f9ee848449279e263c5d0a3c3f8f78b6e85624cefcdbea25ee8b96c1e1ca97d9e1ca972fd374e577364a5cfa9aadd4ad1297a88bba67d2ed386172c268b8c20c577e1e8f9c8d4eb5b444cef9203871db874eda647a84eb77b27b71b934a86a1486ee092c9ba058c18d8f9c0aa41c5d687ab8f5ddc895d4235c5b02b911d78b7c1cebb7c0b8d98deafcda4748ae247d24ae2d246e6e6922b559e2dadfeaefa0e1b6ec84892e94c072c22d1feb97c072c2fe391356819cc3d2c8c59829de0b1c37b7b62940dcec452c96af1493e5662f52c4bd48c3f21bc9c7fae2722b2d617e2b99583e88ab918f9512d11935a244b48808ce21906d579ae846b81a71b98d9b1ed91fbafe996509db99f334d3914c7a724467505880b32bc1734524c311383b12142457c27342c19309cf77d9d3bfa01c145cd708d282b0e4f6d3f470b3cf90f88e1be1e94b73ce1f2ff8b7d57d34df91e6bbd17c1ab6aab8d9c620dd6c59188ac499dfe10ac700f1bdf4b4a74edae4b62da99b6387bb0a85fa061b2029c958dfc9cfc12c42520b0f5c1cc958437c9c9fff27d32463e46609f5d482ba56f010874dd985e8a3ee6ea9f8018a3f11887d6d041bbbd3f3fcfdf7fd270e117272ed67d9e5051fb9979f099e485dfaf8c3a90d9287d01f3e0e1112e2638c969ba515895558210556b8f2b0e5e32a945792a08892c9ef82596ece4961eab8e8e35d2edac89ce0bebed0fa7ab6fa753ae99cd4ab47676b7775a57dd2aa6d965375dea77dabea75aa158b5615372deb4e6aefcb7c698f839994568e5a6fdb569e67b574926b6bfd4edfa667dddddda7d74a35cf52ec3edddddd77a6e65313e974ebeeeeeeee4da9ba33303a15777de7a3f5eb00f6ca51eb59ab39557195eb2a0296e73bdcd701ecdff5819c0341270295ec5422e5b4d070e56f5fdc1a2c9b5eb0450f97a60596883b33846acb0b8c9278c10c4b6e0c964d53aca1b1e0b6b06c9a020c0a86b533534ca124e649660a27d803fd04d7231fa5704fa9dabafe54a5ea547daa51f5e9bf3e9d418968d1949a539b931597f49086b23f27f623e7ad9626a59473ce6c3f88f6dbcf1226d6826c6f35cdf3a4940aa05f4d682c02b15f29f6f7a762ab05be5661a6ddfba77a0ff36dead68589e5e7ff805c06d99e03930342787b2efc4c105bd60a5f4f5cd932944082d4d7347103f5299d49c1cd8fc719275a746d40943e46f9286160b82a2c664e20b9bce73b79a83fe923c52ad1fac8899e8f9c9855ef59cf7256256eaf0a7d7b0fb9df8238778edbace52cc75465c969f577798b221021c13a45f742b6774a55e106ec7328d4b6a0777da3a1c46d05e7c0b2a98b14f6cf8aade4e3fc4d9c51136a3e31dd77229168be17cd6fa9f93d355bcab6d47cc2f3a5e6d47c9a5194080a44e2fa4bdad54e4a1fc71df73d170ea1d8ebc22c04e74f94cf8540ba211477df710fa4ff13817022105709dd7362277e12e30d6af3feef8770988570f7b6b96e6863220bc155f49744f84b4bdde8a44d9e52d5dd7fbce0d0d10ce2f8414a065548a144e6370d282ea482b881890964fe4e24e54a50293529a59452d6de4c6805501e4cf7f1fb8fee5aecb8f542955d9bd2ad583675394387db62adea5ef076f102e7d1a9e6a0c57cc1104b9a8accc462cb1b559a6801850f6ac4105eb1822850da408242a20c55942c335069571a77c1827d005836155103abb06ceae284655c8a1581657e0289379ce0b04618455099e2e4040aaa1cddf084ec5a6101910a44e2e61bdc3dfebdc82f19f0d0850cca685a03ca0c7c98011bd8f39d2560f0c27ad9dce8a481843d8d26d43082fd4735a0b07f8ed413d85fa28105f677376ea6394e39767ab0cc3dcc30d28519639c3144d3196c7039c2050a9737b07f7b1e5417505c71a5a80434500200368ab0a031c31860ec209bfd3048220616082eb8f9e6c679a8acf5bad1d952e4c5162d5150444d29238832a6b053c704fde123a533236a667736121fe957d105828ee43337a28f29cda11fa3fff447fa8a72bd9ffdbf384d2268d55699484e5799efd835845b58ee7c457945b92dbbbbdbb7551ec23f5eb0bbe8fa04d7b16ca2410db85faea81799b9912d44d85fbabbfb949d83d645b70098e8b8f8e8523e7ad4749a30ff7e8180911b346e9653d469f2b84c5403bbc0e585198be5e48ce3bf9cb24b97c9e98942b184fd9b29c7c8dd7ece1348f0d1bb2ebf0ffaf2971c2999de487cf49ed51cf14d25a55271ef33df719e7eb24444440483f477248efd5d8913f6258ea4e2e609be93b9969a301f4fa8e2cef721a5b50c91b562067fc4aceffae8e81671bdcf3d43e23b8eadd84818b07a6b99f07176b7a53cf6d2b760b859b6de5562b63053cc604b8cb1164614bba7205eca2c608a80133671489d0fe44c6143f1d1bf4e5cf2c27e27145a8a68d66ecceed2535ec48b1a8af7d2e411e2dd4cd2ccd2554105145468e2fa48e166268b9b737a96e323502ce76c6f6f66de92a1c6c8cd2d65edbdaa2589f346124b4eb47092c9e2bae1239154971c3adc5e7298bfb5bdd456248e7f4c979b6f96bc89c0721ddf21ee8d8f73b6a4d4b7d58637f1e6c677a4cae7093c54865fca89bca8a5fc7b2ae6069716f9e84b325b644d0eaec444fbea4ca40f0a2a0091d0627ab97d6b87ab34ad28717ebda25c7fe1d7119542a489f642449ad0efb8ff4ce8d7c49cedaba2cffcc8917ce646349c71723325ea50154ad7744fa17b4f14bab6135d0e90b297d1d0b2eeb39532a19675e20c969b7dd6df81213ca7d84f46ec4e67fed4c89f12f9d3a2a31b5e51eec42c4c581437cb252019125c97c4e210de81e4beff0bfe2cf8fff06f61b98e3bf786f0949e7baf28f735d7fc1e52710fd7147570cc47e741060afe2af80391708132b0bb3f0b12c7bd27275c4c14242959c7c529064ed4b1f62f384f9b21a57d1361ad890c2358d3344dd3261d1aa13d0dc9174fa46476c2b4afc9e1d2d780a6f6c2b460100d489ffada0bf19dd790192a7e08f9d60d6e4bcbeda680422079dec280b56b03d674c0dac710e1305a6e7e61edb5a58d8bb7842bf79d90eabd97ef855d3461f5bbb08926ac3aa1e03bb9a51b2a541b212e6c3496983a92cd4c71fd97206d4abc5b824bfffd0bd23cad3e0d55f0b152147880516d086ee10b0c62490b24990cd1b5b66aaf7958c813973a15c13d90a350dfda9bafdbe41f472bd4371c0d4124abc1a1716e226d6bf958bfbbbbfb0b8249c3668beb9f6dda884ba7fa5de43b8ee485731369978bd2491a0aad5ec8fbee55a1d0f7aaef565fb4ba5c896bb0441c4cb22f943299248c9081724121d57f52f37f93354644297182b868594ef3486b8ad9138a79996f8542332f24b9d0608b1559ebbb99b091de28c34876c47d6d14c786b81dd6f19dd6f7affe763356dcdc6a8d71b5cf2dbc69af7a4dccdec3fcf62d983a13fe3081a348860437bfb6ed7720b91bd0f6246c9fb37d6cfb971b37671a21db3ef6225e31dffb891904652865ad50ca3cba7d67095d263896d886398887e94f1965794ecd10aa595c6144920cd3ed9779e236086454316a25b9937a7ec4c9dd63407de54e67a3d1dd2d65b56b97be9a620c94db3f77e8a44df70611540ba95a4bdcdc4e0e83dce9263912499f9ef47574acbd1704dba999382d4894643169dc4e25e3e4e6d6a40d87544b35412561fa459852da7445a9d6b391f84e23d1aacc39e713283b14c9bc0a289e5892cd64715b4937cb284c9f529129bab98b90b44562c31f326ae6889bdb8936154c7bb410207bb4986e0c113eb4105605988ab993682389433fe68ddc464e64b4b8b935bf454fe9e656abca9d475e0a984870988bd2159ca7951cb52044a88ba0a860e7827313893e352a24c41871f338c6f01643c4ed16746638014dcfaccd6aee558a198b81a0b5e214e3f66f729bdaa4da9c734e5ae79c73cea694ceea5bdb39a737e79c533ecb0d5aaba6695563c15b686f6a4635b491a390c9b37a51f585de883515f0f672d46c56c0dbb79cdbb77d69674b9cfab8d3344dab745220f3a9a67d37e714bd6f55aba7fded392bebcefbe2491551455067df96ae7dd2faa37f489f8e85167e3ce1582c27671cff41f0dac891be90c9d3b3d95401d797237db902eed96cf69a46297dcdb66cf9633e8feae5ff50852d8789d7da1f5e55799ef63fa44f77e98f2d047d6c0551b17eabb9f27e481f57899e83c4b1bc1fd2a7eba4151a214c5df915cb26244ae4e6ee1bb574d649e974ad4ba076d50dd13e69dd6a4f3d9856ac6d7b56b80abfd0a1fc69eea00af30bd3bf79b98d7fe29530afff3d5d7dd6c1540c7f22d1348d0489bdf721b1277a9b2acc8f552afb9caa622b86d99fb4da4435082a7d67d3b4ed73b4dc4c31a5dd77f46fad9ee7bd0f7f4f1cc25ad885b971c7bd6a48c5d5ba512a5dfdaed2705a6bdfc7b4da10d61c8a05283afa43ddfc21bc8551d75f9b3bb40e7fc22e5ba90c373a292a2aaa9fbaa39e3a2a0a0f608fd1240c2b9e8c8c8cdaa8710d949b299d12c7dfb3f961546105165a186d5edfba72f338aa71411f7d6ce3e6f1ce29f5c6c80556822836b01cb180e096d302397a77fae85a4617b83bb52074b979c4de63b4d267250611a61e8552ad87793e61f72e431a6f603e615fc22e85b3d011de404b61959889cc302702a10fa4be96a9985b0abf70c77dd775620ed252b87beac3e654b9ddfbe7a325829e1433fdeaf1d8f99eefd0365a82c0e4563147915bdffb828a3009a97f419ad74ffa583f6db32a8f864c8290248eaaf3be1678bf4f5382673daa46128786f7ce240e7dc1689af612f38d3494f98242a5279386a669e83ba6629600963eca78312d1a4fe6036540106cc5c080e0cb05419005ae3e10044140eabacec5dbed3e370d57d14a9caa537368d5b4ad568d6a967a9bb7b55a177cbad65a0fe9f5023f8a9b46787e279b5ccffa38e95c652078669bc37ade77bf567d109cb817fc9597a3c9ed2ccb86354e1f2f7c5f5e606aad55d3aa56b53ae2189898971bc35a7d5e4c4c4c4c8e9596c401189df42e47910b76d2a54030d43748e1ec7948dcb62e708025176760c1c5194434f160460749d8572f57d0a44b1632221d84162f8290b1fe0b8542b0414664662663d11a5e0fdc6029e5607f2004e85071bf5f7df7de6f3c8e2e3859525abf277754df1dde29d7e952f111c97e419525e5a33fc792c29b164a0915c1128b5e76c51e2e8b2dc5bafec6d747d625f2bf45fe2c297fd6940bf3810469e1296e802a11056e11c8fc8f25c543c9cd2ca90e9236d893cece4b6fe63b948adae7c891e3b53074b97ebeab046d7e0be96aaffe829e030e5e402e33c5d55e7e0b682c86ff7960f372f2f47cfd0f8178f012d3305b243e56a3ea12c3dc58f47ac22aab0973e39aaef2abc9c0954d3820c11cfc374093f5f0f89e509b4dd8cc36df6e7a4b7cec90fabffbb0f35eecbeefa847b3c4471a241f9de2ec587d08ab17c006e3688e244e68599f61660da8322fc71237d794d00f7b98986fb2363bc2b0f7bf09bb29b0b071e7747323764eb08ec98d9a255fe35f33c577fefd6b9ca40fec6f3eb3b08af598e60520e6a7d9687e87982fa6d9baa909f357bd7f23b076ecd8f106f81e3b441464f0f9d8ffbc8ee84a41e77bdee58a00cff7e83c90775393076686d34ddd889985736780b0000f0b6b8a248e3fcc6cfafdccc2196606338b8505789f30f606086b60e10afb8839a262618e028439a69edc5c73348bc2fed5c9cd51c4c74714ea21c2a4d0f3b18f89423f224c8fe842c13542c3de6d584a92799126a66443329d4c3385bd2794fe0e82f7d61cbdbc7c070b1d77edc1c2c63547d3da996b8e60a16714ba604f4436d404263a40ca62cfe363bea35e4fe8d47760a2d613b6cc9b4d9e29f3f78ca40f4ca4b3ce95662471bce7a7ac471422228b89da4c2774c1be0726ba10019e8f7d0408f03d1ffb1dd84b99d0eb892cf63da290abebe9115d1d1589e3dfd323ba601f135b459c00fe3a5238c3d04cc1c4165158e4ca7c8699ade0cebcfc8276cce47cb67846c7cc003e837806fcdc019ef98e8af4c179ff8ec9776edebf9be23c3eefdf2df19d10debf43f21d01bc7f77247d60e367f0c784f97ff0d9f1f752fe7491d6e1ff3d8fb0489b4d9e9e0f01f6a9c0f33f2f3ff9a5b0f33fa2908bc70bbd9ec87e5eca765e7eae9b8fbd503b91c1248b89ae1d381f7b21d81638acc862afc3236c1c0b3b8e2225d94fd84484efda1321dcf13c428963a190a4410f7044c988783390fd884d767cb7230ce1bb96de7305db1116a0c812069a3c950a09ab7f05ae9f23665b05d7d751bfc0f5070085eb838f53df27ac4f1c56ffa6be00c21a425861619d499caac46145faa7ea2075d4f7ca04ae3f8a1964826b5400c4ca03aeff8198dd46cc16d7aeb107626eecc1d39e07f2fa3de2ffebfb630007ffa2fc46e82004f2df18685289c3f0261cc21d841900af13baeac75ee7774257155dd08982451832219d271243828aac09fd9edf791e15dc5c730480af39f29d0c0b0300e1983362c18168f39238268cab7d7e4580e77bdebf0810e063ef5f0a3bdf230ab9154f643bef5f0a3a1f1385a4942620aa90e93cd0ac19c260983b0877c24e0b198d36ebaae43a6b85d9c67830da8cc3d99bd5d8e0cd72855275f0b459ee98e41a2daa910d73d5016798990b75d61cd5244d1e0c4cf1031cb32c69bc5103997f8dd2e4f1c00c2827888cd020e58accbfe668f2c829359488824a0c6e48c30532ff1aa4c913022ccc4041142d5a445de9b490f1ec8452d6b5b73301118ee0424a4600d1ea84525675b8d9c2ba211ede01101d929b5fddc4cd3635adc39559d89094850d47b89bb832e69249778d8e5553c4ed9f41c1cd39d84e3be382dba3cbc9c989494a424ecce783af077b68f5c1d79863336dc6a26beb9ceeb286b044b1ff0a4bb043d12736a4e01980c05932b1c1046749c58a7caa74da60a178be0f8aa78d8d0391901323b20222712518c61582e76b21902114bb4ae8ef9f435cece1a3fc29c67c04c3b8ed53f4f1f29d1c9d988ff2fbe682d22eddc02a018890fa2e1380489ca5cbbfb194924926f948646f8b12d1a22935a7a4947c2792ef4595467171239b60a1dd022b58a059b842b6522bb59366a25b899ed1f992ea2b134afe648653e5488ab835445ceea7b8050c94600160b9c5164cb3b18c16df489e6c49792c1d21752db22045104c829111b37c9907721ff4472a7e17dec984d2c7980bf5f2f761606062c27c710c7ea12f61ebe52787b06cdda7f763c22c84af7d98b0aa24cce71bec3b3916e81f09b3e5b67efe0b7df9243384652865440bb48e97965c82f412c2c0c084ac971f9d491c0f7d899111937b7f267d247e79f91be6217cdfbfa0eb32617ebf8ff92d524a5f6062e4108e09db49eb7861fd0d59ac59e8488e543dc586367f23021137c9958e906060b8eea1bb9f9113fbd1c2105d9ae93c34a2cf1ce63f23d21913e20c16373fce74867d47da16133540e11e2c998a889c70bf277d4a98af79f6a2f9c4c77e2042e6bb4cd0dec5bdc456137303d17e729ef52221dabb4c70201b56f231d682a0e87487d289001fad0541d12758a526664b31085a1bb6ffc8b7d31b0399b2d6d112678a58bebb67c7f27bce39e79c73ce39a5c491cfcad2b54f5a35aa6d53d69e1d58c2b0fc50f2bce4e4f4264ce5152c1f4b1ea07f51cca0682bd7b2fee9077d2e8f10b07cab0245076a1d2ed6a3db6fed573aa95daa73d4f15836f51481802ad0d63749e966a9bd4824295fd2c76f20c23d9b3c2f876dbfbdf04bfaacf0f6364adcd5b366db03396bfbd5f6ad2337e7ccea17e498f36eefe65e12677bd70e5773f17073cfb20b6f9fa554fdcd5240a2d03ff2017247f5db4b40cacf69cb8db374f25ed2879312c6799ef779dee77deddecb0f05570a53b6fa295b89424464abf744ed0502372cf92871cf1a0eec899e7463b05cd242021cd69995382ee9337ffb4eaae1afa5234b9271d588d580bb87cfaaa89254fdf484fb73431571236e3404ee7725aee44f1c4a7626dc6f3baaa5fce8fbbe9735c0dff7fdd58109fe1edc2106fcbdf7057dfe8417262092795ed7c25df7b4eb6634e06e660577393c81bb577d411df705a92e1786ed81b7233ed8c7f40a2b05d0816b353204ae46d6c0550a0e52683002d1e40a22197d2be75d9a6fe7df2ff094420220f0bcc1073c9fa796d1444ad175c25f7b625ca5a533d2e82c314829d2428a0d4460b7d771e8a4cd7ce9ffe9913f614c5a3145112c7ffc2b79a4cc913a72677b26099309a8c149c224c618c8431a52293544592f8f17f4356a9892020a14073636d287c3f25d3bdc89a5cd2871320858fee7c8f1923929756c0f9330faf2833a9fdd540173f30bf2dea98a523afe7c2075f4081cee441b89d3af0ab930c8c8612bca2b30b5314338c5518392144b6660ea62446e6f5540df77efdae5ea67ed67bdab1924c7a974c2167f5ecc819c0837647d36499181084a0a26fc8a4e8a3929a5f4007336a507a8417476d610cfef3929a5b4e7fc0ea4f4f9a4f43a95949c757777dfb44a3d1f65fb4c77efbbfabc8e568d9b2e8ce0788a2cdc2db258da665b5cadf04afac8954fbfc42d7edf57bb764a254ebbb77cbb5a751d35c10dd6a8437cbd841127049d6bf4b8584a0c4ad6c0fed655584a8961079c034b2930b4b144b734c670cb4721a602dc2f2b604491130e35d8125582a6389060d9148794155836c5b104ffa6e1dcc274c98a28a0e0290e71ec0110e717888888f01010b356520bee7c549f3f2c5d706188c52ed4df24ce9ccf85744e4cb710678b359cedcfb5b871b6138b4e3a0f77b91c58c70a86bca6fd37b1316d5336a60d8bcd89ce26ad4b5d70ad4c354a8552976a94caa403954b40404a452fd2c72afa6ce647dc48d1119ff98c09d8a426aa412daeebc8650297514d94cb04aea319d01935a248e88c26a1463fd8238b64912c1176893db24448d81b72681e1df1c8e1318fe6d193a9b4b4e4e35c5a9a50cc275369423197e6925492f23b20b7b4da8e29293961423a51b2ad55d153d433eb31d253d473a46746839fd98fd10f929fd94f921fa39a195cef756345eaa317f12827c3a19c0c8ff228992457a607efde2963aa48cd1997fae876041da8ef63fdb6d977ef569ea554455fdbea043c714c47a5038555685c4e07dbd7ece64da3e82837ea3156abca71ef514adf7eb92bb54f3f1f34a9c7a0e0eb2b0b267c019aabfad9f76c8fe175523ff54f9411ee42cf72e9286aed10ef31fa87b355ab3d46eb481a630aa91f236c7f98b0fd49d2fdb75269bf127357edb9ef7e663f493455948a0b149eb92679dfe51f25f8c708d79f24dcfc9fd90f12a35a7f983077c6b53f460ce8923cfc49d23aea7707a9493f491afbfc49f233fb49f263e4b566c45e4ed4b48751bd7cb6bffa9ef53caaa59f0ffbde77d282af7f118233f7f737204c1f687257cc12ca5d8559276f39d2bf7aa167cdb81d100f1efd935b157e3f4719501f685a1d5c5f93a19336597584e777b1336c7b96b0c548dbc7809e23ada331eb57abbf5f43abbd676f7bcbd5209f26e6ae3d475a07f7b43bb205b9ca4885e4862a25af428267ad1ff734cc3d487a6698fb8d76f37b8a7a8c70dcf72ce12e2ce3aa7a663eaabc086f6f27cfcb7362aef673905a84b9d7eac3843264408739b1e7c8f6b3937a8eb48e7e20ef29ea39d23fb26786fd55476774d226ffcb114ed830b9a0adae148456475896649a6843570a55a8434356c55b995c6ae924c05311566435ecd090790f33e963ceef292c168ba6bd6f5ad544397dba947b31b7d7de7b0b5bd6ed39d1d484c9747871e5e79ec22d61f5053ba5987492e7d6226c71287a2187eeef0f4c7912a40ff79e63ceb3facda5b7f781a0776f9757c0aa970ae8800a61958a7bc9d9489cfa1fe0fa5225e50a585239859ec0df25951aeae4a4554e3a6b9d5aad4995cea426f8a3e1e176104b7a3f55e3ca6f15b488b013899a9c445a984ea9260a854c98908ffdc20e1257be13612087c10bf4d93da597f8cb3b3969ad9ab66dd6725c93de5a85d4759ef77d5f5077f792debabb59fdacfb051519c1dd0261c96d10cbeeaea6852dad5fa65fa667667aa6bee3daad968ccc8cf705753432343434ad18181a9a974b4343c3a2597d343434ad5abaa16b4cabe851f5a9d6ae7508fddbaadc77773767fb35fa1bf02e18888b56f448d0b27b57ac7f9a46bffa2f5c2971f3060501249cb0bdcd54f934a2ab8992473ed28e42e24c6702679f8227d30a6ef628963c0a6bdfbb56777feb4e6741b0fa5dea58fbab246e9e2d1d55d81dfdee7dd0efc421ac0aed7343b8436981266dc772465763a5a629d570a9897299a07f5a7bcd8c6ddb680d174bb3a66926681d2360da5c479aeb680ea1d9fe6931ecf3d0f0b226f55fa5125d266caf7a1fdbab44d93ab2c52afa2acb59516b1db3ffe994ee70743f436b7fd57d278232b8b576b5ab2478d5a39ed735f6669067a4ddbc6c9cc7de1eabaf955559210d2f6be26ddbf67d9fb51dd7bd8a136992d6a139614dabce24795ce3b0145fb0cca1cd6d73296b3dcff37eeec0026541471fcba752a461dcecfeb8bebfa669fddcfbe8e7ea6687301de255874ee98e86b669dd6fe10dce1bcdd9229facd534ed7df86ba2c6fdb069f5becc5fb5aefbdb691ce7cf7157e344326ea678dbb696efac7e7b6a552a158bc57a1ffe2c11ea398e7bfb792ce0fe389b43819848e4a67a95083edd0c62cac2b436a367a8b46d7585450b15a201000000006314002020140c8884229148242293a54df614800c8096407456980a644912a42888318610830c210401008c01404343430400aee422c25c1b409928e20eb26c467c0e4ca6384123f21e09cd806d82881b4866fd25a7fed23b9acb61b290098e018edfb5bb9e21cd89d7ac1df6bd3347ae61c535983411fec740e701ce8e3fb0cfa34e2b99204ac9a302c38dd8a259c5e964b56bdfb10125041e50fabeea92cb5638f112d6e3873caeaba44b0e20b41cd7ef3330bfc28c4fa9cd75cf28fc109b8af91b5cb5907c933d9978380b3b4887e7d2728d18e87772fb6530beab6a59d51a72f8d93591281cb80ce7fc0a04dacf3a727aafc3f0399edb85b204f5c549a310d11a1c9b5dd6e7c60e4d358cad0d4a5f5a3cd33fda48c25def7975aeecebaf42ab3e8d50893749ea7ffe0b6cc64e05019fadf336e93d7cbbcee33491f23c0c47ed758366db341aac0d641b4fb8ba15db828c9412bbda9a1cf476b9ce011151c04ba06f966686a4a6ebe61f2b2940bba816e0b097b35df63cd01b5643b928bf454e46a412eeb093caef3b0b66afcc3c5643cea24a06056b9fcdfa2e200206261a8c55cef1fd4a8083e73ecb08876a1b0e590d302d5a8055cca152aa29dbcea115a317475ebd858c22c0f8e0101d20063efd3b15bf4189c1d696647d9dc16b69e31ee078410370651cdfd79bd7eaba7f00e486eb46568d16c967f040c54f215c588c8b9141876e9b2161733610158167465636dffc128422d80b4d1ad0baf3e46974ab82c18954d0b278e48754ad93f5606e8c3df45fbf7e732995d52053e372620a9fb041067f5b490c2905ed1766eaccc06f07868c18663c104ef83dd33a60e59aa4673b1d18a572c508d0aa2fb983a8db685abb247cbedcac8c08d53bd2187d30fef67432bd233578fc9645ff8651428ac1ef08711adec4267c74c76b42c9a28e40bb44c4ca21145826192e130cbacd09015225b02f4dff4fa4def57f6a3ae14a1498beaf29cd5c58ba9ddda438c7e0a8b18b6e23f43bf8be5fac35a57db64802cdf2cc494353b67b446a2d2af8b9dead45b252671aa86feccff95a8d56a6fd35b1915c9163a8c9851cb712e8b01bff87e93019fe2bcbb3aa243c6195e46bbe92dc8208896d3229ef1bc1a7369c056c73b272cc95544a8d001ccc0ff817a96916e667ebb3b1f5cbdd8e06a90b81c6a022225a07e826f3425aadbd8a36436f78e96077010642e9cf226d2db07f5a48d42c895d4669c2c494603b60f6115fd290434849859b3d1056181cb7778d112c23fc04aef0e956f05ed8f565773d2294992e504c1bc53209c0d928843211838d22a03a85041b11196ed8675977bfd12d56a5e538d96882458d6a4bae466d354830b3b634e47dce4a20d15c55154b21ea646ff767a085cdff7cf92086f4c8e943fe67d4974b10ba6c28d49a3fc88d11aa48dd3ac3654ef6a51b5834629eb151865858997f3074d67b7795c2b78f7fcba31abaf0893225bad87539905d63908c9d79f3520ca655b4cd85a4715ac94542c407c3315295104458f2ba05c4f8d4d08e4429def6b97f83ef2844abf2a67d29744dbce9f04dcd950e150ebfeb325ddb6606dcc98f16203f30687c5fa05936aab142fedd9e42dffcf696505a4a8c76e24652903024fc2ce065ba9d7560f0984a920434378dd8d28e783bf809d85ddf2633d67d914eca9218ed7d2b580f3b714536074b1481e0743c42bfd2dc6741380e01d697c2b5415bf2578114c3bbe1d544b0a7d0911cad4ccf1ab60f2d3892632851816d678f036f7e57967a52b43c4934ded092c80ef12fe14513f0458894230aca858b3c194b35f23fa1ba520523f0a49d6816553c27ccd25537c2586d8cb8eaeb1cd68320f5db5528fd1b055d11e892134d331ca5c985967ad7cbc70b5b5567516d119206b66638cd70be91a4875b7af422ae1add03662c3d9d6c849ece67f94a56b8300a3d882df4f45899ea7566a67a6216a74df40ec019040830582e17cd7275a5668c2871e0936baff0d52fa4e8e69f58d9a75deea2116157237ddf1d7365d682a056db73cd62550d54290b809d1525597a9fb037fbfd4cd1b80f1956a692acd31f89d0ec09e158ca6aa665ecf4c968b47e85221a705932383c4a37869b088b4fea63ae5ad8deb1097823e019b55b9d4daa4beb1e6514c12575db7c674fb4dd2afe1a472dc3c002fa6c2540c0eb92b8d91cf00bfc5869260cadc32373791e8902814afc644cd34ed76df5f8b1b644fee5e142e4d6bfbc0fe5f265a52f3aaf0b7c5fb0dc41775bd0e958503c2f151743dc6f82c4fdc409e6ed5fe69d519c434461d0bf58010665ca1f9dd567c9819cfba2b90e40932dec918212672edbd42d1112db18c64021298cd19a7c92f55a569b3b32478087576e2b630f48311f526cb00b5a91cfa94e7546672a84806c030c3da0e07d1d82dc5c9861ea6f9b1c59caaab286728b20a831034988d6702d03c6f17c8d2870fc0ad1df150dc470232a558bdc887a744a94919be00153ef2d02f02834a7446f198e42e201e921bbb00252fedbe01347ef0b2cb68248b713642484827323a85fe07739d929ecc90094de9b45de055d651e4f97c4caa2e2468dd42da3a803a24e4ff3573aa4f050f6043f551d4265647bd0d8b1e9481375274c1464a1ea1d3fe4b13ec3a574fe9df3b53fb48980cc2ebcc06956fed453d3fb16d743fd4da5082e1afaeb3e817422912ed3bba69834bc1539217d583b874ff4c8d1eff11133568ebddbadc7ad813a9ae5978064bcdf79f4ce3a971666696d907754148d3fd07c6dad2fb831f2f8ce83ae009b00ffe8763fbf180823d755784853e96450dfaa90518b1b6fff2053c4d3e987786e25cfc962a941ecaca1e8ca51e65ee51037cf8b31e3c4514581a3f2065b3d174aa4d9952551d83aa06c220adbbca35ae8039840bfb0044ab0ba205e1f4c15b107343242f34adb20bb7662a1aef18fd68975e9e62310082c51c093c34465412a7f73ead062e09b90f3a6e08a0e8e980cea89a662ab1571400caf84b803b1028eaf2ce6eae6fcac5cfd624cec951b634f8d3d997a2eabefddcd302c7e3995f76fee067faf772534f42f199161e940aadf538cecb8eddfa562feb9d7fcc3ad3b0848943c2414bb7896d9c44d6828c1e07131f229093924dc94785a1e6a6ad4950642c23dd6510e9751ea8703dad3b5a9c9f84b7cf50093acf6cced5a7ae14ecb4d61f2df8ff48003f171d62eddbb3f8dc27de10f5a13388f23ff4880f7b55ecf0ea045b79be4c886a9e070afbc84b2bbcc63d8b821079578561ad2180ada681d20603b1caa2bfc11d4bd68e05b40b363fcb86e5ab7913fcd82d94952c7d6a6892d806ea8ad1bfa2059b55f26521635fa55eeb034a9e2e915c4e64e9f07826570d51d6aebc25f2377ea937a705042fe0565018f7e98624fa6b2c71e958cd3f587a2b74b64056288f8492a2426cd04e093ca272cb7743b7760b71cc17ecb1b7092bdb604278725e47cf74776b05928854b1615cd5586c668acc1aa4bc4b50e3608c6e73768f81f7d9571a9135c18646c2ffc514cc139bbb90f72d73aeae6ef34c252bcca1b1cff21ac1866e7daf216636f489eb34150c73579b6270a8dfdbf5abba30dd237a85bd71aa448d7a0acf0e7d7d398d14d5314f169c6e5062ceb6987180f30883590d8c9741ec84eea12fa4ff6640d791d999a948d6ef5beec0a82415d8689c55e0933f5004c327ab367527dda88912b607c5a15eff1a7bd4b854ecce72f51db5452de780e4b978344dc2b381c44ff774a80454710d161431e74c3d6d7702e850e167c0debb2e45bf9f7fb7d163fbd895dc86771af436641f81b981bfabf82c5d960736d798b9c7983f6e8d910d86ded5d11aa409f503313cb0db36ef6f32925aa9171c5740b9804b53d9eff0ce06571de0c3f4b5b8fdc8c88688daf418731bbf6335183e399d655f4217c994669ad17e52a76f2c586492c12e51baa4b3b3fe6bc4e90963305e3c9a989db7762d0fc0fa47f51e85ee0f752b1e2cc3970b5477aea6e236661567a1dd698c456b4bd78cac06f6fe2b6f32cf2eb1ffa02f420094950fabbc3b4a69383f49fe2172d5287d4031f99a9e38c805c40ee340ae710be2a85634dd7683dbe9c15d82204e46ecf5d9d490c6d1e182ca6377ed318b693798ac8d81d7a1d3bb8a83980e658f059776dc90aee5b725cc80668fda78c249db8a8c0841b3bf12005f9dfa3598bd9dc81ce940459e25187078bdf572154986990022df610ef65daa4c63b967c0f43e0ceaf00d2df3631215830e39a020cea26936834524a697d39a942d8948fd8940cd9b4403d6d7e7c736dd50e4ea9bf357d2e849cfbb77ed13c956aed8ba06a4d32e8ce4e03a14443d0010cf8fb1ed113b29da6bd1f6b5e533692db9d789085ed0f148305232a9831d89899b16fa2af2046f8b03ca4547aff8ebbd528083941383c979986cbadae1776afcf08a15ac6042845f5606dbbb695dbfca71ab2707c0ed69372c87cb3e41a94a1c90dfc8675bbc544c4df9ff119a6499d0b9904f4f42fa8ab957b855d4594cd4e5d0a6ad61bcae75da6926238e1b8ec604b7a7cd2d180830bd0cb19ad4b1512cecb9aad4c572f1353b22cbdd14947be4a5c33a0a8ca9858eab510520fc0dc34d12438bc542bf1acacd5ea413832a1940d3a03a8e6bfecec3c509d5d99caab6f51b4ae2d274bc1371ac6de195dfb49c82a44088f01ef443c060b270f5090c59b1f4010c7c002b89b10818b1a06a6842824797aa02db251def55a77b253fb3e4726d80726ed7f7c731dfd28de177b51a2da2ad053c6bac834b5a0d0a77faf3ace420908064b81473e4dcb245d9ce5cdff85528101d5507c00634b9e16c9bc75378c34e87e4adf8645d93f469a6a23894c6d733b4217db0e9df422e12275acf9ee458f468244af767a94eafad59aba631e4cac2b075c7571ed58d456c4489ec37a9890ae283eb948e1a3b89d5862be3f4e8c862aa99cddef698b82c33c15417ae0a255a1ceb96330b392799802d8a46e7c8b261ee042e3ad724e164788d76040acd0603da03294e2508071c3e530fcb8b58da6753788aec434c12d3419ad3ff8f8c0e170968b1ba7138b5cc1545845839b332e50871575ce10bf160e0f41fdbbc0384eb43928e557d38707c0c13d6b7a6c099905a8acfc2b0e89b642e805554a922460cfba5f068ca9e023d75fd5a85c3388ae01c747417386102a2e7af995c6f57076f9a5f9bb44b4c6cb3e151bd707c867ad32a9fa8402496625b0ed9228d4bd25c12cd1b7b1791139b069858b4af5d9cdd123f08f104372830946c62d6c5a005e66a71c8ca03f4eec93b5490d9a4a1c182a4216024b3b6c727e3ed8dc95811074b93ecfd9b974ccdcf468057b21fd6408aeaba20377614c9b51db9e9f8cc89e9d0f436b332ae5f26db18d9cf42f5159ff5945416a5e2417958d6163b5271a96f80ef40236be2ab7db8e437a2d2ac96ab5ac70bf393edc568d822b927b42486289edc42d159d5fca889949060c3d7efa6e0c400dad4466a2741bd18d90ad59a96f062dc3e79c0a39dfe53ad5d984bb41aabba11bac60942c21f34519942f3398ce1e7aa20283ac9858c0521b0fd663d13819fa3375f57187899b0167b0689ffe8a5c361a55344e0f2e1bc05c3bb22d6af9b8eba2981502fe6699debb8da6cccc85e825ec7402ecd86df099debaaff7fa142046869cf2d1152dd1efdac35e1b001a0518bb88567e1ca99d428e288a57e0b5a6b841fafff2ad2c108fa2fc2523dd871aa26c94c98e16524120fca53badf3e8b00e968a4cb587200bf61283ffadcfa5ff56ee806088458029716b83942e042aad8227ddd662b456cb15b00cf1cbda8ef12a5eb17cc7fa6f9246c4ba2a04bf2fc249c179b025898d31b98011443ecaa253e1cf1a7d0e0d55ff67e3d1ae2227235c541fdc5fb31a46df1b8c46b521cbb95f6f5d0ba830a81b18ab2610a8ee82499512540f17a40b22386f581cbe0bf23e611c72c78aad9f3abf7d85c019d4bb62c868bf2806a99ba86ea292ecfd3c6cfde936a26f9fb950f90f5288764957696109423796ce9963d9ab4e19802bb65d48cdd56152e5f643f380c4894aff50895998446461dd5301e1d7f5e6f58bb334401878284220a76f03daa3171ca1ddf8db8290cbf05ba75b3b3041283382c86471e6c5182c0dc7b9465cce2e561e91999a7c51b72c5304ed27f8a1640d0d5c8d9d9ed983bd7790559c1e42db2b2619e78b9d76a3de1e13296d5de8519f53518902b672424412815e61fd93b6ca60bd698c20016c22df33fdf9c31c3c52e5a662206e13056b9e3a4183fd5d4976946aa3a3cb6657ab094217ea73550b60d192d47ed7cd074fc21b6bd422e3abcfc3962dcc5d3d646dada5dd159813f73574754881af605dbe640ae465338528e8666020eddd7bf68ff618ae46df250c4f3e95614a7b031b61ce2a08821c461a786cdceb411bbc8d04a8f07ec238175fbac1eaa1afd6fabb9a3f1002f44aaf1b124da9732abc350a639f4b99388f98f44e5a2696c7040cf5b7d506c19016c77c52c97c7a3c20fbc95b37f7336ceeb3674255157c051b3b2d8914ec9f6d149f2f6fe733b2b3eff5bbb7ae9d546f4719689d2f878a812a465c02f0141735506285c2157f1be28365e64e16bb71548a903e6da6529df38d3ad94ef406a251b8493df64327cd325aa5726ea516347ec64fb68f216393c283f37c87150fb657197202e92137395a4b09e8cbda36b43bcc692b979879d2e3b8e42c96844299792da3ab1fcfc37bde9c39f6dc171f4a33e65a1983f428c670535d15f77ac26f7fbfe829330826ed5bbcda628a4c19dd179d128e144e91e1bf86748723666f33d06a59ed409f2440f29ed62394d3bd0826cf724f3f411a8e0843387904aac233643eb07e2fcbc5374f7fa3f242a69d1b861e4c4eeef8d3bc11ceb00d62eceb32a20aae06662fc05a56d85926e842b2b0e289b03661725bf5325679685df8e452780458fa1939b48ab338a43fed970976b0366ecdd566c6b7dff6ebbc0209b8e6a8026ade51a46604a35556b0242887b5adec84049ff69675593c3b7a9f9ea57f4543bc50dce5f8eecdc7f8ca263b4f513193111cb95d4eda3e43e63c63a63e018d9dde38d1e0b839aec759e98b6fd0aa665ca5b4e9b31314f3a86f5a4939d6bd237f7c71fc4df9187e7ea9136f81d6349f7e8811ecf9364b1cc32d6d1dce58d580dcffed40912f6e77f6d38edf1966062515fdbc90e90e0d589cae75202c7f71df37eda04a1ebdfa75db33b06730d5f2d15ac19082d50745f0af0e379fad9f45e50de691fe3c97ddf2921897fcecdafdafe0f28376e8e7ac0898abb3b4025896b504a87e758fbba6e94e088e825aeb08569cc191b3c76ff25168f9ce5064c4c7d4b4262a2c25a6dc483a910316781e43d102a9b46c89c3e45570f11fe32cb44bfdccee25e729092819a5db246a6983e2693391236edc243fa36c41f09ac49f17e54779e5e6c59fba3ca25fbaf76deef123278e164c2a2e512181de16ec1e13df15cad237c8050bb325080019f6bf9503fef2c349447b0a219740b9dd0dfd59ec9cc703ebe1766c30e7c5b9c5f698684202a4f810c175c8a82a8315e4b943032488f1c271d4e38b991b26593e4213819ba9b6a8a94fd910191dadbc9909dd208840a8f86748dbc38525a045b34feb36119da90c53867c842a125a75315f7f72cf03caef6a8403a3ac80f7b13435028280989b1b27bcad96a69fc0ac1ad7144e6c933636ee03cd3c2406e18084ba14f35100b8b93f764ced4421dd39b19e8537adb5b92b4bcdc12ec2510a0175414b949dd184ecf8f5a51bea43e01ad83642eca4450700fa2ae5439389dd2087a879e6437e211429cb8d793ba0c7a8343f66271b962ddc50df45a5f4abafb0a4e422bcf343dab330db7cc8956b3339c573108a3dc22084931bd78f30c499bfb4cb83a08b634831b82bf1c289f0a6353516839e5699a11785dcd86432ad2eb5583b1844c1394ae606f037f63962b6fa4fc111bfc2fcc1a4addf1b160143758a482826b368f0a170676b5d6af0e99e9abdee8127156fc8ae5127d4635ce9aad329a073ad3415dd3ad5cfbe3f78ba0fc466a8e65e1e0ced4191f5efe946bdd44690803352a126fe0517bf702fc8fb27be6929ba301b743df3a248dce1ba2a7ecb0e3f28dac4790a9930a856b5088117658161be4be3faf0c3717e5364ab5e985c65360c50b3e0e5faea08977cb9e0ef5edaa9e0e6a66395fed0aa99c1c1282ec66695e26edc6b1ba3e5541c3fe8be6564bd99a2194d8294b9e47c1c8de121e466b93eb01f7e95bed99b92ed747ffb574914d383ac2dd9fdb35baa30c80dfcb109b841855a592e904926d42505f2b32d0bcb2b197ef12be832202f3a252e4ba7a00062c876b0e8a04d248d392c7519cab561646b742a2b4d82a441c59471facef7b7c9646baef74c462645f3f3b9a2a75c0f4c337205baaa817411190715325bc2d487c70721d1cca2ae134521bd37aa4d798bba709594a653b0e520907838ce9b1e92dcf5f46a965fc34d14816fc2b030f2107c41014928710974e5626d9f6750a1e3ca7e187c99018424f2085f012bcf7cc8407146af5b71c924709513a1a39f5db4c52d034386f5b88ebeec20660f507275003791a873f0bc1930c9969a71fec72c5126d315220e185cb8c0a98d4f122f3098b863b3dea7c210735e182ca33c3b904320a831521f490bd039520ba8af33cd7723b1fc87eac7f388f9db5765d446d479611e9ca6fb8071215708a1045cd8da5aa543084f2d151b0fa174aa9227005f32028306a813fce3642c56a29c53cd81a14acd56bfb72a744e471784c7cf6feb345f98c4d7d4c328ddab567f041d92a739be86a59a10c308777d229b4761224579f0f9f07fd64ec6cdef699e7454adcf10c6c75e2073b49404ba2b599b40f45460adc241144876d5009a7a49617acd5e7537852622ce2d245bdfa6a4243f4a69722852c764ea56204b2484c0c0b74dc2eaeac48c848b2271d90ddacbfb335379fbd9cbebe8cde3ecc51f46249a1b4a8f28bb73535d9aed296a4d3f7a56d712f9566d8eef059b3116e2f9a7ff71c81890a776f38769b5241103a93f1738d0b036516bd2d8a9c23b702e4c8765400a516ee70b01b1db4c04eba50ab336d5abef645a6e1a6fb1c402ef812358c3b2ab86c4981b140046337f47b12c51438da2292b7e8e80a22db7528eb425191c9e3f47c1b245d51334962d66a7c1a3d49c9afa955b2f983f00c3f8abb975d8981b85473c7b70ea2cc9bf639176d5a8faa700226f5039a806ba157a71c6a21cd1af0d2fd40c4f03c3a3404f98b1d66b1f7f68dcedae8912ef4961e13a0a596484fc30331e2b48c77d22c5fe401f30c4316d412f6dfed24d31ab1210c6bf62ddbfdafa516c17b51cc8fa9f6949fc53f7a87ada6432210334038243b8a43d5128ef6cf12c84fcf483cd2497ccd6da4551eff76c3e3a5b2980286cf94523d43cc294adce323eaac5b73819125449fda5a315c002b0f528a4f714f0daa2d217d6024654ebde0c37bbeee862e24220cd43e3040fe850614c216a43db89a13daa22bc2daea7e4abfd96568dafdf958568e2bb636ab03157debb5a2ba74820a8ead9736c2b2edcda111922103505063e746963aff20235dc77d513b4422ad97bb8a373cb93dc3b8ad720fb70897bff6a518cc3a05bedbcedd82db38f7fb61e7636f6aa549f4ec7bea03a6ff8e42c862af9cdd34246a9e46bc2ae4cb75f1c6845c4b014358927bafe774add24f5e307b85dc649ddb78d37804abd3f39463bd6b66717e44050fa24cb7d2794deeef228ada03cf70d3c6bfc0d2175550ee119c021fc1d70c79a5fcd675cc19d1a2a9e509809c884099c780a0c354c13b581816727c9f45f2e9f4613f33c62f2e1dd3971f7e30e7cec9a730afed221dfbf73d4b6b02bf6bf40150cb5730ed31541bba4a8bc782c3f567ce0d094882eb2d6b1f7cc62bc71e47b70751e25c01dcb8ab49a0616967bc702b18e31c3bdfea34a9d3a3085b8dfd7bff0f141d7f1bf0160fd6248d94168c7aa45faab5d9108af951b764daab5cfed09719b05e8559dc2b0bd2f915c94fd8de1f9be9daffebd1df301dbb1c67601ed83e2df9b15d33549dd4907d7c86187735b3d20823e787558e14e66f5fd4da8c327155bc181c57227c6a60b3d2169994ce20925e003ee08e2afcdf41285ea8c75a12973093f65d4a9cc62027917bde4b4deb5bcb1363fe016251a4730039ee42302a5416bf3e532d3ac3e3d39bea68a513feabe57f3d61741a45ae02437941b33331ff400646acdf7cc1584fe709a3eaec3761af5d8b9772384f7a5b4f1b24f25b5f60d8a7677f0650a28aaf15ab90387f12e1e12e5c7b44fa5c97bab3224a5320e2c9f2f57f45b3b5527c9710f742e2eb62abc9beac2623227052ee8d459adfd83a6dd2d7c19405359ad6aef4be51335229d7c49b1ad27b5ae751a105451ed0d06f0e74ffe6459907bd29df06a433b86d2f235d4b36c1dbc2ae3f8b8adc1b91e4dba5038106397575f086093f8a06d50ab3b17c563f791e9d8ca79bb3415b5a9ec4cd082ef87735dc5a9681ac20055bb03e6347a1e33ff0116872a83da120114ce9851eff87b038ef558e1bc35cede5c7ae5c48d00bf5a66dece425a0c8229d3296dc57b7adccdcf8672b936a02c07a8e5fdc7ba1e864bb9aebf215cf825a1b5864c8688f0461eb4b412a192c475209ef984f2a9112db1457501d6d78739db2d59464eed9d1c1752963d7d46da2047d243bd7da79906a1d7c9178e6320eecfb4909940ad4f25c54d398b69f004e018dfb95a8a57d34245d9312990da20ba377e4631840280de6e8514c23f5b12d2e0b721a7e6750cd11da8f4a4a1654aa27f7ad5538fdc4d2198d63a8552cd7355e4ba8076fbf4e70d0b865ef3bf86f3c598fa2544dd51a2e390b676dee7eb106cde76634c3e6429411c60ae414c8df480fad4d951808e6a8415119187f4d551a9230c9ef9bf8be08199c6c8d143e0bdeebfe81199966607e7ea44e15632b06857188dfce1b5faa4abaac8962d54db08bca769d49b0b016a9036595ea2127dcfb24abf8b5a2dcdfbd42780a9aed3b8b24dc850427079d493aa4a18a21a7b177c58b41e1592dea920c1f7d94ef5e596ad5229f9f8ef63baae55b8b309d58d00eebc9e33819b990d9cc66a7a05cbb93df6b584952db14489b28300ee6306c633b78313ba23feb4d5efff64f61090ffbea52c957d132bc21b35d0c06b290c7478b3359f5eb30687d2b9f4942bc4c029876ee70e418b5ee639bc2aa1439b28adcc0429b794e851c62a7d7a89deb2dec1d18b07b245544c6f4ef7da660a880120b71c2efcdddf48381b793b2a4cfd757efb0d2505de7812420fed223180aac2eb3a11dbb8b77329f392a78aefa4e462b4207ab417d76d19fa52b8969a2641105139371cc99cbb095776237fae3b5a5bd45ff4e8733dbb350a5ce12d09cce8ce5866e7021e40a1f19ac2c62a18265ab52a4018ad825f0850aa5b419ce3048b1c017fe22b0409557c2908692b49819a56ed92947f0c714a000ed6ce05ed055a54170649f4e89cb8db4f7d40a8be6c9805bc5bc43c0c53462ad3d16ecbba7f8b71334e667eb37b5fc3e425ef157e80d0bffe5adcd0f9a40304e703a1c5322d1b26f78a6c973322d93fdd9f15f4569680ebe64b350ef44065ebf5a5bee72e695f4d666c9409a9f76653fada14c18213b94b43d5126b47d8597b3071f1fb81cadad7339f50556fab0e5e96043417adea35e2c36df8ded1ccde8596ef0c3f2841aa0eee481da7e4f506bd1338790b8bb6b7c8cb63c461804ad72ceea2e753670e6172e96990c8def287183e7ede12f904133ce74389e9cceb8032b31add1c0b1b0c81523d3a4103ba9c551f67877597ea9c72680383a158fe3f46dfbf53c468f1d2ab480e74b685d1a75f725a01c71bdb7b85d5e0935979e5f0aaf37d46aed0d6c60ca92275c7588f2e5f5d85b5c44e291417945fce417d641ab04adb30afb3186d3e39372e56caf2dac77443ec2eee7854ee226ade8c564c9f38b5371d035f4772ed7b6483adabb008afa78cb7b14a27857e2fb2f7deba0c45d36f9063c2ccc83eeb7eb2d3fd9ea678993142b1bae166b457179d99f9526744cd663746aa949975200ff8550c825b3be630c91dcc7aba7b7bc23ddedbe6ba2d8ced831e2f697377e315bd7a1cb20d484a33ae78016196570f51c728e73e5b958d76b2ce32bb1bc6a42c406f71f777ecb03212de2c6fe107f5ab56ec65e1c067331292f29a6bd37013ab47b9c1e08a68bdca526a2f6aa91a88ab4bbed003675564ceb2645bf7122885006760a60ad29efae8b15df6afaff56cc289996b2bcb8916c4874594215b9ff6363332ce1aa7a41dbb08403893d472644d095a0b8c07042b1a985b265a6709727432c9aee8646052d896119356666ae01de1a3fdbfb56dd31bb761b3a496503313fa5349ef7690feb4706a374a706a200aab6aa9b3250b482799f4f61cb30b7a93f969c5581ede95a75fb7c03bdea398e8a1d3d7f158d46fa82a6261188c5ef45ffc86c9aa92699423d72f037168632f21984cd2cabd2cc3b7d73513653e672ec6bfa756a4369db82ae083a1cb0be5338f0d77ef1f9a2fba5b6c5d2daf2c70dd686ab97bb9692c22071944ef011a9c1809869dc625d463d3afcce057a9d81ac53904549a9a0f81e2083b59c79767ec759aa1806729cab141e0f956c90aa31af875e608502ac8d30e1f0a3a2ff5c1ee736e9016557fa52b2de80774c41a6806c33ed42e2206712fc1ab5c5cfc1ec15ece64796cdec08d0a4930ba79f3884b195588a3f6fe5fe9f3832a1b7af239389136f9a4f13b7e4a39ae151e806ecb88d0789a56087b1b57918673c0042867d8e165c14e771525022744272dabbb0b690a54cf75a5866b1b99aff5b9171cee724000267e8fd5641baca9b165be0e0ec89be773148de447e396c457926fecc0d268097017c6309d21fb2d9ba4c51a223871e051d67b5027efbfc1b6072cc49b2aff51bf45ee43790c13d4b3200e4b4c63e567ab068a97f8c2e104e60aad32cc26765e6736c1bcc5dbfa4c7b7d06dd3df1f0b2273a17d1c2ed4efe319af4c1c75b3e9db9fa091f16916adee9dc904b7d098442f04f6903be6f57c0ff02b64f2dc64fde3847867f67b926a49e216b0b8a7f95dd06889afeceafb764ec59893afafebfd29a729bb316342132edda6b82acc900931afd7ca5961a71d8f257fd2146cd008be2cbe058f0eb8c0ed7412d5717129d5ab894f36ce8d88f5ad98d88f38801776ece2f5409b6e6a60080714d758246adac7616fa07442e474759af259b90e6130c8d74173f51b38b7acf4d7304dd4a73b5ceb962ba1c0eeefafd8004d16944b764ebed35a661736bfcf126391696ded0ddc1cbaceee7341b3ac93a92ca0e0389a9133383eba74b48ff92f661efa6009e1ad7f577d85a364ebe7dafb4ace97eb81f40a0b550fae8e7d8bf2f82db0f5718ecaf267d2fdfae7bff409de286136e5984b6ed9bddf1dd90c82bc51b60fc677c4452406cd17c22136c6bbfb988b124f59a8ed28b2afea11fb1019d989a803ba6933ae28b46291e31ed4497c26e75446c536ad57e8010b931056c3db399fe1629bfa70ef5ce9798e41a096dd92266bc322c03024a8e7fe08730ea24731447a8bd6fdaba5860534e1a5a653db0fb267cf727ec22727489c6f0f928cbc293d9eba86f9494a0e75c3513fa3417a2b1e73cc517b4c8795660b849d2affe8c5dd6643bee971f460e06597392ee3d7a3f55cfc0f0bb47549026ff7e7d2d71277f1c37febcc8a970f6bf97e575c59187fa440131fa061922660248c870adfdb904cac8087fc31555c23e0e2b28cb5dda57f664d9bcee46ae9cdbaf4f6720dd332a973e3dc4b4bc24ec00a2dd2a104112f8cdab0c4751bf6db2d04074f3bc172a45bc672769d6734814beb915069308133d3d54e197c0c3beb90d63eecd7f07de539d63c038a12c2fda6d190fe1b366b35c755584ceb151943debdacad43cfb8b7aede5fbd6df0944cc3de024459dc12ab7d8f725d9652d64155ea037b32c516a1150a92ba052c780cae29d00b38a3f14ca4a9d5fe5dc13c8310b7ac7b5d43d6e5922eae8545e663157aba0a7a3967965c4620207d9ec45ec5866bf85c4d5b327692435dd744519b1249d3e6cf478705f009edd59b0725110ff521c35c0a274059e64b3183fe2dbf596a9e08c92404852448920e351a0772a0f9d6ba74d9cf80dd0b3042f2bdb267463afb99b28e48695ef3f6519d63559950b9784cd299c671252ab59af537158baff82d7bd37498d82818d71e20d95e851dc4f639460c85160ac8d2adae230f4dccc3d1ee79e197d0a56cd2216d1b7bb8f0869e02bd2ee6770ef5d4653227795f6291421174e83ee423635ba6f80f4da4cd8f940580688807cfc7c08199ec62af11d52e0743b2bb07aafa493b36c7ce06efc63812abf34ceb58cca7a356fd977895a3701ac29e3456a970b47c133f147d3d24cfbf44082563aa93bb200d61e1d64baa996a5d44f0699a4771962028e1f48ac572ceb8728184eb501e26295c1602e05509b28583913a8595361a165f83d6c30e3f24ea5bd533ac28b9aeeb49443291409b772dfdc9489ae4ebb91be91d7775c2531eda23dd1a2b16c0a3768f692ea8ce0eeca52381266fb0611124e5f995cc1cdd3f6d28c627d4db13e7d4dafdb8cae1c72c18427e317e17fe5f9cc82f8cd2c7639c288344d8cddfd44e5558636f806edbf028b0adef454ce98cad30c6137af05be0b26eea1984ee84547aca7cc72ef5ee810d90ed4fb7f20e5fe439f96b4643d2ddd22f4dc2315e2bbeca7c45273f0b2b88977d8f521814b6af96199f1be19d6f24c55730953202de688361c21a1be0281e358fcc696bda63d5bac35404fac77acfb5ac9caf2a5d4dfd8e17ed5649243177792fdb129061c9bc0bb9ffd63f8cdf76d007f482cefa996b42dcfab15ad84d2fe4ab3048a044948eaaa3c23769982df4596016a9414a30379b3a4721a6963167184573d1c63a629137ed4137d81cfe34666f6e8a0430ce1401e5481a5b1c135ef99dd37a739196c45fae34ac4d52b8065a9d2206543db42ddeaec865650bc8706b1eba88685209b8177241691c9861d87d5fd98fe4222a9212379d247bd6963779b0d76fa983a5cdd26a077fd60ac2e930b8a6b651cd7028fa5e543cece6b7673fc2d26b2bab24381489522f9a5d0871b8226c91a6ea944ea13992e41b1ce7194fcd41b60ed84cc73f0b1028c9ccd41cf7a6198a8eda1a8932b6f86436fee8a1b3302cb29625f1d4733ad61b47422f2110e56ba29396309b73f8b8b57303cd740dbe149178ae6fc773d355de3c96f594f027f93608f751b2e6b9d0b9b4926738b93dbfd9ade89bc4752c02227939a8ed400f8d6a5547dc2adeedc65638689da9ec4c7d474237da31da7f4d3d5215af72e68bd44437a96aa3dca961004bc3c5aa73c6b37cea15f9878e4294442db40a33315cae36aabe4033672fe140dc5606212e01830c69f5f9420c7ce2695673e362fa6d0dfff07082bb13c660f8c89b90f9ff5866ca1cac1e05ddbefd3cdb9a45d24660949156a8ccc11296981325f33081e9dc904b891f9615542430b1d4a8ed5c48c8ace2ecb400f7778d3201093fbdf95c4a0b69e68b5355632c599d14c38def030e9da40e300859f391a2b243a6decff44b7d4f4c5f919d539d9aefb8d99e546e5b5de2f8a3890f36574d8370977a7068bd05559001ec588d2454d9e5ae3313813db28ce4dd862e61d48f6c22592b460111b9dc22d1b2065894e216d2b1a4ad7f9b0218dbefd9e92dc1da75644e2f371822c4366604ad21dbde36d00273c6e822667d3718489974a4b612f731fa42d1b65955e936bb709bb518250ce3bc9e1904c3ff28fbe4ec73c6388645c3ccc9021bbc7625356033a63305d99ff977ecaefef92c7fbc3f8ddf9ce41c79aba760c8ac6c80768705730024491c455de47537910ef656a4f42ee0948424b4d4b85777a46beb431bfb50b884e81743fcda2020d5cec1d8386718eaf2fc6e13bf07b3604d8920250e8f007bf5c0d5cbdd60e37647d110747578bd82ea60debf69acc45e19482d1fc3554d4b28b9d038d49ba255f2adb8311f0960d2afda5a51644442b4e40aa1e41b69e6c7dcc5179b0f95826153a6ca9486bd128aad58b0a336568773eaeb86a2bd692ca20e62cd5e038c39f3c94a3c9801288a1a88dd18834250f6d15c9435c1c79e84edffe718de1f35667d6f2a3ae7f8a4b92d8ea5bd6ab04b8050c9f96beec53e651b8a7b93c6488b9cf54a042da39288c16cd509b0a432ea019b5e86f000f254526f8b0b5cfd81baab0557f1d3e9cd23b66a632f6d7469a468b7b706d54e07b6de02dc7ee18e8bd010581db5fe1f704ef8f096e472ad6eb6ded8ca5cfec652469d5b3ceb6a0cb082f8047fe02c4f07130be996a5ae60ff6c26907e953133483d3add1ff4b225db144c1399a04f92c3f6a898e7c7419d635a49b9ed30360d9e28331780c7bce7d4e144c5514cb0c3d585388c49bab4dd60c004e6c356d189f7e4c050128bc1f68ba4dc7c38cdc448fff26dd1a5fde07a2e944895c691d93ad751cb3791ea7f2a765ed4aa0a80a86cc0ad60ac8f991206527ed17744d3455179be059593a08763bc60d897d52601e8749648c5ccf154c9ce391fd0d471d14dada550208a7a13c7b494c1be566e9ecd80e2712b299b8ec4fbf8f3d9051266fed96ce7daa366b8018ed4a4ba912be0a4a836e97c5331eb903875a6c0769430ee39a64e41f86978df19fd6db13d5d2a4b2315c2760daa13af07352597978e644984326de9a1c9cea04b1ee64723de5f04cc04bc64f9e9c8f68ca2cc0ed0f9fb183bb24f3a52bf287b9fd15abce51e10b41a63044a7a96957b5233689fcf7fd81d134f799272b7721acfa8ce57b4124c2ba32fb6fac6b4ed6e8640cb28c7cb983cb0cf914c6439decdd0089e3f2ac97cf3d8b3031cec86ab8fd2ccf2d88a7cee8c0e4439ff04c46784be4150842405f53208cd8566db055e3414c39707ae11d11eaf7937aa40835bc5440d71826f6d6a8901e122b307452c94525d009aa8e34a5a2d6726b39f1383970d20545b3959638a33cd9898677cc55f51e90ab63da394f67420942aac0c90309e28870d286770927707582295f4328a24ebed40758d469cbf79449524862cd6e882ad193330e91f1fc4d3b55893a67deb7038d2617b389d5d01178946ccc02d0303c2428028150837a573ea31bae26d1b4ccc0f493d427f819ee9a27914fc2af7e1327d90bf8f55fb62784123778c7e217abeb49b5d7c9855013476c801277f1b940192910d03acde165dde32d5566d05d23a27c29bc470cacf6a624a3b053d1376541630ca3d26eb83d69e586e52f1c36e3c77288d503ceed7683d3c03426574e7d6fa6ab93176cb4583ef3fb7ee8b78d2ff003dfe8657e66cca04dd07c47732727c2a8d9e86a0e97d17d96b1d500631462391992257f5a338231d8b53d2cb8b60ac28eec36e09e4d118431d6560d4ff682878cbce4136304b90389c70732078f16ea0f2fd0a5b95f565d6316834f5a804a846233f4e050ca770cfb73c7e2804ad15c882440dca6726d567b0560111d47b16e9a55e56274d0d3d83150ca198263c5eec61b6b0b74632f6594faa551ea6da3d4eb11a88d03289dec191b23a24693e8dc5e001813a1fd839eaa00dbad5f63f6da85c911606eed051a21b09c1f5aa01ca12e3498cc9276842a090e1dbc3658be82093d029cf22849edb709734d7db1c3a335d9d31012b732644a4c9e8be9cc761865f870e2c783b0ce64d81f967b5c81aa742f75cefd9aa1d4f49eec1725f31227178d28d7185ff74c7c68d125d908b50844d1f661bd09f01339c01f6c7c4b7b6777cc4b34e0f3c0c131fb1ba7185807aafbef88e5ca30065b937ff51a12cd70d840401cd6f8cab4b4da2894e2f3c565d83b969a7978fe1ffc340774eff05c8234da9fbaa15d74840f0c5b186d6dafca047f579b72c86541a1e5ea8addbfc87270df54c0f846ae655d1f3374a75f03e32d60390bd61b4b5db276f31ce9cfcdffb989943f9e2c50d896ad37e8e65b47d5cd2c1fa2b8ea33a0e53223f97b93ed240601f243b18245ca9a612ed240e778fd746b1bd5f27122954c880106c68501289f13412c21fbdcaec8b67d46be201949d9e7d5ee716d1fc54e63dc32c40e2f980202ba52c458a0658b8a79244ed32d88738bd652e588b88b71b2189481e45f1bd89b0160872706092373208ecd8dbda5863d6dfbcd8a779c592d94618d6d19b6059d0257c6156301a17b658e1db3f9ec11a7b436d4059e1e7bf6ffa0c4edc58fdaab92da6d1ab2534edad60459f8abe3fb40866c327109cd30ed0c4d96c2e4b89a91cff62a1e6d9d7d95d0b41b205c02552a166d90d5a5f398818388fe0d19f0b49f19c5f1080fb22020de7236d1a7175e7f498aab30bd917af5b797f4c3952fe777153c6eccf15bfea8bb3e61956b18d2c8afd529071aa5948322845506dfd53553c8d8919950bdd8eac5a7c3a6bb82404566825b031d78441980c0eef0456c5d3f94a3876bcbc9b3083c7f4f510fbd9cbc1429ae5e8090b24fc688e022c42630873b17c3a05028c6e651bef0250676619d0f4f215e9aa28171a194e41729de1d980b9c5ba441f6828615728955f1a25b5f68a1f835dd58e65a8c7427aa85361b96b39d9e29d1f2f27fe30a820e59f90ab5827215dc4eb43a3f9686ec064cb73917a807bf656bada45812abb657a124a3f2747305a03ea86ef6fea4878885557de88a12139ad50cac4c021953aa55bc2514b000735dcb1745162a8fa94b4c849ba76728b34ce6a2aa72d5fd5195fa2e62613f03df02d2356068751636617dd0fe2118e32052a6313e821d36b79c0b0dc33bd5048a27a1645e6530f6dfe1c8fb74fa4ed2e8288cd50b02aa518b1895c4be43c415d8e226ebb0d3bbd21614d74df1891cd0c803e3c98330fe3f630202081e58279f139fd9c24bde714f84a7804ce3433894eaa3ca9064aa876f853025672c5836a82756556e143dd921508bd012efaf2c2f7d80d5505e830dd535d8eae2f6314fc961d294395035b9e8c07e095d7a208437a53d919079607900f1471be98a0109a4b4299267ba204de47f1aaa9729dd0b59fa1c28118c7c1d73b6ce368624df781d44435eb681e673690a8c0ec88745403e768c801aeb55a2b5f36790ce318548afc4131c03c8aa78d027060018e3fa7e0857db8342ffd1fe4ce84d01a46923d845dec3888cc1e1f1fa0f41548b637ac6575ae2201d2329baa1ea6a12874116a7f3789b91a5d9fbb33e15ad7e8cc38904ffb936442a4dbc4a787202b1fb2a6e1b5dbaafdc8837687f839089c4a00e1c86b97a3f1efa4c84deed87be85473da9e2b6922f1cdca59a6905088eb492aee2b5246fabe8df73078138640e3433b07bed3ed6fefb616a45355009df14d561dcf25c7797e6dd5a214722523428a54cc0924c766458b4f10b2fe9704d1d01ad4892162d4b190edcce66125e964b062306a451fee02dea0b0b3c84a0dd6a96f632bc7c84482cbc3cef598c62bcc0fbe0a497e5344a45627f177ef6e03f74539d8abd2dde11be08296b5ac9217040b20dc2b5c747b789353b695c5feebc25b351e4c7738cca4612bbfa9b94e8c4839d5d445c6cb210e46f7d447e13335667812add207737e42a93d729f0607884910279696f4e0c1a3e8e72943c1a0a62e8239543a9710892d1b6debad168a91406479bd84613ba414a73f43cb8be1c7b381e75f67404ee2a29eecadd95e8bd1373e8c5ec037f74e413ea16afaa197daca028aa2190f959543091cd346dab85aba379a58c7461aa9926fa2ce062909d48ab1185a5fac3e850811acb981cf6902e278ecb0568a8771a7725658a19bf5185adb46ff2c4941e1397c65ea126502ec45e59449e526eca96586f9dbc5c2f998a40e54cda1584d9f9a47c6d3b023a8adb019ee77298df9e86e8d1f8e19b69e5f4cfd0d662deb057662275fdf147b57d7d66658d2fd2102e3eaf3106ecae7c4acaa93f2a09bd6054e7a42c03f95b37d48be90e571f383553125e4c00525dfc130795d02b1a640e02eae025ed952e3a39883f3f3507499856c13e45c5d73d1a4d7390d7b8ae1ee1b82aecafe446f0a71abddca9f188cad6ca24bf1f6c598a38185f21e63e632a11ea2dd5181c76246d75c68788748f2848115d9220452cdf5b78fa873960586d32b1dbd380b89c0a2361dbac06b46dbf8350f19958dad72db492a0157453c2700a34ba5f71359e880ee34a347c2d3919f7802fde0331850fc87a7c40d7970fb013bf931b788531334d5921cd15dc6a15364da84684dcebbf5f77d3b30aaf460a737aaea3c6a03cfc96c23be01105abea4d8b195a00ca5957e93de26d65006bed80071646dc92399c45f107a701cdd63be1b791420054f18ae27079eab21595ee6fbce7d15a322b7823fa1d49a36a3b8e6a0a8402b614091b24bfd49e595b08169a84197d59a1b4a52f5aa2f4f62e07eafbb0ce727db23ed86335c035e1bf402735534d5159f39ee3048a4065bb3c2d9ad72b01db78a52fed5162244ae10e642cbc8dbae10da7dbf47b49c77781941db7a1569b0e8f2f831cda4c35228080d6cfa588760b9a599632ea579992a3a228ef485c96a5e82252ab38f0205bd526c1af1342d73f491db7d79fe1e8b4609ec7fc61820230b175b01893e04b1456d97d6420261e10bf59c60ddc8bc5b52e61480663592c7b35c66c4ff8c0f84e289a301be70e4938d85891393ba12710d823014c911e134ebba88bc42febe7b01c42d1288354e33e60d29d20bdd45b81f6dcfbe0a15dc57a0736fe4f898219ac5f042a30a9be4001863e79e741360f9408e17373b759b37cd2259b0e90ef23680f069a1fc45fc0b83d100a756d241f444aaa2ce751f8139cc7f96713996aa7a4cd091c16bbf09742725bb78ea190c12b188f0d85a4dd1cd8ea91f07a79e4f412ea2729b0b1e2d3b4cf16b0b95899499eba2333e3f20543b546844d58782e0811bcc62ffd31310afd184dc223b698fe37017b8f9695c14ef710710dfcd948d62cf47ee878508af643ec0d111af5c3fc8fe5de6c2c6d62a9cc55b67bb5a728180fc0b4d45380a480074a27879e5531d2b38e1ad3509bb9acae8e21355f93147ae979281a5b940e41e641b27d79d7c63e5c1a88de36dca4bc1316ea1a52c4139efaa8dd2cd93ee1d33bc84b33a72ddd88384eceef5a154cc445221d509bc0f507cea22a77be90818b2f4ed0c437f2c8972fba1dc56b2e9c2371546b2e453cb17e8336c5a0e9ff5f945646b83aeeac41e527209332c884e94f651b1494c101b827dff1c1961ae2429e045e28a3c3c72e44c3ae2732eb2b6ec8270192b5f48b51b12c8561f37dd2ed3437075302a8865c82903f38ef3dfff9e69db5d43707a5402495147bd43e36f1893c3e904c8f8611a3e6909ae71bbee8b8da57104eae2cdde453e97803d54926982abbd664a8018b4b71541a7b47d33257657e262e1adde9632e1ae2bc02cda831d081fd128b72a2c073e5c34778e2a830cc7d515adceab2331bd440ad5b6e6348fcb64e377c4895ecd1a58a28f435e99caf809002e336f5e91f5ea9bebbade3491a9ebea2161ef9873bc2fc9d9171248003dc832f0cb538c28c5d5e45fcb43a4c566b610a1742b0b558e5545fe61e4da34bc49033876c5d1a0678a47204bd1f1c7df98ace9830252ceae23c8754998c2e921aa80f727dff3651b1914adb4826869230662998ea3ec3886e1e27280128c0b752e214e9abca1f801e694f4aa2bfc447421f04ea965715423291f85a114f3d9e6c54e112be13e84e07181c87259ef427215d18932aa0051fc9fe4e393a5d5c3a7129f42ad73e7a922692e2c408f8d07bcd25e43a498a85c43cb26a8f8c682954638365b1e98257fef9a31d76011c7681d296808667f104379a61938c12a5b4147976ddd721578dec961ff6d13135ffe97941e26b3b934b853127aa46c931f6d16520a2bb8a2ec0a2080c7c2598c154a694f727d3d8da49a44daf78b70b46db05e2089d8b93589c2f8edee8605599f0448c6c58731c243c9db33b52cca9448521be3c3cbe0d1ee736db881b390eb6a0e98403e9a1e2fb86b43e6a9cfe957582662beddb9e8dd3b9957776e632199523a58c4a602da3faad1897be8da9ce4cb346a4e08c5e4c7884c947f0f2ef0ae453d1356d9dd1634812223c6aa0ce04d9be5686b0d0c7f95867489a5e2a7f1c46780ea07b92531db73afe93268658a4a4d83adb71b4936c490242c938dddd25d31927ab8031b85d4db02acfe644e1f1407e5ded7a84d419d3aef2ddada878d43edaa86f35e07266655f9f17c9c92c4f34e44060b5a16ad16c57abf238c944c9939b3fc9004274e33b031ebb8b853230720da5ab7206ef1a966fd1fb26bcda339bca3b193fc419c925a9ab417dea124578343db4dcfd62125552b66ee9212150c956913752ce0b8c442b7edc0ec616fe4ebfb0bf7fec41cd5855d466edfc94d5315287e4f947a64ab3ac42e2cb305a872af516fb32c8150815b4ec0c6237d9d43e129412365732dc86f41020bff4b60a5eda911ba91346e47bf96aad9221f54fa7e92ff467f5756a98e6a87e4dc2504414dc51e55899f0e6b4510e747193fd2a259137a9a5a28d8927c1c92c27d8b723d5cd7bb2bac9afc487b1fc5c1f2a82c79c062a5c0b64ef0fcc36a05be9df004b148f12ab9a1ef65a7e133954a1beaad6c5371ebbf14e5bb6e7f3ffcc783ea1b01d22d3acfcaedd70edef3314794943446a9f0729112306021d01e88aa8c67ce008145f9c9ce22248d79df4f7fd5bbed62c398aca0a93941c241e91434f1f1f26422025dc965b550ce2078011b1e36a113b4e1e2b44cdec0d5af558637cb76086dda1e89e8c8882a7d061b4e562e605dfaf8286643c460dad8fa855d73f91e01d33ae4405c8ae20b993b7d66238abfc5664454988e0fd5f21e3f414494447533dd8a012ece713b3a7cd7c0e3decf35919ee2941912b3df93c92e971e14366649427b7683e21122d31c2797cecb18e39b74a1f29a3572e93c73c625db802b03451f5d52b4debc90b5295eb2a31650a67fbdab8d27d97e13183e201d3a8e141f69309344c66a0dc003c7fbbaa4ec6fd721098edb3e63206f074edc801eb23e1b799e5e6c242c782060660063622ad49b509d089cfbfa923da2df5dd632e465f91539df4e2c5d1f0d5a210d13091312f642790b355b3b7fb1aaa866cda9b9e019f60a30bc9f3b8677a18c103e676bcdda0b5ee2453955aa5120bed97e8fafba11351cccf1999c1810a9cdb91be032e486ec4dba1ec0a60a77ad63d3cd0099302a966a345c6fdd2af3231bbc06f8e44deb7d14c5e9daa2e701a8e2201384570b740377d1e60fa7919f5871de87c850de6a6c5f2ba74f9b3b3662c876fd7600a24878458acaa3cbd53ee9f6a90beef21c0f20095f349ff192c1c9fb54ad566e08648e029354536e68931ac1bd4ecdf6f907a870ffe3d4fd0ce9a8608d06c0481fd63af169df45e6253469ff237c551c7b057e9c16cb05b52de7f2c9267d1126b7b834b086e390e08e9ee2f72311fa86378ad34ef223006093db18efcbd94b445ec0ec0f2ebdffce8d82bf17b3010a89bc1c3607cac5dc831bf99bd409e03cf4615664dd6c7d6f663ce6b21671065d5507ffc6396ca6e78e5cca1a75cafe1d483dd5d78c36ab28413a7fbc97cbec1921f5086a2fe1672d3a8794df6b9ed50031b561bb219e652801f645b2b39a35aac3f0b99a99bb5b8c3830f7799a2ba93c83bcdd2e0584fb8e2c650b78b40e3aa1a478503c044a8fb13ff231a39166591ba09431170f46d058b7b245549427a8049a90a3945817d32f73b26c34a70d25e40b9dd65116317f89549c8e5d0bbc0a078f05d853925aec2523785b328ea93701c3c6e2f7603cc62d0880180c7518a86c0d4459f0ee9d14df19ef1189ac07a430205d9eab7710b799bf6b2ebbfc593e007f9348d1bb2bf496a11bd3bde1053d1f83479c77f599cacd37ee8d06a8b4d9d98a9ff8083c02f6446d50d67854b1bced55ae8517458f17c160e71465ddeaa0bdb3c1c0dbcc4d1187b2786f5ada2f3e1290f120b236fa99d3413e523e07667d9724e503e59decd38e0f0f2208edd2e0a6cb46037ae0f608848fc9e4e026095e80056682a1b4941b70b8fa2e2de689b31aa331af99a02ea6f4a961ddd9d8f4ba04a1c109e353b6e366f7c8206bcf2d2edde14a3b7a553efc74cbdeb4493828c08d860b8c0203d7a5f0ac375d7723b0fbb1b97d4405ce2d1e1a9deb70fa4bc0e4a52b9243f9af69d9b03436f0536997d436a4cc9f8fe04ccbaf3494a8e5836ffabcd35df5983aa9cd0e82a1674751c095fa0b1eadf3fff8972e2f26dfce3779f5f28a8f1d736e26242c0839c87303f89e10c4dca456f4db1c4411aec5d6f77a07013b8c94c50b716b6ee346364d19d42fb2c2fcf0d74db3af0bab81b69c40453bb2bb4346463792e7f8beb0caf2b53e692fb3cc2443e10c45d031fe7bcba3e7b9ab9547a730d4f15fd957f8ffeb2ad49ff2ab787d328e9da40a370ded07245cb9d60d9388b8349527d40a23edb7991d0524cc9bcd95ace7fd91d23055974af3ab98eb98743f3ec23a5b748d6d7434346ea80c2478763d060c3df89043c901162b384fdcc699caa4ad95e82f2e7533cdfa78b1d13be18d42cf4399922ac91d53e4ead67e36218038c6031592a817f568fd13ef0f3f17ebdb933797c79478ae7f75ae371ceadcb9cfdbff8de4b06dcb866f8b8ed2bc5f4324909ef2cbe6883db806eac29ba2c97139ea72591d8bbf83a54e11dd46535785d32c3c2a7fd18086c6db8cb06793d3ae79b5bd79f617c5580847ed3b403ee97c2abd8e1c531ead760651095afb9f633a42521b7170fd4e3153a8a4a289aee06b9b8fad26058845315f5a550aa29843c9af698e084f7f3a2f341b9d05738a15893a87a80bd5702be85749fbc493912e242532740865e6b5e9636c979f66549f4e76e1c48ffec6e2d46ab2a4e7e1f9e11fbf54375f34f4a23f731ca0e950890e5a44e0997e029899f9f7a782ff7d41f075c5f965d1f419830fa3876ba15e9131bde0954ba108490190b5876d78eac45d8a605481f9267592554afc5a42df4c0b823c1d4300c2b38b99045d80d89b455d887fa1c737e10e30e40365521c5a51ea9dc61a222eb7c22e2ce0191458642f0989b5e72761928afc169fae55dafd4a41cbe4463e438b6058ff668c9b76d3fc5bad449faa756af57b817892ef631a29818771b7cc79687a91a19108e00ce916c195386c0862749190ab7808e23093215b640863f79328581180b6f5264a86d02283c922043f508f09c9290616110e81ce03cffb7b5156af6c56d5b03f4cd49eaa2a48956e8d46af19cc7486ccefad04eaf22cdf58550d02c4e7bd5cc856c413f667ebacf5b1e79535480ef0c6537c2474f7a9a8d8ca1f0ce40d9649c0c499a14b23382a5df2e9564073992f73a632fb97c933a761f56b057fa4503bb9146a7a34369bdb8495614f44c0fd8dc075341e10cbced916d530405db3ef8d43499364ea36b1767ab25bfbeb1e6d3eda65b2355e3a4d9e09d3eb9c2ddedd72aa154ef00cb3d5602b3060668a468f30059b01ab1e5a4ba96cf937ec927ec9ea678da72142331f820b3d2d2c01789eaccf9d6b49b0d8fdc29ddf4d6b795fe1c2e05fc710c6378312fb422692a0723b71001314ffccb20f89105755a6ca91e6365dde2ae21186bf0973a2c745e016684859f0cfffd9ea4f0713d456919a658fe0737fcc17ee47b386c5f29da1c0ba94496b5d86f63261bb80d755fdffd19fec7ce550356813aaeb56999f989b74d82a29c257e2a169fdd2014ab53ea4e04c4b3542acf2ce0ed8185d5275dcda4eaf82c91a613570ead10d18e9ec78b078e9501dd011b22b8b528200c57095b2915d80865adbf2ae6dc2482993def0f1d596960e7e06ef535eabf34f0ef2dd18c08eedade7aa5d284c94f9af90b60695e7917acf1a79286baddcd5d396ce35cd8fe52c6a12ba7609ad77f9a4ae5871a74fa4a878becd33586b492af82a09ebb561cf2458c257ac1001ae25379ea8ab0fe7c639ab216040393dcc64efdc04e7a7fbc31db518cce84db20369585d46b1655a95b8f1750a98217417ac2c63ca6bcb84a56f55b58d84536b59e387d2e18c96df18339aa59f65c94b43ef67d9d8b72613a181fd8e096f0506d434679cf2eaac9758297cefb58355db95c484c2c16b8c98192ca39db549bd6f60b7a19848cc1326c34c72de374b43bc034c3dbfdc9e6c5471ebf7668d657aff301395416e326e510c14e8abc6f4a8ac12380a17ca1e0b54aa972fafebdd79ae8337a813630006138e6f57f07693ed179bb1478f61555e80cfa8b82a8cf25c78b89ec5529fa7d1df547b32bc647e4b936c821ad50412adaa015949933cab5224a7c128125050bd2b78afce7756a2908f5a627b5302a25ebad2611c817828f21161c41ad80ca669b6410ef983926e5ef9b7729786169a07327929054122ce05acef4fdf6c1aad90a307c0abbbf45967856987e7ecabf7016129297b10006c245fc2cee18b1c0afbc1af1426218df677bc51aa98e5b514850f4ed0c1a0a2288726c08211dc6169a6ae27f7c65aa10470630a0d44f828282fd1b9da4f80b8df22a664b70036534e0e3c934aca71dde5ac130c7e685fc752775ac05b0eb2b82ce7e1260a4c84cd16f4e26c0ffa15bf093bfd15b9b54d330cba17a78f7d64d89b6fd86df26ea086477bc04326b5e0013b8cf7ea8ec731ebd32056fbbe76d52eb05d4afa9cd8417c9fcd3ab1d1b18d7977128c10155d0d44c4f9ab3f6f7ad732976b363d54355732417d7c1a09b4239a1313756c22b668267e46febb93719125c453c253f2503c525733e45f9f7dbf3c7ec7039541452ae04f1bbc0d5734d8746853dc8eabf7979f0fa0c8c7edc1462ba0ca35e5db90f15073c5e5cbb82a260158a54a7a9dce719bf51d7a900489ee970ab546fbdfecee29a04f645daba667cbacb9e72e1b0b724d2467c900d92453e43a804da9530c6a3398ba3a3f021caffbb21637e5070ed270fad5c3e548c30203f878545be7a638e37c7261c90e790f4c2a657abb11d533e082d63131970636c66ed04efe0f539a4fb53f7663f6ab61f1e9740b7372f56651f749148fcf9dea59c113cc0cfc02147b1463e4442244bebf82267b1a8fbfa79305cd21a9ab40633e261cb6711944880543f82e18878e11a6313232249fc1e84d56110c3e1f8cfeeed4731754e03f73c1d41c4f4034b2c865be2ef21967653aab2d4ef7920cf2301b01b4d43f4dcfa157f6e85915303bae4a8c3ef47bba44e6297440d5cf2d94dcf25ca6190aa1b5c9c9703692d4127397d110bea856bcf3430b6579d21626b0bdba52c1d224570365a22e5846aa8e45549629593c9d27ab91279af1a6c22ba1ab61653366b5c51cefc06784b24b9e922552599a74a5e8bb22a11f89b924f7ea90b27ac3599e42472b62731203b50c539dbd8ce0133d96569ee84d9ff887b56852e6e2ab6b8f682f9cd1ed9cb582b9446f62ee765cb3878f6e86dd5107f225ee0463bed900ae57293365f83f2f09eebbe54a44364c5f1203ef3eab19882fffd8e82ee8a5606a9258c0b82350e350d81d3ec9215a79b920aa7f6a31773aed191cb7d789802cc17db4373e029c2c42c1630f47eefceb6ad5c821420f2b65b47ce6bdef77688cf07b721c4d6a323c0c6f5e86bed55a6521efd25c241e6ea5dc6943703c69998efdbfa06fdb039a08c824d7f0361f874c671003c986c1d374f04bd830ad753d54b3e5a87f502cde3fc2c9e14b9465faa30a74b19413b44cfafa35efd7adc95a7f4ba1edad324984a00ceda21b7e3786219c8e1887e9ca51e1f22b25e687a61028e4930fa5e8d07af39807a68ff9fff246c5e470525ea967bd228a23448ffd94279a7904d07e18c899109a1c3939962fa3a2a2679e82054c5cbf28d19be861179cd3f5d1a8bf33299a6cf1c54a74b53b17ac314e57aa250789f24a7535744a2c156d8743f0a2bd6947d9a64f1fda6b92470a46806eded6a35a7509f4949df79b737c93ddaa199a5d7d1659767fcb8743342aacb8f57fef73de58418be820a4b16861014a438e770de85ae000c14498ab2cc7e0e0282f73f33a36d8d87f73e2b3a9960e628ff5a953e47cc9470f3033b858d2d4688fa83cb0f903a076a99ec46fecdf6b072a3f6e4b81c66adc255c7c5bdaa2ff992abaa9c6ee964d441b7c38639bfc971c2a11b4611ccaa186bfda4772caf9ca60e804b1ea491e7405f62ab1bc25ebd83b462eea1f76a695617f84da648c90f4c6f69dc4427efe1a12b10f1f6e70affdc70aacc1c809c4c8c050af9123b2f6ca3d3b187164384fbb72b2f4d911d3268c74d98c547124207893f54a39f4cb64181133f04bdc185da4d3247ec66bd6d6838b03704ea86d6beb3e7ddd1c43dd6de18bb7e69da7c3209c20e7cbdc9d05dbaa6ce7487b43f4089f32f0a0925b6120f10f4ba818a899c079135476bbf4955350a5dbb33d262491cde7e4e7f2e1b4a6d2af1ff39a1b7584a91fca0deab246afd411da56cf30dc1776622b5d207aee1e8e863d957f11981ef1694625c47294d7c7031d06eaedf4d56c697483c822c00824316a5d631a72491657c9d8e6161555ba53646e99413a2a34d3356fc391207be6b206e02a0fa0989789fe7c3c4caa65b98895e2f70e18a7ef3e92d42e91736465a10279c874ec4e6ce66941629921ffa64f2115312661e4e4c9c24811aa0933ca1570b2670823bbd68c3280b8a7aaf3fa8cc927495e9f9cc07e21058dcf590ff8362bb216a31c212f026083a9aeb61f9fa82b21c34b77a9fd409fe44d4bf134c6e721c5b038c9a5e4c2c1051d38bb5cfe8286a053739c78d98e2a71036441b79aeecc8185382eb79201b3186f05e81ffb5b07d3678cb8d039fe8ee07e01c1bf1e3b4dd6a6154543d6829ae7551142df8b6ad96660294a3259bd8787bc59042798d99ea84bba863590cf635df7416225ee8ebaaec3ab546c074261508817074b90cc64afbf7f58960f723729ab6ccf28df458d9a6ef7fbd41d32a361cd63a45c3e4d3e50267fdf830277308c36801e25ce42fb911f098af53e7b640ea42a0e67d9b36389531ea644ae57071260442027e1ba6fce909b1a7542348acc07240ae31e7f51951e12b410f1fc692fa463694ee1d9d28a0fbc7e849c0915c79cb61dd6a994b1f578e1cfbd72958480ff629ae15cb1846148c29d42000ccbc9331ce58571220f553b4ecf598e218f93bbe9499c50f539eb9a2fd69e14536ebe3ab672a2cd52abdd182a444e31bf00290eb380d632c995b77ce1b9d36953371f34ac2cf6b8e190fdf7c122bc8e4381114fe7508946d408d529a2296293726a90b572771b70ce9d3d5eb715132fe4829807db98e5a4d3bd7b62900f7b0efc56d2de7fcc36281927a182f2df492b66b6c93af8bbfb5275aea97ff9842309369d2f05bb10c8c31f16d02b7defa38d8e4f0b284c5da31f2d020bc8dfcf232528b383a41a6c3c94c88aee81419e1fc87be4dca58b411395a78d95ca9e25311a5cb0ba727bd5361149d058d70044050821b927f41484354aa7f54486596ab417889d948bb278cd5a2f39b2a9f1e876a012c4ce4718eae9ff6d57485a44fabe08c6c8f9293f5a863ffc990ac1ee55854c86cf3911fc4620456e31ec2892ae7fb154b3abcc57c5e7e4631f4f4b4aa801ed8b78cd73e43486556c18e6592b5adbe1cbb939aab698883421d638b2526752aa10ee724a80ae04fb21e1316a03aa3f5c09dc61aae6fc7919b156012081d0951ba23f549c0c092fea4cc52c73d05db53214e3f083c59dc76b2e8d7c9a2f6543dc0c704eb076b26308c66aff75ab799ee2e5910f4d941e5ab638a231927d90df446db478ba68a9f41a005a7bbcdd26699a903e16d8159c870b7f695b5f4f6ebb8ce3e8b3902080ea6c0d6f72b908ca8e60a3aaedbb897eda80b45f8fe7916d3ead00612e46fbf6232eb36814d0445b960f115b627b1640eb42ecaa201360ef60b2c5c0426cf30a5871d1a11ab0b3516fb48e7d97c71e4b549ab361fdc18477c1b2b080580814303f638a2eae917d54530aa5f2c295103ba488ad898e778002318f34426223c99dd65179d6e4b8d43221404c2f11a08ec3c0d4b36fc491e74aa40f93b362a82bb81cdb71bd622a95083c12537cbdbfa972c1e3947ffefa7d0680ce882d44a8811b47c5d8624f472829a25d18a8d3d1adc4aa7047bb2691c3eb6eb0e0b1dd5ead171dbf3f423562888dd0f66be52ec055514e4c745b06d0f37358052e1abef712f4a26bcc7a8b0b895220d14faa8ae7a35042daef35d910fbf74bedfd47c0e51cd76840ea24b9770646865e0e4c3c415cd7541cd123ea765e6f5c98a40997f8a4ada5b36f9a8f705642f71c8518483f43c0fd2052ff5d7036d412b2cdefc02b3b6bd99778cdcd62228f2ec8a746b7ce18dd52d09000bc99cae49b85a9e3515a45a4a04d1d72deea9077b70709d9918885b291091fc4c51cc30e29f63f382d3a1ddc34d7e5c1f11128d1f2c5c8406809d63411e3126175e522f08c3e7465da6579290ed584e8af1f89f6fff8106e70e84847b09fa22a359965846afc0e94df61182e2bb8361b1d22740e8ae4560541a5a6744af97bf8bcd29d24961a8c59af70b5d2cf072c074cc0ff37d43a09163bf28a94cd4ad81d671821d4467cc34ed69742a1e255ee63281cd9b34f11d9a679a82595f8ccdd895d80fad4f538c18d36ee7eba11cbb80f5098a1dec42eb3dd885d6494486920351499e0afc5a1dc2cc6a5b160ed4be1827633d8336244680291c84b182c1824860b00acace00b3455f717bc95b01ce12cb5578fde310717c6b029325eb20cb94e13f5d64991c39fe6ad890248611e52feb74c979a201f202d927b0bbc0defc74e69ee18e950844e312a7a52839430bcdd0891607a1a7c0d3f6d6b9d01c6087f6f6406ddca4122b0c6618e10d552301ab75f3be8490d5ab4787234a66d2042116487cb3e025290405e4e4c994f28b6e082aecdb0eb015862e49d343d45bd7d48d6507a2dc06ad5984093d46410cbdae2923982c2a4bdd489516b23daa8e77c509ae8ffd785f10349bacfcf8ad60d6358f503ad980506fa045334e7e72a5b5124f9804b45952040b562d1471f07c007f7110a1cfa104c2b6fcbf53913376c538884731926f46f8d46319767d3f9110f1e4021790c317166a1f9d8b8ad0524711d210b3f8ff3de3750edca8cfc560c0da978d05e7132483d755ab8792bd9bdc019e194f3061d5e9f77143d2f4ac3aca93f0432958439da2d155937d261ba5e9e8ec85e4a9816ccc15a0181226b5607b00ab8f73f6df0442a66ff98650dd633c88cc2eb935acab12c4b63b7ddfe6cb2d29c73f027b3dda52c8b6155dcead499d4912a56773d1425a781b0725fd2d37148f6292f50fef0be74156a51f0dcc738babc3dc131a46ec33464404386b5c8302c5c62e15e0522dabf4d1c034f13e15803b0a3e6ed083876bf84c35f63898cc7deb988b878394eec03456230abbc66eccc680cc7c3adac8ffd84d61b5279fdea0b7bf9c0ec6d87d87ce496d4c12de7be0948d0ac59aed02af5d03f7a1df9dfaf99587160b7f07d9765f6cfa51905bb0d190cf5e0ccb007ca646fddf3a9945da5b211d9a15d7b7c1987d709a09f5ff02698bcbc81cddf1e8b25bccb0576f4f6c55c1068eac460ae6ff3294635a68d78e78b458fed54049fec6ae5a22e508ad9365f268fc3c518f6e0ee62ba7d5a89ade4eb2872899ac0e61854fc6874b650712a0eeef1c03c07efbc28d37e794c022900edc4e2242683c416dd61bc484eba0e7bd9e4f2990baf692b3042227cc146318d43918df95b75785cda6dc4a400e14969be52a42e72fc39de61f3f985d5ef89f26ca3a2da8e1c2164719ecc3786939901eb74c5dc49c41256a23931f0cda3c286f073e97f36f0acb318fb6c34e23e947115007229472f1828d47982b16c7a5e8cf4a6bdaac8562f17399405ec7bbdcf4a7ff0dcd4f07e6eb3e9b99cd49aadcec877b56c7063f23c36c5dd69f083a9b66bf866b8fa89166e1252bb92707581907e25be8402919becf3463e901c6b97773ab1a08f52c0dc1a0a7efce4c27291889490d8f7d477f808d3d5ef09d374d94569ef0aacf6cf091098e2ae693f99f2b131c5029006ae9ef630827a06071b4716fe2fda213079478b6f2a40f0def79320152d6d86e5aea6b7fed30cac9b3a45d61bc19001aefb92bf63a93a6d7d53426515884ab75ebaea4a37131d744cc17f309309f785a02d4f0f192c0d19f69b80447f840cb5e0e44e515d81c9528187dda1510746f1bcea23ee1f5739fcf4282026c811aabc47612b5b4d646bfc8d715c476eae62b671ff2c58f93fb4c289b04edcce9b1b32352b4cb2bb5ab54afff6be133e2190cc6afbd3ab8e6f77e8c335b57273da05bf64fbb6d0c8b4d2090893773c0ef3c5c034a4cfa765a3016daa80309f7fefdae7bb055ba7f3f47ba44b63af77f502ea15665c62c51694d0500c7624cdb0ac1b20cd867f318e6e75de71b635e8de37676ea238b1a1abdbf43ecfa8f32f77393b9266d1e1f3fc492bdf7de524a296592293d072e079307b9698ccef9cad40aa98550968f6bb48d83352c5883004de0f8ed2629b023dc146337741605881814ab294bec0b2cfb0b11d963ede545d9b75bc69a57f36a88f404df89021799d6a919ff2b1c651b3ac0a4183c35730cca02dbf880c387c1e264044ca5b3001a6981adc8ee57b2bfc362dc14ef699d2f1ae50179aab58eebfb1def5f3ef416fe1bb2372104ee40f9815ff43ba17f0149f1f38f1b2e227a542911b8237a0c9222488a01e65ffe070c0825be0ddee387011bb4e1fb17520cdee3ffe13d065b9e5ec05893bfb291c396dd8e2ba905908d1cc69e924f91b9498ac7dce40e7353ad6d5cfe21898a5f74f11b7013484f70f41e2c701857abd6895fb44d91222b4f8118d9e3ca4a6d476cd2a8d27f185f0dde8b3dd65a6dadd6bd56ffaaf732e5813b42525e9dd16b2f30ce7193914cb7c7c9a078f18bdf1ff546a421b0d7f0c13403ec600d0f81717c00b4604011311e1e6891422523cac474c97e2fc6a3d1bfc9d4b2d7cd8dc9e492e404cbf87996bc7dca24eaca3b6553769f82169c60388a60f85d89f4e3236549755e89f4e3eb2027528848ed7c2029bef7dd0bf9ae486d3f24d5518fe4043748e71cfdd7c30729823178eafbefbd47810baef1138299e54aaee4cd8dc9e4b31196abecb227bbf4f16e8747d39fde0087324bf62c2515e09922703865a329f315fc290b648ed399d404cf1c9a60b2c8248a87057bca3acf1e70caa6b8299cb2d9e338b239a5dbbe1ef33f080c995bb0c421060ea7ac25a32dd994394e96517daeaeb7284b9ebbe9cd792f06b6b85a4432ecb0e367477661070358681c1a77c4b62277e4a65186eced7bc170cbd137ee5e4eca7b6ff732d5b5785c54ca8e1d2a6bc5abc0b0443b69fd5074ef0d6eab02cb422db7dd18c1d073a5338c9e3d87377757999393234fafee99235173da0057598f29397ab7951d726166b084050e73b2b390e3afa319d03834e6d09e1c1de4d44a6d8fa68dc6b9ef1f5d124393e4f8a14fdf2ef7dd9cde9cde9cf37b99fab612e591d236323ac54d31476e45d423d6d608c83c1f86d8f493436a8c46a80fa6b9ab20c9c26ec19ebb0a922baa2099a2b391bb0a122332931c9a66496cf2de2b3d2677cea88289f1daa8e2d1800b4323c87b791e7ee7fd8e53fc24e3014fd0db89aa163089e29312225b10ab2205f34ffce4df4dd9cb3b11987ff918e57bd307c70df7251a2214f92f7307dca997c791b7ef5efceb21fa2159966e104c7f8a80660cca25dc4754da521b18dffbae24839b9c03232cbeb8e06d6ee10c38fc659825262f5f99bcbc7c0179f460c9fe29d4701855dd7be738d11557339b94601cb9be377db2bc91d2fb62fdbe1e3832fdee899b62a9e3138c599e0ce190588ee11024194b23592461d8defef6f631ac076fd6bed7849f37969c9265147d2c6df6ebb16d943e7def43a570061cf9035f00792801875d0381087c012fef5fc38ee38433340d7503cc7d17462fb27fe4894bdce49fc21538ec9f97529cf9498a8f0b8830252895c9cb6fa517304ef1530090e0f046de8c84c03c242af2a41084c3280b5b369b531c87f648e138db7f815ff7fd08fbac1a3392e00d0c67c871589620e9da18c9b264073aec80a787942ba1a2074c088308294029ff28a39982c3f8f28f3d282236d58018e316638c31366d8b607cd920c16194bde4d706d8b8008751166535005ce942d03dc7656e8c2e27ad934a8f76bacb51b4b9ab34c79f2418e20fc9325b2c59273331a74471cc39e73b0e4d0ce59cf34682352e2d9d73ce9177bbbbe577774ff9a416b6900694b2939e3f27a3c805778bd7eda4d5be8ded25f62987934aa74e7b544a1dee31e67829a50cbbbbdbba94f367947286b1c214399cb4941c7e32a9bd75b3b74eaac506ae30e55a5b5f745a8cc3f3bc6ffbbe2f7467ddec7dd559e766afc7754d29a5b36ef6be2e9d777610b173cc69839cf3be6ae7a0df35789b683d41cd723e4c8c9c3535e67dc5cc8f91326e639b6afdf893d6ef795f95caad52a9c536e7bcf3a3bddb9db4ca174752d29dcb49abbd1bf7f5a0cf598fbb37372af4e4708cc3a675bc80a375ece33a71482e832bc4f076f3c2cdcd6667c53836306c2010e3f03cefdbbeef0bdd502824aa691d69abece6c6f7edcdf04a5d69464bc12bf244221c3dea98d7f4fd232cf3795be7d87e4e6badb5965ee9f2fb85d0584159e584818991356c8c171323657cd6b0218e94d2992738e9b437524646867b594dc3f4cbd4efdf5a078e392e0d2c70fc8e1bc1288b6094e54712c8975e6409c6b6d9b69742b27dd96db3fd967b88fde1267f207e8a3a22d039fc8124d0361c049ae0f8dcacd588810ff672574102041798cb5d05c991cb83b7dc5590ac381d7097bb4a0f63541ff097bb4a0f352099942bbb5c151d544cd08c30a2c4c7499197121d5808c20583150b3288f1b062c1355bad726041c92a76f188e6aed2032b871fda625b6e555e77cb84136890c39b1a0427b02c0e628c9105be61fca02509b8cb4d450c2d6ea888f183160071371fb0979b8a1851d822e02d371531967001a6b9a9889184037a5859bb552764725379c20cf2cc5dc5054be47024f33f9856cbfef2c399a0ec4f03032cc3048c672b7f23368224cafb07402dbb0d57f6678013f845830b318da3c90177b13b1402bf091c5618154564b3eb9e87c724ea0349dc0f4985401207fa2082679806f3507e841effce0e941f2ffffd0e941fa2870177448fc11d0c86c09d96a79d0be2d9157d68fabe53f8c351e83b85411ff224f2c94c5e98e1b0ca2c886761956d205d65cbb1726822816d0e2391a7200986fb348850f1f0ee96a69b28489a611ad0cca7abc8e18c1225539fd6a913966305a3cb0341e0012238c4b31ce99478967d36fb3b64f3d010baae096f72dc56b5e3d18d2e787e389a738e7a7ce7dfd7435ab035ba71226326c30a8bc5922f5b9b25518da71437511adfe6b2fc842feb22699d769c52f6bfad248db241f2e42f7360595a9044592d6c2d071b6465a14ca5e0f0b264fea244e0f8a1bf2eebb25a27d2238d1ac593ff4d620aa29bb800dbecb008de234847f065657f5b6f08e004d36f5bef779decf2b8c925e5b22e4bc6aca32263c2137d737e736a11795b6a5990e045169157040e2bcba7b61c456b2e47d1203fbda02bebe5a8aaca12754c9022a11e691cdac5ac2ca0ca9a4059806240b11ec464b13963b1586ce290e7e343639e4763b14ff4bd44b1582cf689704c147a79997ac11f6df1b896bc44b2cf4540885ee7ee34667870a446709a6791fdbbd7dcee064e3ff9cf58cce1a47403d3a685414043a362eb477d43b728f530c6bc8a2a7208620e636c1459ad58eb24e98e41746c921d0c3939e7ac776b9b188bb1dbd3754d68ed767dd536be5ac156b0150cbb0c870e5bf96ad53a455a27be731c0c8cc3425f7994b681b58dbfafb655f6cd9be80be25602123c3b2ce18baf71be0c847fc8fe9e2477f1ebd14dc80e7b997429250c6667db6cb2260ff2f290a7573b0f594c124b3b4aef25c168b210b2c72064ff1e2d7f79ec058b32727f30f80d99febf9c732e99ac8ba5eb9ab073754af244852497fd43996fcc510ac9a863c61cfe977a1ef53c29dac63f447d2896ec33ea88ef60778e90f620fb532b9e494e3085f9e9097db9c96bd039fc99cc5ae992b68982435aa34b821c4567ae7214f5f15318adc37b7f5a6b9d95a328cd4f1e487bd039be681c4aa54dfe6e6b1e487ba664ff484b38e0b0b6eead50b27f786a55576dc9d34b2601cede87312dda2512dc5c1d3bbe33f43d0fa7318ec6fab349016e398efc2a1382f9b1f3484e7004e91237d51a2a79aab66aabb666f2a51fcee4f07a200db14b0e95a04ca053a2da0a9566aaf3d8d467e803f1cb14067784a44460cb53acb4457996d02044952be8c9d4ed5c9e8b6ea032cac47dbd676b7c29df4d2e33c3e1e714ee2a32909269ddeedd6e0b170c672e6d381c42236bc133b44f0e3fe0fdf6a252034a107053fcafe4e3c1d00379c8213b5104ff04db1c7ab9034d3b39b4cf8122f00cc3f3c39fa176d30686d3beac4f30c531060e3fffcbb6264b7aa260ed200bd8c3e383253e8009007bc14484c1226cce396788b16b2df94a543cd291e512c5ec12f5f1e45f5393e329bc81f9e867e3423051701861eeb2b463bf7778efcfc307ca03232b8cec5fbdb73d73560d6781cd8594031c36500c3625186130304c8047a153681cfa08987fbb39bb299b73d2168f0b06c39c7f58d4bce8afb941a174ede6a9deb95cb608ba9212d23db35510a9ef77280f3fa484d0f841eafbfeff4a39be827f4729e5b81554b82815dac65f052df00b3d366470ae953232054e66768d32b4bd731cc7711cc771dcb5b1e785daa57516b1e4b9736f7aad5a1500c50e043c812d9018631c8d9018624a952754b8c4d898d9b0722fc6a3115699b0ea656a1d77acba17e3d1e81fdb97610296a9ba5a2b0909d21038e468d931171c62152692046e49bbe50cd110bb641941d882d9bf09c494fd58ea7217bf0788b8b8c9ed631566e11656e195fdf838c85885575f0c326541ac8a3102abbad648a9b42f53d6b6b00aab6ccc3a2b4259e51e0fa52f37359601008cc3007df24f0096c9883492e048bb06d804b79be4cd121d5881658ada14489acacb8bbbd7788dd7b84ba26a6abce646eb44240820a26212e2c95f47c71c210c0c97c3a825aca9b552afc6889a2d77d4abb9c9ad43f38c11b87d64d4c2c2a1bbdc6ffd584b31b2e4290412208d7a8030d9004846b058ebc8f7f6f1256de3ae9c4929fdeabdee72d70b8dc3d275dda53b55bd3259cb64375f0c9eb29901bead3ec5a7206cb66c1e4082584a2dbd31524aefcbd4a591c7a501951f7bab9cd48a946c7d20255b9006cb258bc03309868fc9160c517053b0b3caa7d6dabf17ac74da79c150d2c85d60097e40beccfd00cfe12de19b6dfe807ce9a62f0c65c73563e0d0da4983185a4c16aee9f2e9ba9e6bce2953c8fef3d53a5ff69f4f1c27e2e8804397655acc966564c6a66bbebcd6413914ecfe04860390437f1201fb71e5268f443c7e192849f6164dce8d2282434e6afbd54d71bd835bc0ea5251c0ca94da3eec1eec9099aed284cd7881a5576bafb5d73e0fb7d66e2f53db666fdbb4785cae181ae7444d4867d9638c52461965680ab8c5ea0267c115060cc95d2b2527f36bef95ee5e655382787155b68bf7b81597b9cc652efb524edb789ffc7ba8134a7da4038b85c5a2755a7e927e94524a297599cb6e9499c9587975ef0d812338e7e380495bf64838e0ce336509124086438ebe7fa8266aa27168cc45b2d6994f9568937f289a92fda913a12fc27a84ba94689cee98e3d0207a5ef96f9b982638318910f51cc90ec604b58d3f03aec0e1f7489704e7ecff5f660646ccc28b2a57b090e477952b61e4ae72254b6e09b610b9fc92a3e8b1f264b665266507332b2f77e18a9eb451376a664d8dd609fde444fa618ecd398ef3f9f89828a35aa5a452d29c99edc96ed3f7ab5b5b6bbd60e9b6cdcca183754444498368175a9327a735896a5af320a29c5a8e44d12ffae44db8550553040ebb5548578e2341079f8211a38e0892e05225346a64b3aa2ac84ec80e8246511a061a95237f64e81db49f1c2d8274abeb5de4442183807e56124569ad5b4590d6b69c9c514eac31568992e147f64fffcce0e8825544b8647f5aa34134b410f3d35ae79ea23589eaeec5b4d6adba55b7ea56d9493ebe1f921292a235afe44cbe4a59485a3c355aeb60212b2e25597e3f6ec0911be4608727331bec9092dff2af942d7bd428c1d29b66d13da303a61fc1766fcd5c2aeb7944c016c4a3d1bfc914defcff9cde9cf6fbf3617978582535b21a265e534e191b05f30d03de1995c8ae6c110ffd382af2b82ab40abaa12cd9b5f06cc9212a8daa301f867cb287ae20c9317263e2f2aa19fce4aa504f9e69cda86656aba8a333c292a80f47a20f3ff4a1e99b51792a2bf0b4aa19b0199544d1947f4131877f9df9c77af06ce4137eb4ecefbd1439f4b4783337793ede8f476b9b2c406e3a228796c7aedac66d913ef957207ba402654fc18056a5b22ab72b1818181870a6070e27e0d68c2abb4a5523c3feb1a08b5ec4185f1e6ff7918d9403ecd9e3129e6ddbb60d6582eda6071c76d0f7f1efbbf44a343e60d2f643f206ce2cd9c01a6a84c09d5b9e24d841b42b3f24c9fe3b2bc0f27321b298601ba0c4cfb3f7a177f06385c06e1fd412b08fd740b38bde73c088c44ffe219efb2d79e2136f90d41c601981ce92637868495202c7fff85d25b0ccfeb124f30dd99ddbb66edbba7bb7ed7edb7697b058b56f8a16ed092c4f004d175642a3057905a179b41fe099438f526a6df7352a034dcb2f281e316ee172c12056e00cf4001e2cb8f003e9756e5f4a29ef9cb369415c56f8006146162870e15c8e638051ad7506408de49022e1428a2453249c8b7b15c1e1e55cb246090e39176d39e7e25c33bce8bae676bc6f77c0be03f6ef1ca50d1538942a1c6b6d0862fc1ead5a27fef6f2a58ffc719cd9d336520a2c59ca0c20933819a0749a517a912a227e922f5552155525180e5d4b8f6bf1da160f7254bbf7384e15a2881343c8f28796fd474ac0db73cf813648c9c30f9004f6211f887c1ef25f903be038630a9649d4a66ad9b24b49c0e1c87de52c378244d25e52d5257b772a55cec35b890621f93e0dd15a6badb5d64e4f4aaedb46dc6f922ee1728320486edb77dbb66d9b94dbec400ea4ad6dce6d4e54c93f927c8051f569d847e5340a95bd398ebee4699b735bd23e4044b2eee4cb0e58e143163700197179c1a50a2b74b08205136eb0722943edb500fb01a3053ab07e6022c8185a544065084e7228522b07ae5c8905a18758159869095ab2588293245d10c1da39db41105114d1042798e4200564f645cf154a68222528e58f47f70e40268513ba50828b275452fe9364892f546005079e6ddb46577e602d01450f530891f2278ac0250c16912955ec90f2c723514886da4d5c177aeffb3ccfe35e4fdc24fbbe28220b0f58557e666eb06ef090fdbb17cd7788c3518679c13460cb1910cbd30cd85de1029645123533ebee9733b30e0c7f66662998230c4c676a3333c72165ff199fa82366ff991fc7a14914fe0a86a61ccecc2418dec870661667b0e0ae71a56d10e08345ff7d38338b3839b0a8c3db81844c92d611a5883978645c32add6b93e0022ce423289aa41f2e4219a44e10fc542b150ac445304d7a090128d82016d0d92600d8a6090fc7a70241e606f147eff0fc5240a83333ae8933f3dd1506c060a39339b91d236fe3546e017588e8fc1298e78e508ce605b52b2bf6dd99628c797128698239ca18368ae250f9c81954439865b8ee3cc14114d334864ff7087911cca2cc9322e191e99961451874c121920a214af1c866a1d92c9937f68e6a11e4f613014932757e53014e3810c7ffc2a305318637066264f0e846766d9dfa59418cfcc666637582187d981c543f692a750e012ba3ea7ecdbcb8c2e658f5f0bd5da3a67a5a54ec592e7990f9610a4e4f0e60891f56f02aa02d452dbfb8c18e0b83d123f37d072811d19b114f71b5805074b72aae040851d5ba9fde569248122f860d1c40b09134a8628f10418b87a8083289a20d25fd3170ab060852a41113688c2e3a2010d944839ac5523e20a26c4c821e57f2351fdc5117c48820b1030c8a1966a40911911ae2574b1c511523eef9cd7c692676b37f0e6c60b9195394319fb8f6496c0b76ef1ebe6e45ea6b8edda8ab1e807114d94050a5c70f804485929bbe56bde3b4ba71bf94ac0f6def82fa25571bd92d9ec1567121589109911f948a46dc2f8722721812b2b5f22ad13494360e786b01f4622443016c73e3815f44efa0c712f53a357cf775b7757f214ed91a8d188f68c68cf88f68c46a3d1d679930399785b4ba646dcacfbe95e2aafcb9128af8b2f7cca893c9073ce7ba7947b848645c343adf5e57526513535b39ad956aba9d26a609589ca8f909bca185872ac3d8e1356268e13565996394e53e132c5715e4f1cc75fd9b1c8a197c32a933132691db73838e0b0ce6473c659673f5635ab3e5187df6057b5d6708ae05aab5b1c67562b97eca0040225904f9de580635eb97003bc8104df60619f93df2a5d90fd7d148151b28b4e02c1e45002d5c82e1a55af0001ddc001bb116de36f0120bd83935a6cc145fbd4993cb98f042b2d827506f3f3e5b086db2ebe75566915a8ceaa0fa5b4ceea8c86f53225465595357aa25010891285704480113ba6571a1e8d5e60800276b4ceb6835433a37574e798604893d4e0deab91c3d1684e17bc73617edf37e7d7719b6c0a8d16648bb2c844500d3e673745333fe09694f825af2b47e6c4992d709863adad350bc63c1188053ac86734f2d8e209a2b3d2597fde6dbb96c75543a7c2e19d9be469ce1a1b26b8ddf42d6bbd773665c24fb775ed096822a703735480e2c3518c3196bcebba8e7b8d5af0327bb1e22c16cbc462fd777172397cfaf5f8deb276e40b6460858b9410a9830d5250480f0073e80e059454e8af96b3bc0533ddbb6854f42fbc0972e5efa1238d22cdb8476992637e2381b1fb91a8b1b75aa7b64ef7fe1ea5513126631a83314fa28968852533b3c1d8f08b5f9ef529e4d0592990ecff90f134ff63f4314fa344f3334a311f49a4df91f91a323ea6347a99d20e941f313f03eecc7cccefc83ccd5f89b201ca8f9827bd0d34a00d507eccf8d1db40e3657c8db72106b461c68f9ef4118c41c6d33c10194f03ee40f941fa98dfa1f1354020a38f0177663c89a644e3634a335e4a1a33420fe490fea824a3d4f244533fa6d4a908248160700a3fc2581481640e90bc21fbdb985cc5f22c8b9b68fee326c7e2ac92cbe4c95fa6e4475288f24da4b39c0504c46a9d2eba811a28da18ad8d79998a017784a4626c4c696b9b96b39cf532e386bc38cd1318c360b0d78c31681e9e39a7b5132651d3f18c47b44e18918a1b342a27f41db287be82ec611c638404bc45bee2fb30ea207b1f462878aac0e1080544721ea74429b0fc548f5a688a602012951331b8816164c5558eaaecdd411f185e0f8ca70e8ce104ef08e6feb5b0453b8329889b5c861ae0308707954a29a54b0a0c560bf96008b4aaa25d62087ac88f4fca8094946ea06db604c90193e88724da5c40d5a12805f4c97fdbc21da4ec240aeef05cba82c3a6d198529e8254156562cb1656dc1cf7e2dc1b1fa068e9c195430ec6480d59c28a105c7001820ac02092f26fffeb7ffb6edbbdf7b64dcb4d3c42749287ea75b2966e24ca036536461965ade3ee9d9451b6e326d8116e72e130d268d82cd1e2389116b3d0dc80b7681d241865f961e422cb5a470a1d518bf844eb4422c4fe6078c9b20363163f398d86123874200f729c30c2f216c701d2e23835c7a130898a341a2cc67680c39b189afb9335f9eb01862ea594b2dba6d2dab57685a13e4d0a702cd1e45a9289304cc0e14d053cb0a53c5520d2684037ad533f47d976d3ddc892c8e291cefe059239449ac768add3c4a572f3956ac170f4b9b0d5ca551b5badad6f3f6b294bb6786834160c96ee87e613937244b758b52d33f40b5dd336b755918bac42860c79b1cbe5baaed7cd8dc9f43f1a614caae159fa1a6621c93c5a078646dd20fae4df24bbc3a84ba26e6e4cd7e532c514059fa0810c9c480941a10860a43e2a9c90922693cb715eadd3a45141e4c9ff46c149646f22fbcb20c1e175455587df86eebd173dfe0a881ef3f0eb6a1bf7ba0f9b865ff440bcef3eaabc5254853e3e06af2bac2a520d4730bc526ecc85e42ba3140c5f4a2925f8811a8a954a4e7292fb80fdd835140eb960289f860aabdfc592addeeeee5d23cc4d6e2b18cb32155712d57de81ddca16050418518aac86acd2ca1520c0eb07f2094f8a94e9ab2602f77048904a149da28779dcb55876c0e0386d7bc10b58dd1466e8b92b3f6f6b5d6dacbc35dca6bbb678c44a3156480c39ee518cdb0f8749ee32687aca78bd147edc5881635a9020d879e0c4f1453136d4829e59c1d3bca392795e2f29c00c382e141fa4ca29aa652a98448fdf8152b683e63838771dffdf65cc94717e5cf7ce62687b9cc7b645813dd64adb5763683d9ac04e1ff5fa23cefbdf76450fa42ce9b22283f49e93f115d177ba56d197ee1ca645890c7152cabd6d6dbc2aa25c42e1dd4411d54430dd2a54b972e5dfe67598344794abaf491474aa9055a0ba01b206de36f0304ee1fe57e82bbfb5b8cf5c438cbd131c127e0484e0a7262ac602016a0bf6d2f2fa4488a209408aa6e5c8135f0701821bc5a4ba5a4f276500daa0ecb9c4d2b2dd305d3afe993ffbd18cb1a5a12705893e5bcdbb710fba3a147f72dc41c7d071c76adbefcb026d36a1ee43407a2d1dce4b5a88a2b10d4403323050e6b5ee07cfc00e2a6201bf00018d64ca95e9c31ce393db045a3cdd90d820d7e7a884421a5462422f48540cf85d67129a594b6a99594058760a8c0918806be3d03ada33b47a4df3ab5678b2b9f28a8a44dd725b5758a4600000248b3140000200c0a8603229158482e9645393d14800c718046745c3816c883490ec4280832c4180089010400408000293335b40100d688641f47bcd21826546ccab075a1a8e40fc70265770515f872359336cdd01c4069ec4512aa6d6c9d8a2fe940a17ecef8b88f24e73888aa5a24f84882a84692489607e5a92705158de306af000358f94eef8037ac760cdab4ea0b9303d82c68f994a4ce9026c3534814d8bcd0632394651a9c15e4773e0e76d863a1f6fa40427bd0feb955288e2f4129ced8f99e55c654e8cea9c3398e9b7175b3f08741a35947f3319cb03ced39d26209ed2a5373b90fb47f46eb79c4f82b89c48813c0534200edbb5ba0e354d505de83213eb90dd4381dfeca8a305a3185f5ae37a595780d4f5976c372c2a5e85c33f717a3e530ed7ef514a9b609c56f270f4cb9c909b6630b6a148c5b17a8e9989a410fd3072bf135e8919a5490a075acb95b41803674ce6c29f1097ea3299df8217baf260d87221483947083f36f59bb5f56072ad780b5ce0286bc067481e51777e485f89d3b9885231b70b51eb28b43e9f1d73460707f2375cb40432d07381b5e500b9004ab20e41c4bbd71c3b5f0948eb01c487d173e0d3590c167b31d09612f3a41ce37bada04dd82a7cf26359fa1b354931b378e347efd7770f0464f03d0f36a14a64951b529c4af6b7dad80d7db2fe51db636beafdb62f23bc9dc58e542ed9601f626f9cab44b23e7004178f7ed87652e4840a779c03b41e4e5862d89de647cace1fc23993553b173047092be24b4b4c3b88072c01923c3c540fa1108566432f9d0b540843de44a893d818027dbe44deb39f69164761e45529b4c327cdb905b3e711aafcd5b6d2298110b73002d6cb15fc5b40a766eb8c3c2a1ec811faf3127fe9d8a4e7e7cda067b28d7f8d28cc78167a5f84ef4b913112abddee2184b99a5cf4cbcff73290ce1206fc2b399af441e77e709dcc439584153b32ea0f24b655b7c579958836853d8ea4bb5728a60842b7a9044de041169f0e11d0c81669841f9302cc31473090855cc9a0a94d149aff24f0dc1be3ce582e1e8bb0c2fb02499bf19a7a48ca794f4195fa1923f36e90f817fe442c45e5cf8b14a4b69618358cb6688c960d0a18f7a9d088c968e059208b570dbb8cc56f4d6d1894a712ad1d54a6f1ba29f99e7aa63d28a2cdb754d180815996955d1add38e291f516c7980c60bba490c015d1e52af1082a356b92f228264128bf33698b00251ded7fa4b78cbfb1bf0007ca1339b82e0812655b7ba5a01c7f14d8033570ac20f25ec59e6bd8f41390d5c918321796590de27a2f72ce0ba2183c06ac310c80a4d2c1363b3c69783c741299b4b2a32e6b3d27f77a7e9879a9aefebe08e445f4b6fd3c326e342f8b4e7436afc39e60a2502416567f3f0d922cd12afbca64fce52e2eedbef851746e24cd2d92367bd2018a3f8eb4177caf23a3685ecca07179b0e2779baa8d05f6a0026a9e19395732ae811b3eae3df3086d3662e90ff19a58c44114850a6f2b7917e91ff5c763244d983af00750dd93c582de5161a933ad14f44cb54c73f8934d3bfab484625573e191d85c2a78a7d3a7cc991ead503853329dc6c60777db4ff2176019f95f01bb1865994cf0ebbc889c5ef2a079db676b1d857e736bb810ef929ad607ba33c48539d9f9b4915b9d5d4e7b424c2549afae460f37938e8245189d8881f4a1b7e3f7ab34efe09f94e403fa02139b6dafdb5a03d3e19535a8d6afa4f588e45b780acf615fde77a73c4e93a9fd2fe6fb802bbec759cca4859609bd035c15556deeb8c60d9e7600b3b9903d0693be27d5356d1a11bc9d1468d559c843fbd53e00c04033c253ea97d2bb6473a50685a3e91a8145347e0e5a6368ac60b1a1abbb278108d6d2453b84496235b07ab788a64bcefd24071189b312019618b18711b93e0a9064c327c2af049ddb845282695ba6344e612744af88e12d2b520fb388e0c44f26066fa81b8a410077849baa3836b7165a248090819069fa37f0eb0df6bf39fc94d311a8fe35c40e9943d108c39b1f8d90e753219ef567662e5c138164d935c5740b81b15a15e8d0f934cfc7bb347b39cd40f4305603d10e143b023009ce32eeb8779a68b7c28795a4422af7c2ade61d02c48777f8908876b6af88d36beef580d523978b34ad114a205846595704d75112cf03cc91267038ba05b0323208fcc83481834bccc8320e69e62d27e6381c5c9b5230c6188f2ca811ca1e262edf937485928ecd556bec2f6e2aa213298efb33ae2b93f64166bf2ca8612882308bcec3a56702292353c471bb9aecd489361f250816446782e3e344a5fdc39b38b58cf54405eb0e40f814714de57c124bfaa5b22c81ebb0110b084469d0f374b8f7c84d3f3c4cc7375c143a98a0ad931278f6b984233b517539d15bf992e21068f8b94488aa924030161355afde2b2f778181ac49652a4a49ce191d188708efb3435d71215a95995e51eaff4ee420071a8ec4b3fff48e95617483f45a921a6718e9a83d5b0084e14a403141f92f86244ae54b064eb2fdfacacacf5a7227c9522446ccf6b13969c0344e4b208d322cdb0d8fbbcb64af2ba80f430f64708a344e342a9bb7b884a81618fcc60dde9b2514943659de373f426c75ec4d337a9b68a1ce57bd3d1381b835774a0ad92d061d12531a4a90f19229f1443bdd01a03e923e85decaccec5bead33dcfd6b8b3a0a09cebd5ade4fb1f5ea80d68916617df67f94ad1a9e6f8fde251208f4e788e3c82c184b88d08a75fd1ed2580708fc4581d7be87e2ff9fc66436be4573a51301a14234c05c6a827f05d010d43b506215e6b8e2f472c35a58792489892602a16782380b268b45fc9f71d4ed213bf295e3e1660f6bbda389614f4d72ddbf1aaf7a3b2ed65b53498e575d2cb17db6d3f04a445e597beb4c5663c3ed2b2ab18173ce831d7f9995f4616239f8b113a61d13cab45b6b6e6f042ba0efdb4dd315f49f072260e0aa6b46717b3984594e181409d48e06ead633a2cba1800ab734c1208a798fb2c853d5266e6d02c621e62f58bebe25470d68feee9b7bbd89a29fd465713042c223061fc736acc6d329886a6e06e4c9f556033f17220821bdf3597ac639c83a6162d5225ab6402e71ecac874221040269c0bce25666786d29801576a52cd3bab5c19006b716d166ce9cf92e30740d82d49b0d8aea731738553b97afaa97ef39fb14ba08824eec8b3d88e9f21b39ad3780c875c3ff654b64d60fbf87431a80db87fff918b60bfa709080cd22c771df7a985d96b2e5025c04699e8959d91d5fdee18241bcc7d548d8699452f74e8314b062ec8ca50fbdc77b6b9c47ec642461587aacc2c912dca872cd4ccd1c7a91cccc5be0c8c9826c9dac57545685b7922e8bc33d590ca145234b45a6522d3f60e369c0295508a84e5e09dfd169cc56dbaad13ce5d09c80628c78ddeeb964111178e9920b8975612ddb44dc8b9bef9e4f7abb357e66ddcf7336c963390c189ab5ccddc86a8612cc97bf1acb91745c3b41976e8268751ad627f33947ba0ac2d1c08c77d65f72a28ec45c82615bd5f9aa339937aad951ce0c0903d4e0e94a0022f3c24536c49041036603e2316f2e80635eaf6edc8d7a5853b8b8a121180cd97acb32ce297425f7a3823a39ffb52c8ec5120a6f8f1bad907491fcabdcac761b99340d167351636888c5967f2ac38235cddee24923e1b8f1b08e4b58102f4dc2dee028c10390c92f6c022fe130d7d78d500dfce71ca001506167a0f48f0b8a21502c3b0038933bac40ac41fe2b90e8ed15944e7ec1adf1e50250368882bec25c835b0ee69675f7837786774ae0790c0f36f08326112fdc5dc4e77e252310d776de7f6f1f7b039b644b9f2e30bb4159cf2bab1418e05800eea487f146611611527d285f0b1bc11585d4c90625bfbc9f85ecbf05fbf50cd069bfd319202bedd47e5fae40abf2b3d8d52eb9e5935e5419fff92b7afa98de79a3b8adf422c05764d4f3682799caa088b820de2457742812656994556fe6131c50b4c7743cc30d0c0ab657314f0aa65e1ce1cb27707cce9e6411860b3a6393bdfcac561736c1e201d300b3c734972e7fc037afde6ce4358b793e91d58782900a258172f22b058cc4b2d0aa4b68688d7449477435b6603e2b57003a210688024c2bd3d85847c5b70b6a7835a6117787c75cfe2d74a4fde455dcccab22c0985f3c63432ca8c5559c975c336512dd2d209683c2707cd0cf16b726306accc60ef6fde709b265806b53435f894e224a8855c970c32b011c97605fbc24a21cab18f0263c6b14de5a0ae5262211021072d4c956e1fcf3745ec577f6a94367c2e51c49a4ba49b5ffc266d1c28f2522602aaa6197ccd41f637c356038f5ab036ce4525107d5bdb5dd39b7851e7ec21a804fe359120f958aec48d8b7bce57f145c33975b7c45471c9a4c3844f6acd9a8aacac254d6367ad6827dd29bc8fce414fe40d193df5171046ce82f8ac06cf62e489c99685410efa245e8cbeeed9021441e6fc322fa77559f567d01b3957f436764c75ca6f5334b151860421c6c5c23ebc1c6ecbe0aedd1c9fcc692c0e3b26107e654c49b983c1dae1c358a83b48d346e22dd9626c44d659da1bb5d30ded39d2f4adff0c4417073c9c2701096302ca5d87a0928caa05fbed61e3770a3ea5f8771b19e09c90d27211ddd494cb89474de065412fb280487665e911c85a014766239b96d104429d9e269cb163ed1ea6a6bf6e049cecf20f0cf3318fa1fdc34136ad1d7edf41f1246ed512c29c476a6bc5b4e57e32a160d13e16191c7455dcae89be16327750939918bb4889c297a71d74202b59ddf4107cc08088898db44471757681c1b811aa9b10d1c31677c5f799681ba26449f77a8b62c9ac04f7234d3e9fd12f341ab7ee46ba2aae34ba28c0888a30016f00870d63c6dd866560d5f2b48f1b99477daa210573e64a16334c7edba8fd4ed8c8acfaf056250e0578f51d049f206909e128fb3bc6d6d64d33711dfea90a717fe79c8d80e1e5fe97802f50e9745c3bf6d04b5252bbcc2bf72672bbec909cf52f8a008003136f07cc273f51fb55a0a870f4be318aa72bb18ede2ef44f68cd4b876f8d63ea0d8baed012c0972396a6862c87b04a9982b7d5e3bf48e10f4dc0a8d6fc07ece1c690a0f66c23b6fcbba495845654bbcee635dc96b41c66028c327c290575de131145b240b4ab7557dd3a54590f3b9343cc3ce84009873188908fa4b43caf240d2f3d204c155fc10021ef72265e613607bf8507b529c13b14dd7fd52f2e5853cf27a314384030bec71985069820b5216355ec061a3aa884a85ca454dfbf55c925d61840bda63e294314910085d677c14f933d922397e999d98c6315224ad8ee47a16ad0cfebff0bd74706299187546e9c4aec563123c5d69e357e7a9671b38d2c654f428b6a6a148b0e93c285759b1dacb88e5972b4d0a9a635bde211de3ebe444eb0a2763e7c8d19ccdbce9a7577a4b53fc1be40433ec23d481da24c6d3369ab34be671ab7f86ffb246b6810abaf6d6872729199b1213f21fdba4854c4fffb38507124851437f82b2d58f7a4d1ad0412621e5377d420ad070d853d823508beef3b1ea9f4730ceff30da57dacdfc3c4c3030fc5a9b9983da21f48890a9cc9b21866a482763fbb0bcdeae7ae3bbba26a5259ca6c08870ebc4f3d71721dba03ee07995f3fc6c6e4e06636de8e79866fb8af308a7f1b445396894354f23c34690878f52ca5c41bcdcddddc81a7f57651f2c161c8964484b37a26de3354ccbc02d0e0960714fdd3064f9626cce14dd8a23cb03f76e9d729cdc2d164c93e4c8631de5b008ea536b96e465e0a721a22e8528daaa2f1f3da0b353425f59f8c779a09781da84539eb642bf446e9fc27043366a73fa9b1f0586ba991283c0d43f36d8510445cef97438fa26c2f606954f231e63d93ecb06648e3eade6108a4834b13c047a1d5b8367f268d7db14381f9ac55aadfa95ef728d4292b7ce17ed3140ffe1132177e59cde7d05f67bc3750ecfcd85bf22509de4fe6fd346ee2d3cfa99d196a2c768e6e9a62f13c225ebe0aefffe26bb3e6f06ab0eade9a1fbbd6688aebfde0cb7f3b48b094431c03a75fdce5f4cd5dc61c76b963dce72fc16ba82cf69659d30302a32705450d7e4c3710a0e756c816bdec780dbb56e747cece6f9ab7bfbf4cde77dba4eca72cd4ab94c2cf278ff05454a900317799f23d0e97dcb2a26f6bc21aee204b23977db649a4d6730a9e720bbe103491126ee72868893262ac452d236c1cd53af46d267faafd5811d2a004a4f51e3e2203d5998921c59badaa7e865dfb6f50e8ff5e613a6f1eb000b06a7b0e282afb0136315fb54284dbe72d4c0091f3e239c49451a9884afc2d7b792480e9478b075bf21f76654f752918c01d9a5b9255bffaee98264f22eb7d15b91e2262e5ceb941bbba6ecc76e8d580a04a1434a95827ad2e00dba321d7829b6b42be123213a4c651e3484b5a701b29d84110cd20512a9de8e0e6516b192f9302f460b6e609d1fed27b90d20ec8c3412a2d1534fcbc41a0c764626472ef9468b8ccee473c11571a3feeb54d43665dff34433e375d6337e605b9762d5200025d4e5afbe76108d361167d5f830320ca325d8c89de0e101e588a4f21dbdf35a61afacaf2c8fe0f020b131f4fb28a88697d52679800c64f34761e5cc3b03c30fe0da15b12379cff15dc7a602a912e1952a1dc154299c53258294a0cb824fcb494be923522db6849990998b220c8832997feb10aa0da6cca0bdf369db6d0c674e8c7bb90a89411cb2217ff8fc6e44c13e31941762d6cb1e58c80e549ccfe3662569e2b729b58235abc658747bd9cdb6cc5cf27e21871ad63f4917f75b7d050265c54904a17cf4b337c06ea40f1767d3168246f131b0dfdd88a79cddfe09cbb1ee7ed428fea281b8d9c7ea2016d988ca008b273e3882a8bafaaa3c8843304f315021a2d423c386bd4144efb823862325774fc60426c10633f78f7aff77b278258838431fe020583a823d13465aef2cfb3aec561e4e2c21364cad9639a316eccfbc75f3d46f8a7b0613d367eede409c9cf4ad06f1d20e5a757f827527db092d255b3811ea240feb85e63abb86264325d34a7786a613cc13335ba40e8b86934e95f78a4f949fbdcdfdae182f225ab3c5787e45f7d1b46c347dc866bf237061d5c105816612e5ea3ed07840ff11368c28ee80b340b17429aef3ca64e647245a97c889cf51f60519449fc617cea311fb92e5cd64a6bff03d70785bb6efca340c6f15d912b93f2a5c960a7a5362cef8464569277bb1134efd42fa67e0e0c4e3e36272f79879e520a7663b03e1758ce4d1938bd17003af7725fe2b21315d242a543fa2d29232b5383ca260e78e5714cc4c43837ddd35e71175c9cecfe38c037d74e4cff607ddc1e4362f879a5069c2ab147e8888c4c8c25318bd76b24d1b5839b484c1a4b446d08ff3290292029e56f114a4a6f00cc5bca94ce4b162e8051bc71d37c8e6f67516b5f19214deac66d24402e59e33619a8cbece4b81651ebf2feda1e2ff9df438d93c0a41945120c536de9bce40b186c9c59af855ea8c7f1860f86f627cf6e8ac5d702ef34653bd30edb2f4017dfb3f0aa5e3983729652b159ba9c406d9cf5a1194047bbf62a795b9ce36bf68d336f005b6c9343c7947c1f31b7bbf3db979b1dac18a2a833e84ed431265fbe4ce7e94894257b5363d90a08afe8237ead1656c7fe28e7756f2812db4f39598f66a5a6842247fe00e14262c72194106f9f4714bb4f0367b9e9a06e99521921866e126c10498ba6a0992be6c42539b5bab5d462273b96aebf0c424d8bd839f8346e677c9597d2f15b5f342e9a3e371d4e4a27b559ba69a1ad12e797c52a8d06a79a89ce3c45ec3218b75679bb16ce1a201478cac04038839d4d9a18fa3996deef98a0eca2f042c9fc6c3eb18ae752aca0c7a57d7a9a849f4528e73d95e0979ee8eede93d6885aa5fca332392df00983d1a3c34281a005bb229246dc776b6d0cf5be9bdca415a16a48b4ff5e32520f4bc08c045e0777ad675f4cabc32b279181dc196330d570f8d534bc2cb242cb9c4ced58947dc957125b93de256f88b15103262fbcf71968aed0b29d7a684090fdb68382c1d4ee37ce8f0fbcf1e46d56c9948f287452477e2744d002522008147643465d08f55f8f0b835ff173d51a73bdbd8fdd9398a4b38151b566ade04a4ad25ad3e407579fd6372cd1d52c6368389c9671aff9ad7b459b8c3c226ceb36a0d313f04eda0e303452fd70667f3c31991fd10f0f0cb139e59c7c33d222209ee5583e920844e0b093177f642cc38c1fc8622f7ccc86d37b41e1ad9c73778f393220cca5371eb690116c6a995bbe65711b0c278f0a20497cfebd34719a903d64947b7edfe717b04b92d88f98fbf97ae58c35760ff88faa918cb922e3d2f64d3db176f9cbfba579fde38df7cf5aa6675c8e039e0b51d01a5f0d508649e2d3c01f7e4fa61ea049f70a053a6891ce88281a5b9ca3c5d0e2aa8d6dae142cdd121dfbfed072dd0ad92354c162c7d95634ea3f24081c9723124dbc9b40278a475117503c88c9297045e76d5b374b4cf3e8d2449c6e5bfb136ccf60723ebaac162c1f60583239e48ce08af76b34fd0979906f3cb4c645a8b042392fb7986003d1dfdc331252ed3f7b8e27bd5944e4c1a0093d7a84b8c8e600c8fb7381d009d06374770a930d55c7fc38b4a7138f9153dc801458c26fe0c30e91ba400839484f73f53ce391401fb44a880e6a8a681d0d225040307da4fd27bcaa72d47d9edbed3bf5e66332e8e411b104df2ca0de895eef674a8c05125e8e51ac6c8c485881a6ec0180a78ed0aa8d16fe1014de8ca714c52d424cba4c0b3b279845e10c0867d14cd742ee89ac125b078ece78c6dc62f3f8fe16688d5ef77a20e33fdc21a5effb6fd181798ffb285d20b19af70091507e23dbbce4bb92339fad8ec89743539576603da2bfc84be05097b18b7517d45b5e9ac027a1e12ddb4bba17639762ecc91998fb088418184fd875752e1dbe33416c73d003fe75723a4b64e069d46d57e9f46b4a81ec7c4c8ed19f017bad320407306f74e78177fee8a78490ca4c66cbe598d28fd75821ac341ef9a0b20413a787d2dc9abcf103370894ad864ca610ec3f3fd312ed91435f6999416430db1f24201416e26c9c1568b62daade78588856e3ce2fe3de78e3a6931539e3a894b91849d3b819f821f230f0a6a72972775adaa36818fa040129549ed502448e3e5fd2b644d8041e7fefa2ef3e769102f5cbe20fe84605d452a970c19fb9f161c349b4caed4db91944308c1eb84e31ed9e59fd9df479b91fb1b100ff6ab71a978ca5597fbe29f41377ec4098b5e6d9fbe753badb0ddadc6251722c4a56c761175ce582a2beb707c14986cb61abd1752f5339dcd3b32be5eb4bd4498f124439a77e333093168c64a26981d7da72ed46ad6c60b3ccd190d9db5f264e315630e2c0f1b11e811f31d442564139b1479e8d7eec48c70902b5784f423d59e03fcd963606549bbb7d2bd065550dadfa28565326bb01bbded3c44db465232c525be3210eec93ae4864dd36ca24e837c89e12bf0441ff828574358dcef536c7bf8f0f77529abc90deced0a46310e1507b80fd22e0d46ae25f8773a66361d036f3a686ef4d1b79f246746306707776e6f30a19151670a206c7b1dd99bc074f55310e4b25ef963a711bfc546ca5c8d949ca93c045cac4eaeb8fb619a8360e91bd5550462dd5621e3af1871af40250f93055aa3b064fe56c9ae25324e4128dbd30d1bd24b4ad1efd2e90bc53ba90dbe0e894e77026af546ed5d7de087f0fcf39710ca9c51ff8990a9567532ecc905622abf75e2a44494b7d788d3614ba0360da9a7313fb06c9202d5f36a2697bb628c1dc280d0a5f90125a9dc6ef7b1dc954b05691e33e020fd643bf8332bbafd4f602eaf68815ec7acb37728627d339c478346cc7bd53d9ad1cc487e27ccf11c24a6ba398219ae5ed3bf7da3c8f68c0040184b46f8ba20b41277e0592c37037075202b37bdaa3ca0886f3688c0c89e7e14c77253bd5c7ba380283a43e51d6184615120196527c9cfaba4b97c5f789bb5f143d19a15ca97e253b98ab70a2f616d7cf84692a73396e8762a3360c291a087f254d6015b48b225973123caeaac90f22161a8f006d43207c03c2b1e8f3361c4d6457004296c1cf89a474d7a271dd856b28beb449b040331aaa8e337ddc922716e7c51ea177015d56bc6d2ae3cac952b025464acc398cb0701ceb4d5e4f1a5a0a0f4ef51686034ed49ced0270b66865c9742f8d30e3154a1817e2f30b6e9266e6f39db00e0955e8e282db33a3ce3681d908f52734a9df924234020fd87faa8ef81789649400e16e82a993e7d983fe4397e4f0e8207f64a3fd4bb12cb11f9451a884b7ac548450ab0c34c6c859a64392c0e4728b48f4c8626fd307e3005c4c28f4d80e518bb17f64f4e4fdce48804299b066a5eb12dd15c959ac93f8983c6bec5d07116d642469edf16d401ada8ad015604ef8520966551edc4108e1f91c3115632edf254213e732c31f992e01b4b2f55d4f3c3f8d92cb1298bef25861038b1a7fd117aad5686df02c88b6a46bf51e23f42bc66d58300bb13dcc934cc04182cb20f505b737c1c2d0fb73df0c795a1d9260bf88739937405d5ed460e051da10ad722a98fb9821f6022c5d8b94f49f46e5eec0c8e82e9ee244e35fdd957d9eba441efa6e978cfaeb7e51f350bf15c4cb1aa57eeddfebb46bb5e156496f681b273312f20e7754bb5bc9e5d1caa74da630515461673a834ae574ce96f7615462651be17e026788d1f9129dc4711dccd849fd107b24835c5d6ca1c67154ea6f7e5137e3a8ce2a7e1deec25e6f82dab9a84970536f2b54fec906eb8d408362f5e0674c3dc2c7b7abd993e032fbcf3d096f9cfbb0e3a2844065aa1ad76071f5d04dcbb7fc983917871ec58e5632e01fcb6b4a3049d2d51d89fec00eccf177841d90b72e9e0fd9efb8dc7147df70419d9fbb0002518714144add4950a9040532ab222fcf903c0e70f2f7e7f91a55d0d6c99f441077561c67dedc5d27c5404cdc439c4da3b4544a042a04c91c145914b2ddba8861c25ac3027639af181edb7a5e96cbdba507392f439134ba936ea1f77173244126aa0586aaa8d6e05ad1551a769c7f9ec09e6f8687eb4a469f6675ead15d8a1df2ce8b44d43b5d4dc6c3abcf061b1c68e1368794b39d396f2e26d731092062a1e01830771e46d4b718a72bcb18aa6bab13689438db2f49fc95f90c1e4c6c63b17c01237b660b9405f241ef2a03dba96318b4e9608b0fd78b2c9955f2cf61b69594739e39e8dc904daa6264453bdbc2864728c3b7cf705fae7aff4eae31be7e2f6a8c52bc25218b533a0ac58f3d175baef3c23fd88acc030a3e20f91f8b2a7f85a1bd620540e40261ab5e2f4337a2e9c30e1a861876cf0c14e1c110b0b5ecddbe4d38552e90800cd65a9eda4c0f3aed860866c6a59dce7753372108d88ee75e99f4c70bcc1a746bd82cddd6d74fe985d78aaea4a01c1824847ec7bd49ae93e89e35b618ffd89371bc3585cc97f3fc90a69a4237efc4c2d44bd5d08c12291a7867374b129e340fa79e6f7a94cabeca2aa98e16a7bdfdaa251b337b9acca3dbb375c2f6a41088288e8b26d56beca5c309bb4ce9e95c8952995f996688f6cbc037535d80249b3b3b73afe245d71351bcbec1360dd0b524ca9941023f82037ce005d1fd098bc4b03a26bdbf4d0bc0a00761d9e220b033bd40023e29656bc2f828e4546d94c105321967479c7b41203cbd07a5fbb67f06c4b160a5a1b4835e0f96c6ee72d8bddf8ba459e630d0eaa3994c035537a35f41ce20b1e099053c1c24ae6480115e5f9397996a3d5f6325c0300c1cfc7f633427a01c1d8f0db0f0d614096c168af85bac6ff6f5aa68cb83009225139c1a199d62fe6fb19a06d016c7b030f3e60e3f0ccf5d947a133d1d731a7c1f63735b22c67085744dfc9223d5e705eac1df42346e2ecc495ee249572c821e3de0bab4f551ca7e92297e7eac6ea4f9e6820a82122d053d7ff068ac6e88254ca4db6f68b088fbe30dc843a45bddd3e5732a4d4288efed41422a347fcf4e8576818ef1c91001cdcd0bc4c03ea47061f7411c39c169b8f239e129457b7a095ab0daef12f791264a8ef44cafe967633b9eb1f6618e0a22fc725fba4addbffe71032bd333ba130e89d19b95a2a2efaba7d5797897f5507f13f0c421843017095829e8407170621dcc85d4927fd5041f69c0e0832896c6f08b41a14b1fcc199b37729115026d41426b2aac2a9045d4eebd3aadb9cee94c86d44ad4466afc881060d9e0d91c02ca953f52564c94b81070c10bb94cc4825a672ed7bf4ce899e850f37d6ed82e03f2645d01c04897ded0e271b2f72426d474b90b5f150c57158d162699c33020146f0e4604514ddef7daa0d28918c80d301889c4d70a979a15c51612283b8de293ef5283bbdc1a311d92f5beadb7b302a4aab1bfdda8e8ac8370337dde7c7ba17cd224cedd7e63107b3777bf704b94974ee710c4701d107bfc503496e0ed125ef8f81440cdc0f16786ed165a7b9baf59f5e3a0295e4403e1cfa620ccb23329bb764cc7efe4578bb0157f568792b876f7080b1a0e792b2b34904807a453c7c61d2bd6f117da1a2c7545bf9ffeddeba528289e1ec21c2c991c9b81def7cdd69130d44c092874022a67614a0774cca98b671b1fe5b6430654c03cf54110ca75efbf08ca75ffabd6831120bbc2dfd46b74e8b0e4276a4d8a12753734bd29c027d038211aa1ca8f96907ab33131da0bee071f8b07b8e68d7f41dabc615ab8362da4b77078c65a3e41386bade378936706a0b0cad2d9560de02030ae6f01a5a715b60b00628de7314a0ec3841c83d5ca4b550aa85bd1eca8011498c0ff4b87871a60872b07e521484bc34335546c23a529e6c8f1c2f274c9a9847bff3a8059271248a39a0cd8a0e6f6fd409b8bee15489dcf02c126bd1622545b1389efca129fef2525f201d3b33e3d86b51513608d08e9d2897ed685296eb71697ce8bd18a4eb1da98f94e03417d4d815f3e0d83f6eb35aa7976aa9a08945cfaa21cca2f20184a434bdd1999405d83a6d9254f915cd6e47be0f587259342678d29987966501b3f3e89951fbcb2d83a90d90cbfd54daf3840b29c791e1833300c6402d5bc44db09620e750bb5eaf64320a768c3701904a1a09eb0a9ca34e8eca3d455314097895a86967e8b3b701a859843b4404f76900ba411bba07f33a2b8798bc15b0340c4dd3086bcef9189abe8891e338c462a1673e940880ba2dddf9daa3851bb85262b4c06efef9aee82198592f24dcd25111f07512b05d27ba4f8822e3ce4d1fffabe84f75fc5de1756e7a0ccd0146f42c824925a47343ff86e8ca65c793725f18adf1d7226852ed156702715bdb6470ace08869a07a9c085a605139af7f98012bb94f2d0d5f27ce0436ba8e3dd3205d711b3140e38434e8649e28f0e8c38831f20813ab5309ebb50a6e1b45b6807a0753697881180c189dd15751974f0b581f631e15e7bf38427305ba7002fb79fe912298e4129203c047d2b7033f8def1158a77b355d87245f8fcea097252cdfe7eeffbb84d58e4785cc4fcbada26ecb44bcc5d51b626a9b0094a37aa4ce32ff2c5f2ad5d56764670cf40894db010360d32b898912a4d541a71c42b6903a8d370b17e7ca12913a3f9a06ff930681d54b718213fde900c5f871071f92e4b96f6a9fe76f9f6630e56c01086e3e0e3d1365f65f9fc99287cb855092a8e061af0719106ca03dc0232e491803a89f2ac1c8d0f40308bfd125e70099754b5f0685b8461987913c4ec9d1844eff61c7c2a96e0aa4143998dcd132d0adb0a3a440e60a1ad40e44efffb01601fbcdeb532cb48d71d66e0706d064820c4b2218567863272a3268dbeb3888143d94ee6ffc5ad648456205bce5aee57ca39dae8fd48003423380a83b2508658281534596e045c08900a7676c42c6a556b08338b8a9511225de887410f61b0b49e3c6d6a987d92ecdeb04160f6a1aff25b62cdf2fd9fe91579d65e36d19b3fcd3b641a907b20e7026c686a0a71776f0e105dc13e927e2396d725422b3a29b6e21147246194befcdabf90dddc51acbfbcc889bf02f53fc434bc0820b8511211fe5c5d8d1b2afc82bae611dc830a09526baccdaa7b0d274cce381ae1f4f245d356b596e74719d7aded4473105a34b9353483be76db5bd2897b091d20d98cc9d9c227fda2b0862fd23d2db863ad6011980e89ce7aaa0395dd4643a7bc600e054db8f9ea98c6529210b9097731cd8843c1166dc4bf4470e625150718b38eba91dd4e335d095658dc13a7480186b8760c98ed78d6037f8383abb8b2c39fde64cb2d2318ffc8c8c8c9257043056cd9865381f8c59594843dcd916021c9740787636e0275c015278044e92e5f3fa4ae2e710d66a382461ccbbb817faadc2e706c3be9117fe2602a7f840723b9f419ca809e71157ef7e0a7136b5f8308a07c4663f59f64bf075fe0f5ba60b861733820aaed563ef935a4d1c3cb473ba3243553c7bc588a6b3cc6a3275186b39a24b1c9fd3723ff9b5a0b20b208e99b915888579cef5eead5e4da33f1b26e826fddd5adcad7128352b1697fe3df7097b3dd5992b4e6552d9a979091fe84b7f2bdb2331db362afdebce4985fa14470ea29f00df639d19f8372dc0d3b298c7512b1677815e2096a5d05d4df9672bb13b96dda81e2033f2a9d14eb8820e88610bda15b2a9e1d1be360939b0747ca10ceb725d46bc83c04c523103ddccbd04d81b9e68829d04d70851a2f83c2f2dac16e04f29b0254d93bfd93bd79be4922075a1c338bb16e2d326353f0fb90915da7adcd8238abff15b9657706b6a110ce66f0b41c41eb140716f279764813bea80c11b56908c8f2d05f274a26b1e5a9a2c16ee130a832a7cfe4d7fb56a522e905a25c18f2c4e46f3d259fd8758d71d8f81c771ca906381a00873fad7126e9fa51e6bebe22b6c6a59f0e040323e3f7858b40e2a587dffbe3b944d50beabde1f77977126c37e42952a3929442941d82dda8050a03541ab980e516c55382556ed0157c462112a74a0d52d184a2f3a3ad21c29ddc2ddf4e1fa088d60a10ea4a912d52065457dd4e9aa3bc0eaee8f9c59b52bb1e75b37f0d16d7b993c0d26a1c4b3198c3d94dbb91a3cfb672f5c830796161422422900103afe34379d159b35c1efc11e6ed54a122b0278bbe532a5a4ed269dfbbd2b8fd489047066417775e521210bb260dd2c2c6fba9f11762ecc0c3e7c0f80f8aa9418b0f1b9bcbbb4e0289b57a606cc10cc23b46c1bf1783513ad73c2d97e809bac4d00e341b296df1405b2a84b9bf09885e0b5277c40e8ecb3f20ab23dd240b60a641b893f298466c8513ca7a7f7e17e4a63096ca28604c55febffe6324473a9788a1475723b0172ff81ab0ad220d4714749ac844e4383e177525156338736881d83fa76a56263ce655dce8408beb78f63d2832f80a1c5c5c2f00db5dd03745ae10604037e5bc3b23586ee8553636599e743d28a33ac8ecc03a071af5cfb35de8aed732134395bfa6264d97a2fd418b15f139a955baa38b42fca0c27ce158b19feb308995d2610d752aa603e582b8d80cd65545eb90e69813694edc5c96c6e0698bd57169fc0508772628e31d1f73451d2112df135e093f8e46439d6bdf5421080c1424e7963353ea1d6dd882ca51179b6a05064b5b51681d5d349bb27eb4571d12aba2257594bd8f0a9d894f08d344b8d2a24a4a69c2a6b716074381d4c715a18969f819b0627f3626d0e330db9165b952e6fd644107a7fd8d422b75f7df9ed67f5035485ca617c482d514fedfe72da520ccce30651851412baf11f83c6670e9935d1f4d2f563a3896a6bac0c5171206dc4a15c66801c8e546fe11f56009cda2d2b4553947c082800af5ca23a7f6c162777511be088a4921c003da18bcc3c5977e44caab915a9a1009351d7b9ad43fa58b6dd17153952ad4d14911d248e9e6a383d2cf2c3cd584d188edd097b8ab5f985709e79864965f7f28e7033354e11e851829fb69c06d460ad2b0618b30cbb2893c1915ac3fc5210ce91ae004f0628f18307559037272453bf4a86b7e698e6a7cabbf2fd47d111253ef061182b5b7a598de0303c6a6a56a750ac56b03558688ef65495a6652fc3839a65139cc71c8c428f279cb5109181f3b888483d76c944ffd18640a5a748cd601e36831344ded045791c6457e5be7b0175f4b94d21e2b93100da21e43c5f9c807e98ca7555d6cecadcd5d7511f0fc30e60b4029fb416f8ce3aa69095ec1ca996ab0f432e8bd6c988c078953fa19c2e9b30d5d942e6518d7779d5e42929bbf4cbb985b923086928fb2577d1ac2aed47834770e4286632edf825c7f1a4995f84abdfec43c1a16c7b2370a62e33871e018a1dfe91c2410bdf28419ad05c8e0cef4a1ec91cc19c53fcc2bedc5e8fc5eb196c5d5a83c14b0f3163553499510150f7c73f0778646f046a6507774c32b70e5c921c448aa24f8188940bc7bbc3111590b9200aa969ffbffb67e81f61c2037e25b4b55ff2927af8e21ff380382ad309881afdf57a02575a240ad3116af3ce7af927f256d51552831356e650f93ffe177f4dfa6790f2ebde9fd60634942c606c78a233696d33fe481ec1e688051d45e6c708ea24ee2a30fdb3a70ff3eb6e7da9ba1110f07b9ad929f22904ba8b6157bf476be0e6012afc6edeccc80f0111b5099d123431a39637be9478300f62f1a3652262d588e610753a9b386d5073114f2c75a03609ef1d77c8c6b428081cd39ddd3385329b358179237d13ab2f2f76a4b4d05a7cd9c5d39774cac0e35e36badd0baac2a011a18e18e473da38c7c21980185e39432ac11d930beda0068fdacb35f06fee7d8c239bff62552c3923c02f2f029cbd682b7e8341f772efe0c12056eefa017fabfecefffd942ad144ccf51f753a99995dbeab1fef220bd34340fd83e6f1f7e696d2166ec24440d9561b18a91f76d4fdc0329aad13e685267c4066b153f3c02255f0d0a5803d3d6133440246a56f092b9a9831b25b213bd426cbe20353616b6a8cb3ec4351cf176613365c29d74fba8ff53129a81113ced1ac7a9e1f2ce08a163837194c70fb4487d8a6f27f22bd2ef43ce7dd56d2316ff719b2ad6c30f928cc1343e53eff34db527a41b1c8e64e99cfedd9e9b56476fd2bc8f924c2bd80fb860cb43d12fc7f961e5058a792aa3eb4be472c23c23cefd8bd8bce633321d9ec9c9dbb3bf151e3d4528da096ddce403ffe32d4c9d71d6b465feef1e2e1d75da70359513f16c95480cb64e3fa15f8f91278fda44e9e60f8c5242a84e4cb13144480955a28c8c69472aa71aef9a502f8f6d8d27ba928fc1ccf7f7bfc0405b233aa337620fc4f6760de018510e5bd58208b59f846c0a6418d05ca47f17e9cd609ff63efc2305065d7bbc4c31e5000aca1c915398b211f2e260adbb80a231184d94508a0e97404bc228f38ba2a5bf6788519912b06eddfe5ee325a800ab2cbffe338f4107d663f5b4c2f02036c6a782c7389f91b199cb5a28ea28dbe121cd022ac2cfb7b2a8dca633220cc7b5964c91f6e25b82373da1431a1091489f6e6f372f200211c86c4c5c5c29d7e2bc017379ea7d2c49170f30b466f7dbef2c7bac77af5f099a518c4eea7b204746c14dfdd7a288e92a7e13b63e2677038470c45de6b3015f725e3eaffbf8e1a0061c59f15b720b180fccc817784f58370dd0d8916359dd9868f8523c9372318a1bc7cc5d1a63a60d1c7108a0933d901ec4a8acffe79a5cd1cf8334846f9f9b2284a815e9be0200ebec58ec0104422810f33a23121e5344a603d87c5012d40c1e59243431d57c6583e39d6e0536ee92c4e3e7c289755ab13c84271efe74fb5358d1cf6202cb1ca6ff206fe0002c8445d9dc1e2b306543776a61a70fc3936d72ecc69ae4310122f94a939b3512f427f1774f5c90445c15aad436e7d5096e99e674418ed6e98b7e0885b4a7318c88523821e5fe8a22348420cc3167f0a7c3689fb7fb889a602eeba42fb19db0e28302cd086389b2052f769e8a76dd417029bd976711097c686cd587982259b9db509a1bcae272c806f7349abc70746934ee5328f25583860948afb098641ab6328d7a63d4bb40508c63af89228c06a69d8018bdc9f00d4b256cd26b06de55ac39ce6a82384fb410bc3a3bb151c82baa607703eba5394b97b82954284df5a3294e9e7e5287dc7096b510a009332f8899001508c3160166ac5e41cffc1a0d10144f24e92beae5178bbcb75e4184c0ae90e0176d544abb14afac751caa5d2588d97719f9b1e694e245957a404321cf18b4c8dc9b66083ab3408b8fa07480c25e75d5e0c5bd4429bf17740978b3bea30100dcff1fdb0e173a1ce5155045699ff08119ff6349c69177c5067b4422aee0932d85b0ae60422bb8ea9e423d5f8245bb304a79648571a5c5e2011a2092f7298e6845e311dc6590377bc562b7b82adb7ee2e4047304f27b7a723af3a0e90fe8d5f039fb2b731ac0f4c3c457f370954e620e994c7f8cbf5262e7eccca5de199ba3817912f4aa7ae1e067981e7c4ad81f2cb3a644e367a43278b09e923106efab35b9af5811697a5eb4799a58e2c064448c6a8435d23fc65e49831f3cb8159431cc55be89201cf51b7459f44cc25a1cca1319ad75786db0caaac92ca2cc9b647fb0f0b3058e946763ed7aa2822f6463f11433ce16b5158940ed1bc65f98dea47dd75b63119d4b2ea7d4df67cc160b304a44d079bd8e45612aef6c435292b32ca15dfaebe06173e67c7b41e4829fe63ac05c11732172fda5bde6960e0d9a3fc9845fcf8d405cb7b1d37a928f0329a7ea1d70333973eda8b5572da2bde8172324c62eb553580b0c75f2341aa83d672831fa3ccad0b2f9b513681cc72ff75f8f5f4d8777590f51f6fc0c76badd80c664e35d0f64fc3f6c461bf0dedc9d802f2eb73513c38dae582bd716872e9d5f2866cdecf628adb30c3e7432ec36be6c70fdb4df2b5c7a954d001e913dfa7382abc451837bbc2680185b6a76920de227f2be8d5ce5ccabdfc57aff50084010655c64a8c6d94ab199bb45b9e13fb33970fe6dbfd2cf34f3da8713cf2663f4b7796e0a02558106f4321a39d5ddfe42c9ac5fceb909ea33f870169700057704e005ea59ee7c3f3987ca74c85dba3ef9f78a6e752df0d9b0c5b26ece3fc2c1303ecf01021a1fedb5c25f9b6e0ee810675c625dfdb5304977deb26d401cdd0a15211235f0946aa8e3ea1fb8296b98a1a1aa62d2460598d96f87a4bbd29707497b0d4a3ce891e6f3ef5b5f5d627c1bf37106c3ba0e4fbef9ac93bd4a1d1263ad37e5e6b341ab92daba0c840741567a1e8aaecb3a30961e2aa6ff24a375f33215190840837d6f1a879a71ccaa187954e6f4965673890e1a49b7b3a5148a36447eebf8dbd302b1f5e54869676ac174af1f17967b4374010347e31fd9a0b33de35f2906ac3e9af21656b3363ed7e07d7589f166e3f7922c9acd47bcc603f9eb6b0f7217d9df23dd59451f49b6de9b18536baa3ecb2cea8eab09e589be833b308390f09d4959445aad5971793aa7b721060cd47f40b2384cef1226ac8b6354f5804a0c77c946c41684199cc05afec6c9b3d6258c0a893cb83863fb7f5e848bae93baa31b2af8af538b25e2a74270e1f1ed9def763ac8b6ceb6702c8f7653e3a8296885f6864ef8b575e7c028cd14eba96146e85fd4523c8dfe8ee3705c3bd858e454b2f9f2a7360b628a0375a094614c4a0871433db1ffca5de7965b16c9af62e0e089891309a664edd81316d815cdc145df9e80457b3ea205cd656c2b244b4e000e45207f8b2c702b33213624862e92ff447df3563b4b8b3e6085a0bc5c1eb461d005583f9adb486e648c155e74ad9bd7d525af5995ea01b9476a0188471c9b59d101aaabeec00ce5ac54c861d5dcaa1cac7f300bdb964f89dfe49846073089fbe91b50c6194a98640406817be8f55261f7654e230dc9d3f4ed626ac32542c7b8316f5d488d3f4f14558df9987e1cf8663b6f2e2ec2180fa1404bc2895f04a10bb43a10808532aee42c45bb885b03ea6b5011d9d766c0891f4059322d50d660b4daa89d49d3f4abc87e6d6712084f2e38bc412d70c1a4bed0bc618d1b667afda510a89e741af9026d2140f852ebc4830fc455331a92f1b728077138007c298c6c9c94af079835f96b1c8ac4118ed597e7e8610c63e59cf01b919d98ce0f60134171221706d1733ec449a24ce58e63cd4604857710415ff19ddf0292d8d635fe2c6f7e3ee51d48939c76cfb79658623a2d56c7682556661d1e411f4d9e16b0e396e79011c5881423fd5c673611344d3e13e2576e765ff0ef8eba1027fa33a63043b2ec6f7c655646d386b27a59201a908fa361644994b2778c56e196b2dc80816dcadaf1f26d1f6cf6a3c2ea9731332e3fda0fb55c47227bbe97d551697c2a8596b9bc15d8df54e3e3393986743d2b64f4426a6eec3bdaff83e5c8e57739d401bf2dedce296c4ba089d4f83034e3fc97a198ae62c1c83724f4cac58b5fe1e57607b218a6d51d8e980dc30d4b06bb94a8ce4a309fd995c423137453016777dda588844c7daec20d600b2f0bb4783f8a58ff37845c0e0da958f53812f5a19431dc80ada2fa244febbb93e0a0317f904b9917d594f16d4832cf1d36ae72edc70a7177e68c8d45be1448fa0aac7d53284da693d9546448fb9fc8d86539a9b43191679b6aa9e3425d5f366a17ae63cf5246e699a15013913174013e657d2bfa64e0194c94371e21e499d0e7a17491513bd255cdae6a4b3aaf9c503aecb099f76e47906128c1ae286539aa1e383f520759fe8b507c69a2817fb803fdc5308fd38d17fde3e4aadd2d02394e54deeb434e981e981708180894af274006efc79baa0f1eb988303623af8b6c7c3529eee506e2f60c34339e29edd83551f78d3f8a94a6f3f6cf7702c4bf7150ca45dc2769e8359f7e54a87e75ff5e7ecfc6937f02b1352912bb3a75b07646089a01cb1ada9bff4a8a7cbce50495365746914938de43ce40df78d6c9a8ea7b6c56cf68ea0f107666d7977d97f58e7b49ea0d8510fb01c53f2e0883a45c7f1bb48b5227233859fc342b562d0c9e017e4171f4f990cb84ae8183ba071b9be6155c540f409c4652d31764896be1fc60173a1506912a16ed55f2053677529761a6552fc3fd2750645ee9f9c1cbc07d1ff2ad47c9d76cb411e52950c8b3e48b5bbcfdd45a4b4c3603c86cbcf4777f092d8456cd24781ee7b76141a7a5f9816c8e471502502766331709ca89453d9d7c647c7c25870b79d86a850a356daa341c95569d7a7829e9d841954b5a2933302f5a1aec5b1e505c735e29deb228a41e7139f80bd9a5a06904a0a2150e411dd8f7b277cc25f26724ab15bb2e1695695f0466760569463856f9b5581dfdc3cac46a1a680c2224bcd9679ab3fc26e41ed5f9a9ec00f1c38a754b6975b58a4671cb17b31cde36469232be39e716d8e516292584415a8169dfe1a139d7c7b443b4c19e953275f71d0808b21da44b14c997c8c26dda8234693139a34237c036c41030abbd380664b9015c23c13052146963c1ec209aaf3df54ccacadbeb536e73bd272ead969ef53c1c0669f8f97b41ddc030d23af660ba9cdf40fba3ff4fee588541dcab63fe4528f3cd9c7cdbf0cb171103ea92516159a11a426f2c35f5ba9e0e6e7b95e7bbf0e80d69833468b062b8696a380cd96fe9e825d1abdbe04adf7aa49d03b6ed4b3134e494d0022a48b205ec3bfbe80e0167d4f28b3a9af1b7ca5fe5daef1343edee89e1419a5a15d6842b0d0e8f5bbe0280e6eb420028bf80bc396012361aa18283882dba30e3d31e0dbc2f3aee7cbc63619657beb2d6f606a1205ff069f5944ee075684ee78e6fa239158ec93e574c00303901efbb89014b66cf755b717dc97304eccbf61fdcf849146750169b326aa342c078c52c5fca30c8cab8642918a453c0b3131f072f57cf030044830e1cb05f3e1754127aec31155be62dba239dd8acfd218eed046b6c7e8b730eff745cd9a91f6f3efd798c9a017a3302819153d394711b740da18950ae6deafe6d5271269e6485e7dcd5ff8b0e9c0bbc6452f868e7bc36d157123c6bc30a92d79a4541190fbb1f2f3a93e936070131e06d78f6f9630a63968809dc0bc0f8a63394438148da2c3cff385f1c3146f13672414d0888d6cd6e78fd384cd366b88233c1ab551917df64a43f9d533d7a7431b7d0e2c6b2ce18a25e7cf9fed4a4db79f4977923832bb222f6261ee4b37b733bef30f18f58d64c2989aeaf32c618baafc0f3fa5e6cf9a69f4676dd5dffb1035a48a34cc824cea29265c89b320025bf5e90dd1f8623497815d48faf5410beef9419856f9b4de99db63748b438294f331d26d7ac4cdd2e70fd5ddbd6b6b77bddc89bd61a7347cb74f8d06205ac7f8a43ade1f7b506cdf095355bf0e0368b83f82182bc2220100737ad1d1748f83df27e35f6c0a5500641b28c0ce953ac0972ed45652a12a7fd4d6577247126a56f1ec00a11874ed580f3c0b43b8b87dfb6a53e94a018765e685e2fe4b95525dbe9c121a006cd1295c063550ae9015383ab0b9e59dfb170b6768f89c4bbcd372ae866cda6646bd66e86fdd0045fb98dba0762eb8c7a687a550ed8167430e8cce6f3c1338e00d841ae449b1e6728372b525c43729750c83c404b6adf61bde2cecf2ecee863d0936ad0a3df61a322c1fd840a5e2761234033f2d8ae01c40b64d6122cdec28a864958e007522326c14c4a96c203485a2bf9ca347ac7386f015a20ffbfb87a7c30114b813e339a6c275cc0a01d7d771c00bcc69ea178754ff90b19522de6574af3d9575134d6baf34e7ac4a4cbbe4b88a9a39a1c90df37c4ad75734fccccd5c865926a87ae2f98a0133a02a716d8be849e741e37d81373fd846d47962468a515af689928cac866d96278ce189aaf40dd95d768d82340477a339d2516445daa8c30de2796bde548cf212ea06cb53abe29ccbb817cd18147e10b99b2e39a489193f2c7096ce7cd6a4c2600b5f6597ed28e4d61328c8478a9ac8a40dc84fd491547dc72f83189b7a9a50d8c88ec172a5a19afc195de790c7846b517fdd99e9c2ef160411b3a27c9b32ea893ff2fc78a53e105e1a7599e8ccb0cd2a4ba7bd1e8429f812f1c7392b8d79431a1f3127f78aebc1daa8951b6f3c9247a9f6023055520b301ded9f8b3dec31bc389451f35032f0ed274ff5543709dda4eb2170c567f6514bd278ef8ce12d1a3fa7b410167184e76f033f3fe118e771de2327570fde39936a28db0b8740bdb1a00d20765ff65c5cba1ce13a410bde982a33aa186d99036fd8af60964c29d81beb4650fb946087b032b6e636c0917f76365dcb7ca92942934da4ad7574468801f0d21c93cdbf78648e6be8bc9347217b78dbb0277192bf9bf88de1a2e6aa6146aed44ec6c1e5b51f65b413e39bcc26be9deb02dfb68538d3b72c615bdd5a1124e5be0d5404bfb790b35532cde77997568e43b434907034dcf67df178ebe672f7fde44c0436fdfc1aa6849466273a1907d269204abb82a7b740d9904434fe39ab255f34322a07a7aceeb7767a5987b7ca812c62b0ca7be231db6d2fc7733b531435da5c40f268f81053226cd18c2c07b500e6573d48682b83b2d231e67db5d8bb8d62c788e63b4c4a86bbfc55b19293852603a4c301a9dc134acb8dc5e848251b894ca7bdcd62a13c548a591a8c7411d26f46ea1ebfbe9d2b13302e66187be23dbbc52b9d9639f216bb3223110d7c8b25a2a769e9a2ad7813e269e19cf82cd69980af979617b9cd9c23e21912903da137f1c7cfbb4524b071efba69ac2e5a5474da937b514bd6c73c130743f751f06081fd67e16d3fadfe25c6479d222edfedbc10c0d727e5ea4128c57c8dabc11fe0efddab0fc1ae3422c33fad0a01cb1a49bab454fd4ec40d00942a34e5f2175b4252ead4c724cb4b5b4f15dbfd17f263ee229fcc6e007dc196af3f101126d512c3387f87541c37ef67b6353c4b319d890ea7e08eec8ba299ad0ad118c21ff772890836514916e006a0cb68eb236bd7222eb89b0f96d5a87d17c19e5483d94dc43abce1f87315256cba2a398df75df6a79c1c54a02c0a7c15dec7122c70cb6733c532e02495f7842aa858898dee72a8aa8f2730b8ad915c6552bec0b189e3d10a2355739eca4d52f0916b96cafad63c2ba29d0d65bc14654a8264f22c9aabe88216fc1cb85826e540a5170dd723e1e58a52cbd8217a7a1fc76f0f09b6b51f384561d142c66cac9729f0678b6657dba4c69286a768ad74bbf46ee6164e6e28df6efd445cb4897a02bec7b71a415007903d799a87cce321cf1df2a4328de371af7b233351c6f64410d170ba38391cf3afd6339b79d55154f7c9a0461a0e5938b60cb75ce8c96c35e0ba677f8a50114c4e3d1b75250e199827d9a7e874420582ec0d07b708f5b9d67483f1d7c3be26371a02c4ce7d52957e1a888d33dabd77692da05f86dd1c6ef1072f96e43ee3683d4240412836a9b1b132c27ec07611ff8edf033f97b9e237b0f7a33d50847ffff02fd2319b58d1cefe511d3b890d56070195cc67b9928520f9619fe6f0ca2ea9ef2b4df61123265741e527ee29f7801196fd7030acaa53ca291e5c1e02ef5a0f3ae6b0f88dc4e89383f8251895f9711160dd19a21e89a7547ccfb0fec334917389ca69c9f716518771ae2e5491ca47c75015c3360012fa8390594e584214a9f81560b070e11ef643a075c14fd77444174dc27f442e2e208a755c9ed85bc43f3dc008d22ab88ed9063e4e6d90448ba513b95858561470455b4892ef31b9a5770e96559685f65c288aa8f2cc7674e064a81c8ca08f0f7198f3d6be0092d8e084471411c85dc3c9c1b21e64bbea575e846230ea321b09e5c7da89e22b48366359d1df800136c9ceabce4f0371ba1b25f1672d1835787762a81212c2f9abf5a7542bf3dd8519a9d905c05edbe77d7a7ac3d4c4fcd74a0c68cc86ff4fce05beb02b918784b23332ef3605adbb6171cd1f0ee005cc0e59ea8098a5badc84837428606df50d994b3e5621b24a19c1dfd21783d74654f4f1bc310d358f1d3e13beadec64e3c22f42ecc8f766defc315a463102186d835021ad20e7a4b376913999d594099575ee1ce9ff33583522ee319f535507d629edb32f4865d5ab397e820c61e4c6cb144a0fa7e88c49d09416193a18dcd07979574559608432237e5ccb0a3cc598509e8277bc414d76a324aea7535042ec64389b6ed1140c2ebe5fac75dd2188e23e8fe8ad90f5fabd0f62de721e602e716f1be5f4696b8d67b1852e505b1a72c4ec58348b6c7817a1c66c61f36b18104768a31344412a3c4fadfb0a6a3ee6e56eef23f2fc799676c57c5fc018bc4caa5fe2502a3b489800d049dbe21b41da7047eba7156c3626d873c08d4318d2d164ba0c80bc81d3243c5c87daa071e2a2abda153462bd4c7c605d8d4966204e26e470783af856c10bb4d02032fc9db8f1ea53f4f8a8c97093318cd7ac864693774926430855344841f604e8e1c54cfa76bb702c06c8c5b1312e1a926e4affb3237a6de238d1c391251be8c4f79cfc89e9872ab213c83314e7ef7131bb707747a663a08bad44690442f495cd14fc307e51ff4a75f60b850032905a55267705fa23c3081d67031f710dde288553655248308d519dc6b4504390dd89b17f073d8041cc529c7965dfdd347ef8f4155e749f9f8210eae7413516a1100a85fdba766011a045aef4a2f1f3875c9c89e09af7acca042a2239f50c0934a42951842f5979fb76e07eab1c201a10eb47275b0843042d9f331a276fd7e085a08450a25f284eb4a4160f6e4852ecddc57785004cd2122767eceaf9f83862f2716934b5861d9d1f8492ba7edb0a05a996e8cf95fb7c06537524f1795cd61c642bfd9d03fc2c4eadbbd7ab136a34b1a7e6dde2913adfbdcc63b5e154def1c694ac40f7ca196cacc22ca23b18eef385ccbd4ee0de28bd1ac78abb450c6a0390fbee387db63746bc178718a4cf60cce4640c706db6173616bcd83c848b226270173e224172284980ab5925f76027cd6682fd266e3d2bcd6a2bb57753572afd7c8c8da92d94662f852ccf7fd667dc23b84197a61b4b087d7f50413ab0323e173297f449e0eb76a0ad406f497b57bd0d6a9af93602ba51910dfbdcf8cf71406ffca6087aa97fcfd4e15875e59095439bd3a02b5fb1fa7a4510515d9f93d4ce3280c72d7420c51d8ff1ac021a29f0af1b75c4061a8ee95b2d090b4ef6bd579d12f9b495335d1c15f42c77f6930cd68f348356560c14ad603f696d1672772ed8fd984b439107bae355f7ac8d9e3b20b80e872393aa088a3ef2d045118a0a2210a07301c12229d76e686434f6c87673632ae931680c8240e5964543f5f7db7586d75a7f78e8a208456c405842ac667be2bd4ae27e7f70904f09edba3d936f311ac4ffbb6ba1b0943a6eb8a46320ae66cd5cc62c627393e59210ac8cb739a4298732f90ee20bd9a9bad00bf14d68c96f565ca62b18832d13d08801ba6d95da52e56d1336bdc39c87e128d84a1df0325b035f086997048b2fec79dd271edce0c07033c17e4059a86f1c054c5e0bc33265026f9b9a2e8d4a6321feea8332e0a8c0455c12b3e4cc21883086ab86db9a311dc2520deca68994482ff12e1faead46569926cd2a9bff1631833dd53c49bb7a0a8cd8ccb81a57fceb2ddb3763b7ba8992247d1795751aa219d200c641b3ab350e03b5f1e5a044816073cc4bbe8b79fddfe3207fdd0acccb24eff3b0c4771a34c480bcdc4d87e3b2baced9b89f59083293918fc14d75c049368486e9e035193c5a3b90b339e8b9a33f87a1c48f7f642bd5934d1c49889e9aa55bd46f09f1540cc68d9981e17f076610cec0dcd1770714271c09bd720454aceda4d51a3ea1042a0fe91cea0851d36ec888b7c33a40e1a7e18d356a4953aa3ab5ae6a23246701a42a41792a01e442bd9c44aa328089d48b02c9230f924ed47d30efb5aa5adebda889234c955895714820b1ebcd5dc29674f58ccc74614fafde12708c0e04bf9919c69413fb4e9062a8e69caac937c09e5c4345e6f53167c4ace8f56d0485c0451a7d9bbc5255f5fdd005ce5bf062fa967ff305dd287ef56cc966055dc840d19eb11046c65b8b5ef9ed8d625933f0d619b38cf5b2791800f26c81b0d9df414691b4bf286e3c7fc8078d598e790f8ede22a35a66966847ba728dc5e1633c340da7f4bb5f76910f1201c39084d01ddca78c1c86415874c675dde890bed30782d811882ff5574a9f2feab062179bbefe0cdc9d562e85086f54d5161ee320e54d82b418a605479e33129cd4efefe8ee3045968a812f6ec30b8e58df3b3aae725d5f6df1bdb47092c56a5bedc83cbd38c5895d824d2bf19696d07d274ad573653077c3d7ad3d62f8331e79f7dfbaafd55af420fb73180cbdcadf898b1e786fad8d87cb67e304ff5c2d6fe549f4656b6c2b5ffb5b6e784592dbbc166260b330c1ebf51b0d6e164838e8b5385c73b54b4d96577123a4b177ff9cc3205223f33f46cc601b18a0ab69d0130647d29745682a9637336ecd9a6eec8d98afea7163ddb0aa4a8e70cf3b949523e0c5bce5a79cdf65898996f3289ef9d299077e8512446c30bb4c698b52037ce2013f6146861473f600b759db576940c9fed516e8fce7abb76a14e2033e91f51792014b2845cc5ec80734a134319b421a9084d2c696a189753270f8503c522c8985267329d2d61505f9343e372ccc7c316f820f446913f423969fd79949da5020f51a7f69311ebcf43b8667dc53b738e6e5e0a2d53512646386a6549c152ef4fc1f0e858645f75823a0428374d7b15a460c8678b2c71ffe92b7878556cc68cff2722d94712515a77cb3e1cf6e41d2e3102d96d7da0937411bc01c4d7798270dfc9eb7aa4077fc6f3f578235a52d0c8ae86ad1ce0457ce600a2cc303ba92c6865a2094aa710e392e36aa27a35b8535dc60d64c5f6d820d5d21de671df986511ae59f7ac0d7a5067d29afe88c290614608c113031003a3037bd7c45014cce4d1d05a1b881705dcda730408e3a3bb32ada5e7a6969932449a44c52065d085008600854242dd16665b2ad7513a5661652b25f51b5bb6f9fb427efeddba7b127f0edd7e079ed4d113dd9d2e2fd96a43fd113752772f53352fdeee04169ad9c2851519b8ad2010b91590d3a43604268c907a5d68cbbff6ecfd3a2bf6d1f8ab3eb09b038a356282b272c4f0ed893254b16d64a898a4938f341e1b880899224fb068c01319132c44db5a9a88ae4f311826d81868d0e58eae995eaa90391d995ee8504175c69e20a911a74641c115256c3902a99874b2b0869541922e4b5692f21331407bbf09032049399928f188d8868005fc8e08b98120ff2e5b6d4ea62c1177002b264c9b2b5bc60b7dfb61a1a8fa00c344b962c59f6762fae1ed511a28871450c1e23d56849cc3bbd983de5e024bf02430e14c6be97c310b5da545c711def69b138e926cc3ebbe3a4bf8ecc8a9baeb869c74d58dca48df6ca2ce6a4d5b9285412ade5e0a614ea0f363e20a84c10badba0a921dcccc1c3f637018b36f38ed7728436b995c9ad3c19191acb69e3260de5a68b24a5ddbc4a1842c7193aef38e9c59c0c439b79c7f476dc54b9959b38272c282d37716310baa3b8dc84e3a61c5c4567fbef6881c65a0ce1b2a76eb43127f9df3163978db9de8b9920ec8e17e5a47b310ae3df838f5d28147659b1e2a48b119af2409b9e59bd98e98a366d2c897631429b7575a34d2f66639eac965ecc8b7da9eddf8d954fc93e42d31d9bb3d6a2bcf3dae8421afafece7699cc49ab59cd967cb8697b2d265ad90db4f63a0fc4b5d1ba8c74d2673b3199b5e2a66d1c957cd8989bb28e9b46756677ae8d9be88e619163712bcec90f5605bef779a5592d9b63613b2e4934b74d7ac5c5085d352d89aedba4572e0865fb6b9a7635ae251aa1efc8b19cdcc270ac295272abef4665a4f43cb1133fd4a87ef846cad4a8d63aee78324bf2ce9b6def5ea0f16296e4f8d50585718c5d6f7ab1f29bd026b7c25eec888ead38969bb61abaee6cd38bf9832f37d519a8b2a7eab224fb3939d69a5eccf462d6a5086ddff462db77c61d68ed5d92e86d9bf4ca101a0b56a0fb2cb44973681879a0fdb91678e0561ca03049c4bc993d89608e1cccc0850221dc10a3f4f776ecc9e4011249d4c0066fe49ca04405a9dba6610de3b18bc554e2cafe278a41055f00c97ef740e9577f38ca5a8cb18dc1f400d521a27d0788501d229a1d3d407576fd3a6e76a3e20d2138098349185112b022c9ffbca2afba57a512498a4479c5b2a306be007ef4d317dc1f48efa5f77aff85f5d3b7de5037485219af244a6ab904a1f1a66068d359a7aa43ed48290c3ed5952539f71ecb4b4b624a2c90a417653673e0bfe580fd7e654f9ec395379a222c75b584369dc9f6af58ee2a673f2d16b1ce2cc98a932b0ab3c41231b6bf5932eb2a25a5a2ea15a229b6bff51759faf4504838d71e1d86a11e434dc564e584e52494d66a157a888d1354272cf1c0ae07664ff77a601e0fcc53a56a9ae6b7b6b86d5e1a1e6a79df7a6214c66d79470f0c0a4d9df4c03cb0ed1e9807a64ab9acd8fe22d35631ef2cd6921369a9f78d96d2ee9fe90e26b47b93fcdb6b9beac0c916b4d65bebbd177bac8054a025b1582c168bc556d66563f6148b51200ea4e45c7534452e8e867014074fcafc464aef7e55ac1450a4ccca2c521c3c291d84211673073f27b5bd75e33197792ba6c568e9263834feeec1372b6c8b022042770f3e11eda44944b4eb681ad915e6e448b44d97ed72a41347d8b31f101a4633e8f77c0540e2efe0419b9f013fc9dc14862158419058127720d1660984459bde02c5f1194798c6a347cf08be094ab38234e168e38d23fccdd0bdf743c0ffbcee034203b763deb2a77a73d3d2f541b1e67c75e63794e4bf737323476d79ccc3f0588cc2b4cad04850771cea36e9ce8d0e14c6653ef318ddd19139e9b11c1dc4b155592676659dd896c766b87a93b97167672708088259b5a38abbcbb57297cb55efe85e979f5238b7a96ea742c6b9dd75c5b2966c1748a54a0db6596137bbee4820054aaa8812f8216632fd8dedef944a1533b6f9bd609bee45add5df98d2022d62b6bf47a4e50d6d56d78fa0365e8711d0535569ffaa1f8e6c2e37c3f5ae1b0ae32a252895d1685d82d62748d87634bb5d2ddeb65a3f25249c596470c2f6df70a80c68fb6f399b147a7256ab0b2b602249a224ff182b5808a9d0e6e6dafe9bcb5979c378c31bc61bc659b39b6b739d98ca28350ac2ebbc5164abe8e5d512e8c99b8292fc8ab6d54b7b658c5d227bc24e70140c45e4bdbc9707ab3fc6f0aab8e97e16fd8ee6de0bd402e18072ea8fedfd415340a91bd56ab3b6ff67c79e40e7f6ffc0ec498b59927f8e69d96776f371d993d7b2a427e80f51fd40f9b43e2c2ac3bd7e6d1741f1a1b67f0b63e8510cf8bf26e7c66fe4a3729286e3a46bfbf65e1e39843635252d7ba2b695a3b15c49ecfaa609dbb59b5dcd123e424f78094a65b8c72ecff8c42beae5003f0c7673235e893e68ef4565bc7013de1e1b69de4b8444952c4e9401a5892ca5bf27b32702dc208a39c65083871b304a7fef654f1bd051b1808d23676c1144e9efc1ec898231440eb8c051c112265a94fe5a8c097aea46acc5f218c3a7bda3d7a2a589cfd39e34978b361104eadac31f8515e82528c9f113dba770e12b5c78a5d266e9b5f25ea96d47efe5bd46a9ec202a6a4d81bedb6ffd5099fbf5397adabe7ea5270795fc63fcdf9fa4d9349086267b1ec61853984a35df8a8899718a443179d476bc30d45ab91b5815312db4d0428774e941e36d7edff28ff17f7fd2771de96b71bebcbcb83bc6be7a797979799142db6dbebcf0a0fed8b8a0a6fbe0c75630eb940d26d12d4d36953dd172856dd2170fe06a8f10b0a15a80a6fef8464a234029404df7a910c08d3fd338b60f411403bd8d2c977fcdf1af2ccf5f5eb48b5fc00f7f0213081f880bb127f0c35104da68b3b2b0766dc743f7eab878154422b718bf9031ce5150a141f1685086d0a608caf6d7726e2872eefa9e28456c7fce3b627b485092b7528213bdecc4d190cec7911e72ca4eec62a3215c17b324501c8d9d6c549f1ba925914c2fe5bd37763ba377763bafb5ec9bdd13daf4581eab0015855d47bbe22fd11fdad70fd153a7e90ffc2e4aa2db013cc8a1245a7b685acb49fe39772ead55df268f2c279d654f5dce2cd6c52107bd420f93b4fdbb9d2bbc14cc743bde89c510e4110df7c2dafeee25164390f7256bc98d2c8fa55f9f163a6df8884224c21b683f4b0184d53def9d1151273920a45a4ac998b2512ef6bd97e3369cdf5a6bdd5aeba23040664e9e965ab07683dd77a43758b0de5a6fce4c7cbc61844e59e44611a5e4bee50640802ce0003309d8f18536e96b48deb6bce16dc31b2712c9cca0329ffd36fd8fca6c4f4d107c33dcf4a73cf1fa27795019cfc4dabeb8a57e0b95a94f3e8f142885a12e50f300228cb3159f886d3ae50936f61547f6bb1dda95f3bab03e58ffb3a28e6b69f97147979616af32d4d1e503756c71320cfdbbe41fe3fffea43f0f50119a8ee2a9b58bb77bb77a6bbd1ecfdbd273f36ca89688b43de5ac75cebf901a3880b74965d66b0e804519acb2a6080a66a0caef8b80c6e8a27c52ed68adc8a8ccddf53c6367ccc6dc05d3ba6e94ed5084d430432dbfcf62deb5e5a63afaf69de77941bc0d77af891dae2a1c2a5371404fb887982aa68aa962aa188509d1409bf5b4e769efbddf4d2c16522da1e23297e9bdf4de5a39ee81389745d287d830d765babd5a6bf54626dd8b860bc3b3c10b7eb0445984e620086e0851e64c5d266cff05bca0838dbd07db5df05aadde915b6a8f053829c449f5c7a63d5c0ad801a4051a012204d41ff7ad365a1127aa3000714dbba93d5c40687004385ce54b3e8f86c2388d7dcd5e3454c6dd9dc69e5e9ce4ef03d484f6cf34987b0f87d4af570a6bbe6d9be7511b549c6f59c4665e38f102c76459d689d96c369bcdce3aabb3599dd559e53ecf03f1ea02035d35f0058c93a3e05cf2e185a1e78521f670e879a1e781337819f2df56f49d31f6bc307c2f0cc3300c4593484bde4adbfbaec64a3ebcf0f3f13cd0b3578f40c49899050861809343f2fd883438a9fa1b9b6a6d33a6be2cc93fcca2e568cf870f7e87e4f37ee34aec8cbe927c61458a1459a881064d430d22a268410517aa98874020d0bd20516b694f087e4e58515c38b399657dbc04d8533959df142c387bd32946a0c1aeff794e4508d736331521a0d8f5fd3d6a8e60dba7db24c1b6634ac7b4192ad54a654fa21b6d52570af507960195a142657638d1fe3554c66eb7410710c862b0d7dd210e7ac17d6f4cc99b41c984464dd55a6b8c0bf4473d6b8f3306ee895dbf9ef5c74bede1ef2ff58701680fff1fe2b751e0aa5478c04979a1b9769881ca939a62042cb6595b3bdcb8595f1566c267e0d8363f55adb5d67ada930f254aaa510cd5099d7da9c95b46b6261ad9140ca26d4d61bcdd4212aaaaaa2bf0c508baaa54b1300c1d35d0da533084a3b5dff096f3b66dfa1a1162859cb033fbb9afb416894852a3c1ee59a94492ffb3d968a66b0c909acac40a30d2286b8a7c33e0a2a4ee826959dbbfa60835e33584f249f745c82da20042f9047fcc5319b7a1ded39e409f6fb4eaaeb03d79fe3e0f213e3f1bedec85e522d96eb35dfbc44c3f7168124b871ef4356cab430d5d1f87c49a2743c2f06b42e158e3f9cfd784afdfb3d566fb9cf785c41ad05803fad038029f9635a11f81348cee587e3548edbe04d554558950a84cb551d0132e8292fc9f3c89228a6dd6949d45d9664df90becccce280c4e4261b0dc53b4334bba36a2dd62a34b60c307a808a8e9ce2a0f5f0c0d0dda7dc1a2fdadb5b310ebf34f6ce5df7fef9f100ae39ab6c2a3a0424cfdeebd77c3410058017062369b9d36cff26c664fd9da3ccb337c3a19d3a94261fc5d2d0a93294c389b85b3cd8513859af90caf50191aee8472d053080bd7406d0f5d307b0aafa024fff015ae2125b65d65495174a8da6e7228fb26870a6754a6e2d3490f83404f7825473de2f9015ba1308ea2302120b419cec2999b4cbc0aaf5cd97193b925f19c6e32316a6371531237edf841fb9f30459bf8bcb2fdb1159c634f6ed01f9e22680fb7273c8633ed2a6c13e7e49c399488436dffea492284c6361466b6fd3114b2550f98708208a2d9ca8c15ae12f3c744e06caf3fa60082e220a8a96a5d88b408d9603b46d9aab5288c47a99a56efbdf86d8965b35900581a07002e005811ae6a78da534b4bd812d6162a1a6fb3a5254412a2ea0f70fb87aa30654f9e0f37d2caf6ffcea03f6a94da43e7937d312aa3d52abe343621e809c72c69436d29963d793625b66f4898db4967a0bff73c9539424ffacded87ed9b101f2a414f7ac4b1cf88654b6c1f71f8c4f611c75169adb46e7f6ffe11156f3bd8240c471bfd3ee4327c0adee0c2ce6134442bc1a7bb0958f70ec5f07d2895e07f926c336425240be984a2d41fa133680f0fa551ab506d33747dc4f007ea82bf16431465c1ff8a618ac2f87bc4906549de8219a6805216cc3008336ced5088551c4acc4224647b2bbd310cb7330e6d86e1199e007071de4db153839b4da7d400056ead860708a592b72ab924f1b5b0fd63c0510712da3e599a694c8c5c232bceeffcbed39ebeef3b3fed3b3f1495f1b6ff87c44df6cf6ff5b176cc49e766dc6c937db4525a29b5df91effcce20741d4dad85face6df69da318c61175d2ef38aafff9ed10fab7f99da9effc509a8895609425712bd8fe38b3fb79617830d30b63fb9bdecc9b7957bc2a1ecc9b79617ce7959db11dff404f363ec494dba372995174266f323759218a30f111a87d5a927f9a4c93691ac61efeeec5e0bd39df798e5c3b4ea587d8a6546cbcf0b129159b253bdbed73d6fa867aa0f1d337c1842f34fecc715c10fc96764fc15a3b77b99e4952bdb5b722b65b8dd9d80791187749e1d5a73cf0ae1b869a65d38f560fa98795c28041ec6e7132a31942eff9212dfff996ffbc595b2940a14522b52900a1452b73f21bc951f588a35afff3f9d058a3e33f5fd3f29e375282c411f8a137526a71043ee88d949e0eb164da9918fa7b4f8708c4923c8523b48e6f79ed39eebbaedb64c0bf7d10fcdb1bd9396f36d7907bcbdd244e7ee31dcda01ff443f483748cfaed3804f4a1b1a665a40fc47a445a7e8638a9bf9146f484362f4cb673abc474ae952b6ecaa3b6372d89938ef136ebc971f6b41df921a6dd1ddb322c8ed42354663b426176a8a76d874af26d88213614541420d1f823428fb4ed4c21058eb233bb130395aa0a29c26d5ed576a2a1abe751b9d43fa3dfb7ea79d38fd8fe266ca716b7d3b39ddb55c5dce426f3c22eec56d9cecd663b2f4c55b771abdbb9a5b69beddc50b04f2da39e5ac60d6549fe177661a18703247ae3a3af0ff4b809609b8ef28c04d8a6a3f6e6449bb555b1686d36a0266c0535692d2a5b0df6444b7a636facdd72ce51ce7307e83c80d05aaf740b9571d75a7b13daec725aa4d0de4265428fdca43dd5721b3d338f8c043934b5a49725e57864e7b99b6c5f456e3bd800fb1e9d6d3b188367b390443f5922693e4216ab392edb930628c93f032a6dd223509c225bf6800504a1325013c6a161e0e0e7b8e79efbba5db507ce02b637622a279bb8573363c32d14a605b7ac1045932db464e2a425c998f6c45f8c311c78d3948056b8f695aa6a4d65fc5e8e033dd1cfddc736d8158a2a83ed6665a23a412d8c067bd29fb51eab2726537bba80159d2a1586abb3aaaaaa8a8a4422914ae5a15e5cac8408c9c9114265424dd384f4a09de82a0e20087dc2a653ac78b22f38032d6d9a684bf27741479396e4a61ec80b5092675b45545fbb6f456d67bc6d993661a8a3452171f22452dad5a6c649234839a033b449abdc7d010c5035dcd070f06d522cdb4fb771d23de5249222f554af1015b1ab0aaa0ff5aa5494844ed583c60b70b2c2baee6b9d614ca3f0a414c7d54a53273f1d317ddfc35fce3953197a02804bbe6fe6aeeb4c980511c3ed6d18e311f4d22d4f34c5a1277a1b412f6d9676c94df72975a92312d18c08f4018d9f7f46ac6d5cfe406b631466db7ea030fe2944a1753e0f4a5281a83f9cb1eb7b4559b161031aa0a3a951b1896480ca743a0c5000f8fe1b7cef41eaf278acb5568711740db516b18dfbbde0e7752e9c1885d191398942b13a2cffef5e7815d721439bffff78db70ce1a0a0f569280e1e40b2c31396b2d12fd97ec6a9bf4e5af4b8373d65a24fa27c91291524c3d82a4885859aa6d90512a69b3e670855e513dbd42721c07d68ab74b0e74eeb6a721d1c2aff94a3126a84057999329b8a9497ec70f3b54a0cdaad2426baf430bade995c62187baf36882c0deb0edfcdeafb0840e81dd9fc7517e70d4c1c93c76633dbdcedb267d79b57a09cd7d850125f9f3e0010000e8d0016e935ed1deacaa92102a43376703079c8c71e3e421692485d11e30f20065e062e046adb29084ac29e30af66cdbb6791e887b94a854211ee6f06d7e9aa67d0350c11cd1d8ba987871914aadf40b95714f79f57b7192b3a5d46690d217d0365f464e7409db7cd9a69f7eba8dcc4a95f9b98329596c2721fb0f9ea03f429077367154a676e7f7b58b2aa55e11abacd153758afc668502bf8be67d2b6e6f5eccbd46b1ec5a6fb0d40b2e07dff91b6065a05ed40ed8d134e2648f23fb1b88a770fec61137e2c6511e5b9e835da2dfd0d40eb4495db782201f50cfdb86f1d675f5a39050b41c6bcc8090b7ad04400a2f9b586375d0022b9a288b6c53881145b97d166b5880a22c52db28b747010b7589d6e612e0d0d4c9928d43bbd050182f4355b4d9f2623d376849b5040186ac973d81a3b39ce4af3deaacd965e202412f0aa84c67841c1116da0650885ea093a0f912b1fd2d1bf5545752a8fd0195c154a6fe0eb60842109da1c5b05d581b7b06819e3c1fbee972bc114a65f8a66779bd6054460c2ab3bdff2d82960495b9277fcd414f3eb363d4938ba0684e5e4c44446820e84ce321a3a1d9a85708db7d664f3567ad45a2fbb2a7ba5abd56afd5cb59f6547372b6ba2b0ac5e19a7fb5d59c6cefb328adb6566b64c4d29ac6c244e46ca995266005b7eca52da1ad3af1e5b3993d77c0034a461a75343f99d7755e8771f714a4f17c6213f03b51634344e5327c8f6853833d612028c95f6b83927c6584178892d044601e5073a08680d2a468532ac95f5b8292fcd1108db1832b4453e43cb1fd6b0ece36afce7d85da7863de6bc4210885002dc38e13b532280c7e2ff4aa9759bcf4864db03d69665052fd3844405803d4a8a0305a159554df594049f5c1106da1651185e8c9ae30d8f5beeeab25e5b94beac5c5cccccccccccccc0cb5e00bde68064ff89ebd335535035a4a2da8c5ac33b7838bc807adc5f790bfe3d42691fa3214e08e772452c7d103a30f275f2801a1cdd2c889d6b1cd990a82b08806a030feb40063c40ce1e1062d8822832653e880fe6007702c59c3060ed715578e0b278b38b459555b09fabd495d9e42c2896e735e213be28cd96c36bb51d50f1f8f2afa518563a5e078df8e260934f6f47a7ddfbd1f08ba5cfe0ac7c042efc52921bf087bdf077eded77dddd77de0e78d4dc04ec9e7b5c03b3327e2c61dedef08c8f75e2cc41731333acf136bb93dd15b9dcbfc9d37f3f35398227cc7ddcb2951fd6337ac68292da5a5b49496eab6a9b13c6a5570a2474a2fa5f7daab8213ad891acba6b4950fda6e0e1f00457af00cd63e27699f0a4fb4de428535b4a9a5546e32bb2d947c122de5a69b6e0beae415b393ee935084be6f764decdb5a810a6d6a292db53da5a5b24eb8ad05b1d8d2a6c8b7a6699aa63d7ee1c065c5799ea73ddd7bdef39ee7e9f7c8bde789a232dff61a73e3e43d535081488894016284dc5c81831cdc98f3bca95a6b5572cf7bba5605f03c2f2e36bd9e08be584b57e2a41669c989b4bcb582ff89dd73defb502ab9ff3c21a80bfe57f46ebc4ffde479f3db38f88743b873c89aa6bd4290b71deb0a79fba7e2deb796e68d7bcfebecf5bacf8513fbcf44b8aabbe4f35a14e61bbdd5f2688bcaf8e76a5a0b4608ddc2acfb590a2ab75aad1cd35bd65a267f556b783c2b6a9f2b71dbffa5e5561d3b6f4e9a77fb9b83efd1e6520057cc939f4de9b7f19a66bbcf6d7c209715dbdf5bcd9c1bde729e3967662c7e8bb9d79e87103a3ff737e78dd69df5eb3689b59fc1172c97b7915a92595755654926d4196c7f1a7b1e41dbf33c41aeeb1b0fa8dfeb59a2497e8e4a153f08430e1808e18bb213429322aa280308296b94fe346bdcbd4b745a5f38f162c6c5c5c5c5c5c5c5c58a0e6dd3c5a58439749d43868f1cdabeaeeb3d1d375a7bfc399f2d2a1d245404dd62459b1a4c83d913d65a0c6b30bc82d1e0d39280d0f84d7cbaa149de2423d1f8bdbb39df9cbb9710219aeb4d727f23104bc2288caa2004b4b27b2fe5a6246ecabfc63649bbbe49c227ca9eb8d17c52f475b316a370b5265e8d55094aaabfc4ae8f4f7c7a6f78d4f370303a1af209fd67d4de48f9f95a6bf54a9bda534be8037afab90b1f6653026c6af6d8a68b052559a0a6eb72524361d3bf5850d346497f0e567f686358ed187230b19620d1875c823e24d6781471cccad0db80bee543e2bdadcbba4e9c5c5d308aedff49b1fdbd4b83227e441584f289593d5067bb03f6efb7885709ea823f48b4f1e14b4a5b302f0cb6bf0fa5925216ccdb64fb6f378a76a7a0a4ff121e7ea25f9eece65c57f664f6c4c138984cc6c1381807f3ed9eccd2ead62e9179324fe626dbfdb64d4f963d99b64d4f86bbaf035fa0bf814cd89df8ed2cd2294b76b60a4f68bbbd973f2b7a61ad32999b4eb0021a0d09e9f888a3219f96ffd899ddb1241de268c88ed7f13b463b1be5d7f13bbe451c0d21e15b9e84d1ee8ca3fc3bc67b634924cfe30c5e821e345e1de34d695468d393ed5c973773934c832f6490e8e575d99397939373bc9ceb12fde3680ffb5e4ea84387d6610ea8e5c3176fd9d9b2cb14a665a49634e360a69f9f7dd232b4bd1639f3db401c82d00d015abe27e250c50fd44c2275b6b52fadcd1d17829fe862907b71ee7551181373d7c571a359c79b735dd765eb174c6ced3d5e170addb2b365170ac10d7567e76077b639770884c1b9f17ea0f28ad97e1fd14b6b4b99cce326ec407b2c0a7b3389ec694bf957e846db374b3125181b6dd988c3ff5ffc7d743a94d0f8fda9a5d494bf86907077ef3a777b6dd8bdf679f77aeeddeb390903cd80c062b1582cd64ae4d68a4a3197654f22114bc412b1442c114bc4e2b2e8e911b878e19435454229382575178a9054b492289f6c229188e5a61695a93432f04dfacadc0602813a90a8811e1c6bc0078d23fcb4ac01fd085f8d96a00d021a6da867122a53043d695fb0e88c45672c3a63d1194bf3e2655dac6c71b6ffc662bd686a7dd5577d5d8c354d0341160be4b94707206badb5d6da5289245dfabaacab5eecca2e9b75028c50a206654d1102a8008e12fc2234e78c1394611ead2b0788b52e97cba5c3b92996dff60a6aa5b0605fd6a5f2549e6afbeecb7f6d8eeae2fbfdfd3e7f90efe66ac355b929af48ceda5099da033d696ab868cc45632e1a73c5b64bd3a18175e5d75e188738f8000ed00cd8ad06ebb22f0bb32e9b03040495b85c2708e511800046308145112cf8c1143908a37437c8d8218a2fd26041a62559cc8cf36a9ad644df3763684c4c4c0c1ad9071a5a2412514ba9a51cd7e56ec32ba83d70129fb77ddef6799f77efbd1709ac6db012444a62e1debb6dee39e7d699f39773ce2f5d8666063dfd095a19f484ed5a1927686550193f81c2f88b5de8aa83d5f97d1f57da5b94162a6ca7f9e7fd847958a2a845bfa9efce72a2a241088bc5c3c9501bfaadd7755ee7759dd76db8736e0665cc801002855c2097f775dac579e33016b712d087dea4302d5cdc35537bb8f77df7da25efba84051a132a635302ff1170dc4e0844030c36ca605f1404f5408320f8e007043f20b6c1281004416f4bd0db9366c54915bc824593e97433f5877606ed9163d3d8f54d04c05ebbbef6ef084f90488922a54a973349fed76a4f5ee62c837d424417595e6cb23d052ba0c7f349fd22b64a1b49cef289f6d788d883ae77db31e4231f103274def6debb713f884422d1b52227a55b6b1511a108a150a94492ffee34c4d0702fd8cec301ed181a58ce99e3a6ac5c1e572ad5d02ca8108306654d11179a6095e017f914e1086518a3c44df786ca68355549fe358b52375553ac1c77c11facd5e524fe2ee7a69ed074d7f1f367c780f74608dab49ed7dd4e86edb90fb2e5a0ed8e7b23f86b755167cddc34c3970423b4379a550b2fbea0271cc54db16e8a7553ac9b62dd544da536b86517a4a196c4715fff820fa8adea0580227553e00cb5aca92488a0a623a8697bd790f017ec54ce9b0db36ddb6eeaa642af100e4e489d10c4e7e379795e1576af73f9b561f9654fb8f4c83c2f0fcc33dbeef545615a7ed0b60530b4e969797fcf03a3329ae78a13676cb7b25de511c3b3b3412d80615bc45a7a60f644c4a4af17f8e1a71e378dea8f2fa61f1c4dd1379a7f3d2f8fadaf577de5df3ea7f6f0dacaa89cca3759955799b5ed9cdb9fc69eb6284ef2c7c95a682f44555e87dcac94b62b2d168571b2da54300ab363495ec5a0f5555b9fb1be3caf0afb7c3e9fd1f38239b5e979bd5eaf94c70a591076b87658b1adea59830e292d89b69df6542a91b18f89b6d39e59b8674216ba060ea4a0a46ab19655a45bac3b85261ab59db58abe6a2aed8a9b049044d7bda14c4755a15e6cd35174e352b5ee10a1038ab6b9aced5f738d983d29a32c12aa42555277e1bee904bac2380c33a1409bdbb99daeed486363f336fa53182257d8b6a136246eaa3b4e7a6d55d2a66ab9b4711bb7d38ef7841a68f3c278ac40d3ed1947f643eff9cf38b263a299a8a46d562641ea10890000000400b314002028100c85c2e180583c269a77353e14800c738a48745a3695c7b32447621c8490318438430080800080cc08cd38000a8b4525eaeb47e8f032129f8262d28602a1b1d6e8d68c5d56aa8de1a39cedd5a79988ee64d8591aeeb0cdc4147ef14ec4a68dc9f8af72a004680f2a2e8aab5aa6772736adee8506497ae966b6703627ad85ce7f5a8d575d1ef8acc33176e7d5d762b2c778f9cc43e03b42fcf6253df08b319c5ab8529b57b97960968d772b173a6b49209c12bcc94e42907b624e295d2fe93941c8e5569d022731f74a106cd57229713a763c73b59ec2da2408aa214b329eb0e0764fa1cba1b1dcee5cbfa06c412c25a273db4ce972030676995368a210d27ba79d6f2aee3258d47d99755c8394cab454a2682da31f2bad7510c11f134b8099612b549918fbcdc4a71173a4e517dc533325580d18722626c6725d7be107e15d9405a711339fdd60d15cb55e41aca131423a6fa7620085ac586feadb908960c50cb6dea0f178a29f9a22013ed6627262d719a941b105f5cd43637efb08d23686c0639b806afb5f80c8cca9d2b397097035f25281c0b50716159c50224a8c775d309db3c63242876474e86ee73f8151048b8221761c8b54558b0476d1622964aa86c5d058b30b23840f86882392a5f06090f8810de482604350746cf22c824d257f7141dcff94190503c01a38156f61067388eb28674a68952da4f1c1c8b33da925a8069010d80acef82baa0e085fb20d53ac17dc03b8c09cf01b56920b477518b687184159a4fa423e22cb393b0ac0d288ab5c55d357bb5c6a30fe2aa37678d0970b3b08548c6aa74efce3f24a4bc42ca64051e96b1e78a6f9972d6dc65deb11e06bc84725333cb3ca717bca6c04f4e2a68be6e07bbd77314b4772d5754c81f0aaa26da5f332b9b2595a590b2275e7c2c004396b2780bc58420545bd1932375ab5f6ae6ea45999e4268e05d55287e1e6c5d00163fc6ebfc3350712979aeeadac5a50f9decfaa4c8df88929bab2001c12cb378c567dbe4b672be54f3bbedc20f357208ca534e8b1fec52debdec085c6d84fbf907b5a26ea2eb6a7108e819547024048dd545e65d7eba73d7d4b00fe30018adb4ea75b48a5585fb1f22953a07955aa709f2d8ff1468c9463a2ff80d728ffcd76cfce577cd4f8c4462eee1ae8e7c2dcdd59ec90014c1c215a3c2d51e91aebb2ae0957fb88dfa2a000804339df7f744d2952bb5d7ed40f9c005cfeb14c3787e9449c8555eb86203de32f12dac1a2d4ce29a026a54ba9d866e484c4257adbd79ddd21c2a9c161e793b40cccb6db3d2fdcbbdefe337ea26a26e4ef6cacde8d63183c12dba0114d64b3b0135f453732aa7d6f2e3aa21e3cb7a69358c0f319f4ed2c6dc0cca99c8fa8b6ad9d8820cffeb4abf0ce824606618c2aaeaa7649b8552501a219718c62bbf41613134659b022f09306eb0cae28dba061dbd09b753828e1166f52c9b27ce060b06a9901116fd9e647c46bbcbdc5787b833397885914a9d10145297f5633cbaf87026f92b664afa5127a191d2c2d2d091a71677801ac6aa301f5c7567c096aa597f1312e83ffc18be44ae3ec0cac0c7e133f4f3eef9f5df07970713222c945c7d43153f130eba49a79d90456b975b66f98d6158bacfc0c8edd3e6383400d2e1eb669104e5552945c93136d4f04cfc9289dba0ee1d949e4a2d49f1950015be8dda18338780c24ce4bd3ebda1c1155630385a454df2ad695bf6301ea14132b10629d911acd42775fff6368a1a0e685207b116a91524e6514b272498d5f43a195b932772d1c02983d2688199ef76de83da4fca5828699f5ce373f983ee57b841c3458ffa7d65fe6b64f2bd2542c8594aebc80f8bc853d0f487f27b48c2b8b8f07b4c081c1920478febf4523a8bfeace61e3ea8e66747e2f82048405702dafa3354dcc7f359bfd8b73cca41c4eb7447880c5cb482456668cadd77c90c8af77fbb4c49f8eaaf354860b71b26bbb67f0918798dae66afcd34d60bfb707b65261251a339e6dea829d1bfbfc86289b0057f59680f5231ac7487d1d9f105892fd4c3ad05ce99541f4f43d859b66c05dbd2288a743a325c072160087eda2419bccc43d97d7484861e95dc03f952a686c086202b8be0db8bb3d706fbeb323e995007bc1eed01e09639f978afd1072e19db8e930e0cb07a2a13e08d4d9e702400cb2b10cf0dc83acec01de2feabab5a0c23725a8785807d28a8dce74a8886f20961c6df241c090787f0af50289698aec7e850d6bc22f7cdb1e045d89ad9f39413b5811a0519e2e165f5afd92b0f5932c6cba1c0cfc708b40ab4e3914c92fa8059db5081381a34e854b1220c14d80067ba7eda8f4264d1cd3efb92c7a020c513976c93fc7c7702f608a7580a9d6596098fd17acfccda529b7e2295ca9b8bed23ac751c588a59e2849ae69e5537ee30ff7a1643ac6f4972492d74da0c178cce36157110a57c065b2c03944fafdf3bbd9bb9e0ae8b0cdde4b1b664e2aca6f0c74049aaffd612cdcfd17121af82fb1151261e99e7bcfa171f4f3bdaaba77cc768055ce9e50f6aa94064a59fc25fbf1388316b9ecd6262281e8da9840cc4a5af8a5b70dc46710a0c4f291d8a157a06181ebc3ec9df61d9bf35600fdf34250f46e33e73924bff6b3c44eb0f47b4bd83133d9fbf8b90ae112e262a4c8cc45fc8179ede1b9f33edf9c8b4d96ade11839992fea9066f9012be3f744d624ce78a3c71a7922f0ad3af6f2369dd4fc5629d06b63a390dc3d11145a543e34fe6e659f58063d93e8b04c0de63b2782800e27825292d517ed9a08acda092c153fd395022b2de8261ef7cfa6818a780ca6543e24945a219741112806de9222052db63be6447fa345b339462ffe8296c5a8d084a0b4fd1ca6cfd3892b72d069d3ede803bb88265a4e360834efd52aa518608b7c51f019b4b04e440ea8d94e8ba7876a9e2041006a97fa51500c58bfa2cdd1e370f0e5dc49cd8870ac2685e61f99220630acd9626737d6f6ae6cccf7880d84f4886b24ac00123c707cc9077bbc4296247cded7fbf4dab0d217ad0a5ce12d5f47a90152090c93f810fc53585c6cf9059f236f5c21074e72e7543b444608a2308b7decf6f54e65f4a47561fff5c0efb58976e113bd7be8aef436871b1f2b8d99beb6b588c01b24218453a2e8eac59ce85d3ab9595c61f099cb2c819a0eecaaaf8785fc05d612a897533c02a6a4fa9a3ae34d313e8d26fefceeea4e52c6f66782d5c1f866e7b4a90bcd98eab6c077479dc789db3aa862077fad4151c72f9230db7bb32413296877940a84d759b45b6c510265b195a5f426da87707c26bfe47214e9faae25e8db70aefd1dfac0eba3d3420d3be62924fd4f00002182b41a2e031e4fe901c66dd93e3c253e03a7460384beae8129ca5c8ffba7e1a87ab48fa71c2bbd61cb1622984b518f72e3331245f9f178216cf77037cbbdd7f47255f2448993bfc03ef50a0d541d49a76ddff7b3591d6ede8f96f7b75cfddd72ffce7f0e3b109e84897c5c6e134dd00524af49e14fca82d32669ff0176c6537bd81ea4df573c1830e7c4861bbd28f41fd89e66d318a2b03dfaf8029989f0d7b0d6bd86fc05db1389eeb1bdec390c686f29be49f764d9a15ee3a2891621dfb03dd29cadbcd81e66638d21333c99a8cf801a8dd33a1a5b07c40811f9cc92edf173d61db2087bc449655bd8647b7c59d59d320b1684a12e02656b29998c97a6f0ea3162e9a001035a12a3f288dc4240fc5777b56af541a8ed969a76f792f089e8408d3d73c1d996c3555cd6fc5847139508f3188df179efbd139e4770dbb3687df87759301d3032b23a589b5d697f76696a4a43b5028491721621a18379b89e37db9fcb62ab4699073558730994c0b994601e664c2f7154028184956bbb09127b8b464c803205f2c4c6ada9095f0c31ffcb79d47cceab82bfa4c44d68ed5a987490a34fd5b94258e0885189b74d73b2a5830dc7e71c709a00f8ff09ae543db9704a735e303c5a50392bc6e1b3314a148a312a94e15f681dd10ded96f7a5310ccf66ddd510b8c19b1ae93ac00504f361fcfa478250ae28e5c7983a8ad38ac8203b28204db16f4078605603cb17d3213cd404f274f1c8fed346d851c6202349e2f496910429c96cafdae5c78655988ba004c2b052003de9ddf40e58419ec0f9674f3474de01c17d27f1c10d4972127f644e130b078961d782f9ba9ba416f1a24e557279a417767ac112a0e65d7b1bc9d38fd12113641bee88d1aa7931be1fb8e4929a69d5083fd347de14ed56064776e5e1dcfb5f44642d90a1bf7c0aa3df60a99ecb943a3d42bc23cf6341d7b695e4953027e47ccd3395b0e88049ae8f16c1506391a59b64f19c28d021be8488d3240992724b752711c45250f480b1b89abf0162904f8418cc439fd63692ccba73ec37ab8bc0427f867b628040afefd705ffc6cc30c17300c6c2bceacc2b972b83fa31ea5554abc238d416fbb7ff672c9aeba59ce04861a1756d4521521d41f4a2a3749a5414a6cefb19495947da748e23e921ddde5f477119297b09b000c3c5d24c72a6da932cb3ac93c64643174a7cb21eaa1a533c1cce791d62d3da741a0ba96c5451dd096bb7700e1ea99f79b335988bcd1ba88fc2b554e805596ea68c8806238370589ef991ee3bdee04697f10788899bd5a36322471433f5111f013bc5a83a056734ff81c6d26628a1e8ae9c4f03d7d2b35391e436d38ba639901f38bc2961d6029a6c1aafa628f8c3975ea647d856c60bf90030e02304158ef4f6da275379c474a0120932c9a1c3a49864c91f885224e7901eb565b877d9ec48bad0dbcab5c8d6c11491c8540fed4f2fa70c6d9f00a20a8a074814edb1cc7426f0edb4847c528ebc953bd2e4e06ca7c5fa9fb5ceef6f00632946d75c86cf4b89575244146e657087535a6c15cd9d1e5d86cc2561f0d021f982d8a74472c3a2ba4949934e4243eef53cfbf3bc2314261f63a94b091f21bd20ba9e2a020e92f63a7a58f5bdfc8835c64dd6d8715714a6561cd884ec6d1dd1aedf81fe819380126456899dcd8578c8a1b5ff7813c8a48ecf4266135caefd255ce95c3abeb59c6e6c5508169646b09747521ce8f24fdff328ffc68f9b56db1c7b818b87a3e3b40ca50a09eebc136f391ba85c939f507079f8da52381494e1f978090560254447ab5408ede163597fa208c4b1fbbc73b14e920b2ecc7df6c11f959948a7b280feacaee5fe7ee8b93e9019b1c875a1effa6a29212a62151b36d924b6b3a923691d55f26903f3f6488470bbeeaa87f5aa928cf355ac116f9de752ac3e28744624fbe2b814faae3460319023465b33016379d63b957196655816ba21775e9ce088d7b589a2ea1f1005180e7ceed609a744a34fa820eee916fbf16573a997d8ca2b2bbec6990296f1ba036e3fa53bff763f31a7d4910f20b8f0b558e78092693193198a9d3d49ca4106d834a3c24a05320254b87ed1e85d3cfd025efd948c4c1a9ff210ff21b83efd8af846afdb8aac168084a05571aa9c156a19cf7005988642c7bd7bcec260fb75ca8be00c41bcbcc4185a928a85e06422b61ef8d153f8783fcfa23112244e1008771dea0198e3b03ad939f4cb0b5c56a85ec8e29a0b0ee1cdbd29e0b35271e7a122a01f840fde62f8fc6a5080b15e8ef6ba2786643bdd99fb8be2878fc35251b017613047b1d2273b0473a453897ac52f964ae8e693485cff825be993c662649851429a982f33f680a9dadd48ca2783e21b10a7ae67b284868702ed75dd1da50c3a6dbb0714bcfd83d3a3e752b100c031701ef0e1e78f2bb24fbebf344676ccbfd4eb2a8b74080f29157cb831c01a498b476dbf2c7d9087da7d2556ed5e9357628271e30b30517a3caf77d6f4da3b2189b05c24dd8704e13f1f84077c42f8de3f446eeb12b469971882ec92da2e386f4512c37b74cd822e6138abc261c809306bc684e7867cbb2ca8cec0706efa1ccef811e3ee5c19c6881336d2c6af1d42611b456d4d5af83d725f03869200b36094cc249e39eae82a30c09df4021a7ac5054100dd6a39940294d54c822f0f6784087bc528d9d89a6f8bbd68cba51a22ace54cb860ac15275bd86e5e3bb83e3c01abd20d0e9bf89534e2925aa5efad3710e320f9f8ba700b133d3b091cb86c34cd80b1b2a2a0088f1153a28d9368d4aa870110667f20fcd04d9871ee0b0ba68676f326b8b656ebf834a0949848a73acff723a2aaf086525dbc21fb9c0ac018d5edda965d332c7e7ab1d6da50541e2095402a99dc94aa321605a79b9a7f56f67557aeac4fe89ddf102d91f9120dcea59565975c32668439f166b66f99a5b137f949198678cb3a8066d0726774793a8d5d336b45e919f91739e7ab66cf802b43e638ccb6c29e0055f2c4b5c04cc82a97b8b9fc7e0abeb53517708ff009803e7f6c3e0f441e68d20afd2d93c198db9c09a9d9ba7302727517bceaa43ad32d24eb0214dfa099ae751bee13f2cedb893382938aaad6529f0d882e672ca8b967d233a1a6964c24720aefe6d4e3e11bc8020bb044e66e45e2adbc0189400558230df19ea983d3557de422e467918a698142439bccc9945a358640c807eec9d7c118c57231e35050c304c6ef74fdfea1b4de5f0ffb11c6f0cdc106acb374d9ce23a3e8d87518d3e51c8f22d1f72fe8d3c6098fbb151cd8dee0816990a57d3eee4b31e93ae7de71ed91d5223ac2cada623454c369fdad3eb87c1a3cf1db7f78a5c18170a656f4042ea5ddb1292366bbfe86d4e55e03ce6bdad59bcab0e6eb346be8315d2829780488ba7b396ab87c0344de50657fb2d01966c32dbf2864f22ded43aab6b75907e7fec0102211e2207f5e1a2abb9aa7eb07d9e73cb173fdc7414c47ebe1bb8f43e88f849b72a18c8f008eeb6fd286dbc56a462a630e3bb91e686151579a8359c55287b68cc6e5c3c388f8f8719db4c38890df3022b444dbecd96930860463357cb1704db5a96df5685b5d06775edd7080fe92dafc0aefdd464244f051d5884433133025ecd16cc49f5f2f15ba3f21df7c0d45df5cd6cb6a957c58f84a29dc7b16e62bffae958d511e39dc13885e5c0c76a92f2e7a943520372495bdbfb4444c380fb0270f879f61cf754d0a0e8405ac72055c8e8683c49c60fd36207e0b7bb262c831f2e678e21c623ec7e0997dcba3826e6f9183521c8a9ecf925fc52a3502e49667ca7e26f883ca22f080f889a5160db9dbef2000f381992f43ebc6a78646ba416f5669c98fb55c4089b586e34c8005ab2fb8dea7006133e5bebe8ab6db1dd23d2cf61f7a61bf0491e3d7dd3a38b7435c2ead79eda483d2e5b63e0e6ecef724f3f18e2b25dd31d1b3348592ac4763e00a628feddef4898004af2f93fd622ac1c2f0b01c6a638d50a391920d7122c42aeb290256b0241fd94755e151cb65af5002041626c2bf205d83ac52e1e1b435de33a63185ac57e151f5e5bb38edeb53cc9f931e7f0a003e49b8f04072b41d365c7f7d4732a332331f6031da484c8053f6f665c133fb7295769a39795508e0f11ea0baa2b7a60a959443c9b3c9a2283ed90a8251b79ee2cd836b7b7ee3740662cd13c0c25c560d0a28693bd8de717f06be8fa333640eac6c438265f3a02f2452945236fac092693b645526c354faf4359dc7a478dd5fd3d27f327e57fb2efae7aff4f83896f3c1261d812e9ae7349794b5835caae21baf0bb5cd53b40a0d2f2da4222467e2e31438c2aed97277ecec529fbb487b5da5d106eecab0ab856d29336b2c1fb1d4b48003cb245c1548c129be12b4a71b689c7a315c9c32a10ff5e6679d0a5b8018dde18b217e72f0475b5135a11062352e8842bbd5d12e0ec66176c7f4ffcc8c6e6c10551a1e1c9d3d3d2d3b3ca7a30cdb0b876da1cf2c924f17a60b6328bbfbf2a543c04046871f87aadc0f540190a9a42f117d4124087812267ea6abe3217a9dc9490ca1c957fdbce3a7c01ed1810db43e00d779c26046e0b43f816045d4d4aef0a8852b22d1323d8022114ae89d0ae51ef461af8aa304b79d56aa45cd77ef72dfbe000886caa001288e4930899d89ec9f83cc88a6869e7e7ca220f19af792ea7104b8690e8c2aea7cf8c860cd2f5acce672e1e208889dc172975cc575a9d3724fc2d92075bbf6337ef14be43ca648f7523da2544a4861776fcd0c4aa715786aaae79c467bd97785080fc817fcc586c7a27482973d54014a93ef3da843470fb0347bc9e8cdede180545ea9671a393006529736a24bad61446e8ff9a1474b65476c04f5947ae94a8d43cf861fbcaa894f9d3ec51f95d6151a1044bc1f8aeb090c593d0b7e57399d4956ac977b019fe077d291b78806d3d4a67df8b7fcdbcf25a4b040b56cff6c65e6ff4acf4cb081e78863ac0573218bff8625b3b4fef5e714fc8e6e7b7700153130e27c6a6a3581744c5de88538bb408b3ebaa2974913d2d4278475b394248179b0ac78831d90ec3792b2139de4d69b3844bb61e3103268ff6e308591d1ee5eeb122e672993a1f3c3d18a639c831b33a3d17ec804bac91589df0b545fde721e04261120ff8e9953845635d38fc6cda562d36dca09f412be04c337f792fd5dc2fbdc90d5e03af88fab2cc325598d4bb80e1099d8be1a507478935268f51b2be2fc8cfea130a72d0cdceacb7db274587d9070606e896fde04263180caa01f4cdcdaf5c5303c8d06f5eb6d525588aff6d8a39acb135f59b79ea8e1c5946e190a5382e4c544dbe066881b5e57cf225be4b9e182f87c34b34b88c535ad483e981f8dfaf98dcd3ddfcd33e20d7c4f8cd78c6b9bf7fb5efb663c88b6a8e25767407eeb56c5859c284de0c67e507022e2f6688c7886bc0649772bdcda1a2953f396d57528c78015ed3c246408947b348ea49ccb8326be6380a8395fa7220ad1f24c9dbcc1708e3d4c3b7ef36c6eff074feec4eb45317adcd0f94db203820aab3402d7bbab4001741e2419d87166b7308016bb05e968c783822e549ea739c0d8bdec0629e8a6180ec50514326b8e1c53872cf034ad11f9136d474c4a231218b88f7fd1ca29a3942558ca859b53b311ba1f60f36ddf0607042d8115819de7509889c85a55636b2e93742b4d6f35e9b58e5ade70ff195c8c86ba4110127627567ca80fdd452a54e2b3ba809e929e9aeadbc1c5cce766fd48b7a04f70b36417e80baf317a1906698975506155d484bca9d2b5b168006fdf53e4fc18f4ba4b073045b25765311a90b84d35d0fd6e7a744937ff7b5d661e754faaa261b98d85811bde5c1c5ee4d3147d6e66b6af4a3074e87c2408c6bfdd89c78a7181832bb5da0762814b2e93a35382692bcd7118d625c955f57a604e20857ae03f3e3fbf3a9b214c825528cf743240023c89820ba7d67bd5d8a44f86bf06d522deccb3bcdcde75ff915bfb5f669436f5c47365b99fd24a106fb1ba6ac4ff6541b1ae0a0f30d22699eaaaacdadb534986b87bf6180502d61b469d6f45b4c93789782e062e0b10528a92874e5915c657955f28d21274cc5e7ee840e75532743d6f2aadd27b795f0e3d22055a53460aa358e8a340389f56c68f5418c147d5ea8612dbe89cbf88a85ab66a88cdc750bc6b4daefc30114e594bb5781ad23aec4bb0c23edef2a7235498ae6dc2434361a1a791cee111c161808b40bba571b791716719b15bdc449373d6f4733221efc0f45dd7f733534146d98c00f5fa1264ec69a349212ec5e37d7fdf4097af98b7b5726b8515f9c466efad9df2a060904e00db2c8975abdcedcc4c3da9014795753615d79b62de1534b448cbd0b98af27d043e619f103b4c22769526d9af2b70e29514eb1eb9a13300961f7bbf7be07e50b0a16d5eeccb7b9dfc5601d8917e63513e07e18b278a1673c07c819a04b886177cb73f9fa76b15eec6de26e8b383044c5dc257972ec1f03a4943d54d2968da7ac10aba300c6fd7c87541152b5fc365abbe4521cb69766639ffa131090f2e1716f65bdfdc1a2cabb38d0df3739f5f34454638562d3a4352173806f055449aa426b4435bdc28dc62ddb0a7da28948944b1ea2f0ada05d94e92f5d868a290a416c397c82ed5315133e764b7cffc5693322630a854a3fac7816150e7993f49b0ad81f27be568f043e2a11c54f04b1194775e8a31d148368f19375ed564ca3e8b5b33e8860ff30bf12d4b575fdfd2cadb0e2acb096ecf50a41814bb0967eb0fe2905c3ffab6223a2aeee537d7339fea1db1ee5e45ff5d2c4f5bcadbe0a0ed5e58da223c5335cb6a113f62c72f60b5503d0a56a418d7e24a6fec4befee67c9ab6364e16808a7dc679d2b862a5c26e86c74482ca18f89c6458355a649bb3a90886e3b0ab79b354c13a2a51684de8559433dff6e53a234d9e0095827eb10049a8125a477cf8fc0c1070eb1491bf3bf8b787881c104bc19030975afca95e9567475498c003c6c09efde7128aeb2985567c9ccc52bccc6ed26425e704deec189ab1185fd52a125dda2f7475a584cfc57186544107715d27fb89b69514a218230fb5a87fd8747538cb0fd4cfc92682d90247bba4faed5ec872c90ef1f8cbaa950047b8b6d53a3e762fad4d75564d464ef0a2e684e43d63e7fdc0309431b6881771fcc032b30438c92a41fe4112be8923ec9080b270fd93ec0483e805137fa5f849111eae65f94d60f68cfc89cf006cd2033213747d0739641229063a4e9cb01ec47b88492f3469f66e11de40da0c2045bc6ff952e4ebfc130a186ec5b539f4e33a00ea4819ca5a9ae2027ec7a734e3ae9823a541750ee7d909a4c48ceff62844f12798c621478bd353fe04ac48a013acf20eeea8bd1b3c3f15ef59ba7563a5150c553652c7bd1e06571f72d8f9639cb1c6465052709aeb8213966dffe83d5fb4e4b978ed8b4d1b3aedb82c1e9db89cf2e8bf8092da318d85d97a7c58270b20fc4a2de71f9982501fc48599db76a52eb92935e392bfdfad1355e018647bb4b41a527a74c52e1895feb704069b61210697806f32fecdef802205a50a79601c9e8232dff0052d2d61ceb6775de81aaaaf015e04ced6ed1c97e8b1ae53cd716835c9d1f424a8dc6e03268f8b026e472b561ad500d02cd0cedc571eaa3ef1f040ff4becc0a554ed464aa283566d6293925303723fbe7b266799f9a2139f6a5f86a39368f3c2e126ed1d19ceee3c9c2f27dca5215a3a5758b5a5417241030f1537502aa426651adfe8d4f00f6867865e41c4c39367e4a242c2a254030bb03ead777eddbbd393472fe972e736761e8f8ae2b891a239359fca35b83302555289aca9a6b59c2544a3dfc896c98438464d7c123e503d50dd13b952badc182a59d085ec2af9c257dd8cbe8725aeab26a0712716989c5fd4ee5aeadc606a192085f263ae14f50bbb83c5c1d5689bf812f2c986e011085016f8ad71cae6a5c57efdbb47398b8a14207da32606979e898e1c96f6fc593760491e4a091019bf576b7ed07bd411ec641e4d3caf259bee758d144644f3e6e9a943da71402e3c6176f5ff7cc0b02974a84d9cbcc645009461f49c35424f115fa125d5e29403ce9be9441dd37343f05c6d8a8f56cbe59fcaedb96f3306e97491fa69291acdd5a80d016220010566a1ac1b59da677af3cbf33362ef681d259694614f842efc15065e6a50d93aaba93c1fb3eaa406c27132327707790070cf8a1405cedd80d5178c325f2f9061f4bd9dc501e42a1f43d92f1ff1d41eebcc18f26885e288a035fb0d8ebebaf32127caa264499f0bd22ba565033068d8654fb0030b28949dd0181be9e05ee28a9efe351ac06ddd854e0fd4a3c63730f99bb234f0a721004a9c443512fbcac416aeb704a96c70648bc54da1ea91e6022255b9a848979e27582844bc96fefc239e1ff72869ccf0adc1979e2797f82a495df90c078b49048fd73af50409f1c94657f36280b6dde3dbf442dcfbaed2749ccf82a624c4bc450405d6fff7e747e1fcffdac69cc3630bb258e9ec3a61574fef4128cfc848c11c7f852221cf91b344736890722d5ac2dd591861c020e2e951ae71b91538a6283e43cf0b0eeff658b4a7c8954b8436dabefdcd2cb6758aa485cc7eb9fb941e6fcb0a6273f23a4ea95b355c0ab035ff5e54230bd35ddbe9d6e074637ce90535871219a8c21062c6fe8827527011c02275794042e7dbc1f31d3e2494877c5f48f2aa6e42044d3272307519ead9acc7957ba030d2da91229d80dbfef18c77eddbe133e4b4fa54883df2167ca20d67d87951be187d96a8979f39d6f8e3b4c82ba1afb3dd308b4a8f75c5944732992908879bb84d98a108058903989b73cdf8ffdfcaad7ef2231cdfb5e46f7f53491c5373339fbd48bff4459f51545b70d1f4f486b5894bb9b8255fe609e2473009d70de4fa3276acb8fa28234bd3ec00e886127d3c9973ae11435d30e1b0bb0451eeca81a28f41333b84614e980af898533249b9aa75534129dc92c6736d5159e9ebfba6e5202e9b15315ddf09822eb4f229fec984d6b4b17aebdd31e789256fe4c7e1d8ad664f390879ba17501f0152863763df641544c7c74c09950c89ac08a02e837f28d69e7b147822f500a3b5d3e0cddc673e039f8754577e62336943aaf49380316923fbd7880837fa5a7fd3a2a41f83f2658e0998061dfca634ac3eef0e839a64df2e5fff05c41b1c43e8d022a5c142e48352e65a3d919d50cde624446043a89d714e2b1b6b472a423e9505e9de0edaa82e002c800c93b618b7fb91be4e9e95839855f0deb34e863f2e3772bcb01b96dbab0609955db9c1bfd9773b891cb154fa338f55316d7c1e4b358f071dc0123678930234fd289c8bcb4b97e2ffa53c1427388ac297025258b756aa53a103ab1dce7344c649c421b90c24693c06f97f224cca6a7007ad7a7241a4f242b766561f05b7112e31cc7b085e35db0135d40a13f1666ab114dd7cf807963c5d0cb3dcda3701208e6c0b4c309fabedc28b458e45d9329e678bfc8910c98b78a699a6a60dcb024743afc1737c8c0742cb9d737ea1a8ac4ba95d972a97bc855f6c95316f77c1be3bff036a50e1408ffc2cff7fc172aac49e13c9cc3e62cdd50330eaad62e72ca63fdd0bdf04956c9762f70b6c4ebe09cb4a036fce190c93844778e0232dde4ba061e97ad60e5d9d48cf14a936a42f5d90a82f44c80e451a60766691034a781d4d86fa0e90501a40c0065a1e793379f876d2575051da6da9c3fafd551309fdaf2bb851ecba63ae0b774c4b0c5079b7be7ae46fd7eae4b3e53a9534aeeb14da95c7b7ed54bc2715d811b3547952a41641c17788c54519519b86a86ab1c8ac8081822b75bdfdc6e99075e33b7e0b799faf2b0a6798324f2c202f69f40862593b66a6eaf6a46c2b9b879b509b4398f12e156bdcf138b2666c5cebd973f21249e5684c974489ce803d56b592babf0f46d031e1ff65ea02b33f10b035959aea39c588774a28743f8410deaab34e799e34b1d380ef43e221ab40513a246d9ef060a2c8a8ee733147f9291288e2a507a03a2767416d37e992ac8730d315b005609ca90395d11a44edf0bfe378001a6932655c7b0cedf47b166f456d462ad46523df05346a098a617c5519fd93ce8a02ac7aee9d2318f72793623d579f6d9f4542e6ff84e674f0c78d1192a784fef960161b7442ffcfb523a7acbb5ee5e9023bddcc8be8d7ac308a96516892cfef267be4fe8675deb5f48e7b0f25cb048088d61cbe7cc2bee32dce5deff28b1bbc52ad481bd4f07314853dad43f6090f53d9f5fcdb355f3a3093c51909cd617aa4558ea67cd2a59e83861892a6b7ddf0ca24cd9f3280ce56d65e509fdb25d7d7892cc423131f18e9f42e79b09d026eb946e34411020a129f3e51836f9dccdf9f15b21636cfb620f2b5422944d24fb589d498377c528b05e88d1f17922c69ff54e3dd1ae8787ddd5fa01fd52d8fcb3827bd529092c58c2c7ed0c28bc56fee8a7e97856dcc65d25ecef8e4881574b5dea26040cfb2490ec1990068882ad608c935c2e1e94bb481830af925c4f99a74880a8762a125f9285be843c4ebf84999513cfe0d9c7491c741e0069f613cfa10a23cf8db69e66f8ae46aee89795cd8b566f4073b2b37f52bca4579e00da3b42d03c71ebb0462e4756608e18d1d38c1d42e67e490c07532b9923f55d33ed87dbc2513421915d5907f18ddef17671c140ddf11a596791ccbd74c96bbd03f9ed597434f728274ef06bed38129e176b4a9825b9bb5c03e14f5176922f58ef8f84a3a6b575ade0a3b67a9de098f9a41bb93b3d4a1cb9088c81a7c811e8f409550bf329e418b5d5cfaa2ff001347e839b3e0bb9ea30c687999e36f3b7cc54e6ec2de021e78a1833d1cfb46619817a5ebe5483058adceb47ecb89d2a30a98fdd4ba5dc47ce290ac3335ee4371f3858a16c02653463c6642ea7752f9cbee4dcd4d1a6381cc957c3b10c5bc53e4506b8e474befd02e514830ef8c4b1975f64c413f514406a98f42d991505e12b567b4401f5d944d434fcc0d1cbe3c53a8d958214667c6104619d330ee8e1c1a8c10379f120d2cb1cab07e060e0361a48c66f5a794c141a3ca92c9afb255eb1d4e301047892fabcb8c60e051f3c240bbde9965bb9fb360078126581e0a9e98d77e5090421bea07b82477a22ba6b30eec3917ea049636e1149eb2223679a5a8a2dbfd7aced3064609b46b37b0db25898b361738f44886c486ec392feb5c6263c32344f6e6e008c3212cb99ea88bb703850f04e02870c00129291e84a37812cd8c0f0311a03b6e2d67230632b7a0db409a20b187c1212b7af95e6f6cc2df0bb64c4e2f8460400ebdd6e461d22c08cadd35378402355a291a8a55c1ab9e67deb50c541ea32348b626f7b29dd89390140b324e39b5d61891326c871688f51456eb677c86736a40e75a442cccd9b70033f224dd43c6162cd217960e350a43cc443f53221e50b4d3e6e5db1326da4f2b9a9e5ef89240fe3b9a1fe2d71249c5ed36bfa5746c4a3677c725533dff358363305ebc45c89682f68bb4ebb38765cb80dcad025daa8073c5a8232e50998de9a87b24b8d0e297bcde9625b3b023cc098b8ac3d7c30505272e1ee9c06db6864dc804dec00b39ea1b65c48dbb76ebcfe64d8be30be11eb5c67d25bddf07fced703ee5fa60f7568b2972b8172d629fb2bbc50d323156e9483f2f55cb2af2965a6403221d45c4c7452c372f437e046069a309593b3ed9de10ccce36ab6bd33d019b77e5d17c9f632bf4390a4db51f043c62ab0cae15bdd7ccf345cc8c6cc4ad4c225a02fe35be338bc8332460909cfbeb0f731d6ca873d04765f51ee11c0f1c6d09d1d166aa1312bd900ae4e58a9acc81f8c3618d1c73e6299887fd1753589f23e2ad6b987ab1e85d0cb5bf9fad4c71efc6f2056552beef494cee7cc8a8391bf35087e1f19a7a318c27508836f4ed57cc144e5ce7c5e1765bc0b5734dbe5b25ea3003308b863df36349445cc07b6d955d0b03db123b597183d8e429c463c2191af5974c2a327a383d59324df32502c0ae674eb489bb0f85ffa27cd03c51d0771f0292662a78c37ba782571c37dede63935bd87357d29a23f72882180f2765cadf7e650ef9ee7462ecf427e5de3578304406c846ea1450a9449e9017a5c2f8881a487fb9f50a741b092c88400d3f909a026b70920bd253d54bd6a13ddb6725c9c0a49c9d0c0eabd2a3bae4d48726dd5f78d2d4f8da4ae343f976a20c57690612579a84bdf244c70ece9605a050869ef88250e96e0d1b651447ed0b05ace5311aa4d9ea16e6051cce3910448aaa176c28b323bc84aeb7ee12d4103ed260713a13d6fe4f099d5d7c75fc8edd33989844f826f0c96cbcbd908cfcf4e50afbb4dc0ffa8965a87bd41ac97bd3370194e871f7c25e61d1cbadbfafc6ea9af8993af84bb11bb07c8c53a904661d759b00b6c2c35e42301a8c9e7266fb6bed8e449d6cf9e96809f7ee0a1d1248a37f7dc5f5a1c22f0292ba9cb5813618d2233ba60edcde041aa5740e9143177e7a80b294f3fb8ce2365b1fba46f7db5e0e0716f175a26834bbab9e65b081c932409fa31b7655adbc59a8761f23fd461b85298b597af60e55c8f8a74c5f6239460e65679959a46458f9a1d0eceafd9217a9059f8d02156621dad1675b5ca6a89334dd0eb00bba5a3a918ed0c99c83dc9eb28642a71406dbf1bd951ff0a5c68493997f705691318e361be28db8ef1760f4495573e046bfdacb177d8bba57080ce71fadbd56fa83a3548f48e369fbf0957f9cfeaefac252150146db31109b3bec386ce0ef85e923b6d0b65fe622b1fbe7888133665cd4f6f745b2d1e7937df6440117cc6bf4c92b59fb7fb186fa2bfe96e0bc08e02b8d553576d1e29740d81321af4ad1d19503cbac5e9a303239e0c96fa0e6d41bb94d86a88006f13a7abbb5b76aad09296c90fd33ca1814c9d50a76c4713656edf25f6a495167cb4e9f0289336cb6b696df2d78677f6f26c91010321efeb0b5e77e73bc6d449667f03a47bbc9b35ba8c190fb2f958f4305967999be859216354a1c47eefc73390c72921a708529514621c0b647381cb090b015b3a6953188a342bec16b22364e1724e4176dc95137ad43717b19cfd161e4b22d0842ae3239dd5232b24ded9fd0d1d84acdced0c7b41d5625552959611e5a930b637f0b1108a52ee1d920a87ddec2fc180d55adfe8146b5a22d9d60a2ec8a9de2fe8c1412e2cef3dd6602cf1da9ec5dd190ebff6efb0c2753bd6260191dde8b04cedb932ace8856c6ce1123da802f424b840953a420c89f71bc37f8f213a1b41f6570ba84ff344565667ab499bde75592c8f377934ff6df4a2c4d3685465475f5d19bbb0bf8c907c2b05a68b3aadf6722794b10844fc6d1a3d9398a04872fbbe9e8a9c2457bedf0793e8b9a6bd6210296676f48e289d88ee88b8a875692088bf8bbd5d20497c0dfdf3577bf93ed6b933293db90ceba7d59da45abcc97319ab5dd53e8d9355746edb5ef391678c900dd62eb77429144cf7ba788d81c98a476428b12273535689448dd8daa6589d04e196da8a60b931b8d2b61efe1a637d884a96ba67b716627fa09bafeae308b5aa700e4eb86a1aeb8b72f07c9035546aa6e5e6a7cd39a67c0a907d02459c2f2d3139f0d606cc41b77b52b51b09e375086a05a2b1fc92219c6d6f22d56193900d4524c96a15eb341b7a7a5f2a8c97a71761a62cdc8197c59a77743fad27e339aff9371dd02afa7497ccdaec03cb9dd2324f3195e0f5b7355d60731b4095efce2a854074b0b6136bcca0fbdd5d8d3afb86c34c402b73981be51f0a522ba9060032869228256e6689a70ded1644079dd165f6d43f516c3d58e20b91acea86af7ed4bc538c154f7875b2ecae03b9db0392119b2c5430d409239d4c130dfc4fa713b2a6ffc0cf33aca05767c062c67768428ee18206d1512f8fc5de4c426130defc0a3fd080be2caae2a9af6c471a0053a40d254abed0ba8c5d64397500a6b4aabc4ce534eea950c09f25ba4668e95f9760a0c3bb402ca5a7aa0b3ed10b7e62f97082722102652b3c2db324519b03c016fb6e6835d29ef813d1e1e19f855b72344b61afe04ed05ebfdc467bba2aadcb9d60cef71575e621fe984517acf85c480471fdd1b55a30be90248aa488c2785ea1ee7fe22a083e24ddff4c80d48341253600375f72f80e966001a3579fef16d33b667abe86547a59df6febfeb5566c7fb950a7db46abd26480c2fbcea41fe30e64a19ea97576684c9411acab0d11dea17be570630179aa4a510338f62d82d8d6d31b881c61fbda0e6ca8414a21b53f905618d47e63131cbf234e2a2a51fbfb3d7cc51baeaf81330a3b67ed6ff86fad2cf3cadb27aada5bfbb3bdb75092bc6eed0f31fea677f236d08319085c7e8d01d332b45c50eb8d8e64b0c3608277e86dc4abb5836025174081ad7c5b1f1b3ba352ff8d09a991defe931adb7b0d6063742c848c80f4bc91611094e268cc7649be95a223b6856edc05e30d3b8389930be65189a22d189191b338a24e4c11ac84a1b6304ed18c6953f8054c2b5e26dd0f68684d062b06ae7e1c5bd00c1b5a2b28066f187bfa0d1b7b2a42fb467d0047c4e889326a846f8e96333a22a6074afe04100ec5dd700c0df2128aa0e4bd7df719241d794a138f6d9cebb7cd959703451488e8e0cd07b02be9e000c4b484f9a29b0b18c7c79fdc6a9c3e0154ee9717344b7a00752f41cc80505d193dcb5d6a61b12b7dedddc3bb4eb7c0a79988f4ae33577da0ad11b8a33db4ad19516d2690beb015b3a0515e336ee552a800b605e0ea451ff382a57ee1c6964f93fe58bb9332b4423f48045cbf6cbd544408465177ea8068caab8014d4e531bbf19235c5a6d6d25c6371db453ca5a4816ed725dec9012b678d37be1b273ea55a5eec141fb4d0453d106a3e8d2767541ad1e6c697c3eb205571f37ceac7e1b90d399a8c9567091dc463dc232f74e752d71e1fe3c9ee22c084beec6c9c0141c07cbd3eef41da0e4981be88c6d230d8e380a450324e20bb052b8801a783a241e34494ecf2fc66a203c7ba4db2aaa15e708511975c9271f994f78e98c86c498548724eb21bd9e601c00a56ba45c51e0c4a554cf47a7c8a19440e07ab89706f9f5c0b8e0d30a4ab4100b80dc1d8e5c04615ed96d582c769a0e1b88c3ff3e7caac4e9d377489c24644e371d5176f6007085acab6f575710fff0dcf09bed0b0bc31546f073d56d102d3345ab7f89cc0f232ebd80a279ce3ef02cc94133d428a2cee016efb30fbafefe0552e7eda8e46f7f0f24c4e387e10ee0fa43d7818e870b600365596798013071bc4a5802e06aa87240ef6c19731edb252f6d812533f81322c08f13bca256ced7c968c6e63f7456a4b4879b642fe6fceb8bbbfde65e0c8358ed70c7ef5f3a3a540b34c75012e8eeedc4cc7b412d8f2424c1815fdf2386ba4013240845b08970ca06e0b61e2de1096375dba9b8e4f0ac8ecc199354fe37cd269d8dc038fb43b737c4712bc930b30505d05a7efc57efe2a3dbebb712eb66a5458413ac2714a7c0f261360bda749ac4a3f305332cb75961c3ee7f5c0cc0e99e9fd42c5470e5959fd9e287db6c95cd2ced1db06ac3de71b5899b7225c506cf17c51403a6652580143e63b08813a1e84ee9cbf590c69226589006528124980a50c4ec2362004d9aad05cfff3bade72004ff900ee1b49c0dd6bc52b90d08798274b0ec144df2d01dfc488772fd701a9ae9011d7fd7ad5f538358c1a2d452b473fcc8172023d3f8ba347c48f90ed2637231b6650dd77514ee031f0943a3c90add59907adcae2182dcbf2518d5f1bbd41818238a864cf9c70d0d63c3ddd86222cf410ac5fc9b3d3a3694ba032f649f8527ac823253c2193928788c21c31a6a3195bf1448eca1c54f7725c04670034117868aed0a87cc1a806803f15dc9b1b0be3a8f21672299b514bf559f8f282c27dda1d5498f3a39823f0ac8b9632bbf163944a4155701d1e2b61013999cac463c0faa74c2360ca9df26ec8a02b1a117bba02e41306c52cae6e90b5a561fe4d7d66c221c94442a2d3117f2a4d73994cb374348edc18d6d66b91b2415f3018175499ba6a2041102158964b5e529c103af0f6a189a7d169c92bbb08b4006c6d86593280df41d893030c1ade3f3511a55f996c00a1478030ea60593cb17bcb417b589d18776f49c660f1ee3cd94dffc2d4c3a8381a272cac82940091e5efb083e4b942b90c430d966cb0f05a746522b5827becf98b724b2b0fecaa72a042af4692ddc1a884b5ec80e1c3fd14c5f34929c98694e3ef3943552907d90e8c47913d89ab0914f9f2c2e5c059c410d40c3a30f170b969e1752bc5c2b6a8646014e45c85806a56c3c09c30f3386069917c69410009c957267f7f498e5cd88e4a5cbd4a2464ca3f8717f4c2db1ea3abc3609b6c4773ba65f0c4c7c77df42b4a35e66179b52fd97a13f5fa01c65f29a317820c6b183a9ec2558021bd7d4d8403ff8239b7791bee6e2c9051454d11fc2c6532cd973cfb0c60cc23c0f1801520aeed6bd94f109a72be6afc9225e656cedbc27492662503c71451c63fe71100eaddb63f4fe69be4e55b2dd661c1c1d7810e1d6a33ef60ca8ed8d10a0bce5ae0e685a6c216b962aac687815128ed6d353b511da6d722dc9bed419ccfeafea861248b46f83dd5fc6e4ffd5d24e1f99f0fdf19ab339d2bed9b9fde07929de34f096c3a8857e95fb14cacaed37b2aa986febf6d964769a3633b37cd661404576cf772639e1c5517ad0da147c5b83c371a1bb129344735feb8fee4a0e48e7246943551aeb21c85fc6237c255eb7928d3e1b282ce77898494bd62caff71c098d08112680fd2f5c41a6e455c541e2436d56f613221b2b1c023a7f88ea7ef335a5507e260b7a4ee1e1debd9f1fadb1fc194fc16360b4b808cd28aa53b5a78b314b53c051c0e9ad92ecdc8bf48ad8f61db13d27bf395c4809d7274b5fd987fada5ede07fa875f63729feb1168cb76caa687c2a3771bda6143af57da4dfe3205b205bd2dd04b977960d03117ae20ac2e6b93dd94a819c6ef19a41f257102bcbde036257d05ee450884f6929a0c8db55621d4513214a4d7f2c19dfec70cfa27fdfd7dbfffbafced8d8a61a25b1ff9314327bbd37afab976d3e7ed9cb908fee2c33f21d21166bf95f39afc7a7a6fbf30b04abcb8065d8f8f5f643d5e80cd0a581b6ff5b3e82e8d4c4b6f7d8a065f81eb23ae69078e4c46c40524c5c12fa1e0c5bb9c01b4806c3d9854898a5fa93067823a088d9813f4de91492826b557d2405f53fe4859963f76864131f3dca2af917215eac07cf82224eec45ff4e03ace03879952d921129521156459230d6d58222570e066aa62929dc3739da080d45be41694ae8522c5e043b365062e5efe3da39d8e0bdbed3108e533e629af25846989dfa0a8b5ae4cd012cdf1c2f8d56ec2bd52603370f146c7a591ff98cca777acb7ceb8d47e0c40645bed63b1f97bfbe7b86d35056b38fa00b0207d4f2849521c3879b7935ec887f5a3d23ecccdd721d95532a3e80f924e40ee36f98bce7f3fd052449cf81f29e6bc2990a5e9c38901d69a456ab45e4a0e0382e6d3ee83ba4188103a33b62cb014560a7be500b347143ba965b670ce60aab16aab3a319ca5e0278927355b224d315238e56b742c5e7b7194733663e298d0798f896d1ac7c15f53df53e47e374ebaa2be94aca9b81ea9b1034db84889310ffde8003f697244a518073d875fc77b4fdedb48cc3654e66f9ca48bc155a52d632d4c94c14b1383ed5a781db47818582e984ee3b725813d5bbc609ad217fe7493b1fc8a0090deb67d9b67da0ffffa6fc031399e2be29035eb721f9298004c4c05efcfc3d04b1f79abb23aaa05bcec24250d03c470c74e20923801e10215deeb93e0cc819c6a698cd04f92a84cb70561457681816ab64b8a0d1b94ee43ada447fa1fad3380565d40f553090be8e130c38530479435d5c21e71623a18fb1f6dfb603c1cee8942bc212c8f155622d4a513380ae526ace371c74bc639599c86e1fb69514056b548c2bfd01472be1b32f8d55145746137ec212cf35b5dd86b733185ed392020e408701db55e259fe459c6cfdb1785dcff245010cc8282b6a95ec61387df5ac4389aa06976aad34e80f46f84d359e5299dea960291d3d2cf5fa8ced9ff6d178d8a1fedd5b3855dcb3d7c76efc88ea2ab503af5cedaf3040ea8f230b97c14496f7fbe5a1336597b154d367ab063028b17ff52b020512e1b920f3041c72d8947006c914633f506f14cd231b60a24428844a420c3e92d233e63d98924b4933a31d253348c3be477d8cb1ddeb35a829e25444c86c5a086135b3e16b8bfb17c6404858f899f1ce1d067634b78e4e12160e8189788a0f9bdedcceb965ab52a88fdcd087634eef8893b910c48daa02b50e655aad6429d34536c6d843b9f43c972880f381916aac7331750558d1371d0978514519a25f5a7e3a9c203ef1ec3f75fcb45bf69ab30f06e6f250c1858769bc0c1c5b7888eeaa69e98a3c742f97d6ad90da4b96832fe3a1c764285fd755f0f73c465b6d489d62b26fd1516c2db65ef292128f85350b70857be437e1d9fc85501f19ddd1a3671a107244672575042f52c34fda088a779a641da7fa9c801a2948d2bcb7f6dabc88063e9f34792c227b6c405403464df0b706091599c20dde2bdbc730c745aad03409486c9df099da4cc11c3176e89f8b880d7fb22244f40e5c24c6763964bc399180b7b6d7c2e419f021fd4cea3930245b9f6773ecf78e647b102efa5c2a6220db6a66b823781a7e4d8f4f6dc56c0161fb3a579c1fce6d9bfd4ef4434ee6cdcd30a3ba097e468f5ac5a7f0102864e31aa8a98d1153c4f892fb39909b55510940c43dcc98828f2bd097caaa7b8f9cbcc70c88917041e64ee753a70b00909346764d88ae5932f9ef41af0a4a91af00ed60a61963c2503feb4b298e33680b7f752fd6b2989fcbb0fe4cd655e8e9a6043e1645cce28598d454d058f6b1719e434755ed730a1f5af865ccec639db24243b5bb0e0cc86338386519a7f2abe5b958004126f903b3cc3adfc18a0c5c326f4c9908d13fdc46e448b1f230aa889e9e54ef7cec56f271b9746b53c96b1ed2258a96db84958986c83ab80f25833113851abfbdd3aa4982f82069a010718dcb7e9c6a02432759e17bb76dd19ade07866e3fe7be251531357950ada085840391731bc7338c3340912cb441ee128c6eba3e2ec444f8499698692e626f5b3ebbaba3a123b8e065806ebc388153a5693fde82a823ba5a9ac802342cbc8c2a38ccfd64749750b0b4f560f4418b0234c8d63ed0ba121085311c0191f88b668f79f63b644323429e7f1b67112e52d1918452b04c41acf1a88973142dd24da4d767275c78551d5c4fdf07e8d10eac582f618b7a15b899cb02debd005df96ce5a379f895b3792b204b9d51dd34121cb0d410263aa3f86d8d0e470c6e9d473a75bad3dd8901d939781b72e7378094e1544572bdc50b7547cbed1b73b4ad10a5dcaa860501a74d5082e019bf78c8be41b8d3ec38dc8b23fb0b5870ff8c7e925e0ac8d10f8a782e5a7ce72fe99c2e2011df1365c51ec1bcad618cab67ccdc72ec6c81cd830a3f8856c7eac5a15be7ed1c1a41b2e89498a5f13a38d108c28c75ee5125670c39f2db485c8f168090e40872319437e09e423d7b2508b38506b5613f2112cff75c2d18e019f297189134853879051291dd96bdcaf29da9eb6791e55b05018e415e03d0cc3da1b83784cac16b75f69fe0d913c37ceea456710d668e23b616e3aea022f4d9359f4bd592ae83de73297116ef4f8f447af911f9ef201010a21a178c1fb956cf9e548b23d6d1e5e1f1482840d0bec8ecdec88f8342b483c566881395a5e8f357664e85a9766f6a1f92dae615d009f0b6bf92d1c584c8dbfbf08fc08db87f2bfdb7798bc2d1722b1e21701154b109f705175f245f52ac467de4d43f59309dd457582f0120f9d1b5c5e65b2cb6dc0e20a52bf9b043e9073c2030adaae3c64ff00ffb5ac16d2ea0332eab40ac9b19ffbe68b2fe816ec9bb6b0a89fb3f93364ee03354f8e6d1ae4acf9e0cc7b2e22d10a8243bae41d3fbcee19b4e7ca87f5a730b1d8f393e158b7cfb3ff6ca28ee70fb8ce0ffdb0d388c60fc9dcb1e8d8a96bf139231e95945f1d2bd94a498eed689894d9c293c88cc5f8044a16c0c7cb334624143beea6f74924eb8664c4bd49769a415564fc4c2cb3849604895099f12fdfea6156264e8fca1bd4fdb8ebb5de7bc990225cd7a269b9dd8ea299cb70abe2c93bc90b355b3c99b60a40364064ba4e6d9383e6780a07ad0a4ba123b01673ace8f4ab4dabdd85ec6caa2977e8087f626b59258edeba40937a00f43280acdb9fe28a2ae53361f41856a01628584e02cad10ac745fce64506398a7b4241035168c6d7918e4883c7a47c8b0f0ea48f0fe6ae1a74ebee8069516f40c66772952e2dee3807c07528b49544a5a0aac4166bbd48257d29b9c6151da45501e852bc0289946bf49b4bc0c5ff15092aa806096539133ed3e05fb763a1968334cb8d52de7de04d17b33c26147860822b033aefa7df99d18d9b1ad61d30b6d2dfd27eab4c20c5cde4c583320d562dc0c37e29a74d6dc2d2143829e5803adf99c410e0202eb38f82831339d6cbee8046c9ad0d4668ffc368f2f417c30db18e2507a937c014024eb930d82f74f8abe096fb84b2724abf59dd9632e4c6ce056790876120af347f03d6dc2ade35dd573e7e6f301e849990f80071abf5ce9e5d0a0d17338be01c581cf37cb758d6cf525b5dfe18382f48ea7fad37825abd0cbb61cef9374901945f48ac2087b13d6fd8b2702a528a3f64e69a9bb83abd9f162d1c2dabcfee73dab21668e8dde52bd2d376a6b8c8083db605d703814033722d3203cc4956250f1b09fd1f7f2dfd3b2627b2ddba7af8c61d6f3f71f7be3abd5e7082127ad300ef1750e382bd8ba25458671cf4314306420a7ff204aca42dffec34cb1e0d9c27cac6168d664ca651bbd68125ccbb19a86ab58c641130cbb967d33888959adc594fb00fb27d1044cb0b0a16477e36892223374ffe480697e50a47311e0888e0a5048b6b65e68788740d3168d7e5600400aceab072058e69f5d318d965cb2dbe84b24d660197ad2d94926dd5053a00908c655031876d5988dcc5b025c40390f2072bfe0d7ccc520290943b23e045c51a2a5201782d46901d1962f0829732b2978c5670d79d1280a7f5cee7756b45fbd7c55ab581f13902e6e2df1f9280b1eab4edbcf7e09ec2fe0476cad96437921374ff352554f8ec6a41f7d63548497cba38d4fdce8c25df524abe8c521203259eb51fc6b5b55356dc4115ebe09aa962fe78a699c005525cf33137b3d4409e85be537f67c13ef82697f1bfabfee76da875b47b3db89cce289cb791f6287f30807b54e4e83f642910d4f67aaa0b188069f7b6d781414ce6529a2fd7144fcb7e43769a7f2e67bb8f5ebea63ffb1413424c9c9e23971b177e6bc74e4260099611809a291aa797fbe2e34d125f5c1d51d934cf1d8f5f9e92bac338abc48181d8ff1983857b3ac2a50fe24d2997c0e096e8244e34a9692393d6d068650ab4ba3bd37a39f6b6c0c215f39353f82c70269202abec1a19164deeb2f03d4f0fa9d10abb01b8921d3e76f5c0a7d7e915fa3b9d8afad555ac0c94d7c7b4c094aa55cd1c633d869b5fdfe0f7cbb40d214d47c881865b4626088d2816fb9e838d60515dc88d09f299d83784735ca8338f1997eef4a0a0db1c081323080f6d3f99baa3564a56b2e701b64a0ee5bf8a7579eaf30a670a11fc79df565025608807f721282440179e282c21d5c40f05a3f89939655c25e387e179f7fcefe94c996c34c1ae5b4d435a6ad59000dfa508291a8a7b6a4e5032ecbd6a391ff5411ec93d09f0b6bedc40e97c4d53c16b682764e6a7aab058ae55cc9f5de1c1188e291abc7b61ce7246b758e4a8b00b0dc94ca1e6e685220b8202e00dc8bd42d995855a4d1a3c9a55e7ecc89aca9994c68a2650caf7fe19d0f50749879b95de07f15a3fd3b4f380f4be2abf5ce8f5e00aaaffbd312b770ac2c0a4445ea7e5bf509c8b70f9eb3778582c769866637c5e64cec57fa4444c06327415560e7356d86c4fba446f5c23b645d3671a5c65e27158c6cd8eaf23e7da6de8af49b0760b0c41540cd271d68c761861beb5f05a022a3e28cdedd6c7166d296b4c9eb224b5c67ad1ef5290f75467bcc62b72f51113b0d74f9ed7eb2270d476faeeb492352b1bafceed3a3820a82e1ab0b6863ab34401b5cc21c69fb8fcf70db0813356042be382b3c41328b0fc3eb23fb4051f9401b0452ffe439dcbecd929058d1e6bd5e74c86f50ac0cf1a6eddf3098fef1d09f95d755abe75e83595c11f18143db2fb265aa4681f4707027c671a04ead6c016cfef1a3a98841b6ff48f4de90383a2aa2b2bb077f95bb300dc08ac725c7b4bccf7045ab535f98884350920cf84ec6d54aaee5005aea38cdef9c268af504549695f9484e65c89931cb47e2dd54fd21f948f4da250ea66e6a45eddaa64b84586467dbb5b2b6dbcd7e5bbf8b30f0fbb308f6c62c5d7ab9672a00e4c84ef292087a83de48ad29048353ada8449e7e3e80b4ba7186737b8e8d379b13944a1ee80fc27147ab509d2e79a455bf7fe6132fae14cff4af994a53a8dc93608a24b608edb8a5561047cef37643486c1d4bdbc495ef58175013146f893cdf94b756446e0a7906aa09c8330a09853f71be04c8cde8f6e715673ad18492222aba28c65a10a4e862b8fc998d37663e9522eb07211d2ee7807ab8f4fd89cc182fa98b0c6fb87e35da6b6af8dc9899c32e9417d332473a85c526a790be20189c2760e76ab79a6bb199a088fb82406415eac19f9bd9cdce712d4eeb6f8e2b0cdf9b44b817672729b4edabbaff34bc020fd03579effd6225b88b6323e43b8b22978cdba79169bf008ba0140a16c3eab599ca5358a678821731053fc5b9d7c45a38bdc6959ae270dd8dc09f2d28299131cbb347d37f38dd3507eaad2d7e8079c4cc89120736f5181d16a1c9066735164db6044b6882de0ff4070486dbcc3f9fdc3ce70fef4551b9f2205036beb8b1dfe3a7031cd94fdda66417410269687303fff3e8ca12ce50ae31bbcfaac92f391c157953471d537a0fab81280c61290e4130184d8e612ee609ea477ecdd055b80c9136d9de5da9e04153b5a5c044969c9610e5ee84bd48f735994f1b51a7366a5d3a649337337f14ef5d6779887e4f4909531673ee038b479832c22b145100b14e8710364bb6a15b5e0f55ba3f13b70fc19b572ccd89328d8f0f7477c327cd4a8dbcb784929be789bf425f8afbc2b89b44982d71301f39151dc7101bde94b13526f439fcfae20709a3d49624e81cc16ed487921fb6ec0228473aae5af9bdf2a997da9d705bb57bb3483f50386123dc809c33b5b5589a5af02f6c53ae3b5f5deec43a1a5385fb9409f26a69f65cecee7c790810b6da4066c9da9a33ed40f1dba7951b1a6bff9cb6bf5bb19227bfb727022f65a0046366385f278c88b30efb514bb6c2d4064b5e7df8124a1de99d8d94951fa4781c377892c7e0cb596e2af1fec729f713e6171970373a0fe8b2a07cc15a95e70fbcfa8a924a05889709be1743dbc89d445997b71af710fef22f122141aaa5c4fc9cdd219f74bd6cab9ed95eee4e68afb19765c67235100fb59cb6faa7e71d1595158b3f06d166dab9167b519f7dcd18c75ff4b123308040a9e71bf128a55a4b6dadaac9bd1765a705e404c0bb25a7ad42df4488adedd9c2102264c9d1fb3e99e54428a89816b90deb51fc42aae52328dfbbb0a3a23232af757f3fa981a43d3de05d0c0d3b367e7476c422979cedb0948143e61327fbdae48baeeddf9817c440231ccf7f94b5113c783a9a6100e120ade1474b286b25233dccac40e96763493e6abc38fd1b199bf21ed191655fae7e881c5d01b96e1439dd2eed9eb44d78d908b4534750840d7e0552a775fd9d3cda3603b1e46e1b863c9c257b3b1cd6491390e5a2bbe9e488e18f83556d01f658e1f359c6849ea1a9b2c21bae829243392fa52e4c355c0d9065a978e49d521e93869efae57d11972c26195798a9f1e96abc7a87ca3482a78d9a9d328bb51ef9fb24004d2844904de246916810db876d3757baea0774be0905a13726235135195461a13af57b7b9ff6b2f85ce18aa9a37eb27a54a20039f931f7c3c4ca928119e5c21efbe0ace3d19216a8706e1d6d180ae169a3c24e4ed1d3fc2236e207aef5bf611cfc79910d482451a83b5c4c74c8ddc8cde3299c37d089ab0cd86ccaa4fa214d15102245b5e81e202044358dc06b2344b7bac05ba3588d14635353e59c6a7c4be1209b282405a62c0dbf36053a4883ad704f2db3ed103782dc4cddc0da565de13c490fa190ecfddf623854fe84e668e9fb757345b448b2db6eec6d337a0689005063dc89754c1c937f88fa3fd2db0f1836efabb297bb66b48bdfb74f59710ab34599e151632fd4d1ef68243c488c27c49b42ecf346f33b7d05f6d79633d3264add091a1ff9cf8e5313581b582353b5737730a2652ee49a83609052d4a36ca30a70f4cd457049dc13a677ec7ceb03fcc93d2a0cd480a247c5967d9b3a22be882ef0a2d2c5efb1b061a51e3850ed127c442bb93e3e9e4baafa2298a70049f41b668b93ff5e711dd21f7f4f9c24d6db84fdf29e6dd22822a3881f68c1dcb00da4b64433183997accf134a66b2c63b04e9e52cf576a8fe9109644d4cab14ed0d60812058503a09953b25773579355e17f03e7a5708f2e0b968c980f033cd3c828987468f84d8a09046a74fe3d6c8e5449837c47917b9d3419bed6de4290841aea7bbdfb70718e6131914e7e62e5ab065f78968c44a06162595321c1ae3692642ef7acb2030b52d6571a7d3f8f0399ac2f13cd4018ac9c780b93b1e8fd24493ed16948847c92c681e34a0bd34ed41baedcd00b35ccad2efe797bb1eb2df05fb3a8a8a746c4a6d38b3209407d1c0a19bed56d1d1d3b93bc8fcb88c6f4a3ecfa43dc7c6686ccac0a585531f42af400feb2542b989bdf276bf27f0efd69e5773385661e6046efeb0e671f581a994ca74a7053b003845990e4dafac24d31ced919b28184b247a4c17f89cf798433596b81b380c9d52fa5cade950909461672d1488a733bf64c1114c65914dd1d60b28ef9536dd78482b9837a31fab3e3a0543897c7632cdf5ba266c70a7859a314e2d76729b33705f9b63bd581531d493c8d9bfe63caa589d5953c1cfb197ef4f648bf228fe70111921330aed6abc7ce0955a0eed505735b3abf2361d3c18905c80a28e8ea256b65e0157d0e35431c312266739b56bf501192c73d54bf3979836562ff2c0d230882996a5b2bc037d4c0b13846b13f68a003a5968491275ac270e048f9de9c614fff66d3b27cbc7b373880cc8dfaaee65d0fcc4e0856460cbd316763e78cb5eb26c48d341d8e66b9cbfac30297690fd1602f61c6d887b0e0d047c0e6558a295dc6217d82d131c9cf100abc7ed6a5f773471e2b0e44886c533d2468d3636c0729745f463735a9674352c01d8610c34c905b1a9bc2377e65c0a7f7379e5559a5227849a82472dabd997aa1799ca10e60dc0a334ced2700e22688456171c92c83def194293ec651eb5ca7fe504b842155cc0a4c96a4701f694c2bf31913e61797058068cd89bfcd92b01c3247f9e6e14c855ac390be838dc13552b2d7ff664e457595cd5c375d0a951d1fbbd56c3aaab317e8b89e8acebec4a047b8e002bebbe514b3b35c99c105b75cef2a1f0cb7ec64327c5321ad8a27e65b116d8482a0f28efc41e0c1184cb24cae00548e4c2b390c8854ec0495665dbdb3165abb1c98ce30e018be0f70bab4efaa590070a17e3116873518eecf0e565fd2543f9c4f7602a038d4484323cac5e2c985b1d2fe0369b228d454b1e48c1402b93cd5276e20449d4930634edc43883aa5418af20cb0bc4c1a9a3021f9a160ca6f95ecb23c95e28ecc962e134a845c7c015d919d2f1be249cf35615943a64c0eb841da8c7bc97456fe125e5383988a3b1613aee4a0c0fecc379ed2ab36bec83d5bf506b42b764c263208c1d190e64cec73dcadcab74363d3e55594546d3c4ce80349e650559a95a4ac872a8ae1d7d4b941cc0bcd8e3911ed3c463aee6e08915914562274850e762d14e839c15a24d0f22584dd7065a471b09fc012a5f88db1a8fc64154fcf6f2c0a5a21e28d336fb94cd4430037fa515522e3ab880208ebf3c28ca5abf1111d674ad3acf741e18dd0155f7b6dd151f20389b6692881bd7f651d6db1e47a91a801e01af2a4bd7e607a3815e19f7d3913c3f13967a7a94f0d8d6771a42fa3927145192111969ea8eb0a08e5cf81acf09d3c748c85201c2423ad4ac5761a885b221d3dc3d3c268548d318012f5062e0c7f2fe9bba66416a6e8b54d89443b2d5b19d8da814a3c317594fe90bc32a90503447d97e2bcdc249b54ff24f3f9c22db59823146098e402ef5e25eee64d7bd7e1ea190515ec222cfa22686160ee37c052d79fb5db7a7c73ceca70ba7c57b9ff9df2f916cdb27326e8b4d830260e4143b3b2464f96aa5697c4c22ce78b685f7eedb6e35d5c61645f993891846192c855baf1f27a17d1654cab6f0cdf5a92c42e94717057d590aadcce33d70adce701b4a85a8855a9ea43d9ab13ac64ac0f02ac2be9d2454ef3e0c0b388a0505eb5bece0340f6b5f99462c20098315c4c4f0b359ce20142dfd0a8f52a4b28d6385f6408bec024fda35c3e6222c1c21119029e689b8b120eaf31cd5db975e214287b1c0d77d6b14d87dcccb2add383ba2ef9c9db10cd1c7e6205c424d183d06e8d0815e708ecee1a0a57a883a2f11c59b2fa46f07006b2eac16c0424cc7ed99259a3f02a49d217e1c2923a3dfdb49974964979427210f63cb5c187742201feec3c552e02f3ba753ac5657eace354f08725688e2cd6fe08a29f9cff35ce91d77b9a6569f4c57ba99178b90e78639e809fa9ffb8468e2dc8781b2ee9783e79b2e90e69fddb1d26041926d9b6bf701230477f00555c85febb3204aebf09154ae221683bdfeca112f270f15f8a446179591f61f37e0ae23b9f6cf0d80d9ae3b30755ffb73e384593215b874570f1bb53fe6fe5de38255ec08aa23aa025a91045e8f0dffc70498e7427882a1b268a2598cc5239322ca469c44008b39cff9dbfe04ea4c05f4071eb34d6acea10c0543f953dceb5ee6b3bd5fa71f4efa256859e3f414f6c98455e43a8ef8f5689096043acd19b0c32aae369fee88a203eaa869d72578a3b26a21c910234493ea5d1f8fa6e90bb374e7b85634201aae0451d462caa17009f1509a057ae60c772601f82ff5d3c02e54da05a93ec09a6b2889bc38f543503ac9e173212121dcf3484d0ad4e40b2d1a5ba290806aff3653e2ad27aa47aa3df12d5432915994349f69bcbb6a9d608f35bbba38fee8af081763bf22ca45d4130c32a8c80aea4405f43f4f59e03437fd2a8770a122ad00e0eb49e31500a71d507e98711475c02216e5e37076acdc216a98746e7ca6eb12d1f19e741124efa4aa501601e8811a94636b1313df1166cae179fd12fe69e62182f80b3c14dd0beb731589ed532341a1cb8a355931aa65ac9c3e13dc9e34111716f19c0e69d2867216d9841b5ead5e1fa742721d437c26cda8aef364e7295e4a9a6123424ee6527fe166a14aa8dca3c9c4f95b3998b3a49b99f66d50109f9b244e9762f2084f4d7bf24ecdef7e1f1b726a72376a3f4ef58c8e745683065e1d0bab5f401ef311b701ec01a40a031adbe9615d3052325207693177d5b624c9ace738100869c0fb925a8154d2415847acecd17903a6bf52e4750042c726cc718e0e2062568b8261b8000431c68c55373ee4daad9f7b800f7ed4ec2bf7c3609355473d1146c41742f6de7b4b29a59449cafe05f305270639a8340885be1cb9408efa1de6a1d0d3061da7ff7d356d40fe6c0dd0ffbe096b4b2a4d5637b6a4e233dbdea21d8fef29e8258cc159c4431ef99d20af98f98b40316f600667de7298b7bcd53295423f5f15dcc077eaac9049ac51c4f6229e9a2a17f3df1d12162d5831dc21ba106e59a5940b9029c7419efc7968c27dbc05f3425c5ebf909787f9d2a6526663082d04ca7c21fa5d5e08ccbf3c0d7d14f4bb3c0a30fff2f3a3a1e003f4bbfc0ffd2e3f41297bfe7c584acc4387e119107d71ceb2f63179eb89a43057744b859dc5dc58582fd4f12d4ad9b843e948473ae66dcbd85bde1a432ed31384a0a0209f1a445395ee9c99c29131a23dc109933645489b69c43cf9a4229b49c4cc946ca0c51974e3c3437762a65288800ca7dfc0add8ae65cf94646248b32664ce1812ff264514c9cb4b398269b144a2697106ed4993277f39693287d2f89aa201d280b4293361d2c67720233003141d8c1993c34c2e63f298c97f3c947968c5431f439efc47a0b8c180627e018334e838bcf3e04da68fbf66103b68266d1c260763489b221c09ac06186cd2640934de7f4a215333a5492bd16a5e2cd1006bc09aa7aa1a30c80baa9aaf090a9ae12226a8608932aa9973ce49452460f9711b6943751ccfbfb3829e3d9f3f481fa794967449843174f568c4a0c5525e0dd99352dc2a958e90499860a92bede05bf1a9b5d5e4db6b80f4b0ce971874ce94f6b844585cc1f223d45732d9f662032d4e99cca9ee2670b1c1d85b66add49bbf6503eddaaee99715912991363d5a9459382031a0017963b388712dd689c5dce3388f721ce5a4cddb23e48df92824643256e71e12188fc3a10b1072f154f5a1a72ecbcd96ed412c164041aa202effc2dc8115245041b1dfd1a8d828151666144392523f1acd6fe887ba8ee675b4afa36dde0e41de8ccdd5fc7d9bb7812190a1697e5343e6e8efe55f5e462f2490028d9ef42f2f1ff3421ad16adf8db450e8be5eb017ecbe3e98480413c144b04ff4e27d7da2cba6e39b97bd50f7dfeb3857f3766f94cfebeeebc2eeebbe6e14bbf9dc683496cc1814887a91e334489b94d7e03c9ef378d6da90f3d8f083edfd5f96b0bd9b209ef28b36f945110f0feb82dea23cb5569a81b00ef4cec98f923fa948801b5b26b19d8b9a1c3af286bffc1979034486feb28815096405bf991f273be9494ba50815bb8b616959db7e721cdbc7221ed38929a259b095048c319e3be7eee3dea3a6997fcb5f75481b7fcf76974eb04706757474cc58066243760059a27df8a7e0afe3df83524aef9c79db728f0bc86783680485eea93d94ba7b16b97f18678c33c6b86464c4f2ffffffffbe49d838303cd053357f7e3031e8698bac6aadf4e3412b0dd5061380f853405dcdd2134eec8964826e02d2a4933aa5b487d548da389e9bcbf71d76ab451ece37685f1f7af21a4de9510a0e03c9300d327565dfd7bf62e000d5f0aac4830c87fb9ee8b58579e9401718f0fa70a0185a89a1950b124c424fbfbb1ec537c4bd8e4f183607ba6c2013176aea76c6b7b6c5509036856af414020ac942317ae2f1a1a99012f2e4a1267ca4f09123121b26045093da2843b2fd03432b7a9a9d7b47843edb98d960428b482ccf33476344d388a2bccc80c91c496ac91cd84c6d460c00403562356435acf80d0b2de4ddbd7f8d5908a3cc018038828550850d5b14d5b8b2fd6b4ca9f103801dfc94ed3fa30879639d48d95a8ad8fe241c644a34c43cf91bc1c316254162915e222144af0124a1c5118cf59a8144dec08c33583227c35c3123c9f6d73265fbcfc4640e7e124be66c30bff9a48c62211313d17726263c5925eabad81649ff3d016a5a9ccf855e8776a610a80217dbde9f20ab888191cf03dde9b7a308cfd6aa24004500a1a5044253239afbb8efb0273289a2f8c9452f0fa3789e120d8a60a2d708266d569a348291449fc44d10915a304e0433c25c216f44302da2d70c3923c90c2472660a894562491b237a1bc144afbf18ac3312abc492363436066762d2867b85469963ed10db9f7b0d795997b471273ee2db2186bcb4b1609d8d308616492c126b3b894562d998b940e16eadb5d65a6badb5b6565babb5dbb66d395b6badcd39676badb536677cb72c3f8ee3b80f4c2114147a19457cf4d07196ed7886b1cbc4d2bf6bce36b6bd9aaec53db8273f7e0a6410e3d975e1d90dc2b3fbf3310ffd6bbd413485310bcf6bba5692e0191e710dcf30cdd6fc660b9ed91abee2788667d6e65c63b3990ccb7ae88bd5f4c939833e6849730d45f5e005cd5bde72ce79c31bde72ce79c3db26b72de76c6dce395bf9edc8afe33a743e3e340da487f55e4cf1f7d01dcfdb199368fadeef009939f22a476e09c81c1fc815b82ffbcf51ec90f2de7b71a514bf9d98524a29a6b6ab9fad4f4da14d29a5f4dea79b10d2851d2072bcae0ecacb0fda7efd5a4b25d4113a983bd06829f8d41468aa565aa58966d0a2a4956416dfb2c2913974023429692e84a1c552a954aab5a6e0f2813bfdd28f74a075400c4e3048109af9ef81414220cdfcecee9de9e26ecb79cb5bce5bee9494b20c3a29e93451fa939a686cf8a0e9cf9f94deeb5e9db3fac81a0aebe3b1acb56ea7bc11ebd3d8979b624b4a62021bec20032a98d04082f1c5e803145b58118413242c5858800297163c3c146822082b341d6a28830995e37c587fa83bfdfa97d27dbf821b53da938db058a49452a2e82ae71c0b513024d0f36bad3c644c676475def44c72d3792fc6b6f3bcd649e7a4f35a2c6995ec2a273ad3e7b6a462c4caa65275719db3ce59e7acf98f48593d8e27a694730420d49c006aa2899f52ad0664e59e47299d3470a05802f2e47fe5ca2569a1c5c7e00f3d830652a3a1f284165b528102884d3f1e19a4a13f69b0e7cf9e26bb61946851d680c899813d79a004902db4f8b424f1bd78ce5903b245f1f1b0562aa33355b69af0bdff8ffbffcf6d195ffba2278997e8235a26874077cd9c755a5c6bad359760022dca9aed3e8072006abe9a1b35d4877ef14057e98eabb43113119820cc273e383825fbc4da27ee3686d0f8f3e328a1c7e8881671688d310e8e748c29258d44a31baae3f0123c87e1ec8b9cff51ee212a0cc3102655f4c90b7b52c671dfbcbe0099e303efc92b3cf4e499a6ea0620aa0852bd20c74802883054a11a542fcc2354a117650c25e5072dcaf1c9cb9524690599aa907ed1a3f0bd07cafb9487ae3cec5ea4ab94c77efb76ccce4433f5bb88fe0514e2bde88790efb54abf54694d4f795cd9508116e90aa44556df7bbf022af2bdb783d29cb5f6189d6daf43c5e9a2b4dd7dde0972502255f3cb201d9d68d193d028574899397ca544741ffaedb073529887f495c47b5253e53f33859e3f1e26113d0905668b9e4efbc5891627d09432611efa4fd0e399401efef9a22f0adbc1007d8d51404aefdda8f7022433e546d1b79463321c59ad562b9aca7995577995b3ae9141d32de69c39849ba919c32aafb81a19b4ad91417b8d0c7a6e31c73ccfcb5e9639730e59e5551e418d3cf9d7e001d4ae0d6b0e19c0165aaca9bfdd1a9489013a53470925acaad062cdf6afa12182cd8b384a983313666a0e2941e1b2c44ace80262f207736566bc5c3597c2fbe5d776ff75dee72db6ab5857ee9208056f019ad4084c673d393e338d26884da416270d0dc8f8ed0a2049a337c45aa62402ecca009346ba56d2249a2082c5c2b9518ce201dd1a2044a85a181ece06278f406fea03d590f99d30179f22f955c5caaab0b5102bd0469b9800d747f6934a0c2f6dcd39cb0bdf71e28c2b0377b4227ebf69886084d1bc081b20049f2d6715cc76d1bc76d1ff729f1d1afd00fa37184c55aeb53adcb65b758ad7dd19a4df34b8a43206389556259608fb050b6ffc8099adfd4df68aa64a97065d0baec68a9a8956e96e6a187e1bfcb25f7065ad748077c5fb268d1ba682a5b57c9ba00b0ab8c8816adcbe5728d5b13742502b5e8c125fa47f4faa74a7c2f77cffde67ddfffbf95ae564f140a856a0d5998140f633f320fadccae3c2e238a6e35827971d1a2900b34350261727ed1daa554d2ff4141a62474a6a75a08f2101a906c19ffbcc1e1e4fcdef7795dd77df7e4e582e7dc0a0e20a5960818395151385bb246852ba8e0a15b93aa9ae60e1f0254754194163d7f8649063ddfa250a82c6ccc22b1f24c44bc1d3676a034da6ab55aad56cb5b396bfd7fbff396b7bce52d1a96c23044914005ac2654415e2081890aac50e97f219c72031c5450b81771b6931ea85e1805db9736d29364b66e243d0c0c284ed20b49e63d1289f4544592f9b67f1d8f3149d5e8c524765b9b58b05fc8e712d1752393548d6246a351cce8a96a341a9146a398a7aa18d2e8c567940079b17d7e120504a50525934152a677576b9d4cbcff613f53d625d383a1e78b25397e53648e1ef3fbf9645f4ce6783f56216d9c03c5cfcaf6ff64dfcf179b39be29f2867f319054681b83630d8ff4540bd568aa93d8d66ea8e6daa21dc7d48de2960a4b454dcd16433520d22603f2e46ffd01aeeac01d3cca9176128ff4e48fc731040a1a6bf85e7c2fa5f776e34802f2010e0e0e0ec5c1c1995bb43d1907c4c1c1b9257baf9d73562ca67071d921f48d3535b3a646e6d0a7518346103aefc83e4fb0f0efb30e74186544013a0f4df9f098c3a8bf324bb7e60b640caac89729da85271e72df6d5b7e3c5af070050fbddbd3c76ff0f8c453dee327ff2ae40f0a753a994c33332e2e40de7a814d29278c8a88d0e21cb7cc1cdc344dd133e3fb359546107aca79637b1d474d9f3ec7d115eac2677eadb556d408a8191e5c8cb3873d5fc6bd1763db79b59248ab4e87871eeb21d61200162af13729072de78dfa9d87f2c675de17023f14ca0bc3ee9f2b9542336b6dd6343308245c40aa09a56502984afc91142d8642339ad2e1a1d9bc6149704287ae8466215a141a7f68160a856837a8a3748b210c1b08045c95411c9add9c730643169c6d175528e59d046bb7e0db037ab7a0f7dcd1c34390011e8e9ac854f140992fc918128320222af93920843d41396fdc9b7368169a91c6ef08574d065b66b09af1e1e383a6729ecd5c32d95e6fe00f3ff9e723f4cc59e684baaebb0f98e5d9f6ed60c17e0b795f1286d09b0f1284d0e25cedd47ef878693082d0fb954e9a25206ff85f5314dbf7a40ffa50428baf80ffdf388e9b5c4f192f2f8f628e6043801bdbbfcb1eb47910e741b57a90932173241d22533b9486ed056bbbb459803cd1641361141e85cc91d34433bd1663801bf1bda048c19d39863cf90fc934202905fd0a2eec00c939db7a6f2b28e8e5e58da0bbb702992ea3a3444f5034db342788667bbe6886c53780131a86189600618c2858f440441720f8c1902a5a7c51a489162a989a174f64f16305184838913501230748acb89841e5ffff8fb10e500a6c9db981961bb43d1ed24ae79ca552ac3561bed033063deb159fd2aa542a95acb52514d8160e8d26a104fa53e3143b3c21810f230d06188801c492297cd0e28a142a7f148ebdd7a25062f4f0e941dd6949da03e0d870c864329963eb1f8f4c832fb6b29752b6006973a54dc807ba6644a98f6de17c6fbef9de7cf185c97a70394c19b1a0668840571b61a5a6528d0fb6cd676e31cc7675b7c94d2838d080a50ab281f885cf07d5f6307034aaa88270a0a4a703d8ed4610ae595a0dd770ed074d770a999eeaf740c1d2ac145d6c167cb3e05bb434ac45da34b145b4959accc12f716d33d9be7aa1453cbbe2ab15b6e226f0bc6a0375e8f4a05fc674b6d123a265f28723b827f327c8d987cea7ae3a0eac5c2d4d12a2e8104ab6a2068083d78a71758cb10b2ff9a188124564560090b30f8ae6075d6529b77aa66ff1937b1637c91c5ec515d2c65f0c61cb79cc4f3f6de5f4f922ca7b9186f7620d13bc973b44f323f41f13d14b9d1ef424c5f60ff1649508f4414fde9ba62a44f3c39305b954ac5851bd808d5041b9efe974208e8f403840c040903973038d596aee592e25428b3e6e1103614b2b3944d9d43d8be8351fa5cd148ddf6ef1b36c1a41e86aaded712919c7ec1225b1d1c2dcf469e6772f55a1ef1e859010d18b40219d88b33d4702e40ce374261a1fded3d0a0d0cd9587413cf42f12c231d1e4ff5eaa3c134d06691ed07de851f8defbc01e29d0938352f5e244fb8b389b6f2c10494a25a52924be58a2882c4a54e0854a0334e0c0e48a2f9e3c91c1bf9bdf8ebc7978244fec8828b60d0f77f8e0e1e111b30f1d7a7aa23dcb9e3225b14c810410141c60314209d5fc924c491d70b14a62072d56662d5091b660420921ca606141453597206309d0163aac7e604435bffebda00ed78239ef3522c3923982673e9b4d9c03eec13dbf6d1b49e650fc6e1a51e137409826731c3f0ab57150e6295d174dd1d0ad7b14b8f74021a010285748fe4daa28e8628416f10ccffce65af17089ce8fbf9b14c457f02c3f7e29dea0ebe2233341d05512d1433acf6f5ee8ec5cb684fe0bfdf75c6811b585f8a3424f33ff07cd045108819fb5b6561e26940bb4f81ffae83c10ec329e93e68df6b8a46c4f6c0ca16b6a027093318e49b72de38dab95e36ae57a5c526a6ae408c67167671c7fe5795e08b5710664ea817c3f93572cfdd6bd9bb77d14e9de7b0fecc0d72147f976d480df76e3b82de79c77b830b64933b7238010420fdaa2076d33d4799f94d7274d3a9073e137f029a2b8d7e26da3dbf6dcf6f4f3ba8d524a73deb82de76d0b6171eda044d39cb52e95fe293de2a3c37f7e3a1d728f9868717e2efb3c57fe14e8c989d84ef2ec61ae6032a1cd15abb5e2b1d6ea68815e3dcd8fa7a120d8fdd318c19224ae95aa03834011a99199aafb0d8c36930e14f2f4e42b6a64a6fadefb509b0b10a5a3d6fa1171ed40bf445fd2cf2394a1631a091f6fe579dea42981b65bf4be19cc1c72de9041e6f830a7915982816ec1f609c45ce91bb6ebb0fd4751e898afec1654c48062a5cad967685d3d1efecb16bd9a37feebadf55a6b75ac9ba14e5a27a594667d8dbcac5c823063b1582c16f3c9f93f0c51a8d116bfc6621e933127aa699fde7b572699f229d29538d9d8cc17fa825336a578e8311c9c982bf682b97c4e1031b1a50344b3fcb2eaa4c0af20b67fe7b3b2d582a33a0a4797c743bf366cffd14c195ab4612cb4313bb3341bb3b219830ec310bf5c70001800a106d50b9309263590b5e98396555849b6e8c92aac21db96558e046d59e5486dcba7ed6fcb2a47aafc9616880168cb2a318c3270065a561902dbe2c3a06595213ebb0a11d6965588acb46c5965050320ee5a04e48563fcc993bffc648d08bbae8c903953c4b4e69c739a6297e6def9f2940c6394948a1878642dcbeb4ab8c5f9c2c137e27ca242165bf40a9044f682d13d9f07dd33a439cb50d6644dd6646d2a8142fb4b050f81bc60356a555630d8aee56e1867bce1edf6b8b4888771dcd919474a69ceab5a79bc2ca14539fa0d0678644eaa8479f2fff112c612e8bcc55a018bef74f79d1b5e41c438d1b685b344d3afb44afbf22bc220e5c00ee1d9eb2c8cc26074aa808a625ffe0270629fd80ca93e2e3117247cf5cea3e716b5afb6fc982bd9aece59572b97d8125b24d1b242460d7ab22c91c2d481426fd2a6053ad393e76da394629dbcc4eaa083cc99ff03319e98610558585554d2891eac50a20452c08acc308974368ba4b5a0c9055ab43dbeabb5b5d6190d80bca03e967a54baf73a0e8344c73e11e8b1ce03fdc74f6ead9452d6ea811ef3f07e3c421ba55ecc43ffe942daf807200b2dce18bed34a173247ee9c3aff5d34b8866c8ea5e6af55885d41912b556babadd552ebe204c7076b2da594d65a5d3838d24847730e496fdeb8b921053e2a6b6dce261c334701e40d7f12a48d7ba82b5a943f285de512d66aad35a554ca8f0e12199abe0ab200c202592fea07a05429a5292b853cf9db2a6a15b43423a2a5ddcf826586b62863b1274cbe33761aa2db4681b6e4bcb9b5ba46cc4e4d4df4533a46107ab8a8aee427313b8cd7e78e4a28d582f22cde0836db081c37e347cfa77969fae208dbb970d5fc66cc32d2dac41863172782137d5db435572ed77585d7e63c8e738bdffc589339d46ef1b045533b1e4d7d9f5de21d293862960848b4dd027e22dc405fcc40b7464f435c9adf88d46fc43bbbb37be5baae92ebc2e2ce78467a1a434b95dde2e11d6750d1a2ddb2459041cfa735a092fed287359eed348875472bf474595ad342591e7aee759d54d117a1061d02290bdce23713b4218186e18e3f6891b6a8de27fcf3c5f2d045a0e175658bb4f6b252fce4ff43afb4a8c83223c88bdc3175ab00ba63132dca51033b37d78e5f70de08dbb6b9ee78c718d74c136b4ba539a70f70e0c08103070e1c38240d0e4fe2ef38644e48e6e030e3c19bd8f0a079c86db3022d4a19c6987e0e981fb3d2d424b3a7c9cb64e8ef04fb230f75573f9a0774e04461d4bff79449996c0a216b72678d3583b3a44d4d269b4a34b15d0a59ce39739bb4f16a10f323701472e93a4a7a21913e7b5e1763a2f15e867bd2c3a0eae676d92f2d65eabee4c95fe79c7fc4bc0c1844e6639e33fd188dbe9321c998687e945ee64b2005a2d95ee64b3ff3a49f0169509d0ea8648e97f5b45433120040100003150000180c088542c17050402698ce7b7714800b62723e74663c950764a1248a711805310c8348c600430030c410a39431aa0e20ebd607bd3235d551718c8aa92791d51b3be54e6dcab6564a94dce821a7fd53f80f076c7b64e10cf5019dd0de5573a176599c504a9dfeb5e62ca62e5f482ce307270933d5a21da101be627c56a7097015b953d3b49e8b140a4b2f20fce815ac2a70b1768bdcc4fc3e2853787c02fc7d0c86095c0e08877db08fe49f514f3aaec8fe9527b0b7fd8039db8c7adc4f075130ba2498ebc974c84c419a375a9fb8dfd9cf810c8dd635acfcba116e4e5737d3dd91b5e0ee93d7f1287e8d0199fee1f4bc3c0dc859b7ea4f7b40a118874aca4affb8b1082405d538f8f8e3118c9c4dc4a8a68564bb1445f0e877fd462829de885fb331b978d4011e56004a6fb6ff83f62814f70f77c5c04b50c5b54b7b2c5022962ab0aab4b2e6436bf7a2c0c250216fe5fa7f6bfb7bec15a934d021c11fe504aff1ca980e2c48208c5f8f88682879ecd26c3c9e28812444950071b1a334f32b9d4af095ed5c48f10131906dfeee4c30263a8ef9c803021367974c2ab3a7c89ac385d0a15182e7f1845ae7b05616251f6c4c1a2c32f2b032bacfaebb303a880e585badb46e0a5e8588211c46e4960a9569266f637fce04de89f95b574408d220c455718753177579f7e7531dd11a32a9f21879d99fb3e883f0c878108375246f11b79282210defef477cd7f68faf62b44f35623175db00766b193962464925bed1b3b671e386fddbf61eb64fcd921c57dfefb3ec213539f080fd8bcf2478841cb2b608035a07c6093b04e3d348330c6a2f6062f70c368621fbf78fc5257b00dc5463dfca2ac217f7708614cc68f671f6fa42cb4fe73057baa550cea9fd025ac4445c2430f623a487584376a99a181bb3b0c0225700a982f82eb30942e048f5c21a33511819add08b0fc284c89de26c4c813a293250888b8cb62f72f79049a85dbbc1136d83a7b26f10ed02d20045702de0228540c7af483db3a66b96b70462d93e94b59d6515444258d55a91b5b68145febf38d648e5388b16bef71fd0087d9f48091ef50cbef7c46cd3b3a275df9d29e52706b03bbf86ef3e88f734918bf5f6e4566bf81ec28fe4e8337f2ccc23b0a6d89052f4fff70f67aaa70d961f3f2e80f4b750160dabff91b840e6fcb0df681f77b37c1c32f8ceaadedde4481f85d3ccf80d54d137ad73935c232d5bef58b9d394d2bc874ec81925b0fdc000b2e1ada860988523eedddc00212c38281f60621662cc65aadaa735ce838b98e5181f5c391b54e3dbc2e562d24b498d428237469aba32eb847636938424a4404bb5b792f6eadfad80caa812710ade98b86019c7383966d8d3e47e2673d28c660aa09176c1acf454e2f38752b98907e3570709f4bb08058eeb56bfcc811f182e8dac7cda147e41330df2971509024ccbcf7f720505757436bca927bf17c5c4c4e89a34b0b052101fc96289fc1919ba1364f29aad3ff318caca9b79bd3137a602136b91d974f5546f728bd28ffbef288ea384ee50368d9e978a32eb651cb45f6504e21454ba75c548c65af592d8e19b1c44535000d52ba2929c344c70701c26410b44c84cb683f9c245cf8a2c58a110e83d2c8f4084d52216f58420b76b915851feab3772352e3aee768a20d199c1ecba30527810f1dc9a6fba7484646b4d553872bc34c39da78550f7d02a4f29b14ec12e52c1c5d147664bd990698cde7300b939ab862c663dee5d280d5b55f7b87912a992e3aa2548394a97a76104af37ab5f79348375d55c6773e3ae4b137b4ab6df723af12231b6f4c12635c4e7116e630be909de6fe7291d1db0c9583ea06275f5909af14200ea3c3d0e50b8b6646967355aeb2c01a491216bb99622d52c6ae1fdfc606043c0aeee245130df87ba135f8a82f3101e77cda6801c39d77325cdd9034ba5d01a5688461700d61631b2047c913539a92b7bcc243997d52c04baa49b70e84f2d85d9bda04879c441e995fef27eada4cfd8ab6dbb18fbb94869e55e621fe0b50134de830b11519c864d028492b785d75c0727373be8a8cc3722cbf4fe11f8a25b69dfdd82c42d9f80a2359ba2725aad1ce8e67805f8e5139efedcd78c3cd3150dcf1f0d9dc020b3a27367002f87569fff25521a7afff2594765716420856e1e4807fc9be1fdb6cb3ba666d1fc6c4240de60dbffe4cf342c6a2e289e0002c45d6abce4e44d01c0e2f621025ba97ec0505e4c9c876056f66254517d32d15f73955d69492ee1b19b893905add9dcc1bdfa72f114d3fe1931f812ef302199099f04dc4ae97b0298f8bb29b94ef8588e29f7dbc424305d1d9ba51e4bd1d3facd03a05d71d02576a463d86f71843bc517e8e4b820ec393ee44c51f5b17f4aa9116ac356180665945f84108fbf7aceeeff3c8ed342041c0995d06418015b83328c8d8b051edaed4d14d9750c02e44bc833ad6f2f307e7545fde814218c97ece6d6d2c6fcbd5a6d29f09db828cbd138128f655db813a3572de2663c6bfaccdcbb506a81899815e8563d2282a4468ec7f7a57d669b527f84bdd08902c60f390c31e671be7202ff5c808bba44f562e011d77bb83799e0c249501ecc54ee11e2897a96351ee994f54da41c51595d081d3f08ec53b962578d3fa355e722a0f233d1f383f9d6f227bc3d29623615fcc95c4d29f0e9aed8fc14dc7e00cc88fe4f63fec7e5d438ef18c77225dc1176f068bc9611a4cc703a1252e79a0b961f80cac8d000a99734c48ade9f4f82ac7b53047ee78da5f4f1a1828b7175dd7f27d7a71866143393c2f594b20ef443d7527378057de297279589a96e7b342e46c74de6a926092a1098cc022321e9f64a902800d66a1d50e19b333ab0df76f80acc828c3ca2a0f641449f959548c08c6aa81d6f4ff4fd28707a3cb8deeeccdb837b957dc9b4a6b73c7eb012ec1073d6220a5c9e829605d79f1e4046d8e58b08cbf2995198657a409202616a824c9d9c7dd900758bbd11e000e615ec3935d26b363b4eb814de83769c9acf8e5759eb75639532055315c73fdd47ccd241a5ccf3a712d350717b52b2a76869621e6f9532955c1e0a3b8ea44cd5a3ccc49b0ed7ce4e36381d92d113653664a86eab7ce2c19d97312dc5e88402f1a64699fde244d076b15512724e9d6e212b903225ba2ab1e06a2b9af1c9f623f4d86fe619ee03c893e3448311f6a601df9ebf3c90de6c663bdeda79e1093df0748162417d416645e20e4782844555e1e222af083478eca10809699b4c62e3226ff1ed8e642efd05af8df5884c34e63471898e2a04de251d2933e74734b9c47a66da294f3c92cb8b38d40367619a891ae8c0c07f1537815af3c0294238f0719f2d1f9eaf40d9e91f1b7122f645b0163826bfe31defc41d55412a3ed99753678560af0903cdf7c573c98caeb8d03d2425bb36b67295a6f359003bc635df091277d086fb123e4cc5da0037af61a95039c8ebc8ab653b3f3f5cb19b02984dc3549590971ffb09cf85a6e6e61144b550375cf74058325fe7492bc161b50932176f9f1aeab5c3c3763ccc2249e86ed3509e1c23ff5d1f1318ddd8e1f951a7acac710e9ae4bc2542833f376560c299a23d686bdc5d46809b1e3d161faeb1dd8170d1497cc4cd790ab63f1c77aa8af94dbc178133760726a7ec12d80ed42aecf0dcba234c878636ada9d910ad00992e98c7de892ef1d9a1a61afedcd4fe1a5ac6813fb86860040e41b809a4a6ac27a1b2375e1b1902a12a9f90396b26ad1c0602f1efb10ca28ef200d5a6cc6a5f1c46abd07a46fa32194ecef2af3ccc4e9c34b6cbd4b6b7caddc52632a40626f9e1eeaa4b6f3566dc60c28d0a51e7ef14d7b842584750a1336835345849845aebb13502bce76e90d255f007375cdcfef003fa4bff59757f89f9381c2d97518dbd440773f53b58d1ae6f08873cac8638a0f35c177cb124dcfa94d28d344dbdbe2d0134bc6da537db839f2220a9110575d734d2c3d2d76f9ee3fd7d13cee580c3164e1ddad4213a8bf746caa350ef31918a7c20a4906076e7221570591d1c5b40ac12d0a34792e61fb6c97c17074317d3b2f67cee3cecb69fa71fe87aca961c96359b00a6e8933cba4c0c42315ac6c857973a3cc79f7720ed68c0b19afcf5012b56b0bac5cfe6d197da2bf26fdfe385412cee4f1439c58da44dda452a757ee88b45aea1347e483864610efc352cd36baa6864f98a857470be1c09d7c8a217ea76aa0d92b351ac0d6c1bb5b22d38177e728543f8d170807b6ad0ebc9a8212f30460179d2fdbea56ba86647a48cf38c18feb5806959c8234611ef6e16b14b03af73659f6ccd02e29818f672d722090cfb944ef65891ca00fe0b2f4adff2ae227471ac727d139895c3622bf89a805a79f2a1ad6c83158ba0ca680d500116c80b32944b658c2c24d1903330e85c2e68a81235a99e83c34f075ddcbdbf262d89ea76dc642442ae62f639006fd86260121efa9628da89a04c9cd77955b259cea8041f5a73097151e82492d425d89362a7ee4a7d520ef2da71283df44faa5bbcd54198f5398f23c33ad461a484734f2910d6fbc1de9e3bc2d496970265770f46232b303abe4950e921789a8108ce67f260643046fee6e7429a9177b8adebc064ecd5cd46c2c3cfb7ad0e24b87da4566980b0ee4e828ba85bc6f7bd0870394a05b12f64d01ebbe3fd8b1e4100d60fb91eab520f829da82506e03d9b25164c910288541581e76c43e017f80163e55f39a6c60a32fbf8053862b5860706c9d6bbb426b34d3fbb9ffa64fe79a5f80da6b84dc707f9d525277e297913170237d5d42de2805b2c65f3a21c4a92b3341811eff7eb0c9f7321a4d5b21e6706c55c7e0407f4f103bcc37a34dc326a2027d6f7b5605765e138365310b00be8781a566aa93e3def246300502b9eb93c5dd9ccd572344c8bc8052d1c00cfa8de02b56e45fce2a10f8f39f12e302fdc93881d6fc766fc342551f737c9ac546ab6dbe9958040854531e165c153b9154d2a7ea827fc0fc33215de9af328069f891173d07173749a30aab5f31e2533a98248f0e0a1b99f5af29edb3eb70bd44186e5f53ea2d78a51fe611dd64e91eb17117439b8360d33f7f5005b8df42ed13097e1aad34adcb02f66c22d199018758cfd447eed3414bbdf68eaa96c0cd5520085a49e525160f98abf81d0edee5299f3dc6ac0a6d492bd9761ff7ba86145179eb869c80004f582d4df9d29ba41bd4d9b7dd3c7dbb098d6c5527faa3ce14621627da5f140b196876b65f6e1c6391866ef62dcdca14f10975bcec823bfa5e9c9c247efd2a22fa5a31b04f569e318b6ce64f7c134eb63fff8eaa4c153e30857c5973e49b21e3c883592c8afaecebd62cb85f065053e16d64c3cf97271bef7a9157038f4c669d7c7d794245f49b8adf82c4da7b21bda4ceeed1189561cf19b3ca6b9a3c60cd84d6560610eec72036120f7adc1b84986bb16a6003a265f9f25813c3af63fe0cc7cb48f29c6168b7e26a0b172a4235593e893879a90a8c567b5676569b414be3d8da9ec1c108228ad50779d0e233704a9e1135a40e563b280906726126b216ec3e8bda48a2b622eb69cbd01db3b742be3b49c05ec6d775a94eb5952488b08539773d44c7985bbc41eeedcef77c9b842a5e6eaa6c8175c8569926cb75b4a03502e82bcdadc387fe8c43d6884f3ead40d218fcaf4fb7bbfc8f5b8db261f327b521d365b6166eb0e2e55d0cdad3681f32a942c2e1bfc0d22e46f19c23783a2bcee008855c94cfa13fe543ccdf21cc0fe10450b20fb3ff079c1214ead10746c73fc1e10b345dd051348e3a2ca5c1507181b7a0c2db5e97d813bd6b6c3a178a6b0a51a9bdcd24b1772366c1f10544bd0da28c15f50bd6d09ce0171b72022cb8bb5af78a3c7e9b1b074135e1bb4e5e2b67fd0f01eb7500d0b9eb4486219e3033d66741319fe0bd4484e54f845a8155c7e8f1c87bda8cfa958c8d39ba4a4de062c3a17f4375c07801b682de69d8286acbc91ac943dd1edefa53466e6050dcbf50a1622de008614707086078b6d7d7927c798892fba63cedf29f27a9801e578433e1da7705295baae73673330d3af02d8bd37f678305bdfecb53747bf545b165165b92b42c5669c3ee3b4301e795c73722aa8f95c2012ec285d32ea34ee1e934fe76bd9a0112b56c36cd8d0a7a08485fa4cc8f6c2dbb495472150b5d154be1882fb25cc32e8adeb96a5c8a39098dab6aa613eb89d839afc9ad739ad6e92e897d5607445362288c2a1af27ef21b87debea04aac61c862ea3a7dfd6867d0669bf4d42b04363bb6313db7548e99f693cd9061e9fa55edbc96115148c57d3331819e2368c0eae571a4c0f82de830f6f60172ee9ffdd8b0b344a1693c5e8da02d755b581e43897598755aba64c48b49eb1984bdca17eaf94ee430236f636ae764811dc3aed210a782b20b3b91f4366d9eb648256c342a205c1e9ffad9e2d1e64cf7dbbfb1e76cf385c2c3fc095919979fb89317ab23d60da4f2468e141a0a1bff7de8a0e1571dcb401ca63a7c7b267c795942b6e8b38c88e37fe10f33c94cc5ac055df7ca5aaaf82f68a80ddc0378c49d12c088847baa8827413a1a245d5ea8d712a4a5258c54a38384441cd230302e3805ad928a9664f4c5bf527426560f2009a6b21ec914daa411e33a86397987a199dd07952e5f283f57726984e0e30e524a38bf4a6b5787835016677a185b673efe12b6c658e6f045409392356b118835450c72bd8729cffd9ac257cbfc5a584f1b44a1565e43bd7df63483da1dd2f2e482834bc38fad800be39ff127048af019812ffb853e5cab853f680d18f5a2833f92df0b4b95217b97c659a2c030b1429acafc793bbb884376c17d22f23409f0f08cf609f0b7bb232aa0466233c16a14fa5aaa8765b8b73188f51bb6a519e87d0cc06822e760c0c21552a95c85e22d8f7ec0bec1f11b93cf2c1e61c6f1a2396761383d8188ef0e8f66a1ac3ab6fb0e4400652e2e56630bc4c0df68df2929c8502dae36492705075f3181611a071dc9b58b37cf1806f0bd0ce7892f7da028c0c8c274b6f969a63bd654e7daa59bd5a1c21dad54262a013941b311ef0823eee6f1fce7f2e2e24a2dda5d7058a7c5ebc6c735c0c6354806329774411617227f683267bbf96741853cf6d4e5112520b30becd88c74c5e1f9420191a7f5f1d99f28f07e58803f0ad2a816fc9c4e4c155bdb74dcc6a93de49d47e7273d83b994720ac55f11cdc1e211118f9645d259dfd9e0ede232492d233ce49710da6d392708e8105a1a53065658a92ab8a21e4468638b5861932054aa35130307e05579eb392b8907e1b9b7c8122598067e61fd96e04964672be3c3bea0dc3e25a8233f757b8b1af88562362fd103b47d77e47824a05469ecf7057b1a68e6ccbc16d1a9563cd419988e4b0a8b4a1e6fe61e82cf6120b13966159b1017e61e19d2f81be5bc9bddfd12fa10078e0412a676356c57252fa0bc4583c458aec0e199a1ccd452cc7d294dea9dcb0c8d50cad4aeea71b7e0757856df4206b2fd80a956e73cb5e45679fad8be5541b16298f9723f16718e8b27473395cf253add944e5401ee3c02af10abf3615399e8674672117b3559e20805e95535ae1ee1ca6b9b45894035352e4f92fd9637afd907b868c4e48fa9ea87c2357cfd1dce2256f348605b41409a4bb2a0c0c744d8e04b18b4fbbc9a973c11d7ebc9c3478b4101459cc5f88f728cd4f4468bb189f5b41a4935248a9b54d96ff7b88c2a63ce119f6e54b6ccfcfc52ea121b5565df062c30a44dab26be53e2bbb89c4cbf67e6341e013b5ac8c268d626a2895bb87af4a45db5d143daf45019e7bb9bab3915ec79af5d48261bc6b2f07154048e51da648108c464b2973c031dd1efa4b17126a11959f65348ae60ffa57aaf9885520cc53ec5cd45bd24a4fcd9559cc7124ebddbae4c4a47ca895569bab687f2ce08e963f76d0a2bb8a290e681a9941b5d95da0546d50a220ac5c58330497fba3e055c92ac5e9ea47ba6fe2588620184a60c64586565f2656df23990cb2aa6905e416d6f03264f53630606c96f843d1be4bc16c70caa95e01f17bd609bd7c1c7134072ae9c9bb98c2f480afdf8704d4287a5578eb83fc54a75e4c9bd02072127df62f7a4abda593dab59a68201c9c62e7ca55c71583c6eac5f387a6798ef9ef4ffe20ed0fdbe9db2969e1238aa73c6572e5a041286db9b76a0840c06e0c9daecf7121ec0a7133564fc03978751ae4395fb536ad7a48a8dcad775c8931b7b514fe6d37b5877b04a2ad9b3bda5c65194b4f550abd733c0f03e7dacb3957702c3f135470ee7bf55398747d31f55ae875b8bcf00c885b23d92f090cc17d7bd65afe77e8a41a6832a824e41233cab1d1119165a00fc5494556d5ed03b80fe9ad0ffbef2ea0b830a430f9f8664c60c995401ae684e18f9851c7444566bc5b88e5b34dd4e581aff5cf93f4a11bcc9fa43ba0a4bbd111bb9624a582373b23cefb1a0507b779a4088afc2a630fbb9361161303c74a75af79b541ac8ec0ea6b88ff14aede1277ec8fdb44b26380b209b42e200da83d7da344ddda3f7388561df297138a0f6714c6f5e13d858f6313bfa7f9ce9ab111eefb31e6a776668f45eca2dfcaaec43df9aa9f003036a6fbf8aa54fa5d6b52d13d75d01d9bb8ea144c29cbacb304392177ec9231a0fddd5c66ea173decd092c6525a486f5ba6a219b7bf79f497d3deaca34c3d20d6c83e050dd829c01a66469f469822efd349d7fcbc4f920bb1683083dd8519d02da3135bd56134f49f07b9ea5f0bd04dbd065955fe9bbb329b971a4008089c523ee7bc5e7c7bbba9ee3c159d90156655ed1f69c8fe3f06430dfab5bac2216e2a9b091be77e89ffad8b7e1274c2eafeb8d78a4e05c75806a5df8918912af742ac66856092a41d398234506afedbfd92cbbd282ee1acc85ae53226c68a8498c894c479e4ec9d010b80124257636cf34caf56a16b5af5bceb3b7c6e189637c24f709847cb5dde39eb26287e590d0cb4a19f6ed4490502654a6179d1f24bf6f30d1c7b1a67d149cddaede626d1876a93b2d43988944615626eb4934181844bde9ea671970fd9f16df6d9d2ec7c4b0ec1e27c6acb830824f2b43bb8aaa416ce43142dce440eae2fe176a2aaf5e49ad2fd948a686471879614ddf4eef62e841d3bd030517d9db15532f0c4bd7257f88a81c18da23c17781d528a8cbbe1a40472195235fe950c8a1a3b65aa1b1ec8229565bacf9504b3ef32c69cfec62bed0712b9085919754c8d65535552a1a002c5eb8f180efcbb5051d855312e40f2eed3cdd68176629bd39aeb823b034f07d08004e8c482316f0be7dea02c5134dfc6779831026a0d324820a360c6b89bb4fd9bc8e2c70d4fe4bc9176a2e9f70508b91a6cbfd018697608c403334e3b8a05bd3cb0bdca30de368c07e79d9501804d9f996e3ecf9c78291980ab3808553c798da3ad71e0697eb0c9d6cbeaf3aa33f7c539ed989fd4a8e02e7229141849ee3015cfaf765961b16ca7ac7d73735dbd171bea3f94a601e375f5c1fd681436a2e76b065eba1fb22d655e484dd145d73337ca35f0fc2540bae22bad92d0dce064b1f9b268c72883819254d16c6ee533af5561d7b133e878d05a6d4ff9427d8d2033a65ab1472da760cb5e2fbf761a56470ca9e491aa955716cc59376947e1d3e177a3b422d1081c66fbfe0a09a2600e33f4d43af7b1e9fdf2174e2a681b374d2b94fbc7731999f06ca9a055bbabf71f5085279ae7e273dd343e3c2fc3979b19f9dbdd6ba5d5b1dc002b97007a7f63255a44afc920787053643bed61c86fc21c3d7ea9a215c23e9407001766204723f17a614137e6ac4c49efac158a9b6937a8b3b8349c96814c7cd3a423faa141127a4959493d9c4ac0afdbf36728d25546256f9ad12a9c86cf57f4cfb5419a699a84677e2d289102eb0eacc75272b47cbf61ff02f7aae149ac02eee6e486b90b2c913f7df049744f36ee9633495168325b0e38d2b14b69205d98dd70016bce1436cc1aacd2fd3a245c508718246d4df4d63223fed6b5c204554442d84282d50e7d3cf5d75bb55a09c73c03581c95b3e036faf30d1116a8693fcda75c77db6f49102e09acf88eb764da48fa67ecf4b3fa2c0aa5bb42da05375f7c3724eb718c330c8373743ccea1a5e27490d90f8b0c98203984e9055cb449c7398d3a4fcd95049413b4812762517e0e7f7a248e9bb62be08a45d4839d98b6429e8e05ed80db08bf80f4cf178b3cd01ec5f385aef04b3cfd8f3a651aa97551a5c68b5f22646cda147cb05491924084490d65aa62a8d40f35356a510258223431035b84639098d08ab7a32d1a64f3d3f52092c72a6bef56f3d77ef9f974a6fe2bbcb9074f7eb85f48a400d2f5f4990c440ac84dbc9d2014ff760de64e7c537d1db240e99dd1e8850eafd7147a3d0228379b92154b4626faa607c7aa83b57d89f2bf39a1b5379a49525b3e3f88f2afe5dd5e52e2fa340302ed00329d58d807262587da641b9d586dae10f1dadb6da2f45c5b29b2377c0f74f3482b1de578deace421fafdc45aea21b0fde70aaa371cb9206406d78316699c549048848a47136d36ef17bd47ba3cd2b7470b1c231d4183d61bb8f5a1fd203e38fa84a152265616809425a54b335495063a809e33284068535af81dc9ceb19afae83359442079cf5bbbcb74ac6680516451b0ed8f891a8dc1724fa5b7f950915592278385d4e5cb828f92920ba0097e7938034a2b3caee57c16e6d6bfcbc1a87209e03967e440fa185ee85fdde4a083354f164c427edd2274279f80e150f9b370401a960478c8381ff13bf157f10bc94f62815ca70c7c0ffd59e39ac7bb7ca572464396e186338ce88e8907f2792320585fd68afd45a8d8fea29aed3b9c81c1ffdecd6339ebdd2e159d4d727f3130fb68ec8bee94c70ecfe53813e60f591a9ae2ac2fee5aff1cb84824ce10778d903cc3de37725f36afcf9eccfba08701334310159ae0442ed416dfd51be8613bec3e8fcfe2912be7bffee29a0942418612d7f50675c2d24f30a14ae9c0eacd62a3911994ef8dd32cbfedd04de9e26b2b733025a4dd937fa2989ac36878bf5b4a72c150998393392390b1502e1a29a8ec8f5ff0c6366805bad9e3c0d97c80064eb2d5b955dee05a956605e0173d094fc591515398c146d42ad0f29f2647ba7873247cb4ba50316339c02a4a30325962e447ad2a512cd0473a367a79de61f4ec0a152c4b50b429a6f4f2aed888b04010330bf2511011fa451a25bd6c731cd2841d801175a5e8de3caa24f50cc43ddc862f83d33fcce8d4008ee055d2c3357562e4e1cf6ab38506d7232c66a293a1f4025f8e9a615856548824c69436bba9d79c14859f96f7d2c2d26b7aeef8efb4db4c55f2fd89ea498fe0aa9c10a61a1282ba54c10ccb46c144a714aeecd69e1f9ebec041e4e18fccce4bf02b4089fc67864fc18ee108231cb855aa524683d04e7c73b069427cf258fe7acd5196c32d2a6f66dd55151c3bd80b0549c0fdd462710b7abc45d141cc910782c10c855f590136ecacf1530fd635a70ae6943376f461a4fc2c786346d8a96f7a4ed1fc49515fb8ad65be388bc81f75d1bc07dfaec0c041d0def06bbce1a22d1f1df40b6680f0dc9c0e064c69b78b8ea2445362b4caabfa4ab12bbca983b8d83ecae9f5b76d5df3b21685b75df3efad88fd29d094daf9e63d237819353e0b2054e86bc1be658df7ab5099f2bbd607e7071e9c328f09ba43c8ff942b352ec013fb82961a465fc43025629e6f0154ea8025a8f9281b5e561dfa9b9873e517cb1153ac49e820eeed498036f71f35380e80162230085e427aecce36614951d6d8d56267acad80d6965d9f82c9932137e88d5cf7f6f67b0e197246172f91f4909a43f136e28fd49d942961ca30009c1a20e8abd753bdad12908454c4bde0efef65b12dc4e368b856e742c4b2979ea021f12d1d2e4cfe44d8822352f9367113ffe25fd6601e8b06b4985bae07bb69ce2c1b49eba02106f26408f1cc4a4164df582fd48a555d5245cf2a7fe5724577b6806bed037dfd6ed2750f06aed9d784e5a62c5390862f8847dd4eaf728f1bddaaf549dc8309b392c4eef8331d0e814642e73d22fc35c117cef534077d2b9477447f11e6ea6f8960f47196d07246f5e478623e3945d48e7853f688570fb016d4bb954094dd0982720c455b174a66303d41afce39ca28d5aba0e3e96d256e9f28baed12d9d16a47cdd9eb8787eeb95cd4c22d7b4fe9a6d3331d645304fc8038034b53585d2216617366d8275fd9aa7279012f45beb92812624430779cda7b7a0f333fa915045c2acf254de1f4906b17932716956ee6c032e47b61125edac4aff728789f439e58c08a2639645382799615d3ce2a66e5cd2a0d157e60d7d5260b6d992a69b3ff3d416f3fcc1a07199013431e092374dac69eaffea68c714137c1f802ae1dfcd2dd8a461b25b4fd409c2c49bad85f15792e5a012822dfe3e97ac41717f19ec810ea44d13195ed3fa393a9598483108a3a9fd1d59d16829ce669aac768ecfbb3e2097a4bb6fc869c5f02045d2b84f746a44cbd6a9180692e5575d0608ae6155e8736fad000a0dedaae147b8322d4ffe60e9e3f101daa7eb53b27fb03c0bd6571e53d06456797821e7e9cf3ee0f74e11d7ea0403fbc5c2a81a24f274f20be08143f356079a5234ec5f3e574785a1f0a1b20bd498e3546dfa2a30fc114cab7f92d1b68182c06f4cb05b7135e1e4d22e65d02c0b7f4413947f9330d5ae711edc7103213599e3fd86b8d9564fe63de4d0e5aa33b83a58806ce247c45ebdc43482435221f4b19e6eb94ef6a75e01a0ead12368b609a79c5da8c5bcd36988c5d461b8c93532888ba705e257d80c72aa8a7346c5a4a3a70eeae2f5144183d35dd6cf1b98560da315c26da50b40b1733aa005df964563af47af1a339fba68b08c2a79c6b3fe72e24ae3701111216b9109bd69602092b26509eb4e11671ee128b2c8d7a110f452303f6f8bed62ed2fbaeeb4702765ad25303f8223ace36fda70f87ec45719ee6fb14ebf6bf9546e4302a044500b24322999f2a1ef94141b1baa50aae07a02d66a4f3eb3bb5d63d771c205ffb6516e46e7380db0eb4ae4936101d538add84d5e71cf47dd097a9b7389cff9fa7302dc8b9637c8948a0c27e02f9dc08e8c3279c2dd5c6772af2e564c3c6359aea30cc4b4f870920d9e8cd0ec82544fef86eef5eda043aa1454c1d95bf5523af4c83c3540a44cde031cba846a7f039a7a568e546dd4a3b765a5eeacbfc2c8825ab437033c0c7224114db24a101180806442094a0a972aaba04b01f5220d154f5e0072222f2f0475fe228b2d6122c516443af8634ba505c9b87882efc79ca4644d7180fa08b78f6d2ea5d1dffd507f3074cefbe7c2b9f1209d436db68ed711a1c228ec4c47865adb8e91d12c85569518bc95db2a93fa548d49ea8aeada5be1ef5db2bf26ee41a9ce020a506c14d4ac54270217389c8150924efa1a621b278712074897e2fbfd427f9e24f9740640dc4591e490117fe0812a38c9c212ab3295d17cb0a22d2b1940c3915049225090e62beb7f0653a6b4c254bc1bdbdbc9e3bcc2c67395f444c72f1134ff75e7820ec5de3383a84822067a9b17428ae30dc2964854b2208e842c68f0bb5f35b88c2420bcd72f5b126fed5e54fb31445c6b4ea2854ca49864fb887cb932ca296a7b828be4a4918093709272e44166027eee670dfa2e24b9dd197f5da311b7ae1e0e44fb0548a19fa98504f53a3040a7526bc2211cc6c99ca4f176105d5e938efac5a561bda53e6132e2fea8bf854d858c54c43c1bfb3c89b3233e123b33a80982f3b1a5d028f868b122675d2ad163c180afdf34a5ace7c870c8c3071aee4271914792fc1798a6b53d4242da40e82e22170a326abe8ea05a4d55085466d052d2f35d0ffc8dc1af14a998ecb8a015dda554c9cfad2a4c821d2a6b888821a7011e9491e56af4e7e7167b1a2df1e016f46439386aae0cc36192b3dbf2ca953c07c99e89b7546f77ffd128f0247775f28f69667a86438e85e438780bfb08512a08c417165641fd2a06602b850518325d00425e5c6216ee448e0c9786d258424a9979ef6119046aa2ff3d2deffe899f616b1112485a5784f595932c4dd4989a327d55f712b2f98dc4ea2da8e3298946baa8356873a22d57e282003fbbfa556a4d96e4c2c46814e1a58833aed0939acffdc9017bf8434e3b1d01055de9845be3c13417fa9e4619c4c96e6b8d1a262756c1a54dd9a43d5e7a46a2064e144e192354939bf50607deae599356c6af7154b619ce06e525c57b7a9f2d21f9d018e300b5b5fba2eb31bfbb04a32114a83a27fe58219ddabd79046903006166090124dc13c7ec4e1e2a69e70f81002b1e03462bbd73f79d231199ebdf3d9dedf3c236494043fa87023c6506f6e42f5a714f7768c5baf6ef2937513871554ea05f19106bc34b0bf4b03c21f3737fa36dc88c1a66df506252fcd4dd61e4d4f182e5e76bd20fa3bfb2ef8e75c206905a8ad874f4b6e4bf40e9fbb41b785572e308193404e20b764f72699032fd9d06930d4fc49d2b6510bbefee9cf056ef290c835bde607910d8c216e11386f67d26314487c22660487355233c6c42e0213c5c45ce6e8679be87a7ea4e6ee0cc343b1603de47ee0b7c24a11e63129bf82d5fc8c30afbc41341002b687cbf854e45135698723626f6a6d8b6c36159e07a90051fca4d8aa3bf7fc4fd79df284c23cc478893b57a89e74aec937b00bad88d9b10525db93edd330152392e1e1fdb1aa83841b9c212b3754e87b38e8b4b8bbdd93200f854011f4e8ce8a2e2acc44009ad4172df784ea0c6cc298f24ad52df0230ee46cf984581cc8b1b365e90709f193b2e5797b0ee67676f3e09c8cc4ce163c5e4a8b0984215236b490d0440e0c140cc72fb652555f7a360ec882f8f0b7687daae36b1fb2f6fc4911e1bee4af5fb291660b640af442342053a1148bc7eba71bd6f0709b7f6db17392f5a0fe6df1f15470b21f761f13394b6484f9ce0fa2b948cd7da4e3b0605a8ab60b388bee517f2899d513d596dfad290a7c20d49cd43149ab14c70ab7fae7b8096d2934919b9a23a073ecb61a074e51332edc3cdb6371c3a0effa9463380ba486e113ccfe40a52fee338b123ce4f4a906bfd041704ff987306da8021479e2ececa9e2a132182f5b269d1cd4103ef1c40375583d2967a5918cf7036fa61cc04035da83327210d6bfdce687e3c020e8842c81287ba0666943404f73955f09fba46032fece186a2b8bea3e09ad5e3bb37ae59f940dd66bf4de5ef7f48aa98b8ec48ea62407ad87be29d45df0af873888fac2821bb47a2cd6d58bf4909a3a3974959af21fe1691a0ec011db276a979e7a0d224358788e559d5be6496596ad2df7efff83ff00b13c973e1b10e150cce6c1075fce00687a66b2923a012e8a550a7bc155b6820466a415dbff6fa964134406045b170ba4651bab35635c1db4777c05ca49b67224446a7046a43190c2347e3d7122544b68299e1caf638d509749d1686614ae6c80b37f8032aef12f6ee584fb8c25bdb3d6da6fec0e86b38c0b4450e57f6e6f9fe7baadd658ea168b78b3c14ad897bd9a2d561a02125f4a6f8b99d9352f4fd39ef6a8ba4e79695cb557e74f9de05c2a02a40489e18c0770ab38e56539be8a3f58cd0eb3e75bf71547661ab7c31d8ab4890d0efa0db58552a183ad3393f375f294134f735cd6f02d4a294a9f9eb8a4dd9ae2e368d689a69aadea891a1061f19370d0e4e34f4c59587e5db33767b46708f9e6359b4f4375f4bfa6830b49754c4833630d98a5d93d2b76f515176584bce76ad3a02bdbfff06e1813d881d7bab54c9ff04783841d480527fca1bb9336e407f4e6c7629406226dc426ada32f2071a6de80e7aa7fd45f926d24512dd930c1f83297324cacbcba139738cc9fb7ad030eabbf1c658298e3bfe3b83c3e1e30a94c3086fb4ecc83f954f0690102670e268489f7b8bb1acf0c04cda7ba025d0bbbcff294f72fbe9ea72b4ec6cc8ca3eed92a9f1eb438de1462758e046d60e79abb3038eac89b919bd20aa3c600d430f03335f8add1fb9ec0fa6a348fde3197c7464a17e8956eb3c647085af686c950d91e3852cda94c30d532a0a1d607e8e2df5e393bfec5203f457d0ed3faf7ac80b150ade4b0d9a071dbbe6e5b4008902112f0ad4f5660562f7cf259af996a984812825f89017d44373ee7e2bc92a614e102b170f5bbf23344b28f7fb1c4b53de845466d74852dca9ca0eb434935682c788916c8d84cf5197fa148b50fecc360089ec9c5b3d799eb0fdf459ad723b4b09b26102986797850fd6e5c5ff134f86a8da02be7024841c1d3e90ad3f9cf57db77375e2660e4888c554bcc287359202b7c092cbd0b7756d0aef29ab35ac0733106b3d9833bbe9d609a177a21e0e919acc2d954277a0b5d610868720a4787dc08808aa389da7818cf3337c629eab05059cf2798c557230bcdb1aee62d3d7787533e4bd30d2816adf984aa5a9ed9ec3676f5231c5ab31cce14a74fbf40fb28e1855cea16096d489c2276bab8af3e41e6589f354d88b1fff617fb4abd2e890516662a56d0e99539be48a3926d3e131ced9e8a5e69ab9b520a0a643e6c89e4bb5c7ea8157f2636637b996d7b128328c880d5964906d8076f1372c86faf3736e67884cc67dfa42ab0cd65ab343229744fe807b3d8ccae5d7b5fee044b4b2acbe613c17dfbf7edc7c2ae6200cb275d2d8786a91cbe3d27e1c9a3704a474bde95d8adcfd8bd22a59c21cc453fea6a7456af6c2cfc7f28574da7b99f81d9a0e259f4d65a147ecdf7e891c0fe282a2581bf4ca909c9740e6704f8921c9f19b4e236384eb7d5ea26e44d70738956c58c11c6042b9b108d25188978b12dcc816fd0d3ec3459e2d846721f226da00085c2508fcd0ebdd38c01a3befc08c261511c0179d47ef70fdf60cf169cde401ad3d6b30ac7d4feddd46aa54d0912683e417e29a9eeab31604264c9708aedfe53964609454e57080eaaf5646f8c0ce31c1815747ea2bf752d2ac1c7353f351d80d1d843dad213eded5a0114163c08ea0fe95826dd69811b02462dab1d37d9b134d8e61a8b4fcb679cb2a65f634459238c71f5b86b401f32ce4ab4f81a372328984cb48b677c380657d3d3a92515d8a2af0b3bb98bb57e8ad9af79fab0055cd98965767ce1d639bb624f89b4c9bc0b62517248df93ff9a782dc31a40aa133618693386aeb84461e5dc2d2361eebdf764ffbf9ce0dfd9bacb81cffc7fed089f96ef323d3a718ae876926052eb9e9cda42cef5451eae41d216a1f1d91bc6391a76928986f711cc3280b68afe1f4392bdd8a1a0db7b33ae3622346e6530739fb2499cb24daad5e4d871ef148df5215706f8709e489b1c1759e100dd2a0c275959e7509b024b0716d6fe5f33fb5d4ea687945f837feee7d7e688c2ab35c10bff7dd9d34ed1e14fad98694c8daf1456213c48ff9f16f5de12d0883478c783159f82128a839279f3c0809b98e12be76a54cb0c96c0d5189adccabd075c14c8d045d43a454fcaf60707703c8d4b7ebe5f84e8ac1029df4a2aa966bcf7112b2253d1316db8322450b352ba3f78e164bf16b94f9fdb598a0b01d5eff52d05d54df53c90bf6c720de73c50455a03d62d65326c62832dfb6e6a1434be9e32aca355b605245837d0a8e5f63781e7b3f75debd8cc5905a90d4f4b59f8a7439bc071b5d5aceaf9bc625797d76aec43fe67fabc95a5e7080d5d58d998317f68ff5800c14b766d3376c2a22520ee727a8ad8143dba789399fc494d1dcaca6f1af047092231ee5095dc6b1d528677cccf0fac6fddcd7e7b09505ca129608b01feb98125bdd7d31136283a76ada0bc6f06bb5f1b8ad1971610b41cc625d0f636138435f399a1f49d75378dfe0ca7b29101443476b872c48181fbc918c632470ef9531781a932a0a28770dd6f4140b3bbe6beef3f35558a6128976d16ad2bc474ec508e26d6976ecc59c1fbf3f45f456e2a1bb7701c8342397c34a9b9da4bfb740eb0bf318b9e57f6a9b5a5ecc059306adf4640de7fc94c237b77bd67a02d47904fe45ded690f99531154a68cb2f13b180e0d134f99ffd2d5a182c16938a4ba0f78f67c937ec722087b52ced58a37c0b803f42a9a9d0f8a922336166ab61096a2f73fdba32d369dd764f6ae798f76bfae663bb06a52ce0cb83c3137e87c1a5b3ea70ed5a4ef804daffe90283ee3622217b965cb0895cb6f8b0d38fedb8004f84a04140f59c3967aa563003f05f7a983f8c860b27a8781e5f19658ec125c6087937bf25e72ef32549a6843828b94f68c7068a084ab2fa8ddcf7792ed25a97ae64f71a1b999f66a8b81b7e6c4e8579e1d9f7a72c066800977a6059a210c684d1e3a1e368e8e83ccf8dc434c70637758c46288c9bed244e09e62531a50b227d7f2637049dbd6b26296d5c88ed5ed86007723c25c11e4b206bf9bbb1978d1dd5807d9acbb698611f66478ff28312f03246e270ec40898a26ef9ddb828c9de127f374a50044880c1b6960eca6c2de6c416b339e329ec63f022c0b5d629e8ba71acf77e7b716cacb6d691c97407934555ed8bc21b4be8f5cb90c9678837a7fff06a458380f1069a877d9aaa800d287c879199c3507274ed694e34aaf803b1d65b22bf43166dc37fa7e021c56b66399f675cd4442eb04af3d551dab581cddb768cec944f0087f94c996b06c2a8d32815389dbf532ccd50a5206a3cce97eb86c8ce8ed2104c4654ab1db3b3b2b8c688e1129594b169d195f0279e38540fe92dd203d5e919268045ebd5301940cdf8a0bb3e3dd9fbc7d45628ef6dfa7b1fb370513ca588cd10f2483e66f33a2f3fa25eae63e8e97b70bce0db2efaa5ded6857841e7965b4fc221dd8ecd2d6b3fe0625ba0cb8ff45119fc8bc7997ebbae51be2fbd61de4d2deaacc4ed868e5d29943afe2cae6ead5aabb3211efcd5261e7336b6f50e5c8ee09f6a5fd312c108429c151da96ebb4d4bcc8f7125e8565709666372c85b26e59a085c87558357a1c80344faa06c4b081631416dc64e1f219ce7dc588fb653c1ae4bb20da2ace4826cd120990058903ae0bd6ca84de534ab96eb2cbab2498fc11bc1cda4d31968a325c9c56e444ba20e171be37d5374b300d17e4b09f9cda8aff3651e43451d9b69e44aaaba50d47f21c225437040fc76988c4b69c3cf051b44678aa5d3300ee2c8084a3b0c47a6fbfdac8ed808f7d7494c55c241d3fbf945e79cdec1c1313b1a69e4c088dafe9540dfcfda9184ec8eedbc892e3b89b2401e3d54008d6887239a9626f886c95f3f8436769e1329ecac033b24e1a1936f28c37f3e7dc538c19268b761e60df6439c6d7a6eebffc0572d992e46e1febb82126149c15416ca1a11904366109102f293e2e65e7f0579de10f9940f0d8a3014e2d5713ad12270d5ee6b9317c6373e561d27eae04deea4bf3654f82dd3725f6764acf42bb7e0ebc30eec7c9007377f970f22d113b14cd58ea92f452603cafaff8e4c69a319137d6fa331ff8b238f9065a10ea35da0ea4bf3037c8372dec0252964dd6029bdc339ceb5805f3d08dd529cd68e5641c203187188f73e14585b9864de54718c3d5a7a3d7bf11e9c7714e463a444bec9d3c37879acbf953fa74162201b4cf128dadbb23e35907cc30b77217af6f2cccf1c5bc61573328cc314a3583dae9d994c23d7e06345572cb86bbacc0e1896f662a108dc19f7eaae4605603df340e01db59418d91a52a9a882b4aa6ba84d50d15cc46363ddd1ec1968818289dd03314b517c65a56efcb906858c2c565176d5a266d09403fde40d7ba8dbc221f0dc8890ca247dd53705e8d6973f580ab4abcbe5407fa5f891a99d05a9fd4480653e9beab2ff5c581f1c76d26115dc7c52699bede9a84f0e6afb897175313ede3c4753a6bff6a21fcd44c7eba37ee9235a7722bad4ef86790fb53a4950386deb399d9bb3b1376a0d94fd40bc2df0c8f8abc446f78bcf2f85655cdd64fa7d4d195572a61db5fbad944c4ecc48b57014e167afd4dd9703d2d0c152d5714e818fbb97e69400d3bb1853515575cf6bb88ea954a903125f41e27b393cd531bb21e76e4cad82744935ee46990c328ea9173e816d5034e32414eca8231cc2cfea3ebdbb01562c982d52265cfdd764dde54884a42817a58ce683f0cacc1410c2523121ca28f20df29477b33aa00556af5d4ebaa4db8a98edf82aac26f7d45fe78f641c25c49c8eae831e22d5cdeee0e8c852351c92dac8453072863a9ae280b005a1020e9b9d547d1077d0d69683868ed8af520499f291f1c9174b7b6ecc69e9ef594b24825e499684b247fad9e1b7a3a7d221bc399da37825c95c41956f7784030ee13db9e1ae02c8b752151504de7906234c74bc184c913e700ba922bbf3d28d8ee234d2455ac1eaa270d14001368e558229916c0e0eea8e41f6adeb81c7af267d63736507f090521dd2565f68542aa86d0f3d84f045a178f83e4a52f11cbbdca8376a43e48ccca8f4a154decf1c425fcb9ce5bb1e16afe09c5871bfe3ce72099e86495143f8abe4e7b333cc191f13cb17675aac42eaa0a9f06b49f056f5f15c3d419b3d57f68b81b53132a9cf4475bcc145e3a403910f1e5d27ee3aa34567c2e86cd3a3fe60fd6a6cdf74bf7b58ac0ca6ea34b53a801fb11a400e3d4539ba7c7ae808e3dbce0a180fa644336e79d262690b404fc537f170eb2742d4291aa9d7cf8d549fa7da8cb0dc9b922570da1f7e3fc6763688810ebb203d675b9cd0cb570d5abc930c4f5299ce294732ef213820666e327349432d8084feeb9cf37541dfe09728b9d641a2de0def843ea87b5be8be64ebb99f75db72b1126f38d452f85898e0e4f79dfa58897891064ad2d4811401482998295c4da114bb95b1b08baa72cca35888e6950c16d8140d13da9c5da8d1158b1005dfd043b6513855371a13a2aeb02bc6ebe50e0ccc2797a3e51c5d227ab135c78753dcfcec24ebfd8a6a0b0f8ab104e3c1f107523b064a2e566704a741874738d5bb2dd292354e208ef20792f71bda3ec05bd6c64de40255aaa89e3644eb85bd2e1b30f035ed1042a3085d25ec2fad5f256e11167a31385b4a70ea1203cc4b5562b8eeafb4cb87dbb0d13574aad2eee5dc8965c29559b38ce53cae58949054502d2384084fdea675e56f73ec6209a993715603866062f5dec4903ceb38c5127633d9aee5d4e6caa0f1e8c7de72583316428fae38592618f2727199a36aaea3c2f3b0139316cb86c64af7ac226600ff63d71b8767f811d2bc86ea498a389a3a41422a0e75550e12c47dff341c3d23431a5c56a188cf3cbb9110411bf4442a04e0619f0cec11d6410b6d13e1b33163422b80e10a651e772688da8af68576f73a9e0ad3e14e537b1e1f0f5dfd84f8c03d9a75aa8932107fefee5a6f8e422a1790627b81b2fb3c03d3e9849c793359e408e09bdd0360fa3f55812f801b0d35fe839de120e0b76576818f5514b4aad745865369e49b3c8d2d212a211ada4ff96f98bc74475d4f408912d412c5906341e4a0111fd7bafa648b3f097ef7dda6d7151deaa0136cd335de7e49183cd283aa330eeb09cd01757ffd28781290c5f92d9132fa73bf42d7ae09ba32b30431a5d00afd85775b4228102c90d5d71d08509e370b37eb879bde7dee30d1863a1df493339c3f41f11a6d0a775afda29bdab2683b39a1c4c4b4fc3cbbbfac7aaf9ed601fd7c408a0d9f018b9e9e5c7b2b6f61e9b0bcf91d5aca4e0df3ac6f869a46a5537743b050c7eecf43c13d6342ebcf2fd69070ddbd0a94a5abb7ae9794850bd7b35ff822c3d9d15a146d66c27b5c996c36b4ab3363f67c43c9850813be3a3c4337fbb7e51c2259830eb14bf758c66bcf81ac2df8ae6dc86165845f6176f782e8c9aef91e1eecb01235f025442210318bbadc05a2b47b8146e012f7753fdcd9f20920f6e86a740c6d2b6bb960107b4301970db1756bee7c36dbc9f90f01d29a88ebcb33f520afd390a0503d2d3c3c92808ff9cef03d3fdeeae84a08a2b94f33564470b3a5e334635692b621ef170b3a1c105656e6fdb128ba70977c699b38e09cda2b7a348737bcb2fdcf733b083da653be2c34802650c647f909638a45654517d7b20ac2e8e914214d438f100828bd34017975721157d9ebffa545e18d474c5b52a33ec758c4d2661a46e99174064522cb242f65682ea9162c4c933cb8faab73ae869ccb2c92bf6882a1698a04245ba59a449fff117831bae4ea280fd39c458ac8f3452dc288ccaecd4a5c04fdf375c4ac0e828460838c7ce416e08741660bdb20773a481170b138e2e3eb1c8c02117cdedc5b96b64d80f77631ab1b5962e5f02cfcf9a4fd72066079a4a0cc6419bf115b1ca3a5a6f674da7ead0eba9a49e22d5ba537bd52090e47e529e2d66318b120d9ab158f3735839be1c58b5eda40a078b8545617fe254172cbbeedb117b5e757738e0e62f2a022e8fa3b97010ea1272782371b9f86fe4791736bb642b55e9d4ddb96fbd16c23a37532acf2af85c6302bec0d72ef8c2433067b6bbeb7bc83112fb4914c194f119780074e568f7048366fab700addb8b72d9364998cd7c735103a540ae543387a791653c81f598b287e302ebe204bf4aaf4dd6d76bd485ab822ac5e46abe2ea8006640284ae648e939fe8aac161fb360524fe827093644de162808d9fd14f72fd522f0ada81f694a798b4c57242de1e4df4c85036815bd78c0f58882faf4041837614faaf270f20971bcca4d652024b6a15586218803d18e15391d921113087e452eb079642214df6992922db3729dbdd5234255e9be0e11a99ea04f66dfb25159a12895ed1549c70d48cc8aa9ea4290ff7dd552049c67b81b0f28ac6df1a375cdfd411c6e834a146cf6279c80af7920cd22e5c3c1bfe55500294b2fe71f275c5732d37fe5b95b2faaccd1b4519b0785338886631bd45c28978175daf953cb8bc6008013f536f330c66524392afabb6438b6cbb1d2ee6bfc84e87a50882c483bec8d0546720f10bf8d0a030a2e9bd53a851b167582b1f2e3e5248f13975300dfa2873402dd56fece6e82746b78bafcc364145c871953510f36bf4a2f250ccf96da2f297dad300637a2687cb929a6bb00c6d7fdee7e9d93725babb9752c38ecc69de9d02722f131314ade9b7ca86b838e4feafcdb052440b61e5a524d636b37511a38b0213d783289835ecc4e1f86d6524e5c893ec0904cc7f59915b2e45670cb29c11f675fd0983f894ecfbee05df59943259f160f2036dc5d10ae4225b896181b110af612bc2463fc2b1c77d9763c56cda92fae19990ff8c6168a8f6d4cb03413a4a286c6518ea34c44e672385f845c3689843e229ee2141e4e62fc69cc084c55fba55a7434e0c01cc163aab7ed0ced3ee1b9882a75fa61faa6b8400b65bea8104300775344c08874b919405598aad29e7a63f8db532402a99e1cbf73f58009303fbcf6c4dc4094be7d8eaa2e45a7385343fcd37d4a16d09605cf4c733730ad1e17be26120149b8780ac058d9ffd452a91737d033029cd17237fd42985edabc89097857e9c1a22daf8b53a7c639c280912e0651f766cb0bfe424026b78fd9631431eb5b2bf142356e7f9af199d512c7372fccb793d7abf747eafb287d2b73c45f8fa5919d5be9f0e8f7437a9f5e7c24a443da67b49c180c3a1bfcd772e15c300281d33dddb252e0ca61ea6362fbdde01158dbcd41c7d2a1f07f0f7a4d1ac42bd83af7c30d16cef0679a847019934a7899a2c3e44081150dfa66cb7829772a51d22e1f0e2895f2b7811d4f6d97a83d60814f7467c09cf20cfe18a5c5ce83626b571c0d501a1145fc1f0c4d587755f424976423df5881f7a840112c1e17e70aca484b4cd3b8140af7b48bf9c088299340b0edf2c5dd2d2d2f891c636c12807b56c5a1868b3869b46684c1f61232549357c471496d09bd809549146fae886287f61039c973862bdaac4b0d3292c585e2a0a267acf757220ec4f069bcc95088a070a74812398b476f99ce514d42fb377391055e2f5f9562513a645e81ea234965cb1549bef3b1c006d815649e0759c8d17268ec20e4760a54c0281259f05a834308425a77726ed5e94bd877ad517121269a0ebddf51b4e4481600f6c4edce636b22dcc1ca201520b5192c26897269b2d35ca837856a93da56f40585fcbe5d147bdc1730ca10b7aea24b7aa42791eefdcd81f4b61c58281d21590ad9347a60836623b784cb13f82838142cee91e630b487912ea717fa84c9012467dc5ac7b9389f7ef467e9c9f5c4d18f60298c3b9a5017523989c3dbed73cb0b74402cd92e98cea67556f52cf4403dedb412a45acaa1159b33a52f38b646b2364112769110eabf2705056ab6d24d2d6eb41584ce73569c83971335b65554178ab4e5f2d2ce1e91901126314312debcec1bc826f7bff39caa18e65af03227217cf69ce01e5941e64db04212712d18fc7cbb8eec7c98905271042d1a50618b1b2cdc44cc581a40c6c2b77a0f6cfbef19a1fe81a75a2ee2a890123240acbe06f527505a3f7003602fd6d77014d8759b2eea6edaeb451772075e0bb7b3704915216a68423fc35b208ba8cade1e54f579bb33182ce357e6cae01dd2ad886794fc8d9ac6321361d62e320468ed70dbc502c3934b4ba06cf7ee8c1227e690ea69922cb32c2d71aec1a13f91c1b6c237307c84dbb3d98ee108c8565a225114ca87e5dd101d7d090991a0eba9fcdc107a9e4a9d89a01d8c8d2c43dab804d02e21f74abc90345a729bf98278b0d2e347eb712129af33907d9fdfa44cdf4063c9c58c96b12e99c37aba1b6fdb016e45062eb40739b8e1f12194e2f3317813481e6ab3bd8eb4d230ecdb22a9ab3a53bdcebeda612c05bebb53d6702879d93ded5cc6fba1b59a0c755cc830d969b8c9abb821ab6f4349e6911deb577670f8a8b063b589746092bddceebc79e94bccc548ad8b8cb4c76b08f1074b1c5ae1f97c9a06be85be71251460ef758cba438c0d01db0467e505e9adfaf7f9d7e18d7123fc96c001011689a8f2c0389c15f573134db0ecc36509e832afc99578148019b872c1568420c77c301846431abac7ca217e009356d79a31383476bd43c53b20e9f9a2cc0640782988470e64073699871401e65233d50c703d7469a7fb0c220cdcc2586ec6a6107b7d127fef1b8bf6a65e4cf225cca96330d712d13950964840bbb5687cf4a2bec9027e04c929bc8e01a19b8e53f4e0c43fc6a761193b42e780467bbc2fa9ca0365a696ec2db3bc41697c722b817075a2a073ca567b503c33d728d2e9a557549b31a2069f98e3027e332573e0a8d0a38aa6d0d0f1d6b3d5435ccdaba2f88cccc5866fcad3396fa85895270aeade9d7c8842d603e1660102ef1c6cfb9690d01b6604c96285ec2ddae5896b0cf10fb9426fc13c8842cbd7c73adb130df7b60294e4af7395b08539bca9b92995043b3905582af7e44d9858757950f1941c1bda5dce6eed838f6c65ee6a16280a95380fcbb726e3eb6141112247b7fec17e45d1b5bceeeb9e843222fede24a209b9aeeba2590beb884220538c06ee61a263ce277a024f2a27f68053f4980ff49ef93e76cc3aaa75ca35983a780a3f5346ac772b606f071d20a4aeb9456e9f1570058a0b430acd1b3796d71ebd6422da3221e15bdf508e2fc20069fcb120ae2511dd44161c5f917961106e76a2e4a48613d80c6c0c45862838388c7c95d224a88a863fc0bfecbe768c98bfa45fabc814ad80a79a92f6de4b51c99c6ca47710f24c05b10bc841b4eee2193a0df2fefcb54559b6cb9e0d44a6c5c7e9e3e9bb82935a1e2a09be458ad04c1fa83473fbbb66ed9a6c832de9cea211b6a0281b45d1ac0d89b67a02624ac83e17f1cc2baef319a36fbe6f82e3147c2ba4678fc503d2c25570ce43cc72d87851f436f33e212f199dafce14f473ffabecd10e549a587d8667cf472644dc421b540c0198268744b88abb06acea4e2815152f407557c79bfc3675584c3c15e70727d069d5191abba359af0438bdca519cee58f9cafeb83497fb2568ea58f1137d0637f68acae3e79a4ccac94924f76834e8c260e54b5836705f328e298c0aebef05c99dff94dfe8d38a318c709365e05a324f5bb7eab9e3efb41787e52c2c536095f32c3592737570ae4538c41f88a4d1a6398e54b6a090f7d88723299d2e5716a54db126b42a97bc6da366dc75d90a5c88097394c01b95c3e013cad048a70c3b05b30d2a29a0e4a3aafa070053e978671bcec4694b50d395beae8fa1cf56dd4990cdf3a2c24b84081e259e5c92cd1f63a5b61a208100c83e8f5999f9a2be1b6bcf2f15a1cac82aac5dff7668165a01a2b624837b2db9adc524a99a40cd90aae0a0e0ba947bdf7305f65ed5969e552aaefbe9c424bc951f955ca21223af9fd2ec55b91b40e7998dec764f2bc0f870eb14a8a4a8aca0a8b8d254c6c7aa231a74f578babc5dbe5367d89ee3dd91dcbd321cbef273fbecb0d971beee297c794d99e7ed91f0fb43a964807d7a60c8737d7799de7811ecba328708858e3bd256289388b0d5f51f114ec28698d7828cfd33befb3463a246289bc69624164459459202024dfaeb23a58c9ea746ca6f8743e73783a36411c3abcc14b293b5d0a11b993722425a8f39c2224738c7cbbd449214a014a198a9f6dac59f59f92620d8bcb0d67b95a5acebde77d5527e6988e83a7facc9d9feec34f9fbe52017ef9c1e1a2004fee02f25fbe2c7f5833bac46eb8ecb8dc68b9787cd68c4a412e976769a4c55b2e108c1de2a58ff5ddf013cb0a5652ca293bb14393cb7c4c6fa6a4245171a292d4a1dbb48f29933fadf2c40393727854967454a0ecacf47ca358983f41acd4e14e87ed799d5b1eab83e30867aba3633b5367f293cabbee744d9ee77376d3519e2d915bb799f633a156e85426533fea2ac557fbbd8ed58832abd3af769d1dbe3d013e3847a5efc1d91259a28e998ed492142df1bc35addb9cb4109ffccd42513265bbf46d13049c2d914dd232aca4b4e47ee7320646b9c933567aac447309037b3e2751c7e209cc79bcd461bb47fb7693ceb7a3527652b0528afaacf1dcf4cdf7fa4d9e0bd157b7691c620db5ee7dd964f2cf334b8a184e221d4e1c306e1d8729fb40ac341d54b2d6b1d2bbd2e3f860fca2c01873786ed3263fb9cdf438afa8152cf803f58264410b58f0dcfd0a461e224b34e46a0040a12ebdfbd1b3f7113712ad37274a9d4f8d8706a7a5a3531a349d269efa09c8b3a5552855a92e75cc76772335714105e24cb63692ed496dcaeaad22d120c6aa0eba5a3d9938ab837a43aa4a9e57e4c32fbba65e7f2b2fbbeaf5d76af9b51ab45e77bb6c10487f0d42cc41bdbb9dc9ebcf7bd99dbcfe545e769dd7dfeae5774249b0f3bcfe4e2f3b94273feabd1453a90a25e6a08e7ad9de21a52da99221e55eca55a99453ea26d870bdcd17f5d477eb90f660f9fdfa57aca96608c0fa906b9e12e1a9e713bede62505ebd9d804e3777a94a393c3f46806c50bd591ddb637d2c506d09d4b1ec3141401ed3b1c64caa9529741e39d3c97ab516c4b2e34c276b2b779b520f4f26ea1700724fdec9fb5631078b2ca1bda797dba15aea9582243c95340dcb7597568fde716e3f12c2e740bc62b938eeb2ac779d1058d7c32b97c778d34d6eafdb56f50ceba4b436e9828cf2953ee92a659ee9ed87faac4ecb57cf058d18a57bacb6564b931fbf7d3ad63fd2635e7d94d99c7e51cfde2a529f3687463b8179753f9a85d90177763ea9b70ae5ab4afdea01674bd38935d56927f4f568be80c0883b4f6d9d0f1dcfd3cec7f3698aa77e1a8a35ed94baa5756c67ca5ad5518e33931e51ee813d529ea7409c99b4491d7b42a58c38f0a2460eb511295dca97920598bf4146f1a47a6b599cd5a5a7965693ba821163758c930433c5010e80de04669a0fddfb2eea1b83baa5d524e9d4724dfe16bdfc56df99acf8ee6484d0cb6fe5bbce935f4b7a091ffb1be61ab49f1e991481e4239322cef8ce83810f0c76505e75f282235fc11b6e3e032c0f247586ca072f3fef3bcce453bccaa4a7d6b2be7ace3b150f052fbfd3772b9eb4de456f1553a976263d50bcfc506f755015f46c4e8749a0dc00d4003506786a004b5bb5c41afb019708660b82a74e5bbea5e5ed1029f687a72d4f693107f5ef088c3ba4a8cfab9656ab9f863a5aad5e3f8fabdee238eb3ab23a3d16c812b1462ccdfad8204a2dd153e76c1dea18a5d48a2193d44fba8757b757b4a87d7b2a45c5a3af3aa60a512cbc48782cbe512c842f83e85bfa993e88ba54441d1e29522de9b01e4155a19390cf49a8fa4c59dda93bd5a7f3ac1ae26c4fc77275e9dc4ef5fe5c77beabd79d8e4df9a61bf37c36a59d0d10b8f314b72b36210f1cfd4c21c59a14c79e1a4a01576c9de72a3463f02a96219fe30eea50ace9cac34b1e6238f13724f4edb20ad529aad037d29b9c76e0e853ee82c69c472aefe4877da6a478c7771102ceaaa18e495751a98c101de9581255916a49c724a8226a954f754465e44bf95449544528d5100d778e7295c953df8f3af49ea3bed6c9e37bf64fa6bb62b93a540da986ba257a4d55b450afb6eb3833ea35c9a5739cc9afdf586ba2f48806ea3ea8f3a03e43f8913a1733042cd63020e6a04edd04ea61147dd182a73978ead9004fbf23b0cc0a700e5f565aacb1f1d4eb51ac894fad447ae9d25bb536eb4629ad4fb7d879ea91d2a35a0fadd55440b500b241a9ce53af0da34e7da663568ca7617cf6621002547dd015599f145927095f3f9395b238318ef2cd9ad3fec41c2288397a091b857de2dbb3cb0fd6a104adcf4b542a8a35479608cbd3615b9f23d40dbf7341035beba3fa30120e3c4474332a7545f808095bcf5d510f58c75a0838c360a816605ec5f202675863a4eaf3e5f927306319442638e902f27357945955461ed6e411d6c4265b12bfcf0f91a06fafd6c7fa589fefa2a2149a6906247c742a533059f96a8f2916d7df8f15083c5dae684270bd81eaa8e85b75b44a00133c3dd7a25b51ac599994620d8bea68be3a76328a61c74e451d76f55621d19391eac84f456006a5eda9fd766b8476d1c5981a59df56ca6ef8b439c1284b6052c6ce472640e0f9027c64424693cf928784efe867faa194c69843b2f0fae94d847317e59b665abf3432050b465982f58c59087f823664008a0682d0c435503ec021502e2e4438bea56e72ebf9c7046042c0d4fb7f9c6a4f1d023470f1ed51666bd12289321e7daa59a3ef04f8e0dcdd5e39ddede790c927e8a3c30efcd161bce990490d885c024a1b21e0e9d9d66252ac59c51a966ff7b1e25ef22861f952e72fe7f30354ab79b435905842b29484c774440dc90f12a24e1a2d4122062f28c1d04c0713183d44b3222d05e00091d16f4779b4ecc433c37ce9373d4839d4a6c5672f55c4185f0491cf118a6ca48ec92cf3d2656ea26e2293de841e4c830f1e33c8eeb6d1d0b23a7e2d81adc3fa255d4a09837d6d9b2b34d2007e80a58c33d912ff8c22c55977a7f0987551cef828a10a0c9bd1d131f983970e4377cb70c6105668b102152831213b61c81746d0e0a1490f473776ace5316e42cf110dd27d48e731038d89b9d203a66dad9c1cc782d13ada298acd66b3c56c5346ad4b152970aed4549dc41aae08ad2463819e6b0f72fdc1d313aad361f3f9e968a61c84f1e2ae876f73dc1d82b3f3521a6b11cca63ed57aa25f159a7306fd00f5e907e3956b0401cce69cf109d00f93aa0408547c9644c6f8510284a094943b3f35982c31c03bf2a7431d9ce50ef501abb1485815ed88aff67b31b6110467b9e3f31d5b790257cf247cbbe5388ebb3e3f384a9cd169f211fec69858628d990e750b20bf9452da4d3dd337bf15ee10154b68575d6f0d4c135df5812e6c00d960628437a974f4e05c6db7da620de7240d0c25de27d27305e3dbab17310509ead0a1c38644b5504fb713948e99bc9f7c52c7b84fbedb4ed53665b556abd96ab65aabb55aabb593ad87785d8f8fcd6603c814d42d431973d898c336c7dd304c43e8cee963ca6e775212f53b512bc80ea0c344fdeaf0919414c618c650ae0485aff00c137cc4b85c93e326c755221f506a4a42a95bad9d8a33834b43e77e80e7fae0c3dd648c4c7e7574faf6b852bac59b924c21f572ca68543e3f06cea9db8d8686c5a29ead592223d4778d52094d393b35c4e41125839c996e9926d3b04eefe4b30b8953d2b971b71ba5b439a536799b4c263095bacd396b85194a0921e4b9e79ef7018ef2c0ce7f788f4281b94fa7d3e974728b3aa94c28e7509ee23a581a18e59ce7d50250600d9cb3ea1723d3e18e8f470fd081b0e7d9236a0157d5aa1e5bd35d9debfac98154571af8fabdf282524806a9e4af3a6672e92c170acc27b78e02f3c94d2e512c9cbce34c40ea731e7f4a2129dd65410954001e240a6704a1054c00b1032a8066b22646104f4c7044e4036acca4571a0fa5d348f77008b747d4b90cc1249081735cf252ca1e40e84f67e2a5c75a8ac0b565c396057fc4b7417e8c4b2fa5b72881b34b107611c2adeaf25d4680bd6574263df4aeccb9e99076820de292a43b5d8c716102b7a6cb5fc9e82cae8a605e8732fc61e58958f30316800b3c2a5796c0b99ffc93d87d9357745d6c8e75432d6769647528047b1dae7c045d10a1c1120d96ac6ee044064d64a05464bbeefa0de738b3ce2425cf743bdd9abc47e726976e026f8a9cc01b13d89227bfe93a3739ce4c7a9d4b9fd347c3d9306573f29853fecca09f92abb5e996675249a9e469198b2da8fe7479e428c94f8f530afd9444b5a29f4ea3e431bdc79c3b60b043261faad71eab8b7c5a3aab6536805e051b85e98835d9d48621c68d14888c8e7a8ce9a49437c004e12b89e2b4f58309c217944af3153d9c2fe972a96531de9cde79c9eae99f9754f6bcf46e201e6c6c48e6ababd0b1a82409edeb92aee598af49743ec3901c7dd681bfc836ee5805c257fad15ba01f25184f29318420a943d80fb85667cd70cabc1944273b55094db0741a1b10ef6dcf73cea9246f20d3f8e9d27f649024e214a434b765cf204738b74c519070517780b98f4a90d46c54d801036661efa31224463e8747b196050b7e08c30749ca98e286880f33a8e248075fec24e13da162c9906992c4104b98008c309c08019116216041953be4f01b86848a1d284172826fbfdd352548964022240d7a4513eacd273fdd156be277372bc678a334416cfd80e9929e07d22f7ba82fbd9652d6a674d68f764879f8d59c93fb8cf3d187599b7e656573deaa633dbbfbfa9c1c67e76571d65eea93ae565efcecbd9c35be355f0ae2bc4703e9cde913eceb9efca427636c59eff76a8b036f68a15da05fc11bb20e3fddbe7c9bc61d84ce75c41c52a6f8e0b06530fdd20ea78f00059e9ebd5ca9b55e1b08cbaa76ce39adcb0e25a53d813081971e3f182470fce17a69abad3f5c2f5d9ad1b9e43c82b7434c690bcd571cfae898a3b70e6d4926a48f3eaddf28bb6ec1cc7195e3bc82b5f98a7ebf883bc68193bb9cc33edf8f5a3c077b7a068e9e579646a3d1a0cce6e461e950a5e851d63ef3159d7ab4699d2eba949c4b2b4d2e4fd28f7ca6b50e5d46e02c6b3fa3e9569fb6466c03ce9336693f387a9e343ab1e822a55e673a8c968215d661750ec4b5ead03d47b0f32c4de35187f32cb1632d60a62d2d3c3a64b180f9c70d971bd2da8fc20c81ab5327a1086759c653bfc1c2625b5afce62526bd75f716b0078cbff80b78d3d26225ce9d3277e9d23bff70662fdfcbe75f44add04213b2ee0d25087e20ece7bbf6fc8bb397d802667cc3596eb41783e2a42e4117afac2fcba1fca3fa912518347172c5128a6281f3956561b9e12c2c6e33652c0e63c3c55b2d073dcedcb3bf74b5c016035efc7319dc4130071877ffc01c8af48071f71c70f88bc7d90bcc06f87d36f365dd3fd87c5987f960f3c5e22f9fcd34cfc272d9e22c2d2c2c5f75d697f15b1bd6e2fc44b1bc637d15b5028b7fcb8bcb0d67913e2abee2feb5acea06d8b2200bb2860458e53430fe3975697b71ea92baa4ee381cf40c2b02bf148f287a3bb4200eebd65f3e0df4dff068d3281650dee22c5f8bdff8f2cddf68f9327d16972cf076c8d2b5907216ab05cc3842dfe2d6fa6571e944be931e8bb758dc1d67f6e22e37401e39381cc65b2c608b012feedee3c51dbc2972c36f70802d16c799b9dc001dbc81015b2c208f1c1f29345fd45f1c67f602b280354e60d03f8f71bf29d2c31d87df14e9f1e2ee37457a8ce02f1ee70b06bc29d203c663c09b191c077853a4070e87016f7c80fe02deec982fea2380493e9e6391e798e4710603b21c84cd17cbe3ec05f4f962798e4a0c88b31cf09338cff296ef47447ac9627dd5593e93df387917512c60be7916d84b7ff1ee45eacc17c5fe63601c0ee31fceec8a23dfb587e3c399c17c3833ffe44ce28001e37cbd7c7136adc959bece575f966f035cfdca97a593a7aef26579047ff9beea4b7de075d497bf7cf31d088255de5afa4608bcf77a6b7762a13a975364d6e84aa572aaf26ef5f97ce51f342f3da216703b44b9c43dd8555454babbdf44c34befef935256b06337e1942f7baf026d7ae5b90af4d487571d6801993f813554e7bc5e535f6f99cf3d668316907909d670fd8231a1d721f51715587af5ead9955961c7ae530ad20e61b8ed52173980a3122b6edf450fe0a8e4086d4aafa78c5e0fec2a0caa1c3b066f30ca4b812d06ace4e08132e4a002a6c4190573b43dcecbd3a922c905705126e726ca5ae1f00c5cc1fc23223d0563f37054d3278240512b620c23762006921a0b8488a090628c2044927cb0417c760c9d9a24694815cb5062051353ca4725562079eea3122b8ec498574cf1e9311d4b016193d216645e767e0259f759a1b7773bebbbf1b17c36beae3f5687d46146707bc6f45b39ce2c656515fec953c07c7dbe4ebed211e9309c8e0202ceb14806cd39a3f4f1b13d75980c4be995929eae6ce0305fb52d640af229751bdfea8b71e58bb1e82f0e53c606159f0e43cb6c388baf348b8d6fc77cadc0d9a8dcb5cfd451ed1295f24ea6a4a4a4a4b84bea285739c6790aa4be0aac5bc81250b00e512850a6c31dd3947fc0e2bd5f8e4b56ff24ff9091999147b84f5af44cf4c053679a51a27cc7b227bf5fa743478752a660bd7a94329bf9fa017ef52f70f53c43b3820559334a94990ab4a1c6098c72cf559ee245522957798f94abc09b22359592c27196721c25ca2cce543278ae027dbeaedb7494286f3365edf182d34fdf0fd8d3eb204c103d20790ab3d65ab799d77fc0de9a6aa8ef9e3ce538b3ee3a0a6c9dc0937be04d911378e3f9cd7594e3cc6ca689fa9df4bd057447f8bdd54375ddf5529ddfebb09087b37080be73d529eb70fbce55dc8d1cba26b0e5c2f56e8795d6e460eade7d19766d66077bceef674da7fbc1886886bebb3a7f5df575b7f3ccfadb5dbf9d5f577d9df4fbfdc904847e5fec44bf778a6fed9b3c4720e31c799b71927819c4e4363d5b2e608f0b3ca344f9bc9ace72750cc7f465f04dfe030472ddf465a9f3f7ad75f2368bb72613f51a6f98547ef747547bfd808c93d21863a4534a4a9974d25afbc5a65ec3c0d461c079ea281728e84d193b25ab6c1b1839324a628424a9ecce6e856225c1322a3e5809911e3ec77c4b29654c8e897987b5561a5aa6b2f90032697a7e2a2d154d1a6966502c2aa9edfca48931464943236d38bf2e43bb047390de7ec11c8af490de397026d86c314a601edc0c53d68aa0758ecbc15e8fb3f899be70bee6b44de73e9bf99a289a0ea7bb9c6d991881912d8ef454c943a3d186cca4db948eeb7285e901abf88adb9830aa2fd7c3712b475c0071aa5896b92ac0304fdfc10459b98acbb0721550061b2e69f3c52347c5573ca25a0c507116efa1e22c2ebf1c722822411a549c058cf375af09ccd7e4281083f3a640f91d98f1d753d2143a20928216d8f85a66ab2fce246a458527d6d868da292dd36d994016fa4d2d4bb9f9f811652932c706cf9baebcdfbeeafe887215602ee5ab5f754a6faaf66a43df9ec360de173ecdca601c06f47fbde752c0c0803c260a15ac8f1e92e7753e915a269b36259a53f4d4e3ce533a979e52da534a799ebaf7c520e4e78bc18726b658cc0ae1e9bdf70ad10ba81cc87d15ef8074ce9deebdf7769e5fceb3a1431c688ecb08dc49419eec3c4f305a4021cf6508ee514ff5d631937b2ea174ccbae752492e75ac730fcc55a84506034e21d90bd8492da25e72d5725c8a9eb680473d1348082ab88af07ce8339e4d4ef79ba19364803de73c1baeeb9c3ca53db83a4d6cd7790bf4bbeb9d5fcfa977ded7b25133709e4815fc1a3cb0c579e79e9fc01607be209168b4e79c02381ae940f9eb9d0467134ae5d0199509dc7975ef400dc8cfa7ebf5e321b94e5df7bace73ae7a9e9fbcebbaaeeb3cce4f9e73d7797438c30268a0e1ebf1eb0f1f8d7a68f2f49172b475f2a9910f3f4fa7f79ee7790bf4bd4eea58eb85ce3df7c01aae77e00c1d52a72e98fcbac96fe702dee490d40324662e37375a60940417b31b188f28195afed27d5d44f568f1229ddfd60b26effc012d7fe9fcf479eefa8a74ce7d8d62e17ad749940b2617ae5f9728173cbfde59d40c32b4b8cb7bb4fcc523ea012dee026f5c5c873e22891fcc5c6ed31a9857becb7773038b1344b1cd60401d547a20c6cce52fdf8de7c5ec46070ac5cc05163179903a2b62bde56b6d81a99b3e1c3aa4ce83074f651329bea84f1d44598bd309460bd849f3824927209473bcd2e1afeca4be9df4f42710096be00944f3e48733719d73ef3d367127ce4fa7dad42f28511ef6ebdd07d6c0f975aeebc0ec79fe21f327e764f7755dc511face6dfa640199af60eef11d5883e779eef1553a9865debb1763164be8c20081f3443add7b6ff49ccbf4f344ca15fc21f3d7397002c94e80b314ea72bc75d7e5b506ce13a86329a73e835ae6e2b163f4652d38c7c55b52a7947b2bf92b4f29d279cb17c467455cac16bfacaee5b258ce9970be39ee5ef73aaff703527f82730893dbcc667db1c31b5e4d36dd62c32578dd66def8327d16b06dac3ce52e5f4ecb0ab480ccaf8035a43cc555be0b48e7fc54bd937b9e075a40e62f58c3c92f386dd349ca1773027c3de5294a533865f2940ab79623709e48b349a5913c53c60304ff497ee79ebb2cc19de78994c3894487b8e06f488b9b3e2924691dcb710163dfe897c267805842cc413df5d9744869a66c36e997ac05012da014ea17f53c9f3c75126ef807751a26d0b119e4a3337941dde9c45170da93df7befbd5647cb728a9c3c88cf8adc1308cb323dbe73981d1c3b8c1dde6f8252e8fb21f326500ee500f3e8d1086871ea3340405605f69cf389d4b15c63f074024da12e70cb37937a3ae9907a4369f1ebf422a0059c4dfa756ba589d42195b4db24620375202a7f28094a387bd46792938ee51f348f349b746c4787b68e5d50053a919e529a89481a0ac1d23b1ccff33c10ccd7399726fb6d397b64a30de003893fbaadd6d276b52c0040ae6387170317ca054b62f4123a6590185d8cc934d65b6fa5392693775de7b17a2e23701775b87c1dd42fcee56739eab64a9a8d0af7729ecb75e1bc3b9d6ebdf5ba4d535f5529e5aa5beee22d3758408f20487324389528cd05b8813af68269e8af73de412d9b368f071687711e6ef80bcbdd7968711c1e7970f1cf7968b9cb6ddae6298e111c67e6eea0f77007bd3a88c347006f46701c7e03ba3e07bd87cb47f0cf417ff95ad5631c6706f3b52ad862008cbf78f597178f016f5ad5f17c7137ad0a5ea0478c03c01b10673977be38071d00e0cd0d001c00609c2fce637066308e33f3381b610430ce4010e6fb70e4971d88fdfbe1c0b83e9c59445dced621e73041e00972be023ba8438ef3950fd5403044601e4aa3391d3b798a7776e53a5df9fc32f8179c6e3357a0ca57befc037cea534505cfd5caa2565899b3da58d958f95cb9cd5c7d9eaf7c32afe22a608adbcc94cfe4f8537da9af7bdcd41db8f35c873994d621e77408b879a68cd2fac571d26509a6ceb9fcfa5e8ea643e5ad3ba77552ae23707c5973faa97ea7438efbba04cee1739c7cd221572fa551de94963ae4501ffdbad29479fda2ceb94ca2ce717ea5b73e59fbe27cb548677d71be6ec4f9aa511e8ca36a98d8f506297b5a0bdee830b30ce0661664f9c8c3e7ab8f563d40797dcbb02da41f8d820031a9439f65be55464176f09926465b8c75298d65d8e4fc7e4cd007904859328c83db205301ce3288170d67162ba4e12648dd06e1e900a4a8c227670925487082d904f20117459624497a810aa6df3827d0bdb54f0b49f07b80dbe767e26b2a0dd18e084da529ebbc9daabc33a5519e0bd0cf4d448dfc6d4e97608641f5797cc517f55c46e017268c07eac31eab566ee8bb87aa12587ae686bcd6d9edf2766d361c1943a0c962e218d143133288a01c1931c2250a2fd41041101143a4600b5b0bbc2dc8f8c286859204044584706504d199a66e4293c59dd1c6c51c91623ccae1f936ca39a26d321f8d7670f2f8a3d10e4c4e48c418639c9f0a88a6a3e8624b5a777cf5fe32cd3715c0d3d753991c19f6349fa7d34f664ae9f4894365379192b375d2eaddb04c1853065e75d878842666123aa2d319bff8735a1db1a676281db0199a8e4dc7f42508336101a9cfe3eb3aeaa33cbcee641a62f2f90d617957e095734dad388221a1e2080ec91430484d284e78d105120b7ec862073857404146115ca421c60f0a2c1a2968d064714d9e3d230741ac21c50c867e08426896c3ef3098a0608b275bc8008bda2cc3525002ef68071beb63582e67b9fc8ca218c4c00659202d1d8133451557b4a00a1aeca084892e38272a4a021c2b5a4002237888b400028d9803244c20460dcac841171f9022050130a3730c4d11809ee5218a882eac8d745a6a4bb2965e8ed28e72d5a957b0063acfc0713a994e2f7732791df8c1626867dec2f0d4bb90c2789c3e1c5fd64d9fa5818bb2a6c4a475175d17e3ed58a6f68bd3ed9c94eaf00dce213d95064433c22089153915091485a2bb78a28b28beddf359d3647177c0e0003282e8e0054d82708312cc322ec210f6f6383b22e0e2a70647d4d01941f75149117ae09ab83e2a591ae306934ed2faa86449682ef9ac3e2a59ca8952088e4ad480825d3e2a51030731560009e9ac9689d66be708ed31363bb84f850ee5910fd366470b2a803b7080ef47256a143d974590182e402c7688e182aad031f9ed4ad4b8a259cefaa8440d24be7e54a246cf67d810643e59fda22bb0564234705efd8a83df2bb1267678fde4b565d775de42fcce71fe661aec69fae9c3f93987689b373fd0c926702bfae9bd6f45301a0d01848f46434001422aba011314ca38ba01cd064d1050a7cbea3c3aac0e4b034f9735481fa2e333138f1d2d43e01d33d0e8e8b07ef261e041c3043ad696da7458dd4e6be91d1aa3c7d239e79c6ed3611938a6baab63acbfad7b7dec00526badb5c6a5cb755b90857609c2c22f308d8e5a1d8535b0053390969ebde7e1a6441a9a6930a12195451c1cc174ec50428439eb8d084284aa2333a4f81c6b6940f91c97bcfc53a9923fcbf81cd34294527e0330f2d3e104ea704eda7c4db7e9962103e7f0e76c3a2c8c350178f93dc0942ef958b323e6901e3387a680a9a48e0e27cd844da129243bcedb8412639c13a8c3391304eb700255a1ca537d826093086c0acd29f4d32bcc66078cc68bdb4f289634f13322a163466fd84c944921e20b96bd0e36fdde0c06d147253d203d013e2a01c3873fd5ae7e35e9667d9c15939e34a5599faa549f5428a99a948b96fca44af549bf66d25caa499446693589b37149dc118734974c9fecf9e9b0c3e971926765099c6b524df2e9d7f4599bacd993474e6f5510ae8e7b5aa60a7da8c3698dfcb4c99e7ecd68ab493c1dcee93cd487d2a80e0d733e913c2630df3b39b02681b70e674a0c3f0c4dafd33deea6e2a28b31ae2c3fe5285ff954be942f461a1e0566ce81d44f79ea93284e86850cb46740c328b0f0e8db65666831eac30638df7bef2da3eb1cafa4e74170f67a3c2f3e7f38778ee4bc3d7bbd63c50fe792a7773aecde894ce0ecac1896eb26050b36c35a812b26cec88aa201a689208c089c59b22a7d8521d5d5af66c160713543e7172b108a018c144243580c0d19112262a466029ce950fb0011e908e1949f9a1115d46e4e88906e3c984ce087116699719329c03419bdac33b014c2354a816793a443082cc1b504f8032788418307273f78d7c1c232e1bd3cdf2ea58c5f7c0cc84781089cab9252c7a257ef9b93b9259f29cba94a0860e9ec54a5ba6473ac4bd40aab0e33ab2ef5ab9598c0b977a8ce4c5a1ad569409c55a5dc3ba6bc81f88a77f60ecfeaf258185c14f8d0e2550367576d892a88c4099b2a174081b317c4c8112f42ed04459c07f159115a2d06ef8d36f37af29b325c342b3a3ae10520c039d68608d2b14d800a24b963028f0c22610832fff0de358f626e8a0886e80d5c3e4d199946c70e2a7ebe1d062654d25813c1160ba7975508e7583491fa863bb8bd291891073de21449df4ebd98b05d32ea50c107dc43b08ed6118600e799191a5b0f4c31ae298c7cbb87924746596b489d2613765c77099c5d352764aecc50d1b7c368661a8576f0079c2f0a9d23beface64628e367dfb094e603f41099cef1f0581d2cb92c2c9b7bb622e653965d14b592e168bb2288ba2006796ecc6970c7d862583635e3706527c670a4483a29114404d443e81330592350f4b66864787881013c59295cc142708c1f9fa60bd8893389d9bdc178f628cfac4a83a5b15bc21877a82162166d19e6449f483c8d2b7dba0647b1652bfa20267b984a846d4592e398a4e56b23370f63c6c23c24be2612772e4db63642cf6dcf33cc6555ae0ec55e0ec1e23d333adc389e45cca2a82b337f3d325c1988a83cc5bbf710bdc4e82109cbd34702865b49ee74d5f0a62021f1b7ed4925427408002cdcc10387b302238c312009b2981916fc72e5c0185126cc707305b1138944c749e71549ab488c4809882fc8925b4c7964b7c7b1e002ac4f7b27e82a211143a42df493f917c963f9289280be38bba07663984464af14d53f08dc50038798398cff30c311ffd2378c3cd4f2965fc9aba7c3abfaead0e8464e88d36d84c9d78b2e89d537ae52ad04fcf61e8670e4664edf9ca594b3d2c02675a80251cd64ed3ee616d92000fb38634f976574c0d7b2cf7564ac062b1d27019020a2d2f6859633504e8db5949c8d0fb44135939ea7845595ae08ca98f004528e38401e725e8910067fc981a3d2134e373b5c0656419c44b9a5d5996872d0dcc9394dbc1f9c690e78b95134491a3dde4a75d2265a7bac90e9b300bfc1252b3699af6eea2cf0620383af522d7237d6b28414b2c670b3bc42bea784531ad74d222e04cbf1e9135947eb57bde6ae572c5c4c8c8cccca024d8b71b9e353a0b5252eb2cc89f3d478ae2b7ea7009dc9e2967bb5f9490afb70ad2f97696cf89427b01b4c0f9c29800e79594a00e749080c9f4e9e191cd070db66f9fc0132c057084a5872c1a6030c9ea155626f0e049a027a2784810180110be74061a78c099c20ce119a6cc756b2166c966a542134eb0276792c0d98399024f1a73543a832682ac4f223fbdc6395f681c2292862d880267fa8e032023dc9cb45a9f93ba9521120c530467fa3e91c8143351f4ed2b97fd300b09ec853c18b284ad56c677c76a4b1049b284b33799991829ab65cdcab2699a59ebb06807b708e16c8b2612f1d5dec41114738aef8cbfe9d349f345002f70bef86f32a0de97906342eff6ed98156b30fc52d2d60baab70113e14ac00c8d2bacc90b002a70d846ac2819af5822a07dbb2b064700fc803d8cf1259d7a2fc1d5e71b9a102a8456e9a7e8db63649a26bd355b131c6225bef3355ff3058022a0ff4ca2cba99eec69147e80c0d9ab4138378fd4699f9e1e1e267e787a7a7e7a7c8048003484aed24829250aed0420024f100a3cc20f9e09a70cf30c0db67dfbca05055ef5abbd5d982584f3cc0ccd6caa4484a2845011ea417c763dc6b4941cc7711cc7494981dc340aedaa17e04c5b9ec099d282e87282f8f46914801a8576500767dc42023c654a5dc189a78f0d9a40d3e767067d7b370023b89b76eba48cfbd63730b7d24f1a2d4f9f7acb41039c6d9067877c304a42e7db2f0ee2b3fa794d6780fb46a3d1809821e142e2474aefca992370be66dca4cf2c560a4cb0f418eae8d09c7cb74ea3d05ed3853423851a900067499b4d04080e73c618638c52c6e9d52b0884b68c00e70825d208453a5919f9d064ad94dacc3a25edc3e1064887ede4caa4ebc99b139d9cc598962670963a4b9fbdef20bebd958c8e903d1e6b0498e65f4b3a50173b80a3d1113b7149343222c9cb1585c9486983427b4c0ecef7852286fa1589708d55e4472152bfe20b45d8fa65d3310801ea982925861014755d0c427e6627bd6e62fbf831f860eb5864a253f4f1533108f9e958eb049576b28a25a210824c167d860e1a2c98d18c28702002ea4dda1a2a1032a39f4c16b3ae224089a423b3ce62f18310b38ef3748461c60766ddf56000e5895977bd8864872b9a9855fac57c114f0760c10e7698d12fe68bee16a3a763ab550096c0392669cf9e0a71464727835934d650d54393911039dfb567b231ab558738dfc7a11d0ad1c92670fc485d2c21381a8500cae75514e2a18b1e6324907ca263364fc7f264e2a54b9f3d1db343f014ec0a677c66088afc30c41b8500e8b30c8ce6a58e8f73f6fda5a629c51cb26df3755fca6e9df98a2efdc61adb3cedf39108dd05daabd3be3fcf9359daf7d7f2bc3fb6775dd7755dcd3f56363352aebbf7defc63c5c5d8f909753dc618633cf94549153570eea321232400e12c87961c69590c02110e863c61021fb2f86216cb30d2c58e114a5cf1248c5996495ec6d8349942e7d2bbd6495a26a12c894b1a045226a9902fe9a7938a8a8b0b080a4000279cf0b969349fe5d0cba6c9215982fc8670efb42ce5d24320706e9d3e7dadf3c53645bf3f5aeec42c7d7222573ffad9d0210e1dde504bc49eb3e5a3cb185b4e2f210a2ca7aca293fe2f65a2c0511aa06354d801c38e29f34eb1430fbc37a4a1f387fd6cf8804cd9bd76de6779057d9c7005962a47e01c06499de9324a389cf14edb958707bd1763162b4687d944f7648d26647928e951865e155846d9612a05eebc7569c5cfa75c48b11c67967a800d67f11e379c750394600f1b2bb761897c44ad2017703d45b5e22ac718a752296f217eca71dea6a4f8ca8a0ace4b1595a7a8dc66e28f76684aa5be4fc211b23f22129e27cf4d5726a52f149d44920406d2e8071e263b3821f2d173c0e42308a45f7a0c832841104c9c3e1a055144103b4ba827b5c9e9c81a01510382880342e802210253ca472320764e42968c8408e1a57b9d173f2321647842bc403579e9de54a283b1819025498c7cbe443e1a09f9f9ecbd91909eef3e1a09f1618ac07bce2808a28f46412479e971842efa8d1b481b59f42a8a39026a44f53e1a0df1c34bef6cf7d14808ce4808345eae7c3412c2f6d23b939110472d174e1c781e3a181960e9d1730918ede43cf7d18888a18f4644007d74fbd18888249ee5a3111139df804e3681551f8d840892f1a95d5773c6cc09baab89f0f4e4e8a86081775a366f4bb126526ff1b95125990e512b5039e78c3748fa03c443959672d80053a73add4b5289e65328d6dca66c3ae9179d3ed8a713aad321dde9900798ea7c431141ac699d2993ddb5f7722e1aa5d11e22ce4ed9bce9dc564c80a353aaf3d17053afca9f2d43e02c956a8d712551edb6656d75aa4379e80f1532723bf2947a54fa4c89d0214a446b4fddd656ea589e4e6ed427143a6f2dc8ef08e5b3cc71d2b1a8c40c1e5e004f3d4bda53181138cfc8cac0d9f5140535a4924ec71a6ca1d5de390dc6eed941210ddc394a4b1dabf488a58c941876843a8cff7af9b33567af78628cf107dac718638c31cece2e57ec24a531d1a78b312779a21d06a88aa8444d3576492c95a281000000000314002020100a064442c1703c20d3335df80114000b8aa64e78549809a4248831858c2180103040000040000004b37103ee0050510152ab06c334aa23d61a64546111202742d4ca06e3cfaac7a90344a8f04212d80820e20f4ee00126ae7d959e5b2c0de2c4a99fcfc2af6d2bc199a74a112ceba66362a4c6692c2b464700285e1b5d2c5caa73e1fc2b447c31b8025c30e11d78ce88e77b68876188c5100625c7b37dc2f8ef21c7b3d7efe63ce4144600e2c0b128f45960a10407781735813125f7025d69533fd841f2141cdcf403707a14ecaa26f3673b7ca4a6f1738fdce599ff8b605585e5d5bbf431e98375945d3a542a263c214840501aa76284d45218498c0bbc25d9db88a4c4e42e7370c88cb609ab9e6425353864c51d250e9f711dd3909008baae98d7747ad8a04073f0770f6d39c05b8359b9e6c009b0808199b5a985fe988c654613e662fab8053de07659379d31e6c20a8f5995c9da06e6ce0449decb1e5cb54d17c5c6a5be6da08cd6f42a841a772ac762bd011b71b5f2c0603d4bf2489691e9613efe665c634c2afc9c7f3e1fbdff9471c6d2947208ec35e9bf1823b90e219e0dd02c9a9d4832958a43d9be00395ef5fd04966a95bf49ea63fad357b79f70bf321723e288f375997f529275131e00e5641c4a8a9de59b9c1a758231767b99146eae5daa0980e58b3e391cb6800a733d64413222feee04385e70245bae7cf54816d1faa02de8dd4d5896f9403a9f37b3dfcaa95402b7035ee707782a509db6e11c7ee08d83174bb352bab9a11683b6bbb8aebe31e80aab5367a98126c254ae02a270cf934ca401ec686f36048802fdae075a3ccf348506364c44b653ff0f5f4546796b62006f312f2d7ee830e8721bea310344b6652d41ca221d55ea873cdfcacb83c81d3d064d6c4ddb0101414105aacb80321737ed9033c41d1f1fe8cf78cd50e3c0ec886d0d7861c992a081d7e6e44dfad88d61f5c350675e09d56695a11ba3730ee49567a11fc720c6af0273dd7f3d67cea5397c254e1dc4eeb130906ee318b18320f86497313056909acec7cbec1a65d69c9e5e184db9108108281efa1b2d7a2d2ee12b9e56ceaf387a72b0d45d2df7a8b08c01f9cb92f5668101ca2852ba657ad9c2adad6e93601f029592caa243d8823776602c36c391773654c78148246ccd12f73f9bda6a597cf2780602082520f7b5b7ac401aa68714b0c69862d6f16fd67a92610eee9520c9829aa9f69e94ec0c1563c849e6d68d42530a068e62fb8a6326406183055f5dded58f34f077e81171fad04315da01065b5c7ce11ae9988b11d760593cef453239f68651b203ff84ce05062cee38463a0438ce4b93c6752aba5b9c0c023e42cce57014ced8c187d6d670de8edde4a00ae52ca037d4a8edb868a01b26bcc6fcd7c8306db2e9036e938ce735c7327bb934c1ce4b8595c441ede18bf79cb7881dcb074ff53b8319538201accefed1ab546aa3073084d3c7a227da1a396297438cc3365cc338e3bf5213699c109569d419ff32fc462ea1c311fd4bd645d45e4a5f3973e447f242c0767439b8c4684132471bd6cd64cb5696d4e590b356d8e2a08c6449362b68d4441fdf3da16c093cfc225f724b13d4158d570980de17060bbf7cad3f8c67437b47ccc0e72802ccaf54326c048a442092f5628b768933c6d18605b1f6d273e112ff9a9c73be1de17e188fe293965a0a245568b9a01b4b76c0c0fa5427591ab9526e5dc1d62f2cf9c0ca205359f1b1768092db1eb9d3588c76d1080b46064721661790c5d09428a0eca93b977b7b145fe4889eaf4031117ac0b453f97a8114a5da362f1025fb04d1cd3503c491d1c6be2baa44617d60c729dee4cfed16f131d669f5dcf5bbc4d00c652961e3db9a8ceba2b399869fd52f159ae6a4a1047338cc02b153feb0904d14a73571ac440580d03097c2702982a04028420bc91037b26891d1084cb7539653767994e457bcae6e0ea63a00c65acc52628557b223384a6c4b2034c3bb17878f65e0d187df755e2b6460ce0ccc2e00cccecd938911a8516e994e8c3b0920bb24c7e2ffca4c6075ffc904db334e92f3482902010bbd6201fd48503c558ac4bb12d0e9d2538534124655ecb5b6e8a72c7df1bd7f87fbe98873035415235cc78805fb255c6d28fbca8f0f5b40080f1e9a127c855b34f9aa6fc46b2a6e697988bdf0ce7428d7a6bb9966df413cce8facea4a444000f7696c23e694213f082544587069820387238f2c894804beb80b7e908713bd85bdcd89aa5e91202e167b8eb94adc07c1eea8910e16051ca767e823c44eedf40eabafab2cf84b137ccf5af3538a3958ecd5463e4ee1417ee285ff7175cdb4532cc83625c734bffd7bb3be61e3f285efab380eaac44d0f49a331d0641bcd72bb3e87dc444717de83c2109a5af02c712bf80abc1a2086ad3418a9dd0979098163102caf714fadf2711b271cab8f375368715fcedc1a9d079934f2007d2fa51de76e577ee5b8cf05a8be1f325856dd0dd339d471e32f7472eec9cd84eb2d52ccde5f7cc7b2e033bec4a3ae5c96dde8cee2eef8090ed2e24b3f71e45d2908e3a1056d92d6329bb354cca141eb09c1796a336246b7c0e73fb1f29328267246dd18b2c5f75011d25e5ed878d847b7b3305cc593cdf880c6fb49dc17013c1b36890e9c0d94ba0e80d1c2a92ea9f6603bac5f690ca49eb9e58473c0e76f8d06ca4d8d25b311e3b6ff5ab519a796058ee7571795f7d8e1a54130fb45b72b3cd18bc1562b56d2814ad3af199a129a4e4dd90a337c3f341ad1041f60220acc8c89e17ca2863d2f6c39c4183883a8335881b0308b09fdab82fcf72190173b211eeeb19091f2c45c5a6ac3c68e7593ebf2b54dffd25c4d30a38d31466eb081417f577e31d4586e7f51c5d09a5c76d9c7516b95a077ea4642e113a68935ee7e033dd886609219db905d7ac9cecaef18260ad6acb097e18c89833c4c1250d6262bd868240ee9187afbefc3d0040210c233181857fd9095989b0405472839ab7b8cc19c55615a7f43167b1f8c4ec31957964f958ba4e1a1eb520d891e1875f408f45e551b148a3d580ab017be918a9d6d17250de757c37613ca89d487aa5d1b9b766d6b15e776caae3fd9f997a67532ef9fa13db9782450388d4f83d609c72f2d30c4ed8da171dd920b66b44a1998c7e2d2970c2e3706e447e1dfd20b2017ae68dfa0e55a52f8e857322258e918cc08df4465f61c36e439bfb974f9ba97018a9f10aa0b28475524b6f23e4a293579df6cb49294863ee02f480aca2e94c19caec88c582db19d02f1530bb042659a4c55094496527cee65650e01b35838aab7f9661220c06ca8eb47c7b442e53cc74b79ebbdcecc23a39128f46485365d984bb5b5c3787cf5f850be7255aff7eeeac9f8f486618ea1161394aa4cc05d65f4203c0fc4a96f69e29270a612ee863c4cb5e9311e975433fe6323c87001d15d960caf02f091a49f3e92ae46ea07761e501acf07abd378431fcbb1d59a1557ed3a8d5bba8b12bc973a466871a4f276b889577f64198d924c48db0d8cc9228727cfbccd64067035c66b98af946cf6f70157bbca51038b0b3a1bac9a08cc4e28e791420ecdec0c1f9eecb1f6d838addc70a46c50c9d776bcab2003254338e31cc43a71ff4f0ef3ab47c64560829bff6561ca4ba26764eb1134ae41aa8cae0d43098522c22d8aa26daa3de11546bce548cece909984ae6091cbea58a942207a6c1115093e023e15f9a7b751844c08c63ea5c156235161b9981b3f269864441002772b2c0aee031803484531f8730ab97a9e0208e4464b7df5a719a8a494da4797af23092b30aad71b47b0113ec2d5e259cc66bcd3df2d0478db57d4325c85aace7bb6c7eec74a2f3c0813efcc425e04460b2f18a20e44d2f3512b2159c750364463763335648ffd7151a2cdbcb203ac46a7b3037fdbe2dc6d47cf62a83fc0fa83eb0804d0a05f2e15467ff1a7d509004b78825e573106ef4bfb3af903b1965bb0f1f596898d22217e2465893ac3be1c1bcdae629fcdf952750368a32dcdeabda2675a3f4c4a8502464b33514cffe2ac59ddc1e97cada82f4ee2da428b51390916be4ea29aceafc2940626e9b7e4955a0deec73ed1db44e36d4fbce1fc4b69c99355e79a9a18f9e8f058db6498f84193e9617dfb1f552b2e78c75256000eb1c4c678e3d8848eb658483a652eb1c76a0f73554ba2d6ac637023d56b3dfadae89204bfb5b52945b5d4571e0c08e06db55700cbe9e8f2e8d175d8e915c821e56d2252411583cc4830491e6955733804abb6514739529b653dc673916416f2d4c468d31831e1236b78d53c5c69fa2c52809784892984983ef5e864fb0f3c09359a8380d80bb7b881efe345a42b6a0896cc84c1ce435ab9da8aea5c67eb78bbd3fb19884c10011cfe723fa460b342c13e9a588a43d7fb4858cb45be62253bb128d773bc1da5a379bc701258f54998781b3aec931e989b0dc7ad654f0e65c179d560928a845f8678134d7b5180c90a2a977bf49e7bd5f4380bfe294d177ccc8bc1c737bb2a81571b9da00fb5f239f4738b9cf21cd7df2664a89c17e933905784afe36d9d2f118266dd3594ecd272917a1850fab9f98bdaca79a58889a4907dd26f854b036bbb6e9e390bd091778b398a46de5e56cf23c1f432c0c7302fb9f8350b1afe567fc755ac097649b63695be4692298252f80b5066ea769c4b4dc1b1bf44527fca7181206b89a64239fc1ec9d61d3d9f921c1e63c8e5c14d77ead88d5e692d8403edb2794c836017e9cbda4b58678c7462451392072e63adeb2f0f62ac3c5d7f8ec695299871e1eabb6157d5d2c784260332d2e43a25333a309ef325e83e58ca0b887dd38e7c99056f20afb85a88d2a3c44c225fe14ed11f3984caf237a2b6438131715e4605454345426984b1a973da1e067c807fcb0289715af2c2216d9b9e954017a5c44318a518e9fa1ab14ae7eb4b850ecf55ed0bfed1ad3011222340e6335c22459e1c275bc3d7ba9d481b23920cc854812872c176b3c4e802884c8787ac493eeac6b13ac65770cf3b3dce24e20bbc1cc9465daaf5662a61420f5ceb9aa552bad3f5c5ecfa1b90f1ae163a043532f09577a1b9dfa26301a225382b0fdf62ba372c30fd94396e072b0d8658e66859cb1c11ef884a6ebdbc8b252e9b2d8a93582b52e5c3a0cc42cfb1a27f3971d257750b955793f3fb1dc4183fd789b8b98f2b3db5806dc3738cfec46e1a60a7d046aab4ee6d18089213a3ac84b7b38073d490d44e9e50bb16da1db4801e9f86de6700eecee467a40e69d5977ab238a4c5e02c643d5ef99821d3059e4fef732e78ff704f0b78ac2ee9b8e91e7d01c3e7278f0788366b82e479cab3444c365053937605e15a1a3a8fe424ce16466d1de658630229131f91ffc2418691c636071ac3be53bc5ed47a26578b313c5c45b9e0ca4a2ed4069dc0b6791041cd9e33ad45cfb400dcb8a8daaa32952a7eb9781b75f8d5f3b7b23274ffc1b040d77f469aadcd4a3122ab14554e2f97cee01aa3f057e23d65b92a96135e9a86e2430e8d25236de6b62cd50530d472c983735ed2423bafa68b5e93ade1e940fe34e149c9a3163201f5203a696192a9dd5c02864e8e446099c1f80a77020c29eb971be91c6ead19cb56c4b820f8ddb653418531df01d473b52e81141600e820702fe04e775daa18eed528e96aa7ce3ccff2c8869d33d9a6d5e946bfb90a1054bc738e247a987b74e6edde584a314e4fd3e53a8b781c1b530f70c954b668647c45dca929783dde731169104f2b27652d050a09ece1c355bff97d541a14492bb318c4498d0a8398e2dcf0590dff460b9dd1c99952bcabbab0b6f472f045bbf578b599552af5d0c981a72f5d0eb76c640ae5f1f6b9920377c0705d66e629dbc95deb6fcecc10df296f8c0bf807ed6370c403dc18bcb9bc7def68c02d7b9bd45ce79cd250b5a52c782a58238dd8b6ebc502bf6fe30cad61e0c53f8a9625ff34dab8def1f4f73e0f4db2affdb8e201901df9201a174a9ec27282c6702ed58477d88036d0b13dc4f482e7708f7e1ddc47527ee1757de3966291e5fde9d8e5600ffc825d8b40e9e6a8873b4cb3a24214be02edf12dc2b5a7fd8f7a096f99ac2e75b09604fb4c79714efaa28e2c4755883caaa182353154626f11528ddddcf1e948e643a754c8a7b24ef3fa3c95497e9fe4130009c3f50e8214a554656647747b93855879e7472d4c1b220af5d4b1babea26b2db3819f7fa5fe5843bb6796f75b3b854d5021e737b5da6a9125908836a8cf9e48987cb502d47230cf9385ca6f2a53eb13f8081024c605a0482c7f324bd9842ebbfd58d2d1c3595e393caa1673dc72a801392a43751f8494c3b5f0608bc61e209afb6c2d5430cbf37e0558c2789ed2b5daa481f02ea09403facaab28255dc5177bde700d5b061aa18e5cdb3db53938742b531a933f187b366cc0779b100d8a88b31b92b48bacf02c33fef02af021829eccca2512b8a4eee502943a6c9f8f1e2bd3ce0c22d24b3b1a03e957072c109e8f4fae7ae602e1408454f159256e0ffca1734d3c381c01c149742b59505f221234ee667d0d7032f8612370e02a019ba30e7c05666425e1053dab3a442dbbef54797f44855968db2cfec550bb78f656ec342f0965f04f43a889457424d50c3209fbc491851837a8070648b4999c7e7b47677d10c37ed2c5a368fe2fea7deb7aebe712b3b3a7b2eb4ce23cba1d28c9e5e0a35892bd05455bb63e0cd0ac3863dc3625bb944460ddcf6cff6312af02ba738e9d234ee32256ed904b3372dd1be6814cc9880cbe24bb06ae6f3923f86097688557e8b8ec44f371275abea94b8a24fc353f243a402ad6219f29784f9b5e73063e441579143941fb28940cdf34df8b65f656a1b5d5a4b19350b1685b9897595f6a44116e617beb2fb8ed51af594508bdda9bc5c0b396073460124bfc01837ec7d3f992439b8202caf4741ec692fb1b48361300b8d8878b49effeee0e5841162ac3978c71af18ec29690751da2c81ac0c359e28c6c47101cbe743d95fd735e332e9cebb849c5f0fca6c8a5b2febc401e4972f23ae3bcc94b489e562f7d7da62711d240c80d0594bdb77c13570386e329c03731c4c633f9a7a5758dfe49350b3b4cb5a930250f49700d470a36db27a0900f745b126c73c445e8bb82eb88dfd89f4ca959718138096c04494304601d28807fbc47c9930d6bd0143a541f1161312a8c70338272dc20523011bbc6df74d3f9448f0134fca516ebc466076322c3c48ad9f1763f0a729e52f1e9a4dcf02a6e0da28de962193168bb5a2da89d9daebb4fc6f2bd590008b34685f2b79c1fa2112680f6079b92c0d81bda8f136d4d68f0346cf594c4fa916e2077cd63c6b43ae37d3fc7715028340896535ec42ffed9c6e7ee02765e6f48ec3a55250564df0194cb089e6cc49ffa38735618bc5d219cc07be041b3493d95f0af0a8f860cc3360538861a5f04ba46ea1cacb8c14f5c9af6036c4458bf58a6701f3b59e04911996b81e49f71d37a27b8cf00316ff9a47458cb2bc45e133871a15aa91a0e61cd3214d650ef4c39614f48d72d2e479f08b3ceb4756920d54103490b09b57650c680db320009961528ac23b9043da40bb283c2d110bda4f9c0e85abcf6abae02e1872036616055d511273b3710174af3bafae5144787c642583b506c1f5c171484e02cdd2e23779274e770ae4cace91ec3f0468cde38676ce1d8f94a1982bedb5f9afd02415bbf0efa5f1daae01ed8b4436de4a5a2479f7a7c63d171b9075bcfa241877882383879d07d58cafdae8f3193e4b45ce0e7dcd098f0cb46950ca25d6d7b959a312b66e1010e895d1e43d7380951a96dc866aff51f03958ede9795a42d3b672491c91107c6bf38b65301913911042be013c89018b498d2b8008b53a81b1dab98ce75cde7ba199072defe5fbf8dc0035524d6121888cb54d8ac4aeb9fbc7a23a45da08fe1870cf284d60382cbc3f85ed8d3cd4bc5e8da013611fa3b06624eea6e9a4b472e012b6f5f7e045b0e4319ab1d3ba17d497bf1111622c49a5e6e1a511b0604b83981d1efca746df8df15a39d67f32fda5d0b9f17a4f4a933d793ae81f42c2137c7893831b02f3993e2f54c94db7ec75842606a9aa289e1dacc8809dbe40b4379ccc5109f895251fdaa7f8e64baba8265dcf9090710ef063d6883024f8e8c597af5dd2ad58dbef63c84dfdb54aabacf77f52129b90c98a6be76b7bb76607c18f38c29952c9e5b6ae2e6f3b59c7265103ad299ef7d280ab3b116db06078bfa3b4fc83088c155113f220abd8278ab24a0b6a412809d48d9d80bcea6c1f4e16a66f98b6ff0cf056d99bd63f66fbb41e524e4f92e22dbf96cc17185333027c728046c5748f40da6a6e64c8fdb8df1234ce8658c28cd6e2cc0a5ba4bbd836e292a8eff93e834d79e1b9855b3d764f292f7c54ee80f4e3eabe06359eda7370398e5155d0050c71ecf27049aa36715b30c1b7a43d0175c7b6421724b2b083435e189a3127e92b5882b71339a7ef047947e287b6c3058083365ab9f0620a053b77604051f825f80669b3baa2b3218ba59208702583bea93f18a790287ddca2e7fbe82d086281700abd6914e4df5796f38bbd5a1216fb789dd94a55e9412280583ced63f477f4f7709ce4019e9dad6868aceb02a1601459e93f656b15178756cda9d782a6eeb379fdcbbd4b31eb1314c839a051a6f8179acad58feb33c3488a60da715b7263479f46a14254616cc2732ee28f926e4c5f561928d9817e49f834a71577d21601b0e2263178ff4b067c1df21ceeb28d60bb24ca61f9defb9081000c8acafbd0ecc7300e32dbf7e8c52662639e486f3c4854c04ece8cb4a0ef26fb97b8c53215de44f63f8360e61ec23450fb099870d098bf9bf177d225aa82b42182adabb53d2f880c9b03a21498c56c2af0ecc964d3468fc5f14d4356793c3c44dd43a83429e58ea51e5b62674b4486525a0e2cddef739483e5159c9220efa9b0749a13892105d895ae709c026727a828b0eaee1f6b66f182908024270406000c7bb9840c0396e1852576418cbbe3e40161d29a541804301b29ff444a858d9c3168c9d320629e166c20b5ad50365c45200f03103ac12d62f884817084c2995f136d6bdba131417aab20e4a68ae8314ae72b61108d89a3e821f14c15941cbbcd939c7a53646c7bfaab4ad6cefcfe8da4efefffec02b52c2385cad634f8b11608cdca510c2e47d737fa785ee927cf8f07750dd325219fb4411002fb082e8760bd4879489a3a2d8135fb7686ab0b8a96a633d29d7951a6015810123a82115f061c0010e504e46f99435b809a55534582aa019773519dda85db9843151f9bce7631b94662e433d726bbae06c6e1f60bb7f3c436a3fa43811a5da0a925ffca6487ce748d4402a8b42aedab7877f40d5fd45cc00abb18db97ea372acd307545cca183ecbe7a9297e765c29bc119749a0499f9c3a5dade010dbc06e3b4892ec8ffa2761be6c8074fb804da63de3f9e9914feebfbb06ba629c87a193a6df4beda7097d23f379f5be6dade792b79e61a5f4fc5004df95c07c2cdca3fed2f7f26bad6dec8051fa5e499aa69947fcb02e90f23b0ebbeeed329713a3dffd4725e7b540375b031c2f2839d05bde442a004ed7cac8d93d89c160f2035c3d06af0567b2158e5fa0df20b4ea5b1ec196443ac6ecf852512f8665c311009c3f428ba520a2b16a18bf5a1076f49bc8b3604b9e7debee2b1697936b0ae0142824f8475c8e99b062c9ef3e1d6351b738444966b4840f8fe78c03f94442e4f24494ce0eb6d8c49c2f56b53d4ac82c734a2d5eb3a325216feef5b5c9f4c44b0d168b32f0d691d8a82bb2d98f5313df9619ecad8238eedfe7aa206256fa36b952cdaaed548bece4c0a7d6fff029c5b165c4074d0486aa739336b6583cd0ba62c20917e19ef7d118936195bca71bd37c32ca12ec861a4626a31e8bf86c87a40857697f4778ef4855dec802f44414f9019ffbbdc18733d4999f0ae561f26f5debcaa14ac5211b5c661f1f233c29aae603e0df3862fc5af0cd30991593f4ec39416f4b09083ca0745c07064c9acb924cb716bcbdd31aeb1714d1839b42c37693f830f09bb23e0d415ac2fd796dacaa276b4272bd7cd52c15de93563046c0b02a39e982d03d74065d17de7f2dfa7bee4d6da88b032ae958369a12595bcbc8a5aa3ddb93de9209f1bb60dc0bdbddaab98ea6610d6c05c891ad5582e9b94dcdb3552f031b0ae69fc48c4c574715ffe6e890a8d623e1222d45b2ac03240f98fdde005e25a38f43c99fe21140facfe2ba817b42777eeb51295c1a8eec5dc16d070f61b62b58ca2a4904152deb76e9937465d596232a2948a9ee36b919571125e4520a16274e11c5f2d5947593eb52804c3b5c801d6c698b85beb33f24e0f1c5c201723d0c7415c8c0f3c98a0d89711f089e6c6377dd7e3f90bc101078a46565d46d1e394cafae32ad1b20275a44aa0e7225ef7574f0f2ae4004eb5d89217b7ec7b17414febf6e4748ae328324d77a5488973592e538fa62067d8586df9d487dedbfc781ac65ff5952134cd730946d7183901a26bf21a828be2384e2b0c1dc8565b6a30a1cec0abda730c0a3c26bbc037560c786772d7bbf17941e15e67fb31fa9290be891f8677f6411bd6b224bd57061263ef083856c9cbc1d310ca43458daf4ed2a85d807ac4003e55ed4f49d6d0136c985132e8da9a099398bd51c860db1b9d06357c16f577efa798f00426d81bbabeef053988721568a9eaa95afb31acad7a8de06c5f10d90697ee1e364293639243447c669f9be4f4b2a83be33b1620ad1a551e34c7baf85b0cff3107c77b6a8d6239171dfa8fce0a878bd139a1419d5705f7160b18733bd8c49a015e827dbb531140fcaa12a94cca75009693007edfb9d1c2b152449c0dc8529bca6421e58b2e31e7064453cdd7de8e7d959c701406c0cd03f8a9dccc21e5e5e39f5d9c06ad90240a44b2d9ff30749c829c20d9cd8d20999333fe2cb4620f8664a4c5fe1007df659d3787752778103dbe45e812fc2939266a8f70a63fbe188307ec79b2ac9fe8373054a4098229249597cda0cd5f31044fe176d25b575a1948990ff6c21b6204f520fd7d504083c39ddc319f60a6d40c09eb18257418271107998925120e988b395a036f42f8b7e729661b8cfdf0fff811903cdfe118aa73af7a2a219c1d1ec131c041ceefd93dc82be0eb230311cc70032e71bdaebb5375cb463b35c1f0bf29e72aadbb829223d50e103c07392c1d10d3c607fbd0db7e95472abc8e03a5dc9956861ca3237c3dd75fa3ecc644a6865652f63224915fbee0e08aa8eb09dabe0eb3cdc5768127fd2f2a15924e7767bd5f7b2aa822a6f59bbb07547fe728fe9e94cbce232b18685e1789df02fcd1d7107be82f6f5e7f12ef17a74e058f1f00aed4e3cb7ec68354ea579200859831b8dbd269a56246804cf9d6fecbb6b611e8660bb2d58331abe646fa4b10fa7b89f1dc5c5951c77f9a7b4a058e146e63e29f47a63d92f69da1c773ed518f297cf6e95fd000ff73481e74e5fd3dc55562beff884cff13705ccb825b77cf748c6958b3e75d00c3336ab4dacedd0244372e50c0c454d38a94f9491b615b89205632f78ef76fe212aed33ba4c692aed1f980fc57b2ef7fca402e1cc0ef75a49cd9df92c3af916865d31dd7051696c94f7292293ca65ca73f9a6a1f14e5664f8e9f44e829c879709880f74f268f19d9276e36fac9a2d823aca9ca62d259704cac128172856fb23886681fb460812c457e388596020114e66aee282f0dbcc6d25fbdc6838253ee8ca3cb54b9d36628f3d41eb6af9629f5ed27dcb513b2d22dc0b52b00c254c3d39c19aedb5b14586bbc54ea825b035c5923069cbf059aae93563779e4bed2ba0ddce3a2452b075cfd99b441ffaf83f46b7841369d99439c711aec3efd64a0181b30fb88486982104b13846525fc87132cd201f4e4948f3e5f86c2982f0312d1ed7c99b71c12af5da2be0bd614f347d1f28662c20e5948388bed461378ad1602a88fca31469175bea8b608dfa3b31b6110c6576188c8751f9600f72073f8c40716390bc14d0be344b8ac1971b7e1f698155a21e48afcf5f817608e74143471987a4a3b3a9f45cb59545214e2bcfcc56a8fc89ba02f8b8c009beb8a04ba392340c8d1734db982fef3b3f887c3b85fb85b848c33e16127fbf015a897225198bd01b744d59a9598837f90e0447f103e1b83f62b7a2c95fa9a7379fc29ff94c2aca436cb796cc038b490aedadbd6e4a7a003e817819708cb378e7913b1533f5744ab22796a6f95c6864a49dc12c260dc1956014335f51ea84024296d80217ad083789170877e04e2071b12ff2c5e13605041acfba96253c463e6b427df16eda6fe2bcb161af2ed99c72909e6437153a4a633304583f10f9609e62669a7f2c8f84abf164f1f97234b526299453dccd1930f6e532ab3f636ec48e1d75163ed1b16a89ff83a8e112fe9c7e64019cc8279aaeb0cc496558b1cb95d5b4b74c5b767338cc58c76e537c7fb607be9d27d73086290843ed4751caf00162a08c342c327fabc44448cf7a81e20ed6b08292baceed0d728165c6a299e01caba5ed54a3963a49990a0e0950d0fafac2e10a4085615dc07108220c75eb60cf001fc00285ed75d804415f50f20439f0c2dfdd6dcecdea95d0a8a8618d802562f616a48e82f761ef0e96a7042cace0f08fdc6f9a27c4ac58d7678fa537ea60060839a6af0c45e57c1c41339a89e19f63ab52b15eea97d4a7a15f2edd37001bf8feff3449b662849d4441897882855b2764734483196990d9ef5e3ef7589e6a94e00a8811871d90b1b784d5bca59663cd0477486476b8ead193501c5062b761c4bb57079d16756bc8866ac1ae1b6c1c358d885bb80f3aa84672eb43a68aec5e1b003ed7925cd2d201cf3acc1501ac099245df859d3de19ece73de2bf6c38e09e4fdf6aec516134919012c575e88a3cb5cd1f47d59d0b33802b2f4769de57dc5424a8120807ac76cf625759dc0a83485994724bb81c1e4f0d92043cc4fd803752975cbf14cd70133e08996741d5757b3ba99f9bed4d77ea58e91da5c28aed608c60358024c465a9ad04046e27199c22e402494ee13658873d350fb23956422d5d812f04dcd98f3e9b8d2bc4c69cdf2112ca9cdcc95d5eac7bcb240c975d8a5a650fc1b0480314b5471495429b68728fa1bd71fa2ab9ddc65c53ee81a34914952878167eaed24f455c81730d4f41ebef9b3614f19b160c9b1bfb95ab6bb00cf461d786ac24e31153d4da49715d814e66eab10035ad257701704314b305f745ac4725a086efca6cbc14c08d25de4084228109ce68e402d5c9a896a708b98839e5a0d12561c1e1603e068f54deadbd6082d179437a22824f458ed212a5ca93714d21a763b537cce97130eff27b89174d9ef18b9284c795a9cb8b33d0840cbfcd92cd34a370ba967089acda6f2b2656e3a6dd4e8ef91ce8f62fdaa58a108f62d7abf4a7aee4b7f04b6f5d6ba9da347ec125066f19d20d3b42dc11e1bf549bc5ef6b363bcc573e91c4fd0582b0a181b7e4dd4fde85d716765592a66deb055d4a8e05608f36e67a1f1acf64e2034dc07a9f7c0a146c0768c421d47d1ed69a3210929e7ec61b990d770bda57af71b1ae98f8dcebdabe235096476014263d05b5aa113069e784097b4d5031c828f39840fc38f0d7a69b51109e0f8c441c7e33ac675aabd62c7e23a662cf55c15bec234356ef6bdaf64a854e75393e97b08fd6ad8f9ba57f79282396a229a1ec68fa906f0b5a6f49c182a94f081d02392cccc1ec755a4d103342caff80fa1898ec62a0171c0790e84ef5f39af825a0db528bdf97d4b2540a5eab0cc9c67002830db4f8c18456d1d79823a56a93f988b9ee943eb3941c889551e63888f4c264a9af31194f72e579b4e66b7cce12f0137b81be33e5b16bafcab0d1f635bda31e4867192aec1dccf6f17dbc2dcf282054e145cfeb5e93949dda5a98222a849dc0563328c3f6ca36a9801094b8e88633dc1eb7527f8178fe08b1c08de287bcf16c8a56906f40c13cb2a73248ade48879ee77c3099823db9a8ad4030ffe9612463b1e9e0a8e53e9b8d905d2b3c8be8ab321aea62e327a7310e24491e6773c66aa92262a940b2fa1bb2c626be2b05346a15d73a32c8947fc1e71c5870bc7a48364b99417b5594641727e5eca9880eb3c568ae4d9c437d1e36fb90b0e4cbb44035b8a602240eca9223661df00508dec3212e0e9f0a529b70cc1041c454d5252e1893edf46af65d7a647baf089ef73e3b6244e1cde6c9eb156086c26de767b123e75b40ceebabc1ba81d287782027c69e5a807e7840c0d692200d236b72061be52ccf89838fbf2eb5dcd7082ca0a130765d9d114727950346c1e0bf198d290b5db1d5132fcb9dc596891bbabdeb0ba45528a529dbcb4f66909179b11742e9feb6cc03208a35a767f02711cb5a02ef1bff0366223d155a65b9a03da55d9dd92b5e56dd242a4e9346c4158a98dcbe55ae41a524177e241ecb402e226d9447b36320ebc1072660f90932f0536e0e75c11bec8340b59c16e6f68a9265a41ac018d0f75b896cc45b883257bfebb5661ac85ee4db40a5aa1258e209540bf4d24bf68a9d80bb64d0d9a6f5a15235f81196e38c11cbc0d90fb3e8053a13a90b3438285eee6bbd741271ca1b733215e941b1d46b9e9cb9b308f1d867a254ac35e6e84cee3cb8d12404c12bbab5de6ceb924c3967ca420b31818ff29539b0c6181818037cb7557844758895337e5da12aab8f21b690c352a9de50e2fbccf5be993b4c05a3a700655926e33139ba042f06003c981c189a56325bb76af9324b469bcb309db3539b5c37f67e7aabe3644bd152f0137199967fdd7c2bc2285b0e60a3913a923e530e267e142c2f6724b4336b7baf2a9217f4417d588296fbe981a565fc9d591cf85fa728cc4d0fecc9c537bc3d5d8799548be41718feb39e28f21aecf3063b7e3dd562bf4aeaf5336651da86a7b02ed4b760c6033fae903045dd6bb7621d3963f3843d6584c24a4c6afac14454362949a3a05d137f91129e3d12cbb6d61dd3e491486c2234b2d8e9d0ffb99d9420fe88a8344fb6ac71df9e8660e0ae8fe3efb9c4ec3d1076b988bd9cd57bf03ec6973dfe987b0e21a1ef1433c00d17c6b1dbaffb1213ed87e49b2ee04e6d9aa9e55b8ff12b09d24ada6168b550cc38641829bee8f0da276688e82a91742af49d39123b65c08d7d29206b0c907d41d8557cb06fc5056f7a2e149ada448818e29e95a69be5a79753d3e1c310c700b69e95f4f9e0f107d4f742e742b69a8a3b7bf3cc5611646ef8cd74ea514a30ce08d980c88747609bc8bdd5663e1f6992444daf2ce3a435a5476955352ebcc843f8b677510cd9f8fe7c86b2bbe5daea3fe845b5b5ca0fc80e0e857559f9b4a7acf740bad3f73c0c19def9a165793fd44898e040406a800a58cab0d09469452e655b417759b3c746abb48461384ac95b41973c41a034620bc004dc4ddc8bb60278baf7e0374cb893a2deb86c1479674ffc41e33b8d2775fb637ca6bdaf22a807a33b9a0084a45bb631494c8ccb3e9b9d641812f35e1c1411623b02f639f4dc94cc3adb094a99f7175fac687d1a8f2b1c54f27a46ca8fcef8a88b997a84253bea8144c5552b7a43eb4905b15650f06d4ed12a8ab8fa2264c9ec840e1c4a8481b24de699da72e160ae9157cecc5fbb5e8f6ae6d17ae8488649bf6fa8114f0ce9604bff4dcd5d8b33d984d1d87dba02b09396a561177c278061f9092e857789a2b00282b8265c105cc463bfa67d2fe7cec0a5867bc6bf18dfe99a64aeb0cd41f784a96497244cb3339960a61d0f5065f8632b90b7dae69bae6268cc299d4fa3e3414150c5d39a9e9b8b0087a8af49801beae2e92f69b4504b339bc25850cc521bc2f8700fbba9d7935c6a515f8ef27d9f6d6028a55f08bf1b736c4c40610bb9b149b63533c27ab8cf2d821c6dd33bad3d0460458ad6ca024e2b06b458cc23e5cf041e46c4dcf9954caf885f1b9e7111f94b9a8873318837c53321d54fa5395e0f8d4e8fbd11a5701e370e486cec7477c69f68148ea40aec6aa1e09aa3083b09fdac2967129f02f92f582ba2696a958c42ee394caf0139945dfc083d4ca900ab58442095bc30dd483b926c9337192e1c09bd0e8e386389e896e803b5df5c64f662a4a43d251ac83d578b92c1a863b5b3d23e2f6b705b8bcf05de701e9f1c6f9e021211965f654226fcd5cadcbb393a28edfae34252c74ca4e4be46181116e2f9f53b4ff77f1cc108c0d6919fda26743573b0f82f1671a2ca0fb960ccd64ddc15c152b8232527f6b60311afe55cc130b01c31b85f30b585f69410c9b17e77c08ac005f16873cf3ae6d00706da08da07525eba672e8b6860e58488860148ffdca0112cdd9519db09f83a3935044d8216cc55c3d1603bed2f14735413e5a43438937e9ecfe1833bf28eb4d20979308826593f12eca48ad6a0271a821bd2dc21735330daf9a9be4a5a57606413755f0d1f8e9b11e9b7fe47ef0c252f83d5ad0c7f05434c582ff5d90ef192d8148632e437bece15a40546829b4b11aad6de0a2ea67b61405a2d88242c357bad8ca2cc388ce18acdb974ef3b4bdc17f3e9e76d55b2095661f13c2b94387663c0af76e8f45e79451d865e6dc8ec8ef0a3ce8441a46bad96628d2b60d614cb5523bc2fed4d111ab1a1d41ac7fe45d3412591458733a3c7d23b65cbd1fedcd0bb40d6b4a02ba891c71a147143d50028fabb05bf1d906bb5980daccc401eadb35915221660647c3ae8fa23928d7758b7ec530aed51462ee49bb8ee672dc25e78ee2a08474bd8621cf28d022abd4ba9fdc137d0fb14d867c9eae2f2fbb554eeccc9685dae37b5e93d73ff667b14f09b4b435b2aa294162a3078c8d7097d4153bb1dc17cfa65e575af652b341f11e2d6fd954d03745937c35a7f363e650990a447a15f39619f257a5ccb9a19160edb73540f58f4b06461bf96ea3e28d49c38549adfb74682d60c1fdcde7c05e363bc0d6a5458c96ec62b4e90ef8460483f33725c047c35eaadb0849461880238bd6bb12587d0fab39ee58e51eb0eeff26342354afe03079a6091de7b3896e8e0f42bb10798af7a2d388d581a98a5bc42c35fe986d971da53ef0be7a81128a97c9615d0b1b99403a6109b1c0b12fb356bdcaac5cc30152ad4ff1739e56d8c508171abc20eae9c17dbf36d72bda3c945439beded8c631beee7193077006c6d339e35d09c407619d796daec5cea3b1d1af56174befa69a123af057550a1f488c0c9bae4dce3726ab727e83a174cfc5fdc0b3456cc06f3eebdeba42526676a14aeeb1807be8f25e91b906157d7d01dda5be09f893dc066a554f865cbdb16bf37f420bfccbb6db6274a82383c6cc0c2663bd200108c7344862f4690e92d7c8f00aecd31d8c46172a1e64af0e72fe9f3185a57a71e814ad5f0f0d33f7db151018ff3cf7144c6ca1b5654757449ea57d9c165b0386105717498d620839c39f85b1d01228153bbf43bfe1b51ba9718f0108fe1c0a09aa23f9c3d83326a65d280bc6843c6dc4a3aff32e86443923dee6e21340fee839a99425694bf86d3f27490e877f87981767acb3c407200097ac2108093e6069b0c65fc1c8a4622d8f3553ab77213a68b8a65a53b2ddccd69111dfba6c35ad64f6bccc50c664f65cf77bb85edaee120162497022348d864873679d7c6991f3ae7d445e467dbd9dcb45927c7dbe358b5c31449ecfe0d217f7913e83497fe781e4d22554577eb1ad0b4162bbb27f8a32cc61b50d4822e493bb0749448100c30102a10e50ad2c2b443654187f34322280292a444e6a4f5e38a8809d0f470cf721e29b46be30e59018c863b9e33826395679098ac5285882fcd6b6e90d0e3b1cf3b47cab42aaf44111c25885dcf068e4311efc18606a885415c9befd0ce3144f427825a8937a257d529819a237434fd7b7593e567778962b9a195e0b489f86417d63e2987306850dc0b81511acb5b605d5831861dd6685b79b09fcd3286e7a4124989648dec074f41cdefc3b6e6ba2037b3956ba12766682f2663696e53680b2ad68ef66a182d721e95d4055372b9b6f0cb20a5e0a93a9fdc03e8ea9d90c36e5c1de148378cc8441a175f7fa2b132c8739abd90976904982db0164cbb92ad3d2a8311ef7d7a02df5096383aa043cfdadfa0decbefd8b1aa7be45cb49a9b43228dd31f45018d17bdbaa7baee1d7fb6a403536d288a72aa4a277ccc6c24cc44ec1ee699e7ed54bf465bcbf458b6b5abdcf988adbbe7e7c0781b7e277908ff99487d74ddad48ad6b0f1fa8b4084ae851ef522b40ddff9c43f01b1d2a7247ef8706f58e397a444afa382ffd05cfa4a6c3d3d3e089d9e9430b8666ea02c5e458deb0c974441e73008e08f45f678334fde926d4bfa5c3e981e392949b415e67eeed162ec6e9c23a1767f33f74b7518a1868f7988f142417575a151096074d80a48b25014be6e60344fac207ef408a087a7f76c08de33b718128de5a81cdd11a3428eec14926c49d82c32a9198c5d237e62ef6ab5ff73d6e746cb6e5dd22873f7e6cfdd19a9c3175975da1abd0f50319c20b78fc1134084460c4aea6e687913fe590d1af85329a8a8c48aecfbe909287b1c46efe93b820c2da46d2d77c6c39ef309a2248c219771cf8a138e448ec2a5efcb89f01044d6bf12da439636671931b1cd53eab02d753ce8604850a5999f4bd05547d5cb57e1081290865be4854ad50098b2c8214c04a39b7ef77d64fd9c83ceca301058494a1722bda91dbcb1ef063588686c82c4de671fb2e2b093e40ec616f5e640423f96bd3fab2b15f9dbee2000294b473e099de8447d0eb5309ea2af755c6023379bf91c3151593bfecd2c0cb34a8a97754f398b8b0a4bbd231c0bf30bbffa1b0c908dcf5a4696a517d7f144fd8ef82ec784225466c9b15f57c69449cb13652fe15378ac979f895e06751ab406b440662ef70db2132facc6ffbe80542c9059bbac35650ebd7b222d98effb024a8f0eaa81f52dbe2a46282a76159e1c2bcf1a6cdf3511bf8967aabab2e6ceba80b5cf16aae7f813c02d7b6890bd403533c24ab3841425c05bd644f4372934c403d37ce0cabc570eebd8c4db011860c4212206ba48589d8dcb418827efa7f311d2076566114f665f5cbc00fa1085af326100154a905e6fc322015ebb4d4e0529c7a703834e7eb6485dbb687dbe2249e701a27bcf42b49d7e92ec7e7cac3c53d4f82e89ddd5273f96d4ae5264923e4c91c049e46d16dde1ff21466b22012f1359b3d69000d99dab16ba700c752888dd6d8db2304ea5aa4994a52aaed08e5a6682f390e6154ad1bcba83d5688e90f8d41da8a872d9c077a97247a435b358773246143daa68098731c5d8137714b22e2454a9e8c27bf21a4680e65f1474134a99c1242c5b66e81dcfd3d2f451213f6b0897d09ee24f6b4d5bb0913467002d682a11170fb622f36b00406181636404ef7748afbfed6e0e3433125871840ab13386f79c6f3080600cd35bb9468eceddc7d8862506245c511ec4951a96193b4714357307cd78b18c99d4663fb0747c57cf23cff60c8149f56544503002668dbc16f59f2d7d01928626f95b92a35ad016c0a075cba16a5ef86e24ca79f634d3a9e41159c39747a101be661771895b37f7bb58b171488000035e427f5b24bbf7dfbb69d13685029e18a0ba5222d2994a44521865221ca64cc482e3911c8f2fb71184964c22cb39d80e638253fee90ecc7a0ba82215e32f395ed11b2dd140f393e53040ceac2a926feb8311a00f26777ec2df32f3670e486c421167a83381934a6d17848171a72f3feff6c20b339bf3764e2cbe20d2a801b3da4aae68e4994523288bd17f29113b1f26ea448002e71e267b94b2739583a05d7fc32e4c33d1121d2e10d2dd344af1e97602dd85f7215cc3132602c5540eb16d8738bf8b3bc80ebe629f16a887ffab31b03d06a7d0c05d876aed78e7a344c7058f51034e099ee11ad5c3ac1fba0e2300978701e420143426046c26309d6905509ccd0eb99257c728ab8aa077ec3656ac00b6707252557dd9fb5b4684d6cffe08fd1c3c7f5df976125d71fef7918826dc7f94daac9dac60841e078217407afaed550acf9acd21a8621087b5686134d04bffb6ee1a3979dd1fd0080b2c0c4d84af73d03f23d1538c82d064627e329d4b3b33c1763b2a8cc4bd944d4ae0921cdf0245382bd5d3b1c3b18bac82cb3c12fb9980eab3b4c4c928597dd39ad744586c2330d7daa3a51eece85c80a38db0c4d47f71d1d24474b33909a6d13d33944ee01303536e3d3e0580077c71d72ee432b3170355bd10808b4ede9999bd2871f99e38bb5c818a2a2959442d12cd031984288b41aa1474d06ea129a55a5dace3b6319195f370d31d9a3099e123e2be5b4ba7e123863f99615a26b9b128f7511dc4f8591c8db1142bf5bbc325848201de112bfadd85365c70ce7d2c96ecd8334c2c6efd6e3543fd153434e9815a138c1243d61432069b95d1372611802edee333960c0ad5e301285d1a02c19cf4255aa48f20ccbf0b7386f0221761e3463f442ce99a6d9ea3ded47bb8e9d246b5bf0a86eb161b233ebbe35414cc6cc56f6f912b47027e02caef7cc296df0dd66c0c3c83ba075149a27840610100fbb6e45273e6b6fef9a5f5158bca3a796b7c2d1ca67d021db0d13f13f5e07825aa682520db76db087efdf9ffb36f05686f3da6cf9ad6fd4689fcabe50ca238777b19f973219e5870eaad83824cbd7ef103ce2f584341e7f7838402d5d572cef013f2a6af1b4b52cca8d062d4d87509d2dec2857ce2653b5847a3eab69aa0d0daa24783982f44934cd93c1d56a2746f95859f493ca11f0e3f2cfa36e72315c7f1cfb46d40bd5ed9075b87814f1ba4da58936c95be06dcb2f1c3d547431da185f6d9615057215424b1b562642d47248563259222613ff2bf6da4d1e1dd2073dd0fd4834c4d6eba97da2f30115dbf022107afece16ca59efeded5fc8e981159c98c4f4ca87c1b04358e4c50d9469b2ebf56e15d2d2fc31c34213dfc4e2c26b516c3daa0a4f9ad8e5a5c8b4a9e951e35d3ed9c93cbe743c8ab1a773c80e5ce7142a20a88a3fcb4789d6ef10bce4071263b815777f61adca04903c39c886cdd3cf7c536c81ba01b9e39e2eea62d6c85ad44233fe821c5d9839a321bdbb0c6da9b40308e7dbb65ad02d9a64c29df75d5e9dd2c75615da99d7f6a41564d1c41d4a6ee973c68dfb255659b500f4c6b31961e60c4be5903cfedfaf316045eea71061fb05685456e8edaf4ae6bdce5e2bfd276d6096229136e53f3d003c89aa71150557b39b1d0607851f268a42e83a700117b927966166688129284d107f9a8c54911232e573ede366ad571171ba30952a23cc2c6aa86fa167c8c7917cd6e9441a50b3efce0c3badf71bf17460bf019b4e658efd9da783b58b0fa85213adb7f294795bf6fe87614a6e59656c061ab81525528b223f063c15866c1639126c0ce60424401591fa2718e3bbd620b8ec0c1a3506cdcd800a017dd9f5631cca001103f42a98ded3ff7b635544983da1cc032b8b2ff8aeae8f950aed424472c6922a141b004033ae6a66e80d3a560f3b25ca02c4395f739ee76b65d5f5dc1bd76f449fd6d31a71cd8d27500fe35496e7c90d543d1641a5bac190cea8933429f83d10d86c8b297ee817a4f625667ad0a4df32ae5f7d7e42f0fc388ac1c83a7a55ab5377a34e74335305145308f017baa3f01e0d7da9f2a3c4c645194612a7939ffc06d4e6b633204dfece4b7feff7a3302d7067d57f0dc2f1c92d22f7371f21387146649d900e79430e61201ea26f16190e7b23a084cda3ef38710568716e1828c30bc6ab5497a0fed01fc18f9673bca9c423ec9d12662062c835eb1d295a61b86b0699bb7696158a55077e55edba4606bbad29cb3dad7d61ac94c7b4b8d68218c25766c0162021720a8711dad58e3137ec5c787e71e39824a626e6b5bc45b44767752935bfaffb5ff2041c77d7350daaea85dc0d5b3e559a1f595be77cf275035c57050cd282c2289ac3ecd780e13c9634237d14452ff5980f1c8cd59129ec947b7bcfc14c833a69404a4fefb810c3c90d49cf4d7a20fb057912857511917c0076d9b114921823166dcb73cbdc6a9793162531fe61d22b95a7f51d7bcf4c16ce15c57c0c080974d1273551ea4b3a20f32627fb02630eb71ad75c0caa82a952eadfb7432025c3e1eaa235baf75936ab341448a3639a22e1d7478307b7609befc96bff395b134ed46e84c444311e7e59b21b858e73c6a189187eb0c587732c1dfd1424b07a64825a2e849ec95f052602e40d3db65a74bac04d965d9dbe1a7d69b9a713596d0b9109589d303cfd13734c518cb136cf0b745780824c063abe94da2030f8318648862b5e8ed633947c9c413f6494473566420ebc0b2851f8ca80d4849867d36ab36921bca6577421e43f5bcbfe9426a4584baee3e862df16efddc897c74923d583534587178b592c1d5808c9a340fa8f6455a0de05d79901244ba71234e5a8c54bb55978e16760702e67090975c35c281c1f91af2226f13ac16c8551672e342c68841dc1ad687073233d4c43c57706b0b21375371cc4d1c070d0564babe6b5fca49ff301fc9e834b0aada02c5e56244e30ce6bb97d0f999036ab8cae735cba4b7cfbfe695c454ca258ce2168121d214fd00a51e7c2ea987c6b50d8337f9505710b001a12c7af0da7537d692e74e93f984b364b26771cd2c5f586a43b03951c7a3c074a9b113b38f3b1c09cf367a9f0945eb4e641a8c1a7c3d91d2bbaa0a3088a6badf5b3f71e4c768408a3073dc809c123e01d1c325669c57ee0d35485e395ee628e60c60b5a4549c7729c7e7ffde6cd19dff46669b3c3e99be0282ab2b1126c352a98c262e608190926434e8ee7fedaa0507d6e72bc2314c5e5743591693ce3f4c128c19887e0ad31fb8b653771b2b54abd47e03d135baf6db7feecce315f493cce2f0c2364b90612c2e1123230b26caf1a1e76a20bf05b775b001329be18ebf5341db6e1fec7ded1911594c16711c6edc8822b9927e9cfeb10e16b64bf250e255e82404010108c5e79c0e9208a22c85886b9eb653cb14a3db395401e0be09ed8e63051b761c0127d8f741379f0add898ebc84370fd77c1ecf153a20a1055f3c353f558116e9d464869e05398a5362741780eea0a2e25330a2c97836a46adede1015a60a3450d62a5493db240d0e9074ceb3d61a08017981db12c3268eed50dd489e3ed72b49cd2ff8915689f1a4c43c5a12544eb17b03681696a7280c39f19b4f67d111000fd343fc8c63c290b694f3c1fbe9b54aaccc1043dd86910a603464d4573ceb4b1f3ce15146e0c2b3baa9cd910b42f56204286cd931b834d2b43373a29b6fa5eb80d092b32f53983ea42e744c3adec58303a2e79b29954c1ea11c17be2c3c2b4577fca347e2e3ae94860b77d092767a0d7ac56a40d88552ac658d19ed00f31bc1c1b9905530746242587a3a9fd8015f48312294e9494d88fa27620a22f81bc294197384804db688b3c1bff37854eabb9110808715006ad7a9c8e4aae3cbb66f10dc454800692265c7f449d6312be4222b5e3e2628b6deb2f68131b3145b81c6b95434d698715eb1226459cac362b2af7445f630f2a93ec480cc3b81bfa24bc6f177c2e60174f4a74d8c445e5151b4499a03d40171277243d06dbc5d1d064c4e327d158cec2c5cc2880d3e4389849ac5663b94ca976b991d68fb600651e62534aee4244a8044d87015611e28ae08211b144fa8662ecc0ae7d05e42302492884c645b76bacc07d3f141b56d9782b6d0d47ce7865cdd603c305bbbea7fcafcec2efbc4c0a8a8145cc0a2aea91a8717ca74b0e789c3e6347100126175a3b3962cca3ab08c7d3e13c051ed3f77bc39e0eb6781b7c0468c020274f56052787cad6b00b6309838122e651aa6082fa78f5f468ad81c27cc1fba672b8b5e51da18de77271fd3f276a31a2735d61b50d978684df9b33ab023e83b36c35223761199c003113ea1e456369640814952ea99c279c9373721cb49491741a612bdd113225c66c05fad8d01e00902bb56690da46344ced576d81e5322509537fea6c36048a7a0c2447ef63679f23ba7b8233bb02e00489150a6f3b3dbecba976c05479ea8b949d4865a1910f978bf82e3da0c2486a14a3aa69d8d86188783e7bcfb26c621f07f35cf1480e540cf0f63a1a2c56db2fff45bd8511588b4aece27319d97b0e429e5aa5d3ee7fe13d5c2ff69d54289805a1612a30c35e6ac093c1237e25bb5627296d052acb2c9e1ceb04c2562dbbdb544dc79ccb13249ea9dd6652117e67bc88c530e0241a293e0577cf91975c0cd3159366eea876346c3c730482448f43c9c085ba5ee3e4487a0e3481ef8acef9ecf26c21074c2669619c975dac97033d0e8f41156e2dc136c7111a653b210f7aaec3a00eb8d4f768e595c087163366704bf9da2d901c2e59d011d7247362edba989a0c0fee103ee00a9a7979d5de433565c2977d641ef2b53c8679bfa79ad2f7e93910137945c7dacb1691d3a504ebe826d6568cd18dbd4b09a4393aa5eef59da91aa777518664094a302d7def6d91eae00068861a6a4e0dadb30cccc2b6921121fc8c4af60febc7dd2c6e435ec19c446a6deef9c5000a2464c6ce96c0ee46dc9c7cdb05464e2ce575a19496753ecd83987b3c93f51919962b3195e33585c8c4df5389ce16b26a0c210fb9cc004569f456689c09d28daba2bf045168269b2d9da4cbe6f6a47573b14130adac730623ea4d19b05080c5f665e8173a1a310cdcba25fc66e7b84c7082611a2848bde9a8285162b82033fa96ccc8dadf3413feb59a44d235a4594eb0d26675684592f6f2028c9f2ba5e66810aad6c25ec8408cd7a2a981e215d525938d5aeb6b5b3b390e2faa109d16935e9cc44e818494830eda4f2aadc7819cc94b2afc2745ca9b3e105e4e8ac29e8ff1aaa16be53bbbc1e94d0ce4b68c388b88adba45ebcddf65d30ab90a7d89151d400772300446a21523aac9cf318f7ad6553d49cc61e494ffbaa46bdc42a249386ced8b53d2fc537222340333fb5e16b23defe96b0aa20110602acfb6b42e1886d89c33e8d33818c7028f41ed034d356dbe8f77db12c2ba6d55b6f2fb0ec2ad868e61e1a9abea62e7e9ed55923a5420baecbee8be368087c1cc8714b93e1124c5666d63952dca5f4657f249baddc6af4f5b8e740979505c15a236815524c0e973b553146ce727bcdfe149d499459e8869021ecaa96c09d0767cc2142792e8ef683885b39139424cee3205fe62bc6f0aa2c4c73a0ec3fe6b6df201a4b743af57ddfe377fd59268a14b1f7799f126adfc03095638fefe06716c10b5258e67bef3d268070d14916d1d7c66e7eed9cd7d7a64edbec588494aac36b1eab42e2c3bcfaa6e09174e4f87307b38e9db98fbecabb42db94bf635f1832de016132fec5c5f40fef4e56d4bea65ccb7c04ebc47404c6cd995ef6a54adb43e4af6793c798513c10c490cfbe88c68e0c4f127e9263cd1a92ccf2693313a061c908ed68b503527c96c0d94bbc689fc60f12a529f3f672616891b5dcc6f489033a571b8e88f589c6383f546adca452bbfd3d3527a4fdf14fed1c175169559964f8ac27aa2257fa5644f32e72175cde91fd69fc6132aeb8a14b831ba2da7deaf159442509f34ac8b84422a2c122176238c235549e07103d30537197c5bd55d928f52ca147aac94c7eec846cf6985239310c6a5c17200d2422f6b724858ad113c50adfacb1c6709d05229ca8ae630c4a585729919ff50c7362ad0620e94e1996f04ea2713a6761fef95c83d4b7cbf7c4d26a4721e10715c8423366befc4f01a2349da7a1fec9215640023e1519a57b988c60fe4c11dce5e99f12922385b5743ad71d14123b469f7409e0fe8f957942412ab48e4a9c1d6f381861083afc932b710ff069cff1e470a96abc4d2271a9b23ff22a585c4abf20a824574d2eedfb5fa1c3a56e3cc826fd7cf0f9f9094a74a083ebeda3b95641e78b72428a89fc7785043afddc3421a690066d27452ff9aeff9e4ca57d56509acd34a9160ac70c1156a2682b30e6904100ac1365e107bed84b6c0d7feb747822ca2fcf3efcc48f43972ae5c2261d01a24e62301b37069e3afa387c0384643eef2fe0b9e239b4ab44c68869e30ed057bf7859c08b8b9ff420a0a6d6b2f1996b18ac003da9d0cf18acdd44a5e6f08c2579a124a1fef977fb2efbf435ca65fbf069abf4691649eeb72f02d7c12976a1daeaf00f0b30d0c5dc084fb4553d8b28056db895b51dce5194d1d676c7dc46566b4152ad6d5c513ccf8fe19277da874d71e58cae2a3fe25a3218e478744bdb2fd7cbd4334fe8f29667ff9a61eec9456eb24b0fc25d808b22e7f6d6849d9adacc4d6c8fbc89dbaf02c06a61f783e8eadb1ea39eba30e5040dac0ec555e13ed8dfe590c008c0745a83b7b32d9a46f9773bdbe2a95fb96a8d5d2edea94015512393708f7b99f04ac04c31c4c2a4f51aa51ac3844930d3766cd8079ad56d7de2fcf61a2b1c84594b6b91c58bd37380d2e4abbfd10c82cfc6731811eb93e253d26d86db58fb9eec2ff3675e951321b7ac64d428f5985d6421b6d3618d159e379893543bd88946de334823e3af6f525c4972dd56f1f4b658c04ca5b7b3108eb379742d2c52a342362124968efb18e627671b6114e9de5c89a3584190a9a547612c8bb2ef7a8424757d1abd2bb3fb70a537d44b1cb1e666475e93f6b443677fe669351e8830fc56622092e0460e9c7b0345e45513abed7763655d44dbe0da934233a353b438eb70aa291739753fec79b17b7af67a5b2e978f6d42aaaab0d794bdf8338c6dd82bd2440a4a07b9a7e5cd4657c34130149c428664607c8eac8560b2faf9ae910c8aec63a623d93d9266a7dafb11d5067c0f40f2976a8582afa64924fb6e3b5e38baf2b80c496f4e3e9f7d8258fd1fa4d817ac9910835181b7b31afc0af2233d1db89a80b13cf28be3c6db99f65e7469e3fa7a5c451682f6fb7e74e5a0f7a4e8e2a63b477bf9d7f9c1a8eece86fed094c97a443a418d8b93ce92db44a6ddb71101a9effafd61f1e4a5ecb6b76b76388cb58d782c268256737396cd41c7f5981938282ba7eba85888307134284465c8dd257a9294e188e36a635c7bf9d08c10d26355f368084edb0111d6337755943ac840ae3a1f64b465b69e87891ce9c340ca183960fac0f3fd697e5ae11f28746c60f5f96622bad9f2547491087b6ea2186f75d66070d519ab8fc92f30ce1560fa3ce69c77d32f20ddffbc1121dffa66506037f688edf92431ab869689610a99239491bfcee6822f1bdb50be68d3a92d94b62683f371069700c9339a9f243c28c41e70e6a2d2bbb9cc2f96b52e889429189086eca5539f072669870c7c7f1831217df0105c59d558aa8c805e216bd97a978f67fd225931e7af767f4d922edb2e2ff0a651ab8eb2028675473cb13be984f8908f35511717c9e8d1e0bfb5861afbf94ffb06c94ae3e179a87ac2c56b6cb248d7045dcaf1fef53908051193d597c25d72fca44255029c166a51a2d3e772f6d5cd0e08225c3fe6569d01ef7c00c93fb9867e21f7d5341501a297247a44517fcffc8ef3ec27fff0cbd1423dcaadbe8d80549712271e7d668cfa11f694184bd0dfea86834a37fc0247b37a07f83982e884baa6627d2535068b5798b49fcc2462bed110590b26a4ca37e8a6a5438c8b6b0694b6e49b564778af8402aeedc72f554282223cd98ad21ea5261d33f78a20ef408a9310e6500ed6116c0ed32c8aa558becc934466061589791203e17651d9c0d0556e948b98b95664149306ee82e3662a5758bc79a6948f8ad90d0bdb843cd2f1f211cd426147478ef968b628cfeca24c483b9baec9069261d2aa5fe35443d67318af3d4135ad6781d2f94558eb5ed60ed21e0c52ad871871301f1daa6c4968b00b5d3ce97002287de9a1d10f9104a20a0f82f0c54f6268c70a0dc0716f73fbc75b5c9162671dc2a4052099ed2278fc9b147837089afc35f2efa973321b712f973494c1d6629995a8cd475c42f3b4034a57432c1b21a372b83a58602fb132c3edc154df331e580972a88923dcb73604123331855d4b669569739b6726609ed87e666f48f5e171359e60b8531621f6f76ef349023bfbe57b96b2a0a334c5af08469bb750bbf56424ba53e9fa006b2a212c0cb58b4f3abcb9c5c345ddfafd92f1a9d4242d3ed849eb0347fd64b2ea68b13bbb3e7bd98eaf03e0f539b2a5cd2aff144a53d39beb3004bfd54dd92a4ef71edd02e1f92e496f21435b9afe2406b058f79fd95626c16a9d483edfe2030233767ba2911fb73bdd535995af3f65088f73967d3896c7f23d31d518a292a2a0918a24fea0260d455aa5108ccb8ce48f28d7198d255eb33ac5be87cede16f3f1fc0aaaecb7dfc14ca53cb8a1826e0bf5d2dcd9c397e98cdf8711a8b78155a09995fdfd909248b779fa92874b15bad5824eaf650535afa1f72c127bb87489958834a4111bde5548004fb2b2c907a89858db4fbdf5504474baf96b3f81cb1da6facfd2673e74ac735cce461215dfd358372661f306d89f90a6018fad487662ac08a48cbd52ce3e726804092bab24accfce86e0065cddff1789c532cdfc535ee8b486ef845dfd8598e68c08086125888189334e60b64c5acdf117abcefb7f48626eb797faa96194221d98886b8a2948308d0ae6e4fc6926791a32194335d7e99f782595088c3fd2ac255d7ba820fa7f97361b63736c6ec9092e58ec815c756ccd4ad9cdf271213ef314fe2139b8c2ab8ab301a88f83d649fd6d5317d48f0fd6c13833ec0063b9a3d2122c1c0c73927e4e9b82e616ea41cd275ef6edc513ac495034c6453a9136b2a64167fa7e1208c5b79f085405698d84b31eaa2e9f35f2ae700ac2fe0c60d866eeec321ec709636fbfac26e11faa83c830fca5f8516938b7a198afa14dcc4cb1320f27a97cd2864c494241942d35ae298a5070b12094e51db52088fd06df52138b691f4125d7696189f306778d5f69eb378d888f76c76378c0ba211c98816826732584b4e36ccd712b54948a2c9d0a6923e4a3dd6d939bfdd15a29d576028e002ea5b2f5da4718a2a1b6c936871bede6f97c01f6f882aa959b00b322c9be72b4119f283043a31d11682af482eba698d382b92dda710bfddb41d3e5a0a5a6110d58ea595f03c46f604985645c7fb00c7a0e268cedb855e2a83c44bcc1de0445a82888d5fa07ca27cdc529b8a522e5d0869edb4f8f0f0bc5bc66b18e785c9b805085ac4866579c3d353ef3aef1c493b17d194c4c93c11971091f84eb2cf5e12b5edc3933738321043cdad85e25e559c628d18c884f015a4e31e83371aec50d68a52e82d8f7ffd5a30597805ed201fd2a268d231af0360cfe7ee77b8ebc801e8f3d9069088cc620740d39a9ec66c3d55de44ba3311f0c0c3d997f836e650adaeaad4c0d636222d9af3ac383b39841043044ae7a6ebc21ac4727fc58806776d48ea6115462ffcc47aa27d9f7615923ffbfbf266bd7e348454859bd16abe15f360d8f1baa9954447b9f72d3a342b2bdcf052044ae1e5ca403c011a1bb2164a1115e02afc985ed3d48596723fb5a905fc7394864f02b88341d5980da87865c7f415407dd24cead9fdb014799b763653b404e2b4719ad164426e7523473deaae54481c7183c25ca6852fe34897033d11afeda232f86d8dfce04cc22b0803b64fac10ae0cc2d49fa13651dc972101d11a52f732375a7c4d73ff8cd88e7e85546fc9720819ab37bf0379bdb37eeb433b8b5962f6e73d8a355b766944d2b7d1c9c4b444b17f3f6d80001949d5dda926244af2a41de1930a02375476c37954211ff04a3f91dcb362ba78f5d001cead8ed8f75e7666b723d91cf90d8cd470ca123fd776b80410c4f309f6c110df84ee160ecba8dad3b7557e9edb303475af673d0275df206ce474e7823abafde1540a16655fc0cd0211728ecc972ab0d242fb0717763d291d58b647fb5aad98d9f9a95037c128954f54eea2bdf81ae066f8538216edd0d78d2c218e924dfc631cce150f3ce0d9aa4634abb68c2d4be2554e80025f300fa03d9581e6b0df005bd6d5d8e010981f4ebde5e8053f3ef0c326278c777a9c3f5dcfbe3edf76c8bd7a6bb05d9f2ae10f46eb7e2311100135927db154942055d90b8739a5df17314956243afa91112a01d7283a2942cc394db810973806efadbb63d6b9003e91ccad622ee6d41a91ad659445fc3f44bba9b5f48646ec48782b14936d02f02cbb3397f70ee2d9dc0bdc23be46bbdcee4ec0f3c064fbc5940b197315e7b11376e9fbf5922c1d6b927afa24e61e6e4b59d5868644612bfd231a8d673089050996e1839aa9c7a1ca9f0f01295c7dec2cab7654ff4243bd6575d22aacf0517ca09c5748c85273a99db50232e370f8a3163508e267ab5866ad6452f85a8487c235c08aeb7e5c72d92d57b70ada96d249046a14634a1ff27e1741a1c96a7a932890b544f115fd355527dc7caf3111dfa478bff6630637a73790ac382596002ceb8fa0c6188f4f011527be1d880410b0751c94bb00f577c16eeff0f5738939610c7ed38fa77078e879480d2a8f6715212161cca47675deaae401c75132766f1edd2709fa7fcb8be1284e40a4996ff4d13d9eba4fe3b3a4fde1b48f5477c4f0124617a835e53f9ff27e646039029ff77afd80fcd81851c97d4561a06a0e021775943338c7766847598f329a6a0f45c42c6ef99621c5af1e96ffe70d40f3a0b80a5e083faf7e04fd4f0b08f568acebde7b0572b891635e0cd1ee98b53ba15f24c5edf152aaab9700eae722896f5703b20e011a4f2738ccf969d4f5694d762b925a8b93a1d57a8a91c41419bd23c62d9a28c48b3ff13746079354f17d6815fec59bd07099116240335a79d0ba74f6f7c1998326b2225188c849f3af89ade934f763ee2e9ca6bbddc81c3a1510840df3b24d1624d6354ed10318418e4c1d12420a91c76c68bdf46baf1ee459618524ab4de9a41296e791e49915248a4bc9c2454f8223be9a11743c36276b25cf4faac3d74d2171f63e4dcae9d48098e24c9904ce446b64b4339225f523537526700f85b66e391e04a8756333586cf419be957ac7e5ec6e16669427189ca21ca34fcf70cdc7c2a8f31c94a6e05b60673f0ca6395cad71185936e064dfa746a037a7c567407ce27128d35a87c36acdf996373ce02a04fe71b19e7d45e88d1f4450b8d25d98021029ecf0dfc41dc352dd5377ce488ea2721b4a66cf1ec55189d50a972667f0ee4cb3504a7711a0cb5d4c4908780bb341dfb767b002e6ec7ac289e10dde285c59a054e82916229d798134f058808f5f9c6116e895a676c30d5c41731874bf0c93c1201af554aee52231471e887cfdc18f317dfb908fd2cade1cc5e81cacaa0a457fdd8968a42b30b9d934434bcc61db27c386fe8bc7c00ea36c56965b9f96c0dee97879a67ea3e73a216b2f568cc8ac7732b03141376fb6cea78e03eba834939cc38aa3a0c84bffe6739716b79589e0f6122284b96baeb02452b5b76c8ffe4200943a10297755e8595a68facb63959a281d9f44d16f4cbfcbfbb70218544205d49c29e0df3e302621b9fdd8efcb1a741fd46d30dc6de233d602577b616b49b709ecdb3954cdf9032a59346b0239320d58ffa20d2e013d6b0ef4f8a41264e6af851ee7f46fe5b62ec9f2df684edc0555d8cc01113896231748e61541992158f735af641ef7296d6979229444e4c0815d5358a94826281944f0659a688264da578b18003f7ca2ddcf9f47c4a89e61f117146545901ee419aa9e1c78427b9fc35c5dccf93b28def3d268f609dae10fc4ea57ff91a81c0ce6480fdc2e612e4109d7977ee33d763a7767f937f2c79d80cc00ddf2f3df077315c876af8a29e6f103ab791a18914d2cf60970c2968c184d38a998ab1dc9c091ec95c5a71ff89884efc42f85f6c8558e5bd3b67d008f4cb3fde01953b2652fe16b682205d700d249e6859655e742b620354631aa6ea5c9d88d6f6728a55a41e8ebbb997b9d6b622dcacf0a7665c285978107fbece7af277881b73e82b0e90beb2a28c445b789ae742181a06aef6051e28f4cfe5d05c79a88e6d310cc019ef3d227b126bd8f6765e7de0de30da94f91dfdac3917a2e3fa2a19d13b6a9da74a7e9dcee3ed4b7daa794e0eb91f126ee9433081430ccc898d31067a4080f0ca7fcb6f598514d6a94718318fa46577eaa0f45a605f14231173840a3919e4e1d3f7cc6812d6d6596b702738f76d75bab6084a2faeb1ed8630d41071ccc705dac06150d7c262a6705ebdf71e5a5b8abc76ea2d53330d345220e19994da782c2cef5444a08a409236ad95e14a08b39ff1333b9a26feca38d76f953e480e87f7f7bc2c93323863dd2ea6169109824cbb3b009128f2df133940beea3c061a6648b2944d9f30a6cb5a81bb212260aeb59435aac706f3ff96dae0ebeb125a0261b0a123e412a44897c469b840a55ac51b4603147315ec1957e3563c679073d6edab1cb98de8fc254c9204c8dc18e618eab0ad37c4faba786296acf91e4babbda2df8cf3bc8d1ec547b0ebbacd1e49584630683391c714466ff7574a4900f5f9d51b4f8006347733de1596b7b6df8ac885ba64858e8d9412699abd7487cec8a41d83b2df2e8e81e40f1fd436625baaeeb50cbd8f1fa265e87e3c489cc0b66b8216cf38128eade0a9e00bf7c68bfff3b712bf832085420bd5c122a1fc175a06253948f72171221aa2dffcc2b58f28a50df2e55840441b211e50abee33ffed238a682328a7f6cd4012a51ea050f5b242591a466d70974762f0fc101747cd0987949848e30ea59d93570f3221491084c127682d18cf98c4b8e3f110eb1f51057505728ceae23ab5040abab6ad51eb68a14c4dc2326a1c5a575459f436baa1265a90d9c00a58d322b99c519359b764f3db6358fa8c8a9b310bcb2bc8ff8f959fa6e3748a931e6dd7294ec5db657528ab0a999503855e1e20b98302a654261e0bc1dd2407a9695b278c19234a3cba31fe60ec8c4474cdfd0a0707e3a95d0ad1d128157cfc1c1ffbea222f52cd0f34b4fde233a175a3cd82aac86083cc140510fb1e1c8f01bb21cb57c01e18ac8f00530eb871f52b55ae7649d64e5fff0f88926e740d0c993d1540a2bfb4e8c9758b4ef6cdf7567811970b4e94faaef192000200b49888c862fc9d65bad7c6c0f8d87be27f50b4be119042ed4784a4317845436190388670b22a93c645fc0657d2f350ea1c239a2a90443f74ef3550904e785cb362855850c9969bc34c858224bebc1bffe5242f04ec11ebfd534bc5f88c316c591004f1875f2bc596889ef0942c06d852537a9570ead758f490ca779c5988463902df32a49454cc655ea44d586b321a1251b42e1a02816808b58edc664ced8187b5876a73217108fa93c4357a00f689542be9bfe050e5ddb10e5e40669b90f7b5c8c86a0ef56f0cadd6374040906d7048b6012145b3bc6419a02f30ea8ece23e602c03b1daa205fec288fcbd07e88dbb8be62293ada3189e2a807cc3ebe94e2ec9d3818a8d4976016738815d960e783a10d00ff426b815b541ce0b2b34400c4c501471cdc8eeea03662ac4a7b9f5a6a32ba6a21580c258e1c5b76778970a2c897b767e31073b9fb804a268e9f18a3f23d1a8c91fc606991c664b14206c74acc62c7cbc5375d9a0c59fb9e42f1c4455fca840e2f653f0cfa45ae86931b4e3fcbb387e74e2548143033ee232f28717167a03ceaaec66557faf5eb19205770738e286a51f2b76a90aedbbbef373c06b09cb3a6f7689af6661a824153ee5a8ce3b067d6bde74de6a9d5e9474ef6ef39adf409ea131dd2681a7abbc493a400ca1c41530ced1e398ea860cd337ccdfc357ab9fa53f29f0bf1abbaf02e6158d9cddf7e211dcc4179e5f1ef000567bad7fed739d7c074384173f3d69dfd14be459d8f00ee81d1009d568f10e0702eb1009faccebabd0def3077dffd13ffc61af309aa88734d869767b867632668e8a14cf8c9acba869bacf9a37b5c822b685df296d8cd49f16f252f3eb06a758f882ef5d599e84d2d23e8025367afc4dd6075e6f59dbd1120defa08088f618051ce1756100ddd5e71622b27b3aac1789dc1bd4963b3b54596cef50f64795346f39448c5a47bbf8bfbe0c01c33b31ca57aa8bdf3b98505f5eaead16c0927f23bf99b983a3d84212da3d3e8571967849b95636df66103e9f0a7c6056def0d952c2d53a302f41ab2ce398176e8f0837a3d3351f8c3f218e7da70db13b34611077651bdaa666f426f767413d67f64cf675950af654987ae977f8198c131c67b56ac155c0e34d24818464092c39eeb9356a0b6a5f1864fe07582d3d0f46972f9a0ec46cfff0f11db38cb3f3ea594fdc50ba944ffdb0daee7477414e7588490be668d7f229fcb6fd50ece42fe3733e3f1f7f59c2e7e5101f3f60f8e2a750da42efb978b083f1551321e91877adddd203004f4397c8c3c5f4e97935f40e8fd9c63b5364d589d3cbff9c97ebad6b8af2f19de8d46978f817fa3860dc069805e1ad075f56274db8817641c0d6f3c045a877bcd037a0b9ff61f593c6fa2214e97c2dcf5ec292b215b9faef226a0962e4d4ae7e9bd026de1e034786dfee8b444773c74c94fcf4955561355472ee184871bb0d42dc7e962384375b913b6a8655c41397ff87e46f49b1a4c518d739749ab5d3f641085cfd85a2515073cc30c7ee354a980b7010f04acf742226d45cbcbf52cd785fb69f199e9c5bf243e151efc081b3dc57d58d9dc2722940c771586f0640cb3ef9e20ec5ffadcdd9fb7700a99787c5a08e00de42de32a821562a733dc6fa8bafb8021ad91030fa5bcabcc6b526fbcd50bb9641e0ce6b5aa15cd5c0a9a1424903e7dbed9ea411769a1a338981c2c6f7784dafc972acd76e3a1170bb058b989cca2ca66d9d2a074c606ea56522771e84070438bdba07749a4559cc04bd3b6a9262167a6ea67eb3e61a855c61c4a66d76814ba91747b24cd87199770b8a027c08f777df95c432ed4a556b7e99ed9101c287df42651e99371968a58eda98b75853af3d2970f0c31c0ace6eff6890a5433b5c928744b0adb1feb2bd0246fcb330f26727112c63cb88494a83622835a6f0921a087bc7c163df72b7c270a86f5b059bb43c8ef3f391744c64131e2d3cde0fa4b95c89cf8d91114c117801cfb835c2b9b066ceb7b3efff04943f17d17f7a0055991ebfcc6cb4960451adc70cd6d8199b4dce6c5d2d4c00921c4ef7f56def575acd0ae18627423ed4d2ee8711767ac17159a05c010dc32a429ffa67665aeb3592e63e1d519239471dc678af4ef3b926c30489b3bcfb7df57d061cd75c6ee72399fdca263404ff1feefd9ecad79521ef88397f7dcc2810a2ecedb1d6daad80614c53a80aee3aa9251027e7690dac000b9b8af8705823bbe5cc754c6df5e93dee6f3a0f4934c99be72e64eecc623d8b4a38b5812896e521e6076a59b0ec755a16ecb4b47371c2b247e5160e37afedc2d5450df0a649d8fd76205c45bec967ebdd4df13e3fa89600e6d16bd503e44eca8a1aab90e2981bd846d4a684a5b21a92d4f0fbd2e22bd9a63c336f9148f26568b77d71db85115255be86688ed72fc074d7eff109f0e8fb46132b916ac0aa2da24e372a959676dc6393b5dcac6f12dc0933c637eb223af65b6bb1393f149a78f904396477a76007bb43a16eb655194a48ef810e5961edec68978f5524fdf79367679bde5607fd4090c365fda7a64526fbdaf9ecdb3816f793b871edcf2cc656c72c2b76f7c1e05dbab30071b2dd2f2c9827239625cc4da1ce4e32c474b3ce900e1a4ad99fbde4e1266f0650cac1dffb0f81d83cbb4c7c997232a489089e2d81aa9de5ef42b7d5b04526e9f33d1a242d663714bab14464a8457756144c2d7e4f94ae0c7c2ecdf41a2ba949d756fad1710b0b341497f924133a6a75abf19bcb09430c38df327968b4a96617c2c88aef61b06d4342d1d4088f1a931644fa1e3da6ad3529e5fd332684c8ebc8093c7ac4a88d04666418bc03fd305a54a15fce8eb2bfab80b24c840453a409e419a2dec1b808a82c74d701e155229792a7d3cdae09f7fa766b63b140403f3c674a0bf84c8de726ac6613b4453e53159ecce2e7bdde49ae9b2a441954320364a252da4e8b87a1b1c7c1b3f886fa94dcf119fad71f4ba1af2cdd85fd68adcc9eab6bc3e12689383749ed611ecf2ba2821ed58657eb7b76d85ebd84b3aeaeaaddd31eb59f28d06d86662b8cacf759693f7ffc3eb36721a6fcc3d02e4f301b2a12c6f85f205b57e1e6d80aa5e2c9471157d5d4864b70057c1b134f301fd300fa1ab09f7e8e2056f240564a72276e08c8aad82068c3c670d97aaa626cf16ac1296efa217423580200eb643f972df16d382d217ef0452428823a965e2ab22653faffa73c9c25a5671ed8da07d4c3f7b1d69608c946a17d0049ba97af303da6b82d581a8b0ca62a8157287eededd1e63829209499d20959da9d9d0072926d0e42c3fa41d4250db4ed50241323bbe78d15b20460b998f136e07ad676c8f9b48b76c924c48f199ffbfdc836a1248418c49a38158940a49523ed52b9d02cca6110a42663f1ab9877e54b25c9e0145a0be81c7e65010489501de75b8ff76454ac3daecaa2176662e3023678bc47916db0c6b8a6d510223543e77246a87fb67f6645d9f625435039eedf9352ec56187cee28fb37ad07590de40a67616201d82da5a24dbffd31d051e9e2a960c44b6a406a0cb753718b412f0dee75fb6a3ff8d09ca56fc2c0095726eddff1c610265cf8058d20ae7f517495a0190302a27280b1192d96c133cc064bb797ea5eaec7fe9ceeb3f861e87610a0e0d9030662423d2359e5aa8fa3340808392961ce28f922d3dce0212066619665d462461a885fc6a04b2575a905cdc45a55f8a3cd31957229eb152bcab7675867d4eafd2fce0f1c6ff7e8a3c20c9db430362c0f8c788b7cf93f0d890a608013f4588e55ad39a3dfe07e75f3830f71dcaa4012bfa2d19fc1388dc008b47766718a5c17a8681a02cf52a918706190cd7fb0f5af7db3644021dfd0d74d8f2eddd6f21f250f375969b97e2e93a4530d22c82519aaa3860e914b0571b22cf35ff6622a506565a1a7991f64311724d6971652f92d4a570a90bb4db8011825b77d441984829ddbb6c9e378b4aa26808de7c0f7e2265d7b334ac7b30371a0d7bc7e0c72b94cc9c491f264f2675b38c59010d36a2ca7125a99e6e9ef31cc682900e4ebcaae3172a8119cba582e2962dc58b9b2cd9afa2af651284ef3803be0a0ee45f36cec000016ea3ff19e43c4a0ef8045f20f54abd40905b5d24400ae7f18fb3c2d5e3fd4557394d12f68b1379aeca5faffab70e12ee103c99c71dd40bee6deea798c9d7ddbddb5bc7835dfe3ce56a12415bdcdb43d500412b053b4acf23901cc264cfe3a0e5a569c7744666600de1632c4060b5a8015439b3cc42f87f683846025ce26d3d3b4c9ee597affb28705bd8c34ef0fd3893a65f255d807afcfc1efb14794072dfd3d111b1d3fda6638220f56763cb3b39d6139deee05b6e4b7190a730cc500ac365bc749ffe48abd7cdbb6b0f3c03208f5a37d33d29ca0facdbf5070c7307055e7bfc804152eb8622d286d2fe49de4e73f48ba5f5c6d6b5279d52e5c459cc934c2bc3445682bf5ca03c9bc0a7a85634d801798acb4708cf747d7b5bbe8f660e8fcafa4d10ac723f52993a0b060a9a5aaa1d168dce390e7fe54812ef87d7ce451be865ccaed774db9f48b26ae3e30d10b255f9cc2c10b2f979e12f6a0882682e1260d91044245df4c91ccdf2341d19c84bb73768a2130eef084520af779906ab9aa9594088f2ebda13f63ed0f4ee6fb1166174877d1f94c8b5f30af25ad8849c676d584789b04625cd272056bc63021a9429555f3010baa20e1bdf5bd0abfcfdc9e5f1d65cc0ab06e8d24e259fe54a60cdec343aa26c349795f33733b74af14506910f850b5481b8a66339000c4041c16a0a616b74e8a2b27b34a853b7fb166b06584b07d79b01e95c40ac6cb2f79652262965720529051e0516e7ba524b3b6773605ca99db549e9681cb1c954527f9e4ab229f313a1a705c73f7e62b80b2e420f0c2777cfd19d161d734ca33bd40797dd452ba54b9d2d1dac7f0bae7a9be74b80c0f0984f45df2686c3c4b84258022486c370fa3630ae106a0a250001eb8971c1e8b0dfeaec68ed1097db5698d2d9564a67716aad3029add3bad44eedbd374c8b05af61d740edb2f2f175cb3f0b989e92cd81d131ba63399167b66e49775842becf0689805d51a3c25c2490d6eb685b4ed22941523a6af3295dca45aa7aea466c939e9952f24c8b43a3c400a83ff7743647ebdee6b9f54ccbf50a078c60c6613a8d920315ad7badd3f5afe1223b60ebe4e51738d9ba6591c3827feb5a1bbb772e96425a43ea59b73c1461d03221ba734d24d62a25254d99727b525732583188bfc99153535333c526738d29c90cb7d81d52012db9204158d8995b9ef14cc95784afd4d4541d39ba73317e614805b49841821cd964ae79220b3e5611f62f073daf47aef9a0483c746bd3f1745eb0c95c3be9cecde11d6d56d2ca4ed25cb7d53a96e9590f7b050c75f2513adbe407cf2b8b266479461bbdc1c64938879794bc2742d8cf0a0a4c88a802556b8748550666b4d30a9140ce42aa58832944f7406aadf507cbca0f9abb41b8c6c355aada26738d56cb65aed56a5e4d68e32c8e577b9b5aade6d562b55afbca9ae7aab11a116cf2abb18fe80bcd150dffb192d8648619599ce7469fb95fb06f8a4d662253890eef6c7933ec4587f4f8de845d170f8b872dcb309b9e4264cdc4686dc226318ee48f69a213427615a56c3a31755284fe09f1a2ef85625f9fcb5b97a9da702252c02e08a4190e46a5e8a73ce0091055bcf0c410342c3521c9ba6b84082dcc408512a830c20635f8e921c7229ed84a1f624448e992a728123ea938d8103631ab34e9410552f4b0d4a5e99a1e0b3718498189932351bae02db80b1d62064744984cb8e0882894f4c04005382891c31250a030816152bc2596e8a40e144c90b083104d1c2142861d1c9078c10443284c51225344114438a460490d2d5c129c4a47546981141a8c48c2a566fc5e30faa5e2192ad8b0bf53245d50276b7a0d7423e83563cdff9bcfc58a0b66b05539f221871e3a050c2314d9d1a0430f50ff7fce4fe89fbb8e84feba5d84fa029a60e2031334784283ea84962a8a308102292b30a9095f3802c0f90c742e549609ac0ac6eafb966c12e3c220d8e700d1afd97abdbfb272c5183c5ee57436cb60c9d3dfb777385a5e471cce8673f1b7d161e9d3599ccf1fab08c431ed2cce9ded5052f5d2f19cc481ab3ade85837660790084c8e1d6f6d416c9b9f8c426314e67730a53a40831174c419bc98aae57239b2259c975f2ebd40a52af3158d9a157fe8030a4362affd5bfa5316badb5d65a6badb5d25af9f68e74525b6b6d487728afb5566b5b70eb72eb72eb72eb72e372e372e372e372e372e372db02c56dcbd26d8b79db82bb6d39a2b5460d9b4363def020208868d6a04f286e4ad2d788dc5341fcea1725bcff3c3b9e7e3d2e1b921146e28d594681c156399511d6a27ac38af06749bd5cb4a335b25f8036f9cab9dcf21e8ceeebf5a95422e5a107a01723d8ce6c122bb331d885edfbf3b940d62dd731dffdea79615fccfe5c58ecc2ec8fa517ef44509363afaae5964677be070fa1729d55e520e6403e8e39e80a2184cf2542cfb7df854f3c85e7c23bdc73eade02f5b3a9491275fd497f3669816ea7aeabfbf34f12eb4ebca46199a809ad3e1d690f5b67754b3c46bb6ef98edb9ea5a6d82b20f8c76af523f306d09eeca26f93b90cd965cfb7b11c48d5517b58abb3b9b779bdcd48b4496ae3d941ebcf6e11b31e888c6a2fd7d67fd8307e36f9321c846318089736548e774a36f9b2981d650cc8b6207ef6349bc3721c289bfc3a0c23f6cb5840b338e4cb5e6866042352d280efb97125fc8be1b269a73de18ca15a2dbfa8da4772ec987fdfded46b71140fcc33e7791e8b2f30a27c9eb7a8ebf320861dd3e90d28d6c9efe365486b485bb2a8a0573a79eacf635cd8a756ab85d49f9f401afa73f0ab46faf3cfd3d29f7b9922f5e7f9d2e8cf4fd4c6f31828bb721bb64862d7a5bae31dbb7c4e2717596d2d2415b2802eb2ce9e8021092b648535a18285daae8b87ff77ef585da23f87c9a12ef5e736f6dcf27e5de40773b2654a8a1420ce4ea5f070eb2f3a95c24351165a9699c692744f8b4391a834f5caef0ecb945ef965aae00da04d3b7d71b2b9d73aa19e9f38863d06fabbabfc9e9587281c448c426c7a5b629e6e903061d3db9227e02703884d6f4b683d7b34a4c06d7a5b1294b3aa06fb9ea3941dcf1cef98666cb06d4ed8f4a6644acf3c5fc08e90b50d099be4000e435ab99755fcc4c1d6ebab9f06ed50adcb744f3b569ee5ec14298bae77d02952965abfb9cfb9b7c9ecf547d7abe77d9b92dc26e91fa72df2846ad5ee22afd722c19feec14c70aeb033a2f38a98576c598e9cc05d2aba0f1cca28a57b44e850cfd3f15a96d3d62984fef42fcae739b52b1c60a45ca253242d40bda24eae6b2368d74eab903649a73c946991c24ef5bb63416593b6bc3b6aa3f2992b9bb452dc9d15297ae516f7d5969fa3a94de5dfb77725ef0e85a2deb7679feea3140429583b9d28a794f290d2bb7bad354fa552f4ee765cb7486ac26664b55513d6c9bbeb95674d6d49fa7ba9fa8249486b564e564d487021c90c36fc5e01f3516ab4554b4c0b383fb0f13cb9a1600537a8c416f0e0461b1a5408952aab603a9c1d66b23818e7175a482d0e06ec08dd02b4a656cb4aab330babb5d64a820e5973b823db4977544763802ecc453510e28804c1e1ceddf943a4b2497cda78aa082cab0413e37bf189a9c06295d80f9cea9ee789f179be4e7ce2338a4d628c0b67a82239639c63f948f6e1d3196532c7f090cfa1b4c8e8c9ad9a407e3407c45110219fe3e178541dd846bf87c5662c168bd55806bed8901576ed53e2e1b0a8c7b0cc3e89c562b1582c9663b15cc32673ac88a8eb320ce80a19de624884563e6e2743d031c72e3ca5571ee25e5ec8b99e508d54181122d4062b86cac566c5a6cff9478e0133a072c49379a88d950d4cfcc4da9ca735ea571cb2d96a3897cbe55cee737ad88688359009199db5cf76e4e172a66ec79497708f8725032446f8c9c73aa2a481349c44a51e3c974311891f1aeaccc972b30a3b4f199115139bccb973979ba572b95ccee572b95c2e97cbb9ece26962933977c6cef33c754c9dcc39521b754b918e40d22dcf495969a756405e7a323f7da007bbb0ed15835168b338a40e9f581c9273bdeaa281cca3dd77c2807ae5f9c7e6506b12e5b250467214d4adc89a313799694a46b6239cd92b3782b1ca4d661a3fa21d99baddc538080d0245ac00d7a3c7d2a675ab6328135657beb27e50c158edef3b619fc07b307f2ce36b8f1f0ebbea783cbec6ef5eb18a4d62d3b4384a56e35c3b5851857854c524c81933e8cef36b574ae85e64cdc468bd128248378ee37831d6c13a01e8c8d2ed8847289bc43a98d6e1b03c45b1724227e9ceaab802fb442c7c5c7e38540f8bf3c2294740d5ade094d75d65b2322b544ffac136d0a7da5c35f2d7a8b35fff03bdcd73f235f2076d0de7421750f8fc3ddac945aa5c24d71ae752b8960c2cacfd1e592751b5fecf5f5aa8540b55a475a8965ed2bbb779eed13e479f6ff39cf67291e38be4a38a6feda2f5d28ea8229c1d7f8a1d13d0c240e51b068375ba6593295d4a6771327f174a2995b3e39f6c4415e5f1d60c26db5a2e030c7fd1c286e11bb7a70432a81e39740e94cf9723e3e41f39cf22c6276eca3f41281eab9e5d1996c122b06647ef63a07cfe2606eaf59ac95806e583513a3a38364754e5e8082046072cb262074bb0ac5a499bda1400e6b43bb165faad7688ea34c58eb957d77f533f8ffad730dfa19330287f5ceda93d65273d18ce45074fa5533905ace1dbbf630a306691bffaaf027157871520add6dabb81765169e9cef31cf0a79c95b37256ceca59390361a50e2c7113dba73f7defbdf7f7977be9342bb136345a75e5a25b24fb7439086deb9745f433ac628da49362a5e5940771600cb481486c8e260271d906e2b20dc4651b88cb3610d72dc72e3d0371bb04631a3742dddd52faffff2a3bdab0d26b4d54df58976578c9e6e026a69fa64e6946d22da533bc9bd158e7a1ea78a7fb21e4236cdd29cd3627ff7c397e397e498e5f8e5f7e795d19f6e5e3168f21bf11f063ecb8453baa4c0869cde3df3f18732f63ee65cc69cf183fb5d60948433ec20eeaa488f9d3f2135493ad3dba93790e9894a4a4b4b4c4946dd67381b19a512e7a5a51b7fc69f949b6c5d8926cd00625fca7d6fc18ddc99c72ab649b96fe8769895a269207754a69b0e49c33de9f8ace70bd26a85fce9452ea1540ec98bfde9fa379a79c522e5a1c4f87b815a39f5a958e1783e0513e70ffd7cd073c3becb8ead895e306b17f9eab870d3b07b136965b202fd83549b695de9ba2ab4e5b23e7e6b512d21a129c89ab992ebb72f19be1b22ba72f3c17dea775a03c04f5cc96939e690af54c155acf5081d23353a4f4ccf9a267a4d87a268a152674529567609d478b8e77345842183d03458cd8e2bbb4859e6172a1679e78a1679c50e99926360c3db3db756557a2d812ede5d7f5339ad92cc711bb72f2599ad21db1531eb2c4950c0cd50b172d626058acd8628a521bcf2938fb70105933315ab3b4e85393c521efc5b62a0a59960baa8e673db9e21098063967f34a1699844cd3cca6699aa6699a665e05b1496c9ee697b379258b409bc4e6cce25c9bc979e7bb5049b758e9eb64085a3859a5c0cca970fbe86d9fd212535365e1feebc3aa8efa24fc09369961b60cb31df11cbe2e534461b7dbed7641769fa3f3dabd7638bc7bbdcd2a072bc43ba1f37aad7ed824debd8262d7f372edb26280c522f988c7d35fa0f37add609378477edf6ee784d2c621f98b8ff8089733754736982658fc21c5e757bc59ee42c1c6744377563caa8e14ccb66e3a1d9ff3ca420fe5b935e46dbed6a0d1fed8f18378d80f54835052361913532980830bb7247070e186444f76ad7e642e424fb5b5b440b1af6da4366a6d61776d376c435acaf212435a8205a4259ccc45401239feb038de0afd6f192022e43fa6ff8c6edb1fe98ef55184a21e707bca6f16bec7f377f9ffff7ffdfcb5b063389975dc23e0c1135bc810db29af595d7e826d5f5f9551059511b4e003132756e811e248175914508860d0a16e8087edcf0ff0fd43d0dfb5e291559dc7f258fed2e1232c8865c325da5527c3324de74f6059513c8b0526a3cb5201b398c82c2426231cca3e27c3322cc332cccbb1ac425e0128c37086e936a983ffc378a7460daed961933a3849f67919834d211623599651213c5996cb321d3bb2926c32cb4a8b437edfb6ba23db2ff90b03169b38cd1d66c22109fac14ddd28d7c864cc78418c89e961935936eac870d6c1185794f1f560b884b486c4ac1dea0b4216e3978d0e6a442c46b16f181fbd8468331c5e22db6cd4464d3a64457b7ed6f33c89101942e4737ad8a88d9a94e7864dd6d0c1dff759242a8c554460aca8f83efc19d9243d797037cf42a11b10aeef1bd051af5c87383b299d7c85327042338b92a24801063424e087235424a15284922d25d071934373769c68276fc6aac5a8082615826628390052c10d8caab060090a27546179484615156f6e743ee041da0181b423ea2f9d22ed76e8df29d2ae68dfa4f0b33833313ae5d4feaaab72dc2076caf5b8756e5e7093e4b338abcfe2cce0b7f5de2b850b2ed010d26a3f876c579e6388fa2b1f7702fd9ff2da439cbd4f25034ff62b3d7da85dd8955f8dbaa997fafff4d24b2fbdf4d2a717638c6fb5ffa997d326c16e01ca575c16f79f5e7ae9a5975efad4d6fd36947280c4a6515200d4a310d5be2833a04e2dace7546b0bece25f581586a1532f2b605abcb0aa2a63573a869e71e96f9199467f8bbc35fa5be403c0dae84bfe0d2dc698999999998921ae2effa72ddaa22ddaa22dcae94dbec9f181cc6539470e3de39f8e74a4231de948675096b8a23c090e38a04183060d1a1c88ab1a5ac7c68d1b5a001dbcf4a5aaaddae64befe0f0806ad5566dd38300c8c0e02aaed2dde8db0a73bc8faa0ac9cbc97cffb6f0df6e53a405fac5f833c7b8ba5ef4ebca759aa36f8bccd1d1d1d1d1c981a1a2345a40d03c18e34e29a5547f18db3e001040000104100620ae64bade81f2e9d104d028eea369b4eccaff2ff0a5edf81f7fb8437bad15edf15c403af6f5e3e55e0bfcd0473fe7b1600b68f7787c87cb03df65f880e7711914900b83c140a95040a8adc7737d6f737bf4f0e1c30049ecca6f6800940fe596e2fed4007c8792165af87fddbdf7debff765c2ae9c7699fef8628c29e5f7f2fbf6de7b2f79efbdf75e3e81beb3a2b406194a378cefaaee0b4a73c62ebc0f645e829cff3d92524a294d0df56d3362d8b457fe351c4a7c303435e09c5984a7153ad47736eb1ba553a8fdfdff63dad354a95496752aeb14078aa60699de9dd264a121a52c4098db9f6686a7fce9cdf705a7aa8b69b2eccafffefdf25ecdc2ae3435e8dfa10c0cbb9221454e7eb4fe8b29970dc22afa8a6e9158218b04088b647fdf6ccb0d3720212252e19c5b1b7fb6e70a114458885ab0028b93224360c6e284c910967b2f2e3274f50cc4c50c612901cf86b00441a404516c6868686868e865c51a203ed5db10d157912125980cc540098fd39c2f93a159f6fef38727f0cb1e11d8a274bcf7fab85d86907f210fc3560f8f874cc7ae1f2f1232ad56e67aad1b1d3bbe2ccf074c3e183ab54622bc033f82311157e19fc93f131ee32970127014781037c247c04f508203fd0f711378095c04fc8717c1f8bf0ac6ff55fe1fe3ff2a355adab1c88245600d2caf8659fbf7e2ff8b71be65b65f065129d1850a860c2b869e9131736f93dbbd8dce8e3a1e5adc913de2bca5b24facade5a2716154abd1d4506c6da929dd89910116c9006bdd721932c012cc8149604ed300106a7c82252db4b3590f8f2d5df4979cd2aebcd542f96825170d0dca472bd5cc60019616c8c26268982ef54f529292d2d212cbca5822cac668c6154de7298a2b1936bb32ed762b0d5bc9d8d8704083460d0eea924e2733d2c0a7064bdb64cf5ccde1c7377bdad39ef6b4273eb26755b2e357f3a50c69686dd240f968a51adab46103e5a395b44e9b375eca1adafc94bea58fa94ec1e153f51dd5c0a70d7cea56add5f23bfaceef667392929494969698b4895d2fe5d7a317a86a0b6ddcc027ac8b86dde8e0050ac96444b3aa84f6c2812486437be07da5492acc8300e4c83122dda19d4b40480686ac2ab56143d03dd07c841b7c3fd4264841203f5e72f44fa9cd529ba5364b6d96da2c35ac5ca17cb2ebdaa687d9539ba6c5f91cb0082c230096416ef40dcae7e21cf87eb76fca97844f7ce2f3fc8ef295414097cea8344dd9e62c64a30120200843160000200c0a850443c1589ae689e0e20314000d5c7a3c7254401c4783f14810e32808832088218418600030c61043185286b200bcf080c3a348d7876db866f6a26794e6ef248cea1d83a763c637ec943c36d68f0b39ebea55b918891650f9c3225906efab33c0e64f440dc7426f6e9d7126712937f6a1bf3f450f7d390acb105af172d3ad4e87d9b9a2388069995cd65978cdd5f0038ec7a6a88fb9aa379639c482a6c9f84db5bb991e417416776e16f0656da0fe626d0726153959a14816423bc979f8c5f060f21d0b4e1e397dd7c4383e6d686126fbeae6952489d050f8846f4e9e2d1261060b9ceb362b6a3ec027e47ec777d6db6b9b127f642a4c63b06175d2f782681322effa4a8a8a6f6044f133b85384a78be12ad50702fda2244670dedfa227d847c62e2c7b1afa0fde99a92df50feca93714c85edfd76acd7f34383b5c417fd087835b36e3c8e965e8550b3993e04461353a015f086a19a495f909ab742875b119d0c4a2fbda75b70ab607c0ad8c6e5fb8392e5f0ebdc005de02ed24ff6fcd20c997b8f6c6d7db1e430a5779f863d0c28e35a8f4cfda14ee368769053a2b5825a55b2db2d098237c808c0deed3326138fc21a9cef8aa72ad5f0ab446863fcdc2e2d35bd34f48075a2b45865145eaf84e9a21f15193e5755b24b519d2fbffa95e51a80019c946fc84d272c1482d221fe40663b2205feb86ab6ca6f1438bd2a6042181d17a806b13e27a9dbcbe9ee9a3e1aff829fe5fc6ce48973ee00700d57068d41e84c160fd46fc05168306816561adc4452673c011093fe32acdfa243db0e4272d120c1b4b3f83504df40825e786eec4f13dd0be0d5ecda3e5e233d1eb3318f07ba4a8022b42f315c3d2a6d0667215d52927eb4d770932b3912142194a6e3a3fe5325eff06575a673157633491f2d49a737288c49c1c205792ee1a24a1dcba011429682ef18e2ac93f8d6a445ee0b01f10f58c825dd6ca0387bf4748208b49e8210a353e1e63f30478d9b47b3085f6d50135cb902c22b5acb91d62e1cee1d8e698b2c6194a453976ff23b31c6cffefe04b7316a6eac62672fcc05590aa246f98f5ce98085956b3b02abbcd8fd26f818da0f33eb5e0c26b4d739ce0cf4714c2e17b816e7a6c5352da7126c9fae82b21043e2702f1f3448116b0a2264f595320db095ff032e72bb884a0265f375ddb5194c09e2bedf78fe98611ef60c5943c71ee658406540c315271e2d4c60f1652386a69cb9678cbcb227831c8c7c6eb30fdad87e477e5192392f90a078f73bc7cc4aa23798e5c9911240e01b995ba0a749f7d0984a9463209a6a9c8a3c63fc516638baa5fc221bd194ca915e86dc1bad2fc2a7891d22e6ed90e84903f595501586914bc7df38a91224b44888b404889bb49f2ea0a6bed066f725c968254acc2d97812b670acff0a1545d25121250fb2105020f84a3793996a62691a200d1df95bdece1cc764fc20ab3b2c5c4a716886e3e4300b86d628b4151a0013944c99c2b36a4ee63e08112e51a3904f132fd3b7e3a54cca77c168a297c6e9435e9e1d6871497179086592fa15cabdbadf60e62ca2ebe7d6c2e7fd9b56d407e71dc0aaf5d0455f3a31406b0584a63c3758cd0f354cb4ca58d7530bf3a28a9b52567f03e61e1b8984e89cbea6722e61b8de3d94d646684a442839a4dbab3c753d41e18f8e0facdc3a140b528eb648931c481d8980bd7a5a7b11046265370814837210353736fc46ac950747a3ff2d987aaf97fed71b791800fd82a04e6dccd5f12a1921367b3cf07496cabe7fa3b0d3e880d5077123fc6850c5482cfb922937e33175b72236fc3f1225555057e5f961344692f637e867b8f6ddf723a3e03472cf475d518eb58d46395b80f8cc62e1d61cd0da3ab79782fcb1edc0cf4db20db6bafc205cc8db0b13ac87360b9b61d0bf684151d9bd67de3ccf595306b2cc012ccdd444e169e191d3c3f9582a8bb17a0a68e171a3e3a414113bcab73c90039d36853e97b7fc9dac6faaff01fab3af7aa6d5989d34b4b6e2f8dcba433a11ee55170343d8817ba51d659e17cad5e1fef210495d559eaa64b2b651763759cfa6694705b817ab987185cb6e0cf024b229140c71dac1d7505efdf30a26aae4bc76eaca5da74e95a83c2de77032179d7697e54581e2a4c8810e566f07ee876be73237f32780efa1d0295bf16bc037a53ba0c7c853d5e85856027efec56927c659d8e2914a34f5f524985f2fdf49b4223439fca239facc0c81e0cda34ed2cb8695a7ab855f2460b68665d1163b259beddf852a4caeeb3808bfe8a7242f0c3ac6809c2a196182e6e377a7324230ab932f5553f1a0d3fed1c88688bcd156a0415ce8c8823b4a2f28b9411ba8d147f71430c1115bcf5845cba6156d88c9460e58f8b70b0806233e29a6c934c3047d5af46065055fce000d83033d4a7913c08bd6192c2064ce92de9675052dd4555a4828f236867597a9a888afecfe02a2e4560b696a2a74a947f5990cca3ad239bcd8384f6799cf80eb96b2c63cd1b372c6f1d17e633b6ae4778698bd040ad9e92a70ee08a0ea58209ccc39d9d9cede3037f1e63a58c9ff2914f33c5f14ce79bcb83dd90bbc0e5358757c1a0808078b9dddf23724bf39f877983ea03b95a57081d3e8751374de01eedbb87b072627070bd329931af602355e62b2f68a5514a1dd8d653e52ea713cdcc31d372a9a91f088b8f01314af0d6642343f8a9d05ac479d81bf27c14f1deb5ba9a9c99fa2a5f2ec7dfdaadafdb17a614934280e7c821a9169e5a60674062b443123b556ebbc7639f49bda8182409060a97101fd43e60f5e64877641a562f7883d322b809e2040025f91f1c999b45b9f2bd1258cf6156bf60b1f690f14533f96a9340c2c2c3ac30c3cb7ad602cfe012a428f4ea0993ff6682c3a2f3ab8cf36ee559b90f63a2c675baa47eb8d83ea691d4bbfc63d31ab67a46f385a46b5a0923197614e0ed0b6b6b92d64ae4f4843346767e61958ba0ad38c3611ffaa6f4ed21496ffa968d3ea1e4c6fe4a2ad98edfe3c5f84b6926e9bfb3cdc5fdb75a8da3a20c2d3182b647ce204e185ce291a7a08c28308d9de83f5f643e854b0f624da1993bb89472bcb84e1915620fdfab79266c70670daed2208386be883848a1cbe7e54ee585331d25aec45106a60c4add2ea3ecdf9252d4ad1ec5a61f42206f14f13ce6cbce5e370ac132b90e4757cbd9ed617db8b797158d1a0b5e476c694813fb4897d0f44a91a992e22c94d0fb97332b3002b7f4b249d48d6bfe1fd529d26ee25daad2c97a31d84cc822514a8a013b29e81dd3e36c4991cf21777d2ef38d426a178b654b849e4712930354d1dd12c0b3ca78d49c346bd09a7659528ae399eaf3537e0de931019d1647b0f12ed2c6307fc8390852c48cb1cd1e5efef4c40534b41448bca1ae5bf4cde71ce6803cfc02605548d6656410e5a182e1af9b955c35018fb4fbc115d7e5fa19620a21eb4d596e043b3ba7ac346b1acdd211c0c059b86d6ef80ef11559c0d1b3dd983af460dc9a3214ed45d2084551fd0dcd2c4efcc3912c0c6b3cb3658cbc1fce893a3e5625a8925c4658ffe46570e9618a95efea9fd47b76462415c04c1c3ebea66d163a1b89c508b02bee2aff6945c17cf7a9b6f3ceeb0ac277754fffe7c3b73994e19cb537168486fe3e5adfe3b2d3c212a70a4783da36c23c15068765d9e6846302be811e2bb8fb11600e45c54dd5a14e8e42b9a220a1b0d386cdc7a0b60d3ca1deef8eb32012f00bbf09bdff825606b1da6e995f43fb9cfa439b9a86f29c9be8fd50c6692f899082418d4e952bb4cd717a5c2bd9051e48674cb04c6814497468a9cfe568d3430c6781cda719d720da97d5c5211b48288bee2ff63caf7465eaf16780eb42ad96d70d5f5ee568352bab429baa2f7c6e89d6d9dec2ea726edd0952e7adab1b09d39b9f1e213e79a46cb6974523af05a116fb413d98acb1b3bdd583011ca6944e61718962ea982d7de624ab114eae4e3b13584f38f3a4636ee13de1bf898751837bb2a6e958b0eb4383369e99bf1c15c5371dfd90ca549f4fdd6cdae19d53099fb9b11cf532e0f62d2a6a1641319250695712dd124d9ca9618c672fbff0a60168d0bac4d915a9343dea37b632c90ecfc60ed55af7fa9be33f3c17ab562410e9deab7aea4fda0674080f3177155584f4af7842f9b98e4f7c815a2c8b75017109f08525f71221d1b03a79f37a96547483cc2f1c8b999cb5f4053c708181c194a3fd5c009495c3b16c5c65cfbcf8be71ec55b18a0df2833ee28d40f01b9ca4ca06fbf6fb95f68ea17b22b09891af7fb09ffc4bf0d4e5d8e817e5ee8666d8ed0207b2dfd83e3dce647c3a8447a07ddef6137e8c4a58f5505b02a7146bad60f9ae35e4727935271527d88c4a69acaf1fa9d6d96328c7072993e6d51fe052a0d2b91c31228f26f8c4926fdfd5814d5e2571b7a88f30bc5291b2a5443f4acdd325b8e8fdaebae0bc10eaae791606e6762e691f7c89c5ead19077e163dbf251b4f97894c842a97cc11168dfa6826cde37e89978763a841817c97a89d104fba6613f6057fa94928c58fc0ad60292c425061ccbfaed88e92ff7db1588612918c3a0fe6e3f672331f6506128b358c5225e8c97c34c3457bc4bc68500e0f56b367802ab40b5daa5e4674eef0a689df0c609ff6da39b4c53e9e54a628c046c338016ea22c38b62b3d953ee3138afd1c2b0220024512d1e45fc4c634580dc0bb960ce3c6fcea1c9a39ca97a1280402902f9d231c46036a79c4e60dde8385b0e75151f75d13132e38e975b9cd86367e30021d2910ba589198128e59018f88b024c19c010ca660dd3280d54358c718c05217d60d0358ad1069a1645f40f809e8940ba86599e2ef3ca8d4ef7507dcff8a9e1acd5827cafa1c1a826973a91dde5d8ca69be2e6724d55b0880e869d80374d601f98c0d745b1e10931d527d1cc6424f0cf74c5891f010c7e62fc38461b2cd372efb790f51007b0fd8021b52b41f1c023579af7b651eb67c899ad6f183840dc8216df717d896982102aeda7be0baed70d848324ce63bd92abca2ef024b67951df39e117026d4a8a9e887131b02e2e83ee0a64ef14f288bb063e3efde72c49f2ad6ae3d28c1d4c0649eca88ee686ce56380c24b809690a573f8b0882396677653081bf2214e2771d5b31ca10f09869d84e36ee7b519859cdf4adb5f860292c78dce3e45da269b4209480b1e945569978b566ca861b7d3fb9b574a1562cd00669ef1467076e3f8bc344da1407ccdd570d8d8fe5c9e00d4c582e1b50485df4b08b165025b7b54e3db41f7d3d62b3e248f2820117db23bf97a6d45ea2fd9bf736f9dbfc28a0c1c25c5db3dd9cbb9df965d75ee912d03468b049ae9cd8f5dcd3d422a36792ccd3099285312b5fcadd3a246a69d91232ac6a12572bbe7fcdc465bbcc5d3545436ea9e8b8002b7aad3316d5871d2f291ec80be0a67e5e4a681443714a3e14cd5076a59018468e48072a6c84165614919ab651addedc4b946035004b24871fc0b62b0d930d02b789856f42325d5855681c2efd568ed1cb21fe02f55f4379f78e49e02656a43d0ec9aed01486af0b460b7b02b805f77c4980fa58d27185507627c771a687ba137d18a4617bc8fdeb5ed501a3662baa64203dcd785e68a4894686f34ec754cbc869024a96734af951ba0715db1281a93872ad39e2393b08dd96fcc1408d07c9880c11a6c6dca5514fa585efa84c13a8e04e272c9c5932cbaea0018304e8d5e15dbb136bb85df35a41110186f303d78cc780a48499f981ce5c5aa13fbcb0c381c3a6d86953af47c555aefbef33bf85016cc9b09f07976f2ae4200472ac360d4acb7c565cfa91d180be99358b86ca51035573de782b9e810a84ac541d475705a0d68ec677d14c8e2120392069195971460f4ed1fd438cdc74d8a02499b3b035c59818062e85d690315bc0d9c304f48a8ffa2871952f628ecde5d1222be8c05fa9ac735a62d6319c6b716927864343c42d1fdce1b687ff1bd08f5f5f4edf150c05fe82953f8491607dae024aa8af6b1567d95eee63168b6b1e0e89f7427195788dfd90c8ea2d29aa31a249b7ef461da7290b657e73b33e416edb17b2fe7bddff9e13272140a1f7ca5510e224c3f94547567ec66f41ebe0df198ad23a2decaa7d1ae52923d05504d05cd985a7abab18fe8d75db8fdf64a55c9a68f367af7844e121f33c08c97807cae070723bcab10ce3539971dcb62201c0af114d6fe22e722640227b393b88cd469a51d9daa3f1b63b465e4dda1d1cf81a4327b97a9c3a114a18897a4afb466fe3f7680445f4cea7eb465845cc0c848c1f3d1b690b4cfdd42031585c5e976b2fba7b93d63eb34369c44b764131efa3412628d25601f052c3e5f084cd287fe6a52d0d16186a9b81f58b92cc02b88d0d73372897e15894781e36a9320ae27651adcd7d6beec5cc257f5109be5fba8dec9bcdae340802d45c01cf2f0c6d3098cd03848b270b826d0583bcf7730e7c622eba1a663f9f783c053d7f5f49a51b5e3ea185f2cac929f0f1d499011180520fe6982f96ddd443a0b4d5fc3d118eb36217866955b3e42a625349167e8db8b20a84615f27fc1d008030ce9eda68375810c4db3dcd0c1add10e48d1971841bf2414d2071fa743b31940cc34caedc32be528e533f6eaf151454812f5a398312d08f425209000fc23117a6717409d93461c6f69f9d90cc2ad43ad6beece31ca6236f2608e0db45ec388c19b343689bccc8fd32a6ebdfc9fa9c83e0bf6117178fe3351442865d4fe92594c3c1ebb60bf490c3508608a551bda22cac694c245e77d2ea1d8da8595f3d8884883dab45fdc0a50bf0893605310bf4872610dcd88d21e1f1717b3dc7005da1e939182e50fdfc874f21df0f5cc184c02496ecca431481010c164aeaac62b652eaf3c02a28ee640e3acdeaa16b6c6ee6c65595ea15a5aa3f02b1adb4478060684c365ddd861533d0d527bb3144365890dc3695235236049dd47d43a394b9a762bb2af0a19c249f50a75a42e551601573f0b96eb3f131c472f3024e2c4c917426af892c08882c82550022e73a0dd0b256c8af8401ea6f129b0086f8c38a1191817c8980e7ff314a4dc3775b2da7f168a18484d9d6708d5314b7431733f989085183a7e2b8c9d778b8c2eed8143295161b182258f976277476abac09c35770983683fa576267277a7dcc1990bdb86055e3b26ded5ab95b50c5f582e9f32d803d781f2079af5ff9be5adc6fbb6717c55562ea8e6e9cdb6b10f30f51e320afbd3bb3ae2e8988af3cfc2ba10a989817320e8dd0158e5898ac782f3f9640fdbf6e0454fa4efaa1d884650068bb01c002f943112301a575a1a91088e897989a069ae7a3066401cec0e89d835b40bff34931dd9aa5dc73dff33ed0d46e90e2923586a23844302a3dea333911854535cbb8fc8794be1d6838d171ef24fde35ca2baa1e710c476faa9e425033b3b357da12c7d451388ee46fcdb80c4414f26c24b70ff77f1f052aa6868bf97b503e5c364db2881dca2292caea2bc1e638de6f890b4926187dce4ce293f67087dd8232d1b8d522375146e3f5c25e4a8315ecdcc04d8fbd952fc5740aa1249033e89b7bc5dc15318993dee5a32e42a6532c1c6feeaaa642fb36e1d139aadc8073f5ac7ff2da11fe166dec95e2c5751def14dd1641464736d52c828fd137abbeeea155e43f534ffbea46bbf211564c8d70a5a87b4972040683e838efe101959dda41519253e1326f743e03afd56181e2198573025717685a290403ae800060d30b7b4e97021d6e7b5f66e77b7b4facd27be27dfb094f6aaa28074a7acac22d9aa5ef5651c818d797a41eaf47e545432011ae42131ec7b308a520d46f0622c53386abd2c515176729a61b722f7e6657de0798b00cf28d27fc2ac0583a0747cdb1e4efb2ee341aba3cc3a6e0e71870c9e1ca7535251cb99015df6871059f684435041a47045cfe8f6b699989d734e71175695c3a4048543174483e16a83b41b434c4e0262bd0579eba24a6fbeea3d0ab490b797b7c6e4e545379ebaf4ee621b578cad064b590aabb79ce22d2b68e8c2f75feef32a773fc5a220fe66478d3aa5deeb59716281de5390a39e7dea169bc91f296e934bc2624e1bc3bacf4bddadfb11544f41dc5eed26b61d7e5ff2d1714fc4af81c1a7b20065cfd81926ef1479719711669ad92d0f810c07dcd1c6709708a659d915965cf6c91c43d9a0618c9d63c05fd172ca8afb53eddf14d754d5c3e186e8c8e58a72d49d9a5aa4566435848261f4f2acef5ced9fa185668250fd31321cbdad7d45081f7ada37d6f1a2d2e9a8150afba07c5ba569819ec1e4419359eb9db10737ed6bdacf1e6618c4bee8e107a2ca0aa7d990942d2d207177112c4597616648c4f1291b6b5709d65bbc0bd922f4f0162fadc1f8ddf830214c255d6849a00f00dbb9ca3f82759dc77efae4e90dd040d75ab095a8eda050a07c98c0f3775428d686c83047578b2cbf94e4534f946e629eb87c5f749e41767409a5bd99d4e6eacd68d020c7a980b97bab13535fa8d2416fe6879a445f0baaa10ae95990e816509fd4fbe22c988af92ea5e2637cd19b7a19314e0a31e41307c4d028b33a27d1abd6442539e6843f6bc88ee5fb50fe3333d5066fa08068817093c7229f4dd12326234f92dcb6bc17eef79da276ceaa6d72ed1acac3dcb5f45faf6e2c713e893dd1ef87537bc19945bc22ca8f5525295e5e3fd04463fe0bd4cf13bc12cb7839a26b74c07d814c14bbc84eab315c09651a9607379a732818034c80ceffb3fd8e2e8be4eb3e6a3136aa0b1a889865faf2ed089c6c4f49ea302f4cc7f96b4fbfbde3dbc2963eb48eb1790670555f404fc9c290ae7aebc0fa01214754ac1db8e7caca7e6e510e8ce73a03beb95e689a548f12dff351238610eafccf355c091b07c929d082b5fb57ff49c2be674c34d2fd9d72ab0caf0c0055a023e6613082056e84c0dcab0dfdfa25719b499e15e4ebf747938c286aee8c5d4ffbbbfc0ab1c7b5541075545a368f289197481f086b00b40bcb20c2b2e398b14157c9b65652c31c3f85af9baba4f6b54410615c5c95142318a8736e01342c095e2be0b997fa2ca674842ccc013371d45403b09657ed14bc45e17e74cf25aa16a66b838564798391efc42cf29c1b72ea88d2b3b4d5c280a30ba8250e86de255b7b29c8996ab67c56e9c0066de216a5e086206533b3df17ef16ee0432d0fbabd48c5609a6b761b18a27ae4ec061367a370ceae5dff11b159a6cffe83efae41f1b0e4a8b3226a69d35b208866ec564f0105d9186743278c74178424e57e435cbc0931fddf70f00d47ec67c9150cb3046ee2fb15cba64d575d09fe71551f9a540f13c3cd0ca5fa8978faf650ba5e7ad2ea23320c6739da8687a6919a7331732e199e8bae0c932a4dc6b9d0036620c0e83fcd9c373c209f7e9ab3fb65f5cb66a0aba028100fe28f70a3300eacc7de46821ecc03da9b5889b179e5a01f885887bb4cce1d2bffd125fc70f0de79b2a3e9431f693b4977d394be415536c4eec417804f3ea40245e7d4d1ead55309c7803dc1820680f4e1b0e44736caa2d08b5f240ab84be2ded11f6aef8a53c9ed5992513b851e3ea2817043ac02a2aabb62a40541e126e1b2558151189c63f46ff9e735766c89442299daefebbc0c4f388f24550498a370ad14ed2ebe9d5af5594c6563ad40dcd5c92a5f806da52265d59a2279dc7b43393e756c0d472c108f9cf775edfce70dcec2b9a39bf98e69e4b066cd54e0edc35fc60c2703093b271e894b2882281faeae34112a3040ab5dc5c815cd17f4eb3de17f57f1c04b3e980f9482408dd5c6478685c8fac22514dac70ce58afc3bd27d7f18882eeed6f15c1c7b8519630468bc35f8ac3ecbff587db5048e42717bea2124340d4043845b5d85c03c2241117ded9a54cea2415a9f3a04330a8f3b61d1bdfa6c70c04a533ce4bf444914ce8618cfbe13fe05ec9ed13de4725b2892a9b672a1dc19d5d48fb9c1402675b1fd740b25e2dc19f147e1b93291253d6e36d9dd63018eaf62df462cc168ab2142ae1db8155e8761a1b6fa827b854e40af44efd52342047d74cdb48a3d1f4b52b983107806ae25f533282f4ea83cc22d0c34cb9575fa97ca73e29e758779c195ab3458ced87829e8bd1cb0ab1254900988fe6fe1c21dc03d393f3ff615a2baa0a53e83b15d8f54f97a10ba9589366c53bcad4a0152d62bf3895c0a42b022a01c71cbe83c6436c086c1a0a080605f5d87c71b40c4c817e064d96dc7339ee302d7e50640a9129806b6037657523c18351cb040d1267644feab24b8aa4e9c91b0057ea248103aebfb6f195d928f963eaaae2587362b6905c00b95d7b398e7f564c46651c023feaa6d7bffa9ad29c260cc2ae3ba97162ea5ffcee3c92ba3dedc087309a98f4964591ecc4c9b00e709b165f50f887846dddcb9d77dbacd610f11f40b800df282d746b46f48f2f61fba194cc4226c8166e0ae23d391b942038ad420a808b8d83a83544af89907cf381c6853b7e569832dfd048b6f2799c5a927a548a297afb363131daa53e9c7340ef43951f9fe9cacc1e4c2d56a8538bb81bbb3ccb383a3b9a1a787d9d8ace31af5e26e70783453546ead4be58bde577e472ee5f342076487a1f924df20f76dc3baa13b8fdfcf3187dc76841d2f9e88e3525503de569ebaf4dba27c060026aa4c2d85fe90d01d4679c07a02e10e045a4fdf883c592ae094f08bb8e95f26f7052be60a3fe15b5a492f03feebeff3edfcbc676288bc17cc049aec25611d9925b92a479f54c21dcb96a728ee436c6f052e2022fa78f4deb310a340986e90be3d1f0a6400ab14bdc131be914e2f90dbbb5408b2ec28eee610dcaac02255bb4f55c5df372e109d8e1720322593c45998b2166a76624706b3e8febca634d50f041c99d2234deb8be3a175169c732c6c063e99a0cf420e11a7df152e7d8fa31b3eda9d484b9a83184efba7553e092475afd771905ddafce6f43d2c21636358018c437d2d6106b646f7603a8477c1f44c77d1121637d41b5ffff13c4951e84d1d37a22500218986a17bf2cb5792871d42fe25af593063546e3546ac573de0ba1b7932eb14a521bbc81cc4e50ebd3269aaaa8d037659862d08c6523d8c208a153de1c44d30481b0505694c8e793e37101c9dc19f54cc4f5f4e9891cb113ac3045b99b5231b28a973729d2e80357467991b02622989ed63f3eeae89b2fa6d7a5eb47e679a7198cc8a28d5f4e921bc6b95e3ee58e1648201812c50a0802558f183ae7729cbdd4a0d5d60018786f3b935ac1a647213a3704345a539cc1487a7a6f4a3bea06531e171d7285c1c8003b3b68f26722e686e7e42df2668367b7500a705027c07e36b6d599e4a5b0b4f3dd54587cd40541031d25dc098b362cddbc8f9916e69646af2b22c2286eaa09fcdac4a6879b76f30be8e43e34ed444ae3664e606d7179acdb23f12d8fd20d77acb623f3f046126358b2bbf051119693b4a95e21dc8cb4d9fcc31368c519088f0a1e77ae89418ccf9f848f01dc6dd4285e0fb1050fa607da20802294cb54dfbcc70f2b5e448315591bb2fee855d5269195e941870765a106817ff325edfb3f483ced20bfec8926427d56805d7c93c5dc212de9c6b75304519520610c9b0ecc8a3c7ba7dbd003f6933626c3b9a2408da7b5c608c645dee3575043529d33d0fbba04eb7b41aef96e0da0b84d4cf6a18c3574581b46e4141aa462599d8c3c08ba8cfd38061ab68d57c84669e5c2923f08874e94c49a468c0170606695ccfcf47b27aebf1131d2eaa60685478e1a9a65957685bfb10f271569f3fa7a3cd584a7a1892ad6705a786f59dcf3a2f755c0a0986b2f29da81413677b51c5b68e127b5f86f51bafe4570890301e994c4cfe197ec5988d21208a75658c5f4c881b0a326310deaadd2efeb4acf3a64266903c054e2eb49dfe81fd9fcc95a6aa6ede5e4bf79a35c7220c78bc50158b96251ff46439315a280df215b5562d8c3c625726e6b507cfd26cf7b4180824758516e7f7ba3ebebf41aeaf0fa78304ab9831efef809f284ca2b2f4ce62e9125332198cb834b21e7f554608f530f3b539f555b37523a9fa7e3fd58f9577c0da3b847864f906d4d7affd3eb09d36482093bb78692d107a34ab364a372815532017208e549dcf82bdca812e405dc817c3e65558131c6f5b2ac68a6923c57be2ceb1877c270b0ed3a93bfec1c2afc569adb86034d9d2d98e364442640dfacd03e8cb0afd310af177d77614db45ed073658ea3a3ba19de4bbb4009baa8f61df86fd8d38e8b1b8fabfdc80d2a1a08d9cdef185ca35b6f5375fd07593357621b54010d0d0e54c0465b3e5018bd133172d9ca97e09cf3b3c2f42f9b494a4fdef461fbfe653068aa44d312b1ea0f26998e1e29d4e4ff1b1e30a814f0a2a15fd3e377c10f317bbc316cdcd41c028053a08d9f786a8a77315637ab0cbf54aac9241e488b7fa2951505258426860e64259401082636db4b6cda76bc23fb281a45b56d21b6a817626b0e6689ae1e9280752403ef9375dae2e39d04189ca24b78e2fca09caa474123ddc334ce045fbee6b2b05e19af7d25f70676fb22fce14e9fe754790ed1483c4e6a3240826ba23e206d32206340600946aef7c1071109ccabfa549b7431721db42f7e4532acc22dbc230d9c1b26240c7f6da57354141628edb1089a339b287954cd1f280caf0c5aacfb57cfd667ae6110cb8a623e07781f32cbb26c6b4007444830621ebbec4a950a2f2fae1ea418250c68614ec56526979a81bc80189e615d0aacaca2cb9fd5d74439b4a0210ba05cbc8726568b31d2eac008ece7634cbea298c69f5d6f042257b6ec6b8d018eed9876cf22e923fda3eccc313a7bfda08c82a06261377667dc2452914a04b37246a3cf1405a20c936109597886d6b03a7ccf723580a06217dc8093cb194e5e2ad5cf3e97f964021ae3c84fd1b7e911b224b04cdf4c0dfd2e41bc403f37949ff9757732e016ce7bb58ecc1ac992fa24606b4c5e10b475f495aeaceb8f7fcb7ccd01b2095df7b31e4690a3955a5a116e634b407dff34228732b070af1eb3acc53de94300031af1bbecfdaa84348d3402465bc6e7fe5ea3753cb8b23efd30fb849a9b0309fe1800816210a0bbb9c161da0d04dd6300e2e9a4080ba3700d84b4d0964aa1627addc007ade4b1f826fd1dc2c0528da0be062a26f3cb0e04fac27b2051d712dbcf22ebf0bf78006398d7cde5e3973533e6fa5c43d20b69f6f0d42b268c703ae535e407d71967eb6e929e7be04e72d831fcd436a2ce97ddb9299b9ca9975faa96747f8a772aa0973625d68fc91ed68ca165c4895bdd0f00afd118d9632dc57fb3550006444c20f86ea2135c45c92fd0b52c7c3311bf4b11f2eb6beee378919ada384eeff0dfed63b966b091735643244f45488074b2a9eed8be7d0f6658c8d158f2ee010da8b9f97dd6fd69dc2e82838df10ee2d72f449bf5d58f2f6c670444ab4fc08207f6338bc6fe23a30fea1c4871209161206473b008b84c256df44d6baea0823eb5b313ca80d279dbab5d91b652a0b2abe56019aa2cbc8f836b0f2d73baacd6d37eb2074ae9f1fe33a6bae7cd5ef0e4aad58f8670dba3a621b4b913376b7ac68f7e8b430e7149002d29ef521002bd6dc193cd00ecde3125d4b2365c763067b7c4c4dad30050ababcb7f3668fd2d2968fc069aa0071d29a888ab92ab2f1269526f60c1d7daca4ed2b705383022505220d65bd570b156eec26a1380f5b84c1e7e168864438229f0ab3ad5da0ecfffe72e274b6c4937417dbdd7820558404cea8de4ea6f1ba08867584d0c924a9722c73829eb406328858e56ad7d3ca2488f8452df88d7b80aea22a52f6f9e607ca9ac500125aa3acbe0487c6ff988a58943272ee31026b9bec8307a986f176139eadb8e449d1c0ee231d4ec9d239ff5ed192fabc4e36095a6f5b4267aee36ce98f8e02ac4ffe963b93241d8c3d52f00715809c4421a992ca2076e87462b5a2bd03f3062c2ad3674f79555aeb16633e6954d647e2db5668835125702b4a0ec7987c8541a512ded1e13566bbe1b29e4a01cbe6b83bd68572588ba22a8cf5a656a1679168ec01d694b6db18014d292a95806020234137f4e00f4ca3b06a7f75fd905dae5b9be25279f0a89c32a3c5c4a2e5b9199174692eefc10ab5231041363c17f83aa9aae8fcef7a87673025722294ed4310e72bab51fb7f8547597c9eb65ab9d68bdfff4b8899f226efb753b80c262715c18ad34b1a5d6a77aaec1ab9c523d15b96659f4ef108ad860435d80ec11c57381d8178f3b8443d9b55c1b10af7dba12be9eb21f32e54a60a2e81d0970c3e27bd23d4489872141da7cd068a80a5061272a58f94e066ae0b5895e9a1d7cda616025ce00e1f89b6a19451b6750332af182a7d04128dba1189aa7e95f338ac94d0802a201abd17b7412d6fb1aaa8d21459f53da8d41be488c764f39a1395ae03580c0ef03a54bd276fcf0fcb8cce9d2f99bbee1b46de60e04957252fdff54e1dcf71bb7052d797b7dce4eb75aa0c0fd067019a06308222333bdbb88d547339b7922636ea236e6d5a375415742c33a3ab58400d06c15d3fedc75ef7946672b0b8288c4fcca8edc00ee4bde260fba186bf1d34efb6bb1eccf17c80664f3cf38f4bde0e1cb9300583cc1aa9880c79fae2ea5ebbb6f73cba464c9b62acf385e1da80f8b49eef19c6a7b4807533a94a47c6fbd2924dd6ebd1a7736a98b8cbd209a662e0124da9c07777e0379ef8421cf10aa70ed9ad54fcf8f8f6f202453ca53fc30429089372fe56560a502d85f8b762550027c2512e8b024e35f682ae62db5ca47c9fb38653369bd9adb756667273cb63183c1681e5ef5d8ad88d0091d2e6bcf2a2ef5c6a2b00d623970e769bdf147d7fb3b4db51271df1baf2bbfb093b10d878b139deca873aba98fe19c88499b140ddb9d8e70d49c41b1f198a1f5f772930a32e7dc93376b1c2b4891e02faa0cda10aa561dfcf56fecc06ba2ae5085b7588e3945179707c66e9082605024e68bf9fef1c0431b18be6b65a53a636464f62318ee1fce256b545e374c4bc71ddb1d07066ffd3f501b934ea07686f1dd707d24d0d71f07363ec8bcde2e5214d8b3c2a7cbb2f14dfc98d82f7df30baf8e935ab7f1adcafa25aaf726923ba2ab78262b3dd0d34adf7844975e3d89f3f634e0c66497adb27906dafbec61371ba0211cc184655083c10b3e2722b2e2b268d30f1165fc7a31fd9f5de6feed7c06e7bea0703f95a903a2dea63daab366e6c70d649f14f36755022f033af95c70c01d947cd305147d0785cc8552a17d35a750cf872f5be86a5554af0390f50f31e99eac3bfa8d2abd7ab94556d221ccd2ec95d568d6fd20197a309bb32123eaed30910a56eedd5b83034ddbef2b02cec1bcb8a8e914c22012e3626eca5b472822e5949cd16791220b07897a48a7997b07dee474938011fe2c4575a8c5c6dc6634eaa9333a381d24c1ed34f688913380e7a6efdfcb226caa2664ba3c90f20cffcb2943d5a2bf40d42a49151eac1747b8d08274a58f93ef4d1097abce380d1072e455b31e96c827664d62221e3f6f5d825c83c9dcf491e60b1d78c519770bc7a43b20ce74baacb9ef70b576878b8bfe4407e075f1a041a03cc82fa114fd11b56873776c3d685bc961e9ed03ba4d4399696ad9251f95815162f42bb50f9c31017094d38c520cd78ebc10c051935380f7217e43508f968c5e0faae871ff75439b385455de3e6ce212d89964c77797895570ca790941f8d22041ba6d54894140737f2c2e3296cdb14815232840233e509cce401fca0ff78440da93dcd9374284430a504c65cd7ed8e64827c2661820c9103f0ab021f67e1bcd305401f62d0fd55d44c4886365f86a0e5fad49794ec4e9a08d8b61c12787cd2de3ddab6b8483b0cc537eb1ae05d5b7e044764053d6505d5e6eee82446fd3c161e1f15772ffceba092e241a0f7a93cfd0412a91751422b08f2ea198cc645146884ba2804d3e5941aabc82ba9b879831178c8640913491d2c0251ddd5a3fed3daffa6778328a98cee0d55133cf9cca66f2a53638f23da16f7c8f330cd499b2821bc377f20ff73c45693f364b0e215ba46d8d85280acb767db73b12a793449bf38be994003428a7286d9b91e40b84c9fe83274c66413e2c8ed3cb2fe826fc8731c68ada09c217fd61a49b962d54292c246fc11ebe7c821e46c764ffa9b53bff79e7ad1b3d20e3bf1d447e5b7024985ab7557771b47c8e518512fe86a28e671b8bb7d2312d54adf0c8213a6d2f6d3e1ca6099a5100bd610c2ce93f5bcf4884e3216026a428f8787acfedfb0c9f4f401acda72df8ccf64d088bb4df4413dd365619a107fed82df0b4ab7efb502ed5a3296452bfb9f2f6fb153766ed8cf42b15e741925c01441721b6a3e9d875738f58bb68013b47a7bb614c80860311fed3e58efd02a0362ab0e938676db6aad819f78338d2130f8d7012e8b5c5d9a53e843c7179c20d7b796a02aee8c448c93832583c55318b00487e7e2dd52c108eb5ca88722450b4360ba58de5bc369c11833aa22ecac51c3b21ec942387ea008ec02eed9b2c945b078262bb741b43471e8e6578b0b81a87a20ca1c9a520ecbbf9e77a9adf5bde8a123910fd80608890c301b7d4aa9e5b0a6120437e3e9e3024ba6a92bfb67451f6969578e639b8df8239a533af34e4cf5f7963412f8651507c231f46ec15a619aec92bd7063892ad9f2f902d34f0e93f8af976660d65ee0f5eb40ce94804bc7cb23b061e6df10a6e872034e33e3db4c4f5c60948aa2f248d388df7096ee98b82f2b12d8e2be1d7ecbe8e8ee5b8c899b29df04dcdc6042241388bd75427cba62de0e45808f58ca858a50d8fb623c2e4d53d93e8b932e2acb2d96ba10a632c5e85387d4d6b3f3ce16bc69a84e5fb11e26bc27581d379c534f9f260118ef9c41b73372ffb342cfadc6e65ea6041249a8835fe086b6cf0b0626ad710f437477f1a98623e81c1b6d54dfe3d78a3f954f1e350b38037b65c31ea13491666765b9748c0d7c402eca5ab4bf807af0f21533f5b8ffbfb3cebc4283f7ff63e943b91238f5d104e68af485ac2b237cd20fd092660f4912f795588fa561f7fa12936aaea07c810615088259321f542dc3e2846e211eafd7e141950703868689165323fe512de69ffcbd56804836b7cd706a0d6930e4d2b046c430a934618ec8b83b387363e241102b6c2cdb42707ed6a9a05d1d2ea544b540d89981774fd38591945c28b5afbe896d0e027d307bf6c67c5a97a260b7550a7b431a04e43c12f056df9d37b655b52352c93d0f88b6950936e9d89dbcca6a6628599942e3cdbdd2e31634e42772b9904b9c3c1902133f63556ca40869e07ea6cc34d5aa0c70973266190efbb702861660242523599aaa819b50aa820af018557f62092ff08b2e41244df95694fcefa67a8e4272c52a8936f067523ae14d72b88ba7710980a1f819f9a90b3426663f10d830eb6b5ccc60537d6d47350cfb6a3f5e1d20a843645fabbb033b39117e591f0cb5013f7092ac3336bd78fd3d72f72476811a4374834451561d62132b04e9137ee33222bbba93b238f399c4f064101cecc20ba21da38f5d8e4302d16421d2e574019ab61f4a22cad43c44d9e977d39f0f1c6af59aeb0e98d10962bc10c0d6d8caaf2745a43e7b4e41d938cbfba3e94f4e444eadb0758ae6acbc3f73148f8c9f37b44c42dd089368b326e5d375872e2e75291c1271d2cda4231e62220feb1187cd2a05154f027ce082bb19a734f22d161ec6b6f34be8deb83cbac3b41f4c8b6f5cee37c98b22503cf2f1908cc9b9f3565a9742fc32ac98e44d3087f6c7f729dbf19c9f96aa116111df2836b7663211743487d270129e17f4723ea2ba3972e006ec5b5010299855433fd8a8990cf9ef01ae8b3991ae32b357471ebf6b6bc142e548222bbaceb532a9116018467fda32e4c04f7fe75f536ccd634d8bd0250687da97eca9ebc8d44359dcb299e367d5901cbe25e9d3ef81b7a1c9c130feb324e34ed9c5ca328e3458be6aa6443c70accbbc85b7ad19356e71269df1ec94ea8fb0b401271feb29f217c7c98f63a012819427046b0fbd3255a30843e8cc9f3eddd9da5e9966b6c4c594070e49c2e9ba685abab044c61866e1513b42b677812964433631556e3a63e00cf037104009e6e68219ff10abe0678f6a65134be557a605b381be83d4367d280e00b579a2a0982286bb2e2cd32950d2376ee90e2fb793cb1f2cc541b2f8ba2f4eb3b225eefe27f4f378e76688132248fca6db21f50a4f3c6b22d43886e090828f77be192ed7e77ba2871b284d3e843fc9761ea5347fd79805362ddd592d186a4bf84d27dd0732789f421cc9e7da8bf3ec6a61b1c27d0404e4d205e241b177b85349f7e7e70d55c05e621b92daf98aa6b11dde1139c3dc01636266a5d317e1c07ede9216be2401d53d6ed70e015ede263187c6975c7f32e96615370ab382b64114993078bf5163a45bca6a0edc095278346ddc067255751ec2435b052ed190d01912698bae45721098a72d64db830b4567eef158bedb4aaddcf9b411628daa40166f033bf600628d9926e8b7b59c1c63d27ec2b99c11b7d097ba7c6a2cba050ab403e987b474105f5ffb7ca734bc90e2fb871b0fc73f60a722cd9a05c162922281375146c4ed864ae8b3ff9a896892c3cbcd06f99919e9440691be191df467d3ae89c7f686537961ba101a6cb5017e0bb1e8e3d36c089bfae85f85f26cceb7ad01bd8f5e48c5d6f0b6e3abc5eb3fd2fcffb9d6504f62f97ce326b93ec51a5b0d1014c11eb0980cf965d0431d6f70cd8e669d39d885b56ea31f223a95ce721d2b7e6d089f457b31142bf811c99bb7b3b7d3585a12ceda1f62ff19fe6894d26d4044cee443217fea3923d1951e330101eef901fd346d0f94363630b86d57821a243abe293b8954821361bf805c1be4138fbfe295d4712c11777386c3a2d47450c4419c88dd34a74990cbc6590d24763d4ada8bfe25c44856ccad9e96d7680bea043151d935b1c2b2e30579d69c6026e43cc067aaf6aea5d72008650cccf68b101a0ab12301c50b2d9df7d9b6cfb4d521a3295eccf1794e8bd7dce5c269d457976fa87c02ae6a9c3eecc851c88ab4bc75978937683e9a1735917e0b8c5b6ae1e463f94ec3266350b2b57c9b228aa2206f58b12f11371d11e067561695776ea951b54b467e3bb55e12b72c092abc36467197ee0b640d4ebbc873bb2c7004820935914176913b82ac0a18d892a0105dfe0196de2749b52f63e7643b1d38af662b49979b9bbb9f33d6874d987bf083bc3e8740b974334e70502b4c88350074f6dc6a883a6d7e8638b2582636beae4adf7f2b601fde96aa02ddc875807406456ec33874ad3b2a5d66250d05bcfb172ffbc015f49c2973035794fb5679c6ca8ca86b7344e2e1065085a9ab18e00ff26830998d4f84bb447c0c63581cdd838af510a7b90a0522af6231469f670295d5f3b80f03cae2d5db9b8e65154e4910584ca0e6fea84f5b945acaaae242183d2d9d794690333828c30bb056ded4a5afe43192174158dba27df9aaca985878285579220d4a6ed25d1094d81d45a66d0128b3d31749391f55d923640ad893fe99d67708b94f2e8c46fbab3d54d0a109d25017e961d73e078ee61213e3d291dec5ca8b3bf0e8bc2bda0ae9f69fe2f9e1128856a7f60b89e321b1a5efb324d524880cf68c8b1f77989f7a5e747f7a67542cd67a2d6460bcb2641148a2b07265684046632829d7ce05a750d1802f6929b6b1c94f703ae48e0c53a81a1644c6f5eaa9629c10af02d19db01ae1199047133012ebf03cc6a02caab89585f40902a5207bb9a143c715b05450c86029288cf7a9644eb1a04a66f7d169cc0a8188849c07bb10fcc71187f8f2427d8fa9e9c2e0da8bd437f80e68755db626b25a0a3ea214a1a812feb6378157b43365bc01e3023908ff62e217b4b299394324c055f059a05318fc90f7aad4e5752ce0b6c772169eb85a42d10873641602fcb2abd6c4c7eba6d82e0d855ddd72688cbae6e36dd517a361575d80471d1151b9d304a2f9b2038d6f4a244890265dbd226084d8ee2a83bafbc0c8ce2f052a912b266fe4cc43eab8d8499ed250db22b52798e68db56d2dcaa1a902ad315af44fa12905de5b154926077c9ab21db89114122b3abf087223b7f69665777e72fd12cebb6f297481e8dceae4e19980cccae4612521be4a136f27f4f777e1a2776e5c5b0f3d3ecec2afcfc3461b8ec4a8473e36557e0e7bfa163579e0972b8252b59c20a75ab902934ba9d5f2457b8fb24dd56c893c4b3493f3bf72021c933d24f9025f13869e6acaf957f65e72cda9209ed1c23d176cea4d7cef943d8cee7e923cf994db2e55fa1ad3861326493786de715243bff89a4add9ce7f82e513cfceb19d4f3f3bbf77bfdaf2fc5c485b8e12a81beba80dccc502704b06e631d94612f95b9ee57b58339f32b09d1fe370ce59f8f3e31a8fc98f496ff4bcd2cbb25e5c25189e9d25204b5bb2cf507409c9a0945e3b0bc96cfb2c11d9b924db799fa5d9ce25daceef559b6719c8ae3c1878663b9b609f5968671ececa1ff5986c005900b2a000cdfb8fccdfe71288f93140f45e09e44d2045a0ab88a4ad4f8511cd694bdb1504fce950eb9eae8b74d65afb23d8b4d01536268403c50ae1dc2c68edf5ea9560db6f6443ca7f2086610ea04a0aad417b9fdffffbfa34057cf1bf3fd955f8dfab3c154fc09f227a91bce4175af3fb14925e7be369d032be1202afe4747e27d4cbc8630fd1d3edc3ba5aa30d81d7d712bd47e2bc50d31295f95f5f6b547adf88c479e16b79ffaa6989de71f6c24f34f2bf1863f0ba7f6158ad5f9c4d1f4a34f20a789fbff7c8fc39fc921238e1a7b78400cedf5b20d3af6671a4658f5cd2fd89264aa9a63793e37bb97cd5d81937ef67f2035342cffbf28bf93df2f3f2e75c9e3ce65e8cb1c99edece5b534a7510b73aa59452bfa15e3c638816ed1943f4b7670cd1de9e3144e71943349e3144df3d6388f67dc38c21da6e00c0a0ebcf18a2ebae1fa54e3ddfdb81ece25b2fb571b1c954325dfddd7b6fa6949ef4efbd97d2ef746fadb5decfabbdd52fad18e31b28a5743c80689f40b6972c2e0025b0836e1b449fab7bd88578425c8827c48578423c21fed62fcede271a858e4742ea50a5e2c84b8bb357eb90f7d570298eea8dc07bef176a930aa6163d1a81380fc118e7d11088b1077afaddcb5f73595fd6ac2049f4215dab5b7a6f0a2e2bb58173285651a403bba2c79e7ff98a3ed20128a8e0fb237df4703b83dd1f39428e1278849e083533d4b807e69c73ce39b3108119745844d6d72d2b8e129cba5bc022af388eb3c35182231a8d4853f4fff3b7d6118a522fd75385eda65dfdfa7ba27c5d449eb46feb3aa0a8e386b9eef04fe6679db2e22851b9657ebf8f7d86141f5114ef0a3a6c00df3f879f77f7251bf023e6fb9225f0a8f9e8f1895033420e9266e6e2944ff46d6fe78d3788434c62d237f88598b4db2355105388e92ada209922eaf0544629f8da156857a308380b2cabcbc6d8ff9cb5e987e483a7d3575dd4461d8d3c0fb53f93e9f595678581e089fe176a134843188a2be8f0deefe3117df347e5595fa2f2acadafbca4579cfa1275e0b2d6c0b9c45bd66f57979812d3f54529d5f5ae1a13a7f06c00e64021588860cc4b9a3c81d377d3234d9878a109124274c045d4a4c90ee1abc99026250862fa8a34e9c0e7850ea1d0b4ae810809ed1205165a3412852ef99049a0431cb2145dc43ea02e4670a48b970746a73fa1fea54937f896d0a52b3e1bf48b11ac85fe363dc245114db85801e882901c06173b5074cba6477450f647854e8bfde1a0c54d8fe892e41881470901214e1ca7484185402565831e73ff7925b50112aa033477cf95683a93ae8b910b7d66ddcb74b29a2d7544a843715c1b2d630efeb34bce395759aef5a6f3accb355799c7a03c701a71c098a67381115dd45995555911facc3a994ec88e0d7b6ce8f303916d6b3498d0e61647106d2a99c83a840b3fb656893401d1023d2cbd89e505dc2a7d0bf78696e9594820e5e925e9ebdfec95bfd9fa6fb6cadfec94bfd9e2dfecf06ffe668bfec6fb9b9dff66e3bfd9f76fb6e9310ab4bf890ced8dfa2452d7106de088147d3a3c25bd2fc52c2c267246164ff0b44a246df9cd8ac52f8c52d4140d76fc8146035d9da7bcd8dd9a9a1af48977b0f3ffee304a097de2ddd368205bedc632c633bb000ce089f9f84724fac4bbefb1ab539f3435bb9a99ccd31755bf8e38b7300e737fb9a78a5dfc692e613616f4afecf324a0d7aa63d6741ddae94a599d6c7fd516c96811d446fd15188a29a21f8fa9225a05a13efd2aea9b356b925d3f87d5e44c0aeeadefd50bb3ab4b82b55ba5c79af5b5f6d1a528a6528a78ac198a6af56bedbe6e485e976f415d09f52ba8db1c89df3cc98fb3dc0634a6082b76fdfa225a0dc17115841a457fb1ebcfae656562cb9befabb269118f8867fb5fd87650969ffc8faed6fd7ffeb4a9bbbb5b7777ffb16b3468dc682cf7d2ff34fd197a493d06054be3fef7699de36dd0a60d5fe33317d340038d6babdf9e19947ee0c13e29a594babbbbdf0e709949ef3f9528c62805a2c251844979513969979516d67d8f712f994c251652cb8a8b3ea9bca4c08863880253a31822d5f71e994bdcc195e11ed80f424a71544a679829d69a6460d619f603f7e0cad0da636830e9a0dc94a4aa510c3015a2c4310546e5459f565c482d25966f9f9f5d99362db5905c564efa4505266544852930868a3e8965f807a639e3030f64745092af8a9132533e6ac37eb45a6c00d9ce0f520f6aad0e83dbc1a714c3b77acd1683210bcdb0cf8c440626032bc19ad9bda463cd5c82edfcb2e60ecf56edf4a85ed654e9b8ab5ed6cc2ad88669626fc4f0b1d6c66a8feb2a19238c1b3b3160559fbfe9ab6c255b2a9156ba1bb1124906a6b28d400695cdaa6cb6c4534bb19d75b88f4b5ddea504f2d1c8415b1252343aaf2eb99d1f248d24128dceb2686834bb1b383760377a78b8e143a3bb7163e706cfce3fb34f1add8d9f9d9fe45599b3f48ad9d5f9f292d9b12b0f0527d867484f199f18b0157c768ec1b3f378020b0662d43c263fcbd31626c7135a3060a32d01c4a0f19f1e3c91a3fdfb2edcb45e1e93a37d9777e1a6857fcbc046191e67e187791ff861de3ecccb8f2590fb2f2f8fffa675f298fc9646975f060a346db9d076fe1b63b6f33fd167e915c6f69967cfa384fc20c9854b92eb66f4e152aeec6e5aa498c7644c92784840ee244b92824824216b8e2c4922d148d28c0444a259d37e064195d24be5a965916e1e93ffd36fc9d2ca4eb7b223b99c45da952497c7e4bf6965d2cd9af97528981d90b825dd76fe1e92ce59e3e727ed9c05f3f94b2ebb327d7ed2cdaeaaeac763f28b0f43853e55403bbf25551ea9aa59333f38961e864667cdfcaa9f14abfad93918da4abc90ec1c38d92bb67aced66a67744e98d4ea3647c3c4ae3c1976feeb2c9aefa3d191606e1a9bb96f404bfa79b7b5d6ad4d90975dbd563b3641767cd0fef489203a9b8a3b6c82c06c82ec9c34b9cfbeae68bfd034b69d3f35a3d1edd09735e9b97536150fe045896213c46513c4b54198dfd804d1b126dd364176484fde40832339fa18ef8f7fd38221471f308f1f97405c1ee66f5af4e57139e33ef0df1288cbe3d2f498fc3470d0e6ce7f240a111d977c794cbaf8b8ff520229f1bc8b3ef0bb94404ab0f797c7e42745a05b5866c79a2d1a9d5d4de0667b29d363cdec42caf0784cfe17522645461778ea871ba56666067564c685737337e47abcdc9254ce057d804ee08fb32cf6da6ca63693ab999931c1ccc5714b230d31129263ffa811bcc7aca9c6e26224bc3755833e71edfee09a9b585ceec5429fb876bebfbfbfbfffae6123a924fac4b5215cf3b1313586793c66a41b05e1ed704fadb5e66aeb3163daac69333de605dfa7f5e9f46f9a33333516f7e5b46e67ee1a3366c2602316faf445e1b8053e398a5deefe7848f8d4984dc89026786019466d74a1cdccc426daf8bf16b26bd7f75c2c3b23809254940dac12a19750acc75155d080846841b53beed083ff3fffcca6599661afd7e797c78020e7ac027dfe2bf67abd6640fe45ec13c38073b89c3f671960affcd2d1e70701d568766591d8f32df5fb4276154413b5fc302ea9387dd2da08b656ab793c78d46a351eb51a8a96637233be3f0e25d77397e0940c326a0b1fefa37ddb8bf93eef83813e692d6804cfe3418d7cdf11faa4b53b941bc162962a888c60bc8cd3cce1f7beefa32daf9ea38650ddb63c154358635b7b63a0dda162ddbab8c3affb1577dc7bb1b803678c31cee28e9c73ce3bbced0faa943bfac36dea3087599a4e5763e58e1611e08970f6be941d5ede78cd2e8c62fb81d094d225f4d27bebad746b213e10fabe6317b5714b039080521b4e7180dbcbd5463cdfa5511c2cf6c3987535ca140202f3e058088208629f1f048ea5ad62b8c73e8d8964506339e7c172b91acb2da9af4cab9f041352cc20071e8478bd6ed8e1000c5d2e5701203c15a82e6b533ce8b3c692a4e0af14d8f249953ca047f55c2e9773393793c5908213abe0f2d4180c32d414c05eaffada395763390c8315af2530dc050f56a1e2fabda61949c07be2a07b468b918a9aba613c138001fcbf119aa6a686efdf98ada6b3167853a111ec6ca5cf72b1f1f001b4ebd32bc301937d86479e80a01ec14192aa6bc5f72b0edaf5f384d8f6474275195e68fb57c5b61540758e9b31bd725c006dbdc7d43fddb4469b9b96f737ad176acb1bbfd3aedea22daf1c7d78fffd570249a144c163eadfb4acad7e81cbe518c14b3a3fe20593333ced697d5eea5f1ebb3aa9931db3abfa2cd8d55823428f0636cf754fc163eae7b0b6f2f01eb565eb9f34b7ab1db32d52bf5ad64a8ce25cdc0976a52c9c94d80d8cb03e76458fdc00c7ae5e767d0b24b333cbf256fdecb68651c71ed6ac66ae6e2f04d6656196c7baeccb5a6b6dceaa405f955144571ea0975d7999ffb3af2534c5f1cfba5e2519b4f73c5eadabda95bfb76f79aeecfa03840de8933ad1a9cf420528a5a7ae6e6d0a28388b5a1b69088a89592c168bc97eb0a0053fb118b53103bb05e83f62b916a3c55037e893defe76fb31f3e376a3b7194522f7d4101c671c179c8134b006da7e4c3142a14f7aabfd7cb313ed67326ea507903e7d5d43a040680ce102acd582080912d544cb49205704300c99b55a2071d12269eb490f41b096254718f80baab061d6b2248d0f2d30b09801d7b2648c1cb4ca293290c4ad654954182df0da4a3d51ca79b33b90d500cbb52c995ad26ac1d62a7ea0a684be2f8f3a14f92e0a8618ad07a3a498b95e19658587ca803e33ae07087b23112700620a21b66d82076a24075a040d4350b12b18553481628b25f015476c1c9d9d9e30463d4c70a29623c728440b75db7eb8dbf645d70f14743bd125be2c2bfc4fc481e310383ea11714c715751ee2ec9424e26b5b7176d2db496f27bdbd6eb7db8d07a6824a6228e3e4990fedfa4c684e6a383bba22447c7a362cc3340e87cbb88cc3651cca8995271f29550b7bf2eb76f9052b618cb30223a1cf8cc3617c2fce8aef7be1dbfd408105639c15d8a5779fc6b58830d18d5b964fea8a540f339c450d5161155663a0eb0401010e1020a91ac0ecea0c8bec844784b56dabacce9c55c4b2446f3f04b2acd1db0f8fa021d42c0b7c1b92208c488f9662028f59fcb8e5f1090b7e664e7a957d403e5f65a254074632d417f6e947353d81281ad8023df4e0a27d18638cf10c139b1863ec79175fb3868065bc140efa3431be33a69b9fa69940470411395c0e87bbd574301ad0608783cd66475447f4a967dfd027c383199f0c0f66a04ca6b166fa3413263bdd4ea7cbd9643fc40ff113fb89c578f24b16628c31c6175f4cc3a1e75d7c710d8f883e290d9fae084a1659ece8171bf26837e79c2bc618df1a3c88904b021880a6468d5413354835a69a278c99ffc44972e05a0eda6da99e6bd7b74f6a4ce879817ba007bad96e365b4df6397c0e3bdd4ea7cbdde8379473ce39e79cddfdf33f9d4ea71c7f729d5caeaf3ca13ea0cfec028914b9d7dc97c83ea92de7d9ddb183a211514491c3e570b81bedd34b7e87df61a7dbe974b99b6b89256eb69bcd569be99a682287cbe170b71afd74432a9c0aa7da42b5854e25a412cae17238dc8da6b37d101f448d56a3d166315b1135e5c44ae129125c54101e7739e7d3cba8bffca180f868417ca82b44b6911370a44198d289231329291f95510b9df3e757e2bfafb682720129e56572d3a74eecf35e5cc3355c136dd03456c11283962f5c521938217941ed00a36464821a9ba4503f317c50a55e8f620199828295634ad40f1d8c41326cf00095830fc63066a09e30472f6250379951063484a811e69c615e83868c8d041cf919fc0c9450e28978225c5860714522914854452291e8ea1a149c75b21a2cb5c7a490645708bed5c1e0277a146a08a292ec805a71d220695bae65da7edfbb8b441fcda140715e28f3981b1a893da17a429f7a171b1afaf4129be7ec9e73ce3e9373ce39bb995d007f3a4da1cf995a0d77775c33e333265b123415121592996c2693fdc068b697bd2cd7322dd3f22cc73e6d5be2841337dbcd66abc9622fc40b9179322cc3f24b175341a1826297832951b2d3ed74badc8d0926b00ddb6a33fa61dc900a062a18d00f36a4830ea2bb3bc63639368c437777cfe5eeb94e3ea72cdb27b5d1bab3dddd3def5e9cafe7b94773824459e801d7f96d93aa06fa3c611dd69d26d3a75d4b32146ddf26272827284768834ede6bbb51176d7284ec2a476875b3c9b1d9e4dceed327726c9b8a3b6c72909434ac95a66b699323941354ebdbe404d955cd09cab1e520b126ddb5dae4d8ac996393834428a7669353cb09ca09caa165974d4ecd9a7557499b9c206bd2bd332c0fe1cc202424c660c40813426a35951d74dfafa10dad3f8eedd6fe576aadb5d69af6a54e6da595524a29b5b4de276192195ef0022d72405f30fb012816e78d7fe301a2ec5c9ed94687898d0e135a2dcda58d0e136bd2ad33e4fe363a4376e53a435b47a7e3c49a74bb7b9edbe8d8e8ec3cb4d171c24427e7363a399d219d219c356d7472d6f4d24667c89a74c772908b31d6a5185ab37e186a8f3cf9a0bd7ab266d51f0e3d6c84b64f2bb61897a0f751c73557ba6badda0bd8fe3648c5fc3d68057e9397d5e57939cbeccb7e00c3b647fab0a63f0f6be2bf240b16cb9c35defc74e5497f42cbb33ce9692b7b8f3f87fb398f3019ae9424ff1cce1a61d1dfcdd236b7619c688cf18f5ef477f4e5b5a6e8a9a8c3a4365c54020f6bfaa7e8a047f565a6bc1705687b8f4fa6fc609541b7388abff237a637a1e557de478bf8404adf22963e564e16eeae2f970f26faa4484c2f4f5a0bff86b37d9ffa5b9ccd22b747201580f4fe22d802f8d7d802b0bc7b25c5af678dedfa555421fdca9fc0f22da45f61f9962d56fd54dc71b32f0b77ebd2665141a8a1187e0e2bc3aec227a1e06c90821e5e16d2449248ffb72cda727114c51acb2ae94af8e1001c26ae735541c43e298ee6b68bf99e262ae4dad934b6631fb1870bd463806cef47f8475f4713fe845f006de1b860021260c080822739fc30d422b900820f458021144507224ad0cafabc3ffa50c462693f504118bd479e9508088e6ec213ca8e98c34bcb1bcfbbac1a1be35fc5efc50b6b6992323206975da8e4acd92b52c9cc8c0000000053160020200c0a878442a124c8d2409bdb03140010648442624c361ac782912489511004510cc0300060000380310019439132c407151766a0bd1a507a78ba24b1ef75afd4550f328e8ea21b544ada45587c1a8df7afba5a1ade1ae76c9adaee32c2354ef53cbe6494cc4f6290bfc0c3b8e3f445ef1093793757f4f87dd7d626cc980706064007f5befae2a5e18451721af138a19076b86a17a48f57e0d9c40173aba6b7aa8b81b3ce9bea96724772734a7561de30f6ad2a285d3ca1e1deebe01b74d7c47a6000fa02068f318927899d8a7f4a33a7d7800c88a7a9278442a4d2cb8f52df6a441bbf4504ee7353ac0b48dd278268fb0fd3acd823d8ba3552e959eb3f08a03df349aa0b7ba3a4ecaa0b01461d1beb92a52fc764f2788013a59c5e6ea9c45d653160a86b73eefa270d626eb04bc304a196cc763b82e28dc8da6043611930de438f4aab3255582e372f0afcac40e71718f974b147a89404c35457005d7c41ca8856c478d68aa67caa1f4023e8e9b339adec689cb7ebbf75e94bffe046b703ec53cb411363b9f047fbe8f1ceed31739010aa05b38f2e1d617b5031f7e90897d0a01350a7fa33b1922d2d39800e3b3769b3b1d801754cff9852d7fe5d3303986ef3d4cf4b7bb9997adf449409c08eb0568f51f374b93e63ecc8c6e01968df87950eaa239fc7736d91b02f14f76090a14c0bb9a65cf2e106c954d6ee2521d9cd76af6c0c34757b5d1a15057532a0886deec76c5d7e12f4ab88b9319e653d5ae9f1a13da886e8d17c839822e631497533454aa6eb455dc0737c4eb3f0033a2761d46c149ad7259410a6fa703058bd896e9291d49a5552faf105bde2f4aaf80d834c5e846146cfd797af3ea21386cec7981657a521c3544db810ae6754eb2901097c3449492fb64ae4d0bd63e0b8ecc3435f768710d31c77e4ee5f89a56fa2c60b5894271d71f1c2f35e73a7070974490c4bc9151a530b55c9518002c0cb0fe4b48437eea66a1d208cfba57cc069e7464bee8af0a182e5e9c4bbd932686df89054aa0000dac7c839c690e17d2c72022e338b7aaf430ac00d8cb3a69654de904bbe70a02cda3b6aa5c46189553daf6aa771c8b8371901380fa5e635925eeee1f1ec52f45e4ad4708cca4187735467d5c6ba9584fb1ed264c20173ab1076d585c0a1ee0d74a84e7a27b10ad3071b7dd5f554fa1ba74cbda4b9e77adcdfe635e9d697b76a8dbde9a7e5f4da377b6fab079628d15ec5aa1d8f8abb71c8c86be3c12111d41c5e1a95709cca7138b36693eea3501c088a53530d4ad3acc135b44d8b3934403ab286c268d60457acf47369d830ed3bc9c24e98bae6f0a4e401c44ce054b136f4f36341fcbbd48dbd08ccdee11a63c3b8c0bcb107429bcf0e6c888d360dae5168b67487268445d154b86e8dcf1517ed438b2a1174e0042c4633d5630f1766a80543535f6332cd0da666f327684686936de1539910c5f224a17e0dea0c928746262e71f888965a013a4811d5dad8a5eca2b222196084412306f1c79bb889c1ca74122cecd5ebd8f581ab6e6bb928c9d120244abb1db722ac382b2ab12d2368cd1326b13794acb7f17b004d31d736729db2a6d57a9b1047362a5d8f26d7d2d4b4badb08e1889aa00461b150827cf5826b3c1db886d8fe230635473194c6c5ef34667bb439b52b341bdeabf1e3609ae2564b93e3b4355add5d136ad14d562a1766f40e0ea643d23e92f03f7024bede7804412c5b89fa63a52f380e6cb0ed93f1fc78af51e75b7bb0987a6722a9ecafa1d4fa2d7d6b058bf4adb7bd7b97527afd895f1b253d415b4b18a1670de3b1d7139c0a7324e4e58d881a5d873d1b5b9951853d506a4a9038f1f7109b36e638ebe54901583fb3e4fc63eaf4d0340090c90919e795b768c7c47cf1c8988436e65904d171065a20196fa74b0e71b6018f8c3298e319268b2c9e376217774520f725d9f92bd63407ea9cb287a7a6cc14ae0691ec4d7694d20fbf83b6da1ae90574d274668c6c1f167f6f3fd4aea762fc13b1e219f53df5eaa7e1883807985726d32d80248bba211eaa758c5fc06d0dd5e5320b8a56e237706d7889e3f26cdcbc4c139e9b5fcd762dd560cd101a7e4f917ef0359497477690e4890a79ea0eaebbcb66a72c9009e44f620774fee5e0b2e2af3f6d326f424f2855517f94e7554f20ec854a2653376c1c7cad67a3633ad3705e88a094cb0ebe11597819da7fe0e6d58b6fc834cf429063bab198c6f23eaa26ce1039553a442bbdff4135beaad75bac9df39be694ed20c6e2bbe802337282c5713b5115efaa8a1dc0eedf8536a7ef8bf666ba0a7a39d0a169c57bb0e0172498301bc6aedeb05d5e9361d2796492e0574d25620f134e32a95b7f5dea1ad3f928d4d42e624c3ef42cdac9f0254e82ce0fe1dc52ed340305bd97523500a7721dceacdcacbbaf6f85d10a25fa8b18a0f9f60e4ab5412ec16afb4da3a5427b4797689ca735a73ab06e29f4ae722d60d66133dd421ada99e6ed60cda3387cbd0523518f96541ffc93aca6cfc683bc5af61af4f1f4b4de36d30d2ddc451db3a903eb964aedaad702469d37d3457ad721d1c4767fae4c695825400ee831479767fd2ac00f89d5cda0a7356ca180a14f8f5f00ba2e12d48ff9290d058169ec6d715abd86f0f2ed8082e6c2ad8979711ceb00163050655f791237f4685e38dda60fa84de87a8b40b6348199ea83cc304568eb11b1023d30f78543696182e3c7cdeb21554cf7e80133f03d6ed597a0f054c0afab8d34cfba362c75ef604bac4a36b7c836c7936eea087ce5d71cb67814e97ef72ac1e968910eff91515b83c77cf88727c9a5e034c9a9beaa39ad249400cb7ca4955beb792359330292dc9da27f36f4a4425317d3a2576624db503cefae55ad129f7300fcc4067424a1bf7954cf5f78ed39685c6627aa129bbf25fbd4bc297bfc20f1df146e8f415a998090deefe43afef7b18d54c156e9888d5b335d9a3495d3a6f13284493291c9c341cca1f34fcaad318a1a0cc0337f324fc1e88286295040eae7d0a17cd6d81bbfd7b968c24d104049e6a0c8b233b7a92d685db3e31dc311d5da41c81a01688cdf31067377075df45fe955563162dcb8a30e1c021d20585b1f3ae875ec4f65215c2dba68988397b7cb5fe51e60e9442501d677ca3c41d8f6d2485e09f726473b48db83a5c5fd991050a6f96807931706436137ba810fbb137882947627e172b0550af3dc057221a4ad8ba65db9a688af68509954131f392d852d8607534cc0e4d23bd8f8f9c7d5ec8a8a1c36661dd11093307ae90cd788d89209d22b021d06b38e35430cca42085d4546cde9e610d304a93688dd14a42393b817eff06f49c9bd6014daf255c4047008964a161530a43b8ddf142bf34ca735e08d7eb1d0cbd5c44e03f75240bc1594b665a1bb6ba9aeffe53b1f3ee28a9085b9e26b13dd8199eac284a32bfeeab5e4f3361ea3f67865d13d1473bf771a84e6dadfc3dca4f32d6c1cabb917c3a0eefc11781898754463c44826b8aa0484dd5400fc8d385079f5afbd9c6c1b9c9e302a1077434744ca98fa6670eee238e136ae64ffc007feba28263c47e95e535697a4f9343600c06e526c43c0926deda280c6b3bc0078bedd7a9215bf30ff0120c998f7d4cfa0899353e0496c1da6e3335963ebc660f23217234568752f2089d16420b485e525af88915d18075835ea017cc4b3357a04432bb6f131486d15571a46989ea28ee4bc802c946b3f0e09d937d342391dc1777375139e4d5819b487507df694d19ebc12565c7d07b914b14cf7afb99b5988a7dd0b4a0ce13b0319f48cdbfae55b31f47fd18a324e84d29e142e2c904e5dfb89127ab48332d59f50a6650eddc27dfa028aab5aaa1f3abd968b7b622dec6dc1811dc05074556f9017783ee8f6d1082941a07e9b1cdea1b7fde178d420f90cda12cc599f33e01996c36b2e41486207666b802328f424004d9e2712ea0da7f8a3d4c5ad31fd2d85756a7cc7d19add1234834580240814dcc419b4e871d169001800a8f0e2442610cc8c309eb215f51f2ff090c09c44c75cc955947c4f864704da90935432e0a6808044d3c9e9440f0f54bf4a5a71b3ebc9b091fb256c7c402eb6363385559935599c250ec62081530fb71f8e203a96fbe6503d887b701e7d3f2d1cb630cc0448719cd3cfa4abb5875c033720d5dc7e9e40833eae3978b227d838c1278b4725038f422f3c246b9e7bc8a402aae697bc2b0418f6a528c8c8a3a8eae38dca8124cce99910bc6b12122e3c4c05b4ba1e506d05698e3318ad661fbbad2fd489a4894e4393e097d913dbcdc044086dd51978acf63230286a213583fcb1002cbe7800d845029ab20dc9d7d707e17fdf3a10a337a98edf2830e9ea608119223967ff6a71919da3452130b5c2ae46258152a2e28b8fa61640fa7b6d3b04fd943e4705d2c7ba092d310791ce04fcc303b23551e106f2ae360faca308369f4ad56348869e4cb58b23f6c9ad723ce77efc6a1f4bc5a7b2c5d663d87ad367c5e513411e67c61a9165e612accca7b3895b6ddc37f49221ec4d73573284b82f7c386bd737030590f94098b3a35acf39f730f43e21e0defcd0d7895c2c81de9dd2e96cc4890e5eb3728553813b11f6cf4a6d25b59c43d1a2fae33a23ad6076b2029ad9f02e2f58d0c477481abdbfbf60a2bdb73ac2aac3920d624899a7b5214fd3c940969b54f46583342240a51fa908c76114c537040cf340201572367357cda10f7cbf178b850687bf23c2b2508acc0a4739aa07fe93c4edcf3ab136b7d1fb9f0583bb69c353c4fd57fbe2ca80a9a33e8a8976a9022b79e08dcd8239fd089826388d80d2e542a35bdba874e8618819b558ec2a83be682454cc795a2806d8c010738106c192924ccf352623798b0eda9115a0c33736c5d5655a30adc7f9fddc452c56ed576f0e727b8fd84ec2aeb482b07c0479c4bb89c32a8e42b9f63db211a8acf5bfcbe25efc07963be0f2e01f085bfeedfa2e8fba1c89e7ee590a7f4a778c884a73ac05d05e42515742e084ea1e270aaa2a1fb4a3b8b0094a5a46604ed863166411bbb3ce08e909dc6bec72411d2e1540130b74fad735d2c1f7b76a30b7c026aa17fc993d550a0e75cd14688d325d3bdb16456ae49b6cfdfa5781929b0d6ebfa7787c7f4eb659894ef5a3711857a27bc19ead01d7604e74b2ba82057253d8c6bbd33efd9154b42f8431fffabade9891342e8b8e85c2a3adb1df83eecd46215e99aece6a48f7cec2b0547c76520cca666974177d09ca54083144e0747af5db462f4785f71b9d45e4db5b57a350ea2beaf6db199735f3751a73c246f6e137565953c468a16a638d1a49207a0c6fd3246cdd1657bc8546b8e19e4c0dd2f152ffc78ccc3846a5a303a76925ffccd09ff143796f46577a68a89c15e433133e9dae2e6906c54a621aa178a235492247bc0bce38413f309a7480223f36023a23ac6ab8c345333fa904ec1caa3648f4720524db3a2e993fff49129cacd2f5694b2b0775c9da4d4b04c32d500a379828b280b90d6a302bf5e70ad0c96f9858a9bb0d3accfe295d6d721da54d319a6b89942ce7a1bad609ad92df50c0a8544bf9bd0b265bb859e6627a096f1cd347f036102a650866c581e843e9982f998af9ecc72769a1917e8f882feea796e142bda35623b4c24fd6fe4f40f38826b938fd4951cd4aedf89520ba214d4ee6293e5d688c40b52756e7d95eb670d15cfe4a7886b84af11f3e489ea7e6b7abae8c9bb10a967dcbbf91b631407a5353880d41888851d94f24537439ae4facf3b8ff11ca961e4376a92511b0553d55cfb52459822123815d9fefce80c8f4ece0d7e7dd4d0954a006e7ba95a7118a3c4cdae1e9fab9904bd8f16ed2795d063145eddee521626f210749e3f89f3cae85033d54c09507afa0b8e7c23b274bd98fd5fd9b92dfe6a21eac761a4f1f65b7f44164aae6112613564164337b958e59e5bf6c2a4e21c0682d025ee20892cf9140560e25c8ab226c6c5a60cb3c155abc4ad745e01827e4e7b7e66daeef0e0f9de4c9ec529f73fac67e077cc35c0da0c477d1b9ae3068608e09c2caecfe809ee7dcff248551a77dbcf041e4b32c2cf34388a5a6ae075b4326f9706b44658c9dedc4d50db5dd45f70b77dee5d7682940c0ffec054e56f74a9d1d48c3eedcecfe1ed4e55209a4cfef61486522abe798fbc96c7844c8dbd95e52b45a5ef8dd8d8de525893f1042e72f970a95895b5ab0fcae0a4198a2ad02f0dd2dd7a046064c60992109ae215cdccb1b45893523c90061597990f0c584682953a19b03fa59835f086a88d6322b669625841ce2729625fc091fae3cc5519bf55d87cc49164cb9240a2a87606a649c2bf4a95241ced6e160ebb939227ae25915798c3de46f7c61c0c59126fc3890518d5d58dd4a7b4303627227cbfa61d92dd232f13121ce968555fc8e0ec3ea53ab8ae5c718abfbd7e1f444c10c38e1632cbfa3484f7ab8d1bac840cd659301201b4a9ae82acc3e9848492b1092e606a98f910a94d10d7017bf12a84851eb3a4bb58a5f726275d2aba6547bfc9dbc4ebc92082f9c4432940d56ffb5b8f8c6873a7b4aed19b895a4c687245712d010aa5ee4d6a948409c000d1c5bfac1b83f5e4b80cc44287ca9abcc1ceda603fd4a02ae61212b2af64473ac3d522f7b628c7bec8b12fba44e40ef04d5f32881d62f13c76038c951cd2983ffa8908e54305674f35b05f077b8dcbf91cafbeffd7bf6bd2e3719b6bf43d142c0c3eb3a9098b369b0cb0f7911d49b163c32f344d9384bc8cd3518507fbf548dee7f590678b06e297e9d494ec61b9f0caa5831a57d0c473178207a186612efb34ba880704e82c156fc90f5d50800dc58ff40e8c83f3b4855f221d2fd606574ea082df31d135de8372f49281491f7f41cfd6db577cb9964680483309637a8985f6f77c798e2796d435d2dfb7de1a3d1abfc1dd9f6a456660c48a86018c84132873f67c0087ee43003080a74da97c6f1cf8f29307daa10594c7607e18f890cd1b51cad24b3fd87240b78f998b33a238b4a1be6d9301e2cf3cc6eb5757bc16af1a1a57b9d755e51e29d7ff09d743b951f427f8c3b2efb4bc61f18bc04ee1a83a818027515a092e92879680a83292d9cfe8ed956b26c0b596d824034703e687f02adf76d79712bdd2044e1c91d466a543bdb2041ff6359fc3246424d6f7f00a78bcc7fd15066ec202f140d20cf4a744a5dc57c8db1251bad43e44a1f3c0bdb64969ece4c7c085f7d2b4fc472136a1d305e1591e0033d4e2edf442fca8365e90f9e5f11e1e6bbe54780ba17bc5e171bb501040addc459b4eb9ec5f21509f9d00c1f1ade7e86ca2e2f0411ca25e8db0c42ccdd4f16aa47c4225943c9f0fa2cce07a1e2f8a4823abeb2e533b74bfea0bf0bc4b8589260c79062bd4d6966dc3bc054b126e96fd0b59947b6217d811bda0c4f4bc8a527f2e8fe8168bd63e913847d0cd9eb81a5fb8ddb833dba77f133cd32d92038486cbbfff9e4993ac61c8bc30d60b45067c3ac7ded4c37f72254ee69c07803394f219b9bc0577b573a77bb71d8158a3a34dcdd62168dc1ea5fca809ba9a8f0bfb3a02f0f539e572efc995a0cedfb554a16d5d4ca13cc4b86a3d1a8d1cce234ca6c6ef54935ee20b6d0f5adcbc79e16c60594f23c2283df098acf7ff5f1ada25660fc316f7166a0347e8abcbcc2810f2c65452132104555140b3e3c80ab5850b6f5d5bc059d636a0b5c8aa1939421f5eaceea7943658c807a0dae66d85553af2ac61f55b91efdd09b779c80956eb8719c101975ce3e7263e086bd11741da27894bc2252039e50939dc842d35920bbf92fcb75ceada9650591302abfe4543b93e984d5541786954b03ee67c033016c0149080f80403289b3ecfcf7f1f47670d84a7c01cc40d97b6725cd885d32735cbafa9a68b32ba7418bd9105e1cd586b4570b3c99709e339ac862c7358904ec6cb6463dbc227c654e66936d1927f9282598088c632463d88ae1a723e6bdd506284081b5455cb199e1c556caaf0520391c2ab456a0ef6c3622d31aa299a7a68dc996137608a4e10107a83dd929b8d2036720abb120c79e007cac666a99917f7c9bc7175e77b411e77597ee1f8f8433ec979c402888a4a955b17fbd2a619a2db5a55d6308ac5d3d5b9db28e1c525cba61b7ebd27a09c4aae0d559d6d5c26fafc0d505bef202fce81bacf21939c9c4dc70742a687ca305def397eaa90e7c26ce017e2bbcc30f249e5cda855e3945a339619f503250347b9a420c244f8c6eddb865d445f47ab16a31b669bc8d342029d0f524d4f6776854cfb3094ce7ac0439c11ff89ed96cee71894ff71e9fa7a2ead17e68c11379ff8f81369afaef9d8caf268128ff92777d21832822ccb3e68ebb47a2527b956d9ef8ee837e78cf70c359397b257c7973e1f95327ce7cab2d0e7fa9361895ea8c22453490b3c76c3ef58f93eac1c34af3b1fce73f6091f90fd30ae63c6d1fcc087c4ab3d93b2801e673a369624671587295a6382268312ad1ec8ad46a79e1cd20f7e231096232b6a1fa5ef5749995e470087792b2d2e4c3ce8a896f6c299b5e955b4f1e7e2a8d1793b5ba12a4b70f1bc0cf0263e3549581bff44e1d465ce724e26c0cf985c05ec56f34797bac586e1572e2d3f971b51e09ad46c3f6c81cfd70f28a667ab2b1dce12e036d9ba5502686fc061c70c5d55a3fde15f6b2007cc386884742df27697d4027ccdbd8b5dc26962803aaacebb5bf1786a93674c864c0a802be7b580e3c99dbb83412b74003be39ce9655570b7d34d5ae0943d9c9135c9c27a8859e80a7bff6d58567a75f1194d7c29622b6d7897bdff72da9758965b7ae95af74e3b9171d76a14c223ff29a05f7ea821f6466bd98391e9a7303a3b2dfbb42b06f5630804ff4a4df61054e1d8bcce2eab23eeb501474739bd0c983554010dbb8b0c8253a732fb17b7b7bddbb0c7a248ac92ffe7b6bbf69c701dd696e27d9c4b9c7190cf13b69ffe57f1394bf2aa918bbe0081e2fde566d7941d20419a9750abe42de7f8f9975e62aa4055bed98a24321085849586becbacb4a08f2f38c409269ef307e7ac97d99cee43aa1692b5e5217ad122ca15bbc0f8179bb6b28b0a1c36b843f6a110fa4b6cca5f21d449ec4744bbebf543a4aa91a1684815e629db98fc140075590b65454e06f6a83317d14f0a6380b3be392887e4af815f68ab348d9493237ed54166230c3aacb4d5073afffa620cc2cc65712b429b8ae9588062c7cb3e946fe5c13793443163c1e1cd8007a0141bc0e112786164bc4a599632461f3dfa972d6befe36aae8ff40023b0608d05a92aeb1a2fd145bd0366c1d403cede68559aa86fc0e8c32afe8cc9a9a1af7860be6eea2aacdfbe6c36356eac3e13bdd2a8f6ad38e67d1e579b0d03e8a4e54781c5f4ab8dd9ad7af6753b08f7a30a7ab53f364dc51824853ff2cae44a3cfaee0990a5aa8adfd6bcb8edc50520ca901cbc4d359fddf5eefcf93b7b32a68767c550fdc7c8992e86d1723c3a42b34f1710dac4953362bd07d6f3d5f0965c55b0bae53ed2f9ffdc2747368519d90145c457436a0b50c42fad71c103c13a1afcec81d0151ae0a582e3d11eb803e6def3ec07a6000d6c6afea786a2530b7490af757d8f19b8dcb0a56beb1a708812253e4983a1bda9b83461493eabf845e56e3373ea10a57ddae5d75e9529a8f57fa658b568158ffac501d2a67cb0f53d1b605312b91c986afed84b5d150a2440fdfbac91fcd60781b9e0a9b3476a50584983e5c958e2a6299c5301f04bb57f9fa48dd3cb6001d2efe452ffbdf47c9011a4c5c77811f5bfec4acd15db404dab6279cf9443b82e47ad32de31c7d324d2228e0ca43405a5ccb8a1fcdc3011f0e3f20ade5e8fbfb0ce0d8c4d62ed9446aa16513ba14bcf2db6165aaf919d4d0054b9eb4c5c82d049ae1c738bc3d38beb63baca3927e25319f883a2d77faa6451bcec1868251b1b60019d0a6b253c02338a49016fed9df05b0b8043fd707a59733db7f890c69e68fa439039447d4648c80e67c4dd5d8e97672750142a4f3e2c0846dc2f4baba20b0ffb4272e04770b1ad7a24c35fd1db3366abe5b9fa3197057a86bea278a531c1732b324d5047c7cdc8a47f517a0b8b48d9d57da702137c5fa358332232800bd4b5abad435a7fec438fc36d08a71d173bf9576935b53f4a3ec7e194d3aa88a1a06a0bc915fc894d76300b09f00065068bcb54d62472352d991b9294109b8d59bd966b338b34be412d2b80d93b1d062db7a856d6b725b05b2452e1e1ae643a1135b298806f04269fc4ca26bda9c87c203a9e775eb5d7ddd3a0ad487ee30b42993511ac4bd32cb7edf62051cf65e95f45bfb24644225d66e908f23c27e32fcb84c8c70db58280270ac22622d6b39f993935ae33b9dd8a9f21ee765293149b816d78ab56e11323f593b94a6464a05b25b7da80e9f609f223904857abb2d74c1956088780a5c7b6ebd0423b1ffc5cc3bf472b382c03fae767795c283be78d807f7ca777b70fd9e5333f7b5ff39ef83e2480856be53d2530d3931e5c4d86b246c7e45dd76bb212613a5c3012279dfbbda691fc50d9acf4b9e0d48bd7f65be63b5a6724c46124e2c7eddaed0ab6b9a5199d714d32168f3020ccdfb59aeb449bec696788c7681b3621ca3817486c9b80150b1c6d2c2e26ce51a98f17338df26c64178124a7fbd08d1218d68d65b9c846916a0bb11ea4b8a9efa102fe40c966f745d44a89dd690bf72b58e7cd4f91bca81555338dd463678c89d31dbeac0db0508f872e671709b89d98fce5816195a8198b5077236f50e21b925bb96832189a1ce3ad5699d475bb73dc1386157251478809870d340c14d3e45febe1490aecc2d71526e5c2bf168f34d44367285d18974d54b3ad46cd6a1d2790f8ea0166012bc9687f990578a1b5f16532d73821281746c4c0a0c06628bb12851fc70d74b7ff737fe4906b3679a64ec44b7f30078b5be78d849e883c8b7f404086e31f81bf98200c9bbcc762cf7474b0f2be93f1eff10d11aa02e94031c3287c9e6acc1ca389b1f3085123a6473f40ff185905e51e6391b4970d21e8111b28695d623a0a3522d0ff3088a2d8debe5150cfa7a995322695203b1f2ac018a210716650e1c8a6b64f2305480e39c06ee2657f9e7020d424249a0227480c07bdb7027d1241d04832a0fd53bd8e1fbe06f84445c4f6be13ce564ec4bde41fbd9cccfb108c0cdddfbfbaea0a224e3ad2b25b5a1417ba0bf275af16abd3e218b72f1145ed558f02291f3282ea2e41d2c53e0853ef7094879edea105d17b63dbcb76f1aab0406ce285b93d4b11dbd4493dc37ebaa59c87ccc30f3a7733d86d929a5fa058cbb38b8c25bedb2a80ade9f20376990e274c0ef5411208bbca6810e923f677d1c68520792d444ecaac95df7b8952b33285bc859b8de1039400404db644e043c86f90c19479d8c6161e782dd1739460d4b76f624685a3670d0891dbf6f8bd3a656e4f52400f7b01b147a8536433f9faffee935c33e919b05d17e1128ef37607d9acae33f9d8524145eda3a0d59ae5a20bb85a6cdbfd8f10bec9da7f14d84776f974a1be30e2c52d0b4cd4ee538846e2f22143b0ab820b3de22f4f6ccc7691a5a729d67a41f6b3b005513c65f5b83f166e8d81a7250666ee781209536d6f1c43cbda864afe098070a84ae56288a6a29e30f436b5a24d9f3c6e2ef9d1d0103eab371b365877226953073f7943cae8ef76aa0d2de8522634159d6aa4de2238b29cbb4baf913f9f2b0f0237012aabccc72f98c9c68c043cf58f338393af406e683456ea2dcc3738126aaa05f6fb8c2334825c9e350be024d6fd6f7ad271e4c89d5652384b6df5191bb3fbd91e681f788c0d304840d20ef0be2c7e17390f6feb34d730785542507f722fffdda367b0a6b303ddd7d73592aa6b6aaf14e1d2eedc654b06ed992280254ade84df76b553708fd27bdca700639d16b87bf4ff7a54063a7322b1dd919cf528802b61438b771298e9035137dc59380f8e828d68652298d75822e76b375028d6971611b627c3a8332d90601f7c7bc4107fa0747251365a62f6aa099eba421b636bc6de2eceb926c2a9bb29ae8e1f334db2f6d0a1adbd8195e82cfb177176119ef43df257e743467dc6a431f12a12f2e6461b9120d34dd1acb273c59a6903a6f1471ff5c7efdabaa324762f655b5ca91ea259e865afda97028fbdc0109fd25d9576ccd7357472dd58ae275eef01b306b8a1eafa0b01c958de9ee06f44e8d0096c7703677b90b672610e54a5d1dc085265aa06a9a86010b34dca3ef925420521c10443cc4443b82ab598439c2ba714bf808d3eb3cb6370089d4edeec0a8ee042e9dfb8a5677291bb9d430753b386c1297c6cc16c1674a90bf7b77dd41fa49237554a523442286e5df89955c39a733e11cf6add6310ad5d11b536fd38826317f8d1147e7d761ae7ad634b82f6478a4eaec042b718304175fefe118b8f7e61ecc1277eda64030be00ce20b8abc360da007cc6a7b993da71ede9262c5f49461e464b1117cd17d6aa3e88f7b0c86c27705c0c396af60ed3f938342005cddf0033c12d7612350e06bacdf307b527567d5b6cc46bdc533b0e19b8d2c44147f50fcfde8806ebdda4376fe52c353634946ba08a522f7248095800b5d621663f986828094e696c2f46f8a936c62f925a1145f68048708dc582cbe712af887d79683f946e345191ab746ca49b761fc894c9b52e74801b333eb7b4e115969618181ebd20fe8a355d2bcd804735e6fb107c050cd2b4b5a3123798094693839a6241ae80369a6b5c7d65c0593905eb075a315ee44117439ded624937283cf5afd8caad20cd61df2f9bc5473b7e70e089d0960d1818950495273c9cd888f3227cc22a50e16127b0eed5a1c74fe18453fd0351e1b880d9bef34b8ae9f0385e369083ad83bfc2ce7b0d212d2c37bfa00122c6dcbd83b4e7a3a8d872f8beb748cfe9c69aeb8d6c381d0925117adcf16d39c247bd7e43892d78af980fd4821d65a2fdf7e65ce2397a8c331cc575bd58eed1d14c9c914086d460be161079b6e194562a6b7b9f168a7f1c4ff083d991909ccea248d8e0520e7fa09d7cbee24858a55bc33c760a44f5010d8fc3d00ca7627cb110fd43679962c7e48e5fc7642a91c79894ce50eea1a7a6c529a94195e3f2b8afad2acc935dde1ad8a337ec2f7f668522520e05d4a432a10c837d0db98493c3414058761e0b2b696fd7265f01d96384361fc1731d43e608559a9b578c555da003a6f367f8d804eb8d982020e9f3986b7fa267135a5dfb72110646f1aef6a9e2cb51fb9cd1f4a9a852c435143efe89f4acaaf2f68987b43cd8b062eab665696aef9360cea1036bed788d56a337f1a7582dd34b9737c00033a636ee016e8496aedc139ab763c70418bcc8a97d3f6d0e9e43923f782cf169603c8b4036418a5b0ec03da6dc7455b0c74d2525e1b3cb038b73b7e1142182823256edb3dfe1eec52d750958b2381f116ec95d93eded692b31f764bfb3f729e09ea554269dd418225dd0edcc67675e8a431ae73109b6b010f07a9116436907769b4bd769605486c095c43c32f18b3a48b5b4a15071be2b0e5b8cd04702c9f5f2a541c3cd508bbe6022f5a0a058299867241b007c8059afd1dc8873f81ae1a7b9534b886bb1a3a8c1f62bb4c8b96d6744d5ee8c835d8f35a949e22e44a8b8a728a69948a28f32662611e28308b8813ae74c205085c594bd3a4c44fb90147c3a96b19c2dda1b43d3224b243e4e22fbc0aaf0055438eccc5fcf0279b8509a2033a0d9e664411d984dabc2a017c711219b26821737342f8be46b3b77f2fdaa75df14856d753be6e0b95c0ea928da1a868ab791bba9f6ffd5a32f8638f23f4eb4e1c2c54a6cbc993ac96eab4bf4ccf66cbdf1c2051395dee8769540f29764664999b3f166c51f587cbf70201c5f7ae4b03ece704acbb938bf5da4418283348bcaa6bdb67b5e373a41e1224a36de9e2a79172b4b7accf6bc9ef1c28289966c746355f2dc5796fa3cfafed6bd9622a79cc8177896e6f46927eb3c8ed473c76382507d1943ecbf475e3869018bee4465281cc533a2429547053a04ab82bfe9c9508007dd8d6149b67d0f287ce0cc48dfac771535a82c5ee22a0c0422140443c671541447c5715414c71e617129b7120b6471ce1242f6ce3f79b77026d980691cafa0bfd9168b6dbdde97bf61f0e96e90c3247e449c102c069901803b13b8ef3f0ada4f06ab9deac0a792017ef7cdc27cb1ab707e0e2d7283eceb08f2f67c6f082787ab500a0a8c5da05ab62cfca06c2ea0e4f06f913db33f3111488e676cdfe8c71c3aa0b3efe37e78394bf1b6918b8bfe9d7a35d09adceee3b3d613e004706b5609b9069f1ddfa240e9db8163c7d1bdb9a0a313bf2bed0dc99f1bb5f3df37d15abdc806f37f259097d989c064c37236c829f40feb0eb8d71d00cae5f6ca7240cc48a709d80f8d1146ac899251b89602d8df06050e566292950d415bd47f31e431e8741854dfb9db95d3be9f31849978263b2733125028beb38d1f2a8cc5b89f398bb9ed7a14045ead8d1b8ce36c497a5d3067708f2caef9b8b51f2a3993026aa104d46408bfcc0cfd9b41e4806072b7c8db23aaf006e22fc9e87a98649c53470df6de4864f41cf62f5d399899ad6f7480a489fcfb1f1a2f0468b8d43363d9464ae5a0a2e8dd41f4eee1864aeee5fad99469d898c4d562a13b5c1c0fa96afe267b66e60444800cb58d4ec581e5637f8cec7b8ef92340d5bbef65f6786e6586652d81d2a84bfc57ae02fa28d73fb213b9802862e1e24a6e43032085aa061a0118c3c89570693634d59a5d81bc8eae533b3f3523b35a6ff2a827180f97c7e0f48020c8eca925c6c3ddadea689e21d7d5391a15aa4355374c9444914d4f3e6843f109437d79d58d86e5c1916b90734c5ebf39d711fe1ab915d16da418976b828bb6f41f81f594798e3b895c28ac96fe6f8a44579cb14684bbef1eb978518e0c237ca6dabd2f97d93f4edc2744be33ce3aa3c2f88043d5368688facc82660109ea7a938b0b29f8982c03b1559e7143517f1d5114ed1a08dca3f8dd7a985f8c405d30312de416fa136ca21a9a7a1e6f45e2412c88e831c4e586ca126bd734e2b342c413eecfb6107504df82b243db86b03154b26a9fa93a30025c3807ad1d169eafeed6a2ba3927651dbe8de72253e9e3780e650613d0bca56c2eaab8d55d45065d8d74668dca2bbff28f469dde16b46510b570a7c32b5741520c351c9a917cc1fc025e4d1b36ae1ac90823107f666f43127ee399db4740ec8b01afe58e44b965caa190bf3c2b58d17fe9fd475e8848aa51bf56200b260702e7533687c6cb43ed477d0aaf8c59014bf5df6e0378d4aa31b36343c5aa7489bce9ab8f65232893543829ec51bb9cb0978e8c91698e468e97d550ed226efb2be4c8c5512df6a380fc50b1d14c796785d03840ed887b7fb5acc6366967c3eaab901c49fb98c616a4958deb658786f43e795ed6d9e7c23879990fa51d48ef72a8f8e125f340de2e0a80155109280973a54d15abc6603515ea702dc5be5893f63b15edd644c1ac160e64954dd29ca9e8b4886b061d8a581d694cfea916298aa18e9b369fe9e0e21e1b147131368fa817bcf307d5b30c8a65ce1df244fad50fd1e07a2f7c444a2758686bba1b4743bdebe436eb4255e5089f7a832cb98a60370dd58ccf5fc4885501185c4c00a50262df5169b7d47a241fe5f6c6add3dd54ea3d5ff40c8fb060b6950c3a1a071fb8b9801aec3e8ceb23b097b6b4bda01589b7623f4095db591f374eb92a9fa7e5e0a218065aceb39ab6ab5d065460c5318c5d3529e9f9daea6a84aea8e1a3bd6468ea842383932720f5d37914d08b5e80fd366af677f8fcc69ee522f0e768f9ad0864cd3d0b8a9ec66ed66d01efdcb1d695743101e0dd3bb1e8d15e0d11353e95070b8c0d2d36a93b99fea38cedc4c65ab888435c69b8e0a74d1decc5950f2b76101a42179b6a47794fb50d1675ee6d83523be606d41a47ad37d3699198cd415de7e5e359e86ca55648a47689375b3323ec31969d57cefd7bfd06c4bd70ad6e06fbd475abd222aef32b3fbea0577ac420a9f1c2cde1c97594b4d13492e84c911d573287d1f0885accded3b4967a7b4da5b78cb75430e39cd36010d519d0ceee7923356f3edb1cd3e8d95362a110e4f2cabe2d84a0b054d8188b4d080ab718905fbc1eb4d37f19accfcf375b654ef83b2581952ec86d9df5d5eaf7dd30efb5550da32e44e981de29f83139966e91101e4c1e19210a5539491148369982c3b24b63f996a92453a978e6e3cbc22c95b80c45fb625f5dae5305cae8b93e044f74622ee72e6ce3b1358d6d135686a96b993dcd5d762ef498ce608358c0a6220d9e0b8b496fb4c87485afe6105e75b172712ec2580334083da918ddf14d4777019d6a1c73e18724f7d042c477bfec1b98e6a3631b81c98c01a39418ae7c87856dd44076acbdbe04867cd981d1ad3525bbde49e72b2e1dca07cf95f3a5979c6198e7c91040ec60e3c7db219625c1540cf112cd3976e7c332780a8d590577a92c4c33ae84e50986f50ce115e3413902499a18e41195738e1f6ebc8aa7b68a6f53dcb78e9591ffeb90b86d8e502c90b0b0a7c69a1c51b211851a4296e5e1bc0699ff452f7ed2ff5872474d4a241cc97adfcdb4810f1ff0fdd088c0484780c549a08dd131e7560dc715aaa0c8d364cab31f04bc1f0352efd9667cffe101eeabf8755e63dbe1ac240abd5bf447d96a17ecce8ed48357d3b2e69af7794fbb284061bbfc00e6037b134821b93cda26d9e53bab59375e28e3cc9058aead2b62777333a56ab2233cd6cf99da16a215dc89a446af23d128a0bc5ce07e5dd2e5c99063a40d955ff0224577e240a949ba73fcf6854e887f46a5f5ca6757ffd826c6ff3a8734551298febb0adfa7886426d1389d83b2b4bb38aba7c79382f3a6977cd2d4af3c15e8550d64723902281ee7e8bf0335c29518bffad54064330ee24b3c5f17f1b38ba95fb043254e8ece2270f5a14e6d98aa9ef428c4e153ed402b1f7e26eb70eced661f8ea406c729c41367de0c05600206c095d3f6cbfbf72aac050bf1c1a0a7de220955ceb6fa4a59c7d0f1e664a2f024625e256aa88f338905793dfef6b34bf99c1f1106d9d9b759f23c7e863b70471f64b16a963c912b83a4d759a863b2cbd3e5506ae34e82c1524508115e17bd9c25270b621912e039a2a1c6af21341f1a1372ec7495572f01b84d34147e3b809ec0e9485ed692893989461a093cf49c7876eefc10fe159fd50ae8b96a7e21d0318758a9dd2cb3ba305444cf493936747f890c5f043f502ab964788c122e61ba5a510562319177b87310513902b1ffa4b3e356a5319c5727cc02c3e610fdff5d87f35ac29ede364021a3cb0718345033dbe405e6570fcf4ac1ea785b48b8bf1eee78821c85dbd1b035a5bfef19b53dae894775e40ea6d322acb368e91be6d49a0a8362e7c766258b455872d41318ab979212d07b2a617043ebb8c27003344947e1f7c3690ee123550822e94c284941acdc87a40e0201590ad5c59f150ce7e4d346113513a882197f4ec058a07e2ac97dc47b62ed27a79bc10c5fece222efc858eedf7a6e894361fba6627fcb2273715ae4fdd260ef7e9f02f2c7402519422813ade3c764bd25a522d93df478b4037434d388c53b2228a6c5a56b4f23978f55a71b0d37b79083f9be8f6edc6a7bd0eb1400551150459c172804d03409d629dd0096e9405a4e18fc6d81d2f5249bdc55f72d51443a38872b27e0678dff67d509289b03be668e42182043435c60c4d89948e1d4006642332670192a8886b91c91913c5b5206b69169a17e8d02863c422d5cfe29a950bb424a5d2558937c0464f25735300f2482cfb776af8d9c0e3e701de5018197d2ad70a1bed091cb73bd84a543ef764b3e494f5696a84af8cd0fed7bdb16abc56f1cea401f2ab0b0f82ff82041826bc8640a19d5c13b35218227642ab02cce25bfbe40bbe8dbbfbabfa865a6f39bae7d833b5d266b27004427cdea9b59fc6c37397ee2907facf64f7d5e8cfde026b57aad66356ee2bc5003e9661258be3074ea63666515023d8916e199a3bdbb3aeeaa7d8ae021b312e31f20a6bf133105eb30f98e265e16519482bbea319c8adfb1a41b795914d83c007b0c252a9d7a6e0c67d8bf128042568f46def4a199e58077f0462af84f0b462882f71308695e24476681dca1428e46e5e44faea6c3b03ea0838aae3c39116d16374c8a297649f2ac8b75647f626cae2bafac8be2730c3135cc795ec72a500c14864fd999b4a6ff08e537be2ac8c6250df8a69c7f1713e1bd45967abe42147299ca514540bd3b13561df26cac3f8e0bf7ed07f1b29d23f2ea500d3aafbfe6996c08bec7d6a1353497be43e13358a35d780cb0bd10a5eca74dfe29627ea0fa8213626f217e102a451df549225910c0d463967beef760e1b9d657cddf02c265774fd7441913c616bb84a7ddb751f9daa2ec531581c453e4160cd626de136de02f6e9792ec950863ef2ebf75c7c5189738f870490b16c672251c819edcbaf3811709878d4029182d30ad94ef7ffae71058332689b9d2435805ecc532d60860fb4f45b5b301f784d9f8f761bd644b2e7b289cd37753828cf200cb19d6a02e867fd4871f3bd2501d3fa62730b190cf66a9fc9fc8c84d847f64b135827ee02d989b0ab301f058d954b65717fb44ba337b43f8e6c827a2b74667bc54e739f5dc40bb90d26e195591aaee6456d9c523357dc0224f4752e9f021f5084602a00e1a4dc56f5b6d07a3e46cab6fd213954100b4371cf019df217fc8c2ff3858f5b646e0271a8c3bfcc7174b97dd346b63e47ec276487f4ceaad1e0e22581643da00ca3f45842d1f773b35ccce2a8bb8fe337a7710685cb2e87dac9302f2e20d3f6b3e8c0104bfc65f12595940a88c0e9c34a8d93361bc39edb91ee62da1b8dd5a32cc32245c847a8d19681422a0cae410d6f9d930f6c77a54cc1265763a6752f90b2fea7964eea19ed557f3ac25c83bcea5c8582abe05236fa0dd1fc359f976937b2b8970eaab08236282d30414508d0fad6580a0e45806a0a259403bb6afc1206dc4b04d40a2d86d24a54f17a179924c9fd03d3bf3ca09946b79d3b8a0193748d26b97454204aae9b556f104052600259b21a8141b08dc76748be1e695b1d99f4be90298fb39aa934f033c2d894dcf2f05f095fa84c7c58f6cfd135accc172ea010a7923dc4502cadf65f019ed39e03970c957e50b2e6efb627bbb5724ef86321eb25d0d6de7dc138c94882e3fe3bc060b5fde221532c6cf4679ce55c9ad7085785efb17e07c7b30a07caccacddae073755d0255361da690196753e13bc4f5bf4a11a2446c85405b75380d350ec66ad1893d4fcf001e57571da8594d66e6f5b2e0896d397f9e44c2a81442cd7a5b0be046801cdb3c13517441a267f86cde7a2387fae6ade2303c47fc9f810df23c5481975ab267ac3d1eb42e1eb762962d51bc28d424c8a691319c7862874d19ef0fd0b8fe8775e160cf80ef23015bece2503431a10dad9df89897facef26b29821f55170bd6feb804e08fe56130f83852371d2e89cfb69e8e6365ddf42ce9d69ea15aa06124e0d0f039b714226b1d526df07aa8826fdf9b3dd24a5462094c092c81e4fe0a501afae3061c8b24421dda61d03f386851cc6f068d6fecc3e513d8161f7d3407cf3510071f4b6f2f91ab733616ecc1b52b8c8558e2ceeb1109f47a3c7a2579b34533b8f9f957aaf472b69eee61bc7c45f983b66102311b6c0b5fa537264648b10ae43f57ea5d37d823dbe246dc5361efcaa7666a51aed1f159c918c44704ba29e7a659200eb39a0e9503864496ce0781081397a373e5081f626bb48b486970138a5cb96c1f63b32061d04ae55502e38aaf28700e3ea2bb58f8bf5e46225c181c73a808d409b8d5ccce8ebd74442385a7cc32db765b937b4b29934c01a10a0c0a280a14648fd209b2d88a24129226cd8a24994eb5f26e56aba094af564be84f7087f7ad7c6e63befda70d95fdc9f2853e4fa100a278a7b37029a42286e880343313a479234d3d4d2d25234d2da51afb1355c12b3a4f3f26214a424b32e4fa89fab979f97669f92cdf437440d817986d893bfa3cf93c81f1816a1737bb9f2c3e3eceadf2f179fa49d2496a25f1ce17306154610729aa9c4e5290229b5510e72a0a4a70ca9de0cc87a0cb365b5d6b625a2272597115b98c5c472a2c903c1bd5539b2ddb6cf53cf580e909e3f3949412737315b98c7a6a638709c729f41335f6d47ec9c7501d6f8fafc72ae8f31e419ee37c1688033597c4847d986d9502af0c0100ed068019cc067b8262413d7df9b1fd64f951fad1f2b3d462ff84f931e387a95bed62a6cf1312da10d0076afa3ccd964f94cfcd670c122155f893d4d99a45cd537da07ba0abc9b5e4d2e252720db9a6b8885c565a045d4edfae2d2e23571617524fadc7d6f3a5078cab480051dc4183a09da017068aa9c9a9e605cc17a25b9219adc54e02658a2afca52457dc91b724cdb6240911b20a52f90ae454cae8b2034d063bcacfb993dbf543f0a1a266eb8977dae6a57684949445a9c52020cd653fbe17835fcb8ae927ea47cc981f3db59e5a8f971edb2f7d14c7acb73f53bf5c463db5fe02064c989e5a4fcd0b5d45aadbcc0df60566e37a535f06aba0ea6210d7c1877a30ffa4a485a9a9c577fa32e4c526190a7f6e49662d761221285ff84b5780d428950f987e0169177733c19902af841e04fc899aad0e7fc624110af236fcd1e18eb5168240403b5b504f35fe09363741c9cda0fc945cc97f0bca9f01e55f50fe7cca3f412e0794bc2383f8b3666b82dce3a55bdd539382abdece37419cf78c7c13f4f115bc127a2a0c7f8077b62688c157f00a10a7a19dad09f6782f4810204080ac827a883071b9ea3db616bba716e4f3c43bed44be5c1797e2b894d7f34db0e6bd23df04797c0abc32c48508f10a5e11d2f10cfc074873d910129c609104a5f679eaa991b0345bbdf4b67f1a02fec0c1adf620e04f1ddc6ad7c192420a4d6e13d64f7f41946149a1c97d1c6f623fc749589a2f126addaa83633ebcfd870e8eb15ec0310c38d6c3db7fe6e0d88db7ffc8c1311edefe1307c76a3866e3ed3f70706c87b7ffbcc1311c373896238663f4870d8ed5584a0680e642a18f495ef8f624b3fe09bdddcb00ac1624fa0f466692f2ed243059f976a6222dbc6400529b27df5e13c3b7f390e1db6fa07c7b8f28df8ec10cdfcea2e1db7d0c65004e252ef04e7b0ea8e405de69c70195c0c044cb408913de694f814a580095b4a04488773200a5cc07c8bc83017885c623039d055ea1c164a0f7b8096dc00ed8dfe1356007eeef701e600758bf23bc02864a6842cc740333e5f0ed4aa4f007cdc9d0780620cb3cf90d0195e8802ccb00ec644168422533986dbe1eea8928dbd7bee9999ee8ad7cd11b75e01f91e0043fe18504a5763d163e4ff3f5484f68f179e227b2f83cfd44f5d45644dc91998e848055a93f900e04649a55f08a8e539d9084da6c7d19b7a17e84dd848030a8d96a0702c2a266ab3d047f40f0076ab660361bd48f833f35f0c709fc69fa296ab17d06fc31fa39fa419a837fbe7c7bff70c162cfd74f961fa6ef1f2d473e4f41409f271fa8a5f9f209d3adf611f3ed372b1058df853f29209e24899e9aea67cc4f1b1c83e2d7601f12967ea248f0d22f20de4e42ad5dd6078355900d7d9e5a6cf725eef8e3f3f4ed3f51edb2defe23e6e7d6ae94b7fb1cf9d838b6f2761fa47e3dd34f14ccf61305b3fd44c16c3f5130db4f14ccf61365fb769b1508363f6d683e68ac5508fdd5395045390a5e1bda11d1a7b2b2a44395259b5590759bd512faa92536ef85bf74c51def4fd4b7ab409b59cd2a84fe94736097aa6e67b31f6b4feddb53602522a156a5bc74a9456d8229d83741eb01e09b60f71cf8f9784574848460a53d6e7c66cb199b1a1e3b6864727ea8b478c2e909c0c0f365bb5085ce55e1e55c15dad99a1a1471a55757819ca7c0ce2938aa964a4879374b5039375d05d6003451502d51ef40967195d9d9ba5236f30e8fd205ec7ca5aedce0697704e8a909ed62a716ac3c759e35a40d9eae969c5ad8f223333da59752ea5c97d32e4adde919d6ca6fd072deeddd2aa8475801056b69980329470bfcb47969431a16650875dc3162e0678729d80ef45bf7b8e77c1b5ede913e6660a4611e35f073862c29c350f5f1d6280e8dd910abbdc5f9bab3d5c4a45f3b861cd004ea26290c08494b29a5ad1cc29d7df551e7439cea36b4ddec39ea6fc6bdb3e7a7b36a0357bb6a6bed38db2db99043f22ecb6d4f6f97ccfc35c82d82cc3e7d82dd056d174bda5d1a86e05f736f0f4c2b651e9a84e7c9cbc3239da77926cfeb088f163a5f4891a5f369789c07c64d3c3c1ce31a55a2411669060db05c85c04f6fce59dd73c9852fc40790fb66b5926ed18388940b6b518e28e83c92233c9cc0ab62891f794965c4e59e9d5840e2c74bad122623743f3c9301048d898da7a727bec81ed006185c6c4e78314518320afc00450b1f6420a3045ff4a492f410d022c61a4a4059411b2ff067271686be7b7662e1c98fe20cdde1153d13abdc0e4739e6ad3bb9cce1061ede6ab10438ab9e773cfb78762163aeb422776089c57583f8294df0b3a68a2b9d73e9234b127936988ccf954fa9346d187a1e2861f335a594cd5a8b537a11f4b09942d7de84f552069456baa50373508577a653a973c3fd42155632a19d53be0a949e02e5cb70b7cc9519995012cd965c9a26ccc9ea887898718958dc9a6eedbd4dd64843e8d9ccc94b8b4369786b511eeb80b42bdde39d1c1edc835d36265f5e0f9befd6a4bc67a7fdf3f90b56e621a1b1649e8bd22715c224075d43259283f69828f340968935d2e7c88dca84235ec864a66a98e3c5952f884334615680e50b6b4ca625dc0a04349ec6927dee8126cc1613d9923e27480ec698e241e01679b5c4b6a4a9548a53a9a992a1de01fb743a0abce3c5956398f00b20c0ae06b06b8628d4114401f6e8156fc69a2f2fd4408b4d81d9e2396748c55ce929323cd6e9d909b325bd136652ce66b33eeaa429ad04a58f5a4b85323485e8861ca6bc74066aafd3da13a4b35872850029b3ee715fc5a91d27a594525629a33a8e69ed925366527a9d93d10fae0f5eb64ba66e0dc13ef209e640baf42e8263320ade91e1f81d7528977847bace0d37e59e972517663399aaa12cb10c4b75e93ab20868ca9ec9312dde5a42cdd6944db3257d494a6f6943b954335bfa25991e3c3129f9fc63e60f97b6d82dfb6f34ea8e130a8abb4dcfd249935ba60b5520814cde66cb8c5b38ca97344c4d16ae71050d74fe5e5cee398bf96ad9ddc2c2820b16e339801af989ef313700d29b99b7f8b6d1e25697365fae742efc40130d00756ee7eba1381043e4a20b61b016bc20c4c212627630da420a07a0c0ca0db53a8ec6c4c0a3163a5469c289349a6048c24b5c9801650e315ce8a2040c08e30b972da20883082004f004d2196e08c1041c5bc8180c2a9ce078411d373c49b1c21139ac11154417562441e1288028820d26a12f9ab4ec50c336431162aee0c18827187218fa820a1a35c470f905eb22071f7c6062a826a403061b54dce0a40553f420632627d0486ac110548870412ec941460f4852d6a821c98d052968019824232f6250410d31e270828b172f7eb8ec47547901c905221664982531724245e54c32f18d0517be9dfa0bdf0e0483130bb49f3991672727603ccdb393932edf84020232a1cdb25af2853309f4de4b7d1ea87a5e7193b50df5d677c07047d85b13768029c8b76e82756bbfd0ba97e29fb7f992dcaad55a3a649578c77245ddb26e6d5bdb379a903b0ab9a216ada7a81b47ed4ab9f5281573ad12b7ac5b96f599242e57649d33e290a44fbb457aeb32be0239a316adcf80f6572157c43be013775ab74af60b473b5e2506f3aa2055e82248f93d8084f1587e98dba4be05b8d1ffb2152918f3d439a74e9de3b82dce1f475c0982f4ce53a00553ee71e028bbe7952d807c1b3ed41d61855705d9b13a0752bfe2b5151f2e3b7d76dae18ae77914d88187236a3bbc9094b4830c518eb85911930312332b5050926a569ea0b03cbe99d4fcbe5bc3e2811d69918b185862904644592bc408e2c7efab4981a24ecdfdbeaf26070a9da55a4532b3770e926df058879360da55571ce5264b35be7d5cc934641a73ce5a7f31a3985b8e215f57b6da79877cf5ccc53e824c306026981fdb49cf7468d95c447a2d1349744054a9e327143fdd3b4115db4f223f5db50a9adfa7c1176e28d581858d2cc0908d37044384b102441114242064edeaee6e2dbe8d18e37bc777777777ce4f9d4bc5879fce8afa5925053f9da938113fea8da81ea86041152a327085051eacb05121860e5d1048889914f6e32787d561cbb773af263ec890d4850f34081149244c3065453f6a3fd9b282080ba216be4f8b14493f6a3f1705924319dfdedf37e714e304d19c73d2f9394c11844a9917fc90e3d2679ba32b5b00527145c2406b286ec3823b368d48d3ba879e7eec1826d84f7887c6e38d3b761dce744aa4dd7eb338c499449a9cda4bdbf8d52c1a8cf64f336a467547d1a1ea8565117ed9b0a774177ad0463f7d8cfdfc21c7ed1d59b414dee92063d8ef871cb73ed33c95df5e5a9c9663f425269975b697a62d2549fbd03a4c30eeb4f65e7ea2468063d8c230a3c5761f735cefa9e5530c4fdd75e785a7f9fac6ef8d3bb27870aaf36f5043b13dc8326b97b328260b823f5aa6d6b27274eca555f7985f945bd5adbdf4eb933bea7c757183c93f9d4a59571c8d0262b0f9f850d72d077e191c37c20a3ccd1cc771b23d204925ed7898f5ad2e0b36348a2b6b79e41a3f9de5d06c49a516672893b2c8a4db62538b1d0fcfd090fd7924c303b45333b5387de469a5a3d99aee8198db473f7d839b5c835b330e25274bfda4e96714eff0fc9176cdba0ae228c77194b955d09139eef8edc8709dd6666bba8caad566cba89651dd93731982485c6eb1bae4e418a19ffedce5094bce233626271e4d8020a73399d361d3e91465d73337172e7bf7ec84831c9f110e6ad8271ccaf094b8ab67271c6c38d47040b2b6c3018827146989c381488659c451b53c36b4008aa8511c64a0491e18cf1118d26db6d8e230439acd165b24d515b686cb3d7379d29252c19531a223e38908389e7ef872eb3397a71d903c6ef728576b77734d290d2b9dd6721ccf2ac49c7336cbc54adde7a44e27dbebeeee4178b236538e0eafbbbb69f79cdc4e99b982312b576dc7a52c08d652163bda558ed53f2b3787a8568a4799d6ae4c7987e3ac4bced659b969b9d592fee93a39cf75a65c322b15eaf40fe6a0c79cef20394757b2ad5c5ddaa2c1ecb27bcf4e37e8308230430bca5f22feec9c1bed779cb596c50a298b5043ca230ae257e1cdaef96a3d79caccb086637c6fea861670e0a02c8f3da3f2c0788ec084a23acba305b5d77e97e3e1b14e304c010c71545310a5a3681250e415a32b48f6da94d3942319581c8e164cb758004761bac53fda3bc294227b59a32fadb8505dc1250c2394b839cf5cc2a8c10530ea18c186288eb3cd6c6f9eb99481c3d367271b86f891b544a97896bbbbbbbb656e4080f8ee3680fc906249e160011c2c984f4c3d2069ede9a38c2bdde352c0632db5541eb10b2f9db64be665d3ca0a211b6e640b4fa3942e59cefec79a1f73bed3f96e83efb6f8ce69e7b6f30bc47752d4f8ae0626beb3e1858ee748c6d93a6403ed41431a4f6bf6227df7f5d8f2d5e46061c3467e642719de80e2f4d32f0b0a0b7eba4759e2a78b33c4f0d379dac54e3378f1d36145fc74a0e944c3d2d79698e393b3d96c469332dac10abe6590228d3b93b87455a78a6fa713cb0baa9fce6404fe6abe5b13931cc450e5dbbb004870b1861b5d68d9a10b91981d84a26c41e3020936d272b4d239426b1774f0dd2570810a1b5892a0e2c60a3e9ce1060d528af062892519ab03a209a7fad57cb766a68927537c3b6d97cef757b342f2a40ff72bc8ac1621b58b296a0906da8fd5680906a31f6bd20bb71fab969f75a9368dd5a9d67e7a95b222c162a5b2122102d2d96c55af44b5878f2c236905e469c2ca125ad4a2d10cd31d2b51256ad7386fb568be8a78fd562991887bbe0128ce16750d6ea18cf37c892191301c2dad4e89e68b978a263de2d8ad5decf4c293af3ec7b48bc757a7b3f95a415665acf00329b73a5057a26e55af4dfbead5a85d335eab8b33549f017b1271d9447229031f65d3286ba3b4d5f8289f78ec70f970d9a3b4228d747c9448afbe319830427c6431a18f3c06f4510abd741f2594191fb9a902f111f653babc2f6de27c892d97f3368784a3cd201c6f0fbf0947964d387a4d388ae1c8b3231c61932af56dbe2ad16c4ddfe134e16869aeeb84369970b45d385e2e1c5942c2d1c37014c170e4f170844d6a2be2d3e96dbe8a8441c29175812815f11fe1c8d2c073c2f13a4e386d1b5cab01eb160979b66e3fdd479649a963104e5a7bea3dc29145fd261c9dba4d388ad46bc291873a8f7084fd386f3bc2d1d2844a371c59d35ba008ce78b14d284f5d261c614223cf18518c8779eaaa4a94aa443624eac29105a3b69f4e0494b7d9a2b3a9d3352e704d450961d878879f5d2584e185457ee71ac7c1c40e462821c30d60bcc826bf604d3516e74aae56b4c5d45d2de1c17d42a6f3789ea4d96c5684ac84304ec02dfec9c43fbd57d3ab061abca41cbd4053dcf17e09612cc122fb94108613cc01ffa521f05227bd84b1c49219eaf6298784d7eecd20c09b097aa8eb09b01304ca94524a5b5a99e9ec314f2ee4e9d1fb5ecd7003a6830d29a1b448e4873b72348e46c7b773503828ddeaa1a7c1684a9fce983bc290b48b9d5c80fa76a0179210a6c31d391a8d630c73a4bb5ae23df34e055334dc9273a664a8cf4f328701cda79ea5b473051271ac470efc944f6b58674336cf9339ab208fada5b4560ea49ded3e09526b6d4a824841aa2395e77ddf8a995982942733b304d967d867d8bd994f829786e7e4e979343bc41a25769b55d0cd8dbce15153632399886a8d31bc196f86ca9a642d9e70b8d23d29657fedcd49295d055df76aadaba019f738ae53492965ea4b7deea93e99b30af27815b472ef0b026fb82b1999947b333333771574efcc95595d1a9a7befbdf2bd1428dfeb40f99e07caf72c28dfbba07c4fe69b596dc04bc1531ddf845dbecc4a04d673d3b3cfa3f96a28a5f912e2ec937d12cda2f9029d7b8e29cf3ea5b0ab6f43b30ed3af399bb3293469f3e59eb3b7adfb4bbb661a4c434d225ba9f3062f8425e52fb8ec47cd710ee27ddd3703cab7df0cc81c97e2b7b9093a52a5f240b62647c3ada9a2ded404452ce601086280015b1a5e15087e717d74406977a7522e59902f9611989ef2ea408a5854660c3f398e9fbcc64f36e353e8388eabd2bb18c3051501f22be726f4ab278a85a6f3e43193a3d3bfe9f748cb4f4ba9b5264c13240064c723e52aa803de9975f4e0a77354fc984a8527d8ac71533eb2eda74400eff0f0cec7ee9ae18e6c9b73958094ab5c054a2a9eae20f378250268011ae284385d58423b145794bec97cea1d904d5f41d9f4d4674bba5c69f1dae9e3b0704739a58adbb927bf1a35eec87a8e712e9de5120810168b4e6f9a02174a33bc93a3dcf2d2050f32142f9d6513ef7c71473945690a0e3bff24b252245d5298992b90b22cd6bd4ab3d54a526e792999146935f84ae38eb0974ef305fb40312ac031e935582af787970dc4052fa5523fe11d295df2fc240b068d72ba12e68d3c3b5d41819319a7aa902f29e980628d71bba04350225a348f381607738c3aeb89cb44188ef326c3d4bb66a04038d550a9dc078eb1209dfc205f9308aac3d720690c1e78108bcde66d36cedb10bcd3de93b6c65585923667f247861a19aa729d074452078e51a7c06c4924a424423e31995466b85b3826fba88fe6d11117a655708c7a3b758174cd30c6b85e502de6bccdd9c48277daa78f73d63e87f8764642124d209d7caa61d3e64b9cad76d6e570bc14e018131c6b4276d1c51863b8c0053f3214474581f3c63b7dc43bed524ade60a95b32731f716c7efb16de69f7e6adce1b53226a34671469dee810b5428be811ad724e5abbe614ea39e513c7e606641d4e57a67c1701c3b8d27b65c20e0e977d07872b7d9500955b9f340bdb051de39b4251225a345bb7f9a243f08c835bed4fb09840e2dbc378f2edc4824089666b8b4dd465a751377380d2f28ef520c62dafb7704c8aa258857cd9d97281744936a4d8ce1a238bef37def851deb0c022eaba66b852cee4ec1663d6963b02e0e5ad83d88dcefbe215cc664d3443eaa72937272188f8b187da9ba5931050bee56c7291673b5bbcc31e95eac2b957c205ea2baa2b296f96e0a5dcf3156474e52a57f90a32ea6c53a0c8aa3215c879389af0b7650dca7721ae8fb0ba346bd37ca99cf35aab5e6cf385453ae7558963501cbb796e62b132ceab05dcd92a61e532a10a2a5f852578fe852aa46c8b1cd06c715c7fdc079cf3ea009c33e75e0a14c262bba65bdcad1c9690d522c7d525dee160cfd52518927659e71c4849579738c6cf711c076b91f334eead4f5c944acaed9e9d82d8d225881f2c0cb73e3b05c1a4061514bc605b020625420c1561b4f4c3d151105468576c414489124452104ea05c19329ae305a3a81e6ab400cc161d1177c7331730513cc1054c12528031ea7cb8ab672e6072e8a146f7ec70054e760f2ce5e4f913876e907a5c738a06033955950c97ecb5a6267310034d0e6c529f2795744a2a29d7ab12c3a3935a2b25183e50ca75573baea31da574d290da5a194cd341e7ec593fd936dc6aafdea1cee4a96f44352164d9b8e317ebe18618ac68c1ad4989159c10e2045778684389282e5ca1a2ca9d615a108061228b142ea670c30cd9c8d3b2ad81444443125120c9a833992f6aa75321a6902215c494499f462bf86aa24ce0313347c32d91b572dc9492f3c9f1ea00aa1e2e916c59259a2f1e6dfe3d0ec6d44bd0e6fb4e2c15b416a4b21a5ade9134b4aa90676bcab8e6bcd464713fd040136ce14ad92465825809c7fae79d0e34ed741b2dae75e9365fae741da22b67eaf8f9ec40ed294b737b82fc629fac4b33bdb854f69c32573cefd5025ae6b90a94b314a8c20756200542e8c1c5049a338e87540a2ecdb39391146b839391134e0988353a2c8060c1b5f6bb355c445951399aa982864f872499256cf074b8ad801862065595a38f071952569e3c9aed61a60202075ab743510a88166ce07838b269dc5095ba1fbed440955819e2782c172544514d485152c556040d4a76b0fd50654809955a0f5b0f13743c2dd7dd759dc70417328cceb8f7990b195356400614a5316e3d6cde9c73cec9712e274542e98485f408a5b48636d353a84ac3492533f39c734e50ceb9011a27d99356aeb32995b72395b2dec71213c601fc922c2f486a729674300045168b2539c62c16cb729572b5e32aed581468e9e3f1baac940428d0aeceef91d7c161f55c0d847c5b2725fc92338eabd4b94abb4a674b4b4b2b1e32337276c26cb5d3b05cce582c564f168b3579b058ac1b38bc310523cfa16ca794d2eef64e811bf2324c01a79bd4a73d52f7ba1b47fa946008d2670ef72934e9f9a540bf497dfb1e9dc3fb5acc69c7f1163548a31dfcd6cb265574aed3ebc6595741ed0c8eb4bb676a154453df9c93520a9b1ffdba950d9adee0d85f8363fd76ef7e0d8e9caa9b2a494929a5947295e3bc725cc7d916f9c894e29c737a900fb4b8d23b184fc74472c09f1023725a64ff408bdb1bb871a5f39c94ce1f57f8192269917d25474faeb32aef93a157a552a928e77529c9a24da5c2f1039d55a9c0a4869fcecadd93a1f12c47ebb74a7568a83c4ee5ad40f95dea6dcaf3bed5a74ae5b058f672dc0e8ed62ea5f2d0b8415ce8438c2bdd5a9756655daa38975c2a64ab84abe180ce11fc0c61fe746fc78e5510c763d20904c4834e24ba674faeb32aafce6cf0f5f38ed4a7a98e997e9ececa5de52213e9c33c4760488094f088e25c75ea047f95ca4bd91cbb62b19a04dd2a840669973a92f99ac9d940fa4ce7efac4a9c016540200dcef05495c554ca69751653de7929a7e12882b72c5b4615a642262d56da4df3d57fca83c72a88035a52d262af94b848dac575920c92a5accb738bac786af820851a3cb3d9ac851fa2c8481fb2781f06f33fd67cdde2eb1de354748545c4a5e8824b3abfc1153ffdf210f5b387a49f3d8cd9218e96dd2a68bb9cbd82dcc1947b9f9d76607ae9d2c5cbf2c1ab4e57225880ebf8d0c726f5bb5db6a4e2b1ced823eaada72aafe812feeb2b8f3d1fbd53d639b672f60dfac57146ddb240f08ee57c600eac733bfb136f7d6cd9a6b74c70cb7eee813466cab20234642a9060660575cc0b66deba7d01f30d1c85532e7cc1760b961b178d323ecfce96f5e96fc5bc0599ee68dfbe75eb06fbf05b0b470d6b759d246e17725ec314f102d1ccba7449c4a5682663590bb38e3a64a98fc3bc751b10a704ee832ab21bb7be82ac03714ab071ceb950059c1a767e13569b500576c30b3a8e642a90404608d51649322c2a5f41e6cd8fe7ab55f0d9c26549a602093e346458689c041d1541c8b0a83ea8221b5942c6dcb79e2a4a19b54bc6b9653d055fb6786bbbbc4dcd88e6b54f162aeaed0d081eb922462e09ce89b7be43cb1dadd2a5b3ab55520501b56b8dcbc3c709456fc832b9ba82b3c339b72e64ccd562c5b44bc6addb28abd4aef0addb13f08b2b9a2deb52fc114eb51491bdcd54d2dbd451d1dbd4d07b36357b6b9fde32d45b1aef58e78ae68b7bc22deb473c27e5adef0811d0322bf355cb1d2bd45b9b83cd816392885fb0a27bc41496f4d6ef54f2289440daea4737a173bbb45a00e75669b6ecbc793f8eb8fdab760fa4ddd12a19c0f30d50dfe1293fd2a2f50637b0849fa745eb21782118c5954ec3a8b73ea1783ecfeefdcc550278b65a7395801c9f0f75a973394b68ed2a2bd75aebcc9101ea2b39615d22ce24495e409c7352c02f2061cf8f1ea8b188524e0f54912b7e15a402d362f74085350ca1716ce6db716a75be3c9b746bbda1587c755ae318f5ea3a5fed7c01f1ea77be82fcf0ea3e84e61579b3cff1f1fb21b4f9fa111619422b3284c2888a28cd9711a87b7f3cbd48247d7f4246a03cc9ab03d070c79ba69b9a8f1bc76e3278f2ed19ac96fcf81afe788fd6ae9d6ff7a0f42bc7bdd9951ce75509e20ef7365f39fec4d79c1cffe12cf403f472c08f365b35f46e2dfad072475518d5d3dffc294cbbfc4b8b62da5543b7b578c40db224aaa0bee78b93b17fec017d40df33c71d69929e7867da7020f8495470fd29039a37a65bc18f548c21be897828f792e73f9c2fb20fa756ab0f362ee73edcb8952669cca541a2396ad7749aa490a349928902f06e4bee8d772aaf44a51ea86eb55f1f6c5ccf7db87155292b65eac6a95bed375e6ebe7838b5f0a609881036145605a78311f9708f769432c1a007aac8d2574469ae96885f1dc7a6f4e571c2343d0e98a51a10a776e5725efd87371716db6d9ce72bc775d3d462a5fe03cc096b5a6c362ecb7db8714723505f71bcfec871db0d979ba61ed3c70cefadb101c7f95d9882fcd1bb598a23716eb39c2938b5b0068e35cd178c882689c8d01d87c088be1d66054634644abb461452b42150860c8ddead5697fcf26eeccdc12faf28c6407dd3e4b90dead6ee63dad53d81f1a85a8b62bcb4d86196f0571f4128a0956f50c84790f6059c6213e23c8144e00c1cf26e351c6b8edd7c6d7ab71a9c1acb47ef46abdf38adfe23c76d55882a8b89db03474c8b5dab210ba756dd71c071be8f300589410fd48823e6db3befd6ae1a2fb676d1b88b87b7e33cb5ab265cc20fa65d50eda2f1769c30ede2111a0913d5ae1d210e4d6804ca4854e38869d7c5a9e18cf191e7c325fc2b1f738472683950bec33bf41de2ac42236272a67c8f98b007ca5385630f948e27b98c0f36eee86e5c7ad3d4adf6be69a249ba695aad085044c90854bbc6224adf6e04ea8d44d16431025544895be47aa086d0e20871bef4cb6db3d53860bedd471a5786a337e5a7fb50e3d670f46843681c0379a7076a723d50df411fdfa3179fbd0339ae378a1e084b24131902911f4088100942848848c44551048988a1105127031c5114451f434451148910c969c96c40d6f13821ecbc7d198e2378ea46a08c44b58babfd58e346c4340742b6d1081418ef667b2366b0cb8b838d8461973786c5761a7a372877bc69aa7d7b919b26907744f87ed40115a65f3add8289f9f611564134599cdcd1278d12013430e24effe1409cfae8dd727c86238fdbe6b4d526db1f2dfd00e7abd6aced29071447e8c0030300b97147ef46fbe94062eee8365bbba83fada47fb0128103716a38359c5abb3819baa8019e18ca6d0d8015019ac8ff885a9c374d2d22f1eca404f5f56992da35de34d1247d3b8d52bbc68fe89594bebfce089aa49b261c9b37145b4dc7a9cd970e8e4d0649007e9200bd6ce6d2084049d00e1078d42c6112c20044188100363705e8c15a61f0e5fc00c223e3bcd3ce79378ef36efd0a4011137d46616cdfde6d09313f7e454749dfce55db97767558be554b0a88426b578d3203dfb89200aaa7ae8028b3283350fc0a8ef2a908aaa7449e168556c15aa340793ae78c0225ca942843d2a308458145f9682934f3665876d0a495ebb06049a554df4a4666862fcd8e8f87d8b36bbee661f3754d8d8d8db4e1513363736373737353c363c7cd0dcdbdb9b999b99159dddcdc7c461e7b17274753ba3819fa274f4f6ccf5d9e34757982825f3d77798244019a7dbc48f7a95de3b5c2f7cb45ea17379445a95f4274888b6fbf4b4cfd12929925218753bb6aad766ded62a72496befd82b17cadb4cba85da34ae916b5ababf2edf7a85d1dd2b7f37ccd4041cd40cd40d131aa94ea139512c7d8bbe7237e944f78a77a07a6206bd2cfda69124ca5a4aaa994987a5429a99c545e5830f472f58175484ab1c1362e7f77e158937cb431a7cf94acb476d10a65a85dde142bb588d6292dca0a4555692d8c40b3ab491b00810708f2a98f95f64a1cb379e92a1ac77815c2fcf103444fe98c77643855432ae8e2444c172750cf5d9cd89edd3e777162c573cf5d9c34bd2480c7591f6d50eab4eb3aa9722e65bf7e798aaa4e2d7a21c7d4620a6c5f4156fd4e6f2f617a872a60a1ae42fb2ce15361fa175ebe1c5ae99686e3f71fb7d42227c71dc55fe29c278fc831ee39aece9ee35c268edb50965b9a2dce71700e303d012c9b21cfd6f42b33e46ab3c5491ff99c7ce6668b736bb3b6dafa95539f6a987ae3848ca87150661219b6e7388ee3869e73dbd5cb39d7e5f0d479582a42c73967cdd7e4a22c1c4c38a958548aa7dc534aa9c400cb98cb85725de71cd7759c4f9f1ebf3af7b0ebbace612eaa244b9972550a044885380890322f88c3e15c3e174aa32c3c3017797cb664eb2629114f5880e2ad03b5edb8ae03a773e088c2a350f35586ec4115301753291a094aab454de658226e109ae2a7bbae60b9e08e36057a53c41d91bc64630b14272fb9fc0c470a64873b5a7be70b0bbb6c9ab4b40e716c4a25aa4a554bbb3c975e97daf5b9f4ca54cb6065b9d54a5729ec8344722045edf2d2eb162fbd7ef132fc326e1d4240955134a46cd933a83219010918d8171e01f04e3b058ae1727cbb1d68b258e057d9799213038593189c6dd511b145194534c051c5172a3c4852fae189c8a90d27b87041860b1b329853f4c0c4048682a1b4b4832915452d8b3a90905c08c34516968b2ebe33e804c38a55470493e08517b72b6a6624e144c654544b32b261718615d9c8f36d83125e48b1e1080d29b215a84ca1a38d3027b0994007d9c8a3a4c921a4a112d83004d214a41e9c9090be8b583eb2eeb313d2ec53cf4e45c0f14f5fd3c7d3698b9f5c627f2e59f14e4797dc25f327c771cc71542533fde39d06f2e4f6eb34922b31fbecbd89437af9b1868e6fa75d04102c21225420dadb3bcc88469edc1106932f1f432648a0e8db67444c01fb6a5c58c0c38f5c234269e427869222cf06a2943a60d043dd9adab1fe6bcfa15b5d86287c3f9d82f4756c0d9eaca9e12693529ff6520a345bde45ba9c4de94664b8302623f4ab88db7e8408d41de52c47882571a8d3a7614d7bb07400e926480792ce04470496523aa5945740bc233d0842c7ca1de56ce90026003151a3aa916ae266674e3ce9288878f8582cd6111ecf6159254c4660b9489f9d8600038833664c501162020b1e7208e1054d5db4b810454d8c265a40a3a0d55033a33ad611181229bff86a72e6b3d3105d7c583c7ea69edc52451674522a93fa254651228e92609264d3104d3fea48dbcb465935f081cb426ad25e8d386a881fa5d0d147492b2b34b1a5f6c20f6fe8c055c4811486972e63c6b041d60c70c20b31509c1145136264cd2f58811b4f5cd0a20e3590640d85d251bb52b20bf91a806c75acbe5adc6a19f3000431c0e0479954c310425126d524c79c74eef99049a91038c62c9578a7bb6e4e0bcc1087f35982f4e91d903f65d3cf1d605cbef325bd5d0c4fda38c6b4d5d79ef2ee880be9a2e82cd69115c38c708d9792c62e59d24e982f29876c4997cd824d85360c895ed2c1592080775c33dc13641c1cab2e9b8a74495a4fa7520ec9420c61d7f0f20466676797b4763515294a97341c9b0709e5280ede911df05cd999e171a52fa8d44d9476412c151509008020006314000030100c8784428148241c8d6479f80114800a779a4e7e609d0ac42488511432c61843083000180020002040333302008b518c381fb575648b30703b5ee744a59546db74d9d1680bf733f87f1e3601945cdb4176ee8fc3c1864378a8caab7e439351cd30e563e42dc5d7bc721c30c2e5d886c7497f712f49edd65e27620c715b961c7804cc881349cabc486d51bdaf249f885d7455360a1a15d24fc665a2a620cae0dcbad7ff05b14376fc9305e27f8c20536ab8f5f0763351106c2bef84155da180b3275d54432032003c97dc740085d3442b41d4fa79d7741c8ac104efca4f3f75288badf45322d3c37166d91e0eb0ed6099814a2d7f29b181ce2f0deb1e28bf3fb4fbfcbea345497ef50cc43ab4629ec02d9a1034100f8d23258b796aef7b8db7c49364ef9d23385ae0eaeaabb8330da8650ecb996038b202c2b09c80dfa2f4daecb695bc483b1f9a7e70bb00ffe87ea278cbf085ce807fca3bfbe28d9eeda4ba126029a659d9a83a4a10a6ca33062e2ea31132cf1f420659498932488a3ae3c7680ce447139240437fa8b0952025524b8df2a28565eb3d2fd6a79d4c9fe0d06211796624b54b3e27eb67cb1acee8d247ed87404919d320a9b37666aa0d5411a7d52c408e6a1721314a1d5b7e9c7c120356f48c2dd307547f13b80c65c73428eaa1b2fbc36d70707c803fe4c1471bd9bcd6d46327b314bc34d5012c7af524d755789b82c40ebe2c43cf65a699a82be7491b3284bb190e30205dd890b39631aa5c64245648fea1070d753de74d3e6c60c072b71e813259dff4676b05a50db9d510bffccead67337ffa3f973994edcf03497af8f10fa36147441c313f2fdd9dd0189c5be3874bd9fc4f8058ff468c0424d33c681622bbf9a624ffdd28526e7a97150178087f2e6a3ae17e2fec2c13752e0e18cd903ff0d8b674fc33b51eb90e4820e3b12299ca22428238187bf7f018d929b635c7168d76be516da95d045b94a7a0fbd8bb65f1011a318e8a42561960226dd76a307476cab967ea18d8b832f0f3eb0bf55d2d28680f88c5c8e66cf20ac78b10f042cff1abd4add7cc7de0f80ec48eceb222c4f68aea21b9043303400a421fdf27a6993728edff5e5a2d0ddc144350787878f49817ccc8cb089f60b34a9f75e82781d69b3088033578f09ac3b0e9a86ee99d2bf5d312591549afbdb78a6ddabdea1fc3673cb1dcedc753896b92b7e31c2a07f04c4ad06754af0d4997173e57bbd1884cb64849bc892b91dad602e3d8e69fa630de71222e9095bd8139eccc84edb0691ba2a8598f9a5701b5ddfc0164c1e869a33a6d62edb4cfeb4f5bdea05f1c8343bb95381fb4171dccc61134a32a5a6d3378d0cc7c8f520815ddf6b8ef9580c90f11eb0c0d41ec0af7f22564d55dc88a7841ea70202ee44ab9f36ecc3a71fc98e1ba4e7b36353a04cd03baa13c74edc977ee33b6545ffa903112be92018d3f8f6a84bc59cc238241f297d1600deff980f65902ff87b23f128240e20f09338918c82a0d138136443d12472ea1c06b831015f7ab0830bb082948709d9c972acdba99888b73546fc48c3b8d7b4c7be3d0520efdc74fd5512076777e40010512c797a362f2772612a077e7b7a0b21873c59c32ffcb09a6ab5df8e3751889178dc47b6aefb98a20023e861f4cc52095797a67104310a2ead57bbac1f66c217620a8b0f924ae8f3b4eb2a8b798015e67a5bd0bf020427638113eaafb85e64805e8146765503f12ef0813714ed1feac8c97cc4a1602c41e8c9a296d8b32bb5ab738302dd1c6d96a3999701c76b8e508f042d0be3206a00283e924d3bcf29d7518811961aadf2143d400478159783fa6d7ecc721aa341b4ddebbcc91d02d98b3d68085f7e26d1a316053ad4a722d51d02ab360df4eff4905a38aedf6645bb427efa48a3afb8d93640c59967d8c103d9ba4733087c83287e1fabceaa0a1813f3feff3fb11042dc2aa1978b3e3dc6f6827fa1c1814b9a9eff829f9eeaad94c471c58427130b39d47e50c93e5f30edb68fa9c64058ad1076c079dcb9dc3568f946c3a06ffa50cc7942e394f91b316106f9b5b1fd89668a4312897b96d3016aa906fffcc68d37deaa321a2d090e5b4913b924b0b1d2b87c2c7380f299d4ea7b1efd85398dfad9a5e68e8daea39a22cf1838742fa55dccdca22d613d8f07b2e11e9a16cab5089da8c8a6650f33e3df1316659350cd836464a89d4acf6f4a090302ae1bcfe0a07b1cd75241d77e0e0ba29bc05987d0b4f7b1bc38f857722e1151d5cc601f99a9b98b976e8cbf37e88a7ba933d14eff275faea39984cedb248691f10a30cc08086435f306fb013a07902514a48cd42e2cc4a8f5ea4c9a6fc26a224500c0ff4a7aa0687659b3121e0d52ad403cc7e07d44f4a1c9bd3967d64da8d49d0d48c814ceb42c9ee902ba614e4c7dacad87fbc033388aea1ba93e81e543adfd55689d8493db581b6e42c7fcab4af7d512e940dd74bbf31f49ccfe89bb430f39c918867be391871f7c0d5e8198cdd1156207c40038f0d918547ff130820a2d30df0807cc193b40a9b5301bb6e3dd01a66c0532e383c2f108092e113667414f1c861b0fe490c7f8c1b4d888961dadd61c961190974e65bdc609071d042741045b4d5a40fd9fa3cb3ffca31f77822b4cef9d748fcf6e39f01c7c44069d14d7d29140d3811dba7250c7ecbfdb1e40c96849867093412c35120e6f706e2551b92ade4efb6d7b56558fbca57aebe7954542178793951d8634222c39684cb75a765caf4f7bf358a4ae676177565705b23c1e3dbf975ab208dbaaaee2827d79af1803572e3f095e52a701f82eca6a8d2ebf9a15c641e353280b217bcb2e6a182300e8d61e276c5a984f25fac986652576ca6d1b76d0b47f525737474baa3bad06fd40dd0db8b4190c76a7eeaf9d9012d299e35923d8635f57ffe46513df484b70797b44e04b191908432b4bc3ae6b1a9f670350965ba230a85080b5ccc8132d0c2205cd1c719238d9c724fdffbd9993883b2a98257d2f6057ff44fecb1f5184159fd28276f6365c0ede7bcfecf14f7f21d7d9b669f6a3ed3d02115a234e847cfbf5668f77dbf061df4d31788e6cc7855acd4218ecbdc3ea7451c27aa8450c6311041f4ee8a9996aa600d5fc9b268273953458fd24a78d63d845bd5b4fed17663aea245554a7cf2b70f0f5009e561ffda93e8356d4337e2fadfc74ca1733c587f8052a77b88c1879a915a8ac8ed394aa8337c9c5499f622906f7a243eead027c01f3397d436c1f7e09fc84419fa04f803db49aae79db8722a2a1e8044321bcc86441d24a6b135dc271229e37a2ce57e823ed331d9180530765c547e0bb005be0a1d524410eb3b5201ff638fdf74ae59f458ebfe0cc6032148294befef8310e53e2f65508ae2ecad1ad9aa2efcd0acd9b94065c704408847a6d9d7968c94703b56d01a8d80b97404cc5b01872032af06846e6e1dc00bc59c45060e2f3a62826ddb64edd1046ed46acd91a8d527a6541e8b04d00ed315da39e9fdebc4c3b721190168b0dfd95f669a78b9e6d863a4d5311d2c8944399fd968605f66f670a732c471309e0d57c697818e7b499b086257a2b736859f8a170a9540d632ab5bfe4fb2e081667d8213bafc2048decf45ff073d157b0905179b700d7ba80d47cc91e938b4ecc1b44c1f8473cd5057e6be8bc401ecd28f50cc3037cbf02c697a82b9da0a3a41b9c8afcd657b88863499dab8e032cfca1b687d1cbe31d3693bb669a90920f0f8a5280f34e25a38141b493e9821e421406a531a45126ce79a8721f1d64b348d1843e3e44ebe25bdaa3e7674aed49d9b6edb77cd3bd233fff2768a878770f1685e406264d865e4183db5d6f356485df6c6a4b8e67e91653dc0d614f76b6a5cd7eab0b3cf32ae6629f406e40ba179a4e0610845becca99385e1b02c55ac1ee93edd17a4a2eec2ee89b0f2c89e6c30191e82ae6a34edc5b82029902688bb6eac2743111d6ac4d9989148ac8160709c13b0deffea8146094ccdac916f2982b3cb46e8b6f23402f8924b69287e96a1ff62a4bf8ed959413130f5fc82d61966372b1218cd2ff048fec3074cab143bfa63d4c7c39e624979c21a3fe029fd5401f6cdee3bc08c0791ccbc0eb555cab49921f68d635efbc208a50a2588a4e58d553671f80f49bdeda49c006f34f06f5cd7aca013e5dd9a65daa84bdf6ae631c1ca5f212f8dedd6cf5a55ae5043ff57f613d1de01515331aa3e4a120e4efcfc13eb2cc4aef5daa5d8145b22d257c204351aa6deacc6fa743fd7bac597320f84238371cc46b3394c405fafaa13d9d6998d9fbf143c24406a8e7ba6ab8e4791f8b3188951fc4428bae2bbddf31ff7845bbd9cf0fa387e8e5f2e84482ce1dd105315571e1957a0aa2f971d064939053d8521c7cc99034006052c4b1b612ce330b6d6b5a794bd367fe147575403eaaed52dc3043deadba8bb9e3050faa93297f1d7194f65d6e1e70e03f7276e6e236b232743fff06670aa4cd42384f0830160e56b14d08583f09e8cc4a3b3bce508c005c0729809246becf504c45edd9d8ffc631b8ab76fcc2430b7c582661fd3431bf8fa4aeff2088d8c781da06acd79683418012813b873b8f84af120fae40da5ce65de42c7bf55750feb9062c119a0503cebe79273be5d027bce0f774099343d200b31de35b3dfa732802d91dbca4710747911288003f4db093e0956c556633b32eb16c5d4fe55a8551cd80a2e3af64a72a17394b2a05f61e02be9a5e1b2a29e303dbcd3b36a550674c1aceb18a193257a610235a547f292e13510491029855f343d79713cd3f34e756ce49984860fdff86bf8d47082da31cd30bee9442482ee689ff1ec39c7fa0adc12a5a3a21454a994825a4aa520c5d2dae3a5e2e6fe90e1d2b39168cc9a53e042962304f903af9515678058985f91baa35198429543693495a53eec5bbb102cd3647b1e2da1cf13ca4de52b6392dad2634c59a616b204f8fe4a7198e01cd4ea6b7eeb32af60e1905556e9aed64680f94dcd62f876b721e5beef7544c8cc6ca4223c6f8239ff319108110a2e3e37adcdea025f7f18292d553a62caa96fbd132e0981c7213b630288528fb316167d1dfff83c3d46aa14029458cbaf8dc5873a159fdc8e5af95e358af66b376689549b7c1363bc3357eecced98bb3cf6214bcc610379ec7850728a640e898348060906c4086623b6d066e09d27d78680d366d0b98e10a2a3a97ab9de36c9b8b6de5f409ef0820fe4b5cd117c915fffdced54912e87eba972c58ac39ed79568854cc93b40200e1ec204b9fb46fe2451d904ac03155ece641d0f94eb99f93700ced0c12e5d20e349e3136c2e6e7f00e2f2996d28eca52dbb2b9308a3b483ee7b0b26a744083de9896c64460dd2feaca3c3af0c620409730d2727412113724036c7629493b04b8987d1208efad90669879a6161a5e2e09be0b8e3664f4ca899679ea8e814f7e335db756eff0ec5813fa3971a83062f2567918afbf43d2b19755dc1d02832649a84678ffd9eb6b1433a77b1d62fd0fa55b360d64c9f55aa990021468884723b55e66b39f83e6cb7575ca88e41108addfbc1f55f5daae97d3ed267d7649d61e3a75d152c18ad152781a39a1acaaf7d02bda7b950f7bf2856650fd9ff5e180f5552496984650ace359010c982a163bfed4a8914badada2efe7d7cdf652dfcca1c1e1d41d35a96df65b3afa80ba3877d0bf4a1375117ca9220e63ee003c8ada11d06e823d0a34b089a6c64b5a7e609502b2af0c05b97597d2b6354c3eb0d26b24253f20963269ce41d5ebde401ff2898520158bf403228881dc1bb54d5e8f95e7b2332486b9a137a8e3d2d83a80217a1c495ec36718aa68ab8a657b928e254f7937aa6ffe95452ca59e4d40ded5023bb19c1709f804fd2a0305580faa4aafae769e2d090156bbfcbec546a1b306b3739d5f175ce3eaa13deb1c8620a6c27d2858f7122e778ab6ea01ed4438a2b22090ec64c53f769539c98fa5d11411aaee7e18f3f1829e722534c16410c6fb7ae74e7de1b1e58422c0780f803446b150570e4eb107ff8320194523ada88eca58df744a0b9780ab8c24582cfca2479ad06118de95a0d6dfbb92a9fadc649dce95df7cd0f13b097f9db0267160efe0eb5e6cc5a38dc6ccea865549b3969a3f6ad987243fe14f47363d09be341bad273ec32f2453128642d8561a5f958c4b9a9d32c1e4c6825395127f7eb209e593669765c6e335e0da1503b9c8114cead60dd90fa0a652539dbf8ee8d5aacd24b0e77c6c5e9daef4eab470f954665576fad550c0e6b560658719f80d5ecb462dcbc2a92ca68be4df11324187eafc09d35b2d10ece461e613482b8f7064ec22de224d5bc1933741f6b76bfac3bd9b3c27cf7f325a4d1a4e8e3118edc34724a8eb0420505ed289fc5b4890791b42d50e4cfc39323c8f1d736271e2eafe555c63f538ec4995fd59c6cb1ebb5f4ab9f09ae0fd0ecd7ade199d0cae90190428dddda1bbf62dfbdb41d63cfcc5916d98eb18fc59ce571395b69f76fc42859bb5c020ad89cdcac7acb3b9f05a9fa72564f26d500d8db411cef1a91c5f4db81b8f2469ef4c177a55cdc25b912d5b54bab8755d59c2b11cc5d82791a022c6503cfa493821e41cd4c234f6c25609ed8ece51cd9bdbcf89ac5b14763a971299654f1bc773ab05363cadc5a33b8050f17535988456f87e40b5e23b39dedf3c7338a1e803830fb00a59a992a1dbfebfc77bf6cd07eb1472bfd0b3ba59839cdaac31030ffb254c3ac9b2190cd841259c335a3a2d7e1a0475d37f51feb634cf783d8d3be463c6e83d6b564438227959c1740bcb80ecabd6a4c56d4fc2f3be7be16d4889a9792f8793e7f6e758385a253b1a04da51204fc7dc22feb5853de589cc6c76345218418ba6f66900f173facc88673e2ff196e8bbec1269c1e216e5ee25a64c9ecb8588bd6a7f8b7b1d8ae17f33ed2f9d92379517cbd523a7c8d23d87a689f7543bd6f467eb0435071a4c4e33a48520cd82da8aa1a3d7f837982bc4a5479bacf90980b75d1b88cf5d0c4f4956264c9869f2b7d0d3c35f04c561dcea846f56dae809a5c82185fbcdb33c40204a222613e7e59bccc1b992a73e7e327d524c00f43824fe704bb9e99ada040b00ec1b5f30d0ae1cf5d5a75349ecd79ee02c6425045dcad1ec999470422152b8b9d99cde31300e7514172ab45752b2a81ef0e16259ea7cd56282e11dc7e5487c9f14d37c4c776a00fd0097afcaf7af67bb184e6f18dc9a2ed25541ebfee4d79a4e2d7252dafe153cd9754637ccda7e565c7c30fd41bf4847bb43ccc858a9a98c0f78f4fdbaa9c5adc4700217c7465119c039b85f1185f58b434d072667c13a63843b8d2d9aa20044dba5d7cfd6f2e0fe5f51c7c8830763333fc6673e069b38d55a45b62d5162e2a1f04ad1012759ff3b0d8067d4f0feee6ac478171f9addc89995902aea1105b26f8cde8dfa1d1ac8074e01c5c9a44b1efc89144a005b0b2751473b75584202b6ee1ec15dcfd1664fbfed5ca77e2373200811fe9b04361f8931bbd1d8627a6f69f7ef92aea74d6642c8cf6c9b25aaca935aaff6ef55a26d849f6cffed046224cfae279cdc9cb7f9e350a30e65160c1ec58f720e6b412e6521533bca98f25b9884c038705879d274d3a6adbdffe8f244764c356b5650a2afa37257ef448c686568ed15a1d69c726a95894eb7a3369df32176102d06f3c419ac6635e7becddcf0f98c6b614483cd505ecdd225907a781374469b0a8b463510f5226ae5538de21fa2e09874a22084509f6994bd920dc51d4015d903f37c1663cee521c021204741f09b7ccc41c3225100465f7590a73c9d0a53ec527b366097c1afa20c511fbc99189b5d8f95eb65fc7a93658a098a5d984b3f5a460437219d65632b1d59aea4968945c0a52ceec277fb7f96398dc700efd9adad510f48d5434011308fd39fab81bae86130c874b56b09f95a98f65accc344441e9b06db7782f5658d0e40fe9bafbdee7d7537dca463734fd2c89e91d59f33b69ac4248450882562b9a21dece4d4be001c12e6b981e1cccd36d23d1023d9c090c65d9557c1d3d4ca618ee390caae0c6adfa737dd02691b1654369895bb13719c10a473ed2144b1ef8094e0b7e012a895754246e61d48721e38035e38ffaf33d2faab470d961c2a8469a89817d2417e7cedf6d37f32f078fb89c4f02bdf621952cef3a0a160ba8579d8cb6d48dd6f703411ad0f398141c5578f1f6e31b51a37486b9040357aa69366a2c0401d527e4e3a704d825129ffa7dc1e5ed35d7cd4965d79672605202da169eb538279185a42b7e77c322fbd558abf5a5e15af4dc602ce605063165643d1af191c97d5fd1cb55c8c0c71f34030d92df76c0a1f065388396a9628675ab8671e6aa7af8f9079cd5d34b5bca18bd514ceba8304c31112ca021da5a74a2d8a33815f54c6b16960a27bb288cbce20072be0b2aa1caf32d3c9ba139734baf1d8a16b89399af1f1bc27ade37581d19dda85e4d1d03c144a35420e6adf0fe0a524d8ae74b65b3e9818c3be37d780e11e78c11bffb93ed29bd56de3f22eb2d916ce39c54a5d1c15a2d0eb18c36966248a559dea227a5d13530b836a887729d3ed77628203303562088a0f38f08b103cdf5b32f6de0dd188f3455e2ccd02fd446199e46edbadf56bdb94c22f052cdafc8426b406afc09832f4b0da9990adc5249ae3961129f1339c583940e11c38e46d70a34fd74d6996b30ff541d240b01efaf2520fec7410409dc53cca0ef8519eccc7ad71d62af948ae96676b215576e242ee753fc0037f7ab69056edc60e08b255a0775275c6fef5fd3ac9edb4145cb19417d265d7887e3361bb3f70e9c938bfc4f752e09be832ef5da349742f90cfd8a6fabf66ab4ff9808a143c57baa2bc1164044015903142eb427f0b61ce29e489fc5ca0583b3f77f184954b8160d5e3af6ed867f1ee8e8ff7d9c2e09eb61884c21a95b5b9009908a4ee8b43a99fd5fd099124d434ed189563ccd14cedf91542f3000d31f4057905daf5c7cb67e8d6db9b76b83e02eb09023e706bc96071232d16fa9f99867df2810cd6e79c7acbaccb7f39a5f2b984947d751a6607a8f606420500026470794da57d035622ed5fa6bb5df52b082451da24947a9cf2f1ae61f875c149854d77c60449542093d4f45cd3e1901a6269046f29b8c20233024f5fd368424a0b1d1f47d103cfe136cc08f0e765961a6ca79efb1c713b905c4b62fc2f6bd9969e432c75d33544e70b29b5b3e5b1be2a88b19bf55645a930c426f6b3c8af60e876fb3a4eca64067090ebbef8e8a2d76d511a0f20163ebde43f5ff80a5111442fd7a2fad3979c062ac8e88e33936cdafcdf6b972abf9e37233c3b425e2f58590cff84e12d68bfa923278026e3c872fe345fb63c3527f58ca43508b8a58da0302a0ad2a9b544af999cb4cb7e832d450fc227341df53b52df19d81dbb6db6c3e40d9134a99012ba0d7c0248c43761e8bf2117e8bde4050e6cc8686493698546b9b4f0dff7bec57a0e078b69b6de87318cc45c3f0994c75a7e93e336944f1283e5bf01059a4d5a013cf9beee9af4570deec3caded706e836289d0a6135262593ffed843c629831acfdedd2843d87baeb183297ab14c281f91cc2826d4ddb0aa6c536beaca7b8f03a9fe41889644778e866a471c3fb6598ae6e5ba3b9ec49920989fea34e79e414d4f4bbe2c6717b280fe67ec7515b037a5fe4058e2a8ca658a71bb3990c577496f77d74ae3a3c32d5c28c2b6de4a66cbf1e000afdb65b5fbf162626c5d0745611d7852762ab70adfb1994b64c3f463c3b09c2cbe9a5f6310c0e5ed9b3856b1bd6944ae860417a5dbd6c32402dbc2ede1f800e81728dd2e54f167b57f8f8dcc42448f2798e39528c1e13431680a733704ba5e12b21fc8e5699abf863054ce46c19c0ef6b2066f54ece4fad39f4ef07fa8a5ef2900fff2483a5e5a869469881679bc46781bfe0bc7473a0ee7bd3b0c6d8c8a9f279f1a387396cae5715a2c0b9bf2327f3eb4f88a5b95cf077109c25fcb3360cb18be5870abaf2404a3610bbfdc99cba86ffb3a1bf8e790d0edf98c4261614fdc035c1c7292692dc26c1b690d874f4fd07d7395a8598481b5019ccc3b4e67e95ce8a61fdbbb710ea6f559f3f61810b61a8fa1578bcb5568a0f6c6dd4879208452afd222e9a15b78ddb334ee552d1acff70596f6de43508199e9b0dbcfb8f39b1ad056279aa8e24ece7730039b391702bc877ae05d531e621bf9d5975bc9f3de0a679f72984046691be6e87c713f227baf7ea7ba3c50c312d7980d62028b878426610a077dc16155dbc808715d5b95fff773231fd1ed3427865c99b2708a4a75d89914ae17a714cf84dc4250cfad303068741d2b023d38a36bb6f478a91b9d568675321119f984d913607aa1d5c8be3efa6e4b52635c222f93167e9750f7cff6c83879da80755de54cd5234e78967371559ed37fefcf55fd2e27c63fe7cbef4fe00949e48b275ceb293e0673113e3f666703727afda784cc641bc9b201eb3dee5ec32fe7ccf51fa32d5cbb0089c47341f9a61ece4167ff0b3e6708ab357fdd0ef990821cf71fb5db3916f945b29311de050d740df004e02d8f2a344320bda11ec1b2bbbd4c9554b6463b2bb687d212decf1d1b4a1f5ae9f6abd1b5543451cd4d3db9e7141ff80765025050a9d69d93f75f0468502b00ae9217620628e80ec5f15b4bb0fd61e27f47c10e28c878dbfe7ac1bf16ed4eba8d203233ba14ce744261be1b143d3489386bc4869346dfa06404baa7de5e1d1a5fd26cf45dec6e72534a3582c061ff9b56c8aa31bbb76e7452efe5a6c3874a2d24f9d103adb259dede370ebc2cbcb9fff9a67086184d88a1cb98bc08cedb19144d5fa6110101e605022e1939557ad4059f03665471b3c02d36837162a6c7a94e128037a3a60cda490b47f92f8eb5953c616c11357475d2c505d1610637f5a9f95413de165d8bd3bca2ac8d5db03274e9da1417b89c674be7d5e68c4ac52a0c2e5ff16dc08ebeba8847be84e29578451af9128a7220a80a37b4ef950d7e45b7234a465470232d3252d94d5499110a6e365f2712459fa84a2b3a4a4dcfc62ff853b1b64a8eef8aea9a815854a358204b74cb641884ffe5350315752e23ae0331006279c76b6277178908d4834bd64a31643d77238a4acd72917023bd96156c15be8439594c7855c5cca958c324417356134cdd43bc2b03e766800e0dee2a17fc40654778fb2fab0de10cdcb56f3c733b8eb4d8b59b338dac4971c8648ef0efb3277331539ccfce9c35a8fc546e20816a8dc7aa35a4ab3596cf371c80dc5c5ccc817f51183d43e22908c1fec301e62e01e6a93dcfca6a52b2426599e61b3c035e00c70d147220500a2a3b9f4ed359d622561160d30cf22c060bfb519be2b1f73ae62685d26f0ddd927ff1074fe2278d44b70239da95b569d085b20a5ec6baddc4efe39eb73c98952fa842927aa53a61a5c590bacfd248de306ffd67df6d4a64aa853c8a262b5707d8e21f2e0a021c5894cf6c46fdf3a93341447376e21e05a45509d34ff3fecca8b23832c3c7202bf7f7a4910fd7ee561ec3933f1ca07a86966a12f207547c0a8b48e2ffd49b00a6926e0432f25f26e0820d14ebdd66a779b310e51ce4f8356af93b7f279deb3b10bfd904435d724c6a8360b0375b0ce4c8337fe7892eeb6977d979929b535cbfe099490c9631cd16d13cf4d72d42380aa404ceaed6f19e995ac1186265d45c350513c8c98bfb79d016b119178d95a5751b0abd87f2aa2a6b47b7c560350695eb5bc998f5cc16259b2b76c49754ea164ea3350628d7ab053aa15634de1d5852aad60209bf9ff1452da49f6231453c8d567b7256a71f52ac28e225bbd4bb5aebcbf0fd6a2191f1b1914fd24e2d888f1e385f5122c3b40554fd565759be3d356ca67b252404c673e3786dd2c403b88f75e1cf6900ccc23d66cd30361ffe8f70521fe93569b64c710896aa6ef22ca3158e9d08c7928c33846741b869c6f30d087110bdb873ba59776aa4cba60c1198bf8da3a272aed5aa4763d4f683d3b54cb7141f38afce25e696f119e5dd15d9ea1cc8741c544203ca02a041fe33444a64d53cf6ebcacd253a9851859c2465488bc180a700acfb82ba821b5700a9c0e4f794a00948f39c17a239ba7d3f2fbc26954fa1b26047625e867c83e5a5f0e95dd0bd0837a544618dc348d77499c23696e08a1cdd02c8a240104bbc0c25589a93809d9a1125687f8a04c615c1c0a1c3ebf5b6a61313aa116d52e466ce6cc1170b5fc3bcda137c3246d2e393b85817f81e2aef085040d30912f3a548eac31a979ad6e108ffef54e2882669ac71638d9c045e7870d976729c3ab0b0790926f15679a19d26bb8c121f1f911cc900543e6c1851d4b4aeb63047ad8c86b09c51945db01269e5e3d8c1a13093b2ea8afecdf9e6722eb64f81457f67dbd4170330280d9aa1a0a1f37e500b4555b88ed83b3c13ecabed3f48120e35a3ef3244e23fd4cc2a31cd51d1f15dd4dfd41d38941ab80335dbe2798ab1e4d24762e7ad383c9464a5a674d6dfecf6b8c390eb4b92f1a85409dee788d4c72c1152d3c0bc8f8328aacd9aaa6d6ddfd201c283004366a0f2df7799419a1ae4732df08ec6329fa63b5a021ba2fb5c682099e0258a88e4e40b2e4563515ae10546e94c2502335575f1324068c5fdcf97f2aa6184451ca2a053b2fc60cef117e6d8fce906db061cedca027f024e1c815d7ac07d44fb125a28a735700dd4b5abe1985b1475c821b64ea0cc4755e3446373a21ad1d23c2eb202baa7dc2241fc23c3610514322c8f6a5186d227622725323f0008a5dd699849295f4be6484fc9be7e7834d9649be5e9560a344cec6912def95be64299aaa85eb1c4ffdd4b4d4f1f6af021335afd6129ad5e5bd35da1be8c74d3935e857dbe09cfdeb7b15725aaa6be55f56ace7a8bc7eac44a5006784600d68d8a34c14e04561a1739818a68d86b4444081a71d869588708964d95a17f0b6daccc08a08460bd311521781103dbc64498a013001b8d230482dee2c4eb7f1a95b311eceb44bce3e1d9140e6e599d9756422aba21f553da12fae86e4a2a964a6bd9a194053eef89f964d12022aea9e902ea08e9df13638649c749c5d9828fa64b9b56f3fd08ffaf386bb82e95f092aab42ab333d28c205a3040498ca6be240743e6a0aba4e514bc6b1c98a71ca5df880a1e68b500138c6df8983b1cee74d85c5f045897aa54bcba23bb47c97337ea301440e1feba9f351bef845fd184d3a1bb6db19dfd82bfaeca220225be5e80d129174cfa30aad0eb3be76b9f86de448403ccd50a369dc2f872fd75435bac3c7e67e35981f5a59406c94fafc2087fa44fd8ff0f02a2b78be44a699d08e7671bd19fbaa6edcf344fe41a17462334c2c71c1c150caad9566398fa3bc0d807dd51995ac21ae81d0e410445e0ee588e008f59e3dc7cea095b3fcf2c515df224db5b454d755f4dffd09ecb954adf120acfdd1103cf741c0efc7820c6d67d60c5bcb36f108c5e34b6c7d4a650d57c7d1f9570ef8cc302ab91a037333ef05e7b34193f60d443672c3c14d223d2fad42f646520700451430dc803aaba04c53bc199200b58840765ec4e38e6f19e9e179f67e1f2512d603a3329a6ddc391633b5510f61d36c8a440825dd9110458d50e0ca12192ef24b099dcaedacb90ae5cf30701157e781a1df0240b9acb74f922017d0d9e818664bcafd88761aed9a853bee41c03fca0508080ad895ade5b01ceb27caba8aab3ccfed95d2cf525c0d241c73f8192ec02d2e3fb474ea35e0b3f3e45d47885e58f92af559c779c70f90c77bc3a3652e2419537ec854b8f6515d447af0ad6be9fe5d344e82d1ab23b551dff7cb20cc013331d1b10aaf736e6970a18ca62e291979f7a40d7e04e777d1424f1f4c2068864103aa141f709438ca9c58e57f130e00bb97dcc12e72bab3c64e48723d2605c839b089b8fd5b330a38c9fd9016ee0f9280512bb2b8c19113334199f927f26962fe52bc39e1bed518f785adf0cce1e3587db15d32363dd281125db9b507756be3f67ccd0e95133e3e362dd52cc7961b2ce69de0a415876a23cdceda53583518852eba74958e9a6a3d14a4b12068f85bc619f27f32ca9c5d17fd9e6f0006357c430ec559a81deb1e177a16601e3efe9e8473cf0fb5688f868412275f4bd99c0fd927ff639780ebac27f75b04dbffa731892f6db2e0d905cee153c9178ecfb2ebf7154ed0380ed1b4580e6c424dd1d34eb2c5dd19e4d1bdd65b228376d08ea35e43f9152c840003b46c02848301ed9df2e340a8185a2400436c8e2e226a6a0e5cf128404f51cc141fe59b2938530d415c8cbec91e151b7020705b6a7e6029ec15b328ec62160eb2008ce202a7f1dc1a6e2bc05b4786cc461e959722ee86a9f07113c2dc109e128deaa69eada03b7f077ea028c59bc4533e8e43ddb1a1d6213d2384c62c26b253b351038764e5afecfdbcfc90c961ceeda12cefe2524f8d1a46aca15e4d5016aae6ab2eb4eab74b1266dbb95b791728c65b97cb794f2cf1fb8af53eabe027431813370c8617e0e7bdec37b5d6aad66ed02eb1f10572910561561252a85df1bdfc259d23d46e09fee0635d7b6ecd1d8111169cc126734e042cee72353f9b782b6d4695e2226d43d3ad1ab630b6aa73aa1a7d03d2c2ee7ca3e3f3a86c971f436cb3655dda71738a1a10b350d6778bc8dcf4662f3906f7ae8a7c7074465dc70797b09d74ac479a7a2e812ef2bb029adfc370473d31388a55e7d9b1a23297a5c2714c47c00497bf08c564f60e633ebba7143ba2f1071f2215bc0b8ff67da776443d6324203e50b9ccb87d683b0afb06da05be1f23994d531d3f1110bbf635809e7b5ffe2e46633d7708f0338dda6e40be80979c1d3d713c2f21ebfa0f8dbea85111476407bc13172338690be177d16a03b04d97433a3674d2d784140046352b11056cba60def56fba7bab5aa7d41fad2f1c6ebb4c3fdab040b920dbb15427bf779e2b5e85f2f2fab036d41a6117bcd258a9e7be6ebf29bfdf064f39404cf2117883234cb8dec315dc53fdb801f1309b7889725d2734514b72301970cd9a663c158f79a9a2ba2b9e0d13feeb7ca25982c1f0d768845cce4d71768e67b6f2b0a77c24e0fbbf4706b94bdaf020ee1173277ee5184a430aa9237d7b4b905677a8fe86edb77a1c40be06e8145fa2191df7609d060d0960c886ea97510beec072c321e6629b2c77bdfe80ecfce0d2aa2edc6bd66a6f1b794c4634aca61e3dcfdc7ab8bcd76d3b00fd0102b9604ec045b96bd81575b491056aaf2368d80e1797f60e8ed4be81cb0ced8e5b32f2302f5b6939e02e83099a6536510336c3b8c6f0248824972b8335322741e8642c7c9b1c245c7813afdf517e081d73802d5660f5c35c2073a1318a6800d19d65c222b544fe1a3acb06a21c2462372ca9c1f14d6627d690a81167b167770766f0cead1b2814b71cef3a0b555f391f611d8db3a72d80b03c75dc5a9e912f0067395f88cf39f7672dbf6f02ec125846274e674d15f5171edb0a1dbeb224422b55ad0297fbffdb453408cf3eb058a04a85d6e64bbf9114b1b5f027931d063e3c049f214770d9a42fbb9211651f98f5dccf06aba7f4a31c93ee92c5ecfe9988b203896ada622fc5e4680fa4b4537cd99da23cc0a837da0431c4e809aa9e4c1c22ef301a3a32258f62018eb6249d5ba1052c89500eec777472d8fcd90148001d3b40985ce3ec3d43712aacf6b1ea1a458a5553d1f1ebdc1f76c6a80c87b00b766ecd25593f8a8f10dfcfd0688874efccb4ab83738e25ba8f7aa769c1b700365341c70b0ee067ac22d4cb58cb3dde6ab0b670db88567cd765b7a1cac309cd3341f8d97d10df3b07503058704fa2b050c11a2edd0eeb71cd187b0bea5cc00015e3f870497906d49bf4b114054b546e08c5b782360119d30f68fad369d18072c2747d597426aa20efc1495259121c312a2134cb22386146a0249e37ce94bcffd2b3fa1b89a32872a450c6deff2b284eb15e789d6e5568df3a5937858a6a5df30ed6edb774f2faadc77dc321f499c76b7e80a056b914fefb64fdd419adf8949057eacd4a54b2e4ea7a7c2f44a480bb04415cdf10d7943e446fb873f598404d0b2ed35c88b1b95ccbd1b7e9c20eab4321a5c958884989359675f5bbd9ae0e6f71e6d9d9a4507ae0ab24e8aa450346e0522ac5e1516c742f25c066ae83229479731f8133b814258d935f2dfb57c7f95a8b644c1000bf372adc559530437c868aa721d50431ad03923ce10db9a09bb2f92ccbdbd0e47cf18aa7c6fc933defb97ec69e273258c314c56dfb1de01be3a41f16b2710a5a717f10fb4bbcb9fdffda3241b3ec6fee00fc125f6f96204ab52a7073a48748111e6c12447dfe790af89ed0fa5b6c458d9a187d56fb1ef72fa5a2cd425efe7ee771e3763d4d7681b4144ab0bff10b970d900eed35df413a9e182552f778561f1654a55c2562ee11132c471f588c26172fdef763ff6d1e25953f3c132e8dc0551af6fc3cf452dc62fc2eec3a49538c9eb4f4a304c1eccf7917164598ffde51bc1a227a64a4e43d5464be455a32173a0431aeff0064646ed02c5abb6a5fffbe024d71f3840909ad6bc26a4e8c120cf267882315804bf52b322b88925d0b4dbc9c20af6c17e90cddccdb4a99449ba08888ad1b74a8504582566984a7e9172b322c6e5e11a1e0fe9ffcdddd34a8313e7d55d608909355a5059b423747809a837ffb9688e950cc4935922c0c37a95d4eda52e998d7ff1f651a1c40fa66f10061fd730847f2fbcc85e70722a07ca26d25577c28c48c9154f52c78b49c4911b4f650218e08f6f5f67627270e103d4c7d41b1098b4d0b8d2456d05177a54b00c48537392163c313242f659e1612032b8d8d4649785b19bc301eff18632327a6ab83a34f452f3782d2286eeb88e95e4a14e452c6a404716a3c1001e3bfc2d7d6d7e8c86fcbced271f617f813e266ac4c4d2fe2200af4088b2ff2aa712e4fa58ca0092a37ef9363414563be783c60250f59a06876ce22d4e48b5aa4562d55ecd697089bdbbe292fc2ea9a02cc5b20bfceda679172e06fe7f6dc5dba4c48435cf36411ae48c8514d25870bd847d1a105142ac6747f5f4af371d80960cb6017318ecf0428723e717e1c37f3abba461d510a8aa28b6beeccc2be818b039236990eb7032a6e06aa238f1c9085cf19a5732534acc7a0d6f5917a898be430120b1371b7a1980f7eff251b29401d8fdcaed811965d2fe4080235638fcde074405616150762d79504b8628e4bb14e813de9a29d2291749b9c88c5ea71b47aabdfa53f112d4111278a88b25291975c389fe0a1f3e15d53a2171ae12086e25487a3e20f23e35f2f73711720f523141328580f675b00959643f1c007f0e1fbabd415d6ee16a329e5bc66b8878111445dae6b03e18cbf9de750929738ee065c1e57c582f6d58f29819e18648479b5bc63bc1c5697ad31ef7823a30f3c8caf716f7d431a59086bc174aefaf6ae92841f97f4ca01864174ec3a4e9916241a761c04ef06090fdea1d24b193a2bcd3dc1fad5d3d567fb944a66b40f5175930c591c4665c8c2851543cee6eebcdc0f298b67ecf15d965ac709f376e3cd866b203a2b8d3bcffa505a54384ae1d25b4cac916b8083739b3f67bb39269e431064b003c2f00295cfff35171a7e37a5426a9a623ac4cbeda97a0dc83572a6de8cc2a8e702b4fc07d0c6f71266260035be97e8bc52c51bd25e180afdb2be4b6a2845347c8529627338587be76a07d71573ae689c396c70120d290148a7e2133edb133fdeb69ddd94e7aed71dc7d1014081a7fd597b2e24770b62f178e4c17b624d222863335c8677d95e99a7753a17599880e778797c0a918867319705a7a55391871f04f8fcdc6896e19a5d504ed06e21e496daa8273c80cfb64b3b97f3400f6117ff92931d0989f3d245f47d7317f9c1596121ccb66d068d3f3bbd68056ed2684f2369f705806453c58aca2bbeeff0cb01d5672739e0884d03332f14628650b33d9baba59ceea93de55d09c44082a96622e52865bab581f761cd06ef79d9b07f7f1ac88f3d6922fff02b8f7acabdcae6863c39b18bb26fd4bd76f6dd2e2b8d3c6f076fc3cab5d0eeaefad4ca34a10c9eda4d04865b7a24a46567093f93b914087b68acdbdb0d7d4bf0923267dc8d90fff83a0f0de507888a077acb371c964d6070e8d6a72f78026b5531dcf2d61752da83596f683042e898f98d3e8d10c8d7b14c41b3656ff90e5fc88f0c560a9d2a21c290839a0bc5c0c0c76f0fb429b5f945474a1a4cc93cb90e29cdeb68b05b29ec09f695976af2ffdc9a5789fcdbc32d34f41db47557f6028279af185e44c8540e71e8a554e1cdf7e2f1d7046993e4f9940b6d106049e97ae68ad832b186dfcde54e0f183ecf0dff2409851674b3a78c4c583985f911382442f209c58df0e8717d541fcc58341772ad8c9ca46fc7ca58c407d217f2646bb7a340f4c62763387eb6853eea19ca865b4cc23e52ea1d3db602971129247f1c9f6b11b0fd4962caf30c78209142ce1b4baed82d38161b75ddd99b1da093dee5a2a057f145ab4f53198ead5912676dc86a1d049536e85e11bec0d635da4a90d9b376a722cfb08a5d89e6fd094377526838d6761b8a548007b7eff8d80dd2e476965091f970afcfe6b013edd983a7c4559320d8f4c202d721adeeacd9c8d30b8bc5d890c68e059a8cb08900695563f1ad9b2be836c28a2776f31dd1e41f12559e2853fd431a9791216533662db178c83a97761e3e43676bc0ebd0085935fddcd0d2d040f6eae94ff54fb2237466a6cca6831f48eb2ec4e301b4004691b0378a043c5d317356675afdea72fc118fb935c96ad3628890edf39a0abca831a6d5c2bc608626cc33fe93b6b9eb41d074d4c5e58da5fb7884e091f0ebdee4919a28192ff7b791f8e12ede62771f46cb43fb1fdba9be67d6136c539c4576c17703432969334ae42ece264d054a52000e8a856a63029be45750a83b25b56a800cc3414552a34bd4c251835bae6f8bc52825c4066f7c56e7f63a67cb5f908de97bf7615f0d0f202795f1fc474b43be08868c154ca2d283a5d16eec70cdd4f26b796b0514db3c30a6d81013e62ab456da1c14404034213a25bd53f41b8bcef94c958962f18ac1b11f8d3db4f63e45de81b1cfb50fb296753098cd5c1ec97c923d407cdc300e7ce0f8c0ae00582b7c3e84ae979c31ba1bc308c014e23b633ead6e2d09ac7ef294257d247a6d53791a2146311f44244401fe217115d517a72695ff0a970ec62cce029ebc9898190ee04f777f6874e1d623522153e3b8e5778aec72b55ea5dcb22afcf3d08e164e8c6b69eb0101e4e73d2a8d14d238ce65f03d0088ef9bb59e28afbae5f144ae231fe3a81f833fa70e252ef9c79ddd22bb41ce073e4b72b10004cdab0f4af623988c9a616e0827c25233b00bfb113ac468efbdbb0216b1c3c63996f76061fd5d5d47ba2448875f033fbfd255a2ed43cc50ee9c66ce51a42d3e1a1c19ba0d780f5ef581f214d7f4112e98cae546d6478690a625fcdebdf0501945a6b2cb9591dbd46869b9ea93f8d04699fe8eb9c8e98b0aaac832722ff1e50628b813707176e3171cd54306213a469433071bc500e8d2a4d0e1db813186a8a0fe505b91031cbd04fb10e47cf1259f096a341614aa8dcaeaeb6b2ef901c19a9179e48b2329c84d00bf37d8cf9f3cc5c09c9074314b3a5373d846a095e26c8d6d707a826626442997d3ae7e4db10995517c7ccff02019ad9e772763306157cc55b34701bde2b3aa62b9a4503cf26576c8376eb5d1da0a10f9c3fd9f9ef955fbf8abe46a02e6c25d1eb38b2814a57345cb8193a30378ce3f7191156b163edcb9669f887ad270a4518e909a774e51b8dbea046c439ff2b94077deed438335340ff22e94dbece15a216d39167a2cc508f2be081c00167426341c2bd43f8f3abd8276ec6479ce1d60865f048b6cc46e58bf466454d48a6cfe02e1998c683aa069f520a9db44c3bee54baa2c38537e253209e5a31869230cd9905b59fef48674bee95b16506f856ba4fc38dd23898e0b9384f6d25c54af5b05babc9665e7e0efc05e4227496d3367e9134da3ecffeb400bfa469230bf8d49afcdfd5329a7c7824ec19cec22b8a642dbc12a425c92d2c395f5036a3df9e4154f8261a02f5f70de4932a27c8823562195921c97bf0e31fcba2d00694e92545b6ac7c3c166ccd6c80b6e5b1ee47b7fe0241f70c625c9872f79aaf8687adf3a1b46489d3c1c0cd1e8841a54a8133dcdd03a600165d6ef53e9ab66855c2ef3171c1c1bb3e1f127d3f082247a7adff52c9f11ec93ffa7745c6c7137677860a82cf2468fe0a1bcda4a7a5ac4b69b28a85597038f8ac6b70eab1c8b01179e9eab1e0f65e838124dfba2f4ce421f65821e2aaf2105aed22f37c17c4c231bfaa57b857ac9918d48cd7619213052ae5446a0c30ec40418a02672d228d1248c7f9d44f08e6a863e9388f811d6a6c4eb34f7b9a32d65e608e135fd43ea13a51404d838a5c544bae9713ac9d2c0f65de2b924a1ff5d9a0ca548629c8a6c2266f9bcb6a0660d8aff8a75e96f11ea6045ce21f6be7f37865e00b94a422f21f3493aefc485e187c3d463d68e4c18675008a9edaa04120234b857c92d62e872fae61704ae4c3f3bc6a977a638e903173e1bc81cb9ac36f3ff70d5f8d3021470a6f8177960b1a53e4122f274c34647635f22d0a38efb9ea25fed7b81389dfe5b8868a0b90eadcdaf307d049613eb52b0e70bf03a27752b9a2fca3a3cc24c580d389cbf314ccc29558188f15bcd0e3f869da86693741535363a94c029373a8d2600c1740d5017cd0698d3c56ecfb4ae0aaf18e9c57fa0ddc30eafbe51a9bae206f495010b4aeac0082bb582c0f2513e66e3003c13dfb6e306691960d9c3b374395df6f4c52ed7951f70eab39ddf89618e9065480004500ac0d1171087cafc438c57d752298c0510c9ead219f85fbff7b3a874e0967b73346299926008dcfb3ccdd366a263f8daeaaeb252a358767397465f5891a28d6f29315590753375e669da4b7b3ed37dd4aad83e982c17b017e1f02643c01c1e054175512f0a500297fbb87fb135ab93b15500e43e9e85e3b29e3afbf463287ead63cbfe0f3e2d1e00f3750287e7d296b0681a51e6e1fa739b8f1797c141ddfe0505067d193084a4f326b7edf1a0384f90ee8af420cbecad1b3ce01f59ba381970166ff37d14a605bcfa68041ef04259f6ba8bc2129e093f7f165e66e1a1160ecd0b949b8cdea54c41cc100176ef5ea0321a3898010c54806787be889d545f89b8eaa3c040413f04e32ea3478061eecda786a65e55af79d613d2b59eb88f0044abf279ddb91161ce6f289f9948b2ce8772bb89c97bb1effa559eb82895777ca26f5f097d6caff039c317a2357592f469210a999d229d63f7eae16e233b2737ed100725ea58dae03cf8a4a54d6879c2af78a98f757a8eea8e464b46a2f7662aee33438509235fcbef9ecacb44fb9f77a2003d785539d7ddbc6edb2f07062d1115ccf470f9ce5ebf6250ebc9e06a327eb875ecbbf2cba3ff9c4464f655b451acdfd7dd706834dd61e7aad2576294d4be9cb89f6a34942b2cc6a01cc326fb159c44de5db37680b1664a152c3d2187ee17641806a9e6aed01cd1d70094354511cb8608bf25c3db5a3e971081cf95392195f49c7a0d19e7d5737a062c015de57c7eea22b2de46e79ea9a3857b6949449ffc851b50fcfaa9265e26ac89a61f0c16197e7fcd0f48d7e62af70e0f5e98907acce959e8e7030f5955e9abe495b3fdff77dc579cace5d65350df78a8d48d9782ccc959c92c4eecbea38a3776f2b806efb1106372a51bf49dfb88754a51212b99f5aa41cc798f7a1954afedf53176950e0702cfa08c8d786ed7d59db6508731b3e04e8b040a52926736857ffd4d1749565c39388f9ec534a3b677c4176aabff5cb7ab1bc4cb4fc7b94a90b11bccef63125d4734d48d846dbcbe91265bf749dafae9413fbd355f0c7460545fdf4f703e867252cc924633df9387a378dffc635c995d6c2d1307390a627d3427d76fb4ae1d4fe5b650fe3a1bd1b6c104ddb5316048d837e1094ccb507054a278f81b050d6bc39433443dd7644e36f3a47bbc3d9c898db1f2ddc7e4638ee982eadc90f2fe695c0a6c72f3a409d15c0c6155cee4995f6505ec1b196f0f981e721c5c79b75e3ffc6568f6eecff7639d860c3d4a494645a52848688549747cea90e80fc9324fc0bcbd077ab101151043b63163020848a1c562deeab235cde257ac2fa7912ad78e6cad8ac395413c9a6defe22ab802b1b738269b4186dac3eea9af1b0b45efe34f4d4a7ea18295f330df5975146761169af332d19ecec237929ab539a3a9507364021a307d884e266963b47a3e9cba5ce203dc616debdba5c032b9903cd61bd40a3f3f0bc477ee30a8ce09644381c4f0ce393c3407034144e5898b4563bbe9a2c0f3da73165776f1a1c9965bcc9c937ef3b5edc9a41c9240dded42e9904ea551a367d995de82caeff834b4e37be69dd2609ef05f6a17aa114a8c61702285f711c61cb3ce209307fdaae621ae429de4f5b53801048fb783a1a6a66d7b69de9bb5b3a6379bd5b19349571a5407f2dcc50dbb0734446f401c19af29e04b1c6eebc4cdf436a0b48ca4fc15c19a1e69fb2dfb783955e39ebcd06dfc178d97a8cd06613baa89330062bcfcfeb4e9786d9297fe14b23bdce39e3d60614ac2462e9ff4c4b90777a9f7286df16ad7b3677a5b01b26b1f07897a4e69f98899407bf7edc5685f353749797a0f88ce71c6f3aa0549db74950d77c6c4468cde5b0e9752e06ebd8ce7fd14541b4dfe5da0a9464554aa902e01dbb59cfb7780685a79ada3f59c6af7b267f77c393119361254ff49f37005f9d698887f52dda5a5e09b812848e57e4519d4a92393a73116b8e9bd2e3add004a30c17cd2d20baf24ebb5587f056f37a32d8825a9d5c40f44e3cf83adece53b04e134b00b51ce4665f41ad832de85cf37c5b560d2748d0d7d60c7e54f0fc2e918da1cafef4cd93a0601ecdd30390f9a598870144a524fd3c8e4c72b0d1734e2ff603635879a9c24aff68575f401f2993d89a75b94f77a948d835ab73c89b3ff8a7dc022c888a8da34dd1b0db6958c43ea6cd810c2d71d610f6250a13fc125f09b3b7f410ef80670235611555c8a1a61b62bce732573a20f8a54d7e63714451cdcb973fd4fe2fd994b0f10506588c98ed9dd3d0e5bafe1b5ff04114f9cef3e79bb4516513076e25ab780245344c1236030714bfed5e619c9e9890bf1c49e45e6972aecc285db6505d75eb7188d0fd50727acbf42e4d64c6c5969c1e2637f782c37d365780d8590ef73baed92192b887aff27e13c91cc30106fbd5fe46a1d285e1429645ed77e369f0027ead80f8ebe28e0369dd89360e49b4788d3f9488d973ea33a37ece84226ca61b72b73440f8f0e3a3072e32ec4bfacd76a9604b4e827a4020c2d5170303e8fec3f4e467ebb9f5a0862ebc240044cda85bfc20975a5dbbe2c7809a966186912d05ef1a6036c8c8ab3c373e39aaf6c76c76eb3e0dcb1897c625e8a305e5718c18d40461cc51f98ed9f1f929c3e358842b4b951b50396baf710c128959afe77b2f4215a231504bad5c17577ac59213847acb075ab7ec046824f9f4f080f0545621df8bc252d9e11c870ce15847bf1ae4a69aed9da89d7eedf472397365a778f5fdc2b03a87ab5faaf5c117bde0bd7501502631ed1d268aa81822aa9f1ea6bfdba6788329279d6be06b03b704a1c4ab73077149db4643c85391f3bf153b40a49003942aa98e37483b960bcbdbde3c49a0cb1cf9678c68eadcd71176331f83a03d48eea90752ffc7139ab2d9b0579fc67d158d2a680c68a5c4877ce7cf759e949f1e248c2fb755222117c682a33ca6304db30b91c1cd8abb91d9159df83705e22db6ab1186f7bc2f70832daecaf6910ca5a5958f4dcae4da5c672e0d35c8e7cc95d3d7446cb86c8a941b266710671aed0aa5cd00e5412ffaee559ba32b5cde89e8a7a95f32867b9e533dd04cfcbff4d56c21c277669ff9de73fd0b41c144f3f8bbe4a097d838634c4e6bc321f74cb2079ed48df74a2c0f598ead37b2389f4673404dc9e8fcb5965053bec58ed4d23e0171e577699ba5ed7254eba8d129c6734923e7ef46db96509164b348243058b6fcf490d86836092bad6da5647066185c16f77c28d5234205b76ff9e4c44829202991c6381eba973db24d03d45a26748395fe0852c63071d283912ea79647674b1e9add586900a865838495ec735e4210b29d4609b63c13d65632eb1f6417f728fd6f712f9a82c977731d25f992680a861db4a0a0b8bd9542e1adb906126612598d0ae736fe79e6889a7d8754a87088d2af2df5dcd356561ae114e0a974e657fcc69449ced3d4577964c50b50f9510a25e1b26142875d937c48768647f4ca5d0e899876f47981377ae451f8fff17fba3e817524a9c58233c70f7bd8f19a60afca05633cc9c176819d6a91324eebaeef93be8b0fb4ec7456bb2f0f2fd3bd96de054876f7182d8b98c40b97d3c13605c2fb68db7d468d42dfb5876c8183e61dc59fb44efe9e01c147abbd67843da6568f5030128b28c39c68e45174751c3d438f62e078667656d749699adaf1760e1fd670e1207d51a298508398e2161ee1023fb8b4dd12001fde3a217da2758416f1674012f1aab8affd7c7742a128771190df8c045fde00e9ae270adcd91408b299447dd01f9a92fe5d47da24daddd859c0ce2faf24fa10af842edf854bbc91b2b2d733f663bfb037492b9ec2c6e229cc8d734513bad64d5bc45c91dc17966de21d73245d9389b18ce9f1f0c06f3bf02ca4d2317d259ba16fcb474091f092e297f074f6ea0bcf9370e70713f0c5709522e83d6feea4f02c98315eeefbf8c91017ac5e790e6cf6c9ba89e518630c2fd8ac3a2b5a67106f267b21c7f7b0b5efd6dda7432c10fba9c71484308e8054ce1894e125897b1bb3f82166104405fca7d313172f92332f242887e023fcf9209adbee5bf7d47425825e4fb4792de6ca5bf227887ce331cdd8432d83d5f74e9b7ee1fd9eff8aa2f1a6c8ed657d827688675cf1eaa58618784ac438fa8d819b271865539d5cca37a4bc42b66d8b1e2810d270c0b9b4c6d02bbdf6f1ff0804216da50cdf5b7747fcb61308db3bef45b8794db62063124cf1468075697d79df7b3fe7d4cc63d4943280c17b7e092648f67565bfe264267aeb04841501ff612b32fb2d17bfa97ca05f6a70a4207c9a3237a6ccd223b5a27ea3688484a80bcd15491127f40f64fb899a8e8ae2caa31ccfe70f110dae72f30ac851ada9b53169be19432dad8f2c4d7ac43b9b2455fa1b6f51604b0fb03317e95321a8bc0248837756ff4a6741af7eab7c18332ddc33cebdda78629f818f0a9a57916f01a4fead34352ba9849c7c2c8cfc4b5902a17b7d69c8bd060951f8791a4fac223b9dc8f892c9895afd65ae0e14857deeb2e97161acb30aa5e5a5f4dd6564c95525477c426c99350b7bbd0c7122744063cfd042858e645065b0f65bc3e66e82e9121ab8f1012a13184400e1513bd028a08a96a4ad5790232452dc2fc2dddef73139f35e7ebdcbebee799274da9e98826c2d7c18cc8a94f0455fd090a014e3750f29f34a99cc3c96a9d38b4901ce5a9b0befa216a89e1736fb101fa1fbd28a18a68e07e4ad362c8161a405e7c764a2056fb6577664422caa9266441ac66d35083a0c358113e8568b417eb27fcdcffb174705a2fa7531cb3586f9aa218e31ccde59c9fa547034de7d8a195a0fcfd7b26c5c78049444d13ef1bea4f9d9d02c28d133857081864ef7e7385b9d4e527be93217f82f2b4702b9f7101d1d4e9d6311ad8eb0c3275217e126d138e33d145beb5dff8d7e36afc0b27e967e1ab36162e25e885533e3ea024fc648b89e21a230b62991399f7fdd1b388c86145b406be8e0d5c654a2db42ff3c5b7ad38e9610720667890297f3ccc8a9af8516734629c90a5642bfe94194be048e9408f487ea1b12865bec082a02e3cebd5627e4c6e2a1da1571f653e04805f2ff7959b6231b4177c35797ab4dcd14e9d27c8f8540bbfd31193725e56ad0cf36d71b7d52f63af17e95f331d6aee734723a9ca4d846df093537de4bb08e6fbf67ee8bc6e4090f595c8ac11c795a7190dc04d6aa88b281fa7da78fe254f682232d33d5f6bb002a5badfa76b8ccacb26cfd25ebf325c4134b1a213a94147c70f1f770cb697d75578a5f61eedcfca91285ce79775463964497a02a623a499ac0a58bdb9e1f69d93b1d03a230a1f9080f6cd5a1d19a768b43088b9681efdfe5d117078a233157ba54146c046d73226f89260c635f2e9290ae431154c914fe74f833ea4145f3f1fbbf9b512ee387bd41d086eaabfef892250a9a9876a4542c144e27d87d4d7e2fb859c08d3e9790765f9c11df0a5df63f2a0463d8a8e435a6600a329baeaab9dad680c6cedabcde889b1a21ee8f8e1848bf74e52e01bd0872574d9967bf1274ee175febd389184557fb1b537a9818e2a3005f630fd4b84cd9d0bdfa1c04a945d5bffaa0d6109cac9bb3823ae369d863291694e92e4f49c871d833e7194734a71f3d9ad3f27baa85aca9e4823b5a9e6dd6608a7be77effbf05c148e7b5b010c290e96d83a508b428240c25c95e598c31f78606d091e17584b28b80a290382cf5846cf131f3229ffee2d7c56ec8ecb9c360566dbac1d6d51b113634c3035aaac64e6e3c4908e7a8166739a8c72c36562dd4a69a1754208c74c9f4d2f2e58c868433104d80973dff93124944502b5d88b89a8a2383536744f506dc39734325f062d623849f0509c08995ca24be3dfb8da5a9b20bedb97187fd3687fe2c7e047805ba4a07574e3f16b1023417d048f1748b8af5f227b21ae50dc83a178b5bd14d4b89c937f38a601880ab6bfa8985efe2dffa1c6db3129642876050a65deea0ea565b931ea02a226db02b535ba087a381557298dcabae61eec6184235e765a06c6d09e4afdc0dac69be58e0beacb637b0b5f505b061b0d50cba8b1fe2b0bc38649effe16f575efc55534a4470d405f41fe84e93bc808d8bc5e787a5723d6b0e6a587a6a204cbb46fcad7291485a1786dbdbf9c10612a0aa7fca1cd221f210faf4d921129e5b56b2474675b9e6e083f4ec6ac6e6c15cbd70689a047ac58c289933381f74dba862453b7e87126465a8d8e15563505e26166eeabf9c8621d4900efb96943753521c8947e97e389c65d6f2f5a398e710bbd030403f0a4887c805ac2b599476d332a33cf1ba1c15aca6b45b269087a85a9e479faad8fb04ab48c703cf7810db853005af30502ab3e1817bf140acda5067cadfc3f8eb66041f774eab31959d06c3e9507a05caa0e9fed4f0c08823e5afcc1175ede85aab90f3c3d7c8ec78d885d717c59677cace1bca655714287cbc7edd6ca8cf564d14bcc47fd1cb50e1924ad4e197db515c3de42226bd0931c4def10d1003fcb226c41e8338dd124be28736e500fc510d2d70184ead50f122f4d3abece29ea3726de9ae50d5889d4f4f06cbc10dd4e30b38693528677ad16b185159c2c7b682490211136c1fd0b36671268e18caf2803e0f6f91a8155e96100b57feaa816b78d427b4815765441d0af7152227bf2348ded07f744a8921e1268343c790c49f31707f098a8416d5ec5bc8ac1fb0b742f1a3db0263757456879e23aedbf88d7bf50d41a3839a829af1b7c01c989bd898ba4b1bba05309f3e9e2c40df701712bd6ccbd4df9672f1d088b88f306b1777fd3344b22366e2c4023ae20fc5a351f925d4e988c9d0a35fc9f2000dacb064a6e917aabb8bc15a285eaba2279295059dbf7f518f1f6d59008a2c09eed1751eafcc8e67f11190b01b8b26e852e2287feef8fc5f826bae828b8a0696207e56410c78f7a7d981712296113edeb03d44226467d98f18277f4650963df60681b72b064e200bf99bc526da604b94bf86dedbce8a1a556e8622469a2d00950f17f2b2c652d872ed5aed2981eefcf2941147bdfd078feab8f70f8922a7d376a424ee3b5e7aa9efa94d74791cdf6d98f3a8d45aa3c6c2e3a88429358aa3ece1cdb3c5a113c7a5486308cdcd07258dafb00ae835a7ace6f902f7ba71637d2ca5d0be8c1d6b5f3bf928eccf9ea7ce0df4b27004033a1578a3a98146a55773a438ce7e35ff638ebe6fd55c986012b84d295043fe0f3cfc3a6f80db9a2dd9440109fe09e0e29e7a63461c2ffeec7ff4de6c8eae5a9b1336ddc1fd4495015966aa47e904a03e032778fbe13cd478a901d5b2267e480741c7453e5b37b2e9ac234ccc93c0d5f5c85c266747328e61e06d14250a0fdf9e1014031e8a22782d0581ed4a5d52c487366e6bddbd6d8004d040c9d66f52787b4894c3e804ef658949bfe9838203cbd137319703fc8f16ef74958a525a586c78f438fa36e5ecdb802d64ba186503b59dbdeffd5ca2b2430f4f87c1100488e5811fa0f86c31d7b40a5ae476235ca74a6c518532c0903a1f4a8840347dfef721c4bdb3f7307aee0df66b1c895796a0a533fca1abfe289dd500b7176af94b6ff826e46770de9ae967fed5b6a059936f2f52211c11e50bba834370e7f7105715357a86254b75d5a253a92ee55fede016c9f817d8d001e78d6594cac5f6cee303aa8c8cbbf2f4320f59318b6ee78b71603465a03d9dc9e95897940482496f63777c8e4c19a9c34bc416e41f8df00ffe374504545c15c2aa0df20c78d918d0e6feeb4cdcddc1e5d851dea0fd28ca9cc8b35fa16ef0ab36e2c100d5075b505602488bec048c9baaef2d0c79862a1fe8beff0586eb2ba9796825839a877667a12cc065a6512fe8764599b7816cf024228dfa82beaf07b21520eab1fe3aa3b7aed69bc728f972572a6af4aa42bfddcab243a24045f99a6468d5395d4415b85fb08731c1bc24111fd3a82c66ec0d36541a154e22bb69e87f72a5463dfe2d9975af030162520b0528acb0bd68047a20cd30f5f0a0ad6ba2b760755234c16b44fc9e1124fe029a69008a1c76243e20ec04fa70278dc50fcc6f19323462af2f9301f49cc57fe21ee48747983c73bc8608b2906ff9bd1ade4d7c340b572be512744939f0ac6ed71af2ba26e7e41a1cca2bb253816943dbca1eef436d6035454108d62fa41e2649f24bda9fa77cf939ea63de88ac44ed14b1935d8c4122cb12713ac53e1c22a48b5c86e60c526dad177067be6ba6a6251e7c36f39dc0b4be8fce1e6f6ce884eaeb1fd75b6a2a472164d0714e1d685aac03ad264db767856511728fd519543fd3bfce2e7345f489488a155b0609df03f2d6b6d279eb590423b4d6129897bcc3e7a69b925df95816cbcf2e309ffc73736d4a0500ef1355fdfe1f17cb20ffca8dc5614dd46a079ada661e087b9e7e057b6b7843daae14dc94cd029bd0e8f79f3819338bf78bb848324071e7f0c14aedb33a47d17ec905572474456a83de1c4ba8b08d4ee62a15a19e2704bc5fd1270235286f10881f1ac596e6340376e47998c481187860a9f1c2136cb62a4ee819dd538736ff63b7707d689d847080289f13cc94db86d9146247e40d8c2718c769097d0d1ea5c2597222c49d5b5d635ae872f5ecce559cb3a95fe350080e8b15ac73224114b327cfea6206d203d3cf1fce8b38afb81a966636de92e5dac4644f8f80152784b0196457be93c3695e371789d55fbef341918ce95b17c2c32cace05da6b5fae1d2b21278411e392253870c1612617b88f79086c7f29da4d55ad678fe8aacbf9cf3cc9ef68643d901be0339085a44ce808198415a8666f2ae9de242761ffc1bb08684cf0a7346ed74bb575fc309f2ef548bb822c2f92c0672515b80faeba8472c8b0ef2b213b6da1a13180caf34fa52b6552562e53cd2da016467f059bcc9012fb640a32f72c6affbef380fe78e51cc3a630ce5c6dd188886951b198862822653346795bbc22baa9e1f961765cf21879eed082ea85834bd91cdc0f114ff061a5f292e8e95ffe33b46f9f84bd92c2442e36807bad35d17cf40b18678faf867ff63319b7259efa8f35e16a313fcef963aff8f91aae8587394c63547cc433c51fa7334a0b359e170ce13071b2bce6e9aee4d7d657f73b458b0d14f349e4d2104cf20346883b614acdd80e7f203303c61b06f910405fe7b8d51401bb325c2f5dedabb31901e3c9f7f1cf3a866d11f55a10f76459177ae751b6feebe3e2dbba6158ce43d885e62eb85ab9cb2230c6b62d78b7ebeda5612c5b35099a307db7444f1629446210ed8c9bfee46bde04842f845b707799962fa4d4ccd024e56d3024ae2ec5cc43c256b4d695de3a5186eea953d8e9b2488f9f292cf21794831d7bb2e9c4542a7b70f1eb11dc16f3e1b27b5de74b0eb5a1d249df21f6c560a60e900b92884c47863cb0af07ee640d5b6fd85e69e23d0b64f2230686c3d03dabaaab2721eb39c300c2636bbeb0c7eee01ed1aa06394e358c410c6dd5a176165182c804e87f20c7393a8d653bddf7de529b66c85c16de74cab905a013d2282c2ff801edfe01d4dc64600af587ac32c92b38b8c07f6f6f65f3e0de1a1bf1abdf42841a0a07288ecf1a13a74b2da6e71585cb7388a337effb1d87f6b81cf310b7cba9aa85434096247db94a63128f6dcb35758079136c6fda1c44a8401a6cd35b4c821104126b504ea351d10e215ee7dc8df8a300cef9e0e401fef306a815148d8b61286407f48db7e89b53b3340b5f89974f392a446a5323df58c8305887e4b9ebfd19e026cd51a69a2eac6ab15913d19e71032c7d6dc2ead3e648b302f388a104bdde9ba22384c2794bacc534469a3191fc244c1306c87368d8dee453cbf798916b84d490207cd761073f447da5dacf9e22e24c98215b49e7a5a8e9932460be451ced725b8205ea662f1b690903c67f21d6c8faff97ea2e0180e113968e3b1216042512a271437dc4f2882de49643cf149be2bc759305899968862979d4b3336461c185f9bddffb765d3a9a82eb4aae07d4549d2383a3418d288757a587afdbb7c268aa914b91337553896c944ea0a2af3e6ed483ec66bd6d74a37868bbee605cd4a678412a40fb2487d04c452f95002c12c2a0d0563334655e4acba32e61d619538652cc92b7f91c46eddfc2911a3242b77d408af90407a94896b7de3d594b57b465c194f58ff49429a9309d5229cba6184ae95cdb28cc268d173222a64f2dcb3be88d42e241ebded9f885a89d066e6eb84b6f30007d0e312949a5ce24afb08810a1a687722c58e247c6d0789bc896941ae648be9f1f8f05d116f18d4a909036e0d90a448621e99ef2a527caf4b129cdccc883fae651a6f0a9a75c2c513426425215b191297a5e27f6b95367b5fb027c27be96fee784490c640b810e41219c80551ff93978e902dfd4d1be43236e4627c57fca38420bd8a4cb4138cb509b114641a8a81d085c1da6199a5ae4b88eb14649c9d402c2d12a3242b58404b400be1f5f5f59603fce1590dc53ff6535a10eca8509eddfcd04bd3cbb556d07cb143ad3afed916852db13b51c7c38d53929da7156331100f6f18a283c61b66d70945c995077d792d858eaf74d1f9c549676a64ba956799c83777dd5ea04d1be42edb4fc1891e33384fd1a7148cd159b0b9e369dce4c3aacf020bfd5e5fb5d4ea34541220bc5eda3d2a2112f69756e6f84cbbcb7678bfa55b5bc9e77d1b5a739375b3cf01a486f3d76e31fc143e605462dcf61345635406e7f21449cf4d38e0c2018803432181286dc34fe0f3e28380529a9f6b08a9532bbcdf61aee5b9a78e9908100a6981d2132d4927e90063d1e718537bd638ab864562c8c800b426df9f68104846df033e8dcc0c2380d70450065f7a3f2bcb8c9d2e489061ba34999dcbb44bcd993082519fef9d83ee3988fe53a8a2e489069195de37095d19abc7498ac379f561273c865cc773d81e9181d49318d07003fb4a4234ab807c1a11d6174b3588c0651216aa0d4e5ae69b405160b2260630b4958c55dbdd48bea813e79c24c4b1002a0820c58b674844e22f911ce7005bf586466dd70c3544b76620d528927552fd99d94b23ba3bc86c5c03d69c2ba902df9edd76f495304891a1d161cd2f001eecc4de919ce68683894552423cfab90f112873ea3d4590b708732c62bacb10468a54bb20fb43493a6dd229bdb2829cd1a17a1e146493104599c1367bdc301e58b0267922d474fd2c471f566b28c7c37e3191b17f6c6d46ba376cb48c4daab96aa2cffd552eb7435b78dc6a710e70afed632959f5c38f5f970b1612421bdc73f57e1457db186cbfaf36d060ca7b066ebcda5db06ea550b6affdef73edadf1eeca7e1706e8010815e3163c81880cc9d4d822962d67a295f539608c50e8ee900a2781596ac2a241d9bfced15c8803c0c01839360ca82854a78eef62f6b02c2d4d668ec25ced2558ac9e098a669d4f74b389eed8c51205ea13a83a947d77314d13e2d967acd710c489ad6518ad65ef286c797b6fb056b7e804116f1c1fb604f86f01132ac24846d304ba8a192a127c2f1685e288568ee3d89af90c51b176ca7a59be4605f245029b2173876c1fe03cfa260650d2d7f55bb8320d0df0697c778ecb66572e3e6a823b236f5050239f41e04369acc03f6f97eb1fb9b32f0e72814167c109bd95cc6fe5f6d3b3cd30a83fbc2bb16976ec526e5c56a9f7c2d148feac11d4d5d144abb1fe45c6db5249329809961618f4c030fd453593e0f9aaae8c9cf7e9824f39698891c2a78179aa8777c102ce7cad13d35ee2a2ae14bc066ddf5ca6c0db7ab02d69e4bda61ab9ca750a8232e3e8b6deb936d153587101251df99f3daf26a88c1a0b373b7355400c59761375b5224cd2fb983f38cf5805e6c732f85875c33be88c1e1a335762b4a1a066cc7c98b857a6b9a810b7849bb41834e1cd0644f1ea47558b066f8cf141e5bb7abd518941420d9d47ea821f7d960552586ab13ec64c0f2b4c760fdd6eaca17959049e8868b5af29d7b30001b039f2aa3309f60af28b2649cf78e04d6802756106783049b31b962f6129a9b81f4e66985401c93ac92a0e5a3a2035e532cebfa5beb057a563b821fc4e84041551bc9520ffa326af11e2385eeb8015020d3dd76384f426defed15084455795b822cee4733c7a88a217087cf42829dba38bb6d1b1dcf7947dd4d9dd2860362041d4a35ed7f53786094886dc7814b3c7a10d4770e2531c79986006490eb5299ba99aa90530a92d6fe7eeba4f7a129d3c7ef546a08260e1e7aba282d4fe0a7285efb7f54b965ce168cc62f398cef8f638e147eb1fe329fec986102d0eb32ce414c6f78b1c13f9435d7ec72eea79ca9854d0f47f5e5c199c69a9daeb1d1ba052556233732d486a050aa047a6e9d0454f424df48957f3719fea6a77c4277a5ec8fd9aa6b6dde678a34d7dd86d42e3e82994c37b298783abf8da15eca2350c80b63ec3614801389d1e17f8480f63906ff8a4ec091f92f715e24e54d1b2df793e29dd7d5609f794ef4f897d7e1eaec50b469d02bf50246c09c077a91f4b4c16fb9cc417a9cd8e4eeddffe40095c11b0b8b3a92f87d657bad5c9e8ddfbaf11490cf0eea2ef251629551abc752217dcd20fbd3c8b88d7682f9a2b0feef04fc9798ce23596542e39872819fe0f8277073c47db9b682625b5c3ecb2cd350f82b317ee756481e0191e107a7b155fd0fd767811547ee2482f3e27767a5f3237670e61c884111052a1ae74a460ea8e9f7406fa9b9194bbb3c8e261d3cb0d87ae1ec1e6bb025efaf4df6ec027a5b6da24bd35ba3e1541a0f7cd592601a4d109cb6d32423537e632008a73b08f2e9c25d6ad282976c15e896318ac59a0923d28daf1217588854ed86882c7835e2178d8423b8499b85be95f38bf5cfbbfdac3e345880d4c14b47e808680188d72d9933c4cf5a236f0b285d6f694c51ac6a0540158828280637b60259691f3028005e9dba410701fb9af08e222240b44e80a39813410316094f437802ae9ff9e41d418c1b8f86d8c0386b17668f8bbbac866565cae278eb10bbd07e61ff88a51f0464ca113af95123b3ce90fff8951cfb429578d3eb727d2c30a919d1b7acf27a98109d81264c9e00aedff9e275d009fba6ea4a756b7aba5d90ab4b2202c801e57f48c6403e007ed5bb269ca23741f16803d23980edd92c343773e47b6de2348e2b0fa79d23c2179862ff338ff57ebf109496134d12fce1d0260b2d129d52da40f55f21b96510a5fdfee264bae0190be6fd34ccfcccb357caf04ba9897ba5a20d2aa9a0eb1f0b0566d2d5601cf9f3ff79f1720583e2c8f8662124bf063e69916b5ce71d016b5202793ccd4a68b47e7cc85308540000749d758d36d00b6d1080e2d7a982046e5cf7d1e366d5d60da505bb2bb6db9b794322519bc0b5b0ba90b40dd923fda2b250840c4a42c09b164a349b102889a14287e76a6c8a43401ba4949e267678b996ce78a26294a86d8c942465b03881d2c966ecca0bb9d3485d1c48de9c2dbe1da2d6493153dd41d2937eb99e07e4527155c0a851b41930a3e2a9a84ac0b9a96749dc735cecb296a1481a684bd9289c6048d8c972af2469342054b7bf064baa562898a15dc1c40a060c9ac99be7b2a4f34a857f144eda5ed2bdff22eaaa2a2089b82cb3e4a5394e05bca143df876150c60e0b529648fc35eda840fb3252c1cad3184c403176f5f22f1900510252b50620225414a643f82df4a707876d087332ad759cf07a6298e363c0ab4e75c82cd9aa89dd9df897e6205ba5724e2561f5b8c9ef3ce8d5a6afdba4374529f74526a9d8e364fa97f4e9d06e9ef649fe3a290f2dc588184d4b80d1c9e039a2f954fb738e76e057ace7ae2119fe7dc863662f286f30e5b6c41dccec7165ba384e47820ee18b6d848582a21247aaec5136ef2861b0095dbf94843f49c8f25143de734445207e639a729923afd9cfb92d491e1688eefc251f51485ee0c21b459383893bd39cad5c059520bc8c9a1b3721874472b64c8a94f6f694a0817f1d9618721a1bc610fe58df5504384801635845d288f3ddfa186dcbb004fc58013f5a87f60832008822c6d0b450db1a2957c3ab554e744296ee07b2a733a38c9f97d3e33847b1dc7d99bd7716847699a456d58cda2a816f796d0678b3a2a54cdd6add1e1563f39f7853370a770c513b533071074a9ff927b61548ed3ae1c20d2f6940ba95b7943290585561c77fbc53c30a96c70a80389f2d4a5d1d3b1c75318d7cd5a7b0a0ad1aa51bbba163cf52a025a133f562adf11fd589586d420c4861076296ea5f665903755488ee7f8642341f5cf4e708254526e3a69d7926afa0a3a3be956f54a3f75e54df5b14e13dcd183f2d551ee41f18ada057a752f4abb3ecebde9abd3eaf40a17d121cfab53a27659af4ea1b4ab739c5bd152114e11abe6b4d453910e89455cea4c6514e82b0dfa8a866c55af59beeaf4f0d56f38872059d553e10c8264556fa9aee21ff3c629cc3150e14caa0e56ffc259a559d5695dd4518aa6cafaadcce4432cafba0db989b1348bc9a659b59b6335abba337d759755f740f688f07116b51c78ca51087c907c88bd4492317dbf449201415685529513744e14286f98a538723f03d883295d4adc769e05013d77cb9e3695cbce602c711ce7c5650e75801eefa9b82242fd472aae888c948a3380dff354521619cd1c002c3064dc3b3b0552a727ed44d099b2b3145741bac10099f96748e50d3768821294a20ed033d61f574474ac881c992292f933806f6708260a5edc9132d8f4a9b3d4a9fdab9590ae279250d291a758be1e3ef4f0c1d2396ba6b83c5d822e3bab6688cbd58334a57409b0668a39e7945d4c950bb5a1387b38404fe5953d421f09f7ab22d655f85929a59f9cd5f3449e24302bce2e6a15a96d166bb6a8733ed28f4efa53047db6a4f770025229ab78e4feb8a21ebe94f2e107e905c81be9349436737613e193f255e9a5dee24db1a0c5651c1d2c9b66499fde2ce949baa77b62a1e374e972ba54c01465900218018569170555dc3072a499dd50661075808109225d061a707e2c3203ce0a2f6538aaf0b2c74bc93544c2fd9c775c59162a6a67f653a7744e59a4f315be9ffaac4f9bfaf81c579d210b4fc3713aa5655cebdd89697c2979d3a9027c15e5b659d27b98e915ebd1130deb7b34b4f993f57e40c398b79e5865ab7ad5400cfd8f84f95f09fd9f09a0174a58cb84d92c59ac162a527953bd76cc226d82e5d3b9f4be095a13fe9b25fc4749789bf7420e05322e03e0bf4efccf86ff79a711fefb6afe0373fc87a2f9af2535f3dfc5f19f8beabf171b32ffc174a10e34d80ec4b08c8dff6ebcfca772f90fc7fd6f26f5df8a06f55f0ef0bf9aefbf1140fb7968ff13bbff00c0fd6753ff23a104d086126602d8ffb558ac962972b5c57ea2bc74d44ba49f273f27b84838c490624d2f916237e94a2f9162472f9d7b8914bbe24f2f91622fa8434688b845c4b5d391ac143cc106c5129f98d8e03abf9932ee78818ed01f6f4cfe49a84f4137d628215f1033ee780a623f059d8498d854b6a4f95ab9c0237e49432fa7e880c9f35f990de8ded4ad5d724a0e62df9eb2d2ae3953b724ee5d4269ca365fa7d96cb507a592886a41f8f6d451bb02f0ed21d3eddcfa780aa2af7b7f96b8ea0924aba9734d48579d35ab9d3a47945a82521f321f41f8007a095d95ba35ab845b54fbf114e5443bd59a053796baf1b3fbc82967cbf301f4239206bd513b608bd7706c098328ddea9f7be3079a9ea25ca268cfd370862a7bd489e84fee85230d7ffa79f6ef939d824e3329c794946f61fa14edbb4508b63f41d6134871bcf9f625be478ef9b878cabf5478639ce7793540443f29db89c9cce714f431579aa2d2ae8e09176fbfd75f78f19b7a118b9efd857b43caef4774830dff662b175efcfa8b5f9733e562435cb9e0e22977f1948fa7a0d3900d678084d9b0e14d6c881768b69a7ff0f37cc9beb1efe51b0a53b714530e22268aee29ca89369e8268a5b19bac5d63aa768388ee0502ef12267ebc42361cbfd9b3a76e29a6540de7afa73c15aab853d029a85f269c664eda254f4646a7a3ef53d0f7e9e809a51f4f51c613ed543bd9be9dab74fcd8a79d6572e425e367bbe2ca45bc21e9d9af084b89ab1752377cb6d9624f891fed12ad78d083d17c39e5f6d30393670b20f1f0040f27e8ee25120f4293879f9797483b1081da00a3b6247131e876a8c222ed1085b7031327ee25d20e4b7c3b240122ed008442f2c182e423860e2e172e545cb1364027605080e46304924f0f374035542721e0f06e33322f9174e862a5c314cf21920e2bc8a18350cdd11ab69897483af430022a0d4fbd44ca810c31a6060000b7b0c1f112298726542f91720841094839fc98605f22e5e0f3ec9d10394c8ec59534f8c1f29f5cd956549a15a671a78faa5ab3648e384f6a1846a100c5ad8eb3e3e64a3b6ecd6a195bcc4b47418a3b6b3a5b13b2703a108528ee748b7229ee17b33365dc2044a09e27f368dcd1937ce9dbe9e71f6aa7b24f54156d747c206a07c65708a881b3bc0df75d1c85c3c5651cf495834e6ff88c735ebf180fd2b3863031de282197551f96af8989d0062704c2d271c369eddae1aa44dc91feac72b6a452d75e171eb716794c1b343034df42f00096dc71c70d86479033cdd7d7245d3bc8903790ae1db766b5d7f9ca01c67690d1b4e3c689636de2a938d65a259237341750a8e5850a62fcf421849fed63bd818489711819991b39dcb881daa98e03b5e339825c99b4284abb5649cd725bb3a8ec70c7afc957b6fae50043236e4dcdc73b4430945d11b523532b6a5647a1d5788d8fe0352e6134a1d3388dc8b37a81c673780ef18655159bb8d3485828f6c0ea374208c6400fbfa60673842e44b3458463ab1638f6557429eaaf51e256e7719bade67173168bc7cd431e4ccd42fb083c9a7ac478dc5a67dc1d37149adc91845b0ea711c1188dd7885fd38e5b4d6dfa11e69bfee542d4ad76a1b9147912e85323ded86ef51a722cc56212d5028ea73ef6ecd45042a35aa8fd3d44c01f799e8a2b1752b01a3ffdc8dfa446e4f1006c07d36c354a015e0772a89d1a988c223c12eafc1caa85160b762805d41f6f78ce3f0fb42805f48f1d78ea5ecf5311f45043bcd3773a9d4e1fd73226798e08f76392df61a559206a88cb0a3b82b04a5ad976a0b1b2edc8b2b23511499dd395a9f1e8b10690cb0fe52cd08fde836381e31a9fa0974c6db6642e4b9e631b1678dc503b3cac34abdd85887dc68c9be367cab82e505ca2b818cd947147f0c7952d478d1f990b5050514e4e8e0f283e5043e8bb10c99bf61d396e163b7618c1e3b6b2edb89d7830b5eb84df71e37165655bb251af3247cdea55154ae323a3d4ac4a65a834ab65929a15448dda8ca74cedd3a132351e4d3c9878dca608c69ad51ec48ceb52e4f3faf99a98ee88f3399cc6c76fb6b2ad92bea69a99322ef51933ee077e90a62b7f0049dcd19364a8c8d496ea4da726e92cdda64f2c265bbafdf8f12387cff9354da09f0efefca414fc59f2354dda57237eb4d99a353e828f20f2c8d91abfa69f5e23f6c0a8387eb59f9e23046353e640503bb53ef1a466f992a32029004384e4b843f2260001f65e95ca9dc5c2c1e99c6f2579237de502093c49180b2f5e007be26e8293e03d309b70e5828db39c25f29420d2e0f28f766667583ebde5322e4ff0187fa1e0302edddae64b874bb74956a95f372edd5669d78b4bb7b7f9dae1d2c5a5db2bfd4ac1a55b2cedba2edd8bcd178e4bf764de4fbf5470e9de9276b5b8742f68be5670e9decc63d22f1e2edd73e211cd570f97ee157951fac5824bf78cbc9a67f3a8f4ebe5d23da576590f0120860000404813e38e012086d2bb8a0c4371d5027fe843f843e91e7a288e2eae88cc5126c67b1ec67bde86f7fc4bec47687542eb23cc09656c0471b435e278a55d89e39d1147150e717416cecb84154bb362424e16724b426ed62ce92f21e7a459d25d42ae28e48c42ce16724a21770bb92b61173b859d17764161c7a459d25f6127d42de9aeb0039a2de92c849d4fb7a4f708b9a6d992ce23e4ac744bfa0a21b7a442c851e996749c90abcd96f414422e4ab7a4ef0839a2d9927e13724cba255d47c805cd96741442eea75bd24f08b9b05ee9967456586fb325a78f76f6d25269d7d84b6f67fde241148bc57070d22f9f2a7be9164abb8afae593a20101ca4bb7346b24ddd6da75d42e79f403a6976e6d36a95f1326ddb373a967ed5ab1ac59524a6f57b7c44b6fa6765d6957f704cbe424226a96749aa03bda9e9f52ba9dd958d968835e3adb9909218f8d2031966cb02437d2a80246829710f28035180f0f15f664c52e9f04f1c98ac57aab569ad52dbdf4e6a10367b162205ee22c346326edce4eda05a55f122699a6c212069a61650aeb2dac569a259d870fd77fb4b397d22d91745b24ddcee6d08d259b6605f149f768c055ec154dc59c2f9194ec80d24ec2461435e093365e3800132cd9c428b77dfec75fd0b66072616e835e38002d884882d9f06b827d62948b1a3279cebab4d455be5d76cfca33f014e10269e58182623218c429ae18457cc165d490c9a114c048208089f0113f2611e339141146e239ef5008609f3570961a384b92af5e032794d060365c8500c6853270c7b158bfe40e6c5cb50862c2434cc45cd52193ba22a038f1a3a4f2b12420d74a45a3020a8ac9aab812c27df52aba0d8f243c2613572db08b37465c765b4b13df2a9c33e1d25de187a525f0eb51e5caa327464f8c4ede67a3c675273894b823d398496b882b8f9c14fd74db9170c5d010576fc6b8f2e8c9cf931f4ac6a52e8f9c30fd7494d3e9d2579f4bd84c010e500b8bc0608ba9208c0cee8c339398a2cb932409b60ac39533ca013c045bcdd74a5c5167d88cc8c3b0199f0955f3fe5c9610112ef59199f053f99f0c282353652a088220f8e1f4c091ec24c504bf937ecd783b177d0ce555322ad50d99be4d5dab6ec8342d7d83f1ed174bcbc8c8c88c48b805d0813ce8f2290e521098dc7e7a60010ef206409401032d7c482376052440b36c49b4dab3ed0b79a8594ea8cbead2002528d78b4f717cf1396d0b659acca7bc59ea5c87b161c36128c501c625bb4aa57215fbe4c9939d3ffef863ff18649041b07265675a930c93cc4d46e6c68d1bb48e795185a34bb9f4238f3cf26ce947a6c18833340e6f836dd8105fc20ef0342ba88c3bb2cfc8414640ccdeb5783b07499d21f2f98756bc0294e95f480392197e9e77c86eca53620c237be74e85985ae2c4e10c8dc39fc2b17178b6f6de3943e3f01fe8331cad4f2c89a622987eb8713a2ce540535223c24645908483fbc01a82b9fb6ae0f9d3e9e421a97ff2503e3b69d65019d79fc98e1e6e0c566e0d5768d2d3840438b0810850b04609964820044c5c590ac20c8638818c01102fb5cb0a12bc8444d183444b55fcc84943cd44273369d7500ced5a19a1802d8956b372d957a184cd8412a66a1594fcb237b79892e28e94b2f5da619123d4bd3a5b9b163d8ba422740214d8a0a90a170c414889357c92d8210536d0e2b3d96ec0d8041288fc3c36e7b6ab7f54f211ea421595e611faa3b4b1ec9622a3fd51da8ab80043bb58cc91556c9ac51d8e2ccf47b72ea7ca7a4d5939cd92dec3e78b36dba7f747a72b604121a0a48c2742a0240c2b4c4029c20a7eb81d3103202b174805ec9f6490ad348b995c76f93d3f4ad740cf374761669e4bf366639f494c1dc43f658d160588090b17fcc84253d4c0e1471e3ac222e84786c2453f696c549bd987f4f38fea2cebe3a7cfd1a2e0833b32104399aaa31ee2c8d2d1b0e946e6f4186808228fae70f2f2e88a297e3a015cdec64f2fc064a6729374d919887f54ef17fb3011fc82716bfae9f4f23233238a279c2069413885397dc642eda23e819e82764e16a5cd1212df11e9eda18dc55e86aba719dd24dd768ef10f9a51bf90c823f6d3e9e5656646144f3881711807878611046ad7a438cc62db17b4564ad33db83c71980892564ea4e92aca6e8d9686a600a27422f24705640aedd3e58f34536a3fd250a680bc6977a571e7d1335f7b8511480419ad1124248b1a8e35ba19317172707ae4684113a245ed1a515952a3194929424890901a13c8195d9a9753b0b0c086e36cbf5e4ec1b245930d0375c30546b3d016c10a2c576c3da44e1b0151bde42b5b278e1512b9278feb6c273b9690fe4edeb4b3e0c4651fbb97ac5aa7d34bc34f9f20929fb47680290f6511f41af7c80a2b3f761063e986b822b4867cf16d3216a9c3de8cc6cf0edffedde66c46e3db3f21d3a588e38c4bbdb2d84143b8541c9174f755491b13e2556fbda3cc313ccff06c257731587e4fc480ea5994a173e698c5917d9c41f59e739e385a6bad373a11d58f453c1103aa1f63782acac0eef918c37be20c2ad0db31026d1f559e739c2855b26fd660430d32cb4b97547e48f7229ff048daf9670bf5bb45fb4cc327bb99c526d24f4d272c39dc4ee5cdf495d2ad3ed299ffe25620b073fe1b5503bfe735fc3e47ead4e93ee4ab8a215bd359ac30c4e1e27190bc996e0308b77dac5b481d1beed7f847f5e9758ad439ea17e873ce4ae5a7abe60b146bad265eefbcd66a35d5f472e6abdeeaadde6e95e9a74b7913ae8a9c722609cfdc1f9d5d7b3b38cb19246682aef41134c027bf94e79d3c3f799f943afc7521ac0b6d4fd44e0d9ce5bb29694b9de069a5740489af8a4cef812581754ec386d9d006fb5dd88921c33ad1c2aad864e5c284519f302af224814db1a74bd23dadc8a46be4d2d8b4e04e8761beacbd37c9936412eb8bc028b9238d7d0983bc19e2b2772f25772d630f0cecb7ab7cd2667cb1bde7dfc46996c79a2dcf69e8b3e57d2ba5dbf9c73368be6e609ecf99ece9a47d9c50a2bce73345dc2ad42e947ba07b3e99b4eb73cf49bb4eeef924923af23d8771e28ed25664bea464cf6998afe99eb5f7769e37ddc56fa07efd86e92ecece0569cfa5f30c92375e8bdf709dfa0d2e3e9da9bb70fdc5a9b7882eb8d8f0c9b6667954dee379e2c28bdb10795ec43b5b9e7b4f5c78e17902fa8bf8e4b4d42ccf41efc2fa12d5022b35cbf313ea25ea34839ae535d37b1ef37cca3c6f26cfbbc9732e4485e017b2986790d2055fde50c25c42099b45b3e5f90d574af7e4fcb3f67dd0f4dcda7b552a77d60c7a8f8be6db00c2ad2e6d3c518617dbc908a5db399c0fd96a02b4c762b12730ce83b4abfa91a4c2398c1397f3ca39ce7c4df738ce47d67339f286a3e2aa7309ebc42673a574fb2508b7a698be4a27372b9db3e76cc9296be9a49c19dc934e4b6b4f59294739dac9abea2aa54b7cd3b2a4b49bb3ab9474ce39e7b474ce39e7eccead7c714ec520b3259db356ce39e714294dfaf8f45e27b99f21ad9ccefb78b9bea3dd7e7aafa3dc77684f56367b4450caf381dc92a2f44ae6b88a83d3230785c3f3a1bc3450ea469ce3ea1057873c1411fa1e191ff7a495f3c09377929fbdac0a820fec644b6d49d1d4ad41e6cbba6c97afcb72d93838383d725038382a481665a1859fa16c51ef401411aac25305dce7fc9e5c5c503bd55fc091a6734927e75d930b13aa28a59e16358cdbbd3caaa20aa46b5f1e5521c507c5055f1e55c1021754c1e45e6bc10b230225282d665a0ca56e1543f8006251d452050f3f7cb5194a0a16252759b7e45b0f768a5bdc597b044eafcda1d6fab55d510349e79cf40b07874be26c1d5027fbcec9b7774cae7c940ecab777b376d574419dcfb777b11260c9c12d714735317e8cbeddc9b4cd9a2cd6b4e89b367d5326a2a1afb7bac4cdb820ce6756a3fab344850a4da66f9f55661137c4011581fbf9a661aa4a494c3ed6a3ef995af4ed7506060e558d7d5bb98561821b3e52255bbb646adf3e522861b4d3a076c18c1408cbb7cf26267c9c56be7d2eb5ebc567d2a47db1868b8f138ad20fdf36394f0091b67f40956f1a5af07d45b684856661dcf8919d7c3ba91d35956f4e62c2553e1fb079a046aa9ba8a85da836b24dd15498fa4a63910d7dfb387f7e851fe79535d93483292668da41e90627e802f62384ada93603201df45027c971027d8f0a9238030c28b418a3960556e334a89d1a9ac357a89d1c545a4e8ac59ec667503b34145c5159395ddd72f8d5ea46c4afc270c1af1c076a67e51ed1cfb80ab53383a3e971f80dd40e0e95cba0765437a8f81b1e83dab951d36882c9c8380c6a47e68a1f0306101fe336503b313030187f41edc05c2cdec61750bc0d7741edd8b02f7e513b2f38dd72b1810b172e2e24bc8b8b5f1f9cf12eaeaa82f42eee5f08bd8b4b19bc8ba7503b2eac1d4bee28b1b89706f706e002f9b1c75f974afe3a6d978fbf77c98abf5e04fdf542087f8f7ac082bf3ec0e1afb7a0766e4eca51a89d548bcb167c8b83a89d16947fa81d9405611e1c691e74daae9b07c11dace04157f140870775508407fd84da019dfee7a753167fda39dd9a2a66ef7987daf128b53a58f256cadb5b513b9d52d053cf01197cc4cf49bb2bfa1aaea181e7e0a870708a7c98b9740926a2c4a04e72c78f344f9d52b74e3cf59b832970a0b443fd061183303f7ea4b1d1d123a9881c4944375072031c90e0dbbf9c2c35b51f75d020043938a2c1951a1071540320d860ca370e13321c0c148bc57ca09452974775f0230ccd833fdae8c081ac41007e944634198cf1edaa2eb2f8769fc1ecdb593370f2ed3954a828c940055dbb4209941a6c01c60e52469083e88927b448220d2564a8e405a3a8e9f610e28e2e039f123a5577510f74f2093fc23ce5f194fac581ad491a36e0020a8dad553268dc91ba0d4c0c90beb9e042e85b65801b1223b035894959e28812475c2102880ba618ac5994ad700289ee6ea9d3473180c1534b290b822ae59fe04a070a476a7cbbcaf6ed8e4486eddb250e4c199ffcc0656f95ac4c8e6adf055042a68410d7780bf812a89f1ae9ef10e6de7e9e1c63c078927031d8f5242ac09ea09c874e61410d6eb02430473b803d697724dcd7d8388e6327a1f5c47efa749e47f94c350a154a9844a18c748b7da9fe6783194fc162413b81548b73ff004243b3d8a768648618422c965a88d641940ab0a4a221800b682a20fc43023205f609c81be66eefa779760ab48b3d15caa316287d0ced72ff8fc1499baf6062662766283133731b69177b100a4cc19ad387cc9f2821fc3d9a85d32c66d903a40e09cfce321fd83bf9178487903f1ec00c45ea8c5cc4b784ec2806e21f19c7e40dbf78493f807f300eec44263113697b1de40dff27a4ca1ba681bd03ec4662105f706932694091f2c60647e56a77d7e6222662286c9b2d9ac553a649b642beacbff094a7e2a4cd1675eacddd9304a0a269ebc9362baa95052795698bf2e3ac629999278d326a487d8e226fa88b34b8cd3463b59acd96943459dce2b2e3a03e6df3b500d9a24e5b9c494ffdabab5adb8688526aa5a9e929a594ce504abdf69c3419d0a4b56b751f867d28c79ef2929b44ab519f73ce22f3e5943abb8a663281617c3c69ed2c75aea2fcba962cee031c5318538ec254aa2954f7ba4c8562cb09bf420250e10b38234640b6aa9f4e44ee1379f9596154e4e24ee77ae58fd09f35512e6a67825fedd0f824c78923128ee3388eab5cd36e66912709ac26e16e223a9de9d2b6fc90a2b5f7aa544e6752ae3caf63ea5738b33e16c80a11c18893592656f6bce439e899879eddab9393b56bf49a80da15d4aef1e4f33edccf13324ee899e98ad8c3aded9adf577d7c0ee83374819e9d135667b2b17bd1d86c55f983fe48396d36896f93c97ff8870e8eb5f8f38cd33af84777a28ee79367c38ea98f54918b3b764c74a85b4c4b204f206f78564104a00e4bbf28d06ca16186fc41853805a6765de99a96dab542397f3d859e9f1c155e3a9b2d8e8ab752e95757a444ab52b362bbf2dc25b17b430f86de9257a559ddd26c7167a58a9c387312472bebc2aea9bb226f3a2cddb2a2d75979066a21fec1f9700aecdc5c399f672a94fa19a6beb95addd66abdbbd231713e2d22162bfc638a9d193205761f34e40dfbc8f120764c5b5ccfc7934fd3b39f7cda253bfd784dedc2d2ae239d23b1a718d31c4172555812f3420e089902bb0d39a090fbe19670419c50b338213859b3788526aeb3db9895754c5dd3e9c6111ffb578015d092246576427465b674615e702990460c3e8c481d1b462420a518529490f1fc35536e170145f1526a944ab79416202bdda464eb66a0a96a6aa2b3900ed16894679a486794ce26ed59088e9f744667740929e9ac9d0e499d2e92375152229dd15913a944462d40a4820757e6f43175ab461c6ac8e7f3a5381ef93ec2cf40f3957220e940b439345f29d148f751ea471e62252c4052cc39a7d4e9170f4d289ec643f3454bb0042582e2d91b2504c7b74867f2865dac420da59f6217358b3de512f50285a53a8cf98e42722d5207e5ec7dd42f3ab32707dd480a355f1d4557514d254a44e5d93b514a299f28e58eb5d6aea37655a319561acc4b548b358a91f9eaa2ae89b46b4402ec6c44eaac7458b9239de1f8fbcd4db17b552a77d6d746c8a533a018b9de84e35b6ca29a2ceec8b416e794b3b352bffaa85bf39512bb688aa34a1c6f0f1d3a88ae55dd1651a4e28e4094465035fab1a32c1d9d51063731db19bb317879840653d14c1ea971f4dcdf519ebdbb66cae5b8a276a4caa5013f444d4c673ce3389e4c9a25b53083e965f8f2144544fa48a51667e4f032accf280448d690cdcba334869e51280548ef161fd02cf61a86f4b713a1dfedb24318e40d3ba594555087508536ad543450544444fd92621419b54bcaa3294bf8979e6fcf2e67cfda3532ed87dac52b960ecaa48be2479045ab111535cd574bc320755a5c09e1dc864712dec55db5f3113ff2113f7d9512795868066b11613d709a4544ab1109cd60ec41dac5473c6f01e5a5adda7808e1b3fda29667d8e36d288fa630f9ea759ded746a5eeac09158c0d0220a18e860c8033a4aa45d77332a4c428fb8c1500e86497298c104067a906631019ac50ee40342443f4cc1848a2647c098032418ea410553a668620918337b8d15770442009c2646fb643a183ee9fcb361ce27830148ce1066418058b1484d15362c1dedea2923826b5d2689eba9429a5311a68f4058456e9e73115dd18fe07b27ffc2cebd93b86a61bee743e67beed95015bbd3e57f4fbc3c3a22fafed17f0402839132849e9f1d880bcc323edce9591fd26f1130550790a8163ca2b722b30e34ae8cd175184a781b4a1a17b4b7e298e4adcf39e79c6ebb2566e28bef4456b3d88688570c16da4bc2d8e9f41b87a1c50a07d2acae11bae395f5b832a9c38ec330489dfa752584eb6fa78053b73a42989b8491183e697bf1132218e4cf6fe71142510f1f7a74f4236df226afc8a6d8947c34ea18af6ca5a269560e1b89a1080c0768d7ca6d8cc4500406a903a793c1d24b9731ba3712dcc6a57f9452fe611342acde40696b2fadeda54d7ae9947b1231c588dab5b2d58a84e247f02b163b3323a275912b4abb5ab44c8824936649918b7b032218dc6024780fec7a4d9a2de9d683f48fb0d650f2e4724279d0bfa176d12174c60677fa0ca687cbceb59670ac4b2f1d158ef5080d8fd01f12db89d84335585c96f7248e57e589edb2b24a1366c8beaf41464d917dc86835188d0f301bef819970040e110efde1b2ebab678e98a0b46727131b4f76d168eda241586242e5a5cbd1b2357ae9d966d2ae713a79e97205c303ba226452d36f1f42bf390b138cb846664bba0d1177d5fec37b60ab16570ed0711fee428eff10799eb4e7788b2ee8b80ff721f2e8780f6cc987fff0b163b37691f0d2adec588c65612cca04ddd96c49b7012cf3b28a27641006a770a04073c987443501aa8904a1a06812c98322098a2fb80a28cce8bc18cff8803a01b5445c5469f60aa48e0a97032fc6281282b44ae40b114ca893289c60298c5050a0381dbdd480edad638a020d49636c7da86cd992ce895554c5bc94c10c963c8be3fddbcb2aa430e247ff8e5508820090621004e3b1518c8660497c28018410409d1558b104d3220b1741302e8a74b8c1b0c4e08c2558873850609ddcc1113cb8c13aa4f121059f942e4a67ff6879849f4ae9b3a5944e45ca3c7bb096ebb84ae7b4e14cdb4958e79d8475a15fd6b7e7d65ff06ce793569f4ece16e75205f74a67c7b36b77f7cea50e4efde193364faa4bffbe1a3ee9bc864788d0efba9025ab82a0fe3c72f2ea1f47c4fbc9b2c579e51ab5b35a00e89fbfe02f9c3e8c5b836cf25cdebe07d6036bb7a7d0e7e437f4f95cced603e1d6209b1ec9942a74f30b25ec14da70a25ae0f1b6a59413d5e28936f4a92dc771734ecb71f3642dd755af539e4c7fc23ed932e57c56e7421f4e85f62961954e2adad033cb9c9653686f94b5764e3b7520ca233e395f4fe84743fe279cd3f0c8c78544ead3d005962d8f03deb7c3207b5243cecb8064fe74d6f7c0ac531491cec591abdbd0c73a0f4f6ac879245305aea74349c331c9886ac146d79629dad0dff9cc92a5a3b47b427b9a0e4169486560a73f82dd0c7d4e521b5a03f3d95be8ef5644e6773ebf73ce2702384e61649681bdabe2103fc24f19cb5319aab338d66f7188d409d168018bf4ebe9f38f58a9431b011ca7d0f4a3201fe11f394eead01ff29b2915abbc69afb55d934e2ab264d5dace36b44ca136536a4353dad350502a44459f22c29a2805f0c8fa3324422d8b473ac911a1dff927398a2ba9100dfdf41b26aa85ea53a4cca184f44cef50be9ded9c28a902aa4f47c0c81d4f1c6495a75fadb82a18fed1b19f16f54501267d40bf80746b3aab5b4c439c664d2041d806a72888055924e1822eaea896b8a3cd4ff9853c4de36c51851faa28510ce8207386297b18fec1ff5f7cd2e653b5cbb905e1b35f15123fb6162da039fa9635544f69c3a838ce30653f6b70e5d7265205202ebf2c6c2eb1a045c62465cfa21cef8bf3e0b0272e3ee27c4c4ce7323eaa3ec63f86f5313a3e06c6677cccf918496b8939fa989898182b8fe3468c4bfe91f218717459f94bd864756bb2729aa6679f59c1303d4b988d2acffe727b76172b77291544d1b3b7c49e1de5f3ec60d0b37f42cf7e9a3dbbc764e8d971c884b8e1356c526b72c355611049cf2ec39ded654209e36a4741d09ebd659fa4eb89692e069fe41ffc83e573884482d823616e011c4ad8a40e1b22ebaa487b0f2c090cf64db0869c68e50deca373d2d9c28956c2a8d864fa84cd9e49ba1b053c38725082192c092705f183046c7a873cdd0e60496056009b229308b034c1b62841900cc6c3570c61060c6049629ec064b33082ecc410c6a0c13a5c553182713a1bb290048c474a51c389125892ef0651c06cb3f06ebd1379748e502308968453c304b04e0a11866ab0245d0dce8075a26c167a602b172c0b49c0ac68c5d50b9d7bded9d50bb6734fb49ea46259c35efb206a87a3576e15693a950d67175a912767c91342b0249d14b0ce7b60cd6a16a67b9d22d8839b8e2b450ff35dbb82f1c92124256cede291f5c1a884516e211f8c0be56c75e0dbb977a60c48e6afa648a45d039f0c8d647e8b2b22dea20df36590cd923839b776b2a351a96c5bcf9eacb5765a18ef5e0fa4e049ea506fce3b799ee78122b537fd4f4fa75927451199222dc1c9632b032a69542aae61eef591ce7f9dbcbbd5e0b4943a379f7e953ad2ab92ce9652b2d7c3c82fdbf648fabd6ffbf46f7a336cf68f63b17fb66493171ac6a29cadf6e25b29f1ee6e969205194813a8eeb4b32574db3b1858df8939dd6acfc9c949e24497d09c3ddb324d27509245106c50de345869f8a53ccff321b2869d0c4723d6b9e93535f9258a6df0cd2ad39bdef4e6c9e769ce53cf13cac7fba7132af47a69fae9743a8d343fbd0045fa4503e87382e275024c7082d46bade2e7ec39ed3cb7968a2050bd22e8819eaa747047d5d34061980e8a2390a43bde977d7bd0de1bf652bf823aa791edc2abe5255bc44b0095f4500690cf89e07b3d3bd07bde32178e3e72de8527ff3c48f7fca96ddf7990693b09d4c52673489ef32f65802f253df7fc74623eb1683d4983e63c9d4e3e4486dc29b461e7941ec12950e7c2b1959e36aa793e2133fa1194e2adac05cf7e816fd29e7378cb56056121d0c9cb88d2284789a588ac49dae3348e9e69c931e268659e6d88a3eaf9451cfdd9451c59cf37258e393d6be19a7655cefa5c8a4b333667f3e5a2ad228d18fa4a1ced8c385ec7218aae0a672eaaa4f352cfa4df10472b8e37461c55d261c4d16d8823eb451c715caef494385bc4594dd834fe79e9a038e6f88c38585857dc8aa3aa6b5a4d75e5cc1172d36c49544f1a4d83b3a08ff3393b9fdcaa76d1cc4e1ce90ccdb5767d4a89007183643606ca745ba2485de55cb670584d0401c9624544433c4bb2d568f2559d631a2eacc9d9d3cd459f7cd938fcf41936c3be992b57baecbe507655cddd1305a203d4826c966a30f831e7698badca8fd2a8369354a452d3b02c70e551164a3f9d8a2312fa7532285a2bf3823bead061d3604c135a66edbd23d39666cc331396507fbbb345fed1849f53ca9453024d2a9fb481d1e2b6ab6fca7e608b901180b1f47dc3afa82153861863e9142269b0599ea7c2d15b2cdf68419a6f53769bcd21293e889283a774064e868a8882868664440c54d4358e0d2db58c16d404c4746b4a62baddf85603b205258d3da3599beac4b75bbbdaaf53e71b4a295750f9c254d8e21f056f88029da2860c6104607871274a8810037043d8a10a18dffe41662a1c99360486a30c4f2192f644292b118a3f293eb6c22806394c390a41d87405165c6451cb814970fbe5d11a3e9c452d070120c57e62b12c66bc952299dd91b403075618c54009e88ef46733c126f8388b5a0e3b6e00341365b22865a1f2f2288bd18ff4b3c0e0b99747598a5e8b2dba60fe2173f2124926241b42f6d34396670727f07113179481aa0633f0c9ea02360be63b8232460bd2cf0fd20f6820fde0031b4cf9810aa41fa020fdc004e90724907ef0415272e5680d19d2cfede5142f925efef4e0655e22fd1cc5b8b86c6b6a77c539fbc8ec3c7e82a6d3e4cc17ab5b2b3431e784992c9bf3e7688da0771962bc2f7b90bd44e2a1e9a5df78c8c173043e6953b9ce7aa70f44b5a4ae0b3b8cc388e38bb3b82202e3ec2c8e361cc6639c07f462c6884fda25b530a14b78433642b19f3cdb711cc5a5271adf8a572c828e12794011e529e7f944d05bbc07f6f9f51e9864b2b33583e5d2349a6012f664ecf1956b0969e0d8fc5afc890ba827ab057c0efa93aefbc41cd6e7a3ea1347d6d7451040a0c560125680d9a2ae23bb9daf3c97309e2772b64ee2ca138bf8d0305b54882c5960a0c8f3891c9b2d1d145c0993556012d6a3c407b5a51a173315962431e7035315ea2ca34e03f50e28c142d281322ecf382bbeafe605352f98f4c5d59bb2b325bd86d37954fd9c5f3384edeed6690780bc912c6f6c144b1cc74d8e72955bd55afb25cf49a728847fcef95f0d226fda69a5d5b39fe385416ccfeab35269d9aa58f3053a4e90db5f18e45a00481df6d5db7839654b1d2a049574f9e5b36f07710bebc4d172a2e46ea16290ae05fa53e4a48e1764896bd4093a5bab156557abcfc9d54a75d8d89871696c542450b528c005c4515bd44f2175e1d93329d8ac5b73c4ad36055ec775371a3f68d2831fccec123f08c2c1f98192205a5a6aad38e99684d22d6b254ab7e48f770c2b44b059dc96974756c8e83a8f03c28a0e829059e9e287242b4641dcac3c116465760491151f9010e2031b19690821048c2232c210e2c58d8c192c899131c5114464b04008213da8916144088454591a238d248ac6581281902f646324755d87746dbc3c12630d31d0f0acdc1c2f8fc4c04197c65dbd3c12838acf8b9b7a792406918e9753ac8cc1716af6491b9ca9f2a99ab2ade470323333b3b5ac21b822d53f0b82ffc56526aed5a60da90aa894add4954d2c15a21101000000009314002028140e878442a1502c1e0983a67b14000d849e487e56190b942488510a194300018000200400460006464c1300888517a3e66db21ec41b12d1b522b31ccc48973e666ae4411df104f1540d4a45ddd62aadaaad099a605449556d95a32af669d2512d70a04ac7de8ecd48275668daf4ea404931aab268587520bed8cbba162b292f95ad772c633d495951b4b54755ecc91cacb84f938e6a08a76a8aa76a532ecab610b5c140946bb75273d24b7c231d18e5647e94c281aae228798fa8642d187628a23e2ddfaa698f1436b6ca4d4ae9d88c7b2ab9011c9ab7c746d2842165a564eb0ecb784f88836a9a9252a9523bae514fe0b08c7e823e60a605cb0d723304880e2f1aecfb409402b8763d793349f5187ddbf390151054b17f57a647baf31003f89e9720bcc8a162fcc53ca44c0bb8b050fccc9dc0ef666fe11ed00f2a051200deb32994f73bbe9455380dad85af97d3e09f00fbd6aee9e3d0ea0848684c99ab71a7598513f395dfbe2f73b288d0362f4855579eb965e710b86cb744be0a1e88d631de9c78d92521907c2739bc3cc9b0d197707e2cdd6c68953579590a816825230ad587f7158243e14db61d4ff132cac32476e734b78621c6862dbf8b0c2262ab718766c4e23ec9f1293c520069d9576226bb92b24500c3e8ac6582b445caa26139df6998286d102c2a7264357dc5df9130c3d0d982780b2cb89de192be91c5df18c65bd39d0e6c8af160fb000eb2e7371289cf161f864ea8977037058063acbfa2f624f641ac7cd5e599203660a449a3d0358fc21526a768a31402bb384dc51090ccdfc77db5bac80902d68a7d8f05414c207a027d5b1ff0dcd4a57e4550c3617c9996b5639a3ab414ea0c0910054a6e6373a7b77afb9745921dcd784790401ff8c841be48a58b136bb2a41c6956b40a258b6628cd8c30bc701ed45baf74267e7e515ca5d67b28e7604d147da84b7dafe3976f06065f98b242cc6a29a53836b4e763753b6783269ffbe9dcbee847b36a009b02a676990dcf93876a4c99cfebba9f6c71598810f1a648cbc483c48c442dc4d6427b8c6d112a831a9ac1bb90f1d66f2a842d8f0996aa24798af4318fbe35857d39b1694bc65662d828a23f29e9b40e1e67d48b869f5082267542cb55b014c7786894cca042f9600b5d9d8fe5a7ddf9b89abd72d480aecef6fb9d19e9cbe2f7adffd261e9143cf600f137117bccefef1f8545f28d19130f01a056cbee1428b3bf86b82412a8e0a50d339fa302ad8c846df9a3910ae21e9ed46ddb20242f060e23257c355d860dfa926007d187da0647f65a7b446016344203c352b20848dd165711aeabc78470c8bc3874d56912b001cf6d166271237768ce49f59d5a1394069388cb191d2d1f6cead1d9e8e49c951aaa5e68eab198d19970ce5dde9036a80102dc7f1226735bba63cee01a1acd84099c87b58d19fc04d9e98de96af2b5f405b82f2b4e49fc9b05351e9c72fa95e6ff6d45d7bf0a3e582ff60aa4a25709c8ebca78b760a21c28ce72192bc3a26461a52d1f112de3ead163fca7a1b8eae4b8062456b40f32cb7fdbcbc9300fd80746a4ac30e39f3f57fa2d7318195e5bec37daf2f1cb8c9a3036fd5183766b231f4103c2b0efa20ed0b8f7e91687982ca4fffddadc2abc5a012897673f229a4d2fe3635df3b9930dc81791d79a6353481619f245e0c5d87fcc19d6b49b56ca65d9c3f793554bc7e3d39aaba8baf708fc7083cb74be4a1feaa74ed14fe604d3d975dd9b8528f892b3514798e54a0d15878c4a0a4f98840ca649131a77c415976232d0ed6939fd342dddb83ce940467d32d1bae117f9d032371adaaf5fe3136ad41de9a7ef69c4cf40839e961a9a1c20b7ec80bb7265bef11bbb4246d725c1ea27d4ab9e6cc2ccf8219d2a380bd7205808393f1ecde0b7b69e4531e91c3ac74871d236a45b658fb942ea6bcc2b144b46a62c8c04cf07fa16595695ef997c314ad8165539f00235930fd4eac972e8d9111f65d96654b649a4ea9d3a80cc8ccac07447339e85d0368b8239020939361b67dbaa8b8b8945a0d8300d8dec0848c5277593e09ba733638e16dd8e9fe1c648dd980a323788895b6358091929e45fa6b4a091b27dfe98639dd201308ded299ff83cacc45ca32c2f40fb0864df653867e730f6913901edb30d979228885eaa174fad6f6ae785993606acf7008d5e9840e2b63fdeed90b6bc6244f206c3bd5b8dc6a219616a23ba5ba86bfb96b5f92687c6404add9ed413885cedba61a1c2292e6397ba582ea393d88b42449c3092dacf7293180a8d8cdc35619e45f18741d8bd40d76e215417e24f532b6c7517ef2cb944d2c297836a4bedf5c7297254207dd560aafc07486a33d864941da2a228462b05efe0251da0439ba712a3cbdb91f217213dc19f461f04ed2843ea41f0ea7f6e41688eeb528343f1ace2db11d094dff3e08861b22ca8b88843d79af0f9870e6230727d92a451546fd97599166ca560b77a0e8c0c7d05a1de3004d8053770b9efc3904ab606005e07610f7d0c3f27343f4b71baa7a6b860592515ee85d7ea262960d0ec568890a0ca4094f54ced73ea73386475d2dfab8c60d37d257c54505ab3c89b0068283eae1b2248c401684b2fb931cec5ad90528754c1d51079d1538954b9128c56490a35ff7d3094ad88c0a0d1eed21f25e911740baefe18ee0f245a185aabff10d5a39ca49cf91bbad27dc4f054204f4ad8cb87de799fef1553f997456670172f2c2b1ec72092509b930b91c3b5097c75ef44851831f434a4ca6d0c10b48fc178282ee30faa5e765b13e073cf191f9542909433401365c6ec3ef996b2749fb7bac5a61a0828d15f020e22f266b860e015630ce0000c0448ec83b756135c305b81901fa9d64eb16ac927b3af19d3a554fe4e0ddbc862e40ff739b8a44ddcb6cd3b2744ad2572bbdd5e5d6987281ea64bbc0b1334500af3e6037880e7ceeccd6267d1206394314d224e4290b3eeafbf2fad7c578e827b72d9be48743c2ddb72b33ea2eb686ea369200b57d84ad53369098de484e3b5a6ca18d6d60ab7d8b97077c45728d816c211bdb1936ed19bb8b96bbfedb9670c9fc6902c026dc61790a280b7d145134f30ad90be2129a9e365f6636358d7793ca1f0105a3f136920adb9fbc6fd09a95795d19a7b083aabeacbd5812ea5a98baf110a90fc94425ecacbe4ffe18dcc2f044fb01bd02feb8036e52910e0fbfe5fc82bcbd72babd90284936b4687b2aca12dcb8a02bfbcc2a24b056c5ab8c65b4f3103dd85c2b7c89ba53e13d713e832099b5e8dc78dea6fdbd57508ef8d32874fb7b4848d6e83ad496159e33143bfd09df877b48cc8e97e389760f3d53e50b74b1004f18a190b691186351c1cdfcb40568e1f0ad05671e9fbb632761cff1a3df281d5b057fd822de56a80abf6c9b4ab2fc2d8600a9a5fc7f7e406581f13fe401e14eb4f80e6086dd703d62ad68bd3b41603acf3beaee98f2f7de55972a65d5be1dce18aa53b04b7d2a0a7c0e8bacc5d68be11955d12e6c1a4123706b370fa8ff659192ad7c817253034b99db8ac123129e17f5ff8af652b7e180ee53a532cab5d6547f84c928c2efc8c16afe9a10b3605de247603f81a06486e21e359fc29b8179f6243a237c42f1ba8d6c3ccbf1fbdd92c0db4a065e639b5fdeb19ae56ed0fe66ba7b4900b97336a767936a8d880f6366cd3645bd3e572bfeb2c861ec4b1c94fde4da41888be0e3e17b70c4c0ddf50298c4744bc9fb0f2de4091c060e1018501c4470afb7a027a6c96012d9c2da20a989fe9839e7b1e831711d00b4bdbd5fedac2239bba70d6027d66a1fc264a14228ef4e3646093434aa91e1b812f0e492b195d8c410d428bc81db328cc1d8f971e2838406dc33cf9aed5a03652cd66284728b66dfc5c0cf6c5e59d660d00228fc1fc5c6ecd4d0396540ca0f0cf4ec57f44554274b89873418485d01a1cbe605de1039d28d01caffc43c575a5d556ab5143fce22d1b526a272800daeed621df2fe76725b73eb8c5c3135b045526bf84cab024fc8bffccdd51193b5bf0dd0a1fdeb1f9df53e7204d7c72abfb44079fd0d8bda0b2d3fea91f41939fdf19f0ab15d3cd416c0c7d756a68ab01afeb5b0bb7840ea4aabaec99aabc444bab0a65750676229a19bba05188accbbce6923ce372d421909902a7542339354aa8fdd104a880b2a60963460aa6771e9f4931fb6aa128e9e295de8aecbf0f359d0d5dba0790e377cac700236299ee396bef6b280a95dc2378842b099c180834237b823ae50eed35eeffb52f206a50ce9ebd62478c72c7e904b7e1a6123991c1c4d9cc07b580924774e958768767bb131cc02827bca6ae60d1d578d6d4c9c5d27b7cc3c8aa0190de5939d145b82e6fdb65a013494d6b5da9ce3af4c15932a141623ab7cb1a6ce846d852730d8b307bb3d9116c22df16d3006345880d8d249d823dae98aae5c20fda7f19830510163c18a30ef56c5f5101fb9f097022004024e624f598b50e48b500dc09484918cc44dc21f84e367d96d0d87632edf7490c221680cbff0db74f1283fed42cca94a8375d940c227261085429aff708a04b59247095fe91ded7ad5b601ead6cf3c151e7b96bbeddeed96e1be4b114dd4a1b5324c35b940ca267992d1aa8b2fadefa714e4f5f2d5b28227d8bc10215e60d59dc8e5ade6f2b1ddfe5f5609c7684f979c6aaca2ed1cbf4f01ec29cd3600509c97ebf5269ba05c6404188cb3e07deb9cbe574ab782a9201acf306dd68463d2fcc6aa2758d72ba9c29d39845b9ddb4569c4b804b2a0881a7947b6a0adcf425ce244ab08e41d53a9ed47f68ff61e806c0391fd35d15872f0574fef71030f40c2b312c6161f451a1420a11e42d8433906a4da6a02356c75de7e6296095c519cf317b89b99bdaf99d734971e12e18725c9266cab5c86fd05be0954aa8a38d247d03161954e31f81d645b85a9e94f527d34c43b0665e7bbe9f5cf2173a81f2d0628e429f080a96c9210a6700ec548aa9f4547c31f59bec4aa30afa4decbb856ad1839a29440f67809fa2c1160f0a780b37ee17144ebfb2fc5fd5cfb5a0e64e90551f5a7036b8426872d34d089c521ff84c7284276ee01b1dc0d4f306c2c65fe020c11af1a67a372828c98d375cca15c2373480aa59288f11c099816a1c8b94738370982c6c47a6ef30a2f1179fe0369a48c35c227d97b12b61225e74396a4945c60cf9f55bf241d1c0d4ae965e0974bf68c0044634ec220cd79e0fc1a8052ee00a0361f3babcbfef0b420a15d408d9606a8bcd37adb6db1742ff0cdcbf09441b8aff167a9c0570211ad7a6001f12fe8aba55772bb89962c0403cea22ca928a0f3253a45ae78160854d59df5b4d35cad9ffe6ef1894040f6a1900b583a9e82afcc34738c70349bba2ac2372ac61e0f7574c947548c7c39cdc6818d03badf6cc7bc961766eaef1addba5b0f360892166c9787d3c453f0f22549e35d96a5572a0baf046fa31229f0acbae46f5b27bc60157fc889f5fecb13a6a36e258af987ff01651b5bc245b76bd3800dc7fc5af21dbd1e8034651edeee084d15158cff786f715153c0d04a5c3f2b291254c1d40254557742f06bc3ebe57edb90d1c34d5e238580c55521e7ef7775b7b90c15dcb741b30d40c519e10583cc7aab1415ab10b11842e0b0f5cea1dae18ec130a144198f1f333f652e5b7d1843f74d469ecea158f972dd7bea4265ff1eedb57b43cf42b166f6171293a2c350d8bd684ceee1d36540c441cb00fdf606086667460e00b00c01f7a5ab332f0e1d0fc0e2e48d509dafa3995b6f5f39d95ad9f05b06ffd5c89cd656cf7bcd2f2db65523aa8dced179f938d6fbee0c57b1c77936805032db863231ea62261e1b3280866ba8db3fe16632ce783a86788cb96eaaefcc35c5dee4b25886732f839876990700c279ce0384b7385bfa287187a8b572c90290a8083e12d741738b0baa42e0b05eea64d1a7fbb88f58497179bab9c2b034500a7bd2c03fdd0cb8a57141b3339fafee71a32c21c4d2b1c4e72fbcb9c0bf9fdcc6f4ffd55264b717f26c0206478a1e99f2be997c9e5058e6097d89152d4a0552725d941cbfc9b1d6804287de0069a2e603f624bf18ab24c332324754d0dc71e4ab0c36d8f5c884d060fb79b4050952525e8160347c43008da15812975ac4108f4a72c6f0bd109aadd5071541230e4515584e40fb0ada325106bdae71e97fbd1161badedc6c6251b720503ff8bd0469fdc00a2c1570336becb4e762205f40c040c77d894fa83ac9bcd7ab292cf75ab08ad7223baf2ee69069d06c00cd932d7a43d63eab42ccdb58517913de44991c5258a9ee6f6af360087d521d62f4fb7a2b2ed7081c04b538c86153a3a5e56b711fd7f223bd12c1b34f845828d6f83957706d61314765faf1e6bc891b22483cf74def2281ca4fdd442d04722e721d3ca3c967bbd5b351ec7c837c9ea29323dc208a127d5180db016623fca41801cd8a25363abe954b2ff9d3a9261f23a664acfcfb60ac1e57fd1f59702c0c0b797fa6ed6d4742b245cc2c52924432b42f6960caeb74da1b5494e6def334fbead4dcd2ea996bd0c3896fa39d4e29c7cb9431058c337cb6ea72cb63963edc14e05f275f61dad01e31d79175efb82e9f75e03814013adf85ea508b03b41663e41604f57890732a63f487fa26648a5015b97e6606628aa17467f4c487c75357ee39dc423bf5d1d373217c590832c900cd250f2f89e15390e391813ad124789acba20f938f29938cb061f9c1eaa665cdae754d4cb7d049d0367e62291b0635f41451d7564140fb550b5fdfdcac4ca576f6892984c61b7ef47d0b81c07f2c96ce02d574de790919834f332a981cf3efed305652c6fb37066b2fb24bef9d8393d4b7a5d87a9b1d15090c587319dfb5d736b5665c60e389c1c57da02c0682f599bf67734eb27cea41ec22c2a6a7f1ca9ee0a21d201688b3b8bfd4144393ca866ea5260a8d5f43f759a5b9e6063218b0bf287e1a6ddf92213fce3264e25e87913a18a0b302c5c9f98b26a8944b4578d24942592d60d56a784a70d29868bf98fd46c07c3020f8372128372fde49483af402c1ccdf1de3643ead44661915171ea340602a1206976a2402c70fd930467110815274899215ea54081d368b884ccfdcb7dc09f0241fa7206c5eed39f067789ddd88a9044bf0d8956268c2558bec64cd60544ac46016a8c337a1de4239f9b0dc322a4c7d16333dca6774efe96549b01844230908f7f2dda7b8eb2980fd5cc10f00ee1227442a6b39193d2ffa906459b8a7c31b8aa546e307b15261308ac7ee46b545b42f632507519d4216eaad7c76c9c2cb47b55a9266d12c184d6311128c7f9812ee3420f07d86f57d50fa2b50d918030afcd9a1092c0faa6d89f63e1341c2c60cd553806be9c9929ff436ecf00365a9e46e9546afc375b58bdd34130c4d8fd7bfea6b2ed17be4e9d9209c5bbb2451e80153551edfb118247477833c18b821195962d883a76a737a0a292514bd786af41a17f6c4a35b0008c81999fb9f19a7c934686fbab7a432fc5c54474d783f06b1235cd4e24a8f81b707807d117867dd638dbf816809afb9b02890b5bc202d0d1a900c4c2633b30caa7e2e673dce091e0819358b4e0194903187984bb379d7bc0083eb6673b1caa99470a1120c146579304e7d17822ac84aeda8a17f46bfb950a1e713c39c056f201113bd8ac3ef0ac0f04cb119f8206b0e18ee5ed7daf2bfffffc0375aca9d29dd781e3e314eafe3fe4af0324f3c1e9377204e83949eefe2dd790c8304b8617066a08b84bcb703611412e9e828e32034ed593471411e25eae904a70f0857a16a8ed8be04320354b11aed154fa96bf4178e9ba1bb00110dafa27a6820df1351a4110e001e6d624334447e00527461ee56d80260517d1ba67fdfb83345a0b3f83e343a69a0b4b459a9a89ae65fa73670a6d21b0c9240440b587794b9ee8f146f2ae299aa8a81b4cdb61e102e4dda834f18880eea3fd1958010a027a7367d99d5b8978da9f1f93d400be2453998e02d78bd11d284fe35e78fede45bf95263c59706bc12c1d8acdde9f5ae8e537b3e550e74c1986b0607437e33c58308adf98452f19936088241b2da1f414ace17f502eda427b63e526f76c95d8652948416c900cb637d14b8f0fa34216576bbda3a5914b7178c8db41cbbfc78f14e7fbc24466fffbeb1dd082706034ccd5a764722d3188fa390f21f9a036e8a33a18d70ff89df4ae92a1546633a41095706be76ecbb14a245542b9569fd311f6b28e28da10fe586d984769c2aa6241ae4d1e108c8d9e51d611b636704995e833ac14b0508af0ad0e04e3ba3b42e13aed6ce968a066d991b212cd43295620becb94849e425bf451fd0d042a72dd1b3999292c65d166f48c5246250e5253a9cf4d471d968e6c356b0e7a3a4986ecfd99cab3eb9702a37b8d4ebdcb98e96a48270987cffae022b2cbc15ce8e4806382d979288df70227399a4b75e3867bc065414575a801c04c53815a1c8de1aa5848ae3a11a35d2f1e59c0597a72ccf7426779b4b13a9c60a715e092420f12c0cf153a0dd38d059cc6782aee46010229cc75cbb42f39111498d56f55a343e425971de9f0be4151c935541f29238a6e45af38a2580e76e30d6a2af33191b1e6e0314dd78f1d64da036860e0f7b25871d041afa9442195bfa0118b374db946865814b46ea2805dc5585587e0008641a351ad5c2be528827ca242f07e70b4314d206f68b8cb22e5c9cdba8a6d945e9537d9ddbaad811608dcc4c079050a4a243402255e4a8cb93cedce066634211086d3f0891eb1e5c92cc0a3109b4b7881dc1178fd484b7611be78d71fb38dcfb60bb46fa8858e6501299c5af399d595dfa9b8f451b52797bb23dbfa01b5f89c3c2f594fa6a8333a8ef31303baf77e7cdfb147f3f35b371e8e31fcb9a944c793908e4aa428bb52f636d72ecb6cec3e308a829215d427ae9678529688042fb619411a86d9b9852d1abec15363b533f2f3f15e9e67a12a06dd1a3f779ddab6167064834edb20b14b152dd050494ac17349377c5fe96f6358752f24652673265b009be0dc059635195834bdf412e102fb5db7d87721082350a76ba2e648ce80e31213789eab5fa6eef9dd7117133e9cbeb7fb591560dc604d1cae8248e5b3ca4b0a4b23c080cb342233a7cf40bc7cd828fc3137c471fc8419d07d14a052acad07a3fc5dd9dd394843847680c461f26614a4749d50ecd3c715ebde4afe039b04241b02b5181d8844f647ee7e58b2a9aaaf775972012f295f619df3db7d6664454d099392b3692cab5bfc9c0064080836e6f33984080cf5df470dfc0defb62eceeb54e97117405d918aa8975390d24811254c19c467bea6b79b01e560a5be067b8a41ba2bc2c1e367ccae68c0e2cdd9751ac284258d3227e016ea2d37aee07a3dba031c473ac4375608b4843f88ca8c447b2330b5b4c277a8d5f7432468b447420951860f496b76b4834b3debaa44eb7ef15ec2a850b49d54771c13d465e3812d1b15c531e6a218eb6745c4f83d54a2fd51bd503a62a10b5a87883ab09ba240ba8617c13cc9cd6fa164c3dbc82b5c031fba4bdc8e10306504563dd438a233829a395c57a8ef185b4f90ff1cc9cf5d76c7e079a9457786f8fccc6c70c5cdecf19fc470369246e3906439a1a5568958be75774cbe05684bdb9663c4554cd1402788c6c499c05241e0ac53267c0f529c3ee879a8d038c9066fcfca39d79b8be2d1054b07dd20e2491213181890a8a978343a04a7179b840a73c438d03fc407b472e6aeaca2bbf117c210c6e69d986134d8661f91861f0b03bf6aa4bc42d4a3be7506fc592ce5449e189410b0cb8bbed792a123fee2ae564c9177cb43a31cb1e4d6b7efe00b1834acb934106c8bb8c726c324ac9d5b19e0fb2105d035054872c375bfee6416330e833e421625be409f2d2077846307d2ea6c341088f03dfa6d2cb9226ef2566d8bcfe8e86054ef801da316b5fc985ee2d419027a906eb6a707717522ba7ad19046c3e7ebbfadb94a1d9d7f442fe9d419023d085f27f7dffcb0ac78240e37aa0fb1a867107f6f9995e17e3774240363fd209851d5e1fb4e5709a8939baf08d915a71754777a00d51f03b58014141c7146830fdd4f453fcc44820c2660c7afe3fb2037f6778c237087611b297a7da48562bcda60fa26f8b4e73604ae4e5a62f023336679f390c2b24d82adade5592ba2c09ae9edd36070131de1e9d83302fc4cbd252a9a4a656d406ff7a0d0a410d3116bef93d96ec19cc10f6677fc6e6333555d755067b52ad88c9441e52989c7777ce3cae6ad476f3f874ab8c972d3fca5f04da2e3fd093ddab0d2416ca601452100b2fab147e7a8ca0504231310e906aa76b5faa15567fdd070ac89739d5035188bc31cccb99e40c36e7093596879dd2743d302cdd30ac6254dab8119d733291ad260d94e90cc584eb3c4e448eef0d1ba8308b1e0650b474bab78d82a35b569450da5b840c7649ab15ae46969d7f0b8fdc2b101270642548d12d14ba38a97eca4f6c626c94882ba535893382fc227894b9c3460959267c8c93d0f5380093f3f2682dbdf8fc78ae9d1f4a9e899c8d74251238a3aeba8a31422eb17cdf62bcbf15b4c1a97fb1a2fc1ce806908fdf2af665a06e92f3d03ca39d06dd7618f1c04eb9672cc66a82abf92f06652f5e4eadedfeb077eb2e01e602a847d14f6eb1089bb221d3e7e323beb308696e05b24f339087c1499bbb1b1325098e0e691328ad7e223c6af25f89048f98b1d037da05f8456b6151f002f22527b3bdd89777c914e92cb23a80b06f18586ebfc2546228625ab19757133c034b8ea4d38be00c40fc1a95bc861d373e8c5f582a1f42aff06c021e1f910c92015f735d35f4a0012e7a0586da9479a634e50bad026c9ccf3a55adc28ed1d6f4dbdc7ea038f5122cec152bffb921f0ed8498af31b60ae385bf9cd2ab615e2f9e65a5797a7f5593a0fcb31ffd9a315f5f0a9c6968213a189b2748fcb9893e663cad5e6db633bdc2db566ff1e47d6d9673a4cbd1145c3725a66561c8b0c24258af92422e62e96b51088258fe0afb46c9e8e3a1089028bec11e8997f7f1b5ddea01ee8d537b5b46f718110203494d42b38a6e4a226f013483a0b886b628f516dd43e307b1e156bf0dc9788c78226fec67b5c3a1684ee639908efeaaae5d4a8586ab3cabc7f84e6b3e261294dce739327a723a20e0fe4b97eef28a7d46f5b7e8c0e97e9b45b8d9b51611f2146e251b7e2c3bba652e7538724c048838670f6a88123cf4f0812982b36525d8630fccd62fdbc189eb4c405941b652f769c012daf38c8c917920db4f527b0d16069aee1232e3c486660fe6f2fc82a558d63830da08da6a0aa194848840e3a1ad99416f6a9108a1c6ab55e1c1aebe63b5d7d8acc9a65119fed6d17676650b46c8b85ee09d18c67440dd4a5db38405e861918fb4801fd140e313234c47ad0f94c0130f1f2083b0264ff4a692fa37cc78269a6c723692e3a8eb0890fc76e70efc230334d935fec8494cd1a3ef246f80abc0b242ea80c6aab0422102263337bf3867bbfc3f11584628096f4ae02fefc7fd93210e6d41db38700679a1d73744cf1302d719653bb38185fb397f95d4104d860ebd7175b4051a7c6ce30674e23040f22c4d8368734e053aa6692e1d2c482676a58447d6e7c305d3e9d6fc00fb8931450dd1a66541e082d5e8e69e85a65e02aef1b7c60119be4672b9b192cdc38ee0b6b94d59a6116017706ca236fe8fab8522741d0e993e816ba850b92735451d27fc7cc007bdd1a3f0440b87ab75314f248cfa754d37603e1ff4c408852696cfc7a6eb834f8c42e4deaad3f50e6cd6d796ac640df5cd64f341153b3c54ca0d7de309f895f15ef5f8f762b9f11802bdca2d2b4744bb6a480a0bb2cbc1e178a50c6e7978d83f85587daba99609946d5f8bf4b0a0c4c39cef70b699035c28ff390eecf89a3442dc9a35a83d91deef9193b8b10cd385694b18f31b4c0a261473d741d83c2b9341927facbfbc8c36503de0f0f79e00b77764ff497e498438b71521096ba2931bd1c3e559ff6c6ef809f0901446dc5223317f3f703134f2c43ee9e0feae99b020f17e9074bf2b7da823ec42696bad8ecf04df413456a48a02a8190dd0272e81e3583501279ed5f14192b543b3396c44fe8957bde111dc0b651e425d3e9b52b0789bc13ea02a50e4f2342de4079e1a019c27b3a12f0ce538dec61e303b4602bb8386e4cbb590060e55ed059c2e1d68ddc19c9d86d62d2c1d002f13444672177e95cecaa496cd617bbd87ca8ff46773d80ce4e8a2bfa8d252bba051c3eab99bb26e78ce88776282a6173fe3fa8a6d74df3350fc1ffc06430a2fbc900e4851027c1a248bcc09ebb61bcf6938e26b533a75a9abb32110b09f3e984c42d5c5e3cb67ef4b0aae60e670bb30270b039f6f22b6c714dd0e223910895a8713662f2a07e1e7f213fbd7f49100e33e0793d9e283568ed88a245cc3912c2e1318ab90d2daec3843d7f71593003bc96641f7f9b29ef122dfcc51659d3bdf267349b1e4e285ff604c26e250b56de5d5be481bbc04d35d5be3516711ae6139ed61b88c204cc90695590dd04cb8c4b806df3ce24bdd08ac345ae1d11490e60c234bc27e23349fb42123bc849260a279333c53b9ae6ed4b7f06249a4010edd1437028aa2794f36af787a48d969a9a9bacd7ac8d75e8130a81811c183504f14aca65a08717e9d516779a523cda9b34d99bd75c153c5cf1ca14a918fd699d55ec4fc2e566a4df8c2449f113f53ba1f55747bf1866732d375a903d82564230cb0aac0ca99d7da6ac3aa094f5969062a712393fa8d3ec51d1133992905aeacc447eca6cc8819c7c0939022cab076679cfc2db66efe0fc865a43350f61e9f8c2d28fec9e59eb9d1b00c9774b7e03608f45e5de9890e5749590144f482ff5af6871bc516a810033841c1b85f938e4cb70a9ceb66105fa2a247f250fce5d53f1652c60bebeae4e6b7c202d88764ad2d9dad4d072015e82b4ad3ea90d28b9b0e327f97fd1f133853a075ae2f93801b5d6e2431e81a7d524a2ed09c94bd31f51c8467070d268c2a090f3a067806d4bf2d0a9a5cca65b7a52da9d2c1005ad9cb857a2f3557a1d03467ce2c0adc0ab4d3145beb5fb302055724ac2e5ab5cee9e326624ad7383c127e1aef1bda4a2e70af8cb3b97bc6a3049b274e4989214169b115269c598dee13ca44c460faebf69b63adafb644cbf33ac42256af3cfe3f95f3a24950c770399ae411dac24f9d3b49a592b4b7e120df7e6e4c6ec434d36a2de53b92ab2df48af7c3396f3e987a8a933b4ccd846aa05024f3f1a5a101deef61d135e97a4895d88edea0cde5e19669a02c98cb33e2dcd33784638b5fc3e23de1bdda2c340fc149ec7aa906473e7ed729027673ee5eb03acae8f9be3f6c1cbd9bb5fd3289e33b2fed69f358421b7b90194234654a2d291e628c3664860908493d829c08c1694190bd25df66d4597989d776dad0a1d90b4bd4f771a2dc965b74a7981966306dab42a7a00a73bf0d23e20ada99fcedaa54502189056b72d9574206650295ad7ea22d3adf8217468c99dc54818a0b0f546d8185a2c1a45c561addfca143c4db91dab623623903d8688c68a9448cd13ebc236b72681332fd50e1a4b38ae3341fd06e72281e58a968fc71278f4d237f715049a1887198b6b308abd62879c170469426b4d8f76ca204114189e7a08e3fda4df8a501c017358c937a1c156dc22c56ad41ced06397818f3e84e1777b8f8a589154535c639617ab3672ee936ec46a1fcd5020df466c95654c46ee491a454384c37eb3db8167cb229223090b95ac726d24d722cbf37122fbc38f902c7592d1081cabc099a5aa8e283d3b7cc37f2cda7444af66bcf709d16c83981e62bc63105ae35275082b2002019980b8d50381d002c42ecad38dba01b812faa2ec31cf3ec4799502d0d163d0710e163da30342ad86a436063b1f306f35777e3210f65f55a829786668450b3488be945d79bc5d520b2432b710e21798b8a833d24c76bc4d20b96562ba5aff4c64a411202bb736ad760a04aeee7a32af02fdb9dfb77a2e365db308646070a3e6424ae8520f20a376e1e89a4712a882f8be27e0aee02cfd1b0f77a1054e5f2cb25f5cc4526a12c48c55bdaa85d35125ad74896ab9a33898e559c4fe6d74c6e796e9dcb91cc4595dcbc9ef99fec445aff59462210e9762f479aff3a75e69a59d9430a4c389cbd59d503cf05be9cecae19643ca0aeeff2d688a8c0865710196f3c78031ff49fa927e65ec4db4520dbf5ef35995c993f76e09cb27ca02527262de86eed76a0cdbced5a49e3ad0ab79913c74455602bc3aa120b664d21c9a6f1d3e8420edaaedb0ff5fe9887b580a0759d2102dd493baba07b00bc3b2a7e1020b96b8b56e607d8ecd7d3c7a1a1fe7277a4378e74d237e9947e6a3aad8a5b0ebf16cd654443e9b02402c2de539e8f02205723ebf935f9f39d3494cee7bbb562be7b27a1c0e8b2d9a48ee819a03ba307a98be93207cc262b0f99593606c058adb819b675fa6b32fb00948904703da94158bcc6ac3850bf45db51a77a7842949221dd83854ea07efb91afdb18ca5d74694cc7e32da6ce5498b8cba295a4e06d3350b0b1214d9bdc3aa244486782375855fbe6ea4c7bbf583003fcb7444f39ee95cfb6db7ae793daee383542074c720c6ae7b421edc076718b03b48811c1f5ae65640ac5a171fac34778472213462e7ca2a2873aa5d621a0d0c835bf69481d2fdc0ac72e898d78b607c19882fd4a7d91632c2c9c7e0d3586a9e26f9ea63d9fee2a8342f09562d9863793853d93eab0bf734aef65d98b2e67d004e530cacb5740a9daed3d098620fb4f00076af249f500e042e6c26c5b0be5ec7c52800b0994ac1171cc02092a57a66a27949c729300c33e08f70b384274bb5dee00718610ecef8b49adc871e469e491cf1da0494881bc5f02dfed1f97ed21ee460b2a73639bdfd9840b2a7cac693dc21645a4be4149db17b97f4ef2aea10965beb6f896925536c0e4a22b050aaaab1085ec502d3331eca72d5d37592480c20c369af86fcd2c79374fc48407028fdff8376dfa426d43336227e101d731582344e2e3d182d01b57eb215c94923223e23226b4644b2d4aa6daf632cfc045b01a72397766dee1d4f4a81d37486f87ee67c24b76735df0fc772c81a7f005ca2f766f8ca2d4f01d46f73f5da05323032f4391448bf4f666d2e6fcef96fe694c910fcd826ee7b8eed24c86b5b49df3fedc74f113a2870b80d9f66cd6edbe1a073777e030ca94fcbafb9b32562c851aee4d80c32bd1e46f5493430d8f5e45994a517c120e354b54c742e35e5b00a5a56b96458155250921551b5f21124cf84a35920c702288043a276cce5962d0c8f12f672ef6696cf6baafbdee75c57c104a5208ba36cda031a104e1f6e9c427da06d270bb3257c8f8523b2bcaa85e789d15b5bcdbd464cf2ae02773c2513f9c5025bb918e82e9cb70375841ef67821e3d0824d2f5639e6c155ab433d5f40359c8de59ca9be8f60ef68857418a2690411b654565520ef75a6813aa98222ad05335a6fa6f19c8e1510e3cde81a47e2846b92e01ebc81688007c519b98c15c9c40953ad3557cb8f64d0c173f06e20fea6e2a72fa9f84c25e5cacff66ad51713388e2c4624485a7b01b20c6f336ee4e7ad30604fc252a018a71720e7ad6dfd16a831a9941541301f1ed4414eb26272facc8b335a99b0a96742330281e5662c85620e0b0df719a3c3945ec96423170dc473727f7dffbd2ad005e5a54bc81718e5a7b909b6063c22f6dce54b25bb3893c58c29c4f9b05152274c0b42b426a4255d1f5b8b3e821b0c55796ba0ba50303d390c6eb3bf0c175560af43321e6fb767c764217507423a619fb4af33884c7a363460893c36b5c47b0c1ac6ffa99892158c198018640ae4dc960231dffd42888acba5ce7174d3fdda9e663bfd8b667f2798386a6b8af8bec937d29f6c85cce48b719c278ff48ee2b1dba232f307b53ea116b4620cd934d3e6807042f0b04ecc9396f5336fded6e7759c757ca84c8d360281723ebd15c64a79c8f656a56c85792d75213cad1f2bc9c13bccdc8e065bd2d762241f3252f8b56f126a94bf395a3a7c4226f756b6a4ecec291a3f08474ddfcb1385750be617b06d572c4c59052a96f5a117fb3f52470aca6edc7d1c8510fd0b022f1458c21c04a125bcf619c1e0b1daebe5d7295992741b5ec080e2b9add449d72684d5f411c558a96d92fd18c403f22878905c20677d1c41723da8df53a5c71a558964519937f1d053c903a4143d112dc7f5732da330d957d7bbcc2bd293c2a293ffbe5cac05cebef82fa6ac0597b0d36e16022fbb918bcd2ffed4607156d09ed04000ebf9460025bffd50a97db7da978a34783a3d8ae5fa2ed1490b190216eb1ed2498efb26295ae4cc83f6d63374c3358062a16ad1fc33c3ca3bd5a40ac7ba9ddd55a654b30f64583ca42c364af005be5ae3bd8b04e928aefa10709b62fa2076b7abb47c48094a2d8f7be6889f46a857c79b44a489aeb6ba7fb142e37b08637ac907b3280aee05d6c25c4e780dc5b40a9c4c1f2b944b21e58ec0cfff2090acb6c679d6684e1713a14463b366c23c9818c633d1fe51d196a8f4810f10884b9d653c77042ba5cf9c6f595eccaac5d33826b558f308bacd0851837b9f71ee0691f0e5677b26d833b5eba10a8f6bcf559a7fdcb2bfb6313bd00c8cfe8c2656eb7ccf7d5a0c4c6e9f1aa89ab3a33070a048dc90778a4b2521132b77a112d4f87971f7c40fce8126bc76b70382a1ef67221a4574f1ddad2b793bb5ffce2ddfade910c9dc47ed66f029c1253eb47aef2f113914da6b19fb901d431ce9fed465be3648991857e76cf090d4907302cd10749bda8486f0464dfd33f99ba35121d73c6e4408b57441fbe1b602b4a81a6fbefe2c057a609bf42e5d2ef2d43b64cef1dd21dcbedc0732c93ada3aef2e4af8e342c5bb4a6d4aaee59772c31c9388aa5963eed2170a53dbc2737d1ad0b38e178159488e82350801082424a2638a1e250920906363b4da4269a7ec4128cd237c58fd79c413b301f3fde8385da3589a82bdc488f7bdf2f0263fe8041df7ad1a03feee4779180282d8013a3225eefc26b377c4188d85e63420a7c4c4ace67ef6ec0445449325704fdcda06d20239371f6033a103a3c014bdcc49773d291411789adc58c4033ae258071f185f4e75b5daee81c0d125dc6ac2364eb77ccd17ea23bbb71c0338729d6a75d02b5e60d53db4063b0eca65e8957adc991612cc6756221e3cc42dd12bfa6872a6708b7ea90e30034bc5c89b209b40c83d3421a49607c49506ac4d544c5f86b02c628d082e66c694c6a59f8610b7b82c68e7efa2a1fea6a5edf84ce827ddfccafc092c4944b4b06759675b05c60661425e2ef429b155d1fcc0a5fb1429654436a225948e906ecff1c0d1355595ddb2d0d43aa134c04021bdd82e8a88efa2921cac91fe94672df20415e0914277024b40d066df069134f261ede1987eb0b5f1a83e9cd0b60da3c163ec00d1ed476519186df5814042b0a3a12e9e00ec67e816380b56966a8125989959c6e83c971534859eb825f2a8cb23d9b3b0e21f6f66c68e892557c39fa8257d64f2865ae8d06e8f93969fc452c014a1d83060e41013258b0b4c4c86a9c8225dc2dd38150bf99988545ed0d551cb1b695a5a5eaa36d8d9759ea5a47d178574d5e81f775a247c4b20df1be6801421a59059a89303631965a7ce33212faa904d8d5712a9efb2bbbf401a4ea6c32724df0c12684e66efecd6762aa430f5cb6ccd00d348f3119fe6ebf2c97f9b7d6ff697bb895499903cd20b1e428bc05f2ff363c98fc076f5b49e55e0a7d04f3e946b308092c1abf5ebc597b62e2058140b0496101cff2a2e69a185b24101f0f6a203b64af517bbab6ca0dadb72283758e696b3f0431780ad37d848e5ec427206e57b803953ee12d451f3e4c93f2b3cb4cecdd6a09b5023f972be84191d20940ca70a0053aca81de7019ded1fc8dad376ebb08a5f010320a9c898cd461f9a275b7f9a49e85ec800a192e2f74695e0428673115a65fa5d951167dc3e5800cf760c938b17f486dd4fe3a88bb055c8b57cfc51f384056d3d490ab284e39d3272b8f8637290d91455a3bebf6f4f3393535313b1c65c01f2f6ecde6e0b60c4f14459f2e1e4831df14027e30facccfb32ea769d7ebbb58b59d814df3809423bf302ca992a70ef63c591ee6c3f3a1776cc57e5f0faaf01b250f1cc76c38f2561ccd211a490c4ef4af2b0920a624454aedddebc5525e3ade80a02e2c20bc670ec93c7f15159ac3e620ed208f594be6b78d0679cdf55310e1a28b6d0be19684d2a4c2a86fe717abdfbf877fe231cb05f011f010f282a90f540edce257a803965c2b1bc9852bbe49999f4e127aa1eec7776ae9528285b0483d2c1a90606ac02dfdfe05efad27542fd432495078e5d2adc1275fdf74c53fd1ced6c8457f3bfff77af026d5136389c9d36c0ef5e4c0936b30bba4997bf1f0d489a2de9b8fd13672a1c36e2d042578fb8d37a888ff0cb5a143717851b632e41623d4afae28f7bc5f8d7adb1e6bb2f53ce596592ebcedf55981fc50811d28ab7c3fb9aed84983a103fa657b7d3bc6e3641649b8b55178350e603092b81b4da44a8c9421a89f8fcc2987a9a09ab312852e30af136749ab6fac2da7293a0be3fbdd4dc2b8b99290471526b6d5620d31fe3e4d0d3df4b8f71b18704cf2c849d573f1d1acfd1fc0ebdea8784f4b3e830d6e46d46f7bacb46ba51c9e93b0e6c1b789a05b9b8affd4ad96d907a5fd5481e31ae950d8b7e05e2504f3e5125e701a22dfc2f795bbac83fba7db93434f5c9508f6405cd4a250910d532cc5a3039173feae3c47ad8d87aebc186387aa096b4694a3ca337e57be1bee6d0a383096d50fb2501288fa454512c126866588ae341dcadb421af8c47cacf2b8cd1f09dcca94aab27615b586509f3ecc562949c163de07309d72423b58b4e85719039ca949947ef28db32e67e848dd6061d04abb86ed9a8f60bed69e2e9c209b8a386bcbdf1973b103aebf868b6318503dec4ef6b3b05d369a3ce94a0938fe3605e809f56f5ab37625821aa189e0125604d05247310db3d407afc36eff936fc6f440f54bea23f4625e2da5a127c98ff492e09c00131071d24be5e2e1d1bd9ded495bd8650126932055b33cf45e21888a74c3789a565f91664cbab6af344510c1875f816ad9f11f6414b8fecad6c534e1acafd7d40a249b686aff933fe9e6d7c7ba705f2638567c023825bdddf277f3b52c3cd2f2dd79a82faa504eb09d6640578078dea329725855bd49bac4873ac65be2dff8a865381a20959909ef158918225bc37933828cd11b75342aa058d3d193fbb0649ec3b80ef9a06c279cedcb09e323abab68eebfeeb484cbfd9714f96020c6839b9d7de028bd90aa2b6557b5cadfb250c54f9ab44a799286ec82cf371051ac2c6aaca0d9f82d4fa778532418827f895527be2ed3a271952b2bc3cda45c4253d1e5e987e0e989331579e15517c91a1d430d4a8084bf24a6fd0d7193cb48692f919893d51f74e27ca86bb1d148ddf80151a745a4d1df077d626671ff5a26c08433b910110098484df5b4ca36f23c81f5a762c35d9e726a848c8421252ae31c18cd4ddcbbff70920a79d594f4caf806aa8717c69403afad9ad3a40c395e086bc2d46c3a59ae2cc2830901ca75db62cfc5049fd48807e1c7ed8d18d920263941bf9971bbf1bc5b20948e1c55f175d38cdcf14c4f04d4eee8e093fc8f984ce00cf53ca1e596c7d8fe80dfbf1d38d105c0c1043c987d6ac4d46d44a4af0488278702023e72547c2b1625c448c5adc4ac395697715fdd1f5bfc145135afe8f1c17d2a319b2f8052d563e4100d169c5651083bb2e4439eabd3e5185149a85fabe370afcdbfcb38c289e84a8ea0e37c43bdf00a775ec10a77e3ea2a6e3a95e7c14b7e924ba2c1f6bd5b7d8c13c4543a2dd9e58168b9ef0f10669d778e3be2ed48e0933a3a0f9f0202b3361b3ef7dc5d10301bca670190ad3e16d09c78efefc884a42ea1d99b32656e271ed50122ede7ac1c00202da01a51544c7809966e32c6e147859c9be82080ca9d0a00277ca6ad78c7a31d84435f34623725fbbab9e3b95002c48e891dcae1a9f582714b23b46aa704078a7e6d1c3f94a03465bbd198f96c9a536ef6c8930cbb2c9bddc4da4027bd918d0c471367f615f9912513b5981a4a0617869091c2cfe635e76e18ad0c3e8f6f2546b2a26be637b9402a953786985f008d70e359fd977c2e0c88d950fba85e37a1926e458d40b6f7e4e4856aaf1ca4f4cd3fba2972dc7918197afbb9ef01db2446cc8e957bd8ca61bc63caa95056b542982e012987cabde8b6d7ad01304272946c2a768a96c440b39ac00d7a6f20d3958f3e6c9f91b03633b6f3489b2bef9751939af7367446017b0e7ab278ae6c94df5e763af4a3776f1fbdf3e52134d54606567fa054dbe5459ac84820f55f9703f64980daca11a1d697f09f69fff790d215d2e624a1d460f465a27fce65cd1c4319dc1a2a82ab1226a0cff3e61fff88415550fd3827f8d9338309d1783ec4cea0266d9be8b21736568289bf4f90be914d0502dcaf0214a403c4cfd159055120ceb8b37a2ab33c0e8abee4d4897518990d5637b1675c18c21ac9bb57c561e5eb32dbf16667a1036cba855308e3ae53ec4dafbc63aa93de44000708f728ce42e8e769e11ad1d28104f30939f97fe628c33fbc00ad626f5301403b0eadaa3be5ae8c2db0e500752d5fbdd80cd2c26dc983d7b92bb5681d3fdce474a3d48abb98e2fc6a7a07532f704905402b73e24643f68fbd95557fc127cf68f5f3d281736302a3f7a3579b41fe52eda066f05daa1d06420182cd7b64e454472e7df90b786060cb1e504e98f0f36d6e1a01b5f9a78b92ed2ce0610fcc0dbd8d0dda1a4b6bdd32594a18288d6a9f41ce7ad1e44de19d32636f38fc5fcf0320a0c15eb004e105ba8faa25a4735ea29ba23ca05cd8d856dcbc84153cee318c8b4800e9f9a6b39ccd3aa6e1ae6c08948c4af9a410910685c6f24889c3f18ab96a03b418f93a4fe4cab3087c27260a7ca779fdf33ca5583a017d4eb85eb8089e512dce1440eb38cbe5df2c8bc4546d98864fcf4ce3da92e65331723bfb036a08623f037ddb0e485ace2c46abe7eb4964091d2350c23a3a7a370f2bbfd8d67f8910c5175bcabeaf05103dd7d14b97bdfdbc863331e0ed5a9bca5d28ada8f62163cd3a4807613d89cc27c934b6708f9b6683f6454bb8954ef6459771c9638461dbc6746fc3dca5cce18d8e48e77ac1bcb164a9911cb649cd5fb19e101b5a8e09bbd6ac3f08fd97c3e513c4a36686abbff4fc8d81a7a084042067b88055fa00f85cf34003d5744563b96c9ec367744afffd57edd032da8eebd47b029610038408c078ec1a7f4047209b47584f8d68d99613f0ed6ea9ba455e804e83873560456b45b9709dbe1e1c9e1620e96c75845da3d622787f87fc3948ccbe224d937ddaad216c15d778d73c2f87c0a97c07e05089fc1f81941fe0d70664ce465cba8e705222a5b1dd41af0ce44bfabc793559e14ba47b638a21ede9585ad8269a003bf7d5496e040f36ea8165267a5cef36686e8bca9318a6d28f468edc45b4cb8f00be37a5749cac14758926a289f54bedbe12321e2017add6dca76e44f1805c0e5d7ba2284e53d6ac771126d3ac8cff4f79a63027200d88e6ac1c0d6ad3de2b1e287b91d667127bbefcb891aee97b0e0ae07b3d8e4f700fea72673f1029de7700d9815a57dc09795a38f85c840a2ec0848817406c66f143a88436d9455ab3baf296a0d2f1f7c66a0be153beda2000dfcfaa5801408f8573a6edf971faabda66bca9adc70441acc7d4331f51292cf9e124f52470ce3f90546e2090c119f5ab39350522679bd148a646019f9a1af6c74eec41e0ce45f5caf505612c5217360f7fe330fea411c39f79068b206da524c69c8c240bda24d79a4e812927c6db00b4777e0ff956f746ac816ada50420943ead5d1792c4ee4d0f56ced0dd80b025dd39296f6902d51f4258f0ea8f3dfdb82d7bd3a3f73caa536a0db2fd931bdbc4d42b2f4a16b3801efa3c6fb11b04ff2477633428ddd407b69097d931a6565468d32dc6ed6017f8511956330c748ee5af14469c6fb5ca298f4224e88b9f46470b696735b7ae90ead5ee3c2b968756faa9a9daf59d35fba14f6d4cbeed8a8934572d137e7dd90b13504a8a5d37373080b814b6e961adf0e88fb235af26335fd40b0f293480eeb87e86868f8dd28c7dc35038e3cfa3ebd2fc672c919781782272018465c70ea8501f82efe316a80961e088c81a5c786af218b31f34e53efdccfa3933c1a11f92c66d9f9ab4f56c29e8c4e287450f8b34bcfa9cbe00c218d58b8eac4fe5b69cac1d951e3a7e1a7b8f39e1f0dd37b9721969f11deadbb9db7e366af43aba223062ff6f4e0e4deafd3d023c565e621f81edfbe4fbe4afe32df17767ad0a73519748fedfb5e3d287fef5b640340838060e404dbfb8731bd3a813be976976fb97de4d6a8f406ef0373012d44e8079af112a1bbad98abc5e9ad276459e6cf63e631442acecccf7ce457eaa09f6116d02195d2ba271d11478cb7411ccedcdbd9917320c0375d49b9b2dd80c75c9a8c5b369e3b8dde6b536d109b2dd0a4afb980d567673c33be1d33db4b96e74496fd41b0c09c117bb428a53b363e45bfa0cf951e5b8a72eb18334a79b986b7105260dc56a6bde6e16d103980af9a2ce625a82ce9dc7455f0d5031a7a1cf75a281131748746be22892c444d504630e6012f4139c29f07a578fc367cb507a6a48e5c9fb9d221d83a8962690b137881dd01fe6fd3237cfca919df6bc256e46dad066e69885f9f6e50fa906d4fc10536cd89fdecb7a8d299cdf7294104d18603bf984b0b847201e2e077994b2cf0b02332276cd09746193efa1aa0bcbbed985b184866d8c09b11e558df43c6483c72ac0e046b3b1c4952f823e9dc3150ef0e3740e9c5127f4282a82c577d38029a282af680f89cc98119200119a11ac5317ebafcd82ef579f7a73974f42f92060dc4099ac8d862c3cfe45a8bdfa867ac49ab7c30645031d995007783ed9e05b41778ce5954395a01bec72a4039d018fef619725b7085d9ba3f2d7971eff2f9d79c8da6be5fb9fce1f258f71ffbb3fe88b7da546aff675ead04e50e227f3130a4cb79b039a4a4abeb65f0949e2cfc7ce44c28194c1e81504481f00b0be0ba11b8c1affe06716123d4fe547f448f749425e9df80a98d83d5a93f33c501dbcf7f5a57dde1afb9d3305e3f442ef33ecb5391c8eb075f1b5c229a951b119e62786f58ea828df7fc74d6444d6d14a733b2aba1b27cfc1999ae54b5d26776ae2010b39dac00fdd90ed2b0089fb3da687f3b43061bda79386e648dfb5ed4e9e19cafe3a6ac3b339e80a20004a38ad34a1d8856f6fa1c9e1a3c2aec1b64827630c949bbf3b58202e673cd76e7c6b73be345d42f4faa48046431e2bfb399d59dcfa2b11c131f951eda8846571cd622802b60c54ada49654e4cb098ed72f1903bfe04486e2616bbfe96bc6adb05dbec54e61489f748ee6509f0e500b0b9c100ce8ed3c1f6e0f9c08c387983aaddcd65b480f4dc8a61bfa7079dd09ceccd68dbdf80cfe74dc3f4c49533a120781d6f50700832e0f20289b5e536b564f991c78e04c6fba920b5402c4b782913975b945212e88f67265827a44fa34d5e4b1a31046fadda5161b2ae6d1166c81fc5c4f7305b4c54d42267e36bcf392f50a4866d631974899221e0d15f0f0da3bb4b67bf396b2c0d335773c34260dcd0582f60f25cdd8f612b2d4014191b2c46e871f0a2511fc7bb54e3ae236b07dba9bea249a6fa848f33adc147ebfaca03aa208162fad2f7445fe1bbcec61638107aef4b909b80cffea4f1fc2fed3df13f36cb285e5207daf93c18262df658aedc19b8a1541f7b06d0c02e67e1c935ee3c3b38fbb310d770b77387fb845fcdbc15999b99afcb6cced866322f90e1443158a90e2014312f1bc3d76b2ae6ca69d40e575a76e6a3b8a34d19ff6d0daf4220e38e251a9d1efa0c2ee9f98a6cf853d02e2cfd20988e0693d1fcae65aa162ac7ed3c8fd20ec9ec41df5b0a3f5d11f2963bd563f284eefd50fb6fb6ef77313eb03cc8ad4242e4ba805a83d6712c266fbb55b37a63a9b6e6042fb423659c6a1b2fca03cceed44867ac958ac013ce5156cf02e2f7b479548b0a2656a668d8d2e7b5639d722a0b9c918391123bf777dce8a3630218fd4852905e83c40e51d511f534ffe6877756096e506a7e58eef718b41b43ba8928ed3aca4531f7e19db8a5512fcadaa573e0a86c7b2587245b7bfbd684447d2425693656a8ec02ed3fa040a73d84d07fcdfef78334d62053c3bd4bf7c068df9fb4840d72b20e8875012f0c8d7e1aa8759bfdafbcef27a1ba9ee5e84e0926922eafe31e4465699f1dbc8ad3db34e026dc345aed708d99793a873d43c4c641f924c5e01510143efbc10448e3394e7b53b194a8d7ba10c3342f6aeca1566662e392e4deaadc760dd7ff7aa2f821354cbbec8887bbe5ea4f80178413f24aecfcae532dd851a0f5d18c5be586089eb851cd866797b9a950b156d403a92a7cebaa266f27a1cb83dc7a5ee9207b9a6bbc4cb0fa3f08299993c7e6b8210f49e9a9dadcffb2c6e798072bc7bcd431cef280faa25101b2ae7452aa8d5b321fc043d22a1c24e45ed55730cc61171941f0686e56686cb3048259bfb214d2b0065c497e555f4b70289192275334fb07f665020d72df0315187025103149f59fa3e693cf310a40575437a47a2b1be8e1807ff30fa6ec034ece84b37042838ec7e01702ac42a191ab45d6c8bae238ccb65cbeac0ff81073aed7eb65daebb60f31b90fc19e3ff7ab03aef8204835b3f89b2781b7a7e56e10c55e8f40b04071d7e825882f19827f1cec72e3fa145b1d55591d608ace3687d7f9a0ff14f03c210d1e23bd1e7b475c1a0da74baa6774c67a46ccd6576972e6638bd6df17508dfd5dee54fcff26d20c2ee402cc4edac2c00d61d5fe255a1444f398c2428908d386a0309207e788757b88a0cb1f9a967980f8d0a770743aaf99875e4136830292c454deba4bede22360b3e33088249a3a9214a0f00a513bcf3d92b42e367de85da578fea53026aef2da181b17abf4fd014805dd99a3598339994f974382c621f1efd3cc1052d4a29ae1677ca41de1fb7427718a9e1faa2734c9a128fca39730ded316b5919be13d30eef11e0ad1945716b2d12ebe240efe85a1d21284c3f268250ca8290a6f86d8d8258d9d49c0fe73985cf3533a49fdae2073cee9aaebe5524e5359a0a8b108ef06be12437a6e351f2f0adf953e26e308872c136de58337543cfadcfafed3ea27682b1e8d81d7e1bbcc240e3b303f1967eece32a097f2ef8be1fa25c81b9ab52368930f003809a0ff87d5d13748deac501937e5a3cfbf0023a43b0221ca4213a0fce302dde1dfbbec63554a2fb5f8c7c134effe875a6d2ef74d76831d2d5f3e06cedf9141fa6bda867c64bae24e7cda7642bbfbde96244e6068807c581395ba0d8b5173900f9bbe1541eb5769f1c005c73cf1bf5704e4595943b629662b52b138bceeb17ac2b680e9617eb427d0943d5dd739e7113f34e57ac0d8edfcc30fb08b982681239d64581b8bd3fec88ee0b1e8916de43a0091d0d8a263434b3f2f5a5d66163d144516db906de01f0b2f30b2e08d75971adcf85cf4f14cb4e8b2a6ba064b43acd1bd252e9e082ad45fde6ad3b52269945c370f76c9b41ca1ae862c030c54640a141857c3733c45bc71bc5d311535efbea29d0deb556b48e7c9ac3232ec7aee209269becbbd44906842319ea1c7a3cb5d077aa6b997aaf4648f1a7e2ab60d6f6497ba728b24cc2fe17ea2bfac8b1c256b09f95957b82301f4ddbffc10c903233edf5e3f1bdc7d97f1d8c834b4ce8fa1a5ebe96da5a5daa0320a0b8c8dad5a4a67701f12582f58373c42a87c24a430f7f1b6dce0420e5a059617495a11eea1178aa2b3061d392b1c3bd61461d0ed564271379a054789a01e7df97b794b159d5d77183363740b7e8884bfcac70cf0dab04c00111ebc04490b1560bbeb0769361008f566ea81ba03c7642d2700cc5fc63210eb1143e0b0c0b9cea37a25cfd7cd7aa35a61b9f76241d44e9f2a54c38ee7804d0d69014c3caeb123b0a53f1a12120bc34f674c82646703f08540fa3dc44017e319816de8bc242b03a3e433273ffbbcc9473542fbe793f789e9ccddb173e4d7e9f14adee199100b310014996d1f3dfb14915bc2a6e64a60b29be9d07b602d50d6807f02ffe8598aa216931f9fba2a685c92e8ff296558edb07ae48e1aa563c1a305815fd30014c3deeba825e44bb5bfc3ee8f4fe3ca7a70008461388f847f3d1fe4e7f18a5d3af9f94383c9bae07073698c2755e0acaa13f6b7ffb1b12b3fe59bf87848b1251f9e0e1f7bac863c615c9c2cef147c4972b1a658a4d4bb8bb1c843317928862b4576aea7de2f8dee9846dc16d08c19829c249e88810ca4a5fd8f1926008ff5fbfb609bd3f8358ec510976cea954d71d3ead794291632fce19f029761812e43c27ac8c99681d777d51e2874b9c5f428858d37155cd14aacc5875b475317a0f8c3d9dc3c5edccd26b48398587c1e292d191b18d193afd0912feab205597cdb553b58d1ce6e04620d74d62962869325c1a8f0bb35946dc692758b0f5fbeff84a10841e071bfe2f1aed3c5cc183404fdd78346496b97ae11502eae63c48dff6890688f2eec9f89c2b209296c8bf8ff282d6ba71a0290c58b427ff6f201ad90879b8055446366adb94384e4098fa12ec8ecdb795c509395ca701ac85a685600c1b6e8c7f2cc484afebb6efc0f354ed5126ff6db6f0954ee15e0b7c03188ea4313e07f5aeb8c9892ef3f1304512288872c0969a7c4bc5703240e472e8861cc1d2cd8e6399804dfc9ff7dc98f88e199e20b709771a803cd4338b1480c2a573f2092bcf65c8b993c2d98600d80f155616ea0e6be1a010572ad2e39a2c971d72fb326dbf7cfbdc60fe64fa3ff01f8da1e0270131be924363cf1cd1f22d8e26ce98f2300a6b440b74f5cd3edc5b5ab40eb52a5c9e1183a02cbd27dcbbd8436d6db18c4536144b7fefe45644f1837dcc6ac9ba5916117386d07be0f21c4fbb96afc99d8bc4419cad98811d9f916169ac39db16d635cc9866ded4d5c41d8df193251cc03f99e268c9f2adc354fb07002c4b76595e8a3c5586709331fa9eb5a1c5b910697f57741c0d255af2914c0e2430a47225071bc7c549bb4f536e0641f4e29d61b3c1e34c86929f7d7ac10b5973b8e00af1472288504e572effa8f6b9c415a7cecbf4695a1d6a416297c7043ae770ddeb3338fd5f13a25c538ca67f9c6e5aa34511b553ab9098a11474118ce7a6c984dec83b24ee98c62e38750f56879b0ece76fc0a9b75af982f492424a9bddee20e006a266257fe3629caccdd0d120e5222b2e9fe2da715f42a5edf0370e1d657087d4f8a127dccf32b867c124f208120563f0dd09514062b6f3d87f2188e74e9b670e4f158bcdace9c39e08edf9bb7aead1c55658ccdb1093fd3df4ad1da635af6b47caae356bdf6980ccfb0420f5b862ec4aecd91c530ed08b02bd6c7ff07f9e9eb91308ff357aceb9ddf56170e3fc196322dd326fd21ff41865678214a177288dc5c91dda6c083a7270f389aa7fe8d571d5d99c483146e5f4580015524aa9eb4f547fd414aabc077bbf6fead77fda191c5483ad2d3469612e713e3e175149c205384b3f5498b4f73a7c529aa061791514294a153cacf59ee3d1e8c0baba839eb080d556c7ba90d6e0dbebdfd65ca78eced8d65bd092055d44d1ba07ee1c59d512e9bb597293723bcf884ec2b703cffab5461231be0aa455f1f98b8523814827edd379437ae8049130e036ef6f80fbbf0ddb3320bb1428fd1c5949293e86286e2f8734d7f7a64ea4f91bce2d9d5191e29e3b26190c63e59541cddbd630552387d6bbe5c7daacb46136eb0c97c20023d5491a8fcb93ac198e326960c555c869895f03d5d2fd6b1d22d8cefdfc6a64bc1c52e5d9b970596979a47954436a580855dcf0f576489ca36bb90ebef30ee97d0c9240fafe8196741c2399c663e88d62135c8a5477e45ceea14837d3d77e68a9a124386bb0585242c692301cae40f01adc2c2a84068647178a3298536b93c8b38e4b3d47752e5a66b4c0d3dda75fe997a279c74eff449134b382b47a3f3bd0e709abc6121cb0f75369f938100187d36cc60f3385d7a70ddbec011a3533d9f2146fe2def40cb5ae675a88fd67dab40628871b36229d3356576b664666fa0892437d6450c988e99e70b1aacb5c7e92aa2945743e9f8e032d6a6c8fea8c2a823e0fcfa83c3b3fad3fc610a2b81cc6342002dd7aa6aa7c6e255caa37c065a55cef9009052650eb0d5649bd9b6b15b5bcfd5fe8115e09ac28580934a0c0570a0570b2f1082ce46af4ac0418c7ed74183ce2fb3a0cac9c0d3e8cf8cc74618c59a8a30596376d8771bf2ddcc0a093e29e973ef3b37a7568fdb355a44fd329eb12729888819f52e9a6af62cbb25a76e11de4379d1857e6ad6878df538b121220582361969f4c64485817bb583c524d5fa70a9487a2fe8db58dbcc9a1593ebd6ff99848759c79c343009d95dc69573b58635862e839492590a4c222df1f8cd22bc854cc1cb3b66598bf636c16e83b7547c19892881c140c05146af28eeee4e79d45511ff9c56dd509243944bcdb2cb3e7a83c14921d3a28a9fbb8c8acc0f500ad7ad35e4121afebf7939a0778169268de0ad3bbfe75335bc36b1d9d684e30dfd26b10f41589c18c3d0805c9ecf77e8a66520672d28ac62dc0cc2b2b8b6f09c690e62d0b91e2107137fbc8ffb8115d813c4ec337dd568ac9baccde9efa7ce3b6cc749fdd2f24a80daafcc2827bd04077bc2da04787c02e6e8e662d38217ef345e056269d8f17aba22166c61f5133431708c3728c79c9d898f874e03c5246926419cc0a5245f9c3203b041ab5dfc4381ca369e7d04fbe310081a3d280ba028f7301190b1f044cbc3eb83157aebd88d9b43f00f51cec55ab46a73482a8f14dce80ed39af80b38d8f06ff60ec6bca4f02f8521e759246c20048742a2ec6032f8a13286e5dc469c0e7360e1a133dc3272c79d8783e2356df2b8bbba0664a94b6d2fb5d501deefecb181d4251d9515041bf564657f857df4ca2714babf30917b223fca1dbcf3ce933f3858662777677147b1369970968e698d24243847110f6732a9002340244ea0b96cb7e11e8270478d8b8322f17082d6d3e92b7ebeafb20333fce77da822c67ad7f707fb0d0092c6090e7671df12089ab4cea7e57c735fbd170eb1390f45958e98da02e55d93f62edb00f2e19a1dd1c48ceef44f330330856099cdfd44a869d18fc1412768a7d2c79ecfefa02c0660f1f3c104e40e6df3bdccd7a830a09e11b1d7cc621a09874a6e9a12103e20c5af88467bd408c510c32b4a813de8dd2cb08c5f7205b9ec90fe0e37e212cc43eab7298d7f828170d300fd448df92e3c86d471221095612b0e8942be580c23889781fda641390d32ab7829f65f3107af6971b5a064a7adc58cf42be9266ee21ff009c85ef15ce07b1496542e2b49b0236a740b091082f21c29645e5bf2616d983b033b2b76114f54dd0aa920c9d68dadd0dac336900f95569a8b064069cc5f4b25c4204cc5afbd9b1fab7269d346e415170a69a5721216e5dfc8b0656c6bfae9d18047f4790b72de1b3f91e4b99353c08d9a90674457729d3c3c83bc6bdea64dbd8d023569fe30b7970af04e5613b9004b6b1592f5d7b6fdee7244d1648ad60e1cade6182cdcae3a7b2a0dfc5ae9d815a61e75f6cb69723061c0f519e8bc065288bd9aa27e065266cc9e98b930906a022e24b14fa35ae5ebf22254acb4faf4e795d02e39b51c0017bfab684abb33c719409112277d10f522e31174dfd5b06c97aea490212667c8f39407508988b0b78fe6accbe2a2f055eee893e182f40b3f7d525914652342948331f2e53a0bcbad8b77f89528ff8c1119a22df98699819ed1c7ddedd04ac2c2e95a7502625a0f31b8405b58ae690b8f2a752ae149e32f00c34ebac6a3df2c0f96409c39d6e47c641c8e942b288708f7ac4c516c69a96af2c5d4e650f8dcf1fdf060b2bb0ef1295128d5a6eb5494dbf78096058d147086386c6a81bc275d9f286032576e5c312d151af62fd8e69b1fb4bc76aa699b5e14142eff64c2fb3f9e3decd86eaf4eb9593bc651c37c7f678b2033dbe1efffbbe430cbfc21f089b946c66aa0e5cce9d1b4f2c4eaf94685418d8577ad37c2dbf9d85aa855d077de7f94fe573f82f0cc2651b2971e342cce9a178b3b4fdbed3c6e42ddda70908688115d4f9ea70253e03d1e239ba7fe49273fc3edeba73972327dc01705576b1a3d3b8c9edaef97146d9118d196e408a96f934965e4266cc2805b4f77557576567946ac12ec0ce5debff9889ac8f1d6d90bfb80a15f9fb1fbab9eb302cec9d96a2ac566b1a46ad57f9c465cc498898b6ebb259089e7ad1a14aaf422f388fa0e6d4a6db3c27b0aebfbffd87ee75291d768bb32b20708c9b5f019e4f8d27170027c7e48325a05110b312ca9274b664c84b1a343e7ff8c83003ac3f8437ed3f0b515958efdbf02e54b8ac3844d41dd44fae1bfcc186a84589271cf91c707dd8fadcec6531e0f5d75ee69d63c324ff0342117d5bb10d0595250412b175cc0618999dfa86ce31b401ea184c064c67c66832058e186cec39c253e3e686889fe9bf0a863e131fbf6529e08869cb7abdbd8999c9df98b0616b8afe3fd8e714afc8fcf2bd941f6e7a1b82afeae204f6364e68bb8ffb13299e218e2e8ad6b18c64ad0c9a9799c870006cf757a4240ca4568c71d1ae9ad5415e21deef6c3504e8e8aaa9dc68d744471a789d939a5eb8a3bc0d8d3dc3ca087b0d30309772caf87b29233a60f24e9bccea5b9eff34367f14514fc0b3f78fba98f3c8a3b13725cb8b05ef8074d9a7e557153d8885e3fbc26f402c71ceaa4dd42b54790ba983f4d8478f784628191bcb12ecf45c391065c53959e6da1159a2457065c101e3071e1df8453c63c639c4c0edcb116889f249c2156c9179091b9bc26a5dcb1c71cf3946f15f175048fd41d7a756c85122a912cc7594636c1fd7b9adeb0b94009f3520e9a6142ebeb1160825e85112c82e210d4a834826b7b4db25fa26cda3dcd79c5163bcbfc1efc044f1fc8b6b40a313fbcf45200bd25013e1a654cf764aeceba1d9a2c01a79a6de5af84c2e55370eb2e15a87a392a17e0ba983ebd0faff136a77d99b723ecb935b4efb787e66ac65fc0b2beaba2368df891e4c3a4b7c2fe8f7ec6a608ca638f089f95c26d6f60561e1a3b245453be3a58761e85114ed8e725c18d0c8ed535c6e5c1af26008892248d0ccb7b0c7806ad6b0478486ef8a996072650b9647fd466ccdd725a714a447874ffa57b338e72f49ff8be1a3a004d9fcea039cb6ac3130b0562f36bab64c7847a664319ff9ab6e89f70e31deebd887af11d40cd31553ccb3e1a4173137f9230e50ce873f6c9c2edf71c145814b71d6e60659ac0ff5da601015e5eb838d8893289d1b7516e559187b12d580d13ebe8066d047a21553cd95c73cc8e8df004ef43e0f6db1eaf2ca7cb0ad2d03d38fe7783f09874ce2beb5de7a425bc4a2c23cc334f5562fa8c009b0e2808051e43f76af0451458b199c2d43441023f9f8cd1a2d337c184719162e5a80ee1e469e14f06e2f86b242c76c64f199f61a4b407be136a436d4330fc13abf363d92f98fa86a4cb28b16fcde6ee35b5c38911b45565f8cfa0981dd3b2b3d7821b739f49a96e52c1daa4b63639b1eff5fd4a4b6ac70f0e8219e43fce7c005fe3254c77d509963f9f2886209135710f192595ff03445c95d1f4f634ff80536d286098e55ebdec72692337d8078ca536db91b9cc0f5761c17e2530b5a46b4b612fd89ce25400f0c6c509f72b09678193a879e8b9d8c58c3e7f3b83fb911562486c6753ad828eb740342a5fddcd92f49ace9874c800ea72bd7f66c9dfb826eb120bdba72c9fead5fa0adec9b33fd5298eff041d208b94c5ed40645f1546f02d594ec3a7ce4c6495f85f180fe5eb8cf6a023c8384d2a358aafd126e6fd84d14a89bda0d228177df42fcd0c2dd01e1edd2b280b2aa243d9dbc48d543a436d487e34a53e3790e7fac9882e7e15f77435485efa65f35807b8260cb5a8f733a233d2d70aedb4ed05c23a19e265a492ec2d06612be05209b761cb0d30bfef4b8c31e1cbada752039f9c361baf751cb817e51020288c198841ee4922f0c1e57802fd7fec2e0813b64ee5096e92e06f6f21a250c687020b4d9b215a6f337a23a29705d2e0e9787bf20ec44feec3527aa9f69de989b454b51069da8391ff4deff6c1fdf6634a57d1148a3a7a9778adeac3c4a5fb036b9a4dbf507f45ec7ff40ae0380e848dd7b11e07ddcdc0bdcd6cd9dd67336e78332b75f0067802bf03dc10889221b025cb33186b6feacf88ea9e6f4e732d4661499994909fd8b2a591edd6424c077d894dac721917c81c6683bef68929c09787622b9323073d2127217d5a47c22a6c6b02d45e12512af6c195defd6d440397f6e4ed3fbad7af57dee253d330039c2f2c7d124d2838a39f3a9e5d5265b4f77ca61c969a2b144df534d5512e15411b3e15eb8ef0b32ddea1dc4914ab2ea3ad8e9e299b9b700a8f3bd422da95fd130bf5d7cb3141c042ab601b8a448d22bf96265564a25443a996da06221a2af6bb20c3bce71615c9f1cd6373ca12a2249c7da59cba2e6bf453e6c0083f6231f41c06b9bcb0c3a08c9285b898b2801ae6b90854dfe9be302768f520153e75f881833a8c4fd5fd627d5a46308d37634da261fc639ed6d34825a039988d6ca3eb5028471e346b5cf1daea53dd0a235f8600f88f11dc5f73e406b7811ec23b2fdbe52b8197ea05831f73b7d6b2b3a26668e25084ced3603497c2dd835ceb88b4b30e84ee6d3e0bad096ec0587523b002f9e8f18fc9341fb4b32a7a40f81262b54ded7d0d52c25227928c4579ae2d92edb470ecaabe94cd0e4e76933ad8cd817392c646c9ab45edd24d96fadec1c883727746a9098ef420336131712f579536b5c6ba76d2ca0ae4d2c60a936e99705f2d1e90403f0f8a23c653e0cb3835c6460f036bc028bec530e6d8f2808e3e01a8f2ec0d4296b65bd9c98b5246dfe9a2d74b990e9a49fc47e3c31553e809512f2c3d5e1a9decbc377338c71494793eb6d4cecce6c54d162dd9a00f2edee6a02149965724b3a11c2b5ece31b1e91b10047ab03e1cf16ff005ecb2278a05ed7464d07234f22ab7baec21349188eadc92888816fc6ef1206c10819d192faace854c0a59dde79e7481d2723955c5909b2924038f24ed1b55bfd2d6ddb6e256ffad58ddff825949506f9935316108b221d6ebd457d8a91842055f30909396cfa1844f921e15d2aa7c8c1e0a658e1a667d6d3f23a6f2b74643e93b48ceb65160511703e9773e4fdc8751650bbfdb6aea7433dd89979aaed059ad78e0701612433d78eb2bfda370a5580f731f883a4a9a8994dd5b7cccb81ca6bd19f3e01ac851949c58a1b36bc4399892c7b9d8ce1b818f93f00840e05a11e02df15e8f7e0f6be6ba9eddb5c286a70d66773762dc65a32c1812a2e263e7282b3770105704022494973bd562b2594e8902edca5b424cdd79aeb1d49f447c057f3f2743d5a646493b1e5ba20a3255ef6016173e0e0c502519a562045b24ab535f6ba81f25496c45857f8c182b1e3714c718bb5f605ad24d21da803b09fa38d610c9d85d40470fb6d6dcd1601d190d830c6db9cbddc656aa221c66c1b4680a29c9eac8c23c98cc84211078c3e9a035c8a752681ae4534a87d3ce2c1139a1d514026950e9cf3f2c93212f463c5348afd51811c2098042c4cab49b8c039a8be850381692806ed7eeaa71f12efa0d0c743ac95ed43fc1ba21ba58538d1be5a69b55a33b90e2b0ef273eec0db0cb5523833c9ea3bde3ac495ea8dfefb842075048b65a4a27f815c3c7e61a984556e2c981d394724702f5210ce51e716c55d8dd5cb2c1e4838f4385cd1c62c1e7abe9f96cde42b562c7d13fa635575778a47e73784d1c98284695d5dcd908ee7256f7baeff26d8a1383f482afab04d46d7bbec479aac53b8c479a82cf617ce856653b4080dbe118fdc03da6e1eb6089943e8bc80b145b1e7f06fc56436266bd6e9802098b51e155138aacdba834b24d104c46dd1964f7b11b73ba51a3b7b6087c01dfc677ff3d2052f9cdaec41929347519a7db8d2590f800fd2354c0db458958e81521cceae5539d8c02d189e226e9718020c04eb25bead0b8fa4cb4bcc2b3b4852c3545a34bea388ad347159c3f38beccd4912313ebbeaecd8e27cbfeefaa3a012e4196161cc33e646d78bff0ee66147322f118719e888e234b0092bfb5e18c8a9e8f2d8f33eb44d08337b51c33ce9cceac0ed6664d0452be07cdecc846d38f5fb95c2106d5dbb41439959b1b0604e6c8b65f700ef2ec29ad8237a76b3004ce0bc064e40e71cf7bb62b4636099f556380e61051d9a7971e5b075a68d1b41f7b84721d78ae6ec462fe7896b3fe7d1caea38450fddc568d4b81a142e565fa409dce678fde3ceb13e4bf91654746e250d4c619cfd64e77b78c94c1a1bfebf29111cefb43056c991d7b3a62bca56b3a9cad42f48184f961b6b30c24cb50f5f821877687e09167e193b41262853c31419890ac53cab3cc43258ad8b8b624508efcec98b1a90f7b2edf53600b75d1741df381286c02ac66e71c37bcc75bd4c521b1e707a1232e1191ed4eacfb354b2dcba9bff6a67c7bfdb211715493819577b9fb34d77671dab4aba43290987a22951134ad9f0583ecd760498bc562110a5310ae83c977bdd0a50c8fe2e36c7be290fc3db2ed2700955b56574010248ee32bfea6b43209f6ec684f0b9cab24cde060bc70740ae610335dd29ff06aae0a9fd98b9eddc89f2482190d47c637906e6f7237414f7106f4be4476a6e707c0c1875963e71a087554deffb12357141904ee97c4f0886eb1f872012e5668bc11fd515a2ccaddf0b402c74a8d372b9cbadc1c6b220f14e80e7149e985773bab2824d6111e8e65873c8cbc961916b6004a574388557ef48b6604ac67d72ba9628acc5530a726d75d6293b913fe7fb0238da293dbfd241081cea28e937b04d237071307bf19ee33ec1bf5e91ad1c3dd68d5d5e83e1088f4199e296f69453325a2d88c3b023e11636346b627ab02fb3a014e6dee0a9c9401ec289f10170ed995930912b553e7ce99262578953a23383e174bde227f63ae9ec2973de5883a230a4886c89b2fd32b8327a5a9f764b442ab50951c9f48867a4d3bac6100a85096c80b080403e2df351a6c656dc88d9315522d10f5cb8f22ef865f6534b114172e9560e2f8888b8e6bb81295cd686f8fd55c41299873ea825a6ef7e491825b1e58a8f356e87dc10a6d4046a8d0d7f1a304950198d45109ac199201e9a5be78ac3651f132870304d52960c0b5c349cc1438d4e234c40bcf830956e731e8c5b3bbd3cee5456010949bb103e3030dffa55b5c8009fb8878f2b0d6803305fb899b7b644343e23026cbeefe63f440ad3640f3b9439dfc5141a3dbfc0913e917828421779bd9c58a59920c400de019725af3cc8bc91339e79bf376cfb04cc31a4bb5bd51476fc0c921caf6f35dc5115a4a31a4e047a921c730122e14cc85905948491f6673301897baaf52f36e21a9b6cb9b71f505a792db74930fa51226a465af3b708cd12482c9eb163721c63909fe97c0d2dd56a684a4a390f5282f52d630c6aed1723294ea45e30bc866beea4acce1a9f9277a45070106c43934cb4f5243e8051c89e4eed61353f0f9a0c04bc8caf4c78058b04d1f700d81ad8de1764167f608494c0c18978e4e0cd9c05ba2d8709d80a9ba3645a7e4720688cb7928063fb5e2c0d692a0e54d8850398262c4357b625fede9d63732dfdebe9867e4ba097de97af15f49544065177b1dfa682a71a387675baf505fb2fc1ce94275d2b14dfbdedbd849e065f9c02ca1832376a8ed2f42c06789f3d6f6ba868db29703b15a9db5534184b0484119fd390b6bf95af105dfe87f69f70a1e8bf072a6683bb755bb934bfd81f8cbbea1dab971f50224200388f8dc402e22eacb625cc05f5bd8e635022e0a092899d7844b4bb4b21a026932113001c6492d812470aef2c6d5d7a547b020538d2b69147e1319a527ba26ce8d80e40e71317ca5b830ee0b9202fb90ec8537bb5581561b3c9650ca347da253e793ac66e0c9b150f43a2f149201cf85a3bd681f43cee746db3df7da8dc2edbcb3f9327ff62d6f1bb6d3e1a90e5b8c6236b0a603961d8a2016292a0caf1fca81740f2b7f75452f30a0a81b8dca9cc0ae44add3ec14cd998ade906b16b77f38b63d3a67835744c919ddbe20f42cf605a9e817c9dca01391d4ab187c56e848f97b4939593038aa784863984bd2c777c33b173247dfe5467ee2f2a95fb87f5f44ccad546d065f020c89cbaca5d5e4b333f1571fc5633cd8f52bee1722422c7544253b818b6cc074551c900370fc19f43cf585327f6be536a641262eb75fec04a17b0c58047c313e5dc08d939cd83b4b6399c6eb8dad15b057079c7a06775906d949f91687c2b575b7d6f5f218abc1663ff1a51ccbab6de95c4187dd2938ec22cfe7d8225a15a7403990d66a269a6af507d472ce03e9e6b18a54431448de3d04cc71c14bd8cd9409fbf510ff78790cb03e9f533f50dbfe1dcabe4b4c483c8cf132b44778cf49f26b664158dca9c568aa7e2292256982fa69414fc420beacacc292a95591db1c9d5435dde4b7dc96ff7fcb5d7f22a50d8f46a378389734016461e61ece058534d2222e5b489b2c28ec80d7b7e18b3582990e82884258001a136b1d22e091a4f0ee7a9d15569c207fd51e6e20e3907a62b47efcf8e3b4cffbfb1832ec485fcf44a16e559163e1d23617cbbeb10a45e5edc48ae4f543d9090b1834c34abd5228fad07ab9fff2d116fd7c17d9046a1a60a0cd1bfde28c936990372f08212e781b49d9039f9174be1f973a32c4e120691e795f168b92e4b7e272cc602ea38327a5a304c61b53989c58f799d0c0e0faf22e45190a5b49dc6a1d625e0652686cb5c0c7d9c03b5d82330b2e32d4030be6a6cafa44d152c14180847054e84820065cda496c8d3230d60753513a37db5cb35d182d7dfa3df429b4899a40c96070f082e0766880f619a20b83b44081d82ec521bbf08538488a2f0ad4942ba2d094edbfaba451419f1095fc48fbff0e7496e44647433baf1a410e41dfe72e009c2fded10ff6311ada828c4873fbf5d28264f4a3d09ca932c192457ba2158f619766d2ce6362673c4b74fd16963f149d49da29438b5f117850041100443585b33c3aeda9be1cf6d22d3c66f9ae8c60fe24a59c7934e10ae2fcb93424fb28fe25bce958f638fdf9f1cca932678db3fe1237cbb608fc9c7753a3cf6b94992097978fc7789277df8228cf1d0e3d9e357806ba2ff128febd82c7b759473f836e6f213be8db69c26513b3505f96944ed9ce1718733d6cca876cecaa2da59d676daac9dbafcda59ce529c6f5fd3ec97a6b6d257ba3781a9d3e4f04deb75e3d7524cad6a0ea69e721bfe20534b7530b54e4399fac96df87f4ced24337593dbf00b99da83a999dc86ff832966b94ddcc247f8c52a3fc2e88c4bfc3d5de9341bbf9b8d7f668aab29528130452233dce336fc3433e4721bfe12a638858ff01f99f853e05bd8254af9a0de69d6c46437cdb41b84a9f79069f7ccb707d36e21bb3b98770799777330ef2e4ddf31d33750d626cd708f26d09119e8fe31ed0c3fb4d0fd322d99749730c71ccee270b9a7a727371b5b8cab7439863fb761dfa009fe19fe46aed1376e85e69835fac62d15cac72fa2f826569111230e96982d3438ac22091b1a8ac03065061a70f8c35fc8e5014a09224939c3b7f0e747f8b59e85bf1b54096e4b6648917d863f99275912a5c0476859c5b00273c52ac67ae604ac7dda954ad799137a57db68f420449b3b3cdea8a0048512c294b0e629ea4dca02f8d65a8b8ba094a7bdea00df4e24d064aa593fb29f575b3b4120da5e3b95ce99056b2278086ea32a5b74a0828507ac328983044af889d055f3c0c19a076ebb698bfb1ddc57719fc5fd1a95d27e05534afb368c806fe0a38052d2f011cd6b1848954342ca1a08ee0d1b8323e56310a41c8a535a54cb8034237a235a0bfd7673e028ee35fe8ce66b7c6dfe9821bc5e1f804db3b516afdace75eead91816cff01e850144d8750d2c8f64f4163d684a96cffdbd0d00d70ddfe493d14457164a7d7d5f617801351107285ed8f82545114a58104cff60f0094d7151ab63f062f1445d11c16cdb4fd2f88d1056cdafe48562f1cdbdf026dc6836efb57f09ba365fb9ff0a128aa036666fb9be8a128dae3452c6bfb0320cd5868d8fe2574288ada70c234b6ff518aa2288f1ea338046cb2fd49fc5014753a02d3b2fd29f0a128ea430a7966fb8fe8a128eab4c2e86c7fa31545511cbc91ea86ed5fa4a228fa84054bb77f0d47d62ab34c6d7f131cad6cffffa128daa344511e706259aeb6bf883c435f59e26cff1079455154aa041059f46cff09f28c084c4b8fed4f9467aa5eb2fd693e14456f0cedb4162cdb1f04eea128da34b4825ac46cffa11b435ed66cff998ea7b5fd3f0ca511b5e8d9fe1e7056217cb1fd857e288aa6705e33b6bfcc87a2e88efb82d863fb77f01e9ed792ed1fe42b8aa24fe516aaedcfc15514456fcc68e82d69b67ff92347d09638db3f964a6da961fb035dd584ed0fb3114504b2fdc91445d11c577e906cfff1a2b32a8d65fbffd810c56cff178eab1facedaff39aa0d585ed2fded8d230b67f68e327e6d8fe20d2ac069aaf0e45d11b434c1acbf6f71444d34bccf6bf6509c24c4e79774dbbed46e2fc659e34b4fd6d88429bed240566b89600eef726617c44bf264b19d66f6f4ee38ee83ff18b38bfccf0c50e88e03bebefb02d8c838ca2fa55782dff00438c2f2d3ff8e287262d83b55c0a47c2b6fc38f0459b9be1c01f33688622f8187c9cd338f0fd5a180776b598e906a54548ab33bb295da34e061a4b9fa830fe9ec040a5098b3e277252244ba8dc00a34eb842c5e995b4a2a9933e548290413add939dd1e703a582e4e521d501b16243840a94d72f0bdd32870a16ad75590b10181a5d40f287d26083ba67a562f5c2ab88b2598afdd85c31e3c10234fc758999033b01f6bea440eb972c54f6c0c254468e181a4646972a2f622156305d020a0343499514d603d11e9e3a799caa38e110652306e8c7a64f951ea3aaf3526508a851a63bbd3031821c014ac330515d6290428af8f9c884c91293638da960aaf4783981678035d461e191b2a30a160cd4812a0c3d4daa6895413d7506fa65489518c65bf5c34be2c0a670488113933d71c10efab260d2b5ce0b258088fd7abd5f183a3b22b00fbadfae6b6d72b5534bf487b5b6c0b4db6912650a63c390307cb0c2d8d074ebac59e784d97979a1546dc870f991e152c50bfa998172c5646a97a6c34ffd81e942060c971760baec762a843d1878903d3959705ededb89a99923315e3d334676bd3aebee0591c36ae60a941a6787e7f5e343bb4cf5ace8d8f5293fac5ab0d61f3d1a3aab6c563e3982449c5042af471a29fde3f106caca92133d2d2e5144d4ce0d98cfce932b3fa1ee0894962a71ede1325aeec07465806429f38425a54aeea57360f8e64059b9fa218166dd9a1c6ba4585913d4e325a93164caca16a8fa96a82c5cb1f21383502b335396cc287132e3a467650698cf55335050335e5e693b49ae63b2b8fc3746ebce3a264e94315a28939ea94e18b542d342cfaaf7646506aa297bce78286521b5afaf5c7844d6ec13c5be4496ecd3ae1ef669abb0cb66f7cbbb44b54ee9426f58272a419038144b05cb073eecc33eec3369341ad1041384082142c49bd934b15f33e8a6faa9d4cda65eddb6bbaeebbaaee99aa6699a5eabdec5f26dadebbaae6b9aa6698a732ae24db3562b2a321a91478cc07e4770f000df40dd96fd71e5df6fbda9cd36cbf7e94e5d755dd775cd6b51adc8c868c4080a282061cb361bf66b2b91748a729d597d2fff3cb0d9fe67b39fde2a7f33cb75739f7f6208e65fce3f9c7f4646d79f02290a966c7f125324c86bdac86b1ea925728912d86f090d44d3e0e562e31a18dbb60d8a1aafb807f6c6daa94bb007ea6cfb04c1de098e00da165b6c61af2b298091def324a197068e40c94159da20b6e53dd97a6266df10dbf29e4411b12def89136d5b9eca07948a6dcb535fd822b6e5a96372a862d944dbf25421e26c5b9e3ac3890b591719da96e7a4b72dcfc9bacfcc73126417008e7e916af610d54f93243bc4b6bc264d3c263c2677b69a4c32f942db96c74409886d794c7000c1c8f1270644d30068e307dab8769620850bdad1da27ea80ad956ec3f82db6165b5992f537d6262d21b36dbc1f3f285da2d07064007d602ce53022d5e6cc963a3aa440d9b1945b53aae0e68d53112d310c19b5eeff083d7c68fdb8a1c48ace004d49f6fdec379c91d48f2ccec893dbecf6d7a5079a40c98a22841783066e8332328ce1e211430e8b4cca2e75b9bd0c31339526c90e1a5e7025520b303860efb80fbc616a4e0a679ec849b284c8951b76e840b1a460858a0f900d6e6aecf4204923058b9b13b25ee843278f962724b9861ba844e9d284478e9d3e67c8e0e8aa91a686902b3edea5810e35bc3d2c1ce1d28252931974f6f4518247891e2e180d5461a0cc4026059e8e061a1e18363488e4bcf4e1f3eab3cb13030d4c685670f244043b17ec3411f2232523f2b9a5a00aa131a6851c5242392d36ec18b32463863c2417942a43ba84474756983725b0a0ba51edd8a0c347cf807d31745c6c6c39b9c1e65962ead36381c7025428833ef2940a89015da304e1012506a82d60b4cc8c31480088d49a1c8e1856cec4e498e0a3890d1e43f4cc10dba324830d9023768abcd0d5b4802168b46e5465edd070613d575db2b4682c6561010912f5898d3d4650d0a3fbc00b797649d85ce9a304e7d9412faec4a981c99e364d38fbd603229e385578c0ac2908794203777e0aa51c1553d7993a582618c1dd47ba7c2c990dd8c1eabe5954d0d3654b1b2653948a34ad3193828e1a5dcae0eecfac2c98771b8c2c210c43ca8f4b6c4832b325204c7240c048532e8981c5d07234c64081ba602921974c0012c375471767eb49d52f0344563729d488e3630d53950f5551258c784986790c8d557dfaccc6a52cb07149499078e7c58e25ad5a172d3bc4bb44063a9ba185c38baa9810a1d19ba431724688151bd8cc888acd5081f295185113ccd05304036d46ee45130fe29d18363a7479628278474618a318738648fc82f1ab41aba969852b15d8c127090b6564a063e6051ad997177382dc05183366a38c26b4ab3563ce496d4619a31a1f988608c978f2f005069f204ca62a0618302e6876d89249c145f3f2dc3096d896cc102c9bc3b6648624d9e7cc470014f4c2438ce825470d286a48de14144819d1876d7978aaf478219d947ac4b1618a173a63d4e0107f2a5a90f1650f9b3e3c2021d663df7240bcb34fdb46c0551e1b51b4a8f8ace0ecec2ba74a579e1e2dd0b0410b97012976d8a07af8b8f025012fbc98b1848f9116e47c09abc008d9f24d5941862e4c706729c2b04d6429c3030cd31c1b7170f6ed06c0f007b0558e17b6e2c84be92b94767f3ef252fab6bc24b91daec8be45b8fb8619add119bbe2873510b5197400e8efde5a25fe51b5277e1e016cca717c0b5b8180144ad8933013015825d0f823698e3f5ed8eb0d833d90693788da0c647b7c306733f03dfe6bac6dadd2f6c087991ef60802a9f604f817f4294f10dd3fe8388ed864cdda13632dfc1faeadfff51ae86dddf6faf031ad01fac3308cc1c1375c06b14170806ded090b7b80c55d742c350fc9d6ceb6766a18075a07d65a80293eca6f410ddf6cddb6e6842a4cb67047a6d695002f9cb0bc8821f3bb34e8aca0832125630ae7e6afc18da1a8496c7a972d79b85e24360b30038c16eefe6ea02137926e4ed003874d17564f0d56a6702494088223496cda352a6851716e165de14618894d17fe864f0a707024fc8841a1a9e80a4742072c1cae8930d851c31523b1f9fa71152609ce4dcba50d6e04140e9b2e2e9c150a8a23012351c281c46628f5dbe1c38d412436c315507831851b33f60bb31d3c842a0d45509c9bd60b131c097e71960bc2b8d0c3e5cf79c482c385cb4a2f255c4c09d7e1957042af84fb806b340ef087f89570fa95704ab8f2957041af8493bd12cec32be19470f63f98764e69ceca2d5907f3a1a4406a52c64c141db4c047fe25d694ef7fd50f40e4287fcc1142d895b95b8194f2d6aa004086f2bc4f4c3b250c6536af0e1f4d3129ef9320a4741dac61b44627896efc2fdd7e49ed44aa3abf8d715676e3b62eec42884e695fa84d695f05b6a63f688ae0b529a5ecca909c1265cab0967d19cb6d2088944ee519feb83c4936c33f757eb036fee9e199d5deb3f1e752944bcafbb21f5fce9344b81498cbe1dbd1e307d3b02abc0ad3846196dfdc9da96b8f8a90ca6fb7cecdba6ea4fc76d384507ebb577e84577c0b618fdf0060e2f183597e3bf1f841377eabe0f1835d218a6f163c7ef009df901e3bf9ed82c71f36f90d83c70faaf8168047f1f82d9843da27982b013e9529ac50982df011fe00981ee023fc189897ca8ff05f60de2abf3bd302f3aaf8087f05e66df223fc274c00c0bc527e84bf8429e2adca153fddddee3aed2e14baf167aeac991bcf585ffb1f58c093ec078000dbbbbccd003f03ec9e71b0274216af84bb9f9ed6fa364d8b612d9f22601aae41c07160cd621d762dc368afe374dbc74e595d51aeb015f9b10529bd76ca664430d8858140340dc2edefdb6b2705f68e6adbddb6b595244992ec6d24bd750fe73420a841321cc3525fadb50581f4b5800213caf363ba768290413795d4ed4a52b65f10942f52afc86e1bb53886e09847ec770cc10f0d11bd1a4a0f53e5198bf576ecb7af88ab7d5fc49527f9fbdc16cbaaa1b4e7dd9ab600bc633e7cbbb1ad55c676185429ef054eb15f8996b9d82fc625a2978994f8c8aeb877bf3db2af758994e50d12d2c66a810f9a3133c8a0d2c0d91e94122e19603c695205c93e0b6c1fc97e0a2eb41b25be37042b2eed46e93f922190019a946ca0df5b25b82d17291f9b167e7e70db17ef25c7b7e2b5f80133a258ac4614012520ea0b3d3cbe3051c302cefa51f9319343ed24776c8ba675b169fa479b1108e4c6f80510559e331c9e220099d9dac211b49ee11951074898b9b54af2c77f3df976bffc453ef9fa4752fc2b661c92b56b6d200edfc5d79f10dfc3c79a7cbd6a44e1e3d7abf0d9ebb0d681fbafad55deabc13547e09704e0b53617efe90245affbe3d349d8e2e7fb27f467d88ad75f2dbb1be420811a2452ecfed820be8b567ccf2fae00b75b0c14be0adf59b4f662a0b06641705b23fc63b4462af0b3904929ef9f4adbbacf6a4b1a3f0b1327520d9f642c4989c465e3478154c40763fc4829fc5ef0c568952eb616dbd8bdf7c6624018871cfc31ce3309d77d862b26e87838a979b3a3078e17aa00b9a2068f1a3d53422c613ee244cac53a25a6284d2b9ae7776587528c2c53388a3c95c9c2b2810268e30ba8d199bf915a612a47560a2500915a75dbff81ac329c92e099928294295a098ffc630c677431a1cf6882e64226460cb75104ec62230968b43d9cb1596802478bcc13115e0bb813694a1130c976725bde14de141b7540f0158afaf57abdb4183ab8ae29af2dc59081c245668aefba0840149e1f8e840f3edc385ae1481871638d4978b7d5faf5faf9f9f1e7e7e7e7e70441b65f9fb7ce4fc334fc79e35a1653fe3ecbf7b7b9bb97315f4b34bbb2f28ef75ebff6defc17ffbdf7cb59c6eac881df6c15282b94ddbe9f813abc2853971745b7efadb32fad85c54779c4663647f3f15108024f79e67d5b6ca4a5f2e98310cd7f963821aad2be85dda92025a57d1042374afbf649739c990cf04f7a51826f2ff9f7de8bdf92a1f92020ed35d42e6a2ec6276b2ec87fd54e25dd6eb1c3578141e062fb5bd21d04a41dd69099466b74ea1819e310d44126948584b05f21215208f47d00b366e0d610b867fb83007fb44ca361bf34d0973e39413da552ba300dadc2aaf02ab40ad7304dd8739b876bc815ee09bb42aef027328968f813a7442971893825ea4429a22aea442c318de846c4127b6296e8137b8e3348dec72f6cd2cbda7aa8cd208d1ba47bed3a82ea0e5cadc01e964ea7d3e97461aaa538e8f3f97c2995ae2aafb6a77103dbb9a696333083a1b8aeebbaea344dd3f405fa7ca00ff4891849180c0828162b39640e1cb05f0eb20da2600e4c9f401d38a5b14adf6ceb7f36ae704d41354dd334dd812b6805f6402c1206145bd7755dcb344dd3f4833ac86442421e3c7c98e5d90cfb9d75e8652cdfd68f4ba7d3e974baf0b782e8baaeeb0ae6c0147c0275e05407994ea7d3e9843cacebbaaefee66c0804081a8d88688210394408ec3744368376d64d657567cb563f2e54d485bfdcfbfd32d6d68f6b08844ea7d3e968e12ffc4d104284887fd3ac15e5a222ecb788c3fe61ae9ccb4f62afd7eb893d1d3e72dbcf74659eca6adee5355b8510a1d3e9743ab3565464643462040524320912d82f09525b95bd1996d8c33f9bed7f362b4b8db94a74ef59eed3fca4355ad3aa188a3d50ec6523239dd34cba49e734aa9d74aa51b7f9c7364e718a6f1a4db1d5c63d8c857d784ba3f8a6cb99aa51add56c026d15f886d3355dd3355d29c0a9104c42bf80607b029cda13fe384db5ef4472bda7343e91f2befdd8bd37ebc61fa2b77dfb620866fbd87f484f54adb5d65a6badb5d65a6badb5d65a6badb5d65a6badb5d65a6ba3a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2ac8d8a8a8a8ab2362a2a2acadaa8a8286ba3a2a2301e329a6e23b7b5a0ad9d2ef6bd42466b445e9cc150cca288fd8a98c46028ea577ebdb0df574886faf533929924b15ff287fc216140b1329725f65b92402587a00eb22c9361bfb21fd79e2eae1fc804a2bf0e289387dc87593a948786b0df217427856aa756ad2adec259b80edec27bf00ffbd22727a8a7544a979a21c8b7caafb63fb6ca39a7d9fe60ee85390cb1df30af455be5e81a28c4388ee38feb7cca555fddeab75af5b07c5845bd87a2288a3a966fcb7fcef552555555551445511415c18103070e23643f7db94a7496fb74b5ea61f9d6755dd7354dd3344db14f555555557d4af5ddea561afb54555555f5853c146d95233aacd7eaf62e966f6b5dd7754dd3344dd39f8fcb51cf79fa24a6699aa629f6611f0eaf5f0987ff03fffb1cf8d75c30b1350eeeeb9a8bd75f17f7dd5f0987bf45072a5858539b17f8cac7dfa203152c1c88665f84bb69e073a0efa6d92f0193fc25f0d75f0226618d03f1ef9740bfffcba4d91a0dac41c05f3f07febac6c17df14b20d690b0518e12098c1ac58b3fe79c7588712d877fe69c33066dedfc804490d25a9c33061d6b51e3238c31b6d662077d3c328ef6832d82ef0e5a533441986dfbe0db1a0e9f06eeec38fc138777abc0e40ef39f38e70f901bfcf1c424789e25185ed80a2b92d83cc18db17e91d83c1d481bc139875a7b2e8086adc09fdf5e117f075e04c31063ac01aea1a82121bd604e4a133631a5d1ff98452210c439bf2c2c35bedd1ce23084ad087f4212d4b157eefdd0b0550548f1396fe036ff0ca240ca6058a53461b38121884269f41f8a9948a6f508aef7558002aaf68f167764b739ae01e08492c7679f796b9b010d1b5b4055de325a8333f0f0fc542961dbf278592393f2675b1eaf0d8f5726eb04f4882a63dbf278eb0b8aa7f2a644f1155a5df2784cfb9cd1d1b3b2e46a0275e8448de4c20d1d5c6b74f0c85909e239d2e4709323ab9c09f9e194e554877795ded8b1dbb1fa91dda72e1d7095e38d9183020d63d4ec13e98718a41a25b92d6f8c96a63151e68c81a252ea6d79639cfa8c9191a60cb7e58d411d20864bb62d8f08179e1835cf78de7dfd4ffc77e6e669c9ec7a5b84fb72335b7c947f0caf9933de2376775cc65a6b6f17987736d31a63c7a05b6bed4df251de17d98461a85f76367474c100b67d33e39cc18c6f6a45f2c60bb54730eccd83d9db8e4a1ddbf5adb17d035cdb6003249b4d458a221bf895bdf22d00686f308a64381ba2dd9d6b17bdfaf151b88b8ae88ba2afaed1bd76e66d6fe6701dcc203b0e63f0b567a6bb2fc9190451d81321ae2181b593dc20e8a0e313049206bee79208348d4edbe70fc19d419d1466ed201886b59901bac85194e90fdf1fc4d63ac9b7b5d9ab70d1eab3745b58b30e82423bd794b6d661dbb7f49efddbe5b7991fdd77a6ed7eba6e5391beb3a7ab3ffe64647bd3c672742edf26f00b5028f2d07a867e6d11fd8cab3282acc0656948956ac59560b0c0864d0d17ee4ce9e36ecd9cc101e7e7c2963d5170e7552d60420d3eca700913478753b262260d8fce12a7a52ddc79b74e34318149d695b58522b8f3fef689ad9f1b28415680410ccebbb459b22225eac75b42669f469b7cfb65934f5afd361946cb26c5d0b011a08413f9cdab8c36cfea84fda3994acbc548ba4faf029366bffe46428e9a279c7e6b57e8f20b1e9df428f8854adba48ca2e88b1f8ae2b657f9d1fdeba50bc5c67f4d283d1dc7e8f2de5ed661a9512a3b802c0db6225f2f6d367e109a0993d95010f6628567e39fc1566099ccfd3ec1d225743fa0a93a61e3f720a24a4d1fae283c1d6694a4b4ac676a7052a78707a763a867dc662074d57409b3f1cb602bf06ce3244f55b45c69e3a34a8a2b6d159123544c9cb4783cd160c1b431c618638c3bc05660cf77441a3303b0575b9cb6f3b6acb0fdcbd9bab4fd7f5db3fd6d281cd9fe489687828fc785ca765e971256b84c5605cdcf4d2ab316391b7f0cb602739527791f0c8a7d121921609f28b6535929a9e06cffd9142f8b9bed5a8e6c4f8162a9a1002bdc430c58e0ac702b51e14d1a33172c423050860e93a7262a3876b8d27a890105831e1c3a750ec842631e6094c058e0db0cac152129cf99c5ea604694f36bd88a7c7b1be317a98878579250b0fd43bd77494a524696b2db6bb3f16799ec0784c79b3736943182e294d29c992954a913346170fe98053ebaaf616caf5264fbb8dddd3d694c79ce66332bbb8db14c76c54cc6a48c2c65b7828dadd0d06bca939c11f176bd0c9ec8686faeb4bcddba2d6fa7ee73bcb3ef9bd896b763f2cfe36fdfd956e9d3b292e8076bdfa7599126da6f616f63ce1e5dd2de7eb0b4146b91b4aa776ebbafd533e579811b365edab8460b7b3f5dfb7631fd98798f7674f27c57d5201925fea98140eefcdbf36a55b5fe60e19bb6e24361df0ff3db170bdf2cceb7b57f7e5ab5273028a2c729bfdd26eaa852a5f124cbab02a533dae738b5efd81bb148dc39fa9aa652d569e763fd6105a477b13ce97436fbfe3d41a67d825bf6fd7b8a7bb693ed44bfb8ba2b85cae80ac53eafce4dd65d3ab1cfdbb5cb2fffc5f5836a156bdf17b9342a72895c2297c8a5519dd3a9c8a5755777d5bbbbba3b056e41425281b4923f9ea1bac31db85a6d90c9934ef009cc813930772d2e7dd2812a9b23b087d55ad0292d0ef6167b607be304603fd6682d7016d60256a3f9cf6d176bdfb43c1d4befccd3755b876f7a372bd5b7e9f0cde2a6d4ddaa557c74417005ad808420c2ef87cde003bee29536820bcbe5e3033fda45996cd676d3666dd429d7941b3ad2464612de0e15a6ec9d2a223ea87435e9d9e169ead4e449b4fbe436a87b5f6badeff8fb37fab7d28b247266c48931c38b2c383b7543093e186cf4aec01870f7ed335e30564b7c42f01fc1df830f3cf0a45ba359b779f62fea2a3df8c06680b10e4d6aa2eabe28f998c0b3dd843d724f8005f6f63704d88cc5d8ddd4b4dd84d35da7f870e75181f2983bb6265e6cdf12c2081e0499a8a8946c76672d0c91b211000000002317002028100c0744921c85712892d6f814000a58923e64583695c8a3d15010c2280aa3200a611884100300320629a58c328901af7673563030c2f6b076330d324136a376ba9b9304684af80ce0f103a650f70f95e22564701e2664850f830cb688415634633fd57b2eee3515ec04789af8ccfdd3804ec1b0d4cf0d6ab81bb4d058930027f2c593c422857224cd21e9b7f9b3ea989b2ded11b102e6893169e9cc544da87b485ecaa1513937be55046e12ab36a1acca56ba782d4c4cb8e1655b630711a43308ff1c84fab41bb597163235b29eefca09c712028961555003151cf3ca4c90bc591b6ee9c40b4a3824f61d61c921fc85ec5b45a42c3547d36ad9de822bd57af288e2d62273da847b2bcfc455c4e7e4cd5e4b0c4dcca61ddd2c081e3bec9a328928f1119a0d339bf8000d960a58889eca166b360242c18532e263d0acddc801b433a0c6b238d9dcbf35850e16280e0bafa2d34bba69552560d10412e4d1dafa1049c80631c751615c21bd10d0cb08e6e8bc1095326abe0df23431c94f8a87e45faef0a33a7d9cd519975d3ffccaf666a8113f249e889dfd3c36bafd3ea16abac94bc79dd0576062206190edfa2a404a58cf3ca9a601bc0102b250076983d23dc4a34508ae39f6e38c63c3651ebe1f205c9c7581e8ffb142a39712a7d826a06120d25116be90fcdb22d69da1a4f6eb1d5c966b74f24350867059aa168d1d2cfb5d91b1872a926107a3fa258df44b17e71c035b8cfa05f837c774a1707a835c3ac7b1445866e8d4a594ffb83f341f611630171e50b2cc01dd686cba916514678c5e6560eab089590fda0903b96c8436c9a004c0856646650a935e3a477186803f155cbb052f2a827d9cd337d3ad58d2611736325d151b9fb188156c2ea49ddb027afb69c9ff283c4e6fd168607c385e36a5bb79b919e612573b6418b450f7540f90efb0b2aaddf444c562f00c2cfc0843f68c905603fdef5a8ec062f3cdb2457dc0fa6807dc06de588f986dc0573bf943c53c3e4686ad62fc8b06b857f05f2754df1d211d8384cf25e13a5c279343f583ed275946c8570feed57c789c2ca2484e306642c5749541e0edce16799d1a6b4c8581573dfa98d0a8961a9d57e6e6e1387fad4a81ed1848ac4a3c105e31f686f03adae7a3dac2ad2298aab7823fa073806be845bb93c9e4ec0257c0beb96dc5d54036922cd5bb9fafa0f70a40ee6b4bfb61f94a699ead97e783c8a4a62ee09a932c7410fc5ad880ca902949260bd9bd800855d04bb632499393b35d12b03f15d0418dd5326287abd2dd238c763dda5d714fe19984ca996284a5691065abee69940f504c4d119ba3433aad9ddd2cc887f54c3ed17525d3403c46b8b1e79d991dc38e80ad1586aaab48351102fec2bac620424638cd3ceb30bec919651a6f2d10c86cba92994f0ced44fa71d5c9a1130b642e8136733b604819614e7372f8b8a3ead549bfbee38f83ede79757e3d15805f08228117ad3953f18d71dd6bd68e4c0e14be6b483d1043a1bd49c31c07c33fb1f2f225f083fbf154bd8f4d7ba60452042ff6bf00a62c4944c341c122c4d4d4388b6619b4a5e1c3f04f30ec82a9dcb63194eca31255c563022988b42174109316d07a6e68f219caafd07e56b1ed11ddba1f234d3a29b31bae08f9c371e013cfd2dce73e41a7fc79b552703f17790ba1cfc8f3c67890ca4914fce5bee283c66e73ebe14e5bbe879470c0a0ccccf77e8ed0586ae41b97b487d009c4437ccb80da946f5cc879f1e80625233f39fe1724b5df828b606864df9c6334220bcac5bbf931df973409d026dc05ae24fac144cb5c342c114768603a4d85b566bc796f36927ccdd846f3544dd050142c098c1ab40b1d6eba265b9890458d9ef40975253870d6c0300f8b8355a05e448b0ff79bb1958b099e982b50a4ba1e5e910956fa9015e882497abf2e43eac1ec588efe2c6d64c915eb132899086cb0339619e8ad0e821bae267e79a284eefc3e70b7aff3b41f07d16bc8149f1340cd72da77ce93952e35f4665003e97c08682f87b42536e729e28b7af1d7b70c9e05d583dd82cd4b02a6407f8f4b0ca22e2dc18fc42799c189e7e52f912e12fa9d0aea620e8023c900a833186250a6fa82615252bf0e24cea1c7345b4da26f863c269fd174bf3f95c1185ab0182e87b26ef6d8d7cef79f043f1c9047aee35ec41208361db52b709e696ad768f90a7112286563121d007f01278d18b88502011373c66f4537aea02077576ebdab3f11039f099343c2f7ff764df96bced34937675e7a713f640464654464e077ed3d91b3bce01f0b309fea23bb04dad7d3c3297aba80c12c5f30329ab8d4d152122413a7ba70087a88d890fd286f298f8a00060cb27ae859448bce52aa0681435e2abd5329d508c11ed075537744d2b21d527f310e43e40cf8794701eeac7d94467eabb3e7ff7009ab418571852d60a54aac8460791c96d94d98126a9a44457751a883d305c00cffcfc44443e4f2a9b7b5e85c827be0b5cb69dc11e4535a1dd6242166c5f290b63e6846320951bca5a270e9bc9d7b2e6b918bcd3945b3651cfe6f7c802b78e8e74bcb984f11314d31d56b2a00395510b880df99fd51a7d986218ef16ccb8033d7ad6301582f822c74b92492e990ade9ee78209d619e2a140948464f3c82a1e1774004b1ce11a20f83fb14663c7ae70e66ba7749a02d8a424956f3a5d99094d770ec41801fc113690f21eb03ab6a04898b87c517d5c08fbb6fc9acc145274ff4ac530b2f4d3c2aace493c87ab860ae1a0458f8a2f3ec562ce83114970f6212c08da54976f018e62b28107410bb312f3e2b48b444e32bcd591246708fea89cb5fdfb4d6a76ee7fd58dd3047a4021da8e40d1a27d86ce5f8096a3d49033ccfcb627efb896106c2249879e6375bf051d0f264f9bd3f5239eea72641a928f2aea770bc6a574e7155c30510dfd9129b2ff174e330fdda1b193117bd5366dfbcb600b8c664e810bbd2e58cdac90d63389c5af93f3d6e452dde42af0167b19f82316e3a5a7bffeeb724de2cdd3ff2341ae45c7f57e35edd2e695fd5b735ae8a19ad9aee1c06567b2320f926b0b463e4cc953ba1e2c0f23592080e66a387254e499e750a32c0200a84f354c33dcf777b114ae8a35de93eed8e378aba20c535d5fe4507ea73339169074f84c4270c7eae51d3ac8f42371aeeb4ac66e4022514520d1937770b21fa63bb01306504793936ee50441d8afd0f572dfd1a6bf5c95fd8fad8c7b19304e37dda57f3e8c0716207f3ee548e0ab714cfe6dec63c35f013d15672f93b5d94aaaf389dd7605e958eb09c76dcd4fc0fee8679c4fe35eef478fcdeb0003cd5f03339fcf7b69daab971da7aaf3da894058077d4922a6f9246d4efea9879bcfee9084075e60bb59a761900043b65187cc9bc561f8120c00437d4afc882598895a2c0835ac29ae007c51a5b79bf2cbdcb316052db5fef39e179ece3f6533298f8ba41fa5981b5fa4e986c5419443181ad7c2c3362043210f29877c74efdf571b86c5a17c97cf6c62195bb6687cf0098a9b3d66333f7403c0ce3f71e8c3c7c537dc54f3280c586b1b4151c875261bea7a46db9bcd8eedcd5037ec140a327d79926afb8819125117f0fbd011745fb9a1d719b800bcff3bd52ed4d9155c668ec132a12f3613ec843a6d19de06c64d55570fc68764640df1323b4337186063f46c4d91aa8a4295f5b73af841e66ee5d585555400f78076e76cf3c0a3f57aecb93aef4a5f27c4b2522ddae410725e0dadbfb01f8c362b50a809816f5d7084f0f1e0363aeeaa0cbf423e9ec3a8d2a34bfd07a9ce289f9e948719a04e8131aca29f61e9bbe75852a1ffe8515cb569f06971af2942d4682dcfd90481a4ecb5d51ef55d4a8338364cf8a53b9820d848da0da9498b6888dd270e7f6dcd16c8a601d3110b198e82e1a08527241e2289bf706c21d6ff693b9d0542960c6a85a3ea2deda488c44ea0b2a0620acdceb3ee0529dbbfcd5ba8e74e6ae139e9ee27ca9ec99d317aef6330ab33fd3d2bb9cbd1b354bd41dd3f593d09534fb1be5ff9987fe37c78946f8520d00f5c0fe87fdcbb6d11c6a751c4f75a8f9ea824fbdd6b69d6024ab19dd889911fe4be2a7d8e361ca8655140b01cfddfcdedae423fcd6c843086b61b020f2f0f662127f40d1526c3b39c8cc17ae9002466e1e13766bb029f1560abddd7f92575d697d85ab91ecb34c64ed6a09d987017d66827484c52c619e2b62674f67a511d80c1ab9179faa0d9dcb1cbaebe3127c4aee7e1562a5915c1e8189855adfaed7ce4f93649cf0e4a7a2e92cb942289c4b4481ea04751ca9a21848b15267dcacec11a9bf960afd30c53d808e3039d7e114807b5803fb36c059562362f614fefc6a97ddd87522d87198c20cd3c0ab98df8b69291de94e8c7b8d8f164fa0d5ae5d27ed12e530fde7517f319b4ec7344496f4edac324f63cc4e5b88f689c038cbe3622938d6fc1c6875824fac87b51d961f1a0e2162b01e98c1671b4af027e4cf09b82d6c412087e681acc5e20c42279191e39a024126c1a32cfabf82d58b3dcc3726fca5864dbe2ce05b9c57f2b14ac192ae22ab47f240abb6297dd02c34486e4f1ae3f18ba6194359ed925b316684806e6f4a4c89c5d5bafe2618251650eaeaa74d3d9ba6d5dd95461691dea287b33ff75f2953150061281e514374823b40fdfbb9efb019915bc011f98ccd4b00b1ab34610bf07f8b67ae38b92671d0fe7705d3e1244e4030f5d97b379a5600f4d6d5966c53752cd51f83b0633cb508fe9595afa33070244dba4e656c1624bd57430b0c07ff281c484bf2f544d25583d40b0f065a78158228c8e5c63c8b89e91f3311e62b9e796abffa9a8c5aa0dd2485570ab4852941d3b301fa312ec400a20a3a0a1c6ca86be27afaec883e4c620f47c9a9b667e1d839776790ebef81b8226687f4b81d10c84bc44ba941e839c30c8752f65f948468c2263897c5f716ef60d31aea0694cb152b518f1f38d9e363eb4370c54cec1bfef3abdc29cf8f5dc1daa30a3e1783879cc4b9dd39c58e6a2a788868dfb829220a1e1699fd5bcb1fe10b43fbedcbdd2a2ef9adedf498300392bd354c6ef1b587aacd4501808e308dfd9b0e50487f92537aa1269f056d4370f4181c8cfa5f98bd0ff3b1bc56b3eaa0543201dd4d03d630a5aa02f7fa6121268f8296d326a34c330e841c65fea35a0f1d49c27950e78453c13cbb563afb8ac287d2dc5978c4a8b471143f9f2fc99eb60eb39f220542f1a2b4b3b0a807893d86ab35276efecfd96df727375239909272e1d352c15416ba8c1b4a143192fb57104d2bd4fb05b311971b9cbbf3cf9d50e1106628e5b007f3832b7ba300891d9de5ca1968b9c7f9db39bd4ff2b51d054436cf4b89e5d2b1f4aed7ce2f10998c4cab03c51b42a3d716521230f1100f0d914b42014ef796669b73c486a04d98247cca0817ac5dae5b64c93172f6c2b9c2ef6de962fab4873e2b641a714d4a7ee7bfa0558eee87718d5f27bf794559957304022c0992940d84a7aba99b898f023aae30ab0b1538a4d95d2a2dedf1ecc621ffd8ab90be424fc2d121da63cea8a74c0ac927a5df4d36a38e054381095a0f10bdeb95a4b36a267ccd2e06b184dbcfffd2b2611e085ab33167089175ddac06f37c1d871cd460ed6b9683869efdd1482a95cb2bb6668304052c64ff9bf7ba5d102cb6ff09ba82df828120bbd28221089d0137b582e3771f62e8b8cc978f7dfc54368c15d661475530ded50008d1456ea26317b93f0b49aa653a6e5d58c282bd9ce0dc16a596d02966c5856149f8a65de45f381a0172fc13c1959fcb532934c5625463e4e457f9ee4a9faa6f5046c3a802dac71b2fb550ab665547bd9549fce3a498d324f232d1a76564700c97deff80fc669b165c1a00526ef5b593b970ed661c0f687ceb93a9a683421ea1cff828d004cbffb457aab8583e71022a19256011298cce5323430024391c0e415465fbfb13110612880f895327b611c6bf6bc06997e88aefe730e8152086bc0ced2045ee9f78ddaafba496977e2837a1394f126ae162248c3580e6fdfa6ca24968f319df4c111cb5f0c3a01cff0ae8928e6b7dee4a500a1f66fdc8b2d196eda6e3e6f310bbcecd142ad019100ea14abda0cac0fb4acec566026d77beb77c61b20875974540cd112d87e1a034cc69f4a1f4ba8ba6f5ccebb53579daeaa6438968fe82418a15ca20d527516600d77eca800faa6aeeb72e09aeb44407a3f969297e7a9117c8bea70e75655cb49fb77b3295cefbb124c62184e1d2e109c0013d2112877d4c9a5c376a9344fa2a0f83562018830e1c2a3404eb3b4604afa8c6493898d0d96a812b198304072368e89378198ac4d3ef7d2f31a157b6a04039e34f8009cc19f2dc9ffc04493774a83e10b3c1ff8a26a86b02f419f4fb3e0003cced74475d6064db2904a196000b044e118f8a6c07b5a273e51918f375c54920498e5282598e3a7189cf4b4807c46e407110535d8a54398d62bca35cc1cc66930a1eaaa6883bcb27b0511f65d757bc73b50fe9344624ef1bd0e7419beee6f08951e6d82b52407d74a38cad30db912bfd7c5942f5cc697e04edec337b86def3ec42b22bac75e2c1e4557a1cac08b2001d40e40404daa3a7b30f153f7dd0bec58e01ae5a7851f2894bb4845280cff70c77cb52009cff5aba04375160e1811838267de297f934f819ac4442585fe1ee9a4e16f4a62d99ca2fda924048e662bb7023a67bcdefb03d038c64c5f918e2e3f9e2bed73224142d4a8623dbdd1f2c38ea9a8341e4a227702d3ffc818277c8a3aa91f12c1c5197d8204886fe557402303adc0c70460cb2a0178fc9e31230d54e8251cad2398529f683b999a5ca8bee1ccfc4a1ce2c10ae65ebc39cf1d913ef1011ac3222815da8a055dab5688467d1b9c50ccc57a8ebabe2f8d45b644e6befda3356a2a324dd68d052640a840a425d32c5981e143923cd03429faebdbcc399e1ffd7d329e81508715786ac21213364dfd4a44aff97cfc11dd69689609ac8d52a92ba13c143cfe7b51bcfad78a48efbeabf5f94316fb9df37a326ad38ee155a048bad30b880bc1519abdbccfcab72ea6cc6e4d1f0d75b4d8572abe7bb4da8cd8378530774bae6c6bb5c30c9158caf56feee6772e9ed5223aaed4a0372782a4353e5780c32ce5e29cbfff652a4402e0ce88060f0815b8628161c04303c3f2046e4d9f1d30079178dce7f46cbf710e12ed3f39cfd4cfb45143bb5121390d1483b5546edb5200248a0295d0c243750c68e9bd4dd52a86a833d30013ae1c20338c452d3a990228824c1f4d288bdb8f0fb403ee493a2502d2ac749e1997355e6c670bd89fc4f66a3c5b033687e3ff0511965d4e27c0695d4d710487b9e586af0a6ee35c6f689e4ac01457a1fb59dba8c293ad7d953d1456be27d5704dd73e84ddd2620c655cefffd2cab87d106952fe9362fae2788842fad2c9c7c9f31e4bec230635ebe355de17963306bbd7fd810a7c343f89972e00586912c8c4efb5175ffeb2a48c4e677e9b84d9a73efbab812a23d1c4ba7c7c036ffe48a4140f22554bb52cf8959669c1f0c6c1c01ff10c77c6028e2f0e0eac58865842ca6f89c12342d934b626067c39ed828f689dfa6d6e5b8723a66b312d695d35c4803ceafcd8dcf6100de6f61523085aaae9c325694aa26a431d2bbc6943beca5eb819df0746fc10d40e514352e020e0477b6811304a2fa6b530b8a7206f1896bed0bf6013528a4c684a9ae63064a2c0d6ea8c9f2d90fbe311fa738a05ee280417e826856a5d5a846372d90ad1deb2c408413d7016ff341fdc4f0bdfc07f3dff6cb5fe11d146aeefabab18a57f464f92120ee0625f0fb6d5008c7c413efd1ff30fcad20e42c8a191c2dfcfa77e82876bac4a973c2edccbaaa99a9da6feb97935b4fe7d8a37fad1d19ba1505bc73fefcf6940ebc97f17535ec4cd7811e6131010dcfd905b18795389856451dd46c02ef80ed0e603ac612c59a6103161c69b1af31165ef717358f280b9bfcba5953923230f466bfcf265a2f28728ec515d547bc0c0676d23423623e119a24ba561c20927320bd0c780913754e772af2c51f63b575bd4d92c3c935a930e195bedc4d2d182e7e178ad2a8fbf4698c71e0c593d8a6d97f7540439fb2ddb9374fddfd677a3abd47c2bfc3b9788c01ed046e9990cde562c2c2c320331858054ca04f9406c1083819783bba9dac78e1d9903b62abcb3bfcb79be2da018c0240ad5871dc3943877025bbc80b01ebb25c5eccb29c8bedc5b6e2966dd9c513f25c72ced22c4f7a5e5fa803a5a8a956782665c13882217cbf6f453012ce4c5bc74a81d4371716267dcb8c7741d421816794109f7ca45e7ff0f20e2a1246fe7f14dd1b62cf9c138afe5440e79da1e06023c87f4c090f17d436d9c4059722cb0be64969d820fdbe9ba9bc26af21f4da3cae9fa4adf00704f0a439d5985d83b33400eac2cd77a6b0726a13e34daa58180e127a76c5b315702408d76b4cb2c469be91d4fe83a705d627003163422810e205c213a40ae22cb9e7382d29153a74bd8bd5eeaf01f0d0b7a1aaef274f85b95b46e6e20fc85993b6d29e689fd34815253e9d9fcbfaf37c3880f12139fc408f68765703c595788ba2cc72a2c6a112dfbb819f2060dd2e419de3b681fbe6c78e622cd9e8f1581309b7e4bda2806ac10558ddf6e1966cd6e6c201ea0940ccd9781981030fabbc3c0db137f83b78aab2274827db274fbc26a944bd2e427bb88d476af9541e1bb0ad0c71dbd4b9af7057bce609df0bbae1b30d040674115f713aeb02fa01b286f075909dc3d0b1992e8b884fa0cf1814164ad593ee6dd03f58141f65e658fee0b973518440da9efc3f7a55d23128993b9fe081c3c9a8d6160270c66bc4df56876669e301953e253eb9f86d8e5c7974c2d60b7e2b0e2fdccda253d1858f942ce06c079225a828c11058751ff0a9d158b0a786c996264884e702b11ec56ff67bcfd4607af19684f3ce3cda6c69ebaf94f1403a5cb61b00dd508b9152bdf3d4f830efc2d02b6500106caee61eb580bef25a64420e5c82f52f42de075a4bd9d2d2cfd8065c4fc058199791ebd85323cb1b0d1a3fbd2fc7b902b1ab449c788063f25fa3dfa1dece2aadb517e61f00c3c444e675591456e992abef43593be8342d6f6166b6e84ba2b57af398c6fe3d5cbe353e5cbd7e4d1227a3c085d96832a4c9dc01159106553282aa3998a2aacaf425ecc4363fd56269850f1e55891e94caefc71582fe869fa5e78ad19266f1257a8368f6505296f52f9aa120d2ad51d41145778d0f880612988cca37f75f2014197104750ea8f352e1af45ea11b686b20d788fd55e1fb63332f00d8cf8dfe031eaff5e825aa94a5796d744e8d221ca8e62801340c7da12073bbcd0e94a1c4f3876799e73ee3e01e5c05374046a9e72f1e645f1dc6b93d03fb907ef83ee99307cfd96f5d336e5f57f5648cb2f361424808e80043ef26ac06323fb459f25001b0d96e1c4ebcb12171a0127ea4171a96b375e0d1e13a17f1a41cda2bf78197c59d1a2e8d951a7998a579db9221b1e88fa80d467a3a3fe0fc85fb90a3a784f5b53ce0e09fbb91e1693799ee8ce72d2707ee30c9ecd33e57951ee3d41cd9ca7e84d77ecee58e4b0372062ab7d99f3cfddb6ef196548e067c082bd408940201ff47a89d4438411f21aa1a2e8aef2b519a0c2a8bac3a66d02289399d62a7d2ca6090090be58a4f6cf4c32c29c25e63cd917b188cd5c720780c889def9fd2c1527eda0540b6393c75feb68929285248f10afbc16b62cd0ccf68a907e6e4eb18c089e5ccd91afa7224d4d4f15e63ca4baa20c2da07483b50d867a6116809a347201043ed76ba111beebd4311509131ceddc41dcbec5132d32b1a5deda77eea81182697efe7bc4cc5ae8bb9e23ace1cc750b5b31a4feda7265ba5ce6cb354f64d29cf05457de7ab3066cee831914838d27f59cb5382c10db495a1fbe3bd628e61ce5090efe5418d9208ff587feda431c68c76fc7c94518d172d60e182ff3a8008f472171695c405f081e9e89ee56d993ac6010f4d83bad0a0b18a13eb891040a29cee8007e0e147ce3082f96637f1b8bcca0a2845b92a805047bc7d6154f87ba16a067794dc0339773506c060f566ae79a3a21c867e320ddd109a8e6e88d2d82e24ed7e194c0741fcb568d13009e569b7fe0ace5d7600a14d936bb0c99fe09fdadab7b8a2046e58504ba728d50f7ad0e098255d95fa57881f84d3ed697de94b82014cbf603c4dfd1cf4ff00d74ebd9a17606639389f526b42a91551a0a80ab71f7f5d6392941900650a68c7f145dc05499efd9c9e269a40d3bb34998b03240040185ef1961224fe3e68e448cd41030ddc1e9372af9b1f79fb86161ed1cf5c7e8b1e59ee33a028a91f64d0e0f125850321dcb22472e4010c3586e554a4bc0a22b33cc11d8fc3bd18012dc95d3a10b4a2ec4818cf29c12f80347b3036605af69f692bd5e02ca3c5b73858850aa45b116731d199fed789bf61cc4f6cf3084f6e93d438cd36bbbb83ed56dfedcd46280dd297dfc9e68dd9430791d03d1e7e13052639d1527dd10a9ce0551c9125a0f39855546ac7ec38ec1e9835d2b8d99ddb3cf3baeeddc1467cc796241051002a314bbb5d6c436b56a13a521a9f9081a9b6e74aa5095c12599509d24b40a00486d21edf1cb6850ba6db8aa68a7a1c76010e5b54a77fbe7da47a54b48f394bea256ba18619d665d3805c1abd6dfba9e9cd574b506f47193b064727469970519c2ba947fd8a5e5725005e3917e9bf0ffc53282af5071830088f154503edae76122defffb4954ca7ed10302335a5aac1ab423aaf6d1d143f56c7987b50dae589c18246a012345ee9769ba7477c0c623588a96aeb3f85b239bbc8af6b7a2f5e9751a826a74b480b11337bf47107b3e0a4e6a87710ef21d0153064c954f3f09eb408cbc490fb158050cfd9ca08a3c1e28ec310fe399c67abf4e67a27dad57b38f2d48995d241e0060112313e8213126d6ecd36980568d59a6870c7bfcc43a3781e164ee6d8b0628401dfa942aca5601aa179c55a761665b9b728c2817827e114bd239136a4573d666cf3292821449be2598c1e352dc3bc1dedffc7d2deebf2291dfb24ce0c199fc97831d99acbc3105de8644045870fdd5c1ae88362b3213c646a701bf83a193d47e5a73ee3c9ccd76b4f3b25981c7f475049b1c1a388a7ddf3a8f32f1a48007baeaa0d8eab14a7db0ed75144dbecca7d4c154931bb9552758c79657f355507d70d9d46a30e57b5ba7e5a322547b137b159e0f555a329fb00caaf994fc860ddfda1ff3927ad62734ec19853fdaf54d1a67c274689f03c5d293172573a7578af717c7748dd1699879471b73470664e47f65970d1fc929de22c2a1a7fb81e284b5035d9346dd1ca9b611f6a816ca440dc052450d32cfc8194aa2b365767c0408ed765bcc97994147bca2fea7ffbb5097cd7b7b8de5fdfa4c328d680fd583482a76103b81b241b2ede57cf652ea4010ca34452fdac7cf39d66ed37b70c2f9174b191eca4b87c584e352086d21f012d4238c9ac51ab6a2c7695609e93054b603b388b18278bf01e7f827b8e9a77d3b3734796f86d052b587a43b919fa32cdd36806c40bd6589adad364401a4aab394d147c9e918bc69e684e13019933e26a82ea6505958b09baaf26a85c4fd0bd9ca07b558792cb0c2dd7097a2e3394b84e507add03f6547a534b74bcca1029572dd1d838e39da1a2fbab5634aeca8a94da1a9e6a5fa51189ad3322255f633476675e052dd2af9aa2712a53afc37b8fa5af6e45e2938c5ca771bf0221d2cb138b66eac6b21e5c37409a8305f5b3927a3727e3e7d0347713aac4e037eb24f4744c64cf49bf2b25561bbf939c291d316a1d75ddc42214b2db6dc70723948ecd1e24ad75fb7a56e3005888447d3734b355ab6c245e3a22453c744e9ef33404c4f580b9772b6dc955610810e901b8db355ce13f58840a811e20ebdd821d98fb2556851e152a04bac0ef77eba0692e9fe930e4e21c4241443fb8906e22a89fafb7edb76d7797e4fd2fa5da8929f6105be93afe9e72e92d3759a3c8858d5532072e577aeedcec684ff8c8992245e1fd147875df2c39b1038e3a91d1c09a2690b5b1d11542daadd141ad00aa30aa45c772db4f2926fcceb51926f57da9a96e442246780e94df8acc58c472418b81830497bc146e26aa4e238ff34f63b7a65087045d79ebb7ae51dd241d71688794fbdf7a5e29050c86c247a26407ac3455d05923d6b28392e2f6013df021f3f7c4539fb212818490c87f1abf586f8c17bae4856156ff8b7a331c50e379f91f9e91ad7e0203fe990ea008b1f7a880d5ae632424e4a0eef815c8fda7c34bde802f388da249ef85a93acd604a313e0c273b5efd812d4ea4a0052fb17c8a51a38c8c31feaee1de217e543ccd1bd29438f51430c797480bfd11fca5a69a2c485675422d3ce91428820a680176bafcc811478f948e9800a1048809bd0f96043e8d2f5b000d585b6875284f6d5947c0715d4116640ffb6d39a8aeb371bbe3d823e73702059f0cf7c345c89bfc41cc2aee52395fa9462191c92b55487ccbb9bafaa22ff3f35843043abacfc5e2c4468c32c2c6245782e55d5f134365227eb6f3966363569ae4f56abaf75ee43a486706788935676ca364ecbd19d2c4bca38eeab95f19222015b65c947217028412833169ed7e4e316c02aef6b2bbbc3c06d00461d1dd1eab5865be7f7eafcc555204e62b53f09ccdc8d6311c4b7fd8c78e20b4c4dbd2828c405e47ee1874a32676ef91a2762b0477079c6ed19e71f4fa657d39329273581a6ced37c29d623d696d6b22df092a0604dc06c740f1f94f44c664fea7c7a6d4bb102094644033ee4f5513af3dbfd5308f52104445ac480e54e807567111857eb903deb178d2d8334a7cd3088d235f95d1c401911e568ffacc42d6394fa368ddb8b11ee656ce69c8f1a4667c5efe8b32e4d334359c128d022eec02afa6a86b0eb84a0ae941da6735c03dff562daeae2cbf13f58e32a7bbcf7ea885d2581c25d32c2fe620c45d093c1a06d839303fc96b8cfecafb391cddf049518e9e72ade8b8dae4258295bdfcab04f7b6e4ef8bc5cdd51834d08c5121ae4135195aeb7bef916a8fa6114685001c6711b0b3517d3a8781879c3283d1aa7c2a63796d4290fc8fd597b11aef808ced42b1d5d32df872a64e1b6f236bed8f8f21980b932180d413bf83db6de4c888f8e2a27b12fd61994d5c9072799de8f94433293f4f39c30e73ddb00970cb0e81d26885c9d3218026ea42a1de2a199646936c801ac384e9d07c479a80264be6320f192d054e5ed40a24f93fbdc393609fba626b7d4415c62b8110dfc84bf48faae20f12743593cd54c85be78684d72fa307193491b06d356b22f839e2207089931f87edeb7eb8b8fcc7f9ae5ae001aa05ec84a166b62b662e04be89b9c52632ce593ae69f70c2109e0f84e77cab5f8302290db7a1ce809f0c3f12a323c27afb46ad158c74d26436034c8e829af52c378d4b5630f2b23062152a582f3e898c841d8e3b381d3dc7e4fda8f6fe60f154228c58734c033206130ba61bf18e70a1f61e7e96f0e62eb460528955d8764d8c5ab5f9dd1e8d1ac603b1d8f470caf88ac75fe24de98a37e538271cdd6ffdf131ef75bb8029b61795c94dd64334b7c0b08cf4dbd7158011e06cd91401e7be9b9d965a31dc618533ec13b064d13a254bb374ff9883f07a2e4b3300949d532bc4fb5a81149c70a5a557c2387d754e9f2c13ea088b4f8bf333c11011da1876681f55719f083eada573b8a4bd7dc739530e35d87fa764a2541b77a2cd847190a8981e258ce704249490088c19b3d02024c26e6f75c38f08dd2af2f8e00089a6e773386e77a34cac3c26fd117399fa23781b2585dfa3bcaa490807995c44e2710ce33dc89bf2b0fe52d070a2b23b3c92a81b91568da9b41bbd981b75ad8162d7f95c06070ca090da316204088550740b623d8f500d15bb251d7b50dfb3b96c71338199c1034a831a850d3f960c0cbbdf64b476d7354dda7b0313b22f91884d8783a08fd112c400bed11a3fb1cda4243ce15ac2b53a5e8e369766dbf01746dd2c6e71c0fe0e69b66b887775ac1673bb3799dbcbb2b3ad75d64b41bb12e916151c578398316ae62c83c61f950ea8af429b10eabbdda51f7701b63a9bda236a1a92a5f9024d703238fec44945c570d0a4c2256b69e7f56cd677de42c83ef80957087c14b2c1aa79ab6097493525312a36784f798d35cd952bd40368e00de1d638371a434e13dca8e7f7ecc07c74448d1278877114e20c9cb256ce4cd13bc6a51e7760dba88b383d01bcc3b80a71dae50175cf981304b7345f708a1d6f7538258385bb5985693979461411f854e02f47f3ad1025e00de3d638572bf6c82428555c4facc9698548632114d3b63f489bd10f6e4816de5375433ce9cf1a2b5006edd76b5719431e5a0db600a8e81f2f21cd1acf9ffda8e830c4be512de9808f07431fb311318a124b62b4df1e088e7356424235e1850b1d26d2f7309f78c487bb16ed8bca196fac3a5c8c348d4dbde31715aee0e14d2cbf9077dca22f1d219333bb2034b6a1b28861102134abbed21c907e806be77f3a81f57b0811f393665affb5e3477cb1f0a10e161a4bf5d209fb1768cfc4f6febcb81f848d4ac21666de8f6fc044d6982a7db008b7f984a94c9bf120175132e5f04d0048605ae58f4d4b587a4b8cd866638481217ca9153cdec5238dbc81e23d7891d7a7c886abed97798cbfbbbae9c83831a699447fa4c91a693180d7e93ed27afe47ef48152c1f1233a23ec8c39e654868b0d76e5e7519b9955a4a8e1493bd4d6da81211e67de8956024e8e00f7653a5a00add733b92990fc6b5d32749a8738bb574c079b406d12b7918d8747b06a1d83c4eec982b86f1cdfb4457a06bad03aa862fb412a8b79eb42fcf41236d10aa4ad192097b4d28d8e470621782f62c2115dffd1d6338f0235b1b308cc00a22c5d613a61772878f2bc1869286248ba8df22b0ba63a05de07a15c58e669c05327ddc9b9867bf285e566b892903cf2ad17a3bfe5e5406b70a29e0fd71ffd2e0634a199321a6430fab5cb75920145de877c844f98121d611888505b42429814062fde62fd38d78ed42a7dd1a94c0acefdaf1c61b36ba175d3a4719e4cff36a45b967c2948cf317c9986b07ff593e1c8627654407261ec1c0e513459af8bf472c5982397b6138a384bb48d35a6b79a5c9149423a9eaf295af9b9dcdae64a125cecf58613fabf4d87239a494cb062d5c6753b74e8d80aea527f4c67f2fe5f23f632a72cc1d8b2bc4002dd260c704454fa843539b90d01b635303e10753d8747b38a8c32a06a24313a1a9ac6c03f065ff5bc13ad2d1a6cf553dfe33c4351d1555f8ce65a933c50aeb881644d0e402e62f37345839afd1d3d4f97afa2d6f61ff7e490affe2581655e91db5195f8aefd3dfffe6612596ca9d9a11dfa383ca86351984759cda113744a0be47974e43943288f113df55a6c13ffc7549543373ecc54fa9e297685431f5bb608da3652ed86944fdbe8ddf43af646b87590335a535ff46aceb5460920ff197b572e91cb715b76103fcafa11961a79f562ca282050c9ec6ee3c801163308432bf1b1d8a5159ec3f341b8d3f41b582fa36d0d839201550f2219f2f6943e74486571051894e6454bf69938699d1da53500f59ea79f66eefe6162d99a1e8a7285eb36f44de423c959a2b717104a8238641dc0cfd06e55f5f8a6912129d40b0d774a63370f9a1827c1d146245b5a45d2b35d7a238c4a7636d492c71252102338b9508e3ad036aea28a61f9860a02eee51424567372eea4698da23a6c5b29c76688c6339cabf8843b4abe73ff7a1ad4d51880ef263d1bb5d5ac0c0fff78dd3961c5043d11163190aea13c68b98df2ca127707c374e3fef59d7ac36b74359146a68326f55d4017b9e82f9b105ea11479089450c8d94a99efb10733ff5ade1f62b3ca2c7336b4171db68e55cdf8115714df69ea6c6b9da505959734b51af822b2b2a8680d11480899280c4db13b290dc08fa9818c2a7df854096a0bb23acd0b7599ec9acfab0c5bf19b851d4b291c58776cd1aab3d2e4b652c71c47e30254c85b43e72f214307743c3b7337d9ad2c1ba6c17219c00d4d50e564c2c7b31c7eed6b58ede1f4b691ec27a2d63f998ac83f54aca1b9381a7e35a709a5c601213736e5ed4242664140d03d21252c066c862979751091f54be60563fb87c50261bb02b41ae5ca937f2b71904bac695e11ccf149231a66eeed7afd56bee1304c2c4c0868db8e08435aace174bd95d68ba6d6b961dd61a042d74fe604002dbd601eb874ea1ada0efc9a71f0bbdce6673f4d0d479325cf725f695d173cdb3175908821d46c95d69721241416219ea45f479c9a079a10ef7caaafce6b0e5550c367cc369b05084d27c40c585f85fcad256784aef4674a95f2ae65eeacc780ca88d5c59b93a1b9007c040a407e621e1f03326f54d87dd6c94e7936b93d6c83497a22b64921635917ff09b38d666a58db4b4fc0125de676a86c190992fd0472ba28a1c40c04c167ee33a723cc49d288b5a1e73a4c9accc11b7b49604e65b2b1ea7974899225134c5271662bc588f5041cca2e81605d4e3f68ba4d85822cd8b68468875d71c4c432ae674db8305cb3c448b487f7225f5c105ad8d41938852f09d45dae89fafe235c41af68d94440be6bd52baa3dad7baf4f5af48cd65dbd1091d03591a322adbe6534284107a01850c1b2290cb9b2c979a518e55bb9cbc0bb5815cc62db729e84244e8d23574dd85e8d245d1352e6bb142476054e8382a2e48c5341c6bc51799019cb8dc080094e6784bc53a4c05d1541ca31e4204890e6b30cf706c0bade5c96bfbd616082550a493f1f94598cf5545c68caa3652680782a716b4189782d66ec9a5287e35c76c8caa460cee9c82cbfb0865e34313c6d2f937220dd57e6312876842a3ad17187460b8390a2752853a92fe262414cf677a7324ce63d7b983d7e847eca33a720b8512edd6f20b5fdc196aa01ab049e7fa6673a8bdedd0732c2d754f1a9d2bcd4271068dff5f0c9497c25531bbf3b5e758f9db509bbdfd0874e376e1e8f60e21c9c6d4d4397d2e2001a2fb84d59915591cca9be99b7e7920ba5ff9fc9c3201c8f0a775cd91040452fbb8d92bf74ef469b9ab361e00197427b35ef8e6035641b8bea77f5abc77649469598e4deb3458537a28876c9c6dd369fb81878cae2436771f8dfdecc7f7e23ebeb2a360692f860dfbd17f1466c3d14fa99879537d334aaf622b6a7a82daf0c37aef36bdeca4ff0a5335d53883baa45f2be7d342a373eb61a0b3d84b9297a9cac643884c398bd0c7ea645ce097de589057a88c0be8c08f49bf5c1c7554a43f5189d60ed49c12030fb7e4a3563621d453c2791ed3137add191c6a72b091c1323ff3d8d899f09ec5d9fb15bc2964c7e8f76de93fcd7eabfe71fc9fb85f81ef25ed534faf06a78d609569ee0f026cfe5fc7208238b6e3cd197ba217c4da3ed2f5407af1923d172292c456c640b10819a3e3286e923cafbd2b8d1072bef183e4b8cc42781c1ae12ffe83f87f2d7ac636db6e51412136caae4563ea5eb47261f55b6b1c58cef7b1744718c699d1acc0be61ecda18e47298b988495b785ffefdcaacdaad207b6833ef67e18bc28c32b5b3767295e2827bc2ef713fd8bdced73e4ba29881770054ae909fb993db56b63af88ebf520b085b2507aa4dd4c87c5b9f26f0ee281317dba6ad4ffad83aa2b345b1a3057b16be3bdecacecff0c396358c4c96bd240c08bd77aec748bcf33decd8b3ff675f5ced2b7fda4996bb4079774781a4ca0cb75b3353786e2dec9fb4b2a10e2448d9f16263ff32be0acdb18a7dc0206ebbd0a7a2fbbca4d21dbe05acbdc9299436639faf57ad728b566159643df0e4b8886831e33833f1bca9437c2a2e82950a1fee0eb38040ee5c832a26af03b3da1433c02453688cfa0f977e3c599ae2c674258e4da33755bebf83d693188acd1b863798906ea159702166217000661ae0d336b1842a0522ba08ca7e8532c5b598b511b2b6ccba89a88cb405dd9e4f0e550e7e17aee3839643397861635a91a5cd22ee3584f041887921605fdeca9501cd513381603b2846d5114e93c3d550f85b50bb36e6161933c34b73f5c65aed297a7bc5208dcb016597961ce99a54fcfac136ae81644990eb764435bb5dfe8a76bbc831cf2d9863b5dd2eaa7d364cda888c22b20be3c90c17a3d08b2790f12802b7e0b7a25c3ce8450a4c254597d0061176728bb3822b39faeb0c4799116f7bce0c090ec195549a7777154aed2b4df3415158d9b54cb6bbfea441c9b900b0675385d177d2aae6e49e597189400a94a07ce6c4a9ef54bec2d0a4ade5b07db344e28f61937bd3eb44417f27532e24b0fdb41a519a7e1c5eb1681b99f065bd2dae9edbc8c7d71d6e6f11521b0ec3e10f9b35b1adfd70c5149ddd5fa37abc412f91225fe50c61ffcaa857cd105351962c14fae245b9942fc512f1c4c1cec3805dffea236f4e3c81d0e5bcfb4b5e848bc52450b5099a6478837fbe09a0ca8d274570b930fe44177404e9357b22804017dc7e1a60ef0cf3a418cb4fc04f3850d0fdf546e87454be49e46dcacd043147f0af891fb59813d3bab327f448967213ffc8191c37f826125fe890889c106772c88cc4b71b42676f64f83776e26509e5048bf99ee0438334e504bd3313134ce8d14d034422ea2ae5a6c18885481db6eef8f36308281284bf1a43276f9c1f16914fb249f4d2e51363a1f78b049b10f02ec1df4668ea0341f74c851b114cce8db46f392418c405e0913d7c05269e20e54bb1e381001293012f06fc6b04a0ccb2e9066909620edc3f719f32a8823ff45184ce14eadadb47222572c246e1a5203dd1bda51374cf00917a4f3a03ff8143445faef439a57c0dc25e82092d86f7dd827b90ad010c303aa4bb0428572c843807c7eb7f5b3084984328be651519135620cbaf1054587c3bde8539d82feb9f045707f17eaf062c23097ac437e4334237b43456ddf19268a8de0e9596ebe6e71f06e818c87fd5e6944caf8891ad4aefa1d6fc0fa8991c4c8471accbbce8fed271a4e3103a5bf355f694216708d55b5a4a1af0a2a605f34dbe1cc0a29f862f3c98223c7a5ea4db5d9358f2ebfb0327fd53a0d06693a7fcd3b1d1a78ff18f60dc4e26f2b0f8199ac6482039bbbe4f2c8598a4fea455fa94faf3689c72f8a634ec4c3e435e6a8d8b1f98d8eaf514b5c8cf0b159e4daf401a238812034d644786aa54199d770a0b7d4f355d318b516a0a7336b03a2f5e0d8f4b3b3d20a287da98bca3add289f9b0635c657d01a9866f6ae6414470e1a92bc247bfa6964d38d0bb5d60858e55347c67a76cfb7e3f5c02b3b87d3ba427592ac51c85b5240dbd0f92b2cf4273ab6c8ff1edf3cd0d23ceafb9ebc1307186977eef8b31b4649f284e0d51b53863d145c0d1d020abf49cef4f49b43ec8620c51da0c0452c00b31987861ef32f41c4e8d6287246fe2f5b4d2f57f3e51ea6724b6f89d7c7329792c0755b8a1980951219472ab4d8fcf12f140905f39250aba0ef101c6714054bf5510cceb9cae00fbdb5f24168eea8c2667e16739ceb0fd996aa88ac2f17aad8152e6b402d699b8162250fc4c0838bc612ce609daa01247629081c1326357f1713cd2d9e45b3c92a293d5d0ab740251fdad5a173f837bb7eb435e44ea7213ecacff8d14513d08f37aa8afd6009aaae0c85da92f6ace9f641b94294a4d0bc6b1aa46c0f373126474bd2d03d8e7b9b7fd601380c87e351d368fd4882146fd06489d2dd18cfa663d28886f7f49141dbd55f9ecef64a05488d7cfe1c899ce44d42fad4ccb2e12ebbf2f526d109d1e77f2fef74339ffb261d36729396df661b25cf44304a7bfc81e9892c224e189c069da45365c9966abe8b00228699aebe74446aeeff77e81105723887940de3990a23f383c58e2784d68f8871c90d43a2e03f77030083e9a6e0f0470c4186c103a31cd427be5b80a8316386c59d8001b88e40a69fd3b6bdd1e639481512ec1a10e3f2289fce648ca26e210689f1f48420887cd99fd0cdd022bfd0f9e4419dc1a8590357d80a444c832e099b3e2b3d10e20bba8d9324847b34c80d50478675354dabe433ba8b298c31ffe8059f25c6aac6acdea9e11f54371d73d21817f6c699afd3b4fa0f473476c30648434e39d8159b1518fc8ce9dc3572c2aa1651f786e4e8efe81eba7f44077fb113b9cf9bacb9c4dc0620f745b9c10069cfe00f9b7feba58fc806dae02c819243e58f9f165a4865248708318b6fb0103225c806b1088aada03eca72ec04d4a55ff8d08bc2360dbcc1f367860572d72ea3fc682a546acab578bffc0b031cbef15b7beea751062806a1b0007b078369dc95dfbb0d5632cfe0386c9588ae160fafc9200397f8801577d50e8f17d54087110dfbfce3522e5733fa53ef96f29821a7fb81f6ca0e8a8cdfe61fd1cd7071cea0af841d3a326526bc6399cce161111007a1c346c0eb006676300b00dff8e7fc004977038de93392aeb31ba25c2e132898fa5028d7a8bf183272a2dd670a69a7f05e987269077c0919b640b730c7f464485db111b1ad2a2c4f9f4834174b5ec61717f88ca2613ab6bd0c546d390a7c1657dc493c05816eac8abdea7d3b600fb0c44d5db23f652557673c706805744a7d6ec4f63161e279c3772bf24153f8f8e3b0e5b8674bc56bb9f1f1ca31c127c4695c5160245180fb810e4691b82a1e2452c7249d03c3ffca4740557730c10bf7af8d0811e9088c19531175c77fcf331b56ce0136ad9f2e93828711433b375bad54a1aa2d36aba2ece0c1942b50bf62133716b2557b88f8580f93ef6dbcac187906179df65a28be3964d175a5c6a038a0c5f975a309a514d8b016d064300769c1dd6eadad48fc022cc4eec8d78213a8913a35d076a8e55e231bb025954413456dd7f3bf55500240c7d4b1b1f6985a519b0f6a8567f2348ced5268e942c486970a89999dd84104288442eadecde01fd080609950937abeb665f9fa94bcb7c20d389828621ece18b165c418229e47441480ac21914808113831a2a5ac082245213f8118330b4d045133e80410a1e4278f8e2155199f6d73eedbab22b8c2d8fa8fa55cdf6da2e7efdcb09da90bbbe74e407721f5d0ea19dfb10a18f38fdd991feacb6a16bd742fadfdcb557bd7292dce5a95efbe823fad1ad56699a5ec5711c37bf5d24da718118615adc43d7a6847176cf5afe302c147a0cc73dc4697a4489e07070d76442d7f40af7c170a1430ec3b44a7b48cbcc6b3b0405969ae430113b2342b2c81a2a596ec56eb324626b58b6d7225781ee9a9e495500ffbbdc55da39efdaaf568564400f7d2615b91de4de81e30215463b17d2acf9cd6eec1aa6bf1ca36b8f195d0b9d2562690f1da6aead693be4a91ec705e4a9fe831189b48f42fa13953f2bcada478f099da459a2873ed2ac1c9548afe89789aa995d79c95de5aea2e7f469d76ffa7657ed2a7a4d755378a2bee2f29357449bb9645783784dbb3e5355b3aedb5ffd51ab3f96a6afdbc764d959f6fa933de41d13158fe5fa883d568d699878135590c056b372e659f6d7af66dd67d7aaab3f69afc2f4278fe97aec31f51876d6f6fa18ecdc39cdd28e65d7b46dab988d3d7695d5dfbcfd754cabaefd7595bd0e53f5577ff3baaeecfaa40e221569604abb88c427c8537cb4d189369225b4e3e30db4916d18e3b1cf6cf92426325bec4bb6240950c40627da44abe14449bd8a37d166a2e205b1192ab925685045a3924552ba65f9935bfea4fe72d0cfc768c7340bbbf6a95939ac797a98d2585ab8fece96ed75cb6a55410253483fc4f6c1503d4fe79c496521282495a88a9232de50efe352510166e55c4cbb7cc5aea2ffecb1d363bf5a35f5777d1e3bbd4afb7cad1a16b55a230e95880d3756d22e935dd378a22a961d729ab65a856959e9e7a1cfe505940a68fdc0a9f9966dfd5849c9d7e0d48944d156cd4a6afe074ec9d38f8ca01126f417a9e1f084a00d28eaf121e2e168df481e74fc6efac4225ce018215d66d3b95c725026f9964d8e73e9a19cc383e89ff7511689dedd13fdf344e700a4413b107f08b1e9352de2e0049182a78a9f27e0ec08e9f12162eb11223f5efcd45a2b91d9128144204422102211480452b3208ed87f372e1175608033834d230e4eed519f3de2206b76e4ec23f75d0de3895e334cabc57dbe057a2acb3c8448d907d375a39b324c59cd84bea475b4489d26d13a84b4cb489f4e5a26a75e2674a965e443d4feca9312b08c38dc1daa67cd77efae9aef5eb54a3ef4171939ec9b64f121fb753a7335ed03107467af5fc59d459b7db0877b6babb8edcbf4e17adc32b3e4499f9995a392fac348fabb971f9df561fab357a9beab3fab73cc931e233ffa3cc99e04479bc808a21c431994234ff49928daed10d978e64a90135483b025e7fa8f9b9aad1a97942ca5e42925634beb874b0abfe6a6beaf2e05d4dc14fe8adb107f05ba51e392c25f89a884b087135f84a1c1a1b1a174d6d3c79aedf431c86c29991e6d4c1e79aaa0e901cdcbf92e4b8e9b9cf4bec1140576540072016661d35873044e143e52d0dc34114467e72222595678c7e7093e12efcc961f248a9ede9d4d7f8034e8b53ab22b8220479b284444d1cbdcfaf1148c065875f100ab38824d69686e8f7b4d671a664e790dccec0b679856cbe4de5ba56fd7220db4d17197b93b96e3771df89ffeeebfc3940ed0b97f8fe1fe7567cd738fe9fefdd32c994b6eca2437c9a497b2e89a6749177df42d7b87a37bf683e1b8ee5b86292d9a324c5d386a8926d12e240d676b0427aa25d2a7892a751a94bfebde610ae7eef831dc3d8d7577783f1f0e883bf6c8133d8e28449ee853d8e35221286870912b3d2968970f791f40dc5d0f44695b784578c3759b27a4b16d780fbb1cba2d5554708f5dc73d3693d2813dd39fa557ddd3f922352c85ddeb2a410ee64ed4055590c0ac1c7b16bdb2dd90c371ab55f39ad5222344bb23477fafa2bffab3aaf9cfeaecf72cedd863eeb76f9a956959feaec35486294cc389baf2d7a1caa57d803ed7116c7655c03cac5f611589d2b1669e66ac59998f40ea6b6c429eead4b5d6479c18049f7bccbcd42cf9794eb37254f2933523876f34127d2291d9c2d2c2587f59dcdf5fa406e791ce9ebc3e56d26bc9eb65cd6c31798d45768d3fbbcaadca24e449de7c3025391e70eece65d363e4605a43cce30f21cffd84e4a46b23fee07104b1250221a26a68afe214a13845a69182c49802c35b2302d81299107d6a7c208deae3e37a0e3d2e7a411ff75fe603d36a954ef256c9498f3fb13e16992df2f59108b47177add527e5079e112736016d68af8f31802df10710555f4f845deb6bad4ff981657dfcaea3fba7e3915dafc3f4f83351f52128f00783b943eecbb466e56467cd63cd92e772cce3abe6f133ad92e7bee551e825d9bb468285ee65d049b93b04bdfb07833177528629b86f869b84c38e349c2891ded10a6938eb5b200d270aebfa96a775b43aadc344d5737a038166aac44f6857db3ea35d855dfb752dfba66564667efae356cf430b02fbf6988c95a3d29e6d6765c752d8cd3075e5ad1eab3dd4efa887a9d7512f7b15f6f95d67dd5ffab33ac73c76d53cf6ebac9c4babe6b15ffdc2c2f4ac31a57204969146caeb0acde4a5c36b3ce467e655dde7a596297dea2fca98bcbb7816d1a18f3df451873eeed06b7085e1102c7e36bc62c321589c606b736369c54e7ff79f0a665e83a68f9284d8893da3d34723b345741a7d480f414167cee101a65adf4d9719a634b975f9bb964d8f1c8e2fc74caa3b0f33a94ecbcca4647254a677ffaeaac0157c764090eab44c05aee0b34393325de698d262c9490eb9ef7ea6470ebbdfbd42240b4cc2155358416dc43ff4d80bc97235f1c363b6c424289549248135805e6483e3e3bf4b0a8263a809389ef3813d871ed87380f1293f30dd3e28f5f15d0df3995e8f6598568bf492b74627b9f665949bf2c94df27c294b9d4d1f7d429b3eeec85f22b1c8aad41de5aaee285a26e75e66befbd52aed275a467eb9bb296b37c93055ca30a5559822c93045ca3035ca3025fa606c49f64e92b79332fe2873cf72e8a20cbacd3cc054ab85efbdc57d7bcd8fa901a706460823a5c1b45a43689856cba3ef748bd32f1345dfdaf4ca278a86a030a9c800c7797910cd68a64a4f06f650fe3e26de6a16f6f8ab59392a162cfd830157f6c89ed922e3f5d24716992dfaf5f267b6a8bc7ed2cc16001ce52b4964258dec5a67cdaef30879aad306e5a6cbd84e4e4c47315d468ea9efb329329ef218fd4fb3bceba7e8b3f0656c875c4797d08181a4c10c340d07d2a8d247f64e7f5cccf20892883cd5cf2c911051f554366157d90369d4879a60add6fec58bb0e7c1b3546e80f1bdc7d74beeba66b798a3143db27f9c8641390f3085f2981e30ad1aaef24d7f39f0655c8777fd3b5bbedb707bcd725f0e1008140ad97038be1ca57f5795fe69991ced32f8a56b5a25ba0d5ac6bb08e732c9f900c87f0d39c655f2c9351919a65262fc24a75ce7eff0fb60504c00c83055438629950c5332324ce90c53598e1bbb595f4b9a181a4e542b45c3598fa2eb5b273afe7c3a1699a87a938e3eb67e2779abd502bd74d25badd0452f39c824479cd9227a15e5d344d5fa924c92759448a7513eed2aed419fc7d93bbd0aa43f7b91a73f2bc2077d166b49e4b3a2c7e08b342b7450484b9096462e3e79ba864c6630693204af4b75dd76d063bc779ad5ddf3406779df36d0268a911781360e04d29f060281fefd45721f7602da4a26d1c80473c7720e96f7790d7fa2832287237be4342b0706f434a4646652a09fe498fa3236cadc478fd91ed2aceedb479a956198adc7f4a78940f71f76eec3be5dbeea1e302d93979ee9ef5ec73c771df2dbb1fc5d9d83fb54618d0b715db77d3022502c21218d44a10e84618acbf35b96ef30137ca0afc78f22bdfac0161de615e4599d562b2b971d22799ee7d1733c88b20ff0d5835b48f415de21d157a51d0a1d8a34097a791577bd0633a53948159322a61883401844f1e1af5fbf9aa6359aeb218410c2cf4dcaa393e4f992bc45af99de835c8ed13d910733be97f143f82310a69ec484e4601feae9b50f6cf87b5aadec95cbbe5663d7b497d53ef000b30fd7a98ddbbd3ed4fb70d9bc8a0d8874be4c0cee4b8aa4606214fa459a357a8c8b468f7196e89f3005125d3f6674199a158395a38af1d159bfe831e6173d26c6473a87e87fcce831628c51089b2a048918c43ef2c17544bc39890a1cf202c3386769c3e86b73cec79b29e393e413e5b3e4337f9666ca344dd284134e2dc334525121b9ca4b2a24979172141d392cc6a8cd39e79c73460d9b734e0cc3b039e79c7062775e64a391c009678c3a07b8af0634fd22454f796b7494431e515ed12ea3910cad72191f9d54b40cfd6997815da43f6d0504dd7487b9b91831d15f2696b76da351affe6197f1d145595ff418191aced1b5168948f9e4397f7f363d462eb9969261ca848792946c72942c32b9e82f72d7fcc1944a24393bd19fd661a2e65b2d936e99e8926e956897899af324ba3451f32f920427bca8051856ac560d0d34387b8278a210a861f6574a0e07f6afd668235b486ca6988773ce3967d5b01ec932a7c0c49e3fc15560cf416e8cdbc1005bfebb6a8fd006be76096d80b6afe6d64ea18dd0b9afaeadd5ad7d6545a14c210deca02cb187bdc3b06fb3046960a37cb3a801f537cb470dc4b525bc9be678a8324333589acca05d2a39d83bc8fb256febbeba6b1b777ca857405cdb3be89a97b79521e0d0577487becdad37706d1f40f77e857e890eb5e61d5e5f9d6c0f6fef269085cad36c29ddf37e674bc9bd6b35e7303aad59ee92fcd5cb979c247f557f39e6491e235ff27992d1719e7b947bc430369ed464c31ca49732498ea5f922bd0c3acca08be0412765d87d77ce6ebf0eca387f5f8fb829dc5e865b24058eef329725a491b3bae131bbe1ef05319617156aad2cc0dfdd46f0e30f776de3324cf5609fecdc63b46f9a6371cff417214e109b1b2db3c10962730306fe2c366ff2187993666597379167699fa7263726719fd87bb79a8ecd8de93b3c3d263615e53423299de2f85861432446ded3ac2d661edfe61cf3589ec57d5ecb79ccf6ec383e4ccb26cf7470d7e251724c45eec3748bf491cd4d133a5314116213448a1d9e1e6ba4a708164566cf6c9101a3078c9e4d2f65c0b87587111f1fa6ad8d0dd6a3fe9a9c0798328169b5483eba297ff7a6c39449d66ef298ec26cdda9edd44b372549bfeee4d2e9f9dc5e9edf32a13fd61ff6e76f94d7f98a6f4dfd55ff6188dd33e3bd049b287334c55d0b68d3e182dee5286a97a897428b964bad4e5124c69f25bebb6a2cd44a128c80a8f7a9efe2e8c4b3ca251c76d9a9761f1070c9d2339602af41c1ef0475e06bdcbd8f3401efee8d2f82a9b9668e6674bc4f13cd975f9629e1e9d1d1bd10690063d8d3788a14cea48eeb3dd357a9d461b40143dc57468747462122df2d4de808e0fb9cf6a180f26a45bd9b97961cfdedace25117190355a26e7baccbc87b58c7cf717a9cdd05cc3be65e75632cc44f5305134af600e67e7d863ad831b7428e7419af57d7e2c944b5dc4880f11e953c4880f91b8bda6b391b2d332292797b288111f223fc9fbbc88119f4ffb268948fd6171c738287ff629ffdee5cf6a904e1f9bbfd3133139a59735206aa294a39452508c83a6fc4d37c1a68c71311f0cf32182c50f18201bd9f231e2c5114983d908aa01d9808e00d9c418639c628a1a3ac51437480c52ff61d2c7c77e3925b3bacba36456ca517e925927ffbee59879d0b59c631ef42cc7c877a792fb300d33e535bd557293a76494cc9a07fd24e7a8bacb7f9905f221026dcc4d2935792f65d14b323e974927c923d143aff98301812e12fd48ef10b57cb0d6a1830be702c219cd60126406dbc1d7cfc7d44ffd89b27f911603da046652f432c0e80177f080363af8058c50043ba6e2796438f04894c2888e91a8c92812318e943008c66922e2c0523c845f41985affc56079eee021875801bd88345408b26fa684251f6c29afdf4c07315171478e35f147023045b5119d3b33e50583fe7a4c3cd42cf8c8822ff8e637f597831e5e75a86572a656c547fd4d9978aabf39939a977799f29fd205945215235cf4743f48f5b7838fd8c4a61a088a8550610246c78b0e25253081b357a5bdb2e3e9e5293899b0d9f1271127b5823f7b477a47b0a308f66adb2dd719a0c303246a6ea95fe429dee50a8c37e462110f48b7230db7638b3f681b7210b8178880dc5403816dc839609eea68c5954158a80302914715855029361975e4099e07dabcaeab5e578d39ee79884d786dce3b3fe7fcea538bf19a578c33ce38e3cc74aed0a0ce8e9f74ce9803bcba70f68503bfba6ee484775e37b3e54ac5396178a4d261811e240b961d7ec70f3c240b7778208290912ca143d1e1e89074485272f81d3ede489693c347987218e3f0318864f9613ed4878f52481693437a883a0cc0a1000e1fe163159285c6e1000e6f385c397cb442b2d8704880c3ebf00018c952faa9060314e0abef03d470992d2d2c04c82af074ba0df92a1658c927efe1865c3ab9c000f2a8b48346c63fcc38d970e2b16bb84004e064c32908d4c986930d353c1e0099d6a0a2a10c93be509c4c261d4cf24a8d07e83c630526e7133ccd50b5e4124499ce924f3af0f5089e4ca61d9b3e7231348627d3e88794bc611e28f96e409c646a0ae2cb109e4c26872613bd92d9f9a414802470fc6a740178ba8ceceb8624f7004fd7435824912d004fd74145704659073c5dc756347180ac430d8fdb8112096280ec0251d70b90214475b0676647946b70dc0ed84587505ed96a3c009467682b30389fb2192a2fab60271d7b1e53b100974fee7bd872c99e5c60544b3bb28c2f6c458696b0d64a51852ea0903101df212930828604c6806087c075e706f8ea5c1ce01be4fe604c090d0b5c57a0832b0aaa15be00e40c5f686161c0160d591270688b6d092cb4600ad91070b6042e8ca00101df0d8778810acd0a6c371ce2851d2fe8542f6c5ea02902e38211cd0b20b727db8209ac163e18154850bb7073b918820a2e2347ac0e4a9045901364f1c3040ab4b8292205467686705144178e4c14bc5b1c49c32644109a2c30de7088133f603881856cc7092858c10921645738214555021e6d38c4099c5aaface172037e0b9f1990200c4d9b2351688224969eecb84d96b44c62d74af939b50e3e5ad64bd2295de68c134a49ea02f0850655a29c74442fd145e75543d582ecc517f3eccb44e11e2c832fd067432299c669dbb6711ad7659d8779f85e746290bde80485aaf480e088e9bc42a2eba25334a223d2bce8249148d8035b3ee14b97aefaa293845412afbdf6bae8bce82c2195e0b5d7e27fb6bcfc90075e23752f90467c2942630c5abcb6c41ffccc867a15773cbd284c8d13a695ce73801e44696864961b523df509cb1929af09ee95a718e5ac9f7a35e79c53df8982aff994ce4b860e8ebf6106d0fa402a2785d75e10b5d7bee6a6327d2bbcc7728d4bea3ecbad1a9714762dd3260685b66cb45dcb7138e6b96e4e128610c28b864a23ecce39e9a4934e92c92ca1d742223013057fd5a02748634e3a29a574d249a1d64a5b10fbb6fe66dbc3b499a6ade780ad56577d228934afaeebc22bbda7a95e96d4c4a5975e6aeb15845e5baf2794d0346dbbe8b5f57ae2463ae98df2ce39e913975e6aeb15845e5baf27ea652fad970d72d57ad927e8add713975e6aeb15845e5baf27c42027a5d5621896d9da838e0c07378a1ac096ec52f3883bd892c7f683553f9aa66d17de188f8d3dda6f5c3cc5b1d45a7ac3715c375f5c5c5cee553d09035f43b6449b169b1b425602d83f25bb2f8a4304126f90f53c62cde9bf805c561eb1e6d291e727d6781e87e3dee372907b1e6fdae1caa58d3617c48147bc411ecac0cab82351118798036f90efa901a421af3d758c02d290bf2148147883d0c6b5f7466cce2b455bb81b0830dc828a0ca312d5cab997ff8153d967f61f38857d1e4b96280a1dce6c66fa93d1e42f32fb9cf35a2c71205209ee4846318acee59049cec9b073397b29478ee38e71190e1ff59f3c4c652088d59c57bc2e79c928638c71ce39a7bdee9cf49fcce47c9cc7e6bc337297d4305867f395354992b95fa9fa2269a690c3e171c7dd41879c0ee1eca2839661c43f9c0f280f96371007a1c883ab842bb63428dc215be4205342e489316a71c5d64eb5671c0eedeeec188723c3b430c1c67e391c98e5705c3a846db308c2b6af1c0e7b69cb5e6dfb4ac0beb210c20811f6758a894441c65ecd8037f6dd82cf9e5c90c19e5c48c39e5d78c29eb30b65d802cd9e375b8b581898e796e167d3d3df2ba0b029bdbd4418a2852836a5af578b2d0b3918c0a643b2a005ec046d9934315399117fec558d1b14422c36a547c426aab057310a241cd9f4f417977e5a41c3a67779d101e60a23d8f43a7ae8c1a6dfc143c8a60fe20a26d02ca1e60a21b0f98173052e8214e1d16981648143b0b0854d630f8cc058075b5e62dc050faec0a92ea6130056d86c27c06fe5645a87b78bff03a7bad3d36e643a363936390bdf14510ea96853fc93c77447d12c93773f31797796e998461a69a4a6e3c798bcc3373da6bbc9480c380b1caf792f7925a0efa1d2c80a1b222739076594f141b94b49f19ef2508a7794929c8120705e756408c3a6d7b4b861dba66d1a26310cc32266354fcbd10848831e3b4167c3824d1f69a4596643210fbff456f7924c5b41f989cbb5a71c45c35a49cbf20a880d4b39b778c4339cbf0cafeec6220dce464e7e50b09fe86f7bca51dea5a0e86f3bcabb9f60c7fadbf497e3e4f83128dd4ff05f6606c6c65d04e6c6f42a6e4cf758007673455270a49b40d74a19a644201c0a792f49959090462210f6608aa39824e1d0eb7d037703a51db513ad1f5e1e49143d2e51c9d2c2f16ee2bed9b9c1e1787a7c4c37f1e4db4b27bde4418860715324e397f2e8337dcb9ce771f76ef2b89f944c3d687cc0346c0acdb06996591e3f2b08c2821b186cfa78739359537d96615a2d929bbc457ae9dadc628e43401a47fc6815b1e95b90c6a5a67fd761d2ac1cecac79ef9f8613a583d32cf9ed7167a2e86f963b04058ef46eee26577137e17eb56af49296911ffd457ae7f27693ccbd9447f3de55f3de31ad92df3eb7a685ae711dc7719eb77dc48d442110f6f4cb44ad6cfada7dace44c20e5a73cebfbbc82e8ecd0d868343435104339e950ae14ce2bd901451d9e2a687a24e904a650cee5ee11638cbba7e498f232cb74d2419995a3329d74d67792ef924eb26bb2d68a558c8b1d2e767ee022b358479365d7300d8382a281995d7bc0b44a2f39c975c893aea57c2685923facbb0abf3bf64ecbe4605ac53d45cb98cefde433294dc3342d33e9ec44205147cb30bc438fa7902c571318673fd1329ffeb0cf9370efb48c497f98feaece314f72953ce9f2a473afb65e23510854c25ec7d14c54ab8888cefa151e49543d9e2d5407314bb5255b5e2f235be8e7a314b2656a1b5f4f73ebc74a8abe06d36af3ddd7e69d1e3f66ded378c23044d9845877bd6402b471335bbc974680a7106608ed95ec8959d062d7fa8ca58541959326524a1819524594524252d2a4919452824026071d0564722d6e3a839e9243976172955246b9289b5ce5cb6c441b7420b6c41be8c0233af088b558a6a30ea4512fb2c1f241401bf7f51c802df208d8912b7dc07cc71ee201a664fccb2dfd94cb4b181414130d532a99e4883351f528d96a5ddf2a794abd8cdc4ab949343d25b74807dd24b74a1e9246648ff4813688d45a034370c20a32a4eca77dc81e54a279459eec71765999e1054ec14c94fd953faa754859d8631f77ec238e7d0c02533557bf7d88ebdca5d7bcdcc344d977358c601e6716d91a0a4e9482c73e566167cdb6d65a6bed9c35f66a62db4e479eec5f643c62db93bac0f341cc964e7ff4dd61ca9ec76cf1b6778fd9de7167d56f8fe1debdd3acebdc11b225e2d847ab57775b7bcdc405ceac5691277aef6e233ed8f173da5c5283c2a7833bd58ba43fcc820d7ec0e026270c653822d4853078010d477040e40b8ab91da64ade45fef2272f34eedffdb43494fe9e52ba7df7f0d65a5930640a57f448c13383548d3636d188fa0f0a6c836111ecfa036ddcc363950551f55206d2a83f1102ee997508ac02da98bbd62dec5aaf80c2aebf7250bfa8bf185e59236eac25ab63bca054bcf79b96c9b95ab5ddd332dcb7cb74f11ad7e5bd05f7ee2bc8e59954976752f0de7f52eac896183e71270aa94e08419023110b2af66ae39cd0a2c25ec59d5aabccaea5912c2b08463d2259a0135a6048967b825d2f97bf7b972e7f57fb5071ef7cf000539fa8fa1e3cc094f64ecbc8946652f32b977998d2beb980616a3b4c5119c9029d20441176ed3257df8a417010e004b6893bb385c7d2d89a7ac43b7e9195eb323d97bdfc4581b35ac190a7fa2c4b9c6311590369d4d723d006ac5fc096eed58260d7ac1ed9b5d31ad3abbb8a3d31c3590fb7d4b12ac520c01698aab7de4caf30a657a57aefb5bfd75a9c4b8a6ea0419528f0a783bb0f4695a5f616b0decab4764c944419c1ab4805153c910a385de00eb06a8a21a59452d0246d18ab30bb30efc518c693665202bc2a9524cb6afb499cf341cc9b6b66dec893bcae1eab6fa378a7fcbcd1202cf263c5968fd0c6b58dfcf4ecc8e1d8467e4ab023b441b7911f20ec086dcc6de42767c78be3018a41072df0c21842800521782105212a421b711bf9b19795d65a68e9b471eb62165f6c47a21045036881092360b085125841848a9e22499c000462c0f9a204dacd486621026708021a7630c4901c1021899d1f1c0143128828030e8e50911d81849ac184084bd0e2094610210934e820878b18bce00948804113864ec8111a6e1ee164c3215d80610b9f96991364f153861a1b248290020610f9c20812d00081e78713231fd8706d90b0090311459860051262d0050c74963085163610528622b432aca0bb26a0c2dd465b174a48c3cb90051c4f6ad470b9f1824b9864c3215d54015f44e8204fb5c6084738a20233f84021f3e20c341883144ac800c81448ba88718bd00b88c20a5fc8a2082668c10512941082186460c50c7c308531e888c630045309565fb8c119b6c00152869d511521a9051b9418e20a42dc5ce1838417359e172ab8740464a0c29409e17384204f20020d3b2a3facf8c11658104184119061089010322081ba403e800384128461070a5afc80573043088cc88289224ce1092facb079a185c925c11524631043954e10e2851226362226386e1303184a861083266217432200361cd285135d2441e2026b74cefb22471b0ee9a266afee6e02c38b361cf28531ecd5bd9bc462db34d21e854038c6df792d5efb15ddf2c6d8295b9b1b9651b646372c9fec6f6b75c3b2696b76c3b2c9d6ee86e5d2d6b00dcb255bcb362c936c4ddbb04cdadab66179b4356ec372686bdd8665d0d6bc0dcb786b78c3b2b735d086e56e6ba10dcbdcd6441b96b7ad8d362c6b5b236d58ceb646b26119db5ac986e5bbb5d28665bb35930dcb756ba60dcbd7d6be0dcb746b271b96e7d650362ccbada56c31505262fcfa686f79bfaa7be420daf497dee19b7a2052134c196f9a1197b6937a096f9853671b62d1ca11d553b443d8db67e2965ed775ab1f6315ae1b5c8bf72a07bc7bf0d8d7f7b5784f4a5f5536d53fa86c284f33b20630ea8ce58942781523e0d5b6e9489e288d7a244ff487f873422fba7fa0515401d2882ca0f252fa604f39e79c73ce39a7ea003e38fe00463840153038825774d3fb65d8e3ceabefae3efbb8b1bddadf75926d63bfc82cb2007e0179e44987ac29e9e015dd13428d336c9bf717b660bf7a456f75cd2b950db7bb577425936113b25c17a5f033218841b3e54ebcc7235bbc88061c39af4706f8e260f8d5f57a0eb0bd4b0a18deeba15e8f64815b4eaabd1e48c3ebd9f2d0eb8136600c1a1a650b54281658dea0248b70c365e29c489f15214ef20b2f3766e0526322b1dd2033547e521a9d8e8cb09ca2c68c93ca4f4678bb7402b92ff84969843715225081d2cb524aaf8b527b5da5aff858122cc184baddadd6aa4411945e96527a5d94daebc2a393252a17b28cd62ccb28cdb24ae94f2e45a4a818638cb1e7799ee779b5eb3a25b6c8325ab32ca334cb2aa5b874aa46b28cd62ccb28cdb24ae96985c716e1397969fbe874c2275c3af1d42d507a594ae975516aafeb86102a8accd82bdbe844c5c505bb6017979f5c6c50bf0a75b7ab5ded6ad7711cc7711c57b7ad564b2bdd6ab5b4564b6ba5d4da4a2ba5d6da4aa9666b96654af020cb68cdb28cd22cab94964e5494e0398247e5625c2a9dd2cb524aaf8b527b5d2a2a2ab874a2b2048f1155dbb8ce2b618c31c69ee7799ee7d9aeeb3c23945e96527a5d94daebbaca8c257a3063c68c3be3ce285d6586fd4196d19a6519a55956293dcda861b930da7075b3af32edab6dfbaae356a1aeebbaaee3388ee338ce6edbb60417371d6419ad5996519a659552979e2b0297192f3c3c3c3c3ca7151e7b4584424affe9b4b2e2c2002157c81572855c2157c88a10971b50c81502a597a5945e17a5f6ba566adc58e202f1d1630281adb5945a4bb39be95aaba5b5d65a2dadb4da4aa9b5b6526a6dadf88a820655e0def0c6defa81b3b77e3c158219c68f95544e8cb4d6bac57be966376bed76e9adb6daedd6de4b6fb576d3348dd2aabd564a2fb51aa555cbacbd97de6aadb5d9a5d766b53ed65a6bacb5566b312a638d1b4a194b5c8c042aa76d748267cbca0a5ec12b5f99b6881bb854fa4fa79515972ca335cb324ab3ac527ae3c68d7be3deb8374e2b7b7563a5c60d1394b68f4e272b2a3368d421a2e47b760f863d11f6ac6c17d843f3e3e4625cca325ab32ca334cb2aa52727f7045f57b5af7a757d758265b4522ca3b5625846abb5b6d26aadb595521b6b1671c6af6a5c8c4ba5c7c802e5e94e54bc945e9cbd8fafb15a38e5ad1c8e1a11257f2ffe5ed588a7d5699fb81c66ecaa571487fb95dcf630e280ddea55fdaac6bdd9a5f7b2d7cd7eefbdf45ed6de0c421af7d16e40ee206936d53d6618616c7af9817d3dfac01ca08511c65ec9507a2a33af28e1bc34ce17f922a1d5022c62059127c8934f153d4278e02f94c22548133849404a730068703c0582de06488d3cb5e4a9ae946864e0e705048f1f765ca0070bd0a9439ee0495800218410420821dc21c61b643c7184943622102c179d3ee40642ca7803c481521a81905b4a4a6984f106f8c905ed4a16083d88e52f4ae927d451473d2509a494524a29a594524a29a5bc4e3a5401c0e1039f16a8042b5bc98a542aa5660600000008006316003020100c074402b12ccbb33453dc0314801277944c6a549c4aa324495214648c01c61040080100000308102846201b0050b7b852d72d16177ccbcfa856c15a7ebe85802eb1b02341cf1e049cf7f3672e4be7c9b0bd6964c41612f4b2287008832d7b10a111cfd06f9ea0f52d3a4a881fd4141bbcbfacdd972747f6cb88a5b6c94312fe119987fac4954f087dc82213fe4f5ab7745c5cface1b1ca0b5859f8c8a006c5cbb0cee25c59a71c35f1e43ed0cadce7cadc3a5ccd398718b857cd21e717c73ea1a88ff4a8622bea4b4cb1ab99e5497d2467eb9f219b7c091b9610d788749fd63b9b8a2e717a9b1396e25612c80968fbed8dab93d3c333233dae7e65d223a888d30edc2e86ed380d319cc1e512e82221b70bdb08b343d380a3758d10ac63da7852b0c8c63828ee864416e96fe8b1cfd1e0d0029858f2795107b54f4574bbd70df3bd6e1ecf6865555495b3d3dc4694a04920b0da3443239046790a4c6d5f6fbbb256a5c2c9a8714dca363dbb343326116196daa5711eaab1adae2fe2f4e8baeb3d949ae14b520e58e1514cfa0c256b7a45144c6df748c099e54c8fd3f1b2c2d8be5e9d36dba4f39ec1af2ce261e38bccf6dfa71ad3145b4a1db464bb36a8a9ce2dae10f3da954adab68fac30478a2aab7eaec988edfb7717e901459f70fd64a331447fb6d5085f325b821ed7bd4dc9d2ddfec0b0f9752e9e5ed7d1f995e99639c6769f3513270d8df9275be234634a068736063fed4d514d86abed2715991cfbee53268ef6715c7ad7081ee33c652370becbbe7cb257ab21dbffd50a4900cc757839fb608f8db99d54eb9c62d8a4ebea53fc283489adc1ec857176d13cfd87a10618abac8797dccd5cce09124bf5459ccb3874840747139152298637adacac766de6b374a1b6ad6e31086ea7040254e84dbf8bc65b4f43c1ed17434c8f036bbe928e4be9450406ffa74d965841b56519296d3be92b0c300d6395170eadafb3038d3c834a29b787f46a7be16750ff002502642c09db0fb019710820056f3be0806dd70c76b0d5f69a8624e25a96097f5370e7dcb4a3b6f32e829c38737d3524d6cd374650f5523c0ca06a0f1a6465be77e65f8dd2513f1db100b7a2384e1db683f234d48948057533d2b70e1e43900c52660781d6cfa4ac2f730b030bbd35fc457ca312fe4d71b7a6df2083d4fad4e4b4e40e355f74b0fe8e6cf911d18446430a543202f1506f9d745585ce61b9dd8b0d3826dd4b8482d438353b2c51d700d49104d099f1c79c96c026cc71482d75e5694a5d55d6f8656f10c8f9523a1d75196617ef8e3fd3b36ceb3cbc88bbd3d7853db93e80d1d8e4273ceae154f6994a73dcab374aeb1e4083ee8094e64a252d95ab77183b9a07d1013c269abdbe1c559dc58febd61858e1797d1611d79e1aa5c2e3cce1f88936ae906ca5ee7ee219dcd3b81cee22ede8a56cca33c84aa3acdf25bebb049a4ae80c1e02ee7d108cc0ee125dcdd2338a4d2a6acdbc43fc460b2bfe5fe695452edc56b8d0abb9cf2fd61aa8029c5cbd36fd2612f663e45f742862f1034b7703a8e30c856f362d5332f0865d837a9b15054e61ccd4d83a4b5fc481b0167ecfdf5565e8ed3458b5038824a643d10c8136d2e9b04f019b7be0dad3904aa91df9a2771f36d79c2c80720df310fd8a3d644e4f149a5a040f882c782001de590cea0cdfbd966c24aa7e52494278fc8b474571ed2c612101ea3355e70ad3c1c8fe43f4498f69cd9d021f29c2abd1bb503f55b79c53f62704496b7b58e31b5bcdf15df302eec6ab2ce30a7ed5d39692200790cb068da0dce889855f168796e469662456d512dd326d3cf528fa850955828717904ee467ec8df6f8d5ac5b80bc8aa472bc9851740bc67ae7f1826fb8314a0cbe952b93eee394d095bf2b7e87f60d472a10880eacdc91655719b8f724e89b6149edc772e1d2624e040f8f6b5bbee5195a9911b2811d17d8b51fa21e6bf17b44190eaadf2a4a6033d8243e2669a0401ce2bc80ce6ea55de4dc0e07cd3e3e0823cd6d25b58b63ea1eefafc7945d335fb424ab283591d0cfe735604162bc9d211a7a39079af1296043ac1bdf9cc4c4752dc14203bc496a55b92114d00c7e39d2c5e440989667fb7205c3dfbf2963780d589947a7156983151973014ab66abd25569ce83f8206aa4f385626611fa4174470ae6a434a650cbd8c555c680be0fe160f0d7e1dfb64098c406bb130c9ede8c95c9e02ecbffc2715e56206d5c305046c2a43adaa5b258002c5906d3a97a380f61d2b631d0c59b4f70152e506e636daece96079386beb66a588b70daa24bb6952fc41e88e02315a198265d62040f392e6a1023a98cf396b2cbb09fe0bb51615cf4c5043a6a9827d3d6d10e347411a2810b44f8dd7fb4b372362216af60cca3d149d476d969015372f6822eb7cc4b260a59f392d95fe8a9a0c523cfac8cd5028dbc6bfc4d2c28d84b62f2e1c52f59e2936e9c503f497694dcbd04ffaf9d6850cc2b34ab286a5e691f233a24f6c4097eab88123d11ab904c101c9cda5372cad2d2587510dc30dec82f1045814780b70a2e3993cb9e9426c86ead45d740ec1e505188947a695054bc5227c8e9c88db6d40932617c906126f66c74b26152a8f414e0ba13df14af5122c5499964e88cbbc37e6478c10799e6d48f4d40f1ad9faebe170704bcdba79ec0e0268c83fc5b9cb86212eae97aa14005c808a11260c817358089cfdc63d7032a5b87cbfdc2325111ac31b5bc6fe41bfa478b3c2993c9c7e71f51a5a8c878422f85a58b4482a07811b2d7bfa0f26fd9949719e85313d23548e5a2806dd4b12aebc00be1bbdddfdf0f9eda963f97d0cd4035036c15816dec3e77fe8bd679af4e020eee550cae59325509205247ddecf70226ce1c146efecd700f354f996aeff0701f11005259a1ef83ec38a8ad069da0ccf4eb0209911f694977101c8d08bb883ccd5f4f44e1efc83eee81bbd53af1b718d0d450ffee29bbcbfb7a34be6d9c850e52be61e1ebf002e422fd85d840fb6163c8e1ec1e4ff0ce5c14a08e16363e0e0d9ccd5131ef3490a8f7587ba978824fb0fd0f7d9151f99b23496155bcd65860ce7dd7dcfe62283879b5cd585abf65d0849964e90874f02853ef295c7a30c7c2314a06aa591f49899da98b190b71474355d53c33fce1cd83e5f595ef928f4b8ff204f3ef6eba78b17e23024d984465ef2481980183f04ddaa73c105537a2f3ba39e5506532141b7d5bc025bee691cc45a9b87f469fba82543354c895dcbb372eb91b3a4d0f83a886b1605b61293621fa7d19291a255806b6ebbc24ba681f5ff9d28120411621791ef2350a36ccc8e013ebe6b9f31e872b605e8e4e03ba1e21fc76b8373764bd4829fe60071beff0282006116bfcf5daa6bc8d0106db4ff38944d924c9cf93b2100b21ceaf2023c7724397a36bc8a41affdbb802b94ece0668c412eef9c183daa15aa4c67657c19814c0e00c5bd622d2563a195188b09410421c95459488cc86430a2f46d451241ae6f69deac8b64ea811182d4900504b9cdea7c9f49cc601d396452274648b19433de94eb3bf04a52437b25458608930397b8fa6c94cea80dfb0941987ab4060cb2a9d5bc8505556943e2a51680bab128f0d8d2a2ad2be8a50aefe7b657f86c95566530abe8c37640decedb79c4263e53abb2fb9a614c3df6892148ff6ac84135a684b1b63184746894c1a1f215ea0474b187e4c296a61b706739733fc80ac093b2cca1f5aa4e9104791988a7cc6e993370eae80ba85af50b732434f55eedee8e4e0c0bc9fd4833238cc4cf01d92bea433aff78cf20e03379516cd1cca4472cd79b9ce0cd64a43231128cf69809ac06d3a85a10c5364d0df063909c5c056504f67b8edc23d45a44484cf08235363af02a201cdb66df592c5b248a5b3b391aa0c103f862c04671a434c230a36748d15e4ee333aab7ee8fd1dd4095fdc1a9fe698c3b548a788f5e7998e475afc90dab515ff6dc43a7ade910292d23cbeb931ba134fa90b64ecb4f7da6d5d969946f4be1cbfa79f2690cfdc4ecbad2a0e63753d9d0116511a9a36fe21eb04b7745e3e1a365d2afd7df4abda62012a3adce91ddd2454b51b32b233174f6b2fef3496e4fd60937ada2a6321951ed7a93fd1073e8d1b2f36f0c32ec6ea2de80807f2d23e1624160f612b2db7c823f3733ebe1f4d30dbd4e53d9fa9338f6d306eed71981556a5d31a41a3af9ba4edc25b1a52a00a8c63e3f58a4ad3c835decbd7c05b4bc13b012d13808f6f843d4df99a0150e82f6bd54c1761058d91a8697e37a61e70ea2e4d46e1e6282d04fe09dcaa94b1e7e5d65467e6a761a50374b8fa00f70714755e5082e132a906ef117b62389c1afaca726878b25a855d7af1effaaa7d0de5d1ea324b9cc7b0b97446735c43a5a94d534a406b90d534cfd71968e0d8bf7fbe0da013be04049df76e1ff61c330871b1d2452e83caafab43f1d04a4fca113a0b17ff1495ae2502e08950009e91a5e1089ca2a75f6e68987c569a811ed3144ca4639f177c74c38c618e570c8802208d4eae27cbdf4d4bd95207850e5b10ed579fb27214a6f62edf353b0e52e9b277f2fbb3c6b6d7bc1dfa78e127076e18269ec2002e6c52b637c662de88f79b73aafaa0984fd5314fd81a5b65f3a7b032f4775646ff6e2b830fe21d50c4fb81e32966664ab0d73512c940b5abaca0addd3edba0b4601c5304710bb881f63f69c741a83c464ca074713bae89218807952ee712ca9fdc6315b73a997829421c276b0a0d6628206c1aa0a12871b880e2a60aa7c9771c80301ac7cdbb377b46b6cebc39c1a378d27f39c702d18dfe317b2ea76ab033b60806f39367de12004aacba460ff7faed70dc96e7d0b03fa8020dbec7655639407c7460b661c6e92c482df2474f1f6f1074c11fbdfa1893d56aeaeae5b7af1a5eb20941910f9d026a30fff075ee6c429181697d40ac0a7cd1e3f0bdf561cf0606100354b2808b67fe72ed156d5b9917cf683686767220ee324b0ed305f81db51403e153d66fe6b484c3caaa87d7eb792f9c7bb0c8f8986b39f8386a677392abcd7525e103d3ebf121a0987c1c81311f4da4b85ee11c100d9415f47ad0fa9c3850cca2c4807cd889ee83b9abd8900b2c6c58a4d6e4681259834e93d5ff08674bf9291bf94a96a1ec6e7c7e43ae0139c918c4aa6a411989801b7198c0a0f3fef6c19e73061013f45213ca08123637455787dcf189fa91de7a0176317fd66e7bf4bbcdb0dfb736aecad21cd07814fde5624d81f7a79d6602ae8b2947138897ed048b04dfc108dc5919126ff72da714e5a1c4b4d3b05bf1fbd0635b57025ba7cdd9fdb0b2356218f43a60e7a4310be511237885f04e1de503f95ffc97573ae19ab1421348565c4da4cd9d63db1c689d8cc18a74fab59a0cc5a8ce0afa6262f771df288d294920fc74c1b6c5cceb5808040206bff9ab42d656c3945ef735e1880745dfc65ca3baf552b7730468005e37227c881a4b9f94b1c9ca709e536f58d654ecff336dc011cd89b5b94d07ad8f83edb9c6beeea9ba2410034374094925c67310812f0f2bc02370c1247b57e6dc2b7edd2b2b67b957729273861f487c7d001d164967f07eb93132abfe7ae6fe2ec16341c2adf3d27f0a045228962c5f4364192203b79b245c91da88b01a76c873d2bb43d10d1a7eeeabd86f4e28aacad56ae22419288870955c05025384c2585a99d10a5dcf24247a416ebd4d08e32abf19409113238563aac693f44b7b0d114430b31d1a495ac1b33d336ce60dc702045ed277c38d637089879886b7e2d10f5d5a564d2ff3f0c8d5105e3cdc23118a44d538fe32274a9c0586a0c82c81ea5e7ad76e766ed03ee1a53c56795f9aca30d43542462ed4620f951a165dfa11266ea93a3d96cd1818017ee69ef44ddb9e959c7b0705de269d5abfada1dd324effa2ab1b923d51ac9f75eae2a6a3d24b0b0bbffe3d012e82ed36e14cb2d12d0f92fad567038bf3e213189a4b6064322b21007eba61d632c76b06202cf823ba8d07c280345d0ed8b47b0444279c2c32e0ac40df5dad54e1fb0b546f9ac17d76637a73066b494e80b9eb89e3353a03cd8767b4bdcd1e370f3214d214ea8822aea093857dcb50e39c1b3f55807aee18820ba897d4696e0500519f7f4563a3b9e71e10eea54aaa23fab48faf22d5643ca1c9f58a3deaf31beb33af26acc45a1d3daae05abbaf8d1b289ec445fb27bc2371f123f7b6269441263c9b10003f1d106399e33403a66089faa79b4d43b1dce4fbbfdce34238db88c963be6707f71cd98b8ac373320770150fa91e6226863c00c972026650cf8310f4c526257cb7e0ce3f10a4dc7b6cdd6921c5ac5e0ba35d5aadbfef3c001346d8329658f9d086277afb18398846276069d1455a1a971a25634700da92b9f53cb99e0fafb84822e0fa2280fdc7c6d306febe80552a853d947e50ddd678306ea68e23ea754e235ef129b714a8ac8fd3451976085eac15a1ccd364286156be90ac86aa7623c56641404ce4b512d78dae2337b25dac83c9f7146957a7b5ea72f60e6ec8a13dbc7c57fc03dd94dbb901b38353339c33fe15d1e9d0be03e4988681eee433471797e3842cbd3c3c972392e90cccadf9b96e0d013a8227754ecef510f9ff77850e8826b5c4162c25f6b73f3e381914b185a63db7bc1314e53f27e7894e4e19c0f89936b0cd778b1ed5234c8e8827c6321bf35489c5fa7bf4c861e83f0d0b7f10e067d7ded8d922a5461fbb8f0eda43a8a1865a34a150924c478201d513d5b0588ba15f8f85539b3df889231f42c72bc24082669938080ad221fc51cc59a681881fe694f8c07be2dbbbf4f1d50c5eccfb1ef9ee4cd4f8abe862c07340ad49f3dab86c04e301426919ea8eccf78519355a183e46f8dff68a0b1d42d2533878877c4d3077643238617c8a6c836290ce36c4949b03fa4e5889d3d05373565afdf77b9394af52386e3d024546a7b14c85f48a14865e0462923ea509d183d6a06fe52b4d0e40e57ed33f07836862dbfe738eafbb625597e923d034c11a9395a0c8047163430ec44be8c87a77af9f7a42fcdb893d730a58ff6605ab1d449894d13762bfbd1c74bdb1f72feb34d3b477147010e0dda67a6ec75ffc5c20f76286e553addafd7d8c0f73994b9fcff0a16453f056e6c1ca5c659a626db5078420dbed190c92684985d0b174a80a553efe92a181273071cc06709c23dddfcddaac2721adaaa3fe221944470064c7ee6d37ca4abf19108b6ed3dcee7805226ba3cc7ed8b74abd6371ddd466d2843924d80d4364b575233c12731a4472afbe3b0675a32c13d7a97ab832fc5184db35c9597c3d9ad255215ff493dc0723692632bfbf78aba2c96b15c1859ba587a27e9c54409338a2436bba9f131594054f0963b88e98a28ea889320110c4d7398d44a6618dc93b1bda8b48ab35cdfe014d4eab9bd4559456575420bb97609eca4b88347848fab34a161a27bc3c031e7a0f75a05b381e1d7a2d8fe8ff67a0a3cf20c32bb307e26dd4881de0912ef24c39d05028dccfd09c3c0319909c415aebcf1bc64aa415fe6911cdaa409f5e1dfe9268e3f9e1a3c6db9db661d298ec3ff258fcda44bb751a90e5690723e83a8274888d424d7e9fe66b6e6ef7558f1553bbc4a2e1d25086465f6d2ce9a17b09d47342ce5c06b8f5b11b0d405adcba565ae5f8be0ddd0f25499e9c06fba34eebd3a7f40f72812011391814aefa023053a2fcb0c1b1545743b1ee85a61195da35429b949d72590b2b4a817e53a3408f667d966cfce1047785e61bed858788e2287cdc1ed8be1f934ac2af88c52bf4a332915fe4e87f62f456ce84c6620308283e22fda0c7a54db219c00b33128bc288cf8f476ab6acaafc2598321389ad6bd9345ad13209ea9c1ca60fc47eaa46d8bea5e773fd248008ce5e0e8023fea4555379515ea4441de46b970600a8a2053fa2e54fbe188e9e198e938ba359f9284a3c1892c514b078f886b72537fc6a08b066b412880dcb13271c80e8a99bcd334e3d912a737198394ab662e98e1c85ee6e725a0e80d5dbfb5a07c58c08fd2b2fb93251b824424295eb621d63a7517416b41739aaa0338718d7c348d3a7f444f226f121e0e510c48e8265c7516ae18be42518d0789d87251caa2b885e00bbc9235c719f778cbe806b20bf61be44353fe1b0c840d440d4deeda23966910e236ec0dd4b8ed5f9f559844a6bb0a0be492aac6901fa27a9d2b416a45f1154a61a16d467095ee92d1a1644d8339e8110f8c5087648c6ab8fb748fb1c2385c39b84fdeb144703a5ff984b71dafa3ad012b80f26893621b6384ce4368b9a101aa38b2e4e6c16878377eeea41e7fcc824b559340aaf238e0e2e03412319b93a66963a57242a9cee5d8fa8428ad89d657ee9353c01d79098dd7a84283c58105573498a85feb194a5121975f2357be6c88bbb2c9738d487008d67fc387b39b378cc058944e1d7503090e18349f027e60fe8b5b1b9997b018e6cc1f18a8f5602f1d4f4a9c02c8bff57fede1d2720dba9d1c3fd8d3b5c1cdaae47ffa7fbc707088a5e22a748aa05c64a82995080fe7d16d90f90719d880e7781f9fc57378cbb0eb74e75541caea003f78fdd7102d25e1fc8cfcf78bbc39672556eaff0a059cb12c86213a3d12f7ee8b937e0b2c328d8705fb3be73f5e0a690a29857b00c467630dafefd6f8b7bbb8d9472ef3e9911cd2bdca0c6fca3f33ec46b0da293623b06b43f2411708f1afbc8885eab26389d6807037a1363cfb154902b9d9503b758a36810599ad81e329592d9757292d1b09ce6140499ee208d070357fb8c8a1ded74d9796118681e490d288627065a8724039acd5303dd73c94e0e282c86186fd7239cf65e1ba34383861b255d8b4d5be0d5b2a27fa5407be942c9ac50a9fa2a2411ed749502da1f0c146e415e8d86f89795320ef24a350a3adf83e017259c35a260316ab8792218683e9774ba9fc2c688e9767af0de90c5dc63c11dc4060aa7eb050dd1b13c5a61b2280aec65cc941354c5565080bc71c75b1d3f5225573c76195e8778dd8485e7af210c612c18a8696d350d844fb81f0eed8978f4b04124b18d8596c4954e9dd994f490f2c86846cd2d6ef1b04d55bb17f9576401de5c1ac68ced9b197eacf88cc77a709cdc014a39064c370304199ae3c026f63aa2a5416f9a23b4517407c1850ad9a2242cec0ea8cdd4b32179cecc60526677689ac72be21d9095a1888d7359d2899a98029da5f25abed78d370dca24a1c32b2fa30b4dd37a5b25c28eb4386e26126b65e76a8c28b54363cae90e703878d2c8d134980cb8d8511f82020714a7ca4bb8c3f4680fbb2054f46b9302253f50848450b97fab682db48dc60571cc1978eab65b18a6c642d45f21252ea9c7f96d06c2db32cceb31621fd41d2a32ca0f5a33a48157c963501dba54eb331a67df940a15be7b4f283be5812a60a47e43c3211c4d91232343e125caea5136f80eacb30abbe899382b96a1c06a9a7988774b4b2af892c458f111310fef1a88a4c2ddddd583de6795f665826fbf77c20a74277d14ba90bdeb450742a76ffb04adf837a43b70ba376f4e167f63226b48538b68a9cb4d7ba799ca44ca33283e3d0a28f6cbd8ea691ba7d630d62d03657c0d286d47dbb8cb0241167af49d89762c78712d0668f96a51a43a46828c9bec1eb6faee4ec250e1136884ec85d3ddea12c25524290c94a222af68ef1d29dee19954147c62937b13911b67c5844c25e7dde4a81473ec9c424ca3ac24849d3c2a09001930a5e38838b54b363ae2e85f2175a73a618fec399104a81cb2ce43212808fcaa66c3352fa971a73d18bc458d02c784b48399c0c706b875285091369b7cb72b2dd34b29379ac7df70c069628e042fe5427ec4a69ce248e88d00537d17a22313bf8281b52b42d33d76fa5918da989367b06082169b1a0515e890cf7d95e5e6df1cdbce7e6810997218ea75ebd22e121c40b12e3314f5b8c9b2cf603099e5a7f4d2dd303b6228fe76b1f39cb866081b4c008829fb6464d314e49cc3f9e0dc82b7bb71e5982e7103f6797cc99457087941abe703fd597a8d28de0beb28d63fa5a39001800d0c1e0382df853734a658c2a48d02177e82f10f93c47f2aeb41338075d529f93d7eea5420d7b00215bed716a9cb242c90e7167fcd7ce0890ad46dc37d0ad8fdac14a2d5941d3fd9f5f49f194efb005d390f2d98f93095c42e71e1c6f78186c48a02e660b617b4de049f0bef68ec58de91f567b86c2749d07a438e776c46e07364ba2ef7b86d4568860900db054defc3e78a37d4f87b20ff8690144fd5f3e1d91d2aee2a1f5802dca4eaa067af74f7eeea8211be87d6d5e0f3542df45e961c21880347963322b4178bb0963dc7120a2a6985705ddfc473b248ca17e43c7d19c7308d77fcb4d07e41ce934fdf9058fa4809d908725f1c2f3487565a291acceabcedd91003d93d3e5f28a6047a0a11922dbfd0900a8a2fa1129209f571197e8e24570029df4a01fa764f8ba01b01f5f152a29025b90fae0295c2e736b5efa5fc6d680b979818ea948407ec26be939cfc8edd596ca2df7c5c8e5648bb9b8e606f21655fb95a5f73d4924fccd54fde6e9fc82a3a7892e5b613462247d27514a1b0adf2e9ae7903265d88659707a9c44a788af13afa90c8af8229214e5a846a1560d208739725cbe61975620e23e29dbcaaa8393ff4f5ed4a6fcbfe2c555b9d26a6e14e6b7f49058532a8212548237805f464a71227a7b246532b49b5cddc4b384b6081954f6ddbb3c4a6ce21a6d7b9b45696919104dc20cefa9ea303275aebe6fe4280916f177b34197499a06bea35c194e11b0d44e5b9a39b06a710fcd9b3ed9d8ce4ce8a7556cc29786f5a34e8c10f0ea9b0b645b0204916c5fe0214d3dbaf500e6dc3027e92a99d90775dd92d644551294f56d203eb720f79eb559e16aa152f228133506c4b117c8dd749018ff8c60b90e0017f33aa436401e88f37c327e99f18f23da94f4b5ff952eadaf9317fd109367b2b4a0839c18c78018b20091b6cda5a92749e8a95c457b7e7d2916b161c9fb1b1b615dda027a621e054137d34a967a4599bf0b0724cc4be1634e7d84c9e12a202336462db2b28ea3936d694d6263c63322add833e0c0af5493e33030d14f177f3606b5f9516a20d6ee81e514cb2fe167d26d41112a3c9c066ee24c941062abd3433dee00dea29df0fe7a1802ad618656c1bd620d846206b5c39fbb416a009ebfe5cc1c0827106bb8bd84bbf9361ece1be6646d5e6d611af53c5ead1c36ab39294b21e7c6fa997f90a61178540200cd6e8d90b8a0bc12bdf1b6a836d2f88310c90bc2f7020c952f9280d0823a6652791241b81b80d44b289b8732fd348ff8e7802f41d40a61fb54ccb0477a469905fb4a7b648fb7d34164e99e3a99a97758a68a6ffab18e2f035c6a8673c509e443c5a936cb69367503cbbee25a40440b6328fc3e59e6225a8573568b4d5c887c59b421e8490eabc94b296af4a57ae49255a113ab9c8a3862c8c1b8099d0494bead9243b6e68f73c01563a42053d8bd156b79b64b10c0c99803c224d5dd3dc44111312d2b0ce7d7b02d6536b4a2a754b13f85ea60092c49f77cf61dbf575c6d249207b16c40653b4c9356a82e5c83f280c25a6a94db17fd58a0ddeab41c3dd9d3acd0e6b0d12efea746f428548c363fc5c1350b377e0b3beb2356b71260dc09d510d67d7f3d8cb4780dddcaad5024a3813e3ba064d4db33ae9cc44e87d64d72cebe0595753f0afb50f95647fe89a6b1c188e2ffe55ed6752322482062735217f1a89a8f63775c14495225b6793078b689ca4c1eb8fdf774510fff6ae73b0b5d015477b2a3d3117b09d8a1065d34ba2f29a80c28a901e945e0e0200c4d2c1a8e77adc1b01fa30ad0511b0c9dcb9a8be73b7d6d48c29d92ffeb37861a33f21a0a918f30753cb4b345b1ed6973959dc221a62ef1351cdd614b341927b156123ee3592bd97307d210b43b3705819435656d50b05cb0775eae1811466d5969c45456a746d108906f6d284302d5f0545fdb4d2db695f9ec36869f460df2e5a16d71a3424539a1c0ad05c723bec9e79077876878fd7772cc71b39e4cfa88ed33aa1cd4fe4f4fc2aeebd61c0bc8a0ed08c7eeeeb3add9c29ab60cab2a082da1e03514d66c91fc552f8d3e3a6435f859dee669e05c493cb606b442b77d0789d7f017f6ccb4028a1a5399cf9b364075df598517db7c00aae86cce9ebea1b98c6e22785cc05cf3ba0a9f173d90be48aaab52ba39fb0c2e99c4875bb8e3de09a6e4de7750cbbcb4882f25b333cb30ce2eb77971f920c2dd5ce287cfb0d87ed24aeff2f939b21a391080a0b8bac2be0097a38f88f644c1e8c8336ea6856ee682c7e91cd0aa37b1d497834aa5f08fcb13ded80ba92aac9c2908b37c28e6854446b80201b7f2d345ba02cae439fc1299889fbbc36007ce4a5197da56e31b231dd83a018d17de98b376bad8c4fc11b03fd0b1d46042449d4c300f25a7025a1f16f57f8e8eabf9a2cfe45dadb4d01ea02922be7255e81aa43d841f2cf78a98c6640069fb277ba7356a7a0484826f8f06c203983f441c72f1b2266cdb23f0b8ae1bafc17822f6f8d8b49d6d3c79173a4666f81b6795cf08c0dcf5c2cbf86dc0c35418e1d89d28bab89570387015951c980f75e1dbc7ecd9bc3bcfd5d5a8b6829bd40d0c56662ede50176a1008f207aa6d5e635073193aa5e3f711807986c079769157ce4f9b414bc372d3a94f8ecdb905e1d7fc4d6cf0087f39870304157e500b45a10c2c558af2c1ac24db5b0c4a38aa6cf4687cafe8dc00c7ea27ee2f9b51bd04605612234cff7521d68a3b4aea721a6075ca3c527998218e04414ca1338d810498836054d712d542ca80e803887828de80da8871fa41ce4e88da259229a68a5916986b242d7f0d8a22822b3d744b42a8c19b68c1528f1054517b9552b42f5fd4fc0bee98144a7c2af66cde7979acd6339285cddcfa44ec52de3d60a39bedc2f983b0466f6f7df732b38943c9ac966a1e3f5eb332eca2a3a3e1725158826fb5f2bb1f10c7beec06b02aa2aa41eede949828535ab8feafed71b48342e9b8765564b41b96b7f2682df90cbe60fb7e572361102834892fb6ffd1d32308137c8361ae67a37d562c193942e1bc66dc6496940a62739c76138656a13344aab3ebc3b7725868f0bd2fa4f86d3bfb6cc08d47ed39f6af9b804454f79f72fb1960eceeb032767113b77ccc4400a2bd1acbfbedb7c9eced1c564a5ccc5ea24f649690f0656cb0a63a8f81ca2618335acd7cd8fa20ea11b49dcc5350fab6e201ffda2343e11e82a4bc085aa30f4df112b962963fef6cde1bf71c7cebd6d487c84f23d15854db0017ac4e4eedc43c50d8313e5b150021fd10df19d14c2bd1199a0486f7c8057499139438dd905d40cd73d85787c591db77159c45dac3175e21de763183de2016ac0aab4498596b0b06131f81b47a3613f0cd9488731df9f8c313a6a5e37fd76bc7d13e0bd0ac93df335432be0bc6e70e19781a082ff5be3c22a5a6625594cbcb9ac65fda84afbec1d8fde25968446b6ecf6b86010cda390d2919c053edf82e594477bbe79547911082e6b6f85821fd1421ad0986dc7738d8656fc0376860d1a9c42b4ddf7d232f2dbe96dbf515ba971f7d41a538fba46e24dda489e0fcea5338f73d61bb07ca8bbadae77ed2687e672852c6245f6ed771fe78dd8382eee84fa9717fb6444bd6a7852658e62a84d327976173c0c9f8782da56121ace03b6e3f5039bb0b56cb1759af688ac0c8aff1982abfe31ac403c1fefa0789c2991fd81d3de958936f2885652063c282791a1dd7cdc367a611de5dab895ed17736510c2d9da22b6d408bf1b57f12ee33c35da73ed341b3562aa9635a699438558e33d24aa3ae2138ad05252c5a434250f1f3a25f2bb93804e621202b0f443d38ba7edcd4ee523aad4300768bc44e81299802f0b9bf6eaad0d50a84900352e604b4f131178663ea7dc06873b9159d8708277c4b71feb9e9b7c4af1886a8beaf42191c799b2df4c155a16f259220369e56c79423816887c5b3cd070ccb4eef9e4f5a67a04c833436012195c830665f14cc626c3eaa5c6b9229e34e04cea8613c9dc8f075a97fa70d5c8f02f3bc333edca3fc10c7c5c6ea57c0961cce55868010c7ec03cdff29950af9243c47b6cc34a3a0ff2d295f4dd251d101f6dcb7497860e4404767e62e97cbb2426449ace2f0b2d33bd4a7fb7c0aaa615c7bb8561baed4ce10f8c382951db1ea75066c23a2c32b2e23c1b8cba0773eb029e0164f04dc306dfd605514879db322bbd1701156dfae9b10936ed01b59f236485fd932c4772db79de0e8ad10923bae9b704fcba9555d30a7812151a61adc8a9114cd26df665db09adcd4f7dc73edae10ff70015f6e27685fe60b6f13b5b2a6963757b83530140791c93b81b41f593633d2d608af5dc449f27692d3276192a9c652b252116b4fded9a2f4b79739aa96176a0069f22195066560a8d6d4cd55a93012373c8f7855c94f1acc057b952e03906960a876a3025b0154ae460de9c1bcd6c4e096cf86f6e32b0aaa146113fe10680bd7c070e2fa5031294a6b82a93c4db2fb53b7cd58bdb1b5d547115bc827d0647155ebd4ed4d1aa6916cb0121241f5b65122e36cd260b97f947a4a9a615bac3fc11ab88e122cebda67715d69cf3f8ba81b7ff3a800c96392692d310f91a322f908fa17f499e21ed23012ac0684c1b487b595a5d9e230daa49255cbdcc1da23e4cdd1e6df4bb57db00e99a24e28024a0c56c252d221010f2572d01ed9024b706c20544a9f58a0176a10a92a2fe179d3b5a6a0bb3f7e587ee420ec7c7c47b4df448aac1ec50ed7d54ecf341f1d52a4611312130a5da7d8c73ee028f2b0c9ffc2671877f7bd053f84201648c33b8ec85044fdcbeec08b47528072547065297a10c854176c4be04c70c6015ce82d55c9f66e880cdac5f50a4c2fcfb5666bf020c0c6ae59232c2562c05427465af3dc0927c05e0f107b9568a73db521accb012ae8e6d75be02763b361f42c5187eba614a70e364d8977267efd50a48642249021fe208b4df35dcf3a93a4550df09001a801f3f1dea62ce78083b9fc9f185f10ba6cfbdf2c9f9505680413eb5ef4ce63314b9c7a9735004a842603575920bf681303f2cc17b717cd766f7bd90726fda5d8752b50760f26b5b7530bdda43372f0d88eda9009af635800dddcf22b1578dc92b61ba8f80dac1b6edc337d125c0b813617dd856f30f31cd0840f299c490248bc0cf6b5bc0382d5881dec6928a41014b2d133b06c6638cc9a2d6eda4204ada02b63e0e5daddb70fe6ab4e5ac7d4a5cdb88e0b6f526e2c605aa7a09a784a992a90465c8fc8f2895b2b6e75840e2a1f1e19241cf4933442abc96f1ae6549c8a920d25682ced4a2f5f3f52f8614562a0485881203ea0fa428037dfcec2cc9ca5b861adf7853d8daa279d2329547f9d60c66bcd02aff3fa559705c6ee1daefa3582e5a0120151d3f75e4c0b4c5b9835c8a8031abc1a6de4e7975dc4c5903bc27297e739a64b25d4b5fe2eb126d18421dd8c4e301d18349f4ef6b8ff59806985079219176f185713c146ee0323af22ec506a0ac23880ea55c5519b495701e6481ae8c0ecd9ae7ad1a9b219cc61594556a406939a26e4a0cbce6df87d51b7d7df434d9cd6d023785705c7059c9cf84df60686a3e2b57f542b53c8349e382350b31f209b16cac9db278b1222d4b45b9f2ed9444483c90c461561783d9e480f95bf14b243e858572d02c13e9b8124832628a4e74350de0e1d88ea1da1e159358119e21aabab0ada80eecd4e6d850353267ab736caa8243551a580e5536550494a588c2d9e5e22215c148cab5f77bcef7f07ca23c3306d353c6784f094a6e28be1d4a495181d766347348c5052b92dc54c824095e1d89372c27ba1b185acc4d13973a9b0b37c81070ff7132598f40dadf6a80b679de6221a6f34a891bf395220c7d6b19e83a8927bc6f7cc51a28f02676b7b774d645384c4cf1d805000d9907cb1ceb02e843433707a86698f1d0430f8a1089ca82693b9b130d134f0cd778fb8b614ec634d281a66ad44b26e6d7b0f4af362828daa2daa43183a92070b49f18c48be047e53268ec851c7819904f30f2941340e59e67bf191a53c25cbdcdca56867d3c8d3acdebebd2940dcd6588b7273689019c696065c43567d90fc4fcb4c7172a8c50989074238e2ca7a6eb41610e29f0e0e49101cf39798dd89050315760b5e9b3872c1f48162f5bcf224f26eb978a5d23f9c07bc5a331308d1312b7fbc2434ac27cb3da76d1a3121626e7cd5a89fd4aaf6028cecb1aac29ea88c1e6fb00e1166551e3e537e160da039dea8d59bd4ec5847f5898176d28189811d289ae154fd655f1e1a2d3199bf9f2338cdafa8b9117abe48a3b7aa2fb9c2b4411ef334e2904085d30c67cb3d4a91b226d27566fb70761eeb67bcb3e9093a787ec9340d1ff88d0b48d1e8fa773923e623c2329ff4353d342d806a6779c722d933309b7af8f39f12c20ef0ca5c99edc7828777d031d511284eb63db00878a5b44f830a9fdbf377daa85fb369bed4e04de7455755d16a9eab2ab09e9ec8a85e6c214ea72359581b459103c16b77106e29febcf36317ea55ddc419572e62c4eddae1d537764adf8ecddbae0d72748614496e599f5022a71c12185014a461ca2ef0bf1613d4c8a484f9021e5d65a4bb1d2ad925c8e2b79adae5d30b6d810b51030d4dbaf1879e2bfe0e994ee2a4634eb7ba7cf8a917960863b9546ca2d292e8e3dc55afad1d1a8d6e9c644dd2149ba4f5f4ce772b28be474b23cd679312f99075f1175b4449bfed0fcd316bdd2a46ae7175357c422ecdfbbf28eb3fc24355e8251df72c725ae1574a3005c558dcb8d4aef1c84e9bcca9a2459122644e5951254adc68c63401563546ccf0d57e1b7bd17ade4f0a762b1484340055d48a2897b0d7241206d4e36aab645bbefdaa2c749b1076ba8b999a5600b86dad35ca209f33c5fa056058c40b1e3e2ef92a3a5b3286e845161452f45e9958a1282c4b86504fdaa08e5afa2b9126b9ae228995cc9b34f1f5ac5242e6a93f708931627531b3f3b99f63aec31a297d633660cbaaaa9d8c52746b25cb5a59009a96dd05d57811094d010fa78d72bc756735d189de8949bb7aa92902cd10427c03694838048aa6d95194a6488d85c05c0ffa01d0cf415956a6b91d6be4f341a4b81cf4dc31408c96f39e8bc3030f366096b61c0c092a4cc64026793c10a8ad33de854a2e08e0c49ec6d406a8441b69016d257a55dab12c951447e97db10dccdaf15c2ecc5e4b3a3ff058209822de356c91e287ea140fdca172419cbfe1a62bdb7d76c71ed00f6c5d46ef6e9775452752e847c1e74c697803d1c657cbb5a3ef9d61ce357707b91a4367eeca369dad96cba23370c4020a47c1b31837d117080aa1a7f7982d1e4ded0d2fac318dd9964b663c045bba76bfcd3d3a30436e1860193330a3689711ad8b118fd6b05d19162ce233fb307b5f0eb532ac480963e7046e2fdbeb721b0f59b3dfefb6ab4976bc06aa842f05762cd0994aac83438c98b2705ba4a437184a05935c0abb7a87de8dda06afcc7777fbc8f149c2f8a22f0e304a3bd73152c68f57de1af080757be4ec8a537d259879fb62d5145e65baafb0f1456c4880f5ac2812ed04164e09ead69b21f417e2be057ae91f1a1209ccfec2d0605a61db35131c580d9f8aa67f20b63b8531730d649934d65071bc58a1f682f1181c133c18cdffe758a17121e04da159f00603082a54249302930b8398cc693f06a067ef58adaba4334601d0d604e072586fc3313217bcb1bd32cf67fa07f622a135c5b1dc3b77506d10730001c2a7050bb28c38534173ccd310fbff1ecb963937fcca6ec5108cdc0e041ce76b4c0637d363eea7bd51ccf2449359598962bec8a422366aadeca0d9d0c15de42baff5e218de2de89a3ae3ab492f06a00b2b2b5e79fbe0c97ecf1d9ed7d05bd47669c82ccf8640327a82da323f596b48c7a02936c149a6b28140f95fa872d2bb7b847ba3c6eaea96ecc1f1b3afeefed9b67bd03379eaeef62338e032d55b00001b79b276fef90a5422bfe74d41915618d29a9f2e7ebc38c6aa79852a4d1e9e22b5ac1dbeb536b84cd0a7af9174714120bb7dcb4c51fe8b9893e3f50438eaf72043ec4da1e9fea120c15e5674206aad0d0668fe6dd5ff05473f06f4d1b36163bc2b56eed10429179903a7bdb81a5a7a3b5e80d6ed31a8fbce6b1bf778e2a0f9b98536a20990f4bbe9a4cb266de4c668a0f15e375431511f0b017cc151e440b039a77ae09c9c9c07741c78821b14404c06f12ec1016322b4b9b2b89f74297a614954608a3812032f95f179bf7ab0a08bbe1f4a23d885b6e7ca6471a8bd5bd381107a16650b4292c0c5d3a75f8db5f16da411843c2297f5b4318d0479baecf2d46fe9eeb6bfe7a35fd078fcc8c4550fe2e0e6487e9d5337bc5b8ed98c6b70ae596ea7d825e3fc3255bcbf26f7eeab742a66586b9c98c631d1be17e7599ba3d4557be9d1f1082f543632320c25ce93f4d6f0cf45da113488f1695416d9da844a64183bbc92827d88f45e5fb3d8f2b0070114be92278fb1464d6dc6e4112c0aca78c1e644115390415e717ef5826b5b6bed04e031753b98341d31d90113d2c8845309192208d34f61d98d883cd0be120f9e1617a611bf59b7440e0fdd628649469486c6d6dd92ea4f359e058413abe22ddb786632bb0ce6c64b2d8cb6dabf41c90831f820e90d173a000e6613b29b87573be08c1b7106b1e606558a308f3058c26d4604841f424c19b12f28031fe9b9572578e9951f5824ca5f209b13ccc425b537949e288cc556e7e0867009bca2d4fe41099d9b84f2f0fb72acbf934ba43965e48d11a6be396b313cd623802cb3c063062902e78188a88f1a826cf88458ebf2366f14fce55e29f6c65c33957e4b0b1d61df7af63f961ddcb6f3c03b06c428e2cb3bd3d0c07bd9314428edf3b9b00c91a64907dea7fd55028597ad1fc29d689a8f850d77e1471f0467e40bbdde529e9658f799147da6e7cb1d9e2e64263c57f4c1388d05068cea2a02316e776ecbbb02ece56f8b4539a45a0258bc9150e80d7e278b3bcb997ca2d8eff8f3852e2ab35462fe893ca4485dcb6e009d7edeca2ed0dd4655ebf496311f14be011988404e220f584b8cf88a493ea0e86a88c9b4dc636d67ef0f0df6e018d33fea8138c330f2ecf5a76a1a8daad65f654a04d62763071ad23766dac24ce70c46cb5d37f40a773045653b073b191b746f039555b2a91482a7d4bf44074cd051533a84a350921908fd61668f14e54d51838e6f8feee8ca560646c47e5c44fb408d0489aafaafbc06826ff1d37bc2f0d1a15f0cdbf2636b946c43f5d1a7826e756e46dd608f4b8714c223a94d0f305de34809bd3df540bf4eedc08720b97565ab2f4d41ca60cb19a1b21049db4578659f782802e0296995e609cf7fa347bf4118e368e9d85d9eb78acc5ec018b8057b1bc458544f5ecfb1adcd2432bcff35fad4e281cd6b4e5136b8a2178f88ee3a08389e3e07e7eda314dfcd1c1ec0016d332ecdf32eee6bce52efbe5078988441e40c0e148d00071e9f1fcb1f69ec1f185ec88e9158c38e49d67824b73834f820de70522901a2da668b9a51b3797a5c6a957eb78eccead487513f0581061923c2344af9eb78cd0930b898dcb6dfc2e6c22b19852f91678ee43bacda41daa0f72843bc3dfd448ae60185320055ab45511fb200373608ad970579689b34dc521f87ef664abe0242d8a9928c7a1dded451d110819ffb3d0a562bb14fbcfbf81f3df763a27caa87cbc4ab2aa007def6c023983219ef5169fdbd3fab9bcffdf0379b9ee4bb3029a9195454a18095d6049a071cea4fad3436209d59f524bc1cba881673535f00e55034fbf1a98eb27bd9916d0d5ff3d072993437de463c94a05b9f8d791a2df807210fd9e8f0c62a2a9528d4b6196c59052dd53cd89685a601caa5c5718e27741a0b63e2185ae93fb978ecfac0d70b5b27831c66269011f9d0889000810d5fffc30f90c219d0aee31e50c1d71706f7c801f7a1640903762299b6750cc00f44f53dcce7b55bcfae793ad18e758cb2c851a57b6b4d47bb433b935658de90d8ec4a5b4c88a4c9064e825356e64d82281d08045f6982448ba94d9c5be9d48a1a264642e0390da4c220605737f3ea9c71923a5c5eef13ef71baea0d742748267d40e95adc8f1ca65485713e59b2f12597a26e45591636ec7a32099f7c3687cd2844d5ede44f4e0a9809e16c6bc9c74f6fdb960655916491a900e7feb87716b4aa5c7a86723bc3f4f35a9fe8624cd9514b9ec3b63abb354e4068b368383c1febe80ea1c50d069aaabd3b7b935ef569542e14054db77baf3eff82da02af6041167fe3029b6f8a8ff22846d2b472a9c545e317304d65c2632ac66345b3dbfeff0a86ef8049ab4caced584a6010747904a6c09ce37cbef590187f5d941c9e0673ce4251fd58556109dc386cd528ceead6276acf025c148acfd69807cd991cd07f2b9b3dc28b6e6194a7b0bf00b2756d5445f8a9324fe31394334a2c8dbee834a7faca54c5c68e9df4668a97b326f4309ff79e9a646cf6405983cda08108d00c10056084ce11602bd4e45c7511a43a0a363b2af34f44e6b3199028a62bb1f061da705ce4c8ace6e302f2b58f02c7a1e5888b91dd4668af5a1bac79030980b3fd38f933ffbea14a5f48dfd48a28745a763295a0c8eb67dce8acd28791576522609ed80c1073340d4db19c862e0ea2433c484188dede6242cab41ac63d596d25150c9383c0198cf16e8050f7a6bfe4353bb8c6c1d48464cbfa3963aa9ec46686024ae661c03855bda041e14d1aeefa624da4c917d650a64a9c094a3ac18038efc2acb71a0c6f3ebddcaf66b16a64ab121e322e412185de21ec3b2bc94b73167d6912f02fbc5ccd1d6c0692a9ed119b1cb1640887d0e16c85a18daa7cf7ba197fd201ce738aea4ea07bce80d44542092d154c6e6b2a1b972fc81950a62653e4f978b03f48a41814e59955f13c05e523fc50a0a9f9aec5120f99d88f7e1152e9dfda32c0663d5db7602c0035e7500554904d76885c18d35094dd2e56d7fc8d218e8b4a533e87e6a0e5c3c0ccf7044862e263ef03fca0180da65a531052e0e6687c61b050f3c7ac60c3e423e48a836cdbcbf33122421f9ec3522801ee33c3db8ed81632c1589497ae2cc8826346489d0b616a67197cb8bc8e72911b815643939d983aab88263ea71add156f58a1bdc7b2cdae7583535f22fe9c1a32aefdf5c6bd4d13c66843d5e2657b54ab22ae83684f49ddccdb73730b194de1a792018d1110ea1463d6ff31e15f4be92f5dec9887e27df76964e26f4212b476409644ee42ee7b5ca1c816e64d2042963afe941d37d01f38ad32d793b40da07c2194d64f722e10232695cac3618c0ab93499aa70b9b806f89d7cf694a1b0e240c962804c7d2a43ada48c0fc32303a3d5027d465178fd883896f60721ea2aa53b85550ceec32596557ca5586098a68e6cceca82d2cc0025d5d3ae556dcf78bc7771898ff37484eace81fecade578bd3f97479ce9e247d855bd21cada939705258babaca90ec1ab6db44c818352826ba70a1d2a0a791456aa552cd0e0636c35e38251b8cdf27d1b2951b6aa14a500c5ab2def521283bdfa184a25024b2379248ee087ad96617da9cbcecccf46956670981589bfbe912e6d412625f30e51247410a25f50370a6e83a0859a21fbd555b2aed5fce5cb76dcbdafa7272152be07c927a6c20dfab9cad2e3b69cd4856285d2269ca4c58ae719801cda64a6ecf55c9d430de08f2481d72b079294cbc9f360c912be3cbcb41c2d1e18c857c3da10ecc5bc897575e421bfbdf1b26eb68e06f0696f1cc980446a4b48f2f9769e3223619822431abe91d69fa5d9dd4453598c64aaf9f7a1ab89f0dc3fc8dd915f25968dc7a1d9943ae81a48044ac91d65118b25d040e32d8ee3f36ba1a4ccb483a3d2e94d49451deed197e895dfeb57c6806ba309225601b2eb7cb7186ab9536bfe1c6fa21c4824fcf21165a90779681cb9fb9c35a33cc868c4c4dbd138017d2f01a75182440cd96d23b138b2df98358b2da14dda1166c16f6fb5256d6d67f58eb27cbae19782236fdabd2e17a05da4152b66f873345537c9a0853c0a3295c49e7bbb9ef482845a0e540e65322f01e22179823125ec9653d02c0566009466f8721137910ec8a30b65f1276eb88d724a14f7d9f2fdeede0e6ddf5bfc5f105046ba8129139ab6dc6d39f15bb7aecf5dca63e2451d2a9e43a804921f984d95057a98c0428888dce06b97db6a30db2ef2ab2378a46b92c33e0f383840318298182ad131ee607a375254451efcaaf6e62d78753ed608b17f0196ad6cc77c2b0277f6bec4736a61589be6bdd0ecddb30bb5e3a3343c0a51c27035bb4c7c76443960dad1e2bfbaa7b1a3b12455e2afebed921ae7eb98e84f8975a82eb9c71cda61b268757f1d0a97e98e4af67554ab7e1fb7c6e0485e965c0d2cb8557675a71b4dc8ec496486e223ee20404a0918a84f84f02af5e0aa9bc8e5d6b2826432361a455d3295dc94faaa01b224e5321d56db3dcb0a69934911a693426daf0aa0e110224592c6303d6c6ca6105d84a81bae16dad5e9f3f6efdf10501686f5dabea414ab2e6329901971374f785a370a85086433d0658d3a9be1b98ed7027b9354bf59a2fe4f0746023b4d8a5d8604a6c4576f64cc504519b59d8671ac9b0303e19b7f01fb955946b2a4f36ed0e19e6dcb07310a0e0127a93f3fa56f1967239efa2ebb5b29bb97fc38e29827f3ba9a2b43834868377f84a06261271349b441d8b226ac06fc5a34b809f66ee87a61c80a9a2030d2bfd21a8604b3b38e518cd1503b2e67b5581840be9f4d732a907291a069133d436e3e7d318444a4a858dc3306e5ef6737ec00eddf75e2b9ab4bc4b0409011f04a9bcbd78d81ee8a38bbc6447fe6be96cf8b83519e550f7309f0b11351fde20d89835a6e0ee4a8cf335cb9006a7a3409a2e419e66da1ba3b1fd2114429926682eef8f93bd9fc261b57f9f10311b067c6c227d7e4c34e96d57de0693dca3e3b9e9b5d8dc91c9f65c628794f32dd8a525f624fc87672d3690f76b5b67b601bc47e37e1bdb609e7c6f292383c613d0aa06160e4aab9415e01f00500680660cf0370e4e30e8222393938d37733f78a3af139eac64131bd14227218f6c0602018ef0946ca43d8cd940cd0040f53c7c3c1f585b15929ec7437c6d3540bb3abd98fc081d15fb1f3c953fa3f4da0cc422f8bc6a670c28f7da3a89241e9eab9fc6121f674e9a5509c7557239a556d77d1282580b8941fe13d5ee4dc031e19fca686d6b70a372303c6f52be0f34ad422b3baf680c499ab541a0be6efb9e58a3aa32a7411e23a1704ed660603baffdf23911658f3540efc86ef5626c2d96824acb403822da22b98ca25ecc945f338820c4c7a1d7782aeadb0542a6241c65ae4973905b9fa314cdd0e8b61ca9465d7c850c97bdd8b0d6f0b95bb125ccb6a9b18494ed928e394472f122f6840c68d999f5a3f9a91afdeac3912a5a9d0340f9d7b71e32b84fabe9ff061ba48c37b3eed9da06942636a93879dc4707b7a11cc2b01393565498db5b76c4be95dc766074112e321825b8887729d6c5079fd44cb98d641f559fc690659a106a04d6ae2330914023f7b1a964d85254597d94ef3dd8ed34a9a6e0e8c16667845eb676ea1ea1a48348d0c04dac6905048d9e78ff2c6d268a02751fcec5c044d193078d9ad8fa92008d59e94375a0096f7eac56768fac893d33f2e67e229addf7cd623f585f4dfce1fe3453e3c82e338c9519d09fafa7b0b5f6bd2b3a812f1796ae64c1fe3217668fed517806feb024fc236eff7be4e1fb5989709d48692ec9f4b87208dc281e42a259814cf4ac165ebbb0e5fc2502bfb2e7c5b1d1fc1d322f177cc7e35c03c64abe17c3fefe001cdb2d3ce6d3663816c1989e261fd80d7ec228a4f5d58e8500207804432af6ea5cef9b51865ec10a6a1c9450ed3c0695ce3b898f4dda269b045a648f70da7b5d131bea52ba56e9fdbcc1ca08aa597bb8be007a829dca37534e2ea8a6f05ffccc4aa5f085339cce19ca84c83044c31cf9b4f937c0affbbc43435fdf249255a0521f825b6e254518651853bdf7e533a2853fb8aac56446d2020ae53d74a0397e353e385b20b4b5b88f80f7559c978a9fdcd2fcffb20ed9ba2262a165598406448b4c3c9cae9b9dfa998ae39effd010c8a9b938e93d02ea060c9148def39178d6f4b5bb57c8a49d578199d7252d12b49f810b4cded8446d3a7e04feece6df158b85e235bd6701de546eecb0f977cc3235422aa323580772b726fd152e9821689acebe36d674f5eb9b3e3d4e55946b88a656bda7c3e9b00a1ff5136d2b7c58ce718e37774f6676b2aeadbe7435adfddb6ab245e2a354b72202df042f42dbc6062d4b485c8ffe442705bcba0fc4f356dcd9262bec35521e3f8a786c02a2d4b4d2ed080a6d544dc9c11a5ed624d0c5ad429cf7663b208103648be80a8d6cc57d6ac49a47da571e9659db3663251f553502edd8d3febbbc16ca136e9b522236af76480c69e825e0e478a508f8a4678f4a714c3db5ef659f5527dde35264d4672e1bd4980e83e00ba90fc4431029981a900f26fec3a858fa51c3c0845b41c75e898a7d93b807d32b33cd6c43afe8896aed9fcba580f369001c5fd18edb6996c24fc046fcaba41fee550ebe138b9714dfca582df4429a16f1823b610472aa0e3b9f1a6e267eaff4829415bc64a44a8851f4f94229370d91541e2459181438d282cae8bc05c673391da5f65383129373c3e0a740909e01ce06deb1ef60575ca5c56b1e65eb87d2224fbbc2bbb0fcef23db256e41349cbb76cec109319f3730f84979f0cf56640ede63318189f7bee37f86f5c8117d2c6770c2fe3e307a4e297028e61537b9c461a1e69d988999d4609d3d2c112969c4a080b51821f70693c4c2dae1efbba4ca75c2f262a2388a539d935f428c140271fcce8b91e3b65fdb95223a88fbc19f0d8ba132036355b84e8606151e6d6db8283cf660638d27c717c47d91a6e89853d7a64c034b4d9b7a105fa1d2b76dfb5f105b5d5d187f1c0108f535311b63234fc1773441fd2302de80e3d26897f32bfcdf475ef3721d9cb84bdaf69e3036c454f69a5039b5f60dff1509801eee1d79e7e65e902b51d4af929b1a8226a65268b2bed89d3f15d67b8077534c8bae525eef1b0603e7826c4d410d36e2dd1786535a011e39646301d225badc608b664849e4a0b90529d8b3e7d0649a2041a04404ba05eda44c4d65c5794e9a325ce1abe4976e252943b07222cabeed2e89b0d1d7ec61eb81d6742d8f27243d94ac68194678fbbf19596378f1bb20ad709615310c5f832920f18c5baed66005370eb9db5e78ef1a05ae0d63867b41e32785ed06eb70968c71edb502d67382fba37d0c94c65eae00defa5e058c18ffc09723ff8a0d820ed748633ba775cfacb2767343feb2667ab10af87352fc7adcf9cdfd8324b0bac725f378d09a76cad319bc796a7efd6b9d7609f95170c6c872934456f5b9754c5775d9996dd8d29d60705e33c089dd4e23d884ae4691ffc60fe02d10c1b064726434982ca0543926b53fbfe9aec864cb7e20c8b684dd01d01a44a03ca73fe805d30d2a14907796c6ca63bbc8cd8787e73623b39bb8a55e76cd01aec6d8a0df55b5e6defe2463fa2bd11730f1960047e37cf0004c00554310c5064f9f892bac47d9b50083e8c139a712c946b9c824e7e23846c49c8de7b6fb9a594494a19f1079a07bb07474fdcdd1d0c9a46bc1553352a8063c79c73ce59a9271d211db9bb1760ce2f0004340d3aceaf41795cc74ad814a0da00b286ab48a6652525d804a652a593f22065fa3c41327d9e1f3fe5943e27206752e693ce294b51e080a34d1e16326d36497d7f7fda70d97d8c73be9efde9512c3176dfa146257b24a9dab787533008d458fc6e8b7dbb7db7bf0f765d28312d53ed2b51284979ca72b77d7f729954743681aecbfb3909ca639348993d89c562518c75cc84d5e337c0eaf1e7a03d51c885da7f4c1172979dbf13693776fe1ff79e257e5fc595684529bee8a8331defc39c64daecb1efcf1e469ab4c44ff44429c5b9ff1e214da5fbb9a4492e367cd67377a8765c765d35c8dbcb9820d3de868ef7612e1b64f963aefa282885480c37c03e25701013e4895801629c1045051ca0aca006578228e1a143e988172931c032a44b1021ab2033f4e0a40d6c1912e5064868e02109246c82f0c85b3e2582c88087205dae131ac82e5b72a85ac205253d4e20ad1ce1430d3cbcd8b0c49624b8c9a64e58888f2b04bc547aa660ee450f0c9b233eb82adc75d9c2ec8089212fc05eb26f09c5440cfba3504cfcd871f382faedbce88131a39860c18b11e785f95eecf4c0f85e9f0e9070ca39e7dc0383f274424334e610322f47fa3b7404a7ce3153f8b265532a8a31fd31e71212c6249760d397935222314f9b605c4fa33ac2a751116c4a7df81129a31fa369f8a569d8a569b9a56939d434983ef2c595a48cfe126ecad48f1c813dad554a597d31235629b32f234a29936f2b90292823ef5add1f8f9b25ae3c2b47cb7160fbf701ebd9043817249074ec4e4f39f7e57cf4d9c4788e7b36f986b3ed0336d6538ef6a5fd80f51705b578b0a63c6b86b9ad13cc0eb6b06acb11dc5497904a10255ccf14fc13e3841a7042fc00999ed038890109292160f06283130f07555b74106181123e9c785244c0319c608b110f4a5f9408220739708152132f0801922088201d711e904ac0c2416ee0c2911ed4a6a850e5052924c10088244b4ac4900548121b10265454171420c609b01f97c0a6701121f0a065881f3f8069824101498e68a28b951198e0079620b88a820b84e181071e4e6a27c8c107238c40c245cad4122f39d8283318e9f3b17201d0602acb0d9270c10da8108009090ba4e880840492231d0d2006d23305ff1420c609ac2da174d003f703f93418423ebf6ffbf92fd8d6021e98051d40a91f4c074e28b725940e9636edc6aad0b402257def3b4fe697ddb37ec6f759b37411d310f8df17adbe7b099403082432423222a96a3d8e9294786878cca9f5de7b318e8d1b385c56e5538caf4fa38e366ee018777cd67dfd9e3a7add7fbfca9d88aba55689cf7e67b9b7cf81df6922efc127fa62f7fd0e71dffdfd4e13315d3dd4bd6b22fadd13b97dcf1377361299fbeebdffdef3c41c409f9803c83332dab3de5a8dfa12a872eaf3c4d8fd7759bd3a0343de7f19a8ab4f1311e51c40f469bff2aff7ce1a0fce27fec4705915725935a25fbddab8c17d27b463e5ef89f43b31c3a6d4ad12e2df7ec67dab8becdfdfee176d967e900484b6b7d6be7dacf15b31e662ccf1cfb08f39bce99dab7f28b54f0160ddff0325cf7c5781377176f6597eeec39fe17d7eeec3e762f5b9ab87f07b9a88fba1ef39d67f5ff550077e27bd9dd773baae9ed33b2e931e8c734d348f7c66c8fef4bd773dd3508bc9563dc4bd4b135909c47d7deead048a558efba297b62d4dc4445bb2fad5b7dea577ec6aafc41a085c5ff4e3d24cf55bbf7ada8e11ad9516b2402bcfcbcf7d9137a328ebcc7a3dc41181fa2736893e3df510d7e9d735720061f186cb2c4e90bcca41e9a6c45fc83939936a528d37377494374eb178f2e6a7cdfd1c40d67efd21fbf5af26aaf3c7730ee5f1df3beeee4495bced7136f6591113fe22faae97f35b2fdffb2a7db6d29b1e7abd83de0c73c270f5397426ad54bdf83981c6125472f6bd2f8ddc7b3f5c6d36f7fffe8dd2be7713a2814126e037d19b54d9bbdfd6390872dcfff80465fc97fffb1abe01ec879f63af445addab07455adee0cebf89a128a904b71f9ac51c7b1351a09ba5670da608df93fd3d28d26eecef7ffc13b9f7c44e4b7d4347faae1fa2efd2580fcd6fdddc8fde1fbc980c1ddbf4e938c28f9f740ec1b0cff3dfa9fd03ccd8b6d9122c2187fdfa06b03af61ae1470d7f1bfe4234fc7ecc044c81edb18ec16c360090a46ad810a2116bca28b67caf21b125d4125cf6dc34288f0c48dbbf004b48d9fe423db6bb749a73c230c104147b88f2b88e9940816ddf00f5adceb1251854a5acda7eb0a476854179e68ed51f336c45983cb6d8f561d2c7dbf56dfea6ce03f85b19fef67328cf68b36553ae49807f0e1d47d0047ffb06f0b73a9663ffc85febe7fab0ca92f2c18b35e5903a91276f62d3a757f43d278166e75337fc71f8d7f0b7119bef719eee34909c52d29a269f53d2398bbae7de01de63a621eff17f6211f53af9f3cba80343a635a4ce74ff26539c4d35772852c7959852a24ace60ee79d318701a9612cf39ebcf598f3aa3aefb191cf79d2e6292b1263906a54938e85fb9227da49d57e4e5c418f952fd24e79c73d6153c79930388137300c59ae418be7d0ce3184210b081c2c59b14e5994fd3cad3fd182eabe1b2fb3494e49d012c8921e433fbf7696ca9a0d00ea11df4a7fdfa463de8782f0ea5797f569133efef9544ecfb31ee3760dbb66d566d4c9ebca1f9d1dede93a1d9f2ab8325cc3d44c7f9fd0a20f761fd0a9640f72cf242d0903a730e2175e6f6585306b7b4bf7a07d85f79598c09d930fc986b8975acda9f9175ccad8ed519adcf3a07b34f4ea449027e2969c0d744c2400974d77b45b9b92a69545cb1a6c06baa15a56a7be6dea9e7df38fdc66e75eba9411b7e6efbb97d2bc2e47147fab0b6ff0bb7e04847d745444c45dbd3c69810feaa67fcd0aa3d16151ddd63254179ac7dbfd7be674509b2a82c8b95041d9dd565fbd3152adea1e3887d0018688eac2d05d23bfab41d310710b54ed6ceff893154a302b5ce4be544a954eaf439cc510e73ee948a26713f2a5225972d51284dd4e927861095eadeb3a0d00efcdc7b9ed889b126f947ead4cfc0acedb037be5c3d93a44ea54ed489b2a0d22abb4eeab4ab10bbfe8f02aabc57ee1b8a3f95568aa7287d92fdeb7f18851a95657dfcd51c3c79233f943349c3a7e24d19720aa86cf9513a50d952d33ee0e37a159dd7a8173a3ae569b1fd3d49faacb6d3f7977ee452aecf11f3fe9ee44a32dee4ee44f343f969fe3e4df8e113d5d03ceb67c8bcf734cff2275a4547aff9d8d35a56709a5a565a507236659032ff96152732d8de02627b8d8ee9d8acf9a1ef6b50fcf7530f8135138c2960e6697e68e669e6cc5f3d047b194d44f343aea7c9ef0a61dffdd0eabbd54fed4a2ef37ffdf49921c7bcccd3bccd330dc5be733d24f5c774f79d8ecdefdebb9f41f39deebe088526621ab2f99af79a9a9a8fbd8ddef19897f91ab106029b2fea6c3453ec6b9eb663444cd7682197f96f246834cdd7fc0c9aafd1aca799d1794fc3e9a18ea8d3314cf343ddd3b81eeba1ee699e461375ba3535234a97e104c9f43771d6fc47f891fd5dc997fcc8938c4ca397e82e0f45ffec4bdb5dc99ffca9e830481dff9695fa2d2b74a6d3d4ca32d54aa2f5c55db5ed2d2bdb5b5db6b7aa5a5e5a615c3bb6ffacd461a055d4cb0c36edabb5568741faf891eb06cf3e11736a8aa07b230c642461784fe5637b1018b42964fbd114fdc85d893ecd8fb6bfeb68a379064fded0ead4a6f948ac317d8fcba8cb153e91f6a8261a3511511e33cca3f588b1f9dc8cd573daa58bba5a8dce6a8c90949a504fb2e44d5096241035b2e99c45dcafba075d1fbe03b85fcd2f4c439ca6ab07c52297962e0bdf863f047ee8499b3eb8d2447ee432d7e7ef9e69c80b67a83b4d1443dbd0397b1f7e1111d3f79f2ef2bc9a7341024983e0fba2904aaa4d69d3e8d34c3119fedbc7be4ba259c7e6e7cfba88a9e8cb61f719b33ee7ec7dd64baffc2fcd6d1fb7c20992a1d01fe147764fa2ef353772895e73a426ea44a5445af5894b474b2e19b25ec2d1e904cbf3cb486915751a22d7a98dae9278f286866b5c90b997358ce73c3c326d9bda3be82c87ca6a393c21c8952a25d1d17bb63b9efa82c324d3a8528ff7542b349c653b978dbb6c7fdab8bd26b17d3bdafef54bd5ae606cfd52b535a2e317a9e3f868cb155e3c79c3c323d797952704d99fa7853ca76eea44d2b4ed7b9ea71428b9ca67b2c5b63fbfccd5db9f50c25ffdfcfc96ea90330964e7b3de67b11b7ed6e3b334d1eab37ed6d350d39acbaccf0cae0fff0bf3d067ffd3a1265a816068c11b8a39bb3c5ab383f2c8236ac45a6ab42d45da96266d4b95b6b5dbb53fb3d45853ee36adce952816b9be157e7e07ac9ef53fd425320dad74fe965814eafc141c5addcad244b426abaa56aecf9a88e9e66b3fb7b4cd4f934ef7e6fcaf7fe9a256eb5b1a04af2f9a3597bee1324b9353df7a5d0effbdef6ae98d44a8635dacd3454c45d2c31939873fb14e872151a787f2eba9e4323b8fb67d0a0aed583deb718264b7cf59fb23fcc8b4669f1ad99f55f6e717fb2e4e9628c59528fd864d1bcd603ccf16b14d6c13fb84675a2376ca36b154ac94cd62b9d82e3609e5a1d922d68811775b44fa7894c2b8d3be1106ba6f9fc6d9a781f669ac2d616c3aa9bcea4bcdc8a7f674dd90ad6cda0b864381225216a48e43a1339d2677b2b2c5bb20d9b44f861da873d9d4a79cca9df2d0280b9bbe4d8287d029cac213c5f85223e6aca34fa7e814f73fc0301e6c99e6e0b2d83418966ad360381497d126506c553725a6c605f96f8dbb35796b4bb7766b94a785d124a107e5994d746cf299d7ee5dda57e6ddc377194de4efd2b78613822c5ea3f088faf47b74771805e97bbfca23cf2de4b746c7fb384c32cd971c8acf60e1d3fc0cd7c33e7c9aa7b9536cb27ed343f95d9ae8f5e10fbd3e9cf9d74f3dd48ac9b1f9357f41999f2fb3fa1a4d24f32bed44361ffa54d8046aa27be4d767860cfeea5d1f7ba6a19a0f639a08df3084ed3b653efc221b4dc434147b99bf355fa38b646464bee663ba4702d1c6d5cb883510c4be28464c33c5646c3f7fd331efbb9fbae6659ed663448d962e93d1345ce6d2b1f9322f8142ed7a999fe17a195dc43404fbf08b687c46114c3b94ef8966bc2a363f3d14c668a70adff543e1bb5e3ad4614bfb962ce210a7d13dba7f93eedfda35ba8fefafc4e934a5a4cffcfbdaf3c8ef53c1aa7e4c5adad7c35c1d4e9bbe1787864c9f766bfbaef028068a362d874b4aa6ff43639c369aa3e6f31c651e21794ed1b1c59ef36f86645a569a3743f2ec52038304e689ca229c7af0e58bd3dc2169b86cbe0d97dd10c972c7eadf9740dcd5454c43f7b72fc2f72ad1717e4c08e37b5f4ed170598c6271fb6e7b4edb70d9fccd35100da9339588e041123d7842938c24a1e3c4e1b2599b4d52677e7eca4fb98a9c8d3eec08d143c49eef49ed200b0f92a6a453475f94dca967a5bc44792494172b7b7e6ea23c126a070135b02055430a45b24b89148e5862b2e5fe6032659b92ef96504ca43069ea51719010251c95bb2da19828e9b8e4ba251493126c1b779b14e1b4f9c0f33265ad28a59452f073cf3b4e6ff92f9ad0f0a61ba594527aa985e13c564a29a5947e86ed381d29754a69c594d270f57352aff6e6bbb136bcc21cf8551b18ac72dd07001ea1e7c9aa9c61a30d4a4727ae7ad9ca0fe4b86a2357812b9a9c02f3c90170b1e5eb1f624bf90490331bd286d49992a58c3ed6b42f810a604a9e3efbd1ef4edacbefda4637a58f5cb1b2b4f137c33a56be6e022775e84b5df31c7e362ae59161abd671276f69852ec73d5bcaf5a23039fbf9eaaf3066ee6c1b9e43f9e3326a838e756683ca0695a492f9e47be1cac6db903ef2288595fa6d8c65fbf7dc31a131d78d005247b41b476fd1e645fb32102b61ee3ac61430817eb8eef3be29660067f99a8b504fce0be300dddd5ff84829cdb4522d646fb84cfeea9b624cc6fc1cfb35bf29e2ede5af565bc36f8a31a11d6eaff54d9102b67e07ecc7e0dd02ef6ed9bc3d57abe572ea19113325add35b02b052c33967ad355fc99a7867ac2b2cc1ee0ba364ef5ba1a54e1825cfa751097cdde77d22a8e03f10f6ca9f96fa4347f940ec50f73cafddf7b9bbfb5c7d12b4b6d65aab05c120f912afa472f097cdfd138189151198dc8c6de82d10883cbf05922c678cbbbb0a2492d6ea3ccc49296dccddfc695b30d79d2075e4bf18f3adf327c745c8ddadb59616336bad55dc16b7aab5f6f3eca5f96745cff58aa9e162385bd271daa0a029c214e00224d0acb5d65c5f14f50a02481d281b05bab5d491df64a340f7943a52f46ee894beb27929d151226d14e88f6734d251caa7719bbc7bc69be9b6cb8032668d8d4dcc0b478affac48b3f7edb52f30665d60cc6a21ac6f586bed4b29e5cbdab9715ac4bcf066ad7d9adb6a6d753a41f84000a58b4f82d66200b6ed7b399fcca92fada640dd64b68fc13a323b365caaad76bbd8f592b11cb8ad06b66fdc9d23c70101ace055ebb53596e5bcf5d6ed5b1bed02dbda6dab77c9ba63ae6eada7f75e8fc0b6e2b6d5a7bbbbb86f0440192217545938c0ca804131785b711d68ad15e7b6d25e0a767373d75eaf75a5a99d522665e10576adb745af7e4f0556c67b297652e77a607b90e670b01fd82eae76e96edbc54bb66e7749c97671b5dbc54b77db2e5eb275bb4b4aaac55bb5d756bcdd25161fa63c568e96db960372b41fa8d5568bb7abc456bcdda5566b5b0b6ca73e95881004ae3fabbd1be636ee0359396cb95e3232311f95feb2b5562cf1588966ad152669a472863d7d1a0590c3eb9bb57e9667a18be16e85a294355f2ccf582c66c3b2190a9d2c6c5aebac3febbcb5566cbb34c9d8a09873a2909a00301246582167730977b905ab29809cd357aeef5c5a5a5abaf7deeb0fbb3418e31923a327a327fa846bccc0a69cfe56060cc2313e6b7df8c79f9d183e8ba1db7b5086eb9538f79c7352e9be5a69297556bf5aad366edbb66ddbb66db3735f6badb5d65a5ba51c2b51adda25d20914f37251a059f30d9f92ba0452f11e93cb54b19352674ad18793a2b3f9349fc61a6654a8008356af825b81714060888602e40f5bbe90f4e166535a11b67c1d49c2ace519ad41c462b1582b70c7b7d4e919cede59333292a9411423b465498c4f8341f6f576b3dd905f969696e4548d29027da31d783e71793a81cee67c923af62ed59824cc2a4b4baf2dcb8e49c2aca2b9ece859b2e332fa37373738f786d69f46528e1c37ab3c4d282d16b5b20083eab36851710df376ed52283e85e199a54a974d7f5be2b2e95f4d9b4d30703dc9f746d997734b9c53469e40b5569974b1a796a188a50b9919991184680195e796504c58d8928e43b2eba9db2164f292e5e3195c24919126c63d08d1a2e24493541608abc2e66cfb625bdbc2da97349965d324976db1d817866cfb420eb6b530b8b02d0c466c1b751465dbff41daf663d8281f51b67d1a944746fd7061aadbd5a35ca061bbe31c76da1d1ec36033cf3c4716961e47b400c4cea6d9b4c0346ba14b8f146cfa319ff37ec203af38e32f7bad2f5900b3ebbbc0a08a43c0420d5a78470f22c0e2021692f26c05fa191d09b3699ea940a38c5608a27866946978bf78b020f3f001869998706dfa314ec0197f99f60a010ebb7e0ec10f561862d7ff603ae020c67321b582931ddd13aa151702961d275081885d1f671b98c038e32f6f610ca254904a8107bbfecd394401ab6ab51a0f2b847ea143470ca27484d9512968d954884dbfeaf004a594524a5bec1a55bb61d71aa54b9e998b969b153fc8e1891f4f50fd00c8a62f696c784d6d0cfa9275da6793e0f7dc5a6b659032cc3de73248196e01bbd32481eaa0f85784b14a94a7c5c66f97b6ff34cd3ad9a78d9fa747de9e664f60956af611594a6db97944609d2c15ab6481ac1525c78ee3243be84a74c4f8719ab2d59e44c795689794ac121df1e3f0c8da2a491dfc38469936a12855cd2f3b680f07227da03c3d7242b1489fb9f17b12e5d91e67fc94093983399b492efb210693c73e7eaa65f2740f6bc155d8f8a5f4c14662e3977366c4c64f80d8f8a90ae40c87cb70903c29903cf5f1d31b260913698a801f3ff5217be061e327780886511a5a5cf274dd8f79225ff1c348987e4c0864b140cdca1208a66317fc192bd6832b6fa563f7257e8bdf8e74c49fc51d97e1678916bfc56f95f0db25fc57fc89e132fca08873bc649a55daf867d27d72ef4cc2ef4d5be988c3035f94a696b278a9d8da1b303589c740e48dfe0f9deab29d0682a42de356f9573babf6a6734c6e9a63030049a6430635203c5c098243b7430a96502902073fa87074f9410a3a38e2070a5498f4dd35cd5e77f729ca5d2b75da76566a05450a59be4743edc9a64654260fcdbdec1ef43dbaa46a29ccf479546b13e5a1b3c9c4a956abe9482265c9964d2995a1d569d34aa54ef74279becc1a5292582b58826fabe993f5293a52eb3de848add740aa6f267100dc858ed47ae1b6044d985fb573c1f4cb26b7d0915a9ac56e093a95ea5b82de14802559095682f7c820cf2f49f64caacd5a6dcf1db856db4432e89e5ee848e791bc61c2e402cb40ad474874c6fdac7beacdbe19385b3ded5b759959a6154ea42245ca93274d9a24b1498c3cd53074c6cdba99677758fa95be3da23cf72d129d65283e0b775e2098daf5e52d31a4329628a90cf430d4dfeb222995d1a497ed55d3f0acaa47b24705a8e692d24c7a7aeae58826d19ad7a88c3e1d22d3af4bf4eb514da26f7343ce40dd28543cf3f6ae87374ab729372641f245bc2537a5bb6f2b69b7577bb74d63ceb75a7d7a1cd7514fa4812b560e5baef015c6c8ccd0d4c46c50a450f152a4e915f50687458b51e602841c1d1bc28ed38b39020d8003a0ee58116eb8a8a150b7fceb13c2aeb8f2e8b8bbbb7b0e0742e7c26bf1e180372bcd12f387eeeeee9ec285e265131393a19981c16668646a62622f1b178a560a968ad5832f44a0bd15dc0dc6d958dc1676ac327741dddd3d47e680e0424707c8d4f11caf2054eba212d5bb8dd9bd489563adb256d97937deb7e20335b81257ac67651595a8e630450b4525aa2d974dbe91af9437dfc8af981a199a19d80c6c06462343531353137bc56c5c36285a28528429546415cf7a71e538380e37de0d4e87c38263d102b718b75176652eac0b102a08399ea34375429828a24c9d493dc72b08d5bab0f36e637881f0fed537d48511c982eb70e6f46ebc6fc5076a7025ae586f3f505959450e53842d14f603b5e5b291b17d69794a990d2f905f31353132343233b019d80c8c4686a626a626f68ad9b86c50b450a40853a8c82a9ef5e24a831fd62b3efc5501d4b0e2c6bbc1e97058702c5ae016e336caaecc857501420521c77374a84e0833049d10724070a1a3a375c4d7d1d1d1d111c00ab602a41c55c406df2b3f01c3f79560c4e97b7d2f9b9bba03244138b999128412132d4dd8a06506b74a0b0bb8232d357446b424f5a0a5e604105d361be49b2da18070da840061025c25b3d8120a08a39b822c534064714ba82c2bc060728b2da1b22449f901ccd6e3072e4c7e9872e4871ab6177ef8d1d5208f5b4261c1c15582e54a0eb0dc50856508a743ae79a24322db14d11d5db944e4145b42f9e064073e8ce03ae1430fef87ac43031ca0a648452992635b424929112165e445c928b684ba828409ae5cd97145074fc7952558aef8087245077e22aa6a47541047d9664ba8a825b28a2da1a28cec04604ba82d4b705d2a062b0a6090a9fe55fc4d9d5d5d0996199ad8ee79089117dcb0c4892d2500a2e52d6fc0820f2e406a3b38550172774cc4061f5655cbb74545121d49824852c40c5190d46053da23e3834cabf36d28cf44d214802d8522525c5653b26cf93026f233112b32aa482d5751d9f2c122643a4869428407134d43866849591a82c4854c9b4986e8900f3b22dc526522253d119259331c6df9f257d8407eac071a27a39022375dc26cf93430f8917513545bfe8d969915c430e19c5cc093b5cab014856ddb36154a90b4b32554103df44c4f055810e08b2359b428319141870c2fc840457ba234d53a5f3637392f6e2aa546b26c3a82116c4a47d0834d49f0c3a6479a8cf4b029b82b6ec8b4d7deef33fc7afbf3ef532be299e911191764daceb62f187476636fc87bc7be858d42e4faf6472132fdf9c2fbb3d65afb3d443c99bd39a50f9dd2c74ee973a7f4e1b69cd2a79b734a1f3da50fce9620ecea62d7975253fa8c5b4ee9a3624eb0be578b4cd9f33d19d9963f7f53d996514474d8f33d9a164cf67cfa15a9c56e6c58142192b78c2252b5a7b7a2ddd834c7566cf94ea408d0e69bd2e7b7149becf972b565141112d012644478ec09db322ac80af674265e60d0a68a01057b4a1f9b297d5040d920b5ab28f7107b4a9f9a297d629b6a48d49ee00e926202267bbe87438384c5101dafb78c0ad2c4cc117bbe8a39a54f8c4cb86554d2961d6efadeacb2278a155b4609b1818b6f4ed696514933ccb9616050d2933d7f0cc224a78562cba824a33dbf7a9595c6ac6818d854276ff92e213db0886d19154308ac2d7f3ad9730518b4a96280c19e3151428cf67cdff9a68c902221c86c19150469cff700f04de9f36df933e0a6caf465540c5df6fc9c9b2da38268d9d3b3d57a1733ef8b82d65a6b676856341436fd9b6f4a1f39df00e0a60fa6d31186884c530497428e89c23b64153660a50cdb320a49881726c76c19859402c725bf72f0bd90bd226e11416c5319ece1db91bf1d381e3287438725734d3c221927e1808c6048be5b4601110244deb68c027284002046f7626a84331e71167fb0030615478cde892580a468f2a4c2892e2ea0504ae184102dd898400985132d8821a684cac6892620354aa0c49cf841021a25463561c00c81d920e53438cc10249899c10916a605493248aa66c2341112c3f44586865961c9ebcb25588aa1e283309bb673839609b6bcb004099948b9f00e0b7c649cf197912cb57c708217584e8c42bc5383d10a67fc651248e51d9c8407b8d3b3f3a2c7090c419ab640f940851e3524f1c0054a2995810acb5085731338e81174640bc63b426c8cffa3330984f1c330c68f337e1afeb0ce34d4a84c1ae1a3241a947edcc14f3eb620a6250e053e6e90d216bc836978c1eef4ecbce849e2254998ed684865d283091295c94dc3b989da519093263039f81154822f60b8fc08b2a10646aa475053ad5291012dd4e8e8b453c54a93289bb6637f906b68a2898f2dca13dd0a79b5444724b768606d905f30e076e41c6505d9e6e00dc1a46baae1085e227f5b42d50064299ec5a874ac18ab8ad6292a0000001000b315002028140c0844029150108789aeba07148010719e3e705e99c9a34190a2288a6118638c2104100400318610470cdd46026df88771fd61ac7e604c7f18a31f8cbdfe09bbb12cb252aabc2150e1559cbeeabd05b0e33f7d2f4da79f8bc01796e461c5c52a51db29c0da3316f8732f43b7be04fb8d302868e8fa0d9397c1013e2277108e27d0945c3e0fa13f79dec76bd3bc672850015ec5e011016dbfda3725527b8a3ac97ee738a368def429800caded0d271bca861d5780a3860be1ffce07d2d525778274b95ca57b0eb5c0936ff06ad6446d5fd2e71564e2135d6fb2cf36cd000b84882f851c0043debcbf52b1afd3917eb937b43257f11d1684e9ca9544184a71e8e2d880f1014c1e905f4ca1c350aa02436249c88f116319257bbbab7baf949a97fddee402f4700c62ca2e38e7d847c2fb0d336246c1132fe0d4f340d80d8b892c6b92c5b6696e611a88bdfb4f718c0732ce528cf213995957c2a9b9219448ae8100767a821fc7f816e8517e5701ba80b87f60bc132f63aa389b0b0d47395e8cd0245a26a852f5d128270535a71cbd1c271b525d6c794d74a659274a56a8d85cf82d7b38d8a475b33dc95578c42072e8936475d75608839eeb71db6b4d1522a67ed5467682116746286723393815ab5470afc850e2b101bf9a16333a8aed6af3dde058656386afd44fc222c489ed330f1c24c46d868bc978f1b3dd4b6c03e327ca303d2f3b8933b88883bf9f639d97f04eba16eb9e1d982cdca4cb4a32ee11ab1be31eeeb9c6b43a1efd40c5a593726dc85157b91ac0a1ff7318eb25dbca0c0ae82677da3165a0d803e939a0ba9f06cda1c53aefd88e20e6afd03f2b686ff2cf5e83b6eea78bb7acf565b178feb3eba9817c1692397555caeccd3781b54a405a8e0308ad94635a79d14716372b54962d0958fa976833a4e426ef247f0ac97a5420d60919f9cee5ce3b98acae3b660868a0c386dcfc00c5ba3a5b1446dd287f4b0006a29746ae2aa591be2a694427771d31055be7326d22d4f8e20fd1d156b63b2b79aa31eb016834c10e021aa4f66c34c13873c12dc6e4f9f35af1333d1bc9281f41e2b0873740b62554652d90543a9dc98506896157e70a79b2ba39dc446041e5505115b140835d82a39e57b2e3d7ad886985bff3b29f114c80f3fda8d5b00f749111333a97be208aa4c7f63a4646e123daa3a6b071d0a0ca527fc0147d544d496e9da19b375f55b5a3ce060cf49d933b1ce3ed44c5fcfa7639fa18a3b33b2b387411edbc0a133c033d576521442f73c748ae035a420b66457ffaad8345ecaf5c1cb95914fee88e4ae5ef025d92eee970a1b26e8819e9b5ccdbedb0bf418dc3076d964ec19be258a7ce413c64bbd649c16b28bfcb3fcba3a6ed641033c971a461389a28d65386844ee7d0c487e489fad850342b5414a816d12009f3b7bc54d27d98cfac434b8c8d0fd65a624d9e8ed40a651fb5b7bc14de5905e3a48b81014b02f42e4a6aca4299dee96aa057a893ef2d1de7f8d2ac70abed835f1459a484ebfb8a35b41a655957cd367dd2126d5cee32a41a243bdb774f7beca9378f447f7cb952909a12c2df1458c97c001ec6dd174416f2bee6e58ea9c420ae0a78a2a2ff6fb6c6f15ea6c2ea88ed951f52babf2cb490816079a459be08df0f5a8c8933278d6f4c70e0585e4e7c37987248550a5cbb1cb8bba47057a970af520d47df688aaf04ccb284d7bf49ab2554871c5ed4f9073d24051b2925bc1c4e382c289558cf091c60a1107a0cc2070e5e832658d8a8a35e6dbf8294bd0e01253b7cfe990d234e106153999475c5594b82bb9482732905c7520ace52129ca5149c4b398fbba2c96a7a68a725073db10194131363966d5eb30dd9b3d744debdf380f99ae102deda26aef585f22a4b11742d17d7522acead1cdcad3c385b2938b772706fa5e06c25c1d94ac1b995837b5b194fe88ea9c7c903aa9434004842b62438176dd5a98ca08be30bbd2c7b66584b86b0978cb02e19615932c25a3284b5648475d9ccf60af434fb9af5d31e9c15b71c20010e28d0035b029effdb0dc24ac735e08fb3a54edc9ab04e7a4999df33dbfdc705090d04609f89f3187e07c65a6edfe65f4e9d21e1bff02558bb35f5f9035c104ae89d0d529364f2ffc37441950e6f8454eaed53363371d69536781f99e39364e6766bd7f2a95a129c6647701e0a2ad24ae7c4955f4eab7fe378e11fc84b3d9025309a231f25e449ed4ec492de8d0fb0e02886f6d295ff709cf6a330ed8f20691f87d37a0c6f9a0b0e2d848cef9b614cf1af2ce0b4e45d69b1a2651e83a702306c1149091a81f1ea5b4a8539454cca549bbac1c0c8f68d53314fde369a2ce702d95f01f09a614cb95f7c720697b6e7fb19ceff639cbcc7737e17e7fc1d26e7fb784eeff139dd89737e1fcbf97e0c3b19ec879f202122603a83bc8182c1d905b37329a7724dbaf8112cada310da8fa0691d09d17a044aeb6898f64750a3e54d0aa1d6db9ee2f8d41adbc912e58882bd6699c67159d34048c488f8fb847315c65ba6c1c989a4268c81c8d95f21672ec4fbb256844b6e7a5139d6c832f66c2b462f217ab806d6f0e979f6ddcd89dcc1b6452179b140e8859507d85a1e59fac7e760a62d012b2d30056c27e00c6e5ad65ed976cda83f685d6ee73e7a947a314bb9c571d7b002cdc8953247aaec6c136023a0e950e68becb849cea106c555dbc4ce61bfbfec0fb330de5168b0317e16be952e79d2d182215ab6f923b00151fa8bf6410d09e1484151d96da4f6ba6c580e097f1f3e68f2995475370c340fe2240c62b76836c71345abe7d19b1fb28d1ed26cc84abc784944f14248d105a8d29570a50b908a0ba16ad740151784942e81ab5d0829daa58557a044321d1e5cc442301672cf7c49a263bfa6bda2ebb10cc57f950c8797ac91fb2afc90ed40282e802a5c01515d0052b804a27201ace00244e90298c25ef1238aba543870811d0f051d5863edede2311388eec9418ac738d665f9fd2ad06fe9641c3f0e200947cb53b7ec5da82b15dc3a068b3b1a5ae115d599c3010d2de98759a19f21394a2c3106345093074a40282672232e7883c15dfc145bfbf1c56125134c620b91c2161c964ad4010d962103c012ac80d74d27c5738b8443952ec61578ff25619e38c7a8bc25a6ff9e2f1c958b8031bfbef08cc1b0081ef88d405bd316e719a475260153f8f4cbabf5bbf4166ebd5772523901dfb9c3f4e14ee5912ea5420f5c3c0c999647bd2039f9247685c202dec2424546085d3c94b7c22dbf4c712adbe7d498b9a7147b198f84d2b55402ff74489b94d08f6356582541c0b0d042e8f24f2b56a40eb2a025eab9b0829cff9cce40c242db26a1a51114646641454b3c88263cfc4d14a284a3dd7263321622f31e5edca1c0d14005cc6230b405dd8b06f9cef525bfb300012cbe7ce7477eb0dff5e4b51c8ce11a1395e4f03fb4044af91fe1b6871bdaae4c750db0151a5f451a2f035198c2b8ced243a5049b400fc9fe4d28667f126ba19326080936d1a8ed7cd777121a32457cc3d393c1beb2b9b1840e4ed208d6e561639054016974eddab1f0142f366a5887bea6e0c0aa4b125dc113abbc4fce9e11617b58fbf69cf8fac3d8f37df2c58ccf1589b410fab00d29df9948f228bd78e19e7ce205b47e73636d9178a4e949984c541b41d54e2a5a3d77ce648957091832654514027896c351d3575ba2b44dd77d88698625f8b3a5d5b6cdb812ec62a95e59c64dd64afad6bd63859b5ed5d0695863f9a52d559939ce6a942b4946ee9b940a093bb9f61aa683847cfdc859568f04ec6c013edb0c28e8dfccfb52da18deb9f4ba85d79849b607890c337f7dfecdfc60ece4637d6843631740bf6aa98d496ea6d291cc7cff8615743319cc59d3fd67886377dca2322c9dff8bfa8fc8b093bedf6740045a8eca13d2b4d8a0c8faae9262de56b311a2a65790c6e80d9de7a0a3e336cc32ed78c5ec8fa3c23f29f4064dce2180510c29e2113f364cfc322414716b3e80c8fc96f1cd5b9b18b2933bbf0d38c180c63f884b1037298ac107168acffa9dda73a56e39c06d0fd3c4af576592a5fa1eb8555b98b42baa739efd3c0638afadf08e3dd43b429afccd89794503b33089b037b2947122d966b30575b84fb481cf4290155d63540a9db5fd052a56d6c4aad41df0209e940dc949d91b2a81a43e2f6ef2f1614bbd578250ea2a566f1f9081bd6211c438a031c6a5c412ada0b166b013f37f6cb0afad3ea8f75fd8a040180ae1465cf1bb808c41284af3b83f6048bf6b3114d3c332369bd4736053b52500cfecb6b3bb035684409375bd842d2bdd140d1bc4269d384f7659cf522be606c3df5b9eeafb459e47dcdc24eb514771ffcc7aa21b1baf8df5c5489b0824bbaf1542482d11c8f2de3e22121f0121583e4a1643938995c17ded1d09667bbf09407d01345425a2c0b526d69247dc7527e6fa2ab84836af7639762cc8f769312f6b8187573d6e2749019312c3dcdc97f980b3a9a1ead348076d7050a07431a51880a65fcafb9025b64243db061d355b246a299c3cf5804b8e421370bb7f68efae0b0dea1353b695eab51cba0ad13c30a8af46ca68b462ebed2e5b57b6981404c103043b669badeabde968392411b20d431bf7107b7aeed99bb66235b48b02aa9312048826ccc57bd02233ade4b59e8863095e18a949839af0e26a99b5bbe7590dcb6372bd70013942ea73675720eba019e79f34a43d4017d2d75f6bf05fcbbefbad7c6227fe555865afbecafd3825746ead7e57803abfbc05d8d8233e2c6254bcaf3783c8f91e724e5e63c8699cfa46b1353e9552b9a19a8abad68451635c7429e36ae37fc0bab548bf042734db86274afe57b451e4e9fc7253066902f326b7b33bdd4fafea46dd521df14b14cd3926fa5f258eb552f57b61bec2dfca18084bb5ea7d893c50e3e95341358618177d4ef9809a96f101b02263e87146134cff2d6169d61cf623311fd84e39516de29dfab9f7b8c868f4c429d8cd7c920888cde890b1fcb633a959d131b39ef3841feeae88dcbd8b477d9a20be87393646a454ff037ce7355223cdf933ce5892f2241a8ac15f0e56b4f7ec472e03d8f0e104855d01bdc17bc775dc69ecc5dadb7ea4df8af7041d5343212a4010893034bc307864acaebdbb3ade2e34874fcf0e7b1a84c8f2e959ea4a5d012de92f88d978e96e14d5e7be78e1be7f9f5a92c48d7b4b8020105ef82a4e7742c31a250df8532b8943b30bfdfcb6f462e36a3247fc0feeddee1a25b38cdc41fb8cb98fdd8ea6f417d78c5b5112ed2265764b7469c4a260aae450a8da1758153815454d05a52a4785a86d8154c519116a5830551dd13e524557e33452e337e6db8ef322ac561c356e363a9bedc3754084da0b4255ce09512b8880ce6d1fd586af33490ef5a7e02030d030fbf2e93a651b762c8a5a83a98263217cad5b22f2a4cc85a3747c0f131745e210e66ccf01d1566b0d44fc491a84c929dc06242c8ac4fc155328df60c3325d87aaf916b399f26ae985b70445f74f0183952bd8cc87a6cff81fdbc154523421c2bd58b3f0ca991583614e0b308cc2cf983be05d4bfcf21be15327850e79504226847df2f40340ed44cead255616991f39857de1415fefe76bfd0d5d62154517646cffc5a24dafc1b7259e5bb3ff0b7d69189e02f331d8f2afd6bf9d93927a2d06878ee891320c9fd1f31d4e485c1e6253c0c9d797648aa98fb339accf0d921cb723ae9278495bb4227923adb062ce22dd69aea9121af03f7b722b1bdbeda912ceaa1251d14de2d1637d93f9872ec99300009e54422ede668a201bc72315be25317a56f7666bef150bdfba5a660b90970d305f9de40af0497f29b28d4ad3bf308c77cd4028a2730c49386180c1d4fa0b439b02403e2cfa0b4385b08a96dac080bcf4c32707e6e9d776841fa591777dcf0671e09a07b7656c0c7093d3d71123cb4bded2b86050764291cf10d08a2b5ad13c829099b039603ae7861c0df47d1ef36558a65631dd94e405ae14d79e709e216eca51d604fd5823475823739a4bf492b96c0d899da86670bf6b032372662c54fb1c64becf98281b500044c839bfe684a253f741a19d3f34cdf95febdc11d5608485217817c8e6ff49a3bf9f7da122ac797ec66d64b03ef0d1ae298941ddada80c9159b2f75972b28d6f0a78ebc2b23cdcb43ae9431a173ebb54d377d2cf61011d518763f5df8413574ac07b9b5c3e946bcd8f7dc20d841985063bd3497309d744a781367a0238b68cc3cfcd386a20c98aeacf0168066f5352851108310552de8936fe1458c49409d706f378253920c72d05ef48a4f08c1856852520ecb11733a01c90cb045258756ea449335d5184077ae11b3c457737b1c5146c631c8508d2c799d4b12bd011c3d565779dead622aee0abec64aafe786c79b4b8e1a973e455edc12b6201ac101be75655d7b86404a2c523e4b1844073e05d21266caa3fa413b4ad60926be891ad2828612154ca891117355ce130ffc83b475df58b5de0f0b460224301fde38a4b2ca5e2ac8addef0704c12fe0fe55fec272d042e95dac413cd28860dcb860b982d4b67e1372a01b65be33f814a6cb14ac3102ee0c6e7a510c4e059ca04867ed04d448f5e1c602ab127575ee246051295c2c69c502164235db02c8824da54cc2f15623091675d134a62a9a41a7fe7150941aa196892986caf4d1a37719ce3ad69532ec04f7a4337c81f77d1e60315a0b3f446e3fb4ef9941d508828bdd1ad053785c9655e08dc269ffce3eb09913e18b6d8048c66d02ed61611bdcd2a8be4654c6e6e8e5f45fa8e0a15d08ed8712feff7184068224ba5d32f723c2275ff072428eebcfeaf7fe9cd936ea1105bbbbe5d83e0f8f948cdc84a8a2282d6215dcb2720a5113404e024b94571a7cb4cce0485afdfc2ccdca000b36f2fd1ba29fb4407fe44b6365cf4c092f292ab9385a06add3fef9d86985851adff20a6da815f40098a1740b805f54305b9a482b4dc48966bd496e39de51198d0c37be5546ae2914ae33f6cf3c0e18e8b0f82051d4f1c979a259e82c49b3548d39bb1b29da993ec062a7082025a27bcf2150251872bd5ac3db38747b66ca6710d5134249fc795a68554e24aab4f2647782b27eb954b8cf8cef0618840817c09aa3a3010c2854ca9769a922d6eafa7df67fb8b64d26d98d4d2464aece2ffa5b6c9d138df5a1644c2114ccc8173b18c7959db44278a4d49cfbcec06a5e1ca991e83a5cf03874c5e9898574edd507f3aa5c28410186bf8a64749c740f46a1cf4df2486f316179267c1dcdbaa9b8dd3ac0aebde226f4740cd4e29e9b7705218ccc3ff8467db45403de8ce6d2339823c2909a2e849fb50ffa4802c96a5d4ae609961146e89d18641fc62327637c4853073a6745a6f566af9714623e5a699f799affc947ea3dc8558cbd72b678419c6a08e58390df6e1202616c4af12679d6f02cef39001434b6813e1153b6a39bb1005bae9ebcc95eb730b4fc9512255df9fd077bb155ae041662ad6261cea5cf2729b935ef45d209587f6d8eb2991159468293c3c76d494ef18a61e6a7805789b2525286f30b48499df75377bc609fa986b5cbce2a70d7f7e775e5c67fc16cbb480719120abb55e2442ec6f48ad0b31060902ce6e3784b96d1f7404293ee961c9a045be6ae3461c1136a16b5a192c797a7dc51b2ccb6c0a7f0912511099ae29943c6f4af77441c79d30612f680e9e7eb17f4b3b0d605943099015dde5f29765c22a8db5c04a0c4764e055cdbe3afde7fadb380e930677bfd9fffaf28701ed459e4fd3da2c78fb5b87786041167822eb0584952c3d37578d0d34896292b17982899f974cecfe469ba931b28f81962272ff05b2f54e932f7d65026ec3dc5ed4ddea8f8d9844fe91d8d85c0b1c025dda6802a6d345ebe3abd96a3e9b50ad9b3893ef603d9a86f41152b8c870addd544d85f84f5b464fce4050d385d1980371ee9f2e3ef63d46b12484a82b2e03e17d9fd2f2a8abf77904480a21e78141812f8bdf7bce2a6cdbf90251d77b60420809c4ad058d1258ad6fc1ea0083f69a25698320004dde378f8207f34524d1c61d65f61781f6069edb599c9830b45bf15a2d288d1b8c325af0c3d6b4abc8d13e95eeadb3478e1e318cd94b1a6a91629f6986cca2e3e77c6bc1ae722d51c28d23fc2b0635675f01f11c0960e6c3f4b10c85fd11a9d33d22c8e01cea8758319dc49813ab7d3e632fd2f10e168383295e13fe1a62e0da4e0ddbe3d0538488e1f148cf73a42eaf681c64e52c1b53024f93c080682ee26b8d4adf623bfc2568f9d7d5af2d2d4b9d84a9e566647832bd2d0e1cd16c96313680bc410c5c09f82a708ba55b1d5c7b34b9e37f212033faa2c6a8c5ffddb87256927bab1495c8433c234c7ce58a19dd5b44447222dd3e7190779ec217f24121f70ea60f4e4f1f5c6bc71cc2813c9f6c9debe9c94af0cc2dc8f153508f9bac1c9e8365a7902c5e1c72ee542de1354ad52f29b1b0134ac8e45a41e86c1d7e30c3a1f19abfc0f34b8f7ab5be311bcc3b55ffcb0586f1cdf4b3afbcfa7fd2bc56a5a99410380f919f421d7a86aff1bd1bcddcc312bda4e557f3dd4d7e97f710fc28b382848bd33e47007f16b1ac1b1d316ca866c43fdfc70a386b6f1d34f0733d5ce9ccb8eae7da26c7e6f4833c40d059c9c727db0bde4a3b3ba6ddd3e45c0aa75d983ea3b050aef3501bc90651b4fff32708b42e29eb5a70e842ac53e3350947f0d69d709cee0a6bd49c875de2f64ab6512126b9f7c764516394504d189566da312d6a6580d563816faf874f69616596954b7cd392355e33a5d6bdd3f12a423853953de1c68a3533af3ab154447b71de53e5801dc5ab4cbe4ffd9dbe19175f941dd5965fce6a77dfbb61619611bfa81df2a0d1e0272306f644668356c80b0c853493a6d2ffbed526a4395e328fc0b93afa7122a3377356688507600a4ebcd107023a01409747d342d70cb60d0609cacf9de288390bc913bd4cc183bbc02ef310cc12d26043c34e85bce529498fbd26452ada6b096f68900f921eae6c30837cd526b1450b9fce84ee4c1ff43051f742bea1e499be3d231129c87a8f67158f0e1fdbe7be401123cbffef94c7daee296099489f3ed21aabbf9f760b538bdcd48a795fe61924bfa5dd71a34f1d88fdca33b63e4af7ed7b5d9cd6095dded2b7e873133433333f8d8bc3ec13c10e1f361f56d88b52d7a0de6e9681b4d0a873daf85fb6875915215f24c5c3fbe84cd87ab6bea1d0fae7f5318c210eef4ff656d869a1d22185f7078b185465cd2c026add78de0e474c5432a0cbb4409aa0202c9bb03ffa03c7082927267eb5c71a38b71421b2dbf54d0cb956fa65df890e82d52bc4f41f43ad136f76e12ec85274915889d0da1138ee6f7eeecfa4c4295e5161431d9d6f82bae75c2715f23f195ddf79242ba46a0c50e889a913855cd5c9153e90246ce6e2fc1f5114f215261750f24543241899aa948657839e149e16e42d3d435e2137eee17225a2089a72a695b1ab709d4a4593e93cd278297e51940787dfc7fbcd580a6d746ce2eeeac4f8885925eae3fe03a6889dce585991300b1fe63b1e4f7f17db2ad97bd10d545a10cb70a5e22239ad6d4b818dc21c5d830cf7623502b1ef500bcaee606b256931af849c7a6ee4d40a38d0942ee72305c41ee61f01bbf24ac0cfb35ec3655d8d6c2be26bd0b703820c0eab42c11bd31f30c31bfe2f2ddf6d5785c7c0f94e39585f2f5752a4ba03b62229aff68b3ace0019be2bb4188ab8249fdd0be080efe10e4266a4ab5923f2e92ece497e02eca228c922f44d4661f363fca9104985045e3832251a3b21dfd2702b97223a3eae2ff55cb31f02f738d63828736fb23138923ef1675b2c488b8ba5914aa04dde4e1abae93d28f1f66170743170f88238ab11ebc0cc0448868e9a0ab391c2f97cc6d997ef4e83e99312e5e7ac5e43634384df8ac2bbae24b987207c1d4fff8083ed30c4a1adac46d40717a7c4e1704671b719a31bdeb6ea6a98c3f9c8f719d3668da11ae8b3eae88010bd2225cfb6360fd4725bd84d780cb678cd9f8b9cc0b361c1758a546bc3613de34617f500b0f93ddc75b59813b126ab1780558c2c443af116a54a651d8d0c157c10df318ec2485f6544dc1a1ee9ced48214fe293351e79e789274d4471b26b40c80cc69c2e1b2c09c33ed7ca6bc78db5ef391a76a547772c0f3e0ae8d575089e2559e24b17c34bdf08ed35092ac4b483b176c60cd64e7a3a92b8df9e00f0b76f0baeda0519f5e3ab6398d403befbb3d2e0129222d1d456c3ffecfcd54bef23b770e82c5b7639a3813dc2fcd6f800c51e7894cae39e5da87b62d16ad8bdb91873ed3eea0b0465f239906ab6abf7477324051272c611e666e7f669c7a689975010d5f431222c874b7bdc905930b4448cfbc40707cf33c83b912ff1cb4144a778b431c270c19f3dacb193ad71e960c26c20180441b3302fbacaa8a18725d23b28b42cb09506981a2aacd53c6d7d092004c1e12ebbf9ed96906494cd5f19f0a811394f7215ce9f7f1e2a7198c4d8ce3a20c3278f2fdf3a7158c0fef771bf601ec38c26196de6686999e0477a4cab49b87cd4ae1444febee2af3250369f177b22efb02724617315d81ce104f36a6206f3bb9c64e8714b04ea0061fd94c452c8a6c031c8453f93a09d4cc91b9202c12d0f88f04d276ef45a107aa6e8e5a6d880b1778ae69cfa2eca31179c1be84dc8f65171edfe2d5172e6ada67b76ca56f56a5a5a764bbb418455a6c266f151b13a52f38bf72b31102697814b7a3fe18dce388934930dd74861c73c210f25f890b40854b34dbde4b74675375af1c20c42918197144226d9c8e39b1348048cbd5822007b25f1b90b2af1421dd4264abfbe90ebbe58d6b77252446bd609b27a5d2c918ba5742a68320020f496f2b666ad49e384d04eecb6d92c220720d73d1e0fa8ca1a18be4cc221db4b0f38fe3b4c1dd100459a378ad5b23011ad94c19063184ff085e7547800d1c6a74b611e0aae8b3dcf0b2d80da503124f954e5fbc2bc27e4240002e1c42f760711c42094cb0083dfb05286960ede5fe071a72dcf71d64d1138bae6126c2858e7e449c4d13c6090a7e6ffb402747473630823798364788a2575682b660d942bff8e8613bd532a1fb0820b3399059eac423e40b847c18aeb0b858ebd07595a9149cc62a3249b97585b646175c603d906f40f1f424d167574aa6b6c2fffa84b35a4081711d8823bb1388b7c3c815f60d1455ce80d91307e88c8d69a9e1da55ff15a0a31979591ecd8225883f3eadcded9895d6123f899a55529abba553d5325caf0fbdeadb02982d29e219217e13d9dace60f1e65f0fa451866b16df3780c3e5159a2b095935cce1c9d18b25f21aea83638b7beee78a0eebf13a76b546c4dac83dd5276f7759b3089bf3b0ac37b8a9ede129e0e508b9612628a2483f4902e3aef27a4e8f21d586c1be009f0854e0100dbecd704ed9b9c2a447c1bbeedaf4a7c2ba84a4459d39f2228b0a022b688de7709dc1ea5c17f91b25a6d220442348198c5d779208efc0138695143f93589deea370e57790b06824ee954276ca676abfab2d547909de81149321becb213e3cd4e9dd8b4a0903e50c4e8ee1997af48c02789ef8b16e2b55525d6962f95b206a15e03c91f0d8c57aa9cc1426121eee97b69004098988174ba1af6dab07a903a41d22af3944fe55cd72f2028939f162c4546280725e559e91e003e362c3659b97d91dc72508b06146cd930f5d2328daaf3d616544d350350bc82ff574e00b7b667af248702e4a668dce413ca64a5dfd74b4d056f609609c73b5bee156641624316f0c923c0928bd027bb3f0a1b7ba101675b5f1cabaaa462037ef9f4a68a0cc06b70baad6afec034af021061862e0f0d920baffefed9ee60379f34128021e811460a7a5fe5f02ec744bfda93f21c613958c237ea387aebd9f52a52e0459c11cbad2d512ca8e2d58854a96cf22d76600d5775deb5aa550a16d79c6dbc1b25f96c5238869c0a09a444eb7833ec182d94730babf742da93dacbc34f638a38263c279e05bd9c65c0e87bf665ab47b3b3e852544db661fd3b0005e5fcb0869936d1cc7827c7ea8605f93100944c544baef0f14e6db637543208e292034bf34e4929799c44e3c443806c2b0ec1c73b506af764c9129fee94010102458f9300e3f1a157f0a6fc01658e1467fe407917b9d8938c162a653fdd8d6d4d170bf01551f363eeef7be1cde6f7374a2a40deda5bede6b50227a25e4dee27f0c206419b17a2aa26f3ca880185203dc652898707db2d57f5b33400133d1a691cac16d24f73999500072077e5e23cd9f7a20c308685b872b4f579205bb3e65b53b710c6ad7b32d4e587d40aca4bf7b99c4228c003eee6e0695be3a6f728e3f06c3cfcda10034be0573081d14076fe8ce22d5931f45792095effce4080a26753446fc7626e33617a053a8f9746b05d08a628677e7140357c1d67eb9ef25f26674a5c47e320130eb6b091c7f61f527d28cd36aae9f0e6dd559457a15d5bb9de86abaf770795c4b79af052c820e11880a00c44d40c4d29f1a9ac2b8af48ad9705d9f44c7ac03d72983573b327c4ada67f534e8dc7b9d652816294413d6597978823aae03fad16b23275e919817da92a4b5494b52557c41a78e47d1c716c8fae02c8f6b39d382667a563a5cf195f58a6964ca8407ce3e0634d70bb0c1a348d77f3fe178c8cb09c54eade6b3c2e71a9686e85557f8dfcc28706e802b8a32cafb39429ab5f7452aa9d4b3dec02dced931e00f2f453c71a27cfd88d2fda413cc32ddfee79a97fbea48e8de559b6baef3c47ae2ca6eea68ae5b4411e070c24f10c875f86c0fe0cdd36bc5933610de558953ca25365281b74f07a2bf321086b1cd0d45f1e9019be01b7671e34f3d40a820d70f4ad70216dd3cec6c1796262d00199c4ce449f71acfc8c52f950e1760c1ebc2f51e71211b004d31f77c7d76ced806c5b763cd3b7516603cfa7b60639c174cf2811316084a2ea7311bf3113d0c819741e8eed154a54a0d32c1485a1c98ea172147ac59d426c68e87f829327ec231ce1c3a11a12c080d34500f530764f33602f71d4b8ac734a4e1c0365207dfaa17f1a41cc5d7ce81b700f43e2afbe761d2439c99daf624e32770892b07c6503ab9c868b79c8e8e9ddd997c895005591d41a7c28aba2c493494d6866d4ee2041667febc9a4fbb532e4d957a14bc246d34e22ebfc354c6ddbd1e3e61e6114772cec872573c938c661caf49272fc96992a291e46c7a032c8f0d5600a8d8a798a531b466380b1ce7a9c4510d9d0bdfe40c73cac73cdf2cbe9cb65de87d569a7a82b971bef6ef031eb45d129f0018d1480641b4e91ddc5da99e30e420a4a018963600b8ae2f849e7e2247bafcb04a41aad1d23b627d8ffe29848446905f76c9f118757ba9a907f16c26088f3c7a41b5d3f6393ed37cbc43bee52bcb3717592e6ec4f4eb199c8ab1439e3654e49d240909f6ae5700fbc8c81d03e28a8c749e11c8a41e291fdec562549942d8d531c902a4cf1e116efa6c3ad6b86643046cd890c6679a05faf5517a934843a1f43d72c4a23815ae6cff4182a4247452fbd29b0e17da5009b6606086b593b084e60e1e07621c3d828d27a311f9202ebe69eeb06ea90da4d372a216cd60b041d8cd4ed438903c05042192ec809444fcc3d2d09ba411a18f35eee0b4f22fa985bd98f63a9e16d43318fd78c6bb7d88553696316f5a28226186a8b864d14e9e3c9a4198b2cfb11e80c241cc7a15835f13d2ae7e3d3dfd29ac887fe3945e57c3c8c2d46be4cf45bf6c41f6eef18f19963ce5661142bb0d982d0bc57c808bc61ac42f7892cc47a4504df4034277191381f8fd47824218bd97b49de4c44bc221ba900bf1f4b8f90b958b442b3b62c83bcbb77addcf84876f71025d989918d4242e37f5a6d0473fe7fed92bb62b28dc11a741e43f4413665da01d0cb4d9fc51bbe3223eab33dfe9254f9e7efa564ce6aca7eb44a5a9610ce8efbc82871c64982889434be3530dd6d84d5fd3bd37648f61f795c232cbc1ed0338abf4be12e17d878c555f1b3114783b2b0c148b1658dbb7ef2623cd8023f0544827f3e84be4d69621c0acdf87e06d27620dba0c7866a03233e0db6aa5a38860931e03a442246a98f4893bc0ce8c8a93bcea5d0e7994d1b9081dd695ffc4b4b4bbfefa324595cee0215d19480338c2bc02286a92eded390cd4df76b1caca405351525dbfcad4c8866fe7b9b9cc79ef585d74765acf4fa83bdc332adcd1d8db53f011ecf6e3a5863dccba6ec0c6d1032a49e3f65afc35fb867c15efad46397b309c0de591856f86e1f812c3bb9b61bce2ebe7f178ee9f83310e97bc84783b186e49e616447b6518874d2b6008c3109e84d20294572ffc92baf0fea87e17d5c45b1ba8d4bb44e1412c55a5daac2f23c4e60eb88aaf1d4881edbd4bd23dc36bef69b92d3706b8fffb1864b3a0eb01d4f826efbf7783a5974b2c80f3cf2c06667808bdfbd4382fe34d1d215728840c3fe843f19a9a10e4b59bd42ed0e6c86694403734e1ac8209856a2c7124c59f06909d659ee309fee37c1d64a1bc2404325df63fe507d872770a3482164a29c5941202b89d3e4eb15dd0c2e5f886909710903e7278aae89bbe39c988051420417439e6ef9d9b29cf721ac3abce4b005ca2a7cd3f129e9b17de5cc9191fcffb60165d6f1e81c82ac9d69044a3fe5f4ae2d15e0a74c3935f0f289de8d940bb9439b8350534f2d4b06382b2b01a1d118571eac4ceb883874db316141213f157973be466bfa0ad709d00ec9e0210a2bc01754e93434eca855f3b8b5ee661b33e256f837e8815016211423b0cd080b22f2f7a6c170c14ed47d85a58fa5bb365d0cab255abd8e2eae31881e0f0d6a9eae2409da22865e2357b957da8d0274cf59a35b984e821a9a3636b601cec1a43d267837644c5b97eb2b036a9df9036d2698b494c6cef6494fc30006337c30e61d8423fa40eacb01c10eafe872df6bc17fdc66244698c90598930d11d4a42017710865125e3e62fecb0f02929149f31a982ec6f4425656b6cff89a4eb0228505bec77c5a866bf100786dd8b6c0e4e3851771ef59d45b8990fbb5c0e5a80c1e9caf0aa1375aa3bcb648217ec36f01b2612f4fdcb06b74e8ccba2004217d582a997ad2749d367514085cc12e2f2a98fd14c49652a275c355b5db1ed77f6ec848c1270380ed0bd1e547793078841b408063bd1ddf450e32d4413d23bfad47eb1e0e3629603bb570f600ac60bc84a882709be053781f0f918b08f6925e0d3bc4d578c0daf619c3c4d2b19e46afddf088a808b4017aa7be2800fa8a5775334eeaccbf09440e0b7eef82e4b160cfe1add551d0d48b8f1186a11b11791cf0343ee67b79f0ca01514003ad3b1c9187c4a20afc8515978afce26e731fe3b7b646ffc92b987f30bfa19d3d80132b13a409b900e00c554d0891043768b20bab5095b07166010621a9f8ad088fc28e66e152ffadbc7f3b41448bd8d5ee6ddc6bbb1af7e0b38c4d570c23583792f96312d59631fa008a947a6296bdbb43f2364487cda71578aa420ab19ee3681208d5a9448f02d531f5d6548ec65cb08e08fc549d1dafc7badf96b66fe08fa3c08516b7a597fa4c21d35dbc5aa5ebae44293c5dc97c93cd51d4894818728872d3faf07b8b5028765506aed73965857de83703f3733779aac41af9d540b8a5ec399eedb370a3fada9ac21232915f60ec600ebf181177ed9ba0e9bb2f63785b29f894dd13022aa58fe6af2470ffe6d10afd80742936ba8ceb69025481eccbe25810cab301011da2ed095d104e3e8256bd3769c3f33daa04c1eae7e881fd049585223d2a04948aa896cd0fe9198b42d934ba2a33c6a677632a7b76dae85cde84d0904e85cfe293e7bc048e23657b6d10b2cc3e4c7e5e51f20835d081f1a280ae86e220ca9f48d42b297bba456c423981150f1d0bf12732b1968d2191a9cb2b81af03032e3cc50a6c8c055b27e4d8233293aef733a98d0bd7f15ab6bac6cc106f17ef9f8c0166f4392b484b76b590a8192e6870b28d975ca441bff050183219717015a547232825fd7723cef8b33f069955d28a92043ea8b22301a2429c81d91c442a01764b0306b92e43f546439fd4c2979cbdc2e5ac76afb0c75b18be57b321bc1c9c5ef763be258057784ef80f170eff9fdb84b70add0573633eb877b1999a052b4a377bc4a1b061eb6b1d4ae48fd8833c387a4994237c79b9124cf135fa85fad28c4429c201ff75ef2ddc5163c6d2bdde14552f2db0d08a8aecf7049f2ee3fdf04806e4ca6a1dc6f2dcd128f234c1c0194a7437fa640291498dc45a687d3ceb5206a9df39b91f9f1ad85cf29faeae669bcb0fe19623b3043229792f469d31b96798c467e32be76db2b60600f7e737a60c658a24455924db0a845f2aa448dda116bd2dbb3018e7632f3d9024dc51f126225ea9ed079e9bc0a74d51f1709052084d81608695b1bd401c43e7300c8d03e256336534749d5a08e981aab841a88ee39fc35ddbd8bcc8c2a4ca2e594de81dc168bd2a6db2eddd018808e5a37d8463b0b4047a1698e25cb558503ad2cf3e6f344c95ae19fa1d615709164453d7100c39fd3e64058940fe414816fa9a3389968ac16d244a67740bd32219993a2e4c0cd9221f43748ef5d6af3afcea8add791e382bf5834659e67f0d4ae89e3418ea270727b4df5fabc9d85b5c58bdc7fee0801c33caed5949dbdbc469e0b6d90a191bed22fd4a4f8c76eed69becf91ef53c828f58d5db22d69b82403ffd0130d097cd0e93ebc4c89668fc73f3c9d45bcb6968a66e1d400004588bf39e2b2b79b9613d67d89380a3773edc4ed1a5431ba7e4ff994d3b5f186ecbc73834749025a3c084f9a81291bc6ac89671e5ca374b09a8b2e987ba2c92b09bc8e32ccdd6d575df9471bfe18aef822ace9d57c1aa45b8603dc3a6b6a19963e8001faace675109f97ef29fab7e831396586098cd194b32fa56fa0de927a0ca8e2a51c8aa1e80bba3e391a0a516738c18d050cf9cc63e26f4b03ddc8e6165f903a3ba43fec22e3cb71b66e7ebdc75045c663133a3f4f0d85d05bbabd87d68dcbb62897121e6431faf747d2bbc341a98732f9fca8cf4abae1c8ad3863e27828b9daf4003a13d704d07a5c4803d34b48b078530de502f16e50c65a979fa5d1ae38bc2d2f7b07885409afd302aa185ef98202e9fab102a258ec1ebcf8a6db3dea579ca2ab5214e7803af7af64c7bf89332ef8041a2c9f973cdcdda215df3521a5d17e69b64661ea805fe2f12e3be5ca970c1a7cdc30f5b2f063601667c5c8ee343bc0e0ff683675881c6154bd0190ecc61f99c2ff23c5669b4bb2246f7a1372bd6f848b0aec2044180b9a57955ea3cc619f423544c80a32c3e3477e0712d9cc163c3296818ebe447da0280d65a706b327dec589031b396c58c312d9320b753054839ce6a375ba6e3fe31fc79f1e7c31583cc1fb32ad5c28f43de328af321131868861da1307f395d7ade9b9688e79a835f2e58c42525f42e1dd861f222e92189529bb946c575d888d369b1222d8674fe74242461e7ae36db492330e03e08610fd9d4129744842f7c4844ccc8773dbe414decb178fbe43b2bd7190931befddd3cb69d95edc889b2d593f501cc55bd97fe542727c1812461c873adb21b1c706eb9a7b5f2f0a69cb6eec50490d1d368edc8bb6ba2d7586fcca4f5a6a1eb3abb18775b9fe9d4de2e63db7aea2b63b86d95446f77be0b9934d7325d6f88e516bb48a9b8cd4250ea63f8c3af8d315453fdb7eefed59b4f1bde267fd216ca5ad18db6ffe5b0342b20aea568fe55ad098e822933817c52d5917a9876a1e600a4619503988ec47b5ce1abc575d4ebec3762a4a9c1b335cf24831935313a9b02cc1dcb3421e48360ce06c46c666c2f9c1a7d03744e4d24e4dcbba35bb9f9beeba61c5a9412da4e7699144c5f4200e35244cf70a2b1441cb2070b965b031bbc04572fe85524c38a5b723cd43e39a93b87fbdd57561a2f3591be6adefe1e11a4ce0a21517e646d51286ae128f5cb2fab8bbd2572a8a97dfba8fdee241fd889bde1e32a6bc9b887c57b41b0cae7f9e5303e0d1c25e36cd669b5cce6aae4d5001235d1b80dc631a5cdb0649573fb842950b42bc8e113677b94059ed169aa99bd82d86c6719af285d71dad110a4003697b0c876cb36b499228a12ea66abb5d7cdc506dc7eff1476a425c3c806a8dc5ba68532ea8038219747600eb2a816d0ee0f37ad9f4ecf27085e8250c0a5cfdbb8613b71215b1f9c36ca949d3526d398e57b20ded2e77bca9b58fa21897c5b9186378b9a7daf90275bed0028b17427c9e84bddb684091743094add181b7e1d37958b0efd40edbae350a66b23d78dbab1e8bba7b9048eb79999f9d7d03b332fd3811cedfbfe2b4af95a3f4b6e997fc403e65d5d524e403eeb7f422b15c5d2f9c9bbeef1153a076eafdf2800f6157fba54b4bbe568c9e1900cdb128d74e993fe2a65ae5e311426083815233d25b1eb4af21f3d40d16576bc2782ca545306b820dec7d6109c8e73f5867f4be31f38226250d5242de88ad54824299971341efb5790a083d56c04c23bd8738649f1abceaac563ef09e1ea64d780c9cd782abd4f95d2184dd2ff159735f96a27f26c15d089ec4f07f38611eeec5cc922c434dc888e6573942aa221382fd2080ca6e89afe1a2090e00c17dbf69c40673619db38cd20c6bc7c9b6f9d4e96f9f3fd8247fc74ceffb5b180867f667173f5e2859b3a830072fb3430fb06e1800dacb9d98790cb81abe8536ae3a10b3bdbfec1d5f8d7149095a9572f90f2fa7aedae02d9231ae0ee40a649192f190261016f386b8f4c878136bc6d5f6d631f4924c88cc3f1543c3c4ab9bc3f6b646fff4a803b4e2ba04265a12cf6745288fde4441a2c8f740d715efb775039d9ff5b7465708bf1d39a2b24d0bb0cf021ab78ef6f00eae1f635bbef5d4013074d1a8a1af632e7e24db923d8bfdcdcd579e6665b08ace019241c9c06cd6f4dc4466ed6d124a88eb440c923139953a4f9adc3ea4347bafe7c181f7df3d696d81e99ebc873c949c48a922aa4e359ee092b84d82a4b1848fe019c13ac630a79725027fa1f4eeaefedde826e3b6fedd9b7fbe8beed1f7e560fe4486d7c4bb19669e0dca7d31117a1b2a1f1b428c0daaccc84aa16b8f19754a7bac74dc0df9eaf7a9763ef6dea9d86093c627166f2b4fd2939fde81f51342b7b3990b5a1666d58dee542aadae4e5baf12874a4e4499f5475efae89164ab73775cd133560bb9718700a1973bad5704856ec3a1bfe41cd218e90345e68020fa2a561a1c23b7905a00c7dcc1d471da9a3cd360aad7b12f465dcdcef4ee8512873b3e7b68688487737b02c3368be84a6a8e2643dc0503051142f980273a19dad9c9f940a2734887db7d74384a182db8924c125e7fce19e6197d9803b724aed1e99154c7776b6cf95ba571d20fc5e670c2d7ce6c0cc4742ac8e25e6b2fa89664c72aafcfa6a2810b2dddbaa4d83d119cd9fd53f5e5851eccc78eed86dc52aca0072a615077d393d6c89f147758885f62de071f476872f3da343a5bfd5a772e4c29eed726285b00ffdf19a310f23016b6e0c5922fc7c4ceeaf579072946af901cdd41ea3d8df4af3689dae90f0660a224e5e46503dbe8d02c3953bc0456977d20e7769c656be321472090431d4a67cad40f0c2a1a17b68436cb407b5c58ca94c4c35f3de5a9037685be801c6e4b82aea79c12f2d401f94c3b4eb8ae9ec447315307b6aac08909aaf1d243a55117ac1ac8b6da80d5174a317bb272155177709c6460b086f5c9e3d5a5468b410c423aca339d4037481382d8518fd6ed50b7e4f44b11ddd9df716e7d1583a3c1781ba372cad7bce9f94994960af770836452d2b92dc7ebca87cd679f9f3a05f81a62fcce4d908bf01a95d0e0e5e7a397b72c70f4c904a37bae3ebc140e8c0666057a53e88e52e584c800f29e4cff349e3f2c2bd38baefccc788c4dc28b0f8179155d70f1a78bd268d979f120ed660102b42981e9d7832fe5b7039d13c7b7f1d43040135837249c4b7637918eb2e9154d0f6b7bc40483cfb90f99b14a3357d63994de7d8e223ff6386247d8738c6ff4e39ae760285867820016cf5faf778eaad2f6cb81d1f34769824c6b26fee2620bd3e4af145f1080f6d642ae201c7ab6d4cd3222aa48f61210594b0e4a42d759a9abab4f290708416bc62e2d40748e73bf40b48f2af8d15a6a7116c8b50c3b349abc94f76f71d6db5d1e43c912af8cba16f9957ab581b055adec250652454d9c5e710e53110964582958df19adff42b63250b354e80741ca0a85bbf15e648aa130aeebfafdbea76d3c431c7bbaf82bd4d341e76e9ea1fdb32664b7c017c5c14fdbf53d50f10f3a61527ec4f1bbcbfcd674e667bdf386c6f7c29ba436584c186187ca7f6c79ce1fa5fdce207123892fb99972b4c2cbe2020d501b6a1f4de73a542b87a25933d3d2a39d8216e63dbae2dc99311121c198456e8b8b904a4d38d072c73e631ab326e510ceaa3748e7f286380412d9be74978f822b35e5f38255543f0d620f82a1759f97c6a6927e4e43da9dd81417c1337dfbfb0a4b266cbe8c5ea0b43efd63b476d20abf141f5b604f6ef27d353b2cbc8e909a7df2f185bf993392cf785f93cb4d1662214dc7c668f952bdf1a1494be9540e2c767209936fdde5975f58d85606aca68950431a6f7d85f272ee7e508ea35cd2a474348fa0457e199cbc972b2e381d315e3ef7efd8b41a3055f9f46ce0abaf0b3c108b17b1a8fca50b9a131e6fcf5bffc9da113f1d5658d3eee0baa7013ee0be477c7ce9a51d124ba176541127fd8c27121c6dd357b8f0c61cdee1594d22dcae3d789e8c35298edcb615b4232169848cf9ce8c040d5fcd810c9df34cf1bbdfed497353840ef7ce43c128acf9a08b1fbd9bd4679b01eb16ae5bf931f4b9b9a53f37dbb96da1f2411f0dd4f13b4b370f3b650514b15850930de0c206329488c888879809709cbb67d3da86030ecca41a8fc3abeb6fdbc49bd37f8475e49885ad530eeb0c24b2404408696bccb11d05045c7b6ac36a08ae42f6a613c54837bf092fb8cbcbbeae85ec4abbbbddfd40021d5bbe99217bfa9770d2e93ba5e8996298cf6d843274dbf9428fd72feaffa3b20dded6ec92bd8e59f80adc1646cf5146033865f4eb85792d6a91c1b35d3e3e2b1f7ba96ff6c53884359b6236d566420f73a6e6f1f2c6e04c2f5c4916df95ed51d77479859d8809160a5016e43892933c4ba2545819d84a802d0aa220f498b496b600e83a9244663e3b382e9e1d8360240cba91b1dad464a04bfff04b1563f47a891aae3e5db1721358ba22b97d81e1482b3b423f1792b65b83440e472e9a246d48fdeb7b56705cb90f6a20a0cb5eccce4b872ab8452bba6a80ca514201deed30c10e9f4efed9a5bf735e9e6f771ec3ed220cc994d4e494a9347a8661ccc8900acf3f10cd6a1ca26b3304a544b7c7285379fe0d9b0077320a20bea8f21642eafa4fe5559ba62cea54c4a247734209aee7b8b41a6582a237cc30158df83c5486ca82fc881a644b110f0f04a4038b7184e951146efd3d0223fa306e526de35c9284236bf3231bbd229ac512c850ed995f7de7cc8241aff537b846d448e84941d0bd0c796a65a64bf20b4392b279ed74ce9af8bdef26c8194347732e470073f011a27f32e429fbe00ea237c41b85f89b2b839df12b3d575fd45c1501ce68452be791dfd481a40a976f8a05052c108e1e8ed2fc1489b993ca0e4b633304961c3b84d0f4a2223ecf68785d5f789eb8c532a0c52f6df0b651b96e3528c29b469a245bc4c0be280910a562487e63562afe344c5809dc3660b7d5e34767d185cfb16a53b9eb63f9d8229fe73bdcc6a3929161167aaf89e58fc2e5702b45bad99cd7e88a1d755bbac79cffb3f063520c577dc9a1fed3c201ca767d66a196cf31e4fe4fc33ce04d9a32224dff0c3f5afc732c5417e54c796b4126db25a4d5356d68a939d9ff9e37f446e98e638c147bf9c2fa3d859697ecbc29e1ee600355a3ef6dda8e809e8f94fd07717216a4063def1802192e6d36ffeb523ee155cede0932f5c833b5b82064a0eaeb828fb48c3ba6916f6d23e68b441819b8f4dbcd40df0e346cd0076c1b9a2ffdb930454759905babf1f751f6cf9575deb87e01e515754ffae70601ea631573fb0a468c5e93b94b7ff7669e326e65ab25e357f22c2962696d25731cf9ab7c594733d917fff69ebf921656062f936a38f8883061077732936e916c26423473df05cb0684c87ec86b3edb42b27cfd877c4a409f34949bb7b7cba110f8d02b9c16ba2e5e47fd2c88bca29f7a5757510496810f746df0fec32734ea13f33a170cea6254a51159b05381b46a46678d0bd0910d84a2242f72600122c5c24a9f6e7430a95bd20b060b2e62cc287d47bb4e7772808eacc084e069140fc5e83d2a837ee30755830cf2e8ed0f1cdfe212b17cf705df210f046d7e1e1a54b8e9b14e5e4e05266a1d4564916d6641a49c540cc275c98358f2e2e6ab41f41b66abce76188f91c62cbacb1584236d82642c6bc07a85409a8f16b006af7aa8e06534322b618e2ac364c671cc5b779e3292ae6ea33f045706c361b440914e0f57e7c52aa6fafcae330d27a4511e2b4656cc282b003f0e33af05aca2f97374361bc121bc702f2acc530bb8e8e67fd1f50ed595eb273c2606fc46613f930b6047d7250eab41f17a36b30f2c3e8c67af2ae731d4fe623b3215161203a3afb03dcb86111fcf56bb2d659d1be0b567ebd06f45837f2542537ccd6f4cf7c4a67cae921c4a98335b641e8ff9016e2edf18b57298a294ddeeda4f1bfd8802102c90c54374c9f0d264272e6073551a71b9cc00216199bf29785a812558a6e6c269884be63faef05c04fd03949053e361d6c4c2ff40d36c544f2402fe5497585c8edeef4906969d61dd18a350523ca6eb96fa9916fff58449c0e8c53e20d46279d7c01c16f04fc806d89c9a67c14cf7a7d45874daf751c985047dc59b9b1dd9e0e7c1dd8a6bcb9ca2eeb940f86c9b78334911075e88c979c9d813d0f692d73eed0df839271a4954de6b5cebd83c150979fb1adbe15320003be166bdd6a02adb12849bce088bd7da5c775bbf5a66c062a0271fe7de56dace701578542390b393667f45e904d6bb1eaf241aef406557b6cb8bfac573b8efeba817265b67a0dc8d284ecd0693c56dc683380243296fdc041b41949185a1426f2949e674e482098135a2cd508d3c722ee25c8193dde180c1b8a8d5af962a1c7cbb08862de930632f028fd02e3c0d20ddf204779d6d2574dd5d8d15e23760b1a89bf276137b8827b5728b51d72fa40c6ed81f2bc9640b916a5e6a85457d4829a95e1517bae6b856936260d77c4d53309fb0c75439d37aa4dcbef85440b5d16f922043a7d9b8227f8762acbbed62c0d8df79a313571bb5c900458b9adf39ea455aea26fcd704af5242a243e45e17578c0b455f168d53bd3ac47ba33b3adf08f6eee7a69166316596d999a6959cc2d7d00ed080916a6458c943947edc8d839e323d7f2ed49c2473147db8840fa0b806a26c8d00751361edb4579f9e5a0924b978b37276d1e16109d5ce5890758063fc3c557b69f695eb7d432af97383fa5238d8a28294a0dfdc48a52aa01a4a6c483562641ed4d4a0813ec9355ce2e86113afd9a0443548e9599ca9d82bfc44019ae9d0e9118990fc6ac9a3ff0a8a77856c867b00f2f5499cb60c1f79c4f6bb4cdf59a396490baf0838cac36ed4b657de97bc69363bc064a9458512c5a563446ef82100958a3f29243a7a183f3ef74b25e6831ed51d3ccda91d881e17cd6a11076554eb5271d98b0a4a211eb13ce67d3f0a7e82f148c1d68be812021608cecb1b21c1622c74fdf54dd9560cbacd4254f0842beacbccdcaa9ca5d4bf4944d0b0f829da84e438a06096f19d10bc985e4fd79e2f82afaf05b2850d902cedeb8587e94cabef3408eb816bc29330b810ad200d73419fbd34b890393ac34e8a5b8169d34c251ff9b176e09002757b847c061e35ee2bf3644cf3e235c0da0f20068c644c9cf218c76e563ef3da6eab0bc5b37b417f95b6611f877b21072dc9cb51d26c836287195095d659492c0b87a01013b3762ce768764191c7de3ba43e84695833566ee7c1ea4b2854cd9477c3eb0abdcd6f55f8ec6bb844d228672ecdc5f36476e00621a168fc6ffa6296368ec4a46e934d69ba19d8428f162d9cccf977d00baed34921c318e5e4f4f3fceccf879ae0bc026b8520ad12476569e60bdd4806dcc68f47b61e89dccdffc5f6c1c782f729fdaf08565f7618b3a3dc2123eaf8f07d011e14aa6fee3e91e9af56091319fd4a7785d9c1a345011a7545b0491cc8af3588b7fa16c4c4193fd1f14116aab208a395784655d9a522a58f622f9cc2aa89221672660428409593a6215b305d641d9118506f94e516b6b267ea13c6f4dee2fb2186b3c8586a2704e429a35a950097f2856074ac0ea46bf85b9effe982b976203433d74405787d541d8360209c9f205bd7d015f00537fd3413a7b4a4f50b012b4166a2c6491a03c04c4c660d49faa9218abd1ac2c56770c67c8c682f64ab4404f8cc1295f6ee2aff2374e69e3696458dbf99f96aa875c3dbe4955ff55a1db25c2f27f1bfc3a312bb5e286b941c32c22e7558d333dc914da107cdb074d84c1be861dcae173c37d18d668fcc46f70aaf7ad0438859893798f35a5553dd9098aa1b66577583475a75131723e3733d9617366e33659aad3c65990427234a9c542381f7c947168b0e8c455fc470cd37ee3f90caa3ad7b4597f7114780c369d2e3989bb0a8b582cf10af6b572d16df405637c91316c1064b8b35002f42bb8bf9363c3b7ddfc6ee64693b9bdaf24f89fb98a4e8321fd906e0c970771af922bdfc9958065da479d7c221f9bdbd3a66c4e9ce6be55d5ad9567c4eef2e12a6cc22d9162cc6409bbaa559c8e56a2b6ae3bc2165eb243b5a66e235db286e2af9c5ba4cafdece48d46c1b8a0c1612017c4c04ec9481a61a60a4e589918f14317d4d83def2c2903cd8e1491d7f091468970210872bdfa0927467f6970dd84537144363fb5922709cb95de0b613110e9573e4aa5b119d308e36720356c605b8abec27ccd87045f5540208489d95984b2104d7d84d28accc5a74bb0ce62d1a017f7eb77ef2f4efe3b8e51a66325fcef5c1a75baecc618761b11a32ad41c63714f23712258187a607efd529801a7014b97024d57a015f3a8501deb017b929411b419af2f8605196c1d48556db2f66f79ea6f455b7a24551f83660621d50feddab72653c4805c29b6881ba9186df1be52099d59914b3a2511d828afd53bf4b83f1f837574bbcbcbb236396460531c7dbfa80607dd6446e75c560db6838d97f33b8ec92c01c981faf6b109b38ee38e2977a70473ae3b85ff370d0b9bb19f388906725e8ccb4b96c8f8ba7cd42998d96c0173dac32fb5780cfc2cdd602c5d452d80d630afb57a6ede7121eef080a4d234321d2e3def2a8a83b9075b9802ed6392c443b923829517c8054d342744fff3532726f4ed349a111059021e081bb01079f4e74695592b7047b23035d6c86da5f093c7a8e615b1bfff089ee4b3b68ca1d39a6ee540f87f0f443dc4cd372dded616360bd6c4a091713a4fd1f3acda800f0475abe2c034a1191bdfee56e7639cf2e19edf252473fdf86bc5a597de0b081c125eeeaa11def2839452d8112811e3db2dd33154a6ad1f966e9d3755aa9d8d1cf2d225526f4ff708cb2f88b45747c618be60780c51980236db887f4ebe6af21f3672ef98f6c817c7a2688284ec79e76032212806e0f4f40721800edafee1bc63687ece56be3e4b9d647380124bc2d56c2b67580f96dd14a951900f44f019e3aaaf5fb58f16417945b316bfb5441ffb11b3c9493607f17d3561ea5ca665a1f471b5a6e23bea17e5e9f89e19d3b4a74fcffaf3d89f6b091e36a0b09998706601215009ee89da70dd486f785fd07d37340a4c997d072f0196793fe4bf85942ace5dbb5f06a47b7de18bd6a9304d1aeaa092bd0db722558d678f7141b75126a70ccc68afc0640f4fb922ffa59c5f761c42312552f334500b3855740a91e420fe22e22fa4ebbe9813e8a45be37246e099e62891de04f9286d274722c993ae899bb85fd2ba593dc0dba65420b3be21071f53742b9bd26126374b437511f655e05276bc03ddf07f0f5e67ba3bbce5b61bebaf22baa65f9d298386f3ad40b0093851456b8dc303ae9e17443056a7527389cb8bbfbf4e973cee93fe79cee5fddeb0da89e6e9072c3d10d3b54a0dc8082932e35a324376cb9e1614ea8dc70c3c98f4f315245796e48e2c9941d484d6e207292d4748070a890ba0391c60fcd1051751260c01890c3188421b86108108481087e84608720621002b61fbe60187baa276eaaf52b54c817e2a07e918a50188b8e321844858da70c7a83d9254052ba14a3e29379b03c93cee96f607c57e5eaf421c4a6af1322f54400e221f64402d7458f2d61bd5a1b31f01c6ae0de424369a5d69d6ab45dfa5f6655a7a6237ebf524731ac1792d5a5a9208c89bef1fd7a2aa223a81a5fa0a9e8630d0654c8854055700452911b9fabf90aa56216e7c156a8b071076b2a0663c3181833f9c6f8e5a9d8a353e157a3627bb18d91d21250bf928a138963e7eb46851ee760820a6b2f8eae98ecc7417c4985a0c278a98206ca15be948a60fceee257a508b31d5e007f24cdc50ec0d7a9008c35182b386c3007153696c19c8ac0d85fe22e1ddcba03daceea9656204a6a940e3d7545ea3345046f03ff95efa2b7a6a78d9cd363897591e115ea33a7a83e9e407958e0b870fc97dea2146d5b31aa5bc60c47faad9c23eedb924763dd01459cbc8a236942e19784ecbdf7967b4b29539201d608530b80086dbb0946d8b690e9ff6bdb51af3e9da255f56d1deda724c5a56f2b28a349d99d6dd1bead7db932c6d390e91f84c6c0fbfa129037baaf3f019984bfce7eb44a14d85ff1589c9266df13a9689f02d1fe4cb4df89f933edc6efbdee616fffdb380b000a7038c66f25a65dd77500fb0e17e9b7dfd47f394f7ceb5bdc2a268a9e0800d1df38ad3e05a2c7716bedb72776feac1414264ce8149d6292ebd3294fc2af34fbfaf48a27b9b0a257b48a6ea161289a5c1fd3295a855275b8c3f11804ec2f42b3fd21b06c718b7f8b8b74ee4bf40dd5514c46984af4691e5591cb034f6a3c7a5fd7243e91cef83d9cd9dc913ebd11451a85450ad581552a9df2a3fa32945cee8d1e4647a57aaa725f6b9dd5a87e66739bfaef37d128a7d527d2994239adcaaeb9be11ef67a25ff951adca2c7e743b2d55bf133bea49ac63e7093c3c82d3ea7f8c7ff1453f82114f924931be7e43d95082c7e12fd22a94ca6995863d427d7b670c4ca79c56dfe861fc48a790d81fc1935cd4b003d9dbdfc21eb21fc1935c7c7d23fd246ff490bdedbe48e70e8f43a2cc138832c3108d8a5e88d2692e6422751afd162296d94b208e771347caa27e4c1c41227194d55f218ac00d895724d2198639705a7d105e78b435ac607d1e536986a51fd1afd2a577bdeb7b4d3ecd8ae114fcf06d214ca5160f7e2dc49647f32510e7b310e9d0755afdfb444fb9dfb29c4bf45836f4c9a167eecb58b77193793d27f6bfc41ddcb78ef935e420d4416793f1a419fef68e64fbd86bdb9ec8ed7d75218766f3b6b12823a9994fe5edab9ddf526d25528c8888e8efcf0ec227c2453ac7f0c87dd3c771f48f54b6d6dbc4b45610c1361cfb421194348f0267d733c82c170de9cc7d119afb87c072ff1676107ee3229db99794d3362098fbedbfaed3e18eb470fbda54ea2b3fdafe65b2bb3519a76d2f83caad39edebd3d19fae905162f1db7b14134f82e2553c298e2771b236d7d16c3fda98da92b72a0fa80cc525499b4fc74ab6a18a53854371c969369c36bdece68ef48ba03079fb4683f5658b49defefbc0c9417914ae396d620ac8ae74baeca7b9f148c2f42779e38a27cd0f01847d30d8b7d8b8e6b4c61458218e6d416e68e82596973c996edbb68570652b7e7b1c338685c5c344113a8ffe94b77f89443a8758034e0b6103b7f1667981a9c40253a013a907f33d0be613dcf9fe935f83dff85906f12499bf1f6183ef7df86af9fbecf77250fe0871e0945be4b0bcc4c9f24f96585db4f2d7a1a54ea3d469cd8d36723f0d992cb631dbabacf669b6587effbde48662585dbeaf1978d2787178d26be049fedff390b2a944e5513f68639fd1e7b2fcbdcbfeb66fbd1fe51b1ef2c6ccdf73e049f3bfafa332eebfefe5ecc34dfd84c5054d7e91251617aa5c65ffd6c1e7be324cb8d469dd2d8f1c4b0d5da6a47089f34d302b9665943543b2832e252483faca24cba8ec4fff78d654eab83aaeacacacac8e9007c56342c783b2d311f29e8c6476e8c8b29cd6548ba2b9b4e39a47feded3bdb22ad5714d25aae547fefd4e3b2ea71d97d3aee3a27d28213a88fed9f179f6d98a8979884bb1f4fef926cfab3cbe8c2577fc3cb58ec8dc91765cd993bc4dd6d240de7079c3e50daf72256f78d496ac41f2864fc91b2e6f7896dae02857324ef399d36853b5bb943573daec0600240d51d6f0972fa3854be733f8f74a06a94a5529a32a15ab5255ca8f7ce64a5266f0d8bf863bbe842a59d62981491e3b2e245ae156678c87bcb9873b52293a5d767d51115509cafe5528cd93bdca8b6cc4ad50f268f3b4711af5a48dce3a736cb416679b7087cd14cf2ba7f9cb70e1da3eb67a9ef75be4395e79345fd2a6eca69f69d350d056e59b2f3b7b63e02fbf31f0fc0014fa740d3963c07865b9b91316217c3c40286f3f1639e3eededdeddeee2e80d7e854d5b5d121e3ccc9d28d20f9068b8b54b61fea904df8c9dbd350873c024dde9e082b7aa838e28cccb345de915865a49c39f2ce08f44cb18098193d0520a2004bb8d7b8810e3148395835b0684de4ed319f1834d09878a1046bc8851822601a227178429289788f58763c76800eb7711bb771b5d718c29aa11692f4e48d863bbe3f3a6c1079fb4f6e9bf71a3d90c515a1638bb2a34ed131c9e40d4ade1460ebe4ad863be6156b396b2d6739ce5a8ee3acb59c6dacd7981530c728007695b7b7e10637f2f64db353a9ca23ff3086a4216bc81a8e474eca1ad6bf36cdb2c5487aae63113e735f75589087c8ae2af2f6be63c49913a1c896ed13f12108a0519f730538650fc83211d9068e2928db3c25c933a62bb16aa093e9d7d9c83dfdfa2fb29fcb39c517fbcbdcd986753e2916f590f9ab60a55ca593fa9c734ab1a93f4ed1f673dbbebbbf226b41a425d996dba8addbb66dfef43ff0a71e3642dfbfc3462693af63b82f2d74b0b4c041a3c58b16aa2c57626991ca02857f1ba02c7d32105859e8c8e142e8fb24d6f149b720864ffa273bce9baa2b504f5a79d0909982324850569c2d5551507db4aed05499e21a4427ab86f4c7bf3b64ca8ab3a58a42f5d1ba4233ab700da29365b5c59978d4569f36574dd5550871d19191c2f59aa44193217f763339b67124c3c995f555b4010930299f94948a5621c465874e18ab293599fcd97ad3e68a53f913a13c76c88469282d2528888cc363f6c948e1caae673ea9544709549675a0aa2ac8b24e12429e345dcc9e4620d71583b29003a70e6bb28a6334261d4b2ac3277df4a143a84deeba6957599336af647f6448b979f2d85aaea524c5059de636bbc85d736bd5ae161dda1ad282b29434c6f6933be89e6a7912f7be256fc81eddd3b7df51b14867cb613b0595f96444ddae95ba981aa44e232448be8cbe912715d19f4eb7a66236e276c834dbb387f3377144ca5da7fee4fe427794511953ee48f352924b9ba9df1b3f1e9768f2c690ceb4269b19c9e742a66e3aabb7dce8d34cf148820addc9367325561734b9fbcf1fccfee8a2db721eb6edb211b77b1b32711dd740e40dcf96035269490a4abe62225b6bed8f8d6473b1450d38cd620ad021d069f6abfda5d18ed6b970c7e7aefb4ffa10e8342edb0fe249f3392ce0db8fb9b54ff31ab646d9e259b648d97e536d855c64f983145502ed8981caa097e1caab26dae4ab27b676d05c5ef808f7dd4fb148e2a21017c9cff1e10fecd33f427191c435a779bf348fbc59965bf43df771103832c75af6b8f770935c427e7d08484a6553371e5010bae5536c44d237d2fdf29d72f4055afe3cfc6b4b548a4351c8b2960e4296822c312164c972561422daef82885f57abb8d695ba1322660e37d12e5289d428ac2d52b92964969bc0c9bda5dfd5b82ef75fdf7650a473632a649637dc44bf6230856f04837857d95fba5883e20af1ae32c54d1337392e01479c29f3f92e93320d7c8238684cff7e8a63adbd5726fba7d16ab53ccaf2d2129305490770438386a4617f8a967adf874799ccc3b56239b936639829a64e03714f9047e6b9000b0bc329c80d8292467f96b78453c53ca95f0cbaddf24d9825ea5435c22c4d3a5999e60fe6a38dec7decfbeeeeeed9753b7f9fd8d9136d6419ea186532bfe085cae1d6e2f15e99ec7fd63a2b874c3488a4e15f834b06d5052544147a610d1e297eb404c240171af04891f3c211503ff4fc98f2834515c70d181fac004d61ed6025490b3b5c3d01998008ae8620f3860956abe9b1c40b5661a43841288c0b7aae7ed872441a3e7fc84c6d519102072a5f8014e0ce0d50a41043460f15253e1813f582941b1bacfcf131850d9f32649ee03a639320420a90150b770a5155a9a3449ead14903ccdb17e2aa045cc9c255c02714539616b4a1b1fa65a18a26a2b4a146eea84e91a225015201c9441a3c218282c2a6c4122b6040e9f3f48e24481460615424ff0305941891b7b44075896606305c9172778f081844b8e9f207a78810918ac1147599e206307871562c06206aa091efce031e289e610a5844a203d73921402b2ad3026cb843550e030031e1dfa386df141abcc1d364a06d10162066acf1327a04972854c1148fa30890283952692fc6843c49c21968062cd98273e4f9e7670c3041aae27c6b0405327cf160e53434cd9b334f524a84ba11d8c00e4a30f57151c2e3f044103212d164b04f1441515345f7cf8138750117c00ede08225424e0b5b2c389146cb0c0e40aa2042cc953957dc2cd1a3c78b172d37c839c3268b1240560ca2028a9a2f4c2ca96104554870c0c18b0c54dcf9b18547892a4e7208040550e3304412384ac8704159d90324a985386a9edce0820e29e471ead2044e116b9c9c60e5e40509da03c4923d46b0c1c3831049aab2689132e5cd943d2ce0d08298292e280cc1a70c0c4294f1b2041c1a2f6cb9ba93c3132af2dc9162cc15ac24453821052109a2ace2e83102100d4bec3c290305104c5460a2901c0000daa1891333fc7043109d46831eb0a8a92384151c02ddb983e50a950d43684d9955fec8c044ea0f15415c7400b227062dae30625ef0418819a63081a1d00d83d450d143cd0b280cd252c84f1b3b630476ea58c10288871f7c10926242171a2850a460a18e9d2d40d080a7093d44c00122076581e2091f700044678d07d1c2891b2c535c05d1054e92209e88400452505ca0e80e40b2a2283923454f145498002167073e7ff698e943e7481a2e4ad0407581d367db0148122552cee880670a2a0a081f777e70c1872b774a68f3ea61047874a022850f95fd4109354738b9926213c6078d1c4a7052051a289e18e383d6962e1eace080e40a9f0ba4a801cc1a2d506cfa64bdb1128513564a602551c6043349f460f1c305500e76ce076f0a29496347092b5e7e5459196444971050484cc18240932d490ca2420c201b7e6c4d00e9401329516c4913050629e4dcb062068e1070e068b1b1e1470f422c7992650910535871a4e628c1e70d1933492401e41aa1254f9a3a4d34f1c56d0f42b6207142541329292cf848428a4025cc0067061efcf49a21366ce2f82113c70c0cb3850ee84c61a7cb0c43ac00050f439e50014686334f30a9c2879e4048fc00c40a55bcf0050eb843154dc4f8c12225a7490f63da6831c20a0a9437960a8a0f431471821e263a2b3f74c69099a1ca9e1d02253922051d6840814b9516664013c889a0428508593745c41046100c6b4e789203a4cc1155d21402d3650a2f3ac0d8a2849d412c8871610bd5f2044316159b364ab46101072758b85089e903050a1c252be4d0458d09960d0382e81267890e697af0f2c3c91a3f82d0c08082191c68144f688591218624f2e09903e40a134ff6fc8064892d390052c3870b9541223882eac70b80a892dc50c54bd50a388cf98245cbcf191124e902f269217184931bb268b1e10a8322e6b079f264cb8d161f51459450e8496b054153d644000917b8d2ccd9b3e7ce0f0a5600c24e9c1e8478b262d578e0843e80888052e64f1d1f0e3813a689541518ca98a12b845a604c382206139ad840039a142143a127c0b059429dd0c04cd50c3518f18402950f1ac270818288264db26cb06924d7115dfc747121cf1428a7335855ccc961071bf6e84193c60f9e126a28a14fa142406aa012870a0d48f830a162cf18604d0d3524c9d2c29c273f68dc9902a6cd99398578307381416500b500c49a1bc0f8c8f3c3698998285200c2cf923c56fa4a0bd4551b3c264c8d800a2e13cecc41a38296327d6c19273356528298d2d503143976ae5069814fd50e56b42c19a63c764ae8634505191f477698e233821f96ecd065c7d8c822c41077a874f1f285859f1952981233a544d4f6c30f1ab034f1439504cf9d1b762cc18a228b55550a537cbc680250559799276e5010e3264e1829fc7839b325cc8fd89a2853534831a1cd9b30370a2d6ed858d990c64e04deb890248e134468e962c140c0c99715906811420a8f949baa4150f89026ca1e6b8304a8f0738508395fe6c0e045f8d032c5091263fc6c71a933bb80fc58820c0a35d4d9f342962f5ac63cf1b0c21a255b757e64c00426a25062c21159aab0da9202a50741583f3889f263e4a2439620608072c58b17a727aa29e4acd9720288259c6698e2ce141b84887a628dedd26304666488028d151425ace429b4f507082583f070696a33e50ed6184037f4f9c1c19695142290507206cd96a4a8ad13c6f499a2cb071f47523cb53015a609992c5b039e4154628c10c286207c98539c528062a50d9f13b0743863b5404049951e3e4d9e6c699343befc81e404892f3bac7026cd106eb35cd0b5060c1059c2d0b0c5876f71fa656ea8e8e2421362a202b1a1c2b7640f4de40153f5b4c3133d406c95d3131ffa305104d70b56ae36212678226a08404af8e86141f609428d9c3b5151a0a8e227853c2a10c1248a931228979312aa3c5370bdb0e5294b833a348c9181d09219465084152e7bcaa8d1e3678dc592a1a8e20915549c0064056ad0d31077e444c9c14f12a9278824415cd97235680559e8cc9592262290418a13c69ce9210c943242acc9d1364b382de47e695db6992d4c4b0355972b3d4be450c64a8ed62346047ce658b9d2849a6944e62587257290f0cac1a36ae0ca418200a4a8012207fd291a9173729020dde4a0984b51bf53eea9a40527545630d22cfc119483045a0c5b5410270a513f60d1ca61444ea1872b07095486b6d24a3c2f381194638a9f9c1c2e7626c19ddafde93671471b18dc515e81c083029e64b16c1894fd35a875d26d9a77ddd0f8ca0e9347fe9211e5f270da0b870634d4d0266fef7fc5026c5d19321de970631b0f77ac65cfc09324560d7464db8ff2a7e859bed12c6ff8869e7146df7fdbb60d8f14bf0ccd7d7943047995e50d118ad85c9fc8963d6f737ee7ad070272c543645757a698058ee3c4a128977baf2092ecd8f5d497b9fef6453a6f75f44cc2551374b20c37702aca76c2a359b9cebe9948c29dfd27c3c91d25d74ce4e23979df851c3865af65683f99e7dccf822378df775354dcd1df510ec7a453553e501f09a5547e549f92c9f5311e65b25ba95ccfe3b4fa6ee7b79fafa4d4f2087feda0f0e34e1cc188f81f905f7f842ad5df22b928e47a6b1545d840cc1ea6390d3f8bffa4d8795847dbc7df33a7e1ff2608b683f85bf4c4a7118c38adbe8c17eef7db8f6e0763698740a77ddb7f2cecd0751a935cc5173196d9b3103b53265276acbd5726fba7d166f893bd95bd7d1add6637d9b66d2eeb2b7baf0cc41fad86fb39ec4f32dc51728d30dfdad943de4dd1dd4703413c09c46367f061433f247ab50cfef7df276ac089db19fc51f631c2e583f0afb4b6f3bc877df88effbef07bf89d66556d632b50070ed700909f4aafd7ebf52f4cc2fd2338702d8479e4ff7d3e02f6e18ff20a73e98761398f42ecc13cca4a64317d2f311443a85eaba059f1386bfc1c062bfedc2ab99abdf0d8f9d5c25a5ad18f4bd9b2b0af812bb5d1e3f0a4ee6dec8b6077e85b88721ead780944398f88a4f0eb6bbda084dbf9f5e32c87cf0213e18b61586a8976d8793f7a955b7baf4cf6e1b49d9db848d2272d2dadd748a92c265ea7c9385bad7529d7d720897b1cf5ca936ad3ab2688973e3c2ed53c3cd6bc8a96477975559543fe17725829864b9d8b4b4acab9a4bca5de69be62ccdd7c8bac430f581265855744b9dc5bf1e98e5e953917bfb5f8f1661758666d0b2ce53b97ffc98e8d301609661260e94731874571a5f4bdf7d25dbae7d5b297818747ae467b5991ad73d7f2494fda6a8e7ecb7d34547afd18cb9fd1fd7136f42392d28a1f6de42f83fc7d78e47e5b213a4c0c45ee277d4e9419734e9e3b0edfacfed1f92e0e2161ecdcb27da0dddba1cd8238e610eddafde779d498db3df7f641df823df8a1383ae5f05e5cc443e6fb3b64beef943f11f69e385e207b7f45ef61a253f6442235bfb01fd13540eab45aa1b8dd8f1e15259365f728a7f9138d9a822697e8110d713b1c935a49a5ba15e6a43fbdef6fafe3da58ea3fe706b247d77425a7edaebd886f0ce28426d7a72edb6cb24da6956ea2cc467c3fe7f46f9a0990591baddfd71f96326b8eedec7fb3d60fc4d26916c7ecec0c31b7fe273ff949a9f5a2dfe1f75f7b0ffb1ddec3de29879b08cb135ba06b50ee3fb17b4fb493291e83f820e9b457531aac6c22e26067ad37fbf60dbfe58c85db5b0fbb5d5bfbb57d4917c716148a94d26fa23fa78ccaeedefb3bd554ea3744efed5155a34779149158bbda55eea859e0b9dedba9147b222ef644ff418cc3bdf55ab9ca1c3888e45c0ff7687b0e5dd92eee83f2f6e10c3177fbe9715bd8893f6788b9549499464119ee702a7663101f94e7dbfadf7b6253fdefbf2a8ede831e2edaa07bef7774ef61e9343cd62fd2d97b6efbba754c15834d937cd6939a5e6423ee18c4a92ad39fdc48390e8f41a49d3c5f8a4df367c576b287dc4fa810f2749a7b11df900b967bdb74d28dce6d36413729f1b83d7761d67250b54d4a9aacb9180cdcbb6bbcf5a48ddbbecae72cd78944680fa23fdaaf23ed05d05aab04fb096bb9e7383ccaaf1fa3d25c868fb31cf7dbe3704fb498c3544a79fd846f48caedb73df108e6cddd3d46c473f7ac3f7deb2d0898299eee4bc85a291ec14c93c0918a31799d366b4c56514e2b8edbd7ca49aea5c441c05cf13c615239a94884663ae79cd8029a01e683dda66ea574972e5dba9475932de54bd952ca96f26798913230dcacca38eda56c7987a46c39f4c427fd82317baf3af08233acee0c3030f290eea2595f1a779fd3a7cf9196b53edb0d360ddf27284bdfcae334a995256d466452fa37469b7e4f4ac14b9b522e5fcff8723998f145c6986294b39df7812153f80a776cd96d1efb8f7d8174522b61b02be78cc562b198b7bd4cb11d5cb69286bb5f29e58c1aee18d9bde1bafb0bcb6df5850712b57b4fca244a4a12d93e0422b10dcb68ed8e69b46634b56a36e810239fc9ee1962ae3f8ee96db3b2570a32ed9891ad04125c26990b17769632b021ed30151519196d3060c0a830e8f48621c1d768c46bf1c95cd0240df92e42a64f83fa2ed62eb7c8bb48d29a837b272174f12206337223a396f5c5bd4543e2782798604e30814fd0b66826338ad918f455b4019dd9c808068c092688c5b8ffa2286bb3d9f4ee60c098608225a618e18ece36fcfe6bc0c4c369f2c57087630c0b99624ef311b2f7e7d1b243064d9886d252820605c9e28134eb7697349a50c7422699d364c8983264c890e12d5f1fc07270efe5b133160e1bf9400e3b8fc25c163124f816226e21c347b215250dbca371d8a373110066a20c97d1322a0514c898e1337ac60cb9fd873a628831dac769f265844c33684737007646431a6715cc0a2aa8c02ba8a02b9043af1104b490e9e8280001a8a0021b4c483468582047a77c0406a02b90f3359230c3861b8000843a8eb60a645e2e8d96fe1a9db245a21132596081054d41705ee01774b52064aa218350998bf3ab388484ecedddb2be400003b041801a3566d87025375fa3532eda60667f9ac7ceded808466d8218b5da8bb55a0d17d56a2f6ab51a9dde4847722b408aeadedd534afa0a12cbd4b19b4f89195f261e6fb7cfd872251e29c83467b8c19625b692067d49652dd7bad1cd52902ad3e7406e86196b2d68ab64732ad39f616606183a030c9df1e54a496978c3d86695e5cf2873afec6780b9163683d1661916ee30cad269f70e0d4929a59492564a444444b45517af20db4f11af48f2f99cd35f86b47b8823e5c9a90589e05d6579572010c76a459569906ad55ea674aa75dbb80ff43e2969bea1bde19ceeeeee33884fca1b2da534c097b7e7f0e7e1a9b5d62af308d6bf75abb18a4167b3b9d7ae5db70737dfb6bf314fdaf0b87ddbb96d1b6d4a2f2ccfbf9dc3c6c44e7272939bdc52add8c869dd9c5740dbac55722ff1489b626cfb7944a7b518c49c384a0a802cd7a862ebb4d8fc2b42cd4168b93ec573cd000071db8f1cc771b4e380b5d6da740174e62ddb721c96b567edaedd74d6f02f74526ae7ec3ae70653fa56b7ca5469a594c6e04e48cefb9967485f0e5a5ae96cfa331441f1135dd29034ea775cfd8db3b3865aeb956fe973a2e5ba9bebc6d4ebd30e9ed68ac109e713c19fb2f4fe93a2cc9efd0fbab7cf6123f32df746ac94de739e671b49dca5a974a4fbf9ded7784ca5cee171f9a0fbf947b8b70fe222f9f5a7878ba4fd0feacf9776b2f79ff745df5b205389fbf910e8bac9bdccf1793fbd0bb4aee7799ef733f4807b39bd67f8afef6cbf3fd23df7470281c6a80984c13bb4e23524def0b65488fff5fe17d6ed75d716c360d8c8d8dfa3bbba7253102ce41b07e9c77204c7ebfe8b4bff7dbd1e067b7577e3ae013a17c553eda8ccc521bb669687915cf1388d02197d83604198b563998a4cfda3ec925b1c0702070292052946a2d1cadcf9a3eca2b9db3263c68c41da9aebf0e8ddcdd5b7d636c889e06675c1a7a3a55d966a366cd4963af728351a1252ad7347e24f5b85618b24fbf5f145fef3baf78f84e05ed84b1cbbec1d95c1efc1b1d9234872c5e60cdf7c328a6222ab946a2833da549d1fa36faae96f9f62aaacb65ada7c0bcab77ccbb7b67c6b4269280ee5491c877fd2be97f2538e5a9af3e9eceeeea53cf258ca8d9be8b71d97b2834b1b763b78eed856dbb416d3a6e7c99e27bbe75bde37a73dc97ee53aecc9c0db792d0a0173f77352da793d12c9b47a1d2d75ae644a339d42c04c71d7530d7dc29c53e6f078c1d3edbce9a03f369cce556c24998a1a4cc9c39c933e8d0dc782df95dfc0c9fec6cc9c33b3e812341b4b195011a99b76d259e5e1bd91d24a593f7737991ad4ace4300f8e7be2f1e6cd1b9f4feba45f6ba5df37d6613b9147fe2e582bd91f4bf6ef2692c6976be4a549f6b7de569e0e7bf640098ac5b051104792b9a756b12962b2179b27b955532cecc47db3656565490f43df78527d2b83ca8e846249da96623971c70dcbda7baf40bb657992bde3cd82b612b8b2cdb448cdf5a5e5b619ea902512922339924bae3877b4ff3dec250c8fd65e0a3e8ed93d288e5d9199ebc76c0adf864e0e75d03cd626ecf5c223cd0d1365f8f77bcb819df79fb755599f8432585aca9f67032983cdc973cfd29eb78228367495723f012406069035fcbb7b297b1f3e8b18d10a518622c80478717cb097372260eb6bc330fc9a27751fbe0d79c37ef853e97e18b61628d6e651dbad3bb971db36c58371b6e22d391b71e7963cc68e68b8e3f6dfab7aaf508c195d71b4d9e796ccf5903103135979a35dc57986595221b5dc44bfda5aaed5e616a2915824ca7954f495963b7bd1bdc057240e793f369104ef50ae3cef5bcb95e41b2ba90435ad547025a8f0494dd584e262911d2b8d569e4a33873f91f80015aa880f40218f96278d4d25b5b22c13565076bb0414ef77ccec1579dfe2650ef7701103642df09116dfe25dbcf8232e5e868bbc9739642f73782f645a2da8b97aab85f8a29c33ab6bf445ff493989621288a3f744efa10eef593c0b31482d7ba3d4d29a47ae75a575e55744af721a11762aa7798d1beed8542bdcdca1d7b1e287acd3bc1fc22d65f4fe9e7881a9f4e28bdeef0b8fb2915684632abdc072bac0e157cb1edebee977e275da53ccc2432821b5dc248e4bb97ef49b8577e8c7ae336c68e815fe0c757886d9cefb9738fe0c0f72f582ccf2d6030475b4d491ca5448ede57388eedef3de726fffb3816e67ee476abb18d7d65aeea837bcc4bd5547ff6c8364e5d5a0f0ca5c219b559a3b7a595e96bc61b4595de0add9ac1a172109baa38ca2ab2ce3c9287b5992869755748564d5b5d92c735f414f0ac11f3d69e8c71192ee8f46727b20371e3d7b9b82dceffdb0a5dcff9a91dbdf54f967c3ed794e50f2168a433a7b58c82c8f4e64f2e6a18eed5f864cf763fa682352fef018e6f1de0a32b5529643a21c7e32c9fbf03999d48fd435fcc3f00994480e233b9245c0ac3bafed4b5b67526cdc4864cbe15379956161f8afefee70488c225b86e111476e02c31f41f0e977e2d8e328af8e5cb8fe1f2c0cdd7dc2f0083ecd2fd1fb501c6d64975979fbdb63032325ce55ae3677a2cc5d64cb153bbd9a1a8134a3d1903c772454464652eae200bf8a46806e67faa31155dae6cf0e294f3103f9602cd759375c6ba502d249143714a791d99a525b524ef3a33537d66bc66e2e50ec7dc665a5bb7b924b510aebea7a5db18154b3519d3cf72816b8b1c04d4b650a86f6b39ead59db515b0b6b5fade907824fa53c8b47bad11e8da5751a47ab924f86e04aac3468f207b39e54c473d3fc6d6bb2d66e155baeababab6bc3dbf7c4e086edbcd845db4ee893b3914645a98bab6cf3f6f8fbba0ba06e67cf7b1c4e9b933abaecdf752ec621242fc85c21de556e3acae18edb95bc51cbee5ded80917bf392ed8f43486a35ca8d5b58894fee08a3855586a1a5a396b72b4963dcae44e83cb6b0cafe34aaaea8ec5a366c8c4e55b9e9fba6eeb7aa5cffcb60c58f76e85b29f6df18d821d94a17c0f9f5e17fa168a386848da50b785a9d966cfcf0d83d4cb420e85dd083bd67adb417f42a1ebfefec7f1d1ea51d1b9e14beff52cb0eff1b83efc397ad14865138baeffbbe8f1385483b6ec741f17b1a32d9eedc4f53a9a9ac1d32bbbaf287b10cbd0c8b2030bbba720ce19f477ee3caedbce2873ef6443fca8a18b00202434f8471cca3ba627675e5a69aff345aad46c569bd6468ab4cfb8ad2d7ebfb1c8f9dc3b7ddb5d735a41c3ef81f28d2246ee7d06b53c903afab5df928d8e10e91c34df5e1db98ea8ad3bcb7b8d7618278efdf523dd5fd9c084277673d68b13e8e19fadb59068d32083a083e7525d883a0d554f2e05eafbfeeafc5979e074eeb5e864c1feef0571d5588d4aa4af4ab61e3de124256cfc073c75a761b4b41a41dd954f287892f82f081d3ea8730edb493f3c0697d153a18686a55a96e3c725f6156065efba240b619260ee9fcc242c03c02c9163771df545f0f9b5f9f93b5ca727777e57e138774575757151b80792422372c04ec460e8fa015bd0b907c30f05f8413f67a187ee191935c3914472af55c7b5a79cb6343a9228fce82c5e1538a052a95dd6914ec875ba32a133f40d4e7b8987a6739710473a555eae5168a4f2e5d0b82b6f1c7d9ef3b710802babaaac8dd2c8f14e066b97153a8e37b6fab56ac6598b882e8c7ae5fca1b314c64ed0a7c63c4c8234d1c67f99347dffbe7638cec9ff7768737241a51e2940304437f2404436f42e718c246ec5bf05b0c431cf660a1a58e9610c4451bf477df58c82c377d58d664b175da9d9b0cb49fcd84d0a853e70dc81d04bb8e28c6428216b215405cb890892d44094416626c840eec80f4d50a7c85af5e922be4e2e292325c97eaec3d6c7b6de1066edfe66d019ff2a8e46d15353d0a15c34800008000b314002020100a8705239150349ae49046fb14000e898e407250198963498e032908a48c21841004082100008088682410085491f700f761f90214093768167e13d7818a9dce5728a1d0eea4d33d539e5af3f86fe2ab72ea3ea375bcc69718044e9cb57d12c6d6e9f74e5e5244c01cfdcbeafa4d056ee2042f659d394784a1b9eb1dfcc436901199a54d0bafe1d64c75e3c4d27e61ed97e44933596a8e2f68d7e304435c2c6ccad7c9895eb97c39f1d334ee21e86c89fb21058dc40e21673627c65201fcbc1a804dbb430e914ebd8ee3a057f78b78eda648f4037e988bfb933885e94511b1a1ed9f0e54835767bbfd7a9a8e66eba1f873d9aaa5c001d23eb2be19073e2a9ead1559020c939937f440dee58016153a9cb62e027552f645a2185cd1172ca1e2d6f77e1f5b3525211fee1ca97e10bf96df8494565a65c7e255367bbaec84623cd04c93d97a8d034d3e7267dbd89fb8dd96f1bedbe70334d50c560dce1077ca6291c647eb6c9f676577ed75420dc5efa1e68f51d3e951535add249b0406d2198d62bbda087e933ec7f0a8040f9c3fd62dbfe875e04792dad429505b4b0c66c1b1910f6275f58dbd520e1b34e7f93d0bcd6292b53a63312795a2c1b511deed9a6cdf91667d258fa661d3f75589d3681c0e568f89ac07f20afa2568835c4e0d20deab2eccad939953a08646eff54435c7360fd627de3cbe411fb889ad59e02cdf0841176a847f5b122f6f1b10cc8f800b049dcf5235c78614cc0c96515410fc10eaac550f7b5506d936720a308d018f76ef56a83b8b01f293f723ec5e8b28250c7a7cf4a680fd028136540f934ca25d817b36aaeef023386b5a8c06d1c2848068e6e4462e5294c41123e1f1bb94c92ff0eb208efa8e230dfb2fde398061907f4f9c3dbbdabedaaed0eeff8c2b4ee8b4e5f296a882528587eab9e262573ebb39932564bb0622385a96b7b8727bf9182bc70be69f2b95250e02be993bd26f21d5818d175b8fcc2d4c37b9cde1a9088f9437093766c7a7b0fd65a19410533b8cd637564870767d7fd210d6e67e4ee7042ef2fd27e069f2b0cbe4c35ce2e087c8807811c717fed1907ee3c9f5fdc73fa2368c3a8b7a12c4310abed7c7b5248eb7a3b1aa2a99d6815201ca2a9dc5d0f2d5ffb03fb152ab64469200c32bb292d3ef23603d42d005b0e97c24b6e214f622bee59839d21dd1ba216c33d7112d6eaa1fdf0e250457b89cf6d69825d452f76f90df22f00ebf353e9ba39b83cf4df4dccad8d0b9fb133512f00f306758c6f2e21d627b75ac7b79a484d088643794f9e19e70b796574b81de1d37c63578a2bb1cc5fd3de87a369e6c4b39c9dabce7132398d3f1d95a78b6d4b42abf33c46482971931eb27472d40312d7e182c991e30e79ef29eb5af6b7dc6a7a0588e2694ab3682aaa6f4d6eae1c5fc4901737313cc31e681d7980c6829b7d2068807ac1f759c7c4e06317afe21c83db0ed9b950975671236b4b19b0b279dc57da8a4d802d2de57a64c3f0e896d6a2bde56080a466c917546812097bd82c8e5fee073c010066790a50781d7fa511b503e2c483b91a5b17b084eac487450dc4a98eff57fd8079ac07c9a706dc1c6b85039fce975c632755c4915f66607b9a1cb8de64c937f3b0f920f3b80806aacc63c1affd5384e452a0024cc35fddc5aa6894767aaa192c9ec063466a8083fe8a0b2bd9abe2f8460d33b3b828205337f1bf32f0c14c7fddffb1a85ad0e01a514fcd7f660f1387826a86276dd8e82685ec7705c5414dee0d3ad18d8b0c0dd98cadea144c4810b5285566b156c9896eb5089317bee137fb480896dc325bd2c8438335a087d1d961e8bfd6822bba8ab53b33a70dc2e216346db75d27522c85017d92d55ec21575954f0249a98a9f60bbd347233a74976ae6ad7bb1d5123363e4a292558f4b3f4b0260318d2c1e49006af73dced302fe9443ec8704b6597cdddc8e622bbeed3c17fe7f5a41105e01c5c1caa018ea4c81ab2323ad58e56e50c576559800ee394f56cec5162461d519d9ffbc5304ed942b4aca55d624f98cfe9864ac3598ea8ffea72b9e3e40784dd09f1501c0ef86e17b9c222a3cf2b69805d6966dcbe7eddffdb321e2ff3f6272dfdf99076ee571c60f12f4eba0fc9776ffe1ef94f6ef43ba11755f89a8739f01e04ca7fc1873a5d4340f5ca448821a267f229b9801fe11a1896e4f05684371c31884fdc111768f03f1766f29b2560309f2bc6d03c05535de86e16a035017886f3094c493cc3b39446087cfd13515da6283021d1cf52e4a6d56c283d7b7a34ad90cf42f16aec102403a0bb4329179541a3431de3849cd505eef0ce4fd95d1fc037b5655bee76460d19add5567a050746906efb399c48d86e605db91c4d6b7ad90d3bef8a8af32e0ff9314074c705f9ecfe12e15c3101a63514dd694d4f393fad5e3774b1fe40f7b5c4b8410b2d706448df69448475afc979e7629573c62165f83761e1a28a70820d9a7a250ad91f7e7a6905280d3969a49f32358e5dacc8848073d4bd9cb96431d2d9e6ed9fa492525a17c6782558c8caafce5ea37242e75a56aa98ed3c64442742f269a67c7a233f70393cbce01678cd31cd4e1aa9ce12669a083073025e40d05b0f7f1bf08047d2581f877000cf70e6b51da920ab287f30ff836611bcf651a05ba1c9622c9775371c617347b4e18591fedadb1874e4fca5c0020780d595465767625ea4b3325c5c7066a6b63a802ce0e64dc3cb9692e0f0a380ed1ec76d05f407e074f55fff3b21dddcf3642ba0480feb6542a84a3f0cdef047705fe4ad4ba3c584bfe2afe358baa8bfaccfb3b5ee12c93990e7d17a703dbcdd7ea6f4327e58789aafccc7b866ecb560efff93d1a35398e44c70fdc19f0258f08fa953f34beaa3fd7800ed522a998dc5cad3e38925b24f8c4d5902a32bdf372634b2a0b4e884bd1b501decbd0602c0053243410224790fcfc68477cfd59b4149630bc5ca81544dd2580cf9f61a24a4bd929691acade8f06a59535760a8305238a22a4bbb61a5d8570cb6d1af46608c4c0a00b898aab8f4a0f633b538bb47a6576a99ee7fbc77f898f27b3c80e0aee6ac93e3f2e8335bdfb3cf0142a7c02f19d654d8043bb8718b6e1f6ad926ad8d893af93afb6856a33359dee20a7b65459fb2b6f1e12beb0dc76b437a09a23d0004944e8a00147bb14b3704606477e80a1fb50e80fc416e24a58023ef3ca0347741aee562bb22d268ed6fc01ad1bb61b0af07cc487d551bbf0d069006080e1f47881a379308978b7be4afe51dcdf5874ea15417fcb60a94f15c8ee0144f60041f42c77a34f91819bfa9505ad3f936785d7382a090aeda2154fe2b4197ff4f1384ce0f577d625032ee4c7f48d0016a86ba67af1be268e2e3864c36c7051abee1e225df0684afd3aa09e211a3ca05c7d5a755f4d43a226d80a5705fb565675657ee802ab524d3b7a476caa1f9183e1d0ca90c2722ae673e4d9d809055f21ab01017528e1fefe519a375abe522d25be294043b7ed2c6add163bf29c6261701fceef8f9ad5baa3ff13ce0aea3447422937c04c416ed68db7f6cc7811aa6f8560e070ac44a6fea506c612197db0e4f8f561d498e330665bfa23ec649ba810e494d38a6287e814cf9dcffd1575a440516c78528fd0d5fd28a6de3501db3b2daefac84d0147f1dd0f39166631bc3d177a682f06a35acc9aed7b0da55c72a0dd7700b9c8c1e96b3814a65f284812c3178937f4cbb781f93c4e2b9879ab1e2970be648f063cedb2961d2be34d4ff89d2e5a77049e00417123cde92f0cf26cde02857def5534e69f32cbd4f7428d44982bf2404740172d6bfc1cf997914569a1d9a3040ebe24ff2cac31b38e82be1e704151532ab15e913b0e8e5fa18724237f0b0f5762d7f62af081c2b189c0c073b9197e29448eb0b3d953e04fa456b8a5308369a6a833c3d83a72ef260d457bddae69a25275ed5c3070b01a5547a61f215028ed2981ac97c975e45dff23e8d9091380fa1bdae333949fe1b51dfcd909b6ac8730033646c32f05df8e8dd65a7243c5e689b0e9b7b02e6d206dda03921c6034c2f07d4155b996bfdb8355064738cb41607b69216e76bb4e5727c1af2c97f1b29297d1d104466678c5560c6ad0d523d2042a7450287e8fcabebe6cd10993600a545cd08f2a9ae3376f39113706ffbd4aaf510786415da0211d196f140333da3fe9a767e3ff4f04f6587a67908e6887a6fadc461eb82aded5174db3ea510418cb1876b28fbac36b497365a3723b1483bd9cd670d2c452d37553bb3cd90b5bdbe0a7b6a0d2e89de948829c15a1cb6838f9e0fd5a0517d4102d0b0baafa86aec5903b5fe6f94d68aa28a549cb48debfff13102a57379c90e84bc8b6697ebc81dcc762a9bf343839071900e9be8cc8358ffb6212b315a8f878d0e573064afd99b613ae8e2aa7b2d1e84abb72b92770504529fca68a903d664fc9f09d05b34d78374b1080df9e5d837f3d97c932d50edf759b1d8fdc331a444d4dc00d7e2c122079a604424ef8832a748663f113d5e3a3f10e2365956e11024c04d8efa89ab4709dbff258121b48cce784c3546f664611d62441acc8d287afb0047c7f189260b574ccf7c10c62a3dd564d111083feb094d47bf5433a09ae04dab51471a65846f59a18c590792f6dd7bd23efa20b5e5da47e872752f8770c851e215ce04db9713573d65a2af1335f4acd03e2548c2877c4ce371a1a7d9a1cb306212179c5b8f08a03d88585550d31f1355ebb1a688e6d33207a3b0de5e25224055cf27916aef30fe8b8e4227298e5ea91b90a42b0faef525656a4f9a04cb089678e413151a306d663297b6c72fc811fff594267fb22fd5558c0d53438f29c0ebd8e1e1c87d932deebab111b3fdc6f25d9629f4025dac5df29d3c6b195e0d94508256dc030059d110367ca1ae6fb306f5d17709eda5aacf31a7299ae5749f1baae8b8c26c8f9659d0ffab876ae685759451257ed64300d5c3ceb6519f3d66476d9e606483b0e32d5afe8ec35cbe68084d8c4509abeb7267441555797c7abe464a33b6f0114bd181e292e02d84df8b29c53a74a0baa12ce017c56b529bbd02dec998cca69d72fb69302395c5f56937dc109f0ea83243a2f110e661a9c4ff4890a9f215a8f34095ae8ccf38858b050587fe7aa152d9ccde1df032b0e22df410afe2b8698ccbf77c6f70190117b1e08abb7206667ef6ebc4a9c3d3d3618873ac098085d78cbb530f62645c0233a27d869abd5d51c2f189e5999c7615aa50acbf3d257526e9ddc59c16a4206cafe831e8aedc7abbc194966c9ada9b21b26e6360835e82dfa04f2ce444382fd22a51121c3fa73749a28488ed32229d8cf9c488578a3c63b24a108848c8da66899fb489d334d9ca52e48a5ff2a03837a59a600a21e4919b82e9d42244eb998d21ff952d47042e1817eb56527558f07eae6e6e29bf2afdc4caef9cf20995333f1775c8e544ef0080f640f4cc231b134a3828d0918d782ad714a3c94a388c242980a61a928f5c63a37d61b966fa9ade68b8252b9d15413b61adc35012f620e7cd7f01ea120809a1eac411bfbfeafea6cc95f373bc65bc67e8699906dd61fac5ee6663f6a39a73111326ade8dfea851fd1cee18ae0da7124687ad19a6864bacee40b0f2a407c646838ab4a7e13bcea756ff622e15971989120b0fa58a08cf1e22c3db7f2aa5ef531ad75ead1fb3030600a7ea44de0a986094b20b1d52e26943f9f6b9e3058efde50c6cdf21474025cdb0546a0d461f7438c23f93a81df08dd5a26ef67c73032218e6a62314bb8aa07e68497021ea00d471562ae3403e2a9b6ff14227e0b9f670a4cf5e7c5cef89b1c26c65686afbf97a81142343cdb688f880b748a516d4dfb214a1be9ae189d4b630cfa262d991b2a4b2a88aa5e36a2572711014ee66d0b511ea8f2f7602042fdf6c9244e89c9ea184dbc3ab6409d44af6ef338d65662cb5d5a85789f36f63d0c89708bddf035372dff0fed3c6b4af07815407b940a963005b7bf91e1dd2b94acdde3586407f2f5b37cf25da46fdd0ccb09adc516175cd1a788a7cd1ffe21a4837618cff70062d4ef2a6901c8a1bc0396c32543f4bacb7d840c8c863152573d8c2a81e9ffef116f2c4e7eac2159f6df32dfbec26df446a6423c321ed99e312c216f6734223a6173ba87306810d4b8bc4255cde6b4c9fcf79460fd439082f26243d4650b850504db98b88ecea56b6537b8a9c59eb4045d2587e594505c3ceb7cddf9f0ab2953b7b2123e1ef95f063b17c20c17258df2480b9d45ca9e11effc90328cb6060966a009e821544c6fca709ec799cf4a4fedaf1697ef0133bdb63ca9d2ef66abc1f2ca6f581ce2eb37e26632d0a7c68edef824b95527b87d7dd2513ba9ee7b8d93b0885ffa42620fe3d2888db85c59837b3861ca49c8173b03ba74a6064b5d70dda7b09bb8872ed11e0b9cb089bdece9c35bc5fa409fdcbe09c2bb029d652791b3b70eb45d1d354554f8afee7564f79f582b0084593682385a7cc423ec74a985a1bc9a9bd61be9f40eccc8d906de825288e374b64e79889432fbe4b50792939d9b4bc57114463483d6c983d80549ad38567263bfda8ebb83cfb310278bf762864c5b42c43e172c9f7227a6a07a60656be4a43aec7da5f56876a4d82c81a4e0cc3a3b74abae5f9b265288ab263fba603d3cc8cac23d198f58dd3ed162627c5b402d2e48a61602f23644f2ba62407ff02854008769e876efab4637fb0b5af4a64553b65c21cccafa32256b741e75a1c5045fda71c919299ef25d7c3d48ff5d69b9b76ad658b6e717907b8619bf8bd5bc881e71ea94216161730651fd4c4ffb842ffa18411b80952a2a1115541c372e1968028b94eb6e95b11269c6b03f9fc9df1e5423bde64769a0d45974fc08c373e59529e8ccf4c1c762def1cbd770623ede0e099e963709d563779254230895190a2a2639e00332090d8ab65e3a80d3618d159eb499e6ea0e07d88d13b01d9aca73054ab70975ebef54ef74eca5e918845bbe4be1dcca54d86a3aca2e748eb4c5eb9d5e6d93176f66f188308e41d94832c686604dd02c5e77cd295bb09567f132e486f62496ef2e9e299801a34ddaeaf9f159e800e6fcb707c3532f04c28cc99cdfa95578547ae5923086e8040b2cc103987256583f2b4c33eae8d1a166db842282e9f2f3b6514edb0a520bad9c1d6a9824a091e946d8ae4547b8c4d0f6e1a6eecff43acd47f8053349c396f1e8da336cb9df7e80649499d495729163f92760786816d064cc36bcbec36a3b80aeaed13312166e1a06c52335102cf26b17c114d1b15075da72b7b40cd87edbe62040e4ad16318c999f36584d363456989754aa8db5a09f2c2f0ff98c75f8c9f89789b662f000e00efc554569557fc0a354f0d97c9c1518ea4b51f718d40b177b3e28fc22969246297acbae5e52ec00780ad57343399ed9d30cac5ee9486010c93366e9625c18e5063a6c94ebd5e1e7ff3a8cc7c085f010505f6e1d31c184b26967c0a6ffdbfd634bd7b2301d7a089e26fa4049f0a6571c9174dade29a7e8a515c5ca06e2b96a61c5fc186c7b7218d907db8a37af7162e44808c06af56289c1c6d81ae8b4d7aa26644f0e59647ce709a8ab06b185354b1f477ec4bf6edafff109be2bbd29b3262b52e68cd03440c37a8fb3b89eee9344ed59d88013cac98ed45517ee42f75f911a4467c29285e5d70049efd5c9e831cae33dda29d9cbe508c5e559e16d156589523d3c23bf04aae72db7967541a42b921471fed7b55d6b97febfcf09a024885f771523f7e494e6a0ed677e8b976e92dda987b9f016c44b543e93f5ea9f57770a0f9badad6e2345e6ba89a68a3b14e6ecda5499a60d0c8ad95c92481b552c3a835177e99fc5f32c33174420be706453096333fe4f410148f5cf0f93471612f987240789a3804fdb89e19d072df2ca3e6673909dab79f46837aa2b9ef32d4fb5008f1a9f71738f4bfd0ca01cf11ed2984629c64d2b8400fb48e06c13c3814faf471988b85f5ad0679c35415527d5f9947374b413bd4ef8c1ecf89359e2f8f33a8f070ef06efd284b82423e185fd86cf41d34f2d1353fba6470dd437a129b6286d9fd6770e2ab96e81f17c2afac05d1334253a919b5c017cbce7ae2ff89584bd2d06c3c5ce497090fca027a540148e8ca3ba672c407455ebe69e4f87824b9d64b6b87086bf1dd120d0828d261ba8dfc3159550180c9bc3101dee34f6c4ebdd61647fc9fedcacde1c1d4087488470845fd5f2988407ee119e2e0f2a72d681c91e4671c0367c62bc250e8d756fe3c5d08eb591ac0f2cd0a199d8dbd278d136b18b6b9ec38a34c64a848c9ecfc7a89ab33bb51533d36b1672b5b1cbcdee282bd979b7f854800e94364e3957f6b26626490c9c78086ef827ef70efa71ed00ac779a9e909799d3a8d52e41c8c8151ec311265395dbc01c10f9095ca47b00f4788f9664911b1a0402f02866afc8aadd3f53c63108a6574e840d03e8b779865501b307f941aeec64834382f2256810e6b30ee27e6ab8d31e89420e0447f002fa3c38531e0e4fa3f3c37e4a20046d8d71ac7988fee083360e8369188193ffa2d84b521bb872f942d2052b2437e7fa2bae000b9c52a2b5b3a7d867ef0eb38b15b0afc4391adfbc11885604d12e768c687d3585ea0e82dcf5eb691536d1408250fcd9766b98b1ecef1808b4263d9e4034af70a7ea5197220020c87c7c283bf209572dc768e424b957ae78e3ecfe47b6f21129c0d15e440b3f9938ff9590705a8c5d0ee0a8278f63cd0c6e4650e88eddeeef984358df0d891a882b08cbc5c6fba1556b416d2b7aaa365ed59478bc815d7a3baf244291e2a6222e28fa98a5c7e2ad2c2b9cbced97170384750ae3376c9c2168dce6fe9f169f59da91e2bee3eb3ccc1d9c9a678572bef7461ea48f3ab404d44a3de90217490a7cbf645ef31da0205fd6d81ec11d9b9809bf009280d4a05ba4e6f761109a8396eb0af5e2118e581eecf22bd8d9bd9e828cd5e3cd309458478f6c01760e22176282a140b8de08dacb7f1fee6e15603cd58d64309441f6956ff0ac23b5802701427700d50336f5c26f020ab6028a80c081c7af9f67816162775cdcce6be27e939d452da3d2b08eff76a0e276c54c52d55a3c002ca8e9fa7cc0f8414880d763302b650848ea980296c759d26b72161e7376ed41e24c71be16dbe939781e32276e8a6e6ad4e38605986733bc06fd614bcdae384bcc8560861c2f65cebc746ad0ee289e87d6b097f4929f080dc8c2ec4fd9954c8fe6fd7a3e9715d04539b812629b762f1c6c30e734672671801d2c900e85a7b013bd30df6d47dbed424fd2a5942a12003e60d6d20c34ed5af7d34f6a7ed1488d14edff0e822a0ef48dd6638115cb2d18efe3e19857af7136cd50dca3763c1d5afd6dc283ecee67bf290f7580a64bbbda94afe5a59be9c8f847abb39a50963d2e3b128ac72b9edb90602ea0af22c579cc86b84ebbe19107932d8267b4c41f8c9a38a8ac59525e4ff406e31f55b86f791c0ffdb29817f695970d7d25cf749b190bcc0760479ac4b502aeac97649529bbfdd8d1843715080ed6158433fe94f9fea7e7cd7f50968d32e8eeaede9a42c53c63f55818446c7cc68899dd6e9db53b3c7118c39c493500a758decc3831b6a93ef72ad8cbd1183b79e4478c3349295fcfca42aef5ab4f606196790089325b605d2191b4b819a7641dde6b4ab483025c15f91c5f7436c3036dc30ecde2968f47b905a5f62f7f18115f4c795835510b6caf16eedd8eb672e2264f4bb5c31f15648fe31f8146b811488be9599090083bcd08d4d54b88b34e169cc82c8706078c62b42b166c556a9c6d90c89f316d22ad501fba7f97c7456aa3f7451b83790b2dbcc09bc7054b06aec00f720f96fcc87d61f51a3a65e04315d07471e0df43379412c291caf5aa42e50fc3b71e5e8ebc24cb00a009b29714621293383d54822ee9438186eb8024d56d54195dfe3a3daa81678f4bf9cbd71c523e2cb303c24c25aee6f7626a275ee1897b099ac9e35fd2626efbd493b00c23ff73ec7188acbca8dfbb96809a5ff182abe1e0dfdff91422091acbafaa7c9a164d28c3696741c0d67ea06176e66a35b920a7f001b5f984c8c893a44a22479e8d279bb40eb6e432b30d050425d8494eef3327499824cfee3d204c6ce6603cfc99bbdc2578d79bdc66a38c7f076c77e1bd9dee578077ad433d2a54d49d80784b3bc7293cb1303154a0a90de16b331930d866094ed1c9944cc14ca0d82a7ac9dd4bbab962d8f07c499bc75764a6a5c46f8319c2f5920e60dea7daf427614d5cf330bcfd0e3c235bcfaa537252646675c4f5087e7a722b58f8fbe12c8a8fe97083e83762fdadc9f4d72fb976aa5850f0151fec19e2cb0454ca83cd5cd51be218517478c39364301879aa1f8c811e4d5cddbf1d5ee5af1dafd24dac2fb11465d54c53e4bc524f0cff7e82368d154ff9d6f4a56a25794b5be543a1906732bf3f4b7aec544e1f203fa064ce626768ac71af7cda1da859530021cf81386d5532a871e84caea62dc222d93a39102f0f281018a3b3f8c1918884129d2f2c12ba747a4c98f96502bfa1c65a6d68f9afa61afb2aa7aebc9a2afcc6c5e2e5f563cb22748693044d5390c521a4cf25f7fa48f595e3c7b7a715d73c5a9ff9de56581c9ec3e5988a5680b8adb1b5a0d54c506c0a50c8a282ad3bc3d6f7ce2c11781030650a629ba8f631587f4d4845a0f59e6db227989d0dab4a210311e93e75223df5d2b898cb38b55800953f357a79912fb107b55f08051a38dfbcf52f04b7c4645d3ef76af2e9d06bc45127559354fc40b9b074c1be53bab35dc6f4df4e6b6641aab0f46cfc2cb747df329728c487c5c65027aa8ddc95bf3512059177030b941ffb4b1a211efcbc4de8ec10d04b5ef959fac8204e01c7b39dd559505b7967439e2d5fae2f8b31566809315a1db10b37b7a7e51abbfc524336d0b98330386425a8b8322933d8868e1a1526a50fb56ec67899cc8be0d5a19405d613199437ec3ea70287f592615b4e4b101c12afb4c99801fd3a0cd52f9e96f7d7f278c4b5c66d9c12bf6db33c1b270ccd93b650bf22d010a18b587d4419c28861348ef7ba825a369bd3157dd0e2dad35858cf16a735741f39b4fed40422d7d8add44c09b02d9c32eba184905bfa0ce646c24ce2515d933550b162f332d77a05033c5715934141fdf158f82195510252929ba421625faaa788c797c0fab1247bc732b8edbb2d7cc407629b81a11ba37f4bcbe34ccbf230fd614000b5d4f5203f8f1fedb214417f14b3ff83b5d9201caf8f3ace516b27e5762473b7ae0c1a9beb8c027bf217fa94fb118053ba840ca63950e69f4b1153410f636367f33a7f04bcde80ff075b1999f2c301d325eb4600a566540d4a4a8852a23605cd6a7972cb085cc35be069ec54bdced3b991231e988b23945b40c7d91c0da7f50110d8828a2d07669b3e63dcb256b508534dc1d5fab67fc0010215f820e8924f4e0f9e4ace6e5b75a8f21d6bd9dce41693900fcc0727e672718f5b024e5a136a4151bb272e2aea66010495690858ba9941806c99b8a216ebf1f5c81db9e240e4ca6284c4947fbe839ab0414f7dbdc8ede15b1cb45cc7015d812aac631e8676e4a018319c071f74453e2077c5c67ccd292d612f85254a2b639b5f6507210a335fb289575ecf57b6071b1d3f00d4bc60354be0d1983a8629a440577ad036752f1a53e10e7e6ccad4e6291c0138a59105b92ab8188e056c65b84870cee00e96a7ce005b11999181d7533536ccbce4b42526d9243dc49f1abc9c16b78ad45219e2a070088d3f0d219538dab5d448b2053e123ed29a676a2f5c3b9843e556782764a4c491766b57061e3e0dc05aed42c893582cda50e472463b9b519cf56e6bfb6ee9ffa8febb1a72c28840adea8bd100b902fe89bcc4608d25276eb836438013b2d485a181d2cbd1f6a11a3d46caecb6b6cbc9f59bc01f5931cf27807d6795cb360b72a7b4d50e9fcfe1374b48d2542b9d75da3c23f922412c3cc18993ef464c6a1672296f6e3adc54cbf8dc93d28d9c0873b6c252950d2989c54b2c02ee67b4de7c2a730dabde33a63ffbeba0813585ba65c7fac9346cae59e05c6ccf8b7cbdf3949434cf2d982fcce3c7546d464b2a79eeb3f0a2c76efbe67b7ba50d616aa19eafb1e25285a802f16f8162df1849ce78f8499437f29ebbfaa7d06a74d615d691fe5f0b888c22334715be2dfc8781ad934284c63ab1e472d94ac547bb397e0417c661cc0986cb63861313f8f4aa80bf8403548689aa07bee6c93b2e199d984380e3633b6a2d5d1b5c1a6ebcfecb567ae67c746c3a50d8c357ff7dc03bca9561c34a5aec0a4f16346f0f39beda949d1ff2a418b7b9ad8cc14078d44802c0e6f943c5834a6a981d34c231bc443b6e8a5141a299c576a15a3a9cce5819f2281aa3822c8d222029b7a0689be2212a9e526563df318d316a7deb884f7e6a5775262fa718045533091d087abea6120c13e149290e635b70fb972622bb5993514d7eded16f9ca3c578f6b445d55c5c82ebd2126ad8e525a7ce5e099c77a907089ac79ef9996e44366bb6802c7ac6fb556e2cb4724cefa0b91530fe08f24b6f038905a3e5fc4b4f265557bfbdfa600930f1ca00fbc1307671dc8dfb609f903b480ec969411d47dbc521bd701e421646fe85f8eec16d5893262cc87d3f66b0e61b0eb2884e58a7975934753f055c035e81c9a954f17f1b85e624c65f7130dbf894d7955adb39e35ce08888a71120436a9cfb1a7226c7df5c5f26048dd8d4eb324eb2efbb0dce30cad2da6bac9d28751552d919d3907c2bd47aa98356e938ce8310102a115f3e3ce37095a187afd09af84a332535e4202e64d69c0bee6a9a5577d508b2d1d467754dcbcbdfc6eeae2ae996931a211394afff6b2ec607a40d44ded114cc1f96e1b97c649493d6a7ddc29f4046193332733d0285bab0fd5a5439e170f0ee51730460685e452a147915474e64ef8fa069cd78b58d43f5c22d076a62f23e62ba0fa67c43d2c1f567f2207fcee48ec0a485feedbeeeedbdf0fe3624ef06cb7f148e89e400b48e526b1cb56b49e5c32e26af394c0692b4941d82dafda91de2bd19743c65beecf53d223c9d37a26ac61f03b79f9dc853fca55fc75d8080d1bce884b36c6143291147626973678f3a746f32fa7cfe6537180a842046af96f920acff2876f3b386e99405b5389f140c0429441e0085c771de2deb727f5730c764643beae9b1d7a6256089ec96c1cf2f915d68c9c5d624c6657315050c609497d17240e5b25280f5e85c06feeb25a9d665a5c0d33f69006337c9717fe9e5d875f0b96dac989b5d0a4274a28bbdad37c613b97565562faada5c32ee9cdc55a841d332369eccd096bc856095f537961cf65a998937b5285ca109ff624bcb6c19c700d7a4ef2fd6818f3d9350ac1d402a1d929084ebe6c122546f6358bfdad958bf07ae2dafaf57a4d291b148b6fcf5ffc112a0a86a3681e53a01d44fbd7702c98ebeaa646618bf02a6dd1979a3155962cb152c56970888b296d7f895319c65838afe8a3596b0f13c31f6823242e24ac58dfa65dab72a162c0c3223d6b9261bb35cdab4e5ef8db69fec8af91c44f17e36a98575f39692e18c19d0d2f50c8d53c78e43e27b4f802bdb0638f1fd500323e2c7193a7c939a506327839c8571bccf6b8fa495b68534e524e95d2273e323cdc2c373a7aa563b307b545dde933f109414708f518dc97d74e4585b788bdd925b22105efc44a671306f5babfc6c8d5a806b871f70c37b7e90ea91fd92ffd2dea308be57eeea689a417064e9b332c37b502da8b890e453a3c3abf533c4211cc4c7492c756d42082cb2f051808a7e3299b6b4907648e62bfed19b66c95733af37956be3b7c86238062c51c300b7691c144e45e00c1a20164483f9221191ce06c490da3a3aa329ce8d91293d591f34af02560391d7179d8857e936ebb479b7b80ab635754800a09545e739a20e47857e8d06deeb162fa9e64ad6748e14db8846730a6390af0ef0267e3001c1e8d1995efc85cafb011d37d363bdec1f404cca8d3a1cb9d5ffc7a702a0124ce4f4cd68af25dc31025cfa11d59c789f2a790c5712a9c7335a45d1ef3ac59ba0b2888c6dfedf6106b477b444d02c002658418e12b9883a500e898ba5b12b055e799c5cb07930c60350572e47231b5aed39c757c7ff7696ed7e42790b72663540aeed29a0e44428321b5691bc78c84a25adfa1f724d4625326d36135cbe7a97e4257b9ecf51ad3878060313900506e6b7a4a74e2fe9a0086082c32ce73089a7d15fcffbccadfd7608bfeef0f1a0d36a7bf6d777da13b90027fb6d267794318786b44b2648128d50bc802f40d9c1a20d372cf4169fb3ba0999abce971ff0006487a33d764365f203d7c1f1e16e4bbfdef6de58569237e2cd55369d0c65146057963f060c2b9dc98061550b635462baeec6ee049d8fa131cd3a41c3258750dc1ab90b4f7f98bf6ae3ea435a3bd90faf71caa33980402b1a6e3095160a8da776fa27c8f6751935fcaf65adfdad94b60cd550fdc4c64af2ef64f1458ee2cf843961101768215dd06e5bbf8bc68b5cf67b6b3c4bf315875e9ceb3bfaa327e73bf01801dc1a09e9ee29776d914e558d0167a8a24ac0e82d36dff92689b18b532c2fb7f647adea2eb071eb9cdab9dcf42ee1f8f4f113f2a77692c6ba104ae36508fec7776ee1b5d4e1d978c4113718f302c54b654274595f3a761cb942f616160d3ad4512958062168ef5da7dd2599ecec0f113479d1bdefec2daf72737214933037cca3d2b709727b4d9e82d10a5db066b152340dc367f94f188955976e243cf913792d46823ef43629152be0793446bba58eb29871a6c06710d6eabceec43eed628fdba0dacf2787382711847c1ff0358fbb220eb7af6bba75d88e1c926507a3435623968178867064a7e53e5a43012771d80913e89dcfe28367c294685b9184156f201b143de9f7ae76fdfe061d9cd33cb195535e5ed2a1c9ad7643256b92391cb8d3a9ab26d5d7024b1843917011be53bb0627feee642228b8c3e8b04906da7b5f5d8b040cd570dc9494a8401f10e469ab7914bb08ecd2f6e46c10116021fd2751e74d178c32f141c6d70c8278fd2ef1f97efb464ea219008eae4d9dd40b0d311d2c47e8c2a05ffc503c1fad9661f468ce826c94ecec9d17d9240c80b0582471e1ea020dd54757c025fe32a934b92651411e8d8af5d69c07aaace65b031655e06ff1ba57e7a0f4e58448e67051ebe298091adf1c3bd01da657649c407b6a91f692d05eaa00d5f3cac5b759b75303def943898c18c8dd1877928ab1a90cd9222bdb4310072bb08f27157e05bd19ada79dc41940755ac93af66239442680d275b0378dd8d410243d9d2274d618933ed2f973648850d53ba5d79c584072b62b6c27d382a6d855a0cc5d385ae0cacf387ae7ae682abe3fe49c0e05000853c866b7e716687ed0b0e48fc5d1ecbc1dd3547aa590418317ef079de6e68c03e4d70e4a069f068ad0ff585b31cc38913505519f0dfd4ee16a17e7dbdf0cf01b43cecfa8c883cb34bad37c29bcb4893619474600027822f4ff7d4ffe45039c059850bb7b72add58493a3be0fcb81002c83b28c9cdf9c03a54d9630ab534d443ec1108dd5e3543dad8f86db82837c4b7a5d2db331065468147ecfe8a0c5da88462d5ceaabd78a921f600d7a954e9c263274de8b157264eb5d383933bc1844045e86fc6bdb6ff6113eea6b43e04026546fab68f335ea60191baff11fa89bc7da90c490ce24ee9a4a9164f3a6af0b2705c0b19be6274f1d3e6ecd06fcdebdb09c8cd68e305dd21d89946ee88ac1c8e14a37c27ba9fb3ec03c2fdfef7938f3d2c3832e72adf8855a3ea42a33df4e9941d35eb5131af12bb81311f265eeff13ee02919dd10eb8110249da88f509b9373b77055dc06fccf17e2951126b773f78f3e26f4015ffc76970ff015078c265a56bb06aa775f0c9f0b755dad3d9218b0220b1894dfea970b1d2b828be79acf1263a3afd40e0b5a813b0cba1a8551161740d2161bacbe060023426c830215c5f2e6a8af60553b4d9c20050d7a64178ba7e50bf01dcb569615119ce7476eb44880f97947c92a4c9df9a32281f83398801fcec38413e366b30f28fa18a822845df53fb26bc94b1e737891032eab80be396e28aa3020e955920a346230a828eb998a269c724da8f01f5e8fede998294b79e40b7634cbb50ee8f2c5acca67942096ac1ed9a952282339832df40380b82b209a0365e9029a8d8aa19502eea67c0fe9f68bd49abd989cf68f827d32e97f6f6ca99e53d9727c3285a0aa91bc1e7f6b384afbd761131e88c45ac229c4507f06117f6c9f32f8122d1ac3d413356bb337e0e1e7f07e03acb26666601f7a4fad84acf80a61c94988052d83ea476dabb2aa9ba1c3605389d8a1c8a2a6c94694f6cc90a95d05a066a69e806566490a947666bd408205e75691135dc34fb5224c1ac62152483d9cc73881e3d8288c9c2cbe4549a6eda812926fb14cfca3302b6ef1e6a590bb62b76618aec3f1d464e1cfac3d874b839035cdbcebe295655d6d558b8506d30cc7c42868729524401ebf72e6c076a818f7996ec128bf49292c9ecec836da684355aa391579d52ee0319e25b52263807d29e4d4cb16e9f5a589c4a014128955af57861163531004b7db5f1cadc17023c5e18e2cec35287b338a5374767079124f01c62ae1e8b9c06e1efc8455269d698404292ebe3beee2b3cc4b3411fc15230c42775483118ec079635b511b180ffe2c40b352dbf3bc278ed4b28609960d7ad5b0e9769321a08545705e9dbec0fa1dfccb844beab512da1211a6bf2d1e8c650b2be80d47d3453abc1e27715fa89a94b146bfa0305040ebab0362c24d2dab13089b42913614b90e64126809bcfbe12841231cd99ecba8ded0c1fd530a066828c1e23db058e61d84c104890795116089ae61b325351fa722022076a82a0ee28aa2648c045bfae8ec90558102021c34e5330ef928c024868336bd33652da7f0ebb531a804bf31aca043c6e23e5b8487d2feade901e44f56f5b18e7a641f32e17372bc243e06e2f6d2fd6a2444853ecb6cdb9f5d72b7135219f12e1732580670a9626563c10b8194f49868b92f1256dab3221a251ccd28104c053ffabb4669eba230d687debabf39cd67b79a6b5a69c4706cc513b7d4bbfa6e4a0e1bfb8fb67aa56c2cfa62ba24d565c01097eaa3133cdec3cefa7706d0adb173d0eb9523c2b81d35ebabb6d7b2d71b92fe40c980a249fe26a427e512747da3f59a610d3e9181ab113b9d8e24751ed81b00ae2db8a2156030dda4835468f04ad16f635da57fb3687e9f1e9518e5ed5bcfea78daa1b46889f74d593c49c69b92284c4d09a939f8c88b62f556f457257cfdbde1cd78608d13f9a3cd1c59f5d9b62b41b6bff4fb3230184ac40a84f4901ddb78d3dd378ba6940b39bbbfba7013027dfa126ea0a6d28c124ce7cc268deb82557c38948469e19e4a1fb00cb41c35215e4d8907e0f54588f61bc6f01b208347d84ee1e54f0c77ba0ce2a32c12b25a5ba91f693a05edbd8b2de0e62b38d81fefa1d09ca590981eb2714489b035c05c5a6468e8d036b6410a74882ab75fb71cb337137968c58c396a90a51a8690b661e33d3088bfa4a49b120b2c55ef8c8d2386c0d241230ee250bdcce8446b04049de18e4eb47b3b88f7cda48733d9f19c92b2b0a07a5512a83430d0340a389298ce6ccb07b6d27ac413cc4cf0e412f311241db342896532ada2c9b6bf43c86c75e891b45bdb5bbcf1433382a73a82fd89f91e1824339ac3e7eb39e0dc053bdabd6cc1c56f46cae3bbb9d197feb88e10ace164dafbd736e73a4137069bf34e27e28dfcd9b0507975b7a76339a110f11365393611740eb879990b5823ca52576c70b459548a2ba21debb02f7e781bf70853983db0d8ae3f5f8ca050f61b08c0d335ecf807e09178ee5306d13f7f355bbdab4656aa8edfce0a232b0f1d8507ae27438e79f02d2d532fc318667257409e15101cc3141ee3409ac9795646bb7ca1ab62c30160344260cc9469664ad163aa8f8f0b0b1c3926c6d3a6caf482f9b2f46292ce56558c0d1dc1a7dd012c25e460df25edeb868a9fca193173441328613fca8667a579fa607f4622fa43f1cf5ba489992f3b0788ae01259098b1df06a97c8371079e0578f2c12b3ec1297ef63f6376a844074b3ec5b0520df743a85a8239d82f9a72ed6388a7b869c87f210478f4daca50939e2d463732fbc58d36dac4ba5e371f5c7941a6f925961f56a2b3c3a0c5d3c6d7d7964573435b5d3b15fb5805aac2f508e598ffdb33bc7d09cd8bf291dc52c217c82ea969036a517c723ed8182cea8c902cd7ffb8b3d7611c9ae500224c7e7cba10ceea11aed55e3641493c4598282eb96a0e00c3a4c9c90262b0a6e1b1f02250e04ea34a69bfcb65bdf49aa3fb4f58196f330a099731e30d01d32f62441651b019c0894a2157db531b86e2dc512eb3af1c4d97590e944326c9326722364a60e3ea9fd41724843782e2a861b06b927bd5f81257adfc576cd1b18c3ff05a26b35c85e83028cf92d0c38b30b23ce04e37c666a1cf9d93763b1649bedb8a7e5b1b3235536a19c51ffec5f7fbbe26d080334ee076a68f591664f108800cfde21ed3c76708ebbdb7f8c76300a2c3bb6bd7f3f94f36a1856c5ccd4fdb1d6ade76750a15b5246d2aaa8d5c20f828fa75c49db11c64fa90230909d640211c1d4a028e67b9bc12bb9be765114abcea1999f1348a50eded358df3866ec187d380ce8a395cee20300a12585d40c33126c5a1099414f2948b03557545002d4953144801b04d72f49e8186dd5af27e72b71d85014cb7400a28846f17f4dd7e1b516cc69b450342f8425b49b4b659d89762e637fdcd40352a36159886d28e920dff4bf9c84646afb1213fc7f7c78d7ce5ce9789157ef251d52b66958c90029f165abe30903106652bccfb8a3298a74fd81b567ec120469acbf6226eb0e3874891115302a0d946449cad8de9ec4714c97f23fc493586cd489a9780b7bc3637777ceedb5e83977bd7b32ee83028b4e96063b353f5e7690aa6843edfaa5221cd970b27cdca50cd535bbca7b367a1ab196ebd432804b058f83d4be357fb57f403023ff8d2dc7ca8766166b44bcec418d7a91e5134bf351de6110a43d4abbf0ccd9c59b84dc51532defef252b87c3b9e34c90b85f05ab34233123704aad1c442c40692e67e014fc41ec637813d2556c574a1b6d798af94d84511ce1630f6f680c31e3c37737f15cc1f2959097f169aa63ef04a110cf689b8746c6abce0aaefffaf83378ce6700931108d5c620cca16064cc81480641895e14d054a0076a855430f676affe0815453ed688b83a52744a1fd2e158f964f4cdc12e056a11d2aab52e0e5010a3d60b1924898f08bedd80121d2190494ff61008c44f7630c0a6acab3e1f23ba2eb14315c3b803d6fe02ecd609ab568b5e6bb9a887728dfe85e4a4f44ee32b4f8e74df3ee74f4e740ce41553f1be02c46d401d74603787a4c877ba460705ac98044f0b6bb1e46c9f6ff2c04d285867de31c3a86c0a3e80be12a228a0f5389b2396ae679b0154742f558f6c477505de9f4b4f52153c219942c076b8533b218c882f16c7b0e49c23e1700e6c2a5daea865ccd0b0e051642d49148831c1c2475257f8970db1f55776cfd9dc95cb0d925e327773dfb0ea0fdc61cd3e62304c8b4768814bb08ea63e52ae2d3ef97648dd224f01356b522a0296d8ec075bf7f6504cf2879c2d464af2a893843be09baa746db35982e60cf06dea25d30e6c7f2c2f645609899235e899a6029f87148232aba441fbadc1902beac337e88a659c4ca5c1d841f79221c5d8a2493b42a7c667876faf6bd4d551f2c06275d262db1ac9941c57ddda795932830eb6a58d8c54878bb16d4f35b07908c9944dff3593c310d01aa4261a054474f8ec75f33d504ada1d939bb6ae21c2476fc80c458dd2ea8743b7a72ecdf3a3214b12997431e17ef23096fd1a983e0175189c48f61eb335b104d80635352e5cae800af523c8250d4f846c26e04186c31dc1e052cda859a1c7137c3c3fbdd2ffc261ac526168fe373b1576fa7c0ceb5fe2a820f2d86a55c03671cd8ac9e1ce390c5612bff898e738f40456a850f93e5b9bbe0e9ec33cb0522a186a2468a6a23c4911659563c02ad6a8b59fb9ae618042920aa25a9f14166c02196b5fb8811b144b275b77683b2cf12b9d338a4f1c247f1b12f16496a9804ec182220a5a911e29257fb08894c4a65e12ee00db94f4be780208b64aa20416dd1d37f456651d3ce494a38e3e8e5e7f1e5bfd4874dc8e2705cb26cd20def0c13798013ac94056e604d781b2487a8786e13f1222100fed94a0506eabeea0ce080a484fcf370fb4b0a812db87e836ec0ec00ee36841630f10a81b8fb383d1a7588a5c93318335f9ada3057b66b035e11f36b4ffb3add14227db11e1458eb6b4d17955228c2df836a99c684644354754736a3b4bb2df092b95cc594687e7439ab5260a95f8aceb899c3441a1622a1e9eee609b5fc10a4932471b7ccb1b64c841c2324bb44c2bd802f6a8aacabd012f3fe91d0d32a02ded28c46a14c5a73a2118b828b25db5cbfe38d72d07c0653f33c54ab880ab26747bba3b8d98a02518542f2741785fb1a37327c33d1425054a295a86a4f93fd4c8b76180d0fc63668bfe8c34c52f3e4f232a25d0642be3cf62ad9edc12040fd0ffa904a7dccaccfef349e31ffc1f038383e3ffbe42b2175879907bed15ad76c8f662feb88a663b8894c3adc5cc2bf794e68ebfd0692eb5ee479f877d97cb0dd8f3520381170859a946f7535da5eaef45e286b17cd279bd23ff664f041da320c26100b25fa0eafdcfabdf38830884f29dfc41767f887bbcb4b14ad5a4165f29c06a28b810c650af2887d4c957dc766dab6ebb0890c68ad2686c666f691769d0c1d882baddadb14fa380c7c928f6d34084c3dc865954d1c8d134f40ba82c22160cb60bc37487e31ad9264dd00209844fc017ded4435e231024ef721acefa2353a69774ca5de6d8ba7e602342bf308f9c85ad97324423bae282c42a5f8286aa658a8cc1daf07ff63103cb11186e39c8e3392403b5022955d91e7b37f34bfe0a3f838253433760a13a2f38ac6d2a944d6f6fbd7c6f044dc4aef63a05138745cc712b07db79da40ce669067a9cdfedc47f1f8c09c29d6e5948445b25b0057eea8b01e8a6ad76b2991396fd0fac9c1ca5e904d883b361d0519286ba5f92009684f1c7e8a44fa98229609e02ee450f667a2f76489c25145237171d0f0bdaf78a1738385685f408a403fd37c8af086ddb08e1332490e2c06bb338c6d5516b4e13515cc787058ac70589dccd3cbfbcf57e761563c94f90dc7b8b1986995b8cf9ad07bf2457487dbbb55cc58cf5d72d3581e3277c860a7d27d3e61b1e7a3a5028d68ddbe951d062ccd8f5f57568b3d2f73b1dd903489e96aa7bdca1de6d9049a416bea60743f1fb12b7eb9125e6fdfd871f7e1f2ea85811dc523a8f5c6f50a13f8bea7481bda00734068f67a7375c5160e245600072b051a5b7cafd0e6a6b2462de1b1842bc890cd1f9bc8080ea0f8a6ed25c07b2808e3c71101c2cf57c8c903203c9b5ed03b572674793f61122a9872a250707d12954cbb0966438a7684ac789ba2e99ca4b58521574d90190815ec9f36e1901633b6992e175bd9505b1a98e513924a8e4cf2e5dd1ddbc89d5edc1673840f49e19f4053b440849c3e075c13c3cf608acb3cd3e3fc0efd5e66b274b6e0c55b9da1c998488906a4db2c8a1030e7af75ccaa557fb2de9c2453b43346621960fddfc11acdc5fd85835b8238db37eea2c0d6c7ce95df772e0c1d1a01caa4a26802fe2bbf91647f8830cc069a1d1483ea01e2f6f83540f168ca78bb1836fdc94d96269a0135dc748ea89f3c4a8232b2d498a0025e58d378bd2b8575cd7ca830a9d958d3991b4079a14ad93faaec8828c787a2fefbbdbb16c925e0bf17ac8152302af94bf711c34bf483fd88fe87873e9f5fb09ab25704807566c8ae80b187d8119ca42d02b0a946884534bbca8897b1d54358cdd301ebd1beae3c1a452d334e72086bef051e0433a0c85d6504c3dae0e031a918ee0ac10dffd6dab3f903ab7100cf70f41997c7b9ac03670f0d8c327f6172b7290b43569eff185090d35f926b1893d9b8b294f341e589c4d985af0884fc0782d4c82558d5dc3bfeeaba352f24eee7edda4a1717eccf396857672f154df328819b501c28faf33e342ee4859760fbf16c2c9b298b9f63c7e2b6b335310c38d974072f31a0504d372b9ffc1e672ceffeb49cdbde72eb455cad35383842d5e78190c6064eee1a7b22105b078a0431f64f2d1ae75724b32da7ae762f4c2fb172f86c3a511f85d427847f928c178e06f176dadeb3ec33bf25c3a498065249a70cd060f2e885e9ba1dc27d3ac1af197dad3ab279edbe2b46a39f4297a694cad66db4c6911baaf406ec3b3588dcf2ee2bbe8c2f84eea89b0a659c46a2594bdca58aabea1969e29882bb7c4b2b401de32f5e047b9c9bb0ddeb4e4ebc95b342569ca6790ed0f9a688a5471c46c7513b24a951ef6fe01ca8784d967a01acea5f79d4f2d56035cba70ba2656fe3c75cea1132569f3ccb04bac0e7ef48c195c6002914da7efe3a65db98b9b1941d9253b3263f234eb199d77415d2fc44f5007009b781fa1058cb882fa8294f423cab658a805b459023eae196acd49dcab43c1d96d68e45f224499ddc1daa2cb303eeb786da4cc6e804d0c1aa8aadd11da881b41da00fe9f750c2dc2a8c96c072394459d9afb9fe2db5b09cd1594ad6ae008abf13fd662927642be29771aa801c82ef654d62c00afbbd5de967e6ca9ee419c825a461270e5807a4c12b31b80af5bc9d7d4b9412c108c6a6fa18286d8ddbdf6b8a91c1fa662c8f5387452bade18b4bcd162915c295f8c0bba6af163066d9384bfa8c65025165d5cef60f51f5f7730e90f1a46407da5ea39fcf78ff67b6bc8386716445a69a15c7608070c7187f57e945622e636cecf6a4d1916f0eac7aed16e7caa1e6a98f08e90ad7bb4776a2ed25d1593f566a2190fc2658262a0b815453d7649aa7d8b34a0c884ed693bddd946148173ebcd66be6442671adbd456c2c1b68bbb0ee3d5d620be5d38ab3d2fac6928a930275ed1b60314d0420a526711bcb3848a846ca4d744bcbe7bad3dc5ae1a7df4539779058905e3158c3ac6906f4f62599b979932741851b1c68f387e096e541f99a8bf18aef7a85683cfc7b83acf5a798a2daa4b0c400e579c2bbcf569ac453f8ecca0ffc88c7c8bd3a723e4e98fc25fb8d7a5fe10be4c1224dede725d44ec13c993fa96b34a9605f6cbecc5653dc8143f7d3fcc03c1a83242ca71ccce5de83e463a7c51a2280402bf410a92389b0ec48332ab244ea38656648a6b1bd43424e1b820150299574166b8ae53ea58212362105d8e1528047909d4d9680d011048d0251040e759b87d7d5ec2f073afe2fc82037cadec538bd50efc127597d3f3ac43dfeade0256630c7bd612382eeb702306acff4ffa25cbb072062113414c2f4c9ef5626fb7068af1032d02afd007d1ee01d59d6fe1c4a7f4022ab466f5b84d172b30f94b3ff47ff2669efe5583573c1ab59afbfc1000d94337b9163e5d5908c8b14b943aaf7d8c505dfafdc1b93152f6059c012ecec83837b47bea694870f8ffad767f8ee3989222378be82f0218f3876bbe0ae836fe2cc4091dee109391acfaea46a61c05d96535935e5bc35beca27a5788a5f588b522ec952d4e072293273fe171e1d9150a5a8ea8ed5aa4504f08a75f265d90c8812d5b721ae9aa7a597902b496909ab3c9dc3cd27db55adc269717cab23e2bc610bba8011b59ca3ed8a99c94117dc8dbecd3d28953eee33a5efc06bbd419ccd53fe90d0ee09ab0051e7beae39096815dbc45d72c4fd216a2928b0a49158c461ce47278da81e011571d85db24fc25f155b45b384318982913ddcccd0218c39d54a1eef171183b50fb0c477962e72a0adeaeed186f4530fdb2fe22160db0079d802b4d835111463ac3957ff16a8943985876fe036058661227a9fad69440e5f11fd47bff44dc80a7f132201adee560bf923fb86f13736d144f3d2d3dd5e9915520a0dfd453da2c51ff0d13132624f03a743526554bd0bdedd48258f29d904a262b66275141a0ff03ca3c5674dd90477a5b291796e953e9448815b7b40f122b3323932db73e908b2ff0ef2679655b85fc43e5cd69c0551b8ae29f851a8450e86fa42b7ec4ec9783cc91f850d15cf18d5b5421899d12699dfa7be6156adf6b1769738ac3b6a8bd6b4f7100a6ec9280462290b48472cba3e6189136d7d62afc15e92f5a9ffdea41578f7724e80eb891863a4a45cb1a37b53aeb518c6017cda9b546bc585dad01d34dd0b931d90bfecdbf5001dd276ae1afbe6d1bf519e1195f28088e25c7d37dcbfef778050154ee2cd3c5d828e9803fc4d6bc0e7fae79231141796f43b25c86e0b027cec4952844faf0b009d77428c34bc40c683c443428421bff4fb0a349293e4b8375c418ec42daa850ff11e3c5b8fab05583c9edd53dfa2a7087651bfcf675f4a2dfc422b23870fba6c694509f3d6baef5b7e7d2ee1f18fb2417494da50fb34e5ce329e340b391588004488259ea5ae6f89c07b9279d281348ff8c4ca112c608165043aa1e18f0a7b932be77668f7ebb922634f242950221845a90e1d5f690046887b717b62ea43f5d1008052917888007e68739f4ab47e2671e2003e7d90f1587d7987a6f9c064475044fafbae7965119e7131767bc59988b709052b0f62fd4ad4ff76a7b5c2e1041b789afb9c116f7033cb36f9257ced4ce4e2181f9ac48eadaa694b5a5f6e8c2e3a05a7370bfc644a65d4c49138cd8c9492b0c0a067d516c79773410faa7354bc15d24acf1a18f4e59f07730ced5bfa8b1f58715e29fdac20b341af796a5f64d08ed527ccb640084af3e397d49011448cff960dd5be91910c12dee1f6493607827a69dbcc206d4c9d9297e633b813c6ba032349e99d84948b32fec41ccf20be0d5c0be56c7bdff859c252803027f4f18db81c52b60a0f1693fbe9e23efcf0dbdddcefb8cc405ce7b2880695e905302f726d8e91e94b711e2eddefad0605a9f146620e46a20d52285161f307829c5390146f04bd3572228b33a01b9f2b3879176eed2d9c58d5c470704e8e4864a3b08fc7a8f60e1348414140549123c3133e3467df0d558bcf5a5b884b16ee71e6f693bf60425e726d4819764deff9b628a63d05b0341e7fbc22a64d82ee98db6ecbd085302a8b1d63443a2c290246938eea2cd54dae5f219eb614901043f7db806e87eaf26c102e0c3d90ef73ab192edeef6b14dd0705bff5fa8ddc577321f816450c9a07aac3b2675b81af878e640bd4968817e8f82b25d8486c671feceeaced885322550de0dfa801fcc727cceac73d296dec602ddfab1ac15ff5a75687408fe710286e88ed27f82ebc90bb9fc0fd2b0b3e992f11b1a6665e4d05497a6e539e25b3ab670ac5ec317b0e2da203124120f960c434ffa87321fff19a2d24e76ea75a5ed2e4de819f450d5d3a481897be35d492b1f0549b55128a237814a29cf3c851c0630562d111e424b65f6a6982825665d147a91ce45531aa11e49b77c3a0c579b27da9fc7ff0ddd9620cbef56028eed380e5250a1864f89bbb07376590a2e0bd9e6453ce00b6d42b7ac2e66e115bafbb5cfbb0511374aa1a9a127ff23039ed60d89195cb05eaa44695e19de40378f97d5383bb9920cac9b1ce0f181ce5ef5d49d14024aeb8c56c54db593f3ba0b86751b56dab1784d83eea7bc660a2f0c939f6bd4733b1bb86b8c7f3cc836437e5424f01cd370416255c88cb2ed46f5de8f44747e04d94f2a077be2a321d0388d1a7da44c957f45d35862de7c48f1569e8358ff313b0b42b27fd7f62a9cd09d92603cc924f740c58518fb3f4f9aefc8ee1bd83019ebd4d46429dae9474b528c2c94aecd8943ada6fe5342a98eeab23a0d459c0a47c3181b905be8973a445c77c2b386028a42131d2002b2457999278ec182482a52b72b4e9658a31e7b82274872f4c238afa2a4c66c6528389c0077878dc396e10c9c5e61c835dbd7e739c0090e5c9274bff7d7f5260fe27c53b3986552d64868bfd96a5af039c5378186d0d4ae86553101225b9c38e630d53ba30ac6b5215d5ef8e459a0b53723c3cedc8d290719f4b1ae10b7c8b9dac72472cb1b129702a0d1418e31cb103241177e11438ab9e6d061078db1abcc045f4cd51c0d1040ad3533fb7899edf6ae20afc160e8254188221c937b6166ed94f1e796f3620801e6bde8abac57516efaaa0495c4440bfcc81b05e4040e5158ea95bcbeb90dacca9b4c9b2ca7899fb40d67f09750acb8b39c7ae2ecc0daa3a78e89fac4fff9d779ee538a3a6f03da04193153e904a41e838c03cd60299d3d9a297fe1baa03dccc4ea5d6c3a9f6c395be653056623183ede22e3755a1c3a706c9966e3b3ea596246c6c3fd4f35f0f343a2480697aa3f6f0b79ae605370754ae28a850dccb359c36970cea0d8ef2fccff5d29986c6f8095fc68d055c3e8fcc517a4d6c0a46962e8b0a9d47525ea951e4b10a4ad7d269a5b22c0af7fc037a1d968448b71c5b77671e32872317606618f380b3a7a3f8c2cfb3ee7848e354a09750adee11a23703c138567fce766a74879a716cc520302532c4e60882d04a02bc3336c03868c97dcccf6a9c915859894b6b5083b8d1465625b074a92d27a89c8c9a5e4cc0d4fdfd23f8c6a6b2be8fc08c6148711db919d7bd0d20294214f4f93f826e7f06bf5ec209ba134afa25e0a8fc5067bba35f31b3e31057a2dabf0c47d43b1157d6a13339d40059df9bbfba48c61be010674183918d6433f7a7389cef21984aef684a90724791b0c4d9a4f0d428fc3a753920971adc5188885113c689388be4d315fc4c55182407cecd3446264a5499efc17e542e18526091cc9e792497f932cb8f6f664ee3bf81ae2b0c6d44d58201d0d099db14ee2506207fe0c4900dce8a01323b29877cf862e92b408cbc1b3d91430fd28470c26c70e59309c1e9f90d5ce5aba7c061db49b208440e6a02b7b1edb523d573cf6175c039b85964db9ea2e0eebd74bbd9089c9d926a5b1538c31fb998c4ef318147d4b7c4ef482bfa97c1e46c5bd7e4a41d866719a7e10ddfd0defe6f14bbfa62cc69e0f001e008e952cb00aaef3a87f3ab2526a135dad3e8b583b606c800e4ee6b63c5e0c1e616cbf834a86331259dab09a589d23c7438f4762e4b2d2045d087443cfa8a84038a9afbd80550f2fcce72a8d446a4e77101854284ba997411675440c623daa1ce27e9f04fda0eaf0fcd28c724685bf7cda67ad8ededa7fb3ae6c5d610a1e7cd488f8d06f06786d2d5dbd44a48f95b75f9d8472035f0d94b00f5f2674720eaf1719f406ae0638f80eae1ef0e81d4e1d3be02938169ee0bc01f411d3b4d3e950388b97f3c40c23902f057b880659d4c625481d5cb07928cc7e3d1ac713b608f404046e405e805b5bbc0f5f0b18fc024c36681a30376083432d50c71318755bd2521678cf60f4d04c21bf4a90c78e4772c2bf469b4e7965f3b2278e3a5f1f2729ba5d6124e69ecd94b512bfd8428463774d4e21b8e19002283ce5db0e79abce1ba8c07204e4a3c8f04092f759f89897e2bdf8622d95ea993137d0c0fbae868761edcc4d546ab7b48b32104da4a50db0c6b5bbc7107a1b616d256c30065bca850d8b608b16de93507c2ee1e82a180d38fee36606cf68b93ea0ffee6cb542ab0de5d5608363bcbc6857ec7ac09a40ed2db34b1b4752cd33afa1535e80862266afdf26a2da00096f3fc2099d2bb570e80b3e79e05f7d9970c1836d8526a18b42ee62744dc9fa6f3af91c8400f156018302f669d851cc1ff5f6adae0178a2d60882c4fe1989e2052a640df69820ef6cf9c7be0f8eef67409d9f31c8e262b4defa187394ee99095681594e95b04e823d208f85cbe3d6ca5e887a3e14a4cb7ad7ca4a70a5c972e6e0f8bf7630a76c41fca7c7bd0f57a8fe8d08016b16a06538229bb8fd7b4d33911761e2a1624454297d5cf2b8f6ab617f06c12b98f67b8a045f338a9f419b08b0045a52ff426b2c4edf32a2359397d5e1a99e53b48f84272132d93ff30785c457a850a480b6bd0d32fc3cb085746533f27d9b2c49d8689a6e624ed7b7d115f3dfbb444da13ebad3770024d1e0816d2bd0d3d0b18b76676b940bd95a8e3aec1c8f752c59b53efc317cc41394e0ebbb3126d5ecadacb4eb6e308841669485d988ec8c5a357ad8eaaff722a0e5af468b911568f0dc982f0540b34464778472998a09c12a80b4318261a445f66e0ea23a16dceac3fac37efed2919f91aae0d5f07f664085f5f635a875e115140daa1a6191c7c8b35eb88ced2cc3e717d1865831dc93b3fe796d55686cc2cd1ade82f8281269cbc0d1254146fe2e14160de452d7c155de910a1c9d96026fe0060ef3b4dba77520ab59b0422eabfc3ed1fe740647e87e6e0417321c303fb1886d664a4405d1c260ff29255b26824a51003610d68e00ea96670982aa22e00ffc45b76f39d390f2a800802c21caa1b36c52202bed826b3e23c7be8c8ff042557140fb73d82a6bedf06f54531c08a38e804e88e45cf01237a45c3f10ae991318880ea458fc2cccae5fc669b0f3c3dfed5e79c8878a14efffce582afb2c488828db70c3c2729930a11e5f93b0e4d459b7f7bb07cf16cb93830e0df89776b803973253597ea3058f2a56443bfbb15dcd50807004acd4a1579a372e459f91efde0c4d1b482bed8f29874aab0289ff84590a295851c2a18adc25e3e0743d2b6ae59f640313f4b9e59f618fc59e30f50cf8f00dc20ecbf04a0eb929c1f88cd82005e440bd43d058174c501f100c9261b294016dc51c78b14fefd421663559b772b10db32762866b9a0143eec5e055197299e22310cb45269f68c360b22b368d2733ed1a173b4f17e8fd02d7b15565462219f90a4dba08a399631fb60bb60af58415122877ed199e8511803095f79f3a8433d33b917d2e981628530ef90e96ca2e0c893a6a07776a7a163bb6e652e8c6a42cdce43cdbb5c70b813b9891e8ae315c35d2895388af0fdfb222a3c6359eaa513faa2aac7d5b29f356bcef85288d5181db0a79addd81ba8f96da9d6af59f8c5b33fa2d9f3232f72ac7084f27b09919ddab3332cecfdebd5d662deb4a03d8aa8f4a60073b453365b597516d3bdc10319516bf27d32db215aa3024fa403e1169035fc447372ec1b57ba4687c096ea7954675c95d404d840802f371f2476f02f408373ed6374087c3b047afa36945930d81da5034547c684f9f8c9a995816ad70acc8e3535e10ce19418ca874817958fe5439a81c23941b0bac81edc3c41004378ace987a3c882ab29d84854f50f7cc10e8b21ff926be2d0e1ac91922f09d97bef2da59452a62403fd08bf083e0942f5796b4aa9a3a0aa619cdb6730817dcdd5e8cd04d819655f69b1cc50d3d12e999999995f9741e95146869e9a42faa6b3724ee965c846ce912adec78f240f1d48dfd087453f4c810ffd0253425884a4e8a19f8a8038f2b0050fbd7828a5941a0ff8c5cb47242525451c78c1b3c3238c686490007ba82c16d2c4e7b86eeef9a1f3cc7ec824b0d1a2b25818c58a3be83f41df057d0ff4ea19a53473be42acf4c1db0149cb298d157b7c0456282dac3adca8d77e53de6a7bcb4ba51b9d7fa9f4b260532517fa1296b85aad56ab8db48aee249fed872d4f94565e481fc373e1421763b55aad562bba4455926535bc86892712a9e42698cd0af952c6c5904c8b9d56cb2bd25243ddf1e18668f169e88e4269d1526bd09a36611b9dbfe590643592935657b029920b3d09d28cce6975e0e54f372c10ce3aebac33e6a1d74a9da7c79f37355de8833407fd358cbcf6f4702410840b8daec651103e98670326de23730a4a62d45149b132bb1d9df3b2badb7f6e44a2bcb656081f8e4fabda6b68553bcde9bdac7b44a8ee11559de65452a4172cb33cbe1b514bac75fa841b65e9cc506af17072ec484426554a58aeb93e410821bc9913327b5914831e8439e3789f37309438a27b6064b6566be7ed9c571e04e204b1aabd7dca66261be01a4a58e6a199042a44577490e5dd892327b07cd7400c8b9d11e958dd2c17fa09bd6640e817407d66272fba8c7998a3841a4f046abe3ce88dff3e6b9cb8c3be447dbfdcdab28ef17616152c3f8c31529e71a2feda7ae20dd067dde253598e6d96cf5bce7e693d4a5f2bd37917106fb4f103597c744c76b7a7a6437f0b98b72b111f7d0742df5070f9f7ef79a45e7331361120eaefd91499de1eb505f4023aa122f1dfb349554fd50b7fe8d71f7a43aa07f7313e1b762ec22e30ea554afa66f49fd24ba93d3365962d2bb367feac140f6f920252faf285741f54617e6d9957f80566bc617c7fb8b32d7ee5c16e6caeb7023cb1506a8c6dffca50d5d210a5946e693c11be94f5651871ea34d478a263de96f3d6454a2fcd2d8f9dcd1a1fe348f1805fbe5c995f9bb6fdd02f5fbe78817fb9757fe0e49fdad64f3dbb71ab0580627fe0972f3236fbe53af0a20ec4d5ba66166bd77f718dcbd975e0253de82394fd71da59840f1b45d8b071d448a03c196ffc2ae34d1173bca91fd11f144c856e7fdb11b69b237c45df7dab15b1ba65d552146c97653f4f9cbddb3ec51827f4d9ed32468ed10a4692e82c573a4b67814882f09d9a29b7131664c53fefad834fbebdd403eb039f30a31e73845d7c1ff8e4dfadcd3d8ecd59645d2041940489d8d11f12578c20e20829da0f47645220a1354142fb0012d916249080c40db6c876a05119ca62a0313982e3e4c1326a57c8a6d09ac8a2683e1cf194f090d0c11e4fb1ef0a9196a75e136f268f866d7d60a3f74ca7de4d7fb0a3b76ba75edf6aa75e2f4fe7d9dd2abf60422fc617e1f50163eeee6e5ec1ca0111a8077befbd07018c1b729378a8ba75701f9f0d8c35efd6486e12741db0b3c4a07fcfa66f4a47fd1795603f9a7b523e8a153cfd5bbc0ebb9d4fcd476fc8fdded6cecc5770ef31f30f00706e61f3f8298f2f6bcc3d799cd1d237345ca4bc1d1ec9fa0675234386c77077bfde420bbe05c06bdcdbb91258196f2dcdc978c959c64f2edc4ee85d70669f310306ef0c1083773370dea954de6d3278274300dec1bcf4ce7ae91dff0ca7719b8d9f8a9dc6f69e4a00db7b2a1936d5c6ce6dec31ccf0ec3018e01900ae035dd85298c6430380ebf3a0f85467291de9c0874fdd2c5e66e83b3954b3ad9a93f117361bcdc9f8dd5e0bdb824be7165af0a7f1b470659c06d59c8c6301d890c0e041f80e0c170afb0ce9db866d3436796b666c9dfd19b505efb294bb70bb191ec3e5d6d997a9540b72866fee33626c1728bebbbb8ceb340d8d4be9820b00f016dc85db4947f98c198e518da785ac851900d85e73333ce53a906693e11a0f2a757db297e1dbc523e3d6e6645ca7515bfc185b7c1927aa452fd3462fb3e4655cc6498df2327208e2e43c958c6fc931c1c8b8354215591997b1642c4bc62d193faa32323e232323e358142743d99297f14c46e6c9c8b8e63297fd24a19bb64e1a75b2e8dbedd675cd25a7a57373a95a8f3e7a0244ac475e689b04a753830da7676424436626064a062ac68c4cd12926276b0213292875ec226dc75f2bdd24fcda32a3dbfbf9b219f615f57a171191471f8016db5d445c89a8544964f99cd5512d90439767ded5e85209c4215de9644a4b27d809c7e475757a5ad0474679c844ff5e805ab087634f1e5fbb9dd06b4ea36f601c2563462ae6c6e4a692474f6d59abaccf38bd7519d651d63ae969799277f1491e838467cf8640a97876ec49412f67b76c9ebd3e95939c743be9321ee3c667c3aed327d781339bc9652779624ed7e7697993f5a8f5b0b868328337c5986e6dce3a8de6acbdd24b1bc9bb9ca78e6d1df7d4afad433dedfcc84c4b1f38dbd367cf167b5c37d796b3bf9bd7c23a0f6b3e843ee3d2f69ae3b62eabb177e3c2489abdb28ddf92f835ec2a61d7bb61bd1b98979ce45956d27cdc7f6c7bcfe67a36efd9c4674d07f509afcf496a3d0a90022b4d88b34ab527c8d90e2bb71f1a1c6763390dfca97936b239f8ae3871f472fbc1696ba3d9fcd9407fda47a6415198e9964ba7cede4deaf2889746fcd3a22ce225933a37b982972af8a7d149c47220f83cd6fda1df63b9757ba4d78faed3ed95d61def7fe84f2f027fde4ede1f5a047e6c82bbbbb9f987e415c25ed9c8e91bcde496c7daadbe544f3233ceb72b79e631b6994d663b6db0e424d78176d3bc94ad5e8b9a966382d9607da059faf2e54ba963153472cfbda4f9abe6af9babb3d985ddae66a3691553bfdcb228cd5e0b8b39f5ebc23094f4d82e7db86fce1a36e98e35c3312f1bb90650fc8e09f8f2e50b13ee1bd8d95f379e7db4a21579607703e1fc9039c63527395b1ebd6e1206c6544dd5544d25af1acc46728d47bb40f249ce91f86324bbccf9aef107ecd5607276cd01a9b4deb368d8c0b8c9352f79392eec866a95086c6cee543fb38f68c8b32ce368798c7d332fd9cd39abdff74fb0d3dfd3f8f159a33df8cb972ff302750ff8e5cb972fcfcd0ea37840c910d40794f8a08682704218713f561e88e2b3d279444740530315d83c40e4b3054521b6349010ffc011200cd8e0890fedc5c4352cd73082651c3c836bc0d40f8de21a46404d417483005abdb6016660a3773927ea1dd7a0d2a4fa93b789e53911858238f54604548fb709757a1f070c30039885d481935ccc497eeec06e6daed6e99c246b6d3a8661da8dccbb6a7186553ae5914a2754c2b5e5638c2d5cbe9f8d31468731cbf30a0b35834a1a4b63e9eeee2eea1b2b7d63d438995fde58fa86faf5ae6b4b27ad601b79c09f91a8b9588ae2e3163f8629f6cae2d27271d11797e6daa378e99db4a232517d5d52dba5588e617435e54227ad74cb4adbed12ab3f2efa65593ca365bd28aabb3fce728b5df459425cfe001e0d65590362f4ccfee8f272abe918391eead2de84cd9aeb25b161107238dc5e15ba44ef72ecd0379d8def14bc3afa618ec611400d7d0324160db5f0ed43da7da08c3672a2ef881f69340e8d74d286044af4207c8794016dfca48da6699036648cb78b3c7c65fa1a6f426a0f0f63760305b8c6c62e3709708de850e341011e846035cbfcd4374dac6b1ead6b9dd2d19f3966a9d7e68624ea762bd5a8dd505f3a16372112dbf8ad90846a18f6f8415275afe16ce4b48a63246daf6d5c1bcd71ffe56273bc0d2c7b737d03c4cf239dfdf22262d0217307d498e6c5c60f352d73ebaa1702750f186f84551ae53b7c04fad804f38e878f414de2ed284a0a281ace628c357fa1681090c45fdfd44600e6966571adea586fcd127bb2343621e396f117efe596e672905e6fb7c390197e805b8a077773c617f6f2a7a530a9c925720bf64586a5bebc7609167b798729b15867bf3b3b041fbb9d4d812cfacbda28133e2c318f9befc656885392291eb083ce4fbe5cb26428c9f717167a433b0455ed244a0a1ed796c9b74f991faf5df26cb058e81da6a4d6c01955b04c842921cadaaaa9234427666666ceb6e474998590e89fc6d3a1ee95d2fde5a539723e2ca58ed41d9a6b471dc9257c80e46bfe5d99228c6fe9c3bf16f6c15ee2d9408f81c2769d648987de496a65e9bdc9e0a1d3d9f27d213ae2e8231791514983514925cbcca45aabf5c62eeb9636d24629bd8f560a19661a51b6590821845c7b4a1a548f1cb7ea1bcbe2da391addd9b081f1f06ef40ff169ae25d09c909ad5e785394873ed4127eb9de5c17a598a9e9de32cab524a8f2ff49c03e55637941335a09afbc1421bd8cec6f79c2ef472c3a4dc80faad4b00ae31bd4a1b7d13bde1edb8671d44efba28bc3c879f1ccfa6739aeb326d6ace1e69645b0e3f369e4daf9e8d2635b9a53429a5a65dce51c6dbfd50abfa8bdcc52cc777e63a9dc373e817ec2e07e8977643fc1ff831db72b04723879ff76cbad55f573a7bc749785d324a282fbeaa3b945e044222115e69dd538c1003414683136cc77134563672fab2e617970eabee4e2f662754664fb5ae5eeab0fa1e78aa1008588cb7db210ef9c8458c11c63829d163357314c42979ada7a7b29cb4595ae6f3b45ab5af9642440b63a42fce5a835ea4300b139bd745ba48f572d29d53d2984d76926b7cb9d571ab9945d1780d95934e3a9fe4d3147c3adcb28661587ee622f6895407b2ffb846e75c699a9b7ef96535e328629f083b907dc6c1d6b906ece7bb6ac2428ff4665a5e673f70e2e8b108618c50b0fcb3a5e487e506fa03aa007b8e392677488d873a73c7eeb37a1d56dfc377c673617d564fbdf359753e2b4d87e5d5a7c643a3ebd093f90aae36193016e6312698cdedc6d7f61e3b7a391ddbaa55371d56f7915a7680e8eb4b4b6ba55ffc98ab269675638c3027ae886a0d1b581f70562259524622c1c06cf10ab17ac966505e1ba5902b8c85c9604830303a6d49b262985f8eb1b5d67748b72ef45989b475414ff24cf3a791849e4400ae8179b675f1a95f9be575eb3ca32d68a287b05a3c58165d3d8107585f16dab59487ee99a50ea471503fe66c2972c651eddb519a838e4a8165ef38890e6c1fb0d2ab610e8a5dd0f700f5cf1ba2cfdb6540e83d6a3c56fc193b20cafe9e2d8c728a87e51763f4b230aaf1483adff4d9d5140fa7737af5e9357ae481291e74def9d99cb2aaf9abdaa08fb7bbba60714ac09a2ebc352deeb3cca2b62a6994452de7e8c2d72b73102950432f62d574489f5a4d73d375203bb3ad692e42d85c3efa23428d291f6c67b35aad45a12037e31776d5c51a57cf393f3a6badb5f04a1963ca879a835ac2dba21f2bbd2eed3eafcb84475d6d2d93b7c4eaedf2c7852f5fbc3cec8480f8db75785dbe87bd99c05e0dd6d1896dd4314d5a3e2d9fcd1abcbaad63886d9977d4b11ff83d1cd92fbf2a46338a39bd1d86f9d3608feb7b4ccba7c7ad56fab3359ecac3833a9d92533d64cf7b8c66ac65f4098fdba9037a085752c86c0c29b0d43b145bb57a76c5eb29b02ba82443c9c076281d1416a7ead9d78ac262e660e66b838f1a749a6b14caddb2965b164a06965d566ef6a47848ff796b220d16b6a390c56ad4d9a30ae1500c22389252a2a1a9a1f1c2b22a952bb8e87611a1943a466f4ff5fa985b971781179bae557f5fb942cb67bddd35a9452dafd5b24818e64fab96e5438858d78665d1c8216d397a6658d1e0bc5a955e6e6d296a5d56b5e8e5f5a957cbbadd4569dcf2ecf2eaecd4a5771c766d29ac5e7cd58d48add5af7a5d8af9758bc0bf3cd60c0a4b3bcb7f28e74560e4a744b07b5d3e9d2757873e6fac570eecd76a6fc76feb5bd73a56ae103206b759d9eb57be3fb408dc615da7df6b1bc786710853852cc72684d077c820933f8d859e09c035aa471d685d9d2615f9a1cf45a4ffd0677fda02a417818f751a80d9ed32874e37223ff4996fc72387b0438716eca90f7d08913ef2f2fec05bd31ca4e17ab667d33ee4c7699a6be8ae79878a3225194e1821116676c9b7677a7d49dd23f422f0e1ede6fda14f5dde22d0c6275d4b31ca73bd4ec3dad28a168451b243787facb7a6eb34dc76bc2f12ef0ffd2943126c874211f9e1e17b80a006e0d7dbfdd08fceac82c2c270b6040a9d32d56175fc984fcdf3750cdb7aaa63ce5b4f75ed1549766deb01e2d7e13d633e25bf0e352f6f4ff41a312d11db3a9882febe487f0f8d57cdeacd63ac49c75c46b7960ad6cd914ac5606b3c50c6ac9f7f78b6cfd36fd2e1b3f979cf46e2bc1dced8e1f6b408c563855a58fe1adbcc9b21fab59db84674de6c735cddba19cf9b3517a1e0d8f524630486710c4f2c5fe6582a4d1aaa45c6596336743a1d597a3a1dd59a552734345a3219b0b09dd5faaa2858f6ec3ddb6d512e0316258e2d4c9b981942860c19be7eec3a6884209fba48e24a144faca755cde9d6fe4ad0b148b7ce8544b8c1941c6e422f61aefb2209487299571baa5dee9e5a547a0c94783896df9a459f37c557a7b9e8d0239016ec3302d23fba1a9ad38dd7baab8f37d6396b626c08022927a8b951e2db08aa623be11a918bef4e158993f8432bf13136e6c403b1f33fad0765243df0c6479e4df40144b1535ad15ede0d69d437d487b82ce142096791650a22fc1d1fe13e0271da49aba2d76a6d971e4e965def6278e78277317cacf111e76302a6f8a85deb4646ecd788eb6285a33f8e11883e5dc0bc4d8c641e04f320287cb3ec0e7976acfaa07e5eac5a61606fa7877c797b8816c1b3893e274e012ccd47b7e1e4638e5682e4bb2cbb4214dff947205c61ca2a674a16c192bee93a09117d53b97cfce2bb2cf24e9dbf23be14c1bb31ffc80094b0fcb41d306681ddd0a303fc540fe8d429e7f0f39e8df5e2d6e510270f6c1af434d2ed073e9d597339fcbc3ab7947c9b54c1a9b90729ece853ca22f0630a30fed9b996b90d82ed5e91aa06d6068853d32ae815c29aa21ae883005aec8f6b4373d04d4358e87d83c16bba35776077ce1b9dad6da30f422b9e7cb9334ca619f674727b5f73333144a03cc665646464c89021c385de64f299ad8b3fe328940c136a8b11e3a84de86d8c189f89711d28b3c5e64c7edaec6b6e46fc8e864c4b1860af26c6c75df1317a8fa78a0edd06888301208ea870faee6db18e8187935d56c2aae8288d54a9a0d987858ff7b10a888f9f3961fdb508d1816f0392cf2b7838ab167c9c45eaa3e0b112c681793cc147e712bc1bd2a30fe3c02ebf1ad3651c009ee28d3b4459e9ab8f9bfe6a3f479102f66afcd17808a5b4621882ec7c6ade3a8c4b0f027164b8742110e7edc0780c970ed437302e3dc6b6b221c3b4cdb8c9636262502894ef788f72a18fb175fe31fcb475f54f3eb375fc336eb74e480cb76636197f9a157abb01c997b9f009ae61f2d3d6359637b90e8cd96273f6bee6ecd56919dbdbb15ef2d738a4cc3accad34dedee94cdab4ad43bd3f76d5534b20b05713a4566b51287fafe39e049a73769415de3b95005caac02008597ad004086014edb4e7401ca10e1c018a2caa18b184125eb0d3ee5c4d3341cac69ca9a0d8e75837cce08b3188216c62a5c84912488e1c65a1326509115cb6185979e22409922c8e88a83059d2858b156f4583bbeb1b5979e22422c9e288880a5cd2858b1546519ef4105ab2888268c84b978745f5c4f2553d813658c2082324ad9ecd738ce7834d250907d8f4e773e36f4c05c54ec7981bc298e2218336cce7567bc29f30e218c235e6543db130559c2089b75f7dda308471d4700d681fc7d6308e1ab84687aaff1836a9ebe1edf5296d3b3407bb06c6f15e8dcba1a5dd78bbdaf50760898dfe44f87d523dec53b77fa9c01ee3b01e4ef5e79771487fa8ad4215df788da7e2b877a39f0d7be737f0318ee6faa6e34fbf2ffd710dea5c4347cf6a88150d1a1cb75ac9a39a2c3b9d6868dc6b6a7080bd392dbe18e38c352671892be7782f8ad98d04f66aba6af4d1bd183d01fe5df152c4b7f6c3a3b90e7ed2d37e78aaebe0bf5ca7a193beb1cef5cd29c65f6518933fbf00c5f2ac3764f3706aa5c11ec08914ffea141f291688538fb42a7aad16552996228853ab51c5528fa2d728760a4704a3d76a2d0ae5ce71ab15e7c42410c706a88a5e95402dd1eb96e8b00846471d25c1f29c043a81388cea121d2681385c0455d1ab12bba46e19d2d19f6345ada56ffaa37751e358446dc4c5ca4730a210c330e2bb3e02c38aef2d1fbb4441f45d7b39f2d1ea97bea35f46242c8c631e9d37a2d89b9c424552692e66692c6d853dfeb415cfa68d78fbd9da4a7346b4ad741185b1dcaf8efa84263649c93065c2e466391129a5cb48192aed2c738bc0afaed35b173981384d459d420b14129c7cec217ca4599a8b1abc140b018840b17c74194c626e5b793671c84ad3963d9b1943567a578fc8279b4ca23929abac9249cca25531c6245a153d72918b9c951863f42755ca472a1f9d93409c88c409c48159c0285a159d098a888f71082f282e960b2756aa1545593e72d15bd9daa8ad889e6d9da58f9a8b1049ab3a8b5645a7322465c993249c098a080b87d0f2ed0505a3c31bbb28c608dbc9291f637c37ea7b471d2dcbf21dd2ad2bf4fc1d14e4b9b72af4f052806bd098f7c56ae21957d0a8e1381a3466f3518ea7313bd1d4d0b091936323478ed99da469688a48203967cc4e994663b5aaa139d9b0218566333c32990b58f9638cb05d73692e9ad55d9a6b7fffbcbd34d7f53617194c6c577ba64b49855f97ec2dcbe22e976318b317e612c5bef72e759f2c760eb05743dda6188079e611b85c738c31ecc23c73202ed75c28c58092678e038c6b9e6240c9330702e39abf177aa8ede02f37956200c9330762720d0892670e84a9244b37c538986e4a9248971b29992eace418c92f17da79da0f24a51b040548c949de8067c49226e9bea6c172cd332d03e2cd3be940b1dba1b9e740d1da7ab85573cf6b4b2add5c8402a47a57ac6e1d05aa6275d25c8c31d6682533338cd397b783891e63ecd8b18da090c39d9333856433179aa727a9038d0f8d90645662ce9a1a771d381f2e0ac9662bd89c6c5d580e49cbb0ab557d3beaf31699dff39c1d687ef7408564b710446e04f3624ece0981aa76e729cd71ed766327f6fd3fc83c44801c58a672594a732614d88ea57cc79864eef57945884d037c36cfe60d400a22fc3f300b475e7a10be4343f5f7b67ae9111dfca91edfc39107aa4220a92d07e87b457d0262ed2a454f5b8a5e8f71ea3137080a8de1311e3483c16c296a32b98c0331b98c5397b94150e80502e3a81b14c3613c6806e3cc870c4551193486d34b46a373f35183146bc3db92a22eb433b3a5e8250264c6631069c0dbf9297903de8e8cc7b841414f68670758a9bf1dd47d3b311ccedcd48f189e2add06bc1dde99f1140ebc33e3bc3373573b32423b257fcdfd8037d6d037959a766ae89bfad06d80ee0336b1b72f23cc245bc74af8518b52f7a9378bdb12eaab91313ad6ddb7ebad5b75abd748248be29e0455a4db699e79cc666acd310dbbbaafcbaa4daf0e94f2631bbdc70ed47a650ffe534b1abad8eef4d1461aba58769d3e62c1a3a149fdc035dae376718d769411023530300c352714f3ed1ef702db57078421861862e048ea843ed290a1656049ea8462e0349ec88cca2c43b8951e13db570601d0b0190053ce33199890a25101a00595ca0557a954db0c954a860a156346a56a620bc78f31ca88a37144c95b68a68a82854f37aec90b494c8e9571c87fde44ec4209fce7278bc92b440a734bec1efa1907f36b028e10b600e34c9f976fd8bbb7c5f7ed1a9ab08ce341a38b05d873ac998b8c119eb032587a4a73dcc34fa2ce57b4bbbb6f0f907cc83dcf6146fdcd1bdb653fa87adf545a8a14db7114b62b884ad2423c1c16e2a9d895a084273c610a5388b2e224cf86ad7d37780630cf07cffeee0e9ebddb9e71c0bc847d4c3484b5bf1a1fa727d8e7f4dde09ecdc361176678ceeffd8c12214c89e1d9b0c75461bb181e7c0d638c8e9e61dbc0ee83fd07109c3c07a9918175e13d08c47126aea0e2045700a1e58b1df6defa87281818bb8fe6deeba8b6b0a729a72eac9ba0609f5779cecfddebd2bd29d827b043e0e1d4a76a97374ee1be2276c263f9eec524c176f5ebbb5161647c63d1a1b8e8d98485c7b3692560f1dd41b0a10afbde632b3e30c45bfee1988ef08084955c098214a3243a030215df71122044f96628a46fbad2bb62a4e5db815c11c291ef9a85ef5e16a526d65710c7e67811a4c2446fcbe39824d81f46b0df6bce798becc24a51152060520508963c7b169f5f10ecd574f0e8290c2fb0a59b9a5ebad64b8e83f5d235624473eb38c090ae4395a6f90aaa34d7dc86bee9c9fc72cdeb90cf1edfe95bcff4cb2fc7665f28ede775350b92dce438941c5e23d04b6eba46a01889912102fb9abbdc74991c8776ef8b83c9dbfb1a816e4a5ddea61b04e5ea1b044d759a4ca67bc11be34048de315ef2989bba4cd331b8913c662bb9a92174230e7f3ca326ac1c5e1d931b3102af1093778f68cb0eef080a7d4df76d6914c790097402a560130e01e79cf3baafb946d29ce6d956da529897fced903648721cba9dd46e24c6a1bf9d9ea26d1da53929cdd1f7efed5492e36072375d12e93acf982d35a7974e6eba41253f79500ce6abe827d28db9316ee26ba4745333c681408fb9a9e9262f797b8cc94b0e24c6491bf498ad9db4ad4c393728084a03de0e12235080c47869c68dc45c3e0255d44b1ec34f97a34015f5287ef218ef384a13625c5672040a33b7747908aa2895b9ed04aaa81b31b991d26522ef78a80931978da08ad29c151bfd203592e6a893b6ae152a699c47c55164f2d45f07a3e0a32a4c6816850a2db40a2bdfd54c79fa1d249ad42b363b7894440b932d48ba4b9423433049dfa4f8a839ead4ab172c34162fdf3517617cd75cc0f8aee6aff01d44f2d46720c23a7538049d40257447fc0995b48a3aa5d49b7a3bc148dbdb814afac622a23ac704da1d45df644eab536f2ba48d77da8ae72d5045bd359e1f0f522dcd51bf91847d4ff252c75a9e7adfd404d25ebaa9497220252791bc7481984c770555acedc06ec773c34a5bea72d3752f2fdd2028d7055272d30d7a410faa48294b731845df748de5a953cb824ae01226d46112d049e34491421d4ee91bfad4211548d437458dc33bd42ded6a2c3289ebbaae1e9857cf1c7bd8cc9e765da9f1f464fe7e6e5de635cb5cf339273631ec769957efb2b75cdb7a7e82befafb8c479c3fd0aa1a76bb218fe2cf7c504ff3d3aaec50c5cdc4a95378441b004c8c705605783835580f4786caec69652327c6082da4d002091158384204494a5083226c9162092d17135e78ed92440a09305b0a83060d9d2123c18327654778045389a9e8fd587d365d359e212b0326b480354a1624bd02db92194d2937a081155d5890840a2e44c0326484225cb10112531031f18212133580410eb164890d86380211484084f7c587151461480840107951c21639aa1598c062664e481a0c2173828923488d09d70f55ae1d500182c833c8d981fd2113c177ce0eae900a42b085102c400108888ae88007bc6c39c1921bd8e0053440010f6860844d16ae585902f6ef8a9522501b6cf16d79fd89478e94e081a8f347ad478c6875fc28f8dd0f3bc24c8317a6c8b213bd1b30cabe797d43ca62239d3246a24ae50dd2899452ca558c11461821841042b9411f51588e34c618e323f2e1c307f4e1c3c70f207d6379ce3f95b455ead46083456dc49793a3b9b6913dd1d5e58f87f6c97823d12aca7bf739e74f080cba0bb0594a1125acdfdd92e4ef867cb1f931b324b57ca1c83c832c4868232787e9df952a5b261845931e31c2d24246ce0e888034cb12bcd00162bd1b2baa3cc740047b0d8ac651342e84022bd4e08a111578f0e50920bc280a628a2278c0050ae60c8a1040ad021111306707308011c4142054590108a26084072b28c2962eaa1802141ecc2089a44e74cde774dcaaca15f5df952a4258a2e31ee8e30518342abe17a2aca2b8bb0041800579373a08f420417c04f115c4d1fc91fc51b7a8755f9554def89a90fc052448db9056b56746b0d0b9873fbc5973a8cc36b1ab261c0a052dac2d395083a269ae0a2411e5a89f54a852f41007e54f78a0668a1b00c1355334111341d44c11af545182f4ef4a1526553ef0ddcaa84b74b7cfe6bd9b39638c312a018b2f2cf1c2121542086137e423d80ec489d05647a17ae7419c3725a9468d1adc8c45014e4c7064850455bc0021c4c4045592108526d0c005514ca008242680f283228880821d2d8ea67084267ae0c5162c9064e1094ce072041f5061e7496185284d0851c40a64f0c3ce5352050a9c34a7e52d298a9e7b4cf3fabcf9aff0adedb8527c79ac7d780cfef058ccdef6aef40088c76626b76ebef4018a2db2fcf328df0dccdf9d2f3df66445e2678e6d5bfc4fe933c7668685c71e3361df1529b0c08657bcae4b5ed7bcae8bafebbaae3a83d40925f198bfed5d91228cbf36a1ef1ec93bcb9b44ea213def2c2f691d75cd4924cd061dac94b1a6c372eb06ed76cf2db7341eec09610930b0afd88c175d7aec919de53d96438f9a0f211dfc7b343a6d019643f8fc6aef60065a3e8fefbd87f144028f0f5982b8c10da06072831d54a5c2151848e1c3151e28c150173bcf1f8fe028c2e8532808de20283870007a5012de81d7480fd07be02e6af083233bd73d025dbad00e756b7be189659415db59b97150c56e5385edbc8ba08ad9a592ceb1b19ac962bb9609c376abe7c6e2d9990a2644cfcf9e03c933511f75c327300c63be9d94c2dddd5b47f3b14a3927a5d5b22ede302ccb348dc45b29898c376b3798cc741125c1638a394da122e53433c333b29f6ff70019999999878a81c4cef75d1ec0b0ba7543ea86c5a7d55aaf85ce1bec79cfa68379e8437ea06cda74f6969a2d7b76774ba7321a61afa66218a682c2529fee987cb0d66ad9886158ac447ee837f7eddccae88d37ae621058a43506e13b9933ea5a7e51778dc70c3d561fa55b1a8fac959f7f66adf5587dd578668c31de0cd1b1a629a8d46df6843dcb189a1101000000f313002020100a07c4218150402899ac99ee0314000d8ca844805617c95110a41442c620620c008001000010082401434069a07181ddaa605845e6819cea16a03192feedd8da33a2f85ec096ff4eb857c6335b853511c8881d44a9f6851cd81795e48cb89f421c878c4cafe034aaeef8f7de8c04e14b8a67e566390bd17bbd82d1dbf2aa7c8593c280c3e9a25e7539973103fd8e73b4259d19ac375be65b643b624a4dcdf3958a3291a2890e98f3184e864c76aedc107d9498f117f87c694b97527417f443403909acafebb6bf9870f95641fba05fa0aade626eddb4121ad3052bde5b6987bc7338f5364bac6674c1c3ef0cc05fcbba71e391ed99ef642d013f486bb04b732b1e1524b76efdb57f6699f22499076a13117db52c0da8e996962ef3eedd1043220579ff3249926c05d602eb836a389f0c56ced3fdfa25860f1423d1399d8f2777551ebd5c741e975bf080335b7661d8d34dba54b38e1716afbac2043c5d204c350093a1e9a33ab1b508bc36398e6688329d1a2d0834625b6ba9022295a5751b4ca0533bcc82600ae73989cefeb9a9c0c1a2fdb840853a45d385bacf9b9b03d4b74caac1b4a24a5a3ce71d338a11824fd9718c0a947ad323f5c6aa468683a2cf561da32adda36ba77bfccf4a7b9e83e3759c95eff81c8a79f50afdca2e3ffa9d9593cae85b4b467b5bd3d9ab6ebb393eb515aeb62a9fb0c0a9e85f0c77e8aabe740db5eba83c6fb28119d7a2153870565baa88207a7c40d1879bae6115e73053b9da083ee9b9fbb64d63523c0f816858e9f0edc9fcd16c7983b79b7219495eedc709404c0bb935026254a8c97e87612eb3af8e8e38b0e28171c65aefcd02d609bad27c9ef07ceacb715d078d1f6a549cd34c8fb7cfe2c8f886effab97f3115a4efa7301775e80b4911b95092b065e06b4f83be008e85bd0c9ba0fa4203d8c8c4f658f54b4f9443f2ce731bb8ed98a1326366067d61330dfea54719617f59eccf17be275207e0d50bfeb8967cdb0113375b10a282bd48c65ee252e8cf9710ec64b4f37ed96191598d513b06bd67feb991d7526ac46bd27891344d78b6fa162466e2c2da7f607bf8aca9d27bc7e1f9b38b751858928e56f323f38cdc5cd64ac9838d6619606f03557e9e25e7818387e422188fdd427f1710cf0e4f481c6b6a65efc94045b43f97e4e06c90c3312fd3f81e1afc5db3c42c360f8ea029705482bd4c05358b8511dff7cbae267dfc02c35fdb2b2e00aaa711a6940e83bfdf4f014d8cb5f9d2b76eb2f87f23ed98726ac126976853135cfaa52a9fa67562c7acb61b08821c0ec668ed49002d7e1bd1d941ad07fcd0f32485647d15bfde6b8422d7880c562691123ed1a711ccc571b13e3837cfbd61ef4c17d68188a599e0c474faea0c8cff9c4440538a0653b36e4fc3905130303e0290342318f11e44d39112efad0d936dc29da78c305e16403cf11d34ab9e182f101737b2c336abe1a25214b25bbf886cb8813e1f96066f341dd59e58cf5b4359729d49ff18acaf32ef14b1ff5b3b45150c9532a2e82a61c0aedaacd78a4c3ba9c393658aea6043af0350f4db50f560acc0e37b7cee01ae7a556965b72ea4cab58f0b003ff8810acef7f8403a0e3d0ac46e6a2fc376e523b684e63cc9020f59654562dc46144498d1297d974e6ae1336bcbdf7f92a4b31ee76e3db836e9212ba531df1af2efbb441872e7a25e0c486ad7ff0ef5a06ea0c9e35c19c1a88016c15a0c4943ba0fa06a65eecc179de5418e90e49ad5d80ca0a04929e460eb7a4454b2b75cc1cc6a21f4ba59afe5fc862645e8708dcde1f663901f0239527775be0f995754234852caa94b4504329cf932c67ae1bba032e0973782bac183a0dea6524ce31b6ed4819c6854bcefdb691e0d585bef9da158abb568d0ef0773f9b63d1c8267636fa018105fe51927b2c0b9defe48b2c20e94486b61bce54606924c4a2ab50fbd4f24f4d4dbed9c89c0319f857fb360af91379962c3fdb9e480f499cf031a5ee73a367b0362249bfb9869c496f2ef7d1fd84186bcf385c9ee1bbef43cc6f53843cca0023488ad320725b6110f5f07ed4e373a2a67c34a6b464dd80d06ae6487e040a3ef1950b77926386c1438239aa2154d9206056fe19828f5d3d4ba6ec8e2a838cc172587282f86b1ea407e8392c8cf952087c4df00d3bba500837273b7947d5e5db1f5feb0a738378de4e02a85a1e929d9d17d8b9f24a90c6b90d378ce0a7b2ad5479d9725f2ad76f26d059486c7f6414e7647b2f9cf4ab36f532b6efd69975bf6a203ee0fa388c2ec5b817838b5fc87e5b17cbfb93f41cf338876a9d3e9b4d61ed736aa41a7d1752886b11efb4e816dce009dae67c89cca46127171d8890586b8092873f2a49a280ba480e429633bb68041f3d7ed105e05287df723b7d03e14682bde148273e15e920d71a4c36c37db19094eea3501e8c29dcca9b6c256b0578a9a40bd3a9522330d6e02dce6d68a4ca152281831b140c9a0b2902d80318a52933cf925402a1251e56cb27e00458190ed324d01ec1868f89f8cae4ba9c59ec1b550b45bdb84df031ee2e65d1048c498401bf35e2f32fd0ae489e145ae1d8259400bc293df405e30bcfd713e447f4057c31c95e03c267d1274fd158e19f36a6d08aeb4ed6256aaf08a62c447489b9f4430705f3fb042254b219333338ee540087fc67d463dd0c7428c1c49b66285c2e815746040dac727eb246d978a0382bc1f7595cb015cb889a4c72bd94694ba86d87707b22c63018040c6c6b9a2912a4407e410ac263d2559b3fb375cddff5aa5ff805096ee46af0da1f9332921468a78543935bb2d9272b7fe0afc345de616f6a889d2a34d1c212df0c83e38df4f545990add338f876d9f7138dcc5372d9b7d12d58de0fc83c204928f1d0fbfdce7a694a573a0b369dd474dd2c9575e449b0768fa116551139333bd4e70c5c5af481884d69942a0a7765786fb5b20f4ab67da89576c2a0dae6f15ec976661754f00c8f007b59b533b5393f83b6ed68119f73ef88a38ff955b8bec61b898da2bfff5785d3b22eae4bb9dd77694be9d5cd8dd822675ef0485d53e356874451bbc687cfb41a19727445bd6a34d79f3c62490b399aa942ee176d400969e006b5f40b043d35157dbd3f015c37878b51bc067a0b019642f5460d4137c180afd7f11520363cd8864a338456be197881fdc05fdb91e7bd10aa40bcbb940734f974a482030c33a27b614811a0b0ed99ec3e9305d834f2bf3fdf3308d0f435b4c958ab67cd144c6e63f7cfed7e5bdb531268376bd72f24f932f86df28aecb0e11bc49a84e17702ae65fe89964edc4f9ffca3f56680ddf07c7bee34b36198c75cc91ae0602b8bb3f66ae68d3a6e248f5eafaff4becd12dc26e683426fb52008df86bd395e89aa673b27d2e08c7df8dd068a9126051dffe19eef7c9f135329a012fc69d13cdf57b04b3c1b0b44173b01f78f80ca4b8b502401e3be6dca0eb19b1428b22204feaae01efd2326d6c66c1ff3ff1e2c550eb81a17cb9f57388a819912cc56f592dc12c16d493286fe48006e7085a58a0256a517a9456a7402dd5eac6ee88cf94bc352f5830ae7f9e2ec54ae76587a47aa0c4683f74d51dd2114c484ea3bc349e1cab3eb10f237208a527912e90fa46696d32b6f7c99ed8fe83a71ce2559bf7e497cd21c74bd0a69cad30a00c453e7da529857f734cbe95f8807247383d8f79de41ca22b90872a3a138a6cd8d5d9d5351e47b6ecf114553757e798e9caba95a676a1c3f0e17d63ebeddf0616bdaf8b59cff7be3d30fd6c5a69e4d296de302f6e320717168d49ce7c24d3e0517d62856f6be0c6f03d945685604d97c1efc24ced30188f9c1c6a1ff5cb5372804a9758681c248080107cf02850c1406baa10df50baa401fd4317c0ddc012fc6d407cc639e003e2c49bcc98a79cf21e8a16ac3ba75352c79e246a0f231991c7fce15d516b6f0190e1021a47be18b9597ec1240643a909191d35bc29251bcb3412004bdb48f64464a1edd90bda20d4f8e49d6ded39f1002ff7d19557b1a985c0b255c5409101ec06b5eb171ded7b7eb32c445fcd5033bf9cd671f062eb77634ff2f273f71e9dd1b0eaa00fb15ce0c4e1a50c9216d510acde21f998fc5320047195f973d3fefcbb938e37b4ec137b346b9a17262965b4357fba9796c1ae18941f04baf7309d22fa408f7eaec37c0de569c093ae120d0da707265dc318efb2a21e773e88c4a0640932ea12f1210182cc8e1f892a5800781af1ccc47d025d8fef0c79683dfc517a42bd4dc5158c017e696b41cfea6a8cfb22807f8402eb4d2746a26fad9bcab37c4d22bacb94a55eab4f73e9dfe3a3d714acbc633ff7deb794b8ab087cda76bcd1994e72370d28b47e6fab8b1c09298f2aece6320895c2be8df0846fd5869812ba80a1fb5fa57b218561ac2d99024e5001623a2197a9245bfe0ab1d9225cec7f5a3f3fd99dcc828feda8ccc7d4cfbddd328983e85aeb9e2bb50049fbedd6748776fef3f97afe4cf567db6f495b1c2af5f32b551148dc1b8375a09487e363e955666aee3a24ee7c6fa4a8e79744a5bf5270c1ffb93dd8321c5effc1b1dd42830932293df7a62b78dde1323e33c2005ba4317a745c46ec8ed2b16329fda6fad6a9282fd05762bfba899b4bdf20d9e7eefd52eccecf338b04d0105e70c817f2b0bc19780176507b380ad50c957e40144469a9c3c677eb3299a47b32c15a5a7505c2327dff359de8120ac8a839ef503a2fa8d3aa2376335e9c864446bb0206354866294d8648d262b65d1e2a0349f0c5dbb94827ae55c1e4170174a3d68300ad070d11bdd6b97b2d68bacc92ac3acd4bff68aa579b633202f535a40b3e47015fa9cdd89fb073daa558045d6c863140184992abd8ee0075b8bd59c256b4fe987a74e8566d4bbc287a5fade9f016ae8e749ecc52ce328a5209b3985a31bbe7a5ad90dee945890a19197928e73f22c661416d6d7504ee452d834efd58392566e49afc654a4c9d90d5864e8d6b3ccaea8316b92779a8d9b2dd0a490347914a695e1c2ae6bca1b3cc040f136ab88435b9eb5c655d49bf5aee5e522e8adb078b5b73611d0904d5318845d5e7ca68eb1d041faf5cf32995f85c34b7afec63cd0f6e9667ff57c266df458edb797a65925f39ba04c90399e3cd7882c258e02e6e3cefe26660df0e871d83809f58f31e7316846a6ea290a26729a359bbc8471b8a28eb618a5e78c23fd5343e6ccb12fb2ed9f12edd37f2b3a41f8942e68db6d266a2bc47be0972fdad3025d688c9503fd731c238c301a4d568ec3564504a08bf3eba6ba144ec1832c906f05f1ecf3c50e95676edf58292048ac6b999dab5f57ca9485203c099cb3e306cd76e6e36ee68f269a0679f069f6f2b11e324e95baa97fb27d4219876ce4f91e8c78349ff10347b4df906fc54e8915385a83b94266464a49d355f49359f4236fbb839d7733243816e0d520c3ce98f6f869e2c48a070df2a9fd0c2bc1ee9e55f714629f841b2a5bd8f9154f1d0edf37ca240e3206bfcf13bfb21b0bdf421709379424ce28248a99440ab5113d07bc5d68f130c0bae1c28702b6b8cfb8a1db1be63b5060c915a00415370100f27853fe3b40759a2e3815bb1555c4f16f797f601954cdc9b5a23a6249e09436b8015278ea18844b3180356e3965364ac78c02ff1aef5a934225549bf9e480336018c65d2ebf11eba1c272e44d6c292d293f54c21ac26f4ac0694445bcea8dc82b66971bbdfb876fd17818c16aeb9108900661d92db56b1261b714f4e87135052dbe634500a9b081dea546f4b87459ee3192aae9d883358732c7bd1d7dce6a30ff75760a032f1ce19381833321fabd03b790150af646a63b58998bc977382ee785fcc37f54724ef35464725686e28fd6292ae03c16878d6dbe59970709893a87037a629e8d48d87521d2cdd05427568668b47963d0c81b61b16134ca97cd33c1af3094289f27ca165328cc52bd1b62f51b79489321251b749c80ae25f9ef2a37122030e80e615c5be49aef16d634148c70cd21b0af801999b5f730adf45de4d99e74790f780d4d573547a341573e1ecdf5034fa1bf42433dc579a1a2f4f96f9f9f2bcc85dd0f9e2bf8928ea1fad4cb28c2548ba8d6bc933673c8760c671528a0b4fc8631e316226a35884f90981739c9c3ad155665975e60f5bce89ba7f99a925fe174175969ab9ab405787e7a8c1a3a3ae4f8138ded8b59733f5f24c5097d5f65a0c583655e0053835d74e3f6d11bc9f5b076a1c917c8932617a82975776b00f12653b266e09ea4e33d1c3bc8fca9d98197a39d8f76a539d1a6690ec2a96332648ccaa4e087e8f83d398516bf1632c4a95e9f88a883d26a0146e87dbdb73986cc270b7cc095ab6f3a5970c1b1c9a768425d256bd986fdace1d1468d04bfa1f76584e8add83aae5706419c8d972bea479b5c048defeedb8beef8c4c509d5dd19d69c58a7a4feaaa367d81213faf4c6dd51ab56e3defa2b8a7b8c6653a1758d2eb75855f9449677e2dbf792146e28529ce71d971f86f182dc55aa8f3f0be101fb096e395f131e8c3433e39fb2afa0dadb76ac00ab69b52ca8f48dbe266f42ab810f0c912522606e950dcce720bd4474fb4a86fc67a51718b71b8bd013a3de30fcab54343dcda2c3ff654ed91b1d71786cdd134ea462847d1973e38cf1011525615f0e1188f6a29787ec72e4c53aa2bd4525ba65157d5b7ce502f31506bf87eebd76d1e0daeb18601c6fdf07503a89429e20392421fbe41b5ff4d5c0219297f19efc02735db80a33bb40acc809fc5a7e2c111c5c4f6f87ba9c9acdb37e19e9aeb4774b10e2a868e42f698904820e04b3faadf60d50475afa953074ddac83158012cdbc32d619e43abbd65bdcdb855817e0f6666847850eff53c1cf3002a1888a47dedfec62ae8449fe427fb82cf71ecb4c253eb7efe640c6f550728fc08dfe43cdeea2822d5443818f8052a928f1e9b32c89416305d46c555fa0cf9314934810992d107b92a3e838323fba778cc35c258c8b725643ace13684fd600d97815ac0528dadc2227e028c5d3044ee21867963a01d54a2b0be3bb79588c47f73a02a3036f20490cf69ee0dc070c9e48739b6466a29c7912819a3d525845755d11f452239eb286ca2dbbe34a60941f612161b9f4c14d4b1b8e999bf3be8246dd67b63bbd8236e42444bd0a755fb534eafe3bf294334591ddbc05f2a30bca7f176ee28710b4b50161837d8fb8fef56fc98ce0f40b447e3abf6a412b7b06e19f518472ab680bac55235048150dbd5b9a1d2f4285670fafd4b1cb10358d8c2cf7543e47c824bc38bce8ecf75e3f34ee71a7e98611cd857afd84e1e5fd7d50ab93e58e845dd6952c45ec22f08a81b32d983b0f77810cc97508b0f3d71e8b6516dab849050561f8bb17f8b0b38754b87f88731f9ec6cc7571cf0a8a226da242112dd6ecc3b73f90417274d3bce7733ee5c75d14f417823860ba541b73a2c9312d77c6bb8fa9c62d9f95ceb632b80fae69554d013d92c87b77415ec017418f96be4ce2630316c98d785682f110189cd8043212ebccde76f8ae15f2ec4fe50010a122204bbe5592b0173ce33d148374f652ef1685cb9169e38a8428acf1bec29a413a022dcadd3143506482ec711596891c3e121c2189101725e24b678c7c5ef590639b9728cc842c3313ca2059b281025b91e35be373ba5671d756e41ab6c1097edefd1c74d114cd0219642618ebfecfacedec080f24ad006a252a22c7e874a714cbfec831486026654bc2d2a90576bfb892b445e591ec2e012d9d2e607a56728c70b0039521540d97495f78b43452cf9ebb5c56116490354f2ed4ca6ccb91b353d6c7001c728c6d3cce1ebfac2b9fad34f159422bbe61ac502c5cc35d17dea923be059f4154ade5f6d232651c0fcbb875a8145aa3c9e4c7b916e93b91c8b535ae22fbe338ff2e79691e497e00152c62ab018ab1193b257ba55a344d7526cdc7e6a1ad15ecd44f534f18bc6e0b404a5f573b78458b7a3ad5a3bb8c043c4a40d4eb16ccf0fe6838f4849eedff30d36c85d3c3813c0edd3cbe780483f53bdd8c18ddfc76729aeb95a5f803b670dfdb4590d2b448197c78ba484e0983791b622eb5b43d439c13d0f9ef19972c684240f08b78c8c4969ec0f2b79eed5912d3272051090e3801b68ad3598e185a9fa077dfcb5f26a17809ef31708c23d40bdf1f8ffd6f8bd24c8b2d2fd420678b53c82c91513df4bbfe241fdccafee49c18b2fc84fd0716c4fe63e54a12dee29fe063cf6303bc3be6f4e057f0b577c1ba1e547a32a39390a43d3e05c9c63d3b68c983f145a0974c41f6a0bffcbd7fb2e44863a152c77c780624042d3fc1d0a336bf764c203e841ff442e52de2b70b9b16fd77f73719c8fd5884ba46912eefbaa113edfe61c41ca63b47e9fbe2249c689dd10a9f62fedf7d92692dcd8f738e498e001aa96a953d5baf3e6d1a8a5ec097ec3b299a2b7be4d3f3d478751fc106ea7c8db9490b76bb41625204846d88fc3edafe10b1c00b356c81c206a91a7344bf917f0e782f233afa808a3d2eb87660321b663efe500ae365fa0623c793ea2a0ea458405ceb58eb30334fcab5dee4701373295e54335c3b5611e3598b6492d0af2f362c37ddca4214fee8c71f6081e5538b7cdaffd3131e30a69a1937491584c95924289af962128447f1c7cb4540ef4df062c2f145209f70e42eed257e79eb23d481629fc2c75d2440072628bcc471e3bbf2f4c3e26d2eec492db6000c26ffe29d22c60ef8022a4c97254047738e850780f7c2d2059fe61195f4bf64b4a8325d4f87a9e9ba1d5731a2ed2d7e5fa67e93fa30342f063685e582c6f3ca04b0679db90b392b5491a98483de1a303458ece0a99f437ecf9f8de3bb9b52599e41b77e7612814ad918031ec417fe79007f9fd0c3a9fb32bc1a9fc0de61d50f89eb84b8393ee0b5319713b4961238fdc771b197c0eb0ddf71420375451f1a33a3164a846c869d3a91ff5b3f35a44f2428b6306e430de39896193f1b5ed77cf3d35a7d7bafac435d91f4400c6b9e9dae36424cb31a987391da5bc032986f14b241145db44adfe12f23ad4ddd58b7a7e4f5ee6135b66eb87d15b584fe560b4cb3e14ba756cf3c7e8482bb95bb6a199905211f153f9ec4778030d1cb5d98443a84e32455a7e6a9ec8b96c5c41120ec85434ec43237bab5e396565659799ab9d2c9d75d2506c5c42264c244258f63e663cc51418d1decfe7673c51957aa4dcb5062f0bbc9785109bb6e9689d6f70f6efe43d22b5da8a552b000043d7be2266cfd7b9d87b4adaeae6e914ec24d72b8606e1c0da2093b8bc691d4a199a1655108a35c74107ba5ae8eb635da1bbac7c7fbc73ce1dfe6b4224327e6e9d1babcd5645f93eaebe2409bf1cc24c9d93142443a9238672c7fdd983e0b99478b8309b1486834400c627ac8ab1b9e798264bbab6d35908dbc94ce3e9050e54d1fd34be9df3b90af803b8b63fc8481df7e52ecae23e57ba99e15e16297a76a53d16db29c14c828be8efad653486576b25d03a225d51944689514eb89e08ce850d1247e2a6341095608818eb07c85e854d67df01f8a3abb85189ca1a6c03d631339dadc4e77cfa7fee3025c129bc177b86c62862a7ecb0061f2390fdf6decb12a0ef5c9f88f885bffc311ffcd8a2ce736d32b103670ebd4d9b2458fc93907443259ab56f1151ab1b3c4bb8fa08613c261787aa350f76f4212fc5aa62a70735258758fce7281d99cac02ba274f8854bf4ee3374c564a72cb76adfc604014047d72a431f2eb8eb0ceef180e6023f7035e22077ceb4f17ce90615c2fda6e0b97c583016aeb496aa1324e5c22ca8d4c4a5d7e898b9fec50945239dd06c4ffa49576d5aa0fc05bd389b9cb052e67178234e81bb6cdc1e24b7f5090b93f59927fb21c2af8d3ece9a2c24757d3152568f2480b330014f3d9db562dd0cd6776bb17c673732583f5a296db2a6b02f37a6b9815c56da514086f8fe07375e666469ce58d8daf3573c0492552dd12169b96d11c0a59f633356e410e32b58c8d468897075082f3e1fb0c38d5d164b0927682bc3ea58c627a4e739d2d1c34388913fa7948a371e7661b1d5e043e2c79a2bb3eec0bdc284fc0df1380b039f6c974d0ab6863a0abd5b7197fc4d63126f26fb12b30c81ade249cc639120e85b41e0f90dbf59d94d1b52019986d010dd0e17a1ec7ce2f1602201463838887d0b2792c33a42b073d158a23418b205becf6aeba74a39eb59a49ef0181ad2800af3890c5a37d9a196ccbc13544e8a6be0b71212a1208338879a440c967d0deab59b60ca65686847da49f1bfdba5fd26cb79d368517f80a86a130750f3718c2a5e89dc321e05ad551ca1aae983e7ab69890bc6ea53eeaf701065bd856079e69deedd565a70ca517e210edd06ca0fe3ef8089f728d849d81791e29c6d134534751a5dacf39b3059b56537426b841907c6d0f5cd9c6de66ead80b90d10107dd8ac92ee758e0cfb955b8906af89ce145e44f13382a63d24093cc55a28c9b5df16530df551e7be8b1503c3f57e9c9a9677c0be683ed6782b100e89907e27804adf6e1581ce4565a48d58acafe300390a4e962d296f67a3e48a750430fe99b11af3ab13dab5f493e087b25d63a9b9bd97cf813c50e4ca590ce1af616f0a8917edaa8565021b12a23763de3ef88e8e8ededdef64aa4cb261b255c8341bb302dfc6740ea301cd50988c456ac6c42741663ed99364579ff0712966c7ac76e3f4d360a2013c757423f8e5bae00ecf67f32d2fa2f8a0e8e9e0b08501c8021d22c66acf2f4e7358163e937dbbe482c1cfb8f0efa68f41445ceb436c54522025900f4ad8f8885eac18a6c104802de686731053676595b1d030f1a42b93d097b5e74b598069c25a55cb1db2c1cac8bb9574182bf85d8454402a14a79dba2e3b7202a654057de04b46b5de30247ff10ab9ba3eb7f7f29d0f8c5e4405a07285ff55d5a05429cb7e76e0ceae49a851b0b2741644f0623a04ec8fcaec192bd8d393c67ead04ef4882af26aca111275c6ed0ca68147431fb5733ec01d940c0e240b13503c15a98550e65417880e9f69531100f999dd852d3845a61672316ee7cef8e734835b75b2e7a4a59f3d9e7f333d85b730563a5f39a1182f6ffaee1f4f5c20d912528023d65a03ff2273ff8308ddfa2dae32b41311f7f3df9109dbdfa9886c67f177dc5caa40815aff77bbdeea6c62da66432bcce4405e862b7ed393502aaa1e956f33acd4a9c509c6886378a94e248f2bf6076b360a25f11037c8d3fc133e151f2c916c408bff552a82c1876f6472e7007398895043eaf6375ad3f9e54240a3006482868375b2d4e8ad8f4c43b8c25b484ae3cc2d859dd542c11ed136d6c3bc41c57a61ee2dd81733564f1147203e013763bdaa5c3606ce110a0a459013f5242ca49cf92199a863904cab66c459647b531211e3e7eee6ff6393ee3dcc9477274de0cf2e7e1e616cf8ebb0650556dc5f8afb5b117f2bee2fc5fdad88bf15f7b7e2fe56dc5f8af85b717f2be2fe1aea0862099977355c2481da55608958fe1270570dcf4c20c5c92dda22986a2622a2c85f3409cde49c4f23d6afc4fa957969e6ceed9422fe56cc5f8af85be405b96682d225044edcaf68e836a98ccb6676cd654440594546539aa3fa7b40ea633d58e3a7f4489ece0ce2836fa615fc1bcf8412166441359a18e09bd910a0a1be1a96b7504a8f2e6cc3ca2d65b381e79b53d5aedaac6e337191438eb9949af361a39cd1077fd41680d5a9c5e31eb9f6cbd4d481c7edcd77ec44fec13861c3acc4d1d411abf63fa99a1dd5aed4fc624f5c7c95c8e80dbb3ad6a19a8a3bec45c7284d0d1eb8db20fb60e1882660c3967f9ecc75d15be044e379fb70dcd9cac5323f6da112e8f3e95b72d1fe3cf709fb9b4cfecbfc4e9bb9f153b08988fe99f452d7bd797ac1ff43b5178550fa9468befce9edf156d6c8ad273db04afe272a441ecce10f639dfe2cebec8553849d821d745d91d416d057d1733483bea0ccd9299006b9afe1a99ef99abcbef94b911dd7b3292b6814ce70ecdd09df7d5d4481fe55c8a330970abaf29140822628819a84cd28ea32b1c3a6fd042ffbb6ce090575f78ee2069977fe2cd097164487eb904ad0da1ce603f1d0806d7e2e6f2f0fa82f5290b3a47c989f696672ef1ae2b06b4f0d70959680691497263ba81c5e5052305cc5d4951f24a69e029ef15de80ce17cb0c928ca9359a17b65de31bb6ac54ac46cb937454ba5c0ac11ac0499d3b5cda97637e28c342872ab0eb56802c40bec20629ef9515559964463615d2901ad9b51dfc65b6fb6f406039fb52025217031199b7113330facb5e9556c4f1c71413bf71033faa5553981a10f04541aa48e027e69d019aec3b889b94675fe8bd27585dfddad2fad3766ff936f7710b6adf60319321a0bb73d344c039549a175a97df554fd9a949eb4316e8b94b10eefa53dbdd3a5010f2d0f88ac0591799390e41a5fc303aaacc81dfcdb687fffacc6e7318449d5b21c9d5a9e2a2864eed1d7dfab1b6a4d8e753284025a4c4a7575db4d4bd9781d00dadb3c00956c76eaa9eea42051dbfbb22c5c30fd710118020854ec07b9e7cb0b1379209973d444371f9599c61789992e83aa71daf5b46e0a3cfeb32103d043c235786385adc23d46514eb8c871ca05a65c125d142c58037cf6dc3f49be7919393b2032bdc4f6835572932543895f6d6b650915d70cf564d8114626a09590c1529398af190170ecac465b5f64368ccc9a304b35ffe72579ebe339824675acd89e5948625429c98e322dd66f920312c7af25f92f3bb7af78aeee55814cbfd066d8187082bd3ff2751357a78959a8460f2e54ac5d7ffc16db23ca0c4e3509511191e5e38474b45885e882cbc4019324e7855db185cd920c6e34558f29fb54b0f80aa81499fadd0ce92c330b949d8cdd601c590131f30d731c18df8fb3a3a18f0162f360f4c7991fbc3e7bd08f228bb037354ded3e8fedd4ea07df07e07d1a2c5390e0746d089a016c0477c2f31679b656c5cc2e236b15dc94ee15b4e3afb15d93f7b5125eb0545afc985745556283ea0d375ddc0d544f49387851a2bf2785bdc207094452a511ac6d6275c7f5f178c122a282487296ef62b51dbaf5b549e4c70c778bbcfd4442f7e12e96a0076787360defcd2f9cd7df92fde035c1007b1b5e82832bd045a009f29bdbd3bb244d7aaa4dee06e95f607e3fd9294e01122f44c987efdc80ab93af42c97992ebf70727ef0944424292177df336e72501545d81122280fafa2c07361a823d6dd19f30767f2dbd7b29444af7449cbf0172850a3a435b701102b68ce360d00aa93aa322fcbd807672ce993c76c9cb7af6cb537c2cec5af0456ac5f3294d3d1b74aa4d37efd01a537ae4a2cc140c2437caf33efc55db29e2ec17da8e937e9540b30ca94f19efc571bcc16394118ff5eef1b699198f9551aef55d040a3982ff78cf66a2b2e83ec57b03a38b8bac9c7887104e1cfb6b671ab9adaed7eb18f5a2aa38acaa462cf740508371cc03cbd2666edff59ec87643948d98e1787a79a2714db1f0377ed690be8ad76c47b3b93030c1177046af53d5ca9b0b9e964f5424b7b6209cc2b9acb9b1bdea117abe50798dd564f2e4747849a1c8a22fc6e55386c92737dd4f5151d93222d0e72ce16743e486424e5eb002e17df25344ee5a55c8ad153cbd131f4341a6ecada8156a9f85d01d6956dbbba76c14560c994f000bfd61297827d93a07ada88c05c5093bc582ec894c2af52fb772463c8a13a1d1cb60abcd604bbe89e00a2b57c275448cdf0d754e532e95ca13032f2667cb2b8489e5ab994a71670b5de3e16cf85f8ee62221f648fbf74e637a320dca9c32eb3b2f738a09d49c0c80c1e76c8ee17844d00d953d3c8d32b5a8a2b5500c847db50468c2c666cdcffee8ac621d3c81d1a1cceda7d003ef5e97815c4475af988452084413afea70fa634fdead5500c2b77b160491d928d871fdb334f5e75f61983a6c71a6c4e16b56c06c9dbc00e22373d0441f3322b05c6853348d8234bfb73781b262959a16172dfdc3682b1d0867882aca92834d6d74bc81040469249aecb634cb941b639c2e7ef4c32d971b14936482a2f0e3070201ab8990207b2eba5b00574420e874f45ab01636188d219ec7467d7b137b3d2b77af386e28039de2dbe54a4f0b076329c23f28bf8d3a6eafa2c53650cd628fb3c710b725034c4537f8d3c7798b18cad633f8018f5dbd664e7539629ffcfa456791816b36a4a89ab471d3258e89f93e4a02fd8a8afc3631db5da18ad17ff945d03c35d80827cf887a29c34200399382acc08a4b223d824a20a2b668c3ea63e02ea526608d4462a4c9195bcbfc8ac21c46959f0d64a8327b4cc56fe15b15bc8202968f98a8c950f79b7e7af6ad6bb789721affcb9d44e29b0d76766b9d5c94452e411ce8c4e56a480d482b0451e096ec0fd28102da1913d9bb6fb4b6ea1e5318e761d25f6b51dac39db1e1500f413d5819bbbf7788a8e6ce4bc8788472b4cc83f6906f4f786e511a345e6231be4bbdb303f5a84a3b719cf81d500ae2065cce97198da734337132dbf128196c9e35e10537eab3592094446b234f5d628ab4bd2ad2d20b04e9c2b93221b8fa83ef363509912396d45d21881ce993458d1f505949919e19465d2720d1cc3887a55a2968a74391e778c27be1956b144633a9c95313accdd19b51247cd307b856ae402235881d67e9f2a7d1a0d358a31be18b83452f85efa00d227d4c9d9490d1c7f5e72bb42af6c4af31dda32fdab35fd81914973c44aef79be587a232d825e4ac84c7b1e7ccfd9a69ff1dc59542cb56d140522d23aec67ac0c1a8d331ecf83a4c11c76fe89df5b52001f0e119173fa8a48aa6ebea40f0ef10585fce1782b1ae4bafeff09075a3893d17a69417b4b7be8de7ffca0208200fe899493f7fa066a6c19d968eb292e56130779313e2903fddeeee6e50de6ddb44c4059f190a6f6ea637d84becae73d5b2d7b769a0151cf429c66ba7163823b3e342ba613e048fbb25d3d575e3c8cbc0d19d41e3e4f42e44cb5c35bd20fd612ce310ccf6c3981a33a75ad870d2aa62246535f03c3ce7c9579432e997e99c25cf62774e9e25ec6163d9251a52726f04d9a0d9f3e59acba6d1d32b53d197a0c681d73971965df83ddc047c94002f9d7b1cb60e095eaa2d2a35aebdced370d993bd616af2999f0c659a4ec524f1ac4319c2c122665ecceeb00802d7d90555ed94aa14b3fb31f73410675032cb84d5156cf7ca08464c8fa81c9b19075ca49681961e12f760fc37c1ae1237ae0da9473c2af43672ca640e47ea4675f726a72177d4eb387bcd478eb6a0b35c7bc39ac20a8da32c579c7b149ceaffd0dc1c99c24a4468a5fdebed76b48481d1da16571cd81935d63a967ff0f5df366e83f85580e6e3af8f6af2c5fa162b012753a07260e66edcc249ff037cca1a45e90e890cf3ba91e23e8db5361375961b136e0c15b2de41005772b62beee0728c8d76eed5fc986ded5fd1a6b0aabbd55674813acbc9740056e9a77302ae3857e7b013d026bedb9651401f685407ac44373e79f041e7959e8a051f50811b1c074765671bf76a5354a62f4cb04f92d3cac180ecf32b938fed08b838284c242d524e45c550ac8276b1c9926d383ee122ee426234c5cdd3d988a0d2e268c0ea04d44139d9b5dafc8dc5787e6d8d0be60e0f272d14c9f49b579acaa26c647114412d0137c5d76048e27cce00e94e9177f5e711253af4175e4bc9fcda2819e571af9dfba370b940c1b547791468053391f8ae48d11f6d8dd6c3fafa56a05c5a5ba07676035e3e8ce09134d38a234b35f2309fb8c72f3b5203f1bcc5653b93ebd1695df260fc50d0c708b56d957f14daab4b501abcf93226962da1c37f999e8f4246bbab50d430ceb7880973164905a34e5cfdf9b97cbc9e8800ee905319d4e6aa9be3bf95e99711a917874eaaf5b8141a54ddb5f2d54f1422303b29590b20b8f2bc679c1fcc13cfd71d447356dc21956ffa555d5b20bf2d66fa3285c48c521193d3d3e342c06569c2e30c2abd080291d603a4919b58512122144230a32d059750893464d46d5db913be9a39789184a3857d7b3577ba60bce2d396eb599ec6c6188ad1e230b733e6b364ccb874249c7ff28e38b5f2b9bf78d17c41f389395068b1970a4faa0e457e18bf204639455ef5b723c29584f3ce1404d0ae79cbac6294005a15e7799a337f56ec91ced62c5a7ca8233675691b24f2d25bbebbb5a09f3d4a7f3ad0084c7e7a063db46c378a08615ca5a6bcb752bf801f177f51dbac2a9a8bf92c79228933dfe600a04747cc7790ddeb76191f4a74494fa9f599d97d47f2c78d96bab8f8d91d4176cee70bb63c3733ee01be93d9b0ffb07e65c604b8c1ae06a659a83957b46c8324b640750ac07b2287ff476adb0407bec6fec116b341164d2c6b5dc3a2a4c3d68e2ed98bab648d407e7a57550b7a8c6f7ded1f91a47e239d2a0ed0609816272e9a7708599918828b001fb06ad873135c5de9b1dd749046323b8ac3cbb5c03968445a07972fdcacc603dd43a4da3608e211374b10615b30f0bf8b8df63118dc69318ef005a34573c18f0841dc26b7d6b3e916f6ead42c6ab2bbf472a652f9c553d6cf9f72ead892340d43a8fd162f01627ebe9947613a9836a9ee7e71b232916240ff0ea3ef049000d2581af708a0fe64cd783ab7a8279e76273c0819608102c2ba8f99e1a677aacc8a29cd01f6e0e7ec11c61a2a63f9de6f80549f8b31171e6d44958777579f6b47cce09344cd0d05926ff1fde4eae6f7c86e9d9562c59177e056c7641404c473feec2e73f6770b8a483e51deb703f06bb267825c0799e71c0cf464a0648f12db8810350ce27ff2b07ae829f9c227dab0d2a72592966a9274a37ec5f74cbdb88fb448bee7cf8ad2a2b83b46bcd96f54131e8fd626da3177b5a12d8f714e65cae5340cd66f9151c7b657c75d98267554111746009b5c6853c050003f69a6f83bba63f9a399eede38499348631c92a5482525c331cda39471f55f593d310e07876b0bd369d39c19481284d0c86f6776a5be5e4123a9c1da709f73070a0365f2bde2db4b63c2420eb565d903f7640a8923abd9107c520e82d2997ef4b20157fadfc9e80003add0913ad738c5d36a4dae1d3de822cc46af8da451f8e9277440dd116d2b695b0ab20f2811f17a2e5d971790836beb8795e24bdb484e4dbfb408c1b1e19b0c4c744e5b64e6acf531466f6a25659c5b19c753b197ba69eeea6392038e052a2aad2a69a44715b5732b43364e192779d172b5d2040184288dd1d0afd254f638a484be3ff022496f6bd6d5eecb1331c8c17d43cbce265956f8776da21726a42d1f210d5f077f5562d5be969783272d5323506f48172cfd4d2390f551c5b9cafb7d50a49aa6a1707f5433adbb8cce5a4be66a1724e8312038dc8c3d01119197581b3fb768814028e895d102ddb382fcef18a12fbcab34689a88bdffab67da449eb6839757906867d7b881fff1fb59f262d18f2db9993b2dac916984513450a5dff02e712f05a810521445ce7abd242c46f58f3c6b60d6aa40cb467fe4a0f72c43523f853c5e90ed6b80833d735a163697d5aa57b7b8e9086c69a1fb9e356d56aeaec9f5acd98fe9406a94a704509b7fbe92ccda2f1457cf5abe120204f74904446b5edc52d86e43d34bc48a00ef340ee9d6042e27be0e9fb5bea00ce180af7888c7477e01c281bb9a2df2f3f5141d740d754f191a351a5a714aec2dc8015a2b841181535ba69c681549a96517778a51085900785348231c655a86eac2f26445b12ac7e584c63345aaa445785c7c9dfc2c1582a109965abd38a8a7a76dad2015c5c1d6a7708f1df96af59dc0034a4a5801613b9b117946b15d87da38c75973e0f5995fc90c88983223f857d7e95554b66495d13ca66beb3a9a120cb5f729207384abda08c059fcf62b9e110b3f3067a23ac720ec1e25041d9362060c6f85bba2c2dba7ddc3ced398a9cea48ae46e9441b76c7cbd3f50da0b8f5e8ad47295de128c370530e9fa4716bdf02a6fba741f0465615ca3f612c81657b30f56ddcb9d232887b6552d63406ab8a24f830314188b2e1182802e34cf10302b56167252822327243573b36c1ff714c254476ae51709bd2124dde64a6a2dfde9836e89e89eca2e365f94c671afb783f888e27ca0370a403f0ee33a6a713909d72047a39134eb381059f0f522d55e07aaa77f40f95ee598981c63da9d1679f2ab717442e3318554f2ae463244ec98dbfd7639bb04f9d9ad9ff8d85e7ffa20b1739b3b1ee5b3d1babe67ec616eac20edba1721058cd7f7c6b5f818e105bc3ae8f67397a3765095850722fa5f299fe324041a4ae842a4c9c0cd9e36e72bd69894e4ebb3e9f0e97c1f3116e93e36d6412cc7fe98c3750a19ed1e8f68a921df628792be91eb866ea1744990f6c90a22987e3e7d25611916638875f06d6f8bf29e43b799c298a2bae19d79975c8440cae8d56ddd52ab9a882043eb18de30017d9902e30faabfb93cd7b3392f3952ae32cc713a00f6ae0e214151c15003f5ceb7e41c7216f9bf77d3f66f18cdb69603061c8aa707dd8018ae438d7589c0819c958ae29f0e159490ba77315a3dc8f7c23e5833a7dcf24a9d27a9f5db8dfadbca814a527fc3a706c97ce745a3f052550b57e29ab57e2cbc6e5b0d81cc7af2dc47492605c7abdd4093ca44f6e0c80d1f6a18312893c41f318626356856690fb4bea2409ea811f3f1d77af16211847219461334155442d55d2fe0ab55d0514803af265728f18e1a1a22e9a790116d4f65b0290a77da953f058beaacccabd232cab919383e3ad4d31392fa0048b96d7575a9adb9b597759fb2148483c2efcca137ac6e19c0a6d4621316c82db7417b9d194118e00b856a88d5685628baac24eeedbecb6cbcc7be33dc8296c34f7d1d52cdef645b8cb54b949c04adf130d34416c02a0171a07065eeeb100f8bb697f6d735b0db5646e738c927b9e531a36fea6366d00043489b64c1f766b69ff1106e21f6b4266aed356670052bb379552ac9df692675012ec8caa94eaa2351b7b787ea4b9ab0954ea38696f2c2c3d94881e13764522f54422dc69e07a50b5a50ba8ba215dfd3c86ba1328eb3066519f2b8e822b3b5e083efd1888ab1c7f81f6ad2bcb0478857aa1fac5717b27887cc4c60ea525e0561dbab2885b36f7e78fd5e41511c69be2984bf3e4eb9222660d61bbee913297a7f4161f5949b144045482d18dbcb29d042e347e5b148c7a2ef8501bffb3fd31f83a4aa70a44c5640c31a3e6e7098ceb1aa06c453252ef9e6b6f7ace636503c4707eef13d200c2f24faf6735d1cc4f9090acfe46770e64af5fc7a3c3b628a2afb7f58de178f766e73046347aa765537ad5d23328ce2ec69651a8a7688cf85c80f51f2f623677a760a69e7f51b3d22f1def11eb20e157a8e165844576db6386001c5e568049f3d8aff8895c872344b043d2f6bd4093913ca5839b96350eb4c759250e2d002a20aa5b69db0227c8080611ff936bd05335ee80a2397c6c580b6f4f7e8c3c1cb744c2e8e13ce9a767f28c88bd3d24c502268243b17c750d99b9e8b5b8c12265ed8446460bca794cb94243db9d9b071e8a40ca481426b2d73b2c2380ce1e38202eed1cfc94e1000bcbcf5b12629438cad40bd647988fb29c5188ec5a052683064a9f0c8baf0db93278b9622778a57b9095a96b8aaa5480a3fda01ab28997fa4bb96b1a839f70ddbc91ee8a5ee7fb927318339e9265703beb05a30c8d161806f6f1d7ae854fa3fea92bd508e784ac378742503668a8ba348ae8e4864784eba21872abd3479329962ac928451b37c876170bd1ba73104ab4799e0a0ae692bbd4f5cb793ee522c0b23b4f1c27bfa7ab5c04f2402ca53a3226d38e4e9139f2067b0b96576d443a6c5ab3ed6721893f53ca40b09a6f5313c7b86c3ad494994289d800836e4e4684d85189271a63395a7154a16ce3266983c29a7a6310e4a669de184939ace1e68544499d384c206634f3ae0418b26307c4380e30cb907c536862c4eb911c1d7560890f7216c7af62825aa41928af1dda9248eec93f147f71f5970a92e44a56d6610ad02407ee09d52f7c46b8680b8abcd31b7b14f30eb89940ff5df211ac8aca75319ff286366cdeab4707c43b3edab2df8d0015930b54739d43f762678f96f450a344e05765acba2d2f8fcfc7ebdc548e3d9cd234dccc3be3b2ec6eb8f9281d4886b21161a67afee9826144b2204914b67b99d66a24927a86815fac22c42ba966904718867730cdab0a8467d2eb773a073b1ea14f24e0e502ccfb91922cdd2d246a8224739d015552e0b674de8bc5925116723150a2c79904e5ccc242b4ccdea5a5f707f93b0cca59d650ec6ea847c872e6b86319281b1ff6790b15140d87825cfd5f05e7bece02c90a895130e9acd6a42f141d35173a8c1d083fd04cf4098a4dd5275a9b8965939ce201341ce40b4c92576bace0a794e9ba1a2f2ac727cb12c6139f53ab722fa54f8a978c655eaf05d2ba65ac9a3a07928311811d7e043a293252b75ca6ecfa7c3b40fb9158ebbe85d5d42213a90ac9a108d786e3e6ca4f1353744835c582317a692d4d488351a7dfeaf784459d6f1f1d8eadb7235db1ce0e26c31ab6c753f0ac1959de8b773e6ed5e92c8fe56fdbb974f9cb293d6b8f538314e11a22cb73f4e05637f1ef6bfb870fb1e718721cdaded75c0f60e3e879bce79ac02081299315c51563f3688813cff3fadaed753af7f5623f3c28691aa73035abed7ae5b65bd78dd91eb799495167e73b986e0e308d01d5a101ebb26f354f594e6ad5be5c5935a8697c82a10d9ec397610932df99f1c36707c2f0e720d07a1b0986c50e3f2eac5307da0b25e41301f57b77d87d38f9096a3f11a5e565ca5416cc8352d1665c1dbd60f602d8e7cdff2211c0867271433fd2d81385d4bd26a7c36770f7f21dd8d7740bdf0b1d8a66fe5ab462776a38aee1bce8143f1fd1b833599b68041421868ddee8a7eafa9dc9873466ebff2ff4ff86a695668e85d38813a1526c022e0e30558ee37d2782490677e1f809013a9f08270ee7a967cef0f40fc6df73116daa4cfd5d0f98e883ccce202dc030a22fa8b6ffe03708bf9c5d4f93221a21c58e5946000abd3fe19be044024037002187d03403a57a845df7b11f5a7c631eb2345d00035b10ab8ec5dc7a25e761da7c206cdad4eb0751a264efc595418a2f231cd05f66c28c1ff8eb13de2355ee73cf84a2c245589b67de53417bd97d02ec23efdd3f3d9dfbff79def4d59ba0c1be89eaa52b8573a5d2248b2ee48f96d941fcf1b69ba2c7c410f5879ad92f53ae8a4b4ac0e840c9d3d7b7fc2647350873c929bfde7d3e0d416652d0cd290ec051537a487e71ec5381b8cb0e71d9ebee5f24c91942603311f3b083e3f6ac9e599b08e7235b17f9b202ceae8fd1991981920aeb60170230d0c82542c7aa851bd6d5f6e4de0f0c00f8ecbfc9fc033fd016cbf873bc713dcd41e726be6d988732a520f6c7e87fb2620e8c1b979a4f6a0ff908e5ac3d7ac2467aeac5d291cba0080e20336b8b3d6040f350c8dc7a0c2ed975cd828eb5f8d70266bf55b0ba68cfd361a11548f40bcc23a076e45d55a4d8257c582c9d9e2e68d953d1209c3efd34a13c792d29b076b9e1bbc8c382658831c7106476fea6e4b47f5cfb8086475c000c99b0a24dd118632742da840c6f09a90281d095bbd4c11ad17d0fba02dd9a6063c9264d8104ab3415ce164f3d129b56d8e9151c4a16877f97a80c0e12c474451d49c94e2c0d25cf2a1427229409b62955d3c6c2cfc6161bccab71c4cb3b004eadfb270e232a89fc1bd50509f80cad6a864dd461cde7d87375c57f9020efdfbca65d597fe3010db5eded724d223d5ccdb09d5f319d7407c2f1ad601aa847fff5656cb651b9450c0990fa7520ac24f6b9d6ff3c70e49bf8ddf43043d1c4d0f4836ef7a457d5359d71c1b29f6e73ad1094c6e156a2bb2329ffa7fc621033fefe857ff5f3a6d07c5ed60ec9825a2a3bcb0f97fb0242a97b357394374685e848286d98983c51ab4e2387c06f101745d06edec828b93b293f91deccd76770a95bd6f796ac81473d4d201d90e2ef1805e3dec993fa3c8b5efabf3e583b26a16f4ad3d0e0599b052fe561fe44ac65d76315db9555581ede8d85d4e3625056da1a726e6d170d727800dd8741cb3c7e846a1285724d6106c27e5567e16ca50afb09024fcc60a06e1f032b2ff830926cc36a02e16bef1bab3c80e21fff01d62c7d44961073a1d9c5b3e7ba0bf2b8f87ec2385578662441177e26b23237b209330a91ee09d3e36f4fa3cd370e4d006235c67ee7a5a5847949d720b33057e6bfcaea86687702dfb837992d6decbfbcb85a18ff4a6d5eaa9000d497ec89a2901e1973761cedd1ed731a503132fc4df696b6cc8d967227cdbc957cc2a8ca6698473d6eb036ca7c1c44458590eafe3e6d8a9e1bb417a0422079d43185c7eeff34a4575ea8800aaed5ac341eda7360fabec8eea63ba02a17128f057ae638e2a232fd08bf0687ab3fc7c18f5ede8d353334dcff48e54b2847fa94e2e4fb0ea72c03b62da4ad2c36fadc38c9ee422c7a1ef2c3d1f8f7a410fa5973b4f53098416f7c827342a469c61a232900315632695c6236ab25690f7895fb010644d1de9853328bab20133aa7e3656b745951b0f27e8bbf0aceb2609fd259eae66a137f39e4ee13c200a03a18d8f309fab0c96a530b2640659c87b7c62eb7ed6544c8c8971b128609713cfbecf37439971b0f51529335791b4a74a6d8ba706550a6d1b83ed9d20c0456f3befc31041c0ac8bedcf043f639f20f1a2af9ed6ba79c5afcf2454e09d41aaf102225e62265be3335ce5e122521033d97caee8d9c9be02fb1ebac1e27490d1868914acbef607fb318e5dd61ab18e4d9588772dd2c875ffa5542a3b1655bf891e674382a53c5fa1b912974e2481f51609914046b518c847a4fbf3e07df4e9d7af09908ea0edd29486fa910c160e2dee194f4afecd5822cccb8ac33879876ca20bf6972d19d691191577efcc4e83e2230257eb890b2d88a378a05386342aa192210517f81aa6141b2516a2d9b2722277244b95c6a5fef0a5365a4c5e71a1165a29a676fc9ed6e9f590d48ca9e622772be0117bb87bc3a7c9b46d60edbcbdde242abd321caa78d622172be4612d90f90690c30e29513c9f5cd3b8b1662a1a62fb2b721868887ad1ad0b7672143bb97890ef6052a0283f1939ee56680c919505d54d64794ed7b6acdd239df17b1dbbf075e53656bdf4e71a8ed04cead879adb1b110388b149013becab5ac919e0cbf84809e327dcb46e5142fbad4181732939232c4fe2d0920ab23204e3450b34d4febe5739940f069446349fcf6605d1016d006e89d1eb63c4bc060b26a0dee18f6b0c8dc2017731fb5766bbf6a5525def300412d182a965f3f136112389781eef335cbbe6536bd0a3f8017a82d7b352f668e9e15fa9d08ad44854e4d5c658e3bffa56ed922fcce83512206846ffe2e81aba0f0dad5ea7302666b69c622d4c0f2298ac03cc171726b2129d4da8b08c9a00d9149e4c398386043301c4d9a1327d8f066dd059ab21cc6885de68ba7661474473051b70002c83cc820420662eaa667a422e08f070afae8eee8ef244eff26a9b582f8d60b952b5ab50e3ebff6988c17a2ded6a10492f6dc0c381061b139ab09884f338878a1007ed01a19925502a259270bc33ee389e568507dd29f06348d471002443c25ecbc38481a3f13b8fc87db468c3209fb4166e753e9259c62c86fd8f4821e0c3ef8c9fbda9590757a01a1208e736a6715789a96c77a4b8caaa3cb6c0274a756e6c9da47154dea06a4171df4effa9f7d7108427658e3f47e310a6051dc1301e2a3ddd99f209310e954185dafed7a86474cb86b42d95cb16123dbc4cc81d151c1cb576962d6b0ed5c0f51c84d256dc22dd4950dec3b4057312a4c12cba40893419e20642163fbe4fce7e81ccf2410185eb8645bbf60204e199e4db7e3d023514056362ed7a155007e9c6d36dd678d16d7b3f063a403538ac1e8e51377d45e2acb4280c6f60b957f234e03c9c76d9b77442fb05dd56ce785a6082c4a061823ccfd75cc4cb433fc8928b6f4bd125ae3673958eb24f72950227080669d4d9f7d9886e1a61360e9344e438f60c9a879977d000c59b625341b817335fbabaf5f086b173f53f7195fe8b00fc4d929ec6da16d5d36db3a05ff7061e38396d32147a7a30c729511780e6d969d1fcd100f7b76c25a82c14a8d0cbf2829c80142ca585d121aee8117c8570970a515dfe368d62630fcb1ba816050ba06e3c00a2adef949865da7d77239bd193f3f548609d6d6aaa31402a1a437a2cdf4cbefc79dec828c9110d17791130454f5108d6e24abae5360285066f3261cedb3eb42a3a82e10541e80950ab4143936a311519008ace6f998ed38a409b18643c8c26591a7068f4499d450653055084dcdaaa6fe7fa696ed76229f699eafcd679f313b44b925d2183d337554e847960e702d3b2c805f581851e4beddf805d911fbd7d000e9673e9f4d1b8f3205e5aefd965dd03185d9ed41dfd6f0589036cf86ea29fd74e81c1f93b82add3a382eb00e4c42c40861546ef42d1a2444d2d39e0dbab93775362d3d264834be72f8590597cc8f1e9d099edbe78c7ec7beac0be2f136ea56773a17668169f42304469a9142494f54d48f9757c532e156a0946063beef78c6a8bc63d748620b6571729729fbb7e977887bf699931b0bb969f8b8a68867d3113c76fddd399203f9282380bb8bbbc56b16e3744e06449aa8d85a936dbbdad1d5bc052bd7e26f99604937e8e14667c3054b96407373c34dd72f88e7a657356ed8960d1a9a2d99083883fe9828a5409ff31734f8967f87b44b43d09aadf09252718d254baba039010894d5b132005e75e71015e911fe9327d47bfabfe437fb456182d6c4738e03125b71352a4b4dec662e16d73e31fba812bb32874d3de46a4978d796534379c427842da77162c00d4144952c56a5bb055405896352d1b2adf58c03c90e933696253efeef0017eeda9a1ce8b0ff90c4b7d3fa0650ebbbe6adc2d2f5397ab0307409f5e4b362268d05d8ce064e2a992e65dab88fa2f6af3ac155ca767f540e5e1c8bc09cd4c2ba0ca0fef1c5fc850c5b7c433571417cd23442fefce8189eb2b6edd922fde5987ec8b2ab2ab353a05d7560d1eb70d5254279f20b01d35fe24eac42d7b3d0cd6564a049dcb5f41b6edfe176b0373919a839369dd784c0cb39c4330071a437edf84ad1ccb28e54a8aa1ea2e5067cefc8368ba475178e21d0799bc5da1634652013aeb597d0ec91bb5c11e2585dc8ec4bdd158a12b80b831c745e7c203b7ee6df24d438ea9472f53e90a6aa6e652439f163116f60515c7cb3df698d4f55d8da9a3b9b4965dd4d611f2d03ced09046f5090c09b5e6d91840722ca85e81c43a73f00aa69d2ced5e077d306255013564cbea0151fc2591546db68e43c62e83206b249a4fdf0e1c9165c62d5c0f8ddd0264208e0fed6f874ffe6af83c4a121ae5f8a61eff0f761101805b6ee27c4efa8b062635d5a654ca73b51bf7da782c2fd918f3a70ddc52002155928ddb525ff152aa61263422e9c8018d9c0673922edf037aed683d2f345f8810ba07254235e713dcf946806e7cecd4e9e905e9f0235aafcb829b849870b2d22a11b336322296af43d431c707bc199bb256961864dc71f2dd84e42684f114e7edfba5d949cd44d44b5a85a1434d69343bb78078e138f2fc7bf545568ab03224671365c7e2c912f062190c7d5fcd0d678e7354eadda1a8072544b114d0ec1f0d866fa0b77cad2e7d700d4dded135d82358933b78a30d6d26c93790fcd43220c13e24a2162210071975b1c6ff7b08e1e3657b2f9f3de4e98a0cea398bfcd5c95d3edf63e9227e55036335fc94f80c6427025b4817486ba98ec4e7ae223446442db2aa2d009a56af5082ee63e223bc2f80b8489c9e8231e113118a6ed51e83b8d8bd8dadddb0fd48bfa516985120fa82603402644aab0c861c317d7d66ae926d394126c2cb0623a2d78f5eac70d25eae9608625567999949533b91ceec59ec6de03531a0a039244b4b521d642792160dad23c8ab4b0c26a806b8b5d3e63d4bf84ea8c06db0033819aecad90088f408a44e10f107821de8ebc3a5591d8b8759792baeced1f2da4a4be3a76a839530533001e657dc0202c40815308f8959b734d15ef5bcfffb3b31277f43fda6304b8257f6ae05e56b0901114397e2c70e5164441475288529865e74cb622b117608f262ef40e89c88e7060a13c35fd0c4806a8b0e217bf6bd7c73925ba697787e2a258c16c9ed5c4d1c98ef3fc8bcb2eb1f908e57b223bc0d0b893a4997de3ea65fc5ddbfbeeea29a3a05ca6f9797ca45118e9b196f080a5cf2f4d6e34a70e53b5c5150f9e0a6d3588c3a8d20d4000adf65b6214e7fd7b8c45aaad26d4462390dbe57ff584516568129758003c7893074f02d3e860314a3ebef3fd48607f5442d42534cdd0d2976699463985349d15eee99a30cbaefa80a7b33bcc241f9d0afb717ce895c0c3d85a66518214c97bb60c8618f366dbdd1e87f91314390d96c3ed9f192e27e59cd5511c1712fc5011b170c1a7cbca4cf3c73de0f1048d535ecd0900d8da2b1b3162d28a8950a5d60144ccc41f9784a89f17615edeb15ee3e86b8bca3a34186d982fa93e6a1351e61668f64ebb4dd3fa03ef89bc5888272782a4a6eccd55c1f55a014664cd4dd6080b3ccbf8f408efc0d38d7e5a9bbb9ca80c91ffb4adde1990e46504237e5e2855f46ecca656b75b369f5331e4f696b5fc3c307040151ba1956068ecea8b48c1b1fcc310aa77f63176b894446a67a3024ac98f2ac136939196d465b064b854bd417cd62e6b64c870cbc72ace961deff82ae2a5f4a9c87de526c1be1b8540df3215d596d4c4a699019fa9e4e08115168cbcf60a78bc6b6d9535458cc6ce83a52e0c44d7e4bdb1c18654bf3977734d8f10a998122a748705029c3b1c04e51b162085b74ca99ed34c70a8ca09c8dbb38cb57e53b830d916c03c81a7100fcd72f1476e912ff9a297b8f1eb90aeff4956bc20e6cd69a49d084c126c10b53bc2bbd1d007caa94e54fae13c0bca1f539268a7101b0a62ea4aec3369dc0f5d2d884390daace39c6eb902dd63cf9d4af0453396d07c9745a2b56825e815543e0bdc16bd6ccb15e262c75bfd28132fc55db18aca7a213456037234a6c2fe0bab181f79f7dcc59edbbf9a046a0836f046174e81ebe370d43419c5f4e5b5a75dca10a313628d0c4cf1b652433151960ad261bdbaf6123af55994001047a6ad2c57ea692e3121a8f2fb4dc5a3800224be6911c3fa47515bdab7ac56d52624147daf230b5aae07b1ce36dab8c42b279f5ce83cebb45ad02b7e549324efe0a3856e31a3c50df6e52f1229f70fb8e76f72379d752228c04d6f2b4ea56f3e6a250acb829029b7055adaf8a321f61c99767771e4bd3e28249665d975d323e5171d7f1f0fe0e11874c4d6484112c21a5403299e61cd50f2524834af9cf1d97eb349d3865b648bb6d0b00bb370dd1e3d7993afe4dfdac6cb87af9bf484db333bf38f21a2b89addb251d21fa5108bde52eb2dd55e9c0878e088cbb07cb41840a0ec46447bb2c439ada44b923cc5390f240d41a9cc087c5cc2654e2280a58eec074c725fc78615dbac3b0cab585d6126132ac71d06ab48a15f88fcd3471c8a23b73ef807f588303011c35a5e3572bcee60a0f2b6d28cd6f99a2164fc57e4c6e488936593718ecfaae7d1ca323aa2aed62762026ef4e2345faab9dbb0cc21018ccfaa776dbfab8e2024b07da9363d4b4a6ae76f05a1ffa1eee839126054579e335c7938ad1a32459ac24c9c269980bace727c3480d583ef25b49b5796a74230ef98744297c6ca23c9a7b77956405b9ab10e77caf4e40ba634ddfca6375584b47b5a6f23be5be7a52234a0cca5b55c243936c4615b9b12db1447d5c93730d497fbc0b73005e9f439073932a47b9a2037a2de8ef0c5a58953274e9c4701a6cb6394d758b41713c2cfbf249dba39d94ed39787934b4442d70a0585f646c6fc294a10d16f31309cfcd9bf9a5e0d5664fb7f36ed61f50538700063fb1f129141cdbf1d77d250b99945935bfff34c41a8c7a6d3e2b3e699c635f0521873ea65a90e4b4fd932f4f37f6d7c60d131f9bafde8ae2dbdc1bb82457fb685fd1a1932328e7be02de5460e72bc6f51f8c2c0b440cafd76211329b8ff637f0e9791f680bc5996544e127b94f2be100143b2997bd1a54665c0916959b15269c6b199a2d70356bf338e50489a6d6a29f3093e69273febb56c1b475e853c906246718348ddd9bd7c200a1faeb546277ab6094e6da350b446581ebb840fbde4b06439f9110c4facf3cac35cf0915fa838f3f6744baaee1198767b2b791b1440d55b50ee21446541dc8d8dc7a979f5950949e65ed3553abad3c4227fcd54100d5d55e19acfbad05e373db54ccccd08ac34c7e50245613789d21a5b7ef0d61740016c701bd361f04769e444b4e76347201f0aa15d3580f4d3fbaa632c81e23b84049276b2e2b7e9d9640f599cfd2a508615349f2f9b3afde360b44094136eddc3b6b853604251de5b79bb4ca4862c21ec9e7ae701ca93208b091344a2946157ffd5bf279ea289c017d8ed7797e12871d793ecf11506346266e082817e52244734f12be0e10ad887efc68038598c6c3c8e72e9ccc70118460f5109857cfc354e7de7b4c06a1d8bfaf229a026cc035725d6345f20a0994f15040b0b9eee6295f247cf63cb4bd723349f01badd063870eec38da176be8bc5d634b5426550ecbd24802f640a22990c622b153fa91339b32d53323b41d83c4a138cb2015d1b2b89690051ee1b75d18794d5642c6e19db73d7cd9005b909af5851c1d3d386bd62da5664067f4bd3cdeea1d5a7d74bcdb7a7829647935066ea950d9d61f54baec474725a35863e62cf5fd28ded7284e2d2f13cf20e64024920837817fbf6f4c6c42d9b8ff6a38eed8ef8c6f0cbdfe0cb725cf65610985008756b51c4be24931033b3c72399f36880d7a180e71f863f5cb0b1f79a4ca1a1afb64bd82a06fab42e74279959c556615a397214ec9d8eac66640cc83ebc977d5bd4d5eec903cd832ec4e41159c313eaed9924f3194ab6be50bca36150cc6ace527f3fa1bfe4a71c1c15bd6f672adc1500885422678b6c0ad2ed3131c54c1a750fbf2a98315529d6e419c43e3f726ef25a0b1f7294f4ac68692240a194160ebfc4e30ebd8bd8475226f5312d36b6745076403718efb0e5acdac21ea5c56fb00d66e2db36ecb6a51d52ac1dd9ae0f8b9899211c30201c6e39ffcedce860699efac4b847603959d03b0cf00929d8038aeae90b60c0ddd096417edf4452da88734be0dc63207f88a6d3eaabfb17d6c074baa55bc601bf344c6399c35f8c3227eb11dbc1211f88e724caed10282d0593138d344c8be9dc34254175c162f4efd57a02c17d2029d1663d0d595e70071f1fd3f64cffac46477e085b7875ae3eecc11092df572735b41c1d58d00abd93222f46c906cae4690e095bd07590b39e9ef38facbc30ba698ebcb6c21718c5f4415dbf951ec04876dfee80624906976f580b1ff8b55b924ca9807e0126a7b3a0692cfa3c9f38dabb847028e1a1b00b9232090ace239f336f76ae06885102965afb3e05e12c3c73ecc47dbdf07cadc782a263c7d00daf4dfa1b8dc07aad9d7972143257354c0575e8394fa12f59d0161e7b5b616cd4e58a800561c040f77ef55824104cb66455727850e989e7209c796aa9b428425d1b8881c49edfc2a454e592e7166d391e618c9b034f0d1ba689b5128fe97ca064336d3fed300fa8085a38cb651d79031565e361c1bcd2c4db56c50eb7768e070978ea06db971888c3d9e2d379bc0ae7d212161d490d66bb6a5f813494bfcd8c3330ef187721a70651e4fba80e965310ffd16c30e7ea637f1e0a279fc64499ef324bcfb793ab02091091662844e3b347c87893d0d51ed1cb7256d0c2761f8ea8cd4dc04472f72d6633f5f466e84b473bcdd1c549b39825c65745a0027ba189746adb02b9eaae1a295963b2133ce02cfd6275d128d5f6d44179bc14d793c5ee13592cfd474f4b023db431189e9e1e104b5f2dd458a5668932f9641edb03967f5e5812d9fe38db315e10a463426a0f7af12432dc9151d3bfd0e8cd2916a3aa0587cb8b08c45ae3ea89f844372b9b2b83821ef59d4323a6004264549ae507271a975ff4de5a5fd38e09d7186e772ff02a77eefb78e5782f386467d726b4a79ca811636c1bf9b428827f3468a42fdd2e1be18e7054e913283937e5ee6969e962fb8eaf16ce6461acc0c2ba168ea03fabc523c7c74cd5be2dadb73833efe454f1da2788c7565f3d144b7fcdf2992f05660daf65a83c630235af927c694872d2af699dcd0a12eddadadbe56607d9c5f0d5a04e44a46fc62f16772ec63d6b042c3a29223ccfe6a6591271744f9ba66e6f576fa858d1d54168b068469bcbe0dc95d64a866c633ea6ee326dac00eefc13a21050575835e1c5d9a2964c96f700048c49e5766370f8b2e278d47045822689e9c28615d5194e45f51162e4ea9c00d278eac5ff0c152e121459e13e7983e257af138230058c846bb25bef4efb02924e923bb2a63d1e2545dc663c30f840450bf564887530c7fed372d63bd3d8be9c50cf23a3bcd28b6b53491573f65562d91007006a608644794f106fe2a2d54eb3ec1eba7ad5c7e9cbe486a3e03c529093bbe075121544424ae9e49268bd98fd0db63cc2126070e7465d7104da4c3320587b66d0d85fdcde8695443ac4edae06069e3b84f3d50d9dcdb7305e19cf6eb66efbeb53b34db7aae7fc913bafc702b86be443286a86251754cadffe666d63e60e44cd06a24a2e43ecb214e4f64aca87ef9a547c7ab2533a9db8261b44520e5d7ca50f3c780e60918ef16d34fd255acb90ffb0c4df3e9bc4613857ff99f80e3d4df534ace8ba985350e6790c8b910deb6865aea1fee37c2fad45836223e204abaafc49c1537ab184ad7966f356ec603f3a1c56e23acaf34361bbde8da688ade465ef436a2a263a3accf46b9f585f48a07761a9b34361b5dd193c6a61b0fe9905b9f89ca9f7d246736d777c54462f0607dbe1e65d00162c2fb01010e81661c5c9766ca0025e9a5601007e44a78e99924f0c4cd2cf6b1d8c162870d84c033cfc287adfdf422dc12c59aa702c1fc8c7229a8fd7040b143a19fd3f3489b0dd5ae37c150e1327dc69c71e15b090c6be5870fb3efdeb2e366aa8ed487aee386e22c22568f928617809ef6962e33178331d362c84d70127fa1a67dbc706af2e86a325c9c7993bb1bd4c8bb724e7b1812a4347ef03ca9fda55a1af37b25052a55ea909a06e65c793404ba7461e0694d70532ad6c0b34bd12e2b37dcfbeebb1808643437bf1f5cc5de779edc6f90c03a60a0abc449fa0ed57b01debaaec35f3d1fd9c5547a52c9a93756383db200b6720b6b584a7d9bc98572d07cc28ef0f134b247429dc37a08928c7a0cb7440c56bf7fc18e6955791bd06d63d10e32e4d3a927164f80d42e835bc025a69db13fd639a58893bf31f10ffa50de49539ce71279950e40d9f0240c65e70bf9e218bce3f928ab839a995b633174930bb2bfb2448692d2d154b43ad628681e9164c49d3363b7bdecd6cf2c40f288c443a9073b957de9323bc81a0ad74d8c6f91d341b3d1326a92b3f80c17de671426a323f36516f96b977e4ceb24fb959a0669309c3e48ce1c15f9c14be1b2641f38e01268a34bb3d02e3c9efb1af10afd5a040596c9901e78cf71b3df29a318cf79bf17a8ee5d6b20898bdfdedf04b6e46a0c321816e56c409ba6e95e1e1e98973558fd8dd0fda9a826181662903c8f62895c72de69545389a83c22f4af29a13d5ee5263c9e78ded08897f8c6667fbba87810c726b165f3015ea696979f915d2af3fc7316de3d063a547ac6de935a40245de1c74793ba55efd508c589282aa241f5285b0477ca420f00d13b4a23e2371c4c56bbfd9a86249cb041607551224d6a5cfd6002829c649ba431c063630ec76ecf3b754022bc4a6793f3b34b50aec39310b9cff2a73c92e3a6899dd07af72cbf8e5fd4c62a8334e9a0ceaca8e20c8cda0b0379b90b34735a71bd1a85cc6c177cbdb8a9dde59209d2753f34998672b7ad0a7d03e5cbaf6e9002ab81211bc0d1555d187c5dfa5ba532bbf44e7301d5219473fa698aaeaf4e62f8611e51d4574b30fa3cbc7e9babde67ffa7c9c5e3a3b2d1da3f6e513ecba795e3e845c5bed4f6928b7000ad82879e89e9386bb182d84475fff1ed65140a5bf6798bb19684167cd1d431a7233cff02092429e964f28b9cd3a552c88d822595208e066e4b19e28d31ec173976806e9ee00888626840383ba224b459386842a498c52768516da67f351584df0974804daa3dbf53493f6cb94abe05ba3fa8462c21ee74c73feec4fcc4e8848363b149485a52df8434829082efa1c2600bd5eaea58db02d5329931555a7d61cd3cbfd9176f523f6a4caa748e20813409f749a679f12bc4e8262f9cf25a0b697e16d5a652e39c44b66bc99905e12f2f5dfe2a1223db2bdb92929a28bca8f4ddfa8d6e0af6f7591f0f4417e95c6dbbad13305a9f3ac76648552db3ec22c65b8950e6ad9add4b152523f347b910fb06079c98eff457823b0273a9c7eb41dd42a446e138ceafefa49c6993a011afd14dc4d838de178d360db38775b995cbf6e0985d84801a61d4368b3f7ce54d938bf8f4a03d056f5fc159fec0be370ca3f91b58a08862f1b43e4756977629ec299bba6a6c424a315204751878200d40d59aaedd3675c2a5d563971112a10507d5055808f8217a519fa985b8a389e3a6eb504495649d47442203b4042dcc8b597a3cadcc5c38b633f811614cfaf36c00a5e3b50e716375d076d0a226016d9c7f4bf4012a878b51aa63fda7ca6eef3fa65de9d11b24a090709876893a11edca16e369630c5d31669aaac6a75e3b07a9aae7d1a4ebc6817cdddcbdd07232297ec412017d69772718ee3b5d48dc984c27b59c0b45aeb6853257a6701266ea7d1e0f36d1d071383250eac584b1b8900ce50ab547350153d46e46e16e6fe4bb8dbd9e28a50e816513d097ef926402e5f4ee5ec25b474ba3c3710cc3a2a0cb8aa14bdf3ab4b8e0f7542ebe96f02cb83acd1416de14563ce51e0c6ed28aa7d010d89aae2f38fbec26ec1911df0bcb831b3b348bcdb22799c0e433c10721455ce9adf971086418e7b8cf545da25bb42bd029068c8e18382f37f4e02c77d6911f3106c1d2f4d4e7c97869a73989da34c1a65aed18e3b2dd9a302073971bf1f4fb5c4b91f5d84bfdc0b3ca6971319f482a7c7c7814473a0097ee27325bbfc8e4faac60b6a69d603e5f91b7683b107e1be1e7c7196f3fac07236527415e3bfb680fdb54f8a5be7e881f52548f0add84a9069e75c2b6d1e42b8897d3ce162188ada17fa884bb849504e76904101fb434254c110293f29502798f19b45f5a4591ff3fdf66bf29817bf22691dc9c8c73cc697d007a143935d096a110170b3e1916b11fe31ab99021cd4df5cd88cafcb127621bb8cfc08127d37c2b2668e31e53637a6ec517068f58016ecdcaa4c909838f0e86362c97ddcc01553c4fa8bfa9a4a1239ded5e4d483eb7f8107570be9f74702543cd2b79ac888e991555688b0dd93f07af3e2559bc59c3913955cb4ff5172cbf93cfa7c442410aa3818f8ea6fc4d7a7978466c310155995a58290735e572db6475eed3c24ccd2e9ad1f432020ec90f36cc5c3a729fd8844b492a40a43e4ee3f3fa583dce56a5424bda587112dec749aa069bfffa6a077d62972a42446dc034e808dd6481cabfe210862b71fc5de39e2c3de21123b9ddbc6307b51cf681e6cc230b82527653ad68dc5a5d09d55f8a17720766085dad6dff830285161f71140e3abd433d98980a29aa7617ee057f539be4593059b29d770a19ab07ba375b35fd3bddbfe90272cc52a69ab1b22e80bb899b06b11eab5ee56a45ecb9236abf6a7b0afefdb06cb882b33222c61604ac702c1c0e64df040b23d13d3c8ff4227e9abdbe92a9b07171982162b64d8c3983dfbb5221dd4b5766e424ed0da96baf28e494dc09cc5624d5e8518d2c413a83e2fca64b1fddf00011c407cc1bc2b950aa2f2fbe72dbe17b6726f1ccd903f090d04e20baa978dff5821c7a561cc0badccf6da66bb2d6c8e0b161d2f93d61736e4849e1818e57be3b3de124a8216d103076e238e180195a626d27c41839c87da86adc84670e5d0c3321c42bdd258f8002aa0f8e4298f16d009677c3584f7971d4ae1f28f40eaa7576f31508aeafd2c5afd74e686c9c1d241c99ee6b5d2b79de978407b7306140fb4f44522c5c328bae271fc31519f2efa2f8052892fbc6854e3e40154c092cbc9ee5fb2812e08876c00533b1c7b0e46f6997336de4b499947fb34af16c0270fed447b9a24ed03d438160f3cd58769afdd353177fe723f093992ec514f5e3a4d8cd9647894aa126c8f083dce19ec1420caefbcbce14527a498958b1f68f19f4e6fdf9a9c632c55e64c26cb88328a73fb4338bda042a00520c6f7aec4ff39bd5491960463a3a885024216702fa46cc895c3300b8061cc307fc37d8ed60fa048d9b94610211d8badf380d4cb3dcc259fefd306532627a2a42a2c8cea3cf22a3b6b535ca07a0ce639af211e65fe75f028d3e8b642628d1c79797e9566741900d71676077cb373edf92f08f0c863f2cfc8a6a3edc60baa9588e0eee0d0731fc6d9bd8c198b397a776a85e4b91993cbddda15972d0ab8b0350f438079a4ccfa76a5cf493e116daa6a9cba013749d3db109a7b65a2ce18a90bf1818df25a869ef893f40bbf1c0cf2d18be156b3e69df276a4eb4ae73213a87d9baa20baf19dba9b958c15da84a83f827df2a8a59a1effb9cee57c0ef4a0e1d0c516b4c75105569905d663596313aed57f63cf3220fc3316c9acf13f38e049e1eea07ed2f606a029a7047f9cd640b0f646c58a8b4a3b38b513539c6c8f6a2dd318f3f1e3ea64f0ea8c5347f93bd73877a611025c8fe65939c0459d5168a732e7dca2824e18a54febc419ce4afa5f26ae793107a10e62c3a5ff3308113c9c16cc63b3e3ceb5b1756eaa1407d5d228e4d26e33690cd295974b2568bdfa65877f2787e82e05c633df129b9e9ff2fbd27e0b04efbb63337e8487d7638fff6477761b3206c298fd0ca9b667e32efd3e946e5354f989921fdf3ce8272135da42b3aaad197a8310e9c9c466ea559574131a52fda2bbaa2893862d3b85d6288a786f60f61411efdfec4f9ddf54a11d27319225d27c4d842f48646b164b7040afac3c95930585d493a61c140804d3138be1beb16e846ac54baf978fc0a516b808c69bda81d1c8b445c2e39d8ec44402ce401c23cf401e2a3b32cc296ce64ce89a3df6db8f39f13eeedd9eb3abf24b2a136d1eed608218d1042f6de72ef3e0c740cab0c388f2eee759d4ae549248ce657c518bb35b3c9ee7ee6ca4c49b9992b2333f3644ade342eeb78eebb8b2fcf56e2ba8b1f3baf48a7a3ab79a92b5ddc75ff5e53f2be5f99f7ea5d1ddfbdebf43dc6debc38eb72f4c829eecc4b311edf186fee0d5bb237bdb737f3eeb1d311dfc9609dee6e98f11803565decbf34176544945948f96828b3cefa11904fb67928b377b484a46c739bcbbc75988b09d5a1b80d5562c9605d66168bc5756b75eef1765dd4e97ee978e4ef9db9774693f16e32ef8e6793ddcccccc67666632950a635d0eeff8aa2b55bb1cde6dde5dd575a6eacec03c64b6b9b4e1d1ba0dd769991bdeab61717ec02cb38f39f3e7fbbad8e9c8790dd7e92680f2d051522e3d3611d4844f2e3d76394ac78e2ff66339a4080000b03500000000000c2982b5bfb83d3964ae710068bf347ccfc371adf3e2adc7031b238c7fd724bb17bfab1cde6feee231dfcbdc388eebf426795ae417b90d1bd8078e673ce486e3f8109ddf78919dad391c7765e33b8f3b37eeca065e0de0c6f37bc0f11d1cc40dc7f18c8338c2c70dc7f120747ee37127762d6870e0ae5375f7f1660dcacc3ce4b28cf7e2f6f0d43800708e1a8e3f73c396fcab6ed892b3d3dc1e9971f6090353fcd2f1e3ff63f318cf16b747661b7058e335dcc595195f6d75961456eb8a701c57648619669081c362c9a041838dcd8cc78ec7e63a5d53a554eaba9aadbb1ef95fa783329d92638eb9eb72943e73efce2c41c2241bc9a5c733e149de2a1731c6b85eddb5e9dc1bbfe1e2780dd77b8d2bf3afcc5368fec8dcfbfb1bd7b90f37f07dc081e364a97ec3b7d8753de2ddfe5d5517e3f1caf0798455d01916c92befa57b973eb73b1ea6901d0fa547bcda9173ef31cfe03813b3066bfe1bb3ce2db273c32db293c3842a15b0318a12aae4cd857be9becbb83468d0f84bf7d1380d7cbb4ea552e16fdfe77933f7aeee714ec365eeea9e86e3a03a1ecf9bc1abfb1997815757a787e4996f9391b98ce73d7632325ed6a0c4df1702918374eefedd30cbf8b1935cb7be47cf53a91e3bcf535da58a99ea111fff3a8defea621e36675dc667e0d50e9bb36e73d6b71357e381a120e418a509a22cbb1296569e58f1b079eb2dfca3749baf78b07e94ce7acc72c6675c863264dc9a0fc9aaee864372f7ad737cf7dbe44ad7fb6e8c3759b766fefd5e99ef614b8edfcc37f33893e3ebfeab8ebb986b6e2873cf64d9fc371c9267bcbb413adb5c758374661ddf704896c1d2d123a7f86678a441e3dd0cff6e4ba6f1b025cf40e358c7c31434667836c3639783069ee13368b0e13ec39d477752c1b9136956c962570f6757f3cd72abaffe7dabebf46af58efb66b9effb3edbf1445c246b90bb69f5d54b2f973ee96964d463ce78a9f5aafe3263380e77472bfe8719beab1b7128b3f7cdbb211d9a47556c388fb29d3f4239cb429b43efc7da7fddbbc76883eafb3e1bf9fb8c98a3f3ef53f11bae0a15bfa1e2aa1be3cd0c1200327ff97b773c5f50eeee9c54701aa9916ccc69a468bb67e479d4ad903e99544e979f55bae572f9696566c6f358b0f0a9b93273aabb5cd54f1cf7991b16c9338f1dcf4cc7e3dd90662f66e55dee2b8efb514c4c4cccb79a1bbd19a4baf7d50d8be4d5b7c9d5acce9380fc4940c66ec5f7c51bced0e96fa5558880fc3d765dd7c99b801cdfddefa96fcda9aecc635724c7fbc3cd32780619c3fb77431ad9bbcef42ef7d59dc1b4a841499f3428df23da20b35c711c667905c6a1cd2b1ea3e971c51b29cc601eda9c7a8c39a19753dbb6b56c87d96e7e1df51b54ea8632afb8a18c99472155c1e2862d39c665b83fd8e43eddb6c78e07b58a97592853d961c186d2a7918e8ab66ddb3a100c71c21a45b3284b9f98d3442fb11ae5588f6ee0b8e1a19777021080bb7de7c6c8ca374696ce8d9175c3c5716fdc2ca80809009909cd5ca71b694a5924a1748be632e621183eb43d72e6d1449a2c30738fc121c8ddc3e15598fb27b134d284a26c73174d68926bfe3dd33c9309b1f8cc55dcb04856f1ad85e33a1dbfcca71c6d1b77f9af5b31ab7026548a98f2cc25c41b798e3331b1e15c4296f2f449b7662e4f8bbaa5e2dee5671384908eba15a3342128cb4f2a55b23cd7e9888f7951b9a84e2a79d565c7b322f5e190e6ef31971d0ffd3df5b1b84eebf4bd134a8392c77743ba02e788f87b4ceacea2209d59e069f40206eb20f87d9dcee34e4cc4ab01dcf80def01c77570103ac7f11b701047f8d0398e0771c36f3ceeb49846d3c8c52c9a46b3684299460dca9ab3b8e137dec30db8450f3a18077e8f1cf6c83fe0e41a16352c58b8f8cd63b7838bdb3cfb77db0508de17bfb161bd78ec7860dc17b799b05a9c050e6966f12dded0a68b2bf0761c2fb2133fbc1ac08de3b88f1bc781871cf1ddf071e3388e030f69a4c992bf818bec7c1f0e7be44f763ca9c72faef8e5e2b1dbe17bcdfbf1669f4711af7674fe706c90c5556771c1bbb8ed73fba741f916177cec7822ebb611b7858aba2569ff889ff9fbbeef47bcd09090114321912cb33871f23171c2a4890464fede4343310f830835d8430f5b28472981993fdc23b3387112db4983f2cd446661e244a873282806db47ba153652962c9320e9a06e8511292be925b1adcc9c223b12dfc55bdcd7b0481d67deb88c49b1a871815b605cc302dfc7893b7c84f31b2b1eb19cb2e2dbec78e4950663bec52e85c31e5962e9560c7e68f3f7d0cb371e82f9fb670e8e7fcf374ed60d5fcc577c8bb972c595494c6ccc439994e59506e563aed3386edc2e5b118af9fe25756bc5e5e595cee99d159d8ec6924a83926501292fadfc40d12d7959a55b973885439bfc3de686357c64ecf1d6efdf538f37c4b951c6efc61c5b682e8bcb4ec537169fcc5fea31379439e6db7775c4fc7d9b1c8b1bf3d80920e60f7befbeef3a62feb087c3f89987f3287ec553d78bf16e91bce206b9320b9c152163cc5c75c31a9467ae33b306676664643e8f66cefc992c19432fa6c3d1fbc54253663138b4d6939f3fff7e1ef18f87432f5b99ab7098651f0e6d96b138f43c999b40100747c66629333b1ed5e9575ce6aa8889f1baa5c2a1ccaacb7810c49935e6d16405b9b2eaab6faaeb5d8b0305ab7a6855d7e979e45d76de67f09177ce7b0a875e779d197365fe5d15f72ecc63c703ea2bf08af01966f55dde53753cab2b7f1daf109923fe0175191c16c9a8af767446a9705824abfeddf0f97bbc317edf75e6e71de6d1e3515da73d1cd2ec3de6df278425284926b12281909088ac147df17b188f70f2f77d0f71722caa9165d686def7ddd4e385b9d73324a38eba4e7b3766d5ed991975d9f1c04454eac6065b1e51e741e1acc196eb7413b5fc058759cb5d5aecaf9fc27337b4399c471995b5cc47bef00f36f409942f0c36a48d336a64a3e51048db5952c41e4159ca1043590957f216699db3ce06a794739e25852d92512d5952d84e87b6d11b0ec9d2c69b39bfdd962c270fd81453605a4651f4f2c02a9632a8066526969138b071daec0a5b8c36cc8794524a67cdb09a654a334c02d1c8a0380a66c7b22ca3f49396609b31c618e99c9d83c5cad32cb322bbe9176477db491d1001a4ee146ffa396038d5c8a2144f535884d239a5300528421305a51ca340b940672b509064f9688324400443124880a2841328f18421ecc4c892a61088dcaf4179be3ab135499d81edd7201adbaa03218da6dadddddd33c6ee4635d8ddbd0292a9132fb25f66d8d55863e67491d54fc9e4eecbd9d194b917bacaad619a66b5aa5934f164e2b81507acfcac2f19c7451b229542128c8e04a00945024842519424283902087288832921091a9832450c4c98c205a02954d050606b8e51a6204d39ca6884c9310a109810658a1046f37336bd62e9159be122f9862658c9c92cdf2c573b62c4f48aa577f62401f5426d49bd509160e5279ddd00a09f2d634439cfcd5f456c1b8c2c58f9ee4c061772f743292428ca413af75f3071634abb76c0ca7cd50e58f9964957a44f74e5b04cbbae16d41668a52ab3715401f126e99a39f5331b40dda9580158a85cad62580127936657ab1856604a9aad21b145d993d60bcb32cd6432994ca5d20b28a050dd9cf3a217bde69cf3a217bde6c49d694e596bad4d3f71283829ce8ca6b181ac5372a793b52d2e2f2f28542a0503a3ba542a5555a95a5c381bfb52eb8b28a45aa6ba1713ea04d3303027944db5b878def4bcf6525e0c0c8c4ae5793131dfb75a4d61c50a95caf36262be6fb59291513133f3cffff73e998ea75dd0c0769531df4a4666252323a34266858a994e47e7ec05142ceabf7de5bdf19ab13dd3e598f95674c88b693a9e14d8c6b3a6a6a66b6a640d66c18286a6a6a6450b172e40904517720556e62825043957ac141064c92d93bbb5e2117f451c64660e2877dff3c9fd8cc60e03134c28caddcf7d908926a0648e663bc9471a204282ac763c638f19c33fb0902cd32c2fb1531c0e0112e30dc541b2897b66beae5b233715d283dde4b6c960d35c73352595979452ca1916c9530a114e72d32bcc78e7e5dd700f964be71ea30dabedf2ab1e3945eec13d14902792fb40280e22b1cc7dfbdca5a1c41e730b99b94762c9252da4f8eac1f036459e3e3e447cf2fc0b27b6e7450dec8b1a5820ed937bb0e99335398b364984a20b808008fc40ff0d2ac92de50347729feb8cd21df294f2205f83cc3cdf749e8796d3025bf69ec8d15c26cb1e3b6b356b356b6ffab437951e3b1e6da2e6a433d3ac04c928827226857296655926a7e4ec54cb613faf5da958f11c32affe72f0326f9466f24a1214244e243d913bd9259d4892b40be4ba4d9bd257bc7eae601c759752a9b4852edea2d6efca4cc395400d66420d14b58084254b1be99f46d2495a49502fc9de4292caee2cfc211a81383db033cb9e4925272c32bf1e825289cc0a15cf6ae4189c1c7363700c8b189a989a98ec31df302882c0b0246734c8ac5940eac8b040040494334c763bb06e071a2e03d42d9967078f955e94ceb3e2e6dbeb0caf97312e7f7373195ed4c002093233ce65bc9ee2cc8043191887de7c7b0f9663e050869bedcf1b163227bd2b5e6fe9f2ca34985d022b9ebdc706f370d7e9174958eef52b5efa84e1cd0b7318577599f402e3a62e935e5e5e5c892593490d66331b9246c4a04c0ac51b4aa3ec0029d3db1335999d5eb924de648f9d94d9b116fa791707bfda28285d9cde95a42e28485bb878965bb0f08e2cafb84ecfeba2046625b004d20b71817bb0bce22d5ae643a924675f49a3a43995744ebcc25076b9442863d239339ed59c5d1265ab9cc52858ace4ec724ae7f44e764967c6e5ec598b679757341f1d35174088a02e470b2c64e6159fcf321a1c5a1638f4e6310e0f1e87387166c5a52a642e77bb1937e686366bd75bb57c359f82b9ab89736429ee2b1e3f5a9ee5f996a3f0aae51505799543e697f91617bc6ac13d58e65e4f90ab923c3b1eedf6763f8133f33c6bb3bf18b2f3a18c0e0e53949745a326b269d612b6b984986f3177554add95e603e6283c4475986bd7077d0a0ff14eef42091905232632878ddeb8d3ddcb0514dbd7a147b7eaba52152005fcd02d9aaf2f1c71f9baba9216897e699a761d346b3a38e4b095baeda79b7e92394caf3dcc7bf80483573e52471dc5bd763c6c3f61accb61525defa90bf398abc2be4dae5e2c3361d6b6609f7dca81dde5da75b11153ee3aeda2b9e8ccabcb81a9ae77ed9a324dd3b41ee81130261b326bda36396dc7f62ca439b06b12f730efa94e7f51ddb8839dbe9dece92db72da757da727b3823c7d09cdaaed3ef1cba731dc4ae61d8638751c9d88b1958ec3ab33bcd9a764b3037a5699aa6ddd26d685a942b5672f6aec484128cc8ddb51d25989b3abda8fbf0711d4a170707b6d4e9e8d143cc5178e5831e75141e62a3695ca73030366c742bd2ac7d4bd13458c25d8a763c64ef5e38edead023670af64703b43fc7ac611f0d5ed7c1e5e2d0e0f58de6d0d2a2c35e7bcb29cc8d3bf606e19d7ede20549fdf5a6e77b5a7ee90223ba9c76eb9bcd4727b971bb6a078e8aebd0085891c618fb0f5a9200d28bd845f903aa27bec7674ee30d8e0d56049fb751f3ca50e07e9ac3d769a06ac0dcef52de3ae6b0d43f32f1fde559f07ab604354be7ee82b3943f9409fc22b1f1e2a763dccc7e0950fd590d4552fb27344f7d3e949b61a6e79f71e4c7bcb4bda5d5eba0b0e32730b1632b3761d74e6a6dd86ddb5d3dc59c3749e2e77d376ee5b77bbad89f276d525a2a5a0085b28da3962eb2b3e503a348debf484d5609e7a911d1dba057387702f20820896d82102f301265cd9894d23ec2a59bb4ec35cf9020bec400aa11d9d89ba71a7843d75e34edc416538036271831825edce5100452f14d5212f3c91c3787442ea8295285e30ca61ccd2a74e4ae97574a602b88225b2a24ff61359318302f0025064c5ac43bcd921b2fa54c87d27f47350609f63942b587218a10019d9220d48051bdab0a1c30136abd3ad28edb36c593c43de88432fecf21e5e8e605dfef2ed2691f345b2bde1b37d774ff67293a636cadb752687d360966919ce7ab80ca0e78f34d260287d6cd7286c59b762c419d6d69b74ce392f4a29ed6efc991027b7ea254f19a534d6cac4267b66184ecc8951bbcfee7fc5ee3ffe2ddbf43fd9b6d8ad963debec5a6be51a9c75ce06637d1673329b6bed2c5e57fd55af5ab10b67995e27d8e29c73ceee8e15cc3544703efe316762178d08d6a0239bc726e5917bd249eba4ddb30bdba4349ebc1a22486f04204f1a11ac4187766b228d4c79e47979c5b0c600589d3cc030ac2485bd728cd2852308d185201cc95eb813e5388bf2ba90031e2304715d1002010f94240e05d2a02cc7a164688ec030ecfa801248701c5042051e5002c97565dd22c7284af4809a80522e38214a1750b04929b96085460e178aa0d3085bf581d73234336cdce430429912a954b99293c398e5892cb94fbba1f8416e2c94203716a690851d3cc98215723f071fe47e8f273e68018adc5740920392e47e04a26c410bb9698e5dff091b7da4719162439c7c8536dd0a6942ce66385cbf6eb303bcaeeb3ad773b88e73bdc6d5446cf2f2c63cb7b9c38ebe7cf46c8312ff8897a778c5a37f48bce28121109570c227d7671ad878d07a3ad872d05eba2b8cbb76eeaeb07398e4aea4dbb31b77b80de7907fa891af4b79571277560f4e567de905d94b3df44e86b3171ad84f23f9c413199f18ca4e4ca13ec1822c1f9f40929ff0b9b890e5312792206ba57546cc1402c962e858b0821831aa597d54868a42769128b63a2fbbefaab3cc3d728ace4472bf6f678d02536b79a234d6dc197655ac361d03152fb7137d39415b657bc3b696255a94b05db13cb33773c0c96a19a25e8d491b6d883457772c60bfc7705864932d866b42a1f5c26ac8828d4430c2951c8b48502587efb0794d542cbd4cf3a52ec795279e5d0b0f4ab49401278a28112c0a872e1c59b1dd0eb41fcbe518c5092472c8fdc009a18c8dc1663946712228879c1349ae905497907959c659941486a85ab08265384bf00015baa27060e2a08524956888c6c211ad81105940ca8a10223bd2ac88a459191694342be6d07a9d59ef5c0b4a32ebb672051564f9900687880e146951aee04344c4383c8125661856ba7a956394216ce022428c33c6f9f8897fc4497b62d7e5c939e79c3473ce396966ad95ce39a120d54a75885ab1469d00e02ce3c9b825d82ab10bbb2a16b5d8d2f32647554dccc932abd2d191afc7389d206cdd4884d862b768a457ad2d7f409afb190a17d0810e4e40033198a0bfcdcf994323099ca143db291bf81058f0241645a328c3ccea9032e38010054cdea65b33e3ac70d4449e566890e7dbe6d4042151e4b3d33b7dd9f1b42d326527d93821e4171a897e38146751d3089366ce390395431b2ca8d082242451aa80852856781225e702a0cd8c32ab5c20cf39ab2491270ad560144452e0c1142958c107619821075f4022620a2433c8c0cec4f1f1f1c1c073c8a6d801123a95b073ce6850822db02198e5147e70a6e064c357bd628c317e668a29c690e723bd902b772b467942508e1d5d1cf9e48adb49fddc845c2041660f96eb55e9b34cb7cc0206b40a5ba49142b2bea19039af76f91e2c632f9cd81735b04066909935bcda01247baebf9c5bcff57a7136be5d61585fda34ad1eab3d43f2f5a8d3b567e6eb52883092c3f8841e49968d6e9cf62dbea8828d52446085661cba15a584004aa6f43a748bbe47677846831a96b91e4e83407aea7b62bcc16990be470245c2ac620f4335486d1aa4b51fda54acde15566bad33622dd8b53f4465fa0dcb4ad85d95340ccb304dd3e4f50cc3640f962560bb75619c25450c24489c371432739c7fd1010b24e21e2cc7639fc77090992fbcca72c542668ea72c471aae76643e3e3e3962dae0125b73f2c26a95b4db5157d05960e20828c8ccf1094039468e519cf0842c7f75b783a4b807882cf10b7e54ec64585e180f92caeb959a80e3240e71b2a474ca7bdb95d2d7d739e79cde7561f26a27c8a31ccf3a8a4759c6232ad84a71a4619c93c69b496ba49961b84299ebf55ae34dc5932a60524ae99c3348ad18889683d44c29131327c71969a491abb3ce7adf609c6731f479d5ab567ce1648aeb29a55efc8f967a61af5776ea056cce593feb7f4c2a29957d1643f2726213af76fce0655ae4a22d95567abbd6f82183d2e4d8f990f3e21cbb15c53d349ce1dffc9ac416a30dd8452f3a29a53407cb326cb2e88c94ceda190822e06383d02c29a5320a9bfc1785bdc1a1cc37dfb6d8dfd732ee30ad56ab9b388efb3e190130705824c3f87543991f306f1fc3e290ce3bfbc2a17d8c1b18f7013d5f91cd8df9013d5f51bcf99ec4c0306c0d0c1b03c65dc5b8604c18f6d78df1ebba7ef3633ecb3730e66d8c0b9939060e61fc060b99190656f20535485fb2ec7858edd08ebf250dd24cbd60bf250dd6fc5312146df89ec49b998733af98f98a8fe84b819581510dda15d85066d665e0b81733b0e147943fa2a417512c51ccd2e930e5893fa278438f73657c850d2c9781ec90e132eee23a8d7365c0b1c19b8b74731b1b9b18df62e0150f188f711d301e03c70663bc48bee67c8c1bfa902f19ac0c180132c6cdcd75e6cd5ddde67e8771873448dfe28632b7f88b9a215ba3414a8f1aac39fdd6b2db7457df9306e9817c4fe20d7d0f119fccbac581ad798bcb8725a299e3c30cbdd0e6290389c7097146504a29a5945ef72f4cb0f33ebad5e2f4b3e63e803535c7b1e6f771d6fcd7992eaecc2d369a1be2b0b82198c3ef86debda1fd9db9615d71c39ac34f49a657f10d657a991bf6931c7e5fcc0d239177c388a4baa1f481b9a10c4a851f51289172f87d44a76fd121871e323670e816fea18d328fa9812349723dcd269ab94bf366a373e80e3d0ea5e3cc9c1251b3e8299d354a44333cc13e6601052cd80007449065c90ae8b71f2ea841fa1135484b4499be64445f3aa22f11d1978a32ec82bdc206b69443bac385339c7d4499fe33fa8e3ea2afe8850f8dc6e10af23c6d7bf783037cdca27c68102b61258c45e1838f1e3a481c1a04830d2793c9a45bcd7dbbb68b9e103969c24467163d2172329b34d893c9f653ef9ecc279406fbda9c73762daf23d2dbcd1b2a00e3e6f6ee16c993db5eba59d52e0e741ae58e2787b64103eae0c819d485e0f7884eb6e51b0e69de7862eec9dca264300c2b69736e316655e3b46bc808244982d05b14920695fc1c69b0e72c619396eef5c3228bd57ee6946ecdbf747779ac5184451de6d74f3fb376fe20987a38bbd269c00671e6cf9d327722b933c99d4131777a771a31871a049a4a1aecbf44d19eb7bd4f87b99ee47e777f6a84cfe1240ab3bc1d9d2814a3069be88926e744711c2ae4b245991e4dd774aafd684b1aec87d757490db606d460bf7435245a124d8916d4606b471a4cc28693e8277347ddd28ce4fe247ad29f54a20d31f79c134ab768ccd9e9c11226387842054e60c1ceace20211444185221021053bd30a4d7c604082241ca052829d0268e1064da284a0094b886267ce1d4f46ba082151edd22f2e477d661d4f0aa561d74a2d9737f438abbd05eb91b9054b83d7ccf279c0c6cbd276cc6259ba457dba558d748bfbc98e5ce7384e6251d8982d58cb228b85c10ef7d3afcf6062b93be9561864e6d3bbae0a7578498397d00957234025d8f0072eb78475491f2db1a7f711908d79d523ac49f2f53e6ab012d5a23a549bd4245549b7ba5faf41dd32a152ad253607b1150a991975975faf3c8836f460198543d485ccfcf28229936eb9e0f83a24ade43ad4e0757d125d9f45d7e7d0f5d9e47acbcd4e17c7746d401a4fcb38d4d54443358ab0f2d85037c1809264f99fa20632ea241d64a585927a084b1d4a9a39b1e5cc892d9f64a522211d1dd5a25889f205745464254936c1e4afa12617d0ac4946498a8088e4cf1016a1a4202b40473f46a8a4131579f9646924cbc70b28cbeb08965b54967caf64f9ad4291236e1a797a095bab542a413327b6063473e64fb37ce4717040f0f73c6baf20a02c359f2c97e47036994838a02c8fcc1f23b2a3b021972514f92f60b0f29c1624844d66734e2a7b0a94ee29e96d2872ce196b956d9424528c095ba409abec492bcd686696690dc5680a2683759e77695a495e286b6bfdc64169a39ed247218d8cf4daf04c4cd339b11c0d8b204b1b8a1314b65bd4444343136d887508cb0116020b93a3940cf0a0f40323b4246c4b8e5232700227ec29472919383203cf6699b55936b38513c86436e32c50b36292a0151e4e66240a402b9c1a38336a4031020a1315d6485109be220cc918b18108628e60b4328202af89a1cfc80f11aaa3a1180c5809829912e461208a0952484052618007a8258c603080c4102f46495218502202972849280c7800042d472f194e184060339b7136095894e0e36271322a10713ad2ac98b3236956cc56091f301935b14040d2259d2e9004101785c874011588609b52d44d11543a0210f704128d094d362424c996482ae9a0045813479a0552a0e492629449b1c010403998e0230508721861c80a1139a2fc08a10539980892e2a38222722c618a2c2dc87240c162696eb92ccbac274395dfc400bbfabab0e650323254a11102511765072aa12986b8c9aa188cc106b9bb1b8a4d9679fa10f3751a5b2b1e357b7d5cf1a031b6e28d88acc8243a894f2201b040893699de1fea7ce2f3a0f5b2eb34ebbe94bfae1a4640c958ef73268532cb0980e548644524fda324b22201b02c89ac9824868d524a29a594d258b596b7e07002d995b9bb1dfa19cd4188d6f216bcc25eb36f8aed6ff5b2b3e55b4bcb75f45b5e246b59a665d90dd2f984679031ba6bd24783f240b86f3d1c0ed283e5ed326f38ec1dfa4266865718de517124aa3c8809c10ff70b884090b880066d481c4cc547b3644e0eed23dad02381b0864dce01680eb080032c600a413372321b4eb0612cf241be63951e94436115c3ae08052024b9a3151391143fc861bc2285929a069248721fbb2a074e6143cf53004fadb556f00936a4a1e91c54b3644fe7f99ece14bfd881959f3702f1462a01ac11822157c3c6f6516d738344f28c51de186fe47764652492eb55eb95dd1e0a10c9170e27407798bf502d50028b11b980cea93159f4a62db43103c72602d186fa781a4aa573e89cac59f4110b367b89d3bebd70a617d33993c9f462baab1793e9e5c505e3c1b0969b830eddea1e6e386c39dc1e90467adda25ecf1bec745aa6b39d7dc761b7d793e9ec662faa41ba9ddb4c9dc9dace5e20d6da6316ffe89e65ccdeee3874eb74fa1c74a0a7ed6432e1b08b9da7031ba250ddda4eefc99ca65b1ba5b7e95669466c1067f7c590c5ae173bb0567230d8e26317f16a473c761d11d3d8dd550e31c7eb8839e2fa2259763a7668728a2cbb495d322e3668d360c46162cecf1973ce3967cff5fe85641d6d106267b44188ce94b1af902cf72b15b2daf18a7f64b92fe583f46039622159ee39255024774f908ac106e7cce48a4a4a299d4479d2ccfa64b5a374ee4068967feadb816479c3ab1d250ca473e925cc436208ccbce241b5d8f5f0cc98182c06c3b81486a5b0542ab55da9d495baaeabb73fa0473e3ea01472376d2fddd9a621a5ad67e68d26730fe8919ee63d763d3c2e3037c53cf552eaa77ba5de73faf61e0a702792b70339e1201c7799b7d24bdf52a7eb8adbeaf4d55337e62d9e1ebb2d65c270e93962cefd3acc0db197beba3d5886f9f60dbb42a69099630e7353e76e0fd683e5d333d536b90b03491de33090f8186cc27a80c8272029dc83e513bebefd450d2c573a67c29ccb9c2edf68fde9a2bc7832954e26d4556f51a1502ad45f5e50cff28b0bea9c45c94ed5a95287c142664ee110857bb0ecf2172c6466175b2a6d0f4bb2547a8f0d088652a65b320dca1e9a06b9edf66c3f99bac71b79baa1cd59ee30b7bdf4ea65dc76575c69bbd5041748a9542a699acc3a62ce1a5c4919234da31fe2ac7c6459731b765dd875d18b5ef49a76551c66329b4788a60e7b97cd1c8a573eb8d3391744b021a5382b1fa6eef55af76c296ddfd834764c257c8f98df2e3cb5d2e77b30f9d25be220339770b8e5faad4e297163bfe44eecb0cf89619f189e7d65e5838b4da39eab2b1f9de9dd8becc46e451377e34e87f545832dd268f598b63ddb7049d3b29776681afe91ddc5e55b76439cacb95cc35b368fbaf4db8c6e81e06f6d96956e53ba38546ce9a18d0e1c3c238c545a5eebb9aef1d8f9a078e5033564b2265e4d3c64fee54576b2c9aa9969935b8d398f28fdd25e63e670cdd2e29434a59b5ddae07c0933ae46ae49426d46b7360dcbee75d48d3b98cb0d7fc0c1b9cd8c97b4af4aa52d4a0124839a1455b924d72a99e45a9fe4fad2d60abd05d42ba05b1503b9d66d5a79a459f51ea069d4bbe8c101ae2b52c97e806e69da6d5193a37669977669b4e3d1367c7d3b926e95300470f28febdbebb54bbba10d8ef6a8653768379c91717258242873dcc31e39c3a51be26499b59ef787914ace019a550f6073602395bc71af59e6d262e591d25d6e68432f67b7377c08e64c1e89d9248ffc8093b1efc82312a85bdd2b86facbcb2b96479a467dbd8b97bb427544b0a573dbebc6ca608e2d4a3539e668a83b44e30015110c6187088c0b4c30851dd4bbe3d97e63d328e130cbaed71b993ab5c4162f650824eea8a50c51246508a22c31308104e9c1725fe6186fb267558a086628a05b61b492e7fc0f0ee8960c0114791e87393190432ef3b021647a4d6c36b3cc5a6fc8264d245240004596c9510a088cf2768abf12885e899443da23b39049e32fa744c2caf5db49e66d93d995d755b1796c9b3ce8fb3ae87bc88be498c9ab336b8cf47587d9780a6d52892598308a81141218c921953282244e72394ac97a04537248a58ce04996a7b808182960620c475c72fc01125c809123136350a2e39079840e78a068c84c939dd3e910d390d35b7488d0f0012492b0636fba44a215b61085a01dd35b2e9117a21d8b8fe87eba445244ec98300e4da3bf6d4db4fc25b1b32f6c66d78165b2746de0c0712fc99bb26c52ca1f092c165d8c2e966593f9ecfe909f3ab4e3158fecd875641a0ee5851c7b91dc3dbbdd65c4a530880d1a52d8bbaaa7b9bebb2b1f3eccd885a31c330e0de2a0434a20a40f4d12368823e68368b9bd0947eca3c5e21729b0a1fc09bd9923270e2f71086614176c182f7d6a6489c322f353fe34f8ad84430b044e3dc166df6892b071b2ba1e40b4bc4e0381617740831d46287e40c286b1c88a2644d66101d18698b3ccf3e617c0d08126b2f8e00b4d50d991f7624e4d165c1085941d0ca1842576525058c288139e7c20044eecf0d0440e68b0a404265802113b5202c91f3903917d0e659398a026b965c87d49347ffc303104e429ee98c0b0c51ca911528810924244122944203131408a0786a40801254729423889528448225f394a11c288cc0528534a8dfc9860ee907922337660aeba2b1faab70a06cb2b4d836ef5c626628e248a8d94a9340203021bb6510e108da5ca8421c2c6bc4d1f8b448e51ac58c932171d4922f9c488a8d65a91aee04c37cacdaedea75b61fd3220b860ebb1631f72faa9e5662a683748f35091573bea312c7d1a945d766b14845a3946810296dc46ddea6efa9565195604a429946658d0697c249577adb50675eb6ac91736b978c2862d44b96d481fe6e22338eea58a8f4e31a785228bfe25d5d2423459f42d8fb1856ad49566d1a3b46c0893a6a49094e9a74fb7e6dca1d732a1a1505ee99f2c8b7624530f64724e30d3f7118dd734d2ade94323e9c8ca154a3f7f3aa777e833ec1afaa16f239a09bdaeebfae9568c22052699ce649a8485b9eaa12492c1ac6050742b6c28999e5e2dd442d18698691bbd14d9b07f905238cc70683b09730cab2e8c8962a5a36e3873d842dd44479129f7925462c3270a579a4acb55d7aaba2a60c003a19d4791c2921ca34421490e658e51a460244ba24d0841487d654e23f2488314b4911dc8a19c02d6eb9247ba555b6e3abd34d2ad96d3cb9f6ed9d3b56fa69bdd140ec9406858bbf46990b65cbb2d3fb5f4514b9006690b6e237b7b0a141b4aa04c2550f71f44329a4d78fb11a473f62a33767fd41cb575323d763c4ea65f1a100dd2eb576759bdb14120563cba05e4ee51fa642b9c190de278a18d3a27120dd1aed2499de508a55060928d523587d3a7ee84f327d3635745924fe8aa860dcd474ec954e3400e25954cb51f49645403c98ad151056288f4a934f7257204f7abe3293dee6c573e9145128a34da326ebbecb8cf8e87cd1c9645a52b89e493243ba55bd745afb0ab64ba812b0a2d9cc003576ee0e4270c3b7509222758200831084306bcb0531f637d164bb4c59055d7c5912320d170824f61c336eac1019345bfea41f5be0a0f491dd921a2a2073baa37cd103b44a40ca6b0d327326347f5d8f5a080c9a21fb2aa573deeac7a88f97c0c1e12b790022b0ced106924a61051c48eea130f2152820210a610851d153e02bb87874c131479a1c9ce10227d22b109507081949dc6475cd769f9446a57429139ac76e8d02095443290c4221a4620897af868d0060c5615a45b15af7c68903ee60334127d5ba1ef233a5f862094b4d3d76917979e410a5bac618da0143d2172228da0143de19ca066602f269896d4adce39710848da11957aad8a66a573625eaf5da92eb95ecbd2ad2885033fb9bee45332525ffa69eca253e46e1b9b06ab4ca61ccce90f34a04d0c2258fa9006db3458b92946a69b4cd75f2c614fdc9406c32b49339a399c9366d50b5f4d5a5e4f8fdd9392066be9dcd5a459f515875966ade71dbcaea45c9b3458eb53536c7831c9f59452ad4abca91a5283f5e53495a28749a94eb1c3a8522ff7013d1a957853b376146fb429301826f5eba1069352c1dc950a86c2dc6a02d5cb0b4c2aa43030982362d22dcce51b8e95503392a7d350ae34444e661326b90e2d6910ca131b0dd68ad360a562bdc78e076649b79afb3215ac6634851aac9f46f45c82e792066bc839c1d9b6ebc270fdf52f8824991ebb5710487252c670180450a6475d67c6a06ed18aaf250dd65ff80a6af04ad25db84683b573cc45dd900ae57aef8617e5b06042b9bee5869ce98632f974c33994ebe7922b89926e69afbf82ae25dd2a3d7b3d0df8e3fac464e7eb9a4c97c974994ce74e4de73800944b4ffd0a22610e834321de7bb00c73154e83301cbd30b4e3a1f4d4378ceb4e0777d3e9351d15041ba2509eccbb75c2ae140ccc5b85dd0bc334f51ca8bfdca26e68ff1262426676394eb7b80629b546f4267ae1ac3e877a9cfa1af5a5ab1db536e56a460dd6b35660431c27b91ea75b339c742b3c21c111714f8cb429da51b72e4c4af9d3508c14b61c5ca633b01de3157ce63ba849b79e583902254f2a450340c8612fc9b387343094c376926736caf395c63e328128e5b2c49dc436b1b1c119914662c992932449526ff692f950a321996e282966d4c0e260c34683335e7ce0053bafd3b373dbe09c86cb6a60c3d3d8e0681ac8f3366a6402074484408a1e1c710128458a78c2101640220461400210a5494c1c3ac10669124626ed393f73981184dea26a34382f4aeb2f2cf3ae13c954dad0c418b2b3da99813c2f536badd56405300c31032d0055c1c80e2d020e9c24d18223357882ca0ea5f471027548d824b745066c91267ce193e9af5c1ba55ce7d888a759a956ee3afd31c18614090464f28f79ee1b55d2ad9658c32e291575ab5b92bef4844a0132ca510a10510e4b394a013a924b4f3ac7c81131324e5f9a19f71a54cf21f32b3684198109614a821a64810d4b3f51d8f0236a9229d6a45946305043cce95504758be6f42a96a810ead6fd0aabf21d750b9f7a9e858939d893c8a287f9d5ed90af15af78c8908183cc1c53639ec30eef2f3e3b9e1738ec9171efb4eb91483e3e3e5906fe96c45cb00936fc8cc8b8f7fa987f9311f342c65dbdf064c8b8d504de9501c4f3bc4b4faaea63645cc8cc3270a8ba0abfa086977e660e256a167d9659eb792ffd645ac2aa64fad3176c48899e647a1659eca9490e4b44ac1dfaee48b73050b72e926ea992744a54aa7b5ef530456a909e1e3548dffd74abcef01a455818b7301e66d9c27868b385f1d0cbb6e63162c488f18f68d6a8818303827f871fd10adfdcdcfcc6c6c6c6e6f562491aa4ac16ac16ac875966bd45e865196478b90c29542a43a58e3ad62463493025ddfa4e8f05614bbac5e2f49810660436d4ad99531a704e4f8b649cae4e4fe9e951b7644e4fa974ebc55d4e4fab742be6f4d44ab7569cbe868bef455def769506b76f1ad772b78de399f9e6e2c5757a8b8991b192616f5155c6558f9d4e579587735df61e0ccf7a55e64aafe46dfe98fbfc7617b7911adc2eb76bb7a16870fbcceda406b7eda806b76fc7b9b4c1ed322ed7e0b65de67a0d6e7f71651adcee72dfe0f6984bd3e0f615176c70bb6e5863c695c9e147147e59323d0db73659f9d4a34c2fc30d5741999e75c38a25af88323d78c32b49a6b7b9e10a29d32699fee686323e99bee686d751a68f714399a04c0f43862883392c11adb0d7cfdb6d930675ab052f11a24674ab3b3d1da24ca893d93f844166b63fc5e86c0fe3d79d57f7794fb744b2e9b1d3e918b7479e82cc6cba89e6d2d5c1d4e11c388e520a234610434130c9d3de1e22154986711be3f6c86c0fe3da4d07196b73f0d9b26ce1416fc088ee96bfb88c8bf39afbe22e6ecc47827af468b05b540619677dfa348bcae4f91c7afcc0ead0e3c2c1e88736caf33670a862e7431b3a387c466464be615cdc899f114aba157e4d3e27dd5271faaf49b764ee0c5cc933394ac90012f269de36ee5322d3875d8e9f51a6df0f228bfa5414e42845035632cde14784a45b619cf215752ba44872928fa85b518a0684724b7e5032fd76cd3927eb2175c921cd252ec619e33168f88c83378c3fc860d6ed30efe21f936eb938fd37d42d23ba35bf23ad8737683ca491e74d9e3579aac833d5e5f91a73668dcfd36843ce270e8d348833c5ce879f914c7ff077a4c68d0dc6c0618ce7dc186fc0631d0fdf924f23f0e05b38ac019e060e71c09f7008822fe1f0e04d20485fea72cc70f0dbc671b2d361732b3b1d37bf3eeff51837a439c661dc506618353d58b63536c60dd219c675e60c183766179786196ef843fc418671f0373787cd2d789b4a833587716b3eafcdb5b8a168b0a66b5ef3979a979a979ac7ae795026841a42fd23aa39575373164737f28a2c50aa7f52ed936bde354f3d76cd91b29aae99d7c8ab6955cd55355e8d57e3d5dc036340107ccc3f23dfcf916ec55894e7d7f1302969b2853caf797d53e20dbdf06764f319a828a844cc36b54dcd32856844000100008314003028140c874462b158309e69d2543e14800d91a248704a1989a32488610819438c210018002300200003341306051442324d2529f5a03d4141d6bfd085c24feb17205815f2f86d88b218d8a7f87d6eeaa810bb0b1cf62256b06f8e7534a14f9651345c80221daba6628bae4d2f4c7e177d161227903856dc0a6e8fb6c724ea61f9e45ed960380ee090734fd2761ba4cb08049ac8167c2fa9e85eebc52e0b8fdc1f42d6e666a4d578f6b045c259292ba4afd95a860ba4962c49a9386a11ab059fe0e1a386d8441bb20995dbb219c4c58444998caea7bf8ff5f4f1d6266ee152e68280b42d10085483e70b99cefc4863ba1fa04bb67e6faff2e9542feb242b9031d8040a0a0cac5771d37fa44641db6e35791d8ea1b750da77ee5a1fdb6ffb1071d45b4dc5e0c0a5ae390aad21be9f82bcefd726b298ce9bcacbec478b4630f63312ea68d0eeea914e0ae743b571a788cc6c4d329a2fbd955ef7ce26a1098d0903a95bcf0b78ae82998d0aefaeece040cdc5eeb1d0979db9f831bfb47cb0b82bec77101c5c117d4e1818a8611d640f44b1db59b09522da5b61d128f15417ca33a7a622f0a58cfebf8688d202550eee0e7c05325b7395bf2bababa87a5a755ddf51e5f43904e738e12252ec9236980fd4131afe635e6e2f8b0cb07c3ea5b409bb3d4cccf3cb4b50c09e9e40db524da6018f0fe36bec5c2c6e96cc23ae3f06d661fa1b581716f14d7db95d40092f90bae277759789cad0b7c61b5c4effff3c5fe8e6190fc606cdf4821e727145c41c727201a872bada08e42ae7df22786d3ff0117bbe0197068f1ed71009d02d85eae0cb75ce53e50a3f02b29e6a176e54a7dc82d89c40fa1bb66a01f481ade033847e70b2c7d6ec18b14a1fcec61d062a9e81ab4e9afcc16b695dbd7518acc0ffedffccd77b5ab466592761b8984749299fd38e1c5d57ea7086d846765f2b8cf785fbce061b9f212b879874b66ff11ccec484f019362a0cf0eb21eb8488cdff5af8cc8a2cc3c0cf1f9e59bfc8cbf5ebfea59bb37cc22a10519e142b269fe917d1a3a302cdbb353568866a29d73dcc46e42059b2dc99ab4b54dfa5ad483d4df98472baa2dc094956605801701ebfa6a87e8cd0f75b298ce4b0a6823104ab27da3b866573f41759444a10d073db9e3492443245b9710f9986f63623f9e4a43b1b29966de2160f848448004ab95bf7abc6261215bb6f63b4fcaca52887132b09ab4d7161751b38425d54a044b8c6e484d527b6f1bc48807c0737740b981ae48c974e780214c267e6c301440a1d0a54dbdb839c28ffdfa7d6965060450c4bccd1e16ecd4396143c5ccc8981b2ed78b3b059b4de3bbf6151f608f3e072378604fcf137db8c92134c880a4af91d17afef3b46654d1c5deebbf7f79c8058743a29f112101c5fd756c55c1f1198a8d1483579c9c118b59310b16d387f0f2b8f2715b0b1ba99d1f0374d44f9331e52cdf155dcfa7ec08064eb544b4ef65d8b03037230c1f1184e3a56c0c1909983bbfbe24619afd8e0d796bb521d80ac70aab170de6dc245b95e8135e2aa2d0f5c83e08c2104413cc844a5ce2a1112c9883251c1b24b4f692bdd88809519f194d74a15b7a88146b02c83a63e38e78fad02e26779a1973da7a2925001c6026229db7f793d14b13039e49500d9b0f2681bc22951444be28520c93bfa668d9d39b081e8cb5e3edf7d7d488331953d40f059158095581542c3ebc940f4fbe56c5183016e1eaedc83fcf0aa752e44179e3051827de2c5268f8319f430b85a5f495a87c797e52c87ef8936d14cbd259e41575cff905b9f2b1d1c336382ff0c701bd06cd309de0edd243187ad0c12759e327f8c92b1d840b5726caf13e24e174dc15f140d8893cca7dc3dc5977154bd128d25066b2ad5d12993047e195bb6fc34ae6125d9b878529c7a49e137641745d4c34a66f11309d9bed703e348c777619cc2716ba73eb43152c24d82b8f613e6ea450e6fcbaedf27ba3e9d6ab05ad6772786e5620b981aae3c8f20a0a091a78892da34f5a2868bea3a4139a74dc17846c048ed067968c470641c482be8313eda016fbbd92f273c21ac3a1c8fdbc1e2db5fcb0403d6bfed45f57a561ac687eb92d4be748ce4745572b2b775257f49691d840e0378091118f60bfb8ac398902fcf6129ab453dd725655559d55cb7c426e4f76eb41f41e718bdebc89664ba2a68e778ae9d280c7f12f375e1bdbfee0f8cbe5b2573cf98f6d886bf3b823d5741e52800f495b86f16b021ababf39e58b245e1a71ecd937e9fd5cc7ec59f3f6b33b38c31636c15a7ddbd27f40cc960a7a0954441616f52e2449d3c663e64080e560d54b2d6cc76c7c081363977e84fb977059573d661f4c01a8168f73e2793728b63d81c637941bdde0603aa1d1083039223d71cf3820c49e294712463feecf5233dc3c8433cb0a5b7dfa4b5e58dc7b753c267256e6260660383bae545f5bf77a470fa8ed589be5c1fe4a7cfc0c3c962925be1ba79e46a59989f15978e4bb04604096081e51f3c4caacd98f368adbb7e02cccc736d65d5b08abca2288d8b0901b98cfd400056f193d28c35068ccbfd17644137727453e4842015914b4132f6812d21b25108168928fe0c4e9c830ded22db6bf43ff9758ede9fff3f28928d658256d9bd864eced434bbd77a35fe76e6562b67836f9f16df6bbb881dc55eeb9f06d1129c1d98ae1d5054e9e436e6e4566b51f12d5e86fa051a6db7bf98ce859d042d39083c31afe807b8ec4c429887541f5ad329a9c29dac059d06e3e673986c47001da3574da40cb6ae618994eedff8217cea5b7313778b709c54d4281daed036fa04ec48a7914b3cdee0658e5f3809b28bf433c0e1584003df4e46143e21c21f1352d0119976755a026ff4b7ba1c1be2c9693a14a13063cee3b3f30979fa05082d12de61424242e5c94ae3acabd67c1c6708e40ccc0604e1cbe372584d3d56e469c26d27945d05eb3ee617e88b31c05d1b47b7c84caa8a687de0a54f9a6e5dc3000358f506ea2e9571cf0141ace6d44c627b03245aa3d68445520b3b99ebbc7666bce7bd6b48d4f34c6bf3c5483dbe76d4d16b5b64500ff809355a5720d2b189378b21e32fe9fe511d7b6e29ce57882786956d4292e8cd79d54a54a3f78b6b291ff68b1c617d6040244626220b60a54df3e9b74c70a734ea7fe8c5917ef34afbd0687dbd3dcc5c514d798c4d2e5e9e611b0ffe6d2cc621bc244fe5d2c0f240a153cb1a5cd12940b6a5dd5ff91e4329b4a18552645ae83a9782a815c5ca69c9ac2868e74c6534386e916b3135aaf1dd453b25fa0cf7a9c4fb0d2f067fee8c24186b9e5e34b157a3fd4f64cbfc44fc5a305c9c120a14f189e46c7098b68c54ab08e9aef7f3ae39c689628a4c4217bca2b400914845c116e4107f9c6cd008903b695b62f8a5e2351eddf92c7718bd95481b40688cb8d20be12ee6ff008431d52ef3b3d4e627aa2b44296412be98889c3d6ceae9be866c54d500ce3be628977a88b1b0b553a56bc521cb0dbba8b866aff2a954224f4270222e631ebc3535f440b0f8d48b61ab3cad14fd5f2f745e3514d9b8212b9187bfd239a4986878af75d33e22a87a2e45944f7d08814da294eb74c9e940660564cd987c3678f20fc5260a8c0784720c91425364ca5f8978b9e59be3a7c3a2f6b319a778580c97a330e4c7eb92987e9ceedc92c892611bd0984fb8074a8ad32ab7b1781d2c50698c9eeefbd505274ab2fa98de0ab7c77c92e56ca84ded12f88770bc3b485603a76ddf11a5585e55f5f6bbf1bd4749165ca9af03a8de4413e6121827aa9c02e14cdb245b8bb2d2c1a6792fdd5a6bd1815635b3e7b8c11df46ab230814e98f4486895b192a4ac4c67ed31901a11e34a0f1acd88022e04274bcd8039f711265e469bdf6920abc8641a83504aff20124709b1a9b91b9a71edb10648c433181159c808ccc8e3db81311b69ad5bc3bbb9d5fb766f56b4a3de663536bd32dd05264cb84947ac017df3764bd942c9a5980e5eeb4c19601b98f1a19d4e200442c2c43e09912316a326e2985c956314660e925263a874057828532ffecae2399a06c328b02529a9e3cd86dc70755adc8e5c0c94ff385f3d62d7496f1d7f6ee19350082d88f4ae64f69e15d1572d5038276430e5531e4ca274fb8d64164fdf0638ac2e3b2228e70624f1474dd5b1ea046a5f604892744e5b5c81c222be5abee25d206487f4b5c86d26f2401bee494189e7ea5ca5c1a69cda0401606db481e62829e6d7ea6fcb1810391bb3b64564fbb6b47cfe36bbf327da2d78309fff4ecaec67abb99516142c825b9672d93229bf8a42660f6b029631fdb23aaf0e688d00145822074424adb40a5802e5fa95f3daebaf609a656838a56686c66d2df317331b4dd2c2496d75a5a79ed49c12ccff0e50b95b9382a89c9cd8d791c74cc115a967f4a252294daefdfd8fbf22deb57ab1fc609bbe307e9731a338bf595dbb9ce00420798f30fb277c463adb968b69941c07ddafa179edec3dc3740ebe9576a46a2b9c6262e5cd8548292dc66b9ea1f92f3d82195ccef3ca78002c3bc878fc6724cd0011724a1cb9035bee7c878badfdf235b7a6b68db832b201c36082c78457d2c3d7c4804ee5bc50d1029bc9313c0149d85cb685849ab3513fa2d98c861fe9c1fb290053add6e44efa002b31e2602faa5aec44b53f916fd9fb6aeb2fa9f199368b681ce3874666ad2bf1ee5007c1aef4a4d7af34c7d246d3555b9937cdf4207eff95ffa9a537e1b1daf557f2cf354a0e487cbee9c651ec5080459f2a2908496ac6b2779b83d65c9d49cd814c2df54040c72cdc9398d1e981dc2fc6a1de6956bf3353637befa80f5a56f48d7add5bb54ab6343a691c5e52f9490ad78a42b4330303ce6492848b42a4418c6e4ac4a10bad2d61c5af11369b482d6d305a541e6df8deb82ff2b6fc95fc7cf86b73c87103eca27df1b145db7aeef917b13a0c1e9b3bcf9be4577613fda3a96992e74de45a415bd5aecdfc38f836bbf975fa1bc49a01482bfa4b7bf78fdc056b094d6575d3f6d608fc51af88be5dc24a123377d5a6ad71fb71cc055819581db2ab37702b8008de6711a13e13a37a843ff5e14bbbb213deeeae090191c2e179198e65b03075041474f14f73ff3b939185019a28de657f40f2a2df9d5f1d918dd077ef865117513f07c37a220c5d9441ba008b51237b868ec0351b49078219b2222d452eeaf1697b6ef46232667592ed5255441e137e719dfcf32cd0d107cadf49948ebec58473290d163db267fa6d8ae19ba334751edcfdf62a96c9447bbd371edb15f7df5567b6eb0ad3e6093408d30b7a42293aa9a8588e3b7f6841be5603e324f298177cb1d9872d5b4dd9abf38d5004ffab4a45d0004ab11502125a97065f7aabec7ca8d2686197770370d459297bf28b53d4ecfe4a7d0b049f3ab5553b9c0798d7c4d21476b166aefb22337fc75393391f009b1078f415f0d7d85acd9c1ac8a0042f8a0b4b79cab3313d6f95babf684173c5314422b4adfb17607b044373d9397c0e4f178f0389578f3c3b1782a840ca5de7d2e5f05b6a245c4c80766cf4a9eecf332cc3adb8e3560a1d33cad477edc60ef27a58c1eb6d02bb0c43dce7b700c2af48be4915e481b3ad61b42add8af9cf1c46fa8325829d112050d6dbb61e0804688a9938072db33c15014edbb9f14788d1c9277cc0811c28c90d0687b77fe90c65044e823d0406d0e72bbb6a8fe04d5a1be4779d443c1e7cc6df0cbeaaae75162c12a2ea7a1c53a192c83f8c5217c631f51d5d25afe05f3206d36fb139eac943a29554468ac6dce920b03d27e69a3f0c07f7fd68faf669c5c1abc522421e29946511ac7763571666ef2305732045658ad4fcca0337b3745890a8c66a3feacb399414776883e48f7024f3f98b04055666df5e25ab68f8799603f2f9d8448b70073ee942b1901da82a1cca2f203461aa40fe6227b2ed4463f885009e4ef90758fa70e8a2a430078630039e6185553b8c509ea8c13150e9a08e586c4c30b24c60a43db32378dd9915eead39034100c0d1b15e7986110ce3d96aaf2f1761e257f490a9d65073be79ee72b5f678a40f980435d557a8614846c2924d47268084452eef28e76984fe72572f36f46f118fce5d5affba092952c563206f1b921c9f0b93ee68cdc73943914d6c408c3d01791c02e4137c1a00e586d63f28c434e526525c21a175c1bcd295301429d3e7ab7571f83c37a86c792010a1050f6084f00e07c1f31eaf3de435a47cad19bf8f89210949b3d01f57e3bdf9370f03d47bc1057574168ad043e727e9cc9c36bd7672a8d931d4ec2c06f84f7a5d90fce59f3c641a3d28e351534afefb74b2b24b31619534677784ee9bab47cf31e28cc538c6ec434ebabf3718f9776346ddf50dbbb31c1801adc872111f9ad1c58cb20f4a03b5066a95cd132487a132f49086f1a7f61e2ed9099809e30b77a1d84d6295a9b5ebef13a5b6f67e651e5f006b14612c3760d82af3e4903c821d42e340d9e8b751869885c598a82e03b1edc0bbfdf027e9ae074fb5b53e560747c3d6221bed4e07b4ace374bf2643cd0c15b50ec7a1785d22d6a442137b8be17e2ec5969c210bd6b76fb2ebb490f3c4ba21789cd2a58ef6ded3d808826806e05bf44ef7167093f07ca361bcd090184cf01ea964bac1222ff956d2a071544692259d2200ba8c37d74be9825c061c2f40c0f7016d85a3cdf41ecc46df249708921388aa76dc4ad673486aabb5f98a3fa94648902650cfa0805b3cb56eace43464adc299e49f311704b3842680885710244b39188a467480c69d2ec85075d449128ac7b2a8470dffe13580689be15a81be067aeb140d77765f506458ea23980d79c4e1263ba59954bb42735965f223944b8ddb0d01933bd97e41ab6336ba012d75cbbab16d07497d4aeb2c1924d42880c906e74a3540ee5f4df74acf60011a3160f74799064eac6ea7d9e6b086ef96f33a50975904d2824ec90bf7115d4fda1d7b2186ccbf35103ef4b5156f796712f0b1eea9197d1c23aea7477839b24b4df434e04462ecd4701faf2e1162ece7b4e096715543f27e66c2d09293e465d3062cb0d703696e55ef240e68c5165be38c874bbef8777d7c7080961d9ff6eef19d23d1399c58aeb412761aa72a6b357d8aa915e2b68dff9ffe5b0c9a6edffecd3097f15d35632f2881970116415debb464c6aa4663d5bff3fcc42d89e5feb8ce1268911b46322198e1a92e946141c646141d882d0e7d01fa48b07087fac86c9725c3f37cfd3cf93beba502c432d6d2a48b3070cc79bf5e3385bc0d5c49e9d96764990e112f256ebc4ec41f83ddd919bc1e494b164ee600e371971418c1791d81e071dd84d60bf80f2b0d52c990ddb4e52218f408a3a551257efa6c80632674612b643d2d2ec3215901c06e38bbb0092215aa1f02f84704900b5714cc54982f4a4a5c58016e2429a5cbc47727460e25434aeb24872f43fbcf1804181fe4616fd5ba8c7c6cf006d11ad28e2f4f0d43930cd8c95629933000386a64ba78baab6d5e37bbe79e1e872c414e0ae62327447430dc9c69a2505474a5c4105833ae35cddf3f48bc9e9598974f1ea2a4a7d79f2b0a344739dffafb3dd055cc929dad6f77ed567c0bfe629177519414df031935990f68d8c114202d6fc4ddc089cbc293ea2e77f13224a300adb6d1a6a719a0af81e192edac2b5a7ae0d49d0082cfc7db4daf688d35ec4cfe82bdc2065267ec46ee6aa554e3d9479aeee4d7e14ee837143835186adec45b21c1dc67313b69cb593ce101ef8f4ee5844fc180187dc6d6e3804348f46cc05bdc010e91dc3996ef62a96f5ab29d14ab1faf66f7478446bddccfb7f8314aca3498e3f42455cd2a2548011554895dcc67c585d33d7e6abf6a5958e94324a2f51762567c8174feec5f086534754b8c8e052f217f0eafb91354e62280e9c8ee68709cfd5b4eab54642e18ad4ef3214668df318a717aba74f79ca6c124dcda35d1902e52aa06f75aacf6544a4bb025d2768ee6f13f0423565383f480769da6da34dc6e0d49f85224e48fe44bbaf12d2693e86d3836676521ae98bb82e1a390a375f9a0b67765a120ec2fa5104675950aa516b86355bb60fc2e2ca7024f1f761462790d0426d391c3eb54263e4ba2abac71cf881a16414636ee4cf065c8db2b74e41a12faea783063d8a6315635be7bcaef91dcb8ffb9ba1066f30b4b99e38e25c1e93ef2f0a31a012d70d845c1c476fd9a8e2cb2c0155d2252c7e8317490719086997218c318f2cbc93144ca6fe562e0a80c60fcc9542433ca92d0490c300f0aca5ae0123c2c5e54c912d8065d7a29b0575be79a88db48661bc21559014f8d4fb7afe5c36bb700b7ffed900099c05ef433b1d2bc59520ac227feb31a5b1b94ac68abd59c8ee6b3c396a4ae4dbdd1a8d505b0d81216d90832b1c9f06d490e8931bbce4aeadb0ee3dc55f35ac3258bd4873b4db3185ce94b1e6be166373f9f9609c111f9282d7bc34f2118c67087fef11a4c057db783ad70d8071d8b6a1400bcf84d92f78ea3686ce9b89a47f84452e19a92d24be2efaf70fffcb7f3ff98c96276acc6738f5452e1b2572214be7366722338117d8c7e4ebda5660e68c94f25d751b893dc11c128570db2fb0c2538773e7328b23258bf1bbea35c48acb2aea1892fad2d7f965c08bbd1bf5403996bfc45cac5700b1514d2a07616753f80ac751b018462e77604d2fbede8cd692ada860ff8046ef4660255a5b0ddfccab48d6056110a264aef6afb1b20c66cd8492f5c64d9a414dcce7212a67ed3932a45b07b0c0428bd718e5b12bdbecc2c58f42d8b8e80fcfb02ebd8ef01e14bd0353d1c724c04d1de49eaed2c30cd466529b427ee0d60ab4b9e9bfa83ff4176c1ec8bee55390dd23e9991a8607b159cc4eb3909f2bb6bcd4811d1cfd6413f91e2a714416b4544ffa28f2984ceba456e51c188342421d9b2918bc3d9b8236141c70922f6c8fd93bb7befe3e68f70a2d2e6fdddabbefffd434c553941333b9078ec1a46f4c0aec2fea547ff2016d66483fbdf0f280cc92e20d2c58ee8861e3acb674193f98af162aab28d7d64c91aa57286e07bcab666c449378957aa4be7ab351cf3aedfa527d2ff3afff7831696ef46a158933ab6a31208717129c9b428a7a8b991586bb399cfbbbcd30146fe9102e09aeaa85afad8e719bb7836c970513854028377216ef8fd3da54114457842d779d7007eb60fe2389e87811bbdf3a459c2be52e922123adff4aad6f6c183da2bc280c2dc020ae186414600b9c1721861aae1abe2dae14cd8b662cc6e38345c3d08ae5d26e0e182a79b556c6446fb451fc2aa3d018390a79606651f05c88015c027631119da87d25cd8a7501964ee4d8f822535f4718f8ee5ba224ca74c5073dd796b970df9e7a6419a1cd0ac8a4d0e5ff9ead7064775e2611174cd792eac626bfac01d04c9bf09c3cf32cf6578a4980f50675c244840fa192183018732785df09e51d5027e77dae6c5b8ce1d87a19e05e400ecb537fd2d99f16b3d8209d29aacc85342c6b87b49bc78f059d23d7cea8df64869fd56ff94483ca0bcb3690ab9f03dfba151cdd931f4d025f4cb4c75b69e153bc4a04964da3244eae6b55d061e360bfd325330d428a7fb7d42f48830c59426a2ee701f272e372acc59b37f612bbdf0efc76d642d2b6d9c38a8f4ae13fed5f4169bfdae1313955ee3b9ec67e4c94ecd27d50b97331fcf0b5ea965154ca727c946682a211711f26939f4759800da59a5fac6e8c83f1c44d4d2688983f857a0b1f10512a60e3476ee2e81ab3f7d0c7d9638af79f077fc00aba966c074634ee0ae48e596c1293b1c12d8c9b95594672a553e44c1448e0c21bff1faa90f33fb6f951da96913a39c54d292129664f3dc8cfbaa36ab013fc534466c3ad2ec581b01f7f62581a991ed2bf198c0cdcc67190a1d4dc2446ee272901541d8712c23f760a102762b19d19847f79b9a49abd3a1068eb1171c132d6c3d30eecb83c1e99cb8cf989e576161bafae44f89ee4b61dc8a1456248dc29c395118a045d5239dbb0d1a885f1259154fdc20e96a016305f697cee40103854035179b1a14fdc7c28d55a5630bc7a636d6e8beb1e3eee001cbe020691b1b8ed99875d51a53c056634de439e4622d2027acb3ff60db00e2cbe286258c78853547be5063011df575bd62a8218cfab92848bbf83a52f619caa18c299f468c6c078b474ff02a435591bc54c26fe8e9d91151c304ec69d2c2c0af270edd533fe507ccfa52d7ab385f1731dcb5b42668b861ab3accafc7499c6266b5cbb7f88219d83de0681628e26ba2ca4b9a03ce28efadf7973e5ac8d0ad4e96e2d1a81700e0e46279eb730274986db55d3bc4a4e76a6cdbbc69b0405567270c80047326e1ffd3ffa9f59c3de7e7e5de0f0f79dfdaeb622b727d82baa3299bdd861233ea113977336363808ebda5e39cf475049f6177c0e08f1efccfab85951e4e9283acccf1f1940991cf7e39b885a3957480a9bd01ce33695ae5ae381a4a8661e754b8c8a3e84e465b16f3a007e966193eb8981128f4267554dc7bb8ed17bffd8cb745f115e623d002985c007568af4562bbb5f52e27f47e9ce990600e0ef8026995fc01c54f533d7e3db317d4f9dec83aa20da6d2700298bdc5c5bc4d34d0b44809d143df053496dafce0101b964ac99feed07600dcab099d49e4fe345e73cc1f293bc456cf69e80ac804f22b2e3fe428bc9cf48aa651569722a50a8ee5821dc51418661378a29dc8cab95cdf8c44cdb5a41e0bed9fd9a396b37e0f4d20ffa820d72a04e2401ff642369d6303a009c966a2f9348e0b1a5fbbe5369843af82282556183a96f07c9ecbb81a6cc42f5aa79742dac030b0004fc75f1fe1ac2c48b2f14d839d57e174fd5f892faffe90b8b4e3d3d192dc8d86a8e2ae29100f6e53466dbb971fb21e084b69dd28c7aa4c6d23c79278ca0df80958800094c534cccd45817b95012b81a5216ad821c825dfd0b89bf1ff090a01a8f9b5c09a1daae620bfa858f3ef441dbb9fad2ea8c381fa9f68552110f491833063f69db4d7e7f7f24bb69e407b21a354edbe4ce0dbba939570f380f8515852552c9ebe959511c98ec6d97a6c664cda46052f505deba4039a55f03b53d23d4ce81338024d2cca916164c39f3d9cc52288eb9bba4f8c931b9832e9d37a8f6875368679c6a62d2d713f44af0dc2294b17e5d1254ea99e0800ed6771007daaeef169015366ffabb9bfb3197d19e2e4b2cf561a471207e81d1264c1517325be5435f390fbd22d4e9ac9519868c2c9347534a3ec433118d704746866014cb09f3bbaaba976afb8f7174bed405f5734d81d773ef1baabc1e6267c2802d7e92e3b3c016d6ba4bbddd9c48a4a23c5f54e2640570dea6ef22752e59ae2b69389d5158d74d79d4f802e35a839d96902511f9b70d95016df4b2ec6ceb91b4c0a81175ff524bd4dd1654e8805fc940371ddeea7b94d2fdb718b37a23c7d1e7b4bb5814edd35f2ce3b3b615534ba5c767802a2ad9176bff38995358dc6612713a61b0dbd738727a28b1a9d9b9d2642f43518d73b4eb4ee35a6baedccc449030d6f2dbf8b1c896f6271af001098708394f71288800b3709382f81084cb871037d30096e78fec95dbb390004a2d12219790108fe2b7a521d19c491212a1c4226e6059463ad4b0d96c5d9114d408702a2a1f087db8b250477a77a8291f824a001af6cffd2fa5719984ae2c4e7617c8ef7c74d39f24369c2fcc10c7005c8ab92e7716d15bf5471eb38fc0ddfeef777c35f2e59f399377677c71247a87586e009a566ad1a530bea8d462aae20c474f89282e01f3e9a59023276484495f983cf8f487dbd26010245b6fad0832eb6c299a222ca8efdc4211dd913d5aebd1f2caa2254c0a0dbb6581c506895833269756ed542d9b105666f4ac77a243d26f90a5b28c993cb15b702f048f35db95112a41b057782ad53bcc77f25e8bc699a4ef17ee6ae39e81825f8f5374a017a737546954c93604ca6b9674865f2136379e4321c1ca09ec080623b67f7219b77b1bddd31490031acd0c93c07d4bde86d4a5af3fd299d84c49e34db09d0902b30575fabacfb20e8cd142c511104e7969cc2201ae4f3b6c9ebcc549e23f4b5d00801bfb4d2a22c74141c13219a3bdb5246663c331b268ab6109579124aeab7dbf7ab8dad204517778e9082cb29a6d246fe951a4fb2090d3d2656f01bb5a21c64b6e74b75397b39b6124ad1bb2e1fe60700990c20665eeadef11f9fe18a8680183c8a4e4cb621f808d7f7bda68e8ee52601bc4445b8897a22baab32eb6c0200004b43f361863139f3f5a2c0460085f8220899ef4c19d82aa85dedf27c5653b5ab394000e6142cc406b3f2793b803f9dbb5dde00260c247252a5e18b7ea6b33c1340d6a33cce748a98130c1e10cc15af1b9a16ce664b2ab46c3bd745560d91a9233378acbc2e82b548e5e2f11fe103d382d4c2b568fe1cd20b9c8fffa06a6abf85d6ad98053c66bf9c3c956311087a1336407de85bce77e9bbc26e8643a4c65b72f4327ae438fa483045b22bdebacf3780b2ccf2d40763808b883a5817c1d625d90effc0383cc206d5331d3684df8c67708cfad403ae6f170195bb86e4509c2e336595aefe713c0a9391bbdbf3c6ebd670c70e3dcc2fbdc1a9e507bc083873d584efecbdd91bb3ee6a603e572255d9d657eb78f10848c1b2ca39c923c7f179c309a92e3e1fc8c3bdcc1e1ccb6bd9614a2f4014b48270008719aeb754b8ba8c4fd50c77751ddc2a80d8b7a8852132cc71339e87544fe4605e86b8912c13f7cb772298fef9815bf38f1596a3c50c9bf4da0cc6e06efa1735d165a3a14d1f10588f717ce9230cfe1161bdcaad9fe7668251800b3dd575e9380ee1c2cb1212090ceeccb51fd5ea9af1218918705a92e09cca6a47a6d89454492067e411607f5463402a7856a3df6af0d34695d681b9ef3f6a16961f6453270273997221e98f8e97d1c8a44942800b7e4108f76c6834246d19b0f2af7d4c0e0c4bd85b148c3f9b21f51dc5a19842bc5c79e38cf9283937616063a0bc5d925c12846ac4b2740ff762619c5388ed8a4b69b20998fdd83449531c7d95b0e4df5f34240d178c258909cd23fb2895e9f3013705aacc8014fa441086e197860705ea90a8ef6a587cc8f5828107480f766be0eed0d40a600fbbd02b8e041cf6b5858a4ac00c1cdee971383921d01308c762431556deec2ab437bc8cb10b95ce0e7a76967004f213d79f6cfa186cd277eda1cc3b961022ec82c15454c1bcc5d0d56b545da8492dc045f4f38ac620fcf0f79671974f49c2c9717fe4cc11f736f6229641fa2d0536d3014f82747e10d227a1c07ebb56b87ea1638d35150243333263bc2f74f19e127926f2b149f1c73808cf6fb62ae82b1511cd6e35c4f144f98cbd777b14eace01368048d5b36e83d245ee44270c8218bb143cb004031d9aadf6a0fc060c63aca5ad3477a8cad9da3580488af1634deeb8b137bf11a4c351432a37327e08ebd1b5719bd19539fbed201ea029b304418f4b1b71f0b2f779ee8cf5663c16361f98e7e92941d2dfc3b185eb74f12427ee8229ff427b6288f6049f76962289237ec8fe00ac55dd214b5c0ab353b9a484f743a298008492da90b008bcd2fddffd90c7f320ee9ac531273b2ee76438ecf349061404f98c353f837995887e12706c42e11dcc4596dbdf997249038527a4038cd230693cc700c215d39e847fc13d0530d10dc6e56b4b21ca3e920229c651f2b30347fdf9733c948fddacdefb268dd262dae275204dce7bae537a097b6b97e86fceaa0349b577cb3013d27697c692051ae09d9056401f5651003a77f0ee6e55de4e67dd2dce33df4e7858ed27ec08fd9963e7a06bb59b30a31866738bd7380371a5ef966eebad42e118c5311b8469dd97a20ce31a62dcbc2f85dd0bb3c065e19ac6b0f08fa13df7a0c3aaac64eaa5e721cb2951b3e20233e33b92ed0b9edb90caaf0b3eab43093c9976cfd9f17bcd0e0625124eee0b1fea577a610e8a5455d41be91c4cd52a7d52fb8f08e4003611522bc9ffec50e9214eaaff8fb0cb7e79a8bf8ac1e79d9e3edc8f480d57105ada8e50071fdbefc2b137270d1ce0d17f33e09338db73dca8839215365eeb7103f0139c3ae6688043de478de22e468020f96921f376c66dcc0ff53c35a627072853758761d274418384b438d8fd9e544fa9935172b40e6b22c4b0f1be05eb269dea79ac488be65ed9547b21d97b55c9dddd26dbcce7dbe4aa08863b74e6974b1802a954fddbd1f1f31f595140d233e952a4ca4db22f2fa255d17f6c41bf3a2851857bb1e3c3beec798026a5e24bec1454ca538f7adfcc0855738edf3a03e579840d8419df83bc4aa53961c604e453289e309dbea0952073beb191638fcf7a73b16c0130b002933e77c20c4d77f8e02e6bc656a36128ffc79a774723701b00fb4ba211f8678bec8ee495cf3f01926606163de01a02342f98ebdf3bef0a2663d3df3ff41af7fbf6f0229f5feea40c8957f9b0033b0a849fc8658c99c9264a14d43b81191cff2f81f6d0b38780438a6e2e3fa048a1009659b619ba2ba04ea23a647bf8a68aeb9c868807e1c3b68be58828c74bfb76d65c4df94727e465dcb75d844781969d7d804edc8a26e3b94cd7ff507f52ba3b155caff81e8a57462d5a6b35e9b1927ba1fec716d6281168e055ea19631d9ddabbee4e7ec9b350e1860882b3b2c75ccb74aa34883c2a255330553e65c3135496e0f305649b3b3ccded8907f95734bd6d1d19a28a02b3cb9f6162f1147ba3802623a2fcdbef11020eb2b33bc0e2529a2178f9ad1852f9999b457b9864a11cd2040893337092647536b926032599aaad978c18b407a14f8af2b8a2bfa09796bf92bfc84bd6998e230130139da2ce27d248bd65dec2520d7987f68f9cf12bbcd6b178a36fd712cee178247b2271216faabc932283234bf22fe7714fa96e807140df5280c3d2422812b63ce758c2c57fe014beac059d9a69bc59ea3f79f101d44eee9f7200f870dc9575aeb1994367d3b1a9cfefa46318cfae897e5a7f681b370e0b2752c47d5ac62fea96d31e49ed371414e59249c86baef0f0ab2bb6bf78b7aece22d26a55c869e22cc88ef966b0bdca418bcd262d22af5ea19fdd81bda200f6005a3c2d6192c6e6ec4e2b51843639ff5fcc66e5726228caaa6662b0a00360b121b8f3f9f6131e8b350e879d1bd88efea14f78dcf9f94992f9b42b7200e20427128f3e2e4c313214c62250a4588877ae2dd8ecc1a9f3e7970d02807fae7b48cb7325cfe6ce15b8a13651ce9246d823893de86d9f6fe8f3d66dbba876090e61df35052bdcb8165e5e01a23921affdb95f833ef76e1593585ff14d30168fa0ec26be48b1b8169cf74be2d0e638435fb75ab2a8fb9a01429c252cd29edaa9eff01a7bf92a85ee3b35cc45d8200cdc895c1d67d51abf197f9b629820deda5e3d6a85436639ff7c69b9a83d0e37b455e8afe95486efb456a3162e076eb575732369667e293ab8db37a59abe186a1ac0899c8a61ff7093ecbe15503b0f359bf99f0f06598521fb808b14a277a1e418235744148c8919a6f49b5ea815582958080d3a8be58dc4d73718a380586dfbe52a01bec9e303094aa90b1f42e4363de5d3037bdc2e374db2e3ae6672bae7606ba420687556263937cc250bc9e2350cfdef426d022a3d9fb8bbe431370852dd2a05e3152c6b23825c2db10ca2aa3e17e7eeb1cc965607fdbce9c0d5047392bd8fc430ccea47ea422d3e1824ade0d4408487544513e4a64c16a30a7d22ddf7602d99e830bc9c1df223d28cfdc3fe11e43e9d4d5043db58693b447be4bcb2fa86fd081029e4ac03cf433d29994148a34dbbbe30f5a9836bd945907686e466f41f416a8d60ae7b9b2c5549ee147045a27f1743a1ca770900a8624fbacbf69b8217c01eac04cc8e2a7ee250d874be5bb34cb99aa80e84dae046ab5db8311332b87a64f3f49aeffcf1db53d007772b29fd251c45206b700138d0f8abf7c1e784887a09374ac80d821a829efe5e6198b78ea3df1aac62fbf7918cf597b99e97a3dfb0555376b76806ee2ee8e59dc295957c05b72de3abe036ede9d9799e4d47833c6b9daeeb5e09e867b4fe6c8d4144d228a2673ab317263a8292e0479f02179152cf31f58854ca0ee1c4a9dab4744f07079217d5486e5b108c5cf40d340de47754bc646dcd1c762bbd84425d171d58e92483134f7649dd749c202595e3efc6e8b2574b34ff12a6f2a9204f6e7f4dd0c2781d74051f7b3badab8526858e344ba074f970b3dc1474e3c82d7f4a7eecfce0935b459307b59ed4860a575dedfd8906e07845ce43e49ab454b2d9d2cf52cdc3189f1e301aec90a841322f7b671dcc7d52a55d138033a5bc06796940df12fd316a8700676906e27dd28ab9d558211e9448ffe4a099cd97c44fd49ab176a0d170077c4fdb0091b2f1e7c5491b48ef4a731c0808825edf1cd46c74a86f48d5c428e48a7c82adeb37143f431d8c6d335ee3d71c2c88b9c52dd240e96f94b700f302847b4d2728e209f2366a4afb494bac16d2a64a8fb64ad44d390cdb9e380d3366217852c8849c931ef4de8e581b0577ff2e2c9d651d37b242f0d65937f6c03317a56b356a75fc681c618b0007fc624176d61a3c5845c272399d76d6d6b82c11990e78f3cfe2a66906465601e67f32e177f542b85ca2c8f80562467eab17b43f90ee1b87c0b977b0695680272c487ed272c548bcaed58caa5c1589db6dc08fa5cc96694d541af6dd91cf6f0235bc79eb7eb0e9912e4e2acabba8a54b9f1792940af21dfb636dd8b107bfd441b4f9cd2e6b9b50bb1467b5edc565d4f0c1200a40bbf4765301693c4a5a3e2c23fde6984be1336e3c2ebf9f91adb38520deb89998a00c851e2e6f82a28c8bb30d0558b940dffbae29159cfb3503fae021af0ef6f3007dee02dabcc229b6e080d24572325850488bb2db15126cbe8cebdd1c64e78be3dcbb7896915da5a2ce9e622561d33c810a5da0df9c541be9b5fc180f07efd2be91f0cf1d7a0334eb658ba92ce85b9a9a78d9e179121019ed66aa43ad14d49da3fe8a27714a9a1d65c84245272d275dab5cf9e66cace5d9e1c3bf568d120e0d876caa813a9c1f86cefcf2ecb2d30d930435040e1dcb525dd2b3faee1a838a1770eb13103c4bea1685a926a21bbe9efc1be640c7f8c3537fd87d312a9663e8be4fca1535d6895e7ef857f5923b48e0c52680c932f033f7c77e2f418cd61dd535392c3ff236734ca26c6d957d9e8a5129a1424eed061bef446c0858635b26a06d862a821489d78a972b628fe348aa38548b00d2794ce7fe69f3900439e1f80bbd45342ed23769d77c7685b56408e37f46b739a8610d746df9f40e1d09eb3501efd4548dbbd7a22dbff38beaf0091394c3c3f5de4c30c3c1b1863d219b055d1aa6459d94a05dfdbecfd33952d2ae3e5b3b3f2feb22e1d9aacd626c10c0c1278acd8a1a7abe7903414b2ce648a251352322e29cbd9c185ccaa90e422b57c46455efd8964db7ca0be000cd7f703dd2de37c00cfac9db3e72c33227b6f628d2595ba56c71704f07d1e5e7b2281b0c76a072723c2c933ac27fc700478231ac3454f4d8ae0f580e01c72165a9284daf0c907e40170443c45b2c518921e3c736cdf4800ec390967553abf9f105bdaf5f6ab48c298f63e77d06edf26e6d768af04473cb37f8d28a1aa7a511683bdb059867280bac063f288f011efdec39e453246dcca27590043484f87d161566f43ce9c1ec12521d46b63b027c14c4ef6dde139130cd1c2c6d08d228319c40235569e7c53525b086982626ba8fb50ce30e9b022f3ea4a133c1207cb397f4dfe660dc53ef4658e6467938eb02a6fa7ba65331cbf7668f0177d62510027d668e25b4154c078b99b565d6f21d94248c1177216d6fff8833529fda106b0f275041164eefe1a7f9d88125ca6506908f4832adde7382561548118f8304d0f510ff2a9b676653f540f49587b8b5a0b641bebf876a36124d247d30253c2598440013844f2582aa12c871b595d8004300d5e5cb3d461bd9408b8910d5c22815f4f5a12593f2fa2179fee57ae43f616ccff3e619754dfa32d734fce9cc301a083f9c0a81edc050a9bb5fbc9b59399275603b2f2aabe114b2a76feba2bd6f7b8cc89554fac89fcaf88e85e96f618fdd9c138833cf908e57cf90964325c761fd3fef8fa131195a9e27895585ab1e13e6906b2af28dc4bffe5160f883d3091fd8b924119ee0708a5b852f8586b19377a3296853079e7f84754bf03552fb9549363bdd055533f0383e635b113cf95a238813d82c1ec886fc422c973b5666c2b950acb3381dafd8af4f6f62649035314149b01be5b3694bdb2439fbe6c0845ed4b61b47873c7133253d1f447a9ebf1a762c18b0c16f7c7b2f4ba1caf653a49e9c181dda0c2804496df2784bb39020b2372cf0445b60ab7488d6c62880f34121b5e682330d7cf50dbd78244f8f3837706911ee7291225b00f875908a1fa43478e943f198354da73c1f477f69c264ba2860026a980c693fc617e424481acd443d1f935d57b15b7b78f7f70262f450ea75febb85b1e0343cac564cebe3c8245998da5a1f4751f5600dbbaf438ae8ef44c3a62dc71255c2bff0a9206301f9524e4a9037ce3ff6e3e9acbf4fe248bef13ded14a57417ae857101bcb7ceda00301312ed2a1c40b25deb25adaf9c982c1ca043ce2e669beb3e2c40fe91e01d75c3ab6e2aacf782b1935d71e7b5c3d54e7b93b477c50d7eeee1f58f4c2338b24fb894ce4faa653b3bb0aa68d3b34e78844f85df13cef7238b9317eb97a61fa1f617306109450d834a974ec640222f923cb01f3fc2ad00da6e7575ca04677756d32e6f5fc2969dabce614c76b6c0c6657e0b4d3ae2ebcfa33c17a0a0bc7ed7dd865e8c1e53d787006559f1d5b6db263fd9454fe2f05c10e34ab8ae7ddc6fc5fb6b7c4c333c31be7a6da80bb8827d119854daadfd068f6b452963e487935e6ecd58d040286225e8b0be93a7cf386bef4f80a743957354be1e748829ed8d60a677e36ee5e87334dca6a11c3fc79e0139613d53a74f9307be1bfc911fe8637bec4ca70721258df23a9c7f9e0995336360ed46fa90809e5b3541d67c22759eb2f108c5cc96b934fb8fa9c62421c4a2068673a22bead43f1c3b8055547d220e2c113f8ae997417418773e9468ce33d93a0cf1e18273e9e12fa0fec57ad1018d9b20fa91cba29f050990dfc73e2637e4b0dd566af37356dbf37368d0fcbfe931e3de0046656e0de3a057da7d0d4789444a2844437269f299788abcd803a028a71caf3f37b9c7b90572fbf8664e9b5a5569eaf946ecf37995099ac63107303adebe88f99753c07444d748870b5d62bf0d5986b878634d73297aff9b3fc58f11623c3899cfaef155f8ac37efbbd9fecb95687bb9bd9cf5ec99498291d5444594782a59c88804d3f6171b3589f7b6aed6faa999b4cfef651a086016a7f13039306cf835b97515a878702fcd709c2521e10cb2683be57545e9e96338eefd50f5604466bde092d88a0a9c40d712c10d05b42b7803e1b940a4623b15531e10c3a89804ce62bb058a42a4f52fa146f7fba890fc770772dedb20123dd57c506b40c3da31314808d0eed7ab286fb8a794dfe8eb27237b0006c711dd28231b5b2dc3d3e2e37814ca176e1daad93bdba6b8f56b3ac9837a3b999f141568dbbb7284855ec5a0767ac37cbbd8009cac93167a82e11c560a5ebb8ea7000924dcd1a3c12c1c4cd6e4b257303030525db66918c34ec60603ca07ace2938ec5d21c66e19b91ff158271096fcaeeda426f055d8e4c47c5e3a0b7a69dd1c81b7fa1af2dda0ca444415d96163312cda1cb2d9e30c912852937bab603166a28f000beceac39a5ec3776166979fce19cc20a2c323ad038856ff5293bba9297486a4f8ef619f78626074068b0ae92d83f050c620d9f134619088278d0beab6652f94b674480c23327c77378409aa083086f246ac69e87e1b26e393a1a5cc2d0e50db2eb79a9955c653ddea8cc5791318f4f719f724053b9360c59359ac4809ad174a2385f56fd8979cb11c4d3f9222d09e7b62fb84cedfdcb10b640adff2584543cbd9186db9925f583a0e3e4156eb482563dd6274cea553f3162a6a107c4c5ba77b4e0f8574e997e051bbc9499882d01ffd191ac4e8e00fa5cdc66e9881ce45ae85706aba25cedfbbd467caec7f6cd445c5d0243473f61ba33a759a57a9f8d44fb28a5412046c8d663c89f6e57455b6d51bb1a32b49a469898a3b0f6a64b19b9373372eeb5b9ba31e03f6e6dd0ea7c1c74bb0572f003773799d7aad470180595d9352d468cd1bacb54d8eec094532a84d7abffc4844b40f95e01de65f20ad6cde53072419dfa403c16d8038c4bfc40c8deb74c2b8c23be9801e2f59d373b5ed0c12167c8168694e7d7c8471388ca746710e485e213e7200417b74d30656c9d4715b9745ed29b5816543f4a3847b735e4c2adc1a0560a695f2bac1d1a75f42064597949221aad09b516e10ed1326cefb9a33e591ab5f618926f91c3cf03014be5250b81bee3425d57409ceb6d3f8787471383c5bfa375b78f0a964eeb94c6e1c01cce5945f6699b5f7298f015abe516e83ff7dd3f5817d5124095938520f6f6790704090e038832e5e78307622d6317afe1a588604b6a6f14f00bf37ebbc2f562852239672b06754c988bed2fe5f4d38fd9ffd9154cd590e554c77756035d94b6828de6081096c06040b120187461b90b0bcc9084e331b088a939ac93b518f73537b033eb9a238a0c74c0c621d9ff0e427661861e664cb060208d81777e0643a53a2d4a23924698cd473c53fc1c8124a0c32f99e0e852f59f33a184732cf93fab0e1926dbbcf4688552c56c58372c3ce9e01d68f4c0734c05f862b5212147de08964f1c58733fde2e1af78e529c6ddb94e7459b7813d16f93ebb2c41baee86d9ffda597995822f5f1ac338b4e814e391138ef9c9361a6d93f70d2e78ce22234a49a20e60a149ffe86d8bf07111595f8b300036cc28ff782f88218e373773d5c0a73f3b626cd1ca6513fcdaba4d6cd71633ba318266a2004e4ce8e6fa2557fa6530b51b7cd4c5d569e63262b8a4c7697593179883f78ea3afaff98c42c7d0c52dbc7e4a9091e6cd74b893a56f13606caacd644b6b5796d2ef91a170fb283e656838f2d18ff8ad5aeeecba709d5b6e5fc19abc6e32ab3870fe0a85a8ce0445ac6b3aa76287790765665e1c2b2bc4d6da7c0f6c74464f12a82a8256f2cb3487c80c78e999d139b26630991821c7f518c66ba0200248a7c72dc86c0861540ffb4e31f4a31b800779ec2d7f7e1ef035f7e808ad4b9401ba6cd540025fb4f9fa50bd337364771b40ae883ed739285d9e0cba86685d9075d4dfbb4b1509d9853b76012c472192cb6e63fe702efa3a12aaa9c00adfc7c2c054eaad50c5262b6b96ac5a979a30d7f74be29430f755525dceb25a6cf47b345ed1b89c6a414675263a59bc8ebe0878674c5cf720d72f5663381c365649b8910724203986fc7ef7027d92bb923580583b3fb9294c8778fe3ea7b6dfc7da576c2ff6f9efaf476c6b7ff3bec27ae0b0d182f8f4f53b3b3a857da0f293772179f1415f1edb5e24ef126695380a9c91fb6cb0ca12c988411390e0777bb6bc8668a75635c435c83414413c886d549d6494f9e2782f89d1373ee3a87a8c44dbcdb8639f987e7080fe2660fe0b3a560b26e4d9e75bf1856361889142201b568bceb788df81dd8dac8608055a4e0336b89b77cf2778e9da6aea985ea99d8065a7904e889d50789f50e0097aba0bc6a33eed76773179b9fbdcb6e711ea48c906741d0cc2df2745b780b927a86319f2c9a7282d35c2d828b82e0e5030c5bff7f4a00a5c06fbda0c63019d0c87100b1eb8258eab93c5272de9e19efae0aba610dd6e6c22a59cbe3f96a4917a02ff9ed0f4cc3fc2c5d5a9b3040efc9b5fb0eb956cbb196cda53f3546276f2451f01650f6b979f1a966fbfed5937efd15c1d5ef103243eb2f4ed929c0f9216c334e73daef536e83f80a977f0368aed3e01a51a9ed0de8bff9aa3d211158cd93613fda59ac148f30ecbd65bd42a085ee4356688adf8889a4bfa0b8e30f31e0dd7ccc11336dabdf6ef4b0cc1f0b7d975f594655a282e3ca098ec8a82584a8a6046d2a0880c54a300ee8c900e18343e6e95e8877800c8373cf44daf9c979342f70026984521a405001d52f7de61461857240ebecb5cc6e47715d45df200638d520c4951e0b36c88c91de5a96253475ee45b099b4b4584a22450c6c7161630cbee6f19bd4cd9afc38d1c423fdd556eb5634a26899fd991140a2bc893a44350bfc7011965ab02e71511e3d2896d14a779e84dd5d6829b3eab9d6f1c900bc234a88bd3c4069a12ee72ec66e749c9e2e6a60df76d0001e107bbece2c63b13aa138ef0d2506ef2021f39a68cb1af35bf4279bd3c8a9ffcb629fe6921f77fd07e5eafb8f1ee6392bcdc64b957a5cb4d0ca04491dc1b63f4f14e0dd8d63b1f46afca0646d2b818411463f8be991c3574ac048d3aa363b07767bfb230a3b380ac91c0878da489cea6f3a73a5da57358d1f64c5acc47863d0201cedacaaa298e3221360b6f9191b6dde3888733a0b07b35c57be1053b16529eef755512c18ad30b16351fda17432ebee6536d8933b21e2003a1e74a9cf067208e4da6b15901fb9429f17016a13b9d4f638a76dc8cd6c7cd6ac676786f880c210e4b67d9a063c2ebb0e0efe6a3217d1f3eab6492cc798286ba0f6c761785edc45bc6862e0f912def1beb18a6e0cd539759e784e8be20fee2607990aed3f546624af0a29e12aa4befe6887f1c1db0455d36e140a141c91213d3865a302e0952b97ffe342043cf01d52cd1ea230d16c409cc2bbdf1a5758822b8ee9a5b4c3215517008989349889568b54bffb54a0ee929563ac6b35f30823c73ef77860039de054d1d20e40f72894590e04b6733848b82365044f91dd0d923131f3f2b447e2af58dc308312ddcebfa9ab03ad26826d810138726e064e428fd87503262116d5d49db3209e0243b721473f9756df94502c7ed9c6363d3000b3f209459f31901da7538b8bcc4431963c193b636689fd8e9f4186303affbad642a6d8c58f9c8f41f2b772fb312966713b335e9e12f042fc951c2d7ec8c3781876e781251472ddef134a41c0e260bbe827f2b4d54f968872ee60b6c7b2c2673603791bf95c155b007871a205b83cb33936fbe54a313fb6776863bb66f78ea4bb2a518ca3c1c88a1cf8b0860dfca14d939d1982bc954d7d8a7e82000099184ce5d48a56be36bc3abc1f28f2face1fadd364fcfeb378da49c034f58e838ebfe7229075303ef9fcdbef890dacb9f3ee7371003e0f5cdbc3822f2b1b6a23f225236cb392494805f67ba79006a3c3680bda2a4c1d74ff6cf159e06429438e0702fe98242f041ec2e9c79b02bb3b6930f04a4da525db860fa26feac44e4f9a488b8e306a8643b1bb45d0a6295b5aa6ab85a809bd9d2c1dddea990b377e2226ab45477e8af830f4b368a1df356ef5a50ba0fa79c99e48c46df343b4c89d0d36c347808f79f017e0e0f87201b00b39aa060e85d778d8671607ab9ded3d0fe2601a9bd3076fdee2882934570bf2725d90993f13a7b7a4ced83c5c8909f8d9c43e2cc7bb7111654b4fbd12ea5d4c1b2296344f5a82c0481095c7fc0c6d15070c8d11ce76540243acafd524f4d47c91d0d6b8e5406777a4cc844350e51f01aaf7bd5e144d2520951fc99c7c6a5f87bbc03a7dd6f088c8ab7d2d9ae2334a709eb86c49e3ac1b9401f6ca434e13a0dc036721bde22b13f09dd050d6f0b2d74c4d043811e6390182ca090b8037ec3b0cc0ea313af5956e405f109dbcee3fbbb717bbdb87a569e07dc7cc9b65fd5a2a9ed37c18e65af5f0bce7d704f797e823cf1056eee162aa0f1f09da08aa227c996103bbec61c9a4d2043410a6a1e350766d58b4ddc8f086a464e936cb92eb40677f5055e6dfd56233c18efd16be52d5c5e00ad56358e8c09fa7c4ec54fd6351236655cadce96b2880b9e613d6e7a8f6af15aaaa23fdacb30d2a59fbabb642d4f81154350f9cf202b249dfcdf7226f39df72922a70a91db2089b691f10be00377b1938fe69b2e5adba632e573dea8af0600caa6929bc519806bb9b084036ec1741de742f3e6b90416d90e09af23941eecd6bba66c30ca93674b1496cb60a6b4d69aa2b9ff878c2c924f36340a0c119bf75c775ce4c703a0708dc4ed75ab60d9cfb37e1abab89cac4e129df590889829fd2f79466cafb26dcb6875cb0815a6a5a0df625a6b08b6b5085eb891a77e9a93d6c9b116f891ea40b3ff1fbb8b94e0cf95ebe259967fb5ef3092b37b012589ece19cee303411bbf5eedd35b1f46d081a28be77b9a5e9a536f3161931c1fa7bce744cbff34764c296e828959c065c7616cfd1854d2e2fd2ecd98f9ee6b38a5f78605cc2ab94f754cd8f356b49a9a814782750c2c2f7e3657cbf49af5f9c68cf70378c916ddce3b38ecadf144c8bdc7855a75fee791601f770df157530cf1861bc7ef3f13c247a4c28063e48a859df10b2c00aa0e2234b4003a26a772f3dc9faa001eca026cdaf51f30a7ed392ffdd35d5237a1704844bf731c3a0f0ba44406fc6eeb792df4649ca01884610a91ce2d6ac09b7abd65a025e88924ccad86d3b9c26ff37dc14fb7c8d4ef3222dc582a84a50743d2b3a6a3cfb8a77b253d8492e64199b93f732599848a14d48d341bc66678d4307ebc68fade0438dde9344e259850db8cf65b6cf554efeb5108e87e0b4b910fe8822c47be4039573d2e60f5c4a1e691f08c0d9b2a2f59f3b007df12bf6386eed65bc5c1391b076c5856e5e04292cd3374e06320beb19cb6147405edebdbb129db08c4a41969f34c807c9c40ce5b39f446b842d015fd1307429e1c9ba3d3f829cf60a1160fed866560c09cf33eea83764111290299f0c7a2f3a71a79fc335f421ddb8e1f7b8efb7e45abb6aee533190c6a69c69e81395481ae43c3978c8a1f14c14c1e67c8fef7105f3d0c7e4568933a76613dfaf6dafe0c04f5f11c8a212174766e7888f86e22d8564a2850faf99b781f18fd4da409ed4a2b428e1f5024f59c969ccda821f7bb03922b6ee5cda87d6b8143bf438fcdbd73a1f784cac27f07cc695bfd4a4e495b247117ca6a7c5425664a09afe5aa6d77566df5d991f4b2536bbeae9635d8c154aca0f4fb6231831fc203ecb5c60c96185ae694bfd0db30f077c7b29888cfb3facd451985c813b6550380d800bdc568fd9f2e5b5c4bb7b4e4bee831008665a42fac5d009f6952a85892990c1699f45a53569937792faa3e175c45dfb42cfb89a9e166f43d0bf4dc9f95c6d430592b6611abe6ca1e5db6538cf659cea6d94d39fc131bfd2bdc3d8ea93f08f17a34e0391b73893df6d991e52eaa2ee738d5d94760720d81087534d4b4930f24457bde192dbf048cc7df4babceeb14e826d83daf117ffb06583eefa1e11c0b90eeb583d89502b66ea06e110ff10a15920820ff2b2feaafbbe8b2b757b732c8734c27368cee3135fb10690cba96d4ee15dc718c108b06051fb218f860e81dbd50e2f00b10ac7a37becbccab92d6b63e74d85bdfba8b93dc8f301c8777c9e7122fd77a1c940bf2fd9e69f079a68b4518dd9ec452240e032f4d8e5536b7bad8bd92e83e86db268a983a3e67ddc202b11513753fe5a6fe2ded651faa5a4d943eef99aba91a75c5d34911b592ba9a8ba6fd338455770dc8a431178c186615786e0e825815750dcfb9767a499f0fcb8ac3899f90b7114b7bfec8325e5a130beba009388108daad020a56851849f2811adb35c26811b92ca019228edda996748865fca1273880029d449c4d7695f9b5a979dd55a26e96f5d1e1b93638003ab86f066e2b1b9992e0f7d611fe66fb08a30f6e9fe43d6410ae730a08c4471e0b44a4788aa1f31f0cb2a7eddd3bf9764a16854683c0ee8192b8100ad18155268089dca576474f403d1462b7a14bf750794c84b507b76670cc7d890cfd98d61edc8671147fb1239a8880c2a49e42ed8f970965a17db8d0d3d4477fab538444820870817b7220493d1cad0595122c886b4bf604ac4834639e71d4d2af5fa35288010efd620ef0606ac5900525094ea157e2d4e0ac7511fe41febc50fe68dce738b8614333863a035c71152b06707b7dfaa8478f32d8013e24bb875ed32e7d306865da8fabe4bee9f6552ab65a5a2b032cee5c6eb1300961ab5424b4ce7dcddd7ea6962b813490a5c867c3d056f642d06c0682244ced72fb53a4593190e0da6e4a8872cade285da70a8dff0c85b9288246b9ef60e76a834fdd4db27979a941ce898aeb601b8e1bb635c81d6df9c6a2110c581a5ecc3d5da2aa7fde9dcc9dd49cd2cda52865904720830d05eaf9621abdd0c0a8cd03bb723188b17fb92bc7e71f1a9711ff91472307cf65480fbf176f00c5b8b09c914a1b207b452ad191a1c3fe964a6674119982b0984c316eec9a9390418523a084958790a001cd9f8fc23c67c5db780efd78f0956a6d0b9631325ff4e1f495745e8ccb33f896544664f4e18da0895d9cc677beb821bee697b222e06d18314e08199f17fcbac44ea387d8aa4e759d6e9be4184d2ff4ecc41350d4a8e6c681f4640c52fdeb77e797b6718f0ca96709808449488ce8c421cb8990b24a433c090577fa68faa4afbb01e612e84768bfbaac5a20949d70720c9bd6f218f74755cca480c06b43bcb3c00efc2c5041d6079753da747eb601c8f1cd5be210c089e1d07f36b07f886a01700905aee7f70cf6de5387ed8ec06f9ddcc89b37a7c2eb0e611bdf7575822aa31faaa45c63d210017377d24c657fd330d0a8e8a4a3f19396bcc85b1f4126c3b03b63995a35e5c7cfe0fc46de53b88319557e729ce4d85d328b830473930f1281daf336e129bf843dccc54b0a2e5818ab4f096ff95a3c496f90a600f168153e06da41664b46f781dd4f4af6255fa5f6eea3b7d27cf15e2e214edec1ee61e791d7f936e8031fa10cad257f6b2916af619d8f53ac731ea70072a32a36e459c8bbc5d90173369233b7e0a6ab70180f08350fc3c78b93f03351b1fd5eac254d819545e3418139ecc7da9bc02205d08ba83453e0b3a9be081ddbeb7917a554f3ec80ccf7430f4949b16065e094d8f7edb4ed70e9f3273ec9b9f7ed90cf30c92410924690da4f5e500ea35238a1ad6c2eb67716986285533269c3f0dacba6858b3952da70715fc16f1e606139db28e5ab4c2ce8fb0fbe6b72a25a7395da8a9cc71c8dbeace952ac7fe3dc944ba9329fedec0366d0e2a6217a0f9e553220d7b878c50245c9a49d27bd5c52634b51dcc8dd45b4808d09ba0066f58ec779390a51a0040a250e786f5594e0c4ed9b1b256bd481e984bef6d06c330d142050e17ddf578de318511a7d1425e098187a2ed621758b052e8e176cdc40c46254a640490b52085832ba20e93f94e8024d508476bddf21a520f17dc1b01b98f020806b66015864cd858fa9372c6e2eb168313aaa52549cdf75fc8c151d3e5b5fba9426069e25856b45422d6fadd2a47acce4cb54f1319060b434c866d3e156a3e745218dbdd506508e6dea225eda0921319760132946a66e134227a87af88832f496ae53e5d30bb29c49e6be1f90a61040ef96c4b5067f3ccc8700847361e033c46c27104ffa87a87178bb265f29258c8d4d0e33979b7d82d607adb1f8c074623b7a9102ecc2e1f9253c3edf4eb4f0fc26dd0de5c9e05c6ae7aae8e1d74cda8d98535080c03a8313ca2acac438509c48a491564c0e1fb1ed896dec85ef7a39b8dcb3c0ac717c5a574255c02aa630dbdb3823afedb5fba03c7bb2f1301e46735de3d6741ad505035c5bae209bcfc6323099d57324705d7a8c34ee489e534640c35bc6a74b879ee5b0bb8dd754f171a7d8a44b37cd90d68bc41e2daf15f55ddb64a17566fd00ce697b4db33d86208976829b3e1f5a7416fd601a290010d323d3434954d93240b28073ffedc7f2b5f476f167bb233632f46a1a6d57a1424afdb0523cad12ff1ed84dfcff152bb6779b30a87661af3e9ad7fb2eede7911eba42d8ae0b3a4922f2c72586453a0903650a8519166838207c7e29c3969ff13a7510dcaf6e7fb6c2b548b49460e18012baef2640890c7d765f653850a98345aef7961d74364543d8aa69700cba09c95034319a7808a8cfe089e9c956d47cac62002c9eb1512e0d74bc8fc74bac22381a98142a690601f86a1a9d0f5c0937dfd851ce08c88be6e43d8fdc8bae35e07fb117fee7dfdd7947e29a0b55342762cdf8159d319e769ead88f9382b463cabf363df0670d7a57271494d8d2cebf1c6283f27485c05151e4b122244ffafd02c638fbb649bf153a866be4239af62f0c8fc4e77693d955ca92fbbb10924a373c3a78ee4c661e1ef97ea11b0d4f51817344d67ff2ac42ee6461c003432af9855a5f76d4daf015727d5087255605939bd34b12451b36ff1db5ad38dc1035047c221be5706886cac3d501648a329b2411e37be97a9a476fc8dd11013ba9a1ae93c02dfccbde868009346905d6b6880432c563f99dd22dfb132e4233c69e39163f8032cf66bd6e465a89be80e4a133c65b39b75d509fb05287e27da7d7285863e3f718c08bfd0a9e5c06890c033c7108b553e436f836a7f5613aa3d7fbe2aaea5d6f5e59fc6962a87c43f734072accebc95013a31e81a50c8f6f2d39832030f25eb07a3b04b8e0304d272f2dc9b47310febd8ced9ab4a655ea31a7691b6023ff12861d76baf6e918c29aaffb05b465624a89ecf744707e6e220e0ae232a1e8c551096cc798985b3194d07333e6bff5dd4829489e49c1209f7b6d2cb30f978bce64ea9b3a514eb020fb3bc6dd5cb65e8d14e833bf021be4561c39df4e9cc1790befaeec343dab7a63b6d4dc8c0a8cc3772b6f938b25e2a47f45d3a063adec573e04fa2b41aacc76bcafee0f2ab456f388583681dd8160ed31f5979e7dbf2c37affe648f6305afea669af1717ea1a819048f26ce28ccd529d3dfab373faa04591b04d031120367913dca965a4a0ab5c6c0832efd89e764974764b168f6c16b3d9b124cb9843f906f4f1a973ae137756158fceeca78b5e2fb43520a6aa41a07f0509c3dc1057b6b05db2016dac4ddd3d44aae9e11580058c0c45252fdc62fc7eeaa412cf92a3610f6d8b9c098aab0c4da270e8eed78ec81342f0696ea18a56125e3fa58bb6bf2e05fda17b92d7ff84a61b069885e3e2c8a17f3d4c93fba969632035108e96daf19c79eb521d4119d63aa2b6d9785009d4fb529dd4eba3484b45f2aca575dc62ab44877c0392f2636d703bd28822f8a1b6d747492be6e5318f225e454ad4115bc930ca147e2aea02782bdeb052789cdf0e2cc670459724114910d8f83112db217258d4bfe7b469c2fa200c6eb4b5aa9628aceb95502881d0f3ac8c1636157bc9481f0bd47f55987d50099c93b0254738d76692e00bbc5bb9ec0d53583fab86c6b1e87cc69889ce2166a2b523d900d449a4c5b5588ab53faa3746101609a94d39a0265f01b9768456ab700084f0b2942ba9891513ef35f793398e607f17e3ea46f356e429a06a03f91c1870fec3d1bad8ee1804755273326342522939752583b2fb3e0656668142f1879610530c4d576c15ac2739bdc6c43b51d00c1199b58442cde7dd02c4bae538d89c3a6d312e4c34404a4672aad4dc04f7b56fdcb6aef4353d554360b3a7ac18a71c39396fa62b743e3d0b008ce25c278fe925e3380090bcf660b042491d72eaff0847d5bc944a4f686dc8f38dc3aacdecfc5693e59bd66155894849f0f8261ea0ddeebda1d747489f70d193bbe8427eeab35a9562d1e052c379614341f867e707f4ca2ea893bd053a69c3b9c97d147b2c5ee2b4b3bcd1fce779d9b2a3ac52000a65443737f616f9a9e32e71f763e03dbe80880e552eb9f5fb72b8e9b17333daafeb47cbb7410588373140b4e77d0f3f93e248d80271aba536271445c214394e60b7ee14d3ac9c69aa2d673c088f097790dcf61d186b066f01dfdc8b206e6f97e8785738db5c38518c98321d31e490f18f82564691de082b51ea9fe89525d4b5623d24e57458edd1a479d23f278a43ec5b12610f3bf1c8a6cd6ddc66461e0d458adcf37616b4b1571bb7ffcc4ed3cf7b75d31cf4d58f96e1e0b1821e6037026069f56fd183c655e90aa20d07f8beaa23ced84129c32028e2d71a54470a21608e30f65c4ab1dcfc3a48bd88efd30c1d3dd112033ef59bdfba60ccfa263e6acee3be5e2ec7bcc14b63b47c2b07154e9f2188b670684890647fa2bc9696c915be52674a70b2c7075a7153024c9a8fa7050a63e63c24c3ed8918241da5fadfa5a1113ea0ef39c81dec1f71cf39b873bf893f22c10b37301dcb9d47d037f3035b31d1233f595d26be61d8941561e897251804cb52358f05a5526a25c8f2b8ebc75175ef74f271f95642730801e9f7f60ab143fa5e67ab97f21fa19f7de4384791109d7dbc79021cacad82ecacae773a034832cde04ffbd3058c46f8700686a47d0b8601121d031cd0defd98fc98b3b3ea7aa53ee629e5d986e79c31de7dc073917a198bcfa4fd213aef21923ee9681667df752800d91749f5180e776fbfb67e569fa4f80577c17dafd108476e9da0ffb33179227f4d0982ea98987b47d0a57350740ed0c7977a1964943c0e09ed5426f34a9510d84bc5a0436431734c68c8e8dd91d883539dcfdca55ca86040d78d83457d8084b4f1e35d218002f8fe22a4737df71b4715c4353e9256a42ed49808561b0214b5e2470589154dd215554b1969d883a0eb348f87e143eafa40df5322ea3351092e85710d90b3ed5d2ef0d627303885c01913c3d594f0b9178d7e0e717a8964887bd2af44c048c77337db1f1f4ba9d2fa096a202a89bbfa8b8bf1bc760d3b84335dc62437777d378c9649326031c3d7dc9384fbe4208f9f51e8dda1a48adef0491c16c3e0171480c36be122641f880d3b058c86199334e45dd49bcd30c6cc731de352a32f227288ed0c4aaa7b409c1c519a062644e12cd11f0a8d38feab8244ad032dd907bed4ffb07d2e6dd68003f1eb739cc03bce18c31238a918f17c889f27047d603672c2f4edb2ea224216ace1bfc863ec1090fa9c47c2fec402a01de34edb21a1e652b6865774b4193b826ba8d82d4b57c80ad1c4e049a174deb209be1b19ef756c6ddf69e9466ac05f857aa9e85a7b16ae8f0e97c98c5134066b472a9f1ea7ba83e9f32b84a9cf806ee0db79c38c31ce611a8d129ac928999ddbbea18ee87b08d653fae943e1950a8ada04a6118e43f98f310c28e29862cedd1fcd1703543290f5a4193ee2de0a2803944d7a0eab7151f2ca61dd325236147f3d525b9f654f802d17141efe2dc3e8ed1f2cbe06d76cce8370f00e95f71105ff7e6da7445f70cb6af7b3ccba7017b86410941438a7e620fb1c5b94ee5dbd6b35b6ea9670aa15137d462baed576c9a65217044ef94112b2dda9cbcd100d8aa518b904f5b0e002569f0fa19f87f47b78cf0ef9210d1a891317d703443911892e23e9125ae2c4636a0c262ce4e2304156aaf89b1c1625644d865da1228bd8cd37f44e4d900264e5a28f11ec33b6b3191077f5376df0200ade31d9e8c751b52fef6a9caf91023eb19423038a5e07d16068952599e51616ba175c74eeea2c6efe68942747f10d7b9fe402580280a34703973600f46cfe728ac7ff38dd22da778266fa5b2345ad57befbdd5693237bf5ae1b5cacd6126ce5b24494c6d7241d53b7d655088a8ebd5dd8989708e83f6b5ae48b393704acf937977a7a1cb4de08fa07c49bcafb10b01d943512a012719ae2615f423edc2d1fe611386339660cd297c28904504d1ae002c07cda01f8ded57d3cbfb4778d07462697b1c8bf7c9efaca93b5d2391b3ee2c09206e5b7e223e5760e41a6e76daa5e59a74e6e7e191ecbd4e6fed8d14966654fce19f251ee4c1f2df62023da152163dee32c199cf14b2cf9d41e362a2b77600ad9562731e6b5732a59033e00c9fd62dc2b329c47d783459504979dfafad2b0314a2ef94f77ecb791274648443d4e3e3238b1d3331812497dd087b9ab8cbe727080d3a2f7746e9fc44471ae48a19ebb8ce60457e19bcbabb745ef84436c3bb4b38a6337dbd1ecec3db2a8869a29f1dd49ad8c48914b4f19743891632a196b78bdabd0387bc8aad04cd7a1ed3a4970ed7d9e20302885b58f54ed3331ca784182d4e13911481ff931558002e7acea8abfc2df92b62daa0f61ba9db684e65a9a577eb6697e570590e5df385906e3c9b6d6b464631cc46ed81e80b723393ebdbe9951cc88ac74ffece1c82ce8e8a1a2542412666ff4e1e6f6697fb461143d20792a7c78330811106e87b71a052640ed28e7896995877e3ca97d791d6ee9c5a9a34f81cdf4c81c03b64b477d5066267a1bc838dff90719b7df7382d875ead54d30e98e68414cda9fa14ab826f93b9feb5bb90f21ed369ce50b4450a1dcc0409863cbe73ed3acd02d1f62b71528e1480cf03750ff4f6791271054aa0969d1eb289b68edb225063f4a12920a2d21f13a732e157610450d82ce8883910c28a23087dc097ee0805345adbe92e23935751b3a4d35a4e052ec0323de5e29d06b492040b900998589d9f9fb75a2a74b287b4ded08c23b0f77f6ac85cd2fbccc087ff5cf59c31e84201f567c32561707e35336487aee85699b2860f977669a2c9b7576b212e14ceed4080f4e33e59ee068c14a19772ecf9d16ed358d57495691bd6ea843f2a381dd2ab53164cc8df05010917e8e24a0a27f1ec658b039ec3febf54d4ef5a38b596165cd0f6ec71baeb9c9ee4c889055324bbe1c0d433c85129f4522bd6ea7befdc0133bba7cc767e30519251aec80f03e5fc6b3681e3b1856226273e329341e60fd59e3ca42c11a55816e6ea0b56a8cca5215ad6b11df3fc86c94ae10aa87ec2cc5d610e722ec912df82ca0e332340fdd80eff795f7c9bae61c233fbe9c9c5b87ce2f91a0616f602f8af2b8203c03b46b083f11afc97cc725ed368a7605c862443a8fef4f88bb37cd1aac76af22910be5a08f4ac4aba389cbb78936c1cf7f3a3c85084301299c08413ef92f8a842ff174981224f1dafaed98a3b450db176b50cb941a0def3fb52d1f2bb88881ab00ad87a9e14a7b8562585f903df48151894aa98159bb9d77e2f85b884c02c6f23cbec79c7c13de70e337815de75a1b3f60e0d7fc07e513a75f45d93b9b4e90109642f32c18d137d84dbd401be6e55dfab30714a360e26384dcca5715c5d9dfd571efa6a82afb9208092f5107a5f6db601e0733369b745805ad42d487dc5fa0f976b878c669af7455f24c80962650689461f027ea483403ca45542edd7ba11afb30b1e9a65576b424febcc88fa06a921573e3c8a1e37d593e62fc22047e9c6b5e69002ca54ed97d567827426c4cfb261ba446dac913138fb4d0ae8347a671995e5996f70e5d9f71ce0d01e5dc6f372809c8831cd18c94b91e54250322c9500adfa24c28f24e2662dae451d9051e7b6dac755c33099b56f8e60329d63534a5d89c12653bd95159a76daefb9e98848aa5195e56e5689b8e7cb21424c6aeb9c30e33afb337d0e7728e949a3b5f3bcc422bca96387b4ec038178c2ec42d38c32680989c02ab8b2a6d49b5e955b8253cb7433342f21a2dd35f1594c118a329d3fc914bb49c2408fe5471163e42eda207e6560a9aa10029b34c3bfa3812ac87e5b120b073483d78d079a0f132470996d689717874420441254e21a49f9387f04e1f4be77d0884e8dee1997f913248d4cc48fe00c922de8d79b4f455c1ebc106659ea9d22129c25b5b5ee9217c0b754553cc371eb9aece1090412e3929cb2b862acec8397f45daf0aeb5485b6875be2c500faf6185827495e0ca620b3e47639cab7cb253c629e03d087907a421efde377e8b109d1281c8e9d8852d13bd7e8ba0344c75b61c2cb528cd5fb6f53a137264fb45e89c8c302028fa9a6177ff5f939e1fbbbeb0cb30df1de5d62330b1395196046a47fe328413870b3390006338c1bc1e8534cdbd29421de49d6afa2aefa193fbe8951b688334805b6dcd9d9e4447516d3cc808b99bd0a75a789f0c8d21b5c7ec1082160a04b63f3c233834f96c6aae9936cbb4a814899e3d05f0f4511d83d85cb3fdf53348dec87d00d0fa2ab4dd0d74a0ab12bfcdf0ecfde43f421731484e3d5ad050a7512d98f7915563c4f17a5d4761e57514d43d1f2daf4b7f462c72f5c543af7e272ce1fb997e2ee353be693d03b37dbd2c4e376e92727525b71d878375b4741d592ed83594ca58af86d206e335337d41a6a2e3badb150415a4e95680e252ade9f249ba3c7be3ea394ffbc6242632ac746f970a538ef787749ca8713b4e446585f7137e2e56ab06bc75992c5f17c82dace8fff4f5243421d6d4172059fa87f9f4c8b5716f10ab2ff6af843f45ac5534599ed880653e87d9b6add9a6d2ed9434608721e2e9013b9e837e5d58c0e62da4df8d2fb06839e8bb982e44fe47c22d5b4b587d85afbe8b01102210dc728f703e6577a96f4264f0adc1800d4706e5aa511de0bc7bc12e63a1097f24a90cc79488c20b26ee2b4968fbaa12221dd7a209431f2742a941f2e0f94884377665e8c5724913d828dbdd9a6d09c97451be6df7cb962c14ce945771af84c4ae2b3fb12b5d25e14dcc645728b8d94875501f6f0827a07722a9d55386c819c0164ec449ab126696cece6709ca4030d879818700bd24c37273cccf185dd36cb647593436505029c4c6aa36d16a73b87df397615ec91dce7da8e3a78e67e4209b6693c35b734b3aff1f65a02845cec47d297a39cab3ca96775e8ab874272880975f391c56dd524003d2567f07a5ca8736b341aaeb4cefec2f67f8cc207aaec8f1fc26a9549d6736479878e66f2233b80dd68c111d087dd1f51a874512b8de38aa3bb96331bbeaf605c3420817be8b90f8c42e5c2361d3f1f53cf93308dd670c3c6d2d40ca0c322ec0c3ccd491619845ba9d84f7a92fe6d7c08502343727949f63f12c82a48e6b52437fbe7d0641f1a61cdf234b5ff309c6904a157bc4a187876e9c4fd3a5e38c84423ca0af24465e6ed0ec6f815c0e120d3235cdf07f6798428cfb85653330481378ea3454e403890482b628ccabf1b4bc34503c693d067f8b20c25000322ba35c2a56620b632376c6350f12aa5b91c024c1b10a20de0d664dc35fa1801f3985701c0fd0c0a3648b8732970646a630579b419e7919e88e96781b6a5ac343bb720b2582410142ef9d5dc609e1bffff12ed527f7701baf0033d15006e7df70341138e501e6fedb04cde03890e24d40fc4d6b8b7300e60597cc340e6c56afcc552f6fd52fb74839705790e50905726e553d450bc83d078ecbc2ad7159a64d4cc8e9a95a76a2a09c36ba4e6d31850bf5652d0ef0efd8c07604083ce33df963051837f70bc6ef43e4990537cdb8e49ed4c9460e709c21aec37785039f5a15e19c51a8637750ff66a00f7601ff8fc20680719f62894803cd5918491950af73fb22904c5b92f4927d3390f30c6c492f01b7fb6b04499895df0cf50c944540c29aeb8c120c4c6ad4a1799de5d55a000d8cafd7768130f41ae454518c683f033f53b2343fd438181f227d5724b012bfc07736535d7bbc3ed25b7f729aa142be9d8119c734393a8ae1840db523212c5ea181843534990d5aa5ffb5cca568e01603f83bfa622e5ffc82c5cd44629a478a0612be855830f86ba9b387eb9dfeb9e37304fbac707e43235d37d7f7e452d43962249bffa3d13f98fec1f81fac7fa93b84b67ca60b61eda0feb325b5b1a388a2ba7948bce1f2d9974d7fea08c07f68914ddd7f4bf1150882b1657959a159f1e3bc73b93f142146eb0ca434b1cedc339f93ff8772dc011ccc4edbea5680dd636cbd0ac65b71bf4fba8cf621482f7880c6fd5a2bc90fa973a4c3f1194815f52cae441cfb020451909bf52d6dba04ad82e9a73b7819e0a03991edc20d2f38fe6f4e6d01d9df772ac7054cd8326c4cdc72a0e9eac003bbb4ad67ba8f25505c9c1a6d60202af1898061076e37cade617d8112e9cd0275f2ee7aedff7537fd77caef09eb3aae3a7c92969b468b77d8b0e18c9f5c22d9feefc6ff68f54fa16d87f53fc1281467dea1240104c6026a3c0e755b3092324e07c9094408564903e0632b121cf75845d3e438bf29391b75cd28cf4755343a4a003b8ac9becc3b00443a86c1eac3fb7f37b3abe402808c78ea30ad4fd4db1241dae85308ee251ef8073686faa51bd97b6fb9b79449ca14120736073d07749a20fdc61317e830459c1fff2007b1cbe1f5d72a88d28391383f862208b1d391c35a1d33a65189d4820b1547e2fc783811934c49c7e8bafee40e4c62622753a00691d8cd32904469c30e49a254410c49ecb41dbcf8a155701912bbad065c7401123beb831831b1dbbcfe640aa480891db7044cecb833e990c58f778a9111542af5f4e723e8852476d79b1f0d16627f1209316410248168e0237637bcf9d10489fdb1b0100249ec5e6ee0212676379af0113b1c5e7f1207a024714a51c64bec60bcfe642b0c22b18b79c1608390d8c930a1612243c3344c8868880190d8cd2851a3c44c0dd728010510af98d8d138b1a18162c3362d2b593871343f1a09b258c344ecb8bb39c1075db46a4a3d1978c337b202f577820fba68899d8d37bf1a25c4fea413b02e80c4aec6eb4fca200c23b1b3f1fa9337bce02576375e7f548c247ec48e3b709a60052e787908dc2507245c3b457c2178f3a36122f6576ac20621b1e38ec7093020a4104410418b71ba1e9e64a1e9fc685898486a473f1acd5170fd24894ac4fad1689adbd78cbce6354c442522973c20db784d89eba512917e4a44b1c72af4b4d493818cd32304f0ebd1a3478f1e59a53b4a66123a619c748cb359e99fad15a5431062c82fac48a1b57b60246435ad7a9ab79500430c2fd1824ac43ae552bbf564ae691cf8ccab3c12f3ed5bbb12b1b31f381e89d97ab49d8f86056bb72f86865d89a8444ca1edc7a2fc686f9aad536e8da1f6a59764d0d55a6badb5d65a11303da3318053f944567aa1be549fe0073ca07d976cc96ea5acb9cd6a9996717da19e396756360f3ae629e504a7fc40c72f058b2d5c4efccc3c459a68c22afe91ae8a34f1c3957651a71262ccb22cebeeac332deb6e26e109ae75f3ced2cc3c74fe40eee0ab779d3dcc1ebefaacdd5d73602267d6923a13b2e034e1a172897977be2b33333333336b5f83ddddddddb4bbbbe9910a048bca75d6b955e95ea094e085ce12b7b2516bad9a6b55f39a39f74634a763966559d69d65dc9e65d676a0b96fee79bcd69ad5aa84b42e418d0d4dd3b46dd336d7506f6463cab4a39db44468527c5883d963a37da4716da76414b02b9ea3805d79f62c3c261cd6984941c17d1d373bd08763ea751fc7b99dab0de4616d0d4dd358d36c359d91e5b42e1634ab9a4f97a71fba99182e7521ecf314579a50afb57e33743c6cf051c16923c85b6f1f645cf63cec94f50ec78f04f18ce85ea2b830e82cf102c5a15cc0a1c890202649660697faed66b61cc771eddc1be96e66e6beacdb3aad6efb03d483b2639b998ef42d731cc71cc731534b43f711f1687f65724e9eb27f5ce03b73179eb3eeacdb82a392259ebb7f4c961411f09c7577b792a13fc2af51d2f0197da6a2cb0e4fbda6a0b8da33155d6e78d9c3aacc997e3bf7c80387555965fbc558af0645fd699031c809fe63bc9f65c926253271d1b5772d6b77edd6ba767777d7ee9d233bcd3c5fa06468c869c16ee62db899ef62ec3af6ce39d0083bf7463a8ebbe71cbbe79765d3f9cb7c76cba9c96c826377c6bd9dabaebb3b88e7a51fde67dd07199773eb3173c565e1388e39aebb53200078825717724f78ba73b71a5852a4555071f2d4bdfbbaa8b73eee3ce58f67e881b60cebaa7438436c50ba06a59429659e2ee8545ca877f4a3e00b944b9d524a29adb5ce1bbaef8766e6cf05695df823df7f2f5170cba0e32d6ccd57ef0a76169f2506af934b5dfe903a4b5cead3b3505ab32c3a118266be8b4c8789cc0875d41b6190f26c1f29cfee91c131c9738351b476f969deccd56957aa3538326ba8cf28a595d2aeb373d0b16fc1b566599665dd19675996f9dca2997da471bd535396187c8172ab4f2f0b777737b3ad37bb6287bba8ce2a70ac8499c70a8e49be3a73917d0878f65ac7249fb99ca091233c05d014402392b7cf53003d3bf7bccab818228c1efcc8ce9f0e131a48a77777777f5c7f3a7ec6e92a7bc7956ba53eb8dc76d373d1500c3c9a655996655996d5aa79966959562b9d5d7400c0e1054a084397ba3da2a37981cdb395143079ea3f9382e27acf566e60e3a95346ea64c4609ccc27133dde48e5e700f3bcb0f35138dff991c17025499058b71cb8814b6450899bffe010ad8f7654f29aeffc887afaa11ec76b9b5b9e91561f3503705fbd561fa902988baf6014e635deebf7001d2c4eb4214a234778cbf83272d10960ff01f91ad801def21ee836daf80c8c3282243c0590c8a08fbf742e4980a700122588843da653a8f78113245c80842b66b78f45fbf95029353c21810f311097c8a012a5ffe01079890c53b4fe83c20f0aa24512040bd291b480240816b60d94167cbd380d13588942060e60b0832a4225395c89010f3c386144041a621016dcc74794564bc7ef838c6bbbbbdb6af2d5f128621e66aecc332928ee74a7cc336be65d05ed0c2b6515954274d2ced5163df287fcec78817f8172d99bb3bc0e13b6e574429932a59486b4bb69b33103d009889820b15c72a9b7fd5a9d716a00ba9a14193c670d4140183b2bdcc87a751dddb80d14d215887938d77c23621ee99a7336f08a6bcd108879368e051c6a3ed6251588cabf207fac407f02c78213ae70adb93ac10a879a6b1bebc57ab14a05c46b3e7241af8d33ae3089559a045542acd27cc816374c7acdb78fe7c52acd790347216abce6e3f69a058524b14af39c185ce923ebf51c850f0e2e95b28d11c20a71571b9d95660ef35ccab8c43ec100fa4c6a37edc9cff45de6555a7b565935d3e6f23bbdcd59e25ea0851906695653cf322f40085d2ee7acce333f09318fce6761f7edcc54e62dcc1590cffc021be0c091c98a99a99ad1fb3143d40c337729776423ef336f8102d6ed6d56bb75eb14c63c366fbd0acd55bffdf19648bbc00c33d7c0166698b1d1053242f68d155ce9fd2365a1639c7126c8884b643ea5dcd14fe78e38249921e6e9f1d361a85cc9c12ef7e260339cde71af667142957bbd8eb89a8fdccb0b6ad606de98d17e0c17f41dd2c6c199e12cf5fc98592633f6f90c8e10a0b4bbbb6bf50674d425056d3f8e276682341854404fbd9bd743cdccbb8c049f20ece3adb020cdb21d648a8b2aa2ee69200e2a6e8c776dcc539bd896a7d6765dd77536b119d2d1e6e8a98d161280309d9d8631871c5debe3c9274a093cf974be59ef34e4caf5b1364645ee33aba47d4df21c3d7b4754e7d6fa4f2b38b481cdd0f3ea419a75c1930fb58159c1ab8e43eab4f3d1e6e4d3f9c9e7a997c0d1bab6655ba6cd520e2191ee6ead3ffe0ec69a760a3a3da13743a727374c9c82e6ea26091b30341f6f8826911e493243da573344419b230d94199a2f56f504915895420aee887211b9f99818ab3a2804ca1ebe422bbc4209cdb05152386c1fc32bdf23897934ef1b22949493936f6f4709cdd569073659a13dd4707d8f3744dfde3d9258d55c08af6fa75fa9c5aad640d38b55eda7a0ef916b7d7b47254f53954fe557905985e38910a649d19d3a3ee5b8ae1c7a067a26f290e2d4ccb007678635b5ca5adbe7373842c06b8ad43c4f530c39ae11aa18a18a1cd70f1f218b4e72f76c8fbb995249794091ee4827cd91e25a3b8d50bab18a66d9fc4eb27400254fc1b1f39e4b610aaedb4e4b4442ef5fa07fa4babecbb2cce767e00881afddf7a54e3a5b33bbae595e4d4c87df7e5131233cbb279b3a47dbb4819ee74dd5b3874077f45efef07c3ca5596bdb578f54d757d7aadb47d9b94a7d959c55a22e46f062042f46f062042f46f00285d85be7aecb716d476f3d85b9d3e30122363946dcadeb5a4a29b98ed68c5a2bad3de514716fb34e6e5d87a859d5adebbc9a95b9751d989116e0c4993c5a2d424e954bfdf4d1c828d5afbf7817c2cdd17783d4291d9defadd7efe6689a60fd0529e9ed8e585de2ee88354b8aae07647ef22e3da0fa0575843a65fdf4e9b83a65bd4eb9a38c110c953b7d24f5f8486295f51c22eeb8c335de1d443b8698a7e4d67724c13c9bef70ed80ed307ae699bf922e451b437584746039985c98ce94c992319a29ebf68aff9431d2119a32463a4253c64846cb64e12475cabaf52e6ac4a9f99c213c87ca2de2a3e563075eed70cdd0eea8e2c3c998b2f2d6532fe6e19c47ab04a11d554a98b2c33557250071c324f6324693036f7e74843e00087120094239ae8b23090558b36e629db27e83821109473e92748602f0ea3e9befb4aa0312c020e188796c7894a1c2d21f75b1aadbbbac9443e5561f56754ffb8d16bcba89cdd0726e9d7975c30587d6c71b2d6fdd47d25b1f6f98bcf59b1fe6e99cfb6460acb27ec385cc156fddfa4d6cae64a8601918381f79b448d0e2ad8f3e92668782e98075a08c0f078e3b28d80d8dbd75afe423e7add7a190b31ef76da51e8b9a5d273b083c57828f262fd01f758474a6f068bde5d19aabd6119aa175f9e9b86668fd0693dbdfa1b4dc51474847480a9930fce9e57aeba7d75bef3a1e2510e550b9d27d5825fd268549c4e66f7e58e5a38cd1cd0ff398dcfa8d11f350b745a38cd15ba7200946acb23e648b3bea08958858659d4b456cde84c42a9ba2e0c8e908bd75012431cf8eb73a436f5d4768ae80bc751d57b3a65b0f7c216495f5223f9e03a39cdc82d6e278e2868f426c26d631cea81267d09052ce09d699346ab5d93257b582748659b53102f2d47f08314f78e505fafa9189aa51b33c05ead3c78ae4638dc100ddd1c6a859ce29ea5682e3bdd349f881d3f10e69dd913e0e77b4316af2d46d8ce66a6a896d399aab0916e11fc306c9868b4d924d171ba3253f4f7dbc69f237404f7dbcc1e1a6f574de3879fabd90812aa0ec8ace366b7698c4838f7462055492c4f9d11c2176538d197e4842c2810e40e2fc1849cc0f5f68212629c168074fe8d0426cb05223a02a5eb082a40c8624a264528e7cb02421ce8fd55081166267bdf9d1273988fd21a13da821c68398a467b0028bbbd1e00617574d80c31a2610938444b4cee83c242926446d8849180685d85f4e152818ca31cad23279fd856b9c1113e7c73968d922e97afd310d88a6387ac922c88a23717e3b476277a37da4e4e00731490723e30c1c56764003212434a6a50826c424a16b680b929d235186293618d5e7e9d58588cbd018aeaff524f4d5c7a9e52bc8b5ba2d0933a8fc6899c0ec0faba6679ff6e23224d4e036c4aae94090346af3a7a066695a666afa16a1315c5c867e6a5d4e44f5a74504e633503302e38755d5eb0864c66d1a95bc6455c7798e0a428961c21473ce392533334f1a60378c5bd5b0565009e34726daae5cedb9880a1744d4c64cafb9099837e6c67351ab07df3d1751b9c18f214c8a986baf6f540e41021869622a413fbd54ea99568719421c0579ea5e0c424ce9969032060e3070a5c04a1555134008030b58194b601125dda0c480047f25ce0ca553a142c4f0f3a7cdc1456a414d09428b20640882074f7d8b8999417a4f8ba44c79ea944eb9312510e329b56d14c180ca536a6915219e9a9e52ea51ebc9e718103f3f2f0f38bc45143fd63c7dcd3c757bb73c2d1272c275dc00a70a8cb525a8c284676d380d51c39d3e2369b80cc3d2e447effbb9c809a137400d0bc6bd671850db3253ecd124f5e078a65d7b5dea3979f3d186f452937ed658a726f5767cb8a3774fa6eb230dd267259f12d3c093252273be36c66469ad4ec59aa5bd9c10e130d4c4b5e4a726f4538bb5b8bca54162158e1b431789792ad1cbebe5ca5ce1f0e92f309a1fe60140112fd077e2850666fcf4ef8c3b7aad24785567e070081c2b0d4cbc0d60fcf47bc43cd5e7d0e6c40b9a2b944f4a3d1d319e140e3da119b6fdbbb9d76a560ca8c574685eeba77b4273e5795278e565319757c547ebe580e91b59a77da8ad435194066b160d9206e3707c5b972d29d6accda853d363f628b6c5b6d7b75dd960db15b22534c3d96a9686a4c53c6c85413f9d06a959da96999a4d83f416d4b22594724450b29fcc678633f319359feece1b9a2cb9844774c5339a2bca452fe0f2d3bd3164500f54fce879b0284b7ef4b4fc3cfa99d1d85c8d5aeb8fe66acc5ada96b9cac0f8b969311b4cf0a337f4d43b8ee8db4903b49e0c72a85c2eea21f6d2e6e757f3327354d19a68f9dac6b3c2244e693e724e7ce56093bbe2ab730dd74895c00c18a09fb565adb552afdfe93b0b43e566d508c85cd1d551a7f904f2d353754a11d528a84b25aa4150aa50bd52afc0687a644c12778cb1304058748c33ee082262d5742aa59412a5e58ef5555f52887caa9d62a72318a00983c364599f7ee3629e9d9fa7d829763a9aabea5c26ab4299525b4b7efa1caaadeac4a5dc53ecb4c56fb3581c6beba7d3ac60dd8248a4a851204d62cd9219c2073d7ca185891e48204e3f25354bc90f59603880cd3003365e202a890284276670050a5e33e020530333acc0c245121a6d88d3b974da428f4eb1194ebf81823bde1b94613183d104bd0601d1cf1f585c396757ca11fd9ccd45c738230c6c655a83c30a03af4c49d587a57311667145cfa8a29f3eea3ca35e210cd14f1298c54398f5821abaf102d384fc60dec53c36cf9276d36eda9436a528a15310ab4e41a522a8a01ae5c5a39c4e413de3a98759a4fb053cb5c02855ee786a31e11bfee2d3e910f3589f5e7f98a74ee7b136791598b5c118cb4f17c2439885c3a7176116caa7fb18b27efac6dd519bc02d8672cd9005bc4255e170facb04d61e607eba48219e4f3751c12c98960b460929c4038f7e76a0d419ef0e18a6a5c573a0eb278c123fbd43b9e68af34155e1155702ee0735c419c1fc34cb44c5cf4f931696a569073f615a39de943457b53543d31abcaa2ca83d6cb0a1666db18669420ad9ba489de93aeedd5043bf75e94caf8e35cb94d42f98b57734258d5beca7941958959c60609856b32e08d3c41d4f36fc8469c294f4d3616ec0424c5ba4cef469daf213a6899f5e2a72faa6284b0d60df3ed41494d00c834a8082e23357a81e389c3e7150f183a3633f9e38a39f6e1a6ad6e90b4e4d2fc33474d201a7a6e3f84e58f8640587d35fbed38def24d4a9e9fd9da0bc9a751a326d91424e3db0cef4792ad281c9f4fa797af2d3479317ad669d7cfa298879a84f14fcb8bd3cb55e3e2b368e3fdd0f4912f154c51378aa49b46a9552848195668e1944e1f39ad3c87035d6dd41f4f661daa391e16615ca0c35e6202e2734438df5d29c488b796c5ea3ef50eec9af9ffcfa3803c47a358b0bca69e2f20c25183403c482754af313cb6805d6cb7fb8231704459b697dad30405f8b582f4e695e9d650366cd2c9923cc92af303f5f9d239242a694af2fd599ad786d07111a7ce525d2c9ca9f602f4f574820bce6a3498dd194f4b2b362e5a5d7a14af4523a47c43ca54e3e8e97d5a51cd2ba9a8749619794cf5c7942fc0bf34b5fa806ab346f304cc2e18e303eaf79ac5933ae4e5de115f784adf08a0b9aa1c63ca6665d1f5947afb18c5e2bf9c8427acdef674f1ff793daac984e55d79c0b9a2b6d66e84f35fc0a8e47aa7b2821e6b1ae398c0ff3a05e73981fe699afb98ce61b10e329e9b5173c20dbf82827ff80fd28d76964b876be07fe58f1e4bad367d7ddb454c4e6ad775b0f65aeb5d6ba39539a7df2bbcca391e172cfde6525e99de69dbeb0cb4c6961d2f49266187699a1e61af205cb68aec61917eb8ab91a8fb4eba3fcb878727965a2707ab160332ee6e1d79c439a68f08a6301174d29579b7677333383b43e8343b25cfbf485fad28fd0d0ebfaea729ae94a7e0e817933b4331ce58f476878869cb99e237a094a507e12a9a4a7d79a4058b064e153ca5abb76eddab56bd39eb4d65a2bfd9105b7776bb6bb4cb3dbb51c5732993c206a5cc9dc7276adddddddadd91723ec3770a06038981c3a62626e8c66a90c9da13494524aa9a6699aa4b5d65aa936e5466b5ab321f080c951734371e837664fbd4b792a6fc7f348e0b90aaa409d9a5f8207e48c6982b7fa7e0609008f67cd001262863ca1d4f3817e06004820000f0032581c117af4e8d1a38708e09742edda33abb5a6727efcf8f1e3470ef8691a9033ee3852effed98903f05c1280cef06b63002522e11380da5022448a1421619ab05aad562b137a88c0eae6201c848370100ec22ce68113c24580a700052c8052ba8066ee05782a78af202a910aa515ea50a7cc18f2a2288ae2cf01780c20404f8fd70022458a7805288001d43880c7020212e0a5f8b6a4033c20670051e34aa9008faab0c20a2bacb0820a9afb484d9f10f0809cd18217815aab04989941afebfa74367d8047bfb185084420021188400b12f02a30010aa8809c01e48c1054336ab7052ce0820b2eb8e082054650510c60200319e83400c48c2b9d6a806a40670495bb8005cbd9776b5ab3cc6e9652a64c993265cacd53b2e98d506f396ed9c9d3368ee3386ed3b2eb7557a2266aa2266aa2265a2a11d1b26a5f4a2bc4740aa419ed2ca355dec0819a2b6aad73be8916fc0932773aa46e3feefb2746b4dee5f0b68db30e639dfe58d1fa662dc5c211494a5b94528d524abf0a663a6817826a469513497bd1ae792d734e161cd14c47d3dda51ef6cacc943fb0c5b8d241da0d96c1cec9d94dbb67d8dd1dd34dad6dcdab9f51205caa0fab2868e90bd2954edbdac9f285cb957e3d2b39efa58c2bdd32b775a651c997acd45a6badb5d65a6b7796bb3a6074d0d4780822f4a03bc227db139b9a191a1990524a694aab94db3aee9362f764a336630ead4c4c8e940feb837934ee9c1f947e93c39a7d0d41f643552bf51c480e53eac9ba36737377edda2370dc93fbc574ea094c49470db7db874ea370bcdcb82a4a29a53b955270460757faa9baf452a9a79abc1d2036dceaa59eaa03882bf5349054c775315026a5946627fba2bde4a0f1fae7ed77373b3ad93a9c2b40a752e814510b2b68198c4c0e8799f166719e143dc9c9d2ce0e8b72a333d4321c352889e3bbf1f272abfc4a1b8707ad5466ded5db478e35c8c3b3f90cac40b3d6be51eaa1b49b230a82f66d56e74c3fcbdc177345b8e106d8a32fce4b8fcba3863f841e9dabcea4a9d453c1192e6e0f4a5dfe74a129f44aa51e1d385cea92eaa0e1b677fd4180a55deaebb8ad33d5584d968e71c6eaf3cc221daa3f4c5d6348d107b65d31e4ea82302464cfb831cf4543405a0b6e0e20d8c08b8ec9353d172161a459416206760748c040878b7a2e4242051c106d807161c2d8620b258e80b98e4862872380c8c0384287ccc9c5f15c74c4924e8c7bb95432ae7d2e32628cce05b7f45c64840e342af7c673911137c8811156b0184164c4901136643db0f7de1c28239ac480030b239822237c9ac4d29801898b148838786169226ad1bac10c444e34c91273d93054840f3a9c004b4cc472748ba061862d0ba1ae081f1aac518c438306cdb555e9a2864c8c21d92ab118aa988323adcaab45c52cb22a47dcd0e2164755889f69aff5aea88356536b9f6819430b975ba575029798c55e15aa00e1f8f5acbd3145447cf1a3fb0b381ddcfb5c4404161e8820b269dc1dcf4544f4e099e0fa731111307044dc1b2eb836ae8c14414324b163c310446871753c170dc182ad074334e9ceb8f4b968881f1586f0b19495986c8b5c47fc18e38396dd5b4f197de6849e3d68862f69f02422050214000819e4e08b2cd080e1480b9e365cd0440d39b842863462d8a6a8a184ed872b5903907c389ab28216b011849c2eac9802450d28d600c2a77e91a381f044f6c4142fca6510a00009f05cad1b92882f72c1e0678cc7cc3c175109c206836e1cf2baa3fdf9cc76d2c8ef97208d01e4d84a5ec97743b2dc21b03bbd93cdd3346ae01b92e54a1f02bbd2896e76338700c495d3c648f6660bcf3f70103ebba4dded99a0c595ce3304b06720687877775b1eba2e478a2b5d0577f29c73fa4b1a5752da26dce04a6ffb7c93c69573ce59ade8184747c64c293fa6593b3b26fca01b9cad622f88be41d4a51ed74a6be83b9d9a1ed312fc827065f873e5e379c80e778c89a999ac12666abaf48aee0e2a9b2b9dac66b0271de38ca8a09fb5bd93ddcc154afd82b652cfe438c9314fdbc93ffce33f404ceb4fc900520c7a5de923d77232c3597fb85ca9488d2ca9405f72ad4ecd66b110d70adaf1dc0aaeb492a7694c32e7e6d51a264fad419e5a41567ebae9c53cd54f66ecf0d3bd3126eb25a84b2de669fac281a8c9506989abcbcf17218fcb0bac9bb361681f56bdc61812aa15a4aee999cdbf04356b5e3153d3b5086d718d31bdcb4b46c109ab5a8eebda52119b37bd58557d4a219bb353aec595c36655e65b8fe0a964959669e0c8b588ecf86d8855d54d68a27aad2733bed6f6fad2307cf579c5649d82ba7d98877a98e2a8e44cb7353d0c19aa190120002000a315002028181209c52281280ba34cd56a0f1400116d86487654341947a21c866114c4400c820c21c0180280310620a454d1d80093af87d57008e107d0f382eae4915aff97e6c010ff19c4bca31e926f67c14a71aa581f0fdec3c384c0a429db9435c7e0eea58c0612b544439cac7356348e7d685eeb58813a6dc52eab77e3168d28f46b2a33a886b6d8c369c6d8cd12a41f546baefea52768f8167673d449d7143c3c8dd076354a33ef0ccf465dd5ad3d965f9f214e97c9e9dada26562ed6b237a60a33853167900c8a94e3a141d803c2c08b9528c6dcf56cb870432db74c442cdb9652d13de599880b539508fef38f34ed9bfacf37f5d27d2fbc81d59bd89784a8eaef3706cc1fc63fe90719ff582931f9f663bf21873710bd3a855cfbb93ad0bcd0e1cb5f4bcaeaff1d52ead31cba5e41d6709eb0865c05a848147e96e962c5e89bd67c2d3c07e416c9d7907396ecc37fcf9230f82112393b9a53363e4b608113ef2ef61f0551a7e167d4505da9edf1e5a684989ec797b2725ea3b54454e64911996dd8fddb78edbb2941fd2b9740d5b3374dee34f1c53c55b7575621bd9d9bc44138a4d7f695e11ce30c3cb073a169b9a4aa2da0d0ae844c81d281ad5982aaa508f815424e91c55b217a4d4fa8dbe5004bed959a3752efe50a5f41b38fbba57491fc2af4701f3a05d08914f3a8da5ea9cae9111f353ab850b0530f2c2f0c1b0354bddcac308cfb7d38178c5bbdb3c4a54b7271091620719333a1241d33170adbc45eca1058b6d1b7e9e89df4eee2cab85d4653a9b052f82a3caa2afd3f283a30ad6f651bc87f75b0ad2025f4a252dde964573d08629ed56bd4ffce29a0c7eb2a0e8c798184daa538ac1e4a020a19e3905091a00870132078f4f78e8d1396984968950ae4349e2ec0cad9e2623f58de8e705a84a04ec11ed69181b30ca0dfda17211cdf0480b176423f38b9bc5b71589c3294a89ea21b157a27ce450c8fd78366d95f6cc83fcc975dca8a99012b70becc1119a3d7186b7ade561604251dadf57aae54e633109dec715da3fc89afb81b4db292851c403a85cc85e59e68d41d5f4c13e13e5fefbc9d1f9139262bff219cf5df41c134b0bb7f8a25989bea18420e19e801cbdd483a747f9d5de180459c8988248c69d1b86a37611376dd2fd5a82766a5f3a2a2496892b6412ce7c9e437843c132ef3e7ba8d827c03bc54fe366448d37f8a9a6a3c178e5fb41e8bfad9841d76ca5213e128150c9fe7829c3f5d018b22f4eda8f19dcde6ba443c878b739fb9c636330594e765658efa24974a0bd812842e8cc387a2b84f98d5eea8e80565e8a2a848088bddfbb9e1ba7e248fcdef89f1d8f43ff913cb258ac247087244065c053125a4acd62c0f66511fb38c48a348d16f6fac7a4425bb5f57e05c9f9dcdd4c19890e0e45f88e09bea42046b705e27b1d3ed108ab697d6824452385cac3164b121638ac187566dd6d963e5775bc022bec77f442b376d6ba0d7ce5d447e439853320957734dfcd3e3b617fa12d759eff61675c562014e48a3ae0584f5ffbfffffa3800f03683455428c27ce68f6f74cb2a26a1e78d021b9eddff676442ad1bf58573567ce920248a863a9fbac1decd72cd0e19f96ee91ff63ee3e9fbb6a31dcbea6582fda9b0e74d608577d28162998567733c1e8f05abf1f7b4397f7f0dd78d8de3e251bb38ffb45c041b35f1c17042714e3e58d3e3f5057e475679eb9db52d312bb8517c661c349fbdbf875bed931ac4f3e13d45f1861a7f51c82acb4d790fb8b086417c95f08724c26e1f9c8c0900505ac1d6053d9483ec5266a5d08bf5b5e31abe97473ed65dc7f5e544d64caecbf324b328e004022cfae956db54c9d4cc9c4bb88e508d9fc08798667c2cf81173582465b85c9eecd3dd78d88694dab70191c243c688955a14559cc0bc0a98980150496796e5034984b4dbf27d70da916810652d2b4d5773308f1ffafd12ba2f702f0c08579aab66ae5fb9bbe2411484f5c889f542205d7034a442d10a80bdc45cd9827ecc90d2666b645c5522f7a5e6ffb1185bffd98c26f4fa47254e5b3f6e73c8791828db59edbafe948b62d96e047365eff035c38aceb228c44c03f08c14b289d3c4bf0f3870d744f2e0fe5929c94119143b2f00d91d96f7024bebbc4706fa8f776f4b0e4e0a3439211bcc0bf0f5ee0dba3784a02ad56e9b63b234bc95ef293ad38f0b4be0d5557ab92bfcda7584424cdfb3fb744ab5a32a68c3c8123d903fbf5ff1ea750bd147043741d1fcd8a47c821c6707650392b0fcd35a84df178cc7b20bbe906ea65f076748afa2bf181f8cf82c06dc4fa1f49050cd715d25f1a4892a222609fa81a44237f8a5f2b4a25dc33325452e39766b82e3ae49a8eace19608e1bac4a3cf33e996a277d553e2f6136549182aeb566a4b74a4d2fe85b87b54bc1ab07e9515b437696c63f30f0ce82e198eb91655a3f7277ddcf04ae9ab07926dca85097e4a7aa0fe1756f55ad8985681080a05e79d887c4c5bb609e58367968037b0a8cd9abb4cf118785c35a2b6221749ab1e5e4db9c173fbf86d457f1ea0a93071fac4245603e6d247ee45efe738e8681718d05c409f774d73cf4cb3cecb94b4d45c0cbc0561a2b9c8cb07852e6c0ca97617539d3e6a022ef0b5f26b50d0f1b0e4b7a7a257f25e035e8161b555ec837e9614b37a90d6f917de20d336dbada1941d6cfc77963970395e7eb58397afc2b17dddc72f806d46ea3a63f853f441294c9f51a2bab0f0fddafd486b2a7efd50c729ca254b72b9287a7cbedda89461754223ab9483d44aed23a55402cbacc6aeb72e47008c2a4af8f2a89b9eb2ba38b32d90d32ef2950aba337a90d59ed995316b732c0152d80c2e62fdeffc84213115b157b3cd2e477fc1e6f1e4ed1ad8a78f9c50e50ad00d334beb77d3634df39fc58153ba26d106a972e60b4c99cdeaa75ff5c15f69db2e2c6c4c248bec2906d9110c914d61ca3db0003e66c47693ab7ce01a2cd304200038695ef495ca0316ea2f7c6916944d961c7863c5b7c342ad5c8089b5a6b472039b0298e3ca3e181820ff43885a31b5636a93fb35ab62b91efb4deb14aad3229750e1c4c4cb1a106056fdafaabb9624b707e596cf82f2e79f50972757258a3c6f6c18793ef332b2195cf4a3a394fec721dd709782516283a25c29a1001e36349d99593205e557589ea1fab0cd896c078e3a55e46aaca94f5e5accab943fe0faed6fd5e0d29aff3cc6a2dafb23dad48e724ccb4ca404436733d3fcb4d3c2c8dbea786615eaa6937ab03e31345ff4355cb46061415196eb1c0b93035632baf9e0bf49fae8547636f6b3e14d71ef55992819b03de9478d5f872a0c91e37b1931fc4fe756a0558394e8f2a5ba3167e6aa0ee872bd97c6a720db711a1e302b136577778c20bdfed633ca331a97360d7da986b3df3242d44e077e72608e55c6a5b251310f8e99d5bf667eb285f9c10c496db2bc1cb81e5a818f6b8b31feefe30ab7effb78443b6a5b5709fe4e940b8eb4e7b3466e9b42b7b207d17302fb42fc754ca3be3e709b5ef5945ca1b3c5c5e9f5ed8a30e567003dd518b5bf3ea8bee48fd9995956e6f500eddbe61ed0442442d3bc11e14d8c4bbd0ac071afd93d7267b9b41ed8fbc4cd3fcb5a9fb178e1548f03b2c221930fff2bb6503eb05a38446e6b3c968d49ac8f056da7624be82c8a013ba0bbe0eed0aef462e5080705637b51b8abe91a7691bd960d718622401948b9a300728ae56c4fbd92b36bf025725afc0be098b5c20ea89b1cd810fe5d8ab96df77723b4b26c533c65b0cb227068a01806e6fde1e3ca4f75d99a3341809ee3a956f219a1c07da907cbbfdcfb22d56e253acd1d79641e0bd642c1910feab51016da748f9df89f75ab1d6245db7b24271860015b3381a10c26c896e1c4a4ede7157de275194bab03785216f177b7c1ee651545358a4d8199025eae1a65a9c0d9b0f25cd5ad5c2ff5d1479c7008a901770e17833ed58a8d929d5372ca73aab2de538a5c0eab97e71f9ab3536ee35f4b440da85cd85f44bd5690a16d2b930a11d0888f8ce5344f51cbac912696bee2e563e3ca5d15f1f49ce45ffeaeaccf6d0360bcadc0b8e7f68e4d48105f6d9042f97c4cd9cd18db009557e80795ac18da3804a01331aa408f560113e38c0bc7da84ff7459e0fe50764b4a949c698c1b19bbc9d4e43887d367eeb51922345663abebcdfcc16a697c9e1498bbe1f6d6bce3bbe7b9f4354114f3bdcbab827f413078bfad1b9eec1ef26e64d170e7212cdb53e6b51fd47a52558ece7d785c1625e07bd5aa23865f86d793e1bb5ed69d51a770c560c5c03f6860b485b587d2c4b6def9275c9b03f8d3f99241a5bf5e947db8d10bf1a87229f2f60f5b2fec79709b433912b52d41df36667e75d0916261de4ce0cb7f1bad8fc793c8ed7829990e0bb0aa0bcf82e2c4c482355aea434c8e50b124981c595a2baa803a81041731b7ca92f423d45e7eee63c70adc163d0bda203e8e4aa0f2bc0e0abab805bb5a4eadf4fed43434cb7656aa6a2244cd671d25fc0b690947b64b51262d0318f48b0fd7157d5b05d0ea575fae153ef8fea5702cc65cccf5e5e603cbc443121ce795201693c5ab7ac2fa7af989469b30034888970a6ed36d250dc4e7c60d6bd8c23961c679c808bfb6a28a97d06919ddcae5f2e52c675baedcf2661996556d2e2e719c652c2d572c3927657507d7cb0f2bc84bcf254b9683e5f7d29f7764157251f9cf68856cf0e1e315af9e816b3cd9d1ec93a85dcb89ab031051ff211c74173f147750d9a93c42536a8ac35ed0cfac68fc6bc957d0b27ec8acf2591e7025fe23bdea21b964ede69fa67a75cf7bcf74acfce48fe1fd5c9ddaaaa31e2f9473eefe3451df6c4427b4c47c44ddb1974639ccad26c54b5e44fc119fae1f04dd3ebb018a67a13d99ad03466ab5af82100de747da4de1ca8ed86e92d656f01118bcb3b64eab3fa5882786985c5312052348b0719c29a908cf50cb6bc084359eda5246733dc7ff34bcb80ccaf2ede8a3a501423301f85253d96ed290b514c32da8e95e763f6f8f4596e26ba08e133da19f468b934cd38bba0ecd178f9d57328b75fe6899d7478f4a1b1ef12dd639ee8da5970cdd3d5b9a3db26fcd0a618f964baac90f876ad838915d21dc4a8b7e2187f93b6f7ee756cbbba6e65c0a773b17a5d05a1a0eb0627e32254d29f6bcc2137bc8171f9aba6bf0c14c252552bbf169d52aa29b1452d801c2fbc2737ef7a0c6eea75c73edbbc76025b7c2e5b173d55cf8a4417d3a9485adb282a77bf9c8657f7f42c8e9f73d3f1fe20da6816feb48feb290b9df8b728d4a10d454c8ebf50d647a640ba3d614d2bdfb4fec046f6ec623a93ed849e635362d23514d184a7042b5ae45e9de1c39c5f50f2b5f7e25e8743f1892b2126e28afaac9f6d265c4db114c9c8d6c04aed9ff2709034fd2815f0ecb1abb84a41d19bc93fdf1950379242a7cd6b32ab257e905210dad65fa89fb9508e3a51345856a794faad129447f9e738915f4b3508876e08957c7a0c56c2c92d22b45b928ff5f341fb353d967ae34bc010f8771f2e897ae13d447a82139b05e16049d1a50387e47204f40864c469f5033b1c9b8f4f81f2968830f58baa951c75570e430796d53713a8932b554291a3a9ad64425e25ca1f8a3d59298a840481533b4f46154812ec033082cd6b2b4bd2d05db61829d22ed983eb1ed18739b25929be7f820cccab4288f7ac046bc41dacd5944a03be4a60c08e2b500dab500ac085dd440e37d92c67044c715947ed2144ce6844358a4578732bcceb83a89b4581848b5e584c6a7de3c6946595e5835b310cbf2d1147d1d622e8e37a479297c6822702f8964949bf103d1abc2b347c78aba0616a0731fbbd44d6343766a8ba9bd20ee53e07c9987a9069e37367c45d68f726a3aae0ff94d13ad89154eb43c63137062249b74e98ddb1587b8021e5d8b18083845ac80eb6afa7d3419055caecfedfd4f96cc2b9ffc23330dab1270546a58090044f4e63a1450d82d81688965654d6c296f0db1f3272bfadc75b36a055efe0469fc99d852602df8042c0b1816e8afd64c2c09e82612075f0b8e9c2ac39d391e1a099c548696650ca3e465208e45b38ac3aa79d166193780ea3ea9a90c7b256996d81d827c400ebf05cb56bdb1f7b7ea5ad4a2a61979ca407c76eb012493bf104157f5e6ce125583c66e4cca58caa7a9bc084aeaaf417d8775c7110509bf469a4891cc7177a3669c692c90ae716c77e6473a5b16a7fe8485db3cffc4f45f2031a03b7a723330f7d56fdf760a75d8f8507dd954254ebf14aaab364ab5caa1ba4a300294a58cfb48b6e488dec4b295f10e622669be43cdd89bfaec4a30424262f5de21e40d5c73d5d3d3755335e26078612253bc59f7d5e9d8738bcb23c349aeaf52d1efc5602968c26e4b181c0e15fb66203d7b966496c182794e76d181169b0fb740621c2bcc7b34b32d9d38c642938420891de4fa02314bc9f50e68ed05fc51992d2598df12cc0326cffea6f054192d9b923c03812758f54a258fbfd913580442d51979ecca682ff66cde115f6af5cccaafaad16e60140e4a5aba538962064a0806d4be62a2efa6fb5e78815374bfe131a98d81c0376bc67a52f58f37274672d6f03e858ed766af871e6a5ce3d6991d036aca7e2edc38ea8b2d84d807c8bddc048c0ada0b20802ffb3bd3f7d9da93d736e879b8e9b1ee34a065987d6736c76c1c5e63a4351857c337edac5d9861cda44222be6a1ee827baaa102293cbc24ed65bab420101f144949671b9cbfabc79c9a732cdf9f7238a3b8c413e292b01996e802f86bcf2a2fe870b4f5d59ed865f9fde89549196ca71f7ad1101340e9db7e7bab16ee97916f49d68024eaa5e0f43af918d8a744c6aead0154704482b458f12f7374eef7a2e822f12992a55217455ec502b11fb67c8738fba50a5bc8590b1bd12aa0062edaf8ae823a56ab95345450528e2fe8dcb147669c98ca29b663bb857827737f000f7bf5b4e516644627fbfba5a13a2c30c3a509a005ce46de317c9c92027f7be5e05eaa3e3671c2d9f21183828492223fe0c08215f571c1af54d33d4f1a51b424a8c5a96ba0c384687605736b2dcca7e8bd6b79fd1761c1e6e42f9a0bee9370eb44304266ddad72412e86916cd08a2aca8ce49f00b6523acd653e36fe482dcd325ce1fd5f8ea916d8a5ee8de2fe7328599eeeffc443c8c83f653c81c59561779cca6403c03f092dedf6e6805a44a0eb1715fea52097ef096f00897447a1e68382c0208ba429691e252a87c99754c4879f15834fc614b23a3d12847102f3e6429fc3ef642c923962d3441eb08c9d770e5811fdb0705e2e409fbbca37ae35ace3efbda3bcaa8761b5d31c06c8c24fcd49e177797d2442f06efd8e77fc521d2356bd545f49b2bc8a561ec7d2ccb6a161c2c21b5a5409a647c23327fbbe7124a4278f316df0a5907cd350a3b91066f4b0d11bfa585f973c26b94fa36739c40033381161bb5f475ec0cd64c43a45f57cefd478a46d7e44b558f12228ebd6a0623a48bb8fc61a5dbd4610c9f816277f2110a1a7d8dd0456dee588680cb024595e9fd0fd7da1879f15bae4ec48fa0f32da853998e5a6c88a2e09077709a3e71c6539e804c57a69391ed2de97b480c86f6234039711b3dbb269191aa312be1370b148758cb52473c69e586ec27fb1aff74fb539fceaee9e18ccde909075b6429aab126bf9839a4acf7b143f58372e90e744f1bf8b313f87b9c4d4944ff07174a30afa84145a7b5cf50014fd1a982dda77c4227f0b788ade3f9ff955f1e92d98d2dac36941347b335fb711bdbcefbe22f91d7a499ae5a7502d533c3d3af0c25cd73ac21abd21bcf6d216ba630c5705432b70724cc43c588faadfe5dec5d8e35d035181cac395dd4d286f2c365f4270724ac63578ee8ed50c8d4ffa3c9ac993958d2d14e115bef45570cca231e4ed2c5c6c044b7b32181d18847dba60486f5bb5c8f6051cb4701c3d0b04c5238bbf2f065ede9612c60900af14c2c8d1058d0ba40cf92accf1271295f964a0c9a9b88be24ab25f42bcc72cbfc6191c25e0cfef73e544ea21efa5b3819c5456409ca6b911101b30f5246a87e3a1a02cd4d6f50b9276503e756b1330709828c635da8662b38e1fa3bbab7a2b220b7fd116b6d45700b0ed704cd61b455246d4b855bf43fe4782620bc85a4581bc6bb2ce8c54112adc067b04519e3b4d0940ea99c532b216f0f7e33ba95dd6806cc1a9160c951b8de42d064172cb8563be3bd4e815a5809c508e2c01f3440594f74dc6fb99f1143691365ffae2b4e22b641c1253064abcb7191fc65ace105699db7a616bd101a986fd71e5c345064dbb83e1213f95c01c21d48dd21f39ce70215e3cb02f1040f5d47d9354ad84dddf665394b9ea478e20ed9051e428fe0aac5ada34b27a5114266267dae7f8595e7b234690998317919050037a42540b52bc3e057b24dc9eac6b5531915d536c85bd478d61606c9119e660a6a01e982231d8e70e6997939d33ee33fb26e1db1688aa7f6399dda43d72064f0dc368e0b0069186ab479fbb1727b7109a355decfdbc5f34dea8fab9f8cfd79431b20f05fde0672fb1d1a1a55ad0d5fa991e3ac145afc5d7717668424713b577fbc288e97e382769ab6ea0aa738b5f3ce971023c005d96d988e869e8733b0ab6014b419aeec4ea40484e32e4d9f95c94bdc9a53d38ddc4acb8aaf4cd9f25efc4f66444fc296d82649df3967b21692ee2b3f4dcfba6ac864348d8068d65bb8a1656f96e065fae246de75c536e76fd25ffa80e794b15a94905a54d63efd71372d5fb554d5f75130025c169c2475f24909a37206bb72b6ee9809a3561020783c894749e4602bf163fd98c5a8e1599dc2fed9c27117cf0ce7d5ea7cafd3167e93d3b90f3b2ed6a875c9e6e0c6abe7d42f4398fea3e808c3b7174033a238c83965a581557319767eb5ff6898d3c6da8090192cbbcb2b13783ea16422d64db54c18d75610b65d3b62fa32d81277d4cdfffced6303224e50422cc0613c6ad73e39c1e13890278897a9fc662488a9962ffebba368b2f6c3d46958bcb63a6c2d89e5d8ddb85a6c98666f9a092396f1ad16543cce5e90d0c69a8bd517c95adebf7a7119a3c0d468ca28e254baa104634cf2f9ff63c64078a740ad38ccb0ddce88cd530faa262b7727499eb1fa00a2b2d54f1840c8d13b31a0efcb302c12c426a1d63d151fbdf32fc81f577ded8f534bf8af537ebc2bbdc824c3ad0b54bfca6ea53edc1a41b08ac8a7ab3417d5ae64757d3198f065d7236006caf6b477f48dc18455abcaaf39bcb2a217ec0c0b322a178e09741a07bdf5e94a3cb0fed4c557cc3ae4c8d457d258182371d0528558d42ecd45bce68aadaf8636e38db64b2d4262995e12c1f91189301f94d7c02c5da82d0e3aee2199916bac3cec3920524ded2eacaf045511c74034ba5724f93f57d00ef78d384fc08cf8e60c9cbc78b4d6a781f5dcb51620420cbe220991257664b029181c003eb9d89a88eb24c458b5d53542ab180176c69a2cec81fc0b1aa3577e9b67d461113d7190c6ea8ba6d44799cd741eb46b43a8a21b38d8395ec154e9277ed1fb49c36f5b61cefd8365840866a21f30bbe8cd703c41ffa31418981dd4a9aab220703d8b5de65290ce091b8a492c6c1d29c68552b58a15df58b37fe7a27648146c45d10e276c9cad63f5d22d98cb352c470e8efcb858db881be27154be0e4dd033c638d1cc49541deb6b90e5d878de62c58fada187452029f7bf314b3cc7aa8997aee0a326e60cf05c4489d40475d154c403c2f3924b2a85fb9306fe0917b0cfdc1aae13763671197dbdaae78bf335aea8643549df88114b2e2a9c64c2f0e58c92b77eb52d399562977615c4823fe17c33dbe9d09f62bd2f3faae9ddad315e104c5e6312d07bcffe792d866e2b5cc9bd7773b849776fb5e79df2f102a48c4579a0b64420f42aa61024782873cd1087b5f927c301fb06cd9d844b44dfde456667534d2e7a9b1893eb9d020ea34ec99d2ae9c4253e02ed221ccc084159635011ff1e9549ed5662163e43a9e15ebadb0b38fa2161a7ec29fb12568e763a0ad11abc0a8c6c3f41761bf2523513114b0390f8c17a4379c26bc66cf1afdfade28c5e47ae53e36871a57998278771fe257f86643107b93090834ff516af5ec7b49c2b2ef391e0906ff0c8567f4c8eb082d741a3e9f4dade60c97e25c14d17e24ab234d046217af976dd85ae0c801db0c79ae2bd879ea91ba10bce8b456a6c15f38dbe2b4bcb500bd0b5360a4cae179a71fe6aac4b67410dae6b2d65240f4fb5aafdb43888162d5e99ad03e84344b11c4e6b3e4cc20c12810128bbfa01b1304a67e8fd97937f4b6a246c190c9e2ddfc036756b91c91ddb02ce3f04b4045281dad8c70474a220d41a6b30dbc5628564e7497d6dd6b1eb67be7582a3c494a5a26369a6fd1ef05fa0b4b0208567e0eab5203551bf26b4da7daaf62e64f41bed47ead3f59441698f23d23cb67f0deb3c2ee0498fc242adb4dfd3509f0cc4a31484c239c724ff416194a5372a1b0014a96443476c623f0acf8cf7a1d5d7cb8f4e39433d85000a4ea46e9125bc8fd8b01ef1dec581680c36c0e3e867503847626372e8f465febb14b92357b4f8a0e2c71ec372305464dc80318c7e91422e95923182c9aaf70a85db149dfa605184de1d14a1cd5ae171eabd8f3204c5c29e5645701b5683e5615186aabcfa838894c4688898cd2207a88888349ba2e5315fb679ec933461c60fede983cc09a877ede71ad5f54353257b9a1bece7b568be2b2d9adddc2ab2bd1645912c7b6d15e7acaa3d0e762f21abb72ad02c2f02c9141ec32fde3f0ab813e68ebc1424254860c3226d0b88e8c959b8d73202da45f5903981fa758a6e51f70aee60314ec5453ffa682042ee229c88196a3bce40f90fa43ef301c3d9334b319e84a95122d109647b273c6bd3e235e305c400e497b8cc7635a779a2109300e478d3aeef1b45aac2e8901458494728276c1de93b440fe65bfa7cdb3a28a0e2a6b8d7481b5ab3a1745a8db60cdb224395be565738d500660e74a3872f7f0f9b1dd3092339d581cd92118dc0a55b9ac117cedc0d2006c0c44aaacb7e684587df5797192b3598a0b36cc0142469a4f0f70564e3c75155fb885fcddb09202283bca17e72a019f9f2b1b1ac4a2ceab20901b3764afeeef4f1ac6c8ce0af7c97c01548a64ccf3aa1bb0c903ce8ebb44d9a8dd30cef6c79782a82c98478c730828cdb3fe92ad701948899b925cf83b4c4cb381ca30e3819a93bb492241c1384915572fa07e24eef8d65d5a8d5314d415523d219c664c52cdc31f6720f1c48515916e0b2c9239ad3b3c27bec5a3b6fdbffe3c8dc0da90705860854a3e2b59dc5ad3621217054e5d236c726a785e73cd9179d63fd0c47327772164f0cf80f60363c816898f23f281b279059e663aa152a16ab51a5a429a621205b0a7f90aa3c4e2dff1b054fcd93af4b8e249a609de1c5176a785b12d101370615af57a58d742b1d49efc6b09e1ee535252db619cc049ed2adbd9211f379bb06d52cfdc40f4764d139f53c5b65d6436d13d73b481569838e3bd1b44bcb8002864fbe7e1c11db33347afc142d3373bb741d31b6be36e82767afe8585459e618f49a31be56d119939b349020f47c747fc23821e651dfce5e7c9d2b0fba382f9b273a6474249d316b835cea4bd8084f6c2890f2346693326cd5aeffb02485c964d561b07df870c731f18a7f8cc53de964f334b2ebf2d8558a0114d0932c1b06d02f216fd2998dd154c2ac4654e26c0ac7116906ce3b5c5e53aeb3a56a465e500fa852b5ad7065084903fefcff76dec3b670e6df6c8fd273f8c3b8b33606fe52ab7ce0928f7b4d20297d2cde01a6463baff446ec22899386293809d16423231a78ab20dc5c34fd39eacea7c80f3bd66fcfc4f951914966fe3e787fcb2470d05a9b0d50dff8bb98c238f48efb417c47fdd33694429a92b721d2f9466975d14927c1f57f74431dd6ecb2de10db5c2db6756d731ecb4d53a710e095ad630f1106dc2aa109f6aa146bb4286ff1dd3df10aaae9103191a8616f39fc25e8bca8dc3a6060eeef8775c86342fc496e18be385e93a0dbabd9d6eea04f8704c682f204629c8eaf84bba02c6f845bd789c2ceb307c4cef93f18b52c053ccfe0f91a580d1862a9fd07ff5691a63220e8da4d8606bc6717ca9e9db46ec3e067fa342f5d6bea8d1fa70ee09af5e344ae82e743af0feb55060fb58c14175c2fb92ac3e5672d354c270da1a1c179c816bd1b9f1369308f5550419e1086f8e85251081a530153117cf094364e74d26321a9a35505c060b7ccadef3be13a10680c78945b97db88626b5039df99aeea0b19e9e73fb8970e2cf7dbf3d6516f18fe2e783c20f5390b9ae00683926c639c41be4d3a0ae23cb2bf026433639a400413ffbd128c2d2ae4881bbb83679dd935f09f95d2ca387f8369812dd100c839ea189fa6b0acc711cf57029ad7914cba159fa8d426d646cd86d6aebd8f63a4f9fbffb1291f73b55c3dbfeddcde8e669349d9fa884cd11cea5ba24122ea0efebf119fcd8e60fa5104cc03ba2204a73e3dfa0cdc1e75ae158f5a1a1f1222ae3f8d753b3c82c8902d7a31eea7d665cd300582237895836231d39c50681366aed62e1bf656662d715cf5ede9a9912ac1c70d6e048dc41bbc647736028d784e303c62d008ec8870ac588b865843a2ec83b430a2e13e16bedad4c8d7cfeef7f6111366d6c3b086223b782c8347adc1f5851a24b2346ba1e6e98cf8918d437f196d780d02fa26b4b9ae7ccaae7d607cce4733427f89b7a0adc271957924690293090595b9c0011b8270bfa65e14f6c386cb58f04f35f81c2098151dac6c384c65dab68e4a444306559e99d72756b236d7903378c8bd92b7e24046186ec47a336dd5abfe99207daac89827782530be56522034405a06ae06234be6be05bb4ffa7cc921b916d39f06703579af9e78b0a07a5e112572e075694abeb3fd330fa810ded3514f6f1f9f0d59132a0bd03b3750d14172434b3905e67415eacd2ffa6cff6db74a381f737066738a3884c8aaf0100a6d38272e7367c8335723d23a61bd9414ab2699fdfd495b72d90bfdabe8661971f1b1c2bf1633f5170a195ed97a8066e2505405d50e2385245a1aa2cd34ebe9516da4bf134dcaaa41e17886584e8768fc7e3125ed62324602ef989eec3454811c886802d2eb1948d406c6fb47235b9bf03418a48ad073d368302b87a2bb6929a795d9aaf400751f9d190d86acc35ab72e68bed12087d4f1d472f995299139061400924bfdb21142c059f76070764a7fe312c13d98dda8a32e5bf4148bdd4d9d2bbcfd731a6764b17b881ee87be5b7f6275eeaa1cdef5d01557416f6585b87522958b5c1c063b009cba5f0608382d2276262d735785d0d1831dc6f290311e30a6ccd8b921fd8a7001f4606610077030dd8c5f6aa23f89aad5c055e47df0077edca3847fbf5c6e79fc48956431481098dea626348e703da8c4cbb45b9cc41b6057ad3522b1ea43f74a7bf3ff47b9fb0a752b4690b046b7889728487ca631c930c4296e90412163d8540d92e3078db65f59195045d36527a553f3d7aa7b3e70945c0e5bada8036f50eb490ff6782cb3df6ca0b3410443a50bcc3a4e733daab956102fa0d7fdd2053ed44c16f0fcfd84228ea6bd5bd6b5765e00659f5f75211582779ad3498649a7af18b0a49a83386af756a764948451c0ddaee01a3af87535985290d90540f35fd6a4c261b437c48588ce0a12691fef668b4cd186a61ad4310bec962542446647e622bce9895a98c93388e51514fbd8d77dce51892a181e08172507703c356f075c70fe8cef8e0cd768a53acef6c5ceb0d7700cedb52be9ede9e6407ee0484a2dddbb7ff7955c763363f0cb63682f68e039d926d93d61cc4c3a142bb032445e3bed8045846ac4313c476663c90a92d2d7304732b8054c9d74d93f9b4fd31ea18a5b0f3c7ede5f999fd41d863b3beded17ac151d28e9b42e48acbc3f7fc36e2e595bce762bc69481bfa3c63240fc1ac8ae2a79e2bc0e6c1a6a8efe9a97c3986aff71f91bdc824a8dfec9e4acbacb00cc40136bd7f88a484f845d2d0c8173636a697b1011a95827dd57a10b0d4cdef2b85cbe80a3540ebccaf2b133c2660fbab6256b4d5d936e84bbce24e9808246866ba906394eb84151a8a183257747a9cb084a36a557c4e88fb8c70adff7dd24190f50f1235625d8b456ba8bd131a561bca53f22d22b494816ad59a26db4b7cd1228b6381317b3c7c596766887eb1c3c2c6e5b943dd1d6251916275b3a935514de466a2bb65d33b8f9994e025b58aebba8984be1b00a0fa9e2974ab241edb3e935c35c3adda2094577248f2d77ae330c50dd58301a3008b0fee658e4583ab48320cfc70e0061476abd18c8540fb448bd3642165b8980c0308101c4cc642f6f5e5f946cf214c32bd421901a5bb8482a12588ccf1b6e3d58022b666b355fec1192d51943e6bd843431f3f491665e54fd72acafcfbcb21bc721eb15a0ecfc792e0016990b98ec4ea114390b6d656131f15face5c24490a60ba2bf8be2e3c8af07c0e36693cc2f97a6f3982d3a12206c00c717470189cc3e0a9e4a011e52361f0ce3f03aed4a3398163e706bd645963eea34b6c91987b58b1d6a06ace56657c4ee5787b69d1afdf7a7aad610fcfa20462f96e30756c86f76473d42b458853b35fc85e3d34962df299d6518fe7f5cea110bc97e5835794594904253f4eab1924699937c86a210d8b7f0746aa378bd1ef096af5c7061beada14da9b1ebc9df7f3a11dffbae9724db84276ae3c1d1e6db258bed793667573e653bebb56b8a1ddd696adf40dcbc410c9890da4768fee44b6f2e16745a3f5f5e538959c3825f948135c26f488d6b54fd57ba29610b6d7ab2f0297a8e5118da34037ab7e6fe8472e5568428a4d02dba5ad4ec467602e39bdd3dd30d693b51b0da02584db6023870ecb3cc185f6dfa83b59c0182681ea799b27819e042a02ce32ca7637b61819e6bf7413bdea73b00afab62d3c1a02805b5a30a203bdb982544ffc2268befd24b2b1b8f824d3d596c5089111f474975b92497df396feea8836c9a819aafd68a0ef43a12086fba3f7e868e54d8d5552f29111d716bb21c233cf2e1a484ac5213ed57b3000ff7ac5a69a0d4a8f99d46e7cf11ba345d08d89867595c0da29ebfb0118f6f019945d7ac9d8df165442878ef9de099f0dea52f4cfc0e46fdda88b51d643b529700b9fe9960e6244742e11fe8dc7b1f0fad97273efde342c4c370672070e5c945673985dc98d62215ed378c481c94753fa81f23390ed15bc2c3d573071884ea329a6d18f76596826344e8158adcb23e27c27294c8a44e945614721a554325ca4cdecf32e272b0e881daacd94617c696144bf959826bfd57ae432e07ff7f701b81815f165ebcfd624b824346e8c5110d8d3ba2d2bff44309ce6e4630dd0490aa8c40460a5d19b40a1189e0ac50518b330fb72f8eb9fddfe04278fb674f7e25c3a1f5053f8d810c3e2696378e69ac62a4e89ebf71bec9cd092d5cf9a549e78d99b4b9055d0375e4d648493ec7b851d4d2f9ecd5c9a5c56ea6f9db47af58921541f2f05eb6c9b6a881368aafc60b8153334329bf2cbb4ff4ef5e3dec2f7d7ed192ee740f65e2567b118fbf24b1cb49fa7c5d490fc8335e7124595cbbc43a15b49842de84151c8c802d62963021573b44c1671ee8691dc22acb7ced1ad7599aef55360623bfa23682fd75cfa69124fa8c37b832a8bbd8e8c69d64c0d89dca1371947d0b9d637043c4d2a6ad36181a13321447d995e6007a224228e6d7da6eb9e2070739df1b0180a8c3399415c8afa06ee0098db8fb0c51d6f119a666c6a608d7b951f7f345f7c5400f71013500a0d0f1f88e7768e120085a04749b52817666fc3108f3ca01f139dd60ccf00ebb6271d67e5de2c8c8f7cb0dc9e0ab6fb0be913984034df4948553543ee325d5d39ff1298131512c5331fbcc0f50bb8d608f7d37acf27da3ff7f9ead18bd0320a9958a4001903f1630bcb1b753f315a25c3bfb9c5e269557ab4b6be74fa4b8e3c825db0b8e5af71463ea35e97d911fce31b7b27251afe6c44cb7a07e25aab4eaa298c79a021b542fadcf96499f981c98a60e81a4668cdef4684a8021324c6968e4fbfc67b6e454e0ff92f2d661f3ae0468037139696d2c3260214094cc4790a6611b58e5cfdf8f4fbf487033e1cfedf760567fe32d9f2d3d00722fd17971428c86477b389b4f3b4cc0faabfee32600419d965462241fbd5b4ec8bfa87fe678db8a450c92a7492e3eac5a78d9d851dae3e7186ec310d9315c7816b76f0fa55237aa5f983891e2051dc4464324d0c4ffbb38f03aff92b4095027c5dd9c7213fc44295c4a5f2c7b88bfd93a052c51e0bfb9afd1109099af560c6465c1e1f97eb5ce7607d916851e68f5833645b42bdfdced0f8feaf8a600068db51360910804f09cf4b6dbdfe8d198a336b0684e3a3903d22bac6affc04f3c8ad44b724e4ce63436e06a74e3fbbb562be033d25945f4bdccc417fb8a370c73a607c57fa44b7be637bbc111c8686753deb43d09f132b1dbadc9e7fb994c089709422f5ec0967c9631fd7d1fb660a554662bf4f65b107e91b862284ca34d3524daa9a752f8a24f8c3bdfb07c9902053c417f61b40cff4569d3bebcdd6000cd9c2f257d89f5873afc4a62383431fa98e3932bd164fc8f09aeae1219a49a960f5e7e028e6c6333d58351f46a34d8569996501b42d2a09a4512defef8996abf5199b625fe43254353a09b4bd8df0e0477b1e64daf53849b807fc41ece5dffee6f521ed601db64f52c1d2ec28bc4c09965ee4a80bf03fd45c4c5dc82f7653e2bd78744b7b7804aff023704a7338a5ac58ceb7d63bcb10dd0f6b421cc195b0e09001537a4b8921b40ee84c931197c5ae11974431bf8917d02ba743822226ce49f216898819c7da86dfc722f6eb9c6b9cdc2ad78785a0a1e83deda7467aac284b0bd02e9d8b3cb4cdb81b0583ff383e40e1b341351b1b1372c973b7be49b2a754f867536e4421c23a574064a32730643201821848c642f1dd28b870dbec0e706df6e692fb52feb559376e777d301e3a90f0f690e17ba79866f8ec3cfab86112bf63cb12051ee5981a4f8489544be0e7d413b5d2d89e2f751d2d271f3d9cd9b3dd3cbb8accc3beb9633b6dd39348a47f893795b9b4a87d10ccdab75f527d7c5cf6f93828b4f58ccaea34dda1362d549abaa6125969de3e50002c887aa0aae129ee8161182802d560fd190bf121203e5526fa35e2b300d864303233588c3083a0967bbaa611427df830e2eb3644635ee11a37ed24f26e81a88e9a31f021b64d57fa9d32023af223cd130a61d4c7f4518426c826697b75deb911297c8cd17827d491d4e465800f094365726674f198c8310774d8cb31a8e5fcfc66955760059b994166212691688a5d9c563b71e278621ebc145f99dc8543f2e2de0d07f8914490e157b877e538911707feee0dae1ff91d310e1691a079a2f26299f2ab0cfdb9ce34b0013600f6870d4d1c9b23811001f0166a27cb1c5d1883aa314861a78045ae05c7780377c47b359801c176b357224ad9e8a69abd94a362746a59a1e03a8ebfeb09db0f70128a6cf3f7956435d37b6b70222b7103f3758c48f59d5deefbb85f3993d0505b60be22fa816f7f3ea8e08361b5b72f10e47c1d73b682b9990a183b02cb0544628dec51d7f6adec81f2f3c0f59fd770028cbfa699ad7cb6f05a2792f850553396a9562a87c035e3d447c728a29fff2f467d9fc2960e389270f15d08d334610015e67c4ecece854ddbb03323220530a0302abfec30ea7b20d23b11575ad7ead54df87799a83e7223173ab60c2bd017e48168e2c732d479a18bd7a41fab8930e54826321f78b33913ef3549b1ff22d4192307a1a0e88415d815d13b71ccc500d7cecc8b0149db39a2539b4fabd7178380e3471b830b9cbad3118d911ba03d540025c4443031404cb3339068a2e1603b930599dad1041fb24e5a8b6644bca39c92020fea715045ede099fc5ff34f1315cb54c730a54f9a3875afe469e4e46e07880b4e7e832041036e075656081d950d1ce4e6dd81d22e1b4a664fffe1625a7949b1d7f535331be51dce44c5e94cdb530bb189e51013e17caf5382dc4377254f69a94ea5f9a335e77255243096bd6519ace89263cd680f0d3f81fddf44b1f47782b94eba96ba335faef18134ce08d3056d453d1ea03f42a635d385258d225e4c3a0a78291130eddb0dc1bb8ae2c2d9bd50c47f5dd1b218a626651dae9eabe42658a061153d2b7fc3350b72bfa740b3a81faf6bf62b695d0a06862fa36853809d8090baa4a2832c7a6cbf569ac7bb63587cec07e1b6520a0305a7ae668dbe5565e786b1a1c33e3dd1a5624d798058d66b06ee8fd6db8a95d09d37895163b1623cde2b68d7236ea0bd24c65848e39df74b27c54cdbd90f372d26d79b7b31a969bc6661989c37f1236293a5e3c6728694217e20db1dbf1864827cb13fccd6a15a2cd48354fe8005be9cf97c3dd6b6290bfb2061a356758b806958511f74c58155eea3a4338104a1e5e685c6a2a88ce1e7398b6e8f1af80f32d18d1a09eee8d8f55424cb0b611926bf9e9b174a8ecbcc5c34370e52d11b0e89f3eb09b89aecc7a486f70340ce342db047bae151dd56fe111e15f95ad14251f97ce7d7e5e9ea433cc31e0a003aab19748b8549254ddbc10bb6dea49867ec706eaaf00fd577932fd5bae29ddafdd756d92f7a8d2858641f567bfeb374c86c114ea41eda0b9cc89ebd46053acf577ce912af981f121625f505652e2f2ef9959d1a81f59da7abc08a839bfb8fd959f3d1b48c792f167fe57b4f32e5703974d9860e6f058982fa7f43ffdf072354f433b3e5d7091ee56e9fbfa78fd68eb4faf8ca544dc5e007cb4c9220ba71db2b743b69fa75a067b2ba1e61a0a5a000ef699248e16d49412fc0a7c56236688cb07ab79465f2de3110e6b2cce8a617678d1b5fc65edb08eaba03478138fb006384e7a414bebf6f3541ca012da3c3cafc6841382cf3e24a5413cad21cc2262864f68924760bce6b0fdac55bec867d378620647bb1580264a3c9ac1fa43a73ce0c55268a3bc51749877234f0b8d1c84e16aa11f427cac321ab64da8be7a46924d8ab82f0815e73aa6080a981a3e307d2a42a99681b25e28376ef01ca109c27d76fa06a0aaa7abf7ca29e82da2f4631c164872b294bd7a5a1378df40e2038d6a59b5cfa4825659482959fd64a90507a9aca996f8ea2b1b3aecdfcc7c8cf34dfd2fbdb2f80b6925807aac6f19a61268f3f8f9866bfa17c13d1f6062e31627730c9a60be93498bc6dd897584142a47d601e2799f373cb5c99281b248525d41e0bca6bb2edd92c31a74cfd12a223d328a0b3c63677c8dcfec880fa6338ab3b629563acbe7b365ace71394f44f7e553749bcef8953a21ae74684e555a0e01a90e3049454f3956a0cfb3859faf0b2a088036f0edb4f4116aeea0d14279319ee429d62278608076d178717fd334468d0bb4304b4a2d58f1e29650146a0ac2135a7b403488bf606c783dc63ee184bb0c22a32594fe908d68bc36aa4a20a6740b6c8b3a1003a3cf70b5a2ceff603b1d50ed2567c47bbd51d7bf589746d52548ea8fcb0c4e7ce02925a0d90a72e23dc921fd8cf76fe797db519c841790ce6f5b250ed7a468e82491e1c7730596aa98db4fbce61e117387ba427494357d9bf6340b120deeda6fd1637aec046e96233d6384f6234c26ddcc5be6ad9acbd7e8530bbfd98a1fc209abac28556ab0066b8bdc144d96f63d562d94999a8c4db1dba442e117e3a8da4977c529d2b4e8ed1cf668d7d9841cdfd9e770102bd3fbfacd70aed70bcc5d66a2af97bf933aab4d5b25150a3cb23d9872bec84fa15f6927b327d8b99c5994329260f8be080d34f1c08a31b7f57c0212a348d5456e9379d0646ba9fddc40ed217be3ef95c1be8cb201602c501658757cd1ac3774b9ec83e9b2f53793952e67d88198696f2be13e0481447b0e549868aa9d626e79dbf10eb7bd588ca6d83b8c84a1c7a826524cf875f6361d69e2ffa284b4fe5a59f746e041bc2a507dd35d109dfea09284c998ec9b8db4798d3f67497eff6547683e77636506baa80a64c38a7dcd1e04c6c85a59fbf2eef8867b68d56a63b3b97a05513b7856b479f8f9cc19b9df82b450df9a7ead1120d0489b8c4efec776873df53cee14fd346211c7faba70b1d916753dd539a39f28564c0f81db3df005e7d7f7aa75bdde2c5ef950320821decb7e559af1f3d493d455427b7a2663ea231db8496d882e4514e834f147bd0abf90f88c455326546b63ae53f46a70528231762afe0982437a03d55cfa7d7942b92bb065ca9596786dad59667e41c648bd11845db607ed34446af52d5b904c7648e3651efa44632643bed3cdabac261751086441bc1056d9beb7049265142fa67f1bfcd7f53a9a96402261de918abff4ecab4e1f892aaca60abff64602b9a36a5c3db1bbd56f7d19dacf413b7ba6d3a34f14b8794a2000f076a9d14e76b07dc4e935cb476b39ab1971b9551708d6e105594975075622a7f9e8419cad9e949d198529733409554f9a417a57dbc997f687c02875812927a63ac7ba20cb01ab62d5ff7a07a63f1865f351b1a96c04b879fba0e0e5b8dd0eb416720e22522c87dc24c5a81acc0f049bef6fc9042fb0879cfb18990dce6021f5f0c388c9f47e9ca2bc620f8733d79d9f22e78b3e10d8999b7dea091271c2173ba4127ee2cfe8a3d822201cded60fe80348d1da39bc3ace6e7a06fe3f4d59bf74305680fcd2a7ac996d0ad8db0c9f80c7daba3a30fd23f365b89630cae3859f9ebf456c5e6eec80d8a15dfdfafc2397f845452458542b29fc398531596022131c453a1a9d01e3ae648137221bcd58827488cde7c2aac286b9a6da4e1a0b76875218fbb1cb0ab7dd1ca0a39eccee6482da315e09d9ef62ba06f8a7211600e08c1e8f2b5e58e5ae05497aa3f94ef1cf2aa04236e3da08387f87b392fad20ce264e849ca9131ec36bf1bbc267fe257e8d221f9e0abc3664a44302c1cc1e8aaedd3d944f3c4cf71475b0556b1e2e2db163f6775820e67bb2ddae8a5e022aebee51aa881864a2d7e79a0bd993e6c391f8e6cbacc3d7754422e30f9c794f96cb5fc738578f64d52f5e5cadc0ea82babca310ceb2650450b0ecfafd68f44624568d5a832ff0b20e09b267a40743647510713869119a569bf42f48aba5befe6e7fa5d1d8ee8ede73d767be88945c1cf3d96853c8eb63c4885df9fba83dc2e66301ff28a340d81ec72dde02974ff5b7d2ee8a7d581c44ea1c1ce3ed1bdfaa3ffc876341e03da44b67a5f30103ae21c8973935fc0aab331d93c4574915b608d76da0168727b24bfb5d039f16254fad44fe99cfc8bad2bfcaaa0d407fb9eb9a2a04203c121d5678dd8e266041818155cfd33d52efae1fd632fffa0a5ed6ba15fd04f4b4b9b4829a594014a077c07e10732bb83be64f45d23b7f66bbf8cfd8f9e7270c0f880f0c13fc1f8e2eff0c1df4f8ee0870f7e7862f1b4f706169129a810ae1dc778e0dfd37b0bb2a74d14dcfe35bf8e42969569a83f4dbd5d6559bdf9ce94d5d390d57178cfd4243eaba724a829095f8fb91da1e4de7ba4db27b8285df720fc1a08c7ec93238a3abe444627d8b0c62b8f4eb0b9d5d38d2290579509631ecface6168e80cd11d835c837c3dccab2f0471b3bf735d0de4058061394b34f66c0a3e35413e998a40e71839e6adc10403dd54eb4aa753dd553f0b5d65a7bc3d38e4c08d3bc768cf58f6abc52b9b51fabd9966fe9ed75fc62f821aee35cc7e3c9ad2bab59a683807429feaa46753b5e0f28b730005c98d9242bf061891326924882c80e7e6ddb359ededbf5c1a875bde0e2dc6cb4f07bd36bb7353c8b0f3a6b627fd65a6bad15f7cfc036cbbe9df146e2c70f204076b2be3b353e176b80b3c0730bff6d81b4acf4cb5a0be1b85bbcf3b995f100523330f0d4f6be1a8ea556ad9165f8f55bb586036cce7d8cc7d1aabdd4aa25b02e4d02ebd229abeda7669d0ce726f80d6dac5c1c029510dcd3cf248d9a05320aa6cd2d6d7bb75f5bb72efcf8af12291a5823fe4d2bbbb6755f144b258cb30d64be9dbcdfacdb47472c64c73e6a736e7ad3edb76cac65d1cc4753862ceb930214d0a2023601a3149001d7275000a3f9532e46f0578c9dfc35b2cce24eb06a955ccf2fe355828fda9cab439a75fbd8e68cf84d3b962090c38651bb79171f7f69635f6cbff8a18de917dfdad80c6eaf4e38eb56e7577d7ffb77da3e8294195906c306179ef011850a86f0ece42f21cb2c153da22696cc5a40450f3b12e80187157a047124070e36781c31050f37808245490d0eb04f73c6d397eb09aef1ac083205fb21ceb5fad32f621cfe0786df8ba999c726aba3d665b303f1e627747a0c19b83fac09d0ef0b1e3ce53c05e2183164903083a71e846e9f0c51c4c80f3d45552058bbd8113c4de527a1e38fa159b6b553ac9ea6b449ebf1c4a7b5fbb476ef1dd3cfeaf9593b8d03b45b1bb26e64c0f1a7ac7ed39bf0d49432859f5f5846986526dcca1f5a1c9ac29e9ace6c5712df7bef88c51c66fda1d5187fdf067598453c5e1df690a8d6a166cd6054e79cb32682b4ba6e538b1be1bebd1fde4fdd7fe5a2c87b7f5576466a466c34291fae4240eefb330bff4d89d15796d91082480c423920424baaec1440870f328841880a831876f27f7e34ff49099e7ecfff58768ba8a136b67bfe05f8f4fc40327a84d8dcb7ef06c2b174464866e587c183a38fd1fc2f2f7cdf39a8d9ac7fabf2dbe8f903af39a3076eff9ea96ec71dd477bf272fcb6b869d1c91ca6b9262bf27016cc06da469d61b63cc736befbdf7c390f65fb3ec64572120bd5ddc18b557773297d77e1e1900bd1fe99e58b4c14b79cdeff6ecf7fc6e61ca6bce18c2ed93d664d226fca58ec78e826b43e33205244ccacd7671ac1501bbaccdf9465b9b1dc297a5d602b3d4d030cb8b51d4720384115c7041b27a4d4d9019d0e620a7d6e1fbb64eb5d63a6f334683173f38ee311a94ecc394680193b28279a131c1cca8ac8da9d141a6a7d65a6b513a954259cb42e6a47aab32594bb385722b4b0803a3d76255fa1636fed7ba347739cd52e79cf3b6914e1a8cdea8a6fcbe128616d285b5e20b6badb53a07d9597f9a73fefbd9d810523ce79cb78db5d66aad61526c8cc89614c1857144b82f1cfbca594b5a783efdaec5649386d941b77ff95c6ca3ad97405ac8adb5d696a4081f88dfdfb7dcd27bdc34abf588cc2204a336a6f306c2684996b550ebf04cc31cb46da3118d4cda910493c26f1882b56e5f873cb460e90b5be86f98520cadcb7ea86d18829f5b5926d270eaf21b5e7bcf348753b4160ad2fbd6edfd187454a9e733c5807d1196c0a538ca5129509e6528ca512950d4ba6ed023d032bc85fa535cf38cfef0fb298a61f8af46ad0ba33728daafec062d423f128af1d7637571d6fb03437124cb11c985975e604c3127544a86856a8666d5e201b0a2f96f844dc3b28f4643639a270c9a9999f3ac79cda8543535231a9b25046714c3510c37a862c18265bfbf2c462199f0ba00c1954a8150da047110460778711396a27891b339d9e67c5956dae498c8ac1fed7f502874cf6821f47b7e348cea8f8642b5cad68806f821e4efef6e0901df86af9a647461eac9330dbfd0fc6ab01c7c347d636242b0413b79cbb26b522f4f8807754c0275ac9ad42cfc9a5499994935a9fae6fb5dd377beb871816063625c63cd8538d4ed8bb8551f85ac0b061463328980c3fa000a6b0948d0c379d55cc0023b0e6c80f78251a6b28cbf1db99439f9ecf6b999e2e8bf8c17106c3aa0863a8113608c508a9f79ed480abf78040094ff61c8535fcd6689d417a3dc349bf37d8b6e52b550697eb4d048c7ffa546a18f96226394314634c0ef83955ab539379b138463fbef8f4446a155bf67d701d7eabda1a1747b55939a6532aa49d56599ddc1e12933e5336576231afb94d121e1e987a3063976fdc38163e9b742ff56408d0c40d3ad58d19e90d19d6474323a9b6361341feda3d9f40fc26fac24e56c0e89227db48f465bed46c0810901b6eed2ed93211cba88b58e2433d62e39df28c1efa357f0fb1bcbc6a6f2e621373588228a284643bc4c9c87ef2dc3b4e29573ce5878f487c42946090e6a759cb25919f61172f3c13e9687e1a752362041032860288e245814a2607fe6dd2147512884167aa549118c1ff0f0f0f0f0e00c8a63f8edd147f4097dbea2bd41739f56098f0f1ed3e5c1390a1f1f9f549326fb037d3e1f7d6586297c7c7c709438f84d110404431004c10f0bcf14599a4c11a4087c1a53cce0e3e35314040f0f0f0f0fcffeca253c9fcff6c12fe8fd65bdc1265f91a813a3682c3c4c680de6ffbe6f6b2c3c7868c44b2ea492248bca717c10144752db28f9701f17d24d9491cfb63da9e2a6cb94f4dffcfa6cee0c8e70d2c4081c4030c9edbd7711fbfbf60782dfe62187bd73a6b277ce5496eced43849224488670430811c411208a6490338f11a1223ff8b077ce54f6ce994ace79ef9ca9c0d0e014c3e463d91cdb77437ae0e1034371fcd66b5d19efcfa4e9fb0ccdbba34d2deee41e4268a150ccb0423143aa64b2b2519d513bb642681853804579c44a18d52caba1aabd06139efe33c184bad53d43f16305d8588d45f15bf4d6c8566d759d4aa0e76bc5ae4720f6fca16de9afc935b28cdbc7af5f9b39e39c73ced78ea4cfe7b3fa03bf1d6a9d4d62288661a8b7fe6f84007dce9cbae7dbfbdbfb64f5f4a0ba85516b508fa4b833f963fc21fe10d7dc01fddf5e808d4d424f6602efefefe79d73d618875aebdf5ae33c4280ce796433be367fee99861f3f7ea49ef41c29a208bdbf9def7fd81d285420c58f1f3fae2871297c40f0e31186df073ee991e20a13297cc4ef9322abe0c78f1fab504f4f4f4f4fcf2e439b43cb81f7dd6779eacff99503bdf5c744d489519ef42ca1f5b75713653de93952ba70d2881cc51d298ae05f91a65f1b1cfce1f28334ca953ff68e1d647e8bab5b2b53badf5bd37672bf3e1b1d721082430c647e852040a80cc8027f4019191999b7b23004653a46c5100422f33100593b90b5830108fe00e2f3e3053d379e1b6cf021f33735c8fc7ae3829e9b1e37b69b16dcd070c30290b50359bb9799b1321064edf60c26a896f56f30049c9da622be0c51e9f80547aa916a349d5403ef7749b51d528d5483a9651684d04261d42cbee0e21a6a975bb86a9cc6699cb86a27b48a63d7f1cd5cb42d9cb3a6dcaee5d09677d098cb85e3143b05c7d2d10636b039dd2b21e7544ee5544ee5d4d84b1861c677fa0689f38886f8fb1c41f19a9ccb2021460aa3f8598f8bc81736f316b5cee6807648893e5ad1a22816dcadadb88ddbb88ddb4a766ee36e3270390789532653a865294ce39e267d75cfdd645d2693cdb1575259e71a20ef42ea14d34df9f139937a243c254147c7e6587b71e0425996752ecbf62ed2d396ddd9260fcdbfa3546a6777763b9b63f76ee7c48ee32ebe57ec5bce0b4fd57aba8382c621b266c7215267aba5c4b5cb17861ba6dbadb3735c1c5ce3b16d181d7d461f1cfb5eb679e0d6ae7dff9ddafa086d3c725c9dad63870c35175c78ad95b9008d41c01f7bdcc7c76dd350430fabb379b80065d9b6d95c803ade36972340b800213219836cdbd8137336bb7df66dfbd83caeceeeb16bd8346c9b0b4ef78099e9ab8bf4f492542de4a3dbdf3c6c42ece8f6b70eab231eb938b836c282533b1ead381c0e273a11ca29397275c4a124d6853f1585e8f8ad888311050e6676c5cdd94026399ba3419bca2dfe566f9c83f82568533796de6f65314647f6d5379f3162d81c8b592949bd593d84459da8e2167e511595a462929e847403bf25cd54d5e6d82e46119574fc4ac41c968951445554b525a9f9c97e5d6a9c8632d45f7debba36c7b2308a9af2b3bee86f58077cb7d65ff999fba55ff83502ca32326f215c968d6f7fdfb46a73c0a7d2f1a730aeb80bda4136c7ee20eba2e941a35b9a296eeea07d04a704a3f84773e74606c0e9d966097e5f0cda2ccc5e373bf8cd22cc40a3c1b022bfec7fead157c72f9b9c7bb302b0bcd1c1efdfd4f8fd9b19f8fd1b1948175254abc6f48d1b810516a98654dec450dee4e0ff82dfc050b225b1859581e0b971b985fffbb4454f531de745188620e727aed5770ea330232f30ab1968b415108c89c61473a30254c8730b48aabc7981df47ddd0f8fd1b17666e5ae0f76f58e0f76f70f0fb372bf0fb3729e0f76f6ef0fb3733eea2fcb9dd469acd152e5e20b830b6c0add605e309106c7e805a1b2bf87d9b2dfcbe4d15fcbe8d0f5ca50d155c041c8cf3ea2d52aa719765dfb971351bb7eb1807cb01bfff7abd48a9d44ce3172a5421a26e55a128ae6e55219fce4daa111f29d5088f8e5d6243ba0176063afa7d128fca11a4f0ef8fe2e7f8534fe8171cbf37e4a91b781a83e7d6bd5fc2fdd77d1959960029aab06108072d4dfcb07331c6a619980a328572d6f35b56ced97ebb7beffd18b5eff7badc6bf1bd17638cf75ffd771481cd2d16ea37b5bb7eb975d930e34c84d52967ddfe0bcbc89b8c02d4a08013b5e79952d7f3ee6a7c83638fcd7d4f6e5d65ca537946fe12f2bf6460dc6ca691206f11b914a849cc8620bb55ab10e956ade2d3ed7fddaa556ae8bc5bb50a0cb85bd6791b9a3d8ea52995ed1aa35bfcd9bf81fd354ef854df5703cb4cdc0919c031bbfb7e13dcfa4ee1e87b7f78c3404df14f59f56739e6cfcac491030abce28a2b7672484105dec9cfac12af74a5b4beec605bf7ef0bd08bd00b1016bd7f656eeac6dcd11defbd2fb87eef29cac5b97fc57bea5c98a428580461df243077c549e7e1188c894531acd62bad5906d3f1b2038cf6029465b01b2f406c7911c2b11854e0707a5a5a4b6b4c691d95d6d2daa3b4a65e705db7233a45b93a2f1d58e9f8c553948eff9a3e5d4f61d0741845e20565f6eaad45bcdd14fc8a8251d4f952096043089a3da0d549b5eb1835aab1c2ea51a303e8fe7d36477d347f584f9a3f2e5e74814f4417e7ce5cb052508177fd200c493d992555b5537b0aa3f6a26271f0d79830683066b9d5d382d1d57cf15e44af9d75bd74af28553afe13d1d5794141e244f49d2fd5069e92d4924db5abf5342c4bb05c6e615da48b827e6e3a5ee0a916a66972d27c6ec26c268c665d26ac87ad75d80b1676c3e2e8b99f21231df340fb9ebe54ddca7cb5e01559d7cb098bf382620998970a33a2f163b26031a2f16366739be69774d1d511a75c9c15c7c45d161b13ab5834e6532cf272ab88958d3f76bce5d61884d19dc4c6c62138e8cbebde2824eaae0ec9c9d575fc3434c873c5c328143c7ddd7065c250264c090c7732614253ae0e6cc8e2885560477638063362510c4bd2f1c7885362ac660c919765b01b3fbd8e3f0616db8ad18445f16358104e10b4a77f8f948e8edfe54c39ec858eff8557eb69d8d397da5f2a46a338b93aaf1abca49c4e3ae6276c665d78c53379d64532cd1569ba33a54d104fd17093a6e7d37ca9e66b8af97af3c5cb2dfca69101707a78be743ff095d962c66c7196ab4ef66c7e23f39a3cb72eab66049e2b9e0b5527416861ead96c347f564faef88b72359a3f2d5e6c71ae5634df6ea13ab95ad1d0d068bd5a318a57bc1974f6dd983ae912452857a0a971ca15d8c1f1748ad9df067fdfdfe01713631ad150ed543bbdf5a7dad91c4b237d1b06e6e5e5a5b4e261745534a2412280159ebe545ec7ffdae1981ae5a57b4dc1b1d403aa8681df7ed11985af9d21c5662a5c811e99f69ca7f779fe9bcf0edc33a56b82943a75730e311a05c77f9f8ce1c0f1eb57e5e0f853cca3dfc78901c77f7164acebb41d4ceb33705682bc6f4384cf7ad6856f0902396e9ce069981fe75a6ee1b7ad37dc03ca41b434f37a1ae65a96e15e38e3b454cef986f9dcc057cb5594a85270aa9420558a90dbc4498f1078dc3e294249ef451cf972aa1cdaf8e1082974bb2655849c000222c5500fa6a06d79819b306a87883c01a20b4243112a86ece0062ff841071eb0603b55298c5e1c235c9fb51e09884c606dcaed132644dd6ae9f6496eed79c40031a8b3304a66408505a8ef706c24e2e1d84d55f8732c2d4e860060dda62e30c69d7c6b8e9fc52cbe1ed1184f0fb0baa87df872e083319610d96ec4ad7fea9dc9de87cdc9fd7e0670cc3e3182d6ef7f4e368063a29028fd7e53fac5a4d4465f00466f0debba3f83847fb62f87b70fdce6dc7e3f4d8567f89d319ee476b4198ed19b403ad3d00419b319111c4bbf5cbf332cb347e0c83b21a0b061198ed1ad8620faddfd3ec9f9e8f72f108ef17eed9322b6f42b94b1b5442a8ea531744e9cdcffa2e0d806a2dfffa6e0988843bfff11e19818833ea35d6bea5f408cfbe53c60770bc0a80541124b84a82571838524ac134a8ed8b0a336450a6298b560831a6e5576b060633171c10f237c7801908e2148660054050eb0e06007315c71e586a11e52fc0879d9247ec8a1dea0c3f2821319a002a4a74991a22d30183de0054f6cd13193c10724d60a229d1245acdca0cb61019e65a4bfa3bf390032c4071d761002fa5962272489238c40421ae119f8d6b22c01525e9061880c497c9802045400192227831c9ae0653303d66583f0476f6decc10f0b84c0010c4cacd8a00187102baa0cdde003111b4108294143922d3bb4200614c0a8fd2ff7e55c39887c96c0b0c4c78e0bf0d4e08829a2d8e1086f079b256621e57b7d9e40ff3e817df4f1836f63e7fecd65997e13be3f4d90033e4dd8faf7a9c2fbbf9def6decf07cd7fb28dc27c10c1610d7ff00bba34f9b5b19851a61ca04b7dbfb5418f1f1c44f4f103d5877ca9ef17785bcdfce7bef9c73ce4b78ec0c74943b1af406f3cd39df7befd55be7d74e0000081878c839e7167aeef776e7bdf5a986d5dc1414a7e68af0932088608c2ffead738963a9a9ef3a4780f99d8f67761a6718744d4426a8ce61ac6c86bd314bf6947fe20cdc050a144bc025475cfb063c2452b2809341441b1ff95897c850c592158288f08116229c109b108103229aec60157153111dbcc461af37181091e39381881b4078198538c4491e84347091ca9315a8625032c4b7e3badb274a8028c2471e7e54800252e2630525319c4a663198461a2fb5f0f8c8cae7c3b91471058ec2559164c86622c94ddc8197dd3e49c2636fe164b74f92bc80c4969e5a1cc7fc09122c234fe114c14d34fec24438030ee324fce13243f60e90e030b680a4053db5b8100912157c40f8c88aef063f590179433400553eeaf6c910115afcb6ce1637a300252c6048063d45558e031e1e1e1e1e1efce9707c6e22432b9a3fe4d7dcbeef9cc1e8cff835b7940cdceaa1c36b5938765febbdb3288a6118eafddf08015b01afbec1effbb4fead8057d73b6a82b0ba0da2331e92a2e9e853f1d3f153b2bce51d91483f22dddb4bb12c5f2ccf7134f10786e183e13923a3ddeabc031b3f5964b821c9ae071625ec9614a440c8ae8917dcc0126489110467f03da7b0e24a0f3ad478286200961b20b10148a887243040711281b8ed291fc11f74008a180a33b08bad869e6eca619241c7cab6f2b1aad99cf2c791066773f6f76478c8d8646e594687d571e10182abdacab6f281d1bcb5de5affcaf6633cd3158f8e1faf7458143f3e539aa131080dceba7078a632403237195b96e1c1e3eac8e8b038672ab3c38506999acdc932b48e3f93cc7047a6461a8d4e199a0c6db422710c1124b72168f941922a2b604114183420050b152b3f331c78e0b26ee1321eff7e968e54967d8f651400cbb04e72b6d765c541a4d65f96398fa4fd11ebbff78eaf31fa367575f089c23d53b0aefd038081a716b7de1e36f6286459b8ffe2c031f2f75f5ab97f0336e7fefe19cf019b65a433f569f8e94dcf9c73977393cebb89e076bd3c2e6d64dedacd7171987796652888677ab37b5874ffdefb67e0d80ff8299b7d7863d6f593b70c43fe7addf58c59c1530c946a5b8a7620fddf1af21490cd21757d7bb9a59f877cbd6b6e696ddb423c05ad0a24be7e0b6419896619060aca2d9b133e8afea774ea754884a7d676712bd11141d8c3303c6dd6f0eae40f3f1dbbfd8bbb3af9d4b68b83c4e684a776811894652884a656734bff686699b6e5967efd658881469c97a914f98fa617279e3a975b5b787efcfab54d3f0a28fa9fc333fd7e6f2d8f331257d34f3ccd6ad482ab5c4c30b71ee9a5647b72fcf25bf81d16e28c4aa552a9742ad54a8b4abfa032b9d97898bd98ab75d1565a74ebba7e69fefc6f13d6b3ae588e182d36cbad2d18c545bcdd4a9a3f38542daaa082c5ff687e0baf1d16dfa209222a576ad6858dd8762c5aa8ced72e684513a42b49f3c76c61e268c1aaf9f3082e49996bb9d09ad57664ee92035b36f3a5030b002b1606150b6b71be76b523681214571e69b5722ee7924373209ad3b996cd9c71c9654daea6498dca9a814563d95835a450a7189164dd6a9ad4e46a54d60c2c1aab06962d55a3a26ad4538d1a53a38a352a59a3b26e63b9db99782e65399e7aa72a5033d4102821540ed50407fd1447a93ad074aae2d443cd5043a084504d503919d42c859aa150b3136a16839aa16628b5f34c269752b7d3f174b6955785a6569eafddcec49b11599839e827cd9f55e9f22111ef8a0e0ade9aa5cc23ce572bd10ec6cb2da29d75c1a89cae4c39f1700ca644958e7fbcd5483a151425499749ba918eeb483a92ae8a755121c2e86ee7627231f14af3c77cd2fc397f347f54268e1c4ce5e76761ae597a458270494e3122d97bc9ada7499ab27be9b24ce918c7604a5814c36e305a2fcb603d456c415906d3910496038763b0177a31a5de50a9d7e3a117a4c75f727de6e584d5743b1d4f67db55a8d468b3d7961e46f1ca894ce76bb7eece196fe59954a5f9738e2cce75e5b3d3bc4149c7d3dd562c16c706a306830603078c167cb5ae17ef459465bd3a2f2a55d69d6ec7e3e9ca53173d519a59a9564f326140d665b3ae55905b0f1d16e5d17550980c19e161c75399afdd4ce984d576ab1569feac7e74996141c4bb22ae3b00090141fe886361aeb4e0165ead375499604422514482a8cf4ae2388e23298e6216c5555c5d566debe96b8783d5d6ddaa5bb5acba5545d3f368a5315fbd51f52c6678aa9919b345cd3902552e6648f347f5a58a85c9734b2573d2acb4a878bb75e5ed8878453c9d8a8666665d753b225e916e464b0ebd95a47bb1aeba1d11af48a7ba2955e4e82a552ad00ccb199569774ad2fc99f971265dad445e3bebd265d9086aafa7afddef667dd7f18b3366985bab16de6e5d793b225e114fb76ae1edd695b723e215f174ab16dd6e5d753b225e916ed5a2dbadab6e47c42bd295568caeb49074bb75d5ed887845ba10fc368b5a0a9f50e4c666ba4867b17ac528ae79a12c33c3f3ed6f1f1c7bc1517a8b3abdfddd6375f4a7fbc7fee95161c36275133d2a330dc5ca746f17b12dfb7ac8ca7650127da4dbd7466c8cbfe9ed6ba19d1bc5b7ba17a40bab23ea2ece082cedbd186bad75df241a3b8c39ad8a70eb67f56116d88d2d54e08cd852b41ea9ae032ba66f258e780486bce4650d4c27cd9f9adf268c675db02db01e6c851561144fd1a9e60f8ed5b710ee9c573a7e58131607d7a458dfc26b87e64f25a240b141c72c9a5307eeac68e0a9eb968572ebfe0775d7ed060afef2229e268ef35f7c0baf1d10de052a040d428788a8103a05bab666ad15c89befcb2db5682e1f9af7525f98af1a5800e007c17c49b130f0bb305f502c0c7f68bed6dcc2e7f9522d0cfc2b1e0915820e41834454082a844e81ae5916034b6b85a2184da0421915ea7954a8840a81a8508be6f2a15d3e7806e2b945fa805e5e4a3d8b4fd6e06a92d4e4582eb066ac1a6b878a854c2a66c4b2a1402388aa495283abc9b15c60cdb20c7683b5630bab866331a8c0e1184179f820a91e3eaac9b16c28d08802c1984c5924330c0c39be785a4f594e3d541154100a874a62d1fb339fe2a09fe2a89c6acac882a85396d37aeaa18aa082504950b899534f75eab138f5644ebdd499f21ea71e2ad7f1b3545346aa29600c48ded1014691d437d388868e69b17fe4d269cc1cf48fe68f499a29ec5721ec29a8c0fbcb974ebad39413132349555951ed4e44272b3828e72bdea9288c825bf8a562b405fceaa2a8d675d25d1d18148bc3c4690a0c898e7fd4755c9b9154959593951391485249929a49aa0b4925a9241509eb826271f0eba2b0689f2fd5ba30017cf094f7ab634634f6c78c0ea03b0afcd2d59ad449f387f5a3f9e3e2c59dd8a8542c996c7a1422a20100124316000020100a064462b15818a49924980f14800d68885068523c1788435110a42088a128c618420031c0108490518688681c001ba6d72f52f88b090a94543125810405fb52094e054d4357382d0538373683e841c56a94d50560d69300a3a709262ff8372dd49e6594ca567882766aa6c7f468b830df84b9e9380e931653e5c7eeed71169d92159dc254112b408904af70bb82893e9d8495780f751fbe45fb462c664c2c8f8e2864c460fb2c79f129da7bbcd45ae2103ccb695acfe2e46d975fb7d80f0c6ee065428a0c7472935d78f529b5a70946de0a4b91124695440bbdcc9171cb7e38052af586156b88037597af0733909ecc23cfb64ffce5e3e98ad5fdeac2a781a8951694fb8429aae072e878ed66ce7f9e76db9914943d67cf10a18ccefabe86a884829671a3f210046e715f77a9d803972b36b8b8364e3e4f639b910b7f2e1e276449a7bafaf3ce5d57ed81c3efe3b31bbf567fda4d30c7fe6f558ba15d95a410a5c45028895cd4783d988b99483275015a3007f5056a6fc4b54996b25b09a32d809927a8005fd5b2c4edfb04dac4e5994e0d7018d739808069a10294153842b8854ed93d11a8d52a0d1d90673110e33f7707f1885278cc25213a47306ca4b92452d0432cffa868d9fa99c492bf197c642ae529fbeba5c7cba4ae582ae4b68f4e2342e44a902d8dfc3615c3a075f3aef16d01dd1138567d43c558d2d2c41bc80b9f9c0d376621b36574158d444ad25c4bc2d2a6585389eecdbfee9296d5aa7b85f07bb60c6f71b7091ce8097914eaf2f6163ad60f62cf3ae436567e62f26ed1b028cc459dae074448580498d1245335bd17913ee2386a340b118efba2e82c66281ac8cbc2fec3e2170ab03ff5820ca2c5c2f48feb47e6068ace0eeffeb2172f57f36cec4338840fb4c547d9f45ab0d74ea94b67e83436fe5feb1a6758882005179407a274e72822a43385bf50f42312189ff56ea16a16473733496c3ee8829a3852e4f2934acb608a5db2d1f9cd97e9a798c980dda537e791a9c1c88fc26221b239c070e9dbb0429613d15db973ddb9842252b5e26939dd772c642b8744456a7de20eb511a7ea395cb20f465bd343d8d7589355bb457898afebfbc82df47906bc5622b28f29ac1831cf36e8776eb37c6511552f11729ac68579396d30c39f9e4da50fe75feb5fa0d942565f720219ad2876c4b7dd5db42d50342d78dc37accc10362626c275c6d110e1622399bd7e5b64426ad34fa8e6cffef4314df8bc9bdd75b64fbcd75be28167a6ae7f158151342dca87c54ae18a6c93d3c6f1623d67cae73f3a5fb608c920b9bee3e43562f20b62ab3212ad75e3f4b6f977e10cd6942c9889c55a0c5f94330a423e815a13778e235afd5971c15d2b63757efa7764e25c01e0c0b8be40c60764dfd45b5308902212e604999cb845b7aca449ae1efcd92c0d5d5d02ffaf827581f0567e334884038c65f9ef77a062961f9d0250756bf213f024a4b42cd0a4a5cc2acd7cd87538c74851d707e6458195bba501b6d798c0b447096d34ee420859c1593ab56c4b7b4e828ddee1de4a5f4ba6b6bf76d5b426752bcd0e4120b5ed2b3824d46a891525847e4c259ff870e19bb0c84af5201ec01a28a46e5ec4c610011ec5dc28aa5832a0dbf03af5e9a7855eb5fb9a2c4f14c38500ccd66a49791278a495988c3f4b2bfd017e41a328dede253360c7c65970e4c400d4ed45c39dd1dcc554b635fe140f8cbf5bb19b1fa4b191d96c9b3f543fcf7403f413b4e6b4ac084476c5fac65b56ec6cb60deb1a73e6bd6f551d64a59d942fd82185a64652ec1fdd43da542f6e55e2ca8ff2656ae107f08da591878a2d7b3f838fd400d19db70474032aa492d9c9cedba3841367e8e65d091de59a6ee81111a011154e3978998dcedb24db7856e234f9b9091c6ed16993bf6f848071e3578bbfc3b3c6e61a12224ee092a815763b62a88e511229456e8f48b06d742781633e34bae34265cba9d654122d089c3d08046eb392c799c02546cc056520b59512cb099cbbd1fa446c3c550406e260814d2ef08ddbfc326c95ad4cd407d53af922fd78c61a04f5681302a4ed154f8566d49eac957fbd3561adb65bc5a5b7e1c626f24006d1e1e5a713c931692af91a6c307bbb230244b6c23f74551c048d21bacb49f6209f0ab2749683a067782aead546c562955c8a15d2b17d60766b9ea0f9a46e84ab50f3ddcbb75431b91a1466f7df5b3d638b736b6371344309a66dc12e8f54236801299c738ae733d716f6ae5b518285d76e20de3f1e5d1b5a15edb0db91c66b11aef931ecaf409712728e007f03b744c1e6e9976b6bfd9ea9a286f542f4b5ebf521cb4b143ebe8d0a4d7f8fe573412fd14953bc76635b9b815f97509f28653b26e82440db791bbddb06c09c47eaa51df4ae2aee508d2f750e43db15598f8b7a07230648f99853f4afbf2cf069dd54b0f43507041a2fd2bb4165e326bd4b0c505964061dfdf1b696cb7939ac32a8dfe81d83bf30669f6e2d20f105345a9f3b7983fefb47c018b29cb3b5646d26a1c22088ef862fc6c9c9fb5f381da2376600159f2bbf5997854e8d912fb25a6eba88daf5698d91556c6f20857cee155b19258ab2b21f52cab5c3b283d949f452dba29723c42fb3d4f3ac1f67f40b4df49ab6547b55c21986d3f12473eb8285ba7bcd60af277958dfe48fbb97ce86cda3745fe548554a8f2d8a19321126675326c0c71433a54cffee6d2f81d1d617f34f67ac1d9be6eed90fc4afc63c42449a0e11727e04fade7657a0be7e4757a0280529dc09e73b85aa16f5b1c1be774dfc949cd9fa792b9b339e082c5e37e4eb1a4b56cefbdf680ce8b7a5762a6a011fa12e80fa5ecdb7607c789dce050692342d916ac066d1a230b0e9475bd8498032a7415a2e484c24bcf830d48edc89ef670b56c4b7f8754c9381ef7bc5dfbf5fa5fb491421677e95ef80d19cdb55c32b0677e10ce48fdd576177737be3afe036f74268803f092a329e1e4afc31b9d583a645683bbc01b2c5ead3f69c6efc418b333e14e47fafe26548652cbe282edb494dd28d462a4c9ef2e3cb9c8b7f18ac0fc99add0b73eaba12ea5d6865089a395f165c73f90de6603f2b7e5dae8c788c0af63a2a6454e0c5f724248ccf1e6cfa14535df0667f76fa2ab686577741700bd0ab1f05b618c5dae97d1736128b7ac3d0562d937e8b77c146346b70986676ce9e29cd6bb94238e1e62fcec109b3de93551c0011a061d14847482a88a484da2885c9251bffd24611df2249a71c943f2698114e78903424569ecfa622a013ebed5cb498389d98410b38274c69bc6d2059bd3a4745303fa8d3790140833584499c3273c2a00a4a110e55094df1410906052242aee1a89a304db611ad674e17c237857e9c9af85f3c93ff81292b74e952f508c21c2df922fd931d5a8c698d056de271a926f80590283d5bbdcdaf3aaad5566d095d5afa4b9457e51bb25dc0e08857601675756c8efa1b0f0304e44c44d160005a3c07d07ca9c2236d9698d3b90684636e7495bb282406f8a1b33a37a79f884f1d1302bcdba4ed868419cffa980086ecb08962ac7d3731834ef4ac63974ff604015b768880b03779ef47a83e6b29fb6e8a6b703de19a5ed6d7e68b81e28aa1fe21f3ca7b0b04195a665103ac2be723beeb355b3c7954b4e03ae8261c578661a3107281c1941952c994ec6052216949ea9596d5c2be2942b2263dc4af4887a91248cf6b4ada2e2560388f456b0d58f70534de7cb75b45c18ed7cce3b434cdbf575e06920206e8e0899ccb0f937f3e4fd3288100e294baae00118b15eb2623547433cce47ef5c09c02c6e3fd8c78cfc642cafea800e00dc607edd9422886a726d78454c06cafa5959c1f048e0b989b5de7f66da2223da2d394df0c5eef25dfbb526cffee7db38def6139484b68eb32021179ebd848305e180d4786e66ffc636e3ecc73df113f9ba698ce4e399dee10345b862411aa5a791d126a8f23538a6748235d38b8ee4442dfc6bfd9600766156f567c48b6cb6f30aa52bc3fb4c64069abc01f1039a2d3f98ab198afdf45a45c610c4ef5743a424510dbbfad05cc1687d057932f4adf2cd50cade351367af07b5b25d3ecaae432a16b464f04c90a7dd1e8c00e4d3f43eef9bdda2845e3ce94db571dd83b79ba5e12a6d641b9e7f3a0e2fe6f55ec87a08489b1daae41865a4d1c93fb238792d0aa53111a233c5f9305dcc10f74ec7a33a375d9d0294ab9cc20e0127caa4b19e586d35061c724f55f18e932e5c6a1b4a6cdc7fea1e568e9db56dd4b2446f45a755df968303984cd5ee2b8455cb5b0219f9a8dbff585d879eeac32a869123eb10c8436fdaae130b1ea7f367376e54cc104365d5d8d27de9552ddbb55f5ae845b7b4915b2b1dda5f1e445dd08e83a64e73a7e90f10726bb22f109bca4d09e25a2df470cc1100f342758ea906220823c04844bf7aac834d24415f3d52e1c6177615e3241119065c25207160df525ce040f10ee445a4a6872b507e04a48c220ce2aa426e4aaec34ee30e405b35c3d829bab5f0c48efb55845db938e7c95804c6064858e8b864d4d6079e26727205759b20927722376cc0dc280561ea315fcc5919efb00b15a0c28277ba19b997385507b2efc07566edc3ac6e3892bdfb585f241f02e1ebfa039fe196ebf00f30c763ce42ecd588a3df50246ea7c9f92d3e874a72bb7a4ddf85ff1fa33e918039f5ea7b7a090051098c3c036088207016e0058037efce04f30c000e7d79ac9f12637aac5a82555fb148cc0a4dd8041b1c66a83c742df3d8bf21f39ee991102f00b5ab376c78c837c7c1e6af50afa2ed9ab372bbc4e216ef9f46864a8e2a9e8ae86479c3f3d939269b24175e4a0fdf4107d2e5b253c8de6822f4a0e07cad4133ffef5530633f96f2657fa7374240e75c1f1042c0418c86a91f0173decc4e88232bbb2eaa8946c9ec23bb229d273ba6849a28178f72b6d3bf7ef4e6366a5aaa245322b7ae4f89f6fc236b7e57153d0b07994388fc7811eed96965237cd852bec77d6f55f5dfee111041695a62cf66045557fd918e38761933a7ce61cc47f40802e3fd8fa6014e7ffd1d40604b9fd0576e0d1c01b82c03412f1339148fd978d1a05ba2960be466d861fd31acd2ca5fbcd49b24a56e64665a91131589dc345219ef546b69c2ed2bdc820e12fa8a9807a16881d1197588fdbb836946146bcb93ab87b13810bda31ccc4f533a599fd57bff035b55273caac2b3e32d92b2cebcad148364f64d60d7c2cfcdd2368750bbbeed63d7896fc5f16894658d0556899034cf54592a8dfec70b57ba878b7b75f285be1f9d21c1e32f4524c03e8a05646808a90429fe533e9fc9e2584f1bd9ca6ec1f6edf8452af7aa32c63f8959562487c0a8be654e048ef957b23738abb5145af52fc2622e46fb79fc281a4c113b20c91c87c03be49768cc8d510aff2723893530d7ae8ba5b37dd4540c2b517af017348b6b9a98ebc24e3580b751ef4b983ee574dba8da748be2144ba5f5878aa000ff8c0e09bbafca89e2561a2e84320a399a62ea474b482452286e6ff168154f6c4cc0f09c00a09a56b7b7aed7d804960cd3e7c3815f853074271ae8a2acb5ac016d36046db5afe04f698713caa93952e680465d45444f78cef22fdc858e2d9ccb03752a31d22266246a01012059ec37cc0570f0ff0e047655ec61cbf797899ef254648de1461821146ee6e897fbb42685a1a0723fc3210fc779cc8625ba88d86890f70bfe2119817ac0897a16b1cb647d4604316edab552f0c06661779f5c158e34041e061f1dbd09965c8b684d229321375f106bd0907fa9ef3aa7ff0decee6465fb3d5984f920903ed4b6fca65f71e0fc70ba454e1871709d59f3aa805cec1d4767aa06f19d41aa3615428d9d3e7205ae0e22b8791d6f7c18853ada0d4f72d8e2725c1d05cdc71eb53ff16d2ebfc7965129f18e81dd5f1cfe3137a0e78a7ae3fa2186d7dce4faa3a55b31199c65192ba63dde093816334df6ef3cefac687ea71093636935cd073aee68fe4a28dc635a32227ab2b5d01c655bc0df937322feafd5eb449fda506d626f529d164eb60bf11f171f444f8c6fa84de56d44526d1f8d08fcbe38a472f4e356833bc8dc11ca3e609f4ecc98cefccb344ea266993654960401b603c2754622075e69e944ecb7343d4bd161481da1a3a8c27e9f57f7810782e1b164ae46715bedf3987c987923e2abe7ef6d58357a7ee0366118b3d6ace45a7c2ed13664f5e385cdcc62e55b9f9bda29ecd3c607b4a0df92d065a202ae0e7c3936c16cfd0d3599d12b43fa81aee918d2e6f8d9ec8ba53663ce80b9806626f7d01934446097df1b410a624670737e79f2fa5076d5d2a183ba9f5aa45a4bc63d12200580b4ec50c0b63d34d0de9448d1c81f831956706d6d3242c612732756ad5ec60613d0b5597812cc5e2af9f049317c46d665850cfe0b39ca34ea4108a8d1a3b42cb8f1768f030aa2676759b4de3d3e869cd644888045d13c3c927252fa904f4dc94be05de02b2a373361c3568193bae3546d44f19eabdf5dad149535adc22a24d1a1feccb838dc73f57bc628182d42a2838477f862870f5ca05f5f99aa8aa813acd2d767eb2c860dc1821e20059728b204fe0897e0efb2074d9eea861da313c7a2fce3412d60248f1211cc12d275ca2aff33880ed144074fb55ec5dd4c5bb8e045c849d43e41f19040f902777b47cd5bd66e96ced565252035a3f864eafa761f6abf1c15805eaf258d679e48061a0b8bd91b22ea4d97ca0c6992fde352f4e860061ce649fdc1e9f8ef210fdda3cd0f893fd6e9b8a2ecee0384c4dea2cc7e3b4803902d90a07a697d6c537d8088dcf6d15e75a872403d3b2e85fc1be02e823cd6cbc09531b9fe967389ca99734d4de7a98a705e3063efe49cbb10eb561ebb14607b625bede0626e2de85930a1c4c4d1f33d60a835183bc4c06f38c1ec8452c284e9fad7972566b65fd2cc91c419c42a00b0cfb6aaf5b6b7881b7aec6894b58dee19b42fa164d90e0590e7d52492e04df7b0ab11649595b8e7824b0303cab159be89380c3aea99aa9b8a3191d26727654013e5ca83e54a012901f2f0b6090f0f375a81212fe22cb56113dd26fc96a24165ac9381a238e97295457f4d291a959037b222f2453763ddd6e9695b1909e4c2294a06336e0b7a047b8b74832ea327e2dabd122bd49f1543ed0313c600ece2c465cd58a7fedc4ca92c8c910743363fc694465cd806020c77ccf13bd211fea15d9dafe27e90414cef3b328109b65409e361635e98b2053a09030fbc0a6a07ef25c334ce8a962ed2ccaf51b52fec1e9838714f97649e687e019f7abd08a811a6971d5d7449b8000a40f34c975584e466ccdb79571bb898229102bdbeffd636bb7e5e839650238a3c9ce716e0ce47c8b0082dd1a9eae5b03cd921a91d3fe5c4de468c5325cbc2de9f0fb140ea143a246424f14de1e46dcf24b59150ee1514434454da9952f83a2de7c366680aaef8f202747614e835e7a635180dcf51622719e5d1ef803a6b3e531149f73f4b860d388a00f8d45b12c78924064dd03a13920962b172524f53f09bd916d4996c073724d1e80967e9b7e6b21141f61bf2d6583d577ac5510008e9ae1c1d5befd0168a198da8f1bc25602a57c3b3c0ca3a7fd5cc7fd9606440bab9423909dd97e0ace2303a6198ed044056340efa3396260d08f96b2180040d61f4c9b6b343068d76823b168d0bae21e7a19cd82d36528cc4f8773d04d0052d1aca134e857c2f0a54de577347dd332c69eef581b3a2199b42715b24657b7cd83baf9c4dc1e6324bb202c1677a9b949721dc63ff17b18c1e2d891401b2363d1c8a6597d85376f9fdc2a149feb937fc9c689f53d52d96b0471bb15caa8fb9c470a15b0126dd32a52570f7c27741fe4a1b5b1c1cfe24b5c967390a95c193a85eaaa97012e56413afbde3ce9775fcfed4030e69ff1c4d66ec301e096b074738e7708204a204d988798d60d8d9552071c6022042831ad15e9e5e568ebc778fe27e46c2cbae9e75b775bb074f94812307f1c4e362af797a85ee4362cfb84c0d160058dfbed6eca1b604ccfa04244583cc5d13ef49cbd4795da20deac657db4ba32945d3aad93e2a9b20b53dc92f4a4887bfdec75fd6bbf75d054eb4024e4a8162d01c061d03dd28181a21bf9abeb3dd8208f0243a314e4a7f13c4ab436d887924c782824b2376c297ad7a54bb15375c7209745b1835f369c73243f778155866390aab42bb8dded6196536d7ff388845a51b89e4277e98041c814a3dd7867e60b4abd14f8853df8f9b5d95b64c86d4c89b4ebddf39bf70bdeba1e85d8740d39c1f2013343d19523b8d97d2e2517dc4a7ec4446c3d4a2ee0cc3ed4c12daf7bb2b084cb3c5e031787b3918990b069451c0469e95413befebd3193cdef04453952f866e503855378ff6ec9a88e7a026f46ea4a8c7a37bc951b84d6be3e3eb539d2eaba148096a9b3aa2d244ea69317f4d9881090b5c764302461c11647abd3b6d711b35352961c4b7c9fe86fecec4d1c744bc61ba24f26b4f2766104489ef5aca9d22b2d2e965e740acaaae47fc7e61d5ce4d68c9ba296746cf2209504a19e801c3f9812d143367c56c67e6423085d3c9f019c6c04280be1c6935b79623faea0271c06b655f4fe48a28fd28262d0ea94349db52790ff2f2802215f18aca55a057a220c80cea40869a53aad95eebd2879132417086738baca77bc736064cdb5075782f8b19d1e9e8aedc1540f2aaab1ad89662e8413611b669499f43c9669d599630cb78119cd0e84e64d1035141372d0c1ce3fc71c5321638127a150ef23ff31e7047f061983e8bf3806d54f5e434f97ef02f25c7f9cb2c0ded4b693113c220cdf66403e55aa82f0cc032110f41a6cfdcaf31a73e6198f4a9465c8ee3f774cc60aa54d68f81df04234995b0928a9067145102d4fc3325164716a4cb130d270d135a4baa08d002b5fdb3b8a39ea7902dfeaf3a3c8f9d517cb56679cb391b752c6b9cb76a9258db2b047ce40892db394766d2a5703fca0e1bade5b3eda5551a0e8eb42e6ea31186575c1fc3150690bb217e3c325d8d10ac93a9530544dc874489f3d128b7a36270b9861e1bb25829c7da5678ef6c02090cba32c9fc40a5e66027476d3f8dd0aacb8803610bdc118c674056c7ae8f32b45d519a533e1a7c182a4473ddb3fd56940009979883d6384405cca36a930fa99916b63dbf4fb18531ff9985184c5efa67c1b2a82fe33216fea0b65ef8efadd078c3ce8fc3ba4f327324b0e1b79146ea3f3f654562d68e7e5cd8408a08af01928f7ab3a205101ea324fdcd843f18131b31fd1f56b9d4fdcaabfbf371d926e3c7760166b417d662d3b9fcde33c26596ad216fb7c86d8c459bae562e5506d4e17d023b164cf4d6021387d73e59b5a7325c65c652d9debfd309ec6c19100e31756229a2bd6844d245c2e775d6a13806c07c54b8f0a38f0cb7747bd3cf940a5138b52823bfdaf6fc89dc640814767b9b83a95117e0c7497153610a0bfca4fedb3614f2390a76c2dde410f4531f467f2792f06e8fd37f4b5d72da79a598f6f28a10e7a6f9ba17788b75f9c00bd774b02bf9140efef108514a545010ebc3089e03888361b32e493498beb07ecc62fec5b488a809c330870ef201c1d9a9162e3218c475104aeeb60031bf5d82c8d588a4d8ea314bde4f93f9fe2fbc351c583ab39d9586c8d32d29fda8009688aeff56bc0d8f87780e48af0ba3df200a7b1464a9089305f37d2b0081b07155f6e12589ad672c00f5d99727a824d2b76024d4ab677c684ca85100e9003ee0239300295ba71917dee7229f3bf61e33a19125bc8f227d746c54c96d92e4af5ab66a46429d0d1a57719adbfabd33eb97272888ad7d70a50e250e45fe2f5bc4c342132e9a21e527a881f07aa8ded011bdd1e28f1d33d2f20774553c511ead3131a126be8d0b92d0a0ef49704b99ed70a8fb0434e41184b4642013094308631968df20a390109174f27dab2e9a7b7c4ebbfe6e1f01cc9a926625916ebd020ac0a0a5c01c177261472b460f0bbda4ba89384b3693fd86a165d36238e9b25c8f05da49ff06cea8516a47bf4261721eb943b784a174a72d69188b09b13b05caa5600306524043b695bde271d02d9cafec9885591754d411e5c7839220b4453a396164cd89e36cfc1340d9cf0b1b945d6bfe60308087cf1adb57ff2511773233f3201fafeddc023f8f3d636b808b41e10c16a1bb447d87b1989460910ff6865f530535ecdf312c02a0f96e8daff5d3c0e2f69f785b2c417bb9ac1178a18ea58fca750d41e977bf2f6327cc113bca6a846fb7dd1719082e52a452ad7796cf613acb02e4f7fd9e437dc2c456368474b81a53d1aa7c9f70f5ef99c2ae707eccd73522e499b5e75c026fd716b353d844a4a205cffbc0b2cdc269878fdf3d6dbe7b1f09a561b2e5bbcf6ad4deada631ccc5f34cda80145f3907212bbd9946aab973ecd8efe053e817500374b7736a40f7b6d66c9ea87556f15e21e6bc8b13c161422db56dad915a4eda49cc484d19a8c6bf3a4d76cfc231a2a1e1a89f5486c3da1196f2f1314933d48f33e4a451de2a98cdc3aa097fa6082ee6f6ce170e66e0e26d3e7ade8c7e83221acffecf61c1c575107742b0c4635c2a6bc743dc64ef058a9f3fd132736c0bdfff847f624b6962824b81e88a202103c2b25cab3f654f7954bfae17b2d8e60e1cc42c31252e2f9b7e951c07ce30816365e81cc707f2c2ba16818db6098ee42ec4de8461ecff34c95d36f65e81a94f80af75ade8935ec11bd57ee297433a21550e957221e7872a8e8117566152378ad627cbae64b76397f34c6b776e2011302dcdefb7b343b51afd034e2b22f1623d9053dcb4aa78c80fbdb967fec5967e35c6f9f299c854c80aef7e91351e6837625b9db29357e8507d2d48f196bb9eef4c5389afb00890c8a3e1ab836961a82845eb44409edd5fccb3eb678a7d4ed9ab31bb5139922e5bdf691bad4698841ebdf560c7cbb727298e9b2778ca4e20a9f4313023be974f907f14e8f7f483b49573247b41a70ad35524deeac5a9551cfc3efb37598e4440854b3f69a195479ebfd8d5454ee86c26d46691665349296ae13c836d7ece8660d1a5c5929c7af63f269e8148fa8553c6b27fe38da500311d808f54a3d04c3dc83f4018b4f70cb8a94b50dc62c88229e844c784fb2ba3ffb6ac0f022caca988207b62e3803898552543e1a20bd01510e1fc82bb4fcf1b6794cad94bbe77eb2751d239ca1dcbd7f0528d2cdeead735451a4e9f10a1f61466021b5cddd87f885eae0ca1bd0e89be616128750cf44511900743f4bdf6fa33b41aad228c797cde430810f69bcfff4d40e5572dace36fbeb6ee9d3313f468c7a8f2273daa29ff2939e373978d41befc544e63755ead1e8cd9b5ea6723a99f6d4f0bf468afadf02771aa39e216cfcbe05ad7b9d763b7ce8f0b1ca74babdd7f115fddefe03d64d39282b4d98d294d0e67a37b058389b51351a91d4c090772e62bf0d2765357a7bd2f3dddacddc2c638a204f20cb9c9f811e0db1c2bb4160ed7958c983909dab21c011e896f250d682d18c462435fc5b9bf94b1cd44853f0ac34d8ab01f67850884d4a2173ea3bc4772a8fcfafaae6635eef0d43b79a8baa06e2641500398aab8949629310c9c4ed4faba16a88d6ca95a471dd4d47874df31d5cc146344d970fec86901e63f8e10bc38eb4b454a4fd50b687e486ad3f9cd26642af019f3db3dc118570df58d67f1196f7263fa4b73cfacf528876e325e72737ef4f57fe2bb5c8dc9f7943ab736ec68460ede1ba290a4ea4b309b600794c6263f970ab8d947ad49efe5b17e3747c72905a705f58980a743e00c85ee42166fa3a37fae38d4afa3c7c5c47f5809d9009a69962f1e2c9fa75b8b6037f791e080caeb4d2adf91c251fff561b9342c357b6a2f8fbeb32c11d697778f354fce7f5af9f1590af78eeea3310c9bb7cad8b9abb5f6cf1f5145654cbcbe0581c3e651bade0074d4191428dba1a2713dfe3426a8c426714ef6723609a59471e96a8ef0fb5ae8a4f948314da6168db88a800fb7f23201e7c7a0f3fe4185a03a2e11ea198479c32b4ffe841dbe51b110e49a852d879aaf49786cb984ee8bc7820e8a3ef269f3079bfd4030a75d0c3826a3d202d7e7d42fd10d3c2b45e8580038910cb71cd73a7cec5bf2ff6891865f13149a24520a905bb70def5586b9649ba090de5dc0a6f66b77bf5911ab8b80edb89f34dafc004d321a98631f1329d722c2255cfa533a6daf926510568200be69b6f51a2f12e3208d72b61e4991e51ed0e79d328e710e4dd029bc881f1055cd449994ef7e227335b0c4c268cabf9d17a824e9c78b5719a4c273e48e38be4b43426b67cd2d87c80a248afdda7a41fa80e68d825b817ace3c08126d5bbcad080355eadc7e419fd6b275903c0ab2e34339205264b196eee440621042f2eed99c95406894b68bd58e214f5de541acb023d20dadedd8a0e83250737a2159a0a23b5021a8fe23ba99d80222c1e689940c46050fbbed8f0ad69c0b5b0548824ab97d81b5daaac31cd22097188debd97c3dbfb91cf33ba0ec6aa0101238b462699f3725ee7ea46c0d8b09e8630ad5be55a4fb4e1061ac4610519f942f14e5fbee506165e0b58f8e26ce03394f3f3460b867457dc0c3d3d470c6269dd08aea301ec07866df174c75cf9f97f0c30b813c777f4e0960acf43eae22064246b6ad387308067b67afe0f669cba5c6689d8ebfb5129584b77ef889564773b13295cf781ca5dfa4c5f2d114db2d64d021a13d7d5125a3351a68090128f423323aa66175e2ff8be72e417e9c1cd0f3b5329b12c47df458d0e10b594b0b88e7e4dbcc6b3316a8b9d3f41a06c3338c6bb8d4592cd4d0fb9921273c64860039dc16d7f5b4a6a163223b99302f4f4ad16c5bed6662efa412462d291ca4a8fd3046a24416da9f49fd5172c9270b283d0f7802138087d999505153fba2cc42fda9bce1509cdf96b168d145a447b83e2bd13c97125451639ae580d8d4299708642fdfe6c11864f63daae9d0cab717e08468b6cb6012f72a251293775138a8c3e3bdc70a2445058a75cc801b81fb15319847d3fed917750c6e97de9f825838f7a704d4c487eb3fca6bc46f715d504696675d74d44c3b85404f0bbbfc07bcb7f02c2e07290e12e11ea4979cc874cb4c14f429cd1894d754e483f6afd40e2bc314bc77ddd81152f1de04a94ee4bcdccca2892849ad2674fc988cae657fe90764d632ef3c6b8ce1f6f795a920601e41501f97b4849fbd9bb00a9a296038583f145e60b4dcf3b659e37338e38c7df2701326e4c490ce02be0fa7bfa618cb80d04af40ebbc3f5a9746721308d42b10e8999820377aa50e7c962e0ca1c06f591ec44b30733a3816949d440d2e9d28f1fe14794f7558249420924ea08a0810f6cbd5a26452858d6fa80d4c97c29926e30257be9168860e5ba62a93215c0262674a070d54e296c7c7f05e22101460515e933c89c4d99caca7521cb07929341e2a4832e31a0122e9efe8a04cc038ce0dc9de0fb345ea7978b685ff21efa8c6f0ac18f5c67f20639864d405547197b046399b1166e2d8f7dc78335f28e58a4e045d2128b00bce9041001c95897eb1b4ce70dbae2dd74479e071979258a834d2176b3859f3a14b954841bccc1c016acdb1a2785253b777a938ca725218024eb428d2b7d4cf18299561ecada4fc4f3957d377b606914b95197102fa94ea9b53fd6eea90940a92f25bf5887a33378e5e935eafcada1ab832dc14b30e8da01592d68caf864cf06e3c13f26afad5e731f789a13c5e1b566df84c9d3eefd7ac67c4f17267e30054b4bca011c2da5ea95c8bab2749a561cf64b88eccd85d3f21f68cfa9945eaa7e6d649231a48593bb28cb5e4bcde79db1df4c246d9a72641034354e926a650ab9b353a9a3b58c89f0a398722250c8224e3fe865a2889fae2f2a8c9082611b44078e3366451176cc999a241e11d48631fa13d00c762e8a4b5a8b5d0e0d2c6808137ce109d3abdae87815c60d8cd04d414f177efbbf46bc7b1df02d7af8e1b5e4e084cffac6c17acdafef39a9284b147c02494c00870a2892c6b4bfcce02297732ec6a98bf3d36208a30adda3930f529a6c7ff2c50596ce7336747206ce57b826f254d77b3cb6e2ab57827a17082feba258ba306033e80b409530cef3eb0f5c68c9c959beafb7440a8516ecae0c1b16c0354dfc618454666b55611e1eb2d63e9b8a535a919ca23787d4a7335d91cace77b4a2f4b23f79e470c75e053c955f56dac5c1cf3d2565cc21d396346cc72f4beceb28d4f43e72ac9c651f9b83b308d025a2180f7acc0579592ebc1de853e9769d235a6d26fcfdf07ef366bcca2e8f6a6ddcbaeb47f7db2c521f0b0e318f0e5659dffb172dc2f0e2c0f13fbbc06b7cbb3ec625ec933ef9d54860b8839c92949cf26369238880b51a2fd11786f82075acc93de1842721a841129fde503432f0217f9c7005b53a5818077135679e80133f936d88742e1e16987063b631a44dbb13e1845d72222ca59193ac38a622b2a59ee8250ea2ce83a7f21cc7f979407d5072c61ca93499f2170decab63edb5e7eb387fab79790d648809db3bef891856adcf120e3e3836a2678ce940414cb942dec3bb43926e696aecef3d53ddbc1836b93f5414623728c50b8bfa7ac7dc9d6a21c1543afaeae95acf49be8540d42937983e579188e1025b87a40af4ef4227f1402fa7143f08e757ca6a4070f7bbb1bc3ded971d9589a2e6a7f7df5dfc10ec91efe77a6dfe898b87974dff839b348d3f34c3905c30c9f095f1179a0998e11871d3d224e65ebda3452217c9a849251dc864acc237794241d23f35d70d83a5acfa9f71781cda5d4f900e07c977acee4069d739e0348b9f5d69fd461f30855078f8e03adb575578e5bce8855f87fc650eb56b7d5abdbe1eb93eaaf573359b75cda0e94e53c6629c7eb018e6239fb94a6b9133eba1902f431e95e2f6dd76304f09c418560b68012492dbcce88c50ada4c5101f8ecb38d7d3f94590940999bb569adbbdfd9374abdd35194a57cc905d9d760886be933b4c69170096fd6ded354bcd7acc3abeb4aeb1fd3c241d62810f3b21c05645de08102363dc40619d5bd54c3c56d6e9f51aec37d0d16952fb107b99dde3a118f17d73f48798924b9b80c93081379ba20835ca75ba0347751a1fc9ce807850384bf72330e85e4ca49e17e78c20d3122d6bd8325003234c4f99c3636564db1954a63e7654c709700fe5b29a948678641e9264ac4288ca7c6d84c2ce43637c6302986d8d141c94eae01741be8f6ae95d328464a1a7b09e589bf8b6edd86965e971ed4fb6b5517613df1420fcf9a7824bf05785fb3598495601ebc4f38786f0b957bdfc2caa44acbe64d30a825da55ac9c5f4dda5837568d39ce4999a924e9a00a50e5759a46598e8c586024116d4dfcaddfa1481f26b7a7024be93f87506bd6ed61a2fc10c2260a3caab0a5601f4a10180ba70b1baf43a497b7af4984ee40f169fa306cd4da8674ec94fb7d25bb91700ce9f291cb66a56b0d481414bdc9804216615cb011877de231722df443de35b0ef89c65fe0c69df4c92676b709920825d10bf361f551a9a0853270e084358ce884c74b7eacde5d4269122487963669678648d1f51f19160f6e686572b5a4e036ddda22f55f6087c0a8745e1e7bdd4d9b5ab61f775170ca2084705e6e4bbba312814c84a5496c4a70db3432be323a8d58130b6b02a2a4cba601b2146c1528c5eff2bb8c8ae56c945827e2c4057453d4f57f27cfc6fb7492f8e98a8fe5b6da88480137dd423a28a924b52415d5da0de5149d4bfed665827c53287e3db9adc085565abb007853a879685d91af2635880c03a854dd706f471a09986f114eb99f73639692d0ef979adbb47883fb4edfc12a1eb92e3c887c3aca481c7a9c1bc17ec6364ccd6d86bc159dfad9b9380c6b753d454413e5fbeb5fe375119ae4980f725c79d184578076287027efa6a72a6839547a6fd1e5ca3f11413bf121a2e2832b7002cdc84d1bf584e1a7b4297c58494faa6bd64f305063ab7b87bacbb7e1a66f9f712f68994695df2611fdb662ed6f439f7ffb8c83e13e7928f726cabae74c03a2e781884a288f9bad844cd5451e022b59699d33e9844bfaac92723712b0d6a12b46964e451aabc127fb91b52e13a725e9d004955f890cd101d55a6013e6d63460b8184219a89202e57a2b103ff88202661122b4d6ff81a3ce78326069f800c1240373daa8572309a46d00fb0de1ba9f1541eb5caaf5d61d0abfd4b2bdf5feaef8dcdf53767f5f66fda7856797c25449f23f27668f958aa7e782d0d1186e67c7dfeddd67219ec29a574dff67b6a83955537fc8051fda24c5eaf615031e7e414c3c5bc6c15a5dce2179e7258c16d382f3e38b9714d01240a8ab23c549833034fedbae18b20f3de2c989cf8c43808acc8ddc52171ef6feb11ef1d9e3de8cdb48ac2715075f32b827fa7da09bb7efb2fe82ad98149ea821c71c16bf66bbb8a36762417f0371b0e47fc006465f4507a96ee81d6658a1a5b394a5f10a301711584b9ca671341808bb44e6579412bf89d3f4740a64b18e30873d0508efe8d249d8320f29e9bce0bc70b48df5ee2acd4eb1fda4671896c93655326c021e9c48f70d9848207e1a43c870aa0e5d8cca0a3222c3f8ce69d1fe3c448da2c044da311f1bd6ac0db6d857baaaca16922924c76760b61dc26ce97faad813945669f77e134b05db2c28385e5afd8a693f82e4dc16e8d251f9c64a39d57f8154cd10056d7f669d90fd33a44b5e23f948a4cd17a980a1f679158eb17d68be7123c6e707deae747fb7df585f6ec6aaca780cebc2d012fd2f8b64ebabd6ca50c2dbf53c26231c0b72a9bf674d589b7a4ce703b6cfb65c5c925faa4ddaf4e83cdd1a31399f5efdff287da35a3c6bf501b0074b06797e5a026119c0f8066cc26fc6e37d67447199923a3b4bf9e3b1d8803b9b3f7c7419dcd93b39593703c1948140a974625dd2d48b1e707277cd89e721a313f1bfe70ec607cbdd6fbee846fe996b1bcd899b4d0cc5a64612c05de8801ca6eba7567888787b982c45249660f425afa807477905f855cbf4ada20aabfe32e515f7d42753515a20993523006c2e4cfe07978497082b406133376ee3136f17d2c51df809237bab6dcda43bf298e9caa2213f57755889ff366498f8d001d869f5ea5fe9b785a0b98ef5d2b99bb39f3c009030f294e67e2650f60d80c8b0d5edebb410f81269d71c00808c0138897f1b47197b64eb80a3afae0153f5784c1eed78d15dcec1481265c97c15a0a9979408a0c11a017ce2886156b7c85fce4a6885e52517a7ddbaa00c157058cb65dc54cd82c16b037a433bd15b6b9db58644637ab2384cb6a0bd560cd13c83aa8d7a652ed7444a3fbcbc0e3f82e0ba567aecc5761eeb7e6c36c80ea36f9442526eb9bfe4fc2a070c44c379df3135233a7349e55eb4a6a315aa4eb9257972b2f3ce327536a0862ced5ad64c42b90dc2b883f4346555764020c3ed3a4d82fd886e0eb1b84374d913d70e48b11ac5e492b9f272b36451d6a439a5bdf95c3bc6c68998a3be6772b7fae3b2e03005127b1738ade6d40b8df4f48059904bba92a00ea38674a04cd0fd00e81d5b00fde124bee0f8d92ba555a55a2b873491287ad45eae51ab166244e3ea991b3989f98f475e5cbd22fc045de39b98f77bda523155ddbc80228eb18c6a7e8583e968fd9e98cd8131bf84ad3a33f523070da76cfa2eb244c229ef79f85fae68b080b485fa012d84deced39b55e8a0c62757abca4a10ab68fb99f8132fe19d5f8be3403b79d6d4619e3509e759839eda842479c3d8a38806f4a55f10a03a89372b9578c49d7aba5612ca6333d3ea1e0d2f59490248ce7c5dcc5473384b6d66b647518564fdd26e4b07e7d88d6d43a0c4348ff2128c267bef7aa728fe83138064a005edc85ef40208b66b0a1932f371086855b59cc0913944f2c1019beb6ded8be0bb29ef17dcec85fe26398569982a36fabfc9b29083ef85e95f8a8f1fc3aba42cd7e983d841ded7d00770af4c3ad29decca04b355ab15c456d785fc80b56b621ade44368a3cafc989578c0c672cf90577e4d8e953af40fa46abebf130f69b9c8eb811d407b5a37668b78c4f54d0f405a0d31b370187c32dc22723cbd663c7ed90c6cd2d365d35356b4dc5c7e79013c9483ba2730e79e77653b39dd7e5db8f4042a270daf7410a1a7f8e15d30e1c3baadecfd655fbbde20f5af90ee94346e4657e6157e1166740acc6da7eacdac1aacdf8cb78d3429469087cbd036f863aa156b5ce4feb7ef0d25dd7f8779633a7ff803ab2d0f375f1720746062bb72802c9aab8c3615779e06c4cade60231e0fafd92712ed027f7d9fc07abb24b8912f6e41fb99c354eb2eeba293f8d14768cd6e10e784abf9341ee6afa89990861df54264fbfcc4e8383187062e88d0ea30dfb1add0a8a8e95118a5a9cda1014e912498e017d4fb6e57abaa500fa75a610aa9bb3405477e81f866db6e3c04b7c2a97a13fcd95a21b111685557e70c2fdc095dd7ed540036b6b5ef40e4175a154e95cf2cc14876aa071a681e8319920a7a05107524f28d9c7f56e01329d270607cf4fe1487c32d86c9d2b531c285a9cc08fe94000b20f2a01ad535688d81b1234a1e79d925724a0b702b0a812f667d8c87e08003188361ead9c1895a140562d602d4a05848c60526cf88d5a6fe1081ab8b5a0132a496b45cfdfecdf44628b4584748465fd151e0a8b9599f935f729dea0b32fec20a4a1aa8e256ccac580520fe7579421ada85f6f32493299c80a453507839ee7ce6e14248ce12398af6081d57d1867ac3105c5d03f08757e8ecbf5acf3a5564baeeb9a4893a23d03207a559fb15ef4f7535d1072a6c64298c2e0a6cc07aba935c72a29bf8fb71cb7e7aa0199bb8408ca1569b08eab1f9ed85f1e616e36a4943286b636795ad720933a38dab3942c54813af707881c67d0e7859526d55857230ca708773298e5b63b04af93302630fc199c7294e7bc15736a08a8636b2da13990dc6e36657a0f1027ce3175aea4c7f9cb819ae8a596a8de03e3941707396bf53f6ded2245c2718ce684c874e026bc807975a4c359aebf1f8fba6fa7b64bd12c17976b6e3f25095a63a1d7b2ba8327ec94421622cefb7cf035e90290689ab628812cb7442c32773c57686156062db141a3f3b88b4094e72384faf327f60460edf6b1c6c272225b0e1537cb4023c092d19a729d7b60bc1cbd8634b49a66c8939714480b2bb9b2f5ede70c75959914d0742b8a7f15ce517df306026d0329099d46bddca172f5059fefd4f005e4f36640840e148415712cf29a58d93cb3ad727298199e29628d2579fa2e4576176aef7da05393a27808106af4bdc6f10ebb5c95b9b4c549523bd89a4f47c3ea9fbc0b5bcec2ca0b40f8a3eea20eeb3476fc3106ee4ae8924ef4fcc11a24ef92d57624d6a6d88e64b8fc7205c9a3459168c6f2a73c27a238455809c5704ee35330b67b08f5723fc778ae06ec5fc5e739f34b23f88f47784330de6f0489648337f002e7b206446e27853a3f29b96bf603db206ab49a32cfc863dfa277c27c7239efb7f055ea2460f3ada5a2287e0e05123906960477049526e3f53bfdb536cad0186acfa8465d74d4d31f81aaa984cec1f69942c594bfb2265721b5d0e79fa8bba97a54486b514cf65af8213d329a1d8c491935a7912a07fe82ef14e1946ac253a2e537cf19aa0754b70348c4179dcb216f27fe9a4b3397b81171202747e760d9b68bfcea7495731283b8fb0ed0c2324fba509ff70234dfc351ea95044c36be6f32021638eb3b871f20d29053fc93764b69480626eff29f133147ac6842c889aa1f49992db5524d06588eb400517a57cc11f110c515ab3b561345c2583b03f4250fa38e304981ead19a42c9a0f7b89e242dd15202aee55c7aa4684cd17216f98b1ba410475808d784e83eac52454b0f082155be2a88431181fe058ad9be4edddf014705b9e29ee63179ef54702bc7263d1c357b13066a686815f768a2942e5c765402dfa821f6c0c001a3ab32c327e1e3f9cc0f1c15529fbea1db314efdbec583a92ff924955b27e17c80a30f2c13c40051dc19d2c922a78867a44606433c623b1a2f120619bdb1752b8f2884a04752a04be3bab1f55d0d2f82434c98fd76fa7bf5304aa0ba1f29abb9722ea7fc843e26e76988851913194e1ba81970a74a4c88248a31b14dc0b6be71ab799a3a38e9e5426531dc77698a94b1fef82fd60607c234801387df0678201a3ada2437c9f24eb228334f465ef2e394e90af4723f7973d552680fb29f7d8f6bb7a3526e3a3ef1125e7c88146570d99aafef3235ba452a8849dfb03898fd976e7ae18d4a056b810ec2e9f8061f1250e673def1ea198019464259c88451d07ad3e0b05cf4e606bd6db3499a9b62543591e2414c74adef5ee79ff0bcfd144fdf26f83022591b4681c28561ec243888578d291adac2f3b87141547837fadfc3e98eb50e2aff306c982eb240ed18a64c95e3376e8b7807fb26e9388861a962d16a7b532a36e3136a147d1250497137187ea6da7c26d1cd620a02260003c3e91f37b6e990862f7155fe74b8e3d1a393a03cd625e98ea124761e56a32a926dafef13ff8c64790a7e731af4bde73c51270b1a427c5cfadedcf34b3c90a325aa4dc20e72542ae754be8d44dd3cace8bf71acbb79d4632de81e4f896019190839d14cdf5b2dc901d4fd7f5069160e3b5cebec88a6008de6e8c8fcd00338749bc7b23d4edd106e4228f609e544dd2e1c603f51a100d44502c3eab449c350ab87e3b1b2109f9d7863b33a4e0ae52fc3a28dafbc4214588d367124401e3df880b72b94da04b1e64bc9dc15efd3f295f841cc09e0402f432ebca9f96cc78563c7709cf392167c8a4756d7e5bfb7f2f4c110f6f45b55d6efc09703e868a2df39960391b868e23b440b897c1944c7d98e4fcd48ad6e04e515160d3d02489d2205dd7ed068ce367d1b5aa4b30660210e3ae299483048937c7141dce48557f9167cc61a51c3700dc529e4829547f3b61194860ef1802b2c1b577ecad9003ce93fa936b80db0eede296545dbc594441484da0afd3bb8c962fb8ee9d293e435acecf5e250af419254ef81b30b76ee40927c4802320091708d8fd21420dfafb6a76f913d81caa00e42ea18ab3c9354818260d30ded891bf02de8fdd63947158604297d559e0aacc9885bceff45db6c836e603ae2488032bb4e6ad8df112f9b95540d385bb81eb747480c757ab6b397da25c2e0db341126b9dcd5b21890d3c7c17c8bdc10735bb6214a123f92ab67c2475df7f68f9b447e3b0ccf8bc30b4388628c47bfe07073f2d77e39d1d86920174796074a03fa59a194ccbae1baa6929b337b1bca2bcfaaa813418999e4b2fb768655a8000e32a66b00af5a2ae41a403414c3145e31a6e9c736f265031398929f80d2b88f04e6324002fa1662e7ec6a1cb2e1389830febf5db6a2cf75d52349f5f4c3df54178f830e42baa6b08b0af0b92a067859d0b2b472d6e6aee12dffa09d57ed185be52970220e239bb0520848962aca0a1b606bb1bdcc19c102748207af694542baa5c2a73495287aba0a5486961fb6a973e76ea1ca9c8718e3864f1228057868b09993966bba466865f6bd136880374fd7f2a849b3ef983d1dd359986234731b76367ba476b3c50d149451edac9c6c2744d25fb04159f0051981c1ec0a0d5c5f6d1d642e8e2bb67dc1522e8fa4949d4ca3dced1311cffec000839bbff62194051f7871962b74d20ede2cce1cb058418f98d0d9b88716843f7a7e7436f5df943e63e0847f39644a60878dc7eb2b9c62656e3118374021f9f2e18499ca9919d20e2817e0e3c17578d1a099dd749aba3271810020606b4f535d4cab69b1e3d85e3a8adf29f485974d5a51deac0b4fc79cc4c65e2688e4eb275861561ce549dd75980486f8fe78d25c44139fe2e79853c7c0f7ce7ec2154f67a3030a00b54f1b79a4aca673cc585868b3c83f062363a9b55430b5f63c2b5d2a9e0ed7b7abc2a35642db7ec4780542b181df494ccef8fbd479506d7e62f4ecea78133012f969104975eab8b6551bf01189ed2e4a5974cade921c095391f5d85607ec8879bfae071783c897ca6952c778ccceb1b8cfeb36c760a3b1ef24533885e0218fdc3df64041379d8af6a74db7d83965a7f2ac8cac759ba23400027b0a31917ee6d555b4621b9ee5b979786f7932b4f967570988c9de2dca7678987fa2db28be205884f6db26a9d9c4cc96ef9d326404f0862900f4705f52362196ff6e7cdf5a8d9d3bc07f498640a6c523d7732713d1e07da59f4e4d71356318223dc1ff3d4e2fab0d8dc0e8b57cfef703ed5cbb2bfcfbc5d2a626be4c188ea248b5b89cee19cf3420c7afdc6f446fa8be78f5e9ab69ccc8593e73c072d5d8865df11b4633732e04b930ce966698901387450e2d03ae880fadf3a809d11df3bbead6a74ff475181914487bf3b07c14694bbb08100ee7a7c5fa5d0a7925e692ae16e9d2a5d971c2a212804fe05d4b6d4320ec4cd2a7366586809e3d252d80a701f294b02b5b5ccf0af58cbdd8ed20d5d43e12b658b090032dbf4ef48ef6932d382a1594edf2539985d1c59febbefebe06871468b807d66523674bb6c282b32bb1dd4397202834593b63e7d81c5edb46a1fa8520138dc93496a649fa251844ca1a96ca529de9a3927ce03eb9857becf42b4a6af6748ac7df9af261373a1b1e12ab0e9c1f0024de6348696279930cc2c9aa4d06c5c5620bc9e43ed099b0d2ee88c352c849a1d3cec46e71c791f251991b12346c1d38b39fc6c318c4392ca3ac2f6be1f284c0fb0d63c0ba5f836868897b1d8b6c361de789648500a5bcee6f0de32a80b9a6dc0945739b72ee8209bcfdf08f4c40fcbe2c645e25ccf9951ce0951cdad7fa4ce4e8e8ad71fa59c83c13551092086b9b43ed2d2a89714b6f4b03ae90556f35548b5b9a683302c5298bba874dca7e5b1678ee9817e4ac6b9298a09a2b6e1eebea1948c576a38973675497650a82304c3985597ed11692ce2711653e1219dec6804a34878bd09ca2d0109923e00154af9eae1364b0a394a38535d11562dcd81bfb8199097e2afafc958f1cb17bff8ef6b63822e77c65d5b63070e035cd49d40a965456b77759b50e6a7bf1c398d3088b68b9723e2c86d5ba667485fb3f6e15f5d49e1057dc8304f8b0b9b25ac291259f890dbf568544de159b9bdfd52e6790283679f49646bfe4b6d8c1d7e9fad4ba699e3e207394c039aaa54930c2793e814c5185308c6a0b1614d1ffd8769a996a23783961e5cdd9bfcb435b226b2c494dabedd1e138aa69df5c059596477c5e4b6a54849dae5e49a9e5719fb7959896947d38cf41a5e5c41ebd14842abb50b271114c15242f6dd1285b8b768a1a58854c8b7178df693956f23967a8c1b8ab04a5bccb436194c4e45f11e995fb63f54f74088d08bcf3feabeeb9e851fe35b22f1384bc2cf7d6421c56fdb0eb8adc78dabf088fccb403cd3764e10b6c049aa23b99888a7e0b4068b41f2f72e89cb496fc60f6f6150117c3960ef5920c680bc1ab05243e6bcc2d58125c62d712c4767823e189e930109700ca4ea9fd0e8877d2b2f4b544d443a1a249cd5c4e895761c056dc1ce53508366f646522b4d870cdd7d46ee4e5505dd1fdc040b9b6cc3ef4f5fd62fd19f22e43bffb4ef9c82b62421cdf9dc848047fd4093313078cf1a5098831aadade0f6e0c64d9edd32285c8d945125cc24eb44e8529c74053ae81ae887cc1e7291ee3efd7a0f9a4a2df0942348887088f56d2248e42dcdec2807215128df8aa9fb731cce2798a465d0ea432590d34f066c83336180fb9abcc2526a30f41cecc60509a3a0e2ffc7d31109d8975bc7c08259f9a585231def61305e4209dd868fd7d5d25fe906970f8e80de85e6f0855a218344457270bd5ce412e31e6267def85a42a41887ca1514e032e3f50d266bc50275357966295d5945b0d0d5f541a4f65fede598d8b18f465dee2dd592673937e6b9cd09976cc9621076438e9bcf7724773cd181c5a52b348a1b0fa209fa8d577a1e5afcb7d3d48d149f99daf53f040f91b42a2047bc3e69d8f1183e2495b4d91666bfc5cb7ff14e7c778033ba027d61e35042f942d982099796c8ba28116168107748f988f54843f91a91387260d9ed844d870bc52f15c434d16114a6b0cf0837f376c55127904abc47e50aa9dfb936a69a94e325cebb5337a89e2a57fea8ec431f0841279153f50664e2c781e30ce88e05cb735f39f1604d2b112c3f03387002b682c30a064dd27dd48bb572a3156b5d953972e2231edba5cb623249bbb4735a61811b825bc2d8462ae841006cff8b2299d36d16427c89f5afbe8e09ef04b82b4f313b14e2e9a8ec7cc31ba68890a94cef2aed005550f07a48075b1fbcb3ef67c9a67af0b1b88fe49ddcc480a04f31f62b30a4ecf41d5a520059f6b805396b74a4f31baa25cb2f72e95936e46013286f9637c4a2adda682954146e8c5f52ab9552d28bd8f3eeb6c45f274c5593f002d1efc8130b72d484a3dafe870866f476fe89a3178c88c7e2960c16083546b9c3bcb0073b02fdaa565378d1b21d65a76d837e6bdddeec6cf9c0b3296eae82f0a0b18b04edf4dacec1ac79b5ca432dfa05b7e27a1a26b6638f5119cb394c34268cd27db5a9e7747b462a5fc293789a178a08d8960a8927db5ab7394928dba9ec6f290269ad161615629840cbacf005bcb606a56c5a2f7945b2c72e1abe5eefc487732c91908ec67a2fb68fe6d6381dba5ebd03aea9f5f83b070439038047f041aa7a7bd5b2052b2da1c96cf20bade6378cfae106d42cb94593a15ddf45fd34cd09cf7789d02fd35ae933457d24cb0f1495dd5d2bb0a06e801502148211ea6942ad760e514f2a8a6f83d025ee8d1c9c9188d58d5566d32ddddddddbd654a5206880b610bf60b3ffcfa8a180890558c0c8e4a916ad0aa3c593132d7899ef7615830304131356855aaca93c5656b47fdeb0aa87fe9f8b2e586a1b8f270e451d5a097ead92575bdf592f21ac5e9e5e57aa5710e9884943011dfa75396866035c6fe92bad7a976b5b0a380e406096df7de8b225fbc866cf5a8f119aff11917c2afd6759fbf65caf9fef586cdf875935f2bbf8e835f2a87f1eb94f21752e40932b8a91f250c6cb28e17ff527e5dae4bfd14d9d281f2ebb73c5b3b0e70c619673c8cb7745c4f79aa3c4d1894fbfc756a56797ae579cbbb44de1a7995d8e4cb04c5e541db27c67a312e8db778d0d4d88cb7225063335e6333a5e93468bce5214c8d4d5662932b151a64add184837bde5a8dead41e443f653e14fdbc1f8635e83a05e59871aedd2269c8181a3653bef0796b90ec767ed620ce2786a23f9267756276d02387d41ad2a74b228af29d4ba0d680eaf4771d9581124fdf4d8ebe775808bf28107ed1d88c734e9bb438ae4543eef0507bce6fdb56b674b4f360b30605a58264b866a9b3dc936e5d2f5104d5b195c2505e82a1d75a8001dc541327f5f5fb5abf52ce65a0ecd5b93c0ff075b473cbdc7ebecc6c44f46623da88daf3ee25026b5c112dda3dcd5fe2b25736d97c364d17bd61666f4aab5ccbc985552084a82e8fc1b74731f45d9b7ca53a1ca8e48b95dbcead215c4ee8052a2f50c0fbce999e81deb4b7571abebd5939b40fdfdd3ea443c8220a48a89bd42f755607b88de300e0848516cf71ed86a3755536383b3729a0dbe6ecba11e2d2a77e9382cbae83fbd68eea9b6f5da95dc16e6500aab0afa753ae007afbc4adf12ace0e0b38391c94f650c5c43d1908a8614782902861f2ed3dc2d8e0b676549b90fb3c64d8d32956043d9338707ecab82a21dcae9dd6f4edb4db9fcc9b1baef91d96455d57dbf3eeadfc799669cf32a02796393d3539310dd188908e966aadd5f34a7ebef57c27437f43b6ef2a536daa57da5e55a5ae24f7ac4dd4ca9a84de5e816a7b6d6abbb1f885124942d7cd0eb73e3b61b3cd157e388207673839a3092dd617b062840a7e40c40e643011eb6e0f85869cda133d926b035193c964330c293939ab51d77e6423cd064bfeac4267655267b69afc6a76f2eddd4dcc1ad45e85aaac0285cc1e7374ab6cd6715c2811bf241076c1606463c93dab118abc2e9ed128363122d246f58836ab6f7bdea8232c37af230c6c6e0e24478e6ea10523aa65a35a8e644344fc6217baaccac4097fdbb6b515775cecda99f316f8e593835f3e39d8a43fa44948cdb0a4cc532b2a890b03901f3e727658e0ca93d66c36ce87639028dc3ca4483eec33860f32c43e3b75df461edeb6a6b6395286fa9c37b9c382f78538be3a772187f31cc7b1f7772b3499011c8fe339e7cab3d6fa793d7bbc101cef79fe3948da53088e1e9cb3c02fca029b1fb9e383f37ac673258f1a441fbd0c224375fa21f59b84407451149d236d797ae4e923fb6a9d8eb56d6d22b20debc8655522bbb61ed93cef471e67733beb91cdeb11d1057f2b96945d95acdcc873840b7ffc882186115270cfaa7456a0edac4075a932e12849fcfabc6387722bd26fb687c92aab53f4f0f39dc7c02f9f18d8dcb6f007bf7c7e738e52f9c1e65681f895636b72ab127ef19092dfbce70586d1981086d55847654b0d9afeac32988d6dce420bdbc604b383905ac00855833431426bb1a2b33ee85ef7d764578ee4f7f94ef7475a6b81bcc02658fe6093d25e72bb3c99099b9593965cdfe4a3fca6be1d45ba90230980da3dab937360e94284b058e5e7f5eceb52356c7eee954ca5642536dbcf9e356c27cb10bbe8ef642162933e0f36dba9123dfbca4f33e5c82e1167a9f0cbd2dea341ba99915d82ea845ec3d03bf214cbb0b4a00bd67a48d20fb2e148fe1475a844c990d216a3efbcbb989db6d605125ac301966aa97c15fa76af5685c80a59246c56df8282c0d0a35ed72e65dc5ab34aecaa0e8029b72672fd36c8f6119b55885f1fd5a902ba08ce9aa9419fe95c9b8cde4abdc42faf6163399bcd6666dc2096099b7588cd66fa28f7b44a94a46270698d8ba6b7355bb335281e15a5eb198d3a5cdf54d8aced61931bb0b5affcb0597368b8a725faea413e1f52bf63a5af41da3baf9ea54275b800491950efa07be4c995676995b8d0797b48ebd1a54a5b987ce74ddf5a256cd6263d40340b9ba9d2f7c8b64cec907f931be03f9ba94a5ba9305b6b57f5ea6787e183af4e6b0ba10abda935cc16415dd56b4f2cd3d7daf4d5c5d112d19bea0040baedf44fab447ca2d32a2dd9a7666a9d3d6b75b9c3fcf63e62627ff8057af51ac65e50577531bc316e19acae81e9abb751cf9aca69f367cf584937cad950be2af16ba9be8df2d59b885f2733792795c626947ac466ad3d5057f5cfebd415e14f4eaa4efcaaff535cfb953bccff9c69df9e2387e7791e4d81cd1c5c5ccfadf78352da44a953d32eda019074db3df74617baddf3960ecec12f5be0d70ed31e7c0adf40f0e8195feb08be6a80baaa77a50f36bb1437aedbf675be8d3c31b02906d19b0af3d515e079df6e6f97e790ce6cff4aea2db410a4a5e3f3ce3fa7df9567477e6402d8a4ce12e29e94f6955d990403bf8474ef81feb3bebff003c8f79bd7339e370f83d8ac5fca864dd143ef19bf5aa09f35c86887f9a0836545c2e613aa539fe82391ec41f2609332937a4275ce6b5057af50a80ee89e772b57af7a4eed79f732a92ad42091997cf5cfcfebe23c5c44fe2b7b7c62238f3cd6690da3f7f8c4c69352f19fd329ff3998f45f79facc3e8807fa9b846074facd632c47ffc8cf4520ebd8bd1a7c64b4a120a01f1a6690c56cf089754f7cddc8175ed86422e3b821a4b53287e06604d4e6ec8696983c7e72a279debd2c96bb4984c35fd75aab8f91b541ff6db67e6ec9cf435204f2ac2f42255f368e0a9b3c2a4025827e83c3e56afd6c18866158b7da9f3eb3efadcc4090cfbb262c6cde005dae894d2a9efecd086eadb5d62ecf1ecb35bb58e7b823aa63b7926be268fce2d8c64f960d18d24ffe95538689f1d59517dcb343d229e990f0ebec823e885f4c63936b7e2b4ffb9bad7fc32dc9ba515236dddddd92324ebdec80d8b457a88ef59243c226dbd4ae334e96277f4c9df118afe30af66dc91d29ba27bdc2b42b47b8267e91cece61e19793340634507a27e98b2479491245922e24395acb529ead25c994bbb50be0a6e8210ca6b725a566599eb4e645ed37df61d27fa76d13bf9c5f9b8fe0cc62dd4b53e3341eabe5274bb144f4868788f82582b3f31097e5e9d170b23caf2d4f9675cfbb97c572374d9c1a39357c2313c002f378b9230249bf0679d2a8544e1bf696259a21375059e4694f9b982be2afc80dd43fb923fb27777474dafcc91dd9119cad8396c2527a04fe274b01c953fc48ce1b06219c44af40e1d710aa539180087ab63126830e81daf05c7778763a1465c6e4cf7a34a5064922a50287d99facee6ec886656ca8b214fa87ce7e107fe6340a3a057c9acf38cf5e6ce183a7225690258929621e2002081b2425391923089f1da0347194056d09299e62375bd0e0450d7810c41065c44eea54031e9e9fd97dc704536a90826796fd654171cda7536a5fd494b828c23086426213a48128e070cf0dbcac076c31cf27c69134b6913e313a823410044110044154cd6e514385c4665d7a982c278cd3338ab625714f70081ca20184a2a15047edaab6568ba235cc6bd751c3b618db7a595e55360baa061e358c4675a85747a1923c1085e480aef4fb731bf24023366fed3112dbbcca79c7384a6e442438dbca6d48e82948fb0ad2c023f008a48147e01178041ea16a2fd83ade8456a84c29a51d94e487e64890c8015b6c136a57f52797fab9b14865a8673033d39bba79d003b6d836446faa6f496a0ca1c64039a99a6638176b42936b4293bb09b1c9358466827a26e88a3582c411e54dc699a03bd343aace04d5ae3340ed7299e9817a57c5724bdb9a50d7767a295aa3872641de933f678090109a0102955415667a386780c014a882d8645509923cfbe7cdfca09cf8b50df118f4b5216167aa0aa0125009e9bd59bb59fb801a73295541aa12a892a89a9e7de667e6069410ea04a8252827d5912b93c9680820974f5e24b02d8edbbabb9bdb6c77b7b5d65a6fdbdddd5b3a8917d0dd6c49636666666666de6c3333777777495ab9373f785ebf006156ddc6966b529dfbd55d54a772486faa73a55c0c359d920593af4e2bad51ae35b522213a4e8e9bdd4c5718e9544b2e9d4265cb532a589e4eb981122e9e4ed9e2e85dc62d889e4ed94228cad32937507aa631d320880ba6cd6e6c14c42434139b206125ef19b1ac0d9bed9b7b54383515a56ea646b5ccd99fe2c948deec1fcf5e6d949fcb484cfbe1569b02bd691c276ef54aba3893433615d355f9aaab418fe46b189e5c6be521f53350871ef44a1a61fa0cd0da83e5e93373d14f9fd98ba7e82bb2555564abfaca7d62436c36934ab61ca0f295f750f5c4b8cab7a31268b80c855f78b8ddce4330dece44ec845fd7db99dad5cefa1559b4c91e76c53d59c69da919be967589cdae9155a95261b37d169695c9534c3d562e5365c81e195f7909605c7e01e2d619e875d6e50dbb9976e694ddea04c9b32a7d3b8f3bbed9a398c651d86c7721b9a7580aa1b5ee236b5d4d71abafb2b8d5575ddceaab226ee5380c9730ad2ccf5bfeb0ec65881715dc23fef32bfe2b2b906ac93debcf77653247cda93f9b8ed60e9bedf55b3bea730276180524345bc2911a6f48e710eeadb38cc678b4abbd5b5cc9426c090526242689f488f0e43eedf866dfecaa92c64d546ec91e86a9bc9d888aa4ec0a372319cc9c58c63206fa0103ff7c2d601993519d757b6bc96a0b0d4663ed5c63166d6cb94ce397eadb1989ea80df1e32cd5a189d8113925263e1179d828359338d97f84af7d3b75b7e750b48b7738d9798c6482d6b589dc956a8f1b58c32e445666211d625f74aad9603a3a7e510fe0eb0fe242293c97e080b8590da9cedfae252af074a19c8a4ae2601a84968122ed5d99e47c824b5f1e1b6330bcd3d58af8b788a380f818018764316c4affa748a2d646f9d9e0ce52d3b79cb333ef2ba8d870f7e9dcd82f516b82e2ca5526b13d5d9dc6e3c9c371093b727bcf58d748571b9a0bea1b3f7349086b1f5d17bda413f7388346c2c7bd8a688eb2c0c519d25145691a8cb7a116f2b126f2912756d6c9f9afc51bb9888818090745018e3405dd66b358af47da914456a261c300ef4268a256f8348f9f0d6cfcf02d11beb30532e1814563f8ea4d46e5ce9e64202bdb1fec53d4978eb8c03a53ab42b4fefbba9ad9ce9d92825a43627fdfa072034d73909e8eeeeeee65ea70cb5dd325dfd25746b758971ab6d39b9a96fb721157338f498e7ecd511973d87a3bd5ae25667176ad86c56f5b07a51bc50957471509ddab09e76b1ecca1a36ab75d17b50dece436e9d8748221e3f5df614f568b7ceead26bd7d65e4474ebdd0e76d0d9c112eca16591d1dbbbd2d9acd75dfcf44c562a75bd08ca5329bf39f7deebb734e2a35d658e79737e0797c6dafae61cd430b18e3c3c7e9a7e730b5463d66d59848858026997e84040ed12a3d57504da6d59a4a8878b5b2f82f2b61e015bc4c5adb5a5cc96468a54a2766dde3eba5856a1d859817e1b4b9307505d74eb6715fa422ceb513d6ad7b6f98b71c4e3c706d1addb307a8b6e3d02a33707356c2c2b52bb841a26963cd43097b22eb56bf3a0df1ce68acbf9e6b569eb6ad36f5dd7f57ce81df97ddef93ce7213984ffeb389e8146782a2a2efbea887b7a9c9bfce2dcc5a35424771a245b3b3a48e89eb7d3a7a30b4140df9c2b5d2c83781e7ab78774b441a887e48ecec1d68ecebb7e47da901b39844ffb38e80df51a720711521b92aabeba539df02b0a5f5d5f5f3f5cfa02844b3de46a83c91340937a26042b876dae535009faeaa20f1dd56127bbb8d477f825745fb65cea48869e43f1b8a8be7e185a1cae8e9affdc8774ce2d137ed570ce2d5137b34642ed6a9809ce99ceb14af0d3fc9c3f9be9f39605b9962d57e0ffbc9918566733994c360316ce5b4b3f716e65f687bbcfb9bd62cb736e83f8b53de75688615b8c033f3ee217c79d15cb73fe3a1be991a07c2c4852622a9c27a280c4e33ef0f3d1c1f2fc56f8dc9bf1afdb7e66b433dd4c49d9e4dcbb34425a54cf7937519d7ecf1359f4d004929ddac57909a48bec29ea31829b658f0d1fa1e49e119c76c358ac1a6403b1c9b90db283dac5f908244d79f2781a2fdb9bd5350d03dd1b66e3a0109cef9c2cfb874d2eecc83ec226e73846b7e3808e903dd4ae7635d3c72acfebe5c902bd74cec3ae24dddd4ffbee241904f4b2ebc052756d988286be1ab3b0670d6b2476712e829f1e8dd9ac61f5a99b81b3cfebd391b2913eef1cf46e6a1bef69b953cea6ec6979d9c4e53466e336ea13cbd8ec5ca40ff207d902fd86d31809640b2c5b0eb071123c0236fc4659e486db7012ca22453d6eb80d2fd253d4830497dd288d987ec349287b8a7ad8b88db2a7e8465924a75d9cdf709b92d52ece632830dd6e7ad9e17e1ee367338dc032ddc66df8697e6153726ea364418c1be557c4f28f2c491a5208ce7f8e33c6fd9cfed93f23bc93f4c920211b248d31147e45e1d78c73ce48fcf2dc9d73aeb112d50199d63f0cf3629cf7117e513e7a3ea23a0aa00fd2074936a2379cefb00e3ae72109d6380de9347e5ab2860c621df490244b5bba0f613306e7fc26886bc36ff84cc94c98899d9ef3d113a1ac519eded328af052dc833266293731b654f510f1b96c26edcb8e124b80d079dd2d7e7a0d7910754bd259ba9996e92dc1825ee599fea13d551397723747b030aa0bf63c641d21ea1379cb793536371ce5b4b6b08bffd569019ff3cb446b73e8daf9a7cc6bf7287c11a1fcb8fb43c32c073fb05f9661c245b35344e4706783e5353b6538b6c1ab2b16ce1d7d9432fe3d7d9b319b2b5b0c979335920fb638f501dfaab95878dd4b0fac42ece3da486f111bb38ff1ce9395751719d39ca475f795e18a4cb79cf8e869a88ebb88355b6787437b1c93114366b97692f39dc7619b7b141471e3f3614295200e5dc862245607936b151da701b364e6395a85ddc0d526664042f5281dac5b90d27c16ff85989482869737ea3ac4847a6238d50167d7ec36dfca44f5fdc286dca2ad4aee69c9d9e989a9c95f873ce43fc62c22fafac4e6c12b1ca8ae5c5396723aab36d621d75a43ca4a3b564fb0f947f248cb796215c7da765848e984ed59fcdb402c39043b66fb9bc902914d972f1fa2e013072e2d7e79c57272f4f2244be72487feb73fa4c2b5b9fdbd657f2114361937301f01043030d091fe0e0890461d01b60f144182c38030752a8542a6c72ee21d9da71c2f05f59b1d0b12e7d7ed6a5e7be9291d8e49c3b6a17e73b1c002d7c5499ea15a6e7bc9d38ef27ce9b89f36e82f97c68b00933842d1eed2c7bcf7a8b964d36afb1160f6b6bacdb6bac4b56bbaa77ceb55b4a8b5293c85a63b3b6ab76248d714b94b64375ea0c9f84e543aa5217f5ef4ba5e80db5248ecc0c1292315b26c25613b03462976245ec3e5fcbd68e209e531f9fa71f843fffca20adaefced3fffca0054f1381ee9362c1e5ceae213b7d54e5597c91a36ab3b4ef59cea6ed2ba8acc5b21a5abe30eea8129f7481c6ca6b075dbe6512daf7ea6536e0045e8d937af5b7a7ea5c0a68f1888fc00d2755d1713e59e39393f1ad6436d504315403123621037688ac1f4b498881981598ac194459dff00427550cf2e4487e82ece9129f7c8b39ed585143a679df7b91fedc2c1660a526ee7278ece51a5cb4b99c2cb967be68c3a52de2d1d5d47b674b447cf38e33def0ef09eef70d7f97cce76cb46952ee5c9fa416f38357ae8dc5949914dd0457208c9aa11e2e2e097e7ec383cde904d84dad2e1f9e69e6f658b73af6c39fd189a9d48b3ff00e20d855b043d3b4ed1ed5e60170f865a0b2daf28cf6cf47c5469b6eb534ac6af93369d140b0c31f0eb2775c46b588db3dfa786d198cf09b24801c58a124c641102d582c5097a800311586e880286283df5072994a400c57a8cb8a424012236e34604e0c48a306234ce3d2d319062465a3c88f5d094407800ec33a5101e003bbbe7f1dc4beaf07cf4ce1b5dd81c0636f9522c41b59b3d77a764d7e9092df7895f748a164e9efd6e614f054f68b05d791283aefb38daf8825b1bd52e4eadb5d68ed613bbb71a726047d6cf63791fab03adb52ccb514a291547b15d588ca21d67adb5a5adb7e3ba6d7777776b8bcca6b6eb82fd5afaa0375c370e65b9ce65c987677e29910bef136dfb58f2c1e4c37ee197aa94c9090cc5facd78ae5f22c0532729cc870fda5bdbadad387aa34b67b7b65bbba038bbb5dd7a4ba57ca4522f3935364f5dac4b3e2ce50da77170967c6c4bbdb4f4928261cf4f93e23c5dba386eb78649a958a0b5f6937c3af3a9afa4b0eb951080a73a4faf8f259c5f1a57d85c36f0e15df796522bea3181a48f762df9b09732f960d3fae0175da55ce8920faa4365344358ff90551ba8f4863da7edb66d9b8dfd21fc944d4faca4cd875f1886a228565288cd8b95448d3aaa4c86baf7de94981a5bb4e4615f5a2bf4d7ca952bc96f3b847949c1801f0c8cd771301b8ced17ce8cb067eac6e25aabb76d9b4c5bd6c6a94e7801db1c36c72bcccd6e9bca83e9b6176f0bd2f47a9dba60b96d1bca5a4ae9b6f150a7edd23d8e620dbbc1223cd715b776ab174e30c2c66937a537368ab03686cab0575d57d898d128a2be58ce82638b07e8f443bfed9cb5561c2deab625c317d192e14adc624819d656555798081d79cb9539b21fc2dfce664943d334b7e6c565bc4143a41cf85fc7d510e1067519d60b2dab0dd146a4fce2d708247881e587f0d35ac20be1eadcb8df9a3c6b73a5bcb970a4109be73c2c61e4e1bce438ce398ebc7143424dcb14e94829256500801896d0b40cd22d9aeb71268874e438196ec21b19babf4fc7dab23866bbe809332c191a991d8c170cb9cefbac88724959dea80dcb4a6fac8365f53cbbb1c76d22786e548aacd4b5a53490faf6e1e158cc65b496bb04991b6205937369c3dce33ad2f3cd7696b3bed9b256254ecdcd63d1adb78eab3720420ad3b830c336f350baae5623276aedba4a44134f8ebaaed6256a2d2bbdd9bcd625b52eb1a2eb6a35aab58b5abbae12516bd755225c407f1359d0b0d045e63bf7bcedc7390d1b61a85107971f37b5edf295dfe7dfe802cdc17bdc959439925273dbb62d44d5b73db64b9263216a246b0ce5d296698fcd20189294ba2edf6f0699f3f1b863b282f117954a9c42e5516adad96577b6bcb1fff00fe2154b48d9c4699a36759c37af95c8e3a4a1ca342ecc34a5d65a6bc59b256ee5626e8c543c905840ba51e256f126895b9dde2071eb883a6214459d7a53610276287d0173e4bc502fbe31caa881ac809217ab98302a402d40619bf356d61fe6c1b3b36984477564a83829b87020432dfddade2e166b46a45487368cda20a2116f66b7d2b8d42151086137154f9678caa6581b7a9222ba42f7696d080805f7696d68863b069309ee49af3cf5a51cae1053d0d5d39a104cd09a104465b40f5338859c1c55cbe7f46323c57a3939f83544b7a5268410429ca08a2ab1c67b4f6b4294e04f1b32dc3d5b8b42ac826932990c491697235412125427494a33380b31c3d7ea7cc450ead0c9485fdd3b798889be8a3f8ea3cd8f3852f831c78fe3383a5df2a3dbd1bd1358f9d12f931f47f7247e745389d38f8e33fbd1735090e547df51e2c71468f9d1c59167f4173d1c79c450290cc3d06f171fd68250e2c35a92251f86b26f067faafeb3dfe77704ff09495103428ab7ba33ba9ae76ccd6f4eafe0547fd6c4c08437c053a58ed22a74fd79fd598fd0a09b044c7cb77b351268f11d5482efa02bbeddece1876fc7e92106df9ed383956f67c187137c3b0f1fb4f8761f3f72f8ae95a08aef762238c8905401e4c3b75713207dd77ed8812abdf5192b6a0ca47b59de124e15f2cbf28c5cd0c9ac3c716409932c3f48fd1ce9800332baa0a20b338a965ca800469eae2a251e7ee001cb0e3278f6008c3cccba3f580031c6134e3440611f22662e289a010b8030fb418c2dacf2a7c413339899198591872d68ade0c2e54f1a050712dc0007306a388c51cb818a226a3a94513bb254dbc1e8ade38c3cf64913ab13469e56e900293182203c3b00461e66c93c01c00d5ba68c608ab77e7365ae68f1d63a8e11a86e98c18491c77aac1b54353620b121076fbd84ebfd245105e81a79ac559d10c58f12cfcefac9410d613cbb39f27091aa56c313128eb08019e1aaf0a768060d4f9efdc67dd204a6be6c2c010cf0ec1e0d32193cbb0d960f0f5e4c01d262c6165c24f149e2075282aca0a1a665885863812204af96234f579ea188873d2154a2ec08156ab22fdeba1fa9ce7081096fdd3bc3ca5b17e176a14232430666f4e0add760cd00c65b771a6619752693c974c00226a50039a2d024aa24842d4628638632b2d8c2c3962bde3a4ece107c78eb3b2cf0a81979ecd296124e1c4f62d408020e41a822041ddee2c868b139feac01820e4940c8e2a9056f9dc63d6005c82888329ea494e153021d6a2801132c9e90328498adf44bc205b32f0522ee793956fd54d590a1f47a76ef07393c3beb074ccf3e03650990eab0aaaa4646cb1664186989f2ec323ee321668ca767580e3c93c9642588d59ab7366fa9fc9983662d156bb56c01005b916059c205163390bc75df8195b76e16bd751c1ed4e0ade7f4c088b7be73c3db294e55de3a0f1f5996bcf51f5982f0d68128bdf518a6f8a00753c690e2ad57a01a2454059617aad3558c1d88b1e419864318b51412360c200860593750e9fc59d3d4821a802103838bb74e6d30c5104f3650d1420556387172c5e8e27faabc294d24b8e2d947561247bc885174d0c3b3873d8017e98b289efdebc19329d78ad613bd8bf4c50ccfde39ddd3aa52c8c10eb41c50c16359d656b3b518118d66c3ae8d2272586b8353e5c5132cae90e0d9ef152bcf6e594c78b1ea1a18ecc40ba65891c133ffce95140d9d8a5c1fbdbd3a05755f1fbd48e8a21309392074d123707d246f2889bd903d1589110d50c48c30162362455c4515494faba062e9cf7ae5db29154faba02287af57f8057ede6ddd5627afc7c813b260a98ead3508dfe7f6f2e2a997b2889d56a7135cf6d31b15d0e2e1c293d330145005eaa3f61678784e9e93c762dd7b549d9c4717b81932c07a5b0601dd7a18d6f2c431436b6d8be36ec8bd743a58cf796841bffcb20e5eb2150b49514c9545421ffd863e7a91ebe28ecd6d792f098220ebb9f204f25d298407f8912db0d93dd875d4dd971c1d45e6380155a7fa3492a628c4759bb7107d0afda4594a2130beb95a9daa579de80d5215aab6d26a53b3a7368c783e1e5f2c2e4f6b56908efe146b569cb015253f3ead5939f2d5bd960efe13041f025a8384b6506b6d535a9424252a5e91524a696530865c468ad33017ef8b83f3629a2977d4bda10b49632d27b7728f912a0514264c1073f19e4e81163123345e1073298b3c3f592498e0a6fc76946f47fde9270f9ce756e72fee134b91a3773edeeb3eb18d895faa176f90e338ae565a5fc6171f9db6eb73191ffd85bda8f355d83519798ce5e6d4aefea1edd259c1757194bf788c17f580f1b1e580d15fbcc7e82f654f51e73d234e8fd163ca9edbaef67163baa39f4e632f7ee2bc8b8f650450eee228d1bd933b22c1e0721082230eb1c9e49b6e2deba1e68ebe7d0383cba1d22cdf3408dfdc0cdf5c9e97c36188930be2db69e57ce85437a5481abb51727b8a7a8cfee23d30cef21897298bc8f88b8f65cfe829b7b1e5b2c814b9d3ae76197263da9aa88ef8ed1b167eb16f47db518d298b4e80c17541951dd4aef695abfca4b42f56a5aafc416b458ac08c3e8e63291b4b2345aaecc553650ced6aa697b2d67800ed29ea940a0a6a971875e6291fcb3a6474abd75265113bca475aa5b5abbdd2682c64b209cae938818b2a6545d68d14b1f3cc9f2ee3249e4e7901ed9b49b6d44b50987cfb76a537a65e626696f16b631d7143a7e77d8f472e694913c53db7a37353f22d54048771694e96bd3071cfed684b10f6cf2ddd619be4835b9376501bfce7211d77701076eb21775cd9dac1f9e60ae03c509f73cf37f7f91dfe9c23bfba85cdfeca209c77de1e1af1bc3248b99141d8bbb3721fe7ecde67d93b67ee9cd9634e62b359263b9271953d6b416116892a60d02cf4a997962c1214669f1c2c7be92b7be97ae549c1f8b39768953f7b89b7a3ef8d69cbe27ba9c69aa9bd9bda5b4e2ebf7cd11b93e7ddcb1dd19bb64ff689ea6c55821004960f52619cf66963ea247db29696584bdfee42d2f336a94b3053dcb36ef96e1f19c0eee3e4ad0e765b562d6cd61f10d5f024d629d022d67272c53fdd89cdf68d6973da9e36a6ad8957430110d041c626fddb37d49af6a9ea22450e324a80f1d63d277200459578559d7515d158a5258b13903183135940e1a307258868ca811858c8a8aced8813326c14d7dab08a7bdab06036564443135d84a24abc2a7b733881e299e99ea2e9a4c95b2f892ba3a812afca758419154422a4364d5b7c7a19a8f554e6639eae9ea6609ceabc38f5737ceaa7cb53d4533fefc394ce238f3b5992366ce80dfb48de188104d3550269020ef28604c009d7bdc5b0d4f2fb2481c48f0bca70e28c2fb4c0000d89891cb494c08825aed71f94e726cf516cf11cf3ccccbcb1056d786b398c6f29be9ddcd37ead7123849ead78662f503c5cd824dd9cf20bbc9f589e6149d904cbae9e53aeacbed396745208cd895b53aae1a00b4316f7c6883726b4f7eacca81c81620c1baee8205605892d78f0c5145758c9410c756d16f65e1ab7f67aed92611515d0eda798be9d7a4865401034f22b99faabd263939d45f237a9e13b0c82200882e00ccc0813c157dfce476d74b244289928e6c6c494675d1111b11a9401794027a01d529f27c74787f1d1673c04c9d3c2385d952ceb8957c53a6b1e7410c657247dd638cad0fde83df2c080650c99c258b2667c875b98d949400d5208bbda6748206c1a1d39999595888d98c8c908765aa04daa90438719dd7e0f7eb5ab6456342bd68a895631de420cd923c6513c62ca1e4cd4b0bac5a3a1445b9868c6db790bbf447f892c96c7c4c8ac6456327eb2623c6685181f6378665e6688b6b093b312b111c3e8184970c14316355061c595582b61573b0f8c6ec003133a20f14513b194cbb45c86b55aadca5346094d19c36a577bcc503b2be1578ce7c4d0a00cf311e30de41211ad2a5183de60790a81f11e796462627c1543d25fb1e8cbb0566510180fbdc715603c743ac6c4f80ec3c4b88f186f071213e334966a4ada6a8a719ab229c667ca1867d1a61897a14d31258cafca2695e72f50beb1d02430244f215e796b00dd93f57dd914995acc02c2b34849968e2a6badb5d68ea0caf6c9b2b79e0f9583aef2d1da18079d635cb49e78552c18b71e43d217552b95c334a1015a558c0a54f90ed3a8298dd4788d873535a5acc8c2c09442fa552fa85a761cc7d668ad0eec4be7759ef779ca4150f45e543031302b18186bad6f3666a58a09436f57a954d64787294feb312e866e63c41b136ab18db2de8c8b8be55832312b154c790a49a9ac751807411004c1158c6a05530649f9e7765c2165cb9d94b5768663c9c4ac54b64c39cc4be737dc72562843b60f4bef234f219df53ace6edb5963759083c62d0dd9e24186554fa036a7ec52b3f56c427e8be137a577f1d5eab7ae3740dcfa3fd8e495bbd8189972f48d3cdd7ae255b1ce9a576d64e8323210d1e1fdca77384645b62240c39bd4f09127e45f39499f8a21696bd0288dd0701a1ed220c33af6a051ca8a3695ab4a21fd2b1abaa8c41a3207e73cf5a05470ab8c089bb5a9cd1dd8f856c278e881fe327a7df1884d282ddaf0eb29f56a5359c3a68d94bbb9dbb0d9d4b08a445dedde8d29cfbb2a4fd6ab8890f116fd257a6fe44a4537777155796e2ee3755c618b2145df4899d1c7948cf834be9441c2d03d6bd36f4e550bc3689624aa98f283122690c24aacc16842c809136568610519b14b637a3d2272596ee23c8976b323cf6adb5c455f15a3f2558caa0cf2e2a0db718597addc61733bcd8625805d9bb7e7d098db4ccb94a7c362cad35ce56ce58bab72beff85f43c458a7e49289ee7a0e8593673b0b0d9ed350cab311b22018872ab536d62b35f84ae144700e17372f04b14bfe5dbb60520c9653f6d6a88c5aa2a613dc3011c1b3db42618ddea4475da366cf45ad24bbb65546a1de3a9d72c54858aa4b563f3eab50cb2c35ca33a9df306aaa555cfbb9586a4aac04ace8a8474e121ae551538a99ea0e4995d9aa07c23e2a18631126b3b7a1ea2f2a7e51a928d62694955056bbba467c7c121465bf0f90eb07e08eb63f48cefd29633af869a164d352b3f4f6b55b4d4aaece0c7a7b52a4c5f1b30bb42bb11529b7386f6ede116a3ae9a314cdc84986c4448b6289b8cc8055b101351173522267f6e46dfdbd146b3955144fc3a3b941327eea99aa96665c855d514442bfd5c4e236fcf0ebc2d5547aca66f20d58cea54d54c3563d88c4c75a48aa2aaa9a84021535d51cd54482a25d5928a49d584220a4125334824f9f6139cf22111a96643ed5535db92706de272de9533337ad3a48a2355462c267ae3a2833b93c2e4a203171d501df60e0a32ba28a086a188dad5ce62dd8b02fa0fe99b55258aef19110b45d4309726ea6a77faeea7a36f94ecdb754002e1a2836fa724d8027ad3bea188b60d45c4b06d083543414121a194f8456b52fc7ca3a87c5b26285044df2e3528921a8aa03f5149dfa81a6a09652b38e3d7a99a1df1eb045b001af1eb73e2bb0970f65d49ae600b3aaa33437ba98173259a60d92a0da09872058b01d4568f1e14a200a3c6de4255a17beaaaee79f764251955f1da05848b0a6a9264fbc9933f737cb52d6b85b4c0b094acc70f222bd8f2d56c2268121130f8935e2182883fa9d3d7994ab7d8ae51dd865fa8af8ea3aee0e82b114cc4efe873c19f348a93db0a5c8daa02970158726b5087d4011a48ab81b40fd4b050aa6181f4b486c511162c78ee690d0b228f0161e7b4c2282d5126eac4b1d09b550165a355d103d4d094135441058609d18c7604c912f7e49a984b4e89cdae327d07c65b36619e07bda1955d4e9f2b36b515085da9352155076cb14e688bd960e29e5c1387c46607abb1ce392dfcaa9f147275ef2b735170384da9194541aa5da9512a52526de90ac73511f9d0366c134bcea95d1dd7d4aece89c0b8c039932748034370c4e47981eaa8a3f93917fab95ae6bad56870f4d55989ab759d10c33ca6aeeb3a2647baaeebbaaeabcc3df1ebe4ae7c57030aeab8a72348be73ee0a09811d866adde484c431e1d7598538227e79e75c10f6d951c342efbeb22209c90ad4aece2dc991447470c90d953d6c769d900d17dc931baa4243dff9c93d7de7dc13bf780bbf6af73384480f9b1c735c5dc96a3410e22a532d15c910223d5556812a123af298c0c46dade0bd3763b3fbbce923392c6cba66b522d83fb26ee416f31a56c4c54797b293955e8fd0c51ac93db50bf440f0db86a648f1047e1e37e39ab827aee6919c87e4e6dc8c6b7aea645cad878f177e0089a1f38d64b5abf32645d773e47333cfa59eabd4f38df41cc7537ae395a710d0678771b0d9792569d879e75c8be36a3aaf434eb6efbc46e117ad3149face69e7674dfa6e057f8add1432e8a610fab342f9aeeb2a0bf1eb1487f8755e227e75278e4e49f731d1092df167e7e43ba33f3ba3ef70b01a66bd736fd8e65dd7754c365a6a965adbd96062b61da5b44675680f9b9cec398e13e221aac3cfb94763a4eebcd57d4356398eebb8e69af3b6dd13bf9c38af5a38fe79aeebba6e46753aaf3527cf11c015c6c56959079dc646073d02a383a21709bd2b232016015d2c693be83beca3fbc4b66ef41ea28f622982e4b531823a574b6f5795bb397b751ca08a0385d5d913ce73b424cfd5dac373d4f57d2cc424780e87b213bde17c36a33a9c84de208104cb88d410cff9f971f486f35aa33a35f8be548a52ee86a66a96452994524a3d6badb5d65a6b6570f9d5a0e739dd51cf38c32bbbcda3e6e622f588547b7f7ce02d09a1436287ae073515aca0633dada96087079fd6545083d1c7cc520c51061884db7a5a1b82e98821a0c872534f6b432c61c510446014432ca90d71c40460eda9cd18e206ef89fb0da10302196096cb3dad2de901d79654e161c3c9c14921670a120f7169a48c71c726b61f28c13d71bda7b525478024b8ddd3da12262ccf528f06f77a1eab65c5053ab08668ac340992799af1707e88f1ae27de29b624fdb0ae151b7a5811cd642e1941aa284131587080a142b6c2010f2f34151640a9284d30b41f2eede9e50813a0a2704a5541010f2e50c86e151d4621145396204404d0825c70bc2e7c0871727052c8691a3d1b1c01a310a1d8e18b0151c8d484270512c8644450370325eca24cd8e5cdc009b39892f470c4a4030f485d9c7104a80b1fec80001accbac8c10e084002ea820b200448699ad2456d07042411b58b7a24ccc0f33ada9fc7ac05ca0e7fe268b93463744d9c686272e20a6f06276a00323921e506276290c40925827022054ee480e44261e23c15dcf1e9942686108326b0d026bad8614440134e5e1356ec2042016a9797a489273e1634b1821e9af0a1891f1ebc256abd8327653cf9810d1cf5ac87002768de0faec7838e76bd2fc27872c50c2ef774ca9329df1301a6c4a8246c56964dcd3a552310000000d314002028100a87c482b150240c14495b7b14800d87a44a6a5298caa32889510a196380318000000400cc008cccd0360097f32720e36e7a6172fdfd6f24e2fe10e76aa9f7c1b6a5611c7cded1797201744c2cd6bed49592bb54bbe5bf706fea3d1a41281260ce527ac0109b5ed61822337625082c462907eed847cb30f308b2e3df8b92a0d129b4fc7b8005052fa4226dce556375ae6516cccd400e6f94691131cebd80dbbe814d54b3de953644e61e7a9d80598c26b904ebffea588500478978cae7f177c59f5ecef27b5772750672045fb7dd3f47d52bbffd1aa57ea080a0f64fd6837f26b61c83209d54436e960048664b6128a6d53a4f9d3619b2e62e35b298c520d978335dce1710077b49680cd11e614517dfad336ffa4cebc73e5aa0f58d6ed39fb13e272dd2c7c102048410cab19495f4843a81ffe6871386c61d4891fb185c850d89c05ec0f60223e2b0007003ca7e49ab443207e016aec621c31818076e8088f54a93d29e082f34541b1d0bda2bffb4d000733ee353ead73a5c6abcd6101504ab31ec81096dba2686ab7dc24ce6f44d46fd1435fccc4e8d48226153a309c5866d856b205a90c873cd6e9f25d18660c7098d211dcd3e7cef9749d0272f5e609554cee8bb21a264f4489d995c8b91d5739deabbfe16319d6d804da3e40fdc031efc63ed0826db742e6698828c58eb681e2d878e820c9e917280cacbe340e490a38bbd7a61fe24466c93783e11dafbed0d91323911ed57a719c32ac674dcf3c3800da83a40ac56c4331f7ba944a92ce70c16372b20c72c69a09d26e64c98ac01f844ce6430ce09c98d84d0fd034bc8958a50e19d1d0a41afd76f6a2a50d07fb48092163af35ae74b6852045f145c892c43d4d4fa760367c311691415050c4fa0f010cab5bee15ea74a489209a1f4b58c67ee90d54d2947c9ee42ff4fb4fe428642a65ad53da591181485b7a38357e86d6e336b03fbc0b114f1b002d3ba82c961cd26624d32c281511c6863d797fabfcbaa4dfbed5cb908321f07c0ce4aa20250638ef199b23d7f1d2c3e487b3bbea0e978f09bfedc1ed4418c9bc34cbe1981becce2f2f6ddf88f76d94869d79f1fae808ae801aa8cf3a48839264e21ad283f822a5eb32a2d96c4af00746ef3f719340b7a7659340bccfa9e85ab2fb9439ac830ea59c0123befa35edfb50fa09503bd2fe8cd0ef04667963512d66fa9d6e3a7477501c48dd945532f84dd4414d70cfa3b29974c05cec37a15cbe08ab48cdee7e80bb896dcaaf1da44ec2cf60337ca6cd540f1ca8481ff63aaedbad5d45d4519ca409e775e609b7d3a022e14c5e88cca8719b8743b29d4babd3e62e47faae9b86c33b424cc47c126681d929e1afa7198143e32c8dabc9504c38a3e9da3c51184ec80211de7b27b122cc28bb29701f266e335756c02ac32043b541b73a005e01cb4978e14e7b919d4804213a156239aae558ef4f1480c2ed8bf2af8444812c369777f660239274ce5bc2904bae95bec6e3f79ff806f6e26defca84d6a563490e4d370082f14a7ef75daff388d6626aa12ca9e937d1567b8d2012386c330702b05adf051a02ca2655a25beeb9d02279cb136f7ebfd3ac7d26a36b147f6a367ca5814d02b59820a87be55aa91a0989b9f241eeb31881215d645898930c6ae8200a5b57de27a99e309561169ea7d72be4a5002d5a74360610cb38826b711cc8aaa0bb0eff10ee75871f5dfe0ca57c0e9aef0da74b1c3e63de12af7f25b5c14de136969167b1a56395cbd22bc703f7cae71034344d6b69f4e567e92bafa08a4c308c570df80af584201819ca652bc3c8fd813bf94d4d5545ddb854a5ff94fc4978916131ee9ff44061a04ca923750fe52d53f9409f1df7bf44154aecefc8937049e736d95826f4d21f7ce92b8280e46d9e2eb1c7b8e7e62da55cbc00da4ffc99b438fffd03b21575972e0637ef8b6814669a1768f00356c54ca794f35144f0d00945a75a9b5f990d0b4011d08c6129c2ccb8da5e5d6bcc87139db86c979f4989f3bdf1a88db028065287404995e86646a187d4840972044047c084466ea09dcdf0ff0ed021bfc559caa1aa9a742b788cab643925b9d84bc0a1815e40362216fa26f98b80b725b83de442f45f55de27ab67e51bfe51eea806a014454e092c0634d253b06b8b5a4b8eab82bcb366671e107af79517cb0599a9d22cb3c116260801a3f77ba182f1d52a038675807b1dacda1b93698f21673d8519daa0fac0d7a2ad220dc4709911cdd884129838e8a72605a19e2641141b4ee780dd5c6d41bb4526a359003bd6a328e402ec2ec7bebec1036afd8233571a29fb024a2aa06a41309d64e0468641acfc7cf4f26238923c5a3c5bccdd1bc942aef0d771ed89c360021dde9bf9646b0bbae635713a79ae6aa2d2dcf004628c71de9cd29317b42d2a7062f369f2f78a110476c28a792213b4f52344d30efb6b811272cf1045ffd954d353f801f18bc69d74a313ead3bb4164a0b026e5ac24b90dbdeeffa3efd9c713ed7619354c75a92544287f2f17ec3f742fd9d21adcde9b2958256feba321ef284a55a8670daf76bb5f0e428de5dd86454f95d7906128c605944dcfdec3bf7df2689e39c5eb527705cbbdca88aaaa0c38902244c3d0f279a1d617a4e46071bee1c2db46dd355a2815617bfaa249907d0938c883832797b2b166b127792ff8558785c5b5dc2e11b7e5ef096cf53ca332ff41285112bb5542c5110f6e9cfafd058f3768eb49cd2adaa48af00ce94a6e60e83a825610469e7d32c700020819273be9e4bb67c1ff6f10fe74df895c5cac317b633b1fa7db34c89ca20dfb94d896b4b67c8c8c50c1409b6de95d461632c017b0883b628c2bf7060f5fe1db9585332be19ddda8e6e2bd982a6d8d3805bdb588ab7a8579f8160dc97550237ab5e6290d2ba669a2c8533500beac93473fd69f014b07850db3ba28f60a60d633e836a980f1e3e4620ed0744217c45955877591d74c7d01774b463ebd376185f428db325c6ab98e068a02a588d6c9dcdabf825c111e6c4e74bef201fa945c4e6121320f06edb2f9b89eaee32e2a84cfdb1adb2ae31ed174670863541c9abb3769d647b150673194a318ef9a98e1fb5ff3b722bc600aa9b0af1c9cd3ba11acfbe877e0e319828cc57c4df0c5e7a1244e6cf2a8abdf1b7403e1708d653f15c05aeabfdbc0054b150a3d8592fd28a71fd747dd54801a99865203b37def102a066fa054e6dfd7f27573ca5c69471e4e38d328a462c04adf9bc05eed4af96ee62831efb6d151f3131e1467228127229e53126b868edcdc1ac57b49be96cc15b89e38b6578087d5c6c8c2c683a5903b73c24ece7f7ff3c9eeb1bb42839ff61fc8c507e061fcaaf5d3e6733bb6fe1ccf1c617749b2566bb707466c4a0f193a62401cba6c53b955a9609cf849488ed7cceb97401b00d65e87ab5862f5af32079f458b256db9ca24d574c813948d5ee48a73e0cea24f26b848020d4c947944223acf797cd1cc88e864bc45fbb7c265e0a0e11435ac887bf7d37d77aeeb84a6507562cd89f8d8632f16318a380c6da6902ab903c662bc57259aef10294caf707e9d568d029fdc4beca267324f4790ef95744e005fb6efd62ff145df34dc5e0a78cd01a797a36f6e80398dd5be66481a6e49f0681cd43f526d183aa30690740355bc9356d2a17f0b2de0175090d8800957058a13cf027b8ff016bdc3df76ba19a64619e9c0388504d578f370f10afd8e8a2689db29f6525608d475c79982dcaffd96349bac8183c4d3ecb169023876e928594db08995ca6b5853729e3cbf7e13393852085d4a8ccf4b33f64ab51569b3184451316339dfcf60b1953bdb4cfc12c9ed8163e0a9da3d40751c07cf4ace5f3a19f6e60afb3127daa5b0dbfe64d24d2668756121f8191eed60a8c1b136f7f52e63aaea5edbcfab394126fce530cd87b5fa56fa42d35bffa3556bf91eaf041bbf38d7c5ad31153ba6ec2d92aca8bb781e9c94ff6d5b3b234aea5210d4d8225f4211741a0824adf574857c620d9c28a949d5e94cd79f92d72ddf455b2ae0630cccdf7630f7e9f4f350954323c3d2722d27487b807c0abbfb8ab6baee129b1585f9de68e88b9b808968f92b871d1e9237e3a8701ac7d8d5cdc695cf7352236763df4449cb9a39aa467559ffe1070269dde01d7252e771e821f7d8283e7d1ad332aa2f9abc29ae813a313c3c804ecf9ec0014a557194632148f4de2644458c3abd0e37c97c3b11ab2a2a3257ee30a71d0c0e555477aa08e78157acb2e9ef2ca309351e543fe06807db71abdcff0329c2245f749dce29901b582c7df6438f1c24fa66f524182ae96b8de7ef128fba55f1a71f41f3772dff136857254d07d0aa52385a8f84c3bb5655c47f79da1a0a3e1db208bafc3b97ca16d0fbf54d17e3d3555f96a5208191d83584a09f37c7a4a56bc15d1d5fa9305a50989d5d901c3d721e7f3cfcf24cb64102d31a318b1aadcc0436ffc98da10da32eb46474d8a2bb941f0adce0d7abde2ac8d138d422385352163621a49c9047d2299b1c69b3faa0bf61450e199a0d670741704041c9e2e8942746e2396135d0c2013865ca60aac6006839a9561ec12cea979a5a307811a51cd217e1f95f93729e1cb32cf2eabf1639cde6f58f8fe76ce372fb49fe62eb322faf5cf9e3fc8c84bff502d1faef9e228b9799671721d05e71e54d4db659455d5d58f24675631caedf8597a15a2e45bb559df6ec7a2e28b6d13738f4249883adbff6fafdc1c0e82c474f2b2347eac8e5bc13d85b0a46f18350fcdef6e96f1a66e30215b16c1b706073da62d4bcf6066f7982e3573f04ca0d76827a3cab2a874fa77ba0da88465b55e4dd9ddbb0e2c521ec60a18c7dbf4d8bc8d58c36807098fcc058dcf56f6439ccb70c26a5e33f1d84608e03239459753d9a5fd980c8dc6f6b47c4887dc50520509dc0acbaa603a3195be497a79b43a5e2a549e6c59ad26c9010e0e69f0ba5d9e6b0328cdc9b392850c28569ad8746f067c6f757a1baa038b08e2cdc2fa8c5ca4402677fc5c4a5c10d4e85175951a9bf7e94b5cbe9c5f446c12b6fa4066dec3ad340dba5b73d5db2f04e6312dd379b99277066be7262bc8f4d5889a09374d4d3c775d843ea8b3ae4012e620e72751737d10d72d46126c9a90becff6146a447d2cd3b72e46a289aaa81e9ed61ac3d60f4988ea157f97c8535102359daad76507051514c72e989a023d12c4f746d2881cf18485d7ef26e4243fa8d095a887f8e8c18522fc48256b6c2e0856526c8a03a63ebc6fae8f2e638c93aeae786755976b1348566e9273a4bad672c5d8d6b3230a2c244e0cb6864440fed3ab89f66d37187141bf4d9916816511f867728532199eefeb090a7f56a0604979da9eb6eb11f5eda00fd76919370caee86655d8752b1ee96f654d9449eb86f66ac50fa6a4b86721f35c86228c3d90a77ab4c619efcf011f053cebf757f28ea5eae1b7414eb17592ec018890d85ee2da6fd133b1dc4aa095b023673cbaf1ddf940d2df100272209a32c33b1cc21949a316da9e63154db7174533fdce79cb7aa141bfcdf016134161af240930ee5c1d98ad16995d38fd04aec670528be3179dc72a770074d794a8669fcecccf232b95576f7b9374581f8730c29f90698947f28686cb24c15c3619a8a9c1b30e4e9e9b2d31548841e8e5964afec7bffe05366290b2cd94bf1386bc06d85327eecfbe70754bc97e061e070e49a960355047f340f1ed090a29c137e520ce1642a5f1b94eea082deef5b491c8cc86f4b205500fc33a912f1ce873f82354274d8e69f37c560403cf0dcbbb3232995362fd418eaa25b1bc5915fcffe4a924939e182901418f52b0cee40165a1e97d7e64fa51329699ea5f75aeefb766425829cbb6f386b0a5978d26f5177474a949fb4be41c5c4e5b2776e220f5cc4d8b0ce23967aaf95d74bbc3e74abcc0b40bda78e7cf59c590532a0a38cacab2836c9362b9791679fbbb666549fe2c156687516eeef3c6c465316ec2eb76e206deac2c3c299d1568943d6ee9973cf234d93f6b4b5dc1743c869ea0c45dcad6657fd295864a773a1a8a09b9db61d2d04f9e7a09c5d579f1d1789bea4620d844c77f66f49c2536b89f247c204ad4fe4c90d4e49f436612d1c455538577e86b2743ec032ba5898aecc70bd8461e34c8caa6b43f2831879ee70fec55d674b1a4f58180cd73dc82db0a0fc16cdd85405515c0a1c0bcfb0c44eebed38b3fc8f1c657a6de16aa7258e58b670841494ed47a3b9c55e37964a0a6e02b41fe26ea38ae3877989a50ad83c44631c50fd3934272630da357aafc3c0775fa55e62c4f5652320ee9adb435881ce52b09dd1b7efc2311f73c3812c575070c26a6cb0dc8c93d806e90fa58d53d4274a408b19f3db6038b05c82683410eb0e8bf06c02050131e226c86fe89f93def15ebb4a3a4d6b27b968517560ab22a1915798d482bd485bf8dd0016dfb2cc0db54e45396ef81e7cc5fea341a83a609ce9cc36642220765b6bb34c111e8a153ce70e89b67576e029b1d8a1c422ad24031210f432505588a117cca017ca8aedd7ec7e596e557203369e2001489256c0f23a5702909fa3c1f0242203ffa8df1710ba426c857babc9d0b44def7aa64aa2b3ae8c8192c22c2ec8860e2367d3092b1d573c35dc9eff49dcbf81ba28edfff8e5753c342e76f054290b1285add577b9c31d9d1bac32b5153d26b85245bcb0ce39b2df9ea931c25240d3c45a1f740033291119d9d743fef667d0e364829503dea2a20be61164e2b5dc13332c480642d3cfebf2c6d4576cdf102fad3d194659874c6ba652297d038e6b56528150eb868d44a981adfc3bcdfc402e13eb623e1a8aab5c0f1c4a222cc25fcfa5beb2432f5d170aa7283f89c2ec26ab678cbac5a19a3e635506cdb89c176c5e242739ffa3adc30b04f2466b15e45457f812d789ec214557dad9ecb4c784bb879264e8c983308a53425c63c84b20e8411779fd37a717e38c925aeebf9139980b8fb25e9b03900945b00eb9ce8c484c732fe3fa8ba45df6c0a1e585c6af643c4f5ed2eb7c8fa87d102e30486476fb729cf7669b0c4840b70de13a74ef7a96ec26accc22d71fc37b26a5f0a8b38a902cd133a1a7dd2922077bc695897c387fc39f72a7477a75464f15a0e6ea264b8fcc14adbdb45d1d55b05459c55ebef11ce2692faa1c6bea487b474509d69bc61192e5743945e10247c40f5411b3c436d341d5f4a0399c721a9cc48c4eac8cb6d4301a1e6f554986b7d34d94045bbd01b8ee63049ca2e3867e079530179b68605a069f5399530f13160513b549f938d47c970a258f82aa5ec73678dfacfc2ca0ff2651229e2d2c4ba7e861d36d8cd45ee1d268902bf08ccdc8260c6d58022112a37caa6e8c3961404a077266fd4a09871e81407a2304c144c0dfd7634d90eb24856b644aebf2f5e2800f17cebfe3a5fbf5f6f36972319b409f59766b1ff9fddc7deca32e4b58652cd410f859c415b8686ac0caecb4fa0a1eccdab11dc92a3cb74669decf2591077a11487448a5f4a69c138329a430b9d85c03406257b7338849fd5623e0caf7bc4f3870ac3f4a80a7b20219fcba3ac3cf5809841ea4a8a617336d0739b99ab3a0c57dd07d8f5971bd722c69080cb39c2ae43c8ea2e00031e74e1d6a20a85fe941f2250bb13ae4b63eabfa9e5e6879ff96e2f99b44209715fd6d1293e88c4ddf7117aae4a0448e341761d8b810aef722f9b490571bf15347699efe2da466f8bde82d6bd7c20ca5f871e439847642145d5c275b91909fdd94f554ccb5d47d7887613c42e14871b8511f8c726ea9c3ee6cc88b1552535ff7fc0b6c068f33bb86ca15a185350f4269d178ed6796f0a330fd52f5922a35189df15407f828a99c05a7b2e47fadbf690d31ce8a97277050620884a52c8030b5300d1f507174bad5eb038e225f779834096ed0174f4dfe90786a1d57990069c8af54f8f22c240cdc8f8d7f06c89effd09db78861a91b13f78494272bd30bed0a809bd8177032102d0d8df69054e26e8fddb6902990974e673ea815041e8f6edf481922034f3e11481d219f4d6cf69074247d0ae1fc2266eab0074fadfa9033992410beec2feeab208a93c54aa95fdd707e4685cc87a9067e01d81241ba6138aacc0d73134d6479c98c84037a6d72e1080e1abb12a450be9123b624a6e0363faa6649bf10ef4dfac99248a4684f46d96cca50a47a15c5e449a44bc581c5b54e9c80aeed1cb2339b845fde8948c9289948c668482c2fecf04a5cd75279a2ee5fd2b0eebdd0385e16d23ff1ffb0b7a06c4b6b22a3c93cdce7db044eae9f716c1632cb9a322104ce625a38033422e4c7418051eb75b4c5c48d0e30ca7489d9088a1220a088f30191a50a12870a704e0982b4e893d82474f506c215f355840e913b2fea74260ac22ba4282a0be159b99fc3a3946c0ab8b230aec5c7a1c425890b3597e48b3055960fc645b7b85cff875cc175f663945852265e008cac12a8185dc845924b37928f0a7bb281d008107fb705bff21e32430c1cca91380fd74f76f437892bf045e1707a13657de12879d708f5cd4a9163ffb23a0d1e796107cc180a0d9120ce99c8531d50c246c00e379a32d453c8d3bf7df833e4613204193abb602b64bfdbde935049702d541d3dfae5924777b9818d8ce55229212ef57903e7db33ecc3c32b9566abb86b6504149428936bc7464c9ea87a79300f48d1efd0f74483d0609e65183a3072539156ec8dc483f426fe426873213fa60a9901b1d4826347d4b81dfe8403cf1020f45e428f9ec991d872da231c3210ee120e417e9c0a422a78336a168a3804aa6887cef5fbce1f4a07c0db294c2561ff6e00a2875593299414a8bcfbc08808de01ad2eb25985dc5d438ad1834a81549546a994c6cae82f1dcc5146064c8987dbdf4b31cd3a0472ed82a1d0eaff8f66f6316a5490d82ef69e5f7a0d11d049044ca0ba5a61d38fe36382b7e7393abf9448e3ede1a0fb66f26aab2f8d2844f8eda63cee7c2f173c0d98603c168e4b9fbfdd905c5189c0b1478c9a0916d82b348f2d87111e4030a28547e1976c7c95a349272f53c061c968bc7d051a58dccdd0f958b747852888d758158aef88cabec45f1fa316d7b91adcd0e01b68f20c0a7ecca04e420f2c56a8044b225b3ba464df7467f4113d50118f6425aeb993e911be8fe0caac35a872907104c1ab9a05326f43842bbf4e5603c92b2a9118e19f01e85da549d7f08a742e1a3d6ab61b8fcde5592af3802561fb36dfc40d0c02a777392318422f804028d604b75649359a35bfb6702000e2ca82839db31dd1a0a9043b6e6b76d1c8403d0be39af5ce5fe3e735a14b036aae20f43d4d53e5cd592f243af0686bf139df098c1532250a7ad260cd20ed0c851357e0ab99361980f8648f1a3707eff8f5b3a30f81c103f9011224e438c3e1fce7603fcd31eeda568959db6937f2766f2366ed40b2e3efa2087f043341e20cba458c8fec0c8703c442314aa81c576717413337e4675ae70d7a97648b5ea60dce81cde2aac32e3ee85550dfc46c97c36e328779566ffd55fd49c0e97692ef33a559f1d47fac5baf193f37aa72b4b84ea74cdd59fddb062a070a8bea0d5770e8d67c8c4a8aa765155ed4bbe55a496bede2e1d572e693312b725aa7e4716c0db71fb048de676e4db1f2986d3f0229e26eb744aa1f80c93312e6243a1921b70815087545ecfdb551ae98ff45f266fd81e47b746c67bfbf05c94dc5d1e63090885d48e3d77e2850c35f79bf0947e56da0945ce6292ad97271ff537a74da630e8e507f76fe2d43bf2a1485482ba2769d27f9f802b9ec4d94fc98f795fc06655857475a36ada9861b876b7924c469f5760abb5f802ffec96455c86c67f642d7f09042f2ef869d4096e07ab81d8b3f1a0fb2c17596eee0c10411f9a2018c86e6cb60ba31f4c7256680c86db8ad85be2773444910879fcacb9c4deb4f5d6b90d5644663c5781f8b76b52a466f93dbfcb3d4ad5a99ceb109578240fda013a17ef4547f12e28a51af9e8f06423e96c1e5c542b0ac3b3aff4bdb65b0f9e36d3c0edb4e585cc3cce838bb46f3711ae3f669b9dd01e0d102b443219d2e4f805477bbd7c9d3c911abfb4eeee002a3526a6a6c2f3c8e1996c91858cc553f3783c1274ba9f5c6c0b1f7e6efa1816db278cedc981932b2cc34aedda18656a3699e0dd8f06901fae1d52aad8e84524e8db022ef03486c7de043f88bbcaeca541dff2b9bdb6a4f5061740a5a2004724618f723fe6b7656850debc01b6867654344730a907e3dd7379f71db34c788be54f72b66a003be127c39585ddce695fe07e2c9606df97e0eac37be11a9b417e2b58d38df3ff0c9b6e30d4f7329554a3cf3ffcd9eaa6fce8ab2088d12d7ac8ee4360801d04897166f91c18230e58f2b87005d27a97d0a16c27681cfb3546ac7f0baad39af0255cbee32ebd18af13a61ade223339639c5efbadc02729818eed49bc4c2405a53f119f45c9c4467dd09111e7338051c1ada00a901da7f367d3ffda227ea6f690fbd554cb818c337c15cc4c786af9c96392da1127d2f624002b560840b1f9a32b04fef6d3e5874a7659532116700faf867479a5e277027171d496e791043c7fb07752d9f29fbd65b5de4ae75bec535b6bdc78e6e0d097fcdad81d86b5867e8e9f24bf2aa228ded4c713358d4c1a96337dcb60ea6d32f48dffd08e782c43725c7772413d8dde737096cfa3c2865993ca6bb1bfc811154f46fcac932cbfb70725402023da363d1bb4b9aaf60157a8df6d32e88e2c0a8b46c3db8b3b918388d5f1e7155e317ece219d4f224510a3b548fead2a7990bf4150280bedf8496d652224234b953b3c460869d6212c24f50b33512ebb679934ed9e0492efdea5f62e63113d28a14f4973662af998eb833c59d678c2b6e1c965dba920ab19cfcbf2d893c388d531bfb0c5206027b3e8551c698e317c188d61b8c019c824bba3b969c91203e51d8c4a3d30d6ec5a4eb336cad003fabb555d5cfa4bbad85fcd6e35d30705768e69b775d1d5e75bb34add73167178bfaf0b01c050b2998036c3caad4f060992da2d624fc7e68fbf4aa3a3dcba15d4353228b5538425da164bfb8a2b0db4a741b9826bf8f8b3a08665b8b44c74bc9355244f3067258bb325b0c7104e172c610f53d11f577d20f31499adafe2c0e19c2e0a073a51910045bfd7296307d45975ef922db3ede32d169ea271da7a97dc4fc602a65d44759e6babff0c70164f722c916c56a560cdce0fc22c966df51c2b22810bf095ca6e8e20fcd043cfe4111c3123686dbaeb630a7306a32bb237975bc0bfc973d821fd191686a50c10f29e4bb02f6acc0d1b0361beac24724e3361f476139c05a8859d695ba242efb993ede216871679bd44cfd703f5b0fe1fe0e1da6bb159b107e68c36d257dc2f7e7fe6e5544e7364bd3298fb01cb016eb9dcb82e4e1749c0bce0203be7a4509996f51c137a301cc2890c689e52046483413366788585bd4e80fdc1e451d529cf6ffaf4b20e531feb894bc5153f13a0b909d95aa149d3e5b9726307abc631b2fd3c542e89760b09777c4304816fda9fcba501a99def94bbfcd2f17869f6735f2a188477d4543cde582f36832f6468277843f56ecb265ddfa9979e716dca1a4974292c8ed0acf71211027c34589875ac227979e76b80b7567c88c81b0538b246dfa2eb5cc7d780aa55634dfdb212c2870efec504199d46fd566c3881887ec01bfc180831918e8fbbd00f2c7695e810dc32f2b1db46f8a6d5117461ec2a1e9386e6046b2cc2d3a988279b3444316a0d561582309cb0b5596e69814b1c4ad5f1d7b2ae8f6c65bb092dff0a1f480c4ec266c87326e50567c116cab44a9e3f1c3fc934222df8e10cc3e52e19772d3d9ff18513086db3ee49bbba6bd5cc01164c2d07ee91101d8d611d7ffa8bb3fadf9675953143127bda477d2e4ac3fde92c8b232fc77d457ed0ddf4496acaca5ca6e825227c9e18714dde6d5de40da5ed8d16066fba17a522c3e4cddb25e5002bc37e91d7221811b727381180fdec061f67377cd785bf75a49700e62b6678f1557f92496f08aec1bd11730e08aad041cd757756f496381c0b4fceaecdf0b6fe63d8f8bb8935a8ac86e82f7feed88d71ebd6013ba34090c6148cecb193417003cd07de1b84dbabd33e5f95568f0394cb2623e74744b7ed63befb5fd7550bb09f49827119928117d1c877912ebc04f90c0fe48fe5e67568a96455d4b301a8b87023b14ab681150bb6d94415a5b58847f912a87a10d05dcf42ffb5a4612651b50dfa881ca3d583f8c7014fc4bb4e265c18f602e691d01a3d824a1d882995daa32b52bf288416199eb36e447ea8cf1a8a6ea47454bcf0e8ebb53c3952ebddf1d11af42aa8abb4e0de45ce52d7bd79f9a3490661289abb69943c3098478564bb25bd7bba459f0edc0f704cf980d0d67456c391ebc1f3db4da540cb8bd6abe48e3a8e7d66663ca525c33acad09b6125eb55e78db515fdd36397792fe8f1520472c80513656a276f5122f2468535a0c00d39527981643281e4ad9fc649a6183c0ab69a8e5e976f3354dfc390587c36502fd72386b48c79d02120de69df2d1de294d421c67bb500bd588fc82b23a0e8923bab5b7216974045a450675ce9ab029fd3ca93cae6e59257cfbb1408b5105ab43ca772b3fb715e9a0b3b6bd887e4df391e2161d2eca470fb89637d0446ddfa8e477839a9605ad8d3ab6500be81dfe491b720289688011dc9b047f54a01115433acaea18dc0e3165b4c9eaaeb485702d79817215a8fd3079527819e8f8f3101ea996409e26fba70d87304024d698a20c129920351160355fb244266da46bf8f32a3889d33ed8f8e88ba3467ba27ae7129edf6b659a2c7a8d7d191ae564cf0c4f1012215aa9bc06f1b7a498a999d5cd85ad0586a4de20214fa3517826ecef0e23810b6d4d93a887d30fbb9402f6d120112e89fcaf05a4a233171ae30b42086de134d38a5522f8265c883904bbe7a3c38cc67cbfed88e8c07b6ed4d714a28fcdf3ef5249a4b1ce40fc4d05e8f04ce18106ad4b3aebd255c9ba1918aa5a6471bfb3dbeaa12110d29472a3a9880c3f7a433692ad7d93babf67c604d5a073d4907c85140a7058399d2393553d389b478198a70771d6dd220b7ecfb6d0f7cfcc9cf8d2b3f3e4453d064634c38dd2bd04c2fccc998c378e0b6fc067463d03d1980bb50e3b0e91ee9f7f7d632c027d2bc9505a5e134f02c86a523e82cdead2de2f400e38eb27e0c4346bf6e61ac1b47277a7543302b6974a640edb7668e09335263b626eae3838e6b7cfcf93c0b1bb93934731826f591c9da3cd66f3e12107601e9f84e1421ca6bbf1789a6bc5126544ff5111a19f0b671795b83012550df7a6e81e8cf0211a51790f2cadc0af8f6e17030cc413a2dfc88f4115b01513cfea5890992605e0b559bbfa01d3a00d72ec07bb2b6d7cd13993187081c002891ef086307f102e8001e812d84381bf8483cfdc0c31c11ba2a7b80494deea08081e5c73962ba3e6d2552eddddc230b65fea57789252fb03c8ebc4ef4f554b0fb1371f835b36268c18aa5eaf6325f7cb995bc40db67dad16d9217e8665ec1b154494ea0cfe27486311b58514ad6987696ccd4add07490c1047bfaa824c32ccb8f92246783c1ef82db1955685b6b91b1f431b19d84343d3b3f04406eb7687c3b5237f0851ea73d5ae1b23a34998484cb90958be7758b8cf820e354b28b2fd31f580501a9e56b909371f902aa3ece5bab348dd4b8d5db7126b80478c33b6e357281530508128c42c737c1744ab7b565834c252aefd58a7021e1131710e609408fd68805335541b7e62970e2bcbc1366c3f7cc843ad6b911d3003e3bec9b59cd7256cad1e6d9c4f6cbb976dd5823eaf4761d3ab40c1611f732392a60618e8929f040b3cec13ad2ca5d1f7f49c164c8c182d25728ebcebbda42bf368c82a2b5677054b7fff7abca1d5f474364bf223d29b846eba729db18b211a5e9abe12be0edb35bfb4c50d9e1b0b41a0ba9b1304b661f98e21c02ca5f617ccccd4ff089aea70f80e5adfa9e4267d0f55129cbf98ec9a36ede54a6a7cfb02690e6e7721d6535e6438ad300a3453419f34ca11bab4330d2cb1b5523c109c2ac7fb4cd701321a972d6d16e15a377ef2de2fa3506bdad1aa4e788c41a370e2631df91349b56685b1c00fb43e2f77a66871fe1260785acd394d5523e2101163b49d95ad546a7fde5ecad364448d323995c6d2f8d50381638426587fb675e4a5034181b143f98ac4ce81be6880b60417085d90981f0d336f26aedf8df4c76297803dbfd605aa825fe0e193b3d208ac05e92b9ec890c45216531445579e07db2d5b085b71cdba1f8835eb8e31990c5c2759fb04583c736d1e8fdff28fa2ae43bb486f96cebe5266db8269e95b68ed4d35564fa4dfc29521b1cd1f4803a13550aaee7f36652eb5169d2400cd23bc1bc577c275065aa0e1492302c4d677707b50c6cf977a992eab4e7d190c1e9b087fcebbf87f7bc0b225e20c5b54fb41bb6971a4a21bd6b80f756a9ffe0404be35422a70659956b48d10c2ddd2c9148845af7f7488d03300aadc4e5841c06698c7e35469b4862bebde8b7c6bc71d7e8cdfa4d32635e88f1e799dc820a033941e2fd3fc6138dbb217f57a023bfe328ff3469b8ad648e832d59199d3120e1da4b23e36c2685e16cb073e6dcc77e5088f3173ff790471f7da000fd8659f57e8112664df5ad1df1ff1c0480647313b889cc08929f76043ef97831574f5c46aa98861fb6bc24918ae4bc341d74ca66b63858a383992a9434a4ad48e53c8b1f8d4a67f78c5989c23105cd2636538128086d5b00b7bd8675c99812f7f899ff9128e36f76d7f29fb85f52e0b9e407d1ced0421c3593b2400361e61ffb8a80a53e4fb85a293eb911612c8b9e44a356eee9910c915c716544f2d41d9e97980cb52eca94a9ff0068797889cc89bd4d5a91b03293789697a0a4667324ef4c081cafe1ea8557189c799d538e0c0a93fc69f117d76d5dfa18f9906a0cea0a9f1bd35a5ae95801dd95f8abdec913132156f4e842d315f45aa23d4b636f558ab594ff43a8d38c7594174ba74ca8e4a1644b5ea9e687f2ce78ffb85539216d53b36e788126cf4fc74af42fc5aef6a9a33e47ac12d5a494f8b1d0b92a11ada6bfe2ec5c9c0eacb09b517ca46533f7902bc139c8b843ca485429f084348885ca486cf0b9db968f223cad6cdd6b3698cf3ede3c72bec068d9990771a28c936e9365cf788e0d6e2d629d469f257895aa9f23576e58643ce6f97179084cf44205e916221489cf57944f99e63500ea72c8e53f03177ad775583e862803e0701b62a5c56acb2ae34d28e947166be7233f450b5bc555b5867f594c8052db6e94d8d46c2da3c1bf7da6c9080830fa5d2cc6bd652dad7881183db4a76922243f90c13f3beb28e10ea7e9d5806c937113fe5d18fa9258f45c1a12a1a7bfb1109a3b0938a7f38fdd91388295b71408229b4725e07dec481dfc2c7d9aafcb7efb98a353d0e5bdf2cd8a53256efbe682492cdf92721858ebf42adfcc17617241db700793a4c783b70655e68d33b9b7a89f17823cfa30f03b2c48baa93fee2994a3ee85d098b20a35319a5d215ea7ecdb7408fbbedf0c37a0ed227a9181bb8b3cf78db355e988831d084ef7eecd9d0e6afb2935b20e42d1f5218f0e9a7ef76ebcad48ec09ad30bc9cae7034a15f59e78d8ca5c88f211c77edb39c6e9d6deb66dd164317a7c39fa76eea44c333cc91455c8fae3ddf83a0c5dacf30b14ba5150213655347deb05c290681d7a18efc9399ea47ed7506e52e6bbce3bdbc94a110e40091c51aa641598eb2a4c6eaeae9597d08860ed754adca74077ba0fb80170e6e07068aee1607b294a86178faa91ad32e3e1d7c7f97f36c8f7ef7dc0c1a31935cbbf0f518f05b5a8070cea244c64ed71803b3af4e30842191d3d66c74be4cf6af66565cb2a6946b23f82b2ada7b0a46bbb75f9fb294ace030cdc80a1865eab26650b101fddaa944802c75560b40d7d06377205a45098f34098fd4cc177106c29fa68ccbb4d9c65b258d4b02d37a5161aaaf3fed3b02ec3fd34edf59df480c5ad54690d0b85c61add9734f57a5bc02ce2888d333cc7cfc1b3cb180d9f31d295af135f4a22dfa826b8c03c541c9a541da7a7272404a06ba257cb425161a9cf10ac7ab887d070cc8177ab796552a9cd115ae4dfad52491189f5cfad25afd02e9045e68924d0c6919c7298f02131d54e6a1e747f4c77d1904f72d27e1e4c217275aa3a210542c918cac7663cd33a971cadb0ce13a943f47b8019a4081a9f70e6599eb3942b6abdf57feab84bf75782c22de1581969c42329b476dbcbc05fe95ea44ea15a9adf15d9baa2d1a82f9845c26b58f6402d4041f3c90d39ee8a8d7cde1a3ea38eab5907ab1542e8c20fd55540c15e00546244f58b14afcd995a225716154709da95db5060e8f344c4dcd4ea9e78bd271490d917dbeee9d52cc7cd48bd74a68e6ab6b83547d9e898ea1c0ea61b5882b491ca60495ffc5765a0fe0c16d296b3949383637e020b43f881ddab980f5daf346f59cc88508ed4c4d22c9f6b8537d91641ddbe36dccd7843022fc37b4a234f8be5f7b2da84e21424d40b91a815a429a538442402dbeffa5964323b7bb4debb3fcb528136a0c27dfa039e0d020e079bad1184e53f8e9092897b302e19469f87431bf775f8b0470b9ef55deb81b764cbada25dc0c51b0c78366fad9e0b72fb3f0186bbf12771b755c24c06d914d3b51f63b10643dc7390ec88a157fae7c101e434a58e1c78dee454e59b49d6c85bd667adaaeaa9d9960567803ffc8af515c36995bdb6f4d9ef9404212df076d67f939d0e7045ecfdcf953e02aa82b909d1a3789145ab96a68e12194c651073a191d981b55b106bac3294076221b780c197d03c1c71ae1cb92f4aa4a27127d4a7332f76516c403069ed0a8e91c202cea7728b39c5d7918f9606fc401fb8be7c6bcb8977273ea288ffc8a560d874f4c782a199da01a8debba4fdd2b7638b7cd2ef0f0d8bcccfeeafb8f9d42abeac75557cf21c7ca8b9267b296a00615aefd87592ec465154c3ee04cf649bb33582f3484670daeb2282f1c5064fcdfa89a7641cfce127bfeca48211200fbea2ce05fb762da48efe24e48f89789f328cbef3765f0c2bb9c67d7a48785f1d98cdbdfa94ce8229f2c8ae7fa24fb8c508cdbc167df2df618cf279fa3e883a8684f2f404e16131909bf1bc831568c22a965a320d637640d24d98cecbb4ed9b1d0931e8260220ecaee98c82b9bfc0b9c2f1b28e1d9ef8959787b10dbf4dbd640888a0708d854544afd7dbdafe328bddde23878c64dffb1c876765b45919c204e19c8b5bec3d87aa40a43ff2c1928b183655e2ecb4877feba0252ac3ef2b68a4f20a159303e27baf291d6d78e8750c004ba6fb95bdcb9e416455ae07424a377995488a7ac4054ef007e5b8288e726ede4189c643c6e4a19696fe4210212d742106cca9c3387d312869c2bf3bb2671be4a7ece8a61e14cbdad0d271b86cda649025b143aecaa0a0d5720104b884cf6efe5fa988a783a8b4d17025ec124a2877ddc6dfcfe0c9831b396aa9e77c1623f9958c11ec6b33db46e8f8825a60d839bc27ded4afa114073fc62833cf6a00501785017315b7bfbf47202754150f1ab4275783ec129d9430d45e6882d1e1d5e10e49bc0248208ccef36a6de5ec201fba78f3704c990f378ca03ef9159d444c872bac51681f955c9a45471c70c48388d2f1e8e6f2125a8c21e91e6b4382fabadbfb7fac89d1a3cb15450d4708bcd3f55795d52243ea37c0df189c3e12ca371aa430e3ec865d3c3c0b810ff331aa1d2ae555bb57d462c4c55d6abd6e3021420919673d03150ebe7ece55b97cf8eea1364c7c7e10e2f5014bf5fddf420cdceb77c1acf843e11d275368ac9101efbb2ae1f4097186f5b433b0872d3b1441dc8e29b538c461f32df766f4052afa47efd22c846e99e12a6365bc9371832eb5962128de997e9b822fe89180ade23c7ffa5847fdcd2f7d4449dd7dce7a4d79f7f7e1099f274de86186c47d4219d5fb7ca3dbfc784b36830e805843f61035ca0f86d7cc6d8ab214f239469c75654f93a5f912417f9a770dfdc8943da33c86278b20e52a2072ec4e4fb2e568ce2d86bc4c095bfa7a974b85bd2288437bc05d6e20a430b57f55af644737d33669adf8b6c3b075feb412dd7a30074b6ff6e9485f833bdfa4de3b6137e9cea1dcc6d1b3e40b53fd399fd4848e0aab34f442f2a117dd391409aad8d89aae2e4f3abbaa7c003ea3d2a9347c38386d32402f08bf92518910d92c179f9f92376f3ae60eb54e3930c9cd423c2c510d0de1da187df2b29ec6b998185dcc9aa88c6e95ef35741020b505a616e5c166003cd7017c2c4921389530f2eced242f9cabc1efc2394292ddef93fe106dee087af538c779bc209722027adad0dc6f66a0239df26ec3fbd56101844333af6919fde9710e7f83d50291768ff0b9949744f49c54d5ca92c82128eea0a618986da47015b0cd5a425c23909863ec9829336ef44e0941d918d1319c48fe6ebb5ee8f1370337485a6e8a6e4ed499a263223686cc0d9406d99d0ac5e46490a8dd2da1799ad32774d1b1a6f5bedbf1210d424b260fe8215116e25e364ad8079be564d14638a512a586f5eb91b61225b791051993a30081a30d014ba9d024a7a0aecb7b6cca4c10b026539c8d0c7960515dec52fd5d3c3ba9a0f8184141bd51fce13de2c85e945ad0df71ad49079f6708fc13c9c4116f98846f9289d8707e227455c7084c502f834c5050c0ffd704b366d409468bf2ee9f4818a25ffb5a6400aad4e96da016cd199a2efb145e1878f9aed01e53f79131c912d3a1a9bd81270c6b70acca62007c822c2d93373fbcdf4cf92cd7fc08f1c317e1d3ecf811410a2223e731af1a173121f0296f810dfa63d367f885630dd61521ee7154a907903cb329899980e924ca1284b0cd5ec1148c9d82c097c0be6cb5a621b19a294b88bfebef424a5400599b05c498e11ec40ed20a66055bf08c223ac1287f5a15cca138a37f62451d1444c40708040097620335057f62414c5090205da0e9e070aefa9e78b1f302aaeca279b7fad49472a93af251990333d58a6c1f071310e61837f0b75d438d2a77c1ee5ab6c217e50ef2fb8846a148dda441e1cde3d54faefb436585d2eb4585804b83a42f5830d78236f35ad3b493565f203dd2f977b228c26f3f1302d8769346181b31fc8898961d481fee259cbdc4148315319d383329934a64e579f5e12b8e50e95da333fe89cb12f48d08d558c78068dbc2762379539131e03488e1c8926ca4e86ac9948847584de883f84b2226ed2722925e71ea39867d16fcc5c7e6fd0da34ae9e496d146d20cb8fbca2e4fa5d69649aec827931cb281678f132d338ac0e1cc400f1bfea636066551a6ba334c9205f8acfd10f60a0e90298197d9adc8c8ecfb0d9bb4357fe529dd7b70572bf2dcd35c9487c18e38f426765775c71b59b85737d04c5993dbc763955f27013154483a5128aceb882b749f9e58d5f35203607fdd19aa0f616c0a6fea88c2731881ccbacdf976d862d26c69b132f24ffb5a2817a946f6a319e4c4b5f21418e255b55a38c6263dbb33b14431532ef3542f028c52ce039cad31e6e7e3a8f02b0b6ac1e098cbb01ce5765d45ce7a3ad9a8bd68e50eb8e4aa3b2e0f6add65e0dbbb74f4d25605bc358f807a5bf3a16dbd69d2b770764a013c88141f088ae3be9fb6f542c078fca6d17e44d0f084241f0ba9315fce847fd313d3a9bb1b1ff00c31683866a17893b423af691f5d9c2d7b6894e33c3ca372d18cfbdbf66ae3f68fed900cc5115248c827fd5a4bcf16edb95236a9b66659f6b9f989d148d584a6a45c85d5cd4cafc55c69da9349d83ea6111a33b1f0017277c0a7fad79dd112ba065225b1d128e60fcf78588e604f072af1b046fa184dd994874d3ce96a00dc4d03ea95411d2b8a6d12ac60be2988527ac10351165678e0fd644c25014ef43a4d2ef5cb5813006e92081b85c7aef0a824cd652907ca6b6575c4380409bb61cd363e86523405c354e114313685897e38063ab30d0d0d1ede82af028b718adc5ac08b948c787308052a9b21a77ed6bbb8694dfe2c42208ecc285751bfa643870ba4dd5a6e1a58ad516848d685d841d892484826a5e54d4515f70b539fe00e63bf9ac05dac05d8e10b3dcb29244c1be4a88d526c3b50496fad3ae7c29f85e64c19bc4426b63dbe37445e33c3cd404290b3b449cd51ee2313479a5d5a17700f44084278a8324b8b08e96a3124a42a820fc8afc06a2fb08f9d6003cb8cbe709b0408c81dd3fdb502d3aeb8245d46ace98eefa89e784053a505edae3ccc7607947bbb43523ce0156d1e30ed4dec329e8d8fb9d66ed77d15e8ae3a0906b60894d9d8d6bd8051c7fe838cd81c286f614eb191d5bde825d8428a6a5d929d6083170a86e56cc9d52aec7ea3e85ee504b2cabaad56fbbb4d686810f210efb646c0ad35a535cd6843f3c53a9644d6c8a3c8fa19e1c25d9d805393a7c34d4ec5592918bffc4cec332735dff9e92c7b329e7a4148eaf0768b86fe8731b159e370787790c8b6820f906450459ae58413b7263c6824b9325ad7b29564ce72c2a034341cfde284c44c987ef680827f95c46a6f1e1e4a4a8fec02c40ae81a032986051a411c8331c44d29d8f20e77ab0de5d2f6f244ff5b624f2568f01455f0c9120a53a699335951892a9c7e4a578be54c2c109f3218dcee37250ab55bbaf9932e2a1fceca2dcb2e48142cf65fe01ce281e2ae43b72da33d46edee8d903a78049fbf21a4f20808986e2ed83684e2063cf0838fa18a446ce93e118e4d818662a2db4e14a8eb071567ae607d2e3106932a4e2de96fe5e6af6088cf9b575d881eaf4b8c420419aeccf2ce3da7b0851427ad58cee20d0c89c271c309b72609d0f348d87ed494541bfea3d5313548525f3e322c796bf5765edd0a24e0711f74fb4e17c7b3a56d63162d5f34889086ba8e4d906c7a2b7a9b0fee6fb8bc4ea1c0217e4ece5fa15edb7e2ef4cd21316ed38cf1eaf67d47577b6dccf9d07a9e6b89522704da0f46c335ac17fc0ed2db35b5aa2069449323b194888d9c3b76dbaadc7440db31a3a5ee1fa6f1e329d2b8dfa6788503d1cc6111a0b94e47dc2df48ecb69fea62f19880f79b8b720d7152ca329cbc420321076977dc9c6573c2b0f977c6fad268702075be1b81fce4dadf5d0c9144cb4211a5429c5b81ea9a676a6c84a7d80d4b07e501b93848d61f7b61a591a86ed71cb9690c9b8045485da2a45e8e009d43155257083237bc9153a390809664cb9a37d7f24b410528e0a4c942149323f621060cef00fa36ed9170191236400cc03d4c598e52e1b0dce250c63d28af8c948555b678aad551ea320ad938318825787feaa542022892da5f867b58d7c5107f2f8567c3441435dde57ea73b67345c06b68b57d1f8d18dbf66130961101ed85f64948d2dac3459ff92c8378136d4943400d7a933f6985065dc0165eb643bd8e94aee6d43c46a9cac8909709fad89887f68496742e1ec97d166102849d8a04d749722103ad1e831f09f4332ed04a01da6ee6703402c81890842c74a842abfff615d5bfce003cca0f6091c4cb27deb7347aceb6ba5f3ccffba680565b1623b1eddb72db2ff2ae0a2a29cd7cab4f5580374d0a8d6b39c12a4f1c61fdb48936a05422a8b65eb4f03cad8f4340d13c98a0d9e86234513b4352abf167e270214c127717753e6f073b9e69bf8b2fd4e05b83d4e1000b839282c9db91266f2fd5c9a314e7fd4346d70569600070732522c58ee074c3a46eb66e032b2482ceb8906c7d45fec95b7a29f7816897d13fdd21be52227df222db0824c5b242e0fb47925c4edf86f3b7ebbea21e948d7a3bab696f698c4b90ffb8bcaa5ca6acafe1799f1e86a5a3d584268db7cfdbc9692d8a8d512643fc24dea5e2ff77405c98b91c6f5543517c3f4de47632e7bcc6e2ae4e36e66939e67d08039f77ae68d1ba7c3620e3680e97421ec0f95524b3d2b2556b2ee966778bcd3a5421208343d97fe38f7c954832a298ab623af884e05960724d192a862ddf0f851b0975fe117111c77328bf93962c9d2af05929fb8950b28ed22679796940c9220125ede1d846563e28e4b7e01d960d10583dcbb4ae6c4dc5af1b5f5d45edbca2efe38aa96df4aa164c6c70434e9e33d0c4a0e64bb16cf2bd7ebd0dd73ed6289a420f9e46b5145a9f63e8dffb9af89f862fff25f0f327c531f3513d9ca424a8690c411cde461e78b164680691bbafc4151324571ec7cf0e659547674162ed44921a56891a4629539517010fe2f1decaea9474b2706f856899afec3736655d77086b47b021d14c1675e0f370d44e2e638271346ffe0f251953d16dd1cd3c92480c1f62574782bdf3b19a7c13de8b38a800f6df44388983be32d6655bd6f08ebbe670b69a77b329ec0a249160a49b65522692199f07b4a50deaf34b747bacfdc7f45b15723cb7389c09dc442389730ef3b242cc7c795196ff50862561be4a5e42f3911b793fa49297edd11cc668eaf5d7cd2e3d8794544c883ab04c116e824b4ebae6c8e1e07021b98590cd1437f8b52912cddfd60ad7192e5bfcf4593166e71b93b3fab53dfcf028b3f8cd42c9daaf8114eb3a8e582e6c8d57cbc7eab1fd553129d2311dc83cf247e44a3ca5df62db4f25c6c497fa193b7355a4caf53308115f6a7f686b980ef60f79368570ae577d7a017d8d15ed005d95d9821d4409e45956c933a32f0254ab18d192bdd45bb68afa239590e0804ea30b3298b89c4a7d7dbea11bb83aa827589d0ab1303f210ed2cf876995968500a45fb34d86c8bd4230cadd345b88e5a171a0637a9058530fbb69b8e55ecbdaa2c15cdd380444005e7e633582847f94faefa49647114c0972e17f5dd7de7c08f1764e5f75eac3c5a8619e8b5c7dd43b78efd8a087051d3b846e853b266a0ce9dd61467d0c5753d16006c13735f58c0a9878e1a3d60be0a8154db1610332eb684e4917a40340d35683d0a9cfd37c62bbb1973d11611f2ad304cd5c031503c9d9f5187c0e6a6f3189b3a4cf190a52935cff9aa469001528e2910c10c21feb833335ea61ab5c4b4b1e9d9edd80644662e5d8826ef173a1b17be10f77444e2961b5024f7e0fce9e73a0631578b3c3d0115089d8220c200b147f97b492b2650943037e87afc1fdb51b5d3dd75f9c90a5fc79e6e3099da25d621242ba8f3b73831e84f48d644255d82b7bc58b713aac7f5d44e011bc4b2106792ddee70958697737bf95524c0a0ff407c1e6b0be0e25ce8e67094585a2b08ed35f28cc2ccb80978c8de63e16c50f20d9de140e01f7a070c0de6dab07f00fda3b960c2810dd25117aea9b7cdc0e76f711febe7c2dc52a5c650860caf8d4c4e06c7e950d8af29a6005ddf501e7fc3fd2576f1e1843d31883b67a9e0337d5ed5119fa8ea88127416ecd50da32cdeb9e228cd64f784e5d6505f4fa255b05a65c69b59bf178983905444d2facf856bbfbf7d1f937dcf8fe8f060ad20e042e501f9390c05030d439aa9dae4ace874076f2e9f038c9757a3bea1782834dd6d6c1d3e4ea955867fe802b75b758f3a93f8dc675fd92a63a1cfbf573ed763f9c9eec2ea4a9a624821cf9c7945cf0e465148235709ba03b1b9384ac6b5f7b2084e289477d2b2f22108103b4002a62ae538d4dde78a6e58aad2f04d5aac7269d05f7515dd95c72ab56710839c72473a308ed1fcfb9dcd217e075eb8465649ef74be13dcbbb06be57b4f50f85f3dc8bff37ce9076eb9b53fb12a7d149ded86f5cda69d2de30f90321600626fdd3f248a05c4ac479325824b20309a39a7319a1a8ef6c0060cf3ebabc54a9c6f28d51a890acfb449c383b388c35daa1ba42e3ff9589abaca85d76d105585f4715b3a073b4405a5235103041d2588a4cbdece0f8c243a1f00acce22eaf9f2b22c89ca119f19c96664733510a35981a492f49951236e55f3851a82afe4f07a896a9826b505e0d55c81383e875b5eb2c9b1888a547c79449bc6869538051bb6af12679e3b2a86813f24fb7d15019254962d6556de4b431b064051b355bf7d0bf77e5c28b9ef188ef7fe729a5d86fd040caf43c7909a6bfd5e22231d0dcf78c2eb876fb076dcfd5cca83efccdbd58e46aa014098a5566d54a1948f37e79c4f58f2058a8eb07da2d0b93859a17f2200eb6c41eb0edeeffa14d53868a961678a301634a5691613104fa9644d5b49217ba7b67a0e21ccc37e7b52ff0c6a3300ac508502789793c8fc68d1994f3a738403589ada8f4c738fc1e83380f1ad53852a74c94060982075f4f53a1eed52286170c7a7a000c719456efbe02ab8f3ca3de73665d854298e3d67df5e461ff38c0fd64e016b7f95ce1a5a612ec2f038f9fef91ea3d39311b8a4252e9b1bbd570003d238ac6b5c30bf368234d88fbb415b28a15ad4a3a2a001799b9554988dd81f8557357538e03b5f1654acf5072a9e11cc67846466343e08c70699d5a48569b0097d54d385e96f08f40d10aef800a135c3fe869c20eaf2e26a191f30d4a93529b324358ae2f7bca1bfa3938453f9558228e3ed8404bd4e2840a76bd70252b78c4006057a8e803d1f36c6602964584fe12f0df32be838e2929653ebfe06771351716243d6c673e911205ec3963d3604aa9e470ff36e0022abb987c63daca10d74275382b177f5b37df847ec7ff00bd314ba045f271a0d66dd6109ace768cc82dc74e51ef7917529cebf283f4f2268c5e827eba2f2462e0915bd168ea2b83049ede1ec16a01d2455d35f3841c952146e93864fdac4e13bd59d1fcb0a89f2dce414ce5fcd51797f93f0c4ef377e4fbbf7026053244fe6a4166c811c39a1abf7e83ca528d82e7786ff00b06ce7307c342fbb07259edf39ce2ddee03259e8edae98b2b51e6dff1f5eb5c4a6b1d37b940d1b9342c2f8e7a426661ae82949055b90a417914eeb95d8a2161c1f9b672b7b2bba7a0e8c4b20f270eed8a1bb056311c541857b70d0dc1d0c9aaeff785e939ec306a95ffbca6a066d70ff888585d7ad1236fc3973909c286106938f3dce9aeadc8b13dcafbbe48685c09424ef352db483e233fc391ad00f5ec820e4d9e0d1674551aafd6bdee59d25f7b9d393863b0f612aafab979754d9aabb5d1b009cdbfa6f8e7fe984e8e1fc695800d1d105765b253035f93eba6bee8f4f9fcfc6786168e607d6425eb751cde3c635d8c52dbd3a4cd8b9d54328e0d0ec1b309fc5c2b9e05fa6904b4faf9bac72189a7bf3e17a9815d63ce8e1425d70d3ccbea47b7d7961325e536488985d297ae4adf84552468e812b866252d58f455bb2b142a048ae7962cfd8a464742b036bbd040bb9d8b3d6c653d3182cc268d07470989f6d5e36fbf06d13686b83accf3222761959c36b1461564c18ff7859322fec2faf69923feafe973ee3dcca8d797896d1f872e734e9133b003025ca09f77ab16225f7630af111a24af7e20c2862580982b4f2e37a4ce387d71abc3a488e02864b18f047c11a46d8e30ee983323585807cb79795c8c16e6ca7c580fe4eca1ea73b684e4c3e18991d91a59f41073eb2622064344082e102a6de49e1fcb71c7f52961c16ac2f57d02157bc1801bb859c038ab3561992e8936978747c77a8569b1a626fa55ac084db28864881ad4c67b8232285e2368357d055342158ff45840a1cb3705be06fc266652b974ed401237d34883f6aa1f6489f6eae917bf5e22693094b80770aa58a786b2243cfba3ca1cfe20de0d7762338c88d1f2201a9c21b7ef1dcff607292ac9d2702ace966ba9db05da2614531b33a1520e831f00afc957d36b9600b816acb57b73d5f27eaadef3a11f515d89ebc27d6e33a3d744d0b73ea0d21f006e0931df70fec35eb6fa2a95957e27f1faee1f6bb26019a89870da7b52c974b0838817049f77b47d2aa88fdafa65362f74c2ade4cf4f7ac7e85389cb9654810be3d7eeeea765c2fd95ec75d3f0d2c43c93b970225f0fc7dd6fff5976282ffaff7e598f540d3257fde4e1c77288061d3cff68cd86b097913960bbf19943ff77bfeb83f19d0d93cc286b1798bc2703bfad55dd1827f301c7c211d750368f710f326c11fdbd36c413be0247f21715eaf85f0ccd8ab68c85c701229cb02d776ac768f7371aad0b8049b42841d3822add91b98a46673f41f43146fe01f996309871c528da3b3e6fc21b52f7bcd10fca3a26c8ceb917e04a19fbc76431a93ef4f916995f0d70c13af35784078386848aba3dc338c5d8f5dffd66960184687b8d1f995b15c256c88f09c4392181f02e072a10be4f43151d75fcd3318d6dd060583a26a8b2fb50c76708184642add16695894b8d321646c36168ab4965402b73bd794853d6817c76f5a1491b609b8a4f5efb8f75bcb8ea540a4b92aef6061db23f818d1e24bf866eb49eadaaa6dc7b41ea0f6038ace19e6817024d093456dfbb9a4bdb06edf62ebd7ff955a58838e53b7acfa7ba82000f2b2166cea6721a3b3c3d6884de63788712b3b50658acfefca8ebf444557cc15552de8e174693c71828140500987009403165a1cf970e52f7c42b8888506a9d36afb4b815e4a2ef784ccb7553e801bd7fc184102bb7937138ccac9e56baf9200192f72c2cc80bd4da10422de51fd56d4489a1c80dcc2e39c9e3a0acca627064c1638afd6b730ea1020c17a3f3b55e6a48f2c021d59caeb4fddb02cffed3119c8336a409a631bf6531870428ad55f99d131e3b872138c32bf9283516a8e705551526154df6384dd0f18e105be7c379b8a58441e0061513fd794b9bc0304d03d5a923ff7337f716e8aadfdcd3977f44eeb366d49cadbdafa0bd09637290019e0e37d50a021c4075c83f69309f29a9cf579ac555a7ba8c5e99d46715e5f81f7db1ff783cb234b595c22a873c0bb79375f3c1393be090e0ef11ba14d8a09d7f29ff32254e1c15ec77515cd06184f7444c543bc6cdb5b883749bafa2c8ab96e53ad119d41a53238f2025c736029636051f80918dd753508c6a9b18562d1f05352f6252b09ae979b512fa5e7c7ff76f2d468a6d89b973cc192ef6e261ecee8da3423160a341d6ab450940732e51250fd8a86bdf58914a880a59d0b28e0f145b043bc62aa5ee5c05fe8db5af64488ce1b024e110cd88fe820e4b54f9e0490f4a8af7d44d5a763b0ed2bb2d77eff363f786aecf55747590a8e82f8794495503203fa7d68ca90012861677652cb358f4ff6e9eb82afefe598354c8765a9f6eabec6fa9d0cda42258981fb67f20bbf9de5c0c9c7666904d3934f88a5283f0aa8db2e5a9c6df55ff6bfad8ba8017e1c0f4bae59f20156eff9096e9385fcd11a18e5caa1564e0ca7be1e183651cb2ea63567e433f53f0d688d4fa1208a82c6d625ed651bdd39361537ef3fc170634b71cecb657b0193d09bab1370dd53d1a5b32d606e8102f7cfc7347a223040609571fcffcc28243c37aa46ddee11c95137fd21084bddc41077dea0a5d17440a4ffe55912ec57dbf92773d8bd407d903c64c7a263592b70d1e0b5337d304c75f1e3dc98533ef78ba9d18be2a6709df42f9c229d15b57a9d47055995bfd68f7163a4d4562256bdb9947f89623e5f8ab430a502a54b7b0180afde9f11a45c1b143fbef0b178336dd1f16fbc2d53a92a5c4385f39174e6ef021eb866a729f4086f07a4a118f0af584f05474b3c07e3af3c9f6293c4a37657e4785d36d30b2e414defefe6838306b96484998686cb206fc610d7022a90753738b741e58b4775ee5cb62d11d8fa37d0b313e7c3938d757191098d7ff5a914283fef2032f64ef7867a24dd340a4dc1504a59b91ed37a241ca788247c3abb50d082e25c0920d7742dac2ccd51ad59dc5b4006ada0fb1d8cf80b9f4d53884006f828dc413a9316a3f0c56b85e81bee2dcfc4795454cb027dc4f02e652b28332a82dbafcfe3dd06f6bd31fb5094b0eaac602232aa44acfe8f78c74c9a34f1f3bea058bd39424da1529afe48b8a406b9497ffef2352b55cb6fef1cac29abe5aa5d0c2a34424e56ac608cd3d64499021413ea7bead593f1ae6356071fb510a3f36e383018f7255041d8456f941ebab96e6ad8515b15fd31a8863fd066ebaf2deac7a62d404a790c1f3fa552af24884f75912ecf00b83922eef7e9d6bcf6d3640907e2a3f5be42e6885768200e745d484f6380a7eca8004f5f4e1ca5f21d959ad53b7479dcb8b30fe4dc544f2cf679372b6145aa43afc43f745fc1fb51f06f76b6cfe1f387de1dfdba9833fb2fedf615f3f1c81a841b7853cf7c1f4dd8fd76cfbb6db2ab9c98e2f488c775a2e9a2a1f71454ada626af50107a588f9631a0f4b0fb4905b98b311d3c6aa68c96229a1b7d2bf178eaec6747a6474b402c23c4fa1bf6f4369e1a0cb3ddc7bcd359dd8c97858192f0c232c5101e2402284d84c9fb43172c00160e45140a5914586112baffd00c6041a0d8c0d965f15e22c2af9288fd3604dd75b31691ecc89af2728ffa758d568b43a34abeeea661971586d58b800daeb64de45c01deca1ad9dccbd80ff5beaccf646b3b287660eca50199ab978447e070f45afe401c091068c0f48cd00372584576ad3c7ac8dca81f84af545ad0e43a3ed8ad8f3a265cdcad06d2a8b41e13bb81d5b97b209f844975933c35d17f28fe78f4f9a57a01697a314bf797f07232fa0a1dd39a93ea3f37e8980d5415785c41dfaf525f066f9547e22a2ef77d82145d1d753df0df8c96cc5be124d5a1ceac0b46ccebb96f4586833f6da56cae5b95429e60f58ffb99d807a99baf29f8930de59d8823b0becbd570023c070ca1b87721fd8f0dec9696c610ffc95dfa016bbc8636fb024df06469b1261d4b0d736d59b7fb0261551dd648ea84a83173e531c3c01ce5652cc3cd3a3b5f9359eb5f441ed038edb8cc5da1728db42242a37d9bb3703a472f42c0134d201115376fff801e9eea1f0851ddb8513c04454f781b1b5cb9751ce03c97a7c7ec698788d2d093ee1994763fe5b876ceab8533d71ccf77a5bf6f022803d4e54719555e19e1ac2cd3f074564df363b7a19b99296ad8d68a37ba8709ec59679e516e24361535eb304f42c065f1c684cb6e3d598687532a17f687a8356bc7a935d637a97299c7088e1492f526763a16be3b934648fcd83057263048d734f4e734f8d70d6218a8d17d9c9bfa423303f50db7377d5585b3e4b0c3dca8e289d11167ed15ea6cc5341f498d09e66fae4b5cb9f51fa1064042405ddcae9055dc85761798b1ebd55cfb61602bf5073a58159de59f4bcee5d2d180d5b7d710b52d88938d95bfdcb0ce33d30266e18fb282cadc3ed3806e8560b8cc660d882a52449ec4c1f922b401a02bd2faeda7e435a2cf97d403994acbc945d77789d0fe507d97f5279938112cfca242f371196696119a3efe05c9d1ec473980b931472fc60f440612e2c834130c0109317d995ac5d32426d2028967ed1620b4b5cf72cae86e27e1ccfea8b951ae37c591ec2710fbed68180ee19cb183b8ea2e089c0b641d6eda598431fdb24f34258a78d48075ca32c9d2cc19b5662d73647ca6e8ac4edb20bfb29e1652eb03ca207566d688c36cf608468477a70df1cbd7210eb245d2c9ff2e0b3ee269b7fd91e125bc94e816e7ade02e845987d4b655588d691574d111ccae888f8c173eed6a9fe1961ec7a9917dc9378f93be2f28f5567f023e4c92f23a09b6872a0162c2de579a6f37272b72fcc6104ea62aa3d3b303b9e0418e0330cd3131a6d5fe51d45bd8b68fc6f4f9ca2c878798fb8be51ecf19929bb88a3b53e41ce3d8736450f9e9fe33e7a00024fe489408547500d7eabb6111c4f25cd37392de8a7b28607ea8997bcb9088fce3b9274de2e24c558abad82ddaaad6d5168c9b568ef16cf3931a55f8e8c13f50dcc505d6257e89f980e3d12b2b3ddc765a284bc5ba4993eb247e18c43bc8e3c6bc9258302b701bbbde3c70eaa2298cc4d4909626b24f847d393c843fbd435e52b1bdfd3e2a1a8bcbb4c8e097803d06644e5841464a88116fda5a345450dd67b295714df5a4ae0b74fe81374008098a4b8da50cc0a55b4bb6101f91e50ef2877598de71d38888f03699db410d26de4ce8ad481dc480ca5e36c3175e89386e9818ab5299a7c93d7c91360b03e21af938b031de8243e74484ab108bbadb132cc0cd4800dc8b6b3a169c9d14166ea2a6ca512436f27a0f4f3893e9ec24819c2058e7a8651b98f6c0800a0942fa08d8748446b4cd6956cd25100c1bbf89d89bedc66ae24591c6eec784257a20a92d22ede8a4c0f0cba6dd160b7471a7982c9631a2c64aa30c4c0b0b31fdfb3005c490d2a58752ce41aa119d8e1dac2027470f484b1ad9ed4b1406fa8180840e394267f4aa14c5eec152666b621c47fa2ab6f45fb527f7a654fd97a005a5c2e49f4aa22737c659dd2cda5aed7232f5b95f376b9d43d4404d274d4dc3dc2c221bc95c63498057c21cb9d9bfcccb3868d2fdb4bbfc7f764a2a747c6f599deab90e53acd82cd9eb7b3939cad2fac87969c5169b6d5d28531a00e8ef1d490e14b1b30bd0578243068a6b8875cffaf209f4b5f8e1c0fe507c802b4a322e24d47af489995993a0a5177bf41b84cf1babd516b749dba3d71a08e8984b8c25e1d06aa000d3b045e918f90c232a2b7752ec9c4c6f1bd76ba55f2f8df3c46e7d4a87d3c6d5ecf4c1998cdf51a8860068a2076a689d2828aadb4fc896d38f235812a4374cb2b60ddd10a0e1d3fc597747349923c305d006da300f2ed1c8bb5d18d9382768ece3de1a26cf1f261b89f605d9e04e61bcf44dc7c43165497c866d2d19b9d00d1a44ebea7322aaae6959ca8921e767d3bfc1b692ca5dfdcedd6de2ae4eab1ed75918c08ef84d6c45d6f850b5f48c65f1486b0391874c220175311130e7a41824bc7880318957969554e3ce14a39b0d65564430ac12027c1ccb96f22e705c6a588e32a43115ed02af079514aad8342f77b600a0c5b5d3b98ece0e677046d02529078d9e37e51a291174a4e86d2fb8f8f6da6b820aeffe3ec6879652d5b955f54ee5cbe284df84ff534a4f2d5cc10e679915d15a7ed8431339e10bfdd38349e90b0182184f0c89053dd57bce63ede097c0525f15a53a11b5c63f00df1c7226553bfdbb5c36d7f68f7c41b623558aa9aa91f6930feda8a0005198cf888a2b31be77f52004800177e0a3c30dbe8d29282e3768b8908a52979d0bcf4c51ebfa4de342f0d8e678f1ab20f3df585b897a9dba7385207e01ad5c2423de4bbccc796a9db719e87092f2cb2c18d4de456a0fbc6c3fce32961a8e7f217bcd6e9887e876d80cba1dfba923984a615d38095238ce1f384bd2ca10c6f34ad5a9b69df25cb89a63193be925027dd2775e6403178f5f152dae8e8fcb587e800f1611d9d1fbef67ef7c8709e119c1fbe2517a5afd2bb2a836036f3d81ef5970f6df62436c1e2fd4157b56166f3a4d0acc4e738467ec56afaf9b5b29aa151e5b7021d7460e86c700d0d248e76d0ef69f35e12e6e6e55338a4bd6b0290bb211712a7b1c1e74f7962fbbeddec2d6cc92cfb1f7f04616543d8f527016cb073fa717e732c0710d4547959e091e8c658a9f2c8aa48ed9c89c986874bfa0fc3ef87a3deebf669bc2f644b341e925b4f72d8d83817584fc7c736de12a68dd25a2261f179b0039bebf3b5c061d973b327f72531fd6e005d88d6c1e78a3a949918702efdea8fe0d459c94f73e758dd333593454ceeb980e754c23389b5e4e60bece41713af37f7f4044e07c7903a248f8b1d8cfb7066a474ddc52e074b1be9d46e1f569e4bdf9296039343142bed2cf3f4fec7bd6f59d6526b16504fed65b30f6431ed16e624d880de8d951f904021ba4891514463fdc557369e3bd00c67e2fc0ab7ce0569dce84e5a41e148963b925541d6964ff8954497997d28701e6260bd4dd99e10cce432b31ea34db9420f0713ddd7babc91b3a2c5614a0519da2270739fbf955724633d038d0b9b452c9387feecd72c4323f2ce16394df91e0d466874e261b928b64e3cf22959cdef7ac6c67e3634cc8f2944fba34c47db84d7dc2bd7648e80fda5d2f9e7f18d9212f5a528c8654295b2ae74edbf457d6942beec5e2049c30b65be1c470da965f9b326d1c6facbe59dcba2012310be5ad562c56834882f3b20dff8fd730166ba462d1d38705659624408bf24948d8e5c565e0c1037a92ed672b49084f4ff26cc4c40d1ec67181aa2785a6005afb3e2802fbe27277117968529fe31650c4228de1b1cf361d5d5d19eaa1a9fd9c27e142153458a575388e2c5970c2435c7b39019c0cf2dabf91aca5eea146eb69fb99adc144240ad1f4c299a4979170ad9f399e1d814fcf27dd1ee23a9484834227419640ee438e4eff9fa74a5bc14180a9c3ff17ae43e5b54f922e96f29ed4683b5edd7918701c4753b1eb5d59ccaf6009546d131da84099d6b1a80ebfa0a213c1524d5cb2fc8f90c96fb6a03c7293d5cfb6fdadf60c819c379fc23de0b1e2c79ed40c916ff5e5a1418562da68bb839c9e28372bae4c1cdfc6d4d40025b5d94fabbf12936a31f584253c6f217252e709b18014cda517ed7f3a660895ba53ae4ac50df543122c4bb2bb183f11b9ecc5ff41dd3d92d9845c70cdb8e80704d72be2cd1eb8c940afa537bcaf323b6b5b226379a80bbc1066b9a1532f0b6be26335c203358061598fff38e1f649f4e77aeef87f4911c6d9818f420229da2b06a3fa08f9b95d18062e6faea3edf2641d8d35f473d2873911d030554484889eb81f7c7a20ef3931c5df86f321348ecf296b33ea11a0d9cb84614e340348d2c04d1b16f875c3893326548d37f0bceba34c73ca9c94882574f82ff807532b5fdfb2dd31c97a0f0c8fe314b4453e1e4e773cf4439735072e037082b4bba80f4b26d1b70f4b468556bc873087c0bc9fe87ccda14d5191a59e687f100268813c76649b6bb0ad48583d25a88fb539fd8b9bb7848206c7c1a160f388446345d9c32b083192f2efeb37cdbfd5815ae30fafdfc2f60b253422460a907da1d7715f48cbebf8be56b5539e8bf7c2733826f28d8ddaaa72ae30150be1a6debb3b5333d1c291ae6fe135ef674b410240bde6cf54f3075348622e0d4e8059b9ac58238329719ac154fc3b000ced433dc01edc52b60aa467d58bd9b16ece3663c837c2be63389ef02eafa17586a12c74e50f779c6118256b899d1e95e70bfa0798fb11412f5bef40d0ce042e7457545411e5c6826ecce962797cf644c0836cab223528a3505486b349a4bf4aa66af21eb81c1c76561490a99b05ee96112dccda8888e217caf5b715c3f5ecf0910630c49377e8f94e713820d085f283017dbfc981b90ca9d0938cb3afaf07a06824f400b423f4fe4f049d43c3441c8b26e5e233842716450714cb168d0009e5faf015c213cbc61a12a553f1826c107adb56ddc22265407446349e12d2b43cc99b2f06ccb20160bd6506bfb396397d394f4b3a5452e06f21860bae7a31a2e777339997e696e586f12526119a6c654fb59df07d145f5a55186b76473ae92abf178d03733b2d52e2457bb36a071dad4632f413e4b3b4f8293b701b83c198a981846566b2a95c04ad885c80a93dd61c0250c548c2d19fcb094140e9e53b5ff89270d10a7ec9523e9549953fe698040a8f2db0989c8050ef47b2ad4755805ef71ad42eddf40fab733e36c0b8418d69a954d325ba78c4c172d97002a06045a829f8dcb76fc01b8929cb1dedd51d577fa49dfaba1870e52d95ab0ee360d15be370571ddca3891b0c2f2f799a52b0e2a7b78efeb470b013a2bd63e8379d5f902a62b35c1fb526490a1291cecda570a8219fe2c714e7435957d7963e3214a9f36395cf87a5c5d0894237c7a893819318abb43e71f7b3a877f425086e3744f093c3cbfd1016d3d5a462f0f612c851e806781bee0ddeceadd34381be8e7fe950b27a8f4a4f691c466df170a72e2da5518814ad1a42b54960f12c32e4915575639d802efd4c3bbc44135d7f2491e4852cc9266caa4581c520a55ff197cea635a9201554ebaca6ed1689690cc60c869bbd2403985d3b1f95e51f4aa209f7f399d2c8dc32b708bd5f8abe2c460f7f5eea64851ed4da4ca826988c09644f3640d2663dc3aeafb24f35dfaff7f1a4e2a5da347cd50f6d526ff0d717a9974b4cbfbc27528f753e7951a70deccb4ca54d4241b0a0f112288b328fa2da6d14a8bce4b28658638e96febdfa0e1275a31633b3dde218ec0820dc641c30a0221fc362a1f96966eacd8ffbbccaa44e2fdffc568d3c11e4c832dbbdf9860eaa5e781e80898f2520dfe9f870d724d38370272b1f0bf5b82e01980f551d3757a90fcc07472a62c3d913913e468e255a6e0ad3f9ea09440eadbc7507383dfa98caf57892638f50b0086be2c2cce2a38fcdb190a71c300273068296f4dd112bcfe5c121d6ac597e23f765e59855306781d9bec5738883d502059dbc2640efcf53d129596120e084980017c0ce202889909104845ecd645d6022c746889d5da2ac4c615121495b7d226ebbd6681bf9a0849f67548627011a1021eeab423677f0ff43edefedcec75739bb77745019c55d97efbbb7a13687c6dadce037daec19fc309100b18c856d9ebf7dc2830fc5df202f38409c61b6d7c56d538ea8efbe5fa42fec3f96ecc5b43e869da65fe09c1328d045b1ecba5b1d93346190839fbeb81b414ce0227fbdab1710d02b647d5c1c052e4f44ef0d571050c968d30108af3ba8e50c678d3d48cf5c89bc6dfd3577d3bc56267cb811a52e8aac83d57b7e6741cb07a69c4f169c34f06df8724a03d9043ef85f0531879d3fa91fbfd6b6c1341ee1df38063fbac9a88990bda5dc01040ec70da60d34cdd2b24c9472695dc2ab95f6a0a2525abb190c19196658abb46a1b0cd538957c78d267d2ac2657b325902a32942bcb6447e9c864a1ce91eeb687573bf38fb406e61b349756714b42bd2ba3693131d951c84587bf845e50ac73fbf5994851a8306c1fb26ddb3693c9047e9e49eb93ddb66dcbb20c44927d03b76799c8b027404c1f9e00d1e4ec2ccbeabd937dd263ff78222cc7b159a753923b65472873e15033699aa66dda2626c93e395bb7539f98e8ded7b2db3dc99268622786656ce13a544dd3aee338ee1509b76974d26b62177547baaeebba4929ea66d929ac0f6d17725df78eb5f29ab689b5d69ad55a6bad477855c239e79c3da137bf9092279a1e4faf076d08c6ec7e61f6cc204584930d174f01c31d9a55dfd7818766f18f5010862a6176a9d801566224552fe5d6b9a39c64c8e2223c1b3dc8cd5ac167d993cb81cfbc135f9e27ae9c49e7a107c303daece0ec3abbceae53d548efbacf3375e74e3fd27ca46350288e6366247c0d447262f17bf965af128ab555f5a76b07adf68925e7896568124b542796efd1b866814a8c6c1d8869b0071e67a3034bf8bd1196dc0cf284328cf9b523c4d0933b672c89937aed34d8fa2e47465c56df38a6e64f97d24acdd3997cf3504effc4285798c83ce651660e7e8a4c4299c75c4aebb35168fefd64a35c6142f3ef5172fc741d148439c414a511c39fce04c7bfe3f877b233ffaccc71d82354d5c55e2384b375657e464cf5c17f9711532da67288f9e9313ffdf47d3fbd6c57d82e7e38e7e4a6455110499590aaf8324e08c1c7bc0c3ff124c65846a22a3e683b885148af9a3dac6e73cc28a4624ac904eefcbc25235d3ecf9605a2476e4a7e9fffec12aa126fe8711e79b61c6fc9af928bbe5976953dcca33b9f89506ad74ae711526d8388d24a1b88459e925ac24cc4784a121a1184e55c0ae3838e1a841da414c59706f3a4593797ef43b3741885fc1ea6196a64b90c0e607ebe451d9af4c84fe5d02d7e5115f35d00c303c9404a588ab874e9f2ead241445b8c6247af6695cda91d24f0f31a22eaa016e2c1eb1c67fc60465117622c1d21082e984b49b820092da86ce9819176b5d3eba020acd7be9d63310ccfd7f9558eaaf8955fad0e2e666b8248964ce15eb9f36968106adfdee7a57398ed5ccf625d717682304a7d6a09942b4c4cd72ea5a58950a204b125b5c453153f3553535cd2e4dd5b408af160718411366f49add9da44d796d4d2c414103dbaa88a9fd9d96ae1c70527c2ca4434fcf0abe45ba572c8cc41414a4b94952865258696f8cd049818330775cf18d151b34a66e212719798ab5facc44b4cb08264687b48dbb641f1e509364c696ce1b2e58b238660b4a50a9810926a9ed0b9c2999a78c1185014b1a5899a273c7ba7381e0d5d8e0876b919558db2a03b3f6de079dd59ed1d306156b3da4d87e04d9d92863b6bd44718ed69a3519808a34694a42d130c06ab2af078cadc29a66c98539cf6728fe135575ae9a454bcd9b3670c60b7bb6adbd711d18ea86e5c57ca514a2badb423ea0e964d71a29df5f481bbcfddf493041de5a408bd3b635a96b4b85102e6e999a1c8a867a0052923ba01125f0c6909a3886f862f621fcda843137edffd686ebea2a16ddb52b004161d335a40842a8d30c6cc5c2715caab1d9a2297e634df0f683eda535457ab8391c2d32697db03c38a6704729dac4e1cdc5ac56d708ef55d066e77e0bb5b3da0d5ee13d862ed31c8a36fe6cab5f64f385fef8c69a9526b15e39c96155c7ee5686e905bde1411010630a4b8f428308a70520a97052785a52f5e20a1075f2c21c588257290841438f0c10c8ad0c0e04c5b0f0c47d80b23286e87a8d0c78db9333686d28d654e30b7c32f8b2157b10c7b6c9a27d9a5dd2d721d46c81d45811a417f5f28a1488bdbaf218a88fe428adbaf7dda030d8828010fce1003c6829e00193f0002147200840417ad46157561a57e345f4813c40d9e811b3c5da411533b1a1d1af40add73cbecc28516ae0b226e3fec2205b7bb58e37616a1db599070bbbd00baed85106ecfc08adbdb178f9fd082859e5a1ae69dd1222c5bbb1a9d49229ccfc437b5f33728114bf85bfef2390c57c475b2e665aa6597052c214d9a41b41aa32a8d8f10d61825bb059e71cb99544e235cedde96dab6690652bb7e803a0a57d82e2eaeb8335f6c055deef7c945d005637ab6ea0310ae1e9ad555f968beb8e54d98bafc85372c61d2a459d9b57376071d485e6a1514a68f1096b88804788876ed0e9824cdb5b227462cc7d578cbef7ee217851c2f7b8836a291b8dacb5ee26a6f2870bcc5725523f2207e87880a73e010cb20b7e4221ee8d7809275e022fc8439248858b695318aab3d462c0f8a6533715173a90c456ffb364bef0ca2dd003fb03ab9f94d1a765fb759be9e56dfe0b51325c3f11225796895c65903fc0c4d56bd124eac73dce61c372897e3441f7ad478e8f187a8c759ce241327b6ab1379a9de549826e990cbe55aa2a55999aa8965aae65db24596a99a97b5648b30e6386fb9e3064142c8efcb35ab66ae9316da693e959d6fd6a50a286e972a56707b6a32f7bcd6344dd334bed65d05a5699aa669da4c0e5a4124da719c3f7329b57cc269b67ce5be900655dee078d5001077bba594ab81ede260ca41d8a12e5cad9a7b3535a2eb0aa75d137ba0b7e46f40cad5c001304a642a8d01d3e53ecea5cb438f7346641897d9e58e5dc43c50d53cc741e9713ee9713ef43eefab9b58c69c3f588f934a8f5042194e7bd2e36a8cb0d2008408a5c7d9415335cf8573d24c41619402c53a681e218427c8600744e82d6bfc08018b2c6082d03a693acefd661855cd4feee3aac66aed6b9aa6699acc35ad65440cb4e8647b29656887a8699f548788c221c246988c58aeb4932dbbcb9def18f012e871feb31e0f946498e671ac55f34f9ae4d60c1373614bd8466217f53803343fc9b0b2021853999aa644d8aa29a6fa6fd22c6e8a31498ea9c1674cb28b5a353fd7b8f3a3126139f608244cb865faf09df333421c373345383f6f0a756fc28bfad1252c79eefcb67dbe675eee0243b33631a59daf26726c0617b05821c2cc728c8f7a9ca796bbb8a006c7e6fb68be8be6dbe83ad635e342758d169f3fd2b3ca18a008a21d66f6d1843d521afb24fb687aa43deb10680d6284f048f61a687acc326d45ea80f5ccd0378b19b101c3cdb22dcbb8332dc0b819106e769dacbbfb668c8559b94a696f2bb76c2b7723fb7586121733c808a38856f61a28b9c30a159431c50d17175c5ad957d97bb26ff673057953a73cd52ab2185b9a41413488aa681025e98b55f41c17244455942953716982750961495f3410a2435c44837da714e6f2e207062d9a0213d590c0e2d26b302ec26929983e5c1166107afa351aa58684c98b51d26484c915c1644ae221f4b4f48ee00d813245e1d27b311c97a39cc7844b0f0661921e0c3cc2a520132e05a57069cc082e8d3922268932a6894befc12819e3c136b1447d65a28f11a2bf492c7bc07731429ad06968aae8cb18a14b27c81561fa247191d3d01c424f7f2a62fab078c5a5a755c484094b135215979e863a91a3a7a2247a32ba287a1c5b84f39627a48a6442aaf44a979e2231490e33552f2ec26ecc21464c7a82c9ca4e994eb9fc12c72de9ebd22514c10296903fb908dff9378d692a34b89a6530a7989f2f5a6071cb151142700acf8cb0bae083220cef8c699102930bc2993b635ab838248427379c10825e3c20841b0f3c2e687c4284592cfb819149085980616a41165b78485958f1042e3c719ec993c2bb020666a184a98aa12f0b1318d161f1f2a600e3a8789956dc128cd8563d2b9e1e2a8a3a6e35841f8d0bb92fbcc288c3810a3221ba713340a25a11d3569c900ae8aa67c5d333b46586212e59c8ad807a862a8e6a4c063f3f542c71589498004018222e5c8225e9e1c7ed38a209c315793c68e20dcd4af82d41c3126a47e84a1066494b3af098d0441926686206d90e9aa8a2a34108de6946132ed034ae797a60e08b71bb771c3de165d73b81979b3dbbc6725c165336748b4777b669c89e725cef44e47a2799eb75dcb6715a5629a5cd9393d2dc409e3741fa7537f52acf4a659ad01dee80e950e7362dabb4bbbb51b4bb3babddcc62d9e1a976777777777777770e2770d39a691bd781b533795f8cc77da08985a1100469c0b9a8a31924a6fb84389e3442b7ffc51899804cb0cb13068db909283c859ba9aba18c263393cde0a8210e5a2bad343942d47c9b864c40e15773e8084d413a7600551350b3e68e98534c8c09c8046402aa193a25c106e66e54284a697ba935b24628d6eea6b56bad599669611d79d334a4c951ca5b4f8d87086e68074cc8cf6a46bbc70bea1018d4ed06ded4e1a655ab3932ed9e4635dbd7dbbee9996c395feb14b9bfb89627763f42bbe6c01899191cdfb465e699be0a52ca4d540b4a9843394e3232dc56afe940a13a2ef5181e789c75266e337d4c88c4a01bc20e6095261d911ae554dae252f10621ec88c02438b78a26a11d257c6076430d604ceda9b25ac59c808c4f8ca79075fec4439925d70be6830f424a218a9ba2f35c48785367c22e7733ade3b8af366bea342ba3df6ea84106365e42fe669b2c89356bc70dcee465de57571b58651c3f90bef60d2ae6f46d5f8109d33f640ffd8290df554e5f67ea1a3654511c2daa228bce8b212a389230f4041cdd0c1ae672b9506005159e1552a89d17578cc16937e8181643e82eadd4603a4c495d976358b6b84c7f989b43e8019015344c10668b19b4cab9e42596440dc4d8a2854aab64170f31d18c7d5145052ea02fd850a35572121522c8000d33ae60011659a48ce00654a89146d0154b6db4501f3512855d0a84c512aecc651e8248646caa70ddf0271049783ae3f47a3a6141e59eae30e39e1e032239a1bcdc98832092980a7e5e210467c51665c06a67c5cfedce81483a2ec78fab0e5529bbaba140cb16410872b534991c54a185ab754ba5e79470e927154d04260cac6ec7996c776efa6c17b9198451afb14d7aac47bdb3e592cb620fab9e8bba87b24c7aacb1d929e889dc0c8242bdb3618f28d477a0e44dab50af7fc245ba3904f57a1d742e6a8221004a64d2638d6151871e4737423e27e484cf99381b364e85973dac6a142d54299fe36cb9e4762a30efc8614bf1e2388d2d51178788447343fb039f6c49531f63cbefa03dd263fd6761e8b1be866671aff54bb88e4de20c3dd6d79b80d047f78de338eec74b4e8597e1a832ada01a55e025d72496debb528d7aeeacb2bfb8f53f8a1096406e5d5d20f51be7c364eb5d5dd38f1f3f7efc38abf0430553c789a20f3ba94a2713bbc876cf0dbc27dbcc122201a2137b5d993b636680b91ea73ec398644aa45d6e68cc060008672c076c5c4e8895b09c31ee92c392e9c39d521c4c1515db8c4bdf9c9029c22333d4f064bbdd761d9a1eb7772fc32394bc3955599d566d73f6b81a0ae71ddf9dfb669d74e7442038f0ed3e6ff684dcb998efb8b76d2012f0f559b27022f77aeef356a221cea29e084b3a24522ccd0ac0f98df89c73eb48cf4b29d1bc5e152988842996223a63ec15d5da65c68a6a172ef2c42e97debbf1ecedbdd46f0e36b949a56ebc7e52aff7ade63737160a8fa74ca292ec1b3072b94391d2ca79941c317bb7b8c5e351bac5e313cc01e71cf71a911235cb139da46cd89e7d1329961e29518f599ac59408757a8aa5c6f432bc1c6751af34ac7a501fc332bc28eec63b6751a7f4721ed500bf81aa377bcdbd6ebb0d1200e773aa3c9cf3cd80741167e2a13a67116a8587d06f3adbf5380b84a34cadb049646ac5d22e2b4adeb48ae6220de57273fa1a7434c3a3231da7347351b2e364c2234a66465c98191d6546a9531e2f399c97e195295157e699924ca624f3393325191e2187234eaa1233ee37dc2bc7cd7033dccc0c8783c3c1e1e07070c7c1551a9b3e7de9372555ac12e5e6387513696c06e9d223bd10188438e75ed2d801700ee50a7773c3c3e58e73a6a70984b38c2fbedceca594cb65379d7b0dbd26d4b76fe20493d41cc5dd2cec91eb00bdec4d908692cb5e5f72f7668de54c36ecd10b972e5b27f5db0e4ebe84890e3678622965820ad88ef32d7b8cb24191b8ec66962b81b8559c3b6cc95d4edcd17d311a8b2333da6e99b96a1497fe547ee27ba437a826b95602429b97a86ddb72c8c452fb137ab9df0071b76f1bdf520adf8d0c42e8a402f5bfb313024c62e472dbcdcdcdcdcd39b14a08a2a46e4428dc4a69df9e5db3e5137aeb33ae5586774e556633678cd376f44867d64b4a95bff4489bbdecd084bd6419b779453810d2b25ab9ad074ab3ca7934825bce255a6bad272e212b39c1a29776a73d454a0d84a2aa940db54a8a49e724b5e47b951413eff35691f64ba9b3a72a5a35256d4729a447283d6b294dcb3a244a63b1309752971350a09f52500aa8135ea80e4a29a5d73256892aff843e13c1a534b8e547b7dcd3e5cb40564d72113dcf220370c2893964de0138f1c40c326f39c97eadf7c5ad8e5228bcd444e8bb2720ecc428a6770e267104465aa6734d84deb32155d1aeebbaee554c6208a2f483684d70492742e156ff277c8f2a357011a106425c1102c70b0f7edc5842690a305af404c6891d1491618323845a3476822658e186120990d268f55d6012ea57812a8c424163707b92a6ae22314993c83fdd12b74f69104d9f06d12aa608cbb97481a06485358cd60ac4402c0406e16cf54b7e6d5d2f51b213695285552a348992340a6a856910134218d0eba75995a9d0a057f78f8998cc4693bd4455cdb1faeac46d14dc060a435fe197a641b74f29a534a99b26354d6a9a44936895499a6ea255dca65adca65fd031a819b7a91ab7e907f360a6daf1170eb6c1345806ab3f754a18305dd78993aa626010b69e60ee9c5e42264d86506a58d22cae7b39b7c0c00344bb09fae8eb455faf1e4ad6255ed50633298c89d4c5b1b81d831e286e3fabaffa3a5522a6aa8f0221256e3f71bbbe66909ead9e91e121263923f2125346f4ba642e3a7d38064cdc66a0a96a206617bdb443f2845e5e624e86e20d0e422f8b4094214fd2871e81c812d22722103db6e9a556b5ac3ecb7ec2b2be5e1998a429cd4e5fd3e719974f361f7aec03f1d59a9932cafcbe3e9032d78228292332100dbafdeab17f188892f5a755fd2a8af52585b0cc82b034abac42e5137ab72c8a1eb99f8050e62ffa027a8d715f976f16446586fa1c1786a849cebc5f5f949c11b9ac8adb9916332fb32f6e67639831d5b89db54113722f4ec688fef4ac46aeafdb5694a24862e23695dbd4eb0175b959b363aac8c86cb01c5bd1a5f71a465d2f7c5e8bc2245f10299556d1fb88f4e536c4a5f73cfe5eb0455eed576972badb2e1a381725b59ccce5ba34c7cb4ac5a4e3ad2d652285b511a67c3c95a42f8f9714e6e3f44c5578ff4c9f9dd323e7396de2d2ad8939847efb3bc84555ab71fc51a8304cb5514a05b1e47808fd8e973e2c91b70d67107a1e96c6a5449f33fef427c8cb55399647f1785f8577a6c4fd1851aa15ae01d10711cbd5ea43c4721c7221627921072202f9788b9950ac85d51021216989885a98accc6e47afc5a8e791cfb4a5175e6a4ba4cfa78f98015118c785210cfc5a587d1ff249857c52209f2c04b9c7b244c4251311cb8a5132136a153d0b2f39202f43212f51434e4dda92b6a42dc5f002cf919d98989898181c1c70c42a517cfec2fb5993170ee54afbf8bcf0eeebc385e3b8f0024db3789c52574d0eff4c9fcb60af02fd397dce22f4670ea13fada014aa94c25129fca1142a65402d88e56ab512cb711c2296bf10b144a180886518b220a23e9bcc84503ec42f3c84de268858f2b03d8e636b8f1910253324aaa25a8caa78000179c9b1f01594542faf2d6d59cad58f97e37dbc44e564424259f6e55220628c85d80a624c252e515203ca9028d9415b162ea27119c57235fe10cbf12a88e58150404b261e62b665b9f45e0fab730d88926da40169402854a86399ca1ce9dbf2a549dcc8cc9ccacc340c8797d0e73dacd81c1786461a5010172ed2b0d56a1c7f144ae625a771b954b40c83f5e951329f0dbbb48d28a901756b4046ad0119b50664a401519f933e2267e432fc12358eab9591908b0115fa88d6ce1e65b0b7e0a4574225d1d074ce678f38e48855a2c4d04d7c0ee54a16430ce2a42a1e62c9c3edfbfc08ed0175fbf2b0e593edf671c42a5160b80f1318441f114a9193e08a068cdcfa208af47cbee78dda1276531825b90d18b9fd235dc442e1d60b2390d2f239b7a09020ca077c6ea425a5e553e5033e6fb14a08a2bcf028ad092ef111a174eb85176ccf6f6c99dd9458d2d8d81fb8c696343bec0f3a280ce8d2f01456d0c2cacfa42dd1dc522b19564e29dc27dbcdc42f4b386f8fa5ffb622221d8405ded409f2e235f869c42441b1a36855bfc53e2772258da2eb57b6ca1266e72f577b495fa6bbdd13cbd32dfb75bb83c29ff0357db2ec35abe7b2773133ba5c7a67cc8cd8f566104f2c9f6c37bb499c3f30c7af1ff8961d4423a56fad30536742fca9b3e2a112681d6035e4b6581941fc656a99d7b19d2d02cc16bffb9848d398a8c90f56c485b95c3af485bab65c0ac6b87cb95cbaa092b2a14e8c4bef99b22ccb4024a925f30498ad291e80450e0219c8c70561c94445cdcabc8ef28f0c42284d9e4aa06f60d1a9b489f76b19e6d440241910f3331049166611962b9f1684e58f6455ccc41401666b4a7f0a77d3bb67a16522366a1bbb746a69430759585902136ad09a52d8c28a1b5eb888b95cad49b42a57ff5a06195e6f75d8a4092599e8f43e4b84f5d2da5c9352aa5ce172b99220822360d1aa356422e3831e4c3185292e177ffea4850d966e9ffb1a97d7d5ce278fbfaaa44170b34beb0c52c59269f31b0527ed61b352366499184e6ee338e678dbb66dcb4cd9b671db9d6c5cb79d13e746b7d3eb64eb998925376fb2b355a20dd9b39492ea7239b9d90f328eef4db332ba514a29a594524a29dd324a699665599d95bb76aa0dc912f6b37e1982dadf349bdd5a6b92e7a19a95b2a1b3b798b281d2be94dbe29cc0e42dc219b341d2dd5e7fc3f0fbe6afa6a49580a0ccf8de4f70fb91d6b46c8add4cc86896d511d6454030328e2ab53640628fbf9b267b260c357cb12e6e82ccf0220d2dab311f1cddfebcae953268437509a1443b15e71cebb6d56b96ed149d707c83d2accc1b54adb5d6ea856b4b174454314480029006818c32ce826a03308c224e407908234a701d1161841011620c8da145511194ca902209a450c9765bb6044521d3d21d512ddd1a9996fefc5265054a323082073840020c3008b797e8028d9f2354f0c2a2d51195a1041a4cc14204430c2184328478a1e24a9614bc206603d711aacb8d1fd50740f5e6c8162ec8c8b6177858626a90184230515747713a33d470034786954ac6d06dd4513477c6c800baf5ce1819546e8942f2be78939182b830bfeb2d84b63b2908745f5a2c48888a0329036f52c8df2801a60fb64081953496a8410f84d0e20dbc49e1c6514ecbb89e1cc7715c4f31e5076edf05b5d63a4190832aa2104619563c610d16d010428d2b50c0a203257a441078de38e3892e6c60861b520ce00446444105558af0d2258c2d5e43f8010a9ea0c6115fbc8855a0d182130c210d27e8400b077919cd6acdde53ac9496a0b99ba39476b58da8502ceaabeff76bdbcf499f037284908fd0ee81be6e770dc3ca7df445b3a8a156b57a352265845fbd755e6d66cd9aa6ddda5d4696bd72999024c25abf6665f4bb1ca5dfbd34ade2673857758f34b5375464b3d24a51fc8c99451a8db2117e2814a529337186b5e7f6602aa22205aa3a7e90cdca4760a106b5395ab50a223162d455bb26969a66cbbe9e966959adb566307a844cd3c4fea493ce1b5416d2d05b7e618f773ee40eeff32851d201eff32c51b201ef1371519201ef1379515287f789c028b980f78900515201ef1309a26402de272244c926ef1319a22402de274244c903bc4fa4889206789f88112599bc4f2446c902bc4fe4889204789f08122573789f48122597bc4f44899203789fc8122505f03ecb45491cde67bd2879c3fb2c182595bccf02a264002869c3fb2c214ad6f03e6b889234bccf22a26492f759459444f23ecb889233bccf8a511200efb38e28d97a9f8544c9fb3e2b899247de672951d2c8fbac254acaf03ee9a2640cef932f4ac2f03e09a36491f749204afabc4f0651f285f74921728892e4fb24112559ef93459424f23e6944499ef7cefbe411255d78b7f03e9944c9d5fba4122587bc4f2e5152082573c42943f01bad2be049c0b574deef795132c8fb3d304a0279bf0788922cbcdf1344c915deef11a2a4eafd9e214a8eeff71051f2c7fb3d459454e1fd1e234afa78bf274649fbbe8d159b217ae9b91ea7e229a5b453e04fcfc2106899d8fc2294179e2083a396cda965927a8e086526f1a095bac71bb03bb19da31da49da41da59d251e17cf8b07c603c413c423c433c443c453c463c413e339e241e249e251e25922e222f222022302442488881091212244448a881811891139228244248988129125968bf562c15840ac2096106b8845c42a6219b162ac2316122b89a5c45a225de48b849140641029440e914464116944c6c82312894c2295c8a59e570fac07a827a847a867a887a8a7a8c7a8275625e73af6c5b572c41e1755f571aed7df06ac0dd10fd6a64803d6c6c8076b13cb80b539c280b541ba80b549eac1da2839b1364ba54db92c60532f1e6c0a56019b02a2804d054dc0a6842460534311b0292208d854d1036cca68079b8a39c0a68e1a6053480cb0a9241d6c4a690136b5a4007be34a80bd7935b1373004d81ba003d89b2003d81b2126f666a800f6868800f6a628077b63b4c4dec406606f8e04606f9070b0374937d81b2525f666290016c76583c579d560716034581ca02416270889c5119ac1e20c01c0e210b52c4ed1b53846472c4ecc88c53992c1e220c560719260b038452c8e8fe5f182e5d16379909607cbf2206279f0581e3b96071155f55db03c8aa8aadf82e5614455fd95e511a3aafe10cbe388aafa422c8f20960710cb83059be35ac1e6a86c0e8caafaa3cdf9617382a8aaaf82cdf1617386a6e47c88205c2d7b1d3be78e7712ac5a73b6f09d0711261150e491b4138bcd161a87159b3ee0fb3daee98373fb46b484a5ce0bb43f5e33488f3640d646b43630aab209cae17adde6918492c71176bc942f4cb97dce962fc06e6b79a25ca15ce1e7f66b8688c25295a3da7955435aea5ace735236c524f59ce7a472725236e7399f200d649091654f80c14ba88b30f5f2b4745a3229f51c51954949b44f99623ba6582a464638554394dc41a1b81d364ad5d00e1b335b42da1f8bf6fd03eb517cc9d194689fe21f578f3da5c7061580f39c77a00278dc464c31b19942139688b56c2ea59563534c72a04c34a268e55c4acb9be122a49447d20c4246486f398b887a8e7adc89d9f4b886581196395c3b2e4afe392fb9540e570ed7bdb813cb1167efc47a2726ee7c99310ae7a5cfe1615da616912497a935a9aa24a2747b27766f2caa552d863dce968d0b30d889ed205152650591185680b0842ab8b4fa3b4994f4c145050ca4d872c50e9cd0eaefc4768ec280c140ca8e6e82048f6b40460e28fd8b83497983838cf0eabd18cb48c0f0479aefe926b1e4aec643b4eb2354fbe7c5d591bee1a1a9aae7b855d29d465a862e27de988ef3238df315256b666870bc136b2c942b486ade36a9c76ad4e3bc5f7a1ce94bfacae8abc91d481446a59e32d1ac206aa5eab8d50c27b810157119556a56d947b7be6622c8b6dc4a297dd117bd95b6926d30bdb4e395fe181196ade4453b8bc27aa42fad959ae4567d2f75c85154ca768b660b7d5172b50ab7ced263159b88be72bc6c221bcbe313cc81c79b5bde677958ef293b5b5f9594cd3dfe78589b56ea3bac0e4d5bcd5c070f553bc3564c55fd1628306a0dead00c52c90b507185172c9cb8c1088a68d5d750bfeaa9c7b13796652c17f11623e6c231e097f4e5d6d7224228fd2bd9fb8f12829a93805b3562aa859f7a36aae7a1aabd76a9ae1b6af214e472b984d872eb998da25b3f2b1bc58c6add34fad3ac6e8cdb6d1bc78561bd768f873a6eeb590dddbadab8edd218caa57931ff4c26935239c66204986c1cdd324b7a985b9f19018cc988308bf4fd6c3847d363ec3747d341cbcdd174cfd6399ade4b26d3cb3635534f08a87e26cb5f0571e0fb8961b74d563659df64795cc83ebc2fdb8838d3d2f461139b1e36eb27539925cd19a413cb275b786a6a7f60ce474cad1e05ed753e11080ceccbabd6cec8185c96443ba56c89d6ba44c9e6b83044a1b2244c92664ec8deb8552b8176825bb515dcaa3971ebe7ac722ba723fa106fa4eb30b556fa732b9df289e557a3892e315d1a7bac1cb3a597cb7adccbac8ffbf8045751669186cd21f59d77ca74c6930a1e53c9a8649ee9cbac567b9a2e6549354bca924ca24e57247b123ba9eae4c19023cbb21c346f1f76fab0d961b03c6506e127b14ba88a5fc436e991bbd3c783f1929a642f41993e309c5fc324939cbf64b28a9c67510b975597bd2822eb1255a900c67c45ddd9f4f7b8ca129a5e5e67ecb15aef9d1744455c84a9f0905a04837de145accf935824ef9652b52eb51746c41d70bccca1a1a1c1798e1c38383aa724735cc83d3db716f1d26a1c8baa495c026bf62553cac0b46b823e5e46cee757b34e343e5ed2ab23e487ea0ca6eb29613adbaf76b5202c394cbbc47ec970232294189ee21b39b7685ed6a56cc966607aac176233251d9b7d01c3e395c77d8865739624d346d84658d2a525ba347d3ad00e798c5dfdb32d1c8935721fdbf3172cc943b4c4b52e7db9270af928c4c76b76afb37c3b4e26acf9a7e9b77d3d4b2f0591047905337dbad7a556d5735c18d6fa5a44c923afaf46cdf22839c36b4fcf6a7573c6f1e6fce6e4bc0cef4beee67c527206919b2ad3730010722d714ed315e73479a61f3185b92613ca8432994c47cd499a5e8677079b4024a667a69bc024f47a15cc815d542544c8c74389c1c8a31809f2236de4743262c48811235162789456b762c84186c77030d3877c3d2da2a491d753a366f5b05ef6adf9e121f535445e9ee7256ac7059837e4f5744ab356af2d4c1f9d8bdbeff6e3652bf1907a9b1bb1355015bfc79216ca0cc28fc19e2cbfde3f5c240a935631eb878808e311613b30362edf857a1ddb820d3fcbd529cd12227aefc4767962a5e9560a225eb440c4f22c88256a05b10c5b2536510f0cbb9dd8534691c3fc68a5ec1f88c4c792ab599d0f91ba7a9ca2524148976898aeec81f98b52b338ccd2ecc41e18764d3fd22b20a092cd044c105dd3bded53c1eadc87cdde43491d5ac5af610e06d583a2d1b1df0e5456e35140988a0c404c224f8ff50421bf04b2d4994ea74e86eedeabf7fa0c4cb6d475412c5d4f861e3d7e84f6b0e1455baeac2dc7fadb1f98e3617fe07b634b9a942d399b259afa1a5b5226dc7a545dfad54197eaa54962c2ad9f09652caac7eb8cdd263ed96eb89b80308697345dd79dd8721cbf2ef72f88a5fb8965f71ef8754d0f62696bd8ed4e56037c8bc2a466955a123cf09f8fe960966412b919640339068f5f468c183162c4880c4664e0570c4f364de98546024617ddda3d83c03e7f91dca4307bedb169f896a88c4483ba2a318b70161e52ab0e0d3755cc379c25b37d1946c906e29810fd185629a51d7df71eebd86335d9668261d513c771bc29439ac94a01c143ea7bac291cf540a0c21307a1bcf0882ccb32304910d7e32617039a9464cadd27156bc47f6c96f75ae6dcb28180b2a07058936d1a71bba67b1cb7713dd62ee3328ebd9e26d3ec91d2a09d5bd22c167f142ae41aa8559dd840d3875fcf794a749d8c4c4dcd988290a6c9ced64083ba69963dbd3bf87a9dd99a4fb0c966ea616711ce3287d467282e6532994c99dd8eb2dd5357c7ee3081f9cd393e495d9bed59c67d99d5ee35e9318ab0bbcd996b6ca99344c7a27a449d7b29e5b278be1ef88120921e0741ef4b9a559b984c5f73f1fc90671146e221f55c438f75498f751addb0a4c76a7a8ded711d6bbf59beddc424273055f57507242ef28487d4d72a7e133d0a22e9c419f0062957bce8ed404920a8aaf20f0fe11f99c3a8701c67534aba9a7703e85ec3a58e3a679d706716534a6a4427dcf9ddcbee9ce824256e9bc8404c056519d689369eedcbd6f36cf7b01d245adb429e8e8fc11ab1684b162f49cd48cc04133a352fbddb79a9e39c897848e53eaf492c4d672098c93213a820a622321338b6856c47d163f528c84db0890aff21fed86c0735132f99d733d10c4aac11cbae9f0d3483644192410a4b66246f96e18c99517459b833664612d7d3be1e96c7459b736b7f207e98d6f32559388e157c14d9ae8305166c3755a621a5ea9aeec39abe82355d654d6f919b414c62d9c38a819a653ab79541dc1a9b7b1c8e6caab2e3f8bcece50b676f1042d3391049f61bbecf64e9d5a6f76f822653786f6eb22925a8eca8cf9b030e00f58c99b974d2c3ea96ddbddf9cb39c98529289a897a8b3e8c413734024a8f36dd0245670ce20d96d2c0826d19edd64e9f5b61e56d749ca864f28e3537c28525a3f1ee5c7b9f5c376ef165b25dd3760e4f28f1f3f7e44191fa5d5adf1600e2a7c3c1b713d67993ede2bce4b2ef5b26f1844677df81c4bc3c386a39d1e50b336d14977ae7e7210256f9ab1f02baae2882cf28dc8b01eeb538c054c82ba771b5b83baf78a7aad793d03799e656f9a02f4d3963d77da506fe95d1e71824926067aae93ef31f76a287bee3c17f6b8dd747aed4ba66ae2c0b9f3333eeebccc7c789a67f17bdbef386cc95dcff31eda932d7f5c4ffc816f8c4863513df45c2abe860c881eb3d99fd39ac452e7ced93bdcb912224658b22b0b09dea2795632161a3b9b48c95d8ecb9e95402e77af5686d22c76b52a3bc7856179d3e73b65a22fd9c56239da1a747ce0ac78c0492ea1aaec939bf334ab2fc2f9920625def498f11429b3a5619ececa73be4757b3a6347883943be9d94bf5a7c9946a51c978fbadb384923bb26c87a883e372cc6cc2c0c343b973afd59661a53cb5d6c432884bebbfacd6ee8686e3388ee332283dcecf581b45f74b329a1ee7bfb059f445325556d9b337c9ae43ac075f786e76eff4f698ed6fdbb26769ba3c73d9e63255652f790669910a310ab303d159352bcb7e43b3fad997641955c22b7db84e7808ed5bb706454a4bbbe7696f9b89fd5a338daf6513e501b9d9119055102d256b6a0577b73855384138efd139e79c993735ee180a26a197e79c2514ba036f3217e1348305a81a0669f8a5f554670df3d21aced92c5adf9beb361012d09d5aee8cb5d1e57a415e2127f5c8a9b0023eba652d398945f6d223f984d0b58577ea9d7aa7db3d26847cba7d9e76de52600a14958cf7fd6fdfb679db46edd863edb1b357d3b75f67e34a150ef45e8a3b7fda40ef89294e54325eee9c38721cd771dd970c9d6de3ca026f52e00b1bf7eb32052e4c086b8fdc953277d6dc3b46985dc812c2f2574bc59cdf3d15e3fd94e334147bd77e9db1331007fe97f2de9f36f03f31e5894ac6ebdd1347af037c3d11488f2d9aec122bce4b46985d47ab5e7853a764d75774092567bd1622cd24f0aa2a42fa1525e79c7345275ddd1b84dee45457051496fcc3494c9fc97cab6d20d796b579d6ecf566975f94a49452dadc10eae332cee5154f9059a40938c91eaa629feeb9fcc9db575f84fc6dfb2d236c6275e8b19fcd5aafb16e9072797636555f72cc71b5be56e69eb0e4ae766eed0d228181e76aa7da3dea65b369c699c6af97ff6b1496f5cbd5bc5ab9ceae9ad8650a56aecf9d5da4a0743dfe78787aecff305fa6b89e5954325e3e8bab1ed9e3415872a9f9724ccd8f577b4d4d51c9783571d5637fbcf596dc7d527fa8f7729517b764087ea3656211488fdca455fd9edc3cfa69046199da3e3fde793eba3dda717e461e87b1a58c950bfa63172c45b3fcb815102e349ec068285d66c097183698cb19453db40951a033f8053950ca6239f872f9256043075174f1e6d308c3697cb97cee8934bc5ce697384f809fb0ef09d100070d446990a50209d1004b65a308ef05191b534026b4ec8d18136cda1338ce8dec8b1850e892c235d858a3cb11bc35b67c62d800e4b4b8fcd81a587c6b0871376b7409ca96e0876b007589e2562882c233be54993380c4106286c351049a9a63e6f2d71081eb8b1d5cbdd38c2d5decd0c284262b6a4c556c6a8a8d2df574a7195b8eb8d9e2bad9c1971d2011ed20c6e582c1f9b42de017dc1078846ae4107d07423b086226760014da8a86c8ede005d3d101e74307496018cf0d15be2e8daf8d99367e7c5d1afc8f8c2fa6032eb833867480850d2c3a68c175e9b833a6831777e47d612e062bb0010612158dd3ac80850fc823c388a32d574b1140be232c47568e9848c1ed1b11ce581a5d382842500a42740c61c8eacbc1d26521be31c313e245f84de13a7add194b83e896e19d47ae3b636a28dd12a54610d4e872f95f04626820c5d038ba338686d12db3d79dcf72670c0d2b77a211050bae388128c219fb8117208ce007602eff33e2cd2aa1d6a5080d5809030ce13f1d201f1aae5bfea68bd277c6d02d7b38073477c6c4309a398c1105255a00852b5b7869e96022ac8192a92550aed07afa1639aaa224b7b88aae2da945c514ea54c52dfc14f55015936e4191d21aa98abfa4c9911ab8590a9dc586d8c842c199410bfc67a1b011c048b145ebbbf7f197d2ea148d898981124365ec89eba049c69db11860b9554e628f17a38344386f5f24ee09624ca375fa67a1788f79eda1dbffa18290e638ce48f3fb448612fe93c249b683261761d94141321d14ead0220ced6cc91c0ca22478997714f0df1b1453f5dfc37f8f2223a6aa98ca01bccc997c0f0f9607e0a125a825020c4c2fb74a2846d981aa7a0955f56719b05678999733260419d107a418a614b540d1888da6d26c4d90db98412c94efe0bd1b6031420b14afe88805c15398b1822fad4fbca2c30aa3366ebfdb983e93db2ae529a6687b629b449137511333b16c2cb7a90f397e5c9f9d2d2f8825d444df633e732950aed00f4a8c14d78baa38458fe333457f80f038c428e0c3cf88e16c71100321e92c119ec4548d11537404a098aa220fb598484c5131f5fd00e063c42857be833fc0f798c77c9fad232dc508227620948497a1376ad06a1c63bc8009274cd1041743b47ce86005615021c410a020a2d552b05842164b683c81684b125afdcfcec4202c5989955a296b2b596e63992daad35deb98325b86a3e0ee20223128afca33397091dd5a75469729a59467a55d2a63e79db132a72012993748c316e408e1e9f331e7afb8d5aa3f066908dfdd649d50f1267152ee9106df163cf5819235473d8758723b4e232a1d87b8849235228ae36677844e3049f86ec63493dd0b3b4ed1cf6bfac9a61ea39dfc222cf98bcb0745203ed187272d63809fa9c464890b6acc585867a6846588ba41f1e7d156ead6ee43932c20a6644a318e42a35c25bb063822e8db70d0ee030f4b134111dc7a293e4c1903fc7480ef01745c8718848eeb780c538aae57ad9218a614dd18a61451b18a2b862945ad20748857fa572abd0e7ba55f7f19b4413b02c4617e2ab9f83ef081e0e3b022c21bb745263d2e4125219cef640cf0d3ac79ced1313fded4296718b03b3f73b872e478f51846f89ba39f634ab3aad4bc9fa33ff0d452af0bc2cd728080c419413958f8222c7762497a7adaf4fa89354336441687e808ad56d412044ab7ecde4306bb608e6da10644889137086bde7d27d6acf2b4746bac4d9656f5391ba22ec2df9aa11b4cc1851056b690f1002028788006195698800a4f8714a1ce4be74707a643a5599e9d399c03766d4e07da2a37a7b64a0e98a51e7b24f5d83dbe782d822a9a009ac2e9039fa3e3ad6931aa21158e7ba045fdb33baec3b290e3a590580e175581df61bf9f4ea7d3ccede9ce3cc7dae8b0aaebba650ed74948172147a290588f39b1a39da4f75543a14cc8331f28048b102c4288c09c83e2417b100441f0ab54248296222d94cc39907bfdd9d8f986124c589a685401ee74d93902c59d980ac682b0cce1da89d1406f95dfc615c61cfb25bd262145ad7acdd0f401bf82ebb33d42c298544851f7696c88b80b6950dc111263c56690064518254531270c29ac4752f748da12762f77625df6baa5cd0a412bfc582184e584c4aa15b7cce1ca84b8650ed78e979da5dbd73cdb5755a41aeab1b1f4d8f4deabc87d5684f352d516559639390c2592306ad95c4aeb48f708130e89a295ba94d64e6ce7a859f42052b37088ac85bf602fb80bde82b5601930166c0557c1573806fc02aec2523014fc043bc14df012ac04af8093602418057c023e828d6022b8043c021e8285901225041ff000077a3ce9001020801204bb58043c8549c02f2e827fd8040ce3143015560103310b98096e0107b10b380a9e828518066c85a9e021be82b170164cc45cbcdf734449cef27e0f12257906eff72451928bdeef51a22483f17ecfd20bae56d0f491790b89ddfef60ec60929a2aa267b24ddc371bac71f0ff07ad94763633e634fd4c65c6fa35bf336b72d263c0e4b73d0ce9cb5d81fce60587bd16ee05c647d9cbfb01a38cfc0aa70f6c2fa70ce627f9cbbb019387361c7f31616036722ab3a6b612f70cec2ae709681ede18cc5b270c6c23a395f61819cadb0e579c8063957612d70a6c2e69caf581ece56ac90730c6c05ce30b043ce2fb014380bd9d5b98a9dc0790adbc2590a2b817314d685331436026717d89df3131602e720cb7376c23ee0dc024be4dc84dde1cc84659d97b00e38b3c09267256c03ce40b6e7bc02cb80b30aec0be724ac0e672ad6e78c845dc03905b6c819055601679885e17c029b80b3096c0ce7236c93f38f95e16c8445c0b9086be44c843dc0f9658f9c4b600d702681bde7115826e729b6751ec216e02c020b80b310960067979de1526c0e0fc2227914bbe4506c9287c00ee020b034fc03560007c2d6700f581cde016bc339606ff8131b80f7b04a2e6359b8d49ee8476df85b7bd662377006c3fa38175915ce5fd81fce33b03fce5e580d9cb3d8f1dc85f5e1cc85559db7b0193813d915ce5a580c9cb3b02c9c65602f70c662819cb1b03d9cafb041ce565827e7219b73aec296672aac90f3156b81b3153be41c03cbc3190676757e81adc059c8b670ae6229709ec2ba7096c24ee01c85dd39436125707681e5393f6123700eb244ce4e58089c5b6059e726ec03ce4c58f2bc84dde1cc02db7356c23ae00c645f38afc036e0ac02eb734ec232e04cc5163923617538a7c0c27046815dc0196663389fc02ae06c022bc3f9089b80f38f357236c2363917618f9c89b00838bfec3d97c01ee04c02db3a8fc01ae03cc502e03c84657216819de12c842dc0d965915c8a25c083b0491ec5e67028968687c02e39086c0dff801dc081b036dc035600ef800dc0396071f813abe43dec0d97b1aaaba2d245a8021255650fa3db9f41222c7762a50a76755e2b00b5aa2fa428a987f87e0fa41e4794fc4b2145b747d2eda01e7b05d70a535678d95871dd0dfc876be0efef28513203efef2c511203eff3b8287981f7795e94ece17d1e18259dbccf0344c9f27d9e204a5ae07d1e214af2f03ecf10252bf03e0f112529f03e4f112527f03e8f112525f03e4f8c9211789fe7889210789f0789920f789f278906ab21a8a40cb586594c95a9111100000008008314002028140c0784a2b1502820a9b2643b14800d8ab250705499085a9263ca186208000200000000000404a34103ad9cab2ab24c6020ea6d7a2f35c769b9ec2bc8072869d2ce1431415c92a27a98ad2ec8c34a73988b66580cae8f803ce74ab577e5ae22b344b802a0759a07f4806106592a83a393ebb617650da8cfca3705aa6c3dba4d93333430d94697d6d9177c728e8e3200ae391d38323e2305396ca4e24250a6a1431b6c3cd93e800020694cbe2b7d995ce1283ef79e29aee52a7aa41f4b6d2ba7110f7d5713a741fabe6c0996340bcee56dd760d6806d7d50a1695ba39458b9eee50e04e5c0cdc10058237c51d7f4738721c650f65c202eb2a1ddaf79d3930f8e2df964d290867748664ba6e61aabe6c1f5bb5bfa14899283fc198ed29717d35c4707a27d861efdc4360c5f7599b1906254323d7892c3b3bed4a0f5a220d4ab571060d4e322c815688c1d712cfe416023d5ee8769ec0bc1393b1749b913ab1c38fd897bdd859cc167a4ae56ce703e04b660f0162d60b7d862ec72d52d0b44b348d5b7507ef9d3c5869cfc7b2d07cbf19e67dde622777a5888074a2eca9b8282209a47af7f8d892c7e2f727906c58a7d8e842662d3a310b9acc06289516759dc406692c7afbb4d29b9018f1c2624952b5fd7b13664a5f424d1e01eca98e5eb6ff4743bf3f5c1128f61a256645d9a563469207ca5566531bce65fee7ba3468f8f8bbfbb65dfbba47255cda462cb6222f731dc69f2ba430a240c9b12f0d4d6b68a2b95839da36de1940c9bef3c786e1712fedf7360f8eab243d80851cb60609d932a552815bc6763049dea0ac3997f243e006cea759075a053ced5df299e2383620b67e488dd154c35108dd95de2f81d7b738a9be72e3c667caa369c09506de88d4fdbc719bc0033c4e52058cbaee1acafd2b614236e3ebb141741c14d0152200ab0ef770e736518f4b09bc30f301d859b947fb04f028f54634c0c79541e28ded132b8c6f18ab0ea5649f26ca7f1c886e48b8a8a051f3fb22398ace18e989e26e8eda86013d8331640c8acbfb2804c9ea7a438b8b55a4fbbbffae24a6632f56072a8976b4b6302b69b69f101f1ca7dcb7fc0acb04647b5d50c648398fc14e1e1c2887de739ec33ebe3c6a269e143b3a689615d669e35916641f33fdc9680c0613bc6991eb80cf841aa141a437388b0d51c6689204667c20df7720cb0657e4ca9e65015b4af5bc1e0150d5df7a444cc7a2b55b9bb378d345609fdb8ed831ecd83b9031c1c53f7a65dfe6a50ef392eea0a955fe74d6bfa94ddbc49a191301b13c02c7d7360620210c4ab034ccc395d788cc60b35a150beb8511056c91d8bb31166c177d3feb926bef567ce114a61dcbb02e392bdf0020c8f0cf3523c768c6722d965aebb8a6a6efad42a9820c43e19608a97e46ff38aa4a4c64447c4ea3e668d5e7d84194277173ed386650e670d81a8ca3df352bc469414a4a919362bc37d602904ba1580e5618ca159fc7ae53e951588daa10a1473836136feeed46c67eb50a1e09a25a1a6e0fb3be4b876790b8a838e6bd5eb48b422149f4621d539f3cdb57019ff65c5ef50bf23d37e17ccf9347bdf4b826b7e0dbebe09c3505a3bdbde70dba9a588dc7089e556614d81a3021bf159527036f1395ddcc539641b4541183304d52ede412e29d3029b8f17365e9bd401fa03f4042f994eb11089ef1d1d6f25e92b0389ddc59c9f91f0c97ace373cf572f2bf2d62473c3955fc80cf27b8a2fc0b4d79904321f50a364e609b0d1e33dc2e4fb3fa1416fc4c66c3b262c3c0697c80c5ab66b45849276add0ac6b3d4242fe3437a014d3048c25ca8ad61f8c8ae7bb2369f9f0a22f988749b4deb688596532a489253e16be23d88d77b915c5bc31c1f490ee98897a4704abbb78905a2e83926bf9a2053eee97de9dc4897b5b8b6001e44262830d8c9838bb008d55db85d0e3e35ad163c16a7d9f67da5cc7e4fc8793a6de0a8b6e8cf6325090a64b09074eec143e9d58212747c21030234e963ae1a62081c8d7bb239ad32fde8fbd5536f94ba9ebe74fa1cc958de137701387ad2a423afa47be0d30f5e7d66a856cfe8daee3de11c4a132179b777a53b7634bc2061a0ba92fd3d352b52487aea663473a491527fb15af13b47dc936ad552119fff1e30b9dc158870d1651292ece9849f3579da6b89f8f3b86e4d5fa1c51056ab1314310ec47ce04978da46a2e2e55fa767b3fd499b19afad6ae74d8dd0c0161f7972e4379c13a0d715206f407e5f94d995df0999c564510bad4f228a7f62f2892636085e9b96c9d277839a2eafcd2b8edafd13de22bff2696b2b210c74b0c43f4ab42e4bf68ed51a99782b688f1e08f8d698c85f2a9abd2a6700cb956953ff2316708cd730a80b4372d213bdbb45456ec92d8cf3c1639bf64dcc49a76512b3089f10baa456b22039676383a91386bddea8221d12d90bb8c64a050dacf7e29bd18731b5b45ce4c2a2d876b2a9bc4c218617061e956cbda015e8acc4d7626d021af2f7c3110d5c43f9863f390821e7a5a068ea5a7a757ab7de0e8ab9f4378b2c095f1eda3c1bafd3a0f46fa4961401bd50bf891395801d6e43c310cde64183a2388db67beafcbfc00fc4279fb3af8db56c12a3443ceb393f65621dae00b41f32c87fcfbc7c725479a56d3b8b37d231a9bbe85590be12951c1aaf55a9a00e2b345f7f94292dba16ee547602aef63a0e1fe800b40912a45ddc8891b751f637a4d4bb57d0354677127e32e8825b271dde90083c98eaa435667218f003ba889dccb5485cf08d6a7390c9a5ef0af5d27e5e73a0771f55201611ee3132649d8aa081a4fc0cc9a2351d641001e779159d129495806b133beddeb75b9b72f44788191c743449c5b2ee98d6bad088427940409f1e04a5fcad8697272fcd5bf4c0f5e09e51de80e159a953905dae7d4966faabc1e85f043bd55029166c20de0497247105ceb9f70864a6f0a425a4bdd32494b2083450f7be969ea6007bd4bfe543d0b7d7a2b03ab7b1def861c6c6ce1bfffc51d13f2402b230233b5635897c6989d1af86e09461fe7189607da75904ed5657b71afaee777a7f516727a029ca4249239cea847c883ea0191b2ee69141036f8d60ea16a88b69825b7fe48b64e82f63c623bfa7c92146093050b4e3fcd0b0fc74f2d5ab17849de54717921435482b38e72f5deb07a9d8fb03e3ad312082a2946db08cea02ce2b587274e91874fc22b0274b3fc62c20949e4417ea0dfffb3f1189763f732b3a5ddce8bb043ee76437dd088c5b63e43b84907599b4d764ee7a66ad4b4681eddd62f370a7c8cf08d30b338331c70bc4452dba8849072345ce466f61fb751e5a17518fe8a5ac4152be18e8136542954e5f3517ebba8a106b38924270f07dca77d77ef344b47145a7363b2456cf14e0e88e4aca036005d0dbc9284fc3ffa2cd5605597950a2710a602dc57428fda53feeebdf0bf75742f7228fdb269efcf8ee666b7bea6561571a2461f46fdfe5b421008d52031c7f87e4ded41a067a7c79b4a1d1d11128097658b7dcbd6a7964d02d01e409c4ae43281aac09809a44b0228dcfa2309e843e8d46d554478e9d901ffd134cbe47023a1b186f47f886ff975c8cd32a60b4062c84466a3af89faff4dc1ad1d03a77a089c1a3fb49e767700d028c4af719651cd83e4376baf50c0301cecadbd5aea660bdad6cee8c417d75b9c9f20c0d73ab6b0b2d58ababa426539885c0752dc7b08a01c8582ad80652e29a65004e7cdeac2c0d7fb3a3bce93f8edf12662ddd031dcf68ad4127245675a73b61b3d23117f4f37e3aa1f853f6f29d1f5f897b3408fa0e8f4c2832e64b81b95e8086850d2b8b2feb37133c49afa9fc6ca2a25133505bc01401639e7dfb134975e59df1acadc6142233387a6c6db3f3728a001e878b37299f64f07a55455e86a3eb77b772353bbf3877fe5d5a86c373aa0a3ec432d40819e7f4a6edb973c40cd6cdf42981540dafdc70fe80c0f64af76d2207d08a7b544962996d77fa5b65a0d6746bd93acfd1c8395376a73ea94fa64beb377e32c8ea3a23668b304f6de2067680323c2a0400310f29015c3dbef93d7e41dba67c5ad67eb254bebbbdd426965d47cb521921d65f819d69e304c47e556363c3263a774007d8ef31c1e508c73d970221c7393047b2374e130fb8b59e148dd171eb07972cdf34c1eb8df548cce9a28c51003ebd265aa6e8bf72f601987b9bb9c5ba7121917a4f269310f4c4002c85025066c2ea1ba58e35d203b03493c88bf8234b591d50187e4db4524c314c33ab689ab4060cc169d8891bc419f75f00a8b319e9998a3b812d24f353570a8c52c6b39ce2654b8612884b34b6fb4c5318bba707e2dd7a1ecf60893ba3028cf70b78a4ae07c7847e6c25a72f1808418ee569b5727fc62b03f595529a592556153a33809e2ceb8872c145ab15ea1df1b05c0cf1fd2cf1d55670bae450d1e8576c89a93bcba238a598923f876ca1ff781199c9b1804b165a64bf2221e1085cdcc48282f1f48fb9a87f58cdd0b92c5b1465b8163df3f9aad8ac5945c037968653ed0667913c9bfada04b5946c75b4fb1e52a99a90c044781c0b6fe900e46657e4be16f3e8e315d5912f4d2785481e082ee6c68563e787218aa291358183aa4d372a63af7f2a99b3c4e2c0dd4ec35528b951968347a30c58b60168008cad523ea8ed2fd324df4f1b0a9d2eda0b43ea4f8f2aa89c6ca90236f5990240c0e0434fb61550a7e2ce4dfc9c41b631d1819511b9e9fa380b285c2919da9cc9d4f1beddecbe3ca30ebec672b367a1a68b1dfa75a67d2af3a96246ca794651003a104c392f0f34ee12d69ce54731bd8c446c030965d2cfa3baaee371e7d6250233023aeffa702aad704c02dc93a94addf52cc517e3048dbd0b4fd91186782726b1505ceb436a227b1abf062844f7908723cdbe38a340036c7014fe04f8551fd864f7dbc85f9d5057059d181ea4052d69008e2ed829bd9b4aacb1bd96fa8d5a5e6fff2bfd257016a4f5e00879c9e692f8353a3524e220550e31bee1326c6e0addb17525b527fef8915ae83a75dc70da00a09e1422e93367a325f509cd4d81d555f8cf402c7cb35f39a1e2f8ac2000389227fb676945e020acfa90e5ee6aefd6aaafde309f630ab925768f1280bae8d0fcc06eaa4e995c3871095d43ce321fc3be25c24408937a2a28741a275a41070a34e8f76fccb74fd5464dd473ea393d3f68540ddc095bf438e7bebb4a91cc6de086cbfb043a263f505040248c624cea1dcaba5e809868da138b57b8485a82a48b24aa09798b50448a4aa9adde069d0721148c01dd469aa318fa221c316fa898669a56e3543d482da607b7a1b16718e737a031b4688beb1a26bb7156593cdd5d271632031404b29b8c6a45ebc5b032aee22a1e95f21eaf380159b206b42b12084091f7660951fb201810e06cf2164d044f002c20fc9a8e75d72c47743de4681af52582c1830698d10af56e0118110a54b8cb535c0ed0b545e419b4d00568412020a1aa770c302b5bc0e514f1d2baa7f9b3656beac7b20b3dfdc1ff4fa18cd47616262e6f063151a157550c7aa4efd2b3c1c63ec7aaeaf2e2098db416f0393c9668872dc73500a992b91801467799ba95f2bff78e47c011d88659b17d61da611c7082a4d08378c08b1ededcfc870b540d32ae30e0bd9289dd65020000c4e82afb54b1b4707749d9856b4638e17bf04d6c7e00bac080ea9d27ed9a35d8cc50e4e8169420acff7ddab3b7303c67342c745f66a8eecf6873aa3efc6fe92079864dd5ade19750223414308db7736fac61b9d6f50e574a08309c450db436b22d9c00e2b61878f77f655cb219c2a7fa7995af41c4fe44d7f12a8f7637780dc5e66d50febbd218bc18082982667dae442910ad020934a3ecbd6c8a61ac8cf2be07f2e1ca3ae0617a4f4c980c9caa13084c87bc3fe8120ffe938a0b0eecb66b9f65ad2e4d5684bc8016ce7ec4609fb37410f014ffd50eb59752fc9d0e0585cff396437d9641833730bb57070a8cb9f13fd471fb327360acce5a580843915a5f401ca44dbb9e9a499dc29a9f45329ffaf391a82c9569c00311411e85e94bc9c9109b3890dd0e0a0d36111f640dd6dd5deefc77df184541aa60b3784551b3c03d0b8d7c42e9988186763c467b910198f7fdcfc64dd373557ebca2d3258bb44a0bb2082f16967dd9af5435c2b2715035cc0cae863d58318a45766c7f34fb9066e4a1d3d62872c9431b033dd6ed262b4111d9afac7dce646cb7c8fab48ec0e3bf8de01deb55d19e13234b2e9bac38de84de2ecb957e6be386440f27654cd8d433f263304088e4c0c6d50bb85b6903ec528064db8a3cb637d40404aba81e32916a83429ac0f39c376330f1e6a48fdb98ecc23e570aed64317cbdb6282dcf2588cee2ce1db1623af306201ee98dbb1fed2019f73797bedd550200f0a819986376ace3a71f1a197237f1a34baef1a99bd8f53122bf01e475d44e0b469d2b7504372aa357fd7ac3335508214cc2e49f7aa9da0c606e54f42b711fadcff7b981303f9620b1e040fdac1b891af09dbfa38138a1dd9336c322525b7aa7735a0b6212dc5a5e112447402c43989bdea3ddda0e56e6751974413d3293fe1353cc73adb87d19621ad237cdd0b32d4c127c7b7a0561587b078fd96201c0adde6b26d6c4bb1240de95e113f3aafc9f58a639d7b8321f977be2f64ba4ca6c0e501e831b75c28380275239e843d5668cb980d1fbf9223886f1ec6dc3d14e8cffcc1f57a9051e7c66e59b495efa4887bc82cf79e9120ee18ca9ea6a54451fef68fd79c682a0babf029ccb40860a601c62251062bfb2fbc9b7d971821a34f0b9a213d9ee50f151c11e5f9af6337f482a4067f85fbf15ece96ab200b8ae88d4a786f89b867ce4be1d86604bce29baebde31482ee51a322ca3604f847a70a5c89c0ade2f8a649ead34d751c278384ababeac1980acffa32d688c2f499d40ffd8c52c09d096a2835cb12d61274ff331ef7ec82959c82819beed01dfb8bbae539ffe19de38e32da8e181591d46c061abe902a2f16e047801a03f093abfe8140672b10e33ca3dde731bb17e6008bbdcf79543ac5c7c49acdd176713a70690b70b3fa5963c165979cb2350216b4b2a00b31741efe3cd1cfb85f7c9280ceeb0142ebb8cd03f76574b36b4d6f2c86029c8dd12789c1cf6d32d6427f6928f58cfee5aaf3af55116feac5e6385a53c465f9147187fb8cb8a9b7a2121ba6e3b720d4906583fc50c3e33045234041a1e7dd283e458453733435d8eadd6184d231ca9999e5864ea8485aa1c0a351058ec24472bf99abd597b8741bf01b2de72c74f10315a1cc76ad2ac343303779945f198ed0c66ae7819c2bb4c955a462b96ddc3caa2b8ca76963257a00ce14fa64a26a355c9ee8164517c643b13992b4286f02053a58fd1ca63f7d0b128ceb19d6dcc151a43f88ca912c66875b17bb05814afd8ce286685a9506ee4077554bb7d15d37462db4bcca1470ce920a6c5c3e8dd6157a86171c4b02d16e6d015867409d30261f47eb02b64b0382ad8160473e803433a816911307a07d81562581c2d6c0b8539f484211dc2b43818bd1bec0a152c8e08b605b7813958f8b13cfa272b4b48efa1bbcb1087197701b23a548a7bcc563c69eb0956dc05a53e5be68fd317b2b8155f1da334920b47d73a9b65fb257a17aef6f7b7447b36723af833ca10e2371ef4ae1345fa4d9121565a2526baefd101183b78a5d1ecc3870972b7c1eed4c5bfd63d9ede14b9c5132622aca740f8da8b60be58d70b6045b6184bf154dae6c4ca767f45d387f359c71f4ecc48c629d44840ae9bf110c33b0d0d9fd8fa25034530c784311f917d9207fa03833e7ea34bfd8d142e35e52fad4d97a9c1d068295b568c64f6201de63419e6c64f6f97c3d2da099a70907a15cd50590d7ab25308576f4d56ab16ca1f1294fe3bab44bb3d1d3e5d9c8f5f7387075053cb542519fa5d71b3ee9d42a103dc807f6f0ef79c39746bcc03dfe632ff6d534047a2fc51df0ce396227dda0ac51fe6e5befde2c847c4f06f53167a8b1ff5da1400de20e77e7df3d05764e1c1fa531d434e6fb1bf82fb9141754d671701882dc30f8baf2b5434a27ddd3e587ca980a211e1fbf661fd75051585085fdb03eb2f2aac28a27ddf5e587d51812210edcbf6c3fa75851589285fb6078baf2ba068449d7d3bf63af830c06f1f072d54195b37e28b844fc7cbe4ada92107b174a304ee3ba08bf4a8b0bcc78d3fa693ceac7ed2f179d4b669697cc33dfe4f97e6596f154c32e88bd2572a28beea0c3ae81952b1e8ba057f0cd9470dff19a24fcb21bb40d3b3f7fe35cdfe874944fc5758cdec57af0519e7249e258c0e9d96c872f2e2cf978ae275fd0b399778b4a1883907e8c7de9665cba5ce020f29ee1cc1ec4aa5d35b4c5c7bc4d2a117f925dc2cac5d9649f5aa94361e073e9d05520e3dd010f7af308964fc50c9013f6d00a798104a3d791187a929833d9d83cded6e33e73089ff7b3cf7e8acaed1c046ef786e1dd910c888d93b942ffdee307f68e380042bba79d5008078e22e3ff6c052d74bbc929f9e71fa5250f404fcbe726ad51fbdcc2ecbb197824c866a7da9a0656cdf75b6965642995d575eb76212ff4eff71fbd5390447ef541af50c15d28e69501a620f403c31adf3099ad6d790fdb9fd711793baf6c7e0ffac6ce3c6e8b52327f5e77667d993196c914df5eafae234bd1afab0c0ccbf46c6fab8e7a46b609c47d92af10eb04988de901734b186177f9c0b598288612625e0d740bba8c32f9b1271e56d3c00402cbfab368682e5ce33a23bb1b1223eb03a53e7f0f7b893848b877fc7de4f1c2b59a789e3e3e71d42825d1519dadc35846e62af310233546870a0c695963ae7c28312526dbb9994a51126ccf7d24f06689b39f540f24028f390102b8fae12470210f2d1b9659eb18043bd49ad8d9bdee05513a208c1066dd7cb2b53b3af515568025dc5537f3d470c2101c2a64e773fac3bc17ec11f3df2e649f078c4063480e5e223970aeccf231a923a40e692c90f778b52316643d8fda2b502f86a6b9408b526d6a6a1327434e80e016ba99fa4d94f0565b8650f9f9ac75ec0a30f1eedd2bfaac3188066e54384b88eb0675939bb9da31beb502f852017a2c8e111dad0429bb252aca025f91a943111be97a2d45b66c3a5c61e2840a48dcd0be04041fc7b0f2f8aae0f93af9c2d70867c660d03bfbd64b7a4db54de1b1a755acca6c7141f6f1a62cd2a5509b642c79f764c6c1e3e2a203ae042c1dc3dc1e2765d5dcbc82f318d57800e0aba9c3c1ba60814b7cdc5fb271ced25bd6f6a71a13cada7cf8fb60096399e9150d61acae5e27d46784066d802982dbe372190706693a4af01c15210f253ffbeccc2c1a8e8228863c599cc486dd30576f83780f1c5d346f48518e9f4f9dcf2268399c8febf9f993c810e15aa9bdf0974df6e3b8264d036a93ee1a0cd541d678e617bbb9f5a839a86f2bd2411b571346b8512187a388c8b69d2a1791d3224c5031a6da12cfec7e620348930b0193d5f1874616ab99583b43ed99b48aeeb61ff4ca48d7322d8d82a1f106186b7301073eeb15f58f9b58fd0fbea06c4fe9b8f08613c0c334e3e869ff9e14f08dacda82455232b11f8b6a876500fe4f7f4ee76cf4c99d5d52474e4957c0f2a267ead9fb4ad2667f1cc7533a8b9c53acc212b7ea6ccc1819c95b120f836d7ef92e19b3f2659157929d9ce399ca00139e42335f855724b38f4054d2e36ae9055e4084bb548c4e959cbc5901f7535546fea88c61863c688be424f5e9e307e243c3a62a42a4b215952419c83f09a26cdcf80a88af1a11c31a7ad1a9e00704b314d2379c0dffb630d0fc54eadbeeb2835910cc7e473d9a3e8e99ceb572f46e02719cc6b363c8c1a6dfad397b574e4f00beab831d706341fc6c68185a01812249139adf1aa9515eda5a49a143f928970b7d7264327b2e8b2d4332369248080b3f922c145bff03971afdab66c82ab807664e8e4aaab9c9df97a7d9b8661a9b26d27186179743d074cdaf69d76f422845a744e9d7b15fd945a20a1c2cd01e5d0e88542bcace9802473d0421517bc6633e9f9d30ee8b18fbb88eff878f7dd669cf8d6793088f8102a05407115f8fc8261d1b10bfc778206059f29b3fa2605c1924ca4e6022a882e48a8cfcb5c4223838a1315359c8324ce92865c1c14e1e22193d3879d82bc47e44a0ca4f7855f31d83965a2d716fd05484178f48114c36149d62eaf296193bf8ef05ac5608df560f2c5f4af31881311b380e1582435a6140b083fd4d030b0651c93fe7d8fc659f33a54e14b68ce8703700538bfff6347096aab9456f903964d020eeea1ec6f9690f3092e0a192367cc9be1806432eea7641f415d6dfafceaeb06d15033688e20baa30c93af04b5a4956d8dc163a788130d94415eae6db083e4b0af50f06f02402655340e3a000127654f5f6fc55e6123da137f8b203114e11ccc928cd42d6a5403a60f3b20e29f381f298bfacd01c00fe67922dc896776b82b8c676200558d37ade26c50bfcfee23a80b8625c311dc00b0465c9e5863effa4f601f81b65bc5f666647a27612fcdb5eb9de4a9d101bf9314485b2c6def34efb5c52c57572ae3b586ee694c5b0207444ca70274fccb2ead251b1712ce63c72b977889dc9be222b8f10041ad0302790f75cb24856bc7d6d158167db142f8577ddea3ce677d06d8d1d109927a51ce741176bf6d14dc4d7017a7a66e0a8e5e6e9c5316c5d710522a44395e489570545b2c2e1edc21aaa755944868ee15061e363e71837c72149af44e3691b66c83312a247d0ace90a1b53812887550e37930692725814b0acd6cd6b4c6088f1fe0f281b9c443bcd14c769b23a03295e24d1fb7944c29dbe866a4c512e475a01c862220a5647c5e82e0e3787258d8620d5f61f0f34c48ca854865aef7378accb5b43a516455f0d52fcae8e1aebe56130442072ce63a04800498b1e6aa747a60964f8c7a39216709b6d86bfc759d42eca469f67a081cd2b7562c2cd17d423d8e1c93188a30b659e556dabffb93748de98f70a2dbb98e8141fc0fa238147ef0346c7d605288e126b215dd6804465fe2034648a097e017da85aaf88238c419cc2339c091c0e44a84e66eb63eac9b41054b0124adcd00fe74dc22dbca2d1ab32fb0d8d561a10288b93e2a58e5e7ee34ef15a99b83975a109c99da6075aed66cb84bc013348aca7e3a20b9c93682cf7a11f0046166b04032437f9433376ef415e5be98dce2a4fd0429ee371f9d5cccea3a631d9ae48403bb025c1ac2c32c844b5487992f2a7dd39d770a55d2ad5ee33b487be36cd9e32e858298e64c9ae2ee2e66326ab06e1faa55997970a7c1a6e2060fd3a0522bcddfa1bc3298e49a679ee75fbd8a6f0d8da9719fac76328f7834238384e9ad7019091c99bf777107ab796c7229328d06795e90b9cd53a123b003b95d24929edd93515751cddd12fdc7c6a30389bb86c90ee66b49115f8a7ed2478f55a988df864f1d04e991e85350426224ed945213b561b47bfdbdf7997e5e08dc01148d9fda81883f6d9b96a32cd8df3c014c206267005dd4f889613239963db2124b8620c2e6297c8fc8e5e1fef2048da52c70a8ad4b27d959734d21a7f3efb831932868fc28093ed67fcc0ce1c9a9797303f596794e3988c2a37c8f5049960fdc5cd98733f1ccc81c0a4af6c0e5f228993ed2e035be382a8e7e8e5ebc3b53809dfb22d702a68dbe490ec407201be949839e85fa0043601d9cd5336e44fef241747eae2fae301aaed33c17bdd233a5d9ab665ec3cd8640193f68e185d3ae922d268079051914458bfe2fe86e59a5a08053bf9182101cd5a4de37065cbb9ae5e58cc7721ff3b74d25016ec711d676f04513d2022a09f8383d62b130baf91ee0f99145546e40e9cf3d86fdec2ef98d7c3b7e4f20785a40e5f3970b220dce08d13066ce0d380b84e4ca841cf01176d86a9cae8b9a5f2f0748953685eb9e297707c6882cc282dce3a6789a247700e24009c0e4351743dd2a9849780b834c00683dcbcfb2bedd5c087c1b869009341d78120f5633a2a2097c34041fb78102862a18e5cd6f9068cfd1507f4a8983401322f1436249ffea01a6ccd542ef2e0357c1d9a935282a0d980851b64aaa8f649609dc5f01d193e29c0b29924118cf07423a7a1bdc181f595fd0dd5a6310aaf7a1af4fb70c5057529408a5a6457259a60a09592aa959d731fc1ca9662afc975584e01bc2b3f3b829834d00811678ac2c4c0ef0eb4078a443c09fae4bb7044c5405de41ee1c062ec1271e26673f1c6458ebf714c187c08d43f543fcde3299be0401c500caa1c71415e2c936745816264cedbedafe5b0a32290af7ace5d8a2f8bcda6b40c13629900d183cf75231b5a9281f7de9ad3d607457b9f817cb9d4900a66ff105a8705eb56a44394b1d1f2fccc45aa1ea429e58bb5d5d588ab561a05c5430f721582ef98428509f7b542cfa3bba6a7386daa99fa5118d5331d69a44ab46910d18036bf7e46c8865059e44adddb53a5ebac2292585a448c508ab28c3ef710c0bff592306a9519457bac8395cbc4e283fb49f3bc203854eb70f430b513d4adcf328679444be0f052723eae4b21d62227ff2b2b70637c3a1631bb5efe225389450e83615e80b30768fb2f7af69c9df8e4ff82619df4e0551874f1d831fff75d65b34849a45a8066f2c70e65e5fd686d0d1e0fb24f7e1f46e52afd6cf4fe5388796916d8596908bff708eea8d5b5cc38ad7b8b938849fa8cdf80c399b4e2e8a92fb9d37127d7c894ba7c1b8acfadbd3eebb3359ee770cad81860f8c80fe12dce2ffdcf6a74e19fa8814f6445b7d484cbf9fd6c99206cd73ab7847b32bb445e868fce6690d82d6faa67bb4248096095c606be2e97834bf81dded680c679087a09f2440bdd1b6871038c5d11cb1f576c8cca4cd8f1056d2c6a75d53663e831ad3f0fd56e19785a02c4b6c9c5a2c7e47f63f5dd8b9e27a2efd1577911e1eb593be7cd00cdf6f66b86c62f3b0af73e1626873f7a196555f8ffe7b579e28e102ab7a2b7a01f4ccc8fa17c2ae2c2032e9977707a82c3fa770202fa51cbb1cdd6a61bb460234eccc6564c97f8b9c99a297a0cb11c2e0cf992581cbc3ca367de1d989bd9b0581c24dccd8273c4ab4d17b98279158315c5b98c4dc46505c1b7636272e5b511cec700804cab5856970dd082d88505a96040b3b9e2b5604af08a24f396b346a4373b79534386adb6849918c849c93759c2f0baedc536c424bbb73fb0213b7aabdf0ef5daee664fb128f464cb1a3070407529c9ada5db600602da1f3e4463024e5d9f4dd8b230d17836f396f9d284e7fd7b62d6c4f15d120d0c432136975bfbb3d725ff73332b6c025232f91138263128c449b80bb93688b03000adc4cb7c49ef9c83887f935e7aad69a865be661bc8014c677668de22ddc11e118ea208087288506ea4cdb79ac81adad0dc5e90a9712702ee21b644921b993482290a18394d4a68b121c80d67c8265aaa207c405a84321903ce04c0185a461818cdf0d545c342e262f000e310db372cefc91ca81c544a6dfe97e6e330115404cc15c8c5a79d20fcb92082f54b41248b03dace430adaa5db4a63afd137e96fa36a708d3a25c4278108feb12149aae4e1d6d3b83c9bc8520a674e28e9466245e96a8e978c20e6b70ea131a2d9bc72cd0624167f6e327c07088e6f970600353942d04e5e900e68188e22c87826ca90a004cdd11cfe77aaade78f0437a2581e5ce06ada8f1f856cc641260da932297a897d227a45e2df197cbf36736069a123daf0ada390147ec3e05d0450e18f60a25135ecc0e0762229ada44783011f631c70a314954d649dfb43078935990353f46a7c4b1a292ca66091e472559d40900b400fcbba936103d45ed127e5a4b0e4a86e439ce946a322fd60039f53f112a2aa237d0dbce6f520303cdc2deece9af680cef6ae833d51e746431d4112a5862c70d2ecc6538585b4fdd5710482cf064eba2d851fb33524799c630ad65bc2a58fe34501a1cc0b1f3ce4acc1d2350b83cdd37fed7388e9856bc51eff38b184a63a7a86912f03c1f469ca6b3686d7c859a4711b26c53732178d4bd33c1b0af00b6cae44c512a379de78f9bedf5ccdf6f7de13bd4cd410a223b17fc60c2b493584bb60dc5256ec0db29f35a67cedf7a8b5dbdbc20ea8e1feb00343445cceeff1e3de74511d297e254c721d6db15ea9b559f073703716bedfac4c0ff0bd4b489503ead95f5fcb38f32bf3123c2eb8c1ca66c3a1d0273fce540c5aa55ccd56d3924b7507b6272c53c738a7cb8d07b2e5b416f438cf33dc52d28a28b7f4b4491597ef08f0a6452f633562ac3f2566ce1b3cb30cc7c30b49b51cb311874e3ec4d014810900e59f2505adf1a74845b12448d55ce244ad90dde23af8010de4d380f631c5c0a12ee6bdc040e541ebd3a7302e328443206a68e8ff890e9748d765d918150dc9b80720a3d5d5181c6c9813a32d729a94fe23da705476721c82bf6137d411239a1e45bac56b2913729fd11d889425f0e8fc0349e316d9af8e27c8063e56bd9355cab1a967e7d35ed164d20815e160f4e06737a9426c9c65180036179cb3bbf9dcbc0a5bd1551bf0e70489c17f75b836a5ce8185810432fc3d20018588e9c4152fd92fb52badd36731dc5e4421409a868fd1ba78b4652fa698d0178eb9620d808a1166bd163861a7bc21b3518ced71090ac78a66fe83a3fb1ea028b8873c8ac88324b0c0e7d014bbf1ffa76a07973f2b436d32e5c02fa68ca41bc5737205039a08a9b82a185534db2a7e0713e0ac391bc60fccd8037b0455e42b09f1c0cc24b81cd2851ebe1ffa2e9585580018536973bd0904939b84d8be4633ec55ea91d6c300f8e88d5c43a9b3406616ee5142a7ad6eeb6327d0bd0e990924691154cf4f41115e2b290285d1c2c87ea03067bcf85da40c43b361614e0bf658a77a2d6d6f76b3c35ab403de32625f7158a56fd2bd999e5a6d348afe0e708c2dff4853160989cbc2e2e6e71a7146627489c0bbd8ba5a49a04714d475c57dab4b75639fef47d3fb3793b31731b556d55631825a9676354d7229bf8b0956d4302be892981a3c49ea9cd2358c336ec1914cebe9d8aaf7b0a313278a0c119af29fb8c6b1b13e48c9247cf9f8e33a72af8937e4002dc1e0fc8bda1686c9ef0df4c98d48561a16fc594c0fe96a1632a83e3990353565cb493fe244b97733383578ec29c6140a5a29d90ea298d76465a35d665a0edf86575e1d4fbc7cd81b3eb8a3276de2c74cab79e77caa2a67ac96a1c657299c0ad474ed8bd97e2166fabea63da8d7216e0418facedc31b00d4e1c12f03f19fbb64a5a47d68e7c9a36793033e2d5658567621923cdeda8e82a96d338d80df12a21c044037835df3e89829eaa62b1e299963b7a8b32d5756c0fe81822248a98ee2b6ff00ee37c2049182923bc11ee8099c2f4084c369944f02761276017c4c8bfcbdc00e41cf29bb2d4314701778f11dc501a4174849008642ec828021f21064e20a060201542aa4d199b018cffb1162d0fd9d7df57664ac4ce993dc4a5a632f2c382b54d742d551fc523bdc4942eac3ee11d5cbeec4e62bc9ea19fa8b3deaffc6f45a0524b5804c0cef3572e6cdb3b6b272876f908254a91d807dfd6de402fe3652bdf05f167a56d239eddec4103e96cdb3f0ff58f410d98bd54994761b00a65366027dcbfa82354ca68d7b3155d84f03850b518d228281d809b3d0fdc888a9e01e542351812c87a1ddb3d3c30ceb4aea3fc68f16ae177f96e2355dd6a90e63bbf3c7cc4c71f928e79cbaece482c7b1a3dba088c6447136e6cd0094a486d0c7995f97798713731ff8e533a95c59f8dbf30e9511143acc24c321f102db62972812ebd0e3d4e169803d58ec928356949371264ba5cf930a1c0654fd434cdc3b08ccd32401dad42e8be3f2d25e29afe6db0a7b4758d0f8db04cc2d9d2902b53493d3f4420a9968020799d9c4f365ce068674be976a40cb5495d3491bff6b5d63cae048612730b647b1cda157b5b918bb386f7eee8e0147c9212994d3ff5fe0bbb27fc47f60d2dfb1806c5f7d848dd2de04f3aa258dad904adf14e7c5243f95d99e0e71fec037d3c3ce93e42e019df0d11fec36099024bb5e6e99a58bba78a2cb3875e67f9dfd5a584642427fb7f8bad3c6e04c60812d3c33370cda202d1f8387492c0364d9711dd7f9b48e35c11cecd566ab075e16977c61a59932f5dde659f0e936174f8f168b11a4c49c05266db760077742c48d2090053e3f62f66dbde4f4630306e2f3f7aaf1ff80e488c0cd59f4b765618f6f716386befa2fed59e544b051ff41ad051ce735ddc388d976d63c75262857c309c1ae7de5b38990acbac671797f9acf9dc5c0402ed152d0868ce6258e47ecdcf48613201a43239dbd7577a5a1c922e70733f19a0774ed660c2f240b308d49d5bf3130224b045f0b9b15b76f50de02b7f293710149413ef06842cba063e11b819fd98b8a2633b68a98929bc4b14042bbd7297d5b5b4f0dd91599fb7191ae34368d48dcdee8c5a0fd070e5b2fe62e503d6af982fccab204dd55303399ba18279033dd3b42ae94da9c652eb1ea2c02a08f5795ecd664720863673b9b2d826b3999b1a62bfeb7fb8dd887fecb80947b81eaf3dee7cc8fae18e52e435e7a4414ab37ea6d2224dfebc9e021f3474c1260ab5e5684882fb4579702ec3b6c21722093fc9d5fd25d5ab9b19f3afdca76bff22bdc5e10dba1d5baf0f5ffb3e5f6f933b79e6d7247a48d424f475c4741cbb4a2ffe6dc63d2ff621c4745bd7bedf582916b1926c1ad718d7abddfa6f430745c2e14b30c84e544624639fd895c089357c1207259bb29216767fd1c8159ba800e7804271e7ddd73d1820f9d2006743cd5d7004d9e134f02dd795aaaba8c644652adf8034e992e2492f21674c0bcbf42808ec39316f15d8192c329009aa5cb0fa714d7facb269ba14b48d6097ac090ba5a42b4edc8e98a8946f52d3efc383ed6f3f1f99444e17f3f0e9fc14f10b7c3e159612eef4e26e1c1ae43f604a3d8a56db7ee6d825df827736234b8f58bc1efda0c223fad4f2f4b6e7e17454f5ec29ea5c2f82360ae9a90b8a84ca52d2784f901402e39efdb16592cc5d3bc1210a67a766b9e90a35dd9ce4fdd9672c37311cb11ad93720bee52f8ed2ee0e75a5a4cb79415949db800af8cf12eacd93745e488d97ab7e73632f4ac496b6333037f2af5dcf4230048e9335352b3899af4e043c3cf7a6ce6fd42487c4b28542e52e3745cd889b4ee469e8fff742e4a95d095d32e1a2c3dec0842a2170ebecd3ee1857e939dfbf060aee844996614bbbe797e69724380fef4a88d7db33bce6313687b0a4afebdd99632cb272fbb8fa2f7dc4789dddc931feec52d3905acc327ee1ebd923ec42c9b993570a296d745fc57a2847e798122bf7571fa8f93e55feaaa2c2feaa03e1c0eb0f4638346cc4ba6d54586bcc1507432b1ae5b6ef2af465ae67a818a579b0696bf5587b68425e3fe2e2aa79571e13230f7aae22e5f9788cdec566238b8a1d34bd0eb944d19a5c27798db4323a205b95f7c5d4b30a88a80dae9c41faa73dc8ed00cab213a19714b357e46a5e1a8a68212c571c1f06c7b67abb408b1a27a218b222fa0394aec1dfea9a46ac9c1da620d9279e987a8034f016597feb33a488ea3740bf742a360362cf48e3a4a7099abff2f823fd5f47ac2ffa2beb4652a74d4474765fd54c250687ad8af27fb25c5802aa1a4a476b5dec1672f1fec2e848ce6fa4168a8ad6c7eb5dda0c660fb89a2aea3fe4eb2f96ef733b4462a4702725bea99aead2c12b5506abd2087ada8b8011d95338ef70e442aa591a2df0cdd8c9791ed967d9e11ced114a09881015c296a3687f105158268a4efbece2606531d94a8dbb570c5916fb843bc900760b9ac89358a5f880719b6c9ca0e173848e0160b478b3416e32101861b496347db1a0a83efe4cb82b18d43de044a2b754e855c08aa32ad4172d4a2fbb61eaf1139ffcf22927375929ff872501c29fdeba94ab19df9f6c724152d9ad827d5c5b7c31bc8cc8ffb637a31829931870bccf96b693a4f77a782355429ed794553ab6593fe7c90d6c3d374ad9529796d90688391002fbc2ebd0e3c71ddd9b46aa60a41031e62a0e0b0472841e720f2470913dafb47b2d3ea801eb9ce37b424916e76b66d310b9e3ebed5b3cd6829a8d8f2ba6b01130bb9380b0ad6cc24c69764f7a0236a5a29061363f7a0f80f6e735ab6e2cb808554575ec75a399c5a5af304ac9dcbd766d102aec67c0c91aa71886a6fec28fea584a85eaccfa74c5163bae12317e5c3917dab300388d317a62f19344a68271cff1eb9dea473522b242f57ad9801dac894487130a4e42e8d7ac71daee543b32128c2a3ab15d8a910d2206f212d243e046087fdecf4fad8464241209ade57324da84cd4736b4404f5f633e6d6f64f17ff08d571c41fdd2c5d507af7d14b4cfce35671f06a946e79391c8c657c323a4ba87f3526d86045c9104973d1f68bdad04c55d72eb145b5d6a2ca6331776231730edf789aba01dce00e1c7d9a0efe8d70df73d3ed1722fa084167fec9f2519ed984f9824f218282f4ab8f3a56bc644b838c4f8daad5b1dc9e9bddd2df38f0cf9ae323128b37726c9d756ecac7fb2202a9fa005ed3585214ce1e0f81c34a3c5d798e128cdcf18b5a855890003983b1a28b202cf1ea6085ef644f32014763ba785504e3934b206d89874f0241d2c7816609733f6a7d1fa3a3e31e603efc0e0d4d6042015b5b9b187c9c4c9f0088145f44391b4ebfb8938069ef7a941888a0eca8b7e88384fcce02285034c60482f19f1cb83cb0329d0d7878f46fa4e2e503bb8c8a068e827c30eb075519bb351c7efd82aced6445b7c2721e10129f6c5c9cfd08b49b3c079c3b3850d4f857acec3b2a36529e290fa430f83a81b81faf8098c7ad1994d9ad4148684eaad2e7f4c5ba4a797161aa00d8c6ec636602a523d22575bd889e14bbaa7ee054c1d24e5d120fd74c654dca2af2f0a7a0b7cff930cbebedd7ce15b73b2b04fa22a0aca05f99939cd6ac2692988f1ee6cb55c57248f25863c03c91aafe9c901fde1ac77fa04037132f50a4e6b5576575b60c3d79c0b1525925139e10ad2e765fca00b86cf60d7d18dac9a32eee07c0120a18d7cc11fc4303a35619c445f3e071569aaf19abbe72efd06d62679d9662075100986421c400f5ab07ba19cb84d2c6fa8c4390d4eeae966f92301fdf4796f3b92a367ae4f552c39eb3253908109f1a719d521cd7d86a36d3a17bbca9783314b7e9153d17f56a963037e4bc4df8134a174ca23b656927ec35365732cd224bef20d6be81210b670f01d64e4f4be861586132ce318c827c36a8352fbc2557290269c9b59ba8c775d210517d2b01bf4e000d5e34522434359e70437b0f4cbbcf245d1fcf8cdfbc483766e2f628d36c3f1b7a4eb5f0a9cf56e819da11b6d078f5ada3e521caf4008cd2f57b3ee5ed2fc67ba0bd7f4a8367e63541ff47fb787c91b2b7c4d3a170fa5a3e06a2c09fa5f7a8afe40e0d6faaf1ddffbed5a01c8cfa7fca63e9c87fc9762d888aaf14119849f8b50a83bc4329251e0bcfae5513e959e331c7f0859f8db9d19ab77ab5876bdc3df415e0d0e8ced5662c0b934d561f0028b8171c354bf080028e1dbb520c59088f5ca6692d4a772e7b11f6f734a5dcae3b849b88c3faf14b45d4dfea00ec0076760a54f62e08cf4501f861e1aaa5a3f3f51cd1d26bc649fa367b110d962de054ad075efa4596a005c3c73c81c8600ac45e2b2db01e94eae0ae009e8d34f0492b6ef76e6c69ae4543a880896956e16f7f8486ddb0773ab9f76c4a23a23749987367373d2aad8736c8b7f16a5a987d0704fd042c4c363b2783e7294af5df9ca4de4a194538a4f8869bfad94d75ca9585364faf46a4c7d5c8d57b6d62df1bac60c68bac31dad82842475a3fda53b92394df24f320b7897575cc644cab8374150a41e33389be97eb63a8f9f068a04195e00cc0e3569e29c4cc300784456017154f0018b4708118c375119d569b97bed293941190e3a7779520791861c1e76354fe917ed3da0756701274dccca6084d7b1cb992fc3804afa691e9c7575af3989e9670221b102ee01428fb7c56c3db1e0c5789e05292a76fa31211517a7cae275391224a7c1407a16a6c3fb7dd7160145059e077172f647bf9d638af80b0105e6861b339b49d67b390bf02f62a8e227f8fc43a252cc30ac653b8f06c4c8c30f377a82624d40646a035d8e0762706a974b52502e2453348b9b1e9b14c47e806eefc92eb1f17a449ca26c47536852a0ffe7802e6f4260d448b19f2042875d7b740430c8d18e13102f5742c94af460fe3e569ebaf61a796128ad10dc8be6771413574144ff375e61e100268e5b56a0451b2c2d0c6d97567463fead046f8f33f73a7a5a6e5a6e7c417f3a95ba714bf107b44394f7f2286cf2fee2108d03569750e8908bc1c2463dc93041aff845dafa15c6704415a957c09f144f4a42b9a92132ec1bbb1bd56c9af2309ddb88392aaa58676f1e82d7b1c6edf76d34f9f3baa885212e64630e684866205c4742f32080d67404c21e8db212613d0e105958201d26eaa1f39a636406fa3d7d576e21af2d911a7eadd30782828c02f96582c17f7673d327e15540c2d12fd7f8a343d6270d69ebf6e725048f3b3922f70fe04fff7bb5fd30ee557dba173c294a8c6660846dec786d09083d43dff4b03988a90394482ec14da2d3d5fd4dcc4ca5ee5e97c079894f0794c5fb6e71d03109dddad7ea9d3b011e019aee97927bc4df490d096451423c3cf0968138946a9746eaab93fe36fc59c1d7445271c1af017ceed24b3c751cab88f0cee7d6423861a41b5f42313e10635648a05738022d34962cd094237c67da2033a33117cafa7136a7c992e897302db14acd4b40246f444e1b2605f091288581fda4ee85648c5959b59fffe4bbb4bff3862b376cafd2c2671ebd6ae607581ac800c12f5b6b19991dc8d00aee5f06fc141c37273d7cca7e0855b6086c435072b7f785bcdf20ee494e34df7a5c89e32e4fe930844cadad37c22726774ca3d12dc6afa70659425f4969ce95681c1bc633bb7136b4af7ea4702b7c80dd021b6d2450bff45c141064baabd4057464bb67004a65ff6927615eab43218b4acee8b5333a3c85c79e67a3c22c21a028ffae7a3e0e836e10425fb72aca4693dbe6e5d70e71d48a89e341a4748c64d81e3fb1d6d3c22ce1e5fec452806fa3876294d9011e2e4b3801375914e715b1015d0aed6094d6cb945a8bde1d71df0e9823ea1b78539da3f068a68044540c2168badd325cb21a7ffd67985054ed39b0f5b9ba418656dcb5123f9eff744947f274040e951838e2d0a147bb90fb7f07059949e6f1a5ad18b4fd98243ab07d3cfb3f0a4c08a43ebaf4be1b90abb464b530b1158c9884e9e1033977812fc3f0e39bd71644d1f8cf3fea1001f688f3e051dfe6988ddc8561fca07d09e84ca4b8cf863c9147041554d31682d13df034cf97246a10efbf13960cc7935a902e7c6ddb377b8ee7bd0ce74ee50f19c169facf28271c710374051d4b88d194b57280967a820c4ff651d00e775838a3020f9b45f3b54a14727eb0e74c590617665c9371a60ebcd5f3ce98f35a2c9632e2d0ea29a49f76795520f9cdf25d70764c69ac966d9c2abf85beffc6a8368069f0b10c2c8fa417eecfd06a1fd87d98f74952da8010efdc84f47dceb714410d420185903d71d4bbc8d361d540419e1968e091266b01aa104d842355068cbac81c30aced3d19b63f29367377c1d696ca8f7a41c30479f943a42a7e62284764991518ec7552c8271b3c3d042b4e7736337c864453f90e1170d29ef3e64203fa0448f68b991729d106ee50058730a32d4630a8a8798d74dc09a04275c84092de27bd1b2c64115771f198f159d4e41fdd9b9382804e5ca9b2878313325197be9cd8a84c41fc15b0e708378a2ce9081e3c0b9ce5619340c9e4051b23b862706ac845e82d9f3b3da4810a1b33288d56e2aa29cb3ce1a231240d33cd9e320e6ec04dc73d8d8f954caafd0a92b322ec212ce1b2b5fd9fb2e023f22b18375ed320ea93bacc624ea093970a15cca40e45a4b8bd35cf6abb0302fb882816f0d3ce550e52c75d4b197e6e17b47ec41a1b180d79d35dd2de02ed4a13d4cc69659adb45c4f208eb580cb00d5d85119779020700212ca921e9648ce8178e3a63eb2e678a34012358a994fc93aa2cb22de36552294ce89a88caac6215baec4fa059105c812800288f32d5441012ceda786560c3180cf32e65ec5ec2534b09b71991523670b14fb889ed489593f7bb52972a51b7fac39224011f03ee9436a944cf959c6c53b13dea8d1d1dcce834005f95bde898f5f22da7e11b0537214f4ed634f5bf31f1d3ed4164d277f8793a0e05b463edd63f582ee1f1436986719424ee35ffba6e30e161058765cc6d7fd442bc7ca6fa8a8b2fc008e473e8984c7bc2bb219aca23ced85710b038aa26f3922d7f8988b707b513d811976cc2bfa2f0ee4149f6a36a16385820b33cabb5c1133a6813f3eccd75b533c6060532cf5a09c27117107f5627e53ac55e2daad72945acd15be5804fcf24676844b08e690226eb8000ca226540c78a2c40169962d3417d81c98e0fbc8225abdf67b2ec2a255c37688fed611d7fcaae580ce0ea58c7cb228144ae510b6da068603162584edaee754f7de143f38fbe6c9ab759b320f1bb8c41a270cd0338e55fe8d0df327398004a363584206628d476d5b836a751a097120419649dffc4ab5cb506d35757d96835950ad74ce5201cfb1c6e9cf74c4a41e914d94db015394c9cb98e4dcae721a7ae8e6505612bab0317c2436315ab4bceede57d9042e8daffc82f7125660c461fb4241a36434da5de35618b0176ae0d6f8ac7ad3728157eac9eedb7128a95ca7a7652e5dfa1740c9ef180c82417db3b0f783d0996103327fd2b977318606aea870dc54695d0538e51a825a65c04b68f10ccd82356d48688053caa8e55a2c0593b650e07736e30ef8ca2e749380b22e5160d10b5abac45f99fdb30e74b0322bf63d4cc4ddcc37e2aee84dccb236a4176d4c6039dfd00c0c10b55d49d786e7eb617865d82d89e1c058f73fe488e77e633c2cdc2abd4e5e1e1e70e02cc2017883455ff999491b1abc6360aa51ff154a6b5a943294fa413da37401aa695077296115454f32b03360ee3f2ee80ee2f641e6771b748cec8b13c023597b2651f0782b6854f3cfd181167bdc34e111d1b099dc18ed8ccc2f2bccb75da9e781bc2b802f88175fe518233de0c938f126481dc33906724f7820e66bf87f1d4812cdae07ae41c7257211fe235b74c140d1813fff1121827e349e4fdaece5f5289cc80a8db2f3e49ddea4c2052f95c387737d598879219a6b85a1af17a66a8656db018c081c863cff50d996391746ee929ed5a72b145932f70b2a99fce1caa07afaf57d691fa0333da1c751dc74549613a1585981603d93658a82707667e5f0518210c89a95e47a9e9d272615f4412f7e3d9d4c68e005cfc6ff7c5ba3d51b0d5cc15e51db1bcbd3cad370690aab91deb3572554fea9e336e65a8112872babeb9a3de62df0a69287cf9ffbe063d339893cc584aa84b7c839b6ca17456e47d5538f4364f0a731f9a1efb3690699bb00a9adb25a3e5eac4de6505561c106158545608ebd88a9f9186eaa2380b2950d4dd8c03617098033ef6fc3c8410b7f64ada09cb59edb448a0930c690ac081c24fc057214834121a35b0722264f7a2a1c5f4453b46f4838a37f5442cd9876d8182d69c3db2842cb83911641539348f50d943086a1304eb4858316b67ab6310660d660f06acd071b1fd6ddde3ec8c9f1da29fd8c9107748d5c0eb2f7301b12ad70b68f452982db7112a069977942512b47400005703cb807c02921ada532bbaa254c82d5a755c89b0d50fea94428b1e751bf820640871213d8addd33428add0c08c254a33a60db57ea6113b2af936f5b881e0ef721f45528207df2e443e3638931c1939843917870bc3a786e514b571343fadfcc0686ed211a245db849c4758cbab2a11e3a060e0e6860f203ffef2836a35f897c50a13e4a074c64111810cd02e097d79ac33115a25ba63b9868434c421abeaee37da5e96feef23e9e1950f47c1a8c15e6c6417bc80651b6ea0272867817fa27434485c1c02c5b476abb61fbc5b834cfd61fe9c8c55a3f430174be51f26ce448fc5ac53e54cf919722c4b83efc07766e906a319e8b44eba5ceb4d2f451bfdd28b25076d271ce066576853c0a9c06138d1cbdf6487ad9852ecb6ef665be9684bed157c64e70233b11f8136c449faf419c2d01a4b22dd94463d7ff7f6909d7cbaffca5ef27809b2cfd1c12358c807b908387d0ab7743e5663aaa83295f34fa0bde3b114c1dbdcdce1acafd0a5afb01cb2706f0672b259ca2b5ce731e8483c13f9d511c587264ebbbe95e716b2cf570a1af43f8df86a8ee041a4c3a94074a13244ab028b414b87a8d7d682cbfedfb2b62de4f74f2b979dde5cea17724e1408dd770bbddf8ee637a0861ba280414b705d34387f81c80d347034b0263b568aea0f1fc072421c49ae2c76667daa7f6765a57ff4b8216f61fae5b30628715b75249df9384a11dfac34fafe88451192969030e22c839e27b103c96039ab371e4060f18e544969dea55c100604fbc57c7b56865e1f5c694b1579ea0709656740b66e142af80348c1a826839636509ce7acf97747e54343279a1395301be24bc98eaa01af4a5c76e03d1eb04b470b09b884405780010d5773982655732989a86f32ba130199c60038df1600544b21553d3359a1374f486a30698f7992bb459f70dca0d1052582907452ebccebc827b93f7fa6a9abed8338ab3da843bc2a4a0f5a1b906af6446f387b2d8f6b3572d3aa52ffded5a41530608a37b40a029d355ca58e727d3831e25eed86dbabf998b8088e2daee7c704d7f9241516f3c52ac0ac960cbc92b9537d9395757bb6520beb8a14b84615127bea95d1d846171c02b8537e86ac42df6eeb49d2ee56a6428f16f7435ea864d855ba9efb15f5119686222095e24928e048335da478206972e57e3cfc717bb0814a7a80211f01f830e5fae1dc40364e91c50ebc3df8eb1a0dd4bdd521986f753f7c71cc00e03cfb03a4c13a617fd2dd3e1f9ca85ca6aac844977af506aa49c5c86c13f47036b752f428f1aed0db0a175ec09acc90df7b57c974e79042f81aeb884a42554b6e205bf568319546c6241f1349adbd85172214a5a52b8ed5e4a8ec6cbaeb42f5950768809ed2070c526bd50b9cce91f524ca700c70a9893679711b55eba3f25504ae8e2df6d6102d8e8059a69dee8a50df408a958070d3c60115009576b07510cfe7eaa8c3f2db6a308301854de8753433a63c4b6804aedb119ebba9ad40017eb63c3545eaa045301b0d96bd29600024931d20ecde301265860c83bfaf6ebd8fc94f9cc6eaa9d30f8a2aa455121788e13408280d815a30721e2a941cf40fdaf68a81f3811e4afd22012307f180359f1abcd2a137f088c597d4c36458ae6d56340b6ff8f4f1ec6ccd55a64e1098284e5b3cf96e0a1a941764b44290d580dde8f46717a5e12d30408aae804d8a7dd3d21fb5d56c7d869b03a3c66a881fb5f94649415749dd2066164ddafefc0487c69041e9af42af8db0dd48aa488ed642a7d8a3a18efe9e40b6195f2625df4897016ddab8c7d1810d53ea19f3ebc3ebdd1683e40c647e2244a4bf6c754c190af5787b1bd8f10e209837d4bfa70fb493d73d50f976b6d4e994e6908303dd128f763d3cd6b2f978c4c7588369400394e62d086d506921c978d6070da0aeefab82adfc56cb42020444ef792570555800adedf8034629f6d1cc0b292bac4d395fdd86ac7d18d672def11e64f90c61414ba3ffa0dd85f0f682216f51826ccef1d7a23b33b90d70494ca9171f63a671f91d06b053847d313be3acee57256ac1989ca28a9c24876dfb5d03dd89d61f50d19e760906be138c0244404dd2d0c743943a892057da56480522626d536e8e747e17d74b74239bd2ef06d7ec9ed9cf453120cc856add7fddbf526ba5b92ebcbbb518e3810c440c3cb4808114b202e751ee69e4a9d36111c740600060e9dbbd4101730795359c06eaf4104a2f14dedc80dbbbefe5e8ffc55fb6aaa2a10905a1a4a47318230fccc5776c98dc678611a3febfa70dbfa97542c8853212248ff9cd226a8cdd2f566efc0987787ce4f6332ba3993bebc28502a1929fd63d8e3043e75ad78ed6f87a6115390b73507a3db74f6e12233f449db09d36275e9f94161bebb4714ae2326604f2d306a571eed2716596ccb3cbb22a6977583eb3f840934595ffc28b043ea617ad75e507b41d63b49dfc86c3aeafe746463bf3c9d8d0daca3e3361face57a610f8db0338c5618d6bb318c4921012090752e75eb778e78bf2978d49b23bb5c1f3acdf66d6d8f0452f9cbf3ab5db735a016a1112c7e24c08c261215dc8f982e636eeb907d87eafc02762a4375e824a1551cc6750077669a7594865e407be6532f5704dc44078b2e1166fa1d938a7ce671ac47d84d62c76b40e81f27e4098e5cdb757f6c40b88d63fab52815bc0c36dfed807ac516731df84287be6994be860fd5857340c61687b2cf699effbbc5390b549dc61b02f89d3a149abf6e41275f5446884057e42dc9906d2b720bb4e29e15616c6f96a1734650db832ba7d32e2442e31c227a2b44cbd3c5b4584ec905328ba8131b94d434bdbce317b519b40c2adf2adb8505e4ee184654988b04af4f9d1f5c497a648918d1b116415eba165e644ac32861e351f3f83cecbf11e2089602ea5081ae6d2f08c12e523752785107c86d9833cd9fb36d54eb1eac4ebbe7b168b8fce595bce67c0b5f2cc6917766bd0029e7f00c6a3c70db05c0091cc32bba1efcf3c62112905cf8aa16e5d42be08900f2b25bb436bc3e8b50173ab95d25fdfa0a5f71cd0fa8b3f1108b27114b2dd07d08a6ff871f71f2c17046323ba82a03e5fb17282c735924cc872d2fb0e601a164be3278b7afb262edff903a76018ae2845648016cafeb86db051cfa695ea5b5800dfaeb44081f38cff86a746a9b1aa80160a638e1dcedb9b2de9f226da8c24586b81a080ed9607a513b87ae79a9654aea54803058e72632180d54583a0e2aa1b0d89f9b042bf8e955d94307dad7582df39382f6842e29949085d9ddf6a4f24bffdbf9880d1635024f0d563647c20edfd58f82c92564723fbad8b4c1722598f677ce27a8b0633d1e176ca890d822574700b1797130cb0c42396f1641124d88d176c45f7f0d0aa1b0d116814fc9627aed5a9b35bb15575b10c3b3891f7d964060f8290f822b28ffb8c06bc5429e1bdb4a65be95fb12169709120049fcd533deebb12056dec2c65cfc2c80118440412cd19b34e6977401bebdfb3bc383ee68875a0125998153d903d4df5239a16a2b587bf75cf5dc6a35fd01cb804059f684290f752c2dcd23b8328098393223aa24e9b7380d24a9a3f0a879539e2b03dd3d843b5fb89b84ace681ed8232c96b116ddbbf10a212c55058fc535d95a71d9a6288cb1fa36eb08f82f2e67aecdcd43b84d2edf2b0c21cc347476c07585f54b355f0ccc369084f0a8cfe9f7c4d8132dd4f074150834cb5ac8e10d8cba4834eac89e59faf5150d41127f7f3da37dc4bb7500ab0fd38170e28eec8e04bd362e0872118571e25d15dc50fc819caae8c07d924e2d97697d31bd88d982e36405966b556f28afbf35a634e34746270b49f9b42e2e2bf0239d794da4e2acaa9b3f5b6df923588cc693ad176d697faab650df59814318c22ea355d085c7d80d2996d0c7ac8c697e6a8fc0e0a6115a58992180f92b294795ba9463e91bb8aec050f95649800290b6715f45ee7bdd5219093ef7c70d1c2d1f3172e54478d690aca5fbe38379235ee0422e2875dff6b3508ac1e2861227a6656d79377d0b2e04d78adfeacb6db92a5055de27975495ec76d18cdc74629c1eb68c0f7d73e1a9f1d65366c32323d1107a058ec54674ebac2208387d49284a8c28df0dce5d9d02a2e68bdb54beac72bbe7d734b38181af23d9baf923def6fd221ae8c7bd90cc023535fd2826281b83818ca820ed207f59efaac027f69b89a23fc9e59ed8bfbac35cce63366c627aacdcf1d585a651cbff32702a1d7baed804d469de068884071536b4c8a226a447552f90892b5f5c0b0d02bedaf6981508d10b863ed2671de523d0d43f626c5099f22f99488b243b17596306ec5e176a994dec97035550567bacec5cad2e476ceaf61c875632883f96e84d54c141f784d4ea3bd92648b8d872d8ff0df4f30642bb1263c5799b2aa2b94edb9161c22dcb23218a78d4b9403408c90e2f826344a0bf4119c035ffb9620e225ca096f4bbf69913f8a020ae9097463d3ee490197ac9235eb36b37062860f9f1c6696c4585475bafefd2a361ed608651f8cb1a518b80ddedfb9fcf38e80067077986bac05c65a22c0c91c27c6665bddf2626e0f2262e939797a6c5f0ce16b44b87c5381a29d4d8299bdfc53a80eb8899d7fe566a02e28bd04c326a058d50812dd9be4502b2c38705ac63d5186899a1aa4a7da75f59b5666e99b979cf05c393b3b34bb231b9316cc543351bb03d7ef8fa967be9bdf2ec72459a59ccaede60347def1f368649abf50e31cdec0e201e3f585783e4d92154a260506dbbd13655ba6efefb2b04388306ba2a83450cd54386f9a3ebad69e1998aef9e9288622d6bfe5359349c84b56293072cd82d0cf13349d4bd026bf7310b48dc4eace20dec3526d75929ef345d47b02d3c100e255a283fe99114ab7fd548ea5cf29944b5235b2db49e66165f7339daff8435001a37b3a74a5a32c75157b3941f422ebc7ad5571613388811cc2fb73b0c73fff362f7af6694b3c6ab493452f6a6fc41ca75403d1e9ad0c868f9ff5df4d336ad5c022e516532641349574a5d901d1c1c579209a730433e34c7f30c6b20b49430ad0576aa54dd3c8f6d2f19aebe7313ca1e2980df0400ddb036b0bf56b0929d116cb784fb1dff610363086f5b3dc93516013fc0e4fe3ee008e88e2806620045f97d635e8097c15f3dcddfb3fb7a4373380b3dd01d426cce51e97d0aee0481bea5d44a3957cd7a056004f2435ffe35477287ec72788d519087cca9b27ab7fc1b2a0a2f75ea56ee1c93ede0390a4a3d2250ce97856b71e4c636e21ab03784065fbc70d342ae25ae38676cda860acf5f6772e6900e88159f365c4c4eb9fd04820c1ebe2c40b7068b6c784b32026958b29f7717112f93be5fa7ed5791e53c1820bc4228110e8c72d4e102115c2460a993eab2eec994e0c6b986f858cef0399130a2d0c2e960d3dc855ebf4a57e4e673fc351a86e4f95e24a021a57b74e3d6836fb55dceaf40e8509a593fdf7e84e828d51769cb294ae7895e3a86e982a1a6ac5f46804a9290dffdcd9081f7c7e71e6b9a4580544e1d07964c0e29ba110c2748a0dbc861ec8531748ea0399ae9689d96bf57e853effc0d798c3e037e160ca816bfc251b2394da3300d54083ed58b57269ef6c7ae6224c26dc0bf0c5c56902d8ef8eb066ef2eb603bd219ee97f5586c872922595fe77d2dcb9510b84131a3400044057444e51898053a1bd84a854f09c42e7701174dfb5612ff1967385b55cf9e0682781d12452fe075b04cd1413dced7c52b60e597bb53c9071d149f7f198b3fac95970a106a5933f8cbd6416f6afde1aaf09fdf37d2e1de30d7332b3283d7e7d5ef2010c611c463e78738699355a98792f0a66fe3f3033aa73b9428fd4b9c9d2207acb8c4f4f5ae04bf768b00e7cf65b4f1b29b9cc53f8839747db9cfdb48fa9526d38d57f10f4589e05f832076ba5e941c03bafa431e94a33d05281cc9626444723a1933ec87a3582a984499e1c01a79745d7adcf61cea84a45eedee0cac958300f361cfc83ffb939b92e63b7020f1babacf2e1399c4436c46fd1059e59aad7b458ba4f89f6b9213fd38db6cb81ee9b2ce5bcca898ddb9a5e2f20027fcc3bdfc073af86223bf641c9afbb3f85cf9bc5d7416df2e92c9f39481b8a62b35e0253bda1186836ff162b58621d934836ad1ee9deba36857e1a83f41ef010a109ab5c9851fb316e8d15fb795600b946e9bb4fe5cb0446e9dbac277f2292e883a446fb4b393a11b019c1ae1ac9fb4789f61b18b15ed27d8873364c4ee8553286261344da980d2295b7a8bbf4db7b89238e2631c333324ac37d5b2f1d831a96a7f141ae3bbba84bb32d337b31a6f98566492cb12e84cac3810651e80725a7a5484b46fbc074255f6110ec6e349a0222556761f6291d1913a175e80b76c8be6c55918ba6ba9f67b466c6e0ce98644fc645f6da538a6230cdfc1975778c46a1488adc089821e5f9bac427225663e33fbde91032607355976876375ca4a98f56d1d9ef977619b18e45aba296b9ad9d431eea856071a3874933aabb39baa53ecb6507c4a6d87e5ae4b05ad33d983b15ad95a2d38db1f5036c2c0c1a786fe15ea6dd02bcf69caf65b9385355f72f73823e710334f47db116665fee051937ad19b836b36b843f5e44e4c4350c0eb6618627f20da3c1066be231c18618147e32ec139ed04765fe14efd99f9d519a11fb61c83a1a5d4a2a105176201eb10a1dea7826837c42df0df059a973778b694ecef96ff10e4918f0428ecd81e109f0099230ba0671b25d8caa1dffad38115790d167e64c1849bbb2c6c5556230af1acfcd5d3ede0ca90d3d0ccf33ac5d65b2d59f137962c189cc7ebe511f50c938f536273cd40b816ec22f639bf02ed9f1bf419b2250875966463998f86bf8546f2e096fe50b744da19bbcf1a5ae371c4ce889f2650a740e53bbaf1fa9296804927b93c70f1d182a8ecd4f9665287ae87b5945ffa5e62e52ad7812feaa9c29046a856203c429b1e4eb51ed53fb03d82e311209426d194593b68c3adf0bc535ad59ec3991fa33b00d6d4fd7c151e68ef40aeccb78fd9fd122c39124bde40e94b2028e16eb2ed3e44ab17670741bc5541316ed264f847cf221103607b2f1678d6d0227f747e4e8668f8ba34500cecc66337571cedb40c1324ab4951fe15b71ce7356bd3911022741e2446ec3037ab0630e97288e1561e2f71705c8fda167cccb044f4eab61afe1c6acf700d657bccc7017543837f4516343e9e71a5e862bebdadda9a2edb13ac572c3894bd3d75ee565829f9baa76aa7891aaf1a40b7163a97e07bd302209f98f95b5628a2f45113c1dfb781b4769be61dc0582c8d09e4e446dce2bfce992404923bcda111c42e64c21a20f856fe8200f93782f6aff18183dcc1a0dc1cf16b7180ce1504e8b72cb68d90f0d4ee3b6b9bb99f0cf92b7f9ecda7e0e94c6845ba7f08e5c328f91ae7614076b8bd6d9da48ef16f9a29d5c34a0b4f2a82d2d92e8907dcc0ffc97a19456e2e2413e794a53a0100bcd11cca2087c32236746fb3716f0a9c62b33796416720064833a0cb62fe92d62ca463fd5ec128234b37e9e518a6a3c9fa3b96d0d88fd3015be877ae4168871932f558c52c932a610cc03b6683c95d49605d2ebf2d75d4289d0e88955133c34b505ef7294bf293a0c171f8cd5f5930c0051bfb62284557d06f04e4364f368d189b1bf6aeb04f00ea599791389e3332daf6a701a358d2422f9ea68468068b0476085094f98464bc305f0ae6f44f3d2adf0f4b4a8912ed59932b10ccc8cff76c7d11049a6fd1ccc0e28f76f5b861fae068041dc0fc8f16a14e7f02e63b5cfe61c622ad2a7092de1fb57295056ddd9e5c0991d0629c9894bdd30c1ab1a0e12b2e661d25ed5d981498cb37eb2768394f39a492c4ba41434ae1f660e9e20005dab853310707622b0c019ffd0a4e28201b8769117f95cb0acb209514d7ff9bdc2477fcfd1b8ee140b908982c6936b0bc49835d177b24e6f80aa40946cc69b85cb61c1fab107776a2825b8d3a2b47a91ecf32d51e2f2c247cbc852b681af9cebe6e3ddbacf4ae40fd80794a810f8361399b81a1d8be9b5a615196930ebbfaf09d16b91dac2baa8ad96564fb305cc434d966545a4bc6e8782253cddccfde64bec9b503320539905d555b30e09bed0576dec47bd8a54e0a9d63acd2556adbe217eb1d7efd03a615decac709bbc4bd5e4bccb6b3ed5e8a42d9d08844c3c647f95bdd75dc7370e7c4806dc4864588a7b446b456dfac8d92511b1082553e8f2c3457030aea4424429e5686ad1606f6e119884ddd684ccd05fa913602224155d2bd2642e51caf24bfe2c2254bab7bf56c324f835adf74ba4c7715e1b3e88a949db7b28e335961a76402fcddad420538d538415f50eed8251ab0a7f0a884b535605452be2cca302c6042a24950f7433f69553d2a860757aae2eb178865dbde4ddb1dd11e757cb2c39c81ac1d76b2c94c7004768786a8c5c94ec273e4d51ffd226c659a967c2a05da5d01214079feb612673062507ee4ca0a6577ad24c98252850d9c024ec17264601305ceae759132f9c7eaca1c172806379adcee3e8a3e3313126eded019564711c9e7c84f27f853d506964992f96a3e5a817831a8dae5e15f6d52b7747ab574376f2bb2e0acd8fe01fe2e1c2ffe7c08c1d677552a1fd96b2cdb111f3d0dce3465938d25079de769c39c642c0ee805eaea7306fe96423d02fa9566e3fd4090a6e5a4a054a1bf400bf7543cc5c001f5a7e6017c64d90d112afe7b26e5ef7625cbbfa5b8269da9f6ab20044cf771709e11d5c2603329380de4d5b5009af874573cb3dec4d00bb34264881e1404240190b17c84ad5d67c6e4081426270beadfc6a80ac8e0e25d49c5c7b11c14d4f8d8aeb1c4e9ec29a1d8f68591c716af9cb803bb5bffed4615bb1b306ad3fa31db16f7da7da8444a70409203c92168270fd23c018f1a40877ac1b44744c8370a116406a02571919b8faed40100d628c19c55661d5f4869c7a24ca0d87e2a7ac55cc52c4fc7c40ec9b1ba40137b9d71589479dac73dbac12a04d29fb1e1960a37937c2871502491079ee4ee98a375b6ec9740ff6f3ee9257060ccaf5ede7bb1fbadc6a34634caf92331bc895ea24b069074aeb8b94642fb9979db6ada8f76198668d43ea5828008cc59fa8f30eb37bba08b922556ab9c2c024359c2dc6ec19678b643a5a8c4ff4700562a18b15a1553b63a40c7014ebf125341100a453ea14685cfd218df9d4791de3fb8ece79fd78a362c529104088e8a5951ae1fba028ef7295696f20aca994c5514b90f3f94c9414dc42a36d1bfd028394d2545d80fcb540d99a1fef7fa349eb516fcb2d96dc25f21e1c16bc424beb1245d33d0229db7ee814939a6a4065ded5fa483e913ecfec27c14474b88813d1cf5b06a486a49dff2f41052f1f5903b2eb406c6615985cd659d2b3b3abfbe0442f7dd3c33c0b903f3cfe6839d9a9076886ce568cf089696d301baf18c17a65509e72758047fc4eeb7cc1e4db1f10538e362b4f5a25ad9ee2e25780a438ffebcc33d54186190a0d039135013f30c9a8e6a943f4842d7f0fe101103a68fb8906f6f9ee26504849ef3a60ab7bdc7efd3f9dbf63fa0a8043dc03603165bbf3a227136a48c8f4f7a978a6c05b5e6a890ac0564d43055e22d5eb132a00986a76f83e9928578f072f6bb3aba3a7905f2eb9cd5bfc725ec114e69e475d5127d22f73b619a3880906de89cb87451dd7272025e4250582bc079b97ef02989e06d973b6ac7b8eff4f3d8f765ea5e317260e6d5b12946ee326e1dbf6b2a2d0de9a4dde0f390a35f198302d5d963a7127b83d532233ee021aded82ae303fefd871b146ad45eb63945a80805b555154e7c4088eb545ba5aca314514eb9f3cf384fa85acc8cf27dd7bf87f337e6ec2d70d3a5d976586828e40de7f614db195d4a29dbd536f38d548121715f4c1d6b75e49b45b912d96606240b0922ca63e80ddb1d599718c46fd7e6df5d9a3c7382e722bc3b046a316b6d14a314818eb2288f32dc50e1a1179791afb50f2437d6d9a4bf932e2911d6f3ddb0a7e024b000968c44c8738f8eaf37ad7f9bff93886ff317b6828e2c1c81288f8f4ec0b986c34c8b63071403c7f72a2f56aebfb27a0c4492d6107e91185c3037812e72fa776b2d296fe619b06488e3eb880d2f574a7799c1aa7186a1398ce4c518f0c2a000b37eb3122e4c6d220d61adf2badbee3669359daefc311313a9c8925339db6b62335b80fcc53c01871159099e11578562548b38e1e4cf75cbf874f64af01e9f5e60c0b4fe5602701b289a5ef8a518fb71167c28de281fce9ebe7d200b8bca7a0dbcea9138380e550a7fd40132c329351c733c3c6f21066ed43d19dc3fc2ab8ee6e986cfdc2d5c6bb234d81cde22f1ca6f674c32cab2b72a232aa05ece2419f4c6009f04c533b6d74f7a3bf60e2d4ae802b935edabeca00b5c453c0054815470ea1f69a5b7b224aa40d27ef88dc575856d175f96e5306e6fb5ccf3eb953ac1fbbe994ff4ecce3ce7448fc29980749691f8006eadbf80c115e4f3524011f3e88deb867d1b33381980f8af90f551e21b8758addd274dbb4ce4c0724dde52fbb86460f927501b3ab11db79f5e7362ebb0d896ff9a1ca423c6ede30a61408cf93021c289363220c090716780f704b03778443cb2f69d55acdf17ef998b6f849693713cd2d66f51592e230bf246d06565745c46cc58802dd3583d3128487c25b3fdbd221a25d01438fa89648955106366bec4c254e7402cd9ccb19a19b1a70a3b5813c795c1abfd5efab3f759af6d2ea2cdc3255b0019376f99c1903a0d6c94ce6f652a31c061cbe638fcca6b1aaba07d1ebef760970fbadfb6504a30b62689fc5802747e3b7c677b455eb50901599c5592d584268358cb31e019f8e28ff6f247b1fdb4a18d5112f76401b3158022cab1494cb79fc3bc89147191dec29955785a68ada6026821507328e4e38ae85a1355141cd79e118d1713fd9686ac1e67ba3173176b4c70878bdd4d62883f48e0d4c7442dca3a3f4c5f453b8f8615df3b99ebe285892a4909242f25da0b1a968ea134df08ce5255b70db6ea1aea001140ea6e28a59d53a5a6122f539744d361456417dbd74b6686dd93f1546bca8e16f691d34506b802115bc3b22c4d71b4bdaae629aad25561e7746c3cd11d8938d9ccc29accccda95120338026598919ec4487eb6d5ee3521fa419478dea4ff62025f2389faed52c0b7bb46cc09a4e6cf480b6ac1fa7a10c46b744aab6508ba7cc8c1a60701125060252e8b5ee602b6b8e156d506321442ec2ae029b57f881a6388561a9ebc064f1fcb6b0acabab4e6df5572c7243bed6727c093d36442fd69c07fb9fc8348975e8eed7a48c29e56333ec05a7f86e067df175922ec4bb365971ef362233f981721fdcb1880f3dad821671bd5aba306d8d33378a77eede2fbf6015a01172430e16e3633acc9cf0decde5e430454d6161a24235f1cf42f0857aba0aba4f363a9af85c3dcdeb067d88716562b03ec4d77bb16872307fbfa12f37585492262a0188751dc6b8930373184a03f6a5e39602f8538c7bc0464f6928a47bd69608475ed8a802c62edc70ea50f6bda90543903c5f5ca389a5fe91451a010dd45e7d11c873f86c4b94fbe5af2414c95eff960c09d1ba3ce4c1edf1ada8ccd8d500ad91c617758d3f85474b3bdeb9ac2a780d0ccacf7099e84b26618760a7f122ddf5883b0e106c9a05f136833d9fc12e6dc3201d206107b09c254fa7a4f7b8d55a48b288ff8150034a3f63e2095f9465b939d803ec66a44f938d4b56abda57f5bab7f07fd653f56c1bf0cda4cdc2cf6bdaf12e508fc33d920c317ea860857a3a08f364f94fce0e007bac44942383d97ef219c92596013e7ea857f35674db1e6b124f5f1d5cd2a399217a4b37d3e93d4e56b318e10bd14a0d9435797d25331b180be34e5003ca666d1c892d119dc8b00c2394b8470ff79b04f1c36ce2db3b975fdc880c7d9095d810e9880260e58225e443ecdef7e11d93ff1f265de55717c82920cca981ab0e55fa2434b46031d7df7f2b5db934a7ee3d847074bdd6f59b111ccb8970e0de8ddd82859a29df511d12107feee38763bcc25931f669d91e16d02eb4a7e61f4222a85a21b8cbf3764be7a55e6dff78da96fe692489a3180191c5f27f54ece1f5c3f998fef1c5ef9cdf499755ad7d972f11518b421498a892d3d0f02a4fd645764f32caa501898dd26f09c90dff2217cbca54572b41e9f422dde1ef6a2eccacdaefb7decc4e2bd3f4697de3ccde2488299b250be9662c6d7c0baf8eecdc01453ef1ce916f5218b7abbfa400ad1e38b1b34ba54d8659f100d62ea368fc5f92cf5b4128c432b50504d406d760105ff483d7e1f7a06d01430e5b552a98f464e60d4d133edfbf5a981c52bf565163ff81b10207c3ee85cc6d3afcfcfb71878cc1c4c203843744a146cd937f882ff79d6fd9ddc8464a5605b03590fa51deab21749d9decce17cc20600d85b9d02a0e5b0004389b96905b3f2597a1bba506a91936d57b474168a8080fccfd24f145fec3f1ff221ebcda7d5fe38eaea33794f95b265cf7d26d365dd95a7b2dd272c1ea7ab3149a6a0f068f771f5ad845339fdd78ace24f6da42a60afa12865883a7918f93b5b695beef9900b83d03f2a0460f50ef397b3294706190770f8cd840c61d8a89742d58d1cd1879c96821cadc6a065e7ee5cc45e32c9de62181e4737bc99dabceed8e052994c66d9b08f974e6193e9d08d8babcfa040c74a4d58a817f86ea11f3f9288fa91be3c8cfd847b42f74d2e5cb2908b072682404812cd622ec6dc8c1dc5befdd404ede25d1eff0df141870e471401b56f06964da8bcb5c25ff00a8290fd64c9f3aa4250d6dc430b47770c673f6ddfc1b4ec52847b088870222947568bbe9fdf49c99214c1847a0840897df9853593da95a6e859195e743c251b83873d5fb860a7c5a9830b54010513b8a47c83d18755fa02d13883ab14b60b5c5576c2d9dc751b3bd3f4670fd2123983c624e2b97cb850c4b56c85c68379edc2776efdb682a3e50574765b45d5165e8a7090289afb772d48771a1d35a60df739ed4e4871b99ffe95e22bc3fb271ef831a8d183fed3f9398d8ab926798c48876308d878ff704f4b2daf08392bfd99a3938723cfe78f05e8ee3ed694992c539bd24f826766eaf15ae1f72f3545e7eea45767a64a506bde436f536337754389336d91f8003fceec451870824211631577fdfab7239f5221f4f959e95ca8db3bea0031bcfb2b9b69bb0acf89adb5ae3e85efa84f5caa7e84a5af22f49850582b621e8954042524623f61af2c20662dadae729398fe710487fc586d2a2ca1e3151b1a233df3841a90f69b133f7c1529957774fdf505ca804f202582613b1cb3341dfda9c3ea3eec5b067cf1329f4834592a217a779eb28a4a9c773c71716ecd8917a76fa0d760bd4a838fa62dd0b2876a2757f447622848c18871bd94d6b0eb7d0c4fef3b7a452f8b1b46516248be8ed619e67c87f1166ffd8ca8f40c179808b2a86fd628877b4a8e4f41cd05d9e0b0d35c4659b21cc7560dc12511c507ab4589900b07d7b5e3f6e27d428773bd44a21d400b1170391b552900dc4ef76ca0b21a470eca1188438cffb6794b638675b3021099dda521538030a4a4647315f82c1c901ef6ea60a897d6ecdd144fefe0c959a8e41f578305eaf2aae00211dfc4ea1e8700484c71f6a25cce80ab3a841a9ff4c5dfc334e1cce5cf35709107252a4990c2367b654fb89612989684c5902066819f49585a9148b052116c43e9ad76e268a98c8de63ee9a714d8f3a256a4d426e620abc4e03f86f934f7d82e59a1947754685bf18be0d43ccdfc6a5f5d28d82c6f65be024a66107d6054ff1bde0246a1562ba3c94eb9987c86c894c0c02a45361216f810e1a3b7c98cb163ed352c44c439b7950f53759bd33e492e609895ae025d214d141b3a2230a78623ec1b023ad7b77bc62bb86fb9dbbe02448710dd0f746515cb6be570be8f2996f64db26d6992da5796fe8894ace0b3410b8bfe80479269f7240bb9b788d6441c08628b7c7887ba9def329eb24a090784b89cf4b0b706e126a699bbea6fca52d0522e0f7814c886882783636fc8085372407700e3658d64c0412b7697688c2a0e73c8991847d9aa08c8e68644e4880137e8027dd3b8a1e1eff41cc77c1f3a813aba7f0277b639c6f5fd278b1f58c1615792db173c17c1428e6f132d8c34c59efa520235ca3f9788254ba977a0d19582a9d6535a4a8356d711e1c3ea004f68ca72adfee186d70daa8e6ca144753b52acce2f1f270010e99539f1523fea8793c7e83c4aeb1c187ed5cacaa0ef3139d975fce5c94b85dcd16e5ac641ff0d8ff28475db2c3253b47053eaf0c1d4d03cd96ec4496301dccbf99ec338da2fc81b17f40571c304df64510ea8de0644f04291420acba3250ce93a35ffc2c21c519c3c0ee0d4b01ae80d2d5ad3e39e509710775b26c2b41e5c248381fbb05a1573a0b2be999a52a5d5ad2e6f33f0e387fdd894172ce846deec97bf6ba2ed6eb8244f6229f7b96004effdb412fad5b91d6fd87fda4cb82444fe260d58f98d3357495ed8cdd090c7ccaf04f63dc73d38e38323efc5474d2213b7a0a44497da5800946d58d6da3e2803ba048aa73e93760c262c33a8b23a4224a9ec11288efb1b01e07dc32d7a0d73365e90141b86eaeaecb9be083eb45e648e003026c5ad3d54bae96b01851aaf565a370b6a0b69e5aaf7424d7d6206d36abbb22ee40430ae08a1c9e33e0ca599709b6a3b7cc1aa4898c3bca704f012c01046a3dc3a7582018564d07f173b09be9d6c02f57728dc798bc69fb187b6732d3649d861e3542dfc7bb257b4f67d770ff80015c20969817efd16fe5a43159eb9bb12cf5126f7252b3c16bcf5ec447f58a4f1d4d76db7bef2de59632252903360a650ad909a76b326191cadceefefea04eba747447a61acb8ca1dfe1bc728677be8b9a37a190e95759339fcc183a9f4e9bfbbf9b79badf7f45995b0b95f829e5f264d68432863e9530f4c7ce557c1f61601f676aa67cd2d4a4dfb9673afdc53777378b0947d67ec41a4ba6ad7df77744c9b7a43d89ac1744793aad9abd1bd779df08a44199e68e54c34649e5060ed34a0e1d2c2d3b7874a14769a84f991e27af430de04d4d562dd368b3746f5559e34333a63a6cc6780b366bbc1fd89d1e2b47852361ea733a43c818efc75341aeeffd589885d9278dea0183f703083d1fafc74bf54b4de120d65d02a34020001e046178b083c16030d0e5c16e36035f78b06bb55a20283ed80d0d81e183dd0a0441f01fec8080401e0f72b3d96c06ee78b0dbd9015b1ee46030180c6479902b2a02753cc8b55aad1698e3416e08e486c0950739d920089a1ee48080401c0f6eb3d96c06de7890dbd901551edc6030180c2c3db8151581361edc5aad560bacf1e0363463ea10487a705b812b109c0f821b1078613018ac08bc45207ef079e899edc1df41d274ad560b7c245d87c02f226beed00a5c812f7b05de15f83de80f0281337006ce40f0b90b048276068277070461f8868000f0523018b0804117d80c0683bd3012bbd650d7ea5a5d6b28d7fff0fb6ed50175ab6ed501e5fa3c78783b66ddce6c36eb7672fd1d2d1d0bac08068315e5fa2d3ab81c5c6b886bb1ac6c266ec501712b6ec5ad38a05c5f078e7b639b713be036e3b6d98cdbc9f573a8d812ac6883711b0c5694ebafd8d06a6cadadb5b5b6d63694eb9b4858c0358fb6c9b6da80b6d5b6da561b50067f9a40101447192404560483c18a727d1c76bfe1e8ba4e1c7ffc680db55aada1cedf0dd35d5da0bbbaabbbba40b9beab509bbd656133a67e90c7f27c3c9587e30539cacbb1309e57de715927e4fa3eb4c0e39dddd9944609e0a94fa32c8c2ea14001f89132c9a3d38f9e2e216b6c91cbe8298fa479e1474f538db22c1655b1a80e8baea6f4cc9b216baccb75461e894f1a655550a214c17a26e442d6581f9f260c491a1ead4669b15850cc3562f9d17b14b2c6e2b80ff2e8fd47d2ecf8d1fb4ea33497cb795ceeb3248f5a7ef4ce0259a34181e22ec8a3f71c49a343d6288dc59ab13cc57255949e315941d668424261c4244d8e268dd254434f462b57c81acdc7070b21498383d5a81a8bfdc45aa36f9f9e29fde8bb0959a3e1cc983afa1e7de334aaba5c9de3ea9d910a1ab2a6428192c614496383a85195c58ab164acd9939ea9d185aca942424f80491a12934655954aa8c908fcd1cf1fc89aea3363aa4f10f2e82790a4c13f8ac5664f8c35e26090473f8f903515c787465197bfd4774d8122258f1e04b28642993a92a6fbd1f3d033f347bf83a4f118261a3d92465196bfd417d2228fbe88aca14233a6be14c24154a3efd128aa3ad233de8f9e88a4b93ffa53cfd81f8d01c8a3c7389e619035d4c745d2d01ffd6d94c7fca5fe68843363ea8f7ef438be21a3a06c61b62800a2850140b43018440b73118b72fd177c8881c1b72c9765bd6559962bd7b7218e42abb23e56f5566555d627d7afc1e36389599c184b2c6671727dd20eafc505c5c5e27241c9f5b18ecea4b1843496496369acfa600e6e4553693e9acaa4a93495e693eb8f706ca59886132bc5be58fdefc655714119b94aaecf0525d7f76cd81a95558546228bc512caf53b920656954f15ab8ac395a3b18a23d2188dd1188ee74e0c4cf3689b50171497cb0525d79f9b38fec8f5074b88c562d5f71bfa7bd7fda4f2a12a9f5cff86f46dca5696007801f88abc70a1961b07b13469e512e613b18f28d8f327a9542a05a44fa3b014545fc60b7231c89f389143b1b3bb63677666677767c54ea19e71354a00b864f2c249d4a7fa3dc209a55fea77eed9f798a3eecc29ceace9a6cc986ac238966b3725d7c7c9f56948df862c1ececc519eaa5fa6cc1a2f081953bff6e43abb33efce721542c2d41fbb2ee4fa9c978a82e97b41f74ae9fdfce49aaf6dd23323b643f6e5212e35eae56bfd77e9acc785357b6b68d678413366e5282fe52ff5316ef95076cf57eef96a95eb8f5ebe7379d2be432b4fda634775f245fbfa5ed0ac2db96ada8f9d472f28d7e73e1b3e772873bfe67938d7495f7e53a433e0fe996b0f20fcd1327dc2383f018f4ebcfcfdeef67479ebc89f9b437daa2f8070c2faa57e0f592934f5cbbbc8649ae73765b2977254c974fa9acad545fff6885cba87223ad9012c877e5a597b1d46b4af341cef6f0bd07e1b671af9a6f1ddd4bf2198ade860a6620f609e528c6459238013ecdf239d2eeaea93f6429a004bc0a37d656d356be66bbfcd60b4af790409e31ab720fba2740b72d4d6b305699f235301cf9ecfe5dd208f5d0a88f2e79a50c0e3e7cada6f3dbe6d3d77ebd97af24846451794a7dcdee427ffcd337500e8497b11c0f63da976addb2cbd1d5d89b9755549b86403c70d1cd6237aa5ea743baa8aa56978807f533d5bdc415bf39b8707d694a37acb3bbe1ab4bc8957c2c8882f48397b64f91d0cde9434036800541d7f99bf5ab1f26cc1f07279410022f7f77ffbfa0db9dbc6fdb6f1d0b6991d5f6fdba1bda6f5f09ce6b37c7a7a011e2f4c0b0fd1070b8f1762643c3c3f47570093180003fcb81183abcc94502dd6134b24677a6249eb67c913ad1f5f5259354212ee8090b403601af0e768e6a3f17e8b570d118405bd7a068c6d2167c03040582c8c9718effccd3c22a534efb351d2445ce3d146d55455e51eb15649f09c90c0234d8287d5c39304ab876715e41beb113b2b55e89db79d23f2ec59d111a7ed646e3ecd1d02bc3bbd4e066f06211eed93477b86f6a9b3f12e8bd4fe7e27f30db9281c5e4f8027254db55f32bde7fde8e58e0bca1a1794c79a7afb3e6278d49efbb1a6aebfcc37c45e2ec44028ede873a1d7791488e7d1d06179ef6f9ba6699ae6afd5ab699aa6bda689e3f696da986f48a5dd677b80aeeb86f82b571c28eee3973f15284b39d6a0dc35617effcb4688fdc5891a8f432178a8f15d8db0b2c2569ecf8560585355f5023cd6968f2c3f2d84d1d1c21ef9ceb7e194ed779aa78502c81ed67a92011e2b509e9676aee228811c4a1b2d7cc9a4f0d6d03536cc2044bc1fbd6dfbc9daa74835c030c01806b5f707618eaa214a9278fbc158cf804161c819f024c120d00a0cd297f986387d04500f6fee640c00607079c1074c8f0188e1f3d8d1c22280975300aa92af9168d136b8e8030c1f60f8002377a09412664e70488ee17e1be86ce9f3d5cdc9836d98b023271374f28cc0f6e33a79fe9cb36e2a7f991f8bc96479ce394516a5de5377eaaaead419758de53d55dc52fe02b3051e7d8795fd7da767da79b61f2f42275d464f969dd2299330b4f31f57c1dc8b207294bf9ac834894c8fc83496a92cd3173b716bbd1cb579ea67b2cd6379d4883422c7af1165ff6ea84fb03ef9bf248cb7b2df7caea236b469446ee3c16c81476d28fb8f5baca936a4f9f464750a52d3acd5ac464b5d4740b328c7a0b4565a297d2728a52f3cc162a294564a29a5b9d64a2ba54a9ede80a788973195e0b1a38545478e15138e1b160b1b3548181c65f99f279fdb68183a78a4d96add2ee8640f00a8bb33f0e875b36708e827bf93a10d326f01edb5cd5880ff57eb7711ff4fd4514523d56b098988af44b54cbed0efefcf91ad807598e3fc29b64f27477fe2531c266182c023f5d1a1df871d07aa333a4cbac3437b24cdf77404fe68f45e38822391aa66e6be9b6dfb29b3aa4f74be90ee90ead01cff01ea17d5cf193cf251f829cc9ff48bf4b0737f363855e5f4cc266eed9e69741869aeb6efe9047edff155cf542cb6ac653ab0e88e539f964df166ceb383af57f41579c9ced3273a9ff489cee953fa44611286eae0efc3ccfdb4c5c4872069bcf07bcffb1a8e9fe889d4a74f1457569fe8bb48978c814767f964faf4a9cf48bb88d53372d634d5338e6a1dc817fa3eb842c06d85a9b5c8f451b26778329d53bce6e9991e097e43a637c9b4e44be8744da11994847cf9b96275d6575de43b6f92346781477f98a4b94fdfbb9034db53fffed189e6fd6efa67ff6f46fa7dd4e1a291535ed1a653d2ea3c3dc3893dfae42887f50bfd7b3136997efcbb7d7fdd46875d879daa58ea13b5b84fd444f34cfd302da07f7efbd73f42439fe69b98a0a59ef1a7d4ebef4127a5cb2ccd2642fb0e002ec023ce8f2969be7d53901589224280ac693fbec82019cb1fd8a14f9afc1ad297a10df5299dd53fb003119309e3232f29d794524aa9e72a8e40d06f0827eb3784137fd0e99ad4b8a79913257df92262e551c6b2f6f635bc83c9519a7b51d65ee3b80164c9451044f9fefcb64df4b49f01d39f79bc59a35226044a29a594babb53a71b2501f1cf071698524a29a5148949a518feddd04a3dfaf252d95ffc30fcff6f697916965f597993e95554be547a12e9f1f7bde7fdb6fdbd4fe9d7b7e149e9e25ebadb15fcd50565df5a6eb159c3e9e86cb1d98136e4cbd49127c5f11db9d2f4022b7568c7b5389d562b04a50d79c2215fe8e872031ef43ba224e7e941a1a8a7675ad99f63e2dfadfc3b9e9e999d8f3743cff4a3519690a8f993e4477a834ca1a03f68c08f94496886444d18921f69109916418f5880e542a23a75e447f7223b155c8c223f7a514b1412d52c223fba14d9a9f02c86fce8af47f58b861f3d06d997f01be41f7dc98a1524aa67427eec31729be1412080e50a89f29e203f36127273d15e1ce0c786a2827217901ffb89dc51b4143198d0902897f510b959d03120c08fcd4342d1717e91a715e61819a624e7d044c2bc620e21f7cf1f48140d82e1c7f9339f984de4990490cb8f3347fc710ef1c28f120db985dc0f821bff456ed9058f1fa51354ddbcc78180dc31e486c94d02619097bb173be0a7013f4860370c58c0841d31925240aa488a45846503ab06d610d68b8657025e36af97906d350b52d343d373801e171017ca35e38ac1f5c300052080cb6462563e5630ab20568fa001040920e825e85e8c4dac988b8ef8028f1c3a6ed4168f1a443688461714c78bb1a6e291c76ed56ac2cb15e4b29287096bd4278761e7ae85d3e1018bebe1563495639c0ab8d44c1686e2e84f3eecdc1276ee5682a2007bb95a2cdb6a0bc2f6b3b1b69e961671f49ddc7925ecdca9c836176caa2d359399ae0b0c22d86b65451cbb499e9d55c2ce1de9f614a1f5c36295661789ab735537a5a2228e8d936767d24b4a8c0846842dcb6261836ccbfe9048e23899e4d9f9b329cb845d591dab9aab0c2b431623fac4511675dec2ce5dfd51822ba8b5bd7c85dd4ae5a82e89aee544143d4fac9ac8fea326c3fd63b7ea21619afaf8063490817eda2393438bdf4f5312356118f891aa2ef023d5b1c08f74ca0f1cfa894475aa023f3a140afce85126d02ddf406ee81d2d896a96047ef4a008fce82e08740749d2fdbe2351fd7ac08fcee3801fdda701bdf22d0449e7904954cf6e7eec19037ef4d402ba6938d24d24ca7b8cfcd8430af8b19f1469956f22447e6c2c8424cd0d9644b9cb861ffba7861fbb35a44bdf47686894cb12f063e7d8fcd83bb949df4884740d2289a2ab197e9c31197e9c3204fc389f2409d24c248a06d5fc3885687e9c4d0ed0dff70d33c681f44f204933fafeb992281a43f564c5f0a371f041a22a017ee26403c8fc0802599303f8fd3cec2069b8981f654c12c13c12d9a3bf88ace1c04b901fb9bf874471ad233d73bf9fc8e9c700bc7b86a1df45d2685db3c38cf97ed337a47f36a0819f0cc8cc9ab9240799cce96060c22e306116104793ff8f59335938885bac02290aa42620029935d3e70620128840b32020069935b32749906df50071bc0e1047ecdf0021b366f22011c7f62bbb11c7cb00710134cc9ab93a225e97911e05f4141147933f9159337788b85c8eba3d36b86a700d993553870671b4b304b8ccc665d965d91fc9ac993942c4b1edcb5f6658c9b04280e89f64fa0719ca6e5933e6289b4acd18ff9a209aa0030401815d9438de1971c431883fc4d13598bfcc1a20668cbff6336b4a3063fc0da053001d02e8c88863c7882d1fe288fda78738b6a6fac0acf1a1db61d6f03063fc5bfe2fe2a967882a91e8224659326b6acb5b4bc2300cc330ecf0e58d18862e2fbe8b18861f86e14b0c670e5f43b5858428509e28610a1797269c0e8bd3e174381dd60bffffff1f8661f839be9bf0ff850fff85501c67feff2533ff929f275c412daa8330a4ac17c2ce62cc27168bf9ec68696969696969f9ffff6f59f96ebea525fcfff0c571e616b1a5a565e616aaa2397408ea4355f04f71b6a09ea0a0a01e1e2c2c2c2c2c2c2c2d2d2ddfd2c2f2f2a68585e5bfe5bf451c676661616181c2239c99054aec8b3c3f35934da1a5858847d879c78a8727c7cacacacacaca0a0b0bcbb3b0ace0f86e5856565a9ee55b58c471e695959595a099573cc8857c084f869a781158589c896c2593c9563a4c2693c96432adacacfcca8ae9c677b36232b1fccab3ac8826d3cc26e7f11e4fc25bfee32c57626545ec9ce3ba76aeebbaae6be7868a8a8a8a8a8a8ac9647a9349e5e58d494565e54dbf6212c799555454546637c299556653b6e0398e5386c954747b746ecfedb93d3a384aa552a9542aa9a8a8bc8a4aa9f4dda8944aa65779934aa9f4a55269a8340473228b116da1a2f2c21176be31cb99cd663935482412894422954aa52f95482f6f4a2492ca975ea5248e3393441289f453239c9914365043d1a4859a49f3a054ea252fd5eb655f536503638c31c6241289846b7c37248c4b4ffa12491c67c618631badd345346bd546904862e71a2c1c168b357146dff77ddff7618c1fe3efe50dfe3ed2e327e1effbeffb62337f33caa442e3cc29b3688281f1846253336553363553a0e7799ee779dff7fdf779f8bbf93c0ffff7f8f3bcf73c4f080c67f6a6906b5241047b4d2bbe6f068161e7110c08180c3685dbb66ddbb6cdf3bcf7bc0dfc6ebc6dfbdefbcf13c76d9b3d7389c96402cd259309cf9b3cda4f09b41fed47fb9975f7de7befddb6edb7ed8ebe9bed5eefb7f7b67ba7dc2966ccd5d4519d213b732a1f8a28a594527aeffd7be9f7dd5c4a29a534e4a2280a94265c2961881cd5adfc65ba49da5a6badb5523ad22c6f68ad5494b5fea83ffcd5f5643780949130b33b1d9e6c54b7f26ec9859b4206c8bbbc866b354a4e2fa690c76ed5df7d37b369ae2649d42991fd7d48c1fdddca51b26b9eeff293bd63657f69a7a099088fa7cce299dc6bbe9ebd40705096ddca9faefac56be4697e0d6790a7f95ccb5109902ff5fd7545d622cfafcfc39b5d33111ea97ba92c5f50928139674085ebe5052ce3dcb84f7e2f969e27615c7642bc6cc8e8410b5c361eea80f22d440f9e35924750be9e77bfefde3902c10f7b974442c30731100af8054804ea00e527794e4e0875b9171d7d43660d1b9e8da694eb1139569fba37284a98d95cb12a691b2ab1f91b60ae87f301e5ce98e3883c670e9e3c3f874e9eaf42459e2b41798e2a3af6a7cda882a392e332d89ff355529286c4e3862f447cf1a3b25d0cde7bad05ef1ddd7052a10b4b10d9d8b0a5089b860115b270d9742bde0da712b3590eef863d8a78f2b2b1a1b4e9747836d491e49a6065d3b178d7bbb6b3716f8d16cf864a6cbaee7380b411c22ab1e9386e2561fa418e27f77349c82d8b49474e4619a3e2e8e5c54ac2b872dc95a4a9332561547986765e81ebe1baee381f0e8081c7cab1dc65b81cf090de7ea17535430c6bef7408c578c7b6a39b4484e184958d0d75706103f6b810c99736ba5692caf59514c1e98f4048aeaf44874a5f864c9ffa8edf91fb77cc4047b97f047894fb473cb2929946ee704441109dcadf907e19f630d3a03ae8302243ee1f810cca52d4514486dc2f834c2377d4a3d35ea1d3a175b4be7fa7ddf00a78fefd6e738e078f7cc51e78642969be1a70ee61caf2e71e26912767cdec203aef3ef7dda7d551a50b08b7e7421b661a99db3e9f21cbcf48b561867cc51e66c8f73bec4dae9d8e48d0f9072d5f2bded4d74423b88b39674b8d7adac4384818ed863e69efa30c3c7a79d37e94441367aa66ce0650da732003b226c98cd15ed39e084877434981382a898c497bcd469e3f4a2664ede9b649a2bb7db76db4feac2f69bc4aa1932e3d276d97f72e07e90b2c67cd9d31fea71b1c75c589f3dabaefeee530de4c233804ae3f4e1c2b8e27faf55e531e3b4f2a678f8474157fa814f7ceddddddddddddddddddddbf47d249dab9201c9851ecb3b2fd66d19163c5f438666ebcca97dec6d7203d067f740449cf548a12df9bfedd4c27b2e35b7e8748436e1147d0f2fe2fec4e7ff2782fdc9e863b34edbff0fe28fc1e0c478f43f04921fe1a35be54fa1ba1cae3c0f12ba1a925ec0105a97bb18925e4f13a4296cf11eaf89530c777e1cc628803c58e27e26dc4cf41fa02ff6c1f054379f23f364ff8632f916d947eb1efe1cd189b4c1d653ce54e1ce7d85156c4f1945b42993df1bbef8187fadd7846bcf7f75a5ab2378fb08b665376842ee4ea17fb9203f2e52569a86cfb6a095b6eeeb0652c61eb087b16f64ae8a93ed937851eba2a7495d075fa6477fab4ea93f3380eddc747a12ff11f206f05f5c9be18ba50bf34991d652e69a09ef11f7fd09b439ffabb092025cc8f3c29a5007041eda6eddd9fe11ca2de4dedaefdd3adeea0ee281d45d2d4b7dfb22873480c59339dc8182992666e2161e64bc2d8f711061e7fbcdc7fdcf4c3fdf6f345d4deab656d9fdba4e06ebee66b164d2752ce2eacb5f62baddff5bbae90e4869a2f7fb13fbf50655a5f44b36cbf21b65ff66f1545dbb6d4278ba44ff3652599fa34bb1bd645aef5bb76bfba8a5da5c9fda5a35bcee8a4cba8016994c95fec5b6b6d9074a1f90197ac3f376bbc97fd72f4dde8a354d3b41c498b7aa63fecfefe40f3f73d80f9eb80745df7b7237a98ebe27bef03b9aebafdc5ed2f6e7f71fb8bfb55a2fa0bf962ffdedb7d37a3ae7bf0470f8ec46e76b7a148943d798ee34cb9b6a364fbdb8fa3dfb0389ebedfc41eea7ee4df4d27368c7beb11e934adeb36d70bb6e47e357426c3759c8907791ea5b3fee4b323eb12caf6ddfee9ffda6bbbbbbbbbbbbbbb35d7e8133791133b6e1bea53f7dcdbaf48ffbd186bdefbdff74423a7a98594e77a91cbbca16cdfc70c8f9a4ad3d18ec8f6b59caaa95ad24823dcfbf541856649d92fa2581640962f2226b92391784a5e54e2e993f5fe7a7fbdbf61fd2fa4afd9bc692f77d1a9cba5114fbc2fbf1f66f63495cb5c2661ac96a3d33395fa46df39ae5ff3ee3b00652873dfdc77f7f7bbeffa937adf7ddb7fadecfd3d0348c6a321f833a4dfc911977335cd83f449d6b2464d59cbfce8fe2b6be4d839cb77d3621077b9819b7fdbb778573e230e8a23f8da9bbe1bffad338001be222f99fa7082bb49bfba5b4ec3d68184b1df613f2163ecd7b081fa641f0cfb27ec1dcc182824cc926649185b69ed1c9f112a4ed514674e9f386ea5a34ac95cc6b1701ce73297712bdce8b99c29eb17fb404064764cb25c05e6feb3e18aee3fd0ecdf0398fd47df7d9fbcfe8ee861a617764d1dd4d40973cffe5e7b1ddf4d8b9af6deecd68c90a819e42ff65d307829019b44a3d168341a8d46a3d168341a99be21a389e3e254713f7382a6cee9df64c29815742fc653a73b6de2dcf88cb4278ea0f87da7813e66dfa51ee60f37d3f7c21ff013bb0dddf86eb8ef963446b8ef1f7520bd0f33d7787b310647de9742d78284b16f23f431648cfd1ae124853327bc8ec5f15e50f4fab91fb7e7bc59e333d2bf893fccfc7dd7853ea880592e9b35327b91cf7aa6be7dfb74d55df568a39ae52ff67fbc56c9a26cbfe4672a9c364e0fd13892866b9cc6691cfbcd7577c4f1dd78a2278edce3af48f79c0ed876a20e9f3873260e4ecfe8e81f01a54d8ab27d97bcd8f427ea72b9ba0278d38be84e9fac94a2cb0ae0321fc3b3e0453ea33a7487aefa648938caa7748ec5c9f6afbbffe644311851b6f6ddb3ff953532fb7b7286cef4903272a676e0f4795f73d7bceedd73239d26de18686ce65e6a3ce8f130985ab0caa7a9e48b7df99a1152ae7cf03a30d9002b316a30a0b2a987656440a2a6cc41d2dc5f592191e40f34f7f700fadfefa6fb7e1d5f0d57c45e9fa85792e1e179a8693a39aa9d7eb10f0464c98927dba794524a27a595d25c05bedf52595ab65f97344a767df54c0f336fdfc42484995420fb238f95656b94aeacb14ef1ba4f1cb51fef3bf187993771bcdf7df71e176a4cb49606f4136a3a12c6fe166a474818fb5ea8bd60c6d8a7a1b6d3a7559fac8b41441ca5f1f48b7dd77d61189116cbf6351ffb3bbe1a3a93a1923436dba733da44d2787694a6a2ae1963bf5db28e1f68f6be07307b7f8304f9207d9a9b2cdb8fbd4c2e2c8364ec370d7231b56095dc236d22db1fbfdc5f258a36215fec739246e3be9bed1bfdf72399bf2373f4ed63852c6739fbd4f5efda3eb0599f91ed3ff187495dd6a452b5d50114fb67137b890b911da6ce04ea1ea18ee5fc0cf9acf3bdf79b75e3fd157f98b97bf1aba1fbed3bd1f350cb5d38a4ffe6f4c9fea7d32ff6ff4d18bf6b3fd936eb3352c55e325fcb9130b60cacfdbd7d721b74e87efbfbe3c3cc9ed84b6ec8e2584b284fb6ef84cadbef9f96e1c65b98fe4f3c752aab5e2ede0b5f0d2deae089fda44ff67d98b9131bd627db43433da3e3fe268e807a91cbfac4eaf76e09b398decaf44bbf7dd941d8657a3dcc7ef7c3c39ab9d0e4284dd52ff6edc5a6f13953b6f3fb39ed3362dfc51f66be22ee93fd7cfbfd6dd664a3260114bef7effdeb0010ed5e691f4d708af60baa04fd42bd59e4c5088f96343ea58f171e5bd6efaf7befcf70a869e76c657a3abf452fa77ef909b9364a1bc76d2c264a2a37be26ba1b1efe86e0c8c138b66d7b628a0904ddb6ca0b3a53ffe81ccbbdf5e6d09e7bfa429aeb6176751b524a6bbda1274ff56ffdeedab90380d58166221ae242ee1f6bb27c1aea6b2fff0040240d779d56eb28151bf9dde6a8928dfcca715c67a3fb86d0efa4a36ad858ad0433a6fd47909ddc3cb97b34e1aa4844a4cd3c92cd94af0b359beef3bab0da7423af0ba94d077a5de8361df63a92d785d3c6db487913418daf0829dbef6cd4b0232961b6b7f970542598352790314ea344c2f48f4d431cee7783a3409b8e4660a661d38952bef84dd2a79b5da4485ea0a34636f27bf886499fcce974a94dcd353a1bf9262fc4d2519c8dfcaee4a8cd46e2e07e2f0d4c6fc8ff701881fdbd7b6e78d2515d78c3c6da4c9932050820625878bea36ed8c8efc0919430bf750908638cb9e0444a3983e5a1e103522a954ae9744105a334706063c937f6c541a39c651eaf34a4c8dfd3ef2f0b4a4068f7170b5d8801e6a8156808ddfbf61b72bbb0b68b6ca7b020db2955c8f60ca06c5ffb865853183830182365a369d58bdc533c6106291bfa57e218b8c093dd51350b5c70c105ace49bdd1232b64571a20732067b6c52420a4288c213b12410296193a41b7773a9dc3f42c85dc30665cc6cb004278a286c64a64d3d7959d91ffb9bcc5842f6975c64ff29e46b62c1e25206918f590b72bfe9fb04e49101042b232777e329c8e4b1b40526ae97942764e06421ad5dc1851661698d01c5e54615f0f4bf22c6a08d09c253f3a1b6a0a3d74a7771a45f35777f17a9d36009da84b775010aabf244892df030818c317e803822ccb0a53182d9cf124be4e06431844f7a5d98b9a2c8a7093297145334142772a010c18503278abcb882e7a4e6076a9b01db6b7f0452328218697c22ee13be4e5243981b1a374b2738f8a1b176779f4dbff60e40b2bfe722357cb280c10e40bc271388a4165de45cf1f3844eac2705292d0544e0f02b62984256851674c0851498b0620b65b895d256ac90b0557421cad64f3827daec86c678faf122630a4b9012032d9c6183c2b1051ac8aa30a50b4300b219e54bc81172ff252aea21ba4ce83a29693429a5942de594d26a95d2a64d74f5ab13485c14f2429fe606f8ed43914403020a1d0d982f3fc97c39c524f3ad0884145f9c3f62fff911e89732e868e8a49ce9a7528a774e1b3ae9327ef69f5f0d2548a55241648f07782ce552cfc899f9fe038f4c0225b7451d46da87d93f5f8b4ee691c630c1af0375a7b3e9ec71cea6b4d6eac42766355ac5716699041dc9659f9ed3323d039a4c57fee8a7a16defbb43e74ea54832c6df0753f69fd6dbb7b6fe976eefc3cd52c2d8c612c6df8623291c79c85489a3a0db8fd06189523245162f226a4046124ee0828d3c82194ba230848429528081fc2be5cbaca5642b0a2b26f4a415d4e405238222fb4983b50488f523616acb7aab32a9ad566d492d75a7c018632c63c2fe0283bd6081312e618c710fa71272633c73e5c3cc41cd14f00084ce4ecf766d0e3ce000913e0589957a5cccf227991f40847c60870fc43e2063456853cf34c6ddb3bbbbbbbbbbbbe76c1c9c4f7d52b50abb2c81310d3d33b68ae50736b99c6480041142c4aa724a7d6a156e55ab4aa612c6fd82c2a5675abac81f9e8bbc25d39f7ac800b17e030e37f40d12462f95f709138b0bc5f4524a2fa5940e75336ff98e2f7195eb388ff738cb7f1cc8835c093deb298e33deec39ae7295abfca54595dd5b59f5498555a026f5a7679854577dd5a16b7b7a66ac5272d3eaa393694bc2dc1ba4d4e362963fc9fcb04e592c0f7bf75e25e8206182c891249ee779dee801f13ccfc39e07f33c1c72c0017603ac47e874bae1c791934c1021449024c9414a31b1b8f4bdf79e4e38f4cc68fa71e4241344081124496ec8c1c462e24c26530f991ea7d349ca2e152e1863fc18e39ed9e475f52003635cc218e32ae8c8489ace445cdb05b26d2548097101902034504a29a53f240c25e2a24976208204871c3ec0830f2548d17b8488a32875517a04c9111711974e0e3a7943a7b9ddaedd369b82d7762db5d6da27f9fedf1e82e4fa8ba94f5307fbf747fbde154bddedd30d7dea7f2dcc54c0aed9eb9e22b2eeaeb9e804f742dadee03dc49da35c88837b0e386c5274b2935eb55ab5b6fe542da1c324f74f1dab3191a467b6a2596bad633491de2e810071e9d3d4a1bea98ab74a4a828011aa26511d393d334a17e50010a8a9440e3bf07023cfc8d7183ab967464d9b7eadd22a7429a5f42461e6d5e993dc9e7e89b2c8e06c4fc4db6c2214f573d0effc1b42bb7e9f4c9246e6df421c6a0e375812048c58cd17df94502ad998eed3dbdddfa794d47d66774da7b44369d676afb46b155dae0b13b47a2ba439a76bd59b4a413acdb2fed44125bb3badfe92869a289f3af5222e7ada905b351ad6dc39ed69651d51517a969f8b94ee000717acbc158137262c908c278ff8162143620cdc59be64313049eccd1ee0dca38c2cb5f5135911b9ff62133665f99212cb1e0ec2f7b17fcf9442208380cc97970c2c1c0c30f27c79041b4481440e589046105cd0c2e5c42a0a2a308295453f992fe7dff9a62944ba9068f7852382b6f681c6b2894faca07a4f11d4bec92251848bc5e2b50516aca02d16e2f7e9095b20228f32a8280d15b84c2bc660419bc337b27c15415144461e6536247cc18331ba2005eb052d702df9c014ba88c24e4a070b424638bd9014ea419e2fe7dff92697cdd2094e1a535c8e58b2450f9808ad863001109e2042166958a1e3c415f278ca634cf1811358e0d3841ee098920417e4c0065534a9e28af9f2ce37cd287c68c2881f2734e1029c554f61bebc623bc3c70c962c410b515c2d10832eb181143f5809d10420aecba30cda19ba2eb3ee8aa63e5951c9cf176130d103a122a6b0620b2f0a75e38247548534583190c22c0a94920cee0502a85571c5a80a1fed5e22b095b26d5b1539f0aad8a2e5f75e1cf8008c1f8891451652acc819825a2184b799b185ab0b2229448002194b4c97a381c8112d8f2eaf222bac94820419b9ff9ade84234b2748300109314c44a093190d443022093e2990220768b816f21c23e552c118682c91624a010734b88288a808520c010b6ebb5b149f1537f09149b0bf8932d312218bdc0d85dc7f72998ea902755b3cb994b0274d43c8c0c91048e41e8a829422a1269491852a7825ef25e549c6593a198288bc65e96488557e5d612ba3002940c2136c908430a4a0c2868c3c97e00206668c5145103f688931d184217041074d563baca2684203293d4b1c6104ad0547c6c829821742403182ac896ca4c862064100010a5430f1910333b290f3041386f0ba02b50415568802c6853070b06de1068c2c70c044e6050c8ce8c069be69de2c8d603385b8d2031d1d22cb4772801fe0796713b866e92415cbf3e56896b0002359fa472c119746e1345110180b7e70be1e31b03033676ec89718aeb6696a65147277d1cc5307faf647fa1eb5a522641eeedaa7c93244eb93f83d7a79f60a5658e857416cc7d54c102a6ad9742885a26063849d27f408610526301b34b45002cca6ab5e873240361e02d1413016156c50609760233b86116bd28e2419674451d9a0a0e7bc4127dd096b967df0aca34d905d24cc945d64c1e6009d74195d2777cfc6ea5e79f7ddf4262069eabc17e371cab253279a369dd8711e21f7cbeedf3bef8f3b9cd4600519f66ad87ff2c23a9d489ad67e1e6109390b49a3bd13962005275c410ab9a79ddfee44b356fcae7342fa8bca18f8fbb157267f6927b0bf34c9fb09481a1fe52c8b09489ace6d051519ee7eec559ec0cce25e8ce78eec3feec89d7492bdf7c44ece565b1e5f2e218fbd925d6427b84ed75b0fa808849ce580090f6c50f07961535f898d873aa0d415e0b02ec153a45747120a451258e8d8a0e0c3810d7d25364000d1a2850d556243461201a572a8b4abd8ec7a1c2aa20100000000e315000028100e870362b17814a7a1a2db0314800d749c4c625e389509a318077290310829040c3200000004466464c6093a8da9f6ad05b91ccb04988a9131ab67c2e0b991b1977d0988a9d0e7f7d5bc433d8b339da42fc8d552e7d31281609cb0b24750079c8130ed05250b2058a388eb2cce857d4e709ace3cdca279f7eb810ee218d9a121ef0270b5d3e00f9e07a11e74a5b97e716845e431833eadd0da4a3818981611a375120ead845548cb9863d0e98e76cbd16537085a4243fa02a40c2a705469f0f92ae35c71219e0c89ca1db60c4a68f1df754be56a819d942143ac2b64709eff954c71acb142c9bab1401b31169e05914cb42b35142f8f889d6651ed7f6c47129c2363ddf9791877078c38f068f55704f8de769fa04762bc925224c6b97f93d6c30a6d9a473b31fa34b6f779aa1d1eba2939c8caa98becb88dc071db3622ed9285e8ea764e4a2aa9cb371d791be6c35d83035d14f56b1dadf2bea59906a70708074d837a8552da3490be379bb71d14b90cf9b224e4a9b45b5bacb908a811d8f869e9bd423ba33a08285d3dbf95ed2fcbd7f1d0d485b9f6eb68aa7e09f20070c3d7cfb9f16e1d98adfcafcaec0eaffc58f209d0a902df81cab0d03cc9b0fd5e9fa341fb7a3180dbc75debcc2a71c38489d25177ccd06acd7beb702099d2c863e62229ead4a964d01854c3fc73accbd1dcd23f7bc16f274be2b25ba95852e280e08a1a617100c026c6db2af3a7f674230f50b9c102a2b8464ffb7635ecd49d2c8fa315d4bed746898cec9354274555bdf35a5ffaa0be8b1c04d358c395f678c7c2bd60eb3742445d8e06c41774981f748e3fecc89d9a869dc75cf1aaeb68003fea8260b5a0a41327d9791229b831f99dbd4b5f90357cda0e36ff5126acc106b341c1d9c0cde103ff38f579121ac6dae904b73ba26c15a45e757699ed806a97226a822461fcb7f94b3c8774389d5e90120f83708c9b02cbf9d9d24d180c344f7db58cef61adae56d1a21b44d35c6f4a9e9e8983fc54027a912c17ebbec5758be0c946e6b3c7bf79331eaad4029d72d51bf04e4751af87a5b7168cecde1f78e52a0f08fc5d0ab192fcd2fc02ba182c7c8eb1156e05be1811e4fbe943fdaa1e023ff96c9fdb57633aff762a5649d9227a9471b3f1fd77972e801a0274e6963ca41ffd71958530a09ad1773587cc8b998a909c99166ab88f4ff83e9a808cd055ac0951da458bb43c71693b59548d4028d49f65f62deb0a130880377ed4afe80175e1a075a62cff804f8fe899dd758b6f3ae158fb8fc9b94be8856532477a6f453d5c8e844827d4377fd0e9a5b6d842fe33fa24a7f8e7a2e2a89f8c6f2a75c6b0de2af344d352b1bd3257434ff58bad95ff85b7b4e9c537831b3824c637993c0b0a700edf1e279c7ab0125d236bfbf79b1d2ab55e5d7d28535fc3a49eefbf63460b87aef458a6fe9d6d392ffdd2d68ee6b96013e2b047cba1c1a77f289745e926faec7cb14488b8bb90b1310ddf2c8653ffdfa055619b2701eae7c8c75edbe68863bb0d316f30c90940a5a2d910d66ae19ce3fdd8b0e633a0120b45f14dcf2f09f852d139c2de4f7c2d4d4c38c25d673474e3299b34be43dbda0b96b0203e3f89f32aeda4da858a5dd244cde9862adb59d3e3599e5320f918d0dec7e76df6d6cb055a84967f4079fcc0f3e1dc05d79247834147d254c72debe58f5f3500c83f6e10d391d79de47d158b295af0ba12b2051cf0dcb6e1c08dfa8c27ba9c1e1ab3f376f33ff60ab4a6686acc9f122f6e22a40ec7246d94a90c711d8355413ec31fec89aea07ba272c10586368ce030fe1fe79207d097b365d58de66a6801c1a5f0858e5a614256b6848cbc7356433e5566d29f73be01662b5f8dd4a47c89aa08f1a68170ce3e777588d31505bc6c11317309dac01ed41bc73a99deea7f1705ec704da387c375d72f6c39509dd1b369ab634a5009c9c3b04dbd048cc2bdc8b540d12e448d903e29ca1b42a7fba2aae46532f742f7b23271af66b961d82083fd584172d9415e002a09709b55b36e12e582776f76c3a4b05590c6e8db3af0689b20ae2b884fefe1a61b3973e27fc6490ec8d5c6fe0f8b023a726fc83cbc5bb16e03ca1ba49d71602e22a36fc4af9f5ce7cc1a5550d408e8509c20a19fc4b968e058cead90b0279d38dfc6425ea126263d3d34b61c2db0e0b0288f8a6ee038ad40b4fc8060139c6e1ae394afe794ce999b6616104650390e135df06c616a91885d57fe566c44051661e42cc2db9e1533e5b4d2a572aaa68d3849196311886852c21e212a7e65b6cfead987fe3a3e5580f048dd389f661a4dbae51274d8d70b0e433a0440ad97576d4379ea50eec0be184e9ba56fa7dd5e26f8105b52de06bd416f3d3f7c85c6bd57ece78b634538bde54969dc4ef3300ae2023a49ac8d4b79208db332d939239bcda7af783b760d86d08387551a8702bd515bf5f778c233f379a8f1a06fd15ea8b446c8b6ee74986d748da9d59252474824f5e528b2b17ba5879120d462e605294328de5c98534bebf3478194065c1973beea4b30058bcbb19a618ebf39d3309206fb59bf2784ec265d1448dbcba98a4c5865d720e7b240e836aa50c4f042f89dcdf8f1073d1341cf67fda842fdbd1fac4b83ee70843fb64bf6fef4a6678457d36eac6b96fff73ec57c1637ac76f0ec6bb2a888efb379f89c69f6849fb0c1a8725adb12dd8d3a6e7f5af6d990f78e535d9fa8b901cbfba336404d2997f457a1fcbfc1bba11221b0ba07fa23da392ebccf6e2f4b18f67d9ebbc0df42b9a387a61f256a77b881abdf7b087b388a1f426ca176019c3fc6e036c117626250c1dcaa95a2677578ffc263cf475c9271a279ae9de2eedf481a85f702d38f4faad37a4851a0064a9fde4c1a02b4086117cdf455af1099b0e4200b980e357e001cd6999a017a1234ca6aa3f4aea7741ddd144171178e6f3ae27372ecbbc8094255f23bd477965df76543a47a1bea8a4fa1f5a6c322ce017f2fb5d3b583bc37530606addb181f9517bb99951a19989a1ee4c63595a95a4431a62de1f470321f027baa07b929146d973ac1c5b63da001b20ade3b14d526d83dda276d72fb12238b79faf83ed531609a6f543f2f67d52af6a43c66873b51c419294d3be94a4843c745fb9f7d56bdf25066103762d3082aa350dcfef1a076d35b9486395d666f925b1acbfe82381d4504cd97a43304886c5b9e0dd58033481548815bafe8e98de9c9cac6ae9db311a9f1dbe53ea8a9e9eb749e7d0982b3838aa6fb74bd9a20a9c688aeeecb87898bd3557b188fe895e6f1885ee53ff8a29747ca2676ecdbfdeb26c71d55003cb7eec750c7843ce0590abf6b37bc8034e00d048b7c667954bd7ca4900d4b45282d419ca2bec8597ccd90fd268a70afae2d45cf4ee3d12f37d6a9016efbd656b6c34769cafddd668edf622580af0ced03d4637b54b76b3820742675af35961968e87e53d8be6a05111c831c1b040363fb8afe75b14f5806ac54d40759b8e62c65b8bc0ca5ba474cf350750c3641eb4d0d88c695a05a82b7897f4c0ace25e48a6082d9f15beeac220df4ca9babfbc4d35b0bb06947e03f1ef062d61c5df47a2ef67fb13b40b7da8931c6719a567f4f74a9761cbf16ee5427f911e60a8ba0b0e4feb899754bd400c68d3b31aeff1176ce34f0785274f74c4550d2914d7ce4d81c74f050e43d892f07b25713af8514ff31e5d8b2e6e7c3284470a6b1c77ae370c3c2411ee7931d7c5589018246c8566c460e1d2e59931027ddf0bcafddfe3df131bc34c095482c8b61df8a9ef03bca54499a3a4e6e12fae68373f70a6d1a1efc5956130e4cf9e0396953a74ff4f8f9b2dcb86cb4a011e329a1ce4f4da65b99386be3819d306f48889e08049dda80bee42eaae079fe523cebdf139fc95ebfb0285dfaab7e788509bdb1dea5648375b5f4be08fa8190c39fc7ada8236ae5067b36da2fa61a530f0aeed36fe159ffbdbfa62a480de32b27bfca31179be180738658eaac37351e432c8a420d85aa562c87f8e48eacc425dadf72a04de3ba538e214c5b7cb07256646b53eafebc42a1e20a76bec600bae05d19a9be31822421cfb21e9224f5f6e761e984b54768971263894f892c0d023c67c699e9fb7e39d67a98fa9ab054b743512120ac832b606df39099fe1f64450c36d96049eb118065377a5f37112fe2f02562bdb2450596c2af13886655a27892cff1b5501df2a97cbbc812a699751b9f1035701d0a688ca7d77fefad7b91cc746be61847295399d6fdce389b804e3c0b9f0670cd1062456a64cf7136cd1dc94cb71750a19b96ca5844b9bd48e0464333fb30b674efa2c07f7fef3d1b80373b93ab4f9c0539c5d98fc5662ae3bd2ac558073ccffbd744def436235ebb57ed2a502162e91eaeeda65daf419b5c04c0b8bca4ddc8e47310a74e6e665e50730c98f0c0a5ceb466e3154a847869052eb27c78c890e61f0877f9a8f8a064d0ad60de59e04c1f0d3f78cf64f0f2b1a2fe6488db1c6ced04e07f320a9356db841b582e2fe94f13df4e5e65d0a0eb281f18531ab0ed37cce97daabfcef3939c481fe088b60c7d9103e57df1b5c61bc132ca48d4457cda01920c9b496415efda67d47a90a92f847e176f087f93d2223a2b8f145eeb1866a6817ebc554f0f85cc7f5ef153f52c6a1bf58500d32b2e59de8c2c014f74b61f88cdedd352acf4421bb3a769e3ccfe75f2221349f618fea2dc0ed6ad1cfb5c17dc473ec473ba4c3ef43877f64375171881ac0b4063be27513a0d63163176a7bd9dbf487f34d159bc9bc5fb15952f17ab4f4755bfc2cc8db2dd1b2c9260bc500c87dc6fc067097badd6e8eef82628e56db0c1d31911779a93c603806ed6806687c87b4450804c2f467c92d6994f44fed965964f8c5476534e3ad29afd7b966b5f76ad0dc95b5de83461ee814183bafb4d26e55880cb90a1fa70a44a91077ffc385392e919707ea145bee886c273389da6c5d9a66ca5dad2d45be0a1a55bbce40656a43bca474050eae6b11a2e425717fd18c4419f6bc688d6d73855aef0768cb1b1c9dc5c9cfe2e96500a3d62ae032c9cd4dc23130373e30fc74c3ed87f320d6f009113843dfa36537f672cca95ee6c9a6795431884f897a488e0807f1808210444935e8094697984da22c68057b4e9c3a32755c2fee8acc18aae8c160b4b48553abe9ee0ef0fee8e4d65120aaee022a8bfe253ae12b343911a238d12dc2d85fb86654948c68d4df1186aba98dd48f2d78854afc5fd97c7040162a9d08008e60eaf453059a769cd915877d4fe923943bccf748a88f4103890d4b963402fac82e8e4114ec0147b90a47a5318627231d17948779b620bb1d84b96caa8e0e01a4db3e5748415c208134a574689985eff8daf0719b5ead8e0df3c37d91c261587d1c4568436add23aa5b438519530bf379e3f2411f2b38a099f0839243101bb6248bbbecad4c136b3e9df10a80bf243b3aaa686f2f9484ba71e4a7eb9d6589a67449249a0b279e5430186e94613737ebf282e411e828400c5b8ead904aa1e2947f989ac782c858a6c046fc613709904a35af3c4e07d7900d45b9536a16dead1a3c7853027cefc6c2c34aa70f9ccaa364121184dba9dd220a164005521b0cc02fb354ab65d1029ec903bf60e9b5842712c02e08a55708ae4efc18ef5b920811a18d207db42c87e7b8e1304ffff351a149e9bc24c1344a23cbcf55309397e410b5bde5723c081653f3e0825956caae259c8548720743f666d92d0884b39244446f4ff049882fa8112cc8f1e00bf35ac4ce229329057099eaf3571734bb8b6d25c8373bf7e855c3f09b06a7f7e4bcda395ed8a0b249b1dacac9b42819a3690c400b3ab76dbc9f3d433b95ca9f05aae8c4b83319a81bedca062676407150121fb77a8902fc66e52814136a53130793a4e91daeae458fc2a024b8ebddbba1ea7e051a6f08de2c75d2378fee3ba7550d37bc1db8112a21ab9839865ec7a9b665dc221c6bf1834c6b08c006f40e6b4689b0909a69d0f1b7981f1097efb9e6b15a11695f644cbdb69f3a75d4fc1efb19e40b8db83d178344cfbb58d63121d1cc981bb82a2da982487ad53a050d0df684d584aef3a3816938651ac2ffd4cfc2a7e747098ed32c0117c27015a2e7bdd5ace95b1e9371678a965e1f1aaf89d0713f88d6f393ff989945e63adef2679e7e7898272f02a7460b87c8b2477d419f3ea526d71473bada923bc83274c6e9c67488d83ce28f86756c1738b3346cbababf17c9b4cbe5c7e1894868c0f4ec9ba049868d2f8a4338c129564bf5dded27a182f7fd78c55de27dc586b90d159755657619e3553750590db45af0c7375b5d17fa8c4496b94248735f6ec08b729ff411fabc2b3fa328afc9866e35da067e41def2bcd53318e0de70e86642e2154c4d9aa77ce6c6cfa28bca8096f30d1b00512318e5d07a18f2b87f6bc385d9999593e440c1545c149832cc71bdf976110d324553c89577a995fc12e4d6d44e26e43f28c3ff3b9db0dd74ce079ff787028e4e2a95d1b121b77e492cda06a0c89d9165097a58e77c0815029aacaa0cb0149a8095f1d42b5db2c19f58502c5a70672cd986857d1f642f8a16f16b33a04a01f233635e8fb310449922117c38054ff501a87496c2a9a588db5884f04089ebc0d7408749f4395b34882acd09de0342aa963d8602bb1ba0190ac3cfd6e40543a603db0572a2094892b425844d1ec059f0d9d847c9264a35bbd6fa170707d7454dbe0b7023b16670548ec2ff3811e24d57b0c7f114b9f603a7d0186e6aa64862e639b6e1b132fab15bb3259ae2c4027e5d9c41356695a222e9c46476713965c03fd4ad403c3c2f8dfeec97971086569af872e6b3ac31ea230d2124bbfc8f9a0801fe68d7f552ce9c7d1049e5975b70015c308c99050e810deb70d101abdaa26d227a157b340634427506f77083e80efedf04cbb33d84b402deaf2db10a4b7178c5cd06a786659e25216accc5a4e5a7e53dd6301fec212bdb6b522fed489b26fe2a03737b892382040ecae225cc62bc4944824393c19968e3112e2e7345cdc391187bcb16acedc9432621239a2e7a753a8488a732d747232f658a0b206059bb18f32c5f9f279bd256ffe590b402c1ae2e6decb119031eadc77fd2d4d202c3650ec59e9e496c3f16c4744b93fb681c80f9181ce4bff606c203eec83c9df76402151256ff620d071f0671470b606674178b01b66be135d92efa2110c3706f02f0a931b27683888399fcb9c8bd29f5fd9ba9323636de34f3674cb44142931ba9571e11476dc21e06bd1e5b194ce5e3d504704cda92bacc0636dd403c95980686ec63a7da12687032ef48b12c4720e6abedb5924782465952ee9851549e031fc1ce08c3f787e4e0fdea749fd5326a7dbe0cc7bcfc9e024cac0f24ab802b4ad4ba80ab63472036bba04b982b117710e1066012597e914214db93b0fa67d730496e0be7be2abf8ac055bd4f90bcc0610067b2735526b01f8322ae2136f20b5b1c0549b5a7aac280bfd12e4c809b1ce15fe37a510347ad53543b54bcc5a5415e368e428b75f810c07ed5c13f4c220725325387b06eaa1b07332df569150c4a3ed22be6f952b612b1c36a571874b6b6078072c055bf4155ff7039e6892621274d6e8e904de7fa89bd5045a72b146e0e7d6d75116aaedc4d207e079d7d57a0c3a63194b4745c05c160396af62d226791fb9842f9ce90887507cc0ad15641532b6df78ee291f5be1ed911af9dd80f683fb1abcfda6a96d4aa6294bc539778ae5f63031e4008adb38caedb343e2aa799e05f0bef535de39a1b21362fd4b7e594e98ee6892b354813ad4df666a4789dbd294c626f67a3355b6cf7e55fa8e844c7b2dc18f3748e68547bcfa6c8dd4d5fc23316df916e3f91cf6175e024bd103ca4c70eb11a41c072e5da6104ba8c3c19aade8d8d65983be644805e140baa198cb6ab0ccad2c8c19cd1320e8cd08e728fd4cde46bd108be8d217d865f18a9fb5651547fd805340487f5a363c94b87a432e65471fe3b4b82c79312945ea4bc2b8c95f997990aa07653b991ad82b600469a3899dd3273bee028e0c8be326f695570d79d7a67d4d2ca3c7725155c7fad08ccc8ac90f22b06453126b16cbb0e171774521ddb2329e0beca9cfbe8e8100b370a90557d8f5c5518864813bbcc37b8166d029ab3ac6ca2a8d288724d7dbf1833be94a90f960f2256be370abc4562e6235088f5995a9ba62c84b33b988b43ba22da25c96b116a250d9c094a75d84c74a07e54a623eda2e2876d206cf754e96123ae7d448418933bfe40cf14e200aca329cb31977e6179e247c7a9afc038a4de00beafb59f1c60fd935050b5aab00379a46eb390c0004a2db0205f6bb2a2106b7f5ff045046f04ff117c98993b4d922a5f0f333cd31c3f3110c2c21f7012ceb471cd73192cc6905b97244cec9cbfe3b61832c1207cec436a072a76fe1fd2e546d09c46890c4d17b174f21484d00a3f725e4bccf9ac707219cf43f23c5c4c8137061d790517d51d73989c5c81711b6cacdbd28fe623d5abf9318c72d52875b864e076baf01e990816c4c4230820204a88455177f57641458ba9c4aa4f6cf182ffa8c53ee3d4d4a1c686a983d83dad02066222cb266ad6d68ecca93d80a1a3b58687e7a6818dccead0310c13e418a2cbecab0dd52d57c0897adcf10ab711a34795cb05add6f8d391c265126d2dbb90cf4bdc80ad019d8b5445e71b939bce0b335628414eee849ad10345d24dd59a3964526492f924c6f4f182d8e19b8aa686f53282a2a8f28b81c3da7220ab93bbd43a441c00f492f64d2d26f009877087bbb1b93cc3f485f9a505188a0af3d993e7ae1ff0e9546a694195e310384a9bfc2c1d9f5e724c0aa984d3ef55af96086194490d5b62f99a227b8777f85981414cea5b2680038442363e89be6c6b6e87b7f1994ca23cf8822ae7ab3efcfbd0458dcf4c28412b50733e2e585dd638c4aa6076c76e9722426198690f6f98982e5bd26708a96ae53b9d6c70a1d994b4f3f307afef40767e62b612670903e49162cbbafd944d19b84692c8e7beb0b5e491824ddd55ac6076a1f8568bee3b505a838381b6bc06461085226788ad48291d877c968ae0ef14cf192cc21f4c2492ff8b301346d1475ee907a0d6efe072bdc085ddf90337eeaff039fed1740a6451b4a3af409915d24e8b2d8e5ef1112217a676edc07ebb8e19017317a12bdfb86ff4033b7d5a4ea454c7c6483870ac52ef0889f5074c4796f63f7824b28f10680ac3f4c3a335eb6f2410c567afeb1193f0cb2feadf3fc5afaf9a107754bacf1f2abf001bd7079b179a98b4eea5f8643d3c0659da5b88eaa6266d1061cdafcb5ae90deb084ccc4d1beadbe8cd30bb35b90414d80119dbeb80bcefc7991cb15c8f13afcc07f024eb8e2aeac80b0f712d1a21181e88deefdb1aac00b96f1ff86a2f36b1a1cdd1a21bf87f5526f7280ceea63d1e3c7a72aafd17805aed7e87f8b457543ed30cc0d31defb848bc96d3ec2a18eba42451605a3cae5356453fd2f18ea5e1deae756942ce135592c764b9f9bbb56f6dea4158cd7b107b8b688882e464d409a5eba53ebb4d2abc6822f0f090b50f99120998f58088df305a1d74d09f79609d243de29245ede611190bc3c6347481b05e42c230090bf35bb06a6fb9e897f81f81c313f260ce68ce84a1fa3d5a5a48485e45fab2daff8e4d399b701594d1f6c715d544f03c40e9beb38f5bc0b193a86dc9512b3253b042b8dfb75e907b7ab7741725dec67e1c133ab2b5d1c91894909e4400020a878900b2884d23267779ee78f5362dcbcb4d32e8d3a3a7d374672197455fdd1eb70cae61a8525d73d6c5368054e7cdfb03718b3e1146a85117afe9a7dd9f98be5dccf3cdc75ab52792bddd9257c9ecfc93b2808dae7c8fc158fae7aab5a0b3b75eb56e01897a5edfee8ffc5c1aa68f9976ea86892fa5f3caaa3420978e524fd3131bbe38c69cbf0f267f4590ead3894ece3114899f99ed506b7e62d12004b4fe9a9e4d31e85b211f26a9d5c734224bde658470b4d1a4115fbe59a7aaa83c54cab5712e1030bba7d36d0275250d49f675b9c08ae7ac8f59586212eed4f46f59075b777912a5979ae32967d7f220091c7c60853c218bc45e7f942814cb9dae5008040a84098b2d08d82b39174f6d2a69da87d757aa9ce90e11aaca16b6db5f2ad9547bbfc224dfafbcfcaac20994730de58da8cc155b18fa226d6e9e40ad8758c959bda3e425955802d8107e189169e4ee8ea694312eec0f87e2d4f5a2a2f0d2cb97777515df8ecfb9121f843b648a10e3139ea1ef02423ed366764ef7ecc19c54ea7c76a5d10fa2dd33775cdb98ede619fb1262f4133eaa795fb395d64207b665db3e7499640ec397a428a5d42612893f69e527aeba301e6ea437453baf29b74e8a548017626930815031ee6372b93e9266c251630ba6e4ab8f8ecda9fa29934263fbd0d13834fe7c71527010f64d33a43791c2f9af940ece21e43c1480c614cf26c92b6d21090e9aab49de99c7f46f5be67d88908fd4268ef15839311bca140ec8d62d354a9df11a567d291d8abd3de7a537950a317b049f05dbf2ad7be8807cd8e7a3f76d061215e3a02a34d62796c0e7aed05c07d7da91d22c814e7f3b7ab5ae612cba1efe3290172a953d7a5cc2655d0f84eb58a12d90eb615d88e06aa158380f7374e2c30b97a8f7257daed87fabf072fdf10d873edc64d2952aaea1503efcac48c8d1e7c5cd546b42e3e6931307d3d3a818666f3618b0c2dfd74037bc809f7af80cc0a10def3224cbc10a78bca77315be4b1204841bd379a7ccb7d47b0c02f91dad3f8eb51127b7f2cabd66a7dee00662987de2185e1fb721065eade08a78e9c112113e302773ba976ef7f244c3dbef45f7ee1c5a42198d21dccb0e5cb72f30923a346b8652619508c5babf5c297588323650cad16f3789d91302d22cab54f1a03dc449e74190570043f6543ea4c5bb12f20535c549ecec81b02c094298ab1d69d4fe1b4b0b71c45cb5e6f84d155ccd058e825fc087f761ef0c95df11be05bf58af00105e94cf2e3ab4ccc0ff5f73b4c4122a2559c15391c680822253e43932711d98df6ff1940ae564e386ab9b43cab741195a9d63d3be8bd80737e108d3a44047f288ecc69906a36068e8f6f795ec23a483dee6de464f7fdd85fd64ce1667a0b0937f831359062e206e5ebb6913128ff28c6acb3f3f1ca4b8a1eebf81c518ff25681914a4f0e1db7d29549e8a18cbfd70abfe857facc36206d3442ab0fd34ca788c01a0fadff3d28343600a4235521c5d28c3296dacd1c630bd017ac936a40048be280f37af8dbcef34c046807933cc7bc146f9c892533228e3238d30d2e027f1a8fb51f1288ec45c09affcdc53ff61a1e0c815af26e1ef1ac440600aa924308cc663056cb8a04259e8c7605736108b40dc93915d8ac2af4590b68352cb28cde3a16bfcd06940faa70f0c1a2a33a75e191385a5dfc8e05bf8af0bff677aba120ea3c07131c602005771f3f8500cbea4d8a619b130cfcc5fc9de280fa178a6b639ee2888fed84047492c17c992d9deeda5661e27c85aa879b41d3c7a557fa6062e7abd9f2272809719c6ccb336b8abdadf9987c88738cdf31fa504bf1cfefa442faa08c17c123052fe3c9536ca4d762274fefa47026999526fac55698d522e8862ced8734b4ad8bb23896de8e1167d880abab1f84d4b15bfc30963496bb2cfa277957ce33a6df31849809a70ae12b30be5f8946913246fccb0b31c0e73f9a0941ccdad943354c549c8d297ad57b38ead07bea701d3445a6be21e8e5eea95c5ba4d511549126f81e0f565c55142420753c68a9b913280a2bd272eac1d110f9a4f3861db3ee5e259e8143c325cc3436ada11d623865b4521bada6323a7ba75e705caecc7c2c278233a23f89dead1c84e08364427c008577fced0f22ddb818768f1efbfa7d14ae67ae26414056657bc9e9400d7212b86a30f72f28672eeedb438cf99342a0637829bf500837765bf53bb2fd64c87c47518a9fa14f24c6f9c93685ffc5e845a729c8db101277366ec9ef42434d67ed99049a6ca1ee056940be8dd984552b194dfba524163285e69ec729a5d973d03817387c1f15083ad01e00d7a5b392c618a8540ec8820ae22492499150b549aaee81f29810752b40885c66827b7998a12fa0d5fbf243deec9575678917cf9f3733a881ada58758c4a7548390dce034b95362968ca8e34642ca8f15d04cdf093c34b98a58ab53ee33e3312e8c1765246b01ed9961f4f814ceb79631651b9a59d92c28fce92e5325e6bdeb3491b0f953fca84e94a69b92f55646eec768688915192b3be1d5931369c7b4e753969bfac1472669ce39cf332a93de0768ed324549da11762c9272b2499ae267e39f36a02919a3472b9ab951bd1ea964a2d49866cab85369b15bafa469a838b6d193729b9c797abaaf4b5617e1de70a3b8bf2516ae467a25d0fc94dba37fc51f4a032aa9b64b36c01a12584580426d532859aaf14ddc304ef10507c93a0089b9a2e2cee7e7e0885d0ce2c9034133caf73366b3440d3986bd119f90fdd403243dcb702c401ea165640751652fa4c5c6f105e5913b3de3b30d0311b49e6ce71282e8a5fdb567a100ff63238b4bd068bd9195eec69b6583612b91b3bc1729784a011f78336955f823be5560c6771eb1b0003982ac16b2736b68afb020afad084175f7cb2d2ec6d1eff79f5e2af9a4f9e995ce4a65acfca8d0947ca155ab236511bad1defa27081dca29420b6440f803ae5fcf405d65c9e9ac132353eb27f47fa9d82685c140d1f2d534751594dedbc26b53f13b8eae0d85fe3f9764ca85130639ca9925b5d6486e77242229b4df7b2e484465f3263a3d8e8a5d302c4153fcfb8d1f566f4a8d312c807d4ee0b683b328cc33dd3c939bc3101fde61c6519926ff848c84c6d0e18bae13b5750a68ec55e562860433354527cef4de9e389e91b6b634a0f7e6d0ab637427a77c87f63e31ffd1c281b1782f9f8f3c1592883145d2523bb3b929a73c53f4d64048f29149622d085007bbabc9c7a0f32f7842c786ae58248d15413f7a7c86aa1bb74cc767d495b0945ece260e6189054db7a46e93a96e9fb2f255a9e7fbedaa09433ddb0aec85883acf3a6a9580fbdbb4d8b42e4618c00060668f690bbda51112e08d074d858c8374c74ee80d664974f1ae0955e174f918a60e54eb22c8034eab1968d166e8edac0eeccd337387f5a0b61ae71d6428858cbccbf0ac0d137f78453da341944328db27249cf5e9e745da681af297f40317582cdc2ea3b7d71199930230e00bdee87fa43e95bd033147928e22077129f3c5a7e0f26815c6cea73a094e7458852455212ec8de562a976808ec0f3695dbd70e0329e7d44c832c64a5867c2b6299861e5794a823538cdef737dcff7f8484ff7a7d31e93be9dcaa480d39a843559d878638223b74948b6aea805232d79ecb48c4e62eee074328ed0e68f79429e0dc026ac6454cd77208d9ff3469542bf86102b1a0b6caa83a0c7c93310182858ec390d395db865d98ea5603be6be665e80a938ed64597afb15db10a92e354c3fd718b40a8d588aa42018a6eccfd35fabbbbed52c2301812dc722a913df0eae6f0708fb76fd8b5e837a2c0f56f00cc6de60fd5e914a1ed9e3f10dc8708b12cbbaf10c9b1c7a55934add6265256a5373e54aed42dc0d81eacaaeef96d78bccf157e7460d399a653b2bd6901cdda503e2200ca9abaa0684f9ea428f8082de67de51bc1e8c844be0fc818b2053357e745196e4f2c71d16da3b579dc39330902eb17b026fff073757286cc54c223075802a492b10f563ba64dc17827c148bc07c790b869d2ff4284c4c428779570fe35a69792050097a8fe6154fe37db4977272a7c986964bac982159b9a30dd322f529838485c654d9e8c6fa2fa050f122d908a2abd2cdc4b29a674ce27d429583a153168946b6d0fae7213590df70b1c498ef31bfa88b04a7a7229817e695b8a9540515843b62eedaa2907a005f39521a58334dfc2dfe7a3942ae0a37bdadd63a4291378c0d770fd82bdcae0d0d8f611db5783569b263d6c023b49dbca901b1fadc9cf65201d05287de5d5c7788cb24b00f3f889ff734619c27485f7620454158950eafd3c9825cb383cda6f425e79ad7b973eabaf24275b962749b1525a2cacc0021069682d70a4372e2b56503a52f99096681f5c2245699c92e0376986e02c388a89d37884239b01490972f1381e93be27c813b4100fff910219558d5d06761d9621fd75f48861963d8a67d4422caf30366aeb86ef7efffab8d50dd6b3ce92f194169d3197f68727d4b66b07906e9b8bd30266540a6caaebd896369ceeef263acd1c0fb370a27d5462597fb989d7ee97e7f310511cde2f7182085e214d81a3ed718b6d4f53839d76b6f68f57cc9ecd3f5a48b08d9bec21c6feccb8daaa9846b94d345230285df8efbac0d24f017242709106cfa8ce2a4d90f8303f129ee6cf591262c805c94ca94aec594ca103d8949f1e07a3e11b7d622fb939e723c7f2491307f47f14179a1908244fde9ea32a3e9b8c189dcce5e61ae22261d5acd04cc636f763dc0be1ba1aae230872862a807dc15c9961ef8e020045c1d99145012e62f3309c6152ff7d488cd853eaa61566d5c1a3c5f0b4a16798555434b13343eb4c53dc93cdc5715a40587237dfdac8a35b225286ce3953886e6c639773aebd66e60b16df2867c2d8a7829ccf2002de5f3484d031bd47ed048424e6d1289b17dc1754fd9b16e700fb386970a4ba52ab768fc90dfb6118811cfeef5f63da7ac9d17bd996a1fcf3480373ec5804fd93a0fa8fc555aa4764d39d5c317d293d36b0bf00ba2b4e1c748f3186835bc5e026305b9cd581bf743d0e115559c82c6699e5c801df3bd3b5ed8e5642197ae6a18211e169ede542c2c87cbb5d684de7776da49e04e1c95e4add8aa9131ed83ce51b0e5839ad413f3c9efe81c3ba474357604cf708620b8ab695693edb70747c34c311e9e3b4eaa080be5559f5967563c582204d6f7a535c91982019476e397a372ec4eab8e7b81ae21147153c77b41a36101bd33fba3b33bb189d124184953a548cf70c2a23e3986c4aec300540662534cc176e01a5802f4193b0680ab0085d7fb8593e583821c61b3ec8f08df9442b276269dfbadde2d2fa8a3c6f4c73263ac44d10c9840343ebe2f846685ed81b5ddf8b4bde61a2c7d32c8f8844d3fd775b30f40a2c96c55270895fa937c6c2ef968c6c6f82b036348873fa94ecee16673384a955aa0d0fb52819b0b670c15dc872753f1fbfd2a0ef7dbd9aa06e53cc42776dc0d54982b2aab81b9b9a8fc72935c2e644feb0de707de186c4010bbb51017559640d083cc181148f42a9a6cb6671e6e60f48257f4fbf93ad5bab95007e2a4c762f9cf6fe03e88217b01ab695916bce3d09804080910840a669f76d8272e15f9cdfe16e996e5bc618af225d1fef25c4e3737ab942a61cef08574e2488c773ccbe026489000f87d0a5e33544db847009740a4a98414bf5aa5321331756169a85ac66f604b0c621de48e296fbf0db441377af1401a1f3797e06550f180d09368b769c27c4b2ab287dec2aba5cce942b3d5448399903944db1bc34ad23aba570c2a2850351290ce8e6caa6dabf380dec59da12244e2f4b8da3c6785da877b8e86e4a5e630a661de67040088a58e1aadb2d9644ca3d59826f48117b39ac0f528a94dab06c786eb1202539379adc469ad6654cb385f2d7ec04d11fc57e4cabdd7619042dda31abf38917cad4f7d448349ffa9ec9112f12d39d73cdd1d00c5e6889337e057334701200437607e2ccdbf8a4b794afea80d5e3c8132e895de711963fc7972cd8ab79afb3108ef5eeeece52c49d4581e2d71ebd882748bd8c1b546b1560c9645a1c509761db733f32956bbbfe9c9689599a05b6f8fb453a3f5bbc7a02f8fae5571357d62d02a1d1db1a6c6564d36e8ab0122916606fbe511d5890700d53ba3d6df72778ec5d3d547e5a5fb1aae6ab778e8267d4475f6aa98b86c1f72a5161d0ab636bbd83210478915fd2fcfeea902f1b1af6d17ef35ce2c6aa547b02868d1aee5a860a13c254102a003009386f998ed1436d10347102885836117530739188979c81fd77ca3274201fb724e0cf7dee307a7a7295e251a507979336216c93a9a162f0c8e38a54b8dc0eb0b6fd6064581475a3bdff177b08e369368ab6364108466ee0780c44fa8d244857cf611af68482f0437f5b025e0104a9751ace979c653285039625f2f265d9f39fed662d92efc5361a87785b23f0b362744a8b6949eeeb8b495e197a5f95906fe57da6d7844de2e37328840271e9d4d10541227a36735e4bdfe42af6ad23b3c8143b82fb12f1208ec50ce2e30df351527d081fa47564fcd7e96db375f4af98cc6722556107d9832fe7495e1ec8294d611e51a740c302bac4cbc0ae80a0dc46315b6a0af42385b5dc2086825b06b4ef50bcbe9daadc12579282512dfd616f9f43f5f780256ae027ad7afebb6109139d287e10776d906fdf7bf3eafba204f89120c2943e9785b6b82f2c0c5082e58a9f12382840b9f652eedc8382c51942b1267d019b7245183b489214243e41ab5acf4e65b405393562cac63f265f615a9ef97158e0aff1bbb4722ee7b15345c669b3c6a7d60e5539bea0afea71ce20fcc2ecfd1128a33dbdda9b5a5ec866d22a0f64f35b94464088e0683bea137d5ec4442a1676638a3b4c7482d646b65db326a9024757504ac97a96e6c98d1fe54be95fb8cd422e8794ccad972ac61ae66e93a45e625af9466c6cb1132d91034ae00d24d5bd345d3e2cab6c75ab01c52085751bcb8117666a982ba0f2cf3f9867367d44357874988d4a72e7cc1252436167c3991f474b4547f42d8675659a21cc997afdb55032a683880c0408b8b66c5439c99f9d3d578a29b2b0659cc3e8bb90d5e8814984c60f180890cbd8bb6b0cc5d71281a87441c06705165dd6253160ba381044700f0a616cf311890941c9d082cd1c234f7c71a5f97362417e22a218803bddc51efd41091d6915387780a43d278dbb800bb724f1a6ae8882877c1e4ca11551e2bd687b937bd638e352a1e2074d27aa8b8669d66da9bc614481302e9b4e570684a680716cc288c2d7b9466e8a15109a1fe7b2b2e30b378b71a6544449c843e68a39a8955d7946e6424cbd413a7c44dae255cd57bc4dd26332dc791dc1642939ba4594464fd6272f86271873b78d52d6f9d6fd6338e6a2aa8e29ce7303dfdd3f1ebfaed3472e7569af82d552108d0660cc23cfa3857b7906f515a1769e2836c958b0a87e3ba6bf14d97dfc8491109bdcaf6ae775ac32ead0d0ecab1eb03161d33ce922ba70c3f6f65f33d5c8d373c1503f0c90bdb150104a424305da5ba55fccf256850871f64937f34a42cbd92a5924a7d1496d8c30967f1557e57f3c4219444c8e57dd17916a37144d7796619a0db7f51025366f8fd794e27ea36cb2eaca3fef607cf4466941f6b333f0a909277d28b60829ae8dd52b05fd77c06af38be8ee12b2cc5173e706275f2026f9828e2268585e21d538483918a645d817a0d77951e473f8cc4e8498e19412ac42ee4cb6b7fcc5c000482d2d512f60d58e7fd0de413400f195a0a6c5ca922d3a42b9cc290c8ccb0542c611d028bf477e0999264cfc25456ab4884592baeecc7fc9014c80a73ee630903d247bedf1ff77a39e0b042c9dab7dcaa4fde049dd6768d49e54b3c00d1ab8425090ee3e805c0e7fec6c173612f0ed3452ace1078f3bf89f817426c1a754ca14a17edc6e94643f3aac513e057e88b4082f164c43508ca04cd15996153f2a71bc9abf95f49e71342da557c7e4e8b85313a6c229ea874cf02c3947819813bb89321ae6330b141e8de3ff3062c9892a268a10c371cb2df13b4a0d88f043a0aa8145b87fdd64020f7af87005c043bf8c16c3694ab703b1789256ea1704dfffb432a940f52436bfafca62586975b23720c392d1d39a663f10e7880668a5767316d62717a58ee1817605ee5896dd61a24aede3e12bf6bc00c7ac4d7d5d9ea2c4d563ff0c10c12f2cd0936439f1f3440a05958e7f87cbfd28eef54b634c317a48221fa7ca51a529e95ff3e6403986097142fc6520f9e42ee29f1d41cca3ca171959afb87a8d32d72ed640fb1ed9e98eb34eacc6e2ac3fdc240d7245d4200e572a7317d5b356ef98fc95efc5a2dbe32c980d53430bc5f2181c63031e91897d44d77a8a791975b742ff0a77c204ce0b11912fe386c10fa0a02e91c14288718b552ea7586209c303d4b6be37650f7a2b0197ba66dcbdf20ff9e7170b1e647d39f765046f67aece8771789efaff1d87e5927fd98a6722573b2f69a28b6e1178d555c8e2691120ba976d0ee435799ab23d0e3227ca020132ccde585dc1dcad9b6f0442c8e29c4139278ed1b1ce22fc605848b7972f718b664e3902550e5cfef85d5c3669a26dc90b94e030cdce647f8db5b8db1b132bb5fb4cfd5f50279895e6de3f3b2a7bc3a7eb147885aa98c5e93b99c1d27cb0d4ff336bc6c48bb9b77d6d5c0f4b417fd1bdc88327d2010d76a9837ca3501e1627c46e307950aabf28053db1f4d685c5f23115c24248181d3f9d017895b1df8685d4b3879e992e10f34886a61819b7536bff3642e8178fca4d8e68130132c31151371c7f6e1a2e08deccf816130ee61a349bd5081b552ca26dcead9afa709425c9122e585ce1874c412a6ba9551fb2b7522d05a7dfa447a70d6be39cb485a26ecd61e0b38fe989580ab919fa3a7c5a05b54f12f1893cd09ec83ae9a25bdb1df0619ffe50f6249408d4a61cfcc498634fbc3a5333a054f4e37f4f1733d25c4da273963d604b79039c13a89393421295c6aa6777406c5e4cacab0421eca4902c69c4000e31387f918c2a65969325a01b052577e5546c01e915719615a62979751fd83bb6753765b22a9b8a69a562ff39b6dc77978214b14e07224454056cc4b4b5b10b71514b010c6b3b11e2a6a154cd471b3d6b8f1ae00009013edca416f8393d605e7dadb1c975fd5431783094861833cd35ede77b941272256e3338f8953a1a207ef085cf9754dd9b8978260dcab672949eea2468c5c711cf514d0858027fb012d0c25d75da6cb887f4d64cd6a4e3d90d8768aa2ef018cf12d312f878b02cd19e12499bd5c3f93072cb16f5bcf422918df17efac710e6e2fb6182227bd75502eaa81fbf17ecbbecade7192149a830213f6cd2b561f942f0f96347120716621d7a366ee1ed980c5a3503e0af5becf35b95048c4e08d4c3869971364672c50901c1b2d82172d2b61b6b1ba6412366bf44acec6c0721d94de55fe3a233166f746a8ef8680f76c46f76d202a5571fd9530b128cfd75027d99ac06d6bd2e58e428cd21b5f0e06561618c792af21ce8bc84d8a0c66662a9224e513b1e744fbeea11ca5fda748ae56b8952f2b14b8b971deb4ea510a50089b4e60dafc09118ccdf695888648d220e6b2bb5c3422e4465bcc57aa3839605eaf8cd054331e3261878ae160ae0efcd5f94b2e106dd2d2916531f4fdcfda9a9158536702e44b22f6d4715d37c4395ddc574a55d8be97c837c478eee04804a3a7fa67fb7fb4caa9747204dbb77f188203f9b6a6d4736a9a0e0ff69a4af7b599871122e567c83764e3f6cc46a4ae532a64487c5f0cb38bcb1886ec73f4fe4923bfddf56deed4e2acac59da657655d5a742b0688c6493ebdfabe7cb27f305649f39b9f1e41f9743741491f2ca4dd5f0425ea60f14d664b6b1e714df2a1e639567679a893767da8f31d4430391e142c6854b1a4c5e853510e15bacd1c4a0a000526cd56f67dda0e7922e9bcf1768d970e8c369e90ee957550e3b90a5f6c5fcb019958726bf16849b1aa41e4fc2b90995440b5d617526be5c24931ed9b281fab80e8ad1a23c57df4a9342755fbb14066349423d787e2ea51bb09797339bb2d4ee7bcc2dde06839aaaad1a9b6a79480a963503b019596645daefa74ab6b8f4f7934bb15c34ab3ac99ec05d0d8427d7cfd97c2aba8f80f39dd61c7dfae1a284dc261064c4001f0b33af4faa5fce083255b03ac34433e201493fb7769062aafea6b0c2615b658b8cf416ba1716198bdb79f0d7aa3bb0f5c064086cc07f8d6a0c83fdbef10d031fa8d5fba34f0b1a19878b3180d30a56fbd09ad36896cdb4bf9c5dd79ee16f80c030d091be0f30c26d825cbb2851f118231bcfd0a2037d8f540613214c8e4c0ff3124e12c487fa9741f7018f440ed477483dab9b00952dba10e8465000737c7a56115407c2588eaa47af8eb677a60c189f46fec511d39fc2091705ef8df0912ed1da1da038d73b239638bc4a78480a437d7ebbc15c5d1405fd7e0ef4b68cad09463463222ec901e8c9106bb5a0d1a8de073b558ec7fc2576bd22965d68828ff4aa33922e9150c085004271403c7e52fa81fc58bddf93380a758f1a65b4ae33781a610e928fe1698376835d1714ec401035d55bba86183cf0e4e2df2ce91ab68d90ef07fa21c05e782da074d1424f2fec1f0295e90f08985725a7569601700ee672f0d4ceca053184189881b0647f8fde7132057e09b4813ec9a8456131df7f242ccac8a1d1e142be1cd29ac03d16a4268881dac9cb01732fd4698e80000e1a170fac5ea23fa82ceac6029e4106b8be432c32ef377647719dea2d7dc7e717e10ae72ab8cee3a9540b98a932a0dc33f5808f1ccef5293d66dee7db2704884038ca4417b827b3d8e2d4a9c98373cbdc7324aa072c1a7c6c5e41cfc0513b55c52bdd027e3abb746e8398ecf8108df69b854dd012fa0b72c7a9ea312578abc15d0073e11e3608cb16011363920aa2a64b62d9da90ec5dc3b1939ce9d5964180dfbb8514e0c8b51e4e5a73e32588ca4b320f363763a60d36ffb392141394c728371c67be3036f40d07d159d5ce42466902c931d3170a29285517277483d10e1c178823630fbdaa445aadb569c283c532d06825e618e593ede3ec594350ddce4314382ed89448365a89329668a24b5792bff3795a8c4f2a6cb88f43ce6f13f803cecafc58e0788054d35d8dd365cbfc8755ff07724d0926d8666004737c6dc1182a2367413a395d6013f2ba5e5e4c96cb43815fa3f31041a5300ae6ad566a3574dd055e943d940130e1ba3e048850cc5517039acf31a5acfd43f232765ea7f4dedf26e196cf43bee984140f8308d2b1d33dbe551208cb2a96ddb85a0d61bf0d224c4ba0a48edf1e47ac05d2261e1336edac6455fa84dda915afb66d63b9b5dda0d001e2775478bd18cc61028a6f1c24637dc83565c65f4aa632d340ada64856f093a09ca3c60f9e3b4f25b71e0d43f40f00c61be945adb9a6b7ca86fca1764f8be843d74529b93290dbc6ee71ad4392a54f7be2649071f1da4ad0b439dad9ad52fdbbabfb5f67ded7bd0d0859f29a670749e97289f0cae51a3bd49bf1326679ad5a1a6742998e8e224243a7cb508c57f662bed3460bac7fb2a894fc814075e3a537f8ccf33acd84c018ea3c7f82b28bdd0508f7bc5ea48d301c20212d961c35602a6ef1438b2c3b22281014581aae3a7a98cc02b07849c0abcbb380ff67994b48ef3f4a16a080416204441469b7b21437aa0bdc08741b1d5bc01699968533d4c398bc5c9c0751e2db1c00c45f5a6694f8a187ffeece67791f60608518a09b0267386c8a3c2fd1e250e4fc2a174b1681d860dbcadbd1e3752401fdff1eb7802c79d3df0226657130aef6ac45de02252e41336f39adfde8687d010d3c334d8751e0e8d515bfb316cd8d59c3aeaf933d629fd41a472ccf81d0c4a36d2a5f78aab617008184933beadea99c7b7793950cd626c98815522caae3fb15b36006af3b7ee7378884349afcf944759d49e3f13219927849a4724eaee7c2390b93bd40912f1bbb98681e1c17af53e81a20e6455f5a90da6602e100ca86802beee0b736b70986802d8257773ae2350d54203fa1038c017e88f961f40af5f9d50b586a7f070afd5cc6cd22e43e0353851d7773d0d8546aca57d1936ee66a61cf5fe39eaf484d07c16d94e73844a5f0e582eee138c092fe33015b9d8096512c22a830864fd072ae7e78d04d810df5158d418b57d61b8e3718300ee4a2118a2ff070f72df7c88540f316ebd25feef94d6d02907fab3c5d3aef1f14309748aff49c2f718d51dc8c241f16addbd8781382d913fdd7ffe93681e11e166559b309f9585cec9fc79519932a6b9010df30e69bc8b74184a214bcdf130b09bf38f0fc16723410e2fe791641f4864a18d8f411a287c10c6eb348d4abbc413e30ce825bf3deba43ed1215ca3636acb196959efafc3005e6fc63e1b32a083edd87534534e49c3a8015132ea5ece8968e5403afdcc34af1a5e0ff8c5e188498369d79135db0ea96a8d312d7366cec0838182027a5068f86057219077946c35f7c3e10ecf3fbfa93ec101f23c999dd2f64e8e98d91fb60202ea1863bee271d80b7fc196485c0cc195c848000182b7ec3321386bc151b4779cc625fac0e30d04dc922ac79f775e930a0e080134d3c496ab8b8bb58c1b1a0b58278e765c10aa873c4ce32ea7a15098b7b46fc347ddcfa4cfde3f039d8607613dd0d8ae6d0892cf2e5817af3d7f13b22e6d773aa085af133004a85867867fcdd5be822251e2d5f789f18a440c08387792cfbe478e4d1204eb122bd070cdbdb5d867a76f0f316491a2f7b1fbee69b7c25331b19e876b8a3185afd225723bbf29f6ea5e54041d7f40a7816b9a20bb7d1dcbda12a11e96615630e895863b1a3508e4ae5cbff2fef957a9edd1df149a925a4c4ce9c7fcb466ca25fb17992b85f550c2c72f602065cc1b43a02254bd57904c14696f0d6226b713da394601304569e72dc9c3412e562f2c3a37a0e88e4ba23e95c66b30c14f83dfb7b8703044781d507c4d6fe63a1d6f57ef19dc81e806e07617393232930e399a0debd7a4670d88fe95745a24b627c847d2d4362ff5a606f357f2452514c1608304d60712ab9617ac2dd2d9af68e1840491480a81df64f25439e6dd2a32fd29688507143af75a45d371105ad8e8b515e78b5de7b22a7825eede83a7868b97bc2cbc294b606883e1a3170e549bc78a9b48703e0140100b250c6e7dea4aee917c7cd64af9b8a7710da522c2a190e483fcdd216695c778683508c18856a8f215f82062535fed0a2ad4eaed5769fbe50f05cda5320cdcf2032b3be3183c65074612c8c7c30cbe29ada03d3eb7a07b73d48df4f6be6fbec2f7d82c3438a678764eb209cca0a3108ea0602e8672e82c8cca1301775be296a757e8b217eebedbac44403e8d319767073df2a27543f56a49f62bc20ac4922fef850f3ef92f58a40ab5ba4532d57b0563b805c30281ed1ec241b950ce52372e675afe7eb3450795e00bffe901d84b7f14a3cd5309a985b98994441d481982746cf0c6cdb6d817aaab0c41bfff7262e71535d5f19aae32387212c67a90ab6b83f8b1dfa0dc0c86a5c425b59df1c5d9723e372b9dc7be9f6d0872bdbae24d51eef0dafffda71c0e9f52babaeb5882b895d2ddd38cfb033ebb3d248b94e6513caf915d200d3e657c476a3e21e9efeb6984d68ba7ae59ccea4656d6455ea2912903b0cf126a6535108d5a616b189a35d7b0bf474ae910a4f31a907bb3b66af22aec037610077aef6de38f51d85e55585cafd5e0d5c430d3b8941f0b07fe4bac02bfca1f44eefd42bbac60997d257fbb377b119d0ab617361a4ec2073e62ad8840f61e942452d0d31d44fba5b41fe509e7406febffdc760bd26f341ed63706038917e5ea1ccc0eff802cdea838f4921bc3b9701f4cf1c57cc72ff135cc96c729f81030483c4a6c41fdc9798697db41c176a17e22414c4ef6d1616e906868cdfb700038342c883c9c19279efedf764d9cf19d0def1474475b363596d02e3830ffd105e2dbba68d22b8b8a866acdc89c43d395f4206a1ce0e7b7bafab4d5419c5359dc491d621927a15a87a6ca826a8e9dbf07d6e7ccc3cc27cc90ef1143999c272c55b3761e9c8fddcdf3e8b46bcdb1b8335aeeef5ce1d78d64828039a2f0b3ab16864821d8dae73009630f901992034766b62dd15aca856c4e8c16de796c28fe0d0cab82c923effc8df439c492c7b33bf33b150683bf80bcc994465ae9d24b23fd34694e93e6b4693f2d35cee77f1d79ba74a5492b9db4d34b3f7d9a69583aebf403e9bc4a50501d6f412960d03da481caa38cb5044d81ed8461734ee9f741e5082d1cd8aa39661aff463ca9d193362999a49777309895efe6ba8c933c78001c7bee52aa57a4c9bd6227730463a93f245e39140d2a61417304fbaf993e34060d97f0f97cf40a5705e478a9d31e15e712def92221fd77946d1c2db7445de1e161a07f188da563d158e9e2fecde6353aa59031fbcf6235e0a5aec69be5e29afd5834353af6fcab94eaacda8ff19f51fd85e673ff383946cb52067c5f4fb6e71476c36093172dc6d54c0760ce45e0347a7083666c1f0dd5b50daa51b0f2d866f553e4dfcccb4fbe860496ad3c2a188679f1b6127ed92595f8ea3f68b624b32f052a9b8a306a139cd609b0bbb11e995a89f2e40a5620e915111a896c5f20957858b9943b430c4194b10292db3beca2c6c883a950b75442463b2c5545155bfdf594fbe5f9fdb1db2b16d5ecff53c26e45755ffe25c0fe49254cfcd9dbb645751fff4174701fee660567d57e02888beafed6d79793975c517d9cffd5266c51adc36f60d6ff37cbff0fdaaca8eede875a4fad1d601515a7f9d32ef797e8fbc7b4472c2aadffa30bb715d5bcffc24e296a02fd9f31fadfec5577c2c0ba68517141e39042a4b4e86c7318ff2a8800b0d3a0a862f06fff5c5554f3f03f8dd5ffcdf2ff433758547719a1628fa3c2b07f51c534bf3ce5bf38bf7e74fbdaa29afd7f82dd6151ddd7bf06587f043cff9c2fa845b524b3021f207a28bcc7b1a27290f60b09c8835cb213647dfb1b32fca97476c1788a38e2c5dd98ee8c986c507d4428872b2a8edeb2bed0f6c880ad4d45f5994abef42a40d92e2a5ea7de68ab6ce30751bf2d3c4535bf75c1d8ed960586fda009afa8568ddaa4a2a2f9c99bfffe8dbfc5d8fa6d64b5e36c9f5b992aaea5a82e35b32308a7d453b3e5363f384dd26e57057a50cd4320ac562aeab92653c987b43dba9b8cd9c24ff10d98848739aaf23d43c70df3f8df75f794bd0e626aa4e0ef35f137fddedbd4eb3d901803078247b9bba47723235627cd0ee19d97783d6989a272244bc4491afbb8a8dfa602d0936e10c73751539e31758858d0a859b027ca38a4fe7b83ffaa3d30cc8fa3fbe5edeff4614ff3aa85176dec8a19c217143aaae1eb6575c5e7ca9cacd7c3a3d04689d98d4fed36714b20996d67331f49110234ff75831a0fd3629b8c0869a223b1a0e187a45e110228ffba641a37217d6021bd69ac80aa8426f4da8e8d183d644a8912d66fa86e4dc4c8b54420c2433e172c81b08fa4224442260715956d294c537a67592a0351545d3458251eb1cb1ec440a1b0f075d54fd57a49672783721e055bdb543a24ac2768447f8d24cc7b7cee55eb42b1149af1ab9a4cc0ab422f9bd49367d0f1972999e785ac1557d1cce87c5bfd802f81e470ac61614b6b129d2f6166ff89e60e3934eebd554a70016f431153226c916805407d91f9662247071c219aa770a72462f9bfbd00a5a80e83fc2b04e47408cc8cf264053901607bb20c084058d229e472246c81529f8c90c4c2f844b1cf121b4184672168485403dc3729d5543fea32a8a91b43919ed35632f0cad877ce36dae87f7b1442e1276ab076ce8d0b21ad0bf0d551d702b0c0f018ab7d2458700d3add9015d0599250fee2cfbb225d51abbf5fc8c32b63c7edc5681d401985cc78f2b3f2839ef4a7889d075f9fd48c53895947d640f62a8c3e862d5527f05291a3e59046ae4112463394a8de6bf192c80b84d7708ecd5a9c844e05ba581a8993fbe018e4c8bd710ab6da81b84fec4b2ea97542c0b6713c0d0da9344c8996137434ea9d9af14b87655a9c3777c3a574348809b38d58ad1c303f0d73081817f9e9bba40548bab8d25bcd4436d6d6dc9a046a2bf631fa2a2a25e3297b3928bbcb555d01c60bfc332b621562e254961d93b72434f6471b1942722f0e2796375bf699f0239cb3675a52c66fef6caf6f844901ec491ab4dde11a988e24457c88335a3f110958c6f9f7cdb2e135a7eec44ef2b3c0985909b29d88e21efc9d690c84bf951c264b7ef7d6dc4ce75562506c683ff82c3b9806799a7336a9e1e354e24ef0c8f35bf57dd815a2454f8868d55e90b09fd5a2cc8011f3a0b92ce274207e1e936082f233961332eeecb64a6a6a19aab53d826466d4e873a0174ad6aa266d013fb32171e29bb8b26e54be894d15148b897e80ce744df7a0cd4a62ff75290d49c969b9a6215b8a1d1f1377b062fbfbd7bd0681227a0a291c404bbf246a921d6176fe29549434fc3a1f46e5e1f1132a231a902b3fb51de61213a88ec876e935703b2025297db184ed2b49bd2e075f3fb4b4315e13fed5a9121ed18fe84d9d4802f17d17e0098274295091ce52cb6d5d4f5f2787b2790b7680e40a22494bafcfe40ccacfccf595735d1f977112d70de8327e6de1eca5c2eddace3d15ce83706c04c834b6d9172c32964f8247d3052e38de2ab59f90143ac46620bd53e8098fc5d8724526c26d18eb75c4d0c82cd5f74d93d41f81de3a20d580310ef8c18b1ee1e84b0e427951a4dc01364bdc46d68dc701820e965899f3e7c029922b763e40f29d09a16fb028210393555dbdd5e5f7ed7b3d5f26946e37964c631a0c6a54fde72bb2e1c7f8da2a42a5b50e54df6e0b559dd3bddeb39105ce2783c127f3cc3542078e4367694aa7221ec703998870260d4788ca671aea15cc26f8983fa44dd5433beb9df66c29ae083b6834487e32cf1b3fc7b4ecc2540906419397e8c069f51842cc5111a6fcd7cebe65ea800b000a094b2522eee1fe50318d0fa760cdee11640e99b8c8e9875382d71f2803333ea50d48e848b3d16077ac5d04222f989bfa7f47ea54714bfc85ced1b5056cf3f62571e63e8774c0bdabb499ecfef652be7b8185f88dd0e66643149b002955c137bb0f22679e88206faf369e7fc546dfa34063e6b957c888b16e3d761d794c79c46441627c8c1d831f43dfc17fc337ce242294e7af979be42a89a2c184bd741125163f8300f95a5904453d82ff2c8cff32580f7db840a02b148d01f2d3a6ae525a34392d444fcf2b14f765f630af9767d393d8219784ad9700f2d481a991d5dfa893bb0b8483d91cedfb1f3697ef21dfd87cb758b7e9f0b910f210819043a6df48ead066510205312cf5a95664b4a08e163c4d705cca994b9abd86172093fa400aa217a1fa73fb5e34f9336cf6cfa09f224421f41949d5756815831f44d3ab1f09dfc36c3af2b6aa459b0790117cbbe6b8be147c546791b25ac2609a4726300472eb6901f8e0a98c60049cc8e64f3273c68c075a69d33f45e2cfe2574d477cd369e81bcaeefedb034421e71c702ff97825cca817a239ee6545360dfaee78f257a8ff460f3f4c953bc2848c4277ba99fb12079c1a5da4349934c784a2ccffa6a8656b8e76cb9d5422525df4857fa32b073209ac26d443fb5b351c9b84c0b44b9fdb53231f0abbddc0034d18edcde2cf513d0ff1df51c03212feebea1560f388db90f544c11eb4f071184146b5e59880598ab35dcb976dcb87b6fda2131befa0556efd886d05d2b105d47b45b128a5bb826169879f2f9bb01e7676c7984a0eab686e509c57e4cf99c785e18f3bbaf30f2e6741e831a84e1ab17664056b025e6a83de3c374cc51686efe8938b582b1be175281b3d8414e4698459209a2379cd3508c8ce2efe48442b2012a38328363a57be62bfde636d7b321c7036ea5554df60df8d677dedac2c3509041bc46b5ce7b41395eb4a71a946c9d5a4483abee42e8855cdef50229fe11219806f48a027f57b476ac533bc516cb054b812e795127f71a32923f916d483628e867308e5842257a998379d86e49bb8ef2cb49c851f678e91b0c4cf422edf616e539bb4c6e2262059a554b0756fe17a82148784e256091093d590cbce88fc80087a2339657cf768c215d448c785c866f682291215f9c1df95872b780ff4570f9f4be8dc32c2e4e744373b7907048fd7c627b37a6bd4c30e8727a3f0181695628a4fecafed3fe6e5409122be10dd8ef9e6136bec14afd60838e1ce0b07fffb7ee83d8af4e29553321d0127220fd91a14335f19fd4cd81b9f78e1ebb1a37a1a80b38abdae97d1cfcacae820675a3567bd3973352485787bee03f6707e5c40f14713785d83731097493576b1e7c3be8f362f71584a0503f4e6b1ce6b3d3f41acc29843ae10c297ace0fd6531ea573aedaf9feae83a9eb101b64ba2d7fa0ce7ff528b2d17211902dc215e5e4e5175d57d1423a38adc494508581389d4a46f2bf297279913b633f272d0c3eccd460f3171985ebad1ccddef8f69cd41e81b75383e58aa39834a4a29c5d45863cf8f454c430e20c3bd8846054f0f471a66e37cee1d402f8f0cbc6d1c421bc37b13e6a4b01eaffd4912b27e02afaf2b489ad3d1f79672a50b62b404ac3d6673495785e9407f04f46cca600f457de60cd0ebed5d12851b7f238ce9176154fc83b941ab19f60f913823fbdf2e72728af98a50da58e5e09fc911765bd8395221e56e6e78103a950372812242cb545e1c69b6c110f4c17598bc1984ea80a8f76ed63e580dcdd1027984b38602b8e00f9ab481e4bb60f1645f8c8c253ba2205b64001c034a0841a24e1900973da2817947c52a9320282283904e084d946f7f3e433e08c8b3c70be42de3ea148415039017cdcf15bc897ae08da84c1e999058ccc685ea80399ecb1b972c90822f20dd7704f7d5ddc90676607e37ab1fb8651c06e1691ab00b493593fb1535ab65e4cd514b03c0bd5195326abfab7a7fd706f49dd98672cac4edd7604a9f0e661d7b5879cde1a08780c3f9d3f2b44bf31b4ee2535be8822627f540dfd8f4cfe0f12d083fdf091a235a8cdce1cd591d83c660f7a8f2ecaa73e723947f734b3b305e8b28636cc7b234d51086b14626af4ba1a2798b73d147c6b1b9c00634e011055841ceb586542adb300732fd528073831c53a6e74cf4e0d472d2bbf60f3593f5f50860f994ce7a0e457c85127ec9344afeb0c29cbf9a981db518f11dcc547803ef7c11ab9c4de5dc6581abd565f56fb463dc845ee6df3f01e248e50c8df0fe7c7db5020e3ded1ca8a94fab77ad2f684ca382e01c42f3e10cc3f56d238c78774cecc2ddb5ff2299c5a227d9a1ab251f091c1c73db6e952405fea0a92770dfabb07109e5938bd6a6c17318a2b078e6dbab8dab137df88267f8d527d5cdf992ab3664ea538ac1d8b09f983f708cc6299c8cadf602b5fdcd5006255ad6e2178ef1e494020c37e87d28ac208c52050d71a868ae31f7a144936fb1b330b617a93f21463dd4d907e1a8ddeecf2266e6bc187e0b8ffa009fcbf76ce3f5f6329682d7a5f6390bac3a32f43f7cace8e0089f3442dca0880f02cfc61f82a957bd1fa4e4c8725f8d1ae1497d06150d02786a56942a3e7f3b7c55f14b1a068ec75fb8d5c4937805b201b57ac2d03911f76dd4c10c5951f6eb7117f5fe10132d9a5e2673af7e564861726f468ec93dea1e7f0b755e166653f9e0dc24a9ec3516a57eb58f6017bf3005f5c6930cd2c2181777b001b7e726176f8d01bb821dbbb91dc3242b3721904ea591003b95119463ba9200179f877801982e9ac1eb5b4ba9730d90f579402503ad36f9bf0d90818645c999eed0eb43e9efb4ef4800623a5fcc0e189f0f6f0dcfb700c300a85de99f9e270f8b8999c544017a0be97bf7ee12452e3c9b298a19e107d6e143bf90f94a3ee460042dc8a83ee9112905f0962a967bbab4dcb73a0e30e2627377dc1191c1085b2c748e91d0d57fdad9ba1d4b8bd0d6b66fa49257b61ddfdc7bfc0277658d19c801821cc89e6fb50d997639e38b4525406a23a468c4b6f8b10520196c2d5a3021223c3a1db9d3bfbf5e6d2baa861b763372da666c9c9a480da4b25cc26269452c51a8f4ef3b31633d8ba495f7b62e98a9cbef94d4e3cc903a14534ef147a0ec0a89641bb68ff94b8e22f3e201d14b6219c38b1b9b8aebfc0bf3938f2e8c20042cdbab89e0e939ad5cc45021976c18447e19f067b4312c55722338403756f8eb99da62e91988961d722f7400a5d1977329ebce1599427e6fd0c42fe028f8c111507ed746ba2176bcb05410ba8084fa2c39780e1fb6a2a984bef187d6e6e7f5ff05b1f9221556cbe87cdbb5423910e53a386129e566f9f9cb5677831905395f39881a0299551896f7066ef6f93fd27335d7eb46634edd9b003734b307047d36b205edfabd1408c10d546d30f994325f20083cfe6225225ef500ee2401306d6f127a0c4561d4babed6c8d921b8f15cb29b1d602d8971b6e20a7d35dea18c000d3229c3ecc09a90d54a41f25e9e42570438e02b06dbc5c9fda16e45a000b852df830e3f6288bb3f629f9657ed539f453988bc42a9eba316128521f7cd5053513117b09c23848e381a304b3a56d229ef24108ce714cef21662078d27fb305159d698d804d0a0c8745588f8620c5de5ac369aae5936d301d21c36a4c01fc494035dc1f9a72fe518eb17b78ce3f330ca66216c98dcceedb30fa35e5c9958e3f02309be5804c6fe6a958282c88345a83977aea0f2ce8d12504016001305c7a51e5178c0195b86dfc22d5bab7ac4f3739be1dc41a98ba1df9853eb7b3ef61498aff704dea7bd3d1018583c21b25171af7630b6a2eec061e63b7cde3f90f6995afbde0fdfc2a3ab517638162cd01a7433b8419b9ec2ee510404016b3010907c2f71a2521aa97f696898994d52b5c1cdae5f80f6b4e8a75b47afefffe8cfdc509fd8da7ad1095fdaedfbfaf0feddedacb7394a019d85a153e40ff275b6d557b0dc0a008cf5e992df994d0ff59b4576bdaeac0d4dbb88cf5fc21db8a2f7dca84caa9db32efaf306840c03da2396725820ce61950e03c936e082c9d319154add78949efa902ef5ac0d26c2a152d00c2d693aaab055a12ef9ef297a551270400170c2e5ed33b4a43f39426adcbab7f85d242f968a4e8bf7f2b343b463edc8277bef2db7943225290301070007f8065745b64b8ae3385a68224f1b103bc023b63b424cdd01424955679aa44dee89bdb0ccf0cd99c4e18b4bbf3dd2304fccdddb13868c1c5487ed0aa55bf661cfb87723fcfdff3fe9d7c07c25a5948274b657eb4dedb43fd3adbdf3f35a7dacb4c37c9f099fb37fce39c1e66827f17d2ba884dddddddddd2114812baedcb5532ae6ae9d32658a67cd1ac775d16cc139917db0652958b070e1a208734438034a3455f47bbd4444454444444444444445ee6ebb6ce55aa713dd9294f198967c7129e3d5c39e316148d105337bee0c39f3e308b0cc960172599482858becd30c9b387a321d93018de267dcb3c6d0ef4e76aa273bedc3d44fd9bf9b44c9de560e7ad696b7992058fc8092fdf66388ecff3964ffd9ad894ff61b1033d9ff0595ec1f4386d310d97f04128864ff11bcc9fe356e040182f64443530f2821b7282a215e55574290c06aa4791bb2430d8a169134940bca4af6a75d5e5058d99f7e3dd18a60354141193113758215aaa5aa233c5c6129d18292fd86844ff6a75b196626a45ab914cab495be732146ebda769c2646d48c10e112220d591e4d6fe94b1cec011d8acc20049c292643c8b6d6da2a61f94217647f1c4ec9ee032abb2ce9477be971e5c9a4b165ff0fcf169efd8a921ecae60068952771eff7718dfa4fa7579441696ee4ffa245ae16f6fd33a0019a44eb5023ff356bdebca953a7a45126501368b64ee998a051611d12822a5c5f2d3459e35485479563849590163cc1bad26788ae3b733bcc790f61acb0f818d1c5cc991b2c99ebedb8e0ca8e2a314a5c38c1696a015101c321a2797ce10174cc71b4db438960c00d54766a6576e0c164da9034e56125f7ad070b3ab0903192258922649ef28079de7aa0c0f57595e763e15aa895e79c73cef9e5512e95321d6bd6dabf9cbd77ec19753f829bd028f489c260d8598f7255bab396ef02f28cfb9c576e9cf356579a46f3ada51867d05f621d3607edaec8373d89a45a8ee0266194925ab919cc269c7164f34ed7d127ac84396badb5d6da5ba9157895c396c3cc776dbddf5d5bedb5d5cef21d2157cb5d5beb48491d5d8eeebb8452ab3c6914dae408d592a74de9e98832cdfabc1ed99d382fdc2849464515b692d751d78cebb764b6508bd6bc7240512b2a7a95c31652ab1c663eaa4e49e9e86836fbbfb3469f720e3d628c39b394cafb65a1d7b464bf4f6bb556c3ca7eed08a04831e5029ba6cf29ba4fa59ecd9761afd5aa8c754e0ace1d83e603b9f65c30ed49a153c1931f27543dfd1082862346744b1a641e59446ec883c4e685c7173d03c157e8642f23e2b82038cc711c47454b4a0e51aa04914201c9958a24cb098f538127599878c9c982e3c475574ae3ba9e1d4d1b539bef5fcc1b539ddc8f652f8be57963b23169795440bc90cb236f89294a5518e679633a7263f2c1b4630ed72414caf3b624c4750a83f2bc2d717530843f79de96ca74566e4b60bc2ce197e76d098b024b57b8ce89c3a520b9941d11eabc6e8824402cbcf1c95662cb4928042ac8d6140b5203bac1c74f90ae70cb1098d355ac4a117008d7cf13211f102d188f22423c2790478fa7cec9f60d01859f9abc3943b8aa6e8bc805d28575b05c5c96531552bb3238b9902fdb42530781aa549a280843a2fcb623ca8893577f8b3ba06cd79017feaaf7948e9ca61f0c82e152cd3d5ae8b101137a65b8841c0e9d5308e372b3844056ac11a107c50e8124c34067c88d13fee469a343c359215010476d44bfcbcd9023a523164a20f8c03184384f5b979a5ce2a510ccd3d69543eee684b03c6d5db59c44011ad7871b87b63b6872cdf3468b21979fbdc62c7c5fe732ac945dc6f5b5b862f76bafadb5d6d1dd6977777777779b50f2d1f66a3b7bb1ad7e394e4986655e937d7dd1fe1cbe2f6a234ddcd9ceabb6daea9e87ef46e28b9ed5cf03dbdd56b715f460f4939294c27fef9779cdd660de0b37d2b2793f2f6ab3c5e44261c2d6a6f6daa697bb4db9210a50b23ecd40798e4a1cb8373491c54c53f4acda6967e759adc74a1a332bec2613998e26d44c636a65a63c47b10c634dc39f9fa6f3c7da8a6df866df7bff855f117f27362991f73d91c90726ef372991dddbcb35ee3e10f613fb3ef004b556a8ba588e16c37e622150d0cfec6e53a0b28dced94729a55869ae4a1c98330c4d008941f54bafb596a042a289143f41611038832efe3bc71976e4b80f276c68e2f738ec46553c016ef1af3bc269b39a9e98334fc82e00cbfad32706e0d098bd8e77ac6d82cb0fac1bc47347d8ff79dc58ebf469c7fb2b94bac9260963cce18fe3525c0daeaac36aa1065342fa0d82ded8ddc1155442faeeee1ec3efe592662978c491829f28d88a75dc72393a6b955dae5e1077de27644be06b391d2cbe1c9e02c2f0b59c0e165f0e4f81fdf4e9c44b7e3f60275af28b819d5849273f20b0139bfc82c04ea4a4277e6258a3042c84300b95c03fc9fa3f30187872778aeda66261d7513c15024df1a680826047b3d90c840ee57d3c0a85f74bf07ae2cdfe60235d136ab62eb21d6dd89a0afd1e9eddff79ed469d57bb11acd53d094aebfb083ca96dd1083c09bce149dfd3266dcaf3cba9359647992b8afd50980660ff8d39863040c9ef29f98d4d2009fba64a327def03937472d03d2852b26122541236fa588a91df37792f0c19df382707ddf592bbffc2257321cc2fb46328291d1dcd669fbba9a5e525e32e0e0b617af1c65b54d435e7c3fcb66e7f9c515b880f37d205321427aa09a112414061ec07067e5e87b96bab379dd6dd73501ddeb0a8157942fa2bf2b8d79dae38f56778fdc0bf295d617f9445f9eb59cdfd29c016fc79f19296ace4e720283af955f0c244ef67fe623796e97ba1e5862050e754811fec465cff8e10a8993e5773d3aff7bd762ad55e7bcbfb9dfd7bbbb1fe8745ce8ea58e6c692e812641455badb57786b59eaf5880b9c1d460cefab127ad9844f4839f2bfa33e8cf449d2bf7994e3804810cb8ff0f0fe0a7049a44b67fdf67d08a6967e7c38d8401f8617041966514d7829c9d463013e416ca257250febcef0341d0c558fe7e404e9ce44f0c7411cc5f08baf8e50f0874d1cb5f0990c399cbd7e60f05e862cd9f0c74d1f377014874c110063f3ed615581cb99bba0f76c20bf2bc2da1bd094fe47993aa4325c5c3124e2a4cc52188e37449613902474a2aea872b292722706c51449c10070b873894618fca4b851be469b35324974742c20bf2b4d5f9ca13a2c8d356c74d0c7576002289064342fa5ae1556a1aca1ebbbb4b1912a1c9fe267bc4c1829b83f66d431b83fc433882cb6c8e7118ca64ffb31b1a080947e8ffba31bfe697adb6ba25c939e7cf4979a843505c6badf55ace626bf1c85d5937f50ee8021ce4b34e21fd2a809f1dc9e605303c4db7d79f2abcda5ecc610e0871ceaa00ca0db4b5d676e5c07a31b6360e14e85fb5757c3b39fbaf74c5d0621c42adb5d6103e37a6b5a12e0e358e28bb09354fdbd1d16cf62f9351a8282577a740931e5a17422b7129a46fa285b0eca626da04e47b269125e4fc79e29b9e444e4fc2f48d98196a705ec8c2a25ce0a0e1a25fb07ae8f2b696ce6a2de922bdbac18a560ea794c8e7e549145513440c5c648b96c812f26b1e526ade022e5768d5ab8621660bb4a8f075a1587969a251a2ee50611bc45461755a32b840f36e73a03ed0c5c9a7ce538d6c91003f901fd8e48b0d06ba38f140e1c345b6d8386c21bf9ff0e795b8a240912d8e58c81c4d9b4badb55619f5b798c31d5aef1e2f05bd56af3de38854191e649b8764bc70a3696d19c6c1508fb72b7b4332fc39c834d31101998e3af2d7f64e9ea58e6cb9a68af99ee9cfa942c98deacf3ae2c841fab8c3f7615bbd3d3033adad3586e24748e75b9bfd5a8d982ae6cb8860e5f91eac09e97c0fccf8f7d7318cddab3c9fe338cedadb94adadae076371da313d9bd8da115bad9ecd178784f403fbb8d6faf78baea8b556112cd93f7bc491c3e6e8fef800d1f66ab93157cffda21ff8cfbf46cc16e0cfbf5557b55c86e18cebb7f2f4b2796979d5b03c29f4e2f2eaeaa4900be33094c9aeae6454611414549e2194ec32e59897ed63f5e4190b87c0c9f3ad9bf941f864e3d8ae46e29a2deccff9d6d69c67edf53ed07e1fe87520ac7aded761d88f779d87b99fd88dc733e895a6965fdf7aae175b42fa1e972785e0582919240e601ad92f21da0f4ba407841916823855d87fd13018f5ed0a2e47cefa6198035a0b280e358066f03bd35c024d6262aff0cbcbcbcb2bb74c089727795c6dd4fd1e8009af3cc9eb72a3fe10cbc297795cff41e5feab12a3075b82c4a269f43d508afc7d08e5495e184285503fff4520e3307bef71810fc6c632fc193f0e64fc74f6793f6bfe7064fcf43b11ac73084a9717c7cb93bc3b1d3ed10f3c371f53531f5af6f4c0099f72bfc73555f48b4e1e577b5c1e579e2b2e47f53d5088014918255d6d6a681206890db344f47ca040d3a6c70a4880a1c10a12fc2507219562c9b261c458e7a1490ac90f4e8ae4599226eb059cc525005e8f16b2c028a1e1d683114976e63cd9d266abc987100c110dfc4b094ec00245c6cc52162d1ac832670839533600e921ab2b28cb8821365490609119e2081582e0833b1c6686eeead04d6982e1cd983234a8253534753514116aa3c30a0c6762c05244eb871f6cb09beb04c651c9a262b3b2ae781e0fcb5e1635c744ad69a18725b0bd93ab439c8724bc79dea2dcd85113e41665fb7e68dd89aae11a6e513184b869308e4aaeb99c3ee4727e51261a1585875c522c4a63b9a475f264c1092ff050e4842a208a90e5cc023cf0f045c64627cb0eb29c5dd38b36e5a65026b8ea618d5498206c90204b5aa504e64c0e61725092041eb2a457d496fb27f58a2ae279a2a2fa298d4a85dc4f6b5154723fa685b2306a299733a6a32f970bc25928b9e0fa0431e951075eb5d73fb8a08c8ee7254f892c2f71ec4f9e37255a50b24230cf9b922db93ce2b4429ce74d89162b205558f3bc290977aa68343286704e0afe583b5e114f15d45a285805461c39c81eaf4c24f6107943bac8020c4997947025a921ca0f1b7850dda082b8044b114aac1047182173c12a7a5043d611f2e3c02a22255952457e18aca2076dae92905fc7e60a49140face2c423c6c9870face2afa121c1058255c4f16ec28c4142e6e8400a9337647d1771d02a7bc81c424ec83a5671b699227303e9e23cc387ecbaca6d93c366863fd6d24f1ac20409d245a12f9ec2378b4c784ae9acc0378b64619d481356717c30bcf2bbbbbb5bfc7ec2400b83d5469af393cdffa2598afe81894f2416cd8e58b878e11ffb212c414aa97ff8b63db7d7b8bbf79af995958291dc5f6b38ccf596bb7b9f71776793e97b7f95c9929bcd9adb3668a6b59baa7177ef347da6c8ddddf61aec3277f7dabde6a8d5f4dd127277afb567b3aa2685bb7bafa9ad66acdd5beeeeed6a6ceee349aa6d50dc902631406d8d1d420088fcc81ad1502eca55c51851e14cd75592a5aead7b77d8b103891db433b5d1d446238a192c7975d9410f97958fd6e3577d45cb097ee52fc69b5a872b4bde44bd6a0a4580502fad9f2658424c2d281be630416d592e5eba64fa5bf78a52d9a6dc2b17404d595068b840536bb5f6d6f432bdc4b8baf038d328501a8d8287c9327306fb96579b6ec0fcf06a3ad8afd486f0341a6d87d90563e468997179936b0b3b1afb362bf603cc9bac2da2223146542012ea406acc1ca1da686ca09c2c4b6c5b4e69341acd86a7e1308d46b3c1a1a1c1a2c4960e7068b000cf503953054c4323058a14f45ca191024d37ad891ac9469db59a66ebbbc91095e9d6168d231354ce54399211e00c15343464366e48a3d16c5c4ae909b037c579c2e8f3f89ec26034edea3e615dc708e8f0517105c19e799d6e6bad3ee703aab3c59216518f6d8f326ec6e4fedaa006627996716366aa825f6e51b6b2509e37286432fe69e48da58d9b61de738f394f9c81df01337f9c58ce5aee0adf13cb17991361ef8936ee0cf0699e30ef71f93d6c2c218324924822cfe0667ca38d9bb9b77173799ffb8f76e20cefbd710637ce180ac1bee7bde84e4747fd062cd8a7a4678c952d3b33b0214296321ac2dc1a68c0e184d7e4c1973759a4aa7e90f3dda0e050b10679dea054650cf2bc411942e79b4562e8d8c8696248481f02971e2141043d739cab38690dd55126a842da54473412ed17ed6e6d7094f67ddc57d6b35a024a581211856135f2cdf76c82294c1aead74176ef7fc393867a94c134dda8c674a34efcbeeb1e7c11ba07471c56baeb5184eebfce1bbbef460ae74a9d15962cd5a0d1c82337f2cf72482b27f072fd164f40738b267684e5f4aae149267a84e5e4ba4302976588d033b7d1a61a3720c03d09e687341a2dcf11808e08cb39a96c9587e18863322824c9fdb5311e5a14801629b207b964f1c48c93dc2ff392fb7faa4dee9f49e5a672a3f446f6848a05c6a187f99c429ce7ed49d47d2ae2f9f0c449c8931e2c58272d38d2446a4eb6a46040d59c80410283c8573f97430d6278844130805161900a638240f243103f383378be38356198a70d4f1700f2b47989f1e19b5e6b7577ff5ac770d6fecf80ced93da1edf572cee1ee67f9fb92b6e28e72453ebbeacd5a6bbd6dedd0eeee76f739e79cb367cf9e4a476ee4ee2ef3ae2fab5cc7e3526b6badb55a7707359812d23ae79c47d36badb5d65a5ff86691f7bc6f6bad0e54e79cd77ed9596be79c9f091026fb89812168ad6da439a7b576cee9c5c039a7d2911bcd5a6badb53ad80e8bddaf97bb2f7323ea312853535353754a9d9a9a9a9a9aaab656d529766aca4ad514a787b6577bb98e3b0205d5242aaaf32e65a2d126cf121a43642605dd42b7d4f0c2f1f58155045c344680a90c1b074680f9d14cd6b0f901c502c16f059a456b3d6bad36c2a21f501a6855ee2e14cb663bd22d20a01081185a93fecca25bda684603dd92fbc10f73b07fee5aaeeb2e08ecd5824ca9158a38746add9c38652b00aa2b5aa10db72654f01686024391a64eb6b53c867e56b6d9bec5d6d645c98e14a6ccf0d5c47e8c8aedbffd91ad182bd9da0f2d8c277ddef440a74a9e1a485b6bad56c2f2bf47f6be3199cafdd5719422fb73866cbf36528a6c97ccc9f61b06c3566b2d9633d93e088d1466fb31ec47a0adb5d6eb2f6102666886b0735965a0a010fd233ace82ae42daa7962d6dcab628dba750f4482345090d11d6a71f444358e2fc9884b75fd6ce223bce0899640ad6304e58d64a4713b08d9bc12c3a9618a45f3872fdd4d7d41372c212cc7e1fcf24fc378c6151e456d442a4b296762884fbdc73633914255f2a3b9a209465cea6bb6bb750cf6c9d301b9ddba5b27e16cd2ea83c8dfcfdd53cecc554e1bf35a7077481205d3d9b59b93efdc4dc61176bd4652dd47d7d163349368b665299028b9c78c531ec9950cfe6d79a95ddfbe2a918dbda359deedeffa2a56cb5f6defbd55a6bab15e2388ebb1978f7782679df8d0151fe58ca7361c5097bd2e8c06449149b0e26a0101182c4ca8e94145b0bd9412273ea64b913654c9025283e6dea7c41e1863e3308b1e4137380012039c245cfad6a4a4b14e72427851a6a538228f3e4858f06515ae043844a1d3c559e95075c886386ce55d50d743c480a676a75c0703112857be8e084291f6c52b093664d5d81050b34ac19b21404535554b74586ec8fa36e0f069059a2a40f973c607c4c07040a47682c5c69e2c5737292fdb1147744922f4b8931437a2011030f5a5ab54d100e44a4e0d4c34a1e1fa2ee157f2c051a996988c07326c994141a6e3d47470a39ac81a384cf0a397459d259b5487244952c5053a2a4c123812878d23819b161091fcb030664981421a4c89a1b93630972d282aa3e5235da90280f4f0786d0010995a53a5288a8674020074d9f21564a86ac89fa9efcb1d40e2b40e552d641a63bac402bcab4d68e5c64eec7dc0d8a08713bb076e4f18fc84e0365b29e296d60242c65d45fc78f3368b0981903430d07b20c61e893fd6b11d98508224b192f6a92c8f142c240000ee1cd7b32b54bc6b80993a429062c575e90220fe8700493275b98a2be6aa62f3c79f8a55f50f3421f186cc8fdfe5a987e423138c50b564e1cf182927ca2ca903f2b33ecd39f2dacb8c154d14f6bb93f057d23b21630c3df4729f7ed58933ab0c51081c651b7db0d94584071df60b6987c961290fb8e4398e4e0a92663841731aae83246e87fd11f03ca53c1feec5a7bce19a5b368b6a0426349c3bebbbbfb84bd7d9818f6accabe3a5bfcdc0a838d313e3055d4e8a40350dd606a526bad55b9c2bacd084450b970fe2e581bc9849fb78133a780264c96f8125f9a28815d94b8458d04f4f36729c21731f61a35054c04df45a06f31f60e830164c3661028a6e40d83011b87204033ec1d4033ec03313c29451bf96c5609cbe955a391beeebb231573c5cfbd3f2309464e3868975cbdffbe7e0d124cb051c2fe036163d919f63fdf6219863d74902a087a16ac20e87d5072ba51f7f7b01272340056020cc6e77d6fc373f7d89358b08212ef839225be8e4332823e68c441c9a0d09330598f54cc15f5b915b9a6982a6af5b21df154b115de68a412d7b0d1c2cb3548bce146f54b982a6a4c151c2009c5a15a6b2dbcc1a757afee7d7d6aea3ebffa9c1f9b3972ec69ec5ff48c7d1563e3f7ac9d163ac9f3668455aeb99b684de91214d9cb542c813a80cb3317738e1ed5b3d88cc56ccc4e2b2d51575697524a4570f7d8777737adbbbb5b29971db394d6148bc5622258b5c9ad94e7f42a9ee44dfe3455b437b1109694468b817798d026f48ea9a28946699dd4016d6aaba658564d5aa2aeac6a6d6a8f6a03e47eaf3255b45b59b7eaa4035069a935d21cb3d51e95bb3d2bb79bc9ed6c727b9cdc6f27f5a8462abd4aee261dc57995d9a2c9932853f7534a9b2ada00533628222ca7191270f7fd1c10818dc671efd723a5167a6a1f167b2a964d9b352a558402d991296d168a33ec3215124b8873ceefa9088b4d3cc5ef673a97dd7249e010038ddf3f2367ee0913c1ca101e75e4f0276c041c3ac8f03978eafea6f57bfd340679402ab45a664a57a6fff589329e956999295a7289cb4c999281f22c33252a9799e264ffc6ed7b2b6dbab5bf40cfa9bbfb8650834b328a4ed11d5ad1b3dfe1dafda52f44d065738f24f4cc5170fbde7a6bdf68a4721291b9707840720966a763f7df776729eafcdbddb34512a60affd93467520736282d55d995b6341413c85544562e295511585758f42a7195f30beb81a2542b09371a69de5cc092fd715014cc16d666123c49461bf98b305578368095d0bfa44a409a660b8a8b50a94c5100913e5064c10abef741c9efbffb13fbb1ecbc5abbbb69d75a6b77d7172e76b436fe86756ddbf6da0db6c2068952d0282a896acfe8ec19ee1242542b0e4d30516fb0c849a6880c55e4478fc0d1a257ba507991a16c4f5886637c18a165412b521580b8b922eb05adb8002275b8c82a8e58c88f03ed0a22c2be61d1ca65512e5ddcaa3ce5fed95137bd9fa110702e73647f4a99e4c1d244d617947648f122ef5cc143233b8d9a2d4d138c0b2dd4f6f488dad3a60e549257932bf2e3c0168980902ee2a05d6a4f4ec81c778bbb787674d808b9e2f261e07befbfd1fbf9569aac44755df73a32c61dc6dc1791589ce4bdd77a4d04b54adba12b9720cccb4c532ea76d87ad30a9c96babdbc95e279760ad2416e73492c2f50c8f325a9c6f080338689d2bd2fbee6dd0ac8121fa28b0481c28b0c8efbba71c3c859f272067cb072f5547ba4ce9980109255439c06083172672b6c04e9218fa3051f2c38b040188ed0ad51e3a3fd821fbadb6f22ccf9b150bf966f595e7cdaaeb66b52387b305ed6cb952c77f781c8a31337e1d798a252ec16ccb1bb92bbc5529225ff7e7441d998a363cdf91bea12ba657b1ba93cfabedee765c9c22b06b43d51a273007219f97270f0b486725087602c555399d70ab684e564fed7a721bd410e9baaeeba4d832c7711c77b99b94accc3d911cc771dcb5a15e2b1e55d18b62c87e86803c6f527ae49be74d0a975c1ed9c21df3840f5896e8d01a0a539e1b6a53cb4d13216c34d2b1dcaef4089cb5a864edf814b564664c0400000208f3150000200c0a8703e2803016a792aaba0714800d6e883a72543298c8825190e2208a611808629021c000409031c010e5d4dc0d706260ff91457b5f01353193402f72bab5eb96235f5f397a294e112b7ac4b82985396245f84e0c24aa7e109a93b73c63d6b489fee112f7931643ce8c30a31377d1d37cda4748603df18c3b6247653ab958487a9173ecffd586fea4b6d412a877390f3a96d40f7a847cede324f69557ece127f5f96d9301d9fba4a83a3749dc7338c596ee5f2ce49e5262a6c093b33f7dc3ce808f4efb0889bda4c53f792f12f83143de4b10a4ed154e9c88fdd7cad33f4e42f691a3ffb800206326a15f9500b2080274cfeec312a9f7ebcca958bd8fe7695eddc725f44667b1f4e882207f3f707622f65f234ffb3889dcbf9c3811eb4f945fffa084fd49c7058dd28c276ed6025530e5e9fbf4a7f2096798ba161bddf88fc4557ab614f294b2f2b2f3ef4c3c15eaefde607116ea5501357f2f7aae2846669c96c345c185a29a5e3961214689ff5c35e13631ff2fbb8514458965bdf1d8b2f3b8308c1f38276f0d7b8b05cf9722c0ea8b5102681fe1169a704abb0104bbad057c6483658634b5b6be47b280f48685a2282ca179c57d2cb6d215b34469264b6ba87b0de6a7ba830e33ee2ca624ce9886e5d27d553df0c415eb92304d9b5bc62f4c444f07967e752a884ff55013221a454f4eff02da4aa9ba7c84370a4f996112868f3eda4671c004dd4fb7f69f272d25c214ce3d254001611877a5d9eeea29191994f42805f3c1eaca5d991e392105d2555e6479ecafaf302535071130ce619a59049bf1b6e4f6aef6f6da6a5942468acfa81e30b37052d5955966906e46504ecc2de1a0b1a0749db70ee129a645203ede094b64ef7e5a2321b3496cddc657b5221ebb1e49808a15c43c0312bc214799920bbb27aa906231adae80686041dd6e85cb8c0a04d39872fa33e6f8e8e9a691acfca024e1f9a78223e0eb119870a8ac3b5d7566e6e42bf7286871696b852a230b4d3b7b5663951de0a2283f79bb875139b8fce6919189c02523a9de9235dd02a39269afc5a58a1eb46f02dbd55ae30ebb3367bf5c78dcc217c98b8781963a8b59895fa556b79bbb8e734f48ab2bf5e12fb6ebf34d3de28d3fa53ffbd4089253ad94eb20d25e350887cb8e08c2c7785cd5fada830a2de069bdfde38cffbc7fff618bff057ffc5fa46afa3a9ae6bf198295d5cb0c63683410168ac14287d894195e8106468ddf256181092206e6e017e951178ff5691e9191633e31e6a18161172af2423cdf315a1d4b7c6922d331332079c2c04cd0154cb14943f3ee18988b50604869a570f987d469e92e8be3afe175d6f241df5d37694037a8b9ca764199567cf50ba0fcb55c2f7041586c1b41c0c218a4c2f0a7d276111a6070358eded57b22f90a43c5749ae44d6c508b9be3b011262207a5b039e1de50ada9b8ed35d8c39790db7206e4920fe9214c2c04603efe1ec5c6ba3b54dc7c3ca4d5d87d4d7053515a701d16dec6abcef9556812bc73de79c349883b04e66b01e7a16080b41ae985598c39e3f053fbd6dd30283d77f54ccf9910246215a3631ccb97199a1bdab1be00e8493ff491f6711c633acb037693873ab10708911fe71135dd1472542d2590eb7257bc5dfaa1d4c9a1cc447d525912a06c08f24392d90d648ea8b0dea3f424303278d842d5a8d2e734e1a4610f2c267a0a4257080d297db59f4b9f2cfc44f70138df5f3dd57010498f86cfc9b92eb4f398cb161e11333047502329648e4441636173301411498c9d31633d87fbef2e095e0ccde53b9b91641861502688d790116f5e656c483a859bbb6e8fe5c0099540b4bdd1499b71cc5a82147be4c9379300aec1b336a988800cae1da75f411c226cebebd9b5ab400be7ec19112e22fd4b47c7e91e4f932bacfedb775eeeaea29cf80a8e74a7567adfb2ee552165eda125b5058cb59091ad4c324f6035c6de88c1de8fa94d72080a41629319739e334e6d2d6ebbcfa8d5058bf727c0b9da34c24c30f0230c0b0d8eaab344b3892606778977e5feb0ffd6b4c500e8578c3ddbbb0eb1b6d3c881087b93103a7bf6772a5a66e96a920deddb0ab5a5fa0aa15184dd09dc4b7632ea029cb928a765c737a9b9d3d4b22151a896bd15283c28161039e3720aaeec1308f06b602b390f98532a3c18675545e0f7a95681bd0d2eb3088f8f4420730e5dbc0534b14dce2db93bc648f4dae2cc34ded484d230ab1666ec813264995cb802c24ddda0e081651158e5c6ee28492286508dbddc4e7dd9b7b0f5efefcf113995be1ab8ed9f89ed3ca03775a2211190624b15e3b08ec7d407a54b8695d6c9f8940d4a1c90c0aee91248aa53b14d6c8fe5d7bfc781ab359b29693a40689c71f565f602d5cc60a472bf099559c458bdfe4f7d223641471aae71030e2047c3d00ecb139162fdb66c9abfe19d4bf28f974e6cb841908abdfb42d4a1fd110ad2364411ce60529037fc6c18771b25d4d2e6e354b8561a25f55450964b587232b2128db198119ea2095159ac520a4c14f7212b14b3a1ea1c2262fb18fa5880d9f9971585543d9e1688dac03e4eb66687ce09f397a049426c7f844682ebb3096021e6542541a47ec3999473c463c5a6f2d92cdfcd04c87171e692808504fa1a5fc1b0f89674318a97e13e5bddae94ba3ed93db1651da17b7cecc691c6c1a9128411533e6aaecf69f93a820fa29a7270103145c128a114809765c8ffeda6b2bef4c1453d0ef9b7c0d4548a621013a87797c5676426a0f71c9f0db3e2540d4c730b8abbd97dc03dd2a02930a52a199f40ffff8470048013b64245e629b50b80018878e6effe673ea745c68336e7e202157c7826075b0080bd3874a24c61bec107f218e630743960773a53d676cae0dd7aad12d574c592883492618466180a6afd54045759f386bfb1ac802f8fd2d943478bee47568cace032656645299227b482d91561364095a124f4a83cc0bfae7bd60655768ca744d09fe8f9a9604ea9aa266757e3351c5ba1a1b655559ce7e34aadec251a4ba2c0476b9d4410c986a0f44100ff5b5eca05245afcc5f74cc3dfb28309fe2f164ce35c8d2fea4b497f098f83cc6bec0b459581be27db0a297516a498f6a82d3ce52633beaf8a5f739c9489fc8f733529a8c90fcc042d7bb2c38aff73f6439b5299d4ddc07d9ff94dea18b100095447ce46cbc1ba6c0084f2f09eac00fc807a81bc133ebbb96ab42f79107cc77fbff474d0104fd8564704cc6deb311d351bfb6232564028b8737997fbfb56449d0e984207b58d9347fab5daeac6dd87dc0e9d00057afe654990dcd482646609139915492230dbe39d6de53096f2a6118347633d85d3f26d79daeaa5d78ad596997d5bde0a25e918316799914ff6796ce01fe6f0a275f004edd4a241dc80af1ed34fc4c2561bfe867ac8ab8f4654ca9171c97dbaad7ea293ac4be6893b3ff25eab397956ec1627c07f0266051c74402b6b8e77977d680db5c6d371c9414d3ca47692f002ab27533fd4f210f601a38c0334bd1a803b615767bda68b36523fb2dd1d322285202360b99f6ffc2ffbaa79f403cc06c34a8a8fe22b5b03dbaaef150237026a1abb2aa6e12fd619d76636d5a5c982a5b0d4846059b8bde93c63a399d91eaac6b142f7de254233a886d46d1942bec80142b3a74851a366e40d0afd1524e4f8d2e8d8b7f247f22bb34d771b1f548be48e5baf06d8233895cd3757298a8c19d15fcdfe11cf996fd31eaf325c4c0c539c0a4a595bc941439923fca75991ccd77c0f7f012247d36962c55f5145a53fac608dd88397d51176d92ad534467f4285a13c0ef927eca7b2cd63c1e1f83eb2dabb2308efbd6fc2021b2c2a326fa4bbf7b5ec6d7f1db6430162d7cff8d5d076572f9d5a5d19e53e257540657d985efab09535477f9979870d2a4c6a3b2238c5fc3975bea87ec92808f02eac3e97f3965b3675c50a80684e9421377b1f13d20e555c403900d9d23fa9f980b4e80a96386d21b3c4c8761eaac75913acce754afa2036e9b309a1822ba3e814219229fada09d2cddca00f8116c2abd3a4eb71d60df75c16bbe2ad0ce24ca1420da8020b3ef8bdaca7a6208944c0de4878fea36375cc8b3b091775778f6bb9552e41bc2d33950059bf7a6d3955684262af3b59ca482bcc373718c0b0ff4794b67071f58ea784c207749aa69969a92dfe67061ae67e70d41f193ea35036c6a819e7bac0b1d497de70ec538b4b951be18c2097b169a46e2f68646bd55af3020445321ad404297115bdc8687cd1b48312234ddee4644c43ceb5676cf218b7775ce820042eb6738cdc0092127c9573d12021c5b239ebf423a4369cb37ce538a26d8e97e30a1d57136736a2c1ca3c988e90305ab881baa9fa11e1a3310e1ef92b8a81f3fb3b9023ceafdeefcaa6076e9b0019589638d0c00b9ccb6c6f037f0788ff3808044f1aedea9dd52e5d36c1eed304e59308d2c92c9c0e71a24081f23fd49e452afd583f73f4252447f4cd4d270b5983f5347c4113a35e8e25935031780a05a04bbc897b92f99047aeaf846565bce821137e6e9843267f189704a2ebe197f74c13856b80babe4456e3eb0bfe32b789b80eec042ee18ad9af05c10d0a51d31dc5a825812465952c457bf4bfea39972162261836c588f773815d8649cd1a4ee61b8011c5f2a258a997a1637b1ffff10c8f97c15645b8fa3383999edd4aaa9762afeac55e2d7aca666794deb1f96da447089d3a22069dcc4d286239755b2e97773400934f887c04c54e062c2c030b21045ddab9ab5a836272a6d032952865eeb07a4e61e84ad88f93213d7aacd2841e2c548aa0bd453b5018bff23caf2ba531505dbc4389de4dacc0a2a503fc152d4c5decf8d4a3294fb6b074dce12585d14b4b4474646c48db0e571e5600a233e7977765aa7adc8c9380d3b491911f1cd4d2b88c417d34f02cdb9d7c2b7eb1068f91c56dc51e56e0bf13c5dda27db00207903f9f4a7035bf6c08eb150a9c9c7e419678efbdeef0c79e1e0c536600c5f7fc1c426cd011cff77d199ce9aee8e0fcb8cf22393c98811ff284e07ddf41c36ef582f8303314bdd3c1ea91f08e06a005b48a23498a68b01537cf2771d51459d15cfccc7edf08e3a4e6a31957ea51ba1717fd3dffe3a28b2c11529cf386bed7aa2581ccbb5f155900f9fab6dd4c83b3bca7d0c30e21ed08c2ecd1309194ff02a0afb872371aed068a9178ac0633fa0bcb82145677b56203d425208f250776404a6f1c7b0b13ebd14c3ea5427e8bd3d9a6138c7152a19bc21fe6deee087c84fb7b9d962d8d3e467cd8897aeaa85380f4dc9a274efb50ef97f2f6a6be56a25b4cbfb17e61bcd7bd5ed8bce7e172b0cd0373881041a1119d5c9774ec4ceb4f2783e494ee2b4c89ee29794bca175dd603a1410d811b0cdad5715264c87a0fab6fd15c5a404deb9560c5bfc16eaf7ff0aedc2489d4ce11da09bd98e080517ed66b5d61a741b621e539a0977706fae7ca22f46b5782043eec65b9e05f01114111721865721e817e303a4bc7ac942ac72f2e384e582f8dfb3100630b35f435b06fdb1f4ae58491a4c8f99c58f3f4d5f6b93ee538d93671b3c1befc824f943fbdf056b13465b39fc1f032d2880bd6bc9dc708018b181211732a9a5c62ad70fe1628db9c9d7a7a3962f831605d2311ef627a801ba54cbfb288a396f30abd0dcdbe313ae52d45d8db49450c0cc0b9081ae93a11608962dfb1a0f05525e9628937eb0ddee1701cbb49ea6d0b1ab08fd9a9822e835b34af400d504c9e928c86c68da3066fe5584732f0412055d1cf256a63f14ea3be339c621876f7c80b10be7a078d26b2d6ebe5ad83f7df04e1cc8e2921b02583c2767620b55c66c98b7163ecde3a8adb874f77ef101341b4e3117623de8166aeeb81a2551988b5a20e206bbbb253dc55ddaa1349aeafe38e4e12d305d7aa8ac8102e621b2acf717669091d04d95e2e2687b6f1f6724b4b175460bf608bc5e18efe5e2c1a46eabaa5993d69444b42f11ae0702b0d0cfb5cca75cff19feafa13d426bcc614392d2244691af5526a0572b173911403937a15adc1e802aff7231859947050e48ab06bcd8dc88370ce78f43bb1cc1a8bfd8d66ac220544ea29f50d935d2e2f13a523130ddbdd20c9cd2457d50411a594333ab6f171b6fd1a6da081b1a1022ca590107d3d71f02ec1f128f7c6c80e6e4219363ef0340c1c01f1781138eb342899aa4a0a7bc79bc4179524a19b5910fdfc0e97f82340fc1449290664247a63a41b877fe80b9ad748d15d2ee0d68969b69ee7cbcfb989e96473c2b38f33b92a497f8373600c729e0f46f00945fd2e89ba1531a4963db7f3629dcc7f3abfef5fd9a6d4ffd4fd4e06f8ab1740f8cfab4d390e3fccf8efea50fea9d5095e8097e2f066beebf13d6fe2eef3521ead22fc487605109ad594956e4f7ac29ea8649644a24b56a4594bb8958bd68b19e320fc1f2332f33c544bf30b791998b412547500c1322062a5b7b85755ee23996b9d1cb64df4690e2b27fcda198758d64a9691c3d41d88f4c4b29412a2c79eaaea92dbbb19b88aab40670e176cf15f4430611286f66e00a399f694a965d43297121009e46b951bbb8884334af756771b775702b1c6db2567eaba538ad84decac4e4021953b3771eb32ed6e1fb888b492af55b8aac480f44f94028aa2246d09b77af9dd25f7ba58722d8b71a02c92b9fcc845d1fb5096edc932416d4ce73fe38e5ab1df440bd291e7f8e1da84b627922eabe4b4aa8540a995bf202fd2577764338abc89f522548b0b3d55b0bd8675c62194896225e3160504f1def1701954f67fb2dcd7ba4defb9b1e706b7cfa5267f546a62cb3c3c02216d1d3e7a63b4c604bd85c8b285698f643eb3f312f7f7796e6d0b185bc336ddae5aa45d0a3d66b2b9840b7d34c840f0a32c7bf733da87f0304adbf78445c61d476699f4a8f4165a0b0acbb6ad3970622528de8364a4e5b7c48c41eb452470bf72aba91de15ffad2b8e444dcc07594e94d90d55ba4c95a8c9db74eef8e8a5b764da7ebb8a910cc5955b93bb29a7ec2c7b34bc0d951b8f2475f8a33c6406b54db773e32774c1d2b7d2d4271c1581ba57c74f6d9f4d14899f1267884ada9c662e5195b8051c9a3a00505d09981d4947b892da7d42a9351c3de283e64d455956ed8fec1de09737293e790f9ac85846fd674be46d0560bf069359336283ab31494b922e58030ad4d2aa0fc751fde9c1c5afb91c2477145d44595ae1503cec8f6e2130816ee626ca53a5827fe4c49ce089e07f2c2d26d7d254ed6748c377c70169506ae8e6030bd04060f6201f307619290c67c71056d84a4ab7b07f80942c5ca85c43beb94a7f51dd03a4d13d24772c00868bb3b90c75b4daa61112a182aecc2ae4f63150074baf3b7c9aafc3507d845111644f0700718220a1028f708d4d947ffda1af51dc12a0b497359d95fc33d5b213fd623049973be0d3dc02256eaefe5fc4c946597417a0403e5017e54294342f0ad210c711c4097e15aa59a99fd56e26dd130945340ebe397ade5f1171ed7f10e86e734f2c60cf9fdf7de45e1b0c65b3ea8bb2e53ce8508546f2c32538065a7dc68851ae68e2ea2b20668adc945a403ad1d6a21ab2aba6ba8980e76a86b250b531db5fb4b6a40dae8cd585ca9eb2b88fdd95296e2b02ff5fee79f1f558aee4050aed2830e5588a1628e5fabae61e9097e6de01a211e911ee6f01d8acb6e1f9d30c65bab127cc3609c25d7ca25474de1ce796d9734e221a564aed581a8c5f37e9b3608afc6608053a077ba96a59bb5d0021aabac3a2457a741a88d9e0e5ac9af6b297c3e90ad1031a17bf44eea3a040548359b230c4067364969e88e0c284d60d470810a422290279008237a2d337ef74d99d098fce2ef3c04c2e589c056fd6abfbeda44242399835e8cbf0b4d3742337c542f289ddf4f93c4e6b6b9f25fc59be0f9d2c5ac6ed7aca79ffa9f5f04d3fde83b72807b63700fa7f17bbe835078edec3724df0e3c2998b6a3ee8c9323529b4cfabc9b47c288bc1585933152cc3196273747b9ce6408f43ce3e91352ddaca186e7955d2ed0afa0c443e15a888c3d032d627e6b8078aac7777ccdd55248452b9c9f50b5b03bbccc3cd02cc2deacf20929b235e11c27e6562d4b9d8393140e837a06916caf876c464a44c6a43f3a506b5e18ded26995dc95483d60ac94a87c1196d7644e3079dfe3d927a3512ca3fd70a5b2d292a70552cc5b8d4111dada812e9c205fc13467e0e9961084e10bbb16328113e2e519c68dc9c3e5bd3c45d5e4da7a80f6ad432be1486ca5af16522e4876483b65b57a4cf7a93320d9fcc512015c5d89386018a4f66e6328c17918e09ade1be41b10b47eef91759db823609a6e92ccd37e7b95591215b4b17101546cb63cabdbff11e7786d3210c763c2efb80145b0765463f46807bc1612d8a99a8755b4fb54b26bc70323631570cbfc9aec314fed8f55f4263db481f3ab79349b8a94068b821e27f2c762a8e61a693f1b101b417726ea68bb218e66eb6c2c106d382e58c7e78f30167e96e5029388232956815d44797f19eb690bb07932721751a41ea49e9391561f0f2368742964d7d0d5144b3a6b3a7efce558865ec294b57e1f8009c4b0c9e27847bce12f763300c60ee428ebba9727921edd9958c2edfcb54c51d010b25779ec3226c352267ea47c460636ffcd8d62ce21b08eca8603d179b50c6d3b021f28934497f8cdc6a9734a2bb1771e99ffa270d499738f4794ae1b301d680f669eb4dc7b18d9db3987e7b6b2d24838aa84db0b0ef488f724249d2c1f2087c1eff68b69349909ae74a1f0ac010b0b002dfcd5550dca050873620c2faf1193a8983d4151bc08886a1b776f3bd4d18e451b6e8863847d95cce003f3ba8df3d933fb9743fe02a69e0f2d171d530c0404f0c9ca3d44646708255cbcad64e0ab3c1d822121735b2a87ffa17e29d18b37cc3a226af691bee67235d981802e536077c87c573f1db45e0509140e9d1f62dcadc364f01798e484f555f86abda2c29704072943e9c212f7d0929462bacfbd4b2cc2ee37508c0615490ef8f408e705f3de00d2206c7b7aa815edd6c58ac5c2e564063049c1f82fd9a9553a07c7be444c030f445fc701c33e5b594408b7ee3e7da152baac3d04f13a3f9026dbaac3576541b185998c67a7e8f594f7d39ae0e94bcd6dd0f7721f88f3ccfc5083409b058c92c3984b5be2b408801574322666288a34ae269e104e3f8cf555ec08271b1bd5b35a27019b13c4cf8d4f361063989c1934c07e5d9a70de5393c6e408e62a9e0b0225eaba0ad49149b3e9a50668b408f7156568e4446994cac90fbdcce2b0c35759709644eec8526f3f0d2d0351f4c640b319379a5dbca78ecda153ad7f2047b3ac1f3cd9d8985fc71e1433839462fd7fee7ba34f04c3ad35ef71207bfe3396c67bf28a876980441a7b906980a6cf29eb0ed23041d0b64ff51dc543d44ba34c5ee73c858792315c542d8a55887234c594d35e341a21c85e5d34d6a629528852c3e2e50b05008dbb44f39394898b161dc72a6dd5ef020817cb9ecf811d1e743ee521f9cdaab7c8ac822eeff5df003e19e302744b79fa3d7d4f3fc11a186c2b6e79c1de282249b28f259e0f8481457836d8e3770625fabfcf4cf394f8ead59d01d7042faf3c80a840fbabf65f7dc81350fa9d8fa3ddc517c5bf8c0caf98054a54a7b85da70a14e4c10f7103834a19d9694963d9427e004788b1b344f0142dc54b95ffc7dbd304eb0184386cd80d5a468bf27c8b2d846ae5e7eb2d9f5802b94586cba55dc40e00266430c55435b8ad150464c5e0878f059b5d7919bc1e94354b62d4c5acaf5f36cbbf49064846e19c1161aa2b88d10374db65dde4e0da061f14eac6b1a6eeb3aaee45cdf9404f21e29d84bc55ac26d86115f37173ecf0534a26bcd89b5862f4882fcd12b4e2f21b0cf59b1b8237645ef8684163f8971d5ea20402629cd2b55488619ae541b9ef401106e1146d9076af9e00baf4a503319acb542d660a49d99631e58486845bc66fa585e5046f81713130b63dfadce514364abc255a71e9362402ff8b73ea0af2b2af4591d245fd77583939b2e094957108108a1e47c8d314043c0d68aff62e50d578141942d6a089b566eccbff7a36083651c792bf7d6b5e10533b9dd64e83aef1e24021944c2a4b4379dd6960695c3f2fa0025564d243b611024615a2069d68d3bb6239650debe0773597002508f7e3e69358832f4784b99ad67699768ff32c3461216f8c11e13c28104903791967cd7c41cd36a4b307388135f19ecd68630b7ae5fc1a048897ca885fe1a313fe4de14f3a67050a6b6c213dd236ea401b67279f2edbb509a106a018e4d610a0015c8ce9b10a7b87bd88fb0fd8c9ce1b6edcdbf7779c13b6332758f7f62c0e8b8645ca84e0e4779cf11b47c90ecd8c1864669e35a8b4951a87f8088c14d46d0a8a61a736b676bc7d9ff39691168fbf4bdb2d76ed900d66b6f085583a9db7eaffa83f9a858ef53897a4e98fb0274f107a1151a3c36910002dd324ea9c0929542f1af2f16779c9d1b957164db25e0163ce2c6008cd3ffb1c39a60eac7812caeccd2262f5a01af84674eb246c05356587c51e0c917b6fda2378c57a38366fe34acd711c9484090e3dc4e62b02d2c506a86c1ea371e5059431684849245a634663d7e2916d5cb18c196be35620fef886ce8aba3a8c5bc68136668216d4236dbe9d357cea77e1412e1bdc069003127afb2436908c96cad7f64b02a435b32b9c5092666942b3a190f4ed623599f30646169d219b1c0ba3303311a99ec740991b359c29bbe4ced9da9611d6be4335418e597c0f460d0ebb688e1cd80eaca8e19d05485308b5403b4f01f2e14894e444c558842deac5d014cf2f363e28d18fd82b1fd19081634e7ce84332ad0a650624812ab776968d3f2eafa0c74ba55a82cdc105294b994ea55029604f9d6a7c7835ecb650a9be61cd184bf97a051c894295aaf0e96074f7733ae4bca8b11b6a3f95beb5f8f4aa40a3c356acb66fe38f8f9d50e411656f1dd8ca9bd8da8043ef486cf31ae1ce6fd092c0f5161e2446b84773a07902575ddcb2f58b0e509cc7ecdef102d52bc3d1cf0035c626ec9bfd17d50f653e22509aa0edb5ccb7382140f624d0b98481cffab2bdd8b38c37bed88c54d91fcd63607944d9762e303df4ccab23765ade1930abbbdb911c1c6fed43cb22a541a7e2bb7c27f30ef96e440548880278398c9fd11c2a24ad2301309fc4d15e2c699b67f9ae75a834c371ad7abc88adea2e2b3929326e34f46a72f8bc34a72eef20db44b0a37ad8f4f41396be483cfb7c91d80a1feec66d11b878a4e98b90e8846f30d43f384e1df10190947d3cf57c66c00f613cd303f20c6968a4ce8ad4edd159b8b544477da2117ccc0ce2c16686404491a1c0ce065e00e27c0e43424b32c3e4c2e128ab69b53b309f4e72cc4616bbd76da5a3f78ea99e0811e0447b4b228dcbfbea3ee47e78be9fa2d958845c8da27cba46b67f94d6e2763759ea54ac94c06ba27b16ab6d24be11583a8a9efa0a4a0343a2d3b307a713460791db00827c6874107153271c42f5e05938636b4814ac0c7d008a304ca11a7fb6108f4f37e3863fb69b3a90e39e2679fe6f009a49ffe19617ec4457675b27d11a2f1bc071cb0ad81a14bd3130164a89a063fb818f2fcf218a66f2a1390f7ed5d05f275391213076274bf8b273ae67f1b47f72675c4fc57ac04cde9b6fe06ba5b5651bbccd5628eda10f262dac0e8345fe143bbfc557c4c7c03e6ef4a8875481bc6587b4d99943b0b65ebc9c222a18e809e17ca7785f67122b2b28774057308bd1eaa8052acdc4a954a9d389d488dec20f29fc5459901087a507b015d3ba534aa69551a96ca675cccfe2f95e4714dcdd864b676e41939beaa826c5338dfd55999184a52f7b3d48d10f546b72657c61b660448077e92ee72646f3a33f2aaa5b07ca8b80ef8ee6f6cc630f58cd420c38aa721f83973d19130a01d65d796ea8844ee45482e96804b10ab2049a83065fa8545e9f60a0a120c494b6d0d9829595ab1b0d5b6355c8a534f3333a40a81c48c33439279a830684dc2ccebdd80381d57d35907515735d13c4025b9d807eadec8170fdcc12407a58a14102e6babb6da44527a4f83d6a2c60a2dcced62254b80fec2200b48e6b136edca9102bcb75782b72f750d100179facc9d89ff869bb4616876c5d02c12c0798050c821665d23a6094266a1310411072b90ed2a9c74dfad18d44db88fec2b81859c2698e6d8ff0ae08a2703494ee48a672862b2af3e2c5a9a478c8978aa5746fffee24e3b495edd86f49fbee27e190e32eaebfb9fa20914d5963448dfb0ed75e5954039cdfda3f2d777f14face466d2a52bda32238862e830e00726e46f9a922e09c47ed18c9ac42db1ac1917ce5000886f53da6a09b9b49212f050543da6e73814213f53207b3a8a2f827e67b9d841fe64f7878f2d4b84cde1a7aae5b83612605bec0b9a9a5d7b4c258a560337a7e2b6c66b64b3b8fd997cff4b7c585c3dd114554479171838b87ebc2a0c2abdb5fb99b9ec720c27198a26dffcee1cb3212b63763332ed2d8bd485333138a36e27570c7a0d4c247e1ea8ffe0f72696d00bff2c9a9799781a9c060c22753c1ced45d4970bbfcf93fb36771e5f67bc557c0aed8151015b64904189d5132361f84387f6b9fe3f11b8758e4cd19f0182fe352f91af0ecc955cf727d71e3d5c1233c742634be5a756b0db1361735a815640a8d34872914511d28660514a19fd226aa727dae1ccc36a4e632a337d123455d2242ddfc9ce4272c9aa5b26a16652558855558685665912c8445586b5665d5ac9555b0082b3959c9f6f3a0e891083a50fd3058cf94756b55b9eeb858fd8088183b337a020620fc80b6bf1617fb59462bfcae40120d66f48ffbd7db68794af9005306874c041f556de61a31133cd4a2edd5a2e369f2e70b283d92780b523cbb476ac28301c31e4dad30a706452369c70ca455bcee55cd9cab3709d969c8e4d4947f2a7d4f9c37918afdd3ee9abd2dea98268ba0a2d164a1432a4f1b1523937cedbd6ca26a414db7e857da819cab69d849d349676227350b9730d51bce649e55add7e7531e08f087fa59a39415f32e8a680e7e05aea4bbf55969d0dfb5d80e2b95e28668cc84a522c523ac81632a1680898168ef664a1150c7e3e3a07a5d1310e0a62a4d95fd781a67079794facc8377fab8a1057f5ed7e8033255388a595df426ac8f0d20ec1960340b98a3fe1fe4e9bdf999194f0320d82a82f93e52a689cddceac55b0464526d47c199cf1290802177eeff399323fe3660a507b8c110818635a162115bdb3f0750f1b04b74877b57b25b76902f05d6b44b13790b718b3e02839709b014341b14faa859c1e2a68073cf0ac772e24c385c2c846468d4e9ea9e78cd74028f3c5470c415004d1614069d8546eb56db0c549b5d1b8cd8e2bd3137c6cc82c8e748b4e942aa5f4dea2d93fb507601e7ba29605040da08a4bd2c7f7f1d99d230d18e375aa1bc8dc014029826f01f11f8971eab3d2b43c468ee67fbbe4634601348e813357ded76c3edd87da05c8400d63424d83dd190e0a085300cb9fd856812d2e33ba30f9d8ad06c40115ab22259cec048904f0b125988e574a0c6de06cda6595e864bc0c68836a3aa81bc4220c3c9d2bce17b64a9da6e3231b81e9e069f4741e304e6010bf4210c00d74adffdb43dc0c5e3a04d49a8000dc8a1d12db83a25423c27cdaf74920b57946ba092e931d1683ca2609fb5ffb641ec17a6a353dceaa78549a1d8058fc56e383b8068dddd4d20a6a351b3fe072288bf12566bcc86976e1e3519a9496e29a526a3bfeecf3c84821244600ec8d76f04f06329d62489948f20722494dec29a1bd4aeeeb34550d91717fc2014fb1804222e95f7892d7e3b36c1da9c3c95841460d0e0319bf9f8d8dc737724e8e241d848dcca03fda06f84135539f33928490aa7efcd4ba2a3d456c314c039a759c38c019c739cfdf17b31f4f3f901712a871cda2229ef2406d7ee6569bdabc1a7edf4f80e88c138e6b9f34f08afda467bd780ab8f00a95cb42dcb54c6993b08cfdc956d0dcc0f46e426773042c45fbc0f222a89aaeb9f729f360b2d8f5a7f00a15b31d2ecc6167c3bf2710032e2c486bd516bd6fdf66d8e175f8255dc29ebceef1e2f0b4f6f5d63b3ebcda02596be5260e31ded6a6ec2c1d288a74250b12fb90fd3fb137c779484eb7268af1afe045bc6a7f0c529eb505182786a2e44a7c608099513b5418364e9418bc87ddd4d7cf4c225c119b14210d9baa5bae677848136941be63a9c7021478a0a0d7b427745717262a922c60124a6ae045262b0d9570c96e46c62a04b9736c865621a1773ed5b43101a3c1ff00974630268524d185116e52248702969dfe20579c4a5f6300c6e4f74e247b7cd9e241815585a05653c10fda5465ab362c59ab9e26fd5e2a00b3a20abce27f41b2994894e69cf0931bc8cf5cb35f55bd189b211d6d5bcd9c7c1e57c0ed6968e36ee1b36ca4179abeac3d2a30c2407302592e472bdda290bb6074b39ac6ec0253e460ce9e3e939041ba1572c71a7487b699720efb0bae8b945a86327dea1dc8ce912735b71ea6aed1f18d5c50711e464fd17ac92ec738c969d87ae6a8a2ec46ca7961e000c855b34584e81e5463af3f4ecb6328e9617b08502839d8b92c080d87b6ce602b0994aacb5f5b11691dfaab00226dcba927132ae824429fd95c34ed013da54f4c6b42642db3e446ec946f8d1b12d2e6c00ea460a650f8e5a124fddbe54a415af7b77d66b01720f15d332546cd58bca89999359e95f31b28e14633cdd4305f5a565dd513363695726aeb59fb6d8c1b65dd94958df032db9f52f1a93fdc0bb28dae400298d27b398885ad9a51b5d629b4344508e05ae24a0b84ceeef2c90ad5550e59b00d3ddb9372dd2b8bce06cb056f62d283b3f9867faa0ccd56b1dc93bfa13f68d630201b9e2f7c740a2985b8b4fbc85caf6f99b00cef2f51b43685c5b4f5f7981b809f627f84b2cd5292de93a40be40f077505e0036c13d353e1dbb42bd042bf9985db89552f6644e724c6ee692c37375f4a24168d46ae71432a9eacd5f09a2d3b873af465e316c2341adf5a862a5da4a31133010971ab1efd5520fa7e1a0017e7cbcbb41a206436fcd3f789e3628d96c72dcb0246fffb65e3d8c8817538d403f84fee7871a6bc2f9b0f0d164f60e97d3da993654e2882ed35b76d7b1da6384d36b1f0a685ca9e69bb7d32e287aa6df96a7058fa846a1ad293c8a5db204bd76566ea358260a9d9627203e0c625701b75ff4f15422e2254c7da2fc6e94247a3bc7b05660f41dfe23a39468cb513bd83d4b31ef9da9d6c51f4d25d9a67b5f4cdb82f7493feb6bd10c540df3eb119304bdc5b16ddf6fd49d165cd2f945557ee93f4bd4231407a71d7c94f41d1bcf02dc371478b5ffa0b878a43834cf1d7fe148b783b4b0b06baac24b873af98d9747921fe0e944900f3e5060cb8b1ffd0d126aad74bd19601995f73012b0a8fd6c095190d21eb99f22e05cf396a6169af5faf5f503613a1191a90cde5a1d5eb31d74938b3cadb2c598d9d62098d9037af00319f3aaa5d724c8494c980def30a1b03b34030f13a818b87c3080f9366e98eeae094c475fb58cfdc7b5b05d3998e69ad369f9a3be1f9948431299d590d9802e50e52ea3f39074da5cabf27c87a473e7fc41cfc0a0658c059dc033b4f7c5147518325b1160b6013b710cd0195c0cb7024e082b882d812d409f538d9a826d24c5123c36528afbe35b5a4b7291caaf97886d60d7309bab5060bce3b18211c1414a2fc5343f4b24024ba545b1c05b15b72df389d98fc6e3b311002546ca9ff49bd534dc74ba3b07dc73187beeb8f34a49ef0a86c984e67ad53a0805bb89f9355c6cf0633ac7e44e4504dbf2a2be1a715eadbbf5db2511a28cb34d347095e23f85302c36f1ffa3dfa17e428ff49f6c6b9a9d2fe5eef7ce23697930e21939f58613128efce3fff14775c78d14e599901365df12e866e580d5966933d9042d4023870c66aa6439a71364f870889d1cedfa0fb435787d40a0b42c59ce70dc487de167feda34ebf795d6e28099f1caa5a4d46ac735a46f2a40e720386cdc64dde45c508fb06075867f3f443cb0a34e05a97b92805bd5d216e4cfbe9f877437d6c3cc83fbaa0f39a4b42776da9f2c1197c323a30d56e9efb0f4a651a90dc773a6e0b68a87b31ad347ce64e1adda2d54564de07955b8dac56bdbec7d2c00e69e01808699eb37e20bdff9374a433f72c6310bc906628a7f55a15281c79e92858610547bae175b4edfa5bec9e0e9496fd639dfa55a8bcdd3b31edb734e17b46b2b1019308cf11f7210c0d6a057ad2502c4bbf84979ecc3676a8796006e5e2226792613ad16c4198f8e490e7db63cd715775f470f32f4c3fdcee4d7e1483916048a489af240802fca246197076069886c3007e86ea18a684d60078939631148a483b29ff60bee085cdf1f837dd7607f54b3a7b6bcfd945b27231831f078f55167b83c05dc50532a0518c598d8c074f5407086a7a5296f292291555eae7f944fe816d4180db56f3c640cf901890735e81be51f6af43e1ea725089b1953049fc4860d970197b4a5968120aa5ede49b432e9e782e9220972fb13caba22a0efa66e4e2fb92a537fe4c1d20b5067b868377cc4a3ffb1a739e67aa4c5c7e5846e4610f4de5cc703b425813cb706ca16789115473b2b3f418a116f5b3e089accd8ed7bb80609378dff2866983f887d9662c8aed5cc359b4d53d5e0cdbd475453d02a1430bc71dce10926368498b136b7d3eed49ff1a9307ba1b34a552c17faca45fd6ce3e8245e023cd4d9e44c789be7895ee88ac38a196c0deb35b24f16e75ea46785f4137d3a49a69f83ece72f0458b1a322e86342bd6ec13328ad9854ed11d1189c6d3537c638db48047bdc6b49e3915b41d9e4b0f417c35bd6061acb3191cb07b96d9a3a4175ac2d367463b755cb69588194a45f10110e8a28d1dc1acb4590daa651df53ee7fd94c5f744684800e29160c8a15c712897d473fdf3868aea719c4334387d60da3624cb2b0000be220cc9b921af1a398d8087fe89c500ea4310d94dab04132dd11044e91180042aa98c8ef666c58bb8e30612301ef14424b00d0243a4a1b3af5755eb51c77731f0e0eeea3a29962777f4e14535f7a1a7db917883773b769d12a67dc7f5a082968d6ec8c3ccabf3e1e5e6c72d6d6de95383c643f3b03fe3f3d1b3fdff1e507f24fc53928f73984d3c64176a6fed794534cda6a2477a4953daad7a03b0bd388fccedd554708716091e22b625cfd358511f79c734047ffb3c5d85409301536617427d2c9fe284de94525a8e214ff0e09c9c42853ff38eebce5ca6d660c7f59e4282cfe507084b7f165a8ee96937fe139c13896c1f207bdd8e981d6b10ac191ee37fe26bccf5b9601894a24f1fcfdf851ab670bbf690540c3c483710897bfa16f1974de8c624bb650dbc443164de8e333d62afd001550a2edca75dc50c1095f7109dfac5612618b06422d39b5b37f71e8bf67af784f6154221b4b389857292214ce13c68888c42427a06559328018d7ba726ccb6a1ea3e8692bf164559d77423e78e12a311e5f6c2e2542fa9e45bf04c9670125964681b741791e881aeb16c2d4644d2c65c105ed2e4c5128d455841de464fd072134c7bdcc91beb0a9c9f1e48133712117d6b3ab02a63340c01fb14a47401a8a15a8aa0a286527a34eb9d52880296385114da50c5116994021a80176b023686943fa827325d177ab001383fe8d99801f278acd5f022ca48157fb6e4c2376c0bf7a7c91282cbcf62247029dd9d742cf3937193ab474e65d13fac34bd6fee7dd5322840f00e6b0f18e7f889807668aa78e68bfc2e776cd2e6ded48510d88128a9502fe2020f677164058214d30908634db3b07adeed1b0004c3d9ddeea1e05c98dae9a272f8450960ac2cd6600cd226d63744d2032aee2c8fed717ba313be245405d5b1bffce4bfb8d2998a0c3529c368933f0af1bc9a8abf6a9e350c3994621591e34383ceae760502151e5c4a6eed8e04f5c3a67d7ac7d2c1d703db137867a7ce908f795ae810ae7e55b79f289db12cfe5119853a5427e2156a07163c582505cfa024879ea0866e388cda5a0b2ed22229dc419cf0675bd3c8716b761f57c45c90665d6e5163a3da771ea99a0464337e649caf0a89b060dd866033f0e84370ce1ce307b0ffe811a7263b0da653264a6d87a8de247a1782e8dad6eafe8261beb112257dac393970d0a2a97c5dfab939e2c43f0776bdf6e43bad90ff7cd188f42571ce4f54e04321def431c1631bd77ec3e31061083179d750321ef0221468a6df6ad20e2587bfdf8c7ea2f13e82065c30e2131cd32a1f98773f0cd06f6d28e1f27e3b37b924115e86696066425402d2383dfbdf110274360cebcd6bd804e107dd54ba9dbdfce209637eba6c0ff0b47f68c48906baaaf1cea9efa819b0a5f4caa1eb2e4ab3fdfb6a5e945fb9b5bd852de0d305495c8a2eacfee5981d1a589891cab2865a3961811ed9331165c579e26c1ac2edf1464b63ee34fca824fc44659423482f2eb454293bfeb270dd5bd0054eb27e8fb6257879421174947ebae70426d9d4d7f16906216bff4ed5d69045f40c0ac059d35714c57a6847c4505c020499d7e99b2af48b60b4bbbbb7f68518fa631b01632392e079964ff95c59f2b52eff8478c073e9fd8ff542344172fb9c4a7338b2c4bf0a4543f6c0683f6e71a3bd1751c51069eb301f8a7be5f8c4c498344b9683e3f04b9145f285a180287fd1e9a7717b671cebb2dcd547f3c4e5c47151a6b2f9c540cdbbba991c0890027aa96270c82fcf901a66a33b76e131fdd9c37cbe35d8cd8a97f16a059f8cffd68851edc82661673d951f26150970d71f6bafc6dbf5134bddc4974d5055432435d494ce93f9783be480e7fcef2ed455eb2daf491489a54da84ed55227cf43c512062fa6b8d94bd41557f053c6b5c2c2cbc77c8850ed691f103019793d97ae2374c4230af2d19b80412c3cdbc19f3c1de91f254cce6fd881580d715352824949a91cc6264410a5145da6b738e0863b6d04838668b425b72e11382a207281acc75d0130ca7c04158e5df41bd42cc42f318a87033a9ce491f40926fba1c98b3ffebb564a10acb14ac6cf8ed0502b627fd4e5f3204a0f1678543dad9d70378ef98591dccb0a420b6d798c99d1acca4d7f77101533c5052858aee07c77d33582cd193401b15cc8aead73f90cf9863b2715fc292a134372f38906deb69bea2d588af36c13e2bd5594d18ed78c87fbb4f94f1af3e832d5342bb4eb584308dbbe412eef14921deb821acda3d9d7a03324118583b0b0dab45a0b1d214473c1684a9e5fcb2ce57701f4af09cce560b6499c94af4c614f8541ff50309081f575c4404a2444bd4b1881ff46a09b805f2b145821a227c10fe75dbd9db94112d70cb3e14d6c854d261499e18908477d99b7af1a55da726ef8951f9090cc5947c989c80a31a231840226cc0827f2996ab29aa22f59fc73ceea11eed2cf1101f8782b56c4d013b408c41eebcdd7ec81601a8d951d63fe2bbf59cda994004144cc23a984d28b8cef7cfed3c7751ce32d6ca48b95ce9f1196feaeacc19b915ab5f8b960c45ecf6dd0e58837b80263d78a6ab2aa00bef5cbc809e1fb4278a13894764d4a2dc8ac8459d259baff7e8403d01355b9d4f616afc9ce3813fe3ea9391a288e9752b8dfe92215118cae223dbfa6e935cd4ca6e836628eecd5ea9c24706fb082399becb88409d7c172ede8e5074e5cfa78ab9fa7c6707f7db6c69d8e7815573805ac21bd0a8743dd0f007bde6e7fab7f8a4685a7caba5f603dab0db13b4035344aa340e914091c185f500779c22efed705cb93202fe5cca5605e68d16ee750bfe8777c7ff098f9b778270ce008467fb7058d520c7a433afac95f739d1e307c110a7fdf19d1a0ba8ac9420edbebc9fdf4060e5d18125f57158d912f9a0d6e914642f044bb8299868be9de3df04d14430416d307041bf24b5072252f08fc8d0f45dd7010a7ae09f8f23f3e2affe39f3c44332914dbf1e8bea7d4a4d6dbcc792e3b220a17fdf39aedd750b71383aae5856c52a9018432cd15559a880d93e12903436b69cd1af00a7ed57e0f3f19522cb6f5636775fee4a1680a32632e1a750bfe9543275dbbfdbbae0568e42f448e030375c740af046ea408f7bcb82852ee77c21ad914edd38e729bd013ce1c3a4773f4b8af016ec37753ec253f9936d59eb28a900739e9a639425c8b011bb0ce0c9a0199a6845e014ebfdef6dcac4852c920f3a7f3b6a20aea23fa1d29aa53bcf9a69d475f79f08a115898badd78a224efc19183f792f60e75ac0ff7961ea7f6aba874719b555c6054ec381279be809147cef8839da7684fbc065a9f61607b20cad8ce14587608fec75d5e0b82b6fa131f4c861bf896ba900dc55c8a137985cc229d8f199a9be3a725197de1bcd1158f8b0fc4ec03b551ac902cf8ae3e4e25063abedc2a9cf863922e45586d9c3d6658d1b296e186b74e76eaf3a85dab982e870f266c23f7a3f22c2aabc49e3f3bf1fd33c2bebf10db76f3ecca3552d4b3e77a9720bffdeeed4928fe3e7cf8f9f7f9f90a46f17c2b391ce3e34b11fa1ceec4e781a62f1c3b093bf96b18e911ae083c2715d915c16d329375ab44f4d3778e6c24d0edf87daa1f20ca1becd01fceb7ac91fc09af4cbfc076f8e0ce7b42898c515031716e8a5d35670408b740dc29654f3446ee0bf197dde4a9303d6a78ba3b783eb8a0e87d10487a8ac379baaeaeb194f26636c4ea70ea37686aa376c1b0918b581682382b39b26082a329020c0c6635e1d2c0d7c80c057b400fa9b6dc86ad127b5277f26da39651a25096427f572ffa5bb1ff3f52d3016378d916ef935ea52a98453b53d9972d2051d5c04988cb08b42c3e649c1cd7d1c10f7f5e79efcb4f6b69ec02cf8a21fc7f8bfdc3c9910f96ea41442091056f5c5ac1f4951bc0b2ad2fc80565f14d262916a1c58d79087d55abf61b9ac9b0435965695f20995efbf450317d042859bf21b840e9e522a20035dca400b5abbc90c59064a8e688b54469affa062899d7ab67c68154ff8f406b38622c33312a1ffe0d77d99009db6240469d5164b7e65956dc22564e89fb88cf4cb1cd282a05f13f3523fa35b06ee879b6b898c4284f9144e862ce238eb9162ac26124c133dbf1a8fb22b97566d758f3e214299c6e2d7dd4a38d3c311f06bf2333d31b61be386dbcb3c03f3e524ad76e7bfb22be1bf56f19d77cb0e5a0d79f37bb460329ad4e66fcaf8bec80b973eb28c3d569cc518594c6bf7c2d272801bc72bf1d1208b02145313c64970a019cd27d999fca967c7aa7711e1c71f6530642cacaa06085982e919a06fb1af2bbc335b25262db643c595384a87c4ee2cef5e5b8d04fde0dd3262b347f63eec0ba4c00ffe75558a01bd1eafb40eaa8fb5e76f1bbb682bdcde41f581df5079b073aed5be5cec3166503ca682c4b29174111aab6c0b7e4a7ade59315cbce7a83881466f7adb76a1b2e8dc3755442287b9082e555b2e267595e56830990e4bf41a657d074b5918faa805ddbdb7108424eda1631d2dddf337f041fd65261170c3aea291e72c0c9583f4775ac233e2342ba893f5ba4315e03876a0b360f35da77545ddfc0eaf5ba9ddfb08b2f71b16aa6dd8f4185b73e8bb82946ccb7512e0202d4fc8380c065fea9da78907ba4abf92beef83987735d3a6b9b4f38f19c831c1a134c054aa57d350ec2e6a799d8e82144e386c898db017cb2caf7bbdbdf6015ed00ad80b8e665180cd50efa5b1932c8334d76284c63079a4082179bb40053628ef211be8a28ba003bb5955a0942c9eaabe127b1bcdb2b014aa313cc4a8d11ccba1025157789af29f744bd96b209265ca034df9aa6e1abf7fba4024413a9d8c0b4ab2a4d2c8b820254dca1a19094ef0a4ae9921c1049dd4fa191688404813ed0c0589540b70fd5981649e54741910a44c49572723c19924757d06085210d2d788a0a31aa1ebcf0520f3a4a3c9d0094cca7e438a4c82806633a5f50de215029823d82f71576712901f16e0f46c662ce85fb06cbf3f50f45a7d6957c58af170410102746f9fcc0df0d981dca0bbd75ac8c5f15ccdd578408d248729dc8540a6da6aff5d7d069b41c6dad7749e0f4ee013f5357021985782bab1374b46c692643ea77516b4debe46a7b4261f66534e663800d4d07a1d8be001733b2c00d6d7e33d619a8814aafd26c7a8155e5b668dcc1b9abcda37fac0007e43be17a7798b28ffeecd78f81fd4574fa153e633a97b2be3384f8d44402f3cc8d4784ceef1eb7b8ff771dc6818a088444e10ca150a00d779101abec79602c4f378241f7c544c6549121975afa583140340b87e2b5dab2049b9caa8c5a6416e73bc4fe519ce4a9788b685a39fcb124745cfbdf74cabc41f776c63a4b7ca2fc81ab7a1adc6e6d199792b8413ca339f4043fc9f1accb597b90d485440472a3ce035af456f0dd8e25514e48fb78f738486aaca74251147d6193b834fa46fcc48da746e5fcb50ae9bebb848b71243547094d694ad94b6de293e37488ccb1647a899ce6eec15fda3ad152d38a3ac7e137b80d8ca748580addd78d7d17b0f9849103e3fda63192e7b3f8b2c07bcd34c4f860a0418993ba1af811940750a94554b9840937a39adbb742ebe036618cf873439eee490f383b838279a6a9c3be0ec6d853916e3141e0256f414c5984e844ea9ae23eb1f05bef84de1adde0a3c0aa4388c2c10a0837436afd56201b497c9661e344110d019e5fe96d19f4163d78e36b532335f82197b3b14df347e0427013223c8164205551d9696a2e6d105b372f250235285a4227d9cc0136b1b30801ed4e0a94b4166de14ff59c5df4f5d13544496705566105a48477ab1a931962ebbd69fd2c1fda14cb523a817ee8d9002b447158f834de0caa7218ca841ba1f0d27891d431964e090e96f7cd195577bc571f3ca4c3b2996ec30e33fa707a6768115f41af0cb8106c2d899d75db120be8579e514199a1b40e57d67f58093cdfe070cc14cb6cb28703fd2492918b454e308b1637980307175e527160cc9c92752e1e3d712ec7218df11723b2646931f7c313e13c305d1caeec7bb86172071bcb55f3e805bc5987a9fd0edeea1029ac4f40d851de0c80f1cd8191d141980110fa1aa130a14ceb8d9c0754190a1de6536c6968e4f7a32833af9342cd0ea99180ebeceb01505d8facf61d69b005518a1f2fe771aa72b8b747d0d80d0088119707d43bc0fa33b6263c83f636e46580ffd1be6627b00f7ebca80aa4652fd1f5409005c6e913a52d7647307378fd74a4cbd51ddf7fff6106dc743765d01788bec067da8169b1d3136c45212380f8e1136e30093266899631331aed50c05517fe7104d03302fb24ad272ff2c769cc13cf6e800be182787d446c7768341d3ce03a77665c6ce87552eb7ea581b2c638fc66b4a22091e4aa384dd690f0d074e0431cec99ef74ac531f6e4b0052ce6e0ce05c13fcd6a91c7c96e2e0d803455814bebf234945f0220587b1053a0017a9406cb593a81a886befce4a8b4d26c79120420a26744e98e0c9c8ef7d63d158488177cff5e43d30abe5d1bc95cf21e9f8a4cbc1d634e93c58584055e429d7104de26f5c4b55265dd0b1f12bcbf8423c343026135aabb78814a96b0e0a4950ddefc9119124b74e05a89e3505ab612b4b8c02f100be90b41139c38333c7b4b213ab7e2e65853069dd9c7009231be949db8b69a462123a76a21294a7936118f8d3a735769f39e3bb7b159f787806d65b528c433be3ceb31c9a275e97363e21b96d3ac0d9d4d9f6483850f6712c5b43e994b9ea4276e7b4dcee822047f910075dd950d64af902d4716b621cfe82b6dffdde22453f3d4c075016c2c5916d0c43aa5d18cdf04284a7e2aa75da4cfbdedc09f61ab9d1dfe62429c2fe74e950d0b9038a9f7c39226c2f612c88fcb753bf43d33ec64011553e634e4e37d8ae7fb81165b069a608ce01a8ed5bc68d0798d5a24c38190492bf12759983b617e7b011e69f0164f1b1882e24ff2a28bfe34cf638452d56acdd4cb9461c9ed11e0ea400963c8157ba608be653b52499067def0b3d5d305546767258497463d5386f2632ab6b343a36c5f982cf9a2f561a67b759ec4662a7079ff6968f8755cb1fb7bcb09c437bdaa17a33c516e686e8e637f7143684fc1763facb72007876365d1107063141558c918f5481283a747e5e734b1a6271c09a6a139a703a0a982f3854981750caf3b428d2d0c45b240d79cd44162bb3addcb97ecce1c7c6af2c3c3ac8dc1f020970c84e54bd19f86a4dc3a63fd742795c11b47d7c61758c2f79fbf3734b54f408e32692b563c1156a832b310ee40f965d81b262272f12ac9b97914b706905f448499c041fb5c00131915088475f00fa42f3c1694ab52b996d6273a0d7e19cd8d97219cc579f04a9e0a2a5ac7968256228b21b4385f9e628104bec6accf83bbc87fbf1b30ba970fb815fe1eef2e3ccb11af72d54799d26926310e7765bc39419d30a7042fe04731e26d4667d2b95571a5e212cf12833a71b6c813ddba707a334965c31677004edf5c9c75a7f41bfc8469a0f806b2b8c5f359352c16960663a080550633405f81849351a14948c86ed62032bc370189aa0db417282c9426fff71801bce088297e387b163f949095fa9f818d07d8b4659e215246070514ed023a26e2705a300e524e07d8ca3b0870767acff7f7a8833a2d0804ee9bfc33be7cebdd75bed5e8d21817223ce77a20fcec8ca2f3b206ba49000a40a463aa30ca2375e6c1d104b41ee13b0a6d5462a6bbb80166d788b884c8de7bcb2da54c49a6ae07b8069d06afcae28124ee93bee7d80f157d0c750ffa00c52232d6bdf746623eb8fffaa75b0280566b95b7be94b374ebf3165f5a7a7347f0562ba62a576b4bcad4afd1956efdca4e566ca1e250f8e0ef6857ea17974112e2d6772cfdda80be8c7132f0ad11f077fe53b164e173cf2a1fa1f8f383fbf039d18fb89731eec727d66e099e853e08be159d260119b32f63f6a951f8f912f8dfce4b207c503c01fcf077c413b2fc003fdcf913c2ff62df738c846ed5df6181e0b3012593323a9215d50759b27adfad5ee7fb7fa40ef8f253b1409654c95781e28ffcfe07f87daf128bb0d7ff2a08d629ee70d07c4f143999d76483507c436497be649960af106ece15b2dbfd6a71e98b000287794fbbb760c84383d2faad865930e4a171698b355a08c1573df7f6c7c51ff72ff2e3933207a2927ff8756e3ffc3afcfd0fefbfe7ffbed7118b7822fd7838107d3461087c87f870903f57d6cf0ff0bf0745162debe787154721ff08748c0b65f508f75fc75a666d18f6bb9e5fe8b3a10f0d2721df1f1f43fdf277aebcdd8b3ff4ff138bec7020eebb3712f358423f54fcf9e18945b2d04e2ce28946621e8b631d8b63ccc14013861c447f8783be3edb647bd45763fca18fb394ad2b155aeea76d73b57ef56aadf5b9fe8e6eb126eceafeae21f24bb0d73584bb2558ee7f9e9dbaa0b9eca403951bc24f7df9c3955f2e2756516c512564045800dc39c6b8f3c702dc397f14c09d1c34bfc59ac9412cae7cc305898b932e4eee18ca2e4aae94978b19015c76e28285c70c61bdde7fafe588312d501913c4143e2f84fdefdecb77bdbc4f841cfbf1e1893f9cbc15b06216f187137f20f0bdf712f8de134fc8f2a37bef4fb0ffbd04bceebde71855fd40c0fef72374effd54f918fa443186b0ad8b8e6110219761b7641870994241114dac1c2dc5e4941ee03c6d6993e4268598f4c0d052182590302344125e78403a82e80a146a90f836d9acfc795c08f976de277d6f4511e15ff9ac14c2d1872515c2be97efe33019c3d1405e594158b7c5ce8543a8460ac220acfb1a004202eb3a334f71f4e75e3a54d106c914619a6652aeb018292aa8991295c4890566620a2422396a6e28686244871886646083256a082a2f1d5c38e812daedc7e1ea96122870082b3636538694c44083234b9c6141d41a3929c4e4032141d6a8e080e6b2130e62ee68802bffc565271cb6fc57c9affa41157f7a9ca4a008293ea049fa01ce1b282b44ac59628632b19e2492b0a1055a134545b10a38cd20e6298a0a569089c967958d33e168af946ecaf0f054a687220ef2b9ed63b6f25eaa7af4b5bf26ec97de7defe309d4615c32bb8e7d17759c0839a8bbe5ec96eba0e9ff7efe94dc85ab3e7ccccfdd3240171607b0269c9f839d79329ff1979dcd03a9b0551f00f920e64a3001a8e2089353181796b439c18b0b46dca02043449698971a8800b9582fc6e532485baecf6527a62d476c70b94c92a4cbe2b213530b688caada943525c27136391172d3e5f9f4484ecda67136155d2ab3618cab262c9a4d2e2b721827e540133cf26e77ec9ac023079a522e76bbbb8b63c718652d8970943589261c5d56f3de9b82e293c1f98ae278b7cf48ca619fc80e54a9544b7152e1d835dd39bfb3798c13423ed51867a68126948c714730d06cfa7e7e27e5b099cf89158131efe7734d0efb44906bbaf31d49c835a23bce231bfafc046b176c026d777e5377b9cc14a4ebaa5a97c4edce6fba521ce9957c59fe9436240ee3588ba3756993e2285da8979d985470e7fb91f0ce77222625f28634c31ad04a1458a2406116450b51b000c5134456a61c6162894d0a285f1718dec44009273f70c1e1852b96056fb8289620c1643bc2a88d1b229a00a146cb5214238ef8e00414cb248513982a6aa0ac143104118c241bb47c992384910d28f0152b484991a60729da509141044aa0492a220a8b0a6dbc5b1419241b908ce9a2849c5944451a3303261a720843a5635ae17a0b5c549aa3ca449f711ab484798aea418632489870374c9eb4f8c0a6065b9222281f1304d4707822c5c311433039a20a143031050f508e9ca9e289b7c4df1e1dc172fd499c14702102881f9882986ae28b8c3669567881490533332e8913c0ac80051eb8244d716596860830444ca89052c3157505217890e18d1134bc898383db82e488812229ce104c8879e2e560850986208e70811447d31685654a92336590a8a9012492c0d40c3098c1172664cacc27cab429d384144c7a788ae2099804156506b22f71aec0a0246ff8ae60a1a6464a0b5156f0e42be2c611344069f2c30d0931334d08e44049c185284e71c01c713508c006333c48f14414342d734889cb0c51ca1c19211953a3a0708517255b91154ea6f0222aa0054c941a96f8e1caec0997c5a3748001ca0b4a4889f2022d4a2697d1cb13922676f072a4548f9620b3c495219c90419b2727c2480e2eb08149152d8012050468d4c4133640a94972e6065913c14f2a00620ed1152f284144e702232c6072244c1423a331637ace0252f0a202941992102df1c4891f61e0b8d1e28219393a28b11138a809330312253c9ce95af0b7506c1b3e02063bd480488192992b576480a28217185c9166081da0c400c4d410513944e126081b50e0d1e40564a82c99c2091b0fcc0e4f69e2a4204aaa369d607001d410493001020e1328f64c2b8d1b1848b5293ad364be80820c599c88c10ba6a8c0c421f12a08a066082c5566e0431c18aa97de94db1a5c7aa3b7799b81013ae5466f9db8a2cd229c5f3a3d38f476a3701ae6c1f16ede6d75f37208697416d124fc6200b7fa99c728b94e9226a74bd5072e57ada207424277470f1e1cd4cf4d2c0e5a2d8fabb833ef0742e40de71565a16c27cfe936eda369702bcd611fadfe47fb681f6de5449d6e7f342aae6c1cd4ef6304eb4559285b329d3a2e7e34a7fc24acb45aa5615f958f96837e1f8d161e7d37908cdddd8d84ab747aaf6e20911c17d93e997efd0de49c36e084f27dce392755bab3abe1bb712494fcfcfcfc378e84f2651743e77d9deae3aacabffa137765369bcd66b3d96c369bcd66b3d96c369bcd66b3d96c369bcd66b3d96c369bcdbc67889bd503c1f7b8073fd0fbc0eebba3fed22ba294baa594ce6b411004ad70a2ecc4b12fad17fcbc592bf55b6db7dd01414444444444444444444444444444444444444444444444444444444444444444444444d4f1bca1d3b9ae7b8e7e673bce764d3889dbd583f2a4bc270fbd2787716c5acfd6add9755d9744c57a3bcbc9e9c99f5078360eea6fcfc639d8b3d96c6197836c9fb472b6f33e50a5b30a7758b4e0e971f1c207468c1f191a40d1647349c64f0c183e2f5c006003196e44a33628173d3c2d58d00dfea76b3437da09573aaa67c530ea9a0afc3c9608c388abddbcaedf8a337ce48b46b7bf25cd1acd78712dd4ed966ef7732a107a6e77636889868b7bd5843c9738478fd7e9f424f18ece3397bd9ac7e43575abdf0bc3e4b095cd6bead70aca0bc3af559afebefb568550bf5fd91cb6925a41f56b35b54ac3af559c9648e44aca61aed554bfe42a0ebfe48c5b52d2895c41075e1332904da4ccb379365bcba49439ece3a695b27e59ce727482dff7b209c3beef9748beca799deb86cad2e04a994c265ba1e092bd62e3d96e3fe550e639cdc9f2220ee2ff269db18629fd7771fde9533ae7db6822943fe70ce1c575faa4bebbbbbbbbbbbbbbd7ea2e29cd42c56f6a0614ece23ac7b9fb0d52b88a8f698ce092cd8bda4ca38962d370a4f8d686a18fcfbf1447cae6a86da409473f728e63d3b04a5382b35499ea50ad5525b5a93aa9b68a74549f1cd62935507fa7a444f32125254b4e9890aa8058aaed9b62d32935077d9267eb480ea22eee6c5bbf5e54340d549fbad59f001ac2b15352ea179686395203b51f815e42d08f6cfdf2b94cc1255b44fddf8086e16abab59c83b65f925bfc94e59c732e31333315f99b734e6fad09e54fa5ebadcb9e8d85b5b685b5d55a3bdd2dd881737a9e4e052dc7feb6b26c7deb51afd6ae2f39ebe274cd78a267ca2ad1b8462bc735a8279e2692ca9a4d9e1ce64d53f604bfe6ace96922f186bc216f68c91be29c187507ec4d4de6dc262fbb5c5191c3ea140deb94ac5a59bfeaad5b93fbd984614818d6e25c9ff45572ac221baba24d0ae58fd6fda1b0486db975f1a92f3d77ca1c566fb729ab3755975087104da8498c1928a95b0767aa4e6f536ff5566f1ecd23ea6e73769ead916112d3794c62c830552aeaadbe6158350365a6c6319383198b8481fa398e6b128662062e7d59a7dc3d70696d0672132e6d6e3967adb556a628393959dcb55ed75bfc5661f62d6b7620c6acb5abe398f2458c1934516ce4d45b9d3527ea2d883695dcb4bb7d4ebe8f9fcca0798a82b2c9f6492b67a7b5ded256ce766c3beff33e50a5337574bca50e832a5eb94096cea783e5f2af942eb3c270cbe5df59623159b0f0962c9ad62dfe293872a6e0f450cb6e4d5fc4347d3163abc923e94422c923e9445691461dd56c380ace0d48e3b83a70f01757b7f865835652d9ee73525a27ad95e3acedbce979ded2e36cd779def781aaa952794b95fc4009aaa4ce2a9c61e82d43955cad4019eeec8092458bd9a285b76cc1d3e3e285cff4f1f1963e3b56de110c41b27a5421c8dbf2862fe68b17def285d5f9e9d16375c6105cf93d578a6deb167f01a47c4c63fc6ca40947a1eba2ed1fb1b14ec8fb559398975f154db7389653c13e876df4d726af35097eb953c12161a019c36a5403fdedd62814dc6e6589eae401c639747b9c476eff947507cdb520a576fb7d8e4fdd641efe957e93a1df1cd6592255d5c3de4456f598778a9dcf61a7a2e5d319992eba928679cd9938877f93131b92436171a92d7eebb93299949ac33a330dd48fa629cac6e676706ee7522327bb81d073a7d8d9bad53fa38950fec8c9e66cd5072eb975f88be10b39870942b83973a6aebcfc3dd8a4ae144bf0cbdd92391660bbae4397538605682e8ccb4e57d06eedd61f1f20cf6e95326ed5ff99af9a73fecc6f7d5f44f5dd4780635fc4c781eaabea0b4d714804381676e2e8a3f3a3108d716cf5a3fda13f8203552a0e8900c77c88457838507d23b107df48fdf9a1faef55e28f8ff07f7ed857bd0d2ec4e7c871809c7385784f90523a4a1c466ebff74b8a2d26711f7e684123147c4c63ec9e2c6191133cc5bc67d50fa9c40343e8fd58bea00d2736f4095b1ae816f57e2bda282c42ded85cfaac1442c9862515f24d626998f7453af63d7da9e4350940e8bb88211ca5924b9f5669a32b2a1842e15230f45f649c708ea3af7269b79452f657dbb5ea8320accbfdeca603ab38f08b340c24d22f96ecd2405432d12eb266e4a45f478dc443dcd12dfa543239cc138bc8d827fe74b3de8692873d11fe6d1afd56a2df46f4251309401ce6638439670737a9c9b611e6fff4a84b96d07638cce540f4297d188ea53eaa72293b81d9c2f4e5525903f3e58e20058d60c072c756ba74cbd5a15dbb947e95afd155eb17ad4b68d75372a964ba544a291d200ab5ca84ae86dac9f2310d66daab6100b8b5d2967e8981e32adcb1b1a8683e54b963337d9173c7fed24d6d1bdb4c3f5dffd9dddde57adfc02def692d8b2ad11cc652523162bae5b54aadd241c01675e110ce1fe997eb4d54cce8b3b2d52ad7bf69b7db8cabf5b7451a48a322034db13e65a235dae4d49dbe2cd9b991241ced6c2c4cb89ed3dd29f5c07252ba74e9d2a5db77ee5df248e9d25b4dc207f4fdbaf43d31a45f76e57fccb2324a2955526597af511157aebbbbdb4f70796ef5596d6658c4094709256bd078f40b94cffd9adfa0bfe431d33039d440f2c54816242c2681e47b1e0b16316288e2000670e38690dc210d20593584f2613ecdfc2307a0517f55f4df4812d2e4371696df6212362d46cbee481b278d527983079425299364125a7229b56854fea4cd2d1e78c0e4301f212197abf5df6a128e96766557da10dd6cb7d34abe35a884ddad4125b4b7069590bb35a884f5d6a012d25b834a386f0d2a2137f2d4a012f655f530ee742107f1db39997dce39a79439589579823c3c2d845cfa0c19be1b73ce39d9f62834a7651442764a67c0b07262006a421ef342d1e5315598ee687d643c8d2a2bc7c22b57b9dfe11cc7713ee9f428a5a0ffa49482205be66e9d7eebfd5a72bb0e3de15231e42022b12a4405ce5a2d5775782aebc7bfbe15418855b148a85dde9a5f59f672914facfeb702395b5fa7fe647da7b22a2107e2ff6ea46039889f5308e5f30e337b57889c33c55104aa291204c1972a9d5518ae7454e00d99dbcad1e9f3729256a1fc722c117834e958c00e5aadb270b8682ecb011244d974da4d425151425109e55fa92c22c2412d41950a4c5ae520c2419273708f8e108fd7d10976202bb4b2db81c122f2442c57e9bc21eb24139144bc769144b64fea6a013dcb0746b843848d30ccf34417a5942b5b904d24121d514ad1e713e515219d6eb94a9faf955248b02da5dbba141e529a740849119a2c226d027eb95842b7dcdd722285b1333968b6fe959ea5cfc91384a7b35ca57442f98496ab74fa9c73b6583076e6cb25a825a825a825a825a82587918081fa51b89da4b642d3139a11b7df2372c5d4d7e9f4ac5ccaaac7fb5e2091edb367ca7593925a5e2c22529504022a505050500390403310f54ce8b4c0761868a93320ce13cc9eca396c0f23a0ca2954ce9e429ba543887a0d07a51ccb8434fc72310809ddea6f6ed15a82ebf33fae25b7a7633916b6a7637e16e7a8cf9283c4ebdf795ff89e8f8ff7f1d7035a17100b46c713840704baaedb59cbd5e9aed26972b558977bc96152d6b55c922dd5b8a394d5b84a3050bf13456e8c1a852e4abafd9d0a04ef4e5142cd8162e9ddb63a20f7ac5cc4a07680dfbf27adb5d64aa7ca556e3bef932010ebe70c233333db1932d449e96466fedf209cd2ddddddddddddb99b33c255eb0bfe1fbefdbc5072ecc3309c2b701c154a29e5b88ea0bbce52789df52667794e96b5f29b0245c8f2c0cd9d1aa11a48ff252a1bf2ff3eafab3d7b5e4a91e334f07274d9a907241b979d7890bade13e7f8dee573cfbd0fced13d77e31cfddc77cd8d3847cf73ef509cc33ef743a45b568fd2f5a71f15d75f0582b9fe3a2ab8feaba8eb1feeb0f84fb2ecab7efc9464e8fcf851b1fa11041323fc51a502183b3faaa27c58fca8a3e5fabf68f1e32a85ebcf8192e5e2ae9cb84a27f8f682e07b5eb3ea4f567f0f6bfe0edfd13cac21f2b660c11ad9e972bfc31ae594cbb9b9dcaf5863f370b9d7618dbec2e594b81c287a451ce49f8021c2917a453748d23888fbca92b71a07713f596ec441dc5b9643711047c375ee8effd3a3031c274a2a4ed6530bf951f16eeed6cbdce038238a125037b45ffa0b3360f03927cf39e79cf3a34f29a54577cee95f02fa86ce7997f8b45c75771b526aa5f874f9734ac94d5fd1ea644adddddd0d7f5326e7753686062cbe4d0f434add6381768a9d53a67b9f540991736e77b7a4ff49eed6e9be53e67062d0ae49e99c734e6bc3b09563826063ce6983836cdc763d03c906c1ed962ecee1d7e8b9d55fe44a3b655696d861d25a29a594721c154b68ae47759f735a3b6b926db6ecd2b28b24736bb24b77f70814607f259470c209376e78aa452ee220a031e128b74c91861c8173b46daea9f96034531c47a4615e935591c232258d249254e2ea949a73ced97d458874cb7f64a859eb039973d6073239ae3e107f0dcc7620406658b1ada49f612504f243033b6ab6dfa1a386c4062d1cd574ce2938311c27c20f204188e890323151373872381d0be8d7d8daa163ce39715ca6ff57f4d112c1871007eb78ee3f2e00dc029ac00326c771e2102e006c0059e3ca6f8d2080b1027cbd399146002c70593c7c81db9381e45b56109e96509117b296b9314ce7a494520ac26021e4310209fd9abfe38ad02fbe95bed71715750871930e9808476efac14384269c8341ce8aa4c1f25bbb56596573d0eaee96dc9a2f550d30c11d4d2027bb8e149983e607d4ef1ceaa5466aa4ee6ea47047b702c082706c27eda49764ad95561a3208e11502822b439e3216e89c94524ae9534ae911aeb4d65927a59452d7fc5aaa74086903782efd8fb24ce81e26b8aa1e54d500bff404cee018e943a3d572e9e0f183d1b46a626c4f5045fa25a5e496b7ae8fad968b35610944ac8f9d456108001ea48474525a699d3a84e69c13d63d77f3c651f5bfcaea31653213ba5b5c9e20b2c70eb7fa5df27dce1d0eea77c977be7307c707ae7c6e105cf9de922b194a27f3e0204dc82868b55c3a78fc702434ad9a18a71369f4199ce648a10b5ad7a528e388f3252b6edf6f05d210ce9f2f61b586424d8645a6428c3e28615e2d859aac8b8c498c3e8f8485b521994c8e2c4a8cbe0c8900f1d267f9a200b893d239bb5d563632fad55dd7d9ce72d0b2b4724288dce9b18656dc81fc231febe10e3600e34a9e0eb4e020f95205020f082d6ebf0e07c951487722c8412118559727b74051489116933ce9c9e8575fa65073b4b7d2dace6d3427a573ce39ddef27851c2682991b318453643941a33694522ababa95c4e1a0605348e9b3ec76d0e84a9d754e3a69adb5d686d5afcf77d65a9f16b9b5db0457deaea2e456f7a075ec135cf9ae72809c33a47e09f6d6d1c895d6c74e99924f7e7777777777777737fff04b293d89fb1dede3d3f7656074dde5125a808f0bace4845ee95cc9e9d76887fa35864d42d94a0e96705cc1b9be82b39a32720e5a73ce3976dddc4c6076523ae79c3389fb16a1817c1e09a709b31f2036b76a09ea03e690d9fd3ea72843a74f4e093f03347fa13f948ed030af010942a45f43cc4ca71c9f9f5f045ec3524ad900d92c1ddde2177258ab81f8b9f2df111888dfda6e7ebafc92c50e48da992f64981114cc4881c5091d90d4bc4026898a1a1610918233320879043558a1018946850a1756b374749b01891daa40c2cf3bbe9061b01dde08714354151764d081244f92d2bcc18285a8670d192068b0c2484d10311642ce0bb7355164718ec4f83901dc041827b429bc202561c6c89417d85aa8a28508222e120a8824815f8a27882764f921ff04fe399f278d15427e1b0cd4ef813710532e161461ab8ab763013c5c2cbeebc2db755d278ef543960e8b9b415c77aa589e3c4167c4b607f17ddddddd3df52a22e46e6f2d5ff5a177360fbdb3e9fc6ffc38ced2670f80778a6309d78a41c0db0fa7df4c41857477974147712bbfb955bafd16e99ce3fcc9713f3971fadde1963596607facdf71b7766f59b3f3cb737cf4db3eabea03fa75a460658df576b3dae3966ee5ce124b667777972ea940c2dddda54b6677777777e9920a24a0b84fdaecee4c5ddaeab482a005634085d23bf2f95b015e762a23c51ded1dc00cc2cb4e65d67454b5160c798a70e247656a2b984959a523a980a15b19a3a34e54c0eca84c9167431f27a83b7eed09d7822b5dc884322e3b5da17160c216979dae2c717a621bcec0841393316b84c832e524040e45977b1f32662ef71fe572b6275c30a914ab4f551f543b668ada39c686be4ab72af1b1dd79adabc9651e4fa18627d0ddbd4a18c09cc42c8d51e1f6739819dc7e0af238dd51c6c8018e1e67313fc8b9fed39b6cd79bd600c18549104884c972fd77b053981ed85666c50302c2eb6ec37577d7e0872a468c7618723374c58997322e44cb0b76a801081a192262eeeeeeee31b96e039cebdf7577f757103f14c900793e309f9412f6e53251a44220af3492539c184967a37e8d2ee576bfbc25b7a795f9ed32556eb775fc6bbffc21f8e597460de53c9da300346ecbf01dedb4197c4c0347b78492b8e7587d1cd49faf8c59e5b774e0100abf15faa879f9d5261e3e2e4042094272b8098acd6d4a46ad6c429f5823028fa590aae67f93357f8a43e80f71f5bb28b9454d70fb039ba6a8f4ec98252d75866c00008080022316000020140a87830191509a848134ca0714000d7292466a4e3c154823398cc3200862180842880062002104408248a1013a036aa556f216c4c9e53075ba782eb0d1d5d130ebb8a96f9de584576a48171f5536d9d1137a0ea5c3bad00986dab1d100646bcd9d751f7275324b90c9712ec4404b833799f33342dd33bec056ebdeb9fb4f0a4ef4b5b4d2dd82c6942cf346c9df98f750fdcde0d78992cf65e40ddfad351ba7928e23eab141abd2aaf21874a421f6e6047167334a9d56cc544dcc23b2f8f154ae7ff9bab2563c1f532fad781ffa5d9286dd43cb248e05981c627f411034fb6d959edfc742c0c6dd7a33f630859bb746485049349f5530ef4f91fc3daaa4bde1f8307a872099541e2177bc70f25c40cbf75da827d905eadd5bd817bb389246a74ce8f63c8664b1afb32f12c547bac8eca8eff51e3dbcbfa394af3ee98b7d0d925d524191a83bb1afa32cb0b07538ab08f4fe7a042186019401b8d91d4fbcac1ef43eeadc19c0320b9f90fbd09656e684b3cb1226c5692b671839a38a982cd01a51c4cdd34d50d2516df2a6c8964d67f88fe3500c7c5420ba0cea263da8579f96e2206320d5983db0c76264827d552182bc4284b462ea63d7321df62ee9658d49040f37537d3ed9716c6fec42192634e0eed962204a6197c8ae3ff88ae6d6e22ce4133127f016eb8338ac734cdd07770bde600b9ddf6d88f6e23127e57e9d838e643a83e9044048d76a403c2b5c214afd44ab15018e708309696b9b01ea309424d6b1a68979311f9bc265589b2fd631d2e06c0287c9d2456cd684f986aa7db8a98065e56e56b8318f1a586f4c5afe156eef89a62d7e8ef55664805aff02396c45ac1a189f8373e685f8c11d8095b43a5e37fe296e43a31eb4606251bfc2f2c81b7f559684baac937c11289d9b7a197ba85ad27d3b3784e0ea7a4b14f6e7e4ae3323273a66e1716cd53a1b7c4f105642770102d206d6a156fcbcb42ca82a8bce4eef09e7d2f841f2a570bb52fb1489bc40907acd74ef019642567c413fee446da9ac25804b782d16171116b2c20d568460b69284e080c9d8407355b540ba36e26fd767ab7f22a51594f27cb9e3736a4342b54f86adf201b23f6e1b52d1facf95d025d8d99e3af4f4d1270862d65153766619377d4683a246b3f844a8da89cc41095a3f1aa82a7374e8c9c7ecef8ff16c30326fb118309ddf2be4aaf09d0aa8bc98709d8e0237a3fa5ec9da45aa706e8bbda9daaf75a9ca798322e9c2fee628247801df20078fad8dd7ff2cc8324ad1bc8cd80acefe78684649f6ae8ed3fd50ef51e36804e2e57fbe6e926596226b623360d99d2a70faf36af2ff2f790b4a8149d9df2702945049ba81ca1d6df6af2aefeacb0c541a3bc6e11c4dd9c2fda2ed258a4112d7587b0fd36df0e260bd196b28443359b1c3dc294d29dffb4042cdbc04203b3b09e3132603982ea146d04e9451ddba2dc9658d3aae3790dccdb7544e91aa881eb1b44652ac128efeb135fb4ba59504c5b9429e2590528af58e972f3ee276b1d7cfc0a3edf9d01af4e42b8049048149838544425c99b6b0a75bd675cd22e84d496999b30d40f707c97ada4ccd59b369762e40889e9c897df57bef717f0ce94cbf2676e3ae5b0208359cfc51634b5d5c2cc9e91cd91d1fa05f1ac1b5d5700236eefc9b90c2f585be632a519174528a35f0b0c21a82f9b34286a3adb842e51444188336798e1eb748be75033ea3f0d096c32813b39eb67930122e5648d2f637b59e868ddbf8f9975a2c05aeef3191b568b2018feef6880e80045b3c5cad4a249dfd3b3a0d98c6587835f060408ddac0cd75fc81b321939a13effda04813d894ad25b660e2dc2528fb8d16fb0264fe83f6e308293a254459be18a737efc015b7aa1689cf824f53cf32199f2d4f358e3980ea6021c22d446fcf5f1d7ed538ea3155069235e9c5d9ab2f716cdac44b0ec6522fc0641f444c0596a6219bc553db1993b142d9ec076beac72e9175e993f239e8d69387b07627df4d905d40169444bc749328153225391f11dceb5946542a4953b01f1854ba9fa384fa56f121f2eafe71715e8f536e405a3ebd6f0b9c4fcef8c7570025849a41b8c1f9ce6320ac57f740a68d6c381f9af595f40812f3d39fce70a4d369a0221d586de6a2cc058b06b28579bf30610704b06a978ddbb76d5dfe27f88712aef86d820e68feb7643be98f03cbda4903a3b8b03061e14ae72f870310b174107803dc4388650f574d8304f7e2726834fc48dae4bac9c5b95b51c2e9f16e26f2ae5318c33dc328ea0f1af2534cde4885f11fbec4c330f374eece28b77f4c4c0f048d672919258fd8065a00af53940104455fabff732673ec878d3b114503948c9666add5b01113594a8f463b8c2787e47410054e0972778058650eda2f2219c20ced9db6dd452a90775d3c1e7e1d3c18146bb0e42606f49900ec9091a6274e4ff7f30efe8aa3acf210f0d88237814cfd8732956c952d9880d0a3676c88b2151234811993a66db99279a42043c52ff3a71d8aa04369c8f6611066350274315d64d8a9a89da45f7de58381e432056830441e7963947b9b93e8b7d71cd29aef303bcbf4f07519844d57ac45b184152718cba676f898b69a258428a8e61181dce6eda9bf6094c93e503d0d8927eccd0f789bab435d96f4b121ae0d7a33df84460b064108eb891e1512e530bc06da251548e241173878fdf39668c0c5fe85f088073f5c14fd0a8b9de2065bd9f1f4ddb1fe589aa64fdd441870e665209ebc5248922bd2d1e2650ce2f31dbe1e2121c7c7c2fc3ac27b63c06592dfee9d986771148de38b77841db863d89a15ad1a149b17031f8268fc04ac68258ac67736712c8c5a05fff0aefd137f69b48b13c5c059321f3c3d9b221262c0c43e21f392d1ec657f6c4bad8341cec76e9cfd22a2dfca3064449900c9925dee022c604aa9aec9060c78eb802d56140c6be131960ed2c9813ac83df08cf8f34dfc867900e149e838abc80d983d96309e87d07988a4bfee88ef6300e65a91d299e1827b85ebb44d24f958b03b7d7e4816c389109122df113f7a5efd93c13b798e7e2cb08e1f46e929ff25d55d248796b0a0d88f50195f979a8be08f0b87aba7e1c1c93c207050fed61ead15480803686376cb69bd81bf2d50707feb628cc432772688f23941e062ff953d72d5e6249813c6b63b85b6da58bda26f04190c77f7f54c7083f87bebb902ff10ca71439837cb7346524b1639dd0823781c413908a0df79fe41c0786886a65674c827bb7a215a5a5a62cf06662ccde91747c626df98656c89cbb6b6a76fb568fb6617c1aacf1ee48eb4827ef0580e92124b63cf666c28936e94ef94da913b39eb8dc4d5475c9e1f449d352717652043b95bd99e64eef79c80a0d78365a94935f9e280929bb1784fe19dd2e7b51dc8f237ea539958c3722be3f2716c8e82c3ba417401dc8d01086b0100dba81356c272523447b520e06db0efbb9c06c4bd0d3ee0590e0dcdaf7b439a77519c42c3257e289eb93cf15b950ef0b687d7129e8316917b64a895d0fdf908e17db611f1a793f31dc9c284b56f96fe13641135c734c7999b3337f70044eef404ecb4da9b3db7c3187c9d0091aec382c33ce7d426cdf57f9a0ab67826d311186fb6a90e969a4da7bc7205c4e2ca91aeffbc12d749698a353ff3c8fc68a86e271263ccf66f35c39b18e9170225e3c6ec21806893704950096f5ef01468cc993f144d57c0527bbcd62900d996614dcdb75a93209bf5cd9156d874418305e30bd2572b087c927fc49cbb6128a3419b10083c88712519542ed31946d609de71fd04aeb0d8e5916f7a2f20898b708b8170544461d811a306c162205558a11b4cb92f10c4858b422b065821c0935f2bc1394e8195ae813d3dd600854481d6d90138d72f2f815887919698f99ada7e1a818531c904b188d8bf4889fc183900c9189b45f8632088b013fcbeed75988e9f079f451458e441301ea5b5ece99733917e46702ae5be5ccf24380f48b9bf89bb0e62123dec8bd04a418139aafc2dc705701981c055fab1870dacf4ba5d23dc1601d3ff168e950acb961a1558a927c2d22b82ff797cc8f8458f5c437d3e43b43eef344f9f611a1042332146260712dddd7ba56e27175851906fb734759778da3633f100770b413440b8bc4a7311b03ec77b48d64fa4bb16b65eb866b77c3b1c85b9e7e465e4e89dc185c4fab5af6eb96b79c21d7617c94d93e543c1ca69725b1bd1f915fb2a917bfd2c11e88532ae24f87685f17e741310638ef48999fff6ffe2d0dd021f9e3b8b287ca17ca22f610379c11da88a221128169761c10268497674e28ac5a2f631e5b9d67d2582b99544c2c9c2d48963e84edc318fbba3d682fca2a1e5319a495d3dd7cdd5dd24a5185f9012a26b1b284b8c60b63584db33b493a456a04418050f1019fde1332367ff508f483985cea2bea4b14143460d144186edaf667c1ac45f247a2e91bc14dca476fdcc6ade5d1354fccc95d6d5520ac3558c02f283a9e4793be280f03d12062119cbd0969aac77eb03e21ca9f100a18b2a2a309fae4c04a412c6e301f9e5d663a334e24eb448915920e4f300a2b22e5b06d543a6f139375597d8d84595dcc9fbda415db6bf028e69f89f437dc4c39a3be9a046e016485299bf6a2c57c8c9f54013f75135af124403643e844c28547873926b3b375918e10445dda37d2d286f2bcce3252df6bfc0430275fbad46754aef68791a0d3e7f2343faa23ee845f6b65d2ddd02f425e37470b2f678221e9d9799fcfafc3d321456c002b80eecf40d357067f8ee446a79b335f246a5f053843fecf3922d9ed18696ab45345ea1b089a30a8958d9efb315960a2f9a932c5e60fd3defd6caf578d116f874de0e433a0f7652c1203e29d7eb2eed60269247c864b2f2487b7fe51c463b1bac682822276d3ffd94084d036f8f33038f009b5f6b44a4dd7a74b1b71fe13a0795b777935c8123ad086232220b4bf3727628f3891a4b9c2f33dfd983cf7942bd08a25197e05a9e0bef01855a78ed49ceecf87110f59f0bb602104a4430690237edc0479e482f21ea2d2c85124a2c347c8db4587d19c63dc9d83a1d43025e6b5d45d9668cc9f43e17b0706c56563f367ad2af59d1f4a28c22b96a34765d7aa1ce5d97be0700dc84c62e16cdd8722fabf491aec4fe9c5529f051981756b756e2c08366bb00a59205fa7f28a483a304dac4aa582058224c06c7ca96dcbf898ce2cab643c83902bc127d22b76ade95cb4f425a371b7f051e0245656391acf873d08a471c2627dbf6e55e89f85cd663d4ac71f8ab693aa00825bc06ecaf9e3a9b107f909cc4fb258229a28862fddb556829005ea0df3cc6f95f2646abd960a9dcce0651bfcb5fe6d16393ceb6012c7142eb58a823f2dfcb06f5bc48074fb81f840847394fcfd6057122f0b61662e50b0303615b0f8c88a425f5027c8b62abbc90d7e833868f94cc1fefa5280849e3a648599546ba049c4a2d6656cea69c79d2ff91e29bc7901104e5c501dfce287097fdbc4908560262776261ba7f418b397c7009a37da578dac676d10114fd138ff6ed07b5820a1fdcc807d10da9595ef8e73500d165afa6a0d6cce335f6d40a22a964e5ad12508dd501fe78a76532eaf10e1fa4dd493b73fb0f29763b7adb2dccb74d5a7d4cd20daa62399b29063e59c7a0679ef2c454b21c40a00efb09076419fc6ec2d949b049c74d243c18a6f53479362472f47d19652dce2904fac146b81f7c53251797b6b20f616fb678e0a2fe2de25e059c7034f1be9d1239b678b2d4f827b828b319b549c37e467b33fb260ea7299d2f969bdd2137614297eb86126bb8f001f162ae4602c8046ed9b03e27a282a0b5bd2bf42093867d96c03c88902763ee2ff8903100c294d1043a983c3d308f27fc201c55b1d46a203cf8432c56894bda802b322eeb22ace54fbd72514295821d2eb8703d410488d6093ac0dfe64ad802bf391be33eeae26747dad35313c6e9616103ac00fc809f5085458f7c2c819719052c477418ac57d91cbe015cd24a4f97f2e001e61acefdcfe0910a8cc553c062cf77df057627554c87d683cdf949af06afc91a1cd93ff4ead84c37c04a70637facc0e080be22b4363799404305a9338b71f07f1f2ae55b9e1382db9d64871d8afd80798888edf3995778393642c51bf8816cc47ea4014da3623bbf32c8c2f9e2879580e79cbd8359d3736d0a93e57dfe2428646031f6dbc1739e713e828ed4a2582fa14b495f445ad0c31d78261d88f6a0cdfb82c2de0d94dcefd2af9579aa997bb18d5bf2eebe3adb373dca05c4f842b346d1e085400dd20d2ae1c4010085052109c1a75abb40befbe800ca8e2f05dcece4c41a5df386722154aed44e1c77a4df5b8b0e8ebfa7dad47c1b7316a08fcc9cb7421a59e7a960f708dc347c39990e77ef8a9296f6cf6469ec2f11852bbf1d28e11cc6f9b85dea18319e35c712a80dd2152307f79baf41e5dfd2934544a059880a228b00ac27a8c896249fd523ab7f056b20be270aa5935fa1c75e5cc6b68a741965607e631559573c056bde25e37f48b00e79d7691ab29a46f84d5873a7871a3fdebc2af4ec31fe7f72642591cf82330724f03875ffec741e39a8ca110d4adace4cfb929730cb4cd2bfc15d96b7f560af62dde0565175a7db7124ecfe39e3bf5cd39ee14ffe281f257f06afa1495c0be71e1a2a768fe2a6a4e08830ccadc965460b44e0f3487761072b71faed4266762bd9134211cb529810467995c3a8d7b38500d541b1a987a7730920f1920e2363abbc1a85c47035b5ce963dc379a5ef74c46d1b7bf259b673a46b876b90daff7b594eb80bc52848cf7c12fd3608b7427b7b7153898a48614475940944b900495837a3a6851a4c28209a8529a223410355bab766cb70249524ba88e5645ebbe070d8d55168bccb0cb29ba1ee89d15b8733549a455303d24224ba2c1c63d3d7c30231e57c7a16d386c1bd5e36f7b6e9f5ddc000bdaa962768ea5112264ea2331c2763dbb331fbc1f58d71910a5ed7e036e74ec0da2452846f370dea1f60323cb2b02cb3e00e205b0cea04d77fb8f2c0f7f4891e2f412820427257535386fa3b9bef4a8ae49aeceaaf69592447e8d4d9b13ddaa1205c32dda2ebed8c57c439e94e45c97bd0b0eaac37e25993584cba2f37804c3ea26cdff23b1fb27d7b80e13863a05a92edcfa4ad34957d6a35952e11c788bb7817c059ee526ce5dfc2877cbde26ad7f87a2b0c88c204698d52020e95fbac0ce5be65937c38f63f7aa57a889edb1b4f87aae5abf7beba73d7ef1b561abc0c7f398269ff0c5fc6877dd4d9d49419248e94913ede5d3ac2baed901263e9226c19128c395ce75c2323be581f5aa555fd8da4a143ce68c73489aa99d46a6426f212211fb3f7eebbe29d1500cd1e6fed370c79b6caf93efc7804410f3bc75ef3d9d908d756ed3a656784930e59ed7c1e033f48c8224917c2a0c12ef33eadac2027a73c2af78b211072aed4abf6bfaf194e7cfb0bebc2b9854d51f0f2c4e065ccf35619b6bb8b04088a5ab7a0f26a6c1e36c3bc9737d22209e5e17261727e7e738fd9366dc76084696a245464f63b9676f2342f42f067a7c1a39a8cb12ec16ec4018e5c36ff87b63510927d0a22ebf25b59e01c9afc4694fb47e712ee33d348cf9944329599ad985ca072b85db0ca1d4ec2ff582937b52f6e507faa79c640a5372fa035c455c5db18af4de3ac8f0bfdc89e0adf6673e1c0922b9e53be64ae61d6c86719c41ec156fa8da4483805a331a3cf1870adea90f44bf1dd90ed17a84be3284ca32fb3197d09c32d04cabb2f9db64fb404430efc5d39faf35b9ac975a22fb74f135efad9af6954ff2f83e85c32b9c2f6b29330976116c8876ca5cb306968d48c389a2e2224d985b450bfe3cb508640a1256215aad8e9962186b9160838c1706cf91143b5a6682ba595f133820501e19581886991b55936d06f0b4afe11072d03cd7127a1ab92a75d38ee607352b8d6e30d888d4cc94f79a355521f866f059f4ff51791b785a5b4b0e0742c808113213b79a8e4a2b560c36d35f28ef7f70bfed2a8b1463209ada2c36db004489e88214e685fd18edd3c58dda9db1a24887b8cd38409bfe73be89da7fc0cb7e5b87de1d95ac5f362bd644fb8101f46d2a6d05505d9f50caa3aeb58fba675ba167488a9b8d8ecafdd7819d62c0d34edef4c939c0b0328a1f2f2aa11222abf9f5f85b11cae4e64a557b98e1f252e9cfbd1b8dadf2ac4831aea87b8de707c08f69f4eca6c262ba2d45e05e1bfc1615391ad1b1d1f34d2c2398d0033b12124b39ba96a40b653d9c4c7774994af8cb46786b935122df494b75f4b194a45bb1bbcad6e1d8544016336e274abdbcc9c9b4926334fb1fd8ec9df13ca8a7fde3909197e5d21145ef504f5c9a29469604190206f00d77c539c2dbac6c26d0b429ab16b5ebe41bc3ea6a56c0d4a8ff9c1742c1f3b505be7b61a86b7ce4def2ac939e589a42d7de47f17f226a72249ba0998ef75eb64dd0cd74f19767e3305b5a7d5c3e1d353731a08e045c061a64014dd1b5b5fab414cd8c5906e978c8667af8ee2595b927abf7055f831c5008445122e3d56df6d1a1cf0a8ef8d35d7a7526f891f32aa5b4b9a620662634e76dab338b7cfd0b8ec72d179a2537df1b119357f21d6d2cb4b0748837c17cc6009d9d01cb11d50cfc5a60cc702f588ccf8d6df667f6fd979511c879993c4a75420b6840d628caf9ca372d563690a94646579e0c5b8e9118edc680666259f5eaa0a13f5d00255be324281faa76c54e73edfe17d1bfefb40e5e18784f1a9cafc758f004da379a0c3b1747ce2d3b0e90e33494612f30f6159be257b158f86c68391c01d27b12eb19402d3bbea839f089a4dc7baadc08073bba5a11fa77c7e4f428bcbdd410f9dd331e783c7ac21643f6def65cf289844423887b0378450fdd5dedfa793f8daa9239729520219fd545cc03eee18219813dacf7e6ac8c0e1b6db0ef4c3013169e789f6f81cdb463b26e266931e225811b2de1d8bc4dd5558a926b3c937528bcf60854191503768647dccdceb0fc108e09335ea7a2781f984153417052bfc8f1ebf1498f59a828b96b03602584886d17747b253392a179c40d5f9ebb94f0e307c3bf2ebcd555c8e687dbd16fd44e51ecfe518d299cc8752e39a7e35742071d84ca44ef3978da9987a58e54facfe12214db7d62ae7c8598df8da86dde2fc934b6df311b30b03f97dad3f796ec3e4411ec9f7878182463d2791cb8d11bb95e63a5f8961731b7bedb7c429f2ebbb2b9f130f0eea007dcc170011529cb23ef7666aaeb8d52cbd5f8d50233b9782b3d6e8f9ebe0d698ae4625336d2219d13b836570d8bdd9eb17dfeb2e6e04f59c063164b3cd08ffe449bd489162f0aedef4c32bd754a5e02c804719f503c517ac736e20d80d46973ae175c43bfa8be5d4273c3f220177ebc4c42d361d1ce14c03af8db99b04c82d2aaafc48939d2b4853abc9964b4e739122d7ae11f6524de0a5a1091edd71b9d0511b54efd84e810402aeee4507ba99d736a7526549bc4ce7bfcd18efea4ee2f0c0eae650070dada7ce2dcd0548433d0f8ddc409fca79d7046458504cc770261d1b5dc5ed31fa2002ac0a47a4f1d240a318649e310d1be2d85aa87d8a162b25ed8372c08a382c641d3d73ab65b055af578d1862a173af2f89fc72b35ed2de9500ff10f242e7c4869935582b1f28a3a1393d6c9542c44b66191e5691e668d050303b8ec1a6fb02eec751fac163ee8920873cf2a50239dbaab679345f98e1a57a7c4121d2858fa634a64b01b417d4db7bb2923dcc6fc04ad1a4a7c7394b844240daae6e99407ee2dccf72e47fcfe9b0b4376b9c1f2f32b7f928a34aebbdc2be197e24974146fc86f148a482487c7ce31d7e5cf1c6f38ebc6a6c849df9b7fb7306d82db680b79fdbfd82e352deac044175ee246724b85876a3d7c68fffb342b2e265a443d9f345edfd593d36508110303c0f4d15c352cc13e26f288233fb057d87aa837419cedcb5c2f17cab754acbeacc326587b1946d3c30fb1c39bce08177dbeb7b83e0e9eb8d85893f2a10d4501ece892ba34b96ca41bfc0635c7585cf28ee2d47a503eefbccc8354c1ef84c1d2a62d7ca79be7d8771590df686de7b485ae355d4f75144905a12be611567f56cd34bb34dc3b1d3a28ba6995804e1171acf83d494f9af3c15c73d20ba0ccdc4c6faad2ae1c0822605577c82c1de41384ac581fad3b828f5ac322785bd5f7959e199ec31f5a8b27a8c30de18eb006f77e361857f3d7b1afd16b263347a4c9f4bb6633c0a2dae831f638627dbb94ae76c9b7479ec6da3a4a9fe283f4a2a7876ce4b2a78913055260c67ccc8ceab15522b896b3a3ad0ce3690e678ab3ca995df37e67957defb4e6c5f5f40bd63e3bf330018a6ee6693aa0df099b238c6526729e3c70a0bddf299e80ed19ba733e730c23c771448e4fc1354ba1c223926df9d6a06bedc7368a5e6d3b6e593b634a8182b9470ef54e934b93149c4f85277ea56584d8be090a004baf0490271ee465f9266e5abae7d1c94694cd6da2ded1ae1b42ec569267d6017efef78917698d289b06d9bbf1a74952c517bcc680068e9637b4960b30207d38e987d1756bbb10ecee1aa8f02718217c7780b0c8dc9e1eb3d0f0035507dde1368efe356320e562bb2a1961c37fecab16dd53479d0e48d6bd5d569fde6c4386d45ef73918811817e228125b647a80928e9529702f536bcb2c8739a91c5bede15c8d62e8a5f4a9bcc201bb34409fbca09efc9a736b51add8ae8d19dc623c448734bec3ea5bca99192dfcda9569464004dae50f4fd7bb7ddca984645f7b64b3e6b3a5c6b8d27ccd994a350ea92bbced803dd9bc0043bd1d1d12abf3e256bcd27ef63060a268777fa9b3bca24087ea11f40a29e93f738124a44831f480081c21a2b53d0388a8d096548d8a198a9d5d95c62b964893087bb21c6cf3449572fd186ab2bc941dfb4b0092cf32a27f43babaf618712d78511b98618023f251aa604d0112c3dcc8a510a4e9340368742370823cd78213976a2ad4d8e1a9d84b3d0dd342c31cb28ce76ce7f5d33e1c38f8b2261bce6a74106d7483d63c3f88fc0d80624f8b3942ac5ec72ba9428950c7482744057446eb45f1e1cd1b3e7a436d6ab68a080e8096820d8fd9cfe0ae6c871cbd57c144893fe2e892013ad957884a454cf1d9655036f38a5bb512431e8ed7c34c61c784f86577450b557cff6691508cd9794558b898823fe3737d56e517154ef2fc5ed95be58d10a6e745faef16022436aee6233fb7d60b68239459baf748009ecaac530ad5410f85577bad3227dfd502bb1ce008e961c8060c3cbc518349e0bc50fbed814c8c5e4b0b1c68bed0c31ea5966b00c7c9f38b0de022afc01568eb612d6b6a6a51fa7fc6512d312c5ab968b6f66b9dc09ea9026221e3d8612405e8471eaf3bd033c457725f541b29870386860031077221cbf5c13b48f7374ff4ef83be322ea49841630ed1cb1b055d68c44536d46706ce570167d09725683e209304ff1efe09414a5a5f5260c3e8f2d7db009906e3e727a9306a8d7d7a7e5660c20ce2fcd01a39d3bc8b0925c2146ff35e1cc6c233f38a53270fa91ac6624065c343919e058f387adf81caccc9552d030b0396d650e193a1f6e803e917c171defd6984aa4a8094f14093729f697a6a793492b0ea485ec70acc570d94276f78a7b2903572d3e9178c7347da900934aaea4e41774f4eddf6a9acec799f923c1bf5bd9b3a57bc1d64b63b62af9524c7f37f224163a881119a0e908f414ee36f90425f22e2d0e78560763b5b822b2d47da38e2bc163fcb40274e833019ba4ba7c7305b2c085f4b7d5609e15d7fbdefc7aef9a8f0a02b7699d603c3414a8bc11f9e8a4b28dc0a14005f389294d003ed9c2a3180077c08a8e06d281322b162a5ec1d79bd3f957a9112d54c6117f849ac49be8ed55f6590160e7437417cc27d94e198eb9b7ba807106fb195618929a03a11c2dfbd9d6ce27e19bf363c885b0d4ad473e382145a7ba3159072c9703d6d5498ea34fa14eda0d50a79fed5062d5acdeac0a2420ac44464e0ed9410af515cf46b924fe2368fe1ac53883502376de37e1e8acd4947f1d18a15f9b83a726cdc58c2f5873480b384d14d3c84aaad74341babe1dace7c2662c60dbd6e4e27b3e41484a346750805dc564fa4bdc35d269e5e05103901887111ac6e122ead54721299ac23ab2ea3ef654510e8489fc1eeb5767375641c1914992d21a94176d60b732f31827356c832d403b29b9206920d78face0638e4c3fbea721d45a59e86332069d447d8f1489bc15f55918ee837d837c16c9e65ee1cd6b5d4d794dc0697bff94ea3dc2791b1647886c9030392297c498954865c6cefbdd0788ea7555730ae826ec52564981ff0222b04a6d5f91a32fa01462e80a1e37d1a1bc237b64db9c4bbdcd2264fc61d67475023731e2f4aecb4e4d31527070c83b12f1355a7ab60eb35bad37879982994e211406035eed7cddf7fb3fa18625a94e6d9f066141e7a7589920cee1a4ea96a4685d5fc05707d03b6d6e96d799742ee10c75fb97cd48e2bd14879406656ad5322dfd993ee154cc289a653ccd4b80c25eb66c3252257c80b15e20ab651ae4d55cedf41af565a07b402b6cd825ae065765c9344f5878055a22210fcbe548918fb3bf6c8b7d99f61db9d3219aa8fbc949fbbd0f6ad2d2566e2002a5977c04ba49a62eb9542d9c1e615418275bbffd856f37b549223b6016fd82edb86a0db4d69bd461d289f16237b11a3f2415c8b5a4a8555b4b01990b352b0205411b65ef2058bbd498e864a8cf03a57aa45c15323f301db16adcbf70498438d9e1dbfb31d26500656585f8246957b7e1f2707bec7b28994aa4393f81c4d10e412bd8b69efc7f1369a1cbadc6297437c2ce4d70ef25c4ef6da26b2d6eb55e9bb3dda11b7152a038c0b1b4e12579e9247f83c8a8528d20b4e5322ae1d99bd5ad847081089bf97bdf46fc4d41b7e7f20232f48272b3de5809809fdbe7eca7098675552c5eff02f4aba9acba941c2f0092cacedd2e6725ec4808222621a70d2d86eca0e3244e1532572464dc61f91856be389b124c3e3fdeb3c4f85d27bd143e56be51ba988b9b109f1d06f13fdd28260550009bb170487ab053fa31c8e61a3db54c015f6dddbb43f529106b4ffbd454ba69632c0e73c217650983b31e3cdb50ff1737ad192cf526c521505a331e91dc550a05a79edbe16cbe347455e7ebdda65a6f14a6f6f6eb905c7aa21688ff6fd47681b78aad246f12d2834867ebba6805d2ebcf5813d82e555a565a704962f226b5c866652708738ac267cbe10c8c4df881419c815b28630c0951528e29fe608615170f854a33d41567257b114f89522907f6329da7755897378f04faaca548296abe7c7d509d0bd7c14137e62155dbcf8f260459c02d74ba98b8c908907e920e5135caef74b1ee93ca54df87756a68be4c8a8725a769ff8126f44934b24b6ebf3062e7549021716893fe6e535d6b67eec3083cc142cceaf01bea4e09ad7fa0b0056b5475ac2d4e7fd3193b2ad55174a1c6cee18407572890f93429ba23936795c0883db8709be653d28f0da03ba2ac9d343a99e43227d245ea49ddaeed787c33b30b1973751862f7aeb44ac75b6404725f21a2ee8e9b13f5176cdf9494527347e18354a98c83f8271db1c83ab03eaed965e6770815a9912b59ba20b3e4ed1c64baa28205859602761a2ddfe53857c35bb46a881c6baa18936e2c31145c44281b7c705ff417ea94bfe8ca448627257fed3207429a54de9be0eae64e864ade2c28738d12dfb8ee8ee4091cc9abeb7088c409e5aa932c299a010c67cd61c521a607fb6dc170750ed0683567c51894d10157b5e17390a34de85ddb9464dc831c7c8bbefb07a49bf8e4d55442a3a9e135463f5a66e41d659442c2bd2d369c0adf14649d90d27ee504dda10fa404372c6a07158fc1e60c1b0eae4da4640fb1a515a3bc3b024bff4e0e0df4b604ef5fcfd7e3f33590c202f90df1bccb690dc4d00335e525ee901292f07cd99af027f09486bf1d508e560bc373212d816098a8b691dabaddb86ecb2d0f380f5520fcbf1d97b2331f83befbc19454581a84d898808be30bdc7f654084900d1f9adfccf28f97073ac641d70760d5d9dc9cbe8fcbfdbc091c30301b0a06edf42c80a82c202f1b3d3b59ea3d3d1c20af6d52caa66deb8e0a5700275787eac313e346cf63df4eb91156bcad3ec4ed048a9fb3665a5cbd8441cd8573ad114c1b9c2d72b23e5550f0e2afab6c5ed9e330dda498082a0baaf6cceec1bd75520863c64f5b0e50a4e04757450f09621c958aad3b75ae767a2007849f99483344dbb63f3231e1b93939f7dc0dda6612e824095c87e263512a7dd4f7a4229b3dd493f75a72170d7587afa257364c25e1c27f3029ee6929728baca368e16e581b6c0b320a9c2b2dcd660fc99c16f6bd9ebcbf243a58562864fa5d0c9211101256c7afc6c036da2b1f26b897f3d3a7d96e5dd440b07fefb483cceceee3a5762debcee8e95ecb7467631f689bd2f1a8e9ae33ba3752faab98cc394d777d5313b7f4af0bf8361de743752c44db761a94b83e133236cffb4aa594d85ff2d27299e831d446fcab59a313adf57d11d30caa7ca96926fa91653c2a334f40683df55681dc32a3bd5d65f577668fd9aab861373ce75aafefd5157fc65ea35d10afcff77306373ddad5d768fb82ac9f8ee741a3dd5d4dce3aea870bac68d99800fb28ba5cce1179d3a19756584b154fef5e086edac9a7a9493c275da0f60eac58e6f2f72c1cda9635917c0a6307efbf178fda23d793ab70dac3ca025f42db233db457f00c18b1e0d7d111e05a3fbdce3caebba17e8a862c611348fd6f757d547ad736ddb1b10cdaa6e93c6e7ad79b3f95a71924e6773ace80b6695d8d4f9bb17cdc74d79ad68d9556e48ba944dfcdb7f5799786bd38e90487d69043fb3e0168af4899af8839eec01d4a8bfec7c0b6951c1e1f683adb94631c8db9420ff8963d6a7637d7c90b71187d9107914a42dc55affb84e92e20c3fec67a459d6c276abdc3549325b985a0e7f747c34522e5cb5cc169575e8ab1d2f6aceb5efe2addc269a97dff02c102efdc057744e3ec89ff5638cd10a69ccdfa9b0ab19c0d0015b7b6ff36b22b1b30cf9e13923f63a86a72c6ea736a06267395f93781c1f7e66ebd3ef8ece3ead7a94dde93a7a9866413235c808f28ad1be6c18a461434c0fe7c5f8d54a4036fa7ab426b92a1d7d9572115a9c0f1d81f68a81ac9cedcb325cd06245223f6d7db385eaa2e93e5a05d9940364d671cc736edcc6348b6b77a13e85ccefff4785518452836eeea8f4e995ee564e2ce64b7250da878897f9e696ececb440ea6969ff664834c4d82e922c3c8b7f655cd25be29ffb92b3ddd6bf394f72c7805ed958520493411a51068efe29dc4bd323ed7e44a9b1bdc791bcd44376b3e400ab5affc235c3151f49f4e09e691e1a0a0c7975b750a3f41ce0e2370ae3686ab520fbf6a50a3655b58d2ed3e35b7aec02a02d841c90263e08d0036a5ac419d9325310a6b53cdc00de1da6ae52873e6db704033168e94b81631e41a50166c1ee95e71d925fb33d500237b1006e3d3685e18bbc6e6ec485812590b4eb9725c1c690f853e81c352a5505aeddbe3bcb41d67129be061cd9d9036839f8c1c111b3bb15693ba448af104d669031e61483d8b6bb1eaea46245bb11babb936dcd721d64f1cd0cca0562634cf7e4dccdda217ce4c95a2f915145bc29b1c83a0115428c945bd1d087f4e08a1081a39766a60a9f860b093117d2a654c2cc5182880931ad5f136423342bc69db47ec04a15c4102b76678aea5a8a889ac4775596e68f7b1e906147183f15db84e984d95ebba136ff45340a45ebc3118c80c5ddca396c91b0112a41c3dbcae110b52850e9ca603dac4ca981adf496d035ab2023826dc88d2ae2069261803ff34da3478d4439ad6fc53cef98c77718198b4ce237e68d14d6e18ac42a1416ecf9d40d9698a1cb48ddb57a0fca2cff475d3841ade192fb9f27a507da9d90988af1c1f32341e32c0e2c05e7da06519bc160fd0a20b94693d889b00b3040741010ad39d0f74e074c48e1cccc3664547e34ff39caabdf79bf3c5277216e3d4ef07a8313510df07661e72334165af5cc26956aea36a76b0b15d380ba68d0daa9b8aa95ea218a64f0dfadf9e62a3710c8f8cf520475644e868c5afc99d5f533e8a7162bdff50afa1f56c5a978ee9bf4a0da4369ffe737efb4037fcb53324002b9b7073468f9c9352a7364d168a5afd6bf0194d097b4561fd2ff81574cc8676643f6c7e91f702c85f4a029dc304afdc62a4ec68e3dc2a12bc2f0593fa5d7db44b216b45877a4a2d0d7b11c48c6235f363c2ce0f21a11886c4344dedb6561415ed08eb4395c24d56ea68534f59c018552c069c706a1203a647504815b3c0ee945729b78f3d678868eeb77c4490ead99a3722455b7576e65961dcd23043c252ceee5e0169625ac5808053b112e20b0e2d15cf6b55213d8ab903a0d504e722ee5d3a1a7bc4d491bf1499cd5f5eb171a8146462898432b63c17fa648c8bae26bd13e8481c01b84d3032ec567573da398dfd491420f8ad89c476ea0b0e04bbe050f32078d8c06cae99b0d65e9c93ec2b7ea1fb8b767f5d312de7dd3d6bf3a8458d4fb380e3d4558f8a2d5b9948d529632863ff3e7dcc8ea28c1939b9f4509e471ee1a0a85a07e496a9ad43e4ded6785dc43d36471eaa085dd9feb2ff5194176d502af96e054d46e564702731b2a82f387a1f51553b0a64492ad26aef1fd07b22252877740331f026cb67c503b7edc53cb5b319b160328394cb53217de0c9ec378b70b7392fa5f48084384fbe415911a49a463cd17d0bc9c7e3f0fcb832e7a65cca4f8cf2c1cbbba7d2eadf0deaa157518127c1957a97a3f54d7cba1439945b81a403da2232763a3e6f44a10311bb2afca59c3ba3c6f8145271da22a0d84025f8777eeafc5f98e36fd0c1a62f8fdbe2b1ec3839af7470be84ead429c1aad7be9b3aa40a454727e1d8f36d098409bd2212d9b7b70ae258edbc9da595bd91f57e4a4f73211c574a81a79980d68ab354b7506c37c70515ccc647d0275d917a2c99ad264273e780868605563ac2520de5bbb7e83f998bdc82642f0fd0c81e8f6f8354fc30a526c77c9bc033f648c9f51b4ee8572b4c2a6059d3bea96c6ec8fbbef943e33a8b8393596a48dc2f3574f2b84fbc735d51cee8c7380fe0235b2fd9bcf18225096e7f391b43a705a61f1cd23cf8dc30c08119b5102c14a525fe835539e4c91c1694fb13f85532fcc4c26ba70f3681f2b0047c378aa29906bea029e33700b4f75d0c2d57dd0308131aed3958b3f1b04566685822947e31a1c0c521f20ff473f6b4b41acd5ce3e2ced0e4388e7c3a3fca49b227d24315034f851a23b19c2702d95ba574b7bfa6b6f7e2a387e43ab4b4b735b03d7061a0083047c9ccb51d4974c37b17568123343e9cb3f78038476a32ff1d2929e8ca57bf0679d57e359cf43bd4ebf22541bf8474e904465204a4af6723ab53df393ff0626ee240dedead2fc64f2346b7cbc8511de14e2dc7e9339944b98f82b710b9585133688588e63e88921d6825f04a00e07e931c4c610de5dc42a68c87e15c46847ae4e832dd4f487fe3eb0bba9d203ec6d70ee8c09a49d15b5b09b74481d924a09c55552e605d591fd530afc70ad80098b1f73be4e2c1ec2ac84378e6ae29fb011c686fbb683e669bf3313b375d74f4d9c334b2931ea89b79e69c1bcb1e59a3243ac99b6ce0e9f3bc7f51574b0a2336c0e1bd5b7c38133b641b2faea0a17972c613e308f60c60f1ebf037db9a3f87ae0349b43f463312d59442bc5b15832f457292a72a0095cda79dd13590c60b9747fe400beb1cb1422c12d1bc2c1ec30e9388ad220948074708568b47996c4a3f6fe18c168f35e1877bda4caf9f9ebb1ad6f39a93e8af9cc652202d35cdcc7f4d738252901661639ec3cc07c31b1cd417c03c6a9f1df933dbb68cb34da6e10cdc68480b7a23ef02bf4d1c60dfe14a5ec9a18c124c07723b7ccf1aa698d52e4dfd625a29310156dc5cc2c76e55a9973e6c029f5963294c858aec6fb8f7c1249a9b5fdcf3ff2b68a8b008491b5e2baefaec2c048eb339f733aa4c9aa8b0e9a229ff163699d4f2a5315f2f04d9cfa090190f6088201927f4b3283899d40e02889e829f3e0c4c25bf27b8b985d59d333da67a7b710bcb92bcdc08c43e228943bfb54a5d04244e0aaeb98fa126fa1eb8db2356e5050d40166bf558fc5a5dd7bad7a757cd56f88b821a0b6f2878031c032182f2b9d558398fca602a41e200c626071d70bffc45b9e6cca51010004304a0aa504edee7b1ba2af055c38b0f0ddf12b0f99748267a9aea415d6296af4c16d61bbe9f881583dd328fe9a3253c67f65d084bc41738010c6df26815464722a9518084daad44115ead00c7c7090ea552248678e015a06a748e560a2e3fb2ab8814a15b3c1cb680f322bbca546382183bad519e46045ba41a9b2a510f0c4d0a8099fe3908565c80f4a4327841cb084b6fa2f3892282ee7b5ba44f2475f486215b4d9aaaa172ecb026b630978c13139e90649175cce4bd01798aa80d591e4e2e39150e52f4dff2daa941a78f075a0ce5623ed3f79605a6ce5454f0033b334b117dfabdf554871061f59514ebee049abad06ecfd4e08aa715f29a83b44062349c7048abe1bffedae15c974f408b94b6ba7879e4e713fdfa182f13d0237ee2465ea7077ed903002d38d0763a50b3bbe3e1738f2551e619675b59d9be2df2590a96f71e6e30ab547fbc496fb8d41b1bf7f82249d436431240d9ea48e76e24de09f804e35bf7bd1e5a94cc4ad35c5c2c6ca074552671e69c1cdedb278b770047b3959dc29ca091699ecfa61594340fdef3690588a2a5c1ea5b81d982ef24cd1ce317adccd859914f2f935002e82325721b223c4a60ee04680f45b9de2391930e24ecdc21054f7363fdf43e7cf5d3fce74be188b9b656238015755dc7f46a522659e2e4a385981fcdef9d971cf197399e8e4c89f26c8ac364478e5c606a4cee478d8e6097654986acd1dc8ccc50b43fa7c1855de47e6a1c697e4e8beb5d17cb4a066f3e116b489df8d7c442a0b2b818727121fa7517d1509567737f3e5a486b7210c8c239d30be00fe17a09efb66297b1dc6eb620f6965096794cf0854a14f5797a56c08c6130fddfba155ff0fdfbced8ea119d66b9a6acbd3ece6415334daf99280998af37e74e21d4f92e5d84b9f98d2903880055f5fea2b285a6e915ac8c5dc7e52f0b249e5d6195fa8d029382b3dfba3f81ca7ed08ea9a7cae3282abbbb30901bbe26de459d51146316f763290652a8a5681b4199ffc1de357caef374d535ca3d3c7fdaa4ff149afc4deabdebcab051cc9edd56eeedfe975e8cf28c397648fb4bd5f1d6142888764fee33693fb8061a5c26bac75d72d4ee1c0d700dba6d13c9946e2be873bc9ca0976edf8b3576a90eb43b1ccf6483aadcfe3f33aaf5abcfed50cbf62402bdae47ae2fb6e83f070c89d0117bc9964a53badf25ce9e531a53e03ba09062584c50f8e74f4b9f3afd26838a0bafcb13cfcccc8cc0b6bad8fd90a12d6194c94c49836ee13b0953d28c43d581b6e546de9760c02363a0f191e257fc106e934a937f57acb0c87e7a78f55efcde2263f9ff41f9a0aa871f2d781eb396924c4b2e2923c4a779382c9316eac7cca31817f1e801ff423e6ea97654289732447c52202f2a646129cf9c436d8da74cc3ba3ff4dadcc5a466fe71d36736091e943be5c025c8444ee496b9d015186184960b2da4529001b2290b86b3a039fb18dfbefff9e8472709aceeb1130c09baf48be881c984c1c1435f0ed9e63085804947fb07768b8ef55a66bac4df6d28940f037cf63f5248582268ed164c0976bfc12295cf13e2ad10f186279551308fea900c8760e7f90877a0a170fea09402e3f66aa37abe8522f54038da520499ad0f59128504bffe5030d4049973cdcd07a127ae5523d6ff0400a220a5f4712b75234c2f5ad40a7f3598e61f19453bbb8f795a2636869b2d7e4c03254b8260879e4431299970f73d9069fd0f86b541809c78382a9d462fd237e57d175631caed1b4b479b4409a74dc5148ad57efad8f052b867ffb347a96bd694bb8ebc03933a5aefa8f1758b65c24c4822de363776e268a6eb38bb2bf40a1edd459760669c26a231886b8df9dca3c11cc2f1c16830da04c16c00eaad2b24f33adda369fb911ad352d597285da1a5e5aa20695d9fdb6d3a2b72852e1e823210e28ad60d6aa313aeb9ed5531d20491868820b3395aba57ad3d676311a1787fa6f5c1681229bf4c881e85f213274bf8633146df8a734abbec719215f36f48a800f7776ba10577ddb0a8b7931d89124de071f6bb51e541a5ee8101bf2bcf8b3efac10ca2a18c5f2458dcf0bafd43d1406e488ccac7b3d44a0bc7c771b45892b408536b881c8e85db5ab2b1377dcbc9a1a87beb8ceee2df332c2f299c1cd15d84481cde222ed80ec0ac19e938cc178ed6e2b6a31b11e832900e7b378f6ae5308a92d9bd4e4ba44ff5759d3b66edf4af3836bacc4f7f40ed7a4a1c6eaa365e8c855af0b50112e7b84e22964f94bbe48955899ae2f076dde57ab5f24364fd44b003ea661909756d4e6e4f5bae9f4ff50e3e54f198a11e57b50039f9913b16e65132f69cd87731eb897a5df46d3c0e99ba431c7374f1888bbf723e4242471d9890cf7bf8b8af051ee9243949f9cdd70c07651513c4d5109c87019a226e3b71a5f039aeb27633195b3f7ceffbb6a0e9a08f9105d9ce32728fe9c3cb440b6e275bad11ef5e653eacf376774a576affeea98e7f320bdfb149d1b7c0d56f436f3c0ef280c53e1e9e803a1d9939956397d5cea3f73f716075bea52fe50780dfc237baba01d050b2e6eb0e9d896b4e90e06d2317bbe08a7cafc4a39daaaa421b1df71ed9a74db41aae9c6af0c6c1ee12ea63887aa60defa82c591e991c61cdc8753d47dd09287de0d1df24b9e3960bd70bc3c36155229c9494a2fa338d489d32a34e02a9a2d679b72097678f9d0c9c6ae53abf87657eedd0fe9a956d27594e0c8fec1fda4b77bd701db54bcce94db13b7b2bd77fbc516e06d305fc73c2bc5b691676bb64ee757043593d06e9ad8a907f88cc96bd858ec0a8d49faec91277afafbaca2e35e6067052e93dabc5bf2f36f74748970bba5cc63de808cf04d7360dc382f79742ae5ad81cab9970394f9ce7525dc3dbd54a603f47821459215b1331d4e7227d775287f9baeb6ee79d6b1da4663705fd0eb156ea6d9b15596899a8f19d5c8047d15368b7bc674de40a6193b3daeef44d0821c9e2330e961d046b9121a1b040de22d8aae2ffa247028858f9022dc9614e419845e961c880d632ff5a02cae336014503012da8bead62e574809cda7001c9cde1e69e31403f80b2d7be052a2983d87724dfcc3e477e83259e275c4ba1f07d498c6b0601bf36559df1bd7188543fd8c1917afad596c1f20c9a0c7bf4b5c64234d90cf223f9d08602012c3c1c64399a42498bd5c91c58e48fd760051ce14ee9f5fd2252600fbce4f3e0803c00303a0fca21ac74d1fd46de1ad442a3883cbffb2d272b5e5fb1e6073788f1f9b1bcb93655a2a05b1da50ab9a26f1904a0d0c84e85c5ae9dd66ad741ff4d183cdb5eb8fabd686f2db866c1b404a85ddd50444ac33dbb944418a29564da730558a1769be7b8d2f967b55c42b08ba02eed8249ee5743b7f41f49cead0d8c94ea15d8b2d7ec994933f602e254db65927ef6e8d63a3eaffcd603db8ad3c7b32a8d7128094f750a07a222ae1d3398b9f6021385dafeae8033af5dd20cb0ded8bc16d16068546291cc01dbec1a4125f2d9f0baf6b22341d039dc5d83455985f25489b2b4d7176e85a1bce6fd195e10981294b5066cb39c2944955e8fb49ca003b6cc47946e77e6f60e959f9dc34802313e5b6b0bbddc4158e43a81764300d30f047aa9d5dfeaf8111e6e30e896870f854a486be804e4b420807c44962ac480a7e83add53b1c2b940a47ab3c6fd88481fabc580d68d52c056bf1daf20419dc066b5a0706fafc0c99e0adede24ad0b9b587781428bd34b43b648e7a9bda3c7143af77103478e447004e139147d724b09e6316c3fed4e847a4e3b2ecd5d768f9aceeeb54708900fcbc9493dcc9936a5dc053fd044478376737a08acbacf141177320c10340f67b42eab2e6185b4bb04935c970be2d5a41d308e3fbb4028a78703327307447073be2d237e6c42e79dbd3611d00a288d23606034db9b76986459ca179f079d55b7467fd56e0896d6edeb6dfa4d49f13cc97cd34b63350b395e085b44e9f534f6d882695fbcdec75917c21dc5ee616255ca1b89d58954338c8de937781e0c7d5448e6f5a311b564f323825b3257f0567cfbb2acb98aa85812041aff47fe7bd4910e6f9b688ffc3676fe5614e1c1e08dbe091a4f83703706f14e8bf9786b71a521a16811b33aa78f20dbf8079ef71eae7b57396e25d80b0fd1ff09b6da83ed69dae704aa9855475497e2b40709452c50a2580ec27797f35f7d947812f551925418f6186812047bf0ef012dc505c4c56336c4e44db1c4a9a34e14eca06ecb131951fd700820f838e3adcb9456ec426f15c07a074f32ca56e39ee3d3591d76301fb6c6b29822fd6f3c01f60f55a5ec7c8becf66d2e7947d73cc167275f744ae055df1409aa33f79a3346c67559ad131dae6dcecff332f5fa1808682c22a4a635de0dc1516e256011e678f48f8fd460ef2713428f52a7fb145fd111eacb0ce0c0da3348df8f4f862e765e46d7702711ef4a4f7459c4612dccb9ae93e890330673a1e87ce789b8ba933d42b5577b90b9ef3cb32931c54aba3b4ae7aac45bfabcd4deea89e9c8bdb573b14881839e13643dfdfcd931a904647de520d2231b27dc54b6b341b2e1c79621094b4b964d44ad6993fdace4a7363eea1948a0f44a4f8166938d3db81bf4d1165900916e4ab7b8f918ba70f52d6c0c340890c194c870539e6f5a32b4872ac34ab354624cead4dda695f40fea2d931831ca4eee969d2e1b9e580379a6274fe517a3714da01ba642bf40d23563d0f4ccdfebcccb9a65c262bfe778d8fd88b5d3f221dd40a725feb36ec8b57f3ca92f4647d35ee74e22de8dc5d06a97a7c7c62228df1d1f41ea9e76e6b4d89a09226d261248fb9fae2cbaef719bc65509e7963a3b2ddd1b9893411aeb258c18e3f4d41d1e8270e23b243df059cc768f9c6ed3b82ae186d45219d013004610079ac99b6562acfad44d18b4f52a0b793f3d8bc505d736a418f1ec5e30b5d290f6044e62bd46c4497b0df7c4fbe354faad4d1b32f6980d45f12993246d7aa44b2632f1a4675e22bac209fd62814ea2345c27ccfd3eeb4e29fd30a7a701a98f64216343d369863212eb4363a98498d4c97d01c867c8fd4a2a1eae15a1054265216f9f89d3491b48b925140764e7028a18e26a5c4090b24a07adf8df33112e31a78274c0f56c902e491953524573f29b067e58011ae4a369d0a80f0497eb645fd80e747c0d73c5d6ff2c6a0ff8822920e6a8208599e02f666c9c3fa124ca2563e000d58edf24217befbda59452ca946495065c06e506f657b0d65a0c65c55030ec457360b5e25f9bba9ff29ee5b558ad489dd27e090d3179d20e6574773742838bfbb514a6891700044929ba2007e407028e280e2fe7e25c3e2fc3bb389729378cb89f6bd1c204c63674034829a53e5a524ab98d90da21c618638c9d9b368d4bd45a8d86780e6cd204071e9e34597aa5b0dcaf972c6132c4c885fbac0d7a36e5b54618e22e3fd0f21c5865adb34ee9a325a5942d1faea365f02ea53a1f2d10a057800354295f051dbd937d20c04180383d42b02d1044a81148f654ca763c8ca42e86b56c954d8726525dc81fa2703eb4773353a7b469bbbb3bf4a8378d9bc786535a9aa51494d2999259b97cddc049c13962db72514e38f17ed24f9aa8a5f45157c1d24f7a4a5369a44ea2d39d7421ea7ed274bf66125dcda4993449a2ae44a92bb94e3c5ae2d21495442157f22e3e467cc5a038e45370f0dc045e94210c727c9764c0223b6390418e41f9abc99f2bb912539463ec70bfd924c79f4d8c5c0a0eb087e7f9643219813dac9108c09ca6017af1b930e16602717498b05646600f7b2473a8b2565aa9bcf1b8a0935249e5f4d19a924a4a29b5967eb4cf8292e12e37cb7c1b62e2724ed9ad7ea1c0021326433d14d4d4f4b5a0ed970951a79f401b7f4c44d453fa9553f8917b29cba32c51c8ed55beaa9427b007a594ce18638c3176d090934fd97c1f641861077514006d22c526409cf83aaadc8ff553ba377d52055704abc85fe78fc5823893096bb509c17e397d49ae1ce2a57d07d4f3583e80f464c9e21d5549525acae2395f7b91636769283407cafd1a0b1625da5d5eb854524aa59452ba7764a2944e3a870089363923c896625625abe23958b2a4ac4a16024f0030b85f4625a39221c56c698b774b10059c52ce1b8fce3a67f5583b84ceb3569e39dd638c5718a55644600f974c521a49e213b404a909d34b6888099d9f3702d29c736a2c2225400bc5b502c2d752480c9153a90f2cb491f62190a863244626a4d4e70023dd7c4ba711262626a01764ffd96f8489493ec46907741f0a596744600f9746968c2860a0935249e59c73cecd5aec356cbe95944a39df4a2a27b5f2b169316abbafeb9cb56659b5c18d17db9b0b8249f9a273524a297dbd86b8e06a6031d56dc88bd9ddd3a77c49306e28a5934e29a5cc52199030ee855eb76f496eed6cf48fac2cdb865649397efdefc67b45d4be225a1979ceb792c14a871591bb3b248a2c182277652f4df98331ca3a18a2682302cc2b4bfcc1a3f61551b4f7a63618a21c5d58d7410f84ac88a2cef6764594631320d3d7226cb7dd07142b5e4d0ce566f7ef5d25d12c986ee69c74521f2de8d528e27eada3ae690775788eecab05e4c60df8ce015977b1d4a3d05568ab0fa137a59c73ce99794081e2ce778e84e830ba409bd0c6b12c2b741a7cc01e934a4a279dd247cb8701f207910c106d84104224165201e04ebf007548dc4a20ce8d231fb0c7dc86e60e4d16b6c750eb6b4e31438dc310582c88e359bee417f6a6de83fed0e6c777ab7c492d3548ada0e7b17c00e9c9e21d5549525ad2925590f9f3bc55119e33e146d0432ea76713bdce0ee0f6804464f801803a40667d2f5a1b530ec85d9448d45a0b7bf85075e2db565393815c715dba6043424a79838d8a76d8304c29e79c736e2d8872ced930787737a538d88a0670e3710f4d883a27b874539915b178ea86401b87ad2316f42c0b26653924b7c42924794db34e8190c10e4d4ae99c73ce2dab107a18b598c56d60cea969b469523ae79cd35f99ca6e9f4f70313ce1017bb8e7b17c00e911cae21d5549525a125ae26448484a29251625f2e72991a111466458008f00508701d026beb70088838beed74a3c25413c600fcff2c995a1591f565aabac92fa68514a69aa88522b21004770afd65abf521eeea574c0b00df6702eb474ad9556eab8b3581cdddcf0f83182093a2cdd5049c292458bd4c173a4bcc0480729a5bcc93728a5fbdd3411770316595049e9a453de78524a691fd6409bf9a99c020b3239059f2c25a494b6ffc43f33d0904a4a279d128b49b9155b2d5d76993d279db46fbcee6ed8ee274ce97f2d1f9e1367f794b3fb3c723cfd5a9431ce1ab496a528abc82a58a848aad2425905c9724f67e87e4da5a934522f49211243f849f9ea00a4944e3a7db466ad78042028f0b621a9c4ec39e594eddedddd5028a4325af1a0e7bf7d2be4f6b1f22555703b2c4bfcf292ddfbc21e313fa949b5065183a8543ce7ab32a8473508da44a4eae872bf5a6b559a73ea404a29a59419b2503a686702f6942fc039679db5e668bfc9879fdaba865ef6acccbe8621c4c99e35047b37555c96f360719ccf27bcbdf3ecdd1e51c71f8a70ca157b10c77f470ff7abf1b8aee65de76687d195b2bee72d17cb24561d63e9d0e1698e45a1d59861b4e91104dac81d3cdc772f2249f92b115cc823c229530c04e2c80f5293e58b18f7b33ba0cee60900ea8039dedee7792e63a0ce667ba75f8339f4e5a360ce7c9a944c4c4739dd8610976788920cc337aa86580c738d192e05bdc04ed9c04e8bf55bc8599611b1d85e6bf19115e6d72560dd11faf36bf77516b3986547ea63f85b2185ce47e86b7812793bf1fd1829177a51e7468e7f59b115dda2280d1a5c69338ab8322cb89f7c18c44676c9024b67f81267e25e7c9752caf92a49f3a92f414564f5335f047f0dfc53e3f1cfe0d44f0afb580c5d33762695e3f1cf55f7f37fe53931aff992232efdaec330da00893634af1a42438355b35d3333dfae99136a7c0aff14f5d4f8d4cf60d57cd5abf04f0d5c2483554264beaa84c0bc7c98979f9a799a2f92fa1a9ffa1a0f5d2bac9a5ff3402e8c555ff33daaafc13e45dafba8b257a1be468555f555f3551aeec9f1ab5f611f558655e8e91ec797005d3fa24dfcd5d77c91d5d7e0ffc1817d3aac54047ff71dfea9c126e01c5855b10a3d359fe34b80ffaa2ff2afc23f453daabff13f39b08f57e4c6d77c0dfe516123d0055d40ae0f22e5f8af55f3d0a5ea1eba70fce7e587ae0e17c1bc7ca8f916d3bdbc4c07f3393a1cdf75367ed5fde3eec637a71a32f3453cf8d44f3c84e64df00017c9af01800772cdcccc78f000c03e33ef012e9249a57e66e6f1ffc068b3fa9f19cc02fed47bf0190b0f308c36a91c5ff3abc7ff75d0ea73fc09f86bb06a488e1cdfaeaefb7675ffc36b74aaf91e3c908ba6534dac1a4243837d8a26eea9790fb00fcdd7e02298af21b51a1d0da6f91bfff3ff4383fbc6d7f89acf585c1caf226a2226385ef5ff37e23f8e3fe1c6abb06a080e1cdf2e1b36be5d36fe8777bb66d7414c712f7e4fc9711cb1a4a324a4a4a3d94b30f7520e9918dcd597ff1d8eee57eecdbfd1d9e82e122f4bc0cb47b0cf7e7bc79f07d4cde8805c34ba1e1a3ff3a89fc13e4528dc33e369b08f0af5340f5d33de4785c22a22a99ff922359e06ffd47c8d9fc13f453d355fe37f549f7ae8a2f1402e1b755259f62bcf397d96fd8d3a342a22aa4fbd0935b86b3ce454e8a97915f669a66813bf0697005d3d33f3d0a5faaf9b7214a2fdfd8b8fa05e9bf9caa1ba77cf3d98ea663a18b30e7b9f9dc452e541ee09b9171f7b1a1d74cda0ffc3dd688a3fb176d2496d482e087d0c0304e3335ad4d76a77d58610d74ff63110a04ddd2cc7a365d8a92720acbfdae1b672fd96e7dcc8f5fdedd265aa35beb7ea8b70ca16b7204efd1fbea387fbddb4807430dadcb05cc747cbe87e9feb7ffd540da58f61998d3663f67f78b56fed5bfc2171efba579f75c4b5ffd9ed85a8b27d1b755a6e53bfbaea5fcf69b957b5bad910e2ce0c7f56d9940c9ba9ec6b7ffaad5359ac22b27df63f3ec330397c9e0a7b0dc32a2259f6a7c7b0f7d94e9d0a7b14767a14fe41fde939fc53d483c227fc83fa0cc3a8420ff728ec634b80ae9b4157c6e242976a48967dbb4ea76fd709bab82295100a3374d91fd17aeef9bb37b988792071ad4b22a4b7dcfcab7d6a1fb318ee5bb3c9d0c75288ddbd79769d23ee67ad05cab6d6c6b0748fc752acb9d76c77a4fea7bd7dac5309a947b423da57ccf56f9df158cb53f187f15889615d2bfeb4e7f0c4a8049c84f8f575accc23dc671bfe80f26623d67d2bf886c45fb3f893d96258c6fedb62eabb9fcd54da6d762701ff0c1fb14fb12a48fda75825a47efc2a83d4b7f883eed9af365307ca5a6342d2feface257b37a7edcf3eec29a534fbc6aafdd9a500339d15dccf5a1f20d7a6f900b9b4ffac06b9211b90aac8fdfa147b6deb7eb4f7e17efbd845fcc33191e5877bed9bebe1b0fab33b01f5a7ce461bfb154b1354b8211bf601726d4334ecd32e1b6d2a093e647baaf906b98a3fa09cc4ffeb1eb7c2c418d99f3621250320432b4d08e5e64eb8af611fee2fb6d1666212fc29ea4f4f5fa813e26baf43e5fa5bd4d6fd6c7f7a8d4e99bb6f8598eb37d602eab9d7b48a3ff97d3e4ab560ac5f67f5c9fe01654f2166bb82ccfeb95f285ce47fc23f1c110eab7ab48fafbdbfcd569ad092e16b5ab77ded7c2cfd862ebf14a8bab674525d1ab8b3b58ef872eb1d0a401dd1c22a720141770866792991eddeb7c95c8957b84eee07b1503c21c7f26e9ea4406ad2a56cd97ee3b9bb639d7dfaf3a5acd6ce5f2195bb4e3b2db5f66b27a9c52a041d84d12615574617b7e0b78d8eaf1175898376ba11e54aa79f7d115a24bb7883146612c35675fd22d9d7fa3f12abf85db221ec1d0bd3e43e8b156fbc51de88726386b0b920ae0ae22fff4a114679be3f50fe60c97b37553b9f800830f334c4910e71e43b179d7260a61042e7293b08c6a01833b071c9f1e7c604714e33c0884f431027fe29e8b481de39bd4e429ef37136680713f73b0de5d350ef788e7f5ae239fd724f42ef386517f2e74357cbf9f57fe8630d1bfbfa45b0aff8a7e807eb223f1643f74884361fae6692e78470caf3e14f2c9ea3147bb89fb7943f882576305cd8e3e69863b7c53dd118ddf928c84e294b8673628133c93d38bbe04083ab6f860c96fb9dbed3508e5f63c795abda4e43a72727a2d3d08909a48d5544e6d3ef994fe94f9ff91503b938a41cdf421591faf67bea5b0ef7d4b76fb14fc5b0f1c731714a393eed5ef9bb75a88422d235b14f0baef93f7ce2cb748724907b748175228ba0d592fb82032517c5648bee098acc887baa92b900871eb6b85c8656b0c021072c3c808485126bc5961bcc22ece5b66811433606d3b685892518194dda962a560747d99617c450c52012c308bb5b52b001aa8325bb258ad02423a86ea9410649061165d92d1c188aac168b476b074fa20d2c43e800622c8131c69016278e628a50fa4106afbb1551c30aa0fc90c3e6e48711e47e8c8c1f62a03bf8b0032fba4c2e7cb0424b1333195a99328696412c5a58299cee32fa94b16543ed840aa33dca496b36abc52c662996d54ca3da363f2fe51877ba297b42394abbf745d3324c3b41c7be70ef6e778f52ca392795f15a6bdb6f6f6fa736d0628cb6abd318638c114208619c91ca18638c32768c115a13d02d468cd2182385106adb0cee4465507d638c14c29376b988594a658c11f5b2d19739310b632b9d18c7a5c8498c4e627412c5498c4e62744214c5498c4e8a88b02615b3488c30556b218082c51ad2252dd4422dd44242b12665da1c01089244c08f0dfb4dcb56aee524d1c6adf7df4b7ac83dff2451a799b88df74ec7d042b0a903b3856cab5157c813422863282c042979edcbc7a6a1d7eba50129c878f1620a2d292a42007325554510563879b1a28a0a60bc8853b4141193bf48450926e6bfc82576e929cdd45dfc8913b95264923b22e59894636472cc1548392626e6ef155be49887e1786252fd7a65987fe17860a4527e9942871344bc5cf75ab1c2c89ede428ee339dd2b997b588512dbb6bdc6f16c2f57b6c8bea14e0f11244174f472652f4b1c91318c0a2f32b62484152aa2fc0d229e64fb95e3b1aa5c1f4e318695410caf4c651353c85095bca428a229c7f465ea7ef9fd4724be329b4ebb68d7d8ba0dac9546fd035296524a29979052044d0b461246585ef061c9881a6c6ec8c19206a818820c205c122e21ab30620618236416a69fc24be24696ef3232b20524c8524a29a5dc64b62bc3ca22029bc32b4f4d05043de5c8b30a11d7d64692f9e2da4721057b402dbe656a229aa994e7a8b03e8238f3573fdc0cfb14f964f8ea7082083feaa4a803e4420e865042031a5e00e30ad73c72c592104c6440890e96b8e6f751d489465c5e10050c23ca28816b3ee00436bc80b46414c6906b7a18454491f472c5ffe12a20e837a702e2c60c391e17a05c4d7ce4e535a10c5df835432b5960c092b837432b51dc207fac24ee29432b5130b1a258a2d43ab4378a1408ddfa0404f2004b48d44a14414ce201a6409a6568c0ab602259168a2d40d0ac168b478b8a2e6e03b3a5a2c96d60feec856269035520118142e9680acfa96da9bb6b5784c884ee8c0cadec40b48d60071abab859865676e840fe5816efc00125f0005204415b6b46696468c5872a4730d06a0771e2928cf33d56eed4c73ba352d67ae5cec33df755eeee9693caaf052a342cf78ce6e14929e5b7fa683484f062a721e77f11c60c319d4adc3d4a1d28883937dc767747e940c185702a812d63b451a2c8d81f65ac324a1425a436310be4085f4a3a6192211b0a5107f54cf079b8ce471beca1a7b504e0352b1bb8d36a1dfef5219535a8ee421b4dcbb4cc6a5aa665d64eacd52eaba4b0062a59fcc1a49a5a3911dcbd66f840ec5bf97dbaeedc85566e1d84b5e123e0877b40760a00754437c0cc115b8c0aac02b164f1e1239b9eb97683ec02c592172fac40618225b8d4c81e4f4091e3ed9265471be08d6d42ce0d3722f1f256d33f33122f773b097a0a962a8302a51a86a6b862c50ad777b3c481162da83c610510485764432b60416f32db958955dc9f904129b54409a6e072650751b030c275e5962b7290821976b081861db8a494d246d98123a2c8f211e03975870a7862b6a2c4022634b8d2a00db4f1b721c46d1deeb9eec66dfcbbf3d760eb6294e3c93e809d1d043804f46f3ba7dce426b70dbeb5f7ceaf256098b5dbc3a8b369f5b1fa18911a23e5b67723fbb35adc9fbc1125e74dddb77fe2362d6611b3b15238531d68b9cb18a3bbadb6a672ecbc75b8bbbba34edc764fb075c0af71447b9493568b65dac69d50f705f5027be7346944c1c4703346669399a1cda091a168cc60a899944dd16cb0776aa6a9416bf4ce94aa4dc5f1cc57d548fa591ccb0f1f3f76b2943725c38fa823833944d26068cdb561e386dfc031b9edc8c6ebf6be7dc1f1de3bd25a88d371e0c56def407b61efdc93b5b6c6031acd1a6a660d15b386f2775510eff7efce531542679bbbac41c75058dddd64f85b8803f18c73763178f6a0bc823b8c36315641691e25f5186374e9dddb6873a48c75c7bd5b079d324287944aa751ca4e06967bb6b3e3de7cd0e0ea9b31cb5e90582c8c05809f78421cf934cb0b480cd4ccdaf8532396d14c9bda26372e4a8ee378b0e750d9cdcaafc56eb2b62df7dcc74def7cc0f2e80e8d153f9b7144a64bc32c0c051427191a5c754f99cd8c6234479e4add2b3b62191fcb62acb67a0bd52d46b16c669ad4b69849b9713cd96fac1b8b653babd62121ebc6665505b08a597642b20b6d28c334c0a5ee65c282df63b8bb3b0d9e64f72db0242e648761d425f6bf6ee25f80203be78dd4555ae90b285acb0ceeeeeebe832db6f020a509134f1021d1c516618c200cd7d75d746edcbeb8216b2f3da795b5d7381e6dd51cfe808232f75af7b160b8fdda731d12d8a47d37b9bf9bfb8ce3396115a4f6cf8d0094b51c355c27b2879f4ccebcf040ce322fb4c8d9df2e47397bcbf164298e079320e46fcb96896cbb40caf6210dc0c8f629c763ef06e5c55341c8d40a0d7ac8f425c743ed945c6690a58cd799e4536b2f4cf902d3a2372c6fb6301a628b2fb2fccb4511597eeab9d892e57b5c88c8f227cb06292dcaf820cb2a240e296bb26441964e64a9c550965af0f03ba4988470eee5410bca5ebbdd2a591cd94b91d0e0ea9b464ed3c395f15e661a4d4083ab8fd2f4703fd825ab584802818e17e27412d8e356f89fb5afa0fe24fd2cd05712462fd84385f9d963db6338eeb87231ac67922ac8cba3fe83f2822a9af62756cdc79e621df7b33e9d8f59cfa63c2d2274ae2ec01e73ba6687ca8e52489fe46198bb7342fab5d7660724134453d4e9246f8a34b9b3562d0bf5d53b355cc8f02b8d0ff7835d0c90bfa69265134d2929a9b16ce07e6dd4585c87c6f5d3be7da04c3b6ab7fcc3a7b80ecc810a9828d1440e4a6051854b7e6711822cbfa5780e96e5f751ed5a3edceb5e62eaa456722f56994bf1bfa62899b019a4a94cca94e964713b1f999f1965140d0f37665c72e4a14c73db3a2d675d670a515d74cdee9421fc319462f24f3c22230f5265f9feea1d2ccb779bba9ff2763c716953ebe82488236d937c9b6526d96deb66ce01c3b37892b7ad53f90be2c8afd9e6d9a9bac93df940b9773871e9b74c0ef7eb6c764079eb62ae31e3865bdf5f5147fa5331301ce3825ba708e99f581504fbf9534a10d803e56dd3c9e27e1300cad9678d23613ef69513321fc31a17c20c329ffbada3f9b1eceb37470277fac84d6b55445e1ef54560fe7e09d0a5e485178cfa176052b7e35e047ffb5ac661f83392b139bbfa590a58872495b9ce19875b29c93df9314bb7fed749744aa7a1b66ccb937b5590f9d99f18286b954b0045f950f6a06e8a79987f79992f2a42e11ed4cb601f144621dac8bf494d591ea1ee0f51128355f55f1ec805835515c7af18857de8946813eb4b1b6ef61c0d296e96d96fa128c32a217d8476712f4a39ca165e91f2a2536667e7788664540c88135febbe0dca2b639886e15e7ccae4de07affcb0942d46029b4840e5f942e691f9d86b939641332c42ebf8e0951f943212d8942bd099621d2adb2e85584619b9c6a71d2acf0ebe18698e61c08e80724fa03347b934855f0910e65ffe8302835f2d141d4152241f09862520312c9d5cec33d94927777b6d83f2f282fafb309f7af917ec53040383e19e974f611f8bbd546a02b92e56612f188665a70a121f024079e6def140efd825da6f49583ce7f413e6e73751d4b93f533fbf9590803bada475cc9fdf04e784210679bea6759e372dd8d738fdb6c5ce5ad7f9a475306f5a723fc6d46fa44b56f2cd2a723f7dcd79050c79ce258803637f4cd73027807b69c3e5a0c4c47450b499329ffa992feaa1f1311f13f3a9ef89f9d4fc180ef7c4fcccfc184c03b75e7e0656711ff3402e19ace230f73ea87f790ef7c07cccc7609ffb300fe4c27e83e9e894fe8b4493a40d57c32afa3067f87b1a525c8b6953ebe829b99f7628b82733a0cc371018a97a27e66e7550d4a9dfacdcdf2ffaab192ef6d99bd0ad29e9252db4059b4da8148db92439e910cd4800001000c315002020100a05c30171402ca0e8a1367714800e72923874543494889218c8611443310c04116000308020428841062a1a3a0324039395ae1164b40b9cdf92d84fd776aedef2cf8ac5364ffe8616c29a01ca4fd4d81fda65e852b6768406f6315546c0375096021121a6f18918b083d224b8a295fc3476b22700c84af806d66f573e0a1409e316a2f6eabc588ca254e6936cbca9d16df08e9bacdb48aacf175cda3d05a0293c26dcb4dfa84e526425f2178b07ca9e99f962ecf52b1d0e654a21216ca31a475de7c8194a1cdb5bfb96ca59b03491739d8754503ab60cb83ec6c1528cc4432747ca6e552d2f538d8bcd6f4c73902543877ee0927abbdb0e97093d82ad624ce32855a0be24c2b2e6c98e6e786e431e36593263fbb7f9a924846428faca268906b7891d4f8c732d1afed53646e2273d4f0296da96157466b34395b954d4d9bc97b087c2fcc73e73411c5043906a1dff2f872420d9c412485caca0c131d32c52a1198e23ccdf2c815078eaf508c570aa2f29230eaac0e02d7ff95611532732518ce49287084343731aa0779dbea5ca433705bdac2db4775c84f5f379c082a3d3911b02152f499549a07aaa9373f0f8dc80900cfad49fcfd26c780743ae37e75abea52b352c4c9e33236135d3635551cc63c8aaecf17c4cb7f206ffa257f482d1cf938f6e0ff8823eb72e4839c94f9ac26aa2cfbaaf9c4682b04f208e5ed387599e072d2f96b09fc5b08b907a62851f96ea5225ca92633f9e5980af0989fb6be3ae8758c71375442deb0739e3d8003692089bec00f5c7e0133ccd44bfa48a889334f6cca893c65871042d8e7e586a8bdeb94bbf9980daa80ab8b399e5948c453795b0c0f9e433c8712802f681fc2fd440f5cf32ef6dd73796bc0220702a7574a42d79bed87d42654541c87422146dd07292e864f9f0460c7cc6c3611f717af8645857423d6faba1b7c52fc15debc6528546b2ec868762cd94f6aaf91cf4ba54c6a21a0978ccb8648b348751a96c0ab483a268091c0beb3ba430d2ce83bf3e905c34869065fa6753e2d514a20919874ebad156c15927303aed5a59404217dc3e47d78fc4fc75357177c6e337751a7913b3637703371992ad3b6ec88083fecaaa2130bd2af795e1da2988336f2af1f3ad8130ddd1c5b4390361af02e59f1f5629c40f2adc742bbe38382bc728d50fd65970f00c0b9d697b13c97cfd74ec2276183042132ad2f323304af0154544ec4bd19a0754c1615d3bf9cc8ed2c4cf50f6a89aa4426a9e300f9a2209b9a276ac48900400db195fd286a5a11982e9cc1e56330897d5cde5b619f709b49833ff50d157854f06c1d96913d2a22617d029d578404bde1713c05149a8b742e9e18d83542a8cbea23a16a5d3101725769123931b5731be32f104a989d6ff64622f64e3a9346c7938f9ad5b0c7d0d9f9f7b948e2f7a1c3bc882c301650d0a4953902dbe4ce367328508ac7e9d457b17ac8a7a19a25547c48520a84795d7c82544a084fec6c397d7d33d09b958f4aa3ebaad335612f997ca526d3d1023f56056e11f10f208bc25c76b2dc79937b0f90de9a71684605d164472218ce6d208f821736e6307a097759b15513656c47d84cef3b540edb53ba61d95603a4f80d907f42e3c2c0df09a3a4371ddfcd5443cb4d1d9efae445eb2c31bf6b8ae10a65380ba5ccd1983f5e4f51fa09fb098ab88e704a3830911b9e8a5e35753c537295b37ecca9b7ed90b8db1a05d4d26cacb2cb87607c4a5d8c6424d1fc927646592e92fca2a8b30f504b25ac924c504bd51d13920fa8ad5908c9713222b66886131ae645c2720906f379d80ca521d3d44f68cbb6982d1a9542ae3cfd3cc743e763d6c85dbfc743362771ea4dbcec853235b6e4bfe9d032861b4f6fde5dbfb3a28d8bfb902915dfcfe3fba44dce473a55f7da2c359eb482c09a7a13484def53d524a241c5ce9a483369de0c05e279d83a62ab750c6061705f1b09e4fe78403b5e4380d43edb16969d0dd74c3352a62b4a462b268b14dd0d9dedee94a611e870bc268d7e5f28317a98b478bf8eb1f46bf024c8fdf66c620100979539cf3f746e32d4362128840fc429448eb14ed4a1cc6209c05ac60ada7076bb0a2a966ba0a660e519fc298d53275ac8472dc509f48fb1bf22505505e40cd956639ee12066518601ed6becbb3e9d022244138f45a310143cfb0b8abbd33c991d4b56ac19d974b878edd72cea908d13aed787789b362a799eaddf61029ce599d159ada719a7b3c6bb44cfac4221aa0d65d33538338cff546da4ef59a5c4b40e1c72832d069a45799043d9152af09bfe5d801956d07544a867bc0bb934f83c922560f9d6573d582ff7ac0203a82c789b1ce38080a8f79ece6c91fd13a6d6477f0312c8b30f28568ab29071d47ada3237ccf8c2403daa58ce953be71bad30c18e6e5f403cd2d8563a0460f00ca460945ab796e5ff267a0c12778648f7894228ec114ae149a1625bd94dda7521fdd5b9676fff7109f9698c240e5af5538d9ab342ecc03bf676287fe1ca0427f7d9f27f450bbe018a4d3fefeb9f32f7321371bdf082a92a8848f88c55851ab4d63061750547b5990c229f98d88c6225740b5863d72e4edd1c69be141a75b734039d2573ebc2b140410ccc3dd3b745c576adf3191d1a52afb86fe232b569838275a35aea2547ffd395eb71cb91c4b1802f91f8569204bb14a34515a2f89ee9a8da91a50ecaf0eca43207bac8b89e905fce9c8fdc1d0108d4255f41e33d90af6387dea087d070d824759bee695a7b2f79c9ea2d3c43f14b970bc9a9832f1d2aef8050949cbd05e4a39d455cb98a863d04e092ee5af66dec3c13cdf22d98792523b677abba7456d74837e3bb1290f183f1eefef8cdf11dd7b9346284669f85c3bb6a07dc6c59dc9457ba6840cd0ed355625fa58b26a143300550b569269763056d01b360a8507cec4be789ccbd70a0ba8446bd4e968b21e38611371476f28c40aa946f243cf9af053fba8493ce81511254f02e11373690b1372b382c7f021d00450f8cd0ec20be8b45a4d7ec73f65a1b0a0ac4aed028ef8525903a2230802574111180c095c0d19059f04442fb56ceb4b94481c29981e8ed66e950ddff73e3cc98cfcc9ea995489d818ccb1809c8889e2104d9a2caa274f786cbccc87a4f935090c0423bdc027ec101df4e65c0603365ef87131d21bac0555d08a5c4896b6a2f4146ef5f994e3555c3ae1a27772c18813a39a2ca317ad4607cdb0d13b39c1a043e0f59a5a019ff81f875da0c4ee2206b607f73af12598111fdb32c81f019ae1beb4ecb4cb9440acc4ebf83855919c0a6c273e5e152375d65d3f0b6eafafdd6c85c27f98b61675cab783c10d8e24b91b4dc4b995126d2b3ebd7dd7fddbc6c5669ddfe27c81cf84d374274c90626d2a44ed9a7c62863d38904913d562aaa8bddd1153a360253af6c479875580dc4319240d09795b0f57e48856f332981301d40f86db49602856543ced1044c209e5d0ae06f3675bb396682e58423b5e717a29255a20194a06118926401f23e85e880a5ce453fac4930db925e1afcb73b79f5f5f5d00483c60e22fc9f1593a0065920e70add1017cd71f1a84c83c7611617e4a5b7666bcda85b889ae18cc6270e1182053d0223bab18bac7a78abc9ffbca0b8382fb4f8bb13c09fddb4d485656bb3d6e678029912671f43f447cf9c2ebb5c14f876594b87698458c20566e9167546d646495466b3ae08d44387b99cba80aecb2350a831008591cd0410d0902dfaff8843fe0c8d9ac1f0650e4fcfcc827a0671c39e15e4e5f4eb399bd9f28f67513a95aeb5e7448667540703290bece59e9c6ac3346a4969769c5f6d2265a4692b76964d1faa6c162ae3df10ea13fcf5fbae7a0eb239327db1f2de357084afedf8f65fa8ce7eb80965afb4de533bc7b662c8f2df9b88d3d9d76c683716779c8443d74c61c534162a7c8d4ea8a90c35349b3bdd725255467f168c447525b6dca6ed99921238c8136f7aa8adcf4972c85389bfaf928a59a81f26f85cc8674080fe89e19a0faff35cceb78af9d265e978df4cc75ac06249f12a9838fe3f4b59427ffc7f87b790e0eec5e03ad41c009891c254afcd95e0cc727ee13189cc458a073f11f7dc51e4388ef4be00469b5b51feaed70becc70bb245df3e52943ae6279c967670e0568617e3f13000512f19de5b5f138b251c56af9483edd41f9538504de82f8937b1b44e4afcf2a77cf8cf66fff30cadd1ee862d89111fb69c5546116a9a16603ea57c2a40a72913bc1e51accba6c147160f256fef859bdb42f794f55e5529374ad2cb909e1df2410edd57b023786cd9a1c3e24719d9116bec17881de531845733a0ac779e8c1c66c6ffbbfc59b716c5ebd8ef7b537068f91f263a3ba9a7072c95e658d8442d0d304c2349094e45a823b4a82de61164814fd4d15dcc54c664673a7fe956be4167f82740b7a45dc46a0e76cb4bb091f56d748b14433e97256027c9dc205975e975cde78971b72821baea71d6ebde0e523a44c945e32be7a07ab764057bb13d1e77cc91555560027bd136ebc9cf0e0699ae74f19b2fc475ed9866ca758ac350f66b02764cb47e4b8118ac97ebce7e9cb774bb57b7ad6defec1f393e6f9ac5eb8725d6db82c133bf418a697aa95e8d5e910f9603ccb28491171a4c9223fa3d878c73850432a5dcf783e7e6ed9790c81ca9c8318b2a9e18acda2ef9f9755c0facc0741a1d4388dea422770886ed13d70f2845c58bc97672ef68e0be6a2a09e228e92ba157b4d7625356705c637b4b69d0e2f229268481bc8b5439003c05f7a85e129abf8ec524a75510278b9d2251012e6692845164f85b36bd0851bfad7f5c2f241e60d92c4ad11c857edd772dca8b5b637f5d2bb0e12a079cfd1a81c369a548450b60d0d44566cf9022101a35e9af9caa0183927b907d77daa631e4ff7ad08277bc4a2efee05c049ab6e64b2ee57ce82ae01ce71df8d303fdc7c6440398e2c7b5d11e026abcf645d04f6824349fd020dd80df1e256a92890a169cd56a5a85186b0b3bed2d0aa0211df01f44c401f0aa7cc54b06b5280259320bab0d7dd68e2871db7f290871539c0dd91ef1faf3b4d49857d3d7ab42e20a569ded80ccfea5d53ba8a3a0ad9e496894daebb4fdd6d14129bc644ef55b6444733df160d22acf63fd427cf73088396df6cbe01d16fc30051cf131d265fb6e7d9c108033a0ed2d2683b416ef671712460a5d4ce3898e00c58b962bfb74919d06f019b4f0867b442677c924afac4201dc10010a2285dc2462be84f7832ab31042233c0b90b1b2446d529516240b5fda677ef3a9bbed75d5a4d810b1c16cf0f1a1a87d900383840d587c0813bd1705e62c47f050e5d156536216f9df43c6b62044a0ee04f9e3e11c9c572f8aae358d06cfd638d9871d1bbf1c749cf05d6a64214af337a12caea3a8649070efe59501c29b0dda634278b6dcab3fe83186c5fca89138d54196aab2d36e2297ea8b47f67f53ce577873aa391585321e4769aef57276479f1404cec9d4e297486e7478744a9ab6489ac54d1ef23ca482ed45799098bd03684e6e39be035e9acd296a368d66908d6bb10e569e66bb620a1d414b3438212ab15c520d8d64511c2c801871142be625ef70c0638d2a10c9c5310887744d27b009d8e60ca6622053afc6cca07ca7825cc20587649e61cdc087f2fe4592c078507e4590d0685f84bdaa0f68e2cfbcffac4e9b19fcfb3c9bcf63a6e5f76518d7dd7ba1801899772ab900523b4869d07af5450310dea5525bcea76d0782a87245254e0b91030b9f08f531e8ac882fb1f4770ed4c027807a9f60667c0562878ce56aec85d1a03d45a2a16747cb49a6f7623aa139903b024f4305e6015a29008196844204148b8c1ea8fb7d56b79e5282bee986eeffb91c9f3250bbedb9d57be4e42bbb44359f4fde64939abfc16573223321f9ef6d9d5b7be3ce80c465c0cb842b5739ad8240dccf104ef8e7f6793f27d5d1921da879730c27d472237edd4497e74dfc39d171e34103957a6c8f5815108f9c2fbe64b28282b9843dcc794580d3206f60bd07c9a6df9d5db9e1082817ef29eb9df5aecf649c38986989fb3820c30648892e7f4543718bfc7cf939bcabb2965f9210734f14dc7b251d1c27095a9b806b460c6da44457d4d7783c4cf0065c4e8a2a5c03bbaef663b7c1918359ec7d1efc4234f764ac61f24031c012887f4318093725fc23e96d8aac44f80f4279e720c7ff3bfc1b92e127497f50fae6722eb0a9354e1d7486d81b0ac0fc3d018394627ac49eb9955ce6983153c00626668d5ec09b7d423f314d306eec73d054fa930aa4be78a9d31bd07bab0df6d351e37093a8fb41b01ca0abb4809e94bc42289d4457d7a983d902ca6340334d4074c58b1afd5fdfbf1c5f305a7c61ad5d491ed09b80269b26c625b8dccd9036872a24b93fe9be5dc8eaa171f4471e591eecf747d2f713abfdff3055015ab3cb3e2b9447cf498cad075c01607af482640b68e4258143712ef422d49eb04600c1b65633e7abbd2115df1c04cfade84b881f8099747b42a6f07873592fad8614298c1d6586c503472de19c08f12a51c4b0fbe08ef61c00c97350cdc7a5a62c84331ccf45e40f21790197d8f575582b40ed4a05da4b13939e2094624a61001b79fe7a810f37a0fb07924c2a4b207745e8acdd46c20bc8bb955dfcc2d0588995f2f9027ec38f16b4e8c8b2638ef94a6498d5cf9e9aee62eaf6c053e2c39258504c05dd4807cd07e0f155e3d7119114dba0b03175053e6f601ba08c5a0be42ec239647c88c95954237f630a841733c230d3f18395648a8fb399e828e400f9b4276ff076c726526fa85f40c746ce1b5f017011c9e562d826c07e0021a4e1db789af0896987c586cfc76b1bbd9eb8730d29cb6dcde2a6d23e0243cff3ebf19bb63b52762d171c5586eb698606862cfd22b9917cb44101e634a8e2ceb81bcdf08cf0dbab94a1eec9db843262c2c8ae200e68312c3a4214d623862af842675c1b2637a81a71563986fb8903c52e73abb71ac7b9a21cf7f8148006af02deacc82b591caf503342b882731fff7aae833b6b17aa0f81c98575a61937e91a56408540b2330da83e13c650c7a194d6020b452b9f094dfcf84a4c5a81decf9c4d5e0cc6419fc6901207d18aaf9860b04cbdba03838a159e6a03531633a2d40ac6296640a6b8f8efd94fd6596b91ddd7a08d63f4fc5f0eaf8066fcc31d7407247d27e2e814e0ce441e2c48a97b69c8955ccb86d1c6a6fb23c8cc4fe8dd56b005486750e4a187a443ddb21dd03fa7320f8c03f5da3c3f135768b2d55b66399eb49881fcf95b6928461c4a67f9ddba2652adfad4b47f6f64b8cf2ca4f82252974ba2ba88f977b6599da48fbe8a40fe92989e1500a43b75e46ac56637e24fc5349805e4b8cbcf3489f39d4de3a8784f5ac992627b9eb1e01561a2c543414c818ef396244ed38541144271780d2e4448d84410ff7baddbc100077033ce4562d6a6fddb15ea05f0852b3c0c398e6a0295bfa2d9c90c14ecdd29c937a21274c7daec6c84b96cf3140b3d673fa640766f079528cfe4002188b808d9113d49f124675dcdeb48b9b56b99f62c27753f2998762f8bbc3cfaab06265c2c9b0449418633456d761f4996230fd485db3ccfd3150915e68ca62e8f0340a025e549ce67b78f23caaf0cf6eb4d662e8966c96812d701ed394e5b3348bdd6e4a88b0fe1ad3e06591c6f547176388a764d30c16215310c3e86220787be2ea31a16b37a3e35b0bbd36c8eff5c88a7f64b1f9ec61df1c402e399915176306113c9f96bac1411d41c3c57492941628fcad6f163f0cefc4a02f75312d7cdf805f65107f5312f166c531988b69baa14d774aa5df42c6412c26196166c716d4378657d94152fa278b49029156d73673797fbc9a542c0487286310c99111ffbf5c6985de20975323815007410262b0e0d0e3bf32f6a265e5bc073ab9474921675eda294eb39caf4f94212b33fdd6b206117da192bd5ab999c20e7e18122ad551d0d48f1f7e160291da91440a473a3e79ee7bbb5ae65742a745843266d804199fa79f1d1927fbe277e598925f3326a8502aecaa92ea5d3215d2cb95898f3f13a85f793715a54f489e7e40c1693e85ec143c87a9caa041a135815d504651414691e87941c15ecad6c7f87adb76624c91ff9501f40f2a7aa59e2947a13b8f9110aa3c23d37b14b21a3a3e975097186e454522fbf407bbe02c91c1c9ece72dc141f1e99962926ac636ededff9db2187e208d9b04df44f9c34316d217d9e1bccce77f2ac23a139b5582f1c18fe2fae14fd92bbca23302a44406d8c6f5db67126f94c858c549a1bed0e30b61d2d1526fcde447db6bd03d1e45c5a7a5531ab6596ff15babc2e3bd5bbb18524d002daf030b5673630c149c60ce10b9674a0f3af231caa2dda0619121f5b5ba6e96648da212b4e5fade50d29428a4b7233279bc464e910118658ec606dd313f5cb0b5b640970734c767bb62ac5b50ab25bb21c5fe8814f2cc1ce403b1b4aca4677de904a473088a4ec1457a8a4428c45c3a22c046d64d38ad032d923047957c0a689079a5d645137e90b3263d5cc321d2f2af01e98c4b020b1f0f1ffc450bb6e31f650c9560c9cbafc5eb71f16ab4a2d0d23502781734a1450b39d4af4440ab2ea165797f1305a0f365930a685406187bff7ee6dc52b586d566ad416f3cc7a47f1e95d301c12092cd25b280a00dfa36c47d0f7e72fd1b41cbd2655fe6cade36d0723e7ac32f7dfad522b498fb94a5e026530c1e0a38521294df79735f4cc85b4111e9d0c8b8d4dbe16abd5458eeab230a628c16e7df65f0724d9b5d5afa565ff539bbd43358d88bf36a35c8eb8bc069d50e9ebf13bda73ddff91e961fce57d910544289f182b779e747dbf10565c4e778e7eb8028ea9a980433c4b4fbd694eed95f41f50c1fbd24a9d9da3c5d590eea74ed9eea3dd8899168a027f96008522f138310c6f3fee5e4136904336c90080a2e88fe2439744ea98b4965c19e44d6e64ea9583870da71953be00a118b7a2888cc4d192817d13708858fcbcde4a2f2aebacb4dca5009e99ba6c23a381d6a9ab4bf68509c9b3c6b050df92053142affb6d20d25a11555becfa24712ed947ce00071c7f7dc3f2ff0474353e92d515259ec8826833851d6475bc2cd9c2efee0a16a13674502268347dea2be41d3b11a64b0d0633c8b8de9489f7fbd99b380978dcf4248ae5520cf3f905e8e22348e6d79746fc196f3103f5f1c852e19225dbc77fe93172891f2aed11fefdbab2272eaff910a94fe082862faae5282d8383579b5d8f15399627b988e62994b257ec365f2512a0551cd1527c4b8e0e5003c493cf9ce38dc59b1213acb908b7d47a2dd2e72fce53b82eb6fa2e04218537086a8e1d54d99715f2616996f19d57a7688310fd0c5092a7ce1a5758cf788fb5d0971b662e7262edc0c81871ef5d0b65f0a8efab07c0dadbce309b162ed544377ed3f59108ff96f8971f88b444f09de7611c023b2f528c945d8f52e05b6b42a40d852b7119c44ce3f5c8cb624c8b8402a35602e60685b71001d20c8235bb5cae0d1b0ed596433ce91448d46e05f465204a3025ac365f5d8051753633db7704d82dc5ea852c722277e24740318e770fd95162df90e9ca9c81eb0390b00a3f37f51fa494e9ca6d7859d0676e4d9512d6e4f49e22bd3e061e8964f83888e88c99536ca31e2bc0d15979007a5eb4a6248b845a039ada456e9541066e9ac46fb817114227288e692670577ceefe54814f4c3aa7208ed60c14e71c1e331ad180e6a6dce1d523b5a70a2a7940aab8d5954f030ba7ccc7a82079920e593aa9d37b6fbac9b76ae17ba5f7243b763a796be087c43fba20aa8a7384034228e9156105fcea086e476f6a0f5a53f346cbdf49060bcc445dea28082660a14968d79417da7bdd819c79abe125db981624bc0558ea74f906081a85bc5a6a5e67526ab62a814c0819e64d7404d2e14de13d35ccaaa1b8251376b8781c643314a5cc0600f8edb5122fc77a0ccf8c1a81b1e7bb3d54945503aa2a0829770d88cee1a39a5f079886a747f176d0aca3c234af29d4559b3cc579ee94193dd6bb1c2025234d65a75615b802f1da32e31e8d5fb1b70952a921a2ac996637e473e7526aa048259ff0c20f7712ac99cc6d6f63a5077980797e232cd030c5d6ca9a82fc6090f7b13575e3686c760fc2bde582aa44d10bdd96160831df199cc1daea2d957ef97b5843f6d7782191380b6b362f0a939da3fc68f784f1b45f59d89661caac69342222b3056b3af21a630d4c6a6133301c4b3c5632abdb56f88458282f191d6125f2a24426678b7f963ad5a13ea184e903822eb0434d80bcbb61372846385677b33cf6b0116c2e04c4c2b178c8e8fa344fe0637f2e9c5c4cfb576b8eecc3648c5cc98404a868f82812356d4ac055e06a6f4f9a07238a251fb974911e9c33b8c8279f40d716cceaae330d608ba66a4fb89cc6a29a7b922a8853a68e32b3464e2b182e353995a1c7d6cb04c79db743767bcf659b052729baf783a45145855cf2860b063fba20f55a8e0120e0cff13b92ce4a27f4299222bb3268866a7bd513a0dbd7f9e97cdd13c414f223470bf0ebcf6085c201ac6ac2d03d31e584b9f27d5b72aeeae5e979d6ccb48cbaac101e7fe66d95d851b376a8030d47080d039876e7a51d47da79e8e0bb972e4bf1780163ff6a6f83935496e32fc8acb039124b5199e9175cd313300131f59c3797ad80ac58ba17579ad73efc98f31ec93810cbdf65de2a53002c0590db69edeaa170e69020cf4a20002b4bb7903928fcac1e549c2915eccf48f60aad08370aa9017f52558a298d6c099d0285ee71d29f19dd6c08452eb1ad8a0733385ee2ff33010fa2531389426b5b4e54ede7b747705a24a7bb2fb0955c68c4165ae45170bfcb7cef375772d72f0f6c3f0edf870737595c2e1a4eaac0335c5957db05b112a04ef236e0b5c07e03c7073c20e0e794e5482d3889ba0316cc108f82f0e5651a657e3e8f4c88e071bda860c2a441660a04d8abf9f4edb97e8b7333ad28cf8e3471d1b1672859ba313a785436f50fec4f6070b7564de8d4837549e757bc980b8370cd88341e33047cf7f7bfd91161b4c3e5819028119fc55cc6f63ff7a94498a4f2146da03786ab4990b8b56033e3634a7ad98671ba24d7676cf0064cc193c72fc4c967152666a860fc867bfd47cb60ef0b28ca29a22a1b6bb1cd86d40cd5786f797c8a2ab113a7abbde8fd08b80ee8ec78c5a69426d06c1c8a9a69b55a9d76d697ab6e780ea15aa29e6629a530379ce0d27f6b12252a1b3fc18bcbb88ebf3b55ceacbfaa0828ab185567d37777bd2d0e38806a0b395915d018b8e120cd9b82f3f95fc5b95c5d29ab041829209e0926a8f2306f4aff07b1250b5a935250afd33830ac6665f13b7692a6034529446bce7137a0aed25f4b3330b62fc75cf6482c0c59e96ca2aa847ad46392f79d96ab9250d13463ffa932f2eceb94f51bf76f9c19cb1d1a85fa72dd253c984b3e712ecec5d531ca03a2738f495d918bc55570c21035624358e40a68114ef338a403ba41618eabc1032f8805e1ec92354bf93ab7135f4a484f35b094929874d460099ab4da504bc22a2904a35cdceaa702bff7afc1a0fe52217c48a62cdfe6bca8ca16081086dd39288d616171acd4da2e24923a7c6332ace2e2d516a8d3f69f4521de6c177e56dab2ab195c1c3ed99d21ebbd3d0b32f84f060ec0ff7f223bace58770b8a0484648eca97604f6fab5e295644ee2f6de1d13e4e2c48e68f3f439a84666f4a83d74a151709983ff08586476f52837c17c79c77800c44a776d5dae895807266d6186713fb07320c9b894b040aea8cc16258555da897c19f8bb629381a3807dc0826112b21c232ff6f339619845c80a2682573441665cf211d059c435009aee5078b326b908407a472d3a4b575b64214a1aec036d6fb4b00ba91023ccc97f73910d3296645653007f8a0ae20dfb26c3102b53fc482472aea7de76137bdf1adf1ce0c03f832d37e1dd9123a9ece3daa140cde4b5ac97dca833f5711e3fb58c57dba799f3628f2ec756e2537b5116a956fa915d294e3daba8b1c7f6ef1d3982394cbc127775308ca32b4df9583d470f8e568d776b28ed7f45bd41436086801eb1f192ca6fd03452d1241e9494660b1d9b81128faa5a5c6498ceffefc83b92344464089b207b0363b4fb714b11530393b2e3f001fcc2c5eab7522bfd2cb9bfed5872c1d5c9c35cbe886eacb90f02e2fbcfb779784a94265f8ed526e495b3b6c128032e4fb7408a266fbe1e2ea65b1c12a113964b75ccfc64c02de1bbc86aa7c20e87467c9427b8c8a305c84fb398570b35027ee5e9d48fa40335e850c7fa244265d67f87cead8a02ace84e14b77db9d1bab7c4f504aac790ed7312b7534d352276737302c3e48ac9e11477683b993a27cc3eebe418ac0615edff517e338454b84a5f3e6342ee73af366c03bb40ea27b17afdb8ed4f71d00722cbb2f0097465831099518dde2c00b7b9409d805a4c625e7979ec8f39e8cc7d29e97f7b76450d0ee718333e2ebb86fef77eb587e5ed5e9583ef9f8856e5cb8fc3350961fe8fd32800555ef84fe7cfa355f7639152f669eefb79fd56c70da67a91592e89acf2dbc2eb868c0189a5de7abf16a8a474defd3bacbfa213c50b095210a6c07ba1a8df6b0c204bdfd39c666bd154d662a19601b653e17b371eedd01edab607ca808a40d9130dc07eeb095041ee232ec8af7fec875046a85239b71a50c4f482424f2665d62e27cd4e325d84bbc57265f86e7e81fe6f256c7fa7a92dc3edc34fa101994225ce50218e4e2ed741fde17f6ee99a21d8eefe22b86086428fc3dedd8167e14c92cc4f82b2544ed0583bd3169881ff6143b366a647af41de23d0f8a8526cea7301295b48fbf28fedd7690b23cf294731dea89086d7f035987f804d606ac69572486d1e2f56925103082c416165c1e557b029044b2504f3b0fca8ed1de85b0e7a0dd1ba08df520b69c0deb32f043c2a621c64b538f5ec98f9294b25c7c5b8febd4242f791b3d033439cd3e620efaba7a9989919e511b08472813094ac3b011e4cb13404c8367e2086ca1c217994dac6da74d0331ad72d2e7575c6c41c1a7683a6b8f38101e8270490c5c59cc2113a8b9821b8d33b1c9473831efff0f40af77b702edea010c2a4626e9df10b4effe873856db52455448d7d61a94fd20e4749b0959c7e63b8b4340d3801d6f91c149a6443cf89f0eedff5faff49e618910f1f887e4e8c77decc324bea02225ad41da9b42f1fc405762175974907c36ceb95746d2ea666790d3af3ea4f74e889880cc8fd3e60fdbe15028ef6847aa13eb32a4f15480a86118136c7160d571805bc248dc099e703f748e2eee883a442c043c3ad231d86a96addcbb8259995733b4f5907a1ca936d82b6433511944a07a87757256c46f0019184f0198867fc45623db219d5414d7978dbd088d5669039d43abacdaa0cc86671b46a5766cd88144d442517b57fd657102f4a2453d6c14e5c457ea214cc388c332329ea3b466ae318c9b1195ba812f3d1522e6db70423980f8f9d52c03b650759d8b25c596e87773237890a4cadae79c5dd6646dcf21c794d13ed8e9728d8f591bd68be01609d61228618a3e05049e454a38ad5c3310431856288557cad19df6c13ebc876eefe25330ea276d3a34f37d23def7ced8139d026ed98f65fde722034ffa0c05f1a5f26cc0d53eac8d31dc92c88672c5d8684556f9dd9093c51e3516fd47830c45c3f059f634b51f982c51bf2bae0c2eb892e518565a9519457cf97fe27afcba555b0491f1bac354de849baa349b0c10eeec72632472af614d449f524d22b1bb0236b521639fca5c0c19f0bac4d98e5b20c8019c1e58cf8b724fbd27da766364e4ae7a3731075e9614a518ff8528e31e442c98469159a34c322e1ad1aa59e5e3d3d7d2709f6cda1b4e4ccbde02cc350e64ef3bdf553fbc6db0f06a4dd5b733a4569bbb59d06d9f636c5751ebfdc329477d9d7c0a4e94e00bec1609fb8a821b552626eafab9efda71f94daa14542cbe00f5c727cfdd6bb1f3f453b9f899def1609b7264da844e47986dcb0ed47ad958fb3e4030766121d4054da643005e73497ffc36a4301799b51391bfee5191d89740d48adf3f7e0c2ab26d088a41453ee73d7c540a07727c5efb1687985c4f7acc28e0d90f9d9739d3517b3c983f49c2cded6d7f590e2d86c28a728960bee7faf701515bbc8ac572913ddae2283ba29679b9bbddbc02870e9c1d7ed2ee452ca33f3f19b5a92eeb2648bb5b102324cc0ae8df6dc76ef2f5320c544eb0a0aba4603ff40adefd1bf479bd7dcab8c1733bb082f233d4704ce532a3b07c8890a4b4fd1a574aacfd3b4a90eafe01c58601423a6309c8998de210695b14d40221b66a8b83163e820ba0aab59d5aa993188c08a857c5a60939ea83fda448065329c7418ef09a2022cd9c2e734d81dece84d7ab3da3f2b17e63d435c117efe54847363d624bcd2ce9ef5be39ab8511b68b555c1126697aa4816fdd2513e6226111304cd89849fa8c21cdeec1baa0e85bc5e0b91004c6e1974c9026da792db7f79d98c83dc53e7f2725961dac8aa243a6f854e68d95bfaac628a49889d9c521634ace21bef42a4f6ccc7c3bb76210ba047287760292ea5846bb657607afa94d2f9b40bf154a271d892664e9bb4fbec61dad1d11b351918ead6738fcf3cde70386d6abb527242a39174e29e73b881d4b36d27ec4c955fa4fcbc215030e67e033a68b6b5808e0992169ed1fbd4bd137f0257035e14a6d653e9a9fb925f6576a193dc6282f7c29a6f82e02dde9a1107875e38ec0b275f6239f4327ac841f09053fbd4b958c894cd6aeb15bc914ae631a220cccd3b0d35fba357ed034b00de1cb3560dc41da13f2e782672a169801bc46f49d86ee03dd7fe55dae79c775610f09660989575e853992693df3458d0f0a2c0f5b974e5c0ab7122e6c7e97188f44d2b80ebc2965caf5b11dda1bf705ffa4e8fc1200166a72f8ba6ba77b54601e8bf70a652fb335615e7ad60d6c0df411968aed0168e448eda5198f780886c0814323c616d42b42d84eed03fb7aa9ce1584437c7d10fd2ffabfbbc603c810e89bac01f29275feec499c643eb77c121912c354f8d1777a4cb1dccec39c2328ee2eecc8ba6ae8c725aae18f21d81585ad2890fbe5c8bcd1a6b9f6ea72566090f20baf3984178f41c9251a82222b1e70343b500c55c70098b9ab5d0dfe08d4b43cef23630261606a14662e304873ee9a8d5f8d9014b2dd8f7af83b8a8dfa57a64eaaa6b9b125817852ddb70c216548afe0dc7092b26d1cb2dca29326ff28e81162ae03bbf112bc0750f740c68d323352ca95f92d5cd2c7b872965b2b4c7fc295d62f11407b61ba03783f920e408d5fd0dadcbab286b55b061b855d12d841f5e8caa60ee03362db1b6d2c1ff1cfa02d6e82c35c57d5beb2b0866595d7440c72e058f98a9418f86abdde71b352d1531fba52035b42dbda41b5b2de98f3cb76346a35ab3f2006c2875c9f977d531ceabf5b28adde219f8908431717f33dcfea93b02526b0edbf7bb3b36f0218389a74e29e8bf5d783d3ab83065a38ef63ad4889832151b6bde9bab6e4b008d2ce03996d35795e99d0bf4a3f03484564fe26badcf091650def45e40ea75cc430e498c9c70ec1839274bc20417f90d776a1a52e370df9d17314b8e5b205b182503251f1c245f26175342169db628df3535cadfb11cfa9204063b44b853addba3add57c1552fe91cd7fdab98dd81faa1fed9b8e2a32d65fda3b0b6cd7890ddfe1e54f3b7a7ed3c48e2979113f2eca4e5b3075c7404f717212d1c4ffe1d8d0a442845bf355bbe7fe180ec8b33ace3f708a883f866c0c2373ba8ee8bfaf423a113b227e1f88045023515412652b318cbf0517cb6b3e4a588e5a73859d26c117c0ac3f77a59a4554955485821c01df0a078a8b6543231e4455ab9456244bc39192470420489e7982ffc90ff3e8a203b23e2862654a47af8bfd307e6368e3a9a69b27c84d134c1e0371b66c6078aa009d1e53d9773ade5d64b2534697f7946b2999f76862a5aff297845ba2c847652068e43830927ce58656f035dd13757a9bc55a92fad47e7dc5ca8bc42dfa60856cb82473d6dce270d0b65171449255c04d206465570f79780ebd2907a216f6112aa96856adb666e4afd02cd943a249c0ce948d2e91192fd18e298f18a7862c9472f290e9d9254210e823545f605310e19acbba0c9c235e6105c86ac70a0c204b945d33bf9cf45df376b02bebc790b18cb3d422d12a9918dc01c611edd7db0d5dcd9db8ddcf761e2f6aec5ca7151677b9ccfe9cd25c53e588e4e95de8389adf0f020cc5fdd08da836c33cc4503790105054517c2b61e290b1818c6be015bbbde6b6dc2d9273e54e1488ad659b220673f01489d8787264733a37a53295a8ed7e881a11259f976b0f79d51bb965e4e38c6ce0e729bad5dd9f898ae5ec812d2e8e5622aad87a1222afb9543d30da37628d099e0899c459f131e8112cc3287084e7cd2ec269db488d404a34440be0ef505a290d6e4c5941695148c3839b443f5d48a96c63eb01ed46bbd60e9b57e414c2860e05eb62041290f9e7e794f371faf842ad2c79d32789c70ebe1197851816cde405f772f117b3c0f9fc17930a3ce8c328c1d31b4e48cde913d6431d8a5b87b1e4e9fecfd1c4b3d4fc62191ad4c2e049331e5d75a652f2420cb53040acf70cbde66d51e719f7a3b809cdef7c9d7c8f3b10b48c9db2cde1d0827b5aee59415081fe44741be81c2421ab4c04d89f56f76de64a5ef0359421300ec013edd52db215cf5f0b1ccbbf0c4c47075f3916d47f90970d15cddb0fbb65e91167ab478effe40d20fa8cec587a77f588fbd8eb4c18bd13086172391927d0a06e9d36238e2003f92acfefd64f09d1c59b18b706be370455fe66d6f5ab9ddb85f48d64b4aa0f8ef478982dada015d086b7e6f5083c2df88fb53a64057dea6a9c570a5e42a707497653036756e95b5d84f8a8e0baba9ccca6e01f5976f7c5a7925bf06553a0262ac52c3b8b2031cda6e8464921f6603a30e454be13c47ac5b114332fc6fee7c7a98b33853eb03f288dad95996dfcaaee55a8d37190b470a24c9071640266b96e66561590692dbe9c2466fc72fad780a9eb816457b3eb03befc8e65fb083a595b5ed67f88047af5fc8d04f9091776a10b5e428b4e54a2e5ae27ee870bd5535eee39bc255c745e9c2cd3f9b850847d7e706dba8ac5a351b30c4b53e598e97e5c88620a6bae1ec3be9323c7ccca40ab85b37447d11beae646886d8fa2b46735a81eaa7b4df7ee08f02ecc35f9289b44dad66bc94c617a8f0c49ef31d0347ed706a006699f1ffe21c1523d7d4061d2955da07d58bf9936a73927bc249d5dfef46b43d037332aca67b4f4a1ad398c220f89e5d25749607dda57c108a7843c63eb46b48f60899b7930f6b0160ddd9b61c38f7ae412a0a050189ba3fe000fb479b2b34f6e15abf2f168dd589db7afb0ec51b1a745d85686c0946cf4242d3783e81d6366c35dbc0f6e12d06e09bc7003272bbec95dc6826c9d8636c94743ac98b47d4d7c432cf6a92cba316bba740799f1db652031ca4cb2b302e5ca0311c844c4cc76e0b6614e854641693b94bbd1962c1335eeab76dc5121fe7544ed875f4c1b6ae827e5f307d769be673d9b5e36d5c2c93d73a7a370b3c56d91c2bc7173360aaec79c5a46ca9b6e227909faf6cfc5632a58c7c3af4764e24172c22198a2aca608cf3e7d972ea8af9c18b8c149b9f3191bbe19a43b6edb472ed02dc1f3d0bc2287bdd0b9afd13a0d2e4d247dc6ddec8e53b6a768fc124a4437044a58ba986b47186939b09cc8e4bd09a62940c6c8df8de40470031594105670904a41977d9f20c532027809f10606a15e13ab03b2273a9888941e88f0dc15754e7cd05afccd49ce46c209598785d1659b46e111bb2c77d2af4a4353a7b45622d040b18e1cdc2a968a96754ef1d148775dd65d93922eb60336c59971ae4733db2c334814ac757594ac82993a48c49e1a5292c01bea3770ecdc55603312825a7284f0e63e49d253a253b567d148e2143a82023e1f079b3ff014e9ce60085a443cf7ba9ce723bb3fcbcb408747edd6b73f5cb87e706246bce7bf2761cdec151832b324afcab3fe8ff15504a23408f712ff54b0593ae70af2359b11f0216d2d20594b15eaf38f618ee813c6e9f09d3e33716f3e3e8e9564e510522097d81d567ca2516cdd206bc27e71237607c9de71f5f50228211a139240fe43b029d20f9c4ffa2e0ea803cb1bea7387c193d09918969f839465423250f31d148be02bdf3b87f935b934c653d3005afc167acc632587123c26611915213613ce4bf64a2460fb9d264356fb233cc76043bb4316e98eed8ffe0cd1daba0f7608097c6e5f0a88f4df216d4aa6c3c9ed6d3fb056ee6d885a07175cffe5fd8ec728552b2cc617451597aae0da2374cec5eb5978b8953993dfca38de2bfb439a218c846f00294f2aac1d7f7d4be860e1c53e952bb0b37b72a9f68ec7625ed21fa6838fd491d30e0efc82b60f31e52eb3140f888ba0d747f90dbc0caf7256f89a117f269a10c4f82a177af75e4bd57a81aaa8e75c630ad3f37946594e829ebe5d53621f9059258a898ee600180e8041167151d7ba65e95b629a0f0be2c094d7a752853f0b4d5394d920252b096e993c7390951ee49264049b4acf320574d402894fc86ea03a27ec2b337b938e7b2fd9af8abf04192d2e9549e084f5833b839960b57e0a28fe83ac6887f2165c4105cf59003e60fc74d4126cc62b184d67b1448bdab78fa6901efcca2a97b789a5a7986da95a68765bee96298122b9ca50d03e07e76faac3dc6488146520a175197511a2602c295034c60025990c80481dbbc757659e3f4473ee1aef59e9d14e20e3ce072cd5ed770ee673e3289ad49bd23cec1777c4af60978e6ab25d555ce5c2e3a70757e696a0a2c43fb1b3d6dc89162d83914918019c08cad0c93baa46b64dbdb81eda630bb0a1901a8d7dc158e53a02b1fcc0286cfbc4bb65daab1fe95b9f2ee029ef1f6ccc8505dc7265c1988816403dc33a5e8bb4305d2a6e7f792ebc55e14c44a45467c91c1bc379ee2dfa5373ba505384768c9237c5f5dc7f18148be09b6a02122a1743f2da301081adc1344b748157c36ef04bdfbc8d17670d410009a36f4e8712c8459755cf187b941608e51f892a758a51abba5689a669c80eb7caa9c7255839107aae2234be9cb6fa1924559d92822a959e0501e156b94c1bd3b2950cd3a461e4d29dbaed062808a85edcabd133560158a9ec0fdbfa36b50fbcdd95c2dc22ec92a486d05ff5a94a3731eb586dfb09ed1c1c384aa104444bbf551406f5f8bdc10a49c09611334b83440c56a20d9c6e0ea42c9b4485b08d2fb3d2489674d20ec0b6af0e50cffa548b0f892d535756824c7bba8160094d478bc5b6e10e152705a5a9d9d329c58be80a912670b7d4ff758a1967b6b480fb521bb18a48b2e802abfac95caee15b221d6b9dd72768595584c5d7b0e24a8d6d6dcf498a9f262b7934380ebde5907da4dcb280691f5fbb672651bf18c3b93690e5d188aa147fc92cef1abc3791bde43df2105904527f987f176559e295ff9850790dc71bdcc60a1d716843e8535f2dca2707041a9592b0c2d287d5824bf74fa68368dc07a2c6d541e0b4117c4186abd814aef8cf00467628bbe967273c36c5f126e905b3fa7b65575f6e636bafff7d7c9d0fe371fdfa942398e63c94404ecd2320e7f892bdec5c959c6720a64dd0090630865fd1541b3e063c6ff082ef50ba212aa5eb0628bd135a56da81c83544949df002a012394738cbd39dfba21aa7784ab98bcb8145ed5902e4218613ba62d0637073437236e3a02a7f0a563cca9b3a9fca031cb180551fbc06d8316a86f1284f3ffe84021317beed54cffe815526cea9b8a162b01bae96c114a5ce55147d500a20d5201611652b534c274e1d4a26220c08a146de942b1454b7cf377560b4dd24aa4c114b24850f40521b56d4d19e60a9fa634406fa0fe7a25d46e14d8eb0d30aa56997d3b1fa4eb55a740d9e107dd1ab9707ad4868e8b5a89d16605911846c8e74ed420ba5f86a6c545b0142ce366f877e939f336fce276e4313a02409f906bb024e0f8ac7d5a1737437b2473234ec151b6f412a89ad1533a569e94d2d44f84976a038479410ba8e61fa04d39c626d6a042059d2c6b917a375cf6c97db1a7fdc873a5204795ce9293986ffc2d4dbfa0e494daf1d63dafa2a26f0ee97cb1a961d78d49bc0d99e493b2e566d43b34531ae761d8b9a0df17b18d5da8e6a433aa5fb231a92c246671b1c5836063df4583882a64edc7202f6d3dbc96df487254770585871d0d3eaf47e08f431b44d27b73be881e480f62f1d469080b89ca4c2a5f1d4930a172ec3089792a570ad338456ad57a3e500f1fb924af9bdf2d89de4dc5015e8ee764caa34816fa3861b77b3cb050cf29058bf643944211592831d6ee9b7ade0289c058026ee947d2d4db1424c774bacf5426868fc94c6c9cd12f5bc9469e207a85df49172acc92e03118df9c8fc6e5fad69adc342bb4eadf143a387a6cab1d1da0a1400e4222d4c945ed5afbc2f3a342680e09908cbab2f674f79d9cf27c0ecd7d57bee0be9345223403e10533e3f60dc0e665e8dcdd03f6afae734beb77a56ba02e1e46ddf28dbbeaad342b7f1fa5cf582f3114712eb278231aa91913c6bd2706a0da6a18851bf1fcb1a1b3d89ee50f4ee650b2fab209730de2559875d97c61a0a4efeea0b6a86e9a72dab2d2df54d0bff2c584e7df9462a8864ac41704ffac85733e7f1f667c628a177224b6698dc94203a8db7faef7e7d7b05687b15831f4b14f30eaf737ade0ac8e310a39cf76e340dc65e036611029cc51e2971660dbbc98fbe1885a4587cb58e6d2aece7b8c76daf205ea0edf9821dd9f2de3b3b5b634bdd4f42134f2cdf9b9d8c52dcd911dbeb2cd82d6e2961a6d7620e87013a84c6a0f7d0a7118205e24da5a598a11d242ee2378c8bb0b81e69385283863037eed40550fab0305875f059c04245a42efeefd62a67709a6896a11d50d7da084d75ed6d0005a03568c3119d15d5264c9f98ee0b5f9accc813d8309c5c8bcc1c75d0ddd01a669061987042f057a8a0e0071a7833448e0a1940d437877232d001e2c9b5e8ad8321ee1dd02a67e4b3287b203707456c7366530d18991198c25ce8034e4b36df61cbd4a873c8f56bcdc1afbf3623c016765dac1116d58ea98c610498548c45280342486471aae59829761fbbb6a202358e200d3ff0d11dd196c51764a9e4efe12a967c4f07e04fd593b20e406c023303e2c6f4d94d9e014ac8b4aaa1f90d25788eb5d22d74cbc270564891909d2f635927644253aeb6fa2ee0a0a7f818bbaf40e00c8974c478ba45b9f1082aa24a14b0a95be5b858c2ead63ccd033ee3b1eae140ef4a55c3e69bf67b4771b57e4efe92454f7b1d9a2ffb18e0dd284226e79dab7896899ba1790c8e0da6320f6eec4740884076211dc035e1f25c5d667f772be8e37890a87e1c4cf70a00e660e33bc5b9203a08dd2a7cd8748192ef443644c71010812946106232ffe7b59fc70a5da065e46cbb922e25493b17858806e026e9003ded4621c6ea31d37d5cab5741311a936d740780d90f12787d885233396271db389d3f07d175193018e44aaed5b0cb31a8cef981a1bc5a159ef25321dce765f2811b7a1966768a504f2ccb20dc405d913fe11e4fb2c5f323656b8ae6956da2085860246b30e6e5e0c135ad40703afc2daaef0128221b8ec984d130ca0d7cac76a0e836f080a4a4da1e9c1c795848262ba92387cb4e0b8a1d4f29055d749fb6934346e61cf1c2b95f8b86b478bad7bfcae81dff6ff4c4e1fad98e33454babec921126dd17a8a72f887a32c21f5e73b84ac6379c706d6b78d2ad0cb4850638d5053103b0ff42762d3cc41f9721d9cc3cd73fe456e58c2ab9cce588af394a14bcf3683bf611c51ef2cd49ab8aaf4e67a5bd0c3de4059e891a643b68c74623c9168322f22d7818170add4cfd20ea94bf68f3a03f7cabc4e9a256d6f9d8c5c875384ef80e1ee588ea40f9635a94316d712d28a09c20bf3b6364fe01efddb04fbb372ab864b20068fc2fdca68fa8e66cb521216e8b4d0791ba23e63bb62b67376c84cd0c364f5e90aa8e472f02a5831c4c8f1edcc1a849a44529542f0b6de1f335a78e39c2a30ca2810aabe9271ac3d31ecb5fb470a693676a68a368219f61d4474bc2ece8f977caa040cf7a0e2f99e7116e13060763f1bb645de99779d4f275f1c50f60b13c677d41a3c86481226dd51600891388d8b158d191bcaa89c9a9a98738eba3986949508841166b2b4a60a36f2a5dc28e72e88dc320d2f8c97e3e7ed2d259e4c5a528974dda497948191bbec2e642dac4409ef4158a0eb409ceede90e318137bfa48768a2bff4acbea99f221d44bed931a6144b6f188085e4ec991e41537d588c51bd6d2c635442fc3c9e60195c4666af05724e3494cd5d0af26a61bf38ccc98d2ff80e22825ddde8d777f44e7ca78206c7747ea0e968282105488f64e2a168cea86ff523223622488819c5f0a9a6b9a7fd80759817f33ad9ea0ccac59d4b8646414e5bf84552f110bc8e83c4d8026ddc1b19bfc5002697f5bc03a9f54cbf21d9f4b40d65ed14e7753074ff50123e2d9e28092f0a6757aede6b3d73b8928cfc6b96b2934c2aa48eb88cc5336269ffe37cdd8440d9481576e0a8d634f34a2b0155f48a7e98665efb088d9957fb394b4d1512f371b2e7f0103105961e5d24a6478aaaf4cd4413de3336b08996db0526797fc153892bf53af6cc0d2dd7c5dac41a75d92d6c438b76eac325b2abc01f8194d0b916be682bce8658c6bf5243ab436e84eb5340e82ee206de118406fdbed6cd17cbaacc693572a83349a045d913ccb02e1a5db2510b9aa2696b1406140323f228e7bd8d971054cabde0db581f678ff14a07a58c12b0d44617118b1b468c2409ebc3f5c54affefca9f09135c042f11abe6b0a06c8a6697e0c46e37dfc7568c4c9319825f328c15e8934571b383a0de356e547085405120b8d31c6556ca66694dc78c9ae76fd4209b78e0ae96f229633724a0dcdc53499f1472a41282512e99256760d4c0b550cff7c41585e30c9ea4654d1a092d99e22a97c898a4af29de0d88e100361e11625877999c6b32860e3494c7513ae95545af5d6507faff50116e9bb46858c451739d4b1ec161bbd9b1fd0c4828949a80b5a4ae461a6be41462cd882a75357c4419a1efe763d6de05c64857c461a4e266f227c80d10b6798af4e077efa5c70c426355c28f5846cf193d4b0b87f7dab9b7a8713fc9a46864ac656e10724572c78613c159a31300f42ca50f9bc4cd7de006ef0c983f700f4aedd792b31472fd8f2c4df4b7692f9816d4353584ba81aeb00321c5b321a57512f669ea56951c6eb1a8274463bfde7e926775400727fc330e87fa45bec1cf7de337dceca0e0968ac43afb0ac59e9879db764c2fea3d1ccf1b03db3f241b80782a980820d8aba2657621952ca190888f10d99071c59454fe3265fff141a2d264d051190ba3af48ecdd2e76198a98dab86562a040af6f6345cd2d57f584dbb50e6b5962726a3b4f8e6442835f0180bd89eacc3c4e503b508d70ade50e1380f7221fbda018e2b452fc92fcbc68c2023a7ed80c7f5da7b7ed8fed0d8f0a7154ed79b13ada2d1473bd7454232fdd474bf2b367dcc2c80ce4035acdaa935920ce424e074d0b92bb3c0228dc10b203c6ef98e173df70d33396dc690fd53d2b55714ec47df9b43305af9d2f1386cb7950001ca172d26eaaec16f601985f92ba529b4fa61a21c1fb4eeab4b20679c14676b72df796322529032b09040907099cfb10c077b2761310d08114d7b66a8ddb3e89807eb20e8af2e4e049b49eb86a203d3ed8950076160a1405fb9edbf7c4164610bce7f23d9185826905ea2651e5892b76f5085d0844ee68074313388476aebd009247fbe81e1ea4a2dc0cb6b8f375a0b8384994c9275b1d4871a75010bbbe29ca0f7090e3cf771f5059827522abf31fd7cfcf20339de77563cc3cdb81c08761d54a9269adb5f36d0f61177b8e042225c764185a98887d1685e131475c2781b015699b7d7976131856ab48c909093204b4f2832fe407425ce99c0bb1f65efedcdc2964c519c4ae5494eb89e30f1004b00b08d85f5bcfe713863d01a2eae2b2907eb1dbd1d6646de5892310164ca215ad759722489865815d720c659f42cc429dc4154bc8945d1d282e5745908257ccc7fde439a544d383803743d2ccf71a6dfc089e6846a4d17d6d4a6e2b6d1c556966779630e9ccbd0cc15309d2a9004870fd47e7e9d88f0e686400428c284a5c61236bf7d1b19f2d47552c39a3022a88e822fbc992431ada1251dc944124eb31a22881860b2654ce3881ac7d6787cbbe33c3e5a4305e84915aa061f2ed9c11c3a4165e40f9766e0cc32667e6db3f9c1c9be3ec4340663f893e3e3f1b105224a9e7266b4370fc741dc7a938c467c755492c0a86286794c8a2ec4758f43a4fbf1a077572949ffc8a3ed755228ee8e33e12ec2b4389387e7222383baef223d7719c88ca4f7e128fecb8caa5ec0401949f421655853a8e13a6e61cc2315b893e33883614349bb41b3aa9a99a8d93862368e518f88fec029f43d32f8eab70fc86e8336f57dc117d66ad5d85a3738b6e6d67e24c1607862c4e64a5f0393b911cdb39ebd68516a5338252be65488e7ffe7d0efa90cf41f1f394e8c39ff8f9c8b38f1674c37b9693a3734bdd107d6e38088a3e3744ce99ddc41aa62cc549185cb00a71dcaec8e2fc47767de4a41d97b29d9c948f3d5b39fb8efb6c1982c33ff7013a80ec8683e24af459f996213b0e8a3e2b9177441f1e126b98b21b6291caa54ce5230fa9441687e3528623b23851481223ee88717cc7b70c516de1dc676808e83ba24fd3c41e92fd1a39a354c85d11f4b169a07864cb101c07838ee088ac21a813ca4f2c21a02f40ca40915beaa10c2080309acd645dd4af761f2096e1780c22cac71efaaed58a1acc0d489021a306473c120328ce4416154fe85bb358421c0b8e4867fd6a07fdf3b16b5476c9486326c836c4197552df5022eb7394c8a2de2c225306fa94814ea3b36f500867d4af76ae261b29eddb0760c3f51f3b5a7b07a59b754060378b8a94885d4d833ea85f4376f5e3ca9532938c06bdea87f2aa0acc4c68bdf060c3a38dec5700aa40c0cfb3fac76eca468d31bc1501b8e24e20709a7822bbf8ac5dad7c0396816ab9239dde430bab59d4e7872fb963ba953ba65200b03009fdf4cef9a61a7c8b9e51470103c33a61869f32bc4cf3dc6340eee81668f003ec8d7061d81867bb7e4d9ba558ee481f845d16521df55c0a7c63cdeae76c5577572a522bea9c73560084b91cd775950b5279be451596d621c5a5ea910473777b0737706f7a12b88b06535876493074f4e9a0ca2fbb54466af5d6a18a136a7d41f56ce59aa0799c7901888f2f33746b413b57d56df82a4a9bcd665464ecdddde1d7307cea72c77434f4022edd4d69534a45d60954649d30cecf792a763d4017b71ea056fb690afa59f4507ebab80236ddcdcecc4a6ce03b9819a15dee486bc5b91c275e9cd9fa1685b0c08c17e88f2cf0b0c035275e64585819b5117e4a294fbf40a02d2ad25741ee0042044838802a940ae5ca4fa6b3ce4ef4167bca40a9ed39a7af90a89547ee5968bd651728774ca1ad25cf76a3f723abfa7c1c2fc2fbe0e6a77761a882dc319d8ba4a41c64b95cd925f5fa34c815acd26264f960d714523df65d87b018a69b1d24b877e88c57d609236dd108bf65170ba08fdb457c10c4ea523a914ceee0061a64dc5ebf917c963616dd4309008aee485734b494a7484756fbfcfa02786fabb9565800bd380882f714c51dc136427fce203c034e94913eb8853f6042e98d1c1c213c86beaeb06d7a2395ef4c41f3ec6e6e6ee6c9719e42f2f37b588b1ba4923ba478b999628c74010df602a8ac36ce7f9327a3b0704e64ca2a99d516f567b78f1cf9aceafd9af3a924d919fa524a2d0e195d9bca35cffab1be114a9f05f02dbb7e827412ca5d74ad4150ba2395b5ceeadec3204f5156e978802e6efbec3072949a52565f9c8f86d6379873006e58f0a17a813e0b3e7ed2347625e58e743a084a2048a9486d334bb28b167d327653330e0ce24ecacd96343593354dcdd8085457faf35f6ee8a58e7a794aa1659ccf137c1304250982d27520a0ef9e1dc5642b2e056b2b1e80d25589b30a8e47e41179441e11f5e91e910968ce5991cc0cbed63ab65add2b6bedb9a33e6bd775d4274729ea8867c760861a32cd66507c3a454f87e40fe9ed75e6119980e40fea6d7af6b122f9a6b117458b19bc89d6b1d54a4ae9995832c7712936a8a25492f41155a453f4c54ae9ae9100c7563e2608518161ed5c3cfb20c0eb0c1ffd2a52b9cec8e4f1453a268d4c11549307fbd8271f1d0301bfd857ab7b977e640c04f9aaeee313adf8eae306afc333016c48216c8074558741aa60e5aafe00f9aa9d38d6af6354df2175c857f50eab02be6380eb4718bef3611703a9a608211061503d08345812d4a9f9a48fb155319ab62a44fcd256c54b063cc7144d84a85a78140bfb8ad198c2f0e60c56bc6298018611430d94c989649737d90d0fcf4ebf5879cecb326e922cb5b1ba756964b5e2785f76a29439c66a6b15c515eab58dd49ed86ab17141b620f060cc55dd295eecd1ed97658e386a391232f412ad95f669ca5eb05b4a902f4ff24465cd1f98b2cdff9c40d676e4c119497070c61934b4a0c1c6b7bb964ac08383c8f6e4346eef12688c971eb1049047416ac12d18d6025a0bc030c1b33348e9172b9a524aad52170a5627dc9252951f796c12f77b5946090a18f7823638282ee7c62be27a491e1287e5da1b38bbf6c81e71bd2c2c0e17150567f3025e6181153558d0c3186ddae8a10d196fdaa4e17eb016a70c0d0c421929f9c61c9da05062bae2067a61e28959fab22cb164924c42d8d0d5fa25db8ce086116a426d96e0300216416d664b8cd8bc4932021921364a405904b12163c90863a0b0b92d19218b24be5165872f6754375fbcf8927cb9e2e3e14b13dc08be28e1c39723a6b8385e96f9e2c3165f9270435ed858bd2cc384142a28042f7dce6fd93d7934ed168179f2e4d14cb9daed9a3f44a03e76c3b6506efcf1b270d57ad78980abb672421cd759963caad3c931d0ae95a395a34d2827542b07d3428563627253ceb9cea5e7f8e2d9c7ef7b2a2781c9839d4747bdca1f52ba354d11382e06eef758726d8f3906369d82706e7279a164e7d5ed9100b53127b74a008d953c289fc00faedcc5137b4225a5e010324441238a282ba05bae0d03e853a3e8d4c50da634c0313a786f88be3040f68d49091cba204b50bc21383568360a0ed5cd907543746d51809a045943c88d0e41d0406212e4cd1214364041d2305a12448d5b193057b494c0149d000c13d604d7f4b20c9821286084bc20c058712136ee2350f27342b9e5909148f54c1d4193afadaf7504487c859283ef50d49de3bbbb8724df3d9ce1c1c8a9989bc6bdb2eae27b0264822728e0c1895fad92803b247976ca3ba4b197bde0f03c8f6fef198107c67122abfb79da968ab5dd84f6040459b520ad2063f639d8b53af90080aef7b49bd0ced3ee2305df945e7422e1919ab6d0dcf5efda5ce56a3757575ce5aaeb9b273333e32031b96b8e72575d3c3e2fadb42bed8e725d3bce769de5f161f2dc6de8a2bbfb5db9bb7be7ce2eea0c935ee70ed6c6e9cc715c9d7704bbbbb590524aa69372949b2796acf5d65adbceaf5cc739915266e9eeaeb6bbbbd9084d993265ca9429455faf40ae564a8552319a5275bb9b40c2c90e5aeef7d2e60488499333ca68b2268c6b5fda9aa8f958704f2f6d4db8f8c2b8a897b626567ce12d419b6d395a6a526509174d9c34390a6a82a4091745b4a4265c00d14ed024c851cd2f68eda98932539420872d6a694ca1c59b6c98c2e8471054010155395e2fcb4ca1a655548da074de9000bb804ffc44cdc93d9162c5461b33b54ca339669af939e79c734e4b5bcf71b4aabe02f95afdae61d3218a155d01ed52743362ba524149344127929a9a2d892f4e0891e60c255c60b154460e63eee5c1092e64e098acf1ec9471d8e239072ccf5e576650904995a74e9998712d3319cf5cc23cfb08947b9631c38501966059b274c318256380a0134e509382d482b049395052452a48cb275b63acf6b69546be26506b0dc39861d4adb79418569da3b56e2b1599db1f13ea97f5945ba04bfb5107942e5747bcab7e592cb7863ba66e6f7566a95bea96baa56e3d6c778c8d79ebad961a86598f8d61588e5b8f09752c06d42febd60a0901752ce5d6a5c4f1934be9deb527715cc580bc18d05b77318cddf39cd0c52ecf3dcfd3416bdd18c6e3adb79018c63b6e5dc7ac632ab7507404bd75d93a84deae7ed401f4d6555d6eca553ec24a40bd496cdde40eeb2d2f57473cb225c78fe878ca77a6eb78ca87e888ac934b594acc1187ec88ac93e892a944964924d2a59cf3233a3dac737461b78e498fcdacc511c77b5122ecf6d653614fbfaceb841d0471a7332b85ce530e81ce53a26c08702e5b88ec97774acd5e3ecb52b77ed9d42c45c31d7ffee35390af75eb97a5e17e3eb63410ba9c0642b77ea7f1823ff93c29a07f84e1c71b259cfcebc05338a6fef4e3913f85e3e94f4e4f0ae07e8c013c8563f727af270574ff811a08ddee4fa1f89f05c193c94f21076f0a49e8fee49f374f8c32f9290cdf148e22f8937fa6293d8b5ac1e4d4bdd99fc2154c27989c7af5ee9480daa106ef85d2a644c9cb5307536e0d5b50bdd7a5e83d718506c514124bfe6c2f1cc22eeb35eba630a52648eac62e9bba394b057659972f017659efe1511ebdf556525b6f2db5f5d6adadb790acd0ed97d20c27bad0c11bf17cbef75eaa76c7d6ed5b3786b15b8fd5524d56604125b6a0fae95814fdec52f4535ca17d3a1d00d0f5677735fb6df6557341e32acdb935a9b78e9968d6d29a54ced25051e4d0e40d5544a39968261a8dc25a714d2f6d4064fcc853b3011d99d25cefa50d280bae897b5fda80a0c8315b799135017db9a113e2971c225a4981c259d08ab78635fc924afc9235bce197b421c92a083bd012275509ad034a52030de294681dc4865a2d430907a1e1885a306a98419220358f096ef81204080cbfe48ff6e264d1c4cb1a8bc6f55e96f192821c2fcb3031337dc0e3f2284fea3c2f4f0178791a325f2f4fd6f57d5a4003e061601380b6bbbb270fbbda031703913ba4f3f04bcc9d5e29ad3e68ad22b52b4a296d2a59435a665dc2706262cea494a64fde385953ea596baded3ea42baf8ba76340e48f59a45f7c3aba5c178035773ac7d9dbcd5a6b2de88361ed2b9e9e7e719d58a4bb5b917ebdac28c22e5a6416a9b4e9ed76b33239bf313e90032eb6d6d9dddd6375aed6ae768b5c0db5b3b68c76516f8efa844ce79c9373c9892086ca7155544356e6da3fe9aa382e849d7455efaf035cd873ced97d1da05d08be7d1e913baa53ce8bcbfa8a0abd5dc924334ba6db582ce4931f6705d8f8c18b353b28516289cc4b510d4a5280c6133cc8c639c3a0eb727ce7328def9c766ec57ce7b70635dfd950e53b578fd9e3a75b91032e77ecf1d36d904e1cad77ee516bea4c26ef3a21ab2ac43dc08faae75c023de794b32539c173347859d9af250843a90e3619c0b0cd00e669d153d7ecbb59a2c0d69200024ce3ba3cb0210942f452e59393c7cc8c67674f0634556431869929d4fcf870830d6850b1e40c275c6683b1831368801f34b9a3cbe5e3a76b821980fcf279496f3999f589349318c6abb627153af738e7e997f50986633efcb26ec1ab5ae5f096cb83373de617a5b728077d044d3ee6e07c6c7930f6f851be79eb3d1d439d563cdd7549eba0b59f68ad7c592ba5ac282ca073f0e64e24f0b344389915c0db2ab3d2ab5b6baf3d7a5b431eb9c33a0782a2ab5fd6d15c2bddfa24923f3e5368fb555b565ce12675417cb2f5fdc009328754a74e85f48b1b72afa8c278bd03292e5de38ef6392f6440c1cd19c642c173eb567476712ea568334309886435a43f3421e3380f0abb38294f66c778f8c5b9b5975b01b9d3ab78abcf8fac8ab65fdc0755aef59107749cb07ba628d609fd9ef77ba0d84389fb533379bb38ee8bc8b81ef2c787e35f487f9e70c417146002bb38b75e02bbe7f4ab27ae244802cee9049a405a827c4991f029c80ad371dc0b2deb84e99e7b3f7d3a618a52ba3cc739f907cee9394e38c230d6f7e4a348a03fbfbdb00b797ac81f464290b10ee4c8b1b373faf1f423cf733dcc4d5108bb38a7cf71415e11196c7179ce0cb608123623663f824b2f6d43bcb17161f3a797362e6ad8258998b171a9fdb47129b271217afa52ee90a264570b5ad0a046eaa51923ba4c2a6ab041fbe9524ae93279396b5d95e33ab7473ae73877715e27d74964dd73229c57f14875ce3df1c816161f6197853d3e2652ed68725e7d9691afe9f3cccf99e6e71ce3674dee98b3a6420ba9cef957f35a4dae75cf87e694768d1a66de0006b8862c76764ef4d9c2e290cf39f7f1becff3562cfb26f8797dc43ae7353cb265881559d43de79cf32326914545560ad6ab0f9941fd9aee4a23a94c0bd25429430a19e756b4ee3109b8a041072e4b824023ab528dcc137d6650f59a4cca38efb8b0565f49590d53b6454875e93934caa8253ed91a75043de308a5cca7e3083b2ad2e420f3f9baf8b046cb4d764302203c9145543efefaca594747474788dcc1def2723b08bad4db41ff26fdfceb36f9c7759676e8f967fac0130aecd0fa877373523a2ab0c31dff76ec7f37baff70ac2e003bacce02abe7f897137639604e0e9813f67f15ccc9116be874030d32e81c2c0c2ee0158e1b3baa294f1c742d548ef78b3d45ef2ac4d1fa1cb187dc31bd754b5171ba4e38caa4e77a271c47550b0dbbb8a50a5b0058c11d75cccc3cbb0e1ac3c6548d09c3c6e9ed73a60368e80bd3f8499252cdccd017da133ae71499885d3cc4899fff74bfd7a9df9b721ca12a54f9ce0eede80e5585f2144447f62be5a3fdeb392e39a6e37acebddec33a661d6bdd3825ea4876e978ebc62e769ea39bf2b1754b39bb8e787b306eca755ca54ad9942a25b28b95c205be72221788b2dd87080ed16725de7eb1e41046699bc2e83b70e3c71eea294f794a7472f4d2e6c4e85d2f6d4e825ec78c61ec3a9230cc7b76f149e838f8af2765d42882f03f0ad6d309fd384e43947f15e44e24d03f390d41ff38b03b9d50ff731a9afceb407b3a817bcf6968fdb33edebf38a870bce0393f852e746db4d1c69b1bcff9178e47c6540ccf75cfe1f071b59aa16f19e7e3f095cb538738c2c9f90b516103b4a1da4ca2f150c7a64ccae41214a2a3b904858956b55a905091d16d22cd808284d8e8d9d724b56e2d3542badc51c74c88907e69b963ebd66ad6b4925a4b0ce3d43c7beb8dfc31ad3d411750246df1e2044c58219bbe9231a9823266524c19634c2d09d97429a75b39a7a76a1dc3e1ec3265edbd3a66dea364c975cc5235f9626f71b436a563d6fd14c78b34a652475296b384439ccec19b3ba66a0ce3f1eca9238671cf9e42c330167599ba316c8a2da87e034f8c6698aab12b95149372c1973627483fa66a2d35ec62efd2427a6edd5ab750042d6c00c95454dc31e5291db59a8e19bb526eeea823d4a9422849a663c6b016ba40f1fd4df0863c0949d552b71452aa963ad211a4620e8cd0095d21c7a720b50329ae901309ed39cee5845db54ead1744ada54868d6a3415684da80a2a5034f56d67a78b9ed1b70310beeb160845d0c8104785e849374e5190d494768d42025292dbd61a04abb688629c3869a5a6516a90007e12418a855c400bbb8c6ae8f86df3f5f335367506dc68449c872a1e76c54edf6ea1f04ddea2a1cd982e32951051691d48f2c250a91b751de8fba620f25aeca77704494cf277ef20fa6dc1e157c40008414542192a54457d7efe95388000e9a0505cda03673346b3368ce39e90f4dc86ec89721d440c34ca623ba12e08945523a447af84424e57c1a72c375441f290605b21b224f0fd1c982c8089a4c4774f5abbbbd9d258cf670110c0851619c4a73cd0c120009ee0da976a4e33788dc707922e2e386e8c32a92a5449e7ea55c47f439c0ccc9d297f239b2e40b1e8a642957853e2260a38a4cc7d52ff63944a32d24e979a7bbfb44a4224123d3690115328a6429efe194b3bb64914a87a52c154a990cba7ca3049f6ccd196860d06431888045972299e7d22573dccad9854192144e22bb871b8875b3059124e8882ebea4235430276d21d43b4ba9d71aba1043d7c3d43b351269ded8d5222779218b88c93d37893098dcf36e6bef6da076d38d22ae1a210ccbe28eb3c631949f49f30dd43194386ba71a58bb35a05a5b3bce1ad798b08bd640ecc219baa33c02328526f4ab811aa8694c80fa99933ac637be8945dc5ac77c084941fea8ce2f94913bd86d903bd81ba881382680a02f0da6813aa8b5f4501355da363cfbc83756e224eeeeb6c14c0d69d63e10ba23cf6d312680a02335b39664d626d2549a4b33a6cd492a0c614ee2611763b9f28f78220c9ed730659eb3178a71bd89d4ecb3368f6a18a0a0c9b5012951c17d70bbce3edd26c58a4d8a14db0f6b0ef0d3ef4bdb0f5d3cfb27bb5b7a95f414a4abcf1e9e50d8a08d1e000dd35dd3eff455101e664e759e9e345ca6d460022219c6fc7841c20443b53358c83469b5789a7673332d2389524a3da0de39d8558b604048c74268d38228a6e4f0459720592d2243873545d4b420514356f955bd4a9672dea0f1cb9a119aaf124dcdc2892fb5cee4641318266d3ec8f0758caf3e42656656bd6cb8ec758ca2a76f9ed6aeab0c0ea18a5046a8211411bf79ea7c9a3d755e923b6803215fb4459494d40f7744ed3c31a2a43c75d410c3465511c3c69d27be306c3cd59e3a7514110accce135304146df2a04e5750e494dcfd5c4047bd53c1aac0aad687706e51449c0faa4844fdc83c944bf4f991b1aa73fe23ebc2aee3441feb9efb70def98f4cc53b45dc1a28da73dfcba03be2b812bbb8257625c91d9cd3b0039a3c389fa71478bcac2bee4c81089575a24f0cb2ce3bb1a3b5e3c81fd5696a34c872bb97de7a00b407fcd231011993b2001283ef1c88fc21572d50c1776050d1b9945bb8d8d5b55253eee8c34707eb1c480a9c571fc2791d521d04a938047413fad53928fae857e73fb254111a64b9200ad4d9bd7005fa7d9ec83ac1e49e976072cf08750fccf7dce2bc09e7e58cd5222441b32476c6ba2224b7d997191399f53b63384533dc6666664f64d657133693cc82deda2cd77bd95304f6ce716ec7aa3b4e524d01ed442939cb59ce7296939c5c413a95d65a2b3b713422dd2b95cd49137a8cb8ac19934a4a788145186ae0d02649c626d1eef0703d58f9e827aef4dac457ebace133f3ec259576a5ede580e0ee923695e1fbb89f73cefadd7fcc3a3fe7bc737461b2f931e78dd0e92d03774354b16847969d3ca4f3d3557f5c77cef7ea99e5e5b135f6b5f246b8d2963c299beeeeeeeee6c96334054720c35a58fd0ad3fb9a5111d19050d0a4f10401c336d0de320d2754ec2045098c131a3b0d5e9327581029f190c615322a16e9d71ce54dbe098a1f5d4ffc288b9cf8511e81f9512635f1e30462e2c719b4c413fd386b396c947441f3e2062a92908d8e011cc2d092054b126b9ec8461f29f420460d31679410a28bacc551d66688e28b9624cc7c098209198b3f382ca105094992a21e8e64e82288376668d105e9896c9c502cc0a508206e6803041915c8a498334038d952060e54cec894f82650a2a8628da3268c6413cb17639a5033059a36b259ab3549c117628e001384ac0a2db47c21852dcb1244c87e9828e3d686872baa1822e9740619b530698868b20217c638438ca51ebab091219bb53068501a6a728234a8904d9f421ddbc00a1a56aa78c1856d8c6cfe6061020d2882b6d860858d6c1e756db89d028ba12cb02862088b2a3f5e6ae7db48c004b3062f6d58d030268d1f5d3532348925444c812508871fda28fa198117b623bef8210d2c537ea418b16143942664b6582201597e3883450b9e902193841543b5c40a14ac4132060d3053a41926243e3457b471628c27a6a0954903690731ce10f14045042b905893c5cd1a2a4ec8c6d5144f8839b3c51a2c3c98be58a2da1493971fa11290208a19324e50c60e3f4d88ae28138615316a70d14a3e80f4f0b45d61c3b3dbbbb251b1f4fed226460c315e50367e463004501343dcd086062d64a3db0db268a3444909953072005b2af0ae622d098027770457ed9c87b7de6e53976f77758ccaa48fab5a5151c4826ffb236598b45141f476f699fcebdc32ac9b230f8fc5f1cf714294fcc08fc8490447fb56ec425557913d86fae3314dd343c5ae28775479d02b7709608a20bfc78790c66d676b5d67dc66661fbe30372a806cd7751ca594524a29a59452efb84addd66ab14c97d58c3ee9b215cb2068369b2199ded1662c6b82d8b84aa58a72471b5ba95454a6104b4c39c011776cb55c1ccbb1a23c33ca17de365784eb816904cfdee2f9c9c495b6a1db73ae7adbd053afa7205d279f4a505531878a5d54bceca2ddd57881cc903c7b4e0f17c84bdb9095e7f8670c4fa178961ecc9934f452f2f0805d2e39c6d483cd085a0841362d526c5ab4740f25d6855a4a9b98eff3fe933ca75ce1a5f7b0141985efcc77777bcfa215b7e79473167d50a4536b67a3f13177cb1d23bfac4c2965cacccca4e9a5b2bb8a10aea8ced5ea5ce717945b755f6634aaabd26903457ee0930ceb58921a5cc679174237d7ca3dd53c21a1308c7656ca7ea712cda4dc4884de042941594353f39381a6b3198ef1ac5fb3ea9387fe88679f46fc6891d85927b4060a5012974167865fec6880d4d0d6744a1cd4bd61a13ae3a1aec67da465a3c69fd760830d381bba7cb263b25aeb0b0405b83a16ca17e5ba679a1c4e87eafcf4aa527de7aaef9c272be4970ca320c843b048e0fe4f4a296773f37015c68939b88abd5a61b921d0d58a79bab185a9d997e2941e50ff703c29a5284d1c572b1a9f64d84a5631cd44f24986810cf34e1ca8b476d7398d843e8e5eb07a1cf606275f8d7ae17482fd1aaafee3c02ee73f0fa4e17d9cff40d49f1efc0f05d2f0fb0f07a4a1e9bf1c1a7affe580295005aac01df00688035c7d2c0c5054ac7247bb089e43c080e53cd4da6bd43e297ffcce0a3b407cb2633217c342aefaf4d50534a4c0579f2fce3be4380d7ccd794ef6d33134cf362d44bb4d55f249867195d4e7f649f98327ff0f9d738b0673d22324bba419f090fca1c1ac4246318513728a298514cf3d21a550e2abdeac736ef95e74cce10ad9b8d3c5af669eccf38acea5f47ee56a0e9e9b0007537c5a39ca5336e7957b0e690e2d295f72ec45e682a36a3e086c5b906c5ab47861b7f8e9431790555659abacb236edf64b16ed328b762794efa06ec9a17c4a250d57ec6a3377a4406f18467d4ea64dca946972473b05a2348671480ce3359d1935246ac58d46714497a8d1238c6896222a4714cd37bd11d12825b011d7fa68615cdcf17617f292dcd1ec46be7ac98c2c3c545c51be89ba492b4142c4aeb1938c929a67492d7619b5f7188eb919179a6feff1aeb196c6341ad39867340967874fba15335160b843a2ed16fa0951a3fcb442a3d656b9bd57058d0e56be01f7a54d88273f8550c26f7e523183ca8d4aed572f6d5494f8c95ca85cb13d3fa9b8eccc9629b311ee67cfdb273d842dee0c73b8533a187287f4108cc24c157cdfe753fb094218979be7dce19b5f72ac1893d7f8ea3d6cd1acc8e0aa48b971d2538b3946773aad274a29d87d953b5068ee489f762014773a25a1ab0fdfe4ab022f8692e40a3e06278e1ee626cfd46dce394720f3ce3979fa6466669ea9da9c734e19f3b3c83645cd2cb205cda66d8a9979e79c939b99bd847ee639e79c73823f39073377501a340337b77b4f5a666badcdc0cde5327073b90cdcdccabf838b3b3691f5b7d68a2f4c9e2e5772477b9fa81423f9c3321a7496e40689cd6d8ca334b5336528936f2b5c4880e79ccf130aa08a75c85c9f1f19083493f4142da1c126690ca4337cd456be8518d6795be79270cc72cf66bb64261a5cb944463d18e8d8495e71f455cb96ede10aeeb8fa9e4b3ceb382623cf33358571a5b3cf5e8652862c29e5bd52cacb3e7dd638ec993ce69c8543d7d5afa57e49778e59cb021ad488f193e7a7ebf2acee40658a0f44d89c28bd9cc224f5d21604193f994829a5742aa5149a5688b191c04886314553ca1d2da889c0074213f04b8a89999385af587969bbf2e447faf24a93b72f6d579458e95edaacac61b3b2f4d3b18471c0975984954e0a07f1a6879f0e22d98aa05559b2b5081b2ae5669f0bdf27dd02fd741364983174687635fcf41e1f2d0f7a0091616e5c2e9725c9abd857ceb01cad1e36a1354deb2d492f65e022aa25cb2ca916c10647842c238594976f7efaed84d6088df1d3afb50981f1464a1018e24a9297362e423fce97bcc4c5073ab9d07ebae532fb91dafad2b6458dee97b62d4b3ffd9394cd373f3153088a9fe1b4a2c41bd1592857a5765238eea54da8879f938a500e3fa7509297362c4a3fdab76109e3b997362cb79fe1ccc2c5339380c816268ccb8a0d8bd1c442f4e5a50d8b152c51b0341626dc4bdb10670cb166d221d090f9a95434b9a1d602beb40d11d40d41ab2f6d43008d16f5d276250d21befc083e4bdb15a42bb7ff5edaae6cf1d3b332b35d19f3b303f4cad7c3cda47259d2ed7fcf92a2943ba48d08a29736228a7ebae4e0328088a52a495c6fd9050121ca1021990c01228488b0f2d246c4932b4b6f7a692362898d08a4ff5eda8840e3e3ca9501a2d1a8c4cdb555392915a2190100000000f314002020100a86842281502898ca8a2e7d14000d7794407a52194c846910a3300a32c418628801c0006008218648114d1500999781fff6363125a6cf805e0e05eb46da04f7082f61ea02130906dd1f342e4d324f63692564c2a0f49824f08f1ce2f529e28d78cf2d677aa9cd473b50c2026c33facf9d626b830623a289ed9c275aa174112f9245af42e4766f6386da9c63507bd6935951f5e2765185431ce021eee31f777fbc9e675256325b2093d75ed5b497f55dff9c9e51909653cf96418989acffeb9ec73233f9b5959c2c6f120f65e247efbf9a4aff5631d19f9ad2e92bd861d2ed1443d0516918f43eafc9c06f0f66dd2cad77afd954992fa2eb1d6582a1997548f63b54c747077829246202fc94f22859faff35f3a30fa27cb94a83e4ec61c9d7c7443aa217c0069490cd9fed335128f6fdcc3fb4a2e7c893e22748531702a9dcd310fb6ae5e88d2a2bb4f495b13d44111bdbd1366c2c5b33d0185c5bbfbb2fbaecfaea6782130bc3835079ead2e4f1155f0d74cdb7669b7d75d4fa82195d7dc4c49e9fd58914fd7086feb8eb72e4a7dcfa5170d75d647b2ba9457ba35ff444a80c9c58ecf473f91e6e74ebfee7b814d4c240fe8663c3212e66b128d782bf925ae9c84c3d6fdabf5eb9809f5e45ad587848cfefa379c422d7fc68515e388c25971eb8329b2cca8db5dce286bdbbd4635daec806b84865c3a95083282bd59a02d13e11ed4b4a1a1f902de8214abbd70c8a1a03f1d0959c222259e3af76afb9d2d03b06980e66ecaf62d6cc19ac602399065fb2ef490808bb2b0133737b845d93210d7def844da1d066454c91f4d6031d5f53840b4339100dc7c3e5a012f98a56b0f097ee8350404b98d0537761b33a43b4f44c6b524fddc1a5e3e107abcd53041a9619933916012735360630cb8552a5251230b3ad8882246effc9d634c33242fdc529493289a9e82605a18831923559d766e9cbc481b08ee559ce1200a38aea62c9d95483c24d82422587804ab9e45669a1d89056f5efb18936731317e2df753aa6d486b92972abdc770f65d37906d3605b8e545fe61e2a02672823bd06d0192dad65e1bf2a4ca240bdaf86435490e020c720a3393600f16b92d97ee2180b945570dad2befd1a9c354a293fc412d057edd63d1da33d195aabba2ca253f36d05d6b802b203048b9129fd8a4a4fd2df4285cbcca087378533fd0e6aa1a4a5d9aad8b3b23f10d5e3c06cb98c13007abac2a73c8375ae5ba7135302ef6f7e223576af1b48ff6685f4279861f7e3a93c260b1e9213944024574359c104c5029690c7ef6a74bfd804eb7a33be11e86335390a7474db65ac72452d1b95ecf9c02fbe4c2bbf84c537e9fb1071f118b2560085de4a307075b8aca598b6ab057a8aa6e26aeaa1d969a304b0b056eea120384d0595b7844881520dc7332c49165c20d6609fd246b0c512f81361cf2d167a4c83b5d38db1f5164856e470fff35271e29d1fb7c5ab6fd56b7418d8bc9b80f27b7d37c3ff46b67bf56e87eb1755a2d0000aaebad269dd58cc4b9493b030e3266449a1bc7b533ebcba9626f44048379622ddc4a7bb58367ede7183d5d8cc281ec334c65a6529b5fd253f7f79a9d8575128ea232b8b93d6b0227f73474aa3d4f8732266bee7bf27e8722be90c19fcd40987d4d01feac7ae450889d6d45e0e38951e46a6100c4dc24ef18e1730a7e7966b9b1b69867b362aa21104fb005d94202219eba3ddf95a0c21b6393adafaa2d6e1a41f2299691a6cfc501f84eb20c45dbb3566dd89242538e5bbfbae044a879ac05057b236f263a94633cdf9508acd0417589d0c357139a67a9a51e51b1aae51dd223e86b9b3558796366ca7ba87df979e0dc9fa9aa7c1b112f888825e4659a814b6a1352c433fa8663729f2704bc3ec549c483ea079950f08669c5baddf35f6a06f2845140aea958c67eb3a8b1562f28cb7a64747a65777739f28c14a4f5685d6b1bfebfad69e78c3aa6b0ece8d7ae35dd6e9c4c92b16964b2ee36966d5d4e3bd7e88f88596dea313f7b9d2aaa3b538ed2f5aeef5ef9cf603c6dbe546c78f41a0bd7a68048cec6e579e9101e4bd37a8c7a5afb1028f130f1e2df0088a55290cf302c367771a2bb58fa9ab09740462438c524f187a5506ebfc55358d5789a0d3889aeda79bcbf959e9eb8fa10a6f30ce2721f0d2a941e55e5f336bba7821dc725983e2486ebbc4ce8d6caa2ec7d29261aa1ce6d5b43eac36251058229afe4aff14ccfc17545e6f6b49436daa1a5180a0abfcb321baea9eff2400b5847944b0f0592864218e512e8818370eecd4ee6d4124472397ef9fe9eee25634c2329d48921188e2aa8b43cf645f450638678d77954f48f479773bf0caa6840d86e180756c37f21d4a7a8b991de923b8a61580c255f145cdefef7548efd748b803eda5506d569ee80e0157f86c58532ca53f60423856509974270b45b04b31e8844cf8b63d54ddbc7628b6796bc97358886d44e572bae4398ce8d6ffd194c00fd2a2e5639d16847e51202ce6e99e1f2ada9967e6063face75b742cf5fd25698652db811a0ac661226722b48722f50c7612062894e632a22a50548e2a7c975b55f1ad723d24a1d64422798349175005495ce5aa9b921241f7edf287e8e23138cd8d828c03ef8323d4255dd55e022600de2962d8ef38017041818450d5cbd739bdd0c4563f4351ee7540a3e729ab0eb8387d0c9ce74e24fdf947519d23ac0e2c08b3ee1c6680dd5de930c4171dfcd23870ba0a4aca702ed4c5940230ae720f736530cf775195b81d36ed1f7e6021a9aaba772308b71c19a352e57f701ae31425eb7d76990df4482b3a445d76b93a4c143774b961a85b13c2019194a96ec9c8abe5a6756092faf14e9298c4449fb4201b8780f7e3af9128a87559a543f9a0f564998bd043a2dd746f1e564a6d86ef46405972f99f7c1d840172718b0ccd00a534c5cc1562d7bf03bf5b076d17ad9af8c5d2ea596f87f28febe059957fd02cbf677beb08006c35c27a18c7508d03bde4ff1dd70ec920bcb41eb58cfa6388ab184d24e0e9e679ced0bbb826c65af0b4594936fc9cc50f21c2aecf2ef3bac3d4941e73eac5fa724bfc5bc22607b57ccbf362a853320afc4c89b550cda5746192eadae91820b901402c4f680d594408dfe65d84eec9c095242748b1cee17bd07c1931d763e70106695bd9d13cd044d06ff051d52f7133eb5165771c588063c60fd041024f1add45693a23d915088bb69f01069deacfd8c20f05c3b4736f9af716040ad16ca39aacc1741847da22cfa3cc0de04b6e4490ff5c140f0141037678e8634feaee2945877e5263bf408003059672c87a151bf7e8cf81b38c5d28a84304dfccada89f38aa5ed9aeb4882f1a100711e1e22ec9c822474b962c4e834fa21ef2bd85b613b927ca15da266d29a84054729c3b43c58a984c43e6366a0e17aeec7af0d96e460ebd76a53175aa1812542f343183fc5812e701d9acf59f1dc44417cb9cdbe0d9e8dcae7032a52dcc549977b60ae082c30ba20cfeed6f7e020b360cc12748bbd1a2abf23d5c31be61acb142f33193bb93a0afcca25bf061f50037740a06e0d0504d6cc84aa78c8e89bac29f90628684ea51ef2ccd20026ffefea14a133e444c39b2a98761e8785b2e068483dfbd0a1e72e0d4abc010cdc85e29e94d404e437dc4cdd2e0702b0f6f8f0306897324c8d921d49e6eff040c1ed73ecc6b47260ac902681a18b2b6c338f951fb7599ba9d3ebeae9ba6414da5328a558fb3f67a883fe991f7a7b0cf7ccb8dac09b5d67ddf0d3755950efeb33d288f09b480ea90d0878cfdbd0c87ba7b848341b7cd658e244fc743440d13e6a8e06593e698e884b84a89e176f1f512fa82985260d9a26080ab0000c468b596c43a2f28a98ddc31b3fd4266f8dfda766988627ea80eaab925b3ecaba6d3525b88cf790278d570f4706de42bcb10ecfae944e0404795071e0b747cbffc13788df8746b23b2238ae5de88e5a5ce119ffdbd854c2a44f014435c2d42e8704103e140f67809edee09226a844aba11e5b22c94de778e4c85842e413c0d9d10848d1679e3f5504b60b87ba8bedbe832920b42e9250df83dcfc1e74dd4fa1950ffacf3f3621315cef02de0521e0ef77a188d6839f18304d680ab135d855ba3f13f3908033dc824d8a5368c37700868ba0e4a4dede7d43e1f7d980c2cef7e1f23a672b1628b51be83ea355adc8a29ae8ac39273dcd582c7121b4d2d48511cca7ad78237f1db7781f3b0097125e4d14d6020eb0cd1c7549292577cd2dc3e2140c453ea1399231d0cacccf6db1e849224d47becd5b4f7957068be40d8d4bb6436a43efc40a8b950b7c85c84d99cd271144816ce35e79c3397b60f08c3e45b3fca67900b15ca98616966b910a211f228812c4186d659803f97609dbb0177c40ef49f29cecd091e83aab9985513dbb2258670c6578a76b0993dfa6daaae3fbaee58e60ab1dac8d23ed12d327d27280b1c006772db9c4c5fb4b20567c2385546f07e28ffc31ec7b93385a958a101edfc4c6b8cda0841ea54191923924ece4819837d2ec2b81f6a3eedaed4ab6d4489b1891b7bd0a0e8a64d34db0cc555dcc703ba783421fcd78ea278963aabc176d1d13238e8a032362a7a305e190157a1eafd31ca078328cff87454f9244540a8d9864211271523fa351104defb4c253087df0c5f8a5f16e9213c67966f958657d9874ff50f91cc3770f2e16813d438bf19448076a1a7c996e89757b642f1fcbcf878023db2598518161e85569c3ee68c46a0e00173be66bce115a6aa2ec2c0c36379617f67a3b1958d214dd573058f9057760a60b34f6f9538c5229098da0374b8a22036d86b7bcb71ff014ec27801f16ce29dc43a110f6c00218f43068579b33509502d164bcac4d5d9547cae3b898b01bf4fbc0b4f24e10fe7990abbbd9d05203814d353796dc51301fd1b66b02fce6fc46b4305685b7ea91901e472fca76ad607d5cdb76a8d2287f2fd25e3d93e400a8f31dccbffc9be7d7294afe1c66e0669ff2933db99f40bd3499ed4d9752e8be57f893b9e353cb79abcd897066cb55c9470c88ea12b6746e762b316363f5f594c298d41d441dc3b98037dabf6932564dec306d9750d00c743758b7c85836b5f8b02a07b8fc8e5774327190bcbc23f4a6a070ac5b1c884382852c10d5f0b156d8be34c49def92da2600ee63d053f6842fb4470252e21f7b971342a6f7c25ad41dc1b521783143e63fdc872ed087c156b0d5a0a1cb46e8fdb056256f9f6c3a9a406b02fce18cbf572527875e57dda93c39efe6b490d74c6c2ab836b2d2bc41fc1e352c923fe091422793d784b19d27a5acc8f1d01c8dd549c2e1e44021dd2b32591b0d75e720a0ec9b7c8631a045fe65a33fbe2b2fd3846063554f47aab51d71aba5f350d96a1aaedaa7f9f0b16c47b26a41c22546283edd8700685250314044cf62e1174b30bc2b269ed0f1a64c7c931f1d61ab1fc1960520cdecd93ffad9323212c3811adac07646313afb2e6da3ef687c5fb919b7ec2dca190dda055e6ae1558190463bdb028ad98b250ac80f930475de29b8632da8e642b330c0c41bccd0d629f35686e7a333fc0e6b862936c964bf4e0db5918b06e9cea86d9b63b22fc26bf8e415088bdd57671b4c58b6c91ba71a9751c6bd1c16a01827319326e17f886c23b2fec70b650035c3a98af57f22c24ffd0125381fc46559248212bf6cbd2eecd21518950737ef46983ce248d58807bb8519a19d066b656e9fb3a9047f770c7207ce52c7dff33a96f0e6ce0c45698e71af11a5fa842a7f6b5be6ae5059697a15338c2d869f43c011543ace6042e5f295e79946946e62597991c5c4150d67b62ba4c792526330f7b32162129f3e34af06056c48081fcb92c437aa89dafd930887ef732dfa5fac344920ce0d13ece18fb04a2dfa1c61b9b48fc0399716808854196b6a4d14065d58cb670a51b4e3c323884339d47364b2f708b81dd640fc3d9245c5ba3528eb01f5e81f5803b7819bd05d0fd54b0af3fa1e1eb356905bed5427b85b524f235273714d540bc36f4fbaf264f8f2e8a664992c496a9e680a01c5c4e1401a4f72ad29c65932d148119fddd1550f9da517ce38c44ca8e186f9d7f2094c5c1abcf6fea7621680f01e730d481bcc1e97c8196ccee756233b7a99157292b4c361eea50c4855bc298734069d58da4f140b6cf885369c9c0779cc24725062ef4712b45381f933c2641669568351f298364883ba4dea871426359f5440b72802a3367b96347758ea7494d1f9db3741572c28090bc258994b4a0bf64f1f0edaa060bd1b5d6388713011b52085581e77e7dc4fae80686165a7b7e88bbcba284583e5d1f05718e4c0960104c1478fd1f4ccab0ce0bfc765b5c6631e2e95cded4d50f8f570e6911600e8f61976b4c9d3e877079c905a6a3d7870f74072ed2946a404ffaf2c86148590e9c00ad4c303c4232751c8da3acbdaf53449ac9308221d2108a4c3bf8433494f27dbdff5e048b3640f40fd6c78fbc8edb3d97086faa0ac13b7d08c6782ffcbbccb1f1ac93f13396cfb87ff79f9efaea2ffdc3f69bda8bc07c8f59e3f8c06cb764768f0e73f9e69bbaf545b0bf7805fc7f16a3db5d802dfeae443a256b1fb0aa50fa7a60a023a2170166f14a6d266f77b495076bd47c5efc73820e3cdf2033037b64f7ad382f7f3dc93b4d8dcda2e8ce0c2b37e3a684b0759cd5e55b15ce950c106dfc753e715cd8c6e6c27a28dac0ca99e8b0ab872326ac358f2b4bfe15e854afcc04239b50c84f0cc37d419ab7564328696733d49f7727eb5f2ce30c870c0db998f76e3c12835346e452596599afb527bb3de9e6c01a776cdab88230cb2eed0c02b66826b77f32c188e578059e852944f0b7b07c1233302985662d3c382906226d1036685ba361fee474054c9370090bc5cfd133112516727192704317978005e8eff7bd2945ef6c107d91804fea1bb94aabb6265b6f9b602b83834639fdba1f1a66ff00ab58e415de65a9b70971aa6da7811c1fdd60c8385da0703e800914b43902edd866af543fce1f71d02ddc36cec590c3fa30adc80837a670946b930654c6990776c230432f10fea54e314bfcac5782335fc326abcc4027dd528694a94c6fce75a22a1ed1c4cd43bfeb5b17f6bf0b66253b36b7a91415750c187eea6bc8449ec923788b5eb3ddbd1cecc8cad6050b9e4a76d894c009c9609256a29e49fa460a62b8127b6bab33b3008f12f66cf4c21446dbe427d75333c80778b8b9ce954da32b0333644a958350babafdbabf7a2200c22f530d3dc891795c523a9c95bd48cf934ebcee89508d2f9d0eb26ba2087847f1d9b220feef04d29a1bdfad65261dbdcc38cdef44d85390e5e1d7adf260e6f8c5c63b07cf0eb3b6532d18c2a608bbdf33173b114eb609e0e8502d464a29d8cc01098f19aaa33d43ce10fc53df8d0239d225d433703efc258007bbb9450bf49dfafe22edfc479d28993a1df6ed29a545ccde108a632da823e016133b557dcc84e6a726687514e9b017b58bb0f8490084b94f43995125dd21c45b54a6f4286b275ba41dfeaaac150a728108c5690aea622807a21ae7932268a71552b35b5b563d72b0248580afc823a3810bf8eb2d268ae1536085db0c409822c257092027f200a72f61cf655b89385da30f5c8cb109e68983e452a84db013a606f736584bae2322427cb9eb33fb3829396ac4cc6f6f31a204eb2c9b68f15a2a07f34a228c80332b0906d675e88354c2919aef8c5f107ef83c40b4bed843eeea9d4349f9a9cbf4b6b699633c046dc9c3183f4abc447add74c5922fd2f8354ff5385792c687ce366b1740b6536640d2b84c4176e72232658033b1638c0bfca1f3a67f6c0a393c44fd93ddd4ae80553f12b25f3fbb126994776243016da910b4bdbd6f5ce5197031e287324585599b642103af14cdb0c1d66ad2c14ad17b138a8afc9a8492743f6a49a611a7100079bc06cb7642cfe2c8e24f572c172ce410f1261ad151e9523ee367290d5714b5c6618a1cab6c09ced64a0a7981a26b0ea181f0d2703046031c4c4029524af659d462a7ae4dfc1388b0d8248a24c3532a8a1486d416cb9949ea6f5e043b7f66703f2aeb9d85dda03fdc48329232f05bdc710392de64e82e0370480004b89a07fe523756f5d1ea180773123c92250ea2548b1c9c8f3e9bc01aea7575bed02a8d1990630f0fd5688876f6fc4231120ab9064ad0c263a1b366238e9a27989389ed087a653ececdda4e70e4d0878f12e280c11e29a4acb691b82bffe14df19fef18bb702f7abdddb237bf42ab670071bbf39d313bdf39c101e955079ef603f6654fe2043de76fb4c5023cd4388c41c45ba41162ba9a45e23550d29a2ff8809d98fdbfcb6bc23b8b4bfe0d944c13c51735756ae3a933164709348be8714c712c6d807b028ed4b48312ae91b98e386a7fa0dd8b5795a216cdc6e98f95c8d113c8cfc71ad4d653ae278177c39910187eb8139d9153c1cd56c38dc913715caa21ecf6dffb26504df10745299b6a5ab46a55d8c0cd5554eb4ee3a0e4bb494a68b407dbb23ae7a23c681f2fa71b002cae0251eb7549f7f84dd7903507abe77cae8351dfffb5a7092db274ae573c8b8092fca9eebb48935ec0a1a152fcc716afb23483a7af1e4fec1131bf420ac693e341d236e001a4c19342b10e3a0a2812e75d575dd6d92f4163940c953ecd6b8b9a918e7485e17366ab015ca65eceb4bcb5612b7682be44c620801c894db5b333cb12658ca75c721919fded5b9c1de2fb758e901956cb29201c901f7241551d87d53a6a6004c94966899f3f028d170fda5b4c001990ba26c814207972481b50d9144612dfa4f2ddae8b1d4db950e80caecd06373ad75d9ce0b9733d7cf81e6f8f2c4dad2f5adea9b98d30c99ddb8977b5a05ba9c9a71c13a17c088189473edc9011e73c6f130b14267f617e458a71c7a02d214bee65a173e5dacf700bde0d7456ececc79c6be847114b076286243b37afb283dde53a6ce9897158229f8047f6627a2bec2d0a2277bdb87c7272e731e9ab12a9aaaf2219a7546d1ba9811b2aefed336dd34de274070f5951898a4f0dd769b96ef177848703a8695359da0830cd8fc19381a880fb1fdb135fd4101ea0e1296c38c52db427f96405c9b1818e4a59cb217b247bed9cc40578f281262238e40b37930d1663dd51c7ac86eefc9e12ad6277b8512d952fea7ee450d92ae339eec48081e743a184c638b6601878212a07942c0ce0895344af91f36b5dd4c8a8d36967d0a09df6ef3602014e7f301fbe84396243227bf7188aa2a9c26306d75d8b641ada1a39d070735f6c8732b53a6dced0a4794f1c808df1554b786196ce792bc60e8b6fc2544c5237551adc60e0056da5c356fac62684d8ea3158dcf78e26b82fb425498bfda6b509abea485994050664e7059dcd04ce5976d0db28839e82896d99419e60926ec730aebcd1222f27407ceb49bcf692be4ae25e711d5459bd22729440a301d70450d7d80b2aa92a2a922d50c2911532a8ad85167073bbf3ee7d887b0f809433e876483436972fc32e0c91ed5e775811a67c733c0d95e288f9d2fad0b4425961a642fd42354b5df11436182a4636a47fa52c42c838155b26bf78240f43c43e128682ecbd4dcb42259a5a2b0a322ee0666602d34c246d8f900dcd9cbe880fa7c2781f769b1ba76e8de16b3fd2a69cfa704582e03aca8f7104b918ef29bd5b0d52de34ca83d2b18c9cc95cbafedda48024c41e1c9c432b89d14bee451711d516a9892a7343478764ed64a281ad5718330b5f31bfdf015e11ce84fc70c04cca0611a078bc2aeae3e62455835735c886539ca1f51bde2bb1b0c0746be8b5b9beea72b57df228a2fa52b8a381936968c040e5f1370c9c712f52020525b49b3f0ff8b62e5f19a1dc1f11a32d3d2221ad5eb490387dae7825a3f90dbd701d6a347b34dc3d85eb02204b5bc27f88387dfd4f79e86e7d8208115dd3de9b94727be2a619b1cd248296561d82f8a5463bafc1c9a4a366004b985b2d1cac1ec09e7345d93b0808203c18948473e54065c6b56d2b41278f801c3f1e176e942654986be4d29c9502305e847a15c7218d1ea7416833088b5c5dcfb91ef237f610a254e3eb576809ec515cfd7cd40cbfe04b21e43e523e096d4daa4cac6673591c11188152ce82405184e0a4ab26d89cd88b1f9c16ef207fe2cb8037185b5747d1c61b2893bc9fd9af746247a8450e3c3e57e1977329f4c648d0c8dcc988d3e978424d804423d298f591e5e11cb3688c96edcf07f79700e3d4f2b9ba1f2a3e714a09eb38af5a69e18e31de20c5c8579b5c33d1d7207c2957e9cc0d9eac8028898a4c0a7cd5318a1687b6633751943f7be648b41b7e9755ff2adb4e1b06035c78128fba018d5ae94f1294f1f2ac364886b4aad77a7001153f68359953cf9b78d58220e20a76648f6da1455f71aa53178ab97aae4eb3e8df24180553dad93ccee3bc4a7541fb1dc57f23424f2e804d59ba6fd8c2724e85d21d9a87b0974115f5192b7ec0d905de6bd50c83e2476fc8cfebb43d0e5d4402b9070ff2237ffc3f86a038fbf9c242f46cb61fd8050d3fcaec622afeac2c38e66cdcb39f80563ab51f67c380b6e355c86aaebac0172160c4f00f31d1e0f01add7017477ac78d5eba9075cbe6a98b4a19f03aae0955be6f0aeb21339584346d17b4f98c04ce6003cce100bdd6eb1336c1aaba7ea25b9f048e4fe5ef0fe13ef0f4443143eb1c6f873cb32c5429572b4f5e5b4f24756a4eeae51f82a2b2c0195896ecee68d54d0005e62f0869c24b3751a2fac18e4eac5f7ae69eff207c04f30793012184536070c9acbf498bb4617035f68abb7153a99170af5aeab7184d14b461f807407d9d8d93e36c80d866437ff4ac723669c41cbff9c9f2ffd63c4ea036167509b79f50d316dd62f0c08920175e3df1c86009eae810156d4577f24475eb730cbf43e68282c856cf89000d084d9c14217a46f114e9e36f4ad4ee48e1bbdaa0c04343450a830083a636ddcffe30c91831f05a9198fa5d8582401ac1f46f8489ecd1c64ac06f54b5cf7ec19121f0682fd7f09380f071294fd12af06d8b187d864949332afbcec609f103498cf5c505de0fcd6342bc5121063359d1236b5f5a7857bb228e21131199f02a303453e7f438e0e7408168403c34340816077d713cc608b00ddc908707e2b0d74c2883dc694b8a8a3dc8739f5e532b211970b8f19b85e740f0034142b03a5a80652290362f6ee91719230e6f812847acd3291f03dfc309e609931df0baf754546ffc4826fced50f22ba8c49f6a2a411fb36f01e10d3d097627c12c4a355eaf598da6cdbcacc71442e111c8e91d07c64c506af11078a576ac808f2b28ef21db216fee52dbb0f75cffa3f08d53fb347cb809c37699813b6f395b555bc19c0f99ea0c50a653672c98e30a0f5f28b3ac27de04121b6b8ce2183d4454f4d0631a95f1f0484c1baeddc6320f400f95410aa12d36c302010b75129516b96b8473533e8d2b157e0d28d1c04a13e2e984d22be5406921dcbab782ed286f18f4118fe8ffb5d186b1c5afbf99f05ff75811ea7e6f2592e882dbab6024eb182e143fe406d7cc0c78818872806aeeb40ff6cd97636eb1d421f4785f5642b33c1e32c86024f556c3bbde9c8648a7fcc77a55c3b440c10db2f929bc1342c5457e0ab7408302292f0ef60e85d541ad3f9c18e4de440200df44069610e8fc04436deb94c8277d2164135721223020f278a3e383700053d8d1b788d917c2663fab57848370fd95460dbaa626bd393185dcc7fbbd84a2a239d8b90891d5977b825ba0c21ef9978219c6bf67a4c7279090262a27ec9f7338c66578369db009959947d69e6316c0fe2bfd107c79379762ba3bdfca653f64b52c4fc5c2fa3efdf9bce9c090cfd02f24af3f5641626adf6341f51678197d4afd578d339eb41f04dfb20915fd62258757ca367e957caeec23863b960609c0499a67fe76f651e8c990a8179c7b4ec30e86e6d7b5c40882c3fab28fc6a480518d17797de4bd1be050aa63e115fc404a9425960ffc74c82dcfb38de69271778d8463b471ea68d6551de791354d9c703014cc9eac0f7c84596ad856c380f4dc53ba58b6680c2d001fb2f6b7be2d9fd30edf8c1d1720b02b062de9896c3295e082d7348f050d51bedc14b96401c258ae235370efdd520a29c4dcb3a4ba4a408419e3d5d0239f6be64b0c2fbea09cec02af9b87665d3eb3e5a11b09b7e2b731608a88ba510eac3b4553ccc1eb85de6d2bbcfcc444f00428178d6ec8aecc39cbfa22caf43320d8d8a2864a13ada09c38c1fe627e00bd3a1e80b02b04691946cd05fdb4d07815f46e6008113c00383aeca5fe5358bd2b10b950b6f2c14e26b22c9d780f7c58a3b035651c23c10eae0849210ab72495910799d900d9e78e7efc31287ef04d3007a4bb53a0840bdde2279d4ca961a064086f251d3fefb747677db81343de0bef0e465899a22aa68d1a5e648a2d43d8a5bb95a38f0b6a5ae99c80bb91dacb96237955ca0255c8e075314af2c2eb38e9dba6fe644ec819c14c7a9d373942e3cd7234fef6eb62ac7110d22e1c2a57f14f5c377821e859d2a6c8323b38a87a8ba5dd166777ef9d192fbc950c749b1d1a5be263a05ee8fb0b0caecfd4a3856108dfdd0d788074006f244e8a2ea2e19c51405fca6b7b3daf00c6bcb904ba589e39d0d5ca2a7a40926f0d5eadad7c289b012ca0977ee07d6b51ae603dbb047b6534a193c349b3d2a497143a685e45affe201e16977f91a2fa62dce471653dc1894966cb6245ac32dd171217d9918f72a9282c097e0e545c452e387501c1ce77614c9f0f63855ba8d7206658d5a0968b6dd312e5d80ee09b4fd5f673e792b566e6b98da939a56244d76d4cf98b028f3db3ce1210e4af1f4688afab4407651e19ef75fdd4bd2e8eefc4d5635fa1dd3f8aee6d608502db1e63b7499d9dd14b9730fdea08b565f4d9781bc7ab8c1b50d08fd6088d705a5d8261d72eff540fe6a675751cc9272055171186003c629a9921ccaa7dd0de7e092a2da4bc741b84412833c90ff149bdd44338d9172e1a5d6e1853622bc6fc5072936d66140343dff850f54391885c98cfe464122d3b20dff3aa130ee12265f5360a1aaaa9704de1d276074153a643a9f03161c062d9af66c64f8d2696429e5e14fad23fdf8c116a7dc183cba400626c1b9e089b980fc02208351e60b95981b677bb203c9be0fa2128b5b260b5e7905bfba1221990904d7432d6913a22bfc861899a515dacb8b3e6327c9f89eea2ba2efff70aa47e9e62644b4247484955c05369b3ed395248bae8aca13555efa148b24e35af0058542386bb2d76eada065970e01c49f932f1ec4e2ac61459e8f2f8a4f715380dff02658e807a647ce1875b2b5e0b6d8453b3e7fc828b3d2f9cc9582e2ba288eb5f7e6a72d225c30a46bc72461cc440f33ba4afe69c130141087389a4d8c263461e4b2a5995f5b5993d6362e431f09d7e3b315ad6951098b0c8454f8aca82120497ad786605208a68c86fc1c40b89aea6b54d98168686ebbddae8ea27fc00c099f22b0c44748d6ac7ccb58f43811621e6a0a85b0aa2374226607961453687952c88d8b70a9163515a0bc2e213b72def7a94cd152b6170db16dc5b6b5948d151763af4ba4acb4dbe84da5cbe71b37cdf7bdcf371048e42ef4b07a72083e06e3924d0835c16cd717632f13f92db485b6443892fe598c9b8dbe1ddde4cd82364d9f35ed9dade2b99407677a42fd8231a6fa35e1e447a31c04aae501e3ca37415ba441541168859b9ace0c5811571eb439443186ab684341e0bcf8ad4333ab9900a43c972b20f4da6de1f3af86de17075f676e65ac0528ae9dfff4bbf8e10e50b2d6e1276508cdaad49cdb8d1281f0c0c1f4f396fb714d3f5f547649e516fe6ab86853781ce2e7ae05d2a4100f76054dc1c22f814063e786ccd7d910ce3bf203458ead67bb99e2c4ee7ae14c8ec2b9bc45865f8a08c3de253ebf02e709cf852be00c7a4074346de7063b7dc60ed09c0082fa7eb6c0e2884addaacde7b9582340f1cf815e2646f8f6745bfb4070a82973bedbc4834f37c51fa2b8381a3b401057433bdf4019840962a96a1d77b3721b02c900dbe572f0e83ef60ba5dd5ee461b3256ad719b0b9790ac8fdb64253d47c11bb893f7698386baf440674c060fc11f870c048c5c16c7c2f28624f6f27e2b3d4c00507d536f079c9d6572726f3064378fe89100d51fa779cc28db7b3ee8c6cfc0192fe0716c888b46637992920be8ba9ffa259866d27de3fb9f8f237866a6f03545474ace6fae82ce727ace2bbd219ffa5cbbdcd44d9380675a26e8a9ff1ef993dcc3bf5e19e0c5c53e64514caa7f396a2efa84b3710a9febf611b14cdd41bf63eb79ea3121b2bc6c3633a8eff82c4f7da1b4d6a020c5af387a9d2f00541afd11e652ac432b59409f45b8c2b38e466e0a33f644111b64235b661d109941d8fc9017d565c0f3bd80ba722969441b9bb218dbf6ba37364605ae5b8438f11e49de68a2cfae6c83c1364ce12b8ffe1e47f4453eb1d4533938ba72a9e8e0183bf0785efbf748eb2aaefb343119cbf6fb5c27f32fbf592a31ae67dd2205e6d926e30911bfa1bba6cb22c44aff642d9ed1d636959b87f8ab6bf10e12b2f6eb45b015775aba84eec7c56e3d7deb2eb689f51cfce2f6c8ee77c4a1298ef8eb653f4dfa778c6cf7f50d51fbc92aa84658b97e7218f4977508a100fcfaee4834d46bc03b61f1e08df544418df31ed10fd5cbd621af76fef119d7a8887a1640b0825de028c295c24ce57a890cfb1f233d0ff9afef1958bf7253c830e9378921d911b76ee0f34bc6b7eae6c437a228a4c95fc513d83fc8b343c607f79d75ec73fdf30fb091c2baee1bebce0a6efc6354d2f4436a7c7492fd5504350fba0e3a52a0ae529f0fde1710786ba5730b565f5fa302dc35c578add5291c3b9ac91789731f26b0525aa3a752b845babcbcafc98cb9da6139009e572aaa06720e0728530be8996fa1880eec58b3c405f3aa97e8d54640e37dd036c999d42d7d4d5e76888b195b431086379f9763138e02501557722edb0501327805ed71c2c4df2cf229ccc12d9aba860d79cbf14210c18063967443257d3badfcaae2ce5aaf2f944b822fb28e5a949d66e1cede5d99bb461875c847c928b8bb488f74b2d517f2283a595017b10eaf1272460c27af9e7f4d4d05ae057d84028a7f22115df635ed374fff6d56f5bd08ebb7df676fd920511721258e657dc366e76887141cd0c974674d97a85866de529c12dd7daf8a5b9773c4f7003f98ca88e8810cc2b55155062c0342b7e7afdeb3faa968df8b98a130e82b30b647251ce5af79a05acef2d46f807a2da070d06372e535c329172f1d4d90cee2f825e97bde04852247938488bfb3d6726b58072b76efea2ed8aac45bf617f520b7a3086f947a9771ea910fdecc92b8874a6539227e0a2a3df1317b919b02880815cf4663b47d5c5840a84ee92c7cfde56d3524529a8dbe392bbc5e855f31e107a117918f9412ab8de8d337af180bd6a4bfa82c38f58d8eb46553b202c035fa841dc94c4e7762f9bfb011f7fe2003cb2cc7bfbcce14182b1cfb3ac393f9fba5d5ac27e6d5f80e96f4bfd73899fe17045dfcba447c04de369969462bb4a56acaef3bfe3ff7d4e1f7feb223d6a940912b84f010c08000282dcf20b5f5a99c3c00e938d4146c1da8c35120488db8201ce5ab949437fb46e4049ec774f9a5c08b8b76bc87c087ee04374cb6ff76a1b00fb3e3468b41c4a3e3792a7e5d3133e0fc599fb6030e465f54e7098c371cef82b1a9529394412988b0fc96826f614a1e95e7967e26982c310523d4f2f122a027c24715db5423659d67f692710866f6a4953e2dd90a63925e075f79ef10c277961695b0db64c2098ac397d7c45f83840268e98d00af2c3971b5e98662ad3c525e5d784bfd9ddccfdbb3c2faae25e77a727089fe37f2d5380f3df0cdded4f8b6f182dc8855b39bd20da32737f0fd4085ca6d7c6ff8428eb9cba943cbe801b32953832993ea19ccd97b34a6da33181ca69b2edb606febd118dd914c6f858e69138a37781a17188aaf52631d6c567d77484a9aa8cc284006a7e32ad1694ba9d8902742682da80e97b78262b2a0940e77f7bdd2d5582c95c5b1a8b6d540b11029e071669319eb7e377a8badd8a93e29f032d4a1450a8f8331d97770975c589617141bb00b64ae36f50aed6574d7efcd9f6ea18c0eb22d4101510c962a7406f3a743cf9dc3b28a8a409b2394658a6a8ce087aaa6d2135a711f1f5f906f99f8a59b9491dd3ebf41ada652e9eda9dc8ae0b7a790f473cd92ccfa1bc0a2593bca6f5e9d82cb77aa026b8bd0e7fdecfe6e7a8362a9bcc6481e7c7c06dd2b1b979fb2e688edfa2d669adaf2694b345d1c0420f63efe39a14f760bad0835f7628c36677ba6973e4a3d8ec4b6d113995e9a51c62d1e80a15cb451cdcc39b19da32cd86090b45fda6164280d7d8fb2c0af89f21e654789c5e78e53fab7ae8c53b30f6a7dcd602b9161c1a12dd45b613978ed600b5dd792338962d8cd88e450c0e87c0330473da7cd227436fa3f9143a7f6d8dae3949daa004aa34ab6788ad11e05beb5566cf4167e4c094c380998861a7d664bc448022c6120e30d600950bf328e8d717ffe885410932d0c73b537baea0969699d27a88c412c7a0565fbc2dbe56b21d1e9988a044cb631375646bfeb1a29a60a6dd3f3c0d4a5970aed62bea6efc993e26ed854398e75d7e7e44d6518f556258fd1ac76a44481162245dbe53c759392edc86dccd4445e68b122aa487e316cec0ac23dcbb85fc12986d49368fe67385ab00958088ae122df96591d7ca6eeda8defb262bc446a08167d552c430b1a007c7cee3632dfe7e6c6d223379b03aca3a4eda4630635e13ebf85576200a6cc0778a6cbb4b17921ff8a88209d3ec4a106268715f463dedb313ee0c04d078a807a07478559b7778e35b57ad1af35db95f87b1f45b61eb5e0ef3d16b9b8a815ad42fc652fe0de463cb7a4a5cc43c6d2c0784550c138d5e3970fda12c966dff9c7f3bc9469d7b9e102ad52db75b2664f376ffe2e9da7127befc91ce78e31d0553e81fe5bc4aff6ea82d83e173b3c8c92e41b4ecf468098a50c8aac7bbef8a1f50c728e82d96a03edae0c1cfc77e9df7986a3494c7297b352d415955548c4f214afe114bdd931758318a3478291276102688c6582244d531cb06126239d20c137b2d90ab1a440241fab6a690400a8a6ea80113b2489e68dd0f547b324a05bdcbf8a192dcd57722b71da40867ce4f8834579f0ce8a870f6a6b4a74fe5c227e1ca42b3ae220577098344321a59ff0b1c6bb80f9992d6555c9480be681dd0bfee3e942932bc71f1de77ef65917de1da89b84299c53a2f07c40cb61527cbc1f42d34565d08dca3a75d50a9ee215e0502ed2dc20c7c54fac1821ae3a8d6f959cd74f260266d05f43b77da84d41b6ba5841810ea97829386e8011825130aa6075a5c97910ed9bc9c11cdc46d821a51ee0bfea86d8317d9df110553a0b822e2d78f056e293ee63ae779d9bd4b0d6c210073a473fb7d1800feeceb3553c8f24bfa9fd96562dfc34f7f9bb27644ffdb51cd2ae8d92a5408ecbf0eb6af4661b351d0a764bf11da75886e49c2f21958da5143b1df7fa13077434ae545a0fc8822fc07f75d403a21083b84bdf2817b5582f8f13cc7d14f853878cdada202487c6bc5869e722cb5e37591a3201e96fa4337d28e0cd0289c2409b387e1ed92347d09085cd9bbab97ab650cd9c0270a1b6b3efcf306d41089d853b8aea0003111cf72e9a7a7e9d95d7a8138517d6730e740d9644d5f7c7ec25488d71709f99d3305a316cfa69c831704af9e81fd1330588f53fff3dd32e566e47abab17b0a86bffaf6f4ce3a6e2a953efa89dc6c02d5850637aa9ae8225f3723b8bbdcec05b2966d4ff5c5162951b4da24454663489d253f9682254926581703139c84845225172672abe99f8e654f91118eea1065a5ebd86c1eeaa777948dfb387a99af16d789501d4c9f4f0b3857d4ef75f8e5b78c485500a01f73bc9e88f4b96bb25a108fb8424a49ad2e810f2eecef8683ab1f0a8873abdf2d242afac87598931daa75d3d7fe709ac056110a053d057af375714322c5dfd8099a0e9e79f20deb5c00a1fc22f7048089929563cb1d4ff555507249da7e9c303bd312d0e121cdab4db63a5359b2586649ff13196b3b23257b7a6411f58fcb562688c698391ed25649b43963d0863108a2cbab54f57a1f79b88616df9ea35aadf0bdaaad50dbe66383595e9999b20c898ea9aaab5127309bf1e7e96c7a97cd05d35285a49132ce66e8280407381b47141064c69c4fd1b083ae18381d361275533a103446dde681717b5ebbd5c150c833321ee8b2199a926cdc4209ab945ac0b922ffd8de3bfaddda5f2969af2d3f90354a5720664319d32e7b6a35ad6926bde4ae1528528e9f0f599160b0e4ca89f96e6a840beec7dcb11e57b3fd5d906659e7370ff860aad6ba1c4a6d690b6ba836cc744d0ade3bdd7059a12576f6b8ace74c0407104a353a9be98a8b8a48e9836b19de4a07c75758a0c73b03046921b55ad532a6df5b8f3041c16ed7f4a5f70fad5aab558d8f3f7af43617873703cf9d9854ff297259a9836a846be54b40d5f8c187d1074d0a7855c347d18b0655b27949330284c9c6ef984041b029cdd0eb91ed1855276272d953672f9cb8f17424c401377c622a4cace652ceba36d2ee81a6414e3927456fa84151ffd5f026f7137b92f886c458d79d4f3f72b7ccff155c15347e795859117c22cd44e705d20b128d85ce83648c38098433c45be2de2e4eaf644d4b5b067838fe57ff69dfce51dbf4360c19e90e5ceec6c044e38db0c0dc8e9b01ce4f0bfc5c9a3addf5f1047d2fd0b4495f1126c6990b865e759937643a35b5161216ef9d45f5533a4565c3212548948692fae0c827d175d7a488042a9921865f1f494224eaea5fa69e949a9ac23ddb743e230e93d61768d6ef062f5a6158ed9775336ac3ad2c6333aa21073f6446ed38b0599685200fb0db663666753402adbf057c570daad8c8550f9673eaae104b0306625d2782409cad601f7b74e0a8f9cb964829022c781a1bee49317483c8e2acb6fff67bf21d0b32612707c2cf4898035a94e8a14fba5c52fae18ea91386058ef7ad20513ec7061feebc49f9f2387cc45d9afbe320ab896fac66bf63b06d09e087ce6c59a2bb8bec9458f13405dd8c936d36bd7c774470e93441b1771e459c0d1a9eecfebe320f82c7c1bd65744ba9143c0c5a47c29e0cd9eca2ebe19372a323303a23c8b3b79305a34e4c1a0b5a40f3f8009eb16ba734a70131d1c5f6f4a06946d4a22c81b93799dea41a21afa47db29f30d7c430894b63cb3f8da20d2ad5f31287fdbc88cec245f7325796f520a09bd249277e5e276638c9eaaa1611876a7c30656618077a728a8b8a52c959bb5c363184078072bc1d17a8f592cc4046ea1a3fb2eba9564cd3928f76122d5b9534ef26e82f4e549f5560093daea4eb89b9d04f668f038563500ced419b3957d3ca5b24aacda8a0e7c57abbf11be2b22b5f39845824715b014e388ce9f6f2cd3eb75fa73fc5d4d3649f28122d6565ea21a1ccb272e1d72345c3a0f155697df4bc310e9c43c60d1742f13d6fbc1af0f0d6e438efd1b3b12a196068de9f27f849a395c75d838d75a26ff5eb4f1e09871da8164d3ec3dbe05f77843e8a1c03be4409e0e0b7af31b2a88576a3f16851c5ad18ffa381563249c18ee0a800a2d90ab15e1b9ba02d071f2cdeee6b4d67f3e877705f4057ac983dd3a40eb21040475e721295eb4021fe755617e1b2be9cd18898dc6f64cda94a0f75481e8d2792434a0fd10d1ea5c35341a4dd5d7a0dbb0a71bbbad7aceb2ff42778ca616f6c112f5ba83f4714e75393c2862233ee5a0ca4c2f8f06a5a429108db35a8230a952e451e358a238ce1aae9af258c4f50e2c1f1cf48a98472cd241d29ef248c16653372f1347104d7ea4c46ebc191a4e53606fbdce714275b76d890efd978b0b5ccc03de7fb7acd6fe1e11fa60a6060c936fa1ceb54ecfda0a975782807cc199c8a99b7985e5e50c165a61738cd7dcb8ccff3085ceff16a84ac4e41fd3c89fd1c58e65d078cb9b32f8aa8e48cda0d77068b9ebd150b7b657ff3297c06d8a7a2dddbd17e420cd82282da3386e9b6adc9018de5d7c68cb5696b1275d440986904a30a4928ccc0dfa11d88725cee739a127019e81f98cc81e76f602c551a0d6e8cf63918948a878659f8ede22ca38ce8c393011f558533df3412cf575cd7cf07bb8acc0d07160c21ed9df6926ced0ee0d334ce3dc29c130bf676f5205a08f970262d3804a8da5374301d53aa53b9a328208e5ddaf7e4c73a588a73e796783bb3ac618e4523d1821288ae2a28b4482d87dfeb1d7f0735424930763c3cf8f7276d80978209f39e9d6f3a649695ccad95a309d466ac5d13ed801e9ee12ff85ce570885928a29bfff2ea47633b933c16ed7aec3edcaae34af8916b18c2b38381647e06a74872d526bcc5bc85b5333beac7522c20de7dd8d666cdcb53bca2bde7d151a8a13dc2ea6cbf75b9ba7b1ae2235875a84717da79d90e35d485e3815762bfa93d1a996a10e4bead328551b41cf5daf7691a2fa6030e9beddff877d2613c09a095b003847555bd6b62bf7ddaea07b64444013e272befdd730ee9cf2183a9f99e060ee551cc71cb3b578b2f38e2d0169dd8ebacb3a4bbeb4ca40275bc23ae8e86423fae3999d37f08ad16581858e6c6e2ba6be850d7202cb25731cdb19fd370a5b087e5d800e7e43d07878ffc7fad8627ce3680bbdb49aca6b6974703d39cda41883fc5e2da08667563a9f1fbf3e9264257dbdfc80edab56037c1d0eab9b4b4955f3b8a53b4dce991e0ad6ed4e00e744b871aa25db60b6bb0a5079cb335a2ad810164996a9f94ea8728c1c01082d479dacf541a5f1af70672ddd157a6cb870ceb34b3e43748145bef3a48e4180c0659b3d73d81194ab751309dc3580e94027dd9e6a875632781d24a5e083c8335c0ec479e18401bf817caa9fbc22348aa4cbfa35fa3b03eebf23a5db02d4e2db1f9859c7e500a12f7d3eb0ec301577e52f03882ab153320610b3edd0bbf05908a10ca47d388aee06f4f3d0abaa723a13b989e3310ab12ec40710d2e80045412338665d881e055c66067a8b4cf4ae9ad0998a5e830b9e6cb212713fef5ffb340d3c39dce240f3c4c9bd33fa6b0e2d8e90ff3ed12db9d1c27143be8a9598b33f15426df27ec99f09a5bafdaac3c961aa16281e3571869e052e08d805b304f59aad9f07db75c93e6cbc6b697f332b604c22df4385267cfa49ba2a4c610e35a598e60c2dc9b905c89f528d0e83166d7cd03108c914232ab810a54d5adf2b39c41f922c7a2635baa613e424efd0bacd21227e0d47201d1b8b7a24c0a38d42f5f63be1ad9651674e6adb9d140932906e9667ada46b2931a6f3edafa7b92b25b598db046f131c3ada426bc474989d9eab98b9a00252add8aa8adbd841d27f1c716ccf71120bcc687b14b50f1b279517e31675d4648389ea1896775d870b62c10ef3a41bfd1176c2838d046fb82a72027cf59f1c364a043fa13a79a1256910052682828923cab1013a447363fd7a1a47e4540210f9712ed49b51057da3372aa12173648b23321b35e4ae8f06b1f0570dda9a1365407c46ca73e40cadd33dcc900d79ae3969a769c11f0abbd334a527833c02a29007bc92624ac2a6afa440d24cb22697273cf681915c5e1e1301e6654532ddc6ac00c8f8c5aae063388b07c78a4b2c8d7a386095066f361fa5a027bc10111cce752495c6076d7787987b2908d722e1c9f1ceb5c36eced18fc31e2ce65630acb8aca84d0aa759e79113431b850312a58f1cdf480aa75151381fddbf8c24d11accec1e06e1a7fd463b216d8d861146c658011f61018e06665a704a06529592742288e37b645a4266d50755359ab4ff9b9ee40675e9c7aaefc84d4454904d7462ca29889e685aeefcbd2d91b5927cc9811a8548f1c28eaa1fd52fec07028273540073a987a47b65e2342fd936bb7c012e26cbe49246e953c92fa91c2931895c5713b9e5a293ff93452c062e6960bf93319f49c71edfef334d115078e66b38b772d49f8b7338598c69db99fd7f620cf1057d2d2dbae8a92a167312d8f84f6353ff8629631d5b00ab98274342458bb3f06e3bbc52a76f59710e437f301868276c23136c1faf8d185a19368e22f084ebca23a40f3850165e8623724e2c7068522aae95fed3a8c4f652cb3c043d98e2aa8ea0d564d4457d4b532f9118a8c26fe02ec8316984955050332cfc7474df770671210e2b4a641efab66b5182142b49eeb594274139022537d0d093a24fcf5c550810daea6c78f3cea0d7c578f9f4f6753e4968325ddac3be3009a268605571153b727571d54303c9c1acb4d6a8b339d670ecd81b4961c7e10f05a207bf97f27a6a35e7179adc88ed40879bb4ab618fdab7b9d691313b81857438fb6dbe228aaca12e000f46ef0e3a9e909b347b1e51d4bf984620cec3fb425b2cce371651068bf59a2ee67075db271ba371dd374988e82ae8bac3b6c3ed5fb5f68e760d70442b6a2804fc7104953ae694a677ed580f8a2c81894b7c5a64def312bee92e6c20981401fdd6052dd487b70a3dab2186e576428d3de5396eecea1e95145e45ce6d2f9cd06e0000f6ef351383ffd480d3c73a38a9589ed7cf64b2f79c462f47f0cf88aacd877c5a91039e4ab0bd9ef335006a0a45f5c74ce3b8a370911baf5b2c5793e7cc9237ca4f4694da4c5715fa3e01ea7169fd69dec76e68b8e8c6e26eda2934d6bda30981bdaea30699d491a09454fffd5fdb366695846f350bc974300c5732fdd4170888b90bac847c5d581932935368964493a5e13f749a3566094d33ecc0e920cb730c0c0fcf3910e0c6bc84e9f28d712b2b586d4150795ebdceaecb17e435a87c3df2a98f93dcd56d351fcc1c18c19711d103f001e70c99ba81d2e82ab16b1dcbb3a76c02713b5cc52843c3d98d5e6a0939f5a414a7932caa97718db08285a9b0c449b2e7cacd77ac3163a6eecd9ecc8be1e9a6c2a8a6e8d6590ecd65d5723dd95b9292ed0b7e5da86d787a9a6f115cedb269f95c35abc2d9e3d3f5da209548cdd04ce4f5d4308ef1be33f78c7301793f88f45d250b188cd3ce1e06f607a6f4c728952db48b13e0c0dbd7962ab4c95d83ab0e99fd6aad327aeccaae2adf8928fbdaede78ac51230e1dbf8cd4dee8d72380cec273d36d0dd4b7cda225e72b6d9e098975afdf20c2eb7ce4077f55b8e4299af6f9112828f372d558674a60f3618926f09be24581081908bf8d7e02878922d4836bd2794148660d3428209522b86383bec4cae035c38f164ecbc83c0dd267c3341cc8966dca7301890f38158c92c197b6cab1096b745426e92e45dd7814b096c4743e794bff3b48acc912a568dc1efd6e6534e3ef76fc07625ddb6d623217b8a3bc29df8c968126164ba27fb9f89efed6b5932889b907ca5e003e8a612dc811fe2206803ed461c7fbb81b0dea4709f0f2963238a59052565e60dce8ca0c20e3f020b575476cd9cb033083e07239cf1276be988330ab9693cbb08b13545c6ea7bcd6cc31f3a18e71339a5ff266a4437fcab53a713e9e67b55a7dc06c59911f1d3a8f6559f8ed2e033fabaf4fca00c41464caa32aa7e8fc2ae347beb41d1f52b3e811e7787e7330766384d49755c8af9e3042a546dc8524776a5e67c82edd02ae5b0a00f97a089bf612df56b8ff5eb5c5d1ced596ce95100367e037b2c3c9a91220317f6f30a8d09e2dfb43ca7aabd2b1118d000afed11505968ada62bb895a350d14aff94b90f028e378c3b875c9d55b7e16643e7861b910c4dce27e1ea3836242f0609206791f6a61c40d719ef25c96988840c5afbef9c0f465ca4fb9bd98100f9204b08001e84dc0006051acf1620176748100596ee0601ef70420ef38fd032edb1d078e98a36d8becf0aaac4c56129296ded04f9ee5a362b2781fecdcac299912ba9fc310be03f51fcd4ca6c4417bbdf2e71efd56fa9303e559e7c59d5d1ee8604650dfaf0b944246141af9d673dec6a19ec4168638ab8fcf29396f12393a2a3ad1bc66dabc5bb927e98e7d8923e8eef128a0786b41acfadac4a0b829591dffa6e84d496f6ce71ee7d86b4f8608e5434f6f6cbe6e45916ac50b4346b912ffb8918a80c7a14965c3f580228004d75b3306ee2ff4af1821a835f06df922742af8219f2079703b48c871499182ca5789f3b1321c79f809b17db3c420ab46c260ce9df542a3e64348f84e0824e621e158489646c67d560035ed3f9ab8edf4fe0853f2064e4eefa0266e2575945db387783074c22a0d4bf6c2a537d8c3215ec23549937773b4cdfe784ff4f3250fa5cc81a6a9c725a8bbdabc253778e7149ce005975ba233315c5fa52001d10ab7bd3f08519d5b8a4091330615c96e285203c5104cb0494ddc96faf649217158e71c1f579ad1a2acd7ed3819bc7d6294b150f9fc0bab9ac653e8fd856e9749bc6e316c12d682a1ee73a7e16b2fb181b5108858b209b9e092cd3fc6d2edcec7ef0ca145f2239f882096aa7394d9435c05fb1df8f8f39ad4e45e7c15bd33b116d14bb29d652bf4f28ddf19cd8dac881d452ee16b225179591cad0a78c847d2a472c666cee1bec59509c5279b353c8e98968b8919af60d941a0c11c03d4d6b76fba1347c85cb4fbb90bc4f4497b7e2d494e2d30c05a8e5fd135b37556a3e372f1c41d9fbf055670747d13b0a592be968f0b492a287aece4c23da6e0ea6eb6aedfef4545017e3e46ee1c93b841edf9dc38088127941809a0484669a558d6310f529ea18023ccb4fe8f976c764b23ba50cba85b8781754ba52aad9a3844a2ab7895e258c776c97128ec5074c029f9ace1a820d6b0af5f2ea74cb06b8107c949e578ea9f07fea17d192e938d4a7a2f3adc56a5f5eb4030a8f444c5e76ca378a18fdf8d0befe35f65ca206d72c65c948a1a73f21867abe0f2df8bfdea4094b39bc61dc7571bea21031b88997b09a0c36cdc0214814ce17ce5be8e8112136c74e1c15bfdc85ab2df94bd4c214b07538cf21791e1a00b7b57be9f41a118c4aa348b86cc8be9b793b8103aa30e46e4c7ad533339b761b80df657e51d0fbe54c379a3acb973af1a2ed47bf71c63be6cc12db6200bff75ab440a2c6f7c1915777f937d3104f2e5beda8d37baac62b96b84fcca6906cde63b42bd71c8d2c3e8bda925bc8553d878362ddb1298f1348f86eaff21bc98f744685b548718b150703207112559c60618cd9d6270b692926e90a207310447a35fa828572de92a88f14ffce723aef9e7efee03332a52a2419581d5d42b15a3f41b548020b2296ec9687b7e19117ad9f835c8d111a495de68888945fa2a77a6ff7d4e47256ee3448c1e335879253efb7a62eb167d913b95dbf3480753df5173003ab6b2c7f51644945d0cbc6eb2a023617f36b57d00bcfc607c1f6fc7cd29b8644d12053f80b48bd427411248812314b9b2c8b18004d4cd2f719d61440604441707840fbe97f4aa4ba4a61d06c1c88a6c5fdd808c3e22f62fef30b5870b5eb9bf781525de773dbbde60ed24f1195a6e92b5e7a5dcf4f73964ebf034624e5199e8012867b675243a3779ea041eb9a9c0fcaf265f71d003c80ff782f058c8f77df5ba05dd30b0affee295930ffa6059bd3d72751e068927457f43e5f1a6b08b296950dc8088c370dae3f31aa48b91f906097ce3b01c2f14a164102fa6483c5130b8c04d60a4fccfcf73a798e6205dbdde4f8ba89e054167f5903525195063ff64e6f351e5a020f038bb856031226150331fddf1f9cc9a6cd862077244bf38a01f343d0022ba7fbd94920486303c91ae2a51de3788201b9111a3723ab77e17f3f9cdc18b28ad7ba1629ede814efe0a8121fce02c1c0485c3c3f1db5d0533515427c87ac023a6e526b564800f01026d986e7ba3a3e2a29d55580be2ef86fe79d7cdf09a6699423d4366333c407e623a45dc70ca9271683788909248f2c1494518da0a74dacb98d93b65551396facdc6b16be2c07dace04d014934f1018243dbf68ce830a0c0b42b3f5442654312bd462c2206aae97a989ac74d7d12dc86ce6760c352bbc43edbb63b66007dcd7a89e3f7090fe6ddb011b0bb748f070bf544567d4fa138a9bdf7a731892c251170497763c1c00b4721d093d3914ca4f25a53d7238b5a103e747903eda6a7b7d00979259dc28fa5188aee7749c79c220cf7c360fbb1d3902e36db40c33efdb39b067c9093862f3644d7d7f1a5d4ff55c3aeff4c132e0a921d9464c205a799f04785581afb6c51f016b7d557e2b00f83f98efebe56ba61b201e409175922fd430bb475d65ff8a800a597909194d1bf94b89254f9da9572b294fcb65ed527e390d86e105200317b5a38fc78fbe09503d1655c8d9d39c95cceaa3718fb2931508e9d41a52e4b1a42f13adafee5da3eb5c373dc290398f823e1826bb0ab6c3443f42411ffbd2a29cf4587e005a49422914ab57f8ba92d2a4f0ffca5ae1ba131c888fe0671abe03944a672cff7d70f76d409c9dc4bb67873d93a5e4c25c75d0a27f12689141a28c4589d015340c540a7934bf243e91048a0f19df95819485b838a9f3ce11cc7926c98ae6c131362cf26440b8f3572a138e5eab45884334e9ab4074d24d1f545f8ec05a620152f5abaa9dc7bf71ca3da2d074ccc78b43df0a75a4c15a9d80f066ac055b26d4d8ea430560ac402c15c9f745e62a8ee9bb862f564099fa0eeaf9e9eeb14a533a60312960e117ea5386578005e48a002c56496500b4c2bb36f36a667b84a3b191603442893d3a9983e01b41bde60897cdc6ff73e844a6f18af02fbeb398bf9426f765803bc053702793840d00b9bf1003cf24659383f9c3d6e1deac23a1a958442af39438606589139bd926b653e17d736a6496bebce95ac8bfbaaa9c7d26efb091e102dbdd7f78e97d2111af0eb9a4a6e4e8194498129034e8b526e49c4c3c8465255616757621b47ed4c98948633a9d5324c9f6ff3a0b9cf1ad28d42b460ab63c00624bec26987da2012f6dd2e1b16805cf45f6ed4e13e309c0ad44e88947acc3541df1205757501507f11f66a0d732ed0366f192fd5542996dee2bc95079db3a035252672e686cccb7b28c64afacab0d458bc3246c9fde8cf48ad491f08411ba408b6add5ab4b2f80f3969898064d8c37b460c694a2b727881fe5ac515a06c54c682fd9d18ab16384c77ebd61c68311c9a50413d6b5051d54e159a61d3a60a5036c535b9fa6b4429b73900ec084fdaba8b468c316e005aa5f29a059d0372e059288f9ca17f38c250123b56a3c91032bdfb481e64cfb639ff7fb8fd345435572e2add48ce2e180bddc4b8dcf430b0ee85f74f5d8cb991eb88bba12ea6d79c8ee4ffc501816ebd96c86e659aea483cea45be6edd0210d790e24f8650b965e1e39a1136138fb0ca045370d67326ab1d7be65f0f2b6fe7814807ea17fe0198f1127c1a0a9ff8cfab3c35815624fecc63938feda3254d318bd0a2cc277466974d6d3748560b994eab18de90618e453b35507c6343e1f378109f0e26edfa1337b2caa38727feea2cb014084187668d0c44256c0b67579d17be37c2d0053f5ccbfa6c1efc6af8d985680dc636e0b4d5dc524a473d6a5a499e5687ff91866382a9c04aaa524bb0fb48ed21c83575efc0f8a8a80b953cf4c2dc49ec3581e18e55a6f8024b88bd5426905eab6ef96d940d0fd2618af49c00095a57a7007a7900ad2ddf87118716bd2f6e0242d7cc4e08383a5554cb5adf52269713dc91cc44078e7d82daed1a09fd644800a5720467cec40ba99d7a0c7a479b528b440d64ba0357934d12e55d14c67003a2a5af585f5769552023920b06f345541a8df92f295a37e61b92574f6e36ec21d411f4dc98021df950e09735f420cbd5472a9e88183b3bb353b2d79df1a2b402fae67457e404ca2af61b3e00a3647d4f42e1acdf2d6da3f94e657075cec4d8d9055894bf321073f8296ae89eb5c782881d69e44c670f027ea1964679d0f0b8099dbc176a86f663fdd51e7579444b4d498355560b23b2c9aafd6615f852c78ab64ad4c810312c691cc631c4d3bad1c4e02576cb03c862c14b3e8f3d5b445f88af4a1fd2fe97443853e1f904c0d3747b058168e80f12edc383cdba76330fcf3e8f5c05a8acd11ceb24fcf804e86dfdb80f03fcaa7ef8ffec20f0411fd7da2b38eae7cc2bcf91612ec11f8c65bafbfcfba142bad03e45ef96aba001618eb0a426343ff8bb4c290f71d43da02ca28dbdf4a28a65196f0c5eb54301f409dcf58d770c4ec1ee2d6997c5b5a66a53e32a68dffc4fb89721568d70bed68416e29c92732ffd5da4c8541b27a0c51d4e373ca90fc726db4daa099b53ae5e618c99a16362ea98cc600977c78a9787402eb05b97b9196f0aa9da5aaa3bc91841b42e1ea8cfe8c4e283260cb6afc283bce209d6c81dde8d9cab05615cd7acea9a7ec880bcb0d0e7b74b97dd94b0c91007da703b07c38caa63adc7f9d57e67ff20a6904e08e9c119d9683387fe4015c0242aba057a911e9e70c6e5ed9afe73dd2b3dfb5ae29d4b06976ba005064fa379ca8a1a11eb3de1342bbd7d5aa32419e885b4d244748117b42bce39f83092dfecdbe2b755df8c09976ec5d25b98c2ac053682d7e41f0b85981eda679699e1fb588b4e49107132aeaf538fe9e7e5231d12471107e8839d7d8f170c67ee63c69e8d33d46edefdaa93b1f8f1b4c4a6463c0bc428a235e7a822ecf471e2172c2c04e570801e38a5152a1839c8e50f2201302b01527a64f523feb1ef901243765fea6a5ad639bf8d27738956e8480e61e0c607abddc01ef320bc0148e053c405535f38b9619b9a2ff05e5e061308cb1da54d585bd244c9c95af894b714a14e7c58ce9d5340df0108e4d888f6df3fb1b58fc0fd67e734b40e1dcb770521b5103a96a23108c8cbbdcf308730e7044ffa3efa2010f5010205870a5f9b4ca3655aeb2dce46bd4b922f352b6c934489a40ad9567a022fea884c963a7716e660eda171ba5d6c8bed49aa37ac353a26da0d48b385f1b71c68b1b5d84b1f93fd22941f8c1b6be92a30017a286d4f0e5c566959de19b8c37883822db147d7a881efca2c596b6315a2407eb938d82d2c4558e93156dbfc8bd432b8703f37a47b6036151627a42d5d75b072551696aa85a1d202c245da9640f9a19616c429e338177b23a4e00868db1ba8627aab4d52b80ff37f4c6fbe0593bf66f4e2d8af4fa1bcbd87365a546e6cd7cd3b44cd61b173f09eb419eccbea79fa5914caad46288325085b267597374be920af940dcd06105f2681ee5f90b53e6416e3551cf39d803a8fe4eded6a325941bd1db425b114386aab4ef782a491b94735e15302ffc5f98e148b9e6e3583828ccbafa0b4ffc380852fc879554674e6caf8c75f79e260a41961408e1951ee083cbc1487ec298f69db8a15bbaddee517ebccfc42fe8375d4af9ee07e3a1e8639113d20dd2de8e8fecedee5a5dcb54e345c797b5f86d1aff1875cd076b1e8f0e45f19581aa86d9a40e4180fa97e2ace6eac9a0449949b54fd198e1411b62487210cb80c58ac50852fac410a53f61103206559b968c9afaf60350d55add5e3f61aa5ed2b048eb1bca44eb2ed44b8bed1e8d5c8ae6b67f52505261ccc1ffc2c68dc06397fb15670eda9556095f4d5e2dad7e93be3f4c6cabd75856cdc7b45ed5eea832dbe47bed9e384a5a94bfaf14ddd834fdd8018f872005a7f2e20c2d64b747bd7d8d5be6b891d0ccb30ed73d867929447a3e3f81c727eb5098a38c081e8cb6dc36864e7af47a762615ab8428195d348ca7dfb505be0c402e0e1300bdf41d48e78c6e12f096e794915fe5dc0109a27dcafd29b11a46b42fac0b02de72675d2783f535cd16d28d8027ba9bc85b6f6ac2152e13545ef1d03210204601ca70596139c35fd2c80f7709f48d85ac42e4c70da65ed030d3012d3089db2a9a6b407b3b4c58a45db66c0c50f4a5b4e1cde16abe0b8e0858109513d54edbee7b9d9c30d27021bd7361099909e66a5ee21732f4d61d7ab4dead5353453ac3f0db10f9d0d4007c14ef31bb042361a8f05ccf3836aff34d00ac532b95981a6ffc88109fabe19664511dce40237e475a0f9bc41f59e7e227c17fc1321af1c1c361531160142527f22ac4005862820d3da037ddd8cc5506b1b0d8f3752ea5c700432407b01aad334a9dab534048136b0af16304d0b2613042fed603c42c9c1a6d03cda570135905932fb6eaf334bb66c0b7d22a260f1471c763ce718bb25a212fa6f19822dd6419fe09bb2bfd12538531bd2d5f9e3100784496b37675c750ed9ec4a7d671f978b624859b97706cbf90ecedae6334c08742eb8471c03478d2151394cad0e1e4e28e18622f5e17b3cb01cad31a42ca5cc09a1dc2dd4d519b0a924db51a06605c9c5165d8bcf1baad636198cf629d757add24caa53c897a70bb3ced23a19926ca4df8addc1bc983f319b56971714cf5555b4a93a32cc8c72df886d14f2286663594f5fac8831081612176e56237f1fe7aa41ee7f9dc25104ae9059caafdc625c7f96b332b2265c0535b538525fada061816349c0fbcd2d47db1fe6b6679235f7dd948ca3a4be46da24fd27f6d3e8e741aa791068d19de9e6468d44701dd8ce91dc25da7520ca655c4300599abe96c52e7c08eeeb7e215fcecdf405637f93258a74c2c221d29d1ebed13a493924967c64723f88272b7c844d37a59af6b3cf7e1d729a51a640a9fc3a61308e51ffcbaa59f153daf2a598afcd8f67ae37a3c6999ebeab230fe99a2a0e5edb183230f6015c1c344b72e295539e5e6392d1a28aabb684ce5efc6b29b47fbb4b54c09619e5cf3c248c7ea6da52bc357e30785445f8c840bfc529cddf9ac8ceb5c1519aab5bff40bded48c8d6ec61ae42e4ebcd85cb863f3a14f838cf815c4d02324a726693512cb8ad68697a7f0d08d6134d3525882290003442fbf17e2d2b1d83f7803fac7aa6af35002ba3cc524d23ceda762f4401591184dd30f1865fcc4607fa0d555ed03c6a1e8a7c5c75af28315b2810d7e55dc8812b5df9e0bc7572d4fed371631ede503fd44a50b06ff655095bdefa96eb0d455761039d8d789bb38e46db7e26720b7d61bf64a1d164847781ce3501ca5ceff69489f11492e68407eb0d2ce333bc074e64e235983119e32037ecd9f1b59b6b67556822b98358d39ec724db1c3eab9226ea38a8357b70e3ba2d5b3cae4d42d1422c299989b66369644e346adf6d510af172fea164444e34ac8c57ca47ffe7212ca98562f6b84cec1bbb8e61825a8975ca2a15e93576f2924e151160786954acb27d1168924a7eec93c7f2c2fbc3f96392087444603e6c8aec9f78281e3338058d9d04db2cf26b7ef6d54b8af0ebac77adca7e02bea1b179020060894c218927704724437f1145f800d80e149a03cae94e975930ce3a6ac68acf33641a797a4bd2307c8325f089262e0604aef1090e60e713dbb06140c791e2bf78e4f629d733367c52c72a0882c86e4cc501a2ee8b76ab5f6b8e2edd178b5c2ed4a3fdd6f65beb46424b5676776f29a594329e08280a740973ae91c7ae9b698cc8f4b35629d6bffe559f596e54356b98a147e0949a4c5fab94df1acc5bffaacff52c5451a5d2748032c62c91031b574441638f41634c1c4fbc8c21060e1dd6a801e5431729c08cf1050a1120192e763822a9082a5ac6ac818394524a39ed992cdf43a7a4a17f9571c30ad9c51026e4c0040834d878e114061a65c091a608316d9cd6a4c132679436640d5cf27357b9c197266b6cc1e24b0d485cf0c413356820997104860f35830c9211409ce1e20a377c040085923069aef090c51aa824f96b6099731fe0f26012ed79b9ada40752e95f99d6aa0ecd2814544aa1cce9f2992f52795cb29eea12d057faaa5db2de6ae923b26e593b3f3b40b4ac3a4c28c073cbca7325c63651dbe8f576ec1a762a6a5507c3bc4d8825e79e1e776bb5f10379ed526ff75ebeaf9af7fbee754f2ff5c8e230fa2a76c7c4f1c81eed85587bdbd8b953d17b27d66fe258021750ae7a146281730bb1b64c888577d0e4b9b34b997d7a22781b5051569d6ddbb677a25675b64cda2f76f61eea81fc02c3300e1339aede3b11faaede5342df7d84b9b5dbec5ad62529c398bbb6baea86d5fa49fb772dbc27829af616f85dd3ee799e87d20e2002afdd7b4a1cf3e56d8ed244cde63d54fbeebd86a391ad8159cb5ad6723e883af80f95c18f46f6f7efda987f359b7fe2f83dd49b4cfeb4cd351d6473940e6a5d1bba9f1ebadb467badcb9f585aeb66757b4d8f98c502c1d56a05ae6ea77d57fb5657fbb2c7711ce7799ae7d97bdf344fdbae2581ba3bd5ae7910ccb2cf6759e665d9b950a3c4f11bf52c8e4676f63c3deed89e0e1d3972e0e0b85c3737ad168bb55ad9d8a854a9140a753a8561ce20f87d9ed7751873dcb6d95cca20a954b6bddf5cca20a98f7783377ac4b677d65ba2943e582a70a5479bf22ee58fd43d3d9e447cd4b3787f12c7a11b8a23ccfddd6699f6bcfc0a3eebf11ba41b14477cecddc76f0eeba1bb399d5d3ecb1b69cd1413d386ec567a827c9f516aa9a516d3466b456aa9a5464c4c484d496694e86bc7524ac5ec1803ee15a1eeeb0109d6ac59a3b4ade8819d6da5521753206d026cb93485119612c3e43fbfaf951a77f5a871f5627a34b287eed8b347f9a14bff9168d3571d68530d29260ac6cc40ee268b7f14650f26dede55d7eade5b374771cf98134f07c5596fa61e45f81ee1669f86e473dc4171fc0641104c690710ed13f7097257896378fbd5539cc8adde433307fe9e9ecf899827040cc38f7973a176398068879fa9cf671010ed940ec18e1dead1c8cecfe7de5339710c9fad9e4f7ae8ded5391db47a4a07dd84e2d0dddce7ddae8337b747b5ea8d8b75cc6bb5e6b1c962b1260bcbc06c3e1f6461cf66a8476c825d778c76e7c46e7618c3303ef71f45d9e5c1ce9f3aa8fbf6efd303eabe696e77415d9e9e90dae12c562cf1eda477d223d13e3d258edfa987e268648720a8c71d1bfcbc6aea71870e1d3972e0e0b85c3737ad168bb55ad9d8a854a9140a753a8561ce20f87d9ed7751873dcb6ad567ad43678d56d44cde663b6c1bb2e6510d5c7bb5767b9f4886df0addf8852fa68d9882c6d63a355e0a5fca13a08ea11a5d2a3f7947d28664789e3d03d8923cc9d6f3d20baf3a7e741fdf64d8fddf3b3084ecf01443987afde8fdca11ebf27dd531cbd6ba7f947eedd94bf3f500775a3a787eeeef4769bf7f8d1c8b697690fd95da3b899362da8b5d65a9f437f60afaf4aa948353a767bdbb88f76d31a868d4037f580eaa079faaa54edddb6e3d5b951dbadb7bab38ebac9e7701655ff4490e39eaab5d65a6bad5575303cc83de77f5fa881f0f79e9a457c95387a97ef1e661173e2988ff3bf5bd5b967712461e763e1393dba8678c7dc413dba420d84c17bc7b8abf42693390d0ee98e7550f75007a5ce81e298f510dd9dfe5297bf4171a71e504a5bfa6ae95f140afc054fb739dde66403da70a24dfe876bddf9dfc9e6d7e61cf88cf5786db883dcca46af7e577a9c9b03410e3cc8fd1ebcc6811c075e779fdfe73f3d721b07e16fe036e283def7fc79efc06d3b4efdfb9eb3ea3636dfc3ab424fd21fe1f34351d257eeb238624f9d4ea81308be0337f09b1ef14150f47a28087affc4f0591cedceaf1ed0f750cb9ac5eca077f0eb3c1d843f3f6e9772e79d61d7d6267064fa1f30d040839cccc2b9061800d1acd5ba29b2983025f02891e79e42e69e4b6a30ed1e5b2e8599b2c39809f302b69df43c9873df6c32f250dd230888466db3cefaeaa7b782c06f07a9eced28284b4b4cf6a8654993c5cca6298c75d2034afdd3df316fc876103cf74e4bfa02bf7bdc53e2d85dde01f8b90345eed283c0dcf8e039bdc964f0de2be609497d3b28629e10efae3ca43bb7fdd3a32b6ba0ee3ba73709fb7bf7cbfd7a3f736f4fe94d266ffac3e774107ed641a86f9f38827a886eac3dd4e55be268f7e9d5f36002a1b4a52f961e57dad2d7e913f269d5a52724bceaa1389a60ab8ea7873d5175ac759ba8faf4848ce03d239bab754f4fc8f81dbca7eb0ef55882adbabde609a15bf54dc87750ebd17ea86ed568e96bfb67a347a2add236d503a2dbe69b68f3efdff67d93c943f57b260e7d43766fe75122bb3c98fbfbdcdf3d1dc45d03e2ae6917d0bcf7794f8fdcd6b8fd5d7a43e6350e7f6207beeb88bc83a2a4d4db4004babf63c9012c6712592e2d2dd9dab90e3a3dd2dd1de51d1447241b4c5da5d2a3dddef35323086af8d33df059f4448c1245c8c2538f83b9c377414374877a84b93f20babf7fd7f4c8fdfbc46e7a43e8a7c7b9bf77f746bbc1570fc87bd63d153c463f91d217bea72d7d61ba3b1dc47d725abb7c4fcd1b13e5d00452edc99a9e964cabb55604b891b3d7e39b4d46c6ec087407d56fbfb11b0b0dd6667dd346a072a7f680a5e71ddb69e9d741a6a5b701a7594ae4abe5a6475b6fc59168639f19772c3d0ee6e6b87770cf9d6893b06ba611ed6a9f59961259729366d1640a2b69957bee794b37a05572fdd5f51edca06b96d5476be4953824c9d662f77e16156d2a7448a10a29ac58010a57a290a10a16695c71e20a208c50e10c4d4a06802c61aeb860cb252d362c65b13203344029f2a2054d18294924e0c61193161c5d81858b294e6abcf14213439c51c10f5a4833a024b16245881cdeb0e268a806278430b302124e18016685344360d182136414855123c316484b51c0a8d20519444ea83069a2a10d2b340cd1a47ba30d292d1081c310653610d325852c94e0724398a532638cca90e28215701cd57064420f48308c11c31665ae203501854a0f428c6142146c0cdbc29a37ce6839010c5d6a98400f5f9069e184259e50816512c3d146260916294e00c517250748a38d24379c24c9a10429704812430949525881441247a3174a8aa080f2c2165a94b42a69c008428c2064c8820525ac6086268688b2c6cc1133c4e0c40c4fa0c1841b47f01005104dcaf810c61b5a8e66a0c20b15518c9244a942c31218279a50e00823cadc00e585294451a22851810a343a4c41e10b1638e43086cc9a1b56f0054a0e4c1ba881c4850b4634090207ce8bcd12295eb62053c60e2ad0c410ad208935c2a4a0c21938be5c51834c0e493b1c6186970cf410470a3f146146891928507801072c5da4004c9625341f8e06a0051a2a6b804063698b11f440460d4e8e5288e28486b9410254c2e8c1092f98d820730128253405f1031531dee832e50608a459c3c60a297841628168052f6a683912430b242d1c97189c3061060b66e080438d962635062c472dc052c60f59a08172a6cc067c48420412461c39414492c605102645668280630b993272b40851c507348868e249064a589e2c41e2a805516c112283265820c4971478d06187151955349949028949135ab66842e945a0c7d72ab5554a29e54c62ebbaee1dd1e6c40d2a007cec89803da702bc9c6ed4c6177d4ded45297d278e09107b6cdd83bee65fde953ce6f0e821ffe4bc7a50aa5d401fb05b0fd9ad7943b4739c1e4fb138e68d3fbdddb4f12d7d51dd7d7a1dc87901be9c1774d9b1889dd334aca8eac85f9dda4d59dc56d76da6a35b09723299452f6c2bbb7e22eda03a26606b5afec56aadb5ca7b57f2c472a002d340cc1a475268e9a1c915306596784183154e967cee113a0bfdc40088065daaeb6244a7642593b5ce7be909f90eba348926b4d8c46861e3ae502b3d940885163051210a9931c244a15b5888038837aed0c082182c7872e2b0d06965e3aeec518a195f9b6e7126862a927ed042298c0e9ca09482162fbe6c11c267944a5d9e6060baf8d0250a539719ca9875043c3b07c8a0637539b58301102fc2a03b40763028da32c41055a2e092041192cb19388861060635c0d0d0c406274692fc44f7ca49e54e764d588c51030b4f98f142195d56985636ed521d17355cccec249a9cc84d517cdd18442dacd0041352709022862a3470822a698290e28928599ab4c8b0347d917156b87cd181619dc5450b172698b6b855bbdf31f79c41aacdf6976a5ab661d8bdf7de8b8942e24c7519bba3b8ab66e570ef9d94d232a6bdb5a6ecb679dad75b5f5b05565a6b338c8809f004955e6bafad934ec1929e52ede3f6f17ec4ae69af6ddb5e75eec5302c579db96fad785a6cb196568a7bce39e714122dbdd65eeb85a495ce5a29b5d2c7a4e38d0a75dab505acd11f52ea11c31e839dd36a6d36597befbd76d27b6dad5a504a2bbd92a7b5bfb752ec5e5b27a5c1ccea0423a3246baf9df75e2d63429ee0a6b690c4b2add6a814365f2b25cb886b8572b616cf6e76313d626063f729714b2b6da6c3c8e2961fbb2029337985c46db54d81dd007d19a12f5b29a554fe524a8bd017b556530a84526dad0b853c6fed116be9776857eeb5768ad226eda4e8bdb3d65a3b0c7b7dc6302cab7383b79a56cc8af96e18ff00225f6c6343d8aebf40e88f6cdb8cddab5d1c64dbea23d2c7bc04637f20017b08db551b15f991c3eb55add50103905aeb2ce35a3b0226e39dc1d65a6fd3e71979a5fc78efbd265c8f8bb4f75a6badb537b379639732556b927b654a4a25f2c87a8ed51dc8dd630703fae3de256bd5c06217bb17bbd584df31db6b7ac4aad4a94dbb7a2c02a22063538f294ba98058cb3c82c8db47ee23f631fb78b76d7b711cf7aa23e7de304ccb32ccdadbddeeaef161d8077eb8f3363336e6c278cec70440d16ac786b6fdc4be8b61187631ecbbda7e3a34e92886e5cacf39e79c76d62be5e67836edb5a37d04623f26c08ad5b4ad592d39f15a6d3b56ef66a5e6c9a0e14073464a8fb64799d890f1baed7273da7be87d507dde72cbbcb5e7ade9d1ead0e7a55cb51ca4d02d184bc9a36519942f6b595547526a2fa59a35a5add65a4b3f3094ed0ee49ed287b451b0ce4e018688746b8918d05a2206ac269fd96a2ba5e0b696ca11b22d83b880b25d9fed7a45906b36466b25bad4f2c0430aab870f0cd9cd4391ab02d3e77efa5cbda4f3b9cf55471e08dcf5e0e6610457d75a6bcd6a9a50f2552ec02a66fe040b8725e6064fcc0f1d982a6d58c1609282d89b0e9b127cefbdf7526b2bd19d94068cb30ca395565a6fcd320ccb30ec5a5befbdf65ead5dcb627d47add44e7aefad9c4ce86da6738911bd9452249352acdebb894728a6bf036781b51f415279ba8f49e5c9973f924753ca62a4c6445f3bacd36cc6a61febab52a126f275fffac9304c072c669331bb7e26b9923776fdb47403f275446866408b98eabda73bc9be6b76ad15fb346d44fab8c7618b1f489d7b90e9e8137ba4cebd275e299db8832552cfa5cefdf602ba41d28c4694d10d0dd30f7d112df1e48c166390b16445cb91325d80484118356851c50decca1a45391c52cdc23281d919353b6388382e9a33b42d076f3d4cacb370e8c66c19d694e4c21e25521743520a7bcc181b198e8c0d1368666dc9a97cc69299b32587b1448f42393bdbdce3c54ae5cccae0964b5bc46cf1b2c797521b67b2cac9b893e2911349d5993dd2e49d790d3e1b724f037e1ef0810c8284844929053267117d4d17fddc54232181bee60d30e583223d3f395316fd2021a167be0193524ae9ce3c06f407067b1e15461e73d63a4d278916452bc812050c46704d24c103162fcab8d0e433df53798896285591823248a88082227ce67f8ca6b0cee4f13ae4548fdd1191d0173d5275d4e891268f3b94e708d5a13d6cc8638f4d5f65107a4488cafa8b10027d5197fc7449a1293df43ff43bf4405845995eee89c6964b43a4d97387209b8e08edd0262aa59460aa6c0c363d2a8c4c2f17cd56393509e64a7fb3bd5424b226a094a260c3a28a0f7fb1e52b5828e16c8b15d659385b2e6db961cb0a3693a6039e52da2ba594f2269846db520a3a91e7a97ee95791472ba9dcac8efea0daa553069892bf056065861eb8dcf046124bea94344a9c1146d31266f8cc7ffe352fab0b584a09d65a99a4d0b9c4cc0e02a4c8f392fe0c2c93dfa94eb4d7abce779cecfcf0d053758a7ca034e94443a99541ea07f405db9bfa021944de0ba40fea7301939cdfb177a6585d6a0b0b299ae1b0e5842d4ae8921635b44b75db964b5ad2d863ce455366db02e72769604fc430809643295d6db9a4258b9db75cd2e2c50a3943e9c3daac1de1c59c2737d47421c5b081850b54a280728518128a8c60d2a2e98c14ac10871b5be69c73fe354f65d468991395a4cc1c93a6ca5ce3461573ce34d29431e70c430d5691913bfa9a735279a202bc306754c7d025cbaf2f59b01151907630064034e852dd1726c8c81eec02cab29d01aeb5d65a6bad1c0897dfc00bbc265fd909ac9924b7b1e5992daf75431d3822836856beb20c09bee601d9bd9180f1ac90678ed4c99e7bec2cc32e5fd9af0794fd136dcd2ad68935ef2c849daa693bb8d65af17fa50a8f698b48314f34b1314d8b1731603f8b38ca355de1be6c60cce03155745d60304e644813851118428c592d91060c4ec2e8410dd2166a844a72705a94512b065305ab544761a50c154b5841c5081756aa20b38310268cc941cb32c607945dc166bf48d364c6889733bc84714202e369ed15ebd57246597117cbec8629e1858b3d666c94b72d97bc88d9bc8451a271196756d65b2e7919c24b933dbe9c6c444ed68c81610a1156d678c228090d432cd9f0461651b0133a2e8b0c862c8b951ab29f0720655e58902981c43aebb5e5121468405146ca1071e4608112518e825023b5040faa3042bc81ea1284092726464451f510c40fad26d05a4b05866d55db5a2b6fadb5d65a4ba594326751fa41cc910b2fece043c4fe070b2d2d6c5aa060210b52d11b453ed65a0c8cf298e9b5495292d9f20da03fc02dfff300c993a5bc804b96b7526b76005038b122a2931ae1574a299da7cfb5869c3db7ddd76863c795898aa15724c975c9cc606a8b342f50810c42b57d42fac02cd3c6fe4516f4854d61c55825fac2b4ac72217d60cfc4dac5bc003bb65dd1c66e4b50435f98556363f64efb996663338321ed760b40218fd6881ebb35b2495031d6c81a59236b44c5689666d1a34ac9de1d7bb542feb062c81776acd3f62cacc71e3df658a56c4cfb32e661afda36491fd80b30060a15b3b15328540aa542916852e5913eb30334d93e13b13dbdca247d601f801af9dea649b384fed85230a22f5026153b3ddab65ffe188936f636c872386f834dfb764b792c525151d1131fee973fb4675996651b70dda66f26d67dafd592a6529218a626339d48c2f7129ca0fba75d15903e2474ffb4f741a7b5aa03844b562633354d65aa4d4c26132229549064d3911090541e2b8100e9f9f9a0889105a080b4b16b99f517962ada48499587c8077b2712f958a5ca637db05b3195472231d92619645e231b14e4b66ddbb66d18639cc3898a86a5a03285954476045602cb61b8252611f417898f096b035765d3ef68f4af3d7c4d505c45b16af9b5a50915e877e8bf0105a25f3863dd697b26cf2db7a64756766dd5e3cb8aedf0b4b1465b69db4fe185beec311629f5288451795fb1a46d8f445f560a912ecd0b98986a533553d3142d9141662daa45754935aa4cea5185428fe8118522850a52920c32003499be8ad8961e55aa3763260d2da24ba81165a28494a42486a9691a4d2326f36842914cdbc8911090541e8954349750a51d203d3f1f1459000ad97eac52acb5d65a1f4be4635f912a8ff5b1af4995472255a52a460699d608097f4084b966db63216448b673af80f64c9720bbe69225d0be6997943e52d472d5b90560dab90772fffbd77d26caaa4def83ecb3005b7c5079a8cfc5303d8033f9526a51c08ee7d4d3685e801dfb4906a17aba30050d53d08034461e659a0f365abb02609143a8489066120f774c4f23d6983f4df372ee582cfc4d4fa4aa83bd005a6499c1089aa6076026df3fc94a7b16d11fe3c4553bd6b4d35df7f438c8aecd14e80bfb5c72fa21d34b1fadeacc6934a14c238c0ba9643a2bc59cc897963fb4db2d87beea35ed47fac8aca669ade6caaab2566ab5add7a67d827f6410fa9c1f1964cbdd77b6c4a23639d86acd913e00804949a5adf5950c6cefcd979c894c76764a3dcff348d8f81ea65d873b916863b1ba4094cdb966c889a22ed4ac91a1053b5825512c01070d60b098e20a0c0bd110362831650d18274a459061faac9002052bc400260a1042a081c10517d860610d2c658cf8382b34e8619a62a0a832a16822b37200b65c82420628967c415fdaddc192ef954ab0567950c7581bdbc180fef08edde6d85f79402cc79f9de5cc62617c4ceca99708782957a5b25172d26e733f3d6a4a4e5a12196dede189f6cfd6f498891b6c7ac71331a02ffab2f7db6ed946945507e3ab442192be26c6c7383f7525e04ff9a913c94f6922e04f5a7af8f4d489d85c755262a3d2b9ea58ac5d444e0f531aa565d5d9b0877f6d2cbfea583d0a0901318146d4d1ac851f68e6aeb7377b565bd7543fa57e739bd55dff8e3d31fc27caba9a73ce39e79c33bcd4c7bc21ddbd0e3cedd33eedd33eedfbb42b83dabba6ede81dfa924472681cfdaae35d13e71a162b6bd74a721cc713b587e2089e7aa9672f75cffb77dbfd7c979a36e6ad799ae7791f775ce03b3dba3ae82ef5e802350773cdf6f4fed99e775707a0bca7bbcb4df45f498ee37c7ae09f4f248706e79a2bc95164cbe3e822bbf3721c47490efdb325a8c79fdd81c752efc4d1f374ea9a0e4a3deba0d53521209e1602b253dfeaf42e1b6da5fabda6c71b2de9cbe696be6cf43854b71422f538b7eaa74b4f88eaa78f26d827f993269ebc7fdde99abcf74f75fa3d5d93f73a3dded377f9b54ebaf5dbba26b6beeffbbeef071aef737b97557a97b5ab2390fd5d8ae3cf9697abdba42e2bcfe9b2f264d7b5caf3dfb06c522e3dee6c79a35b7aa5c76ca3557a6469941eafc7d7fe1eeaf195f5b833ee7cbf28abce872fc5510848e779d57ba647ce133b21dff1a779e05c9f488ef3d8218afa57a26387d681c343cc712de254cfd3e3c411f573883c7e51bc0e7187f53cd8401fe74a783cc7ade78a803ece89f0788eebf84f0400cfd1a1af65d55192a38bb6f769972b02bf78223abee3bfa8659557a2e33b340f2d7a407577f73c0f76e80b1f73408473f14a727c07cec513c1b9a8f189e4f80efdaa83bfe3e289f0b8dea1651535be121e3a571d7cadb1f89fc88eeb10b5acc757b243ebd040aa0ece818f23ca8a5f8580d0f7d49f4d5f8580ec11a6962eda728265e501c78cf5387778aff388f614b153713b7d28565dbbaeebf0b3e3530f886ece041fe063c713eb6eb3acdd43f1d5753afc15f36936697d28da67b1041b6862b7181fe35f5113626d74344138b13989b24fd4aa4ea6b3797aec19360ab1d0e4b931cc520c338125d2688603be3e920d79069ec7f71ecb9d251827eca53b6de4196ec986ccc2822ac6e862830b4aaed8c0135e7421238c2154908598093c39b3022e96929c11e589329ae802081ad4d0018ca32359460f4d25a40003952c4848e00d16604041195eb8a0b156bee0e0040b4b907200838a214a88418625a614f1644a0bb01c0961a50c1b5f8c943843456ec1a2868c384ec0658d9a2aec1629b46861c5145058b1660a294db08023891933a02843c51425a8028412264a0882cb1701a823c2305ce212656389b730bcea74f2c2a39ef2bc5085ba867a28ca9d42e10c1b92611587e26877f8ce03cafff4e9f7f60454c1673d412dbd21197d7d5a236135330bd822d919ea18f7542a8ed9e9e73c0888363da63614a39ec963f1c39835be36c677db36ef5c786939ee2008de8ddb384e3b287affc431d4423db6b7c18deb193e2a75ca2d3d6a298dd236aa6dfbf52ce0a238dbb12beb50ddb30c9e0efefb5c5f2aa55d3f72a7de81dca913ed9398654fa1ce9d32f027916867e033783023b2b773e2d8ed4d8f423da4dd55deae68a7644ac6c9f1d4ff1dd771497f881fedd6813128d2af4455d71d14555fbd87a2502814ca6501fc951e5d546e227b75941e896cd4b1065715fc3ed0c8c63fb3a3f4221426b5d6ba1255c7787a40a8af5a5f8116e8a12a71eca152a954dad5d244785cab8ef546bd0df2c32ddc36cc23fbc2f09ec7c3ebc22edcee8979753c3b1e1b15a54f4fcdbc07b87e7350a4cfbc1f7ad56df70c8b63eb9dde64724bafa80e72fd460789287114eab112c79616eab15d5ad2d7afd2a3508f2d5e0289dad2d7f577e8d041698e1c4f3d47ebb895e3e62d16fdcd5b396e58add3160e0e0e0e7dabd56a712d6e4aceeef26087a794869803eb11a95d6aae567a05825d0a5c9202ca6e618e258e3d36eb37e24864dfb45aa73f513dbe72725a3838bb8542e9d34fabd6e94aab8e52b57e6a5d521e7dd525e569e931ffe6a76bb1a7b244f91b117b16bbdb84dfc25feb0185dfc3e959409fc7310f8c87ebdeb1beab955e81e06a14eab1c37b77f5d4f07b0f0dbbccf380883e8ff750cc83f2f8491cbbed1d6bf155677a435a5aa32fd4a94a7c61ab1e8ab8763abc39d16e89e377eb3654bc69fd445554a5ba4acc0fc594468940b6773c3bfa3dcc5ad257d683488f1d549ff02cc5619c737610f0cd345607c2be39206c39112d8ff59b77a9efc79fa83a2886299b87aa87df10d44fff0e822008822a0d84d227f03df51bbfcbb37efac42d6f600ebfeffb50cf27105c280df481b0f3e5066187dfb18da5627dd341ac9f7450ea5b98b359ae2ee5e19bae5be1aff00aaf56ab70f52dc4abd5b75578306cad74abd5021f8661188644a0f448b451a77fe20842a8411004433004c383c701c130c409f598fa77d571403d763fbda79e44f94fc49e1247a19c9ddf1d750ac3d34371ccd9e1290c73e84b26b5e817c7abd65a8becfbaab5c84bca4b8acd53aa7b5fb1c0d33b16ea955f5d98f39637d5435557646f5fce397fe12bcc458a64d54ba57aa8835859147abdb6eab559ab8d65736fb5ea3a1b1b9bce267707ffd9e40e638c3b295df7f9ae03354dd3b2fec4f15564775deb2c1bd6eeba9f342d6716eb147e77791904c1fcda75af5791d71e61ba5171a687dcfaccd6aa734488754488e52488cd736d2dad4b81a88379c8e9e173d7a134d029c4db7644d3f276646f478e1c4109b13621d6de8458db25c4da36cff33ccff33ccfebba94a753a95410ac231bbcac3c398e3ae931e7c8bbd3423d6a422c168b455fac9d23ac3dc2bc6744cedb6a96e26cdbee901076f66f3a847d3f644318b299f6b01305f52de5faea36adeef9db7116f13711ecbab76aadb582b8b39b7872610db4792ed77b68b7e177216c073fed3abdeb4208c175dce921eb7275767776bb6cd7a552a9542a95fa964a7d4b811b2aa5512854144dd3344dd3342d049e1c3c38a75027d4a0a43a9aa6816298c18799beecc65acbf22198d943b5453b366a09da4db88f45b2eda80da9c7a8b608b0cf4420a4f4614318aa657cd852e7b0f7d56d5a1dea5d0b757048f8eff9d3dd779438869ff7d3e77d1d0a4977d075da484e1a2894d20792f0f350de3f1de4fda4835ab56bad3056e1eeb853617c7c3b4ec4ddf78b3b15c61fe65258a7525dd7751d8f0fa0778af1fcf6897116bf4f8f44fb7bd79d8e42853f89767720f86ea87e394f6fc8f7bd13470d28ec1e8a22cc351becb2f401eaa1bab1de644e21b936cd1ea2b523b393b3eda8610d847d2f76b18b3d47e3badb69cad2d9274a0a82500e1115a19c9d9a46b5170ac6d8fb865abd75dc850771289e9ebb6f841dfb043eccd979cb39e72dfcf7afa786e2261f8adb087e039f6f314f48fea7693987e197ba500e08fbfb08c2060f7e7b4fddb00d3c69a11cbb09e508e5ec55d6a350ceeef0d6a1ee373db630cebf395f9b5f1bd6ddf2c1f0f3bc5fcffbb68ddbbe6d83927510a65afe7c0ec2d7404dd3349c03c20e553a5f9571706cbecaf4f58da99d59396795f85d521ed7a7ba4b945427ebb1930f45ec287114ca398956d3beef7bce393bbfa78222dd591cbb679ff731677b5fa7857236d6da2527e63de680b0e56dce1ea1b2e8a7064b8a6dadb5ca3a270aa8941f299364924cd292b4242d494b82425f55288421fa4ad292b4244dd358fb47a23c497a3179b22469c98bd2177dbda418e19e9e06fc3ce008d6a494af63edc5e51dcdfa8015670ad368be405f1aab6a1bc8922a797a9958e8abfe24431e279609a5ead41e69f24fa7a5723d552a2391c8a0c00ad98a148a54c872690b35f33956ec58a1d4a9d41aa17a070806776a0c300052e915e5ae4f80156f670a26112c39e20c105bc07186924fcdd28592121c70bcd084059f7a23475a70bd88186ed0018b3698b0410c9f8f853558dae0a14c1c4f7cea334b534af57b2a4f284a9fa42c7dd4ba268a302d80c20b2f6a7c3868e28912121a323b3ce153df5379aec516483bb001868c1ad694004493262ab40087167cea2f60227f53db050ba60017144a3043852c767803c9d1529927258c4901cd1a50dc283c09c515139ec0ce300090bd030609d4964b609ef0c02491724055c9d618a135a0e7e701acb3c0306131798166531ada6052d3821b4e468072040f3b58f9c18738b2c49126cb1a2539382e4436365746113b4764100db39f395207ab59b6e5e50b3b6284fec0ee7b3ed8a202b282b12d0bb2e4c912902f2160153ef8481d7b2c76b14bdb06b1ab01b620db5aecea02481fd600db4260d78c0dd9f5d69a1d57cf033007846db3d4c1ce894634ed3e6755cdae3c9a587b69f6c49176b1cc666832e746cea2c8f7087f916696d064a68aa3ccca5fc2d8e34b498a00bac0c2461059a42891c17921862e3a1c51a1430f2fdc197838a62dcdcbca972abe8cc9b65cfa12c509195851caa2091ac29cf0041c417811461138a4f1c28c2af7891ddc981e59ae28ca218a174a3e4835ac58a30217b2483ae3040d2e70163452dc1699a68694316648434b5218274dbcf0418829493829218d222e4b1a2ae02a1c29808105a0e3a8840c596873d22106491000002316002020100c87c3e1a0501245c222e70e140010759c44624e980904c22847411cc420639041c618430000919119a2a103d53cd2f32a64f9ddcd9eb4e7ac9e6430a891382849f415451df91b8fe4cf8538c3bd2bdfb4b6778df5ba72fcce6f119e5f16e1899001c9c598f3330fecf7adf39e9632896afc12c6ae5ba8888fe40aa9a7deaa7b64cec9fdf4906cb419365543d84c44b12e35f27b4b4eaf44dca39037071bd9f6d4bf60f8b7c92c0a749fcbc28bf8f6c03e154b1278ab3ac05ba1037aeca2e67bc8894a8e913c1ec93a2e89ed97f4652b61150cd48235ad01424f866932b947fa7ff42e994a5781a96103337d338766f5d8ad85f4742989570086a426b0091b8be5652acb88ee4b38f70dfa080c22bda06e446e3816a7d216f199837b2f504f4f3c883338bcd0eac0512389d3a745befe564c845e2153eed5b4cceee00e0fdae29a5dc89c5bf342bb5b5c6d20ce2bdfc961e44685afb5236588fd6c75b9cdfbf9470cb00f07c3cb031c7c415de8e2751589266a4736b61c12dc47006ce9b06e8a1044fcf5e0f0cc4044231df29081890ce20f7fb83f350b4a1ac4fb8f07b78ebb47414fa34af5a7cc622046901dd3edb1736ea24baca6a43a4d11916b7788e9e3dd8e8ffc6ae91207f6892e8b858693f8123b6f197a9452ede015db810a69c6f5ddb588c3011d3a378b38dc45274afdc7d37ce6d7dc4691f04abc657031fe1ad95b226c98b4facb36995c96493f67549b1d51fa8188d1ab584dc453647e313c6ca93ee4a55bf657acc40acff8f08d8f94529f31ae88bca547a4b7897082d542e83cec4878122aab06b92fcc44e22ab4aee62eaec7ccd2c2515a2d24dd620b8c4fd533ff91c33f2f9997fd9a85591861005fed15e18c089a51cae810ae19d4de54319def3c6fefc6bcf321a04a17197fcbca8480980991d150816d05143a210a392508e39616068a6ef2c55c29bfaf50c880183ccb23813b8b37471511dc4eeb731c4fad0822768cab50131d5930e90587ec8b5d10d11e7e4ab853851063a418564f6eb031994deb900bcc5541b4e03fef6c8c9585abe3213c5a970fdd12b313dbef2ed98462ff39aad3de099318f3c1977eebceb93e49c7e967d358c84fb649e38addba7948e4557cb3321678f9a5a3f1faa2ee557f5e5589100982fd4c400990628852ab9940192b0e23196edc12a0805a5dfd6edb03cf32066619a43710199f56e52114b7e17c4008df5a6caf13d8b74264e414f825c64e056cd0d04e44a3125eee9cccc7c04e8c187190922dfb7f4aa20aa2b0d3f8de069d0dff78b429d879ab648b68438c6292674ab43de38046ac916ef1105742bec5961597a7a31d04f8e77da303dba9a3caf272bed54e094becaf1021957b52d7368f2ea066cfae6a7846dd83d009e5f94473d47296e8d31f54e152fc42d60d9817959f75a7ba71ea1cc0d2d730aa2c034745a3a512eefc31255a9bd7928e45ff99a3131c182df8ceb2cb061398ed3d6be1338ec28d23ec504de8432453f9b8e4201302ed4fbacece54017c5675ea6d01feb4ac457ed31cac39e997a74078ad3d2988c3d105434e3f3f3ffbf29dfdbfc45f73a63eaa122ffc50858addc59964c564776144773308fe740721bd9ff2e8279cd7fc5d887853d78644d7bac8fcde2a0b5e30d69ad7d20af6d18629dbc0dbb3fe474496217c234edeba3a02e84f5bae7510ac1b6ff26c958dc3629dec2697d5ed85a041573aa31a68ea2d2cb4c7f143bb69a0acfbc3841e50a1e46b0131d47799ec71ba999f26bd21620472db62e8ec1530f9b9a9e44a055717eb412288beef5f50613613edb38392615d2e2821b88d1b8aacac5a176e8b43b70acbcccf4e6df6d9e3d703979b29422153a1059a14355055ac0d26ac652e84015e01c65c4834e3bcc790b438882484ea82aa55678fee87ba388c9caed566a2c296f5fbe692cc4df103d8269c09acda9c82926a253d72931fdbfb0b42f416a39eba5a1a83a8c851c8dda4959cd64d8a19b554b4148e1143ee1188274b2dfc5143390aa2088505382df54743c7c305c9c7f5c4fdf12cee2b834f6f45b1b07904d4593f9c86206427702a56f7deee69ef0d5f3f3cd6c0a64b8e80f618a2928234a3169ddc5f4c891d1f8142f942fcf608481dc0e46ac43e42f81128037baf5476f1f456ac2545e57f040a447c611c9a927da1c36323bc62b328b053e9e40810417c0621e56faed65bd7c9de42b825e151cb30b243414740f259dc6038d3ad1fb4d95c9302c4b7935edcbe2370d8e27dae84a54079f2b37a6b98281aeebebef5ce5a5c04c237d9a07a0cabd5578aa7205b415a01d6c3e90a40d2447358a16fffc14a4156aea8dabf7295305095ab025a1329ff43eecc0c7641df5957b5c05f318db0a545e6474d024ea0c058665e706603299a140aa6220ea52410010f10e7dd913ee81a78d915bfcf07d3278122f5e0c9ae3659f41f7df3967e005279c3bc8b06f2dc73458ed045dfb57e0a7c295a013df4bfa782a0d1f40fa3461d91643070ef7f6f06e584dffc5cf58b15f8441dc54ce7e7b7b8844a6465c51ffd37460a2e8a578c952ee4163484e3d060d3167ac657e98117d3132268aaddc0c8982b3f214d265e1870fcaa11b0af0113903bedd7f8848171275c7d32aeef5cfe631b3f0ceeeca8f19e83e1839109a96a5fedb307d5be1497d7a62b92e14b1ffbd8875015b3102fb83abbae86de9f647b08812e8a1e2b1e2cebc91b1318d574a39b101ea17d3deac48dd5fd0b5c022b9f13ece802795d74769a6da88fe7f174473f99e8bb0ec269470a88a69007559f529994cade9505c2631e351a99341dd9f1d1575871e43da545ecc767da0f272f359ca59222c5ba4945ce2bf1c57897cf67d649c14df0181feba6ed45cee117811812ffcac37231723abe6fc4d71e082886afdee3f06bad35efe73fb5cbf6be631f210416b45ae7163a03e4574c1dc368035c1380cb84f3a0adec401c6b5411e73af4c5465f05464fae05bf3ddf4fb1cdfe1d918ca053788648d30628dd071a6996080fce364c1fc53074c7d77b15ee0aaf50340a85c048a3b57c5ba07471c389db58ddde4858baa69ce229f31de121608e5e2b19af9f0059001c4b290d6c0be9cde7966882598cc5615d4b1974ca9d77f3dba3ccec9481d962c9522678a527e430ba7b850f6a399d3711a561e16f756e24f502af47d537a7aab56b2929e2588fcdc68cff9e740400593c9cabbbcee707ccd5b673821d410758e24b2c8fead9f213f3f66647547d230d4b0d46b00f1826b57c14dae220a0b0383cb09702e6f0421b44b800ce4cb99d52bf08abaf1d2b90feea8be01cb8d57d6143627b0122db4e4453810dfb0768605a35927d01cbd269cec09450cd54382374519f60131862aae38161b480afa4098941fe06352059eb2fad7d7f92472f5209407f29967762f8a7398b662f2a07308362448ac6ecccf7fed231670038ec9e1caa29f78fa60a37fda56d05fca97b02c782572b651b9d945cb9fed2bbcdbd6a6b27db5f8a0e4e5e246d10d405d04e76c28af32ba53beea7307e9d799fb35ffaea224b11c9bacec44f6cd676a62ca96e5b62aa947f4f84bcfbd7e29d35821da6e92aab73d4dbff8591c9d5987ec7906dc0be0ee7984a56f9b1cea86a3561ea307d49700bc76bb9feea0cf0ed24d54e2dbed5994ebed0aacbd43573c674bea0654ebbf4ec4cbcd8811620d6cc23025be89f965c48fa794cea5d6b4017ac718c821b5f77ef88c623bc90cd788115529a2aea3915257ad6e9f61bb69a1e272460aab6ba432664889e06a079c69df0fb7682082c329405534e088bd9003a4325d41df2f469dadddf54236ecf2446697901394823861322ae3c22fa838957a468d150303510a0d920fb5a756b904de073992942a6169a787a71d6079e761ed1c0207d42e8af2291ed38192159faab763662337225eff40bead88629f25de7f122917b660562776ab8188235446d40ede6c2591c55fa62cdd9bc5a5f8c42e501388948ef342e999a948824f7eca96c9bfa62761a4844be8f44c21de0676dd451f16c4e1d167926b6212a0d23685d6509442ebb1d45dcf177074ad32010293c91670ac894247da7af957c432072b4eb4db1995627b068d299e974dce8ce2f57100291dd39d4726202915a55c199e80ca443205221d79b4a3887049995a151e9f9dab3a98535e9ac9f602038bcb93d6fba2e433d51fbf8132cf8b158acc91619142f92ce33b3df431486a45359d7592e09198b6060294a5474a6b8e3f428b024887c1205ecbc60a9f0cc0a17aa7cb96bcaeb04d85c23b7e03c19b3b870c83ae7a2f1c15c0033f8346531423ba378592785bd5940ec93d75d4e712b0950e7fcfe7ece14efb90781e07c9244b2269188c527691de70452731204786ece486f4edc5b0346185914f8d1229cb31615a0233ed940c9191ed61052f7e29cb4aa54facf1b5ee264524d612925cde0cdf41bbb4fac69cad4424f100baf5127178c2dee431d01d1417602cf28804e7ad30edb3ba6046a18cf60058d12f2bda9a3dc1dee61734d7528669506f9cc68527652c75425ad17c7824e9ad6d39949e9de2590bd5fac725f42dda7888ad81fa81b5b554ba807b773f6f5a2b46cb311b01797b742d39942dfabdfad5d0540a1d31cc032e768b46276d27e955621704ba87c2a63660c9c6a566a1696fb7cd5cd00b3b3450f038bd45832981001013c2de49309ed6902346d7638f6881f5cdea954a092210ef5e519839b0b52dbc3b019d4dcb8ba08afa65b909dff738c991170544eff093a51a3c7c165207a1c5ba0f22ad922082857d54c282eab8b2240a4d982de1705d7e21d983ea8932021ce540f3bb84392dfd83180f60a9fce347f8380c27c501b64552a42ec8364dc85259954a048d02da6ef7a830cc2d76d870282e5bee2d09754732e0daa1ab44230bb390f68a486a0e079f72c13c099a8614602a04ae8d022d4d9a252fc732767cacf42bd835f73305e74c6e52e4171775c03d83dc37cde9ed537c331d816f81f4bba25dbe44c102640d6db1c554787187c76e55efb98ef3f75525955eba8eae2e1fcb456133e0be5b9cdce0e9721d6052f2c9bc715b7b216501708f3d9359ff2892bdf5b90a2b827363d19808c8a162b0509c0c860e5e37b739ddf2c8483bc1d3b057071bfe7f083db4595d8ef0175727e236173a21af9629874e020122b4a50ea3c656eeba9c7925e04c295decc43f2e1dec46933df1e33d2a71345906f8874c35aefbc58ef2b1c511598b9fcaede30f43284ec5f3a9749dee5ff483ade21b0d28ad91f20a6ed543b2cf394978a4206f48a7413438a16333fbc55d3287831c14e1091bd5558529c093eb49f2cce1c18e273d412095e8f3684bcef29ed8a3c6153c7ce0c2c8eab5708caa4dfed30b7fe0d323d0d8203f38753af28f8672972ac1b3038f7402a46af22c038aa0f9887d10ee60cb33b8bd445c442f1c52a50672a6d1d6737747962881fe3296684744ff1790143ea4452e54514c231194222a476741018342d603d4b6a9f72370e984d951c7e98d53ff515f633402da4fbb41b084241840657775a650154b5a11c0c08cfad58d2c690770402c9f3d90fe1a30c80d48658017def77c43d27a2961313e9a6447402ff8bace77af2ca43596f1218b75a5b86c63bee1b1f29fe4936097dc5a4de16e29253c612858a3e8a7eee2ceb9f5ae0013ba9988f14e5cac068950c6a22bae9963687b707ad0d4e6776acd7c67a9be9c9994e85ccfdb4460f7866b0663919125ce2e59c5da57555b4bff04374f4ca0c2b30f4b08a89ce8ba88b8ba63e85eb113746e2ca46fe0747208dd20a74a07831a1ad94924c13f3d070929beb2b41f3991cb19fd2a728855010055399322b5a403aa3b0c0ac1174a96ae89aed96cb41ffc31b9ef5c8a0915bb17e2dad03951155ec9e557935c8ea043db8c5c9931ab7eb656c9583041994d071025efc9db9cf327b3219ac07f51b4098a13c8dfae340fc52c6a0a7d02adcf848b46004df48b3566c99ca8637f01d9dea726d1d1440e73adf38890ac62e71984e4559ce4b475b0eb509cfc172059ed5d41e0466e3946b0d61d38a582ca36fa150192ba27ebf014cdd2eac6005ab0f294a683206080104a90938b3e427da6d6b4c5dd83248e1289734631e3d2606393c1a450957175b86c4186eee4a2e74bebb4f4d4d8124eb18e8b3bfd8d288ca34239fdcfcde1a45daf8054db866c8546a0ebeb598c076d881dcff194559a8485eed59946046765b8ca2374fa69acf6143e3746544627c4fa9ca60b6a9e1e03428bd1892c4e156df3e2de64031be57926e890147412978550ab54216b12aed0fce3f8284978ee0da8295cfefb133accf91e1061d9887775da6c0e2fc9770a418a93ad2bcb92b19787b76d9bce806c61bb3364087e3c493fae3647ba74e9c29c163ab7a41f4a6f0e69d6e84feeb60c577b01fd462252860fad9855131b6566baac3ce118bbb98add7f7840bb3094187cdb15170187b47ae5f1e2bf6f533923dde62bd852f707b513c60452bcb060c854a0fe7877e7a583a875521a1282e4df448b241a5c511cb6a4cc5137e1789f95a88d4fd46151fa8d0c6a35d29cad1721019e5d490f7389891722de6c4c725e0935f2b007048da88a5778caf0a60f393b77db243af30a53a0409dff9213489b7d340b8daef868d8c7b6540ca3674abdfa706cd82a0c55547280be6de0ba21fd5eea1510e054fec626e2097e4f08e00891d4d1b69c57f5d47c8adbdc97ef24712a2e5c38e958f6453006f3967e724e307062107f6b96ad0766e9d4e2d9c626d0a4138ca26e9b05902b5f66aad737d96e1178763b4c149d29c28e91d43801244f1ea6de48d764c4644ca8ad80ff0ad5a36f5bf3cbd04e3df981a43953bac60fb7302c8754acb34c4d24d24a39718c8835a0b92d23f4eb207eff06f4457c9005d1f6985c1bc4b23f1ad43af0b0921d9a2996770da3e966fe65d8815521e27a70b7b38acdc58676e23f220cb538ff2cd04e8412931609d7bb085a2a08f9dbdaefa87c1e314dc220a8e3affc9e3892384882a0c5b77439f87496b4ef222015f905bd8077612a7d8c83bec933801cbbd35a9816bceaaa522d5fb553735255b3a2f5faf542aeb8a184c38a143bc5de9957de4f92a5ab9c097061bd3ae3cada2692915478866e90831597821fdafca7e314a162abab7db911cc8c659afc547f2813ecf448880b91dc1f56ebc5b2a26a46c5e15037df484118582f9d0df163857e26d30417e6f67eb965eba5528d6154eafe3f355a2a849f89c3d70cefd3140573cc686a65f6a79576642b10b21746f929a60ab6062ca15818f5248557b91d3b24e6c7ad97890c7f031b19307e4ef64135e85b19c308e1566e01d098831f222401590ce088bedb83ad2a72eaa7d463451f616c08a5a45265984c8f44e84bcc330b71bf0b66e7dad0d7c1e6807056bd0d555ff8b148535ed0549dcac0b413c13738a6eaefa3610cd4b7c6f788395a4e079badbf2dd24444c06336927da4218c3cdde85bf51628f12de1e6f3ef8296562521d33e2061c59e187a31fdb450114b9c0f80e420e060c4dbf20601f0cd8a2b34f41c6831dfce5aa690f0eb068554ae4b4408f4d851b1b9213fdf026bd9002da984b9d8753d911e41b7819c0dd1ece2730d7565333c8a4dbce18ab77e590f101e8d3aee9cb871d1c54f8e810b8d947d02dabc59c5eb0616d7163f711047ee5f9f8db53865ad96e82cf5dc4b4fd4a3e2c6ca42393c1feca0c7869aa6a19f1a195fb6247a17829dd70a03f066c869582c0015cc53622e3ed53ef104516ec6b8bbe2c63afdb19fab149a7c5beadd8601adc865b38f96143771ef56d385ee1f062ecdb9cf8d7756c126ec491ee38d33024a80473c439e1bca9be47ad5cdc7294568f77071ac7686129724de9e847024bcce9edda35baec8eb97b97ea3935091f036944fe8988d6979f5eefd849d646572df8139b1bc71c8ddc748201b9fe403c4899ce3f95df8aee872e6e219cd62bb096183fce4abd3aa4292c70c371a3b2f44431a16cbc29940dc7b31660aff478e27d79d43b75b0bca74bddddb87a6b4f87e07ab22a27716bc40172f1d1041bb5474bcdbf7f985c6c7ee93413cae1d52fc893f4263cb93dd1757bf083307d3960202e74e604553aebe3887ab2abeda776537b18e6cf9e1da0ee90c9c119b08ad888571dd0518cc04a5fdbe3f13070013f09949b9cd8b503b3e9d4425465a1c0394bf5014abea625e80d8e7ff51c284f6e91d8a09191eaf8afe265ee3cd26ca4001b749902f882873ebe726c973d8691fe43cd52560ecf1381b62e1f946d689dac2f7d55a5273a4e168da8f107163ab5b49e1eeeccec46ed812acb70d1c37ce224896078a7a1fb3f7a831ea503dcd6915462c11588241bc62047441a0234200b0a5f8a87c1e09c2f21829f13ee6c8183cf164418cecbc2e7aee6715b6995645bf9bf920ebd567ec1f0b57eefb4d0ef95d0a1a2211a5484ed278ebc19b6f511e1859b645e0cf1b3fd0810f6fb8f3ebdcb925ea3fbf7eebdb02ce48f2cb80a8659f550f0cb7b5ce422ae650e274dba1dac3fced9854a957cc51f8cfe152da58048b99091d9f5539bd81b68e82de29aad8bd85260ce7b76586c573adedc77c4dc9c639f9a6a81e520f8c33772857d13cd1fbb0f782708a76b5ae7ea3a02d1c607836640cb8457545ceb4a9fac745057a4ee12556329816b3fc97c9c8176caedfb01be52eebcff14b4ea6aeb36a5c665b064d1c5302e2b76657f32ae0f31008e45ccbc09c4931e060de24c6ce30651e45598660e405dd9760fd9b5c9d1cd8f2b09e8f4f26bc2078ce27a46a4f278ad9a94c5e76290369f07f843130b19ec2b32011d8302b90c6a6588b3b60cf8ff694381a0b472d19e57130641191f0047d3dfd0d00c9a4dddcb9532d76d22d59a18d74d65fd21820f1c883c06d6654f3dac198a9875a5d42fddc2a4072bfc98181a00bba5d9574fa6a37618191d5884c14d5ff1e999e54f6759b8d1b01c0fb23239da356fb587448449fc90ed0284220f4b58900b6ee2a7c1b4caeaff980ccb334871ad0e9110b4de23233aa24a0f071215985105af4a431563fbce53817fa6a4dd39b1749a97de50550529e30cc713a52e84834604c217f00f23b4973ab9206deb16469974435183c3026b8ec9861bb5d0c9ac304a97f4f4d96e84192d30d4c01409d1f81e6013134fc45f077348e32e5fa792e64123b63b4f4932408f415d743b12c4bfd1b1ef61f97fa10c310a7595c9f30afaf1aae2480b4e83cfd57762c0222ec878f935490d6d797d1e3549068f5f78abce74fcab81b8f6378976e2815adebed20a07c4e6e97551f81f891dc8f4a3681beea06d3807ae92677370547314bdf9b1a27960ef07063ed101f5e6ec8b6e1edf49bf79f57860ed1fd1244a097158c9f735fe33511c3708479f89246f7307023a8f221fca5d70ce41fdb934e5ac6240e82940a63429d6c674b4ba48ac57449f9391f80ea6a1e646bb86526dcdf007c7c739e9a9641b610b3d8077e124112ec2abbd76b2df4e4af820bfa70899ce1cd62331afc629727eb4457826173982a2666cb756600265034140fa3240ccbc6b90fe6e92db9f8c3e26644d0269fab16507521a3e9e5491319cbe7df68c1c1c1f65585c4181306a3cbdceb582f7ae6c9643124bff93e225187386a33081456994197358093bf4725d68e8657b4c64d3d1f42b4aaf4dc02ab19a8b79506ba96094ec4e5edf03324f1c43d9a6a69f32c9e94074afcec8964c2c8c3dd89184d15d627075bc29c703f68967536267bb9285275b9d5906798545091b5a1946363685c3e5470fb788ba1c1afc852d1080ed3d0dff93aeb38b4da4e40c990b10643d921fda60e40173a631388c591e9e3eaf7e11d109278b1da36496609c44c75d1fe5b51073b984e17c252cb6aef322a2500e0c588110827772baf26d2a42b0e6ff6a477d3bcbee330ef0e0fe16f8fdaeaf35ec81a8072eb3e12cac3c9805bd651fabed757c74c5feb0bb894998fa00b1562555c957ecbee4dac4bfd8d044f4ac9071c51f8befae34d1ef66eb8e268c206a406e3e73416d98c6df4d885f3b96ec827b96a3198608647f6dac0bc4a50654375b5d9033920109587a565024174110c63dfd70e6b5323ab8560e38cc1d34d588dd74868101067e578cbb61c09687172d314cebb8b8646dedf2c94fcfb51e7cec72cc08f9b98c5cbefdcb2719b27f8a2bf088e0c787f685a418fd7e9c8bd180458be39ee23af45f93e18aa65f674af0ab8509f18aba9a8737df32595501bff3534c3215ded5980347ea005d9970a9bd3f74025fdcd59692ec92a9b722e5bd920bb24eae813cc29ec73d0a85757a35a143b2625163a5321b6772f985e590cc0dfe14d5e779b80f0021497cc0e5e3603487f488b9bc82bca719aba94763b51933ec9a63803b9d78d1f48b43bee1572e0119de927056cf61a9dc4b79569d356152839ccd70b6a07d347dc856820a4f3ac13a16401ca1005b3c10b860e73ee15efdafd062f2bca07286e33ee26d04ba7594b55316d78bbb21dfbfdf2b5ab630c96040fbdfc337a41c94d7e63e7cd23b68bc536abf38cdc404908f58a14197083352160943ccd9dd92e79c0df9ec909fc83fb2e410f61fdfcd5663807328f19680c600a559a9484be8a43f8cfeee5c00cc045d38615c0d33ffdd52c862fa517ad8527af79bc99b64e8e27a9c0b87facf608537ee683286f10ba21a7d3c95762ca0203ef50b0bac7408f15d4988787ed498afef79f8d9231700922c1d541ec4e908b32bddc65a13cb8d9c6871a5a7e601a2037330814bf83bddfb2b02b0433596cb1888307d479ffd3999dba558ecb400acecc4ccd169715b984701811ba53a5ce6b4f102db471ff0633df561f3b2072ca028c2ad0e19d6e35dc25b54b433aad418909a8d3c2e166830454105e50097dadb75735d36228b32aba7ba2b1026cbcf074c5d2d993a533f4a3a6e15704e6cd7cec5c5403e88a64ed0d5a828aa61abca2cc4b1d81ad6613fb199f1ba22c8de744244383865a429ad418701b976646c527b6e17b539765608382597f003abcaca35c00b646220314c48ec6d6e4afa5f9a1e295eb456eba3a6d42483d5528b3df802eb07d540a8a2c085d85e74c61eaecec3831a5d2cbe84800713d8107141bf49548e82ca99683f10ea77889e1dd34eff027097ac7469d825c8e773e0025897ab49c6eba8cb1e3b82c8c2d48e70bc45a91b89eece59289692511fb39af80c4207caa64d39b5cb78b700e0026d79a0ee295123e76b42c15d2009f7d9cf7297caa557bf4cd36d3479ae64cbce879e97d8159ecb5fb3d954e4654595f0a5b889c035a8ec7ec514cdc91121b7322e9776bb6e522a40ae74e8acaa85d36a48363c77bb66360808559a226fb103553b44cc8a188aa45ec51d3e8ea3ca9808f9ded722f336826db0c5eaca9275f73787daac109dcbd80238f19000d26d0fd617381d5f5815af2877fcbdfd3212bb33a08be94f4ac59911143ca621454fbc98da9189caf506971c86fb5c9defc5e1c1f1297a08a85d14526685c19bf879e320a874f36f8430bac7cea69b777a08f97b19ba33697d9583b7900a35f5a9a5a2edc678bc2802d24591383531de2395e73d8e2771a17892f2be908467a171be2905d96e21a0963a53c34f02449b19d6a8957f2b7d842327c0b1700b6c81acee7cd3b8c297904f9913a2f5b3b77fcace3508101dabfccaebef5bd5616b1fbbb84180bd97a3fba3a867921cf67e3dcc3a05ffe1fe244159c0ae43d02478d905389c7f580a1e434ae7bb8bc69997b107bd9ca51a7f6931d25c5ba4eef84563ef3e0a1cdd9b2b3893a69fb31bab83df498fab119db758e77945d40dc9e48c187b994f222aeeafcbd4fde7c2738eebcb0bbe4e24bbe2f18decc53f773d0f0cba015175e5773270e2cf1ec6f001199168fab0224dc9e1d9cdda67ff9828e1ff0be21d7820bf01c4df023709fafb06849d32b1382eac47c6a4f64bcdda56c07ac257d2066d535489a4c7fc2f38c7fb2216b672472474b7ab63b2c16eb58623ebf44e03edf105fdd877a09df7ff0b329521903807fee5bf300d2c09e8b39b3c42104ca5ead0b752cd3c5573871a9abf844f4d704441fe1d9147bf3a29d3e9f4260e3bc43c9e0d48122ca80d13c6c5eec40e4a4b56b11bd8fe1f1ac717911f1941496fe8a631a45a39dab2b83451967a96e59ada704835dd91091a51a446ac6aaaf6c7d2cdac2fa8254f7d1aba331fe5b40f2cd948bc1f0f1bb2345e9f13ec80187bef04b01ce6db68b0fd06f2f9259645d40686fc98237b1f8ead2c0dc2f38a9d22424eb61c5fe43d6790f96b0bc3ecd07f93e11a203d90b3c81e0eba7fc90e2792740cf750402d16a3a2b5c098184113efd0a870653d461e993060181f88f8ef60d93c04f01f66150b90d5df368db187a2c8182cc63d1fda6744e30ae6011a6b569969262367d43d4b8f19ecfabfceba0d93ddce0924f978f8fd45abe26d0b64e1d5af97527ae7f13e30c81eee8e868f134cd76f1f66806cd67cea61bc30cac2d3ba1bc33ba224f5f1a82df1460cf92877795015503c7f9a5f313404cc7e8912b3bfd9b98307eb14566eed05a3a38b9cf4853f8177a736ff72cfd22ece5e54b9174dec4a0b056c6e56213d5ce8f1e7eb8b44228eb1a8626458140711b0f5f9575dae22b21968b4e27693855818735cbe91cc9a4c215f98d22dbc6b0986eab9d61a4569a1df5d1ee8bd303e9df43d297b3937dfd6a55f3f7a545c111f209c1e06db2efab9b9ceb7b764a845bf72ef31e06d5a4a774362bc43eee6071ec9c879483ee867b996943203f555eb284052ba94d979d2d01ca7821ea6f29b6187db752ad66b19815c380dceab1827b74e3bed401384fe6799129537fc5193dfcee405b6453ed64c4b21a84864e8ec1a95596ef21ad25f772c4b5a9efcb9fd64a91f924737e2203e721e966933968b73337a99c91ef43908a123ba2964abffb66b18d9dd489010faac0251abbb5477c3862b07f87c8b9b792dede837388bd20e11cfdf1c9ea74e2c5016411f9e5721a0fa6cb11e208a79500d40b2dd269fa45264671d4030e0c0ff536cdcc7ac7db60145213538ec10aa3740f17deb392acae8e37030add0150c5a08464e69c244144561cf9811996e8cde52c4f38de6432f7a2641996998b8627487065d80d286e8c072808bd7dbbc6eeaee6b64ff3235fa7c2056e0df1fa11f797fff3c66b40c49f7c14d104572731f4c7046840f8a6237cf62af6c2f1b72a27639e0cc24c749c85035876e7db01e6f3dd1b1ba341a77e531ffb75c37c6ad51a71da626704869d5a51da6e5ac24ba3baef4eb0ba7e34ce95b350fd38e76880b37b3e396111d2d5d2f1b97ea91dab01cb4f0a7501b7a9134632ede9ea664e34e9d86352c175066fac335671627cd2b4b9572a6575ea96f501e5c3bb5b4e6a6965e937aa5ecdaab47c7034ab74a6b5e4a5daf9b8da3f45a5748be1483ae8b68dc8a6a168a9beff7ad66dce922ed1a1cc73a8a9e3ebb0e0f85bf4377f9b56bbd9d69f4cd8c23e57a5007268e94d2529fc7e03e7a989372bd2c1501169c7af2a7970d7ea0a4061f6c306417baa25be74c8d30168ed1b19961b9214bc75174e1c52cf428188eecd370eae5c4865f0e5c535a4562c3b439b8cae98d722e7dda741c297d49fcc25e4277d4a518fcc25a2e58dde648bd752e360dce94be8098302d1407d261c2ecbb50763bffe9b5c8f24fe74ae6bab8f477651f37913dd7b2a089e1468fe9d1bf52233873e2e92d9708968f99a9b4e68fa85272daeb95df8d5c3fe72e18bee02a1973fa5df7cd38db15a260b4d7066b1b4577e4e02c175dcc4f471b05e167e348b9da1d2bdc29704a69758d15a6edacf8b95f917b6b8c33e5c29728852806ec9684d93b52e63ebfeb7d046602fa905c092ce635e1a0ef161600bba63db52fdfc415ded605e5845e54fdae635971a3b476458375022014fc3812c595919a5752b2745c29f75e74c2da03ca94860b9d30d87f8168550a201eee03040d03ac7ca2bdadb32efb1df74dd5c1360d8eb99b60f04b7229a52df50806ef058794862e7941415e2eee969a02c310ede0f7b98301934928b832674d490ee5d52a2eb1734cc2a9f8e5a81685e19e2a442f58066699eac3dda7f3938b4035ab2e38d3ae67fa6d3cda4dc767029970867dc6276865c8149947da1369a1fdce17497a951269dccb53ce501c67c765deb67830f06a249235c6dd1f73c612fba7eb35d02c8d9f90ac655e86eac26ca48df56243a53fb580526e273de4892ec431f298413af3f64a8405c0c357842f10bcae621472a932602b2f83941e27683d53eefc1492d98136b11ec16cd5ee46a96bc83bee5128da27744ad3c5c87d78a21150ec48074be1b1fe45c9435405058f1212344a9ce257a3cefab7c10a5a95b35e0ecb13cdc2dabeba7ccef4e57b4a28e35b9b1c59fb884fd4b7344b8c207553d2c823c8b8581212ff239c274dc82cbd5342009ac7729e1411df8ea7381f3d9b56c6d137c94fd229b2b89904ad37dd70d6a2cb4f61476c1e1f20663bf946ddd870e74c6c7185222d9a8878f06ff5ac824f8df1920b86bf1acd1ca1645dabaf1ca564e4ac382bd01ce59a97b376c1ab32ab6005a0b53dfbff26850e1d356065ec0d25e1b9ed39206edd76e06f6f0ef24b3a0271ae478a323573416a08a9ba9786417b182afd5fd32eacc5e5e5f55656814166fab1eb5ea9776bc446aa4c5ad950f78d59f9e72fbc961e8cf9e833c0c1e946712943fb8d6332d4bba9f08ce60ad8674ea3e2f7a8c5b232d4a3f0da5746a93cf82ba5c1867a5ca4f30599adc02c4b0e75c0602aed6cb5963cc57e794eb57d3b7c02afcba56190854eea8912c1ea55f14f70d8f6d956593634c6bc77978183252e4c8995e5bb36f0c37102db6455cd5801c79fee8fe02c0d427bcb42977dfd05727e19f480c30f27fc338c0efa07c7b4687229ed14b5dfe192e3b127bfadf3a7e6b31fcde2e606cc4437b6be1ff9a7491e6121c91f6079d2e5a276e13f507e8db0d292bc16d84758527ea81eec8016f8da288aa2e9d7b81af03a7401195c9d4a84288e23342e468ae27e00f1f233e83c17f94aa3b50ab0b82110742ecb3f94f7d1a6a89dd6aac217ae584831e373ff9b97cf010a4a1c48cdf41c2b2c389b8260c73d3a7635561b0767b1e31f5f0846b463c57a494aee9573816af2de6b00990d10625d5387e3f11a3a064e7e68b871c17871d57a5d037d4b6d18afd1209afd426c83cac9367383d6b149eebed5ac474ba079670ffa8a4e55cad8d4ed1823d0cc4cc6d99cddf5a7659a17fdb2505dc6096fa8c9e2d9fd910e705e2c06bf73e21cc9ff658ed6b00ffdf32b8bf25d7098761017aa8ba828434ca9c60e375bc7526744d84b8f014d1b9cd0de762527828106d9a343d2bafd38724da34421e3e197a9a079375eef1b175be45c7cda14b7cff6fe81a72eb603e3735681cacd76f16ac457e42db550617b6213a5a3494573f850dacf02581f847dbc6a719e8bbfba18716b1b1d772d645c68afd88974a25422277a897b143e420ca6c80b051ab2adc5d47bba5047d56b9942bc050952239a18151f874a024143ad536dd108f350225f6406376e61bfd465c50f0ac2c8d78e8afca6f480993e38521b5cdb7875c0eb72ffaf1dd9028d8382722a9cd1b2689891af624681ab76d66e754334c60971b948af008af7c08f1bd948264cfc2fd9677c444e37e18938bdf0e4a64be92c17a5ee8d4811bfc25d58af867388670d9d6b9e029c0a7a84d0efc7843e4fbf212906477bfecf2338d359cc307bdb87ae0908414b345b8ca543a66f2bc4acad3f93d92728b05d0a04f9da48f231c82d23297fe6b14c1f80e36f8c0bbd224192f9e3e0d1708c6008599c17427219a7a79f3ffe14ab5c008cd60ffa390313e0c8a3bdf5beb32ae8116076dd4f5821b3484010394eb9840438d0e0f43be9d54d6250f234beb94def3c40570ad28f321e0c3d661e0bef41f1fd072a09ddd4e9240e3bb4cc4ef64f32cc291800b849ff47c7e014aaa74a964ed1576045aa6efea77087c1a6556ef1cfd5e209e44878864926a727a9357b564cf263ec8679a9dc9364450d6c975b314474095e320cb65cc6e50b0618086d3c21b670f3747ab72b03c721806ab0058fe4e1c17a1f6da57ef3c9a4736a016e814f70c1a24b9dde97a342b4d11d7b25e238ccff7bed65cc6bab42f575364463e776c6cdf87538818ae9d1868849dbb1d603072e435233ba66bb8b434a92e3f8e1150cdfa3ae7f0bc29e36d15ada20eec357c32c5cd048e25055d748d6fd10157c3658f41e23d4d771a66cc50313eb1ff82abcdc1282c645a7eb2894f4c9797f9868395fe56e485f85fe4d228cf82114677252701f398a720070c48b9edc37ff8fbda1c178bd67f5464bcf9801dc40cc305ad1b6b37d747b2c5c18d1c87ded2f9750043e3e1bfba796595c1e9bd4e342bb3b0a79794d8c07820b629d68c58bba85a7c98f4e3ab08d52ae016f8703fe924a8e668404f0007ed8996bcd601f27380b7a3d4d4988dadaebdc136eee3bc68dc6db5c39f950456fc6f508b62e9a36197447ecfd707a97bed86a4efcacfcbe54c4cba09ed9157c77fbb2e6993c6533f83f3d2104e85c786b6f75b63404625c84996ee301c1194d5145c1a0f1ca8e00a17582c315d89ff6b4b227005f441360059cd8bb62517e7a2851e7c15131448d808e0867ec60613ea738e5927e51e4901edb05f7d57acbe1a2c44bac297b1cee91077ae1db5b84cf1e125392d6ede32ebbf6fb6fd7aca761ee79d97e65762d5a224901b3441759004b873d9547270efff35e218318e7426b4f7802941365e43d5ca68d0b053aab3c2450b96a86cb3cacae0baf9de2dd7b97324e69848cf648b772c2e772be6359816ae55058b5f1818d48967f704505928c1c58c2949b9a3ab020a84b78dcfe05432e1b75ef5fa93242047ddd376fcd8d6247d5845e66a72a3fa284e6d972efa573123263ea70c091c8541603d11e22bb46f6596f898ee3e4fa94e135e3ae226e0324861ede129e6f40e047c852b1ec609021ee6bbfcb9a843c772b6894d24613d550907fb36f93dceec5a58444e49024c428cae37494774cfbec49b937191d025c16e7d1d85c2f88677e6a64c993522b1591df46631eac8278d9aa31ef170db4ef970883eed55f0ae50bea40362e6dfb8179eebbbe2fcc2ca2443e33f32038fde735454defb168342029f03e6d0c285f799a7dc5c610c471e784ed40763858dd6922a879bfdc48761aa7562e5ff07449435e4d9b234cf20bbd57c4742a96cd44de29b124ed1d4ee1f4c38d94177bd95476ea7286057b7fbde36958c0c3f89a3d6cf46202acd21a6bad1c9f123674bbf3ab8e857dc692461f4e5532691244f4dc1d2fe7a3f1b184d634c5bc409377413ed4397d27dd73efb6a42e9a821ab802900d1e7988d3533b2ba563ad3e4a5f9cecb5d8f8f8f273cbc9bc122a8036b3a61f2daf6ff48a8ff3d9cc59ec6ff171c34e81d4cc3c0ae3d4f137c1df007a79f32baa2322857d682d67134e0897fcb5b6dd98076c285c1be5c069ad831a994b9803c614e42ebe06cdbdc0fd55fd47f668e5eea90007b08721af47f9a5099cc261534e0fb9736879c027b96c7f04caf720fb603e6a043cd5270d5cce123fc419db24242545c5c1be416a5c304767405fe36df84440a3a4bfb28548a6b60fd6a46740ed5e497939b421e67fd0c63e204dace01a74368c55cb370d2d426b129b60ff134b77257104acdceb7edf8376d753b9c1b0964f0cd41a7cf28bd297e9d386f779d9548d81856c2201fbd07d1a45c5e2be42c9a640c046c765f754fc31bb2a38ddc9a590105f754dfd98ca1ee76fbc64c7a32398930b54fa5f7dca1a0944366a81932972b2f7fee7b7cba6759a9ba7ea050d32c8102fe83909ae496aa38dd54002b743981c3eabd05261c6673c80e1fe53c14b8cb448f3d8eabc789f2adbc869ed5b84ae870bf16b0ba491b145b5c391a6c98d2463d7278b3d12f93fb9422fd185006e66b213f535083f79c4fe187af011a20b0dbf7cc1be94a0213ab71be70c15e9e86343928002ca332aef4ee6974ccbb8092d75f770f4796d7f5b712c24a6292a24635e6530c2d35e764f0370b2c23d1b88763d9f2a3584cd869240be08999b02cf4f039a34148961e37c1f56e853964fc3cbcd34d94fec6c8d094e15444ef524c45eb449a3ae20c7fc6f71b0192b9f50bce122df6fd03d2a764903a93f203c7a94389829cf09e1d842cdd49f4fd44003e841375dbdd4d9b74f69bfc85207fbbe29a8d1457ae34ab75b33e786b381d9f80c482de0a4bc642dad4a6709f67ac99245d5fc1759d787491d9bcee2c0558e5c7666266764565492e165f07cfd6a80b829ba381ae4263e101ddf798433510123951ed27df3f6581a61303f3d9eaf13c15def610d73408e36d0666d6aee9500dc2d88a949c82ae5874b76d03e923c6263ea8e4773cc62d0299f81e429f04ddbc179f4f6d575ace2bc5d3bc3d99b0cc8e58691dc5af3e0cab0525a4bd420fef85d73387be448ee3fbb8739ba98517fe8caa84a84116744c0987b4ef9c972440f8cb4f05cba995b38402d9afb93325e0ef7f235918be618418daebbfe6372bfddde723440b84b4508e9802636e62d3172076b6b5deab5419ea6b38c0958879ed3f39774f31cd5c92e47b1f8c6dcb93e8ae66a8b40bad12a3a2d5638f99d3b5d18ed240a284d19d020d0950e33795263d43ebaa93d8dc33a1f6acfc9fd2ef6b306a8699f55c1155493eaa17c06d7bfd272f4966eea01b2f2b60efd8780129a40453395ecc8d92eef2d82a96d77b96bcb524f87c888c75b11f319073ded2637dd6581a6e7bc81f441872085ab29f96d61390eb789c00c948f70c97285f615e2ccdb481ba39a13ea74d1bd6bd336bf1aab18557f77209dde1303cb023f128a648259e59d39cc5e897734b8f4571844cd72507b4e3b269a14d122464677bbed591443800b7460ffc9ae9bedc9153fa596f73398bd6178b3529b658b942d3f5c780b115a502fdd330f2c16fa83b74a87f214fb1e1fe13a8cadbd592af88cae66f303a732366945cdceac29698481250be7dec1f8c4dd94cd9e6703f2a04fca6a5c891a6e2329b8eb870d5d52b982db191556921341fd304313816343fe194b38360d7a53d5670a7701fd9d52c54292f2b2361cab5eaa36ebe824aa488550522f1890b01367c4ad00d8cbea49e0a29362ebb030f5569fd1437c263a4d89feadc6323fcd452c298b14cf74fcc681e1e0ee852c63e286805ce27803d30629143dcc5c572101f7417a7f4a4e23dc0804230bce293fe01f1a4af65611f468c1206432196382e4cb65df6c14f6c7e9ccec81a8138ae52f7103e77fff7844c84cf7fce6947effd913e06ac89defe00cb08e2b015cfe0a9b9594dd9e15afce6aa27e370099a26f8421979df63c64cc2c07dcfd41a3554a66d8be7a909767403d4abe3f35a164d3b1770c80139653c16859bd38f673c5a66a97590c6e1ed83625eca66d46f16d10eb2521b9e8cfa138043237d632b32f13f93d2258da4cd928bb8b495d84097e646948ea95086659f4547273ac6276e5ee3760f53ff09a2903618de900aafd6138e4d12d2a09a72856e145459314a7bdcf9ff1217dc2c1d31f7b4cbf5d3e5253b9fbab9c791c9235567ac58255556fe10a95959ffa6c8b80c1d1b70c7040c5d00471fd1a002a50849b2864d8b2442dc75a899054ba28aeb3fec0d95f6be6292add779c422407d660b67bbf2d55b9c3524e1d63e53df41d03efb12507d0846ab99598e52fdc7697dd435bc3049bb2b91665749358ea1ec1a8ab6e69259cde24ef0e52f27e312b245a528edd8085366ce6a5cf9d7012c1a996b42e35f7701048354da10d194533cbd4e897234041aedf6865583aa7bac5eac4c687cc3c223528a942a40d78a93f98747fd784036b86c8a28e4a3f6e866201d8ca5e2cd59fd2e943527d8557f33ec1c201d3582a2cd51135ee0bb3e6819364942ccaa4e2403950d990382eb46d43003ed524a6881e2939e703a810021fc32631de6fd21ea0025514b1a8a25f30ba0684290104c0c881a28a193de873cb7a3a416d03de14ea9dcf951040fb5f8cd55ca18bb197a70996236cc2fe2700193e24ad0a232c700dfcc46fa4498263d023a6b45c1c10bba1d0b0cb54b82a53a14c98d79c9f8a4ab7e738e9908208e1c6b130bed6044631fa508f31d6f74d2d42b21183b568a91464511942531d200459323074b200e327f1a5b0e61fbebf35dff8ece0a88fb5d1d299b49f30ad8755fee9834999d92dbc36fbe62b9529460f440a4fab4364d77992e8b4e97fb0c486e78c53e59aa3d226aa53d1263e33e0918e3cc3ff730292740a6c7078ac81bd8a8574d4c169d029b16a418d6ca8a3bb04d6d8e27beb4d3aef092a53541511e74e30f9ff37208f982dd1026849095afb6ff09680768750e8357bc5dfd7b237c5328ceb6b10ccd3371751bab30ffbf1d33893f992e138fc5c0986e29001a4ffe0dfb1f2c7d784b37a6a081ec03a9b26eb7f6bb131d90b3ab65b82d913fe1c3fa34a3d99cd5a59320d922ab8fe32e41893d2896feac8114076b83384cb83c90a2e5fe67ccfd80177a0b6e8befc1026e90c028813e8378f863b29953a3f9c88d11611d2b93d350b029180127673136c89c17a1c5d0606d43309d46d4f0107a247346bd90d20fa8431ea8935309d0f63f5be4a4ec50534468575474acbc79cb4c80e6211fbbd3587f5ac6e66b402012432bc7c7cff7cd18b7c7678cafdf64fd36a9fc0735e4c599f9cd49e18a05432c5c9c4e302465f11db7946d259d9d4015f7b675bb566ff37e2c916e9d35f50a12c90d88cd3e9596c235653ff509e53dffe2936a30b3a15c53901ad4d18d8c607a2792c54c19ed3439aa44f3292e5ffa4473061d3d578e6079ab4b08f1ea82ec5ff17f378325b01839cfd644611afe8fb7eecc3e78edf985101a17317a198b45d21481ee029365c1daf416f776b71630207053d582abab22a154b83454beb1ed8219598364c787db6923ab99af0637b8d60b7501b67eedc16c0301392980e67711e6df3c160ebdffcb69e53753ae12bc97f74451b5836a1ee12b5a326b9bd3ffcbc22025988769510bd26355c3266eb8e57a3aeb7de252427679031f26416432006b249a96940fa2380cc598321dd32da9cae6dff543d6ce41f96ced671c68dfb3e2101631c9825857687b1e5a71c8c6fccd3b6852ad3023be705049085366d002fc1d8b0cf379dd27486e36f88b42d19d4f6029e4c9294885a04fe9b7106853504999c85444be18f6b2eaf03fd06166f9feb67a1db8c8fc594bd822fff579871f58af5cc0c168d48ac51ee6fa9ad7f44081d7638642ed1d2ce6cfe8dbecc3c739ba001d0d2bf6d330ce6a6938e14a7b644e8335a3cbdf80240026465754aa30c2d3bc63c9a85a8dfe6a0848542412c1ef2193e3baf1ec925d1855cc63e11a4a8606e184f70a4021cf01ba920be54df98652e9c15a7b40816d37f98739e91b4021f8a64ce8ff72d319764a04bf2814466a9644f563fac00942caadf46e251e6352c7bf2dfb09bac59306d0409f52428555b203d5361b9891130095c02c0ec527ea2330f8b0f51524843c463a0277b80ba882611d03500f4d485c3b482d04b06ddb0c878ebc39777e39eb9309f5fe666058d5143c554e58af5ec717e96cea4752f33055bbd981afecf09757903aff86b60e777ebcb473e3ef404452dab8e4778569df1f988f7e6fe9fd554d27a0b146cc0aead03f8461a38ffc634542ba2e7815f1a377f88b836682090a8aeb60d4acf2211bfc3e74ad67aa8ccdbe2b7a7c28f8513ac263d53b9b231385392040f257884135d61361d9d8bd6b6c19a5208c7b234d031565019178c268db3c02a27f1b14d660049d21a2418f4cd3715b014564ae63da4ccc96a5561ba35b217068c1ca03641d555640fb339f2ddb39a2b711a1130b4a87eb19bd3638a10b7edb9c8a0d76bc822a63bb086ee98df7de442b283cecde175801479dcff714689932b9bf2f553e664476a406c020eff85f72c6675f2931748a56a8634babd1778c8039f290ebdaa735bc340dcd22590787926d96f8a9df3d0ce7dd03b8833deb85ba13f52ca0d6cdc806b881de51207fb82ff5200df20c1219d02b89f22ec76bdf6594a2221f7118a464ccbf8971bceb739e54accb31bd0f73b529fbbee248eab0788eaa60af5c3b31f40ff42a16e275a44280bcbc605c279fb122236d6f6e8e94e6d2918f22c7b898d6f609a5f4ff3f9131b7273ab5fa8e1032836fadb61c325f36d7b1ead5088bc398d6ac4cde1eff0fffb49b85f8a13830489c7781ed4790741f7fef6c8264f2172161b456a9657c5a732a0a34bf2e8669b34286db6d8f042ae7086dada33e467badc8dd45d009784f888f1fe2d7b8b3839909d4e1921617bd72b74d22a52d00fd21ebcf1ca25c59f13edb2dcbd4ba90d8f9b5c203172167e0b3f94c4cc7c132c3080d2ec4909219e3711d97988d531f1007b73cd92eb8f1f7d1648417624404a88f4fe1564daebac7f1eac852ef89dd2f6456ab1cc81e2f0fee7015b3081df09a9de2432cb18280ead7f1e460b13fcbd92209407b69183f7fe41ead005e4c371db84bfcb0fb276248e7f3eb6df90ad32c536ddd0ab25e3516f153c882a405fab79f8e0b5576a96cc910f7c619654f249f60d44f97e52b2ddb1874c1d6a02916303c4e9aff8a14cfb62a71ce6f4a56ce18a127241c8eb377133b2b35a6e459a0ebbdc2e5f177fde76b586c3624cff57b54eb4625f686cce16249d58133d5b55743ce63b4c775975eaeef95c635124141b64255ff8c60fe23245a51364d918239438cbd7dc346b9dcb0bfc51388c221430a42dbe4c84f3d277f7b2bf83f0a20634bad0c1b8f5a45eb1db4009a5ee4c1f712cb84e39c052d5bd4541a412edb1816d11b314dcf4e332f7c72ac0c4d007b5cd243f06ab66c677098d0c2af87267e372718b8b3e1775a54e53d6fe626eecd6cf0fb77282096090cfac5e77df6f4b885c7d99ad5b81f838f116e0bde05c18b1a9d6933f74402e4cbcff9125630dafc958af3c9133ddb4524cac935fd3a42cd2574a37226d917cbdd7e079ffb43f08935bb869b4a180fe30587d1cc395fc1d56006ec454b3d27990ec28812a2da5236fe9c142ddbba7fa9c7c48bb468dd9b36a6610bb5c9133f90dc46cc40915678f12915b93fed8dcaedcd4dc9a916fd3982c569a4db08d433c32739ee5e58612d828c51de3bf83c995a3a3b3dca4eeed18937b17d01cec5e08aa45134cad1bc545f7ec0edef70d862041e8fbdfdd4f93e7961851a7302257b2000a55204d7c8a936d5d2fdc4c85eb2cd8146fe7d95d304e2db41cf6e96e6a6aded7a174c00ea5cc1f9466392745d13cc36104bcc042752c2f958df8073849ff16eb1315716288f99fd375c8dc6d8b82b56c1a5980a7fb54e99f24ad19e569714c0d89b9f994d2adbe0ef8fdf5acd1130e606f8bb618b9cce12fdc86cadc1d1aab4479278e5f07d8592ab8054e37329c3623516d228492ba9f914cdef6e484792ef31a2b7747c883329086a4bb36bf522d2d9346caffd6bf9919cc91e89564654f0da7846c57337a258c7479efe4894765ca8f1480a29586dc5c95b18688a9ce38fb9be0cca2d714f27255710fa184e6c28cda21b1b4a98354d8dc42404b6dcf50248cca03994cf0451ec4f2aec82c68193b971826805f4521e1481d84b0b90ae0619bf2258102b7888c9a32e4981ddc9e712457db9d942895c7d2f296f0ab602be9edf8f2e8abda2275e6f0d750f923981cee8a6a4fccc3f03fcc3d5e10c5e431f0f7ffb9208610a639a98f47ff3feab3c90d1e65c425f5898ca6d0bdf7c322f33610724ac803f9f85c372e4479e2302e0baf75739e295e02cdcd5ce905d08f69f0ebd10ad609bd2d182b98c6e0eb6f2ac7ce03a1584ebbc43578fc83c08a889f5355ce730d0e2e41aa65412050ac2b4ca450d3a979afb2705faf90971e3d0c7e577f41974c0baacf635ef5fee5606083827bf0ef7539bfd5b883f7def07c645728fdeabd9498c5c41c4563ab839d21e3afee2ec388a7c45177e40225c8c24e8e7605c5d34d8e59da157aa72bc597ecc98158057dbb52aae38dcdf04899d72393e4e4938edd8f8d134687a36565005d5993229b90078f2ca6f6ff74c5fda0f0e2fc4059d12fcfa2624914de0beefe320d895ecf8fb8f439cabbd73b6dbb9250aa0d55411336eb09a46d50179a60bb9640d22655816936eb04a46da9149a62b34a50d2867a81291beb9486f252ff6d78a2c01d49dd9d24599ede74f9b3a4912ed52324dddd0f29fc0302ec18461ec373445c390b7f282b9404c5cde320e6eeef3b6632daef2a4106064e62c926ac0f2596169ecd132d6ca0027f34a684bb1604670357b56e4fed21ce6a20e8a75a7fccf4ab1035abed887269a6e4ffe0fda6279440643cb9663eac53fe032d19fdfd2f8e4495597e09512e0172a7a53ae537bf5be4f8bd4720231d366ef2561879f61e4a57944550efa25b50fa8bd0cfa857e77ca134f5959b16100ef5d8472cef2f3229422a89bcc483d2108fa028f569f52256c026e7e9f002d64095022b19cd5c957175c9fca41b36b44b050c8a3904b1c912afb74f102923de654f14c314486c78126d23f6ea140ec1532397e0a192814cb7992e53f47155fb409d24aab50d6fe5a48c7d768a1b539801c75fb18f305399eed6e1bb816afb55ef23dce16ab1dbd30e0cbdff95737042ffdf04ee33c32d262600a9b57070207d5bad2f7d3fe96b313e4077f17bfc2053336945d5e43f796d7e51fd1bfad0c8cfe41711318ce01a49824d62517e64a24dd9d1c8b2e7587463430b4e6785ea7c2464883de7063fb571b584a20fc47e234bcf2bda07f9d84d28af1dab43ad37585ec08b9ea87229d7ef6de3ac7efb18c74405a1b8615214fb6860c4c1662b6f440a2a52644386e8a6ed167bd71d5de3fab3bdba8d3744a871901294da7a7e6deec1273e715f4e1fbf06ac1725233943d83783aa62f2e9107bdde40eab60dc35ea57ecaeeb3b8b5c6278e7635969190f8f22aa4940b6643290b7c8603ac1e801ba1369c3a4abe14b7468769d242db21effc2b2147828a612bd1552e71b1cff82745ec5794ff1aab721ab28b4577c6faf1379259b9a9af5f6e785e71530291cee01d7ca2db743dddd8bdfb6f8b8b02570fba2ab707515499665d9b45eea137a45c5d204155fb09036588b54185784d1f2224e28b5b4e5fbe3a2583ead82d16fc79dc5136e517bcc22f8e28c203145b47c9349653c70469c981f9f5e7674c22d3f6dd3e5dffb15d0b168346d20fdcc9f50617f3b204170ee8dabb13ebe795390646590a15813cf12fb1d7480285cccf1e44cb992b6d40b4db1592520694badc094ad7502c936d40b4dd95a25946a435dc894cd6a02e9b6d4854cd8ae25909656912e13a4107b201677b91bfbe6d29369ad3f875ba077bfdffddc5b465f26a61979277c01139eeecae103a79dbaf8f3b61afef4946cfa03962e344114d6cb8529a6fe22a4a1f80f58e9ff8a2ab299cce714c8f7e49fd552db664f891372d0170ae19368480c923b874c85ea40b6709bc3650726d19b275dad1f279f9a45591bad46c0c515b43c4fb35eb53a291dcf1bae51d3b6a1f4fc61dda4a457e425199392b0ff839a1e9518b6a1c1b329e888aa9027992ba203346b4d8adc1e93ab181d8fcaca24441c965d4da6129c3e096034bb0b45c6652df72ca391fd8bcc37d20e049e74d91e271865e71bf48a52365b7ee21f07f5e016829b0cb9897e8b04b398231441d5a8b2741db0c86a2c1061b1e6603d2cd26eb2bd6b7295c26858ae0ffdfd02ed2a59cbdea490aa2847022534f9e8d361dd4d898734b55f1a6ebe366531964e5dd0856ebd03be0bec65d279715577d8baa694dd35a75ed7781d911432d5ac22249375e9541a553610b4fbec72306cfa6b8cf8aedd463eac513abb7546f6900ef918a9bc57986882849c208d4d00237d4776fc2fe71d7506ac3eb43d8f5beccd01349f076da25ed2b1e10c67503a81afd1416cd19b57ac4215150f4348a71ad40e63c34f9a1787744a388a9eca6dde327bd38d400d8169aa2a0ce97807f818b65048b2051db2421ba41d9221150871edb49adcd1340990a44b3a4117cbcc66793a08c44a87a4f763f6dda4235d17578a087d13996871ec80d1dbb2432d297152908ea8f046366cedc9d329239693068e839c0eb670e96c1f3e198aa83a24b456919ddd87844e0d992578cd433af2342cdba8a462a1026c94a8c648c3758494d279f83e7024a6b6ad81eb4e0f1971d7444098806ae93c3d233d5b3698e26b3cc8dd7f9b36ccffb240afc03ea5b34b79001dc26e52991c2817d57e9b44f34d9cd366c3bd582c984b5720ff488b5de0daa4152e4ae316cd54903168aa44e750b7271f35ae80f87e96468ceea93f4d43943e523f23c49b9176c9d4e819d7ef5a13eaaebc42e363f6fadeb93ae030fe77e1d6f4358d04905ae293cd57d6df7c0a9da0d08bf634cabeff0979b0252ef532a7ab3d3886efc312f0d0a90cd2be06cd083aed5dc46508811885d9fbd98e00cdd800c4aec7c4db8a226e4c81cc70dbff8db9fa7fc8c61fe3d2d298fd6a00deb0774d21f21077f862f65da8b574faa835a8140f1515dab481647ae09ee1b204cc14e896e3f4ad1970f4458baad4dc8b200b74ddbb9a88130678f0c9d7b9242eed0fc1116a234b9281c3934846855b6d80001851f9ffcb1e3ef663aa77aa09751350cf3a0c4589fb455779d726fd40fad630ef143e742dcbf3fb39503d94a2212039334ea8968a409b4c6981926dcad33505ed4bb85b2cfb31de8f31df7e18efa6c084c2a0d56577be420d17f984bbf3ed3e1786def8fbad20b1cc840b401b0aedabe8590965dec3da307a3011ecf52218707f1b073035d4f2a191db63ae31bf8f691214e047d414410b41210d39489023e6a6169b402bd197b6d8eb2451badfcb0da9126b1bc46bd9ed792925564928b289c4432b5baad8e7ab88cbaf356e205476f45365a4edfe4a7b9e1e77d129e9d9eb7e8dc55d7d0fd3bcc850b1b2cfa82a666091f0b9111077efdb46323009018aad4f8afe9e086133321b5646d4e93a4dd04258491f584570a133f6c9e29b6f622bed41819c04b5e889ff0e26a19f067720500541e5f743d0b2a0f9b0d603912cc8fa54eb0e7967e08071de92b11bc3a7c400722c48ddeaf6c81b6202d33d87904fbad6878e888bfcf3a3a2bacc61f558d09f048e29e700ccba1641ade38fe78ca4359aa7bfa3c1d266b965440859883880660ffad0243cdb6545d344526fa6097a3ad20ed7fc1775e6698af76fd1453738c8596a7657938c65a6cb74192c248e0aa2d3b1009dae2dca5aa8b4216156b53591769c2c22691f52f624950377ef96c6df43fcf6c90450c03b6aad7a52887ce87fba5db75f35056ec0fc24c05dc30fe90ca1b6bcf5b6e77db27f12a06c8e4e0ff52ba0dc6dcfdd0cb801abf8ed99be7fcbc3623a21629fa9b9aee28b30ea8275456cbec2318938f9a6d4ad1bff03b06504ca78bb3999401121120423b0d501dee16f3a13d52d804280ed966e9c37e738c290913d1d1f9dc41dab882505c065bbe57c858ec337121889ae559f5beba40847b8e6bd9be7d0175cdcb3022a01921294be079e8964f968fbed0a1c422b68992eec3dd9e6c885e0962c5dcef2e26117e4f87a4039c93dd88ada817adabd373b2e12b12c81046f95ace565ec680787d12ac94ca2c40abfe5103f078fd4cc159518a3f21bc90b6c88033abf768c592012b3491b5eb22dbd7135b7399dc8c4a54485ece46d86a00021e6fd7ee399dff7238727d9f7ce7823fc383b44bc00a89000001057e41f377500ce2a005aa39a52a905c30c9644c36c37eb6f1b694d2421929288d0caeeee1d6c0820080a09369e43357b402e4454aaad07aeb22ccb5a225fad2fe546dcf23e95e57c5a2c7bf55a37e65cbb116eeb6dc4553f50542f6910579c504e83f575419a47ae6d286e733799b4be77b4511c05766c1dfb72d268d39a482ae56e326d1487dadc888de770233b5c87760db5ed08d8780ea7804d0e0aecd8475e731d4788bcc6699549a55261d4e9743a5da122f239a34ca7195337d38d329c3d7854d188111bdf91c3d3e136de0e152a02395c87532087eba0808de78c8a800ecfe114d0916367363b021151d548cd85247a7a554b7221e54d766b3bc6d2b157f28861250fd2b49207b1635cc81e98532f8fb995659c469aa4699a0bd95cf39f1f91489af719f951c9616691bc9fb74aa3ccb7cdf12491366a4876920bc94efaf915dec86bf9dcbe8008bf39cec4d8beaa1cc55b4315fabab01cdf0eb771947ff4519a74cc33995c733c6136049f503a24971f7c143879ccf3fe50e0e433a6b5a63d930e2072b0186325c75c6f1d924bfc5fb40cf325a7a1a1e93add759dc94483d5cc013bbce4da935eb314a45b597b26af1904e2ef70cdfb486edaff459336b6c3e5ded9e1a5bda3c3a5cb8fb4796498dfb1a14ce570bc75e8c871b241a1a43c9d4e24c7a453c949dba9b4915c926a6a6a6a4824128944aa425b8e1af29dcb2c3bdc7919cd68e433331ee6e5feb27b6993dc3d287b9036e9334ef2cd933e729c39f2f20c4dd7759ec54ca7f36ed2d0d0984c341a789307711ccdc837d3ee3ccb971d8ecca1f3be9fefe468b45143667ce442667cb47fbee46ddbc8fb8c7c8966a3d917883e38e7b8192f0f6509fa99874353a4f1238f0bc7718ee78ce3998d1ab2f98c0bd97ce6e749de47a5f7adf01979525792dfed0b441f2327799f083fd29ccb19054e64981c12358355ad156218439929e3305d61857b87343d4275b3bff511797cafd62693bb75650df89159b6513999772e24dbf4846d6642b29ccc473eb3cd6156b2bc8fc85f994f92c3b9edef6788bfdcf2cc017fed5207857048fbbbd4479e0933d906a7dedfddb6b63f6d1ddbd6efb63dacfd11791e55f5a8c7a322499b45961a1d9ee7709b1dee9d96935de3743ad73d5175afd626d3e75f4a736ee7704ed3a5005f49c94d2eb7518c1fb3cd31e566dce47df9cbaf3f5de2469e0832a57dc633d17836a60c7b689ce69b0832a539a73dc7da731ca933bfbc4d6bdfe13c4448390f117e0ed9eabd5a9b4cee5f8a73ac336d47db3df193fcecf4c4d73b477b76f89aa779e769c7334b7d42c931cdd3b5d725f979ed9d6f7a8f60c78c93848c1cfbcc7246258799c9e1c867f6a499196dfb23f29a9bbc3bda81264d3db34578bd3f22df39cee4acf5bcab998ee77aef788ef7ce0e1ed56bd754efe950bd8d7699e5ecd89ec3c6e601283fa14ea8d3e974429d4ea79ad3aea9a9c9a222228208a9140f9a92cf94f6de5b25649baad79a0047e8cc3f954a454425b44396d208aa4ee48de575cf4ce7ab4faefd9980e86b75225335a8baad23e0f21f749efa04872930df360dc4f40b69d6d8f5b655bd711bdeb66d43610c6b1310bde6b453a6b9c67bc784b9ae4f78ea13adf596f568a4b5d67a8aebbaaeebbaae6bcbd9677da2d1089f58397c7db2016102a2faa43e198a72dd3abe2e6b09c15ef7111c695df86ecbabcfc461d28ac5864b1ad5609d65d6e1ee635e762e07ca94d69cc669ae3d6ee330a7dbe6262072c03c63e7bc6deff8cc4f121feff4e464474173ceb38eb6b96ce793737bc794b16dcb7934721cd98d72371a8db0ad1be9d146229148144baeb9663938e79c7d7354e69983726299e940a9478e67960333219b73dfcdd93c472d44fb96a3371af00050e08c54431a719605051551aa37f120169509583297cc2c4d904e9929e3b0874fce9d320ee35376726def9c4e2513cd4ca9442275a48e4422913a1289446a62435dd7bdf7661d5c54853b1d8558a10af4d20c50324c74344e33e3a51ace2f2f9b462a5cdccdd7c8a1733ad3dc91acb56b9ae7fde5253fd2f4b567bd77b291e7435ff34cff3239f49a124d37532a651289446222e713e727cdb57bf52995bd70975ca76e7974c95c3223174b8c48333ef3d578c9bb0dc3e69c73ce39e7dcf6d7735f89cd636d37e0e7f1e61ad73857c335e0e7352cc9cfe33d821dafed6dc9638ee11aaea6c6b9bd43836bf2fedcd4d1cc904a5d47229148a4d16834b2ac254e270fca1ed775afbe326579fa69640e02b6ae58ad5f7eadaa020b62befac58298180414df5082cb4a9d935c739c3decda4ccebbc8696be8387bda945d67d7b09c3d945601e52787bf15419d5810c38298dffcabd126ac7dd37bc7e478efccd4789bb6671c3acd4ca9239146a3d148d39836c234cd711cb76ddbb66d4a5cd7755dd7755d396b41afc5a0bfae4b859387f2ec7d31a8c8c9511bce1e396fa718249fb0202606bd92189f0cf90d50be7cf3ebd227adafcb354dd3b4cbb56bbfae8b46d3d775e98efb32c0d1f8d4bfaeebd2a0936a4ea85c536332994c264dc318634dd35c6bad694d6b5ad39ad6b4a635ad694d6b5ad39ad6b4a635ad694d6b5ad39ad6b4a635ad69ad354dd3b4ebcca7da4aa75f2d79bedeabf5edeebd5ac3af6ec48990b832cefca4e1cd1bf9e5694df34c6f1bc76dae392f795fc78d38bc39b7f1fca675e7ddc7f3da3bb71c1e69a7dcdec13eda3b996b8e233b8fa7fb369c39f41349cbb4fd9948a49c73ced7454343438384ee9ce3f4fe74c9d3af75b7bbddedba8ea7cb7ea0f1bd2ecdc3c3d3f1f0744fb895ab739ca1aecbdb38ce4f5bc937aed397369af1be517612c7f1709c6f9b6b3ccf397c9e91c6791fc76d9eee356def6427ed9d93733cdd73be759fbb3f655c756e7fa62cebbaaec3dd755d34343434563a9ed74afbd325aeebba4ea63a9eee95c46c094a9744612b9df8de2347aa8c444ee5e68e8a473af345ddce6bea587e2df4912b48d94311a492e66d8e8d304ef38b47dec7611986650f18611997ed1c8e738c2dce5ed728e348a512c9671c660f20f98c930240d7177baf0af6987eaf8c3b526e94bf955e556ff78a04d59d3e5d84f95957ad646a5aeb24ace48dddf3b3fcaa5424a85696a7923f62102837bdb3d680218f1f30c31be994fa74826774a2fd1467d059047d743dc5fc0785c27c741c08a4c7fab9a91767d070688b977f38b4a58b4f41de548f3488fea5620f3a5ed52a05c5577780bc217df5610b105feb1627be561f6af5e13f13bee6e751c9c0b1072940201711f9435341ad8a3f6827685135dda4f3fef4364dcc2df58bed14201079edbcb9080064a0231014ed729172c75709618c32ba8c50a6c9346574195dc6785f002499c81f4c346fe241b41d25064527f1090c7342b4d06210c33019cb98fec1305874ecd9b37c2d8bda6b518a61f1f46368afd909d9013f39ee1db9c8d49cdfbc22b7f8ddaeeaf44a29a594515e6bd3b83f7ae50a1592a9e99a47a5cc1e5664ea2607fa8b5cb8442f514a6e0186de83feaefe542b5510e92938000289580c51c6101653e4b5c9e1f2e6f27bd9a9d90a1f3b741282e01cf3f5e2579faf594ebdec3357e99a471d664394d0b7f6bd7f616e3d73cb4756f791da86d90999e3ea61bcc2ce8e71adfe935dcb1501d187e596952fb73c1264caf2ea7d33bbe5e11eeb73c61b6ef09a73851de7e59d60fd7a28bb11107d58b7d6bb3f24c8d44f0fcdf152b1b2fc8d41329592365ee8cf86e888dcdf7da29f9e5ba239452cc1097903ad2c0f5d36f1dff55fbc3e07ee18544a43df09bb2c5306d18b53998219181dfd337da954943fb434c91e533c4a8960c412e2432f2f7f988e235dfe8041e6dc500a492987d2801642cb72086b85a65b6bad96ad134238e70a20adb0c231b56a3a06da0a1dd25aab8f84f3068c558710fae538c24dcb6ee7b5be6bbbbce87dd3a35fd7370d470f6762d2332f7b77ef40873c1e781ed3cb4bce2c52ca29a594d567ce225dbc28798779a9e4dde61973e959fff105c3304fc9acd999717b1d6f9bc95a3779181631e831c6c8420ee69a29678f39b3f098dd9b9bb08d691ac963e6344dd360842360cdc3de671dcb91d43e73b43fd73cfa87e408d06396c6eb8916839e790647d0dc5ac7ecded13c2fd93230af2cb261f65a6b478e799f9ba696980a359e731c719c674fdbd42da7711ab795d0b78e7123c7dc3a67b19192f976e7e8a087339d47e734e80dc1bce4321b52f2ce94f767f9e733b387cac14cdb292b52e33bcf9cf32d6282d9837f4d6ee1ecd1d54c851a87b387bdde79f06b3ccc5361ce1e5d60a39a9cf591c7b46dfb92c4a2a2a2386abcc683b347867a3435cdb33967d9b669286d17a9e9dc7ad671e791fce499308fe49d87918a9c6adca43dd4e61a3bc91bf9c933913c3c2a7272937fdb2e5273f24c359ec9adb7b9f6ac9fbc1a8759919acde433b9088376b2262862c881275f9d027df559abdd3e1e9d96dd994e753aaddd9f8c143a8d39d43a9c5f0e94294aa5c3b97de24b2536ce2f3075fce30b48e3252ba47c761aa1b7a80e8d97bc7a3ad9b563cc7f74d1f35effd125522513956377a25b1eef577225f2774c8eb9751de30ba146370d6628ec6c0e1f633943c7300f6edfcd2ce4688f58f62d3bc45878bd64273bfc243f1ff10afff3d9617698f5e488617642e7307b74884261dbb91cfe8c7f3e12471c231ca106931ce4f6873976ede68baff787215902f43e12eb899f25f979bd35cf7c0695b37776a2e715b66d85ccf1e64d2ab79cedd22ecd370f5fbeed2f023d759c516c7ff4b1ece5792592088160d4b19e159e44829ebdb8a9631efccceb994fca9b3acc48d931d7a8cc511cc92f0f95f90fd06b24395b4ef6a844763be732796cef40d7dcb4a193bc08947f8b40afd138741a97d9101a2fe958d7188db739cc7268f647e359fef9c8cd43e5642f79f6921550de8a54341caa82cb47b78eb19563a653fab8772cfee2ebccc39b5b4fc3b46318e6d6fb68cee63e50a67e3e6a1dc22c1f3d734c73c9cb25aa631dca1456f2e067effbc1ac37331dcb33bf1e0675e20937fbc14547eb9777824df2f3996b87f286c32c8c7a7ff2f55df2d846b73cca1eceb4f0310fe2486d23e6360763282e3b1e79d6356fcb3ceb232f734dfba77976cc43e99d33e328cdb5738ed2768e8698671e4af30c45f2915bc7345ec967bcce73c6309fd9dfcf43cfdea7ed9c92a3f2975df3cc43e59da367509b8f3c3be63cec33dd6bdef98c87bdb4bf9fce91c4c73b8773d4e6dbced19ee5919739e76dae79f137b75ee623cf3ae7e96dc3cfd990037c749fc8137f7efa8f2d7474587f78d1d1a175b7bce8383302cdc49a26f4e1bd3959a622d5991b6eda04a5d7ab9c965a4b2d4a3f6ae54c30bed0d4faa4544a14a8c7a716d5b1fed19c9973c98031a45e75e8d1ad639c2eb74948741955985bc6276cadf387a582b56db5f69228c5ac02e8a3eb6cd69fd21873b6282d9f994ef4cc424b4e896ba65bd2aa63bd9ce8b26628c8fbf337be10b474e83fbcd0d3a5943a3ad1a7941185e9d57f7efaa10a2844effb41217a74ead7bb7c7ad42b72fddad0cb50989f2e72f7953a08b8668070ceeb734a33a8161ff006a3d3482392f8724eb7ee93459fbd83ca895ebdc7da4a3af0f37627a9ee53bfee24d595705f7792e83ed947ba7b2c49862ac5c3a67bebe66c5996913cb19b1949a5a61a2a2df089e49b97af6bce39e79c73ce4cf29c4afdc6eddccd38374332a28d7cd98891cf33a49919e7f64e4dea2fcfa99fa9c134259a52a9944b99a6542ae59c9576966539e79cb316f7de7befbd77db369b6ddb463425d2e449a5b89c53329582598ca4c8a85297fd5256a7dca4f565d54d4544259f6cb9481ccd88dbb43699dc5329aee3eed5da3d95da54c8e1364572d810195d9abb5cbbf64e37ea4ca8191a53e93433532a954aa4524d4d4dcd161b1b958de7b0f172a80218cc978b1581c981094c1324ac0c3682c4454aa9e3c0cac0339890428403185c6e28e38449171714717145882e5b4c2b3a0e035280433d8082840c9c10e205276fecd004881fc63c6186126082c0c8bc94524ba188848a888a13551122c2c1961cf42582077d1f0e11d980de1668d19de9870730425cd83083184dae884b6881c50c845cc0c486214c0c2e4a18a34d5674a7a3f0f2465e59d1354c04610855b080091dca58454429a3063c54710319a02f30685f5a90cd2752d0262702d26a04a11e0ea1f1050d2b9eccbd92a91b747e3884c613bfc2010d237efec868101154f370088d175420eaa5c9a6ac3bdd99542b12da8821aa562a125469c0602b9d52eb42cb60685534d994756759161731641e9dce265fe5cee60c28fee32134e1c05970e4ad17004c3c6c2c1ed636b90502136972cbb24804f138c27487ce2822c874994c9e3a03ca7faa38ea25e594d55eeba5a0458a2955a200820cdac08204414d86c2047104110e5670db186170e0a460fdd09665390c7acb0a23cc5b965d59b9b6d229b3165481c9703d1c3a03888b981590418417559a0019a9b2c30d40b812041161c2b06207f6c6a7bb3266e03dbe46dae33f2a29cd0180379ee369e9bdc21f97d3bdb15d73ed08bd071014944f8da872c30f9ef6781c8923f524a3d22d2a998ab5d69a85fef2ca88af6e61fe59966555ea5654ab56b776cd4ff7dc57ac585c4e25ae3fe74faf2fbdd63cedb5f3cd55864bd249aba59fa67e23d4a69482716c149665ad6280460d2b1dd05001bd6794f92f95c5e72f578f89792983276880410a315e0044100f00f5a0458c2e5a48828820d24d2a29630bf06782fe68a33dbaa94b156070f91516fd412107e030c0b22ccbb22c4b5f6c05853603086650f91d6444424b87d2af7493ec1231371e3a9115d4c10c2b550445e9a2052782a06045105a507141961b74594111c3181f3420c443c6c789225e1c6be3e7101c40fc845eee8dd0fbae7b3738c2f42b7a487c668ff5d6a79164e734c723bfde03e10897922b2110257a42204a4c7ff905ff6e8ef97529b97fe3b6619812548e7fdcb9aece03d6f3f196c8d4edb99b078e30dd676fd6a3e61ff4826473ecd9f3999f1d5b6c643dd4c85e16cbde4ef6ad73bb772ebf18265dbbc71a793b56c73f057923e52712253dd6633d970f2ac79fe4f0497ba747c9fd6b1ff9cb731dc7fdadf083c4e7fe759c89238e2439d6b3c447498ff520e0f100b635205313f3e8f7835e50d6fd472452c42ea6468eff682b991e9218f3f5fbf913029fd3fbbb7973ff6d2b997fb3e821b18e1df75c6e824cf1902938bff8c59bb37bb1df8d33ef44e55c8f7eb7089d1950fe835e86ccc881e8f21e12eeb56dae49a6b0e399a3f74c57c111a6f724f9f9b84dbbb3b8cbaf5b8f8eb1f1589887197679286cdf3530a2879955e36116adeb77ef58bf8f8a8ea9f12814ee63693c46f4d5e1c7bdd9dd63fdd3ad647ef40d02813f575668392116204d431c7d91d662c00b1988d4b8428b1d6810824b0e788822054b932862e05083a171839f1d8010e20561644902076c5cc08b1b63e06042081aac882105859d317f88641451c1c40fde00a383a124b4208206656ce1820e785069908174c0172c80610639d0c00b193f33c8e2c50d4353f0408b1e80c161f4254a2f570011751737b4fc415b6badb6b1d65a7ba9b5366b09848da3852b3de80086052ec0f123a5063f209185162a9c6871c50c20e07863276d78ac8ed837d75abde4e06bf52b53da8a71850c443b80011a446704f94c8f8215384059011c64b67811a43a1cf242c4d75ac6d7ea2abb5756340c423b2656c063d92aa5e3482caefdf1ac542b12a853aaa7432a7e4ec9856e4b8271c50525de5a99041460f8601404183108c1eae11756b83c01860d164646dd3cab7ff20596eff170e80b32fe3e1cfaa24b14580141e86b1583944db084d60dc2e5891074f8212e4cbcb5eef2467af1d653d62d4fdbfdf1acbeee6e15d4c3f470884b94cf1e0e718192554ea8628c51084e1e000514757aad95ee54a792ce4969e751d9bd431e74c6d10412d4a5748c8bf7b75ef787391c42f795a92128c0910792eb13dfb27c4ebb3349f80f96f1d5ab8ef7d671e6bd5ac36f07cdcfcdbfeda74f78637d6aedd3ad0d7a62d4cba7ce5098d50de004095fbd66afee0108247ef50f4020d4eba88ef690d48f7cb44590a9cc39af6e82bca9ce7999fd76bcdd78e6792714ca396f2a5358522977249594521f5634dd57a66af50f0ad54aa5bc91d2536dd198576c5f99aa96656508643eacd7b2e0fc81937a424737c0189d6309f1eb55af65d94d944981e106ae4f2c5344b1f2a787534481622d0482fdca21d7a7c7e96139e398fbe381a4fac497ee40be1d1fb78ff02633c12df8c3347bd87bafbf7513dc6a3b5b45bbbff9d363f4eba7ff72e9f5a28123f3c7e87278dd8458c20afab06ead9be68fd1c6993dac733b3369d81ed1b2bd61de1ad0f18f9b47a6ac1fb1961540133dad47ea5adedc7bafcecef57cfa2fb57a3eac68e9b56e2d53f6be3d226f6ee48e1e9d114eed4a7923f7a7daa2b1c55bcb94f54b712090f9b0ca16e211d9c37a0a0a1ddd0063344e2c21be7506c81be9d68ff8c89bebf6baf495c4979a105e58a194d22ec8fc473f9b0ff350fa10b18b20c905981985a84cd1ec96132e6c9091ca2db6bc94130a9e8ee97f165a7afc71f97424d531e6d353f226fbccbc1f36bf367f5aad0bf1cfbcfa4765b6e9b40295b83685fea710962d5e26d1241364039421a85cc9c285bc91ff73f39844c9ea06a467ee850e9f397e80f5babffdd8a9cdae4ccd3b915c2e24caafd0c787613604058f339f9eed4f0eb1407f2bce3cec391b32a9c8542753d3b33dad681ea61579837dbacc86d43da9c89492f85fa45efc2122f4c2c94bbf598e8e2efd3ab67be2bcbca9041c613a9e114638ab56ef878279650ac111a61b000efdcd29532615f840b706f14d339552a956abcbafe3eb5e97d3a98273b552b99b4c73ca6bc7ea5dcbda57a662ddd029ce8cf507f29aa0033002e451422afa90fe790400804aa656b38704e613893ea41b51027a160f739029558402bf08657b0920f1d2a595e82345005da74b8f4234ff45a1b8041c61c61ce44ddc02a6a4cb784528fa9051083a68c9c1cb88e5077004e95f44e28574f49ee82fa451f0f8710b1d85a2df1b97cc1f4edc994557ff68bc054011aa4f0f0350840ab9fc40a915caee10484c0115cb45883983945a6badb5ce90af961884be56d7b5d65add30c909a440ccec01ffbaacd51224c55bcb456ea1801d14a0030f3f2ec0e28b1e9068a207576af8e9610a358aa8c24416451405d1a0e9a005530b563bada773a7819e52153ca5b40d6b7ba8a5890a2415364e2aeb74e329a59452a7944eaa0506a2a79452ea06b52ff8f03b1e6a81818acf1e6a81e1ca7f3c66955a73b0af8e44f575f76cbfa7b52b36b445e57c108ba78e44f5742ba133479f28a97843cc5442046178dc79c52a0c192d2d1069012253c6b2ac1591960fb5bc30037ac9e878a8e585175c28e3081ca74422a61b419429025383a11ab058008b98184c79020a1b64c041111a3b69a33a7cf2558b0b39ccc42a6d6ca1174d55196084570b59f46f8a2053d133ad93526ad93a551044a662f637c27b6badd6bad4b275472804e1cd1b4b8c6c70ad11c5c233d0489941bd82c043145b94f9810e52e87c2173441718080141086130a161040500a740ebc10a013cd402248516a026ea65467e034724b6f0a18b21aa2ce1831a43245001032fb230c38d2aa87ca942bf1c41c2327060c8072b5f2c61460c6e30821139885963882d6d30f105caf525088d83f2432d40375c286fe44aa7d40fb50035c1a2080e27454429a874ca55ee6cac1405e1490f878aac0ce0e150510d454ea2148ca54b9fb00d1b967cac8ee14f8f45298c3ab188e8037a9c22a5c8548c9e742a32f5d363410f3af52035638341b2e7bdaaa2bffba6ec356f7993737623f307cdb57a900d66f90f474cc62ccb5c08fc6ca334dfc97c7ab6a76bd9fbee7dac79d9bdace39fbf6c154343e6d1656de20179e4ac3b13166d0209ab6bfd0031861551ae58411b29a0f2b3831a889e18834a1138c08063b022e79c73cea214fc9cb368869f452e14b930c6972c7c3066074474d1c4ca082c9a5083075a407181172d46b8c9c552d0268f238c50b6d65a1604b156eebefcba6b81d6e2a8a10c910d1a067a07bdad5ca0b55799f22a773663a69471a28c0f9ce0c764b8d8f8ccab2e1a351d0e9501fae93892cc1314181b4aada539e79cb3db7cce64b8cc596baed633efe7b1e9e14c4a664ba482318d2a9091f2d971a496d06fad28d4574791794226c8c6e6abdb809164828a34b182cc1ea8ec70680c97cf18e6d8c3b02b4b965d598673e699e30c3b8ed77565d975b18b79e659c79eb597c8941c1ae3c4679f3241988e99b23dae37744f2db71bafc0d03108f75cb03242702fb3d0e899dbeee150991b5414c0292a2a8a23c80a3f491415151505d9b09b5760e8cdb5ff6098ead1cfbce9dd8f9ef6d8f61f0cb3248638fe234151d1df15183aca75ede1509920f9df098a8afebaa671dc57e784c8b22ca3de860c95874364b83ca6f2d871ce39e78dc27c277b742c7bf49cf9cf4fef5ba239cceafcf9aa61c79937bacda33ef3507147388275383406ccc321324f1ee3aefbea5d991bb03b7b6058962b01d7b56d5f7d1b33e55e1e9429ccbb373b01d78d4a017e752199ef54d73edb0f46a372b4afae650e87ca04fdcfc7e97d4b7e7a963d7c79f67e1e5f8e33311f8f423882f5cf8746384466cbfb64d2e60621289272861b53ccb8e2839429b240624a0f72d8011b4b8c6c70c5e1658791171ab64aa7d4f170e80d1e6881a3935dbc9066a5538a69e34757586cd269ef755d1cdc48a2862db2604283252f0083862741b8618a1a8258561853dedade8a3578cbb22e26c6cb1b59bca18507537653dc614201c044dd5751ac5e302446085bcc392d6b255324c894c9c67988d1a2d2f0d3af982a6c151f8fb0d838a1052e9a54a9a2840e55827c291d60086f289db2040991c48f3ef2463a8d3ee1fe60065dee19c3556badd7c216b6b085e78578ef78979aae6eca4bdeeb4e39a5636a37f5697918f66f7e955f1fef8ffae53893524ff2f3138b96869fffa2cbcba43a9328d19ba482bc108b7c0751162310f85ed8437b77a653192b80af0e4ce50f18048a71f2d0f5096bf98349944f7a74264d54d49b78901762519028944a00c3287504321dba06815c9f1eeba727f9799f1ef83f316e6055855ec99bec32bab4309f38af92d0d7e15fffbadfc93c3b99ec77e6bbbf8cb397f9f572b267fbcb3b9663d9837f2d87d9f572f676e2b53cf32c8f274ccfbc09339a4db6ba21db106a834a3dc156cbebb5baeeaf3ab6722cb7fbdbb15e77ac67cbdba95eadc3ace69de939da35562ba17b576ff70e0d010f1e1e2b24280d010f25f5727cbd7c7974a5047d2af38d15ce20240413a12b5b34c9a18b3156b061051358bcf0430c300c3f4e8ac0ecb0450c9330c8c0ca782022c1819804b7c41258c6a082c5081e803e2844001eb6bcb065891db06012e4d30f31131f60885e70c118685cd1220874c8019fba8a62e5031e3ee0e183f959af5e3d5eea365ea7318ec1e4fde1901a4eac11e6550f87d658e2d5a09286105c7cfe925b60feb2e1c327e0e15098327e288c9530367c182c8f3f7aa442ad37729cb1e7ef8b54682cb7f6e783f717b54c277a9609d1dc8af0b1859dca14ae518a9c734a6f2783dc9cd8a5c43df3776e11b184e938723a9973fb938f638cfb9b59c9bfe8252caf67fecded33bf98e18dcaacfdc57f80e5717f399647b73c4ef9f1f3d17226a4e4115bd3ade5df8c97fc835bac6f46ceb9bf394bfb835b8458be2375f25bbebf24174cfdcc836f3f2b4194f18b5bffcf0a16de5fbcf23b9ab3f0d1fbe65b2f31fd38259660f9dcf1f3d93df3bfa865b7a48f741f392595b718bbc49a7ff2c373eb681e3d468f994efde8993625fab0dcca8468d13b2f3bf5689c6479167e9432425d878f39ffa293fccb9c34e2a28ffcbb2e1f6dd475ceadc7226209965bcdb34fdbd6a332a5f7b7b9751ab84548f41d4d277f8cfb4bf2f396679e3d4af158442c017ea672d5fb95e02fc733c72262095f7ccbad7f7067ffb05fffa2dffdc12d65e89dcb2dfff0f6995fdc3e33ee3ba5f69de0f299e5d4bff6f5e85ffcd68e45441f9693a31ba4d143501a3188210d19888888c004115df125cbf645081a5dc34b1959268307326a30925dc8b0c19201031a75c03101033298b8910d418c209878701a020e66111b51a460862ed628e385034ca1c50c8088630b2c57aca187f042015eb03588e942c61562bca0050d64418318c634594306431843cb0606e78213555401c2105f4451458d21d8a0650b2cb6cc80658c318618c3881adc821817ca983266075288a10524830c942d5f829822ca0ac08cc1c680610018c8823143cb60b408a0c7a894ec58697b54a22992008000b315000028100c0704e2a048345104618d0f14000d86ac465c4496c8434992a3280e8220838c318610000030c68011a29aaa00039f1823b94d2ac8e3268fb87b5704f3a4a81cbe53726bf858d72aea26473eb1ac9cc035a4a344a3fcaddcce27437683475e62053f2c189b16336d4f9e4c8a7e447a15aa3e5f8c3905fb8d1a45fbc2a12e3e1a5c03f4861b5928650aa20d2ea9d1dc7d4646ce8f1931fe1cd2be5387d46e3be50f33ccc229fa8d33d5710f3850110e5e9130d003a55185ca9ab1395564a381110c0d934808078c409ac9a509de6940ac06ff877bd58966e2f602187c15774ec2234ac9da36fd9a53ce34b481352bfdcab857770de765402b2d1d2346da004edf7138dea768d9da0dbb2da005a1b2d1a74e87f83c64dba90e819e9094f5313d90443818ed25a4cfe492fd72ed02f7fc263bfa71e07157e53cefe0ced3afc2e82ddb3bd7529be7be3e7f7336d37b9d8a70c06ce5b1c4cd2e569945407473e7d1fb37f09625b94c085856478f9626929da91383b912483cdb85b46862977a987422d9947cf46f7d670e033479b6b6a1d2472a61842c052ace20246338a47ada4cae22b804fe95aa89876f93ca6d29d546377b4fb72fad513738f95c92de277ab5afbb453e923c73e8ed287592e3cd046b18d9ab950d7dec7ed8b31f8789d0e60d09dd6b1167b734c9d938dcf66664b189c82f06c0b726a5e79e6168d9a4a15cca1449ceaf98694dbd97895d1bb2b82896b674a3c0ff23ff94e7b800f7b0f586f545f83f53c002941133b604a104fd82804e7a31ffe9721fc7647fa38912aae06ad6df3cee9dce004469918c70d2c5c6548afffdbc755def2d11d07d0e9c3dab29536b13e70785c8df501b56c3fc4ebd193ed9733d9cf5c63a7407f1b0c04b193ff9417817b4f5b0e2071b8a99ae7e1ae244b9dedecad8bdd6465064399b70320190f26c3024b4333e539b1be799c68b285b97394ef6bcce8a62c8bf08e3e40443f174448d44aca8e1c209d4509f5736f043385689fbb11a5dec624e1856f113d4f5a435e54438eb6d0884e4c84c341a18cc6cb0db72a1da719dcb9a187a4024a841aeab3153d1b449197c0cbcbcb7694ea62eaadb2be94cb43692b4989f5e3020ae7200ce451124fd4877877155c5447aad2bbb229224728c184e6515e846a1b7ad33657ce549a50efdd0f8d237f5dbf3818d055c8c51f9f66f7e2585a9206cc71145f662c9c998fae43ac8471b901518b31fdade91da2e45c7117c67d525ea4320bfbdf830a2dda29f18331364c782b216fba7f1400ed49d77ba9151aa23429f4a863c02b174dcb380a1740be81c8e2ae4b98cfb7dcd48dc8513203a19e416a887185e89b60cdc2b9c748bc1fe719e195bf9236d04dc83b6fb5da12b709d3a505e45f8acc43561e83a9e764b6ef44216eb5d8edfc479f6adbce59015b7588cba5a5fc18545385ecdb33375a56c921a56084e06fa147ab255c4e5f0d13adb54f02dcdc3336c356f6e976f11ad87ed693cbc405437b718137394332778ac56447d5aa6fc70de531b5d6813a8720b69e68f9636518bdc5a08b40fd52abd8ba3ca6409a94b477f83f750cf4f2ac519eaffb84146c07ab812728352ec4acc34d58c7436e73c787a70c7a5467bbec17067bf9d0ef9db0fe33e146e6870145b2451fa820a0cb5467fe5fb3e337093856f82965792a4f5396b9b437e3801780a665826717f1a11d3ffe7b03f3cf107efe1011c4177e3bf3eeec122d9fba653e2679307fbbc4e7e86385c0a2a65c5534ea0fefd010c9f6a89346509bceb365e7aa0fe79191c61bc3246c6fdc2bf7ee247fcb0bba5c9f0a3c2db71485e6cad63c7ac97ae7f839002b11979bc99e0348d1c27e10ce499e76b709a478d2e0e31f9ba91954eae28946c67b9b4d255f818d0690119c54c01031ebd003b722a5e1e693c058bf0dfe7850c532d929de91bae65c27a265eea9eb39e9de98075f740e73017444794beeca0fcdb485bd25f6c2661265907d1b4a9727932ef457232cba6ce00d8a5fb17bf28ae4dc143959d2a8a1f8261ff40f3c044e490e33fd0c4410863ee52039900f99a14da88e49bbebb57ad6006bcbf59c5403f3eed29c0bf28c12bdeb00a401c7e96d630c952abf2873892bed1f6160b818c47f6952b2c5351a118ec10ea45f6de4b9ebe787449c238d86418fe86a123a3166c9e93331d56a4347602280866dcfae88eda446cd41f880b3ef525e9c2046c681d4d0eae7685efd808ef29553b4dc2c06077827b5f9f8e2727d178bf77e05aa71d663241db0017ac03357ac52f8ad9e0a2bc53d28f9638a5bc2dc031c0b4acee8e20e6d13e06363931d9e4c034b44522d90f59042e126161f306bf3063fb3bdffba539c78bad5f3e6fa53c44a86e88e1cf00386b3342b6204fadcb0103271e61bcc32ee3c38314830afe5e9b40d521afd279e8aa4f45624282be107fb515526898e06d38f78a6e2174dec5d8bde1375b0aa9e93473de9e0d922fe10c2a610167e94c3491642718575a54a3f93a8b1296cbfcd0e87db0fbabf47073e1f2b3055bc514c1546305c04077e13ee421ab01b8ac005ac8b2b7d3d8ba5c3979490f9cc91b73933f27403a7857d4ec315caeaf418e9d22db18f9b4016099a9555c56839bdcebd0210ad45988c2717db3541cde2751697840ae7e0006492cf11c85cc3a4551aaff5d2974c503ca7636f21cb18853f1bef98a4bfec681136eb82273cb902a7ec7b923beb96b9ea03afa1fc1351347e89d3b7dd58d635e50202b7e07ebea7494ab2b8e4d89d893311b079d856e694cb8ec0203ca7214ad44e4aa7453653ced2314f611ba930825f973b147f2310675270f7018eaa2c0a2e0289c88efb305defcfd9a544219858f17897e68528d516f21c623e36ffbc6dbc228c99d51ea0ae4b55726a792f60a85a0348f51cf438d861c98338bc11c28ec8166f1f7a0661b54166ebc45eff49ccbe98994ec50b2806dd119eda149490456eb4224f20c18b3ebad9d9ce7ba0df72b2e060e66b9e9ec699792ef190d79d9e0f96c1456bc5255bf9c9ec81eec4bc950c156f19bbe3d4fd446a1e2cadad6e972e9ac7ca005e762ecd673f15e89cb376ba09a5f6a7f501cfb31d8ccdb8789a97bd674e7063c0a1b72baac6ad5f1e0bd54afa2e6f304b63295e1252f0008a61a804471eaeeb928b72fde07a1ae7c2983085de0946cd5c21d2f45b7868a6bb9baa4f0c53ab68dfb65ab807d37dc9b1cba02564677c5db85ceab9e8c1d88c83ee417079918b624b91f8462a90b78b4c56e1344fa7e67ec4cedc72497988d071ab572187bcbdbd0fa021f7874ac1d145d9aec3be5696c3c393e56859d4987c22028f88822996dcba3db134586fd6e5223b5fe697944ca6d88f99f2f788076aa4b4da52f622ea0ff83cb5293239ed283996b04efb73026d7843f245a7ba5f3b627987cb16b446ce6855712b7a42e631268a31e68f526e2872ad274469cf99a749dc0e7cf165e0941b8eba1151e7f37979621999cbda0e0b2a742809bf0d725528e2d14c82a3931ac2f2b2c2223ad53f33e83b65d16319d2636357b3310f2429c84654967aa70ff8e4a5ed200840af76b3bf2915ee11fe3c97d1083d529dc10792bf9c5a97e2887a99bd1e1c40af9515cf7abc2e0d915155a87a2bd99366216c1066191d86c809160a3facbc4f7822a98bc7d58b918c2b80308e13592fe825121cc641b44884c18963e905d63b2d730a81ce1afa93dde99bb35e6d30c93ffb1b910569e0bd7f0661602232491836202130a909142265b534b967d68514d8a37a95606f82a89acd01d416aaeca47cd62f5a6ac1ffb9d615b950b36823a6d1138d837dcf21414fd2f1e3410a8d848a0748f9b1de5f5203b11b47c4a1049bbed1ba7a6c11db24da4fdc0a7b0f758f71da20cd82da50ca01ab8d27dac46a937dba07b00f1f42a2c8c4acba53f82f6e94b0220ef780131f0c1451627037a0783171cadb8b213f2ff31a2572c75324504df087237aab7938c87a39d94b2c86d482474c0d3f19ea6ddfbd15cead65d4a048ca69b2f63518a04398a8426825edb61211fed8ae39b6f18bf0c59370211e4f61fde23401b2fbf47b69ee94e8b871c6b3b3af4db3a91fb37ce23b1848292c8dd7000cca8ca00c994957ed230e52fbce54d1ff64545105824354e5b6681765fd11dc5ec23c1fc85f29bb85a722a05b0a6e2fe7a561f7196ed10a5e1847572f41450fa6a514dfb129f4ea036d3208e3bf9f40fa484b4c195de4795979a82a9b4c67a8b2c7ac82061ee2fbe68ffcf76f4211cfc579c7b7dda817384094b52186ce5ca42285d9f3647e51be712ea36b5b110fa11adddfaa43b4782d61818212c3e41c27e006128f80cf563bcea0d6b854035890d223746086434211ca147b03ca7fc09625cbd675fc63b5721bcc084fd703d1282df77408e1b65755957c7f9b83c2565615ab89ec422211e90b315ee468dc2aa176cb60e9603a7e805f6048c83f2c4a667c114307350bdc35da112f3eef64d6e0c9b574688a0e431c17c872a453d798ef3ccf34103ebb8894d8cd80bf84ddaf9d906ba85cad68edd501f894c41f3c075738db4936df7f692a71b2fe6b960dba4437f2ac1b3835fb96fdee833b882379cdc3f9f2cc119d052129771feb57a3699ed97e9069e06187206c39825fa7ca874d582523d0e382bd59f842736840e53936de1e9a3ad7543d0b7e540695834faba561319a9895d5f1c7c15aef1777c945132d0c2a7a58f06d75892f4a2a9f4801459367559b8b1ea99c2b174968f897c63a9f2ddf6af978894ceee69105b43d04628ff2764c1e68e7a08082b5fcde876acd93ed90113bdfb7f5a566012de31d56036d438f6ea49778ae8a41fa48d913c3ff3fa0006c5a93eabac45c9473c7bb9be5a3853ed69330e5444ebc2e1a4d1ad2aac5d9cc9aa58513e84f6512fbb2a1fb718a559b9ce7637714d550855a13b03277d279395d1fb080ee6eead4158ec8a8d92ae07aa944d043d4f8d5c1333c877dd4a8435e6700319fbda60489b87ae7f2c2cd033325c90e52109313553e0618ceab8201e000ea25a3f98294fd73a48790337ab1dfa05544da19781eb9cca02734e5ba693a5b39a26e6f2ff2dac1e6ea710ab659793c134f0acfc814becdd13297a9bc22476cb6ff839838e32d72907b0ea7ab0b1d6f5a6d9c3398916e8b4b2083e6ca60a3fb83b0f95c96da977c723ee6d7b4281952bcd0a0e6826bea356c055af4b2ebd10354142a33021a566ba6aa746a3fa628b798287cc21879179e362e0b2680d2fc36cf901c1756c2565277891d548ec358e6c53631b9cb4f182dd7f41e3d14c42299c50b8895414c7642a05c0bef0e381cc7794246023ccd833447fb1fd53207f806ea244e19e244918c8a459635b0a83f4621996e47c4249f1ccd1a62f0126d0dcb82597f5fd4a5632391d8a4a911af5243fa78e3f579bb608553466b353837a25ecfd88fc00f4ae6a167c1f41362f86a6a70f39ce55f656fd8bcd017f3024e2ffd9ed5d729684bbdf99e8027a9a8bb92931d6118a8d1556ca44a7a0605d8fe22925741e225916f338861e3afe718d334a7a45541c0537768f284672c4359cd7846d166fb1c7315e1b73c6223c90689bedb1896c0f974b5f05a5b40209bfef47630454d670b6691c666b623ae22079fc6a4ecf5cac71e7806c83d4211ea373b0ecab653bdfb5bdb80c6b7bec12d4481dd40b006a908e766c4310dc6edbc2d01a8370af3c354e16a8efb44d710937fbd17e7ab96818340cd9e7d9664382bca187a864b7c74b7744e0d4c5422d26278c86ca893bdca530292b758207ccd688b839a0d1d54754acfa498a93e541fae72220640beba3a8db0b4cc12721164477b644c86aa925bd5c79a0eb329bcd981f49e9ee692b499ff19cd7e541de2b34934f0c358260a9b178adec37566c85164c57d91b3af6c00f60f7e060420448338d38c61e4b58aef30224f930391fecb7d01393418217eff003e66c3bb5ced0ede21f42201f7f49adaced743adf156abc5cb7c465adceb10c2e87d50c644860e5cdf8c9e67a2b330e32ef0e41f047928ebc76acbeef17f7800651fc22a49c22b72885b105951b7e3d7cd79e375c01890817c30450ee5ea420fde9e5d7d14152c423fa63e3d9bdfecef3f02015a06a1f0f5edb978bdf72b365222fadbc4c4681c66d2230f910ccc370965543a75e51448eb7955372bba3d8c94e1f176efa7597db98f4122bff76fcb13c2e7f6eefcb1a8611074ad15da4599ebaf496e226b58a9be7f688312bba5045c30411ed3918b6bf064df64ed379b753f0b3f4900c189e9cb2101a0478eaa0605d84cd75c61754a9c9868923e5884c3611437b605c0d01d04bc4ae18c4005d97b0ea5cce17cc7048ee22aeea0f8ea3f69229caa84930fc0d36aa3d9d172f6df2818a68032b1632a31bfdf3c5f3c0a157dc7f23eaac98ff36ae340960b79bc65509fc6396421b8e0be0e67c057c2b5f76642260b5958b3736866f6fdee9baa1957b088a74545be7c35122131aaf9c474c380758a176592198424c17a2c0b7d7b14d77132eb10104da8e876cfe646ca195d4a406a9d89659f345733c4ed215c4f5e493f2ae99e119e96b0f78831669fe98111f26fcd25cb48b4e4d2420162db1c963bf62a6a27c6cf469f39fdc596cfa8a78287efc14533bd03c5d3ee246773f44053867f4540b9fee347c2272ce8df9b89571c2af07c7e11363f80c03bd4eaf96398cf3659df65b12532b4b2c0ee30c56da1c807bf85290eabb7a2879de636deee178c9b402640d1eae576f2b2f5dbe68a7783050e4880d6572c0a755d55fb75aa1ff9a35a23f2c007d9df33e3752f196c4d4fb6f37a7b1e05b2408d5fce6abd37affa99f23ec0bd8f80a129f5a21acd23bda9c025352441eef41dee22464765bb23e9fec3c7b1fed0a9cb29ee8414e69f51bfdf09f4283d8e84b8020c0ffcae4549319a6609d0e2aa8fd14004c74ac2d304bcd131cf7accbd8310213292041106a26bcc17cc2c8040127e07cd01f73da0441cb4449184c2fc166b027db817d9d685c34326da02cd96725b3c84bc7088c475f4c3920729cf04d4f52b2ff8d176ae262e5fcf761c30f8a534ab1fbcbb35ee86f59d47e03a9e31e8a2c0cdc42283c89d20494e6debd65f0f27ba8edf30b974fea8e3824113491c02dacd467592aeab635e2d8ef468c9489221b0f2071d6aa72abb8bd7ae30cb26075d696ddd90b6b6613151221da8ea4e743dc19c3022b3999a016a1d2bc2b272756e50dc5bd2404f6644eeb9b2acfc12acb76090ba18e51d4f609878261e0539eb4f83b71f03f07ac6e3c5ac0234c5fdede76c757e24f8f9b83918416bcef3a34485c869036290d1e4fb9361dac39bf2ecc2d62549cfff66ab974183b7437a6001a181dc6abbf85000d4d5d3050fb645b6c3028f4969664b060d88649eada547d47af2b4aa9cf108b672bece53a0b3916e9db10b464f815c16af87aebf10614868855b02de5ceaa972a6827c92ae8d24346ac02bb60716eef7b9cbfad0a14d44f659b1cde26592c61244109361a9087a8e56d8d235b59e0a6fa2215e3cc31434223f545706aa4d33e5715b089bfec261a74c174c6126acd404eb0f120ad440f19801a244620de22b9157de6770799e2d14d7d6689b350cda2d2e68dc18abd55cda50ac1083032f63d4c6bde2a739d78b76f154b77b408ca11438bf530768e8634800da7c1cf4af1092315c830d9b927aaff7e8af2909a9f3c878a4feb301af84a7b3636e1d66b1cee486af54167e4e28fd4232d40367e5b5dec1deb6875c3b8c4036fb30389a550522d19ad1f7f39f2ab9323a3c5a9014e4946d000e30c001a88d51804dfc0ea1080fca7a294cf1b211ce5580b03ed6dcb495d13ace3762aaa624e4562ee9870a0cae20fea4eff320fe4a1ec31a10f1e1fd1e5efa767bf04f09edbf8876a819a5bd959021c7bcb32cb50951175107585e3aa8da01f4f5a1f13d4c1bb001789dd0904ed8db84e342fc96e928a6f7b68d2eb8698bed248183c4b7343f4e75c62161478ce76d00ed448fc2173480e9868234e25ee0f550ff56eef54b9b88723e096b540b140deee623f7478468ccbbb6e2d6708256c2e86b87537728ebfea361dec0fae36785e1985aec39aa15ea606e8b529200bd11a3ff82b45d27bdcab7e8681d128c31393160bcb6e05ef29886313d8bb4cec42598c61ac94e7772c9cfef41952ace35db1b595f8ac97cead2f35f55046bec90714284de484a7d61e6bf2d219556f0d6f4a9c9baf76ac535333c9945415ae2b99cd62d54d8367d5541ddb8e19b8dc988a18d4fbd08b71048333bbf05e3837b89ffd788afe89d5b56e9aa3902bce89daa8b52ab9692e47d51c3e6aa328c2b6ca110e5d6905f01d8cdb8685c30677a33502a3863ecefaa028f9b5926529bd77c75463598023b404ae79f0db08f53a7552d1227f2611653cf7b8c1dc0a9414aedd100b9b52b7a709b00e6f9b3230ce399c1b7128e15de49aceaa16aa1b11ee5fe11116418c91a786993b05e1de9ace5177cb029dba0a51014e045cf98ceebe242b57d116cb28d9bba35e541f8429c15975b317ab6a2be68e4a53dbf86cdd369862fdc73de35f5bfb1b82c4f41f5f3196ef1024c41acd27fc111866cf2fd0876ea3ce9f6e7c8e7cf05b9acbf6133bb9c3f11ecaec6e0181b22e634177cb0b9db9c049fd4f98fe0ed52ee4356e1659cf54daf5d9424cd7590b153f073436104932cd44c35fc2f8a35343e7441e6f7427a2273dd0c174493c22540f60414d730403c242b00c7b22f7d951a7faf161d8ce8cc7de9ba529b30233ab94c83e94d7c3cd207051fc40d815be964b42447d15614e9b9cb1a6939e7640de8589c543c2a341075ab170baf7ec84f0703e85248649c10d9a3412f26119b67eaa750d4af7bf0ab59808ec6e5a814d6dfdbe84d621993d81838231ed53998ac150c3deee04e25bbba7560c570013bc73742869b8ca9b3c6cda654f1aa415ba3fd95dfa98bb88a4bae07d27312a44874835d4861f0c7c8cd94df4af0c30037fc8215dde890a4480ec5153b7278b5a33efef47690e3c0d6c1417a0b96e4a22bea87f86be2860c8f8fe60d0b19f31fa2b88ed8909b81879f75ba7953e11d4c740d7199ae2f5bf9321b858a1a412fbf35bdca0d061b92b91eaf7f87442b2bfb89f49746f373d239d1407a3cfd2efc730be6efde274ec6c1a56f04666a196496393e1c45fc8e4d0101aa1d3944b55630e5a06cf1c0d87e41ad9a654af63aeac63b24e514ad6071e4ae70150ff82ad96fb9d94172736a84c1c9652501e789a16546439dc8daa8b6bb328f63318523ed4ef0cc9e9d22f1d809321c92eef9b1768c894e7a2ac47336b3c7b5e91755cf149ad095f888998de30b089f94e23e8057036ede34a5826a22d631458bbfed52da1211876850c9bdcc15a9fcb31c4f54a8e3624861708c4c2654495af97c8ed6cd42e2bb4d8b8eca261bfc8e3ed9373bf24507b54d37b9cc2c50e697d6e96434d5f998375a577038a52f6e65cdf1da17c057918190ec38530bd320c0cf24b52608ad6ba6ded30adec8a7f29fe73e5fb3be9460b6158b119c3af2cc8e4b8a77b1749f715ea7b9752f57bf8c768d96239283e7ecfb232c6b65a8502ba5687e581fb2079a25b812c197667e5f5dfe55a1d93645ae978f7a3543cee8a1b49723827115692d46e6c326a482bf436f13dd362a19824dc02a5c3edd58d42ce09a25638cb3cc82c0b52c02a3e9915f0f6f93138d6826ce6e82fc33ef5d774b5de121eb40b0c9c8b50def79bc9aa22571eecb1c3c9b7f17e8de82817929acc48cdeb1594f463068018e6271f6fdf29da154b7b06e6f32790dc26e9bf956567d1085fab6cb94b04fe29479ab8706fba8cc3526a64214bbf6a45049d7e4f14734709ee4f646606abcf264799c51a18f9606371228692bd96e775c1d75a044006b1002a82f996a0d59d9e124c838f3178e5d64247056736b685ef655c6d05a9a490e596a8a2f07c31d1ff7bfe42acc004bdb838ebbe80e782f2b814932d3dc55370a2a7754b50f45455a261b4e27236d27971347a395a1eb73c568e240964085704ece9fa0817763860df65903144b098220d9673b09677589cc5d9040b6d0d61e23d89b046a10e4905858713a34ff5c38802ba2de40593f56fa7c94c94b061eb84261aa67b2b5e1120e78d2046edef9575d32c520556f66f5e63f7a27b722ac384d77055e302e472804f03deb4b2f523d4c7ef352cdb3a0322c11fa2b1a1ef2819753f69aca06ecc3d3d3b4fb47a50fa8cff79fa194107ff1a5dcd15d15b6d41eef5748892a6d7393428bb3a0b1f9df8d06e096eb93adf14bdd64c44cb09236aae021ce6ab97014d16f80d5b1038f56cc30dfea65cdcb622ec9996dbd00aa994b7f0f66dccc41bf920769860a0fb9676e9de9c1ad0d77ea86ff7fb20ea03850f79cf61a7f1d9fb8da4bdaf2089026ba8c1a86c628a3afed53a224f8e6631016a2511d6e69f03bfd6ae2a6881bb8c4f8b0dc47edd464bdf3316990b809d875172bdab65f1e5047131709299d0c20c7fb1ebdc310c7006af14ec07f1e3147eebfb01c6618945bb4d89896b4d01ec1e6599b83f49fc6da1e2b64bdd1c0cd8cb3fc50280f1c91c7e22fddaa01f709aa36c809e993399063396b8a7de4a179bd847154bcedfeab1443a3eb0302622a7ff334170dd917e45c8c17063962909efce3342083aad680436a602d678128724f5b3902eca383843ff15b7bd270a1c97d7639f50ade783fdfa40ffddc2ff759e29c38c214cfdc7cab8147ea132355ef9547c816d33e3c6df4c4edb871b0a42cbed5f1ea911e3663cd150698fae3feca591d5e04e69dd62ba1393b47a622721b2ad61f6d86031d5d2c90782f4b75fc1517d2e0469368fd480b0e49499b83f4d5ba6d4c8bd2c65ebabfd8ea4c6356fccee4bb45fc9769fd3b4383382e00c82842e273f01f38a199d715922d4ef2c463228450b09a713380ad469fcb51817f4c217143ff348e687d4a4ef7e5cc25e20dd20fac68bccfb21af5151edd72fc4a5e78a11090e67cd72428740d2dd94749e18a5041dfa3db8a14ccf3971082826b2998c1ed19320cfbea8e6ad29ed830d1885ed51eca376e37e47dec735439430f879e107bae814f5547bfaf38ea58bba57bb5c96e027adc0e5dde5167858f0a9759431783f80668f38f42126ee580fa4b197746e93dee3b64e22c4a025b5fcf07507ef25c53b68da96207b543d9ad93c7041ebc5f3e71f3394c2172eba6d9e481ed3a07e4cdc65a3c6a804174e7cb1d309f7b7125707ed83048aa49670a396ae3d16c346a20d534f8e5d573f48b67b957c8aa101fe26d3ad70ce17c71d6d4bc44c9bfecff88000db5bb3cc71798965dfd899dd3974ceb469b894c9667b2b16eec7bce93282bdb5f3fde85a55f14164fdd7f57e1cf265b10481c10cbe7c6058fe42a754a3b024f7be58a41c39a25e54b6ade466e6724c7f50309518cbb14477d79d6a34dc6bc655af6565acf296aa4f52b02dc792c81d537f6ca056ed77d7134b1c57688e9f0c42e1e56f286b40c1728f1995fb1f7601d90e8af785c4ffb389d118c5e34e7b66882782d7557cbb53a01eea8b73e62f33bbd7009f8d7203ef964de3186130d0b78158e6e284a8e074fd4f9748c4c689406e7416f8f5a9107552ee1c2321aae286e7dcd26562bb5357cf2432501075af989dead5a520a10e4a12d19eb6ae84b02076eec3bbf14a67e494e88bd1970e8bfa64486c61a4c7ac3330b0321a2948bd088850cb0a74963060f048119f171efc0e5aa5dd4cce94e651702de9e82b49da578cfc3c0339f31840dc538d7c7d6ce254cceda2f07e71cba71debe72dea0c412cabc7fb2e748e0113ca52b4bceddee84cabc021d789961deb9b292ce53b178fcecebf0bb960f2e423ee3d9c4375cc955bf3d6290a0b9d1b8359c2e5e807e6a894e090ba8a4aad017e5a0a67926e39b41ccb246ee1d59dd25645ec4c1b12318c00a4b5dbee30b76b7f54fc80fce5c3c88599096fe2ef6a50fa49fd96dda76bc068f119dcee54987e75002a21e987ae2ea8b4eb06e213d237537ea429a87b4a2c58f92c9038dc5f65da661d0cc0ca5555a784c8aad7e365f9f97e79563434843a6543444840e31fb3cd34e7017c910440661bc81ad0ef10b9d8c1e1701f891dc11d3146c188525680e990f7c902e36508d3c7491bf476bdc1480fb0fa984e0b5a31c6c861c82e01feb40c8ddc1cb54a0d2c6b35241b5410dbdea79b2b9cb103ebd9135dc9cd93619291bae9350038f2deba387c42995fad10bcd1510b3c0544187635e95a10bd75caab5c3e351ecb31a51a69a1afeba62ea5abd5575f6a05f9490e53f2db65e53108771ff5d9accba1634d84dd9ff34821c11b7d8d93a4fc744e854043280a9223b6dae6088ae33b5dde70373d8010653e71962a70ddee07c74bf948c3d33a5a17061869a6c77dbcd698a7a842dab3149c477f409da42bab03aa9897b0f4f0624c9419d506f7dd0e7ced0426c92e97b3e0b02d032a39d5a870f2ed7ad735979b7108578d262462fb0911cbc8854c26296ee524d6f85e00da929541e782f947dc4131994ac306cd0911cddedcdcdedb117e0bcaa06cbaa4a736ae0555bbbb3e28df8f60f7be47775eb14e994be878a441ff892a3bcbb01dc258f6b6e7b86b153a12e4ba616f3ec16353597277c04a40db87e87e4330a975cbf932b97639e4d51b7da1bb5c50d45f9f0f0aa701258598af19a07287faaa7c7989382c6f6726cd67c526e74a0c72558e82af227634e103718d7a42cb17b38595301fdd967eb9429968f98bdad12760e66304cc2858562f32d56e587bcf88d26bd591c742d6a56a313816e90b42f1dc5747f58cd54912004f6ac59741af86a959532698e812112c49024e9fe6434a0455306352a3b5b8d81262e5fb869e0caa08d63061100fb1318921c6a56c4ff61281d86565cc5ac63edecd11803aad4abbd72cf2839938315ea38bd8ad52066880a8a5a08717162a260ba7596ffad46d82839a24c802e21b19e54bc745410963ef69bb2bcdf4a0003ae3c4094de23027da195efe49295430551e41d5688657e00c0a8bb9c18d86e04c675729a208ece20e2a5b87a189b0d5b5a19e93cb3d62de6dc6ba5b1246fd01bb4353a629759439d54ce1b2342049526f144dbfdbbc36bd00078335cd760c94546845485eb26e22fb100b7980b5813d505813d5128cc9ba18609db23dd639680447e98d773e5be0e0c13e9a589319702886bb2a50751cd0daf5028dd76dd14d02975c53b002f4c636187b0d470df5b23d1d5c057cd189b076842fd2c1a564effc64a964ae575f39a4a609e210c8be4e92f8ae2348810fd1a04dabc0236e3543501a83afff2d6919b5ce4067b330142294e329ca41236212dd2ff32e3808a998c057ef4210ce5114606b6cae62b9ceaeb2e00cd144ad62a46698e9352af64c0d4b83412bd49124928fb4b10bdb6e47852eab0cb23874bd12b75bf249a3f4e56605d1ef864cde00d30cac4e77c181e774ff252055d093bfe9ba713b13a26a201bdc4301de32a7a6436f77e577c22f51d28aff816f55e01f9de51421e79a19dd4c720e20d0ace259df63a4d96334c44522648f6e2b849f779bae0a7d320583a0935d498e68c249d589538d10915522a18e34550f77b6c3d65735a8b5ae86417d244e8193ae674562f6e0553278a0d0f472da5967b5ba50fbfae0ae8cda87c985b6667b5f2e77816996120975752dfbc6c2e24cc19f6f42dc6ad0551fe1c8d08068349c9d2af3931312baadcfaaa3c1d6c9e47201579654f62534e89b3f180d1f220a5ae2a16fa652359b71bc7831e4397506c37605c4426ebe5174992129080225468d863937912146808b613865702027697ad00bb0ba3519a3812790412bc8e36a0386f060dc4d76838015dc25c5fe0c080bed2ac646cddf0e952e261c4ddc269153b72b1728b52d82a75e81882e0545e2a52110d7b729595a888d9454569a4b5998b4b90f4b943d583ce73d6372c1bc109943c4fe05b7e3851acea9873f66d523e64365685cebdece42b39241ff0209e87121342282f300d4b6cdc8e8c3792642ca5e7bdd2616d8ffd4849eee9942bf916dd1d2fbabf3a8882443d7e87b2ac9d1f339daeb22831381d7ed942ea4b96e154ebeb1e2dfbe6d19b0d409196d13d7b894202cc539de8fc04835e946be4c4ddb9151e6bc295d4a10ac31afb4b9b2d1568821d79ad67383447b748b22840f5a9b2e111ff3d0ace8c9c374df49597a91966eecc5277caf3e0bb6774cd9f8da94951e871658d005316f22267c2481a2f343bd9209643c9688358bf749bdd3bffd6a3278a6469b28123568819d7c532852aa9e56bae2e7e32e1c5342561b1ccfaf6db1c797c346ba4e1cd53d29257e607f7400654d7f6e3c3358c05e0d6883f8f8d00b17cf120c36cd04cc27a1962c40485e365051b1d01cf21a5d46754888c57a52227547b1f326a2df4652712af31945c55473b5f19187a70ce2b6adc1be7cd2c0d0f5fa4c140aa505703f953c627b48ccde9840a46b2e8745646d426a7dfa20224e20068390b3abe4ee39acb6a6ab3886f86a6a064fdbe099dc7d0fd8c4de747617e480bad5865d720c81a3c9f31927bf8d1903c0c0c8cfb7f18db19b255ce4a5a328e1cf9a480d8255e42931d6c7da4825c8852750851493ce541d8cf518bea08a679d2b0a6edb38c51500c880b4118744983e3d96b02b8bb3012d372d557d43b7f7f3a135c0171c750f1116c0f62d988d79ace63b489b654eafa2b459b2898d89b4a535723182265ea2f23cedc1de0ab6bd3b5abca379a1e48da48cf232a50b4e16cdfaacee9fd8d47a5cac0e043f5c8738f0b4d9c10bd556222be15b9a8707ebd1c0fe5806015c9e2d103532fb71cfb8038b3a1aef3304d297ce36aa6c9d46525da6a37e95b64cbaceb57b95cfdceefe4a8877fc761d907152b68cd2c916510040d5cea0278642355a00bd0f27da4fe105610ddb72a408565db5261d2e50e76aad6d95fa7baae2038a43305a2bb54250144678a34a38aed40a2663c17e0d9ddab0f50487e51ace7d9a1fc215daf67a52c5554dedbda7c6fc4318e79b163fc3d1e409206115f67d69dd0251980ef373512621077ee73a3850a5ef58c828e249fdd93ff043c0424c0bf7e898a46382b9856aa89c8e44b10542d659ad030b0021087a5afeeba5e1de85ed536f2b54ba8ece3da03c0b2c655c9e65821aae91b58f0351ca9b0655d623714e692f5f745d6394cd460f6d0a1e0046d60c1b5a8fee02df72c2454327ea0e0b2084e0c17ed6db9642868735739dbc928d0768f9b14af09712b191dea92190ff61fe3c07129ffb175207b758c6b91178b1ac35bd41ac1dd585d2c6a9d2e202ceb2275e40edb6c553a1f68d7b645d8393e3ab347c786a084dd22f78871130d7f33b9fda671b79de6bf51fcf22fcdf6d7971e6da1e00bce1c821246fb92b930ebe7a6e849bda0768f06d8a0a882097f2f6348fe82e2b9ad27a0ca0090a2e51021ee4027b0c5e2928a379d8a36cd714f7290021b2fcd61cc54f1c926c609419a9d204cb66dc88e30653a207ca8b74595cc82f2f7d8eb68d74c6c158702ed1a6c24624cb3f897da21bb0fd482940c6bc560829282ff6977a891b62d346e6400a8b543b282ff6925534456f4850016199247914de8d54ca1ce890fa76232d96ce25d1f4b197a2f4c5e3ea56a3e153090906bb14df533165fffcedd97e5bb7939a59676cc4cce831e90b0c7bbb55f928f85da162e67bb587de32f0d6a429acb5a3fdd9cacacc9f7171399c10b321e8f8a60927f23e67608ada6cf725ff674c710e44e709fc1bc530ef7d0569fb12cfdd87d7f1334cfacf83497635ae0dd3781d92a51a849af6b47d85666c0072d9241d0c24833e3ad18806f0c6e2712e0cb06c00dca151ce9792493ed453760629790066245f17df01203158a60dda52ab8da1e21bce43263d7b9d25ce2c288b3a8224d93396e6aa77a9b4ca2d9aab28d838b45a63cb74ef7e0e8e9e4a0e645ec30998091c06183f24d132262f03a4aba7092669816089a5796368153748264b4374db1b410381bebde8376c723b39abd0a14779082f9ec5df5466aec92a14a61a9928487959399cb2409d6027425fa6fedc10898ae935794ec4d44681f1c730e4077f2afe59232cb0b28eefb4d9c861a105c9560b8b7182ac417acff345fcc76c3d50e5e5ebb25dd2c2ca625ba03fc33ee6790ee9bebe35f7306d23abe12439e24a6795104530db7ea958b389c1928be48f4b03d4c8532f604929f188dbe22afe853e75a7903b426346682b068946fda9a58d4adb2d1cd0909ba234bdc15d9020cb9f88102debfbb2596e2773e797d55404f00ac75d9ede4ef675a375cc8bc33a9702d65871417fd43dcfd561ab01d693579b6f3f208a549d19c49e44d5dab2df0fc45931e77ce8dc6e126636cc232c1ccaddf7a845545bf8ed59ace5f3089a942a9e89a9dd030c65243307552869e1a48ba76ca839beab4c22d93bb6c05f86e924678080e12619244e0ed92ae67fb14ace537a3165911655d5424279e241567da10bb6ca592a6c0a69fdf526672db1f2c5c282f070c88e756a52fec1919d870e9c815c8ef408f53f32bf7aa57510f10e6726bb3caa3afaa182456b27bf8de5180819f1b85e78a779e0ab21e5f5f71a01ed4a4f06e26d3f34c8506087b3d440bbec263bb4f53838b3c248c4eacc8a580c13aafae462372c3803cc876411c36f0a7c5d3f267ae0d40ea9730a5b229d7f19338360a860bf2aa0a10d1dc339b7926ad0ebd417ed18778f86a4b87b5c508a965e27031c34bf1308ce8c5da7ec95c1271a04671e614804b98c06a894e6276a2104c7b2bb9284c9e1fceac0f46f87ae3e8119d5aec66c488538875ad46174ce321feb30b535b784fb56eb544ac0b98687dcd2e2bf6ea176ea739ace974b33e094df00c3d3e1cc3ff362126587372e465c84b306035438cbe4477ca1a3f64c136771582b0f9cf1036a8d74ec05d13c0116ac6ac812ce62d1000566c29b5cc27604c06beb627b5b3863907fb3837a05d369015270c6d77f267703908031c381f3488fd5c1536b9700f12b5b38337615a14ecf12062f7b3db6cef65d7c3843baafd34843b33c88a81d911bf3ce5af0703602c2e3e81c0e7992938b0bf32292e05c375e9a94b27cf911f1c3c2f609e02a825cd90ecee3072960236e725b8c578d18f4187d3823c2f20b52b706a002012a3ffa659c7016dc7d3722239c5fd42f68bcdb6e76882a03ce723e51b9e6f9a20b8d80b23e81c4fb3bb414443240df343f810a926587f30bfc339e6c4162cdc0d18556090a980109ce2c161bb6a1ccf100ad2b7cc80c863398f29bf35ec6a076d2566f7db5b5220c12488cbae92c1a111d5a827064946c9802cee4b5dadd52b22330381bbeecd1bf1e7b9d9607ea6097b0e2186b3f31f002e77a4be87b4b106c39a3f5f5b5d409a743fcb31d49c1d7b2006708da91f7ae2cc5b540850297292fe9384e02e7d78633284c01ce7e1ea1856d2da70e86dbc7a8b594c179a4934600e04209ceac2b2841b812d579de4b7d94be42c955952ec775383f9c6b8a00c7a647eabd706736c20a1acea96f3819d2dc17819d0d6781ee392db817d170757c918171c164c0f9813fa22d04e709d5984d6362052238fb3d6938773964da66109317d88aa6380a2b7fd63d321213e16c1d3fb611c22454429a758075aed1bd0062712ec6b36c0313ba1b360fbd4708441e94347b0ee01c611b9cd7847270677e6320bbe08801b098724ec267119403abf579bc05cf19e99f6ac737354fe7eef3883c9b5cae130eff0a351ec5f85190f2d6f5dd079eeb411539ff22e7cb1092c63d4b4844076273a1921464d8c66084a62a3d7a60d8b76e2593bc485196b8e0c7507d0a3449b9ca92b8dc321110294ddc82a47e296e2c432c4cade6192affcd41219dd6af25d33c9f400e25cd8da7da941b23e4484dc02fa07aa9c642c139f81dec81b72a346b5d6e594805426adb5f54d77d2ed7bf7806240ca92b6fc0f7db05ec22cec12fc107be7aa00b0b72a202d772b49e6d8682ea5827189476e37de8e050ba9a8215f53fb0fe9d4b51c024738606d1c6c196be9a8ddfa2cfd8ba6b1a199515ec0321e24859830faadbae219376d4bc5ffa26f2e1abb01a31fc20a2d56a14e4849aa5b5087639a6f2c99894b0ead992750f0f0818ff5443ffe864fd53cac4c99cc6697283e6ba4d6dd005954c60973f218c8a0b9bc5f6e8d24b29d8c39491884c61815416cade9076cf74b43918e46bb5aab5780280feb1c0efb2dfdc636421324b5ca841464eaf0061fb17908a70e0adda28e76ab4b7000202a49bb55ae335161e8359797204c7a7e2967413bd2fe017ec01bc4670df1de238678a7fde2b67018102962a7c0e8cf3fb0e924b3ded000537e7ff56f7e4502025703603752301381e3ccf9e7b5cf66eb8d71c9956f5df0292a2a689527ff51d67ebacb54d00540d35d56e3e5ebfc73cd6daf8a55b79a771bbbdf9037b4c3dfc16bc5c8c33ff548b3d019c0bb7e9a31d07160453d2fd5dfcde47072eb4be0cebd0036c14439f28547aee38c61cadec777499eebeeb481e0596a0fc54d39b7b069f7dc132002b458d3ccdac05243590a90b94fadee65580ea3ce900239d1e7ca3b316734d395017225542998f783449b9eba167a60044588e8ffff7eb7048d7c97ac701917f7ea156356d1e8a62172b40e9d23af9cbc14e4d066701bd8d166f3f3090a4fdd568c5941097ffdda1a7346000498d85447c5914578d6a35d8c1cf0b4414907a798d608db14c5c9790b23c7de588472990de1504972b2e7504352aaa9cc95809519a3274947f9250d5f483d9fbe4270998949d70845822bff187da1c2ef06bf58ed8cd464d1b61a4d2c6d2a21dad53b8299d22456db46301a0f31d3a6d25c815e76ae708f50edc615f1a8cdf7cabb65bdd11d4ffb035afbaeab5e846c9b29903bec53cc491d3c34c20bca34f02910df894180b1fe88b2829b6fc77b842b5e18a521834858b36917e892c80ea622078e71cbc88fd2f4a9f3e2a9c616b1da6791cea20a119785c79c24519af6d349966426c493f0535538689fd4c96008006fe581007fd04063ad3209410e538bc4afd804a6a322cd6217fb1876ecb37e28964f01f15bbe300cc25b085130a3c14da6c8778d07ab8fe0673d0d934d82316dc492b50a21a28600e490e4951e8352ad795e5a115e5e50a0cd0757e69796707558226971101bdb6d1fac961fabe8dbe68d77d1127017a9a6c31b61c2e2ca165087d65882ed3c851186841c012c69bfea93f2006cd82d4e77246224ddf2828fa496284d2bae5b844bd6f517220668bf5219102d32c7b7912ec7d41c4fadf1280219122ac7210a4b44d89f2605463554242f18265599adeb2665c14792c43fed804dbdd34caaa1214752ce8acd0e96aa53ad523151a76d07036aa61a0e86b7c7546067c6e3cf565ee7332acb1c7b0cf677133ad795008fea3d8ad0151cc782140d1cf43614d7e09ab287271f31f13267dd65f38543475e5b20b467463068ca2eb0e7073d8d7da450e2bc401f7e376743f23e2bfdb089415e111457738bf0c06c5deea9a26ad6f53ccf3acbb208a715fb76a0df68467248ee2856d90f846bad3dda49fe0a7ea05359a4b025e6f825f1ec1e4ef18a626cb299c28cf3e0a6e3a907752d3c29a1b1fe1ccc6206292b13699d56d14a0063715e86cd4de5560fe03d9cec888e3f281c3556e8bcff5bdaa4d1d0fd7dca742c5e0f00017a45cd6cd8d6c3ac337372a6841215639a791d5003945aebe9d42d0ad8a96f934097dfd0bf1c7ffe2894a4ab81c4b3c963273c67f3dfab76ad437f483f333b04f28c99b2f3f2766e11bcd3a63f4e64091f16672a0a9c0b0035a50f578640753e4455ec323a19b6790c7a4556fe3db29f45e53ffb6d017216a28f79d2f94a03616038b53a875605a15298016a2e403103eaace529108d2bdf392c3a9858dca6b93cdc62b13e5d2f3c3620ed530963a9d80caeb12a99ac2ca0c97100aa2376d16434412064d32217e409a58e52b2dc0dd8c25a993f4056bfefb3f1dc6be520f0d1bd70330d0a58cbbe38b51e80849c01b90c0008c1dda0cee27c5ae1f83d39af9e1275048032cf30c946164c4993f2453ba09c35f3fa5d8b35501b9968732d80d71edb0e18b2eddfae4544cd27c583868ac1fae11f7e9b9281b402e556f379f2868ca731d461113a23f44b00f82d5e16f192775c5aab0ba43056e1c7bbf0abd588435b3465429d627b11be2551f38aeacf3e7ef9b847b524356e27f26d72c44c54832e6c9dc606892e19f1e72a2b7c7f3c3ceff691fccda7b643d7fc5bc9b5c99aa094eb3ad5f310db88c17ce6db2d9e997b4dccea1d77cb8d04121da4b3ac551d5309608fc4cf0f8f08f6578031f02e3af1d4c7610d6bf455ae274c4c390872625c80737bbd088da042c5597fb7b607ba69eaf95f69861451e266bf50ae1b01f104f10746e41de989b848e3c4c7b57f33547c81f959f9110bd220fadeeccd81fe3086399a29c7975999f8e7cf8cc8391731f6bac0019f23026e7fc6883753d8fcd8c89a4e9b5997845201e82e359220779b8e3d9852663812ccc74aff5c951a9351c8e10faddc4b0a8724aeedfafe1a26383c35808c169d408d59a4de4a1f77bfed094c288c00d0a9698fce3d71882188c558d83198023c013622f90a0873a4d7b58f6f7ecac156f75bc691c9dd813e792da9cc0d8aec7fefaf7ab0a6f4db8dc5003c828aa5638ed2ca274d79715853b7eca4311fd8a7c0b208d7721d5969d7cb86029ced34a42664bc8f602934a93faa19fa51f4eae180fcb0899bf558c867150472d474f56bb777b294eedfc0e0b4a65967961c142630d3ea6c534f78ca6f5f01467af8b50166f48d29c485ce83e98114ad67fa6aa96d06472418e09ddd69c26b42c7bce7d600cab8eb299eda2343e66ac5ee73ce337c778a2cd178d062e60aa739ea87fa3c362fe468dbcedbfb6650923e4b6a24d49e9ef9706b3be8df2683409a5af2181b2db0683e690ce46c6ee8887878ed778ed56e16a4e71bbaeddf42dd2839f37702020412982030340f363083844442a01bd21059e872071938e78f5d522ddd96e73ecc68ea6c1238aed0809aa7daa19021966c44568a14f326a12b4f7df1ed33285b6160aa1fe9f8b093e169622a3ab8fad0bcba02cb31f8dfcc53b1cd168d5d480a6c22a8f52b61843390860d7ddb36f25aa8e89618f5e19ac7cac8d637c9168543e2b7378d0e2b141cec8b5e5c90b575c5c82abdc4420dd5996809f30ef87bb4c5498fa9fb5a8ce1db703a67d8196a6ba9ae9bc78d3761ff0913241d2037ab38f3efb482da658ac3ec26b6604091ddf8e160859a0cbe805ea34079b56203977d5a967c9b9b60eeac148a8617c05f69c5bac12d182e51b85a589c128afcb13f4cdb46b07b4240c129fc7bccd898c820a0aa75a5957aa2d5ca910621f8a902fefeb80783d923e7c9f4662eb3a995be13023ea696343cf5ff8e7d300c15b6cd3f499d1b5f77377c1b1c0ed4c3fc0b43c3127732712158792696b650927b8385f90cd1bb4f2dc78c0bf3932969921a544cdc4ca551e19f22b94ac8872ca9101833c9b94a8899d7221191dbb566d1871d24d5e976cc0cc93287ac98081a95702263bbc3cbd7298cc4007dd95c664b53c327aa14a361881c5f9a0e484ccbd53be965c87b1e25491479f5cfd9be177fbb93956aed825c5ce19ec12ca115513f82a57679f71701c3e4db4a283ab73dc5e7cd9e8375f46bf874fa2ff512cd198047ac7b4158805d71ca8219e71f5e5c7361178075457775296134489fadfcd9163166a772a44f21cc2679d0dfc2156897199fc42a18363588f3d62c1560747cc49b7c854d73f68c1c0d43669d447fc714aa9d470a0cbc8d1d6e58e37004a84a7f442b51f85345ff04c76ba007988d2e92c00e97695a7b46c7ea15a6422654c76048169a8263587a8e72d356c3290247383c343e1217687d054273d4492d687488cfb10112044c4304f31dbbf33d2a27ee0ef2710694b67d57b5d006b176ead0719301e3584e648220b9e432406cfc02323adc71d70d321b712b500be0c4ddf535e94f4bac8f83c990b25d2fa1a487a138ec75915e2f4c81f1976aec5e1160e6847caaa66d8af7657214fe1b006b01b2a35351c51c9bb23babd4edb5ad32b53f480144d003025d5445daea67300e277f90e7808cb23b2c31c7e7eb419f8db18a5150d21001172a8c9010e2b427d54afc25600a0f6d45c9fa338c95c82244a294d0bf06964cf24d4003b92047a1abc5b121254584695ddc77c9ce4328c2664aefa30e8fab3da99582791dcab10d89cc5a5548a769d4410f900f19e3e3e0cd5659d04ebf36bac490f224ca87caaf460627512611aa9e64b08f15247050d70fb232c760ab158f11f30759db2d6e4b759d552031850379c910c887c84ddf87c0f398ccf4ac976fb3672fbd164d08339680cc25886a4f33d7e3ad914749a7f5caf1d68fdcbfc781836ad026aca9a685428cc0dccca4ac8adfd2360514922df078021601758f768bf43ba112abc9b92abe7151262762bba94375593d49e0430fc9a375fe1463601e92b71557f8b4aaa6d5afd0f2d6ef33bce09d024f102132516da8a24b1e55d807c0d7851acfa3e86e1592965ff008ead66e9abc4daf2d8bf716eead26cd9c702ddef9f029fea69050927c2f8bd1c858c129d55a3445d3211f5ea109b471701eca9eed414ccc24646093ed4c79c14eeaf92d379580792f8bec10cfd6cb7399af3c8c1b70547b2d8f683ce22dbd12ee7b341f6617f57b5a27137636c5ddf87b2cdaef8def6c5e5a43f1c1800640092dcb7f596c38ad0cfe6e8b05b2de85fbd44b78194918f02a8948d4157e3f1ee2db2fb16758ede76ca21b389b52827e083566338a044a482396af9fb246b5cec366ef1a7ab79418ea5003b4b29ac855035dc62827238a93e6b7088bf164f340673e2e58580123821c03e222c07bd48698994b6f1a9fd895b002848824100c96378d38d2f2bfcd12dde0eeac52b92594e7448f1652ef060a98e38222aaf8c8fa3d9f642b5afea67eb909a5399d8596bb20cd6f90662ba1b4a45e851503e5029d34945355b64448835818b70927b0016265c98699a13fdcc37c896ad1b480b8c9d3b3014d55c71d24b4669eb1a5c94736e6c25df42963641c86e0c0a93bacf2aaede10beb878025773512e83c46a12eceb5c6f223b32cf258af6006d427b8ddf94670f4d63ecb472912fcc138db5bc09449fb63b17f8c1a31bdb5a61f5dfcade59b6640b98b795b267c1a8caa5cf2259ae2d5cd315adf1062f62afc82d00f02868527409aa6379b42bc5f0065e13946481e9a4584b7fb66a48e8fdc94f27de0db7440229300ae09498c042082ef00e18bb76fc231f0c222e42d48c651b5d3ff1885812a2ad91bfbd5529cbd142d3b88299d9cd0ffff937345fa09ae72aa05bf2fb886c222cc208d8cc40577ea0e2c0bab1a2a61a95320207409fbb861ab6b496272b215f91f37fdda6fefd239e832eaabb62cb159435f8d6a828cb897798a4daa83aa6671ca829435ea44fd97183121e4d320895c915dfb0a7522ca9e51bfd33b7da46b38265d4cada0ac099256ed274683bfc77e8b7dddb6a3623a5520c48722f428c25b280f2bf95e751788cc543400c23ffb57b9c8427cfde2a6fd74f103f85632ec423784900a98071e8058f352bae0bdd1303aa10fd2ae380a2525c45d630cb3ed0cd8bbf5effdc15ce82c9c41154483a272b4f92af26c50831717b0918eafb780a6bee1018d265e71b6f7f54f537db82af09c411d4ba81208a314718a80ad6bbe317fb0c137d3984075250076245449275cca1cc85aa5452668194ce6ed7476951d7e44bfc85c34d4e3ee621320b1a3262cf6d538f3b960722186b08f5c863aaa5f1735a2900a0cdfd4afb63c89a8784cb0313e1e450eafe46c4b15ae41af28ca3ecf31edf5cea9912bf58caac2f75a6358cc78ea3849cf2a645e8c2e4a138c5ebf7cb7aa3bbb345b0adbc9542da440f7d1b8a8b73d22976b52266526ddd4d88d05bdb2b1d96d4f27ad073b83d34e022fd6f432ffef38fc2f4bf87b7cb44557c28ac6a215c8933116fce96b74c16621d6c32cd3028adce70ae3d65448f4a9d76273630be06b77fe17f2be84794a3ae239d51636124b47c01d2004a06ac1cb4bf9b43a2be93bafaddbe6803ae577f84685f5f72b0f7cb26ecb8f92bb1f00986fffc379dbee5445bca05428209449f42a8ee963f07e5181da1de67de91ccc054ab19c1fc19841598f8e717000209e22ad61971ff4638f143097072f7a613cadf014f48a69364dd157f8af52be745443daa6464b26740a7cd06bb0935249ec2e56d2458f685bc4d57e78e9d0b5860889e139256b8de77d9e5052a42c495dd2d6699839bfed9904b15b2679cbcadf52563d8bdb3c8dd861905b996e01f05d03f871468fdb8a821e6f33060e1068e42cbff00e954f7f4e5dce616859a0ac42847a0813c077036f33f68978b3333d9c4372109e541bf35b2234c13666868d4dee38d9c38cd1879ff0b4721aa0c6a532e3889a4424666f0b21d0d904d3043e00be6a9f47318969c685fb018ed650c131a1a02d52cacba130e11dd44ca14ba74502e35d57b0a3cf536058be82c0c5aa33217041c51ffd0da0c112278418da1542b4405d7c203add0f38af48f2001abda7edcf9d69da6b1c00f97c511027695f9715d8777a0595ee03dd1f51291125712998730bc70c79826ece3c6292a0853506a014a8a479cc6edf94fd8ca177dc2e72e81eabd3fa98dc6a979842d829e66fe2483e6ff83ccefec70809b5ef6fa65b4ee1fca10d0a2564a34ee13ac7bf0c6e806a2def00f8141351d4d16d0d20b617608bdf482eb03e311de80d7daa5641eac5734eb4d380410d4df118d723c2f1709b3fb40029897c01b9aa84bc2f1dc624658dfee2b943f3fd11985ada6e0c928ab133f96370d93fbe32422ef7a9833b2d7679bb77a3c1939c618050f42efae4469b84b2b640282b7e6102668ea8f45962e838eca65bae2d1424beaa1a3f7d25504e44d26a8eeb2727a8a3f2446eba5e959a6fe239e247c2fc1a483cf887b10e8f4111914e9ffa870aeaec6b365bda9ec1a940172597e06d76fe90d569598fa91a62f838c655d2b127df93ae5f608927e40ea2ba27b811c12793f913ff1d4e3e6315d89929e422471b85cb8104fa1ffd841a05fda8c22f5a3d939ba38ceaaaf8f928891c656872a9c368af98e54078a98a566afeb6797846094175aba4e94d4d25c125e170947b6a00e6a7cd3b19befb5b37f44c24ce1eba1a752d081cb7a075c1c209f67b0bd128b2acd01eab3405d9a4f521ef73054c223e78434a983b15667826072d8b5066e100ff97e3b3049d4200f7d441f5f46d7163b265d12b07454a6e0d355a56a2e1d0d955fe836b8880412faf0c67c8466149a6a96379063391d3bca243bba20e454edae457c4765fd48b8a69f4beab27775ce7d20ee41952ae30e5b50a298d9ee97008c29b4cd05ce8c80b612b308fcb61a906d9770c79c4d872bafbc232eb80768797a0ecb030640423335f9053280f80b28009aca90468bc9a8b307b094abff2a7af19afe78335088e4520a034a9a0e889ba977ba66b0ce19a3104df54e5b6fc450e8a15f3360006fac25dcdb355255e6b71c2a0b91bed1757827f2d590997a6d38a4e0fd87468fe0e3d6c4e063014f72585918f5a5db9a3c6869b3485d13da4067a1713b40d4786d109b112886ba469c2fe8685a660b52229ec38128b4e65ec273fc8bc2ea16d494753d82a3bebb75401dbd0e5e5ddc2ab342e791708e8fd1360bfe3bd395423db6f66dbc1850ea372a30e2662a2831b30b92cfc600023aa337e605318ad40da3d47b887ed00afeeae0782d8e7b07fbadeab6d4af43a57e59cf91bd4140ed01391b17e43caca98438028c7a64e0ac48fda0651322921a5d4c9269041743421e1414a1ae50b6d384e3f1fea7ae4f6d8ed0f353342b5217f31735f7b65567750ff481fa69fe1133ac284ff886ac6c2eceaab3c869ca31c79ce9835e035f863fd10ed0797b9a68546a563d113b8df3d4b6340fc0f395529c1b37a5c197796d8b2271e3bbf416206d775491d815eb136048c059871664961ce6a06a8f1ec35e59772eb29b793728cd89cfaa80c1261de61e8187a1fe1c6de0eb4a770f1cbcb47f04110d8b3247fbd9d12cf35629c3e2e83213503dca790e208df97bc4493c9a53430420c1f98193323462a254f1d9e88fbb27fb01174a852abe2e01076d0184ef39b622a9fbb91a65383b98f3ab87c24529cad098ad2d7e639112d0276244d6ed53059e8ee7145006381f6c6a8e268ae6476dad419944954f066a0e6c8aabca916f23e763f3f44dec4e865b215535d0282af4845ec6d60a46cd020b1d8d43dab4f22df5a672a960dae07041b3b9f032b3dffc82429041b02af5667275b1692d149e148308a5734e953663a538063f727b3626e1c8d9f4729a136398b0c5466e5e45cde21ba4797646fe21c9a193fd0a6aa99be6dbd70c4db72a87e221f2e39a371c6ba2867d49a4ecc378ecb5d0f97786aaccf7bc19005d4d2dac005af4b8a4efcdc082a13a1a3c304e6ea037033a1480d5c5b084c6c16ab027e69b0102a07b881cd59d581d0bfdd5747cf0f0f7cd80a3727b914aef76c5a295371660a9364075cc9b81a8c9612eef9b8134eb43135cddf49f64300785455f09b84bbdead443fb36031407995c7a63b3e5c8c1d9a4452a15ac0b2aa8601865430b8934582ea9518769cc360d5252605da2460db3315bda48a48171490d354ce36c69919602cb81af1c4c13b03c13d324a09b27ce804dd64c94942f6162680c33493384485a9229c565c741f2979f3f5bf6e8bfdb4bfeeae780658d9bf44fe1358f00648d710cbe57e90a797bbef8e54f20ea129732bf56fa6a70ddd2675a61ad7c0366030ca0b6cac8c5294db440a7889b0460e2926f767420928d66ab91c79e1180961a0200d054ebcf7a0db149a24b5e02553cebcb9e981f3ac36a8c27e42144227d5aff51732bacbffee934d4e7e6147d7981a7265bbd0afeccbff55f09ecef54e9441a42cc71ccaf75bccc603e4ba4933789944a0c0b6881a8402d9b4dc09a7845725050e4948275d44b4e0db11bc1d32d801467b2950d245cbf205b6705eab896bf46f73088239daacf1af522545a8ee8c74c9465eb40cbf911f7552dd5fe9c8c02024e7c19a6f2d43fdd2d80f10046e1048f44cf8489d35ea43da39ee14159b9f2299c646a71fb1b74647a12f70a70315879773ea862129cfff0e1ce8dc3be5940600aa8c002f615a428a9014bd78ff80f887f39feebe0ea3b26feed69867f772efe396f1321774f1ccedd4c3ac5c0f6cd9a5acdf71922574ae3fd9fba59934d8581f705ea8fe25c34c0c0220a929f66f9d4b526cc4a827b5385cfca23f088b88c230519de11656fdc63b22c391034a296c6c06c2e9391d12114cb0528de0c6df8e2a0044ff035c24ae27cea02fb2f0904713bdcefc0998c069919f3ac1869661e54b3b35b418d91a4db21ce31d2e6e8cab63b642b2252464f4ba9a3979ed651833e8c052e25efcb0d722e3bf8c30268d21ff604f440672136a8214fb7d5c9db4ca2f69b6dd8e3e79e700517d56bac75cea716f08742b180865364a5f953d8b10d86e865f545a200fa2c42e143b2a6bf4eaef1314bb373130a5284f8dd6119570299bd035f92ece5e097041d9701065e995fde364e1c134c212553d795853bfeeeaa5c9ee0b9eca1a4ca7469dc497e39ba3a41c1650b93cba04842ff95041aad826db330387bce64691b0807ed0ebaf1367004986ded71d0fd4193725142b2a9806363713f6b5e52a04f55e3dc4676b83f08fedaeecb32d3a8c214b549ed2c03c36c0b81f7c8183f7f9bdbd768841b7efe02e929f71c7f94e8d47abe6f37800cc36cb3fb6acf8d5bc37b9ee579ed18e3c041f647a77b370e994909a40d046a361fa65920923a4a8b01a9f14edf7f4ee0356cbc57aef3163142b01a7ec74563d1785d2c9ddd6905be5ffedc1f52f5ea7d43b550850250042082a87c98010e72501f4fc185ef7e6fb0fddf3e7451fde8bfe8db6e84882422894476f70e450ad909cd09a7fb79bfb1b06927c2760bdb89b0222b6cfb1f79ed37ad87d60159fa06fcd063538bd04128c2e7edf81d113aaa83c80d4334aa91ddcb6607734fbf1c678a6072e8f8f6059fd2ed0bddb68c7f6fa6f1366ac110b353a22f92d91f91846167f99b7bf336baeda3970864bb9d198a7a7ac8b3d17bf7ccd2e84f6eef11cc8e47e9539a3d8b5946f590f7eed9b32e7ed3aaeef4d8e966adcd317abc596ce9e397cf0645334b6944e92f56b1f40bd5f1cb17cff3e8b6452f1b99216cb7cd0b0db365f498f76dc3ce6d56cb9dc558053f520febf8c50b8eafc99ccc393eb30e9ce186fbb475f11463a74f5fe84724b76f745b3df44844fb49ab6cfcd2fd8b5536d69b0cfc11511d3947a6a61cab6cec19f6e5cb970a66a3dcb67d760f3811c5b42c7ec1b2460830fb1bbe8298f2a4cbded9f0f504ca0ec209104b9436b7e39f6cda02a45dc2862f21b66c219e3c29b385a0b2eff6be546bb2fb0cfbe8e973ce392709544cec3a21a84f0cce25ae03c23aa0a8e94d3e1dcbde6f46f1cc1ff026a5c201e110fae071d905b9fa4b45fd7dea6fb531fd8da0dd7bef90843beed5feb8430f69622290fba85dfec342f88e10e8120ffd8de00504fdadb6c0d1dfb3c078300cd3181631ed2919ff5d6bd58e6767c11e773c6e3a9c367f8e0f99c78e61d9c5309c7bd951f1d87cccaebf1142d0df8a87fe0e825655a9b17b515ef5e168a2a93fba497dba16670e1b538755da46ec272fcbea13ad627b47865bc3300cd31f356a618f337e451b7bd4d4ccfebc13c921d461d858a6747bcf4ec34420f31925b33f2f7e72a33e3fbab43f2fbbf77bfdd1a5ed5dfb8e4ebe39c5dfc7693591dc570f614769c8835d638d354ad1b363fd76b07bf1d8519f39e64fd3976e2d63d877a0bc87e984a07eefe5f99f79934f4f659aebc8d95123ec2f4659ed2f46e97294093f9be2f97876c5ff46d81f0fb7d53c7642eca9be6684d5ea4f79fa4b613cf56777e576c6a8899e1eda3eba63d488648d56d96731cad4a9238cb05afda954947aed981112b6f7cf0809dfcece3ea2477dca108a105f3b21de87b0ee4ebb22dcf61ea3ccbaa3c5ae316a1815138178faeb34151347c04ec5d025b8356ddba8181eac949a7246fd1921c10809247c4648d0befbb83722896d9b46c5449cf8352ef401c2f503977d632724eb410267af1fb2740e308a6243d8e3db6eb767871d91edd97f24cc302c9bdf32067d5a20a25b3b1c72efe550d027ee785786cd7967add9b51d90ed88c84ab1990df98c8cd8f6f266595c31e0985dcc4ed811c12284f671ffc86df537826d37fd056dfb19c4c6f69178667f46516cab2114613e434111a4dde2475e582a654b2756b68cd4b5a78635b8901e814b2ed726a2d9d82725e236f6bab4690b474ed8128a204f846dd90206c6a60fe1c8093b42111c0085500d7570a30c40d0f3829dcc87303d94f1c18cbd4c7aa6ac7258b0a4872636ddf0d503191cdcf9103e1e3b0c072de6e0ac477cd34a040ba6919191110b17d50bb15a18a65b55cb2e745fb1d30be163df94b6773a2e6dba456d76a1eef4b57ebb10cdeb2f159bdf6b3a7a993a530875ec583bfe09eb1666752bd3add6764a7f2f949dbe7b727a774fc72dde69740b3bcd6d74a4b2535fa35b34bae5e956b7a5fb76ab57682509d9df67ba95ddfb49c7272d7a936e799b6eb5b6a05692903da75b57b77e34d62c7c4697a6e39225a525a91d2f54a20bdb2d597c52abb6d8ea94974ce32d872c4c5ba5aa5994c6284a2b83d1b6b849ca7a811f8ff4327566cc474988a61749646eba6fb20c27b06007745fe66609ccfc818414c2552a1d97e2d28d5eeae8ee4eb946db5d4b86c84a92a7b932a3a46e41e8c7b9d0007e7388731e03d06f511db71c833b7ab7b987f29ee3de7348d510efa87b47fd9356a04e1d76403abe9a7f310fd5e70fdfe424cae342ff4d520b42aadffc9a87a6bae846b76eaec2a9591e3a72c299333fd8aa2136df6ef3edfb4648dc717b8e6fcfa1bfd3f16df29158b3da3f5276993fbc6c59410b0b53670add1ce75269fec0d1adb8242e993af3d04bce3fb8f445174b00ffe2923de36f32ce6316ba398e6ee5df7c476e19b1765c76aa14707e731570f42d109fa8f4cf54e5a5a24865eacce32829452af937c7d9517f3074a06ea31dbb21d502996647366241fd79d7749cdd4f58bf30f7b7da437208b6bb0b20c72d53673e0039e66400e47c9d3f2c7f43514b4d34979a34692297787ce27ceef8bc44924be68f9bcf7b1742564bf524211c4e9d88935b384f12c2f377e4d68e2709e1bfc94837d7dfdc15e9664746bab92a23dde824aa8f9cb0e9af3f49f5d03d54daf3d662dce5217bf92bc3092c9cf338fc8e674838ba088443d6cd55dfb09b0d5f3db8f68d9cdc327f2409e1389fbf93838710f6a0652b4dd9f3128bccc27dfe2882383c0e79363cc4d178eacc7feacc0ff3d03b3f38ff76ca80f346074d9d1d2f8261c7af5d3b7ef39bf35065f8d9a500593bba21341f3961df879065388105c2a56e41eefa24207c87def4c73d0908bfd190d32f60fb6a2820e812dc5c5ab153d3df0bd8de52a1a89476ec824029019c3a124a0976bc46259ce3e8d4d471296d552e812a9760c76fec827668db4100b276683c759436ec80826e9e6f744abaf63419e590374274f8aedbb9e557b2cb8efbeef00977b6842f162cd9f2d583971db702b6a41b67cef06281ebd564cb183d43707935a1f26a72f46aa2f45a0199d70aa2bc56d06405482f159469f252c1929e215e2a0083553a3187a2f4f0862d9b9e526a002c66d005958207276ac073c3170f5578b8814919fa23c5809e1830ddf0c5648d1d0f9988d9b5d65a691a2f263c802f263cd8338e1c9e9e1ea44bda2c6253c21e248cb55082310d68c0262b47989b8204731bbe983c9101a3367c3151d233830f768f0d3b80c9d9c1cb9c29026cf8da81c9ae1bbe76c0617fa98984933ad2984e894b03a348af0de90b7c7558f160dbe0a026212971b2c2e322536348a909d214314082019627e060c29584431134280184452a82bb392031d9906cd0c2c053e615717877c3970e34b86430e0a70171783c4e660fb829f38a30f094c9e3845ae171b1298558363da574062466b0b229cd1bbe742862e7d8f0a5c3922d9a938c041e05f424c1da86af2558f4581560c3570e5f72f0627f2b1fe4c0450c396821c3b6e12b871b5058719e84f684bd4b789c3031b9c09381c789d403080209183134f9628b0a525862cc0f57c470041b41b0e8a16b531a94d01a789cd81ca2ec2df5882dd83e85930313ec0c0f150d26b4cb0ef3a701130b1a64ea5f32831a7e1a40975ca921954a91b063f586af252c58a2647f3b5b6c9425486658e2eac6cce02489141cd248820318243774d901d8f085830c70a8527b98c0c0ad33f284c13a48f1448a5d8f8a51723c50f0b713d441abe1d3cebc66f1ce4401099b470cfe764a38057a2002c9b52bbcc1aeb87e0bd875cbc636266365034208eb0f862f1c86d8f119a6b55be90382d9da3f23104cec1e9061bf9f5193fd6dda0804b3636b6b1aa6c9ad05c93418b63d1277a68bf4b8007fa63d29bd01968230292003692803030f47e4d0431563ba2803bb403a628d28beb8614c1032d82e0e2fa84a969494c1b5d65aeb0d4a52fe88c00d5aa40edc9fc52970d18841936001fbeaac662677cef9a855438c6c9448591cedf97ff5726655ddf1fb304d07d9545607d9b62ad341b6adc274906dcfbd7f663d32a324323535b251ead0d3464f1bb8470c9697b587663b0bd08a46711a610173c58b0d4f44381ebeae64b9b09e38b942ddd5a50f58a1bc9e707d5c98797c83808117a80da90daa2cc98ed1731ace0eb7a528d590b8e5e9c572dc477bde66886d23abec9aebf8bcb4b2b3d3289b3b86b4b9e3259aecec2a22fa33bd3e7b82ccaee25ae2061b7585c99cb9de8a3b22f319eadf69dbc75f8e3bc52e48087484606fef88d45cc74d5d8c02d37fddbbc8799d90ae8b5d903984eb88d8bcbb4962d9d0e9f3f1c342f06b0d50cddceee5a17ab851a710eca1fc9d2070277c384207624041c513d6ed86d89ddd6bea82cc9bf24fda26dfa86152db9ff6f9d90919c1906d047ba3c79dfeb45d4fe750fd08f6cd5fbda4dbbb5f9b9c3acd39dee5a11cdae958c6ba20f4f8b50b426fab8a88e93a6c478152a743d8874cef6ef3477f6f57243bbdfcea103d3c0da65529ad1a6273d36d6ee2bafc61e73c791dd986fee635637a887b7dcd4df5524e470a90b5c348869b2781f0005ffd7d64fdcc9b1c36b9260f71bf9a46e3ac22c2dd74ee26ad7111060718196daf43c1280a585bbb6843ed34c3b0b3d1deb67b228c3b11b68f64dbdec4156d7b2484ad1d5e18b6ddb408529b28b5b57aafc36285cb9edaea943cd9009f7bd2c09b0c3c25100ce14b092b5bd21d281165c3571256ec1f1bbe9260b2af097ef58281932d81f8913af23d66e00f5ab182f421f42d6f04fa4c2a66f607c7d8f1890be5ef0ffb65054caac796ef3183b3820567c39795275652e0c4999c0d5f4e74d163c397134890b0e1ab09389ae081091bbe9a30a2091c6acc88f1bc705ca8f0ac1051a30313496ed2d8f1526df8aa62e555e5c98e1e3238fea330a523870daa86e6c69b4e5cc8a136ddb083281f17ee6d21b765191c3f87a6965b69bf80753e6e2ced23d97e81a633ddd9bd806de8d342f4b25fe03ce8d34274ed2327ec78972bc62f6e5402c7dbd7125aea8f404a72b2afe434952d3fee3ad994257f40d6115e7c1645af0f4734a147d1ee882d3b27cee94dbd4375b851087cdab13189893433c7a056542bd894ec899db2442bb38d71392953b670b7072db1ab52a8782c7801931e3b0b60ba4d708d81ccc93af192d205d268483756c91104e77b98c0df8e14242398e4f45081bb0d5f529c4869b2bf1c586bd502633aa34b1886d51a065231b5d2a59a651c605a50060c982f5ebe8001732557370b35d4f0fbad95d4cbaa8bc4be836171d39c4d51d45622f2dc85c873da68dbdffbaafd66a38d11c9adb576c5ea95bbea61b167d9b45693b64a39b3634455e93910e7b01edda8c4916f79046fa971209028648c388272c6bea64af7b7b33f23246cfa09b32cdbb6d6ccce7b739838829c9a8a8923dc9a21092a5d922b496b95dc670487521eb7590c4906a074a9c78e006db7473470249e39fc8276fdcc99fe22a5ff901d3be66d1008860b469b522dbb5e4564d6287bfbb4c943db6d4e03c5caaeb71957736ab46fa0081c97bff9d122759e1a4d94d1b4d9afcdd9613724a397c676413852fe489c2c83ea49224beb84d8947dea36dfd0ed37c37d9f09e98ad0edf636fb7df633e18eabb48e3bc4ae12624f5379c8e67073b985d473dce4cf3b7d2a17a9b7fac3eff4b715c1a7bafbcf1cb2bde639b210b8b7abb24d1eea5ea33b9efc190d79b2d3926655bc9452ce5ecafdd2ffc86b2fcd2d5c8ebbb6877521daab711b1421bba68f120a981daf33b9fa995f2b08fcfe91b53b4282a31850e0da10ae20acf99ba7a7540fc14f7da48585764af74682f481998e3baf5e6b338ed3e4b79fb63c5fe79472a3fb9457f79a7286edae6e9c2314616eefae7540dd33dd9b26924462fddea637e5ee32952b538e562c67f22c0cf026334d769a6ca58ed6719799fcd6b8d38ec809797bec80b48d6eba51bab10c313486c0f055849657114a452051c4930b5cf8e39a20953e22bd7c5cade68fa7567bd61d2d1721bdf66a24d8447367df3290a66d9e9b4a13b8c343bb2da79f9259f03f408115b2dd3d4de4643d4ee00d506085254ccc96d4ce523859965dc15f2a85b342b6810f44207167678188045974255d1891307c11a1b4e16b082b3602367c0dd1c3ae96daaea70a8c7d6251733d587090f42144810824eefa1536f001e943be3e6219a2c9a69f7772a1bcf630876ec9adb05d0b23bc29c37de4cedb016d50aeea36bd9333cf394ddd3dd39198a5cb0a902b4aa35010bba7e6e9823ffaa57e44a4253d5a30fcc7f36fb54d9de99aa2c9f4abf564c15fd42e71b65267a6e4ead69b32f72d5722bae9f5916be39cb05ced9e18e07938b5d01298a749942d1f9b48a8a589953eece7d4b21f0634008520e9c303e3c90e68bb5aa54a01bfe62a9c4ef31a8de4741a6db4af67e2de19751a95ad699ae6e40ac2a93fabd42d7161ce15f2e5ec6b71a40fcfd6d7bcc709fcf5d8f5da7b481f34a7d3bc1ae1a957004d8622d46428754c5ee66cfeeee72da723dc35d35cf5a45403031a23d3e6128f02e23de4d99fd1923daf41c0b3bf06eca987b80f65b7f4e3f4c763f7e9f8ff50a7dc651eb9f20e734f167cbf7d47fae8608f0daf694f7f764f08a1fe38ce6af9eb11c4ea0c66da491a060f955bf440d1030511854a9462557a40fdcd8865945c7d3bd80b85152756d90207a70ab6d7926a1c25ac8aae782457979288ae7a7056833962c96799e460dd6030fdfcacc76615fcd9207272a44ee4d9f43d628c31c62a050e9aff9993677719255790cbb7b3b3b3b3b3336f2dc69afe5229ab75ddd914903a3576f4105efea0b6c70618e7cbe992eae19202836118771a77ad899ab24729e65029b992af5b46292f4d71ea60a9e974d33dede5f0e35338873ea947fd19c9c1357f39315bceda63961e3b14a1d66f4c6d8dcd0dd5df337dd601a52cd12402ed4d863b95e9d69173649b9cca359926c79bbc63a0ce74d34d9aa86eef98f71fa965fb9f698de44c3a33ddb4238263a507626c69948a558d6aea99a2878c153b73e68c965d4fb74e0601cfae7a886a2b5d20c8d943f4d8e1edb52db5fd47ae76d45c771fb3b8efb96ce56adbf4672407ee0c043990cb9c9501e6e46abaa0278a2cfbfc4f967fe839e8e3c28e212bec39753687e8650e92ab287d649ccc43f341a6fe61805cd51fb9aa158a5087401f0ffac45d663f433dfbd5b89c2e88d136511c303bfb7db9a0cbd64ebd7bab695e6d0764bd6ff17613614365eff6de965107f28a04c910599693ba08cd5e900c91b50dd14ebd6b47e59afc799fd103bc6bbf262eeb808ec43334fb7ccee937cefc759ff698ed8a64b7fa9b45b47745e8aff72be4a3dfce7545b2a36e334aab88dc7b50fb90ecdfd43471bee636595724fbc7fdd33e77848204e1013e7bec36379b5e933f1afd196da238e43d6e9c87ba6b8f365ede38ad71dc10487b9e72d0c7857871e700a3283a2173c36e08766dfb2ccb20f601268d8d367722792484edfde6a1ee70a78070e48afe87cde15119db7c31b3e186fb6ebaa37cec509a16df497c7b934d0774baebe83fd2c9136abd0f779f49bbf01b6da42694a76445aee86384cfa13e4ef0179d58d9f49ba67544628ce264d3c728914a9c12ab401fb829cc72a0d85cbb943972456b5724bed35f77d3bf4ebb60746317ff12c3c9fe12e58bd28edd6d277d74efba22f8567ff2f85c57c4f4abbf1c79930b465b62db154169dd909a1c95d0d3e4c8e407fc45177d0d67d95c1eb359a48f4e4bb4a2eb7e63361deb803a9389c644137197e9f66182e96d58f8a85c5122e943ea9cb7a04ff733757876f8088151a01f4f8ada15e88f2218725cc76d4e7568d75192b36c9ee3f4ab1c19ca126c88e43649bd92b23375fa3b3d6a2a7d9c7476ca61fc4efae84e8ff38bd3e54d0636fd6e1d1036619309c7cef4aeeb5272457f3b7fa020579c90f4f1753b64366514e48ade0707dc59b1298e198520d3efed804c8f510f759758aee817956c72496472b90ec8747d80c01fb4028520ae03925d9646e44dbfb0c3eff20b717797d98824327777533662baea48dcdda7c9f3b407a05c6171dfeb7540dce5ee95d2d4016d546af6715b3d542f619437754072cb49ee4063be625f2e82c15e9ab06997bda68fa02c413ec959dab3af56964c52111922927b62b5aa88442b89b816a33f61b31ef3bc8c712753907a6e484584dec33f5de621ac6b1ef20e379134416902cd3f2249bf797cec3419e92c1ad9328146b7a02ca1be457318a02ce174284bf05ea529cfd364c8bab003b2e7301ca43bd6dcabf79f69ca435eeeb28a083d777aee3f13ca95dc32cd6635abf5a3265977b81af6cce61a2569b7b9cdefe4501ba5e1136475993b96f4deae7b87fd4eac0886716b6b45b0b83b7cc21a56843a649d4e5966833d43e5c8ba9065938b50d771d809a1ae4317e568e1a21c6fd9bc26474ebab7c949daefbd272d6eed9e32779cb76730cbb20cc3eab525617676cc64b398b558d8dd5b45f6b75efdb900c396cfbad3d3696edbe974aa9a76a2dccddc49522835fa8b5b229d5563b46f77284da8d1ad24fc56d2e935efb05f4cf34b93e3c62649c3bd8bfbca6fdc6fde5e93214b9e48b6bed6feec89a4cc708316a3188cbb62698f5e622831d159f3472425848f463bca0eca15755d08a90415c1308fcdcf2e6563d8589876cdd7cb18843616fdccae8da58b603885b183227e70c41303d4ae79250abd972c5b9e040584d164cb1e32029f71f0d7833b0bdbc63408e06b1cb28160db33f26c193232391e30b258fa25c9b5a1865376388c9a4186c393bb658a3068e080d8a224611630b27859dad9862f2f66705fbcd8411730aa7c51e68b25139731fdb9a03f236be1b3ae48bca6bf4ddbffcc2cbb60f4000c8503d60ef7c76d99eda965b303c224d0503ddd34cb126b5fa83e37f8a4a076915bf0f8d490c3e353434e86e50b2edc95deefe37d0be9041677a95b4942f29e6e799742f152b73c8d75a7934416a7e5e335624597cab230756866fa0a5387feada4eedc3d6df2ae694fb782a60eed38dddad9e092b6a31db2b643566441d60bd827646d39cb5f1cf2c0fc9fd03576a60e2ec5d1e9a3c38ed538b93aa2892562d8d0e5072a8ec82d00030e36962811062b7b950103237696653e4a50ab1c14e652e73814cecb0b257bc786af2fa26cbbe1eb8b271cfd0f7f5937e6a22e059961744102909569cf640701c84232c309ac0cb2b42331ca461230627541479a6e250565d79ee9566465bfb10b3a8adf5441f12aa852481212c2744b86c8c27e35526461dfa8a45efdd4a1f45ac690851dc2ecf4b58b0064d95700b2e8ad0064d95f3a25964558a9f52a9258941d49a667388155841d09a6673881251f244fa5a42a7891a1e435060b7c7288e30636c2d0e06383cf0e5d80d1c5161e9c6d3270f6786b2db4b2a5ccdc2f0b3865b3ac1d835e76baf0c095e936bbc7a096ed311f991b04a96df5f7816d24b56536a74f0cf71e619c8b92824ec726134d515290e934ba65c4f2345481de5365d79eddfe9e72d144bdc8f45692d0e928ddaaf9e92d9ae3ebc8aa781d872c2faba256a560ba7715e675680d5951b67c565453a4e3de63a74a81e6a60bd1dca42327452d54f3a96d2ea2b9496bc7b204ca76ea6cdfe4932e966c49bba023ac8b92ec299b74cb8875bacd4127cf3379a6a0234fb792824c67c5a023fcd36f96599029d09b6e011d9fa679abb9d0b2e5b32e5c5c74d9b10352a5e07934f7302d64fad4ad9a9bb435229fa9826c0eb7508ea3ae23d3bc371af41348752f446fbad0bc771f17fe78b613762d3a1d759c8b741cff52fd6d3a7212779c8bf06db42c8bd9b3bc619ad0e9a82dfb96ed29a7a64ecdb491d426400ea4e4604b1abdd190b7d1454942f8285d847f0a3a4252a3559646ab322458abac56dd0b099db42ad3aaab53309d5e056fbe0b2a1baab46fb737e12d0b997eba965330fda45b49560b79c7bae5bd959d9541169689e8964ad748931807326099e223c36b8b2aaf2d8eaeddac766f013875b22d9571bfb700a7371d6424358d3229b934f14101521732db7689f2d2c20b8d8bd7acd8da6fb06831450756bc749085a339a73a69d8c31ed68478683841d05cf753f7014ea5c2b7008f83603a87750aaae35c4885a38578700741b3b04d1cd6346c24b5bbd48d8e7befbdf7de7b511a854271f73ccff33c1f14586bedabcb18af2e56acb5f1f6a585936d394ec5fd74958793b200ce41b080ea3c5499474a08e72008a9ce03e74220681c954ef17c20e575e1b227a5b1d639eb9c531e995bcecb493919f7e46c1c211ec639e5d428945055a9a4945209b1b8e53c47cfd139299c94424a2985b0d67c64ee2a2b9119446f79e9436aa9b53cd545e20522c618634764ce30d0460921841042082184328b0b254723a591c623ad39e5189b5e4620f08b27a2fbc3fec1cb38b5df89d529b726ede186bd5afdc98cd95b3db461adb3d29d1d5369f52a5bafc22a9db25eca4d215ca8c8674db260b2b70dc3b20ccbb22cc33221f5f4928b5896654374845ec811a6a55cddb0eedb3e4435cd0dac5d7ee8da2a3f14a5946e944df6791d594e79d764da448cb0e070667f46585caeedc2a4e2da5b0b5a5c9b9e68dbe2c2a83dcbb83ea8e4da54e788234c541c611eb2b1331b28c23cec503d3f9828e8b37d1ed3a068074464eb89037fa88d4128024ab341e5d8b2dc281becdadb5114cbaab861d9a75d7575904da56d57d50dc322c568b63d7bc6e5c98bcc8e5d11232c4b5cdb05a328a8b8824c2bae4da9b836d5476619d7deb4174798efb838c23483e310341ccd704f94064f9c56657318cbb0c5186b78c3185fdc610e7b42de9781fd597963a2946e1ba5df367ab7ac03425dc1dba15ccd0dd3300cc3aad60161da6691258551bae5b8eb770f6fb665f0f67b39746976b8b367c7bedde62faab0f833b37c6462fb46d55569aa6aaf8a966e475a9b7247e6a69cded78b7dbf9de66c3b7c654126f1b0039a5613001f2549084f25c698d2c128dc150481524a29a594529ce7ac7a481f33c6380029c30d12373c96058061cfb24502cf7f340537005848dc39186637cb6af780f9ec586623942bb85029db908bebc98e1bc813985e315c5c4da20bf131ce09af09088e875c5c4d763492315e0180cec74749548780546badb5d65a719eb3ea51b12979e01da40b02a5f413d64b2d177200fb3cfd2811ecf3b51b426b0794d10c03a1018e73e34f6ac6851cc83850e53122f304bb3ecb5f1daa9f32d64f2146588c5c9ce3383ca58f29511cae34e2a8727618e0c65a9812b252eb30c0cd8121bd36f82849cea72e9426c847a95138bee7d660482f86514eaeb063b9c24e33102af8e3388c6194520d25fb8f9c74669a06d73f86fb7118e772052409fce1ec89f39c558fd38ffcc1055203c63848dc267c94043d0c695639144e65205850291c2c43a51990a04c4cea0e1f25d96fd816e883dee2a95513ab675c0e7bf82829ca12e48f92a08fadd65aeb85e4aa1e881218852021e96352995b8891463ab5e845a20048126cf1c77103c8acc5dff7b7edb9419d1e5b0fb99adf01b203fe7a6c7bbec70e093c0ac09494f0b2e777e785224c14e46a1e880ef8bb7b1e85a05a730b71cbcb98a1d20c4c62ee28a6ab97346e90b8299e36a989e5d05a28f15112274b989f874090c0f29f154432a0f30d739e527aa3cdd373009dd386317eeea82b912046320895228b932d3f891845b1e96796dd10a8542fb114371ee69c1962c3d70f86d89f69c393b0e12b8e34368c3a62f87321a63f4a8af233e2e2d201e620b0dc908b6b0917976bc3d799a5fd9d7046695f138e5c499ae186af3355b69d3f762435037fa91e15ab874a33206da834031355d52dc43dafa1c4cb571c60e48539df8f92e6939c95140f29c4590887cd03a9418c7174d9f065c68bfd85b0e50db0e1cbcc946dad0c78fe43e517a89c2f425d2ed4bc9cd8d42d4428c227531388ce8a923ea0c4cb0805e9acf81f3969007254eb67e58020c1d93f6af14c7d574ab10c8bb2ea17eaa67ace2980a0604bc46695b6be0738903061e58921b810038c124a948c81a30833708c81c17186d4628b4bb3c8018d4958334db20a47131f0ca1848b1254d040c5982d57e4a8e249920d57c278c30c8bc211655338a604a006282e732931e3f2c2ae7a6014ce8f7aaa13c33ce1620b24315ea0b8010b882a4e4891411667b8ae964d290512c30e1c3f98ff7cc1d18685a38d1d93a001151328a3c9105f24f172030b48460c6ab0821a7041030d64c08a4fb1608c9923c26461a64c18770659b4a08d2d4be4f00414707ca00d9e367cdaf0010f6f94f101928d09816d6fe890450bf3c442e1460e0a73a98fe1301b1beb224436260419bc1126880c2461c10c6810d3a40a1a5078a2054080a1a10c1348bc3e45971dbcd185a34193e695d4996fe0607f3956dee82193c28d3259dea8e20d28a814cf4a724a323b5f1655f46006597050061915307385103850e2e5022ea85426ec557283936f08f106142d0aabb921c50d2038322de8a10b1a50b992258d1ffc7003209cd0011957980c6145160337786801b6ed7063ca5471230a3265b25828dc1843c68d246dc0c129e130c6c61a775c2e974b89151fb7cc1ff4098ffe56557fc734d526ed69b8eab07f9f5ee60fc9c3b3b3239750993a4f616cc492d30c3c0463973d5b49516f8993bab674c55674b12651b631ea22ca36a68780a43e02cf6cf917b22df56734c36600862cc8aaf34774b1e61b3b7ed996f08d06481f1168830c5c411f1168c30db882fb8355ecf9452ddb0c0cdfd8707f108b3dcf387ac38a333cb8a18316d6bcb2041a5bba1421e58a2bace90416314890792dbd806544049438a01051864c1a3d60653103132c7ca0460f48b2604d1f62e0228729b678a34b10acf99df9230429da60220a2e92f8000dd6ec62690917e4e0439929de60cdffd42736894f1e529f31e69c5e5caa34ab6c430b1b5c5e6d88a95c1738eab4e1451b5df64777940b73a0e422c6281fe39ca20b1b646e906121a382ade30c0ea017587040230757e07086873194e8c10f505c3a10c28c2b3457c6b4c18613325274d428238e1d70c1e40a33a00d96680192325c3c89620936a6989ed802b5ca19a365cc1763b458838dcc099a0d5f63981803c4162936036616b7601c4a35ea0352a4f172b951a586309edca0032d908eca04c1450fccf0b830a822d56335468635cc44913c3c5658cb634319505001041a4dc29061e50a296090d4304612343ee0c4fe56fbf136b113a0032741f8b0461664d260ed60f224cb146492084385159fa2c2063c3620c0185f584982092faec0810c7c9930411623a494e104184d8658c30b1bbc7a10e6b5e4030d4a113bb821840f3f2cf15a038bee5631aad2450639240d71c6120f3e55cc1e98590389358cf08162a90cb5a8319492f0c1b2d4657fd827064b36d83e51e06cc3d7920ca85d7ae20c9f2b5f9688c0c6d82cdd80598b8dc0648c154489620824de48415218417481e2d2e14b1a56ba3466c0dd25d2a042d35062f2c166c7862f35c8a831461bf2908c684403174cccc0060c76d8c207020053c5152b5a4011c6952cb012320440c88084cb11165850813531e60a34c4785283307424116badb5d65a6badf5938bf9f3822c6304f102e3c889510e5364d1024b1431989941199b4bc5470a16ebc46a8cb1d2491f6bac31d6a901d8a3ea13608ffa08b5d02106941ebe78598249131da6208388306630230a247648a20615981a56e4c06a30458d18d8334a90c4c00a2ea650c1c115451736f84026ca15336a54d9d2a842e70d6a1871eb86af303198d1820c09bba18106c6387534ac2481860d3ebce00c313fbce075c617253e2fe0367c9da183aac9299588c2891a6c4083154060d1c403dab8020e2096b828c344938e08ee884c8d1b36359a2479717930803be50c0e7c6882c9173d80e1a58b1a40e921075b6c60034b0ae20ddb7dc1164e8a5277839d65383338a30a06f30a63e61566061f255e6292c8321f29a91eab140ae3e42c8171ea3c307306106734c178fa2cf1adb68c58b64c03892352c0440e515ce08505c5f070851466dcd0e20665244d39e24ac1801426b0a821494c191e8c30c2881c23a0f0d08c075e9461c68b076136a53e51541bbecab862631bbecad092d1df38ef968b725192d076ef4532b79284ee762abf5dd342dda76e45a9319e3ad93d99e33a8edf4a12b2798c512895d241ba952494e351b776a64ef61cd7a173e8d68fce8e752bcb6ea3e1d4d93e73772e5ffaed42f49b6e25696fddd36b5a68becba2bebac5c2d4c936a95bd1b52446dd5a61ea64bfe7244d763a64c58c46663559cd212b6614b24c99de6b2505d5e85664e5a2fbeda75c942474757cf2a25d06a64265ea64d9371a182b6a254100b2eab143d6a68bea21eb8b5476f62d435697218bcb9075cb5052c6937da184d283a20ac2ae4264d5ff4896d294d912d71ede28752b2948b7228b625a85ae1e29b26afda68f929cc0c28e1459f5da678eac2b23ac927a2c9a83f20af631dbfff810b14dfa79facd7eead8473d75ab65ff4125294fed216bb2b057ab87e0529413cc9d87396130c6ddef9178e75e3b550addef853aad853cad27c60161b9923c7225646a68b40f4feb10f66fc810bd861deb86c463df7eb71ccfececdbd4b402648c18165175b5ba5d770f4f632a7a1c373d4efa0081bfd56ac5791e778853f3070952479efaf8803f12a036c16482c904a4b37036e533fb5ce769249cc61a4f1d78a9492d8b3b7f6c77cbb07504e366b6560c33bbc2b02709702b49fba63f75e45bdb5bf8aed8b7185cf5113af3914dfbb0dbc3eec8a49bfd892a2592f617b3af74283bddb31b929d66b5d3e80eb0ec6ac5865eec0b4b80e14b8c29fb9a606bfbdcb0ae08942bac65c4da1ea56e418523284b983a73bb0aa64d5b093f2857a88a2b942ba9a518a6cf68c7f86bba9ac6d5173ccbe9a1225da7b1cc2d10d9bf80dda4ed6d574416311d07a2fba864edb495450d32c666240000006314002028140a888442a150309c6a9b281f14000b9ab24464481408a32086511064103104194308010000630c88d0d4900375cca1d5a4d723ce37062ed9346c430fdaa20302e9c162470da16d8d4df4049342b18300ee09a0694e91046d6c61790453b8e0d7c5ecf0aaf9a74af07cc8a3684dc86052d0dca3d2572421fa9b826feefbf1a6c5cec608898130d811f8e7d9452d7351c83fceda5653ddbfc07476a6bf62e95fa82bf19321990a78af02f7e7cb5a320cfd459d22ebac62ab5ce8768f79c71864a10548404b77bb6f0527e5573d0dd40f2335db85011e851eab4f493631016d5d41149817d69dc1b6069b57f359a719608154b751badd8d2d86d0191324e2a053ade5b3b7eb85a05eefcc5aee10c6b0c2e51a2d5f0fc3317756e0858d8cb28e72c2321cab02636cdcecb946e4d5e046d74053ef53efbb979e20cbfda4d23b2ab782f7112a4e285097ddd8c0d7dcf28cd9f916c7c2e2c5568329ed7a1209eda95d1f6f4180ccf5fc270fadbaa11ad4600e7be6f3973f1991c41a18ffc525a91f86f9c508ed7d007a425db014063ccaad2699455cfe3d698dbb4976f82ad92fbac844a027f44092903d6ad03f4ee382a79071730171d6940392aa4d684dbb999247f10f7bc874d64b883b37ec6a9c06abc364b4c2370a6e9a49353fc50371954adba91b1aef2031e14b77d1053e9d2003750f9a4c697af3d830e342d74a1c784d7359481e3788445dc5ac73e5a15cce14ac5b49f44de4838159fd87fcb5254f0720a26dce18351cc9739fea569490f379e2cf108493a87c34911d36ec1cc0026c1fed950ed963870ad3c241632d36f2f400ea34dc0389fb6f425a45bcdfe9d350aaba40d7f5ad6b6ec1ff52a9a73f9feae33017da9d03898a0d90dbdd038501f4c072cfe9985ff38e4822f696e85c24a69d986956da0d78c59eb83693148f571581f8763498503b0325027439d1254b96c6a099cb200c6b927d80359cb036ee47692d2a18b51e22478dd3b30c825b7e5e97e01c3b34458ae1e4828807af34d7e46c32fa8eefaf74082910845d557fff5c0a5f2b8328ff031326f1c1019f65acce05428e67ddaf484bf853e32fc61e509c74329b20cd65ded6ca0dd02ff154e8566411e6d2affe00216bede025ff8379190e0c75616725e6ea48a0b28157ea97716fa471425de1ac1694c106bc0798073078ce01c607fabfc7438e94735cc07610e831d15b6d902520b6180a982b7187ead6351210a7b84cf9be18dea677020cb0df4338d642d7a3178c347a67ef83f69d2a4ccc89b1db9712a3f25d004def3ab4b73a38ded25008e5870613a223204e2a8fa9a7a968ba81f1af8420de6920b8152d9402ae1a325ddea1e987b841e8fa0a2d3e742d5040601c18eeb9c954aab181d56a1420a134593b6f0f0f3700a970a7c0900b97486fd012c7f312ec4ee589a2f104c84f3fcb3f1ad5c55441cae65d24460a102afe71a102e783b74080c20c1a649cb04e2814cf463357328b07b97b7278d556e240620f7e8f5f14199124485192cdc693fd839e9982e7cec52fb7a96f564b05cc858c4cb4f3d8c6451c016c829b5d508f22f57c6b27020e17b43cb2a39d9f59b7b44aac601ff970c9735a541da42fa5f492282a197fc19b677b990186e7d99d0075724f3d177ef2fc5886a0c0292b6ba29a55b62b63efba2f8b37964e8d059db649a4a017bd995a1b7ff8e8b2cd9948bca23a1ac53d449f350533cf4528b1c22f44e78488f4ad77f9e41c82cee21234ef3913c1962a0c5184f1b918b18295f5703890774ccfae07603a5cdd647538810be93f6a938c6b7307c54a138be373d86c6c87257e82862132f7d33ad69bb600738aaf74954a384432239b0c1bcceb68d3307915cdce8b7bd2330ed59b49312baaf85897d20022284157cfd16605d03572d2f085711248464a965f01038a5148769b22f1434c18a9daa6f21d646b35d833804547acaac9ae0b3c8d9d782cc092e5af51f836aaab8686de4e793b671bfecd3447a20730f3da0ecadd60729ebd0f90cddb5c683bc877afe8875b4bf9c1cd2026f2292639fce437264842489c388db189f7149bf2f3430409fea07e4fd6d2e750aeebf5c0dc9d25e0c0ae70fcd52a070ad3509b0e9a320253f0a82ea2f40454e52ec05e85e8b5c4b05031dd6576b24b290fbfe1ba443b3af4c469eae307b13730366359226454465f4fc30e6c95da383b4d2c6858d36f9a1ded6b28f3257be1ed3d297d6f50e3c4d4b1ba8981a05778552261b547628bd7c05191a7d6a1277a00288faf7ceb106a97fe6ebaf2c24aaeb6549dd823b8379bf2110da970f4ecbb72463106e358617f2c892064d711efd333871081e7828a6299bedb4c72c4b2377365af4fc971b9f25aa5874ea4a6934ad1f76d6eca5f3d586d23f9ea506a04ddc13fb216479f5d30737227132693896daea0a42d00aab4932f06610af722180686492481047001f1b5b91481e84a02c19fdcb853fc4bfff0d60c2747686797340fb31157d2e3d757c0f3295ecc416ae54a8d9c11188f366f0d3cea941a66e87b868448ed6f382f444a0bda0da5647129e3f7bb938c31cbcb832c31eb28e958f4afc95e8957cd04dd24669151e25749840882ec18d220b75285132e6ee8804cea6203fb7b322aa122ece02a278ff7022f09e7d6acbead7ce1e2b8262e08a7a4638c361951b370e10a3feba1c9ffa3ff4fcd744e3e74631b8cfffa33701f635be1317469395c265013ac76219fc797b7d626d395a0ec2484444d78eaaaf92bc82c29478ea4e9f13316aebb3a4bed6a5a542b3841862e09abdc4b71d82b632e9a9f171945ccd2b27850ea290ee4778c2e5bd4d2738e8f6893c92e2b255a6f328baa8ed9b639d09d953613cd078744f755081f568eccc26e795fec6f2f18a87405312b6cd64a866dccc0959c2d4e7d0ab13668706df6b095971ca0fc83729b863d701c5c3ef575c2482f4f0debee90b3cfa62e309724d13fcebab7d9aff519d9eec474c156058a23d675de58c1d5b0b72b238e5c464c08a229c85626d09fcb4ec466db18310536be245a13be0c7c5728158e7bb57c0d511132f879187f56fc3208165193f2c85ed5abc06f105ec8e780f4110a6d997c0d3e15079fd4a3c6988718002cf3d8a3268dd61333c9cb33b898a693e22f90341682f127820b0891d9c9b8e60c154f43035d502f29eba1dda473c64d2165571774f3c681d24a9faa498df999af5ca31d806678b0a4f8d63898530e54bb8feebbb8a3a9b6c4649a71bce208cef368798f3d9e29a9696401a5e5ed8e322af089a5e115087a58eee94a06bbe10e45bff371f7f66391a3069ef4cfbf6a627596621691c97230e2426ccc3ebf393391295046668a343ae56abd17507643d11b10fbb35e3b0c040d9bc5f6a5d53e0c93a3c642914677c3f4229a3037880724a07408eaa1c8cfa4642828ac084268063d48cea700955be4bcb5a15426307a1e4898c92206ff9462f2b6aca1f61cfd67951575dd088bbcfac1b4e0fb37a75092d821339b5f0ca3dfddd06649d9f8f5dbc283227699cd253a65510c84046b5c71e8824d2448eaea6642ada3846ac6374449e57de714675ba50277fe50928725fd963739a2e63585682742ac2366cc4fa0ec6cc1ada4c9f9fccd6bd828276284b74ad4fc54f7fb8ba4ca6033028493062da2ff825b35925c67cb75f3718dff2265c43a7b2b45fd1633135ce61dc994c58d7a6595c930d542d3bb1ff73d62f70fb88e43d9a2e47d888daa3b5fa6c3f6a9f1ad31b13f40c67bf52e5807b2652758896a69619b5759de0c099da76909c5e9bc0319cb6fc0465402d44054c3f3032d8bf517cec179a1bdb4627e621804b6a444d7d050cbf9e8ccda074dec88fd87c57f38256340c00b1a5f1e01c19ab76d3109e53af0cf272f766235c37e01bdf3487f53316f01a9736c6b9e4a5e5991f4e22bee6d6b24e72184911d4e9c7955e9ee17c0cde353ef226e6cb589570efb3e01c40db2ca5998c1e247d5e2b8c0a90ef80274ccc0f28c129bc2f7f54ae0d37a02966c3a84fe56358c4a232c981639961bd92400a9a8cd6c879c2531c4476dcd7ae8cfa67a1cd5d5e640248715f894e4059113831d21026058d92027f452863997071c54d99df8d26a0d260c0db532d87281858d2fc317245528085604b246a885f64e466ca69fcce9bb158446b954e681c493b4b2b8873539894eb70786487f375dd5f5dd96fdf9e443b8796a92a28a819caba11f6b75eb6b862b7fcfe3c1e6c473a488eb5fd3c9d5a6d6e0034744608866d8add11d0675c665b6ac20ea55fb661816c006500c48bff14b6bd4e050b0015b1724616030d4624599ae6326e6fd90022021e166d4a84e685194c9d52c4137485c47eb5724bc7072b1e90efaae45d05866caea9537fccde205eaa69f3e88e1900ad3430e156ff8ff30e717222a56baddeceb188135790734b99a181cf2d369e2617098797d424e7522dd0cb96ef3d6278f9f189182f8294b3158cf587f032d5122779a0904ce024e5a1f8a6d6e06e7162dcab3c5f55f01028f0a7e13812857dc87141b2ead0a66ea829f9839248640a273daf00dcf807e711682d90981a8511df71502906c33fbe04f2498735b6cad9b04bac891f3b1324f5e9bcd0731fc78481bbe86839bdf7c5c5dd7ce2b5301101aea334105c4180648b74b454571daed62a93c2f0caeec1531012d9fdc2e20c94540d654f278fb59c1b8bd125a815cec5e5ae85f2e27b9f7f2df15727b37ee4d381ac3ea886b60496300b65c30bc5c5dd0851342d43a85b50802ffbeea7f74f04bfa6f66becb9099769d15f35de04563ef841efc4a2a2815d246dffd377a5c9e1a5466d5c271567eebd2f305a2b5322e78acffae9410411988bdb8a4b040779005be80fc4d9d785853c4a6fd71536d3849936caf90f7ee90494029be1a4c258eddcd2ba8bdab5d46f0be9273055c1325bd47c446c0054a1873798cece702767d625b81afb48cfbd07f40e07cc96796e46b956a2850bdfb8a0f58f0dc82afb1db289a8d094d88e0f79eb899ee8a440703b04c449e604294e5c6688f1ea5384a604aadbd9f6406f537307eb4fb82f48530fac954ee217939bd675b749e6a1d1aaa16d2a1e3170a7400d141473d2de62496941f0777bea68f3b1b1ffb293d224a26f6b924a856677713046299ecaa2f41e93a5002214bc8dcec7175176b6cb6362850e403766101f0dd15bdf573d688610c162e60aa0e79438ea58616524cc5aaa4d060ab21f40b4d2a0837183e4a212bab5fb3120d37d58bdcfc113ff0d3c24d98eecaafa8a7bfbc8b821dc33f79ca6cf063e6e1ae8d27833e0c6d5184ceae55e6938d9a7ddbf7d81abbece6f1b62a5045b44e2d73f4add0ee7de36bfb9405f669c4c1bc1b2c6a0cb83f9f85079484033a5d61934a5cd8aaca2571a386a2270683cf5240682a4df7a7b172691e1c3a00dc0426cfb5399921e2f5ee9c0814ed6bdfb58f6100c9ddecce8770f0bb2eeec4801f66e179d11960e884b981b1854e8c05ebf5eb85930cac10b50222c2e1d4c783aad6ac904948f832ff3ff13dd26ddfb73601643319c995630c05effa8b561d847259099c88a7000bb9c8c8fd18a8a306f49062b93cc5454037e013d6a5b82b2ffa12325b0287d33f6614def781d56887007a04f4e4f7a09bb9c831cbc1558a0ca56487f12669871da0bac13be4fa25e25024a7adba77be103900e5e6161c0acdb62171b20a695967e2d0d0e3d2cab135e83481bb3e1741773dad9345e7e5643468ad3bc6e222ac7ad32f4027f58e462d308fd283c71db63a54c84adcf87471020ff5a1000bcd17c6db68256ab24f2a2b6e81a6e50bc9b2e051b76df4b38a4b2c65661b9f83b0adb1e1a2c85b2a50b1af683bd57f36146eb0b22d49db107a40219950ac06d935143e7b1baf31a229cc7cff218557e6e18d73005e0d1100c7f323a5d78939bc35dc5680b4cf41a596e7c04705e10b03c1613a31ee107371a81661af5e454118726259e928475b357b3dc9b8fdff6e3255f44109496d940942869069766a1de5d21bb89f96b529702f473ae830ec74fd01fa52d2bd7cfa5411957f199e7f2ca702efa6ca53ae25bb661cbdfb8d92715869378b2fc6a5bdf1085a701720b2a52055da3c509a3105bbfcd165aa44a638b6cc51ff0525cb1cc13a7a13d3649e677a126cb41055c6442b1cec2100db75706ffb75228f67f8d5fa09841f79cd5ecea8229d0cf8787ae6898f2ce08662c35cfb732950f6b2c0d95c9459122dd6e8d6d9d5a3da598ec47e3fbab14b8f988a3a55eae6e09fd6d87764e1861e31f166ac09ea49387ade84f0c33008371ed7ad3c6191a30269c66c751d99e2920374d50c5b5ee44cec0a9d9169f90433136859e69e09e18d524a5f4ce6d58025b8154398948c1be4783db935f2ac5a248ce29f7bdc425102f4308f19746e550e910cd8cba6aa11785f93e8fda6cba32268dcc6e68aff85850481c6bf11d2afc0d8b812b2c1380c42a8b01a03ce8806b66491eb159a64a2b01d5712c909a6230743e00f86bb380dc7b332b06bf410b434aa111096f749c6195659ad1c1e738d5fac658dd05094af228deaefa87f03e623e3e88d593cf9cfa8c04383d472a61797c4718a524662c4934daf37072663a1c17fa040353830fdacb1dae4974326e20893cf6468dc1fc0d977daab0c318917b221d35d0c01bbd4228483efc804c82c020c2ae54aab227e6d882a91b420783ccb1954a0dce21f2b00e947414ac2eba79fda86aa939e58780c8253cd8168760b224663c07a90dcd67177223ee8dd6f30609115e8f164591b314178729747e56340328b50a00bc900980c7bba9439811889a86a953e48473c9c47e3bebe9e9372f251d9bbbaecc8d80c0169f8c392e6e91b429186b2d6eae30b9985abc97e093d4b11f322f548927de6a32f1c03ad1dfe273c4e56042cc96c395041110046bacc813f195ad87a9cd90ae11206f56271c93c97fafbd001642d1b18f516ccb916b2ab601f46f12702dcac13d817efd79f3cebf5d2c7ce4a67bf7966ed6c3b7c4bb6d5795254099a230c926bde7803c506837526822debfb541422025d5c1c02a5810a3bd23495752897383b92c02e24549d7b2210b0c0ef5fb4f4b7009afaaa82def092675fc0f6332eb41e77f410a4e5be715b3d823b8f55b29aefbedef43152bbb185b024886029f9861b900b9b9083cdd364241c4c1151c2a99205182b095518356fbc64f4bef8872ebb9e7d0c00ce476cb3570f0ea0342035dada8af2024aa96564260b7762f293d28b16e186750b009d3a4cb1004ad45b8d9fe697a05ae8a24452a1260c2024ad23d31d48af58bb65f172861604e5bd24232cf74cf3a8ec4e17b6c0f59f791ddd62120d94a70dccc6314138a4fede758b1a622c785ffe61f1995fc0ff348b6f4cc2011f29980d6dfd6c9511b84c960271d80a754993e9b0739535300e0f3d995cf1aed410105a294d48dd54db35fbc6c87768134f5890addd6f7abd01dbc476432bd023d7220614f277fd0654335d9fcc376240d690bd18a754c20e598fe99a41b5fa628aab025edc7095e3ae41c622abbd09e61f56e04f6f441e62af6b81f6aff82c4756b4587191006665a3d83c103a67e8a2c28d253ed59602770a6908fe96d0d9ab50e43bb0ac32039de831515f097f5e4b9a717bb40ccde2dfe702abe46f97ba333c1887436157ddbd3120377599afd200b749d60d82028b38fb121fa53c2cd122412a5075fe3503817e2969f2576721c8932970b41b6762d000f647d142a9fb508d2ceb213cc98634b5e987d114b2cbc306f153c735f35f121e64f3d9a513576651971329ad5a8107ca5daed6f5cef0777c656ce5e76efcaf03d3eb8a07da2079ea798678fa19c284c2df23c759dbd3416e336bdafe4d31c22010ca41345705fc5120b5d5783e543577cf91eeea98b6fdca501c3b3811f582974db336558fc0c8d0b30d83cabf22bf45f55daddfebfbc421bb429f8986db1ecbc20130ba27e801d1f1003f8d2324ffac2d70ce19952b4a22bc9c5b23cdb0231b959ade517f8e16598a63416b620857bc9fd1190116c529c0e16bf63fb0890c562995e4753652ebd926073df1003d39081562aa19e41a7e02f1a4e78fb0747c8eac26a64288ff32e03a8aa5338b02cfa945a6b3bff7cd886323daeb47616542ef850729548d8270fa1b25740405b0d46c2003f87c04990f66c382c4ff6dfe77d07116aa9332720e3456270acc3450c29e1f4b2f80ff9b70552919321d45e890fa1d06a2d5ce105d85095c7e67c789a1e3047d10a6ddcd33c716c078414fc444b1e8891434f5f9096d0ce7c6efc63e1bbfcc2ae1c4750ad5ac217c3904af7c55d8747de1d70be7aa1e7291311feef4db04fdce0d40c43d7a1cd7927e11ba6bae1ed8f1b86726d06c84cc4ae77ece3325898a78780020c8806bbd8ec690d0ff4db84497a24416a8b619d62e53c7d26d6d31b4e0a485159869126bde4adefd4bf836ff553e411bac580fa246e342531fee8b57260c6cfb50d2b0049cf0227b25fcae7915df6d9626c86c370a533e5335f08ca17eae088e5a3c1c9512f72772329a8897e1815b303581e3a5d6bc10d3e34a240c606f5a20533e47d9853db244a0c641d810b9d346db675537c7f5b4c812d8f890fc0ed15431d80bf23909f29025578c0c97790c849a668a2a2c81192cfabcb7259514175d3b43f2fcaa04fa1c3d00ba57543823604d4b22a68116aca40fd7f2f59ed2772e224a311061810150aa7af9b89e50fe3d49d40cfa87577c09712c670cfee67bf0c5508f4b589b90c7087c1cf8cbab99259f5d5ebe4a25fe2e0431c4b6b99cca78f1a432061f5e10ce8a58094fc442af5e91b69a473ed69b9c6ffacb092e8cbae453ebc53630d40d57509b5171af2e593e367da0f2f02c521fcfcdc0d088511a9ccca8ff46fc3e7f6e2ea4ce021e63133d2ec45f1b9d708d198f65b2d24c5646c649ee5a2e2719dd029a8848c003f7d6e11bc6720946a29f584fdf75fe62d60eae5942d4008e0fb8523a8131b14ec9955e7485ad9df0b354db3c1749980f3974c4784a0eeba8eb9701717f89c872a645c4b1f6e2a8c752617aa4d220306174724bb3786de6872b75d782d5dac18bddc184adef52e4d3937102fc55d970821e6744f7f1eb6e4a0953b07f7102f8fb9ad35c9259b5efc7fcfb9235870c6a631479278aaa788cc75198e14fb02caae2c3029cc451bd15bd6d9794d8d79e323be17b0da039dae9d00ce462e0b71653254b006a2a416e95cebdfa45958cf3f36ba300b8235de6dcdebb768223ad39f7921420c87cdaa99779e10d25fbe58f99f018a7385ed724f889fdc852acf6c3fd2b5339a094965adbc4bb619cb5684a2b81728f41452e8e85a9a32c533f0cd1094a791af6e91c584b71beeed55099066592cbade189b72ba7fb68e662d7d9b8da7a927b1c2bbd3d8090f6d7de7308eeefdb92f649bc4cf6ea96b839b30506cb7105183b2c3a0f5183a71efe89772b58a6f37280853fc9d5a847ef73ad6c04f79ac46eefa4e1ded511da3c68e84b13954d8b2061ed1d70b156de7bd6b5616de911e689cc276cb05cc34887daad156b2bbfbd6bd689325180fddb99cab17e29589da0fdf3ba587d5cf4ed201f8711107e39accc80db9e8d0c0c2d1cfdd362070182c778316229f0b8b39092ec4ebc2c64afa58bab0d367e364f9966adc4fd9a0b7b00d0d9bde643b717213c8b055f4690ad7c2bed69377052bd223c157042604ac51a1e4c70ba4ff2d33aec089d498f112b4b41ba04511628016ff342b7ea4d8b9bd0243e546f026740d8f895cf39f618ee5e6c7007c15dc4db073f7b56f79bd31b39deea881f5264a36458266dc201c1c56bccf5b72d26d769d75dc62131e68e6cc9d6196e9f2e314e32cc22aeab1fcb62ded1e819dd1e9cbc5ec1376137484b83a552f01faec0e07d7b84cd79a0c7d27dfd6560b3126bc626266d3dde686510410696875a193e7f5833f8fe6774f604edaef39ce9c8e2fee29dc868828ae6f344d45c805b7bc3017cb11d9a3a7398b97ffe94ea1eed630502bc24470fdd02186e847dcfb946d0969484c1cf77213cdaaa8ecfdfa98b1195f5ee0870b3e164991436c1c4f05f89f6584721107863791bd6b918c799f3ad8225413db5b909798cc56d21b9285a66693318ed3861071ee7710403ec0ff7efee9a144049ba7d14a230c14102fa1de8d56240a08bd549b301f532ec24d48fcc10807635106a17e53a918011eb695bd848fda0ff490c804da7db58fff19e51e39dfdf47214625d64fea1c973ddb3007d5c33fd01664d9bc9b04123e826671a2a7e25e83e28055b61ad73794a85de04356e60cfe307e14086755bc84ce6ac73384ef0d9bf7d1ecaa66deadd57d36c10d1f74538d1af710c084557948c187ee883f6329c99b40292b866bf79155f598064f89e61d2ccac48ac613827c97e128ce18766e7114a6570e7d3db8799227549ba5b34078d74efe910cd394924f27755d2713a07b1c19349e336151291372df3f6fe56a1d57218102f822e9cd7a3387d82c8188bb931774636da73bdfd4a8582e5143a65c1e9f7becc5ef619dcd5556b4995a09000b8844c1e221c3d8f64c2f6ae34c20733c975fb00db5cd3d3563ccbcf191da754621d100c7a915b7d3b1e078ef2d7cecc07bb536b28c9c865eea8a552b96217caf5a92e7288acb180451e67ef1059465ef67b7622dd2ebd3daeb89d4062399b02183b8e2116d005b6e866563e1b1de58139381cac69fa574b3ca12839f1eb46891110c649323592e231f155d900548b658ee51a9af10b6f2d7860108740823eba52783c551b6e06947df791261d372e799ce4eebcac667f38572e61a42070d5421645202ffb9348d59fe2999333b3fac6e8f2700502581fd31f46477e45840dba4bb46e2395f95b7790ba8e69831cd9756d15b4e36cefa507f08464cb0ccf5bbfc9ac7aabf7e284610b164bb8d5b92b11d9743908825c53b3fe178002f0d68079c4f88f4c35cbbb90db1e26a7fabcb458952b8b4f9523d16d00fc37796472dba68fa007fb9918c67f8a98ba815f86daada277eff315a139930ead8bdc16042c913f7d905454398384c4a5da115fb0217ac67fb3c6c0392b52261899e8ac5e373f235af9454e0741137f300b6209cf3f54c60aedb94842f7065caa919cb1a7d19f7492d3c1118968678f39a130679426a8a2935b5e5be30a7e2a6a88fa6c4aec976c0e0df82b54de8fe4a7a2e5ea39c7eec1a69c6985c95aafa2f25ba8bb310d1926551bc582875d9b5d8dcc2508c00f71e0130f896de1c49857c948d7483a92b4f1012254e47083be50832812aaadb9fe2c2a47f1f856d5f44882999a97ddba037dc40a88b6a434d50dbb4b9c139aa7d07a1ed013aff079467d9e7384b910f25528266cd8c2afddce7fe4cb3ddfbbfc816d5e6296cc424c6c3b07317b1aea13bf9d00729377a749070fa8995e08e8b9b3a12a1db6913e5449fe8c8dc5aeca277443357075e0f590821207c780369446ea701ede0efa2b769918e5e66514ae6ac1fb4ae7c69ed0ad7053703bab8b863e3a936a14cdca2ca2169b50d05f7cbdef977853731df999b4442f83f3a8661c948296cc3f5014b5d1e1a973a920ed3aeffd5ab702ed519ef20002a398e72788ad2a0567fbf80aac395f28a8f3b993123310cf897e5476ce92a7a17504cec30fc21ecdd677ad287319a21df3e5b804fce3527262978974214f530d1d4dd240a678ffe3f94277573d4ab6df4a25db68271cae801a0d220799f7d64aae020930ef51e2c75a8b92552aba3ade5fb73e72408ea87284576e9b44c6c4ed4ee19fcade7987b39fbd5b9e9cb0f575084176068246901660587b611ec9b5c6db454a9f89628a35ae8041fa6ab5a4f4356b2c7b98ec5fcdce43dc61127859537dc88a50b3b3a17ababe213cea8230855a924485d933b1bae292b835ea7aa96951a32dbe4a6168ae38886c5bd199ce85a37030c90aa44e2272e24fc70351eda802c2c0c79439a45c15a31b3b351459f0342e562d9611d3ae9ecb34bf9ae0c5d0f81e067e4a972abf1aa2f822058eab122bc687f29343a74c32d5c96c590da92987d1ecd5882df743cbff9fe9a515a1f3efd595880b50102d6ce1555747bb196b9be029e4ad788a22d8ef74367af2aa3076cbc2bacf46676bc5f76dc6c25d035ee6d121ecd2c320dfaca27d28befaf1c35e05b14f64e4ebec53bcbacfcc5251da32553ca5cca3d9360f62a7108fec90e80642d261853262cb6aba91feef50dc3e920e169cbdb7ff773f8e8de046ca755c2af1dd6c14006ed84088c01f15c1ebadbb5906bec9e4559196af6ebfc4c12c4a7ce76b2171588c00f53eb4283e717db5f5675d108ac56e6915f1834ad390b3b03dd351717ea6b17b5024bcd03af387690ed57f575017efb129992144f1772d1311d8f087433b1f3bfef8fb26d93f97fad8d0dcdc45fa67aab64504a1814fbc09353ac456cbebda00b5698b8ea13dc15b039867bac64469a5dad31b3254a3b20442e0413481ddabdff3b47e8b65a342297d68b46ed65e3f4008b5553a3a22d2ba247f9571842d65f64cb1e5cd1378b656dc391cd96ac65e901e2c0bbff37347af4998f519667df1ec5b41c77c3590d977b8c4594f43713b3687cc93cfc4767a2cb65417fca2aee4dbb88d2475df71f7f920e267765e5f225ddc74fc1109dd63e2c27d0694cc5c53d2a04f74ed324817b19ff38b5144cc77531df0cbb745aee55808aeccd9de9e8ca2d89d271d1bc61b5433e02625debe08acc917fb7ca1383ada5eb967458e016799ae4018fc813206091e11c32ad2789e9b60f53d9452e26f1b1f7a81b1a989b1556439f613bdae70f94c884746f0d11c39bcbe1eb17c6656a861a2b7afb6cc1af563e7e1688848ab30ef00046665aeb2d61506f1c738c086304f54e5f3013ce3185f7cd8cc298e213500279653ff118ae7573e9153729a6e714e7ea8a853604afb34bc3eb07d2a63643566bf10762619b4d2f514026388d23d8b534b1ab94948f15bfe0ae1e5e83decd52e23a18422fa2576ce84c7b987f15c8fde0e1aa14fbfd3a2e5a983d448f658a58cd065f04c11fccc0960e4b646de68c10952b09df051a730aae420e792acf8e8bef3a979cf5fac658644281d45bc9a51ec3ed58fbdc056e96c0642173133078bd1c49a4ab97da68f7ec06bf601504cae8c7701df7ff7c47ac7c1788218311010e9ab4436c4b531bd7b9629c4f401e8a685b292e1af6fafc954892930cc4e3daa0c968012eeff5b4a1986202d5d78585265e363a8bff5aea69be86778237afdb6240c0be82ecc6326ca2d5502005f42a0ed67b4957699fd1b84292ae33e78e109c20d3de9b219b9c4a23eeee1fdbefb801ff21f0eae0be8499cb0ff2af3805f1e6649db684ff13eb4cc626558c8af52fc8f99564448571cf04b9377694e81950d6f9989a5756d014798f3d0fbb1bba786a011525bc6fba7e2c52a4c17363628a4c3e1848b980db121ed94e580ce0c939956c12157bb6f5b8523f0654939c0232e0450a7516824c4c978852834b43e32a8d25306bf8c20f5a0024b2fc617ac1554faefd02dd4b6334f4fe41c70263775a68f59e354ee52f4d931d6b00c2f4a7afdf3ecc864fa41a98821126f54c0b820e73e6756e115f7623ff380380477a82ea69b489674d213c1535009176532e496fd83a192d93c33b7937bb4fdcebca835f0d138128f0cf2f94493209eb7b5f76034a30f8f97b9a3b13cb6ad9b2e6b734b53019042600848c46fc9f4636d256cf72ac74493d1aa722a61b0d6769cc1b4bc50f8000755fb4f119986af3f7f9153ee67da77a0cb0fc5215dc81b1e74d75ea9e73bc1a053c225ac96f612d1610e7959109931b6372b37cd6b932337810141a37672e6020a0f3f0a69fddddf9c0e042b2af207c93f7e5524b384ba23c714d42148775b7ce23a227f43ff0730dff821ee140a0b5cff6981ed33711d2e0a8616c831f98cc8b6ca804d7f5e07eba49d38e22cea98d299b9e88087ff08691861a951e79457db24054e483b2eadb9d3f746ebd5efd2531586ecb0bcc8e2847fc6e0c9535471b3b5eaaa0a3d9b1a1d925a405c76acd5216ebf9e6766f32aecd060280d846f182a8195a904fef37a0605b1e86ce83e9e6da11ec25f13e389571759166d09059c7c95c505d2e9ba676ec2203cc2277204019d518a4c08757c4b0bbf8000e3be1e971500e1bd32511d8bac287b5104d192fcc76a21dce528cd8370dd012c13e32902435431750dc9f5ffdb9bb0618e4f5346821353321c65833b0ba695017997852db47c718480c30e97007b98d518fb3d4d27be1efc14979affed6f8098224ae84c9d6b481f945852827442aeefee7c8dfd0ccdc1b005cb835d064c58d335ad8e474bc8a9ac656e59c5af7184ba514347ba143dec8df241b9aa7bbc0b9839ecdcb16c0a52203c372d9ce80ed47147bd47ddf4ad69e6d6d72536fa63da27bcff39c50fec180dfc612370dfafba3be709a008ae4346003e28f992cfbfe936b3f79a394e14c1d09beb3c32e0a0cdbd67ac85b6863cfdcc9f13a062977fa768c3bb905467f4b8b5d15a888316cdc9702268282ea2671333d7c8693ed41bf080fed4c4db39606493e16de827b6d94ff6db6756aba5a9e19d1b3929945fc131efb31b21a20ee5b84f0feabd4f14ec03e26e9b075b2c8977e37404c15d66c93a08bf217da316b3c0548bfdf884794f334f330020d5fc3545f62eba20e95c4c761401ac87f0824326a4d413931ec664eca3199befd3a6f7dda6f866537cdf34bee726e7491e894a87b8758e749365cb0ebaa30c9293385bb414acd212dcd2e57a0a8a5ad3095881f65ffd13b7880182cf16ab085c4ff8bf8bf0ec4bb12545dd97449da01cd2e1737bdc7fdcc4004972ecb55494aae2f0d608943cc327a25559f653e77327a94f3fdb8c2afdaa6e36e252e1f05cbcf6ca9608e6e249a41c429593c5acba6df2be2f8c6891d594fea051a9506bffb913ee0e0b89a517a5a09024a70a78df66becbeb2ac8695a2b4580162360c11e9ab986110b528e3cf7d56338c70ba7427a25db33518b08121daf1a8d9ef4fc9e4e0ce9dd5d6498b0d38ac458c9df96c1a42ffa44ddae1a5f3cf0dc2d06eeb941f23b826af7f4b237fa2411ddbd61c5296930abcfe6ffd91101eeae075859e94d16d7748aa1595b471c08bbba3b0278b66f6e5e9ae248f6e61e16273f5bc0c01e4bb5637029a20902c90b68e46eb7add754f6f90f751f473c0decb9589778a85e60d2b303999c3a2f03bc0ed72d31567045622be0fa391f989b1e82677b750a085f8304c2aecbc73e14832541e669eab294a828ad6b911f6f05d9ad48111fafc228988282e7097940204458d9ad624528ddeb42fa793385eeb5c90417c067fe3d408db38fac67a9610c4b07a38cb6ff294b2944178ee026c1d806dab3cdce6b4dd40fe52e355c4e50f8d77215bb54ddabc9c7cc20f09bc253fde1bcccdbf03511cf7d4092f3822338e45b2a78602db0e8d7151f3a4db14dac4e042d4b72613af4911baef8e4632a509dc3bf4223416d76fa6d54faf9ad9150da7d2d7679ec093cc02ab0772ddc8bcc4632203a3f9e7d098a7010ebb494c2dc1ee2c13c3b46e8262d27b4c28a25dd9270b1bd6dd7b449bd61c2d28f38f8acbf878be81af420f83124a0d4447ac1befa127bbf112f6d8b7e5dcb86e51d8c82b7c959921371835184aa26d535626332557291d377279c8193d0e06c2255371f6b028acddffcb5ed6a39b7c3756aec449d91ff560124c78c657c6cac26238cc8c27143d8be3f6bfad0cb16373bb653c56e27128478a8fa171cac4d9c347560b98c133a97272d282c5fdd37a3098d2bf2c6333b96809f46089265b95b96b0a05cd3ffaace3ae0fd59eccb2843c5d6868be717797864b352dca0381a66e0a364f6754bef6f4bc6a9358c8c14db743e37a8540e90993ad590ee447dee0af45d51208b70a52799bad355e92d4e6c7ca8d9cf2fd65d5158839228a165c963a4574bd6afddf10de527cb170965d122813980fe7717d5275d2caa7ea137ba008c5578bc84fd6fd30849df64fbda79704dacbeb610a55a9d0140ac9b24f49e15328e51dc27641edbd2a87c167b667071c5d3750a62042d0a50af263a8cf6f3c19894b040d22219b6c74c654d0145239c174cf0983decef273884adfd1f50b4b4d23e2e99ee534399af07e825e6636df8274bb43b6748cc4215045a20d9d29f91496aba3321ed9cd6abb2997c455a6a32b90622afd5d42ec2b55e174fd37540cd15d19c881ccae574ea1c6326ffd5adbd46d8e228541fdde8c6f8e131428f0c785d6e05e5b494d61077c1e37ceb2a1f629325360624486b71e162cc125c2cc09d98797706046ba921a553d7e700029cdfc39e5bf3c2bb3cd308e36e059a928e7043a3e4102214e03534e59044712b2ff40c2d9e3317f5ccfcd85fdc3302fa87017d8378eb58a7b7f01516cbc007fcb0ceb698170817bc1c71bdd79e463063bf09914e4a6015b5f1d3794659e0db611694bebd405a7c37114687e97ffcf12a31c28ea60078552504051052a3aebd6815edbc52a4c2752332bc6eced9a9c602598b33f00a1c089dddb14f593911d4811485fe90e50b03d304be82bb063ef9f0b1caaef472120440948a0b0e5e17780fe829bee4612fb6ba1fa874e45661e285f11f2f95e00ba9e074fe897b24ffa56fae96a761d009cdcdaa95f4c01acf50b8eb4570e3fbeeba20f24ee103aabb3a4099bc9fe348840c91c322510c11323d2e988525b0c7223bd3144d873960cf6300f25738faa8ad27e663cd6cb776cfbf764a591846693e80db0044bb4f17e299a1843f2ca242d7cf76a200cf38270d4604522da029fba014d3a277361803167203bfb3e4d44c207e611222181e1efe1a4224060e4962d30ff986a72e02c8342d839c0b6717fa8585a34868639c333a0c711d51c7e86553156af978dad204979772ac01b4d7a3e7c05d1ee5f8dcc89bb54eb0cac272dee0a765787400360a8c39e5d2df0366c35108291064b5444a3c74717c650ecbb5d7f729c3811d79027fae00e24b560f9392c473d639c48ee0fde68b4c2a3904989d512299f21564ac1e4b4a929bc4ba47f646412a2cb1adeebe19e91175da4e7bb1108d0e8406d266a050a739cf5d9f09f3504309aba699f721033f986ab49e9cec7b843f73acdb4d36995889eabb565e86c651a3500aad5bcf9113f45710cb5600d5c69952de46c1f7d42c114d128a033395ab6aeea3d834473dc5f5e6ba19bf322544e9f71b7c63b82e6e87db764cefb916385480c200dffac7bbd27c4d4301f0c1c2ec5a450361ce580d9cf19e8b2f3487b857ea13e78adbbff87eebcc48953d8e89e61fa60976e0b1c3f3ee87966c1ffb9d7cd80b1e292411967375a2055c4ec6d29e4c450c3240f4baa209bb3b1f0fc663306e6115ad3d6b2afdce796f9e9dc53827836fb19f35ef8ee04c4fe4cd8eb9d00a7249ce1a03b39ae7ac22085990d9c36f7e1ba3c72eff776315c556eef67f297da44f94176d65340a7711e2dcdf411db34846670109d9a65e185fcc4433dca42658e1d852308b4a867e2a5e00bed36feaab7fa4ca8356b343f034cf8dc4f03193a47e61740b477d893cc4f5162433f7008a7bbb6c3b06b3190dfac3117ba3d471a8a5b91d7d36f9880a2570acbcfe3b047316749cdcd26185e77aa9f85ca70f7ab1b29016ef4c85155a5f14508827ed15c6f6da4579a26abaa72c087f3cb49d14b83f1649cf5a45d81d89833321e95239b5ea5438d708b529d4136226f25e2a1cb3ed8386a86328a0f3bff19e6dc52d472bad98f632bd2b982d56a8dcf4e6cdcba2f5778ab5e3054a94ea1310406d811cf8bff765e67801ff105bdc2cae7d2fe74d14ecf0f4d2d92abd996ea316a315e316a98094d0379a16baa25be64d3d9da804f9219037c6454627d3c5c14ec4862c9b99e326c9305f433e56aa316b5109fb30eb9daad680516242a0a5311fa5059f49031fa785cfa71d9fa4051f63a24049339ff3b45ee72f7d49b774af4e3e12883ea7c8660c4e8cc22eb8dddb2d2bcd585e27698a2def63bf06cfd45ed84e5b3fcc1b9ad75d8e5bab1893121c1e1fe4eb524d3b7c5e0e3cd982b7bd83adc705ba996dac54a74b8fac2682cf3d31e71a7b096b4a614e47e71defbc243c94e4ad856a1d5ddc058e80628de75fa5bb026d36f2b0d1a81424ef94357c02c3335232954149809a346c7ce9b3f65a076c52880256d9ba2ba54d7ff1ce867d1bf9375bd7225ba61ec260181408d8f06c9d2af17b1031a3df020b5cdffeb5fc55a5aa70c4a1668c4240d4d8e80328904f265d1556b0d666a27d3098b5f6a6a445eaa0ba3e5d0859eb22f6aa3584f9c989a4464c2635f70d144f3684aa241873b4afec239f7d57639592de37ffe00386959f764029e6e871bfb77274d7688d18ec993dda6de7a49ee3f999287a218c78d5ddb4af6af45383b3744623f7cefbcc25ebe3de1d5c0db6eae20906ee812330fed9f673e99f0bcf79145eb8ad15173acfd715335dd63bc95ade462f8c399bf3666ec594a2d712f6794d4ffdbd8d5ad1185a6ee26064a2243f428862879b5a5def6e1a3b52963d91670da864b4f4d8ab2b402c3ce2d4979ef371fbc9ddafa3655eb55e0c44cfb38ff00e57e474f8887dc2cd0e523adefa35d1f671392781d54f4416cca02c8a2fcea9f74b5d412c8ab205b871a9ad8209403ce07e017cd0ddcf1bb2b39415f1504161d351f981fafbef8dc9943f584625da43923a05b0d5f9cf5988b59a883134770f71e9fa33aa2af07241740c7a65c163cb0e8039df50a8faa2eaba8fae018334b94ead87a294a2c9b216616ce2aae249f5d3cd38430b2d901025b20e8512efb9c6844a02c21ca76263c3500751cb8181b97e260c59670c9b0fc874485d8e0538ad0f802986662cf25d1e980675c366bb601e8c72d5a6b5814cbce4300d44624b539eb87169686032b59fe8c95acbcc26d93ff0257ff8c4c06a598dc47daa6a3201044a9eb1446e96cb9f27c9548bb5ae9150eb4db8f436e316ee5ab206879216099992940cb2a442414d57e2a95ef1f4e3a0f9075f52e138cce977752e4b5f3b71d471d66a896060c77a29fe792c95c243b8821b2c82c1286abee51052cf019f15c9e6149b3bb73d1728a4cbf3bcfa2f07c37dba23fe9cc8e5418fe88d459455c29806ed4c2996c2543a349d54d84f7fa414c913aaec4b6060f3a1bdce3e585d5b2d9652f6526ba9db9506b10bbd698e546e1a69f209412c84c6ea76f2cda2439bd5c69b25a336515b2bba2c0c54e5467ad2aa51d223ea91b5652d0c4002fb7169d5571e77faba43672ba0c7cd112b4754707f16628dac3fd20eb019c58e99d0238ce5fd155a3c853fca50dbec164fdbda9c29f73a63f3ff75169769bb0d8f519ab28e86b65d0d49335aacf50625af2df198512961969b6347a5c6d677e7f57dcc374c08329508ad8b664988e76b9e515a39cc4a10e98dba3f21ddc2ee494b077818704dbd1dbcd7bb06d1d1daf2d27f7b124afc74d6147d74864d6115c453c693bd1d3760a922306e37391777ee80335335f0d18d74dfb6d727267c02067194ab3372d2eab3c89cc45377ca6357d5ede6775e7285de0340c5272abe7e413b4c4a1074f1a6e086eb2eaa94d4b293a8886197105d8b7893b27ef33922a8f229503a9c5bf0c62147756e194ca5395db271cd0453ee59088d3b1faf822cb49d9b8e7f42c2776b7b1517d348336c6e17d6e87f21ee98a676935b684963a2a93f590dc0a93db97e0f175464040e04ca9c3533beb26a5389577419c7a74b8408ede494cedff0d38ddce98ecc9cda070b9e3ce14a1c40ff8d659a8081f87404cd0362673add444f949cddae44f2f5b784ee746a8b75b9b1c290c6dde1f7604c7cbe7a6a5cfb963eb32d34bab6b10a74249c0955ba120498271f2d5099ba6074bb5dfd90f8a8ff07cd5c679f9ddf928932d8bc532217bae830c575b1959b75c652e3802e4695022be5fa02541f9a58ebeb61598d1fbe068b4731f44a6d74e552eb7d84e3e37b6870cf7dbcb848feed28cc4411f9a4ecd75fdcaa6cf30540c7c0c8da6e403b342b06f157c762c1755e2f580db540b2932a9430601da490f79cd42c00f4780cbc69c7600a1462011a6782843a03925cd33ca9c72672318ea9bc791742befa29e458a90ccb6cd36031b35a2302090ab735cccb9591f5573c420961f70c38dd4d000064800fd60a1eecd40529ecb9dfb6b7d12d16f53d7dca858642c679d4513c5054afe0675cc45046623e02e999be6b84a28a82682af957c2e412be575e8df8a7020c92e10e62010c4f8c398b7694f3e9b725d8d8780a5883b3f3ca700aee1ea544b04e79be44b51f6b3c4e758245864c624e073fd1d3577e2eee1941aafc01f199e3d3cf3e1125155616d915afe497465e2c652cf4554024e9a0346823a2500670a8f4d4dc911a4391655fffb0d17928e99c15a69ae4837dd19f7b2be03157770c0f74627cccfcd296f08ce0bc77351abe5b9e21760a69b2433e59d992fce56a49b19a99a75039b078388f6d5d909468496b09bce286817dfaeb6c2c421778bb8bce7c22f526727ccaaa753c71f16038c3f1d8fd32bc7f375730ac5f17ef7887984cfe2d320ccc197f8e52565e291ad789e6c38321f8246671c66c2989988c77567da27dd85e911eb322f6e5ff8bcc328cb0ed80c1c866a7be31a43c7c64739e0606ec9cb3809d12f245b3aa52b8c2d9bdc7449983120e8af7f38d3e891e030b499d37adcc08f5f05a6cd6157dd41d95e5bad95e116ab448b2e2a37ac835ef5903152393ed4ae96b02c3e3f4f56defc775a38bc57679421e90b9f2b4a31e2ec9ecd0152c1f0a0713422c4e7aa276b59cc92900ae3b5602dc853d384b6667a45a92b083351506b95b483a7416a30f503b7cd11d704c00c91f74b822680fed52814e65ece3806ef3141328115f6f2aab0ee96ce12c566f11377e4281d0a33272cf9fcf9cb7f6b242c1210624cdd7e4552a2954bad03d8069e820c4df0499bdc07d5d08fb4a4e098404dc414b602b098d01e3c09dae967875d13314dbbd885d993ce8b8e043d315e943df62b1710ea80cfdc04b74b1eaff80bc8ebd2f93c2adeb734fe873b52861d7e9361686ea30aa7ac4618000feb3623e1b000662239739589fe65b8970a14a862bf12b9bd4aa7aa168aad02c48746507537070aa5d70490c02280589fa6943c14990de0725661488f640846ead27bd09b18138210276dfd985a6c84db69e28ecdf362ab8603df3ab452db4bdfa07d423adda16273fbc7019d45d26e76c1c0cea6992d826cdadb59d1162d361bd5f9f6b30c825b38364bcf8fe3aaeb6a0239753b2556f121d98f562f6cbd42380c8db7b947d545e2aaf43e1e0164dd8ff7a7996a8aa468f42598e70d5872bf533e747af5b8f27c89ea8550eeb16d95ae20928f902c5c7b1ca59b8ca20caf562de0fb5302a7c62e8dc446e9482ac100d40b74ccc94e7746f27f2cd8f3bf26376e68c12a6d7a634b6f42c2d5fd6ae51d9f71b228a490f1062fd49a54d048b408c2b111db1ba01f3900e82950c862ca8c009d846fccd99dc36aca2e5a8c596d6feb1ad3927baace933373dd6dfec8fd67d38ea921bcb4bf3adf97131c339498bdac87f33a1820868fd262cd25511008517650fef9a4ccae9adcd7e9aad44bc98c27b596f6154a0d8cbf1f98095dffee337c4847dd3718d4209d91295c7174e86b5a0027c19742bdc556e681f0313034b36e9cea6606dce0ea3c47fdc5b86f2663e9f6176ea1988f064afa6c064811c569aed4ab988fd6decf104f8b07a564bb88f56559bbd05e5e8860c2c502e769a3b9b70b1d6864a79b3ae47e05d3c18acc16153862352b4cd7180aae51b98318c6de8a22957005603305de95e439759b2a4fb493f008d22b0b5ef8298a15f5bd3fb5d81b94f79628c3e6e963d9abdd41c71f01ba51bb5200f548d5dab8ae15520fc3018bc026a8258d766c4db456be73faa1e487a6c4754fb20ba13aaf74befd8791690180a032081f8ab91075b8e1569ce84906161e0262a2b12e82bfc92a500b7b46d4661a94698dd4b66895f90aa165fced05ca1759825166b7ecf05e7b6320a4325149df1e1519f480773f46f3d66cd54c73a46e797ae608e684a2fa94f5df47a6fe0cd99b2a528d7f8b261abeb96e0e251b9706b24f4aa9f20eeacef240a88e95ef7b543b89e19a999a8ded281dcaa7e38ec540b523dabd869c576a1fd0e1cbaa326734662fcdedecc855fd6e08fd14bbca2760e540d0f8353a721599d88b5c965d375202f5cf49a309039b976e5ee37961134587102d7a47ae3ce3eb4e3ef684c1a1a659f083b6319f29489ea9ca087674ed23aa7e7677b08657c48bc44f4a40f63ff42d476733a8a060e905936be916c679cd5206a9982d7814b8b80a7af8b7f2a4f0b334e2e347c04dd22b928771973f1173f5592cb05e1f322f1a152c7cb07af8640da0fe0be43b39398b6e0d13052520cab054930841b08eb600d401d623074f0af4d6718ba3848a7f8df3ee6a3611e7c8a0497f7de07462f0615ccdec66fbde29e3b5fa96ba31f077165adf3a97ce979e912838f5191f979d6a5526c01445e22601a26b30c4e16b5e3283374c292856d27f46454e2b80a924b12252c699e2f70a216fb9f1650481dd2c7b1b04ff3e9f05ad02ec7c46025794502210c730c3129425dc646a221353de04bc4ee28901f5e60ab7151ed30797e59696412486cd6fe6637277176ea65c47f16c46db7fe6283683811a34e3a0b1dcafa91e99286aa0a1ebc2c423cbbc8108e62e03c28ec95e442de0275de932ddee8134ddc6bca195544d2ece401737c6e357e677f2c6daa82267a13c90d29fd43338e4c5481d0a9153aa6064e99976392131e6a2bbd186d15da1b935897e3118a2a139fa660fe1ed7b42716d4f6c3aec2e49a5ea9fe1d13a42a9cfa9afa24cc2dbb9907dace8914280f501d7819044d6085e8823042943ac97519dd1ceb7637828daaaee34309520f5922c72d2e822d01c199be9699cf70365e9aeb556f8dc9c7544e957d7a6866e6cf497b9e64b2bf0d5a77885b5a1c6eb24d8ea0cfc94f8cc12254d82645bd44fe095071ef83976c7035f4496c06c463141afaec39df1cc848495b0c18b75471a072a46b375a49bf9251cd6bc3c3843943f68427bdd33057f794a7b835d7971db5b1499d2a51a577b08b6704223fda4b82752ad7f4fedc4d81921ef17ac64b50ea1078a42ebe24427a911376815b84bd0e1fcd960f3f98a0e64e5e597b005b1bd6bcc92b46e52e1f55940f12bcba317d2fbeb2109e10e85a539c71109c5fa4d75aeb469c909a209bd1362fb5ce8c38eb9c854afc2791ac6e35466044b1241cdab8b7567bd1bc962a9c055aff33828f4f7484e109132d41cd49a59ccc1108871a2db6855714094e679cb73c56aff6f709110e0e98f7f2731ab8b8a8b6104c4efe1481d926bbd59c39b2579e20a0e9b6cd4d26af93393ccc97060a236d8cd3474de061aefaf8791755cda8046de5a79487a08e308b553ed26718dbf3a87aed1920e7ef8a24b017223a69935ddb783c62e6c9a0b5f95c5a87e88615694589518da2833f18ccfb2ac8e333c7deee42e1c27601579bb91a2c0f5794635b375432cddc3c830b1293a46797f0844a970b4db9cacef04f2d3027acecfc0cf62d18dade32c903f6570e4aeb0b8d858f036b32525fd6b3370dde336559684f3b93e0962113472946e041694fcca04d5275c4abb6c1931b23e705417f839a9c0d3a6db8b7715ff73446ea23daaed913d11886fd74133e6fa88a56753410ef3f0f780aa1d8863a3bcde15935b82df4a90f5bd1cfd2ffaa7bee84732c7137d1ec168b55a8bb00a1522eeab70d7f6c0a2d6c345097d53a26e2093d8da31de1c8e8c68269a93d646db6d6b536c05aeb276fb9bbbfc56243edb3ed3d853593b8d8d423008ee306d637dd55f829b3567c6c36ca3e6dabe2c56876639163ea03d9494002d3870ac8530999703a7a4a09400de5343eb3da240a90f4f532a1cfd2158d4d92033e284ce33387272c3dc9cc0b431f83a44dbc00897555cf31a329ec022ae649d2a4ad79e47b5ac65ea4fe58867359ac00d63707de44e35bc4f8e7c33481ab64047a3e3c694bc8ce97aff20cf101c6bfa2c6809ec3d33dce29788ece5a68e1564dbc109039cbab296581a57944d055a5956d512fc8d60fb536745c55391ff8dce3860b6f0b0f58d3b16ce6d53a8cbfa1e3e53327abe355d4a602bc3929f18ae1503106e0937e0fbdaeeeae1d65c023e552953bf46088c54b6690cc9ee7d12eb22f2540ffdb018d7b47776aefe9fdcdcdfd55b1c4dfae7762999784b69d75585b8cac2468e9341e9b1d7b0883b95de483d2af4898676adadd788a6cfe7b8268cf0316db551b28d7174fbeb2df8de4d91cdb4a0948b9554b1e92f225b7a06cb899fbc30f92d66b44b7ec8bd7c3b31bf37ad096c7aa726e1f598a04cc13db04adc153caca042591eecd067454ff7cc3c74092d83450be09a3de07913602815977d46e0553591d3cd9092999d7118114da2f4882656613c99b048525929580b08795990d3d01061590f8dc29c2fdc48a96757a61dd3b793e276bf524161363b6b717a58e9e4c1904107ca73d31051bbe28e4fbcf89663a7b63db638d5e8b945956d46816ccc9679b2cbd228ae5948d40856a0685b3aa7430b04d33131eea6336cd0f092e39b6457fdbe76fcdd0e0feb50cad5ea8d41e5aaea4f72df87f1c92410d9fbc13ddff8e71eecc756c9248bcccdfc1c3e6971ab7eab31a782f3bf17db4a9650865de61dcd74e85611ca6d7fcc2c1c23d6fb6ee7fa04f800f6e48d15070b1567d3404df10c2040996489055cd1a5cc0df517458ae71b6cfeef8fabbae6f44f1c2ba48b796d31ae2eaf15cb2edf34691887062864786db213bc110da5f82b2f2a9a2bb31320461ac21525aeada9b6337a8a8a3911155373df94cb8f942857eb471d358b8dff34d9c188a0744245a2d275118102c7c6e4dad370c0b0b83f167de09c0d1e38ccee5acb367f52646459d85718a2c80ec36583290cc7a9799b27f90c0d634ab84422b61e725227d70f64ed01334ceea9e8ff2d49a50190f77e47df84a645db80575bdb5243393178ff28e824dc1e3feabd7a2e384104d62bf832855cca4476a151905584d0326887b48ea403f07c40cc44079b3c994954c3b152287653634605e03ea791aa7e818f1eeec49827abefaa9fad025767404fb26227bba4b19f69c95c311fea170d2b4ba3bbafa65f98f5d1f23d6221aff3b406da2e55031a3c59fa75aa2daf73b005a2e5357151aac1f95412406cbe90bf8e2f88f64d7054139f65eb76ec1974556d127c9d9ec1758bd9c5e13dcb7bd44ce564c5431b2cc894524184f860f257d69f1e93ddc0c40698ff385240c973dcf15ae3b27a2fd876b54a4789690b633a87fad9539682f696a3f75c9d3ce3e99efdbd8f879302a13acc6816cddbc6a9665973b38b52bddc9ff0d15c605be245b07925ac499275106d46c38598a85d122ccd8365572f6682efda1872ee6b1231bdb2fcd5474c3508d6d158cc733cae1c4ce9f3de4d9e0775ad742de2ebb8cd12cc74cab976326a1f62b237af621bcfc04d638967fb63c189a63a6432fb2a86155d49d3a5ac479498adee1913711eec8b1814b985efe17dd7f0d146c2fb233bd4ed724bc55f5efd4bf4b23248ff9027e2d3f4c33aa11aeab8835a3813eaf3711491f9ce4411c33caf3699f85212559c1aac469206440eb3b3d0562c311cd6f018abb2f0edc03050cc3e8628c1e161abb22f2bcf10757d001dd03279411af44a3ebea96fcad71319758166458af597a224c1a3e2350b63bb570dd2bec8c413885d8e3dc95775f9e66142f7442ca7a4dc95c217325cad61faecb37098c9b89640bd3c9d13e81d0e4a684e2abb0120d930c9a673491a097e34481745dc1561b203b1c1162e93a1bd372c2ee2786e42e83f50149cd4eec12c605f772c9b2c7735fac722287dc0b71f948bb655db28b1cf4f6b58e5536b8b5c1f0d8e0e266cd21111f8a0c0d9f334b6914c13b27e065de19b88d7c592c766b6d21871a4cbf2e262be9c2944bef24407b6624e1112bce3e1f96e46a5dd2cf11d48575f942c6d7fe1b9fbd02125e21c407cf6b1b695ee50ddde20dd99227aaf51d37b8e56906b56f1656b3de60c8b1a0edd57a94a51f90df58391cb46d45f86378056dfd954df73eb798f5f6a4ef11c37787c726da9a49daa6dec933aa75a0179de0bab5f259d3e54a2ca4ffbcb95e3bb50387168457de4f236b97fa93539f465b56cba9238e53648cc51fc2361c2e8577200fd53f68ec9850e00c5122e26714ff43e3c564cad9e2abb389721a9522a2443e4bc59900915910a9c6179acef2c229abf69d7adb6daeaafbd0b7c299e4a8c3c124530feda8cb78762bb4e216ef815906ad7a364215395b78ca8392cb059586ef191c29dfe1ae8a01e0946a49dfac8d6317e6b47a2c16f0c9a25bf3cf4f4704a47e9d0e4f694f229439027897f5f2e0e43fe1654ad44f0c462a539ac627e42008a9425a26899fc982258a91b2da13b64240a922a58ad820a4498554ec3da20261196c38612fe4d4bc6a518aea5fb6fd3d3534878a94b1919636f51a8ca903aa7e8681354d2d7d5841664f14935e5849859212bbad9d883b50d6734ed3f004e480bcca5ab7c4959e6562b7d340188f6c7ceb8b3bad596d8667f86c5868a0bc8a88f58f2f990b6266ab555d0860a8b73caedef9cc71bbcd53c99e677cdaeebb1e2634b73e07fb43fe1265b1739dc6e1b36f341e5df2bf03eb4e9b75932a558bdbe35f4c0a8b9ef989cb697c0e88c6d66a1402210a3cd63a1802a08fd007fc1e4f16f73d2f4fe8b4ace7ebf6402d9a24f50b5443238c2360cd59baf9abb2d046a859deb54bd883c9c8db9680dd7524e79b0bad1ade5001e1cc9435a14285108199c05047935b3582973333e98d5ef71982ec4819a0981b24359d82048a593cce761ad64869ff420b4fd4c658cce944c4776f8eb96be5e973d4a00455fb0de93a10bfca339b982356fa415ab9f17cbfb58aa866359ff763e6e9f7bcea6d99ecfa3ccf81aa0c766d75569fde1c4a6fdd16c43c026cf543dc31ae19850e4013e86c22a445026ec14c50d41d0d7d67f94b3f67d4abfbddecc019f6ee97e7d018bfb4a0a8def6f980b16e711261bc8a4392cbbec30e7f47e8f5a27dfa0c1177c33caedc9b953b7b040f9704817b49b2c24077e759cb73ab9aa7f88ea7302d3a40d8b7e55ea55a4d46c6d152291446289f866eeb6669bf8c13a8cf91f9c03d4fbc1b9dc8407876d940209b860c809bb1128a4dc6033aa8ce3f30c7d9d43a18961e24291d098aa3ca5b85ea594dc885bf915f5bb212fdd650300ce038766347947f1cc69d44caa12962bb28b3f95b30f21c99fd64cf9064e0497752915a84b481a8a089cd47ecc312183e4869c1a441c6c21675ad66c21eefc0d8890456f6b3a6e6299ae792b56f034100cd532822a4ea9a92c263af5260f94b63394663d92b7a7472cdc80a7056b2ed27e173b10eaa9fb8a302278773800a549485b5c6da6394275db5b984fc475b3f0594f8edd1c71341820837493c51c231a9a1b7b39718ae6196ee79e3ca55bcdaf59c837d3e420b0032407035484046dfbc251e1711410627060e8c16b25ca529240fbe2dc8a9fca2654ec9b012faf315d98a876780f9300694bef6cfe484aa626e117abfebdb59f9fe3befc0b3175e1116cd4a73402f11314045af5ed61f102e681204c5372d47295f4c4841725a090593bb27990032b1744d4db0306b777b51ba30a2f811042b815ffc35497f601ad2c3b5b8753b805eb4d47c8430d56b9436f348aa61fa10fa934c99e6af083357012d6636d858bcb1d0676ac1e7b8e89a085a88d521640b8150350f34f1bc53388bb4256beda77f718b8432f0044ca8b0420d2f47b8f32d94ff0c2019ba96015f97f30764fcd3f0ef04154365d1e6dbb21261cc71ff78c347c62f13b92b5d46a14c8df63bf8adfe93516e3274d73887a0befda964e4225f9dbd1a927c3f7ae22a882a4c0a60ca25e2842edd528ef4a1c3a25f43ad0faeb11c9e30470c23f030746cb4bce175defcb8f0e480c96f475e02af8aeffa6ac2df9548a30e55f8b488fb586dddb7040a63149b6f6dcc51a8da83338ca191856e707cdc810127a67a2c0127510509c2ba4b0759738bb224223c54ebc04db9372126155303f2fad620d2d7a0f3af9492ab58c393c028a757150089183c5c9cb8068dab86b700bd8faf01192f281cf3ddcbcc7f0bcc8a8709dbe538f9f15c9b7683d262e8987573aec0b33e26b4b5ce7d10ebb0f65a1b21af805508d018c875be4b2dfe97619743920b2f27256c180bc2d848d1fbf0ce434e5dfd38e56de281e56111e74b8b863daf7c4ba44e5ce3c0051d34cc61cb00722e89c472e9aa2710c1606e6dd34a23667fc2e20f2a026c367e329d4fdf0f3b53bd9941eeb0f04d9fdc7b2ae1e37a161c0eb07d1081ce4a6e9c8e12a2395a14176fd62d37aa671e05e74abf86d339d59fde4a1ed3e38ebb1a471d6142962f2351d771e85dc8438af1072382ec06ae39313d356446f2a91f2f82c52df4aab805c1ece0520724c9a08d8d4d31863d98aa233a6abc53c863110fc967f233daa8d793828422aebc2318dfb623db287a7aac05a5adc21f934077ef69bc58f64e2ad8190522b282220c8e23cbe8e32bacb268b12bcafb2dce4d6e945a400867046c9c3c5ad29395288bbfa7beafbfdb3785877ba056113727806c8c41b54adced024ccc45a5fc535d39deac049ea332866d0f22e9ca011bf454f5405db78682d09145bf2db15fe473494a451dfd06b7a2e07e21d5159442a7e300631c8051a731a373ca984c1ea7680463ea18487d68f8dc2b81659eddf9362ac3203d0852d31c45459abb88584a279c6728b842e7fc4dfa6bcee4c6861a92759b6f869f092ea4e2b1042302a509b0d48820ffef1c8e068379e3d8184b1b04ee407bc10fe2af4b5a21706174d80f9ab6d2512cf836c50689fb1037badb4966f62be8cd05046e1e6ed8e39e3704ad39ac1afaf52a37888c4256725dffd31c99d035adfed75ac0ffd2638be471c8ac91a884b64d00cad35f595669f8b757049ef80c7f1719c512cc940922aa10804eac33023a781bbc6c4d7b44fde4ff05dfabc0e5dbdd8f41414ddd0c1645bd36497beb7d6a9328be2d7cc4ae2a527c2fa43cd6b5b687ffe9c1ba46816c2dbb6c7d8553ea086d97803dd5d63cdee4bc706e699b8b7a30b1718bed7e1b1c27469f996058c7a45f6e1575e9a9773b81ba0192e4a2fb3d96b992f7796856679a4e58bc85e371862abed4045894aac869b8abdcd731518af0342757f8d9b19c09b3e5d7262d2a1a25affdac99540771c3681aee7b7b5875b5d426ada3857293a07eba429d68886b1cf0a35b748b44f9e931742d4a855907b1c20f4758e014e6e4e5da391b78955c1c0935b3db41f45ab0a586fabceeb0bce9bbda725909dbf96a27410d93fa9147b682589f24258597dead650599a2ac2145d7e18dea5d6d46d944692fc2e9a148074d3efb3553a02240ae0b09a34b13e26b1d92a6f0e17431d60266f3f626741c80deca0076bf14909d0b63e4ad025ba18c987ccde493efd24b09e1860b5ace34e16a2583a710e1ffe4803b138c5a649d988c4f52895a28750f3e099119c674053a648940974a3fd615ced2ab27f35795e584deda150f642053b86157d436ab3b8376b45baceaee94706c0f04bc1cc7399e6ee7531cc864224375878dc86b25a1f2d996b533b6fedc703b867ea3f1180e53ffa5f301ebbd84631c4badbbcf240aa53f22210509e60bbdf5bf85d0b5d87d8f8fa65233cdae68ff93b1966814143478f384fd5f7f78c092b1ebde8e0b58431be67a5d43a0e82fa22c116f485b3584b12a02437c2d5d6cb4d5125e492665a944e9b64d00984aadf292d566a700c9c0cbf19754b1344d89386964b344a994d81feda592e3ad4c574b0daf9c906d287ac015996d523f3ccfa053edf3361822b5321794a89353ae98215733158460d77a982904680f4e577167461ca1d0ceac90a9f2643ff9d748645da43f035d5b267901a49d79d81e1beb9ec0fcc05913f846417a618ed7cdd227951653246f6619b4022880ead95c7ed32f1078963a6d930d3b43278988b61fa78f396d67ced519769945f6f5c42793db6bdeb68de0eb36e9430d98a0e53ac7e284d1a6298768a676b18d7d8fa42937cf674259f0a9f6888ebd95f5dfc32ffb22e491098b994ab511e75817ec348f64d1c5cd163a4a9e7b96da4dcb54f67ae3bfd090a234529eeeb0739d7ae200bea2040a554c4fec3838d4931e29eaa36529a377d724eba325e0e2987923845c9b57442f4083997036ad4f099dd9a30417e671904b8b155204794030447e325776f5d5af9d21c2f6a0f0f1857da2382581a05ce217d26abc505829c311416c46e46ac6b7a66b0bfbe8a8e99f8dbc9a442e51fb796ab1efc3913c280e8f8a5481decd3c117a2debb84c10265088214aaa87d48238380f03ee8799e8d5ddeb23aa5ead64806541ba31aecd3075a4ce75a11a615069612dca921cfa2885ef372d8428914833f6aac51f115da95199086f50e3922acc601720e38f5cddfdc60d9447a1c135811d280accfcb8de9dd1aa6cf5e6c05d0613664d9ce238e6b0d0840ba1678b9d7216fb613dc49810f8ac9c647a42a43bebe46c56a6c2ba439532d459417e7b04eb7fc2493a24f13103afe39927c553c8e37117e0ad9abda0f216ed9d5649516ec002bf2d6565eb8f9a0aa9ffd6d161cb7b10b0591552050ca7321d95e0b00cab4da702044ee9cdb3d083d60eda1cd21edc568bc65145c89e71ed6722394140760286c639f1a3f2a573e32c429042b7c47523930b3237f3b9975f0b1a43da94b723c782cdcb7702722ee03cca58551f0543adb28e3755563639c3c43cd0cf83a39e45b5469bef0a13bd39051e3681feff09fabf9f55f986685e83e2b0ff9c8a9d47fb13170da29fcaaa420abf94bd21c2de6d191654236f172953bdcf4253dee3e46c0494b06072829cf7c29ea0df881857a9846ef3ac42a551870e0650131a45f9be959304160a2c0e313c036fa41e8871558865b29ebab6029c153aa6682712cf9f02b6d21e825d1b5a53630d889f906422cde9875336bca4fc6f0e10f0713c97b178dc03ed50fefb2555a4060e94deda8ff262252b0b138d7b9ca46b7d2c91ebf86d50ad8a2143464a7a9c608c3d7d43c0aa65a472d114ccd02655e1e2347e783b8adb8bdde862c6b5460666ed168316f70c22cc18c8a18b156dfd7463410c4e773162191b76b573dcb6fc307d023cf7b82143af76b8455257889e326e4fed50f6f524c81229a02474bfa83f786fc3a74b5b04f5b965c06fa016b8661a394d4f4052e2aa9ad80fa69273a653d27513f5e395ac36a1a90a06aaa00f680ef40b47747a277254e06bfef07914497e07cf3ead005a52d107aad592b2a30beb14c457412ca03076bdbe332024b9f02123b2324ba030c47af19528d6a9b17035a1cf586157889ddfa27bc6eae194cfd84a188208036ac13d02f8e3f36daa2f0b6e0075a1d5e1cd694a7db37bf2e285283b9569a164d94b1b513a442eb5ba530a7fa11f9eb3553c99981b75f1018465010a09d13b5a400a21d60654698c0e02254511095bb742ce4a46881e453687d225ef6503a24698402928dd97807b073afa09f4ccc23b867acf01b525dd05ee951be539424b36d4565476647e7e0d4c98fa294497c95771242511c9953ba594026403a703bd0303f0aec6d25e436d0df9e74f21c502cffd967389aeff2deb0c95e9e77dbe131f67116ea4bca182e75e63711615cfbd26e32c44cf31b8a047cc5aee1ef2def52deeaee91fb4a03dfd3242ff1eba082058cfd49aad6102cf3d3a81f0e59fded45acdd45263699199eb73fd2233c1d0358722f47f0e7b1ab420e46001107a40e107fd5221020a427d427f6da2f613847acd77f2531112852b5c457b0c84170833308014a105423dd45e0001a80085e96baccf4f3ef77b686b306fca6410f9bc6badb5263709b5e729f028dedd5a2cfe85ad21f5dcefb6bcc1d7fdf61b6d6be89eef1c9ffb0ebb7b2d74cfdefd4a4375f5105084feb59c76fb09359c7e0181fa41cbe40cdaf784fa47d7e92415085184ba4697de347bdfbbefbdf7debbf761ded73aef6b0db792a95b87f037574158d81350b4b57dc81be02eb055b4ef800df4e42c3226bae973e4f83953a00367e9c055b4ff4d285b3864e044ff54480a55ae829e08895c45fb2e34d396a5136f0a87bf9243067206edb7f621e020d0bdcfd0b5af2f1f0879c3879cc125b65c207bcf592ef8ecd19ff0f45d353d7437a341a07bb7c33f39b1e0f30efa934205093c0082c1a18bf24f0b4337e59f01105620cc5c458208f226a6e4c9304d871e6ce205a10f5220cc5c257394adb257b1be89db77ed4ded3b60619b37509ed73259ce39bb2bdf834d24c932e79c73ce390fc92cf749be7989731fbbb9de50d1722d535d7dd4c7ada197f75d52ff1e508640fab6f0839c240220e02ac1f3f2cbf36bdf087d1f779907922449923c4f21f7686fc1b059287832e85388a6b9554e0cfff43f2119dcc4d3a3c2944d3c4f8f92284afa3cea899b78fa867e13cf9ecf61d987a7900f53be6384d2e7eba1272a745b3eea3fa028ea37b1d71bbabe114a19ff5195ab6c5fefad63aacfaf9cf8bd89a83f87e78930dd44d4ff50d4a33e3d3d079ef2d36417ea63b4b22c490f64b7efe1cc6e42b72c7d4eb2d6507dfe6cfb2ef951dff37ae896fef4e63e87e91ae72949b21b40ddfe02393c8741be1a7ba0baa8a613b827f5af839fbd4a41f9eba083ed3d94431504d77b84e94d10fb347d2f4bd397a6ef807c7959bf54f2bbe99640aa0fb04ba1feeb97babf9ba67acd3f6fc81b4eeff64e3283e939bf42336050b3cf265a0035939a83fc4b9f537e10d8bf2c85fb9bead6e049e066c91a941fd04dbed63e48fa10864b6739761ffd39ce81fb2e3104dad3db73903f1165b3640af4fce94f101a649f92d25152525050be5683a6ff20fde92fbd1a4ca2ee106c10e5e63b68e639f20c61fd1d1fdbb6d71e84d621a079a33925850211d4b8c64dfb7db3b6b7cf295b5ba1fa6712cd5b1bc916afbd6b95a43459895c4dd508351eabac340406726f594d5d763e582e2272a4b4751fa4c094b68e0027efef0583ab281481b7b26000bc719de9c28b8492d3925668c0da18053720e369124b39e06186b7468e75b40e1d22bfc8734b7834ad80957410397815beaf268148d6012b8193089961d254e56b9aa67992a6699ae6533613c08b02d66836c660682648716242b474d4cd48b5e8b12256b3e185a522b569f2af9757e6d102c6a06dca3238016f06ef0bbfc1c1972c06fc06df20be1948db7b059fc1c17aa788ce7b595e5d938d3009ca6bd94dc32b046b11697001de24305a0a78b30dfdd5d32f7217acd9de9e991a181a132fbab49868004789d0510c369545b394d63c693cad020ece38f2101fb0161cd7f29e80654cc014bc46f4500e9b82b58c0ab9ee8257b3106206f9a6f924c17b046e5f1a40ba4d2cabc53130d90dd844f24d9389460d6e92997485b9b1c1ec0b84c74c0fd1527946005ebdb503fe01eb1c9717073c8020f084177db3dd0c38059cf37a9853d4161cd5dd99d4d70c90a21391a7301a9d6fa6c626f53dc2e66b04a5ed32dd0f3403bcda45e6c25ac399d33f62194ccea4faa60955c1db84ec01e71ca68e9ed294652946219df13cc69d8f23f0beb17453ac834b58c1c00830205f181a892ec107ce3d90c0333003b860df04af05ef0254887a208e665541648ee28c762e7829faf1d62404c3586395caa4af699ab1b1eb9b5a8969c41258c7789201cfe47d936943caabe4cc6455765f3248a01be104028c1f1c466e633e479a9f6fa6314cdac9c2ab8ff21d91175df455b60301674c0778730d4d97c760de22c1000b3b1e24657734d63ec8600a418303049515d3c4a0294c7bc49b9d973b2686f1714541059ae59047f5f8ba2623e293b10c4c92eb1a50a6f87f3fc73175df55bd5c4c1b8f9652931d369831a2d7e27842f1f4229ca09c1c234d0cf3624ccb9349833bae692b67f9735ca4faee0a8a1417b1d2b83814bf2cc545a89e1657b51e17e3a2daaff0735cfc6a59719c69adb5d65a77adb5d65a6badfbf04c790c1445c9b22cb52e75699a5a9bda2c4b7284fe3d6cbf85a730dd423790334972ce39e79c73d2d47524d4bc893ebf498ffaddeb0df50bd71f6484d91794fbda73bf8764164f5310368eea6aa8cf7d053e6cd5d2cd8ab70453773726b7ba0551db2166a365136b816cad732b581253ac15d35a046e8b245a26a51c164649862d82d7eaccb52284acadb14c37d89cb11c5d2b8b2e2a264e6d8e113be387bd4ada698392685a1b4bb8ea58a73716c887190e1364ed08af76ad841b5621a0d8b923b5a0d60689e707a9668d6bc2964012135997b4b12bd45a1d356080ad46b4da17572da26e92848658213d2c8aa3106547451292c888130284880eaeb8150a3b82c1265171e2b8a4f5168de8c2814bc490e1b176742cce5bcbc376422d8659bb15c6c25b3ab2da362af6ee893d7154426815d2436374d39e19c5b459782b10ba15eead4d626b7ba8ad6d666061ecd216912c2554acd2d34ae4684aa845ea8129ba614d14c5c0e159d1d5786b6bd8da1d6a5b66ac8f3096c6560b96b526a1626d9ed8baa39684d04e0f2b03df34c131ad11cf896ec85beb836d0da2c64a99a185456db537597454314fadd1910821d6ade7c57bb3dabd31ec0c1e0ebaafb7968aad5da1c67e99b14761ed8fad3648162b47c5d23c291dad7442ed909e36a21b128862d821783d746d9137168b8d75a1d6ce99b546c25eb6d812d4338b35aa27552b03d5c648629bbc297c5a1c599e211b9f2198ef66b217efb75e42755b45fb5308ee734f72ceb95fcb10856a6d32656d096ea25ed93c88ea6f200df6de7befbd77cb422da35a2895fc390e72fa466aefe738c8e8b36f90cf4f2135002ffc1c03e501cd7d37c8e9d03393e72e39a9b5d64057035d3ddc2c5a830ef566ecedc839c78e7debc66c4153024de1904049381c08080728e767c93118f444baefae90a5453874d8c40d36f187ce8f4387ded36e52b21068d0cba6f7ded9a2fafb39fe59b11e517d3fc73f27be1b6455fc3c9d424733e84ff3e778d37befbd37f9bdf7de7befbdaf6c3e77a78fdcef6d383c663a043d9104dc77577e5e2f3e68c6d8470ca72505a2f8ac605223e2770b7eef765267ad0b16ed870f1211b3f7ce06f9c06b931f1629f6e171818979b4beefe7d8c7f84f7e8e49dc91310f930b53cebff464c24ff386adf59317070764960cc28abc54925281aad1a6671401e4c6a21a0452a4428a73442569c24e1ab34f922d474ef5e7f898f44fae2351cdf167e6c8633cbe7c7745860c08b49059363879c92500cea2780e2d6dd5dd6797a8fde77867ee0351309cc19fe39db4ef06bf0bf4912f90e47ac7c14bddc9c2513110f34ed3778951db7479213949f2221bdf4e4e4f6fa78873fad64d357f8e75f0be9bb5823a43787470f0e894d5e888cde8c4a0d17161d4c992f13fc73a423a36df5db9f9e2e40c8b2f97a21a2485188811fc1c077a89482f71008f874d13ba9d266e45c0c094f47821b232922d3c399602484f959a748a9e334d62324f7285d592699ae6499a2609df348bd44dc54d92e4fe34eea1921795584065e9b95ad672519471c2520b2222a2c91312115d1a99119822356d175255563c463c9c4ee850d99da1d1c518218365044c8f9a544cddc854f590bac9f17089972e256f0da92b3294cfe047bf4ca174a625e49a84de56dc186303f2a064044a46d09b8e9606658ae22fb1941871346d44c58b381b5c36e6def062e24a94f5a8c0da29c9a1b2c4b23113e0f9165392ae33b01c533e8e60726cc3c248b5a4882cee2ae6c7c7a0b669c5a4a8a99b26d6dc37cd1445df4ce12934a7f4de7bef0d90537b07aa4192244d4faa64b6cb215602450c1edd8fb21b24688c4c436024c558c5f4fdbdf706cca95925cb5c52689d39831561cb266aaa384ed442929ca72b44929a97da90b589095dd51109230baa364e6456157d097e8eab767c134dcb88ba5aeb93ab70eeb7560c9abd90a855c27cc02c37558c620da8e45ead1af1b927a9e03ee79c7397ab949892a9f2aa68522d262699c9a74ea028fba1d8b1b9ca0d86f9ab840a3ebc21722f3e323b710b24437c2cd911f9805191573d1d5a4a85523ec65514460a287ef81813922ca7dc7e3965e497cfce3eb5638a2c889650067f2a040cba4421c9605d8981295969e0aa53348458e2194642b4c8845cf09fe329311e4f897117132d1ec86369d0928b1a417668b852a09bb4bcb999a9253596b82c45319948a1648294cd49549b2faa489afeb8244595f528b474cc2529caeb4ff8398ec22ae282e4e3529c11d6c346c887169c873bb6172432e2aa9d36d26d424ae2f4993fc7264ea84b28706b20d3a0d65a6bad49cf94872e44aaabaa1b78ba28fc03b7061e2cf3e624d9f7f9e3a0e4c2fee38093bd729ba8eff780efa6727f6ee226f6dca350819b13393611f4f90f023a13b5d79998bdefc73d8a729fa5d6c83c984066d980f3a18be16735320f2cc07e3d6cc9643c68810ba0003308fe1c9b80f9fce718aac97783df0612600f9196acadfd51cbabb1220123d26a435c5145ed24b16c433e3deb0fa8104d50c5b8246c521c7b04480ba463958b9584b54b30c254b0404e942db561efd80876ea0987c7534cfba264c8085be4c63ef1a071b6becd6863aeb4ac3314384d308ab07c581c4e3aed90d34c7b95c7e6a063c586b4377ab44f686c1a8d1e31da142d5854ed78626b9460df80e49a8eed4c93b145c164cd3b620657c3c67aa51dcbda982f242ed8a8143a26a490d82042ed9b887606930d8bc7f40265d7121561b37eb02372c499b525c86855bc80c362e9a46428b53c88582324048dbda8144bdbd34986666c0080022318000018088301499223318ee492ec0e1400074fd46080603c483c1444e26030100883612806a01880011000a118868118042321b2e801f0fa88d9c4266ef25b3125ceb6f377b9624ae864b5607252a5fba5bac39c02852af3722b8b2ae1aa1209d184514d6f540391c6a953e4cca42f79ef94cba5aa34f97d827eba08e1019936051d5809416dfe80b90099a29ae5ff55772bc1632108f6043fa6da858003c0d1c9e84ed90f2f3c7d9ce1b4772bea188f4556742a1e122c8932f8ba25502497de4beaecc1e190d2de1c10fc715f08f76623052e0331a17611df5c69d7f2a8bc12f843144ca773017c472710ae1fab222dfa47b1a6e58775ecb7a27f6778d4158ef78231820058d5af95acd97c6492ded38b2e7bdc8d3906c9f4ea16fa1cc25bb6473756fe8f40a90dcc11e7a4b6d0dcefaddb02c70c1889083408aca6bb366de698709e82f4374ba21996db058f39a7a74faa4e60b88d6ec8e9d3c683f47afc777ef8a31691ccf5219242b4c08f0c31b8e204f489253d203eda0aba4147912fbae21e83868022e7d244e37d5d5f22606bdafe154750591aeb8fe90dc6e9f5f8712155e822887061163093a42ed359452d3a2cb14f21607b6c64e7b85c5b082bccdc10357c68678999f201b880f4c2219b16b0332de8885905c458f726529155fb06bc1e13b4172d3b2e79b3ea6f8bd2e12e7029ebc154efa1a7e3474abdbd7afd59481b854b0b873c18a47fb39a24dceca73a05f8cd34abea649f1cd167825503e935bf13cceab597d1124f1c020415c4a50b4c1282b03a698280cd866ff1ee6cf782cdb8dc434c1d606d2e53c5b7c0d6e1b4cc11a1a5c4ae3c91ace35009a57b9f3514a87fcf95eff129dd55d1eeaf5820c9d936714cca80e35c3ef868be166be6f4c3927d975bee1259787a7c39fb450e55b75fcb0676377d3450e901eef571de19e2467376227a9208788231b7d4c8a2581a453ee48aa56d5c2f577569e0ac2057a57db3c214791cfb687fce4dabde9008db3dc55ac73a86115a04d56cdcf684517112395e315fa2a1278606fd710904c11b8e53154895eea440bc3c3c579c7ac0a921e191a9e520cb31ebc4c2a39be88b3b7078d165f70dc7ef4bef11133723882e53380fafdcb8bd4b34b1503cf72d560a14c022b56c7290d470eac17b1034b65816a571d43eaf77f59404d4a9bce7b1f03f000caed32988fa97b35ccc1505cd90a5c242471b825e79dea381066259e004fc996a6a6e3e2e2d5e985699a6a9ccae457cc41cd3f7089528fed33dacbe5829f6623e1e1ffd2041d3c6691107f119e9231f8bec9eda74d0de4d6d0b4c2e39530b1d72340f2cd6b642960ece433be3c53cf129ed450fabe4d245869c6839a7b71273a425b83cad0e9be2461b280eba84e2541bd0d1fab702e96497394b4496228db914d11ce792c51d4e8fa1642fe0bf392ad4b3e6bbd50fc907690462fe3661f95a6803c90a74926534ce7afe661612485435918ec4763e367247f23188f0e3a6b3da7a4fb6e4f90e8397fecad14bd124d49f91d0a41946dbed32d9665dbe8afaf3b354b1df41c59027a8fa08a9930256c0db0e29a28fc3b438e625e002c748482f7f19d4350b8d653cf8e6db3e184465cec367f325461917d01253556a802739c7502ffc52a7c8c756c114d57542cc5f91fe4709cc5e365d0b80c1debb4709ae3e5e21366143ee0cc7130a02a0b42cc4947cd86afe59ac96d3e48b07518cd1aa21262636d41703399c906a0d3179418340e676e25724aa92b0f1095105d80221663ee77a2cb41404c98251b987e76fb2888442a804ae05df0d46ca3e997af14869fc1ccab1cceed6d9b45835ee4f84d1732e8e704320023e4871e19af3f6385dbc787b2b755d79b78a285bc24e3edbf4bfd0c14b41bdd677447bdef9b4bcf04e60ff808b13de5e509965450e83c91f8cbe636a0ea5a32e4b7597d480ac627bba3b1dc0e4895c52aa3c124564568ac7f8c7838cdb9ad823f7fff6e5a2124613a3b1e42a21c8387528a1a33566e97456f59f49c925fc683b5bf74d1afc7c1f49765b64ee2a91975ae7a8218ca81d1ca1e31a5795b4320c8c014dcfdfb6fb62101553bd9c50fd4dc696e4e494cc00d07404dd474b1d72d15131cd04ba29480a8db3e17e77da1aec072e12ab63c515e18b2030e9828d397a35854fcc592c4a80dcf98375fa548a0c83c295d5da1b5239ac60b5731be5e17de846b8f04736c25c47399b0f9b74aa46184da7de35eab7d966544bcacb6ea78fe2ba191531c13690e4b8a1062b12531d470d1bb83ba7092a05dcb591cf01858d3f6e11bbd5e02b95531311a8ce06c685cd651bdafc0726689813206bf112ae379c815d0a82a624177411d706916e98acaf7c71cd4d26c8cc06da488a751055b4608f541d39b66044cb22977a4c53640843d03c445d1d5dd740718329d3c63060b64b9c230ad0f378c3867788197734aa4ca072e6d24936c7fa5c2924a91179b7caf5d1a6446946e572088ae6155589e67b698f39514be85844e1d914e9540c518e175c9b076678430fc828ff0c19e212b8609f11b10ea35100f7b98603d0b5f6e957f8a784b78a5c501a861ddbf72d4d1ced170338da5c8c9d942ab4d05d0e07447fd533d2d5b599fbd4071f2d98e1182d4ed72b4a4de642e6d253d61145dcc3b7b0c1aca01fc97ae4032d6913d40afd5db9e69aed66e4039e1ea6a14d08c226a62fe41a989e0d58d7b4ac76b284abfed231b287bdae97d690ddc3ef4b64ca75a543a7dad8063e4d1ac3c62581f7815ae3115341ad533a7cb4f26835681cda84302a05f84935e28c50d9d231d686f574eb79b6b46021b1c151eed1d860e6d01703c6d3c3ac3ed6ee14f434eb13e71f6d9cc7b214a004785823cf501e0761abe3861049907205cc39ec00ddfd2a2590f83e3d6963e3146d045c833314d1ec56bc94562b55dc3d8fc1227f152cc12ade6380f8cbeb5ca069f02ae2cc179bba82ecfadb598c9b1f5a089e0ee2b3229f6cafc35d89a2a184d796ed1d41a8232a957574e5eeb588d7cc62ed2aa360ad8680460d3949daf09f5fc1a3e18ec3bd57f02a34ce9d50693ede928c3d4b53b94d95ced1650158489605e162d792daa9642af2b997168c534a413d4d94274694ef2545d1ab995b9364f438686e4f8dc5dc28af82565ffeca8c522b1b03c81f58636083e23921f6c0718fc058bea333fc7e933e422662e989c3dcb4d12b07c135329c1b548ca85dfec146359b503b6cebb9b1a5eee7c4cdf0cd3fb65b04522454fdc6298b1f8e4f43a2eec1ff283abd454e5f4e184f5f180b23107f784c530629f1b229cc08cb2b00efdcc8c372f0614fcf6c1e287ab915e37f836ea23ce0170736f94bc8a2d6ea3e134289901b4dcc0afeace20a512c23d675a3a408e116f665aef220ceee4f950fa2ff9938c10ac390a09a0f18556033c0582704ae35ebf306c712cf6b69ad7c80c202acb889aa9816326e272104f9b0ba4744c230a4ec2ee8d05dfc317b057bff65b8d3e1c8ad1ae3cd4b49a56de3478a9f3e1cf61921228fd28c6eafb46d5e6935fea1f61ec16f32f8e442ead2ce70749baff2fe056c4d33a2b9d7042b34949dd5bc3b274c30c1148e0cfaea5f8e81c02e6e829e8138b3e6022d06bc96a320fea810bc866471597657c3b35da96984ed7a7f18c38c71f20dd8916e6568b7a2de3d63dc899e77191d142466a61d1186cc03cac48f94e9883e768253e303948ed639e23ba1376002609956fed56c1c18d8ff4667d36d406946fc0574b3c181030f37e369ad9e7405dfae499227083cf76e60d40d02d9ea9d7854e3c5e4d4b88f057f285519cedff96cf58c150eaa1ddb6a3d15d447713da77b66ef839ed936ee57db1298935b5df4df6b18f890402e66466bd25dccd64a63e77a994924a9aa3d5d7a1cc88de48dc34c17cbc848af60c6bf49d55bda413a064459548ccc140c141b58c98c29eafa6d7bbf8b9df28aced0e870e2c241c78c5e6410e3299eded3a84e92ae4c76f0a8d1a8feeef24acb1e56be7365f223712bd95dd9a6eca3e99a7a1715a53a8fd9bc22c2a600122643f46a278b02e497a868e283355af3a8a07bf893ebd5265d2e51c9a0a25f954a54bf0ff38962095642b372c4e6994443ae163722566ead12f08ccdc7e4f65244b8c236e3770322f5436dbf2fda073cd2b81d7af6430fcce4d54e2d268de57eac5565624729b256831176cef09ac3b80cde1ad6ab45f3b6c09b69004ae5aca169cc9efd40f744fdb34046a36f5370db0b73c804dfe094a27918cda7b877762317b532a6ce8660b2333800fbdaf61b3bba73d9e1e2bddfadf5033fb56137f81f98fde42e87c965fd5f694d6f15e5da33cea95f9c0436d860bafd5f4495d3fe52b268bcfe455d31cb8b69874b920d2b1fe6aa094d104263915737924a0dd4b00fd659070bd4cf54a8521769d22ece09bea62d9c5fd8075bd7d64b6baf2064ef6ba22079b918399136c537ad7f2b541ae36e88042aeb63eda74b22bc53ecc55dfc424fa23d02c371936f11ec4f1ecf99d6f89d82f36dff29e5e8bcaf7dfca65cf737d99d1c3ffd7f79534868cbae0fb6c024ca4eea87032e422b3e90bdbd899add83f28322902171447b2a75563979de5ba2a3805c0b17dabb5f54de48f14642201e445ba16605b7a73def458561b0b5545f6dddd33d7afdfa0a6d81de10fbe3bcf86fcce92d706dbfe9d07086f80c4e88e2fb137441c51ce2cd6027b343e48261539e397e466ad9708b8ee0250dcf590817dc766d8f006b5980e3602d261be5935bc7cbd97e62376e29b4da3b7c79d0313894cb39034fe518c05b09916e732675d1e987ea7616aa83abfd6cb7aacbab57655b6589a5355d2813b27a681804a59b4d8b2ae1f81bc84a72d25a127cf52f997d4f223acea6b0400d5539ce223e5bece1a591d96346b6eb5e4fe6272af5aee777a243206bd7e463579264f1256ebd276b0fb406935df592b15bca41de8009bea53b88b33122950a980478f1f5461cb27432c675a5b1594f24fb6d8a31551c21dc3808a1c13447086010a091ab425845c7065f45536270ffdf0f013b1e6e5ab9948153097bd59ae8bc0e7468d3542e901e93c5bc71eb62570e742c3281c8bcce54ab3b35d15d6dc93baf1fd499023a79c43f0c8b1c07a2848c9911c88f912fa0ed61bb0b14db601149a04421a12290df3a1fb912c7489cb64042244f1ba59c16481b9795a3ae0e1945d13c855f1d73acd34c8a8c60695b1972ec870c10a4eb231969d3bccbcd51af4923988e8b23221d86286978043d1d65db691b19c0f1e140769ca8e3580e4d2508cc31e2a635a59623d4a1c75180838f038923dab176083bb8d951e44cab97158e453bcc17d10008ce01948e21afb46e4a8dbb423b21fc8e36c714b959c39cb78aff731a79d13b3ae62160e0c280b64b95e3324af4b2b1ae6227d37539683204481c7161b40d2ae803a979cd777d1fe3a500bd3dab325188eb1c6573b4b80a899f0680e03baa1d4e47ca01d0e45a2e1aba60a1c081fcebbb98515d32924f5b0ad8e1c49174a46837f8dd0c49bc73301c6fde15db01b7903dd3064746be348d90b248daae852498a6e3a2b4250d68646480562ea17790e0687d29657fb440c98c834895369a2c3bd6eea8b34468e751ccde6969c95007fa7c1a33a176c03966d721cc24d2604aca62d0b44428c7b70359bbca4f0e1e8ebd7018f2a721bf909c68b964bf1de8b769a89391415aa96c6b8d42770c88e3d82d0d9180e7e8ab31a486032c87de5194a3fa8e8e07453b14ea858cecc688785cb8c63b5933939c59879878e478b6e39013ad3659e178b46369621a014b593cda522ec841c44ddb462942a51de5fa1d6535689b9291304d5ab239ae3810d241b2a0e1900f4733079a721071d2fac8d951f310bc84053643937c7d6be4cc27e91fba0991192dca8b211a9a3464844a33175ec70607a1a38303e9d068471151bad1ff1c4e71bce36d252d8a41e6a5a806ede4629d330fa4fae62e32dfd1463f8d2f861cb28ea6c3b4e3dc8ef4f534c6c888172d52928eab1ab96c72e072c0734497e3b40bad1c89ad8324475bab98e64b032488e12884426351315641d312147690fdd1862323c1e0b2b3f33487488b43531104ec2071d18629905469e7a4c7511bc3fa3832e9af919ce5f638a74d8ac8f5f49f1051d0402f860869989c8c6c696d511d203936388a6a928277cccd513692f645ee396a1ddc8ea423d2a1ca31ec60e280d31a09bda0bab665b8691110380b529eb622f023cacff2a20d9a562223765a5a421cd1eb6a8092b22d16ed1bb1aba5045c93253cbb7a2ffd9c682dd159701933830f388bf4cd31fd7133980f4e73a12758051f982f9a6da2e904bd967e07ec1df650489310ccb0066cb62fa16fa23cdbf985be4542b315b328960ea67714e9d1dad27447b1e034373139081cc0e11841d3ccc29b638783d8d17c67b6602292a675703f4733bb10297f875878631199e978918fc61330390699a145c925c7ce263d42d45624ec1c6ac7908355ccbdf54e5bddb100af9a3469dd94b7452d2addd45636730e64db3abcbac3e114c03a3c5b4d13258a0eab43a31173f49eed4b46129045e36bfa528ee42729a337234e728c409ce32bfcb90bcf399ca00f3b13960e961ed9709c9347d750e6995e3f02a4db1c6597b44cf285a3e93077bc716c1df2b568413a2c38be0eb443c71c6539682b1294a35e384802dd11b698f5655e41a86f96474918846fc062149350cf301a593d1dc5f24ae56b03d31989fdcc0742df68b1d8b5f98e9e426c462962625a1fcfe8932da2f7f144de8357ca65d2c8a30c306069917525325e202362337b159e42cc5d838141323b1a53521b44843582a660a747865a5243251dffb1c67e36b07b549a30b9c1e8a8eaba6afb0683c063b8b7ec4a479a4776c0ad401fb33b5490d702d89c6b8381c3f8422518f0da00a191a15620cc9f6545133b80e48588b3111760587259dcc1f4dd691b3914b3e4f21e49bbb5f94802c789a97e286e2a226bff3d884ef4023639d13e5a0bcda23e98162f11af665cdc0f1f282614a79991ae925d8b7579a4da2de9ca2d8a2b64c34b65ed02333ec1cdc43a4c821e80858c8419940aedc46ee8488b0a4570fd7e987c70e96b0453baba909907e31da86ce6f03829b20ccf1f07e96157c7daa25ed4f82dc00f9c18584c99bad6585a6d37c2565a1ea4f2c0d0820ae41f537dec3547d04d3bee90ac6d1d9d6df03610a044ca4a0fc4981169bd44cd1e72ea576c9ef0c72ae5b2a27a50f37b9468dbf3aba5e08991f59dcf907f86b46930f9e7a904ffe60c15801f3ca77574ba740b7a99834f73bd8c671b7231d7db35fd2d6e09f2b35fd7559ee3c05a593691b4a5d0d4138e03e3874cad46da2b26cce149d5d70ea3f522f6bdf686f70ae0715290bf16872850f6448d12778d8ea21957a8fedff0fecb96df21c771a6f86d6358d4a89236ca83ed368cada272d0d66d94bee420516ccd6327173b65e5de00b1e4f926a0a8854f7b47cb08859662333fa9b51257c4a4a5ddec6bb4c763760c46943f8d9eb6037ad39ff0b933101f90a926a0a6f871fff3f62ef35e62b31f290f9589caa36e9a7ffe2df8f6ccb334cde89a3517fc160942e65668ce57b5e14c744fc699b6878d25c24d69ad0297a6cdce8419162d2c7b90822bacd2b78f6a2a610445ec0f183b7ff054ee3429d43916091a6645529cfad4531810ea9e48eaae559b2de832eeec2520caf276cbcc6c07d9a359112f79c574929d5872928d9db298bbaec8c8a6c017c6ef9409d7ea94a73902e1e56ff167eed2a38fe32aca9c31ea4676d39b44220e59d62a589d3230a2931c37e4ce23c125bb72422f28a2602804755bedede7519aef4c222ccd255c7765a5b5fe8e599d70a565ac55000d5e6eaff9b05dc8b53adf9580291b04a27bb81cce7658148bda2cc7fb33bc194e043157fe07308a5b7ea413d5c3622620c7994e5464357a98db8a1a1a2838858a198c33af95026e1bdbdb3dcc6c9daa24f7ac629b568347956dc3962d40581bcdab175930078ce8242b8f52a6de942cc7fd093728d65c117728dc18e031b979dcde7718367c34107880132b6f350b4f01c247a63a2fd68f64c73959f3ecd65416a1a58e2058ff6ac5e174b5e5046b6171dc41cf0577d4072c5ae0376d99cf99ae8826f271cfb1d8c4c12242090311a0848472756dcd09ba16ed45953726059c2c4ea1325823911f68f9294f9f0b36b16f422ef1be8ea62280bef6cdd2ec8812a3eeb3c04773c56902e253b07e011a308c87cc42a49e4cd6c011d4f95bbeac1e80c282eae4380f08a552525958958d655c5512694b4a596907dd159fb26809809b5e1f1e2a0489abf84203f0981e1a1ea7a6021fae92096914dc487d9c42555c0d4eb180dac842258586bee2251dbf4fdede971e444f581781b10b3585a9e2445f1c16e6cffcdcbf4226db6f50a6ec9c96c1eda2ab5674887336b4e881b7fe249f65c43895e1fe4e2ff823e8091b405db30bc958c0b3aa0e7c561a2c975daa660672294e96b9120470eb85afd288897af039444cd38177c807422868de667ce602fce60374f412d9c8b0ef2f3ab341a96d8c7175c128918185c785b0b70fb51ea462ca3126fa1d1de0c169abad7814d1ea41d0de1a2534e9d0180540215fb1fc3227caebffc977732aefc7654725422d1548ea10e8b7efa09973ffd3fe75494e791441f3648b6a3ae093dcb9a282bb1f7a35dbdccb321c3700f41c500a406814e6f6ecf806d6eac948a85e4b0c33435c279f5731a8d148c168cd482fbfbd16df1b89a899646121b61312843c08dadd0aff701c4c23edf367c9d70352041abc23c00e449910df1cb903381c8342a4c4ed21d60ce1466ecafe686ca2c1f0cfbcf89e7871e1954fbf67e6baaab3826afe835f19ce03546ed4e32be4ab7e819823deb3f3910332057d5963d8e8ff768dc03969cf036ba1801b5dcc017bfb9aa9de70d5fa490ced695e29900ec339673488917ae9223d6617fa48bd92d2f6955f14aae00029311a628cddfda993676735ee22f8ec430d5f2cf39b3596b984fc5c399388dae81fed3d7c384214baf8e6e53178d2c8c86e2060bbc23372cc0033900a446150ef2bab12b606c6ca281393d3312436aeb26189a171a8c0997161b3d57786899d16b9141cbd140218c152e0099faec92fa7eebe9073a8c0df8c18d240ce3375472190f9379800faef5e0224b6fa41c0dd126f8c46639cedaa94903ea03af43a15b31ddc090f221293bea3593abd58d4662ad339a9b3b783bebc4eef3edf0863f541d41b6fe47b5e5a354a86a61773c2d091905dd84397a91e2fd11c9a91045ed31a3ae98bd8eb0639e59974f7fde0b169fc17da925fdd0507eda23ae56ce20a8dc7169387a13c3de87bd13b96ca6f37f7d3ba36efd8e9bdee06532943f7f17f3359b4b539046231b2a492fc6a2b66423b55792b986400e5ab4b83496da4eacd7c8bfff2ec2c164f8df5b871c035273ab61a3617a4a8cee46661c1b339387f1f0a7436b23bf14f00f273bcbe0f81dac72507e90fe94ffb0f66e75bf37f304182b63ebfac405686a0983abb03fd431db371ffbad540d293550202dd0fc4254aa2aaa023f9b708fcae47cc470d9a9b0ebe2b82c9a75050d7513a5b3ec2498f6f25b6e412dba596a369d87a302de615692d34f104d36efc075b8a15ac7f771a39307843a4b1426a1e5b2f45b6d0a00a5014eaa1df925df2382dd839762c6e2c19453f99be3f00621296f2095a1a0445d2402d01be483049ef0d48cc7b2b8d5265830381b7efb8f83a7cd432332b290e77a8011f8428d50c42090531334043de05380282639ce9f41228f8f1106de23be5e0424dda708add13229cfeaac24badc282593114b61f8614e5c73672c9a43bef94694b82770c9393ea09fb41691aca079a9377dff2422a063fad508fe8b1fc1db164fea72cc1d9d4c716e4949b169af68608999494b2851796aa73cc665a21a394516a957548204c7905c507467c9cab076a3aa4e2cca65e2c2bba6647fd859479e7687199815a0bdeb90337cedbeed15b13f76be5cb63eb6625e70674058c29c91e321107b1c0a12ff76ac71fe542d3a30e0a9e36de4e833a5f74f7d287a0e225d9edca2ba4824594575f543cc1260a1ed440af351b19799183db8de351cc1675b49c855ef5abe4ae8b6a76f1d83d231ebcd576e97fce39b04f67ccc3b9a435b336b458dcbe7d88a0b2a211082de9abfc903e63f129a201a89cf9de123356ce1a9bd8b078d61d0ab4f908880672862c854916916d64bbaf48172d28d8cbe272d7706477f569447058c81580e20a8d2ef693ee6704402cb7523965c44690ca9959bd33d9cbfaf1780a26a754c2905b8fd94fbe3d5e21e4756dcbbe3e893d47c3e757df270cb41f9d720789ce9b345c88b1431c25e7389249e7b49fe4361e1f7145ee33f620c63673f065b049886e9006df39f4ae374846e31b52e1425f594145c9f83abd2823576c9b277ce6ab5a9a0f51041f083177992d4ae45622e57a5a76ea55943af2cd936f742caaf6955a6fabe2b7a761da890be11cbd77d94f66a8c0ff894c46385cf0e52338b3d33969c2435a771c123d6ff51545b85687ffc6c8f450bac9a4415ccc827e4d55dd3420742dbbe4e6b2379498a43510d484028aefd9655f40503a4e0755203e33911545b74d2323f8a564ced74e659d4d3cf31537309cc312a730786e85e75c15045f91c44db1651ca8b8e2585ccc07dc93114bfd38c10aa05823c815c8e806d0b886d88bbd9827242222b4a4edbdf7965b4a99929401a406fe0522063ecbf62b0e6dd243b632199662a51f259d124fb7ba8b2146ec5a9834fda51fd3a65b61967862e860e85ee878743bba15742e743a5d0b251def58e874743926ce0a9d0a5d0a130736716c49c707d9cd90fbbb58d7587dbf17c67c29e64da79857dd4f89289104ac7cc72231a3c532693c4f62b61acb6cf5ca72c96da3c8fd96a841e8b7b05125daa159d39f12ad12b3a61f25da25664dff49b44c88368b6da2245a2748a2d552add0275a1988f6caace9bfa2c5326b2c6dd6f477a23dc22221da24eedd4831a2c53269fa57a2d532697a1ba7d532c3214bf31b3a2d8c56c518d9d2fca6eb6c673bbbfd7db2dbb6af395b9e62dec6168c34a34fbf244bb5f7c42bd9a8b33ab34129a510d872c9899ca86d34f8da3c16b351abb95f5babd56a79fea5322ab3b66e979c349a0dec92711d8923f7fbabc79785cd1afa76825e673931728f757697d459160186f015c35f8417ec3b5991b8eb703fb3a6be4b28b43ab976311fafdf2b4dff66b11a6fcd71fe8d67cdac99ef955c388464cdded4469b727c0c67cdac015fa8eeafef0ddea8a32836f832e1df5e47f71cfeed4bc05b37626ec40ebec217bd41473a762995c9105e24127009780ca149fc02b7afc51778838e1bf8f266dec09c54eea64183035d3184b4452efb4871b75d5b7d7647aa20a0616e8c760c93555071d8dea0f0eabf6cf69f33cc24a5af9afde7c47356af60055fd4bdf43a480f3e05c75208be08fd7addec3937fb4feabd66be77cc455d6f6fbfbbbbbb5519bfe755a7eae4f9eeeeee157c6dd57ffc27cfafb5d6fabab628536bad7dd94be7df7b5f75a341f3b7ede5d97bd74e48cea32310d030b98eae9d107c75d7624c22efc5ef8d3338df9bf3bd77e3ebc9b372a25742212746c69f1323d75184be31b63a94e77b604eaba0e2f0fa5b0106ab4fc72ac6c813f01b0b9b735a18cdff459e223cc1ff336d02a6ca4deb16fdee22cfcc563f5955ac6eadc49b49d3af125d93a61f378d99698380d9f2f7ea8336bc8a37dfb8d9df5563e290be9f0113a7f4fd0b9838e1f72b60e298be1f67e29cbe3f011307f5fd214c9cd4f7dfd880f0c1c4f9be1f01079838f7fb0d3071b6ef7f1a1307ff7cca009d075a800e8d11544f4e21b2ea73492ca2436384d593c4223a218cb00a4dab4f718f124f220958991b8bac46ec34a609027d8fdaac225599c680409a1a675497e99fbcb02492c0d0b3ade9389ca948670dfd4db4f5da8de4bf1255e2749a0dd5980aafbe574512eedb9f5b1852908412ee8847ec346132055f349748da60635269a2f0eab2ffabcb5da261f2045f5deeb203b9f75e5c6f1023456c5f2f1b11ef4e077a8022d65afbe12fb31b686103173e3f302180142d44a9291105134c96a84ee026385ce0a30658ac0852e54a14363b4cf1f282225e869eb4d0fd98502862300401d921080b42b628aad2450b9638a10312a46f89113a82265d62455820841519904a2d36042111cb410913a49fb089fcb032c7c124a4de1ef04890878122528468a155d1b2138609213c3c1105480f318c604b4ab880e1a103b642173c545041c4972840b32b868a18c15abb05b104f6ef7698551f17f8b66ddbb6f5b06ddbb66d61ade2de7b71bdb7c80b3f4636285122872f2d58c10f3bb11e2f465d980021555875b2c327cb0d52aa748165470f1d6278c2880c31406284bb448b000061a4c2e8e10a0e5c6e60c2420e270893a48995245b5828024c72b7e766a03f45dd4f96cf86778b98db4304b7e026f99e9ecdb91bb87b97f4d8d161c98e1b040fb40898ed873a9d0042872d46e030c40b1419190fa513584c5982431046d8871e97f9e4ecb0010b1c8274f019428a142b80445136914393262e94827af0067270820635f0a146a5070986604228880a5b808a2c519d40820a33459229acd012248be944902542558e9882851e3d3a08a13bbb3b321092a4052f4796b811c2c4cfcf112c5c808192214aa494be1e5ac90d384890d4f5c729d01e216018a26746c591142d1a7882fcd082213c7238950414a1658523454811051648d08c0021030f4a867ef878f0cb640a135d62adb5b64a0a66a0820e46b4cc404393273e3b4d68c1c38e244962e0b7438fded1c124e45d090f064c864cf60631ab9f4f91273c6badb5d67eb847ef601d86fa8795390e24d8212684a4882341c050c03264040b57f00064b405a1c31d79c7e2566c2ee80e8c14b1ac2a397069b2d3c54a0f076a447686e8896283229624d1c167a48847200b248ed0a2944aae98618608b9520ac1e30a254cac7cae448f0da4f0d1430d4396b8228a00cc00564397253160f992e47b8020dd7bab77689b60d245a907ace2c9151668d8f28317205ee082082aa4d430250993f2f3a30e3a2e6e3726f3ee7b25171021592f8e4ceff5f0f5cdb83797ed7badb366fec5d93ea5a909fadf0658ebd5ab65b1eebdd45e4ba9b781beb9df97fd7b4b12b0ce518e54bf46e08e3334f7905767ea9f30e6a2f073da5ead47af8e1248e0e76ddbb66ddbdb39e79c33e319fb27e0bf6fc75c14cedc68756b7650077577bb8fbb7b2da245b556fb437facbd5487eadc8d9c433e61fdfb5e876fbd173f8742bedf3dd7a932fe1c55beaaacba621f6ddbe4facbc1d65679feecd9f35ba86fa63b6ce2d03cc8aa7c7f3cc1ff7d246dd4afb4bddabb61aef33e90540a4d27544ab58a61c9bc206dd87166d6d07f1767ec0863d6d0181ccc1a1916a869faad60a1300b97ee92fbc916503fe941d7ba6524f6518e2ad3d1964684f46d6dbb327f866ddb5c3a9ee8e04ae1c90d4f5ec0188309bf3c859ea4c024dc020a275e9c60b182932732d9baf2c1ed9cc0e0e3871552c0818b1d9eec54b1c3151d5694f8f0045f095d5c54f01243163428b1640a2c02e0a506a12a5b5850e3a188f739b95ef0e56cf5766205cbcc10217ee2520188ad8a93d80e273a3a217c29184e886cdbe64418b381131d2f4f21273faed019208a92d0c035038c710da125999c658d299af870c527d7086a422493618d2a9a08e170139e8c31c6b8736d0943d6d33c859ad0f24c9e424dac684294c9965dc115c415c415a4fe9c14e32f35c6ddaf1bb5ce1e326790ab4b375ab58e2b17fe552ec0a01b2363d10406f98de9045653adfe67d254e36add32ea56a5771b72a14c1f57a2ae769d37ed68b7bbe3cad9ee7ab6bbdef66170fb30c891ba1247ea4a5ef899bcf0338127122a5553296f9afac4d3109f4c1fd5f34dfaa9f09bf455b5d23f32027364d4391a560b02b2120424442b298f0f2099bef3501f74088dd149e9a4745213269dd43fc8b44ea04cf3ac230dda5e6db5d69bdaf66aafbdd79bdeafdabb7d77c31ce6386fca7d1be6b8cefbbceff3a69ff775def781a412a954f2a625d2078239a452f895c09cd074329d4edef41403be502c19f0a57a31b39a99f1a6339f09cc711105be689ea90f05e6d06c55e08be670a55aadbce90a93666e70769c329844a290eb93f55eea24f2845cc7a6750bb40206752ba85ba09520137832d58c4ca61ae4f6b35ece76c30545e8f37d82af188e33ba21e63a7247abcf0406996a4eb38d2b1957ab7db559d3f5cb0fa86f5aa869e8e7f01c60c0181975e9c2854b67c99249ae46abf5583db6aa81100354cdd69f89737f4e4b1be2b8e8826c8cdc05e94c5b5142b8e8c924e98854739afe8a92cd9a7e52cd5d67d6d055eeac344d7f17d42d5305567014d4916a41b9bf046b1bd2d272d78c4847243016d7dac51392358b198ad07fe6b34c7a3337e6db7fcd1bfef65f4e69b77f9fdba60ee6d4e94d6bb5cf72e738fb9e6d6badb5746252637ce365492023dbf797875ec1577d5bed28ce9a69c1d79da1c0f6f7757023bee0cbe2c81bf8aa7362aeeb3a8fe3ba12b6bf4fc236861082d3709df5582caf84edda8fe3ac7ddb8137faad676badb6473c6be89c5e07f6bbe86df40bc30f2461fbfb81256c23769a06c106cc6cdf6bad954e4caa2f42bf4040c3f80dfb15cce9723a14ba5cbb6cc7fa9d8b3337e8cf7f65effb3098c3657062d08e23c8b5c7dae10ecf2b7977035f348c8b05e1369276e6356066fcdc774fd6a1231a3431f75329a5947a91ff147557cc53e4ee750baab5daa3dc6fed256fcf88c1576fa2cb86103f47bf0d7cc096bff75edf7beffd6eb7ef74723f37947b24af9d43b98e9334012d77c0ce408f720088e5de70f6fcfba83d42a9eda1945e7b848eaa1db3d52a9d95c8fa1851e64b62b6689545ab2b8bb0be6aa9a6d3365d50ab76bc4741d6af5e87ccc77c09fcbe0492204389494303be6428315bfd2f8ccc1689239f4e3fef8c7d968d79d68802998f7914b07ef5384678c1c91076af7a7a7aef650cbd0c22bf91393de9f9fbf9bde89938f63be61be799d3f4933c977e96c8173da86e28f7cb8878d2ccec5d12a791acb96d9a945194fb6b9849552c04947cd1d3cae48b9e2c4389dcef89a48c5a6e5807ec2c8ba49de5fbce934956eeef5e4067eb7e7f8c8827cdfd9578afab4b48022086dc8f29b6b307502852a00020966aef67d67649114d739ad8f77ddff74d1293616651af33cf149648a07b9caa042acbb24cc57cc462b118ca894edd6c369bc160391ce6308739ec7af0891c9850c6beeffbbe8f068d54a6418386e7445c89047a8a730f5502955e7a65798ab5cd7dd03d0e9bbed9cc93cc66b3d9ac2600334454a6b1582c162b3dcff33ccf0412939e0c892611e7315c22819f9b30179640655972a517f3118bc5623127722e4ee4444ee444041800cd29d3b22ccb92e3388ee362b1fe582c86bbdcc0cf4b5bad61ae042acbb22c633e62b1582c56801a53a6b516c618635c6badb5d2d2bf2743f7ca72abdd0747f2fb52291830668ca407997471ed4aa0b22ccb92c65780cf158350867118f6586badb5d25a986b462425e9a5b27e3bc4caaccccaaccc82b5568dbc5ae36a0dd7da566bb7d6ac01541f150f30e4003eb91f013d1f7c08f03ef8a848da24b91f84d9b401c1c6c69bdad04ab969adff6e1919fecb72c0dc8d9616822a0210df974ac180316386071ecc26e5a6f98dcd325403739403663b5a1a10b48d238179cee8905ea0e6b54f9aa24d290e52c0408a0d6d436fee17ce1a6765774ae32f4295f06bdc6ed539bfbbdbdab66d45f016eb9c4da34a21626295a790941ca9396d3c9bc2b4b9a6ec9348a777cc96bfa7306dbc27bb2c7bcc69bc761923db1f96c716d9225b648b6c918d75cb6dec0e0f39774c9b6d67b6bc6db658b77ce3711a7f8cc390c57a72e3c9fe4162c9ee94626d53221101ca409eaf88277b4f264f3db6e824134db56de73424c7dae62464b678b6d2c7f6b3cd7af22643d1668dbfab896b47907e453f4020d91fac617e5d7cc4401e608e0fcc6754e43726a3a6f13781a9e5b0473c60d94f5727fb0948690bcafe3d9a6addf2771911925d4996fdab58e29935def1b8a084f42df8f294ec03aa8144d845ac0c954f4c2129b32c82b67956926611662189b3c3702a86c056eab356d2cc8dec23a981dc31d41042284a91283f518c4409a2e2aad2eeb59bd66ab5da6e6befad52a50ad6010404d45eddd65a96d65a7befbdb70cc9eeb2562bcb320421b061d2cb26f168b454cb64a85369a2b11c4e22a2922b4ad8b4b6019b0a49a1157f79d2bad5a6e977551192a97bc7b05b5e67297dfb369cd2896f525d71882bf96556ad5caab2fe5b21b5deb5e1dddf1caf1573241c84852839d48812e547941d61a2b0f0419415a07cf181d204ca0f50a2c0a1c4c99427a4dc40861d7c3049b2440d5480c187280a1426840881628a215c7c6ca161e39144132d38590114503628472cb10594263f4011820a28436a348102048a0fc6b88616473c29227c42e5c993274a9ef8f0c4c8132e5052786244c3a43940483056cea13371aefde0a88f8ec8a6e11f1cf90d2804cba2c325d68527834632104c4f6e2f470706b95c40da5aee1a9337b103735425246dad036373d43674acddaa24989037d3007ffb2f774fa57a7a771d433a437ffa9d359bfb66c310638ec5721a7cedd7efc01bd662f730b46fc5d7ccfef6fdab7df5d7f7c01bfd39d4ff357d037330686b77c49b33eedd36cfdbbc6d768b6cbdcf3c076d78ae1dc2553e127a23e9b3bcfd06ce70de73dee391286fef6d9ef75ef7df515051d1cf51deb6a794524a3b286fdb7777b75b9ebcbdbb7b2daad5fee46d7b6befe68d2e1f5c3e9070064712850c921e67d248a2f02a3de91dbc511a87f2f6a471f3c091eca3bc7d4fde7ce8a280963be0335a45c741a25eec6f30ad129261aea3076ef9d31f0ccee13cdc077620d37d68ad7be4f2dc1ec7c1699834fdf7c8b4f19dd9ea9eb6f158b770f0207ec3c421f90fb7c16b701ea761e2f0f8cd9df90c2e83fbf01d6781f7f0d8c4397d5f1e1fe24472bfcbbac6ca149654a8cbe1adf348e0b7bab33bcb6d0a4b5995425d0e6f9d4702bf93292ca952a87b3275e7b0443309ec38bcad2ea9fe9663aae5d234fd5d8c2c98cf7179b46e081ecbabeca32572b920245b7dcb4e0aafdfcb21c3d0fbfbbc440116ab7e9fa7523865fb555fb59672ea0472e8f04a36c6afe44ac281b928ac5f5f93f6c64d8eb6577b378e64391474ab3ab946427a42ae3cbae5321292ae93ab1dead67db269354a2985c01664a356547677374f1795eeeea08d5a2b8d594b7588f2bde41c72175d3c84f8e97b98cb9941216fcf8d240ad9e29fe08b8e38c727e8b88f7254d9bece2adbb169ddaa638622f4ae2f3366b8b4a875868b0656091645508666f8520be23048a2a33385063d9c7c6006430061d245861eb4a82a38608a18aca0072e41642cb860c5ac7683293208c1a2bf52005c78a122054c901f2332b4dc1ff56adddeadac137ca9f2cf7a37ccd93be9db0d739d573dcf9b7a1715db6a459513e47ee33aef9b614ae9a434d35929bd97e3603ab9dfcbc123f77f3a301fb99f14eb56d76ef693c4c9cf0ca89cbd54d96e5bd1894b9753113d11cd93d1a97622a2311e1f407862436431ee6d7065109095a0b2a28450b46e35aa441da18c50605047a85a0e18aa0656daed5eabc7d5906e9c17f381b9fdfd893cbd9f4f8ff4c8b3fbf9b40b2979724d441679621e21793e9549318189737f06c14212b325666705a4faa22292c2f22498fb37af4fcac0e8c84292fb6fcc96dc6fbb9ac7ecd08c579dea8bab88e4fec6292c34538cc390c5aab4d96c369bcd6030180c060b4da7130a95dade4ba936185f9ddd8fa3194646923bdc12ee9480485f4844faadd7321392704bb893fb6f09a8495f48445ad633fb2983cab22ccb3236fb39cd4eb3d3ec343bcd4e33d43755ab1903be62bec992a93232de54e67ae2cc190ba8f32df1a8f07e7435104bee775027f7f7b78466eb51e1fda81d35ac75623ce5ece74474223a119d884e44b9df5b792f381b703c602db48bdccfd1b2b392fb43ce06b9bfc4f190fb29d65206d186888a663fb1582c168be5fe18df8d6d52ae972602d318699392fb65b6eb25d318e5a1b29ed94fad2ccbb22cf30c8c183138e040868c17ab287a5391956f0eb985e836896e0ed70a99e034f48b6a47b0948ceaa06a7e430393a81aaa46a26aa8181e073db95fc6109fdcffa99eff684af6a25424534972ff989a75503be8c09b7630c7544cd6e1525c92461ead8e94cc6f68b8ac9c05fd94b98372bf27e35b85806652065683b0a30c6cd6c8c072abb20ccd02445c2e5091e10b959d1950e9e1e241258618507981082a3bb45071a18611545aa0b202959efc224f212a52a8fcd419256a8f7997b566a9ecfa84e4a4f9f86cb1c4124b648fe220f25897352a2b793166793e0cf001a918332f9a4496808317322c27912690c18a59a92a893cc15b128902d1149648e07749640ac68d44aa207b1d8c9844bf3ef1e60cb2ce72bf07a3cee84cbf7099cab231798e198ab0aef21ce7d7d9cd02cbfede7d96a8ca30643a66a54aa19e5ef15b64a932e63aef033d18ef5df1c55b51e6618833a5272d4db4df228b48768a0abfc0c8a40928fb77ca0434e33d79dac9feaeda79f17d5d9db6c8806f7f46245148585986374c8af138c7b00f4324ed2acf88a4278bc9b04c7f861d6dd6983ad8ac716109494f56a24d9ced5fb06256aa9413911589bf2914c9ee88ec5f22819fe73df0fd1102b7a66033943b52e5e91e510dc9c499595845424454ab599b020963ceb4f8a245192abb5f8bac49f38ff95a5ce58aa4496a619d911a6ce23cc6789c4f25501f04340c9eb9f5b72269366b9c5682b964109229982a45cb4e9b38a7ec2a992b89903ea992654fc168d9555b89d6c1545675a9b3c09b33c2a7a5a7a4a74f6b13077cfa5d4e9cefa9f7f49d68e2744f4d469c7e4e2cb9df89e4fe4abfe47edb7f77f04944bd4984d1bd499cc1de9bc417f87b9328834f22f82691854f22e94d624cc627b1f4267195b19094887d927eb94f36d08e6fc9ddf824865cea714ea14651954da22a8762499cc0aca14f12a96cd6d007455a9b35f43fb14b8fcd1a279a4790544816994ac9b447f6272c0c0b8131bec194a32bb4a06244032f4ad47eaee0d2c505c3142aa6449183cb53684a17502c3743c2151f8ec851db82ca8f1bccaae841e28a10cf941a3ee2aa10255334d0949fcc419e425382a6002169b46b5e561ab39368e6068eecdd4f0425218e2c829270d2b18b07a35dbdda6a6db7d76a6dbd16d72323b282a94758246d0e0babd5d6bbd98a2bc789640d02b212c4896415a26d1bae5c67abe779ef79225979aa0f2095c713c93aa4ca3cefab20c9d65211972e4546b55859cb61759ddc6fcac1e34551e08b92ee23f7a73ca6aa2a953755799f98c449eeef9f6e750ecd5d2dbbd5dd3b51d7cb7960e9945ac9cc8829536d75162e5d86f40d0d1aed93243ba10144c34a9edf326ad43726108121ea1ccda37d34905adb70407d4363312b31a1589618972e793e2da98fc640c2212c9f3c9de4f919e81b8abb84462c3039328d99d036f7b3bc10cb8a073f8305e3e683be6999771f9907a884cdd6f6b074a84e000028282317000028140e0a44a224caf128c6357714000c687e48625a341249c3b12c4a911c83310c23040063080004006080614ea14101bfe371b10e8dd0ffa59458055686ed94acc295e451385fefefea8058a5be4e56e0f31ff98eb44e957310b845e5fc57c1a40e4f6b069322dcc4e8027b2f1c88815a765d509458c94d9d07e8d452888f601620a56ce63f9beae0a640453de94ae0516eb369fbfd9b71bd54fe85b2324227af68fcab1e004fd117fcfcca84b46ce8e7aa1d02de9393d1565648e45613605f011686f02f4379838b1f5b474ec926e628a4f89da8ae633ab9e87c96ca7a753855a0a0c8490e54dbb5c3c32dd8ce447784676a512f67c48032a09b474ac9a6e69841a40b88b59544823d6929fe9c00e95e8b6343809b3b141479c882b4f93506df623bf7a8adb448dd93b47f2af5a3c2321faa1a116d6bbab13e30750f51ea84d350fc574b1c7ee377a8051aa5f3799b6fde56437a71a19e7ae296420ed05f3c286089a3b4924a30e2962e1a12fc43fda2b6220a5ece7057040f13d018a3d33f76e98737d816b29932b83432a330abf5f84003395e3703d835d0a3bd1f8bb34a645d46e37a8db3c18fc8ae71e4b433ae306b03d45dd4844e7d742e87aa9b73ee2315194094191cc4174a1437429cd3ecb590b3ce8fbd5909be2e713ec748344659a8ea3211a3fe41e8f4cf0031d3dd50ff8c55c422ff1f4abfbdf4c01dfff934f50aa8e9cf04a71a0a7e629cc85dfa1de1a3019024f4b26986d3564a831c92209a66b3a9f2caf4434c5481d6f6b5d28eee68df1258de5100bdb25c451f3e2d0f303d4f6cf63d26048a659a5a2a74c803824a862c39cbd0cb678e60fb3408f6caa8afcbc10f5ad921bfe8d6902794bc06b3cd4daaa6c4b506787fa979553a9fcb50ad9f5bd519687a2a52de51c16788895bb086acbfb6a73334466bc0a52a54a9848368c368d028c9e12ddfaaf44a06f11014e97ab330450ef845687752a9f2f62dde0f9c29b21135cc63099c7652e20b8fd2670c4390f8eb15c5b9c86349c096ba6fc8c2589bbe65df869f53df51faed4a01d7dbe346d18e6266cf2324048ad99186bd2e3876906bea73f216b6d28e53de462c6cc628da49c26b57dc402106aede0849e7a6c4ca4ec2b7d02883090a6d0a26a828d9c05b6000da0a27c90732f6416a496423f3eb5ac997b6c5822f9dbded3bb26dace7fdf2e22a342ae60c87203f0f2113e926571f2035a6d2dc4b9eafd49e3bf9270c13a469a2bf6f6a22a78e9301d54121590976fc0bf9dc0814d90f3f5e4b43c4051c50a45121cd28d389365f0dcf8dc37c5c69c6131c93329a210d834eb8de6c592c734d54c2a336fda0031ec27eb994bad3bc030c7728338cc2189debec1131ed898d72b20e1f4593892c8af19fc316362765b56dc3bece02530da724f8fce030f4689f2606ba22629f8bd0c5db3243ad9f3427353dfec5968d4f110e048493950be3f9f874cd8dd322dcbc06d1a7ed6f7be56944dc6dada76870f5d3369fc142ee37033eed6fa6e34a0aba1ff40a72351d6076b4855a6e1b53b585b4a3a3b4e873b86770e69a896b952439bd703d3b41071e86fd72d3dd736ee335aaf5ea85e996e9d71cc81976bb142da43f56cd9a8d26b1a74310a5f5bef3a88735c64646910e48f1684efc27ee5f3b9e6eb2846d554b32eecc39e774c332839c471f0cd39cb3f05694707a89097fb9801e0b25aeb4522efae9ff149c32a869a018865671b803227864993b2cbc097726dc8e2487f931fadd4aef54c679f81e19177d0a26a8548082f7e2007fe1fd5977ef69a23d4c8cafe7f1b698e8345ef43abd67af9df8836102aca0645289fa35e43546a4513fe04f7e25ff61172e112ade72cba20d7543201ce60cbb8a52cfeec318c2a6403b705b661f18da9cc26aa252a09945057fe14071df0d7e64a961dff908293667c880e70088f937d4cc168f29c720b6dabf5a54f79272f5ed10b7de00f797977bf4f80195195f8525a27595819f9cc7dd04feb72a608b723185ea9b22e19593529d72d7d035dd991efa61cf6f272039bcc3b962be736e56b88bf78dc51e9a311b58e066a673941468a1262ecf24fe4659a8837a8b18ef4f448345ea45b6c384007722d56d801ddc27af1cf0deb74c6b910940ad419fab64e5a43984aedb945e8ff5386840972b365da0fa3a1c6c1609ae3e46373abce969c8465bfa064544e6543bd5f83ad1d1cf4aa446875ec15bc3ee6ba5cf862e3329d7e1448bbcd93ccbe43771d065c42abfbf529433f41b604f7d8a0dc1bfca40b1b81022f7a197ea2d66da5064e28f8ee2670cfb9b3709b42e0656b855a705ebbd515b3f57189f792a47009e388316fa10072b117f5f1105182183b59358c69d9f1fe13ff0f5bc6e0ef266c416ebf9dc5dc153efb3a800603957da5600a9aa64306e27a580fe02e33d31ea0309c00664d3984ae4d3b9f5a6bf99213a0d77e7b4118d4eab0cc37a1edb721123baae0920402a4479303e85334b1dd066e4627bc3e95ebf2a10133f9fb74120602281b6b3303355a5bda3cd6555bd35cb6b799d8c367a00cdfbc1c4a48a9a299d93a4b38ceddf3481bfe7a2717451fe6aeebba930f4a99493a9cb3aa69771f268bc55f8ce150e283125950e50555babba2c5599a9a3bb0c62b15547b0bfa4b545ea4acaea1f249b499a1fe5377a121bac9fed9a1b5e76b94e5c91ce718cf359550cfca3272fa11b835a26be4512cbc74569088ba978584c426b098a284ea23de9c6f35ad865b86b49a3a7c5b8e40cfcbcf17e34f4d38f0785ab5712839491cb620c767b1e5e52830c96b3876fefd90a8b7c6f773d5ca6ff84b1a0dc11aab4630f352d23ce00aa6513528fc31fa5e63e6d84bdddb56543b798f6ec46cd51e1d48b695fd1fce5721239f1c4be3192b65e9b5f2df18017050e37d8af54169d4279d83b188811ba060318f5d025ac99ef525f7abfc038bd60f19212d8c46c4b51ad006d88d68a74b8b09d8f4b3e1f85581d4a2e3105b66c628c91e4a820f0cf4da10f92046b8934d15f34d7e96c72983599451106c616b4ea6fab9533418383ad9ab7b9109068f180f7b0d8ae280f80fdf50d0b7717ec8d0858f430f6061eb183226900a58a4ec36c2283094911aa055383914ca3fc960378916086616265cfde7d4b3209c73d6228c243cfbf0a4becbf74ec0a99350c2f3ff40cfee9ad1c74a394142c1cea99de19fa9d28fa3eaa65d9a19ea617a603561c16abd1551dd872076c5e0f77a344b5ad8b0a970202725609462104a5d9c817d1714af2954457eb4226f3d6223d0ee26ab6f2e3394b3b2a9f89cb9fdabe7458c78685dbe06b2ded27d71ef5b549a85308d5eab30addecfdd96123013b66a6439f45f459063b60b897b58e792b40808e836d3db03e22af4c099a50119b4d931071e12c334b03f8895f1eecbcc9e54d2821027eb951f3c68a74180562879620a9ac27beb5fb80024bec066c886392f19a766ec88fbf106bc80f03e3090a8b76803a0e38dd4d26f98f403b1ffebd4360c5774f81077217879e128dc6f4a06db1bdee6c102058ac32675e3428fc10bca2485b4143e79fd7783fbda1299a8c598233d8f03107c5592936c09a8a6f8d2d41f2524fb41c0b2aa2706cec2c5e14ff01197068b763bb53f32314c3671f6023f376943ac3cc73ede0d9980d699efa91a3063ac061601eb2f160d83ee43e2c0777abe855324934cec3954f17d1e52bc01290f27ee7aaa231ea053e9cf00da6147dec57fd2d0e2892d68f25bc20c2913b26d0acd132bc130ab586dfe217112a0b29bed2361a9a57e01fefbf3f045ace954769a4b80d5f72bd48c1e584fd4ba2debdc44f7b2442a182afe675c3229bf00713d5262480d6a0a124094212bfb3e2eec98bf2213a2a44cf9af761688daae04b19c43d9fc56c2956a582392cfb280b0ad62d28adf7beeac7b5ce5415909044ff480cdbb96fc5dfc3090fbfd91f8839a7c7d767507ae41373fdc98318aea746b389131b598f2a44c6d439356dcb6596a3272343dd93b6d238d6d20241e91b49609b6964a98463afb1dff51c04a9d100aff0de507d29b3d9d19b0ab9d0017d869b7867b9852607358c9217cd69c0d13d52f4ee5f537b423112e93eb97e0ff9f9f0c4bc72af2eafc44585f8531505af2ccaad910c3e997c42a0b8c880b4d66c9a51ec26d0a20ea3a5102ad5d4ce08e031cd55d800291b48e0b7a5d30d12b79802c3eba42296c610f80fa7f69d48c53b8edecaae8a89d9229d81c03e933a01f0655688edea465e41d997930ed53728bf323f317a979c0506aacb557103585720afe8483103328b18a19827475b4295cdb331748f1331db8046e2fa251bcb5767dd6b4206032ee63454bdcdf5ed2734a9c024fda3fce8a118a1755defad0a40e37bc7e259e821439518d6a0bad751cd48e8a6376210c6fad5e1d2e5dbbfe1589176670f34ad113ba77b791f280e4580ad121f1e3dda37501863df06c6f5e836f3a3432aabc7faaf012a2301d879895b3dc5b19cf4f212f3d7784c7a98b43ed610dab852d1249eadb9fb822b322de24f1e0925c2d9c7dc84142e6f431d3613e8c6337e9bd70dc750cd1b1cad136e5b1d49fee2a50be9eaa979518ff38d1f110197c9c9548a213e14a3bd7eb1349f479062b5bc8358eb7c837f448fa7122d54cb3cc75aad0203e0a4e88359681d76bf121113a17345debc3de68505741cf0378ce4a642576aa3a460d1cdd5769991c4cfd8d1d2c5dc8e88040c76b3f172d5a76be7b2be026825f606f3117ad814099b130a0e38dfc6e219069cbfc3f426fa25bfa0dd10896ca833c7374861031b1c4d070f75355827696517cb87062126ac91be562403ed5934a8255ed34943d980b17c1f53c16a03c9b4e8013cba0c5232d2d1a0a5b3cfe53300b84f2769c5e55f90c69514f3eb66fe7c1467cce06586584953fccd4972c3453f839018e5e8752c22bfe4aeee9d051ee359303e8f838f4346d1852f425e7dd1f951b6e3af02f07cf5fec6be94800922cf26a73e4bdf57871cc5136391af4169ba5983c07f7f0d7086a221fc0c61074b1e1be678841edbcac9d8982c9499bd68b49ccdc91c169e763faa82a4e7ac12a815ca61fab12327a440ca60e369617e13a2233a17b994f30acf02514d6bce9294388dda850600202003d860293a6a78909468f3ad6a205467ec1ce53f8dc81e8cc31cc60124c6b07c8625d8b0f765618c03b32eff85871db6df5c98a607bac9c9916f04cead6855087a500a65adf740137f12637b2b352ce34fe6df043f778db99eef74d3556ff8a3737c06ebd4d4733d2417f69bcab2d784d44ee5ef76a1449a782f9bd38adff597407cbf8afe5411f748271a0b5a5bf795873749bfd54a131eaed25c2ea0acad5bff556bdaea3960ca9da3ac8c084dc39fab7e9013aa50f233b8c67065a04ca2f155c3c2f767418a6b14f261bfb41d6ba88c315223613612479262779b584fca74f925ab3b85dc2935ab57aa9b0d3f41f2ec94c32ffc4a918fbaf982b366766721c91e7c051b097ee359bbb2eb84b40f49f6a1fb406a8e50ad5bd7d548acae52390701550ced231f6518450e5cf2bcadcd770ca8c66860283e6175bfaef80132b13996ecc1311ef61c7de706f100f845618335fb05c8b12f281e5d7e0ac0ebbfe2878d2cb961ed1b16cb8315dd499319d23453e1631309259bee30bd12ba23c169c8ab57d4c46d4c4c84abd712196dddcaca9d20f9277b598d7f443103f49f644dd589e8f9653ac733703ddd1392949c41226171ce058d71c9ca470455f95755e68269de6eff433c30879575e0d5cf6fb5a62f9bf2945b2170779da5012b094a6c29ddc47259b9831e44c75f05e2fb5fd8d6c47899faeb8ee5787aa702a131a2c152c23ac92107b0fd34b66aba71e4b5ecdd40f362b8c70d2000800752d6cf01d0e16f343400143a173b91f50a7dcb9f7dc64ac1699d9b8e9d2f03417510d44f3b68bd840c527ed2178bf86c94f7b89dee9c4ce991afb171faab155c3d765b54d14ad7f7f339807f947ddc16e52176aac9a2d65a4cd506c1d241fb8cb598e6f1519790110a5cdaa732555f15aa13ef15aa4b9a77f5d85004a20c10040349e999653f9e7e038a007c7fb4d0de784e0b0f68459e070439e29ad791d672a2b9e2d324b4a07bd9fb9e24c535569749b2b8deff80850efc730ac614268880dbe82d9bab433821b85e211ce978dd209cdb792c10ce4251cf2803506babe20a8401a143454752d414c280514c234ca2f1f3512626dfac322add205cbc1cd38c0d5c2cac384f7a073da3c1969a076143d22807bb8e5e5118219996dfb958af87d5b9136121fb8d36cc85dc0f3837dd96bf5978f2014b1ed79c9b1a5db41b9d294a6f04a98dd82d7165472dec968a13f0066faacd5940d6eff531a9371f3bb01fa6acdab8dca698f740e5410cec7e2a57940d73e27730ff9f4b05d9a66908176e56bc92bf1b7174580d9baf1b9f6e53c59d83fc096b2593dd1c83f097885d6cbfcb5cb89c77c1a4c8d96d4ea0e2ba80a6d2a666227798b26f2bb344d79441f6215cca51126447dc2c60da4cdcd238178b65af6abf7bdb25b4f5c5c0bd826c8fd9ff555b97c8cf7589162ffd4f2968d7a8d44088dff55536c0f73e52e07d0535af6dd3b2605edddfca469e7b5f06637ceb40567cd58341c0a2074cc369e54d048f40464bf29c9b924658217837cfad2eeb46a8d35ebac8dbd0c7383afccddf4cde78273f59d161a19481c9a6f12685c4136e575b0ebdc13d7875d3003320c9dbc9f3fdb912d5788fe194b5cc68129472e7c9f7c4881cbe7f3f273f93f02685b407d75dfc4aee8296cf02af5256689f4b4a122d7632f883b2acd328823a7e94321a72461955183d0430233a592acccee52522813d8b76f54b49d146d4b4537e5259f8ea19d83c3738af2b9d2a0a2418768d9e0ee774148e9f4c70e23de57275e1462a8d60368db4952590b81552ea9ace36687ede6deb1ea42755b464b699b2d172ae8505dc11a68be34790f5e4d4fc50c5e05ae0dd7760bd98a585e1ec5c995f4601058ae328506768bd0a89add253d621a42c5646ef8bea8a445ee3e355f69d6a5e572bdc27c2f733dcbf9bfc537e663e4019cf559e36c2df432a9b2e0aa1175a4e21611ad16fb9693226b27ca5fe25cf41db8be1a131dfb45b5bf7e3116b1b82ac952aa543efb1a42fc79fbdb091919fb1b45d38518e5d970a8c9bd1d889be668755c2ad83bd87c295cdce6e43d55f193826e72c116503516094192bacf7494d85df8673daf14c073f261449404ee652dee2a08100ba443b694dacfa02b2bfc4a97da90e3286e4e80134761531b5ecf665570760719fef6e84c179069282693f57c3ea6b1328d45b0c703c1867a21551ef355fccaf190fd97148bfbb49855f7fa08460ccaa44c94e7b1b0b0e0a92c7a105c772dd4d758e94aa2f073c6a83b73c9fdf85ce258e4d0f33e007e0545b2fd9ec789280d0ab412af6200b665f43d4672b2df296040393a8bc55069c61e95499df1efa45f1cd94d273f97ef8c48c664e0df88cc495ae059b4a0ad2b3e7858dd136877735fe09d705d133322c83d6727a6eaa95d209cbdded01765757cffb9555d69c6cac9aa8a3e005b17f9c338a80287a7808c034c5545d63dd0520404159220200387f514f14d065dc153417dbf220853ef2354aa989d395001a11cff242746d66f00ae96e9dd174b2e5438676236ef488fb8ca22e914c5fb22a9f97684265f9841793590dcfa21ecadab7b4bb7528d02435a14d8efff8fb2a722981b64341a3f5acfb579670b65644976dbf74b02917f422d4855437ea225f4814f3c6200587821303621d6db41f7e196606c822c68568aa068dc93b01018e115b90259693eccf357eb560dc0523c9c9950ceb95489eec5a1b85582f6bea8e6947a094a0799931a1eec734e0935e306549305837c6dcb4eac72b87f519cc2ffb33ba65bc328e7d5339c22e78bb67f86b49928fa925f2efc6504ccddeecc7dcef46de233f6d9081057f10c97f1c4e5534e35b58454f4b324f253e30abbfeed55a0409cf791299acadc112e90d02d1b8c3ccdefc05e2cf4ce1fe982d621993e141a9e1864ec417196135ffd4f76167cde7f81149a947da4bf6bc0497e46bb95365d073c8fc4a99401bbf52920163d7536140ceb6e4b1d18e0392b2f0fc2a744e0449b50fb9c555535f1606221ec980c7ef6feee0d8f2931ca9251b29678c4900068d8d7d0d80061964c35fe424c7f3bd2c59b5414002a223b49332696447d54a987fb3b793308d7ad3e495d2a615aa7d4f77c21cc214b39a9efcc2b2ee615065f6c4556dbf8f1f39a958be6e7a86802b9140be296e453e4b2ed7e1b8bd877a13ac5893b3397cbca4a70b2ade4bbe541dfe1bbf9bf1c6477c5a49e72cdb707a50fd12daeb0d3e67659a0e88c137f99e46dced418b86189371437ad9b12acc41cb7b3d080d3b322f7abe2f9416e9d2e913bc192886072d98cb451f3f47dadef34c792e3c68fb5c7dfafc25de99e03432a863a9fb5eda5ddbd443ee3e0703d7211a9b93a40b3c8080827da66fadbdc725ad6e12210b1f2acd511cb8be50f8742918769b06c9176994c705ee2f95a0bd5aabd6d37dc56463fd3ff24e848b670a138bd236c31b345b00a288cc293b8ff6efb9e03989b1ed1b1d82f6f095c1fc0f2679dd64b5fa7809193ea0601362696c3e247ba97810a1021805882e681c43ddf526621a6f3c99319d8f5e3e5bbe68cd1d4828abb97b7db15e668117e3fb7022a70d18f1ccda008380918c6fdab687abdb85c8db810d8407d85df2a92499a8b4147c483a4d996f2e46f119c0aa0e877ea73593aa468f50d60c4fa64767aa60689bc1e6bad80820d8ca1f4a09fb3d38de040d37aaafa5077b7830d9cf42f90b30c5ba9fd407c5f4d106d5233a95ba0d6703176f90d76ac27be38c28b86048b6b2918e087d749c973806ade8800f9b6180bec44302138a5eba1edc8f007ae1bb84e890d7530dd0de9c8232003e46eca18f97bd451f986b30f562c75cc87e559e3e1e58e057ec8cbd85d44632eb80a0a0f7d43362272ba4a0ca821acc2a3f6c75a45b6f1910d374aaeea7d7458c0cd90da7e2712882d24e4af4d79c46688d2523157edc97c9c4f861086bd7b03ccaa1411ba159ac9f6f88bd9f69895a4fc95615944b23e3dc80d61b0a3482037de49466ccea17f429a6954180fdd5bc50e50eb6215e44d5b94103975ac5164ba685fdde00c2acc8559f3af840aa8908e7e1bd0fb4bca0434b88db4979d7ce70d66ee04eb0d64d21131a035497268cc54b28b203bf6e4e78cf6f792e456cba5079feec008c04e650a7bde060034acc0c1c553b529efed106e6285f4c68654ef08e668fc215c15fe09aff5433350302fcdcd630489aaad0d520715150cd0a6d4da47ad5e67742fa7275e49d1554f33296235fd7dad2bd9161771c4a63a59a2950c492d581e44665f0c423d58c8ef904d57b2325129e760baa79176413e7b110b0e5cb8f554d7384d18864aab9d7bfa545048247fc0a8d359be8ff9ff37a1a0f0848cdfae97aea466c251152f919cceb83da88a3597361ecfb798e333aad37f1d2664f9d10e749dba9d90c442e0ec11cc6ca7ecde322fc63834bfc211f7bfe6dda1379f11f95c29a830e6672ee4152b35f0cc38a71c8cbd46be2ee3788b5a5be2cdc63d37272546adcb765441b157105b665e0bf9c3a79c109c53b7efdaacd87390ec2e463f54a211361a199da804ba36235dafe0ab08f48397e07adc87de8d38e4d0d8411781c615ee2e1abbac2bf767cbd7f760590941bb21b8eef5fa9679b92eb40c851835d8ec1c64ac907291b677cc1262657b704e3b377bf48220e70895b49c90a4c88edb8e109a692505eb11500587f223592dc087019c7b3104247edef0e3a4e14d2392aa06de0b142a4a5545c09b22b0fdb2169d11abaafeed5f1f196e8d1557c0166d0318e54a4c372ffb0817738f91df3e536a24486c8045ab6244572dbb6e5312c0103fb163608cd87fc375a812c4ce430a87e80f286ef793cc2515c97e72df9d3cb80a89230c19f8294ed5f048dcf4f80c2188d5349c0ffe4e0407bf195e52a8ac46b5e49e818559c362fc4ecb0d30396d3fc52a834c93bfd117348a2255c3f9342f92b2ba31ecb18e9aae85d5c6fa04479a3fec5504e27d42e66638cbd8338c3cbdfd77be5c3fe7b80d1a654c194c4c58ad547779370c48a85078ce7d20c60dbd16f6042d4a3be8b8cdc2eae5b21d0d0a7200561f467b3bad6b06abbe6c60ea451b8aa18dd1446bc54a7e72e7a1b2cbdb9995b58cebca9bc72232773e97734ea385b85f62948ed5f45562348c5a3d637bd75e8ab4ff3316da2956134da5c4c69476372e1eefa45f073b56f847ac8993416453bdfbaae9ae173d7d834a9d57b14e156254377166c31449b0200df523cd5486e72cfde7870a9893de1670b81c8829413b329b0fdd28c27906fb31c59b70c2a30b297a1cf2d4c875e08a27950655744905e68b6330ef8042b6ba77559e61e61e6004fa4efdf124e95f85c5a039de3154539aa62156e7c716f98cfc4e31186ec3716a9939e28c05de0b19167ec7fa6ce79307a8d9d22ab4e5faa56da0941c0d87662354781216deab3879b608deb1504f3254070c26addda39d29501e14c1be92bba7346133ec6fbf4599f6aab0641362dd2790a57b41777db1581454805e476172c26502361b8102b08edf1541d5deb29987e295ff6535f7e0cf03ffc8cd2b4018599eb3f61cbcf4d03f2c5c2d17947bd6f6f7137ba0da2a1d8994262a60e69cadad117bf224e10b13ef171599b0e223b64db469bb15e08dd81e25d149d04fb6593c2f491bcb308b33b2c4e6ade2379c5013dce8c36ca07a0598d24354d9b89ef694c9f63ba69279be304cd7c804865856d78d49b93178dfddc523ad1a4fb1719ba86f5893bf22b561bd13c3a2749c07917696c776dfc07fc6b7c7289f5b89b6f0f6f9edd0208023e14b2c3f467e32416e9749ea8b04a6c8ed68326a729f2ee8019808e2401ac669f37b3287641f7d9512c0aed799d0f4f1a16676cb0c9dab4e0fd5aa15a78c3cab1edecc03d6d4eac719d780bd45dd1da401ded6a2554b62d5015af959b8303af2d64846d75f56046a5a59d64d3694978d874fd027dd0a60f15be84e5902efd179c2dec2ef15409a4e801c165eb6b69c98c5c2568334f00b42316daa0153a2fc17a0e9be00f76a86e4d9e12d05e31f177ec6bc74e8486eae2441a04d9794792ed739c2893f31ad9ef7e10b02b75a1211b1ad415380cddea1c07f40d5ca27ec9087e81d5030e38e60985ed6ebc76cd4e84e45e8419b9fe088c5f4f09967c0f01d41af42c6cb73b86809a4e750d8ddddfaa7d3565e9db185aca2eac7bdac0d9f879b27702bb1b8c0e6e14fd3c8791d63fd06496d0663e00c8bc8bdbc92e5cf6a66f7f7facf4906831dafcb4edf20cd466f367de756a034db15a86430629a68bf2f98c47cab3e954d5ced0c81f3f6cadf2f4753444f26e714587b837c3e91e7f66c7997739019f1519c1cba83c7322dc2c59d2091b6fdd2c71a6f40ef3127aa18b499cee749084edb0f8e032c50ac345ceedcbc4185b5213232f3b8b3ad8ba604813a9bbd60e2458d85faa9337698c797639fe69f09e0a361f970c9bf7141b5f8125d85ea56ff8072e63fb966ae333800cab7e554a6f78f90cf8e91d5c52d513bd25a6a19ab91d1a0bb147f642cc79a10b034aa5ec1bd6cb969d164fa3e31ec0121f37128a1b19e4960fc494f12b1541d860702e4b612fb6d564ac69a9209151f40d0b22fc79310fec909d6e9b37f2d286c19f38f6357e233bf5b6e2de4cb8c17d7e77e486148b3a07a9b7ad662524a9cbaad877ea0304dc940744645ba6dac6349f107dead02df37b143bf3817c5f32023e779f1a895c48c7b7d7e8b4ad9fd04f1170ea4b911106abbf8ae44866f21ecaa4eaae0794d0144930a1f709ed152a7e764f93dd0ba227c6d9bcfaac35fd27787830483d1c8273d45fe3c744bb39aa63e6b1d0e98f25161d566aeaafbfc618d1abad01df1e61c77708b4a11b525d9157ec5c20a3961bf661f8fd3dc25a9703230a95af40cc5ab8908ec1db334a02b0986ece14dd16c2c41ab666455ae0c2c29529b99f412dc21ba4964c1255a840249098fada8e6d8f1d1bb25d8428e45993b028f31b9bb5c2b9eda174dbcbbd28a31a5380a6499bd580be90dfb1404da0916dd47bba9b682c351104c48ca85681488f45228849134c0b812beb0e912d222aa9c35ef557f0e01cf5ea3d1858a3dac58c0858d334840cc8057e692004669a3eeb8530b2472e29eaaedb683feba82775e951c8df505452a6e4c43d2a4341cf4c246d2c18a148c0fa161aaaa54772b4eb4c4aafc114abc2436f5002be7f5d6993afe117651c31034824743e8a3b9516e52956876044860d52a4ff297771596017255bba30d5310db65e61d0eed375d805018592d6b6a57d3cbb44e617c89398767a0c1a8c8671cc90619c0bb45583180b98f44bab102d51d107f04e42b03331263cf8c1bbf5c4ab99a5fb0a7c8e7b47c6212530d8742bd47c2552960408de6e0574392c0ce3eb8c700ca782729c82098915d72a691850ead19d92daf5c273ff87d76c740746236faf9cb01d2b166d6a26fe45e4805216617b7460300a7471fb360c48fa3b68c2b51f34babb1203f7f6fe98027ee407c82b5bba54f501f64e26fe3927c1e736fcf9527b54ff809ae172d13f31cf61ee39b52328e49d9249dbe932fddd7d0d0b9f004af22100180efd40b8f2a30bfaf804a3a5253e3809045ad4d6ca1eb641d300698c283e98105e3b3c35d7bac3c5c16bb707e19387804d73f59280eee3005b95971cf8531da0ede34a00a07c2166ae2871e14c050dec297f7dd7017dabbb042b8aac878c9c36c1c962bca5caeac5daf5c215100813b62cdd689829f975ccd2b70903259d31ec35de076cac66644ed8e2cdba42e24a297bb34cf853455684522f8a1684bee856f112f678284a19a98c72844dd70bab43f54d1148bd2330053be603c394b497e4d75baab7b6c0299cdfa2d5052a1d2c28fa2e87697978b0d0621364b4b2a13919c7f9007d8578c66e7a281b24f9e127de5528e0685bdedf5faf1bf12d22dabb7bf46ec60e619496dfe6bc1540048d3ef67bb3386ccca5be622d53ab9dcfeac68abd7fc459871b426c799945839eb1ce85ffd9d7ccf6d3b7a515072d94c2091671b996175e2278921f62c036e2c8eaf8590830bf6b022a92c4bb20690b94dc7e4e52b0906a778e2942c06c1175d4050ae6c93fd288e93c8c1d397c463b6fa048cb30058660112cad015102f140d32d005056442e1b6ea797e05d56666e053e1a713771654197ea402c459a8a4f2ec5863b1fd66b72d310a2e29abe1e7d0f65bcddf62a165db1a28e74035aa9b733e0a4e8949e8973ca80e426435909a57d738e44b926525ea60697900138f62934f9b4731c5eed49e2853609f01f6ac1e9c81af875588132db215ff8289a690c5400fa3d442f27eda1fe452d971b711fa3adb2525e86f90f95e835c6b80ab23351a21ff3be96d8840fca942ddd48eeb322a99ddbb26a7f276f6947824a24c05b479b68c2a6ee091a5c239be1fb59986195522e574fe205deb12bd6b668edcd546ae368ecc9fab7d532569917d45a0f369a8a10000aceecb6749d4d00e899ba9e97450f9994b2005163fa2e60d980798a8da54716e406bd8d1c4e7a3666a368884193b14938dcc067fa04c420bf954d58a1dbc155b599c916d37474854bf9b2bc333d14aa276d6a107145815676552440cae7899cd3b863a9c49f676c0f34027d4f703e1c2b60e4beab5b800087819ccc62385afa10c7c55a6d656df4ae78014383299fce61c681949c313a3b5e902a15a0d48f2c2534cd2573108e75dff36a98ac267d65d0e9d22ae1c3eb9e823231ae34c03fea6ff5e56e6d163937bec1dd06a6e260d515e2734f68d172f874cdfe2090515c4767e1f9fa734e594a2ae7291398e8cedca35411fcd6a6b1e8adcb0ab59d1eadb6913b73c7b596330bf6845b4bb1b0921da549ec9b79e3cb63d9977ca033e4892ff3bec020547874f71a1d1c53a20962d303251b7bee6ab9deb86719b1aa0b86864b6bb98e71eb39f9b257594d5b1361375adce55d6c36454eb1d13c5b1f64a6a8e1decff0596b178a18d8c849de31acf680646a0adf1b796f6774d850793c5f6dd03f597861efda0c0d4ede9822d232d13671c6399c70dc93c502be57ac63d79920755770692d8b3478e7ffcabb00934e9781c132a1446ac9f605a2bc9425f1d139d5868f2d572e51cab7e349c3a0b0db45fd630351d8116ff84a9a68410486063c43ee029619a38d141bfbf3b94990439109eb6a47021c7946c374c58282847ae326c534d6fda1360e419fe0930100ff0ab95909f9c4a69b41752eee525a96c4dff1740497a7e3d865fba2fd69e807b2493d286934c23c935f5c59b768a0941d372d5c88ef8cf5f20a2bfd67ce937e679f57d58bc8930c4cc5d712065e79e9f3234609fcb27866a6ed8021deab42d7b41027e4aea6462058e729c5f02a44149443daf27eb1ac4dc36529851141da9df05b0b64d6c39345eab9770c2d52fb8e1666342260b76759c66472f86fb3207b7ebcc0cad99adf5178ca08a7e7e8a0bee970b88b08aabf4c1d496f184c98819fcb56ae4127904fd93dd5a5eca23a070be4120d85b18640fc8465778570d04bf19bbd9d438ebcfd002752e7bc23998645b74c1191b702e040b410bacb0340f40824b1d6f81fd68ef6559b79f0efe6691363d6b19c6563f231597902730c08c7fa2b1ce61df402d3d7e8ac08c1e97535fc47f521abf0f531f722f95496f6dcba877b6307d81ac27a4488fdad563cedc2dd30628f674d2c8e482f20fc6396554d723334cb012b380884f58c9378f5bbf1eb2a116905f1424e168f5eb9a12f330578a154329b5549f9c83d698ce3ba0d6860fc0a169e3b701972d144b483277eae00aacdcf47691b8b405217a5aef7b31da52a0b877c383144004242720112c26025dd49cc0b0935de9ca1ced952d682a91bae8f1174a6608c41cbe2e69cde4cfebc01b6652d57d918e610e854cb7555b000b94acdf59d587cd46fbb9aaa508fde2757548b7ce681945c72d2df2a1d4d9323b74ca638145bf5bd1968733fb189eff0b8f6175b59127eb5b6327d123c1376faeae62bc83d2c5a400d2cdcad06c2657ded64d2be8051c526c0d84d219bfb74ec7c2708d4dbde40a8c91e11fc5ddbe6d24fb39de9ab6136c5a0100168082ee581bdf95700b34d7dc4bfd28888ea874019b8703a5a10e2e92942d1a2c1b4d1a9801d8accfbcb16508e0497cc72e7314d358989bfb81bc78b7b32aed9bf4d071b0dd516c958d2256437dfc650b142bee2eb397a755c4e788b73e3362836e5fadf03d2cc036b0906531532ad04b17277a8ca8f70a03c8160f183638b180af1f53eb47a31d4b05c46df4d1dc02b0e60526a727baa34d13d029fb836f7ba406644c296a7fcdde1dbed8c1605d9647f2fa4b2162cf75bc79a43f4365200b02100897b87a0a4a154b9c216b555922946f3fb97a3be95cdd0cb4e7f45646b15c58208025f2a3c7e2780085cbe3b89ce3031446c2c50c3d8e994f37a06c2c98b4bb7aeeb138736cfcbe3c909fa97e308fa0ef03f6b6cbbef5e7deef8409cabefc29df23c8ef7112651cc6606e523d1a3dba1e9bccf041df907990d9bfc594ef2cbe8ae130434c2d4e3da265005efa7d4e1531bd853ade429efed141c3a85f7747f83ba54850a99bb7872a5a04aa5b0cc321537501d26fd04355fe7a50b4a91f978948612a1b074520d9682cae70933e92c52f98464a93641a35c13785655bbb9330123a63928a469689dc7ee9a8b1c78a6594879bb0d69450a0876b037285727586442a6a512a746a76269141b0477113fc4353592bec6afc464656857966604c8908349ee1f7d08db14f37c673e3bded0489900ad62263ee46c56f4c2c028a8b493157e596c02cb1e36bd96501c150f6eae08e4cf6cfe81274c23f7e04c7c560083635df0c6c4c4081802f2b24fa9732531c4db6a48c633c141e487644e55044723a45aef4ecf74018d04adeb5f58f33b720460421bd362b81d142e51b63faf70ca339a6e2be0711b2e5d2dae1d78c8d61cc4ff8358b995f86d7b181cd7813d01e0bce2a3a86c6c52973ac5bc31513e7bdae622c890bc78e24a1ab11b3a3c94d946795c223d7116cdd795a4ef38ec8b8b70e218f45df337168531f49a29fee83e474a52e7dd0ffb704d5c314402116191a5e32f6a0128fcfc15b384676bbadb7fcf1ad7ebfe7323e1d1eff0c88a1364b483755b26db8683f0fcf782de4e88752436c240431c6fd3f5cb93864f224e80da3203a56829c9dfbafe83243e501040b91ea321ec5ad1fc7288d3e5b1e97a7ba21f12b87018635f1e8d7ffd49ee7557a08f5c8068cc201b5f0fa65b285fb08a1b202af5c2941abbfa60039d0738fbfa6476a21649547551e6e50d2be0a0cb8b16b0963e0280f4af12c1954db2b6f536cb762cf4f559df4feb89508c648bb42efd0ed11e041e684ea407e876e6cb15615592589d739dfad5cdc79585611180ff71972d3db723c05c69180353bc6b4ffb5218f803717ece0ce67437e1b2783965072d3872e92fd001c36ba739c0cd3a05911eda30422424b7485956a396a0a2182f608583fea91c63da3f74c60c6931a62e4b670718172f990bcc7c1729e355be8420e86b80f9fe1dfc0f44a56ce079837047a3f57ba6139c8ac44b6dfeeac81481c8a5e0478c619cf948394c9712c3f0d5ec3de17cb6e6449aa2dbf491a920289a21e43c3901f9372cf89cceb9e871525fff02e643ca55873eaa7a8463b0e7d5b6c90839b9c9f4db9385ce3bb9474d85e72e591d527ec5220ad8dbd1212a33a8f3f533c29104a13a180dbdda185ea29fd46f4130832354798956123ffa816ee74d5cc10d3240e0e0fe68366c0b99bd0cde17d7d40d55b80b855d63e5f50b085cd7f5f7f5f79ebe1e6b2e73c04a9f24c058328df4588103d9d931ae287aa8a024b46a39fa838e4a208827222b73d58316d7acbe0061d7ad318a9d215fc3dc082f5547dedb90543f215729dc75ab2228496049780d45f5a40d020c4c82e7c85321aa96500e0d65f25f5e608a96745ffeb8ec54006d4aecc6ed2d2a88a1398b975c70b6d17e51ed8eb5a6b2df10342bdd033208431bda8a60e100e39aeff2f7bfd0bbb6a537474dd27805455c89666afb345cdd49790a2481ceab38f1d2d175c208d6393b64b850868701516ddf7a9382908c120f4e2c6794a7d4e67df699be8629002f28cd53fe632625e5427538bbacf02010bef2d30f8d0cabcdc5af54aca4a2e35689b39d71fb48251d161764281461c8ae018e3c2dd577788b0f2577fde3358c723c94ba1656232492cf3953bc6fbb178e5ab991027b5ad5ecb104640dd9d5967e5c851b54a33a6fe1fc3c7f22ee9ae5aed9b08706f84a8b0423ff3fdaf4356c605b1728fd2bf5fe6686c1b4a48adf5a8808244fe3714ac0d59c002047b24533f124c6becc0175da9ea40660562a428147db840e3d962bc10a980d4e9fc99731c9d9280743225c271cb7df5af167bb78a762ba53eaf1eb3579f0d0a26dfbc97f304a1dd7b18046a2331f52a5d83dd8ed87c5d209fac31d77b547326a9c45085d605823f4d11ecc2f987783431b3d874decf705cad333f85f38172d8fec90b7e05d6125b45c22bbf0284aa5baa91fa1522d901db46287561d55c42659b9df28ff538f9fa4ba45495869a987b771b9441c95b5cdb8cd94ec7200f71393b78a120f039737e1c09fc65fb1b4aebf9b9e5ee7fdd4f7a8c498949cf485e657de75bcd4f1dbbdadc31989d63af007f05aba858f2fcd2dea0f1aad903a9cdfc1e5bda1c2a9a0bcdf33f347c0684ecf2234f65a0ee8997bc07801a66fecd2d43fcea17ea39b3aca48db3ba6465840102d54423c927d640377a34a6cd2b596360f6aa20b71c6e6b5d664cd1178d904898a29a402d2f6154266021a005b4028d24d07494a3ef36a232fcab801802d4805c90116c863b3767b2c9865a819a111a42bf3384e7a450f243418b1526962d514550832b18d0a6fe472e75e4017a39b816e98f465fb62a4227cc1c2350a9a0061c24ba0af1d857b109373c54fd24f2b3670f731fc809f585784c9adaddf459cd6d4a1845b9dc09a1a60292b89e8532efb86f8937327c3112a263e20845898ce10e2b80a8b60a6aa1fe9e658d6441ebeab1a8246197138cb39e33da7fd7f8d7afb982ab186829aaca63b01f719aa595d3dbe4e12d54c1a1c89e48675a3f6c58aade858419d018390fc554ac021bb0a9c660f52cdfc4ab39834d194e8aff1416ea1349a6303af58b683e4d9a682283a341f65a4a1eda4b7a83e102ffb514690bc3f4562d8c004e3f3a1a5c74eecaff2132330b9cfd7b215d0a08cf771e18665a10fd0f52b9e4d90959c804d7a8793628fc434bf6934568e22cfbd057663e2c9f70f9611c88c3b20293fbc567e5644acc0bc876cfc90705444955fe8a6b6dd712ece12288599526a11dc64ca861d44e257e887a615e1614f464d142ffd5afc231692b9b8556327e0b307e2f373e2ffe58de9f5f1528b9d5408f69d04893b798686cd768d431678f4d4ea48169931b51d869252fc503e2375df3dd4ddf4d01c1f99f8452e1b09e03ee55c79d16386f6ebd32f3aa70721f195e50c29f71d69d09da6de316e9be08015a063a0842b94b10704bab99d8e55b1937d374c6ca01a85205d559d9d0e882b31f56b952632b6b7daaefa4ebb498ec1a33ba60741bcb8548dfbcb7341337ad124fe16e128d6034ac2d0df1a32ed43f823ed0fe992d9b26ecc844ee23bb07b505f084f55f968a4ca718b5f5e1922eb71508963a5366fcac67296f421c9fd7ea0e85fe490be48331a4871fb2ae558ff44d485e1b49549ae847759fa8c1162c5d47008f926d06b2b83b80fbdf56902e115e4129c12cd52a41b56a5248655eac1c63b3869862a4676b2792febd25fc31ac4f5582f87cc8d278088cf154f2e2388dde9ec0a920b4c326b7749c63c9c5a12122b40ccdb1334a79dde61fc901000c33dded256a8605ac5c00e6241628ee60c99b04cc2a199e0c65cda87995f8406452cf47e3481d98055ad9b77fb996ed1de1458110ee7601e261216222724d8fdddac7cf1ecded3d1adfb7ece4048fc9393bf814c8c7f38910cbf0386e51b0f0904f8dfa44e39b5e7f48fcbfb181e62c08a801ffa2ca1a5af7bac5e87f3da06ef521630cb0cfdf7334b91e76c113eb95c400d66de5bc49fa842468212b051417a4d1f42d35c8d5228a3e683bed30614f1e1596f34b6c10a02ced489de54fe5032d1db18a32e3e19eb2267479dc7192aeb58b86cfa77cb6987db879f843013037ff0a656ba3f6f083ccf3b12bb509cee18a625e022ff7558ce8b7a3d024c46fd7e7ac095a17da2027e40920bc27fe301816aa7eb519026f83fe6377a3fabf6527e56bf4c17c5e32dcef7fd0dbf4c2b7e3b8ccd0afaee953da32077b4a16ef9a7c9dcac5a2d696e3d041756fa1f0dc84ff726021bb39df0fa1d1b0248a6ada6ed4e0e5dc487c945beb80283e4865799737ceca4d0e181b536579086ac38ef9e2390e02f3c1fdc662d8236183a2eab97d12095cb628bbaf1578e509b4a73495de6ee8d7811edfc7e88439c2096b634808950a101e4bd510818b425994c0c50201a1d5e400e0bf110fcdd7897f654b21ee72de8b5dcd11519820cc76aeeafa0b34948c007d39ff7ad4f338f8964364dc7cec63143b4a671f06e037c90031aa9ce885d40d4554d1b9dfd3270dfa5fc0e217b0171daddc2f3ef0d7d37045beaeced93832f4e0cd7d82686ab8f6be6c1df2249e8de237c7b763e603b06f3014d841cb0521f557efa2ee9f85bf735a84714c565ebefebdfd6ec924d24b2bbbb7b07ec0869082f08b5c66ac413bcd65a6bb5422f800add5b6eb47c4a6684bbf0279331910df1ae4210feae67dc155aa4f6f971f0d77069707a78e1851d7a470b97ffc28ecbb5e7f08634776bdbeef862d0979c461ab9df8bc16d9c1a9e66e519609bb8ddbf5f0af1eb3ee1a5e2e70de3cdc4e752e031fe7dfafdef5db9f3c580786b3497c6d2e0cbe15d69b0c6e14d69f0c6e13d6930e6f0963498b3dc467a35efbb35c7f7e62d69f0c6e51d6930862fe2499a24953813b38c2f4f52de1b0d877326e112d6f0453df186792d6d5643e1375e170441a408208048203b4206b22378901db1c39fec089c234680e48a8be485c40a19922964488a6e2093f5c864b012c86436c8642358821156144166c413322386f89319614466c40f6446f8c88ce89119b1020e7fda17812062448f031891030f23486083206d8d70801f5d221ad27509247094a0e4defba4889f21dac49fac881afca061308422788a50010d973f591145cc29e07b0fa59412764ff62862079756040d3af748136cb04790e0920207dabad1048fd9f24a78b46ebc9f06dce081ec4810ce8d964f1d21c2bb0a2dd08ef4a0067980233770454e85d32a478ed0807747663083b602a7f2722408bcbff8b9e148cf0cf2080b781c29c1eb088f1a82665c094147f6464e1cb9496da69c928b292dad7df15e782989b6225743d7e02a47730d176013969005df311ff2bb1db0c267ae61ffe61a1462344d6a93445fcb3528702b2b0cb72db8cc36b8bb6f9c892a977613f530888171f1826ae1d2c2725ab140a4a09874899439bc69b6d229c512f9b709290421349c5840115990104148295b6813300848fe9505fd200bfa81d71eb88810ce1ba8442d61062f57901f34a1c374028a263e84600426e0304133e79c530ee173ce39e79c72ce39e79c73de54d08f2014e8a02159908b882756f02723828877cd9f1219114132228cf0272342888c881cc88800e24346c44b46c4cd11ac43cc64121c11b648104aa45fd91042644300910dd1c39f6c08986c081dd9103328a10a9910444ac8844022138248106442f8f4c884e09109f19209e1920981f3a2a36ef8fcb76d48f3685a3a3a3e4cf9c941f74b5d6f86f653db33a675aa038f9b0e293f2fc69c2aaf1763ba7497199f2eb3d922f6624cf9bd458ccfd7c1e5f566d49ff3bbca8caf5c95199f6f4b2f3e2b45dd0405f1482389267d7e51ce59ba93d6a46e846fce523763cac67b29d34bbd7f9583b9e6c554ea7de94bfac598cfe51afbc598bf5f8c99238043030db9065faee61a73c358d3368db359022bc899f978cbf3b77fb9f41873304ba0f498e3f204b852c9f4a5df72cdc64d1d3695ee8b71f3bfac25a01f672ee6cf790257ceccbf8f257cf19274e9f5e35cb3f152b7a1e8d772cd76ed46323d4af7bad2435eea7a07b8ee5ee92f2f913a1a423a478ebda56bbbafe002cca2db4c70c381cc8815fcc98a3ce15dc7644586701d5bb212b3b1c2e7ef1212ddb7574c65f666cc97f1a98ef1aedfebc590dfe9ebc5e56b21ba00b388b3bbcd1ecf8277ea7a31e4e7a03dc7ece8cde3b179030d213dff29d1f5697d4f370d212d1fda78b00759911f3c1c3e0765457c70f82f0a61c81043860002880430082245b4602d14c8d80296403f59921fde391ebf06da05181631c20300f893010184084736aed9101a6e980cd1841b05f8a08aa3285c468c80c80fad06dc78000d221c3cb9018f31deadd5801b0f0042063300e206206e6ef09cd467038635102162079717472d4036b454a151721089bc6087d30284e8c99f8c080b88ec90a34284869423bc50a0d0312591a38fdc5002e241b2418f1cc40a4ec005818309301625d8a0388106840450911200fdc003a827054028d0018826f000c413302094e47004c411179123cec29fac080d456e7857a185116834c10581650a1f84702428073a43f1df8d4d8e7083273841a630a28426aaa002919432222184048d221c11638442348a4084191f007f3222c4b06d41bf2569ac761d86204e3fc64363570c29d19822888c083cb221c8644370b594e06f4ff8f62c459c6c8810977f9f086172dc93bac62e6fc22ed80443104adb8059705cd36108e2a5ef3004c110a4b18b097fc9e57197c3456f1d34c6b72fb99c682c25a7e49c493893527226e19c4ba552a944630a4dd33419115eb22110699a065f930d01e21a6973269af4a6cd9968a9691acc36b8d735f93b8dc5682c28c61d01a50b34e6caa417509e8dc3f4457b7a5ed487bee0a9680fb8dd437d680f9eeabdba75efc5d05e3e9d97fe6871ced0555a5e34ab75b5bb9c13fdb8eca8b5f787951ac4a34ad76db84accf45d02d91103d48bdc81f92e7362be4b1cc8d2e2bb0038ec18d03aed5741ca38a1933e72a6f3018a0c782a59e4c674fdc2745dbbe87a0b7fe9faa3ba9eda5bfe964cf8c4a89241459e4a4a9f5db934e2a94e1f5f2679aa24dee9f42814b41075a2fcc9d39e5c0649284f5b721974da91cba078da90cb248f899780f708d0f52c2e20e5df4b00cae34a42fdb67c3f0ab7d82befb25756dee559583e3fc086afb0ac74187237eed52d0f480bda52b6a590cbae3c51e8d36fafc2694481df65a74498cb462972d92697ad398d2874e95d7689d38842eb77d9244e238a2096bf9ca5eb1ce02c9f79cb3e753d3ee12bbb4b959db2bb24924ff0f8a6dd5f1282666cee4e5ff48727e8870e19cb352697d901b106414c706b104c2c1961a5c5ec452987680dc818cf61bfbed51efb88291e08bb0df662dc5dffdea7db46fdfb24d2735145bb0da721e8e1340430e0b4c39b151af2522bd21f79afd6b2a5c57e8fa126ae0686ebcef4e4c5889ff5c7870fe0f7610c41fc7edf603527ae6f94361e3c6f9b8128147596776aeaa36c3f85de0cf8dbbf2878fc37e3d6d8567e7812dee9cf8b6245c92906859491837833f18ffc28e1312fe1f18b98e0f17619e8a5fb06335da184c77c8af1083bd3131d7447094af1bec1f22baaee472ce20c7d9c9ba3775a7c40f98353209c7edea1dfb7dbc527312563f45fb6d162867bc04f86ce3ffcf25210ca96b26ddbb66d5b1234e43a775d735d6fc15dff1414f903b96eebf4861c4208650e2962116568199f74ef47ca90377772939dfc180bd75efec81ff9237fe48ffce9f2e709ef1c8f7dad2bc162aa06bad7231e1ebf1ebd195a0d7887f0c9bb5f80bcd7a352edb8a0ed2806924a6edf5eb956393bb37341e69af3bfdcc29be87af3f6b5e7b01d35cd296594aca9eb96d4e58ec3d6767deb764cae75dd66a5a3c25d12c4b97a4108e193f049f8247c123e3e770efe765ff98077f5da56af7beffddbdd375d94fdb28b974a3d77efea9dbbba717d6d93f2573a2b9d95ce4ae7458ad5f5c40f2658c4f71e7e9b49130e833f1993214c827003f0276372c35f67dfd3f7543e7dd6ce7bcaedcee1420cdcfee3d4761d72da77c4d7278f9d05e3f26bae616bd87ff9d99734c6ebd67126da763a6cd82e87d649296f7dcbfd949d4a6c3aa99505949f05c3f5b186eb531f089fba288a52fa2cfa38d7a0d53ecc2e6d1bf52e767c1a9332debbb0aca448d35621ed7a4c2a6fdbb6e5e05eccb6e5bcc57439f2733195c23c8561c152bed31e4e690b5828a574cbcf7d870ee06a878af9f8366f33d2eb47f9171afbc699e8ceea2c188f8fda39dba84f6b6cd1727a7915fab6ebb038dba89f83dbeaebd70b8a2645ab01511e5379cc7e3c539a37a5d42606d68bb543bfb27ef06e512f03a71b64b176583e78b775eb72d047e587df77c47e7be4a8afbbb360dc12d103d4025ee90f0fb1fa94c793412bccd70e451f45dfb680a2947e0cca3e6b07a64051bfbd85d9a577b1f3a3f6e32f5b6fd68bc6a48c6dc6d2d1d8a95be9684ca54ba131fab7bee4ccfcde12835d7cbe49db28fb8f6f34c6513a93a943d9cfff9232a8909c997f35157a09f518def5ab6357e54cb40e1ae3f9bb0e4a5df97390ba9e9f0a491994c79b813d4968c1110f1c7ea7457c521d38fcaeeba05dfbf099b9c7cf7ab17a401666c1f8b596d503b222d641ebd5eeedeee4b676ac1e3005baf2d3a20543664f9a08c255e04fd6040e9f1531a144cf0fbf471c01fcc9887cb88cc8257362262382ad5efc721e5e1fe7fc99e5f4902ee1317e8baeeba05d8799cb364a7f1fbfff56ae95ceea155537ee996b5ca85f659bde6efd7897229428b074f484eec5bfcad911b5fb6fa5ddd76c4a0b2a2b9d94ab3de9aea8743d72d2afac5edcce0ee3be634208490f21046e87224100beed3a0bf0eddb87d15aabe39772b6b1f2965b89f6f547bd1223be5b2ff1706fb7a2a35efd9048d63e7e58729adbc42003bc5d042b1fbcebdf117394225cb9563990053fae7678d7b7851e7969f5e27d477c8dbbbe7a71fd1743dce5b05ffa8bf555f9db5dfbfddac7fa71e4acb54f2ae9550e4cc17ed51b9a5eee935eb9361d78f05a2dcac36c03869778d87440e1368a84527a1448825f22a1fcf624940ea5bd7edb4b3cf0dab7225e77ee3af2b3ecd37e52937eeafa26adbbfba5fa2b9dfba4cfbf72ad7420ebf1fc32d7c8ab17ad5c5be94056875cca90126bdf77ccbe63eaa02eae69570775d93bb976bb950e4cc1762a3c3ab6fc906f42195b44d0315e1863d4e08d56563aab6c1141c78f7dc629e18daa3e5f76bdbe8d72cac97b4b069a76574aca29c532dc02d27d76cd4d253ca6c09b1703de4008e19c70c299922a4ff880a30c42b80c3f73faf0770383e0401f2ce08fb5d6eeb0d45a6b6d9cb4da99c3cd6aaf5685ccab6df86e98cb1b97e7e4707824e19064d06584cfd7f3c9a26cbe148a3e22103e9fbed90b7a71f462be4cc126f3e10f9f1f77c80f4021d0c8061ac0636cc00d984323d0823b3cbe84f138f49ad8f0749b178f0fa394735218a59c93d23a69add6deabd9ab69db8631b77135575229d75289ab5a73d564e22aca9cbbab3057e6ee02387193859ab8babb0c44bcee14a203f0f92a45757701f0f92b2b5cdd3d017cfe4986231ead8d32cc5ed7618cc757e187f95a45e7d27031116302064cf828c25aeb2384eb53c4b2c0f2400619ee80091c82a850c211ae58c268092782a065bceb1b5d872082c3c40cbca76a9053d67b2f124d03239688c1122eb0610921f36708c9b342153a08c9d3528556d13b182279768075cb9382091f3da8a2a7871e57001142921b244a9a5042637770026e77c0c3c760852a745c628725784c815f8caf25ca486bad4f2801042572e04409204af028618223256a584092a1274243530c112921a4c49112454a502cc815400927849d7e534ab9d275172e3b095f42d882aa416d8e62a153d97500706b6f0c130628f0f85535359c19e863bb67d800aedc8daa14321480b5d64a4a274591e4dffd68e04e71fb2202e0cfcdc91909858ecfa9600c70d95d15e40f3ca3ea85969732b0b43c3faeb507f918728f06cf2d65e0b57e7daac2fb999be3dcc6d930c4e9a1e6dd43edc16affc28ecbdf4b61470b0b8793430ef394f04949b2df8ab35e6eee8d73fbd164ce444b140b3fe18b2dfbc5985be3735f2ef186350d76737b43ae66ab46ea32aca0b9b9a78caa6c512ed420b75def00af1de66f7368d52be594d55abb04ad7d3084040d1c868ab85ab7fc5011234d2e2088ee578bff2d7246caf9f244b1202940a4372ab54704bf88638c24cd668ee18b29d28d292ac4d00c3ac048e268079924a2b0c15a6badb5347248220997325a43763821073a1d901b364fcea380474c1239dc0093840d35b8b842e7a50838a090d86901c40e2e12891ee4b420d1830e0b122b7821c103123928c9428913254b9420515244c90f4a80284181121a849020b44448263404212142303881900a72844890c428891249900cfd180a92242845a4a77874a2456d9491d65aa748d26343121624e9c16badb5d62649729048723343153f38e2091a3847082971841045470c9936890e9cc08d43ca218c33f7c59081866fc3319450ca4e6f3a1f04f44d7cce7f3cf29951af8b00ed38e4400caf2d786de195e5b4008e356d53717af74a871a7f91bf15e40ce6b07238399428702c63844f6ea4f85d843000fccaf0fb82df8ce3fdd0cc6e9fd03d221c6329238410c2873752fcf703b7b966bbd70598059721dac86867405274841024482031826408487c80c4064862808407090fde5b6ed02490350149132d2fffa275a3013125bb010e9aac0787bb032d1b410d7509d40a5e6badb5d65a6badb5d6bf3d0eed371cda6fbf7531689d0d0dad42cf97ff125391df687ea37baa023aa26a7e9c1f810b3c9e8aaa490f300510558fd77fff96bc787c491f05e571a7674168ee63febf9b5c2b3e269729c7f432ed6c3d52c6c35bcfc73c6c5179f8304854ad3c647908218750cfa28a28aa5c7493a882f9e88376bd192d5f5d3c3ee9b1101e8aaa958f8f6711134515cbc7ad87fa6072b93ee66d68523a9404fe631ec77f4c17430f291f43cc7f97d2e1d8ffff5d0cffbbb3a1815ddf3171ca3ebdcb5679d8826f7a78c7422e2ca4676f469194f1df2f2f95dec577c9b58bae438ef21a0b995c760a961ccc4ef9975b3ccc82bb78eca21bd21dc6b88b37c1ed6277f82ea87f7968c35fb60eeae228976fc9167bc6948bfd8e6f78fc963d8578f8fd8e850e1053f14f5b0501c4547c95aeb77094ff7ddf0ccac76c14f9a616bd443fd1592812f1d84d2e938bc7a884bbf7e4226e308cb2115fc67f8f826fc35fc781d8d5b8e86a5c442a4ab319a5e2e8a852717464a9a0828a4b85d5a8d8a8d0f05f8c102aa1e5a4e5a4e5a4e5a4e5a475d43a6a1da9fc70d882dba0fbe69a97bea48cd70b06e3e1910f4b2945251f0e493e2f067c1a322dbff7cdc5217571951f290577e9528fbcf5238d3a70e32105bd19b3d4d3c3a54c119269f89df2947a26d08b3163ea51bab98cc5214451545454a3b5448a181428386fdb3671bf710072fddb93bad612bd3dc9c46ddceb6ddbfad6b596c010c4b97ffa2bf7a66eeb72e48c31c6186b8cb1c6188a1aad25b1d692d6120e06531899c2070fccda22d87c5b9f16c182607104464646464646464646464646464646464646464646464646464646464646464646464646319e16139e29729afc48618509a5047f23e2dbb3c864520c3122e2f2afd11097cfa964182ce75c042bea30141515655886c16028282693c9d462d2048b26415204211549ee493018ac450483c18a603c4b5888afe7951e88e130bec7f1e4cccf5c432b7d9fc5b4115fbebce122c6186394524a29e79c734e4a29a5b5d66aadbdb789561351ca5c53b3d9d062a2b504fc526b7674045b5899cd62942f29a5ac734e7a7444e9acb2bfa0a0d6ac35d3f09fc642944fbb4859a83246d9cd58b44978511549dccef12594dac69cb7b46d3c5e7a9a6f3c5ea23c78b6114bfb4d4f43caa37ccd2809a8bce971a874f94d8f23a5b3b986ad5fd22e9e4b5d6b48e7ae730fb77ff9730dad919cbedd3510e5c583cb9761dae42895009b716adfe9cbb57aad5ef7eea07395b3d2f1d94268cc0524b4d211825b1013f07bd1318d45159c7176ef032c584c59166cd639699ddf62f68f4ff764f58074c3af13c216685047655c7e2b898edf5564c4b0811a9e86d5031a00be7c958ec29f73569a44ef8b7b82609dfaa02e2983f4b0e50a01f18e562942771ad346748efb145bcd47c8cf10a0558eb672fda03d407ef444d506e44d2ac4a4ab9da0d80f2051758dc462417c84fc6c2b9d988f915810212b9d55ceb6726d2b1ded4979dabfb7ebd0d5b2d2a1415bd47ec0ef058d05e1d087c38e33d1587bdcbd298780d8c2eeb68ee84e835eab88cea13dc576e330855cd76d5ec61d6b07a60037d6cb01936b5d4bd28e0ea1401bd3214051458d44557c930ef1f8a4d40787df2242d787748891a892df2aa2217cfb7428aab419258aaaa2a8ba2f7987bcb37ac014ba6ee3840f4e85a2aad326322776b87c2aa3435c3eccac1e30858eb56323a7b073a0059df4c88a5a394a394efeca9573a692d25a29ac1052aab37a51c94108394829a590e3385af29133f649f93b8d01b7f65566dcd2a7dcd26ea51355af1255b0b3d5662e44d3ef5bac0686cf9f374342c17b2988ed31144170fb925248df722f370c7fd906f774f66654de443f28b8fc1bdb6235302fb7b6cd98fd4b611c3ef731fccd88e176c27db998b2a51f6e7f9b41e0d6f6a8cc8200e743d8d3d2026beb8d9ca16ff1db0e42f8479c9e7c44d5d33fc1a2ea4f3f20fd134f54f544d56987b27a0c71fa3c07dd29c6315ba1c442f546cad03339439f96ea0da7a6dac3e9ebee81f4ec96f48ca46759cf669c76938bd3c71b057b31e8e3d8f634668b71dab110a7cf79d0f4513c70fa5a3fbdf807c169bdf92ca494524e1289d4829ef5eb4a67e55ae5c82757392bd74a67f55ae948b972c9954e4c8c43232b3951b5e28aaa5fd9892adbddacbca20a1655bf8203573a93eb68ae03ffe58d0a0c94b7bd220aaeeb4f0c0f77050c50500b979c8178fbbe51e1127b31e00a06e2f0390a6e38ec313d2d5e2e3eb480c104c161d1eab5d289897111fb70c4e5e356119da33ee5f27da2f0c171e4606681724bbbb4012184b385c9d1396b58db8305cb3568ad33aae09c733ecc7ae59aca3720ee032dbfd31f2ebb1f3c77c7ea0153986f6be782dc8e86f22a2753fab9723967ee92eedd9ee372cedc73f439ee7b87dc46ff5e99f175ad74563a2b1d9ea35d01b7454aa9fc9da3db9f96d2483fdaaa6dbfe518b7c8fa9103ff23ddfbac9dcd0549c2f86fcc376f5572f13e77b78f9b76bb0e9f571f26a731c66fc398dafb18ae5eab57b6a5c7586bac35d61a6b89ed638c4bf84b566bacfb8ec97794fe4a19295feafa0e8d65c915e35a7d4c75f5e1b8eb3a6ee6f04a67a5b3d2a12f52b44ea2c8e128f0272b9a150571a8c39f0c8a9f927c54fc3773ce99f6edebcbb0e54ce17d58f3ed936badc8eaf1f931d673e699f57ba639f3eb67ede45634ed2e03cf015b20bdfd567cdbe9ac95dea6d352777fdaa74fca9cd6a948560f98824aedb86967d062b42c58fcf7aa7c55be2a593bd6b27e441ba3b5d1da686db4ac1f31c6c87a45960ffba2b5b6e2afdf75c4ae43dc6996d54385ab4650c4ac7d29abb48fa58482e789274f14f102f0277b228894f2b3fc2c3fcb5c7f6616a40b92e7aec31a1b7ee77cfb3ecb1c1a106412aad829c1cab572ad5eac979cf265395f525abfd62925ab47544939a59c3b5a0e1f15e221fd4a87447afdb4a298b45c353bb30dfbf8b1dd603b7bf321297595e093f6cdf0396da523ff692b9ca3b7d56ba563ada6759d56fa28dbb45bc0f6e14fb8d2e1a9525801b92556701d7f9ea48f1bab07e438db60bd583d6adc8a36a29892bf35f9b71171d9698c838f7767f960e51a51c57a450d077dbb1755b405cc63dc70ba20b3c882c7a72d3939131d29fda31c1b95bbc082bd17f36244daa344f79859cc9bf1a40c1d23ed62e544b887ed31c6db9e3ea54d49184688379a2d4a578cd37f90db6e628a7e2b08ddb79e970f4ee96f2f49b7ae6f389cbe387d1a08efb641be6ddbcd86b3b966cf9bb13d9d4f38fd497f62416f284e54697bdb3e6f758bbf09f17971de353c4d0fb8d6522c07f7df8bae372122ba1759b115c96d2be2f1c1ddc37ab17a6c3ebc4d681bda66f083d041d8810ab63e9ddfd53c75bddd1827a62276e11c7c13534a68f812de0883c7273c72b1caf342a2e73f1f213f43808c04c984866644454d8eac788205abc70bd692e9d90324aa62373e3a04f5f1a991a8d282843a1b8fcf8a6893379fa669f0d6f91e765b776fa40a9c08e29b3f991340b66ef58aa909730dfb62489b1612badb20b6da37e3711cf771dbdede7f5cdb3aa86bd29fc8e4bea8c4544beb08dd5f74ccde8c18f8e29cd09212c1e3c7cbc94ec7143f81eeffffff4f83a409708cd583b5c37ab15e2cd80830754529a59434cba3a21e995998724e2925a471d35fb92895f265c7c5f86a8f91cf911010ba0e4ae5cbcd7abd94314ad96d248cff5305474fa870ed98ab9c950be3d5cb891027334080131f2f2eb43051309aadcf5d5a08c67e2818cd76d15aa86933d780792c730d6b6d6b48ef7b511c6a1ff7dc7d035c9b35b40c6b15384e8c5c3b2fcf6107d13b82455105e3e330c2d8c987a61997cf71d05c9461129226219ea35d52abad2c74af375a0b8ff163bd89aa8ae3ca79ed4415acfaa83c5185821da7bdfcf82f1ad0c2b96720a6e4ef8829f9dca5df13f91253b2a5097a6846d4c47464b2c2f424aa50b57bf60508dcef51c5021d8c97f95e6fb6d89b11f3f5a6e244d5092fbfba6a0ebc312c7f827cf13286e54fe05ca69b7ac30261c078199997e96290897bffdd1ff33acd624a96585c2a8ff26171b1e4b0bc5876744fd73d16b88084d005dc30e6e13fec1ec27b9a45557fb3135154dd5393a802c04b1f4eae37c3c59bde241455312fdf341455fff24db3a8da2fdf441455dd4bdd837d6071b9e2cbc41863ccc7bd5ddcf47029c4e2b200b01206e6022db80d9afe69762a923260b49c5c585c9ec5247461601e5beec58eff32ccad355617f35bd1864f17f70231256f6701998d829ffa9987b15130054309d73e465b816bbf5331a5fd095bfbbdb5efb6f633fbca19ed531bc696d9f77616882969bae1f25dec2df662c8dcc9dfbbfeae3831253f66d79b98920f3b0c636399ad65935012bad79b16afe51a2d505d73140a651292324c320a3d0cccbbd8dd86bb789c6bb89873da5a63fe6568c3610ccc4798bfbb6f80c3a4bc8b8df2766fb9468af62fbb5398ab85cbee94470bcbc924a4555850b49010974fdafd44c4e5e7dd35b7d56edadbc9b74006e4cbb43921bec8e2e177b92f3d0df9492f821ee28b80fbd2dbfc241cf9495de41c803cfe8c9bdfa0391adb2fa6e8d77aeffd6ddbe2b6d2a134462438c9e14f56841737007fb229867849453f7921e10cf8bde8bb7337e28ce9b90e07e051f87e9aad3cea75fad3ec44742a3a3581280420be00709a4118320fe371c83c8c00bc8c0c0a2f5377f7767fe400204d3198b23253310931f99882986211c009c0a3f098f3a84f01999f790ac0f894087ac081c2a75e0401e850f2675ee6673ea2d0a16487aa9d04665ee671ccc83c8ecf4f8ab906a9eebeedd690ae7677189ee8b7e151e7afdbce988add8605a15b5ee6617c37c556662a6f8a998274fa86e527cbdfdc78c094f9a8bbef3006839cf031c60ddb3ea2caee1f51f5c2e5ffe7cdd03fffe7efef4ef879c383e31997791b9a08e3651e078c979179181d8ed4c378185d0c303ed5d9d0c4ffba3bad85f40dcfe641bdfc8b8cb52fb294f52d29461bbeb564b6b6bb115373e5bb1642bd5e8c793a3539ed7d228aa9f9bf4f331e3b54d18b311fc6461dc999f9321be61afc068df2a8a23723fe7cd491940143e663bec54d69092f351fe6df7cfcefa9e893989aef82c7e7e6c390d94f42ba84220ebe14f793fb92f631fd34eb92bbe83ae5c1fb6936795e76a73c50bb531ede5f5e5dbbec3e7d6861d97dfbe0f34f27959d12e3ba0eca73882f1588a9f9e223aa62ec248e987a79bda2aaf2c4a24afecb0eccc7cb8f979e17205155bb07dc889433c5f20b76fb93cf8bd1398ee354b60a3538fa051b5b9421f330beeb0b6373714666bf38c373d01dcf287c796dd89b51b74cfaf764e87f4f8629668ac5d75da9eb5777cd39536cebac29c6039f8f6bb76124d01dcff87c4b54d4041f4595ed6c3c6e057e125558703751858aff54a029c65d7ac3e40c8a05f9bd02a7199f9b87cfffe123d0ddf4f28a4555fd098aaa6d7353101f21a621516502321989aadacdfa0204e4f7d8c17ee90bacb13c762faf24e83efb8bbc11461927a93eb6f33d0827a9cef83768e6d0fea9365d7c11c566515e85fb9eda17cbffb9403ae86d425c7e4dc6db8c789bb1ce6d7a3c390de6a727bd5ccc36f25f9bf23854baed71a0fc93331bc65ddc9dba5a433a3eb775189e70f8a67f1942536c035c9736e9391a7b136f3342bccd08237ed8722fc7486349680825677913daf27ce69acae3cc1ab7b759297243780e3d74d73cc67f9be328d7d1cf67d05d739596dad9789c3eaec2013a673bb9b94aa774e167c4729d07544007053210011c16b800af41db8ed3a0e17dba1a41cf97f5b7b7a1b9b54349400ba1d9a8c42d75a639ea14910c000014a315000030140c89c322a1501405829aeb1d14800b839a4a6450988a034a90842888318c184214020000400c109999a9a1420008040026604c40cc00cc81980098033001300163026206600ec404c01c800900d39ad962ddcf6fa366e7cb9f4600de1cd0148c29802980790073105330e600a6604c014c01cc03985f103023f54977fbde90d80cb223a95a02654065804a804a80940195002f012a012a0754065c02bc0ca8047809502281c00c3c6128526296ef8925346130334013e026802680e614003447c7072ff1a53b4f0270a3890011961fb0e845485018f031abfad970e13309a609304d904f5829e059e8540efda3351ddd04dfb5efa40dc5252e2b60dede6f502acfa2a2b36da04494c87d6e7cfa9bc37ff32d1f63c4d10d9f167bd8d9ed59944a53fa58eefcf7c1d72bf9000ecd48e6ef3969a7ccfdcb89537d79f21a937fbbd66738dfaa07bf0a90d5a35e5b6f991268b56b5273e5937f43eb6d4025755674a066d11503bec58c1396c9f793fe73de6d107e047fb3769be0ecfb6149411304d602ab1ff36ee968cbd37cee25046b5562dc3482c025c4329abe901d211f03afa0a41aa796245a32a991aa269ac1d3c6d58c0361fa6602df8d6281ab6b863992e05e840ae5497996ea6e698c1b145c9d21a87171adcfffcfc9272dc537168a770303d7f1086d86a7a4719718db0caefc97a9f7244a632d575c07f94d1db9b415d9a6a9e2ac4ff472519ca9f4ea63eb157e20c6082e42d1354c5d4075cda21f246aa92204506568332de3ee640a0122aaeb690c52d97ba6cc5fe5e67be1df59b21e56018c03394022f3eb9ecd82280a40fcbcf456a615cef2c026f59038283348df8b70b47ad3a403bf85aedfa13a6b6f6ab9a08e59f7a9d1f100ce269e14db53dcf82cbe1ede51b0ea4da78616cae098c1f827a6e09d583c6454806f427c670ae8c0240b463cacf20bd4d059b78e10df3b6160676ef873e967ff0f57208778628e760cad931c5b6d45ba83129a9061137e8500c730a8b1dd51e1ccd8daf1e0a0cd2239e4ccd8b9512f0db63d96c6a93c5d257b52a075e85df3b56a734abdc0e43b29a367d1e29eb6819df8f3d48795031a0befe460afe8be1d05a089f4f6920931e41e3b9e75eaabb555badb85e1858387cf97f99c1bc2e48fad76b3f42674557980a738028ed571dcbb0d4b9df8975ea0eeb3410953027b940e391131fcf4b209c7813ac248aa628d6f6d9bac2c26881056e7a119ae7defee9a59976e78974d654c5424ba2889660abd289b24362feaa99f407da23711820e0514360877a28df52fd337d6707580d6196ffdf02273bd034bc369a2774bcb2b3b9c5f0187a525ba121b71bf4eaf2992e2ecda86a12eb33079837354a5eb56b6ad7432d8bdd83ced3756d301c5f59b052bd7b3afd23e35ef9266a2c380ac560cf20971638692379c19a4765e50fb570545a47594d9ff2919eaa7d4cf1d13a8ec1be05fe254a3fff9529f563d16e10f20a9f674f876563d732d47aba7f5a9313f1d690352552b8ed7d1f3b47be7c23c51abf3f2edc0e2f4c0a1ecbb8080276bc5f10523da4864c2f1867f80d88acfc63969f3f83caf8048864953dc1061206fc0e3419ed67914de79c6decd98719e2518c0eb5dc55c4957b975a39ed8d035137e965c2e856c1c0e03678a9ec7c95b898e33ba6365dc2379f76c98a9038af09d74cf82ad548f677e8d98946afb6a6b2e451c5a3ddb187ea5dfc9c21ee1c5d4313e135253f58a309eb08d9c71cd9ddb358d3f21180ef0f1001e1424576c7fb4e780c5b35740d7d143078ab79a143574a022de93b6cd9c56b0e3407621061df2a05fa1696cd91bf72849b4911cb982218cba0acb2d8db512daf567329f4b747395303a4de209dea14170214f9613c7c9942d23a4963d358c9b0f99ba347602a1d061d22773516f73e45a79cfb4455552240727422b8e9e19dc99e16d634b9157c85ce9e3c581cc72765671586c6cce0fee7f0eee7601a5dad43f12831b8dadc4ce301bbc540b5e67450850d9d749ef34e7a8c22fced215c41b38668156f16ccabfcf6a386a1d8c606fb458581775a1b1627d5a7c8912cef368b1c21acde68cac0a159ddd9076f12c2ff95155e7e4861e370521e984b7f90d0c0c48ae27129d8ebbb5acf315eb18cb2a63f56bddd2edfbe3f68a27afb53c57b04ead9806949f0403ad5266eccfc401ae95a8adc8839f8eccdfe54b58a5376e8d3457cdc4005e1d0fdca48d3a57eb719c90aab4e6397044309137299d4423649058484796ab0e3248eca1b8a61a06196e2d45dbf9760224e2a9fa7817342f9bf813a8585d78247a532eb237da8d0ab3e850373207069f409431ec3c64ec23b072d369e1c36728208e43e9ce9ecc43a212a49b1a6aa8a8e3684267b8ba0a8cc0af48c88f80a609121fc3fca8bcf84c81099ede1d563bd21feeaabda70f0347aacab59df5c9b9b93c7d4558d641d8d1c885c9331683327191f5160b0f3330889082d73927e4ee8b0b3feb04cc4b09f43cc51b6e1106eebfc16affe0d7936d1dad8b34883816eb49fea43e88f36aae6b11a9b4f5578c8f05b0b5b59334ecdd25fdf4cac2c09d52fb114b2440459a42f552498729a02007cfe48419f125102ea50eaa4d21833b39b1532f334dc7d635bf4d0b9323bd500b0a382de9702f911d3b6c16e7486a94be4097d2e6293f194fc442e68748f931137d7ecc39f5e5325e14b0d3cb023638a9e54d40a27cce419b82e9b7fd927630c0de85a4c600e853df12c53ad273911ee56526608557ca05e396a8d81dc8f2071ad71a47b4d48194667acf361e5c72bd3dcc97c98dbf95225f86e178ebfc6c6e4514cbd110bd95a9a7262fe256c625bef211b0f744ec6f7fe03192091cae7c386bc846b40512383d94790d3be59de0acd070d69b57b8e5d1676544878a5d390d93c4699fc1a47c94cecad56a4b3b7d9252ecd2497eaaa7696cf0bbd3bc39fe1f3a1460b01faf6922a18cb54951122495d75fc2e701c88d56dd4607bd5ebd79b66c382feaf990994898fdcaab91621062189dc7f84179abb33d7f7683886215bb3b68fb9460d4c2495345f7249189a3fee0386051111431d33a88647a3241b388a16dc35cc5733f3c4199cb6e490fe9c9119680e332400cfab9b733e8566d63622182deb3d8c4d0a0c58f188aaa843ae2012a9e2811a468169c4d2fe734baa4f90ce33489078a6269cb12ee4ccbaf9a32f0a8ac0899bc2383e70c0b1493b1d37f9a22cb5518ada9daf799dba0906b053cfe8925050e1fd9ac5a0140cc4cbfa2eb8ceb6cbf40001a603f8da0b172eb24aa5d7c6486134982350d1fd9f07949908c8807b01b60b4011a570d9bf09181daa5e279b717f021c8fba206ba3c361500eeb1ded9f57b3ea11169f76458f92f486613880f5a26fe84c04ba54841d02aca921408640ff000fc2548492a66ea6579ab06f91387aac7f2aebb135a4859477ac7fa30df5435de3a7dac71da5f5c2c443f1d682b8670ee0ea70b80f2c7663b3e5b748bbcc898e7715a9b1e3eb734da4d8500a35e0d2152dc6a7eaaa9d69742fae122dd4b7f9688d4a990dc2bc14827431a6b1856c876380a39a7d1c61f95fd1b61b75b86aa64db9cb058eb50ed1185e62265b414816043e17226721164510b4dbb30cbec7f9e2f625ee0e108f94a60c7de881caa2e508617918f1841e09adf7e452cd89406921c09f10e5861a2d3c1a9118f695276e370d1f6508e88bab5633266b5bab85215fb6a352f2702e0b1869bc732e05ec706cc702383a8e0501d58626bea93eb9e66486f66dce60c06bdaff2718e427f27aa1ce10c066a0fb5e30eeb24c51ee4f687fdf9264b77a211f3834d7facbeed0f238211ef8ad5ca68da6636e811f105d2df1a423d06256a99f3845d760ff4a6d326b79c6baeba489615039e1e42f0bcf18fd09e379e8f6d78b3880888a9377559b5b3e5d06c102dd1a72359b20927be899376e8330aefd93485ec0025384566744106ca088186b398c8e086cfecff9fb43e54e52b1423cdd9d434dd73b31523bb123c17780373d48ab2333697944a06de6cb3141927ba3c42feeb72ff1de2be7954f6504c9b67adb9ce773d7e6baad0be14ba6c6c9954660bc3bd16feca9d734c765ab2023ddb67fc6648f495116353ce0bfa5663ae708d831518a7ccfddf67549a1314bc0d39412b89226ae7fd632778b249d293d89c42dc61d0995db5e64af701a9f88854b4c7ff7d20e82c6988efc595d8dbe368bb510d34e00f62edd724a66572941486985edc010d6cfe9fa845c5de2372bac9b1520260b2b02059f4df5bde705b99a83fedef25b73935e9b3fc8f6fe55f6fad5a304b16717088c5c751e1c9ac801d21ef5fa5484423e1bf424e44945ee82bca24a22543e13e6a137b80fbea8cc187b9b78f8af305e6612682a8ca5add0290a786e0abc33881acec66435f3a5eb50c118f61da96ea3e40fef88ad6f3af7be8a491b228458c69850011f322ea1d4c21e2a71c1781f93369c645de493d97f933371f38886736af5b830ed8077447c3b471148bb241c1e689fa6bad3497475bc7764ab2848c73d9310084e3788d5ef37c565052550c7b3c87c73938b1600c1cefb84c6e46ad50cea6fe0cad6b6ccd0c2ab53e848c21ee311bb45e320ec5c6246332d54de9f6b6540c3bbeba894bab1ad1f7da68da3ab3c8c421fff8e1801e066c0333586aa07597c9549f98d1316e89d99e6b711da51c096912d86466982c5ffcf192d855638e7de790b346bbd1766eab3ee9bcef0b27bab4fc00bf358b048387d161c4065cd1d11777a582ce6414dc5de5feb1c758a0b4743df69a51f28924385c64047bffcedf9b063ee423d9e8a8fb389937400a97a54eb708b956d6e0bd33e283fd15c2d46c008f7c3fdb1e2f2e130389d47a7d3780460fccf2d30e0dafec74401cf0a9d192b812fade4ab4eb31e6b2c9b86f64c131b02a3c8006624baf7b34204e71295227acdb12a6ee876a3812de6ad6b1d5a4d10e40f4c71c71e6d8482f49d7e1b084e405c612fa1459bbc30f9ea1e406d6d0fdced59103b63b3f5f216d81a5d8932a4809afaf380236a599caeacc01b8eaf9e913db3709b67513795df0963672ac65cbdd9be18b9c92afc913ebdff58372f4f5d41418f731496cfdb0720756a811ac81eacfd38406afa0df633bfc58033092d0c8001578b798093bad91c9022c29698fc17a9c2a47d78d03260e2592b0026b418ca0e8ddee57b93ee55319b8447446b3c5413d16a89d80637e83d651af25df5c7287dd50dd45c05b088b59c929664dd40a11964cd3c511b9a851addc97463328cca31464124108c5988f9b70044a215e6462d8192da84f9d9d298a63a5c21b7313a8feea100c438afaea79e3f7e955c97f99877e44581dd5c73f38d6b58008f8b99eebf75e72e3d6ffab3373cf10a60a1fa01bfb95398a876b33cf3ca264f3e85b6ef4d2ea562f2e0028ffade636dc50b13953f54fb7a7349735fd987464d187d8803f1dd973ea8455c96dd629daccba5c7dc204069cbe227ed7834808b45de3075e7ca1d50840a23df32551a0ba1250455b7af6e90281748e9d21747606110e71a121a235c359d7270834aad631698202c797115b3adc61b126bf835a6292674069ab12a19d1eab01ef2603895bd302d6aab60767439a298e9bc61f32b1289d89a943e793beb47c4a25fc6889eca9526ca125668170269336817c4489c0d57f484c92a879d8e495b9aa99c290c3b6e5f51d79f251ade66a34e73911763e5a1b09aeaddae99f626fd802ae8f4bf280216a67fe8ae68e28d016121b61182396145a23b44ba0406357c68480660a4307f7a417bf4bbfcba025de863731a7ec01979dc5de4e22c012c3c15e58e8e060316d5db194bcc4f9c50dd254af1dd4e7a796da319725067b8f5c07305e3c468295f4476e9f4d9ca2db4d6c2611ae4b5da1391d6a4ed2dad96eb368d95d3246ffdf4fc82edcdca576fb6b461a7975fcf1983a5e7d1b642dfb4dc6ed6750e2a50f3d2295b6d3a80e9fc7617c2e5117d522b1dc2864485564588124653188909458b5efa884e877c28e6956a20efb773097383e74fa17cbfa09ffa46afc94f93fd224b393a7fa7da98a653c4f00dec7b7ad2f62376e6b1153d7beacaa25ee20eff3979555a33a337e20e2a0bdeed0508744fea85d52644a0caa104a90a1cb9c64b477abf7a495de037c78340105571f42cf420a06dcfd520abd7d3c4d66313805fb3542b7cbe5e635c43cccb47cc2c66a6f6bd92a45c2cf3ae03ef79b0c5472ae6e2ee62caec759de577a6d5b8234c3af46ffb93d433dbeefc413587cd09ac5bc810d29efdde83c030949ecaf4fd17f5a3c7899937734e1003255f6f56eea1f1ab8d645f868170fe05dd29f67e7e9f6795b3cfb1a678e95c1c12d92e74788454645a55714a7fe235c1c1d43e3014eff563a4560d14aef19a433f96a9bfb9c04e5135c0a4fb8611b1db98f47d263aedc3682cc34e9121170450b04ab755813303f7d1bb9d819f0306de80a6e40556f2e1f647ac39b58c0240ab11f470817b057cb55a544df396e6e1da39503f025ca043016b96063ba12379c1898a0fc4dd7648357b6577f592a594adf4658d6cd363199d2ba160ff456c75d12721905b98a504f2839721c5388b5245accfe534cf1f66b4a8295ae2fcf434ec79e013460a86cdfa7c68933c85cac36bdbf2f7fd9a81a39943034892b0098d603250961b21e294a4dbab0cc94314250765a6856fdb070ca28e938316b67e7cce63f4e1f24df2ae18f990eb534fcb1ca9d807eb061dac62a276389dad87b1116b1959adb00b8297b25b22b9268a0d534e2667bf9bffd1e861d4f46b68efbc325a3e3e3bad4ad713d86bf570c4e42fcd1aaa0c85e012150485150b4bd627c5b8ca746122f7413b779dc2d62cd73dadd6d7e28132f91fb9a4db2c93a53b06f18657f7fb921995badf567e147c9a73099c5373249252ef4277e66edf43ffeb5a0d58fd7543d84eff636e5c829ef423c2b3527a35230a286812a92124567e7c2a9df870e0fbae11e7ef2fdc174eb9db8963131aceaa7dae357e4d68c4171a307f185faed9bb02f6ee8fdfca5c7d87726428e89c9473e32f301d7f2c9ef39bfcac7c7d227069c1bd063e9fec6c26ede5ae56917e7626cb39d5210c5ea719510df3cb4b576dca3e4595bf19f25de889dd400a42d9592d16ee6a521c21b7f8bca5a5cf71de6eab7ae4d635254f22961c3ad99cdeec956a2af1a305e8883f2ff63ced7b9fb43088f859e17f83129269575a4c41a2e21168233f0b5501c23c4b0384c1c5e472f5cb77ce62bf7f9cac6fda53c390be5825c82647e496c341b0c06a020495c0c5cad61563fa699c761362e7e4e131c8a5e49f9f76128f342f6076f73a5ebeb5cf4a3b5c198ad4dc21ee764d282c3491c4fdc60c5664a8810b85ff79ac8c2350ffc9a17d59369a3875664386ff38c27f98cc11fa28e16cc75c71b23fac0f8b4040b229e16672926d04f8fae079c8b0280110ac1f1b7cd6f8184741b1927a93438c4586515abbfa1dcea14757ff2b7b61115f62a2502ceef8331fb8e91d6f207c5633bf83c9a74d0b2711293f2bc8dbc72a6fa547a29ef69e4a62053219192528ce2607828f38270b2648ec1cbac345aad48cf1ba576b6d72f5cd955fbd3c19c218f6ff987c12d99753d97b31104ad4e1758ecf4ce6467b770e8f1e5183e3b0aefc77e35f08e0f14cbfb58453b3d94ba1bdb3b0213fec5222b3f08cfa662a1f5b987c75005d2d05866d3bef1407a880a004a8bba13e698366f32811aa8f2ef696fd4f28dbe06053fc42d446a9cf3c8bb3b5883e742f419e8f6b30760cc98a7e1841f1221a17d7d4dacf560760f6cc8b993ff611b4ee9586b46464af04fd17781688a57502f964e222e3529a637e1226c264f943e1cc699f0efdaaee8e4b16bfb39847fe242b464a40d1d65756cf8b9058b0fa1fc1a1f6f53339f1122e8fe3ecb384350732351a3a1a14d56b0ad2959cd7674c8358e60a75b51d520899a6da412a24cfb5c4a56921708485facae9231f0a5d8614252886d80982a926bf32238f3d51d4b9f003d623dd605ddf08f3f883526d7768e6597a5fceb14bf28c4831b1cb1d2d8c82c327fed9d0b6514e4227cf95b2bf31a3c69c7af7208a522ed16b343bf1767482c52b57b84d96583d5816d69f7ad63932880561df413f357b37f687a32278362b38ff9d4b9d676c873404ad7a967c98805495b7809a73f177fa585c968b4344b6992ea8f8439e7cd3e226bf78a59ba179bae58c952584a674838cab3544efa6cd52a102e68f41e66b6a7f965c5912e546488888a0767220b114993401db38878f3944ba4b8275696f5e10377c54931601b21016bf5cf8b07321dea3c97d78636a7bfc6f378122fa015849092b68bcb655940dfe87a508fb65216210849181911ceb28c6824ebf70c1e0131a2fb85a4bfcff2d7e77bf95ec441922814874de36e717980c3a6047e382e174450a4327878a934a4bd7974bd1128dcb71aa7ec11e0986495c660b535b877e3125612191c0ac1629fbfc79d033197bd40e136764b792b1b02fd871a13f561b40455236e65c8184a46fb44486df70dd1bdf91ec2f39f5c29baf000bc49fdb51a53f0d39f915ff25d4aa953782ff129bbe081020c5e612e5264206ab2044e23fbf6df546318da6d7f3e010a5e814a4c74bdbec8ec6b2a70aa68dad5cbd205aff91c7cabf43536305a1bd68491ebfba7564cbba07582fb5b0976779a244cb38747f56f482a982c8589306554e69222e862d084ad62d3ac1252bb9b893a48d87965044735f2397fdcfa3d655c6889abcb65a8b5ab18c4895b6da5e9b7bee8aec36c606a6641621bf3ae91063296ff4e6dc2b1dddeafcb84c7f913ba1aa15e30120c8f5e0ad83e7bff98975e4f08f0942902f41de5e10040d5a3bef8e9c5c0cf7f7f62522e2a537829471698417519d6829e8908b2cab837c982677adc9248ae5c9833f0dd6d6f6664888b3f6777a379f9c15ce964acc0c05bb7f331c18477991a5303c9ce0ed6b006a1d185df1fefa78075713ac7bd6b2c60cc613cb789f612f206a684c7365b5cacc7d3b16a775a53fc7098e6cd8a889f66e89e1112fbebcabfd4b8e0e66f4786be126f41aa5d978f03a7706306819682408c90dfc421d6f55ea7fb7554d81ec3882fa8a0a0660f51ec391f47247dd3b84cce819ba27833fbc637c93bd4cd3e567e8cac0d22c10f4ff1151b173460c61bbad0bb9f5ae3d847302200c047b6eb4d39bf3629a0f4aecb2d173fe3a6f11d2b3dd45a715bc84a435d92d5cc0b34df9699274dda1f0f50469247595b3ab069d0be3245d64bb22faa30c90a50081565aed5f32867534b456c758269114d39528bbbf6a31ca9d5e1fc79f913ff081d2a2bcdbc55b1ac573f6216ae8359f9f2a625ed35d1af24fe8536346190153b4671025a4595629b2060b77714df023a43fd28db88f4727356baa0b4ca301ad1cb1166a509628dd4b01182b3223d059e15abd1836810a8c079f0822932a234b1e40947cbe0bce8520d91a7445b64a57e4a635e7682b5818a786728c88be3cb1f5c368d3ba8f131aa400c28ddf1f4fe2f2d1766484be953c0258aff46e0320ae351f417c19729f8035a84085c42e11fe82320f0128ae352fc03c12e08b8e4fe45742fe6fc08cf121406542fa3437818319e5eb51f529225acdcb5a7071c37377378689bcd01f938a1192bb8c6d9dbbeceb1c752f7a5ba7ae9a0ecaa0a912185ce4130a90ecbe26e660ba7b0b67c8227ce5c886b76fc862172c691384a085728a225dbd107b7f80fc5af913afce3b1f41634b90729ee7c946d7101df3dc26ad987b1b574429b91f0028e94e4eb41d294a9d241216ffe806e5c604ac95832862f00fed8fc1d232cb382f44228b1e2a8a265095c223d628d8914d5b4a71b6564036bc71bff852686c61a995f28922502fae7a93466358f3bdbb9f54b37971a804c43840b12bef1304bbf9c853d4bb99503b4db5a4db8d4adde56858ba545c4930549d07ef296a47213378d01f4822cb1e18a20cb2efce544903f77ea6aa71613b4062f1b15cb8bf85552e33f109e840fa82454962af42add5fcfb7bc76e98b6b41ece4f8931fbd859e6f11b7685a3f684e82b4896e42517e0d0f340947d7f7553740378e79f6f5942364135f2c40f0d0a8cc0e160593130092bd16e30111ba418d00f31668855b0d9f7f4be84f20c61f4edb0d8c35255c5a0010b30400ba27e96bd0eb4848109eff46a6b89cbf3e7ed21b602711fbd936407f037617f0997426b94648ee684369257f10a2469990804cab66934ae28903900bdea986a34a92e3acf1ec08b4482f0ca51549fa8d19f7bfd0b870947a692fe6488f2001fb3be37f27efbb2bacb8397b0110954b1665b57f7dc4444895bd008baa0536ee5f4ac00e72b56eefa90188be86fea7294a4ba6b09769faff69e00188ffbd6c8d4d5c5d716422fbb7114fffc865426ee49522252f00931afcf1be8ffcfe8f65a864b34b5b07ed51545a26aa8693fc88a9c6aa368230e23f159a85df6332274b88ee07ef76e5481a8586597ec1d1d97f2ba9aecefb119dd0a6eae87e579d5c3426159757e753889eb2d17ea334e8742da8cbd9548b7d0b12829bd1292c25f022de31f5f5cb9c2dec96897ddd6bc4b90e39d33b43aac5b8e366553548e1b4ef1fb020970a6840771530f3a6ca5ce304ad2c272fa86cc3d8f5df2ad70c8fecd6c7e4c2199010f1f70ec204f889cafda721f05a17196c7135a2479002759bb841d62a9e77ad390825dadf97b204ae725bb556334942629279f9fbb4afd170208754bc5babc1096716c95aba3429f6a2364357066b95d58a9311eed15bfff5ad77f60d39a821c2a85ff1932a1428edef0117bfd3aa824a8ba697da58cd35ee6884818b661ea91819118c7f77dc84da57540c1fdb5857ac3a34a2f42c8e5a5e72a76939854e90c18655f170a705681bf46d9e423f1b4032676838f5a1a141059253235983b4a779dff2522e787b4c86ee9f131f09de0a48d13af15364f7bb36ba3b4204fab080a038a8249d928c50c20221a4a59e03b5e9187e141111740c29d6981c06bd07020d0c779eaad808c6807bccf2d69049a056b723124cc624e9ad95ebc1a8510d36faedcb0c591bb50a4ddd407a4bd92dabc10124437cc7453b5fe2e4f4b4658c71c122b3c4242d9044b25488a77a711b6922b533e05e3814a76d7322bbe39e909e82a0060627880ee97fcc2001460bd82a905bfed606086c2068f1da79126e5079d0979f256c8e371192cbaa89618b85a1dbb264cda258e2daf2607c0a1675397585b9c04134b0eb1b5b2ec325332a112863e904a8db6080c13755624984885392fc9485b081d5b110607351d43be063175ec05818028c18fa32a788beba3dcbcfa765375952e85f7d03a77c127a7189f7fcba66302591cc09e2615ada7904bccc74838427f0452bc11b1319fe5418885c6a9ecb3efc9db4acb2479a680d77bf53ab6be6d4ad48aa916e7b8e47ddd0d21e552c375697a17199638e6842146532d4a561352b4d239380fc5ba8dc86ae603ae4085520ab461d7a14b5b941b1c568b80036a9d7548a824d0f3ca40f81a28c8a3f87e927202485b047c1e2c65a76a53fdd0cf7155e08d5c07cba4f7b18c1924ce1903b4e80ffd4b75f30ec2bb16099bdd095cd0084dec6a918120e4dea26e45348061ef76b2def870e710f35ac3610abd73ddfc5a6b288f95c62e8c1be6d0ac1a90a6731a77c221f675d381bd1265c3856a6637a45b1052f89e350f0f4a57188b94aca4c2a39b40e2bd8014ac338cc1e00ebdab05e7b968db62f85ed2fbc33b7543b92cf944e7acac6781bae49fd19e7ac0315770715481735c7b0ab2a8cfa2d5a7c110988ee3a68e4b0019fda316462b367e315008a9a6592b883dfd0d1910ba64e610b44ff27c80e390466a07d2de6abbc18771b5a7cc7840183368eea050f819c661c46358f151914e8843549f7cbcb32b61970b509868ca9b69c9126313b52603edfc66d28537a02d3949a544ba63636be49563eb55d059c9de0a88a45aece58c7b5eabd77db2a353f12ee615c4901c6a2bef0ab51bc70b6b35e8053bfead2024a0c7749a8163c89dcc3a538ade2371d674dda4469ce205d875f3d132923f253d02620915413b0320043e8680823c723ac89bd5c51f952697d1fa4592343450070de10685f1227a9482b649a1f4c5973112c19eda3cbb104422f78c9d902314fc7e43218f86f8f4cc594ce888e806f43e3416a676390aecdc801944b43fac853c00dc5f363fc03ae0c3f16a40bb8f249e9958732236b94eebbafc0171216799a7db432611fc1b988a2f63fbcdf6f16ea480a28624f47d1143428d8d9500403fe50025330b10271c619b6cc365d47ccdab97e051efac3c5a7d8962fd62c575240b477b49afe575b2b1f49654340fa442f5bcfac0b94aadd9915cc4d9a485795661707fe2a9287754ba59ef328363b84403e8e08d58bdf24551972a45fb774db83213563ff4990245d125bb62cf6ae42e78b6fcf2e92120ec4b7a7154e633e74758dae37020da7375949615c6bc15184f26aa3cfc4206a5940136421b23df386833c8fe2ed8108e304ab7bf4c91c69b88c9aff341f2dd16949b149c77f71e68290ee90849f4a6808c14eb9aefeea8582c942cef3b5cc7f450e5e03d3db01c5c6fe7a0a9d95a4cd4cda26b0c542f904b5c025aa210c5e14f3c05c3f49344b6168e774a768dcfbdf14865380ef6e00422cd48bc9eff701a2a4f32b8ab671492491924b30b9266abb15d2e5138194f8e8773d1229973467a6d1a3c44bf3ab3b3e72917b3b29cee93f0645f811e4a038040ae5322cad15061bb979d70d95ac69a1e2c3eb6671670dac875f1bc5e490980b44e40052d6358839e08a229efcf0a15d3ee36c0eb51469a3488e90f47c9d1b8c257082b9bde7df37af533e371f89b43a4a5034a16e64149af8ebd52682c54078648a5f2878fdce3310e18d3e205e893ab0e78a5409bd1d619e9bdb550a84075722613954ad4d0cc7eae4d4ea8042ea76edd0d8aa7a3ad1bee8f390c292a9b470c2372c827c08942160314fb18f2abfcfc931de14c9431de1ced81f8a9c15ae1c08520a381ec34a966bd0ce68ba273d130992725486b1ba64c109b70235de50af0e75ba1154d0b916b5c9dbdd5dae19cd56966b4d6aa0c2b24930619742c7a5d3d81b9606157df5968ae8ba2594619270317347764b87924955da98411346674726a8bbfd93deb65994343e260b8b50cbb062a4714422da50a34dd2da2106b8d059853fba26cba7c2a6ee23985cd14ec6683e16b7f37324e5f290c80564ea7d117db318f3551c90ae14e13412e6e3db02f82a5b8aded061bc1a93e7a2e6258b911852f35f69e9c7bee8de409946a11976b7b972a4c08d425c48fa522e1582113130600ab03b9d0039e4aeac71283b9145502ad59c06d6fe00c197fe6dcdd50e4fc803752cd12351a4bc6005adcf8db3d8b38e910c6dfc9eae24138331ceda2d44bfa81e53f905bed90b10fc37a72c097085686af13d146118991408d8d4735aead758340e97542c8183e39b7fb9f7d8def70f98622b61b28b0edc0a00c758d625c97ef3fb8bb6ad471d8b8ed6af045d00d5d6c4a891888412e984b47fbc35c4f95b05f8bd20d57257ebe0c2c81b15d0d81074116181eeb1e4b0297fb3df07978f2cfe77bac02fcad002d470725e4148ce3d99c71ae29e2c6b9c4df99510022142a275bec4451c3c701d9a77ef9dea4fc9fe80b084442279cee246b320aef439bad59fb0c1a56a1925059a9613dead9cebb079182328c687477e4fff81d31f395f67ac2d908cbee0c8865957d92b6fb99d6535ee8a08e62d870921cafe304f3397dd2e57afe19a9f66d1b63bf35845299bc12364e3a7cdf8345bc259a4f669b9b7e8034f3f5a6e70bc899ad803d35731c7f369cb8a1c2dd0680e510b3264b6c2d3d766b9ea2f2f6a3964b0dd1a60411f7f6812e735444eaea8d1c5abd46aa4fe52e339796c4020e85f91657ec5563c98b8315340242986e7748ca4f966409fde6dc4a01f2b2c74c95abc0f16e5796bb80e126162107e147c92c0058a886055b58f916ae90fa1a058944d5f4ec3c4f8baf0efa3bf5f1c093eb828368215918ad83a911222257f1bd5ee935bf643273d2d43272db2e3bf8c7e939a7603a67cee368f5f6e975b5ca71a40a4188daf2a9ca4dc203040b3d435245fe53159954e3723c519014ef15cd0bace0804c8c9c910faed3bc58b059a270e6f52873ebd3b6fbd4b791e28e13eaa19c4b46bbced34d56dc759834c54d647fac526c48b798a7c1e2003cd2480fdd2919caa62e852032c2baa6ef1e2c68c1b10365f37c41f500d3621af8ff61b1975c18ffe647b9d2e30247de90e285efc8331e73ae519de25b63fd40fb062dd7886ca5bbc47ffc6e4fbe5bc24ad04cee9e72fb8a0d5112e261b94cf3aee98f0ad9f0451d53b0b107367ab4b8068137a7958b30a6f8391f25e32edc8440ba6788e7206c623e4796fe04b64bd5dbbd2a59f3412517592b433034a8509e3c42248208fc63a75fddc3c3b3d58f1732d3732d790355889601501a069193f68c25e1c610a843e2116125a5f129178a89df4564dc7adc81c627ea290d5e6bf294058036123733db35d308fa16f7916e2f3d170a6c71377e49313dcc4813c9de988731be92f649f5bb35dd11e8f2e477525c4dbfb5c20d907c3b50cc790addbeb2f0948f9f60a356a22f7fe384045b869fc918cef88a8d57b8a97bb25122112809453265dac8c853f1f901dc98342c20246d4a576d95504c1f81da525cf25c5c6ef670390168579360cc75f92eb17028ab5374abcb65c3f96686f0649f5c0ef1c5d75ee01bdc8568cffdc77d77049ca85ec52cc1ad8c1ccb5aeeb12a7c2ec8a4d078cc6cc9ae1b395691b832bdcfb9f95d42d325de0873319412ae426deb8116013ce42919d95d4482b43489ec69f571a1c756c293c501b15057da69abebc42832b0b18ffd88956c83cb65bf8f60c630d789ff761312877ae9a1555c7f05741cef580fa1a17c24b73d702740b9230227d156cd813815c73a777cf790f5381054d45d58e68ecdeb3d9a12c74299602ac8e58c25c764c6989f74a838a84243d4da9a719977079bbb10d5c5b93b6448c1fe0b85f5e36476a060142c340f6b15115b469189e0084ac075436ca663b35b419723f63719cd4a87a119e0725faf78e61c1c518170a82d6ec4ec72db2aea146116a183fde20eab21f200f6188997e77e5c24b221fe0000be70f44790f70e94030268fcec5d91ecef4d1aa98914f7394df33d5336fa1f4ee7f68b2038e1a0b21980bd52a24f99a379ccc7ee8e42f2245b98789444405f0e8b9f1086966ce0e69b1565ff8a0ec990ad71e6417b02e8dc2f71eca999dfd830d9488dc08d8a39906337442e3ad01b6ff27f005c268d1b99e129420158ef27d6039e9897ca65cc9095e019f45e244e0ee3ce6e3b5a872be8be0a8c8599b5505486be967280acdaa66a85a904a06d71eab8dc5db9ef22f589004f871278b48bc6ca869e170d3c64c9c9246645c150dd2a2ee582a4b68b4c6f58c5cb0a13fedb49dcc7ea98bcda6fc993ad0f68458a561caa4f3e28b504123bf79d491c8ee240ee3707e4407d5403169a89419a8ac213848efba02b97be6ad501c73e2e1f505802935265bb5fec0511aaa4d0a09b5dc39ecf4ea3dcd5b452403771f66497bc6ebe41b42c2302e8b92a5fc14aa202f663f81b0ccd15493ca45877816d40f6bb709a988df0a1a1ef02b868920908946d1466aaa2454c4dbd064be9ca5a0da9a685232b5e67c7495c0316f696280609a3f0ef85ca1be3cef4bc837e7b0cb99376798f275eb2788f9f0378d5bf878c089400de60aba9b91801ff842ece22d2d5f02cecd883b68a08543aec4e7be1747817488a3d54b5842f9839239abc6593914f80f8a345be66acc65f34a01d40ad20148396eeea4fa1080745db7c52c43f4d2447cdaf8ad87849953de7f8e0b39d3cb7529ae22d81b0cb54254358a01d30f402d8b81e90bdfa27d1dfecfd475145c6071d13fa6110e47d5e0311e8a7a58cc228c01a2c6f47099e56b609bcc6eac542d95dd66d95fddf24c75e24a053b92ee4df897e1e528cabf10f3a273327b12eeb6d5d223430765f842861d4fc28839075961c632a913a27586cc1be7e36cc64677138caf9fcf185b43c600ca180633584e1c86a3e4d0b9873e2c672c098d4be68f5360271980956d334a4eb295a41ce3b94cef07ce9da4209e2caddc6270bdfcdd78018ab692be5d3d017415e66fc3e5ae0c046c25f1b3346052a484aae89f4e5b2be934f64061dea3364ae806840934cc342cf51da854d9c627c363b5b97be018c887cbe194a6a5236952a79c56e74feec6ccea157acae694cd3fcf288696a0b19e6867c07e61bf06dedba39bc867dacc15dd04ecb404548ed166aa347be6d0252cf2dbfb66ee0241f37fb5287e7f366b380d1c28b89c09755b682a89d80a5264d7878e7686febf3accfda31c75e05c97ef46b4317aee4e57181e77b799c966072091fdf5d476aeb1f2cf02a013a50a6eed347396843bb53fa27e06b418a9e3771e827679edb12026d6506793cbf5df5d80ff2bd9e7ce5ec0d3de1e0a7c8be1ccf9bf98f50a91b93ca32c99fa8b847893e48b04417d0d647b03077b06e93764fdedc397f1d021df4f9e8ac93f63075c1d388a5958798627034e025b441e35443903d133f2a8b28c6ebaf14454a6157d2caad4b9bda874326a6e74e8990f7a1c72ba937c812f40ce51b0bd45dd7050fc3fff5e4b91783c7f9c0a7bef7e397b644b00aca9145b6468e4051ec9c3c547dfa1bcb4422f0a5d4c465de61504e8f616b04742052cf2f56d7b01e9917ad3e34c32d80ebe7f1e50e7b13d7d2ce98edcf24f9a7be1cf083238fb00f4a4c37b3e5f17ad18464b7e6a843044f3ffb6128e7c0752c08204c51c1ef8f9d5d2ee10b33b0703f25656fbafd1b5200f14a51bb68003028e4961ec2a8ec336baf68f5eceae67aaeaa2348aab71002700394fb851348ea1bfe91b6dfa2131f28fae5e9705ef72fcba37773f21811e6ccd8be4f9f8e270dd21f7bb2bdda55bceae2bdd9a43c8e71afc17ddc93757ba35208941c941822eee179609e8756f8623e572cfe85eff33ccde329748e9923220b5ecb788ded083911666d2e98b1f388cbbf3923ea97a5c024a7114896cfe5566897aff674e8468871e2e2afd08befc7b4a35b3e2b6c9a76068219aa99fad9107cee479aaec76414b3f913d3abeaaed70822e3adb65a8ab989bd0223c99126ab62e0f3ed79d79424fe94adfefce35996123547648860a89382d197b5e71518703aa4054d47612d5b29a26fc4348c37cc46d692015d223749c77e8214d7feb2225924ee9ecd08dfb6f6958d22b7b0978190ad10db05a5729e40fcce2db750b8d9e1ee7e4e8087b4be4bd9f3bb27ff86fdc3b761e330cfff8f294a871c37cd12d54414cefe5064066a0763e1550a482abdb38555b37ffba85d27f0040da4271631523e739adbc5be88c571e5f612d030ccde4fcdace948d3cb5a8e64876e0fd0b459eb3f982c1277dc1fc3d0d4d959675bd8be0516b9fa10f7c803d527bb1843086001d8827f81970230c907ef756dbd5c399241560bbd9b4b3e03673eb5fa4d961b4f2447c8b02e9a433ef7c825b5df3b25b66185b2bc77e6301fefc541fd74d05585deecf9fdc8315a2a544827ccf4b33a5adae099d44a8f41f86b42c766a88e157ca6d98c6c06971d359e654ea3052673bc60c8a2eddd7c2ca3822494a83347a0a2134fc31515551d74cea3e9d119d5a8244f44062e8a0ebd84841ad356374c60ea8953364776f27b2056d6ff1c18fd183f4fde04f713d48235e82c317e12320b654ca8c475fc2866f6800ee30fa657bfbf63092d689ffcdf3572316fd306a086e21814b48b879c0b9f8cbbf74ce0a705a07d62ddbb5da87c50495141de87bdb739a5f5db341ed026bb9a81c4cff2638224aa9d48b462c4cdb5798fef00c8f235a618ad4a36c665daca437f67ab5be47460824fcd0e23528ddb77f74df099691a1cb5d3e2e090efa4675c07543bf1c421ff063334308812d041d545e83b1f88a93a0163382f1aa27760bf0f06ac277d44b37b624e95717cb3e4074b477ea037cb853b6ec960dfb355bd449a05665c5724429795d70bed49e64cbd75a357d51da67e10ece86118a3ecb670fd4e646e6098da99e4a7847f246b1540c48379ac25b448737b986fc2b180f2e133d3be8c790263b84886875b00fa0492c711573750a3e67398537ab7dba9b770df07652f7cbaf3f56308cfa6b0317bc7551d85ff286fd2b7096dcdc0e03e10de17f5d1d85216c568b1710e34a29db4109b7210482ddec0b760960c467715d171e0fa9791e4ab8756885e2c309d967540d47807e089404df7402dd23afc1b4e6733bed9c7a73dc68f6bd9e9d13a589466d968a8af7d4a33c992486ff11d87c347e0346678a7f1a567d85e928d29a292068ce253e374802759c86546629ac55947b4f1e992c9afcaa551ca39d21284286de63b67ad8d7c5413fe1d226afa20a84126b7165a30f8fe88ba02842986fd0f2cca11d1453e0991e10323a0341b5caba6d958543dce09b91d3c1622b7940be90ad2e6e910ebe57ac7515f47ed5ad3539e3b5c580a9e66c538a7e81dc90d71a3c8ab3fe8f9b4d7ca038237b99522b03d12a6221b43537d38858b28ee14378cb7ae767dc97eaae1c32d4f9ee5fb811704a60e2e78a53e2761bb4910d1807d812463da90a87a273c2f27f70131648c605dd1c029d045cd3d061972d1a8060c389402c511bee1617a89941552b12693c8205bd0a0d3bc0fb3d650fc15730bcc25f563d162fb729483dee5198030ad1f5f66015c96b381414944ee0e678304840d36e75c89efdd9c09c4431011f640cc02cac61c457810312c3275891c88366259b0f9e63d2cca2314f30600d8d676f01a311a9070cfe334099e6ab4b11654931652e79c759d5eb5e0d97a26724884e591f74f865dc0f4984bac4190636144391ab161b283a7e05c6d1b7d37354b17d2b58112c978d416172c00286536602c8e021d4fb2eaf0156f0b489532d0456fa2a60d9ef0c27463733fbb612add0b3f1c7028d4666e26485b32b968534bc4f75513b1082cbcb53c38de5cbf0ed2d677d4d27d3628e5a9155261463a34cc96c26020548c3d1c8f1689a09704e56a93b90c33d2ff5c23076f455c12b0600cebdb3782c4ebd188cc6c25208307a60f9a5f066b402c4cd478590be85fec0c65d4beccff8bfb589bd447c8a01831ebbf3e0fed6c977b5c031edfa17c72512c6f319c0a2314b51c00588617ae043269198bf440b21b687558329eb4b27589b5d93f887aa0cc14d0251fac40ae8e55a68c0c0a61cc7232b0d46a47cf19a4764508f21306946241c5b15fa516140246840e5c6732144c28e59c7288560be925ea0e70e5488630006ed4738571b3104ea6005678dffdfeff1638eaedb0293e578c111e9d6cb07daa61047e2757351e12e83cc8744f697b2cb1bc3180c8d8cf40c7b30720f4e700b3ed404251d26cc43d18ef8eefeca22649dd01b60ae1d5ee2147568ba1a75594d0c00a42e729f0c9a3956c7d9c4541d91eba5e53c7c8e1413f59f350c4511c18321b1235f57f7ccced036f2cb8d8ffecb3688add92348c4492a1e71f8b0734457dc09e016fafc74310038079ad9edda607f0a3b7271e069633ee99960c310b4c855b23baa8ca204b5b27ff626586aa5a5404886407d31dfb3ea33a2f6ac3a36680f7a356b606b38fd01e0990c1b083abc65004bfc74718e86af92ee8a7a7a3711885d4094072e3526c352a32c14d4197f385e4e5f10e3f0d59ec6d176dc548d1b100e92f852c461fe529d4f73426c1f6bec768f3e68a64beb99412daf207df3e8b01c5bfe911b3af07079da5ca82b0298a27b9b28b07940b5e3dcbc25039d86428ac0c0f03987bc497ee5927793007de0b470ac0a37aa315f346d4dd1cfc4933804bd84d14df20f5cc71cda368a3847a151306b6917a81ac4e3f805e1d8e6c2d4406944e3e1cb40ca60bdb6792269db556821ce868ea99225b47ea8dc31ca3a561deecddf847ab52dce2cdc443e954b462ed7c653f13dc7802d9a54934570bf0aa3e2cb3f1e5c217b5a6c4a5c12d426150e62869e3664c4e4a0c443d86f5576bf61ce3a4b54fa3b48d51a6481214284a22b8485a7f14a9a195471061d446685991771af2a7fc250641ffb649cf056c0c2f6bec2f88847ad7a5db01264f7f5a54542fcaff9f4af66e6af9098754185baadf59dca0e892296c4a321246a94c70a4f3b029350a027dc44530ccbe7d043f8401a2f3f28fe0297a097db53ee8906a76c569113f81d151dac61fb678d2779592b8ea5aa04c85a9a40b747ca104d2220c185290a245efb1fd31eb9e3730896233434d4e6b1e82702086bc27ba764060c7d33c55060e6b666c722ad71bcd31de49d6836cf8bbca348b37cd2e10d226c79d5b46b3ab7fab3b7a4555efc1a1384aa099eda5d2e18ad7b33d9dd0d953ee8aa066f6f89e5df337e5ce8a21fb2aa67e5dbcc6e01c28b6f901ebadbc55583b34e9742ea1d0cb7c87510f210e1bd3825a24ca22b47fbf7dc706388df5b7868fcc942ec48f8cf55ecd6893422924c1903df21811bdaaebb77728d59aea9d7aaa48c89734c3dc3e31aa4315672c65a4b34c0f50c8d5f96420731b445479736992162a6f062bfceb9db6ab758062423c6f260c066dfd626cb111f0c968a2cfb0a90958dd0010ad13a5dcc8b02144bd18897ff794dc735601ddb11a2372c5ce1b5ac7f67353e0f9fd90ff7c2dfe539a68b9fa6dd6dcfd18a76ed5783996cf27686a9d43f61282b21ee0a1ede9cd85f00ae2e62bebeb4387ad0874caada7b99c2b823863499e16e920b05ea9c439b46933a551ba82252a6aec184162dc69a08ddc9d52d8d5eca0025bab8dd8975fa4a9c9d4a14df7419347c5bc6fdc470a055c618a344f6f2e8dbbc7283ff6a89915b5033992d95aea92c64881a30a5649567abeb6d4b13452d2b2d99f3ec768c4d713e8f072ee4d256cc335f5aba781f3209c400e86a0aa991c71e6c4c834989f302259ec93425022d5333db48752b7f645ad83d82d1206c69294d199ffbed406b695fd09534e03f4ae332db21ec4c32917affbb992af1a726a2254ada9b0b28d3668f77213d4c5f94860948dd61c37929f6948fd8c50e1e03445015198d85947b422a6cd18ec5d28aa2776c1997f09263321550c1247955314e2aa2c2f4c8539df1a302aa4c259f6ac64f052a994ebe6a8c4b25543129b9fd7d828049876c1ca02083b790599c2ac984fcabb98eaafe3a1b24429741dbcbbd805b5eab0ba0f3154b809ebddc0ab8e5b5aa00fa5eb104e8d1cbbd805b5e0b18d7620c3c727bc08daf1506e8462d19853af153259b1fdccea4831026e4bc7b492d2ee276492e6c4bd6285108ecd1dc248009c6a536c4e666c6ef9f1bc1c1b96105736cd348d252b759d77165437c617f83731a8b91d93c8d11d2e972b75ca78cbb8e0f5ae38ab8f00bede927dc9d9dd5ec41cf365d46f19039ed82772c306ebf0b13286d94ffd1741c76c8fffa25d6a9b13ef3007a63b88ffe6db33ea41f11747290b06ef1f26c0ca6e671f4eb13d0b6069a80da6a20f81ce55c29dd5748df388a109d7123c8405cd83a32088f8a03a7466a85c23eb8a02aeb00aaaf0028d424aa95134ee6dbb88ccb41280b94244d01bad1534a0c155a3eb5d5387d40c4d1884ab7058a88703314233c8636f905530bc512c205293fe10ac229c974654b37e5f737a972c0e1669993792e98c21771ca68c10f15f6f5a907216c42a1d1c3e0065719ba1cb777761c4496c8078853835e4006663d9e9f3a41675e918a7d300af214040913785ffdf63826eda62fc5c34f54ee2dd49295b35f30c55c32ef2378a63b839524bcf1197ddbc3129f9d5baf9505d0f78a2540ef5e75e34ea953b80d2513a07d8d934cc41c5545c21c94fa1d7ef1048fdbc5ce67cc3316894a4c30a2132a0913af25aa930986b41b174f868496fde3ad6c053a411fb0dfbe3a37b7efe0da41a8d8c223edce1496e7466a82af120baf4d533e30f2f52dce2d405c01ecc3250b1fb5a62d2a77c92713bbbc86043baab4e5b7d96c231f4e80284b83d9623778df946e3618cee7576f6b556e641d66e385eaae70a5b19277ec845226db0dadb560bafae16505c95ba93771642740553c0d0dd4bb3fbfad73f1b7bab74534ff86e5d0e05ea18945a58b3f5bf6d44f69f4db7c8889ada95e66e2c56ff51e03eca0dd793738bc89198eb5f9354eab190b53f489700e65e6b045a70d72249207fc92b1324db1797a6b242dffcd30f4df7b2ddb14b810a42c1875655d6625cce97d54b17ce43ebe825e3a92e43c6939418b01ddc5172c39ac0a02a69d3d72b643d141d566b95c470693780be0230d2dde8f8c1cfd4a28b739f2e88f032f719b178c80765ee23bd73a011ab0559e17397388b7213a9335e9f667a671297ff19a86b636035df7e022faf934d5b55a96e98a5b0337f1212a692472cde1ce1f8a444a0652e43c1d1f6795889c7058f6bad0719365cba847b9c0eee1679c930643f44603ab179966d22b7d71ee38d4f31df642e2904d0484339ddd704bf363d08d39f0f2ab5086a232986d8e0acec8fe2a58eafcee861c6a3f1d8f033979a75d3be3f93657a3caee627718c80050109e3430aeb6aece869b78418241ea42dc7d979c9d29505ed2c0b8484f630327b9fbb27dfb0560ba2456745f30668fc5df90018020ac523528f67e9cb0936673e54b50bbb5232909024c71d687253296e5d9ccbb20b7548a4b08a8041ed1e8b1ed15c1402960215eb062b2bdbce0dbc780be85c7340560adbde448d14859983dccdc4a3fa57400550d917ce26f3273a1983d05e972be7385514193fd7d681aea39bd9fcfd97f13762fe847c41e2f1b0a458545981812fd1a08a8471bc2076325ab15adc5607151a10a49a79dfbc589aead50ab5c8b36bb1004a8f35a72d343c1675a036ddff8cf37b34f51d800bb38ff55dcd8e2fc4533d85b05a0a51f25aa3572bd84da2a4f075eda043a2761d96485101577470129d66e4e3c1df916daa81a6d82ff9502445da7ef39a5c9f0ad61bd13a914855b913a6352cb8ae21f405e98857f207518d7a6c2bd4953f1908a9f47ba96102dc7996c3445753dd1ae90a4218ec36755632949771534bf3b5ca14ae64b30d5ae6f0a257f0bd595e734b8cfcbe8965e3d06c46f809472d14aa38ac2a79a2d75f30df1e6e808986f1b8df7834df969631e8e5925a363bb6f8fb61d19a7878748b4c724a018a73747b7cd09d3600d9551c1ec4e3b2602bf6932394114cd5cdaf7034fe5473c017e7603d5ff443968a00e001a4dfb706cf24093e299030eb4621a164946074ebaea15995da9d4336e92b42a2db1d82d12224a027eaa0a8c53efd753e2c78aea5bdad3bef03d555f1bd62fa25f4104510c94cf4ea5e197c6f2ba92846e0b8948df35f61ae86562c282bb76d195438e721fd44dc3f36ad7fa45089c23a71215b46b5f78019cfd3412b53546585abcd027e859365811979fc4be7bd911e321cc3d13ee4244f91bc154b05395d0caf2b41813fb5eacde9ff53bb2f84e96df3956982bff4b389f0f2b70162f7ef7eeeb63fd1eacdf87e577b0fe0e96c97b775a02738c8d6bb3a2f7959a7996af19260ed1ca3916e0338cfc8a393f4a9a251fd6cf5f2fc1f2a1bf8c0fa9d3daf9d1604edfa6c3d7527bb07fe2308e817103fa7ff2b439e2299d54fbf497b74565add06c6d7d0216b9c403257497b6820f7b317f2893c726521281309c151ff3b0645e36aa42091462212c18961280f59e58ab7749652dabd773e44b24dd6a6055c55714d8af53078e859c94ad6778f5aae222d8d01b97a2b777a9ff8ae165aac8066fb25f971a7029d6b276d6f6e8b1446559b7eed82da1ffc448a8bdf19281d6d63de2a7f888839402baa7961ebaa64953ab283dea5f6dd3b0f89029ec5a77e6ea9f55b1937075d0045b1729d5460606cd709a683e1fc1b80a64ecb07920202e8dee9370d1140e55cc1530d4392f0b2bb180e43a9d34b0e69c3aa08f1ccf05c7f192d849b84631ab1597bcee8120f8eaea248513d333023771096f2749e99d7f5ed1dbf7bc45a89a85c422b029578cc5e2e029c246aa2cb00fc3a7910f067c4b2168aad6be95fb725a0db2dd47127cef30e1e1c52bdd922078f516cfc24b12bc14bc062316717285588047d382f04479c5e23452aaa119240928b49bac19e202907170ee9c7cb1905e33a84cc27945dfb8090fda8b3876d52127ec396f783ec6f1472f7bf82efb7e5467b30ecb516d7ce1946eda4559888572cf2fcc4b6f229f8c61076ac60a455d13023b834b34935d56e38350ca9850c57fd578b5a4ced6e229a72a705dbb4cce5a650dcdb207a5bd18a0e6624e0b20de6f7a597d49dd40c7a421a9f151bd52b0f5fd6b52057f60df4c9ca6962a0a1b227b58909cc61083dd4d955778d1a7586a8192d3f192446789b820d2843cad5ba1c959e54a57fd58728c4fc0cc208c9a4dc8fca2f196bacf5eac0ddc1a273eead25b4716f351790dca9f837705ce57ead0c3a0920c31c8bbcf9592fd79d390ccfb166b6b10a3d6672b01fbcdf23189fa52e8a0c3275196c03e8d3e63eac28eb116824add7af3add894ca209c2245352a6ccfc4deb12fe398e95f88d17d7045689a4051cd131c8cc89df1e4081a08eec72f4879ad87c14fe55b546e992de2ff19b1183e14da5381304c5b0987aba0ddd605179dd2a607cae25bd90ab6120db43fb4e8d8e46162156d7bc6969ca4c90d85e09fd27baafd9554c46c421f1ae920f9cfc9f2a641487df717c219091f5ed1184a313ca461ca72c8794b8e83b1269b8488e7ce8ca28629fa95f03ed29ddfa6ddcf35f42a9035cca1ab676cf5f7720e41dda1101d58c3292a8f7cb33cf8a08b530d4d03ef14b2888654f48b9f4c3b272a152364077670f77078b89959d5abbfead3504be9bc9c54be28148105f834de6a9f69187862a4f2554d63543e44d3e7a723917790ea7e55b1ae8fc98b0acd272166a626f4fcad751742050029b3c4040c896a99c686e981b3be97f9faf08423ad1ebc74cc914d54d96551ff0dd35979825c4271a5f04919ab9bc7a448ace189d2db3f6ec6f70bb3bb1e8f97b4688d262daa2a21e6e8177cc1208716390c35ea28aba7427062ddc11899e92d62912c25ac43d89b3acedfe15742df27f6008d05cf26913a0e79fad755568171397c03059ce32cdcc8e7f98d5922e356132865b8cc55d686cd65631f62515bc277e224c3d2cfa392db83b498c1fa7b5ccce0af499db649f81f0ed34cd40c63e82d7155c3fee5c3756e9337e2a504c9bc730b6d04ad67e3a3a275209c241212b311475a832387143f370a64742e14156ee27c28ffcd322bd46c0d54c329badbce586109c2e827340dd9761a953aab5b40afdadf1577b0e1b2b50cc722df367ee08fc0f7696f8b34eb6c925b38e3d9d6bee52d1a14307d6761c0790c5ab4b8e0b67e9c920bbd157f358518bf386b4dfcf812aefc6c5085d43526d33b221c28a2eff85b1cf72d8843ffc3b2c460dcaafefb54f2bcd95206d9982ce99c4d4011b66c894cdeeeed591f10db689288093e51fae675e69e7873ada5e5396d1bd7e156fbaa622a4c604ff5c65745aa98609f6a8d57e55499c01e558d7b2a53cde4eb51d578aa4c05d3d853adb1c5b16cb52fcaecae07d6512c702549ab284c118c77451d043d795f144f0d3679df176d20f8fc9aee51f447cf25a36698158eefaa483ecbb8625cc9b4cc164f3d9a1eae6e41ec41c08585378cd364e64c1f419cb215714a8f464d64744d2a34e7fde0ea98dfe06e9f87effd5cdd3a9ec9f0e7c6fc774194e897b04b3f72d4a32c79a9db0580183c62add58f7d9f896edceeee2e3f5cb7d6affaecdb4ef0e3b502f59302180b71a35bfcdc682b7e1469addd5faaf12d7d7a1fe81a80206513fc3dad45f5b106bad4a7d8bd37c41b0b50a90c2ae8fc04b32990a4c5d32b1444a5256ac4590ad27c060b3789f14d0fd0963e05c302620d555c40514e9584cebb3f78b63c9e814dcae858fe1ac18f88a2d45168cf97b565e62794da6328e1efde6c89ed4bfb08ef39b40d03d8d4f371413542ecdd1f6f3c9ac8f988cf1bff8cf5135d2ebf89124beae5c691ec426bbd9edf9f0798df7d9359a02d068f90a1bd14ca16fc4346f51e2ca88b127e915f77f2dd5f2e5bcd4b70ae2db583ef7e2dd72261a86dd88ebdfb7864bb3582bf9bf6427dcfe158f6f3b561b69e9896a4549a353dff6c2b06c0df6a445d8ae7d8faf4af653c2df7f65c5f9a17d5181ed0b6d55b6a85b98cba18dd7a8464df4909c65cd2b1128c99855d1e10674d4709134fc521e08f2ea0c8c9073eeb9e6f1f54fbae948075fa4d6405082ea16d2ec103560bae773f640b53160218e2cbfebf02dc5b045fc4d24c291a5aa9c39b63ad86e0d4854108419f15cfc8aa00aa03e5a30915c205a21d182f0152b95670b5e00a5c049fd6b9eca769b0acdc4b7045583b72be3f8c25d60cf89e39f98777ef9ef9c6d802ca0498b96160460866140603653f17bff7e3ff2123704904ea0602d549137126be59bf6b608198ecbc493f1f31f32b13c4eb7ea5328e0c9019a01c6931ed99a3f0e8cf948885ff1c14d8bc0269ce74e8f13ac9a1dec06f0ce3a8d92f4b451c6ecc8e8987280f234418af63e657e1a9a52f6d39f1836e31739682579483285775ecbc0626ac3e49cd6cbede756ec920e0a9386fafceb5744d781dfbdaccae9cb87ef3bf8db00fab9dee72b2c8409ee91142d9465fb33e78d3cd857be7522941d7def8d1cf198edfa5b11620a01011615380454d99004031973054cf62741e26a3bf4d57227b35d6ed64f178118f0cc9707aa432b9d0375a5383b94627cc6b07effd227a9fe25ad97bd5dacf61c08ec0247c2007b237c472a2046016454b670b72fbd8eb0cd9e98c4b34c47987637386b706e86b57ce14b59d83885deca5c385517ed7572f216359633b8b63e595d9b7182efb8981ef10b092986c8e1da7cf9cfee7f154321b2e49501d2521e50ffce2eb211a62615a28e45101e5200542c8229de7c58b48564f512eb4151e97e986a29cf02a45dd65ea879ca9f19e3ca341321842db39c26cf0f570d42c060298070715e7a38aecfcf0cc9cafd4a1fa44ff3472230c24a6137a5ebe0ab63dc1ea2043604a3dd870d47b52c5891020091938257bc823ae25e9981d1e87e94081eae4e8478ef126f60500bab49738bd845ff315f39b5aa7f78f64882f2686838556a3ac7572d3fde760cf8c63cce725693f4c3144ee6028639b00e29c1e5710c5017f208628b536e07b22e3e185f6386eb2c38dee6b155dbd6327f1274a4f5100ac448f5fa1969255b40270ae546c92ce2e7070c6b7f76d804f4b9fed61b4b15416641694b4ec714d300914a65280c94e7c1454db072de5cf7ab534b0447e3c35d1368005ca1574a499ff4c6f2a339fb8cba645ef66f71cff89534aea3f5f9516974618f74bba81f79469571bc00924f3140f1e49d1286ec6745c330d8d5ec0411a5f896ce41941d37939a81a720c58b6b5b856474cc5e896d573b90c98e53b471b46b17dc8ceded1616738cc886b721a21b3e97baedb9a01de5bbfb65903675c93221ab0ddb27ba2ed6847738c549976f69274b37bfed4f53d8df921b72b0015a5e2d3e2401cbcb0257e87715c26def70e3dfc8f482d115d074384651dc50d8f5c344e1c83e6c901f7118a4276e829e2e9b25d9855527bd117192616318d55afc0c3fb25a628ad0150c96d99a383045c999cc9827623f1c674abd0f806b0c7979c53a4df2c93b5e4137380999ca8098d354abf9c11f456d782de4f138a193fe274234b5291e5600434813de91b143d5af1050ece1650c3dd924847a2f281b49772a1ffecc4a348089c1d66b47653fb08534d7c0aa01a9a8b922c69194a2a72ef0492cac0ffcc8aca9c4e1b428f85cc315a27ef99b39aa0ae92185ea898c319d748e462ac71cd9132d47bb0e7e7271b0949a6b731cc5834228ec0e6d77c00c2741e0aa2958e096a732fcb6075a5c4d6724a77cff5bb1a95ea887ff4ddd050041d56ea418969038edc8e924a93b12a9f685cef66057fa88fd68d98b543bbbbe218b36d794e3f035131891df0501bb641fd59dc757fd5b6e225b692937e90a28a2699420e7db7268e075b744c7f65fe636dbf00dc173d448bda8d2f70f007329141615c094ac3dfb2e0b6fb9c82563627644bc667bdf3e5ff707afba5bc2c984a9c123e28a8308b39cbbbd31a0189d2fa5afb1ebcc4cd8de8f1270c03909db2f8eb004009c7e219fbbf6173584c175d48f49e47aabde34c764b99aea7191023c3dda97f219488ea8c634b47c85d0a799b7609275ffa3bb860f683579319e9cd9e40aad03a84ce125c385858ee86acd413eca3b0bc466ce3c81afd2e0d2c423d4a7e25984a3e285ceef8a9c3f374ba51f5b16a18d86c0627cd449993c07b5c4271e8f8629077485dfc0b9b29476ad3020daf621d2e58e99ff321bf3160dce1141be326f090e38fe377c0456166677ad58a63f276ea1ae2f61c51c41031b06cfb4f941616a1aee9c8622cf8b2d5507619a5e297411d1435069e4e468815561038ec6a3a3f9f2ee772a735f9240351126683f9dc21a227293e5a3b5c1c07a0f3693c2abaf81348dc52726c915d7ce946d2ee96c6072f178e6f0af70d473f285bdedd40d47e4b012d36fcb440847bdfbe9b8890bd5355177515c614af2aa488c2892d491c918022b418a0894e1021600108201575668d326a2c88110615229e01e183b515995003dbf20316233422d842892d43478276e4b0a989c138f811010848799289530a4c4a4943447e763298ddc0691da0071c6ec8604c110a3cb180256478e2c448108f1cad0e8021c349d1094134b017036c88a201460c20081d5c40c0010ee00313a7221382dc786ab062f0996995a1617c645a2bf84ce01ae371f185e003018d989b305f073e2d3e293e06b09ef8a85a535e08de07b10e5c1ce0f4f8767c3abc1c330d5c197c334fd6baf1d9b86a3e9a58ec83c15c5fcb6379decb6bdc87cc4713ee65adee6bb329e4493cda7b7a343eae7c3c694b653e80c87cf490f52043d6838cd3a80dd31daa43e993565f4bb5a5db521376b9e68a1b69b574ef6a3dace81101590f07c87a288046a3b76b1fa7a33bf40422aa7302d1141004a3e06eaf233aee5292f5f0719dc19b7fef6a0ac95b7b8bf5b927dbf1821d5b5cbbf593946e7ca97e4cd21d9d1ff389f58e28761ce1e4fd5da9de6185bc2fdb01b503092da90dd31f6da969c6b65cb7a67fbb35d3803445feccfb75596dee0e24086c8cbb7fe0ee1a23039a4f0b8efcd0229af3399270e488d0112347828e143942e4c8902340477e8ef81c3922242464442848a8881011a1214240423f423e42478c0819316224c8481123448c0c310264e4c7888f9123414241468282828a0411091a120414f413e41374a48850112345828a142942a4c8902240457e8af8143942448888112241448a10214264081120223f447c881c192234c4c890a02145861019326408d0909f213e438e00090119010a022a02440468081010d00f900fd0911fa11f233f413f457e88fc0cf901faf9f9f1f939e223e463c427c8a7880f119f213e403e3f3e3e3e351febee3f5ca62306cf9dd7ca270c1d44dccb6a2bc56dabc3697b6d961c60b8ef9e8aea6de9676cfb1aad94b52487143a833a3f59dab32664e76c4a2ecb51baffc5549fd8567d36e9e3be347378a4fbc4b86d757497f77d6ad61cd5fda5955e7bfb1ac59b923fc5851a9e1a1d1d18280c353c352e50ca435da09452ea5e82bb931024870e1d5a030c773a24d3e00a0d1c20d320014e876ae8d0b67d49690d6de36e3653ba6d5fd21e37ee9a3b01c80927b8fb8e4e100da4641a1071a7dad27dd628d5f6719b0e69f29eb8466d98ee1353ab274710c1bd6688ee4ac9a71aedada69928431960e165adee339701039c0e915fa33bb27e2e6b53c63927e970ced8c8f6e458ab47848c0415213204e8c74796810bee0e823b67cb20892c0324afa59d4f06bf5f5ad59c8fed5aa7ea54420fe8034ee144f4ced5a440109f20355a44a7f844b19d3529dc1d0497cd48d71924710e8b14dc3f313e147ce517f40d31810057b8cb0c13b86719c095e33042e29e0d5adc83c8dc5964dc9df44a4002af8bbfd889462fb6db64b2a3b3db66ee456d41db5903d28413483c1c9cbf0addd5e893b6ca92146fbacddcef5dbd9a27767e82281111fdd1a1449496b0002696500206190ed9a96dbe2d69e170c100a8b85178a1130112cb8c03f418011050a0e08712442c2ec20cd961002276e812126179e9e14684901690cef8c0c352001540be40c26a82043d21b074b84252d2429524b4a8810396d9840e1541cd84327672b09aac405600358a20e2c600372c130a203b22052cf8e167c76bb128980fc49021f1c40acf1aac970e4cf42006049010e1a38ccf0513a85ab22489239064607c60d428c00b2238220924a22f3e086c114107a2a4a0c00103e8e22bc009b43ca00b213ef0190af3cd90650316c87001489417587c362c681605f880142b0554f12db9b206193bc6c8094f20c5f713812ea8889262c1145940f16d704606089013900501529af0d65040012c406404477610e108cf04255042000586d022a28008cf0a01900085941808a1c50101f09af852240a035f0630e5841d3c206c88620310510a80010b58f17450487d51c48e043c742f7852c6a0b172010a18a86ad89e9111b9040c4100317429945e102ebc30481f260f2d203d1c209a2c0f123f5600458a8f51c5940ed230a2d20101a47c0b005811ebc107133f02770a7029c10bedf0c5892d486ec40b46237ec254a14610f72b08601264841d04c84922e239311e103a8209165ce1783cca871880b270a105524c77212f4264e18206603a1ce0eec10c281cb13213001a2bee2c27ac5c8081266a3767c89009a300d9992c8840b2c619077420023ba09ca0c7f3947166881da4c082063f5dc1028c33314891821809c4cc870bbe3833b271c303c600805f71a38b334c4b8410c2105603f1813067c02fa4c8aa20a104135e589cc931040a27c80018176468a28a36d2e00017119afccf0f04a468c38b1c6265cc5081430715146d5421012433081fb614408b26da50c28c12a21657360c0138a20d1f90f0820208e892822e3e10d1c6f6220d5d08e64e0c1408401b2c0cc0a9861d7e886284263bb4818206a438432352019f282b6d8870434d74952fa4a0c30b6dd43ce0014e6c59e2cc5597cd8618649cc04c1025b8000c4628d9109300266c5070442ece20d98862044d9421c050538015296c6809820a01594c40040d0c408a8d1a304002a4203e3ec0812896b04165e54b182f3c9001144c4062e3a9014237354828d2801e88d8282206919a2c3b4868220544d8f8a1c5062d382004154294c1c3868760d7b001116818b2228435548065002dc0424b599870b0c6062c200b4050931a4e8ce4588301341065b09b02812c9edcac31800560c1f40312aba8dc99d61a2eb480881c1d0861e8b08135d6a806a0d2840f4e20a1810794b186d2075490a2f3d384000000630d1e1ec07ce0041619d620fa628dd9144e7e1023a402c14d176a9c7146c78035a690a20b2661d4e88209b38d1d5610200357b8bbb781851a1550810f2d5827f848f202c7bb91e3ee29f11e004fdcbdaa10dc3d2a37365e8d950e49d6dd8ecee7b016b50577746aa716c9a71a1f3782fbd62077cf8b4105aa444931256142129330458950c2267a4211ddd1b95f82b6b3a62bcd2ad7e7b57eca605bdc8716d17d6b0e2475062fbed54c0ad9dd7ed7bcb590f149a05dda5c5168885d88914519f7055d5c7561051a4da4d019e3d3106307164db497f18cd40d22a30fb0d0818ea0109cc8808dcb080da072c508600b1c5aa4704daa123c6520031d426ed8420408c8537ce0862859c0173cff9c58f0c1082c2486c8d0430a0e70e1a28a5617b10f6861061820ec2f84e0d021c406cb8b216840011479ce83098b0528a45106174908f1822bf402928aa90f3c6f85d1930ba3d4d08418338060c318b802eeba2201701b1758c0bd9f1e770fac5c390b0789bb1375878171f72f9fd3127707e2b2d7ef8a4b9bb8b3a490dc793fe3d1879e5044eb2e9f7c3aa4ab6f8c74bbb6b4354be93187b8db896fd2c64eb87dbbbf64b7cd5c92bbfb28ebb9c74afe1edfb44adaf6669184807e5e57a39b35c2586acca1bbf7688db992b6ac559769cc6115ee0eca5a562e255c59dc7774f25b9bddf749a63dfe548dc916779fe232978ebbe778c4dcc9cf656c2b6b8e223131217221ee2db8ac6535e61a041e017d7a488b7bb6be7874edee2bb8ac8553b3e5ceb856dd75b833b9b7662d16eb0377d720037799bbe370f71b7ee335ee4ef3d3a37fef3cd3ddc63d8acbbe2cae33f8241e9ff4018b803e3d4245744d88edc7c7a7c84fadd5808048233fb6a01e6c41fdbbe611dce5adededb3107ccb75048564bcc7c709d1f97513211fceb72d4d4d69bbd23270d947e45ed61d2efb6edc3d87cb3c337e427e7dadbb7b2ef3bac880bb6fe0328f8bbbdf2f2911fd1a25cb13e334dda1daea50ab27afa55f85739f45b3eab4bb03b97bd8dd7bb8ccb3e2ee3a83e3dde9d89f65ada2b49c9368f4567a31a5ede89835ebee3934b4cb533f148d925f96757477eafe91eecec3651ed519dc391bccfde519e7fcf4564afecde7febcefe7be3473966e5bf3a6b76bf7aeb98c6db46a71cf0877f7e13237dd1dc7658e0077d7e132cf81fcd2561bd5bf77354a6d985e6c77a5bb8c739fd319fc80b83b072e731d779fb9cc91dc5d874b5bfe2a4c77db8ed5aca3cee0f855a2ae662163d642465c9655c82ed75cb3a5bbbb58eede72f71b77fff689777549c63927e9c0c18b8d7bb9ba0a9da583072f9e0d9e0d2d2b573c6a432b87872c3c41d802994764137337b2b901f2dddc7c379fdfbc727e28c1e88b0a9693c4eea3af9c24762c8a42d8a2ad9a16adf9be1b2034f4c379a2a12d205660b0a11ecd8d1b7d406c80d88082118bd600b172c53362511b50306a51cf33fa3e572ce78a45bd1b2b38f40a35aaa13120566ca80dd4a8867a36395756608881456b72ae58566ca04637f4c361e106080c31b0284ece158b7e2ab0e8475938522c7ac533aaa13440ae784634d40688151b5030aaa1573c231bdabab9c981c1c64a2cca8d6e72607042ca867a14861afa513772bda867148621a34a62f7d124762dd9d3473d9c1f2ab4e847a59e58348b4d8882f08b18cbc8f75df9ae00b172a595e30415146c70820a0a1f0e15f7f2e485183ecaa23178b4455f70420a466be80b56ae502397d48bba680c2fd8408d5c392fc4409373851a7931bc682ce70a356251584e0c2f8fba7258d4690c2fefda8426a072d11675e5b8f7b06460794a6cfc034285e52a63566898c1bf96fb90f7001b970108954761395458214dabd56ad10f46d382516fb1682c463dd614cb6bb158ad298fbe98bcaa6fb3688cc658b11760b19ccd5af2609445f5a2c9a17ac55a39acd847a95e3954582e20545e083fea9f8b8b8d7b1eb5b159f2c298d7aafae8ab05840a8f90ea453dacc9f98050e111be72366ba945bd1c2a2f8436fea2b49543c565e1472d60f3f2c2168fab35d50a3f1a1a63f1b43c9d100ad34e080b127a3953bb15b6bcec85ad1faf0ff6234659ad50e7d3f13ceadac1c6c5dca045cc0d59c258e8626ec022e686ab156cfc1383c395bbf77d1fadbac9107e4a3c02e85cc12274313774114ec56ee012c670e012d2d0c5e08025b461dd509d2fe845c314c85e2c1bd6abf55118fd5ad45d406230186dd11b9793879fd24761d405c4719876629ea713b276f06ebc1cd88bd250d7eb870bc80f176db95c9ed2473d203098f7c15a3f5a2c17cb45dd45ab6e328431263f5ed4457542578e4e063eeff5c211b26809a18b99614ce872d947bd250f3f0f091b0f612a168bb1aaf88260316a138bc5621487c973c56e5c39add78b7a30188d81f1c2781e8e126f8c8bc9abf13caf2627067bbdbc7079168595096134067b167589b1014ce8a2af5906b3190d81030d70943c2f212c67eac6867ace940d102b4e4cb9e8476da0464e50c97094782f1c384c2efaca41933305fb4224ac1b1e21f8102d49d14a2b2b5888d8e2416193e346c78773c3e58626b6c18d8b662a46bf187dd18f06478927c686319ec714a330ea38321a202e1c252ceaca69c5be16cbf3cf0b4e1a357cb1e20b0d3e13534317313570095d4c0d5ac2d0c5d44080f015ba981a02400418a177117a13a1531716214b0c00a40859744a86d087aa5a3f58493f583940dc71dc8128f1426fb764089d3a2b86d3d4f4ca71ea1e2bc75fd4f34212c8762b7ce134b9a8e7fc50a2c3d3daad90f582e134794d5332fc78b5743ceaa23fbc97c7137af4a32e04d888a1a10930369e4d4eeb53924149cc0c5d4231575f844e39f8826ecc842e66060fc173a8a8938d7f343a1f4d924dce0f2fe7bbb1a11eb5a10102b3f9be1f361ecdf7ed5648437f50f1c2170d4d8c26a7ca0b5b542f23ce12c11a5084308fb63cd60bf6e3f57d30fa03465f300a63bda86034d4806caad5f2a1186d51970fd1d0168d51bd680da5014205070df5804cd5e4d0e478f8c1e82bf6a2b0166d8991227c515713a187ae18a42185982b65c259e862ae74117a218bd2b8e8d4eb454303a32f58eb86ee160d8501a179b96e62af168db16c5855ad10a7c9455f3f94e878ac1775fd70fd70d1dcbcc22e642fea1427e9c5f4852f5a75932164518ffe88519d1dcb51a213bee82b4787e70b7ad1cfcb71b97ebca8ff7851efe57af1788fe38829c0c686e70b9afa280f1161ceecb04648435b3ec572d5d05016cd6ee1d01bcac249b2c954708455ad1046772b6cb1583f582c5ac362b1680b674a8632de7393f34389ce548bfe50a213c6a63ecaa2353494a6a99553e5fd88e5e8c810d2d8140df5bc900432a71e75fac3a32ffac3a33a1eebc5fac162f1b83ad08614a1dbf0fc68d1a956e83f5a394a74429db0153a8bfef0688bb280fcf0280f0c4605a31e8de5b0a8a63c8f7a0e152f74e55079947e393cac1c6870b0945e34bcc0c6c1518ab9dfd01f3834b406070815af898685c3a4c5ca51f2a29e84e7eb800d8b8d31a10d8db5a82b8517885118fd581fbdd1f1947ed0d45098bbd31b2a4db19c2aea3955ac1c25cf7fd0d018a5c951a213c672743e1d8fbe603954ae9cd68781d6174475e3439f8bbaeb9533e539931665d123dc5b32b83caf015642166d0520fca84d19218bb6c2a8f145f8511bb7c95ccc95172a18659285fa9361ca45b717e6fc60e550e550d9d00fc8940d7db570986ce8476d5c5470849aba987cd4c60504489557e5c568121b1aa755add0a35470849e476b5839555e489343d5a2db0b5f392daa8f6e2f74e550f1c2580b896f515818ff168da9514518a32f8a83e52c6739cb75dc73a67e28d10979c258c8ea808c8a177e94c5a236ad1665b5e8cb6bbd7098e030bd28ac458575c362e97c39555e52cb63da093dfa01997ab5688cbe70583854aabc10e6a254aeed9109a306143ead8f8310c2cf693802d994cbe3093fea29a9f22a6083c37ba8a723a422a422a4c2c6c35172d7d1b9a12ab891951a6a7069c1c2da8925b16e5c382ab870f09d9b16cb75e306c5c9a1e29d09a590e1a0373e9c22f9582c168bde8001c17173837303c70d1c36de0d20afd07192bc288c5ef12117bdf9207613bbc9b9a12e2eac1a16a5b981b97080511b188cd6c0288debe626278b0fc1428f4ec5804cf108a73e88c9a65c43842f336cb4178653248553789e37c5f77db02f9ca24c19162bc98b95144ed172b55a38495a2ed62b9c42e6d1245f3805ec4b8a019105030d20234b2801d767c38d03b008a762b15bac05de8d2e3e9818568c032d1b0bb824107ad4e645068c8cabd0a3319a3542cf8731b68c71c4171a2e504516686841430068f080460d679c39238d33c638038c33be38838bd0c38cd1c519599cf1e50c2aba8431d344183347781bc062083301308325f43066aec2291d34ca80b2097fd0c0151e188383107888940981870851181c84c0b3451834aa00a3d584b7a08c8b09329e96d712686c117a9830685411060d2ac2a03101cf8a2f3484584220ca8040190a28434be861cac052860e6500808c36c840838c31c668e20c2e6768398388d0c39cb1c319339861c68c32c298510233c29801810098b1c58c2c0230030718186658998a9571461965841ea68c2fcae8a28c0e90a1a58c2fa111634891c615cf4c99cfcc9810b0c29071258c990e782d33aa20234be8d11b335f72fc2a4c1a4d84492301a18731e38b30691011260d1f2e504516a10f610142027cf4bbf2837b9e47433deac2e2c3e8d5fa6eb0a8d7927dae580b16a3af98eb86d660436b28ec864ae1e4b8b848d9e4d0c4983ca3308bccc5a5a68bf7b8ae70e9c2a58bd0a35d6868c68c202422242224824be85d3c16b571cfe805248bc7fac22cb2cf03570ecd17ef69b162748618e572856b0c920fc8a672782dcff321980b890a64532e2fc28f8641f0e0c183078f315f19366e4496634c180bb9b84c00a32d586b02322f6cade06573e541e8e5e4f852b346161797355c61b870e1c2850b17562bc798a91ee19725ec4186838b07612b27c71717c8a66caebe6409736820fc707099c1e6ea8b0e2e1cf0127e948cb05566ca861e6db572a6cc295ccbfb72a6a47c3e9d9007590df51f521f4e92242c1c2cae63433fea22e3867e30dfe2aba14464df940c07c8944c0b2bb4c999920de1fa12d6009922e232c3e5cbebe6a5c1cbf3a0178d852ef73c845768469645cb27d302644bf851282c23322eac9d8fca86700d88083ffac9ba841fc5d2ca91656185217c3a5ed883ccc3e165069947a76cb078a159c106210f396c6ee8946b8b67c6f36c68c228645e6823739961c560af1b33496c9a88c95a2ed7179b26bea09a9cef8beb8beb4bf8d11b59cb55c4102d1eb495a3c30b8f180f322fa441e651306453365cb4841f957589c55a74ea83d035868b0d17598e0e32def3d1503646861b2fbb8e178f5014a409d9f649a3957e8dfcbfeea07ebb6d596ffe6df5d55651f23536ad9e7ec67b6b90fcd7bbb2ac58ef9eca862bfd4dc97f0dc3f8243ef29c905528c84f84ba547faaccdde90944940ed16874eb6ad21f6fad226f1d97d85d9e4d45454227d1f84e886833e4d0824cbc72f7135c269aa1424dd0387f157abb76575e4cc9fa25c59bea6d6bd4da6ca7cde2f478040c0287f4f4ecb6cdfa62533f546e845232216fb8d3d8e9a33e375addea8ebad92627eede25021070f71f273e4e6e552df438ab15637d2cd8f782d9e890fcdc6fa6dc6f26bded9323f27f5f262962ec33e7242a264952590a165d75b42ceb88e43229486451ca545ceeb7a55fdeaed53474f71e9745e9e2eea03e6bb22855dc1d5f1268c819e504776fe23216c8385896afd395f2dcaedd3a5b1bb5614ac15d599e364a5a92eaa7364c71e3ae462c540139d9f7ed78c31f27fb7e6e574df175fdf2a156c840d04fcfb5f5f45c9b98c5155a4ed221f712e31af9393c1611093a898a88c67792b1cda688f357b9f8eeac6b594d5d735235a3a0e8c86f6d74df6a73add2bbaa2bc3bf58f74e3f66ac74bb76cc5849db3e3f65bc9d60db65aa7b0cf5ad6654d4ed5aaabab5f824e6240a07b7fedbedd474e36e3647dfdced4a2f7ddce679dcce9db75c719b3ed5bd5394931b9a98712feb88b369c78722a36ed7eafa797ca45cffda2595bc9fdff6b92a9f47ee3bd18a481013f9565629e33d561b896d7ed3fcadc9fba5fd31635cdacad2d6f23fa9c09ffe5673f6b349967849ca848dcba8934a359990d72a21afa58a1ae3ee4d2e8b0a421685828353a46c5c6a1ef25aaa285240fd385d4b4d49f29fdcf7ab9e6a8b93a2e0c6a5a69fb39bee8cd666c737750f792d950e792ba589af696f9a154769e4b554347abfcadee893bf33363f537a2bd5967e7e9c141359147d73b7a97e9b0d4305b12b739f9fc82f4bab04a7eb8e229fbcd84897183fd5ce283257dd4361fc445e5cbb4d2ed38fd58caab2fabeed91ae6d2a317efa2aff59490527b76b77386ffd48f74d26bfeb2ecb50573389dbf689d65f9a397ca4f597e4eb126353cce19bff29676b79f494005bd519ccb91c91a1a19ccbe5724fee4e87444d55df74772598ac706f364a34cd3a3474e4a74811a0ac8786fedaf262315777450a2a02f49f71524a828a00555c54501120ac937aaa5a1a010dd155a89f21ba0ae5334457a18e00e92a941090ae421901d255a820205d852a02a4ab50448074156a0890ae420101e92a940f90ae421df9d15528a11f5d8532f2a3ab50413fba0a55e44757a188fce82ad4901f5d8502fad155a89f1f5d853aa2ab5042ba0a654457a1827415aa88ae4211d155a821ba0a05a4ab503fba0ae5a3ab507fb153d39726284d459e7f6fab93fa5bcd31e5bf5dab64de9e4a5b73491b5ba8da3f29ebed97ec6a696a8ba4878676db8a64f95669b7976c877ad26f75efb4e487db30cd9df7298d461f273534544df2d67cdbf7ab92644ac038487eeed49f95180192afcf6aeefb399f475bdb25e17e2d2a8102926f4bd3acf4fed7a8ae38ba5fd3ddb619e3f659a9ee9d524801d3062e63a271d9521a2e5b0ac3654b645cb6d485cb96c0b86ca90a972d7571d9d2167787c95e371b68c8997f57976027f271637da86d737609f93a5c96e1c64e1b63a88b6dc55925b9bb0c52ee1ec368f7e770dbc2504585a5b4c64e50f973a7f617aebdd9cc84ac46e64985f11277cff9719b89bbbbe0ee555abbf2247113f95fd62a79b11359e2a5fb557056daaf7578b4f1bdf52877e6ecb54d3a5cbed1d3aebc4c34794f2577d7552d89e0eeb2a4292e4b6ae2b2a4251ebbf8887cf29ed848efbfe52a457eb6fa9176b736612ca5c34b3096faadcfbc6f13f29e182ae331ea3776526f937d4f7c94ff5efbe476717ef2556c5466b21befdff2e792a8c65a2bcbaad484fc7bb351d756dcd165228af9495b85b3299e660e1fb9e7af2a7194ce19376127a9b2ac476789a3c8cf78476127aa112f3977db96b666757da5edaea9c6c77dbeb8764695183f7d59aba5ad55e16db2314e9bf546b9bb4ed2d8741a1aca7f6dd3f7dcb3e775c5993d7697674ec86edb5c4d9c9f859dd1c556db2a8bd4c4dd3315a40b580515213204c8ea877caa112f3972ff8dab86866ed76e5c65ab2e1ec5dfd8c95fec8484e65caea727e772e2cef6bfb3fdd7644752ee0e44ebcf2459e9925412c59bee5ec968cbdaf94457504a4a32a23b7f95bb0b01fd9cfa564ba59ab755525986a3b64dff4afacddd1e1ad2fa625d1fca8809f29e586963a727b1d3ad1a7dae465da68d9df2dbaafa4b1b3b695b0e0d6d6d4b51df6a4d34b55d1276e1e6eb9e1c26af843c84db29de54937fbb15379a98fe79edbe78b479d313aff0832cecc0835cfd98d416b7296eb4381de5b9d5d246a5c8d7273df10afe024e6c0149e940a00ff8539564c4044a2a8ae6b9b7c5d319fc1ace5bff803e2050cfb565fedccdcd506081eb0c5ab38abf6b95cda62802dd7099cf5ddf4669fa9e6215154d9f94fcb256efd3afd1adcf1c256fa5a5cd3dbba772772a779f72778b734111180722e5d484c99188e4d4840994a8242a098f8e9294d05b719be617c97be24c4322e00fe8d34355c7f1a9ae62ded5d50c573557ee5757fe5d39ebca71b8721d7470af711a77bfe29ec395d35c5dcd7075a5c395fbd5550e573478b7b7fde770d4934f46af3da934f1490b4fa63c91f224ca13169eacf004ca13d91014bb9a73a51953bdb3f1a51953f27bac7c286ee39a8ed7201bf261c88aee4a79e850fe3d56abc70d49b95dabb5cd59db67935af91c11f222223f3cdc5dc8ce1127ee0e4449a025d01f5af5f93511d0a7e7c9fcbaa73473d6ca8705991018772070c80fe8d3737538d770bae2849aa014a7eb924c08018e6d97e9c99a6f6693adeb12bbed65aa62cb7aad52d2ae34cd5057dc139468eacfd5bbc76ae6f0d1d0d0f8241eab80869c45627ed3aae6a8465cd6725937dea255cd59d5dc68f5b5795bbdf8bad68a80869c44563587af542ea805f99ff4d010d5884b230f0a591129f6f839e3916e7c35a5364ba9b6f47152d4049a98b14823df4867506f9bb365eeb7de494579fe279b2859111e6e02912dbef55f11679b38bea9858c5f45a407b27cbb42fe2a26e4df263af7b7aaf53769fd23ce4f4a643522bf2abf7ea8ffac346a9b1b1ad2f7a9f01e1ad2e7ae3beac75bcb6ab4357ec2b6cbb4cb7857f2d6a6b2e63799943597b113a977f5a9887cd3b479eba19cae3a22eca47f050f0040de8f77c373b97b76199097fcdb8ab9fa79ebf2353d818892365756d9cf01dc41100cb2bfe4d9f5f38d6eec44414dcb5acb732ceb78bb96bc27fd5ce93f192abfea4bb1ea5bf31ec2e98afb49e2ee3b639ca6186f9cae383af43a7fd567aa3388d315c7e4ee4b2dbc45f314856cbbc9c556e77772bb96c4e34ddad7e227bccd5d3e72f724493f5787ee2e020b2e130286bb2bb97bcb3dc9dd6f7553ab33b8afc5e610d008e8d3f333be769d41216011a09e1e9caeb87b0e0dfdad66bcf5d0d0e7ecbe6716e2230414e2c3dd6974884677a5bfebd694fc6d9e39e32392697f59d6a79db1699e5afc5ccd367727ee8e44c8b69bdcaebdb6e63e9375d7dfbb1a0aeeee15b9df26b72a95bf963b9d6e55ea49dc74ab52f9c9fa50f9ab9830b95d9b9fcc4cf647fdaea32dcd13697f94bb9fd0b2f8eaf0a5ba4d48cba489b44cca3ada8b6f1d2f767277a25bddbfedb53613dc7de84bf1ff32297dce4afdae48ee7ec4dd5de862bbadc6a6bb1be17127afa5ca38e724770f2ae29fc3517fb1935408c49b22444d103aa31411897a579ffe6da2de5553eb5a1ee1c65d4d6d91b4aee5f864e36ef675ee0d70f7eab212d8204b7bfafc80403dd8766dcf9775d4e4df9bd51ab7eb5301461881fad01380d0fdda66c3ae2257cf0823d0138a2879df897bcb022d2890b8b77a68a5d0817b8bc95b4bde324146c2ce580b919100c599be1d5684c0c7fd5bc1dd4597ed98b0ebcd56d5775db1ef89477a4211bd5f8adbf6a5489618c6c1d6243de184316b2d3a2ff72be43fef9a107db1dde397251e8500fdfc0c39528bd4b715111a420ef923458a000d013a1254a448cde783aa8d14f209fa79202121e3d66716b28214d351f8141e51226a455a907cf27fb47a9d7fe32afaf9d43729081241b4d0212b1f9d5b957ac1dda3d024a9d00913d4539027f728f4884989e928e929aa470a4a92941393a3204582f090bfc7337f95dd3667b33982902cc88d209ebbebd02951a8131326a9f08869c9d353f8d46464b575f2392b457e155bd6fcf7937cce4a1d4935291232e127ad13abdf5fbed4d368f3e74e27a9a4a8a423a7a4262a2445ddaecd3b4a4849aa095292540895d4d4c429694998040595c4a4c424e59484c444a58e92523062b2644913a724a92326499c98a0a4a2a814931154529224292726a73069091326463b23f838ab2a881f637421e22d27b003c44066715808929c314595907b00c035423ce101879da185c905f0c719879d3973e6cc1930dc35001d8c3c61b3807b14896b8550c2bf9c094fa4b0d371d70058171832421177b1b004d20b9aa8fb0e8c041d4a2872a7c2e28a266178e1de0564c0d280971587f560220345acc1c45b3415088058812a87d9f0058b94189cf1970a36f5842f67dc61f5db5240f1cf054be0930f1c71676389ab29485ae0a241599c0002671c762403a316a209b86b0546d03c2f3bee6dd13141c7a580b39aaed06005ab04f8872467000ce4cef84bef6005929dce595a2079615d4139ebe9a98a041254f157010230420360b0827f5d80965a6b74e0b01a0480c4d59203384c8c1688a0c00dcd5d8c02d6f0818701fcf523480a37b470712f734573580ee62d6f9de0394c8b47ba661ecc7178ae0dbc1b5e0ca865c45901687900fb6231578be579bee303fd734f85160a9ec34c70398e97e79ee7b95c443c8ff52d791eccfb3c8ff5aaf2799f07fb3680f2bc9707f3cfc30159dfeba55343bdd6f7b5b0782dcffb6a3c333e8fc65d5eeb63bde74d7d9ef7c59a7c3f3eef7bb53ecf059fe7bd3e4f056f87e7b1bed8055c2cd80d6f03cfd311637906f03eeff579dec712bdaf554343e4bbf18a629e2b04d6091eecfb5a3fac9aeff3767004792eb47e6c94584f9fb7fc5380d762e9b45c9fc7729700bc1378feb94c2673cfbd96f7796178b18f04cffb5cdfc7f2bc219e102fe6dd7cdff7bd8ebc20dfd7fa3c1f1668d32336828793818f4f08cff5f5f05a1ecbf3efe9a3355ecb3ffb724af3786039900b788e24031fcb7b792d9887e3b1b27c5f6c88e509b1bcef6bd1ef8646c773791ecca7f5f3818ff7d5bc621fccf386bcef1b01ebe5d17c2d569597f39d9ec7eaf1686c3e2f06f33e249f079f075f072c67b987c37b7d9f87d392b15a5f8feffbbc97d7b27934217836aecf7379378cfcf8986036349f578bddc03e4f052fe679accf6b794b5e07df918f83cfe6837d2e8fe57dde67f3c15a1ecbc5f23c231ecbd3f168bc1bcff34cf03cf86a58302fd6f23ecf1bf2bcd7065f0eaff579ae9677e4b3f1589f071e8bf57dadafe51df93ecfc757e3d178b1d6e779de90d7c157036379ad58cbf33c23d4e341428c885582cb6379345e8b874743336413e587071e082deffb5e1fec3d990e1f9b25cffbbccff3bc1e87b9e92cc8e09559210ca72b144106cc0a40be00e3493dc1029a98608c4b01e1c2a38ae8c047552d1caa08290c0005043351a188a9324f95089225457840c652e5c0e748113c841ca937983062029c108c00a9f1824ce0b111443b738020da868334353ce8a16df978c6208179a80f092568e111e3031abdf980eec8c0b56830c569ae1f9cb600e0ceca8fdb980825a27adb9b25f175af0977f7c06594cccdd684e4ceeb5ea35ddcdd85538acee09b75d42fc5fd49464d9dc1ba2b29e2e060fd604d71d5e2e1aab5e44502f56066cc2043e2cae5ba818dc0c50cad227e5a3b6e8e60e295c3556b09cd06351cf000c015cd0a2d16581ed8ecd74d4c2926f4aa6199c0a3c6b581ab5897ab1c5266a031c232c018375e5cb9ce5cb96e7c80e0ea66e74c7145c3e3c60733c125c415802b3a6c7a0841436b872b1c40700061f5cc84c478ccced0e02a2126042362e16011c58eb86235fd0879d5bc9ab862d9bc7cb4786c7e585eeba6c553a3e42a01b6c2cbc7abe6357593e326070d0f9a0d602bb0d8a0e1e583c58506d7972b11a65a392a8ad0416be806c76bd6f2583daca3d6d3cd8ed7077b02fb017372e9b8e1bd6c58aed60d8bc5fa5830568c4553c3418b056b02cb00c753cd0cf682792c588b484b48cb8bf560fd9881e603171357379a9861f66afd8821d1d0f282861c91fa99365620822d3a0046035fce5bd40f58928c5038c108be62842124880c470c168505264e67b208e3810f7cf0c9220412027811022daca022024f2421041c6100e7f64167336050a60b316130f000196268410a0b5098382551fac0831d1b483101199e58400c30881d7080ef0737bc280195dcd59936d038434a941d1bcc426045062a40450420c0001f7ac0416c6185274c9c98b4c822031ca04096a51170a1851518a0620a248ed0a10600c0a04c08c4503185140db072f52453d20ca74c0abef04203189840145028f1030f4f8a2d4889f28429c984a19d198e165968c00005f8c1ca932da4c0a4946482a7e00b2db2b822031388020a062801440f56a6a4c0748209434688fcec64306bf999147c51822b3490010c4c200a062801c40f3df060e504e21429280801d7506389121c3748c001961d7480f2c4c7081e74d85939417386d5c6c7864b8d2f0631265a4b7c4a7c497840df0fcbc7036d7a5825b0e8061f7c1c783f603e583d3c1ead1dde06341a6490014bd6c271734386e3e3a6870d0735331a58ec05fb5e2f978be5f9cb4a8d081bb882d1c0685a376241301a560737523338e5c4745e3531a1d60d0eaf75f3125b3a160e6bd6da020004b87addf860342d1e9c0e84167055e3e4866cf1dc98e123f2c3d5a4e5bd882c7075c212a41900be564d6bc90e1a572aa08104303378aea1d9074866f85a3e583da607a4c8558298abd7eb83272b3a7018d5c04a32c30d573d689edce4b8c95145498719d335675c116d9a24342064301a168e8baa85c30e33c45a375e1d783886ccb0022368480081959819216e7c362e2433d89196928fc285060d5c3a354a3523bca4b448801da141024524a161091f3690f958c11997e450c595cd692b0000708070784e38609900eb1134430decc8ab26768365842909ab831aa533ae6a604c3851b1a51f494a35344b3751249104ae76b478826a5e431ae0bc6a6c92725a40e2aa26b6e4414d872b174be6f25e2abc6a5c26bc482832b2396982c4965e35b01e38483421b49c6876e4e060041f1b90102b4109d40833d4c440d8109c2146989e60372d980fd7102b88e6a615842281d1c06a5a425c4a5a42483cd70f1610d7063e34b1917524c9041eb19a0d688ccc70e4644324a6d30aa115c26b892b126eb4905c25b83ca009a1c5018e1f5607345c81423c968dd5d362b95cc869218829f07d620160bc7079628923729001062b4d65fb91d6821416a0386912f5b42485a521a1213a238800026d65000367c400630528148990e3a3074c0b07b048020915925018ea40c78d4d78f3d4100c141de4cb9add607c36be144cac865a8cd959e14c1e6981c56f5fda2a9bcddfb5aca3fe232d90807a88cbb4a0f9afae9a1aa7eb5259d623ab2f0afa39b3b8ca028a67e193858c0364c02934d20b072c074c92ac1c409213f7f8539f39e0239fe6ef7afbf2c9b27ec615f114f1149966a86b0d110fc9b4b3fd1b0d0d49511e6d452c7e747fc5fd1aa7ebd2d09014b65d2630eeb427f13844dbc0167a996ce08a5975e10668baeac20d2071df4028bbe28b839fc3a3aeb827ed68cfa82b76ae5efa38a92b9eaef07fddfe1a7d5d3f6b60cb2d9f660e679cbf4acfd54009927fdfc8651a90c9be8871079d644c69651d9340dd6fabdfbd12bd7baadaa98b764f55b47b25b9dff6493c7e31fd0b922f32700a4def13efd764dc0a2fbe5f5b91c5c12934fdb76a8a667d9cb85f77e0322b4cb12c2d95154aa872cd7d2e9232f14eaabb22d55d496d7b285d73d2d09054d10ee5011af2243eaaed6a13c9e494426e84a2ab59dc768abc96ea56ddcef85af1da5c59339eaa3911db2ed3e7ead53827ee2aeaafb237f25a256feeb6f854a3dd5fc56ad1aae6c8ffb27662a4dc68a5ad6a4e29634b24e8bcd832118def644815516e843234045455c5ce1dccaf4fb2e676796a59152394baea6c962c8b8686a430796a912cf105c660f2d434bc40974a9a27262f60e554f89ca23b3a53748d720125e4195523ef47e9fe5a61a3b2ac639114918d89795261bc84c4381d19700a4d57d1025dee2962f2d416c8024ea1ed9e4adcaf2d6085c9535b80861528632bf95b56012dee0e9eb50a24f11c66bc4d9b47fd48dbea9df25f1d93dd2b191a922a2a2a9222ca58892889c80a1bb97b082ef3b2c5bd98322f480e4ea1fdedd6d224f39345f9a132d2d67fb3e43e4d9cae4b4544e33bd1f9c9fa4bd87699b6c6e6141fa0294043ac9ebc53a461f52493a1a1ad7fd752ffdb84cedbb5bb6a6e8dc7229289b4626e849271ce8fd375293742216d5011a0259cae380a3899f15da5f0f252d820850fa92b8e4e408d092431812777f062f3a4e4efb152f2d61ade3c3cf9772de904645e3f4345d16587961ba190f7a368bf6d5827bf8eea7fa5285a8822b6b5cdd910dc96929ff1c5b81327812fdcefeb50025048000012e0c197263e55dd344ad62e6174c9e260d311947c628ab7cde14d29597134ece2a44bcc1dcc915211c0c2ac79d3fdb208ec22e0e3eee0cedf8c6b3a745b0ade100263408008082c3948dba13c24138f0e8d5655dc499a00011d5e1cc46dabab9f1fa0c30382a018e3e0e7ecce98f2d4fc557f6fd7d65dcd41f1e4ee4c5ce680329cbcef00313207ece0a0d6a736f1d6f4a4f2f99fc81c20d4801334004b79eafd37f2b4aada5a5aa5edad2a34008a7bd311147a2bb5e1a406f09031c0020cc00cf880011fc8b860c1c5caefd728c6b71ace26d5f67ed58d96f5bcb58982e49b26cee44e9f38ffed567287d6cfb74bab0ea8afad393bd6d224bf8a0b757730d7727c1237dedaf3f9e80932e48ff471524f68b927b6d1afd55d37ce7da6e113a683f977696d4f2c81f4b35e4019f0628a498a7574b7539655875e1d5e40976b73f9f7933a88d4025e7077506a01424e9071076db6b33656936aac25ad549b1597ff09bc96ee60f2d4da525d754e78019d4842e6c493a578dfaedd27ced486e9aeea28be25e5f9b2567163fd7c4faaabae093337fbf9abb0be55d68416f7fd9fcb7893b69aff38a92670e0eed61cc578e7bf3add041277908932fac46d4bdab23e4e8a892f3226acc09da62328d48649ad3fd7b6d5d9f88e54c6848fbba55fa3d1a4644b8c01c9df9fcd2f6b9526b5c40c5b677dd6f16b947c7a4ffa3829f2c9a2a2b2ac474554572a4544941ba1d89c5d221615ede810e54628a255cded72b5a3b5fdfd5a91d64043cedc6f2bfedbacb0d1d64fe64628454456d848a6840e0f97291103a7d0645bc06cd9b20577cf2d3e0e4ea17dcde651d7b6c8c8fb7a4c7e5b125c3c375ac992b802a7d074f95b243f09289f42bbd8eafba63eb5f863c6b6a1215a14fa1f959f6ac4a510d0cfb9ef8973b5485bd526cab32304f473521e1da2f19d8c45f96dfe938a58b42374521ea15387e88a5fdbd51cf9fa4b8c6ba2b6fbc49fb39b7c2754232e8d7665c162309a22e16c0642083a20384d11978baa34a95c22009901f971ff51c1dd7fdc4510c1e5342e1a9c1916387777970f2aa044e9254439d0dc63b0978b86f579beef5791163bc95f96f5a82ceb91161504b183115218d1c50807dc8cc88c38c200089841004600c0852096ec9c116f440b412c1901c5ddadcc5cc6591b7ec88216c1c5292de39c5fdca749837259114dfc2fded49e9447a9080d645a5ae0645977b5d18b2f0ffd1c8ec2e4a96ba6c5e91cdefad4e56c2d472b9ccd9bd535912c2d4ea4dabadab0cea499cb386c13f5eb2cea3a8a358bfbb5aee419f5b56a64e6ead69fb3524c3ad44b4c724cbb7ceafa59ca0994deda96bf2dee8976d2e466b57ebbef898dc8d2e28ea8ead6b7c9d655e988fc5c71514c660ec9cce5256cabb93e69245d737d0a6bae4fb942e98dcb287d2b2e491dce47b911caef0ab55feb30e79a6cf59760f28c7ae209776f015e134cb87b4b784a6c71f79240c2dd538077c45058fe78ebd18d3c53b85d4b75f357e124f96d173bed6ef8e64e6a6785f3d3107e5d9fa49820d15d7d3a732605698066146007e75908e1c5871388b1854b600020f0f2c3147524ba04d1850803332e000c9015041bbab80d3a4431f2624217b722c101ba486203342f83f802041398808f571cd4b03a6ba83f41807863012ca0b81304b8e080002620c2952020c4160207c0380a356011440e2746f023322b57f850012d3ee4080aaa87cc8a83381ed4a005b5f94e0b3ed00083fab9083b5c94a10282e4406a06a0112922c73b60e3011d48010957ee030621b4400d99e73c86886145491649b88e295a45bcb8c1876b7005842096c830c5673a5ce181209c94c0715c801e8000b52e57377450d0c50688b4f84d17294334f002832b9bdc120a4526c16b8ebeb822c304059c464802500738411157b1a5081c416409dc158c0a0735d8e064057f3d51a60009297af0977791982ac0e4aea740a2788103496b0b08eca042f383b75800c28726640802098b05e1991b7ab0171600aa8c305778e28aa5d33282012098f1f289290112471be470f5ed90c1034c2a332f1f9315b9032531d0f2dd3822840d4b977a5b4860021b8461858fb765892412c8321be3c10083130b50a38b27cf09141518401121a83784044922d0008a325e0e2c04e8a1e5c0e266320f4ad6e0c0957be1d3346e0294f12f32ec114050228b43d141090ac01ee878111cb0bb870ce0fc07164ed0e2a668c96908824814379a00e3e7122b96c86081256ec511ec94e080166f4189a72f9880e2ca9bd00c61ad9800852f21a5904086e98ea4254783528a1dfc04246a3eb013c47523158718722f04f98f171d170d57ca380f039ec2e4548185eb8cd0811910b270e021cc7a50b8a2e28a539b1516352419c13bf8318409333488c27d88b0446b054398c077c0108215041c6980ebe891450384e410c4332803012588aa40037c0653a2b2a10726c7b144c78fd404ca380e05c6a88247931bbf819202ee220ad06d3668238a1e1890c069ce987294030531f018191a986007213e8fd58c30822953ece03021323fb02c6186bf5e10a329881dc07057980c30104601d65d4435e70520d4e02d287e0460881c2e78eb0897081411840a676d200268fce0a53a0bf702dc112cb0c259321007890ea8e09f971d03177af4cf4a0c19cf04a27f4232482de0e7c6bd361c6064822e0770ef0a2b7609a404b947448e1a0ea80206f732d1fe028318f796643985e8c4c03d12be78f0c10674dcbbb981c05665873b0c3c10c1f5c200dcb72080124b5c4000ee16108090323b27ee0bc0828b27988c7137008c2cd3c1903b0fb2aa145a1eb8c7208198030e00833b151094c06b2dc0dd86c4aaea82e31ea504b50f062b7087ba6185303e9eb8a700022e84045871371a02044042bce06e42187393f0722f424512100ab870f759b2586820c8bd04110361e436dc4738e30b129460b983e0568c22c8c2fd032a55e0d0460eee1ca040231a0a807b0f9a10326a74e0bec106a6982141e39ea30a15da081fb867f0554865b47097d9b0c4088440c3fdc6163b70e1840bdc719cecac6085e96ea3c68d13c08ce05e73811368051ee04ec3c4073d94b0847bec8917a8a48001ee300edc2136700177d82705e4a105ee2f2562d49c38dc5d5b7042b0c3e7eeca2166cc0a8cdc5b580460458c16f7560f2b8c18000b77d61338b10d90706731e1c9a28222dcbf346c68e8a1c7fd63a2090d040085fb6723e19b3670ff8248400a006471f7c4300294e173f728500480d001ee9e0f3170fa02e6eedd9e886e43b87b465facacd1c5dd0382860f458edcbdd60b0dd04101776f81c743dce1ee1e08f272c50fee2e4508b22849b9bb124730d143cbddb37c8e0a15dcfdca0a665f6aee5e458d323060e5eed7851d02a071f7110524369872f7157472784289bb3ff1b8d9c207774f42c62e08ebee488c0853c50bdc7d480604169cbb13d97932868abb8323a8c04801772781874f130db8bb08608878e1cadd819899fa401477f7c0cad25490bbff98a2a999c1dd7968c101490feebec10741981370770dbcb042092e6a209385bbcb68b828c00adcfd062e972374f71b18600d68e1ee365a80282273f71a2a65f4e0e2ee34351f3fc8b87b2c878218da70771810576140c0dd5f1c28857070f71707b42e94dcdd0543931684dcbd15c699e487bbb7ee0f2f4631c8428a20868808e2eeac20902900eefe9d4008066c71f74f88ab0390e1ee9f94cd171ef4b3c78a4d27f16bb5d603081e1c4a24b8bb03e181bb3b103d40204692104840870135dc9db58209c584981c67c870f7d616239478d260025f8cb8bb870210519860c1c10052b8bba7830b3ed8c92e2226dcddb3004b860fa410a60aceddbf1b40c88100148870c506dc9d65c60b5a6840131818e205eeeed10e70a48912102d90e0ee1f142d3cc0858717627e707716979b1ca47e3cd1022677779d902b3242888fe70977ff9624a190021d82e88086bbb374f800260c21b248e10177f7cc245c804002a088c1c3dd3d072ca0e6385220b1c3dd5f041080192fcc501b48dcfdfbc28c1f5e584308141871f7560168b021701b98f2c2f7dfad7ff7884284e0d7d514d2d404d268bb9253687a68c8567e142d0a7ddd84823ce45b4aa3d5fd39fab89df111583ba39eee8aa37dc647d63e0593c0b2ac47e03ecd6be9e730ed5f094bd1fe6d655947905696f588f6361eaa8235c3a77f9b88b728fedbb0ed5af17eb9ad4e6b864ea0ae22dea24dd4fd15f11669d486cb5a654f2aa4154b8c4d11ccd8f6398cd35933fcf16227106f11a4914c21ede23b566b8651229871a51b9f54a8b66688b40b33ad4708cd9a7597bf0a8fda926fad76556f73127057da6c98bc15772d99311e4b3ce28db3b9e9aeac39bccf4a2f8fa61aa90dd721bd5fd39b0dd3f2499cd327f8ba676737befdb5b9e66c36a9b6a4d5b5bf91f54677fbf7b54c4f2125ed27d9813f3dda525bf97a5b6a048220e5012ac188f2fc9460a4b36f25ef8ffa494af1b6795bca63741ff78482e46b8d234ff271a336adce864f484e215412d0967fdbfefc5305f26fb72a45133f3f9931d5d8a4bbafe92f4d5a626cd6e8aee69c74d556d3685affdd23def4a96a4e97b6ee27e9cdbbda5bedadf1d213a877f94f2a4c4f61c6a02ecb0aeefbb5fc4cf5892fceb8f1ee74d541c09aab69c3a22802d12789b34931120572bbf656b7a6956a6bc3398cd381bba72aa9f2e76825ada8ff9e6091dadfadb7c6651533d6e35bfdc5b42ceb675314b5adca04f7eb8c773525274fd486a97e51b4559925c626a5f7766dcf67932a3979a27ae7b6c636204ab7c6385ab3d554936fb39407db44f16250935f5562fdbf71d5586b94e7f7f966cd63d4ae26ea6d453397712288755973f81e5150977f6fc58d36acf715a9e44890e6aabb3a4cd26bb735edf8598b23d6f9f5b6e12a8ae06baba3d13e87a3686fd2df95b4e4ad23ada6fdf1b378b1a5859ff111131ad5aeb65f49ccd82682b4a3dbb5f766a368b4527ceb58da2a569b7fdb3cea6aea7d62b0ac27950fb27bdcdefdc5b7f2d45c4d5cab36cc444551a491ff598946b5adb9da6ca5bae64abe3ee9adb8ac944632d55c9f68175bf2b7b63a7feed4ba9256acb99a2298d33557f3d6f13f2b51907c6b9ee3883fdbecd3cf47f4d6aa2f3f535d478ac5c7ed8cb5fe713c69d87699a0cc13e724349a0e5f1add950a57d1df1ae374d48629254b8b1bf7e336367738fae4eb5aa3e457b1d8762dcee689f38dd2c76d50ef4f0292aff1a53c9a2cdf56baf599bbf489825aff487efe7dcfc7e5fd5c0e8f9f9928a82da596664cc9276d15d33e4d4ade5a96a7ed49bdbb955eaca744f141fdd9fc4ca3d1ba99f0be7a8f67dedaee5b6bb8ac5127265129d04a75a5db1e25a1d15d9ed5f624b566485aba5fefd7a415f7ebfd5aacd256fffd1bf942ae3ef57d7df1a8a3e01e6bfe7d2dc55beb2f45f22dd5b6877ed59428f42f1e455b15a5d16b29fd5cd5f42b8eea1f2d792db5d52f2979ebc5a6aea7d6f46b3cbfb1137d7d6a5bdbfada7aab3b9bcdef84eaafaa5fbe2e932848bb34a8a6261edaced5f91c8ea227d0a88d89829ada72c636a8a62651dcfa6b5ab4d52fc59ccb691b16732e07e230ce14aaa9897ead1ae1b6d589a2086eabdf178fd75e2614dcb9f9dc64ae38aaad8ea5b76b6d968237f0298f2e6db5edcfa3a6ca58b7ffd2c76d5d47b2e25ca35fc357a7ac232ecd1fc74abf46defab51d1aeddaba6b5887569af1a69b8abba7cab84dfeeb537f3ef7a944c18be957d9926afbbb52babb95820f5acab371377bbbf66b94c4b8fd394cf5dbf23325eb6f5d6dc6f766a9fe5ccdd5a4ba52dac6a5a651d54d999ec2db89719b0e25493171a2a0aeb22795b7b9daa969c65afff8977eaeda86717ea2603e494a37d596ee9eaae25dd6d166bcb5c595e68947aa2dcdfdadea937e69e6aad5d31b52d3e274feabc3364c2bd576e352ef903fde13df5be9aea4baea74a8d55f32d7db677ab1bd56047123369fee5a824946a1d179b3b68bc74a6fd7eeb62debcdf6a4f2995ab3529aa841bc29cde16ca3bb5cab68595a1dadbb92fa71d7be0d54a2bbd2342b782d259f3ccbd152fd9fc3e33ef5598ed7d25ba935c39c6bdad9fe8d40bdefdb722ccbd3f6faa44c4fa1b8adfe927ffe4945c8b6fa2beed72268c3389b95eecad19ae11326a0352bcd4f95b14eb4d93048a974ddf4da8a1b7b6c364c1fb7a3687eb23e4eef5e0968cd1a45c1dd53d112a8ae666d4bbd7baa27f1f8487af754e2ef1b6852f269d292f0288a92bfebfd3477e66cc50580065106671dd536882e0e0681450741037da8b641d03a03e2cbe3b6898118c23de7ce9b3b2fc964f50f66c029b4fb26fee1cbad5f8e3f64a1c2e60fb81f7cccb83b2eb5fe11cb17700a0d8b88d3f6b1d0ecb55874dc872ddc072dae6d3ed4e0830fb86b0f65f29bb4872f610f3bd45d91740f4b7ebf8798ee2f0f5e78b8e23c40e181c70e6476d8e2be83b97f07243bc45c07303a5cd1018a0e348732e0145a0e39e0dc73f0c1c18ce3d0059c42c3e10a8725700a0d07d9953157b8b85f311d9c42bb2274dfc457620e4ea181b921cb0db41b7c6e701bbeecc6fb5b5badabb9abd990c5069adb5083b85fcf6af0b25fd780c57def1a68af6bd0995929034ea159e962e5cacc8a0a3a87452c5ad5dcd561b2c478b4aa39ab8ac57d925627e236ce5f456b118bb65c25ee6af9f9331e45ab9ad3bfb17edcf8b9dc5bb455995a87715ac4b7dc5b6bb23eae52615c8ab76b75aee6fa6434de9d0ec59a78f1950de72b2019bac0381d8dee6a3cff364a33eda6d930e5a1fddb68a1d6b57c9bf86f13ff6d62d12bbd4d34ed46bae9dcaee2beaf8b848686c8ad4553c760c6a748e1c1385d142914cc36062eee0e5e1c430c48dc63e0010319aa9afb8b290c5ff0130c59dc4fad6b09c3938364c6365bad5230f02837ae7ac1cbaeb4babe70be401d3c699487d48fdb7b575d712e94016fd7851e74f89ee4dd6311365d90552173cf2a5faa6871f72aa67b95255ca577f5eadfd5b26e6bd6b188f2d4a322fd1b3bf98d9df094f849a708495f5c4435e225bfabd7d6ad6b7557efc655562eb0d2f2396c45734b7ec5e5688544c98a475935ad3a32475deaadb8a46519eaae3898b1ee7550f0d66d5c46d95a1518b8aaeaaaee4afc4441b256d1bc4ac7dda9129413a7d38cbb9f5c78ce2b77da6b5ba174ef44a3d7da66a78ed7a80e72ee2fe0eb9d65d3665b666fd96eabb5d5e690dc4cd7253ae60ee62ffae62bb32c74d2e8102a63dc3d5f9efdbb90cad5df9bad66ae8e54a03888739fa3325484739fa3b285c9e5cd2b8e7b2a6c9a38f739913429f9580db38ea712f9dad65c94df161d6d5d97d42cf67107cb32a4b3cbc5bde220f9a57966b29e54e8e34d3569abc24bc49d36250a8de6bf5d5c5292b4353abb1ee4bee4684f68e66b8c1b733bb8833ad2e66cb91cee2005495ba33aa5524c79e5e0b5942c1f47b5a556b7672514f75247db32e6545fdcc119d516772a2c0ed24b65c3a59e32e3eebabf535bd4a6b8383885264e594931418a9afa291e6ecb580fd82bf7d2ccd9247457cd209f67f629b43edc2f125354d5e498247ed1ce6793bc5649ee4cc23db9e37c70b12ade4e706d4e146f55371166b62ddcc1283048cdd534f1a617d39a6f5502353dafadbad0b6e54e11a3d0a8eeafcdeada943cbfaebfe4df8bbb8b6559c5cf024ea191b9e2dac562fedbcde66dc53741fa4bee5e1ff73aee471f73f0ab584d7271275f20f1a666d5e95c356e63d0ac3a4dbf1c77e3db6c4985d08d4b5d473223168af7eb3ae266e3e3a4467777128fb406c6419269d7b238b893d3c1ed9aeb13ad363eabc1502b01dcd5529fe569da5b73b87c22f1a8fb5bbfb85975ba6631dd9d246b45e25e7938ad8ce3527c1a185a02f2dfeaadb9a212e3da5f8ba7689ff884c88208a40518b4a0a585215a409a4d39630a18076bd8ccfda999c07bbbb6fea96b0fb843a3e950f27b6653b04cc94de1e10e96f8946246ca172958aa265f0a1477293aee1ec54c388b428128b7281fb060c6c11f8bedaef975adf234a1d5d2e66c940d536d495ab62c6871a7d5fcb4190b341a0b321ac59b66c32b2025d1d13c57d8b2c295575c955a21079430c01dba53965587ee72ddf72c7155594328d7e2291343c1e2b55343a139883194988354a3a5daa94fb6202dddd17952cb7c6aa727b4273ab4fb4bac6ad3d0102d0ad54f5c9ff45a275c1c0c5262ece4e95eece424468d9a80d1440ba8b3adb90914776fc2c149e5c92aa586d496bf18975fdbd51c296593c211154594095e8b53d0e15237d1db1dc98b6b184fe1fdb92824da294a06e28d2fd41710ca0a541215cc501ebc77b75615b858555baa70e53e53c107b4375cea3a32d127330e92afa34f4f11983ded4aadbfccd84633c69b4ea10d0dd1a2d06be993cc73b6cca78d3219e3a710d2d212e3272658c012639b2913288e315252ce52ecc444875ad51cd596eafe6e8cf3a8fb4b5e6c54f3b6fa22dddf6bf194786daec4d814cb5ac3e5adc98bfbace90a94261dab9a139b968071f7d9122e164f2db9222e59a2335362c6ddc59912304ab628b9721795a8a04592a9c4f8a928ffd09094b82bd4d1ae59b4aab92ff7c619b78b76748a8aa4889a509e9abf488a48bc36572bc238eb2f315489f193fe12e72229a2ac97c6804b5bdc974cf7a52577f770c97b747f937cf16b294da2b3aa4d344d5a91b462121d2533ee2459bf2809f11b5715d5f03c511e92897c7d8a4bee0eda241dbf5513e9cbae397dea505ac634a42c7eed0c0969b455f9f5c6778644c2ec882c31a5bae2cc7ab4038dcf8e68b3a39a31ee9fcfaf2a5fe71eb7f1ef5b733d6f74e38b1bcfa89bb199fb4db535dae246a6b6746684c4dd6ff5291c330b15e0e0eef94c7735ba2bb82d79ab8d1a1a92fa5b35ff56cd10099e851e805368b9a88848e713d77037fb5724efdfea93ae6511e5f92a36caacbf440434a409de9789f2089d667d9c51936d6b4550484b444644e33ba11a6b0d0a29e3a38c6dbb8a78dc186771a9acfbe22a36635c91eeaf1645ddc43cc9fab8a29a9da21dcab33f2a8ab4f9d4353fd35885cd8c772d4b7cb41baf4dc4bb8afbc4e2e7ec364f9c937c59b3e689f34d143fe3a3f262d13c8b9688b649fe2ef2a9e92543434343e3576d5ba33c42a735d27fb1dd3bdc6d4b34be93ddb64b44196b8dd3754953e5aa138b9c28cf1251f8744434be932227a223bdafad79eb8bc7a21dcae3a4b3b34f26948764d2a9c2e6f84ec82228d1cc9d5fc53a112d3173798909a7ebd293f8e8622b8ae715cddc295ad55cdd1589acbf248ad7e674cd7f44872f25e911211e5df3ffae1422e30eead2e274b467eb6aa3b76b85beb883a45029b4c34819bfd83c29881b7725efe79aa33c9a164523695a87cb12dba8de555d5943235ab4b53323381abe467cdc7d1634a6fe12bdf69e4147f82c284a508f83f573357f9083536845c01461e2c92a366a671f59d526f28b9c501e251b97511b97e2d0d047511e9289f2602745444745524f64c5e5ae9a24d3fe28d21625e1aa225bfd2544e33b79123b118def842cf110b3e7331def90a45308e8a7c9ef9a87f000f28063db65da1a3fe1b67d026452dccde280908026931eec444d9801555cfe6c81efec8709a75627de2fc59fa71f9d990f191f289cfca121291dbe3e393c95ae5254ba6e1f21f7a39990310e92f76fbabf9fe94c88172156b78e33215048211aa72b0ef4a2ebc63d17eb13cce220696d20cddd4c320379e4ec7ea8cf55272a5da57ab0f4d07a847a64ee3c2af85cb5bb6d8b8a9e8686663c347710c72304ce786218a72b810cb8672560d9d9d54a803234245502757712cc5430e08c842b900412623b60e88e961d7347a8ee50e98099e96471f075c59934d4a1e9f83818c40cc6370818f78b836cd141ac9e6a949a05f101edcf46d8427fbee16cd26b69b5e13ac216f029761a81e656b311e84c04323488085bdcc99a13e14a8424be4f1b4e575c4e2ccb1ac215a79a850025041d31bfee6de25f0c42997bda3e83b00504118425f16fe4794110c1fd73566a969345eeb4614aa576655977d14e2ed7b2eab2ac4745455463cd5bbcb68827ace1d9956595a23c479487f2508d354be5ceade96877dd3ac808fb7e6e8ff5be786deedadce76a7e36493b3e793f879bc4f1c9a2dbb5b95c542445f4a5794a9133fa644b9b0de38ba92c876f38fb600b904632d13ee8025eecf48100284949a6a50f3470af5fdbb93eee7e296adc3e4351f777e64116c739dcc483eab75aa37f719307b48ed5166a4be2f27740e6566b9f3bf0024ea1cd3ae0c186c57d71b689d78afb34ab3e9b9ff1a8b7d5dfcf788c1a1a92ca6f9aa778abb5d2d64dd6c7ed44511e273a5b676b1349317f152e4b9c49a6a1a1a25d7da2bad2fc5bd7dc8eb422f9bb7e9e7160e6cdc1925b5c157220fbd1a5b4fb9e347f15a6ba2c713641575cd4ecc795fb8f27077ff8fc70cf67cd86b7f08145876fa53e9e707ce663e643e6e0e7700f14cc7a607130e3cd44c11e4f396d6fc58d9f9bf5e031e3b1c58e927fe9e5c165c6430aac178f968ace78cc768c0177ed715b874b3cdbd105bc12b263c933e6d9212b6bf666cba7fb77748312b83b58c4447988acb05159492b5a3d7999c8fb66d40642b30d78cc749439673abee8c8e23a4cd2e2aa998e2707a7d074e8805368628e3576153f5b534911c94434cb719503cacde1e30e6a30e6739d6990c5c16d671ad0dcdd7bf0c5cd34e0e1387f15a959065dc68be9ae94a664491323310929844af2d4445c8a0a97442861138dde8a9b32b072cf656ccb6f56f90c96c05bae33319ceabaf53e31c5b708a7eb126ec6e59eb32be2cf6650dc67331dbfa7ac8d8d6fd18ed4538d4043ee97b76b6fd7ca74ce9bee2f0e330ee2f882e32ffe1cd6210e1f4ab5bd31a64677904b6f6c719fddc039589b66377860a719ce98190e1707e9ce679cd3a1bb5b8f663850dc431c1f0733a64052331c0767375daa4edb9bd5f8d2fd3bca43c31babaa939add20f152f7b699cd18b59d7e9bf116f7cce6c946675663e6479cd568c05d57a2cf59a9fb385dc645a1556ddad8e64d79eaae484536d1bc99b5d79165bd7d59da9afb6b6b6e46d3c57de7198de995e62987331aea1e2bb367b105cc62385b7336ef6a7e9ec574c8cff5cb7106dba2a776ea196c8b83ba8633186e06a3eebb5ba96e82cd5ebb6de65e5fdc1d3bcd5e5c1c24ff9566af2ce0ec05c5b7cd9274f692cd5c65be685716f7abc3b533ca6535734171b047d74e3d7309f9cc256b9599f9ac0586eea846bb5b4e80b468a7ee8a4479489ddaa96ba72ee2a92962a2ba52225d74fb5c7745fab70d0d49cd585b5cb3ae5850583eb97ec992815368649d7d6448b27e5eeee70f0bce8afbf5ec43e2e0149a38fba84ddcaf675e19f18bbbd7ce99a7c57ddb93cacc83019c42ab9d5ad4459447670adbae2d22998894b6987917dd3bcdfc8afbae46b59d396de642ee33e7519e29e3e0192ef975264c9db97277906a7b66c91dccd5f16be4191dd7f68c836d70a1641b58a0da30dddb40e20e92656d8387bb0ef2c406995a568d4d2736beb8bf8dcad8c0d1abc3e4be2716f2568a0da19badd18b755d83056b64e160c6bb9e7847792896b2bb1d1631511e29221b136c49d28a6b58adf15485b369d63568b6a6d58bf7b7d5ab51e68b7b396a712773d559393885266ef3c7b1aaf1a44608bba2ddbe2736fab7e5d7d8c4d9144f52cc78d77c8ebb8a39ac4fbcffb32d0634f718c8a80e4d030cb893c6965d8deed76958ed6b7369e8287199193096fc3c9a69c2cc684686371a5ea08145e392ee13f32025511d2ebf46f75943c304c9d755dc4643c71d6f1d2a3b630cb893b1adaab8af699b3b834bed8c2b7e06eecf5802cd28e3b527290f8d56d68da5cc0003565d0e9b9165ebcf3233a0b8833b66504a7eaede32ccb80e97466570b95fc655595a2a1d15520692b08cb2529db7fe4c77db92f6a4624b32c03878b3388af126430b59e225b2c43832cc9ecfa61119443425a7ff2c35869931beb8975534ffa9c6c8e2e239064e8f21e4beeb93a238460dc94435e225a4d5f93fdbf4ad96361848c1c0c9d7b9e77e9c94a565c03838454a1429f496d12255e6292ce35527c610623cb93b782db5569f3f478f28f81793364cd356b5894675c58921a3551762db65fa010a03cbd65f1586a9bf2a0c24ee61f0a87ac10b6ed7922f58c2bd6868488ac80a1bd5b2ace30b90b8e3b6d5bd80475956b14c05e38bbb8b6068a9b24f8aba823183835368e4ef138ba4ada6986db76b499cb37af2ab585df3d7fca2bb1fb9cc055d5c5bdc1577509ba10ba0b88337fab906d12e005bb085d36814b733d6d11650b5e0c9cb1d1d9a5be0a036ed68b76641095890c541f2b725731d6bfeca02d359d043660b77077327a92d252f1e5ff75813f66b5a33193257f9ab584d66c9ada5f9d889d2c749911961056068cdcff3f64d9e8cc3156871d7f9aec08ac4b915e05081142ab8e2e0e34d773cbbb2563776a2d556fd14293c51a45050053e0e565da8821b590a309002d33d054fee2099abaee64f41c639fa8519f04f2af69e94bc35f79be90b2fb86d3e49775f94a5fd020948bbb6e94bba6b130575b8a4181f7d21cb282803a2000c4e6fec84822e200a5c40810e68ab1ab7a9f94f45560a4aa120963b41175bf3536da14e8025db7a7402f3043cdc411dc67d0eebaf95f59a000a9ddfa4f7477dd2d726686282d7182eeea0105da90ef217d36876c736d12e36bab68946ffb5ceae667b927e8933cd78cc15a8a9ee891be9ad47b2314b94690c0fea45184eb4edb359b4ef8ff99f4a8cd39f715bb4aa39247daba9c5dc79c98b6ba2786daea8ac24598b8086e0742d4d262510aa620964eebeab64dcf38b98045f3e93600b09acdc5d24c1d38bbabf24d8afbb30239af5731760bad0d28555fe2e96bae0e1fe65ade6bfe55975cb8c008cfb08b4e4fcb627c57104563e02280e4ea19d23a05c98a917d7b8d862b4545c70294b9cf7e5e24a73b1742d171db88b65697163d1bed6ac4dff59cac8d6fc385db72ec26df39176e417e92656b58988ea9eb8a326b76bcd5317e97dada88b767488c6775234be13b3a888f2107d3e3536ad92ca7a5e261dde028a83fb9e1889893a6d2104d22d5c5bd229045b3c155408acf8ef6a47ac75a578e72cd541b6a558539eafb2e52bd121780a818e0641190777eea9431f045fdc1d04c11512d357c1d9168260090431777721e47fb6edfbb7e6f29b545b315b4889e1e2ee2ee674b0aca3bed5eac92b46480c8f30661c9c42b38501e3e0145a182e9fc36c5dcb8bb1a658873922fb8019777087fc2a5bca3e00c507761f28f2b953bf4e481615511e0f78d95afc8defae2259e4443a15116d6d97904f4a95f5af2d2fd3d2fd72684849fc7c9a9f71074220eb801607ad9e3c6fb696abda92dbeafee6d3a46107ae48b27680d6010ada2ade370443c6419e5d597521982de5be38ac76a834d0c936ba20059942cc8c80000000000063110030382c188e884452c96ce53c2a0f14800378b8648a5a1d4aa3284721841c22600c00000000000000300400a242a4947caf40a86d2d8a2043f4b6a65378d87ae6a6ab230be45da167914bc20d90f0fec128a4574c60adf3063630b74488254ef812e75ecd5d5912a184e0648dbd6c3a38acf26b630308eb6bd7a1b8759bb6511b2594fd8a19c0b0000519e059de9ac31adf358cecc69c2ca94390fedc361ae99846f4121ce68c205e49502cc3391f58f1866b308eb459f613e311ddc805947da5aba496b6ebf0a33843510343f446049aa4326a37e516b78a1e10e1b8d56f126b02e0f6f4502bde60d6db12fac33a28bb704ee5c358c1c79eb3122a76b7c3ac0a80f35006247bbf75d9d4d9ae60cf90a1b4e92da41532db3e92ec2e17e59c8793fd12b814dc5f8a4feb9cc241d3138a2d6b1d98a6751c58e8d83b211683f54b50a83abdd03487dab59ca6832611fe9253075d680fc6e530f061ae390ee16ed95e5c56b71c6679a68199ef410a3c22eff800bd26f830494dd13e1fb318e84a597c1b6294b06dedb353990559984e9193d908970d06274a08dff4b3464ff42814dd06f2e95a850daa3b049bb9f7cab7ed688643db4b160c5fb7c2aa475804f0dceebf899b725ca23c97c835c4ec81fc80a617131e0359ff1ec43518e74e4abfa25a798d169c63b71bea6a0876f326f9594ff3460bf10deb64836bb63b129802c076e7ba8e20dc3ec319378ef2e82fab4ab6a65f629b495bf8f5b25e37cfa1b639076c8f09dff532b8e291b1e173eca8fb1e39dab46ccd9c321f1a5771164460179178c07e4cfc10e26c471bab3b007186501b202e8305fcccc20c68da954a976833cd217396075c089c848db8ccd07f53bc1e67e884cdf247d3d8376877c427e14baaecd9b47b570099bef7a97a8b71f345d6ff6dedfd69608bac7a8e2e39a75c728ec1ac7a7b5efc062acc8db73a68901674e3b1c2303144902400b5ddae592808188c4b9385dc0e9797ed9e89cc7337c8e7149b71fd5caf1a93b1deace9cbebd8df990ec661348e112ce0dbfa729e605060520b4cfe2ac85043b846e8bf5f689bb61bcba1a6138773228c4269ad81d8021dd7e0c2a08b4cd02ec0e2616ccc1b135ccdd3e57f53455a5baa2de4132e39380223eb18fdb65e909703dfc183a6a49e9d9d17d0c8a3f7924b6d09db6e7630e4103bc24585a948edac13c5b658aefe7def5b4b30cacceee55be5d96d763a4f2ca8fc11c7b2fa04b0d8036d77af9742c5271643eaed4ba91c03b3f0ff203956c2c0465a52da89445dc7c5cf39524b200768bbade85da51570a4da5e690d1156f16eff5323b2821b40489d8796938ad50dd605b1b1c581101e228b80bf78aa31fb0903e1960bbd57fd23ae16558def81a29dd2a3f97988823fd9d93d8e05c0ea3c3766f895c62c05784c91dc311fd4c53d5887e521502c6a5257c3793f0aa6a0d0771afeb24396cc5976f2b0099b52dc96ea8fcb818efea56f1c79d3cae5460ba2ef5085613c2116857d801d376db11764f84c98af92aa703011998ef3e7d0e009d5c0a348a2cbba4c89f4d1fad04f9a916045dae11a1fd29dd72094e0134e4a8be219c8bb6fde59f12758f78cb5e5692595101858adf0b29f5b7206778829db3257231a5d5e6cc4becb375c5cd6fee1cb40d98f205830aeb4c90208cf9cd3d41296dfe3269a7ed43d9b47782c2d031e92e5711d4cd5fc05e1ad14bd89bd43c0540555f855e0ed25e486a8a13893485030163ca7033f2578413b7f4194dca3173e90073acb74d78c47a50a60bcb16a02c4820a57ee3455b62a77378bf4df520be0468ced6d5b26be746edbb78918095016c046dc3f54e77fd82da3269abee40bb2a9afce3831b2ba014604c479f35cc441aaff21a35f3a7a2150c2b2c2007cc1a69910bef48be35cc274f3a59168280125d3437ac4ec80d185d8629d809d330c900a9c0f6e4596ac0d679a9adeb286623429188955f92033952c5df7746cee6d628bc82a612f27937147092c34bd1c0d48aa52758e8eeebd70e4bab2964cf61dd364ad4c38b38817ad349b64bc2af5279c71dd3d1b8e8ffbe65eb6ca39c993c7ba4ae14d8a4dcb1126b26859ec199d9119bf2112890f40d19ec71aaa58251a71b934d590ed45259536a8ceaaf8b4bf1a4fedaa785bd60c635a6b1a6c2c3cb8db5c10eb98e5d8785ca2e84455b7b7e9eea30a9f7296be652b83a765f95b390d86124a8c9142fcdb0c3c12f07fcc2043bab2101317b31fe0e91348d0b770b41cd71cb5c5d9188fa588e9a58f38d1ddfa851a9077756ab35253721e84df476e823d661c2132f25ebafd9629be55ddfdb1e7f8be611865660fe2e7df10d065dd7618c1c5c930a783cee06adf3fa7d7f1baf84146f08548906291fb79e4266d61c78811dc5ade2fa391ce8699d45021ae73718489538dac4004aa9fec4dd61a323fdb1fd206539adeac18d501629c7e892bd82575f2a3c11f8703f0f08e63fa4c6c2e116b11e7dd3c56741022753ada8a238e3c54ac8cf1cf522107fc17c1b51fcddf69f0836fb89fdc09b948c152ffc88dabf6e876e2497655e9c4ebbcaba3d50a49cba5039dbb113eca4c5569501fd4f68b77157c65cef1949e3b9d6e1fb3c359024819fdf5975525d37963bff51d4e410386ec092f129a71c65929372d94f1a6ec5c61eff688db14718766861564693771c893cc7806c963914652c75265bb90cc146b0b63a37fa91b627a17bf5b14f93459ee97ff4f7bab8826ff5d3edc58ece21718bb3be5f5f608a60abd0b8e2019731c17422e6e0022f052c9487d9540e3ca6c37d560bee41a03887a871e40dad42a752b4eaebbb91367be22c87380f22b28f32a229d7e103d39c97a811d144ba498074752e13688fd97b1ca330f1e5d7b06f84362c1fc061a306b711008a1d081c5b786ff24192c106b857ab43c529df37559f7955acd45cf2fb50f54bdf6ebdfa4dac735e98957a5ada8b2bdbce8fd8a43595068f393a1f30486bf9156deb148575bcf944788b9aaaef8ae3a2cab9efba10e6c7d0ef63e15c0ec7d0cf72a7c91dc38bdee0b4f08641aed518815c0485f27fd12b920b7e8ac7658c52c84e32bcc9e6e6669458b63dd922588c0c0429805348d512a87112433df46891ffeef80cd400752384490554874fb10204f9e9b4c7d81b6ee5b1c3837047a3010da714faf243f281d1af4f3cd0a3f8ee0eca5842b4e7a2be32329b77a2db0e5db2da37e0fa71ca73f95987f3318ac78dd945b1e6a605ef2a71fb72bad2ea8491a4c0030b0342209cb98a6b1d32987752785fdaf9160d10eaf620f97cae3a7473f813cdf03255a9fae097840f7681973540fd70f951540b073d055282b06d4acf880c9106af8aec4b237ad23da49b8571ce976ad74f22303576cebbea854c77967f862cffc6a9e425b9d246652b0211d61c00ad1e7ee04a2656291f7f66669d1b66575703153459574cbbaff7393d7039e0f0f93d40234dfc850a84d597c1852271fb56a3e185dabd41971ebdab0d1a5d3006faa02545a3fc37ba23b5359cc672103a05bfe4adb3127747d2ba6dbb33269086b4a5e050024aa7b5bdfbc904eb13019e5ce306a8428701846e95ebe7dc17628953881340b5568b1cb43b6f89a6ad15c5afed95104643d886147181fed4dabcd61e1c7740c1ba0df2b435c78133a0c80f3ca024864cc57e03ec7404da9b63242eab11998c4aed9e061e48668539ad4d3dee42fc8e7444df14cc0365ae61983d48369e078a9cec5f8f7943959e37233584eacee5cb0612f41a7b36ab6dff11a5c061532a51c9dffd358addab4b8e482b19df67eae8f6c6c8d2b0dbf0330966a124fa037b000dcf857c0b169c9a6184be227ab0bc75601f66e6908ea428f4d6c30047074a031b7775eca8a30bf4bb8b33211ae16ed10afa6602435f94bc10105711f455d1e9c39ed9abd7ab2ff86d629566f38c1a9c4cae5ab1ddd6b041b7bee8dc75c53ac0e3bdd6cd5d68ac00f6ec0a561ba8888eedb9104f4bb7cd74e4293d9c2ecc8b9315f683e7bc1a26b8bcccd0d03554317677d41fe03ca55fe46006773f83f9aca88586beaa61197767873dbd7177f53b99fdfb957961bf7fa5da0aa61d18082791bf8e3d275b8e91f8d713bf7377d0905605cfecdffd9de5298f0e9fdf63af6c11a8430345f983e41df7ef66c22fdd39f7928ad88a1dc4f3d9390889835457781ccca3e0ca4790fa88cdc06b981686831e0b11d3f2ed1f6b1496be4de62b0398ae49c4944ef23072933b600461acaaabfa661a2638cb0692687a3ed1929758fc5e194c46224d742788c53d3025c8c1c987132e4614e8e841abad5cae229bcc6e83c0c3011d5d617ac89d63387b1ee49ff10853f1861389c51d8267d7b7835fe182a8697b2ba3203dd8daae61def1ab272dd3e5e083eb80c4c948132c03c87981af1cd2bb333fdc7c7e3eeb8758cc377458d2bafee040e6e9f72243571e4df6ae67ac3808bb458826bab29a030f9c08db6b220c5334729b7d3fa19595077af673fbf22da884789db79912f9be1771aed99666e34b0bb7203973093ba55ff2a76109cc623041f32b8bce392ebeec21f78231d93a6e8ececa2beb88c37fc33b9326559da768108b3c7d4463d404458cb8b179c1f1cd6cad1269bf6b1b78302a0035c2746ec9c2b84f1c0bcdda1b7a90f33c957db52d18a8881947e810f7733c16ea5dfa666ecf35f514eb71655a53561da5535dbcd786f13d81129ee297e7e13f9b57d818ccc2609b706f300efbd65e7b8d7cbdfaacb5f4ff0e25e64962ffc12e8ad326bbbabd8ad2440de7bc007b9d6846731ce81c74576f093ad6757b98b7fbcd0928270b6b46c9f06b073f8102c52853f4b665536e789bafe9f0b254e0cdc4b431eec6fe74e8f8766c635a775b0e55eeeccff9b77274e1c29070b43a2d1bf5cb1ca596b77772d0aa432bcbc6388e1bf43bbca732cd87b9d9d552e1a63c1f744b5da9a8002a705dec7b70e33d4dcdd8f45bbda411cc5dac33cb47d351f99830b3fbe39c01237652f69e9d0fb08f51a8a01128a0f4a0e4198d644147f4a409e0abb3a0db11550503d6a1a5ce7d034e89bcbbe8139f5ba4f32a7562bcfb4f3e7c9905c91f20fcf09bad8e7e0818e220c03ea3bc7d42b3ddec5caa3c8195aa034e466f2bd6b21e89c37949ad37e4c53f5c8e8060839dbb832a15ad2451bd88c9e3d5d669f0c9db6af0c5d27c002cba277174f82c914f9cadc28c33b03d47b04992f64c8a10b0e5c60c124eb171b00d2c4732c7519fdd920fac3e8d8c2b6f4000a9aed34add05a177be3f8c3e6944187178f29544741693a8ce49f3521e41b21d8e983d89046f60e0824651f4748e2649d701c1d7e736aa3b1a758bf6dd52e39c736c568895c92b18a602b2bd16c595de76a732372757d1be5904b8bf0b076067f30b3c1b7ce7d5dbaf272bde986229f34f57be5e1b441cf445b629adf841150e3c76418e2119321c0467506763dbe65d4f4f769f47593552050e74541aff423c1186959532ed21684eab88b69ba02a4bcc0e8f71df1dd1220675dfcb1758953b576e0deef09448359776d1a438745480f541290a86cc6456648ef86def693ae25c5ef9f28fcd1585b4d15530d86ba46b21f6e2db6f1f9241bad1ca091776364b80cd3c3d136e53c3d125112b7d0a227819ec31771f2f46ff874703d48329fbcbbf59142846ca62a808df258f56f08f3d449d4d4aedb12519f13ecf86ed2a6b2c3b8b9eb2223e0922d0d9933b53c2c4966eec2a3f3d3ce556fb95adcadb8b6d4e6e625bc33ffc195fb3f6be70bb3f7987cf2cea58d388ae4bc86eac3ce6321f06b47589364a996e240379e5e39481ff65aae11af122f17e7ac0dbe92c3156d1d52161ae418e7f2a64acf04a36449bb7b7003b3bd9ffbec2274cc14f5b466cf4833e4b19f43710601a118ce014c69edcad134a065077ae871340c61d6a7d47cd2f6f5aefc9a2c0d0b98a1bb5b257b11915fb0fdd9396279269c11867a848e97a6e9de433ed42110bd2974fbce69d7a55581109f2a1f9f4abcd300a53d59a726c8c7bab233ca6cae15bd4e268745437dd7cc8f1c2d1c868c50f20da96f1c5e47aa5991093fc29e409b57944f9fd391905f156e708a754305bc733ab84c6dcdd1e66d602b3107c07a40e4959ccf0407fc8cb919a63d3084f4c1e2a1e5cb733635f665ce434f20da115039d6c300add5cca807edb4b41dba7b877badde1c9b9c32d571e8dd8f875e7a14cfdf59a6d6ac3868d78cb63a9ffec218b23f3a87afec8d2821dedef07c93b0338a8c8d474741b7785baf0b496419b71e012f78fd822de46bbaa05d8efed91fc8fed3f90195413ec38f5121657486f57a86d636152d4720ba5a73f01adeea57f121c30d408529982182daa57b3a514c027c17f25af6dcd3cd6f0185370d72526f689b6f80f809d5da38a0de6471f6136753db13d63a4f9c7d1cb4aa9c99b3862c5c025cd12f3b409c60978f9f48d4c3079bdcfca93dd2568c3c381f8bf0c19d7aeb4dd943cc1c636ff0afbd3e6476c3f8ef026ef2f8a13f7a7fa54892bcfba608702c13a1bf4a0d9013f7a27e86d9adac116b4a59961d9935d9ebbad15d548e18f453dff263e4e27e3669d8d27e6fa980f5852161874f74d9ba83959a56112fc5f3df51cce1a90c0b211e10b5d65c860e130959c066089eccef095cf6167d39b4dfbf90b7bdc5bbfec34c0ada482cfdde98cd8d885114cdfdf1fac9488c781beaf892bbc8b1f50c9e66fb6c85b78c17a9aad0948d4ec112e1e72d920db1cc08640e10c9c9d7c8e1a276906a511cae32395f938fdc37038a2b9ba94e883f2f65a561fc24e080aacfb93594334cabcb3c5b2701ff148dcc017807f5822a5902eb68a53e19fe7095f5c79146e2d2d59d416a0e69a6e29c329e3da3e893b8c851352206d2c97c92388b93cd76e761ae9af4f83628544221c439e386baa3d4636c73064f501bf7733ab507265dc3038f40c2585c1e7de58d865bc35ce3c2ca7d79bcc3b7ed21a4204fef324bdd53061c47f908c527db3281ccefb06f1aaca3d0e0680096e05ae573d98cb0677e0bfb5ddb97fd09c66bd4a27b3cc2acd51f070480f228b79e8b9bb33fa55d0103f7543301dee3f0e9da248a7c544bdaebc253f435d7db3a2a468724e063714f7094cf9395d0bbb38163e7449b6f4110f2447f4f3650bb652327150a2f744c4a26cc48fb3f5512045bdc93d47482dd0a8bd0dce7a1a6d301ddbc98165881e068e059bb0402e80dc8ffbb264f36d700018011602214ef9099ee221c5cb15614a23904e33e58c2e71af7e7d8ba5589de18b8dd3318e244ed3e5c3ddf9384c341fbb0494f0b6f1dc91d39928ca0188bf8f570d4f3302bfa3c12d0e0738717689505cb4f116630be4f3df61b0fbfdd0ffb23f3b089a246c5c54a5317fea31e76edcc1ad7d4acc6046f2577b37d8ca36c78057d5ae76005f85317d41810d2fc2472ed8b0be076a17b2c8bbbb85e840cb108407fe464cf376d3a6ff154f6d09bb487026f4c64e896fa80a33685caae3c173b8acc32df56ac0dde4a92828fb2a297e1aad6d060e89f7a919b0e6e35843f6b334f760b0547649e06a1e7d66ea0cc25b429cd74d7789aa1f7dabdd2fe73c76e5883af0a43ce030d35711d89501a67f330c547fac623cbdf874df7396aa2832595ccb0e828639cbf006b8cedbe6d72dea9a83ee7499b41b73936b03040e117e601daadcb23909c0f9c594d8ad848ee7e9e12e0d6a0472584f645207ac025778e212d31e7ea41ef20e53526de45cdffe5344f494272811dea996e1fa15d711587a95d4c01f268527d51565b62985526682188db3064aac5fb520001ffbfcaf43123bdd1eb3fa1877ea3a4b57e0015e61d940dbc14427ad6947d331e2cc84cccd839b272eba7c8f2e5849afd1395190c634fd5ce501e52af7d2762f8eda94841ec9e644de3c17245438f212f46de4878d6a47c4b1707a140253a99de3ae24e7142fa825b4542ee9bbe07944055cd39800fbd73f98c39611fccc2c89a9975ca328858d57ea4da7dfac70f90bbe85852927ea01c991696012854d5817db5949a721e40603bfa642089c3a27ac885edb592b29d04cce8d4531bc108ed86036f166005896be32bf30721d1708010d637e86c6e18401d4515bf3d72009118fbd3a8d6f1b34875f87d39885b43317b46906ecdc5d82cd376ff18e79d23bf8c0480528d559b1ff825acad3fa56338be42ea7ef957ef5d50979aa42180976c018a85ebd14cb98c25f129a147518580ab1f67ce95edbd3a487933a539024526d54b35080dc851b0ec2da21110bbb66db5b9695b48baad9225f0c6fb40623cd8a4fc8bb2fe0d4ae854ebda765c1009bb0ced6ad4a4af517467144081a18cb01c1b24dda0c95be9e074182ff10517732e2bdc9be1552e2a509f02644d1e7033047aabb27fe79711c4ed05c01809cd931bf223c0d5e75c32e31464cdb312d00a40777f1ce170302e507b278eceb9f710f45b450a450dbcc6e283d552fea1c771aff5e06c532600a56e5c033990e752e48e4baf863b796591116bbed0def1e681c4e922bf3890fbce60df408df46626415d5bb3df03019c52b434726e31ade970cad7df969b360c21a987575a15b5e651ad366072a81b8be6b308c086a8d3c6d9e9bb707c8b201fa1bc5f117c2c0f8f82bb872d93c6d30eb7c1d6bcc209a5dda1de6f25323aac4baa2dbe13162319811d1705e3d7729f3e2f3e50ac6e1e44ef00ebdc9c953afc331a51ceb62027e89f82bc31c3fab9a0d6e9606463181e741844a940ddd0eeff499c43b83a5554b978349f790780c1651b33894ac840b96109afb7448a9612e6a16d6e5d521c8df41a063621ccedf6ca4a64485f858db658f074bee25a4d6f3e29a8434e5d8382da74427b05307b4ed882090466bb06cd5c2ff92167159b21beb201bea790465a267eec2c9cfa426629c7997ae8c0aff2b80064a519c7afa7d92c43ecf5197ee145a887571600db7691c5f56189232bc2269480802a6b3b1a48ed9aa17a3b8a9457738639e5c9e8dc34f4380f253803b9beb9a121a00c7dcaede2e876b22bca907107bc36ee30b867cf2517ab9e109bffaac8bee2bb2843032b01a0a6d3b5123f7c19cdf742fb7d3136a7e975771b522bd8a2ebf8b11a68eb023530b6fd5873e3a348be2b2f896ad0e370539579cf1578bac83ce79c50b59fa56364b3c7eaccbfd3010467254f526d8155a86057d6a13f0400f7b8ce5428b0affccd01107c514f9d0588423cc6c8d22aa64f04cf27a27bdcfa3ed2fe1eace28ce3216df3f4e650434553db643170df77e5392cd48ce4fdc94a21eb3b321358bf685bed0cd7b81d74a8da70ff699617767d7979ec5e36865682c73a5b683b7d0e827898033ef077df037a8bf4e4e824bfe90e4a147bd7ec4bc6926dd63e498fa46745a90e769b25dd7f66a466055046d127c08ec1566fcea300cff48e704ee09a466303ae4f2af9047b51cbda7516f6c0ac23bc5e289caaa3d649e8a4670632bcff035603b8ae04fc8741b62ee8361fec684e19c2fa8a240b283fe197358432648da5d3373ccc55f8c6e73b9177ac1c04470c65aad5ff96f2dc8335f1f8f9aeb9299b4e4a4ce64f276625947f3d18c24159b35b9955a5ed7e1ec62472bf1cb6854a46ab03cd57399162c5ec92c6da4bcd7e695432a7134f9b682dd8c78988a7019506121333fa12b6609100f476cd58d30a9c5f4f0eff726d2338c30aa08cfd3554deff7d3ecef41f470629101fa0ae97f54ecd8790cb9a1c125ab877da0c89c7b6082f1066ab46c464b1de83da9bd1c8a29fd582119d7b7d400a67165a607ac985b96471816518c44df2467f7961614316c38a54ea8e170f88d1ee2714ff3bf0247d6a03668e5669358f0f337edbfdabf52c06c624df51be78aff0dc46c06fe90bff90f32c1d7a569835c2ac33ed9ed3d7f382fe9087589f270f575c3ff0a2ee527dad7c21e08274d40ed3b77bba23909abf131929669321984cf33702e76b8375f68ad92314508aa7a595aed7dba1eb8a24fc8363ecd8b1286d422d19f7cd38c7938e5a693e421667163847cd32700c8f041c7997b247d45b35548791484a6bd7086ce7b6eeeb7a2e868a0700afefbd2f03fe2688f77505190ebf17a71f12a0969a82f3050ecfb6c9b20e116cdb65d199953d3d597aa66e76cab7ad4cb2873f5754d71bdb02ebbc44e97d2cc41829c5ad3d66dcc992671122fff43c905152448a7434b68e3e8d0c2a32a3f0e1a25dae6cbebf4f17e2318f788ddcc901be8c6cc31dc2c61ae7f1c22b79b2eb5fd7a191b7a63e5ae42fc328dac11e065ac87424ab73e0ae6cde0436bb82e8f6428ae809c2f97411f2595e3ee8dfe6605383c354c6d2e790f97701eab8cfd45faa4136a7ae37abdb04e4ac467fce8dad7284458de002e4fb324fabb8b114d9e6eb01b9b0a2dd07c622bd4035a8cce0ffec55a2a581d2917faacd81b15de5fb0e023738c26e539bfcc648884543fbf12bc81690c45cb0ca6255493297a1d922952ee5bbdd5b5087771e2bd87a13bbb8588efe1f586d97a10e1ac48ef5b4fe451a34c937e70b516dc4ddc43d537c4a653f8747eacc75f64f84dc7f9a64e6875234643119e182dbbf30468d9a24e37324084af9f5763b95039649e4081d3d43c90c95a2c36a0eb9669ca8b06c4720a7345be1140fb03ac95cea190ac984975dec607138d091c1f92bbce8b2d226c69e61b043c5a0a17eec7615922bef7dc068abf9dba04067dd9ad839d9505694adceceae320ef3cc92f6726cca5d250502f1c0ffd901adddb4eab0a965a253d4c99c695a0a5def568d49cfd1a3685bb80bb835577530a56894d0f30db48bc4beca12657833435783c6b4d0484f2b26f8c69bc9955830b8facfde87ca3771a03652500227f57d9333bf4fece04a1659c3e832c35e24b07059f20d7928e5e95013b67870c8f7ea94671c69e3c0cdda93c6fd28fa964890d130113c4f771e115ae0263d8a7dd3cd59ab82f9965a36c7480d74312d7fbcdc3a1772a8a72f0c1254cc9ba00d7bd0ded67e2e2e3c41fe5b9248992ce3f52d69a86f05218182d3f90184cab7f313ba984114db776136cf369e090501044c9daf8c131a9f79413e41c0b4a32e1251d94638ba54389035cc4ac2c4ea4363fc212bd797038139576c395525f7c1a9b878680eed45c687c8aaaea3b5c89f94e972f79198c4eca065c00aa2e6e51f5639317853d5d67fffce91edb6265a1efd9649c3e11ca76ecfc37f117cc43922d129a29d10e988eea9de6e40fc5c922d6524170ac23c5ccb687b5f0c0f9012028bc6dc0d6585c73d0be3906fd2eca7797b60c3ad3f96b2a2affe680e4a9ac8c25457062c11f3fa9e448fc1e69bea68c62951b922296f238077165aa99f50f424292069356eba6aa2b5f725c6130e8d3d040a4e9d51f7f535441cdbd195c8e8379c602291c50c86d4bc4c1f6b6b88696c534f9e9005da6da789c7528f5dd0f2984aa05d8a0d73bd6abe224d080c41e8d6fa150c2eb3322fad9a60fc38a928dd2d91c2fd7d38232628acae57ef00af9644a3e5f2ad37ecd5d0e8227f9afde36da36dcff574d3a662d464c76e4ce08d6f149fa21c4ce38463ed6f5d2510cada4f13bb847e0ece7de41c46a0e419c01275fa6c3bffa4559a3f8ae5a9a51a1d929c467c2c2f98bd1accbcce4899f3cb893471c299c95ef35b4d84b7b9c4d995880824df49977c711ec01fe7eec9cce08987ab829b428779bc54939ab9cfc931218dc8d8479cba05e4aae9feb0a2d43be5979a45f2dd46c0b5a841be90c74c7108802294f1d104489cfbe06856d4af3488eb80e33b48a6c096189abb6328fd0de0933bc3bc0a1c627fc15113081382790b2f1c59db9169195b4e184907b08016390c011bc4be5f45ae496dab5210e30a5b53667f496d9b9f595f803cd37bab23cb20a9c76c8173f67a1ef12fbe7b479959eeece5c7378dc4d5eaf3db731bb135e9783b1982f5a7c676a71f23064805eb09ed1ab79ad9e8fafa9ba5fc2e8b4ac1c25044b4f5b639ebb54a2152237c0cb5b156748f6d358869214ef9da89d2980776018c318527fd0566c1f205712fd0bbe1d75214bf867528079c5b603dac8f6aa00d2b4ed5bd9194b9d213cd4141c8427b7cb4fa55a48dacba6d580cd6622a5d0af88a1837efbfcdbc62e4d0385b6576dac1b6a0566262d0691dd2af6c71cf91cc60057a27fd2b9d420495dd55180b45c6d837aac4a4b4a33e230dbeac78e746795e187ba3384772bb979e63bd98ba0b9e854446ff6b8a60cf78a2650af325dfc19aac5d96a6f8df0fc475027c5baab491cbc6952172c4ac426e3bd4a46e7055d7c6d08ec22ea7a008ee2ebb05178188100d9f7754990745bb40a1deadd69595f6eb210087e423fdf104b2e486df6737d805fbf08a2b9862ef8753de4cdcf71b14e37464edf88e61f25d911d69d3c2c92065f9a3f641904ceb6ee2821484cbad6178f3a8678271fe6cad4afc18106c4cf1c609d116091131a2fd83076511e91faab225cae47e35af0f62b4c9f5a42e09da06cbe7daecff10da2d01e812ea8cdee8726213b3d2b67e2d956d7b37cc8a027047a2f18849379777987e02b18964b85565896c8c30da08d366feaadd48ffef809e492f090c5c3a0e44c030b91736ec19c4b5a217eab6854e709c84589dd995d770a359897ae1c47fe8f96df739cf7c275f16c9d643d7b0e1a5983f034fe1c550680b3a8598610cd89584b6882e21d28a389c7741741d66792d34d4b3b5bc7ec50b1bb762784b28f94ee7341cecf42c9d6f33e9813038598088a956f14d5ed10e43ef7e327a2968a9b629103952a45300117a261e1cffc2734382a0bcceef5092e767b13a1c935ddb236f191fe52547fc867d04b258f87a27a8eb2bfe3cfa91d19b208bcc5300364472f95b9c835b21ed891f3aeb827c55dd7829e64e0bee43ac06106a824e861e64943ba7b3c3c4636921811e5ce6ff44f74a8ed4844bb48a882e7abe0dc3ab7efd833c0496cd17178a91031030d778766df72af88ca75ab851d099c8deac514d87c3bbf2329a8bfb8e70e9b3be7eeedcc3b9c99737f344243beddad2ea823f69c577db5e7c74bfe922ed431870ad63a5b7739096d1b89d3c6e8e7f8c14cac4221f785162992e74ffead22b75498c2e406a2bf5b94461cd252f0285034622fbe7b80c44fe51e66015ebb263eb5e854990315095f9f6ccb2a980e43bf8a63b6e33a3c3c6654bf7abb9113bfb7f684c3e0f9066bd87c936c22d64e45e88e3f93d955d0193af801e7a5d156e3cf61e8ecfffd1a004553267e96d0cda0d4369a0e886f335979a755db742cb7dc01c0cee55adc85b9a773a915364889adf5ca82ba23984b34ddcb278218b90b1ceeae397c1297305ce932ad9ceaba2ad0401ed72958b64eb1053dd6eb04d836e10b82487940fd75194b491b6f16c74a570966cda157354347a39ee92d5b9a79995276389674903fc709c8f865115e5fe9516978d53eff389632e4bf49aaec5097423db3882381543da126a35acaed80f2d9dd51f36d5974e29734f29b34181c0166bd0e43c95d45cbebb743ddacf9d28be38e9a7ec2830bb613cd9e34d56569293aa183972a6bcd3f021261df9151103c869f90ca875caba1336783901cb2560159b0016c6552b934da25897afe619efd2656982933808106b07018174e3f3c1ef361db773e4c6694679831e29ca3dbb301834ce6d4d1f42a4af463ce245a0af0227d7c1c95ac60611ae0894963484e7b4b57c9d5680ff30d1424904922b6ddfd0fd4ea8fd9abba60b208ba44a3b1288be51600162f66396dc8938fe58053dfd130972d174d0eae726d252cb38a41b9424f99547363d00d548f1cc8a60033384ccdcdf74baddac3bef59f188c7b3a29a296319692d16d62a7f873faf974a903886505154c41e1bf8a2d0b9f21efab68ead8c1af60e8a3fa642cf25757cf1cf2ee1a32db8071788ee97714dc55b42adcd0f301fb3303e7dd8a2c99ecb480d004fe006cbd91059c124d55865760d0aa7226b1dfaf37f1b275b6455a17adb433ae2b22aecc9d6b403816dc58e1fb0563616b8f5acab03c3202c93ad2c20ffec73ee24cdd802ff3921ca8ebf47e457ac471576416e3b6791a11c62f659f61711bce0941502d32ff0d741211fd1ca45e472417a30b848f341332fba21cc02edbe467ce92ebb142c0182290fe5aebb030eee4ac3cf1c759148df6afa8a147bd79be701ca45c9339b7460c06cd3ea136f97403c12e2801512e90a020252e1518aa73b708c9190294a181d40d805854e1e9eb83735daf62cf915788c5313fd849094a02d4532d3a36c800a9ee504bd1ba4c36bc651386ac3109e3eb85e7cf4ce58c2951ade425647db4115c88cceab18b2dfff6e4a11c3cc358fbca4db9624fb3855e0037e736764da3cc183b1092f880d22030300b6d35afb6619393df9f8be098e1adbdb5480963234771918ac70ee52925570404e5306324044b28a713560a37ec7c0ecb32628ecdc809b3c2cf279e63aee920c8b73a60ed72161239d8dc6526bd9aeb4e1b0342e9d9df84d4bfbba94f68d4a326147c926005522cb1b34c2210730d58490de128d9b6057d27309a58ff01f308f47b927e81f10564d2d41b2cb02a8105f0d59dbae86e33811aa3dc5994ed06d838e9d94ad86224b7e810be2948c4f75eda32ca72d319217135d0f965aa8cca688a48c28ff5cd83c2a93507be2b7cf9f49f8940fba268e6b102a6050ec1e40e923c7e07a8e86d0ca54864af59af82e41d0469b84412517ef30f865f82cede49b95c2556dbf10f707e80cc42946aefe1a1826bc0fea3803e80ea318eb9d4ba567812963a11c3acf437b6aa4402e339136503059f9da027e26dd3268c8a941e130b7a009f36274a0747089cc7b3837748346a563c49e88ae4b0b363916d2b457e8f66e9e201b27cf8f2614c2ee88989a9c5c0cf264f7a6f663ee23d10af694ef40688b4526ddeb01674569a6830c60574a826e18b318accf052226b55bccaaf97f4713454efe5cf335e96e348cf05016c1cdf151f2ede23a2b9373c252ea1d94dad157f27e3a960a95144f11ee985bdc38e81e863ef15655b462d79c41e8d6406e4c31ced791e225c2f6b828bd61cfb4108e7a2c3838846284ed6aec879221c8c1de2f01c7334e229321c9119c54f90f0eaedcc6aabcaacb71d99e42f583b325f214f4f335a56150528a4cdb63e3f86494ae8c0ecbe3823ab4ea6e61a849e2fc22ce7abdc727d7423e56ea37982e82d03fa482993afd7d4e84fea21572654f4a04bdf44b83685486fe4438192527c49575243c8e4a271724868fc6e78a907be3ced294299323a8e85b00e5d9b3cc34fb60131cde12913b76814d278bc790cfd2d7af9d129ce9c71d285556781b01dc869afc7662fece00fd1aaf6887e5401546acc6c2e7cdd1eeca5543187c80475b950541f4566b9203d3e70e8f65cebc6de05b9fad8c679e355e65b24c978b1e415aaa50e74017056bde602ae463405ef017c98927bc0ea9963926bec0ebeb5740056275f820e9f039ea3e0ab8d93c510f20c919d5795ee7e040a3da355f179e277f01d8206f78f0ba2b01ff61114b6a8eaa4bfb78288479de3d60ce1acfaf06d9ca30ff99a02da5919175d42c4b00e1277a7da043aafdf231076bf88f56e0f8523c61370fc752848c514822dbe46397847e3bc889d5ca0725ec6336070bd396d2558ee88c48fb85deab6c31767ef76b5b2b96c6dc17bc7f7e87fd2916144957da36e1c7b79c142af2bd07b53ebb3931e66ed4372911c6dd10e87b8a3514bcb04920df5d69db9a262f4f3be6ef06c010902ddc24e922e6f16e3175fe7c2c11bb32b38fe8f8e2a66e6a311e593b1a1013a6af94757fc2daf6e2534c3eb4114685107e11f7510f5986a5095a605e908cb319733f2323585ae3743fda7583b692379a275b9223ad0b8a2246d72e31c337d621256761ea68283a198d2fde9d58746bda697b3d7ce8c09dbf11ca83f77f2ba66f7a3eb1740d28777dad1330a279c805b4df5187b953e1180856930a760e5b026731272950c1f3628250f459fc295a69a664dfdbb699865218c0fc817dd2373debe6730f539d4e3b045ea907774b71e15d062129aca8d6011b12ffbf4ca3ecfed836037cf7bd0b55a9cb1bea5421d497be5a7222d992cbc4372b0b701b9c9750ac4f2ca4d5517722340a74337117afda1241ba760ea0c97f547fc07a29b235fc62d11e2727aede2fdd7b5121c4a2d43f13bfcae8ad2e3ca718e6c89c9807eba3013d1f94444eed820a52f530b75a71871dea862413a874e153c48fc1e99c0ff2aa1d463acfc9c2f19d70fd60d0f964f732cdf16a2917bec27d67a195bff9039f825834f4663f87427059bba2a81f656cfa692549e30330af31896b31d752e387958dc5f0c6befc665c94f69211eb33e5e878de96d5950f30981212e84cccf0df376bb9e5959ab332ecd8a549f019b51dc946a6c692db92c1ed4f6f59dfa0a95a1c3eee808258c9a370b940d1509b8fb490c8717f3abf3e2327c10bec8b33da0bed31458c8a8919db0be2b7628380272388a3e4961d52dd6daeeceb50e5ab49f79d401129fcd51735ace8ef3712a30f3a19a602ee00c53bf558488e0dfc84ca3d67437c516ca60dd5f64fc957cf4120efa6320c09e669cdcde41ada747e33e5cb00eb3bb602cdee9cdd735d64b61f1a16ffd49533e80093aa169e64b9733213906d70dc97cb0f435fc78cf5dd71413125e9f05c85312ac2ff33361b65675e3c5f5da1112dccbdc682880a43b2eb1ea5d8730ac208dc74f1745b1fe72944a88554617c123ddd8e25aa123f8e65e0bc16f53fc330edc1dd5a01f7793fc5601873bcb6a92b7e01c832ac4fafc3255b36a02a664679b90eddcddb9918586fb70b17314324dbe23ad371e754d8d935c3bbad892da3101e5a62c51ca5bd493861ad6da8f13956f4bbf424cc0ed0b1b3e7581d58a358e8281c4f1e28502fe5c1af35ce6525fbed66002e77dd342f30ea7b2343b75b117f9d6d4c0cbc110b7cceafa3cb90edc12105b8e023605938feb2813bdcb7c736c8bc3127ebc0de5ebe2bdf30f779736a5a4742bf411c9a1db1e0941969b86ee9b7d2491ab3692721052cfb16644bd81214e6b465d60253867594717509a5eb4a5bbcb8a2898ee3258dd7ba33d52121bf43e59a0d35d22936196cc87b80d1377e8c4d02b2e25b008b75a16ad1cc1bdd24d06cdeae2b3efc76b99e521bbf35398b2e21293ced4e8e969246fd20eaf330b0a8ce2988ef38ef80ef4bb6b7e6adc66754e1b285500999db0b0b64d1897ab707c9fb89900204e8c7e226059cd4b99cbbbc020770c658a2aeb3a44d22bc8b9ca37fe146602969c920f0b78e868b2c94dda81a1cf51c2dfae09cb67f4f59aceeaaa0959ac4be298a1c30453414effdf7c4895fab5caa1fc3b50481bba2156e3c4642dcc3a1056f778a20c998cd591ab57122b479ae389be45773e751278921b0c7a03847c0caedb9cba9a9b6297b45d510a3eec3699c6408e01bc262b26064006117e49c0f58def19f1e188c7873c53794e8d7cba6777961623dcab408f867913a8922cb26eaf57164a331146c3ce0bac416147a7513eed2eae34a4b1b89cace63d80fda90d5b3f284bd815f413cd25e28dee664af2d4a5cac71b7a7da49bfb04c253e8513faab650a1a58b1da293a9e897fd17a47bcbeb27db8c47ea902a183aae8369b54f03e9fdf256beccc0b6cba865ee98a0492254372aea82ab2221eea99315126cf4a7ccde232f082138acd0904eede0da47744098aef5087f58a5425d282da84aa3d90ed42545d11e04deb04418e1d4ceb933a34391fd670ef0c4ac8e192474b8a1874065f2b69ac49e8a4e51bbee100a3de1042dc68e1d6f7acffab1f6230c00ae7b255aab618880d1723361dd06598192d3dcfc46159b534847bc26a24688d30be649f786194a4bc8e425645a391ed6ea89d6e30469d984d62d85e29d2e8d5a4e3ef868f19db23eeb1c678dc1d7899d5d3669c7654c773348629d3817c3dade0e83b45aad581b092bbd62d1af6823738671a88446fec433fd51cf27c5dc89d2b063a4d399675e44856d76ee420c449b8587eb36364c02883a41176805bbc39c4e519e001f59a03f922df212a7550e2dd89a3b6d726679230f47d71b14f5b8b381d8cd36eddf3cda3418bf63985de4bbd896a6cfe27865a2002961353c74884d9c4be605e615759252879e3ba4583dbc00f643314fd3c07263373aa5c5429a1da3d3c5e0e779bd53dae9fa0de2002b1665a45d025035858ff6ea8218db969b6f560a833b42c36ec262397da4874919b4f4e395430aa28eedadf89659ed3e9761d0ee88010d7f626755a9d4e20cda396c6e1561663866b149415466514dd2a745824f6d8cab0b93869becc77cdeeb4ed8e51cf9d381edd83ac9ec7799f97f795acd7fc22a75ea6cc05ee46bc1cc6a39107be14725f65d172ebd6ab6963322117014e1edd882f5dce6f712c1e19dad33d791301148b90b91eaa0974c2a33ebe81ea38edbb259cefc228e8984fbfaadaef2368e7de99e35203fd36bf0ad0f75459d36cfe567b689d445bf4f152c75f1cf05b08b1c366b5b6e4e2297d88fd0f733dd030e2e0e31a30e821ca757d931528ffea8b389bff0f4a2f08d895422432099b71a61c42da13b5fcfb4fbceb3f940d907c835118b098ec265650db5f72965172672572da07254ed35983b01406deb4eb085231dcdfc90532b337e27f8ad4533e897177163fa341754b14925fd85370e10f009109eea04c1be8a4b23dda5f3e04121eb01dfd084285a569f8ea1222adee759bb0b02b09bffd8cec14436ea6400651d268f3ddc0ae7a8da36812900d8c9a16cc38c74faecc00ea4d04bcca9585099cbd663aee4f43bf37bdee31be7425517142b55a02b6fdca888e8b744d2c986bbdb426df3e33a42bcb3d73a056ed6e22cae8beb1b44153c0d4024038a57f97fbcd30c97ac01460f1e62f06e0803ad2a9e2ddd5646846697f252dc3ea73443b7a29ad0fd9c5814d30d9093827e8d9cc77563501f92631860157a197dc10d1e882067480a2eabd7c0eb79d5dad9cd8b2e281e2d55b929c8bcecbd16a78bcaacb0c2427de57469dbafea49ee20d4b529bc233ece2212efb76e5fc8b390cb487c46ef0204becc0bfd137b921a8619e9e2708dbd1b639f25b3da5b459356e8163ef550185d04c85f56a484977e39ed6ebd5de52a1dd2568acdbf2f9622915bb58cfe554c8500cc899b67dc916145acddebbf0dbb0b28d2eed199b9dea1ae89a7f2d1ec2a22b5f3facc8fb5eb81b6edfcb4c56e006ac743a31aacdfa9226005486188f45cffc5f9ba0d8fcb2716f51d130bc7d58109b7532d1f29afa0eb121d3f66fb713b901a59bcbc9ce7d0ccc520757613afa0ad043fdab712d09de8890edc0b43ca877b74400f08f0bdbf46521b84cfa52c644540a8a456484551e57422eda13c20e8847ab16da37bf52d49a71dcbe98c9bf6de9c085ac9b0e757a7c0510c4a956a6857b27237fdb7b68b96a26ca7ba9f8ae4197f0dabf41b6d4a3bcbd2178fd375ba250e216d703bef9b2225a7de448a87d6733bb2544c2b13ac4f64b0657ac69877c1e99ae01506aabfb90e830a02856e0145d94e55942110507e66faec8b8e77d5d7fcef048c87170f1d0267b889c7e085069214cffbf162c368c3651160b7e2298ace8cd6125f863a1bd28483136916e7cd36df4f22b1c952fef94317d6203acb6b055921b4cad80a04b4b39aab73de349f2e343ff0fe3ce6ea7d5d34bd5f87abcb45f15788cfbf5f957c38fd5e9d53d07cc48d0050f68c87c7ad62be9dccc4a7217370e32f1cb011d8dc7e7e0708fa1d80bc1b8bed3c4258e6d7629f1f707c16602ebf490da0bafd276b4d4d293fe3da73a247da58eb765c06bea946c7f3062835af06fab6e3cb853a1a724862bbc1b3e3e8385c413a71ea79d565e57cf85c43960fd7b5e80ab787fcf90b87372e5d62b44289b16e9ddc3981ead2b504b0eede61d888ad63b474273ae84b435557ffb7bd8e8291ef8b5674e719795c2c165f7d5e0c8e0f448a3eec77d2b4e1af0d281477fb4b7ea40faa1abda0fc9d4ff2033956ace05ff23dbfdcbd2839459fdabfa65b813ccadf80d04cd41f02fdec3ad058d2e0b3a303edcdf09c06f5510d4fe4ab615768ce84593fe0c9bb50efd45079bd9f0f148eb407ed208fdb96c5d7efa83928200cc8d0186515475af7cac5f2432dc9e42586eb34c29a61f8be6f4e957247e7d5d066f7c9019df3fd1386b300f54684f04f398431dbf532cb2cf7572c46161af3ff749880c047b207dc637ad2f8ecfdde8655adb922d0ff6362ad9598885d6bf9383a146b7b0659a12e1fc2789077015bd9e6b06dee2b64d4e3de5241ef990d23d45113602cdee3dcd247aa12e707c3ccf2377618317e54793546dac4938d56d40d57040b6e9b97d033860d010a1e6e26888fc6ddde77ab90ea14592b5fe7bce8bc00333a2427416d1981294b448185b19fd6c25355b5da16ebb4d02c3137b642c06f4ac2cbf473f2e0cc01448ab54477378e8955097f922ad30e019ac68dce94b292653ed98c78d9b4f1cf4b8431807c383f618555ca30e6782c84792354b584d188a269f0f993072b3e28a85e99a94335cc3a22540c85737be5a64e4e37f6ea09512027026a6a0aecb7b7c8a8a60df33ccc8dedec59e37d0b0beda473399431ce44179f1f2c1a3a04c4b7647f319499997b7bd3ffe3dd7dc7d9aa8241fc0a5b9dc48aa17ad7636e3647e2cb1b0e88831af7628b8f801bc8542bb4f1d7d55bd2efc6311ba22f2d4ed698272f6e480d510de988af7d41cc9cc2638926c4493eb97ad00909f68019b4ca84accfbfe3da88e6e497752ca73f884e788929d2aa8eeb84f564e14b82840ae9aa31634426fbe0272993523d87def0e84ce78bfddb93c10b0fa3c711999c7e4554489081840a2c62d9ac6303a7042d19c789de08ecc177d3e7cfcb72383a6dde326f3ea0c0c630689ee92ae330290d804497476f247a1d814958189e0e64be4a24d39731edd5526f4152bf824645b346193d13b93326096031a1d4b017b748a84c4fd9a69e1a112e2bfc0fb8096e95b4683b4ab170aeae100da16c1771685cef83aab16e146314d0336789a4360a5b48caafa334c03d7d8f7aa87c2997f05bbc89fdecc15ddc72a234cceef4907afa47d8778e7eceea02f5391b0406d0da1e1e01c4f3734e7e2fc9460aca5d74f80231d085617c5532da837739edc466179e83e5d0c002809de60445b0baee519f5c24a9b432fc2cc3e0b973937cdd212201d7a183bfc18f1bc67d6d5d17df556cbb0fb6861aeca3aee47008787690b49ea0dbe8002794f3b1094bb3ef49770ed30bccff559b203f8004d6c0c17b0463e6cec3993308dbba86b3a0d3f372f9d64a82ec43d80a64c9d4ca62cd45cfa993078a8ce4377045b230efe08dc56e4d0cbb368d1cc949830ea4588cc70a515177f971e0651e3c9122721c8b45c3523689cefd596ede9ef96a9fa2c2587eef3f5736183dd54c9af715ed9670a5ae52648d9ff6fe3f967048deaf9ae753cf833505bf75a801b1295196497af7f0fb8279bf24bf2c7dd7384ec07c046e92cfc36ea977fab76e0e31af4e94fd0857539119d803c972539a9ff31f50d612ab05f53ee3f906a11acb6e60d317deae612c093dcf25a3f02a422e2c290d19151a23cfde1e4b81f641e862c6ed4b15f3e43311d5cfe06f602938e2f46ad6b777bba3c779faea3d3e6e848775447f705776505459caa375ce1bfaa8cb18bc3fbde60ed201ef1a3bcfd5f3a43389f6dbe4d21201cd36565687b58fc34b32dae63937a74eecdd992280e64121740fff2af329c0c2e1f3b6fe0e1ca57f702693cd648a97156de10cada7503f357b61c968885901bd28eb86b081b77fa06db3a4889f60cc5a70c999fbf65b8b83b6601c0bbe3396d066f64486a73940a80c6866b18fee7f1df150404e844feb81d97e049232a5924fda17dddc80bfc8419e013709d000d6d7e883d274e183b5bb387ad8d3c35b95f1c92ab88981721b293c1378cecdb66b9d48748a406536f3079d5cb18e8f235cfac76a24e343f887dbe86c822807ace21248a18249b3fef24f18f512e8305f0c1a3bbc99fa8fc700b3b8fe9506ffc297c5ac1895bc16f223792a65ac2f967c33442043be3e45b90d3554c9d1c869ad9b1803999cb4c5e8909e2c95bd470bf44386e30563b3a717d8479da5d58978ca69ab6b2e1a3b571cbc742e56a7537a362903f0c84084cccf21520c3ec2c0d9c0753cfea016949590c1b9d5905636bb8de491165a4a6513a9a8d46901e8891ab0cb4768112ff3924533ad668da529db82c6e8b9b9edc9680ea0a0a4e0aaacb061f6032a65271ec0c8809c2a3f079cf28591676b7fc0cd356d288506694e4bf23af36b3f7af56235760aa0c472cf29c7fcee13d8625f863abbcc19f5d62a380bb1ca5c1e4bb60cff0851ec70ae7489fd2aba2a7bf35e75a51e22431506b1435935d9d2ec66a501905a55fed91f565a57ea493a67248a18a42a82a863c13c073be2648021cf6320ba5372acd7a2b1ad3c50c066071771f567b4941b2c5a083b898d16cf14a30cab65c44cfed3109c39fd631645c12c7b0b910e429ca208c6d0ad62db2f154fe5c305dca40ed10b86fa4a027f79260836d5ff40865ece59a47a1b271b3d31653a507f994c22b98776923c7f7b3cfcfd0c1bce62a8426c5a5947e7045e03377882b46fa1900003ea83623a1977e967a620cde641a44d8599946b9b7295684f2eaeb999dd86f768b197e7259e5fec96b0537c33f76918afee021f745c7e40fdf8c00bbf327c818aec16635e7bdc74c940a18ddf5eba21e2432f1b2053aca67f7270d3253dbd3154d36a695acf01cfcc4ccbf8e20e4480656fad058d3acef84893da18304a06843d0cf67407fa26e3e7b0497ea9d13dad8c39e00e97f2a6b08830309645b2e1514a37e3cb1456219b92283731d90cafcc76f38d0dc0bb8a20285a36634a398bee59f7ee0f24d0b7bb59d7e45c501d5d8d087ce062c9d53415023230141d2e50fbe5b15429bb671028dc5e44b04110e32b513bf6d21fe709e156861644d6b690b50cb3b9cc0c9cea2d952ae83bb8bfa5e25a564a925cd9370f982b81a95ea1faa664724ddf5b2a10b12baae6a9e607db94ef14d8dabf09bd25dda02d8f70f9695d5a9d425ba86e0c07302ac1a898a33482720f469a361d0d53f94b38126046de1ffa0468c5f75785fd5d2205fbe44fd03d676269bf45c60950466008b7b5509d2128b6326ab59822c6fcb14dc0eeed561fbd874105e3a15d99c19c60b6483f3e17bfc5f49c76df76f44312a6667f97336a274b94ba5413439641ad4a51d03ffff8b9d2e6e5f6a87eab334ac39ff2f93d5aec0438d738fd2494910dea42ac86d8108a1d9a4c17c09cb38e9a43b7f2d81a5550d6497d98f30085c34d6da797a27715e2bf1376ec88b75d77d4505cdf63bdc778769c3cabb1241f0d14e1d2ad03e47c9ac58a29680d5cf18bb1cb39e6e84f54d53c534a91e6710104e83c8e32aec7a36e1dc315fc1d6a211ec7bc05b102f00f1e09093e0f867279ac681d942a7328c8777f78da8087ff8c554ee7ab2cad3a9e4723c2a7980393c56763654e6cc2f16f3a4ee9316da8f89a3d3be684d5932bd2aca276c1e75bbcbf45495e0b2b804e2f401ce208e04ffb9e8c02977ea7c8891d39328a911fe7fe275488583409f3cf3dc7d61f3ce71c27ecb0c2391a6eada10b586ef62fdc2904a3104ca31c44e001242986753270bc272998a8b202240ba66d6191cf1f6728100e69f614e2368fff58a77d1a809814b7bb395d929b0c0e79d699c84d050c9d64645d4d17f8aba0ecc14b70e4f5138fa90a5c886ba6a480a09e27d1d4e5bf5a7017dabb55d3c866b1241ebb140acc2fa34bad6741e5f2d56547b50652f82a00353133ca212a226646bb101e19fe13034c8c9c49ea8e6f69382d1b189b2e4f44573101775c87d8162a83f469e2f89c775c9d453ecee8462d217340e52b8d91eb26506be5c4cc33427e9a171c1fce97a81a05354b624eba5ddf1dd82e63df02a47113a95406f255058648796bcf8545852ba3bd2b8c2223c14ab5577f3db246df63520c9c7a5965db840bac925f4a1cc0c4e26b36691c1151c23cc336bf6ef6befe89b6cc4588e3615016070ba1eb623ec36d86186c15bbab76745933d5fa518309273aa6f91ea085ccca37b30e3540d4a8d59551cb4994d168ab4b4ff39f45cf6f186c8f0037961629039ba116e4a0a670f116f63d935d9892e62024ca8c176d5516b19840a6f1427aa2199ed02ce5514a50546caf52c3a17e0815d733aa865ca639d504160151e2319b0c089fd90225256e9896db769a38fda1e70e5d03518515a0ea62d2e3dab44990326e97c932308b558cb346bfaf11a1fa4fbd1427e2a0b80c1bb55df25a64dfa14fb35f8bc619c05bc8d4bc26ae001d2c7a979d29a22cab54b4fa45601e0dffb2abd33db10e11b0563e45c1738cc476cf919b0e71237b208a4f5fbb470d2c4efc46e8d6d835c825f98f9fb8b4a52979be2e6d4a8222a63191ec2fdaf2bc82e1c40a7a2ed45409710fb1cff0e105369cc45ec8da3cc560a43929817d2318677601c1df1f357996d84cb2bf86e1e2aafffd8af0f6d6782004df50ac7816b622eba8aabeb4fe84c5c20ed224e714ccf16a3eb66a4751c86b0a47afb1d21a8da7b2ee99c9c164844f864615f544473ef6e447e00dc02074cae6f1fb85e917bc1550b1d6923a863159c4481b11dcde8b7e8b46049f8ba06585d2f1d3e359e23e423628d436b724a219ac6b7740cf68c8bcc68486bb6600a17ada64b40b77400747c72731b21b507e541d4a5ba1f3b568ccb83bf22c08b574f44e3c5c8b5511747484c6e9c78ac8333f40bc8b8d9cc5e6e6f7b6d321ff6dd8df1fe260512d2317f4c60f7e53dda03ea34e853d35d11fa790685c43579e5cfcf3d5fffec32944528304a57a7fbb5cb41f4acb45008c820e4810f8e42c8cde08546864ff70dc28525c0132bf0e5e90802d52d1c6f3650970a2dc8ab215d9b21b4dfcf365eefef1860d49311ab79a32c594523da38bc4bcf74ec3dc84c2b0ec64b99c844a8bf3e74905058fe3bce47662324f6554beb7fd972339579e9e486d41bda46e9f8795dde061c33f27629cedf19453e93c3f0d47103159ee3265c97009c2d352774b7d64b08b4222c75cd98c23c1f0375dbc8abadb85db45e90b44170d9b9013788e24da7f5679781dff25580dcc270f3f626396f4a9189ac0c44228518bf7ea5163ee94d64f19fd3c0079dcf1b4a9cf8937bf9bfa045ec58bb19e797223b2b0ebcd0de8a13db8baf85c7ac381a3f4601788091cc02d3efbdc368703bd4699e8321d9afc856cb726cfb0eb772bdce207c670ee33e042a47495e0ac5d50a756a4c4f08a129d3b5c8a3436434282f56cd709d538f5476400fbfaff7aa3dafb88a701adbb8035a1ea708a20fb91ce50bcd3eec491f525a208fc3ae228bea8101d6692acc59fae5a5b8107b280f5e478d9b3513a20917fa55cf98eded6f87ba318f9bf85a1c79b876e4853f137d558df1cd1c22b49d74c1b2f2f914ebee663b6fd81b99cff5f75fb1edfb3e4d13f22ed908eb05a452ef843d4ed1e3d5a21fdb03834180a7012d5562675d0969f6443c89a82fdb0a3f04790590a64f80e2f1d3346c0e61a2c2e5ed18405ca9b47fe97873a3b711a7f3df9f353200c360473efc44f0cedbc39e33e355c17f0a251db80f1d2b1c8e33bb6c82bf77fe08cdcb1319c61613d6207cad9ef33c5a86d1c49bc457b23a7cb99504de500aafc96c5ec763975cbbf71f22d70aa91ad9a380a7adb09af91c130431caca822e61b378327127ae608342aebf332ddf46dd7924e8120de5549302a88b28ba5c7018435ae2301fa26a337c2be264bcb4c8e1b52a13f6781d1af4a1a7479f72735051a9f4ef6bf4dcc15eef415f54ae51c7b90ac407e41246c2f397f2bbe145a5bbcc583ea7ede083133c58bb3aff714f9bf87f61a8e12cdda3fc332842152bb27989a861d2cf2521b4fde1cd99ab8ccad8d174d820af65d891ed20d7be929426954b34822375bd3fba301df18c20de448b941c905ce3f82455912aa9e1bf22d312c8042ef4b70a6e838a39563c7be9100a9f0d4e2d1b36743fe095958a8797a8fd7cc16c3c70e495cdbdf7148855606a6093f4cc2ff9eb01d794a4fa18be3b2e89c8abbc03b6155240df7da90e1c07241677b93cf99fd6555158d0a141e55eeb069f10e5d59df958e068c900b578b7c1548d006dfd7fd41338d552953fef4251d16315dbb263d94543238866a7d40914f4c3f1f6042ca873a12311bf085af687f3aed3e7d424dfc326c7de03eb4c0a1d6b940af7b6d16f98ff20c519f153566fd6c861bddc2e6f1623c4388a8a44540e5c8a1d967ebddc48b2043b05bd0e36febb3d11d5888788c309e0342a0819074f5334a173900643d981f88f94e398aba5baed4a04f40aad0f9868586101f029f877c0579dee1ae3144736adc8a6f234367927a0d204bebe635d6a1f9e9d52fe22186ad3b76e630bc25c20f3b77c9751c15f0c3814d89ab4bccf251aacb9139f593012adc33051001627850343cd59faf8f1fbf9e85493819ab00cac570ad0c2f96ebbc2c2f4425f8cfe557e5b79ff7d009e3f1ab33c85ff339df81000a069a6d8577400cff24e9a9e1bb5b16f766c35dd29b696170e6275009c5b5aa909854eb0b4710b6143a042e97739489e85a8b12e095099f8de4b8d4d313bd9446c9de7395c061667ee38db84893c42bd069ff0daf9fa60d0a3354470f134690de3c4ed6716560ccb1a54bfc5dbc22704382a71cc780931f003b3ff0f831ab90cae3df53aab74bcdc34a81e096cc77b999f26a89c9bcfae34bec3bcf82f106b3800816d71a505dddf4fcd292ce1f1930cc4628782126d1d978f021e8938c272eac58bc14bec433599b08f31b822c1e861c9164f55a542eb3f72dec663804d8b72d319d996d1dbc60d5adf1fa17c3b94d66e5c7e785d333dc50b67f140d80b03f09e3b2ae5510d886d921095d452303c10dde3ced1acf1caf4ffd1abdbf73988c9ef02f763668be456bf264c4baa31f71b3e90af68ef48f011d0c837b8a76a8d530b7fe7e8d976bd0e792c0f4c8aa4ddb9416f784ebbcdac29032d7abe53f518dc3d91ee5f7902b34279e024ba217d2bc50a221b80d688e9ba46f687baa3af81014982febf10c8d74125c1fe9f08e69ba092c0ffba7ff15b8632747b2be242adb5564922241399773c582e4d0a4b6b6e45ac9e1a6eef35ec242e6eeb61c21cdb5afd57db25b4fedc284cc4b7cbdba672edaec3385233c5562531f1d0603b9ee6f3cc41c63258cf82b66530852b091d7c0db9a87cc9aba6566216a25d86cffda409959403c3847a46d8a362edd027791e638dcb029cd7a8c215fe37947096aef7c4ce2027d6b38c18a46d1d35bb6ba7bc5e44139ea3ebacf8fe165751acd56a2e9eefa6cf91801175009326a425ef5f1926a74f5f21d0aec18da731af88468688b44c53940ef4002489942b3f749188202aa001134d29d036c12a73c4b55f1e510a11338231006af54c30e7e8b25bc6a79a10f440fd33653bbc088731341793cbf5e37613898ebef9954720a64bffaf80d4345459973fa17465ffdac8242735076e4d32f54df4bd8ff5a5185b2bfe816005d0128e7d56df331472a28b57b7d7a4ea34e8c7bcd1f8c6c0bc10e8cf55daf18f6057bf72a526f8b0b7ea44a379ab1f73d84c0d77c89f1f204bc522bc8d270e33564989d10b328548ac300134417592b43f7316dabf67095dc58cd04ee0b130c5a77fc883e9e61f589607041dba73b95f12258aaef57f2214f4f24b10cbc7b4014685039fdf0e9981f0c55a9be17fe0f38600b6dc10c43f1aabbcef496371d26c4cfee742f691c64f623d52b6ccff4c1624269ce90217ced070910fd622b99f6dc10c8f3a97bf48f7ba644fb75fd776fe35a6b443b92f38c54092333e97024574d2117b328db0081ea86edf7728665e7a99eafb8ac3b9207e6a5f3eeffbc0324e11e7dabec72c562f5591063759933adf605ec38e0423a29a0ecb3385fd16254784698c592f97dbb8c4149204a7ac2fe01cb2dcd4a9861ce26e08abc687b3198131340a5e5ced197b3fdf00435be8340761348755d163e3681ef677ce3a9294079a5741243b97d36589fc68ab0f8aa9d003d48f5f2004c3978bac354d0cd0d54128c9d5c058f27bc42b061f5f524876bce06560b1278b2637ec19074f7862fbcb7d1e36ba69a64c72468581a25828d147e1d3b462181a8e4f1bbed503a25776f75ffe3be1956d71d33e8fb6c4f35874fbf1fca64217e291b175feade3d73c7bd514b07ca01d7d6142247ac50e0ac2a5967eae5106fc41bd928b5ea5ce204cdf35048966796229aaa27c14f24ce734c240ebb57fb52abb78d7ab41a22078c00004ca3f08b780cce231cf247edd0d5697e4146207d435c618531c709f507558370e1601c551fec6db80babfecf4c05c4e2c80cf69dab7b2fbc74a3f6ae0eb04eaff9f1f91de33e597c0be454b3684b3ef7dbdac4d2eda8b27f024fc768bbf398f7093a39ff2db919ef43bff0bf53f8d8e4c421e702f3a00a37869b3fb3f4dff397c7900a47edcafad53432f3415176eb7a0befc1cec5bea220ad87ee866e0390a1b64cb6cdd63829c88bf5eed58ff130332b37aadc9158ebf166e5cd80c2c3e2ab79060a7fa2db7eb10b3defafafaab70047014952a48ef508fd47ef779ee0a5f915644bc8ac667f640cd276e5e7a6598b662c86d2d620b78f0d5e28b0992c930a443121c7a2ce086308d2c2191a0cafd2148b11e47bfac034bc0551e1c5bff1611cb7e2b8fd6d91f8c17012e3af4065a5f99b7411ef10557cbfb0a2d1878820ed1c41b1f15498f760444ca51fb07477033e543f33893501f50c2c7ed36cd0f510b9732fa40748a9cef09671db96ee70f7af720db35501581518d98e967aed2e03c3b207928d1e78f09e794f45b3ce62dcc8a1386226f39d7da845f4cfc9114825c5f8d84c056f55d21a1db5f13fe1395dac2c3e85bc9e2f1e3c284a2ac61879705d8d65b5cddf712bff13f4720bc02c08539571ae3872615c2f5915fe241238e1022a3f8ee03c9b120f11649915edc36a371fe872e01e89c31ec3bd8325527153ddf5352a9a774dc22dfb5591fbcb83aa4386e1eff844e915c5d7419aec98575142ebc280fe3d1526a1a7cd25d8571727d17872e888e823d79c21504ad0ee41da990d50a49f1d46df83e8af07d064b6a133242d0f77e02b849a60f437accea3667c6d85d53cf402779045487ae2ba585d11be5bf77cab34dd42294af6f2eb15a459601e650bbb6e830fde95d6b322c53c0c6d56062b34454ff93bca5a8998696d94a85cd6c779eb9d16d028f59c29fcd5d643afbd5698a0c671f658386e50005fc93acd44febc1baac481e5b0082da64d3aa933ca4612acce1be89fe5df192be1f67a85fa31b0275c6ea945c51e82105ff1f1c0c4317d53b9f659dd8435f3b7de677fdebcb044e19fcb058872761026428cac7f94139e8e373e881a3ced8290d7afd023398e77ffdc3232c6c56ded4b29108be2a0b90c3372ae3e39d7826daab3fb4e71bdb732d2e8cb62f927d440a8a229c84f7941f1d67f75d693cdf167d1a2df402cd0c940667d024d297c5e69118d037a1aeb17a5cbe138bbaa48f61f49fda55cf4d2052bc22cfce7e50b03a61d1bd57e30abce3ff70f609cbe5c43226fe9f4afdeb9833e95eedc18e9e7c421a30fa7ce6d8a8e8069da343fa03b6b1ac3f1eb852b2c3e9689e9154d3b2195ea0d3f83b8ffb17e212e367b599471ed79258c70b0f5ac3cd3ffb29f78fbe8fbec7b297e34dc6e583a8b098427f88c255d6d6eb015250ef37cd313bb95fc7cad3f8f36b551b26ff5c7ba49295e3c1e8691e714270a47d6cd1f17b547a1296ef490026be36f42be766ec7393bb8732f0bbd53a36b2d0d085718fa0698c2b1e114872dea3aedffdd61bbf8a7a2149ee7263a6b8bdb5d2bb73fdb450f284c030ff8d47ca862343027d4205af147070d41d8fae4b535c5b056f75750d342f50125ce05b12fcebf0fdfef56b68901cf107f6f9eb33ec4df5b10afb3e532ba1cb5f2baf9f50f9bf41e4afeb02d487c1807e87b0bfa4a2f64b9bbd34b288664ab76a29fd1ff40e53068d5265e6838925dcecda1466eb33bef3b5cdcb30b31c84137d044877669be7fe092aff7111f4efca7befd4f4e3ca85d4e876a185f09b826fc8965bdfcabe486e4f8d9a1aaf4231600a8c8e1720286ad5368d41dda4eee00b7939bf1456e3f4c40261e01d2318ef7a3802da9644fec7368d5b274f60f59218e3024c6446d103c2f173b23a70cec4406d957b6778c451cbfe98d43d395fd391bd7dd56651d075d687ab65be50ddf1733ed1c771b25e870dd84b23d29e0fc7839791daf0d3d300598e630ef03b8ec2aec6e320587193dc634dfcdc1e42caa5b7ccffa0371e9e7b6a5f4af500f2de8324b2f60cb6a28c2351bf7cd7bcf4f842561da72f385e2db85f097d6c4996908d0b5c9b133940ed41941f60f06af4f54ee44585b17cce9bbf78878e9f47e43e7161bed0246f593053f2597bef71c271dfe365c839df95d855d067085e289bc864eb2cbde759654d68ce3ae98b7757246b0aae623cdc6aa2f227a4919e8d2fee9517aaf33f2b4c5f8428d9f4e351b9834c9d484a2a993bec6fff3d1ebaeff03fe4530adb891f1f191352eb59af1b079a563321a5cf7508ea6692b62ccc8d60a640e8b9ccd07b45375ef221b8392ad6ee8237b6b3de1bcc2bd8d08c55809d27bdcc0bb6c030312453dfb17f2ee4dd391d0093095f8b2b654f1ad554a39f654a4cfd7387b8419bb68f43c409e35d1d6d023875420ae5eb9613e769b30cc6bcbbd0e1b581db153cc62be18f11a712f901b154f23e48c4f858589125fc4962af043f5e9c4bc40f8a8592fb0131578a1f214625e9f3c4b0c4e863a25b9fe0c6a96efdfb0dccc7787748569cc4a18c4f912c2e1833b3f0bfac0f129325f4c962af043f5e9c4ae487c542c9f940315b0a1f290625e973c5a0c47d494c95e027127b25fc71e25c22f403bb32da598125f60b6227b5090099e954ec4ec05d7c1ed3df0aa3e8ee34bac119057bbb1c44990cbd0e71e1c9d4de5756124b5a7fbe993d53b8a9b183ff891a46227f673912df0892cdac36a6aad1f8c1d45c1ee3b8f802bd4a5ed0c8387e5974d1d8bfdff303c83effcadbb3418fb0e378a5132330aa6a7100b97dad054353f804ffaf304448db127a4ed06f71bc9cecaa1ffecd0a11379266521cecf039d73c3bb96df21fc4a1205f7edcaf2612fac906173fdf5bedcd3c37c4a5b4c111ae6b4d891fd372edb9faba2d9563a88fb78ed741014e362b66416342a1bac44a3e0fe5d98a7ce1fe7b42776ff806d1856f33d76776cad7b2e6255cc8141f773c86527cb7c99c3acda39b89dc04e9d9ce1f3438e1d4f6ee6b47827db47f4a592a8e3406712f1495e63e3196aaedf79d5e56d326d89042b803fb1e86d88341c58392f75deb04f09fc0b14067361591e881ace8298242132c4215bcbd9d5dd0e5c473ecea456b77b63ca07b3afa019469a5ecd2f21f78a95f20572e1f96c6de9ded86b30ce6d0034494903c6be81089e1f4bf6d7e0956af442dba5ea6e785f92752e51ed19232691484b809e1af88a9e84cc5ca7c0d7eb52bc273c8acb3b4a10601982cf75a7206f317cd6f8e85b9a0de3957d80041fec4ce573259e3c64793e4e72ec04a843b074604a4d31963911517ecc8d775e12c43f1e53330393a2e6e49846f4ccff3bc01c446ef6c5732edf0e5f2ffefe5a5c1371092c3e82b3449ec383ac6f33204bdb50e65d1b6bfa51551a0796fe8f47685ca22d7a9b00eac0e720e9ae9c12632a2a03a31dc571419b433f1dbb8ed1001ebc73debfcb3d256747a259ae4aefd3b886e85efef4f453be964a45afb75463155541fea1930e90c2c3c9b9501b4692c11946cfec6fedb753556d8653d82aea955bfb9d6108000211e059e99c6f55923ca4a8cc29c27bf4ce55ec386fc8aa32e9410340be858cb36aad530e424531932561db70ad6d0b87183b08e6f0fdac4eec2f3000eed756e945970ce45ccd10214058301668670211c70085bda0f1ca8e595d73e185a189cdb0e768a0f642c33c7c44e8613519323bb7777948600b207258d0bd5f01a346f24990634b720525048f3892e653e6dedf9256a653f0bec3a01d68c561517e368fa4b14542be20c0fb2e34f5e20ed5b9aeb776a4834c6e8b21935812863ec4ebf429661d38a49595d0aa06a030def16444cb9dbc4ba5cb7954dd8b0a3ebdfaa258397264f64c285a589f9270e54973fb079d558b004f337c6a5e8da05f620559ecc4db2ff28a88a29a3554b90a8da88e5275eedd2f849a824d64717bff8662f0b875f002d3acd0a35d655649d06806f5ca7a38ec32f6e54c04eb12144e4a8bb55dfd9125601e8da151ef89bb16204aef4f906c4ca3017ef0759b8fabe81fa0730d8a5eb764c2d4dbb252ecef9832c353225fc361f640f8d83d4c7c0916f5423d29e754dde63e81b17dc176a2a4843a375fdc2490b207fbcb82b3cc4b8e9ec54ed38d5760c0db8ea8a8692f63b6a24286ef45c30fcb287c2fed4942da80a7a8c95db0a0b3cdc5b937cd6d8f23426d2712c2d3d67bce591735e2e47980634872844a1cd594c62e13641dd17cf529ffb854e8dc6ccbaa7f955e4d8e4ea21bd990018209e8168b4552d5357bb06fc6a5fe5186a6823bccc5c4b34fba9301a6c64425886441247e0daa9f04d73c3e477a24529e6faa6f23206d89064e55793c650ad14ee3a101dd04ffc9f69743ca80430acf66c1b2dac996bad6b4b88b0441ec9202e758aec263a6c1265d4dc45eb28123859f1ed103ec880d971cb1561eeb8cbe5f48e2e00921d4f9e9bd5cce91c5e66b970b034404f36ff0529ead63301274388791b9c866e663991f8550c0d43120a2b62e5b343fd8c0df24bc6acaada66cb4b541cc0d92f7f31d120c8a00522f3e657f5e598338a4c5857055a18ec366a3ecdc658d4939c113e8c51725a035342dbb67e32635b015bc99de47f944f7ff2c2aef9423a2fcfd81cfd7aac7feaa7814df402f127f84931073593297d247fe20340e701337b8050f66fd24aacb62977b5cca415e8cd17c74dd46965a535e532d06e4829d9d6469e783aa1fc6789ee4052848927ed2270a0e8045cbd214972a2a5936744e1db9538a1fe189141bf4815b12170ff11a8af103b81fa94e92634074bae14b7087012839f16cfe41b6584ee19343aaa84a38484d2b7304b0f94e46b2a5f4445d8b3a2a6ed128ca1938e5cba1e58a5b71ca6d019bcdef760776f183662ae4923c418c476146faddc46c1436219987a79c9f02ea72d3cd49e86e1c9ab6a49a79944c867e4d5bf67eebfca2f33a10e9e080572312f7948f615bdb49ac58ea534c1951f9b7217cbd06bdd2a14e57bab8a26739869318f25720d78c4e31fd94e26813e4e4f4021271ceb8e76d5c59aa9a6af909c6342dc19a58348c917b9e6d71452e983925683408310738044f2433f2651e613d5cac7955c579da2dba92ca37880c8a9af709c057fa6fe387341eb96a1d19764d0e6d5b4dacb5edac17dac2ff750403da464b288e148e81983474452e9ebe0588d4322f7119600e4fc868820ca7be9451869c54d0c20fe8bd7b842ba150e7d799a40057064e460023d794738598eb3003399e79f91f600ba8d502fddf306ad2c74b7a8e6ce995cf9d96fd0f927b29f6981b6410fb8fe112ec7b1037aea67aea09a9f068bf163c3165bbfde371203c9a064da24752231365ce951cf2cd364931a0445190afa111cfad8a7e07fb0c1d432e4266c0f48cd0ad003a51461d86f764cb3b0864ee98ce1776a6388da639e5d19067cb453f2eeb2985a6f3a5e2890a0ce7d019f50f6021b0f069da97e618b478476201c3b272da360fd527e1a8d9ee96094fa9241f7592f8d389bf5a89aaf132709ff394a938c562f915877dffc21ce04eb9cb908f173baabff5eb57ad2dc9ec017f77012af4d411da88c2d813988fc565c6a40def27897753504800ba658f632b3f9f95573350495838816b4b5f4a6a2b3a51b427b5975462c32cb0700f3bf160509082fa178dcac57101773933b34576b0fe44a7afd85095e7d9b5c438febd081c302ce1bb12b130d4cf792d35c4c090ed7f590168de284abfc0ed9fd5dbbeac14e3e803c9e58163207478fc330e796c7f0cfebfe75e96c3c0782390cff0efdeb77e5bbde1ec5e16d46726061db102e57bd321134648a48b79befc7b0e0e4db9458ba76c93a17c37ce7d1e3bf5f9215bf687f43722a1870a521d1db8c55ba09186560487670d0269efa2d19239439f7056cc2c0011f2bf36480a41060bd0f2a8493b45fb72561d36dcec836b31ad8dd8117e9987662c7d38a0e58f94d5d59effd74beb3292d27608d39a457b44260d2a01c9e0b472421736e9d2de4cb786bd392741f43eed1926ac3a6d3d715f4ac4485046d67a015d131b5123d231f196849876669b0b9547cf0d3cc9d24a31f4d6b8093831ac9532c4f2e540e2de5863f76f2f6e27b0ad7d955fe87b02a87bd673196a34563d7ba44fd9a1a45463728177b357eddea5131aaf841e71c0d4badf439a014912e3dbb597c464aaa8100d20140bfa3e940c9edd9f41f726fc1393fb0d5d505f0995676eeed106b51c9acc30e0bda048531549590eadbd810cdc7116c6cca672b82f42dce9f78e1abe7dffc7eac15ac68db20ef9716cf9caf082a427115271db2afb7bfaa8b82d89035a7830843cad883d0628ad3981a1e60d200c27de3a081cf7d9f29ce9596058bf096ff33896a9cc71bc6cc6719c81a6f7a18a198960697ad4309eef93ef2c6f0db9bbd4bf6445f4c428fe1cf695834860d3712425c508a171379fbca2a0029aa9bb90e639efae7cf13f02caf9f0f81274a7082ba0613d57dd6ed80a728fe601f1a7bd16df138807356090e2f5a38cfc13391f21a7bc071c99216f861d6a06a4c9d4f333bc907df5b9da2466e8ee83a5c89134a60558e66cef6c28d08d8784d03439a498d5bda832d4c11628f8fc12e52ca304c3e7c5b045b1fa9d050bcfadadb096a2e6e56e3579d855b20c67c95be7826f9bbf45bf11a34add265d4091d9c77e43069e9aaa802f9851f693b62cd689197e6f8a495fcdd8dbe782576f08df47a661242ba341176db9eca8479f0ff6f55e865d90520e1a51413e868c8d05847c84e2b2488bd0819b7d3d915442d2d0025f58be7bbbe41ecf533e801c061fe4398272a9850e26c1facff99bbe7ad4c1dc740287434ab2c04f7db7cee5f8453ba33ee2921b4a32226502270718475dda922a26a22b666627c22905a61a29bbbedf3d6ae29f6bd8307a9c4c631378b56aa8b185f659dc9aeb2ace038618af4428cd9586e9f4fb8b3f414c31841e4d223c924291b0c65605cd63477b4f1a96eadeb7f41ab9ae96d4db9b9d014ed0a9715c533c02743730c25e4ae4eb9f5b290d10a1ed5c921f2654dcf3d44b03bbf9db16d97413033459e5305c284b416c51482ce5f604c18ecc886e8549b81d86dbfe8ef8ee0b270a8aceb08e5ad8b8ccbbc522469b5f37d6542d898d69b91c81e42f835524f3c3332ac28d9d5aef981a9517490cbe1dbc650c91c32785b4efeebd25cf6cefc640355af00dac234a01553e7f0f2e70746683800e300d9d174d84e3b5ca3f765c99fd24970bbfb357614e2b9d60b0c0f29dd83933912c2717622e592d1b9f040c5ef35b54eec7707baa56023141b462726554bdb3d2a664445eb9d8a00402097005a8acbaef5dd85400bef8e2e95ce5c8d4fea0118235adb54550ccb9b810bdcea37c1144225c04f7c5918a1478aa00dac572e629a38a82f25f55a9bf3bc63723031c80ea9adb41c582928750b7ae45cecc61d90299c99fa39ffe2db4b770b84f5d9312a00b8247eeb8de444713c5a4c00b4ff889de03dc0c3c1b4005a9b51c4de14abcce7eb8e5a3dd451c78c8802a03d71dcc0cf8b8a8ed63520bdbad4821a083256f5379e9901e2476caf1da23ee37a1689e075424fbed874cd57e03a308ab130b2f0e8e6b3cbb9cd230e4229dc76fd02d7ebb34262bf26bba44498a3ff47e6a8316f6a14e38f3c9a02036d6e96b90b816eee3924769508524c5e9262e635221c9a9c8b43c597c78502f9933379bfc9f8bef3370ada0e0a829a8975a5c9c0c1b48adcc5fe38fedac1c95c84425d5ecc9ffbb7e8fb4cf35673f0e20877aa43d65217b5fbe7a4ab7b112e3acf456e94b65c8128cabd2eb85568f29815400e461e0bdb3c70c14ba72cf2011b62b21aea36c39ae05550954162a390b91642c4400b9c4726a3be8083871baddb6441097979340c4c429e14054f6118720d94143b4ca2c3ecac0f4390802ee3e66d440a19c8d601f39d6b39c4e68fea6a8da56292da20a398891a15193b8e30c07d9c0d1286fc836203886fa383dc50ee96f145bbb53cb467f3c628de97d10f230629ae34005aa386ff8762c0267439135d12f7f95903f1e5c06b718cf0dbc5ab6158dfb97012f9c6ad49f6d685b5a8debfcd47dac7a544b250690dc8aabc17ba6080900c3b59acd2e5e8e6a9ce3b460401ec46f43fdd2f5df0d00dd568cb42bd4427eccfdab58db4229cada8e6ce12e6a86d7305eec5b72b5781010beace34a5a64a2f9a0180cd5ec9ee1d777613802185f872013890c6bd898d369b70f371cf96ea7304346b2f2e0e23786ee1815699f7a32e3cffc7b1c0ae5bd69ddd937a99aabda3a89874598fdc3045519717793748b1871273e204f8cd6dad674b16d90f2e928b0d7a275d5fc4cfdd74668634934ef3e0764281b6b624bfabad181bffdadff29a492e36d079e7d78185bc33b69ad164ef8225cf1c03d8439767d93940fe021554a0b2c66a6ca919dc51b0ebefcc6e767d0d4aa355c260c1c53cd522d0b5827bdf2da543bd9848058669a6d8d711bfa8e7334352d5f65b179a22baeb1f2af9168c14a506e6a1cb51dd5146e4b2d46fa1a2bafd9f163f40f44ce68560f0d42d3693eb0a7e28902753a236e0b59f45c1596c34ceb3e78f180b1aa5ccb93091ef436bb174a6963de10389e74fedb3a1c538b3d0b19922dbbea786ab2b3cc70b469d48628cc3d6e30e5ac49df462d4efecdb7a6856bbe11f35f37177f855ee97e760e5803b19b22bd455b7bbea7559c22c034a5e9d25dccb32e4b87a1a3388ffba37f25c16f43cbd4c6fa763088b5a67b2e24787aed83c9902907bcfd3c2a00812bdbd57ec110b8a416b71ba7e52b262c4982b0cca8375f62934c2a1f4161d5fc1383384a1837e0d3cc3bf164f0b0b7924db7b9d186975c2ed6a105c21e929fd8473ca45cee439d10ee44c564398301b7b8a1e97d5d93a1d10883385aa4424ddccb926d81419e6c5721d68893bb22e9f2cafa10154a71346176e3373f9e7a76664523ad252c301f67779262ee04c2e094f9d7557080ebc5ce7289e471bb9d3f899ac17038548c26fdd83ac374996c0fe560504c094170780e83e7db321cadf19220bec88e99dd92cb602afb5c745d9c3d16ea3e6e881e25273f320a148118183c61361b7ae807079ee1cc2b27176720846c2bd1992e5092d040c1a968ab568d4b13249c97803c0c9c64ec075a8d8634baeb6c47ae245599553b5b468bd568dee97c1af0a1af4d8c4a1fbc7965a8f4367759a02305837a983079a11885766c82d20d74417d7112a0e32881f0a09721a9dc0000b75c2989c18986a758e2083b56c79166f4041861da0cac783006c434e52174bed038783911501c0208021663d4889291cc700b5bc85d3286f4c010b81435e082d830fdcc72490b504306f7be0c21561aff2a23176531e564a12af868639106a791456118496b2e7c86302c0b1ff95dff89e9e046aae8a26b9af52cfddd33d8207615e1fc5074d49e500190d53cadb1d4833b2ffd0fd9efe2bb1b05749156c10833fe32f3b323d16d1fc2218ee0792b19530b1970a8460b263ab52e4340dc38fb10629a6c65ea5ca167360d709beedbb6093708883e06d3446133692806aa4e6a231248c98da65968489adf15080afbb9147f9ab315d50221855ea010193d72f438b09a98c5646717eb0c6b8e134728241cc74ba787c91e04340a85f1ecf580020a49404b0dced415b764853e9104060d87f8190b9fae5d9fec03e312ba653265b6e32d1396cd12348e9880055e64c2dca2815fb484101e5d6159ba79206412cbf84cbd66c3ee6c8f5d0516b2ff88d91660e239b30c1b95f2a6386aac4ca6b7300164bd39c758548cbec31da22ead9fa43e03300803d76609bf41a4ee48f32fdb79576c64aa2807e0b7654f190209def41620d889f1532f8cf677df21e841ca856d68e903038cc8d14c7b0d55724ecdc51429608c8e23164a7f5f18c3657b05db79ec8edcb1676ae0f88075ec542487408081fb4fa4490e42104ca3ee06cf9703104541afd5d3d61aec83d52d7c1110b911a925ec53e3dc1da3562cda552cc269284c4ef7061519926f258794d1067cc2202f77f967c89b478ebf3736bb243578834ed65c0e042be3989e1cb55df2293734ed7d1dc10a9cefa1c1b291cc1bb16b9da908e914a386e83ebaad470d461712942d47eb739f0b32f42aa5ae87c1b32e0047edb12009d116a5a796e4633319b0260397883c02b91bcd385593527911ee1b12945446c091de3581b6ef99412c91d2ea64c482292e706fac5c148f54d9a08581238c2318d6d91290d49cc5d609e0901d857d7c2604bf8562b3dff06313127c88c119441983d96c07db8e6fa19fdaed09d9444a2a7248b6f686b8ebed0516725dd5dde2c62fa8a898b27a6c014ee9386c5f92908e98f7b6783418af5f4ac1ca47755a03d33a275491570f06e25c0afe8b701d765564270580870de08b3455c225273498cc29e2d43fa71a24a98a7f62c7651afd00983f8843a3822afaeaaa690d857bec819ec15a1323892ca00d3de06c3d521cdebef95ef0aa4b08fd74e3ae0c8181d12aa09d5f03424a4e7c11dda0f818aefbe02f319fc162dacbd0796f9ba7727928955f142ce981e2eb6d815d2a992eb773cb4729bf90ca62e21097ba518ba5fe9d898a9e18172800f59b43b8c8b403a4f9380953772fa240e203dc7dce136969db04858676bab10450047cc3c545d8145a1eb4ac1414c34324567115d0a1d1e559e4a862f3430141f8a564fb31a721dd7de22faaf51d9d08d5a43073dce5a024d666d878a7d70eb674d697903c6269fe2051f2fb634244868eb1d7a022ea3ad197be98b5c1b3c5a312bcd5b2e5d0c619d2b570272acfc853634029183d0184ed16550b86bbe431d937ac2777998a126536e09bf8cce20ed105018e9e1c99c200f49356fad5970e16e93eb9279dfdc6279826a4dc9c771625159eca1fe28bda940b04f809d7905072fff1705d0d1d193adfdbfbd501abb060f2e742562057d4bf0c7a3d479c703b83b84840820b6fc19f7905c4c342efa433107844a0c7496487cef297d2cb9ae62140d30af385aa5442a1d0be13e40a95ccd58eedbe7d4c4bc99e0a0687c6dd617422043bcc2c2fd9c4f228f8c77bb91eb43c9690827db99bd58389eaf2130f870e3cdf50c53d4f8c99bea67e90f2fef841cd0f657194d69d7c128e0c5e49c2acd06599e813815af88f8bae00b56b6a53e9125ebfe2c3a0c1f90b7f0e7fbd18de959aaabf98a1216588acc49409456f5f93978d8161db52b507e76106efc3810911832aa07d83ae438880bb3eda62111ae517cf0f6a40676f97373e08080f10e3cf6022c3c0c1ef87da25675377190f653ed79b8bab0ab9b55dfd0e1dbbdd237be593c2d81edda209002b0e28e2e9d10bdacf5130bc9ab34cae5f6892e23b34497ecc1ed13e7df8588638e79069d1c75a05188108a771f7c554c54f989bbcdcc213f3caf249a0006f0d56000db92e0bf380048e90f890638a8fac81db68be6ed20eb078ded4a967becbbe5d2d600192b6a37fc6794cd2aa2317d9722ba581c342ae4baaf01038ba97365d2b79cff51506d50a088875e1887990a2b77b009e38b30ac098159feb1bb07e351037a0c693eae06ae932c83764b3ef16569ccd52303595c91870373cababae122afc25727c5005e6609205e662fc30b826c62efdc1e62ef4081ce7ddb8ad9109d2326642754370cb8397167be10c1e7f7b3f4af5c35f39a53c1b07b69ab542958e394a3c6e8b945d13bd34c87c54d7ae8597510c9e328d5d1bc738b8e09097620057e240fdec7f14553ba3c745149977280502016ffad4be4304b0fdef9aa644a49e1054d974405ab70f330160b05cd6dca41d2ca4566473331b45c30ecdbe628200080fc23bf2509ba58ffd6a10d3be808974274624782090a16b27d8d85ae24f087eef01d90388b0efad4126cb9043a315fa95510382b391da0006b82d39ca834e3e741522f4cce7284671af491ac3f5ea87f4adfc37f92275559fbb7549a2de58e56a891b4bbc98acb1ed69e1b5c171f489a5445acdba3bfb604169e9155d235076f657612443605990af209180b756bb266d93d22c76ca461a25889c2c778488396c4327dcba28dc75f2a8149cd2efd56ccd5a5dfea9a5d1b8b4b9830fdae48e137811829ffae0955239dfb29babe516bdf12eba35a4d29e9282916dad683d7c6c2dfb87f3d9206adbe76283cdaae2a4443f7492572cc4649b99f82c5d8c6fd4be77e4a186e5d91b445d244afb1f8845ff7b5e05c141eed8f1de977aa8f5453bae96a54abfa32e9dc4f19ca1f0beb1b7d2c3682569a402cea9a5d5b5ba49a13ec9bea8e11e9dc4f0943fd16f7affc5624ffffb31e314832c56cd7de32c3ee8fb68f696d9192c652278918c336ee5fa16ee95c157f09d7621917e5574137c544d1de88f2c3b04cd7ec7a91b45986652193d67f471cdeba54cb546eba6edcbfb4a67509ad615a45a5a5f0995e2447abad98415a53c8f4dc379175f43ccf9bcd206fdcd69a98f1ae3b62662a0bd6886336dbc2678ac2cf4d972bcc94ac7e980ae2ce9ab38abb9761493d242654965019412b73f64c22ee23881b41dc8c5669acfda248abb409048709e44e20573aea721f456ecce05e26090d69d23469851972850dccdd1d001bd03961258214c761a9e70f13fc09d0d32a6dfa9873024f1720e0628b2e3ee0810e80c1810e70d1812fb6f0a20b0e70800b2eb6a8b3012d3490812cb0b8820e062e60812aaca8a28a2aaab041260a25eb05847c67c9ddb338102fc270e7a9c08e0cf8d3dfb9230af730bfa6e1b5536aae30df59b9961ceec9a9dc3abb40e23b36b5606fb6da8720ad5e61c14a152a4cb2d8920060af24a4292b18a950e46a110d0929e958ab354848e2a3ea19410424211c31b2062d024200887ce04107433800c01a6bfc58638769ea90b9ce1dde642e832c527b1672668e36ae84473673b4dc9d8aacb1c61a33c70f676d4be3810254dc76858da5e69940911313742cac434e4c785d87c23ae4ce338598b30bc4a22844e4ce23011e29469c8b7822c003019e07f0cc19e2f3c609ba70773d6fbc71f71b3ff8bcb1e59b37622d77d7f8aee60d2637907c1fb5dd7923c7ccc0052bea8d318c6543af994117175e9bd039e97e58e1ad598e03a288c3038709283c50ec40a0013b56ec5ce13b74a68d273a7738bf16eb64439ea389eae1b66dae4a34895d0713779fe213e5c475ee781a90135bb4baf3bc71e761803bcf133c4e60e1ee3038100fb460f2e4e061fddbe481b943b92fd2954d8fd476b1d0dca1a395682537f0f788c18e56deb6b5ed4a5214c2cf6548ab175bdc3de7736784e94a7327c874164c07c17434fecd664b3bbfd32f25e5a4df6568632fdaae8dd59476a9a4f7c7ba1c8d8ae0b5ab55ed6c4986bfa2ddc03a443b2dd6fc4eafb5280a7d1fd595d63fa2ddb53991ee7ce00214b0581b5351aafb933ec4445bc35c8044bddf35245bda054bd2506345bdad312c0d2a3c8d329e0698c60a481a3f80a4d1038d2e9c46a1d4a3140d22fc335da1d1058dcfdda9f75aac4623887a5b33a141dda987ab4963b8ec8c29ce5073c60feed423816ad1d6b4d8d819e219023843e5ee947a59eaaee10c8b1966b0cc30803bf57e6e56ba4c7adf1f95881ed6515d296829ed9ee8aaea2398508f2b4968a988d630d57adb6d2bcd895fab7d141f5590be55a146e0e35c65d87019f91956738279de8b64b95940c74bc046e2deafc56afd19833a3c1a7e07ee3edd795070f7eb403cb0e3ce534310771e26de09d32a5d69fc0a1f0536dcdd456563d868ab48845fba1ac5c818ddbd23c30377ef74bd51bb2fad44a2588970b6f619d6e1bd627d86d12e77ab7dc237da655ca3f749fc994480bbdf702060bcc077897342e113851f86422678c18bb35cde33139cf126b89a24620ccb0fc3d51157c7f0e2a16bc7d104495c038db83a8ea3ceb0a29a5d404a0073d74cb42d0bcdc2fa37b1fe92e72db58ceaaead5d2a853f5a59f89f8b3226aabbb6603a346ae2e4797a8949e7bee9430cca907cdfb7f7916040c458220609eeb96f1a411aeeae97bc7e1b1ee5719c917a7f6c044bee0ef4b5aadd79dcb871e3a6091e2644e000d7e2d325371093aea7d5210a2ea08d93457ab9bb8ed28184e0c749f16fb3fc3c6c7896f02e7c1728de6164c0a97771ed5b391cc6ed51a63d2f779ee779b3990780700394f980ce1debe271c4208bb56b556b690259782da9d002beb520b471e553f92b279c608ba85cb1569a5a586222d3025bc1e856bbd5a42697151f71dcb72e57b1b47d56d26d1cd62611939d12259e26f7be510f69894945bbdcab498d094d286ce26add642e1548185315a61b68af64a625fb4c4bc85795274c624d642eb06005081c0540b56d62c2129690516ccc6fab3447fbfc377a74d3d5c8f3409bffbe6d679c849335817f039f8cf1ace151c3a384dbc8c16d90f9c2eadc7de8e407a8fbe9805817eb16661fe60702ea8058e03b89621d750f102fb6e8dc95230681b4e49e808074a1c6ebe96f064170d1b15a41890285fae042abdab547f296b2d2ae3d12a594ea7881e2423b7b5bf8b3f1c6931c377078066950004a1009b350c4282f6c74e136cad0866c5183bb7f88af173ef5882803445950a0d0ee6de1e700a48e1524903a413a774ac83aba0640b8fb1507b2010c907504f77d271c965a9363be013d784457faa9d22a2d871653f13c1d517058c688b291cd6a5d6975284a2c28e32251ac446225f23c1d9ef7e1fd30b4b79925eb8cd4daf39a000101010101fd003d71724213134a60b2042828a80605053dad0e053dadd2b00c09d59506fdd7a2cc9c3e939ef744cbdd3beb84099ca8e3dd0db4b7a7d056bdf1eb0a395143e70492133c6ed8b8a9b901d244144d84e9fee211e3fc7d99f727624cfb3ecf937d4af9af1d7ff5f97c5f1352dc3b204dfc70af42a4c40418ae9998c34498fb3a7f0ddb2ece5598b8e5fa4c38717767ad9aea3f3191abe402ea5459005d00e9de2d0006a44d0a2eceddbaf7d344b1acb579e30e9623c56349dbe46ea55f9428513c4f1654530a7aaa4b41383c347baa398a9ee1d810c1e636fbd97ecde6c76fc9c64697e5abcdeaaead281a5793744cfbdc21ba1231a6a9a2bc8272795f97aeb96a716fd91a347ccd9c3538f76e8d14ffb2d0d547bb35384a206ac0e8b49a376a6e6556e3e45a25c4704aff6221259450024767c3fba0ae4fd44559b9daa380281480f33e6390820a80919448014988e1ded124d2c8928021095812aa8a5795ea3475701f5e5095e607f734a025d3a0a9e3ee1db89fae5695a64283735ac6399a7e914463e4c3baf8aae83d42f3c3e8cc1b9ccf7471a76782dc3badcf6c60e68d7b670647cd587137b3840a86d16bb3fa330675d6d7ee3a0382c41b249030727724e81174fc8830eef4080260bcc323702019c1c6882d5f9601ac428b65005fcd080f8ae0a288314558a925ad8552d32270f8006e580640bbf08998e33522ba942411469789081c9dc6af21c0f0ee623a449b2148f78be9104e5a43e0f02ebf05ad58475a85f0c02d7d6eba1ad1d54d572355d0972e7c0b7c2a429078a45f44052684945a0d0b1126808a0474716ab475c6b504fca874eb5b193ab54c1877ef5c0ea48c941c8de65de673198415e51eb70e0287fb7d1f8f41cc7e0731048c591856202ace5d8a2e6f0904192da2e8b212d59c60336dab7668d75958ff268455d81f9b615b9ded3adb751625ca2b48fff84b41e05309ff963947fb214dae648e06fe50c30fdf0f2a8c9768b54e409e541a43a7674c18fc2434e6e69db663707897a311f940850fd407293e0c11238677a118fcb4e96b311fde558cbbfbddb68734b0a425237a2bedc18a77342c527f616dfcd247a25845b15ea6cfb03008372b0842bc9856061065ec0984103a4ad012322470c7afcb845fb88565afb00e896551a8ab91d2c6af1c60e0423fdc5c5b2420634eb0b51559dae664f53ddaf805cb89b9ba42d7c6afdcad493b63be707f6dfccac1871e678dd6c3763f61248cab49f8758f624f5f84b1ec855fe147d103113b4aa06070f71d1417a795da794ad0cab88856c6367eedb7e557f8b62f22c78d5fb432b6c50e67e86ab44b9cfba22fc58afb50bb0b74086309ab7ecb76ad4640180bf81346da42b66bf16ba747181b5e26071abcd3e56dd3d5db9c4113b50e318559d794b0ace622fc22fa8c93f02bbc7808b6f16be317155fa6b8bf74eea7e057eea784a1107ed530ce3c46bc8cd9f8b5f12bfc8d5f6ec60bdcf5e318c632004c1cb29ce0e47937c070c3df103aad618a4445a6690e6f502003990f7ce9f2811b72182205340b16215918864261f89f4b58f83648c0dd6d10e336103969f27d1f5dd5cea70910cde5b68a8695163eeb47bb7135a9867d541d25812e61f235d5fdad58dfb7355dc224cc4fbeae435dee9b3e550dbf16abbf9a036b58d7dc37a9680e87f3ea73256b12ee9635fc0a2f137ebd48ab9769e3170f922d753c77d4961766b3d94c498e14433cfa0366a0e264fd9b278a15e7bf72f18ba875f1cb85657f84653afc7ffd585fe093311e2a836de7a704eebe440cb4768f34d33d6a0231d2fea48d5ffabf28b6f1eb0923d162f815d6a13af44badcb14fee7f27e8cc99d470a9416d762f54e98023419411c0b86317cc2d0020c4a1306239d3b50db5109a82d172a261737938b1bfd560bce8708de63f37c008b212568711f2a2a23e820410f527074900407002840a87077146e412c7991254de4085864516788170bf89962e82b72c743900972c0800b0869bcf083113b61bc90c347d535655cebc2d76283bbd3d913be340b16fd52f2b93c8209d96c37d5f869258a57146b7993d1ce6783f56d756baa0a6fdd5ff725a1947a1ab3ea670cf63c8de6eaa2dd0dacdff4034477a5d4a3fbd66b471cfefcfc00b17e7e80288ed463a93dda599d318c453ffd19837f9568a7a9b6e24ebd9a13a983b891ad2d1278737ae63afea2bd3d9125cd3db83b4d887663cda2ebdae7998b9c665c9bb987d3e9c2197aabadaefd10971b5311eb6e11e5b665ea3e2c593e2c7495e5bbe96ab4bb2cb9fa54e9912d57aa35ed76a5d956d2453b5ddafa54a97ea23eaafb4ff49f7405c52a44bb6debd3cd63b64e54bb644aaf72657b2ec69a6a21daddc06d9f34bd15d421d53bac2c6ca395de72dc75f65128515a4735573257da51274b69f8f99decbe4ff1ce8995aec2271b66d2e6b7d93e4d6b18ed76895742b4cbf27d589c886e3e2a5658ffd63a12c5dad9aecdd56c88475ac334cbf737b012dd36bd4a6156a29dfebe5cad6871a550ed445188a2ca08df85b7f46fbbe75610c582496be33e69640fe9534889493361d254eee4986fb38ee13d9ed7a35be4988d94c2d7825f17db5c7dba4c44fbeac72dfd5a6c0ce7bfb254133f86654af749dcd15cd536ff2ef1171efd58bb28e111f8543e9affde5cab50adeb4d2759caba1eedba6b6db7864f73d115ccd57d3e30ba3a527d945e4b8f3e3afeb61f8c453d4d7d682adae96ba94f55d1eebb23323f61f195dc8ec254da7ade4773356d77d5f5837d942582f673155a7d9937c5fbfb3e1f2b7435537d9f95eff399d19515d5a7bf26b06692aca0757a8d2f6551ef1ed16d6f25474ddfe65ab4fba074dd47df666f7dc2e1b54f3877f6bfd5a69538a4d7eab7b9d29ebf9895a3b16a35dc737126edae4ef60826444ba57b54b444e5697568c7ce25152c75d1695a85fd17751a6f4d5a2b9263fda78f1c4b1ab5ad708c76fbbed6fff47d1f45c187048a8258935012e87f6da6a2502869f76cbf88820f69b70a142b38834245254ae86aeb327fdf473bd26e1fd26e15d5ac9c787116eb98f5c5b68aa5c5a4dd34eb4abfefd3181f21eda6622d77b64e473eb17edb8ae52844b12cc4e327b996423bf46278ab51288a424bb0f0f10b768f7255fc562d53b947426fcb150963191e3fa98565b9b0fed2c62f8d5fe187d7551665a5d00e6dfc7252729195e8e8627b5dd735748f722dc258a6733f65e9c9eefb6cddf941ba351ec5ea7324bc650d5492238fd092a84092765f7844bb5c6b146b9aed3b595ac661bde1da3da2077077d1e70a5b7eba254b585ac49864b15059dc1d076a00abfd9a565a4ad631039f2b1071775d73cae7510cdc7d1e9d39b2818bbb172d51b9b68ab058788572b09c08036d78e441f8637de1d7d615768fb00c57933ad647f7c768579b721d6fe07d1aedd1fb7e8b92d0e97aeb4a70a27f5b423f97e044a3b02813a7265d57744ed510a1235dde2793d8e0e131c67421e1ee94ee84e122adebc75b2eb4caa0e1d9728333adee1efa6c4971622df1d9b2e1de85a8a2b91a3289e0b85b9dbbdab59e973d8f1cf32dac7ffb1083a258674d98dc36fc0f3d4f8a14294072dff417b7b4100ecb5898fb29a10dcbd84c810e0ecb58073e53f8c1759de1b0c461a93b135248a244da9b9e51cecc6694944c146b7729ae26753e34fd819445813e15fdc04f57eb64f306edd656b4fa69ae21a1a177aaadae09b4998635c5a190255ad570c5db86377dbbeb13ad347c96fef901da42b4f3795dc1efa3789738a75ad162b4fbd0ea5d628b29595f04ed1336a29db6a25e69daf9e84cc314676b3912e77d9fd636beb7caf7fa3e20d6ebfb5bcbd537bbf8f3a9bbe7e23014499acb1593f469e2675c63e52769cd529f9a8ae664b4cbd5266cfbfd7d2c2b4a5564b9fcf351bc69958dd30f5e25da7d4632155e2ea499ece54a52c1159bbd948e64444c4bb2d84b8968b6e46abd98be92f5b51a667d58f3a6957e15da5d6ce98bd24ed7b02d7cf109df26d6b5186455fd1a93b92473b9eafc4e610dbf88763ee27f9315559f75cdeff44ff846571ab437b266dbb52c08258df9372dc5dab57465bb56a48dd54633aec9945e5416634a2ac18fd11068972449fea1da62b17bc1fcc119719e18a0a860791a8f01ec2d6fbb96d084004b2868a7122f1d88afede801cec385a5ff4451a0d7521e2a50c8edaf559dc1129a94b466290a7f31786d094d4aaa6da7cb16c22a443bead52a08e8f1c9c2c1fdfb74ee3c6fae12c0ba56895841ccda11c05449c05fbec44cf261cf6b3d48650a0e4bcda24d15175fc2629989c1dd734c404edc879471e7f172bfd1e38e4a75f11d1be69c002585470dcf1a540a11e8d9f1206abf74e1c319f84edd5ffca2d752d75ffca2b8860bed6c36c28f3b8bd5ca29e99a5df7e87111958b8944229dbfe96d15a675ed97741572cc46757f524da9eedacab848df4ac6322eaa29695db3cb458ed948c33452cd1596df163e52eea784f58df47fd1fdac8bfe8b72f99dacf82dfd36e734599f62b6fac2b2df7589487f885fe02f6d8d9334ee6b4a9a56c66e25637a5fab6b768935bc6fd3fb9635a5fc6419d3e17d3237f42126facf55185993b4de37e3225cbb4763f94c5817e9261023e9b79aac4962624a0719fd5629950306a91c6b7035a9c3f9b56ceaa3daf5f3472fc5d524da89386f5d86e2e77fd2db2649d23a12a29da6354c8fecd7477cf45bab2f06ed882f065fcc1f629a2ad7baa7ce67b5ab8a7ab918e5f1810222291b488472974211b15d2bd26c2c77291e139c7ab8768f7297da2193dac1b92ba1bbd2b7795f9bda2172a75e0eacb64732d2df5a2a12aae56e16c5c912a03268e33c6f0d32d6b0c28d587a9f300a020f3012826aae81c475ee72df34132b88cf2801668718c4e00ec4122bced6e9be1a6dd440a3060e6a3c51cf47ac1babe87b1f11599f74084b6a51fc0227dcdd459bb840042e88e3023819d75a2e6abb76a671c6933852687f2cffc7742992b98ce5ca21239cab95eb8b44b66b3516899efe458ed9e8c59785ff5a2f71f2bc11b482461134823c71c61037333430230377dfafbfa41cedb513054a0707683106d00e06525006116ee407bb6aff341b96f5da74adb99217df9a6d3b1f94e163b60210062ab0310725c7dd797080ffb56258bf7585fe736d9261858fbf8f46bc6fbdc16633323c328a9031e4043f9c60cb09b6cc2964f03945e9738af739c5159f53c87c4e61e4738a209f53fcf89c0289cf2982f098737ee61c99c3c3a79c16f89423029f72b2f029478e4f3949f89403844f3931f89453f32907a907073e3e88b3c5671cd1679c98cf38527cc651f98c93f20987053ee16ce1130e153ee140e1130e1a9f707cf009c7003ee1903ee1549f7092e0783ee178e013ce0d9f50900105163ea180804f2816e0138a323ea1c8f98422e6138a9f1f423c00e2e307ed20040f21d580303e1b80f3d900d06703947c3620e88df6f9e6f3f946003edfa4e0f30deb4d109f0c80814f0680e1e307074f58c0e7130cf0f984199f4f7cf1f984e8f309269f4fa4e0334d9127807c3e8104c83402800ddc883edd7c3edd08f974d3e3d3cd0d9f4d8cc0671356f86ca28dcf2676f0d9840b3e9b60f2d9c4139f4d78e0938918f864a28e4f26a0f0c9c4189f4c6cf1c944169f4c143142a4c8082114f1d9460d9f6d36e0b38d047cb651e3b30d199f6dbaf86403e493cd083ed970e0938d0f9f6c787c2ea1029f4b84e173892c7c2e21c512717c92d93e36c0b1c4129f4b509f4bf8f0b904cae71a16f85ce3019f6bb0f0b9660a9f6b9ef0b946099f68cc5823c6e79a1b7caee1e2738df5b9a6fa5c03f3b946c8e71a1f9f6b7af854d3029f6ac2983c7cecd88163887730d5bc7caa19f2a9c6894f353d3ed500c0a79a0c7c2a51c68f1f4964c0671252f84ce28dcf24d2f84ce2079f4974f19986059f69603ed3b87ca641c1679ace679a223ed3f8f09986c7271a337ca211814f345ca0a1804f347172f0008286fa44b3814f34eef34c0b7c9ee980cf3356f83cb384cf33627c9e79f27966e6f38c91cf33467c9e19e2f30ccaa719327c9af1c2a7190cf83433019f66e6f834a3844f3343f834b3834f3307f069a6f46926f46926e6d38ccba799954f33447c9ad1e11309347c2231c68e0e86f408e1d1c1e4d10112577c2221e41389950bd1d1840bd151c685e8a08103213a70b8901c5eb8901c685c488e2e2e2447cd85e428722139582e0487192e0487065c088e39ee6ea48907de81911d3f8a90c167112df82cc28acf22967c1631e4b388273e8b60e2b308558f39c4103e87e8c1e71038f81c828bcf21489f5fa8e0d909e2a38c169f656a3ecbbc7c9649c16799d5c40104199f4020c0271024109f4f325bf82443c72799099089c22799277c9251c22719217c92e9c12799303ec9d4e0930c0c3ec9ec1c3b74cc311ef03946033ec754c0e718287c8e61e373cc113ec77cf139468bcf31a1cf31b2d40f0d70f85067fa60854f1f22e0d307383e7d58c2a70f46f8f4a18c4f3115f029068e4f31697c8a01c2a7981c7c7a7941cc169f626c3ec530f914d3f22986894f31487c8a09b263fae8a1c7670f3a7cf6c0e39307357830814f1ebef0c9c3153c3cc0270f4df8e401099f3c00e19387303e7938804f1e60e001478f0d7ce8d8a1043e77d0c2e70e16f0b9c31c9f3b3ce1730705f8dca18ccf1dbcf8dca18bcf1db6cf1dac04d9e0870e5af8d4a1023e7580e35387363e7520e353072f3e75d0e253079b4f1d967cead072f71e3b7c83d4102161def80c4384cf30637c8691c167189ccf1c94f8cca188cf1c7af80403039f604ee0134c183ec168e113cc057c8279e313cc113ec188f10966069f60ac4f30557c8299e2134c149f6096f80473c42718203ec1a07c7e39c3e79712f8fce285cf2f1af0f9858a1f00981bf0c07183832f433ebf04f9fca2c4e797213ebf6430bd80f1e965062f2ff8f4420ed160e250864f1c46e013070ef8c4818ea792a8f05412cc5348753c85f4796a8a1853484fad80c60a5d3cb5020e4f1d85f1d4d1104f1911e129239593940a6d3ca582131e3b6858c1270d29f8a481894f1a92f8a4a1884f1a38f049830f9f34e070000c09e3ce23e51ecc19a4f89c01c8e70c20f89c61c7a70c23f029431d9f3168e13386393e6348e33386303ebb94e1b38b183ebb7ce1b34b153ebbc8f1d9c5099f5d92f0d9e5079f5d6ef0d9a5003ebb943ebbdc7c76c1e2b34b92cf2e463ebbfcf8ec0282cf2e00f0d96587cf2e3adc79f0e8b181f718e2387674204c07e8176138f5be68433d1cf5be9042bd2f70b8536f7a51c7a9e70512eed4f342a49e173fb96fea828a2e3e7747830335b8738072e1c6b968f91674b6f8b640e2d4ab93823a719c7a7560ee1b18630369e60664eed41b430b1ab46861510b1ed4d300111a08720db8bbd30c84c900cca9974518ee59a4a15e16a1532f8b1f2ccaa0dec4624e0d0b19163f9c7ab32bbaa0deec8adbbc4235e98c413d3a6adca94727c87d86013a597c62e0e6ee1828817ab30ba031c79d7a35b853ef023077ea5d8008f5b418d4d31668433d6d8196bb5be007f5ac40425b2152cf0a1c55d0f159451977ea69d0a30a24f70a88e1be4b12df921c9a14b0625220cea400035cbba36afe8d9d848adc516156e00ec5dd59400512274bba1f162d64bdcd3b4ef8b9962ee8d9586a77d4ed87fb4caca81c5060505c505f505c505e503848600aea49e0059dbb8b7d54530a3a6005a570e3d4cb34f01449412445cf942200ee4e6dd7e20dde326fbad2a2584125112040047ee60386e8dc4120055a0c77dfe21302a1cedd0dee8ec3513694b7ce0710e128226a40d180520285001e3e878b3975dce96d5bdac498215240b38480250d1a4721e15db699fcb06697ede2fc042addc03209635985896259d454dfc60e0e88008bc58af144e1074019004543ada67487a290e21e278b3855cc38108833c647b5f251e1de47b55aaa6f547385ddb44dbae96a54b56d3dfdeb7351cd1576992e5356d2a2d06bb131fda0dd613500cd0ddc75d436293cda69c2ddb9f87cc3466725a128da1a4a800103d0b97be20b77e7d1e2441927c2ae13ed2e592cbb4b3735f86bb1fa7513c6b29d131640858e02f85c800e3267a47377829312911cedcc9255067787c1271b14b83ffd4bc764f3c45de73aa47fe3d77d16f26f4c85cd5c220596076a08502a852efc4a801239ce4cb386bbdbae7d551cdcdd059f6940b23ec53051eb434c24c444a3a46c2114c22ffc92857f1f095793f43b5523dcdaf8752bd17d52292be56f02ed948d5f196ffc6ad276697fd2ce5d21dbb522c6302cd3431a202171ddbe720ec4116ddc8ff0e2eef388a623c0235a78d315f556d43b8288bb1f61c3881438f5f0ed89ae8cb880fe90ee128f20ce880318e1823bf556d4fb0162914580e14e875e46224db4f8f5b60ac3af251e938835ee64098b05f49bac619d79de1cc28a8c6dded5863f8ef69bdd0ad637daf8258a65d1c6af35dcfde6530834667047c9e08e8ac11dd5c5dd6d14c08f88218c3b143e013f3385f8e13a77f7475bcd37f2869d2f3b3284471a937746f200394f13e7019a091081c91296d698bc01ee46dc3dc8533c98e14ebdcc0318b75d1bfe8338c36827e2fb4d38d3a8b64fff3fe6c09cfec2cfd34bb0168568b792227545880e36e8f0aec3b3d58874500f1b75fa6b95525d622c7ee053f9c6df568876b68bc58c65b4d3a5d6175b5dd66e78ebb8dff685f5c5cf47f5f9bc166b4db55f7ffacb7f2d76b49bcc45366f7debf831daa5be84c0f5a8f58338574bc438296cdd5d2a85b6c26c687277c9f4be56849f27d8e67a1fd4b4d21b58c66807e4d530adae663f40fb078876b68e56c3b4aebe4fe777c29f7112edf2ebba332647fbb1f06675a1a53da3ad22896d68a9de560be577728df5465735f753a8588e2508562a62dcb98a8462dac9ee11afbe8f46b1651a2862acadae4fb44abb77aa2f524d8ffc586dd736e923d9d25d921bd32ac549b4d33d9f8831ec637ddf97e5d31f162a8af5cbffa47106eb96327ee4a92d46eeeea3dae2f44551acf485389e033d4f967a61e76eea05cfc3f9afa86619cfba98e7c9baf841e62e624d535a7eb8ff502850a8cf082acf8b220bf2bc1c069cbe131e498c5fa4d2bd479f8b70653859f797a2a764407052a460651ce765bfc519e1a2e05838117043701bb8d1cbf39a38799eec2da87fe96829cce550d21253a831d2d6652bfc271c5e971398f20a77f7a3528e7735a9b35b3fcd89d4c85f6c4bec546b14b7281df1de788876374b73b9df96aba32e712e865b34e316c8d26bd236d3fb84692e718eee5ae29c11ed7e76ed47e90ae7d79224278af57312234a4bae2bf4256424a34b2e99906b4905ea1aeaee93b856c008ddb285864fa144a15bb6501f95b6dd7fae751ad317edeae755cd5d21dad59c78f1f7e9efd32b5dabe10f6bfe719743f4fb58e042295d8574150b1aed2d46d624289452fa0211a52f5a72c420bedff7b9680835dce9f105ee3ec553634f0ff5423b5bb93bf6d4f8a4fb5ad5252de707cb10e77dadf8464837811889d2d96c564b23fca5a77f95b154cd0677ef526f029a71ceae0fbf042726c4621d6eaae313be4db4824f6a9a3b52e4c404ad8f38312116a336daf55c5b6fb456f74dd809bce96a14fe2d3f14b1aaebf4b6a3a747a0ddedd397d62cf55151da7d4b9894df577edfd658bcf4fb7262f9f9aca2c882322efab67e5ac335aadf6696c6a02585827c5494d624da6d6d45fa4591058d7fdbf9da1a66ed12536cc3e121caba9652c1b0dab547b4d4146bb1d215edbaefa3399a4f4f1d55146fb226ad7649fa5c4bbbd651066fdcbd7a4a005ab8db306283de74bd953f6091a1d4583e2c312c485888b07458565848702731f511414595501f11285d89403d12d395085445a977db369a054b0a8b06574ee04e3d1f559ded6b3d17aedddcb1583c37522cc0dcbdbb75074909808521eeee368aec239cff0aae268de51dcb57f8b89aa48fdc6deccc2ad2c5d6095793dc515e6c8090ab51a83716634ff816c32f9cff8a10ce7fa508e7bff221063d5c1d67a2686fe0be16b7c005f7e36ec6dd8334794ac645e72eff0ddc75bc21003688a494caa496a4a404e0c5dd53302229180ed88e1f71234ebd8883e0423c88f3701fdec337f01daea3470ef70be0a822c49c3048806bc3e587194222dcdd35f012a3c2532e264fb9923ce57279ca9582a75c4edc3d478a88059e221a81a788bcf0145106dc9d071a25784aca124f4939e229294472dc40e140915d50472cf52045851162f057b85025642977da04091db9228915a8ab3024c918baa3802be470a002f93163078b112234c7e7ee4b5c8c0c98f9a1c20eca63c5776c98c381919318aec2ce034a0801171f4c4841c916d9b1610e15452d18010b4d4049498640918690231d8e40b8d1e353c85902a578511842766c988312a3884e9008f87802062b34751934f91cc15386112cf4283ab34151103c720c7965227278aa68871d1ae0d0c1cad500857a91016cb879f1137462e070161e243f4414b8d1e35758f0438b142fd239da932222d082672a7932c4415095177e8830668c1d7722c56ff4b8bb8d36eebe83fa0203125374f122ec01b0c03b0a405440e6ee9efa826883281b08e36eb3b4b1b4e18ff96d96969f24ed9ee53c1b4b91b4b7afe5bf760ceb2cbf286e8cb33b6a0c0c15668c5a4de9bfe86db9de88dc51267047952063eb8e22014a0c8d1ac12d6bee281184164485608342bf3f831713a18218321b411c2a8c0380c0016ce47c8893f47f511358778c1cdd511f083ffccb24d66fe117ce75f4637d69b116bd13ce85b14cff17d9ae7d7d88c16fe17c9976c6642862a317695514cba2f02f933bca033377549ada677714181db00e407d015a126f7794175db88776c60177c78192e3236825bf2edd515cb8a3b6b8362b5677d4123c51781dd40678e2a0bcb88f2a8aacb36314da5d6b9fa08401c5003e1654515a4853415d7439a80141628260accf64102b08472d854246a1a57397f2caa8294f4dcabbd194f7937a52c6fe9c7ad2060aed42b0d227b8d7c2fa4c3e09d24f504e54907252270aa59e130638619372f2038d3971c1492b8555ea8414ac4e68536b2246aac91b2ffc307c5951cc841ff4dafcb0dbb6759f8fcaf3649ff77d54db9d63e58751fd355a7ade87855e1fbdabc6d524da65bceb6ceb729cd112e74a9cfba2c882569f4b1d5aec54b5a63859fe6b939674cfdfb6ae6415df849509b43e997fef1f8a1d3942751d2dad61da7db4eafa351a3e6def9eb2672c6f75834fea6bf3eb9285f7e327592ee3f1c78bcbfa9f8c769a8e611dbfe79f7edbd2854f5ffb7d9f3f9f996a2544bbcf7f7b226b92a69fcb5d35cd6d5dc94a75cfaeab9f9512ed40bb51a8d4bae8b5b42cdf8725cbf76111af12de9476391aed2e26cb55c6abad9ff651b2ac9f7f04edd6f8daaa0dc35bc91aeb436a860f23f0300326421defbaba302542162baec2cfb409c422923290bcb94f62243720d1924212ae52218ce120a659b080a910429fcd5247e87c1fcd189632b285c54a1939725a0b9f24eb532cfcdfb9a1fcb06ee76aaeb0c76048f1f6e9b05ea968177ecfad59da69b23ee914083505420922c6e42c00b701ac34112b0020048047009c7a17eb3acef47fcdab7d9e51efbf468980e1d45b525a5a725123468c0041a48bfb2cff4a496a080c435a48113172d7e3ec56900812771fe229223cdc8ef6c5d9ec7505675b67fcf48118140a14281fd0f9c0531fa8f1502c719512fc00e883221ffcd0b3940775dc3de5c111290f6070a71ef5b26cf65fd3ffb554076b3ad852d2e82afc8cbfee568a43fcfa3a40a25e0746521da43ac0911a2202779ffc24cea48a76d433f236e73ab4c4863f1b41dc909e21448604a1de6c45531cd4a1f888140739a438b839f55a1c3ce14088536f45e9aaa4799e66724769a1810c6481c515743070010b585105aa02ee280ab8a3a870474de0c7faeabed9278ab56bd1206a94910af20137a25fd3fd217e1dd9ea502a480d30a482e454905b2ac8476d9546c3cf828596d10135853b4a02ee2829dc51114039209552c3dd6d7588a260abb40ff16b878c542a4967ab347adf058a02de21be5a4c4d3a2edaba6f977a44b95b93f0be96123dfd8b6e7bcb254c7c54aa1a26819660771533d6f7495c69a54ef540e33af74da91e425dee9b48a05f78f4644e96fb29dfece2ef06e6fa9ae672bd9676edaee437bd530df7e86b6d4fa3556e4fbfccb24fedf0e2eea91d5adcfdbe16fcb928b50328a8dba804f447d5510a15a2110000000000b31000203824180d06c3e190583ea4d7011480015fa05ea85c48168949948314528618430002000006000000060c05f108e2a2c0fa27e23275c813160f8790eaae5683035ac3f1ed0a5520b38cd1166dd7a4b61edd93012995edb2959fed232dc876b7e49c82be6346161d5018ed6f17dd281dd657835718650693cba7b933284500ab1539dfb965a99e23433dc0970e292bb4b9483a6ceddcc954c0fae876e61e5118334f80083f464fec9364bf3a59ca49a8ccbd231822e84012690a361712798c1a5dce3a0265932a2ee0f608b22e390e99a18288baae0bcd826a09dd5d734bfad65acc3d07bf9862eb968410dbbff638f6dec3397dbd0b39a6845be2a1ab67fb26fdc4a89c6a26736d86a9bf95cbb420abce292799a34e214c5b74e40b2439e2e891a1be9da53523d321eabefbd1806c97aee584cb3ae84a273da6869447fffce0f06a0cf383b01a767d78a7b09f9729f85f904ccd4e4e6d1fdc62fce6b6c50fc2683a4e9d24f94136995347b7cb2e8a6c0a4dbb80cc8cd33444bc6f0eca02a1589ecd23a9b2662643f8ff66cd645d65fdb36eb58179ea71e3b3c2e874673c7850099223811c2d064ce5b1f7936470ade688481cba513e7ad2589b6753a585260c72237d019ae0d9e985575d2690098914311d4bd726c322560410ec4435cf26929536652669707f76322d892972fb32419f44823a641a34e21491cd3a2c81d4cb9955d4beea47fdf8294e34a51afb1aad50030f011a211c4d82b02d209b85a5398993fd0b74151a21ddd2284dfdb657a8372a5fa9d314aca439502e5973388d03c21286db1bce8e78137961471cdb80885842eb3129ae018801de6bda021d41dec2a05074a6057d403ffb27031c7c47c1933dc20180d158157c9ec605c176ef4bfe6df1d93a91d325cafc1d8a6431ae6a3f88dceb70a092d58b59000c437e6af8ff5775b18332b38b262e80619881783948263c234f6b1052d09d7032dbee6d482119135b6559c770be6182a52414f541b8350b331246f61ab4a752562b1579a8afb8c4788f0ce69f1c60a5c14658286e89142b64fe7a97a09602a175ea3a296d1277cfd23c1cdb698879be58ee263d704f0624a37e2f80d5237f42606ad4af1e747b53a99ee2978e14d04773d342b050f7229e22cdd40fee8e219cc9c143ea77fd4eaf26fffaaa5650f28ccf7fa3b53668c31345e9dcba9ce140c96187398c9c8c9ea61f3fad8e85d0214bd09590e57e20611bfdc0fdb09aa974c8c2c39839d7c9fb19a033a6bab3fab5087241c2725ce3ff3709f2746954a71b6a58fa3d78e12d354cc520f0a75740095861af0e1f167573365efdcf3577376e2273508035a4c11d162bc265131c89e23b13ec4a3bdccf99c2b43f5b560fe34840bb1b70d2e21e134a98f3440a72c7a6e0cc9e25f98992454364397f420c1783e19382eb24496328aadc98cf437d5e678a2f28ebd1ca5296ceee181d6e191ecb3c34b54259cd2576b62b04ba031f7ab277b572f2daf5c89e4e3932003777d227748c1abb43edda9bd7367fa4c885a210db124138552e000b8d8e748ced43eede885849c7de21e23ff0d65856ada89f3ec1407ed72c5107331b8a505c8e4694b302dbff7ed0e5870354033159264866fee9c4f0eb75a2d6e96736f88c4d6e2a925b67f70495a02e9c0f8dea12d6860d9fefd203f6e12825a0ba8bbfabd5569e7bb71ba6e627d8e3eabd041dd733da2014b68fc9f09dc7c0ca1ad366db80e0c42c60d56135659f3b175784c6c8b6ec711d013747030c548fd0dc6489aa9059fd065853352d69a101c11de88527b469b7b4a9b7c2b5517f3432da0051ca5e1f2cf59703833c651624d6cd888b8b4b953427292b0bfda063a13c6f9db9df596b7ec440bc640b5b1f4d1aae20ac7dc7625264ee433410d3bc24200b277263f0fd94a9d9e1c51c7c0eafb2ca2eb6b73c6d053a1710856230089656cb10b6033760c2acf7feeed160c6ac0ad2dcf01b00e757e751b972a814da1ca3d42c5d2a0e5f0ff8be16e92cc79315615f28e5e864a53ae87099f18f9b6732a42a50b35ecb270d18fffceeed261c34648c45eb7734cc0e82589a3861f8403ad251527ce89cb00558f8b9a9ba39f94ae2e9e711491f51d1abd404a7468ead47a8f47985b51fe5cc44355ac9ef74e1126b6aae28f4d5e421af62dbb8a093e37e37ce1e2858d35809490c4e09c257ccd51ebd56f8a6bdcb116c7cc7c1e4b037a74ceb2a64244674c0fb74b9f794dffdd0da42f2713e01678e78a0056088d25247bcd4e7fe4f0ca95f3b0c84546f1031072cdd354a6947103e04460dd78d371e2bd355f0eaf1d86199520fe98dd97fa4eec0aa40a739407fef4a0b4dd46a4f64b44f004cdb4e98984c907c1c344a0e039f0bc2fa07a90d93e87381cf2e8f77bd70fdcb9838a5d72843fc49ed5b5cc640f380088c796bf3e876cb434902e21d31fb5fef6dc5e6865af4430df0f16ec043424817510d258b44b0b33e57a9fb4f69155d8a8734002e53516a24f0654f94391f6702dac5d5a4e3d8e7fd0d70d06a717200982d37266d555fbf68e6fca3306d61e975ad7b6c55a8c6c3e69f56b2e3f5974c55dbeb6ac9d9a1c0037daaae69f6bbdecc1b3b894c9657528f9b04972822d0401473cdd07290f0bf4054653481f19f4a159c8c7fe463ff88f8c3821860d5c0ec440a2542d160e903089e176399f58a5d724d6c0ee8affedf4446578868a90b57b40d4e330444f3e9a4246ac208e5fc1b81a03e0929030701d9bc7b66e020711d67a4345fc427613ed676c87ec6208723f525ad30aee001844b29643f4c210b4d401a8d08655adb1439b4e0713a2414228b936d000ab14290041bfcf44d80a427442e86299533039bc85463dd7284e07936dad702959a1ef8f947d0e130e9f1edf98863c9ff92717f2ec3673255ac224062d8dab88350fcef16f6ceba41b755d2940a8a142950ee48c46482094600730cb5fe6dc8875de64a3c5b71dab8056c05cf849475c54783b0e85e72917a8e6900f3f8d0a8143de95cba56d347541714799fc87f8b72fcbe3eb78119337fb10a4455f21d8a799e984bfd2f14f755c6ad73e4438e2ef12d0ddda699d72586af11bd9618fef575edc24f31dfeb8ac6659e30900fb209330b38ce1df81936c59d4dd911c3a025d1af3b1ba827ce1104b0e42563652ba0eba2a0ef8546029b00ec1dc9a99b687ff034e858dacccb484eb59fd23780fe35830e8112596b53ff4a648cbaaef992c08a2f3efd4f063745f80c163ab5af697dc54fe37d440d6a85a6c0643ab2352cad8067ce0eb59bd315ce151a66a29438202deb6bc954637c104f51ac074cea2e54005773be881329ee89f7e89761ebedafcc2ec990fc1a4b6cc418640fa4fc8668f7a50962b77dd35bdb62977048baef33a460eec8f595485e0f0d00582d1791a04950e807d1b30a85f67e3eaf2513f8a2ecd47dbaf97e67b885265756108f60dbac7f4e884589a5acf61bec81a564d70814a588bb2eccf4dfed4d7fd864754e79edf85695f24615a0cee3d173fe88f106a79b0102c61f64f12235af9208f28d70c33164c125b06da0dcc518b321158baa54248e7145545ea242e3a91428bc1b986eca7be150cc5aaaaccdad116d8478190d60078807b12ddcdeddc3eab1db015fb92039cf98e97d0acf3c30aaeb7834f66ebe946749bc0708900245fd7e9d942beeefddcb0747b4abab0b4a0ce2eabeaac9bb8ac74bab2391c8a68777d9b03a445fc5c5c53f0a8fc2973f36da5989991ab77b00021cfd1388cfdfd6cddf7ee9381e0671c0dca0c589676033210bbd8b226ef87ecba77ced76fec30829251f1219dd48033ffd24723cf8008dbf55defcc40110da51b49f268af47fceb74aa710b72684605b345ec419d67d170a1d4138e0b774fdb433364b0f73be8611ab24d043e01b6fbdfc53ec748939b009e4fd55e8af645b16006e6027d17a7ec32ff278279be74c9c47e0c27a3bee42f5c810e6db9fede02f0677e6fa9f69221d6246d25171c1557740bdce34f82663d472d405382078faa5536ad1eaf45b42f509b407e639ad7c103a57c39c17c06cfea7e423c44270fab6cc40052fb94b1dbcd82826b96e97840f9c7f6409006bfa61051a1f9c877f1490a17bdf2832a418253189bf8e6b84447141079b6254c272bbc6ddc7371a134099654676de08627bcd9dea5aa6773c02cfbca8940ea8ac86506ba2d51b884dcb7f846633058870f0bfdfe855d60ceba0bd79f92e7722ba377a05029e37cb551e3b8c1fba4b677802f882511bf13d1d05dba732f55e9bef53781e564b3714cded0dcf821ead4578d66ebc8dadf74d195fc84eb0b10fdea6be4057b0cd5e2aa7573190b417f74b354d116690c5fb68c732c6a6a8dec755bcee6f36674a619660aba1d4505f5229711dd20ed12041a7373b035bdcb4d2c5cf68fd4f341bc2757d33822ae73a0418c1d6a1285fc45570e83fd7bcfcc0c320fde5c83507074e31fdf2e5e638b54163f1067024910530466b87edd33fcf7042bc7b8a941a09b641891f042844a2ae526d04f85f3edfd1df48563b093a9c62b561a7af2e8c7144b540cd86717d363909d4e1562fcfd0cdbc97f9d34c84bf9e34f32afd9c1a89d789e0bb45114aebf2f3c30d79a472e930a26b183e295b1fffeb6a770e72f1cbe324e385603a0b378b0dc929375bde0a182675b1e94edffb4dc996d9e474283cbeb7a6d306e47723b990f002ae4d2e18b82018bfdc2ae841538c7e80d3a3459da2f824aa7b4506c4bd0f7d4c4ce1198297ec722e9fc9714463c0036a3801961a29e2614145bd010b60a8e2cae395161430872a7f552f7e2e931f4ce11ae6ee7de44f746c2e0ec5c2c49456cf1899c7decd7fe76635188d902209bc294924b1d62601095b46bb91a12cdaa1a9f927ea35994ba956e266caba92988c7c3e909a05e5d0cafe295782f96394ce4cfee5270e0887ec6e8987856228cbd1bedec55ea4131e021aaa5ecfd2958e99dad067335b40b47159607aba2f4fe8f75426fc1ea0b953cbf44b707e96c72f88b6c945fa34afcc66d4a6dc695b985eec0dd083bfd13b224baf1a11478c25d5e805001662ffb7c0fba330fec56e5a3fb88e2e2bba51353ab215367160c0ec6c76bb9c4eabe1e1566d5791de6f526bd7c69abdbc3646bf169c38e1e9e175d81196cda8bfbf195d46c20c3e756043b9d5dfbf5548c03aa494a102c3ba50e4105147b86c100573846248fe81b09ea7c23fa4aafe6a6f684a807a9c3c9f9089d2bc97069929868354834f2c06e9983ec01d0bd763b3ad084e6bb496229bf15efa85ba33eeb8f74835c9b5bb993c4e08ffca2219e82492aa400d0c7c7d2f707e4df9b78d348c5034c1d1ad7729dbf6d8af854f6fb823f19ae563e4a4f5fb496ad02e01a41192bfc1e918c0835b3a93f0b8112e266bb25a732a11ec1119cb2be38f20f06e16064226cbad63527387d140c210fc5d4f7a84d4c5b8cce4c8f0309f6d2fc31b0672864ddad848c9831183252013720289aefb5080528d0d81fdc52bd24956164d980992d7e6b75018ca17098ab12758ec61b386b68764e5304fa7b0259046a8c820dc1f59f6699ea0a2d5ab5a9aa04f6c1d108d62f6e787f48363672e96fd6618e2f5a51231d47f0970b180d7a6b9c5a4243f89ca3877ec40d118e6f6f8ae294caa33f0e099506c3138c1945bf0248e5abbc28403c79fa82d1460f48b2603a55befff5d867d1dbb24af65833123f734bf614d5b0a372df72182fff2cd2acbb77d5e40b62f8fb5b21c833f7d2525d516f23abec2f98067fbab67d3eb954986411c2d69f6da3843aa6ea2eb8627b9505746bfe8cc800712bd9677caf1c87d5f014b25292fe1cc29ef4fdf89b6dfa36bf044ed3743eaea6024fb3ccbf1ed188a6668c2d0b19bddeb0199d9502abb7a250ee42f9a6d5a246cfe3a88d42ec80ee176b5822a5b22a8aeb1e8fe6087c32926b4d59bd7c77034cc0f1dd9af431e6698ee06d8793a96be0771b96e745418a191813f34340c2881f62bcb20047b0fd1d17d04e3ff34aa0c0799a54ca71dc5ac4cf0bf809c5ff51fda1fe3703eff909043bc0e035d839affb59833258ddc7d84e3d7666e938edc3909c7af4d67ef95795cca6968d78430df1b7b74cc8f36bcd2f9e4d49be72029469ade82d9638c5beffadcb0b7e3cf0eb6ac05f76fca29ba39a174793acfa68a97f8bc359dec3812caf6432087501d1a3876978707280dc2bff96090972b0501676b97fc5b3f1f7a98a794a9242cfe3c439e82fdd74178b1c2561ed5a0dea3d134bc19f2380a1bc84575a7bb12dcce17907108490e52d06bd97bea11965adb6bedc03a88f2c41e78aa788ce77a992f7ef2f3e998eb0367f88896bfa5c51f1d4f573683d7e4531ecd7d8ec395c31fbfdef6916206921fa8b892b45a35e9c4be5db2b72c7c7a5940c318c91586179f0df70c43197c0efbc86483e73f71358b3f1d42fc84152f86147c606f1b2aaeb581eae1446be3df8b115975d442303038d7c710ba4d21b480cabf5f102027b6fca72958587bc3e0933b2d35971a5ef33168e36ec887a43e23d941be69768eb2f14abfd2bef11b135baa5bad7c64e81668bd44f6e43a2d7600250f5b161675f0e2539684ea994bdc84d924c3829c8c0ff90d7c613abb7756e8f91644bd25784c91d6747cd9cd06ef5d2c9181ca07d188e4cb4c8dbcb473bee5f1052a0d6a3788fd72b56167cd0cb9ce8310803434b281050ef6570f80d0afafe5979804392ff7da79307f7c20b25855a064fd5bc84a097e872283542eee33242da0eb9f3f83d049d6b188e5eba810f3629a611c03afdba705777d756d36f3aa6ba8dfbc801d1f8071f8432fa0b6a07ebb923d64b68cb5f3b04ed3c3459edfcc3e7eb0eabbe8b0109ff2e46606e621b531fd781de588436f6fcabb81fd56bdc8bf887d7e1b7dbfb1643f481a683070b611e38558d6577ed38c25719d3c077c24ae7cd93e680c08cd947455cdd763d34706f7383209c884e2bde6103d6f76e0df72e8913bf88f34caaaa1c470d88be6e7119fd8c44889e19f70bfc943be8f7868bebf63fe3ef9b62f1d5e0c1d468a4a75a7df57cede7ad85d1afaf44f49284846d50709f6615e504460b48d720c419709a7a3951b422a2a20072d6459eceb07b489c3b8a5c8a414a87d6bce0e5db95d0f0db7c945db5f94305cdb7e7ac22044718daaf8a1cfada48ccdadd6746061cd9e4892d3e3a43544911f0addafbb0f0d904338bcc2f34236d8d27ab32bea5fda8b84522c502bb4d89b1e59f8563bada56ff3ee6b17ebf3fffdb1779e90767b408d20a9fca41e1c9dedb0b9c569f67b66159df538a7dc5245dd00bddf78cd7bf83e1d1d524a6fe95b14c947e1ffc2d64fafc1cad76f3a23cafe0c763ae8c4bdbe2b0ee694e7e85027d187477f8edf0ba68544cd65c303077dc1e23d64d0385060b08443dd1fd0e2303ee6fe9a0f0e2599cedb750592923419809c41149168599365fe164fbe1e8fea7c746a5636d7234def88f710afa4b9122d22b677ce590836f67ed7f399810ebf794c0ff7a40636ffc3807f0bf2296cfa1506f65a989d2e6827d1b7bfeacacd3bb2d4f67190171518e8065e6811de2c990d9c20440060b945bb1404c3d1e7606c4b26a77dd0222be965352d1c84bcb0078bd78a5e5b454178da413f7e7079d3d9da25b8e7348f5dd286c9bd5051b04c95633e2bfc7e7a61292854d7d17031377821ee2b501a6afd35b299232484d9e2b45028ebc314612fc60d40385fb8020f2c11f1e64d3e430c300537954a4d52f7e072633b5dbe0436aadc6f074013f0969a96834b54c3e65647694d433051bc1cc3d3c5f1570a21e180c1ebbe5ab926cdfb68cde6720cb6e6fa59746b4739570021e7e8b513ba3289599da75bffd44654ab87ece2315fce28a5482460a3ee7e2039d0a82f622e669adfd2599300a7938036e129c94be08d6670e6c619e820d646fec001956b39301edb85f286484c92e2b8c3d30260a873ae0b6bf266976c3db25d010050f40563a305fe3396c19e9364a9d890f4d3b0f2c88482be8a405b5b95519fc76976c45be1d945bd821f9b8e1121b71d68f8f7a6e338934e69296d2d82a086e7032f59ecafe67774e91810f65d073f021ac240d36272812bdcc0438384a6c7e7d304b0b8f6b1e8a0f4a39385564edd7055d4704c6e64d26d9d6b6057c59d4195452f9badad82654ed49718eef8de4be81e54865ed62e6e61464f0acf8d6f3f6533c9bf03543ef072434b847bf97a529565eb290815273b6f86fa0cdb30cd10821601ff3987840bbe3ee7c0fc21ef78fbfb1d2ad2b97707018e637cccf1050e33641fc78ebbf90d7945c36ff5864f8f2b82df28673c76e8a49fcd4fee4f85eb1064dcca7e0f26f4d9d91af1fad3cbf37132b5c549ba1510178e1c059efb6619cb45598847b77278c0bbfba45f5ad9d71c9ec3f06007ef279d5eb7bb832fc174a97aa14cccb89b0f0b512459ac82b761c57f2586d1f8198b19c5f518e40bdb9c9244d4881bde83cc26041c138d11992b3e3b275fb14fc767d6860093c37d8a9d6597d9dbe9d019622f52ac08e99390414a4098c26c5fbcceca250799e14ea0e4be621d2ec828f5b54ba54058a1ef1b7f44af3e955a1397d061eccb53348e9ebcb167cc2e06a6c115a2da0b0f55f14e8bca5a1656a34d8c452c15515caece90bd8c4d764bab380aa535ae93c3304f0e2f13d7302022650bae99a23199081a828deb5955738667c3bbdae78606dbf9d96dc2752589112dbe8c594665b900969fd08c158b70a83a08d0dc9f490d62bc0e5b5101e2f32bbd23313556e0e5d7f9fbbc275d043b9a837d3fdb7215a2793aaf465cd57aecdda38c3154e30ce6ec10d700e8dcbe815574f08a79d85ec78bb5e8a0269b12c2a4055c09dfeb1ebc1172d320141021cd8b14d5aee843169d43834c823be65e8d74b98a2feaaebfb0f55b5b35e284f4a93a6e1900f3d234ce1c71f66a38e0bd22d0d35fcacdc61171d714c72d91b44cae9030001c6b06beb0192aa22a44c58bd4b2c9fb308bf934ec8b84221ea075965c05cda31d151aa2e209106bde6eca4ab4cf6c787532500fcfce9ae6be96f59732a591cd4c8ac06c40b996633a68ef3da75e65bb36a93cee1e4a782c5cd06d7fa9c5cfe0516773dbbd3dac82f992e1c86ef0fe93883ca29c7c19e1c0f754859e388019acde6b3d722a9103714d16dc62a61d76bb583c44e88f57e762c9bc7537dacb0a6ee92bfc1a41952d839b0f45ce8ed0575bcaaffadbc17fa8ba6aeb35c5c8b8dc8c21d00464bffdfafc6952e64f4322fb26a860dab46838be5086b5776c55190affe7569f0f6c0513ffce22fc465c5b1ef854c729af3d73ebab37fd8afcee052e23a3c392b5e5c86c58398e6cd22673bd998fb7808613c15c76e474cb305dfbc10bd37e8314e9bf5fbcab1e589e7cb2aa58a978eff824ebdfacaaf05cc7ae5b663d6fdd89d465509b57f6147b9349fa78ab19dd0f89d8e185c39163e6146c1f3a3306dfbe80a7cc397a46ca9824748e00211de1883a691dd73cb106001b543ae4cc5125b9e2e0e538c728dd8443fab97d2bfa8f0ea938186f3c1510a7c36b252efb185cddd7aa4f726a7cc32eb439f22a0ab5adb910e385f5663447f3d7df06a6ab5222072c69adb29068ebad421b77ca5deb907fba9dfc6295c29880675e1d822da0446d36f1c1cb8edc7dba7f0b4e7bedc9869f70ff8f8ce05455d9393e4a2420dfa805ef799b40f69d752afa17df2204b418a09796ccf8805926acdf182765291b99d58f762e8b67aa04d84760cb3f9bee751047ab497161dc3dea34fc8cf134874095d742b5bbc5234bc58a55d239c33cf3c71d1c13b2fd2c08cd52ace8ddb94cc076b46a76afcae7582228383dcfb49ef8133bbe010d528149346ab7aed2894f74bf06cd6f63b173d055c6651086b33a67163c675f44fd0839fc7cd6a12cb835d3a7706cc7c3cdb6c7c44e394cdaade6b84d17ed918deb4dd5fcc374126dcfb2b03795bdf6508264d56ff454ae42dbe79147b5726a6e7d61555741dc29f6b32dad814bffd01c5ab6c4e1ece03c8b9c6d28c3503db3e1ba4c521c5e87c7469911682530e2b7db0b39bfd82bb71a667aa4ce31b1e3abcd7fff1c72b37b9e47b45d11beb8bc4303887f06fcc1a76c98910e34787b5bd21720bedc44f76bad2de2b17b73456e108cee146e1fa9edb777c6527bb0c0ebb91ea94e6e386fd1240338fb2eb734e78122bae58e1ced1bfe9438c71dfe635c4b48e69d04584e70b0f921b392e7166d1408cb4a556c6a7588bec890ad5eb3b88e4f5e87f6015b717ec6dd815a369ce863e0b3ab65f4861f5206f75ddfc8fe48382d1abed6e8831d919227372c8c3788998b44da61b66cbeb6b568070c6aa09b34b1a2582898be304869431982fd42b1caf60fdae4e6c12e1ff2031499d076f5fc4ad9df48c9293ccec5356dd92ccc03cbf9794c6cc7239b810a46082477bee5a83a962c0301ae7061512f73d52d77f2c238763ab4698752a51e179a9648e038aaae70562098d31df9cc868ba6aa11bbdb9e9984a571666002691433214053fec7bcb755a214d5f4b696a5fb7adfa47778c01aebdedf252197af2d5da38fd09f27b6861a3620951d7601a24a11081284dd9aad9caa85c5b971bf62dd100556a3e932473d3cf059fcefdd34d437a3b5aa194d5f18b40855b83a25a35473b38c24b1eb6acf897eef5872bad2f0f5bd8bb58f0d500c6f8ef76f11f17b93254144fb269fd43e772834bb8c6ccf019b08ed47f43b9427e1ca0fb19d27bb61b84203ba5326dd260f5864864fa9e4ca4800ce2c054e0404f6865eda585161a61e613e9531c740fb0351a21c6b5208c7db2f4bdbbf9622e394bf9e13384f318dfa8a9a9f8975d52d58f89a72020fa17d7d8fa4f85ec2c3d4c8fd207986c93ecdd3ac8b388d729d30babccde8dd02d5860e3af18a134edee12f100d4709d51b6a1ec9b4cfa324f849fc3e526c74452bf0ebd4a7efe91e3221ec0127d22302ca9d37f18df55fe928686b2918fc43c9d6a4d452e93d3dca588cf68a45a398b3b2e31c05de3cc55018d32822112822c195e63acb99f956f86ab00db82d149e119b836243080cdc7e40817cc9f3df4a5a0d540a7d8e2796075dff1a820a13f05b41c1fb8b7705bbbdee6be35720b276b65e53a51497272f1e9cd5b98339704ea0544819f8ca558b121bc16b42e2b8864d25a7013767f6b5d9ebfc609d33628290202240e982768cdc9f734ec702b90a5b5187bccbb1de0d1905ef282806205e90a67a01f5eacf3ef302b285773cdaa0d08f5dd7873d85666562e99128f294d9cf159bc55c9c8a28ed57e2261ab13a5023234b411babc1ae500820f1084cd12317b7df80101b547873caaf06cbee836d10ed8e44aec3598fe126d1b7e65c70c121741ec35075d48f2d80ff84ea1753725385864af4ba969fad521ebf30335a3e38cb82710e42655470ad2710ad1ad280cddf5b40f26f01c4c7f49d1b64a5933e8fe880dc3710fd613035a3290196cbe3b8bf8ea5add0b42b10c13ba45a5fcb2f700ac6ecb2c50d97e6af9f4d76f16eb1e50fc6511dd700d1c972c35b8e62904a0d0d8f64d31ea2b91e4bac29c7c6735069aaa58a388062748f016a79a8de0fbb035d75cb9de73a6b6ed58c5c947df8836d05e5c0d511a82872c046fd4761d1d1e83bc0381be0e18d00936d29369f9b5938a92201604a92be1079f8e91e7fcf7cc0b4565222e161fd3c93defc85fa7816d5851258dda5592da5283eccfe96fce10d318f520cf7731f43930531437d3719424a96c95a620f994280896b61106cfc3e7e3baab02df2c7cfc483a84da47e73e15c39d0e004d755ecc4c71cd50b169ae37f3bdf0a6982498260983f5a301136bfe63dc4874acac3cb626cb3a83abfd07897e5062b1b1828d00377d086185eec624f3cf2819079a31186062b125274ed89a9ba6be6e8537cfb51cad84c3161a08b38a5f5d7e700c1a103ea2f4f46f2e1958bf554a9ddfd811bf375ba1da29350184d433776c12ed2fe43ca160f46b87aa02f4978d89fb58342d0eaf82b5c452baad8de6a2f70e84daee54b27e1ab6aa19144c4079c5c2f971221bc087ddd8fdd2f7abc589fec97c63916a1b7ec6ce359bd10b7ddda10d52e1941a2d314f3fc21fbb6cb358bd6b527a23a10349b697e7ea5840e4ad22e75249d0829ee8249e375b53f611320cee8ab3be62723f6f666462405f61635fa088bc299fbbfdb67af0a941a1ba0879ddd5f24e0832dd27f4d37a2f3565b0f744ab9ec6923f0a35783636a43bc0a7a5b3aceb60b279982e67b2b43fce5c3794a88749fcb8001c0d9c068f683355569aad090b477bb08d242bd219b07b8f301f7f328d3dafd750a9ec178a10ed50a234f203551934f96d9d82203b887a1a531dfd5021ed8aa264696683c2442dc06e640f411e99aeb25dfce6883e4b20e66a12848533f9e4c179e6c667b1be8cb7a68ea137e9622c56c666f08a0e78f32045c7d239feb2ae61858333402d2600b2846e4ab26b42940cf9a3b815c12f56cca8b41f172db2c0128ff80d9d5772315768bcff19af2c3ada97776a68d70b21c6b972006f8ded1e71b6dc17c2598a786b53dabc99ed86f271dfb80ef630d8697c0c37179145f2dd851a398cefea9703e68146662d8844cd9d1540a4fb9acd052cf16c56994ac9e9c3cb4097759579fd3e056a9f03c8d7655363896aa38bfe72598aee468bfd1c9f5b4c10ea189f2dbc33f7c80fd45e545e1b9736c8a047f2700552e0eff2314c766cb61972041178643f4e23d5c96fd9231205d38e3480f56d13c19c1e2f743e0cbac20d3f2576f8d3793040aeb255de3662047805975363c6918ba5f8e3c80757bb282b123dfa51efd8d7b43c5e5dc7258c9bba0f7749b2ad5af67448a23615b0dcd43c559ea7e74b367f23dd719f7bbe3fa9ec5b2a4a82af9b36608f8e9122e362b612abfc6a5793652aebf021c118145c12a7029649743ba3f2a21a80df5a52a9215f584d194fc777bf9d389f579bb0e06a3ee8b99a78b36a5f15c385ceaa92189261e177c8eca89489edffac2281bca61bb285fb7c867c75a7458a331c9af4465541be1873ebaac4197b89655e0f669f6018c421dd2e5c6cadc2b0a7d6e657b3c5e65a95ffc87d330cafc42f01ef82663b234e08e5c541ce1cca0e17b861bd7a743f4e68330b88444512744385d6870e6d2c0311dc257dee346ff47e7560ce98643ca316f5deb3af37d85f930bbb0ac671cfdafa36ed79da4edaf522e0464e7fc9b5547ab22b1423453a55406c02326c3ae636db885b40ecf4ca736d4b09984d75077e1f1345c48dc710eb40f842c8c260f318ead01c588e62cc310acd1088aa687716fc163773cb298f124133aaa4023463f0cd52d2eb7f323541877ce48cc03f075fa2591790da009d9975f94af8a3ae6f123fe09acf17892fea827c4bf06eee9cfcd1a76c446bad204bd3a66f8f26f9430ffbea5bfdfe0bbadcd676e98dd58b65576fbd57b4efe91efc20fbf773197bcd6a68f2f9edc33001ee6bacbe7ea6fecbbd36f3faffe35607d212a5fa8d6903962b5c3456c1f95f4bb285affc71a3739acfdf56293349b782752e69b2a6f0914c8835ef6bf7da85b4467e2e712ae674d5b021e0d2157bf7c6f2f29ebe957994f41ea2f12d3eafad46e31f72fa7f0a53994e96b2da8f49bc144e1e602cf07defbdef28211aeb6a16bc2bdcec07c5a3c06ee39a07dcac45d231e0c7406e1c3d5c94f55fe2947ffe0ff80049d03b24f147df9db9f6e871b027e139339efbfec12e960af07e90f257e142a33dd419776835c79a3f6bf188f20c79e303bf6074ffc1b5721d8814fd35d291e103b4bd2d1274e9a5fb8e34afc837a889985b76c1db0f63a3cf30eaad9df2f0fee218ab28175e53df0edf16b4310749dec874e288f5dcd8c47d81b2b7c7f7dd3b236deee72dbe50ff6ce4d6f7e05ae3eb9fcb4f0bbfa74fd8187385fee7cb829f883badfafdf0a27fb2af89afd045efa6eb072c4f8a8fc90560176a934dfe989b520b37f8fd5eb05e386da7f31a8ad256cfb0bef06f9fcc31943fff7b6418936c4301ab38a3f3df88e3febe3985659fa7793e992c1ba53dc53c7764c98a97fedb6b2c7bc8e4c28b679544f455bd99aa6d8867a1cf3baf876ec20eedf4b51bd848d9fdce0228fa3eec80d3ec7b8b20745c408ddefafec8f60d59eb0e8c6db91e75b80502714c93d5da526ea22c010339e3f9f75e7830e3f99d775b08c79da87fdbc3ccc9a72d7c13cbff011a8c942f504671f85ca5631ef43879af78afe1f48235db9ad71e3c6c343f07c3bfbca35f452a904b8516d344c433b9854e6de32fc2161cdee6fc3611e07e5462b14b6a1d75c9907c53ac2b7d8ff42299377f5dca0500c345d7a65fe41c90e9f4dfef90fb3a09832bd2099558e52ba3cb67077e741a0ffb8f96f3706a191e4ee0ed93afab2eb85f407682d0324fba4998baa08a3c30db9adf96d5366fac439dafac65488a173bda2396e0899eb46ccb9a8689f19dcefbe48e50544f3927366dc2dc327fe471ebb031ede7cc1788ae5a8b394cef2cbfbdd3ac608f1741895c6cccd2bfb64f6c94153ea45881a4ec60ec70234bc7be0032f783bb3e05e88977d4371bb8d8f9b1a5a01c8ed7d20357663f2a2fc3f8b05755fb3fc7f28dd840154385883cc606a8cc6c4321ec8bff5643be57d4c24309d55bbb7e3f36d943194dd430874266be3b33bd2f463fac2ca496aac392ab011fe6371ebc37c46a8d072ba49e6a6489e05813f5c90703a2a6bb7867feef1668dbddaeac7582ab7ff09bcf1382e2c4ec5fec1a89dc99156fcaa7258e8866958450bcfb3e95e7db658450c3aacb1c3cc30e68c7f1bc1abdc8e5f10ce50d8ed7ec1a8d6e734c9dee3b1950d74c832191d3dd1dc3a0cc01e006616d6741405b0aa9f6243af1a99c2980fd7ed6e64b7c582d338118115cb9523d0b6bc4b6ddce86a262bfce063ef9ba418e6ac387130cacd1e17a298948699c05e675a783c62dba8b321880bd34f80baf5787956f95d1578d100cddfc0ae14fcdff406e4de216bfaef55e72a21b7bb42408e44b06277493843fc4c9403c476c90717c41cb6b8ef53791f19e4e664d750bfb84c4a229bdfd60c227a8ab2c147c26e600222885df06ac46cb27b039dd5ed162e727e65f785be873cb35aaf867ff6eda1a8fd5c01bcdc8d100b174ae08ed6e21c7cf8a8549a64ead3a0195bc0fbda4547da2c6fe66af80bf113aad814b1cf6dfae1e8c7ab59cd2b3047cf7a9ced69d061dd56f34affbb39207e5ccd53d62da621ad723f4e2f528059111d40dac226b10c9a78818501c2e6d11b069fd01b54d95d1fd5c617e366e4ef5a9e5a446888f454b6b39ee005f27a25ef61cc2f333c47ac5b2c27be4e766390518f4aec1f1e267e3c913c341cd6d1c92b70d3b6f3d9cfa9f056c484f74cbfe109d2f0cb7411e85a52fbdebc10550ebdb6faa05ce227179e87d4c6e4e37594233836bd39f6bbfdc96cb955bb66ecc03500f1e1ad8f857fcecc2ebf23a2dbaa06341176e014937d11523793d038349301c173f4c93d01a03880a0bbc5762b9572fffb0c38414d85879d7f8d3f7089e5c3f03899deead317c57464ef35ccc2c2830ac2156623cc79a7ba348c8b4cf67b78db3ea1f353302f50c40f3263c3d7a6f786787d4132fc5d73f94864f463e0dfa4f817fe0126ac4f47d0136f38b1c34ffc57813f37c8e1a5f4090bde285af4f2ab1bc2c0380525bfdd0839547b92be07905b267d1b142fbbd64b02dd636b725c1798bf96da30dccabbe69bede0788232bf537411496a8df78e21e17be7be3f04642932296fe5bd4dbef218c6f548e624ee603e8a5f2bd586f851b522e426726efc0be1f709eb15b684a5a1f7c106cd078681c98332f3836f742b7dafb2ce01cf2be4de60e2b2a5dc05bf01d8b80cbfb9702478236c44eb81a92d0f8f0f61e100d2a9b75e7ef3564159e30b3ff15eaca17af5f208f403e57a641b73be1c40e5cf0d4e176e7c56d720cf41e7298eb9256911e1db860dd74657cd0c3c8e757bf4a9bb7a7832e52946e03eb29292852cfd9a11e1808770cdf84845835947b42a4fa8d10fee113c98acc01dd4832a99ae76dd492bcccadcf15664ec233cf400b3c6d4f1d5860f9dc137ee68993167efac27f18e5ed44b3b5e019f763aa8adfd8bfd408d33f244f734eaf127b73867ca5c4f94cb5f56069a78b05196f17903f69e5addfc27710edd439a3f79bee5799bd8e25ac17001e4b0f836d108d74d5810ffee07264924e3ae98277e672242108235c902651ea5c08c25ccaea9befab934a1951081b1f7699dc70c6d278dc775c614bfd880d337f4404f44f1ee825c8eb4fc3411011d109edfda2578422a478de3967d010ea2c9d405608da6525bd2171423fa7d7d6f1ce8a4767866184947c06582d7846e93e46d2321bc73acb9354ae3a9d14cf7249b36d4c349690a2863da1f3ae3a41c7ba87655bc299f047edf7313cdfefa7b509c2c4c32cda273e4a1cc793479cdb4cff2016ea9044dbe4fe1a0f25ec2cfdcad8db31a970bd0f99041cee8bd4c0042764a1eae5ee51881b42189b8c5dd70d87c27f3cddc3029fa07871b6386d9e1e9953f420fffcc27781efa4145533cc18b623bc5702d0c0e133bb92af4f0aba1b09d16089be399159efb0980f7adfc2fd71cf2adcdb01a3c83bbe12c885662d0d27ea299973bfc0c0b85014cd1219a540173e02b8d88c0ad8b96a96e9aa8f072bbd20d026f4524c1fd746d6cc1e503e397a37b4b5ad57d3fba4ac2236b46645847519deb301edd0080d4096c60117cef6cd8b9145226a7865fea8a4d52286886383783742fdb15a2f57b7899667421f5612b8db7a91e0d68c4e8b7cb00c227a0f700e35e32948be40f62f6bbcfb9ac244635b3526e272614616f95f03cc7bd4e4a459b0317fdca0d2d3715ecc47944b0ae874c90cf0d03df27c8f60d9c5ce52768eb3bf820518ca32959f248761bbf21d3e6b4e9996f9f605247ed0822381c04a0c05d93b2f5343775eba913cd523e39a4e279043812f11fa0f71076a8d7474f99f79b4db62ad675495f3902cce7f802908f101f7edbcfdca38110033fc97709d7b2edca2aeec54d599cc2a5db86f8e58bd04bec040b8470daf854eeba2fb930571409c9dd7b9fce829f926ebbd7547620819a0f617e3bf3d34aeac88b13fcd4be8182b61e772edec7fd9549aa58bcfe23221cb1428b749f3e05a3edc7807f2493e47bc34d011e2a5f2c37ae624b182db9d67eef0b26cc87d55d926a17fba17b0f9dd14f77caea0ab82af84286a352c324a20b49779aceeb45ed24d8c2e380fc894235b16bf39f6fa321005f37d01f95a4dae8e7ce2132aa1ed7bdd45177c3b412fca272a867793a51f2b736bc6c6927c103727e3eb2081254083d74539d0cb4792e16e16a0f5a7b0533964a31e851c5dd6762f6649419e6fb958d920515c0ea395cf538ec04d42d7e4801f3ebcd3084cf6c89aaa3dbc696cad2561f5647c3ffbfe610d6e0fbce6ddfe6ff99078fb9c77f15afbdb10a6d4f1123b5904151b3e6819364c360555c38442aa7c1c8bc3cc5b8d4853b964dff0e61aac91c20750d557b812b72e6effe347f0c3df8fb4946b53453ddd83b351422d15a938aa6f186b4e0dd3d72deda3254fd3702fa9e68eae4d8db5b460882d45afd5c6fbc74ae44f2dfa05bafa07c35018dd4677593c3c63874e97ddcc716a1bb7c6b0a8d15214ca9ebac63335c97618e398e43f4bf6ce7c1233a550d4d0749eb7a2c7a105eaed15f852317924f147fd41972fee698207bf0383bc322a589ac1313b9a78b4a6193076f7290ea3e36c7132e41597bb555006ce90804e47d264601c8a4014e24e72dd6ae353a51a97d8e19382cf58f42b15ffa637f0e07880fe914027acc4d01ce5b8f24ddf3746ee7d3c4cb07bbcb2f29da8da1f31dfa4f5493604a07557d8bb997ab63e86d51724f72db36df521329d5ae169e36deed193fc0f46d32f535ec57ce0430e8e0742db6c52452cefb3ebc2233001d49d66cb9865b116df77d7c463dc6b094ab979eecbd796adae9c49fd887b8c30d0b4fba428f22a68dcf3ebebce88e08bae18bfd8b6fdf180a10295d590f145c9270e4809ca85dd7f5f089251ec57544cae907765827f904737a92664a285fdfdcfd32dacf0604def890a1bd7ce521ad91453b6b3e8c8658a3618d95503e1846ea38c1fe98a484e26d28716cfd0368af2c9efafb060e44c01a474a7fb969215bdc6ec1cb46f730e21d6c1c5043e51242b439bb89e2398ca2e3acc677c2fd0c04ccec57baefeaa99baed66a78515ade04ebe16865cd89b3b2d21866e3dc2f5f1519f9921914b565021caa1de8a2badf45198b8052b6e99124dd65af8b447dc6c60f29b514244ef69d8af6a3a388e2fa74eb4b789fc178d3057059638217acbed94639b6e20cb68d158b90712a02a2fd1431b13aa2e91bd91811539e636aa9a30b6bc91b60fd472ae6a6480fbeb37bcc6bca1c7c0339b2301f7542f58c7eb1a018f600fc6b4a8c1d9b5de11fb0aa54ec02ed91e1ae055050b5bd0db673255d0df283b4f9a9fce270f4e156de1f8ac78a30c6c8443f67508efff4f3b725e423dfece93d4486d19f9733c2f267e5dae814370bc0cbd0f15fc369c5c8aeb85d7797cd781736d47701ab5399e5308646fc2a0b2a345634ca6b4794fb496e1b9423fab684fd4d49ae41d655a067482f262d81d877b650a9e837a3fdbe3f2ed6479d258a940584ce6ad687a6c258e2c2c0ed8700a67a395600674b469d5b379211e7a8fee7897226ddeb0cb8b0a46ea59534a9a6a8e1756b1be48d81376009d26082c6d884a4d50b8ea8522b1a8ff70691ac704436e53c2114b12e48d51badb0a6cec07a8fb0b79aff247fb450646b08741382fdabe7f9f501d995f0d653a59cd9036bee1816d9d97e8f502d92216ca4a8ee6122422fb841c2b36dd57ee4d7ad40b0828771f0ba00e4780c3a0a05be8b34af7c98d214bdbddd903204773883ab8ad50e3755a68ce4a1ec480e22c4bb24981e2a0a492202f822a70fc7035f1ab51867a40547081a8e0c8a5ccd6e75c32be2b43ff354ff41b222d7a602538374c80dda7add9e8cbf29bdab4d642b9432d1d8213e086c67dabc40b3f5bfdb6c48b16a9478cbaa32cb5c057fa82e5b694eabf140c75a6d22d51aeba918535e5decb7b4d7ee190cf005376f0647ecc41b00c5a30cb20801aeaf2cb06e0249fed8125157c8cb94acf7c9a60c5ac8e475d76b79a7457671631c79c1875622f35c2f2984d583655b90ca8debd2c7359aecd26577598a7727d67e1cbebe3b9f0a5b12d297832d430e58797072f3307f85f564a3e8a621ff03b9d12d3a5752d27dbcd222c09dafc86997677e07d915e8ca6677b73b1fdd318907e780e5fa8ee0363bd8bb28d0a30b4c85693fc905f8c93fe61518495e69d7466e664145c1a5483fa689b0e27ca524a66e96ff8f72007d0b92f4cd7f5cd1a0af56a980a92d93020713323d4e92aa1b32844a5fd7a4515b7f438ace888f24a5528f05cc5cb41a2879e46c5bc05452f43161387dbdd74fdf58c7374475d39b63b6431744099d8969912416cf6fd1530ec76ace5236d28037976f63d49f041eb196673ac2347f31c5528082db84854d4512d94a27788e55522122f1d69bb4bf0252d9a6d5f83ea441b651559419256aad7d21968f39e4c57e9bc609c5a2d34e77724323f46acb0e8cfd67b1656c550c9da21d41b8cacc9ec74023e078b49bcf9440a45a09eb0a46d56cc84306ade04b38909ab3cf6f25cd2da57f14661867a95483ed22021293162d3e4c28696d7b91f2406aa7fe3d07aa64b5095407a1468c3347852d4cbe680370c25583fc7e328a662855cb397c63cb7cb67bc12da0ba97097bc94ab55952244424a665126ecfbdcd85408ab69fdd4191190ab5145f51e550fce49b4c634ad654185030c2845aa4be74700295a5524a02ce356ff169567f3872247c90df47a88be531b98d4ca97006b7e6241a90c3ce15e0a99150349bbff626bc823dea6d2d51bb7457c2ed3b7c00e8fa14acce5c9e7cb41623ff9a8ff96101bb2f31ec9da9237789cedef431d4f9dae4af76c8a98ab5f06b09884d2c60df6fa70f91bc763170c6a6b4ca4443f7148a3278dd43d0f2b123c96ebdafdb494ae2c4807a11bdd614abac40a3ec5a8fe51d8ecf64bc3dc623a29c01c654950efbfd61dbc8bab131cc1a75cc84f0466fa6fbc3d337a536e582e4488f85db87e9d9914dbb60f485c523f2c0278f0bccb6d7fa1fc4d4069825a5fd4698d85061ce223e398b73a71ee858a8c2942109e0ea0b292a6c20548e1328a1ca2cc5fab9ddc5d6fdfdb3ea82f698862fecabde45b030c0994d094d1bb8ce84be6e9007d676df8df455187e8542118a900ad17dc97546d734801c3de11b10a36fae0baa36bfd676e6084a683380eb0ff02540ae0014b347a18116bf982176dc65cc7bcf221d5f806b0ea85b53cc2b79c23caa2ba7e82d4d0af9a844d732240b1d73c29928b555886ab0ee6e06c3bb1968de056a62995c1bac9493d00a877b1c4c21d9f9213e95cb59e50885e3a973520d71e66c543e3cc1af4eacf5d25a2dafd9d1e3b4e389404651abe3d7b01bc1e35f02a069c5f7f9fd907438ef1fd0542bd3be89d16a40765b9a0da9aa4b1d966b2661d69cceb160cc86e9a26398ef270308306d10b58367d0c596b4ff8d4e1c644a6dd133a7034877545f4b4276c392b1a7d1044ddb4f6cfcfe948fa1ffea588647d2ac8163706e47f7593f37711b549c7c0aa118dd563d4d42fa7f7f249b6118b08d0c2d5c6bbf71a4e9f99240d6057a160b27dacb0c445f05f84401c37003c47b3c9a58411b2624372b4a830ff527ba24d7736379a594536d5a888257ea8daabddbe32c337e5bd7182408635715002fdc5a5deb05c39038fde72c03701c387209228841dd3d3e8e89ff09284f65a45d45a83d7b331f38f87990c9933bd8834d27d35e45125ec6aa076a01101e7f7f1c8920d80138984e3f3a2b264e9edee2d554e64d4d2c3e127d084976d8e88e81c93a2171301fdeeee3ca405f94d88958b941655cccc9bc8d559405a0750e658a8adbfc0747f5794884f18a29deb1460efb050878fd4af2b022b76cdcb7ad2a8136f1eae8bdd0f34da2dbdb723c2ca824b8bd29b13612b4e08d158bd4933be323a8ea5fea33f8e4a92012d76916613081697b63d02b1d1c0810d66aa6304197da6e3abdb24212c7f46cc67af481ac07472f8f1b0078c2b260384ab711c1f95dd634d02922301afc77e96ec73d639d675ae8b03f813a3c23501f606d91d0ae30f024cd94b9897087916662b7c45fc02c34d2080172709f33ce1f4f6fd53a03e24a7c8837f58a16f72de100e21fe6466dcda8dbb2d9b5237e20dd630abafbafa4b0ad8bfb5a146f35a10cfce9f02593557d4876b9c64fc91e1e5a4c904489aefd1f5a1e49681c96be6c2997abc0f935e25619f2448e172e73c145d88c8d3f8f79ab46e5c23053a954dccad83544a8d025cebec8467f296d061424ecb64424eddcb15e82e3bbaff240f835a6a699ef35e56d6f3665ba300b68b6e1940631cd44e7f21d433e6436e3cfe0dba5017f1a9b68573f40faa704b37c22987ddf8764f11f01bc4e624d3268aef1f98045e00213e70e4b629b97bb3c20cd1c27526da21a277c1a68d2022444eb3ef3fecbe89cded59c746cb80cc1ccee08f59d8700a96f7fe467076957f0b00dbdbc8cb5653ce8e8da705639280e969a58d67429c873d369f5991f44838e64bd66607e96e7964788377b8c2b16e85de5d5a23edc84ec1a6d3af8ed985c659bd9b63ade5646ade730d56fc0ee72be25697d7f89e5bfe776c585c598f8b66dc2cf3bb55b5e7c5ad290c87a17fb8f5ea924788cb17e29e0079c3da55272dcc3567d6188fd53c3774a2d5e4d34a7cbdc5add21c64e36801903581596845badc59b44c1b2e090aaf86cf81a1d13dc6701b76763104d575f1fe146277c107a7c403921e25c342bee76a7b3a4b1e0c8dc7beb18a9ab8b8b34c2332976646cc1daef04760ab015725380c235df5609eee5f7f16aba1cb3cd6e02ffec70f3d59a0fe502f3c46048685fda042c79fc952241ba4d37faab97a4771bc4ace8ce4dd230093f62753734583b4cd3e702f08268d048e6a164880d7b1ad2b614bd4fba6205fca499e711052ddf8d4ee111b4afcdfe631ea332b07b36196c4dcac1f8638a0e62869185905212c933b2543cebd8c177f0bc1d6f12ff170799b9e8341043bd5ef14aa3b662b98a3f6c3635c241dfbc983c9f1201af203b46a82878303e1a2523b2d510733a400b577b7a13e84afd19893a06e63a57a38141414f510bc2d4d7ef4a36cb4f9d9b4a1cf97e680b082b4df67bdeda8106493a56efa0b79fe50266cf6c3d4a2748a8aa8d3a0fb3728164c23dd4aade807b29dbc3ddc07e14f4579b3183a3fbb719a09ded581aa15bd2cb105a6f1e6a1c8ae2be6808c36d46307be0804048be9ccefe6fa78fbae3e474055f166566e3c9f5e9a629e9205b5bba8befbf278a06dcc9fc573c61875f5bdea043e61cbdd27720a7fb89ec3b9988e64d37d9bf2f24d330835b1991698b229accb38983ddbf9de938a7535358c66bba0cd01ad35c42f052b2c0a963a5a1e0b8ff47cbf0b39c1b4de63877437a8f7ff12760d1b0461c5f9474e1a13a9d355009a4b42279389afbe80f9ecdc199bdf0c63693dec9ff283e3530d46d5de0b83b45f1b007438baec00249327504b398df7aa1f3c8d9388ba32d0f6b3bab00ebd1a44bbd2f45591f1aa2d5ad6971ea307f3a5b3f3c7cfea527318f56b48e0c281076ad2b85e8b1b4adb9febc842ae9963219c7db99bd3f004c7acb2eb154f18358e8f3dbc88b916df16f5300a8f6410a5a7fa134bded2a63b0145a9b20aec17676eac969ede3a95ab82376b4f804736119c5dd03e9cba718bd20bf40a9f4218301ad4458803f09d2bbf88bda875bdeacccb180997fd98558efa51e3d9c652b9ca7da9df4fa79327b1ff4dc41ada4b224983805211334ce906af613348512cc193a853d8fe5cfaba251833e4210afe5a830702fd0f01b4a23ce6db39738d545ffd19df74d1d5d21f678d43f9b979b5e20faafc735ea27e494e0916256cad15939066cbd484b729242596ef4ff526e1411e0067f774542d4a22242d29f10f8b6a5d03498085448fb01a056c80b9bf16b3705a020793046206dba9e0aca456a22a72dba9721181c76ab9b8cc1664def5b3979f0c059a2c3a693ea33f244971e209bd0f1475c61994a536b05d65c5213a70f04bbc9fc7ebef36017466f5c985e3bf06e2d32dcf69335b5903ba1858423bc5bb78c6c8491b4a423acc2dff5969347849b6cee0e4da1526f3be4dd951482a455814dcf18b6b1d2108f51f016241c9071fda6ec6961cbdb6276e372591bc022574cadf9d3d0b9a783dec0903acf275da463dea626f1ce9870396a8b90883e10773e79a3f4d5eaf24c488f7250ef7a5faa6b130cbfe5b6ad2b25c00ffe61a4280eba5903e93b27b12aabf010cd57091fe86b9ecdad6b7032942d5c8d1131b97db2f0b1c1a1258a902fef20611cc753f0a12452661ab014441f5b3420db3f51d6d9286d1dafe100490ec1e977817cf8df45ffe8a53bd35fd844d1f7a98fb3c873c387a4fbcbb964292fe425e211c9b15baf6bc4891f6cf60a64fcdbbe0d5a483fa8c0507e442777a0f974e14cf2fe3d78965d335dcaa0b079662737d36ea510cfc2bceb092185199ec5e6deecb1c0d57e2ca6e959580192ff45b3c222a0119b6d4003e3a0ae8707f2936cf3672bebabd09f204d99c6c4d3bfc22d0df4dfebc496de0f5575af7662b8c041652c14589ce4a4ab15ec096e4098735fe06fa9bbbd1768ce64b2975d72d3680a8662efa5d3daf8f630610bd4c045adfe0f14c2f91d38c855c5b31d37ae8e60b05a24defac39a2e22c084cfafd82a197c289d4dd74b4aa119c4358ecaa0a5943d60892459e94bd1cd653849fc03648e77128e6e94b0bc2f929f764520dcf78428cabb65beafb31225437025d0d96bdd9766d221189760daa87ceaeaa5165a9e4ace484667f79c9639dd22b9bc6798940c7b3b88707d3156606b04a8b104a650a9b55fd42e37980ac3694e5c63736b0354c1af1c2cc76fee2b502d06a45ce0f311c248f821ab209fafbb8e5f2efe58b64203084dfd848aad7c65f8edd2bced25de939082a4345f95c82f3f24ac30794daf1a1b732b9d3332efe69f64a0eaae947a8e460c3f9ad8a49e7b595a2a23bb7ae97f53fc7408a2fa4db5962fafc0913b9aca2f9a7b77223131f9ed2c6718603bec29427a6c975d9e8895f23e80e495fd8d4bcf030427af4b4338ac66204e3ad875adc11114d76d47f258c09a49df9a1b0c537ffd8e80c822faddb111cddca3a0ba210c02b49f5616ba0922837208bcfe23aedeba813e36b32ffb62538ffa7bb6cc7845d10b87aa935b611f71d775512969b348d1768e79c6b127c02200e63b4aeceb7150ac25cda9eb0e9e0619516b79753ee48a71cfe8b1008909e43d1940bd1e38665f4bd9938de9778e97c63fa256670c993d61f95b0916c674e830bce781487f009133aa977fcd3c2a68410c04f2ae948e977e9acf74867b888c93a31290acdfd13b1f70915cd68f0d1cbad18227e1917c7c8411d76885c2e4777cb5cf59cf73402f0951662daa6129191aad5836e1877d4366b07dfc24a225d6e053d7bc4dc1e26413b7773f02f544ead1298ca88bc167ed385bbecc21ff3793f5ee23074353949a4beb95e8ccb803ca62f9414c326df41e2eb2cdd8f0cd7975385df4878b7075562c6aef31a3bca0d050affdc889c14a618be76699302dfdff940fb58d810b80af013e84ddd9892c45a393d6b8eeddee1ae442a5e3a4c9e3ae4d4bae24d3a30ae1837e525bf09ddf612ca692904ca1e35c044f5fa02c52a28b4c8e74d24383527c396b5cedec50b60098c4fc2f0774d5606e83ffc55631f81a949567db3e14f646b6de95db67c1f61fb372e40e641b14d35672274267139bfbd2cf54ec15b8c03381cd091c5ed5140623154c08b6882ec6c4a4f599f538984ff18e6714bd459e75b52f5dacdff5b374b0baa627de0735c18d2a5be8892e2e8b7380fcd8e81a84d18dfab081fdc56d4a778ccae60e076f2234645ec930f5de341e515d419b22755fc62dee142b77306ff984c655010646d2725ae96676ff003860553421de567315458034e3ebef00722d4a66b4b8409081be245b102ee0fefab34ae436dd35ccda34f03678a27d7d70f012ed7fd467cdc7988d80f2bcf95677d748cd234f11b69ac01b8a9bf372191f76226ba74e23bb0f0a0eca3a4951a8806439eca11a6913ccf6a5d537325522b468c720260873127069e61e2e9fb16955b4045525698706f0ab73742493aa4e838e4edbc0f9dcb98a4fc9ec26e81ed7b7e948aab348654430daf3b664e98b1f3c7caba90a0142fc8fcba278ea89eb278502c2b29f85771c34a371cca63ece5c2ac193502bb8b183fb4f1776031bf66787adbd99049ad39f987f96ea0fb4ff8df9a5c628240f393729b5e62fffca4ad94375056d6204665590a9c4b9d275cafcc2509d26cbcf0df20f81e019103b3e89cc47330d3199be8d5c6d3dcc7495f0aa1ff29e57e33b3a0fbd9807bd2edb7e49ce00fcd37b0f7cea2b5973f057f38fa6bc7a1fc5c8f8afbf9f7eed8fc1b0db9b54a52a5f95ebf447aaea9346d98bb280b77768bec716ccf500d708602a040b4203e300e8ff834748be2d862124fae459fa9e69474a1ae38a1920797ac2885d1deb004ee1a9bd4375d08c255db11314a3dff62e6e75de855fed3932c1585fd3ec091850196b77a4ca6415710c3ee2f04d5acb42dc8201fff44c880117c78fb59f7869b84adb37a758939b9a90628a3d32313468e52c698a32e526862c3201655f5c49e945cefb0c418fdee39b8a64ee588694919c795339ed004ebd9e3ab0f1350fc3a6c5a60167aa31dea2fddad06ac3bcc5c933e4f133419df5e6f7253e5518332f7d738516242bcb37ecd91ba17ba5bf027c681dcfdd176da574152788e01f6f9b351bee74cef4997b8fbc3ecb4e1d8e0ba657acb81d0874e181b207f2ab6f3caa750bb4b5efb6fbcdcd2cfccfb8b72f43e89230a101d9512a42bd81881543bbbbb63802f110abc7f97aa787cd2d2359023acaf74970787ed751ae2c320f0ac96c6e688d5171552315af8aaad3b828678b2d4fbea5469efe0b68b4cefb7af29e12d99a26440d1008d63920ab089eca8dd2c1d3d467f46f947747aefd859057bddfb16cf1c0fef1dc971567ca6db258badee44ae4b2851661a5842a9d0508869cb6a114dfb3ac68fc42167b4c097e6e27129a12e48c7a7c8dd303c98739f59f007ba661a764e797ff5f4d6ee06dcbb005ca757f2aa8e6c136e245d8e9c28ddde2d70f1884255e0ca0d664eb22ad454956094911a1242b2b78c8617683b992878c0bf1146e6cd412fa2d1a33dedfef5a4356ffdfce6b4962072b84b7067a676f68e6d3a07acb57b7399908b573da68d19820333b6d216290b36c5d5f88680107cc48b97d7171b2f762ef73c8cc4f40106220f42d8e161ccb1eb842826adb91456bf02bf164f7df3e58671b3320540f6aec011f80c03d63ad02c295cf3d60be00079075e8481f7d512ce5019895d4e0ad00887da68e1cf9ee18f5d09ccc11b68172b84e79246c3b24c444cde58ef39bb61418a3bdd00da4d6449ab3e77a33fe94f287adf6d4625ad33848c77c11393e6275d9d6089ac1ae30663a7bc247886945c7b0a422e310bb3eb86996a2255d2a0f09e30d0b92ab4b15fac9de31a8d27b095e5e70a6c33cc9390a00773d7a983b01b771db1000e1725def7628690a30db19a1db2f97b2b449b0958d13c127d32b87c3dc155d971689e831fd861273fd8b7c882c968199e3297df81f7dad292c57b1a8921c76601ea77d791326688e03e01855daaa108b10571763030cc1cca946c4edb826fbdead307b005fa149dbb78908803137825e09fd3a116702c4b7ae229405aecedf04813ab33c47ef196a0cd8b8237931d7ce1a5e4aa35779b5aebf5db0dc9b6d7bc76965b4d90c5c750be7e1bafd0e5df07102103f06aff148019f48db411e0e17338b0f80c1a0c29a52810cc07b68911e29f69910befb82dd138611485e580c4f03a735aee5d207d0610df3a04bf98a89e4b033eefb3354d3de1fbac64e893e460a73fd5099635ac32a226268a5a672f10723f42d2b9817056eec4840659c97800642492e9283f7507c565c018cb3dd2403790f69989434c1f27428bb3df1e653e8bf58cc59e85ec40e754f1f6799301f15fd96076ec0fabdb3e0a11dbf8b0cc7318513e9fa621488c5f06ccd530693f9d565945ac74d00556c3bb36d59d407a9e278f98811800be7ce2666f947ceb13f7177a042145b180db75bfd1186f5dfee54409a337ffde42212d5f08e8151d1f5e4d1e34a730136bc63f6f27fe231c60d52316c6936d5b9e1f8e12f5d30b060953686c1048b6aac961a605fdbaa78de5e1ffece43cf8fd60105793064a909e47668fe133579abdf3248b8eade57861acd1efac032420225f6b5b0a3f372c173c39f79db7b060122f755a67e3fe6d715a12d9404f5cbf82ee0899b4941eb62c4e3248b266539bb5547d3d5847783086ff837356e6605c857a668afdb442c3d7746510ad0543e7d35b7c8b26e159f4066f889fe3a0521172c4caaf9038e585b252502a54eff79f047ed2d044b0121a31ef87c8e6c8424c826ddc98e99b5459047dac23c8cf59fa4016586cbed3734f1e4b83c3c16fc14ec5c4f019e0f0b6463b2eafd6cc0182f7346e0814d6c1b00136432d295336d871f0d1b4724dfd6363df5cd1305ced1e6108daaa401c060a4b37240ce316804cbde0398cd4abdc4c39ad8968bfd06a65e166a3781f7c8cbbf9db1b354e1ee7a3b17e1acc4816690465daaffe9d6f37a0f37bfa77542711bd4590b6aaf24247c2c1c0e824e3d285ca52dc5657867506d34ce1a6b524ac94255aee8d388a12483ce4c736c562df1b9b9674277257061915a31016c1be32b2700fee36ae388e3cb0e3f8780407b6ece46b0b30a657e73fe054266d953d539cf70c43f98a1fde972ad1903716e807f73231f969d2bbfd5fc99b139c05cdfde7bbd0e8666ab9d7cbcd7e9f3e68692193f091250ff13c7b94cb046760de3ddd6517bbe307de4896c1a60185a901d402e797c760a37f601364b9cb2708c06f73c762021d4bf8b0936144dbbdf0930d859fa2806862279b615db82c2b7a6035a0f7ea76078343dd26290bc2e4ea1b80af691512799f07d563d546b806c7924d00b2fb848c2f4e021c88faf95060a1f9535f51de841dabc667942a5667809e5add0d9e0bb739b3802a6c1ec0ae8d75de76ab1a2aa2e7e1845f2f95fd7ccde9cc5a10defaec486b5f802a6bd0b110f63d74ddd33dcd25da4d6a734e143f21aaca60208e6971502a0951721aa11ea4f03dcff520a5d458650db682bf83b4508fd5f92a151378d69d6725ff308962a4d98dba523c3165a8b6237f6d6e2f08ca4429536309836ce946aaab2bf3b7667ceef45347fceea65fdba03403935c7d0da05c4d4c37239e3a58ee8942af27c466a48f36cdf4a70815ff6b67b6ed33a522b62855a4f62fa1d7618004eafa4b71852b2dd50e730bfed254de97cee2d8fdcffe21e691d18381bf9c58951c2f469b3744292c66be1127cedaa0c4baf971c30d3b5e8806d8385b0be0837d7b69d37b3a559b95c26ff7c05d746953a9fce696368e37dba1aa10cb5ce357c7b1a7ee64b4b1e139516ed3a014290cec1c50bdf02280788e132b9fe45f0a5fefb50859653239649b94b05e7e2db4135bd417d9ecdf397bf5259fb8b65243d36ebaaf08403a56a6b7ab0a443ab079c0fb73feed0ae1f12ce9dd76973304bc8d63f2e83a37b15456d9bc40001c9d092c8836f1e69fb9140b4b55271624d4cdf0af66f8e9dc1e9bbc9874dcb6100cc30b7ac078c47f7599e82fbc19f9443ade2451dc8fc8d44e8747e7b7f5beb48ac5fd8b688e02d381b2b84a6477ab53c94d055866a25fd74b0a5a5f63c296d02611cd39fc291bc7d6c1413a20c902cf3865ab0c19476c67f7580ea8764961f9dbd2002f1001834c0ebb151fb410b3a1350429a6b57ec0e2cf0950d9b581c670f3b8fc1e34d4a03f58a7aa404605c45b334998caa472d2804f5ed846edf37cd35b375b794dd3b8b113bdc3444159835e449c70a009712b6639b113df69a61cb431b6a9039fb7b3f6198242ad0226f295f5c6a289f06087efec390fc817a41ac9733ccc568b1d9764b11c6a510d8f78aceaf20347cb2b47f126dbf87d314759e1afc9a1c6a6bec1647b74ec05fea7717f80d624bc1473b913a645ab140de9ef9b0692701bb1c7ccae5d4a24fe673b91717e3b93d85ed98a4c70766536afb5e802f38f450362ca1ba6ab53effe0adba03575f0b262e992b7032a35c4c0e9dc291b4c97c2f5537619c83f32ce32d2830675b3474c89f4c54c4760b309c5e14c675e3c1d99803b14be4e49257c06516811582dda7991b1178addfe1ab76aaad447bfa62d1fae21257aea3498611d52aa3595f4be2e82757099495cfee802c33db5bf1c9c1b371c6fc1dbd57f4e3db9babc19d60b60d9d8349a583b60f5c13b2de1788868ca5fe42c4301b9cbf0084ccea1436402b542a1a720f63c1aaa5c8740878505629da46835de3306adfd7696e321c2d05df7ce657fb15dde4ec891393410057684a28387bced7e61f5ebf8cad630b7502f9b3c65b186cc252aa0dd986c389dd27cd8d02c9eef56e000860d10e95ea6519235717a472b4108cef31a7d84b2fde8da16f721f8b45fdbfda6e934b410defd5a7676702b95400a230e665611aa9694ad4aad2da42ea8901f3e00bbb50718b8e48a511d3e58805a764cb1c11251aa304f265bc9fe35c9b90e63855fc349ff7e359cbf30bf05bd665e6474a2d9f491475faefe97efb358b37bf4df600d28d97f28b82dcf9436349ce52d5a31b31cb5cbcd90834883b0f92dda6829a9192856fc57d5209465e4423c9e69ff1a820907b677b91b6dc58031303d5fd9bb857a11400577a02e2af12e2952587b3fe9a0c86e66160d0f7ec60232915f418fd9c388e94c3f01c6b2697cf020f9c3eda8df3e7e8f92ddc63730732026313d5b946fd448eb99ee03af4d8f39e38e3bd2dbae64756713e9c306f89c3e54137f0895afeeb9f54bef4bb87be4b6963313d8b614b90b847cbc3f488282ec26e6db5dcc4edb3b77147c3f61ad3363ab801804160a8de649966829bf4df3fc553c0fdff614270b30d6811e23201de657e2938068ec06643da9ff2814ec2d022fd4efbefa65cc4fcb0efd7ec2864f8a8391993029bfbe4640bd6bd086fab349b80dc6834de05fb061ca30a3619644c137818708c7ad832c89b66f030601b15b0799037ade079c0376a619339e4932d7e3950791526aadd6d7f2588b1677f1c7ece7e918267b2a7f86d88e22d5cd4507c38359d54cb0b289ac3fbd8615ad827b27e45401cadbeb43a60052ec33b7dab978ac117eedc40f213171688d4c953c52075070a8b3fbd5f08fceec6f8d7a420f0d9d394995ce46efcea696972b61a8a9042f7125630984071091c76b57f59f70478bfc2b9c5abbe71f220bc9445b653e06a77d176e1a99afc5b5bbde9cc337638347d01474fd0b19bfd76f0b4efeb98473fdca5c678430fe666d2f79850f0615f06a4b5ba4559e9362635efb889fb283604a4418034a9bfb67b7679983db114f42df6c2765641d77e45e8baef1e43f1764df8b65ade4b3fedb5d4682d4e7bf37d5a7fda9befd891eeb437890b7a41edad8ba54a1355e990f2bf440e329d7073fc67e373ffd3cbbb541815bc84b5486048ece65ffe1fe926dbc54a00f6b72829ac5bb90774e5099c20633e47a55b008e77baf4389af7ea6f2b0561043379c4628af3e8d8bc52eacb549c29076520bea8c431c9670ae4fa3487917fada78b13604e9fc93cb1af7fcb3f0718869a9f0792969ec3019c42b93f49386d04e03ec4b924b6bdad5fc0d42f69807c1a60911dd08ca6120f88f762f705fa57d29e46e8b7defde3c5be0e713e9c1f7f12eb279c7edef7c7b5908a4c47e35f19aa13669c40a0fe3e6bf3a4286cbd5be134be859705e010335d91db15f71ba99fe5542b5a1277c1b4304192adda772f5e77959513020938c754c8a6587a39c885618eee822639ea200794700bf15346837974f13b297e2a574a1c5256832369e95ffc7ad8799d6221e2ee526ee6f0bb21100ed925527e8c8e7b7ba6ec67b1d62f7d0ba75296ad374c4952804ab7c9849243da86b1f50ad483170e135d8c9d820f5559fffb682e514096aa6c23a8958801a930772d7f976aeb0e508fd1f4f7ba8fc6dd7081fb7da1ad5d2a5ac26243199b0518841d72a248c0c11cdc50883dcf3c621f32931f570c499dfc42ca1f6939b2043e3018187ed370cee8d4242b1b6651d70ab662799514b1dea98cfe412d8dc18ce5127187a55834d7a71422a22a037a6d936275096650680d6eb1436f9685ae2925ca91bd8ba66838bd23f8fc9133ca5df315f284d3ac5a1bea85f5914d1927e42505c64ce068f7067514494a97bdd5a2e206b27a81378293f636c0e0cb875d41200675ee2d0a827f2d808543a2c8c1a3cac1bf4fc4e60754abdccec2a4bc40bf248258a97b139b953639fa0ff8bf9010d52b287a8bab22fda5eaf4dc4b7013f02478a28c9d1af5438937e8154c3cadf8eeb0a3a9280ab9b52f91513d07ea414336d8f717c30ee98ad38d72537eced2f81a1e17fdf78225830b2d7f07ee561888a23b4894fff2f1d2dd20f1c76a121b27d9a401c630239075a20af2cb1fc5d8208299862a7660d27ee1bf85a647a0ec47658cd0bae52c10aec2a4c5b1cfc2815701389c8d72098493c027e7c2f0f08903ce9c0a93ff9d9a084e1d70f8af189e6a0342e8034ae9c8e391294f7de4b62f40d5e19673e4dfa48dc07d85e489b695f69d09abd52dd36e8389687f19b2ced7938d0373ebe1362ab069a3a6efa4a6686a1c2d083407267d98babd9b02a5e78a690434bd598760b0f93a9517db9f36ddaf849102d73c13b2fed034583ed3c1b6bc23dc286ace28b35e362f3d1f6c7fe110b520324c44649a17589f9d1811cd412e670e76dd73653178fabf81d0cf38446e3e2b3b43f243da53e2fe4d1ba1147f7344f25afb2e4887f72ff196e551de0feb6edcaef1206dbd1b25c381a3b1204c08f2d920945bfdb8b38cf6813291e8301fdbdf16f9e2eed4cfb388a7b7d6d280789fa4e9fad83102dca538b92dca21d540625d614121987cad8dc15c41d08ad83b9bc7ea9812181dd6cac55400d939770a781c999823859ca54f9909ab6177d5bf514d8de8dc2d331d80ec8dac8ffaac8a451648a4fefca73afca857af9a0a1cb2f97dbc2999565531e343cc564c160ce023e8096477456cbc9560f16d6a9213f3de507843833c1f161d2b7e1460d37c5039040021e9e1a3edcf38ef91a61a9da403b55c5dd34b964f681f43717fe0b0f016412a1e3809d7a662cae02a03bd0a6fbf2b86c985e8a77039ba8a70f266a257d34b9d02233bc26504662226a6132ccf5372335d0451db3f0b4bfe3cdedd4af0d25064bbadba4f5b6993b36b4e70e478f98dd5352e5f567b85af5492b887310a49690cba13f982ccaaca47acdd12018192b572d67261eed6ed1eced4ebe182c04be813fe12667decdece75161f9042d0f83a9ce7d38ee91982ad4d8fd7b68b48b36c95d6fb359c69ebf13eb6f1353955af7f2d32f4f2b14ecf5b84b85e8449dcf9c9655cee104462c29a8c134728695d57cf260dd7fe600db3f0fc2cc26017ec96ec56fb0f003615b7092aac234db925047ed6d52cac57f92ae434c0f3580fbd426bd30c9b3da6e7e961bdf780c1381f561ec700c4d4d80a7109393b0e21e0253e32ed2d48968aa911471b18fe88fa7d4c61c733545ac16aac7e795f258ab5ba5ff3a8b9c28925c5fbc3ddcb6f3824173b130fe7d1edb07114b65ac5eea1d15b800d0a45b8af9af0305291f1d00fb503c64c94720b1dfed29d3b7d373a75aac48ed5c4c6d9e63ec8de93a2395867c66a91f52d271772a988e3e593d7d64f53ddad795bd8571ccf37014c9a3ba2ec81e75cc1775fff39366338f6069347c18f3fc0a234e09c364ef92a4a333828f055028506ba79811fc44e9f6d0a5e8a0d239a1e29b8840048ae5f9c34c5938b1eeb6672fdcb964039055338887be1ab3922fb7ac0fe0cffff9198e395c7f81552c135971ed0db30099c0b46b78070243b0a77429026f088a33ca22406df9166f28484b0376147c7b0923b82fec804433fe003caf41ea99a0721e002929841efee3f17210039270515c9dda3cd89bc42061a155471134e5cb8c578f35dc2fe12cd516c826d995bfbcd3a7bc99c11cdea54c6b5182e4468f1df462a02ff8d4421bb7d1c63fb341f0dbb56298e6a15c91dc2abae98ec89acde881c92c6de254210bd23aba9885916209608c7e14d296d1aa81a541a2ceb04d9c328a75f92e1619f06f027d1baf077d77fc1a78804e0b0e7e27659e056c522b4602342e3dd8ac9ccae9b85cef9365c170e9154fb8602fbcd97db075003ab8e80521aaa5bae9e6fe6234f2863b1bb95f21e258bb20bc3ca780798bab9d5c1cacb31f5e6c16b4d78983b481f2f0d31a1643b46be90de1ce54d675f2132698d5a79d8c30db07de3f4748c9d31bcb2f2bca8f44315ae99c8c53a6088660a9ca24a4a1453956755b534b8a8ec6d9cf2534449fa04dd232eddb09385840f88e0c04c30549ab56ffcf2b45e78a8d402026a6f5d014579730ffa06bc5f9724ffc67c6253cdcd8db9d2506ebfc2ef6c057374038953acf06e17316dd12cab6e76e21310abe0b0411dd8a007319181b107bfa28e03bfd04d34203826f20421f695c11b7126da3bc80a0062a283aa63914c204adf5b26aa234a4d094898b96798bc3b24ee2791853b515d87efc0c60cd62b6206ca33c5b134ad3e56f035f20f514dbe9e72fd6bf9cf610eeed1c71d6fa821f3c1f69bbad43a6c52b198838e2299c54825faa794fbf532a8f5feae05bf67e920932a6e02226bfdf7351e013e3bf9679bfc2c254b71ad82b9071c9619f94bad22016d5d9c92481ed459e1219deff276e773ba705df14a1547ee8fe3e223d30751c85682ed519409a4e17c051f0233056420bc9b29e28f849e0216a50805abd1a163dcf32d0a2762e6da38d1b936c4b14b614e66c90e060fcf56279e4539310c8e8aeffd3eab1224fbf3547bce3fbffbc6e77c74e8b6ab5740176759e95986ff00acfe7b21c3807ef10bd25dcde3d104757057086673f775b8cadd1a934dd94ccbfe7ace6ce5b6b4af46b10806eed9d5efdb3ec9eab586f64276d67112b9ecabadf5c7204b5fc3d7203a3a6a89ccf3ecc842cb4dc93ca29aa759f57dcd909be071bb3c4663d347be359777100b92d3ec1bd7f0efc2431bcdb8b193cd2f35ed0831feff86c55ed15216feb390b5a34c8c1ab4cbffb88493bc3198e08e61579f773a7f9261414fdfcfcb3ae26cd38198d91c5874f193d109e4d513b4101e58ef7eebbc3906fd101989426c85ef4764c6a28e3ade3884516e5c638ce10115854ecea91859c6c85c7ed0712f2896069b3ea2e664907033bdd570ccd10e719467f02f6205e891a4198ea6408790648c7cf098b8a5c2ba8a16008f19e01684758c3264c88c06f243aab15ba58e061f8c913746a5d65e547eae5c724023be228719b3007a38b3024272b31a8d2147cc8c52a9d826ac8cb582549e6c5264dd9da3a455f35f25c18787a23f5668d84d0a9f1a5ce25406067dff3bcf0dd848dbcfdaaf5b9bdd774933ebf1d1a3990f6179de1924b9f56df49ac872f21a9f10d621266252ae80174b78dbc9e487366ee52876289d2e74efc6c64e5d1350f37664ae63a8216fe9b0036daab4111cae5fe872281a1cffc26be237893d2f554a223f5644002737441b4971fafd3ec92f5cfb6d573cd08d1f3bdb2997400413ca5f9aa4ec054c683494eb14b7c3bc33b2240bdb54911856996da2b4b0e05841b0c90156290e211b1aee77539ba067db7d16e1205e69b142c81779cbea478940fc48a1ca2341042f761b321eff8c7161891a57e7628d521d775f66e3fbf885b80ce689e7570b149d96c07740f782e38f18e3ce86a4687bdfa9fe22b6bf617054d0696530e55590d65f0654c4dcb192cd1230e8efb2c3a2f51f25e417717aad98d142426c992aa7be4b68f9b28922a3be9881dabe508d01fcd6d0fe6f3b00549b5abb6a67e6aea102a076302eeaddce94042466298814ac127786abcb5dadfd26e2f6080a440b4175f3242d96cc71f39e525329eed653909526335c02a6c1b5f98b36f18d4b4bc02e82d22aa24526695df15a6533493ac2d576e2f90bf7dbc84425624bf2d254047ecd89dcc68a13287afb90ce0ad870a33724c64f7a0e31cee8c8eff1ee1831c941c092de071e8ab733f8280e2206457a2cc8066b6e049c4b39242b62d35cc60c69ba0c1518e12e22525a7fae0c86d010cd3879b2cfa51bd1958337c746a0138480ea59d8356a24505c584432d4e933e6544c41dc3d8c2edc3518b9166c10d32119dcd810c66d24e22346ac3d1d4a7f3de7366ae555d17745716272a99aa40439c6e09a230d490e0c9f418f4c3e57c311288c84482b0051ae31602181272cc2e49d7a23e9a67e45760c2411402666b1f9e12f9f51c654acf0034a55bdf5217570223244f9aee80b2523d37a0e6ce655bdb9c9171f982962c38db229a29d0a3a4f6154cea5dfaafe22df48d36342d6c3d70e93ab25532d27a652325c73a8b908e9726c2ebf9fd5d5822ad66879c414cda578d8c619a3394fda1d6fe21f7569c437b6b4927e5a5d1a66d8ad1e0906f8cec17cabc646f3f281b71c42fa9f4c277fc76f8e48a1e131e6d513892666e14000b6418005e1548e1896ec3cfd672e2c9d47ea04a5a3b09457f1ac4b730389a6c6e459dfa0c7a110f5e7c2062f082e4a013fa687cf189f8d45030033fcf0d0ffbcc23755d5762cfaae5be5e79fedd6aea0b9bc01b8649bfd501fd610858e603eee2e7b4d28ae7adf0583c06c3c087389273a60ba671d33fcaf178f6aa0f83cd80a4bb08bc20b94fc55814c85b29f4195f0c979b8fa41edd934d34feef7dde76fb3a93848341c8cbebe6b62edb88761bee006b8e77ccd76be0ad7e41feca4362d5553b1d5bd63d97d9b0831a5c873f1892e873deb03b3379fbe19d99782771b1c99e0fc32ad797aae17e1828002cec6f87b6d1bde7e24389d8fd6bc5ef254eb19dc759c7dce2089f46d170c11b2132836fa657e004ba9ebcba86cccdc8cf9fae696777fec357f078a7e64fef36497609d08cae124496910d9612f64d08bae9050fe98381952b54cc3e2744b10f52fbad469456afda654568dafacacde8a8601b9c4d9945727b5f49d6624bc6a4493937074705e35c3347264c64f54c07aa1db272b6b7d37478b06a1c9d3a72854857c0f72862ec2b81a5afe76a745f633114c269aff290da56acb84edf5aa1347fbbc288a7706104b43f5f4c9f3b5fe53477962d02a291103f64ccc8227420a99807c0caccbfc8cfc14be88dc24fb3db69f17ed472a45e7daff71091f7696a3999811e263f13960ca0f346edbec7c6a672f99bb361aeb26efb5d8dcbe88b498020c39e740c3441a61b14e6893fcdb8c4b1c2991ad4e01560cdabf94eb47185d9966262e4525d36dc17265fd77130f4045bef1a781339a65242c189796f06d08fe46de9e55d2438bfef6d808f962f6fbfe108e7b559c6945915b3986a993475e9c18d7973077bd6cd90a03e5c95ccd78b3a91e29fed207cc6c4b732a371227847750c4ffdcefa114f9e4a3283c31697827a5bf224e1c2e42fa9bb120fc87cf956800116dd31a70bd975a336b18693a527e9c73de4fbf37e57ff72a791df703b4e1ab30d0e461b4ee5b38103400a5e72c593d23c7688bbcc9b3af64227ffa5509b4f0de0ba6d522f3863ad4e04a359b477c5e237bb15fe20dbcf52bd4ed6dc38a528bc67afebc2a2606d1413ca1144312fce4de17799baf882fe8e28d3ed2000609900cf6e8958cefc1a40b9007811141ce122b4d88f4d15484ec0028f3e65f1a56257f686adb7d54e32791d9a767be74e5a8882d70ed4212575d6a3f70842c2e40b68d791e7146a6444347314570498fda9cb68558a069e9b82df13b42619889cfbb7237be59138a11bbea94a6182019f52ad28c496f6800740f32f8ed209136086406802d2a4778eb302082bd69b0ff3af68ea87c2538408e7d97eb393a1ef053e0e52b9692d778ba1ad2b5b960f40d1605fc9ae179418fb88997d506b9b322242e616c18d14420b4b8344b52a0b7abb3dda42aedb87b3fe45e93f3e1523ca82be6f184361e19d49024db371b117f85fdc123cf6b383b4a55cd10182078cdbb16fde54e4748d7df468c66fc2d3d5c93b470a4cff1bda2dcb96b3ec7739e46dbdd30b70c742d51a89359043d8760babd76381a0092c71f05fa3cea4c64cfd37106c2cbff0dadc39e1536924aa394cb97148933794684fb1bffbfae2833e2c333545d02eb6cbdbcf65b8bd4d6161c12dee5cc13023c6bd886cd413417cb46edf1cb4ffa611c50e0ef9bb53a0b5201044f1f1d7a84d83a86e515dbcbc0aae328c4b894b0f7384f0d40ced95d7359dcdfb90502f2674a229c3ea219312c333313371ff1cbc33c4273cd4f3c416454415204c72fd9c6dc81319adab931c11374da0284259a8a19cf9752bc1bd2c17030c9899d4f87beb3c7ed17cb8ccf247a8f7b5d49c79cf91caf83069ed849f026c78ef0819b90f24c066025679e24c4b467dcd19c2b5ac219806e20c35b3302d4c96f1a879f8960d5dc859575058d5652278ef341ac20568358b52e5b1a65b0994064e7b0576e184cb2e82722cbf8297e0de639b0949de196651c64dbfcd891a194eb2d3e323c1a124d580353f407f018e4821d612154514b06e972a3d53748c096ecd4f38a02aae4966cce190a7d704141119265c368503c0fb7c538527ec06d3fed47aad30ea6920ee45eda35d799b8af8a867a8a94c0edb44079f27fa7d0f7e2ce56cd7d2ecc5a3b998d3a6538b4d508cf3002dc366d6d4be070d91cfe36b246d01a0fa9ae2b1c2b10a14f521cea1d7ae9e0e2c064f4024676f849d36d2fa57e44ef416b12883c505fa9b1cada820d2c91a5e63821d0e11cec138f5680da63d700bbdd7b8c1e6091a07ff37fefb4887021d9251f792482b97d2748e5f4e211880ff809488763731968eb1e66a824003b2ad46797ec36cafb2b238e4a8b2dc0b66e6a8a32b767136e28380752f4c46655a17cdf13ea1ac8720399f18df2314db099d67e3f5b2a2fccfb3882b65baa8cdba2afc50deadc4e6e5dbbf8847de74098c33acff98d805c7fddabe6bfb6c1a3b53491135873768072c0a053dd3a4fb4ee4fe0028612d52b4519ce1f2d84ab7fe5c858ef643184b26e5f0461fdfd8d1e4a676ae05e0a5b47827800c39a818365c7ca74fcbab407b1855e9a5a3aa774df8f6d152b0ae30173c251836af4e4db0b64fac467f8a0f02975b8e99ebe67b59c45fecd5e9d389fd974c373a898d6f0de73304c0159e38938de38f4c0d0afe4c94cebcf538c05203df2995b1bc8f2f9ea1e1e4b231ee547c232dafa369ee678a1d0c1b807291a0f9a12a643910b6ef63298ee70ebe5b0560d1b4fe6034fdf7028863ec182c62cf74b633ba88484f5a69a13e5c1d70619bfbed270aebc9ae3780f3cf685677da2ea0d2bdbd3e9e78b3a7270445605b83544a8228af89ea36e51718a531dc25396ea4e1bb02c34e691a6252408ff325ecf33d0bd593e48631db727ea43a657f5913484ffc0033befc8a086c1006484160b0727ca3699c56454ed04d9c21d6534c8dc37852f88b294304c2eb36c78aad72e18149a7460f598cec914c28bb6af64de100d2a0dd8a730864dd29ce92da12cb0b329648241e6f83a7a77b867566809a48f6899965b6d5e64861c98bc6548e012ef1c335712448f31a8e5392c246b005b7602df7888dc76135f3eafff4b61666afcf9961c7c2c3d562d330e88fee497bd2ccc13b3fcb4045e18c4260e511020ee4a7549b768e229019a1c4e1e66094542309c8c3014346292e7a35aa0a0d87fb0f41b6960844e3d0d112992880cea0f9cd5e3f90e2a1c58eab92dd9ee2de28a7cf2883fa17ad0769894fd7c7c9b02200e980f400e4a3f3e3df63a292971aeacbf69cb6e3ab6712994aaa6543597941de5887ff3757b5590968f0e05ffcf12c2fa714181a19be9d795a52f94279c9703147982829aa99448a97d9a4a89a0dad6086a4d65bce5dc3725fa0afedb777d90c6ff0705f1deea18c704635354a5a15b57699c5d5376ad9d5bef68aa76fe6efda3e47304db8236575b11c1b6a08deb6bfb82c1b6a01f259fa30d886803e2bac77c60db4b46b5a66ad1074adbbb719a0b92d58a36eedcbcabaa2e424257a42e63955874453abbd3eba649ae27b0ff47e260dd4d9360dbaba36c1cef2a3b63f7a6b46db071fbed1b10ce401dde5a76ebb6745ba5bc0d286f40b80a6c73c72f7358965e755dcebd8a3d37d8176e53faa4cd54754a13056d6e8ac485b57da6c2a860b01fbc0de04acbb81ff3c826e5b32951b249e16d001b551518d04edbb7cbd83eb5e9a26d838daba9acd50be4d6562d6548e9f5ea2dbbf9fab2ed4cf7d9f6d27a9b6e2f33289575d7dc80789fb946f8eb4ef1745b87783ba5571a477237b7aba97edd297d4ee9736bd3be6aaa3555b7ed294aa93b713e54f4f6a7a1165c4b64ae2542ff0f84b25bed4daf64b5f7ededec654282b4b46ab4a76d9f71fd6482b49d34979bedd4d5443a85c1ce7765d397956d6aba571abbd37e4ea527fb6e6e0ae4a643ab6be4d540ee9b6e444e9a4b44d5dbdaf6192d062f686c4f986a6f8d5d913697c6f6956ddbb2f6a6aaa67d60c526f5573341995e1a1367d9269d45572427579aab3d35e2482dc7b9049fc366a4e1ff7f806a9dca6744affa9a76397b56da89235d75beea5acba2ae308e708aff17f2aeb088edb5bdbad6a9a6f4e90a89986ecb9dee49737955fc07b93c21ffdf9243fc961c46be25c704df9243e75b6fecf8d69b36df7a53c6b7dea0f9d69b27bef566886fbdb9e15b6f50f8d61b2cdf7a637eebcdd3b7de187debcdeb5b6f8e7ceb0d05df7ac3c0b7dc54e05b6ee6f8961b36df7243c6b7dc70a10688771d09f2ae2338effaa1000bedfd54f57fce12ff9f43078e13ff3f5a7073e6c851e3766370561c1de4470639750cc991e33fe70d900740136773dcfce7c491039211fa9aaa514cb8902cf9777178709fe49fd5c510cb8a8b8392018e1f6c7ebcf19fd3e63fe78dff1c37aeb8bda32cbaa6e6ff8f3054fdaedaaa57a553ff396db0f1c38d967ddb9eb386aaeafd9fa346ce0effba9dbbff396c7abae63f870cba228511464e1a3968f00ef59ff3ffe38c29ca4083f7154a8e9a1c3372ca78165d9172c8c8f981fc901ce939b2fdbfcebb8e58f02fd36b4e9aff1c09b0e88af423859c0838e58c91e3aa2a6d09c8ccf513814f192e1f33dbabb704c0f040403eac0ff781faed8abfb6b74ff98cbfbd5c59577b0e2c1c1e8ccd8d4d4de9f3ff7cfd3376c63afa7fd9bb8cf8b432590e5a531d1474526d170763fcbf5b04a328abaa1a455d1c08f9ffedf5b2b03c71159152048a5fd9f49dd166bac7b657cf923e35b97ad4e834eac96d3932c8d573e4eae971d9ed6e6bfab47b9549a5bbd5eb65f24ea57b140ddeb50114a68b489a7787b277059b288868e92fed4da3c07eb6234a36d8c64d71e5c6ad28cad465ec1367d926aa3ac4871879b7d6a9f8faba431be7c284a0927024180c6567bd03e9add35c377d61477bebf3855df52518bb699653394f3755d5a8d510ebba408f0bdc5cb0c50554fce6b36d2fa74d6a7b55094202c605efff0fe13d4bfa044deb0a30fe73c4f8ffa6948d42e26b9f39bd6b4b23721a725560f48fc46936e5389a6e6e4f59174a5b3141725fa09646c4cddc26cee55569934cf39525e2a8b63e8d3cbdbdb6b1ef06357359b4abaf913588aae6729df977572b767de2722de06ac5a1e36a9971b580d8d877735b0e00ae205fb88274e10a32852b0813bfbdba03a1a06ddb6aa8218737bff3f3ae20eaffbfe94ed5d955e90a0284e40e197997046c9ad45ed593662cd4aa674f516f7f7ad232e12a13ef3cd54dba2d4957043e425c11007145c0a2c305811a170462b820b8c205c1025c1000f16faee9d976aa52c3603d6d473b136ea5b140ddc6be40b573e35a16a575dd966d6f7275cb59bd335a7b6ad3ec7a6bfbec4cf7d49ac26030180b0bda843618cce53620d80b26a5dbbe056d5560efacb6a6dbac77aa1fd8912bad04f3d960475ad608e6b32179d70edbd07463d7d6477335769382bd5eb02a1bfbaea9abcf97c6ae7ae66adeb2fb897367b31526b575604ead6937abc0b6976e7bd0f6e3ca68bb4df7566b750b73dd94a3a8528754dbb6d7c8daabca2716c90f5f69b0d58a5df2994acf0dd6f6a69685826d1a9782c1aab8dbdb5d1d4342dabadaf946a5d2ce8d7d37bd6d1c4c4ab725d016b4e9b67cabc0b635fde9b023a8a6928939c58909ea09e90826057bbd90aae8f64d37a0963dd3a718906ebb9bd6aa6c3aadaab93a69e3dad70bb6f5d9dad25e1feec7a7ed6ffa7ac1b6d7aaaf402fbbb6e9cfebd63a9509abea69dba66e7775cba560fa65691fc0aa6cae9175a6651d28e5eceaceac7efaa9d7555bf9b47c8555d96ae7c6c6de2edbdad811a827284a47b63ea5d957bf3a91edc7479602c13a18ab5f2244dcb7afafdb5f74ddac7c3655a73157f7dda390366eaba967ba5dde5dbeb2fb5d3b4fdda8e0c6cb9a7624df72b3c44d5d3740c6bc108719715811879938bcfc6c6e0ae4b3f5878a238c83678cabddce3b90db6d68438730665ad6ade000facf4173a60d1b6dbe680381364ab8402e3b5bb78db739fab762d7cddde2c12b84ed05839551484f4ba551c9a4f5461b8eeb363735d2f697add27d36d36f8419817bfb1b4f7dcc0b9b1b75b8a1861b62b8e100f6dd7eb635dd7c8a6c28abdb971bfcd59b1b49dc70e0a7d674cc0bee4c96b601c2b68de182b61c30fe73bef8ff1c2fbae869fb72f19fb3c57f8e16ff3959fce760f19f73c57f8e15ff9f9353c50fbd5e3be57d85c206cff63272dd964362ae71c1ff6b735dad461d1ce77a53d5c0e2d500a383daaec6153536359ea8c1b5dc39c5660a1b2436ddff6f40459d4a4359695953c7f6b24d36eec7f4a1622ae1f512eaae26da5cbdbd5e5049b66ddb02a024c9e6ae017a8eafec99c61d699c49038cff4d48c9b6bdc6ac90851369a0ff1c10e7a99ead5074aa0eed74c6f6a7fed4d553f7b765550da5d55d2deaaadad288bc961a04b4d498a0467d33c83083016600f18fc40c155a65a0699541a6558612657c6568f0c59a9a4ab553aad66dcd4ea55fb365f9ca9e54bbc78ab6a4819ae956a751f2bca7bac9eddde883541b75b75369a8ee3af19ed6b8ee4fbcb72220c3186acd74f9dadd96181db43de562efb671afee561ba7df8d635f2b261b87b29bac5b6d6e4b63dd24176537ee4755356edf94f60373790742d96dcb9d4aabe713d9be32f9dadd2fd02f60ff4fe3a2d6da428aff77e05b5b30608b30ffbfc55cbd6d2e3bdb4edeca647ced33b5bbb1a9161665b82df7d2645890100cf10f25fc2f218229743033b4000f343495396207206ee7a9fbd298fe719c9045800d2b20c035abfee784f9910615534d846f5581c73fcb7b556c56c389dac1ff6fe55b5568715bae0a2a55405541c1bfebaa6ed09b9e3376e778d93daa650666068e14b0161560a828a14545fa0fe5aafccfd1e189fceb40d09a028daaf69d032627871c1cba8c7dfac769e2c71938647242c071c00d3624a0869c2f577ea440c30c32c49003c30f28390878e1711c6895c1e33d70bb0c68af35778100bcdc32669451a255c64bab0c51ab4c5726e7b7a8a6c791a387204f403b4d81de967300ec99982d2770d0af979bab5eaf74a6835a4d88d13601812680f84722a355b1e7501357efbee9696e4a8698fcd5040a916e2269883e9bd8feb757130c9051f39b12a16d7b9169e29f0c1832379089fdffcf9664f319b2bd5e57ab6498fcf672c91c2133011334fcffbbfb9593e606b941bc773708278a06b021a4b5c3019e5bf5a5eea22853eddcdeee72eeeb6a75eb556aaa5bada6af06aaa9412bbb35baf5ce5b761674aee991b7d78e6cbcb71add5cbdfdf0eefebc6eaaaedac75d3b0d8b668d366e43551debe7c66e346ee3fda7e6e32e7144181896f0d22eb19780f2ff542a8de8bd2a65ae8ed2afb492aa773ac5d65a5666a4841dffff3b0000882356dab6f6f64d81d0ed0856450ac6fbeb05abb24dad299014acab3c3557f6e59c14ec4a2bf553055ada60eeacf727ae967529985ed38d48a77522486c249e5e401ba7b1e63862ccbb697ad537293b10f9cf71e1755a7544929223b8cefb4aeb20f64eaddd75bbca8511556c2faadd8da019416544132344a04d0990db81288b78ffff559524a16dae36dff4c4e0a7881083b5f39f07072d22deffe77e76cbfa6ced79b21c00052b0a5614507053b47363a754bdc160db9a96494443474543a5d312143e581520718e5400d60447c31e87ea0847c5a11a6284f7a24e992d21d208a1c3ffbf3655559b4248f0ff6f9a9cabaa4198c00691f4ff5b42d836b7e55eaf1610b31fdefc9026cc92ffedf5c3093f6cf981f683fa83953078fc6f4ab617906efb98177278b349b1ecf0818d9c16587877f72af75d53dd96503e60e083cb87067cc04182d257fe7a99e6193c6830c4ad75aa0d6f41565a2bab15d4ca69c58a152256aa3c902140b81d065a6162eeb774f8e25b3af49012b01cb020fd1698ab1f6a8169e075e000c26ae5d042100e6970c802072cb6dab96d40bc20a5854353eb0635ad1ba4f856979c6f7191e35b5cd67c8bcb18dfe252e65b5c8ef8169717bec5a5f62d2e55bec5a5e85b5c80bec545e75b5b28f0ad2d6b7c6b8b17dfda02c5b7b624f1ad2d437c6b0b0fdfdac2c2b7b614e05b5baabeb5e5cab7b64cf9d696a36f6d11bfb5e5e75b5b22f8d696ff969638bea525cdb7b4703164674747021c1cce7d529a9cb091938710342cc01de008091c4f00e1206401242da4d6977ff53cd315870b1a9af82ff3ad19de7c6dd59a7d5756e5f47f4e91ff9c1e578dd3785fad983a8ff29fb3c17f0e919c0c8c6448f36ecb95b0d49a32e67f4d5b31f09c055a308cf0ffba7aa6ce663024f941220f40ae7461b31d4dd971af208d40c11759a858b8e24a8b203213d540aa060a8518182668522136331cc8e22547faae21248c304e2ae992316ac4885932220d0014535988708244eccc14097b3871864a4c070d7038fb607214a3ea56887cbc1c88816f0001b2028630622c13be8802c4aa3585c50319c8f143cf1b53326051009536189c30a5eb58c1880d9c803105c4c416363a4c0c80deb1c70c6e5cb96144a338cd8883cc1179a02a5ca9c01a93002ca0514cc062010ad0a20194c81768f0608101253d0e0d1c4085cb95ee6328e83c380f367a019a8c10411d54b4e94b627cb104e5baba1022686214b150c4d37062026ac206426e840c53d026d444f15ae972ea92b8e24cc4a2978e28a6e011008113f889e5b0c75013258568491f9b8724ae8c123095f78c05045019410214fbd5c5a96413668bad938110dd60a387930b3b9e4c37db08670288645001360fc6d99560f365840fe2c4522364e1e24bd07b98f96202497c0de8295e45402c5841a35d30033ae1c1962b6dc53d81bb628f3a340751805184a54019d81c60d5085366c081394124cacb478e1b60252c89e14a308204cca98728147270aa0386c19928e5150c80c35c830c33a81630e5309310001b37af248c985c52d84188d41bad793413a1b6a6892a664b05906b92c10810cc388232c11a51ba8010c48e10570d08820508af0e94209804d080b02487498f9296214058008c6c070d1d440a0c2f51c6252aa0498141c50449152185d415256a8e62582393fafa50da29a3298ad45f31f3e2895bb362851915b811c4a98a9518ba1c91199280162b3a8389410ba4ceca0046f888a4018452953b7e586163019b922a50d8e0e4490a3566a892425051033a08400a55a20e60030c483ad8a10a11205b8aa45040042a6d2270ea5409c2810a03782c10b5f470a12282d5511e1d06998a120a3fd8418119262a0208d3460a17180fa6a0c18613a910146031a58821241054b140c494abaf480f78b4724c29b2e2c61b413e504c79600f09ae8e864029629889c01460be235276f8028dd1cb0f19296c03d23a9e080252483da48507a9a4e42829e17a526ea27021f463471448a410e50b1f10b8b9810d3551b8152e070280c34b944e073bc6b47ad0283d6c6862ca989792280838e99a4c01431f512e00e120072f22b822aaca1225581460294854112cfa50028af10014396657f001b43c8042668d21beb8cac00a28067072930626240c940f2440c7044918b101e5020464708031585c2835d20859f3a8c30a94029088280243992d505b888a0853420e1c5493180a4b904945a08230b1801415b30c9ec8b883768a91c47efa010f293688b0c2f4e40609008e285b1a78122a228522e6d0443c0141618707f82cf5387d11a68dde00313070ca810a82057ea650e1941269654c14413a7d16ace0004e0e2a9c7e0206ae900285fa41154d9831041460960f60988182a8ef88990f6057674850068aec832355580bd0811242931d3c35e091c3e46c7aa20c1e04843758d074000ab8f9e0cd1e661394198a48c008922e4d434a06a041c203be30bd11e6015c9ca94015a6055880c50f127c9980890046a069f3804b02a6a424145a480d588269821dc2d0c080c18525342e20c004c828fa61498c1e690a31900d2c59714ca021830a254b44289819624d0e7cc90131afd18b861c94c270e2a70b335928500ac3079907c83d9a28ada71469b08047ab3432fd68c3833cec50fad1a625a0da85a42d30a0f530b3c182241ad68080011d47f024ed36d6ac89820e264948d2e8b0e68c913dc9638b921c37c0a23c91820b5159a3d47ad2421346a34949104fa850d0c6150a2830f4a4e78c124c7055a18593374aa82a19ab9013269868e28a614be78404ed4515197c3ce1e4030b1e8000175e83130b40e002c48a170b486cd860303a9871919018c148124a80b182a465a7873650de144846540c78536405072409dc4c31658b2d4947691480e60e21666a473ed87125851a30faa895d243ac8082358e94ec1f39ca58e9e10800229450870d4e3068e285165f1c994007249ae02080314551520b9ac482e4b1456d4ba38947a45b2a944430ea838b04b82186961f46667c488388511536182160cc124be8ac0230325570630290237a8c7e84a690605270a3ac03abd1438d3086944f18590389064108509e300696357d8c80534211510703f20fb0dc503802e483e3021337ae8a6a63dca00493258270a838c3c91d4cbcbcd96247e9cc0b4c9e54b0060d1a6e9a98f01881c1d512292c293a43ca1a980b4abe68082864534b66bea8ea8b42821860be68a84c4d54e3e58b1aa0b28383ef9f084d135264d0c23f5198ce06364bfe896415c010463caa62e3ff8970c0985b1659f419c2186107e162881bc10b1e57ca98c0c29229bfa4e7df042923c76d393605e25fe7071fee98a30d161aac32c888e0d9b6ed69e648b3264d13ffafaab4757bf559ed4d43439a150c90c6cb0bc8278d549a6d95d154766fdbab37daeeadabd729b66d696e5f59f5075dd3202403d85a765bd24bdbcb4ef96c9bdbd754d73ad58f11ee3a9a95a0aeeb3a9a159fadcff4763b8cc8162920d8f682b1dbaa5b3d33622a5978ff7f561fb3d5b16dba29cece03638c2e31d2e8f4a0c383ce0eff3a61fe7574006385d6a2b4f53d695f48cb1e2d0c29ff1b501847ef0ac307081b3442744e62aee3701fe1d1a8ffef42038566099a4f465b3b1a9f9f69191a2168feff4c1b31fecf0c9d79e2b7333c3ceccc08b0ed5d5336f6ffb03350679efc3f0cb64dad7ad55d65d973d36fda9d6e12183d80c1b341fb85149d196278b7052fd47821e45b2a2e2face81992da2b430cae4b738abd7ab6a9ead0ebd5c110437418f0ff4cefeac2842e846c81c5161dbc077dfcbfd4bbb4e08fb91c74d563415a1c3df02e2d8c68e1fa7f2346b62c4ef80fc17c9445ec7df08246161f162d7080450bff6f5e7559509031d705055d21c40a3e56b61d9ac9a0b4535343ffb6fc6597b8ee47567d89eb7ee4aac2016dcf92aa33591bfb7f76c9ed53f4669374144085140ab8a64033c50bdb1450fe1fcabba680c55c1d836d1c9b6a1704c6bc0b025e5e76e52e082c1982c0ffbba488e2ff613c6dda7c4da9b610779190663228bac7cc8d4536e55e75dea7b4a9b2ecb9b3472bd3810b8a312e287880e2bd28cdbedbe6486f77dd219e9aaf9757250586525d7e4285ff9fcd74d493cd95b97d4a736a00c0850e97256ebc5e11fcbf13474e5ce004044e3ce004007e36d366d19b2f210dcc1a0a10e60105181a84a1d744e9420f0414017a60e68aa033009b345bfce8b2d9e12273848bcc12323d2e269470352036e6ccffb72c1af54486f4f6a7feaeddedbd35e261d5ebb504035c0b68bddb722acb9eab90d055e72b47328538ef52d9e3bdbba690d08f4f92dda3d6f4094f85381292104fe2d63a15475ad3277daf29d5cbaaee164ab27bd49a3ee16bfae40b696b57cdecd190c0f21c297b34ced9a2d7eb082c8ea0721473f591cb08220e8023c5957f9d542796c3c0991b3e0b96344448f97f25ef2a4289ffe7f6c62e15d14211b3ff229a8ae08ae8d988d0c345849a7f2e0a11649edb9c5d8366380f8869e3b69c1828c428918418312b9b4e9d419bdefe75a680e8a0ee9b9eff3a9d73dd63ff3afbfcff96733dd23810d3e312b38198ed8536a0246f4ae41a624c183cb26b0897105c3c0e08385138610f0b02d6f603841f54d6c0e9c205c4983c80f0097a29c0f543091f54eb542b8d5d5d2a165d91bad1cb46e9a2a13755674fbcd57cd5ef537fd92a165d91f84a33ea6d53dbabfa9269b2e83ad3b2a827332d337b6fdf138923d5a25e2f105c3c08fda36bcab24edc4a3bb97b65d73064b81beaed4f36084048861934004194122247936c8c174e48283ae2ff03f00387f5ef72a18777b970c3bb5c8081a5f32f4196ff8ffa508ed90a62fecffc3f95ee009c273698231673c04a29c6ff5bcd61011c357c931a1a28c0961e130c20e4ffbf02221ebc444d10c4ff0761800e0ef8e0ffffeb59c2042a5c5f1a20c6f5258ceb8b0cae2f24b8be60e1515c5f9ab8be24717df15c5f38707d71b9be4440c30a34b878a82a0d851f23579d91fc6d1bca6e9a4804d70c3ccc40c3ff55f7c30dccd534ca7fce18dd3fefbcab485026551d52d521570c643e7bff3a5c88c7afa6db7688af5074825676490748c782a9b62badbd49e75d6700bd65dfa795e96559a7b73719a588524229a0944f8a27254bf9cf6983a5f4b61ff5962d5a5376bf29114fcd28ec6637ebc4d7feba3d8a966997f624e6ead8bfce11204fd5b24635a22635229756f4aff3f3eaf80069696a93ba32e957f37f1d233a426afdc93f4e133a1cd43a55976bfdc98f33be30fdd729f2ff40742cc8deaa6350bd65958664ecdaf9bf4e8f8c5dddbe5336ea0527aec6a6a9aad39d729a13379dea49b1dda17ad58f14ae7436e854e9ca2abd29119d214fc5a4b3e5dcf023d5d100c70936ff3fdea09a05c9d620b4ad72b5a0febb5810f3ffff3a19e860e0fef878318077b1d0f32e161a88b934d70a6d9c9494fedfc83f0e1b2bc854a080ea82d5a07026519c9aea77ed9bebae48dcf65ad974aa756beaaa7b958e751c3774ab673823fcffec5de61aff383be860c03b16bc0d20dd926c57605b07b0a7a6a6a42dc996c2784ae416a94f6d6f72fb948bb2db064a3618920d06db38cdb66f6f37bc056db0cd4d3be836377d7adafa19d46d3df6ba4666ab4cbb1d4ecff627e8a4b93e9ca744f8da3722ec4e5d1a4b9b4adf9575b93e4778dd5aa772ea53ba89af25575361eeebe5d2d8da9b4e759577daa6ea97f75ed537a094f683b22e27054399d69e04abb2d5ce9aaaeacd4d5d969dda86fa949eb1b31f577b0152d4454dba5239b9ba80f97f98ab8b5ae3bad37a4d8db9bd35ea42fe4fed978bcb13ff1cebe2b2a48a162e147009f2ff2faa9f9cb85626eb2e2d52681941cb989fc9645a6ad0f245aa4accd547ad4cc6b92b4b1b52cfd4a56a59abada6aa6a4b63a76c9c4b7bf576e4889b76a21915cbc66a27fbbae9db9f38efec39246b9b983cb99cabea2a59abd5218ef795b6a6acdbf6ad6551242ed7bd1ddab8b6772a6df5ae9af6c1eaba7a563bd3297ea595b81f9f0d4683c96828bbf5297db67a6ad36a8a3e1935216d2dbbcd6434de044b6b2aecf582c13634e57dab9d1b106dd5547a0392d17458394a508890ba9aa29b7b6e4044b3f45dd32bcdc1d6542b1559b93ad6d1d7dd9a38fdf6da4edf73ddf97f1913b211b601b59b555fdab8960b0a7abd605030348f3bda709be54d6db02c78c075c5eaff7350d0cc75a50f489d70a541bc4c738909e9c9a98c32cb26434e4c504fa6f94a69e995ff77c5d8b8622ffc3fd75ba01fd88f0ff7a9116d1c517779fa76f75935ec26eeda1aec077aeab69cebccc0e4e9b9ba3490db725e95ce05af63c1d3121427a694212576dd4caacdaa2badbf6c551c526bb891375dd33c383ba2807049a9ffaeac091794987f1d077ef72a738acb4a9395a47f7565537728086dabd634086dab8c9ca262ae3e32728ae288986cea0f95ee4e4a01674cc47daf2cd9f7cab41a85a33b639f74893f4fe4dcdb1bfd4634879ce3a6c0ca4fe7ad459cf7ce1de73cc644dcbea3bbdba1cfddadfec8252ae66796bd9fe0d98d604635399e6137d27a8c9c7ac4ac04841287bc935dccf904d18f14398ea7acf9ba6b684a9161e78da3d88da4f87560f6bc6f24fbb83b4ef6513cd1738bdfb65276cfc367eef4d78161d7275aa5c419dcbdfbd02f9f5de6e308d563e4d493bf252a66132a65389ea0d8690f0ccf9888cdb74f91a1699a27d506f31215b368ca2766d0f3b03782a1d87578f4bcfc8d27d9c93d8af9d4224aa2524a0dee1115c7cd35d98ddb8b52ea4c7a1ffa658f87e3e99d2d7bea0da2f4dea4a8cf3083baf7ec612750b817821ccd3b9f18f5b487c644ece124a8fe6911ef9173504451f1ec193f792a4fbe35f8693e92df89710e493c3a95e8078a79e3dca1b9e38f3c6322e6eceb835244c1b1f3c01ce67ca2641f39679b4a4de6bd47ef3c43347b1aa35d26994a11c5e2de39ef4e76ba9f39266275264bcd515c2a4794ec7aeca2388e9844d19e73074a65d761117f18f4d08e876788ba6298548a23dee197bd7d765dec4e4fc6b6339c5f4fce908bddc3796fad77cf5d4cc433b6e5d889931287b87314ebec75e1879e614cc4bb5799eceb65241c7a62279223e879e1e7815d18667067be39b871c7990c3136330697a8984f8ece2665474150871c8764c7a7e87d1e18c5a8fc3aefe4fbeb279ac5f1dc684cc4ecbba626c6e108bae7b744c5ecca12f34ff4ba4776e7d99dd92363221e7bcb16b53aea24fa70b844c5149994dd1bc7b1ebd0f3f4b0776e1465cd3755672e9954548a9a3cbf7c82e389fbd9654e547af814c12ea320d7ddeec42f441a2abf50cc1fe965cf1337d93b1d13b1e9618c43bc7b15d392523cf70631efa1d65fcf9c8c89d8dcadcede5e6beec7c4832d8a9dc8b93ebb107b61171371ab63bbc33521ed81e1477e9ff6b60776241a1371fbba23b844c5b452529eb9e7139f18fd72c875df3111833d504d7afacaaa451cd75756dd5db844c5ac92a41c4f94f34febfc9ddd163d1c13b19971f8ca502fec40c6b2a6ca5bd35ce05592609847929f60b775ff481c1371cca5cdcc9645439c44963b8f19ec1fd98d2238628ec6446c8ae35866ceb1479268d86db177988c8978c4a25872107330e439835b7778d431115bb1ed94897358929aa3986fb1eb780441ac632236f9aa598eb3d803d5a467a8a7a807aa072aa967f728115ca2624e01bbb087231e7588f7a94790e438325ca2626af9f077628dd1be3b527ffa3bbd29dad999b8baa7cb34d30397a898aab7fb377ebd4343ef1bfb378e4f1cc771f94333397e1925413e76a7a7370f6322ee816ad2c3bd3294eb814aea31d7d49cb12d17bdf69d693c6611f4c40c8a22ce3814f19731e66455aaca95a3d7755afcc88eecdd177a3b26e257869afd4d5b2bb2bf691b057c65a8d75b767f4b544c279de9d25ad3ac0009ef6608e6239c2c08f716355bf6d517006d03307f8e10f9717d7c7c8cf0559b1f07478ab81bf460221ba02f8d08367bcc34b79bca2e18626403d2c048063f185cf085185870c55abd1954404108e64d081e3336cdd826002313b814f064966d2b70b1ed9196ee6d05413290e0ed53144480dd0b20c0a94cc6fa3c908938d0eaae6e0b1ac01730903708c03a95ba4316c03e40da2200e05dc66e7127f7165385609eedee02e96c9053e487696e8073a5651ab0b0c9aa26ab9aef55a7e087dc780085185697e935c70328d06880813fa3020d436f25a5027df041813df4c8038f09dc61471d74cc21c71b3770b479c38d36d858430d366bd240e30c35669441461a0944600c31c24073068c2fbce8c2032d1f81073014fd1083813f73c21343cf53d4e65f042f04d8f112071cac0838c16a82e88b9d2e5cb6fcf72ca92b8668dcd1120293129a70f9ef62c29008001076b2ecd40240c692a69d233c4b549e254dbb1de2f120cde780830d251c3d09476f4bd441de8a6c6b57bb4dc9906ded2ac823f42ab9a30e228cc05f5a6021aac9032547bf13074f9208fc4e55f66821de79dd9d7607cbffceac294bf9df61572999944a75258d9d53a85497dae1fbfcdfd1b0ff1d730704299e519cc0ff8e952affdf028fb851f97f203b537e6a4d559a0b72f088e733c383a1783a1a783a2035ce1275b9d69f40b56cdb7a2cfa8519264e03ced5b23b4a517e27ea08d21121d9be2a77a0ec40fdef3c79fa4f5728b5752857bd5e7c4d9ff858f1f9e065fbca69e7839da29da61da6a51da59da49d273cfddf71d2ed20ed1cf1700064a7c98ed1ff4ef9bfc364a7a888acc829db5733dd42a92acde8096af7aa7f9e2178f00c91c05f0933c3bb43be213f3810b80df06c38ff394a03253408c28b7273066a3238f3b27d75b22a1157d32846321e0caee0c1e089ecb9d9ab7abd7295108b0aad3d4968ed495da6afb45297e955bf9a0b71242419bbba2e57d3284249768fa2a17c28c4e3dbeaa61a6789de1a67898484b297247b505e77658bc68c6923add6a9be30a5094125d93daa82113e57993c1578c05301e6a960fbd95e3efd4d5b2bae4f77e6fe17d228c0f2b4dda3dc7436e4a6333db5ce64a90b65c5160999f2df44884700523c93d9d3f67d3267ee0454e8b6ebb60441100441f0fbbeeffbbeeffb3ccff33ccff33c2fe79c73ce39678c31c618638cbbdce52e77b9cb5dee7297bbdc65922449922449921cc7711cc7711c47511445511445510cc3300cc3300c4310044110044110fcbeeffbbeeffb3ecff33ccff33ccfcb39e79c73ce19638c31c6b8ebbaaeebbaaeeb30499224499224498ee3388ee3388ea3288aa2288aa2288661188661188621088220088220087edff77ddff77d9fe7799ee7799ee7e59c73ce39e78c31c618638c3b4c8e62087e5ec64a32a4293c0e24fdbf6e3b1255f7a7b649d54eed53d95fde572b3d73db5ed3eaa66a26432d6d6fd68a55d175684913a96c6c5bd96ded59da941bf10cf1ec64f13c3b293ccfce93ff1740077f44039267e77b1e9d389e472789e7d181e17974ae3c8f0e93e7d129f23c3a0f3c4fce1acf9353c4f3e4acf0ff25d213bbd4764dc4355191935bc6de3625d2445da5bd3455d545e68c46c5b2aacbf10eb8556f9babfba9d5cdd53df6b6e9a9b735fdb1ea684dcf90544dc3b2e3fadf69fdef04d991802707f6bf13c1ff0e04ff3b0fec38f0aa66c254b4549a4e4c4e4ea559264161c2a4644a195252d9293d63db595295d094866aea542cd4481392f517698a5585461a525fd3a9b5c499f2cf72add8f5c9eb7654f7a4984b6b9a31edb4ed329aee496bf93c3f0af91f1fd6eb7081c5bfce163a5a005181efffcc028b5ff59acad276bb29ff2dfa501fe7a0f40a2bfe65aeb6dad82377e6463d61e29a54ac5012fd6ace07d85b9eb2514a681bc584b6515f583a41811a5a6a82f45452417a32999454909286909cca27734af9c42c424a2282d2f4edc1c5a36dd5ebd5dbec954e4d23f8f4f4ff5f4863f3cfbb1e1dbc9e7d67b5769bdae5bb0bfd088d348e84070a38cac829ca7dbdfea752a19f0d88fba4e617d25e5675dd9755dd9f2fa46d405f48f3d9464e51afdba7d0360a6dabee10f2bfd959c97b3f3591c9a2d5e163959299eb4aa221a2a723cb4ccfdcecd1424c1b6963c6b42f4c99e6b0e1d373ccd89ce3c89831ed4dd137f2cf2a23655d69dd107ef852c257fd93374ffcd3a6d2b389123762fcab6a88a1b4bdc9f566e8bf88db72aed9f699cb0d162e3752b889c581471c640001134703ffaf571c55ff1f7b532bb7e54c73e6b691e28da437baafc0023f7c10c2e1e0ccb3b6605df1ac29583ab0b1bdee6d9733b0a0838437cc80e92226040d928879bd2e5566d858b1410b3f5d885e1fac228cc14417078e58b026878516b8840195c6055cb411e012460826274baaa0c14546c613211e013fb8886b9298f0341bb8fcb8800ff2cb048ed8a2050c66acfca490b48506ab9d23862809c096fd02d51315ccd0c116244d62d016e4c0434b1e0e448d71eba0428b140118628a3247462d2d0039816a0b283db44cf981c84848097768e921410b1cb2ec9821bca16200893dc6a410986062460106a082510824fc5003e704255e84d0b4c51be06b06098450811ae590094c01210b1b12722c006ad2250b1262a86503b56709018503c04622d82c460b74e025284a0a5924004301f4006266b5341f1501b0caa254f3a1851c3a78c18c925aeb40121220c0e05a12345a5d5e4f841a002c20c2c2ca1dac2b2fb4803327204c5ddd8000139210a2ebe16a2a88d70f141d63ae321c66864410451e567d202df9d6c0c18a9599d797326db81c618580d365831399032bf30754d01e059859f96c518502121ba854d131c600c2a4b085a7ea01509871258b265375c2149b38cc2c21a9827aca400a18a114aa36222e4424f58186e6c60f3ed0705921074d892dfa58b202f204cd8b0f9d8117645334273b80614693110cd07898c8e8e23e20c97b460348e4282ab0df219a10c08d2e68c0e1a579a10594378b90778886858c3bccb85e06dc5c2043141f0ce0a29192c61035a71c5707350b2042c5cb85abd6e0444e0b45003744a3e3a24a55162ece920aae169381360b2721bca1071ea29d61ade20418ea38a13d1f00001d8aee00a30deaa34d400e723c6199401f514c40810a69b094c95f1959c24803cb0a0b60820f17a6506191f202123ce48ac2b2011045a078322498b971d9047c30b4316bc00f073c6584809a8db0bdc1c406138a664d1540e1620bb9c58c82324d783163a50bbbc60619b2904d80057bc496124b4f10256c9613c8123b0580812d2d781da1b2a18a8d000d18e268620bd02a819c215bf4d066ac3dd8b0c60c7b10a0cb8ac58993191498fab2762044065e50cbc2ba330489280e104503b22e945801ec408b99cc06a809c8bc90861a32348821aa20627a43968744bd12e06ea0f281b3410b788c11552abe36964c91c714ea0b2d3051c3484517aa9412526556684004d548192e21034a402a3a70ae0081e547a81c70a50c3ebaec08416502942e385391a17ab2a2a10e9fa88e4a83225018c087374957d878833592a04200ae24e1e60b6e18064784b2dbde387a6127338879fec4590d8ae36a2fc799b8f6665602700850ee1e76f9433fafeb74e61cedaaf9a269cc35df96754d30e37829fb4986a0179e631eb5d875dde1b765ddaf0265c737173b11cc7ad46407e63eca1014d131672ee6b1839a63f65dd3918f120ccfbebb477ebd13d13cced89609c7cdd8969324054aed79681776bd4331fcc48e7b9a9c9a927a7aa092f620412c7e2299711762307bf8fb46d1ebc00d72af8f3914bda935adf10eb3ef8a04e69996792f4b8f121d45bdb9debbf79888cd99968978acb56387f328398945d0033f32c424b83bb2e4388e834aea115960e1519227f98d7b730fed3abac73e8192875af7af7732443bf1f3ce3b4a8fd4df78ea0d861df86dd28e52e41e7aa23d8f2117fb178a7594ddb7c58dc98ee68f1c3b95453f1689e38c7a8a7a907aa0927a665a89e3665ac6716eee301d251a7ea3d7852307f7c745f2a4526927d932a1ba2715f544f514f53ce929eae17db562e2b8b653b55a8676a7ced9258ee3384ec42188f11dac39ca8ef7ae8b24b83f54f753d43111cbd816fd7aa09af4f0ce713d50493df8c341b0e42875f6f6e981212649deb94792c07a53eadd9de3b8bff33b439204b99b12243fac71487678f44074ec719462f74e14dd1e1f73e76171c3519218f4d00d863c771d18ea31266212a9a7a86d8d769f3d711cb77bd5d8619618569bb2effdf52d669044f5b7bf5a13c775f524c1f69d49b1de28c751445151fcf4f99d61ef27cb0d8cc191fcc20f04c390ecb2378a188f5def20984f4d7e797bac364a8f243daeb1ce1b6f10f5381ba597f3296ef11bf7a94f71edaac9ae268977e7ec06b739e2354a1d9263f7a1d80bbd0ed52057a3ec783f79c7b7c8c3512449b063b129f5e7ed7c92a038628cb3f869a8252ae607ac3525e8811a3dc34e83a03ec7b0a75176d01b39d7dde39dec21ee388ee3a6f4cb716de72c344aefe479cc1d1543b07727076322c6d2cd174ba939aeabef4973396e5ca262d25867781deebe0ff4c64f0c470f779ee879a09741fd7531a31d7be08e89b865abb48a9a53a9aaf630d7586a4a3d8264377a1deeb8478a21ff92586694e7d8f1d0eb5b9fe00876e8d879e7544462f67d7bfbba694b42b1ca28c58df9de79ef8c398a6a50ede952af62db268e0b45ecaa19f3c122a30c39dee7263bfe32b9f7e69af3a1946d2a398e771f5869ca73047777ea737f60c7f947d2786fc350882581926f4fece0f6c4afe3243fc30e8f1128fbd8791fe98560eec8bc4f322662aaad671fd86a55ef278e13db9000ac314a7d7e5ed6e8d99d24de5ca5c93ca8262a4d89e3bad10b961865878e5ec7fa13f3f9799d1c6322a6ca56ec0c0ca30449b0771bdc9d16f73ec33126e2d46cb1b899e338ee0b691cc7711c27b25660a129c73d8aa8b84f511cd12fc45d4cc4ec3b62f0fb429a17f255a4eaaae6bdaaa5ed102f5131abb0ce94b9f378973729629c35064130cabe47516b10e4e8ee1d14bd2f4af21cc1ee13bbecf5eeec204af3a2e4a2d7b527927d6f8e7a1eeea2f4b2f7e15387b86f10f43ccc4529762fe3108bbcd35c77e3b845796ef00c49116bdcbfece50eb3b4d821ea7524cf5f0e3316774cc45f28ce64a987c4caa2243b71937cefcf133badbb138b72d4e2e789bcc327fa718fecdf1565d7fceb229af9a9498f0c452b4a313cf3e771b493e0d745de55517ae0cee4d889dd798e9e287e66cafc7de4f7657c76b88738f4a828519004bf3d8ee787c52f87798af21cb7263bef9c731da2980c31044a3d9260d779077a2709e22cce6650a4283bdccf6ff74f8bded981dd78d58fda6ec52681c02a53724c76b1f34e7d821d6b0f4d89acb0a22849fea17d771d04516fdce0f7852814e5d6e4994774873bc431d61365f8a1baf7bd75d619ec5b8f4f32b635e2388e6b5f3794d1d607949decc89cb528e291f4f0d7410794a3b7f589c7b0eb1e8a4734344996132507b91e75973f0e6af244794cc4567df5dafed4be4f188bedeb36517ea4ce19a359e7500cc51ec6442c635b9474dfb17d5d326538826197353972b0eb3239caccb5ab22aecdd80df67090d603d57382b41e3277161325b8b9ce9dc77b3e3f8e379997b01a507adfd975d6a3c6d8fbd0bd4326ac31251fb9d6a326c7af0bbbee9daf0c35db2e628fc580f2cca3f8f54e04c9affbf2282ea0dca7de9d878e9df7e1fe75a78e2d518ae83e3149ee8ec50e7b60672951f253e37014478f93e3497a6288dbd7f59228470fec3e94c45ed78562ffc4d75540b9f9798e9d1647d4eba838862acb9e489459ecc08e3c3f1493bc8b1e1813f14ccb8e28c92e0c419044bd3d92e8c7c554edb011e519e6eefb3ebc41ee851924dd224a340c45b1db249a43318fe40812516ebd37888258dcbc1343cd3f3165ee488ea262b879c747ef0bbd214a3077dc134332d4591cc98f67214a510445be31f7cedc47b1eb6010e53982e7873f11dcbb13492cc6448cb1c802a20c7b3f73c71dee611fd18e747f283dafef50c42229660f1c41d4f3a1e4dcc3b98362e8e9b07b7ae71e4a54e47a04750ff90e31ba41d30c330f65e765141c31b8499d418cee9888772879b77b276a1005370fcf110c538e21e89160377ae0486a0ff53e960e25ef401ea260c7bbc6798805a61cc5b07b7b2433d6f91bb39e791c2b87f21473b749fee9dcc36ff72f26e28c43497621eea8b7bd917ba7f658fe61b07ddd1bcace4345d41bb1d7a123e6dec9b2a1ccdf899e1e397661174fd03b6322567302bace64eec48c86dd23511d13f19831cd7c65e8c8aaa1dc601fc78eb9de5e48a2181c3336595f4614837cfcce133cc11e7e5f4833bbdbead98765b4d55362d1508adaebdf476a527b5b1c3b99bd23d60ca5f7e5ef233d3c7aa70ef53e632236797f4dae67246efb6b447ac092a1d4a74633b8f5877a9fce6168aa5a9692192c59319421f98da3179e1dd8f12e92e84c2b8d2c184a91f4c8feeddd33f748908f31118333ad8480d2d37c7f5fb841b08fa2e7893111d73a9517b25e28d1cd33099ef90337297a9ccfb4920ba53e33e879e0a9f7e891587faf9b26912d94e2798e27f98d67df593c45302662f0ede3c862a1d4df38a224e9817a6fefecce6e8592dc587f7b7bdb03c3efec5ea742397e282a927b24c70eeb1db2522877c67c0c43efd32288d14f8c89187cc2787c65a8c742a114f9398e20d9bd4f8fa8ee3a651da0d4dfd99d19a322a877de7c88eb266e8a7542f975540cc7b18bb8eb9993a087c3cc32a10cfb17eacc3bece19dbbe8b9efda65b4a976880497a8985258256c3ce60fe3f0cc689775076319c0db63d785587f64e76d708c89d85b62915092a1d879a4d79de0f971afeb48cc1aa124bb8fa33df78e35086e0ef235b55a69ecca2a40297e3b77dcf376163bb8c1992cf564b4d513a1cc7ad49bf432efda3bf388b2e88ab4d35537711c8baea9292e513151160150fda19db83bdabbc7bbf164792943f0fc506ff4c86f7394f7cec361ee526a706f8e921a3dc5be37de31d174d38c672497d2cbe7d835cf1a25c3f1eb674cc42137bbacc766ba9d0271c6e29632e467cede26c1ac41de7557dd9ca29d9d9b61ce62ab63fb7bd252763b8b5a3c3b0f87e73e35564ffdd2649d371442396e1e765c24b3c6f82345de6529fbfebe8e76233f5134877b8c899877d3cc5955fbfe6aa5386611eca1b849117723dee11215d3ca552962bd4f94dc9ddede279e23c771bdb52a49ee65143cf7898e6088512f26629e9a7d567bc57588e36aabf696a89865558976e226fb187e19e53a83bd07aa49cf15479a5ab513c7719c3b63b1b844c5e4b47263ec7de09747f0eb449dbf9888fb9bb656e02b433978514f871d1ecf10edbcdec99e2575a1701cc7c9686b08ae18ee1115c3ceebe4276611cd1e8d457bba4c5367b21494d156ef95a1b4b60c51dd8dba87e749e6bcc9218ee3da7736864b544c8da504c57de6f3eb34b8f9d69fde9d9bec6b8ab88359b949dcc3f343cffe8124e63b2662f39bc296dec73dd2434ff123d1aeeb5fb896e887e630c41be459cc200e4159a977fe50d4eb421e9e3d0c794cc4ef8c35490c26a965d831de58ec67f7895aefadafba1155398e9b04f348ea8e8f5cf4784cc420d09552dc9cdc5e973fb477e19747322d4154f4341739d891f93cc72e26e2d7053152ac24456f04b9ee78888e1d45bb98884d9526eb70f75d656f2a251a43196df5924c95bcf3ae7f58f76edc1e39ee9888dd114fad2915f942cb9014bbb01bcf8ec7eedc201a13f1da5573ede69741b0971dc724d88558dca8063949c644dc612b7666aa3419b9b253bae5e128ca682b112f33f75010a3a117a23c83986cb2f5c93dde773f73efbaf3d43130b7ef0cea0cc571cc62388a1d1992b813bd90ec7ae49ec8bfad4f8ff3a380a8d47209c645e91432460000c468020424c31100304024188a8562e17050301cd90f148002548c5ebe5440178ad32846619031c818630c3000000000220333b80aa92aacca547144c509b08644b2ba816a2a29136f4bee651fbde01ce9f36bc3f346d25a1805597becc63af2b65259667229655124b5fa84e0895df5c73c00a7e547841ef5a4b5425fd7d4d43f69da8bb9cdb0e4d71a35bec0d1b892f8f40b23ca473804a02cb4851433904b9e622650194f0277f1d0ffb2ac078aa9fb24c6027877484e116cb16533e8cb558843e04bc2caf509f9d77ac89e787b204d8939d823707fc3d8b39475ce4386e4c8f9f1bd1919647522cb8061de5f98daea6aa6507d2049a55a0e9c236484ea6f17b51e7d5a3d5755fa0f2803c563166680495439919a04cd13b966edc09d4f9ca7579ecf4ec297234c3efd2833e6b2834bc3f759f35a3ac457484901eeab1cfcb83b50d27cbad55c0feb0f14771039050bef3894c30224bd033f1ee39b3e9d267cfc0ece65e3bbaacc86555252765d90792ef3006e15772dd032bf0503a710f26033fcd33eaf506d09d969a29eb214343514b3915e2b1be955ba99dd4ea4ac5014e25abe507419c0a0a6d0290310f3323dff539576cabd81f7da014f270f721d226996d18eaa8a7c079068d675f58bca5594501980fd270c4f6d81a0591b1ac0d4a51b13be5b3758d65e8c5b27c1417b8bcdf0fd804cc7e510c8a331460c435cf38396f95997df690f008569b69fd7b5512784c95f9a77c6649bd12d75870a5494b218f2cfeb4651f455d11c712030293789bbedeb6c2eb4eaf6db4a3d4d7446ad859ab532aeaf58e05281ce5af1d41e0873953515b5def75237bdebd45207ee8af041fa8bf625ebaf047df8abb4e15ef6d607f537a9bb4bf91bd88ba0bac37a33e59d21ec241c3105064132c8cef5e0695c28b6ac365e1e4695410c11d9e013e6af73a54962e19cf04ca384cf905b48e3c2ab40da0011980b7d40c08f2d22c1bb80e576dea2b5723c2b53936d4b864d480d4150e0dc975e03a807bc87ddf9fd1c8564c3422f20e3ba086907a04d23daa46ade08c9e3bca38de0f4d4224eb3822f7bcf82175eccefbc1a60a0096f665af42019ed390040a0a2898453402049fbc979b2930644da4372b493bd4ae7f6110138f5fb072b546891c340566e59e4b8d7864a7b208fc4642a7a084204fd625484881994ecc1805e08aab2667d8f19418366e1f9ff84879dbabce9bb22b9fa5cd812dbbb2450bbc10b94a041a0a9e4a36bf3e328a7a5f1347dce250701383a9143116e1494484a685ec071d19bf8e58f8c5a78c299352e2594a5bd9fecf3225d8f146289a1ed9f08c4e864661db27ef3cf1e63df66d67f9af0aa59532e80f1b7a313f65993184eed2d80c2ac245867bb005792842e5b89162e92489d617a60650340fe03ff63dbc05d353320630c79dc3f0c6e5c1c65926d5d423be33e2c4af5245add0fa041bde670a59fff53315d37559b45ea97da0a7b355e46719905809d00513a8dc379647a6270394323775f446741bc049b92a13259852fe754a866546b39325bb84b5b50fa31be9767a72a061f92399fc06b9c02d1e2e38c0987af8512cfab742100249f615293ba9203cb62e1ae1419aef5e4de38620a12203071cf0345f590a7ae906a2aa05aeef50a4be17d41084ad2b6e08758aa1dce0f3d21638ddfce08a137cda001f76584b0d843b04de9983a31a4fea7b8e2cc3f709118913196447df72f9f5cc65a0c7f15c42224a7e2b1d6a85a87208f34ac6c2b5927327d45e74b6f8fc820bf8cb7fcd50ae28ad45fb27d92b88b282ab6a90c4f4f1e0252c1a1a2e9aff35aeb712ed643c5abd6c865d18578734bef679ffae60e5ce4fc3163178d44495893fdd2a0ef674b55f0d8087380d6672e523930c53e5e9aa802abe931feade71f3f8710e04262751a44be16bfb7342b5a911eb56a3e10db1c6a04094d3f205fc3866111f4f648229849eef6f3bebb08c92e761930c014e9dd7d232a9b2c7227b1ec2af301eea8ab7ffe795b7e885f8c6fdb017402b687b702ac19b82e8784720805d79fd236328cb3084859158e39ea6e50e5ce65ea94da27e6db68dc681fdc16b27f07ad25745196357a2a88f9984b41a63a3d25a8189ef355cfe01b7beceb105a03c3b1e45370878207a6cdcf5b9867171f3951a9f3e1267cea86049ac0caaf8a0636e7e037db334be795644e70602f9e4c1de1c3d2f41b910cc9172c9088271a9ea392644148520dd1b4989da2a84572fb979f253f73aa5a72cca79ba94397406d79d0021d4367ded3e1813f755bdf069ed373ae16b9a41dea5047e223c34473727305a90c0860747181ce0db078cbd24b2af1e1fcc2cddd95f56f89ba313d44c3e359f64228e57d8dfc01619cf902c25684b977a06ad56b91e203c9b83b87dad02ec0b9bbfed486669b5c92895c9a3df42e6286aa9373bb617aaa36803d60bd6c28d185826cf4d2c800f74b400810f1bcf5cdf950c6e8103a94537c58c401ef01c969466009a49e298f9962debf44bf01061f4bb6c29aa49d6db54d1b4c5434c2d46bb2e47251c304526a9c13b18253fd16874d6e80dc88c2c5671c1310b15c8390fdb456f5711bc7c8cefaa337f8298acab2d08a7feb439b8973d3c9450f098c355049d40bf8e29e23d511a3b6629c9c489a8eed845a05789c0a94dce8e3a91bffca53f7da4f34db2b1439c09798986343e0ee80de22099482fc8620df3f79d9f7a469f910624e0d37450f5e440f267543f6bfc16ce2d8e31376f20d25f68df5e45c6fa6cf3807d42bdbd3326813d3ac56218be02959f017bf50e9b9f1584485ffceaade51640d1e0cd568f3c56c887abc3053d50b9797da6ecaf7b85ce1d5edb1f5ef928c466694aefad46a5481c6cca943693200ff45a8998b29352285cc80fd293b5aaca1432994bcc4c287bc81e10a2b6f971c0e8c3c288d67e7a4e723ae5b07f862101c1321acf840e1e1cc98b62a4c37a033aa73df986ae14c71b29b2a1e2fa1829e7f6cfb311d2fa01c8190412700d8fd6dd8b6c4e1e52d8c4cade52f176e675110b2a1ff4c78a358dda0d3c12ab6e607611b13205ecccf629be7b139fcefcea0db1040c928c8df9f34b19472f160bd6ea72f4e28625d50fb2892c4be176da8a03126a872959b887cacefb396c5681594dd3be86c6a6415054a55528acf4436430e45a5c3db24794ad255291e973c55eff6ac0d4af0e5a413cda1b0a6cfbf13ff44f2064d406484012f7c40df76673939d0ba571a8b04e154b5098529548dab13f0913ce26d8cc67090d42f6105a658d287bf4c43d496b1c7f843c2fa41dac44485f13d05d0aedb154f7585155b64ed74322f451ccb09481ff09ea2cd6a82b1f2ea1028472061e94627294999e37ff6014a9e10df05aba80fa23aac5c1c949ab56ab0417ba2609ea998b6f7719b8b664bd7fec1c430132ce7007d8f7fe761833e8c3ab5648f803e42eb41745d8dfeba4ac13464dad10749d476b0a8cb59e3d50ee51299232577f28e2780224f59d409c43401210c83c7365af00d5dd8806d28cae8c206c472b125badd1758b089ff6435164326a6f00685df7ae1ebc530bf989fbc11e4c5a5266120f711b0dbd056b0b37289933f65aa4b5c4b3e26aa63483f6619aede09f321ef10dd1e42844a3071dda33545c525e7abe4adb4f6842c9a86fb282d62e333bc2d02dbb3d07b9c6e7d05ccdd50e9360ebb900d0535410cc6ddbc5ae82443a4bddd7ad0ce0d26e956ac2220f77f3c75d6c7e50d011265c41b03f84b2e7a1f50e506349f3ba6b560cec2e742aa98f94170809376d6493b8c894312d0020730903b592c0d3b183f85059a0424a3264ae9bfc4b68e3d291e1f4ae0e49d96f1dbb34928409fbb9f9f1f22842b090b5423d2dd1b41d1828e7fe585e1d35b869496c823e1aa88bae5f78cdc66bc6c0fd9374fc007abeb575d45e988278a79a15a10f8eb6cafaaf07ce9bb9c5e98fd36dd405b405f77aca741f84fec54a73616b796d5d439c039fe3ad603e433378b6b81e4e82d61e822364a83089d49ee46554e30bcb9c61e0d647c0c0d98c2e9f7554c6fd133e57f7f85bbd32eeb7c786d7768278eed6907e4e69cddd826ee5148d69feb49a5a58e8c01f4028fdb4f80bffc8b099f4c5fc0b78024acc07d1f3d5ba3cde2de67e618a0f0c18b71cd1ec47bbb824628f7dd7a30319b1653187c417550ad526324bb1b3e75f23aef3cb01a5fff775a5ce470d6da758d23b994893575dac18d9f08cacf80e62708085c73699c167bbe4d879e38e4a59748f3769fd2a50bbaecee23d1226c8f6d4abb3582a1112e257a85ae3c68d5599f93dbd3d9cf7a1a4849bb16c43ef1f20c3e6c2cea60c33e3a67a0e3876b90ff4e10f78bf99a8f083e5a4decfa0089ddfd64db7976055e65f08d3341e22757a82e1ffe14d0a494ca93799128cda3b1fc3c5e113b7ab0fd1d64fade81de10637ed844d39dcb7bf95ed78c8c12f94044bdb6617f7f95d8f246c016e8c81ef9ddabdbe15fd3eb3e97406c0af80c326f911f5eaaecc0f8841dded30b857793a925cdc69ea64fa91e17fd096460e8ce3aa52cadc4aaf73b4c46abd250b6187f6f4f8623d9cf95b9e4689ae6a66bcef5423b13c8a216f64c8257b5f7f1d3a85193d0ced745e5e1fa9c355bc3fbafcc53ee57dc887db11cf32f3ef94515244673ab830b07616f41838f64086111e9f7402f1e836c360c872359e5a391ccffa21af1ff061f278dc17967b74e1982947c5c42605d242898f6884f45244e01f91b6144b7a146a033b0b3d6725796fa5f46a20e6fba196229f0acc4011d3a35ebf7af16b91a49677ff85aced510d50f93a55974f336dc0ddca42803bda6288a1e0b10e71f6e4e47adab7232d8c0f6b3823b122b3b68d0fbad58fa1f1d2b380b3474263f4d9ab5ff77f26ec54ab6e5b6db028ef23e987c4913d3f0fef92a8586fd066e10f682bebd83668e4534ca4064b0c3d5eb0e20b0cf412b61691a93e2a3b520de32dff3230225601ac1a4634336b6cd9be60e9956b845d262a229d2fa989bbb2f48258ce4a18f18b08070d2f9dcc802b4f433c6b8a36f7701e453b2ed01ad2ea7a4f8fbc97075d0abb77f7a53ab97b5147b770e606541c3bd7f3b02aac1e832133084231835cd9fbf00576a80ce0fd694264aed8df8e7302dbcf99fc36081c6b561e2677dcbd6bd7ab0edafb053244b2d3ce1184fde137ebc24f60a1d48c02cc56b53ca22b62b86681631b9922f50ff25a3b9a41627a65da80061f489ffcbba6f48906871865a8c4d2d52e849901565455e5b0d5151544a0982dbcd9070e84e2f56df050e9bb67984211abdbbf6b7eb74944154b0c8df3e90c8f882da014ce91cfb21ca8c29f8f5972714765c84688682f6ab970ca57b1145d61f9497f6c38db2916c0c25c5e2f2e3eaf567ac52ac5dccae5278f8ae59a51000fb7141f749906f22536a8e6a1fca8f2d9d3f101c37185db429d11e5d2969aabc889f1502241efc92e3e24bc4a847ac369adae2dedc6488c6540cb4f8b9eefda596670be6ecfb6e17b832bca750a399fb031c3d18898a8eeafcd3325325d668873fcb3355a09fc57ca19253f26ad1adabd5d529affba8ad04184c6056abdd9504e9000ff93233cfe01134545160d74a8e23a6dcb2f0c12674e3ce86d7cbfdfbd5e382513dceb56e9dd3d00c620ad676efc5bf43dcf104e7b65e779fed8b25a50081d2a3e1cbdb80a15a7f458c8001269e745154436f567a8ef10d17e64f5dfd23859d7651bf5c17a09022463ba4e007a0a5d6bbdaa1a17790834464d20870aed02c1a68eb2d1ae72cd04d235e64840119f9ec009c9b060cf814df9f7a767af0d7a99fb23e9181781639a640e42c8330760d24e5cb4f9cc9fb7001e2d3e43ea8ba7fa38b1477d5c9ac284ccd370c251127ea4a1355be6844f71c22a6fc1f0b6e96ec71f7e7260ea7ec4913f6c6edb30f62e319026c1ee25013dcab07973382bfa08d7616003c5415c4ee25664c5246c7372db0902a5615aaf7083f74d37b497496db0824f8eb2ec3459fa7fb33c36948a5105cca89328df572774cf024a0767c82feadfb46fd84023eba1b3945d338c1a1c59aeab3a8a8304910bbe00bf6a56861a9bfe0529edbeecea6d0dc0e67e6131738658335d348316ce06eaaacbafba05cce09eb425f14b2b98cec1edd06bb16950e0b88826556fe30a7c97b3aea58a83e84d3df19e31a44cb5d3f87b1e0b40adc6ab4e199c9bc991ce84608971bf25596e2db2c479eb324916b05e7bea98a337b92ef4138171039b6a367edd9e112b8ace31fbb07f4ae0d45d10a93e42b51ee6e77825b802d7dd198e0b895b00d7d094bcd370ad18ba3f360b4506c704299abd12040626050c9cbca3b74ee1145eedb875330c5fb061fe1945f58edc994ff31f1265ac0d59fa5e48a1f6fc67228ef19fa0b5179d79ae5c5d4fc2073fc92916ea7dd0ca596fe31332c5ceac7d4bbb8b05bbf5689543e3d9be452bb6df6104af5bdd59e4cf91f136fa2055cfd594aaef8f1662c87f29ea1bf109577d63664e57d9a7b881176edc32a99e2bf87897cca378c7e42aabfd8d8932de36be651ba53e8d28796107253e03642b5599ded16b6c0aecdd55ab36a0ea0064aa60366f5d531ce690edb5688ca33db8dab43e6aef5f5ba837ba96d478a80f9db532fa5217e6fa0254de13fb4d7a4fa50ee78575b05b09f192c5f0f26ff4c6ea02db0179dd76804ded336aaaa87e57d0b757406f80cdfcef404fdc19d62aa3c8940221be51844e693f30e99266b18f2963c0f04bf59f62054dca571471035097df1535a7e129c1bdd04e26047553431732b8720c74b35eb71db704eb25b3534e739d884328dae90b28d2510f9e5494305ce2419c4033641f0956c4696a285144f8e20222620b91119e4248cb2ecc4cf5ad95d9d924111f35f1061588d8923fb15e1b0d965a89c0d911e698f213a487d23ffd919e5ec96888ce64e7363cc154711e140f1ad1c0bcb68e75626b7a1eee33cd19aa8ac3439bf88a6b5c3c858dceac3e5dbab819c94aa767616ab926dbc5cfc2a95ebf92bf21fb7c9ac63d7737ed3b5a61f1967aad9944f224a66f8efa179571e8764ee59b402b30258159b2a3f6d716d348a170c812ec4c2dc26bac46e1dfb92ffd88964404fda6264cb0c4031c26a6d2eda5613a1fa5e1571a19253070fac1b8d58d1b436ea03098cec05c59519d6b49fb9405404a8178a33aa1fb00e0105d7a6c4b75eae2f8270b3d17c578f9c76200c4164d5bf3f1443acf6385bfd549f70059ba573675c1a375dc6416cb9fa16ec4fc3c31f9d98403a0334ce75291a8d3991e24b9c5dffd5cfeade9bb65260b5e6436f643de28b613db13f23d0981438db69f4f2c8b199d256bc979d8e5bf94195632b3070b963a99c6ba66be5a2fcc9ff60065e41e846a1f4e8000b2e74c35365970fd20e21dee61df0ec4a9879268794f2171027457220f4863fbf1140b5e7a7eba760195c4089cc918fe04a04d5f536f0e5cc5dc2ff7aa78228278d6005a6ceae75fbb0d98633c261390ed4affcbc3e79d5508d488cf4638229d74b216e29d546fdabc0d22e9dc88bb029b4d77bc3512ddbbe85c8acae4270cd986b05349efc559c8f30b1902560c8ef66b8c44e600e61277ee7af4a06aec250d08450fe92404574f6b1fe80cd9aa3a9a624595f2ff39d53634c024ff22dd627990c137daaf8e8e4c9cf956cb2b82db4f4a2a54e1a97cba5021ad93e10dbcbe8bd07ba09fc4b65df52247351990830c8a944c630cb621e65da8bc40f49e9596f84b15446bca6946a7a2e5aeca82e2d6d8a27fdf849139aa81c45c07bdf5aa3ec9716e7e8c809696174d8e03c11b443333d3a4a18448684d377175f30e9cefe7e3eb5ae2cb03c2d8bd048cd13d5bf9080d203f755711ff0bc60484ec110c6c644d7896347e6b15fb8b6a7418b207001f5f827c467da1aa98cb948130a728d91b5419a8a58f95b0a6efd90f48fce27cd25ce9535b4530979a8af2736e9250340ee8bf7b60cd4b934a9049c382b21563c98bdd10000d17d4448fa33a9e745a39a29164b1269290d5ccb40389123d18a859e2c78f02fb50cca80a03a2f0817f2eb0d69ea0af544c11960d0c7201e6e70e14f636966c4827d6013d62f176f69444677e131f0022d4e2e485b3cae76c644209ad1410de5b56bee1b31eef47c858a9c26cf0fff4702ff72fa17ac867a97073594f7fe3ef6a3f01a52f470db25c46623c786558c052fb91dfabea812dd510b71ed84c7cd1b5dbb302581b3f15415c02831815935957ce588171d6cada2df4a959eb15f128dac373b08c710249931cc370b3288876248af7a75dba47e360dcd49727c4e70663eb7f28a4b8ec38819ede943231a630aa4f0808472fac3d5bbc3fabde58ff9b6cf51537b76c81ba5b9a1ab69c2e725bbe29b839b18cc3effac0c2b8b786751a5bdf0d3a00ff969a7f8857c72cd81e311647c9e699f0b2d05a9e92ee54ea403cd52b35d4ba6dd07528f03dd4b657a7891aa0b4a8f2e21853d19075fd56454abc25ef060a9150f37e755f2369e1b06873c97a876aecbb21dd6efa0804fdbd7a837670c2e36b66f95c6c4c3e1b2778814ae322d88be4a749f5c7e5719dbc53f458980a2b01400a6fc7983b6ded3f02f583fed730d785f4c95850b805f48c6eaa3077d4175b2eb2cd98b1127d2146d6addfe780111f599ae79b4edff72e1adc2fe850e4d0b1d3f78f5701750c0a6eae963682cbf91bde4938760ee82f194704ac23e2dbd5ff3e7c7cacae737ba1a25f79510b147e1a5fe212012a7a78a5ae2bcfc38cc007f5a869218ff7194487788b2cffa1cc7dcd7a7e877ebcfddedbfcb98aaeff37d350e8c0e936bedf6b74f4d9b107c2881e446954fbaeb0ee2f07b1958f68a32340cdabd13b9e63b211944583ec33e482ea873e02d9a33b78dddb9c7bd51cad4e66e21f6d6b1dfca0a4a62cba4edc8e4e04e2e44146a6b322f101c9066d6884b36caec46ac2deefaa10ca05d81e7c6dc222928de4fca603f4af2caeb89f693805f4109740fe321e1ba33dde0bf96b99a45d307f85b9e8ba9e830460b4f14e606d0dd06140bf2bede83f1033a966976fbc6d267af516fad7cada39f758206f3e1d937de7d1bed0352cab5ea164373f60fee0d862f409fabe5f36cb07533e6b948b5d0631399411fdfd49bf5feca38b176928b0299442ad9b7e18b23c9efd8a625f905e06579003e52487cfbf29ce191508e9410f2fc37d5eafa62653eed56b8965afc58cb66e28c2a97e5d80e1a712c46dbdfde3a23fb5488c6b6ab3add999d4059cc8bdab3bf6b4b269a142595356322395ded8c6f51f0ede52cf13ec8a77a8205c365e2da65a22d636f4cdfa88c35dfe214b7ae7d395a5a41968d26bfe480c78f98f3b2d7cca2cb32f439e2babbc81a0536504251ff9f31cf5801bc07bd22d1c428372e7e7e0c14878d99e42da84cfe0eed1d7e9fdbee6be9660cbcad22edb930f00c504fe0c082429395c9d20c638ff28ad4e52e599754d60896aa402f24d0340824f89a1d2301e23208650af1a6c278616908a7df954139a5a8f5ed97fe4817194b3a663b46f78c2e4b510b3456070b905a85ff25cdf35627d52f776efb2f04cf32c3011bed2812c400003876dfb230b738fb23d3099b1d6074b805777b91f75e7e5a0dd40612c22bb9c1e807e4b84e9cbb11e3c67f3869b9d2afe56f8b4dfb47d1cdc6eaf8894b9f527434c7366d6f6d157476dc08b129174b75722c48c2154f0a25c743f0212ef9f8e78ad1d4adc9251c0d2a53f02eeea6815e3bf706c79bc390f022f6ec4e746f7a2faaf65f9445be726fa701e2d2efe95d9518596cfdc91394a36805b17e787d469a168ff5d98c351c349bb8139ea181f8c712226c26c16ed33912835a334daae18e8ffdba3313a11a214172891f331367f78fc5c3b1bc4296cda06ecabf02498964144c1d5ba955386bff137e1a6b7ecd5dd8bcae0abe408639f05c26f89d8bf583e45732b2ab71cd8d74577a3f177587869d63da9cc28d5f39770d77269e173385966926ae888324901627df087cd7f38261aed3d2778bb00e9cd74f82f089327b11977b738a31853205335de44b007401bd9b98fdbe8ca49bc57bd77b3bb3ebb500519f9a108d3f1f7f15d36278cacf731aab6b8bb4a8dd345c7bfdc1ef188b43177fe70e5afb5a5a683d440f89206f4e723826325661efd2c24a3513a69d12fbe617a52874142a8e6076383eaf89dbc818a95193ef2d7cbd995e68fae7e89ff106b07701c90f82ec6c2e18e69d40c06d1a66b0f143e2fa3dd4a981c6bfc39912c93a1fcd1a3a1bdce83bd8404ecb97305b74d163a28eeb70d55ba511840a88915f86b68844764c4fbe939b291372fdeca1529870ecf86a61a920a43ee747411388daed031aec217af5e46ea00f4639d7c1882ed04ebf2c2df157166bd0f7cc94f46512885c9815dd9b7d5ce581d866b14a0e9893520cd0d21d4439875a118366acd863a16006f8b42264f6727b6ade8042e5cd34440489a13eaf248c685278c15dc1def7c107b161ad215ab67d74604a02ee52b60919a3a83f60db0e14ee6b18b2c167ddeb75541254e37666ebf97999647353ad4ef704090e6df0d5ba535d48309498a9d3da15f87159fce7cf6cf80b169482e0bb82e13ee37b5ef6967d32a65d007f0599bc41a5dc6bc5a05fbfe75e902f30caeab8447920f7dc39ef56aa6cc1ff2bad077a6f00b78d3e93e63ad2e42e5c7021a07eebc412f3526a7c90a21797f22c9ce31bf78ac58ed5f4c156b663c95a2ddd7bcb2c220b8d89e166b8723c5d2b6d3a3ca90a0c3521e8431e16b9673e56c87ee1c91cf4d20a422aac232dfacb547e1db904594225ec9bf8e52c3deb9cb9d2162e0551796e4b58a3c3d430be15414cec044835ad296fc19257dceeb3409fa06585b6f94b74f19b10234740e5f2c791d9d00d2d6c4ee208991a83cc2d87107db14e4d687531b7216f9558e55b0d2742c855324655ace039528137b6c3bdd787e365bf61caedf1e6001625fa4988032fdf96c27ba60f31464939beb0281e0d1a8254cb72cb19693bd00ba3a721148b26799208f218dc5a30fcee78a37912b3e054fc783dbccded56a3f7ad4f2990e20b36f80ea080969ebc20736c1e582de7749e1e58bb93cb1629589a5ca0c8b7d5198397f53ef671a19a903075e6ef58e6a5699964b30201d392c3f3f9c72ef2fbdba85a7ef9da61a7a43adc8ff02323e19d77a80009c17ce622aa2558f4c48ea20d8f928990a687cc7435b86b55f78ac7e4b93f84105c19668572340d43004914c5a742509641c17f65a5d0be40dbf082641a8e5eb36806a69a99bae544f9f9a432c701c048526837d244d7f86cd5a80c5accc2c0c215fc3e04c2b89897d1ce46c2b5a3b93326b9b16988f6ff8782e34c801baf2ab517c90bce5790d27c8873d917caef67154f1768efcf00b8a2a4acc223b11ec155abbe8594d63788607ba803f804b74d3fcb0a3767e30646c4c71cb4792f921f8e79ccaa62d707346e7e62f29520c0e8c409e4724a37f34e316fe119fa310ea2da8cf2bb9487e659e0e7db90f8fde7ede96bcd02b2f0b8b6f5edffd9556d498ab54f2bc99686ccd2800f041fcdefbb591d99ff568aa61d9447ccc3655755f32f71baab84d8a74bcb1fa90cae45ae7cef3ec1813a98df9e4ee1832de2ad1cc7a07be379b0939b2e364745b5ed7006b8ff481810fb98f1204bcb74239330892633bde21ad3c7db838936fd1411a09ada95a3cbcf3a50582897631c5ed206ec8229e54b1b4043063d9dee944001fb808a4ff3c3ba3d08c6f9282e228c884e20c344753c36207837cdeebb6a943364e1dab4d7f8fcbd5becdeaf72b284a03b0f7eb25edd403ebcd00b5abdb67ab08c8fd501578c7f36273f7ca2a56dddfa8e28527bafe6d370250bdba540d69bfbb53168c8354667391875f3e531e912fd826c44a2dc710897809307e532de26326e99b519fb9018492f6430d5c81753b73b4bbbdca2718b2aa943ed63c969cbbae5085ea84bf91713cab7ae40c39fa4cddd19ec87c66be47c20686c2ba773f9de185fa8514d48e8a68347230af7fcbe7702b7fb0b6eaab0264b261c932b7ca476741f47f0072bda370ebef67046fab697cb763070e993109903c68766305fb69963eebc5bf87c0f057a24534c174c099f2c09e8012cdd0ab001528f5bc64780d4c91884b431e669065aeb504044e01be46e7e64cb7aee5ab8fdb3309b6303488b850cb68065a57b37f0e9614516208d848443c575ccceecf6fdfe8a9450b4919591506e9558bb2529d4c4126c00793f0ae89c210cb8c800dfecd85ebb21304863a0af570b9fd2566c219d428655f839ecee9e2d071f5a08d78d1ec971821b709f4334cf71ce17baffea460a7fd9508c0391fb6097f97d598f8d19bcb02b4d1501e8730ae959df6e7e40f8a4261f506239f13099f30405434f2027ac9dce736a730b3c9c33ce1d986672fa8233e2215f04fe4a0fbde95f4e95e26960ea80e94035da399c5f739b5343af84a15233f3fc5430e963397969724c0377f3cc8ff3fba07c274eab6a33beff72942b3f0e64854991bf3fb806e0d5778c47e9339e40a7b553992de495233f6e96ef6979d8fcbafa5f71d4aae993ea6218529b005619dbe0b5945369654d8f6987468e4d73bd47dbb723da768dd05bbc2e679d0f98be4e7fad692643d988001aa3e1df5622ec4b0867ca432a47a5cdc6fcd04453286c333236b86c6acc18c7749195049351c85a314dcb81903474bf57ef6b495bd83221fc200be0c49e6696fb2802ce5528b65f9ea4983fad05989f36230e35acfbd39075aadac19775cc5de59bbaeb6a07d5746e1085f687c8316a56a79bd497da4fbeb7a27916b84d6c01f74fb1bbb2a04f93a18b26b2b37ebd5533b8fe5320566ebdfe42f2c1b54147995b0c2e5919d0e107a6e5bc179d5312fc34bb61f5e26d079da4f8bf21175970e05e1541271e06c584bbd70aab1a6a7787247f1d1a9f51cb280bebc8d218fc7532b9dc591611c3c1ec8bff378f731482ccbb8fbabb2522bb0c4c1cdb5a777415186c0a45ae413e6bc73057fa057693083886b29a43c9f593ce707bf238124c6b47d5c08cf981f20ec655301ed39def9770911631fef18f01940388e10513f8b17ccd58c12fd5339116883f5ad0f9f827451ca8b8b7e8dfa32922aab129e9e98b02cef37437cbb8f4bff7207fec50aa1fae1e854ca50763ae9cda32dcafb9302d406894cc3b6923b2d18ef85b4f636ab489c41b32b0998e743708e9b38822c9e8d993b65c4cf2372b7d63e9db48b99758197beeb53f98f1dd592ee6a4c6482226f231948e39d9d851a97339eacd4ba79fcebf492af5c20b78cd9aadeeb696769d684005885904cf50a985c85e8fb4b647423791054a8bc0163460537a9bf609adf7537752a8ee0defa00b79fbc224b073c9d78ddb63998f276fb4430fcdf01634d070000c4456a571e775d1fbeb816d4b2db857edc34cfd787c198f9f2d12c62d00ea555bc4ec8f3e1ee6802737a6afbac9b7e12cdb110d83c0780d2c4309bc71974c90eee5dc86dbc029540d4e525a7ba033d4d8f17aec80e4e88412ce6d3c0ec07c934d9861957f49514b5e8a78016555fdb98f07affa497f3f31b609d455d35de08e2915892194d4e25e21a20708eb13b63fe9c1dfaaa41152ba941761f2adabf0def7bd3ddfb19d62d1ab3ce690b179efce710f97d96dc750698ff2324efb1792f7e92ceac16266c9e93ef9fd73753c898301e49914a080fb63e0a16c626ba713de0e890a9e1aef1b7c22262676eca573db5bff217c6c11749f64a1e35211dce70514204deaf85901189e881ba2d71841c6ff8ba848b270f197155719bcf60536e77ce488d7bc45945b62a2bb5b485c68cdbff81807e48b8b1adea3bdc2f96188f76173d2bc08539585bff0c5486927524add28bb2a1bdaab2aef3b5982ee2bd5840ec9fda6d376598e169c0d4f32da38591b263997c078b369ec9eed3cbe26710f1e261fd3f8fc7865c4d1dadb9c7f2c12e675753658a944aeec2477e3c5334619b6f53077ea19d7010189d29aa3e28175151248abda09b8d204fdc677b52ec6ccda15bb8e404a8c426cb5988e7ffee424f06ccad2df9d4004e492ad4b147743d96dfeeb1ad2b8ddff66b1427db6d1ff69089f65f5ac09c701cddfa1cbdf3497202b62e14b440575a402587e4c4bad247b2eb249e9e82dc95410f207cf566d212b4c0b7a70cffdb48971653e64b42ec70b0eeab84f797115ed60196a55e8a5baa663853cb0827594b0a86b9500d5b25370e861511adf694de1000911aac3af253bb95fc89a31f0b66be2c68e1a3da1a551cf93d3390d36312a0d316e2ef9ac37a750cd510b55faf3c679f1478047623c1506507c4b20dbc1dea1d0c67179858acbf89df1d101257f763c0640b1b4fb5c62e81d6b322f9deedc14ae328518ed89cf9c11d69d909a701550d242af51119cfe2ba0bcc2123a7974cb06eebcbf67835e3689418abd67f28f1a02feeac2bada8ca6d7d741a2e20e0c169129e3f55b2ad68d5bbe33af9ba32d9f70afced44ff4f179a3bb6833244a0074d130e64a7a729dc246269bff6d4ae5a980de8efed3c24ed84a47bf6f604016ebea9e6133943c4741d50068b2e4e41f9293e94ba223c9e7b4bd76980d122600b5c469ee5771a6bc699449829f4a3cf0f12b6a35d7238190a85da603eeee0575d7e568410a8119292009e0e7620cd5fee8eb4f8c15d499817f646cc4a38f2870b2d97c004b0961a9408363303289e160eb155263a2e20aa6b757a29d6a341465b878d0df9b2d0f3d92a6de3a19aa2622a9a458b0201b2e1a8383aa02fe609bbd94f871f470cedbfb52aa6ffa7ba9ea6826498f9c1fab174c4b3d579c8f5ffd4bb0e5ead72b70e05d8babbfda3cb8928ec6825631f575da20013963a1089cbf535e73e7672157fd61a9ba26fa18330f7b1b4dd6d78a63d302e5caca40d5f260cafce16125c21c7a006fa991a9d694559450ff14c05f6386fb374a3c5008aeb7be585d54e6387cb8b42fc59b5dfc94e30f163f6aa630c5cf5a0eb90fc8655ac1d93f4336e7189288fde928433cf972dc2059ffcaf0658efdcc4495fe9240a691f85d61c564ac509638429a6cdcfe8930b1d30932bb84457391c7e0c5c076d5a725274295d0694c7bd35c43b400694e296950a9e38262e7f8dbc163c52c19a1be4d303454a59f2499c4e9528abf3922d1894bf0bd5c6278aae15c20a63927290539ac1385dc22283be4b636bfc199abf25707009a8e5838438dbdf59ec2a088700c07f1c242d9e51cc4c0a84944dc9814e2de885ef6144581599d557f4de9831338d74cc387bacf28365d1b0bd9728e407523664535deb70d0d2e43a71d948d10f0c79f55d9687bef5be387751de2536c4e44202000caeb9bf7aab2eca6d14a9fc4c2133ab6300166cce8dac5ab493fc1faedca99bcc74a4c5285add0455d41b31f49007ff102df50d12c8119faab49252052cf1c9f25e221ac008c6b4496e6a772a3d4cec9d09aaeab9035cc914bbc4c01c7011b9e80315a58146d391b44ff28e1d8f606b43be41a430d1d8e3df5ee05d6e24b9f44d461b4a4b9e8bd181541be8f73ed4c1255d688f835f230d6933efbdded80cee0e86855f47bbec173eb0118e2c86d122685b862ff7e5b228fd7e0f9072635cca88d4b891cde29b7f5b4e8512765c0cd0520e80dfe8ee402e2a92f0d8b3def34819af74cbfdd6e06d660fbae4a96486743206005af60888e2aeac5fcab5765ee7b24c388fb9aa0761f8ad88f64f860213be2283ea54365382888e452f01ef5de8aeb83ac7245b49639fccab1133b133f6221dfb1ae4ed680f1fac930c8cfab4f396dc54eb5b439e35cfb04be015032c510abe37bc4f60226723b02dd8afe7bab56ead5331259227c6656f5cd5ccc502c266986bd1c8cca1dc345dce4b8f478c14121565375417d170dc9b6cd764f39af0d51bf006441f340b480e4dbd1184be574aa187cc4d8470893da74906a1531c988f6bea9d1d93480f2f66de4fdc6f286034e46ff136c7523b1f1d2547a8f6f4b16825941ccc4727fe614bf6c9f99adeacbd403b5b8ff7693ebe823c1c108b887a18609a8c53384186580b20858d5c18048d8d9a93ccfcff160e544be67129d3881c120cdb23b4be922e84e21017a66831096d54fd1f6859f40c5acfe0a4643aebfe4a0f839457c0c17125a04a55e604140a991974bf0c8c795250e3d6cd8f292fbdc43dd2256c50e64a9e22e7f39959b0e579e9d0cfefe7713857df4bac0b9fd1274fa2d85d2fe5ed41ce5288d1da9881535b1ab3a640cce9fb91b2aebaa96026e5cda951d8846660645e01cc909710c27271700617c58599a43da6d1f4e3c5aa9a53c138fee66a3e4333a0b6ac193ca91b2cbb3037f367d23882a39f6eea1dffe7965e23daf02234c65fbbcc0b832b877c0569eca487eb49371683e528a9520c582f73bc86aca57ef56d9e041a079eaf68670a16dd931f91d00be81cbfdd5853ca9b8df9a1d195848bd909e32ff515bb63447151d1e40eb85626ec496b3a797fd8e66a353515e30b3fa6a3e0c953a6349b425c1a513b93178dbd62419a983544f2821edc6396c69a238e43772fcad253f59227b6f781053c8721047482735fd7a43b266c0bb8a4d8c009c2fcbca4aa202e0a847fdb294ec15be4ef301be9037483c82563b883a5c59cf21abf1025281f7b35f20094af6dd10114ac141efc164b4e36ffa0dda1dc6b14e51c0e483ec75ee74c2cdea64b7ce672956a214aaa7cb4f4f5107579a43f7a9826b902af3f78c442ab7a4cd085b1b1202cb5766b1ec0ef7e816d45f430a60c4e068caf4d8cf44ec181714701e6d808680674c23d698038d8be88d29337ad2c3d797a01e4720ebac2ac7e2962dcf59c0368cc4ef83000c100e842a550d0f89885738caa5121523d1d86317c015e4bb2bf16960dcc14c51f7d2eb8d499dba80769c6cd0e55c4c7d74f4e90b034c1e75cbfdc8d7a95eda4276202e3dbbf9d14f7fdd7d0a30a8f09b91f209b22b1db6b64f1307e07bba701a5eea0a8166d11823b2de41a71986f67c585a98f1659d1b97c79025bd145adf542f398b94db3ecb7eb8f8593840a434ec627c765958b64e278a7922e4679ea4948e303dd7ebc4c3f5dab1b6ffc573ff6db662639e10f6a1766bec2e4444bed8dbd0b6b536953e6b22eae7efdbcb038c57e4b05f3b8f5c0fc2cf0118156153ce7df394166892af6c570d4de800297cdf1b1bb94f02ec59ed7721dff40f65121b6b4ded6f36b4db3dd307b95b7ca39e5a7847343ec67c1cf3fd1406b1b9176a7a0d5917dec567860099dcf45187d2fbcfa63ece59a71dc466a6291a92d44196c334689e0702bebff99421046a60a82b3db25280c5dc007b69421fc82a4e72dd6b078e8eee75ed1e513d9dc1fa0d36302b723260fc882b1a4cf5c8a457b1691cbe1362c14dc96143b34daa2b81d0b220e678e19548e129fd195ea53f479e807873f05a158c07ee1478ddc6a13510075ee457ff8988029e55529388703a81e78c72fee269c5ac3af3a4b7badbc26151387cfa81010d62fe6fd0ff43ee17e7a36c89f92316f560f152b03b66c6f81a953a19feb0696fe1eeb92d0e9ede899deab7dd6fa76be0ab5cbd7ba0931d7c7905c21a2e8dfab91fddeb77870c882fc5b634a577251cfc4269adc21a3835c5f6d2b52767f038ccd0aee015dd612f64228303bade510d9ad95fa9b86c7f6adbcc5a2038bd2ce275a51d2a5cbdb4bf4d99276f049ddad5b32d560eba0c48908a5ba042166eb172225c8e8f47bb76e871f5b37d1cf7a051d365a7f331e681ec7d65a58916943283f51066529586975d309ee4963cbc80e7c6e553c1faefadd873e755df422851327bd78b63d2edc8d20efa06babc87557bb6b6f5e51e74e15a4d54f5b07798c7878964745abfef50a806ae5c6fbe39c151321a75228c7ad13b30a03ea45dd7161af317d89878d4b5e5f23e2e8e50d01308fdc51d56f262e5222db53ffd5b120b07aeeaeb347ec6c489f8d289ce0e0a2924306540105680f422ab9e9bfa7d580690e075a62d2926f6953dac66dc3866c325367bff7bfd50f8243a834e95a969af568ba4f7b7306e392f0c2be162cbe4d051d2142244a1319730ae9697465712b96cd25ac44473ebe96cc6e9eb76805eb05af66a7302b515b3d9fdef4304c3267319dd660e6dc998db9aeb0ab409290eb78662dc082accba18174ada35087c57f811bd3a03f04a2b4350d17e28f663425eada2726d6e0f0f6be392f107124b03b4083c58a4cbd8de72e57c0c86018cadb5b68c4d734f8f9538d42d4c43c9878b5d7482ab3e8b904c86e58b0c85a2bb2609cab63b49549436c7612f27e21419ed60d3a105d753639fde3f91ac2361f67f980d81df794417ed3e2344d0caaf7513101165669a61379d7e50d708e1958d4d1b537751eb7cb2fde91951566d233c05480c1d59c658da027e65e39b15fd2e992ceb933b320bff6f4b8f966ae018f658030be8e0d9657e6381309c06d0eff6394d94f1601a92779cf0278f8aa673bc60d3f96c4646d31c9af7f4f093b1a8b3baf2d9347816d4b043206f24e5985c439e99f61ad0f68ee7a32b3d3ab1a8b314308e04e2fba109603187fb036d6dc74dc61ab9419221bda146cd628aa79a00aa015fd5ca24bb75c590bc050b83b696c3dbcc20211d8aef61c2af93b24eb95568c4b079580a98cd154132edaab95e238d0e66c61b5d3d34dc9c88f211bb644d57c6aa49d6bcafe40f3dae34272b2ce417752b4d3d940a285110d7b3459909ac20c436b30857f4e5839bbde29cc2ada0c3106d6b9791041a99a721e275253b7463184e415e37aaeda6229fca6f10d19272419e3bf46e4fa199725092faf198c829e53aea04cf70b97e0f8577313cc642723705b9fa8a956cf883f0190be4b6264151fee80910cfbba667f4442aee653b07e5d96e884bfc629ee5e904a73555c7fdd94d06c31d3ba9d201e577ebc474d9a22fd506df340d06eee39c0987d8372522539997b580750ae08e8d68685354b08da7de4097cbe78ef7be390d34630d8dc403cd103d6656b735b6cebc9be46b5648ff8e389580e07d200baed0bc853d01b198e05a50e097a9a82097b4194e967da15989d101c2ceaf67d440738295931c5c4c2a7d2615a57a28c6d2d1e119889e36442c96c56c20cbc626bc3381779ad214a25e9d4b36a74c566628d4128e6c4e631fe327f06b5fc34e7782f729d5b4c687da8ac7fe7a274c0a5403f2b56881bd85edfdedcdbb68711709f4707e307da4119602af6edf30308e9ed63491a7446d5d0037b58fb1d16d9ca62fe93c69a38549a7b33704e54d420d811b2f0b4e870d9c98e235ba836c3cd1f7b4a335dc4f96eb86139a70c942b683f4b41a2ed5325d4b2772fb545dfbb9d139201d9b1fe27a13a026f22d5f4b2b15135b760ff86e9e37536c34854f246d2539143d753e1e9603964d20e84a035d1bb5e0dd171102f0a33cf5c021dd12bccdcf73f6915e8d30ef1f2c373e5c8d3033b558ca2eab3266a1672a4cebcd0b148942ef49b310818ea418f66a72ba3c9b0223625103b6d59047669b007a83688615417f23c62d7d2c12f52c9739b7c349378626e13be731f2b1b7bbc9b463b8e991ec74b9b4b9218c2e8a55dbdf2ac5fddd1ccd3c74d2499c97654ba0025a9a3ba0be0291cb2debf23b48536e787ac5b7265f9120c1f56c51607b5a1568d0676f7071af28193b4b20aa0d017a19c97a38bbb7c7b3416691b1ded56c74ced11b094b0ece7de3def490d3011f488c61623d40064b5461475585338508095bc3bc5f39ce4948827bec6bdde570203e68c8237a86fe101cea12e3a314c4ba19ca434968a75144155d38ae833cc185007ef4f3bc7898117867c8a8435f211b9ec614e51c92de7a39ba93a0c0ceb45089502ca88d59411af04b6c954de0d1263c207c196568fd3df64c0a04fcfa573e7e55ebc23cbfa7996c1754c67c00e3f42f1a504f43ff84c9ee58d1fb10d0006b15babfc84b5fe11a6f27c2c14124dcf78f3e1ddf789626929b8122c2f578f899cc162e5f055b3d87dc4d094c610270e8d819f926c01fc63a6f3e5703ecf282501f239942563d8e93da687285419fbaf3844fc434f8b6caf7814cae8c86d0d75da03a7e1cb4e8b5e46ccefc38191c2535e8f03928039d9b220c3a9d0dc3d910992aaa6dab188d248c26eab21c84e279f3abfbe81120125544d2de39bc6c5a9ae56576db867c2d3931459c71a84cdfb7e47413e6facde27354ab20aab2883ad4ca6c60d64eefa695fbc97daf779db55c8911094e2a39bb9e43883471b7ff9cb377dd8a015aba7371c227f2b6e70a198dd16cbab039e2e0dc208d86d19fd37afba327bd1d4e450e822584c2450cb8e64be5869765439f9e56afc0899e0bbf47a556e992ce289ec5aacc98a5a5f22c9b1befcccda501411c35c4d498fe16f22448d00c5cd42df6f737d820e7539c1ec46c7a26143e528dde70c361b89214194794cbaac76d94ac88119dee248905c33e119c3a7a06d1ebfd7e082d165f44de70f66bbbbabd10908517b96b7bb7c88307ad135958597cef6933fe7a4c489ae3f81f03319363e480ebedd8de89102c014cd46797d36a4dbc4c0e46e69b1e94a41da73f0e84fa2e2723d5c58658b6458f827d3355d6fbda28e1acaefd030222a600ecf58acefeeb638fbe932a7b1ac9188ca6891d73e20e20a4bf08aa98403db3cce837d36a067a9735d4e6c779344cde2c38b009ab4575296ce879b445ce101859060dd090d74edc8ad05ef93287cb8ef6dec6d39bf4ae97a80f9b914a24370ab584ad5170bd778b79d143447c86ce12413d1272b55f7671fb43f2646ed9d3412a5748cbb92e28bc4a4b0c163a5871f76815ba3d0312236e190ab8ac8d175fb4e75b4ca1f0cea0cc05c573cd079fafdcd11bd524c0e91e09afb73d53ebd6a9097ef877a7fe886c1c64f0ca77edfe5142b67be8b5d680c21aa5de06dc9df97d4e3d1e88a8f65564d3de2fc4cb52e22ad038f7097ebf6c288c537ca49415558d04a2953408a4a446aea458447a0c943b2a16a38794b36e4f9fe1520a9f1bd9b362ff15e5b8a4aeab998f1c5e6707e2f10e9cf26654dcf965a6c38d124ad96840cbe03dd50a03aabd8d58ff2e82a65ccb0e3541ce7b3ac37ded10d9381e2b273a1b501ec204d312977ba8e02114c77b8af113c2399082e8bd6c3d953e7b7a4a17241bff35f37b128cb27916f1468d28d2dc8b204a70f25a8c7f54a23a0e019a4371ecccf450cda43f9089838d79e29998bcc18133748b41cbe11811999631994cd5c8c44caa7cb6081da549cc762ae68126cce94d3111eedf9f9bedfdf9e19f5705529e5ba58cd2a7fda91e5cb39de4881b490e752119d98e03fd02d5eefa28b43f3a36c66645c2bc5779fe6206f7fb7826c7fcee9c9fdfb182e576ea4e2f69be6f668e359aa3f959ee724fed8ee2f9afeca0154ca9fc73f70ceeef9503757ebb76d89a032da22f6c5e7e51e47042aefd5acc5c07a6279c934d21f2d3483d471585c68c3642bd2612dd67cbe16acc3c03f69213610c205cd6304c0a3961f4cfcba17277a899f1add9354e3aa7bb8739ec34301c33704cc2ea7837f669c8e11ae260dea386d7a1e88ffda72976df3ff0109e52de0697fe4bce9be7443f55b956b3f8a592e14b0c33b83042b804f883503d6f4f032b38162e918a73249f84ebc5f0f510beaaa05c5e081e33d5d34bfe307e40c5057c2a66bafef9aa71a0e7ff31f385c8eae987bb5ad70d556d13d5be37bece02087c672dfc03b444b415f13e4b8687880dc8dca4452e5b8c30420c9422a7436e1031adf0b7c4f41a94c783b2ce1c8682c123599b6a6d2cc239bd50ccf172d7ed6bd6a77ffe25445b148a44c387ae2b4fa38d0e76fee6e7fd77cfb657c11a772270dbe91142148a6902be217807ca65239c6806cc1bf142c12ca5b34fd6f85c89f8bd05b16ac5fc91ac6860dd38a8c6e88387133cfd4756330966b461a8cb744c0d991453336f4a60cefaba873b04c02269537db6f3fe08dcd1fed27a2c862dc86b788d6d3c9331643ccd77e389c5d16a2753fe0e2ef53bcf5df2ed827b86fca49ed059879e093be9d91f1c5922d7c1c4d2bdd6076c8f785de6aed30797943cf38c985796177186caf6f672921f51bcf4cbbb7fc29fdf25c6659945d4ff583b4c137db78bdc3f87df86686318d04389fbc4b518d30bf18b717cb1f776915897c56cbdbc2f725003bb9ec5d79939770926636e7bc3097edbe9c93f91c6fa9134d50934cae224573e523045e60ab65df0825f613c77b7622e9f3d3629cfae530211fd4f9aa33a73c8433a4ddbbeeda59529681f19e67c869d81935d92d1abeb7a3e3b967fa98e1ae393e7f62b93f73c15a983c294696fcb46cb5d583c555742c8aafc07defeb4c811c41dc7446400cb5572279c7b9c75956818177480cae5c8ad083cfe015786e0b0d8bdab5ef3ce5a3e85688b0bfa861150b43fe56754b508f540df5647bd8be4f0c7fc3fec0df4b43aefc6fc308c810fd066a6e4b862cb27c26f811ba407d60c9576d6ccf0665cc9112ac10b79cbf5ca978bb7e5dd6addde66ab9db6d71288c24fd877f6aaadde4f097c7da2ba7db43d1e5a13d2c21f973219abe2040e066262621ae67685997d59c10c95c2e357d20bd23d55e2132e3594f7712a799dcc56008dd6fc518bf2306486aee8d2b987eb7f340008d33565953ec4d53c46294aead303c4c3c04053d1495a622bdea1de7148c1f93bcb957ade422fefbab23fd529481c69913f51bf24807c57e1199f83f625cfaf09bb4cda90c0bc5beab4c62ec19d16775d2bcaf3022165ebebe7f35ac9cf79861bca2136125d2943ddede538cc076ec30e00f2027821b786927cb61541bce803ac61a80fb32f6a8b0a658b0b77f643c8c83be7a6932c7af29764d085327548711a983744310030b7be2421c69536e41e1a38439e3904114da32e0281ce7ee8b443ac41a22c5449de9c4f9b8052e6e5a1bb8a54c8a7b2884ae56fa2ccb780026d690fd336fda4156f528bcf75b8579e7c50d31565938335d0d6794a313920805fba890bcf3e633c1c3b708688507c77e9234c107de03e1e55be280cad7e71f265a6c93bb649273e0b75447801a74bf66e248a91920c2a11d4294bb5557d31671b8390daba19566535bb30d99e195e996643dd1a75883c8cfe2ae8ab2ac5c2d59d69b5581c4279d73d109af34196dd6424ce81dd3e68d98a4ab07a706c5d0d08d8507dc1a9181e7e9f867bfc56ee6d1cb7e32e4b9a74de40ceb675e98876596747cb25402ee1d7b957130428bf70e02fa4adbc25e455c9b7cb0c0a4ac4cb751018408025f61ab87828ef100104c9d4d523e2d6c369e3d0bd25d2f1f3c18cba35b482f9e32f6c5f5b8d11fe5b59bb40263a1aac6c45c9a0f07dbfe221409a386b03711fb317edfc3a6702c307ff035eb485c7ad29a2e7df0f0e129fb3d1a59154c164896eb741c627daa4c958897ab860b969456796e9c3185e5d1d55019bcce21f609b58e4950635837bacf76c02e7a427ef2eb3952ca563978001258e420ab603601d3f4bdcde27454325d9de1602fad21c4df95b30ec827f9f15c461251f82362d7231a4c0903937e1efb8063680a2de64b77b6e4dc0be880b091eb437b962078f144e2f2e84efb602fbfa8146cb819c5c34c1cc35584992c56586b3c146d27de89a1cecf66082b8b1a9e52738b3042915144e614832faa69b5a549749f2a60dcde95ab851019f504037a9362e6bc522c7ae72d7808723f8d5fa7d453d4930d38fd5c42a0f63d17ab908b3115a617cb057fed69af6edc9dbabe12da98c379fe75b8bb12e1d292ed22708f10110b8583f53759014c8468681dee8882ba8355fc8d2519b61defaa065a031bba01f695c72588467f3ccb32a089f1b9e2ef50ef92a9aea27ba028058c7e389b4b6dd3d109c827d7771e64000267304d9075fb552c61a527d4608fc91ec87602caa7731f2ca8036b6e8c27504270ecd309b639fec63860fd4a913461eed05c700cbacf648b7bde403f6611ca3adcceae53ed055b434ab7af83f22fe625ff6fd5bb56be0188ea97bce96a82e379bc646946349a92c2f42e9084d465a887f4b216fb576509d133844cb9a929c8e972c3dd0fcf0c0d7eceb762bd9e8c7174cfc05a7d83f5c311c6fae3c3cc54edf5084cc8fe12b34b196bed581179d8ddf6e24753b1de6137592e9b4aa34f4d19e104523f88b66a6f67375bc3c9d25fba5f7c2cb03c17c29f98e043dbb4794dc5d4352d9bc12ac1baf90ae4f3dc2f2503182200e68a5c5f6593898cae49aa80ef35b87150d922e665583869562dd61aec16d579c689cc02f1b0b511bd507e869b2902f5c8260bd783aad5bf659907992b9b3d4b1c9a79027969112b35bb30583a539942ae70ea1cbfc6a1ddbc1042c888634277fe2d260019b7b2d2303c55569e6310b3547b98a6e535fce26c344767b53c1e12dc022023fd5a5236d30ebefe8c7b25760fad60c325384010c8a188a7fe48d3af43ad9737df237e3e970e0982c391f458058f354ea9dad4e089f83ad1722b28f0cb1a465a443ca2a9350172d38c919a975c3b174cae8c5f2f43852a9589892578f122eeb02acf112ceb5aa63e058e1cf6b9ba9840a9bd9118bd04178ca06e688b4307da14d76d1a67679365beb35d800fd02f73ead9357c2446bafec606d4e7b57f334e2f12ddf6af322fa1676d25dc6a04aa0da50d1de12def527fe73fbd42bdd17a12c850003f236c4ae3a1e5a4efeaa384b142d8d06e7085483af9343b256754b4b5d442c6dde70c74c606e772b6f25aff7fdaf155256f34be054deb4a48740af710000bbc6b79c628443cbbcf2035f6ad1e9db946011b0b9f8752b0d2ad1e5cab288ed79827ec3193e8847a17686460a0dfbad5cbb868f586b0b2a3b25453677766d32dcfe886a1a64908ebaea4bda1832f01aad657349f59a66cd23c9c8315f4ef008eef9f81c658dfc7e416fd2af0e5583684367c5c08e194f1b87b3a66a9a30af84453bf64f2ee5d9acb596f7f956dd44fd2bd9c93bb28d5a1a4baf42ad59ac473172aa83b0a0d881d12a029f5dc1389ddf50126ec0d6e7a29e71a497660e889ac8c0a193678d1b5b41d973c75ef6014652ec481ba50e0997055f9db557ff5d48b74fc62e10b1269b221fa21174e617c6f317e044c8c7c8fd115504831e579d348174aaed6730ec38024e753a4c8ced2aa222bac40886fc7c30818d3b92b56c5055805ab79c05be53fa4ee116a4059bacab0af8ac8de5c30ea0b519e7a0c732966ec0a7859f49e6986ca35f82555b057e77aac3bfa9d8be60fc59409b642fbc6fedb94a9ab2945fd4c87b12503c6b396d65074e34c29903d7905ad472199109a3a0d148e3ce2889f1dc5e40cad0aebfa1d56470a403ad1af23bb471fbcb3c2d208cc21bd6130260c38a4870ae1114bfa4b89e8ca79160da31824e1f2b603d248b26ba74135c07daa40c1826a87a3f462f845d70cfd406e2e7ceee0d4a5a7c7942ae6d4ca997b2a8bc91c00ea5e56c5d46b8ab06f44cb2c9088221c5dc3e2447ba55cb824387121c2f013f756ff08e8a4d6ee785cc5e8d902355adc26eab1b3573bf3ffbe4aebb7e4960f48b6019b0808b6224ae70ae2b843fc787ac2bd8dd297b35c4ab055f8399fb1f1dca36d6e23e4e622e8e558fc2e4ab23c8e5ff06867ac72bc6a9944866c9f934d277d3e3746c1b28ae49649ecb9e80ae7f32b399658ceb3179f25154fb8e413e1354aafa43d83ac3221317e1e05e5a4ba3c3ff8a2f06183e2da45a2fcfb668d2624c63ab15166deee1fcf052cfabead1a059937ac665871aaa65a28f07d91c0ae4c032c803e563eae9436a3bedfbc9fccc13a491c2e5629b1cab195a7062103220dad0fd45a33269842d65e4d68a8e04186a58eab3e19721e4349dd74e1a5bc264bce2adffdbbaef135214e9815117014889dfda36750cdcdeb295e31a53a4d0af83b9d8c45e718859e4333eb63326a2de15fb38317638e18731859e04b4410585dbc9edcf22e685c76e96bb522fabc87537b8c3a0e1b14e3b682e2d40c681ae22c84606a929e7cbd015e8ca74fd66b93186dff915adef2e774bd99f6266532d7596c944d5b78347151802006b049afc698ea7394b04b79bc61fc5b31917d8f8542157274ac69556fd24d7fa940787e41140408e50e1a26df4b9d2290fe9f7d8d2c69488ba50b788e2026381523417e41f316e15291b62110627029b1d588911b9f32d7360fa32e1fe0ae0354898b20250d185ac42cf7749e924ddc70c230b8a949e0f6efe2c0fa2208dd25c9f29d57d111b623121aeac48b38344be5121ba97c7ecaa29483615151a6693a102845e0b9e55b11ae3587c8b15a3bacceac7fd910cc5949c55b3bed876fc50a9ad4fa45d114cb730b012ca490b9cb62e640394a3f925279d759b2b81315dd7847e3c1b09a54af8c189308e6e2d2c00a3261641300c648ab033579dafdf9b66fc7f5e34b6011524c0fbd040275932b3f067ce322f1d285bb8fe26ea0515f94d11c73dd94036567a7afd0f5b47391c53a1ef47f21920ce6d680e0935f37080412f425a370a9c71c631e5ee0a98af6fbf519a00977ff2211d77db1e04fcbbdc5af721bcb435817dfecbd861ef7c50158d82acb7a42853940d8115aa234c041e4785596247b27b549b2e4c79d17da87d3182d16d428f17806d63a5f4a87534e02ad3a4d8d7c15ba9425880a37ec987513b2c216eece8febacf554c972b66bdff1880bc3ae3262e4530dc9cb2df4690a9280c9454900900feee4ea19612f52d3f66781f72a3534aefc1bbad320e2054b10c03aecae7d68cc767a032f1acfdf76320a796b2842db7675bdecbf7b37e4eb8bb3778b29a2a0f0238437bd6c0b82875c3261dbdd45114ff9bd01562503bd9832ca38e0f3fdc53c8f629071d21941d921250cb2d2388109aabaeac04ae0a893fcb2b840b5a3e55460e08b40200cafe2d00cd3892025a15d7914fb243de80e44acb8c1c4dd1c7a7de825381ff3b2393c44f03ba96168e1f061c72e404e463f7db98c085ddf347a9ca9831b3082e06ed9ef5aba4e62fe6d9900b290836acae265b5945b7084240e5e4dfbdfab7828794fb53a3ae1eae7b4c383d6d465ccf50fb8405e2cafa1ee1ee5572ed83ad77e8c32fb81f083e459454b3e6afc9acf75d35983fba17e5a3893e037fc3bb93a8020a240412794862ead78462142650a2899121aaa343037708381d685c5da6eed9681027bbec56cbb9b75defee364d4f4f9c889569e601d634c1b2befe62b10531b5b7ec6956044ddd69d7ce7c2b8019b4e0adf5db36e05b7a9920fc00c3470d6c4ad6947f52662d1488df3e404563cc054d85f76d9a65c8f259d4c73576c150d38ecc85c70a0e252b595843bc5663bc241fe504fc8cb4e078a5337071f3df1b6070a3f012c8ccb52375c96760124aac0900ae7aaf3254de8412da8461c838838b91397c814dd0281e4a6f1f1dd4de3d707a39edb5f297f69d0bd1646d7b078df5ec9ebcecb178daf0a862b78a1ace6b11555f7bed6321b19ce58a4667aae4be894e5eba30d469db7f64e162f814d7d80ed00d73fdb3949fe42a6d0d69e70c18f7db67c097a23c735821d4c9b0e9ca0520678b47282a70d548888906b8f01f3cb8069f07fdff8cf21b24e5e42df82ef9cfef3f9c69832c786c87f52a36954a8533f41b3a7c62fafa661f8360c8aa7d07fa3c43383c30a20bb1b366c978afec8d6e791b34c568b37e4e0c566ff9390952e281b23f2063689f8ff06195bc3c3d3aa7f57023aebc47e621230e0003e7dcac69e261271550261eb07f7d756c47b79a5da7d703e85c630280b71e733bc8bedf8127cedcc5bd7c31d358fdb860fa3870df8c31e2e0df5d2511356029f792fb065b48bf7ee7828cae1234ebac626887fb6f82bdc02aac226f25b88a3616fd22683b0d5ffa1cb95d6085faff98716147f35786c952db88efdaff0bf1015595ababd050a5342ce400b03fb9c12c7bb23d1ad1fb6cd74360216c6d15375dbd3a3351007ab2400db25f834ba6752ce1479e212443f3d9ba989d15e489960f83b987fd80d3cc89750fd90ca7b3cfc27471f7e38734fb82d1638a8415716c6987ed6941494939cb3e2ec0e88617c26fb1e21d7c87bee813751ae6ef765a7fcc301f23cd3f3ad8a87e736ea47db70a1f631438c8fab066768cfee2b69ba3c5c477eca90ca55ff8f5a21a36c3c6813d284811ec0f082806147e5ef62414491848c25fe43fe9fb771fa42396bf624a85f7a4bc9de2320adaa6a41cede35f36d399f950687fda0ef80624d7a915ca3aa4f9b07c31efc0c1a2cf9f4aabb375a890b858c5fea67e7e01e9cbc3bcca0af0ae42f9137ecc010d49a2b0a0c014afb76e8b78edbc650e04e50ee78beefd9934e0733abd9c2df7d8ee43130b45ad4385a3def1a84fe04ee125c264f11cf421ff7b0e34309623e46bdd8e6708e10791791ff3d870799e3231c22d1044f6acd57a82e8b157c4d141ff1045fa8ed4a4ab0b3d2c7989ed0e6e2fa16778efd5653d8bcc409d674c9539d082d68ec3b2468310d66653df64e9999bda139d9385211bdbd0267d8e6545850d2df607f47a8726aa2f9491b12684ec24aebec843e49d2f7edc1adabad99c0aa1f6af141c3836b1eec524f08f30d1bcf5f8f75e225371e7153f8e3a4bbbbe9dbc6b5571f4759e24bd4084c41b987a177a1b756457065b8c38c0e48606e8ca9b91051e28b7541a788f1820a3437d888b2250b2bf773ea0789d21fe7175ff603c29604e04007dfffad7549200b98bde0809a34ebadd5b61a85df34d12b80eee0f1c3aa0de0e3feb8631001e9e6673461b0e0e8484f90ef50d0bf3f97d42da04d58edf88ad918fdd6bbe4b94fcf7d95f34dfe4a76fe073af76d2f1f3c358a7e7c2b70571094d4a56a352a3a53829ba5dcb4401f0489c4be5104c2f94ef686e857cd84afc1f8defd79672f8df4434f55af9d1813b6e9e510a8a0d46665794a6dff67e8aade3880cfe36e99a3387428e4dc5c8037cf483dc6bf9a56e298a104a51ba0cb0827f9116f868dfccc262d9ad10ce8e26aeeccdc1cf8560814630491caf460c0edc6c36961d6381ac906a5cea972fdd9eb9ac7144b38af39455131864e208f9309b89e7f69d84e2c3a47340ccdc56ef9e9f5c156919b6f56429c41d652e2f17f039a12d11ebfb5e0719bc906a1f4530e99250e8c2d9f490df4f91ccdb2ca1ed0f2a0ee547deebecb2bc5031dc8d78f315f5f579870fc44b9e4ccb3b8055468009434d700d952138f01320205f8c370757bcb338b27e6a6b4d07dbf459fa29013f67ecd0e75450cd52493d5799691c6503d2046a2ea24ec061eea37d54bcb82245c881f96d527c9a64fd7cac0ef6e87d10f34f1c39bbceb7c9be3c8a5d862c6a548012fc4ced81ee0b17651fd29d25d4f0491cc4e1649ea5f63089703cd86602a4b75894fe5285cc59e54edfd6403d0fa10ec727fb38fc57a28104c6365b05a5beae220b47e3a1c6518812bac76488786b2d2af641c5fc5297a5963daadb628f46e4982de2b5a769ad28cccb3df845ea573622ffd8cdbb656fbe439e6c94ad60bfd2c472b20e91b133e8a26c222e65de226556932c195fb61926add60ed33cc9a5336970d506960cbec88893b624bf20204d3bdf224cf6e1327a98155245dfd6d405336a01c17d654733d5ab3f1bb7e4342e8923cb909d5df21ce630e1603aadebb2082d9afdd70795d9301dc07f0f991455e36752657f25302fca4ff547257092f109b4a207ad32653d5c273f3b740b347b1cbf2c302e30612ecef8b8f2a94f1188379a5f514e921804f6d2378a7a5bbad8b3e12263f030961ce6a9785db1fe2ff960fef796bad70101416cb3d9202eb57365ee3a1bfa3f5024bf3d62b7feff5e82cbe0e0bfd0808f4d4dcfbab8237e541f8da59cf99e324bc3eaebf236132830c80324f64fbc655f0dbff86cad40323cd1c3439b1df014baef56807e0e24b242112715b2e27ff5a54ef79fe169f7e2f2e616ad138b2e06e4681080697f76668d3b71639d4bf11b21ffcecc509f8a6a195dc3cbe5b0a5e6ceb4580f4a4259c47b5f13da7239250edd68c35f982f5b5af2c463b8892f0c8d499896e7f8307b75b27b7d87a7875d16640ffde727d34e580815f351ebf16e99568729878e74ae83de92ea50b5fc31a18440b3be822b7bfeff0310fec5cb570f8875313c4604eb28d7026a0e97459aaef060086d98a6ee2fdf34ae8f533f0e98e950284cbd7d9b1604c7a6f2d5ff44bf46f41d6d3c6f71ba2220830abd642e9a74d2d831c1775e881cde01d62a96c06e9fddf14a3fd749a0dc9801365acb02f0a799aec61428225ec20a9e73997c839d8cdc363a69bf25bbf14fa68f3ade945a48375348315484797b1275ce3bcf1cf92ad993361234c45dbd2260f4fde12db4b33db7de8595d6285d87189eefcd08de10dd5bc850fa6f7b88cc85cd386e1c4d9e562ab5c630c6d86f01a1e2f2d091a0a7e07f8970f24abdbb8acb0fa71512901ceeb1ecde83b6ce527baa24388e1dc380b02cc0c93dc22fa03d655c262a4b09bd402f46399171fc2558a3aefef3792e9076c1b0342c0801616b1f641f9fd9ce9d56264027f2301438639b0e74313357be2739560f048e2ac5d4888976e9b7edf9c07e167ab4e7957c3b39492b9ee10653db38409987ae120c29e3e2ce52c3d2c7ea58fa68d11a03db298e46c353489b0ceb9fc5074046690cc7f34b553a42060fa12e19960aa09b8ce3da030b652b136930d0c0e93c26521643ce93e4c3af878c7c40cdfec6d309acdf49ffe196be469731ab8dcd702e62ed6fed29eab8f66130d5bf630b80b7249dec690dbb0c51f7434bd75c11aa903c58b09d11c44467753302709af0dceecc8c701aebb653fc327f66eb564888f81fd139320a27523420beb1c60cc541d995fe052ffdf14f11667027ffb5fe0c46ad4f30f8893e8a32636b6313ec98fd2f19f262f13ad839647e3f49ecaf65ce10c69e80752d18a7fdc751d4d3eacc56c73eb078b80e98c1f3c215db8c10ef0e2b81db7b6326a2b7fd356a9e0eb0a6b5fd868648e08976de8c43f37f614043f64d81acf907c19f6035964d8a1f77c337c3723d88d0c631853d3a86f05df7f55665c3330d84dd0828d88129e17d8aa40bb494b8d5a0f3c17c4d19c11c6566cb5d0a87337a657662796526661da396bbef7abb225f11eca6236cc3f18c47162ba5005d8451367372567a4c16df34613d03b891fae62d64021bdc6b15bab309c6c984b8be4e83e49a07439b2cfbc1108c63653b8578e986cd9108d26d845619993e0e2c24dc292ac20e53a9123b0d1bd180559300c8cc0d2355a3d17545cf5fe1e56c15c01d1fc44d067a89b6a59a1cfcf0ef2567241bd2d00c71bcb38138a4b45545bd36f0c997609fc85877c5d84d65dc3d4bac24e581ca9f613f56255f7b0639085308a5695b88e144886bc7e7c503c69d1f977863ce2557097d28be36650d25af4c4005d1bd3365f3ba9cf8103e65be4e86a624aca68624b4dce5b312f291de5df77511f6babc36631c76f19bd09faa179ca9e7da2f2f68d981792150f027bbe01733ae05a61597bc570db52bcd149966d690428517c6add8fa8e68841bce3945f2c07f3503d083723ee36ecd73a1c729e7cba84290701352147ebbe1b82bf5f34c4d6057615d73547890e7cf51f96dae7a9ac8bf0a40f8480488a2e946d9f2130d8f805b4b29299020029f79d9ac324275709f4046526be8fb379fbe9e948106eedba19a86d692f9b46ab10c8b90b6dcd3563d132a10c8c1912a26b402f374d3e35cb12d0a0dbd2f7fe2e08edcf0237ac530806b4399a50200c5f0706e09a1ea7b0885acf54caeeae3ca5cc23ded78a8316910add2401f24e4b3be625bad3cbd145214b1399678b8ea3117028023e0a53c6cdaca72b2a22ea4ead5a87b7fa8e1c6b7bec0d2bb1544df143172925768a806c56778fbdf4e93b833cf3307332cdc9eb2e194c91d93d9f9c97f5488d160e4edfb36fc6dca2be6e799553045f28cccdc139a8a11a995fff7715310c70d5b7e58bb46829838ddf7ac1b6bccfd392f220c4cfd0d90237fd68da4c36561cae2d0dde1e0618225dc7ccb2833eae7b937857277d11f2cf05d78bc29ee2c8e314cf313718b4eea092b437f0568d6180e7169e11cfad5217131c1c7cbc37da447c8dcf08a06082aca5251ab9f33a0ff62d28337bef4fc3c012690f0d6b05db065892fb24c224393673af83a30ac630bce4aef3df4546548aef67d09d2ff4d4f839b111e6c1184fa587d6411dda9ac3d731405f0acdaf6fa0e9f74f381e2b81e8527600fbe783cafaf9c79a4cfa647c3f7c983e2e06f155bd78819ddbfb30c4b5a194d655e8d4a41cf426a7aea59f8e3487cde2eebb2965ef8439199bcad3a681497c75769cfea464b19bb4c3b182e6721e5d375ce4165fda92fac5615bf610bd8870eb3002f7a356f28ff361d6fc4dbb7d2f430b195e0960d93eeafdc1e74a9f4cfea3748e07d70c90cffe5d6df5da9e5827bc7477ecfdb1a91b20d9bf187907646bdaab59e442bd7455cf2ca4cb79722a5abe344a2740612c2b285b74325b6a1ad8593ad19d266c8a36a236e04813c6cea3a135bf8fd3399c66b82530e149b470d91f0a93bbf8bf815e86c3950aed0c2bef52aff9ac16fc4174be595c767489c2dba7400ee1e81875aec76e94ab0c2221d4528824219ebac7c4ce15fe3a09d8f7ba8cf55edeb8b08bc99a9497b4f38797edc5a397cea3ba19e3baaf317e61d80ee3ce2e53bca09253ff6a3ad2d60e4c312d4cb1a5483664033b376c8b7f1f7f83bef921687f92e969126e1755444ed37e359be5ac79ea0d88b8f7ebc384490c9e47a06e3c83b68c5d00a17bf4894fca0edbcc177a818386e357e6de8748ed30755d83f789edf7155a0df5646d44ab3324009c2fe8284fcc24e32a4411c720294fe7cf872c598dc7b0bfb5e0fb0d8438bc6c5ba097b218ed1ab6b611d9b971e8b99b3eef3d9924e89e1878ca97d40caf4617569e08a230357ffa68c67154c62c201ba42e33b896884e88636c11d3851d93bcf280c90ab52b8cf4b3674fdadbf6bff0d49b331587ad43efe13d59f0ab20f101ffc36676c3fdefcb500b1ed02464be3303a6a8bd88ab588bb139469e53d0298bdc436c82cc86cfdee884c8686ab2d01283d8f7f529136ba145a61f78eaeea67ae1e419696f52682c04c385f6bd9015eea6447c59deea94d86ef8f60e20a3ce7e811f357ab6a6885ce565ef8cfbdb1a1cc5f2c9f0175f568e8c2b09c14a4c411002245860036df25e0e2fadd8576a2143b694fef465e0501c033adfb62cdad5b343fe36d1811675a969de9d3782436581d6cd8a89da2e9a59efd53ee8df57a62d581ce403e5a63f7e36806e1f2f0506675737653b6d4e96795811cc51c0778a277193dc4f67a06486ba6f8336ae0f7eff51cb6bc78a84ee440ce8e9ce04f6c7b72f8643296bfa408e753f96a64042889d78ebffbce226f31fdda9890a0cfdc0daf67b7e57df05876db1071c8fadd115078d1a1066451067f36033236142b292ea068c9d4d1842905e597f37c17b33fd9c4fa29b8214fdc243aad59dd210fbc22346c83124546088b3ca08d7b574f4cb0e608a272043bc54172e8e4c5d1f9d77d29f705159c74f0037b7ca6588431e9ac856eee8dd7f3a4171e8811127365d61eefa970d7b8de8945df83d496b81afaefa4ef18817cde13749357ebf4dce359c7a75382cd1a103e6ee189eaa17da8fe07e9c07505e4da023e4c46b40f3360b0a4900330333333333333333333332bf59ff17bdab9911e0f43a4289092e4ae8864253e9d77daf01769c35f44e2f9b663094b103110010ff7c49471373f051a68e073fcf62905e94852a8740834cec048dee49ea0de9185a41938ed499ddd83cedc792b03933f46dd959964a946063e88f038eb476bd2cec6c049fe3842b98e4585101a62e0739caa31440f3d1791461838cda1759483b5456e0b1850f5d6d2b4f412add71c4a73e687b92fb016247fe5ff69eaa0452f70561531f4a45a2649c5c82a0aa0d105269ebd07153f7a73c88891b5870663cc2002a51d40830bfc4bdd4fe69c24c6fa6c81f36842ba14dff7526a686881cf156b42e3e5786d521a59e072bc8ee9513f648e2d88055ea472a2d6875d81df9e8eea2e243d5acb0a5c4edbe558df6bb5bd2a30923a3f3c4d21d7688a061538a9287def1ee84bf0694c81cb1f670e2f76f78a78a534a4c0efb746c7962fc7e83e8d28309592e4871cd875c78e0614f8207914415b72482a590d98a0860c683c81f58cbe501ec51c529a6819683881cbe951a40effce6c730c349ac045def2fea4717214e21a051a4c60731839f658a5de6fb6b151e3055ba3ce682c81cb1b19b572a43a91221a4a302d259b98856cb15424557ad6a8ebb757132149602553b465e59fac7a0c1894f182198c11831a3028a306b1315ce0821bc30516a08104b6cd55e3e4282639f328a071043e878c1eb48557be6a0da06104cebdb6e26793ae102f348ac0af07b59f5a21b1f2f5061a446053be56c63b4b9f39dad8d8d8d81a55956ba03104a62be710d1ad3253ec6908e16c91ac60a2ae5621555489663174e091afe5894610d894b13377b6edc7f588410308dc88c77a6fc9b2a5af88918512500d98a0c618347e50847229cb90d4ea2a1629426dcc9c9cc643d9051a3e60dcd423875a485fe4f8a217ec58da58b4714b1fffbc604353aeeb980355b5f18b5df0d144ff3bb9ab0bf62ecd7ab7f7d3889a0bba4bb3526464b308352fc9711c6e1b792d7a830b7e343f4d5c229855cac6c6d9d080096a8cf1c52df8683fc69ecaf669390e5bf095d382eb47deb298530bae72c8a1c4e491379f87165ce53789c894d9fd9d59b0298ee669cde9515e47165c4eb00fa9fbc43ef463c17405f5bd9ea899290f0bd63ff0eb2868ea8dd0bf8231df90577a7557f0696f297d103947bde956b0adfdb1b33efcf778b3822b4f5fef9efc432b7b159c788c11f2751c424c695530622b5175e94d05973448a5f638e6c86317156cc87d6b2175e468b73d05579971d3a2e6a0ef5153706b97ff7a43b4146c85944315cd1852ea4852f09e96b4033d7d8f2b390aa6c646ea2d3ff61c92a26023eadd6b8530147c9d76eea79cf7af5340c1597adae7bc1ba2c47c8253f11062a19248dc3dc1b7f54de95a8eb3aaa413dca9d55aafe7cd390727d8f4b431fd73de867f9b602aaf2559ce619ae037858869eff64c309ad9525cf16b350f26d8609adff7c9e3f0f512fc24dff030dbfd678878f08525b80ef3eb55dae4b1924725b8906ce5221e9460b2c4d72fc91e4bb56312fc07e192344eec9cae43125c8a39e50ec93c3cc74f24f8dcf8db1943a68df7810417c733a6ae18c5ce721ec187df3984e0631d72ee3882d5581f7d7ca37e39f61bc1a57c413cc42469a9c3308215ab8f8ab71ec729c72c82f7e8ca6ab921e3e68d22f80badf2c92caa1f6b12c147e8d3cc49b2fa8d0611fc678994b223837b901f8297eec951f63849481f6d08d6cc2b5855fa8ddf5d083eee47fb1d76d01cec26049b3c9d5574e4f697d283607264dd518be6403ad782e0f26e45c7a6413dc63a10dcefab448e1dbe6bba8060ccde839c39f6f8d2dc3fb06a5131ba59e55171fdc0bd25f31c6e78a0fe917de063fb97ad84d0f6b8e403a39176bc562c761c967b6077facef323e299877a605b733f47217daee82979606b4259580c1eb8f3fb8e0c0fe93a2f776052e7287e93d71d8e67073e69f66a7548759f3af0e1be8d644e3cf30f1d780f41f26f799787ff1c58ed8e3f453c7b743539b066eabb77da41928ee2c0558e4777520e3c7a0f6d6c6c6c10257c010726871eb6c4cad14296fec51b981c497bc85cba1ddd74c325592d22859248123dcd3adfbba7048b4955a236f081f9e60fdea6a9548740181108e3016170000361109cc1176ce036ffe247d59df4c1176be0a28b75ac106953cafc0b35f01b993c27f3abcc184f03eb1634b4647af471284143165582e50ef5aa2439dc57527f48d0aac8589fc10a1d8487d6b3187321f8c20c6ceecc163785bef04ad91865c460bf2803bf1aa34a47d559c3eb2564e0a31cdf3c5987db7b8e311c69923c92ab467473c95faf7eae6f8d2fc4c08654b01c79148f4cc1303099abff6139ca786601031f4142a7f2dcacaf912ff093af11275ada8d5a79813f3bcb9abaad8207d9d8a8aa5179f8a20b7c34e5d145a77b4c2117811a25872fb8c08717bdff22855437211b1b1b1b0a003b7cb105d63ae8d882848888a7f1063dc6185dce1830302ef8420b5b7fbed46521659031010f846177822fb2c0679d349bf4aea8f46381dffd0fb615b76d42ca15ece989d7a1af4545c50a9646a860d96d6d59252942ccba57297ffad0ba2faac087ff194adac6c5dc2fa8c0460fd39a283921e6fcc6c6d6282a626cf8620a6c47d2ca5ba1e3e9582a030622d8d83865c080061b1b991a30418d187c210536492ecffef690982f5f4481ebaecd1a2939884aa12fa040ee88edaa16c2d272a490a22a9447cf1eed27f58b27f01d44cd31068d14fd2131b2363636366000820d417ce1042e74921c6b1adb4f8f37810fbdbbd7ef30455099c0a48f1e3df958ae067b09ec8418bab692a33ce10b25b05e92111e068d3412b3b1b1b1a135aa905198f04512b80ef2e5f29c9af73f1b1b6590a182166c6c10277c81047e37e9c5686522f5d21747e023b4494f8a8e9e59fac2084c8ad1736feee8ad939c14bce0b4a0052d86295504d66264cf1a2af765778891456a025f10818fbdcf93c49c254e4e20a0a24ac01743e02f273acc93b89573b286aad50716510326a8a15f0881cf1c6686b82eae173431b2ee8b20f0e9ecaac377e96c33e00b20b0af152d2787a718effbe2078c4721d6e4b969bc102246aa20065de33460821a1b5ff860fdd82a8d485eaa600b8ed28b3df0d0551d913d2980410bae67f082dc662975c65fafc5c84a010c307066ec226d8d4ce9b2a826261d2229de84aeff1c7ae5a4200530c0400c60a0818d8d14c0a00537461931d04591c4924a881e11521a1eaa3efe48437d18528e185931a8a1980b2e846f1e4bb1ef2d3b189481300041162e18e9c9517f21ee792131b2de05301863026398710b763db859a4750d315a1f90c0c6c6c6466d61862df8d3103b267f5dfd6bb4a0b55ae0fd811bb4e0011b1be7052a388eded8d8d8c0622c00001166d482cb3fffca9039dde45206649009d80366d06216aa4529e948a219d1cc4d8386897e9cb7c39c912cd88cb593fa1e7d5aed58d486c2a2365ec185108ff24b7ecdf974056f154dca3ff77fd8be1859686394118319ada80d63051732b2271bc9275a1eabe045dbea7e4248e11d5570c1e38bb935f4c4cf9e54709f22e7c61c58486b2d2ad8287ebd1fe4088d6d9e82c9714abe26921baa29f8db4ebbf992eb81574ac107d963db8d1f5eeeed48c1b54b784ddd48b858a360db2dbe75e7c0fd4f2246168a825db5c9eb67b707668482c931dbcfce2e55f68818593028a306195a3e03146cc7dcc13394e4a91c072a78c17e823dfd8e08f78e2f45e6093e4c29e728d13b0fab61596836a0aa514585c558403ac1f8af5db579791ce4d0143083134ccc71b4971b296aa7498cac1af418302883eaecf9310e193b63135cbd6d7e092ac9a6dcd41a8584199ae0ddf2760a39b674dce021a3c5e040d6a81bccc804132dfc3fa4d7a36307134c0aabb42d8f3f52a497e0f3dda8d98666cafc2dc15da8764a0f3bc4c9b195d0245959081524755594bca0251a223b4b09b6f3c64aa1991afbc3a24d983109b6a5c30a0b69121e93046f3a1d7858da7134df921a30410d31664482ade415630831a3a4cca1356640826949e61ed53c9a452e0604f611dc5ad6a5b3cdb16a536264a999230a5142a5a45995452ccab3527c3ad4b427da8a9135060cc618831bc1844694bc5d9617b2b1b1b1810685198c60b27696de98bff2c576117c904c577dcaa397788661862238bb9c91952d76e07de32682af948e65f9b7624e16447059296e04edf0bf626ac62158ed48a3354ec68a9c1982d7f3a4df1e92445bab105cda49e4bf0e9eb12642f0914493488effd7e1c70e82f7a8738a741f955488a82038f11191cecfc8c1b31b1b1b5435ca90a1021710c435cc0804e3ea5adf195f2c26dbf60a3300c14e477feb633164774da730e30f6c7a4909bb96a8f739c8e8164460a930c30f7c85d08eb6eeb0a47f326ed01cd8d8a851da0726d685b00c397b602af1814b1f9e859ff9e4a883670c12060828607b503524b5235aa40896b2527f69e407a9735aba8891950215f855193388c10b2c605535c60c3db05db9b3b5b667b3dcf3c0e64afa38c61e4ddd0a1ed8de9cb19255456fed0e5c87377aa1d6beae2163f40c28b0b1e183197668daa3901193e7ce61d180096abc60461d6ee94a2982848a65470cabf4398e7c9122e6a6ccb931ca8881994107562a8b755d6574cee40c0c0533e6c07b9c99ded2e1468f837260829667b4751c7ed05171603d8f4551bf1c3d7e6bc02fe1c0079a6298d6e68d493b31b26a20061e00810d608002883581196f60526aaaca29ea07f1324e0a482919e9067268a56c1e39a272a66468c80e3df66e9790ee196d603fd6b69c3da70faa270bd4a82b0d98a0060f66b0818fb443c44b8f611ee74030630d9c8f7e94e25658cc08ff052ab8c00866a8818d132a1e985d85caeec606160d98a0460866a4818ffe77c94ae37176518c2c1ca38c18cc40839aed65dd16b9324a5d4cebd26688186ac522821967e0a3a8d38de699627e6e065e4b353ffaffd0a153065662c893236e774a122103d7b1461f0f2db6448fcd18031b623186da5821adba6264c1e078c18601020a6819c20c31701fb4456e8d5e795aca1c324a04c2000106c2d0a2aa615450460c54d082161c12cc0803eb714e93bc4c3ac5f6c1c09b468aa0659a391cf70b7c14ead1670eea83c89d17f8dccfa97218daed535582195de02a8739e344b888c63c17f8bd1c55713a78a764cdd8825129457496584694abb855875977ddadc5bcc00c2df061a6d89a2c48fe60e92c3029cd4d2dfee57c1e2946820003619091f52d50c10cbc48600616f8da2bcf49b24f060f22e40afcc6945692bdd42e3d428815b88e1e3978102a495448aac05b8bde8bb7f65469845081891551528eb3650f3b3305f652b2b19f3839f7076da8064c50830633a4c0e4c815d348ecdd8fdc0dc628e305fc01a2861951e06e7dcf3f65debff60956d528c2861950e0e32cf5ee89faef172df08118cce08f04fec460067f3ecf0c339ec0874193e594f2e592546e6c94613a062e2025cc7002f7a9d32177497bab765f98d104b6554b72f7e487955902c20c26f04144cb93464eef77395ac28c25f0f1c75b675f71f34e574a041a81194a6033c7b0bc8cef69838f1a30418d9f91045662aaaaeef6ff52f3612086ba610612382ff50d953cb9a36a2a88c103acd030e308bce5b768e9ddd7e9716c94a2418d2a4583330213d1af32ae75aeed78810ac4a87170a00232b6861466148109fdddf9517e9024a23a600611b8e013f6817dea9d6ec8036144200cea40181cc0401808002ecc18021f87a429c71edc4ed316831b80606ba81666088171d50f5a72e4d861f245b0b151a86a54926146109894304b1e6b3bf3ba3002fa67d1ce66d28291c8398c413b67abe42c780bf95672fe4e264959b0395e9ae01de6a0ae8c059b3983e86553dd2861c156c695e88e57b01e265d0fb4cef67705fb1962be0e66e3716c05db793e69db753b4456b0659a27e5f4c934c72ad8b2d897b552f37b54c1a79872d8e41cf3f754f096f1bcd2acd5f3820a3ea6ff0871cbb17ee5148c44fbab9ca9d5a34dc1856e0d0f5c2c7a9a94828f396306d58eba13420a3e6ae52e0fdd519951f081075ec1c4bf3e79a26027dbe3b7a8a93779a16042fee9db8a288b83828f1ed9233c573bff0413db42dc0bbad3ae2718939083c5d4b459ed0497d5f1c7891a27b84d11730e2c99eb669b6033fe76e05d6982df0f235ba8786c51592638cffb74d921c3047739561717f5c83eb24bb829a2f54ece5196c8bd2d4dccc1a312fc75f8ab390e456c725082cfaabf6ef963cbce99041f7d54fb71e6448a47127c5c51a67bdf61073991e07ca543f238079bea4082c9dde1d15d8e1fe4ff083e2587e9c47adca277049772ec318847b1b3c41bc17ebcb9e3f7daf49d3382d5f620215be838ec7b11ac5d4adf574821a4ad08d673cc514ce9463aa513c19bf8fae41cf273d588603d33f405b1548fef2118cb2bb34c2166f51a820d9596bca31ce9782178ffb4395fe767af84e03c6d4eb0ef8b3675107ca499299d831ca4101504a7b9b93b4787664a03c1c6cc51ac4754f75240b07e973e5f8ade1bca3f702b15733be8c8f0f003ffa179e820c4e8eef481cd711c22636e47c9463ef019ebe30a379536710fdc6a250f82e4c03d827ae02bb6a48f37329787796082e730d886984334050f5c0879f247cdf13a57eec0471ef4a4096e953f881d984e39b4cb0a9efc2775e0ba6248883cb394217460537b253a732a343207fe7db36bda49fa614e0efc655734ffac51ba8b03a7ed1a69e3b9e4e9e0c075ee86b0cd923eff062eee74103c8867c13537b0131d62a6715292581b180f63cacd2ce6a93336b0e5318e7ea09a83cad6c0c791c31c761d21f96ae0ff5f7298d2b98374a5818fc8cb9e3352a52c347057111deb66e59fd419989ca392aabd374eca0c6ccca9cccd99e3dc2903bf1e3d3f0711457232b0a9bf6bc74fbd5aaa31f01f327fd746ca714a2506ee723fe4204b523aa9c2c0588eeba23fd97ba402031b1eaf7e7cfa29aec717b8b4f9b3e97845881c5e607376ad69741c963abac047be7992f56bea3b5ce0c3d51072c8514ffabf057e7208d21f99bd685e0b6c4bfcd30e6274cc7e16d8c80d313bf6f813792c701e07aa1e7594e2a15760472c04ab94d1dbb40217b2a53275e6d02fa60a6ca69ceb4d3db6dd0c15782f1391909f6e333305f6fe6e42b97ff0ca4881bffed6cc21b3f2e351e0a4e3fb8fd284fe280e05f6edb35f738cd2883f814f3974c494f726759cc0871635739a5f7ddd0476d36be6a61859b34ce0e368e355c8f1275397c098ef8d9f8ffb445a09eca51043475a96c3af93c0c4ade49ad34907b9460297355bca51d588497d04b623afedf8bbe35cae11583d0dd7eb8d1253b6087cbcd9e3eda7e5ad96084c7f925aab0d79a21d02ffbfaf371a3fec0f21389691e32006818fe3c5b79437baf740e0248877ec16ba9ff2033e48770a661a526b01f0019bfdeff2b669fe487bc1e7e8e3ee0da2d9392fb890a04957fac31cbd0bfeb38434d6139ad705d77194d9cac3ea2b73c168bee051e575e052e282f7f20fc54ab3aae316fc8450399d75a81fb6e035a6348f9b6c42bc167ca7b09ea8d5d96d5ab039d0cd792224573b0b36996b78c594faa92cb89a1ceb85a44697c482edd0dfec3205891ac282cbb29d6c9a3e1ee7157cf8e87598fc73e4bb828bfbb924674ecf602b38db28d50cd92e66ace02bcd744b4724466515ec5e66eeade8e86354c1c7e579a2a2c95234157cdfe65023dc7c4a820ad6edbe3f48f7959f537053494b4f638672cf145cae1ce4c9bc68ed948289b512b572f4e96f52f0b1a34d9e92e3d8e62838ff2c9d7da73aa488828b109d39a46fe464a160c4c3df1ce9e7180705ebfa57a59f1db5f30926d56dcc75ad5d1a4f30b62b91bd11a9f14eb09139cc19b3c7ab2327d809669e39799c63496d82afc98d209afd7f4d705a9a3f77d4cd6c2698cae5edf12be5fa98e04c53a460d771b24d04c625d8e9f68e23f2e62d89c0b004db22f97fb5a963864e09605482c931d3525c2fcd0d3e25d89ad4f0d4f5d21ac749f039f98ae7b0e320d54b24c145951e0d51ed2711a92680110976f2b647e93fa7b6ac4382ef38478f4de6ab05f7118cae85966075fb1eef8ee003c9528f3f1c1bc1be6bbfe5cbd48bfe61041fc71d27f148937b85e82218eb8e24f7520875ff15c1751e3dabcc7b89d871063012c1a57a0f327aea44b38e47808108563b4aa9af94e491791c82cf9fb3c6defc8b18f1620876237e592c2bd3ff142d041bfef1e8b9476f4ad706d228000621d8d7982475a7778daf11232b07c1796976a474a5cf981623eb8c1103ab304040813068008620d8bee9cf1af1f6ef6831b26a9c61d98037369e004620d8d8716a58fb9547538701020a6c6c90c10218c0400c238001083e6bb6fa4a5a39544cf9031f7654c91fefa6f08ff981f117eb683d58c88debf581efe852c71da5d3a43b1f580d4d1afa515bd490438cac1a662f08c1c6c6ef81bb9cce2baf8f6eb01c62a4046400861ed86dbde41ed47ab8394e4300230fdc27bb1ca694cdbcdc27238d0b60e081f7bff48f5b2468f9bbb141d400c61df8a8397d0cdee6f799aa25c0b003962f5bcc1db68798f43d805107c663b26c298344741c4260d0819bac48fa795f3921a400630edc759c43bfc9b1e418626264e10ec090031f07df0bb61e73960806301883e4008c38f0ad922669598d5fe96264dd608c32e0c0795beab8c35be37e6c2902186f683f8a943753d607ba81d18997a3dd88a74162dac047e9618c9c94839c29c806c6eddac389115352770d4c921062ad44ea1d330559651c0aa8064c5003004100430ddcbfdd65bd1ccc93bf06e0021869e053dc8e2d55c7b1c76d6c78fbf1f602f0020c34f0b69a6ea93fc791f32d46d6d612c038036fb1c7b377d8d1dea78891655700c30c7c941b3ad0d4ba1b2515060828108130b0580536364c042a88c103c6704106c670810b2e0b609481b3f678ea1abd22a7bc1859859c08541083071045804106d6f244d3cd1cc7ed612e469616119441060c2a40060cc4a8a1821834195100630c8cf6df67f28888361531b24a6d6c6c6c1cf3820cdca00c1594b1b1b1b1414604c20883037f64e00121d8d88840187633f83e15d8d8d8d8d8d84011b4c09cdfd8880119698023802186bb3eb69caafa3e2646d6182aa851952000230cacc761da28f952948ea5e8000c30f0492c567d9ef43dcfbec0f5fd4f88214acee4e311c0f002ef7194e24787b1a30b561820a04018e6011108e305c7055e667083314040c30d60748149f6d1c42c1d87fc8e22461619c40a6070818d59967a1f8ea4d83b185be062908af46c5ba2b9d40217aa5ab7f34cb78320185960bd63a7c6aefd07606081abfaf032658977e039c0b8021b72e284e6bffd5002c30a7cbef6d190a35899994255e0274b5b65a8800c15d060c3000105ca5001192a10418d1a1b1bbfb121811a680830a8c05bbce8d1f143faa04b31920c2080310526ab47fccc5b51a23962649151a3063d069905c09002b76e9a3cf5a2e6b4d602c08802fb1e7a44eefe5072889f0bb4e0a0c06ed408bd29cfed2e0bd4a0c0c6c66ac004356e00c613f88f52113b5e11fffba4e09cc0c51c66bc5af1e8e3d81423abc7383380c10d6a004613f84866b9a36e95cca589915523062e3057317041043870690083097ca7c9c9aef7d437826264556df4008c25f0ef29645a757674a1a2023094c05477f4ede7a2dd625a0b5a05658cd11cd8d888810b108c24b07add3e1ede3eea123090c0862c390aabffe83498476063fa38b0607e9f2949140c23b0395393957694058c2270199631db9587c5046010819fa4a5ad95437e4ab1010c20b0b1f1c7063070cb2134414d3b553c9d9ec4c8222b004308f994d7f6c7ba5152bfb1519d002308e777ef74e754b66830f88d8d8d8d0d5a9bc1093080607938a6f14b4cf28d8d8d30404081304a8d51460c008009307ef026af5cfa8167768c82e103f2e6e497b336fc72f4c2ca69592f3d2ccff0c2cbedd67ed9533be22e7a89b993597a6841cc1a6a478dffa652418f0182bb030d5d94c673564f4d8cac303e6710860b542006191b3408c373066110c9c5da99112b248d16d1f5380795a1aa3266e0821a2d68411b2d63061978c1044a0768e082c98efce259166babc92dd8d2bfed905aa2d95d1b1be7373668d8820992f15bf76ed32e550ba652c4547d919f26850c18c440035635ea3460821a1fa0410b3662dba50e633d0bdee3681e27fb8b1272109205677b77b59552a8e82a16bcdad7848cd7f73978e080062cf8702473b68b9d5473381956afe02e9877b42a5a1fe7942bd8360b5eb1323a4be62d6841531268b4828f10a3e5e620da81c524c03458c1b4b5dbbb6dc7973f5e8c0c03041408e348e056c1df6f4e264143e5b07f1aaae03afcd0f16fa2471d98a9e0a463f33bcbbb13df50c1c64bfbe97f221ef277be461a1963cc2002649c326e00011aa760428e1bd9b73f95cb9fc10b6898c29c831c5688ceb1792019344a514c492925cb22493417eb4aafe016f3c71f88a4301aa3609365670cebbc71cb210aeeb39b2791989a13fc50f0ee4188216eb953da028a2c6874ba254929da91cd3352c75e71fdd255f4092ee4284f7f2f7298e3d83b4fb07f315b4c965a2547e95d186874828bae1ed629794e1e62c609a6fa473fe58d128bfe26b814f69eb1c32b73746a82ff6cda9b163fbacd5f2678dfca488fcaf2826698e024e62093e55beaf04e59028d4b709df343f3e7cf4a316b092e57881ff65fc84a41a75109265d82e4f59e976b9894e034279564eaca5339dc88c624f8203564f1979ced3a87185948c629a33150041a9260728584b859736e841d09c6b3a654e7597a27b83420c1e4203784c5d81f4fca79049bed530e53688cd09c3b824b4bef9b358f9a77aa115cfa8f6db7f3438c2c31c898410cb68c19c4a08c196c0019c1e4b8c314db55b3aae33416c1e6e0512be6fe8598dbc6c651d5a8ab83030d4570a9421cc91e77f6d0311b1bfa051a896043c71f871e63270fd2bfb16134d8d8301a5835810622b84dfaff61742c8bec21b8e0717c52f76511238721b89059f3935804fb3c6b542158d1d89fe38cfd71a987105c7dd25cbb79663f9a41f09ba7d3c7f96204c1796666091ec4dc5af336107cd0fcd0426693c388800736368e4003106f8b6b7e6021fd47e61ff83f8967b13a7b5caafb813da96421577efad8dd075ed532ef5d508fc3900f7c5c4f1e7b4988529dee018d98a2d1124285b985d4e8bcbe6eaf29c40e0d3d70db1dcc42aefc1b6de781891c7fd3144d2b22e18153931ca61b8dbe94531b1b687760328a44ced8a1fd68c60e9ce5889891a3cae469ac039ff4562abb6efa1a37f01a3450c19782000d3a30d19fbeeac792c75605631c326a8c018316dca0cba8516c0eac460aa5f6d9eb71d434e4c0e616adb857371e7b4e1cf8c852fd8667ae9bd4c2a12049ca5342a8ac6cc92fc78ed2b6d4f6063e62bfbfe6d1cb36961bf8c914d5c8d4c80f510f68b4815151d5fa502b8638f909d06003ef6e9aa3f44d1f3f47171168ac81af9c52745e8ddd715f1b6ae0e39c31887b9985f2fcc6c6c6c6d628b5028d34f01373ca09d805f001bfc1726465273b4d6902f482fbf83e97e5304d31a509c00b6eb37969a965176ceee4e6894aeb82eb209da40c1e7dd090ce056b952c76da74146bd2b8e0a3e5b2983937ae47fa167ca07f1bad5e532fd2b6e0c3bc8865d782959026679b2ca9c5a3053b594ffc22d47467164c88145137595eff65c17fa59e889f9ee31b0bbec3d871d66aab060b4ecfcf363d144f9157b09d238fd4a1246f64ae6042b6a05174b388df0a362cf3473bd13e0a59c1c77b6b9deed73454abe0bc2ba262a889f4a10abe3f96587e143c2e53c1ebe438674963661a352ab88b79792af24698a7e0c65282757c5193789882f598f2c77a31d9454bc1e94964fde0e178c821051bd29b4a62f2e9f85170bf2956caf2ec18140593a4dc52acce1f5342c1e4e48e24e441c174d29822f9092ec472b3541a4ff02791398c9c77828b397e20f903ab09694eb01b82bd9a787d8eeb4db019b277a45c6b82899b3d07ed172c7b6c2698a831063b91641fbb98e0b5ef5cf237f2be7b09b63ec5b57a8b7bc9b5049fa129f32a8be76c2bc14ebcce5a1ad3071aa5041b9da33fe9daa65627c1f4e6641524ac36a49260fccdadd7ede3d06624783dcbd9a2c6943a4a428237ddcef125f908eed4b4b2794e993fd0115c0ee191a257aaa3b111ec86a5c90ef7a8153282cb13a1bfa7963e5216c1affd76ac6aa1bd238a60b753aa84c825820db7cba903adf8924304ab318594af7607217708d673760f358b4ececd104c852cdd1a53e48a6c0bc1e8787c298db584606a2ac6d6edfc51a676107c0eb2554eb163c765ad2078ffe8c3cfd91ec5606d20d81077c5a3d41575aa0504f7d99356488e249bb47f60a344bb2c2ad9bf39fcc0844bb6e4fdd10736c7b9b9fe41a7f5e0032bbe7f2e493a1ecd1ed897d8e351f3efaf0736fda34fac8914953c706beb572d794f277860a37be8316367c8b17307be2fa7c81d1df5453b70b12623db7398b97d1d989c77b2a392fa1c74602db3a6667747fa9803231124e56b530d8d910353371d954f8f03a7395868640e07fe3c3d4f7589a61cfd06be423d768896fcbe7603d7ba5629f9dd0646625b45b3698d58b381cf96a333e9eb1ca25e03635652be297ee40f3530f5a59fb9f91d7aa4810fdd359dc31c3a7a2c1ab89f98d4c3de3330de29c903a96d0f5d33f0713fa7ecae1a1da70c9cef7b8829ad64605aaa62c7f531b4a963e0d53cc689292a06a6a7e207cded774fc3c079da983754bc354d3070a779a1839c32e62dbfc0a59c52b17b244f2abdc04dd65011a2e5741f5de02a56dea40fe4027b9aed6fb7f7e31cdc02ffa15cb3e6905a7cd402bb1dc1420ec42cb0e9437d07be5d35412cf09ab9a6a390b3bf04afc0f874bc3b92f51b422bb09e3cf78fa73469c22ab0a71d332c6aa6140b153897c8cf981a517d3205364296c6ddf58e1a22053e7a3bda787fd371a25160a2e3e8dbefdbc142140a9c6d4aa932e4a8ca73fa04364dee0be239818ff224ebbce671bc6b021b424d2c84799433c604b63d881fa7dd2c8fb525b031a5d879b22ec7a02981c995a6e395e86ef1d224f06799f36712adea4a91c0f4c608a9b3c3b718d22330b9827b548d314735a911180fb2c7a123d6b79216810f3af2b1ee682a7d1c44e063f0ed389fe4fcfa310426c779edfb2c781c3a0410023bb126f6afc4e8ce1320086c8c79d99176a0a53d0180c06ee826d188f8d96108f0037eed3a5e079ef17243001fb0b12ae80729a7d534bde07e3d2fbba6aaeb79c15ec550e3b93f7aeebb606def4a7c4c3cb7ae0bb6222ff4b54d75a0e782ab4f9d199d263de8b860533a4e3152505fcf6fc184985cca73181562db82d5541fa46bfed03eb6165cbd8b5eec585a706513aabaf7d33f66c16565c9e551aee49c2c58ffd0927b1cf5b162c1e43cd1bcf23d8d14165c9e1423a494e5005ec1591c89953aef6e923900577057d767619ab2a33207d08af5bf831c578eace034a57acbbe1e438babe0a2550c12cfa20aa6726dea64d7d24b4905db164357bc90b95e4105a3e68188d96775ac9c82b5cbcda6f98147513105175a123274ac1f764829b89e4c898ca9271d210597af3f34d7894e1319059317f3d652fe30f31305d367d9a1e69cacc60b053be167f9e378e3a1060abe3ff26825477610b24ff0618eb586ee38b4a73cc15404b518ecea04d391786d97eaad599c603a5f688ee38b1423b509f67b24660e5de25aa509b6a3145531fa48852a33c1eb56fce8703b9b871213bc7ff2903da939e39497e06212df0b9aa3b821a525f0d01e6525d8c9499be92379f6a00437eddefe9e8358fb24b83b6bad9ffa8d5a125c7f1027ea7dd7a891e0a398dd49d30312fcd69e87ba8aec96e3116c4e5759fffda2b4c311bc87312bcae33482899d63bdbc0b12721c46b0923d8ea3c7e11afa5904db71f4122cf557ba1c45f0217960412b4f72ea2482a9b89ba3c9ebb4d520824ffd91a6f41de44bcc21f86c89636d9f9b1a37045755d69e72fce06b1782ab142923354a9c5c1382c98bd6f1b4467ad683603b2a6257748af96a41b09a9f8334963de8d581602c25ff0ca176f6b180e063f7a41473f01c7ef60f4c8867292b7358e4ac1f98dcee761d0d66d2f681a9ff14152a7a455a3eb01eda58aae57045d53db0651eb4d4a317ed540f4c2aade974691e789b0e2c639ee01e291ef8949bcec53fee5af20e4cd48769f735b7426907d634fad57992b41c75e0fef533073979b63c1d98fc9b24738e93973e072ee8e5fdcd98f71eca818ff78278c5300e7c3c9efefe7110fb2e70e03bcee0f1063648f4286367c8b30aba817789296df288314305dbc0c796d399655709f5900d9c66ebf03c4ce33979b806563b27dd8e31598ec3500dac96986a9ebc9453c334b06569a7171a1a986ce6414f861c796667e037640fa22ba1679919f8307718ecdf635ec9cac0c71d897ee5b2d77c9181b74a9fd9e3f4e66e8d81cf712421ba5ea5db120393b394fd7594ca422a0cac68e61ed11c7fcc0a0cbc47eb61499876e87f81fb8fb207e1f9f2e65ee03b2b3bb4cc3a7c09bbc0a5b869474dc2f225e40217d3c4eed8c3ec4a116e818f3f1933b776bc8a500b7cc5da1e0fa121548459e0c3ceae8cebd729736081091551752b163aca7105de538e43c892de9d0e2b30f1e3eeb856f2eb4f15388f3fca11f69226e65081cd996f1f9b87f6963305466b4ab3fe635ded4881ddb65825a2c1eff351e03fbb876ce392cbf250e0234279278f9eb28779021f6f244dba9e9caf7102d35752ede17efe8e3781898ca93c0e19253b9a099c56cef7d5facba897c0a4c80ec1fecc3247096cab871c43ed56ba37097cab851c6e1a9aea45023f6eeea1835412913d02176a9d1e7d397bb746e043d7182d879a3c445b042e69c8714647bd6a2a11f89c6ff71739d4e773088c448999d43e4889c9010881fd50543b7b76c9901c4010d8535ffd9083b6c5710040e02db679a448d8a471003fe063520ba976d3ecc301f8800bba1751246ffa0fd20bde32779c58d1b253c20b463a0593a0162a7976c1a758519355d0ea485df05194a6cddf5e1abc5cb09e273d4c19d563f170c18e073ad691b9b7bb5bb051d5a11172f453670b36b7c53fbcaed482cb2a92b3786aca88155af0bf933992977ec75165168ce79e74f8595be25464c15ac8cbdf51b68c199558b0ad12257c73c082330f11c53d737d1ce7157cbc7ecdd08fcbf2e30aee436e0fc2727aa54f2b988e1fe75c751b2d1e56f0e1640dc9ccc3298f5f051f7adbab0797a839af0aee73dc9cfe0911fb4f051f56fe74d495266b1e156ca49945eab632d79c820f75696d732c62a531057f5695620c1afd717829d874f5d8a34d09a9765290e3382afd888e824b55b7ccd6fc3814059bf45527424c964342c1f4a40fa286dcdbcf41c1f49a7f8f8fb54efe092ef9da567bf9a66c7b824f3ee11ef27eb4b53bc1e6e8f414dbc562bf9c603f47cc5a8b314e7037c1e55ee6a6a996bcd1046f5122e7ee9a185fcd049fed5f2f4467ca9a6282aface8dcda91871d790976377a3db28f3b86a425b8fd8b39db637bff6025d88ef6bfae2edb5d9012dcbdd9c6ecaddc3993606232f7cfd197d25f24c1b4e4c8d771bc1aa712093e886892e3feef5012487092efc3d7be28e13f82f3bebc932bf22fe30846f3dee338aecdee36824b3988d0bb1e23f8dfe875d16a2d67b708367b495e799822380ff9ddee1e65ce27824d93fa5c6a29e61044b09bbbd351ea1869d52158fb8f4edb10314469d571488a112904e369952a85383d2e21d89c961e4224a64e8e41b0a213e3994df7871d82e0e3fb9d07d1a1c38e409c9b22f226092098f0f833f55d72f3ce1fd81c5d8650cdfb816fcda1a78e7e1f98ca1b62eeb154937c3e30f6b716b273fca063f6c0a798b22dc7589fc38d1ef88d57d277413b6b260f6ceee6b8e3b8e381bf8d123dc73be948bf033f29c48b139253a7dc0e9cc490561e86ed7e741dd889f71fe57a50f6371df8ca6114d31ca4adda73603aa5f68ea8d7115f39f025992e6667a470370e5cd254d135ffa411170ebcc6742f313c0cb1ec1bb8ba5c29fd2ea163d50d4cc6f81ee465de10a26d6092076952a6c829c6940d9cde7a983fa3a3cd916be023cf92a37d875a39a906de52ec28b26b9a1c9e69e026e448fb4e851ccf43032b95b2c3a0d217a37967e02c539aae081d87963c33b066e39aa31873442caf0c9cea86564fba6f493c32f041e652c9b159c814bc31701712cf73ae5fcd514e0cdc89ff75eed66b47b930f0e9cc227c3af2fc210706466dd383bc8a9232e4bec04fc8aef7a4917248ce0b9cc7a1e29f92cd62eebac04af6fdf724294a7dc70576f57f72aceafd21775b60f2762c71a4ee226ba70526daf7fbac2cf09a53a63a893f6e1916384dadb7e5315dc8b9029f3bfcefb45de68156e0730c16b67665495205bef63bae602975ea8a0aacfdbd789047fbf25360624ae530859ce3b65260cb73f8b97ac5758c02eb6d9583ce99a98602b71ddd458f26216b744fe0efbcd2836887d63881d7889903dbe9c9e5d1047e537d7eec543ded6102179d82a498c5c35f025fee23163757fa56094cceb288a8f66c258149561b224dd48e0a12d8b28fbe081d68747f04b622e78776d69b3946e0d3ae8715fdb3c37c11b8749f97d69b3dd6108189f02c518267ff7608dc5ddcc8a21d744e11027b97671337a79a9009027b6df635395baed081c0e7c9fe30e6b4007ec0a520f5d99b2e8ad502f0019762151aad9aafdf5e7095d69f5256c8109717bc47ff4842eeb415dc5d30519d921e52f08eb2ba60aa6f24be89d77e682e788d8eaab2a62c652b2e98ecc98385d4b9e3aab7e037c33cdbf22cc4d4167c4fa7ecd1e6b87fcf5a30f1241e4670dc2a5d043e8cead291460fa96a22301131b5a5eb4a1dd443603daa4ae3515d21f09319e1398c7d9eea06811df5fadd6ec953e72200084cce9d126e9336b48f801f30d137f6adf868ca1b04f880af919c2cbb4deeacf782c9619e14a21fa9a69a178c6edeebb24a962ddd057fa97bff76515db0f9a3c9949a2b574534175c7bfb478d22ea394c71c1bb985aa69ce92dd8094f9ebf25bfa7a4b660247b3dc70e7d4c23ad059fa6cf7f7214a37b50d382bf8e4ad344c71e5aa4b3e0a38d2a9ea1729cc34fca82f58ea9adc3201a3a3a63c15de6ef92aed8a776c28277bf493994e9ab27f3155c8e728839767748f939ae60520e77f42ac7a92399ad60cbde237de4ee7a29c90a56ba3f8448a1377d2a57c1e71ca5be28a15405e3b61e576d08d216ca54b01e6da8a9ce1f74e4810abe3dd3d9e6cf29ae7f0aceb743349d5e8a9237057f3bfa671d259ab4a5e052d358ce2a3df15652b0ba397b1835e646a8a3606b42d04c57e2c12b280a56cb93aedb9945b230145c8e43caea6b498358080af66ee2c7940e2125859f603bfccedde15f3cc16bf7f476868af1624a27984852f69633e4a465e10457d92ee6f4eef0b42c9be0837d9c1beb5234c1ebb8fec4f4507db592092e87e379c13bc8d03930c164b878548ff5277e5c8233c9f1bb4951ff7a4bf09b3b3dbba5b95d5f09b662a688799e59fc3e94e02da4d552329360e3448447c99a2ff996049b3f394cc12e877e6d24d89174e51d5f2a4b5148b02b5dbf63b7e9b3f908b62e8d7f18c2f2c5cf116cb4f13c4f398ec262a411dcdda40823f8380e3234a68cc871e616c1a4a66d268d2b82558d2942befb6c5513c1e62ccd71f4feb1e4484470f939a5504dab54f3109c68883192e7ab8f4443b096f386e4fc923ad51582ef38cc1c67cbd72ad511820b7edd295dc714e90c82c994cd3cde780ae91604e797432c104cda2d5d891ed584c400c1e71427348de70ffc258bec28720eab347ee02368e6cf24edeef13eb0995572c71f2f5fd4381fb88e19f524343f28b7f6c0fb5488a19f436f5a4b0f9ca5ed4de60b261d5979e0f623c79f2a2a7a280b0f5c284bea133bc68a48dd81711b8f728c943ddcadecc0a6aca11f67fb1ebbaa0e7cc8d16f90a89034e7890e5c4e4d48d7f47587cf81a9503166eabb87db7260624eaf9e249a4c3571e05a5244f030d65b04e1c04a886611a3a50f397c039b7c444b2d85dcc0b6f485fa2eb3ec618e36701a39886ac4f44a123670e117d1327a8e11ae81b5907218199375484e0d4cce71b67fa591300d7ceaca71e534b942060dec6768fe8590b3ba2231b25e705a600e0d6a6c4de000676034a5bd4d8d9272c70c6cb8f7beb82431bf0c7c4fec8a9f34d5c69081e91cd67e14d1a9fb31063ea8d3cad7996a7b31b01972583ac9b9969f3070d2f9732cd1fe7b30307133a4569407e1f9021f6ffa76b2ac20792fb031ef685e7e0edaee02fb51865f9e4829840bfcc698f951f55b602b748c581d460b7c849c59f2ddf2654c16b8d718638e8e3a46d660818bc8f4133a6adedeafc06ed65881a96d09a94752c56815f8a834c9b4b5ba3ba702e376f7ff365e914e81d7dc21e7a60f8f925260df739caed2e51c865160ef3edb448d7ab1d250e0e376cd10838b9afe04cea35b0e742db88b4ee0f58386f9c6da94b6098c644aaf9f72bff34ce0bf2a2d7fce1d25cd12d83ca9f28f5dfaa054029b36a5afbd47d524815bdf7fff9cde425091c0479bf1dd23f1e8398ec069b0f68c19ebed4c23f041d00bc9be99e216816f0bc9722574e51422b09f3f4c672d9d2e84436053d61c722015fb7245084cce98723ca9e3d0db20b0975ae2af871c00085c882e751e77982f2507f801d39b3c5afb2b4f8f3b800fb8d1faefa0bfc300bd602fb6c7cc29378f78870178c1d5e4c8318e5b8cb13b0cb00b765483a6bc1e4287ee30802eb8d469297858396f8e1d06c80563f1e350dd443a3ced30002ef810faebbf62b7e0534aab495bdf2962b6d03b78c56099d5828d1a9239e5a022e5450b26556d0cf57f1db6350bce63caa371333b4aea90059b3a4c317fb1e0e3e8f3ff26cff5a982052baeffe9e9e398fd0a36a528cdb917db5c57f0b5ad9d9347ab35690597d643f4841c73d258c1741053b6acaec92d3a56c1dda64ee7d4dfee51051f3c680e42fe380648052ff15b43ff570edfe3180015fcee27df00a760e27d10766191b939c700a6e026e97aaa14995b738e014ac1ad276bd3d2645d9f6300521c075e4933e547c1759c1e3d33e958fe45c144f5bcfdfc97a27e28b8bb4e912e3df5a60f0afec3570b0f43d48bfe13dce4150be6613cc1e41ca3e4f477183ca6136c77bcd19037e7dc184ef0a19a92eee468426e36c18490f21dcb35c1ef87bfdbe7af8e3299606d2f730571959c6382a90c397718c9efd37e0936b32e7a1ce3f66f6e09b6edffe3984c62f65482f73c2904cbebcf9e12dc0756512c87b4cd4c82c90f59cf9206cfae24b8601fef7ded48301ebe69fcd37f4f1b129cc5348f03ad1fc165288f3d8839d4aada11dc847d7bd8a1deed6d04979b9e37a464294d96115c87b5be1d4af5fab908ee2279d8a12615c1e6907247b929a55cc944f0615fbdd122a584242218bf8bb6313dfc737908eebd634ba6e0f71134046357ef393dff03b314828f3786342d413d300921f820b47322998468de20b8feda24a9a2a9bb1304d717dc7ba2831c5a7702c1e6bb4f1fc95977ba03086e62e5d97ff4af713b7fe072d884e72babd0d9f103dfff92c9d53efeb74e1f588f63a84f661d3eb01fdde5fefdb749a9b3077ea4a205c98cce161d3df0bb1539f0d4bf0ea29307d6a26d472d2dbdcd8107fef2e31c9ec7f7921c77e0cffe24e4f250cb1c3bf0122dc48e1a2b52fa75e0c53b735a2f3fb09e0eec997aca10fd1cd8cfb1e74a1de75897031fe66b85e5c83d67e2c0779cca18ec6b7c87036fb5c9c30c212dbb6fe053c769912ae63832ddc05e8a9b6ab503bdcab48149e9cf23d3fe96071bf88e58766639887e0daca61d4b5f69bd4a570313e377ce61ae5dd6d2c04928b7b10df553a1818992aa0e519f818db4ed388eae19d83eabf50e51fd915b06bec34c69e7692c5d4b063e637be69493db73e8189830abad14ea9a3b540cdcc5f18cefd69e1f0d037fb7e2818aa98ea46060d376983aae0e83f9f90526f74308f5d0728ad20bbcc40eaa92b506efc02ef095af3507aeb997825ce032ef682a8fca97c32db03951312d654a15422df0d1b575cc8d9d31c22cf081d487973d050b8c558847a9995a39e50a4c7a64d6fd673f59b1023be5665d113587a85481fb1cc4bc3197881c4205be7c920651cd2529640a4c9d485859c60f39f752e0a335c7a4d645813b3ff5f0b4cd4e1b14d8cc9bd25f70f18d790267e7df717565ed502770dad97c2fd66ea709bca5767e7654f7554ce0e3f175e9389964ca1298ac9c31fb790e3b47af042ebd6df6c7e8817524812b1bf10e32340012784921751c6b25edcb618023f0d641df69af664d87018cc09947b9639fd6f1388e018ac0755e8e834899b7c7630022f039d2f45b0fa777720c30043e7d7772c8193fbd630021f07937dd15e59edd314010d8ca314eb7b887371d0300810d9e15e2719ce378f306f8017f912362ce903107be017cc0c50f4b1ebb6596f7bde0e35451d772fc2149cf0b2e337b1ce7afde90c3ec8237cf81e4f851e8a835bae053464a69a53daed3e482abdd888ec31bdf89c10517377ac8903a78cc995bf07e399c8e93755fcad882cb93b45f531d5bf45af09ff3c73c7a9d52745af01edf390b5eba3e4821a72c915c16acd9f969eaf85173742cf84d3147d1c3538ea761c1649eb28bce3cdaf72bd8be7cd19cdeb19876059f4933630eb6d16ab782bfcca16e7994759666051733bd6d3f65588e5d051b394a8ec8618ef9b3aae02d456b0c412a527c53c1a4368dfc6c79425251c148e86cdf93f8514e4fc1a754464f71d74c4953709a6f92c7b79a3a92a560a5f2e69cf18394bf24051b4a57af1f051faa876ff9e314630c8a82cdddadfb31ec3f130ac6b737f5470e6697020a567d53d77ef6e708f9041b9ea5729424d9fd9e60f34e0e15aff246ad13fc7ee5ac3d2e1da538c1f4b7db4b5ee84db709b6637bad8bdb93a309d6b44f4255e4dc31135ce520badb44458a0c134c8cd93e7dcae8f1b8049fd5e2b58a947558823193bcfabab1e3bc128cc49883c4afe0775382ff3cc12b7ff2b88293e0e390c395ecb8352b25093e6affcd0c21b5f948b0af493c5d4b2546c84082dbff78c17df4023ce2fcebb18e60c4367fbb63f30f3c8d60625acacc99e2153c8c60dd37dff7c713c5f38be062ca5e1dead64ff38a603d8a12a37b9477433e116cd4b20f434cd929f78860334eb4b5b2cf349a43f09fe3fe2cba7e51620cc16765cd0fbba394670a81c71b3cda0c213815b3cde952ca96cc20383597ee28eff50f417039bbee6b8d772a03c1eae6e82f67b4e42120f88e5243ac6cdf61e57fe0b7326fdb93f76bda0f6cadc7491f84c50b6e1ff8089f1811b4736cf9c07a500db103750f7cf8a61b52a67ae06debe3bbd70ccdc93c70399a8d4749f2839778e06f235d9be6d8299677e0e394dbf6d2e2d79476e04edbe35c1da7690ed681afc8c156fb52472948072664ea541bf5724a700efc74ccdb39aba6ac500ebc7fbc29e520a5958471e044c33258a4c081e9b70b153943f45879037bf1ac2268d68d49e20646aa62baf4e4071e6903eb9ebf2c04b18e52c4d9c04d74f3489b4dda73be06be345e67667b089a733570bf1e47938715193f4f037b41da83998d77f01c0d5c5f660f3a4abd1acfcfc0667fd7b5ca4a229e9b818949dd2346284d1de665603353b060394aaf3727031fe4b28adf71f2d8e563e0dc524e96a3493e75b91898b4f6ac9bd2d45e7918f86c1d7710b9962f073918b81cda3cc614e2e59ffc0bfca5b81df787ad1758572ba9102abf4ac8bbc0db646d568664fb0f2eb01279d39991c5ca630b7c6848ef4a1139ba1c2df0398819d2d55b0c969305a6a37a85f86e97f4c7021f3bf4f412e268b57f054e74453bb22599f756603a7d14ddd2a3c5ecabc05f86cc1f8fbc2d7a2af0a17d9ca38d8e4a35660a9c764485862cbd9791023f2ea69744b26adea3c0e771ed10620e728c7128f0b9a37ceb90374a8c3fa169b70ee1b61398387137b847d28e6c02ff96b3a5dbe4182f6702db917ba61b8d36f95e02173ff2d89d13f2344ae0f3b7a6ee6fbc4d93c0f8a47493d7a663b848e0d6fec32af856ec8fc08e65fdb823fd286d23705adf922b7dfc6c11b875fd97901c54599408acc6d49a1c43ba8c0f81b50d59ad69760184c0c4fefc90e3df90c7ec0204810f3cc7faeb95bfe2d805000293d42a7ff7d7378d5d801f303177282126bb10a2018bd8a8e46ae42242e1e0c05030180a0381c06037be3593160000000c180d85d2340d34c176071400033b322c3e2420121c120c1810160e0c0c0c0c0608068482402018000604c240301818c493d1ba06de9514db58cd16dcfabd95d3eaddd28c5d84376ef3d1e3b9bcb6ac5e0f606e1fd8a2c511771fc97706f0c30c1d4a7cd1468d0b6a28d1c484c9b7712ddd33b6e90bebaf5ab25d45a468636ae64976f9c918e45043a3a2007a92372148d92eaeda4ce51305f8d8ae815e59ca75ebb6525958c1ef3d03b17ffc0cf14752b2d48e9078cdfa85dbe8c42065cddf8a2bbfd283a97105b1bb939b186c0f779bd66a81088618b1898a2a6ef49216dbb4b435a202bae2ee765c89d435591fc7ca987177cd038e50269cf55a4d3c0c2816fc1c0d9294b5ca28a8091379954c1eaf1cd07d64297ebceebb705facc3e881a20743fd8738538dc3c79dc13cb45ce6d0a05da635f6c96c224a70df5421956b07e60cf1847d456fe6eb9133ba61a38d1b7b638efb1a5f49f7b1a689fdd4a3870c87d28e0935b59e746b9011c280982aa54275246a4e532ace09d86ddf387874d2d98703eb2eeadc935737befbc08a435040372e1f6e3a41bef989d04913694e2c87840d719911ba192af553bd994d007abdf6f4f1c34350573e6c5092b7970f8fbabe72e5dc31bbe4214016a533090a441d499328134c5ed7d9530797b6bb1a2a208270d954440e1fb64c46026dc774845d2a7d5426bdba2b1fe5258a78edccd18147174edf3d7770e7cc0127a3e7a401215e7779e0d17d860b099ae2e038ed8e6dccc4b7c778647a7e5289e6834549244a34c184d023484f92c4299f7878eefae99bf36e1d1237c74a428502688abbf0e88443e76e4edd3d7b76fddcd36397cc3d5610bc217ecb62ce6326b042cb13467aa49bbb3c35c8d1b2358a358a1472cba756c41174152db13bbf7c7cf3c6a36737a71e3813d4e725fea23dac51d1e7387fca5e122a817e0930be64843670834f1d7077efe0bed393f78416903fee0d777cef712b09d2919c6cfa642745069504e924a7a17dcdc1324899122b3afceed9b3dba7cf9cbbece8dac98dbdabe0aef5b8c09972c708b67cd69025f14653422ab727126a4d9ce6f8c8938fbfa9aa1d94c1cf6cda5002192ad4d0491023eb324d8f633bcdf0e0ad540af9cda14cb3063a56b2798d255b4081f0844a5f182eeee529d920460339959c4d43339ec3ba305d7affc831d720064cf6ac72f7c491b7f7ae9f3eb877eab0d6578942b0be38be79e7f0facc8c09b9949d3b758e810ed1af77949969d1442ba10495c4ed6a68219498c53cae555165319b6da6f5274e9ce2559d24330c1b08218ff4fc12bfb5be35b5cbdd93c6f013b90c4bab836879a6051b23784e718107946bc5ff83c3dd7efff29a0d94582d2ecd6d37177d0751e71e47f1f1eb8b07dcbdbdaa4932e971f2ac5cfaacd1dd75c7ce5b4b341d0268153dbc940c92085049c63acd9dd387ee9c3e906366cc125dea3662a14e8c7f8a9e464774ce84829bfdb571b86c9ecd523d596f3bbc7becf6f535e78e5d9d7620673b7a059c81dfbd75de3cfa08c9dc329be859582584ac930066b488e5c4a2018b84f4f8fdd91e0bc2778b3a77e8e0f447876e4e9d3dbdecf8c5c3fbce4fdf9c7add8ee39bf7cdc51b4f5a61419a7fb008f0b56783d70a8c7ed249907f017e14d2489a32f9347e45ad0e4f1ef7d095a383f74e38be350f217ad491a44a76ad534777ce9d3cbbc6f9e5b93b470e2ca774e6fcca933b0737234c843632c91c5b8392fdd40d87ae9999016ed7410a59a448d4c933fc1957f3f7ce6f1cb9efeeaaa3937b270fee9199a359f73812b45437ce3abc76c264faf5a76e9cdf6b9274ef8cf14898681811adf62d7ea6b68f25ceeb9f01f4cc9b53141e36d721b351e369362223fd7943851141c889e369a6a7686d1a37ce6d66f35d48f2dcb55307b97102dc838594c7270edc7675d9d9c9b5b3e70e5c7f7a8d499a09134f9d442448a490247dc2f4274f964639470843f0898c60c34c680b7d54e5a08e5cb28b6ca02929f5a633860f1d12b40c4f48a7b8995da43f15fac74edd3b7ff3d0c1c519e7675d38f91a52fb67e20e419877c50006ec6c9d98ce3dc720c16546b8d070cc61449db3d3eab5b44516b350eb8d77615d1526ab9874b1315dd4617a3ab60895965b5ddcbebde7e8d4cdf9cfce2e39bb7b681ffaa4b7271d1fb78058dea95b27e72ecf3b71fae2b98b1336d29fba71de6cca8533f78ce1829028dde44a9dbbeee8c2d1cd9366f8a73c7b76f9f98563b62121c249ebf0ec1ac777cfee30c4fcb037497b9f067acc865ae1dc6c6ad26d5d3a35e898d0e225611b21129e3426d0b36590d910070a94fc6a522d1ae6d032cdf07755245480a199edb52a15c7582e4475e6396541fc6ad691165d377f13db231f847e3c0207ee8926e27d24b684f4315f3a5d40d3ac37d2312b0104e32d3048d731b75c9ed484da8179234c3a61901e616072f36f530b1a09fa97dc50940928afe39f14c728cd943eb19c77e70866e198c6a6e8f8e7048eddcbc22d038fc433a00e004c87884488627d3dc98f195e28bc96819c850a5560e5f605b53feb0f0e91ff9a1ac354e1f0453899253e1d9c085bf8e4aa8588c7bc0b98880d46d5d062097ff8d2e35e9dfdb53108545281bfac16e14ff86a1992ff4e17b5c574a798bd1ebce18611c7b452ed0c9a4a711026fcd1205e94067b1510907d0b7ee7a845fae81fcb7110b948db36c94c638205de06625225f4a8033b048aff6ec689590168f522513b8a4aa05984e91cf209013270abb2357ee3b79acb49304a6236acaff154e7cb8e39d9ccd6baa8f4b41744550373c14e8e932cc63cc42885f2398c12c0d870be215debd4a9ca16e6d74cb101360551ec482dee3481d412f033fc17ec8515e59117b4aac04bf8d33a7c4d6f007839def0d289145742f128013566654cdede54a23389289aa58667515da82854e195facda491db4fda1dbea95bc4113c15ae4c5136f86343dc1d7df81cba9c6d61e0a34ab68c0a376f485e988bc521b442cf4cfdba458a6cceba78abcccdccde708785375a6db61dc2e48773f08a1fe315ce6744a2ee1400b793b122e474970eb11d7e0098a37218a4f4312a6c12339f07891e10872e1779d0de3fd7fbcfe4145bcac9620c04bcf3295ceed13fa42de23231a18e732450148903a5ac0ebff0c08053fbea353893946e6b3cc4b8c33475120afa14b6f64241b8c90b65c84b44fe90e916204e2d979b3bafaa59a7c5df5d08309a7eaf86b02a2cfbf41f588d65139f8be5dbc3230d50831837d65b1dd49c91798eb2353c80aeb8e71087113589db0714215f1d1e67ee54c4dc8f6447a4d1f0d9ee8185e6a99e30b5e49397e7337c708a4d2c50945da25593ca99ab4758385608b67bddc087b47fa4c421a83606779ce39ed0e94ae91ccaca959063300f1fa4a7cba8a224803d915cdecbcc00b84515147029bfe6b7f5b023d8c20db56aa90678662cebed044083e9ba5f16d50c09e12ba91aac8d68b157a98a292e99d77432ed2302fa138e03dabdf5d00d4d465db9bfdc424b27cdffa014b80999137ee4770d4900ee4fdd0673b57f147f6f552ad4b87730780714812a0a1648edde55d2c1b7cfb9a29c7a70c66a141ea681eeaaedc7292d00a7f67137bf3e3d87ed06300ffc770652c9435eb72cb00b4380bd5c0eaec62da08df1f303278bb9a66c5cdc6476229f135f3cf337cb08041a6f3a8da2690603cfb49bfda52a4c4a60479ad9e258b81d034a5ee177c1983c819e78cc5e345749de13d79243ebe271c14a01be909d7d5bf75784c31205ece6c966a4dc0076a44070b31bcc72f1816e4857150615d7de68a3f420cb3e58a29840102e1671937e11da73885b89f6c5192f0473f75641da416d3e1b1a289c028a1b550d18cb391f30685c0622dc1671f6995205a861cec29afd6d49f02ec8c00688f4a178dbeb9b9568e4beeca50a946ead82dc59c3c52f8f6b59c9fd08ec991d6ab500bc2382aaa47272267fcd55266cd5a77accf28f220d9ef287003df79bd3cc32f0aae9d9f31bc35e69350a83ece6941669ea4346b07006ad90111d371ae9d2fa3fd204019987181c4bb89b020006668033f0b7777b58d01007c05f0420a48c2141c5183ab142b5f51c47a7873617c9df14901052872e5b19b6775b2b06f05fa7100e3e3c31e9f4a48be41acae6a451984f8c2c5100d0c1c372da0b4021007d923833585e6a8c1e9be263de024aa478320b002e978283d073dc58253602bf1252517b813e4da907e59e549dad232f3a1a4d395ef6bf975a0e83672c01f9b4615bb610b4752a9c441136a2f7d642140e69314ff6498e3a6bc77688e3f30e0d4ee5710fa61f61f6cf19388040ff91bda50666800c578f785fe4fd69087dfb1d50c44a8303529dee83a144cc668aa0ae5ab401407d94cafae342c013d32216c6b48882f70c5aa9a3713d132da8881ad539d5ff66d079d58c3d6381c1b0695aed9ee22b42350eb7f8e47ec501deff1cc0eda83b0c1e20291367e2a54effbbdb69df585a37d1df8590e32c3a6ce7e42286a940385c0ff0faf6d1e940eae5e53912eb9ee7c3584b44cc60f1930c9da0ee94e8333df7c7d105fa2b14a1f13d4822d69e9fc2b1440a1d2f21a3c01ee476a407de062e35ac139fdc76f682d9b7c265f7d4ea3785cf46d32e7aa3c5f0e20577ca0968a59914dbdbaa90ef29dcf6a82100abe8e7d17cc8799a0d6535a6d38844be2abb852900817bd451d0c2530a11de0e87d07b0ea35546aae4d269484f0d2bbbdbe73876c070a8a6cf7f89817b38876c9a95e054f5b0f4376e9611b7cc3d289ccd4660016c3b1466b567d1ca3393507b1deb075cba4588445942d676d283aefbe3c26bae958c08450f2496160f97b01fddfb8630fb333bc2fdba2e89d9d0c1e2d3f52f51c5f1f07ec550703d696594c6ef8065d8a516a645ea42da8e9d26b18efc41be48e19664cfac553b35ac1d22e8af9c0b37209ce73587b8f430da485c7a00d1a8e83a8a98189c342372cf6ed8c2cd542171e8aca047c606e4a63c4edefdc31105a50b52ce2d696007be80b60d0b87c39fcd57c0db799b52e536ddaeab45e20c7bd8c9c525bff5cb4b2d6699af31e538eac01adbc88420f892e7d24cb25cfbe65512331ec2ebd02f925981ba25a54ef72638c132425fa2aef98568de8c34ce362323b6b7dadbd123a26328f42a67ff3b34005962038c44a11602be0947cf0481c4d0f862a564fcd41fc6786a1c2f46aecd135a034c11539e5d8b5494c166ed4924bae9f025c3776a3709c55f094a2b753d01663dca1fd54384f02dcd2a687de216c828b4240a671e5ad2d154abfead23bd211cdb160d7c5b959b2e5bc549f355a96cc5b01b45c4ca204d0eeccb1b8fd95201b8bc4e0f10bada9e0ac25d6a2331fb31c7dbfd2d391ece50a05e397285d5b2b0d12646bee054c5d7b592181afd64cd5317987dcc110a0bca066f8b689c7b72e8ed9ecdbb56cb208dae4c4d43fe6c50045f13360f41f042de0cab7bc64df9158b8152b11560e2608ac216d9da582a30a31d48ec61e65be169053c8fb72a2f92a4cce0e2cbedae73ef35a9cf732893abbe5dddc6341b09a794e7660a2199a5ad5649e8c80d072972614865fde8c02eba4c2377b21e68161806c17333d41aec9ba4606fc2e2f4932d9a60e58c5265b674f12c1ddee63d3402e313215b370e58cc0044e4be9a1feb805e6f72742642803905accc01b8766116668073263cb82a21355e8908c06a798711d9ffc961b2d08f18c77699ed3363c474fecd9a81999af865745f896bddd8d1b0a2e9404113a84d04759739616118b420e841450496485907ebc9552896fb3a2ae1c9fa1b52b66813f39a37bf82d22e0bdd1561b2502238c7e69a6bb4fc482d021197fbf5a259a0d41b29c1c82b0b4e9e9185b84f65a50081bf436b541454b9de23e5736d6961d1ea008b6e71fd7ad4ed8476a481f038b275a3288eac375cde5026dd9309725a6f342bfc2fba982038a4ff8139ee0580f9ed0c3448a1ebde4b0f0fc2ea94ecd758faa4cef4e25468f1f9560181be89bc79fffb4bdca91242362eeb229e64d806da634909ca842bba29fe2b60e7318c97dcd058c36734dce8687094e060a5cdedb667b38d505521f45498eb7393055901276169b0382dd7e5a2c32a64e9bb6354c96c651466d8d714c22477b8ae057b1eb02f73ecf343acef7668e79638cdbe9afda34820116378c0842a6d378bf8c60c33fa5e13536abc895684ce9ebc1e775d8d41b8157c6261acab0725846d2e6d6c71b301f2a3bf213bf885b358074633a442b51516436a5ed64e4957437548e8f29f7e2e185395b84ab67ed1d93b99e38e6d10a11b99e752b7eb9c76ff799e6cffc121785e7271704b3006a112c15067694c193520295b7e926dd738ad353bc5f3396620b3b868e3024152114ec19d2a6bd99f2f620fb3fb91676ba448a0a48c7d4f45b9967a425e6d54fd571de3e71ac6581cf30e3e01c82c3c769fdaa122aa82477b6bf55ae10187504ebd262124152f1bedf84c4a8de2c9fe3c7c0a25c13bd1707a664f53b1ef8119199d275e8e132a06adb928119960150a245567a69ef11891400fd001a396296ce8c8bcf00ba09e7874dd4337904a99cb74ef2429ecce7120777584c36caf020b594929906f6615189b9b0ea7f1bc1d102870566049faeade8d901a27c1e81309809499b336c9cb91ada67bf9649117db321683af1608fced281c4ac0a39a887d4a426b82a008a374f7cd92a8f8fc9040ccda4eec63cd080f0e0c0f675c741ac8f29deec08fc9a7068296e5683cbad9c8cd48ae62fa0f88806d2a497871bf2d304aa3e8d586bb99cb468b37219868f4cdab06e40669930dfda328c5ba93e652a292be7043746772501d8011c7178a5f91005c57cdd71a20d7163162f768b13ce18a0ca97b5fc8edb7f4d48c9bf8492c3fa9d691b3e9edd24187491ad00b898857927b75f417489523997e3758dd3e329fdd581c851ef7dd4119af25126347184d704f31322d103e5f8c6ad316ff07e10639912417b365110f7d6b0ce6d8c136bc6700adda71893566c4a0c2ca84118b598bc581298f5517ce10f5d5b5cec7980f4c2c0c11d85b991d682e27154960b860ca61cc65d981bbb8a87e8ef98389815931c814cf868e480c239ab566c3a9299de5a6c92f3302862d34451cc20c3fe4748e65e36c26ae116ea582f7e40508d093838e2788486e499028d504d3428f9a2449a4273779e2d4224125411a12934d9feca4c8a09040aa18d13338c281ccd34b9ef7848d4e0a741a46877566ec49858597e9fccb0925afe82778940ef93cbb0304460ffa605fe7289512f5d509c0b6c291407c22689817b27e1bc2d4c61b427e0acd70e4a974445d9d4aad06dd05c414692fc8fad1ca1533e76cea8ff8fa4b84a669377d9db321fee05d310647e484cd4bd754098ff55ed8fcd8215c6ccd0fdcf94bf628a154da105ea710ea9ee2a3f34842df2d236a9ae0ee7eb7ab62382f852a2b765f34e33cb94e7ad00b5ca312abd7e0dedf35ed497f32469949167f22bb5aa1dd1e8ccb3660cb2b48d0d667d49bf309cf66d026b71c1ce1c635c840959a6a682338ba5b230db024637d7b5f74dee6d3f5101fd98caf0c256d5c5623804a0835c149624b5b589600cff67e4d561167ba5e3afb06ed46066501096e63e8d7b16e940f00618c51b2bfe61b566550ece5dd330ea8ee529ab85aa8a69f27cfbf31013c586e0b6dbea680a90003b7939b47f4761d36f67cbe922be9ba5481727ce0d5807b2776972ec71fd123f26db02a29aec1a15ddde7b4db0abb125d5eb8705e91cab02a6312a2c4806802a2324ac5bb693275a015d1862db9b7b623173c6760b34c38f8c5ca725532820710576525ff11a73b71008556082cb7bdccb59844155bb980a821c1e511bd3277de14108780eed5695861352dbd5deb0d45e4c40b445adff00120705d801d901301d88a28697bbb4f82bf158a02de10f0e8e3e9735c6c94df592f4613785eac5becd98b05fa0cf5e5b515b592949db22d7fa5014cf65a7fddc37f910672fe694efe4f5f6f341f9e3c0fa1fa793645ade3cf6c5deb5cedcbcae11470adc4c2363d2302c2eebfa5f7d343056b5a45f26547b93dd9e23682b575b55453f86c957f8efbdb0a187c50c26a4e62516557099805249c2609b662fb68512b3cf831dc212573904a14e238daa06c02a0a43a30136f6a6dcad205b8d40305b0b683127fa23f41aacf30643a763f9cba541990b6661bde4903870260ad6816f3f6211fdc761f26205a7045ce02ee9ef93a2ac0b56f4523fe15f9a41111b86fb0e9156cba4cd33005dea7f379bab0ef2083cdbda90925db4d43dd86db1fc5b4682405762066291a061948b36c2e7b806d8c6024058770999ad0379f2dec52a143b5ff144f1b37fb287943c48f4e8c5a94625ae48355649a803e7cb49536ebe8af5fc3f7725127dd25d5db38ec80d3b3712105d6a926337cd3c625dfdd706d7304f9946a6d815540671e55230a01c39d7ca853f4caa8825393a89876cdbfbccfe63a93f3ecce4a4bec1515b765b7aee8988e8774785cc18df362d9aab9ec596e825e9d269efa8389858f142ed4b01f862422b316f385e5205c47a2a19d77a569fc63d95e40dd870c8fd87758c03f2156776bb0bd7c076080e2ca187375da4f1c5d7986f231b1723d07f8c31561e76495d2d7e0d3276ed0ecaf5f1038e6b4ac4d05031e1ac05b1a0c2889dba70b07b499d1366d5696eb084115d17002839631235ad5536365666dc76061b49462e6724411513da426d706ac2d94c925c7390026f0aa52bbab023d3f854f923754343c44b102fd230847c6c7e33c1cafcb80b61b5303060df49096dd224ec0917c821c8218f3c9689d2dce4d359338e4061c9ad05f09487e145fee02e372d1c8d6441484a8466cba454c60b45693eb36493e14882f0075ec392b40f8fcd2ef7eacb7208d1543dff8c61e806ee04208f596d647df1c92f0d91151eacc2c3c63834934d09b73ea6d2ec9e97a8d696105c71031a4c8e934d399ebebb2576240e673d6c9ed36fbc604bcd2c123162096051a1345b992535d9bf015af4f6085aa662854dd28efcba599ba94459303c2e421de6b93edae2a2ff5bc5bf66b03c4719b3df82d4f6f3ec16a9df894e836953882c8ca2e8949e31ad17584255a1145d4f6f7d68bee4e3c2588022bc070618474128b7e29c6047610b1994d7dabeb713a07d868d047f471862c2c46e4ea7aee32bb41ca8ee72501f42a1ff82862237511671fb2a006deabda92925fba09e25829883a1ba19f2e7a38acbaaac4886da548ccac63f57a15323a454b0d6c15d1a42bc7975033b87236d53e7f404a869a78eb89a4443e176bdaf7c7ce501953043603d8ad17892d609636f889515d5465372041c9977634cdc3d3fde6e013163c05f5401f096a2a80403fa55b6427800329f1d72896445d80b218c191079a76859d2245ac4d4a1e8dfce89162c1590aab01e84ba3ecc961902d7f69e7c68e881958df474a410fc00b86f95b4ef4367ca50d018e9a6740ff19ec09929624c2b0ae195ef94f9fcb258143ca1c533c502d33693a3505e2e84d3369df647e1a871ecf7d61bdbd646e570b03710421a43a1b727bd47da66476b208fc5d494a9f569319f5a56b8817e7d53e16a2635e48c9edcf8dc65f828c2d92bbcc87fc5a2384874e5e12b69c5389b7d503abbbed981321c8b7328593a9cc1f20bc5fb5a2b92f4e365c73f57921a3263a0109851944ef996fc2e2f79e57a3a786f35a7cec2583be413b8931da98970703b2f539597940f0ca016a74c48753d53bae942f37cbf614bb8834eb09ad37c7311f449f3ef331ee6e9eff931a1db26ef98aed026ff4c8011e25a5960f8ebae9b45202ae2c0241222aff11fc04518563943edfa68d76171c648450c313d0542daec89daaad0ba62d3615dec018259d46bc5aca668288577ae4aa0ef9944f962fd04bb1d7624e0a85434937fa16526d2b8ccf4bac20425a33cec72dcc6278c99adab1682af7cb0e41a24d905dbff7eb01b32b986f932aee7c323da02115893fc758bdca7fa5203f7a376c4840b8369bdd2df9c220cc541f146629ad23025cfa26b984e330cd798295fe90744c06710498a3ef2ef3265fed393cce2b7615d050309feda65ec3a35a1250dcc7c4ad210695b2d4360047abd7140a5a0a7dd812b196a1bd6775152c45029e2ec4a60a3960b46439330f0f0f0f0f0f0f0fafa18d90da483b8424c8946412af76dceaa5197557a694a44c2909ce4c7cd91dbc8377206b6467c4a7934509620a2f0bae0a5d7e5612c6274e5153dbf75f7c99ac270eba2246549377299b979d38485121457b98fa87cc685891030c4e9c949c58150de7a6e3927480b189e395a9a559887532e66634ec03c806189a38ca8a8d980d977d3fb3a0616ba5071899385f040d3959ae4745048f031347ff0f95d4f9d550f382b9a0e15dbc1e1897388ce44a2d6ab63203862556ef5049c9468c8d2d6c201a364200a31296685132cf48b26994408b74564a066050e2145537cba5b1c352bc5cdb9916729cbe861a0d05018c491cf3d594f0eb1c592ff130004312a7494253df09bf60012312c7ebb3935a71439e3941e224d17dd734a8304a591fe8c2c6164b01188f38977ab9141bcd1a6a8e38570c32cfebaebb0bc068c4294951c24ca420b62dd6502bc133e29c737bc9da33b9db4c329108c058c4b1532e62aebad450db82b9d002bde315301471ba8d1199c9235c84bf869a3d0046224ee611d6efb4ad2815d650a311827f2faa0276efe7001888389d8eab4d51b5a4ff431ce732e5b4a410ca5490214e3232d585388dc65512d9bc2caef850020c429cc7fbd5a25ff6a6880ce26c4a95ce74997f477e8238e91eb30c9d6c6a9436e363c69a0046208e7bb2499a0a237e210910a7f0962b7cbdf504187f386a285331c1477e38e6899d2482b4eab7511f7a4d21649c58623e9c7e938ca92184d290e73d1c946ab694b35b627bd6c371d56f73fb8dd5ee9c8753ae1e4bf99f9d96331e4e4a4c7adde8fb1d0e26cf23e6fbbd9453b5c321e5dfdf87cc3721cf3a9c3425c85a1e8d913b4287b3e4891584a65c26229cc3514d5facd80be570c863b2f15a412be671389d929b3c83d01be4342c3e667c34e0238b2b262043468e40868c1cc1192dc080c329cfc45506cdb5f90d0799eb340b6de3a2bae1d89a215ab8943c93eada705229236224c9d870ca9d3196898fb469b6351cc534c5966c25415fa98653e65049684a0d49229386a34fbe0849de050dc78d9727d4522b5406912f609ce1a4a2654d5a3c821e13491c30cc70cc62faf462927b3ac31a38b8011f33ca68d88d77fe1aa9850518653858fe51f54ada94a9b5e0d8c20b2eb4e00206198ee329a72a5c87f69071f00df602c6180e7f62aaa1f67f23070db32386030081802186939688997b5bf14f4f6138650491a476392f520e86f39799a5ca5c3891375fc0f7626cbc709c9c6717f53798b0b50b8768f147438dc98593e8bbee8bb316df2d5b387ce5da57bf65fc9668e1e027f24bdbc6d6f62c9cf5ecc664ee35d98e20049b87808185638989b9d12ec913697485c386b9d18d642148535be16426c2a2ebec894bb80a07512526869b9d974d8563c917b1ab396137de291ccc23a920f39f94e0a714ceb3a243f66d46cd3289c2a9ed2bf67b8974aa7450387f5cf68d24d913ce9a4e5c879297c1f3aaa1368210d048fcc43000817201c8030c279c456ebab05d89fa736ac2416c92a0fde2c484a3c8b352a64e5d25f92fe1d4f9a54509afe81764251caa5dea92fa2609c749238259cea96d6f9170d098c9a13d312feae50887b8d6657e6aee7b538c70b62cdd8a672292e94a114ed935a5b89908a7fc9f5fd2f327e84a86703689e633daae57260ae17c29e5bf8a77eea7bd201cf2cf7dcfc87658de8070da5cb67719edc7de82f18363a5d04ce7235f77be0fce1b438ae297ed1e9cb252388d5ff72563cc8363b01a11379ba129da1d9c33e84a9baf411d1c57a4e60aab961c1c44be942ca892903d3d1c9ccdae94e685b234767900c60dce93fbf6349e12fb7fb2c1393628bb58a6279b08d5e02c26262692086d23453438e8502327cd058b1e9b191c4c48bc50fbb1517d95c12957b8f8ab92a6e1a23138a4175d1e322c3038e9c875994bd246d2ef05e753718236cffc1d4a5643ed3fe04fa39c0d182e384c90fdbba067bd645a708a184a4bac0a0c169c5636baa9b1f82acedb13fdadc4d37e47aa38056da6749b48d779e1541c2fcaa5f83b5fbb29898a7305f5216da2ef87aa9ce2a0fd4385983dc37655a6385b88ef97e2945c5e677c7d529c3e6f444831eae58d3e8a4310daa4968590b07d8ae2d473a272855852362ea138697c6658d0cf9431041407d9c917e26ec9698a9f38b6ada776c5ca1307d917f355c62a624c278e1e692b65ca262c9d8913e79131987c7b869f7e13c7b653ed12322f4df29a3828dbcc99bffefcf24c9c228e06adb334264e42b7a4a8fbf3258eeaa384c7697c4d3f5be218a2a9c6fa7d2b71d211bc4fc64a54b19712c7cc3e4bd72c67e9ce491c36485b0b0f91b4dc298953f76cdbeae4d2783312c7fe7feb519599549b90386493d72c299adc51e6238c71bdd61435c4116799a8a6f9da24a450234e715b6335dc6710f731e2acf5f2932af6a6642ee27c2a6b9226466b508b220e96367c75c66dec0f7441e33fe0e7b7f02eb80165481040224e6ad3b5e948a2174f0120e278eded1fe726b346c386af0704708883be3bb56fe32e7bb12df873e0a011822dbee862e6de6d744104608893c9092e12d444c9badae01268910c737c1181126891300034104021ce3722a620be4d1c1000218ea3b2355f3e6d947635d4f60b0d086010071d93d8a32c28791909e26476596fd35f6b6b62122080401cde4f6b4e9a98dd1702c431bd6bda7f478c1163fe70086ae36662b44831b37ec04d8f188d23210610401fcedfba26735c491dcbf3e1581d2ac96fd3b29efd1ecebea1a331d4480f47f58cab3659ed5d64f270d09c20ee93eb4d36888773ea46481627b488ebee703e4bc9648a9031400076386cd6e01f62d6e170fe9237f8a620b582e8702aad48e21374d94b7a0ea73c3a8345d68c31822c87d30615ae614a5ad4d7c4e17c21bd7e88d312f15b381cc4e4db7d6e1a794af28653b6a0f2a6fef90b3a74c329fba91877662405ef04d08663521f191776574c576c38dfa92ced21396428ade1985a1a5f456d2bef6a38c89455d1083a4cc8d370f22a7161d2e44d66a3e1b8c9f54cdbae2ed26738498e244c5614bda349663845331d7df9082aa4a90c272547f95f881e190ee2e96e1674dfe998c67032b913af6c92761b93188e3fc2e7c26e27ac98c270dc9798134524cded12180ec93c46fe6a9aec2b7de190cc36c52a8b1bcb7be198f6ce72a6b01db1efc2e9f287d6ddce85635a7ebfdcb61223dfc231f4e289bdaf2469d7c2b1769209d32ec15ccfc2512f95509bf6f6478485f388307243d63d5bbec2210565ca54257527e3b7c251ec5764d4f4ab704a3e9752cc57316b792a9c54843b9942c5464a7e0ac77fd1155dfd99d92485631212b4a4a047e1745b32560aebb17443e16c65bf296d2bcbc44f380611997d6eeabb66271c62ca96922aa937e128baefdfe3cf2d93ce84f3a5acaec154be84a36625b9a9527a4d952be1dc16a92d6782c8dc51128e495d1e19564aff8810120e4a49772bd1267e54d0114ef14f9b983416b48dc808e717597f76a992e91015e1a0f233fc6c564f9321221cc44faeaed62071ff1ec2b1d449185b11429fbc8570cc1511bd2d97874a76100e49e48c280d2daa5a03e198fe62ca67f95664fe07a7b2115142aeb5b4b00f0e31f65ac85992ced41e9c3de304ff92a4e62ccc83d35eccaf31e8f65215dec12924aa5a74938d90631de0e69dd9926c9c8353fede4ad413cfbc180767d3d02e226688088b6f701e716a84d8c7feff3638c98914fb746d4a3daac1416bfe081a432fa30805ceee88ace15c69a7bb2aab6380871a4e32c8903645a9f8999d86b356ecfd17ab4965ab071a0e9752241567458ee6ca190edf9b3526192fd89c40e161867368d5dc9b6cec3bcec16e6e20161ca011804c7894e170192e24b75a501f298107190e2a628839c278394af039bab031a390041e6338693c33a12ecc89597e080f319c427875c9f50ecd75ea834718cefbe11b638f96a825bb8307188e32774248485a499cec0ba7d899921eed227c4798070f2f9c7c6edf4f6daa281a77e12442bebd72176ddbe171e1a816a772b8445d93bb1b3cb6709e64316752764cc8a40f786881517726e45610ad520db5c32ff6230b067ca08b0ac890f1376c54a08e6bcce01a3364c8e01a886b201ade05dff0c8c2f1c2dba46c3fd6d02e6cd0489d8207168ebb9da9e773a4068f2b1ccfa2d987fd4912daf9dfe01a38f23dac70de517a825b280b7de11a6a9fc3c60868e0e01a6bc3a30a278b166a55256d64996aa871f1c50d1b5f88208f70e1410563c50d172cc409d6dfd0c20b4614784ce194f13766c8d3dbb2be865a073ca470f6f6fc92383ab9281c53c85d5b4f99fc1a34080f281c74ab558af9b58c372b203c9e70d26ba27d4a69dfd5fcece0e184b4a67cd59a1b6ec2a9feb4474e5ac634ca82060f261c7deb92cde45259c2d1e6eeeee2a8f48dbf124e69f9336ed0162fc4cb47c0cae0918473d8eba83799e812c25cf04042d9165696d99210c2820334bef038c2d1a22865ea2f55fcd53eb2608006687818e1e4ae775a64299965b72458708046043c8a70cca6e9de5e7d4484b986da0d1b39b4e872430239b448800c1939b4682c3840030b0f229ccc4559fb87b2865a0e2d1a6d011e43386484ab79d6c7f6a4861a8eefc2068d3209202db6b0400e2d1260c71e42d0fc4a7a88dd90e5e0f7e26fa404cc8f2087161af008c2c9455c0669ba94ae940081cdb8a523c2e307e7b858a6444c90114d7d70ea4865631543479d55a885f0e8c1317885cdb4298dd6bfca070f1e1c458548298968e789a03d78ece05c297aef621a5563af76e1a18363ca095d1792da13a99483535808b69947c490f7ff076e7ce0c647a040c00307a7f417b19bedf2d285183078dce058215cf4124b93c93362f0b0c121977a1ba5e25a265fbce05183939d67d22ef1afb839390f1a9cf2e7f4569c7c661bafa1863ef098c149b2564ada6acad662060e1e323824b1303b3289a7ab9a85b13678c4e0b8a3a44aaafc1be242dee00183f3da48ab7bff8ce0fe0b8ec9c243b3286d5d705cd3792d21befb7e6cf068c149988aa8070b8e96dfa352f3cc551ce75b540a4267658c93183a5471d8152192253fa93a261c07828e549c92baaca3dd465c4eab861a1769853b5071da3b15b4ac925788f31447dd202208ad2979a638681df9fa23161bfe183a4a71b64a67d71aee7e7559411b1da438785aadb9a9fa31e3c3d9c61626e0097c6471c5c7651c3a46714ac234e809a5f7249a5c8310d44087284edf554aa43d150ffb1ad0118a93d01052eb689712b382e220428a9133053159177fe298a4d68e4c2f71df27c6130793d1441aa5d23b434727cea3eba5cc57333e6d4e1c52457bbfd3e9173a36715c9fa0a26eae53e7996a02e1ae72298292944c9ce2c98adea544fec919828973f986f09a2e9e5c142f71ccb420b6b4feac65d01227a9418624966e256b0e2b71f6b1db1853282971f0bc987bcc3456d224d4b07c15492411644c1267d90d32450a264afcde489c246f87b975cdc8e822244eb1542825d72765b25f858e471c27c860fb95c29a94dc5b204717cfc55aa1c31127f5932d6b9730a9d0d18843981817b12fe95ff97114469c4b9518755731bf4dbd88f38a30314d3572225f6ca42e1c708a3889d1df52e9eddfb031a32311e7f7b1a0273376f13d441c421e6fcbcc7e9f3b15163a0e71feaa9535b9ab4a491be2984d988ab11e5a5a4b4ee828c4d144cce8a34f2f745236d2123a0871fe92c9f466bcc811d3a60b1d8338ee05d9aa624a759cd008e2fc69bad56f696b260ec47944ddb2bc895ee800c47936c5e7c858b5ec2b0a1d7f38cee54cea299b846e976fe428c1f998f191c3868d0afc1621b8f11148fc57dce022024af4d0e18793bb6f0a61a1324a56169021e306175ca009d27f77f4e1a077c44aca52a3454c7c38e5eaaa3c2aaadb947c0fe73c151725a63e514d8ee074e8e1a454acda18d5b4e593c9c3c957349366381b35193c9c2cc55b49bee81d0ea35637779ad468c60e47135a2d454cc973dfacc3b1728890820a173a1cacd298ba1435ef2599c371f625eba6b92829dadfb06159e890c349347acfeda91a11b538681b4995b5e90a0ee7bef7caa332416732bde11813c3669dd8a6abbc1b4e9b2f840e9544ca8a95c5151f34acb4e1a0445c1c13ba345a92b3e1bca7e74d677e0da7645a1b624a133fb71a8ea3446398c592b0164ac341c6c9a4d46caafc7ba3e1a8b5f9efdc64af8658061a0db3eb38c3215fa5d6241ab5ebc21d66385fcbc4149694f41172194e69c25ebf566234cc28a2830c47d98ed39ab7ddba3763386e88314ad26ec4702cefed48aa52674a360c873d8f6093cf7f33ad6038a972bf0caad42f1c278bcc90cf37355ad40be750936d215e94a851bbf0c5bbe9cdff92960d7470e1683ba6f59362082d972d9c3e4bac8a522a36c7440ba7140b136df375cf255938a94c1b84668d60e1209216e1e67e622c835738d68f0ccf542366295ae1ac312bdbac99f8e6b00aadf59ba4af8bab630dfdc8e20a2f70987650e1a041c6c6929762a59ec24934259ac8d923437b5238bfb589c94ba579f451385e6fcf5a5c9c95170a07bfb093dd5f31a0e30995bc0ca7a76571e38f191f3264e0c861630b1c5b870e279cb4c94b9bb4f5f2437c3a9a70b4131b359923d4cd4c388750692bae4974886509073fa54dac970a426e5009a7f0aa15dde49ea64ec2a926799bb0b5cc52de133a9070b40a2a4919113ec2d144662f17e1d6b1970930a1c308c730951d1a4246091d4538bfc66d536d9d6ff2255ce82082a2eff296502a47a476e818c241d89f9a51cb5359f7113a84702a51d33241565d7077c6474de063c64749e063c64761f131e3a322f031e3a3aef898f15110f898f1510ff898c0151f6ee808c2f154239dc58d191d403897a891652b412925b316f8985103073760cf0be6020d161ca0e1808e1f9c2fea294d2762941dbf865ae6a1c307e70849a208a01102c4bdc5169c6430eae8c151c279af5c467bdf041f021a38de6d74a1f7850464c8a881831b500307d7b8e28ad7e2461657c89071e365c868d74774f0007926b3e7b58c080e3a7670de903535368514db3f2db8d0e28a8f191f58d498f1376ce4b040123a74700c37a633ae26639e59430d87163264e0c0f137f2068d2d3870a4a02307075da5a9777295265836860e1c1c766c7d53c9480d35bb0b74dce0f81b36c38990a2e9d372a0c30687d45f15825c10bae1770570f1b1800a7cd8a0a306a7ab79b11629136dff1a6a67775774d0e014e76cd2bac6cd7eda7761c3ee011d33386b0cf18da7b5c64cc6043772acda62c1011a05e890c1e15bde42bc2077ee26171f5930e00301bec516cc850564c8409342470c4e4ac577f3957c04db0fa1d00183a35c9ee02ad9572ea88968e878c12158058d5177c1d0e18293340b218feecab030de163a5a707a11f194fe45cb2aedc70c1ad6c182a39c1c25f267f22f1f59306089e961c1011a00f8028c551c54e90d1af77fa523a98002ae00431567977c714b49f8df2ca9a15664c830978af3bb66d9d3a8da692d0dd6e3dfc507b4b0c01460a0e2706f2959b890358ba41463028c539c4ae8d3b9fc9772c85843ad468ed7c483618a8358962057a11cc113d6820b1b5d98600b1b13f85fc01e0e122c004629ce6f695a16b63552fc3880418a83dee58929a55c21867514a7b0cc25e36ce4964b860c18a238869cd877abc965e32b0618a1f0b7bb5e94bd4d8cd601800f304071f64ce51ae96e6d9ee826fc34e58815ceada5ee728cc7d90a062baa7098a0823e21226aa34e54384c904164a63f8563502337559221242197c2b966df4f5585f44a3272c18a289c548c94444b432a7d3e140eff2f1b54fa3671a57cc259c447c8b1d558fba1134efa679b2e29d56dc18a261c2f4553d2164bc4fa13138e998297ccb6ef124ed6ebebd737a3c4460907514b5d8ba6e2f7c30482154938f9a83497dda02a79e31c36b2f898f1b1c5169c45de07bad022878d11d0e080154838e64852d226c65757c88d1c6507818f191f96052b8e80bc4d61923c5beb3e19a19558df6649ab4a3615218db027aac1b4f29a88f0a9c81cfffb10b014639824b5617121741669e28f8290c30a20a0b343c6483d7957463f682c93d0923399423e20f9c93c57cb92e11e5821744e489c7097632b7890c64ca916e31d98214ce5e7629f90191d9865f36a5836d3a4cdc197ebfd4bce5f650907e88a2e9b221e129237289245d1fcddf669411b64ca6a2c5b9cdca15403448a4c7154478364d613aace5f53b5e70547206d8bfec28697e9c08a191c83e6bf785142a77721e01ce4bff898f1f131c30437fe0b0a7cccf8f898f1618287c0c78c8f1b6feb0573813cb04206479f24ab59b1ac54750678c15ca0e981153138e5dba6a99353e9fa0483b3241133e69674d22afc8253d4d37b199171c129939a4ada33a4056795e09af6da324c6756b0e05c95dafa2fb6600c5c0362ace2e0162b857c21aa388fe6f0dd48c137bc948a83e5d0dd8b7f1911a2a8387758f6b828d974c4f114e7bcdc90bf2bdece2586298eb6224348a737fe09efbd6c01314a71144d1935645d4c95f518a438583c4d5f27770d352f6cdc701a26b891634d70230b1932100e2db84029c418c529c689ea1bdf92ee21238a9354c97fdba74ef67f1fc408c5f96408bde312725c3441714a273625c8abb00a8b7ee2f026aec637a98c295c3c71d4786eebea75e2a445488b91edf5ca7e4e1cb45a8590f5bc4b6acc12626ce2f8397ace267c5cb818690009313471baff1131057595a26a35d4d6beb92d1347cd9c9adae12237885ae0b031014c9ca4a518df228d984a290e70c0071e0b193270c890610224e307312e71941417c9eab45eb610201b5b74d1420c4b9c5e53f6896c4159e8bc1287a4f26bf4ff579a184484189438bc858a2761ec7655d7102fcdee122f44964dd9db5da4b983189238ea8c0ec96f298798192312c749729bda967d279864c880c4c125e88a215974495a7cc429dcf574369bda6ff46f10bb2286238efa36226ca765dc383f2046234efb12b4bad268488d33e29054d7cf4e6e10255e193264c8b8e26306161ca0b182188b38c574ded9e75a3971451c459a49bf6497428e2811c92c1b53d64dea45c459f7729b88f425b2ca87385784bbb4d61ae2907db646e8370b71cc663f4ae556aab610421ca36b8fcefdcd6ca70de220ff82b03d75d9622909e26892d62456967511fa409c47ad2ccbdaa9f4a705c449ac2c25e50cdbadf20f0719b7fb46e8ceac4cf1c361a4e9510fb16261efc34154a40b3973c1e7267c3879a89d5851db12c37b38c4f63719412ef86ff470aa2c2a365c7526c42c0f07192c5f52722f3c1c4ec8d55032bc7e43bec3f9fd5cacc3e254cc163b9ca49d963e13ab21ed8828885187f3c92442a4905b0e2dbaf8020327884187a35a4811ba4a755fc498c3295b9814ad2968d3a395833331e28fc9ac89c36135f4c550a752de6882c3f9d2649c1426415ca76f38e8dc2842e978982c951b8e6159e5f52ae7662578ef420b19327060c1011a3662b481547fb59f25dbb3e13a39d714ee23e2a8d780ce1b512fd5809b927759624b8ca14b037ba2aebb64558c1442c339e389d8956d1a12c2831867386fd2faf6b33166388d58b2b44c7941db329cbd7a4ef7b48b418693c509b3fb4ceb91ff319c326ccae7edc9d3530c07292a3a9bc454743731c270c91d19fffd82c0f0755fd0a2440e7de1705daa4e466d6d1979e12483c964526643f75d3858760996b6612e9c4b56d0a095925851c9b185c36fbc9c1d3967656d2d1c34c657659066e1243be4868493c1c2219b4c7ff15ada4368ae701c931445bb4a2b9c53c2240daf334d2756e1e09e9b23a60952e11ceadb8450425febbd291c2d531022c97092c2612e5bcc33be21fdf63868c890f1ae5138255169a6bde1567d2c3840438b1850e075279ec60c91715f10f013987fddd029e9420b2e9cb06ea4788e269872ebad64aebc5b0d35e7af71966230a136537ea991d2ae1a6aec058e8db10445b25cd84dfab4a86aa8992087175c7c89c04716577c28002f957012b5385ddb2a42732909c790c91d235fada1a697430b2eb4d882468d1c5ebc27128e1ae6e4040bd1f6bb9d11e3088cca1075ad952086114e49aaa51da1496613a5229c4bfbb2e6d4d0203a12e194f46fec68fd8fb55c16c5630ce15426832ef5674ae64912c2796c4536cea659ca3d46104e624c9dde70eb225312100eb925a56d4a963295fe07071513e23f4146cb57f9e0f8223792a94bdb0aeac129a80b353b99761f2f0f4e4205953dc1de812a2ac4a09405d9a0cc1ec4d0c121aa08a11b536e9dd4e7e0a0369a5f62353838c88f535142ba32d10d4e13641257f56dbfabcb0c62d8e098c2ed051339474fd663d4e03896d2abef5757d2eaff16b0183438869aa03317f2dd583e83f37f7a8599d930aa443238f7c4519df2a3189c6265b598a59c21d5078363bafd8c4187fc82b3a49c7f12f33c56171c7305bd0dca92dbbf18a305879467d258b0a83b293158704cd617f4428fab385f5a50a32396103231aa38994bb4794915e2f24ec549e84e102b7751711425ba6d3dd7531c92eabdad8b144648d21407152f987b04ddfa3296e2d421329c503d8d8da22f48710ed3e2f59142f56946719e1421a5944b76a31d8ae2d8be3e7b21188a83525aedcba4a4a83683e22cf2a33a8229adf43e7152a546a45bb798ac7bc0c313c7922b32da27892d912443864727ce73c9de4e2d7325536ec1818386a1771b33b6e0b080c9bfa1587080c6013c38711217597d3345ddb0f526ce21642a1322ef85cc4e13e70c97a99d1f7ea5f732038f4c9c73b643fb2893e4b261e274327d9a9a578b9fb14b1c8498d3741e9aa747b3c4c1629e8d10a3335656e2e027bfb3976a76bb428943ae8ae152cda5332771debcbc41a9fcd520a287240eeb23aaffe2898a9d08de02eb118993122aab48d09b279ebc0c078e4708015e70040c24ce27216e3293652535a8863ee22442a83d11edb327a478510f479c32c4525518690d35921e8d382459618410f71edee6178c38c637136abccee543a7c1c5223a194a735393dfa988c365f4892198897b7ef87ec523111e88f03844d225ba991296459521630b8e36c4c9beb54d89101f29a385385e85cc5952b110e24888639824bcfc358338243913122fe404e111886390674282d99aec9e64f131e3e3021f13b8e2c3031007a1920c1593429031d713b8e2c3e30f7bdd5c4d1c597aaf871f0c42344e6ee632a5217d386528b770fa848548c0830f8d5051cde343c5bcd2630fb5c5a66fd7b518f5f0f7c99341847b9ba0ba3ca86615453777c6743df0708773a6747131da7ec898d750b304dc35c0c30e87eb133da24397a60d9a79d4e1945962da925bdf6d7e036c6421434609be068d0f2f5f7cec171f5dbe3014f0a0c37934a9d89325c489f8e7707851a144c4fcad92457238ce56f28b5113a75e138783ba4ade251a389c9418cb15f632c92df30d6713f23fd7117465d60d4791a15e6445ecbfd886f3a548bb0c3961ae2b6c388f684cba1d0a402040160e415e99a639b99a82b0700cd771d715bac2b9f572c83c42d2c58d158e26729e17db646ab50aa7b0ed12a236d829930a27911dba3189f3b6e44de1a0c4f7ede6eea4706acfb01941478418bb289c35b3e35fddf3f20405c3bd65fab553aa80004f38069df6db97bb36424e385d50beaaf15fc1454d385f703f3713a6d2cc4c3858252fbfd494216596706e3b0b2a5b259c82da7e8a3cbbcc3409a7eaac4d966e4b7d24a82641e9d095239cf2f547a6a0b32d4233c231ae9588cc6317f4b2221834668f20e26ac95c0d9df171e80e4873040188702e65f2bc4f96943d0de1341733a6a4bd422a5f08af768c5b8a93fa17c681800facddc8e105003e4180201c5c82d0d29c03e1242385c6ae491235f483936aff9514969b2eea8383a59862a788580f0e26d42f2d24a931e5a9061008c083d3e40b937d839b66f67770d24ca52bf2d4c129fae4d199effcd4f939385e586568e91399e3e3e05c314c6676c4a99e6e7032e996c93d34e9736d708829a5d998b306c7e0ef63c2d2ca29d16970de90296d082f99c171aef24de58564700ce2cc542add8ce4770c4e33c15e4d3dd4a86cc3e014847cdea697186212bfe019cb681155171cd3c9184959d80a13710b4e21a99ee50b255ae44b00169c3cf4bfa664372f395dc551eb6c738b4aad13a48af328a5952b4890396fa6e26076797276acc7c9a0e2ac3549659fe40b1a2da738f8a9d5202aa864fa35c531e4b90ddfaf77df95e2f4e223c9b25f525e8414c714b3289594b8b875ba519c2f8e4db83ca7c25213c531838f8c1cdf97b22514279710e3a8b2d92fab7c3000501c53fcb9ddbad9a429f989c3cc89c8eabfe0f0c4412911da3aecc47964636269881387499b33ab6bd2268e3116dd6c8297c865d2c43944367d260ed252c8a1b4d4d3d7c7c4295bf5fe7a84a462f99738467ad5baf491f42d6f89e397cbfad76f48d2f295388574ddf4912a74da4e89d36a6668ff2ac99bf4499c75476e5b3049e29c67f2b52f7d479e237112ffaebefc69c15b481ca45e67987871b2d4479c524cbe7f964bb78638e268da726ea5a1469cece47a8c568811e74b2e2792d266a46911871141fc3745844c157152639ba75e464f851271b4145126376bfbdf883804a16d95bece62ca218e1e4a29b56d9762c418e264329e5ac9de18922bc4795344e267d68468b3f2b7e48df020ce2229bb4bd68d200e26834fdeefd21c6913889334bbdcca7db27b630071526a7f6232b4655ecd1f4e2b1629ac23dccec4f8e19c973ee22d8dcadf993e1caf47c9598d193e1c920a773a520ced73dac3e9e2cba58c889239e9e17c152cee324c65caf370d26ed75a4153cc20e3e13c399455d2a941c8f70e87fc954296e9d72ceedae1a4a96eb4869716ab6c1d0e5f363f2b527fe2b77438c87c5149c8a614e4b673384d5ab5bc5e4185d5e57092225267d8e3704a09a32496b0701b048783c5b49c60296648e1371ce4a89ecaa152248dbae17cb3d1e2ccd4361c368892bdc8291b4eb729fca436f96791359c4289ac985d8494b654c3419205e16e266641290dc7389564aa90454fdad170d0204744b6cb1bfe198e1a93069933c80ca748226f440d97321ced5bd5c42493a021850ca79b5197936d545aaa8ce12443aa09975511c3a96e75b229f195d387e1b0de953ed297901243c07010137db38e26d538c9170e9b4d9fca9ab4a0712f1c564f34a4959c97f22e9cb27c5e5e621c59e2c2a944e5d37095a2e9164e5669639f58c654222d1c4cb575690b65e120367be44dcd9a62838573678998d0e9150e7bfa2c8ce8c90ac7b4aba65c92a5881856e16476a5e57ba7c2b94e538a20eb6ac6bc291c2f69dfa0662eea962785d38b56ac13b9454cc845e11893aae8fed17faf43e1d4611631abeb89193de1f01abc9234d79c70526f1617f97337566bc221c811f55a1582e88931e1781a6b6aa4664b38dacde8987c9b21f94a3879cac9b4add134879270d2376d5947e9503946c2a9ae45ee7fe9bbdaea08a74c9b67dd2229a9a76484d3a9dd0ab2952fc2513769d8b86fb1c974221cc2c60c2afbe843388f7b5e9cc916a42809e19482124994d21873ce0ec22989346a379899be080807eb114189b7fc83c3a5a0525710611f9cd4de98fbc6104facdb8343da1d4d69920c994cc283e3fe8b1255896695d31d9c2fbb05e556afdd1ad1c149b3e9db789a8c859883735eec1273a15e41e97070109ff9332a96f7896f70b25ac91b577923656483836a3999746e08ea25050803a8c131ca84582ddaf49f667a30001a9caa2da56ce608a536333805e9116287a4cddc1a13062083535a4b2204a59268cd1f8393081f95545c8d1b71048383c85b6d4abd864818c00b0e2944e508a7368d3ea501b8e018d297efc5ac2e3ffa01b4e0245a46680b25739ff807c08283accad817b2056deeafe260dd9f5695aee5d657c5612d8638499e9e8a734bf6cd27c442ec1242c5d994882849524abc1d9de2a472e694cda4442e1999e2a02fc6596f9858dda0521c75efce4b868b902722c5694c3cc382d6a3389ff6bfa64ebafb0d8be258bb69577ef396f04c284e722932ed7f5d88a605c5c17bd29a94249276de4f9c3647ac58c5522966eb89a35766171311edc4a94aebee5bb48e9c1327ad4a9be426b334a74d9c6293aa8ca01ac5d4c4f9ea4e85b03a13e79ea44a88de0d268eb1c9b37fc3bcc4c9747556e957ca362a9638843121d66a1b73a4a512a7d8582584aef85fb050e2742a448c91567754582671fe535632c8587e32552471d80c32a61c3e89c471efcae2ce4e927b219038a890252fb2f65b04e1234e99f735ea255a36253ae274f325e35509f11bb11107f5d297cf0e19719898912ac435d7b42ee2b4692e481d2b8dd9a288c3e6492a89dd505edd25e26c1da9cbc3bd235881885312baed7f54e910c7cc193f21a5185eb5210ea3db5204b529c4b9f2f859725115527b429c5236df19d56c7ab23d8884faae6c29fe268873462da5ad0cc449fa5fec05b990646e0171ba517f3f937e3df2fdc3796382fc16137b2ac90fa72e8b5f2797a52a53fa708c90d344d67d496b1a1f4e31294c8934f98bfd1e4e32da9908229e157a30f8846427538a4fbaf3d05c690916c6c594783095bcd1bd133695ee70d438579bbcb42aea6f07d484cc2eab212eaac3e14fd296ccc576a90c1dcc942b2e748e733849ebbb9231c44e14570e9ed8eb3d91b1445ec88a381c937785d0a52386a42afb2d9a6f7c0e071831580187c377b756924fd3ccf00d27b1759af46a379cdde6a46ff08c3b596bc3f9f26a99904165c34149ee95604ab3dd660da7245d8266d1b170991a4ea1213c84b62babd2a4e19032882bddb04f8d2d1a8e7ea78450e7259489cf705095d23fb2ddd8298f0a5698e1147a2d7268e6489d4ac18a321c279fde88c41039494c8683256951824b8a79cf8ce1dcdf33326917b14c59311c47f9a8287b1b5632250ce7de8f1d93ba11f3236038c88b3b721e4ad5462e0a567ce1142cb8c698345e38a6ccb10c415e10aa7117ce7726df72da890bc70b15dfcf50a3216fe1a441997a8bae9c31532d1c25baa81865c45cc4250b674b9aea279c25cbea62e1b47d4246d35a5de1048a7da8c4ed0503c248241487030251201406a071a600631408001828248d0663f1681c46c21f1400035532263a322c161a1c12121410108a03c14010100c8bc26040281408050220a158708c23d1f007847dba4834dd96206082be846a04161d2c8fef3dbd4d033507082790a38c201019fef948e867f8499c3f26e558eea95539d24fc7d646c27e09d412983661790432923c2dc12a024e850d2673165f2884998441ca06190047fd3e798a239f72f9f7e44734056563b43db2cd2ca1d65799e832ee56eefb4901ece6af072484753162ea4e3147c2ef08c246980ff51b5d6d9d6c4590822ce9d9192dfd596f70c2c4b8de089751e7d76d1725858f9e53bd4a87e8e83c74d7b637f35fdc01368c6eca77aa4907de187491437042e909ff36c2e6c98d989d7ed5506ff6dddbefb6ab3902f11a283cea273011187940a3e012085e70f6910592088817810c4a5ded280e6c817959e158a0594045c08081e2d5fbf683dd2027201d9060004a90c5c03e41e4018405e009e002670164030725c84c4980ebe1caf780c0d73f9d0eaa78506320764f6b1217609220379f890db420d715a61ea08c011640b93d0d29854d2f80820016007a80770013007d1ec00e3b39b9611bf8310061a06080e005305221f98228035619ccc5686cc0d202bc0a2a0d1c0b102af867203903984e208ddd2100e6539e178b4080c0b0df682e178017400168073801c8af006a8e1d7676e200eb03b6075a0d9034c027400370012005f82920290357b6bc3794e1027a03ec0720cf0086dbfa08c7151018b08379770e40168012107fe0b940aa0b3005b2cb573503c2081fd88f011400f6660050adc690843920bf401d00da00b203a008ac1240293809032d9153420536000a0318e39cac6f36065a3c826ada004d1b10188052407501e000f9007003d00d204c40d8dd59ea15809f00af47c015d5fd2f8b06681db032908a00a69c00a4af118f25373790a37818cdc7a4db03f320f97107c2e22349434eb044873acc1fef47da7dddcb83e041711e84920360e1ecb1ec81301fa7aa41d83069b01181deead7cc0fef3cd6e7d0092f6090d3f27e375b01cb2c4ce6c5486bdb8d91757d1f98253014b6360135714da0521cc08adec6a3b6d9a0c0a480a758a310a4687314bd918ebfac90a24f214d814c318cc2cc2bfe94be27535a14fa32ae59583686081e60a56ef64284fe4b3e7114bd6124f36623052385491180628722bb293c4ed94c9d9bc241814e114241ab28fe24190b9dc28ae40b458b83a6108dd7ccdfd428fac206bcf81b714f9f7c013914316d133d243feb214d4249089089081c060c4b09904f68e5d0089ab9fab945254087809862ad68e06f84ea8b5e75db2aed8310609b36c965b5a3b8beaeafa9e46024863d7bd88517329ff0534a80ae81a8616900e3fcc10990ac16e42003167837d3b2b21f2c4f42d9b5510477682942e3d5b0e5068d0973d70b38bb82c0924408fffae4c0c5b064ef78174bf2013a9528783fed9b110033d7193e6d1b6584eb72f7892b7111f551c1155aebd8c478c0b7d3bfbca8bb6e7e05f38a2f8cb1178ead0878891d7911f212a11db3d2c433270e6a976b2f5e615e30818ff4acc2646a3f0d7f9b16e75078502a2857140a5058e2f5e306e162ceb5d3d9a491e58c10fc8b2196b607601d8fe4a5d1eeaa113a1bce2501a49fc43ab0900a02405c708c76dc9e5e0a01aff92e56f46224899fb5408942609a9fae403708428311b6efe48b31c573c6e3e41e1b1c474f19774b18bfe2d4a462106a0e0d7c20c4d9cfa1fd5327d52bf1a52459253c71ec75db710cd31103884cb7080b13bbe5a5914d73a3d3768b7b489607bc47008b377c918c2496b778e9357516d720ada64e11f3f15b1c9ce923cbca1a0c4a83a60111113f72a55c9ae98aaec73137a5975c15c6fec86b0354371f3074a5e73b8c5968b8304bb14d841abd6b933b5c545d5e9b4d67083836d94cf932f8acdd72b9ee77dce37c27d980eb040c3bb3c3565ad2a20e207e49fccc9fb862b26385750d82e857461f06e4043f039cd1424c8fcba17408132a369ced90c25eae8b1c95730b7e6348b2b100da71dfaca0d19593cca72380eadf381bfce80e76eba1378c8594cb0a4714de6d5a5e6cc464e3c4fe0375844508a5b2e1cc91d395b4c2ed2c9a911f939fe5498827932983fd9c3ed86a90805def7426caa232b4b4a5d1f7e9ccc63b33bbe15377cb047a3b74fde2a7df74e6b36f9a069803ae439cb65b5af50b3c62d8a557e33a9c0772fbccd4b2f80135e4ddca2cba8c256c431451e6ccb1f8bcc56deb6fab1bdcd6dfed006907bad0d26cd0f799bc0b27b6746d31d7b422accd7f288ca13897f15aa628e9e430832d16b4c4472d9101b6975de840b35cc7ad8c22ec43862b061bbbccc4c34de4062b431e9f223d9de6f80c7a244a5d0a1a9a58197c1bb401302417bb46a30b267c71e76388ec274be8d5a316a420384c34eb5a881197e004d44704b27af5f6d2700da618a4701b63eac61594ce4a4ff3020771cacf0602522e5a5cd5bf7b18efc4e4fd42135d8383b62835aef13320d121c82f1630f0da01c84588caf3090e1171a70e02637324be16e1457bb8c88a81f7825c71fec462c0f1b900385a3219c4af3722c9f8efa2b55edab2b29688ccc0ebcda887229a3a7ce0592e760c4f0c9360621a0a8b6f7131835db947f4004095f86b87c326d054e10a54f608ed3993eef43e169baf188b2fe4f3ac02cb10ce350ae06e8ea2e4169ae5b506ab3a21a298f2058467a9601c206951fc9b035513cb51d33e96e24f2af51e8e250e4061ad00f2dcc858f7a3b838e6d01cc16c21b80ba6f3db58c1baf9967796d0582bf83b3d930b70286c9e063aeb8362ca57c01c196dcdc2cda5f347f83c8cb8e527c81196c95cc69d8dcc9a6675635b6e21b8d1b64bbeddb84c8e75388e24a801b20eccd97c3b4bb7042c14a43a7e6200606c73dc2809571c0021ab0a14a8b9817a9dea734ea58bda00939224537589acccd48630f73c7cfc28ed1ad8b82aeebe01c76c7269907f22173bcb3af6a4fae7c46ac5049ad8a7fd2a5659443b9a6bcf6fc2cd202c28cefa0cb615e15720e36bd7068f31d95b5251dde77cd29ec5626e9e68332d9b087c2e2d19aeccd6a2a2590cef301802e2361b6392f8304400614da68014ec4de0f4050e7d93c630bf4803e3a10dc6743d83e539b92c32f3c2a2b078a37afbae9568b0f4ce6380a8e732b822b98ea928778ecfe1d57a69dd985e69f508c67bca385f979a1ac215f2ffa2f437a04a7f74180de147b3f0dc82316091e7c40e967b241f8c0c28a857b9d7b118c99742802a3d497b3f342e4b3f6df239509e454b20efc898a4a614b54b085d2ad23518a203495a03c32092973ea1a037c2917400cd783c8ef879bb6c9d8577d2035002ce311039c6639e4e511387ec51470327021d0e4f772299b1da8383e7f6f2cf5f7b8459311449b210a311f0dc261c1c895b86c45bec40dd3bacb812537d4b31dfda5bef7fd427e760fcc86c5a3bc8c65f43d885b733cabc220f25e754e3977201858ee70caf024d4145b320fa82d6bfd8193f64aa0d112de4883e13c247fd7171f1046a37ef28d23d699511b636dc624e6e65730025598ea825bf3afae4bc0f8ac502da330ba139abbf1349dbb1b42a49eb590ebc07906401af02cbc8cca9f7ec2a24fce99880549003480a060a1098d04e2cc9a5e423428fe4abab24d319115ec3ffdf5b75e35455865655ed6ec4b734f463f3360d36bfff312715e8926bfc05d89099103bfc2c838ce483a1f179a95671106bf4c0e53331c5d0683ecb7e04879399c0ecedae27636a9130817f930f12dff03256cc3e750cc4e519143fd7e5050d611756e74dd0a4a7754ee03100abe2f71b5a9c7bd697609eba2362ca194cae3fa9781ca1254bfcd0897bca290045fe77e0d359d6289f61fc7d9d020e0065697c86701e6eab925d9c5821e3bb8d98e90ac9217df0f155aa3069cb91b17264d481b55e2f774330716bdc3f9b0c4497e7a5b3640667dc80cb349dac06636116ec1945d09a2bcb83fea8ad042a168ca7432fa6d0ec72299e382d0911113d9e3956046e7f79f232929e8a7393da1d5de568e845893cf51b3a3813eccd2276d491fb57b8aa909bd5e60f2dcdf34582331c43080b6affabe0beb78466d199e64159524a54448ef5d966c67cff52b47d0508d1c6e15f18b21da0ad1a505197ebd119e255ae9795b8e535156ef8670d1b177f9145dd3d457a0f833ae130f1c8fab9d3962b463d2fcecf29d8a390caeb6883b995275215dda32e7b748600b997aee62c8000bad3267ee0084f16b4e11278f447d6def22098153df517143d192d12cab0697e72a0f5b6f5a4a46db399c6424681fb080a92c27e2c870eb2f00d5b605e125aa3d41da28940042d6c783742f04a80ef7dd2eaeefa88422f6ff968a757d28480805ed0f6ca7a0a8f4cf12c4b083dfda03e46b96a5fa1b511ff5fe6a00265a6306f762a66490f38f5bc819977b8e387bd33dffe48aec2fdbb869275e9ce25f32965e6d07a68bb654d088e60de38e1f4f760c45524adf2eb2ffe279b1be2c0ab614fce0995ddb4f1e66f4be2127778df03640332d92fb2599712bd960ec04eb1014264e10f1a3b4d6611a2a3a6b238848f872491e40533d8a5c01b5bb8b99580fab7c6aaccf989f43356ee4bb82ad5cb4454dd466a74adae9010ad994f187cee4ab15660e027cfedfef1dfe7fdf034d4953c61aee9ff7c5a36739034fd7f6991864aef9c2d2ca16074b7e989c29e7046c04757444d3f86f4ea42954814d690f45b06deb962b6e1f8b5b26ba819350e3a4b36588910ca01541061295303769a195d7a0d4ba4379ccc61f31199b82909cf516a62e2f0f6ed7b48f9d232b1fd70bddc36520eaed8116b509920666a0d72b917c74b5fece793c949f3a8628e117bfc40fee3ce4114259870cd6436f049cc8e44c2963897f92f01839c98f7029d0ae5b9a908f00de3ebe885f23bb341cd1e118a0881d664a67439a8be538a269cd0e9ee0db247db7a330107a58cf2bacac673a0c1923cdb4e9efc06d5e216d7e8bc95a29414155ee49ff1b8b33f4b4bfca16cc3d4914082c9506af6a28bc373ccb2b37bc4cd0d231a7453288556a8aff5efcfa173b289c2d6b48a7e7b879430147401704491126222029f938e30b142b249eb40588ef4b8c2e558869b2b96d7f3d103b4bd547d6f151ffd37cd47a7d945c8484bf150ccb32d300f69d9c8105e362e0125bf1267a00adcf5fcf954c6e106e5c8125f63a7f6170f812b4c8de7e3fd9135ab6aa2703eae49e00c28471f0c54212ad72ca98a71f16ec4e03e269049021a07b202faaa7f78776407e0c13db436266c3591787329c01cd8fe59b3f319021e28be96e5db126e1127c7407dc47250b2f4661fd723d46d6b0b7864d889c3965f6bc8b3b0ed65a59515c6b7e0364c4e117bbfde2c7ca977dc9163643c9d66cc836d1ffbacc5eb398b1df47223b1164e89c9e33ab33a14d58becdd4d7bd6252190b8bfbd6ab7b66322028b2cc56c18f7646bb77266edfeb2d3629842cde9dc980ddc0fd55512dc5acaea0e9b4be0e8610f3f1d789f0b770872e7f2e0b12d1817aebc7fe3bba51dbd7c7cb489e598aabb13716e36fceb055796a42c88d9e00809861b6540dfd236f20a98d89c46a46abe711efb11177e7b75eb5cd24647ef85799b8b4814e583b85436f2e037f4b565054e2b6cb5b29b1ba8627e8ee5963b02a4b9425b0d94be6c15548125aabb8613ffe533f12ceca01301bf89bb1adec531bc610bad9a8e9c8b546a1949faaf29926302b59804343c4bde249aecdbb029f09ac7167c7472c02116d33f43036aab075937bb742412192b8fde2200920f8e91a859c6cffd4a510b2c4b4971e59d049ed492496bcc0b72dec80593c7a2d40cd4644a04c54f77cc484a32a864e436262cc189ac4466f77252016332af23cc2bf3fc32623612cc52e1502ee3f96f28b77be49ec0cda786874503d87f0911c478d33cb67c02a3841b1fe0bce80cde7454a4b5d0e1126060079f27a2fb05d55ef5b183d45bb2ed015f4083dc4457cb4c698fc2625aadf46db28c21cc6261b33182ba99ceea5189c6c576855bc7ae015164b50c1b1da74e15d05bb2c32248202bc1290b79cf033d713110f46ca252818cc91e09295cffc5251bb94659143e981b0677761e64d25463189249b9c150397412832de2d70a5cb9b294681c54a1380395fcc9be04790a741ea01a12f87a3b5a1afa9f307a94a08fa04a2563bce9dc540a406ab760e0029138659c76278248408ea6f656260e09639e103895ebb2152b1cf929b67addc50fc22b56002a65ec74eb9c4e5578ed1444570f4e0826473683380cb80258edd9cccc07cbe9e3693003f308bdce46a1da3cef4239d068bc26eeb3cd5d1a6107cb30e10ecca731116e0918d8b6aac15c25c2b96fc394d98ec16ca3cf48a15d5a88a437ec14b5aa76d57b0ef02a47ac8ec40d1d0be1d5d130510122257e4cec22119f009348ac679e3943034902b7965cf70d56d9ee7080aeab50d03fc04156d248b548daaa26049b94ede1f53c06bd4a235071eebc4751ef46e757b424d3732cfb087322936bc1fd5db9fc13769e8d2baece84cbae55505db0f410bcf8fea91b4ce0e094060280ad7abae2746330850d544c2f46f4c9a045bdc444103aa41102ba31acc6704fd6546ac0b707c5abdb82155f8cbc479b59aeb53b47a92a6a518399be85f8eb518c94b76ebf4756ca342a03ed9502e60227c02720ddf715d3f27d849c8fa510740cb32ec2a767d57cc7b21bd7f9e9ca78212acf2eaa9e31b3b808f043f6f42b47309fb21dbd87955122ed90b310e62c946a9632a53fb1f9bc2f7e4c10bac40e31f87f1ee1d1ccb39555092e1ff82ae164c60baf30533f77d13066f0ad4439ec5ade2d5e990109add165a455d365251be84b72a9c214a2aa90f7eb09883988c5143decd144498089abfba8d9a15c2fb0d6c1693294d902b99d08b3529fb0723c727fd89e36c47705514b486ee7ab68c4d0945951d2e10b198ccde3f6a276ed8f1dad814f1b1d6a8e3c0f7a0b19bb744de09eb9406d48d319a1c6e70fe6e0df1e041834617dcd49f80fc2ce23835b4f06956e25ea99ae2518c6fbaa80514451968447ec4a6a1e9cc93ea9c22391aa5ea3a2d16537313303adee96fa0f573851ebde45c1aea8bb285e4657e8e1297afd80912feac10eabac22a3c3291c80f6478a08a1a2892258af75ea3ba8c50b70a98f3d9c64adf6a2632471176e99e7e68e5a6aafb7bd354097b7e942a86df34a3ac4fbd28d1625b1844991a6dd3ccda080ce04090ebea9911851987cc5bef050bbace747ec36c661cd29e1a11df12aadeaf8a397b1baaa480122cd202c6252713345996e646ec354837a98d462b53076433aca6c3fe5a6753b365e8cc85de0f22d0459271ea7734a6fea0af6b898e39587c6a2acacecc0c8231e978407a728f95bfe6c3c29e7049570d05d6cba1aa295b471b59361233ce86ed08fbc9bebcc852173931cce92c81cab80a1c0fbb67c654d39be57f2eef1d6f6fd2eba2f7f5cdc72800db38451d02c835675cb2ae7440518d28b8a32594822945c66a21afc47a1443299b7500c9ecea5bf4b971ea7ef17c000cda4bb316e6697e11ac58c69b032c673cbc7044111769a04db4dbad48e2cd145de2bd3a8dd0ce316c3b8d4554586f7210557862acfb745aa2e8c1029b28973d19742e40cc66293430f44d8cb026c4b0b8192af1eb08d256365922320521296b82239e8966969921e1b1268dcb8726d0d4b92ddd3ec79fa97081b705ca6b7117d533a2c87955f43df43551854cd8b7b69b40506070758d5a06471d1f2291c8af9f36de2580c5e7bee539130cd586b36c8338e85c44057e5607a9dc1f906188002124c0a19fbd4c98ab47b8d88efe4814dbae56812352fe145d3e2811c0c3aad7014309eb8cde3bb1ef7267f65152ae1903ba7d1a5144b550437692ed2232dd48dde0964d8d452fdc6f8e34889b7738712f094d8d1dc00180524ed83c1d7c10a8451e24863fba259a16b1c0b3db04992053df73f2a9a97eeadb0d076f7dd6d9da08367420de842d0b48691a0640799898ff42ad53aa187f974468e73513c33e7a34e267bbcfebe7fbd2fe632eee660b1f716724a792b1b0416ae17f3ff7ed3bd6f5d19b280a646ddcf313ade2732ed59a1e4ab8b059cd955da2331ca8d57802c973761b1c79f9bceec5fc67750a9c93025f4a0906a534c09a3bd9154f2855cf677d2601bf82ec9c8a16340bcd8f7b9419daec67a58a9a04957283d62ed69a686e13ad78b1b032484e19251d53473178ed42bb8a88af1677efaef4416da218070af16d9e56cb7e2dd282c15593d813808bb39e42aa96ccc482f7a27b6fab1553c861bbdb12bd47aec90e0ae70c511e642dc48952c568603f9aae414f95d3f2d9fdfedd7ce651a38b45baf5e880d8db75867f6da2f3185d7ce39af99e2fbac6470ff1227f3720042f116f6733dc87a6f0a6e5b65703d72e997f87dd8126d1469f6eb344de11edfb7c25f35645c0acf0c1bf52fd4e09a3b1725cf44a79a9092031357983e514649e22e092aa252dc52be91098e4fc5742310be83650a4a81f108d9c2b2853a4f6787019ae68c47f9f046c453034efb04d2e527c41febe11c3fc16113d4a7ee87a6ded5c850e3e2154c6e528a3684c6a7c067fb6e237a53901a51c1dc89c4ecabe61279acc4bdf803a6206c3995710787c72462520e092312ca11978e213441b0b63e4e085631102cddfe368a85073e702181f4185df956db3dd8cbf1ed165ea727b48af87b017d12a2d35c8a73909a85f0ef89bbf56300b4bab2c43915fe38c1be2f476da46ee2b3e5e38ca76fdacc452d73e6bbafa9acd2fc3fc582c602356309ed6e2cff8f5547d83585cb352c751320959ad30c9e68a5c136a14ca79d59203480a93d6d0fd3ac9ef08cfad2eb6a9fbea599f33e1cec7494630ecc65094a96e46c69912f82d3f5db53c593a307f9ef2fc3c54ae2c7e344b86d2e6774d1fe974f97b6953a0da23c0c5adafcae24ba5b464711d88b8045d1af283c6c24aea6480bb0801e354d6b726d86a1588f48c7f1d6bdf08a5d489dd7b3a219b4978cd3bc654a0effaf58093438e1941965d30a6058268e414754df51ec90600c7313dae79f71627f4c44e10a6e69a053c27635258e72a93785b1a2a7a082b0a0d430e81f878081c1403e90b12035d53feb7c1a7f5f2a4e4f28a5508932d3ab5ca9659c0e8bdb7f0f511a4ba8ac40aafbd01f955e6b7e157e10eb643ccd9f1adda37232f10d2183d8f985e8a0326f034e4bdbe9443c0279670772a6d0ae4afa75ee0a852def06a68ceb40c42896749d813a7fe85334321f1c866b7bd8af0fcdea68401de51efdc67c4e24449733285b6b3e15ceffd40d2adacd187e3731966f6c412985d2408cd506de469a41933fb6a42bb7bb0ded8c6763a0e17e8c39c8cb711cf8d87aa4831cf15f796105ef37252957fccc44294d51b9c55ff2ac418707b7007bb75b64e91879ac8ffda2c75409ce485b65e02720d511c7342eb8820a71660b14c176850ef1e7495b52847814a8c3d1ce08e2d371553c739bd9cdf37d8f5b9b90c64215aade6ae35bc356b1e90a88af86224cfa911d01021915bde3b32209a31be6c19267287265a99202307331f32bda84cd934dc0ac0e0e607dc601ce31fecba873f2d66ee0fcc266f64b6982da82d35b466ec1231cba558e0525ee530a774ac15e3272a5515d0bd4a4e4f449e9af09c5b8a39292eb29a97f258bb925b1713cc179681e1f7df34db90d4e609fb39843c73282a0f0ec0a4fbde6818e1ed6f1a01fcf98f0d03be5a142693f62a52d9d87aa5e179aa36ccfccfc1e09e88b0bcd12e682713a12478d59185a8e779f86491f6574e58c2b9733638a3bd1942f94ad945dca9d65b99cdf3d47458179451988f24a9988725f4172b51184d37624d5f1afab60d16ccd05bfa32f3df26fbd433ffe5bfe64f7283b12602f189f8e448f5f8b4b8ba7c3ab85637d8650300911e92433a96b65f5c96605e81ad7c83b5aec227ab118b4b8eb889dd8d384e26e644d442d5a03d13a88dd7a23a6857251f11e3b4393e233474f8d5ccb8c660d264c730693e698ceace447cd8b571e6a341d86e9b8c04a0ce97121621e86113340be8627168660f092af8a05bb7e0be3afc49f91d02d4cdf82eb2da8cf42315a706ba1bab8305a546f638ec185dc60b09f0bd292a1d7c030e202a87af485d2c425cc32703a5a422044544b832fac7b452333e4a10790501a622997b71d7ffa929a987e403e034c1ab57b6a247d5dd27b129553b80a8ae433f0dc5777043b9712290367690e953914b35b4cd54f33878a24bf34bff0a3b39bbe463fcfa0412e025d4f295bbfb40fc0af7d284ddd81e94a2585426e1f8618018530a036e40190a25e1cbd57a1017dfa938c2ee2b2462c865217092ef761408a4d27f88ad8e55aaf295b565105b2e4ddb224706f7e56f35557a2a641b19436a9243df26b43fe2b9623c2b2275b7e0f89d9cd0bd49cadd7b3f882b4f03d2c45ce083bc8dcbc27ae3be8f7655ce5b6b3d5b6edea19cc9373c9f4b2c268fc87b351258ac77c13bfad6254407e41a0235780dd6027bfe4f6b39ea6781263f278002803aa9186774f8699b30f683fd93496046845e08faf69dc4459d23d49808328109b08012ec16e488f6c22797c439af8f62c65395d5a651b67905e988bddf3b9c7210f8c49a2b8e02d1e66214043e4db017a784f8c3b80b0a4900330f0fcc0f303cf0f3c3ff0fc009d37f1db165bdf851b8a48410aded15eab1172b9bb52ee8a283fa1d4f9398a2bb6d90128d57f72220852087b08e308e42e057a7d0ce1b323f7b0b228d047b626ce62a4d6ca4ef07c6f55c592a7308112a4be2a7fe07f3945042885be97538c17429614bab9e7cb614cf2c3a021c028d4930d923ea51c4cc649124014ca6cea9bd9345be953040284e2d85257b6c5982a03c531b9519773dafbfd4431ebcc278f8dcfb33df1860e374dd071dff23b81642cbf9917bb0927fced0f1e778ee3678537f19588888795262c8fd385943ac4904c2469abf294c7937248004ce8527a63d9a74be881997c8e9a8e2e87b825b47f9992d71829c7bc7640804a28d962e374982f22e54f09dd64c6a3d8cce9fbfa49e8e7c169ac205f1e462f09ad37cf6fe48fc3d0af4742bdbf701792e698bfe290d06388581a3b64479df923744b132c3cc8f0b03eda118a784964eca5f0f1e64668761e6c4472e8119b19a175249ac34c591ef2e54568ad215e3a8fc2a6d4b122d4ec91ed04cba61df92311fa7bdc48928cee583f10a1f9e5cf5b7b1c42abc94147cafd61b777184289de101f67f140234721749f6dcb3928cd732521948f1d4a6be642d8e00e428bec16ee71201f245710ea874f6d1ee94b749440e841238dcfe6c97ce400428f73fcf114cbc2c7fd1ff438bcc8f172dcf2713fa8615b2ede2b8773721fd4088f4ddcfb7fefe583d661436ee2e498fd1e1493b0cbd159f0fc307a503f24c9610e32824d300f6a5cc7d8f77eefcee141b7fc2083db77c79a3207cf299d54e6b083962b928cb975d03e74c2cdf774d0dae34d737acf41e9986b67c2b3859095833aed63c6416bedeaceca120e9ace5575dc298dc5fc1b947c8931793c8faf73833a9721e11f498e63db062549de10a32a987e1836681d75ac7bb1f6624e59831e2b7418a77a54c37eff6ddf9d2d69d0357e1883062597777c36e21625953368bb1d427df0e82888c40c6af230d5c6f86b4e1d298362c1e68265cc296b44c8a0febd650c5512fee36f0cca475797993e62502ae2858860f93cae5c1814b3ce8a7ba12fde3a30283985ca94dfb9dde15f5037c5d4445586e4c70b4a58e41897e93a73982e28939f3e4e7e883b122ea8b5614ae38e580e21b605a52fcc37a7c8fbf8332d68d1f144d69cc4e3b46741b7b05da31327e9c5b1a0c7978376ebafa067fe584c3ffb16b35650f3648f92eea70a6ad7e5eeac1c77bcc9a282b27d16dd79642a2635053de838ffa39c4b41f9d75021ef32de76444193caf83c613c2ccf88005050dc3cc7c977a32657f40435a6f841db84f9a033ef042d6ff4c7933c9c14c16f82126ef29f7bfdc6993c13b48b1d570a1b2c654ff712f4b0638e6cb7c30ef7d14ad03c27f7437c303a6f2741cb293f44fc2b56e73412f48ccffe1ef51114bbf459825988d79a8da048867b7c6998751c5c0425878eef74af34c23f4450339c0711e6cb27da33043507312c9e5484a04eeab03cff658e1cc60441ebd1fc8d1cf9332706088a58107f8f1ce5f82ef303cd2a7d0a1fcf91fb9f3ed0e3ff7d5897c3cb3fd9035d2f3cba147310eec7e481e29e65b2e538c16ecb1da85bef9283bf0fa77bd481321f6eb80fd976e30673a066674d9f3598c61c421c6816938755f92c3750a643ae904b426f8cc4069abf8b97c649b2572135503cacb98fc1937e3cd3408be0b9e307ede0367c33d0ae63bbb3fc09d5efc940e9d82749e5cac540dd9171cb419b7e9c33186833b95bcaecc267cc5ea066ca614498cb5ca0e7c79c3ecf9b660a590b348d94b5dcc345c84fb1408fd35d7fb1422f44b5025df204b7f01c22cc47055a443ad77133057a143ffc9c72d8b1d204054a3a4b9df45b3a817259fe121f97560a014ca048f628f7cc7c29f4f84dcbf3760a9dcc9342ed10c353c814f3a4f247a164cc0be1adf3c409be28f4f81cf5e638ea9c3bca87427bcb21119252dcf31e149aec5f5ca838313dfb27143f0f5305f9db938f7ba2eb4fe69e49f34e28fd22525693a33fc939a1a5907b6c2648c56b37a1586ffeb8ab3c8ea7d39a507298b97fcc3246329d09dd62a6ea38c95bcab331a1d969fd5c4a22b9cbbe841e79dae3e794b6845a93ea23b2b7c448e94a68231fb85f7f74e8784ae85af216bd3908fdf324d46a19f31453ed1d47126acef371a7cbf0b09b4828319e5e7fac2c966218128a47475dfe0829d5c88f50538a1191e3287684fe1d74e6bc9177b23149184023d4f0f142e48eb3e7790e46e89fa3bdaaf94cd59b6311daa40f5184ee1e3e88f25c35251d89d0a3c3cb7e9d909b7b44687a11d2b9450e1dee875053feac2ecd8fdc36841ea3e3380cbbecac6c2174ff8be92a570841b20e4673c2759cda18792418c020b44811a4e6b3c35cdf0a420f1ae275fe1c3bc7b10d84ee17234b5e0c104acc2392baec7b63b67f502fc78cf191586f0aad1fb4dc61868b74b9d2fb7dd07fd3659bdfecd29b0f6af21399349aeed379f6a0e58c1c1159f72fcba307fdbf52ef1de02b28608515be81f5c0154e634615069007fd33eb6c0e6e63681f0f6a8a3539538c5d13fa1dd4f938ee92143b14f72aa2bccc3aa89fc6c30f31c8e74e07c5a54d3e86938e08e7a05874eaf6dcafe315392871c9827e6e09edc5412f8dbf4f2962e0a094eed9fd7fe60d8a9d7d1c72241d9d9d71839e637c701ed4ef843cdba08c7ece90ddd9a0b5f9051d0d1a2376ac41c9091e63e5ec24dfa8410bb7cbf350962387641ab4ca7e1d9b64b8ec52a2418f29db30d9986c8f33a8adb9643cbda6e90e33287555d333f162a6b70c7a902badd89a227c2c9241d3f1b91c48fa3898188e418d937ea272da641f2e06657a6e436bacec110f831a27a9a39bafdeb2c1a006ad5012f9a37c418f19f5e1aaa47275142fe8619274937260e9822e35e3dd953243f0e082221de4ef460726716e419b0f9aef03330f9942b4a04b78c8e778b9247b240b9a7f78bbc8cedb88112ce839d6c7d56196f0b85f41afb7915cee3ebf492ba8d51dfede834408c9ab8276afe17118a7a3821e79ec335365b1b27400087b46cc09396cf1a0d885bbef587b26d6e70ecae40a9fb9976387b3071e772c733975d0c390c47d934c740a1b3aa8992f49bbf226546ce6a0799033eb5f7f96ec18396832a3993f0eaf7ca6898336edf91d7f7edc183470d0f287b1fb2ce68d7ee60d7ac7f156dca499b3c68c1bb41c3aff7ce4b78b946983e6b14ee7dcd6cdc7271b34fd491b9f63cbb9f15c83f22192b9af063d68aeef509ae3fd9306ad76fee2e38986cda041cfc86163ce4e4ee133a83bbbf17ae6c13d2ccda04739baca9037587c1c65d022777e73f0931d4b06f53555e49f8e9b9033062d430c1a530ede6f226250b672de3096f62a2c0c831ee587ab583d18f4ce49a2c6e52fe8712c2307752ef93cbca0cb57aa1f778f62ee827e96ce7d3254ee18c385ff23475d0e62d98292cd4ab77b2d282397453e9a2a692d0b6a7e961cf78c7f5863418de41a591f84e80c952ba81d467d7b9a98a9e3b0821e7df67bedbfcddd902a28769eb3dbce5450d368866ecf1474114fa571a6f1d12805fdafb296a410bc668b82e27af399a3da628482fe13442ba6cd1e5f754fd0f29df869db4ed0aaa733d7b84dd03f967ed68d35594163821a2f45e87db6042526cd61a86fd9d25f097ac60d19ebb257ef4950b365a48fa3d1a63112f4b090e9720c8bce1d47d03aca77c182e6b0dc184193cbe14bc2c383c78aa05458d2b81f39bb554504dd629bd4e8875d1f3c04cdb7267bd8a937bf87109438d97218723899f31b04ad3f48c71c83f4a40c10348fde2d798a0beeff404d97826fb28d3925892841850fd49c7c92c751ca17808a1e281d3b2b62c6bcf1639a0a1ee8a29de7c2766ec95d1440c50ef428428958786db18feb40c99e420a299c470ed4daecb15308c173ac1f1c68f29303cf4c1b4a77bc811e6cb2f2890add71b2d840dd0e4278f990b3327f35d0e3f7ce7e921f0f3ba381a9b28be494f29a815e1582e5780e19e839471b43ce21933d4954c4404979c282edf9a4ae910a18e891d959788ff30bb4fd0f3b22696bcafd7081ae2f9d36d85dc70c1a11a86881eed661aa8ee1ae997311c82050c10225c33d5bfee920d2843860c67a208b4650d18647be0a94fe1c69f8e7891cc44f813a9be793d2e9285077729c21538ae092f509b48f392e33420e2a4ca065ac1c9232c5afcd4f29b49ebc7176b72e63e89042c9f9c07c735f8c51e328f4f9ce8b99781185dae1c297ff68eef886428f37a67693289f9825a0d042f674cf7f4e5d26f9849a7a7f5b37e27e4bcc135a4a7b497eda3afcb813cad855c4e4164ee8711ce8e77d894b4cd526f4dc9d3d85ceb429e4506371c51607a92a28a080edc3149ad025dcc7fac14765e3716442b164d79b6e66628612134af828ecd684e023235e42fd8d78d55fb7655ab184b9e3cbf0521d570925d38664f9ef93b92c4ae88197d7e7e578337610bb05126b8a49689a64420ac9c364610a4930415c643a0abe158cb2c21491d023cfee91cc166cb22d85292081b8040fdbb1e765adb0c20c533c62b3ce20e952e8949918a6708412fca2f2495c74e4762394b0f96827dd5f8c876c610a46287de12c76841db91cc1c2148bd042f2fd481e7936870b3a610a45a81d836b6c37c9369292401917a64884362923270d9b909803a74084ee5315f6a310de634e1f421dd9ae8d55f13184b0017a01a784290ca1d7ff867e9b6fce4c075846030aa1bf95dddc79ae08da22610a42e8398e1bd3eeb139c5384696d79061812a4c310825f48465bbed18d2dc832904a1be077117e73ab2d4db1481d026e43013ba3baad23c20f43059ac0fb276d8b1fc3f68f31b92e4dde72095f7c36e9bdfe3f6141f5d1fb44bf793df47bc3decf9a0a58be05a7aa797ec3de8f1c6f2384b7319173da8e3b12f71ef764bf33c28d9a763918f82db68f0a0f9ffe56c9a0facbc8362e1357764231321d57650a68375e8ce1336e75c073dcc93b556ba4df2d3410fcaf773d0cf41b7105a23ce7dc841c9914c3a6cf2888356dd91e48d9b462b0f073de43cb6b88f52d6b66fd0e38c8777a4d210b11b14a9daca410c164bbad306edc7afe330da7eee830d7ad2d97c1539e472cb1ab4cbe9e3348ce791941ad42099dce3b839fc964c1af43890589315ae49643468164268b967495ce70c8a77746962647eb74c9a4193b19cff3963192a97fc3c53fa162964d06adc237b596f759a8c412b998ecf83fcb0ab11cc90c130662c8d1018a08a03949d0a4c2106dd4367491894c8a9dc3f8610c3851030a87716326ce43c61f25fd0ee52ee93ae8fc5c3bda09d4ff0ba710f66c3d70535acc6c3e01fef7e94e382ba31684587d53ef1b2052d82c69c1fa674511ed1829e5376bec9b0ed1a5916342d97ec8ee72d751616b41293b3f073791ed515f4d85a327496ca57af15b498105f3ec6ba90db29aaa06674c4f0b2d0de310515d4dbadb3943ba4f898827e169f24871c2928152e678ec542ae1c4741bdcaf2e17cc2ddc51c0a8458d5fd6f162e6a2bac3810101a3566dc144fd0724cd98255ecb9c9ec144e50ece378078d0a911f374513d4dc51ce81f7cd0435e7af3012638cd4f64bd0fe3d4c915c32e69f5e097aacc12c2ceb7d601d4f829234beb3b674cc573990a09d8699f3943c47d0c3e90afdd7d5b12766043dea482ec7129a27f8550435ee8ee59cd3a789e927829e66376d0e9a0f410fb635cbc22746c7b910d48a3936351b2cfcde20286133ef548ae7d39203042dc2277898c1723cd6fc40b9fc1c3fbee303c52be7f37d799c5d323dd043f8be0a9515c22bc803e5a7fb2342f877a0c64d6119924407fac576201f41fb6ab61ca8c1d2c454a96338d062a8b3cd331fdf4175033f4d7bf06c711b28b962c347fd576796b6985103fdc3a0394ee7c647e9a781e29174e6388e5f8578cc4071db8e4b7af771ca1d32507c3c8e426bfa0c6b37068abc747fb88a57995b18a85d239d7f52e7cafbbc4029bbc93cf99126d2c7055ad89cc3d0ff713375d8022d6c9e34b1310bf4cff3d69014243b5b56a0dc87fcdc1f6e0e3e8ea840bd9842c81f6c8c4b2437bb107a1ddb3cce02c6d2d819a70b1b3750ec2cb8e48e5c9f1dd2840d1b28356137c791bb5b75aa140b6cd440778bf4e1443bc5e44e03a52dcf42f3ec32934502aa38c02fa0062f8d1a22d019281f7d0af3f02ff9cfde81ad616586810d19a893d2e68a97f925586b8515a49eb011032df7bb555b0c1ac7ba23840d18682e1fc7bdd7adcd92737eb0f1023da549c9d3c6da05ba2673cbd14b4f32dd375aa0e64f8a7b5a17e3741c1b2c506fb2bc34f4327ba7ac203754a05ba6d84bdf6da440f138d8503937967c4ef1c1060ab4cfda4147d6b2192727503cf0e8f4bc4c6257b861023ddc194b3796a23f8ec9e82db42885963284f70d5691d15b68410a3d7de6a0c62b88a6df46a1cc548cd80d1dc58c31a250a77c82c57b9020b8088592f4da43dad8cbea4181071e4c3393a4cc27d48ae329e6d6d1ce1b3e3e70e109a5cf22e73b784e1d84b1135a7930a39d6386062e38a14476cd619fe12246fa022e36a19827e9d017733f638e035c6842f9388248e5109d6de12213ea848a7d396797e61c1723eb7cc005263489393ac9e1227f9c182c63865f075c5c420ff2d2d127860b4be81d749862547490e0f118593268cc380198c0452594eae02b5cbe860c1a309ccef502300a179428748c9f2117236b125a248fdfe5f48d91d559982cb4b1d842468c09586185b100e4c18524d418bec285093da92c1e60851559c8b8020406132e22c15b852c1e3234064f8cac04800b5c404209d963ccd1c590496d3d429dca57926d1adc4e7384966e3e2cb9251ba1bee44f931d6c8ee3f124b86084f2a195e3ebc9c7615e333c402306125c2c423d9dd38f7b2f7edb5302178ad03b48ea8a103fce92424770910825e730eafdc3903ad68f0825a74a23547bb974fc105a6faad2fa1cc4c896c3105a78c7e1e5e52869cb5b085d3cec28aaa2bfa3ec1042cbf795c1e384e7704206a1e6c8987638d1b12f8250e46aea7c2c24f4e402a15bc5bdfb6dbd1cdd00a1c7113a4fc749fd41f338e4d6b80ab9f0835d3105495261c44d18745550400165072efaa0e60df5b193874e9cebc71e562ef8a08b7b9cf0399cd2398d175cec41ffd8f5f5ebf42db8d083b2f9e38f2276737ea49a868c196e85155658e19c85ffcbc80d64714507b6861651709107c5b3b844108de11f3bf0604edd21894564b250562176bcf9ac24597234292ab8b883b527c17393e65d02a54c035cd841fff0f41d74ec75d03efac81e72f0f427d3418f390779b3a6944f2e3c07bdbfa2e3a426e496e5a064cf77992a5778a71c71d092b4c7317dacf7f072e0a05d9ab3cf0c1d6ef56fd02b4d8e21dfe4b0501b3728c9a3a8cb97d1147a6c83121d59b8e810a17bb341bdbefff899943568952e4dbaf14f0d7a5c0eda2eb44b839e9bf947633a4cc70b0d7af8f68fa53d2e668f50e0e20cba4488c57790262ff366d0638f935f6ef22883661dba9134c7fce4c9a0644b92fd2d26899ccb31a8614288c60d1f42ca88082ec4a0496859e630464dcf260c7ae530a7f2b94da58f8041bd90354d848a1ff4f3052df64be5728db0a84a8b113803bca07f2c4152ce7b0e229e5d503a44d01cfe8470414d193331f967cfde395bd0f368c7cf39885a507232a6fabe8f83f0962ca81fe52876b618c3829ac962eadb6c93e962727105357c1cf34fcbaa810b2be829795021c6182a5ea6aaa0b5a68d8f7278d91d3315d49aeec8cfa3dfb0f04e41b1898eb7a17db6f1430aca07a691837cc9814b15054dab4a4fdfd307e51514b409314c9d841ca783a08b27a493ecac3747942403174ed0ba43e463ee8e4c327603174dd02e86089b1d739894364c5073ca9d5899f662db25860146408302c5c512d4d78e72f85fa139060426a346160b38800b25e8e16f3c0ff2459090641749d03afc6053a810211d752e9040e8e0976fa7c443fc1ce133cf0d29e2fbc731c21f59daa0b5313b2f528443eae039e9d8748c032e88a0079176f38554c12a3443d0fba522b27558c747c1800b21a8399e88201fdf88e5254626400255181a3566b80882f72974180be54132461604ac68d498010617405073270d97d8ec29f527015658f102173f50477345b02b8b79fefb40c9c164ee68b64567d23dd023a78bfdd32e3b8fc203bd2c5bf83889f8e796ec408be1eff52f64e37cd381fe7d1def6ce898a34be5408f314f0e1347ccd24f2e70a0042b999ccca1227b72710335dd67c95196dfeb8c5cd840cbf92e480e399fb9af33b8a8819a2ed7475772d1b4330d74f933af7029730c7321838b19e895a93a43f853066ad544e87c799c65bf1659c8701103f52d69c6904f18e851ce7d7b6eb9edb17f81d6e935a7f6c6bb7217e89793c7e1e3c06252b805ca853fabefd11cffe72cd083dd98d7ce1f36db5e81dab1e57479d2c879b40af4dc91cc1f6264b8f9488196339783cde1a4ebb845817616e2f79d6e04c9cd0994f3fdb83c0c29e40e441726d0c4837ce8b842e4799452e8521d3df1f3c5ae9f9042afc9b755216ce89473a3d033e5ac113ff09034178526396da4fbb18aae2b14ca84c971d68b07963b99d508dcd0800a50a87bd65982666eb27e679c28a8f88416d95298799a98379823d819ef129041812a0e50451507a8a2c61632dc035750e00a095471802a1640c341f080112c04acb062043bc31c800118016801159e505c333c3a0eff9ae33ba1a6c957625ba125cb09f53c8ca49de3f9307d13ca96c741900ae7671d9ad0c3d0b124cde513f44c28269a3b8e733c64b498d0e3c955a5b97284652ea1b46b87a12ff7c5182da147b289fd1072a62a95d0db4c4224e4889794d0fd5af42f773f092de56d8753d54b4273d1918deb10337724d478f183f05a43420f3a104b12e3accde3115a6e9bffb0dfbce774843a379ae2e328c4d9c41ba1c6da4f211273c8719c33420de221c7981c8bd0c4c33ac73d92cdb28aa87d62878f4ad389d073945354d2d051efd988d034d24d7786fa10da74b09cdbde3463ce86d0e32cd6d53dd956321742ed1c861cc7ca39303b99106a46fb5732b7f214f220348f214d77acada12a2c08b53da7f871be7ddf8e03a1fec7f1f886e994cb3f00a1841852b887f9077dc7836c19394b58c8e10725242387dc2e1f65bf0fda957bbcf8b75da1f34191e938a40e0939c7f21ed40d99c1620a8f6ad6833ab6399b3fcce641036c50e3ad620e36749063cdb106edf23fd984c8a841cb6539436a2999918e3468b1a3bcd339b7d170d1a04e8e983777d835b13e67d02e879f634bffb1a61ecda0c7311d379285cba3974133c9b127276bc954173228fb2d27192cb70721640cda9ffe944b8e03eb1431683f9511cc3e8e15338b91056304346a54991761d03cc7abe2e4b8a2068d9d718ca1e135ae20087801062b4cdd95a40f5bbbe383175f28b74d55a418fdbc50bc840f162bac3010240b5e74e114d2c179ac1bd5f78c175c282bb078b105a5cb728e3672723fb6a802020da8e2dc025e684149b3b1b4efb3a394c22fb2a0c5c47def4cd2f81e3105bcc08296ba224c7e4c9e67f315f4f87dc2e51c5faca00751fd7576e122a7e94515348dd7f039de4d9e568efcf0820a6abc6d499983e5cf106721e316c0861753d052c87a793c5674b5468e03300083042fa4a0f48ddb565b7647efc7c8926180f0220a5afb07215b75a59c81a18507bcc615300829c30b28e83732da71e016d993f2044d3c38bd8f5ce9bb6a2728369dee3ace79d104adc39cf5cb42c4ed6d63a4d39041b00d2f98a0c8dcb487dd162ba5548c2c2cb05882de39112c66b6a4c7e32bda69cc805103c6d25de1851234bf54b9cf423cba0a99c28b24e861ee91b2af7c5f3928bc4082ae1542b22fb3d2cf5d8c2c193560b4db11d48f7119d9b7f3df3e62649911f4f8dbe96cbd90972ea7085a078b0ff1df8c2d64d0d80012418f25527820bd4172e55c1f5e0c41ff8c1f6e2ec4974b9b1c5e08418fe925c2479e7214a382c38b20687921058d0f7285d8738c1740d06296ce1c22ccb3a79418590e83ee092f7ea09f7847b3236217b28a91d530cc0b1f68915fef2534cd876c01a3d769c44826bce8412985d2defc93a9f2173c50629945a488ff61b9037dfbb72793574eefbe84173ad0d398472958fa0f3b87bcc881f2e12e4688a1d5190c1a768417385037898cc88f17236bc61637d03d9c5c235fadc92a33c20b1b1c62f6d81a72f72f034607f88a0e70e1450dd4d87f159ffff4d3c531b268a05e6859301e043360d4e0cdc2cd8b192831bbfbf9c52aed281223eb9d060cf7ce1732d0637f4fc5c83a1fbc88811a3c937ef8b72aef28465a0f5ec0406bcf54312b77746fef0bf4e8a2426cd37c9573bce1850bb4cec13e7cd83acb1cde02753c9e8e1f2652b858b240cfbe5149c772c386172bd07398cdd1a754ee529f33bc50819a2b6d8a2186e41e39f6450afaf0308c664a39670c2f50a0f6792b861727503b4c6e2529598e37e8a55f9840ab8e3f5bba484123e6520afd3a9acf31f8f869b731b2e83648a1849d4fe8927c597251d818853a3dc17e24dda5f05144a167cafc7a971de55c8d2d64c0a0e1357e8b8542eb90aeb0677559221bc20628f4703264d987e162cffd84123464e4c0224dfc6b6c21c3135ada4996a1cc62703ba14ce54ee7e870e2f8727798d984feb675d765d18626f48edfdb49026123137a1cc7b1d961e75964b13118866b21030b3e1e6c604297e0fe1acbcc363ec303346250e560e3127a69e7984ddb13f3c672c386253469110b7692fd49ef5ac3462574fba06c424c4a681ddbb90726993dcd3f093d54082316cb2209ad83058d182d9272a64442cf71546f5775920a1324b4309da31cf9f8f38a31f908edb7f6f2d3dc44a4080c1b8e50ec7d7259cacef0008d1884371aa1c7c82c69262b7b87b0163618a14bfb4ffa30f79c6256586c2c8214a126d9130f53b66264553dc06400c6b091083da2e2e4f9d05107bf09c30622b4f20fbbcc3fd08e99372f6c1c420f2c67df449e0dfa1a2a6c1842a973bb89fc7bfa11f21b85502ccd8657bd46ccb86b6083109a04bbf0be5caef72931b288051b83383afe8f9a3146560896060cf72f26083dc78efb7378399e63baaf0884f651dd9e47c390d1570908ed8330c94fcaa3bbf730386cfc41cb1d75ee2197b561c30f9a664b71f2644bbb6f7d503bcba760c1272bae830fda68c7cfb817db534ddeb0b1072564cc6f3162c7b11f3de8b9bbef83858dc7b66cd8c8836ee1c30e997f3baac278d03c850e6c63f06a8ff433868d3be851d8db87d09a1836ecb047a1b3e9818d3a289eb43a864e1132a4f7f4061db4cc31ee76d25ba6dde7a089471d5eb622a4d851725047b4d35d661b9790e2a0a58fb3a2e3f71c744a9d0d3868697298b27de7be3cff0d4a0739a44e31173243bbe1063d4899f37e94cafe2a67a30dea46de68ab8fad4def0d3668fb12f244922a461e1b6bd0624a1d527e25fbce220236d4a0e7c8c6364f9ee9cbca0e36d2a085a4f92ebbfe25458e18595589063dbe8ee3871839ecdde60e1b6750225c08e7518fa40ac90ccb88fd070c2c83962c927cd899d35c4d06359cf9758e236f099f31a821c227a49c52440c9aa7fa8a9c8441bfb7147396fc82414d9ac29ca6ccad243261e30b5a4dceb8193ce668aff782dee6162c7fa0e9829a722c1562cac8b39d33b0c105e57348c983fe84d4db5b50729c23fe5b2eefc77616be818531638b2cd6b16002820d2d282e2e9badee80165b0386f3623183686c6441eb38700f724bbebfc6039b058c2c2ca8d1a1a9efc7e7ab3431b2647495161b5750672b87b85397e1fd8221a3cf8c0d2b681d77211f87b3c9623e461616ce592c63160638820258c44615b4b3cf8cb6cc410e9702b1410543978792381e75141bc3116c4c410f8348a7ae1ce4cd392f3f6c48410b1f272abe6be2525f636bd860230a7a59a52ca29993bf075571002b4ac1283a6c4041cbded4e52e2968a7b49031630434b6d0629d460d226c3c41cd1da57517233c923846169913144979af3a0af31c6f3246160def000d192180d104bdf3ce6d82ec7c842ca9246c30414f9bf3847b98e9ceaa185923582c1a0b19bc812caee8800c19078c1835ae08010c2df022b1b184ef2288c771fa38691d23ab03326460d15798a96143098a7b124dd133731d45091b49d0c3858fe27ef889910563091b48d0376648861c58caf9b278c00162d4b8e2025a60818501aa38401516a8e200551ca00a25545e368ea005e9f062e3ec56e6da086a876579938bcf39b54550642e7af272bc134e4304ddff3ed9c67948f00c417dbf37ef16afd8b208418fde0edf8e47bfab725ec0043682a07b5c221d62aa81a06f7c4dffe1f74f628e1f28c972360d7ee75f2dfa408f3c11b3737f3479c41ee84166bc8e15471ee895bde7395968db1c3bd02572969c3e88e840cfb5115e95ae45e38d1c289bfdfab4039b1c73e4093670108886a874ae6402d2581c0e8783e170180c0629b32e00e31408104080b0341409c5b2280ce4ed0314000447281e382422161c22160e18101218080804c260604014088502815028180a8445e1714024287e2e4410114be70b347121a8d17b55692ca54a395206effcd8a78f56bf4da49d383846a0abc2c2da1662054a2cf91ec8df5bd4ff716d4663b2093c52680fc080dd621f5814c117a61c2c5edcaf263bfd844be25fbc988009570d0c36dcddb95a0c99375ed6262c153edd349c045ef7c1d692108d73bd7bb4cc5980a4d22131e704bc77e60701280ade49750c0f94017de7afe251012196e83d42ab2fc9ce18888db86e57c53040739e3a02c6cc590488c9a8a3ee86c02193dd87b5b2ec7f1191ca702089a79d2acb09b8551095ffb70439823a05f30e64cf08be9b384766eceb2173252013c3283112e2aaf8fd014b7ce29d77067e419304885a0a15bfad18a6b55a1d02ad0becb05592f62ef15a20c2a721841dd905d2bae195ab6e6116960e84c0df3e68e484ef7100bd117cafecb368e8ffa64c742a197d217150821457419352f49132eb5d80e1be28942b010af719419820ed841b958538bdea243f6484631b3e6ecf9562a9283fd22b2dc9fe7eaab4e90294019027ac577edaa1f8484ad9d1ac08b4d7c4fb5d630e8dea580ef3f5a56307b5516513ff5e58d717972063ee348a7440f2595c68aba30164e9380d7929e48a3ae0ab4aee0332f2185d20609c35dcd9340693ed5e3153866303e0b0f4f2c0f5770ed2892ab6b6c6f5b77fc7cd47a46a4a45d56b5197c906089ca8d58e959d017b1a95caa69fd3279dc1bf89752deeb0e1beba3950a63622b0971237c917ec4dd439c78db414cece333a1ce1424eed2f7b79320ce3a71da30b295c20e623b11d109c45e0086aaa4fb0b62fb648b4cc4c801085378406c96f8a067d9e3397210ada4ba6e8389785188440581a4f5506d37a8d18782f86db1f542f98bbcfbf1ecc48f4404f7ed469030848c7290fb10b704af54e42fd640f3a20962386fcf359f6bb3698941ed6923853cd8da29b21d269fb4a4a777875be02a4a723d9ac08b5e76d7d0ca85cfc8bcce8b9e19953daef3c1ac9cb8defcfb2647a6a22e34121c799231fdc37ba1c3b34db9ecf0858f3451f18d1301ebd0f33df5e6e02959938e2a0a9b98c671bd3a4b8c834b5261d5c0ee7e1747c64b6f30324f2acb8fc8266bfc0a481e118f3e421e51f3c5a24b53a721451222650d3963c3f38579162ff9e86830005870191e492626078f455f3119d4e9beece502824cb4b0c40c55b35fc89d1b69888096ba695f7ffdecec363ebb3a8c5a6ae9a02c44830d695dc191181c19ae384d4710c90f7e2f40a68aaadeb6a97350388251dbb04250276f9104b757f59ad58511c91aafeff4fb014008a6f07e0f6a6aa6596e449a793efd8c7a5ea7e20aa5794156afae5b808692dc42d704b56fb384137c64977379a76e269431089307cbb437d7675f4b66c076ab371129a2e7b8ec28e36386c4d58cf831c8475b4e0c4751551d166691806d277dea189d21921a110ce8e901167bf747762ad79d3324acd1889c0a19b3d73da1ddacc917a6ec082c9c730a34c370ad5a29ea431acf08c9fe531c47cb284df29118878a8172703d270921dc4095e6196784296638dc8f6c5a5e7a08d9ce38625320204fa23e0b50e801ec0132006033318e602c94a5dbe271a17522c6b8d16851edebef086a813f3100909fe479441ea8f84ba9125bd565367cb449b5499d347c01d5e6583061013d04b83182caa49feeb1390c2d29d23db8ac746df45636003c56f295e107338308080217438621092c375b0e0662a7dad3c1c19acb42ba8d886b8a39a224f51ad020d7179e9982d6187a4daa0628b9f67cd1973bd5b513bbf18401abf7abf1c9a16615676178ef515f77bb01c5635409ac02915e492e397b039778caf3f9464d45de5e2a0954dbff1c75b0eab86be97bedb5a3f44a0cbce8a04b0ef736af092c1bfef425a55f0f68e05d87fc084bc813c3b988c38993627d7ef0a31e585ff6cf4af8547622ed87f2b27e0cfd81bbd33a640514c65f73f6cb5be8b00f9b7dd286ff10486d541e26891acf2f165197d569d3bb41e561e9b86453ce5d5cccb5859c2088c3e9991856c3cc33469777e95dd57bf18b4e1693a5a824049149a6488bf867624f7d351d4b159270eb2e4d835c8e1071f4ad237b627a5c15c0f045d9e06fb776eb3cece63174b06a3298cf4f1deffc7d4ca4929fdc791376f036cc23e8a251e0e7ab9c39cc4673eb9be09c1d6ccec7a3e322993926cce72985df8ea3e00ef67449e1831ca41dc6068607294db503c578ff3ab369d884f7b667b3cc0d38f213608d7f2ac2be73cfaf6680f4a4ea881152523eb9def5fb66034b505b892d2d0dfa7e18dbc60c1f1aab420154d60011021ff7fc0cd30e36d48ba9be0448e98dbc47a837fcbf7d94cde68c987f07fabb5480afd23c9ad1a15ee8e0d2d68d358eeb4ccdfc2f5cac8963c15e76e77190988484991339d221c978d587c856136d1260e9a26829d9473056b7e6bdf8d52e391202f495d7dcbffaa495ad0898ed23155a1e21309c4bbf46e0658f840e0ab898336be263a92ec82d681931598f4850130305b84f5d7023994cf6d8004be35194a817e69e62b80ff9a51b2b61ab79ffa5ca092cc14c88ff401fd228179cb5fa1e1c218d5c09af1953b16bc17a14a7eb84d55ed59bdb680b847422111aa21859db7fff960e9539a3a6d48a3568ad313ee3726d10af65680e1575a1fca2efb9c26e58b000122512572cbfc211efb65a4bd35d9526866d74a9131bab92ef6b2c50d39f7219999dfb72f6dbc8bafb51d28d65f7129990d4f00777e405658e1d7685709bfad5b07edb3e5b68aad89d4ae370ca0a3a9dab8c7fcd86d39cf907f076b56fd9886a37039265e2da0d71895a9f17e43cd1dc017b32a56078d98d6640d7487a5c19800b812300959861fed99e858e531c23e229115d1d3a99e0803399026872ad5d28a3b1eed4b8613bd17b40b6d412a8e3889359e60a9acdb396252a7a2a7959170a31c5cb44be179de9b9deab9de64e3034a3f06fd178364060ec66c125b4ebcddeb630bdeb5e8bba4c4510809ea07b461aedf5ab5971fda46a0ba8b2099aef401e8fff0b9df91b9c15b4d983b5cc7ca61e45698c4b64f5c391a55ae4586d49ad579314a622c46d09022c29661d9fc37e592f242f2d69764e1040e22c9340af1c5897a0ad053c22d127bfed7aecbfcaf518d4bac4e4d66991523b5f0bd1ad7ea581ef02c6ca327c9b288bc04cd55a7a0711b060b35cbc21af5552caa3b1c5b3cc8d0daf02cb082e2156eb296b40b4ac1994f619205031914e96bab41005279a6d0190e56b800b5d75d8422f878d63ad98a321b9eba8d1ccfb410d06281b0edf42f5d35903304ed1a4d2014116516433c5891c3a5c8a0baf803319089e63742698865f997e49cfb548cc0701cfc908ecfb48ddd466473dedc8c85cbf4c76ff4c89999accc70376e66df37e296611e664287f9b55656a5194377874219dee61cbf757476716737239a99a1a79d7b9121a2cabe72636ca558cde1101af4c6b629d54110a1b0294714e12b1681975ed96e401fa8d25e7a311e00971d3eebb20e5c27debbec469a6b7cd53ba110c3be0a744e5d7bfc1c6624fb871cd3c7cf49aca0cfd84bdb58c466a6dd6f241dc766308fb41d96b289aab689fd2be3b8188717acfbfb511bf4a7be53eff9e3e9c230c38d27d22c4a52e8ecc483823e897ac1e33dbec016a136152aca44a15c8a9aebb233fd81e137fc5dcf8374124177646b5b325feb0f91f8fe881e23c08f6ea543762e7eeb7e6155a589eeed3771d0969769078025cca6e4594705e11ce0b24fb4821fac5257204dfe297e1b84fd82eb910f9ec5152253d266af110b4ec44924547a6181f8a8e3af8be3b61eda8b8ebd5a4dbc344f3e3eab283eac3faa349ff925a61479dcfa9b8aca2dfa4059ddc2a05529a503a7ca47240981bc784697e9380ce317890d2afc771f54bd4119c8495c5e0bc4eb8d03403f5a9e50a5b2225aa579fcde9c75c5dcde1b824d042d7d703078eb6638195abe801a2c765b099eea38a3fe636e018d28052cb75b314ee2a0a7cf584040c3dff72f17dd0e09dd59185342fe86f0fcfd60f50f0781b9f522c1b37bd52dd968e809e6e6a9103ef7ff140df37666d450d5ad5adc0e297756042c0144a4c890af52fd81e2737083f33430c99adb5e500abe9e671812c4c7e393eba9ba21e5c7c82a39b3350482fd9ca646d285750305a8cbffbc1310059e8d628f50f3874ae2aa4ed286218cf7242ce18d3b0736d485d895ba257537571de20e6780fb41f081f10132f5b4c7fedca37e561c1f5309df9753ef90869ff3f23edcaad7d6fd11bae6a94aba051b39a6dbf8c9b74b6830aa89d441fc629257aa84ca78be4c941e52715bcd20398d0219468cf724bcc0bc1b4ca85ac7cf8266a85152bb433f046a415076637fe57838d05da5691ac8d53b80b831382d4af6ce3d320be65eaf38095882930084c04200161c0d4804f01668e40ad6720f11687206dd5fe4f6cd929ff0dbb5b180eb87ede37fb4057c46ab5b566280027dc954d5e846b7eefc8e409ef4c4cbfa58d4691c0ff933c2dd2b5c87dacdde2acbd6341946bb50d509dbea442b3598dec0814098ded493bec25f0ddfe155fb9a72a2652e4d2cfe8ee012d2f7dff6dc83d7017071d83d1858a6ca2af5f2dbc480534a2ca7e379982ce3b68646acc0a1c4e3258bea243f02f3204b1e1114aff857281effeec2610da2f47d8200b10c4df68ec81e18f1726587f003912f354ae05af870b1c9261d9b4d71ebdb02b362cc98047744472611f7328a236bc4c98ee9639978f0053d06464c5c66801266223ea4b3f4751a45018a09a66ceb0a5c4df0600646610a8f13fbf5e3c8a63b86a02e2dea4023ddad1e8e34dde07368344aa6f66e5bdbd55ea7205a12da921a900c282df81ab50d49e10cdf545a822f6b18653cda1b4ae05398a11be479f9bf9cb738fedff9a0f812319c11d514943106774415f415b852011486d474687531961ab76d574c24e001989b2041aab8054fed36d78d312b4af8c33606d29e8d917b5ff75519a40a010eef5e276e0dcaedfeab9c07521cc895c19b03971f0d24fe5e800bedb0ed9d299e0c9c9a18e01ce8084366ffb6800e2ff2b500c958b7a2d426ff2bd45771d4ac357f7560cefac6c385e4700050a08759d511b4f97992de2519a545aedc938e96e97a20c73b27f37077fa4bfcf2bfa0ec93539532b259a65d506f4872ebde5d798835d88e6f1b2042c9b0f2718f631f2f926106ea3e82a0b26eb97197142765a836f3dae5c05bed1b68ea0f9e4011a6b026f60776492f18bafb6acb52f0d50dff6e771a0148f02aa1214744bf2e267cec8e107c81e1caf5d49a72419ca69905897c6108fcf09291912aedcd5ef436cca2e788610ebb36fa2322f0417a39a1eff21a6386c315277ac34d711aa4110f00199cafcac3e800a79c5409472d1fe681bfdbf7a055746bd9ed4be42608983f39f793b41bca0fd8c528d465125bba24a500bb1d91b72d338077ce98b229628f6a5c8a65217752998b8701756866b72d938c0fc3c23b7e22f151f26bf77dfaeffca2fe6cffca9fa36ff417faa07c587fac3fb7efe1dfd0aff00997256b4000a1fae43ce56ae7febc9273999b6df4dce67eb124839db21d4107893d24f3b874e6e80e7407c8a2cc02f03a5853061a9e0b8d5f6a52f0c48c77c9ef40c5af24104d5a5134562a7319c901283f163103e5f87f15a13c5e159029d46b2c1054f22737d89cbc8bc59b06029680b170aff01d86152022cdfb39e1c5e619febca85f875e1a2ff56fb177ab57ab37c03ba86fbd8ab79b83b4bbe7e86cfc4075375befed25f8eb0c0cf4f2f75abcb5ee0cd53d4f8fb24e1dbd73f49e73070b329d6e36eb4e4e62bf163a75715d8f2e882ea11be89e747db3fb9a3451d5e97891773dbdcabd01de59fdd60bd2a30404d63d070cdad54dddd7bebbb596b3b7e3b60b05ac8ba6abd785e83edd715d9b6ed2bdeb567439ba6fdebdcb3d0d90600c66eb35f046bef7771378b9dfe95e062f3276eb126e7f1afcbccbe941546279ef37efddef95b71ee14ece56e1ddd7e62033c06f09efce97b83be0f59d6e1d21451a9a19de5e929b875e43ef5caf8a37e92ba4ddbaee7611190befb997d019a2ae97ef3631a818c816d9d5f0f2fea2f50a7a21bd6b5eacbbbcde76ef63af8a77af6f9dbd71fc4b7f9c2bedbb91fc6e5cc34be62ace0e0365a0d74820c25bc6b573aa2c85241bf3d70ad666219b3a527d3b857165cc06857d0db70cb11e168cdc6fe1efe37bdde3b2e772a4d1ca8974c5ec6eb1b81a371aa5e18f99524a429a318fa8ac19f0cc6e85cf70e5d334aace1eeade860bae60bbbcd014f7b4427a65cefcbd104ff0898cde623bb0f7988a7dc8d118f81e8b4256ce248989497ef88c6d853117ba0bf6908ccec1e80d8dfb01bc3818f78cd9106349a9e78af2a871393618198c641a8b3de6defe96025fc72fa37702758e7e0ccec669af705169331b6bdd85f50eee6713ea499f6b167f8e3aeb119273de9eb05ff70368cf4109ec12a36344d359d0b1e8b0d185364d440298ae4d2755b1f1a82ed725e05968a39b90ee9f5802adaa9703f586be82f714a87f74094e6e29bd99aad083063d739ace72d668294fe8160f5f0a2b6ce9db3be76495744e6c7245f906e624fc78529cb0783310f02d94d196204d5d2efe4636a7e22eea2e6839c0c7b1cf3127aaa183c13ad4512b878915cd6c5dc001c586c6a2c08e46c060359ecb4410c7d895251391b887129455212fbc4ef2af8652c128120fd7ba145b71834b0a00e4965516bcc8c50f2245dff2a651bc656b88f1ca7823dd550e8f85d1b37fc98596468c26be69a07f37e2d95b2872e7c08997d8be28c09fe411221c1222b758331844082b1adc781a453a2178a9434259d609d11a28e5f4dc5c6a96ea7632931c5746f8e2a0cc50204e73f8d4079fd29f82be9a9462af4e8de2c512e8c4a4c6f0651bba286902c157a04e33849976f4e27c284d0287e272c3747c295d09510133566e0e7637f066651f83aece5ba06fad102dfe8d2f30399cdb268faa9f0f4e6bb8f25bcfacc309d3c034d818c8825e198675db81fe040c217e3086a028340d5e0e528cb0f9adfd7e140a9b79ca5169d80d46ee0b7ba4db831d02c78301017c609d204cd822f01f202444840331d8201fcf949c0e10c21574825d007dcc8c6ac7b989b93f2143ae89f8a2350806c76002823a64867414db238178d936dd87fcb127388fe4e7dd40f1e021e14410186c0e2e840c03c36067003e804e18e857b2c396f913678357ac20d033d4ef8fcbc8396396d62e1136de0c105a9a8997b1246a8c1f843a288cab1e9dd4e8985e2b40f95ede8c734a5b471f115bb3abffe63354d73b59000ea0005801a076408b020eb018c04b1857a45b9ad484ed468b29805d7dbfdae70045001b000d70080284b37bea0913a001e6ac004cfb20d7cf01b48844968d7a80510068b99ed3574b80379831acabf314a78acc60aaa757e0349a02a0067800360029c003700990250081257b1a800ad0000200a600a0407b00863ca8a9ef94628e5932624b4fc4dcdccfb279e228437815403cd93279fd367850b566476cb31688146af05551e8f775edc2e5e0136a0b7f6dd979342cb421a1249c3d030363bc9e47e411647073542e726aa003fb5f6daab89910fc0016db731a12c3df4adc0d7bf34446450c735c850e783b487e23b6257652bfd3e1a5d94c692c0452a03ef0c55de2d20a20089c470f5585185a83772f061903da806970198c19d806b9c1c9a036601bfc0da0c36043e2a10eac0880070e0931c2c541408244383cc08fc0596825cc234816a612e6133a0419bcf09961f32b6c3612d369ed0a1b1f0259d3df12665439e5cf264437c226f0bb2e41b6d9bdb60793f596eb89f2012504e10227612a613e840d2b33c66a36d8980f9f2060184ca6ebe4cb9104b918f40d7e06a7816070d40d167dbeb4d119026da07c204938271c4808110c839b034a02ba22ccb41d80c6258141252c838ced100682c080a283990bb25f4a3a6184b0b129885e6315852e19dc0cde068cc164e035d8a6c13e7215bb08b8c1d56065103290548345274017011a98061f83b00169f019e00df80c9ec1dec0131a4cd6eded37be816330e5a043600c1ecb60196ea0b7f80c3e83c671b0cfed666e0883968130c83af03cc20c6b2d76664ea0778338d433eeeefe6a650ad06d7442081b0a2bf7b26b6019cc0d60069f81de203198c76005c3762806d400763042d01b9c068b413e7c305947b975cec077409c504fb8247887304332b0d403399a7903a035b81bf6f65ebd26300e17a67b38b8e5057ea133209083c95abeb4a80543c882b03bb04ee84380165e3a61e3a2b2307e25c4ea819612cb6a50b5de278a114aa791ed99d066f8ba1324834e30d807e574fd35c2c1603dd849701f61063530ec81f9304802fdbd78cc28d839507b0a64d7b76dead53e7c93d33402660e591c6697dd3073be96c0c4840dc710dd6b21ec7bf42218c5081816b8846b04182b6cfe7c6abfbe1e2c93dfc28fd3a01a486307bbfb05f672128013ec08c560fea07b029370432019700f7e12a6125c803043cb430e6e066d72b01524dabd403de89cd040380656038a034c420fe1881a2c079e44c268100c9848c6206ccc029e7744b70b3440b0a6d0013c0c3c0c3c0c3c0cac4dfcd6d7adcdbc05996492d3d03e5508ebba24d39629a5e44de39f441a5167ca13b7ef90a20b3e0b620b2d0c962cc168ea5042e36e890eaa04a7aaa593888ac95644096e93a47b1da249b02f1e9398b3c916fd4882b11cf53befe2dec9468293cecdb593e5a43406128c9d6758fe08de84ac50e23133491a4730b2bff67b4735c66823b80e96a4549f3ce89d11ac2621fa26f97d3c4916c1c85c79d36715a1639228020b3412c146d58c2d523bb9668b091a88e0845276a3a5520aba840cc149328fdafc4107d3642138f194e89e5249085ef7db2a4cea5c264c06c1c7cdd7355a4170a952bcb4e992283a0b041fcbd3880e566db295008293398b49ca740cbaf407be45d3c9d3d9a4e1072e7484be6a2bd349a8d0e80327cdf3aa72cbbf4585061f52a204d11e78b5936212da04b7def5c027bd5e591ae3eb79f2c0b9c93a2a25cb0a428b073ec73f93df62eec099a4dc846c91164bb4d881ad9ce4bf53293b9aa5d481b394cf045d5da1035b72eeec41e5e53db1320736793e5125b33edf2a7260d53f74964a2194928f03634ac558caec24994170602beb85d226e80d6cd612af5cc93d9bee06fea2978d09253a9da70d6c55fa3ff71cd52d1bb8ceb1e289a9e1ed1a788bf9645633f92566d4c09b9d0c5aba53a8a434703aa58c5ebac43249140d7c2e1399b1fc5a058d33f0d71fb35745cd5065d028039bd45c4d59493fbaa14106ce4f4c82b6335db1928c8193dd524ebef99a938a18189d529434794278520903376e826ec8a47bb1020646e8bff760514db3f20576a345cf5fb526c5bdb0c924664cd7a982a834bac05aa7ed9d9bc905b6c3ec735f474f4a32b790b058d94665ec58f3911c1468688135e982c864f23f7efcb97103ef7bec7014a0e9096864416960814d2d9a84436d40e30a5cae2a254cb6b05c41293b41c30adc8a0ca1429355e0a44aeeae1e4fb60c15f82c393b2679e9e49429b06351439a1254e511a5c0a718e29615ba89058d28f06149f449b1a4072507853328f58ba6d913381f25a7fd3cb3d22b9dc07e284f269324a4d104ce6de4c8ac1233a98f3498c0bb5a8aa9a950ea8290c612f8a0354b0c1f1d6928812d31689de4f9bfe41ca4910446b4944a761eb3de83349060e58b9a96fe92a0a1048d23f041a8eeeb99badf46e0d4e5527277c84b825b04766b3449276e6810816b93b32839b35892541a436033a8b74e2767be6c823484c0092583ca6af6b1dec460b0e9979359bb8a27210283ed2b1364c98ef71de22fd890222bcc8367141dfa8237df2bf590a5733fec052b723385282595480d79c189799fb4c9c25d70d993a4b7478b09c2a40beeb7d35b29cbae752ef87cb257caec6d0f175c6a2db16bafa4995b7082494d495473ebcadb82dd944f5fcde3a6a45af027c9f1f2d4346dd382cdc92e09fab45970a2c49cb26d9e8d5e65b260544c4c9a4c932679c782cd5ea2b299c792731616fc5f9f3ceaaaa49cde5770f2c7f3fcd3189e5d57f0efb629c6dc69dd6d2bb83ca61f73b97e3e1956704295d0a7eff45b8959057b3ac850f7f14d8919557019a47890f977e6692a18e5317a55aecaa74c50c1e9283165916ad9c4cb29385139dfed56fcd836de1c6698a21ab74c25774ac1bfc53a3d3284126c52f0194b4dcc1b26ad2aa3e04ecce4891fa46d2e0a2e7fcc5a3b43c1fe77b2cda3f2c70f0f14f96776cd7fd61f1a9654ccf804d73b4a3f5b48b3b3be5829667882cf3e667292c245e5ad139c6993947c0f4baa63c7093ee978176c2c5797ea36c1c74b2568935212baadd3045f2908cda27e1ed7ba4cf0de991f4dffa44b4a9860f427a1bbd16d34275d82cf24c99f6926b304f71a969dec64afafd13c07d231a3128ceaf50d4a4c929c5344093e8eba8c26e7754a8226c1c88f67490a133eb64b82af24c9a2936e8bbdf148303a9b78565996d9198343518e1c7cc283478b51823052d08112842146182708438c32cc8004dfc9e47437717b04a32f0893692b5f996e8ee0e4f26c92bdbaa735c146307aca2e7b5da8ce1d46b09e97359d9caabed1457092e82cf182cecda32a821375e4c9b2745bea2411fc65f1a444ab99a42622f8cf49b6fcfb9d9f3e04e7c12f588ee3bf1a34041b324ff4e5fdcf275808c6dc3b644992a426a58c109c868ad18427a9412b1b049bfc4af7e5310982df18e36ba9a4626e522038f133e3552725dff980e0a2668ff1cdff037bc2734776cc6d52ee074e8a29da6ba6a566b0933e70c2a2c6d1d896da1932767f98c1073eeba25a4ab2cbafde031fc5b48a77a7b09cd1036f9ed5a3e612b4df9a074666502553795a0dfab9c30c3c70d9fc45c9655228612e72ac4cecc0757ebf3f9d2ae536333131a30e9c92aa7ed3473d4154e8c07930f76d171b799739f09ae42e214d6a911bad1972e04b9d2a617789c30c38f09ea4b8c94f75d09f7c03a374c926ed348de8b86e0262861bf84b7dd59777a23ad886196cb8e42b49ce11266a41d4812366ac81f3f04cfbd22625d1a9810f4a4a6e31b865ed581ab8f4974a4bbbc69224e9f230030d7c75fcb55862859faa70a8253646e3193831b59ff44ae357e29a81f3244f4f652769fbb932709973a38ac6d3a5a52123470af430830ced57dc8b5b23f21f65f0d091e387ff188fa76387f6983106c67ce3da07999a536862e0eaf429c1537ac3c06bcc7623c5dc5b93e817668081d39a3fafc392dceecd17784baaf3dbcbe2c20c2f70726bf00a42755abdde05764cf60e3a5656a730830bfc7ff6944e10da63f23e29ccd8026b9a4dd2dadc5962120bc30c2d70e6257e3491ba176664811f73f75329699bb51d988105f6d4e5d49583c7d28db760c61558cb65a95c533251db92608615d8927d2a788cfbd6f72883c78d31a30a7c3c73511ffda702bbbb194cfe98e050433a72a462831953e0dfe4f65c2e2e0fd2bf1418b96aa2544d5dcad1db30230a7cb25fdd4cf96453a33dda3052d0013d28f8c172aa3cb1242d42cc78022727e9d3a2b6a6248e57c68f6ec20c2770620cba9bcda44a98d104d6729a96d82787093398c098ccff1f732651d641e6862561c61238997276cb234d9e28cb8e8485194ae0ea358a097e3fba1a9b9104de2435a26bb44bc30c242c5adf5e6742bea80c338e60caa51d2645cd124c439b602769ed1c05c30c23b0d7f76be1fe6e9efc22b0b6299e2df849dfbc89c088f0fce9639b9ad664087ce7934491d79f59af324308dc99da92fc244f30182f292993c9eac4cb3b301851974ef4a5a73cb9f30bde7352ba93abc6177c99e4375ec9e2e68fe905278752925c25575ef0f93944e9135a6de4ba0b4e68d2a3b3094a74c1f787e79283fe93e44bc905772f6a479f2c5d192db8e03caf5349eae52dd89393d063c24b7d8ecc169c9824359922a3e8cdc95ab0dba5b6b172df9b9a169c261551aa2bc876cd2c58f724f707e15ff275ad031f3774ece0d13a40810e12981861d48fc1a38c06c882fbbe8d232d9b30d9772c181f8bb9531a008bc4b29b2497a42727a5bfc224be6582d0ef49da159e7c63622a59cd5664f2077da2e9ac398eac3899b6246d7ad5ad0c1f377057c197144b52b92296f4d3d28406a882931a524f0ce9c9a42fa582cb5e1dcb6ce38dd98f0a763d4591ba1e2abaf753f09baec24c16edb6256e0ad6838b3c3998f5e7f69482f3bb54cb4a4d273587149c88558799e0a32cec51b015f54b4ebb39dda64614ec987496a56a28b8d4494e6232f9ddc80b28d8ecd027b54575f03c7890821ecf8347f76081111af009fe4dcbd5629027384b4227758237b54a5983e70de0047f9725a65841b34f256d8291163a86e8ce35c1297dd5e0f17fc73fcb046b4a450fa5d2f6631e138c108f79b3aae8e827e9126cee7fca0aaffbca794bf0ea174a8a3b9e4af0a572ca11327593983594e0f2c63b39eae67fea49706eea452d8a9262ad24d811919e7fcd62f84623c178d4d7101b51728a20c1979249ffc494fa43e71ec17eeea44decb48e60df4451a2fe08a14a5d0d6804632926ef8b41c4083628d9c4cf6ab2b4a22868c02218abbc92f4d7679e8e14c1a994c44d72ca5c17539408be74c92ee527c898b70a235d34a54c8d2cc1ef6530b9cb729a68f24a704266beba730f253811619622664af5497160129ce5fda714111e4f46129c0a9e8470b1ce1db144821725df4dec5d4ba80f0412fcb76ec7f44b52df57208f60bd3ee92ddde9691c3e72e828838741418f1d1c30012c02c4115c2e49cea74193ea74231df80e6d04a374ca70bf7ce8c841869e0f1046f09d37476f074f693d090eb51e3dfe068f26431764115c86a693d5493f8a8970a8dd50d3914304990344115ced7dc98ca22f076d72e373d03ed08c0a4012c1a6bbff52d21be87ffcd091a37b203b1d8020824b4a988ab13f49728f6a0b9043304aff88b76e6188c10a1043b09a54a576665b082e49c1e4ec133355b5150a1042704198f6961c3a259db1417059bbbe7b4a86132082603ff9a7206c3b9d091a1d65eca0044820184fcd59baa485fab050122080e0a4f509ffb63e53a74104c81f7ad74d27e3c80fac8e2e557b29ee49361904481f78535a2a5ebfa160f700c20776773c59fa0a9e53529903c81e58cbcd3926a9a0ed4a5a87067c348e12d801440fc9936b437cd5b28203903c249a1853df01081ed85319ab36454bb2575f40eec0e51cb373ccc14daded4c4f80d881edd0496436258543edc7fb3865f8b8d18663e4f85146d681379196a34a929553376e58b209103ab07963462bd14aa7b18e473990397016d59224b8c83751397840e4c0ba65101384bc6c2708e5b8c1831f3a3a073fc6f7b86b9038f023c4c45249ee9cba9101081cf89cfa4c49afa6e29edc486000f206b6a44e49d0315da154d60d6cca6f5fa34ef24d17d31c5cf2029036b0f99e1d4ad7ea081036b09a9d73a865d1c9b2ee9808903570298666932bc858524c1b4b80a8813b75725f729335d9ba8b0b9034b019c2d35452b2a6e97d472f40d0708eb77b42c9ac39433a733a77fd1e6f8098810fd966a34b2c65606ff3e6b6b1cb3565ca01840cece65cb7eec1635079f3fd02c818b87c6fa59fe4741db59476ece851823a31f029a54a297ff02eeda91cf9c3478e10a0ffd163070a7cbc0f159402240cbca8d378a743fbb49f80815182d6f50e356a4afd021b6410bfcc9233880e03205ee0a49427a8cb495da9a959f009902e70c9c642e8d22ed964c805decbafcf3573d01f3b1ab061432d12205be05492ea4efad169729203d102bb514d56ced6affc570740b2c09ed4de963ea655651c040b8caadfb14cb9de9f2f03c81538a11b6ef2999845e8e6060ede879ae1008815b864b1aeb5d54d5a1dfd8e1f8a3eb40aec49ee6ed2bef8bba6763b5670e702102ab09f94bcf1f7205fede4861640a6c0a6ac7f92dce51eccb3366cec0d2c804881b5169344cb6c13f2b1e387824481ed1e252f6c93fc2a323700814295a5f299ec9b82fe06fd030548049027b0eb49ca166ee238d46ef0a0c78f1218fa30ab018813d8bb0c1d409ac0868abb214ddcaf8a291b35d091a3063a729838803081f3edd594246142f529c11e75005902a35a53a614b7246d9267c7184902c3118028c14c777629345bdef4469a6d8f5f9024f0a13b9f8ed1fa242979923b8020813ddbffb3cb5162ced311d87442ababe60f16cc64044e446f2c8effef054dfa02a408eca7b374c2a398203a2270655f7552546915eb4186c05a2a131332e6da8f8d437b7c42cb0211026762c94b9deb1f7b8482c18b522738d4d006fa30c408438c303c033c701b366e945d2f1c000cfe43a5b5b20e8ff9f52f38ad4abad2fee45391f1059bf62453f064740f0ed8b031465b2fb854523cd1d6964fb88517ecc7f64c2926bb0bf6dc6e2d944c7173864c060ed005af795a83cc7bd1b1c3e80a76a42059c16b0e6cd8b0e1805c7059f5fa3b9f20754db8602bb38bf230dd820bfd61bfa93cbb09d982bf6ca13ef7e824ca530bceb6e4e86e9baa849a167c10999efcb684f27716c9cb1a73a69895059b4952e529e717f54c2c1821577bc36df75f58b05a6a2f6e4c32a78cbe82d5fda4444b962bf80fa9d94c4ad20a3e68eaae7553e399c20a4ed05df29992bf56e52ab83777cffbb9af27a80a36a47fec6892782ec1940ab6abb582da7472474ca8480755b2bf662c9d824f6da3593d499b4fa66094e8fa29b4e49125540aee4e29318b8814fc2795a4e07ea26d39a3e0a48da7366a2d0a4ee8ca98a575d99336147ccc9d4bb54cfa681a5070a9c4e49b2c66c7019f60e4d726d5ee95ad4a3cc1a567da942d669a7782fb6c6665ab27d4979ce0f47b12d362aa4e1137c19db463194c50326df69a6034a85025cdfa4c702698eb47ab8e31acc704bfb142d5a5d8da31e7b41c7009c6456505fbcfd9b46f09ee6e74fab4a24af0490a2a968eb9a2a6204a7055e9217cd44cd43e09764c7e4cd1e48a345949f031ad5a6e93a4dc7223c17b5a6edfca7184d04082f10cfbccb83e824d5a9e49c8b89a36ea082e9a581e75268de0f37e0e0d39a6e1298ce03efdc99654052d82ef20ae1df2838f522245f0a54c728da574899e4489605bf55d3c88e074693593e1a5748d87e0ce3a8f0629d252c5107c8a9e6f264d3a31af106c8dfeeaa90c328b0a1182b7782abdc8fabd3e0d82937c7412efdefadf17041fa47a285dd1d43b0f04172bac83ad05217340b027f8757709fa1f3e3e070ef80327e35bd0a692e4dfb61f18dfd357bf0f9ca035babef8dd5ecb07c67e3fb58ecc1ef84ed32c71a407ceae629dd0ea6cca03976a694ce82579658207c63ce8933a560775953bf079326f4e1e3bf075415bc537ebc07ace13c264d01d4007ee4c85bc24733e3bd11d30074edcbcfb99ba7389cc1d2007c6a44d933deaa978ee71401c3821f367d3397338f031d47b8d5062fc36bd81516b4a7e4ca36d16e406fe46e9d63c1363babd0dfc655139aa7336b09b5f25ba41b532be064e4cd00c9d6412847dd4c0ed9858b0368d3189a6e1aad1a059538a0646f7abe468a69d7d39031b2d55faf599c59262063ee53be9a9c93270f92e5959289341956460ff524e791dafe278670c6c6ebd492645ed3cd9110327e231492663270cdc49a75ffd4785d6ea8081ff4d26655a12ca46a9bfc0c82064073511769fd45e60a38ec8a0493a59b9d45de047e5d51a9961b1ffe6029bf44c4de50fa14e8b0cfa4824e1802d701b7a416e4a62d4d16b810dd7d2d5a0d4a3fa59e0b74ec68cd56381cd329d43e465f6a02bf07f23d4da655341075981b70df66e59af02eff6c1e44beaa602e7398f9fcaa5baa1ed2970252fc91774ce13f2d252e0939befae49d923941d057ed3a7784aa91a0a7cd01b16ade468e6543f81f798a4a5e80a9affe40436a81cc3ed04396a4f4d604b9026fd5dc610556202970411d56beda1dd5f426f4992570257aa437ece124fba93c07fa7263b4b6fd24448e07cdf9457d6cafc089c89d9635f68bfef8dc099f4e3ada66392c4bd085cd44b9d729299543880085c7eb38cb17a3d95d60143e024991ac3dcf5da5c070881af13f116d9ddb1da0d069b3b4d544b122a99bac0e0f3424793d3aac83ed95f301a4b6dd46865298aeb0bf662daecabdff682cfe725779b92b2c46d79c198dc21ffaae42427fd2eb8de7a8d9bc41cf27e5d70dfa5b3b67663879f0bb6fb52286d4d2b13c2452687ab7774d12d385da73df34b83d0215b704135a8e059ad5f3ab56094fe7862c7cad5e1a105d79b3a4c8c496eb1ec2cb812ad19c392dc599705279a297b7d54f3a089059ba4c9a99d4c56bb0c2cf8249a9c83da5c7d5dbe820b558d1b9a5f455f9596a0e10a2e26e9275aeb3a5334ad602d6e0e4246f51cb36658c1fa08e55b23c355b07eb209161aac4c7d55b055e9154d253996284a05179a4acc24392ab853def5224597fc4fc10795d37743c70f8d29f8349eb3ae5b9e474bc19a9a955a97b2dc0e29f84e3ab37f7f346f47c1f8a6fcb24b2b0d51709ed18312d326213a46698482eb9875d35510e129a33440c19ada91e6714cb8892a8d4fb0a3f5e45dbcdf3551a5e1094e0a326e7a8edea9c4687482d1267f4c69514e706757efa219f3ada99be0d38269ee9910fd996a82bfa42ab782da9333a599e0cead7cb74d7b4a29c5047765194ad0a64b7051f4653c93c6dc9b50e0b256a950593aed3ce70791277015d3b9dfaec8098cba922989e64c2b95d38492de4b6207b36b0f224c6084454b973ea618367c882cc104224ae0d3c6dd98bf61f1b22e1e2249e0eaa2e7e69c2ac68f3b0eb52282044675d4742a3c3d3b96e050d381c8111825563439784e278949fe79249a8f05818811f84fd5226d4c501b998243edc60e64c5072245e03ce46797e41b3ce989c09d7b2c57f5bb8e3169088c3c1d3ba407e19559171182296d4a6a9d3cc160e36bcea31f3bd5540d0c3e878efae9f81a1adb5f30a226e6d2a3e4e5a04f7cc1e86df224e66f1d2593bd388e07a1f3551c515083178ca66e496971dd00076cd8f8319281448d5df0a2af64f44cd205174f7ab2a4e4ea859f0bd64fa93031b93532967091d037e9d641ff5b30322695c1cd04d9824d266d072bd3649de46bc18d59884a3a87097942b460e408af4bd5b13f799f05f72596a81d43e347d165c19e8e49a93758e8744f2cb8ca360dd629ad85be61c1c594ebd2e9e78f19ed577063fa22538caf2bf8b498eefa62eefa7a5bc15bdf8ea796969cc76505379e4992641b2585f29855309692ac96bf39aae02e3da3e6b849f48752c1f9080f6e7ad544e6a082d3b4dbfd62d253f89d69632c7526a6c0d2d65d36192aa5e0533ecbfa18e3a9b714527099df2ca50b4d8d3ad61805234fbbfef92745c1e8a7fcdca2cf24992443c1554cbe95735efd354950f09f5344736c0d329bf0135ce8d0fcce1ff4045b395e657fb5d4de6227d80dad5c163da6cf0d72822f49e53c2698123d4f7013fc055552faa43de87e4e137c0c91ee55e3f7a5bd4cf02725298e994962af394cf06939fa62ce5c823539abfb9bc9a6df9d2538592a3aaffb54da5d094e4caf59cc4e98a42625383b319e2c3de1968493e0c41c2fea7626094e50b2c935421409fed2e73249294bcb3524f8283672a389d193245a38d47494d123ed118c56b98d1a9daf7f47477027982a193c8b8dce4f2318dd62aa26985785a83082b70e6a774dbbff7f76118cd071d426bdba22584d927439f2ff922ed144705fd97b92fabb458b228213e36f7bccde92772687e0ce544a1ea326a5f74f43702d7e621453df154db04621b8acf427878810bcc953f953532a932c85438d070f8231da7784c0778cb163f4e320f8f85dd194ea917ea31a82e0b2c432319b7725133502c18919eb3ed7aeaf0b084eeaecb294e63a3a6abc448d3ff0a1b2494ac7ca1d744cfdc0558956dad4ea21abb5448d3e5cab9693505e92207a53830f9c784149f28999e3503339ba043e74e4e81f3ec8b81a7be0a44b7d2757ec14256ae881f34ba2bd72f5c9f410d70c35f2c0a8379deef17227f9cdbd49d4c0438d3b70ae3178fa9e6aaf5cfb7bfc40418fff31dac550c30e9c98b7e4a649413de524c90a76a4e04560c346f2861a756037b9e5f1d0bc46a2061df892731aed3625a6fa926ba831074e2ed1823e935392a7a46bc8814fd57a1135e2c05ad26954104f99be92706063ae58d2985935dec05ec778994bdff8c9478f1ce9c68f1c63f0f061e6637dac076ab881d50e4b3d4b511c6a64f80e6e201defc3688d367069576dda6e5f49df5ea8c1062e28f10cb6fb5595693276f88f3248906ca1c61a38d10c99741cff98afe7420d35f0e6f153e70f7da6c54b83963f98c95da28487434dc9f016dca2061ad8f5f73c4998c8933052d0010d88408c305810460a421046d638039fa6c1f394ae92623a1c6a7664780bbed00435ccc09b9cfbff464b12735296815152965de5b714d2c431d420032b4aa7249a244d63e0469352a293a8ba77480c6c98862c952b7b1a1907438d3024e75b8c0103174268cafcbf5143a9260b35bec056efa930b5bc17d81a9326ef49522955638d2e703e5e82974cf209fa9a26d4e0022f9a4c4cb27aca16d8b09444587b92dbf6d402dfbad72b4293184d7a5960c4b3a69493e5cf582650a881853fc9622532c7ce50e30a9ca4a4646e52f95ea69215d850a3eb4e92b26609aa029796c2bc4653f4efa4022f2a8959f9bffd4e48a7c0e54bb9fcbbcf043d1929f0ffdafbbde246811152fcf6ce63ab09322870224b92afe2fb2731b927b021849f20476f4e6053ead89525d9bd046113b8ffbe24a50da1b3dac604b6bd2441c7a44674ca9425307a349bec2564a7125325f019b5945827a6f6932a097c901e363af675d62e41025fd5a92a566a643699237096513fc95fea1a46e0a493a4969c775af5536a1481cd59deeff7a62da72422f07e26ad49efe3501b03d5180297af4daa53a9a9210456efcbc4de4c06834d5236d1434781c19a549bfab972cc5bf3177c3e256faa989e84f2135f70d2552519d3bc54e8d60b4da7355d7a53c40bf69385d0e6a1fe2af8bbe09424692551ab4ed4ed75c1c7743a7ea8a04c565273c1c5ecf2ee8a49d76b0c178ca57d92d49a8eba93bd05f77973326171640bde3cf52993c4249b68ca5af0dd5782b4e0f4b9491bb36ed221fc5930a25373559294820891056f365631061df3794eb91c1c194262c157d0ea776a3308f9292cf8b4a5c96292db4484bc82919f469924ba497a3b5dc1c5de6cababa3712f492b6c0621ac60945e658ec9f362d2dc87ac82ed91565243e7c6dd2144158c5a4b6551ad82d636157cf64e7143a80dab78082a582d2956e7924b424ec17af274310926640a4e9f24b8b929a1b42c1d21a52843e7a0b3b796d2e1c1df20ad64134248c15892547d7da7d44b49aa10320a5eb54f673cb929448828d8dc55cf49304197322d0b21a1e0b37e92d726090f214fa0606c44b76bd569bf2df904fb9de40bbae2f59e4ef1045f923ee9446fd9092e9d8e1e64658db17ce404b7aaf74147fb0c316d82abeece599b2d0e144d70419499942c893542da99e0e434951a2aa568ca144c30e241c52f593de5f3521222e4126ca518633e559324a5d496e0a4a0f23796076d3a2d95e0648fa37247b1e4d53525d8f11c93db93d0bf10854c827d4fa5f6829b982c953052d0011d8f822f03073974a800c78f1c2cf851c68e1e366c502144125c67adfa553c1d8f9748683a752621541248707a8209b5a44f89a15b0b44c82338f983f2ffa09395961447304258671e21930011d2084669e7dd710de2504b3b14878ff6406a0823b8e829ed7470cf95565a04ab5e67d79b92f59bab08468e59a76029329b894904d723a679744f29b95410c1c5ace9c430a143f0258690259fdca2653104277e3206bb52eb5116828b31a6ca9c2284d22924049764514147d7b1d3a80d82efb02b99daad5652b28c10417092ec6f4ac9b182456f4302c1dd88bb496dffd974c90d238400823795597143de7f60cb634a924aaae2871f217de0febfa397e42e21840fbc058fa12c8910ded00d7821640fc9a949348d5e22aac135e89b5c319909bd64f941881ed8f4efe1d726a5acebe40742f2c06d2a7157ed452fbd28040f3342ee1046881d2aa40e638cd162d0610e140891830442e27023078f0c94a046081cde75ecd07123070f0784bc2124c40d3e78f037100969838fcf91019110369490358411a2860384a4010d3342ce7023012166904048194c08196efc6040c8187c7c0e901031841112869010303820e40b0808f142dab1a3078f2edc48fd632420840b3f76ece85180902d4020440b4948167ebc8f1b58b8918347067890fe35a080902b58e1c60d1efc0d4442aa702324840a0a0899020e1f8d6324440a0f0889c20442a04023e4093c38379cd00426f82063093b52034294e0e3878ecfc18724c10221487040c8118c1081902240208408120819428810404082e1c3c70e0f2c0004183772f0c8000b44407e5101105fe4e0910114d000e9052fc200d9850fd4a375f03d72f0c8808f3176ec28c108882e725103041733406e718306882d7c3400a41634406821020380cc220120b23800482c0800020b4e6c8f3926138458f7e504905730766f7fe9772daf63aee0635aea4b0fb6c11f5f0313460a3aa0810d9001d20a1056b01e4dc93ebda3ac360ab20a3e7f7b499fd52dde5e094210c6085e70030520aae0cee2580eb7926f64387ce418e39401920a2e7a3653f6b7b5261f03082ada37c9ec9498478d0e6401c8294cd67f95f2e7c6bb8ece01ea3253585ae117ad2b746671688fff91c3478f1d1ce8c00d1b3678201e2ae8f13f7494b1438c313a073bc8f813e820810d1bef237d8f1224037003482940487120a3380665824cd79b4445c1aa26bbd126fc6e8416871a48281895fa2da3c92c0eb5ebf183070828180bed1a2fed67413ec1f899244553997b8233d56da3e2c8403a6136403801b20946ef07fd298f65f5276982d75ca272840611baa64c60825192cedc3ccabf046ba937ac0f4842f1c5e44e23203c00096ce698efa6b4c97c27fb8023b0177a23babebb2a77d0038cc0793ca12698d81781cb799d7da287d0a099071081134c12efac62daf85732044e599da0194b891da4e50142e03726e9977694c611058393cc73e88d6f25e705189c585265d73dd35fe52fd8cc9dc4bdcbf94fee0bb6b5c4a04cc772a4176cc9cf959bf25c64a54678c1e811992663a72e533138b414c7c82e70441766c3e0482e8a315ea97fd5ef366cf878ef51c60e1f62e8c8d138c4b06143478ec6e185115c3496c7df4cc6334138f43dc7183ec4b061037d0e1e3b3c077f021b3672f03dd07afce07188915b7095da841079d68ecd00099a30620b6e2fb7ea6ee894ced75af0dbf6a3d6c71ca1c58dcc829317541e19843e220bae4dec526e7f2cf8f32c6a3f29999643040bbeb45992e935a764f20a469989b1e3e55dc145d5dffc24de0ab63d2f95b9e6ac60ab63dae09736756567156ab032bb8ad75105db15cd2dfb6a77de9d0a46b42f2d73f46c26d7a8e0dd3a0537559258987b0ac6db43bf6b8c9a8233eb9cb5a4bf905ad152b049e52ae52226a4e072d2294a6a32abab925170f23ac949b997a4352aa2602d357a079129a1e0458a3ca1a3df54731214ac0769829b4c59f519fc043726556cb711ad4ef1045b310942889dd6d4dc7582ddd54e27c59337fe6e9c607b4dd268d2a64636c126b73729b3da3ef465441366a307239960834a21325e05a1a007096cd808c20826d831594a0c59a5e492e518462e6136d08825d88e2769e8efb859c34825d851e739896f3aed37995182d3ac7925b3ec488c4c823d19e36df6b7fe92948e1891041c4622c15b5fb2ff8c6ee29f8104e3754973534992ee7c3e82d349594ccd973989274d8811477022636feb07bd65291b6984d918638411ace6e67c72060f32e53db208c6da2f5989162a824b926c9db693fd5dc96a3092084ec9244f2cc97bea4a58b4308208364cf8e709f3ec594587e0f6c646f52539e639c5105cb0249b56108e14829374759027b5f5c352cfe1a3cf11824d3244533e9994fd9f1a3132082e6a27d5f9d48a5fa97168598f1f3c94302208fe649f6072103d1b4602c1c91b25f2bee41c94d094e41323806075af62c8d4232cc95a1846fec065cd5ce5553298162d31318cf881f352f6a64f12d33878dc50378cf481eb8ba724d9f4bb499beb60840f8cc5d4ac39553eb207764337de8526154e8ce881933f53d2697742438ce481d329bbf6b54226008218c1032773e852da8234d9bb0388913b302a4a87c9ce26aaa2e70e2376e0f54cf6f426f489a1e78ed4818b57c16d57e4a792d27448924acf2bf46f1a8a9139b031bce412d3f9ed43732372e0f2aea7fdcee88d4b8cc4813d3d99d13f9a3c46e0c0a52c8b7d9a72cecee1c81b58f72a5994acbea0c42438f4863137f09e34c4f265cb1a5a0e32c8d8e1628441c68fc161a4200461e0f01fad8113884004e67a9041c6f6481bd8a4ac7793d2cbcb8af7f81c683dd8c09e5af656763d29e7a00e1decb8d163c78deed13ccacd61640d7eaea4d735c9a91c3776a03c398ca88151f904f318ba5dd3db869134b02b1afd4673b0f8a68443edc60e8423686084e8571f19639a888c9561e40cece79433c97eb9c55246861133f02955cc4ba93db5b3c96462a40c9c3e0f66da04fd217b632c31420646271996838e08256b3406364b4ea73b0932e3e51313236260446a4c8fb1b763f51f063ebae8c7beb4f9aec42b3102062ee564594afa2425dffa0b6ca6cf98ace437594c4e61c48817d853722cadc9566a84cc2246bac0e9ab1c5ef229a995c47f8c7081b18ca9d225a5c42475e806235be02e692da166315ae06a64ca12f2a4c8bd71240b7fe5ab984feecf47ff0946b0c0be595576693e93638843ad879660e40a66c3062356e0cb3cf877501a138c5481d131e4c94fe9781f2982112aa08d1b9982d9b01129b06d4a9fe55c524d5c53321205cece4eb2304b82022b7a4274099eed76474f40b3ce82562eb91338a5b1832e61f126707ab98385aea669d23281cf1342f4799a27f9a34b6037a5eb5ea69ce378ca8812d8b437395d5d5012d84a72527a2cfac9ed20815dcfa76485ee9c61ffc811f83856b91aaa937812f120a3078e1c310227b6ea58ce5f5257770d8c1481cb95849e67af1b0bba1122b09e25e546bdae52e522244686c0e7cbb6186d93a8b27ef0e0ec8f3278e8f840c9c08810d8bf1293499e1aa3ff2d120cceab52d0627db9a47c038337693c6db64ae4178c98baadd5b7df03115fb0b9b62fab92072df19422bd60c7b209e141692d10e105a319fab4374d9f527fe92e38a153922767934ea828a92eb813659a4a503a9ede5acbe582d3b16a3c9518c581a6c70f1e22b8e084c6abb2284ae78b9d5bf0d91b3bf589c616dcfde8e793ff594cda5289e09f03366cfca71e9f031f9f831ccd8230c420c35b10460a4210460a7cfc73800c6f818d058029446ac1263bff134f7a12e43efed38daf418fc6e166092d78170f6d2d55a9b46997831b22b3e0c3c53cd5653d52a043053a48803ef0cf811d291091051b33d6a7f292b72a44291089056799b1c4b22406d34188432de19103870f85059b24dd92824eb2939c0c249c107905a332e7d239c554e5903e42c415ecc95499620a9a27a36dd82884482bb8cb4963d6132a4a3f8a39b8b1e3c70dbb80082b38ed5f39e992b4b2499655a4b1df62860653db2e5105b7b9f12495c4ce4a376dea0f412ad8245ee98fc61e2544a6051154f0afdb5563c12bccaae41466830622a6e074ee1af55972be49726220520ab67ad376d29a6ce3f7a4d8b5b67cdb5a1f397c07ff68c70846c10932f79baeec25b41e3bd28d5d8188283831d5ba9f4a9a074442c1a5ea2a1db5e3aa49b11c21d004147cfbe572d1d723221d37ec3e5083d640623f7ce418235920f2092e08abf6bc29fe6ea38591820ef810230c31c220c35b4086ef2011f1045b1e1a636bba5c915d27f8ac19efb7e49276f2c509ae4dfeba8d16dd045b7b62e22951d404a76308abf1f466821f35d3da27bd98e0934cf9c4ac3697e0d4e5f8769f5aa3a8de129cfa6675b76c578231c9fbede46d77cb9d13885082172d5a2dc898d7a0753419377aecb83109ee725413753f25cd8ced4044129c0e2d966e9477d6ca8904dbeb6a996a72b39896051148f056e2bee79c7e049fd3576f64107a53437806114770b1d4bf27cf26d7db46914670779fbfb5a40b8db184117c8e126a648e3a4158bb0846c3432715a2135104a34a2f5b35a713c157b4cff869d9a4fab38c4104117c66a5248f6852c14f120c440ec19a8dd9096dc183bf3a440cc1c54da904a5aa1b3ce8d13a729c52083e899a73d27d6e8fe7b143c710210423ac62129ef1ce20880c82378f7ea5f7714130a641c9ef55d287854920d87c26973a6f9346ed06109c8992617ea1fcd7b23f301ebb43059d52ce10323fb0c952a536d1174cda7de03254962cdd181fcc2798d22066b27be0b4b947139a6338d46eec408803113df0572663925a59eb047507b23cf0a5a4cbd16972c614223cb031d96591d5eb1dd81883129e6b64ecc05645abda1c54dab4291c6a85162275603d5e123359b68e4a111d92faf72c5a96340740108618617880073d761c7483073d7690f1642439109903ef1e83f664d993ce6c0361881186b1b25d88c8814b22b46b0c75e3c09f778a41ba499941fe70e07f3cd3b7c43aff45dfc09d5ece5f274d67f1de0dbc8a302ded9ed5e4752ecb20d206de54da9aa4fcfda2989c1844d89076fa25bfa0ed35f0a27674b224a90646879a6cee22afb3e7d3c0a6df93745d96a081f54c2986d4763fe93e060c2267e0b6c562e9fc61fa352562062e43ee9b92c2c7824819f83826f735f858fcbf5c41840c9c3a416bb6103ad8a78ab182c81838eb0b192afdc6ce211503974e2d57d212cc5385641009039f5f74a9e5f926d31e0c8cde161594ee85a554e2500b83c817b830a947875bf01df705225ea853675e78d0b92e70d2affd45875a04112e304a0ef6bfa9194aede6cf9f1b25b1cc2844b660366e20a20546a3e964927de87d93e3504b4260d2042192054e92d5839a7492e94b6ae22258484a2072056e94b4959f17c44f883844acc049b771ac76fb245549912ab0654263d0ecb5cae6d0011ee20e112af027678cf741bf28b52422536045ddb54eea9bfadbca0644a4c09a9292d427a8a0848969c42312055effad3f69de1c5e8ecea8d430455269281008c3e17018043170305f007313000018141892c642a158389e6b8af403140004502e2456302e1a201a181c1c8a04c37030180a0582a1402810000502a14028280c4bcb15e5012b410b5ceb89042187f93993d85d140fdc05ab49609df91f12296db48bd59eda01b1e28d204fee5335cf61a073de20c10c282e25ccfb262a5bcb656404a527db7e23519dee78ee6b0d65ab3e55e79e4afecc0fcfef5569c172e7af9f338937b00740f9090e959b616007e45a8838a00d700320341a08fabafc451e0a1cfff95108c11dd45e57cef35f12f99236c4e07e23aae7c50fea5e6946110c8f94123bc17b04407bf50d7868c33274b0e57f200ac0ad56f94d9889a6e94049e269cd9e101fb313405600072c9b282ec13365663ab9e517a2860b3d3be7b7159f623d7bed65e04bce1ee79901838ffd334767d19bb4608f6facead0eb0d2cd94fed4adac97caa15c9460c39792636d4fb3e6ea3470fb872c9839ddaa204d0a2ef98c9fdd429d61bee9b5f56b0decd4daa4f3020d44b357bec191a3fba42aab23e575281eac4aa45ede540eb19a35cea860b63ebcb04b4227dd4fbdb03bf1a35a3a7d42daf37bc9ee2ceb731e84d1d26364feb2e7d04f742a8aaa8a07107e12c3e28cb8af61d6a42a10aada10ac12e9b7197946e5f68c201e8408e351d5e9041f909494f6ed7f78fcc71b18083172986c60490da6335046e199dde4ee047a666c95e6b04b2dc9079219213c94e7afbf6481209c4e0efad215414a49461f34d5843afbdcf94e3eb8830f93f7d2846af9f6c2a0088a40ba7534140f909203749ce89b1b450911759d347be57032614630d893be67e42689acf2f8dc6319d170011b7f5e643d0a7a67c7dc9659c48817270ec425e2dfdad8b310ee32bdc40ce7d1b023ea24f93efcf30b41004ebf0fb06bb30cba3ad0e3bf90de6e61e3f522f42deae4edb2f504475d72c1f580fe2ad1910fa2be8c7aef9ec130f7b2fb0df73d58fe31679d0dad2dfed116fe5a0861cbb4bcf30447427a712239cd726147f36211e0c2c98dc1d9ed433d8ea88296d9e36d7147060ada48d28401bf4d94c983eb665637de103b1d41024a7190aebf7bd8c40093aa7d1cd6e86cfc92646973fbe3ead7267412c8f59ba9533963f2c33c77d4843f177ebaa14da3645b5f8de6de6299ee285e3f9bd8a3759e27b40c3e32afad090a97809f2c9a822b8fcb345c13988392b04fd333592c5cefa3853e5a5535e33918ffe8d1cdf398a8534a37b662c4deb3de811f988e9742cb8457fbd0586e46defe885f7f2e06aba6e0a6a0397b92e3563d5664e4358fb6cfb531ddbc12e3e6679bd6029bb781bd0db0ac450e23cda64fd40863a5927dc292c1cadfe0bcd2e6d479b8a6729dfd522124137903d4795317896a9766d5d8d5bdd19e80a090c4f121903b96bacf6ab4966490c7aaca4d37cce3d97ebc330f819ee1037c587b6a4aa37b0ae494e9c5e1ac9c76e212b78c07d3d39151b916e6491d3cc155dfb5f0a85ad8d66982eae830306cb2a1a1cdb9dd16e0cc45fbf2c66b3bb49b06bcd18c660474a2440a30072fb5735ef6a9dd75c705405c6e34495c4db0dcdd95e8b9098012527f60bd0c6fc462d8472a7d127f06b8d8d2c83e41eee2243c47f788dbbbaf24e8954ebe161920b840b21624e94591cb9ad145799c510778b90bb8ea88ad31661e76a1540869149760b53c5cb8d0194aa5fb961586fca6ec8dcfa506bd0785482e50cc7aeef91355e9b655de81c7737a9f9566b18aca1b8e2fc3f0b810558fe8e5bad26ecb2edcfed1af70d20e177fbaa3ce2efc98a1f31bb93538c6c293bcae311df40c56efd4a7bcc31bf36771f880a84dab7edf7c8cf334490fd52f759d6da6825f5d1e8bc1819f36313c58d1306e55c5f41fba318ebdd6185904b99348beb82db2276c88c4b11014631fda20e3d8ef4197c54fb4c621dadc1863e502f45e126bf840230ef22d5e230ddc8d834e733a6996312cf85f6b6225262218ff138e12a4328921e2aee5eecb41175a7c8dae8b5e884bddcea219e67cbb1a6d2a943408d509d6a67a7d233614f576a84d6684207c6c10be23335e553682ae60bdf6af9ce1586947a7f67e55122d97cc167538b84a08be56e3de910a947a191edb50e6668b6f314ba04770551a4e6f79e9834735680fdc0a7b6e79774bc413e1d5cb7ad05938ac9fb28e87f5f94fe54de9345e8718338b938a53fe98b43b8841d2960911ab25938d0cd3687111dbf4fc86cff245b65199f71faaf6b5d1559015e38b42d5f8d53744311308ec26eee79f86a10623d8409417dd4024169ff0fe2582323bddcf441217a57a108f936e168eec2c1383d558e16cc8941b73c113910816788ba3fbb8ef98c56d9afad909232064b8ba20d3339d2389ca05fe0da9f8c4583934c301bc6eca2b50e3c3c960fe941deac60f0bb6a2931322f205934b12b11eedce7103615bd628e8351c38fc03d7ca3575d58e1fff1665a452eda103053653cbf35f501cbb51d3edbbb4748f36a57314c44876a6fc31882aa27cc2be47006918a01c3af95a790a5f33b306cb51007f1a86d07a8b4148c071b6bc640fabedc8f470be4a6cb0064023c38f5c60908eb0808c48169f4b853577a7e6b91727d477dd7deb8fa5d8878f0511762ae3c8bd0634b8e8a5a91c3f4a07416b67c07165085b2078c28d00502a99646dc78918ef9757e45135d46839f481ae61fc188fd77a5e503bca7fd8e86badfd245872dd0aefe4a1112e5198bc016bb7e0752fefa4e65d858a40e352239cca58925b16f28dd5a8aa1e109aabf40787ed27db30ef0afdb5eec62a1f89427cb0d2d95190f9d1be9e913632467918328cf1a9c952b7ca256fcc79687bf49c97e05e18d2e1c93cde32fa57ea0c73306ed22a6b651c44f18221ee87cd8f737dc1709c79441e64306f03869d6d0d13ce03c976dc9a889052ebd94293cd86e3018c99c0796c85700565917165b557d61f0397b9df336f6564e40e0eb8d6892cccce3771d2dc5b0ddf8010f0b26ea59d00f5f88953eab86cb77f67abcfc365485884e1d3f2a0de4769ec6168ceaed12027636cd6b349c16f325be3fb195a0cb8466dd0f7895ac27b47851ac7693a52fbc3a61b890788fec113fa99485240ce5824f78d08a9b411546b072628f74ad1d860c0d9dc12c6957c939fdc1e022698b4a580e037a54913f9a008228c21d609207eb49764af300f20cdf3bb3a4d2205e615dbc4a3ccfad3a4e423c86ce891b1b9df6b51c2a06f74b00070ae45a29ebbae188b1744b5e7a9b79eb868e4725dcb5761998735b77e122aa6c2d114f3f7f711877c3a229a630ff2f8fe61dcefd3efdf44a77b203549772e66db295ba709b862774312807a22cffc8c9c49a576de7202332d78a5e69c352a6e6d8295e6ae5192d5d896fd8da7bd1a7a3a41a0473e017461a4eb8f528d7c3c9ca6ab419547e86adfa3a5f6958080582f2b155d914aa4170845d6302b16067a7998fef993a10c08200d3763077940cf2d40dc283071132359d6b183dca58073748dd0218334536b1fd2d1029fe6fd2cac30407f6f2c988401eb5a791951776d247ffa333def6b19d8616dc1802667398725197a548f97f1285d6d4070ac533c9f37ad8dac47418de44b559d124afa67d38a4fdb8d9bab86cde6e7770919bac6acad02edc3bb522a1db29949a5aed1409e99d03d1610301829a687342073d4263125c8317fb335f11dfc8db34a0d1b29d9288a6745f9ac5d1dcd9c4691a0d845ec6461af88fd90297acb0ee334cec4ebf900807848d7b59128be4d28cb1f26b0de77cc88e358f7050d39c9798347b69d07b4066c04932f0d9a825ec1dc382c205eacc4438b489a840ae5a30b05140f161aaf815950e23e18d54a7b1099fd852a450857e1cdee158564d114ea66265fc756dc48d5aaa75013ed5dceeb3c80be74ae7a7870964dd8b139860381913a50e1b792e8dfd1a60f8e33d47e627ae3c2b52e2f43559d7466e0dae13dc1e0e76e0879706cb46c77ca85be1fc9af4091987f3137e34b17c1ece9f7af60f370d2b80bda2f31423c526c739de0f409ace1b227add63311bacfb91cdab7f1c6dd32013900d0641069b3cea81c1958bcc3c62ca078db3d6a69bff3084cd11e2f6a6d60ccd33447a57a804b45f628ef62de9dc84120cb590b28e24937c7864e38ce1fce84e38442313e92b1904186170deb7d96ead9c4c1b9396e707f687e334a606f74c4236d566be88143d881151adbd3eeaaa4aefee53418c48cd2b983897e80616ab0b4739680fcf4aa5046d678efd9154791a282869c5208e0bf82c9f242787e97f60c258bcfa6ed790fd6680ee067e24e3dc8dff11b637785432e8994a545beb5290ac159be4a34b69dbc1b54d8d48140210070c8765c4d90f53d5df14afae41fa8539316265e7ff97da65aa37a6e7030b45b194b916dc41f0243bc6d2fcf5dc1a5f24253e9f18ab3d9e52a799d6779872151a24259026b700240369d54ccee63eae3dc21df4196291a6720dc5d7b373032156475f510ce53941fc14ed197f19dec70a70299764f4d267bb8040882e9c61026d64adb487168a18f083066c96a6b898a55436a4be71f6ef2c771930a0d6c8888f10005a8b50d162fc7d2a901b0d45c726b65d61db0d60e82ccceb09e9762ae8d325d440b56d7b4760f2ca1452051a3c39776872ddeb30a7152e586b35a4d754e29d368846c3f7201483d09eade2b00e96f59f291f63e7040a614bde8ca343eb1a248f6c455aff6bce4b0bff9806b2b07740d739a3c517846547596dfc6170fb63cb4c55c0f9ef8783d8f483000b495d1e04751fca0568a4120cfe3f9ece905aa2210237427b2876f2ed078b3b1776144ac9ee30240bce096dd432f1a16b1b9b02c02e2372b4990469d26ab805fab27b4ca597dec5efd3423bded6815a5614305577b7511076367d085764c8d599c6f99e3106d96c8a4875334e85226082c94a8b9a651f84398838c54493fd30c56830784da99f86a72bb28234245c5a1e151a415f12ee5563ca96cd3b73d3813d0a34fc8d824bc33a97e89161710e3abd4228b7cb85794e30b426f18ea3414c90a448c2957756f50ba7dbe898675db31f7d3d14099a1ea5a25fffae36f00b7f5e17c3fc21c4cb45b9c3c89c09a21332a9a765dac9489ae25f04a5820f898cd0ecdf08ced45a393b01ee0b3f4ce351b512158fa6c9a96d161ffa01e78e6f06ff02d2d323b7dcb12bda98ccba0e3ac26ac5c84926103d1f89096e18aa8fef93a5390830f9e03915b5c3a5fc9313f37acb8e3fd393f844de1e77c96bf29d0c0aa6d3fa0546fd58fb9ddfe75db22a90c9ef3a9c46fa321f257847a137b231ce8eabed7e119633fd12904ee673243304b6291222906c75647745efb5cd4f634bbf22a521ca5135dcbc34c3c714e086608913065a9d03c8cb98024c546e92dfd1d6eb1c45a421fd777bb22f973f80de3959cd76994b3853a9bb7744faf3e81d104dc6de3b6577060b5fbacf0d6b31511abcc8cb30006b36af1d5bce6e6c909d1ce302736db03cd92c465999f24e98fe5049a21b83087505620b5cd1e36cbd5fab6dc8304905c329eb4cbc164403038a0dfcb591e3b7a351dfee759ebfbdecf4c5502bf101594740817adf5f3974dfa9f03ad4066efdb6d664704557972e8276e953abe026d749930edde307c22c536ce537d31648694fddd81648a118b77cc65a807e4c9fcf2ba7560524dc308b64cd1ca531ca4b4ad9e909feb6e9a26e62b99aa81ad28babf4b7daf439ede0cdacf178c713615965e5b9b9cb9b3fa2a1d4a3194aa15753c997372619614b3b25a9897eee74ffab9df7cf5fa8a4c2c977a1efdd98ca489c08b9a5ba74183b0546319a016bb82c53c8a73daae72ef42b531f5fa1bd9d20c62aa144c6ae2883797aa4011fee2ff788c6fd977fa4d14f996f3c93ab36c1696f02cb569ce9f637d9c13a132ec8c8e7815286f592dcbec23a5c337281a4245c45e903855c983b36080aa94b25c0b47d4eb8a6d240b5f15fb897a680314b30a3526693ef0b5daa931dfc1631bc550385ad7bec4dcfa392a8b98f456cc6917fd4c92caea2eee253047f13285bf23129c702fbca032dc3c2ab9fde364ad0c9d200bb9bf446ba4ce6dd36866fc0cff9c04d319415319000becdbb5a180109261289823da53671b50510af7c9cdfd7d78ff6dfa8879621a00a32892e0a64ce8af9e010080a6d891cd8200adc4fba1faf607df584a0bc4ec3ef4efacd5f96a7f7624505584b8e78702f2797ce13b447e34c02bb8973867d517245766a0d9a14b86ed395936e8c61a180fdbdff8a150294d6e9a29363c1ee0cc9068f37630ee8813bb182fd8b5d17550c3a0df415ba9608fbecbf49d8088e7512b5c57e01077ae1822a45ff416c07a71cc9543315aed39702f42c6f0e486d12e702e298fae6aa55a80095ed3426340041c19beea0db49b59b0bee2830a2331cb2c6f76a94aa994b8bce3934a7c1d664d4f9b7328d085bb666173d6d6e7470febf8bf347068fd331c61d09b704246969ce2e1631bb4973febc65952c65ac5afe376a617649bc59b5775e3b06589a51edcbd0e20269ab985530692c950e651c51586f11c5c7ae4a43ceacc4c455a1bc7d88542dd09eb114d58bd308820b722382f863bf9e2b92746ee7d5052ff0ddeb09a0b75c875b2fd17a69cfbd469c46ef3003bd46f33abed79a5e2cf022b5173dbda055afe3853e244877a5d85d3eab9e1108ac13ead1d11ed5433a0a3a812ce763567bd4c3a6bb4523929e39ef930f92ce161d52274daf898e1925b49c57edbd76f4c2f65e9be472e472abbc16eac5b2d7545eeaeda56df65ab4d4c48b34af88edb5687537dca4ad5c158d975dfe75ac3d759c65f37fe3ee94eea4dfb748613db41888bee0c34f74529ae80f884a0a970689a96921858ee58c84df2d729ec4761b389cf817b96e18fc63c726211fe6917500d7f0cc741e5bb1c6bbde9be9fdf146127b111ffdc48e841984a9fb1fef368e61f16c9038a352056af21fdf4107dda6c132f4fb0fa3a3ab671446408836bd3432652136de6ecf0fabdc1990e828ede313dd225bb47c3cb102ea1842df1fb81c70c33cce090ecfe837e7c942189994c4a212ad49366b5f8aa8fdc713d865152417f089a68b865a5cb174099a8e4de0ecf88e10232e8ca10ec5202968b2ba300f302113e2280796bf4cb9aecd66c5d4220db784878c962fb6fc6ddc802e0fc1ef84bb21913c9dd6502c318af1d4a3ecf2aa3b36895512dfe30d919a4884522b9545d8a48088201609630b8c2c0166d3f6bc5f712517589900357ba0d8c51c39941038eec0ea4390995dd11ef0aab39f23888de7c000260328d38207e7108ae0d8a4ff8a15255e6c0896bf022065bd47b24a910c772bb1b6c634af203dbfaddcc414c956bbe9d4f414878025034f48f1a0d2721370a1017370691e23284d40f94a4c55e7e80ea4d8ee5d94a141a3c59e4897595791b037626ed701855f744e8b69ce40150a328edf77733cf58c837f2e63452c6841f4eaac652831b296408c567304623d3616b98be5d3255cace5f58314c166ee2b96d4908971079e1f9cb530d89cd2efcfc13791350bd49e3f0576275c1d303f2dbe7181d49660355fcbbc83b206852197f97a86f35efa3339d4b00307b23ed6e6491145393012e5c25aad3869724932519e583bc363e7ba20a9973bd601dc2563ff72d4ee482e67b10e5644b1cbc2369e368d2ad98092bbad33573d1789fa46524319e954dee88b88212e0e9baa27f840fb4fc1dc8d718738b4f91c58e18d40843e12b98e8c16027fc14f72b89c915b43b6c0794647bb2ac0554026e5d4dc6d25bb36e3dabc4520e2789f26c67c3a97c0e879768ccfd48a465d1e25176bd9e7dca8ffc8569489ef345acd7e853028fdf40163320b17b3b631bfa4c2a7b1ee3920fd4043b418ab78b8d08023bee8f62ef6222d439b3dc22a07fde2d09e80cb040f85e304d463b421f6dc6c740133f848a5c7b85df8a0e8cc9ba9d7703d05f026ce4f4c2da88c08a98b6776dcf4a5a78281af14a5f9757f74af544821ef13be73209b46f60225ede10b9eddc2088b5a8eabcbde7bd8165e19b733d39f389c598c2deeebfc3a255be17b1548f57c2ec2e13629c445e8c67364c5b29bc5db054223a59788d0e25e4cdbe00a64ac37c29d11c7afb6a1f72c8b93699b4bcab6c8a4dfbbb592826eeb97b4055001be6eac48f0de06698d370f44db0d2bfbe272615901b1505ea591cce256ff0b1732ce79ac0ef1f6cf765de34a0c6a6d3322ea000b9a86a37ea36536356d693d2be6a67be402c0d5c47b85884ff6dc2c9a2d34aa80199c00bdb9b5b356e4826a9662d18d078da4a83ab956636e1657f9a64f335db474dee55ccf5bdd78f47721c933f09d2acc7ff661335ba1963d0421bba8ee311a9c9a70ef00394f16626ca5461d6df59951467434403aa9f223723893edf8a0bed0e4a2455557b0ef7b07e05baa2b0a075db59629824d07adb468208e99c70474441c6fbcb03270f62f30dbbeefdf8dbe5e859e8a6f93370bb54708605871a31de84842a53a1f79c29858f22b8bcc1ae712824fd65635300267586459498721ef2c17ad952dd054d91b1d854f39ee846ca2883fc593ac5afb26fb2e8a07cf0a124821d328f0de02dbbcd1ad200feb480e495e6e20fe5d3e609de552748666e1dc8546b5dd7b096471856591044e39df0a9d6068753c7e9a49fd125890ea45cb422ff864d945dd272fd04d5ce50f848986a6d5dbed8bb38e2ccf2f8d9e2ab1d24f57b3eca4e8643ef751e55b7f785c10bdb9db7fa12b7d5e2c87f79e3c37fc60ace07b5b3b74ec8c4665d31f9c32efb80a6f9dac05ff166c1ce475bae1ab01d8b3c9d81045e18b1931739fbee4f1a930ca477a2177949383944a82462a2265fb15fdf4bd6c802d25525dda27cfa34a2f0945d18dfc8a3460b8647f0080020d36cbb5e04940db199f3bdc3afb2b69bf916b14b4ae4abe29b6d4c96ab35c346dbc01d85333d0493e7543cf9d8f8379116728807b8473bf4bd85281595bddffde3328cdf2e58cdf7b6b5930d9d84a73ea7e639f38bb5bc0a7e8b4d02ac3412d12bd6825441f937397a6d0da4df366c80ba629c570438c4f17f9096decfc1d6d1c3b71f5568f27355f5143ab3992121bfce92f867cfd03d4a8f63dd3ac4334547edeb97dcfa5122f79c0338f2450b7f54b00b6757af6c7c8e5558060458f92ddefa38f9eaa7ba56de8c009ba7da844793f3acdf909000b8dcefcfb461996c95a48ebe7c18ec5a53688a698607f9a7e0ec9b1c4ded553d34452d7da1f9b529c7518ba849ec9a63ab8f08bdfdd629f5a133dc37fcfe938f694fed28271a7953a92dd677d4d4d5e21de4747923d01673f36bb1f057eab26644d132081e04ec3ba1e3a5b233c7336a75e40903b079b5c256625d1799cf247b2e428e7320c4c8d43802ffbf11a5e8a701a86b3e009e0ab3708529cf052447021d5b011b1170ba2c660a05360a7c410f4f749fedee45484441929119728fc010bc4bfdc14d7fb7ded793f8943e151c8e64e3422f11728f149374b09075a631356cea10b09d4e5e586c0a85b6fd51a1be5f693f335c119ddcd72499f53019c667b39215431bd067947c5337eaf88cd1732eb11bf085383069f0499714bdb127f95d5793dbdcd86e100a6d33c680c590eb9b0545deb62411378042ab16244bdc43bf805f27ab910578ecd5225a9d749fd4e31f56b0cd90d874bd99ed4d0569462d6f2492a80e9470b0791354d0b08648569d4687c8dbe0bd4718c59cec479256b796eb5c86d11ec0dbd2abc758d01513b3cb7875efbe617202e868672bf5150b618de59c57e8d1b1c7adfa66f380d8984446c244932fd8c346ed5615e3fa460d2dc3348300f945e3f7622dfede037e2d8f61121622e12776f4f4a81b0b9f248879875097261c08c3fcad13ec4dbb06bb6eb08dbdacd032c7f6c24cbaefdf78eeca6900b31241594147513ea5290ab607412298393fad525c88bfd0dd4c9b50dad13ea50facdba9d7a36850bc4b26eb557f81be003d573b11c3db6fb5fb2aebea2b608f6c86d080cf748db66e39387633656683b0cea33bb23c3176eee48870742b97b1726d565a9a8a6876ef300c2786a4e8c51f6989ad9a7be3026114ad8ad7bcd65ec19fa7fac566b8fc93bd91d43f04d822fa288c681aeff8bcc2d7575dd933b9f9bf744315abed0cb08bf94d9d410641c3e85fd3f1f4abede34877c51901322e5324039694472ce5b48fb080aa6224bea56f1cf5c312358a5394da3d2f3c4682d7982b4c461556cdbeca09418c95a42ed6939643119b0233acb72540fabfbfae748257d814b0568b2e4fb8808c4ad9b3e50dd0c514a6c4a0c6bc1ac22f06dc8aa3d4bec16cca4dd297d66ca6eed2d4107fc9f97500dc9f86ca7b1cfedb3b896d4b1d0ca803e8f036b234892083b52208d94642ab06c97133bd3cfa0049c93285c1b1572d0022ca2fe0ca4a10db8b4f10a8358b954e28a8a491c0d2849d70e260c723201494ab4730b62036f5274801351e1581c501487c075eff27c9ca86b1524284d88b76793168521022b0408f13b81634c0d331c5ae66a46d1e8aaac04bd716888195fe9246993145d701010bce7a8c2ed670dc21214c490559f8fb28cefb3b5621a641f0d11758ce592b89dc7ee71af0f1652ca16048deae07aff30e0174cfd6ba48f1fe496c4d2973af939b152f8895d15da431482637419e3757df5fb4d059224f718d3a83c5b206aeffe9a9727ce3747c147beb129850cbad377b128dec1777d590a8f13c4c309e052062780aa72190ee4b78e052e4a64af465d69d29c56efa191698f7638689de9e7ecdc556d7db1cd186413f06145b3acc085b2f7771851b342149af737a9a334c680ec8d45ac2d476062a234e073992dadf7a8be86869b1f25d680d1e2b18618f20a6713e92162704b36fa066e3ae5292aadc21f28783863057c07d0b4ddba29926e8929304acd0fd976536733741d3c0c87a235eeb2f6c6b1cb0b69c175be3a6b1f70448860a53f5a24bc0629f4554cc2faa459a19cc18e3bceb0f3e08e379609d973d2c73d3a3ed6b692dd911d400b9998cdc5abcf50e25bd6ea8d3674fab28128ca6caded58f1506df62abebe037e0426d36dc02a9467f715890797224514215d368009ea04f1c660aaa150fd1428b05edbe0d9b4f432188156c28362ca1cb9b55038033a61c5c0f285ec18eb03f7b2ffc92a404c1c0a5e6a276e4f2aaa4b0e4e086a6a6a85d07538d01cc14523ef79d6a0c82afb0a03476ff3c17caff8d864af4e1d5a643e8f5e3cda8fe2d8240d0658e42b851795b614df42cb3c4995fd969e8412904495c255425a9f3817d8f94747a896c17a394b7641231dfc77617af56310a8f565f8f8ce50fae038f12f9710afbbe237a13b3edc42e98bc578eda9826f81951bf0182378e89fbbedf155bbc64f6b62df85ac9b0b32c9944d22cec2e6948603317121878925bf96c7f6c8e7d340188fb466c8933ca257953442787baa6a42099ed3ccafdbff5da7f8e65efc76f81ef6af11e31e4bd07d08e0b873eace86049f62fa4ed77b6934cd3dbc94e119952fe8e119d61f37baa2ad39429adf9109b08d3ffb93dea239675405cfe63b1c5482242ae403914fbb8377c52ce93cd9f9cb8b3ea3b60fd393c4b000d9803fe53af806708c1c2f7f6142cf47e1f99b1d97cb07fdb84d1aae946f71037b3828fa1543e5fb82e06a8de643a406648d638f1dcdb3e5ed817e57103d881ed1f7f432ba5e9fa59f934fce77248eb116ff7c8ad4dc5326da0f84f3e630d444d625f54b9db449401bd68b1f851ffe5ecc6679d8c837387105623d02e0cf246a58f7c0ad6b25d6bbea8a9f14de3e2da9e715ea9f4557f4b40793a4f79275a50e06bffd0961635a495c09cc61c7e8e5abf6ea2c8b8c7025d4a99666a4849c35ddf754f03ad28435761cbdf353e3cbf2c2cda07dd7b7d14b6bd899b3aa5fe912fb59ce3b39bf4366f63ebd11f824b07c528d3a697cd2516561cff241721770ada7e314be12a90df4f9727c39e9ead61257bb333f70ffa59718f8afd650615bd9ce1332f3101c788b5f3aba9a5e1198608d97f6b8daae4095e89a1a29e8bb17d49d74d4b73b876eaebfe997ba735ca16a9b5fcabe708ee12b3fdf7780cb5f19c806f2829f5f85baa0bd34d7d69f4f3688a81f252d6c2cc6d56e582bdf50db6d093aab672a1cdd771c4d2ce95cefb74b6e2a4d4603ba832002127199bd4f1fd1f31a2d4cce13a24fd93542f19f2102ac114cbef03021e26df2dd697497207db69d1525d6430d306efe7f62059375d3d881df4387f033577c567fe3bf3e43a6eebfe2d5cb8228e401b936c3cc491cc3d083a4a118ef66b497ac904db606c343759302aec0dd257ad99844ee2d5da3d39c0a2f2540bcfb3f7aa366dba3332fa5b6347db2f04e43e5a1f815d17983a0dd5d7b12eac6f41e3c3ae00831ac28de5464359544f41b8399a8f2f500cc9651fb4fc5276197fdbf4712e13efc8df3290f157e3067e8d725870c3b9882d7233bd7f3f5abdb4a0e57f2c05c0b03aef6e6eadf7aa81eaeb7d284c9f9c45ff6378d97d6efdccea6451a69a866d4274bdbe6e20ef29a34dc0c4337ecdff323383e04da30057da5bb1a10c2123ccabd334c6d37860c13df3290bf84af2e902656fc00317380530aef8f15c517f97b38d4c4553a4d901dfe2f90ebe14e3c2b789abcf58818686b769df325b8f3793681bd58bbb774b8d42b9467a55df806ae7625ba01f63a1ea623faac65c60413973835feec995b468c2eecae957fa128a2f022fa4f6e5d82f3d287a46577a1f2490e3a78856163fc185fdccd86c25fdb4ccb98fef61de0ebc5c26975021ae3104d1fc74b7dffd3341119bddbaa294847c845464200fc73d02602685bef279d0c7d8bbf03c198cb83d80bd22e5014ae8ab77aadaed46fe230f442adf635b8aac9b0a11574372063278e2dbeb2f41bc9a54099bd8dcf2c2419e7ec66c53c4d59052d712f5bf5ab9b68293cb32e6b4b193dd032f1c9cdcd70dc388db61a2b1d46509d97b914a0f4fe4f70c93d867fbecd46929e0edde64c0b6fd11b7cdf8dfcebc97334fae24f44b91a05a572583ddde7fca4a8953e2550e11428ec24a4208839ef95cb98e8b901a0e61931b03e987dc5934d453932b343802a46275f8a8148d519bc9a0190c1eaefb25ad765f05ddb989eda2b36f44da0a66d2f09d9f07bc79a3606c00e3b88a08682d88b48cca9f27acd0a7e592cc809dff3a76772f64a01783b8861b86319adb44565aad94a07b3d5520869cabf792bace920f5ae572f8235b00519fdd49f75f90fec1f5c73b398c3c4b0c815752275098286be4d9a3bca50be701029428d38f80283f47800d289cbeb4a07ed1bdf628d32933e5a8fdba1827b65eefad5b87fdcd16fbe732227559a8bdd14618a42383151522ca1fb1903c87336ae0dc8bfc9d50c4ec9e02d7857a2c122ff5ffd7ee5b81a1cfa69e59483469f1fcb59988c71f0bcd23e3aa8a73fc3e808651f992b655037b13051b469c96c15af80dd488c9fb75c5139d4c9cac1493070b54c919f23026ee11496cbb6af7e90eeb0f9da6c1a577ef83adf8abc4b317e3d11bdb25dccf7f0bba2ab3d47f6f6d6937413635e9a7328b8bee0d245cc9717e5f5003227e0a65335be2e39ae2712b84e108cec709771bf5b368c6ec7c049f61cb8e8c12bc8ab6b6cfcbb59debcf593a77f8186bdf6808d0306551dacac1bf89a1b50fb8d505ba5a2e5bc801864ed80da3c703ad2345273f6567cf5b77993709c765a27849b8a2e0a14e31cb1599a6f132213e0db39dc7c13955332373cdfd42eba37e2bfac33be68c09d31e65759e78b5e2178b9e576a62e170f19c5ced8f489a1b6a407e073957a42598319c98a268632fa3d1cd140be9d2240eff48ec3c99d922f86e3ae1483212233a1c0dcc07b50acb50c91a3c08228358f45d782d744a810f7c92433280059afd778129a13d4e2f7bc54d67124b98e538a2539b2477ac17c64afc71b29cb173f3c5f281d748a5fe1f3d1f8b71ddd7a9eb9808104bf321fda754b4c2c34c605436ea01f3f76119c917712cd34e951e6f74efca8880363f1e93865c7f57bf9024052e2af95d9190cd2599e41b7519f7b6d0ac5442894a88ed740819e3ac7c7b77602e62d6edcf4f108ac61d0f40bcc058c1c238f6e145dfa0f2ce45eb85ea8e054c0e2f74ad9c55bb39c7912e462ab90bce8a4930a3d0d2b96256964286f4d3634b45548e6a3165f61ebb896d3e3da091ce4c1706b383dc6ad3e3957e1c9d376990e9b8a9f75f77c41f05a596f789746ec2f20a08bc38ff3f63d110ba1208126c6c73c39fdf0a6cf0c338b67cc16add107599c6849dd1433908c6cf7e913216666bbcc9bb46473bd32b51ead62555ad87e2ab16490dfb4dad823d6b89c63309d8dbd46e99f33f15e82f71dca6e5663877cb986ecb932d1de56f18a687add963561c17c40871bf274e40396cae2b42f863453cb901dfaaf01a489a1fc15841e494df904c81f494defaf090af7139ba1a30f318a24467b1fde36350656daaae3c835386c9e9b04a2b3bd646420c8dc00b7834d0e96d5613cc41bba09205585e669b94b70c6e17a10d1f3ad0f3fdb121f198276a5753369856f790042ee2a336ac846f4910074640f4b2693f402305b498cf144a4d2f8b4fda9b54b9e355e744116111b1ac79e544e86d6544d1dc702c801787f7e1513c3c058642cc3c814a84456e0206a10e205950738fd2ee2225055bb532f260dc78e278927ca4382ef05af1184530fb109a9580c7c24838cebcf05fe42f10c6c702a61a3539f905b8c189ab0c18600f00da32b9f3c1776915212fc28266e04dd0384f548a290b2c108208a5b3502df4eb3850236fc4c5b4054eed48c888cc7c838a923f19d27a961beb115d4f735a62dcf5a6d13e14b4ead17024f837162a600cc428fa252bbb6022a0049fb800953598f9aeb1f488c20a413b036683db29ef29c61d02e9dbae454d8fbbd76d4275e02e8ee2a8e6d24841b925743b95e6c40fb526a3b6f47a9d5b424cff569758e0ae1b1d216b8219e5bfc0b4cdb78b93e2e2cf0c0c48158ba293e73d94c8899acd1eccc3c00f3e56e4e0cf910c589dafcc03b17a6a86fcc7ae17af6e0052daf8af4a2ea2fdbdd7c53dd292796eab60184f9a48f548eba4641843d963e749b5abf3996ea49b452a18bce8634fa7e204390bd9d6a5e868ec46024078e61e7b009d5ae213b50144e43729871eb88189174131a167f57154e8f500f3a88fbd2882d6d1037051b1f5857271852998d4cc6ad25f480e48b39225f00e588e2d35814543602dacbc3993760b46439330f0f0f0f0f0f0f8f4408b7b60642da169220934c12709a62d0c82499924c2945c2153b10b2371bfe226df88bb4e111120004a809b60954097e2e9b9894d64bef6622f8f89fbeaab288e0440973919512748ee42118d5f14b9e56baa934046bf23fd7c8bcf57d29046bead3065d1a4270a7df6b3a7e6510dc9f79ced17eaf3c5604c168ebe451f5c440b0651a4229394aec27080826e6f4a11bddf794d21fb8dc7e79645091b39ffcc0c7cd6ff99fd2072eabc4f28b1bbe993b3e304a7ed2ce31b53d702332e99832c60d3aa62e20850e3d247a54b209b1abdf151d79e033e76e9f3ceaa74c78e0aedd76c42c7d07eedd44478a7442a1c30e6cd24df71eb3eef6c460243aeac0e8a41954d2f8aa2f163ab09ea4e7a76c6ab2480e9bd03107f66f6352bb79b6194f28fd24bd35f552ba858e3830c24b5ada0b716bf5140b1d70e0920c6d2a395edec0a605e9a96336154bc4ddc076e477bdaea30dfc2699d484984636b05a952309a1295407915c031fa228b3ac4d5ea1430d7ce99590db22d8479a7c816698a046165c183b1d69e04430fda2a5732b59850636e5ff52dabe22784ee940c7194c2e4aa3fec7e0191a5a093bccc0da298fae9f2287ae0040091d65e0b74e86ac7531ad628d2f96091d64e0be7af3f84d9041ccb278858e31705ee14993ae6006500a1d62602c69d276a546c7af182ffe863942471838538d1194de2030b8eaa9f53b47911e1a5a37b4701b5b58c7173839b636f13a99a660d3d09a800c19bfc5692ddcc6164be8f002973528cb9afa2ee652e90213cd544a5a636d71a3c8a0830b8c656df98ffc696899a0461643e8d802db9637a6e99cbb337566bc16cd0531217468818b9a1f723c8e97c814d40e80207464c17c72434f475f2c7039263da6d2ba4c075d175760f285b28e49d345c9f52302001c74588157dbd1765481bf9464b092639b35561d54e07263aa51b24fd7990a3ba6c09f893c955939c6a44926e8420134bcc8c2031b820e2930ee934392e6fcca5eb14581dd7ef10832a93a6d1d2830a92de99fd09427b0e19931b6a6886de23981976016df57c5c494d6d1044e9eaf7972c99fe960022bfabc3372d6184b550a828e2530c9447c517a216372130dad2e14031d4ae0540af2d276356cccb8b1680b1b3448123a92c08dda16791683b64b161a5aef45165dace064c8906146e8400213dc72c62da12a5fad34b4bc682fbea040a22d6cd0c00a741c81cf3e52b724a6061d46e064a89f529db3f4655b78e1c5df480a741481bd8c599e73482764bda8ae00e0830e22b031588cd99284d0d09af12ad8c2c68def22cbdc1018b3344aa387ddf6c408810f539a829f655fd0c8133a82c08646fd18fb7389681010981c299808d2528a12b1fd017b155f744e61f132d34ce8f0011729a5587e0e7949653de0456e322969723a840e1eb0a1156ab3b492ea2b848e1df061229795ef46fb5f75c05e30fd26297516858e1cf8d515aa112d2774e020539f3721ad4517cdc577910de8b8017f693fec53d561035e82e7c7b46fd6a6fe0f74d4800b2a82d27749fd43582cd041032e4347d698d3fac68d093a66c00575b2e27a0e49716441870c8c2316ac656526653978e70d16e42c7642e634b40c72bc424d93f2e8d15cc1e5c632215483686879f137be8b56307a938eeb26522c8bc9b72d3858c1c78e393d2b54e98edc2ad82d993d49f34bbeea34b44ec0a10a26694fcb885e961d8023155c081aad5f4354b0f9ec533ad1d77b0d1ba760dc74e759164bbae427430613384cc168ae9462f48a3a5182560a6e73f05c1da93c7fd0719082d1fb0fa1b59641e48a718c82dfb4d737ba765ad20019324c148cde34939b1afb6a2ba16043cede97d4a6855c1714bc59d250e215a2c8f13ec1295753ca47ab7e7ac98681c3136c109d22c9f3b213dc858a81818313bca87893d488663391db049bf37ed03b99e1d0045b1b62e792a124f5c7ac58173832c1f5f9bfe8eb16536d16810313dcc49c543debc8d71cc271092ea5499b3cf9580a32ad21705882f5bf0a4ae4986efad29560824e253285f429247d29e1b7490b765a5426c1a694f5ce257ed0693949b0f6e6a696af4d4d7d91e04f2f62ddc71224b849f69533a8b3cd177f04232c4fe868428e602c9d6fb808e9ddbe3682731119733368d74d9f116c10aa699b7f4cc58e2f824bd52127ab5ad4c92b8249e94ca51ca39b49cc268271cba147969d56d6112238214db38a568b55e21f82cb1075ba25568f25b9214c6a3aa29e489242b041e4942da7ac9f7b6942706a915375064b27c71d049343c65c2f2243da4a05c1997c8b582a8d49896a2050ad512540a44a9b4889932b7fe0c4ef42cab9347ee0b73af77bf2f1f217fbc08b88192c99ce072666f55411fed903a7f28ba68bef114b89f4c06627f5131e6279607325b36e0dbaabedc6037f7e514b345ed2d3d51db808fa6ba37bb56b291a030e3bf0e719218dca98775bc251074e4d889de9ccf72c533ab041582953ca53e988983930d13ebf861c52c9905e0e3930724b8b03a3e2c99437a44cf79b84031f548a6363da1bd83c39998eaa615bad1f81c30d6c88419a92a939bb841c6de0bfdf36dcf7eb2d651c6c60f37f47b13bf5f821e158039b1b5457a8cc0d42ed345439d4c0a4fcd3bd74a3c433a3ec091c69e07254b7b3131f0d7ccc794b6e48798d2c4ac990c1451238cec0e6123a57764b31d794190adb284a4791e00d82a30cab57a6242dab68b9d4aaaafd74be105bb33cb5eac041064ec7dc91904e9f255d4901fc81630c5cf27ecf331d5137e60f38c4c088aeb3d2133f258ddb488888887877777777676666666656555555556545006fe00803e3b944d58bbdae05170c5d08d3aa9efdfa02dbbbe982889f953624993c707881f174d61137a56069121a5a356c6829a594520a22222222e2dddddddd9d999999995955555555add14401a881a30b6cdd0779a6313749be891138b8c07765a6535f2d217c1f706c81b1cd924763ad05fe5cd355a64f22b59959e0e4a65aa78d6982d48a030bfc87121ecf2f7f053edf3ca70e9ef386885b814b2a49f64e2a3a9234abc00913b92742ca39e6272a30f6395a293ff7586e4f819f904452217b8a14b894354fba489e1dd216054ea59560498550275f82030e28702b22afeb3f664ea9d1d07a021b27f9b928ad132c6d018713d85431e435ed3a2d93c990718109740040101c4d60a4c52c1d64bee49a837b430b6f02071378dfd3b9743c75043b97c0a69ca89d54489284bb1b040e25b0153c527b97ea2f9d01024712caa732f7c5a1c081844448b09c271aec25701c21edfc31159612388c80c43c6f091262b4a5c05104469767c564621281cda6d3e5f9ae8f128b0c193b04f6fe6bdcd3edc520d70d7c063884c0e9bcbb9d20e4416052d84737b9ed92b473010710d2a684ce1cf53e3f607564af859aab6451ab0f18bf7819f288509e37ca028e1eb07b29ee4955c9c103ce5bb495e6f0b20817af2be0d801632266c5f7fc0af97b0e1df0612542c5bef08a046dce3719f85c1d3d35da6360d472f88f8e5e29d26260dd2bbabe47bd4b6d187851b7ca165330f055e1498ae7d98847bec05fe7a4fa2364558f395ee0df32d32dcbd3e82975810b4a25e752d7d10f6a2ef0fa41946be64aa9edde0293b3d6326676b5c0f7af258d3ecacfdb9205d65fd26be54abab4e5b0c0a691253188bc6d27b52bb07e7afba332c4ff245a811fb9b1720ce9825105ced4d3754fd2c2a0029f53cc4bebae5919c09802a33153c90a5d39c5523252609295e5a4de4a2ec802461438bb48e7e11ec3430c80010556a2df88a463a5274a0c17309ec0097fd79228292b8d4a0b184e60bc728fd0f53cf931a86d6c914501184d602b55e97c91241a5a3366dc680301cb000c26b015f92b628abf04ce5208517b2d4909ec767d4a16e92544d1a1a1578f6c54000d8c243066a39be19693d226b2e14529a2001848307810a9f65e4d787d04d6f6339bce4976e93a69689d2f6e1881cb9cfc2ea938ea193766a0065a00a3082f80410416c01802d7aaad41e6a4934aaf3084c0e77417a9a73345c8de1637b60b0564210046105200030805e307250361f88051a62b278da634b4740b183de03d33dd8dd066d5588105c81560f080fb20ec63ec182f6c6476c08f2ce5412711354795349486a3e08b195ad8b0b1851a063a1830812d147ca1850d1b5ba40a60e8808d9aaf52bf763072c05f1e1115dbf2c97c32f5c8460d3ca8022198d1001838e0c254eef5a42b86b859ace1002b1837e0d534a713f97f3b378a11806103465878fc1c64e4d03bd680939c96694c253bbdd38089a73178a949d2533d03d6239bf6d12682fa88c190019b6cd3d64242cc2926c5827fb50c225d32c182db5499e259753cf3ff0aeeb5d54a8b974c3d7a57f0bf35316d79752ef9b782cb194a5ec6e897d53e2b38574fd2625756d2e0af8213c9fc821411a982d1591777630eff18940ab693259d3b28dda5324705ff964327a5b68a9df25370133f461dbded0832a660646534b10a31a4a839a5e0fd3e76efd8c689e721051fb2489e147293e84c8f22213778b6585e8b828bdc665f1b27c511da50f0fffaa79d5b50b0254f86529a317f5b3ec148bdced1354dac24349e603d2429493188f4e9339d60256a36b7d6d02f9b139cc4e45d9eecb4c78b6e827f4f8f9643ed5f5faa093e799bf9a810f3f529137c7e1759ea841213ec69f0589f31e4cfd9bc049f431ed312dca5ab67aea045675e2ac1a7b4fb0b1a546ee99460920afe66428293e0a4e86d176591045f4a4dc34b55f64f4682895e6a2dc71c735011129c7a9498cbbc47b0216b5efd98352b5be7086e6d35c7e0a335829d203763041793dc98f72eaa3a69114c3a6b11e999f32247119c4c9fd46a6e2a118c755232d52521829469fdd9ff121a5a66e6106ceb8dd2af219488ed1a82b710a38d67d03c22a41482ebfa98a161840721f8d488aa5d29c91c7a1d04d799e9544e5af4a5ac20b84b4252b02fb1cab23d02c179f00c49b8fb0a080f40b06982169d5e3f7ff0f8031f648b85d09af5f0036f1f82bab01f11e1d107ae4a9ac4c949def59beac1077e4d35489aecf5ffa94278ec81d159d1844e2562c7cfedc1430fdce90941956bd6ce4d0b8f3cf0a6d354edea453c190088c1030fbce853b56926f284ac310078c1e30e859c44f2b012ca62460d1bfa5ba008d4b0809a871d784fb94453ec242dbd08004df0a80363f9b4d74fa2e8790c1d584d52624a692f6e993c0b1e73e0f3dd542e1d245534150b1e7260a4a5182fe7d69c8304eb0c1e716063ad774a23e4dea60e1cd8eebbd4316d87f6856f603fa4c57c1dde99dd74039fee25d5695687142766f068037b1db442bc14237b3ab181dd7a49793ce9785bd6c0eec91fa52d281d4d683cd4c08ae5082ac64bbf1b5d1af84b7252b21c3c6faa4e083cd0c0669151d4bb257b699186560c3ccec07feeae534aef28d10c1bc8cc071e6660b3738f6f68e5609d7f1918f33442d9253931dd6460e47bc6133153caf19486d68c31703a8baebff1fba082e4c5c05ef5491e112d7afccc230c5c14cd9b3a0d067663f68a21f6d60e3cbec0e53a952b871ad38d280dad193650b981871718e9f993d05a39a7a0f91728e8021f936648aa793c85e75117bd822c1c787081dbff117af12628eb81c716b8effc119da2b9fddd85172c781b5a60334a905bc2dd327555038f459f9f58468ed98c1a36fc5170a346046a580003f0070f2cf0a9496eda30d331d5bc02173bc99327575249d25b81ad5cd3759319fe7e55810fea924e6f2d15d88f94f29ee8af51ba330546fb538e933f2749504a813777c9b611c745732cd0048f28309652a55777a690c1030afcdf7da7f1d45b4a795ff078427a4ddfdfbea75df07002bbe9a7c73e53d362cc3c9ac086ae12ebff8b9be2e8091e4c606367d055e3a639c7f412f89cfd638ac493a42eeaa104c6ebec3655a3789da6021e4960cbeed5ff5c73acdc17376e645124e08184548ca22cc918b38805051e476043791042f5e7a02e6cf877910123b03184d43b19a44b8aaa166e630b2d0227515378c698f5a6073c88c0a54db0d4956a77534a430b031e4360ccd45bfb7a8ea75a4a010f21304ad4724b6d0c083c82c0e7be9afc9c6ed2c882e602089cb27611caaff2c63d7ec0e5bcb6b93374bdbd4a436b468dc0c3076c7fb9c5696222f0e8819264e9e4dd21a9e8d3d0eac28307dcc94c4ddabbe59a275fe8073c76c06871315192b4e96c86d7c842075c0a3ae8ce41c31a5fa419a8868d33a3055d78811cf0c8011b414b24cb7fd7a44728b031e3cf0c1b5eccd0c2c6ed063c70c0a994520c11b2666a33feb4a0b9d0d20e78dc80d7a4929a982c6afcb752e161033dde05ff06e1510346a9edcaca1854e9ba2b30070f1a30a24288bbda66b7d9cb83c70c3895948974a363d28f6079c8804b4a899f759069f3361932ae23169c6b90db171673042d2c38db944596499658d17e05f7ed923b66eca4db4d577029c8b210bd26debdde0aee52a7bca7eebe6296ace0d294508b1a828bea7115bce61cba4b989e7a509121860e5570ffb515ccd6e48a1da9e047c57deb2c3a968674a0824b1e4f26353298acdd3a4ec1b6c71862dc3599824bb6fbfef929057756aa74756c3dd59e14fcde77a62b9decf1f551f0e3a9a47a1c656a37a2e045976e5ebd5c3aad1d0af684461129da939e3428d8eead78a557d1d7329f607bf46e79283dc1987d129a41e73ac197d0d1fa39ca09bed282d2954dc69221b609ee3f59f5e4d5ff136982d3a4428a91bfca049762343921e9a99027d28109465f758c6595bf0493521219d46b869c5b82532aa8eb521332ab852aa108d366d52ea2043f394f8ee9333e09c6cbb2cffc96049ff1f2730a514f39ec48f0aef96a4dc8b3d424487029c55394468822fd47706d99ad4143a4577a1dc1a476ff2e13318bd8db08ae63de74a5258611bcc8183989b06c8bac2e82730f42597d5415c1018a8e1d2ad735ae54602375554e499a53e0ab72a37ff5c4189502a74ab495be90fc92a94481bf9c257ac3ea74560f0a6cc68f9ac652824ecf13d8a044830c2ae60446df7890c164d2009ac0299d24ab44697c8f1d26309a83ab08e5fd123825e2a5654a21746b45099ca79a70cdcae125c424f0a34442fc4e4202abfea23e0217f285e688904c757d4628c1008ac008899fbabdcedf4c44e02b7ddbe91821768e0f81bd6ca1b93327a54647088cce29920e4241e0a27f48429dcabc881510b8ea9427c5cf2126d34407f0035633d48e8ac498e3ee007cc087143b6e797c8b105403e801a76bd7f7537678c056b42a91f7be72657b077c28b3ecf36c75c0c9f3dc544d9e84e99a0336e9cdd18409d3219289032e7f8af9dd4e4fa4eb6ec02821a4dd8e0e3169ab6cc0ad6f083a4ba7067c0679cad2521a30a662ab6d6be5d42419c00cf87051dfdb5b2123650032e0ecca3f47a62f59568a0537aa52bd75768f758105ebe9d9e26ff0155cba27e54127ef0a7e257fc7cb296a052341cf445df55552102b387dfad974e2ab607356c931955537c88f2af8a4632ea9559d543021879224d77c458f50c1e9d4162aa5a4bb4b7b0a4ed37dd2f92e798fc614ec050f962d5f5d0aced36ffeab9c149cea38da64fda80d77149c92af17224657146ce7abecb9a550b096c33ca84eb9bf42a0603f5e86c631cd272c8627b8d14968b59c329d6054aa5215d45789a40c27f8dba0d2c5eff46a2837c1784eea473497bc10494d70932ce608fdff2892cc0413b3872426784b95f7b5339fc89272094692ebc4e0fa31ffc9127ca43f759b7494ee7c2518912628d1d0d6d2714ab0b146855a10d1b36a3809c64f4f7f85a4e9238292608398e428d1bd48f093ba762d7dd0966220c1063fa9b9d4698fe0729b7c5d4bcb619f39822dab5a4d31e41bc19ab24e99397777598c60df749c9094b69876528b6045b5781c91bb3c8ae0f53c78acde3a8d27824dfd889fa1fafc438860eb9495109e94a5c70fc188984829a51412c3105c490cda25a9f6b491c428049b43c7f6901074ff2284e0a3e68f5ebb97e207558d2fbaf0c21c20c62058bfa0829ac8ac20f8b254b735519b636620187d5b41a45229e54e16200620b8129664befbd15769d25007c4f8036fea296d858897ce263fb025b3064dd155927a4e1f386d799193a95b92a1e5039fd59227d12951b72b7be024855e6b4ffa828b1935b2682fbe0b527ae0f3aab2b4e5185492280f5c2e2de9447d48d6b7e38133c99f4a53fef22bfd0e6ce4bff4bb14836ba8edc05f0ed282e748cd7b771db8f1a819bf364307765584c6cbf5217e660e4c4e95216489dad44be5c0e58df5397554a8d2548c38f097b4f2440a318d52a9187060734a9e9c4e4571f53d10e30d6ca6d320593d7eb4deb8814b1a4bbdd66b7c4515a30d2523530c3660f1255fceba3eb135a881b5bcf471c921758e791a5aab31d2c0c61853c8d7579df5a91a349c3b6ee9b6a6b36bd5821867e072debd29eda14ef54968c430830b62940165580c32a81631c65019882106ce44e7b8ada12cc73ad120168a1861e0ec64b9da69cb231e43436380210831bec0a735ef4ad125777304c5f002d71e512fffa95cf8070af1418c2e580c2e9c0c12c4d80293b5749241db739c2cd2025bc182cc74ae6781bfbfdc5d971b2cb0212125bb9390185780410c2bb06361c95f2c53aebb1610a30a8c8410ef4d086dd39753017d35b6fd273931a6c0785ccbc14c3d2930e95d82487f7282ea244614184b4a9b8aa6eadfa650606375c5cad6ae76227b029b72c88b219bc71d9140218613584feb12dcae734cbb3781118f92f527a7d17a394c60f344eecd1de51258ad111ad3378ba1044e5d9749bb6a93617954a3679c756ce0011d352cc0010b7430a043868c1849e047051594a9f691c0e50f31f9962788946268a8d517336ad8b8da13c438026bae913b7278a4d0bd11cc97c12d827b2996eee019c620023b2955ca3ca232c44c8c21f039a76adb1ea1591154d5b181076c208610f62cf3149558b142f08e202726cf62b142c960f1428c2070d72552c8feaf2afa03815313a3c994b2c5f823af007a2f6e7c8d195584183f60d52c8936ed4cff4af201371a5f2b5a05d1b6a51ef09eb74c5707dd41e879c08d92d93772324f39da019b469f103a754e698374c05a1a95354d3352c673c0f95d4ab79b83037e5c42fa902235a4f40df87859a322a3478ea10d18b5db2c9df4bb4e35e0def327915e826c9506fcded5578a60dbe6fd0c384929c94ef1c49001ff29461112d36616752c3817cf946458ac4f5258b0218890cc35e43091be02156a94aee0c7423c959d72a790d20ac6e37e32b993f4eface0d2ffaac79369f256c178a8e9a82f524486aae0cdd5f53b2f89578752c18fda547e13775470b9ef2fff6f3f055f6eca7f73ade9bcdd149c9bcc67275d948ea59782edac7f1d8fbd26472705a7f39de698b25bcaa28f82b32472907b49720c2a17051b2d681ef17861e93fa160bc333743a96592fb01055fa3834e29e83df5cdf90463daee25db554ce58e2718b5a126dbd4e93ed5e9049f3c9ea568e526f19b13ac88c8961c73125542bd092ea4515242cce37fa3d604a754f094ee2fb5e3d9996092b0a0a654bed1383626f82454333b477555ec4bf0d95a841ee55982fb12a9468de5d4a6aa041b53b473d5cf6d0f694ab0a11ada6fa1448ea64930d937795e1a15a9aa24d8504148f3eb4d7e478289eff5ed99bfd2860b095ee308113ac4903e8fe0276beab2acc935083982aff5a4d147dd083ec6f5ec511546b0169eabcd33b30836985f66fd15c1e90bd713e9a389e0f2ce72cc1b447bd02822d8b72bcd9292758c513d0497d36795f0f4a64c450dc124ad94e3ba8eeeacb4106c86a8bf951f42b0af7d25c23e575acd20f82064a84fb79e269a20b88935d9e464ffab54068275cf7ba7418858b980e0369bd24a124f7abcfc81b7bb98aa5979d476c50fa94b97739023c53eb01f42bf7f05f9c0b675f4b168a99df43db039e752263fc9167deb81d5f5b496b3bde5f3c049959cad43e814c4c4035f4af443f6afb6e8dd1d18a17e9544e5d221c70e7c7f59e6f3182b5b07d692bda6de5856f6d381ffaacea739c6649a9e031fd7b6d6d52f932b077ef33c66e64c11931a07c63462f5a7dfb0bd0c0ebc4d504f4ff91e43fa0d5ca8ed7463fa7e2feb064e83c4a452ffe93adbc0a5082a779299620377d23fbfb95a6be0532b9426dd15e9cf52037fd2aa3b9de97419ab34703f7e49a96822d328a18153319666b98954317f06467a8ca1cad754dc1c33b0d716b4ffe7acf69d32b061417b254b0a88878a515cf75c271020077ca911ca2ad89e0002e080117aa571e448f2f49e1bf0a324aafe062bb19cb10113d577573c526ac0893e0db943bde6f4200168b097b60aa926be240d0808300336d3e5a5af0e5252b75c74177d812e6c143c1040068c5c4d79bcfffa88055713ff2659b0a4f98085114b06cd75a65fa2a1f50cf8d4376a7040868c2db678cfaa57f0aa9daab7f482e7ccc12b067cb882fdf7907c726fce9f4c1bc3472b1897a0353187fe64e729317cb082d3244564bf9fe86d57c198ec241e33d737a6d40dadc2872ad8111627a5ca4a295847860c3c7ca482b39cbbf72c2da76a0b0d2d1aee45a3a0630313b0000d2fb2f0801d3e50c15db68ba46c7f62221fa7e0533419849efe4e62fa3085b93da852faddff28052fb94e6fd494113e48c1a878ae273add5ace75146c4eb591275db43f49110517d2f8c91cf742b2988582d37f5aeb49d64e0a2550948c4fb0ab2de2af92b286e47e788297981e41da4eba738b2cb4e804177a623ab4f9c1095ebd46cf44e807b9f8d8049772ff8d0e9db2c58726ce1f5c4d69f55589660b3e32c17906ef3121646a8b7cc10726eee312f8610947941ecbe23d41e9faa3125cb2cf561262f283126cb4bb6f4b9646a5fdcdf86312eca4ef0b2982e71d49d7c8a2bbe00f49f02942564be5a548f0a5dfa72cc95189ba4382c91752eed5f54d1a9ef45d5c601fc1dfe6da0b31d446929f1a5fd060000796081f8ee0d24deed01d3b430e79207c3482cb4a9b84d29cd3c5d41a1f7c3082cdf628f6ebd913a2c6061f8be0f26bb46dea4deaf50fc387221871afb869fbccae2b89e062fa66ab389e9a7db3850f44f0695de36f96a02989aa858f43705a72e84abbba11f23704a7a9fca3bf67de695a08bede5cf5edf253690daff04108de64dcacfc397a2e8f61e16310eced4f1a95b46f6d4c3254f810041bed47c93da14ae836104c8c21a95c9353e7f693133e00c1483f31b517d157436b02f413b08f3ff09d822c992688a949af014df8f00323f34995b4d90d267cf481edfcf96e4ca2c7e8f61a3ef8c05f8e79ed9eaafbc8e6c71e18f563d933069d0f3d703a676e917a31755ea81f7960f3e432a1feb992aae4071ed888ee9382fc1c3d0ada6f98eae26f9c013eeec0ff9ea6d0497bc81c94c65f0533bcc8e208e1c30e9ce95112f2250f091f75b07459ac2dede8c0a435ff5093318a90d6b0310305378c9c1b5b20d6c71cc87fd13565882439f01352564e4af47130b72a56e776d11a97f0010723a95fbfb20bbfca0b1f6fe05ac53c567255ed72ddc0d8969e9223d936709e5e3b87fedd47d1b08133af095e3bd235b0de56bdd5a174688aaa81d39fae2b588d6895681af8d3123a2ce7a9117941039bcd5574b09442436bc68c1b4da3d8c719d8d321b5952c8bf91e3370278445da6429c224a50cfce9b411af4939e51dc9c0954c5abba23f06ee2245b09a64b2d23c31309efa1b270911db932a59a05c054230230035f80803a79571530c9e22e6a081a16af0f1051c7c7881ff209397d2f9f9a1aa2eb01722a4cdf38a633ab2830f2e309e2ba4cf132ca4557f0bac9b0c22a59e5d588db4c0859883ecece4675a9e2c905de4b548d0fdb1b447f8c0029baad74166c8a7a5dc2b7042a83b9dcd532726d70a5c1c2fd939a90a8c4842e4bcf52483245181116da15d123724efcd1458df49e9358ee8558991025f955ff2c41c8f9d8f0227725869fa1dfbb82a14188b20b6e933e9ce127d02979744ead121e904be4695e851dffaaf96263022a28bef7585097cd6ea6584ac96a7bd25b0e9e6d99a9a5242d1c255d484dc5e8d38c21bdfc51733b4509005e9f09104c6d2ebf5865216621324b0f143d27ca6348ee7e908fc7fa594da825241dec9088c6de5b23b8bc058d29a2bd9ae5d2c44e03f664e761f9596636908ac8905659d759b3e2d04b6cd22c44add36d90d029bf774d09b2110b888653ae9f67ec07e9c9883854c318690f7019f5b3aa98ca61e70a5b42c6948e201bba2cdc72495ef05ed8049ff593b5bad034ee84f1b95de31883607ec95eb284d3138e0e45fc6f536b967e60df80f1db5a516b701b7a195da832e99a404d58017af3d3bed0e0d18fb700d6d25a3a567065c275d912382103ad21f3260ff4352f34ca1f4b8b160e2a6f1dc51f4b26560c144bbeb88b7196d92af60824cd9c47a62489e3aae60f36204d99f9364b15bc148bc1d333d2bd8ff49124cb555b05a26244fbc5a15ec9ee9b67f5d2f39e95470b232248e0c6954307133894a4c9237fd5370a63f597fbc3791846b0a4e62e7ce3ababc57dc52303a6de6d58c99e93d29d88ca294474b2a6a8a8f82097aaad54b6944c1a7592edd0f6da231130a6e3492341533030ace94b595ed464fd2ff041b3c8665aebdb75c7b82d169b2c25fef4dd34e3096673aafc909c64e33c4fa897983a99b58c58457525f13bcae4ac821266526b8ca08c192da60821fb51cb4c8d8a7ca7209ce3d8b6d289dd2774c4b70a142c3fa476290a04af05ef2cac5dfd3590e25f84df5936369ca24d81077fdd7b22a77a4488209417e16d5b613dc8391603d68eca83a2224d80dc1fff3d4d73ffe083e3b27d1d9a3c7fbac23f8e029e3244da3926d04a3f26a9a2795392dca083ea6bcfc91acff335d04e79f940c6ea722b8ce2942ee9a07d12997085e3d6a3479d623825393bed7299e10c1ba437032f95db06c396bc50dc1a86c21c44ae942b02958e5bd20b4b62404e39af5a2d89a67b88360e3a6d4cd0fb611a282e047086531ad66816054e598124f0d08364af2abd320f289fb07f6b2c7107488d85dfc801239ef4f62a5cc0d06d007f66aa4e5cb27731049cb072687faccb9ae928301ec818b5bf9b6b2e5f5c0beee5966a5868dfa5ca00232645cc08b01e481bd743aad650fc2039fcb34e987caefc0e8584ef2ad62c4341a3bf0aaa72425653a9487681df8243a56c918547460b209a92a7952b41cc98c1a690617377a0533de8beec20b1b5edc7d170b18c01cd8d611f23abf993c213930d2f4327e7be7912971e0d2c8c8232c75cceac1812b4b679a5755f3c4d21b38d55f5f4963c80d8c0cedbfb20db5812f53e29dbb3f6ce035be558a8dce1af81aaf68f92d65b5243570a2a3a564224a0317735e771e65a38137394affa7bdcfc0e468da1df33503a3fe22a83432277a956560e3e42b8b9184add964e02b9ad25da32963e0329dd6524fa318f87113e1d90c035fe9473db86b08fa2118d8f6d1252b646e8ba32fb0a292bd2b2466858c17387d8b6a75f9ceedb32ef09d51b36554d3f2b0b8c0b778a8e49fc452e6b6c0efe918ea26335ae0bc73a49379d22facc600b2c04a0c3a74e678a1af3b16780bd15a3af54516a573052e746ad3bb7ca19e3c56e0242799942c3d19afdf2ab0950a8b11a8d4eea6026924160804e270381403b1e9ed010314000000001413c742a14820908455f40114800452281c3828241a1e1e1818361807c6816030100a86028150200c0a8402615030201a4403aaf905cdc8200a1182db1ce778c61a327abe50ceb843e2acb7b0b269b45ae9c6b711fc689a11af0dbfc9393a5cbb8360edea843fc6be24a486b549cde159628eed8992fc07fcba280c4308828853c9f222e958fd16698d5b7692d44e3deffdeced41112a38f470d64fcaf6116f16d197283e98de2921d840f61fd0ddd59b462cffc96d7997500f40d2031cfc5c7c594dd57ce904df27c1477ac67a7aaa61b7df6f927cad393bd824f39b938cc3d9bf0583bdc9a85cd532eed4e2cf4b73e15aabb7aea75ac32b488c0eb8267ce32cd698f425318372b2f78ab72a47ad9504911f7fc21cfa404369497844e4913bfb2ff05eb812d722c7c0e351ed6e52a8f1a73ed228b3964f52f8998ebe06550384046311721dd29c30a46a6795d409198f7fca1235601a123bdf58f83aefe14b70f21476740c4156ed53830415a7e31591b395b3763fb1ee065b33d42830eaf5c53b5a6598988673c2eaf54543a5e503a2c652050988a494446df3856fbf01f67cc600cb50bd1cfc908b459fb8df33497229945cc586333a9f15ddb218348993f4b26d96462a8f2824ddeca0ae903b77bf031964ff28bc90ede3f1b7fa7211771a72632a3fd16eb6fe1c851f23c1022ca9a4dfd77858ddb9beadf1136082cbc7421a8e2c16ee067f865f145555be73a52838146eaef16b1ea7c657bc032dcae8c9f8854a8300d0b07c0ea3e5875e4cb97382fbe4b27ff72241256e1eccc63b7f55e58055b0e90dc748d3a459710be42bab91496d4fe4b2d9e79c427e0ffe450842420a87a0bfe9d37c677bf7cee0f7c4a7cf4d6687f28c978abcacdb1ab7820f54e82d038fae65a237e5e88101924e3d335ac257b5a17034d674a621b30da68fb0858054bd0794633b633a3b684203c505ae0d4513e2f37c004b1d33b2c7c65ecf8eb4e44f845eb7768af33fcf43a4ea1e86409178fc943af49af5aab08d55dcf3404a93af36628eb4a7fd304adf5058fc4c97d973df0183f989c91a0cd6b26f634f0b26f1a5007f4bbc3f758422c698bfa1f794a3aeddff6a82835ae7d8e2846c8f91ac5c465f07b95b46caea83a356e8a69a40b5036f711467e0c868f6690792512840e6b9e558de401c9a5f042981b7789308e48315a3aece28e90c94fb7a08a18671078ce4fdc0d485c4d3ac701fb40d7229fc817b9de8cec93e846254b33687f617102c6b455aa08a592f3f0b31af5d0f4fcb7c9e30066c079265c78366838dd58c56d4dd876998b4f4315cd3045bd3f45b6bdc6e781daa8d51a699107e010dc2a9c468b3eb13031a3a13de3de43b8a47d8c355032b9a7141a53e92a1359cb0bc3059e6c45067bc157e80871c920308e4ba025823a4b966b8b508760b01c594f7c1d2a8a053e5cba73a81eda523d08391e89d1be3515ec6ef27a91aae98a215c00e0e7eb797ac463d5920c79453d08d194f2f1c58558eb565bc8c7c3307b6e6ea0bdb28780a17b006754a771b53f6648ef5c9548803819ad8c23b56a09f6b125613886a5bc173d8d298f52eab85b90d46557626edf6df67392b3c7c131a1f5514d522b56b73e6302c275ac1aa180e480206105b66d0ca0c1a9b07ee78b0ffc95570e7c51051aa094700abaac002e04a042e7000450b271b08f0615c4158fbf33a69750764177863c1906d0678cf5b869c0fbe50279fc91ced55f0b4d4e290e642a75fc4cd07140982294c39b3d83d350740a01ce75d7a154df546a886077dc566234f0a3568f10e3ec988af19883a4eb9d33ee5c093bfe8535db65879c69d9b27ce80e071e80b814f190759399be01b40bc01847aa019a1ccace809280661481159d3099a81289e9a47595160148e40271a10f887df4e59ecf32332fb025200c5ac5478a6cb69d0ddfaae0242234ce3d440b456472b801b03e1a28385566422f5de2ee8aea60c8e959beca450290256c788dece5acd1534325af841a5609f7bad35135cac6627d7cbd9909027677bc58d1d3a74960fa51a789a8df7738c40289500157218ac756027002226b328d74a610168bf154c5d39a971d3086bc35706efcee869ba568c65dbac59aa9d9132fafb467ba8ae15dff5c472de8a246ac51309e65cb4d68e5ae1e8989b37b7a290b5e2dd0ae6355affa59eded24ecf4de3ae326898b5f26b9c9a28cda7ca906a5e6a67906534b44af3b6542b6c6e9ee540a1af34679dd5d0723a755757d0cfa573d767aa158c10ca5a2ba847414fc83f1469f852eed756d6ae512b125a981882d83fa38cc31766ecba9631ae694fd3bc282cd9c2c07ebcf652efa607d67bbf325ab981e3275a0795593467c56edb279f9e5bf1aac66d63ae8157ade8465e19432dfb514f8d38ab823e4b8b623d01b0de18215ce57c1b776dca95c1f9908d576aea6971fb93022b09b5dc96a1987bb1e6b62a274b582e06d1a4f1a7c2445f9d12a2e103ebf05392a69733732934e31786b3b4e4326473f116eb8b39399a9272c2e778a5601808bd14cdb6b6ae3c8232f6af6907b4163aeceb08c96ad4c873e76699856646d3ac85cf20f53abc6a780a48407d692032bde7a0fd00e94d5df2635b555f2d98931809f0c07b466d3beea8ea388c4c5ba05d0da185e91ce3e8db67a6bb1bfb6ee8af5cee056d9d44c833214d0b0a86c902d44d3763946adb0aa6c57994d9375b05057cfcd0a3719d4b8f4d0fad1b180a3d4c9b8b9b56744fb04601410ac39626fd1ca6dd93ce085733076b360d2b5cbef0fad7b7f69b8528171c784cc3a4534fa45f46b45e3042479fbdc405516d591bad77634074e614d0ca23c03221698c24454a20817162a09e5bf1299a088e7e211b574edda336d3a6bd402f45d46ac027ebff80c874ed20632668582f6060001f03d4deac9cff9ba259a08b5fc26da10090235009641871b030894ad91646dd41cd8c018e7d4e349765936ef84c201640fd2a02e8a41ea66b517b6d991725539259503eaef2908d2feb8c4ef63f2812b02d31aa38245cf9cca488757f262aa8c9da6da3811535a9f0cb9ed9c12fd1436c6e547773bc39b32e890d9204c199f1174a6907959b312284bb1d6ea3279b318de908ac409b35e96698d8556dc5ea862b39c5e4b4449c19279b8ccc693336acf76a55cea993335be65ebf0d683366db2d03b6960363e6cc90310da8b08d75fa506308837637acb736e56d54d8c46d136536d3b0f02d481dc42779d94cffe6a69c4d72367046d95cc29b3192e85083d7cdf7c51933f9ced22ada404536d32c6d7eb2f81b7e1c2f6a53244f823b51c9498f9c19e4f644845443bea6280975b3f8de88909b6a4f5387da73f8f76c844af760303700fc3109bb6025739ca9d27dc74bff7a579732af8bbbc29d10bd55b41c08b7d0fd98d5f8a1d282500c9ac6099f6fa84dc822040e62215043955e8f57153111e9fe20c8ac9719eea700db98f838e1641ee952e68763da9387752dd475b2b22883802ca7847ac5404dbf309829a107179251b4e6be74ea3f856b0caa8647a407ac8b253f996456fa690b19f7686a2a1af76f57c14776c5611970bc3ffbaa6dd1e87c43f10809d5a3adf8977e836cb8d739d6d6d486be8f54618d6184f7c227c13aa919fca89e158487a9926fc84b6ee15efbc94a1cf71383841fbe1bf9695462b1f8cc8e0d28ae5d9ad848146a32b1977aab44a90c55a1399b8800fab5261061af0687bb9a346849cbedbb880acf0a67f31722d0da7efb23f4a90224808c7bf6f2cdd8a1d3cf87c54e94077a2d9f467cd6a5ba2c13c6c004af8251bafc8de92d6a5bdc7a05106fb73bcfe840d68258d803e32516e48241704cd0b2efe9c391f3ec8b36328bd4d11af88f08931f26e14d7b2c2d9f2cbe5c6f854dad67b8366e4df66408579b61bf76b6b9781bb31280fce5e018b6c4461b3d32c34f7c7d122909080ce4cf2f585182cb98845c62a234c699c64a696dca733695143508d428c16072ab40e9fd361621490a39528860a296b6374321aa5d1a2b924df82cb66d1f884aa20d20f1671ba4cea6c25a542e22ce4782f2e2a09194c9a8550be9fe41ebad3baa60228487a07f6e240474b27af9c2344c55c5730661c90bb85a3290408504728053ff53c129b91bdb55b74e0eede061b26f14fbc72c39b4d6c3e79d1b1b1417dc8f10bb8fc129c1d5c66083ec8c9480c1030a0a801857a7005789db26fde1396c7944827d900d67ac5841716cafb1b06254eb235b35850dde3787576fe2078b6f041a9e367404b9f6c9abe87a2387149e1f9599d4d6a25036758d20bd8f91dc3fbce98235525adef5be39e18af0bf70b5861b5df6a2b961e1342158b01231420cc97740279a13a607e0506e980cdcd8f8228159cf7b14994346601131c33116cc8c77803ca82480b8ef1887ab0a97df1bf32879315e82d7c3abed257b01bd582fce2bf37a79355eb9d7c38b2ebf5376e6d6647af80292fd321746029bc442f4322f7b3b3c30dc099353c283957df31c0b0f571081f21adfa1a4f0d8a62782974851d217d953f3be7953de61e27d160266538443b0612630cad73e0c086cf61d98f3b1d525ef0d61630a3c22ed5b1802853066190116f17af658b32c18f605cc33b68061bfcc8591c09a1c0f8a1fb2a59de38d2030f88ec43c2fde04dfa4f048f863784bb832e0e3af09d709147a355cdce1c2f9bfa8e4a0eede61a5bd39980d490fd78d3bca6fc93f3714e6dd69cad88531fe25518c4779bdef17a20722628c016ee063fc526ee20308c600b2f3d7b1e421d504433d6383b0172447617855f9787095ef200f76f8dd6b10f4854b050d481e598b594c179845e9e371d0faeb82000ca6c28e551e160921c6974e1d84168afad061e31180b7c9883f62d958f10e53472fc5165986aa3cea706ed3f64f30609c94fafe237baaf4d45f21a2c923c259cc17e6bcd8ed9d23cf4251f804d1fa7c8457ba091f76413cafcc4a1b7601c10eb56c189b16580e77b30700376bd54e11e34353b22609fd4a8c0101352edc67a223a219b00ebf2604cb43e53ee571674746ce43c2fee137d86f289a837e5292ee7eb2fa234346065e4f576852fa1f1bfed9d6bb93335e25b105524292a98375f4729edc9a13cb1220d3eca399b2fbef2da0e478504f1f7f39545dc90a00b29d7d00dd365b2bb5c2fda9fa13f2fb4fd05a487e48f17de2c0e905424610eabd0fb2e3a4cc9414a527bce9eea29f19f68084fff7ff61bdc6fa333661a9b0fad149f98e55bbffe5c1640ef53166292ac6a57dcd1a828f30b5714caeaa3808c8f2e53a9f75e05dd8773b673232608dc12c85ec5b64eae067e00a3430602ef25870002efa5cbba7e8c594e88065fe028aa90953a4cfe7f667a7f02639dd06342410c2717d97b597a2cf85640aee211ab116b33e74144f0b32d487d0244002deea0af34176e12893d52e2921a52c74c41e0995d642cc84e4095512ca23940742b3cd9c0534141b82465453a8aaa11c445d4205844a62e82d5a05950421c45d0acdf64f16ef17174921ba2774e9d05ea26f42870d5d46a23752d1cdc1167ae7a112641864a87ef16a9a17c26828064539408d0c87fb0a951005d0448422be45fda4e82d991306be11555f43b91670f121930d9540368c08331a5420191a4bd1e728ca5db88464704d28a7c169ed65a29caab4d2e40d7a3c3504734abbf27c694e606e802afb33d28006d9aef3247283daa311aea72e9261dfaa1c1b7c7380625c5b05d084385d46afb2f4f58f2bd355631fef9583c2d1ad6f90b7919628a23a65fdde0384eb59babcd03ab987ba9c25cf436b83b51d0021d42265d5cbd56f8d6bcf1c07a690819bf1362fddf9a6032a30ec259b2f5dc8c3d75adc754ae565999ed26dfecabccd0fca755fc50c74a11ce04df0c3174eb4404813025e278996649206a99a49e10d73b82b17b7274009f549744b79390bd225d5502d6eb167a1d81601d689b670242e3c2457b25749d9479dde3d4147e87163a499ba121b4441a1a622749b9b9d890ff1640e2af72bbec3ae62c153a191f83853e03cc0c7cc0db0499d701ea837064f441d744a77096058f6ab00088e2ce5d83f1591a3e2eadc91f6e0075a30ef7ca0aba9a71780c2087010fe28b2178628c1ace8e234dcdd81726295b5c6d930eedb0062c270e42017349ae9b19478a82fc8cfab5368b952f29dec97f12cc7b228d865175e6da0cc869230d7553f2140b987f80e2239b0f1c28c61f87e255034306080108adb7c4e0d0907839cb4228565158594d2716542948b0e2ac19b94401ee15646b8b865b45bb4a2b0c6f5487cc40d4f4d104e5e1d7908666db687550c1203ad97f0e2dee18a29774805c710a8a211e72df61703d01613d5f5da3b73174d4db4bb1f52ddf9d8e42b81d2b540ea204e7231c7cbf54a59507731209e90c7aecd650e0e5af24f7f0f8a82dabf78992f76ae87d91754b808c95a8bab247a25a341f161aa85610e24108346922226455585c93f6ee28a831427526976ce871fbd33acec1174f89fcc72501711b5b89c5989b821a49b466edfc8b06408881d4da7f13b1b470438ae3bd03722a5d7ed0becfe087a8be060e2e3a780ebb36fc82f97e4504f6ebd651ae12edbff1f2ac61d5bce1590743d7ae1b0913786c256d5523e3b2af8b09984306c60029b4c04315207a220b39faf69320530c9ca230e01f8ae59637f89b9a926dbc683c1dc43b58856da4a10f42432d7f5ea2c0a88d13176f9bfcdb7a9a64ab48c8abb6e55ae88271adbd71be52eccd483f22b105d1d908bb9fe97054b4dae73cdf837b7532cf227a2a121d2b06c32895b909f06603964880981c7a1a5dfd82c605094830ad1be712512053c278eea78df25e86b63e173a4c027dbb98ad81a30b6c55eddde559bb902d79a6a9a79a3910b4302964cc5938c47f0608afd2803a68b0bae471430ec84cf0505a81352c14da711b20048a20a405606c8291c32b898e5431164f938fb8f318462224e1fdaff6293621b9fb6044d352d76cfcaf5545606d3fd93717ab4005b3b6eb662604486c7301c4bf0d1e093e80dffe6e4c1f4984d12f4ef3d743f1668c32d44209becc59b0166d764d2b4e076b7107b556e2d244150f3334df42a90db4e2a40504353d9b5035f79f28253efae57b43dad552c31da0c0cfc0a064b3c02c5269b7a7ea0c02a01d72cb6bfa423ad33e22d19c104528ec2875611ba56f9a83f0523700632fda6ad994347e501529a262e66cce08aff5a68073e20f01b7308010d6e78a00693d3dd93d9f3cf28dfb8f2b878a13406d6d96de798d1d8de3ae07d99ac3d171097ee0b51a3b9345ee2e5d5973209329e67c82e4b5870bb24255b27ae5ddc668dc7897a656defb5eb5c2cc349381fe35da8833dfb608bacda8ac0ebafd6b9e68c7fe3db532422a137ed34f364b6b000b717c3b0daee7d7d5629952de86073d23947b5291ed2f69a1b2b8b967598749585bd9e9cc4e931dc5b2bac20a3769413d185afd4e915ddc1c2f5285790b2c4a150a427abae82e10a16bbc173e1b21a2e273bb01a00f043b81df8db6d6004cb6aacf1c276acf782682f7142740227613e115a2a637eaebda0bde85536b9b3202d3d15a597f619227419d0a66569c03f84b56bafc09d1cf24ab0192a721e9b71373b9b06ddac8595e4f5b85ec580d116c6602daa5cf2dfac335d17655c0925927eb7e8cb643f09074afa4c0a2a296152aa4954cacde496768044f50d627aeb5e821f5b45de9433d9ac841c27aa855a5d6e5b4854672e026deda2e968c27b701149ec71129d031b984c20a7d6f8641b621fdb3bd9f62de3319f0f7c43ba80f47944681e4b1d619cb3cdc34a7c2c912049eb858716f23129f4897eb320bb7d80125219404eaac14559bf204b1c12b890ccc32bfc98a489025422f5c0f623bb908e04b294b8ac6ff300e1da8f13ab3e875e158fd30382639f6c299140fc6eb63fbf787ca54fff1effa9bf634530a3692d74cca210323a4a954e7e36216e94a21897704c132b6ffc657a7fc9df58238359839c806a7aac2434abf1e5ba16fe0cc0304a166a88081114d4af11b1aca7541012967edb09e922cafad3a56f258367dc2c61b3fa523f110a82bde03e59cdb7b54bc54a1c4f0aa9496b737011d69924394020645d6de5dcf9d5a38790020b45f4f26a66be5fb302d28d7c4589eee5ad824c8690b398a91371d7ddd963ec28acb6e8b9105f3fe934b3382c5a0699cff9396312b27011800b6a0215a03b6a359a3135033f5b1b7343659c39ad54786febb404c50fb75cd143eb49b57f3e1c4aa21d98cc22a060adafc9f2174d2bb1bf471263491b1f5bd5839851e66aadba49ae528f34f19731e33116e67525edb84b8b42feb056257adeb11f0ac592923c31cced48e7e5e97dc5929ee7ffd9a67342178f871008c461b120c7165e9c9bb0e0bc020ec9cfcef7c97107156a5a2e652c2f440e6a6f0791379d91d253ae1371f3fa47b67ee5c7c224afc28f121941e9d7994bc36c2a719556b824ada85383fa5012f56854138fab55eab15161ec81cc017066f5fde63cdccdf7a5899e8ca1b5e2e890fcb61ef4917186a1c9cee50bdfe8535f3d746ca33e8fb447b6207ee6541af91c71c9f34612e81be8b992a51b5a732aa08e84af5d5c30d78c02c4af90a7bef8a8da822f915d6033816ee8b4dbc960604b2d40c1622c7bc63b90eba3d6ae783d668fdac4f22d592f90bdaf79d4840907ec12f1a5dcc0aa8dcb9555e1a8641741959713888e6fb341e0ffb89991631567cfb7bac6146147f1257fe4c74aab616ffd60278b5763c508734e209ac89edfc0313c05af26439df61ac9ef05f879ba4f8b50a6cf82ce2c2c2deded88e32d205a210df32e010d2dbeae771d12fc02e3707e7afda2b4f5db4a19bdff161898720d0ede78a1dfd376637548032877318fac9310129cd262d995401252b0686dae6297fb0d6c2df8d833c82d27abbfd330b8a2efb580966e8d63d18198afd4a1481efb72260a8a18bbe53de1e35cbc8229d63529ba14a629823e617eb42aa96c160820cc5053da0fff77f56b04798b1b1b60424521da0464cb36da313d6470f53b57372cdcd2d7e08a5a98d50199919ed36f3b5d3b7c2008e3f409619042f18d175808384031f29108d9ae46ac1f9b5f6b2209cf363e4fce67a15d68a98c21ae315e9fcac42059d0421df9269bfcb2856a02b3f4a989de4a5b34a75234dd19178b2ef0e2b371f4f3a5d43de8f6997440b4a923855bdad261aae37fca4704a6b2449ca9e8e25ee03dce4d268d0272881153951e4536f2af1d4d61df23179bd0393bd4bfc7c336841acd7cb3d10b94e749e918776721fa06382e29d534d01a14db7de5dea5f19176c45c1101767f5f42e767e57ec9d1e135b44eb920bfb7b07be5778bc654e23b857fc76450967eab65ca5a0430489ece93cd4afa1a62ae58207965ad791d70f405f1707cd1286e29ae0a6b68d6c1aab5a7c03eb92407b57c1697e876263c9326d130136bbc1b02bc6854d6386ee51446c9490034595a58a8e38bacf3e2e3588be55d827a56c49347a82747071212667b09d4cb110fc259af9f2dbe80495a1f5b80bdcce7428e2b50ec2e113683f4b2f1361721b8aeeed659c17fbef995aa0c5781d0aa29255d5813548a5544b904aae0ad008a07b2e31dc4dcd7772d9188ea3d5432c52741e35977d4daafdb55dfea430dca168ce95c3cc431ebc771e48a267486a85a91e5880e1ced0ce48962df5c3e2e52fe45ee386c8e748de232954cbd75acec94d34e00d76ec2da181b639c1044ad6930614d5b97b0ca944437855053c6eb78b9ef97e75b1cd715293f09b40d9e295a85bc05cf15a4ef71bdfe209e19f0d890b33a6addc8e076880470631dab86b1832a32ecd43818cc30f90e37de84f38148b94fd44d0597e57d9a0700b9dae6370c631b6941bd5b594453daf44eff87cd9f026e128874521634367ff71d98f1ba9f17ca03a80c2e60a61bd07654f41545db55e6270f3767099f510af7b8847575ec69a4e8bfa393d2b2de42acd4ce0a06c305e1c2089c0e1f8dac0181634b08ee1e27188049715099132532b5114e7d45dd100856c189294b837a62b54e1a16b003d0c0b4e0bcc2ccecf0313a84d8db0e40f0686bac9a7d90f5df7b122f16c203f03f8a7f9ed8043ffaae6e060a35ba019821dc9ee3c00dacbf07638b98e05862273a0004ae35487130ff89edc5a28535b6bf4c934aa1a4211874daa0528bb0038e8cb846b7482cdf46d9ab0d6be8393812af7cd6c8a81f20533fecabb326468a8693c8d96375fa92e21763f6b46141fcccd2549d53aab1d85f9fdbbd7baf1708b302476b2002fd95d346fdc9d553e340fef37550e88eb5825e06e1c16d933b00dd87b9013849bd616d83736c7f68073da0f3a255290d9ed15656d872f7c523fd52a61747851eac1420f8aae7441aa727239a5ebdea84b6af6391809f670142214adffcb7be057fdae8af08fe39551b7634fcbc2354bc48a5c100f16fbf11482f1c4757889d603b8898bcda65f77c9b77b59293b963edb2b5d841fd3ed8110b13e7aa0acafe0e6fcb012d8d003efbf94f94f569bf859479e2011f0f81eacce56a8c528fd2e4f1303ade1619fc5af08aa050d5e83eacaee645e7d7ae48e7a216eb247a611d4529d0f188c53a88aa6067118aee2c5a91ce452dd649f4c23a8ad20340ee1910400904c4d0801daade368ec33dd9c18ba92d514735821f970d540175b3e106a81fec0f4c1aa6c35b232fab386c45897f78e1e351e07ead474f36467316a978a7a315eb24baa29d4529deb148453a88bc6067118aea305a814e472bd64974453b8b52bc63912ad2d203d692af3aa8f2590a086e168d3acda1a968e7a522382f3a0032a73a12a30cae9b5cd1d704696c5ada53c4cdcc836580c11f6ca9801264b225525fe9f034aab0021a2629a7d7c6e47dedb8db8c2c04ac037fc31f3bec88ae688a52c7c0a67fe870bdb4038a5105811b1c635bb8a9d9e08ac730d5c3d78be8936282a74a4eb5b173b781d1ebbb5bec9e1f234e81354d63f2a17d7e3980370afc7acfc71a67514a32ff3b36ae28055879510df6d88255dcbe77f3c335740a956604519cc1b411d69df65297b8e319514e69cba294aa3a2f657dd859229090d9606d0d907490ecce627d308a162ec17efd0f1af683db08f54c925b89c1b92f9925f2400614143d01ccb6a33dfe52bdab814e3e1aaf1822b3736ecccd87d682305140292297d8562dfebf3ae5dbaab348c786e86eb7b058b855079bd03dce5110bd2feecf04a6793f5ba8ead8ac52e16de01b291450d0d6129acdfe72ad299f197d2f7a37f6a2025bbc401ea503be9c8a0bb7483a0562a8b20f71fdca74ef38f69965552b9a12faa4737a766fa091af9bd5f2c98e35b828704ac721202cda0451d87d1169760c230b61793a35a8dcefa63f8fbde6dab0fc98cbfd8a076342a0c0ed86096fb3673100ecf7a9ac3ebf9fef5f9cca287def788e217cfe137d1daa260444d2978184c485cd5ae143d11e001f9207d2c314e94a43e3733084751a96857aee32c58e629af77fad13cd02f5ed05ed3c8763b76a028c9902ae0b04660a3860b46439330f0f0f0f0f0f0f0fafa18d90da083904429a4c494ac0c9624aa14549a694a44c2991bd1ba30de8e9ccc4a733139f2e12872e019d0a420a700a33d470d8f08234014628d2bd2ffa3666fc4bdd0b608082db0c56212f4ace3ec188fc174253b6ede51024c0f004773a82c8d8592d72df09ae525faa0cca93caa9e404bf7e9b54d29a7387082e8e22c0d80477bea13bd1460555060e1a9a60254bf2889f5d77234f1899e092d2a5933215d1bae2828b2fd2596a010313a7fc98ffbbf42d5e022f502738c0b8045ba3548c37e9c534441c396858829d54425d27eb743a56da03605482ddcef531525f1f1fd706189460b307df94ab3e46dad22418ddac5f32b3335b64788d3e4b00b911c09004a373f41c56ea9e1c604482d33d39e9fd6d3e3e549083868c06801a604082b78b9eae34d2569f07c623d84c151d9e725ef40d0e3882d3b9ce7f2d898d602fa8204cdfe3a94b32827d4ba6a29b5f4a80b1087e54aae410f447f6978ae02e68932d6efae39d7e13c16437d19b848710ba11443049736288f40fc1e79862a5194fc61a0d39c19921b8d6c956f991b5ecdb42f0314f2e2531efc9285a1260108249429efaabca20a6f241702745fb266afe885aae11600882b11ce39aea53b764d2fd018c4030f246a753690704bf13b4ee759e52d16bec018c3f70d1732447ac0c0f60f881bf8d172ffadb26130d1030fac076be37a5848aed972729680974f1058e1a36b4f0cf61a38bbf40cd01061f5853e9a172081ab564bb07c654a8e94b7f7e49281102430f7c5f9034396b88ca70b700230f9ce4984468cc931dc0c003e3b9f9f427a1c7028c3b3051925750b9d33930200194020c3b30228745f2d8554173ee041875e02ad47910c9934e214774602f8ae48e1b74c7882e8c39b0a69bb72ac7f7582109c19003933d5b08de2263daf1c8c813c08803a334671271f7941c30e0c068f091a0d29768e4b051e3ee068c37302945e6cdddaa1b92d164b261b4814f8f6a715d73a8cb1f2e3e870c1b6eb0060c36702ac9287aa39deec9da1a5813a2e4c9ee0cd5c08aa71cf2664b73e0285d809106d67c445b84887901061a78558dd8af124b4eb0cc50eb824673818e0b30cec0a5ec5dfeba4174bc6e8b1a3472a0f722056fc200c30c044619c84003c61878f5db6dcff4618881afce97b23d8f64c008835e1252ca9023677b87a909030c380809607c819f9093d69b269d9f5466a8c9a861c30bdc758ed9c73f731025047861e386023e3e70e4781c385a0b30bac05b57d42d29eaa1c423c0e0022fca226598d71cc8f10cb81cc0d8029f3108979cb154a87881a105fe368755ed0429cad61a3464e4b8f13870744a004616d8681db2bafa0717bdcc50b39183c691e1050e848105764f698b97edb1eded0b7c7ca01b07c615b8f6a4619d83367bdf0a9ca90d2674f348cf7c15b8dfac4928b71315583bd56313a3fda8264d81bb1425c8db520a9cb29c47275fb390cf1405f62d63984437156497a0c0e9fe0c16c283656b9fc0e6da24d3c2ee3448df096c6551a3f2e3041db46f0267e9cdf4c499c0c4cb66715744b6f64be094cc78a5e95402a71a25a76bc693c0978b6fd22283a7dc50f0356e612081edb7d6fc5395637d8ec0a63ad5cb15947d903102278448a37e841c11691118897925eb42f46e0822029733bd333ce6929e33043678304df9ed4f83f40881f588363176d6a4cfef20b01547f7e90d25b30f0436570ad192ccd348fe0f18b51b636a467dc0e9341e96cfdc1e70da9982cccb2d0fb8b8bfa224fa7da5b4d901b7222ac742905a2dd201173f6f25d6aa39602fa20413c94d1cf06b5a22e85a35e963de8073bb354fbe693c4ed2068cbb24539633852044b2066c523a89068ca5947db462deacd20c3895f2b8c91c52be2492017b398455fd27ada5833062c04fca1ba345d5899d441830e046fbc2d5ed3a48ce2b982472a81c695f3b645dc18559f6fa4f9656b09db3e8d2dc113c968515c5ed3bad82f7cb7a1a6bf446f255c165d2129d605d7a44a9e02249d14f3f2a1825f1dd93f47efb9ce25531ddf0099e24c5048f01aff1850d2f6e3c8e0e5370dad43393d4482918b55b299e2e85146cd227a405d96b9f9246c18510a9eb540e16c316057b5b21e951d3bbb509051f4b294d6992fe3fef40c1e690ff7a7ad927d8de6457edb7e149c9135b47eb13dd8bf419006ad0d109fe8450416ba87082f16c9f6f9fa67ead9281e6454be00ed0b109fec374caeca5df4e760303a509ae83d2496aaa6c8d39efc8049b3908cd99378e09fe829a8a31e279094e08eff40d41e5e89158820d1d293ff42feca844991a3167867650023b26c1ded968c58ea3aae3da2109fb00434724344d5b9b33436d0117c0e220f1082e930e964b24b5232694830e47d84747233869af9add5e31dbc969540723d8d0ef6b49c4e86239ea58041fb7468e9ea0a4084e4694ac26af4bf4d789e0b564c98d9a2b2238537dbe1abda35885741c827f6f3dd567aa4f6594101d86201d8560544d8b9fa4bbce2a7510c23e72108ce610d931d36ce81004afa1bb621afcb57fe3397e0b2d1d81e04ba61d8ff8254ce56b861a0d2fbaca1a3a00c1d89dae0f1d9d9feafee16a2f37f5f90c352c32c0803474f88151a5a3e4105bd3d1074e744addd724562af9196a9e1d7c60d367ce135352ce50432974eca173738be87a42a866a861871ed8ecbe5321c4a0aef1321eddb091cce4814bfa2777b0d0fb91d32a880e3c70166e2a9dfc5c1bcafb43c71db8e4eb2932a6d869bfb40323377acc1d3526d55a12ad43471d18797b1ef4f6eba62e3be8c076bae7a78d6d08ba9ad1310756dd64fe72ad90bd3e5bc8a86123fdbff82f2c5083021f1fdb21073e67b333edd2229468c481cf298a062d31c6f453e98003b72731df8fc6dfd486d7f8a274bc81c9312c9bc7535929bf2e1cf0393a070614b084041d6ee023476a2fc9d6c9daa2060d2fc7818e36707f91acafd1c1064e86e957478fb8759f321c5b34170b500b41c71a98a4be27bae49b24c9c3d105121374a881114129b5f7f165e8e1504d037b564a6bccd8d2e88206177a1d68488396ee0b96de82fab7a8d1373acec0e6d08b22fadaa2a49c083accc08d489efe66517bd7520626e4d86ae55a0719b8cd2445a89cd3757e50171d63e052545efe9e2a2195e1353a078d43cc8c02586881c5d540000e2f6c1031948e3074808133d91525c690cd50fb15d0c841a346e3f8428717f832916376b7bc943a9ca166aca30b6cb4521a33a7d48d1c355e031d5ce0237ed0186b2538b8682542c716d8dab390a259cec8a1430b8c1e8da4de3b687578cc1b3ab2c078ea04757e771742270e1d5860cfc623e47c5d9a257b05de9492546b95b4022735f9560832749808abc005dd922d5e2519b386b0830aec6545d19c9a24a3630aacc9b413ef75a5d1210526c98f496f43d2083aa2c004d7b088d9de0094a2030a4c12fd1b37f88aa85e56d1f1045693c6ec563a66bef99f7138fa238001484587139898f7fc3de71b061d4d6084122b9db6d184959409eca5bca62229751a417574e858021f49e9d225af8466dd4a60ef6d47ae7ec7ca61cda123096c72ddd51c27ae8767ddd0810446e692d9229be408bca8a89164cc27aedfe93002a7ab94ceca54f1be4a277414a12444777e9269d42e741081cd3599b54bcc835e5cb5d031046e945a55ca8fe91002ff2926d51bb314042ea987f6783a760081cf1bcb7692c6cc50cbc145cf280ee8f841bd315fca98ef93d1e1033ec40bf6b9ccb30719dd8e1e706d61fbb1d2c94a1fcd5015dcc800fa13986d1f3a78c0c40f9a3a4e48aa92a677c0e6fee6fddbbaab8f1b0a74e8809da4facc47689fd0910356cb4b2da7ef38e07a271d248d1b55b5c504276bff4ca4dfcd30cf25f8e89f7f82ca31f1532ac1c312ec86a462d6e89f2dadaf04a7bd322d683135d1130f4ab01fa369ec4e3998e6dc63129cdab9c5acae784a0425c1e80f5983f25f772d4f24b824d2a89c2369cc4d8b0724d84aa2d33a7ad48e27f3083e5975bb4b30ed1ee3e108c6b63a6f49c849768b1e8de0bd640771031e8c6064d2cc25d46f8e581b2ec16311bc054f7af2ae76e8c420c143115cce90ac3377da23118c8da87927514ded8d0c1c34520d1e88e02d674c2ffa53624afa21503184257da9744330c27267f4109ac733a8106c4c7522a6e5eb1fbaa2051687030d1e84e08249887b0cc23e4e109ce6681bb5bbfa41571f1fe71108de32247954bb50008b0c4480143a780082111234bfe5d0586aa63f309eead249bea8fbd94cfbc2c30fdc590e513ba8cbba5ef2e803abc1f3757eff502572b0d0020b2db4c04206161988001638f00e1e7c60548850497bb567919ac71ece430f9c648b9d37640b722b96818f3878e48149afd97edb924b2aa50d37eb8107d6555b83bcff7a0d252b78dc814ded17ef3426ffce423cecc0c9905822e69ae9118b471dfc14f3281db8d1c14c254b316b935ba8e031073e479f0a296e96db4353f09003a336b1934ae9b794ecc4e0110746961a9136c848d9173ce0c085b67526cfa49fd5be81511ada5b540af934c7ddc0e5607e96368fc2e0d10636bfc71c53c22ef3ea6c6027bb564c96e51af8ec2649c83c42ef22ab0626c41c3da5a73b0daca84b228be8100d8c1c95beea274c35299d81df58d2c4e4bf66a86b0656849e18428d5d064637a554efd720522764e08375a8d1513e39235990e6497a7e8888811b2d328994933ce35a6160b2be85203f4efc48193e7880812b3795f46d66bfc0296d37bfda4d7a810b42a9348d6dd993a7bbc07bb2dc9f3a5525752617d88fa0df444ac12d709123071121e6a1053e24bf28b1d4b64716d85342928ecc7c9bedd2010f2c705a62c81a32dc73ceeb7ae07105f4b0827de0f0a8029b3d317267cf1732a95081519274fcbceb4353e052e56814930d571d7848811dbda72bf306652a6467a84581cb25fbf4998ece50bb61c333071e50e02b8886ac09b94bba44468ec7416ce0f1043e6528f57d9541c1d790a1821b39d6c3098c7e37ffb24a1fed5a2fbcf0680297a2448d22d4c804937550714c434e09ca6309dc68ff98fc3cb8d808b51738f6ca29811b8f26bdbfd45b8f2470a762be7162a8a890c089b45f2945d00cad00175c7cb100228035781c81b15c1325d4c8e47fb211030f237011cdf27e2559394278148114f6294fec7bed8307118ac710180b39449f1ad51fa2cf50b33d7808815151a33e9e2c48cdd7305af0080297822ee91f96f2aea76620bc820710d8dcd1e2eaaf9a213278fc809f5c6bada2d4ff69e5cdbce0e1032e05393aa8bdd2dba9341a78f4a0a4a9f26eaa10d7b2e1c51c60c3c6051af0396c4cc0480b3c78c06d7e289ded3ee8910a6efc1726d8019ffde44b010f1db0975c336bdacf0cad0de4f84202095040113c7270fe2416bfa7712303a50c1e38f0b8019fd75c2505fdd9d3b891812d6ad471c0c3068c4e9a949f858a6b5a8ec1a3065c3e15738d2ed18013f2d57c5329d3b14e3360c42c89e9c9b80cf8ae5c71ef1403b64388dcef9fe301032ee8dfbb778d1944f30a3ec8d115cdcdaabeb42bf833a52b77d09be8752b588f413368cc2a757559c1454cda4f97a7362dcd2a18a5c1ae7b4d8614fb54c1df687c370b9223a4cca4824b3923e635a147056b6aa2272ba5d036f2533039fba78b505a75948829784dd394e954599482499bcd82fafb574a5716a4e04c88322f1962598c82edbfbebaad4cdf13e94216a2e03ba48c5a9597d2a7e80c352c32c0001570e1851708d0228b503031e26687f2b614d40b8acbe213ece97351697592852758911cfbe39b8d597482d3dabfd2a1ab196ac6b86864230239687471bec08021260b4ed8c767b189928526d83c3542e5f38b1799e0ccd56285a0732b6414095960821325a3249d648914c4bf047f29a6e9fd384154095982abb49d56b364703d5125b853df7ae7d143092e255ad2ce6954ad69127c923812a40789f99346126c342552ec6c151e3a18096e94ea2b214ba8df0a063e3e929005241879d5766d133596527a041f3c66fadfa83b82adbecf9b2b6ab4ba682378addfecd943c8083678bae6c94155f7733dc862119ce95e488c557aeda3b640c842119caba6b8659aa9de671689e062059142c8374204173a2d8efc2041da0907591c828f31a81f2d0b9e39655918828f2cee63a22b276451083ea6fb3db5b6e2f59d1d6441084ec791a3be17f4887e06c17f783ead1dec2ae7b420f8133d4ae40e03c1b665d71cf504083eb2a4ef8d21e40f7c6a8d9bfdaa2961fb814f214acebf51d3074ea8da3619eae403bb39418268d1d018d53db0ebd135699e187244d50337da35a87fa7f2c0afc59c92744d15fc4a3c70223953e6ccea1d38bf8979b1d52d8687d8818b68e98214617a745f7560d2989b0e62fa54ec181dd8cd911cd3dee84a159f03a345258b869047e41191039f4b97d60f9df62e46e3c0b9bd4409b936660e221c3877b1db31fb94f4856fe0ab25a9f2ce24a309ddc09b12952b63b6b4814d5dd3da3c11797db2817fdf0ccfa742966c296b606cfcefccfd44ed666ae0336bfc3b66acd377a5810b1a7c4f660591fa296860b255a764253c67e0c2d28d0e4a86181e333398bdb2e5cd29a7cac0fb95069535e7d5acc9c048fab4b2da8f2948d1185851a5aa52db79d4498981edbc6e4ac4f5a85e7e1818a1d2ed8e5e1218184f995975b6b1e2565f604349caf8d79d17388b5ba1abaca26675bbc05fa4a8212f8438d9e6029b64cea4a386bc31f9640bece954972c3b2bfdc5b4c0a989ed172f8ab2c08feeb4d5633258b0d47b97e81ccb2b7069ffd4760ca65d5964053e8559f43b0fdd16275560526e2c11a26a52c1332a70d1d4c812cd77156e4d81cbeffda63ec4e4214129f09e7724e5540ffa641b054ec490175963060536756bb0f4a03d42dc9ec07f9b16f3981944ee8a1318112c757d1affbc2b9bc027a5b5a73675d097264c6054b4b8c1829744c862098c4e5d77427dc5981f2a810b3277f8951615721293c0295379436789042e636a9290829649de8ed0a7797fd0258446e0fedc6f7784570476f2a7f1dfce664104f6644d0c212236044eeb2ee94eeb29c8241502df7fa24f9d8b41e076b46809aa9a3f4a08087c081e44e9b5bc9f46e5079c5dd66c1174c5075c6eff88d1ce2b68ee7ac0499d74def6271e146da49d8a1be90d42163be0a38b2ce1adf5415da403d6dcd442a4bbeca0cf01a72b688da1732931ff70c0a451a5c24567734bea1b7031ac2a36604765e7d1a4a5db12520336a827979c93d6a0ae440326e8fcbcbad90cacf3d55306ecb678e494b26a8a746370a65789640183f228695a43902e1160bc82491bcf6ddb5a57302adb77d09474becd7a2bf8b81b22478998b29789155c085e5ac9eaf6620757c1962a399a46e3b6f64515ac27cdb8b529950618e7da9862781aab2c01062a3e8d96a6e4baade70b0dc629182d9e419d902963020c53a067659372670318a5783dbe4ed28d1b19c020c5173046615e1de9363a248ba3230a466fe658ede7059ad20fbced26bd2562f46cb5f68171dbcb9236c94a793a3762f081f1e0d13cfd5731f6c0c874b959fd6b23683b861e1871ff3f1b751e182dd5982aa21d030fbc5866ed33cb1d55217760fd3f7b4a263ae5cdc9f2871876e0c736c5544f32778851075eb35dd20ff51322871874e023a848132165cc1b392ac811831873e0cf437d881e2647f215430e6c89c6f51c82c76c17ba03b0d0020b57438c38741e295792fa396f04074e47daa41dd29dbe5bd4a031230337be02e60131dec085d0e013d43e62f0387203a736dfe9aabc19a6325288d106f6fb32d4bbf28927bd9d0762b08115a529f77eaecbf7118df3f15146d660d6939836e60dcf7c0c35b0213737b64b34461af814747ad99d090d4711a17d6f153a03d776aa7b9a3603af92a2ea5fb0711176195853923e5eb995b6cc92818f6ea5796d9245cd8e814939c493d54bb25219313051339d0433d3ac4cc3c069d0fc135576ba2624a99a4e5d582e7d818f565aa76df5b4c70b7ccc1caae4bad705fe826fe594abd4940a165a181738cf987b6409a5a7712303ce058d195a6c81cff590a46ece1484ccec10430beceb552c2dd1f2a6aa64814df36cfb1d3f5cab9a430c2c30713f77d29439076dcf6b5c1a625c81533d55b20223b2ac4cc67c568111398d1252e3d9ed5ea8c08925532e2276aacf488c2970498988a6b4ab75fe1e430a6c3ecdf9a3dc14053ea6cffe74490f054e0409a92d64718feb13d84cadfa1da926a60a72029f2505734fb5d9c2bf78c013623481ffdfaadc986f26f029632affac7a09dcc8b3ca66572e9a2b814f9b3269245d31d9a649e072d9668f562281ddadbfce51c34dad6ee304318ec09b16a129f6a7dee6d1089ccaa0eba77c2c021b4964a5f04ea9ed2644603fc5d1cb36c9cbed2130d23d7d345a88221702af315ae7a867a33925088c1ead6934440e086cf88d98bc4f776143025868515b88f1033e48127a82d0cc07bc58dedb8f145b375226f0372af0f1914288d183f4c9b397b895e2011f2c95aa60254a5de5ec80357713f6f11443074cce98f7925fe65ab95fc4c801a772a8b8a758282b79e2888103b6eadad66bec1bb0752a447d1c49f921b2019f9ff2667f91196356ba8851831834e04314d7d851bc553cad8287c0f220c60c3891556e9e27e5516d8921037e448a98378ba608316250aa684c99346968ac982d9ed29d36f1ce093160c0af78b068f133ef525005e1e315dc886c42f44d743f5cc188106a74793a55de7b01400c3e5ac17d86b0e89e342f7f7c86da711938ba0c070226f001192df860059342d6dffdbeb3d575869a8d1c3256051fabe0f2b388ccb1b7ae4a7ea882cfc9b2e4cad2c9083c8d1b19c8f1910a6e2348ec3fbddfe7251736643c6a2ebcf81b6a15f840c5974325d50b2529d53b828f5370a74fbcd63d86469a22993ff65fc8d87e2958cd8841097d13ef5472c607290e4a464cc26a1b05935d2d28efcc19354351f02af1cfc52a49edef43c18e4e51d368f1d44de003149ce8788c5aad2239e5355060033fc157e63feb20b38968d4134cc89e92d648bf152deb1f9d60ddedd63f7ec78d14bb61434669c00727d894b41bc32c4436c1a6cf2239ed4fdedb574003c78df7fe19664fe3468efdd004a7a96fda3666b7f493b274e304047d6482531f8f24427275a6bec60726eee312f76109ceb6836eaa78df47ca161f95e0425d54e710733e28611f1f9360f25dbe25c18564d92da4b07c44821141c40eb5f0aa9c7c483039a8cc7831a6caef9a47b0ffa1f4a53121e4e8c88723f8acedef4e4b295e9f1f8d6094dea64b5ea54262ce08aed453d729a14570294212fd1666213fad085e3b75b609a272326d268291f984fada8eaea7ae2e3e10f1e8e834eddf4f2d67a8f1e0e310f96108fbf828c47d90027c1082138b987473ed6efc67e0e80668a1a9878f41dc8720d8c993c36d93e746f70b4302c1754c79dc3a4bc5dcfd0cd4a800c108ed587944e211287f607d54ca19eb5e645ecc0de1c30f7cde8c61dfdb9a472db0305f08e1a30f7c78b4f51cdcde02c2071fb84a1b2db25799f6f7b1072eb7a5113bbbae4cd203232fd9e42d553ff2d0a94d6b9d94c3942cf8c043e6a2635ab5ec5d39173466201939ce8cad0c3eeec05f7aac8bdea66bb7f9b003174a7fd00fcfeca8918f3a900f3ab0e96d358b6db238e0630efc451c6d1692c849ab490e4cccd44b5df9f2e4fb7160f7357f509e32ae780e072e079dd7827abb12b1f3063e44b44aa3ce76036341432346104a3f881f6dd8eb47d297c89a0dfc5a59ecbdcdda4fd7c06e0e59e21d94ce2162d4c09889ca7d9d21c6a453d2c0e5d3d3c152eaed6c221f68e04d44a7f1d59062eeefe30cac9578c531e96ba9ee871998643157b2f4dfbf95b278f8280377fe7ecaba554306cefd5b44a8d14abb6e1e3ec6c04634d116bf2409c9ee8718ee230c9caaa77b9f76570faf0c1f60e0d6db2db544f32b8b99a196650c1f5fe0bf82482269fa68eddf0b8cca953a889919281cf8e8021f5488a6db3ca4b1b5b9c0ebb59dcec9cd45a7b7c66fb15be0728896a8d7e4d458da628b1a346e74d1c50c1cdd801b14e81cad800b4840021f1f0ed042cdf0a105de4663fa8de469649eb2c0c9107a4aa9a46a9b7d0d7c60814b96c4b643c4ab0cf12fbc2816f8b802e7191683596e52e275bf173270ccb821638b3e12b88004245077a38b2e08083eacc05a8d47f6097a0d6c4123011f1f55e0b26d5d6b0ae9196a6a0af8a0021745e737492928b724f442468e96e17ffd3105266596887834f5e11f117c48810f9d981fea2f2283771df8880297b6c62707f32084d75ed028010e2870bfa68305311de266446f031f4fe083d094924ba8baf0e104fe449d44b7dffd949c42858f26b03d2684d2547577daf2f1714ce07d84f707e176697661a1c5165d01287c2c814d49aa7b1c9dfd42dbba527c28816b3b377d37ad9cd9fbc34712181952ec2c4b3b12788f394809c9424d33e508aca6bc6d97fdb72ee83e8cc0e4e039849cd3fef111868f223036e963e5242302a3fef69b23cfea2a3504fe62e6e44983f6a67a84c06877644ff9930892a206f808026fd61793709328d16a20b0192b4a767111fe1efe80bbecbc69cf7248ce8b0ff858f957e97475f61cf9e801579f5310bb3f3f78c0ebc66c1dafedd2d466076cccf422bb47e53acdb9f184001f3ae0f2b42625f3f76be8d4ecff460ef391033e5b9ab2945b775bf2880f1cf0218e4e232c88d0bad7f88f8f198df8b801632a12a396cab944e86cc067eb7ad296314bf8a801977489dc2193a7840f1a70d2b2b7a46cc1779238034647f18b1d2b5f2c35a166e14306fce7cd412dbd660bdaa3153e62c05eed450a514d9486e403067c502551e3e71399eaea2bd85abdd25dd7d178a62bf8edbb9c837a116999d40a3679901f42ff4859c18a5fa54dae238f557011ebf3c673f31b1df75005a33ce845fe2474b3728f54f03925499d924490a9f44005a7e377ea177d4966ed789ce2d7ca2b1d3c75537095e22f724f8594829b542234837f1ea4e064a888d51b9a3478e9310afef2557d8e4a4a2404a51c3c44c1f649dc102704dd9da4013c42c1da59a6b4918182af6cd9bf43dacf7ea62e920f3c3ec1ada6ca1c4fe566280e1aebe10956c3555376cbcf5093f15c6c5183060e3bcc4e7012927a50abfd195addb091fe5c7a7082979083d98f593c36c188d64692d9a3f79a796882ad4d42eb6fab1e99e00a6de02fb54549b24dd944367067d965c13a9b8ea4d7c0eb28937ffd5103274544919d73d2c0fbe67e4caf9793caa281bb929bd28472cf6077eaaf8f9c3403e725b25e4cedd9524a193893125448882275bd25031b4c9baf08d5a4447e0c9c26bdca1e1e42c42031f01e3dbdbe25ab6e320c5c5a25a12c9d704d2a6060b3e5cb4df22c5e90982fb066a1a26ffe158fbc17b8ffdc39b4e25de093d2b14b3aa95ce0458f12692dbb057675bcd592d4f6204b2df07d622aa4ff0e4296b2c0b64889a13f72988f05ce7545055b8b7832bf029ff22819bca268057e4d6d5805ce3b5fcc937f24d353810b5a4aa34af44e1e4e81cfc9432d058d4881cfba5b25c213056e437de648f1640a6928b05622a9cfd39757629ec0d77fac1c437950a17402a3dca2c8bf8871f3ba26b04169eeca9373ad746302a3925049f28dc5d2a725b0c134c98aa5be415b94c06b95cc244a4f50515249606da3664f1d75bd26a98c21814d5164c86a4ae3023b02a3929297458fe837a1590b981118dd164466341d56b932432d052fc3460e19e450604560eb6a42bcacbad1412442e54127f14cf22b36047e52fcec907933c8b00b13025ba222e513a1d74b75160456f376d09245e6d8498de34fd0cf858cb51b5d74610c087cb2cc5cc1feb27e8af6032e454a2993e9c8ff79643ee04a64fda63508d6032e4a52117c7d7d720cf29f81a367d4f8c214c078c0c5bcc1eb548d4db2640008b6034e8d7ee7cbead94c9e706819311d70f147a4ff5ea82fe97f8cc172c0fad5464f614a7ba3f803c30177e2aa1641d6a6c9abb9011bc9effb3a8ece1738f4b46881d980cbe8ee41890e762542ac066c90f194a854a63289241a703a5a864ea33a88f47d067cb95a8da58f4c06aca6ed1cbff3551411b2187023eab3ede73da56390c1800f9542aee01d744b087a059f26c5bb64ea96a26d57b0c9dbd279a78f92476d05a72668d34a23f1344559c1c59446927e4d57c1a98c4984eb6bf01b53156c27992cab68c4ea3015fc660ab2b4e7f0182da8e0d4e82f997b84679a9453305e793c4fe68a2918ad2a167f83f5c57829d80c8b77c2d3a4e03bed22d9c4dc28f8507293df474e147cfacc9ec15348b9da8582db546ec17dffd4f8060a76c783daa02b745f8e7d82d1acea929444a7f2cc136cceb95b3db34eb0d193166159276dba8c138cd98498c1838e96a34d70ea7ad36f0c1d71d4046b72344347ff18b393092e33b7f6065dff2a263851da36b3b897e06292273ce369094efc82f03eb9ef5aa9aa04577eeadae289128c3c59d9828f3f09c6d46531732b61597949b09ed5f6f4867c2458ad9c2f49cc6022760e2478b55117a2d4a3a8d123d84bbd6572e3b66ded08fe728510240769ab6937829568a2e27e2a89d0352378d19c2396c8f5223849ebd529563a21f38ae02ee42444ce8e3f4a2611bc557a45d66f11c167c88b549d630ec1aff7a8e58d3104ff41734a4246bcb68829049362acd6542683d94e08d64b071f112f340836a75349c84a9973982a087e2cf9291f990c0427d3d4ba9bce2f6e4940b06b1e4dc9ed9cb327e11ff8502273a618f5031febfb5db47d6512f6814b13da84ecf841c60bf2814bd5392cde7d6889ee81b51ffd99fbdbd7cda20746d276ca499ace08ca0393728e133da64aaa170f6c32fde423c5e44dcc1db8ab8dd8a1985925a97fac0e9c103a84eea9081df89c2832ccfc9c0317e615122d24f7b44f0eec47db8b5945895d7d71e06405215553e45788393870fff9ce552bac3b726f6044d2881cd1cb972073039f2d2f2ce96056d5b136f09594f24af3d0a65e63031f34677f901735736a6be0cc3ee7de6cff2f2aa6064ee5cffb66973430669dfb476a16919da181bb78a29a4967dd339d81dbf5bcb142277a7ecdc0a78da64c79f805a12c039b9288a7f32559ca181918ada13dc772cda41e039f4b83d010ac8a408981f53aefad98dd61411206564ffb586d9a860c333060d93ab7721057eb0b7ce7bf983faa7deeec9417ec6a5329e9bb3429e6868d2f70fc16349c7c810516e7bea8a102cfd15c2ce0e3a3c6e7c8d15ca401e4a0bac085e705cd2d3e17d88f549774c9d316b8e4a93e9a52500b9cbc4c72372fe8aaf72a0bec66cd20c5f2080becc79049e97a6ea6b194e1b8ba0213ff449bd4dd98f309dda0f18509509f1dbea0acc0aee6a49fc1b7d2e972068e96615505de46aadb57b0edc8a6196a26e8c2f7074505467b94fc18ea428d8360a1a6c06ac618836a8d90c43bfff8f0a20b274d2829307a54123a2789b95f1927a7a2c0a7a89e2334ae95861414b8689ec9830c494ad07c3e8153a133a994d652ae4ed190423981bbb16e3725721b866a02df1faae9a344103dea7aa198c00815217bddb2876fba0436e77b87f0ec2b81f75c9d3b8a3651229b60a824b09a4ee7e0393a3fdb47978aa64c17ea08fcc8ef4ffdd1ebfaff4e52bda8af3b5045e0345ea7564b9f82a520192ac851e30b1ba7ce0645042e2b4c2deccca4b95b831a02a3259a11026b6b7a83901e83d4cf82c0de08edfd10433d5d12088cec3b6da2bb82a89ffc802dc97fa1cddbf201e7f613c392b69c2ee6aa07fc5b9a3461728347ce140f58d39f2d28cd5bed80b153a52383e6be789d29940ed824d4d59526297ea3ad00142a077ccaf72041cf53881ee1808961d6e92e4fccbb3937e0929ea6c7a4531bf076a153b4e4f89fdc523560cf2e6d70f7fca1538606bc6b55e9b7cc50392c35034673947eb588556b6c092503d68277b57e4c79e15dc580bf78ff1153a9c70f220503467b90173f7743428c57b0a7c722f5f9565cc1c6896fc5680577625ba742ba6c5e3b2b182dee41e69034b9dac40a3156c1d7298d41afa555c1061db93b87e8c64805eb5669bcac8448f1c2196a35fab940c656100315aca49d5c417cbb0dc4380523f5bcd5b34677b77e3310c314ac04f9c1b4887c3632a5314ac1ab558f4a4d69f37db6f02f6ac8c004109b816af48d931588418a749a9b3ca19121c628781d212c5bcf62c8498982db58e9f2f8fdae6f2a003488110ab63e930e39e4d441e731063140c15b4b88fbd7b7a622c62738b14e9b513f674dd28385165a6091a788e10936875876907fa226d73968e0b0e14572214627385552f407b527d535b2450d1a4e03bd0238c1e6d851b209567363ed6bf4383aed4d88a109f6849cf4e5ed255aa24c88910936e61092334f98e78b09b64bcf2269042fc155d0df14b4ff6f75c812fca7894907954da413a54a7021735ab41083127c5516bbf1ec0ea6e49360ebc2124312acad5884b41ab4238811892bf4bd7bd0ff6240826f11b55a3313a9427dfe860c1b19e3116c7bb074227e899c42770427d3eb24e5a153428c46f0954c475e8e1416623082fbda24bfc23f8bb84c6fd26b31b50145f07fed9233de5a52a5c1438c44b0318629d1bf3b1a2597818f0f44f069325488db9ffbba5e438c437035f135a57acfd93215c3108cd0bb71f5b62aefe8310ac19e86887e3a4d57be530c42f09e29d967fb28d1ec1883609279444d22699c148f21082e558a559eb6744a9581606fb42555ab2240b0e9d4574712a910e30fbcedae5a0a295d2a8e02a82411a52269281609045220083108cebc07e312000000081a140663a16830220ef54d0f140003582a22523a341a20261a108dc681913010088481e14018140885028150208e84711ce9440f0fa10a8ea20c32aec88a0b5d2ba0a6bff5d4f98a86ab8caa9e0fee014a4d2a8afbd1cb5951a85a684a41eb4ab956e6aa12d5e2a65eb8cad082940eb4d9cae8f4806f163f0a0b75cf6cafba74a5a6a932e596e9ab0c101c9d6122534f85bf6e9bfc683e74366dc11556b33268fbe9abe24fa92189403415b122ca3531d789a80947257aaac8abc5ab208a324c78d6f6b63b18b1cbf571a932357ff991cbe2ea19f8de99ac95f068d7870ff96ec2687d75c15558bcc2df274eaefadf9f99a46046638ce89c13294cf618c94de26a0cc78cc19c1d627216c69a27b1b3cd8069f9f81df640ec70f3d353cf74630bbab474ce5dc965522dc2883a6bea8cdd6bf0146e6f76b00f0ff5ab02980bdc33af510919f809e221f8d6ad32158169a98bf94a43a7b9c5fbb5c7042830abda4478dd076243cfa908de9f6e5bdc41af203c346f6fa5c18c2080b01de1a58042c9b7d63a78dc890bec16729404f9e6b41ac4d97d3ca4080bff2027e52211d4590f542eec0728170dfc4cffed1e28085f60dc6c5904230dc642c25bb8d891b396d66c33114ffefe925c3dd6cfa672c533bda43734b46f8ef37033fb90501a802eb4a47d83d1eb2b060ef753f91d7b67bd2186245956c7a4806bb9d979058cb26e6984f50319cee2277c2b2b42e5dd99268974d7ecdaf0877703ad28f0f87b328a23384ed34052a0ed8f64e309adeb6bd88f05fa4156075bbd6fdf8e876f6e3c35b0b785ba5238293b93db3da70ea16030e9f2fce9c0e86be878bd9d067093e1c5874c841fe217a2bf18fc869a09fd3bf69e8f1e094baf38e070863ad2dc52e0adb0dc249846c0bb7e9806eeccd9cb50e22fbf098e5ecc0bfc06ecfd5855187e0288055e0a6cc4c8db6d8538e58c38a89482aa7befc630151b1d6bd1522745a330f1db8283c75b11bb2edc7c927e93c61bb4be0a1f49b2d1f8c6b05ff0667b085e5e20f2bbb4080b614c930d30940208de0b82b93a662292a495688f437ad1f7dfbd8435ed22cf766f8a5ba9c5cfa29a847ec0d19db7eb392127a15127fe6797494b468a8a3b8b32ae458c203bea0bc18db6464591ac26e9c82289af5438138bdb4132c1b99de295e0c7a76e1c5dab341927a571cdc34585a016688540b6d6863ea4a44325e0bf19c11308b807df059a4b31d266ab314aec2ec0d7c471bf1c3b533278c0442bfe1eb3706f7a498d5e830655c115bb34f2bfb2636c4f7ab6cd793c5ac391d03082f4d282d03c6af13640045afbee18aaa3a54153df4e821aa093a1e9a5ad21260024a29e202436819d2179a25abdaf0d324e161ddaae1c794d0ce615f87c155b0732e01577f04cf1dc227776609399d3355db75a4fb1f4827b3f405380b7f108a4697dd290c406f8860f54b66c9ee74ef878ae283ab65b5649aec84129ffb7015016122a7702be0f222e30f70ddb900b31564bf4cf459bb563c1804b1b39a42a92ac3683134eed5cd208e33418d2e4e72d78f09122e7845378dbe663b82aca645b21733f60ac1b3a5c579b66da55e81234992f3805ae7db88dc141c3767544342cc96c21a032cca4a1dc4ba5a3088c685e023ca0d679041292c63e3b56b9eec183bcb7dd5ff6b2588e81367db76597e1f843c54f245ad5499fc0f148250835cc71c2aef3ba33d499b200a45b28d17dc2c7f889aa236b33ad65b3147aa24cdf691d592351e9491e2da32a1a7df0251eff862de354a5f6229590206eda64006a576dc09a49e73c48ae8e46bcc81b813462d45e81cb8e4c2b0c456194f189dcf92e3e1055e915cbc82a856b72b3324e54f9f8dc1e3d61e5b0b31f513f4ddb754ff7cca18da762263862e2491a6393ba7b0969e0c5d2f63d514ce470f96ad9669a0c0945830b66d5427b3aad663efee4b4f6b7182b397f71080db6d22c5d19bef9dcf33e52410c51aaad0b330fe5a0a42049c15b9e3de8580d663087455a6b6f3037c7a88a82a728961560ecc335c094999f294c221b1a86372d69aafb9b1a06067f5d8c8bce0d6370f676584ed388810f21abd67cd22c3153039669940cc0a48b201a25a932cccb747414176ff587089c296c910d035753e62a5b762689a7cf23dd7c5946eb69cba7a091f7722a853c5bfd58a51a1229174f0ff5f6f064ec446f53fe631ce9323b1f038003d0f52aaefdff4725294d6ba870c4f1b827edc8dc8e1c3604aa45dc78d10d545a664346866b004f6644d56c6a7e381391a8aa95a2a519960d01ef110a6401b6df484d62843c4a7f7a29e3a07a37befa16a529b3b059f7569d71b134f01b135f18ebe220b65f2bd964e42af70f56de0dd792b43fd79b03b3a6dbd863503ad392e3e5deea6e8e820ee4a3cdb4842140c8b54cc0927073aa513614d83d95d759f3739c176e4a9a015896994078bcee486388d3f051fab043803d8cc3b54f689f37c1f714b95fe81e4ee4410e59a22480ef5f68f148012ef979c92b171ef8db54768bc11e82325673cd14c4933681f44dd29a8ca0e89f398599acc8306fa6bee5b461933293332a5883d3a3b417b263107b98b7f28f8e2d8bf85338eb53e8b1a588063be447ad2da264506b30337ec6ab123446ae0f87ba9ed40c7e70e33a59180d7558815a424af1557401c022899fbe46993c18587526feba74e6fee652b157cc968e9290b460424dd6e0c3b5026a2cb6848f2a84e0c742e81ae167e8282a44a3049289d523cb03ef0b60d08ee8b8640feec235c8e25237c2ea6cde6e46eae1508bb622532a1fa0d9be91b6f20ed4254af002d3663dc597b23e6f8c3c14afc78e51335edc62daf7ba6ab129996e5ea6447e43ade9849118644eb0419a3d4ad824f9193a05eba2b9485bca778bb2e92c4868460fa359daa3b327420d95a00a633a14220d570a53db2b025014527125d6c3a08b6873048b607866ac3c85c56bb2fb3f5e7901f9e3ad2490b78c272d63943793384d57f5fb3be41ea6260ba57e9722296972ae36ae620186ea4bfd93ce4382531e42aab3b67f56fc54412cdfc270812487ccaf6c4fc3c9914866abe65e110d6e0b33c3597e6d3879024685a5acca745edb9486571acf07f230a8bda9d2e9f2923060bd9672ef6c4dcce0f26e255853cfa681e3dd9237262c970689576367cd6ff7956b9df1f2b0521c6e563affc96aa4ee44c706432bead59332c0591da0114807d7e32f5633fbb12b54943c782c603f43a24b3bd1aa538644745d98ad4e2c467dfbcf57093791dba9f75047a353c26bdecc429ee568e720d66d61e874c3eb569b2c10ce2526dd9ed89f1a63344edd259f602855d79e2721436021653111dbde067e8c39dfaa37f3a2b4e71e547de5cc4b81fa105ba79a7cc97cf8d230f753799762df14f65b34de75b2d56addfec6a2eaebbb7960a478fc619417e2f2dea3fccada923b74b06dfdbc4b4465b60800af43dded429244e26d4a4f50aefd34681b5603d8eaefd02a339326c2215eec8091366be77583de18f9875afb68481cd82f74ff674939095b344ca2d4084fa91bd82ca5e0b2142f4a9bb62694753c3adef93ff27b2389b79bcd9ddc36264466f6bfa636c76dfb6a3864ea103d6094377dc08224d6081ae609a9875890cd5c9de45d6684f30b39d9dd2fefdc2d4f7ee29a8adae69a658a6884314a1881d38f2d49d76461f359506f43e068483c358433820c7993b8e6bb148c45e8a904cb14b8440007a1435829ac8724190a68c2c0293ab2e0abfe04ceb9043b10f2561bd6831c074f16c90684c96a0e92ed7f68a0cd3245150921a632eb4b8bdb06842d0ca0d62ede1d0293053b94462353c66cbf26b17b016d9c0fc8e2fba5d22b3cf0c258e4f9fb14b0f355168b4604fc68035f3cd1fe4e875ef48ebd5f712a9baa179908e440c7a77f80b604775670e85947d8b0fe8e11047a81c56c109a500383331cf99bc7dce9c7a389fc875e4476f19de171e4936df0e54c8eeaec26ff38b2c72ebdba214875b9efb95cb0800ab702fa072bb4186be9aa88199ce1480f91621ffb88fb3aa9c68c8775c159b1a27b883c6c7410c2733b98a3c7166b17cefb3506651c1484ac944b9649766c350a5fee61a85cf4901ff160c367989363405388da425cf7996f6f8182204ca89fa34b119aff20427e525c33511911c72e4e4d162b03d6f8e7653426855dc06c1870c94823d80d2278c62e4b1f715a5e4a3f60089409a47c78883af64218056f6539a12c7bd615e8ab19b4a4564c25b459e5c3ce920f49d4d368ce7573e48aeb44087ae690db4420a12976802859eba813b88ad0234759b8e3c8564bb2036f21b5cc120ece61ecdb80589b8da150df63d23aaaa90e25d9df002e61dd87cf13b004dc3fbf740978eb6efb349037d4ecdb2694c410da386cc7bad5937a067232da02230a9a885824e4109405e92ff51e81a055b56b90e9083d11f97f6dda63013f90a664f2b3ab0e47c480e388c0e925d8ff92cc378ed08bc208bd800dd05dbc30bf7304db282c8cf3e6d5c610766693af0ad2471d20dc31457bcdeb23507c9b6d1d9d86f902d7101768a913db9d07cdbf9e44ea9ff00e5b771a9d78b469eb5d165c76e60fbe4649831529f4b82b8e95aa92522d00cf740405eec623a244ca2d5bb82af2dd5f515f349eaa4a58cd21484643869f86515052936b4bb9665b731205766427b137031bce3cdf32ac9a78b7d87981143fe01a1870f3fd1da6b2056877763e5bae6609fb1dc4282bc0478f2244b5ade95bbc0cdde018cf25df80725155358bc5d0488bd5c1066cf2c0589b3242556bddde9dd091ea44405f55c7e1722a42ba669e4aa740c49fceb6a67560a28ed2d71f7f147144bc41da845d1aa2f35f97215980175502ced6f760059a8c00cac02e8a877f72524629b8a3548338eab82a806e0d688da288faa3200c10ccb06f91b2ea53c5a14555abb8e882b33586420ece9152952ab8c8f50081a01b18ff41ff6f041e2f93d465d5b3a5cb1d9fb32f6230012547253bc51ccf7f6bc3b568d95c3b175cdc37eccd929b77f7b68624dd567e9ec8d5c7ef5ba30ca88c01bce8f8a46341e9381636091e51eeb64e86d6e38d98b9a47630c5bac748a4abff61297970e9eda38b0f9f9926bf9c0e8780e7efd6eb0b4449fa05a6dd689ce662a9f0182239a66b04ef146fb1eefeae080348f6fdf82069596688642e3787af907f544749cd22f09806d13df7ada56129668512921de147b053f731d1f7125afd0d6032a4562dd06bc98fb3971127693167936533082ffa3478c85fd8182dc23a8270ec18bbaa336ae500d5477ad23efb0a204dd3e96f3ad6351d91f79d8a23d4fed33113d1354dd0c466d84de9549bb8cea9f622c25281a77910b9414cbb34d6d600590235779e6d675fa2c3e8bf1516fa95dfdc99cc44a778f5cb0de8abc8d38310d8baea7489094b7f9151694c93d305b536da6f1432edae98f0eb55b9743a5c3a60606fb12e07aca1310db5c86803b4ec66b7dcf052353f37426b36c65bb31ebf191ec67938931a23c854228d882025dc4289f8606553d14e5b141f1ad9a903a42b183e3d2f3218ca0ca81d2fded7dac0692fa937a306ed4222afa4f31b5e85f5b5baf11e3c0516b7218e77e17eb9e3ee2479deec2980aa88c90ee2ec57319db4bd05657f876b7060f9ee6875ab854ef95d630021a1c575b51374404f0a0d505637c9863ce1d44944edb9872a03c197cb9583bf2c0e916fc047a04bbcb6dafc35c692781fc500507a7bb5dffd04265c732b60c3f45ed3745cb1238a976995fa9e2814bb7800d17f182bdddf70c440cc735ddcbdc467874fa89e1566c9fd2d70d93362c27eb65d78e68c95d9806e94df87a42679f461edaf65cd8aa1c929f01b668332964f87428ce07a216a29595353c359fa19bbe9163828a2274a9d7cbb646e5d160af0613c6253e34bf67c5baea255a1b1d7823acf730c95692bd280a50c2747394460546e75422ca961a01b270a73445855e43d7bc9c824ccf7b5fb82f698e1cdaa8771ea3013918563aca63b279a32ff82102e088abdc63967728e946f27d0b1748011976c4298012acfeb63a5a152d8a0f8f471520122b6bc853928d7be748b90b86cbe741a7a023e5968b403bf2e268c260eb1bf129aafafac0693e942e799baed33e184acd9339b44caa0662de0bccc90962ce6d2e22402f22cc2180926856d83ade7bd4fe615c93746bb36db5dfb8f52f054934e0cdfc4926d39ec70c0ae30faa84b1c07536c41f6ab338a406b9a6d72fef2c021988c0acfd9340d84ee83e5fa3d4d912500b7658057d103f490b73af20d82283c6a63ad99823c6d2cacb940d3a8ec0add23378a5619ecd1732adaf9b0d892a427377f8e344bdb94a04f3d63f3fcb2c79b175fc9153e333be81f4dab8d7ca6a4d31acc844d8f71b7c190c7f63457a7bedafeed917f146b7fdb922141423d50d5bb83a452aa217d2886d4859bde54df7e1e9b4aed8ae6c144e6263b923bc11b79bb2bad2514175cd854e2c44384fc5af72f8836eafa93dc5f38e7f0172c079a030356cec0d0b5562a49ae405b7a04d6e903199a5efcac17c061fc63f42ccc5551ee557401c6d3e2365bb536b579587e326a595a8bbc752bbab9ea5cd9a4da6d41684c0a2b17a6b32f113ec2c09ce0f471e07890dc3a13b0f982308cdc412808db829ef1d7846161d6bf2bc74803d8bb2acd9cad890d7aaf6599c604936c80bcdd2498cb9bbf81e6a53cae73de2d408382e07e25aa059811eb1ff999ed42276d0d080720053022156af525122fa65f422c589ed53dfb792c510a703c77ce2b84d54e5782ab5869c70d7598da6434aeae21e8560ecc2301d7424129360883193ace0dbf18e353d8424ce5b80d8ca4fc3ff16ef75256d7a0570d93b9ef396e72ffa5cf541b722935b5abaeb7357d60068013245cc2cc81a7571fab7f9155291f2aa00befda8b8981a544079ca0c9e9ba6c98645071884936b9c7ba6dbd22f55090657be6124b761ec12920283e053b99aa2a3cab3d3294c16ec956d421ce3afb8f1225397b8db4488586ee2e97229e32648aeabbb62545100c4c75eda431e89be1074218df67cb2d863437143c342f41b782a68b70e49eb32e48c94381b80a139ba93a9005ab004fedf653c7bd640ef70c0dc66c7b003e0c58a0c6d4a03026cb868399aa21e785b023bf3da9fea9678185dfbbbcb8e4a5ad3d6c0a1f1c94745c52585e3201be8843cdc4c0943e87d690d610f8d32cf3849482190d724c269d12e06b6f678f7c70e9f7499287a4143154ecc0a002390e261feb5e714786711fd73a26c9ce0b51cf972274236bc9aa9494f9ad2650acac3b144ea33c0abed8c5b5a9d0a5481b543d59f350011eb34de0ff7cb2074f11aae5767082ffa3939ffb7a4d20f82aa0d35c532b90c625daf5f3e9f06f70b78dcc6f5353836acd98e49d86a0a796b87019832e313428a139f5cfad74f8f19606590b5189eb2890b025e63230f66c0a4af94bc7e679ec54a71bb09fb0658eb00c0c2c076c251b857005ea36aa5fc3a5d12b621be8df4e279b286c7b4d8bedd940aa2fcd9866af709fe5d744a1ba7672ed6a731f277f5e166468801324d942d3dd9472dd4b8b62160adbfbea45d8a2ff858295766f61a7f6a2014224639b64093b64811ba1fc06bd99699f001ae3c5b6f7dfee929a3168d62ad55acf6fff1373b2ae6dee4adc74e30c8919c045075e0045297dbbd9d48b82058e3908efd20bfad8c482097d361a4b5ee2c90650ae35815f3510956cc13bf07506099036fbcc765e8e58d90b1b2d2bdcb4aab5a7f044731444a67c5892a00fa0c3b11f2655ae799441ec4a60b484d6b7992d73e09d526cb94f2c5e02ccea6df03201b4efc73ad09451e2d3539e56790b3000b870f190666aa9dd786d445f340dabaeac4440411e98435815b82b8c2d0400dcc30089c7a86980a694879032f3e8951fdf680d77bb60800dfcdb40f807883a910289f461d41f2355c05baedc114914f60108a7ae4d68228400831f03e302acfdd86e1406ce50ff677770db4d4b74397db5bb2b86084f97f16387a09dbcd8a9ccad68e390b0e3c14e7567d4f6e78c629706bb29ca90cde81a98da74825d87338aeadd87690a3282d07ce29ef011c1106b41367a6dd32df31b9921af3b4fedd248549c4715d2e3830613b89c2a7139afcbab491ab441e8e5256adae8933928684c1251fe731935b8ce4851283e931f1ca2369d1d2e809aacbd891a6eeb9240ad278c104356622a60d491abe47640e006321e7ab23774f2b8e10ee4924fef83b3abd3ba637ff4bc09c4db3b7bf6b1d12e4067b64721de97623aaef17b486d1293764d46e661fabc2a20a5fce8a14306e93d7db5a541ec2593873001a2d150e656e380cd08c73a962f25607465f27da3992b2f34a63be435bc71eedbde1d4d006a2cbee9cca2430d1bed4e9d6724d19418a6b023cbe57fc21fec9237bbb3ad8b4731f0a13e6232a10556588711a6272cf87088093b670a39e3b00677f0cfb0083912a16ed8cc34eefd4035903225a932a14b8f3299e890adeb4b0a4494eefec562217b4a991a2a6d60633067ea87c925966b2cf2a631a4bef3eb25ed63c128cdd8a22b8fa326adcec00ab91b06c4d55f7671685254a186542338207c980f17d2621035bfc3d5c6bb0b81a5f9364d965349286c3670d252c9041bdea76432a4cda5fe2d5ca6e2db6f899d6e90069368719ec62df63890a233bd6df53a0087fc16ff88b5d7a1ed1a4fed88ef5549a23e66e26f02535a649e8661da032fd59835a67f511a87f64b38560dc47deda9a2a0d5fe3c5baec4b1bda9c053a21d5c29ce1c1fb4ead03fe1cd580ee1d67a2eb4451bc4836ae35b1660a52aa2f217e9febce24041834c0ebf004a01af5b6c9deb4fa2207029409e365d57bf6e593b380fc92ca562e1f6be2b5dfa4c133f66ffa0a4283baa2a3e979585adbe1f80c5199b4b98301affd00605e856bba6bd13e065880489e6838404a0c658e11b3f005f13c8c5caf06e6f12fd492bacbaaacc05678b29185882a1677519ddd088a91980fcc389097e9a753c5d23c92398dc7e51561d81516b18c06e2c02ab32e9d10248739ee4ad6ea66b97e13780e72ce268090e94fc29aacc6bb780591dd0e985c715f2b2c2d166013b561d43aecfc8da7b392de1cc91b136776d4c8f645603c048b4032512ea36d8228d141b143924321c44bca156388bcc54e0416d9886101d286cb4c8303ab28fed0ae20dfaada1d2ae3313196de5a7e7aa68bb78ef9bce8e1857cf7389756e68b25554abb302e774fb072b0aa50652bbd0a4fee015a801636e4320ab733af2aab96271771fc576d4be73e96b3875b5ac581d282b7f2bc1be8528f0fa72f513b9153976bc94a31f37c5b006ee43edabddc04848741c7ed39742f0551af58aa690f180a790f14d2f53b37c0bf7bb2848c52d5fdc71928c57f5615092f53de736cc26c415ab9c785e41752797218bf88f4b794956cc13d9fda10ac8297c2f11b18695445ebe17154c0b04f45e6d33729629f540780ec7ed4930c09ef1a562460ac14d24be450052f0a4d94a1b2e1c74bc9880a00ab1488db863f9b8e87f25a2f312c442d4ad081029d5817dc7e5614d3b3b9059230b40a952316328e7da98e9e2e67b62e32b45b52543e5c3bf442285b6dd6e4b54480dfd9b43e1921c21dfa5a65b9534ae38a5a8ed84638053fcd731c46c3e21e6c0f912d3867481d0c8129a966a1912fcad0c3ec7ece6d7f8f405f704a969d8fbf0b1b2f3364dd2ec09d15f57c195dce08d9f83ff79b404cbaf8906276b0de004a31762e32f542a4501b1188fd7641b140793347d6be05d10d106c7399a9428349802163d985ed008a58344d2bd2d0d951f541688ea0b5d60220343fdf2461994b361919301681c2850a8418106351354d4505207a5439450b31a6548493111793f8a5d0f2527541135d0388f50768022f6517dac500c4ffb4011112a5ba7ab96f052149fb3c87634949da080a410a8af4fc2f44c190a2202c450b4a1f5fd5a3a50f08a9280946750ee031450d9d9135a1b056cb3a126871bcaa05ed796aa1d0022320294cf50d4a05a841a26545e42e9d03c5ffe4102a50514a342f5ad836a0844550087d628685e448434883714280dd57773abc12c93424214b0b4fa295b3551c0236bb268531407d7000d9fcb0922ea2b25bca00f95f18ed2a197866af00a2524147389ea7b2dc5ef9043a15d286059aa7a026d146375bc4b29d25bdc2cb22e0ab32e20d1a485259ae265e18b5116a5bc84021ca1746ca6ce4adefa0602052314880205e400e5c4bba4fa60f0637378dc8e3c0628f145015df74748c2e14f109a13d6e8b46043fec756601ce22a16e148034f153610ed38e30cae056a44289a40498562652820e18064fadfe4aebc46b41bdc0808273f97af182eedb95f5bec2bd444a0c9edb2c11bcdc33cd89511e726871b73c6cb2195b19516d3cfc81ee5cf89dd4b9e31edb98e1032f1cecd225b27b8f88bb66203040e8919ab40cf374fe12486a217f515044019ceda9b04d96a705c049a8fb9836882ee598b5c7d57dcc6466bc2d6d1016560dc65c40c2dbcaff6fe154868c814008c17c0dca226e68d08b1425966d301a3b4e8f9bd780948852f9e4b8d716b7a49208452662816ea935028662e67b94617d696fb62d010d5dd97fb61bab6d0ad22c9b6f1a9bb17dec7acf85e4a29b96bea7bc3c35ab2cd3b5eddf83d8f8106ac952fb9d186d3fd37595380120287683092a5e1df946b0a95d39925e3370fa53be1130089405100849b619f9a3a55bc2c800dfcff04bd4d88a2678c290c0b9a5b610e0363aa28a2ac27bda18978217e280f89f4357afb98d2635ba135f71d863b01b0ff814e0e288528fa9f91a6b1753a6878ddb724883cf521f3061845d32820ef66bd1bb55c0de30ce8e48b0a7862d133a5653a8d2eeb59a57872e4d605ddeca4534f1c7beb93d00381d7c3cfaa635f2a031e403275e307a54d2be02e53bdc13e0ccf2304563e37077534af28e917a9f8b68b2c955cdb217171b82c8a923297f0152da3edbac650c5d1b908414ff6130a2760004815ba97009354f33942d99fa1ef81071caef31dcac440fd7f93fdd26077ad414621acf4bf52f6ccb4ee1bb0b0a4f55114bbbe8636476ef158f24327fda0fdd2a56bb27009bf0f592b0e85c3635485d249e07933201e9d162107f196669a1649fae0a31cdc6eb98680e715a3a065a0a7e3e42c6fbe06abe02871c1760ebeee958634fa8e18bc712c8ba1a9a720b21662a91e02149a7a3d4a504f10b3d8884207af36fd5205dc2d81ca6371aeb825992717c50f523d327afa6d44f8ae328b4549e16901456f0e7a66069c126641b43c40a19354048959d703ab5a729fad650584498b73122998815001612557a1ae444ed7f06c6a9c64e0b7722cb272c8a9881fcff093bd1db8aca722bae375076d934cb8ad5c61f16aa002ce12bd9b76607e2f80e8a58ccd4124bdc3b9ad94a723bbb1d36a1410796f567cf2158570bd1266856b88e215d3799585739928207604e7b6a8527cd24c45b006035052080b2f699152eb2935908d305a68327f26bbb56dd17b4ad649ee88435f252f0fcb4a8c95898652fee24bf371859b1ede0e5f3e304ebf203c45cdc3d42123fb2e277ab3ff122a68652ca91083e19c6e78a1b1c9c483ab4ce318c9fa922e57b199f8e8390c51aec391f0f2c8a6d18c2841811ff614f6070e246860284cc4cf220a5729c61720b61c942dc042f2ce2d7c1a7e7eaee634e47683ab069cdf5b22fc761e902fe59597d87614c1178f5d16351c01da0b72b3c29cae8c6981616de4f3cabc817e320702f5aa69b95072adecde27c487a47961d5406b933692837aa7e356e26f0b2e6231e7c99f79e9a5223408f2e8599ea5dbc2fe4e9c8e60074ee7d2696c1f01baff768dd1aab19adf881dfc7909843388e58ef3a7a34d7b81c0c58dd417c23790e4d9a9add26848faac8f3614e360d4230afa9124bfd69ebce2cb04728838e5fab1644b03f588d1242dfa2409b8507240be1b9f2b7cac26360459e725c1f593111c2f0ad8d526bc81433511032af84ce47818f21a9b25a76c372aef5865c38058c38b5bfa3d5ada2f2844e8a206d5fd794ebdbc5cace469a326113a7a958af43672e560ccf3fba4d23afd530da9ec2904e1f54758ec10bc6749457eca59467cd7fa1dfe3d7c61f5180384295afed6b342ac28cbf608409b5aab1dcc48045867e30151462b339d1fb84a9fdcc5418d21c7870ddba6bffdd5864324e7a7b2605b6abf762f203fe1272359f4c55fac95023e86aea8bdc20ea35e33c7c8eb3201eb5bc08a9820f66c6c33bb337a6d373ef67132ada9f5209d149a589a24b7424be61106d98f8a45c03cd4a70069944f430cca07a12bb4f66788739d374cbb648da417a58708088e0d186f5a47f6c30d1e7358b19f65d27eca04f927165e01391bbba01a47fb3f1f60719c34ff7c8d0491ba481f2652098aaaa844ee631cf7a219dcae50a35d2ef144ecbd2b3279e64899f2e7170c1c92329ef8f43c38d98609abb98bbd8251428c81026f440c5dc699f46869a60db1bbd7cc368a14b7817aaa29a9381897c3998d8972905e4a3f5a4388c353ca931dcef75bbb9a0bce7bc5003a0d989cac46f66f0daefe89602406f46de47ad5a2689c1f413ad0d038dbe63f2dc39a6892f54375c8de8c6403c6ef64caac8752b43e81e06b499d8c2c499b25ebcfb3cf9fc7f4e4e68a2144a9e65633167860ce477e66e1e686c72b9224b9cf2f88068a502840105089429939cd16abf23ee5131cbc731417d4daec546445a9aebc7e9920242dde50ec8f9fccd165db97b3bbc2758ac1d3a5be2a0c99afe1c801c4eabe0e5a64dbe1e08a02008ab4e0c44fc9bb06a4fd0615fd079052f0479502acceb7f5ee7ff5feba06d19993e4151599a1065acf01e0019d67467f55b8a5e333158b6c5ad61da265856eba6470d201950f6f42f8cc58b3c9e72a3b70ec9c0cc7d9c8de55914915379be3ef181e9ab3dded33dcf191765883b4078566309edca81723baed52b4131caff1ce28ae7530eec4535efb719b27acc3c7333644e19d237dd8aaa841c6a9c1e0838ea28c44c2620c323c10673f65e3708018af78342c7d5a7199d6d88838dbdc9dfe42abaad9b65525dfa0659f34895b1aed3168331db369a650b402b88b41c6d299a0c98c1e71ee1786e735c4dc60981c95b8cd7960084b50fa6417f8c38cd005d3a1930d651c9602fb126c7a50e8afaba2597264234a57d038a4d268d3ac554736a295a68d0e6495294c4f3ae7f03278d958b3373b1bd1e298187e987d4b6d7d22525c2f8759e1b9df5922463b94ae671dd24c34acb02ce7ecfbe68c6ba85df4f5ba91353f43ac6986e6b014f0486158f2a7f2d9e05613730186d3d19194dec98d524ac54c922ec7ba59982ef36e98897776a92dead567b11eaba10d48348fbd6acf71740544ef088d1242ff2d54df03591b37846dbc21773b71aa93332456a74ecb6fe791e6a252b60a2ae3aebd05a99f3a0108fa5886c6c9c2bdae2738a6015d6a8185b3c11a752c9313f9626599fb84f4484d454166b64099e4e8383bc5f027e724340c09589a92b2d6660af422aaa1a7ddd2f1abb5bcf51f922e63320a11485053e921a161cd9b822c7c54e960fcf895a3b8ab7dcf555a1917cf8e8c34d64e1e7647b34364bad78debc216df8d694952bca2e43a5d1dc4d36e58e96bf30b61adf2adc5cd115edd8478e56856acfe64c1ea14ace8142e3351b313b0562596f9540866307387962a4424d1a808d406e0ee80da312a1dffedfb487c1872fa102f9acaa3d9405ee73fe4b19e478ab69ffecb4b1006319eb42aa0d0ba49837b5a18df19ac635ea03b308e010084a2fb59fb020d44d483599bcbe1fa25072e6863cd144d0ecc864f34958f05bcf38034fc6837325cb83c53664e8622af5e862f23037d42c67e1597017b0e86a6b91be51639948160e34ecd33276e3b189683367e65a751d68c7235bab777001735b9d7c44cf41c711b1490b810d8b79ef2657fc27b4f966a41949e3a701b207a1f6ab4b563c7d53cf620903f1e03f0a50095162db8394e54721de4a484271fc74bd61650cbf6b29464e7e13fb791acf2d8c926722756a97a309ade68028b2c23c360909a602553e41aa8291361c68486b0b76eaf1d9f45d5fc56b134097cb5d271d35e8729a11e3a21271f22404518314dabe25831f092981f3e0b69da8163e3fcee32410ad644e1b8e04ef80c22229b36c5e7c1d4a092b8470e6641f9998301f98e6b8276b4ce6611f96dfa6e72907ac131e6fb5657f56bed1d21e07a31db74d24d796a6e9c7fff57e791218cf3ae845a139200bc5901da4f6be04ed458a85a37eebd9ab1c0e678e6d8f5da008cfd6fa7c24d1a41d6219772a8f2a0e608464cff4886c45f1fcace89be33654b88370059c05291ac08f815f44378d888fd78d26c49cddfe04884f09e9d5c4b172753d62c41aa7bf988e9cfaa9c651f6b88896a43783d91677765a0b17f84ea48941432f7665d4895693882a6a5a417ca035043a8c2338a145af4050668f47c529fcc6312065e11e3e8c696363aaa6f613133fd4c8c37b1446ea625cc8fd1967d08edf87c0b6ec0dc8c0bf63d21c09c539beac70c1a92505723c4dd3417e756441e18d8896dba019a0fa3d82d64545b389f59a365b6830c516c8285e7a472342e5277243d51597664aca5be1374b08b4b271c305e565f50098e1b6984c630f6e43c8840ee0248c3d545c0e8d094d7b55587d76d3d4f3964e5ec18ec67997b96220b4ef2e8320aa09f95bf6dd2df9a55c46eda7c3f194ceae7c0d7843ee452a207919f3e894c8b73e4dab887788e7ecdf66d24fb0d8fa3d2325679e55e8c0e483e0b6cad020e96c45e0b2b70b61a0730c30c33cc30c30c33ac9ca72df7fe8317a9bb39252525259709cd80c6c584c6c584c6b50de701140b4c0b780b7f08894421a5d118628686055f8c158ccac141c249314f86f697257f0c5df002128c2a13b400052b58810b2e50b6b847b849b35d7b4639473871269639549c856b849362cac631c13e5ebbc4c231c20793b43192b8fb9c369b67b8457852e1248ab7df9d38960ca708ef463b468c6c541ee544f8281d653a8ced21c2d7e8e181ffc55ec9dd1dc20b398b74467510b21d6708cf82a7574c8fb9cfa11cc315c28bce91ad345bcbb54c121c215c6b982ae5b289e1b335084f3cb45d5a88df26ef04e1bd4b466a3454cf8ed5e002e137f60c2dfb66b439860f10aec7742a8e442e69eafcc1ff2c764ee1fce025bf747119d41bc52c85eb839b1c887dca281aa53c0cc3707c70aad33d0e29ec4a1a695eb83db89212425378e6be4d0fde27edcf24a6e6c1b1e4153d3c78e1d343738bccddc1496f0c3d835ce9824ace0efea4838ab3be94e1375707a7a663e5fc6943c0d1c1e91862c858a293c34cde1cdc105ff3e910ed31454fb0b5293839f852628f2f35ef02c334b838b8f9f1b656ce920b56400707274778adbe7ce271db09f6dee05ba88fa88621df021794b7c1970bce0d3e8e17cd2a733131fa27d832255c1b7c341aaf31323ac192206b001a708001a008c706a7e423e69ad11851fa35b8e92d99b466e9e86be9d4e04bf2c9a0a18617f94a1a7ce9194d6bdb7a106b3478fd37a1a99534567268803b83bf3d6a6972f6fa89314371d2b5f69252067ffa2385da0ce9d84106ff2c83a694db31f857d1434369c5e0d46ae69864a799ba307853123acea70306371e6d7ef1ff0b4ec6a833c8c675bf7ac1df4c0db545424e16e982977a4c72ca33170867fe783c83b7e0fa4a55ae4fbf9d472d785d114ae6c52c38975298e8c8191e7b58f0e5d782a4d0fd151c77891a7923c60a9e5b8c18c6ec1b6ace54c193a894dd96192af8a04185c9488ae77f0abe64c73f39266965560a5ec66ad964b9fe2d1a05e7c16a68913fb9dfa0e0464a6b6af6ca139ccdb9fbe62a27789e2743cb1eb6dba5d2043f23244465ea14fb99e08620da5ab295736996e0dafcfc796b72a9b412dcf28e998f71127c0dbb8b41fc4282d7491ad544c8c33ec9113ccd3086c81a2ee273c408aae3b8641bb122b89a1ac5cdd69a544588e084e81411d619e6e80cc10d19927b0c1db1715e089e4d68d4a12b9dc64c105cbb680dcec2cb630b10dcd09da2edc2f7036f62229a64399fbc9e0fdce0d8d16904cf20a3bd7063844ca7868e330379e1add86be58a51b3c5c62efca011da3795535a6ae8c2cffa71cd9a6f947372e184479325f8cb3d88c1851fcdcdb3b745d3d8dcc20b595679aef1cad5b6702e74546e4d311ee15a781aa4a5cf238fac490b3759ab584a74bc7fb3f02346ed6e8bb52cbcd8e629e653040bad63e1a458fda64c0d0b1f86895219ab83bc9d5738ebbeedb9c2c7ec1857b83d3973fa36cfc12aadf0b6bf6385354e9f6256781e659a233e3f8e7715ce47c9d02f4759157e8a315da3a0e9392439154ef89cab26c6149d4546852f9172881839a7707a563bf27b7cf68e29fcf4205386d408277f297c183fb3f19f21f785144e4d57869e1a937965147e88f0e917da4b43220a4fe3c4da3233078b84c24f6b29b9a77641e1a6c494831871b2d1fd84db187834ffcbc0b1ba9e3036e830e339dc4e389f53aa36364d336f39e1e694193eb7dbca49bb09274b689cec31ca16bf26dc8a6c953617e53f9970a3d1c9bd8497ff07269c568dcc612e657b8c4b781a12a9659b39a786251c3b1bfb7ce3db5a57c2197f492b352b25bcec1e7e3cd45a63ac93702d6da7e39390b2b14ac2edb9476a3d6a24fcb4cfd2c03778b507125e9691c79f24cea3f3081f2493f6982b5afa7784936b2ac7f0b071b7a5119ec7b56493b3b45061847fe10d728cd822bc741c177f961ade5084efe9572d54f892b413e1bbbdcd6c4ea92dbd8908c773b0cbf8ce43785dbe59b423a3cba521dc587db1159eac3b5708bf26446545760c1e47087f22499294f1d5566c108e7f7d8b85f9e7d552105ebb59105971d43895817063e5f9d031d4937d00416cece338ae817ff0672b5dda98cbad1ae8072f747aec935cec839b791a4ab4c62934453e386162d228971d4f8cb8072f39ead5cc9dcb7f433db85916eeb637433be4f2e0635c15171ec9c3320e0f3e18ab9cc9caeac7be3b78a2619363b4f49cd4b3838fef616450da8da37475f073a7caa58ba152dad0c1b594c3c4acc91baa3207efa39fa5904198f5b41c1c798b5932223d0e721cbca4fe8eed633838dd20e4303926e951dfe06afab746fa49fad20d9e4f454d9ad2b3fd6d701e238fc7ff0dc63d36b811c3b5e47bbf34da1a3cef3af7cf91a9c1fb709724a7096360692053abe70a231afc9445a6c2b64f3257677036b4dd77c619c48a31839f9281e498256570c2245169ac92c10deb763ea11a83371563450adba4492231382d2985a83f94e48f738085c1c0e09d4accf88d19581c01fb82772b593d4776378f792ff8e3682b97e4cd979ee9823f97d263bc25d3d043b870ceea924f258e2df81f23527a48bfbc59d682935db3b95b3a2d0bae770eae9952c7b0e08b78cc4982dd69069f0c76052f1a4b7b78b558c195fb9ca2c2af4dafb805ab829b630cc96e1efa55ea50c187e21731bbc3b9953d057fee91879e7415b21c26051f66cc1a6ed36951f0fe46a32bdf7b8c313228b8961e5cf60d19f3803dc169f1088df9cb72e6a605cc09d604dfec35a68b19461d454990674cf02cb7f4dff5c4e64d1e0b6c09684c5d49e471b800a6041fd9ab7806153c1c35b8646149f0d342f75c6b7ee0957382dd1e181268c9d6f431fa7bca0b5e900017c8e00537f8e20531b8c1f957806d05404002015cc18ee0855f36b7d4a1720d62850333829f26a23374c629826f8dba3de4f4b03685be6044f0e7eaaa44f267508919825f8e734ee5317fb69a85e0e54721fafb3c25cb2d172c084e65d1c86a11dc345380e04ce714a984c17ee05d0c7d65256216b2623ef034b37bc6b081bdf06c935fa5188d7b45f342f1c26d94c5719a7d76e1d8a72c928dc2baf0b2ee62b498954f9277b970fb7eb23f0c142e7c104b452323af4f0dbe851773b9cb75b06ce15df86e1826c5ac4d9b5a787df162e1b91a4a86d1c2dbce94ed1dab789669164eb8b2e8161dc4571465e1a399f1541b7362e1440d6f3f36fe9b53a460e1847093debdc2b36d9825e3f0b8c2ab102bfb36b6cd658f563849335f869c452ce4c20a37fa1a6f86d95c851f414c72f0987ff74e156e085e1b35354dad5f77a970ad5274d89d354b95d8864285a3311323f6a468b319a7f0e2336504fb0ef628dc6a2853b815b6c19878570a7f1dd54f239b3cded3198a149e24af98e327ec233a0ad7bc2a948cac872c8f287c47ad6a86a042c1a5a4d690621e544603234381c24bad1e132cc2833d100cf50927a5f5c8b28b217779c2eb8c71d924a323c76485ea8493613566a3c5486dbc5c284eb891af4f4afa1f746cbb09afa3f85f5abb54a134e1f5984684d8f249c36b5aa84c385ac126483bb630c155acaaad746476b98c6b5feacd32394d41a84bf87241a53346a7b4ba2ce14fe71c5e24c83f885855c2793b4742a128e18ce3adac068eb1e40baa49789673c87b8ad992843f61b64172aa5958ae22e1038d512c87cd552cb50c19c8606c3500031c680614249c6cf95d35493ef737f97c41835bc1abe08b2f5cd0821318a01ee177474b86dd7184336b292bc2d62c661ae1864621f7639c6151c2087fee521d83e0297a7411fe854916edca83a7d82bc2314dc9dba56312e18770cbb1213588f04396b58791d2a5670ee185d8953cdf764ad910ce46f6ca30b914c207f7eadb195a08e1b45768ce78c728fc41380f366535e4b79042045187e7941eeb4078d5d3a629db3df41910deb9a4a3f0ffc11579e49d34763416fde0e377c9f361227da0b5b3850f4ed6d4108928116a9fa070420eb73927f7137e0aaaa1ee3594679fce138e45c9923cd147fb613ae1e7b79432aa59adcba040062b9801058c07c90947bad287cfb4f8c739f583dc84b3317d847acd96b4660f5213dea749b98245544edb5ec039c84c78a921d3c77e7439c61c4c38e9dd2e224b94b6c6b98497a24743ce915237f058c249513208e9eca1ace52be1ac4dc84836172e434f0983f57dcc24dc4839ed720852ea939184f3af9251393b049f48249c87a11e47d7dfe6fc8084d3d330bd671456a5ef4778e7a85d2bd886b18c7584976ded337c8594b7b6119eb7f86aa6f48e31aa8cf02d5bdea41a53f85575117eda1812625295d9a02ac2f193e449846fa1a18d86a4d8701311ae54e8c70d2cc798633b84ef5b9652eee428e4184378793c48c85e2144c4aa105ed6601969a699985a2284dfa9bcac2fed527906e1869a761845b22c4f47109e667ca934dba6acd0098497ae3bb4add778d03c20fc6c90d6213d5ffae30ffe9cc55423269ebbe1077f1d8614e233fae8b7e983134436dfe4ccd158357cf056257bf0e3fd3a62db3d5259f4e0a7f990a96d953c7837edd5ea51ab2d4af0e0dcc685c9dec1f3958e311ca590413bece026cd65e94ddc4a93a30e3eb6185ab1432d3d0c1ddccab098ea33f73b90e7e065c90c42f9c772f0c3cf7d288decaf4f1cbcbeba2812c270f0ffc61cf5e65791e4bec1cf20a7b6e67937381fd3e5d77eb6c1c7290387522525f94d36b8ad751943953a8a5eaec1f5a97fac1ae61a22a7067f5b528a4aacacda5d1a7c0c22f6c59482063f878c635a4ab660d933382239dd64f8a5d8c8cce0e4afe83845668b7d193cb796e065f926635c91c1779873acc5305fed18830f3ae4d47ea15296550c7eb8a3ab737c235e5286c14dffae1583e147a9d9a2437b6be082051080003570c1175fb880c020bfa09cdb44cff66833fa03e98522c009b20baeb6c64551cfd9336480905c70326477c687ea06c6050ac82db8591362c335faac2ab55083cc820fe5aeee4382a7c7512c389931ccf08c357905374f466f9d41b2e90aa5150a292d3c5345cae717590537f6353e87e66eb6122ab816a91e597414d3249d82ffd60d5622c6de5811520a5ed79c550e498d822729cd69b88f515c24149c08bffd2627a39d2ad7802df20927891d1b3f5a904ef052f97cc7a434c10d1315aae5e9aea94a269c72ccf24972c89c608b309730ca9b1ab4b483dfe681548265125cff6831844ae4ef9e344d05a39497807d218317c822917047c83402a208b2086ef00d55f564a5d21922b89a6d1e877a087ecd5f886f97d8a91621b81639cf658e36bf4910fc8c8c5cad16b53b0d042765c931760859e15ff2032f8618c1fa72237de0d8e7ef740f8f21c4bd7042a61c19bab8c30c2f7c9c3dc4827f6417bedc54c819a5b02ebc97f31bfb60fea1712e3c2db5b81cfe6287ece2c2cb9a7144c914e32d7cb4d2a0e61bc4b411b585b3665177eed7c943ad859b8287ec20e5248defa485e358b4424ed779a39bb3f0a1afb8cd947b467cb270455d621a839b6d8fc5c29f0c9a16fd86855392c394a389471bb357789d2368d6f099dd29e50a27ad23d2d9a4d70ab7c291ee984556f82054bc556e7f153e0cee39ac8fc64c8d51857fd1eb2462264c456629ccb26fc30168c001182000010840000cfc37e0043240410756309e010420c0a1c24b1e352ea8877495c053382ab22659b1d7ae61884053789f2c6323d6841cc2054be1631cee28bbc32829bccd97367cfd5d388eea285ccf1453822787c13534120c1485f313eb43667708a99141c05038161d24e78cb5fe490282c2491355d9c1f959840437f842066604fc841f9fa167e968933ee509ff51e798314507f128a89d702cb64cfebbd0d9eb523b20277c98398688d91d8bc7cc26bccbe141869d3d9af0522d79b0fe960b3196093f35b5e39b5c95278309a7cb3ea30e7809ffff3465b9a83d29394bf835e1151f29e7aab10f2be136aa160fe14addc3352025fecbe0ba52b28e9d0027e15caeb8e0a0a65b638c0428092fa26543cb23dfe1fa136c550123e1d4b54c24c9f5f2af2d0021e145cd5d0f3d4965087c84174e42fabd32de2eab41a023fc961cd4da1b5f4e0f35c233c97f8d728530c2b506a32eefb9638cba10b808373aa6c5f556dda744115e700f71ac61c942469b2030117e6cc72c296ef6512d44f8695d52f02c39c45417ce031ec20b61c356fa9a9475493ca0219ccd9d95633cd67cea0ab010bee5486ddbe1d305b52021fc697c6134d4c62ced4fb0655040a28083f0bfacad2ae547df890109f01350178021a020fc9ebe09cfc6e82be7dc0103e1a35895dad01c867c0808c72452ff96879b7535bf807ff0a3c6f4307b5e0fde0e2fa01fdc148f32c68f4286d9f83ef8e9fa2a58d44e1b900f4eaa71a41a630cab01f7e05b4ef3615d3ca6871036a01e7c3019784c99a13ee79c3c38a92754ecb29a4879d38078f06347787a88d8bccb25c03b381d3663340dd301b48323514ed2c29727d832c3052bb8c1784111b00e8e276fb9b7ea0eb68d136c0d01e9e0c318be619f7dc3dce609b6be20810b8ce900e7e0860c22d954b67f327a82ad18bcfe0c7e063068811b00328172c0fd552c3240410b54d0821990e00666fc178b058c83b32621d584ec0840000210c0030358c00028908104fc37c03cbd02e1e0a5f412793ca43fa56f70b3fde5ec93aebdbce306b7330709414ae6be6f83d3696e272b0d7c629e0d3e0c591d530eb5d99135dcb1311b934ad4e0a42b7b98d271c65c9a34389d2334389d83bce46ef994927206a7353c9a4fb9ddce2b66f0fd71248ff29e327856b361254506672b877c912b4608d918fcec2987e059c63ba4c4e067ca90e5f1b73d530983631943c91ec3669062c0e09987ead09b6a21ea2f789abdaa1e62e20527f4a650ab1cd205b71f4dc8f0e83ba490c1053fed318c1ccdde8227f218ac05278966181997b3e08d3490b568f93f628b05e7563de36493225da257f0254334b064a25511b582db2863ce1ed266e3a155f0f38fcd6453a9e05b5f5606c9814a6d3a05c73ffd1e5ac55ca15229f82af5c8c5420c13ea8c82172bc7181236a3a830a1e0a96a9b76c48414b67c82a35e39df5fcce2afa513fcfc7d6aeb40a39b954df035e4c71a4cfaf2fac804ef2393a954e85e79e0127c1c53688b1e1bc28751093e944849f2e528934a4c82db3977cede2cb92d8848f0debbaae453ad63acf1086e56ec0659f1753d3182e3ef3397ca52b8f7ae08ae6df94b248d61293b2238679929da9a4858c6109c0c2b66b056d127b82604ef6263b4216b8ab0c982e0a75411b2efc32bd5050457e44b4e73593ff024564697962603cba00f7cfce999963da6a7a517bec563748f3e656f8c175edfe5eccbee2952a25df86942e53bbf2465e1a30b273cb6708fe6910b2735c6e1c33bd86f4ee3c275e423e9417eb0daf3ddc2cb199944fb6a5b78f6c953d2a429fe63b616be7b5c588b9d917c98b4f033dfc74ef779ed2a9b85f749352daac34c479e9385efa1b5717bfeca2e3ae062e1ba45690ce9374f46be57f00216a0e00e070b7f3367488eabb25535ee156e8cecb852944d15dd7e802bf84e2d9b22caace35ae1a67b14339217b92063138e157edae80d1a568787d0afc2af699c71ce95d2cc7a2c9c2a7c891a22678ad54c9d63854b851b437e1c9e197bad8f8a325c4adf29fc1cf3e518292a6d4ff64ce1788e9650edcf95c2cf932e774fca48e1448ded9a31dd1b85237e9171a6b16ae144e183d8d45d123d62b844d370a170d4b7f3844ac58c9466058597353ae6f2e47dc299dce87358c9776c7f9ef07fc2c6980d1155aab94e78f9277c67d022393b4ef8995190702dc14336f336e17466b2cad1efd384f711d66fc173e332e1484b7bceaf7d3156d032384c7851be194f26e9b5754fb0751684bb849b59b9ee1a86344bda09b6583003e6c159c28f91fc40f3ab9f6ace09b60c095a8002133c411c5c259cf37614a7a511a9bbc151c24d29f44a4717ed26e1894beed0b5fea56a3949b829cc939896dc01abf073a39b3c394223738f2a7c9b7099518af4f8ec543857e944925ccca6c8a0c2cf9ed72323d23d85f3bfda8d362ac3fcdf9ac2372b6d60dfb3b19e3ec192e00b19bc805cf085e696c29f9461370e6fe12a34a440be2b2ce4cc5ff951f8a61552a489398fd52e88c10bc69f806945d1842029f7a61e0b7e3183f25fec0336146e9094b4ed06a7aa0d50782173cc0e11320ec9d34f386bc95187b4293478d2137e58ca8d3ca22283838213c480046adb09365d739d64989cf0cfbdccbe3504ec26bc13c930f3bd59c6999a70727cc68c19e3c32f2df8e2652083f10212a860fc1704371394aca7918a86c18417c2afe6bc39cdc9e65cc28b359137c50ca5fe2a96f0cd3c5786c9a06e9253093f4ccc23ff9ea4845f3ebf769161842927e148a7142cd362347f2509c73324874fc9f3197d24fcf7536bbb7021e18a7a5b45fa9f53cf23bc1497c9f2478e702d63b6b0d5d40d1c8df07270f70d973384a8c9081fcd5a4a90d011a12d8bf05ac66f8364aa08ef32e3d0a9368970423cb628218dca7f10e14f4d5bf21865c241c6211c4bb2197a46de105ea9b66cda14b5885d21bcf5a03d298609e1784a475af39d41f833d15182552d08ff62a3adbb6903e1f8386cd1d4f4b9220684232ac9b1fcd857cafa0f894cf77ef03f4850b954f954b1f6c151c9971635271f1cf5cf1c43d4e41ebc14c3c9a4eec96c51d383133dd59a65b5960cc53c78796a835b8ec143882a1ebcf990c5b2b2e243bb8367dde173e7901d5a991d7cbce2338dfcf346eaeae05874091d3cff9442ee7e6dc7aecdc1bb68b3a5f931e4e0a564a22173acff60571c7c1492a61c63f8f4535938786ac143a6b52af99c37f833133ecadb9fa9e7b8c18d5cd7e0dc34ac41dae0ada5aaca6a9b3684880d5e54b3fca1ae6294acc1ff5c6e7196b305b94c0d4ed218b59861747f3b0d6e87b98cac298306bfa6c33a7b4db0349ec14b91be1e4ce8d60cde68fa24ee3065f545b765582b8a7bbabace6c60206083372d40c117a3052850800c58d0001cdca006485832b8112606f90ec11bc38a418b61cc643c6c50c906360c4eeecfea073369ee6f30f81e7286eb589b5d1e7684fd82d560bde08c7a8890be55179c34762a5e165a2ef80d335a4677bf2a55f218760b4edc6b5c5a4b71b9cc09b674001a70800b4c9080131417bcc0bf051660810bbe680006084000bd0a8080040158c26ac16fd0b0b562f26818fc012ce0109b05cf5289d6f5e7349db662c18b78943699bb2b40c190c133e0bf0175d82b78da1923ad183953ac15bccd696203c970010738b155f0b32fd671e7cc171a0358c00230124b0527db81a5162d4dd08244ec141cbf071163225a54b14c0f2b05377b7a370d2af3198ac5878d821b551ac5a19a464b2928f81bb4e543e76c186388ea0efb043f95b8c5c49ea855e129d609cea79a982979ca4e6c137cd190da63aae076d72d6299e0ab660bdb61dde53b2583188c09ec127c9f98b29da69a11ab042f53e5b81abb069e7192e03dcaca525b23c19918a2d895a763709923b88dab6108991ddbc3085ebeb4ac718ea6d22745704e32df24073311ba8ce21d43f02ea5e020ec2a32305108dec420275f194b2c4f101c7f640d33c3dc294db58b05828fc424a4cf6e318728d81f78934cfefed53bc1d6fac0490f36a5139595caf109d615da0b672dd465e4b06942fcb8505e78af912a6ac63c2d7417cba41821d9cf545df819c4321ec61c124ea583e662bfe4e939a3ce98fd93c10c5869608ad025e5b8cc241359bb6581dec2394b375e3321a38b0e5b38a19549a543f95d4e65e005830524d85af819030723a6854069e155aa10f375eb889967e1bf9ba64669c89cdd809040065fb820060b0295859f4c267c86f138c17e418217fcab8004672ad058781b192e2236eeb631b0f07b32c5e3e0d8afe37136027d855be929858ccde30ab71e3bcc41bc6f85afc9cb65c426871433ac70430ccdf090bb188f55f8ffa9227f259b51165578f7ee8e31ccf8c427a9f042e8cfb0b0b8e96aa0c2ada90c52a2a54fe145d330c92a368593432b58ec724be168d87649e17f4c7f9c1a4ae6ec76149e8d65cf4bb7d8ca2a0a673397b84c87cbf26a289cb3dc39c25bf8dfa8a0f0453355061fea279c3495535bb265cb1c4f78511baae7106dcfd0ec84f325fe9e1b573fba70c2cb6ef5affede26dcd290982743a70927f643c7573949c660cb849341aa08963ce63ce1c2841b9742394e9d4b38b32e651316a925dc0619a718bf8bd93868ae95f0d2264bc5462bd6b6f24129e1265b9fa4152265ec9d9e842fda49ccdca1c30ce29284675d26a59e34d2a7250d1a093fbd1f07f3903b66ef760c0a097f2aa5479651ec118ec34a4bbfabe09223161c39c279edfb724b13b43ea7116e6d6bb4c79f3b37d630c2edd77490126691d3b208675385484fe9bcd22c8a70a52cdd573a23115e56a7d50e0f5545634438d519458aeeec219c07e19a7932caf86c13420de177c80c07392675f557082f2afc6ab86984f051862e1e3e3a0c91d520bcefcde358a4fa1e8304e16948139e2a1b26bf27105e568f901d6600e1468cb24f7e3985dbfe0f5e8887bee94d23bbec071f5408b908e9434cb60f5e4773f9103eb87953c5ce5fb3073f3a4c0e600104204091a054a17a70365b47f3280b31843b0fcee4889dc3a39bddc3281e5c9bf5e86f5adfc1ebbeb56df49ae1e5b583efa81e848fc3749d5307bfebd743c80c33a52a1d7cf039f8c534ff0956cec1b11799b28a3969cecbc1f9346d52b1222233c6c17f18993394b47c9e378583e7d1f7d2198c58cc287d0331b2bcb793eb06229644f6cedbb6911b9c7f6684ae92433aab6c70c372e6f033a90fba06d7c4b378f0183da81abcf1142a40681abc9c1bfd68feb19c26a26870fbfc62dd7d0420809ec1094f5a0dcdb1755033f87d113c5ca7e5a065f06366c76555f1e29d4c342819bcb7f03bb7b7f590ea0632d031f8314fcda518919184eb102a06277bb32d9c879cdef209b6d006a001070ce002e30bff62d440062d40810bb2340cae865023d7c842442818bc4dd529432aafafef82d3021458ed41bfe06568e1410c96b11dac136cb940062490c1c7400631e833ea052f3bb55f1dc378f5f50dda05473e63a4d277da91473728171c5f73104524c7863eae41b7e0a72c4d23924a6e6519c4c00517482df8969d1ef9852899396d2706cd82eff197a3db5c757834161cf1cc9b53e3c9f678c361d02b78252215eb1a5c84c85bc1690c2af5e4be54c1cd20d66d2c9f938c6a2af8e0256d632f0963f99d825f6229b52e7aab67530a5e650eef2ff1176f6314fca025f9257d4bb3040a4e46121da6b43ec10d16a646fb5cb227cd097edd5df40ca4ce2ae434c15f8d4c66d7284c701caf668859962578762984f86910330e132578ee9ab339fe8e922c2309cea76b2437418267aa591e47cab619ea113cad49a6dd993282271b2be4f88c2278216d1ba6168bd020028312c1bff8d0672953c21c6698abd20b3a04cf325eb5c6216c8105fe051618c0021470061582eaeb579331ca4eb075835701096e40484026832f3e062d40356810ec8ff93f3de42cf95620f8a142453568f0d92e2de80f1ccf31fb038796c35414a03e704de32ee3cd91e9d3ee857f8ecbeed185178eb5a4986cb298bbe0532cc58c65ac0b2f324619639c71850e3717c6bbbd4c99bba9a68fe20412178eb94fcc75a9691b856ee17f08590ec237e8f4c4e08b97410b2090b6f06175e4a496f3b5f0caa38f048d6971dad2c2ef8ec8da369741276f169ead4cd67e90a13cb232e0849485dbf3a0a1240f21bc5b4ae12163e1e491096911aa324a242cfc939c4348d50e041630000730e009f90ae79138cea6b2211cd2153ed47e14958346a4cab4c24b935103edad1c7119ac705423434dd963e11965158e54d8ccf113ae35e755e178f615759ce554783979a669fb07010820874485ff21a2f78791d0f2344fe145ba4ae7471f61214de1cb25cb17fccbffd5a42c858f6a82ca787bf99a0119fc173220a6852485f3e062509354113173cc841c8523fd1f52e70be92a4414cea34b31b45456cfd1134286c2c9ba881ad382da8be5020d0a2f39cc7479c462ada52df02db08006fa13ae6b9a3419315b45115d069ef07ec2c3fb854ab3aa7c0b36d0093f2d4ab6c98d32456539e1a645aeac49bd4295d49b30f23d8cf4d8644d388f67da53249f98ff41240613200001be78f3850b6883d099f0c543457a2715135e54659cf517ed66cabe849325cab33f2b7537b42db15597aadb6747c9e5c7b12476587827d8fac2050df8159021832f5c7001021080055fb8e020a18b90233b65f69c94f0cd66d4c17a27c711ea49781763b42c6722ff634ac2752cde3836763a127e0acf517c520e0a209169469d4736e347381fd2c7fba9e454ae3ac2edfae8fe7259231cadf9d35c1a41153423bc9cc1f6e5708dd232caa5a017e1a750bbec8fa13347ef02179015b422dc8c3ea58c695ba3e43ac1d6178f821383197c418218a0802b062b40810c669008ff529a8d1029c58e616b44902a2551d1bcd130dc8770da1ed87d76076d393ecf807fc10d3440833684ffa55922a667f43ee209b648700219a0a00624a8c1b740ab10aee5f2c813a983977f02198c0be00964305e803790816113a2cbeaa8bb2d4bb1ad748db64f294ca6d3bce50964302e4000029c400632200001ec053d08e743a61bf96594db6341389a610629889d47e840b8256f139534a4bf4903c2f791d4ff301e7f70ec32ae0931ca7cfadb0f4eae94d0ca4e761f9c343d9beb525d57a5f98099b9ca4446d6da7564c43e770f7ecd868d1939cc92ebe8c1b1efe9cb15ca26a48c3c789b793248455d0eb1361e7cd478a2590e973b38153edaa64eddb6832f31112634964a1b235d07279f65cf1945d6ba8daaa1e9e0457bf5cf5b1936fbdd73602dbb4cbe4cad356fcf35b69031c81c185a0e6e9e1a5bf5749f2778e2e0cc688e29a7e31b0e7e0a198f7bda8a6b34cb00022ee83778e71ee2603e6f837f000b5cf04503dce0953bca46e7bd520d7382adac18547d2183185cb7c17928a1c73a54fb952502cd06643e44a342a64d53eb35bc2d558c555446abd06b702b470669538ad4e08c55f8cba9b34e8357eabe9a62b34b82e241a3c1778952d79872d8469f3e835b0fb2c845c6b211d38136839f516a30f1df29e542ac037419fcf38c2a47830fd3b9800524a0010acc6ada06309b0c9c9659499686b8de1493064a8fc197cd413e494619645c693178951e65328bc6647776183c3b0f4d69424c8e301380005ffca1c1e0c3e86f194af7e66c321cfa0bfe798c31a5360c8fd1e4437bc1c9719b4522860c1da62174175ce9bf509b341d45c39a0b8e89c474aa6be8141a5bf02fc6d460a1b3ad053f2e6dc80dfa6e6db29d85bebd55d32d4ea4bea6c63e9ac490ebca63754d96ed7cd1ae265fc1f70696c27430dfd41b40b415bc34f2117f1e7615bc4992afeb6f3c2e639b0aae46ffd60cab26dd834cebd053f092250bd7a8f23a9a54069f8196821f7129fc2201d6868e821b4635460ab9464553d6d05070bbc26cb56b347325af9fe074189fedb4102d5ab973682778ee32f1133329a21fe38b71011b7413fc937e4992b5dc6682f7e26213dfd9226a3bc1165906b87a09be858f6a1a294709cedb0633cb7726219d2478921661a2ac6d6da691e06c1af11c36ac32b8fa084ec8ded3387ad808aef57fd61c35b37d0617c1d50e5759a29444f0bca53f926d0815f586e0a5477f3c12c25afa08c1ef764dbbac06a9371604cfc51b5d14bf09f75503c1cb5c1353bbd867ca1497a17fe04f0cf996f694551ba4cdd03ef0ce1a9ba4e49414824d06ee856f315bb29ddc5a280b0a5050cc99176e374ec9e3d15845f6ecc20729b4523ea5c8e0a2ba70f2c530da49836d8f24173e74b18c5170e0c295d3b2983ac99d6ff016ae7796cf6a058d5d9b2d7c0d9edf431eb9bcffb530653e4768e1e75eb74d99c2b36025e262cbb2a6332aba676d2da6f3ef64e1672c1e15dd92fc232f16ce65e03866e49ad3be8585971936b2d33ce56e617e859fb9619ba50769ec1e5738d7c025bf46a4ef07722b9cd83ffe1ad9d0ff1cacf0cdb4d32449b95578a1baab518c1c33fca60aaf53b2b5481f5306c77903a762f0c9b2e7caa89603a3c2bbabfc613c03e9143e96986206cd0e627fea052f18a6f05a2e3d3c94b7a8309a814be1bf439b4a313d76259f49e1468e97b0942454a3703525896e93c72a840fb2289c8c1af972d68c4371ca64cf79ae9f41e198d979494fda49dffc09cf3d976d7c8791b8c09e7022d6658fc8491a4a783be16cb97cc6b8c29cf067b68204eb18336c2b068537e185499a1caaa6dc51df6c604d3879a2564bbcd2c764a1800528980067c2cb281623524a5ee9a7c30363c2b5b210ef9a315508365fc28922d96eed1a51ceb534b025bcebad0a7d16c28aaf5c09b727c60e19d6730253c207b3295ddd864a9a734ae04978c122a4343331bad88325f1662cf9824d58e84747428d1e1ba5b5e4136b010a580089c4b7c363f173f0234aa952be317804ec88e733fc7f2c8f071ee64654de5d22ebb2eda91eee7da5629a433a66849faa1a23b9fcf92b3343e045b8b6e23835657a9cedb3225c999467aa7beb61aa7022fce49acbd187f81c838311e15a6730a129e30fe1c9c7dba784c5107e68bc25294838ac3c29841f3b330c0d3badcbdb84f01cc8794d37f84e9f0771c7a5f6b02cae201cf5de8acec1a7ac0f849f1a35aa464ba4081120dc4c2aff1fefa919cf7f70264da6a670cd311beb073f64ee18b16dec839b2b030dd3e893e6431f603e38528ec3cd843c6529630f4e477bb3930be92193b3f0f0ea9a4fbbd0fa8676d5ed99474209701efc8c3437bead1bc078f022675f74712800b4c07770ad4190feaf9eec1bb4839773f2f8872a19a48cd5c1f10793db436620293b58603a78a7397b76ba0c29f01cf8f1b0de2986871c3c15910acbd12045c6380ede5f7c6a7bf965108397810c120007cf3dc48450a932acaa33a0057e837fb153696e98a5410b2070636037f8d91da4680fdb16a0400261e036f81b3365309f53e34f90001738800d6e54a6be89f2c802afc14b179e634ad9b8330c13d0055683b76296f2a44bcafcc02f5cb000d354e034786915226362ff8219b8e00b077014180d4e63ecd9e4f30a053e83eb399c6c6b49f9d5ba053683ab1633ca5eb53952ccb5c004288841961606b80cdee5b48ece6bdee49118038b0093c1514bbf6116c2928683c7e0dca6863d59421383e313b3256365340fbd0187c1cb171b4832879a39a32f5ca034a80283c19190f933ec1ce92b2d7fc14bd9f92643c6602ff8f7f93eaa1f4489dd110377c12f4bf5217d50c911395cf09283cd309a5ccb5d7e0b4ee4f46bc1abd4e96a22a6742137b2e04486b9a35f9694ee0616dcec17c26914b9496b5cc1ffb0a5a5d9b7822b5f2187b10f31d17115dc185cbcd3d74a048da9e065d4e51aec29f816f15f41a37c8ca14ac1aff586a1ea68834fc951704336481952c38cb68fa1e0af87104b1ff0131c8979e31ed4c4e430eb04533a44546e5455accb3c84e99e076982e778726c5af850a6a11e9809fe49ba60556ae2a71a37f012fc96a9b4feb51a2bc1bb3f9ba8871191e5e73d7012bc4bffab7922cd362a7560247832339dda2d1fc17bc7b1b161d509b011fc1422a548991ce322b8594a265a0ae1d6dfbc021381aa6b9558d3c610dcf458f216c472566859085e649cc7189f0e82972ea949b444033bf18c5f41ef8081e0dd652e15cd97ea1de4074eb4981e457606fbc08deb8fef160d72312468236c2fdc14e4c43afac67cd6bcf01d54980cd1617d4f8541d85df819d604bb8fb9612c892e5409e70e26a63bc1d6e6c2c931fb52c8e359edc92f3c1717bec7949375564e154e2e6f5cb080372ea8405319606fe1a714826b7d6768616de166b11cb4b26abe183c0420c0f98206e70b1adc5e616be175c567f9f05241738716be75ae8918f3eb48e99d85ffd192f6a58478982f74bc200332408102d05a305e1003fc42062f900555570f3e3748afbae002b1703e539e28517360e1f57f78b97fbf4fceec2b5a9f3009796ab386d615fe78f0902c8302ab32197c0cb6157eca69a6474b1945cb7fe10224cb0a27d286501a528a5913a9cc6ef0850c5af039820d8dc4a884b084025924140804e280388a61b0e8f800a31308001038280d4542a168341145550f148003522020422c2a1c262618101a18128d44a17030180a85c1e04018140a8702a1a089b0c431570725dfd8a894a05b8acd0826b4f06fefb46bf4071403f0bbcb34e8309ba499b5787c0ceeb8086e43b1c12a4848be5fcd8011ca94345e40e68f1be4f5c8a7b71b7c572d262daf3b536139d27fd6edd19ac45b5bb00c8870f163671b76cb6b8c41fca09b49be1dc9d6a6bc0bedc1e0ef38b4819c0b507a7f9523936b3bf164b536f62105dcdc9dc438b5c9fae2c4e40a7053c4de4b5b3e197fb05389446aed2ad1c52923085a26c7372e053fa25c79b7bc6f28536c9b0acce84686099290bfb740a252448e34d3122bb778f40758b4ba9d490b52c462dd8167438c858074aff6e0a27cab773c1ecdfad3c057d999ec65e247893254d14b9ca48bcbb036373fb1f183d4f8e103fb9f09cde0a75235430df23ae3f757353bd38e7c2072466e39fa9b6fcfea9b4c9dbdf4b9258e786ff770b2c6f32edaed785fb4f46a9f93a919d3fbf05cf899fd87a3893dc9cbd343bb51c2da64829d379d6082d5ce31dc83c709fd545d80d9f08da41289652ff07b91924e440366d2795927a4800b2260c20080aaab2a7f2f4f93d844e180d54333911c4be3bec10c5b33329b7d8058a1f9dc97ba30a189a5d9ad80039b77c00ef50bcaa881cdf4ea1681a5103f1d50f31c0b91dc3126209b20f7296e846edc3e4dfad56491a096049d595ae41e9a6313524d385867f64f87a42f2e6cb6bcf0b511c31746faeca26d39b90a201afe48bd1294a123ad46e0271f34c37e82ed9ff2a2a9b1692473ba5d16772319ec102d6385583cbca578d62dc6a5ce7a65be29be8161eddcde56468ccae2cf82da33712cb09973a2c234f15392d408fe383e7eb0e09ae48c9c5a043134f9e54353e534721766734767d796037cb5dee1cface38d8fd7cdb891c0f955ccc625b75f6e49230a7358fab3f744bf7db0f464e8a1dddf1876de0aacff5447ea373b6f0d32321f6c6f4d4160bc7ff30ef39494fd498922cf358cb413aa35f995c3463bbd500e39c4fdb2a41eada8076eff05590da7bd0f42d1dfc2dce68eade6cb14bb26feb7f8fb92ef22f9b7d0ea8b427960e2860b628ac9f3ec30053d968901b9bf9edc80a8ce5df9fa27a98b1113e630f73ef5443476f2324f851aa75b0e9f856ae2cdc93e013439c4b7d32a5a1c9b94047170c7557e83311dfe39465708c586a8345e428171c125beac92d55475e15c1125ed3ae0b4218630427e560f114dd387980424f29371fbd9cb609456f002348e3c58f3a010f80b7a6cf0275d785b5609ae8a5746b9ffab41205cb607c8cf0c92591ea66d484c068dd1cbf31329dfd4bb254d2bc827685dc97b8833eda34de0686ded6adea96ff821df2a9125a44bb9e65786d1e609beec8563deb97fe2f3e5100a02ef38d6491078ff743b31519f0c312ba5f5d23a0d9b308186d38b517d098a0786bf38066e6241cda78d2a977f61b3fbffe2e5f0ae9afa4799497c8cc143c4a75c41aac7636da418f9f14e027f8b704a0272de9680f02b4c7ad257f022662e71ac75693519adb424a689c7945d1559dcd9778ed3bb8a3eb257436014298d959fc58c94783435b4b759359cc0d4ae6f39675d14ab6d9e076ea5493c76efda5a811696b0c70812e5dc1136d156f12716e41e630025ab64d9b96bf98ee3fd5dc6e332dc1e5417e2d0420b4e0fa23d8cd04973d18c6682256e46b7d85c2fd72a2411aa1e45f6d91dba5fb2246f3b94892cea2320d9bc5281086b0a120e82c8a091cb348e8e2ea4277f7abdbc693272f65c139c197c960e56683979158596584074b821e44d45fed8588db855dc17859dfdc22e2b74ec211cb36f7061bb6b09f5a359901ed52ff623451f709c6cad3cce12951f20e2e4e9053e54232858bceada6b782bd05315714f558c3d2f542d33df80b9db30d0b5f34fcfee7914946eb6a6a1cac56931de682be808d2dd9a127324f7af4568027fa9c97004d133c72ebf6923a364018d75320b48cc3f2dd84b8ef7e8f9ae5efcd746583abd2e5a93d2b31a79308b0a1405735eae4529ffc9af8eba1e91b1f8a7bf6bd1fc2e1aaf956c7867fe9fd6c529867ee4e6ebc5701a3e3fa221b10735a406e4cd07160d431d874b4d0cb0a91d2f9f1c3f0f1add6a45d4f26839c050994f0a2f85efe6412318fd26bed8e7040aadbf0d7308ad96b634c4922b39f6370b6fe013183e6a0d8b622c58efd6297482accbc6c0e6975d3228b6db3cfc38729cd1598e36e8744fd3a40b286ef566bedddc2875212bb97c3018bfdb79d58a66e66d149c4e681220d77366e9c5e7d91e579944b43f26d6bc2b09bb687e663295115438602eb73c8044e5b283e4ac2ff9a91ad640ac0d1401cf9057dc9623bf5e3c4d0b9339e953307db2f5b7246dc2194cb88bc4eac9b8917ec5f63b56df0095e458b9481ad300068dc7960e1ba05a0e2297d594679f7ad9876993fe3f849e32e07e86fb4e99fda6be9bb86c238f47ad1b46f97d7e13aea9b05c443d963d6ede1ef216e0b6cb71ff7cf081072ce7991a5ece4fed3e6392c41d20759fb192644804134748a25709999da173ca4467cd19f44f739ebdd643094fe117b16f8d900275c73000dfbe3967eb819d66b9a8a8209bef9821459422a1318c124dcd844c1c46e8b97496fc9a3a0a1d7803d1b05aae07d1b401216f1126769c028f2d3fcb8ea7122918e509ca8c1b9e813c4ef687a4c40edbb8ec23a59c71ee8cfcb8441f9a5ba6bb91e29484073a0640c5d119e1568306e840e7695356375ae6a478469a589d7791310577e32fd5a193aaa282529de025135140aab93e674fe1da7442bbf3244a7718e587451598c74567e14700e65bb4682ff314f9c8f8e3882342e1b7d0e9469abe006791ee3684666bdb281cddc52303c1d85beacdde13af5a926d2657275e92bb47e05d05ad53736fe9fb03333bc791b559d071e932e0082fd7fc4bda2b936712042efc3b255cc3402ba656c2058401a20f462f009fbd5b8495986d436da541638f38f367e11a38dfc34a0103fcfc3e79eb0e1d01873f2ec13e7f2e35813c92154b6918cb253334e9394531742aaf9ca304213e0513a66c3dcdfed80b4f4b1ad0a3acf8b7030839003818ba9f9f1e582d503ac72dac4a7c07db3dfe4ce4190922a7bd1f0937b387b6d712bc8b1fc7644b55e822bb84a1190cc4cff1e8dbad8fd1f3d32d7504ad298d05014fcba5b0952783e9847d44b99a2205f7a3bcdeda6f48117e885bfeb164ccdbe25292f700dbd4c1ae89121495ba21931bc16b9293712999be8c279ed24eefc24da38fe43ce79204e6dec823b6c234fd1ae96648890778cb0a9f4e503abf09bc0535d9b06be490bbe5327f5fcbf85f9a18cb2c7bc38b535f136bca3fb675d1046c972a6c01ed9b67f8b8167c1341ce8b97bd27e075bc9107e6b02fdebd390d8c9d25a58a548bb552a0d004026892f061135b7058078290f29761e27697cba9d74259c86f5749d5026d22f70b2aca8d5788c698d8d8411de55e68d7f8d0ed545afd6511629c5a740f7ee568f0457a218539c5426039a7702dee9002ca64a7f6c70d7ad43b3104338322201274828391375a6b20187355570b6ba4fc7d44048d824444fba87b51478a1c3bfe5c9a816479e7918ae91cd27a6db11673f2df01c3961d6cd7c7a1c9c15d166409e7ecb671d8961a0c83cb595b82fd57b84ba1d6d4d260e4848ca413ac67af54cf01ab5bb75f5972019d4723b743397da61950ec447736a59569b41a14652a0ff5b87c35a98f18b49ee911f21cc24e6d346fca2fe4a13832400e8769eb961d18561c7a825e33b9dd1a8873c5af6a92dd13f31252739802e6e4667edb4cd8b81597255704a6d2dddccc64d0d737e718907cf3c5d60e8219df6b94dd547ac8d14be7228ef2f4c4caa66cd6bfa319f378c8ec245817a6caa4ed350293fc0386f0098cb1a09e3128b53de0d95d5028d023b33d38a9ceecb8ae0526e4cdd49a847ee9d1fc37eda02a60144e1ca0b79b7886d6a5d860241b6946fb648e5d1ba9ffbb06f35039a197376d15cb0032b3ff3d5f05b2dac6001051ff120e563b0f184a0e10aa19f41153df418536eb8e4cf0c5bf0f2aae8b46e832d31e6cf110133730ad5707d9ba855b979a6223cf5cdc9748afac3305928870159e3d7698a608144f1460c47b9eb74f32a47450160a020789be4b4dd0fd882704f21561d63c5df34e940312a37d8bc2710efc04794290ada8dacb9747780f0fcbcc00cb2a400084e1177164706c673ed1fc4b2dd8e08f370ae6c24ce6517da9fa5ee043986506ad8a4a36a66d61bd8d93bc99e53b5bb032451582a907b0a99818d20270713ba17240043478fa434844bcc99502824b87d5c093c9195685213698e868e22fdc01f2c371a95f419685cf3238a3d81dc7d9f91204b137e97fa97e27b336858f861a16c010bc867bc2f50dae7a157043a2b5f501904470261d28024e4e40d1f434fd624cceabc457b3aefac45d85cfabc57aecd5f5e3941c5503a742abb19fccc6e4f74b6cb6c4abfa41751bcca3c6281c371086dc8180a0264befebc25d8d000d104bad14bc36d784750fd03eafcd0279f56514b14dbe6d3bf573f6acd866553f4643520e249e0feea3c47ef088f230dee7403a596fd32b2135601a4995f268df40ae6d51442b62cd794da251a15ca8efcb9f35a096dd438756fe6fe845aaace485d2d8e009a28ef023e14ace7e09bdd3881074125289832f63a7a4407da4c2c6aa53a207037fa5148571a07c7f21689d21a38f508d919c9acba9b2300c51db8c0285432d77d155bbb2afebb7b2ee7263b381d9b67173720ecfa5a808d5623a6d5ecfc766c11865f1547acfc85e87e56059d65a4857a8f9c269de37ad06204a9b507550562581bc2ddf3e9ccbb3674303b92c4bc15cc5e601c1fe83624045fc9bc066752e2bdc6b175509e2cd8567dc419284f62f03f46ae1b0c2d02276fe28a37f4f6b116b5a7c64997063df52d3dc46d2081f09f74b1d5d40ad014e30b3d22590bb47488cca815377f5edfa296b5e3c3c92f6293e206bfaf5ad7c56488a04c1b057662c98186f631e289567d904bb2d508bac0d7a1e9aed43beee201a43dec9130d9c6aa2f4807c1ba7f1e68d85c82d6fc096402b7432e4e47ce7e5927abcce79be0d6a527b5944c568adcba0cde32c2db225a91d58c49f3b803f6b92a4078cc0821040a8c7d17957ef026398d97ce71688f168f601aaedb79fbc36306227c69d73651370c158e1d13cb64104ff7c9dcec9ea73ecdcca26346d82d70fcc44cd4e20c0da68a9b0931782be29ea391f4ceb19e29ba065b1dda8f00b78baf1e071d18941ec3b54c777229732c0a3e4a062cc9cde6d713c46d8a487383460074a2f95d43e9b3181b8e756645393ef464b12274e20718a14ecc47d6b5d785d1cdfee90ca9d44711ab06850244683ba8034a8ad1c5111f437541882ea2313646614687c9460aede690614b96c748da692bd21660f0b63691bb7b65eb8f355b2d92f4cc739e536744cae28e3d3146c5639fb242afe28af70ef664c22c472d7af7d1cc241330470aeba8214e7326e8a6c9df3832a10fed90d9567b364a4f744408542f431db45782155ae2b5ba897aac94a0e22940b72107ccb26b826b57f59d5f04319b9cf8f3d42a99bb565af8f49c532e8a16c58c388bb0f0d44b1725c12f3f6ac3a9a42157db72dc308d847892335dd913d48ecc2d1ce3603e7fade8c7632298c6a71c3d1aee483c82bb91c6da6a3fd1b0b0f07a218e16ec495d188464a0fa37134bc1efbf18e3e421aeaa3e82369b74b9b225ff4462bf0ce709bbb8c986694dd1ced204d1084ee483b56e3a8fba5e83fbb4fa8e23454c4450538a4948870e8f95391da679cd649c93076da9325b69a0eb3b13e952991f2c5dc68d5900b655b4e22cf402d5b6f220a1eebdb4e37632c2b3ef77b95aaf4ef660ba236d1431fe597aa7b00dafc4edfac76bb3c95fe80ce8ed707fad5927d42a25c2998d0804969487c90234b0184b96889c332740279c5eeb467e568624b99be4c2d2d3b56fa5ec6a633450127a2b82c5516de01ed37ea9a53053a553082678753f400864777efdd3c1233cc7c2c129faed1684802b91ab9d96c5c398e94e0245929f3d0fb7d1d0e045eb6084a5a2aa8960aaba65df98bf5b572632cd836b4fce506b6fc7a5d2cffe640404c1312b4077c81c4885da955dc2fcc0ed0af655613e2d9bf6af1b721c44e9a7a753634d988847a8f2dd53bf6ae35e15380155d22db8e57068f6770f750ef6cd01cbf166923b3e46b436172109695d23492e43907004955c2ddb91c1143f82f80a7bb91fe13c4df44f06199e01876a8b01fd564b9eb0722bd4cc1eb65802627241d03079bb1b281a5db81dca780993d0bc9e256743fbc830fb34191717e2b3edd2ec61ff1e8eed902261262d4db67c5d04461873288d1121897546cc2ae2c3bdbe8feadb3f2287480e08911f8b86dcc7bf28bc0387148f94436a47a173b1b410b6e6616a083b061d42c7eaf3e12ca4bb464cceed2528a17d433646c89e158933ad01dadbc2321a471e316e34e671e183e48368e6f894e2a62ec8ba5084f0bd9baeda5c05cc9a9ff9a9e37a8b6ce3a68ff7a74027d344036e9e350b4398b72232de379c33b459784d19e762cce7c89ddacffda67a508fcf71e599e2905e528becd86e17a9b0b122ca4b650656644a98c53b4688258cb27ced9fd133f6eabea54ec96c6d01867984e54b02fcaaad62a3f1e44538ad5e5e7c768c0cc8f93071b2f5198d730a60053d17438b26e6113f2c2d31e8be54e494426cc2775b0d9ad728877533a1dc25e86c55419a3623a64c08ba5deb8cb5cc668beafc5cde5704dfe9b648a39e8560fbf1b2966e1d7c6f535a92a7035d1088a0318b5d8cdc058f653c3f3163a5dcf43939651fbdb333fc8090d3e51bffd15b7567c274d31a777cfe67628fb5504d0262a092074e2181511846d6b55d8741c1188a2240f661155fffa99f28bfd1da14e457adba8de37a2baffcf0a3b157ff234c0072b72de09aa12912fc247841891d58988d608cb2b49ad6d6e4baaa59c0fe2c97ad28bbe415c844752501ab253f45ea4febccf8370bb084a2249c7c81397ae182a8b7c3b4552660cb9ac26222911902b993e24a9b53eb610aa1344305b0c8783631df7883fd1f76942b844a2cb25de4beb6e2e23b84f657d998b59acbe488ee998e27ce1cc9334716f1c90331cead8a0fdc729734353ad7fe7e824f0a92f7f853b774fefe1657b31a855aa22ac07706a797229e250203df4b12415fabb250b6246db6c3bd54bc3933bd166a1b77a6b9e29edcf54507cd326d8fd503e2e8461dc62946b139bf62c23bb53af7fb1d1e9506df64519aa6431b2e30bf9512be8b4a58823e9e41af13eeccb51edd0501e06fe9f5817a0732ea9b7dece49c388ededaaeef6fbe5db716c6a86ba60021a8f4c6655ae35866ffaf820f849ee5f10679ebdbfd5561498fbcc585f3212f309cbbad063c2f48573b9cc83032832402e5e1475695ae60a060b9e8d74dd9c5b0807804492799efdf8bd662f2f0826c51134c942988892764c3e5c241126a4180078b5d844da995b7a6b3cbcee505a2fde17e007c76e8f25107265c4de665c1da7b9d9e04a058afc78ca9ff14952eed2d2d6f375487d4ceb8227814ff234d05df9fdca4bea62b0d190112563de7434732dc0cd94178f197fc4215061e6384f104d7170c599a337f772d5a0bd88b44cd071a3e5ddf8af7c4c60bad44a10475a2aad1552539f96b079e2cbbb94bbf6417a3eb222f196f420d47588684a93d913034200468b4c853d8366a83288906b17b52163874069747c84b31c4170c4012f50f73c8156bf3c278a8c272c0489d42b17d1f2f76335eb43bdc19d4e4c0074bd8f1b49e3000354826f0d65511f395ce8027d93fe64f0f3f2fdf3afb16b1e125c8ed221b62c9308c8a50c5fe9444746182c0a8bc4bd19c1de2532531afa34760395788f65a827eefb2e67ba088f0e494e11375b79ccad06f97658e6f91641d91a8c2eb8be5b0df4ca22647f470dd76fddf8982871fd636c5c7daa838294ca2b198908e9843880306fd28bed6f1690a8e91b732153103010b9102e91c9105d104610fd90388a8091909feb5e55540b808001812bd9bd42c4109e9d27f9f638b15f6894a0d3c070c52e4263021d0def1d8d7ef26185c72f61880cef22684d945e78230b51e4eca0562410c409b51e4a24b298f0066ee01bc4fd9073e2bd4b586f18437c1c84dc65b9f20d42149196e9dc5a6043c85b6854484e859e994b32d6380ae2885a851219550fa2533089c81d2fe03514b45d1efe4e6a3153510ba0db55f5d8a7615662cf3f0b33a4b57fa3d490c3d0be2dd6bbe0a5462cfdb4d51e6573961c0000849cf4a63a03eb184430d8794305b14bd64be2e2a3c4cdb7546aaaa16b6a22cc6a6e1b70b1091d6bd6b1006c6980d9ade93d65b4105ae44c95bd17cff87a9696693c3b141eb27d0c828ec86183fe41054520e3151bd8eadba11f40e005e5862f64199610d7264b5be29c6d12c736aa9e85c4ace3e5f082881c9794b1264634f525e5602ab5e7a492671e6d9b8ed9143c9ecac72b2a892b81c46f27788a8f50caef547ddea6fc712fb25003291ea31a4d39e948b1503ba82262100528e51704e7996b6054de0666d264698d4195d1687032ff4a83d08c1603b205cde63ddfc9dc947f20173c2abd5200fe79a8e57f73d6284fc8e1e331a699e3d37f7dd8f1d231c09d075d497f0ce70021667d87487d30d51460b64455e6a34894d4c9a92d8272fd423d9d9aef5037cf7edffbbe9d42bfda77afc3f83e1558ce50e699b4a3ff69a855ce1700c05c47c188067bbe24c898a1375ec320f39cc0d1a720c1becb9b25186bc97b7458f1abcd52fa8e0b8f01568df7a0c14239c20bd58d7111e060054c6c9df25722c9c1605be8f4524c6569c321604c028a40ccc9f2379260d6324fc33e7c9a062e2a3ba5cd7a62df0523242871c6b0fe0708d085e5738d3206a0acd2b0026097f9474423575cab5acd5373d4aef08b57f24fbc357d6dd53f6ef864f26bf127b0f2be1410a8e60678fb22d04006d27d904638cd72d07a92f8eb15cbd9dc445ac0a726a1e6670b30314339b6d0733247d807d1920c0449e78e4788a22e1d0f89a0a3c563e8e35cdf64407b2a9d86fc2f292423e5bb09898ba0bb46df4f8cced86f5b24ae58fbf875121ece5219d85c0d5ee65714e2bcb5602458d0d84631fb97502dc5ff709d38b5aa2f79af7475e097208b455cb4e8e073be1cbbd8601a2f926cc2ec72e0ea2f33e7ef75d19a3bc52452985db4454ea603aa0d54d192175f20645d93e78032dd87b0ec83d648ca560f1d42586489e83b02dd2057638a8c018b558b937d4c4d0b0a3e28120f58e1daba65673d6480c25f373f774e4bbf4e6a56ca087b5326ef2af02d14b5f8a073105b058c781cec450614cf071d6586297736f4815b0e8a03f7cad9397049ec3b68c110ef47f0825ddfc9fede954ff8d5bdceba33b569135e0ebe8d81211537102daaf1b9485d08297927a8b4e62c30a9606ef5a39e400154dde9f5fe5c83275fe4bbafb5209e7184a6acae12073ed621abe8025a824a1c29e4b66bae2d69554ee0bdd1aa97c15d2ce46614b32f923b44330c69b1beb5687031253d2abc90fb417163669c4bdc71998e205ee23bb1e21f85f520d443d1b44d7777414639241b871498b39463f46b40ff8c62ba2416735fd712a39316b3591f379eba7655ec210f45a72acad05bf355cee0abc55604b339eb6bd7446c3f5cfc22ac40b3e5eaf86b28130f311e250b7d306b4a1796e2fb7b0c61fdb82e939c97829e49a0b794c54d8669a682c7f115002256c74196a0e3a3ecb392a3662a8dba6b09ffff68d23165f8a3cb43d21ef8d5b95d6632be8c5df0e5c8b3c0c5c3cfe9991071442dc8ab34eb2a39183c571e732d49c41dd5475ddebdc62ebfd9dfbee38746cea27b9342080877ed1e0f3823925462dc2b87e26c8b73663cf523ae50cabc7aa89b44fdd5f7021ff1284075e92759452058dfe0519387bf04063553a6c68ac4f6074a276c661e5a64592ecab3f00a29e61e9debc1498c1e715c5cc84c1a41498c527b05c361c4a69121158df6dd911a3cf4d9ea889ee84e5ea481705bbe339b717500080d3db17344d0b144d4637ae60c0de36fbebf509ea4881e67fd93c9b0cfc7b5a727549dba72149026fe397b5d1d2d3afcff6eac4918d32489f07e309f2954fe8eda8032a9c1da5685c9aa576746f6552c03ca03d5a107e52b1db517793bc44562e195e25c63776eceb7d2f2bce912b4c63ce2084342c2e35148ebcb1c14f556f0f0388fc47ae1f9b41175b880cc72e9e694a2e53473601d128816ede60c9d4d9a8b8a8bb5a96dcd9bb3ddbcccf0d3eb8be3a7b953dd04a0946d35bc864ab42943760e99262d19b36e5affb766f0901dd865c06143160008aa85ce447685a0d4d7d8e6a3d97385b0255ab21ebce9bce4cb6ec35394290f8164a77c3c6d4decb7bd0e6b0277a42f329c544b4e531e16e3723bee82b80330aa0866a7d45d15bcd52c5f89b88274cded89a828ee5a454df0f4b325a385a492911262b2dc63645c474b6d11a9a2548c8a268956b4e264948a52112ab68c158b94921332e4188775d38aad72565c952a4b7740effa503b32d015b20d141611456c9f08f7f22efeb3801725a26a7d1ec6b31f7b23b186750abd06a43691a70d193f1f08a2f1de4f14ae902ab4a5d5a1532644446025035908b29e4d1d83d22198286146b3cbdf670e2b90132fee30fb0a5f299bf0d57af9f1c34833cea641b89b4a4925098b4cd3b6940b7445ec5fae42988f9a58cad6f84095f50a5191c2f01191852e6f37459a04c18ec349ec4497cb4043f720fcb6a3637e2343b249f9a626cba9429598afe5ca5040ce085a4fb469495bb228f0874d5436400d9e4222b116ceb7b180e06a849654758b7ad1c6c4285f3fe22a0be4d5cd6e0572f34391fb996fa1fcd91e986e27690b7bbc8a44839c72461c8cd5e6676baa9b6b995de66e7ca09f224b96b013e80f4ca35de71a97cc5b1b47faf607e1dff51b9884492f23575adc5b8783cb315975aa5c839d41b9311eb2570d8570dd9926cf387e7fd36055b39d63b7d5a690b564ef91d57e9b2feb647d5b307bd864580f6b0a99b0e3a696b43e272f30c87c23cfa08ce35a07996090930b2991ddb36e8c8ba2207351ce619035f37dc7580b88ee0e6a790a9f84fd0d65fb23a800ad6abf9be81111396a54862562238f9700ccf55b6d4f04fd10b05c566d2e070ee659ec914682d2dc3a0000b79ed1b37c58e7ac0b6ab658eb488417e35c250f5a3e98763285a7ad9284789026879fb38fec8739b38079aaf74ada2e602bcbb9efdcc4a8f676c26048b70b293ca34a63a9d15e07ba60c82e499f2282900dfe2a9aef5154d299e45a1fa818cf21529f82d559d3fcbd6716a2938e50958ce053fb87523f178667ea891b84f7d1f7b1584c827828883f245299bcdf2e3e81a6ba96dcf1850e1d0a819cefb68d65e2e624758c012aa1641f44e1a2fca3aab05a28647c93b8f756344b2d0aa0b11a4492751b51320871a07c1aa28588ce98eef944e210e3bf78c550a10d868e6946ac16dba580366bc2585b75a97cd85e53b0deea2f93695fcd9c5ebe28d694b41c71270d73f6344d6b43af2243b544f71cbfdd0b3dc29b61ffd45b509ac96ab9e29268f9a0821f0b73284183bc79173f2ded6cecc0ca79edeaa283bdade49895912bee329cc94bfa73bd65a84424e760cb9bd2f5b1f570abaf110a197c520146a66c18822324e4fe04b445131d4f473c089f49ea8dcd65192e9db97584556302f0805b360f86d1750c85541a83cc74bd8da45a215cc2f316ebcdd9207a3fd531d50356c251a86a2e2e7c53fb36b97f7d4254ec42076a1557991b95d638d01c078ba60c654458f6e6d96c20bf0cc91178c018e7e1de5bb1cb0ffc624056bc855d26538216daedc5582b328e0c485ff04cc97228b8bd6fb351a030b947bd0600cca5364386cb984abf197845ee8c14659ad5b870e79b6fa8914aec68ae4b205b15de4ac26544c46b41811dda3b6261b6a975e4bf6c95d5175ecc8cf6cc7f53440b3af2151b3863b523cb3561b74a7edf311b80a05da52576a981115f9f829d3ac6691dd8f2d5ec3ef1316c11e375c923863c0580547157b18ea2bfb3b1690d4b1af01a7039ef1382d47fca7a422c52a36906e025a5e0e457cfb2665d353792f5f981814cc348dfa77f2e2fab1da47688ec6f805cf99054f978835210f6f8c17ed32cc2295469c3e5787561722c32405aaf55421916032784962c3b5b0ef8f665f6237cadd8e6b7a277030069e81bb96faf7800b5f3054cd9a13b5690c20aa65ccdf4a09169711a2e1b847a91c91c2a8180db59a894f838b7b4accd862a5be076b655e9fce90918c00099e7ee58a4f8831e3d0417988e9d96d27ccd1e2eb641f4a5a7694151adb7571b1c68af54f4d6df425c0fa97ab537c28ecbc88192884116e3184ab5bf932655d1c33dd771012b1d5799061cf6ce6d7a5497424e14b5bbb72cb9178c6d6f932de03a936ad6dccff70c0d7c735374b841350f1d940b21d052a329ab8f0b198f49ae479afecc25ee1d7204619652657eb90c54aa554af552242a4cba859e063d752a722b6c8339ded156a920c72541fd61fbdc94b2ad19b8a06e34d9d4664017748cc6580e65b2864884b726d8a3e855a2ab8e57448c29f133a2030353b48102b36450d6b319c9a70151ff0cecf2190c908015bd8113ba5a3b6dbe1d6e83b835be598cad6e3b7d2a5205c6968a7628d95b22ef0513c6cbb289755e1931728e8ce1a157307370ae33e09d1f340acf9b4d0a125121fe56686c1825fd87964faf16e39641c9251ef5cf568d4112b59184a2cee08d579808795cf134b876d816464bb16264a5d0ded24a8ef960bc91a059575f94b8588960182f454dc1fa619474fccd0348ce659d748bebb9454bf57167fc2b48b908344d38f06fc72aaec8df82b205fc9d24eb5816a4b9990263367afb528e158a155dd7c18083e843555bb393722c642ac420e233f5f2a3ac5795a6dd9c7d74accb69e5dc26200c076d877eb9dd43cfa0664a1a8e15e22e14a544ba7ee16b062de90c0e485f1baa77e4803422ab3312b516f2af1c1438b516e006b1a76040c9258e24a45d3bca809eb0fb8fdaff0df156293f7662a1f5d13e3de8388825c290e0fea6101d025116e1a2464e52e94e57d05e26958520573c2d5ba4dd7b66aef1c3578c6de1795dd64bfae0d0a0b246f290ff6edaa2aa5e283007213ae08c03a31f18c72d080aad9a53326f73c13ad53552a851c2ae5fa11bd2be28e3193593c564b2a14854986b289fe16fe34a0c064e08e0c8cfb144858457a381c709f3065bce039db8ca6fd22409f445eb237484338c62c3a0b4a223d0630b21b7a0ac3e8f4dc106a10cc890a7e89c8350885ad4160b16359ad7e2ef099ef4aeb3e64e5582a78b2ddd1defe72e48bec7661a6cf0856b65dd0b69606271c5c6cb6d66ba6d14461b060b0240e150b29450625f44384c16784945e473a382b2ed47e19cb9710214556d280891d1723792229eb55b05bfcf2ab230953dcdc8146a345b724e65caa16d0bf536cb25da840ef3c12190456b261386701ce3fb49cc45d1e097c3905e78af94da0960333b1ef5db5b0d225d807e6208b6b967d86a9ba6f77e30b02f2c3b33c6a306567d6d1a4b0b9da92cfc464ac3fa4eaa036a3b46ff1404fdab21f679fc0eefd22e7114826a7753385b05bb6c88d8065ec6fbc75ec681bde38acf274d1491edef0d2e3f415bffe7e0456ffdc13693a7719fb42588db2327f280f8c55a3a3d0412784e5648e3120c3630cbc6821f208552be7267cad74c45663c90bc5a72bb24404791278247971b9c731d8642a2486f4e38932c4f72618c02755dca5748f6d44a3f85f60a939ee8412eefb632efaa92886e53ee900ca3a56beb6c1be4ed4415811e6fefdee423005f14e28cbc42208be8a56cb6148a4602827118628b479a9a8ea3b9860674c30d60268bb2051ffb0db0c1a8dcad2054c300d2ef1034b6b3a73e3da0ef127f15b7219950adc3c5acf7a346d9447d9d454248fe51e9e621b9e7c0c44cb246275347d46d9504c31409bc228cdce17fe29aba8e9e4b92cad612089b06a91b7f6ad3d9b261c3ef60cd94a7e5247e4dc077edf18e9b793b68ee9c2a31c24e4f82e3f5674128dfa567370f3a34f8960e1852345d31b04e4f8d2dca5859d1888d18f54ce0a96e77a947d519a065909d5af39f53cf73aa0edaaba690ac89ab5630431a7367ca15ddac15b105cfb3b0b618c7ca20a11f941be889e0cff2185fdc11745aeb5d6b5006cc5573022e4b04040b3340b2a4d001ffffffffffffffffff51dbdab7cdda07edcb94361913500c45c8fc84a494524a3225f3f43338ed03ff151cc287100f0b6c0acc0a9c0a4a3c39e520913005b1975fb3cc322c830308244c313eba69a8dccfcbbd8d1c3bf011e6d2be8138421fcd0a48234cfab48d560a35dd184118614a2d25afcf9a4a005984f13e95685ac642375411c62f335ddacabb94249f236127c26cfae652e724bd5423c2a04fe816f93d0f953c8449af6b9d7e3fbb3de11a6b6608534a2bb18e4218644f4a2928112a4ad6354298c5d664cb31ffaf837c076410863325433b5dc97111b7204ca7443b09baa31aa2a1c3c659208c1672f1b1b4691d95c510c306093c3c4000e163d8e0c118653000e40f56ba1b1bcb15d6b3933c32727455633900e207731284aefaa42ad907a37512bd7637ef1b678d354e3bca28e38339bb47dffe391b6d273a6cdcc03d184b963072edf43af4600ea192126d9ec44ea29e07a3794e425a78ec4dabf1600a131fe127b37b01c81dcc694cc3afa46d0763c979616445f4b8b97530cea6ea8de97e3c9581d0e1f5d0cf5eff49903998c457ebf23149fea58126079356a8fe53e154630d75e36054cf16173d0c07a35ace11933ebad2e31c9c24f91cbcc1a4ed4e9afcecd458d33c37984a94877e0eaa4c94688d353ea381ca6a9800a40da6dd33b59f4ef8874e52636dad05206c30de2533ff931a77f7ea5883a9d3785e4b52b2c6da0e1c38d46012f48b891e9583b6d4c100240d465136221696e754856a2c1a4c65626f27a14c0cb5748d353e83e12bb89aec5945c5e866387f6e85c9aabf0ca6f4222b2749b2a5bd548d35268341c7c9e1533e99a177a200640c46f99c1dcba348eb0a0840c46092aec3e4ae5ec6581b02903098ccf2e3cf9a60305a0e5afaa41925b8c67702902f986543081d4a3679269ada05205e30e55c363d25eee79dba60d2398710d54949275a88061607e142524992249f166da9b1564e06205b3028f5a1c4ae534dd18c86164ca2a75392f44ea22b00c98239f5ef4df2144ae49bd052008205f39f1484ec7ce92b1884c8b7bd93a3493a9605b142aa24bd566b6b8db5260148158c164fcf7df0b7a01d1c62141a89c60e1d386ad0d0c3c118367080031c622c0e1b34c83020549802881444001205d5dc2c2d2e68c650007982c9d69289fda4ecc491937143043ee0800e1d27051e1e1e06e2049026f0f5b2d9ea59ad7de5a9a49caca28e594098605e4b39f1e2b64936421c3bc610c1128c7a7225ed4949b224a5b59192083ef0811b3a0e0f3c3c9293d000a204c37aac279debc2824a27c1fc72929e58236d4f1222c1ec49b40e173a7d5ecf811cc160726996525610239837fdaddf336f56669022185bd3737dae83502d99004204833efd241ff43f0c934a5236b13bf51fc230c91384ba1065929069f90886797432cb59a205184693da2fe845f74e3afa8b362e59b8d3b2e02ea79569a1ba7b94245f18f76227e9e7a504f929bd30858a551a2127d5a8145e1867c3cf73ca92f373ca2e38ab28e375156bcb2aecc7dae5445329f87e125d18ce5cbbac44b53993fbc8854994d8a13d8f1017e6ee720f3a9c9494d0f716a69157d1644b96dd45b585d94abab575d0b072b7166619359324954db4307812d54faed678162635a22bc927e34b4dede1f139fc6561ca3ff34ea243dcfad5217cc4c268728553dfeb32081fb0309b55884a2594685d6b03c70bf4c6c72bccda26c68937d1043cf870c516541cd3a7726cbb15f9874e95e7da5167561c941c9ecda28af26e15cea5301d46ffe9b54d15a588dc76aa6859d20f13aa4a3015c60c5d7a266eaabfde072acc56264d54b8925318ae4aac1353a2867dca16e1c314c6396984ae13b73f4b45c34a61bc3b553a4d1e5142ac1c7c90c25c794fb783fc5f1ea9181fa3302555fb1dad2b4de5134e1d3a5833f0210a639e94662c66e95c521fa130e5258d9354feb9fd948707280c9773dcda3dd9270c4a56ca75675b269afe3e3c61129f8451fa5acadf549dc8be4beba2ddd55b7795c897ca27459c308cf5e7ec9b245ff8d884b9d242e627cdb64e0b1f9a3025b9b1193732f7b3c52b7c64c26079593d688a8962745cec122653b75dc1e4e5e1b13e48ff6c0983d8b8d87ae2bb9f302b615eb5bbd1159b1226a9aa537849529e24a727616e75b5539df4ee644f12a6941dff4818bb2fa5e52d25244cd9c3d37e28a5728aa347943b2b68597585d3d2b4a81764e57fc711e611f6e5a3a146cee4f8d10853126bf4e8f4764985cdc3e3ccf0c108d309b5fe12be63624517619e37bd22da411f8a30795a2a31cf4e5c35b59de1231126b95f9232199d0face00569f8408429e5131d174eb6868f43184fb549b765db5542e4831ce7868d317c18c27057b9c2082587fd8b3e0a618aa154ceadebc53f930f4258f284910fc2706f82295d268e891a0bc22073bad74559febf1a0853fa649ae35ea1acad0f4098dbca53e737753ae8fce30fa6bc999db42c49a2d33dc2871f8cefb97fa4c5ef9c242d7d30c99e7225a66fdf7eb4f9601c5da16e4d5dec942e34ecd159277cecc15ce174f578aa1ea99687c7871ecc63d9737e0927472ba93d3a280a1f7930bf86be7dfe3ff06034419b7caa2374b4943b9852ee6b2b436c4c6eed601c8d3dcf595475307c7bff9c7b9f5813a5834149275cb4b13ee6604e4f3988bfbfaa37793918de7c4e5bd5cda76012076399d63ad3351ccc2147bdf6760755a27c834996f586851edd5070b93677f98a9975bf261fbf3698f2c8508230b1e4935a7eb0c132abe8eded16cbd2ac429d7acfae269558ca640d66ab8fa5fd4ff3f02864e8b8fb50834938e94cc67deb81e6b317039581c3fb041f693098b55df4de133dd2f001a281056d061f6830e5ac8b122fa91a6b64dc484a0e14887156e0e1710693eecf9f36fdf81c33182c087595a44863f0510693e8e059e7549758aec8507e96d8db95c43268ecc08673c8c7184c4abac505955de2592f06f3996e379d4c4949e7160693872c97bf1c7dabf5533e3ec0603cf95b2c4be835d6c4d0dd31868d32706003ed063ebe600a71c13e65eedde7bb630c1b65e8206b7c78c1249a49f6c954384bd6415d3077a55ba7f315b3fcfd830b861be1a19b969ddffb8d2d9866ee533afd785549d00588850f2d983e3cfa96e0414f9827b3f09105549714dfee52fec082f92e5dcaa93ae90aa69c277d8e118b3df15bc1687eb7bed716c382f75105a3dce778b1fc2413d753c17c296a28e9659f82f9632de7b8ac5526280553278f6619da55b9e947148cefe597b2e9030a0661a27bf9b59609daf3f184d2725c4eb9d0b69469e1a255e4fa047d38c16cc92f9b0c11237ab90bb80b1f4d3089273549bf2583058d840f2698e4c7cae6e9120f0f2e7c2cc1a442c7b10fa13ca77760c369948123070d7fb6157c28c11c7663a276ba54eacf111a49f84882e1a4efcab5d55c3b291f48308befb6c83e29438bd0118cf717475edcc3dc9818c1685f274bdd4b95a0f647118cf932a69e2d2dc7b67c10c1a4e2e7fe938354bf5c0e1c63e0c046d9304c1fa2efe4b1b41b69aab196703c8d1c38cc84a1fd67939229cf493f9f033d3a18e612a57c7be354cef7e06bd0d81c0d0ce3c8a9dbbff53442b5af23c721a36df42f0c6a9d6566df22d4ff3472f04f46b131da1746d74f3749dcf54c9e400b40707e059fd81083051e1e7e3e878747752f4c42ab879ea8d331fbe585f9249d72a5b45cef42b78e51f1144b2e72e326cc83c996433c6c565d98459c4a5258c9615684395e478e9382b3f7cb052a68a9d3233e19376c7878e0b0d1b8308f5b98c7695b98472dcc9f44fdbe0c352d8c95837bbcb4df2c0cea54efe7bcb485919e79cbc21c6d6d776ca45d4e158dcfc1b951168b45c3e26653d52b5997c78c68dd176161d2aa9676667b8449a219f42b4c622669d3f8b25d61f636b9a52ae9db50e20e742bcc2ea2935f92ff4e6c6d5c192b6ad0aba0d1aa30aa9859ec5a4ad2ec9b8a15342a160f258ffa14868fab6c262f8ebc726f0409294cc193789cfb2496671426ffa815df3c278d4561d6be14cd721d0ab3fb86b79d30280cbb1a3aff423dff9f308927ce76cd5d67513d81d85a2a497ed90993de3925ca09396138a14a97286a3d266ec230b2dde52a78eda768c2a039dea25dc98451ad24b14d0e13d673983049aa593ada9524e95dc25426b9698dbf7852f7963025937b74c931ea93ec2b615c913fe9b2f69430c8adae97312df2a7491864c8d36162f9e82849c2a0f30925679c226178bd14aa5ae4ece99030d7fa96fe3919df9247184f4ac1d2091fd32bc81106f9a943fefd4698729fffa829ffa03a8c305f8559524927b5672fc22c5ba37a4de94f232bc27049c79d1225ca25d1449892ae1acf96378b1311668b25c5e7d31a26070f617c1344e81acd10c6f730f9de2ff7655808638ebbc54e3a69890a214c3bd77d927b83309b6da5c91ffaa73741983e97ba19f19e82c80261fc5075f29a00619273e4b4ced208d11fcceef5726fa28b6ecbfd60d4b1f39b37257d30eba9922f5be283d1a37a6a7153a1b1efc1e8b167f23d1f264df4601a157c5fc74fb414e5c1d8b19398167db9d2c18369b7cdfe04f16addb983492a41a9962d413ede7630c53425c6bfc9aeb7eb60f4360b1b3a9cc7ce743049522dd9e87251da9d83c9c7cb6d3cc8d0a59483f9a48f8d6725a54f308983f9acf42529e7ba9d96c0c11c274c586deb08cf923798e2a7b648ebd0ab2b7183c1deebb2b5dee4d1b7c178727f121edbe48fcf0663aa7e1215a4589a246b3087aed3ad38174bfca8c15cad5a925ca72e79380d2679276b0861a73b321a4c6a26c896f4d4ebd933189424992cefd3bbf79ac124fbe74a124fea11b70cc6b558a3d39d49926cc9605069733be874694c3a06f3c5d0255552c560d0be6a33b94bfea40c8329c931a98399b42523180c172ac7afcae215c62f184dd6091aeae20573a938f92e899f59972e18c74ddb9392e682b16333b7b65f2dbc2d98639bce754a8fc99e212d28aac4ea9c9414ca82e984f5dbdbf8981f1618f149ddae770593b0ed93b5d5f52b59c17ca32bada25f0593202ea782c173c4a86c9772673405a3b7578d2993b39f470ae6159513a7e72898edb2ea6e7b0eea562898a414ebe27a3b74e709463349f8befc49ca5e3ac1a0224dc44ce8dd49698269be644fcceb30c1d8fee59e4d6e0906b7b5dc2657824105f350b9a3fac95fd2a5c4f0d5d691601a1343958ff224fa2318d7a49ed27be249b311cc225a642d664eaf49110cdbf1b2441b2082a965946c987ccd7d8661521fd9fb310bc314ba9f82d06e300c4ae73be70e36304ceae941ed75fa17065d5ec29bec51eaaaf685f9353dbc4f3e31e1d40b83d09eef463d47fd122f4c6ede229f4abb30e7281152dfe35bea7561148f2de2d299bc26b9309c38a3623eef58d2716190bd94740e2a26e7b73089271f27f76c9e1c5b984289deab9494a0e3a4168538215a74f9af21dc74666152fad1439778eaffc3b2309d5c4a4f28d14ac99a6361924fede734f13dc4ccb0306988e89244db3d09f91506997982a50e3acc8bec0af32979abeea378740bb7c27432161bdbf1e23f668559949da07e4fedc98d5761f0d85f61b6b12a8c66b7fd9ed4793a8b5361124df94a173ac44ea830e9bc2174cebda6bf3f85b1f5d2b6ab73fce2a63049e17379bcfc452d2985c1d3925612457b3e8714c6fb24a2a662e8d8e951984afba6e9a896e26151984dcc6c8f8796953714e6fe93163f545098e38890a74ac5954c3f61bed15352b985d2997ac2e815a6541ae9a1f7ec8441ccd3d8e7ef2539e484313fa86b988af893b209b37934b972ce7dad451326fffc1e2dc9b75b2513c693544cea6e3161101df76e2cc66f6b7b09c307a157d55ae6165b4b18c5342b6e575b09f3c9f5cf494277f4244a1854d33c45578df2934998c4dd083f3d494c9648c224e79294147ff6a1a248986f54ba7612a9322248984d959a7ffce8234c79fdac93fd59298f3ac214462bed98db089387fe8ef4bf7bbd19614e79cab29f1026e9781126196276e76e4395561146cfe19d93a096a6dc44983b8efef4d34b49a528224c79566d4fdeae123d3d84c9a47029a7673a7f4a0d616c352fb162e1494f8530ec499f922949cc8612214cf395aa4e272955f34198444f5352b092bcca4410a65c975573e440989429d1d9c4c6e70a02c2e0167aa673f433cffdc1743aa7e7b8f7fbc1e871c2a81cfc3e984a9fbb5ea9124b9ff0c1a442cdf8c79cc9c1640fa624ede8fde59c28213d18b5f5b7848abfadf3609252bc8b9573923d45f1600c0b9e3394242a32bd83393fad4b8ea71d0cf65fbad4899bce631d4cdbf61566844807a3c9cba2ba2784bc0bcec16039d485aee594e5827230452d3d1197611c8c21d74a8558c93cc1c1fc995de1498963e26f30a7d2bcb653b7322637982fdc961c3daa93ba0da693a4a062e3b3c1e46aa944bdbe06838ea272c84f651f4ad4603a9d4a5a5c57bf1aa5c1d8b75e42754b3446683086297964bceae84f9fc178b37fb72787934cd80ce658419f779bf8f56d19ea959155eb26190c27ad6c2521945b07c760ce26492943e59294876230898bb65ddab5245bc260f0942264e32b603099fc9e27a712d47a2b5f306d8a92b564cd0be631938b5de2739a5d30accd78a5e8f9e552850ba6a463bb5ce5c773d2164cd17faf6543e54ebe161c13e24a84e85930b52561b904132c1855f57412f4be82f136f4dd7bb682c994fa24792cabdcb80aa6a444cdd968a960922cbdc8bdab1cf64ec1f8497cd3a14499c9492998e5ccd24f0a2f0ae69274d0974d2c39e8efa0600e2ab3e4ffbd53b47b8229ddcfe70473fa0d93562f4e1c53134c722fcd4305dd9d9298603ae531d7b4f6472d2dc164312a5f36c92cde578239098b9d3be59360d05195db4912fb794782b9e436314d253549cc8f60ae921e23984dbc8e2d2d39e92298e7bc926ebb35400493ea9c9b25499ea4131e865176fc5a6f45f8bec23096f814de540ed7ef06c3a4c4f8e5b91418e68ad721f6dae92afec21c4c5011ea74e60bf3dcecf75e28e969b35e982c9892e4cf1a2f0c9e6f2f469e1445671746713361d2e4b9a095e9c29c73f4b4123b77dc5c98c4f6cf5450aa9368c285c94ddd5daeb5e0b3dfc278325f9432496c618c193f497ea716a62bd9430b9394e409aa634791a69e85792d534f5c0c6d6acbc2246527e59eafc4da3a16c53d112ccc39bd63970ad1d22b4ce1db04ff5322d6e20a83da5daa133ae9cfb81526b5f5f46ce9c4cf9215e63013e7250517f9acc2242f594a19f27bb4aa30857f7193b62a76f052613aabbcaff7b658428579c37e4e2e3bf1ec2a9053985408a56a7e4a7c535ac282278098c218a3439b259d7545dfa530aa9d2a2528499614e6718bf96dc2c74f9c466198b9d8271e2d0771368828b074234ede791e2414a6a4e4aded3e0f3dcaf262a05305105018a4880ea7af832a9df11326292991dbc9e48bda314fac16aeabc28a5c0e75cbbdefa93eb21a6261274ccd6a4b163ddde66e2e5677e91fed9d6eba7b018413261f6d1eea79e46aa53b4036619204511993921c73d0a0b1e371900347ba62d79a306ca5f9e9106a8d35342e904c18363b64286d2befc191209830850f99eaa626d6a7cdbc8441c5533c71d23f081d8258c230d7da6d9bba086ee0c8f101b41d941c366890a103f700520953ce9ea07277892cb15f4a98c24dda9a947f12a6ca503a4b5e93244c579b8a7d2514020b93204eca5df112e49fd22b0c63bdd5bfa9224dc97185791cb6420db1b895bd9d0d6185495c45286d411cb20a83ab473b191796d524435491b9c6cba57bad6617920a539d94546fec8bece4a3c21cb236db834af191f914c62c8f7721a30b318529cb7df5ef8d4a61eee429a824550e290cdac43e6182ca25e5da46618ea5325ff254c4bc22428828cc55fa63ce4913cd2c2914a66a8fd62aaaed3bda164240616c0bf2629faaa421e413e9894e1846899e3dfe623d25c91a6b3bcecd08e1040ad98439e3c404e110030734c6b05163870d1e68e2ae59757b7b17adca2c8d4dcd3ecb67e7f3185f43471964ac65c2ec597bf7d9b36775b0c69a8d622308c184f9043361dab9e48841e3737076749b904b184c7f955c63828a250c9f3c334d3e75250caed9b762b3274b398450c224acba4af01c9a8439fdd7c97353265d724324611cf92d8f26a5b9c51123615ecf5d9ec4cab549d2658440c2145465bf99ca0e7bf2904798938e63331b2a58678707218e308ff2ba8f2587ce277a84904618bbfb3d09b28475fe890a218c305ac5dbef5e894b3a5b44882230118888cbd5f2e9d2f9935cc8215071abfaf7002c21c410e67183904298c711c2e899df265c86ec8ccc46c820aa3399f54a91ef0ba7b684755c0f4204611893ec6dd3f37277bd0b84f1c45fcbebda3b407c616408ad5fe5dd1f4ca14b647e30fea95c7292cdbceb83b1d594f49177f2f192dcf1c1e41eb5776486eef660caa7fb52bca44bed8e777a30a7b314f772d0eb336e481ecca34caca4c604dde9a329383c989358752525712785ddcaddc1d42b4a50972127cb566707f3497d174e9c0815c5bc3a18f64b5e5a9354e5e860eed49bf326c7b424e9949b83c1aa837c52bab46bfc0b918326a3dc455a546bace9b09197829038186fc4a934daa40f0ea6b5be509efd69f7a37b83f14db678d95343bc99e5dc602cedbc921e4625b3dc43da604ccfa962b28d3ced710d6183419bd986daf4c610b206531cbb52a1822a253ba506838c35e9fb96f71742d26092be737e34157fe6e25c08418349d05b4a49d79d2443c8194c395f3459ee67fd2d66216630552541ea7d09eb2db98494a18490c1383a429497df55563a86e291831031982b7b8a5715461d48418484c124c949faa0157f08184c29d5be6708f982a92cc712c43ada56aa663a42bc60fa5252647de56bacf9fb0d1c39129f0eb3414817ccf104ef4bd29987102e98e2bb987cb1be6209a58e2d98b6d479e92c3f440b9b453bef2c4bb26b921ea1521b92059314b6b3a9d8cae6af3970d448c6389fa3ac48108205932c1f53bdd2bbf5c3902b983dd977ccd236ee7d9f60063470106205b3575fba684a123f992e86902a9844b930f5df2a71fb0fa1823955adc55a25099982393c55fabe90140ce3614c1a59898241d9f5afe9a1600ebe97af214b4f3005515e416ee4844a8ad539bc859b6092e307f9a5cd6782410795a12d993c640926419a509517528239b5dcecbead6e7892604e56f66bf27ef04a22c1149bdbd6c14e5cb31cc1701d64a629214a2ee64630cd251f934a8a7a9914c1d4f76ef183988d168510c1a04bc71813ef30cc713e8b3a1d4a5f528561befc2154d007c398e9f183881026426018ac840a7a964b7e619091a7d7215f187f2cc583b43659fd5e98e24a45d73bcd9bbc30eb5c72cb1eda8529943835ad756130c1939b9a3ab991736190a332adfa45b830e8b6869b9436445de816a6f5e829af6f0b939c9fd5222aba797b6a61d836b32a25589acb0e2d0c7b22e45b4811aed6998549864e37499f8a3d4ab24850614b8832752c8c6582ee9e64e3a24b5818bcf4d77f986c4b955720c7f2ae3096d80bd159eb56187e7f2f457e49b2b266854949e1e492bb64152653d124514dae33c9c3aa306865c96151bc63979c0ac3fee5a8a44654984e5c59322ddf2d1e9fc224f3d624497df707159bc22426e6ceaf782c41a9140659a3044be984c95393c26022cfe4d3df8ec230dab45b25917752b6a23009f774b1846f43613215cff944bdf594406112f9bb2b96ca5452fa84294486bb8ceca8be3d61101a5b2e6a3d4929d44e98d4a69da493977668ca0973105fa2c88d36612c179d673e7874cfd184513b5a6fe83b13e6b294a4fc7e1384943161fab951979dbd84399dcbe82d614ccdd2f1f2323fa795308f9fdc62a29430cc9d5a2d53cb8c4998c43c31badf4a499835eef3c793c748983e898eab13724d9b20248c72f721c4b5f8088386baf1354177db828e30a79b8b8c88558ba146984ccb96502578da5133c21c2bdcff9cf692e35c84e9c33ac9498a75e75714610a17b467d3ae53e74a22cce1aa3f095171f4fb8830de5a4ab1f52af3f24398e4770ef5a09d4f7c0c61d8b8102a7bbb42187d6eb67366593cb1238431d4865a5907616c134f96a70a55724910e69254092ae951ffe1b540982ffff565bf29ed016112c5940eea2e5495e80fe63422eb722d9f20fbc1e84912bc64cf2528d1d307737ad5a91c2c0fcce083e14fb6ca28298d050befc1e0213d4b96d856ea473d983db9c88e973d992721cfc883594f868e1fc69e810773fca53d593ce7ccb883a9543f2849d81f4d0bd261872a4ebeba92b99cdaa68852b9c429b994c74b52077350c24ae45a34bcb374309e52e331a48866ccc1ac9d9f6e974a3960e9d5dd9629a62629eb51b1b784b766c4c19c34c7a41aff0a1c3e1346a94fcfd68c37983c774e759e4afde79de10673ccf582196d4087e5ca6f67e849b2a1b46dbb4ab92c5aacf911f24eb86b307a3a0b9de42ce2abb621cc5043eea74fd08c34984bd9675b366d21c734030d067dd2884add396730a93df30f9f6798c194b6f2db3e7974b530a30cc6f8e0157af1f3ee8ccad0c19f3274608a0c0611fa835059392787f1f038397ec6188c7de69b9fe25fce1dd631430c5c975e8d4fc15f4187c13c66d9463e3d34bc2f3f980106d3693f4b1327fbb8858530e30be6b42d9a5e9d448c6384195e3065d3ada44ae77b13951865388e2e98f472c71093a48f4098c1057392fae40fe7962d98f2cb7e50923a9562e533b4601e39230be6dd10359f727606164c52e59e585ef5a83cba195730a55fb999170b0a33ac60ce13e4d689d413473e560583f0131595928965273f39cca082f1b4f4db799831852a091df35cab9682397fd2740e33a260f6ce2baa849e9815ed0c2898f2c784c6b0815406339ed088d1a7da3c5f0788194ef0f3093aee826e0f8b91b209a62046e8c9d9c4bbcf3899c1044b4aa26928396ec9e58c256c767529432c76a7cac9faef9736b916c3c3e36628c1a07b37f34f94fc1f4c7c0677a7c4d0bfffd30c249853d49b7cf7e28c23983bfcd34c76898bee8d60925d9492ea6415790f17c1b0a26428499ec9a33e398308a6afdfddcb7a320ca397b6b82e370bc324b645b9786f6d20182693a794163db54ae2b20160184df6d241b9a95cbeff0b835afef872c28c9e92fbc218ae3a424d1e373928d9402fcc6f4975d0a2d793cebb015e984dad09d749691726dfae941ff94ef2d2ba30c8d930418d3a37900bc452b9b9bc5cd8ce388d39b962947cf9bd47be015c182ccc4f52bd57d2be85417db4d88bf15372b6308599137a746fdf956a61169d334e289f16a6d0ab5b15f92ccc26c5678d08fb349285b9b742b34b74ff8d62611ecf7913afe4b39561918e9e8258a96c7a85c13a8c56b2d615c6f5dc5fa2bc28d9d35618dcfafaec6207999315062545d997e5925236d32a0c335796f2cdf28530a9c27025051d37f9bec49652616a0d21c46b899656255498c2e5d4a293f0a2e2e91446f75831d174e52749a630959e5bd0132f85e9a493f36fe9b229053b7af018180307367420c0023a30f062a0b3a38c1b0bb08000747c8e32769c4f341670010054c00e03671828572781f4c714904079e47a58040a10001f200c2800003b7494b1020204e07170cac0c118470c040840478e4fca701c39c848800974d820e3282000b5000000000000004060c78fb143c7efc000e2781d870c072c4001a04307f2208000ce8ee31e0508c0d9711c470e8f030060000f48408ecf417276e8a0416301000840030e506cec10a3868e438346024664516cec701c376271801158141b3b7660c3cba041230123af308997b19cac4d6a2c9f957176e373d0d8810da7e1dfb6630c1b2a1871c58ed7d10a326eb0a2d8d891a3860e3168d048c0c82a0a0d1a0818518501465261aafe9404a5525b5041541874cb47ee53d029a7ce298c5552d0b14387d27fddf81bab23a630f6579a18e6a6e4c6dfd01b7fa3e818c3c68e1c2f860d326ee04876a414267b2b4168e95c6107525082097c600513c0b4c408297630328a2a9e347d9b593a29f588280c3f6ba3bba17bf2ec91501465eb33ed6eab4366fe53b6ae31afcb279f6006345a30020aa3c89d3422de914f98f4a7142766661f58c10476f88003628c78c27855662a9ecb9cca6a8d318e182928239d08c20827ca9da61ef216c6debd3e2ba983914d983d9aba7f474734a1239930d7fc968c60c2542deaec44852e610eb1a1e26437fdded812865fb3244fc85c1939691d64e0a051239518a144625d9d7ecff12f274732868dcdc17918ec24cca7935c9a312289a4383a8850da628f045aa62d972aca24198184e1fe4cec77ff8e65a547184b0e2b1fc654ff898f71c438578830e208c3dced6768132d97be6bac8d31d208539cd359eebf540e96c30893a4f38fd2d14a4b32f911c392df412f22f38f1db34eec5f30a20893785151ef36cbb2ce35d692df818e44987279282588f498f2f91a6b85630411c6d17b625c304f62091934c6c071e3068ea4588e914318bbd4c9e16ada8e114318f459fb9f160f395b71ec388187879916c2ca1baba0741ed558eb010e442307c91103f58bb10395a1434f30031a141821c48e0c42470461b4b025a5b5cb072293adce0a6336daa22a1e3f48d33c51d515afb1f6635c937123f9c008208c59f2e82f5349237f3066872c31cf84f2b8c960c40fbba789659ac96aaa452dd1e4bd52d92526df69a40f0613e27ced83b769ab4046f8703546f6701e7a30a9d52a29d4739aca96913c24ea34de6db4c83f713c98abba54f4bc0e1bafe3c7c81dcc49e486507a4eacb4fb881db20e5b3618a1838eccc1a42b3684475f0ec93df567ca3f290e066182121c8c2596e91c6e724a42fd6f3069ef50f217773798d4e8b96c32649d9ce36d309a945298ac39618349ca8b9ae07943c92d598329b88f7f882f2b4174d46078cb1e2b6bab73c99d34984cd8ebffebfbf0d14683714c54536577e964eb33dc88190cc273cae9fc16af4cb60c261f75299fcce40735194c51d394d8b6854a151c4331677767df2e73312c05252ad7411e11835145f4a44ec235128632028632f2851bf18249f79654763add85d26da5e4e908174c693f4dfc3211e4f81d22781ce4781394cbf13b6cd060640b7b6ab3b4fe338261440b86134d0e954ebe9dea932c98e72ca8dabf207ebd62c1ec95842c31d146aed0a75d34111d762b182ffb7eff08bb0ae63dd3714a4b72840a26f910eddcf1bfa1323205d3c9572e77eebbd6272998d473e62d892fd9aaed228c44c10c25ea7b12e4fa0a05bbba4ad6aaac5db347fbc8e9850d234f308c52a53f2b7a18f39213cc9fdd3fcd8d29b74ed204f3284950399e526aee3d4c3076a7f512712657b67009461f135521ef5182491097d386de175d79adf182912498a229a9ba62877eed7d368204f37deac81565795e7381c3c8114c49e5af133dc95b67a211cc1d33faf31e4fc7915d61a408e6fab7f4f5e291c208110cbe774ad67f7d18c64eb1a4e90abae6a426228cf455126b44f6a41c54826190bf664a78b8458061b292b3437f1b37ca2f4c254fb624c99a2c0422be3095d67653ffa79467d78be4a4f465956c114a8a8618e74478614cbb4cd379ca1288ecc2141f43eae97e35afb8c61a7b40a4fe8f53be7aa1a992402417c6b470c19274299cfc15eb20820b93c679f796f8262fc9ed10b9850d115b5c2d54841629320b7369e734694f1caba0d31c3bc810914535f3157761c1da2de6a28d4ee93d9938a2f1d658cbc1694189a4acbc46c49f4a81857135dd6396506a59ba2688bce25c711ead300f11569854ec1c4d4b2ce5e5db2a4c823c9d2cd5afaa30eca99deca1543fd625920a115498c44d999c6a07955b899cc23c4c6192e2f8a8f47ff2521c8994c23c48611ea330fbe7f73529b497a093444461927949edddeacc2733147a885730eb9275f9ed70b97f4d6c2e020ad35ab61c1d4a1d804c887cc2647625d77a520a15f4229e305dea4ee25d4ff68abd7e42a413e62d69237f945b9c688b70c2d8bb96a4db988aeae8092012229b305908176f3975f259521326159e92fc1d9bcfa7cb84c9e434492c347e5c344c986c7b74ccce2988e5942e442e6150a29efe2816114b183f653dc4dea6758e8954c224077d524a822539c93a11a184f1a472511f4a65adf6e01099843975bc74aea39684794ecf426aeaa9309526914898ef76f32c5c229030cfc935a52f3f970c51e41126252bdfa2c77684c136d64f9576451a612c2947fd421461449d24a1affd6974c6b07123c7b7c0078e83038f6ce44874bc07506411c60f192589bc8c4f4f22a20863a9fbad0fb58b24224dcb32266e15ddc275980a2ac69569ed8b082288306cbec941a8cf97bd43987349d966534f9785dc10fe8cf8f826dfa4102649a7f49f7c564218e7efcd4ef6141984495b69358b1f15dfff2208e385f8cb4edf25e56ce204914098e412c24bfcc9950208736fde88a648cb1244fe60ce275a52bc5c4925bbc50fe60d2d5a3dd5a95ed12929d207d3b765653d93b39ea8dcf1c19cc39a18dbca692e28ddedc11cd277cbf4cbffe975450f267525c449f7db4bfa532e0f86710f537a3a3a05113c184e569ab7f8bac81d4c5a224aca88a99ad444ec70b625ab5597f30a9be5c240a40ec65ecf397a10659e318b41840ee63a759dc49372ba5451292273308d1057a664b7e79b89881c4cf1f2838e38917b4b6986834988dc1033a954d02929f206837f86fa6c4a4ec40da6ef58b9cbe28a5610698359cb94a84e556ddbd12c88b0c1e826c8d2313efbe7cc4e640d06a584d89a245d4cbd2435984bcd08e939e55fd489481a50040d2a72066377ca61ba4e8e19cc29084f7a52d2595cb63298e49c4ad27fd16c248890c168a32f885d16392a976330579f99b47f520a27e445101183a94337f45f2f27059130984cea6cf392b3db9e1087200206d3953c7aa753f8af245f912f18aeaeafeadcbc238878c154aa4fde5b5c184b97638248174c7de26ef7f58b70c1b49dedcce494cfe1df10d98229a83939a6c7490ba6cf355917a79205639a6b28cf9a08160cb3a3b488e65a0221720563b9b5574a234f9424a714112b98070e44aa601e2f420593b65827c5cb39e9ac1415884cc1203f7f1ae59504e56a12838c1b3674f00944a470ae7a66a9d27f0e328e05225130d55f5813c363c4ab480c326a243bce8d7c8108140c3662257aa7d26953167982418ef00d5bd1151127985b44259593242729497d600513d80d8834c16097a4648b30216b20b2047352d663a37b6139f94a30c992c2a9c8e734de27c1384a1056b284aa8d3a418239b5727dce614b1e751fc1245faba5ab24664d6e0463072b115d725b04a3e8cb9eaa388a10c198e1e9baa4d9cec15b43840cc3a85b2a3caef6981027c2b0c45c95784247c34348304c9653a82badbdd793041886d55c933be83a3565fec2606ff36549bf5d5a2e0b21be308593c3782715bb905e987b9497ece085f0c2b0a27582d0bf3c3da65d18bb042bc982858dd35d1706a1f565ba9e5c984f3095e41bdd679260e3c254b363929a249d720f86dcc2244449c1afcf6d3d46b630b6e596a07feae1e3b53029213f5d57a74a10420b93508b25f9e9ab9e3c08426651d97bcba5fabd9a6c9fad9e105998bcc6f4622407426261028bbca8844d86c202712c14094522812814c549de02a31308001040288bc562c1589c4792a80714800261301c30301e22261a1810161c140844a27018100a05c36030200c0a868361704038d72bce01d27eef3b00b85f514892e958453c11e6f509852fd6ada4feb37c69993829a97f39fff8442ca5d66769eedf13552078fa948f347755964205c2adeec70de4a7bd56f01fedc60a52b8fe4e865a2707a21a7b1b39cdd7739a0f21b1d04218dfcbe703be4b8fbbe8cdd5a0edba8dfe5c5052de871837ac68b65c1f84516301e64f33055d564e4841430c57abbf30a7111465d1855cf229ae23dab1f9d98093a221048ed6d73c2b5e2ebb173b4225444b156c07728f5cd09f9795bd4d32b89a3f3b4ab83a1eb80ba8c5b2a2e6cf5c80a53a9caf80c1c9c0420f93547c67c43496e2e0b159cf66549cbc5d49d67983af744d3a9cdffda91a3839816ad87a60072c2690b22688df258f08ae08801288d08dca75a5112e5967a1d9c9d6ecaf2ee9163245504f4ae297d8d0c3b23ab354be07a25a1b7dab9f1546ea510b691178ff3ee3dc69a6feca02bbb77ebe8922a89001681f48392cca6e919a45ac28f4064c9315efe2e506ed11eacf4fd913d21176239fd38b5c454f1c2feadcc24b5c09a1fca23267941e115c686c366b51438ee787ec7c218f2f3334549a06a5b3d5297ab3cc66069ae4fc3b9c2856904361e69f3f9192269c106284cbfac206694dd6cc7582c8902e8b7b4587b6dea3fdc8690b623476e0d197611ed745fb30fa1a02b2d5557aad99f7e96790b8a43b1d4de056bb4cd140064e4576c393363480a54b0a490963eacc1e86f0beeca0866a9a3f9e9092ae474381d56ba45a8b5b470585820a3175b3866719e4585255411d33da9d9320e083f3a2fc01d9d0fd3058b1216ca0f5c15466994ee37c9de8827f6684b292d5f2eed7a2f479eefb96df17177a269cbb261c6b1d99ccad084f715837f8503afb878097b7499e7bdefd6b8730ed378efc1d2046144a94077ca6b359a33a9a269fd18f1deae2513462894373c94810ad9c2c4ef39126295a5b3bb99471c106a55a96a996a420d9485ec6a3f893bc554d13c3b31a8a2e74f3b374a7edf7dcbd92f2c571414c91b724edb8b56d7cccc9dfab9da7175e40b5c3a22b8e584c64625297cc92a5d12f59d91c426cdfca70cea6106b4bfc77a09ac31750d0a815ec275b6e42ee997396bd68c608608629f38e7acebdd0ee29da536d6a57acf58626520db54e03d74b36154003c85072a6dcdf2ab8e9bdc2728a4c21e7ff35af8f56384f969192ff6501702c45b38542f1dc6c0882c04bfd34c300a1a7282cda3c5084a84adb665eafe29b4619c726062681da7b04edde8599eb04c913b24181b00c63eb24d0d89a321270d0576a699e82f91eff82abca9d752db3659ee7772e58cc5cffd2e7db2df8ec1f44bc5420613316f34758110ef78e11f4870587f55768330f4157bf239b242cb36a33b0d8ff0d6046df21626fb402afc73812cce104abf16a4b6530f639884be0132a5b791cd423262a94c4e1c47ec56cd416eee1fe8087f1310d3bafe70d5db643291c875994f17631c1c2c6635a0e35c5f31a513bb19d26a29c40f3ae5ee0c28d5d63df7e932d3cd0642b3e863e90fff9f3369930dfce206f6079e51b8ab8a787d7238cd77ba757050cbf2053b644689cefdce821a4096d58734c50789273b0b5ca466a83a411d353d9ec2a4209b743371ab4491abfe2e64ba060f0b9710b929dbf00f28736fd5ae23514ac11b73e0c94f863477ed0ba0a83f37827b51359469559e8841f6f32d1398fd897358baa67f858de7f406eecf2deff960266edaf14a828cb3d3cc829b26b45e008ded1282fd53bfb94810de98cdd7578add51b674a7580df293d368841e71d9603f06c9b58ebf861829e73949ae2f1cd63050defd4589cf707aff9dddb41796b22ffe0a82f5445cd59e5c1c637364790f863c091483c61de7dbc347e1995182c7d27826b54ad434161a85876c558422a8721f6f16c2b916f941c337c725554cd5d298a28d5ea7eea5398c6af90ce2edd9a3df4fe778163699c87c2037cd9c4445b6d2d37dfeff0ddafba6d7eaa8d91f17c6b2751c761dbc7774f54f32bede8981420d69dcf716543192848fe20e80508512d052599e96bf00680aee4bd3805a81159eb5c92643480e6eaaabab34b43885cd099e6a5449984c1a6364fbf728478630dc8f177a6f382f7587166162f829b2601c9939b37049dc47f10a79fe074ec8e70c877f68f0c7cd07ee37a01bfc0e6f36807b9da112ae79e72f453ee9f9680ca7aac9caadac15c2b80a7ef8711ff954d289462c4544ec4a4252c594faffa49d4346404e2bee859b065bd82d1064d107bf863ff7ae119bd765a901542e702974869d4611580e09f6c61649eb00e1e54a9c267989f713aa6511c27c40aef6beca967cc3bdaaf4a54a327cee306b7697400b62d2cc25172da176b3281740c0531022a6236ba6860d074a4207132659dd70f97588c7c142585268b6611c86d5e74ed83538a11e5faa0ffc71d444630bf645dea15234430844d183182ef69b5d5222f15448dbf63174b00001d38167758450611487cf9ef9e8378d0af1c7d52ed9a8a40d6de75022cbb1c9e90fb72072e490f18050753655e36073f7e8c80452d2e87110da03808800f4ea369a594a0c08b90435dcb4f97136d7f2defa2ab168103a33f723e3b762d77396b3ebe92e88faa2491a3c222819b1f41143f3f9083b353e991e563952a3bfbea4f33ccef436edc917ea9d26ec2f4615c64cb43a3c089f013e0e52b727f6d4c3109eca2a26c4e5a40242aa93f1345bf8d4d0a483b3b2158f91a110abd60d1c705d21c7b98dcbc55a326330a2605aa31995295aaf9781797b22b15ba412dcfe5c22722f732032b8a5daa5e7a4e5c8cdbe046e3e43a6580fcc8a6900726b7f609474efc7d0eae89e0c768dd28920ec504fe5ee0055b0bccb2ef17ae5cfd1c0de09be413aebbd6f7a5f122702d359b85dcdf84a70e5eb3ea5df2a9f9733d73bad765dc202ebef2c7433562166b1d42bc4c20ace8bd951990ca5f450f1106fc28891f9bf3491284dfc54afddd2bbf0ca04071f5293cc6a2c57bbda040442ca19497fc5e35a6b057cc1af3eec5c5623c008ca1c5407505c0927087987b5669c6bf50b66ab40809d0ab4b7abe1a0ab7d1f5f524cb52cd8826a68f23f5e221f65894c00782f598327ca4efefdb49192bd5874c3e3f6aea8499b475b85ae3ef7ab44edb7962b8dce476d86895b2f77f37e46e5f2b422698f93d131e1ad861462ebee61e34b5a1db93ad762c1143325b79a8af4e1152a4c522711f5c1852dd34b23b650d1eca3a66b99e84600ea141634eea15004d982935a73693493fad6543ac344a38007884e204353070f84a4addb531c8933f8b5d7623c08b993c18fb4be5a24d5510d5cac3d2a654f44288ee7c9217ccd7fee0a1a9a0568f8174d9aa901b3bc69270a4363c822c778532d025d3caeb03ee49f03cda6dce01029bbd5a89bf6e6b4ecdd6e122ec0d089c71e78aeec49defbd8542c953663be180f13176e54e2df4f56b621b499d9c8374895ab2dd66a892eddca1bcd823b8ccbd06b7f3dc4f382be151ba4f063635f411ab1637a9dcc8343d7581224794963cad6d7bea7ca9459dc185943364048bb12638d642a78e22049456c84ad5f819aa74fa8b8a26e2cc2b7ee68c08e0b03d8b9011a084d3603d5c66051e56440f95f377a101a5e9f956184c3266bcdf81e11dfe30a4454ddc8b8692271e8f7f6b1ee9a9642d79f5e29ab2cc77e2a06e1c9a1318d1e4e0dc26aab378590d9ae82dcc131ab7aabd14cf3bcc01b7f0991b0db060b4d4daa0118df6212d8a029c91b79cdd130f99dd87a845c220e5402fda990c3915080cd15e62ad9c971b4b1f85f24eb5d18b7f03a129db6d890af048c062db80cf8aaaa3e3569d1295a5cac7b48c0140453ee7a0883a3ee1e559377c97f4be200a569f6535bab0765180877c6870480a2b27574916919c0f42579d9d87be55b2c69941902f75bb0522c68c244ccb7fa469520ecc49dbf2c5f21e57572c8d53953f3a4dd77ba40f20caf009820c831ef7240055d8e23f6df7b1d217bfcbffcc781d09af4d8a3042b2b2d4495dd4f663a839944471a5cb6e22168651bc0bc57da031ccd2624af209394358fae0258712066c4d417503a1c7d467eb4a55706a3628bbc9c199c8fe8454d11bb9300b585d23ea02ed701a820d0f72c3a5c0e984ee9e5e86318cf6bcf1180170a484a576750a36d3a3ec24b81b16a0fc75adb90990c77ae8ce6a239533a0732b45b3341343364c1b0a0bf8e5fc342c8892270c932971ff7cf19dd4b73844185915a6e5b22a0b779ed805194c9593ed456419dcd1b67447dfcb09b342ced21449b9e1542a95d8fd7998e00077edf8ae3eec342bc72b7787e5929b4652d0fa462f211aaf31b584e85cb604be2ab6f9f0d4b663b8e3671b3602d18c231fb1419fbb0d5c448a25fce60e5252e5a5dc790e000c5048b76988f32475faa4815598a8672a39cec134be4283533d93c86a86917e56567512464d55509d387b2a229ca90159a5c7fc0999bd1fc01db1081d66c95472674f41c6d6ac45ada8fa14112c3fd03d96fcb43657e87648cf92cb467c4bc626e2f33a02597c39aaea18882b6911a87de7e4f75c71acda31fdd3894122c87490bdc7c955a9dba4e670d257e91c40608992c17ae3e5ef8a78b1b71c6d2964ded9e81d82ecf8cfe3979fd550803ac33665b7b7c0421482905c2a640914f81defa09b2fd1c8dbfc538e9ec473685462968ee2a76733a4650450806b943690dc056a3e928102e62a95194cbec13c1c0817050c40518e12b9939f2012c43c43ce8242808934c4be4e050366ac0f97f630c2f977762f439e3c8c2658e5093748c4d9d041a637a0a85708a5f69b53c6a96e69f55d6c7d17cfa951147077527eeceda06695c58b21b82b67c8ed511ccd040c9a6177aa912ae7f752a64548e822818aa540d1f82062a970e4b3ab557420ec31f9382bb4d8895e4aa08d747ac8da75dcb6e4755984c4255ef08568816f774b0258b3f79bc738a0caa082609a398a139f2673abefca59ccd5ac600a0428baed34bd84d9b784c4bcb803d96a86337f9ecb3de64e0d5a6f9d5b2e2379f2a3ea82bcc1cc9a4b37cf97ffe25db81efccac93dd706317d245ceb77aaf433b2404d8eb51bf09e09c78adc2248695d1a461d40eefa4878a03300b5e5a683e11f50d6d15e9717cd34d8770089e9b5a3eaecf56de5e8f164291eb3fac13e26cf130c0c62082b3ee1455af50ad212737974435e78972f11cd587c07cc7ea24a6f3aa382375152a83191fb40780a56d43fed559e88fd9e15eb781db1a2b85e748d6659bf0971b2745e531e53d9a34c82d5ad9261f04e00292a2c15dd0423454624d34f427dc7480f5814e86ac82f7c101e014238ca1cdd84d918c6a1d9deaabfe5b2881f8e39536aeff8af171a06da130b8807f69ae243cb0ad29429f98c8fe6cfa9c076f72af35a8a18c8d7c953f20dc83c7f98029e8dd83473a4c8f77e9af0cc02ed643a173776d9acb7025f72a40c51ec25301d122c021b9428d4a8be54cc7081fb932c6cc9ffd69e69a105924ab708e1f32254a658b9280f918b80a439257449726566064630389b456aa4be15d0b0ef86aee0f72579e4a6748f78c5b72e14a61969d5ee344b098c0bfb674ae4170b8008adcc08ac8197af012570cd7679b6c872cca65ad24f40ad8fde0036d6d2953e405b4b81aec8fe5f26baef852f521d94ddded9d1291fab3e2f1f2fd275ef4bf21ace4e61cd8cbacc392a6c06e29d28e3bc5d6d65458e247420242ba964be407f16926cd51daf5b67aac4a5bc2ef1a5c722305bc744422a41f4fddcbc8a4c7ee3a5e5a980c1a6964c21d4400a70650a3a6c576a5c924bb300fe47b282d584e452c07339e073ffcf6811f8ceb7b7c1441f50e2e6336b9d9a4b1a478113db3e21b588b8f13580d91cee9f6d2585ae1e86d8c8e59edfd2983cae5f97ca80c3faaad7091a6b9c8814ed9e5ae34331b9f0d47ae6f6e0ccf74325d3226ba3d68c4e5739043d15c410148897e03a4c8d47cc070120a58c963de934184fd4f9bd503d579916ac173dde7d18476bb4bd07d4ac8e96dfc7437fb1e3dc1e4533f0b6692eff866b685f1852f7ffbf2ae2019ee923d797a32669682031ab53e3c57110acbdb088aef9f82d8a3b908c25a9bda7da060cd0be8569b442bef9f02cf1a41c97d100fd088c87660d3a0de2f840b795aed760c83ebce161606f1a2091982ae49940a36e4884f0dd990e0324b5cd204332b846e2dea877074f7b4b4a6c4a6098219c467baa7778876fcd52ab1ca743183c52609cca21fc8b55bbe1dd9565c69a3f576459c14d3015f39788f4ea8a1d0612275b366467fdefd608e6ba0748e3a4313f12782f695fd09f3bd3f2e1b1faef10a82d064730e429da5a24ff5c37b6b6e0986f01fc10e85340f6b04947f60ae6ecc6fa1d79bb361e05723854bb0d01ac26534739c06fea3848628c351881688fd82a8fd009ca37ffc5cdefc6707c3e10dcc25276a50f8ce16e6277d5943d506cb689675e7aeb0e9775428f60e417d1c4542e9829beb29b22dbe240ec5cb015c412ae3c74e3a7dfec142e2c0c7b4473f0bee1b1dc1e9565c3800c71857c2b1cd6ebc41e33a976e85d9057681ad4c5e50add5d6cbdf0435fbe593ae660c2427b9ee6b909daeba520a2b8e1cc6ebf4fa4ee83e746670efbb76e745522fc4014331064e28f717bd1ce930e72031a927089a8ebf8dbd6627b835ef7774d13d58fdd3fd4494a6e46cffbc193c679a2b527b9b00c3ceb16aba988fe067c0242467cd93df9fb862262181a223b7f2c73b48693995e6f532cbb42545a68eeab263b7050674958684ba326700a63c51ac307846b79536ce33d7229a4bd02180eae702c98a5b3a7959f6acbe716220f5a86b8c46297343fa183d0cb3a1a3e3e24e66de6b3d68da27b78576b85159c09fce7289bfbce34cfd6404081a67e5c6f28fa4420b2b33328ba39372ab5a8aa5fb27606bc758384ed701ff83c0fb2e4879be8d015206e3a7189a9841dbde58abe96762e39cc0a713c9638b4bacd66a4235620229e43efe040890febaf7790f19f179af80ee24e8d023fdedf3148f7e1b069a7ede31ec29560becdf24a2abab0cc5368582493b23f1587c372c8538e92e535ebbe8fa73ac20a6a0c93e5969b827f6737564682bcd4abb84d0efe2c8c032d9252e22e6f17caf6a15e520c9a2f9a51a1fea042731830b9c3ca53bb129d1b53f27b0a12f8b130de612bbd514bcce13f1279660f440aca7f28aecc44c47d856dd80b0deae205c47da81fc983964bf0989cba8c9edba8a363073adab7e325b9dac56bc37e5317c63bbe8b98c3fe668773e5a1baf818f66373068b901da3a414f653ecbb24cab9a85fd770f1a11cdd0d75e84ee3f22edba93f96bb337984829cd4b6829cd931bbcd5375804fdd655cdee93aa6631f9ff2b1f4eb1e5dff05c545ee705983abfec33242a10cdebf6d5c911a894a579f5063c43be06bbb2b67624ae902973fe6b07b2edaf5b7a2daf4a378c462666fefd3b1b9ffb649c62e6da6d0c3a35aef4a09632b7dc316e929129a4c265baee137b66163c42a295e53a27ecb33e21f4cd1235f18b8cb29655b246dfe143ba26ab59ff5d1ac9f227b114cd93d0632fa5bbd8caeebd6ac54c2d90d1496b49ad248b84441e21d828b0992d7c57930a68dc0932935a134d91100da224a76c2cf687b69b5ae4fffeb72699776394feb98d8edc02d2d848e7b757d07e3fe4eb3833f590e9b0f1ec8240c5d93cc52ab25e524eda99fb774922fdfc56191549a12061c1af075f53c613adb111deb9b785516805229a3b41cde2d3b3d1b767ac9909a3bc4238ba69c55228aa0457fe7b38208bbdf753b947694828f3d16598c3a9706f745872d9ce7b4c8898b440c99e185ac8db6680dbbb29d25022a93fd5d8bb98d7efcb81ebc7c70c7edb63f90d3bbb8ed4319787b5738c20b7501bec8d97964d5e82fcb2c6ca61f904c165296a00bfd14767a4f779cf29622d8d1bf29a91544f8d3ca8b286b4a9ffc786bd85cce2a0ade7d3f491896af57c1b22144623f32bb59eca872a960336bf0d25fe2a5e7b070bfd893fc715efc5be49eefb98e539aaab059aed227db8f719a72434fc01761e6ad067e0f202c72d0921385929aabb5070f75146d4c88411753d891ca9456eee3203ea69090f76887fb403bb8cfa202fe9267bbbd8e7e13d42e2d3f3b7825644232539e39ec4a591ff6c83493182a41c7ddb89d844abcdd8468010fbbccbede504e31942fe2511ae804d7b8fdf0b85ff4ed07de98fad90b94c2e37944737d1dbc41e3990bb3bb8b537be79b51a06ef3ceb7bb59480ea51912a8dac763d0e24fa6a4ce70d4e24733242f476842225711736f564913a9691960d5f0ad512b80adeb44681b503461f0139f369489da0427132ccb947afc99a2a5b006be98bf1bf9ee1aef88e3fe06e4b9e18e35d283df391ef98ee88ab84457fa74f06d442f146fdd5a4403fac43be0e4550b5b43c84efbf7a2748584af707351f1ef51222c5d09523ce2ccf2fee4db20e5fec98167eba2b80855717965c76dc19cb33edec85fa39951d2a7146ca8c7cdab9abe74acb20c1b13334337a90699ae8646283c9e96fa59859a55228bf9a8870c36a0910722ce1dee6616772d991bd8decc6c415dcd4d37e75a519def85b94682ddeef8539f910b919ce4c16d46bb60659fdb4f0e11f53de68a76b009d2ed7d8538cdf07231896e622e1a8b00f54904cc6f20c1ec4839866b88dc28c7388c755b0a58e23651b3d1ce085d88646b0710fbaffdb5959a8e5388ca360fb0fdda5e0238d756f5f5cb91af69e5e9afa899404de547a9b7c2590bcb3c05018c4c80499126916e2d0f6d1c4f207276337e32f5362f0ef99eebcf740544d9c00ee80ea99dd9aae37e40975010854c829cc979f2cfc9d5a253a5674a56690e3066c9fa6fbf9416c29c28b8052fddc84d9d69bc72aa258edf0505a99e93fcd55069bbaabdb8e120c1d34b0ca7218e73a022a9c26f687122739036048384de7fd89733c6f1415ccf7009498b5a4de553b483d3436dcfcd250968e3c80bc5a11506f7d03782afe0e48661c84357d8c2dd1fc759525f7a1a80dd346b93ee33b526567c41da09f94f02ccce18451d5426944c88eaccd8474a4b11bbf21b526dc8ecb30903323316a07631128b680afec1a9f2400671ff745a903fcb8c7407c1894894fc0cae758abf57a5cb89872186e42845b5f69b4f573019054746080d12938d399a64c7cc16e1bf7adaa72d646a1d4f785228161462a08e71fab62c84dd9c99e5fc45d18567647a9c89b5fd9a0929945bf5d49d50a63f61971263a45255ca99ab447f5e348b515a25289a953ec813e89c1b69d1329d78b6f6e702c45221e3713066ad22ba70fde00cbc46521106baee69258f3df20d8936e3b4de2cb7e70c3c824c8119ed0b63fac29774b24a9e319cbe1ec922c7e18a0a3cc58b8b2b223a9e3f20a6b577826940abce6dad8f467fc9187b300823b57a9234b968d804dcea80d26391720fd45582dc1be011b390a757a8c8a4c83696fc41859a46efbbf8a30adee59481d3c31782a6888fc13a0476010604cec3c70f20d955b0e8cae6f8d0b678181447d11798e9b5dabc1c7ab88545d345e6f1f182c8308532a333933a8d21f4e640628ff32195aeaf8adff31560fc7e68cb6b183627311cbedd03258aef3a30c712b92f0266b233de40a517f2b13fc5a0e6aea12bc435eede24c8caaf7e4ec971cd951181f36857e1c4c78ed8d43e5380ce88dcf5eb9221eef013dd0d3e2af7b66aa650d1b0e2746ce96e04ae6b3186c51b22c240b3be458ca4538bb1cc890fb6cbf02ce20b9a0f478072db03a079026a467478b1125c7169c97eee394a2688719ad9e293dfbd1bd7a0e96a5d18bc554ef602b483310408c764367ccf323f48cd7f9ab6e8f725333b19a66a1431a9881f36c359059cc1125cd31d1953bbc64572177a725b07c78a89c67f11d019fdd387cf4ec30ee8318dcd76a99cade0d0c45a9cbd10fd93688f34a17f5b8999a2a6c5e6198f74e0b4d0013ecf84c8e28b3eb7775058eeaab7c32160aa0f18b8c33eaeef60f3697dc62434e60d99faa3628b3a94d0f0d85715c383fef38216bb06ef34300408bd73279aa27a0c9aa1549a63388e32d89d61afaa0901c126bc8ef786bdf082c238ab1fd84d0cc9b485b0b796cdc17e1b7d16300060a481f6c5b99551092fda0786eaa9702f1e7bcffe03a5a36422341535f148ac007a3ab8c891eb363d54cd9dd39178da932f25816a0a474e96b7c77ef18b88604c55992a2c99c2f5f09004fa9fbf53bd45085c45be40c8872cf3399df238e21bb8b304c03208409eea94bbdde75f4b969af38d7216532a7035b1b47800b732e9fb40b4fcae45101f9432e965414e9f74ec1e122f2b0616d69383831420a0239d2acc4efb4e07f387a257d21118d9e52131f46fc4a9dcb965e46d112772dd5418be938aaf6c0ec67c9ac27137af60a38177c42a85a4a6135e4534647aa28fd55bc3bba0a613d88a72455ce08dff531b031f0d5c9ff8e069d19217678e817edca92e9b726047665b40cc021801c4eb719d1f51659b3292d2f724e4d1fa99bb481295472c2057f3cc1b2ecfedf59338a2f7fede8128f53d5f87953090b2c7823b47c9f53e93ce0a0eca7c4d0227cba983f5a20ae210321b20c2f4b9a97dda5238268322c5d18ad608c088e51a6cb14fc3be9dab5d80a55af0181b4e95c9394acdac928cbe79f304ca0037ce4b8c0a1e43ff8e54ad8b4e6b97c13accd31640e5d44b8d3302f364bb14d7637986c56a7893d67a3f96f8ae3d2d05b6c5b965a04661c5119080c4c507f0b07d9de283bebc378650c367c24d2658652012515eb75038740ba97954f29cde1978d30297aafa06f2d20ab72d485b96ef2b135b324a64e28f147c9866e6153e84529c4a220d4c90e2fccef893e39b12788625eb01dbacec409bcc038b320c0e80053489ac76a04ec39191500607c1b01049a3561c1f9652906bb28b6bd0ab6cb10436e21dfe546388e6c79c89b2587696736b4a4c591bb6b5567a916cc297fce14235c27ba0d4b9fb7168e6fea995fc71ced97a7a07be3109d429745d3b2167ef7bdf98e50dcc0e70d8eb5449fa1bf4fc4da340abcb189732127b4dcc2a73290a7656eb91cb0893cef64eec483e6639e0e416b3f5fd0708404353698e4531e4746a9726cfed18ac1bc7819bb16d6308dba9eb7716d0661172eea4f44942b266c0c50de7ba1df2d3757685d82b3fe605a27bda547b187f5af0ae0d678fb27089748c7b2d4646c8b1a629fee0c9df95e212c3b053b2383cbd6c85d4b065b6995735566420a8ee17db532a60c34c072d14c111a8e36c741081e2ccd3b2ad18faac5400e9d5c3411740e3268b4449c0a84a0b24310da871dd315ad3beac036e0b6e74c51361131f58ba7cd186d5b437d0c55a07abe61b01553a92c2d3eb01d5587f61d3f2ef9482f61e4d1efede76d4340d87b0fcc9d7421267f14a22cca6ff5bbb889c99df49f24dcc0a8d4c1f72607337949f75a74e81b35b38b4e5da4de598cebf9dab3c7129bdef825fd1c4b4f8468b8ac0810f14d4c07e9e965c0c6317000b1f5f1bbf5ca3c9771b33278dae93483ce9243794e62036dd1f5bd3102a889afc922e6e512bd235a70eb2809ded3005a07478aaecc17002dba479371de6bdd8332c2ccc073aab89e83af8b8c8e0c0b6cab14e1a33b9f69c85e3e9c22b1ac0de924570309273fa30e242f35d131d8e1f8c51d11d7b5e6b0baefffaaff96020620556faf460954223720d04fea71bd015cfb8bd144970791c9b82fdab2263513f03d4c26ad3e5dbb004bf5b63163e3204a9d0dd0a5d0284e4566be9258b96a5a0164e39bfdcc2b58f76d4112dc52d7812c58c9a47e73fef65a45bb898fba8d7fcb8ac1d266593e9b0c2052f46d2d3e2dfd6dc2fe1d656d28a3f04c01d003ad9656c5100b603f819f9ba32eda9dc1643c6cd983283534208d3a4447850abe58d22f971298dc0a5f8fcaf71e534ff4c7d70a783ee30c57032b86a4b9205f3000c61ffebe0c2cdcedd5b02ca80c3b1f5d301c482fce2f83a5cbe317b9ae0466105aa993188faaea171db0c5410e0b468d82b6d4d183b860f1787ab6cca3f567f2f29c179c1e538c4757f825369b411196ce827a6c0b094a29243ae57017ad2db6204609d5f2b4092296f55e27d55801d2c965003a50af84619f93e957365c3fb6518c9b18e070f8186316404", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3a6772616e6470615f617574686f726974696573": "0x0138c8caee6f6eddc41c6cc55e554343392cbc13d2a8a57b97f6f85fc965bdd20ce8010000000000000083f43ee3e4521b55de0fe830847fda88a6b017b87979af1a41b180c39da1e4b001000000000000005fafb6219eb8d463bec0370b2aab69f45fc780959fc2eddbc7703760aa34202201000000000000008270a62b61639ee56113834aecec01de6cda91413a5111b89f74d6585da34f500100000000000000622c382187c0b2c61ecfb17443294d11a9d2ab770ae6f1fb49184a43906d59fe01000000000000006e309dfa4c8de814cad140b8612a9e41bfba244f9ab1468e1b5d9b3cc1f5e56501000000000000000509f9caf32fda5584343c473b386c433acb99fd9400724b8cf3c618d840133f01000000000000004c669b04865e9acaf7b72bdfcb0099d70d9ec63c8c2d6b8cb0552815d7b50a0a0100000000000000e41426f7465c13c48335771c5450bf61c50a9cf28b9274f170c7421eea7974f10100000000000000ace46e899b90e75199549d8fa2ae7097e896ab3c845217e3155f99b6ffb888030100000000000000c9a68a26e9aa37ba6334f1a20275e3be7d3a9d4aa988627eadac8ea0d0a2dfbf01000000000000006e60f1e253735fb113c183fa603f591e4456435171f387c0849001b428b5ccb10100000000000000c8de3a01502422b59dfa601c9c3a04a98d2bfbd79dd0810d1d1250feab4241ee010000000000000064adb43a7628139f6c02100f6a5465dbd33422418426c572b12547c5a665008c0100000000000000", + "0x3d9cad2baf702e20b136f4c8900cd8024e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3fba98689ebed1138735e0e7a5a790ab4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x42b50b77ef717947e7043bb52127d6654e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x47c9410b11325752265d54845357656f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x4da2c41eaffa8e1a791c5d65beeefd1f028685274e698e781f7f2766cba0cc8300000000": "0x380a000000030000000400000002000000070000000b000000050000000c0000000000000009000000060000000d0000000100000008000000abc3f086f5ac20eaab792c75933b2e196307835a61a955be82aa63bc0ff9617a0600000038ea9400f05e7fb75a3f7a92febbf58e5a3060dd06132ed6d5d68a3d75ec452826ad90a2c3fa2c756f974628dd279adb87935f7ea509856276e3b86f759b22451c303094c583e253794c9db14803585baaa74472f4ecba846defefc8aecfb6214ac45a6d0878f808b1baaa85dcfb4e930ae06e3205bb38855527aee6f259e3327b2253ee3c02d89582602ca5b0570cfc01dc82cc8d1b9d2071eb5db6318749124b02683131f96baec9121383995904c49a02ce2c2451f8038291e5db2dce66663e0a988fb965b156a07debf072fd9d34a9c7c0fc0e0ff5bd63ef766afb76e2b32890032c39c968f486f77f8764301a8479206f063d49eeb9f6d499333e2a1be045161d0af40e6efc165c17d0189bd2d770bdfa0a9b8393cb89113f473a2e948c680ce84accce1ced0de223aa72f760f1b3d13ddfd267938cd63e25308378d320084e1a59090261a7e6bd82544df1eebd96dc87b4eb1211346645fa44d6b932960b441629077e228528f91ca7dc17051bb437408a5ae272d0950e58961846a8fc2ed0d052eca7d732d9f560ba970ca48f67387b899e76958ea6ed342a3a553ef0229e065eea4143325fbbd26967c26a228d51a3a8384062f7434973f15d1da2c010000000000000000000000000000000000000000100000000000000", + "0x4da2c41eaffa8e1a791c5d65beeefd1f4e5747352ae927817a9171156fb3da7f00000000": "0x00", + "0x4da2c41eaffa8e1a791c5d65beeefd1f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x4da2c41eaffa8e1a791c5d65beeefd1f5762b52ec4f696c1235b20491a567f8500000000": "0x00", + "0x4da2c41eaffa8e1a791c5d65beeefd1fff4a51b74593c3708682038efe5323b5": "0x00000000", + "0x50e709b04947c0cd2f04727ef76e88f64e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0x7e939ef17e229e9a29210d95cb0b607e0030d54899c05f791a62d5c6f4557659", + "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x0e000000", + "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe701887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe7065903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc402d496d20c019d22397accfc42b7635d94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b100f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc40ffdb9747f7a8bfa200ea2291eea6bdf9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc411fcf3e922de10898cb04273301735e65270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc415fbc5d9b44885a2dce0fbaa3834ba211e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c4130f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4279effc2672354478fb5e3f7e65726b668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c690f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc42ffe7c0dd7d3d8d8485c403a33fb7adb6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a22596270f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4350ea875b4c0459b3527d475e9825c99d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc44d176c4abc6d2410c43423c2714b909c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e11520f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4585bd5ccc1af6cf60c19d791bb091b750efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f761080f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc465bb58c29c9825c1f6727fe8c41650ac82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc46deaa967a94fa09152be01a253511479d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4829f3f4d6d7c991993e0c044ae97410b043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d0f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4ba2db252d1b8c64ec51b04d0f50d7d2b9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af740f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4dfc40f350cc4563ad9ddff5ad92944278eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b8689640f0080c6a47e8d030f0080c6a47e8d030000", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000001887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a0000000065903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", + "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0d00", + "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x3894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b1082c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a22596271e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c4130efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170dd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e11528eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b8689649492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af745270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x0080c6a47e8d03000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000001887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a0000000065903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x0e000000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169031887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e1690365903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000001887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a0000000065903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x0f0080c6a47e8d030f0080c6a47e8d0300", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade981887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade9865903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca98c2640cda6c0d801194a8a61c699224": "0xc8000000", + "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0x0000db02edbc31000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", + "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", + "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00a89c13460200000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x00", + "0x5f3e4907f716ac89b6347d15ececedcafab86d26e629e39b4903db94786fac74": "0xffffffffffffffff0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x63f78c98723ddc9073523ef3beefda0c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6835e433a91d195a7e84cba463249e274e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6a0da05ca59913bc38a8630590f2627c2a351b6a99a5b21324516e668bb86a57": "0x00", + "0x6a0da05ca59913bc38a8630590f2627c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6ac983d82528bf1595ab26438ae5b2cf4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10000182c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c0194c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10016ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a225962761c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b151887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a22596270182c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c011e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c41361c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413016ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627010efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f7610861c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108011e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c41301043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d010efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f7610801d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b1565903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d01043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d0168728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c6961c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c6901d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d01d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c0168728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c690132eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e115261c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e115201d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c018eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b86896461c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b8689640132eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152019492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f018eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964019037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af7461c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74019492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f015270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c61c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c019037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af740061c091f7b7cb03000080c6a47e8d0300", + "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x0e000000", + "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d80863c87cd8a129fa61c091f7b7cb0300": "0x0194c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10015270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00000000000000000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", + "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", + "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x00000000000000000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0700", + "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", + "0x8671567f6bbc0021f6f23105f33002a84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x94eadf0156a8ad5156507773d0471e4a16973e1142f5bd30d9464076794007db": "0x00", + "0x94eadf0156a8ad5156507773d0471e4a4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x94eadf0156a8ad5156507773d0471e4a9ce0310edffce7a01a96c2039f92dd10": "0x01000000", + "0x94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339": "0x00", + "0x9c5d795d0297be56027a4b2464e333974e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x9c5d795d0297be56027a4b2464e33397f43d6436dec51f09c3b71287a8fc9d48": "0x00000000000000000000000000000000", + "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa37f719efab16103103a0c8c2c784ce14e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xa8c65209d47ee80f56b0011e8fd91f504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xb341e3a63e58a188839b242d17f8c9f82586833f834350b4d435d5fd269ecc8b": "0x380a000000030000000400000002000000070000000b000000050000000c0000000000000009000000060000000d0000000100000008000000", + "0xb341e3a63e58a188839b242d17f8c9f84e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xb341e3a63e58a188839b242d17f8c9f87a50c904b368210021127f9238883a6e": "0x38ea9400f05e7fb75a3f7a92febbf58e5a3060dd06132ed6d5d68a3d75ec452826ad90a2c3fa2c756f974628dd279adb87935f7ea509856276e3b86f759b22451c303094c583e253794c9db14803585baaa74472f4ecba846defefc8aecfb6214ac45a6d0878f808b1baaa85dcfb4e930ae06e3205bb38855527aee6f259e3327b2253ee3c02d89582602ca5b0570cfc01dc82cc8d1b9d2071eb5db6318749124b02683131f96baec9121383995904c49a02ce2c2451f8038291e5db2dce66663e0a988fb965b156a07debf072fd9d34a9c7c0fc0e0ff5bd63ef766afb76e2b32890032c39c968f486f77f8764301a8479206f063d49eeb9f6d499333e2a1be045161d0af40e6efc165c17d0189bd2d770bdfa0a9b8393cb89113f473a2e948c680ce84accce1ced0de223aa72f760f1b3d13ddfd267938cd63e25308378d320084e1a59090261a7e6bd82544df1eebd96dc87b4eb1211346645fa44d6b932960b441629077e228528f91ca7dc17051bb437408a5ae272d0950e58961846a8fc2ed0d052eca7d732d9f560ba970ca48f67387b899e76958ea6ed342a3a553ef0229e065eea4143325fbbd26967c26a228d51a3a8384062f7434973f15d1da2c010", + "0xb341e3a63e58a188839b242d17f8c9f89d1fb17def62216d598940d64654f69e": "0x0000000000", + "0xb341e3a63e58a188839b242d17f8c9f8b5cab3380174032968897a4c3ce57c0a": "0x00000000", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc602d496d20c019d22397accfc42b7635d94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc60ffdb9747f7a8bfa200ea2291eea6bdf9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc611fcf3e922de10898cb04273301735e65270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc615fbc5d9b44885a2dce0fbaa3834ba211e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6279effc2672354478fb5e3f7e65726b668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc62ffe7c0dd7d3d8d8485c403a33fb7adb6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6350ea875b4c0459b3527d475e9825c99d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc64d176c4abc6d2410c43423c2714b909c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6585bd5ccc1af6cf60c19d791bb091b750efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc665bb58c29c9825c1f6727fe8c41650ac82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc66deaa967a94fa09152be01a253511479d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6829f3f4d6d7c991993e0c044ae97410b043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6ba2db252d1b8c64ec51b04d0f50d7d2b9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc6dfc40f350cc4563ad9ddff5ad92944278eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0x047374616b696e67200080c6a47e8d0300000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00e4be87ea9ef50f0000000000000000", + "0xca32a41f4b3ed515863dc0a38697f84e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcd710b30bd2eab0352ddcc26417aa1944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcd710b30bd2eab0352ddcc26417aa1949f4993f016e2d2f8e5f43be7bb259486": "0x00", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30afd9ad680e7563894c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10": "0x6e60f1e253735fb113c183fa603f591e4456435171f387c0849001b428b5ccb118bd0f67d77f04f1a92400421813d8927fad109b40a8689254a5f0c8b346857cd28145a7cde195a4c834276730d30f074b212a150e770931ee9470e853e7d22402683131f96baec9121383995904c49a02ce2c2451f8038291e5db2dce66663e92ac14f8ad1811cc83861afadf12f3191cca1391f1f3af705977faa2fa2bf46a629f9fd0dd7279c7af7470472d1208a13e33239b484974d47cffce4ad47856440333022898140662dfea847e3cbfe5e989845ac6766e83472f8b0c650d85e77bae", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30e8cb253c2fa33f382c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c": "0x4c669b04865e9acaf7b72bdfcb0099d70d9ec63c8c2d6b8cb0552815d7b50a0afacb2f987caac6c1290a9784b1efdba78343d39aed805addb12945efbe444000ca3c2703db1633a27eff681d979967988c3a6752c669fd41f1abde10f3b054462253ee3c02d89582602ca5b0570cfc01dc82cc8d1b9d2071eb5db6318749124bf0e6c42698fffc28f9fc769fddcdf165af54c171cde43690cc8f73c853de1f0426e2fc857945d01520797a75388c58e710c9fefedd28387af70880f1682be41e02182879ec92e811e2a8cc117f3cde1f61d3cba0093134cfb1ed17a4ef74915d4a", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb31887a9d70c3dff6e6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627": "0x0509f9caf32fda5584343c473b386c433acb99fd9400724b8cf3c618d840133fb2efe7e70daf44b3466c63ccbf4487f42c6a9f6fbb7050b849691e36ce92e347bef47a9e4b47ed57461e1d28cac7da327a52ebcd64d74080d31deb3ac7a7645e4e1a59090261a7e6bd82544df1eebd96dc87b4eb1211346645fa44d6b932960be4ca45c68b0248885d190d22068c6628ee2f00d9fa0706d5a5c1c8456369f03eaa3955187f755708cd6a8104314b962ff5043e36efa3ec5d84df40c58b44222102f4f4d0eccb899bf2d611b56e0afec7c740efba404f8d0e82a545f988c45316c4", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3198703ff0791c2de1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413": "0x5fafb6219eb8d463bec0370b2aab69f45fc780959fc2eddbc7703760aa34202274dacbca0cdb5099afef67e622c147614198e669931cebc605629da332632473b414aa148096a92a1831309f758f944725653363ccbaeb21817b7df5784b8d46c45a6d0878f808b1baaa85dcfb4e930ae06e3205bb38855527aee6f259e3327b2e2f75472708a497d1743f52b04edf26c250d9e6d220f3bae3d176f02f8e586c2ada042fb4bbfd9b6d8c48293ffc4a7722632c843a67e608554c41d06aabc41303a0af06322d100056125fac1df39d161089b07ae279505aae8731c4d110a54ad7", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3260868bcb77241070efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108": "0x83f43ee3e4521b55de0fe830847fda88a6b017b87979af1a41b180c39da1e4b05440add43e5388a81aef665c9086d386c0be0ce75e4f8a4a3d8168e976ea821f98aab6f52520022d011a6eba2dca1c6327edbbcd753c170dcf0e9118b5f0f25bd0d052eca7d732d9f560ba970ca48f67387b899e76958ea6ed342a3a553ef0223e547f5cc3455a61d0404d7296ceec7375cbe20322109d118c5e725b1a5cbf04cce3ec06252cf7cdad47fe1265047a9bbddb9059ee4bdc6dec83b67249b4a93403f045328f504c13dac9ddd9b1186098aee7c46cb8d55289dbbf2433bab7a26239", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3572aae990801cce1043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d": "0xc8caee6f6eddc41c6cc55e554343392cbc13d2a8a57b97f6f85fc965bdd20ce8b07d600e3487e2712dcc3879c7b17c9b29cd2243b45f0d9343c591b89cf82a650edf2a41cb81178704560b02c35f5e01a5a97a568ebc10c025ade18b6ab2fa1d161d0af40e6efc165c17d0189bd2d770bdfa0a9b8393cb89113f473a2e948c68def964eed9a73f8a6610f1a0373378dca6f277eb7787869ed5841893105ad930f89c97bf5b2c07c05c84eebce4ffc7b28766946c03741fd1a71fdae0942e87680325fc2095902f5fe394f244bce38b0dc3d631cbc05f0b64d5620a71bbf2514f0f", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb365903e26e594bcd6d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d": "0x64adb43a7628139f6c02100f6a5465dbd33422418426c572b12547c5a665008c1ab03b1b3277edfedd24ef3d3359b449b64bd95ed82a04e7f9fbaab7b71dc0152cd12c731d91441f0114b08d314cd3f9a9f7fd0240d467fe54adefbee4d90762441629077e228528f91ca7dc17051bb437408a5ae272d0950e58961846a8fc2ec22ce14ba0e59aa974d4a05c9208ba5ae18674c6a23c9998d91e7d1ebea7e06b9a86227e204a2d003399c2a3b50c2c869c4380c195a014a02f6d2e7048941237029a1eb2e31dcaf468dbb516f9b620fdd7c3f090d58a88e02b51b25255b2182dd1", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3a8a26ecf38bdcfc668728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69": "0x6e309dfa4c8de814cad140b8612a9e41bfba244f9ab1468e1b5d9b3cc1f5e565c44b3e8efe854419ccd5801a82ada22d39cfccdbcece382304cdfeac81ebe402a8a03d86e6c0dbe180cadfc7994121f462b28f7a8cb1be7e0e354147624be7340a988fb965b156a07debf072fd9d34a9c7c0fc0e0ff5bd63ef766afb76e2b3289ef622d2467ed115fa0c6c86303e1ef6a08a0609c97e616aa69b026a6d3f2663729053f28155071474b4686323db5f7a318cb3f088b76660cc8ff5e3e11ec32e030e901c390fa37d101ff25d70594acd2df67b4493ee77a73684f25d39313536d7", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3ab8d3e1fa99ea7e2d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c": "0xc8de3a01502422b59dfa601c9c3a04a98d2bfbd79dd0810d1d1250feab4241ee6248d87bd2a640ffe26d6b831735887c24e2076a3a0f3a74f7ae7568c27604087c03ca47a3201455f8f89defda4aa909cb1d25dd9ddb7fd62a940606f79b566390032c39c968f486f77f8764301a8479206f063d49eeb9f6d499333e2a1be045165f3b255dc17054e6d4447c4005f689eb5ed2f99fe201f4ff799bf088495850047277f22b9ef92a8b99618f4c86c2412f0e3b08a4f965f842775672043d1e2503586dafcdab3d4647d4dc68732a9cab8aa34c00c5edd04e65d9dd44c2a1fd21e2", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d12736684524574c32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152": "0x8270a62b61639ee56113834aecec01de6cda91413a5111b89f74d6585da34f5058108e1651614afc6a535c426fc013945e93533faa33819fe4e69423fe32330274bd654c470ed9b94972c1f997593fab7bdd4d6b85e3cf49401265668142584ead90a2c3fa2c756f974628dd279adb87935f7ea509856276e3b86f759b22451cc083b0d0bd7d6ffd14562b4c9e28738b087ccc32262170c633c18359ff84877992cb05c48fc643f057626c669604675c5ad5a836266f260ae7030c6fdc17a54303aec8e80ea0375f8669d6e55d7abb6a3117678d7bb851a1bd100a01e52a4fed90", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d30ebbc69dfd05038eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964": "0xe41426f7465c13c48335771c5450bf61c50a9cf28b9274f170c7421eea7974f168a9ec74fa35b3425eaf503dd36294ba8e758e7b8084c4d6bfd547f8c6b5827460fcc9d094d21fe17cfb7426501f50cb3d75c4c9395a3140e0f255443f660d3b9e065eea4143325fbbd26967c26a228d51a3a8384062f7434973f15d1da2c01068f7a83678a377701b46a5e6a4637e99868186ff4835fc0e3914cc56a76a360164ffc83f4f86cc595e607a00b977eeb6641e02a4e6e556c24ab163aecd7d146c03e843f200e30bc5b951c73a96d968db1c0cd05e357d910fce159fc59c40e9d6e2", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3deca1f47c95ad9e99492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f": "0xc9a68a26e9aa37ba6334f1a20275e3be7d3a9d4aa988627eadac8ea0d0a2dfbfae240842b74e5dd778972e451558134f434c7a1d8a52bc70519f38054e24553306bd8fd81e50cda2bd67bf6893d921d1aae5cb08409ae43e0bff4d54e1830e58ea9400f05e7fb75a3f7a92febbf58e5a3060dd06132ed6d5d68a3d75ec452826bed3b452f869d187be58a4ba98588611084283810728fa75981e792beaec4151763d070989ead31f265b40cc7a0cd29d47799b766d6a7f084e44c82baedfc01e029338ece1c6bc6439dc4d16bfe33f28c9f0af31626bb8142849742b7a624f6807", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3e7e316712ac574569037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74": "0xace46e899b90e75199549d8fa2ae7097e896ab3c845217e3155f99b6ffb888037c8348ec95a0faad6a638ef74864761028c53221bde07e9ff7c81a3f427abf3f30c40adee5476157ef3c2a26e10cab95ec7d54b62dd220738f5a474d5f86874e0ce84accce1ced0de223aa72f760f1b3d13ddfd267938cd63e25308378d32008eed33645cda7812cd343bbaef9131b2794812f2fd37701ccb6cddf9c1e293d387aeb767131602e6612e607a9eb8e26b4ce4fa4765593d032bc923ce8acadda420203d51bba2124f480e3507eb1764fc3019ac7abae8ee215683a285078bda7f51d", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3eec11b5fca7cddef5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c": "0x622c382187c0b2c61ecfb17443294d11a9d2ab770ae6f1fb49184a43906d59fe50412bd7d3f1075e8f8e3b682d05ea20b391c287d8c849a0e49a78f568553e6994848b8cf2cbc9e6fd72db8d80676591b5be4d1ec68972ada48cf6fd01228712303094c583e253794c9db14803585baaa74472f4ecba846defefc8aecfb6214ab400c4164d016282b202c1d42d9dc8ede28cbe4b751d463bab5f23fef42b295a8c15606f4c121376097ff0e96c2a33ea7b024d812b42fe2c741c8b8cee17e63d03d46c454f9b620603feef8c3a2a5d7098205b8566500fda0fa0b456d6ded54538", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500ad445de53dc0cfe6175646980729053f28155071474b4686323db5f7a318cb3f088b76660cc8ff5e3e11ec32e": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19501153f927dde4f0796173676e80f0e6c42698fffc28f9fc769fddcdf165af54c171cde43690cc8f73c853de1f04": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195012ba4e8c9e90550f6173676e80b400c4164d016282b202c1d42d9dc8ede28cbe4b751d463bab5f23fef42b295a": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195016108068be661c046772616e806e309dfa4c8de814cad140b8612a9e41bfba244f9ab1468e1b5d9b3cc1f5e565": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195016d5d491326e20327061726180ea9400f05e7fb75a3f7a92febbf58e5a3060dd06132ed6d5d68a3d75ec452826": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950172bf7b1d4fd362d62616265806248d87bd2a640ffe26d6b831735887c24e2076a3a0f3a74f7ae7568c2760408": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19501aeb8a0909a0259a696d6f6e8098aab6f52520022d011a6eba2dca1c6327edbbcd753c170dcf0e9118b5f0f25b": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950206946fcbcbb55c06772616e800509f9caf32fda5584343c473b386c433acb99fd9400724b8cf3c618d840133f": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195022df7698c94798dc6261626580facb2f987caac6c1290a9784b1efdba78343d39aed805addb12945efbe444000": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19502bf84b6fe354ffa4696d6f6e80bef47a9e4b47ed57461e1d28cac7da327a52ebcd64d74080d31deb3ac7a7645e": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19502d8e55cbf27bd5fa6175646980763d070989ead31f265b40cc7a0cd29d47799b766d6a7f084e44c82baedfc01e": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195033918a92ec5a0337626162658068a9ec74fa35b3425eaf503dd36294ba8e758e7b8084c4d6bfd547f8c6b58274": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950365a4d707a6ade5270617261800a988fb965b156a07debf072fd9d34a9c7c0fc0e0ff5bd63ef766afb76e2b328": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195036942a186c91d1fb6772616e80e41426f7465c13c48335771c5450bf61c50a9cf28b9274f170c7421eea7974f1": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950377ac9e1db771a9a696d6f6e8060fcc9d094d21fe17cfb7426501f50cb3d75c4c9395a3140e0f255443f660d3b": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19503a73714b16fa1b3b62616265805440add43e5388a81aef665c9086d386c0be0ce75e4f8a4a3d8168e976ea821f": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195042faced8513a5a6162656566840325fc2095902f5fe394f244bce38b0dc3d631cbc05f0b64d5620a71bbf2514f0f": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195047d6f38d0fa588e4617564698026e2fc857945d01520797a75388c58e710c9fefedd28387af70880f1682be41e": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950486e2fc6fe1baf7d62656566840333022898140662dfea847e3cbfe5e989845ac6766e83472f8b0c650d85e77bae": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195048ac54571b914843626565668403e843f200e30bc5b951c73a96d968db1c0cd05e357d910fce159fc59c40e9d6e2": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19504d5070934a9a468e6261626580b07d600e3487e2712dcc3879c7b17c9b29cd2243b45f0d9343c591b89cf82a65": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19504f6e7e79cc9e4f1c6772616e8064adb43a7628139f6c02100f6a5465dbd33422418426c572b12547c5a665008c": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19504fbe8be23f344f7a6173676e809ef622d2467ed115fa0c6c86303e1ef6a08a0609c97e616aa69b026a6d3f2663": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505214a412d15781646173676e8068f7a83678a377701b46a5e6a4637e99868186ff4835fc0e3914cc56a76a3601": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950523f922d3cd709c261756469809a86227e204a2d003399c2a3b50c2c869c4380c195a014a02f6d2e7048941237": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195053557db7980560f461756469802ada042fb4bbfd9b6d8c48293ffc4a7722632c843a67e608554c41d06aabc413": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505bb3161fb65601ff626162658058108e1651614afc6a535c426fc013945e93533faa33819fe4e69423fe323302": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505d3dc734864e36256265656684029338ece1c6bc6439dc4d16bfe33f28c9f0af31626bb8142849742b7a624f6807": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505d9ac86ecdf920c170617261804e1a59090261a7e6bd82544df1eebd96dc87b4eb1211346645fa44d6b932960b": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195062a6c483038fd12d6265656684030e901c390fa37d101ff25d70594acd2df67b4493ee77a73684f25d39313536d7": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195064cc468ab603f8926772616e80c9a68a26e9aa37ba6334f1a20275e3be7d3a9d4aa988627eadac8ea0d0a2dfbf": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195065ccc9ce9f581b2c61756469807aeb767131602e6612e607a9eb8e26b4ce4fa4765593d032bc923ce8acadda42": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950678c21325827e7c66173676e802e2f75472708a497d1743f52b04edf26c250d9e6d220f3bae3d176f02f8e586c": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19506792a176f707220962616265801ab03b1b3277edfedd24ef3d3359b449b64bd95ed82a04e7f9fbaab7b71dc015": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195067f9bb125e8b25cf696d6f6e802cd12c731d91441f0114b08d314cd3f9a9f7fd0240d467fe54adefbee4d90762": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195068d2c0f060d8e93b6772616e806e60f1e253735fb113c183fa603f591e4456435171f387c0849001b428b5ccb1": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195069561cd534084d42696d6f6e80b414aa148096a92a1831309f758f944725653363ccbaeb21817b7df5784b8d46": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950717bb541f3dc932d6772616e80c8de3a01502422b59dfa601c9c3a04a98d2bfbd79dd0810d1d1250feab4241ee": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507180ab4d5ffe8c79706172618090032c39c968f486f77f8764301a8479206f063d49eeb9f6d499333e2a1be045": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195073b0d3d9242f219d70617261800ce84accce1ced0de223aa72f760f1b3d13ddfd267938cd63e25308378d32008": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507466b8cede2256a27061726180ad90a2c3fa2c756f974628dd279adb87935f7ea509856276e3b86f759b22451c": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950749c8bce91e643ae6772616e808270a62b61639ee56113834aecec01de6cda91413a5111b89f74d6585da34f50": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950795802224826305461756469808c15606f4c121376097ff0e96c2a33ea7b024d812b42fe2c741c8b8cee17e63d": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507e5838eaecbd9fe66175646980629f9fd0dd7279c7af7470472d1208a13e33239b484974d47cffce4ad4785644": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195085436b71cc38efc86173676e80e4ca45c68b0248885d190d22068c6628ee2f00d9fa0706d5a5c1c8456369f03e": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195087f680d5c3a7dcbf617564698064ffc83f4f86cc595e607a00b977eeb6641e02a4e6e556c24ab163aecd7d146c": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950892d429df08b8e486772616e805fafb6219eb8d463bec0370b2aab69f45fc780959fc2eddbc7703760aa342022": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19508cec3cc48ae13ec86173676e80def964eed9a73f8a6610f1a0373378dca6f277eb7787869ed5841893105ad930": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195090ddc04de598f6716772616e8083f43ee3e4521b55de0fe830847fda88a6b017b87979af1a41b180c39da1e4b0": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950961fc927b0be61a66175646980f89c97bf5b2c07c05c84eebce4ffc7b28766946c03741fd1a71fdae0942e8768": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195097b423b109a1a3e7626565668403a0af06322d100056125fac1df39d161089b07ae279505aae8731c4d110a54ad7": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509990e89dc7508f8f626565668403d46c454f9b620603feef8c3a2a5d7098205b8566500fda0fa0b456d6ded54538": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509c34aadd3112cff46772616e80c8caee6f6eddc41c6cc55e554343392cbc13d2a8a57b97f6f85fc965bdd20ce8": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509cc6d5a70d4342de706172618002683131f96baec9121383995904c49a02ce2c2451f8038291e5db2dce66663e": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509d726f19fb7e918f6173676e80c083b0d0bd7d6ffd14562b4c9e28738b087ccc32262170c633c18359ff848779": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509f08b41b3412bdf7696d6f6e8074bd654c470ed9b94972c1f997593fab7bdd4d6b85e3cf49401265668142584e": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509f796bfaa6092095696d6f6e800edf2a41cb81178704560b02c35f5e01a5a97a568ebc10c025ade18b6ab2fa1d": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a0aff76314285afe6173676e803e547f5cc3455a61d0404d7296ceec7375cbe20322109d118c5e725b1a5cbf04": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a39a50629306361270617261809e065eea4143325fbbd26967c26a228d51a3a8384062f7434973f15d1da2c010": "0x8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a69397f216a7497a6261626580ae240842b74e5dd778972e451558134f434c7a1d8a52bc70519f38054e245533": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a8670a2360fb4f7d626565668402f4f4d0eccb899bf2d611b56e0afec7c740efba404f8d0e82a545f988c45316c4": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ab8402a39318e10f70617261802253ee3c02d89582602ca5b0570cfc01dc82cc8d1b9d2071eb5db6318749124b": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950aba2e02edcf42d916175646980cce3ec06252cf7cdad47fe1265047a9bbddb9059ee4bdc6dec83b67249b4a934": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ac3745478a256f54696d6f6e8094848b8cf2cbc9e6fd72db8d80676591b5be4d1ec68972ada48cf6fd01228712": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ae71ab667a6367956173676e80bed3b452f869d187be58a4ba98588611084283810728fa75981e792beaec4151": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950aee25d4290ea4d846772616e804c669b04865e9acaf7b72bdfcb0099d70d9ec63c8c2d6b8cb0552815d7b50a0a": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b090c0a6a6d34dc97061726180441629077e228528f91ca7dc17051bb437408a5ae272d0950e58961846a8fc2e": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b40a2e291d3757e66265656684029a1eb2e31dcaf468dbb516f9b620fdd7c3f090d58a88e02b51b25255b2182dd1": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b469b5316f2df49e626162658018bd0f67d77f04f1a92400421813d8927fad109b40a8689254a5f0c8b346857c": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b6da07aad040c4596772616e80ace46e899b90e75199549d8fa2ae7097e896ab3c845217e3155f99b6ffb88803": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b9847e90f2a93d58626565668403aec8e80ea0375f8669d6e55d7abb6a3117678d7bb851a1bd100a01e52a4fed90": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bad6b6ae80666780696d6f6e8006bd8fd81e50cda2bd67bf6893d921d1aae5cb08409ae43e0bff4d54e1830e58": "0x9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bd1ab2cd6f1dc5a6696d6f6e80d28145a7cde195a4c834276730d30f074b212a150e770931ee9470e853e7d224": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bdd996f23001764e62656566840203d51bba2124f480e3507eb1764fc3019ac7abae8ee215683a285078bda7f51d": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950be81b84f18801f516173676e80165f3b255dc17054e6d4447c4005f689eb5ed2f99fe201f4ff799bf088495850": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bef85ee78e59fe8a6173676e80eed33645cda7812cd343bbaef9131b2794812f2fd37701ccb6cddf9c1e293d38": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950bfb5b848b461277c7061726180c45a6d0878f808b1baaa85dcfb4e930ae06e3205bb38855527aee6f259e3327b": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c1cabf91e699980a6261626580b2efe7e70daf44b3466c63ccbf4487f42c6a9f6fbb7050b849691e36ce92e347": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c2bef1fa94927270626565668403f045328f504c13dac9ddd9b1186098aee7c46cb8d55289dbbf2433bab7a26239": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c3b366be127c04d76772616e80622c382187c0b2c61ecfb17443294d11a9d2ab770ae6f1fb49184a43906d59fe": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c61ff1db24ad5e86626565668402182879ec92e811e2a8cc117f3cde1f61d3cba0093134cfb1ed17a4ef74915d4a": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950cb18236e84f2bcc8626565668403586dafcdab3d4647d4dc68732a9cab8aa34c00c5edd04e65d9dd44c2a1fd21e2": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d1c1264edac2bfeb7061726180303094c583e253794c9db14803585baaa74472f4ecba846defefc8aecfb6214a": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d4b8d8139a9d2a3862616265807c8348ec95a0faad6a638ef74864761028c53221bde07e9ff7c81a3f427abf3f": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d5b902094e722e906261626580c44b3e8efe854419ccd5801a82ada22d39cfccdbcece382304cdfeac81ebe402": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d74338095b2b3a5f7061726180161d0af40e6efc165c17d0189bd2d770bdfa0a9b8393cb89113f473a2e948c68": "0x043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d84b213ff11dcdc96175646980047277f22b9ef92a8b99618f4c86c2412f0e3b08a4f965f842775672043d1e25": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d912f1fd94200c50696d6f6e8030c40adee5476157ef3c2a26e10cab95ec7d54b62dd220738f5a474d5f86874e": "0x9037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950db189b9054ee003e6175646980aa3955187f755708cd6a8104314b962ff5043e36efa3ec5d84df40c58b442221": "0x6ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a2259627", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950dd45ac2fed4ab0db6173676e8092ac14f8ad1811cc83861afadf12f3191cca1391f1f3af705977faa2fa2bf46a": "0x94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950dfe86daa30500684696d6f6e80ca3c2703db1633a27eff681d979967988c3a6752c669fd41f1abde10f3b05446": "0x82c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950e326352daf693946617564698092cb05c48fc643f057626c669604675c5ad5a836266f260ae7030c6fdc17a543": "0x32eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e1152", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ec74f4b6dbe6f07e626162658074dacbca0cdb5099afef67e622c147614198e669931cebc605629da332632473": "0x1e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c413", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f10dac37e2a0f812696d6f6e80a8a03d86e6c0dbe180cadfc7994121f462b28f7a8cb1be7e0e354147624be734": "0x68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c69", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f39a56e067ee2ba9696d6f6e807c03ca47a3201455f8f89defda4aa909cb1d25dd9ddb7fd62a940606f79b5663": "0xd0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f87aebc04f6a52ae6173676e80c22ce14ba0e59aa974d4a05c9208ba5ae18674c6a23c9998d91e7d1ebea7e06b": "0xd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950fc36b10abae8cf4f626162658050412bd7d3f1075e8f8e3b682d05ea20b391c287d8c849a0e49a78f568553e69": "0x5270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950fcf43201be771ed27061726180d0d052eca7d732d9f560ba970ca48f67387b899e76958ea6ed342a3a553ef022": "0x0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f76108", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x38043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170d0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f761081e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c41332eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e11525270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c68728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c696ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a225962782c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b8689649037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af749492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20f94c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b10d0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327cd41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x38043393e76c137dfdc403a6fd9a2d6129d470d51c5a67bd40517378030c87170dc8caee6f6eddc41c6cc55e554343392cbc13d2a8a57b97f6f85fc965bdd20ce8b07d600e3487e2712dcc3879c7b17c9b29cd2243b45f0d9343c591b89cf82a650edf2a41cb81178704560b02c35f5e01a5a97a568ebc10c025ade18b6ab2fa1d161d0af40e6efc165c17d0189bd2d770bdfa0a9b8393cb89113f473a2e948c68def964eed9a73f8a6610f1a0373378dca6f277eb7787869ed5841893105ad930f89c97bf5b2c07c05c84eebce4ffc7b28766946c03741fd1a71fdae0942e87680325fc2095902f5fe394f244bce38b0dc3d631cbc05f0b64d5620a71bbf2514f0f0efe248e3ddcfcb4f29675b70fc0a8e2db66b65381c45d299427b60d05f7610883f43ee3e4521b55de0fe830847fda88a6b017b87979af1a41b180c39da1e4b05440add43e5388a81aef665c9086d386c0be0ce75e4f8a4a3d8168e976ea821f98aab6f52520022d011a6eba2dca1c6327edbbcd753c170dcf0e9118b5f0f25bd0d052eca7d732d9f560ba970ca48f67387b899e76958ea6ed342a3a553ef0223e547f5cc3455a61d0404d7296ceec7375cbe20322109d118c5e725b1a5cbf04cce3ec06252cf7cdad47fe1265047a9bbddb9059ee4bdc6dec83b67249b4a93403f045328f504c13dac9ddd9b1186098aee7c46cb8d55289dbbf2433bab7a262391e30aa51ad68b8918d2c46e914986818c111bee03582610cbc9fb73fe0e4c4135fafb6219eb8d463bec0370b2aab69f45fc780959fc2eddbc7703760aa34202274dacbca0cdb5099afef67e622c147614198e669931cebc605629da332632473b414aa148096a92a1831309f758f944725653363ccbaeb21817b7df5784b8d46c45a6d0878f808b1baaa85dcfb4e930ae06e3205bb38855527aee6f259e3327b2e2f75472708a497d1743f52b04edf26c250d9e6d220f3bae3d176f02f8e586c2ada042fb4bbfd9b6d8c48293ffc4a7722632c843a67e608554c41d06aabc41303a0af06322d100056125fac1df39d161089b07ae279505aae8731c4d110a54ad732eebacd223f4aef33d98a667a68f9e371f40384257c6d31030952b9d94e11528270a62b61639ee56113834aecec01de6cda91413a5111b89f74d6585da34f5058108e1651614afc6a535c426fc013945e93533faa33819fe4e69423fe32330274bd654c470ed9b94972c1f997593fab7bdd4d6b85e3cf49401265668142584ead90a2c3fa2c756f974628dd279adb87935f7ea509856276e3b86f759b22451cc083b0d0bd7d6ffd14562b4c9e28738b087ccc32262170c633c18359ff84877992cb05c48fc643f057626c669604675c5ad5a836266f260ae7030c6fdc17a54303aec8e80ea0375f8669d6e55d7abb6a3117678d7bb851a1bd100a01e52a4fed905270ec35ba01254d8bff046a1a58f16d3ae615c235efd6e99a35f233b2d9df2c622c382187c0b2c61ecfb17443294d11a9d2ab770ae6f1fb49184a43906d59fe50412bd7d3f1075e8f8e3b682d05ea20b391c287d8c849a0e49a78f568553e6994848b8cf2cbc9e6fd72db8d80676591b5be4d1ec68972ada48cf6fd01228712303094c583e253794c9db14803585baaa74472f4ecba846defefc8aecfb6214ab400c4164d016282b202c1d42d9dc8ede28cbe4b751d463bab5f23fef42b295a8c15606f4c121376097ff0e96c2a33ea7b024d812b42fe2c741c8b8cee17e63d03d46c454f9b620603feef8c3a2a5d7098205b8566500fda0fa0b456d6ded5453868728d12a90fb1a9f5b0f0b2814d730401d314964113554e66ff19e7067d7c696e309dfa4c8de814cad140b8612a9e41bfba244f9ab1468e1b5d9b3cc1f5e565c44b3e8efe854419ccd5801a82ada22d39cfccdbcece382304cdfeac81ebe402a8a03d86e6c0dbe180cadfc7994121f462b28f7a8cb1be7e0e354147624be7340a988fb965b156a07debf072fd9d34a9c7c0fc0e0ff5bd63ef766afb76e2b3289ef622d2467ed115fa0c6c86303e1ef6a08a0609c97e616aa69b026a6d3f2663729053f28155071474b4686323db5f7a318cb3f088b76660cc8ff5e3e11ec32e030e901c390fa37d101ff25d70594acd2df67b4493ee77a73684f25d39313536d76ed774481ff68097867271bc8ecaeee3500817ccefdfda74ceeafd32a22596270509f9caf32fda5584343c473b386c433acb99fd9400724b8cf3c618d840133fb2efe7e70daf44b3466c63ccbf4487f42c6a9f6fbb7050b849691e36ce92e347bef47a9e4b47ed57461e1d28cac7da327a52ebcd64d74080d31deb3ac7a7645e4e1a59090261a7e6bd82544df1eebd96dc87b4eb1211346645fa44d6b932960be4ca45c68b0248885d190d22068c6628ee2f00d9fa0706d5a5c1c8456369f03eaa3955187f755708cd6a8104314b962ff5043e36efa3ec5d84df40c58b44222102f4f4d0eccb899bf2d611b56e0afec7c740efba404f8d0e82a545f988c45316c482c3105dbd4bb206428d8a8b7ea1f19965a0668dd583b06c3b75daa181fe654c4c669b04865e9acaf7b72bdfcb0099d70d9ec63c8c2d6b8cb0552815d7b50a0afacb2f987caac6c1290a9784b1efdba78343d39aed805addb12945efbe444000ca3c2703db1633a27eff681d979967988c3a6752c669fd41f1abde10f3b054462253ee3c02d89582602ca5b0570cfc01dc82cc8d1b9d2071eb5db6318749124bf0e6c42698fffc28f9fc769fddcdf165af54c171cde43690cc8f73c853de1f0426e2fc857945d01520797a75388c58e710c9fefedd28387af70880f1682be41e02182879ec92e811e2a8cc117f3cde1f61d3cba0093134cfb1ed17a4ef74915d4a8eef6710734f5d1e7d2eb303fa8f04e9bef65fb680647b24624723f95b868964e41426f7465c13c48335771c5450bf61c50a9cf28b9274f170c7421eea7974f168a9ec74fa35b3425eaf503dd36294ba8e758e7b8084c4d6bfd547f8c6b5827460fcc9d094d21fe17cfb7426501f50cb3d75c4c9395a3140e0f255443f660d3b9e065eea4143325fbbd26967c26a228d51a3a8384062f7434973f15d1da2c01068f7a83678a377701b46a5e6a4637e99868186ff4835fc0e3914cc56a76a360164ffc83f4f86cc595e607a00b977eeb6641e02a4e6e556c24ab163aecd7d146c03e843f200e30bc5b951c73a96d968db1c0cd05e357d910fce159fc59c40e9d6e29037d1020ed699c2f538d3ffcf0eb98087ee11ca4bd07bfddb0d68633806af74ace46e899b90e75199549d8fa2ae7097e896ab3c845217e3155f99b6ffb888037c8348ec95a0faad6a638ef74864761028c53221bde07e9ff7c81a3f427abf3f30c40adee5476157ef3c2a26e10cab95ec7d54b62dd220738f5a474d5f86874e0ce84accce1ced0de223aa72f760f1b3d13ddfd267938cd63e25308378d32008eed33645cda7812cd343bbaef9131b2794812f2fd37701ccb6cddf9c1e293d387aeb767131602e6612e607a9eb8e26b4ce4fa4765593d032bc923ce8acadda420203d51bba2124f480e3507eb1764fc3019ac7abae8ee215683a285078bda7f51d9492b8c38442c79061bdbb8d38dcd28138938a7fd476edf89ecdec06a5a9d20fc9a68a26e9aa37ba6334f1a20275e3be7d3a9d4aa988627eadac8ea0d0a2dfbfae240842b74e5dd778972e451558134f434c7a1d8a52bc70519f38054e24553306bd8fd81e50cda2bd67bf6893d921d1aae5cb08409ae43e0bff4d54e1830e58ea9400f05e7fb75a3f7a92febbf58e5a3060dd06132ed6d5d68a3d75ec452826bed3b452f869d187be58a4ba98588611084283810728fa75981e792beaec4151763d070989ead31f265b40cc7a0cd29d47799b766d6a7f084e44c82baedfc01e029338ece1c6bc6439dc4d16bfe33f28c9f0af31626bb8142849742b7a624f680794c4156ed6a101ae478a3de3ba70a05fce8a3d67be6fb85f33bfcf2777ab6b106e60f1e253735fb113c183fa603f591e4456435171f387c0849001b428b5ccb118bd0f67d77f04f1a92400421813d8927fad109b40a8689254a5f0c8b346857cd28145a7cde195a4c834276730d30f074b212a150e770931ee9470e853e7d22402683131f96baec9121383995904c49a02ce2c2451f8038291e5db2dce66663e92ac14f8ad1811cc83861afadf12f3191cca1391f1f3af705977faa2fa2bf46a629f9fd0dd7279c7af7470472d1208a13e33239b484974d47cffce4ad47856440333022898140662dfea847e3cbfe5e989845ac6766e83472f8b0c650d85e77baed0b4896a63b672a7a74f5234bf9ec8567ff3c5bb8f93795e15e2c498b48d327cc8de3a01502422b59dfa601c9c3a04a98d2bfbd79dd0810d1d1250feab4241ee6248d87bd2a640ffe26d6b831735887c24e2076a3a0f3a74f7ae7568c27604087c03ca47a3201455f8f89defda4aa909cb1d25dd9ddb7fd62a940606f79b566390032c39c968f486f77f8764301a8479206f063d49eeb9f6d499333e2a1be045165f3b255dc17054e6d4447c4005f689eb5ed2f99fe201f4ff799bf088495850047277f22b9ef92a8b99618f4c86c2412f0e3b08a4f965f842775672043d1e2503586dafcdab3d4647d4dc68732a9cab8aa34c00c5edd04e65d9dd44c2a1fd21e2d41a8a9678862ebe9d1a1d59cac1c8430ef31e282f9cb391cf6f2b4d9ce2fd3d64adb43a7628139f6c02100f6a5465dbd33422418426c572b12547c5a665008c1ab03b1b3277edfedd24ef3d3359b449b64bd95ed82a04e7f9fbaab7b71dc0152cd12c731d91441f0114b08d314cd3f9a9f7fd0240d467fe54adefbee4d90762441629077e228528f91ca7dc17051bb437408a5ae272d0950e58961846a8fc2ec22ce14ba0e59aa974d4a05c9208ba5ae18674c6a23c9998d91e7d1ebea7e06b9a86227e204a2d003399c2a3b50c2c869c4380c195a014a02f6d2e7048941237029a1eb2e31dcaf468dbb516f9b620fdd7c3f090d58a88e02b51b25255b2182dd1", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xedfb05b766f199ce00df85317e33050e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/polkadot/node/service/chain-specs/polkadot.json b/polkadot/node/service/chain-specs/polkadot.json index 533492088161b3fb7e3862b706ef22716b30ee71..c235cdd09d8b6eee517909052556a02427495d09 100644 --- a/polkadot/node/service/chain-specs/polkadot.json +++ b/polkadot/node/service/chain-specs/polkadot.json @@ -17,8 +17,8 @@ "/dns/boot-node.helikon.io/tcp/7072/wss/p2p/12D3KooWS9ZcvRxyzrSf6p63QfTCWs12nLoNKhGux865crgxVA4H", "/dns/polkadot.bootnode.amforc.com/tcp/30333/p2p/12D3KooWAsuCEVCzUVUrtib8W82Yne3jgVGhQZN3hizko5FTnDg3", "/dns/polkadot.bootnode.amforc.com/tcp/30334/wss/p2p/12D3KooWAsuCEVCzUVUrtib8W82Yne3jgVGhQZN3hizko5FTnDg3", - "/dns/polkadot-bootnode.polkadotters.com/tcp/30333/p2p/12D3KooWPAVUgBaBk6n8SztLrMk8ESByncbAfRKUdxY1nygb9zG3", - "/dns/polkadot-bootnode.polkadotters.com/tcp/30334/wss/p2p/12D3KooWPAVUgBaBk6n8SztLrMk8ESByncbAfRKUdxY1nygb9zG3", + "/dns/polkadot.bootnodes.polkadotters.com/tcp/30314/p2p/12D3KooWPAVUgBaBk6n8SztLrMk8ESByncbAfRKUdxY1nygb9zG3", + "/dns/polkadot.bootnodes.polkadotters.com/tcp/30316/wss/p2p/12D3KooWPAVUgBaBk6n8SztLrMk8ESByncbAfRKUdxY1nygb9zG3", "/dns/boot-cr.gatotech.network/tcp/33100/p2p/12D3KooWK4E16jKk9nRhvC4RfrDVgcZzExg8Q3Q2G7ABUUitks1w", "/dns/boot-cr.gatotech.network/tcp/35100/wss/p2p/12D3KooWK4E16jKk9nRhvC4RfrDVgcZzExg8Q3Q2G7ABUUitks1w", "/dns/boot-polkadot.metaspan.io/tcp/13012/p2p/12D3KooWRjHFApinuqSBjoaDjQHvxwubQSpEVy5hrgC9Smvh92WF", @@ -34,7 +34,9 @@ "/dns/dot-bootnode.stakeworld.io/tcp/30311/ws/p2p/12D3KooWAb5MyC1UJiEQJk4Hg4B2Vi3AJdqSUhTGYUqSnEqCFMFg", "/dns/dot-bootnode.stakeworld.io/tcp/30312/wss/p2p/12D3KooWAb5MyC1UJiEQJk4Hg4B2Vi3AJdqSUhTGYUqSnEqCFMFg", "/dns/dot14.rotko.net/tcp/35214/wss/p2p/12D3KooWPyEvPEXghnMC67Gff6PuZiSvfx3fmziKiPZcGStZ5xff", - "/dns/dot14.rotko.net/tcp/33214/p2p/12D3KooWPyEvPEXghnMC67Gff6PuZiSvfx3fmziKiPZcGStZ5xff" + "/dns/dot14.rotko.net/tcp/33214/p2p/12D3KooWPyEvPEXghnMC67Gff6PuZiSvfx3fmziKiPZcGStZ5xff", + "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30333/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", + "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/rococo.json b/polkadot/node/service/chain-specs/rococo.json index 2648063641c919108e4478f53ff8bd321ff7e64e..e16962981e19af5d3c66af5a4157458b2faabf60 100644 --- a/polkadot/node/service/chain-specs/rococo.json +++ b/polkadot/node/service/chain-specs/rococo.json @@ -5,28 +5,10 @@ "bootNodes": [ "/dns/rococo-bootnode-0.polkadot.io/tcp/30333/p2p/12D3KooWGikJMBmRiG5ofCqn8aijCijgfmZR5H9f53yUF3srm6Nm", "/dns/rococo-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWLDfH9mHRCidrd5NfQjp7rRMUcJSEUwSvEKyu7xU2cG3d", - "/dns/rococo-bootnode-2.polkadot.io/tcp/30333/p2p/12D3KooWSikgbrcWjVgSed7r1uXk4TeAieDnHKtrPDVZBu5XkQha", - "/dns/rococo-bootnode-3.polkadot.io/tcp/30333/p2p/12D3KooWPeKuW1BBPv4pNr8xqEv7jqy7rQnS3oq9U7xTCvj9qt2k", - "/dns/rococo-bootnode-4.polkadot.io/tcp/30333/p2p/12D3KooWNy7K8TNaP2Whcp3tsjBVUg2HcKMUvAArsimjvd1g31w4", - "/dns/rococo-bootnode-5.polkadot.io/tcp/30333/p2p/12D3KooWAVV9DZfvJp2brvs5zcQDTBFxNmEFJKy2dsvezWL4Bmy8", - "/dns/rococo-bootnode-6.polkadot.io/tcp/30333/p2p/12D3KooWM3hvXvaShyp7drQCavFHuwobkYdnCp2uHU5iRRAQwsw2", - "/dns/rococo-bootnode-7.polkadot.io/tcp/30333/p2p/12D3KooWSbGtxfWCwn1tdmfZYESbmxzbTG2LKwKUrioDaZBcdMY4", "/dns/rococo-bootnode-0.polkadot.io/tcp/30334/ws/p2p/12D3KooWGikJMBmRiG5ofCqn8aijCijgfmZR5H9f53yUF3srm6Nm", "/dns/rococo-bootnode-1.polkadot.io/tcp/30334/ws/p2p/12D3KooWLDfH9mHRCidrd5NfQjp7rRMUcJSEUwSvEKyu7xU2cG3d", - "/dns/rococo-bootnode-2.polkadot.io/tcp/30334/ws/p2p/12D3KooWSikgbrcWjVgSed7r1uXk4TeAieDnHKtrPDVZBu5XkQha", - "/dns/rococo-bootnode-3.polkadot.io/tcp/30334/ws/p2p/12D3KooWPeKuW1BBPv4pNr8xqEv7jqy7rQnS3oq9U7xTCvj9qt2k", - "/dns/rococo-bootnode-4.polkadot.io/tcp/30334/ws/p2p/12D3KooWNy7K8TNaP2Whcp3tsjBVUg2HcKMUvAArsimjvd1g31w4", - "/dns/rococo-bootnode-5.polkadot.io/tcp/30334/ws/p2p/12D3KooWAVV9DZfvJp2brvs5zcQDTBFxNmEFJKy2dsvezWL4Bmy8", - "/dns/rococo-bootnode-6.polkadot.io/tcp/30334/ws/p2p/12D3KooWM3hvXvaShyp7drQCavFHuwobkYdnCp2uHU5iRRAQwsw2", - "/dns/rococo-bootnode-7.polkadot.io/tcp/30334/ws/p2p/12D3KooWSbGtxfWCwn1tdmfZYESbmxzbTG2LKwKUrioDaZBcdMY4", "/dns/rococo-bootnode-0.polkadot.io/tcp/443/wss/p2p/12D3KooWGikJMBmRiG5ofCqn8aijCijgfmZR5H9f53yUF3srm6Nm", - "/dns/rococo-bootnode-1.polkadot.io/tcp/443/wss/p2p/12D3KooWLDfH9mHRCidrd5NfQjp7rRMUcJSEUwSvEKyu7xU2cG3d", - "/dns/rococo-bootnode-2.polkadot.io/tcp/443/wss/p2p/12D3KooWSikgbrcWjVgSed7r1uXk4TeAieDnHKtrPDVZBu5XkQha", - "/dns/rococo-bootnode-3.polkadot.io/tcp/443/wss/p2p/12D3KooWPeKuW1BBPv4pNr8xqEv7jqy7rQnS3oq9U7xTCvj9qt2k", - "/dns/rococo-bootnode-4.polkadot.io/tcp/443/wss/p2p/12D3KooWNy7K8TNaP2Whcp3tsjBVUg2HcKMUvAArsimjvd1g31w4", - "/dns/rococo-bootnode-5.polkadot.io/tcp/443/wss/p2p/12D3KooWAVV9DZfvJp2brvs5zcQDTBFxNmEFJKy2dsvezWL4Bmy8", - "/dns/rococo-bootnode-6.polkadot.io/tcp/443/wss/p2p/12D3KooWM3hvXvaShyp7drQCavFHuwobkYdnCp2uHU5iRRAQwsw2", - "/dns/rococo-bootnode-7.polkadot.io/tcp/443/wss/p2p/12D3KooWSbGtxfWCwn1tdmfZYESbmxzbTG2LKwKUrioDaZBcdMY4" + "/dns/rococo-bootnode-1.polkadot.io/tcp/443/wss/p2p/12D3KooWLDfH9mHRCidrd5NfQjp7rRMUcJSEUwSvEKyu7xU2cG3d" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/westend.json b/polkadot/node/service/chain-specs/westend.json index fd1f4550127fc91e743d59e4430ad313713ed445..775f3e72ac75578a0f0166cba96531244af760c9 100644 --- a/polkadot/node/service/chain-specs/westend.json +++ b/polkadot/node/service/chain-specs/westend.json @@ -8,20 +8,14 @@ "/dns/westend-bootnode-1.polkadot.io/tcp/30333/p2p/12D3KooWPVPzs42GvRBShdUMtFsk4SvnByrSdWqb6aeAAHvLMSLS", "/dns/westend-bootnode-1.polkadot.io/tcp/30334/ws/p2p/12D3KooWPVPzs42GvRBShdUMtFsk4SvnByrSdWqb6aeAAHvLMSLS", "/dns/westend-bootnode-1.polkadot.io/tcp/443/wss/p2p/12D3KooWPVPzs42GvRBShdUMtFsk4SvnByrSdWqb6aeAAHvLMSLS", - "/dns/westend-bootnode-2.polkadot.io/tcp/30333/p2p/12D3KooWByVpK92hMi9CzTjyFg9cPHDU5ariTM3EPMq9vdh5S5Po", - "/dns/westend-bootnode-2.polkadot.io/tcp/30334/ws/p2p/12D3KooWByVpK92hMi9CzTjyFg9cPHDU5ariTM3EPMq9vdh5S5Po", - "/dns/westend-bootnode-2.polkadot.io/tcp/443/wss/p2p/12D3KooWByVpK92hMi9CzTjyFg9cPHDU5ariTM3EPMq9vdh5S5Po", - "/dns/westend-bootnode-3.polkadot.io/tcp/30333/p2p/12D3KooWGi1tCpKXLMYED9y28QXLnwgD4neYb1Arqq4QpeV1Sv3K", - "/dns/westend-bootnode-3.polkadot.io/tcp/30334/ws/p2p/12D3KooWGi1tCpKXLMYED9y28QXLnwgD4neYb1Arqq4QpeV1Sv3K", - "/dns/westend-bootnode-3.polkadot.io/tcp/443/wss/p2p/12D3KooWGi1tCpKXLMYED9y28QXLnwgD4neYb1Arqq4QpeV1Sv3K", "/dns/boot.stake.plus/tcp/32333/p2p/12D3KooWK8fjVoSvMq5copQYMsdYreSGPGgcMbGMgbMDPfpf3sm7", "/dns/boot.stake.plus/tcp/32334/wss/p2p/12D3KooWK8fjVoSvMq5copQYMsdYreSGPGgcMbGMgbMDPfpf3sm7", "/dns/boot-node.helikon.io/tcp/7080/p2p/12D3KooWRFDPyT8vA8mLzh6dJoyujn4QNjeqi6Ch79eSMz9beKXC", "/dns/boot-node.helikon.io/tcp/7082/wss/p2p/12D3KooWRFDPyT8vA8mLzh6dJoyujn4QNjeqi6Ch79eSMz9beKXC", "/dns/westend.bootnode.amforc.com/tcp/30333/p2p/12D3KooWJ5y9ZgVepBQNW4aabrxgmnrApdVnscqgKWiUu4BNJbC8", "/dns/westend.bootnode.amforc.com/tcp/30334/wss/p2p/12D3KooWJ5y9ZgVepBQNW4aabrxgmnrApdVnscqgKWiUu4BNJbC8", - "/dns/westend-bootnode.polkadotters.com/tcp/30333/p2p/12D3KooWHPHb64jXMtSRJDrYFATWeLnvChL8NtWVttY67DCH1eC5", - "/dns/westend-bootnode.polkadotters.com/tcp/30334/wss/p2p/12D3KooWHPHb64jXMtSRJDrYFATWeLnvChL8NtWVttY67DCH1eC5", + "/dns/westend.bootnodes.polkadotters.com/tcp/30308/p2p/12D3KooWHPHb64jXMtSRJDrYFATWeLnvChL8NtWVttY67DCH1eC5", + "/dns/westend.bootnodes.polkadotters.com/tcp/30310/wss/p2p/12D3KooWHPHb64jXMtSRJDrYFATWeLnvChL8NtWVttY67DCH1eC5", "/dns/boot-cr.gatotech.network/tcp/33300/p2p/12D3KooWQGR1vUhoy6mvQorFp3bZFn6NNezhQZ6NWnVV7tpFgoPd", "/dns/boot-cr.gatotech.network/tcp/35300/wss/p2p/12D3KooWQGR1vUhoy6mvQorFp3bZFn6NNezhQZ6NWnVV7tpFgoPd", "/dns/boot-westend.metaspan.io/tcp/33012/p2p/12D3KooWNTau7iG4G9cUJSwwt2QJP1W88pUf2SgqsHjRU2RL8pfa", @@ -37,7 +31,9 @@ "/dns/wnd-bootnode.stakeworld.io/tcp/30321/ws/p2p/12D3KooWBYdKipcNbrV5rCbgT5hco8HMLME7cE9hHC3ckqCKDuzP", "/dns/wnd-bootnode.stakeworld.io/tcp/30322/wss/p2p/12D3KooWBYdKipcNbrV5rCbgT5hco8HMLME7cE9hHC3ckqCKDuzP", "/dns/wnd14.rotko.net/tcp/35234/wss/p2p/12D3KooWLK8Zj1uZ46phU3vQwiDVda8tB76S8J26rXZQLHpwWkDJ", - "/dns/wnd14.rotko.net/tcp/33234/p2p/12D3KooWLK8Zj1uZ46phU3vQwiDVda8tB76S8J26rXZQLHpwWkDJ" + "/dns/wnd14.rotko.net/tcp/33234/p2p/12D3KooWLK8Zj1uZ46phU3vQwiDVda8tB76S8J26rXZQLHpwWkDJ", + "/dns/ibp-boot-westend.luckyfriday.io/tcp/30333/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9", + "/dns/ibp-boot-westend.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index ce25c08b8773059497cdc9624f9fb1cd65d20031..1c44b17b6fd24810bf896a64953a1b2f53b490da 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -18,13 +18,14 @@ use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; use grandpa::AuthorityId as GrandpaId; -use pallet_im_online::sr25519::AuthorityId as ImOnlineId; #[cfg(feature = "westend-native")] use pallet_staking::Forcing; use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, ValidatorId}; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; +#[cfg(any(feature = "rococo-native", feature = "westend-native",))] +use polkadot_primitives::vstaging::SchedulerParams; #[cfg(feature = "rococo-native")] use rococo_runtime as rococo; #[cfg(feature = "rococo-native")] @@ -75,7 +76,7 @@ pub type GenericChainSpec = service::GenericChainSpec<(), Extensions>; /// The `ChainSpec` parameterized for the westend runtime. #[cfg(feature = "westend-native")] -pub type WestendChainSpec = service::GenericChainSpec; +pub type WestendChainSpec = service::GenericChainSpec<(), Extensions>; /// The `ChainSpec` parameterized for the westend runtime. // Dummy chain spec, but that is fine when we don't have the native runtime. @@ -84,12 +85,7 @@ pub type WestendChainSpec = GenericChainSpec; /// The `ChainSpec` parameterized for the rococo runtime. #[cfg(feature = "rococo-native")] -pub type RococoChainSpec = service::GenericChainSpec; - -/// The `ChainSpec` parameterized for the `versi` runtime. -/// -/// As of now `Versi` will just be a clone of `Rococo`, until we need it to differ. -pub type VersiChainSpec = RococoChainSpec; +pub type RococoChainSpec = service::GenericChainSpec<(), Extensions>; /// The `ChainSpec` parameterized for the rococo runtime. // Dummy chain spec, but that is fine when we don't have the native runtime. @@ -108,6 +104,10 @@ pub fn westend_config() -> Result { WestendChainSpec::from_json_bytes(&include_bytes!("../chain-specs/westend.json")[..]) } +pub fn paseo_config() -> Result { + GenericChainSpec::from_json_bytes(&include_bytes!("../chain-specs/paseo.json")[..]) +} + pub fn rococo_config() -> Result { RococoChainSpec::from_json_bytes(&include_bytes!("../chain-specs/rococo.json")[..]) } @@ -122,7 +122,9 @@ pub fn wococo_config() -> Result { fn default_parachains_host_configuration( ) -> polkadot_runtime_parachains::configuration::HostConfiguration { - use polkadot_primitives::{MAX_CODE_SIZE, MAX_POV_SIZE}; + use polkadot_primitives::{ + vstaging::node_features::FeatureIndex, AsyncBackingParams, MAX_CODE_SIZE, MAX_POV_SIZE, + }; polkadot_runtime_parachains::configuration::HostConfiguration { validation_upgrade_cooldown: 2u32, @@ -131,8 +133,6 @@ fn default_parachains_host_configuration( max_code_size: MAX_CODE_SIZE, max_pov_size: MAX_POV_SIZE, max_head_data_size: 32 * 1024, - group_rotation_frequency: 20, - paras_availability_period: 4, max_upward_queue_count: 8, max_upward_queue_size: 1024 * 1024, max_downward_message_size: 1024 * 1024, @@ -153,6 +153,19 @@ fn default_parachains_host_configuration( relay_vrf_modulo_samples: 2, zeroth_delay_tranche_width: 0, minimum_validation_upgrade_delay: 5, + async_backing_params: AsyncBackingParams { + max_candidate_depth: 3, + allowed_ancestry_len: 2, + }, + node_features: bitvec::vec::BitVec::from_element( + 1u8 << (FeatureIndex::ElasticScalingMVP as usize), + ), + scheduler_params: SchedulerParams { + lookahead: 2, + group_rotation_frequency: 20, + paras_availability_period: 4, + ..Default::default() + }, ..Default::default() } } @@ -167,7 +180,6 @@ fn default_parachains_host_configuration_is_consistent() { fn westend_session_keys( babe: BabeId, grandpa: GrandpaId, - im_online: ImOnlineId, para_validator: ValidatorId, para_assignment: AssignmentId, authority_discovery: AuthorityDiscoveryId, @@ -176,7 +188,6 @@ fn westend_session_keys( westend::SessionKeys { babe, grandpa, - im_online, para_validator, para_assignment, authority_discovery, @@ -188,7 +199,6 @@ fn westend_session_keys( fn rococo_session_keys( babe: BabeId, grandpa: GrandpaId, - im_online: ImOnlineId, para_validator: ValidatorId, para_assignment: AssignmentId, authority_discovery: AuthorityDiscoveryId, @@ -197,7 +207,6 @@ fn rococo_session_keys( rococo_runtime::SessionKeys { babe, grandpa, - im_online, para_validator, para_assignment, authority_discovery, @@ -206,7 +215,7 @@ fn rococo_session_keys( } #[cfg(feature = "westend-native")] -fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::RuntimeGenesisConfig { +fn westend_staging_testnet_config_genesis() -> serde_json::Value { use hex_literal::hex; use sp_core::crypto::UncheckedInto; @@ -225,7 +234,6 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim AccountId, BabeId, GrandpaId, - ImOnlineId, ValidatorId, AssignmentId, AuthorityDiscoveryId, @@ -242,9 +250,6 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim //5Eb7wM65PNgtY6e33FEAzYtU5cRTXt6WQvZTnzaKQwkVcABk hex!["6faae44b21c6f2681a7f60df708e9f79d340f7d441d28bd987fab8d05c6487e8"] .unchecked_into(), - //5CdS2wGo4qdTQceVfEnbZH8vULeBrnGYCxSCxDna4tQSMV6y - hex!["18f5d55f138bfa8e0ea26ed6fa56817b247de3c2e2030a908c63fb37c146473f"] - .unchecked_into(), //5FqMLAgygdX9UqzukDp15Uid9PAKdFAR621U7xtp5ut2NfrW hex!["a6c1a5b501985a83cb1c37630c5b41e6b0a15b3675b2fd94694758e6cfa6794d"] .unchecked_into(), @@ -269,9 +274,6 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim //5FXFsPReTUEYPRNKhbTdUathcWBsxTNsLbk2mTpYdKCJewjA hex!["98f4d81cb383898c2c3d54dab28698c0f717c81b509cb32dc6905af3cc697b18"] .unchecked_into(), - //5CDYSCJK91r8y2r1V4Ddrit4PFMEkwZXJe8mNBqGXJ4xWCWq - hex!["06bd7dd4ab4c808c7d09d9cb6bd27fbcd99ad8400e99212b335056c475c24031"] - .unchecked_into(), //5CZjurB78XbSHf6SLkLhCdkqw52Zm7aBYUDdfkLqEDWJ9Zhj hex!["162508accd470e379b04cb0c7c60b35a7d5357e84407a89ed2dd48db4b726960"] .unchecked_into(), @@ -296,9 +298,6 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim //5G4kCbgqUhEyrRHCyFwFEkgBZXoYA8sbgsRxT9rY8Tp5Jj5F hex!["b0f8d2b9e4e1eafd4dab6358e0b9d5380d78af27c094e69ae9d6d30ca300fd86"] .unchecked_into(), - //5HVhFBLFTKSZK9fX6RktckWDTgYNoSd33fgonsEC8zfr4ddm - hex!["f03c3e184b2883eec9beaeb97f54321587e7476b228831ea0b5fc6da847ea975"] - .unchecked_into(), //5CS7thd2n54WfqeKU3cjvZzK4z5p7zku1Zw97mSzXgPioAAs hex!["1055100a283968271a0781450b389b9093231be809be1e48a305ebad2a90497e"] .unchecked_into(), @@ -323,9 +322,6 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim //5ChfdrAqmLjCeDJvynbMjcxYLHYzPe8UWXd3HnX9JDThUMbn hex!["1c309a70b4e274314b84c9a0a1f973c9c4fc084df5479ef686c54b1ae4950424"] .unchecked_into(), - //5DnsMm24575xK2b2aGfmafiDxwCet6Mr4iiZQeDdWvi8CzuF - hex!["4c64868ba6d8ace235d3efb4c10d745a67cf3bdfeae23b264d7ea2f3439dec42"] - .unchecked_into(), //5D8C3HHEp5E8fJsXRD56494F413CdRSR9QKGXe7v5ZEfymdj hex!["2ee4d78f328db178c54f205ac809da12e291a33bcbd4f29f081ce7e74bdc5044"] .unchecked_into(), @@ -344,19 +340,16 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim const ENDOWMENT: u128 = 1_000_000 * WND; const STASH: u128 = 100 * WND; - westend::RuntimeGenesisConfig { - system: westend::SystemConfig { code: wasm_binary.to_vec(), ..Default::default() }, - balances: westend::BalancesConfig { - balances: endowed_accounts + serde_json::json!({ + "balances": { + "balances": endowed_accounts .iter() .map(|k: &AccountId| (k.clone(), ENDOWMENT)) .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH))) - .collect(), + .collect::>(), }, - beefy: Default::default(), - indices: westend::IndicesConfig { indices: vec![] }, - session: westend::SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -369,57 +362,37 @@ fn westend_staging_testnet_config_genesis(wasm_binary: &[u8]) -> westend::Runtim x.5.clone(), x.6.clone(), x.7.clone(), - x.8.clone(), ), ) }) .collect::>(), }, - staking: westend::StakingConfig { - validator_count: 50, - minimum_validator_count: 4, - stakers: initial_authorities + "staking": { + "validatorCount": 50, + "minimumValidatorCount": 4, + "stakers": initial_authorities .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::Validator)) - .collect(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), - force_era: Forcing::ForceNone, - slash_reward_fraction: Perbill::from_percent(10), - ..Default::default() - }, - babe: westend::BabeConfig { - authorities: Default::default(), - epoch_config: Some(westend::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() + .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::::Validator)) + .collect::>(), + "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + "forceEra": Forcing::ForceNone, + "slashRewardFraction": Perbill::from_percent(10), }, - grandpa: Default::default(), - im_online: Default::default(), - authority_discovery: westend::AuthorityDiscoveryConfig { - keys: vec![], - ..Default::default() + "babe": { + "epochConfig": Some(westend::BABE_GENESIS_EPOCH_CONFIG), }, - vesting: westend::VestingConfig { vesting: vec![] }, - sudo: westend::SudoConfig { key: Some(endowed_accounts[0].clone()) }, - hrmp: Default::default(), - treasury: Default::default(), - configuration: westend::ConfigurationConfig { - config: default_parachains_host_configuration(), + "sudo": { "key": Some(endowed_accounts[0].clone()) }, + "configuration": { + "config": default_parachains_host_configuration(), }, - paras: Default::default(), - registrar: westend_runtime::RegistrarConfig { - next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, - ..Default::default() + "registrar": { + "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, }, - xcm_pallet: Default::default(), - nomination_pools: Default::default(), - assigned_slots: Default::default(), - } + }) } #[cfg(feature = "rococo-native")] -fn rococo_staging_testnet_config_genesis( - wasm_binary: &[u8], -) -> rococo_runtime::RuntimeGenesisConfig { +fn rococo_staging_testnet_config_genesis() -> serde_json::Value { use hex_literal::hex; use sp_core::crypto::UncheckedInto; @@ -435,7 +408,6 @@ fn rococo_staging_testnet_config_genesis( AccountId, BabeId, GrandpaId, - ImOnlineId, ValidatorId, AssignmentId, AuthorityDiscoveryId, @@ -452,9 +424,6 @@ fn rococo_staging_testnet_config_genesis( //5CPd3zoV9Aaah4xWucuDivMHJ2nEEmpdi864nPTiyRZp4t87 hex!["0e6d7d1afbcc6547b92995a394ba0daed07a2420be08220a5a1336c6731f0bfa"] .unchecked_into(), - //5F7BEa1LGFksUihyatf3dCDYneB8pWzVyavnByCsm5nBgezi - hex!["86975a37211f8704e947a365b720f7a3e2757988eaa7d0f197e83dba355ef743"] - .unchecked_into(), //5CP6oGfwqbEfML8efqm1tCZsUgRsJztp9L8ZkEUxA16W8PPz hex!["0e07a51d3213842f8e9363ce8e444255990a225f87e80a3d651db7841e1a0205"] .unchecked_into(), @@ -479,9 +448,6 @@ fn rococo_staging_testnet_config_genesis( //5HnDVBN9mD6mXyx8oryhDbJtezwNSj1VRXgLoYCBA6uEkiao hex!["fcd5f87a6fd5707a25122a01b4dac0a8482259df7d42a9a096606df1320df08d"] .unchecked_into(), - //5DhyXZiuB1LvqYKFgT5tRpgGsN3is2cM9QxgW7FikvakbAZP - hex!["48a910c0af90898f11bd57d37ceaea53c78994f8e1833a7ade483c9a84bde055"] - .unchecked_into(), //5EPEWRecy2ApL5n18n3aHyU1956zXTRqaJpzDa9DoqiggNwF hex!["669a10892119453e9feb4e3f1ee8e028916cc3240022920ad643846fbdbee816"] .unchecked_into(), @@ -506,9 +472,6 @@ fn rococo_staging_testnet_config_genesis( //5HAes2RQYPbYKbLBfKb88f4zoXv6pPA6Ke8CjN7dob3GpmSP hex!["e1b68fbd84333e31486c08e6153d9a1415b2e7e71b413702b7d64e9b631184a1"] .unchecked_into(), - //5HTXBf36LXmkFWJLokNUK6fPxVpkr2ToUnB1pvaagdGu4c1T - hex!["ee93e26259decb89afcf17ef2aa0fa2db2e1042fb8f56ecfb24d19eae8629878"] - .unchecked_into(), //5FtAGDZYJKXkhVhAxCQrXmaP7EE2mGbBMfmKDHjfYDgq2BiU hex!["a8e61ffacafaf546283dc92d14d7cc70ea0151a5dd81fdf73ff5a2951f2b6037"] .unchecked_into(), @@ -533,9 +496,6 @@ fn rococo_staging_testnet_config_genesis( //5DJV3zCBTJBLGNDCcdWrYxWDacSz84goGTa4pFeKVvehEBte hex!["36be9069cdb4a8a07ecd51f257875150f0a8a1be44a10d9d98dabf10a030aef4"] .unchecked_into(), - //5FHf8kpK4fPjEJeYcYon2gAPwEBubRvtwpzkUbhMWSweKPUY - hex!["8e95b9b5b4dc69790b67b566567ca8bf8cdef3a3a8bb65393c0d1d1c87cd2d2c"] - .unchecked_into(), //5F9FsRjpecP9GonktmtFL3kjqNAMKjHVFjyjRdTPa4hbQRZA hex!["882d72965e642677583b333b2d173ac94b5fd6c405c76184bb14293be748a13b"] .unchecked_into(), @@ -560,9 +520,6 @@ fn rococo_staging_testnet_config_genesis( //5EX1JBghGbQqWohTPU6msR9qZ2nYPhK9r3RTQ2oD1K8TCxaG hex!["6c878e33b83c20324238d22240f735457b6fba544b383e70bb62a27b57380c81"] .unchecked_into(), - //5GqL8RbVAuNXpDhjQi1KrS1MyNuKhvus2AbmQwRGjpuGZmFu - hex!["d2f9d537ffa59919a4028afdb627c14c14c97a1547e13e8e82203d2049b15b1a"] - .unchecked_into(), //5EUNaBpX9mJgcmLQHyG5Pkms6tbDiKuLbeTEJS924Js9cA1N hex!["6a8570b9c6408e54bacf123cc2bb1b0f087f9c149147d0005badba63a5a4ac01"] .unchecked_into(), @@ -587,9 +544,6 @@ fn rococo_staging_testnet_config_genesis( //5GzDPGbUM9uH52ZEwydasTj8edokGUJ7vEpoFWp9FE1YNuFB hex!["d9c056c98ca0e6b4eb7f5c58c007c1db7be0fe1f3776108f797dd4990d1ccc33"] .unchecked_into(), - //5GWZbVkJEfWZ7fRca39YAQeqri2Z7pkeHyd7rUctUHyQifLp - hex!["c4a980da30939d5bb9e4a734d12bf81259ae286aa21fa4b65405347fa40eff35"] - .unchecked_into(), //5CmLCFeSurRXXtwMmLcVo7sdJ9EqDguvJbuCYDcHkr3cpqyE hex!["1efc23c0b51ad609ab670ecf45807e31acbd8e7e5cb7c07cf49ee42992d2867c"] .unchecked_into(), @@ -614,9 +568,6 @@ fn rococo_staging_testnet_config_genesis( //5DnEySxbnppWEyN8cCLqvGjAorGdLRg2VmkY96dbJ1LHFK8N hex!["4bea0b37e0cce9bddd80835fa2bfd5606f5dcfb8388bbb10b10c483f0856cf14"] .unchecked_into(), - //5E1Y1FJ7dVP7qtE3wm241pTm72rTMcDT5Jd8Czv7Pwp7N3AH - hex!["560d90ca51e9c9481b8a9810060e04d0708d246714960439f804e5c6f40ca651"] - .unchecked_into(), //5CAC278tFCHAeHYqE51FTWYxHmeLcENSS1RG77EFRTvPZMJT hex!["042f07fc5268f13c026bbe199d63e6ac77a0c2a780f71cda05cee5a6f1b3f11f"] .unchecked_into(), @@ -641,9 +592,6 @@ fn rococo_staging_testnet_config_genesis( //5DrA2fZdzmNqT5j6DXNwVxPBjDV9jhkAqvjt6Us3bQHKy3cF hex!["4ee66173993dd0db5d628c4c9cb61a27b76611ad3c3925947f0d0011ee2c5dcc"] .unchecked_into(), - //5FNFDUGNLUtqg5LgrwYLNmBiGoP8KRxsvQpBkc7GQP6qaBUG - hex!["92156f54a114ee191415898f2da013d9db6a5362d6b36330d5fc23e27360ab66"] - .unchecked_into(), //5Gx6YeNhynqn8qkda9QKpc9S7oDr4sBrfAu516d3sPpEt26F hex!["d822d4088b20dca29a580a577a97d6f024bb24c9550bebdfd7d2d18e946a1c7d"] .unchecked_into(), @@ -662,19 +610,16 @@ fn rococo_staging_testnet_config_genesis( const ENDOWMENT: u128 = 1_000_000 * ROC; const STASH: u128 = 100 * ROC; - rococo_runtime::RuntimeGenesisConfig { - system: rococo_runtime::SystemConfig { code: wasm_binary.to_vec(), ..Default::default() }, - balances: rococo_runtime::BalancesConfig { - balances: endowed_accounts + serde_json::json!({ + "balances": { + "balances": endowed_accounts .iter() .map(|k: &AccountId| (k.clone(), ENDOWMENT)) .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH))) - .collect(), + .collect::>(), }, - beefy: Default::default(), - indices: rococo_runtime::IndicesConfig { indices: vec![] }, - session: rococo_runtime::SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -687,86 +632,60 @@ fn rococo_staging_testnet_config_genesis( x.5.clone(), x.6.clone(), x.7.clone(), - x.8.clone(), ), ) }) .collect::>(), }, - babe: rococo_runtime::BabeConfig { - authorities: Default::default(), - epoch_config: Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - grandpa: Default::default(), - im_online: Default::default(), - treasury: Default::default(), - authority_discovery: rococo_runtime::AuthorityDiscoveryConfig { - keys: vec![], - ..Default::default() + "babe": { + "epochConfig": Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), }, - claims: rococo::ClaimsConfig { claims: vec![], vesting: vec![] }, - vesting: rococo::VestingConfig { vesting: vec![] }, - sudo: rococo_runtime::SudoConfig { key: Some(endowed_accounts[0].clone()) }, - paras: rococo_runtime::ParasConfig { paras: vec![], ..Default::default() }, - hrmp: Default::default(), - configuration: rococo_runtime::ConfigurationConfig { - config: default_parachains_host_configuration(), + "sudo": { "key": Some(endowed_accounts[0].clone()) }, + "configuration": { + "config": default_parachains_host_configuration(), }, - registrar: rococo_runtime::RegistrarConfig { - next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, - ..Default::default() + "registrar": { + "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, }, - xcm_pallet: Default::default(), - nis_counterpart_balances: Default::default(), - assigned_slots: Default::default(), - } + }) } /// Westend staging testnet config. #[cfg(feature = "westend-native")] pub fn westend_staging_testnet_config() -> Result { - let wasm_binary = westend::WASM_BINARY.ok_or("Westend development wasm not available")?; - let boot_nodes = vec![]; - - Ok(WestendChainSpec::from_genesis( - "Westend Staging Testnet", - "westend_staging_testnet", - ChainType::Live, - move || westend_staging_testnet_config_genesis(wasm_binary), - boot_nodes, - Some( - TelemetryEndpoints::new(vec![(WESTEND_STAGING_TELEMETRY_URL.to_string(), 0)]) - .expect("Westend Staging telemetry url is valid; qed"), - ), - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(WestendChainSpec::builder( + westend::WASM_BINARY.ok_or("Westend development wasm not available")?, Default::default(), - )) + ) + .with_name("Westend Staging Testnet") + .with_id("westend_staging_testnet") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(westend_staging_testnet_config_genesis()) + .with_telemetry_endpoints( + TelemetryEndpoints::new(vec![(WESTEND_STAGING_TELEMETRY_URL.to_string(), 0)]) + .expect("Westend Staging telemetry url is valid; qed"), + ) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } /// Rococo staging testnet config. #[cfg(feature = "rococo-native")] pub fn rococo_staging_testnet_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Rococo development wasm not available")?; - let boot_nodes = vec![]; - - Ok(RococoChainSpec::from_genesis( - "Rococo Staging Testnet", - "rococo_staging_testnet", - ChainType::Live, - move || rococo_staging_testnet_config_genesis(wasm_binary), - boot_nodes, - Some( - TelemetryEndpoints::new(vec![(ROCOCO_STAGING_TELEMETRY_URL.to_string(), 0)]) - .expect("Rococo Staging telemetry url is valid; qed"), - ), - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Rococo development wasm not available")?, Default::default(), - )) + ) + .with_name("Rococo Staging Testnet") + .with_id("rococo_staging_testnet") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(rococo_staging_testnet_config_genesis()) + .with_telemetry_endpoints( + TelemetryEndpoints::new(vec![(ROCOCO_STAGING_TELEMETRY_URL.to_string(), 0)]) + .expect("Rococo Staging telemetry url is valid; qed"), + ) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } pub fn versi_chain_spec_properties() -> serde_json::map::Map { @@ -783,24 +702,21 @@ pub fn versi_chain_spec_properties() -> serde_json::map::Map Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Versi development wasm not available")?; - let boot_nodes = vec![]; - - Ok(RococoChainSpec::from_genesis( - "Versi Staging Testnet", - "versi_staging_testnet", - ChainType::Live, - move || rococo_staging_testnet_config_genesis(wasm_binary), - boot_nodes, - Some( - TelemetryEndpoints::new(vec![(VERSI_STAGING_TELEMETRY_URL.to_string(), 0)]) - .expect("Versi Staging telemetry url is valid; qed"), - ), - Some("versi"), - None, - Some(versi_chain_spec_properties()), + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Versi development wasm not available")?, Default::default(), - )) + ) + .with_name("Versi Staging Testnet") + .with_id("versi_staging_testnet") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(rococo_staging_testnet_config_genesis()) + .with_telemetry_endpoints( + TelemetryEndpoints::new(vec![(VERSI_STAGING_TELEMETRY_URL.to_string(), 0)]) + .expect("Versi Staging telemetry url is valid; qed"), + ) + .with_protocol_id("versi") + .with_properties(versi_chain_spec_properties()) + .build()) } /// Helper function to generate a crypto pair from seed @@ -826,35 +742,24 @@ pub fn get_authority_keys_from_seed( AccountId, BabeId, GrandpaId, - ImOnlineId, ValidatorId, AssignmentId, AuthorityDiscoveryId, BeefyId, ) { let keys = get_authority_keys_from_seed_no_beefy(seed); - (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, keys.7, get_from_seed::(seed)) + (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, get_from_seed::(seed)) } /// Helper function to generate stash, controller and session key from seed pub fn get_authority_keys_from_seed_no_beefy( seed: &str, -) -> ( - AccountId, - AccountId, - BabeId, - GrandpaId, - ImOnlineId, - ValidatorId, - AssignmentId, - AuthorityDiscoveryId, -) { +) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { ( get_account_id_from_seed::(&format!("{}//stash", seed)), get_account_id_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), - get_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), @@ -879,16 +784,14 @@ fn testnet_accounts() -> Vec { ] } -/// Helper function to create westend `RuntimeGenesisConfig` for testing +/// Helper function to create westend runtime `GenesisConfig` patch for testing #[cfg(feature = "westend-native")] pub fn westend_testnet_genesis( - wasm_binary: &[u8], initial_authorities: Vec<( AccountId, AccountId, BabeId, GrandpaId, - ImOnlineId, ValidatorId, AssignmentId, AuthorityDiscoveryId, @@ -896,21 +799,18 @@ pub fn westend_testnet_genesis( )>, root_key: AccountId, endowed_accounts: Option>, -) -> westend::RuntimeGenesisConfig { +) -> serde_json::Value { let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(testnet_accounts); const ENDOWMENT: u128 = 1_000_000 * WND; const STASH: u128 = 100 * WND; - westend::RuntimeGenesisConfig { - system: westend::SystemConfig { code: wasm_binary.to_vec(), ..Default::default() }, - indices: westend::IndicesConfig { indices: vec![] }, - balances: westend::BalancesConfig { - balances: endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), }, - beefy: Default::default(), - session: westend::SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -923,63 +823,43 @@ pub fn westend_testnet_genesis( x.5.clone(), x.6.clone(), x.7.clone(), - x.8.clone(), ), ) }) .collect::>(), }, - staking: westend::StakingConfig { - minimum_validator_count: 1, - validator_count: initial_authorities.len() as u32, - stakers: initial_authorities + "staking": { + "minimumValidatorCount": 1, + "validatorCount": initial_authorities.len() as u32, + "stakers": initial_authorities .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::Validator)) - .collect(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), - force_era: Forcing::NotForcing, - slash_reward_fraction: Perbill::from_percent(10), - ..Default::default() - }, - babe: westend::BabeConfig { - authorities: Default::default(), - epoch_config: Some(westend::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() + .map(|x| (x.0.clone(), x.0.clone(), STASH, westend::StakerStatus::::Validator)) + .collect::>(), + "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + "forceEra": Forcing::NotForcing, + "slashRewardFraction": Perbill::from_percent(10), }, - grandpa: Default::default(), - im_online: Default::default(), - authority_discovery: westend::AuthorityDiscoveryConfig { - keys: vec![], - ..Default::default() + "babe": { + "epochConfig": Some(westend::BABE_GENESIS_EPOCH_CONFIG), }, - vesting: westend::VestingConfig { vesting: vec![] }, - sudo: westend::SudoConfig { key: Some(root_key) }, - hrmp: Default::default(), - treasury: Default::default(), - configuration: westend::ConfigurationConfig { - config: default_parachains_host_configuration(), + "sudo": { "key": Some(root_key) }, + "configuration": { + "config": default_parachains_host_configuration(), }, - paras: Default::default(), - registrar: westend_runtime::RegistrarConfig { - next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, - ..Default::default() + "registrar": { + "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, }, - xcm_pallet: Default::default(), - nomination_pools: Default::default(), - assigned_slots: Default::default(), - } + }) } -/// Helper function to create rococo `RuntimeGenesisConfig` for testing +/// Helper function to create rococo runtime `GenesisConfig` patch for testing #[cfg(feature = "rococo-native")] pub fn rococo_testnet_genesis( - wasm_binary: &[u8], initial_authorities: Vec<( AccountId, AccountId, BabeId, GrandpaId, - ImOnlineId, ValidatorId, AssignmentId, AuthorityDiscoveryId, @@ -987,20 +867,17 @@ pub fn rococo_testnet_genesis( )>, root_key: AccountId, endowed_accounts: Option>, -) -> rococo_runtime::RuntimeGenesisConfig { +) -> serde_json::Value { let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(testnet_accounts); const ENDOWMENT: u128 = 1_000_000 * ROC; - rococo_runtime::RuntimeGenesisConfig { - system: rococo_runtime::SystemConfig { code: wasm_binary.to_vec(), ..Default::default() }, - beefy: Default::default(), - indices: rococo_runtime::IndicesConfig { indices: vec![] }, - balances: rococo_runtime::BalancesConfig { - balances: endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), }, - session: rococo_runtime::SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -1013,49 +890,33 @@ pub fn rococo_testnet_genesis( x.5.clone(), x.6.clone(), x.7.clone(), - x.8.clone(), ), ) }) .collect::>(), }, - babe: rococo_runtime::BabeConfig { - authorities: Default::default(), - epoch_config: Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() + "babe": { + "epochConfig": Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), }, - grandpa: Default::default(), - im_online: Default::default(), - treasury: Default::default(), - claims: rococo::ClaimsConfig { claims: vec![], vesting: vec![] }, - vesting: rococo::VestingConfig { vesting: vec![] }, - authority_discovery: rococo_runtime::AuthorityDiscoveryConfig { - keys: vec![], - ..Default::default() - }, - sudo: rococo_runtime::SudoConfig { key: Some(root_key.clone()) }, - hrmp: Default::default(), - configuration: rococo_runtime::ConfigurationConfig { - config: polkadot_runtime_parachains::configuration::HostConfiguration { - max_validators_per_core: Some(1), + "sudo": { "key": Some(root_key.clone()) }, + "configuration": { + "config": polkadot_runtime_parachains::configuration::HostConfiguration { + scheduler_params: SchedulerParams { + max_validators_per_core: Some(1), + ..default_parachains_host_configuration().scheduler_params + }, ..default_parachains_host_configuration() }, }, - paras: rococo_runtime::ParasConfig { paras: vec![], ..Default::default() }, - registrar: rococo_runtime::RegistrarConfig { - next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, - ..Default::default() - }, - xcm_pallet: Default::default(), - nis_counterpart_balances: Default::default(), - assigned_slots: Default::default(), - } + "registrar": { + "nextFreeParaId": polkadot_primitives::LOWEST_PUBLIC_ID, + } + }) } #[cfg(feature = "westend-native")] -fn westend_development_config_genesis(wasm_binary: &[u8]) -> westend::RuntimeGenesisConfig { +fn westend_development_config_genesis() -> serde_json::Value { westend_testnet_genesis( - wasm_binary, vec![get_authority_keys_from_seed("Alice")], get_account_id_from_seed::("Alice"), None, @@ -1063,9 +924,8 @@ fn westend_development_config_genesis(wasm_binary: &[u8]) -> westend::RuntimeGen } #[cfg(feature = "rococo-native")] -fn rococo_development_config_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGenesisConfig { +fn rococo_development_config_genesis() -> serde_json::Value { rococo_testnet_genesis( - wasm_binary, vec![get_authority_keys_from_seed("Alice")], get_account_id_from_seed::("Alice"), None, @@ -1075,84 +935,67 @@ fn rococo_development_config_genesis(wasm_binary: &[u8]) -> rococo_runtime::Runt /// Westend development config (single validator Alice) #[cfg(feature = "westend-native")] pub fn westend_development_config() -> Result { - let wasm_binary = westend::WASM_BINARY.ok_or("Westend development wasm not available")?; - - Ok(WestendChainSpec::from_genesis( - "Development", - "westend_dev", - ChainType::Development, - move || westend_development_config_genesis(wasm_binary), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(WestendChainSpec::builder( + westend::WASM_BINARY.ok_or("Westend development wasm not available")?, Default::default(), - )) + ) + .with_name("Development") + .with_id("westend_dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(westend_development_config_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } /// Rococo development config (single validator Alice) #[cfg(feature = "rococo-native")] pub fn rococo_development_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Rococo development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Development", - "rococo_dev", - ChainType::Development, - move || rococo_development_config_genesis(wasm_binary), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Rococo development wasm not available")?, Default::default(), - )) + ) + .with_name("Development") + .with_id("rococo_dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(rococo_development_config_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } /// `Versi` development config (single validator Alice) #[cfg(feature = "rococo-native")] pub fn versi_development_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Versi development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Development", - "versi_dev", - ChainType::Development, - move || rococo_development_config_genesis(wasm_binary), - vec![], - None, - Some("versi"), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Versi development wasm not available")?, Default::default(), - )) + ) + .with_name("Development") + .with_id("versi_dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(rococo_development_config_genesis()) + .with_protocol_id("versi") + .build()) } /// Wococo development config (single validator Alice) #[cfg(feature = "rococo-native")] pub fn wococo_development_config() -> Result { const WOCOCO_DEV_PROTOCOL_ID: &str = "woco"; - let wasm_binary = rococo::WASM_BINARY.ok_or("Wococo development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Development", - "wococo_dev", - ChainType::Development, - move || rococo_development_config_genesis(wasm_binary), - vec![], - None, - Some(WOCOCO_DEV_PROTOCOL_ID), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Wococo development wasm not available")?, Default::default(), - )) + ) + .with_name("Development") + .with_id("wococo_dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(rococo_development_config_genesis()) + .with_protocol_id(WOCOCO_DEV_PROTOCOL_ID) + .build()) } #[cfg(feature = "westend-native")] -fn westend_local_testnet_genesis(wasm_binary: &[u8]) -> westend::RuntimeGenesisConfig { +fn westend_local_testnet_genesis() -> serde_json::Value { westend_testnet_genesis( - wasm_binary, vec![get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")], get_account_id_from_seed::("Alice"), None, @@ -1162,26 +1005,21 @@ fn westend_local_testnet_genesis(wasm_binary: &[u8]) -> westend::RuntimeGenesisC /// Westend local testnet config (multivalidator Alice + Bob) #[cfg(feature = "westend-native")] pub fn westend_local_testnet_config() -> Result { - let wasm_binary = westend::WASM_BINARY.ok_or("Westend development wasm not available")?; - - Ok(WestendChainSpec::from_genesis( - "Westend Local Testnet", - "westend_local_testnet", - ChainType::Local, - move || westend_local_testnet_genesis(wasm_binary), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(WestendChainSpec::builder( + westend::WASM_BINARY.ok_or("Westend development wasm not available")?, Default::default(), - )) + ) + .with_name("Westend Local Testnet") + .with_id("westend_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(westend_local_testnet_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } #[cfg(feature = "rococo-native")] -fn rococo_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGenesisConfig { +fn rococo_local_testnet_genesis() -> serde_json::Value { rococo_testnet_genesis( - wasm_binary, vec![get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")], get_account_id_from_seed::("Alice"), None, @@ -1191,27 +1029,22 @@ fn rococo_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGe /// Rococo local testnet config (multivalidator Alice + Bob) #[cfg(feature = "rococo-native")] pub fn rococo_local_testnet_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Rococo development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Rococo Local Testnet", - "rococo_local_testnet", - ChainType::Local, - move || rococo_local_testnet_genesis(wasm_binary), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(RococoChainSpec::builder( + rococo::fast_runtime_binary::WASM_BINARY.ok_or("Rococo development wasm not available")?, Default::default(), - )) + ) + .with_name("Rococo Local Testnet") + .with_id("rococo_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(rococo_local_testnet_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } /// Wococo is a temporary testnet that uses almost the same runtime as rococo. #[cfg(feature = "rococo-native")] -fn wococo_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGenesisConfig { +fn wococo_local_testnet_genesis() -> serde_json::Value { rococo_testnet_genesis( - wasm_binary, vec![ get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob"), @@ -1226,27 +1059,22 @@ fn wococo_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGe /// Wococo local testnet config (multivalidator Alice + Bob + Charlie + Dave) #[cfg(feature = "rococo-native")] pub fn wococo_local_testnet_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Wococo development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Wococo Local Testnet", - "wococo_local_testnet", - ChainType::Local, - move || wococo_local_testnet_genesis(wasm_binary), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Rococo development wasm (used for wococo) not available")?, Default::default(), - )) + ) + .with_name("Wococo Local Testnet") + .with_id("wococo_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(wococo_local_testnet_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .build()) } /// `Versi` is a temporary testnet that uses the same runtime as rococo. #[cfg(feature = "rococo-native")] -fn versi_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGenesisConfig { +fn versi_local_testnet_genesis() -> serde_json::Value { rococo_testnet_genesis( - wasm_binary, vec![ get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob"), @@ -1261,18 +1089,14 @@ fn versi_local_testnet_genesis(wasm_binary: &[u8]) -> rococo_runtime::RuntimeGen /// `Versi` local testnet config (multivalidator Alice + Bob + Charlie + Dave) #[cfg(feature = "rococo-native")] pub fn versi_local_testnet_config() -> Result { - let wasm_binary = rococo::WASM_BINARY.ok_or("Versi development wasm not available")?; - - Ok(RococoChainSpec::from_genesis( - "Versi Local Testnet", - "versi_local_testnet", - ChainType::Local, - move || versi_local_testnet_genesis(wasm_binary), - vec![], - None, - Some("versi"), - None, - None, + Ok(RococoChainSpec::builder( + rococo::WASM_BINARY.ok_or("Rococo development wasm (used for versi) not available")?, Default::default(), - )) + ) + .with_name("Versi Local Testnet") + .with_id("versi_local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(versi_local_testnet_genesis()) + .with_protocol_id("versi") + .build()) } diff --git a/polkadot/node/service/src/fake_runtime_api.rs b/polkadot/node/service/src/fake_runtime_api.rs index d9553afa024b49fe68153500f7d1a2e102052c6c..085ea93fdc78526f793b726bf385124ec6418e5d 100644 --- a/polkadot/node/service/src/fake_runtime_api.rs +++ b/polkadot/node/service/src/fake_runtime_api.rs @@ -60,7 +60,7 @@ sp_api::impl_runtime_apis! { unimplemented!() } - fn initialize_block(_: &::Header) { + fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { unimplemented!() } } @@ -116,7 +116,7 @@ sp_api::impl_runtime_apis! { } } - impl runtime_api::ParachainHost for Runtime { + impl runtime_api::ParachainHost for Runtime { fn validators() -> Vec { unimplemented!() } diff --git a/polkadot/node/service/src/grandpa_support.rs b/polkadot/node/service/src/grandpa_support.rs index 3a767d9783f0dfcff13166a39220ab161533ff72..729dbfde5c76b850dd1b76bd9195de5066d551bc 100644 --- a/polkadot/node/service/src/grandpa_support.rs +++ b/polkadot/node/service/src/grandpa_support.rs @@ -16,8 +16,6 @@ //! Polkadot-specific GRANDPA integration utilities. -use std::sync::Arc; - use sp_runtime::traits::{Block as BlockT, Header as _, NumberFor}; use crate::HeaderProvider; @@ -59,55 +57,6 @@ where } } -/// A custom GRANDPA voting rule that "pauses" voting (i.e. keeps voting for the -/// same last finalized block) after a given block at height `N` has been -/// finalized and for a delay of `M` blocks, i.e. until the best block reaches -/// `N` + `M`, the voter will keep voting for block `N`. -#[derive(Clone)] -pub(crate) struct PauseAfterBlockFor(pub(crate) N, pub(crate) N); - -impl grandpa::VotingRule for PauseAfterBlockFor> -where - Block: BlockT, - B: sp_blockchain::HeaderBackend + 'static, -{ - fn restrict_vote( - &self, - backend: Arc, - base: &Block::Header, - best_target: &Block::Header, - current_target: &Block::Header, - ) -> grandpa::VotingRuleResult { - let aux = || { - // only restrict votes targeting a block higher than the block - // we've set for the pause - if *current_target.number() > self.0 { - // if we're past the pause period (i.e. `self.0 + self.1`) - // then we no longer need to restrict any votes - if *best_target.number() > self.0 + self.1 { - return None - } - - // if we've finalized the pause block, just keep returning it - // until best number increases enough to pass the condition above - if *base.number() >= self.0 { - return Some((base.hash(), *base.number())) - } - - // otherwise find the target header at the pause block - // to vote on - return walk_backwards_to_target_block(&*backend, self.0, current_target).ok() - } - - None - }; - - let target = aux(); - - Box::pin(async move { target }) - } -} - /// GRANDPA hard forks due to borked migration of session keys after a runtime /// upgrade (at #1491596), the signaled authority set changes were invalid /// (blank keys) and were impossible to finalize. The authorities for these @@ -214,130 +163,3 @@ pub(crate) fn kusama_hard_forks() -> Vec> { }) .collect() } - -#[cfg(test)] -mod tests { - use consensus_common::BlockOrigin; - use grandpa::VotingRule; - use polkadot_test_client::{ - ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, - TestClientBuilder, TestClientBuilderExt, - }; - use sp_blockchain::HeaderBackend; - use sp_runtime::traits::Header; - use std::sync::Arc; - - #[test] - fn grandpa_pause_voting_rule_works() { - let _ = env_logger::try_init(); - - let client = Arc::new(TestClientBuilder::new().build()); - let mut hashes = vec![]; - hashes.push(client.info().genesis_hash); - - let mut push_blocks = { - let mut client = client.clone(); - - move |hashes: &mut Vec<_>, n| { - for _ in 0..n { - let block = client.init_polkadot_block_builder().build().unwrap().block; - hashes.push(block.header.hash()); - futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } - } - }; - - let get_header = { - let client = client.clone(); - move |n| client.expect_header(n).unwrap() - }; - - // the rule should filter all votes after block #20 - // is finalized until block #50 is imported. - let voting_rule = super::PauseAfterBlockFor(20, 30); - - // add 10 blocks - push_blocks(&mut hashes, 10); - assert_eq!(client.info().best_number, 10); - - // we have not reached the pause block - // therefore nothing should be restricted - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &get_header(hashes[0]), - &get_header(hashes[10]), - &get_header(hashes[10]) - )), - None, - ); - - // add 15 more blocks - // best block: #25 - push_blocks(&mut hashes, 15); - - // we are targeting the pause block, - // the vote should not be restricted - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &get_header(hashes[10]), - &get_header(hashes[20]), - &get_header(hashes[20]) - )), - None, - ); - - // we are past the pause block, votes should - // be limited to the pause block. - let pause_block = get_header(hashes[20]); - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &get_header(hashes[10]), - &get_header(hashes[21]), - &get_header(hashes[21]) - )), - Some((pause_block.hash(), *pause_block.number())), - ); - - // we've finalized the pause block, so we'll keep - // restricting our votes to it. - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &pause_block, // #20 - &get_header(hashes[21]), - &get_header(hashes[21]), - )), - Some((pause_block.hash(), *pause_block.number())), - ); - - // add 30 more blocks - // best block: #55 - push_blocks(&mut hashes, 30); - - // we're at the last block of the pause, this block - // should still be considered in the pause period - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &pause_block, // #20 - &get_header(hashes[50]), - &get_header(hashes[50]), - )), - Some((pause_block.hash(), *pause_block.number())), - ); - - // we're past the pause period, no votes should be filtered - assert_eq!( - futures::executor::block_on(voting_rule.restrict_vote( - client.clone(), - &pause_block, // #20 - &get_header(hashes[51]), - &get_header(hashes[51]), - )), - None, - ); - } -} diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index ced89c3843a24c3fb8c84c67bd7cf0cbaf78a80e..83a0afc077e7edfb4a60fb8ed4109157f020695b 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -31,7 +31,10 @@ pub mod overseer; pub mod workers; #[cfg(feature = "full-node")] -pub use self::overseer::{OverseerGen, OverseerGenArgs, RealOverseerGen}; +pub use self::overseer::{ + CollatorOverseerGen, ExtendedOverseerGenArgs, OverseerGen, OverseerGenArgs, + ValidatorOverseerGen, +}; #[cfg(test)] mod tests; @@ -51,7 +54,8 @@ use { }, polkadot_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig, polkadot_node_network_protocol::{ - peer_set::PeerSetProtocolNames, request_response::ReqProtocolNames, + peer_set::{PeerSet, PeerSetProtocolNames}, + request_response::ReqProtocolNames, }, sc_client_api::BlockBackend, sc_transaction_pool_api::OffchainTransactionPoolFactory, @@ -74,7 +78,7 @@ pub use { #[cfg(feature = "full-node")] use polkadot_node_subsystem::jaeger; -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use prometheus_endpoint::Registry; #[cfg(feature = "full-node")] @@ -88,6 +92,7 @@ pub use chain_spec::{GenericChainSpec, RococoChainSpec, WestendChainSpec}; pub use consensus_common::{Proposal, SelectChain}; use frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE; use mmr_gadget::MmrGadget; +use polkadot_node_subsystem_types::DefaultSubsystemClient; pub use polkadot_primitives::{Block, BlockId, BlockNumber, CollatorPair, Hash, Id as ParaId}; pub use sc_client_api::{Backend, CallExecutor}; pub use sc_consensus::{BlockImport, LongestChain}; @@ -98,7 +103,7 @@ pub use service::{ ChainSpec, Configuration, Error as SubstrateServiceError, PruningMode, Role, RuntimeGenesis, TFullBackend, TFullCallExecutor, TFullClient, TaskManager, TransactionPoolOptions, }; -pub use sp_api::{ApiRef, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi, StateBackend}; +pub use sp_api::{ApiRef, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi}; pub use sp_runtime::{ generic, traits::{self as runtime_traits, BlakeTwo256, Block as BlockT, Header as HeaderT, NumberFor}, @@ -238,7 +243,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, @@ -623,13 +628,17 @@ where #[cfg(feature = "full-node")] pub struct NewFullParams { pub is_parachain_node: IsParachainNode, - pub grandpa_pause: Option<(u32, u32)>, pub enable_beefy: bool, + /// Whether to enable the block authoring backoff on production networks + /// where it isn't enabled by default. + pub force_authoring_backoff: bool, pub jaeger_agent: Option, pub telemetry_worker_handle: Option, /// The version of the node. TESTING ONLY: `None` can be passed to skip the node/worker version /// check, both on startup and in the workers. pub node_version: Option, + /// Whether the node is attempting to run as a secure validator. + pub secure_validator_mode: bool, /// An optional path to a directory containing the workers. pub workers_path: Option, /// Optional custom names for the prepare and execute workers. @@ -714,11 +723,12 @@ pub fn new_full( mut config: Configuration, NewFullParams { is_parachain_node, - grandpa_pause, enable_beefy, + force_authoring_backoff, jaeger_agent, telemetry_worker_handle, node_version, + secure_validator_mode, workers_path, workers_names, overseer_gen, @@ -728,20 +738,26 @@ pub fn new_full( }: NewFullParams, ) -> Result { use polkadot_node_network_protocol::request_response::IncomingRequest; - use sc_network_sync::warp::WarpSyncParams; + use sc_network_sync::WarpSyncParams; 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 = { + let backoff_authoring_blocks = if !force_authoring_backoff && + (config.chain_spec.is_polkadot() || config.chain_spec.is_kusama()) + { + // the block authoring backoff is disabled by default on production networks + None + } else { let mut backoff = sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default(); if config.chain_spec.is_rococo() || config.chain_spec.is_wococo() || - config.chain_spec.is_versi() + config.chain_spec.is_versi() || + config.chain_spec.is_dev() { - // it's a testnet that's in flux, finality has stalled sometimes due - // to operational issues and it's annoying to slow down block + // on testnets that are in flux (like rococo or versi), finality has stalled + // sometimes due to operational issues and it's annoying to slow down block // production to 1 block per hour. backoff.max_interval = 10; } @@ -763,8 +779,6 @@ pub fn new_full( let keystore = basics.keystore_container.local_keystore(); let auth_or_collator = role.is_authority() || is_parachain_node.is_collator(); - // We only need to enable the pvf checker when this is a validator. - let pvf_checker_enabled = role.is_authority(); let select_chain = if auth_or_collator { let metrics = @@ -801,9 +815,9 @@ pub fn new_full( // anything in terms of behaviour, but makes the logs more consistent with the other // Substrate nodes. let grandpa_protocol_name = grandpa::protocol_standard_name(&genesis_hash, &config.chain_spec); - net_config.add_notification_protocol(grandpa::grandpa_peers_set_config( - grandpa_protocol_name.clone(), - )); + let (grandpa_protocol_config, grandpa_notification_service) = + 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()); @@ -816,12 +830,17 @@ pub fn new_full( client.clone(), prometheus_registry.clone(), ); - if enable_beefy { - net_config.add_notification_protocol(beefy::communication::beefy_peers_set_config( - beefy_gossip_proto_name.clone(), - )); - net_config.add_request_response_protocol(beefy_req_resp_cfg); - } + let beefy_notification_service = match enable_beefy { + false => None, + true => { + 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); + Some(beefy_notification_service) + }, + }; // validation/collation protocols are enabled only if `Overseer` is enabled let peerset_protocol_names = @@ -832,20 +851,24 @@ pub fn new_full( // // Collators and parachain full nodes require the collator and validator networking to send // collations and to be able to recover PoVs. - if role.is_authority() || is_parachain_node.is_running_alongside_parachain_node() { - use polkadot_network_bridge::{peer_sets_info, IsAuthority}; - let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No }; - for config in peer_sets_info(is_authority, &peerset_protocol_names) { - net_config.add_notification_protocol(config); - } - } + let notification_services = + if role.is_authority() || is_parachain_node.is_running_alongside_parachain_node() { + use polkadot_network_bridge::{peer_sets_info, IsAuthority}; + let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No }; + + peer_sets_info(is_authority, &peerset_protocol_names) + .into_iter() + .map(|(config, (peerset, service))| { + net_config.add_notification_protocol(config); + (peerset, service) + }) + .collect::>>() + } else { + std::collections::HashMap::new() + }; let req_protocol_names = ReqProtocolNames::new(&genesis_hash, config.chain_spec.fork_id()); - let (pov_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); - net_config.add_request_response_protocol(cfg); - let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); - net_config.add_request_response_protocol(cfg); let (collation_req_v1_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); net_config.add_request_response_protocol(cfg); @@ -855,12 +878,9 @@ pub fn new_full( let (available_data_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); net_config.add_request_response_protocol(cfg); - let (statement_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); - net_config.add_request_response_protocol(cfg); - let (candidate_req_v2_receiver, cfg) = - IncomingRequest::get_config_receiver(&req_protocol_names); + let (pov_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); net_config.add_request_response_protocol(cfg); - let (dispute_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); + let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); net_config.add_request_response_protocol(cfg); let grandpa_hard_forks = if config.chain_spec.is_kusama() { @@ -875,6 +895,69 @@ pub fn new_full( grandpa_hard_forks, )); + let ext_overseer_args = if is_parachain_node.is_running_alongside_parachain_node() { + None + } else { + let parachains_db = open_database(&config.database)?; + let candidate_validation_config = if role.is_authority() { + let (prep_worker_path, exec_worker_path) = workers::determine_workers_paths( + workers_path, + workers_names, + node_version.clone(), + )?; + log::info!("🚀 Using prepare-worker binary at: {:?}", prep_worker_path); + log::info!("🚀 Using execute-worker binary at: {:?}", exec_worker_path); + + Some(CandidateValidationConfig { + artifacts_cache_path: config + .database + .path() + .ok_or(Error::DatabasePathRequired)? + .join("pvf-artifacts"), + node_version, + secure_validator_mode, + prep_worker_path, + exec_worker_path, + }) + } else { + None + }; + let (statement_req_receiver, cfg) = + IncomingRequest::get_config_receiver(&req_protocol_names); + net_config.add_request_response_protocol(cfg); + let (candidate_req_v2_receiver, cfg) = + IncomingRequest::get_config_receiver(&req_protocol_names); + net_config.add_request_response_protocol(cfg); + let (dispute_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); + net_config.add_request_response_protocol(cfg); + let approval_voting_config = ApprovalVotingConfig { + col_approval_data: parachains_db::REAL_COLUMNS.col_approval_data, + slot_duration_millis: slot_duration.as_millis() as u64, + }; + let dispute_coordinator_config = DisputeCoordinatorConfig { + col_dispute_data: parachains_db::REAL_COLUMNS.col_dispute_coordinator_data, + }; + let chain_selection_config = ChainSelectionConfig { + col_data: parachains_db::REAL_COLUMNS.col_chain_selection_data, + stagnant_check_interval: Default::default(), + stagnant_check_mode: chain_selection_subsystem::StagnantCheckMode::PruneOnly, + }; + Some(ExtendedOverseerGenArgs { + keystore, + parachains_db, + candidate_validation_config, + availability_config: AVAILABILITY_CONFIG, + pov_req_receiver, + chunk_req_receiver, + statement_req_receiver, + candidate_req_v2_receiver, + approval_voting_config, + dispute_req_receiver, + dispute_coordinator_config, + chain_selection_config, + }) + }; + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = service::build_network(service::BuildNetworkParams { config: &config, @@ -911,43 +994,6 @@ pub fn new_full( ); } - let parachains_db = open_database(&config.database)?; - - let approval_voting_config = ApprovalVotingConfig { - col_approval_data: parachains_db::REAL_COLUMNS.col_approval_data, - slot_duration_millis: slot_duration.as_millis() as u64, - }; - - let candidate_validation_config = if role.is_authority() { - let (prep_worker_path, exec_worker_path) = - workers::determine_workers_paths(workers_path, workers_names, node_version.clone())?; - log::info!("🚀 Using prepare-worker binary at: {:?}", prep_worker_path); - log::info!("🚀 Using execute-worker binary at: {:?}", exec_worker_path); - - Some(CandidateValidationConfig { - artifacts_cache_path: config - .database - .path() - .ok_or(Error::DatabasePathRequired)? - .join("pvf-artifacts"), - node_version, - prep_worker_path, - exec_worker_path, - }) - } else { - None - }; - - let chain_selection_config = ChainSelectionConfig { - col_data: parachains_db::REAL_COLUMNS.col_chain_selection_data, - stagnant_check_interval: Default::default(), - stagnant_check_mode: chain_selection_subsystem::StagnantCheckMode::PruneOnly, - }; - - let dispute_coordinator_config = DisputeCoordinatorConfig { - col_dispute_data: parachains_db::REAL_COLUMNS.col_dispute_coordinator_data, - }; - let rpc_handlers = service::spawn_tasks(service::SpawnTasksParams { config, backend: backend.clone(), @@ -965,11 +1011,15 @@ pub fn new_full( if let Some(hwbench) = hwbench { sc_sysinfo::print_hwbench(&hwbench); - if !SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) && role.is_authority() { - log::warn!( - "⚠️ The hardware does not meet the minimal requirements for role 'Authority' find out more at:\n\ - https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware" + match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) { + Err(err) if role.is_authority() => { + log::warn!( + "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\ + https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware", + err ); + }, + _ => {}, } if let Some(ref mut telemetry) = telemetry { @@ -1032,41 +1082,32 @@ pub fn new_full( None }; + let runtime_client = Arc::new(DefaultSubsystemClient::new( + overseer_client.clone(), + OffchainTransactionPoolFactory::new(transaction_pool.clone()), + )); + let overseer_handle = if let Some(authority_discovery_service) = authority_discovery_service { let (overseer, overseer_handle) = overseer_gen - .generate::( + .generate::>( overseer_connector, OverseerGenArgs { - keystore, - runtime_client: overseer_client.clone(), - parachains_db, + runtime_client, network_service: network.clone(), sync_service: sync_service.clone(), authority_discovery_service, - pov_req_receiver, - chunk_req_receiver, collation_req_v1_receiver, collation_req_v2_receiver, available_data_req_receiver, - statement_req_receiver, - candidate_req_v2_receiver, - dispute_req_receiver, registry: prometheus_registry.as_ref(), spawner, is_parachain_node, - approval_voting_config, - availability_config: AVAILABILITY_CONFIG, - candidate_validation_config, - chain_selection_config, - dispute_coordinator_config, - pvf_checker_enabled, overseer_message_channel_capacity_override, req_protocol_names, peerset_protocol_names, - offchain_transaction_pool_factory: OffchainTransactionPoolFactory::new( - transaction_pool.clone(), - ), + notification_services, }, + ext_overseer_args, ) .map_err(|e| { gum::error!("Failed to init overseer: {}", e); @@ -1167,13 +1208,15 @@ pub fn new_full( // need a keystore, regardless of which protocol we use below. let keystore_opt = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; - if enable_beefy { + // beefy is enabled if its notification service exists + if let Some(notification_service) = beefy_notification_service { let justifications_protocol_name = beefy_on_demand_justifications_handler.protocol_name(); let network_params = beefy::BeefyNetworkParams { network: network.clone(), sync: sync_service.clone(), gossip_protocol_name: beefy_gossip_proto_name, justifications_protocol_name, + notification_service, _phantom: core::marker::PhantomData::, }; let payload_provider = beefy_primitives::mmr::MmrRootProvider::new(client.clone()); @@ -1234,32 +1277,14 @@ pub fn new_full( // provide better guarantees of block and vote data availability than // the observer. - // add a custom voting rule to temporarily stop voting for new blocks - // after the given pause block is finalized and restarting after the - // given delay. - let mut builder = grandpa::VotingRulesBuilder::default(); + let mut voting_rules_builder = grandpa::VotingRulesBuilder::default(); #[cfg(not(feature = "malus"))] let _malus_finality_delay = None; if let Some(delay) = _malus_finality_delay { info!(?delay, "Enabling malus finality delay",); - builder = builder.add(grandpa::BeforeBestBlockBy(delay)); - }; - - let voting_rule = match grandpa_pause { - Some((block, delay)) => { - info!( - block_number = %block, - delay = %delay, - "GRANDPA scheduled voting pause set for block #{} with a duration of {} blocks.", - block, - delay, - ); - - builder.add(grandpa_support::PauseAfterBlockFor(block, delay)).build() - }, - None => builder.build(), + voting_rules_builder = voting_rules_builder.add(grandpa::BeforeBestBlockBy(delay)); }; let grandpa_config = grandpa::GrandpaParams { @@ -1267,10 +1292,11 @@ pub fn new_full( link: link_half, network: network.clone(), sync: sync_service.clone(), - voting_rule, + voting_rule: voting_rules_builder.build(), prometheus_registry: prometheus_registry.clone(), shared_voter_state, telemetry: telemetry.as_ref().map(|x| x.handle()), + notification_service: grandpa_notification_service, offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), }; diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index fd618863eeaa737ec4be26b24a0db82e0bdbf307..a8167370464937ac91ae44056ea0b17b7184927a 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -14,9 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::{AuthorityDiscoveryApi, Block, Error, Hash, IsParachainNode, Registry}; -use polkadot_node_subsystem_types::DefaultSubsystemClient; -use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use super::{Block, Error, Hash, IsParachainNode, Registry}; +use polkadot_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient}; +use polkadot_overseer::{DummySubsystem, InitializedOverseerBuilder, SubsystemError}; use sp_core::traits::SpawnNamed; use polkadot_availability_distribution::IncomingRequestReceivers; @@ -26,30 +26,24 @@ use polkadot_node_core_candidate_validation::Config as CandidateValidationConfig use polkadot_node_core_chain_selection::Config as ChainSelectionConfig; use polkadot_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig; use polkadot_node_network_protocol::{ - peer_set::PeerSetProtocolNames, + peer_set::{PeerSet, PeerSetProtocolNames}, request_response::{ v1 as request_v1, v2 as request_v2, IncomingRequestReceiver, ReqProtocolNames, }, }; #[cfg(any(feature = "malus", test))] -pub use polkadot_overseer::{ - dummy::{dummy_overseer_builder, DummySubsystem}, - HeadSupportsParachains, -}; +pub use polkadot_overseer::{dummy::dummy_overseer_builder, HeadSupportsParachains}; use polkadot_overseer::{ - metrics::Metrics as OverseerMetrics, InitializedOverseerBuilder, MetricsTrait, Overseer, - OverseerConnector, OverseerHandle, SpawnGlue, + metrics::Metrics as OverseerMetrics, MetricsTrait, Overseer, OverseerConnector, OverseerHandle, + SpawnGlue, }; -use polkadot_primitives::runtime_api::ParachainHost; +use parking_lot::Mutex; use sc_authority_discovery::Service as AuthorityDiscoveryService; use sc_client_api::AuxStore; use sc_keystore::LocalKeystore; -use sc_network::NetworkStateInfo; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_consensus_babe::BabeApi; -use std::sync::Arc; +use sc_network::{NetworkStateInfo, NotificationService}; +use std::{collections::HashMap, sync::Arc}; pub use polkadot_approval_distribution::ApprovalDistribution as ApprovalDistributionSubsystem; pub use polkadot_availability_bitfield_distribution::BitfieldDistribution as BitfieldDistributionSubsystem; @@ -81,26 +75,16 @@ pub use polkadot_statement_distribution::StatementDistributionSubsystem; /// Arguments passed for overseer construction. pub struct OverseerGenArgs<'a, Spawner, RuntimeClient> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, Spawner: 'static + SpawnNamed + Clone + Unpin, { - /// The keystore to use for i.e. validator keys. - pub keystore: Arc, /// Runtime client generic, providing the `ProvieRuntimeApi` trait besides others. pub runtime_client: Arc, - /// The underlying key value store for the parachains. - pub parachains_db: Arc, /// Underlying network service implementation. pub network_service: Arc>, /// Underlying syncing service implementation. - pub sync_service: Arc>, + pub sync_service: Arc, /// Underlying authority discovery service. pub authority_discovery_service: AuthorityDiscoveryService, - /// POV request receiver. - pub pov_req_receiver: IncomingRequestReceiver, - /// Erasure chunks request receiver. - pub chunk_req_receiver: IncomingRequestReceiver, /// Collations request receiver for network protocol v1. pub collation_req_v1_receiver: IncomingRequestReceiver, /// Collations request receiver for network protocol v2. @@ -108,76 +92,85 @@ where /// Receiver for available data requests. pub available_data_req_receiver: IncomingRequestReceiver, - /// Receiver for incoming large statement requests. - pub statement_req_receiver: IncomingRequestReceiver, - /// Receiver for incoming candidate requests. - pub candidate_req_v2_receiver: IncomingRequestReceiver, - /// Receiver for incoming disputes. - pub dispute_req_receiver: IncomingRequestReceiver, /// Prometheus registry, commonly used for production systems, less so for test. pub registry: Option<&'a Registry>, /// Task spawner to be used throughout the overseer and the APIs it provides. pub spawner: Spawner, /// Determines the behavior of the collator. pub is_parachain_node: IsParachainNode, - /// Configuration for the approval voting subsystem. - pub approval_voting_config: ApprovalVotingConfig, - /// Configuration for the availability store subsystem. - pub availability_config: AvailabilityConfig, - /// Configuration for the candidate validation subsystem. - pub candidate_validation_config: Option, - /// Configuration for the chain selection subsystem. - pub chain_selection_config: ChainSelectionConfig, - /// Configuration for the dispute coordinator subsystem. - pub dispute_coordinator_config: DisputeCoordinatorConfig, - /// Enable PVF pre-checking - pub pvf_checker_enabled: bool, /// Overseer channel capacity override. pub overseer_message_channel_capacity_override: Option, /// Request-response protocol names source. pub req_protocol_names: ReqProtocolNames, /// `PeerSet` protocol names to protocols mapping. pub peerset_protocol_names: PeerSetProtocolNames, - /// The offchain transaction pool factory. - pub offchain_transaction_pool_factory: OffchainTransactionPoolFactory, + /// Notification services for validation/collation protocols. + pub notification_services: HashMap>, } -/// Obtain a prepared `OverseerBuilder`, that is initialized -/// with all default values. -pub fn prepared_overseer_builder( +pub struct ExtendedOverseerGenArgs { + /// The keystore to use for i.e. validator keys. + pub keystore: Arc, + /// The underlying key value store for the parachains. + pub parachains_db: Arc, + /// Configuration for the candidate validation subsystem. + pub candidate_validation_config: Option, + /// Configuration for the availability store subsystem. + pub availability_config: AvailabilityConfig, + /// POV request receiver. + pub pov_req_receiver: IncomingRequestReceiver, + /// Erasure chunks request receiver. + pub chunk_req_receiver: IncomingRequestReceiver, + /// Receiver for incoming large statement requests. + pub statement_req_receiver: IncomingRequestReceiver, + /// Receiver for incoming candidate requests. + pub candidate_req_v2_receiver: IncomingRequestReceiver, + /// Configuration for the approval voting subsystem. + pub approval_voting_config: ApprovalVotingConfig, + /// Receiver for incoming disputes. + pub dispute_req_receiver: IncomingRequestReceiver, + /// Configuration for the dispute coordinator subsystem. + pub dispute_coordinator_config: DisputeCoordinatorConfig, + /// Configuration for the chain selection subsystem. + pub chain_selection_config: ChainSelectionConfig, +} + +/// Obtain a prepared validator `Overseer`, that is initialized with all default values. +pub fn validator_overseer_builder( OverseerGenArgs { - keystore, runtime_client, - parachains_db, network_service, sync_service, authority_discovery_service, - pov_req_receiver, - chunk_req_receiver, - collation_req_v1_receiver, - collation_req_v2_receiver, + collation_req_v1_receiver: _, + collation_req_v2_receiver: _, available_data_req_receiver, - statement_req_receiver, - candidate_req_v2_receiver, - dispute_req_receiver, registry, spawner, is_parachain_node, - approval_voting_config, - availability_config, - candidate_validation_config, - chain_selection_config, - dispute_coordinator_config, - pvf_checker_enabled, overseer_message_channel_capacity_override, req_protocol_names, peerset_protocol_names, - offchain_transaction_pool_factory, + notification_services, }: OverseerGenArgs, + ExtendedOverseerGenArgs { + keystore, + parachains_db, + candidate_validation_config, + availability_config, + pov_req_receiver, + chunk_req_receiver, + statement_req_receiver, + candidate_req_v2_receiver, + approval_voting_config, + dispute_req_receiver, + dispute_coordinator_config, + chain_selection_config, + }: ExtendedOverseerGenArgs, ) -> Result< InitializedOverseerBuilder< SpawnGlue, - Arc>, + Arc, CandidateValidationSubsystem, PvfCheckerSubsystem, CandidateBackingSubsystem, @@ -187,7 +180,7 @@ pub fn prepared_overseer_builder( BitfieldSigningSubsystem, BitfieldDistributionSubsystem, ProvisionerSubsystem, - RuntimeApiSubsystem>, + RuntimeApiSubsystem, AvailabilityStoreSubsystem, NetworkBridgeRxSubsystem< Arc>, @@ -211,23 +204,18 @@ pub fn prepared_overseer_builder( Error, > where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { use polkadot_node_subsystem_util::metrics::Metrics; let metrics = ::register(registry)?; + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); let spawner = SpawnGlue(spawner); let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; - let runtime_api_client = Arc::new(DefaultSubsystemClient::new( - runtime_client.clone(), - offchain_transaction_pool_factory, - )); - let builder = Overseer::builder() .network_bridge_tx(NetworkBridgeTxSubsystem::new( network_service.clone(), @@ -235,6 +223,7 @@ where network_bridge_metrics.clone(), req_protocol_names, peerset_protocol_names.clone(), + notification_sinks.clone(), )) .network_bridge_rx(NetworkBridgeRxSubsystem::new( network_service.clone(), @@ -242,6 +231,8 @@ where Box::new(sync_service.clone()), network_bridge_metrics, peerset_protocol_names, + notification_services, + notification_sinks, )) .availability_distribution(AvailabilityDistributionSubsystem::new( keystore.clone(), @@ -272,23 +263,15 @@ where Metrics::register(registry)?, // candidate-validation metrics Metrics::register(registry)?, // validation host metrics )) - .pvf_checker(PvfCheckerSubsystem::new( - pvf_checker_enabled, - keystore.clone(), - Metrics::register(registry)?, - )) + .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) .collator_protocol({ let side = match is_parachain_node { - IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator { - peer_id: network_service.local_peer_id(), - collator_pair, - request_receiver_v1: collation_req_v1_receiver, - request_receiver_v2: collation_req_v2_receiver, - metrics: Metrics::register(registry)?, - }, - IsParachainNode::FullNode => ProtocolSide::None, + IsParachainNode::Collator(_) | IsParachainNode::FullNode => + return Err(Error::Overseer(SubsystemError::Context( + "build validator overseer for parachain node".to_owned(), + ))), IsParachainNode::No => ProtocolSide::Validator { keystore: keystore.clone(), eviction_policy: Default::default(), @@ -299,7 +282,7 @@ where }) .provisioner(ProvisionerSubsystem::new(Metrics::register(registry)?)) .runtime_api(RuntimeApiSubsystem::new( - runtime_api_client.clone(), + runtime_client.clone(), Metrics::register(registry)?, spawner.clone(), )) @@ -340,62 +323,218 @@ where .activation_external_listeners(Default::default()) .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) - .supports_parachains(runtime_api_client) + .supports_parachains(runtime_client) .metrics(metrics) .spawner(spawner); - if let Some(capacity) = overseer_message_channel_capacity_override { - Ok(builder.message_channel_capacity(capacity)) + let builder = if let Some(capacity) = overseer_message_channel_capacity_override { + builder.message_channel_capacity(capacity) } else { - Ok(builder) - } + builder + }; + Ok(builder) +} + +/// Obtain a prepared collator `Overseer`, that is initialized with all default values. +pub fn collator_overseer_builder( + OverseerGenArgs { + runtime_client, + network_service, + sync_service, + authority_discovery_service, + collation_req_v1_receiver, + collation_req_v2_receiver, + available_data_req_receiver, + registry, + spawner, + is_parachain_node, + overseer_message_channel_capacity_override, + req_protocol_names, + peerset_protocol_names, + notification_services, + }: OverseerGenArgs, +) -> Result< + InitializedOverseerBuilder< + SpawnGlue, + Arc, + DummySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + AvailabilityRecoverySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + RuntimeApiSubsystem, + DummySubsystem, + NetworkBridgeRxSubsystem< + Arc>, + AuthorityDiscoveryService, + >, + NetworkBridgeTxSubsystem< + Arc>, + AuthorityDiscoveryService, + >, + ChainApiSubsystem, + CollationGenerationSubsystem, + CollatorProtocolSubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + DummySubsystem, + ProspectiveParachainsSubsystem, + >, + Error, +> +where + Spawner: 'static + SpawnNamed + Clone + Unpin, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, +{ + use polkadot_node_subsystem_util::metrics::Metrics; + + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); + + let spawner = SpawnGlue(spawner); + + let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; + + let builder = Overseer::builder() + .network_bridge_tx(NetworkBridgeTxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + network_bridge_metrics.clone(), + req_protocol_names, + peerset_protocol_names.clone(), + notification_sinks.clone(), + )) + .network_bridge_rx(NetworkBridgeRxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + Box::new(sync_service.clone()), + network_bridge_metrics, + peerset_protocol_names, + notification_services, + notification_sinks, + )) + .availability_distribution(DummySubsystem) + .availability_recovery(AvailabilityRecoverySubsystem::for_collator( + available_data_req_receiver, + Metrics::register(registry)?, + )) + .availability_store(DummySubsystem) + .bitfield_distribution(DummySubsystem) + .bitfield_signing(DummySubsystem) + .candidate_backing(DummySubsystem) + .candidate_validation(DummySubsystem) + .pvf_checker(DummySubsystem) + .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) + .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collator_protocol({ + let side = match is_parachain_node { + IsParachainNode::No => + return Err(Error::Overseer(SubsystemError::Context( + "build parachain node overseer for validator".to_owned(), + ))), + IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator { + peer_id: network_service.local_peer_id(), + collator_pair, + request_receiver_v1: collation_req_v1_receiver, + request_receiver_v2: collation_req_v2_receiver, + metrics: Metrics::register(registry)?, + }, + IsParachainNode::FullNode => ProtocolSide::None, + }; + CollatorProtocolSubsystem::new(side) + }) + .provisioner(DummySubsystem) + .runtime_api(RuntimeApiSubsystem::new( + runtime_client.clone(), + Metrics::register(registry)?, + spawner.clone(), + )) + .statement_distribution(DummySubsystem) + .approval_distribution(DummySubsystem) + .approval_voting(DummySubsystem) + .gossip_support(DummySubsystem) + .dispute_coordinator(DummySubsystem) + .dispute_distribution(DummySubsystem) + .chain_selection(DummySubsystem) + .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) + .activation_external_listeners(Default::default()) + .span_per_active_leaf(Default::default()) + .active_leaves(Default::default()) + .supports_parachains(runtime_client) + .metrics(Metrics::register(registry)?) + .spawner(spawner); + + let builder = if let Some(capacity) = overseer_message_channel_capacity_override { + builder.message_channel_capacity(capacity) + } else { + builder + }; + Ok(builder) } /// Trait for the `fn` generating the overseer. -/// -/// Default behavior is to create an unmodified overseer, as `RealOverseerGen` -/// would do. pub trait OverseerGen { /// Overwrite the full generation of the overseer, including the subsystems. fn generate( &self, connector: OverseerConnector, args: OverseerGenArgs, - ) -> Result< - (Overseer, Arc>>, OverseerHandle), - Error, - > + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, - Spawner: 'static + SpawnNamed + Clone + Unpin, - { - let gen = RealOverseerGen; - RealOverseerGen::generate::(&gen, connector, args) - } + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin; + // It would be nice to make `create_subsystems` part of this trait, // but the amount of generic arguments that would be required as // as consequence make this rather annoying to implement and use. } /// The regular set of subsystems. -pub struct RealOverseerGen; +pub struct ValidatorOverseerGen; + +impl OverseerGen for ValidatorOverseerGen { + fn generate( + &self, + connector: OverseerConnector, + args: OverseerGenArgs, + ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> + where + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin, + { + let ext_args = ext_args.ok_or(Error::Overseer(SubsystemError::Context( + "create validator overseer as mandatory extended arguments were not provided" + .to_owned(), + )))?; + validator_overseer_builder(args, ext_args)? + .build_with_connector(connector) + .map_err(|e| e.into()) + } +} + +/// Reduced set of subsystems, to use in collator and collator's full node. +pub struct CollatorOverseerGen; -impl OverseerGen for RealOverseerGen { +impl OverseerGen for CollatorOverseerGen { fn generate( &self, connector: OverseerConnector, args: OverseerGenArgs, - ) -> Result< - (Overseer, Arc>>, OverseerHandle), - Error, - > + _ext_args: Option, + ) -> Result<(Overseer, Arc>, OverseerHandle), Error> where - RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, - RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { - prepared_overseer_builder(args)? + collator_overseer_builder(args)? .build_with_connector(connector) .map_err(|e| e.into()) } diff --git a/polkadot/node/service/src/parachains_db/mod.rs b/polkadot/node/service/src/parachains_db/mod.rs index 519afbe0ccd13b088ae7a0efb5aa3c7fa3f16905..59af30dceeb90f47ca6a4263e2c1428fc74ac436 100644 --- a/polkadot/node/service/src/parachains_db/mod.rs +++ b/polkadot/node/service/src/parachains_db/mod.rs @@ -41,7 +41,12 @@ pub(crate) mod columns { pub const COL_SESSION_WINDOW_DATA: u32 = 5; } + // Version 4 only changed structures in approval voting, so we can re-export the v4 definitions. pub mod v3 { + pub use super::v4::{NUM_COLUMNS, ORDERED_COL}; + } + + pub mod v4 { pub const NUM_COLUMNS: u32 = 5; pub const COL_AVAILABILITY_DATA: u32 = 0; pub const COL_AVAILABILITY_META: u32 = 1; @@ -73,14 +78,14 @@ pub struct ColumnsConfig { /// The real columns used by the parachains DB. #[cfg(any(test, feature = "full-node"))] pub const REAL_COLUMNS: ColumnsConfig = ColumnsConfig { - col_availability_data: columns::v3::COL_AVAILABILITY_DATA, - col_availability_meta: columns::v3::COL_AVAILABILITY_META, - col_approval_data: columns::v3::COL_APPROVAL_DATA, - col_chain_selection_data: columns::v3::COL_CHAIN_SELECTION_DATA, - col_dispute_coordinator_data: columns::v3::COL_DISPUTE_COORDINATOR_DATA, + col_availability_data: columns::v4::COL_AVAILABILITY_DATA, + col_availability_meta: columns::v4::COL_AVAILABILITY_META, + col_approval_data: columns::v4::COL_APPROVAL_DATA, + col_chain_selection_data: columns::v4::COL_CHAIN_SELECTION_DATA, + col_dispute_coordinator_data: columns::v4::COL_DISPUTE_COORDINATOR_DATA, }; -#[derive(PartialEq)] +#[derive(PartialEq, Copy, Clone)] pub(crate) enum DatabaseKind { ParityDB, RocksDB, @@ -125,28 +130,28 @@ pub fn open_creating_rocksdb( let path = root.join("parachains").join("db"); - let mut db_config = DatabaseConfig::with_columns(columns::v3::NUM_COLUMNS); + let mut db_config = DatabaseConfig::with_columns(columns::v4::NUM_COLUMNS); let _ = db_config .memory_budget - .insert(columns::v3::COL_AVAILABILITY_DATA, cache_sizes.availability_data); + .insert(columns::v4::COL_AVAILABILITY_DATA, cache_sizes.availability_data); let _ = db_config .memory_budget - .insert(columns::v3::COL_AVAILABILITY_META, cache_sizes.availability_meta); + .insert(columns::v4::COL_AVAILABILITY_META, cache_sizes.availability_meta); let _ = db_config .memory_budget - .insert(columns::v3::COL_APPROVAL_DATA, cache_sizes.approval_data); + .insert(columns::v4::COL_APPROVAL_DATA, cache_sizes.approval_data); let path_str = path .to_str() .ok_or_else(|| other_io_error(format!("Bad database path: {:?}", path)))?; std::fs::create_dir_all(&path_str)?; - upgrade::try_upgrade_db(&path, DatabaseKind::RocksDB)?; + upgrade::try_upgrade_db(&path, DatabaseKind::RocksDB, upgrade::CURRENT_VERSION)?; let db = Database::open(&db_config, &path_str)?; let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new( db, - columns::v3::ORDERED_COL, + columns::v4::ORDERED_COL, ); Ok(Arc::new(db)) @@ -164,14 +169,14 @@ pub fn open_creating_paritydb( .ok_or_else(|| other_io_error(format!("Bad database path: {:?}", path)))?; std::fs::create_dir_all(&path_str)?; - upgrade::try_upgrade_db(&path, DatabaseKind::ParityDB)?; + upgrade::try_upgrade_db(&path, DatabaseKind::ParityDB, upgrade::CURRENT_VERSION)?; let db = parity_db::Db::open_or_create(&upgrade::paritydb_version_3_config(&path)) .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; let db = polkadot_node_subsystem_util::database::paritydb_impl::DbAdapter::new( db, - columns::v3::ORDERED_COL, + columns::v4::ORDERED_COL, ); Ok(Arc::new(db)) } diff --git a/polkadot/node/service/src/parachains_db/upgrade.rs b/polkadot/node/service/src/parachains_db/upgrade.rs index 54ef97afd71c6ffacb643a08e59709838974ae3e..2eceb391b15c278825b243fdb8dc21e8ef540390 100644 --- a/polkadot/node/service/src/parachains_db/upgrade.rs +++ b/polkadot/node/service/src/parachains_db/upgrade.rs @@ -20,15 +20,27 @@ use std::{ fs, io, path::{Path, PathBuf}, str::FromStr, + sync::Arc, }; +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; /// Version file name. const VERSION_FILE_NAME: &'static str = "parachain_db_version"; /// Current db version. -const CURRENT_VERSION: Version = 3; +/// Version 4 changes approval db format for `OurAssignment`. +/// 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 { @@ -38,6 +50,10 @@ pub enum Error { CorruptedVersionFile, #[error("Parachains DB has a future version (expected {current:?}, found {got:?})")] FutureVersion { current: Version, got: Version }, + #[error("Parachain DB migration failed")] + MigrationFailed, + #[error("Parachain DB migration would take forever")] + MigrationLoop, } impl From for io::Error { @@ -49,10 +65,42 @@ impl From for io::Error { } } -/// Try upgrading parachain's database to the current version. -pub(crate) fn try_upgrade_db(db_path: &Path, db_kind: DatabaseKind) -> Result<(), Error> { +/// Try upgrading parachain's database to a target version. +pub(crate) fn try_upgrade_db( + db_path: &Path, + db_kind: DatabaseKind, + target_version: Version, +) -> Result<(), Error> { + // Ensure we don't loop forever below because of a bug. + const MAX_MIGRATIONS: u32 = 30; + + #[cfg(test)] + remove_file_lock(&db_path); + + // Loop migrations until we reach the target version. + for _ in 0..MAX_MIGRATIONS { + let version = try_upgrade_db_to_next_version(db_path, db_kind)?; + + #[cfg(test)] + remove_file_lock(&db_path); + + if version == target_version { + return Ok(()) + } + } + + Err(Error::MigrationLoop) +} + +/// Try upgrading parachain's database to the next version. +/// If successful, it returns the current version. +pub(crate) fn try_upgrade_db_to_next_version( + db_path: &Path, + db_kind: DatabaseKind, +) -> Result { let is_empty = db_path.read_dir().map_or(true, |mut d| d.next().is_none()); - if !is_empty { + + let new_version = if !is_empty { match get_db_version(db_path)? { // 0 -> 1 migration Some(0) => migrate_from_version_0_to_1(db_path, db_kind)?, @@ -60,21 +108,27 @@ pub(crate) fn try_upgrade_db(db_path: &Path, db_kind: DatabaseKind) -> Result<() Some(1) => migrate_from_version_1_to_2(db_path, db_kind)?, // 2 -> 3 migration Some(2) => migrate_from_version_2_to_3(db_path, db_kind)?, + // 3 -> 4 migration + 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) => (), + Some(CURRENT_VERSION) => CURRENT_VERSION, // This is an arbitrary future version, we don't handle it. Some(v) => return Err(Error::FutureVersion { current: CURRENT_VERSION, got: v }), // No version file. For `RocksDB` we dont need to do anything. - None if db_kind == DatabaseKind::RocksDB => (), + None if db_kind == DatabaseKind::RocksDB => CURRENT_VERSION, // No version file. `ParityDB` did not previously have a version defined. // We handle this as a `0 -> 1` migration. None if db_kind == DatabaseKind::ParityDB => migrate_from_version_0_to_1(db_path, db_kind)?, None => unreachable!(), } - } + } else { + CURRENT_VERSION + }; - update_version(db_path) + update_version(db_path, new_version)?; + Ok(new_version) } /// Reads current database version from the file at given path. @@ -91,9 +145,9 @@ fn get_db_version(path: &Path) -> Result, Error> { /// Writes current database version to the file. /// Creates a new file if the version file does not exist yet. -fn update_version(path: &Path) -> Result<(), Error> { +fn update_version(path: &Path, new_version: Version) -> Result<(), Error> { fs::create_dir_all(path)?; - fs::write(version_file_path(path), CURRENT_VERSION.to_string()).map_err(Into::into) + fs::write(version_file_path(path), new_version.to_string()).map_err(Into::into) } /// Returns the version file path. @@ -103,7 +157,7 @@ fn version_file_path(path: &Path) -> PathBuf { file_path } -fn migrate_from_version_0_to_1(path: &Path, db_kind: DatabaseKind) -> Result<(), Error> { +fn migrate_from_version_0_to_1(path: &Path, db_kind: DatabaseKind) -> Result { gum::info!(target: LOG_TARGET, "Migrating parachains db from version 0 to version 1 ..."); match db_kind { @@ -116,7 +170,7 @@ fn migrate_from_version_0_to_1(path: &Path, db_kind: DatabaseKind) -> Result<(), }) } -fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result<(), Error> { +fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result { gum::info!(target: LOG_TARGET, "Migrating parachains db from version 1 to version 2 ..."); match db_kind { @@ -129,7 +183,55 @@ fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result<(), }) } -fn migrate_from_version_2_to_3(path: &Path, db_kind: DatabaseKind) -> Result<(), Error> { +// Migrade approval voting database. +// In 4 `OurAssignment` has been changed to support the v2 assignments. +// In 5, `BlockEntry` has been changed to store the number of delayed approvals. +// As these are backwards compatible, we'll convert the old entries in the new format. +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 ..."); + + let approval_db_config = + ApprovalDbConfig { col_approval_data: super::REAL_COLUMNS.col_approval_data }; + + let _result = match db_kind { + DatabaseKind::ParityDB => { + let db = ParityDbAdapter::new( + parity_db::Db::open(&paritydb_version_3_config(path)) + .map_err(|e| other_io_error(format!("Error opening db {:?}", e)))?, + super::columns::v3::ORDERED_COL, + ); + + migration_function(Arc::new(db), approval_db_config) + .map_err(|_| Error::MigrationFailed)?; + }, + DatabaseKind::RocksDB => { + let db_path = path + .to_str() + .ok_or_else(|| super::other_io_error("Invalid database path".into()))?; + let db_cfg = + kvdb_rocksdb::DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS); + let db = RocksDbAdapter::new( + kvdb_rocksdb::Database::open(&db_cfg, db_path)?, + &super::columns::v3::ORDERED_COL, + ); + + migration_function(Arc::new(db), approval_db_config) + .map_err(|_| Error::MigrationFailed)?; + }, + }; + + gum::info!(target: LOG_TARGET, "Migration complete! "); + Ok(CURRENT_VERSION) +} + +fn migrate_from_version_2_to_3(path: &Path, db_kind: DatabaseKind) -> Result { gum::info!(target: LOG_TARGET, "Migrating parachains db from version 2 to version 3 ..."); match db_kind { DatabaseKind::ParityDB => paritydb_migrate_from_version_2_to_3(path), @@ -143,7 +245,7 @@ fn migrate_from_version_2_to_3(path: &Path, db_kind: DatabaseKind) -> Result<(), /// Migration from version 0 to version 1: /// * the number of columns has changed from 3 to 5; -fn rocksdb_migrate_from_version_0_to_1(path: &Path) -> Result<(), Error> { +fn rocksdb_migrate_from_version_0_to_1(path: &Path) -> Result { use kvdb_rocksdb::{Database, DatabaseConfig}; let db_path = path @@ -155,12 +257,12 @@ fn rocksdb_migrate_from_version_0_to_1(path: &Path) -> Result<(), Error> { db.add_column()?; db.add_column()?; - Ok(()) + Ok(1) } /// Migration from version 1 to version 2: /// * the number of columns has changed from 5 to 6; -fn rocksdb_migrate_from_version_1_to_2(path: &Path) -> Result<(), Error> { +fn rocksdb_migrate_from_version_1_to_2(path: &Path) -> Result { use kvdb_rocksdb::{Database, DatabaseConfig}; let db_path = path @@ -171,10 +273,10 @@ fn rocksdb_migrate_from_version_1_to_2(path: &Path) -> Result<(), Error> { db.add_column()?; - Ok(()) + Ok(2) } -fn rocksdb_migrate_from_version_2_to_3(path: &Path) -> Result<(), Error> { +fn rocksdb_migrate_from_version_2_to_3(path: &Path) -> Result { use kvdb_rocksdb::{Database, DatabaseConfig}; let db_path = path @@ -185,7 +287,7 @@ fn rocksdb_migrate_from_version_2_to_3(path: &Path) -> Result<(), Error> { db.remove_last_column()?; - Ok(()) + Ok(3) } // This currently clears columns which had their configs altered between versions. @@ -249,7 +351,7 @@ fn paritydb_fix_columns( pub(crate) fn paritydb_version_1_config(path: &Path) -> parity_db::Options { let mut options = parity_db::Options::with_columns(&path, super::columns::v1::NUM_COLUMNS as u8); - for i in columns::v3::ORDERED_COL { + for i in columns::v4::ORDERED_COL { options.columns[*i as usize].btree_index = true; } @@ -260,7 +362,7 @@ pub(crate) fn paritydb_version_1_config(path: &Path) -> parity_db::Options { pub(crate) fn paritydb_version_2_config(path: &Path) -> parity_db::Options { let mut options = parity_db::Options::with_columns(&path, super::columns::v2::NUM_COLUMNS as u8); - for i in columns::v3::ORDERED_COL { + for i in columns::v4::ORDERED_COL { options.columns[*i as usize].btree_index = true; } @@ -278,48 +380,166 @@ pub(crate) fn paritydb_version_3_config(path: &Path) -> parity_db::Options { options } +/// Database configuration for version 0. This is useful just for testing. +#[cfg(test)] +pub(crate) fn paritydb_version_0_config(path: &Path) -> parity_db::Options { + let mut options = + parity_db::Options::with_columns(&path, super::columns::v0::NUM_COLUMNS as u8); + options.columns[super::columns::v4::COL_AVAILABILITY_META as usize].btree_index = true; + + options +} + /// Migration from version 0 to version 1. /// Cases covered: /// - upgrading from v0.9.23 or earlier -> the `dispute coordinator column` was changed /// - upgrading from v0.9.24+ -> this is a no op assuming the DB has been manually fixed as per /// release notes -fn paritydb_migrate_from_version_0_to_1(path: &Path) -> Result<(), Error> { +fn paritydb_migrate_from_version_0_to_1(path: &Path) -> Result { // Delete the `dispute coordinator` column if needed (if column configuration is changed). paritydb_fix_columns( path, paritydb_version_1_config(path), - vec![super::columns::v3::COL_DISPUTE_COORDINATOR_DATA], + vec![super::columns::v4::COL_DISPUTE_COORDINATOR_DATA], )?; - Ok(()) + Ok(1) } /// Migration from version 1 to version 2: /// - add a new column for session information storage -fn paritydb_migrate_from_version_1_to_2(path: &Path) -> Result<(), Error> { +fn paritydb_migrate_from_version_1_to_2(path: &Path) -> Result { let mut options = paritydb_version_1_config(path); // Adds the session info column. parity_db::Db::add_column(&mut options, Default::default()) .map_err(|e| other_io_error(format!("Error adding column {:?}", e)))?; - Ok(()) + Ok(2) } /// Migration from version 2 to version 3: /// - drop the column used by `RollingSessionWindow` -fn paritydb_migrate_from_version_2_to_3(path: &Path) -> Result<(), Error> { +fn paritydb_migrate_from_version_2_to_3(path: &Path) -> Result { parity_db::Db::drop_last_column(&mut paritydb_version_2_config(path)) .map_err(|e| other_io_error(format!("Error removing COL_SESSION_WINDOW_DATA {:?}", e)))?; - Ok(()) + Ok(3) +} + +/// Remove the lock file. If file is locked, it will wait up to 1s. +#[cfg(test)] +pub fn remove_file_lock(path: &std::path::Path) { + use std::{io::ErrorKind, thread::sleep, time::Duration}; + + let mut lock_path = std::path::PathBuf::from(path); + lock_path.push("lock"); + + for _ in 0..10 { + let result = std::fs::remove_file(lock_path.as_path()); + match result { + Err(error) => match error.kind() { + ErrorKind::WouldBlock => { + sleep(Duration::from_millis(100)); + continue + }, + _ => return, + }, + Ok(_) => {}, + } + } + + unreachable!("Database is locked, waited 1s for lock file: {:?}", lock_path); } #[cfg(test)] mod tests { use super::{ - columns::{v2::COL_SESSION_WINDOW_DATA, v3::*}, + columns::{v2::COL_SESSION_WINDOW_DATA, v4::*}, *, }; + use kvdb_rocksdb::{Database, DatabaseConfig}; + use polkadot_node_core_approval_voting::approval_db::{ + v2::migration_helpers::v1_fill_test_data, + v3::migration_helpers::{v1_to_latest_sanity_check, v2_fill_test_data}, + }; + use polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter; + use test_helpers::dummy_candidate_receipt; + + #[test] + fn test_paritydb_migrate_0_to_1() { + use parity_db::Db; + + let db_dir = tempfile::tempdir().unwrap(); + let path = db_dir.path(); + { + let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap(); + + db.commit(vec![( + COL_AVAILABILITY_META as u8, + b"5678".to_vec(), + Some(b"somevalue".to_vec()), + )]) + .unwrap(); + } + + try_upgrade_db(&path, DatabaseKind::ParityDB, 1).unwrap(); + + let db = Db::open(&paritydb_version_1_config(&path)).unwrap(); + assert_eq!( + db.get(COL_AVAILABILITY_META as u8, b"5678").unwrap(), + Some("somevalue".as_bytes().to_vec()) + ); + } + + #[test] + fn test_paritydb_migrate_1_to_2() { + use parity_db::Db; + + let db_dir = tempfile::tempdir().unwrap(); + let path = db_dir.path(); + + // We need to properly set db version for upgrade to work. + fs::write(version_file_path(path), "1").expect("Failed to write DB version"); + + { + let db = Db::open_or_create(&paritydb_version_1_config(&path)).unwrap(); + + // Write some dummy data + db.commit(vec![( + COL_DISPUTE_COORDINATOR_DATA as u8, + b"1234".to_vec(), + Some(b"somevalue".to_vec()), + )]) + .unwrap(); + + assert_eq!(db.num_columns(), columns::v1::NUM_COLUMNS as u8); + } + + try_upgrade_db(&path, DatabaseKind::ParityDB, 2).unwrap(); + + let db = Db::open(&paritydb_version_2_config(&path)).unwrap(); + + assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8); + + assert_eq!( + db.get(COL_DISPUTE_COORDINATOR_DATA as u8, b"1234").unwrap(), + Some("somevalue".as_bytes().to_vec()) + ); + + // Test we can write the new column. + db.commit(vec![( + COL_SESSION_WINDOW_DATA as u8, + b"1337".to_vec(), + Some(b"0xdeadb00b".to_vec()), + )]) + .unwrap(); + + // Read back data from new column. + assert_eq!( + db.get(COL_SESSION_WINDOW_DATA as u8, b"1337").unwrap(), + Some("0xdeadb00b".as_bytes().to_vec()) + ); + } #[test] fn test_rocksdb_migrate_1_to_2() { @@ -338,7 +558,7 @@ mod tests { // We need to properly set db version for upgrade to work. fs::write(version_file_path(db_dir.path()), "1").expect("Failed to write DB version"); { - let db = DbAdapter::new(db, columns::v3::ORDERED_COL); + let db = DbAdapter::new(db, columns::v4::ORDERED_COL); db.write(DBTransaction { ops: vec![DBOp::Insert { col: COL_DISPUTE_COORDINATOR_DATA, @@ -349,14 +569,14 @@ mod tests { .unwrap(); } - try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB).unwrap(); + try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 2).unwrap(); let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS); let db = Database::open(&db_cfg, db_path).unwrap(); assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS); - let db = DbAdapter::new(db, columns::v3::ORDERED_COL); + let db = DbAdapter::new(db, columns::v4::ORDERED_COL); assert_eq!( db.get(COL_DISPUTE_COORDINATOR_DATA, b"1234").unwrap(), @@ -380,6 +600,137 @@ mod tests { ); } + #[test] + fn test_migrate_3_to_5() { + let db_dir = tempfile::tempdir().unwrap(); + let db_path = db_dir.path().to_str().unwrap(); + let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS); + + let approval_cfg = ApprovalDbConfig { + col_approval_data: crate::parachains_db::REAL_COLUMNS.col_approval_data, + }; + + // We need to properly set db version for upgrade to work. + fs::write(version_file_path(db_dir.path()), "3").expect("Failed to write DB version"); + let expected_candidates = { + let db = Database::open(&db_cfg, db_path).unwrap(); + assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32); + let db = DbAdapter::new(db, columns::v3::ORDERED_COL); + // Fill the approval voting column with test data. + v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt) + .unwrap() + }; + + try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap(); + + let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS); + let db = Database::open(&db_cfg, db_path).unwrap(); + let db = DbAdapter::new(db, columns::v4::ORDERED_COL); + + v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates) + .unwrap(); + } + + #[test] + fn test_migrate_4_to_5() { + let db_dir = tempfile::tempdir().unwrap(); + let db_path = db_dir.path().to_str().unwrap(); + let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS); + + let approval_cfg = ApprovalDbConfig { + col_approval_data: crate::parachains_db::REAL_COLUMNS.col_approval_data, + }; + + // We need to properly set db version for upgrade to work. + fs::write(version_file_path(db_dir.path()), "4").expect("Failed to write DB version"); + let expected_candidates = { + let db = Database::open(&db_cfg, db_path).unwrap(); + assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32); + let db = DbAdapter::new(db, columns::v3::ORDERED_COL); + // Fill the approval voting column with test data. + v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt) + .unwrap() + }; + + try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap(); + + let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS); + let db = Database::open(&db_cfg, db_path).unwrap(); + let db = DbAdapter::new(db, columns::v4::ORDERED_COL); + + v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates) + .unwrap(); + } + + #[test] + fn test_rocksdb_migrate_0_to_5() { + use kvdb_rocksdb::{Database, DatabaseConfig}; + + let db_dir = tempfile::tempdir().unwrap(); + let db_path = db_dir.path().to_str().unwrap(); + + fs::write(version_file_path(db_dir.path()), "0").expect("Failed to write DB version"); + try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap(); + + let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS); + let db = Database::open(&db_cfg, db_path).unwrap(); + + assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS); + } + + #[test] + fn test_paritydb_migrate_0_to_5() { + use parity_db::Db; + + let db_dir = tempfile::tempdir().unwrap(); + let path = db_dir.path(); + + // We need to properly set db version for upgrade to work. + fs::write(version_file_path(path), "0").expect("Failed to write DB version"); + + { + let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap(); + assert_eq!(db.num_columns(), columns::v0::NUM_COLUMNS as u8); + } + + try_upgrade_db(&path, DatabaseKind::ParityDB, 5).unwrap(); + + let db = Db::open(&paritydb_version_3_config(&path)).unwrap(); + assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS as u8); + } + + #[test] + fn test_paritydb_migrate_2_to_3() { + use parity_db::Db; + + let db_dir = tempfile::tempdir().unwrap(); + let path = db_dir.path(); + let test_key = b"1337"; + + // We need to properly set db version for upgrade to work. + fs::write(version_file_path(path), "2").expect("Failed to write DB version"); + + { + let db = Db::open_or_create(&paritydb_version_2_config(&path)).unwrap(); + + // Write some dummy data + db.commit(vec![( + COL_SESSION_WINDOW_DATA as u8, + test_key.to_vec(), + Some(b"0xdeadb00b".to_vec()), + )]) + .unwrap(); + + assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8); + } + + try_upgrade_db(&path, DatabaseKind::ParityDB, 3).unwrap(); + + let db = Db::open(&paritydb_version_3_config(&path)).unwrap(); + + assert_eq!(db.num_columns(), columns::v3::NUM_COLUMNS as u8); + } + #[test] fn test_rocksdb_migrate_2_to_3() { use kvdb_rocksdb::{Database, DatabaseConfig}; @@ -387,6 +738,7 @@ mod tests { let db_dir = tempfile::tempdir().unwrap(); let db_path = db_dir.path().to_str().unwrap(); let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS); + { let db = Database::open(&db_cfg, db_path).unwrap(); assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS as u32); @@ -395,7 +747,7 @@ mod tests { // We need to properly set db version for upgrade to work. fs::write(version_file_path(db_dir.path()), "2").expect("Failed to write DB version"); - try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB).unwrap(); + try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 3).unwrap(); let db_cfg = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS); let db = Database::open(&db_cfg, db_path).unwrap(); diff --git a/polkadot/node/service/src/tests.rs b/polkadot/node/service/src/tests.rs index 86119662d9bc08e7ed33999e1b7c78cd0749e154..26c8083185d84e9f18c27bfbc00779fce6a0b91e 100644 --- a/polkadot/node/service/src/tests.rs +++ b/polkadot/node/service/src/tests.rs @@ -17,7 +17,7 @@ use super::{relay_chain_selection::*, *}; use futures::channel::oneshot::Receiver; -use polkadot_node_primitives::approval::VrfSignature; +use polkadot_node_primitives::approval::v2::VrfSignature; use polkadot_node_subsystem::messages::{AllMessages, BlockDescription}; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; diff --git a/polkadot/node/service/src/workers.rs b/polkadot/node/service/src/workers.rs index 5f7cc1c2ed49acf0bf170843033f2c5cdbc73701..b35bb8302fdc4405f542fd0cc119066653c76cc9 100644 --- a/polkadot/node/service/src/workers.rs +++ b/polkadot/node/service/src/workers.rs @@ -18,7 +18,7 @@ use super::Error; use is_executable::IsExecutable; -use std::{path::PathBuf, process::Command}; +use std::path::PathBuf; #[cfg(test)] use std::sync::{Mutex, OnceLock}; @@ -75,11 +75,7 @@ pub fn determine_workers_paths( // Do the version check. if let Some(node_version) = node_version { - let worker_version = Command::new(&prep_worker_path).args(["--version"]).output()?.stdout; - let worker_version = std::str::from_utf8(&worker_version) - .expect("version is printed as a string; qed") - .trim() - .to_string(); + let worker_version = polkadot_node_core_pvf::get_worker_version(&prep_worker_path)?; if worker_version != node_version { return Err(Error::WorkerBinaryVersionMismatch { worker_version, @@ -87,11 +83,8 @@ pub fn determine_workers_paths( worker_path: prep_worker_path, }) } - let worker_version = Command::new(&exec_worker_path).args(["--version"]).output()?.stdout; - let worker_version = std::str::from_utf8(&worker_version) - .expect("version is printed as a string; qed") - .trim() - .to_string(); + + let worker_version = polkadot_node_core_pvf::get_worker_version(&exec_worker_path)?; if worker_version != node_version { return Err(Error::WorkerBinaryVersionMismatch { worker_version, @@ -215,11 +208,11 @@ mod tests { use serial_test::serial; use std::{env::temp_dir, fs, os::unix::fs::PermissionsExt, path::Path}; - const NODE_VERSION: &'static str = "v0.1.2"; + const TEST_NODE_VERSION: &'static str = "v0.1.2"; /// Write a dummy executable to the path which satisfies the version check. fn write_worker_exe(path: impl AsRef) -> Result<(), Box> { - let program = get_program(NODE_VERSION); + let program = get_program(TEST_NODE_VERSION); fs::write(&path, program)?; Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?) } @@ -287,7 +280,7 @@ echo {} // Try with provided workers path that has missing binaries. assert_matches!( - determine_workers_paths(Some(given_workers_path.clone()), None, Some(NODE_VERSION.into())), + determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: Some(p1), current_exe_path: p2, workers_names: None }) if p1 == given_workers_path && p2 == exe_path ); @@ -299,7 +292,7 @@ echo {} write_worker_exe(&execute_worker_path)?; fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o644))?; assert_matches!( - determine_workers_paths(Some(given_workers_path.clone()), None, Some(NODE_VERSION.into())), + determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())), Err(Error::InvalidWorkerBinaries { prep_worker_path: p1, exec_worker_path: p2 }) if p1 == prepare_worker_path && p2 == execute_worker_path ); @@ -307,15 +300,15 @@ echo {} fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o744))?; fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o744))?; assert_matches!( - determine_workers_paths(Some(given_workers_path), None, Some(NODE_VERSION.into())), + determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())), Ok((p1, p2)) if p1 == prepare_worker_path && p2 == execute_worker_path ); // Try with valid provided workers path that is a binary file. - let given_workers_path = tempdir.join("usr/local/bin/puppet-worker"); + let given_workers_path = tempdir.join("usr/local/bin/test-worker"); write_worker_exe(&given_workers_path)?; assert_matches!( - determine_workers_paths(Some(given_workers_path.clone()), None, Some(NODE_VERSION.into())), + determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())), Ok((p1, p2)) if p1 == given_workers_path && p2 == given_workers_path ); @@ -330,7 +323,7 @@ echo {} with_temp_dir_structure(|tempdir, exe_path| { // Try with both binaries missing. assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path ); @@ -338,7 +331,7 @@ echo {} let prepare_worker_path = tempdir.join("usr/bin/polkadot-prepare-worker"); write_worker_exe(&prepare_worker_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path ); @@ -347,7 +340,7 @@ echo {} let execute_worker_path = tempdir.join("usr/bin/polkadot-execute-worker"); write_worker_exe(&execute_worker_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path ); @@ -356,7 +349,7 @@ echo {} let prepare_worker_path = tempdir.join("usr/lib/polkadot/polkadot-prepare-worker"); write_worker_exe(&prepare_worker_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path ); @@ -365,7 +358,7 @@ echo {} let execute_worker_path = tempdir.join("usr/lib/polkadot/polkadot-execute-worker"); write_worker_exe(execute_worker_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path ); @@ -440,8 +433,8 @@ echo {} write_worker_exe_invalid_version(&prepare_worker_bin_path, bad_version)?; write_worker_exe(&execute_worker_bin_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), - Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == NODE_VERSION && p == prepare_worker_bin_path + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), + Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_bin_path ); // Workers at lib location return bad version. @@ -452,8 +445,8 @@ echo {} write_worker_exe(&prepare_worker_lib_path)?; write_worker_exe_invalid_version(&execute_worker_lib_path, bad_version)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), - Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == NODE_VERSION && p == execute_worker_lib_path + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), + Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == execute_worker_lib_path ); // Workers at provided workers location return bad version. @@ -463,16 +456,16 @@ echo {} write_worker_exe_invalid_version(&prepare_worker_path, bad_version)?; write_worker_exe_invalid_version(&execute_worker_path, bad_version)?; assert_matches!( - determine_workers_paths(Some(given_workers_path), None, Some(NODE_VERSION.into())), - Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == NODE_VERSION && p == prepare_worker_path + determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())), + Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_path ); // Given worker binary returns bad version. - let given_workers_path = tempdir.join("usr/local/bin/puppet-worker"); + let given_workers_path = tempdir.join("usr/local/bin/test-worker"); write_worker_exe_invalid_version(&given_workers_path, bad_version)?; assert_matches!( - determine_workers_paths(Some(given_workers_path.clone()), None, Some(NODE_VERSION.into())), - Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == NODE_VERSION && p == given_workers_path + determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())), + Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == given_workers_path ); Ok(()) @@ -492,7 +485,7 @@ echo {} write_worker_exe(&execute_worker_bin_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Ok((p1, p2)) if p1 == prepare_worker_bin_path && p2 == execute_worker_bin_path ); @@ -509,7 +502,7 @@ echo {} write_worker_exe(&execute_worker_lib_path)?; assert_matches!( - determine_workers_paths(None, None, Some(NODE_VERSION.into())), + determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())), Ok((p1, p2)) if p1 == prepare_worker_lib_path && p2 == execute_worker_lib_path ); diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..71711ad0fbd07ff84b13bc6e1539dc2c4795ba07 --- /dev/null +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -0,0 +1,91 @@ +[package] +name = "polkadot-subsystem-bench" +description = "Subsystem performance benchmark client" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +readme = "README.md" +publish = false + +[lib] +name = "polkadot_subsystem_bench" +path = "src/lib/lib.rs" + +[[bin]] +name = "subsystem-bench" +path = "src/cli/subsystem-bench.rs" + +# Prevent rustdoc error. Already documented from top-level Cargo.toml. +doc = false + +[dependencies] +polkadot-node-subsystem = { path = "../subsystem" } +polkadot-node-subsystem-util = { path = "../subsystem-util" } +polkadot-node-subsystem-types = { path = "../subsystem-types" } +polkadot-node-primitives = { path = "../primitives" } +polkadot-primitives = { path = "../../primitives" } +polkadot-node-network-protocol = { path = "../network/protocol" } +polkadot-availability-recovery = { path = "../network/availability-recovery", features = ["subsystem-benchmarks"] } +polkadot-availability-distribution = { path = "../network/availability-distribution" } +polkadot-node-core-av-store = { path = "../core/av-store" } +polkadot-node-core-chain-api = { path = "../core/chain-api" } +polkadot-availability-bitfield-distribution = { path = "../network/bitfield-distribution" } +color-eyre = { version = "0.6.1", default-features = false } +polkadot-overseer = { path = "../overseer" } +colored = "2.0.4" +assert_matches = "1.5" +async-trait = "0.1.57" +sp-keystore = { path = "../../../substrate/primitives/keystore" } +sc-keystore = { path = "../../../substrate/client/keystore" } +sp-core = { path = "../../../substrate/primitives/core" } +clap = { version = "4.5.3", features = ["derive"] } +futures = "0.3.21" +futures-timer = "3.0.2" +bincode = "1.3.3" +sha1 = "0.10.6" +hex = "0.4.3" +gum = { package = "tracing-gum", path = "../gum" } +polkadot-erasure-coding = { package = "polkadot-erasure-coding", path = "../../erasure-coding" } +log = { workspace = true, default-features = true } +env_logger = "0.9.0" +rand = "0.8.5" +# `rand` only supports uniform distribution, we need normal distribution for latency. +rand_distr = "0.4.3" +bitvec = "1.0.1" +kvdb-memorydb = "0.13.0" + +parity-scale-codec = { version = "3.6.1", features = ["derive", "std"] } +tokio = "1.24.2" +clap-num = "1.0.2" +polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } +sp-keyring = { path = "../../../substrate/primitives/keyring" } +sp-application-crypto = { path = "../../../substrate/primitives/application-crypto" } +sc-network = { path = "../../../substrate/client/network" } +sc-service = { path = "../../../substrate/client/service" } +sp-consensus = { path = "../../../substrate/primitives/consensus/common" } +polkadot-node-metrics = { path = "../metrics" } +itertools = "0.11.0" +polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } +prometheus_endpoint = { package = "substrate-prometheus-endpoint", path = "../../../substrate/utils/prometheus" } +prometheus = { version = "0.13.0", default-features = false } +serde = { workspace = true, default-features = true } +serde_yaml = { workspace = true } + +polkadot-node-core-approval-voting = { path = "../core/approval-voting" } +polkadot-approval-distribution = { path = "../network/approval-distribution" } +sp-consensus-babe = { path = "../../../substrate/primitives/consensus/babe" } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-timestamp = { path = "../../../substrate/primitives/timestamp" } + +schnorrkel = { version = "0.11.4", default-features = false } +# rand_core should match schnorrkel +rand_core = "0.6.2" +rand_chacha = { version = "0.3.1" } +paste = "1.0.14" +orchestra = { version = "0.3.5", default-features = false, features = ["futures_channel"] } +pyroscope = "0.5.7" +pyroscope_pprofrs = "0.2.7" + +[features] +default = [] diff --git a/polkadot/node/subsystem-bench/README.md b/polkadot/node/subsystem-bench/README.md new file mode 100644 index 0000000000000000000000000000000000000000..228fba41c46cce48a2a4bda96e869f6abcba1c44 --- /dev/null +++ b/polkadot/node/subsystem-bench/README.md @@ -0,0 +1,282 @@ +# Subsystem benchmark client + +Run parachain consensus stress and performance tests on your development machine or in CI. + +## Motivation + +The parachain consensus node implementation spans across many modules which we call subsystems. Each subsystem is +responsible for a small part of logic of the parachain consensus pipeline, but in general the most load and +performance issues are localized in just a few core subsystems like `availability-recovery`, `approval-voting` or +`dispute-coordinator`. In the absence of such a tool, we would run large test nets to load/stress test these parts of +the system. Setting up and making sense of the amount of data produced by such a large test is very expensive, hard +to orchestrate and is a huge development time sink. + +This tool aims to solve the problem by making it easy to: + +- set up and run core subsystem load tests locally on your development machine +- iterate and conclude faster when benchmarking new optimizations or comparing implementations +- automate and keep track of performance regressions in CI runs +- simulate various networking topologies, bandwidth and connectivity issues + +## Test environment setup + +`cargo build --profile=testnet --bin subsystem-bench -p polkadot-subsystem-bench` + +The output binary will be placed in `target/testnet/subsystem-bench`. + +### Test metrics + +Subsystem, CPU usage and network metrics are exposed via a prometheus endpoint during the test execution. +A small subset of these collected metrics are displayed in the CLI, but for an in depth analysis of the test results, +a local Grafana/Prometheus stack is needed. + +### Run Prometheus, Pyroscope and Graphana in Docker + +If docker is not usable, then follow the next sections to manually install Prometheus, Pyroscope and Graphana +on your machine. + +```bash +cd polkadot/node/subsystem-bench/docker +docker compose up +``` + +### Install Prometheus + +Please follow the [official installation guide](https://prometheus.io/docs/prometheus/latest/installation/) for your +platform/OS. + +After successfully installing and starting up Prometheus, we need to alter it's configuration such that it +will scrape the benchmark prometheus endpoint `127.0.0.1:9999`. Please check the prometheus official documentation +regarding the location of `prometheus.yml`. On MacOS for example the full path `/opt/homebrew/etc/prometheus.yml` + +prometheus.yml: + +``` +global: + scrape_interval: 5s + +scrape_configs: + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + - job_name: "subsystem-bench" + scrape_interval: 0s500ms + static_configs: + - targets: ['localhost:9999'] +``` + +To complete this step restart Prometheus server such that it picks up the new configuration. + +### Install Pyroscope + +To collect CPU profiling data, you must be running the Pyroscope server. +Follow the [installation guide](https://grafana.com/docs/pyroscope/latest/get-started/) +relevant to your operating system. + +### Install Grafana + +Follow the [installation guide](https://grafana.com/docs/grafana/latest/setup-grafana/installation/) relevant +to your operating system. + +### Setup Grafana + +Once you have the installation up and running, configure the local Prometheus and Pyroscope (if needed) +as data sources by following these guides: + +- [Prometheus](https://grafana.com/docs/grafana/latest/datasources/prometheus/configure-prometheus-data-source/) +- [Pyroscope](https://grafana.com/docs/grafana/latest/datasources/grafana-pyroscope/) + +If you are running the servers in Docker, use the following URLs: + +- Prometheus `http://prometheus:9090/` +- Pyroscope `http://pyroscope:4040/` + +#### Import dashboards + +Follow [this guide](https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/#export-and-import-dashboards) +to import the dashboards from the repository `grafana` folder. + +### Standard test options + +``` +$ subsystem-bench --help +Usage: subsystem-bench [OPTIONS] + +Arguments: + Path to the test sequence configuration file + +Options: + --profile Enable CPU Profiling with Pyroscope + --pyroscope-url Pyroscope Server URL [default: http://localhost:4040] + --pyroscope-sample-rate Pyroscope Sample Rate [default: 113] + --cache-misses Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind must be in the PATH + -h, --help Print help +``` + +## How to run a test + +To run a test, you need to use a path to a test objective: + +``` +target/testnet/subsystem-bench polkadot/node/subsystem-bench/examples/availability_read.yaml +``` + +Note: test objectives may be wrapped up into a test sequence. +It is typically used to run a suite of tests like in this [example](examples/availability_read.yaml). + +### 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-generated payloads. For example the +`AvailabilityRecoveryMessage::RecoverAvailableData` message includes a `CandidateReceipt` which is generated before +the test is started. + +### Example run + +Let's run an availability read test which will recover availability for 200 cores with max PoV size on a 1000 +node validator network. + + + +``` +target/testnet/subsystem-bench polkadot/node/subsystem-bench/examples/availability_write.yaml +[2024-02-19T14:10:32.981Z INFO subsystem_bench] Sequence contains 1 step(s) +[2024-02-19T14:10:32.981Z INFO subsystem-bench::cli] Step 1/1 +[2024-02-19T14:10:32.981Z INFO subsystem-bench::cli] [objective = DataAvailabilityWrite] n_validators = 1000, n_cores = 200, pov_size = 5120 - 5120, connectivity = 75, latency = Some(PeerLatency { mean_latency_ms: 30, std_dev: 2.0 }) +[2024-02-19T14:10:32.982Z INFO subsystem-bench::availability] Generating template candidate index=0 pov_size=5242880 +[2024-02-19T14:10:33.106Z INFO subsystem-bench::availability] Created test environment. +[2024-02-19T14:10:33.106Z INFO subsystem-bench::availability] Pre-generating 600 candidates. +[2024-02-19T14:10:34.096Z INFO subsystem-bench::network] Initializing emulation for a 1000 peer network. +[2024-02-19T14:10:34.096Z INFO subsystem-bench::network] connectivity 75%, latency Some(PeerLatency { mean_latency_ms: 30, std_dev: 2.0 }) +[2024-02-19T14:10:34.098Z INFO subsystem-bench::network] Network created, connected validator count 749 +[2024-02-19T14:10:34.099Z INFO subsystem-bench::availability] Seeding availability store with candidates ... +[2024-02-19T14:10:34.100Z INFO substrate_prometheus_endpoint] 〽️ Prometheus exporter started at 127.0.0.1:9999 +[2024-02-19T14:10:34.387Z INFO subsystem-bench::availability] Done +[2024-02-19T14:10:34.387Z INFO subsystem-bench::availability] Current block #1 +[2024-02-19T14:10:34.389Z INFO subsystem-bench::availability] Waiting for all emulated peers to receive their chunk from us ... +[2024-02-19T14:10:34.625Z INFO subsystem-bench::availability] All chunks received in 237ms +[2024-02-19T14:10:34.626Z INFO polkadot_subsystem_bench::availability] Waiting for 749 bitfields to be received and processed +[2024-02-19T14:10:35.710Z INFO subsystem-bench::availability] All bitfields processed +[2024-02-19T14:10:35.710Z INFO subsystem-bench::availability] All work for block completed in 1322ms +[2024-02-19T14:10:35.710Z INFO subsystem-bench::availability] Current block #2 +[2024-02-19T14:10:35.712Z INFO subsystem-bench::availability] Waiting for all emulated peers to receive their chunk from us ... +[2024-02-19T14:10:35.947Z INFO subsystem-bench::availability] All chunks received in 236ms +[2024-02-19T14:10:35.947Z INFO polkadot_subsystem_bench::availability] Waiting for 749 bitfields to be received and processed +[2024-02-19T14:10:37.038Z INFO subsystem-bench::availability] All bitfields processed +[2024-02-19T14:10:37.038Z INFO subsystem-bench::availability] All work for block completed in 1328ms +[2024-02-19T14:10:37.039Z INFO subsystem-bench::availability] Current block #3 +[2024-02-19T14:10:37.040Z INFO subsystem-bench::availability] Waiting for all emulated peers to receive their chunk from us ... +[2024-02-19T14:10:37.276Z INFO subsystem-bench::availability] All chunks received in 237ms +[2024-02-19T14:10:37.276Z INFO polkadot_subsystem_bench::availability] Waiting for 749 bitfields to be received and processed +[2024-02-19T14:10:38.362Z INFO subsystem-bench::availability] All bitfields processed +[2024-02-19T14:10:38.362Z INFO subsystem-bench::availability] All work for block completed in 1323ms +[2024-02-19T14:10:38.362Z INFO subsystem-bench::availability] All blocks processed in 3974ms +[2024-02-19T14:10:38.362Z INFO subsystem-bench::availability] Avg block time: 1324 ms +[2024-02-19T14:10:38.362Z INFO parachain::availability-store] received `Conclude` signal, exiting +[2024-02-19T14:10:38.362Z INFO parachain::bitfield-distribution] Conclude +[2024-02-19T14:10:38.362Z INFO subsystem-bench::network] Downlink channel closed, network interface task exiting + +polkadot/node/subsystem-bench/examples/availability_write.yaml #1 DataAvailabilityWrite + +Network usage, KiB total per block +Received from peers 12922.000 4307.333 +Sent to peers 47705.000 15901.667 + +CPU usage, seconds total per block +availability-distribution 0.045 0.015 +bitfield-distribution 0.104 0.035 +availability-store 0.304 0.101 +Test environment 3.213 1.071 +``` + + + +`Block time` in the current context 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 successfully, 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. + +### Profiling cache misses + +Cache misses are profiled using Cachegrind, part of Valgrind. Cachegrind runs slowly, and its cache simulation is basic +and unlikely to reflect the behavior of a modern machine. However, it still represents the general situation with cache +usage, and more importantly it doesn't require a bare-metal machine to run on, which means it could be run in CI or in +a remote virtual installation. + +To profile cache misses use the `--cache-misses` flag. Cache simulation of current runs tuned for Intel Ice Lake CPU. +Since the execution will be very slow, it's recommended not to run it together with other profiling and not to take +benchmark results into account. A report is saved in a file `cachegrind_report.txt`. + +Example run results: + +``` +$ target/testnet/subsystem-bench --cache-misses cache-misses-data-availability-read.yaml +$ cat cachegrind_report.txt +I refs: 64,622,081,485 +I1 misses: 3,018,168 +LLi misses: 437,654 +I1 miss rate: 0.00% +LLi miss rate: 0.00% + +D refs: 12,161,833,115 (9,868,356,364 rd + 2,293,476,751 wr) +D1 misses: 167,940,701 ( 71,060,073 rd + 96,880,628 wr) +LLd misses: 33,550,018 ( 16,685,853 rd + 16,864,165 wr) +D1 miss rate: 1.4% ( 0.7% + 4.2% ) +LLd miss rate: 0.3% ( 0.2% + 0.7% ) + +LL refs: 170,958,869 ( 74,078,241 rd + 96,880,628 wr) +LL misses: 33,987,672 ( 17,123,507 rd + 16,864,165 wr) +LL miss rate: 0.0% ( 0.0% + 0.7% ) +``` + +The results show that 1.4% of the L1 data cache missed, but the last level cache only missed 0.3% of the time. +Instruction data of the L1 has 0.00%. + +Cachegrind writes line-by-line cache profiling information to a file named `cachegrind.out.`. +This file is best interpreted with `cg_annotate --auto=yes cachegrind.out.`. For more information see the +[cachegrind manual](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/cmt-40/Nice/RuleRefinement/bin/valgrind-3.2.0/docs/html/cg-manual.html). + +For finer profiling of cache misses, better use `perf` on a bare-metal machine. + +## 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 `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/approvals_no_shows.yaml b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml new file mode 100644 index 0000000000000000000000000000000000000000..146da57d44c4aaf973e13c886a357028cdbe3559 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: true + coalesce_tranche_diff: 12 + num_no_shows_per_candidate: 10 + workdir_prefix: "/tmp/" + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6b17e62c20aa3f69153fb596d1a303a2e0320ddd --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + num_no_shows_per_candidate: 0 + workdir_prefix: "/tmp" + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e946c28e8ef5d4e38736ffc21e56d8b1c6cd0ddc --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: true + coalesce_tranche_diff: 12 + num_no_shows_per_candidate: 0 + workdir_prefix: "/tmp/" + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f4b050e72f27dd4b5bb0c52bd49162cd0bb83ec --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 1.0 + coalesce_std_dev: 0.0 + enable_assignments_v2: false + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + num_no_shows_per_candidate: 0 + workdir_prefix: "/tmp/" + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/availability_read.yaml b/polkadot/node/subsystem-bench/examples/availability_read.yaml new file mode 100644 index 0000000000000000000000000000000000000000..82355b0e2973aaff490a5c2d3ed54d37c61430de --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/availability_read.yaml @@ -0,0 +1,45 @@ +TestConfiguration: +# Test 1 +- objective: !DataAvailabilityRead + fetch_from_backers: true + n_validators: 300 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + mean_latency_ms: 100 + std_dev: 1 + num_blocks: 3 + connectivity: 90 + +# Test 2 +- objective: !DataAvailabilityRead + fetch_from_backers: true + n_validators: 500 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + mean_latency_ms: 100 + std_dev: 1 + num_blocks: 3 + connectivity: 90 + +# Test 3 +- objective: !DataAvailabilityRead + fetch_from_backers: true + n_validators: 1000 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + mean_latency_ms: 100 + std_dev: 1 + num_blocks: 3 + connectivity: 90 diff --git a/polkadot/node/subsystem-bench/examples/availability_write.yaml b/polkadot/node/subsystem-bench/examples/availability_write.yaml new file mode 100644 index 0000000000000000000000000000000000000000..64e07d76969239bf230146d50cae11cfe5d781d6 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/availability_write.yaml @@ -0,0 +1,15 @@ +TestConfiguration: +# Test 1kV, 200 cores, max Pov +- objective: DataAvailabilityWrite + n_validators: 1000 + n_cores: 200 + max_validators_per_core: 5 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + mean_latency_ms: 30 + std_dev: 2.0 + connectivity: 75 + 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/cli/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/cli/subsystem-bench.rs new file mode 100644 index 0000000000000000000000000000000000000000..deb351360d74ede1ea78494a34f7c459dc544cdd --- /dev/null +++ b/polkadot/node/subsystem-bench/src/cli/subsystem-bench.rs @@ -0,0 +1,202 @@ +// 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 colored::Colorize; +use polkadot_subsystem_bench::{approval, availability, configuration}; +use pyroscope::PyroscopeAgent; +use pyroscope_pprofrs::{pprof_backend, PprofConfig}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +mod valgrind; + +const LOG_TARGET: &str = "subsystem-bench::cli"; + +/// Supported test objectives +#[derive(Debug, Clone, Parser, Serialize, Deserialize)] +#[command(rename_all = "kebab-case")] +pub enum TestObjective { + /// Benchmark availability recovery strategies. + DataAvailabilityRead(availability::DataAvailabilityReadOptions), + /// Benchmark availability and bitfield distribution. + DataAvailabilityWrite, + /// Benchmark the approval-voting and approval-distribution subsystems. + ApprovalVoting(approval::ApprovalsOptions), +} + +impl std::fmt::Display for TestObjective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::DataAvailabilityRead(_) => "DataAvailabilityRead", + Self::DataAvailabilityWrite => "DataAvailabilityWrite", + Self::ApprovalVoting(_) => "ApprovalVoting", + } + ) + } +} + +/// The test input parameters +#[derive(Clone, Debug, Serialize, Deserialize)] +struct CliTestConfiguration { + /// Test Objective + pub objective: TestObjective, + /// Test Configuration + #[serde(flatten)] + pub test_config: configuration::TestConfiguration, +} + +#[derive(Serialize, Deserialize)] +pub struct TestSequence { + #[serde(rename(serialize = "TestConfiguration", deserialize = "TestConfiguration"))] + test_configurations: Vec, +} + +impl TestSequence { + 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")) + } +} + +#[derive(Debug, Parser)] +#[allow(missing_docs)] +struct BenchCli { + #[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, + + #[clap(long, default_value_t = false)] + /// Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind must be in the PATH + pub cache_misses: bool, + + #[arg(required = true)] + /// Path to the test sequence configuration file + pub path: String, +} + +impl BenchCli { + fn launch(self) -> eyre::Result<()> { + let is_valgrind_running = valgrind::is_valgrind_running(); + if !is_valgrind_running && self.cache_misses { + return valgrind::relaunch_in_valgrind_mode() + } + + 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 test_sequence = TestSequence::new_from_file(Path::new(&self.path)) + .expect("File exists") + .test_configurations; + let num_steps = test_sequence.len(); + gum::info!("{}", format!("Sequence contains {} step(s)", num_steps).bright_purple()); + + for (index, CliTestConfiguration { objective, mut test_config }) in + test_sequence.into_iter().enumerate() + { + let benchmark_name = format!("{} #{} {}", &self.path, index + 1, objective); + gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); + gum::info!(target: LOG_TARGET, "[{}] {}", format!("objective = {:?}", objective).green(), test_config); + test_config.generate_pov_sizes(); + + let usage = match objective { + TestObjective::DataAvailabilityRead(opts) => { + let mut state = availability::TestState::new(&test_config); + let (mut env, _protocol_config) = availability::prepare_test( + test_config, + &mut state, + availability::TestDataAvailability::Read(opts), + true, + ); + env.runtime().block_on(availability::benchmark_availability_read( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::DataAvailabilityWrite => { + let mut state = availability::TestState::new(&test_config); + let (mut env, _protocol_config) = availability::prepare_test( + test_config, + &mut state, + availability::TestDataAvailability::Write, + true, + ); + env.runtime().block_on(availability::benchmark_availability_write( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::ApprovalVoting(ref options) => { + let (mut env, state) = + approval::prepare_test(test_config.clone(), options.clone(), true); + env.runtime().block_on(approval::bench_approvals( + &benchmark_name, + &mut env, + state, + )) + }, + }; + println!("{}", usage); + } + + 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) + .format_timestamp_millis() + .try_init() + .unwrap(); + + let cli: BenchCli = BenchCli::parse(); + cli.launch()?; + Ok(()) +} diff --git a/polkadot/node/subsystem-bench/src/cli/valgrind.rs b/polkadot/node/subsystem-bench/src/cli/valgrind.rs new file mode 100644 index 0000000000000000000000000000000000000000..3d0c488355b9e60020dc2d1de1b380c1ee86bff8 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/cli/valgrind.rs @@ -0,0 +1,49 @@ +// 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 color_eyre::eyre; + +/// Show if the app is running under Valgrind +pub(crate) fn is_valgrind_running() -> bool { + match std::env::var("LD_PRELOAD") { + Ok(v) => v.contains("valgrind"), + Err(_) => false, + } +} + +/// Stop execution and relaunch the app under valgrind +/// Cache configuration used to emulate Intel Ice Lake (size, associativity, line size): +/// L1 instruction: 32,768 B, 8-way, 64 B lines +/// L1 data: 49,152 B, 12-way, 64 B lines +/// Last-level: 2,097,152 B, 16-way, 64 B lines +pub(crate) fn relaunch_in_valgrind_mode() -> eyre::Result<()> { + use std::os::unix::process::CommandExt; + let err = std::process::Command::new("valgrind") + .arg("--tool=cachegrind") + .arg("--cache-sim=yes") + .arg("--log-file=cachegrind_report.txt") + .arg("--I1=32768,8,64") + .arg("--D1=49152,12,64") + .arg("--LL=2097152,16,64") + .arg("--verbose") + .args(std::env::args()) + .exec(); + + Err(eyre::eyre!( + "Сannot run Valgrind, check that it is installed and available in the PATH\n{}", + err + )) +} diff --git a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..af5ff5aa1facc11f2d0caa8ec77276976a0554a5 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs @@ -0,0 +1,207 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::configuration::TestAuthorities; +use itertools::Itertools; +use polkadot_node_core_approval_voting::time::{Clock, SystemClock, Tick}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + View, +}; +use polkadot_node_subsystem_types::messages::{ + network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, NetworkBridgeEvent, +}; +use polkadot_overseer::AllMessages; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateReceipt, CoreIndex, GroupIndex, Hash, Header, + Id as ParaId, Slot, ValidatorIndex, +}; +use polkadot_primitives_test_helpers::dummy_candidate_receipt_bad_sig; +use rand::{seq::SliceRandom, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use sc_network::PeerId; +use sp_consensus_babe::{ + digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest}, + AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch, VrfSignature, VrfTranscript, +}; +use sp_core::crypto::VrfSecret; +use sp_keyring::sr25519::Keyring as Sr25519Keyring; +use sp_runtime::{Digest, DigestItem}; +use std::sync::{atomic::AtomicU64, Arc}; + +/// A fake system clock used for driving the approval voting and make +/// it process blocks, assignments and approvals from the past. +#[derive(Clone)] +pub struct PastSystemClock { + /// The real system clock + real_system_clock: SystemClock, + /// The difference in ticks between the real system clock and the current clock. + delta_ticks: Arc, +} + +impl PastSystemClock { + /// Creates a new fake system clock with `delta_ticks` between the real time and the fake one. + pub fn new(real_system_clock: SystemClock, delta_ticks: Arc) -> Self { + PastSystemClock { real_system_clock, delta_ticks } + } +} + +impl Clock for PastSystemClock { + fn tick_now(&self) -> Tick { + self.real_system_clock.tick_now() - + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst) + } + + fn wait( + &self, + tick: Tick, + ) -> std::pin::Pin + Send + 'static>> { + self.real_system_clock + .wait(tick + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst)) + } +} + +/// Helper function to generate a babe epoch for this benchmark. +/// It does not change for the duration of the test. +pub fn generate_babe_epoch(current_slot: Slot, authorities: TestAuthorities) -> BabeEpoch { + let authorities = authorities + .validator_babe_id + .into_iter() + .enumerate() + .map(|(index, public)| (public, index as u64)) + .collect_vec(); + BabeEpoch { + epoch_index: 1, + start_slot: current_slot.saturating_sub(1u64), + duration: 200, + authorities, + randomness: [0xde; 32], + config: BabeEpochConfiguration { c: (1, 4), allowed_slots: AllowedSlots::PrimarySlots }, + } +} + +/// Generates a topology to be used for this benchmark. +pub fn generate_topology(test_authorities: &TestAuthorities) -> SessionGridTopology { + let keyrings = test_authorities + .validator_authority_id + .clone() + .into_iter() + .zip(test_authorities.peer_ids.clone()) + .collect_vec(); + + let topology = keyrings + .clone() + .into_iter() + .enumerate() + .map(|(index, (discovery_id, peer_id))| TopologyPeerInfo { + peer_ids: vec![peer_id], + validator_index: ValidatorIndex(index as u32), + discovery_id, + }) + .collect_vec(); + let shuffled = (0..keyrings.len()).collect_vec(); + + SessionGridTopology::new(shuffled, topology) +} + +/// Generates new session topology message. +pub fn generate_new_session_topology( + test_authorities: &TestAuthorities, + test_node: ValidatorIndex, +) -> Vec { + let topology = generate_topology(test_authorities); + + let event = NetworkBridgeEvent::NewGossipTopology(NewGossipTopology { + session: 1, + topology, + local_index: Some(test_node), + }); + vec![AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event))] +} + +/// Generates a peer view change for the passed `block_hash` +pub fn generate_peer_view_change_for(block_hash: Hash, peer_id: PeerId) -> AllMessages { + let network = NetworkBridgeEvent::PeerViewChange(peer_id, View::new([block_hash], 0)); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) +} + +/// Helper function to create a a signature for the block header. +fn garbage_vrf_signature() -> VrfSignature { + let transcript = VrfTranscript::new(b"test-garbage", &[]); + Sr25519Keyring::Alice.pair().vrf_sign(&transcript.into()) +} + +/// Helper function to create a block header. +pub fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header { + let digest = + { + let mut digest = Digest::default(); + let vrf_signature = garbage_vrf_signature(); + digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF( + SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature }, + ))); + digest + }; + + Header { + digest, + extrinsics_root: Default::default(), + number, + state_root: Default::default(), + parent_hash, + } +} + +/// Helper function to create a candidate receipt. +fn make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt { + let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default())); + r.descriptor.para_id = para_id; + r +} + +/// Helper function to create a list of candidates that are included in the block +pub fn make_candidates( + block_hash: Hash, + block_number: BlockNumber, + num_cores: u32, + num_candidates: u32, +) -> Vec { + let seed = [block_number as u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let mut candidates = (0..num_cores) + .map(|core| { + CandidateEvent::CandidateIncluded( + make_candidate(ParaId::from(core), &block_hash), + Vec::new().into(), + CoreIndex(core), + GroupIndex(core), + ) + }) + .collect_vec(); + let (candidates, _) = candidates.partial_shuffle(&mut rand_chacha, num_candidates as usize); + candidates + .iter_mut() + .map(|val| val.clone()) + .sorted_by(|a, b| match (a, b) { + ( + CandidateEvent::CandidateIncluded(_, _, core_a, _), + CandidateEvent::CandidateIncluded(_, _, core_b, _), + ) => core_a.0.cmp(&core_b.0), + (_, _) => todo!("Should not happen"), + }) + .collect_vec() +} diff --git a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs new file mode 100644 index 0000000000000000000000000000000000000000..c1b31a509f6dd21c3f23bb44afec8859e51fa76a --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs @@ -0,0 +1,676 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{ + approval::{ + helpers::{generate_babe_epoch, generate_topology}, + test_message::{MessagesBundle, TestMessageInfo}, + ApprovalTestState, ApprovalsOptions, BlockTestData, GeneratedState, + BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, SLOT_DURATION_MILLIS, + }, + configuration::{TestAuthorities, TestConfiguration}, + mock::runtime_api::session_info_for_peers, + NODE_UNDER_TEST, +}; +use futures::SinkExt; +use itertools::Itertools; +use parity_scale_codec::Encode; +use polkadot_node_core_approval_voting::{ + criteria::{compute_assignments, Config}, + time::tranche_to_tick, +}; +use polkadot_node_network_protocol::{ + grid_topology::{GridNeighbors, RandomRouting, RequiredRouting, SessionGridTopology}, + v3 as protocol_v3, +}; +use polkadot_node_primitives::approval::{ + self, + v2::{CoreBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2}, +}; +use polkadot_primitives::{ + vstaging::ApprovalVoteMultipleCandidates, CandidateEvent, CandidateHash, CandidateIndex, + CoreIndex, Hash, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID, +}; +use rand::{seq::SliceRandom, RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use rand_distr::{Distribution, Normal}; +use sc_keystore::LocalKeystore; +use sc_network::PeerId; +use sc_service::SpawnTaskHandle; +use sha1::Digest; +use sp_application_crypto::AppCrypto; +use sp_consensus_babe::SlotDuration; +use sp_keystore::Keystore; +use sp_timestamp::Timestamp; +use std::{ + cmp::max, + collections::{BTreeMap, HashSet}, + fs, + io::Write, + path::{Path, PathBuf}, + time::Duration, +}; + +/// A generator of messages coming from a given Peer/Validator +pub struct PeerMessagesGenerator { + /// The grid neighbors of the node under test. + pub topology_node_under_test: GridNeighbors, + /// The topology of the network for the epoch under test. + pub topology: SessionGridTopology, + /// The validator index for this object generates the messages. + pub validator_index: ValidatorIndex, + /// An array of pre-generated random samplings, that is used to determine, which nodes would + /// send a given assignment, to the node under test because of the random samplings. + /// As an optimization we generate this sampling at the begining of the test and just pick + /// one randomly, because always taking the samples would be too expensive for benchamrk. + pub random_samplings: Vec>, + /// Channel for sending the generated messages to the aggregator + pub tx_messages: futures::channel::mpsc::UnboundedSender<(Hash, Vec)>, + /// The list of test authorities + pub test_authorities: TestAuthorities, + //// The session info used for the test. + pub session_info: SessionInfo, + /// The blocks used for testing + pub blocks: Vec, + /// Approval options params. + pub options: ApprovalsOptions, +} + +impl PeerMessagesGenerator { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + pub fn generate_messages(mut self, spawn_task_handle: &SpawnTaskHandle) { + spawn_task_handle.spawn("generate-messages", "generate-messages", async move { + for block_info in &self.blocks { + let assignments = self.generate_assignments(block_info); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let approvals = issue_approvals( + assignments, + block_info.hash, + &self.test_authorities.validator_public, + block_info.candidates.clone(), + &self.options, + &mut rand_chacha, + self.test_authorities.keyring.keystore_ref(), + ); + + self.tx_messages + .send((block_info.hash, approvals)) + .await + .expect("Should not fail"); + } + }) + } + + // Builds the messages finger print corresponding to this configuration. + // When the finger print exists already on disk the messages are not re-generated. + fn messages_fingerprint( + configuration: &TestConfiguration, + options: &ApprovalsOptions, + ) -> String { + let mut fingerprint = options.fingerprint(); + let configuration_bytes = bincode::serialize(&configuration).unwrap(); + fingerprint.extend(configuration_bytes); + let mut sha1 = sha1::Sha1::new(); + sha1.update(fingerprint); + let result = sha1.finalize(); + hex::encode(result) + } + + /// Generate all messages(Assignments & Approvals) needed for approving `blocks``. + pub fn generate_messages_if_needed( + configuration: &TestConfiguration, + test_authorities: &TestAuthorities, + options: &ApprovalsOptions, + spawn_task_handle: &SpawnTaskHandle, + ) -> PathBuf { + let path_name = format!( + "{}/{}", + options.workdir_prefix, + Self::messages_fingerprint(configuration, options) + ); + + let path = Path::new(&path_name); + if path.exists() { + return path.to_path_buf(); + } + + gum::info!("Generate message because file does not exist"); + let delta_to_first_slot_under_test = Timestamp::new(BUFFER_FOR_GENERATION_MILLIS); + let initial_slot = Slot::from_timestamp( + (*Timestamp::current() - *delta_to_first_slot_under_test).into(), + SlotDuration::from_millis(SLOT_DURATION_MILLIS), + ); + + let babe_epoch = generate_babe_epoch(initial_slot, test_authorities.clone()); + let session_info = session_info_for_peers(configuration, test_authorities); + let blocks = ApprovalTestState::generate_blocks_information( + configuration, + &babe_epoch, + initial_slot, + ); + + gum::info!(target: LOG_TARGET, "Generate messages"); + let topology = generate_topology(test_authorities); + + let random_samplings = random_samplings_to_node( + ValidatorIndex(NODE_UNDER_TEST), + test_authorities.validator_public.len(), + test_authorities.validator_public.len() * 2, + ); + + let topology_node_under_test = + topology.compute_grid_neighbors_for(ValidatorIndex(NODE_UNDER_TEST)).unwrap(); + + let (tx, mut rx) = futures::channel::mpsc::unbounded(); + + // Spawn a thread to generate the messages for each validator, so that we speed up the + // generation. + for current_validator_index in 1..test_authorities.validator_public.len() { + let peer_message_source = PeerMessagesGenerator { + topology_node_under_test: topology_node_under_test.clone(), + topology: topology.clone(), + validator_index: ValidatorIndex(current_validator_index as u32), + test_authorities: test_authorities.clone(), + session_info: session_info.clone(), + blocks: blocks.clone(), + tx_messages: tx.clone(), + random_samplings: random_samplings.clone(), + options: options.clone(), + }; + + peer_message_source.generate_messages(spawn_task_handle); + } + + std::mem::drop(tx); + + let seed = [0x32; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let mut all_messages: BTreeMap> = BTreeMap::new(); + // Receive all messages and sort them by Tick they have to be sent. + loop { + match rx.try_next() { + Ok(Some((block_hash, messages))) => + for message in messages { + let block_info = blocks + .iter() + .find(|val| val.hash == block_hash) + .expect("Should find blocks"); + let tick_to_send = tranche_to_tick( + SLOT_DURATION_MILLIS, + block_info.slot, + message.tranche_to_send(), + ); + let to_add = all_messages.entry(tick_to_send).or_default(); + to_add.push(message); + }, + Ok(None) => break, + Err(_) => { + std::thread::sleep(Duration::from_millis(50)); + }, + } + } + let all_messages = all_messages + .into_iter() + .flat_map(|(_, mut messages)| { + // Shuffle the messages inside the same tick, so that we don't priorites messages + // for older nodes. we try to simulate the same behaviour as in real world. + messages.shuffle(&mut rand_chacha); + messages + }) + .collect_vec(); + + gum::info!("Generated a number of {:} unique messages", all_messages.len()); + + let generated_state = GeneratedState { all_messages: Some(all_messages), initial_slot }; + + let mut messages_file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + messages_file + .write_all(&generated_state.encode()) + .expect("Could not update message file"); + path.to_path_buf() + } + + /// Generates assignments for the given `current_validator_index` + /// Returns a list of assignments to be sent sorted by tranche. + fn generate_assignments(&self, block_info: &BlockTestData) -> Vec { + let config = Config::from(&self.session_info); + + let leaving_cores = block_info + .candidates + .clone() + .into_iter() + .map(|candidate_event| { + if let CandidateEvent::CandidateIncluded(candidate, _, core_index, group_index) = + candidate_event + { + (candidate.hash(), core_index, group_index) + } else { + todo!("Variant is never created in this benchmark") + } + }) + .collect_vec(); + + let mut assignments_by_tranche = BTreeMap::new(); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let to_be_sent_by = neighbours_that_would_sent_message( + &self.test_authorities.peer_ids, + self.validator_index.0, + &self.topology_node_under_test, + &self.topology, + ); + + let leaving_cores = leaving_cores + .clone() + .into_iter() + .filter(|(_, core_index, _group_index)| core_index.0 != self.validator_index.0) + .collect_vec(); + + let store = LocalKeystore::in_memory(); + let _public = store + .sr25519_generate_new( + ASSIGNMENT_KEY_TYPE_ID, + Some(self.test_authorities.key_seeds[self.validator_index.0 as usize].as_str()), + ) + .expect("should not fail"); + let assignments = compute_assignments( + &store, + block_info.relay_vrf_story.clone(), + &config, + leaving_cores.clone(), + self.options.enable_assignments_v2, + ); + + let random_sending_nodes = self + .random_samplings + .get(rand_chacha.next_u32() as usize % self.random_samplings.len()) + .unwrap(); + let random_sending_peer_ids = random_sending_nodes + .iter() + .map(|validator| (*validator, self.test_authorities.peer_ids[validator.0 as usize])) + .collect_vec(); + + let mut unique_assignments = HashSet::new(); + for (core_index, assignment) in assignments { + let assigned_cores = match &assignment.cert().kind { + approval::v2::AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => + core_bitfield.iter_ones().map(|val| CoreIndex::from(val as u32)).collect_vec(), + approval::v2::AssignmentCertKindV2::RelayVRFDelay { core_index } => + vec![*core_index], + approval::v2::AssignmentCertKindV2::RelayVRFModulo { sample: _ } => + vec![core_index], + }; + + let bitfiled: CoreBitfield = assigned_cores.clone().try_into().unwrap(); + + // For the cases where tranch0 assignments are in a single certificate we need to make + // sure we create a single message. + if unique_assignments.insert(bitfiled) { + let this_tranche_assignments = + assignments_by_tranche.entry(assignment.tranche()).or_insert_with(Vec::new); + + this_tranche_assignments.push(( + IndirectAssignmentCertV2 { + block_hash: block_info.hash, + validator: self.validator_index, + cert: assignment.cert().clone(), + }, + block_info + .candidates + .iter() + .enumerate() + .filter(|(_index, candidate)| { + if let CandidateEvent::CandidateIncluded(_, _, core, _) = candidate { + assigned_cores.contains(core) + } else { + panic!("Should not happen"); + } + }) + .map(|(index, _)| index as u32) + .collect_vec() + .try_into() + .unwrap(), + to_be_sent_by + .iter() + .chain(random_sending_peer_ids.iter()) + .copied() + .collect::>(), + assignment.tranche(), + )); + } + } + + assignments_by_tranche + .into_values() + .flat_map(|assignments| assignments.into_iter()) + .map(|assignment| { + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment.0, + assignment.1, + )]); + TestMessageInfo { + msg, + sent_by: assignment + .2 + .into_iter() + .map(|(validator_index, _)| validator_index) + .collect_vec(), + tranche: assignment.3, + block_hash: block_info.hash, + } + }) + .collect_vec() + } +} + +/// A list of random samplings that we use to determine which nodes should send a given message to +/// the node under test. +/// We can not sample every time for all the messages because that would be too expensive to +/// perform, so pre-generate a list of samples for a given network size. +/// - result[i] give us as a list of random nodes that would send a given message to the node under +/// test. +fn random_samplings_to_node( + node_under_test: ValidatorIndex, + num_validators: usize, + num_samplings: usize, +) -> Vec> { + let seed = [7u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + (0..num_samplings) + .map(|_| { + (0..num_validators) + .filter(|sending_validator_index| { + *sending_validator_index != NODE_UNDER_TEST as usize + }) + .flat_map(|sending_validator_index| { + let mut validators = (0..num_validators).collect_vec(); + validators.shuffle(&mut rand_chacha); + + let mut random_routing = RandomRouting::default(); + validators + .into_iter() + .flat_map(|validator_to_send| { + if random_routing.sample(num_validators, &mut rand_chacha) { + random_routing.inc_sent(); + if validator_to_send == node_under_test.0 as usize { + Some(ValidatorIndex(sending_validator_index as u32)) + } else { + None + } + } else { + None + } + }) + .collect_vec() + }) + .collect_vec() + }) + .collect_vec() +} + +/// Helper function to randomly determine how many approvals we coalesce together in a single +/// message. +fn coalesce_approvals_len( + coalesce_mean: f32, + coalesce_std_dev: f32, + rand_chacha: &mut ChaCha20Rng, +) -> usize { + max( + 1, + Normal::new(coalesce_mean, coalesce_std_dev) + .expect("normal distribution parameters are good") + .sample(rand_chacha) + .round() as i32, + ) as usize +} + +/// Helper function to create approvals signatures for all assignments passed as arguments. +/// Returns a list of Approvals messages that need to be sent. +fn issue_approvals( + assignments: Vec, + block_hash: Hash, + validator_ids: &[ValidatorId], + candidates: Vec, + options: &ApprovalsOptions, + rand_chacha: &mut ChaCha20Rng, + store: &LocalKeystore, +) -> Vec { + let mut queued_to_sign: Vec = Vec::new(); + let mut num_coalesce = + coalesce_approvals_len(options.coalesce_mean, options.coalesce_std_dev, rand_chacha); + let result = assignments + .iter() + .enumerate() + .map(|(_index, message)| match &message.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + let mut approvals_to_create = Vec::new(); + + let current_validator_index = queued_to_sign + .first() + .map(|msg| msg.validator_index) + .unwrap_or(ValidatorIndex(99999)); + + // Invariant for this benchmark. + assert_eq!(assignments.len(), 1); + + let assignment = assignments.first().unwrap(); + + let earliest_tranche = queued_to_sign + .first() + .map(|val| val.assignment.tranche) + .unwrap_or(message.tranche); + + if queued_to_sign.len() >= num_coalesce || + (!queued_to_sign.is_empty() && + current_validator_index != assignment.0.validator) || + message.tranche - earliest_tranche >= options.coalesce_tranche_diff + { + approvals_to_create.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + num_coalesce = coalesce_approvals_len( + options.coalesce_mean, + options.coalesce_std_dev, + rand_chacha, + ); + } + + // If more that one candidate was in the assignment queue all of them for issuing + // approvals + for candidate_index in assignment.1.iter_ones() { + let candidate = candidates.get(candidate_index).unwrap(); + if let CandidateEvent::CandidateIncluded(candidate, _, _, _) = candidate { + queued_to_sign.push(TestSignInfo { + candidate_hash: candidate.hash(), + candidate_index: candidate_index as CandidateIndex, + validator_index: assignment.0.validator, + assignment: message.clone(), + }); + } else { + todo!("Other enum variants are not used in this benchmark"); + } + } + approvals_to_create + }, + _ => { + todo!("Other enum variants are not used in this benchmark"); + }, + }) + .collect_vec(); + + let mut messages = result.into_iter().flatten().collect_vec(); + + if !queued_to_sign.is_empty() { + messages.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + } + messages +} + +/// Helper struct to gather information about more than one candidate an sign it in a single +/// approval message. +struct TestSignInfo { + /// The candidate hash + candidate_hash: CandidateHash, + /// The candidate index + candidate_index: CandidateIndex, + /// The validator sending the assignments + validator_index: ValidatorIndex, + /// The assignments convering this candidate + assignment: TestMessageInfo, +} + +impl TestSignInfo { + /// Helper function to create a signture for all candidates in `to_sign` parameter. + /// Returns a TestMessage + fn sign_candidates( + to_sign: &mut Vec, + validator_ids: &[ValidatorId], + block_hash: Hash, + num_coalesce: usize, + store: &LocalKeystore, + ) -> MessagesBundle { + let current_validator_index = to_sign.first().map(|val| val.validator_index).unwrap(); + let tranche_approval_can_be_sent = + to_sign.iter().map(|val| val.assignment.tranche).max().unwrap(); + let validator_id = validator_ids.get(current_validator_index.0 as usize).unwrap().clone(); + + let unique_assignments: HashSet = + to_sign.iter().map(|info| info.assignment.clone()).collect(); + + let mut to_sign = to_sign + .drain(..) + .sorted_by(|val1, val2| val1.candidate_index.cmp(&val2.candidate_index)) + .peekable(); + + let mut bundle = MessagesBundle { + assignments: unique_assignments.into_iter().collect_vec(), + approvals: Vec::new(), + }; + + while to_sign.peek().is_some() { + let to_sign = to_sign.by_ref().take(num_coalesce).collect_vec(); + + let hashes = to_sign.iter().map(|val| val.candidate_hash).collect_vec(); + let candidate_indices = to_sign.iter().map(|val| val.candidate_index).collect_vec(); + + let sent_by = to_sign + .iter() + .flat_map(|val| val.assignment.sent_by.iter()) + .copied() + .collect::>(); + + let payload = ApprovalVoteMultipleCandidates(&hashes).signing_payload(1); + + let signature = store + .sr25519_sign(ValidatorId::ID, &validator_id.clone().into(), &payload[..]) + .unwrap() + .unwrap() + .into(); + let indirect = IndirectSignedApprovalVoteV2 { + block_hash, + candidate_indices: candidate_indices.try_into().unwrap(), + validator: current_validator_index, + signature, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![indirect]); + + bundle.approvals.push(TestMessageInfo { + msg, + sent_by: sent_by.into_iter().collect_vec(), + tranche: tranche_approval_can_be_sent, + block_hash, + }); + } + bundle + } +} + +/// Determine what neighbours would send a given message to the node under test. +fn neighbours_that_would_sent_message( + peer_ids: &[PeerId], + current_validator_index: u32, + topology_node_under_test: &GridNeighbors, + topology: &SessionGridTopology, +) -> Vec<(ValidatorIndex, PeerId)> { + let topology_originator = topology + .compute_grid_neighbors_for(ValidatorIndex(current_validator_index)) + .unwrap(); + + let originator_y = topology_originator.validator_indices_y.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridY + }); + + assert!(originator_y != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let originator_x = topology_originator.validator_indices_x.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridX + }); + + assert!(originator_x != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let is_neighbour = topology_originator + .validator_indices_x + .contains(&ValidatorIndex(NODE_UNDER_TEST)) || + topology_originator + .validator_indices_y + .contains(&ValidatorIndex(NODE_UNDER_TEST)); + + let mut to_be_sent_by = originator_y + .into_iter() + .chain(originator_x) + .map(|val| (*val, peer_ids[val.0 as usize])) + .collect_vec(); + + if is_neighbour { + to_be_sent_by.push((ValidatorIndex(current_validator_index), peer_ids[0])); + } + + to_be_sent_by +} diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mock_chain_selection.rs b/polkadot/node/subsystem-bench/src/lib/approval/mock_chain_selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..77ba80d4b2bbcbc7616c00ad6f5069ceb22979bc --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/approval/mock_chain_selection.rs @@ -0,0 +1,64 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::approval::{ApprovalTestState, PastSystemClock, LOG_TARGET, SLOT_DURATION_MILLIS}; +use futures::FutureExt; +use polkadot_node_core_approval_voting::time::{slot_number_to_tick, Clock, TICK_DURATION_MILLIS}; +use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; +use polkadot_node_subsystem_types::messages::ChainSelectionMessage; + +/// Mock ChainSelection subsystem used to answer request made by the approval-voting subsystem, +/// during benchmark. All the necessary information to answer the requests is stored in the `state` +pub struct MockChainSelection { + pub state: ApprovalTestState, + pub clock: PastSystemClock, +} + +#[overseer::subsystem(ChainSelection, error=SubsystemError, prefix=self::overseer)] +impl MockChainSelection { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "mock-chain-subsystem", future } + } +} + +#[overseer::contextbounds(ChainSelection, prefix = self::overseer)] +impl MockChainSelection { + async fn run(self, mut ctx: Context) { + loop { + let msg = ctx.recv().await.expect("Should not fail"); + match msg { + orchestra::FromOrchestra::Signal(_) => {}, + orchestra::FromOrchestra::Communication { msg } => + if let ChainSelectionMessage::Approved(hash) = msg { + let block_info = self.state.get_info_by_hash(hash); + let approved_number = block_info.block_number; + + block_info.approved.store(true, std::sync::atomic::Ordering::SeqCst); + self.state + .last_approved_block + .store(approved_number, std::sync::atomic::Ordering::SeqCst); + + let approved_in_tick = self.clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, block_info.slot); + + gum::info!(target: LOG_TARGET, ?hash, "Chain selection approved after {:} ms", approved_in_tick * TICK_DURATION_MILLIS); + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..450faf06123f61db9ee91d1d935721d3c128e701 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -0,0 +1,1072 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{ + approval::{ + helpers::{ + generate_babe_epoch, generate_new_session_topology, generate_peer_view_change_for, + make_header, PastSystemClock, + }, + message_generator::PeerMessagesGenerator, + mock_chain_selection::MockChainSelection, + test_message::{MessagesBundle, TestMessageInfo}, + }, + configuration::{TestAuthorities, TestConfiguration}, + dummy_builder, + environment::{TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT}, + mock::{ + chain_api::{ChainApiState, MockChainApi}, + network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx}, + runtime_api::MockRuntimeApi, + AlwaysSupportsParachains, TestSyncOracle, + }, + network::{ + new_network, HandleNetworkMessage, NetworkEmulatorHandle, NetworkInterface, + NetworkInterfaceReceiver, + }, + usage::BenchmarkUsage, + NODE_UNDER_TEST, +}; +use colored::Colorize; +use futures::channel::oneshot; +use itertools::Itertools; +use orchestra::TimeoutExt; +use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait}; +use parity_scale_codec::{Decode, Encode}; +use polkadot_approval_distribution::ApprovalDistribution; +use polkadot_node_core_approval_voting::{ + time::{slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock}, + ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics, +}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_node_primitives::approval::{self, v1::RelayVRFStory}; +use polkadot_node_subsystem::{overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue}; +use polkadot_node_subsystem_test_helpers::mock::new_block_import_info; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, ApprovalVotingMessage}; +use polkadot_node_subsystem_util::metrics::Metrics; +use polkadot_overseer::Handle as OverseerHandleReal; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot, + ValidatorIndex, +}; +use prometheus::Registry; +use sc_keystore::LocalKeystore; +use sc_service::SpawnTaskHandle; +use serde::{Deserialize, Serialize}; +use sp_consensus_babe::Epoch as BabeEpoch; +use sp_core::H256; +use std::{ + cmp::max, + collections::{HashMap, HashSet}, + fs, + io::Read, + ops::Sub, + sync::{ + atomic::{AtomicBool, AtomicU32, AtomicU64}, + Arc, + }, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +mod helpers; +mod message_generator; +mod mock_chain_selection; +mod test_message; + +pub(crate) const LOG_TARGET: &str = "subsystem-bench::approval"; +pub(crate) const NUM_COLUMNS: u32 = 1; +pub(crate) const SLOT_DURATION_MILLIS: u64 = 6000; +pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig { + col_approval_data: DATA_COL, + slot_duration_millis: SLOT_DURATION_MILLIS, +}; + +const DATA_COL: u32 = 0; + +/// Start generating messages for a slot into the future, so that the +/// generation nevers falls behind the current slot. +const BUFFER_FOR_GENERATION_MILLIS: u64 = 30_000; + +/// Parameters specific to the approvals benchmark +#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct ApprovalsOptions { + #[clap(short, long, default_value_t = 89)] + /// The last considered tranche for which we send the message. + pub last_considered_tranche: u32, + #[clap(short, long, default_value_t = 1.0)] + /// Min candidates to be signed in a single approval. + pub coalesce_mean: f32, + #[clap(short, long, default_value_t = 1.0)] + /// Max candidate to be signed in a single approval. + pub coalesce_std_dev: f32, + /// The maximum tranche diff between approvals coalesced toghther. + pub coalesce_tranche_diff: u32, + #[clap(short, long, default_value_t = false)] + /// Enable assignments v2. + pub enable_assignments_v2: bool, + #[clap(short, long, default_value_t = true)] + /// Sends messages only till block is approved. + pub stop_when_approved: bool, + #[clap(short, long)] + /// Work directory. + #[clap(short, long, default_value_t = format!("/tmp"))] + pub workdir_prefix: String, + /// The number of no shows per candidate + #[clap(short, long, default_value_t = 0)] + pub num_no_shows_per_candidate: u32, +} + +impl ApprovalsOptions { + // Generates a fingerprint use to determine if messages need to be re-generated. + fn fingerprint(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend(self.coalesce_mean.to_be_bytes()); + bytes.extend(self.coalesce_std_dev.to_be_bytes()); + bytes.extend(self.coalesce_tranche_diff.to_be_bytes()); + bytes.extend((self.enable_assignments_v2 as i32).to_be_bytes()); + bytes + } +} + +/// Information about a block. It is part of test state and it is used by the mock +/// subsystems to be able to answer the calls approval-voting and approval-distribution +/// do into the outside world. +#[derive(Clone, Debug)] +struct BlockTestData { + /// The slot this block occupies, see implementer's guide to understand what a slot + /// is in the context of polkadot. + slot: Slot, + /// The hash of the block. + hash: Hash, + /// The block number. + block_number: BlockNumber, + /// The list of candidates included in this block. + candidates: Vec, + /// The block header. + header: Header, + /// The vrf story for the given block. + relay_vrf_story: RelayVRFStory, + /// If the block has been approved by the approval-voting subsystem. + /// This set on `true` when ChainSelectionMessage::Approved is received inside the chain + /// selection mock subsystem. + approved: Arc, + /// The total number of candidates before this block. + total_candidates_before: u64, + /// The votes we sent. + /// votes[validator_index][candidate_index] tells if validator sent vote for candidate. + /// We use this to mark the test as succesfull if GetApprovalSignatures returns all the votes + /// from here. + votes: Arc>>, +} + +/// Candidate information used during the test to decide if more messages are needed. +#[derive(Debug)] +struct CandidateTestData { + /// The configured maximum number of no-shows for this candidate. + max_no_shows: u32, + /// The last tranche where we had a no-show. + last_tranche_with_no_show: u32, + /// The number of sent assignments. + sent_assignment: u32, + /// The number of no-shows. + num_no_shows: u32, + /// The maximum tranche were we covered the needed approvals + max_tranche: u32, + /// Minimum needed votes to approve candidate. + needed_approvals: u32, +} + +impl CandidateTestData { + /// If message in this tranche needs to be sent. + fn should_send_tranche(&self, tranche: u32) -> bool { + self.sent_assignment <= self.needed_approvals || + tranche <= self.max_tranche + self.num_no_shows + } + + /// Sets max tranche + fn set_max_tranche(&mut self, tranche: u32) { + self.max_tranche = max(tranche, self.max_tranche); + } + + /// Records no-show for candidate. + fn record_no_show(&mut self, tranche: u32) { + self.num_no_shows += 1; + self.last_tranche_with_no_show = max(tranche, self.last_tranche_with_no_show); + } + + /// Marks an assignment sent. + fn mark_sent_assignment(&mut self, tranche: u32) { + if self.sent_assignment < self.needed_approvals { + self.set_max_tranche(tranche); + } + + self.sent_assignment += 1; + } + + /// Tells if a message in this tranche should be a no-show. + fn should_no_show(&self, tranche: u32) -> bool { + (self.num_no_shows < self.max_no_shows && self.last_tranche_with_no_show < tranche) || + (tranche == 0 && self.num_no_shows == 0 && self.max_no_shows > 0) + } +} + +/// Test state that is pre-generated and loaded from a file that matches the fingerprint +/// of the TestConfiguration. +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +struct GeneratedState { + /// All assignments and approvals + all_messages: Option>, + /// The first slot in the test. + initial_slot: Slot, +} + +/// Approval test state used by all mock subsystems to be able to answer messages emitted +/// by the approval-voting and approval-distribution-subystems. +/// +/// This gets cloned across all mock subsystems, so if there is any information that gets +/// updated between subsystems, they would have to be wrapped in Arc's. +#[derive(Clone)] +pub struct ApprovalTestState { + /// The main test configuration + configuration: TestConfiguration, + /// The specific test configurations passed when starting the benchmark. + options: ApprovalsOptions, + /// The list of blocks used for testing. + blocks: Vec, + /// The babe epoch used during testing. + babe_epoch: BabeEpoch, + /// The pre-generated state. + generated_state: GeneratedState, + /// The test authorities + test_authorities: TestAuthorities, + /// Last approved block number. + last_approved_block: Arc, + /// Total sent messages from peers to node + total_sent_messages_to_node: Arc, + /// Total sent messages from test node to other peers + total_sent_messages_from_node: Arc, + /// Total unique sent messages. + total_unique_messages: Arc, + /// Approval voting metrics. + approval_voting_metrics: ApprovalVotingMetrics, + /// The delta ticks from the tick the messages were generated to the the time we start this + /// message. + delta_tick_from_generated: Arc, +} + +impl ApprovalTestState { + /// Build a new `ApprovalTestState` object out of the configurations passed when the benchmark + /// was tested. + fn new( + configuration: &TestConfiguration, + options: ApprovalsOptions, + dependencies: &TestEnvironmentDependencies, + ) -> Self { + let test_authorities = configuration.generate_authorities(); + let start = Instant::now(); + + let messages_path = PeerMessagesGenerator::generate_messages_if_needed( + configuration, + &test_authorities, + &options, + &dependencies.task_manager.spawn_handle(), + ); + + let mut messages_file = + fs::OpenOptions::new().read(true).open(messages_path.as_path()).unwrap(); + let mut messages_bytes = Vec::::with_capacity(2000000); + + messages_file + .read_to_end(&mut messages_bytes) + .expect("Could not initialize list of messages"); + let generated_state: GeneratedState = + Decode::decode(&mut messages_bytes.as_slice()).expect("Could not decode messages"); + + gum::info!( + "It took {:?} ms to load {:?} unique messages", + start.elapsed().as_millis(), + generated_state.all_messages.as_ref().map(|val| val.len()).unwrap_or_default() + ); + + let babe_epoch = + generate_babe_epoch(generated_state.initial_slot, test_authorities.clone()); + let blocks = Self::generate_blocks_information( + configuration, + &babe_epoch, + generated_state.initial_slot, + ); + + let state = ApprovalTestState { + blocks, + babe_epoch: babe_epoch.clone(), + generated_state, + test_authorities, + last_approved_block: Arc::new(AtomicU32::new(0)), + total_sent_messages_to_node: Arc::new(AtomicU64::new(0)), + total_sent_messages_from_node: Arc::new(AtomicU64::new(0)), + total_unique_messages: Arc::new(AtomicU64::new(0)), + options, + approval_voting_metrics: ApprovalVotingMetrics::try_register(&dependencies.registry) + .unwrap(), + delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)), + configuration: configuration.clone(), + }; + + gum::info!("Built testing state"); + + state + } + + /// Generates the blocks and the information about the blocks that will be used + /// to drive this test. + fn generate_blocks_information( + configuration: &TestConfiguration, + babe_epoch: &BabeEpoch, + initial_slot: Slot, + ) -> Vec { + let mut per_block_heads: Vec = Vec::new(); + let mut prev_candidates = 0; + for block_number in 1..=configuration.num_blocks { + let block_hash = Hash::repeat_byte(block_number as u8); + let parent_hash = + per_block_heads.last().map(|val| val.hash).unwrap_or(Hash::repeat_byte(0xde)); + let slot_for_block = initial_slot + (block_number as u64 - 1); + + let header = make_header(parent_hash, slot_for_block, block_number as u32); + + let unsafe_vrf = approval::v1::babe_unsafe_vrf_info(&header) + .expect("Can not continue without vrf generator"); + let relay_vrf_story = unsafe_vrf + .compute_randomness( + &babe_epoch.authorities, + &babe_epoch.randomness, + babe_epoch.epoch_index, + ) + .expect("Can not continue without vrf story"); + let block_info = BlockTestData { + slot: slot_for_block, + block_number: block_number as BlockNumber, + hash: block_hash, + header, + candidates: helpers::make_candidates( + block_hash, + block_number as BlockNumber, + configuration.n_cores as u32, + configuration.n_cores as u32, + ), + relay_vrf_story, + approved: Arc::new(AtomicBool::new(false)), + total_candidates_before: prev_candidates, + votes: Arc::new( + (0..configuration.n_validators) + .map(|_| { + (0..configuration.n_cores).map(|_| AtomicBool::new(false)).collect_vec() + }) + .collect_vec(), + ), + }; + prev_candidates += block_info.candidates.len() as u64; + per_block_heads.push(block_info) + } + per_block_heads + } + + /// Starts the generation of messages(Assignments & Approvals) needed for approving blocks. + async fn start_message_production( + &mut self, + network_emulator: &NetworkEmulatorHandle, + overseer_handle: OverseerHandleReal, + env: &TestEnvironment, + registry: Registry, + ) -> oneshot::Receiver<()> { + gum::info!(target: LOG_TARGET, "Start assignments/approvals production"); + + let (producer_tx, producer_rx) = oneshot::channel(); + let peer_message_source = PeerMessageProducer { + network: network_emulator.clone(), + overseer_handle: overseer_handle.clone(), + state: self.clone(), + options: self.options.clone(), + notify_done: producer_tx, + registry, + }; + + peer_message_source + .produce_messages(env, self.generated_state.all_messages.take().unwrap()); + producer_rx + } + + // Generates a ChainApiState used for driving MockChainApi + fn build_chain_api_state(&self) -> ChainApiState { + ChainApiState { + block_headers: self + .blocks + .iter() + .map(|block| (block.hash, block.header.clone())) + .collect(), + } + } + + // Builds a map with the list of candidate events per-block. + fn candidate_events_by_block(&self) -> HashMap> { + self.blocks.iter().map(|block| (block.hash, block.candidates.clone())).collect() + } + + // Builds a map with the list of candidate hashes per-block. + fn candidate_hashes_by_block(&self) -> HashMap> { + self.blocks + .iter() + .map(|block| { + ( + block.hash, + block + .candidates + .iter() + .map(|candidate_event| match candidate_event { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt, _, _, _) => receipt.clone(), + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }) + .collect_vec(), + ) + }) + .collect() + } +} + +impl ApprovalTestState { + /// Returns test data for the given hash + fn get_info_by_hash(&self, requested_hash: Hash) -> &BlockTestData { + self.blocks + .iter() + .find(|block| block.hash == requested_hash) + .expect("Mocks should not use unknown hashes") + } + + /// Returns test data for the given slot + fn get_info_by_slot(&self, slot: Slot) -> Option<&BlockTestData> { + self.blocks.iter().find(|block| block.slot == slot) + } +} + +impl HandleNetworkMessage for ApprovalTestState { + fn handle( + &self, + _message: crate::network::NetworkMessage, + _node_sender: &mut futures::channel::mpsc::UnboundedSender, + ) -> Option { + self.total_sent_messages_from_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + None + } +} + +/// A generator of messages coming from a given Peer/Validator +struct PeerMessageProducer { + /// The state state used to know what messages to generate. + state: ApprovalTestState, + /// Configuration options, passed at the beginning of the test. + options: ApprovalsOptions, + /// A reference to the network emulator + network: NetworkEmulatorHandle, + /// A handle to the overseer, used for sending messages to the node + /// under test. + overseer_handle: OverseerHandleReal, + /// Channel for producer to notify main loop it finished sending + /// all messages and they have been processed. + notify_done: oneshot::Sender<()>, + /// The metrics registry. + registry: Registry, +} + +impl PeerMessageProducer { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + fn produce_messages( + mut self, + env: &TestEnvironment, + all_messages: Vec, + ) { + env.spawn_blocking("produce-messages", async move { + let mut initialized_blocks = HashSet::new(); + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + self.initialize_candidates_test_data(); + let mut skipped_messages: Vec = Vec::new(); + let mut re_process_skipped = false; + + let system_clock = + PastSystemClock::new(SystemClock {}, self.state.delta_tick_from_generated.clone()); + let mut all_messages = all_messages.into_iter().peekable(); + + while all_messages.peek().is_some() { + let current_slot = + tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + let block_to_initialize = self + .state + .blocks + .iter() + .filter(|block_info| { + block_info.slot <= current_slot && + !initialized_blocks.contains(&block_info.hash) + }) + .cloned() + .collect_vec(); + for block_info in block_to_initialize { + if !TestEnvironment::metric_lower_than( + &self.registry, + "polkadot_parachain_imported_candidates_total", + (block_info.total_candidates_before + block_info.candidates.len() as u64 - + 1) as f64, + ) { + initialized_blocks.insert(block_info.hash); + self.initialize_block(&block_info).await; + } + } + + let mut maybe_need_skip = if re_process_skipped { + skipped_messages.clone().into_iter().peekable() + } else { + vec![].into_iter().peekable() + }; + + let progressing_iterator = if !re_process_skipped { + &mut all_messages + } else { + re_process_skipped = false; + skipped_messages.clear(); + &mut maybe_need_skip + }; + + while progressing_iterator + .peek() + .map(|bundle| { + self.time_to_process_message( + bundle, + current_slot, + &initialized_blocks, + &system_clock, + &per_candidate_data, + ) + }) + .unwrap_or_default() + { + let bundle = progressing_iterator.next().unwrap(); + re_process_skipped = self.process_message( + bundle, + &mut per_candidate_data, + &mut skipped_messages, + ) || re_process_skipped; + } + // Sleep, so that we don't busy wait in this loop when don't have anything to send. + sleep(Duration::from_millis(50)).await; + } + + gum::info!( + "All messages sent max_tranche {:?} last_tranche_with_no_show {:?}", + per_candidate_data.values().map(|data| data.max_tranche).max(), + per_candidate_data.values().map(|data| data.last_tranche_with_no_show).max() + ); + sleep(Duration::from_secs(6)).await; + // Send an empty GetApprovalSignatures as the last message + // so when the approval-distribution answered to it, we know it doesn't have anything + // else to process. + let (tx, rx) = oneshot::channel(); + let msg = ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx); + self.send_overseer_message( + AllMessages::ApprovalDistribution(msg), + ValidatorIndex(0), + None, + ) + .await; + rx.await.expect("Failed to get signatures"); + self.notify_done.send(()).expect("Failed to notify main loop"); + gum::info!("All messages processed "); + }); + } + + // Processes a single message bundle and queue the messages to be sent by the peers that would + // send the message in our simulation. + pub fn process_message( + &mut self, + bundle: test_message::MessagesBundle, + per_candidate_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + skipped_messages: &mut Vec, + ) -> bool { + let mut reprocess_skipped = false; + let block_info = self + .state + .get_info_by_hash(bundle.assignments.first().unwrap().block_hash) + .clone(); + + if bundle.should_send(per_candidate_data, &self.options) { + bundle.record_sent_assignment(per_candidate_data); + + let assignments = bundle.assignments.clone(); + + for message in bundle.assignments.into_iter().chain(bundle.approvals.into_iter()) { + if message.no_show_if_required(&assignments, per_candidate_data) { + reprocess_skipped = true; + continue; + } else { + message.record_vote(&block_info); + } + self.state + .total_unique_messages + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + for (peer, messages) in + message.clone().split_by_peer_id(&self.state.test_authorities) + { + for message in messages { + self.state + .total_sent_messages_to_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + self.queue_message_from_peer(message, peer.0) + } + } + } + } else if !block_info.approved.load(std::sync::atomic::Ordering::SeqCst) && + self.options.num_no_shows_per_candidate > 0 + { + skipped_messages.push(bundle); + } + reprocess_skipped + } + + // Tells if it is the time to process a message. + pub fn time_to_process_message( + &self, + bundle: &MessagesBundle, + current_slot: Slot, + initialized_blocks: &HashSet, + system_clock: &PastSystemClock, + per_candidate_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let block_info = + self.state.get_info_by_hash(bundle.assignments.first().unwrap().block_hash); + let tranche_now = system_clock.tranche_now(SLOT_DURATION_MILLIS, block_info.slot); + + Self::is_past_tranche( + bundle, + tranche_now, + current_slot, + block_info, + initialized_blocks.contains(&block_info.hash), + ) || !bundle.should_send(per_candidate_data, &self.options) + } + + // Tells if the tranche where the bundle should be sent has passed. + pub fn is_past_tranche( + bundle: &MessagesBundle, + tranche_now: u32, + current_slot: Slot, + block_info: &BlockTestData, + block_initialized: bool, + ) -> bool { + bundle.tranche_to_send() <= tranche_now && + current_slot >= block_info.slot && + block_initialized + } + + // Queue message to be sent by validator `sent_by` + fn queue_message_from_peer(&mut self, message: TestMessageInfo, sent_by: ValidatorIndex) { + let peer_authority_id = self + .state + .test_authorities + .validator_authority_id + .get(sent_by.0 as usize) + .expect("We can't handle unknown peers") + .clone(); + + self.network + .send_message_from_peer( + &peer_authority_id, + protocol_v3::ValidationProtocol::ApprovalDistribution(message.msg).into(), + ) + .unwrap_or_else(|_| panic!("Network should be up and running {:?}", sent_by)); + } + + // Queues a message to be sent by the peer identified by the `sent_by` value. + async fn send_overseer_message( + &mut self, + message: AllMessages, + _sent_by: ValidatorIndex, + _latency: Option, + ) { + self.overseer_handle + .send_msg(message, LOG_TARGET) + .timeout(MAX_TIME_OF_FLIGHT) + .await + .unwrap_or_else(|| { + panic!("{} ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis()) + }); + } + + // Sends the messages needed by approval-distribution and approval-voting for processing a + // message. E.g: PeerViewChange. + async fn initialize_block(&mut self, block_info: &BlockTestData) { + gum::info!("Initialize block {:?}", block_info.hash); + let (tx, rx) = oneshot::channel(); + self.overseer_handle.wait_for_activation(block_info.hash, tx).await; + + rx.await + .expect("We should not fail waiting for block to be activated") + .expect("We should not fail waiting for block to be activated"); + + for validator in 1..self.state.test_authorities.validator_authority_id.len() as u32 { + let peer_id = self.state.test_authorities.peer_ids.get(validator as usize).unwrap(); + let validator = ValidatorIndex(validator); + let view_update = generate_peer_view_change_for(block_info.hash, *peer_id); + + self.send_overseer_message(view_update, validator, None).await; + } + } + + // Initializes the candidates test data. This is used for bookeeping if more assignments and + // approvals would be needed. + fn initialize_candidates_test_data( + &self, + ) -> HashMap<(Hash, CandidateIndex), CandidateTestData> { + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + HashMap::new(); + for block_info in self.state.blocks.iter() { + for (candidate_index, _) in block_info.candidates.iter().enumerate() { + per_candidate_data.insert( + (block_info.hash, candidate_index as CandidateIndex), + CandidateTestData { + max_no_shows: self.options.num_no_shows_per_candidate, + last_tranche_with_no_show: 0, + sent_assignment: 0, + num_no_shows: 0, + max_tranche: 0, + needed_approvals: self.state.configuration.needed_approvals as u32, + }, + ); + } + } + per_candidate_data + } +} + +/// Helper function to build an overseer with the real implementation for `ApprovalDistribution` and +/// `ApprovalVoting` subystems and mock subsytems for all others. +fn build_overseer( + state: &ApprovalTestState, + network: &NetworkEmulatorHandle, + config: &TestConfiguration, + dependencies: &TestEnvironmentDependencies, + network_interface: &NetworkInterface, + network_receiver: NetworkInterfaceReceiver, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandleReal) { + let overseer_connector = OverseerConnector::with_event_capacity(6400000); + + let spawn_task_handle = dependencies.task_manager.spawn_handle(); + + let db = kvdb_memorydb::create(NUM_COLUMNS); + let db: polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter = + polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + let keystore = LocalKeystore::in_memory(); + + let system_clock = + PastSystemClock::new(SystemClock {}, state.delta_tick_from_generated.clone()); + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + TEST_CONFIG, + Arc::new(db), + Arc::new(keystore), + Box::new(TestSyncOracle {}), + state.approval_voting_metrics.clone(), + Box::new(system_clock.clone()), + ); + + let approval_distribution = + ApprovalDistribution::new(Metrics::register(Some(&dependencies.registry)).unwrap()); + let mock_chain_api = MockChainApi::new(state.build_chain_api_state()); + let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock }; + let mock_runtime_api = MockRuntimeApi::new( + config.clone(), + state.test_authorities.clone(), + state.candidate_hashes_by_block(), + state.candidate_events_by_block(), + Some(state.babe_epoch.clone()), + 1, + ); + let mock_tx_bridge = MockNetworkBridgeTx::new( + network.clone(), + network_interface.subsystem_sender(), + state.test_authorities.clone(), + ); + let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) + .replace_approval_distribution(|_| approval_distribution) + .replace_approval_voting(|_| approval_voting) + .replace_chain_api(|_| mock_chain_api) + .replace_chain_selection(|_| mock_chain_selection) + .replace_runtime_api(|_| mock_runtime_api) + .replace_network_bridge_tx(|_| mock_tx_bridge) + .replace_network_bridge_rx(|_| mock_rx_bridge); + + let (overseer, raw_handle) = + dummy.build_with_connector(overseer_connector).expect("Should not fail"); + + let overseer_handle = OverseerHandleReal::new(raw_handle); + (overseer, overseer_handle) +} + +/// Takes a test configuration and uses it to creates the `TestEnvironment`. +pub fn prepare_test( + config: TestConfiguration, + options: ApprovalsOptions, + with_prometheus_endpoint: bool, +) -> (TestEnvironment, ApprovalTestState) { + prepare_test_inner( + config, + TestEnvironmentDependencies::default(), + options, + with_prometheus_endpoint, + ) +} + +/// Build the test environment for an Approval benchmark. +fn prepare_test_inner( + config: TestConfiguration, + dependencies: TestEnvironmentDependencies, + options: ApprovalsOptions, + with_prometheus_endpoint: bool, +) -> (TestEnvironment, ApprovalTestState) { + gum::info!("Prepare test state"); + let state = ApprovalTestState::new(&config, options, &dependencies); + + gum::info!("Build network emulator"); + + let (network, network_interface, network_receiver) = + new_network(&config, &dependencies, &state.test_authorities, vec![Arc::new(state.clone())]); + + gum::info!("Build overseer"); + + let (overseer, overseer_handle) = build_overseer( + &state, + &network, + &config, + &dependencies, + &network_interface, + network_receiver, + ); + + ( + TestEnvironment::new( + dependencies, + config, + network, + overseer, + overseer_handle, + state.test_authorities.clone(), + with_prometheus_endpoint, + ), + state, + ) +} + +pub async fn bench_approvals( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: ApprovalTestState, +) -> BenchmarkUsage { + let producer_rx = state + .start_message_production( + env.network(), + env.overseer_handle().clone(), + env, + env.registry().clone(), + ) + .await; + bench_approvals_run(benchmark_name, env, state, producer_rx).await +} + +/// Runs the approval benchmark. +pub async fn bench_approvals_run( + benchmark_name: &str, + env: &mut TestEnvironment, + state: ApprovalTestState, + producer_rx: oneshot::Receiver<()>, +) -> BenchmarkUsage { + let config = env.config().clone(); + + env.metrics().set_n_validators(config.n_validators); + env.metrics().set_n_cores(config.n_cores); + + // First create the initialization messages that make sure that then node under + // tests receives notifications about the topology used and the connected peers. + let mut initialization_messages = env.network().generate_peer_connected(); + initialization_messages.extend(generate_new_session_topology( + &state.test_authorities, + ValidatorIndex(NODE_UNDER_TEST), + )); + for message in initialization_messages { + env.send_message(message).await; + } + + let start_marker = Instant::now(); + let real_clock = SystemClock {}; + state.delta_tick_from_generated.store( + real_clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, state.generated_state.initial_slot), + std::sync::atomic::Ordering::SeqCst, + ); + let system_clock = PastSystemClock::new(real_clock, state.delta_tick_from_generated.clone()); + + for block_num in 0..env.config().num_blocks { + let mut current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + + // Wait untill the time arrieves at the first slot under test. + while current_slot < state.generated_state.initial_slot { + sleep(Duration::from_millis(5)).await; + current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + } + + gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num + 1, env.config().num_blocks); + env.metrics().set_current_block(block_num); + let block_start_ts = Instant::now(); + + if let Some(block_info) = state.get_info_by_slot(current_slot) { + env.import_block(new_block_import_info(block_info.hash, block_info.block_number)) + .await; + } + + let block_time = Instant::now().sub(block_start_ts).as_millis() as u64; + env.metrics().set_block_time(block_time); + gum::info!("Block time {}", format!("{:?}ms", block_time).cyan()); + + system_clock + .wait(slot_number_to_tick(SLOT_DURATION_MILLIS, current_slot + 1)) + .await; + } + + // Wait for all blocks to be approved before exiting. + // This is an invariant of the benchmark, if this does not happen something went teribbly wrong. + while state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst) < + env.config().num_blocks as u32 + { + gum::info!( + "Waiting for all blocks to be approved current approved {:} num_sent {:} num_unique {:}", + state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + tokio::time::sleep(Duration::from_secs(6)).await; + } + + gum::info!("Awaiting producer to signal done"); + + producer_rx.await.expect("Failed to receive done from message producer"); + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + gum::info!("Requesting approval votes ms"); + + for info in &state.blocks { + for (index, candidates) in info.candidates.iter().enumerate() { + match candidates { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt_fetch, _head, _, _) => { + let (tx, rx) = oneshot::channel(); + + let msg = ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ); + env.send_message(AllMessages::ApprovalVoting(msg)).await; + + let result = rx.await.unwrap(); + + for (validator, _) in result.iter() { + info.votes + .get(validator.0 as usize) + .unwrap() + .get(index) + .unwrap() + .store(false, std::sync::atomic::Ordering::SeqCst); + } + }, + + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }; + } + } + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + + for state in &state.blocks { + for (validator, votes) in state + .votes + .as_ref() + .iter() + .enumerate() + .filter(|(validator, _)| *validator != NODE_UNDER_TEST as usize) + { + for (index, candidate) in votes.iter().enumerate() { + assert_eq!( + ( + validator, + index, + candidate.load(std::sync::atomic::Ordering::SeqCst), + state.hash + ), + (validator, index, false, state.hash) + ); + } + } + } + + env.stop().await; + + let duration: u128 = start_marker.elapsed().as_millis(); + gum::info!( + "All blocks processed in {} total_sent_messages_to_node {} total_sent_messages_from_node {} num_unique_messages {}", + format!("{:?}ms", duration).cyan(), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_from_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + + env.collect_resource_usage(benchmark_name, &["approval-distribution", "approval-voting"]) +} diff --git a/polkadot/node/subsystem-bench/src/lib/approval/test_message.rs b/polkadot/node/subsystem-bench/src/lib/approval/test_message.rs new file mode 100644 index 0000000000000000000000000000000000000000..63e383509be9ff6da6e1d5dd61baebc99c574bc4 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/approval/test_message.rs @@ -0,0 +1,305 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{ + approval::{ApprovalsOptions, BlockTestData, CandidateTestData}, + configuration::TestAuthorities, +}; +use itertools::Itertools; +use parity_scale_codec::{Decode, Encode}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex}; +use sc_network::PeerId; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct TestMessageInfo { + /// The actual message + pub msg: protocol_v3::ApprovalDistributionMessage, + /// The list of peers that would sends this message in a real topology. + /// It includes both the peers that would send the message because of the topology + /// or because of randomly chosing so. + pub sent_by: Vec, + /// The tranche at which this message should be sent. + pub tranche: u32, + /// The block hash this message refers to. + pub block_hash: Hash, +} + +impl std::hash::Hash for TestMessageInfo { + fn hash(&self, state: &mut H) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidates) in assignments { + (assignment.block_hash, assignment.validator).hash(state); + candidates.hash(state); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + for approval in approvals { + (approval.block_hash, approval.validator).hash(state); + approval.candidate_indices.hash(state); + } + }, + }; + } +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +/// A list of messages that depend of each-other, approvals cover one of the assignments and +/// vice-versa. +pub struct MessagesBundle { + pub assignments: Vec, + pub approvals: Vec, +} + +impl MessagesBundle { + /// The tranche when this bundle can be sent correctly, so no assignments or approvals will be + /// from the future. + pub fn tranche_to_send(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .max_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// The min tranche in the bundle. + pub fn min_tranche(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .min_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// Tells if the bundle is needed for sending. + /// We either send it because we need more assignments and approvals to approve the candidates + /// or because we configured the test to send messages untill a given tranche. + pub fn should_send( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + options: &ApprovalsOptions, + ) -> bool { + self.needed_for_approval(candidates_test_data) || + (!options.stop_when_approved && + self.min_tranche() <= options.last_considered_tranche) + } + + /// Tells if the bundle is needed because we need more messages to approve the candidates. + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + self.assignments + .iter() + .any(|message| message.needed_for_approval(candidates_test_data)) + } + + /// Mark the assignments in the bundle as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + self.assignments + .iter() + .for_each(|assignment| assignment.record_sent_assignment(candidates_test_data)); + } +} + +impl TestMessageInfo { + /// Tells if the message is an approval. + fn is_approval(&self) -> bool { + match self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => false, + protocol_v3::ApprovalDistributionMessage::Approvals(_) => true, + } + } + + /// Records an approval. + /// We use this to check after all messages have been processed that we didn't loose any + /// message. + pub fn record_vote(&self, state: &BlockTestData) { + if self.is_approval() { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + state + .votes + .get(approval.validator.0 as usize) + .unwrap() + .get(candidate_index) + .unwrap() + .store(true, std::sync::atomic::Ordering::SeqCst); + } + }, + } + } + } + + /// Mark the assignments in the message as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&(assignment.block_hash, candidate_index as CandidateIndex)) + .unwrap(); + candidate_test_data.mark_sent_assignment(self.tranche) + } + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(_approvals) => todo!(), + } + } + + /// Returns a list of candidates indicies in this message + pub fn candidate_indices(&self) -> HashSet { + let mut unique_candidate_indicies = HashSet::new(); + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + for (_assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + } + unique_candidate_indicies + } + + /// Marks this message as no-shows if the number of configured no-shows is above the registered + /// no-shows. + /// Returns true if the message is a no-show. + pub fn no_show_if_required( + &self, + assignments: &[TestMessageInfo], + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let mut should_no_show = false; + if self.is_approval() { + let covered_candidates = assignments + .iter() + .map(|assignment| (assignment, assignment.candidate_indices())) + .collect_vec(); + + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + assert_eq!(approvals.len(), 1); + + for approval in approvals { + should_no_show = should_no_show || + approval.candidate_indices.iter_ones().all(|candidate_index| { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.should_no_show(assignment.0.tranche) + }); + + if should_no_show { + for candidate_index in approval.candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.record_no_show(assignment.0.tranche) + } + } + } + }, + } + } + should_no_show + } + + /// Tells if a message is needed for approval + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + assignments.iter().any(|(assignment, candidate_indices)| { + candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(assignment.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + approvals.iter().any(|approval| { + approval.candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(approval.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + } + } + + /// Splits a message into multiple messages based on what peers should send this message. + /// It build a HashMap of messages that should be sent by each peer. + pub fn split_by_peer_id( + self, + authorities: &TestAuthorities, + ) -> HashMap<(ValidatorIndex, PeerId), Vec> { + let mut result: HashMap<(ValidatorIndex, PeerId), Vec> = HashMap::new(); + + for validator_index in &self.sent_by { + let peer = authorities.peer_ids.get(validator_index.0 as usize).unwrap(); + result.entry((*validator_index, *peer)).or_default().push(TestMessageInfo { + msg: self.msg.clone(), + sent_by: Default::default(), + tranche: self.tranche, + block_hash: self.block_hash, + }); + } + result + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/availability/av_store_helpers.rs b/polkadot/node/subsystem-bench/src/lib/availability/av_store_helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..3300def2235ee0ea90f466316f9bc5a7a965869c --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/availability/av_store_helpers.rs @@ -0,0 +1,42 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{environment::TestEnvironmentDependencies, mock::TestSyncOracle}; +use polkadot_node_core_av_store::{AvailabilityStoreSubsystem, Config}; +use polkadot_node_metrics::metrics::Metrics; +use polkadot_node_subsystem_util::database::Database; +use std::sync::Arc; + +mod columns { + pub const DATA: u32 = 0; + pub const META: u32 = 1; + pub const NUM_COLUMNS: u32 = 2; +} + +const TEST_CONFIG: Config = Config { col_data: columns::DATA, col_meta: columns::META }; + +pub fn new_av_store(dependencies: &TestEnvironmentDependencies) -> AvailabilityStoreSubsystem { + let metrics = Metrics::try_register(&dependencies.registry).unwrap(); + + AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(TestSyncOracle {}), metrics) +} + +fn test_store() -> Arc { + let db = kvdb_memorydb::create(columns::NUM_COLUMNS); + let db = + polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[columns::META]); + Arc::new(db) +} diff --git a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc4e1e4031025ca3f868265ac2e8c902f6fc8c23 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs @@ -0,0 +1,695 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::{ + configuration::TestConfiguration, + dummy_builder, + environment::{TestEnvironment, TestEnvironmentDependencies, GENESIS_HASH}, + mock::{ + av_store::{self, MockAvailabilityStore}, + chain_api::{ChainApiState, MockChainApi}, + network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx}, + runtime_api::{self, MockRuntimeApi}, + AlwaysSupportsParachains, + }, + network::new_network, + usage::BenchmarkUsage, +}; +use av_store::NetworkAvailabilityState; +use av_store_helpers::new_av_store; +use bitvec::bitvec; +use colored::Colorize; +use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; +use itertools::Itertools; +use parity_scale_codec::Encode; +use polkadot_availability_bitfield_distribution::BitfieldDistribution; +use polkadot_availability_distribution::{ + AvailabilityDistributionSubsystem, IncomingRequestReceivers, +}; +use polkadot_availability_recovery::AvailabilityRecoverySubsystem; +use polkadot_node_core_av_store::AvailabilityStoreSubsystem; +use polkadot_node_metrics::metrics::Metrics; +use polkadot_node_network_protocol::{ + request_response::{v1::ChunkFetchingRequest, IncomingRequest, ReqProtocolNames}, + OurView, Versioned, VersionedValidationProtocol, +}; +use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV}; +use polkadot_node_subsystem::{ + messages::{AllMessages, AvailabilityRecoveryMessage}, + Overseer, OverseerConnector, SpawnGlue, +}; +use polkadot_node_subsystem_test_helpers::{ + derive_erasure_chunks_with_proofs_and_root, mock::new_block_import_info, +}; +use polkadot_node_subsystem_types::{ + messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, + Span, +}; +use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; +use polkadot_primitives::{ + AvailabilityBitfield, BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData, + Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex, +}; +use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash}; +use sc_network::{ + request_responses::{IncomingRequest as RawIncomingRequest, ProtocolConfig}, + PeerId, +}; +use sc_service::SpawnTaskHandle; +use serde::{Deserialize, Serialize}; +use sp_core::H256; +use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; + +mod av_store_helpers; + +const LOG_TARGET: &str = "subsystem-bench::availability"; + +#[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, +} + +pub enum TestDataAvailability { + Read(DataAvailabilityReadOptions), + Write, +} + +fn build_overseer_for_availability_read( + spawn_task_handle: SpawnTaskHandle, + runtime_api: MockRuntimeApi, + av_store: MockAvailabilityStore, + network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx), + availability_recovery: AvailabilityRecoverySubsystem, + dependencies: &TestEnvironmentDependencies, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { + let overseer_connector = OverseerConnector::with_event_capacity(64000); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); + let builder = dummy + .replace_runtime_api(|_| runtime_api) + .replace_availability_store(|_| av_store) + .replace_network_bridge_tx(|_| network_bridge.0) + .replace_network_bridge_rx(|_| network_bridge.1) + .replace_availability_recovery(|_| availability_recovery); + + let (overseer, raw_handle) = + builder.build_with_connector(overseer_connector).expect("Should not fail"); + + (overseer, OverseerHandle::new(raw_handle)) +} + +#[allow(clippy::too_many_arguments)] +fn build_overseer_for_availability_write( + spawn_task_handle: SpawnTaskHandle, + runtime_api: MockRuntimeApi, + network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx), + availability_distribution: AvailabilityDistributionSubsystem, + chain_api: MockChainApi, + availability_store: AvailabilityStoreSubsystem, + bitfield_distribution: BitfieldDistribution, + dependencies: &TestEnvironmentDependencies, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { + let overseer_connector = OverseerConnector::with_event_capacity(64000); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); + let builder = dummy + .replace_runtime_api(|_| runtime_api) + .replace_availability_store(|_| availability_store) + .replace_network_bridge_tx(|_| network_bridge.0) + .replace_network_bridge_rx(|_| network_bridge.1) + .replace_chain_api(|_| chain_api) + .replace_bitfield_distribution(|_| bitfield_distribution) + // This is needed to test own chunk recovery for `n_cores`. + .replace_availability_distribution(|_| availability_distribution); + + 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 create the `TestEnvironment`. +pub fn prepare_test( + config: TestConfiguration, + state: &mut TestState, + mode: TestDataAvailability, + with_prometheus_endpoint: bool, +) -> (TestEnvironment, Vec) { + prepare_test_inner( + config, + state, + mode, + TestEnvironmentDependencies::default(), + with_prometheus_endpoint, + ) +} + +fn prepare_test_inner( + config: TestConfiguration, + state: &mut TestState, + mode: TestDataAvailability, + dependencies: TestEnvironmentDependencies, + with_prometheus_endpoint: bool, +) -> (TestEnvironment, Vec) { + // Generate test authorities. + let test_authorities = config.generate_authorities(); + + let mut candidate_hashes: HashMap> = HashMap::new(); + + // Prepare per block candidates. + // Genesis block is always finalized, so we start at 1. + for block_num in 1..=config.num_blocks { + for _ in 0..config.n_cores { + candidate_hashes + .entry(Hash::repeat_byte(block_num as u8)) + .or_default() + .push(state.next_candidate().expect("Cycle iterator")) + } + + // First candidate is our backed candidate. + state.backed_candidates.push( + candidate_hashes + .get(&Hash::repeat_byte(block_num as u8)) + .expect("just inserted above") + .first() + .expect("just inserted above") + .clone(), + ); + } + + let runtime_api = runtime_api::MockRuntimeApi::new( + config.clone(), + test_authorities.clone(), + candidate_hashes, + Default::default(), + Default::default(), + 0, + ); + + let availability_state = NetworkAvailabilityState { + candidate_hashes: state.candidate_hashes.clone(), + available_data: state.available_data.clone(), + chunks: state.chunks.clone(), + }; + + let mut req_cfgs = Vec::new(); + + let (collation_req_receiver, collation_req_cfg) = + IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None)); + req_cfgs.push(collation_req_cfg); + + let (pov_req_receiver, pov_req_cfg) = + IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None)); + + let (chunk_req_receiver, chunk_req_cfg) = + IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None)); + req_cfgs.push(pov_req_cfg); + + let (network, network_interface, network_receiver) = + new_network(&config, &dependencies, &test_authorities, vec![Arc::new(availability_state)]); + + let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new( + network.clone(), + network_interface.subsystem_sender(), + test_authorities.clone(), + ); + + let network_bridge_rx = + network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_cfg.clone())); + + let (overseer, overseer_handle) = match &mode { + TestDataAvailability::Read(options) => { + let use_fast_path = options.fetch_from_backers; + + 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(), + ) + }; + + // Use a mocked av-store. + let av_store = av_store::MockAvailabilityStore::new( + state.chunks.clone(), + state.candidate_hashes.clone(), + ); + + build_overseer_for_availability_read( + dependencies.task_manager.spawn_handle(), + runtime_api, + av_store, + (network_bridge_tx, network_bridge_rx), + subsystem, + &dependencies, + ) + }, + TestDataAvailability::Write => { + let availability_distribution = AvailabilityDistributionSubsystem::new( + test_authorities.keyring.keystore(), + IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver }, + Metrics::try_register(&dependencies.registry).unwrap(), + ); + + let block_headers = (1..=config.num_blocks) + .map(|block_number| { + ( + Hash::repeat_byte(block_number as u8), + Header { + digest: Default::default(), + number: block_number as BlockNumber, + parent_hash: Default::default(), + extrinsics_root: Default::default(), + state_root: Default::default(), + }, + ) + }) + .collect::>(); + + let chain_api_state = ChainApiState { block_headers }; + let chain_api = MockChainApi::new(chain_api_state); + let bitfield_distribution = + BitfieldDistribution::new(Metrics::try_register(&dependencies.registry).unwrap()); + build_overseer_for_availability_write( + dependencies.task_manager.spawn_handle(), + runtime_api, + (network_bridge_tx, network_bridge_rx), + availability_distribution, + chain_api, + new_av_store(&dependencies), + bitfield_distribution, + &dependencies, + ) + }, + }; + + ( + TestEnvironment::new( + dependencies, + config, + network, + overseer, + overseer_handle, + test_authorities, + with_prometheus_endpoint, + ), + req_cfgs, + ) +} + +#[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>, + // Per relay chain block - candidate backed by our backing group + backed_candidates: Vec, +} + +impl TestState { + 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); + } + + gum::info!(target: LOG_TARGET, "{}","Created test environment.".bright_blue()); + + let mut _self = Self { + available_data, + candidate_receipt_templates, + chunks, + pov_size_to_candidate, + pov_sizes: Vec::from(config.pov_sizes()).into_iter().cycle(), + candidate_hashes: HashMap::new(), + candidates: Vec::new().into_iter().cycle(), + backed_candidates: Vec::new(), + config, + }; + + _self.generate_candidates(); + _self + } + + pub fn backed_candidates(&mut self) -> &mut Vec { + &mut self.backed_candidates + } +} + +pub async fn benchmark_availability_read( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { + let config = env.config().clone(); + + env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await; + + let test_start = 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 1..=env.config().num_blocks { + gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num, 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!(target: LOG_TARGET, "{}", 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!(target: LOG_TARGET, "All work for block completed in {}", format!("{:?}ms", block_time).cyan()); + } + + let duration: u128 = test_start.elapsed().as_millis(); + let availability_bytes = availability_bytes / 1024; + gum::info!(target: LOG_TARGET, "All blocks processed in {}", format!("{:?}ms", duration).cyan()); + gum::info!(target: LOG_TARGET, + "Throughput: {}", + format!("{} KiB/block", availability_bytes / env.config().num_blocks as u128).bright_red() + ); + gum::info!(target: LOG_TARGET, + "Avg block time: {}", + format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() + ); + + env.stop().await; + env.collect_resource_usage(benchmark_name, &["availability-recovery"]) +} + +pub async fn benchmark_availability_write( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { + let config = env.config().clone(); + + env.metrics().set_n_validators(config.n_validators); + env.metrics().set_n_cores(config.n_cores); + + gum::info!(target: LOG_TARGET, "Seeding availability store with candidates ..."); + for backed_candidate in state.backed_candidates().clone() { + let candidate_index = *state.candidate_hashes.get(&backed_candidate.hash()).unwrap(); + let available_data = state.available_data[candidate_index].clone(); + let (tx, rx) = oneshot::channel(); + env.send_message(AllMessages::AvailabilityStore( + AvailabilityStoreMessage::StoreAvailableData { + candidate_hash: backed_candidate.hash(), + n_validators: config.n_validators as u32, + available_data, + expected_erasure_root: backed_candidate.descriptor().erasure_root, + tx, + }, + )) + .await; + + rx.await + .unwrap() + .expect("Test candidates are stored nicely in availability store"); + } + + gum::info!(target: LOG_TARGET, "Done"); + + let test_start = Instant::now(); + + for block_num in 1..=env.config().num_blocks { + gum::info!(target: LOG_TARGET, "Current block #{}", block_num); + env.metrics().set_current_block(block_num); + + let block_start_ts = Instant::now(); + let relay_block_hash = Hash::repeat_byte(block_num as u8); + env.import_block(new_block_import_info(relay_block_hash, block_num as BlockNumber)) + .await; + + // Inform bitfield distribution about our view of current test block + let message = polkadot_node_subsystem_types::messages::BitfieldDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::OurViewChange(OurView::new(vec![(relay_block_hash, Arc::new(Span::Disabled))], 0)) + ); + env.send_message(AllMessages::BitfieldDistribution(message)).await; + + let chunk_fetch_start_ts = Instant::now(); + + // Request chunks of our own backed candidate from all other validators. + let mut receivers = Vec::new(); + for index in 1..config.n_validators { + let (pending_response, pending_response_receiver) = oneshot::channel(); + + let request = RawIncomingRequest { + peer: PeerId::random(), + payload: ChunkFetchingRequest { + candidate_hash: state.backed_candidates()[block_num - 1].hash(), + index: ValidatorIndex(index as u32), + } + .encode(), + pending_response, + }; + + let peer = env + .authorities() + .validator_authority_id + .get(index) + .expect("all validators have keys"); + + if env.network().is_peer_connected(peer) && + env.network().send_request_from_peer(peer, request).is_ok() + { + receivers.push(pending_response_receiver); + } + } + + gum::info!(target: LOG_TARGET, "Waiting for all emulated peers to receive their chunk from us ..."); + for receiver in receivers.into_iter() { + let response = receiver.await.expect("Chunk is always served successfully"); + // TODO: check if chunk is the one the peer expects to receive. + assert!(response.result.is_ok()); + } + + let chunk_fetch_duration = Instant::now().sub(chunk_fetch_start_ts).as_millis(); + + gum::info!(target: LOG_TARGET, "All chunks received in {}ms", chunk_fetch_duration); + + let signing_context = SigningContext { session_index: 0, parent_hash: relay_block_hash }; + let network = env.network().clone(); + let authorities = env.authorities().clone(); + let n_validators = config.n_validators; + + // Spawn a task that will generate `n_validator` - 1 signed bitfiends and + // send them from the emulated peers to the subsystem. + // TODO: Implement topology. + env.spawn_blocking("send-bitfields", async move { + for index in 1..n_validators { + let validator_public = + authorities.validator_public.get(index).expect("All validator keys are known"); + + // Node has all the chunks in the world. + let payload: AvailabilityBitfield = + AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]); + // TODO(soon): Use pre-signed messages. This is quite intensive on the CPU. + let signed_bitfield = Signed::::sign( + &authorities.keyring.keystore(), + payload, + &signing_context, + ValidatorIndex(index as u32), + validator_public, + ) + .ok() + .flatten() + .expect("should be signed"); + + let from_peer = &authorities.validator_authority_id[index]; + + let message = peer_bitfield_message_v2(relay_block_hash, signed_bitfield); + + // Send the action from peer only if it is connected to our node. + if network.is_peer_connected(from_peer) { + let _ = network.send_message_from_peer(from_peer, message); + } + } + }); + + gum::info!( + "Waiting for {} bitfields to be received and processed", + config.connected_count() + ); + + // Wait for all bitfields to be processed. + env.wait_until_metric( + "polkadot_parachain_received_availabilty_bitfields_total", + None, + |value| value == (config.connected_count() * block_num) as f64, + ) + .await; + + gum::info!(target: LOG_TARGET, "All bitfields processed"); + + let block_time = Instant::now().sub(block_start_ts).as_millis() as u64; + env.metrics().set_block_time(block_time); + gum::info!(target: LOG_TARGET, "All work for block completed in {}", format!("{:?}ms", block_time).cyan()); + } + + let duration: u128 = test_start.elapsed().as_millis(); + gum::info!(target: LOG_TARGET, "All blocks processed in {}", format!("{:?}ms", duration).cyan()); + gum::info!(target: LOG_TARGET, + "Avg block time: {}", + format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() + ); + + env.stop().await; + env.collect_resource_usage( + benchmark_name, + &["availability-distribution", "bitfield-distribution", "availability-store"], + ) +} + +pub fn peer_bitfield_message_v2( + relay_hash: H256, + signed_bitfield: Signed, +) -> VersionedValidationProtocol { + let bitfield = polkadot_node_network_protocol::v2::BitfieldDistributionMessage::Bitfield( + relay_hash, + signed_bitfield.into(), + ); + + Versioned::V2(polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution( + bitfield, + )) +} diff --git a/polkadot/node/subsystem-bench/src/lib/configuration.rs b/polkadot/node/subsystem-bench/src/lib/configuration.rs new file mode 100644 index 0000000000000000000000000000000000000000..17c81c4fd355bd15c5e2a6c1005e90313ca80615 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/configuration.rs @@ -0,0 +1,261 @@ +// 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 crate::keyring::Keyring; +use itertools::Itertools; +use polkadot_primitives::{AssignmentId, AuthorityDiscoveryId, ValidatorId}; +use rand::thread_rng; +use rand_distr::{Distribution, Normal, Uniform}; +use sc_network::PeerId; +use serde::{Deserialize, Serialize}; +use sp_consensus_babe::AuthorityId; +use std::collections::HashMap; + +/// Peer networking latency configuration. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct PeerLatency { + /// The mean latency(milliseconds) of the peers. + pub mean_latency_ms: usize, + /// The standard deviation + pub std_dev: f64, +} + +// Based on Kusama `max_validators` +fn default_n_validators() -> usize { + 300 +} + +// Based on Kusama cores +fn default_n_cores() -> usize { + 60 +} + +// Default PoV size in KiB. +fn default_pov_size() -> usize { + 5 * 1024 +} + +// Default bandwidth in bytes, based stats from Kusama validators +fn default_bandwidth() -> usize { + 42 * 1024 * 1024 +} + +// Default peer latency +fn default_peer_latency() -> Option { + Some(PeerLatency { mean_latency_ms: 30, std_dev: 2.0 }) +} + +// Default connectivity percentage +fn default_connectivity() -> usize { + 90 +} + +// Default backing group size +fn default_backing_group_size() -> usize { + 5 +} + +// Default needed approvals +fn default_needed_approvals() -> usize { + 30 +} + +fn default_zeroth_delay_tranche_width() -> usize { + 0 +} + +fn default_relay_vrf_modulo_samples() -> usize { + 6 +} + +fn default_n_delay_tranches() -> usize { + 89 +} +fn default_no_show_slots() -> usize { + 3 +} + +/// The test input parameters +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestConfiguration { + /// Number of validators + #[serde(default = "default_n_validators")] + pub n_validators: usize, + /// Number of cores + #[serde(default = "default_n_cores")] + pub n_cores: usize, + /// The number of needed votes to approve a candidate. + #[serde(default = "default_needed_approvals")] + pub needed_approvals: usize, + #[serde(default = "default_zeroth_delay_tranche_width")] + pub zeroth_delay_tranche_width: usize, + #[serde(default = "default_relay_vrf_modulo_samples")] + pub relay_vrf_modulo_samples: usize, + #[serde(default = "default_n_delay_tranches")] + pub n_delay_tranches: usize, + #[serde(default = "default_no_show_slots")] + pub no_show_slots: usize, + /// Maximum backing group size + #[serde(default = "default_backing_group_size")] + pub max_validators_per_core: usize, + /// 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)] + pub 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 (round trip time) wrt node under test + #[serde(default = "default_peer_latency")] + pub latency: Option, + /// Connectivity ratio, the percentage of peers we are connected to, but as part of the + /// topology. + #[serde(default = "default_connectivity")] + pub connectivity: usize, + /// Number of blocks to run the test for + pub num_blocks: usize, +} + +impl Default for TestConfiguration { + fn default() -> Self { + Self { + n_validators: default_n_validators(), + n_cores: default_n_cores(), + needed_approvals: default_needed_approvals(), + zeroth_delay_tranche_width: default_zeroth_delay_tranche_width(), + relay_vrf_modulo_samples: default_relay_vrf_modulo_samples(), + n_delay_tranches: default_n_delay_tranches(), + no_show_slots: default_no_show_slots(), + max_validators_per_core: default_backing_group_size(), + min_pov_size: default_pov_size(), + max_pov_size: default_pov_size(), + pov_sizes: Default::default(), + peer_bandwidth: default_bandwidth(), + bandwidth: default_bandwidth(), + latency: default_peer_latency(), + connectivity: default_connectivity(), + num_blocks: Default::default(), + } + } +} + +impl TestConfiguration { + pub fn generate_pov_sizes(&mut self) { + self.pov_sizes = generate_pov_sizes(self.n_cores, self.min_pov_size, self.max_pov_size); + } + + pub fn pov_sizes(&self) -> &[usize] { + &self.pov_sizes + } + /// Return the number of peers connected to our node. + pub fn connected_count(&self) -> usize { + ((self.n_validators - 1) as f64 / (100.0 / self.connectivity as f64)) as usize + } + + /// Generates the authority keys we need for the network emulation. + pub fn generate_authorities(&self) -> TestAuthorities { + let keyring = Keyring::default(); + + let key_seeds = (0..self.n_validators) + .map(|peer_index| format!("//Node{}", peer_index)) + .collect_vec(); + + let keys = key_seeds + .iter() + .map(|seed| keyring.sr25519_new(seed.as_str())) + .collect::>(); + + // Generate keys and peers ids in each of the format needed by the tests. + let validator_public: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + + let validator_authority_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + + let validator_babe_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + + let validator_assignment_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + let peer_ids: Vec = keys.iter().map(|_| PeerId::random()).collect::>(); + + let peer_id_to_authority = peer_ids + .iter() + .zip(validator_authority_id.iter()) + .map(|(peer_id, authorithy_id)| (*peer_id, authorithy_id.clone())) + .collect(); + + TestAuthorities { + keyring, + validator_public, + validator_authority_id, + peer_ids, + validator_babe_id, + validator_assignment_id, + key_seeds, + peer_id_to_authority, + } + } +} + +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() +} + +fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize { + random_uniform_sample(min_pov_size, max_pov_size) +} + +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() +} + +/// Helper struct for authority related state. +#[derive(Clone)] +pub struct TestAuthorities { + pub keyring: Keyring, + pub validator_public: Vec, + pub validator_authority_id: Vec, + pub validator_babe_id: Vec, + pub validator_assignment_id: Vec, + pub key_seeds: Vec, + pub peer_ids: Vec, + pub peer_id_to_authority: HashMap, +} + +/// Sample latency (in milliseconds) from a normal distribution with parameters +/// specified in `maybe_peer_latency`. +pub fn random_latency(maybe_peer_latency: Option<&PeerLatency>) -> usize { + maybe_peer_latency + .map(|latency_config| { + Normal::new(latency_config.mean_latency_ms as f64, latency_config.std_dev) + .expect("normal distribution parameters are good") + .sample(&mut thread_rng()) + }) + .unwrap_or(0.0) as usize +} diff --git a/polkadot/node/subsystem-bench/src/lib/display.rs b/polkadot/node/subsystem-bench/src/lib/display.rs new file mode 100644 index 0000000000000000000000000000000000000000..b153d54a7c36f43bc110dde9443f2413a34d9af6 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/display.rs @@ -0,0 +1,202 @@ +// 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 crate::configuration::TestConfiguration; +use colored::Colorize; +use prometheus::{ + proto::{MetricFamily, MetricType}, + Registry, +}; +use std::fmt::Display; + +const LOG_TARGET: &str = "subsystem-bench::display"; + +#[derive(Default, Debug)] +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() + } + + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(&self, metric_name: &str, value: f64) -> bool { + self.sum_by(metric_name) < value + } + + pub fn subset_with_label_value(&self, label_name: &str, label_value: &str) -> MetricCollection { + self.0 + .iter() + .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_count() as f64, + }); + }, + MetricType::SUMMARY => { + unimplemented!(); + }, + MetricType::UNTYPED => { + unimplemented!(); + }, + } + } + } + test_metrics.into() +} + +impl Display for TestConfiguration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}, {}, {}, {}, {}", + format!("n_validators = {}", self.n_validators).blue(), + format!("n_cores = {}", self.n_cores).blue(), + format!("pov_size = {} - {}", self.min_pov_size, self.max_pov_size).bright_black(), + format!("connectivity = {}", self.connectivity).bright_black(), + format!("latency = {:?}", self.latency).bright_black(), + ) + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/environment.rs b/polkadot/node/subsystem-bench/src/lib/environment.rs new file mode 100644 index 0000000000000000000000000000000000000000..958ed50d0894b76ddb66978950971620dceb4aa0 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/environment.rs @@ -0,0 +1,413 @@ +// 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::{ + configuration::{TestAuthorities, TestConfiguration}, + mock::AlwaysSupportsParachains, + network::NetworkEmulatorHandle, + usage::{BenchmarkUsage, ResourceUsage}, +}; +use core::time::Duration; +use futures::{Future, FutureExt}; +use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt}; +use polkadot_node_subsystem_types::Hash; +use polkadot_node_subsystem_util::metrics::prometheus::{ + self, Gauge, Histogram, PrometheusError, Registry, U64, +}; +use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; +use sc_service::{SpawnTaskHandle, TaskManager}; +use std::net::{Ipv4Addr, SocketAddr}; +use tokio::runtime::Handle; + +const LOG_TARGET: &str = "subsystem-bench::environment"; + +/// 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 buckets = prometheus::exponential_buckets(16384.0, 2.0, 9) + .expect("arguments are always valid; qed"); + + 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. +pub 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: NetworkEmulatorHandle, + /// Configuration/env metrics + metrics: TestEnvironmentMetrics, + /// Test authorities generated from the configuration. + authorities: TestAuthorities, +} + +impl TestEnvironment { + /// Create a new test environment + pub fn new( + dependencies: TestEnvironmentDependencies, + config: TestConfiguration, + network: NetworkEmulatorHandle, + overseer: Overseer, AlwaysSupportsParachains>, + overseer_handle: OverseerHandle, + authorities: TestAuthorities, + with_prometheus_endpoint: bool, + ) -> 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()); + + if with_prometheus_endpoint { + 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, + authorities, + } + } + + /// Returns the test configuration. + pub fn config(&self) -> &TestConfiguration { + &self.config + } + + /// Returns a reference to the inner network emulator handle. + pub fn network(&self) -> &NetworkEmulatorHandle { + &self.network + } + + /// Returns a reference to the overseer handle. + pub fn overseer_handle(&self) -> &OverseerHandle { + &self.overseer_handle + } + + /// Returns the Prometheus registry. + pub fn registry(&self) -> &Registry { + &self.dependencies.registry + } + + /// Spawn a named task in the `test-environment` task group. + #[allow(unused)] + pub fn spawn(&self, name: &'static str, task: impl Future + Send + 'static) { + self.dependencies + .task_manager + .spawn_handle() + .spawn(name, "test-environment", task); + } + + /// Spawn a blocking named task in the `test-environment` task group. + pub fn spawn_blocking( + &self, + name: &'static str, + task: impl Future + Send + 'static, + ) { + self.dependencies.task_manager.spawn_handle().spawn_blocking( + name, + "test-environment", + task, + ); + } + /// Returns a reference to the test environment metrics instance + pub fn metrics(&self) -> &TestEnvironmentMetrics { + &self.metrics + } + + /// Returns a handle to the tokio runtime. + pub fn runtime(&self) -> Handle { + self.runtime_handle.clone() + } + + /// Returns a reference to the authority keys used in the test. + pub fn authorities(&self) -> &TestAuthorities { + &self.authorities + } + + /// 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; + } + + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(registry: &Registry, metric_name: &str, value: f64) -> bool { + let test_metrics = super::display::parse_metrics(registry); + test_metrics.metric_lower_than(metric_name, value) + } + + /// Blocks until `metric_name` >= `value` + pub async fn wait_until_metric( + &self, + metric_name: &str, + label: Option<(&str, &str)>, + condition: impl Fn(f64) -> bool, + ) { + loop { + let test_metrics = if let Some((label_name, label_value)) = label { + super::display::parse_metrics(self.registry()) + .subset_with_label_value(label_name, label_value) + } else { + super::display::parse_metrics(self.registry()) + }; + let current_value = test_metrics.sum_by(metric_name); + + gum::debug!(target: LOG_TARGET, metric_name, current_value, "Waiting for metric"); + if condition(current_value) { + break + } + // Check value every 50ms. + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + } + } + + pub fn collect_resource_usage( + &self, + benchmark_name: &str, + subsystems_under_test: &[&str], + ) -> BenchmarkUsage { + BenchmarkUsage { + benchmark_name: benchmark_name.to_string(), + network_usage: self.network_usage(), + cpu_usage: self.cpu_usage(subsystems_under_test), + } + } + + fn network_usage(&self) -> Vec { + let stats = self.network().peer_stats(0); + let total_node_received = (stats.received() / 1024) as f64; + let total_node_sent = (stats.sent() / 1024) as f64; + let num_blocks = self.config().num_blocks as f64; + + vec![ + ResourceUsage { + resource_name: "Received from peers".to_string(), + total: total_node_received, + per_block: total_node_received / num_blocks, + }, + ResourceUsage { + resource_name: "Sent to peers".to_string(), + total: total_node_sent, + per_block: total_node_sent / num_blocks, + }, + ] + } + + fn cpu_usage(&self, subsystems_under_test: &[&str]) -> Vec { + let test_metrics = super::display::parse_metrics(self.registry()); + let mut usage = vec![]; + let num_blocks = self.config().num_blocks as f64; + + for subsystem in subsystems_under_test.iter() { + let subsystem_cpu_metrics = + test_metrics.subset_with_label_value("task_group", subsystem); + let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); + usage.push(ResourceUsage { + resource_name: subsystem.to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); + } + + let test_env_cpu_metrics = + test_metrics.subset_with_label_value("task_group", "test-environment"); + let total_cpu = test_env_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); + + usage.push(ResourceUsage { + resource_name: "Test environment".to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); + + usage + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/keyring.rs b/polkadot/node/subsystem-bench/src/lib/keyring.rs new file mode 100644 index 0000000000000000000000000000000000000000..c290d30b46fbe1a2a0db04155d8460c288fd98ff --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/keyring.rs @@ -0,0 +1,50 @@ +// 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_primitives::ValidatorId; +use sc_keystore::LocalKeystore; +use sp_application_crypto::AppCrypto; +use sp_core::sr25519::Public; +use sp_keystore::Keystore; +use std::sync::Arc; + +/// Set of test accounts generated and kept safe by a keystore. +#[derive(Clone)] +pub struct Keyring { + keystore: Arc, +} + +impl Default for Keyring { + fn default() -> Self { + Self { keystore: Arc::new(LocalKeystore::in_memory()) } + } +} + +impl Keyring { + pub fn sr25519_new(&self, seed: &str) -> Public { + self.keystore + .sr25519_generate_new(ValidatorId::ID, Some(seed)) + .expect("Insert key into keystore") + } + + pub fn keystore(&self) -> Arc { + self.keystore.clone() + } + + pub fn keystore_ref(&self) -> &LocalKeystore { + self.keystore.as_ref() + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/lib.rs b/polkadot/node/subsystem-bench/src/lib/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef2724abc98920c79d8dd9d94f97bed32b0ab8e2 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/lib.rs @@ -0,0 +1,29 @@ +// 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 validator index that represent the node that is under test. +pub const NODE_UNDER_TEST: u32 = 0; + +pub mod approval; +pub mod availability; +pub mod configuration; +pub(crate) mod display; +pub(crate) mod environment; +pub(crate) mod keyring; +pub(crate) mod mock; +pub(crate) mod network; +pub mod usage; +pub mod utils; diff --git a/polkadot/node/subsystem-bench/src/lib/mock/av_store.rs b/polkadot/node/subsystem-bench/src/lib/mock/av_store.rs new file mode 100644 index 0000000000000000000000000000000000000000..9064f17940f004fc123bc25f24ad5de1704e2d38 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/av_store.rs @@ -0,0 +1,211 @@ +// 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 crate::network::{HandleNetworkMessage, NetworkMessage}; +use futures::{channel::oneshot, FutureExt}; +use parity_scale_codec::Encode; +use polkadot_node_network_protocol::request_response::{ + v1::{AvailableDataFetchingResponse, ChunkFetchingResponse, ChunkResponse}, + Requests, +}; +use polkadot_node_primitives::{AvailableData, ErasureChunk}; +use polkadot_node_subsystem::{ + messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::CandidateHash; +use sc_network::ProtocolName; +use std::collections::HashMap; + +pub struct AvailabilityStoreState { + candidate_hashes: HashMap, + chunks: Vec>, +} + +const LOG_TARGET: &str = "subsystem-bench::av-store-mock"; + +/// Mockup helper. Contains Ccunks and full availability data of all parachain blocks +/// used in a test. +pub struct NetworkAvailabilityState { + pub candidate_hashes: HashMap, + pub available_data: Vec, + pub chunks: Vec>, +} + +// Implement access to the state. +impl HandleNetworkMessage for NetworkAvailabilityState { + fn handle( + &self, + message: NetworkMessage, + _node_sender: &mut futures::channel::mpsc::UnboundedSender, + ) -> Option { + match message { + NetworkMessage::RequestFromNode(peer, request) => match request { + Requests::ChunkFetchingV1(outgoing_request) => { + gum::debug!(target: LOG_TARGET, request = ?outgoing_request, "Received `RequestFromNode`"); + let validator_index: usize = outgoing_request.payload.index.0 as usize; + let candidate_hash = outgoing_request.payload.candidate_hash; + + let candidate_index = self + .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.chunks.get(*candidate_index).unwrap()[validator_index].clone().into(); + let response = Ok(( + ChunkFetchingResponse::from(Some(chunk)).encode(), + ProtocolName::Static("dummy"), + )); + + if let Err(err) = outgoing_request.pending_response.send(response) { + gum::error!(target: LOG_TARGET, ?err, "Failed to send `ChunkFetchingResponse`"); + } + + None + }, + Requests::AvailableDataFetchingV1(outgoing_request) => { + let candidate_hash = outgoing_request.payload.candidate_hash; + let candidate_index = self + .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 available_data = self.available_data.get(*candidate_index).unwrap().clone(); + + let response = Ok(( + AvailableDataFetchingResponse::from(Some(available_data)).encode(), + ProtocolName::Static("dummy"), + )); + outgoing_request + .pending_response + .send(response) + .expect("Response is always sent successfully"); + None + }, + _ => Some(NetworkMessage::RequestFromNode(peer, request)), + }, + + message => Some(message), + } + } +} + +/// 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)); + }, + AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk, tx } => { + gum::debug!(target: LOG_TARGET, chunk_index = ?chunk.index ,candidate_hash = ?candidate_hash, "Responding to StoreChunk"); + let _ = tx.send(Ok(())); + }, + _ => { + unimplemented!("Unexpected av-store message") + }, + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/mock/chain_api.rs b/polkadot/node/subsystem-bench/src/lib/mock/chain_api.rs new file mode 100644 index 0000000000000000000000000000000000000000..bee15c3cefdfbda061a1313db6c631e52fd91c12 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/chain_api.rs @@ -0,0 +1,132 @@ +// 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 futures::FutureExt; +use itertools::Itertools; +use polkadot_node_subsystem::{ + messages::ChainApiMessage, overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::Header; +use sp_core::H256; +use std::collections::HashMap; + +const LOG_TARGET: &str = "subsystem-bench::chain-api-mock"; + +/// State used to respond to `BlockHeader` requests. +pub struct ChainApiState { + pub block_headers: HashMap, +} + +pub struct MockChainApi { + state: ChainApiState, +} + +impl ChainApiState { + fn get_header_by_number(&self, requested_number: u32) -> Option<&Header> { + self.block_headers.values().find(|header| header.number == requested_number) + } +} + +impl MockChainApi { + pub fn new(state: ChainApiState) -> MockChainApi { + Self { state } + } +} + +#[overseer::subsystem(ChainApi, error=SubsystemError, prefix=self::overseer)] +impl MockChainApi { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "test-environment", future } + } +} + +#[overseer::contextbounds(ChainApi, prefix = self::overseer)] +impl MockChainApi { + 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 { + ChainApiMessage::BlockHeader(hash, response_channel) => { + let _ = response_channel.send(Ok(Some( + self.state + .block_headers + .get(&hash) + .cloned() + .expect("Relay chain block hashes are known"), + ))); + }, + ChainApiMessage::FinalizedBlockNumber(val) => { + val.send(Ok(0)).unwrap(); + }, + ChainApiMessage::FinalizedBlockHash(requested_number, sender) => { + let hash = self + .state + .get_header_by_number(requested_number) + .expect("Unknow block number") + .hash(); + sender.send(Ok(Some(hash))).unwrap(); + }, + ChainApiMessage::BlockNumber(requested_hash, sender) => { + sender + .send(Ok(Some( + self.state + .block_headers + .get(&requested_hash) + .expect("Unknown block hash") + .number, + ))) + .unwrap(); + }, + ChainApiMessage::Ancestors { hash, k: _, response_channel } => { + let block_number = self + .state + .block_headers + .get(&hash) + .expect("Unknown block hash") + .number; + let ancestors = self + .state + .block_headers + .iter() + .filter(|(_, header)| header.number < block_number) + .sorted_by(|a, b| a.1.number.cmp(&b.1.number)) + .map(|(hash, _)| *hash) + .collect_vec(); + response_channel.send(Ok(ancestors)).unwrap(); + }, + _ => { + unimplemented!("Unexpected chain-api message") + }, + } + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs new file mode 100644 index 0000000000000000000000000000000000000000..8783b35f1c04a9c59bf415340689b1a9ae29ee6a --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs @@ -0,0 +1,100 @@ +// 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 futures::FutureExt; +use paste::paste; +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; + } + } + } + } + } + } + }; +} + +// Generate dummy implementation for all subsystems +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/lib/mock/mod.rs b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..6dda9a47d398f3e6e952cd054bce9769e8942e70 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs @@ -0,0 +1,88 @@ +// 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; +use sp_consensus::SyncOracle; + +pub mod av_store; +pub mod chain_api; +pub mod dummy; +pub mod network_bridge; +pub mod 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_export] +macro_rules! dummy_builder { + ($spawn_task_handle: ident, $metrics: ident) => {{ + use $crate::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($metrics) + .supports_parachains(AlwaysSupportsParachains {}) + .spawner(SpawnGlue($spawn_task_handle)) + }}; +} + +#[derive(Clone)] +pub struct TestSyncOracle {} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&self) -> bool { + false + } + + fn is_offline(&self) -> bool { + unimplemented!("not used by subsystem benchmarks") + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs new file mode 100644 index 0000000000000000000000000000000000000000..d598f6447d3d43ece5e1d0fa6364508403c032e9 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs @@ -0,0 +1,210 @@ +// 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 . + +//! Mocked `network-bridge` subsystems that uses a `NetworkInterface` to access +//! the emulated network. + +use crate::{ + configuration::TestAuthorities, + network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt}, +}; +use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; +use polkadot_node_network_protocol::Versioned; +use polkadot_node_subsystem::{ + messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::{ + messages::{ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent}, + OverseerSignal, +}; +use sc_network::{request_responses::ProtocolConfig, RequestFailure}; + +const LOG_TARGET: &str = "subsystem-bench::network-bridge"; +const CHUNK_REQ_PROTOCOL_NAME_V1: &str = + "/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/req_chunk/1"; + +/// A mock of the network bridge tx subsystem. +pub struct MockNetworkBridgeTx { + /// A network emulator handle + network: NetworkEmulatorHandle, + /// A channel to the network interface, + to_network_interface: UnboundedSender, + /// Test authorithies + test_authorithies: TestAuthorities, +} + +/// A mock of the network bridge tx subsystem. +pub struct MockNetworkBridgeRx { + /// A network interface receiver + network_receiver: NetworkInterfaceReceiver, + /// Chunk request sender + chunk_request_sender: Option, +} + +impl MockNetworkBridgeTx { + pub fn new( + network: NetworkEmulatorHandle, + to_network_interface: UnboundedSender, + test_authorithies: TestAuthorities, + ) -> MockNetworkBridgeTx { + Self { network, to_network_interface, test_authorithies } + } +} + +impl MockNetworkBridgeRx { + pub fn new( + network_receiver: NetworkInterfaceReceiver, + chunk_request_sender: Option, + ) -> MockNetworkBridgeRx { + Self { network_receiver, chunk_request_sender } + } +} + +#[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: "network-bridge-tx", future } + } +} + +#[overseer::subsystem(NetworkBridgeRx, error=SubsystemError, prefix=self::overseer)] +impl MockNetworkBridgeRx { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "network-bridge-rx", future } + } +} + +#[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)] +impl MockNetworkBridgeTx { + async fn run(self, mut ctx: Context) { + // Main subsystem loop. + loop { + let subsystem_message = ctx.recv().await.expect("Overseer never fails us"); + match subsystem_message { + 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"); + let peer_id = + request.authority_id().expect("all nodes are authorities").clone(); + + if !self.network.is_peer_connected(&peer_id) { + // Attempting to send a request to a disconnected peer. + request + .into_response_sender() + .send(Err(RequestFailure::NotConnected)) + .expect("send never fails"); + continue + } + + let peer_message = + NetworkMessage::RequestFromNode(peer_id.clone(), request); + + let _ = self.to_network_interface.unbounded_send(peer_message); + } + }, + NetworkBridgeTxMessage::ReportPeer(_) => { + // ingore rep changes + }, + NetworkBridgeTxMessage::SendValidationMessage(peers, message) => { + for peer in peers { + self.to_network_interface + .unbounded_send(NetworkMessage::MessageFromNode( + self.test_authorithies + .peer_id_to_authority + .get(&peer) + .unwrap() + .clone(), + message.clone(), + )) + .expect("Should not fail"); + } + }, + _ => unimplemented!("Unexpected network bridge message"), + }, + } + } + } +} + +#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)] +impl MockNetworkBridgeRx { + async fn run(mut self, mut ctx: Context) { + // Main subsystem loop. + let mut from_network_interface = self.network_receiver.0; + loop { + futures::select! { + maybe_peer_message = from_network_interface.next() => { + if let Some(message) = maybe_peer_message { + match message { + NetworkMessage::MessageFromPeer(peer_id, message) => match message { + Versioned::V2( + polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution( + bitfield, + ), + ) => { + ctx.send_message( + BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V2(bitfield))) + ).await; + }, + Versioned::V3( + polkadot_node_network_protocol::v3::ValidationProtocol::ApprovalDistribution(msg) + ) => { + ctx.send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } + _ => { + unimplemented!("We only talk v2 network protocol") + }, + }, + NetworkMessage::RequestFromPeer(request) => { + if let Some(protocol) = self.chunk_request_sender.as_mut() { + assert_eq!(&*protocol.name, CHUNK_REQ_PROTOCOL_NAME_V1); + if let Some(inbound_queue) = protocol.inbound_queue.as_ref() { + inbound_queue + .send(request) + .await + .expect("Forwarding requests to subsystem never fails"); + } + } + }, + _ => { + panic!("NetworkMessage::RequestFromNode is not expected to be received from a peer") + } + } + } + }, + subsystem_message = ctx.recv().fuse() => { + match subsystem_message.expect("Overseer never fails us") { + orchestra::FromOrchestra::Signal(signal) => if signal == OverseerSignal::Conclude { return }, + _ => { + unimplemented!("Unexpected network bridge rx message") + }, + } + } + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs new file mode 100644 index 0000000000000000000000000000000000000000..53faf562f03c005238d5297c29117fd261b49eca --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/mock/runtime_api.rs @@ -0,0 +1,233 @@ +// 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 crate::configuration::{TestAuthorities, TestConfiguration}; +use bitvec::prelude::BitVec; +use futures::FutureExt; +use itertools::Itertools; +use polkadot_node_subsystem::{ + messages::{RuntimeApiMessage, RuntimeApiRequest}, + overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::{ + vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec, + OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex, +}; +use sp_consensus_babe::Epoch as BabeEpoch; +use sp_core::H256; +use std::collections::HashMap; + +const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock"; + +/// Minimal state to answer requests. +pub struct RuntimeApiState { + // All authorities in the test, + authorities: TestAuthorities, + // Candidate hashes per block + candidate_hashes: HashMap>, + // Included candidates per bock + included_candidates: HashMap>, + babe_epoch: Option, + // The session child index, + session_index: SessionIndex, +} + +/// A mocked `runtime-api` subsystem. +pub struct MockRuntimeApi { + state: RuntimeApiState, + config: TestConfiguration, +} + +impl MockRuntimeApi { + pub fn new( + config: TestConfiguration, + authorities: TestAuthorities, + candidate_hashes: HashMap>, + included_candidates: HashMap>, + babe_epoch: Option, + session_index: SessionIndex, + ) -> MockRuntimeApi { + Self { + state: RuntimeApiState { + authorities, + candidate_hashes, + included_candidates, + babe_epoch, + session_index, + }, + config, + } + } + + fn session_info(&self) -> SessionInfo { + session_info_for_peers(&self.config, &self.state.authorities) + } +} + +/// Generates a test session info with all passed authorities as consensus validators. +pub fn session_info_for_peers( + configuration: &TestConfiguration, + authorities: &TestAuthorities, +) -> SessionInfo { + let all_validators = (0..configuration.n_validators) + .map(|i| ValidatorIndex(i as _)) + .collect::>(); + + let validator_groups = all_validators + .chunks(configuration.max_validators_per_core) + .map(Vec::from) + .collect::>(); + + SessionInfo { + validators: authorities.validator_public.iter().cloned().collect(), + discovery_keys: authorities.validator_authority_id.to_vec(), + assignment_keys: authorities.validator_assignment_id.to_vec(), + validator_groups: IndexedVec::>::from(validator_groups), + n_cores: configuration.n_cores as u32, + needed_approvals: configuration.needed_approvals as u32, + zeroth_delay_tranche_width: configuration.zeroth_delay_tranche_width as u32, + relay_vrf_modulo_samples: configuration.relay_vrf_modulo_samples as u32, + n_delay_tranches: configuration.n_delay_tranches as u32, + no_show_slots: configuration.no_show_slots as u32, + active_validator_indices: (0..authorities.validator_authority_id.len()) + .map(|index| ValidatorIndex(index as u32)) + .collect_vec(), + dispute_period: 6, + random_seed: [0u8; 32], + } +} + +#[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) { + let validator_group_count = self.session_info().validator_groups.len(); + + 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::CandidateEvents(sender), + ) => { + let candidate_events = self.state.included_candidates.get(&request); + let _ = sender.send(Ok(candidate_events.cloned().unwrap_or_default())); + }, + RuntimeApiMessage::Request( + _block_hash, + RuntimeApiRequest::SessionInfo(_session_index, sender), + ) => { + let _ = sender.send(Ok(Some(self.session_info()))); + }, + RuntimeApiMessage::Request( + _block_hash, + RuntimeApiRequest::SessionExecutorParams(_session_index, sender), + ) => { + let _ = sender.send(Ok(Some(Default::default()))); + }, + RuntimeApiMessage::Request( + _request, + RuntimeApiRequest::NodeFeatures(_session_index, sender), + ) => { + let _ = sender.send(Ok(NodeFeatures::EMPTY)); + }, + RuntimeApiMessage::Request( + _block_hash, + RuntimeApiRequest::Validators(sender), + ) => { + let _ = + sender.send(Ok(self.state.authorities.validator_public.clone())); + }, + RuntimeApiMessage::Request( + _block_hash, + RuntimeApiRequest::SessionIndexForChild(sender), + ) => { + // Session is always the same. + let _ = sender.send(Ok(self.state.session_index)); + }, + RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::AvailabilityCores(sender), + ) => { + let candidate_hashes = self + .state + .candidate_hashes + .get(&block_hash) + .expect("Relay chain block hashes are generated at test start"); + + // All cores are always occupied. + let cores = candidate_hashes + .iter() + .enumerate() + .map(|(index, candidate_receipt)| { + // Ensure test breaks if badly configured. + assert!(index < validator_group_count); + + CoreState::Occupied(OccupiedCore { + next_up_on_available: None, + occupied_since: 0, + time_out_at: 0, + next_up_on_time_out: None, + availability: BitVec::default(), + group_responsible: GroupIndex(index as u32), + candidate_hash: candidate_receipt.hash(), + candidate_descriptor: candidate_receipt.descriptor.clone(), + }) + }) + .collect::>(); + + let _ = sender.send(Ok(cores)); + }, + RuntimeApiMessage::Request( + _request, + RuntimeApiRequest::CurrentBabeEpoch(sender), + ) => { + let _ = sender.send(Ok(self + .state + .babe_epoch + .clone() + .expect("Babe epoch unpopulated"))); + }, + // Long term TODO: implement more as needed. + message => { + unimplemented!("Unexpected runtime-api message: {:?}", message) + }, + } + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/network.rs b/polkadot/node/subsystem-bench/src/lib/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bc35a014ff5bf616bb02b0f8740679ef6b67489 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/network.rs @@ -0,0 +1,1069 @@ +// 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 . + +//! Implements network emulation and interfaces to control and specialize +//! network peer behaviour. + +// [TestEnvironment] +// [NetworkEmulatorHandle] +// || +// +-------+--||--+-------+ +// | | | | +// Peer1 Peer2 Peer3 Peer4 +// \ | | / +// \ | | / +// \ | | / +// \ | | / +// \ | | / +// [Network Interface] +// | +// [Emulated Network Bridge] +// | +// Subsystems under test + +use crate::{ + configuration::{random_latency, TestAuthorities, TestConfiguration}, + environment::TestEnvironmentDependencies, + NODE_UNDER_TEST, +}; +use colored::Colorize; +use futures::{ + channel::{ + mpsc, + mpsc::{UnboundedReceiver, UnboundedSender}, + oneshot, + }, + lock::Mutex, + stream::FuturesUnordered, + Future, FutureExt, StreamExt, +}; +use itertools::Itertools; +use net_protocol::{ + peer_set::{ProtocolVersion, ValidationVersion}, + request_response::{Recipient, Requests, ResponseSender}, + ObservedRole, VersionedValidationProtocol, +}; +use parity_scale_codec::Encode; +use polkadot_node_network_protocol::{self as net_protocol, Versioned}; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, NetworkBridgeEvent}; +use polkadot_node_subsystem_util::metrics::prometheus::{ + self, CounterVec, Opts, PrometheusError, Registry, +}; +use polkadot_overseer::AllMessages; +use polkadot_primitives::AuthorityDiscoveryId; +use prometheus_endpoint::U64; +use rand::{seq::SliceRandom, thread_rng}; +use sc_network::{ + request_responses::{IncomingRequest, OutgoingResponse}, + PeerId, RequestFailure, +}; +use sc_service::SpawnTaskHandle; +use std::{ + collections::HashMap, + sync::Arc, + task::Poll, + time::{Duration, Instant}, +}; + +const LOG_TARGET: &str = "subsystem-bench::network"; + +// 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); + } + } +} + +/// A wrapper for both gossip and request/response protocols along with the destination +/// peer(`AuthorityDiscoveryId``). +pub enum NetworkMessage { + /// A gossip message from peer to node. + MessageFromPeer(PeerId, VersionedValidationProtocol), + /// A gossip message from node to a peer. + MessageFromNode(AuthorityDiscoveryId, VersionedValidationProtocol), + /// A request originating from our node + RequestFromNode(AuthorityDiscoveryId, Requests), + /// A request originating from an emultated peer + RequestFromPeer(IncomingRequest), +} + +impl NetworkMessage { + /// Returns the size of the encoded message or request + pub fn size(&self) -> usize { + match &self { + NetworkMessage::MessageFromPeer(_, Versioned::V2(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V1(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V3(message)) => message.encoded_size(), + NetworkMessage::MessageFromNode(_peer_id, Versioned::V2(message)) => + message.encoded_size(), + NetworkMessage::MessageFromNode(_peer_id, Versioned::V1(message)) => + message.encoded_size(), + NetworkMessage::MessageFromNode(_peer_id, Versioned::V3(message)) => + message.encoded_size(), + NetworkMessage::RequestFromNode(_peer_id, incoming) => incoming.size(), + NetworkMessage::RequestFromPeer(request) => request.payload.encoded_size(), + } + } + + /// Returns the destination peer from the message or `None` if it originates from a peer. + pub fn peer(&self) -> Option<&AuthorityDiscoveryId> { + match &self { + NetworkMessage::MessageFromNode(peer_id, _) | + NetworkMessage::RequestFromNode(peer_id, _) => Some(peer_id), + _ => None, + } + } +} + +/// A network interface of the node under test. +pub struct NetworkInterface { + // Sender for subsystems. + bridge_to_interface_sender: UnboundedSender, +} + +// Wraps the receiving side of a interface to bridge channel. It is a required +// parameter of the `network-bridge` mock. +pub struct NetworkInterfaceReceiver(pub UnboundedReceiver); + +struct ProxiedRequest { + sender: Option>, + receiver: oneshot::Receiver, +} + +struct ProxiedResponse { + pub sender: oneshot::Sender, + pub result: Result, RequestFailure>, +} + +impl Future for ProxiedRequest { + // The sender and result. + type Output = ProxiedResponse; + + fn poll( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + match self.receiver.poll_unpin(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(response) => Poll::Ready(ProxiedResponse { + sender: self.sender.take().expect("sender already used"), + result: response + .expect("Response is always successfully received.") + .result + .map_err(|_| RequestFailure::Refused), + }), + } + } +} + +impl NetworkInterface { + /// Create a new `NetworkInterface` + pub fn new( + spawn_task_handle: SpawnTaskHandle, + network: NetworkEmulatorHandle, + bandwidth_bps: usize, + mut from_network: UnboundedReceiver, + ) -> (NetworkInterface, NetworkInterfaceReceiver) { + let rx_limiter = Arc::new(Mutex::new(RateLimit::new(10, bandwidth_bps))); + let tx_limiter = Arc::new(Mutex::new(RateLimit::new(10, bandwidth_bps))); + + // Channel for receiving messages from the network bridge subsystem. + let (bridge_to_interface_sender, mut bridge_to_interface_receiver) = + mpsc::unbounded::(); + + // Channel for forwarding messages to the network bridge subsystem. + let (interface_to_bridge_sender, interface_to_bridge_receiver) = + mpsc::unbounded::(); + + let rx_network = network.clone(); + let tx_network = network; + + let rx_task_bridge_sender = interface_to_bridge_sender.clone(); + + let task_rx_limiter = rx_limiter.clone(); + let task_tx_limiter = tx_limiter.clone(); + + // A task that forwards messages from emulated peers to the node (emulated network bridge). + let rx_task = async move { + let mut proxied_requests = FuturesUnordered::new(); + + loop { + let mut from_network = from_network.next().fuse(); + futures::select! { + maybe_peer_message = from_network => { + if let Some(peer_message) = maybe_peer_message { + let size = peer_message.size(); + task_rx_limiter.lock().await.reap(size).await; + rx_network.inc_received(size); + + // To be able to apply the configured bandwidth limits for responses being sent + // over channels, we need to implement a simple proxy that allows this loop + // to receive the response and enforce the configured bandwidth before + // sending it to the original recipient. + if let NetworkMessage::RequestFromPeer(request) = peer_message { + let (response_sender, response_receiver) = oneshot::channel(); + + // Create a new `IncomingRequest` that we forward to the network bridge. + let new_request = IncomingRequest {payload: request.payload, peer: request.peer, pending_response: response_sender}; + proxied_requests.push(ProxiedRequest {sender: Some(request.pending_response), receiver: response_receiver}); + + // Send the new message to network bridge subsystem. + rx_task_bridge_sender + .unbounded_send(NetworkMessage::RequestFromPeer(new_request)) + .expect("network bridge subsystem is alive"); + continue + } + + // Forward the message to the bridge. + rx_task_bridge_sender + .unbounded_send(peer_message) + .expect("network bridge subsystem is alive"); + } else { + gum::info!(target: LOG_TARGET, "Uplink channel closed, network interface task exiting"); + break + } + }, + proxied_request = proxied_requests.next() => { + if let Some(proxied_request) = proxied_request { + match proxied_request.result { + Ok(result) => { + let bytes = result.encoded_size(); + gum::trace!(target: LOG_TARGET, size = bytes, "proxied request completed"); + + // Enforce bandwidth based on the response the node has sent. + // TODO: Fix the stall of RX when TX lock() takes a while to refill + // the token bucket. Good idea would be to create a task for each request. + task_tx_limiter.lock().await.reap(bytes).await; + rx_network.inc_sent(bytes); + + // Forward the response to original recipient. + proxied_request.sender.send( + OutgoingResponse { + reputation_changes: Vec::new(), + result: Ok(result), + sent_feedback: None + } + ).expect("network is alive"); + } + Err(e) => { + gum::warn!(target: LOG_TARGET, "Node req/response failure: {:?}", e) + } + } + } else { + gum::debug!(target: LOG_TARGET, "No more active proxied requests"); + // break + } + } + } + } + } + .boxed(); + + let task_spawn_handle = spawn_task_handle.clone(); + let task_rx_limiter = rx_limiter.clone(); + let task_tx_limiter = tx_limiter.clone(); + + // A task that forwards messages from the node to emulated peers. + let tx_task = async move { + // Wrap it in an `Arc` to avoid `clone()` the inner data as we need to share it across + // many send tasks. + let tx_network = Arc::new(tx_network); + + loop { + if let Some(peer_message) = bridge_to_interface_receiver.next().await { + let size = peer_message.size(); + // Ensure bandwidth used is limited. + task_tx_limiter.lock().await.reap(size).await; + + match peer_message { + NetworkMessage::MessageFromNode(peer, message) => + tx_network.send_message_to_peer(&peer, message), + NetworkMessage::RequestFromNode(peer, request) => { + // Send request through a proxy so we can account and limit bandwidth + // usage for the node. + let send_task = Self::proxy_send_request( + peer.clone(), + request, + tx_network.clone(), + task_rx_limiter.clone(), + ) + .boxed(); + + task_spawn_handle.spawn("request-proxy", "test-environment", send_task); + }, + _ => panic!( + "Unexpected network message received from emulated network bridge" + ), + } + + tx_network.inc_sent(size); + } else { + gum::info!(target: LOG_TARGET, "Downlink channel closed, network interface task exiting"); + break + } + } + } + .boxed(); + + spawn_task_handle.spawn("network-interface-rx", "test-environment", rx_task); + spawn_task_handle.spawn("network-interface-tx", "test-environment", tx_task); + + ( + Self { bridge_to_interface_sender }, + NetworkInterfaceReceiver(interface_to_bridge_receiver), + ) + } + + /// Get a sender that can be used by a subsystem to send network actions to the network. + pub fn subsystem_sender(&self) -> UnboundedSender { + self.bridge_to_interface_sender.clone() + } + + /// Helper method that proxies a request from node to peer and implements rate limiting and + /// accounting. + async fn proxy_send_request( + peer: AuthorityDiscoveryId, + mut request: Requests, + tx_network: Arc, + task_rx_limiter: Arc>, + ) { + let (proxy_sender, proxy_receiver) = oneshot::channel(); + + // Modify the request response sender so we can intercept the answer + let sender = request.swap_response_sender(proxy_sender); + + // Send the modified request to the peer. + tx_network.send_request_to_peer(&peer, request); + + // Wait for answer (intercept the response). + match proxy_receiver.await { + Err(_) => { + panic!("Emulated peer hangup"); + }, + Ok(Err(err)) => { + sender.send(Err(err)).expect("Oneshot send always works."); + }, + Ok(Ok((response, protocol_name))) => { + let response_size = response.encoded_size(); + task_rx_limiter.lock().await.reap(response_size).await; + tx_network.inc_received(response_size); + + // Send the response to the original request sender. + if sender.send(Ok((response, protocol_name))).is_err() { + gum::warn!(target: LOG_TARGET, response_size, "response oneshot canceled by node") + } + }, + }; + } +} + +/// A handle for controlling an emulated peer. +#[derive(Clone)] +pub struct EmulatedPeerHandle { + /// Send messages to be processed by the peer. + messages_tx: UnboundedSender, + /// Send actions to be performed by the peer. + actions_tx: UnboundedSender, + peer_id: PeerId, +} + +impl EmulatedPeerHandle { + /// Receive and process a message from the node. + pub fn receive(&self, message: NetworkMessage) { + self.messages_tx.unbounded_send(message).expect("Peer message channel hangup"); + } + + /// Send a message to the node. + pub fn send_message(&self, message: VersionedValidationProtocol) { + self.actions_tx + .unbounded_send(NetworkMessage::MessageFromPeer(self.peer_id, message)) + .expect("Peer action channel hangup"); + } + + /// Send a `request` to the node. + pub fn send_request(&self, request: IncomingRequest) { + self.actions_tx + .unbounded_send(NetworkMessage::RequestFromPeer(request)) + .expect("Peer action channel hangup"); + } +} + +// A network peer emulator. +struct EmulatedPeer { + spawn_handle: SpawnTaskHandle, + to_node: UnboundedSender, + tx_limiter: RateLimit, + rx_limiter: RateLimit, + latency_ms: usize, +} + +impl EmulatedPeer { + /// Send a message to the node. + pub async fn send_message(&mut self, message: NetworkMessage) { + self.tx_limiter.reap(message.size()).await; + + if self.latency_ms == 0 { + self.to_node.unbounded_send(message).expect("Sending to the node never fails"); + } else { + let to_node = self.to_node.clone(); + let latency_ms = std::time::Duration::from_millis(self.latency_ms as u64); + + // Emulate RTT latency + self.spawn_handle + .spawn("peer-latency-emulator", "test-environment", async move { + tokio::time::sleep(latency_ms).await; + to_node.unbounded_send(message).expect("Sending to the node never fails"); + }); + } + } + + /// Returns the rx bandwidth limiter. + pub fn rx_limiter(&mut self) -> &mut RateLimit { + &mut self.rx_limiter + } +} + +/// Interceptor pattern for handling messages. +pub trait HandleNetworkMessage { + /// Returns `None` if the message was handled, or the `message` + /// otherwise. + /// + /// `node_sender` allows sending of messages to the node in response + /// to the handled message. + fn handle( + &self, + message: NetworkMessage, + node_sender: &mut UnboundedSender, + ) -> Option; +} + +impl HandleNetworkMessage for Arc +where + T: HandleNetworkMessage, +{ + fn handle( + &self, + message: NetworkMessage, + node_sender: &mut UnboundedSender, + ) -> Option { + self.as_ref().handle(message, node_sender) + } +} + +// This loop is responsible for handling of messages/requests between the peer and the node. +async fn emulated_peer_loop( + handlers: Vec>, + stats: Arc, + mut emulated_peer: EmulatedPeer, + messages_rx: UnboundedReceiver, + actions_rx: UnboundedReceiver, + mut to_network_interface: UnboundedSender, +) { + let mut proxied_requests = FuturesUnordered::new(); + let mut messages_rx = messages_rx.fuse(); + let mut actions_rx = actions_rx.fuse(); + + loop { + futures::select! { + maybe_peer_message = messages_rx.next() => { + if let Some(peer_message) = maybe_peer_message { + let size = peer_message.size(); + + emulated_peer.rx_limiter().reap(size).await; + stats.inc_received(size); + + let mut message = Some(peer_message); + + // Try all handlers until the message gets processed. + // Panic if the message is not consumed. + for handler in handlers.iter() { + // The check below guarantees that message is always `Some`: we are still + // inside the loop. + message = handler.handle(message.unwrap(), &mut to_network_interface); + if message.is_none() { + break + } + } + if let Some(message) = message { + panic!("Emulated message from peer {:?} not handled", message.peer()); + } + } else { + gum::debug!(target: LOG_TARGET, "Downlink channel closed, peer task exiting"); + break + } + }, + maybe_action = actions_rx.next() => { + match maybe_action { + // We proxy any request being sent to the node to limit bandwidth as we + // do in the `NetworkInterface` task. + Some(NetworkMessage::RequestFromPeer(request)) => { + let (response_sender, response_receiver) = oneshot::channel(); + // Create a new `IncomingRequest` that we forward to the network interface. + let new_request = IncomingRequest {payload: request.payload, peer: request.peer, pending_response: response_sender}; + + proxied_requests.push(ProxiedRequest {sender: Some(request.pending_response), receiver: response_receiver}); + + emulated_peer.send_message(NetworkMessage::RequestFromPeer(new_request)).await; + }, + Some(message) => emulated_peer.send_message(message).await, + None => { + gum::debug!(target: LOG_TARGET, "Action channel closed, peer task exiting"); + break + } + } + }, + proxied_request = proxied_requests.next() => { + if let Some(proxied_request) = proxied_request { + match proxied_request.result { + Ok(result) => { + let bytes = result.encoded_size(); + gum::trace!(target: LOG_TARGET, size = bytes, "Peer proxied request completed"); + + emulated_peer.rx_limiter().reap(bytes).await; + stats.inc_received(bytes); + + proxied_request.sender.send( + OutgoingResponse { + reputation_changes: Vec::new(), + result: Ok(result), + sent_feedback: None + } + ).expect("network is alive"); + } + Err(e) => { + gum::warn!(target: LOG_TARGET, "Node req/response failure: {:?}", e) + } + } + } + } + } + } +} + +/// Creates a new peer emulator task and returns a handle to it. +pub fn new_peer( + bandwidth: usize, + spawn_task_handle: SpawnTaskHandle, + handlers: Vec>, + stats: Arc, + to_network_interface: UnboundedSender, + latency_ms: usize, + peer_id: PeerId, +) -> EmulatedPeerHandle { + let (messages_tx, messages_rx) = mpsc::unbounded::(); + let (actions_tx, actions_rx) = mpsc::unbounded::(); + + let rx_limiter = RateLimit::new(10, bandwidth); + let tx_limiter = RateLimit::new(10, bandwidth); + let emulated_peer = EmulatedPeer { + spawn_handle: spawn_task_handle.clone(), + rx_limiter, + tx_limiter, + to_node: to_network_interface.clone(), + latency_ms, + }; + + spawn_task_handle.clone().spawn( + "peer-emulator", + "test-environment", + emulated_peer_loop( + handlers, + stats, + emulated_peer, + messages_rx, + actions_rx, + to_network_interface, + ) + .boxed(), + ); + + EmulatedPeerHandle { messages_tx, actions_tx, peer_id } +} + +/// Book keeping of sent and received bytes. +pub struct PeerEmulatorStats { + metrics: Metrics, + peer_index: usize, +} + +impl PeerEmulatorStats { + pub(crate) fn new(peer_index: usize, metrics: Metrics) -> Self { + Self { metrics, peer_index } + } + + pub fn inc_sent(&self, bytes: usize) { + self.metrics.on_peer_sent(self.peer_index, bytes); + } + + pub fn inc_received(&self, bytes: usize) { + self.metrics.on_peer_received(self.peer_index, bytes); + } + + pub fn sent(&self) -> usize { + self.metrics + .peer_total_sent + .get_metric_with_label_values(&[&format!("node{}", self.peer_index)]) + .expect("Metric exists") + .get() as usize + } + + pub fn received(&self) -> usize { + self.metrics + .peer_total_received + .get_metric_with_label_values(&[&format!("node{}", self.peer_index)]) + .expect("Metric exists") + .get() as usize + } +} + +/// The state of a peer on the emulated network. +#[derive(Clone)] +enum Peer { + Connected(EmulatedPeerHandle), + Disconnected(EmulatedPeerHandle), +} + +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 handle(&self) -> &EmulatedPeerHandle { + match self { + Peer::Connected(ref emulator) => emulator, + Peer::Disconnected(ref emulator) => emulator, + } + } +} + +/// A ha emulated network implementation. +#[derive(Clone)] +pub struct NetworkEmulatorHandle { + // Per peer network emulation. + peers: Vec, + /// Per peer stats. + stats: Vec>, + /// Each emulated peer is a validator. + validator_authority_ids: HashMap, +} + +impl NetworkEmulatorHandle { + /// Generates peer_connected messages for all peers in `test_authorities` + pub fn generate_peer_connected(&self) -> Vec { + self.peers + .iter() + .filter(|peer| peer.is_connected()) + .map(|peer| { + let network = NetworkBridgeEvent::PeerConnected( + peer.handle().peer_id, + ObservedRole::Full, + ProtocolVersion::from(ValidationVersion::V3), + None, + ); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate( + network, + )) + }) + .collect_vec() + } +} + +/// Create a new emulated network based on `config`. +/// Each emulated peer will run the specified `handlers` to process incoming messages. +pub fn new_network( + config: &TestConfiguration, + dependencies: &TestEnvironmentDependencies, + authorities: &TestAuthorities, + handlers: Vec>, +) -> (NetworkEmulatorHandle, NetworkInterface, NetworkInterfaceReceiver) { + 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 {}%, latency {:?}", config.connectivity, config.latency).bright_black()); + + let metrics = + Metrics::new(&dependencies.registry).expect("Metrics always register successfully"); + let mut validator_authority_id_mapping = HashMap::new(); + + // Create the channel from `peer` to `NetworkInterface` . + let (to_network_interface, from_network) = mpsc::unbounded(); + + // 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(new_peer( + config.peer_bandwidth, + dependencies.task_manager.spawn_handle(), + handlers.clone(), + stats, + to_network_interface.clone(), + random_latency(config.latency.as_ref()), + *authorities.peer_ids.get(peer_index).unwrap(), + )), + ) + }) + .unzip(); + + let connected_count = config.connected_count(); + + let mut peers_indicies = (0..n_peers).collect_vec(); + let (_connected, to_disconnect) = + peers_indicies.partial_shuffle(&mut thread_rng(), connected_count); + + // Node under test is always mark as disconnected. + peers[NODE_UNDER_TEST as usize].disconnect(); + for peer in to_disconnect.iter().skip(1) { + peers[*peer].disconnect(); + } + + gum::info!(target: LOG_TARGET, "{}",format!("Network created, connected validator count {}", connected_count).bright_black()); + + let handle = NetworkEmulatorHandle { + peers, + stats, + validator_authority_ids: validator_authority_id_mapping, + }; + + // Finally create the `NetworkInterface` with the `from_network` receiver. + let (network_interface, network_interface_receiver) = NetworkInterface::new( + dependencies.task_manager.spawn_handle(), + handle.clone(), + config.bandwidth, + from_network, + ); + + (handle, network_interface, network_interface_receiver) +} + +/// Errors that can happen when sending data to emulated peers. +#[derive(Clone, Debug)] +pub enum EmulatedPeerError { + NotConnected, +} + +impl NetworkEmulatorHandle { + /// Returns true if the emulated peer is connected to the node under test. + pub fn is_peer_connected(&self, peer: &AuthorityDiscoveryId) -> bool { + self.peer(peer).is_connected() + } + + /// Forward notification `message` to an emulated `peer`. + /// Panics if peer is not connected. + pub fn send_message_to_peer( + &self, + peer_id: &AuthorityDiscoveryId, + message: VersionedValidationProtocol, + ) { + let peer = self.peer(peer_id); + assert!(peer.is_connected(), "forward message only for connected peers."); + peer.handle().receive(NetworkMessage::MessageFromNode(peer_id.clone(), message)); + } + + /// Forward a `request`` to an emulated `peer`. + /// Panics if peer is not connected. + pub fn send_request_to_peer(&self, peer_id: &AuthorityDiscoveryId, request: Requests) { + let peer = self.peer(peer_id); + assert!(peer.is_connected(), "forward request only for connected peers."); + peer.handle().receive(NetworkMessage::RequestFromNode(peer_id.clone(), request)); + } + + /// Send a message from a peer to the node. + pub fn send_message_from_peer( + &self, + from_peer: &AuthorityDiscoveryId, + message: VersionedValidationProtocol, + ) -> Result<(), EmulatedPeerError> { + let dst_peer = self.peer(from_peer); + + if !dst_peer.is_connected() { + gum::warn!(target: LOG_TARGET, "Attempted to send message from a peer not connected to our node, operation ignored"); + return Err(EmulatedPeerError::NotConnected) + } + + dst_peer.handle().send_message(message); + Ok(()) + } + + /// Send a request from a peer to the node. + pub fn send_request_from_peer( + &self, + from_peer: &AuthorityDiscoveryId, + request: IncomingRequest, + ) -> Result<(), EmulatedPeerError> { + let dst_peer = self.peer(from_peer); + + if !dst_peer.is_connected() { + gum::warn!(target: LOG_TARGET, "Attempted to send request from a peer not connected to our node, operation ignored"); + return Err(EmulatedPeerError::NotConnected) + } + + dst_peer.handle().send_request(request); + Ok(()) + } + + // 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)] + } + + // Increment bytes sent by our node (the node that contains the subsystem under test) + pub fn inc_sent(&self, bytes: usize) { + // Our node is always 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 is always peer 0. + self.peer_stats(0).inc_received(bytes); + } +} + +/// 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); + } +} + +// Helper trait for low level access to `Requests` variants. +pub trait RequestExt { + /// Get the authority id if any from the request. + fn authority_id(&self) -> Option<&AuthorityDiscoveryId>; + /// Consume self and return the response sender. + fn into_response_sender(self) -> ResponseSender; + /// Allows to change the `ResponseSender` in place. + fn swap_response_sender(&mut self, new_sender: ResponseSender) -> ResponseSender; + /// Returns the size in bytes of the request payload. + fn size(&self) -> usize; +} + +impl RequestExt for Requests { + fn authority_id(&self) -> Option<&AuthorityDiscoveryId> { + match self { + Requests::ChunkFetchingV1(request) => { + if let Recipient::Authority(authority_id) = &request.peer { + Some(authority_id) + } else { + None + } + }, + Requests::AvailableDataFetchingV1(request) => { + if let Recipient::Authority(authority_id) = &request.peer { + Some(authority_id) + } else { + None + } + }, + request => { + unimplemented!("RequestAuthority not implemented for {:?}", request) + }, + } + } + + fn into_response_sender(self) -> ResponseSender { + match self { + Requests::ChunkFetchingV1(outgoing_request) => outgoing_request.pending_response, + Requests::AvailableDataFetchingV1(outgoing_request) => + outgoing_request.pending_response, + _ => unimplemented!("unsupported request type"), + } + } + + /// Swaps the `ResponseSender` and returns the previous value. + fn swap_response_sender(&mut self, new_sender: ResponseSender) -> ResponseSender { + match self { + Requests::ChunkFetchingV1(outgoing_request) => + std::mem::replace(&mut outgoing_request.pending_response, new_sender), + Requests::AvailableDataFetchingV1(outgoing_request) => + std::mem::replace(&mut outgoing_request.pending_response, new_sender), + _ => unimplemented!("unsupported request type"), + } + } + + /// Returns the size in bytes of the request payload. + fn size(&self) -> usize { + match self { + Requests::ChunkFetchingV1(outgoing_request) => outgoing_request.payload.encoded_size(), + Requests::AvailableDataFetchingV1(outgoing_request) => + outgoing_request.payload.encoded_size(), + _ => unimplemented!("received an unexpected request"), + } + } +} + +#[cfg(test)] +mod tests { + use super::RateLimit; + use std::time::Instant; + + #[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); + } +} diff --git a/polkadot/node/subsystem-bench/src/lib/usage.rs b/polkadot/node/subsystem-bench/src/lib/usage.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef60d67372ae84ed0f5a434720f21a105dee0235 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/usage.rs @@ -0,0 +1,146 @@ +// 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 usage implementation + +use colored::Colorize; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BenchmarkUsage { + pub benchmark_name: String, + pub network_usage: Vec, + pub cpu_usage: Vec, +} + +impl std::fmt::Display for BenchmarkUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "\n{}\n\n{}\n{}\n\n{}\n{}\n", + self.benchmark_name.purple(), + format!("{:<32}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), + self.network_usage + .iter() + .map(|v| v.to_string()) + .collect::>() + .join("\n"), + format!("{:<32}{:>12}{:>12}", "CPU usage, seconds", "total", "per block").blue(), + self.cpu_usage.iter().map(|v| v.to_string()).collect::>().join("\n") + ) + } +} + +impl BenchmarkUsage { + pub fn average(usages: &[Self]) -> Self { + let all_network_usages: Vec<&ResourceUsage> = + usages.iter().flat_map(|v| &v.network_usage).collect(); + let all_cpu_usage: Vec<&ResourceUsage> = usages.iter().flat_map(|v| &v.cpu_usage).collect(); + + Self { + benchmark_name: usages.first().map(|v| v.benchmark_name.clone()).unwrap_or_default(), + network_usage: ResourceUsage::average_by_resource_name(&all_network_usages), + cpu_usage: ResourceUsage::average_by_resource_name(&all_cpu_usage), + } + } + + pub fn check_network_usage(&self, checks: &[ResourceUsageCheck]) -> Vec { + check_usage(&self.benchmark_name, &self.network_usage, checks) + } + + pub fn check_cpu_usage(&self, checks: &[ResourceUsageCheck]) -> Vec { + check_usage(&self.benchmark_name, &self.cpu_usage, checks) + } + + pub fn cpu_usage_diff(&self, other: &Self, resource_name: &str) -> Option { + let self_res = self.cpu_usage.iter().find(|v| v.resource_name == resource_name); + let other_res = other.cpu_usage.iter().find(|v| v.resource_name == resource_name); + + match (self_res, other_res) { + (Some(self_res), Some(other_res)) => Some(self_res.diff(other_res)), + _ => None, + } + } +} + +fn check_usage( + benchmark_name: &str, + usage: &[ResourceUsage], + checks: &[ResourceUsageCheck], +) -> Vec { + checks + .iter() + .filter_map(|check| { + check_resource_usage(usage, check) + .map(|message| format!("{}: {}", benchmark_name, message)) + }) + .collect() +} + +fn check_resource_usage( + usage: &[ResourceUsage], + (resource_name, base, precision): &ResourceUsageCheck, +) -> Option { + if let Some(usage) = usage.iter().find(|v| v.resource_name == *resource_name) { + let diff = (base - usage.per_block).abs() / base; + if diff < *precision { + None + } else { + Some(format!( + "The resource `{}` is expected to be equal to {} with a precision {}, but the current value is {}", + resource_name, base, precision, usage.per_block + )) + } + } else { + Some(format!("The resource `{}` is not found", resource_name)) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ResourceUsage { + pub resource_name: String, + pub total: f64, + pub per_block: f64, +} + +impl std::fmt::Display for ResourceUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:<32}{:>12.3}{:>12.3}", self.resource_name.cyan(), self.total, self.per_block) + } +} + +impl ResourceUsage { + fn average_by_resource_name(usages: &[&Self]) -> Vec { + let mut by_name: HashMap> = Default::default(); + for usage in usages { + by_name.entry(usage.resource_name.clone()).or_default().push(usage); + } + let mut average = vec![]; + for (resource_name, values) in by_name { + let total = values.iter().map(|v| v.total).sum::() / values.len() as f64; + let per_block = values.iter().map(|v| v.per_block).sum::() / values.len() as f64; + average.push(Self { resource_name, total, per_block }); + } + average + } + + fn diff(&self, other: &Self) -> f64 { + (self.per_block - other.per_block).abs() / self.per_block + } +} + +type ResourceUsageCheck<'a> = (&'a str, f64, f64); diff --git a/polkadot/node/subsystem-bench/src/lib/utils.rs b/polkadot/node/subsystem-bench/src/lib/utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..75b72cc11b98271f1b92e2734029b3e34d091bd6 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/lib/utils.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 . + +//! Test utils + +use crate::usage::BenchmarkUsage; +use std::io::{stdout, Write}; + +pub struct WarmUpOptions<'a> { + /// The maximum number of runs considered for marming up. + pub warm_up: usize, + /// The number of runs considered for benchmarking. + pub bench: usize, + /// The difference in CPU usage between runs considered as normal + pub precision: f64, + /// The subsystems whose CPU usage is checked during warm-up cycles + pub subsystems: &'a [&'a str], +} + +impl<'a> WarmUpOptions<'a> { + pub fn new(subsystems: &'a [&'a str]) -> Self { + Self { warm_up: 100, bench: 3, precision: 0.02, subsystems } + } +} + +pub fn warm_up_and_benchmark( + options: WarmUpOptions, + run: impl Fn() -> BenchmarkUsage, +) -> Result { + println!("Warming up..."); + let mut usages = Vec::with_capacity(options.bench); + + for n in 1..=options.warm_up { + let curr = run(); + if let Some(prev) = usages.last() { + let diffs = options + .subsystems + .iter() + .map(|&v| { + curr.cpu_usage_diff(prev, v) + .ok_or(format!("{} not found in benchmark {:?}", v, prev)) + }) + .collect::, String>>()?; + if !diffs.iter().all(|&v| v < options.precision) { + usages.clear(); + } + } + usages.push(curr); + print!("\r{}%", n * 100 / options.warm_up); + if usages.len() == options.bench { + println!("\rTook {} runs to warm up", n.saturating_sub(options.bench)); + break; + } + stdout().flush().unwrap(); + } + + if usages.len() != options.bench { + println!("Didn't warm up after {} runs", options.warm_up); + return Err("Can't warm up".to_string()) + } + + Ok(BenchmarkUsage::average(&usages)) +} diff --git a/polkadot/node/subsystem-test-helpers/Cargo.toml b/polkadot/node/subsystem-test-helpers/Cargo.toml index 9087ca11f5d22ee5307fbc3877c88e1be5a72e27..c71f030568d9da378da61c7c4af90a409f936b64 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" +parking_lot = "0.12.1" 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..6c1ac86c4507b798e4270b7c52e97a4dad74b64c 100644 --- a/polkadot/node/subsystem-test-helpers/src/lib.rs +++ b/polkadot/node/subsystem-test-helpers/src/lib.rs @@ -18,17 +18,21 @@ #![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; use sp_core::testing::TaskExecutor; use std::{ + collections::VecDeque, convert::Infallible, future::Future, pin::Pin, @@ -187,6 +191,7 @@ pub struct TestSubsystemContext { tx: TestSubsystemSender, rx: mpsc::Receiver>, spawn: S, + message_buffer: VecDeque>, } #[async_trait::async_trait] @@ -204,6 +209,9 @@ where type Error = SubsystemError; async fn try_recv(&mut self) -> Result>, ()> { + if let Some(msg) = self.message_buffer.pop_front() { + return Ok(Some(msg)) + } match poll!(self.rx.next()) { Poll::Ready(Some(msg)) => Ok(Some(msg)), Poll::Ready(None) => Err(()), @@ -212,12 +220,30 @@ where } async fn recv(&mut self) -> SubsystemResult> { + if let Some(msg) = self.message_buffer.pop_front() { + return Ok(msg) + } self.rx .next() .await .ok_or_else(|| SubsystemError::Context("Receiving end closed".to_owned())) } + async fn recv_signal(&mut self) -> SubsystemResult { + loop { + let msg = self + .rx + .next() + .await + .ok_or_else(|| SubsystemError::Context("Receiving end closed".to_owned()))?; + if let FromOrchestra::Signal(sig) = msg { + return Ok(sig) + } else { + self.message_buffer.push_back(msg) + } + } + } + fn spawn( &mut self, name: &'static str, @@ -311,6 +337,7 @@ pub fn make_buffered_subsystem_context( tx: TestSubsystemSender { tx: all_messages_tx }, rx: overseer_rx, spawn: SpawnGlue(spawner), + message_buffer: VecDeque::new(), }, TestSubsystemContextHandle { tx: overseer_tx, rx: all_messages_rx }, ) @@ -440,6 +467,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 a1c00cb0652e2538c590d1c3892eed8a168e1014..54c8f7e2ade773f1df84fc8cef1825c266364633 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-node-subsystem-types" description = "Subsystem traits and message definitions" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] derive_more = "0.99.17" futures = "0.3.21" @@ -14,14 +17,17 @@ polkadot-node-primitives = { path = "../primitives" } polkadot-node-network-protocol = { path = "../network/protocol" } polkadot-statement-table = { path = "../../statement-table" } polkadot-node-jaeger = { path = "../jaeger" } -orchestra = { version = "0.3.3", default-features = false, features=["futures_channel"] } +orchestra = { version = "0.3.5", default-features = false, features = ["futures_channel"] } sc-network = { path = "../../../substrate/client/network" } sp-api = { path = "../../../substrate/primitives/api" } +sp-blockchain = { path = "../../../substrate/primitives/blockchain" } sp-consensus-babe = { path = "../../../substrate/primitives/consensus/babe" } +sp-runtime = { path = "../../../substrate/primitives/runtime" } sp-authority-discovery = { path = "../../../substrate/primitives/authority-discovery" } sc-client-api = { path = "../../../substrate/client/api" } sc-transaction-pool-api = { path = "../../../substrate/client/transaction-pool/api" } smallvec = "1.8.0" substrate-prometheus-endpoint = { path = "../../../substrate/utils/prometheus" } -thiserror = "1.0.48" -async-trait = "0.1.57" +thiserror = { workspace = true } +async-trait = "0.1.74" +bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } diff --git a/polkadot/node/subsystem-types/src/lib.rs b/polkadot/node/subsystem-types/src/lib.rs index e3d6e4decf20e1c28e99b7d821d04421a1a2211b..cd39aa03e56736399ab491705cc9568a52b2784e 100644 --- a/polkadot/node/subsystem-types/src/lib.rs +++ b/polkadot/node/subsystem-types/src/lib.rs @@ -40,7 +40,7 @@ pub mod errors; pub mod messages; mod runtime_client; -pub use runtime_client::{DefaultSubsystemClient, RuntimeApiSubsystemClient}; +pub use runtime_client::{ChainApiBackend, DefaultSubsystemClient, RuntimeApiSubsystemClient}; pub use jaeger::*; pub use polkadot_node_jaeger as jaeger; diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 6bc874384594e93ac7970777c16d3a6276c5938a..eeb684149c80f2310ffe898bc7c6de2144ef4545 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -32,19 +32,24 @@ use polkadot_node_network_protocol::{ self as net_protocol, peer_set::PeerSet, request_response::Requests, PeerId, }; use polkadot_node_primitives::{ - approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote}, + approval::{ + v1::BlockApprovalMeta, + v2::{CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2}, + }, AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig, CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV, SignedDisputeStatement, SignedFullStatement, SignedFullStatementWithPVD, SubmitCollationParams, ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, - CandidateHash, CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, - CoreState, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, + async_backing, slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash, + CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState, + DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, - PvfExecTimeoutKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield, + PvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; @@ -140,14 +145,18 @@ pub enum CandidateValidationMessage { /// /// If there is no state available which can provide this data or the core for /// the para is not free at the relay-parent, an error is returned. - ValidateFromChainState( - CandidateReceipt, - Arc, - ExecutorParams, - /// Execution timeout - PvfExecTimeoutKind, - oneshot::Sender>, - ), + ValidateFromChainState { + /// The candidate receipt + candidate_receipt: CandidateReceipt, + /// The proof-of-validity + pov: Arc, + /// Session's executor parameters + executor_params: ExecutorParams, + /// Execution kind, used for timeouts and retries (backing/approvals) + exec_kind: PvfExecKind, + /// The sending side of the response channel + response_sender: oneshot::Sender>, + }, /// Validate a candidate with provided, exhaustive parameters for validation. /// /// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full @@ -157,27 +166,35 @@ pub enum CandidateValidationMessage { /// cases where the validity of the candidate is established. This is the case for the typical /// use-case: secondary checkers would use this request relying on the full prior checks /// performed by the relay-chain. - ValidateFromExhaustive( - PersistedValidationData, - ValidationCode, - CandidateReceipt, - Arc, - ExecutorParams, - /// Execution timeout - PvfExecTimeoutKind, - oneshot::Sender>, - ), + ValidateFromExhaustive { + /// Persisted validation data + validation_data: PersistedValidationData, + /// Validation code + validation_code: ValidationCode, + /// The candidate receipt + candidate_receipt: CandidateReceipt, + /// The proof-of-validity + pov: Arc, + /// Session's executor parameters + executor_params: ExecutorParams, + /// Execution kind, used for timeouts and retries (backing/approvals) + exec_kind: PvfExecKind, + /// The sending side of the response channel + response_sender: oneshot::Sender>, + }, /// Try to compile the given validation code and send back /// the outcome. /// /// The validation code is specified by the hash and will be queried from the runtime API at /// the given relay-parent. - PreCheck( - // Relay-parent - Hash, - ValidationCodeHash, - oneshot::Sender, - ), + PreCheck { + /// Relay-parent + relay_parent: Hash, + /// Validation code hash + validation_code_hash: ValidationCodeHash, + /// The sending side of the response channel + response_sender: oneshot::Sender, + }, } /// Messages received by the Collator Protocol subsystem. @@ -191,16 +208,20 @@ pub enum CollatorProtocolMessage { /// This should be sent before any `DistributeCollation` message. CollateOn(ParaId), /// Provide a collation to distribute to validators with an optional result sender. - /// The second argument is the parent head-data hash. - /// - /// The result sender should be informed when at least one parachain validator seconded the - /// collation. It is also completely okay to just drop the sender. - DistributeCollation( - CandidateReceipt, - Hash, - PoV, - Option>, - ), + DistributeCollation { + /// The receipt of the candidate. + candidate_receipt: CandidateReceipt, + /// The hash of the parent head-data. + /// Here to avoid computing the hash of the parent head data twice. + parent_head_data_hash: Hash, + /// Proof of validity. + pov: PoV, + /// This parent head-data is needed for elastic scaling. + parent_head_data: HeadData, + /// The result sender should be informed when at least one parachain validator seconded the + /// collation. It is also completely okay to just drop the sender. + result_sender: Option>, + }, /// Report a collator as having provided an invalid collation. This should lead to disconnect /// and blacklist of the collator. ReportCollator(CollatorId), @@ -703,6 +724,11 @@ pub enum RuntimeApiRequest { /// /// If it's not supported by the Runtime, the async backing is said to be disabled. AsyncBackingParams(RuntimeApiSender), + /// Get the node features. + NodeFeatures(SessionIndex, RuntimeApiSender), + /// Approval voting params + /// `V10` + ApprovalVotingParams(SessionIndex, RuntimeApiSender), } impl RuntimeApiRequest { @@ -731,6 +757,12 @@ impl RuntimeApiRequest { /// `DisabledValidators` pub const DISABLED_VALIDATORS_RUNTIME_REQUIREMENT: u32 = 8; + + /// `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. @@ -803,8 +835,10 @@ pub enum ProvisionerMessage { /// Message to the Collation Generation subsystem. #[derive(Debug)] pub enum CollationGenerationMessage { - /// Initialize the collation generation subsystem + /// Initialize the collation generation subsystem. Initialize(CollationGenerationConfig), + /// Reinitialize the collation generation subsystem, overriding the existing config. + Reinitialize(CollationGenerationConfig), /// Submit a collation to the subsystem. This will package it into a signed /// [`CommittedCandidateReceipt`] and distribute along the network to validators. /// @@ -841,6 +875,8 @@ pub enum AssignmentCheckError { InvalidCert(ValidatorIndex, String), #[error("Internal state mismatch: {0:?}, {1:?}")] Internal(Hash, CandidateHash), + #[error("Oversized candidate or core bitfield >= {0}")] + InvalidBitfield(usize), } /// The result type of [`ApprovalVotingMessage::CheckAndImportApproval`] request. @@ -906,15 +942,15 @@ pub enum ApprovalVotingMessage { /// Check if the assignment is valid and can be accepted by our view of the protocol. /// Should not be sent unless the block hash is known. CheckAndImportAssignment( - IndirectAssignmentCert, - CandidateIndex, + IndirectAssignmentCertV2, + CandidateBitfield, oneshot::Sender, ), /// Check if the approval vote is valid and can be accepted by our view of the /// 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 @@ -930,7 +966,7 @@ pub enum ApprovalVotingMessage { /// requires calling into `approval-distribution`: Calls should be infrequent and bounded. GetApprovalSignaturesForCandidate( CandidateHash, - oneshot::Sender>, + oneshot::Sender, ValidatorSignature)>>, ), } @@ -942,11 +978,11 @@ pub enum ApprovalDistributionMessage { NewBlocks(Vec), /// Distribute an assignment cert from the local validator. The cert is assumed /// to be valid, relevant, and for the given relay-parent and validator index. - DistributeAssignment(IndirectAssignmentCert, CandidateIndex), + DistributeAssignment(IndirectAssignmentCertV2, CandidateBitfield), /// 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), @@ -954,7 +990,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), @@ -1073,14 +1109,31 @@ pub struct ProspectiveValidationDataRequest { pub para_id: ParaId, /// The relay-parent of the candidate. pub candidate_relay_parent: Hash, - /// The parent head-data hash. - pub parent_head_data_hash: Hash, + /// The parent head-data. + pub parent_head_data: ParentHeadData, +} + +/// The parent head-data hash with optional data itself. +#[derive(Debug)] +pub enum ParentHeadData { + /// Parent head-data hash. + OnlyHash(Hash), + /// Parent head-data along with its hash. + WithData { + /// This will be provided for collations with elastic scaling enabled. + head_data: HeadData, + /// Parent head-data hash. + hash: Hash, + }, } /// Indicates the relay-parents whose fragment tree a candidate /// is present in and the depths of that tree the candidate is present in. pub type FragmentTreeMembership = Vec<(Hash, Vec)>; +/// A collection of ancestor candidates of a parachain. +pub type Ancestors = HashSet; + /// Messages sent to the Prospective Parachains subsystem. #[derive(Debug)] pub enum ProspectiveParachainsMessage { @@ -1097,14 +1150,19 @@ pub enum ProspectiveParachainsMessage { /// has been backed. This requires that the candidate was successfully introduced in /// the past. CandidateBacked(ParaId, CandidateHash), - /// Get a backable candidate hash along with its relay parent for the given parachain, - /// under the given relay-parent hash, which is a descendant of the given candidate hashes. - /// Returns `None` on the channel if no such candidate exists. - GetBackableCandidate( + /// Try getting N backable candidate hashes along with their relay parents for the given + /// parachain, under the given relay-parent hash, which is a descendant of the given ancestors. + /// Timed out ancestors should not be included in the collection. + /// N should represent the number of scheduled cores of this ParaId. + /// A timed out ancestor frees the cores of all of its descendants, so if there's a hole in the + /// supplied ancestor path, we'll get candidates that backfill those timed out slots first. It + /// may also return less/no candidates, if there aren't enough backable candidates recorded. + GetBackableCandidates( Hash, ParaId, - Vec, - oneshot::Sender>, + u32, + Ancestors, + oneshot::Sender>, ), /// Get the hypothetical frontier membership of candidates with the given properties /// under the specified active leaves' fragment trees. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index f7adcf9862b5d09829cb1d0edbb3f068b9c55713..4039fc9127da95e19e0aca427740793c39131ab9 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -16,19 +16,76 @@ use async_trait::async_trait; use polkadot_primitives::{ - async_backing, runtime_api::ParachainHost, slashing, Block, BlockNumber, CandidateCommitments, - CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, - ExecutorParams, GroupRotationInfo, Hash, 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::{AuxStore, HeaderBackend}; use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::{ApiError, ApiExt, ProvideRuntimeApi}; use sp_authority_discovery::AuthorityDiscoveryApi; +use sp_blockchain::{BlockStatus, Info}; use sp_consensus_babe::{BabeApi, Epoch}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::{collections::BTreeMap, sync::Arc}; +/// Offers header utilities. +/// +/// This is a async wrapper trait for ['HeaderBackend'] to be used with the +/// `ChainApiSubsystem`. +// This trait was introduced to suit the needs of collators. Depending on their operating mode, they +// might not have a client of the relay chain that can supply a synchronous HeaderBackend +// implementation. +#[async_trait] +pub trait ChainApiBackend: Send + Sync { + /// Get block header. Returns `None` if block is not found. + async fn header(&self, hash: Hash) -> sp_blockchain::Result>; + /// Get blockchain info. + async fn info(&self) -> sp_blockchain::Result>; + /// Get block number by hash. Returns `None` if the header is not in the chain. + async fn number( + &self, + hash: Hash, + ) -> sp_blockchain::Result::Number>>; + /// Get block hash by number. Returns `None` if the header is not in the chain. + async fn hash(&self, number: NumberFor) -> sp_blockchain::Result>; +} + +#[async_trait] +impl ChainApiBackend for T +where + T: HeaderBackend, +{ + /// Get block header. Returns `None` if block is not found. + async fn header(&self, hash: Hash) -> sp_blockchain::Result> { + HeaderBackend::header(self, hash) + } + + /// Get blockchain info. + async fn info(&self) -> sp_blockchain::Result> { + Ok(HeaderBackend::info(self)) + } + + /// Get block number by hash. Returns `None` if the header is not in the chain. + async fn number( + &self, + hash: Hash, + ) -> sp_blockchain::Result::Number>> { + HeaderBackend::number(self, hash) + } + + /// Get block hash by number. Returns `None` if the header is not in the chain. + async fn hash(&self, number: NumberFor) -> sp_blockchain::Result> { + HeaderBackend::hash(self, number) + } +} + /// Exposes all runtime calls that are used by the runtime API subsystem. #[async_trait] pub trait RuntimeApiSubsystemClient { @@ -257,8 +314,21 @@ pub trait RuntimeApiSubsystemClient { ) -> Result, ApiError>; // === v8 === + /// Gets the disabled validators at a specific block height 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. @@ -508,7 +578,78 @@ where self.client.runtime_api().async_backing_params(at) } + async fn node_features(&self, at: Hash) -> Result { + self.client.runtime_api().node_features(at) + } + 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) + } +} + +impl HeaderBackend for DefaultSubsystemClient +where + Client: HeaderBackend, + Block: sp_runtime::traits::Block, +{ + fn header( + &self, + hash: Block::Hash, + ) -> sc_client_api::blockchain::Result> { + self.client.header(hash) + } + + fn info(&self) -> Info { + self.client.info() + } + + fn status(&self, hash: Block::Hash) -> sc_client_api::blockchain::Result { + self.client.status(hash) + } + + fn number( + &self, + hash: Block::Hash, + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + self.client.number(hash) + } + + fn hash( + &self, + number: NumberFor, + ) -> sc_client_api::blockchain::Result> { + self.client.hash(number) + } +} + +impl AuxStore for DefaultSubsystemClient +where + Client: AuxStore, +{ + fn insert_aux< + 'a, + 'b: 'a, + 'c: 'a, + I: IntoIterator, + D: IntoIterator, + >( + &self, + insert: I, + delete: D, + ) -> sp_blockchain::Result<()> { + self.client.insert_aux(insert, delete) + } + + fn get_aux(&self, key: &[u8]) -> sp_blockchain::Result>> { + self.client.get_aux(key) + } } diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index d9364e2c2c0f15158b861ef0e8c44121a7e2d650..a668f8de76a0bdf15b1e4498924279b5fc467d3e 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -1,21 +1,24 @@ [package] name = "polkadot-node-subsystem-util" description = "Subsystem traits and message definitions" -version = "1.0.0" +version = "7.0.0" 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" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -parking_lot = "0.11.2" +parking_lot = "0.12.1" pin-project = "1.0.9" rand = "0.8.5" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" gum = { package = "tracing-gum", path = "../gum" } derive_more = "0.99.17" @@ -29,7 +32,7 @@ polkadot-node-network-protocol = { path = "../network/protocol" } polkadot-primitives = { path = "../../primitives" } polkadot-node-primitives = { path = "../primitives" } polkadot-overseer = { path = "../overseer" } -metered = { package = "prioritized-metered-channel", version = "0.5.1", default-features = false, features=["futures_channel"] } +metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false, features = ["futures_channel"] } sp-core = { path = "../../../substrate/primitives/core" } sp-application-crypto = { path = "../../../substrate/primitives/application-crypto" } @@ -37,13 +40,13 @@ 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" env_logger = "0.9.0" futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.17" +log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } lazy_static = "1.4.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index a5f3e9d4a0c0e08ebb3b738054ae758f1c98e822..f13beb3502fc22dcb768fb5548965eb7727f46fd 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -55,6 +55,7 @@ use sp_core::ByteArray; use sp_keystore::{Error as KeystoreError, KeystorePtr}; use std::time::Duration; use thiserror::Error; +use vstaging::get_disabled_validators_with_fallback; pub use metered; pub use polkadot_node_network_protocol::MIN_GOSSIP_PEERS; @@ -79,6 +80,9 @@ pub mod inclusion_emulator; /// Convenient and efficient runtime info access. pub mod runtime; +/// Helpers for working with unreleased runtime calls +pub mod vstaging; + /// Nested message sending /// /// Useful for having mostly synchronous code, with submodules spawning short lived asynchronous @@ -92,6 +96,8 @@ mod determine_new_blocks; #[cfg(test)] mod tests; +const LOG_TARGET: &'static str = "parachain::subsystem-util"; + /// Duration a job will wait after sending a stop signal before hard-aborting. pub const JOB_GRACEFUL_STOP_DURATION: Duration = Duration::from_secs(1); /// Capacity of channels to and from individual jobs @@ -135,6 +141,20 @@ impl From for Error { } } +impl TryFrom for Error { + type Error = (); + + fn try_from(e: crate::runtime::Error) -> Result { + use crate::runtime::Error; + + match e { + Error::RuntimeRequestCanceled(e) => Ok(Self::Oneshot(e)), + Error::RuntimeRequest(e) => Ok(Self::RuntimeApi(e)), + Error::NoSuchSession(_) | Error::NoExecutorParams(_) => Err(()), + } + } +} + /// A type alias for Runtime API receivers. pub type RuntimeApiReceiver = oneshot::Receiver>; @@ -157,6 +177,62 @@ where rx } +/// Verifies if `ParachainHost` runtime api is at least at version `required_runtime_version`. This +/// method is used to determine if a given runtime call is supported by the runtime. +pub async fn has_required_runtime( + sender: &mut Sender, + relay_parent: Hash, + required_runtime_version: u32, +) -> bool +where + Sender: SubsystemSender, +{ + gum::trace!(target: LOG_TARGET, ?relay_parent, "Fetching ParachainHost runtime api version"); + + let (tx, rx) = oneshot::channel(); + sender + .send_message(RuntimeApiMessage::Request(relay_parent, RuntimeApiRequest::Version(tx))) + .await; + + match rx.await { + Result::Ok(Ok(runtime_version)) => { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?runtime_version, + ?required_runtime_version, + "Fetched ParachainHost runtime api version" + ); + runtime_version >= required_runtime_version + }, + Result::Ok(Err(RuntimeApiError::Execution { source: error, .. })) => { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?error, + "Execution error while fetching ParachainHost runtime api version" + ); + false + }, + Result::Ok(Err(RuntimeApiError::NotSupported { .. })) => { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + "NotSupported error while fetching ParachainHost runtime api version" + ); + false + }, + Result::Err(_) => { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + "Cancelled error while fetching ParachainHost runtime api version" + ); + false + }, + } +} + /// Construct specialized request functions for the runtime. /// /// These would otherwise get pretty repetitive. @@ -378,6 +454,7 @@ pub struct Validator { signing_context: SigningContext, key: ValidatorId, index: ValidatorIndex, + disabled: bool, } impl Validator { @@ -399,7 +476,14 @@ impl Validator { let validators = validators?; - Self::construct(&validators, signing_context, keystore) + // TODO: https://github.com/paritytech/polkadot-sdk/issues/1940 + // When `DisabledValidators` is released remove this and add a + // `request_disabled_validators` call here + let disabled_validators = get_disabled_validators_with_fallback(sender, parent) + .await + .map_err(|e| Error::try_from(e).expect("the conversion is infallible; qed"))?; + + Self::construct(&validators, &disabled_validators, signing_context, keystore) } /// Construct a validator instance without performing runtime fetches. @@ -407,13 +491,16 @@ impl Validator { /// This can be useful if external code also needs the same data. pub fn construct( validators: &[ValidatorId], + disabled_validators: &[ValidatorIndex], signing_context: SigningContext, keystore: KeystorePtr, ) -> Result { let (key, index) = signing_key_and_index(validators, &keystore).ok_or(Error::NotAValidator)?; - Ok(Validator { signing_context, key, index }) + let disabled = disabled_validators.iter().any(|d: &ValidatorIndex| *d == index); + + Ok(Validator { signing_context, key, index, disabled }) } /// Get this validator's id. @@ -426,6 +513,11 @@ impl Validator { self.index } + /// Get the enabled/disabled state of this validator + pub fn disabled(&self) -> bool { + self.disabled + } + /// Get the current signing context. pub fn signing_context(&self) -> &SigningContext { &self.signing_context diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index 8d7cef88a70e082779cd6e0892502f27e486c7c6..481625acb321994c58482c14aaf2860f092ae0c3 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, 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::{ @@ -41,7 +43,7 @@ use crate::{ request_from_runtime, request_key_ownership_proof, request_on_chain_votes, request_session_executor_params, request_session_index_for_child, request_session_info, request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, - request_validator_groups, + request_validator_groups, vstaging::get_disabled_validators_with_fallback, }; /// Errors that can happen on runtime fetches. @@ -73,6 +75,11 @@ pub struct RuntimeInfo { /// overseer seems sensible. session_index_cache: LruMap, + /// In the happy case, we do not query disabled validators at all. In the worst case, we can + /// query it order of `n_cores` times `n_validators` per block, so caching it here seems + /// sensible. + disabled_validators_cache: LruMap>, + /// Look up cached sessions by `SessionIndex`. session_info_cache: LruMap, @@ -92,6 +99,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`. @@ -125,6 +134,7 @@ impl RuntimeInfo { Self { session_index_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size.max(10))), session_info_cache: LruMap::new(ByLength::new(cfg.session_cache_lru_size)), + disabled_validators_cache: LruMap::new(ByLength::new(100)), pinned_blocks: LruMap::new(ByLength::new(cfg.session_cache_lru_size)), keystore: cfg.keystore, } @@ -176,6 +186,26 @@ impl RuntimeInfo { self.get_session_info_by_index(sender, relay_parent, session_index).await } + /// Get the list of disabled validators at the relay parent. + pub async fn get_disabled_validators( + &mut self, + sender: &mut Sender, + relay_parent: Hash, + ) -> Result> + where + Sender: SubsystemSender, + { + match self.disabled_validators_cache.get(&relay_parent).cloned() { + Some(result) => Ok(result), + None => { + let disabled_validators = + get_disabled_validators_with_fallback(sender, relay_parent).await?; + self.disabled_validators_cache.insert(relay_parent, disabled_validators.clone()); + Ok(disabled_validators) + }, + } + } + /// Get `ExtendedSessionInfo` by session index. /// /// `request_session_info` still requires the parent to be passed in, so we take the parent @@ -202,7 +232,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); } @@ -507,3 +550,32 @@ pub async fn request_min_backing_votes( min_backing_votes_res } } + +/// Request the node features enabled in the runtime. +/// Pass in the session index for caching purposes, as it should only change on session boundaries. +/// Prior to runtime API version 9, just return `None`. +pub async fn request_node_features( + parent: Hash, + session_index: SessionIndex, + sender: &mut impl overseer::SubsystemSender, +) -> Result> { + let res = recv_runtime( + request_from_runtime(parent, sender, |tx| { + RuntimeApiRequest::NodeFeatures(session_index, tx) + }) + .await, + ) + .await; + + if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = res { + gum::trace!( + target: LOG_TARGET, + ?parent, + "Querying the node features from the runtime is not supported by the current Runtime API", + ); + + Ok(None) + } else { + res.map(Some) + } +} diff --git a/polkadot/node/subsystem-util/src/vstaging.rs b/polkadot/node/subsystem-util/src/vstaging.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e807eff5387693bc00198a3be5f257778bea0f2 --- /dev/null +++ b/polkadot/node/subsystem-util/src/vstaging.rs @@ -0,0 +1,56 @@ +// 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 . + +//! Contains helpers for staging runtime calls. +//! +//! This module is intended to contain common boiler plate code handling unreleased runtime API +//! calls. + +use polkadot_node_subsystem_types::messages::{RuntimeApiMessage, RuntimeApiRequest}; +use polkadot_overseer::SubsystemSender; +use polkadot_primitives::{Hash, ValidatorIndex}; + +use crate::{has_required_runtime, request_disabled_validators, runtime}; + +const LOG_TARGET: &'static str = "parachain::subsystem-util-vstaging"; + +// TODO: https://github.com/paritytech/polkadot-sdk/issues/1940 +/// Returns disabled validators list if the runtime supports it. Otherwise logs a debug messages and +/// returns an empty vec. +/// Once runtime ver `DISABLED_VALIDATORS_RUNTIME_REQUIREMENT` is released remove this function and +/// replace all usages with `request_disabled_validators` +pub async fn get_disabled_validators_with_fallback>( + sender: &mut Sender, + relay_parent: Hash, +) -> Result, runtime::Error> { + let disabled_validators = if has_required_runtime( + sender, + relay_parent, + RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT, + ) + .await + { + request_disabled_validators(relay_parent, sender) + .await + .await + .map_err(runtime::Error::RuntimeRequestCanceled)?? + } else { + gum::debug!(target: LOG_TARGET, "Runtime doesn't support `DisabledValidators` - continuing with an empty disabled validators set"); + vec![] + }; + + Ok(disabled_validators) +} diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index 9b77359517c9926511eda509c7976386540619b8..c59c1f88e33995aef7578da58e28d086668f14ee 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "polkadot-node-subsystem" description = "Subsystem traits and message definitions and the generated overseer" -version = "1.0.0" +version = "7.0.0" 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 bc4ff74be4bb89587408ee91f54cf9d00d97abe3..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"] } @@ -38,7 +41,7 @@ sp-keyring = { path = "../../../../substrate/primitives/keyring" } futures = "0.3.21" [features] -runtime-benchmarks= [ +runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-test-runtime/runtime-benchmarks", diff --git a/polkadot/node/test/client/src/block_builder.rs b/polkadot/node/test/client/src/block_builder.rs index b4ff050ff1529f29886f281a1a0f0ebffed2a13a..57e6008917af9cf6a5ceb6d173f680f9d90e5ebb 100644 --- a/polkadot/node/test/client/src/block_builder.rs +++ b/polkadot/node/test/client/src/block_builder.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{Client, FullBackend}; +use crate::Client; use parity_scale_codec::{Decode, Encode}; use polkadot_primitives::{Block, InherentData as ParachainsInherentData}; use polkadot_test_runtime::UncheckedExtrinsic; use polkadot_test_service::GetLastTimestamp; -use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; +use sc_block_builder::{BlockBuilder, BlockBuilderBuilder}; use sp_api::ProvideRuntimeApi; use sp_consensus_babe::{ digests::{PreDigest, SecondaryPlainPreDigest}, @@ -34,9 +34,7 @@ pub trait InitPolkadotBlockBuilder { /// /// This will automatically create and push the inherents for you to make the block valid for /// the test runtime. - fn init_polkadot_block_builder( - &self, - ) -> sc_block_builder::BlockBuilder; + fn init_polkadot_block_builder(&self) -> sc_block_builder::BlockBuilder; /// Init a Polkadot specific block builder at a specific block that works for the test runtime. /// @@ -45,11 +43,11 @@ pub trait InitPolkadotBlockBuilder { fn init_polkadot_block_builder_at( &self, hash: ::Hash, - ) -> sc_block_builder::BlockBuilder; + ) -> sc_block_builder::BlockBuilder; } impl InitPolkadotBlockBuilder for Client { - fn init_polkadot_block_builder(&self) -> BlockBuilder { + fn init_polkadot_block_builder(&self) -> BlockBuilder { let chain_info = self.chain_info(); self.init_polkadot_block_builder_at(chain_info.best_hash) } @@ -57,7 +55,7 @@ impl InitPolkadotBlockBuilder for Client { fn init_polkadot_block_builder_at( &self, hash: ::Hash, - ) -> BlockBuilder { + ) -> BlockBuilder { let last_timestamp = self.runtime_api().get_last_timestamp(hash).expect("Get last timestamp"); @@ -90,8 +88,12 @@ impl InitPolkadotBlockBuilder for Client { )], }; - let mut block_builder = self - .new_block_at(hash, digest, false) + let mut block_builder = BlockBuilderBuilder::new(self) + .on_parent_block(hash) + .fetch_parent_block_number(&self) + .expect("Fetches parent block number") + .with_inherent_digests(digest) + .build() .expect("Creates new block builder for test runtime"); let mut inherent_data = sp_inherents::InherentData::new(); @@ -144,7 +146,7 @@ pub trait BlockBuilderExt { ) -> Result<(), sp_blockchain::Error>; } -impl BlockBuilderExt for BlockBuilder<'_, Block, Client, FullBackend> { +impl BlockBuilderExt for BlockBuilder<'_, Block, Client> { fn push_polkadot_extrinsic( &mut self, ext: UncheckedExtrinsic, diff --git a/polkadot/node/test/client/src/lib.rs b/polkadot/node/test/client/src/lib.rs index 5d97ffcdf1da4d4ec42ead64f9b8e78d98227368..6b205c09f2f3297cb8b53bfb62cc80a1ed25c3a7 100644 --- a/polkadot/node/test/client/src/lib.rs +++ b/polkadot/node/test/client/src/lib.rs @@ -51,7 +51,7 @@ pub struct GenesisParameters; impl substrate_test_client::GenesisInit for GenesisParameters { fn genesis_storage(&self) -> Storage { - polkadot_test_service::chain_spec::polkadot_local_testnet_genesis() + polkadot_test_service::chain_spec::polkadot_local_testnet_config() .build_storage() .expect("Builds test runtime genesis storage") } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 1924418a48565e12fa5f1b628943f604ba9c4116..e7892abcd87bf09140b53f5f123798b2ccce7ac4 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.106" +serde_json = { workspace = true, default-features = true } tempfile = "3.2.0" tokio = "1.24.2" @@ -59,13 +62,12 @@ substrate-test-client = { path = "../../../../substrate/test-utils/client" } [dev-dependencies] pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } -serde_json = "1.0.107" substrate-test-utils = { path = "../../../../substrate/test-utils" } tokio = { version = "1.24.2", features = ["macros"] } [features] -runtime-metrics=[ "polkadot-test-runtime/runtime-metrics" ] -runtime-benchmarks= [ +runtime-metrics = ["polkadot-test-runtime/runtime-metrics"] +runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-staking/runtime-benchmarks", diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index bedb1250b2a9853a7bcf96867b717cee92f272c5..f14fa9fde58b4426690cd1bd0e16129f095c34ec 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -19,7 +19,9 @@ use babe_primitives::AuthorityId as BabeId; use grandpa::AuthorityId as GrandpaId; use pallet_staking::Forcing; -use polkadot_primitives::{AccountId, AssignmentId, ValidatorId, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_primitives::{ + vstaging::SchedulerParams, AccountId, AssignmentId, ValidatorId, MAX_CODE_SIZE, MAX_POV_SIZE, +}; use polkadot_service::chain_spec::{get_account_id_from_seed, get_from_seed, Extensions}; use polkadot_test_runtime::BABE_GENESIS_EPOCH_CONFIG; use sc_chain_spec::{ChainSpec, ChainType}; @@ -31,8 +33,7 @@ use test_runtime_constants::currency::DOTS; const DEFAULT_PROTOCOL_ID: &str = "dot"; /// The `ChainSpec` parameterized for polkadot test runtime. -pub type PolkadotChainSpec = - sc_service::GenericChainSpec; +pub type PolkadotChainSpec = sc_service::GenericChainSpec<(), Extensions>; /// Returns the properties for the [`PolkadotChainSpec`]. pub fn polkadot_chain_spec_properties() -> serde_json::map::Map { @@ -46,22 +47,21 @@ pub fn polkadot_chain_spec_properties() -> serde_json::map::Map PolkadotChainSpec { - PolkadotChainSpec::from_genesis( - "Local Testnet", - "local_testnet", - ChainType::Local, - || polkadot_local_testnet_genesis(), - vec![], - None, - Some(DEFAULT_PROTOCOL_ID), - None, - Some(polkadot_chain_spec_properties()), + PolkadotChainSpec::builder( + polkadot_test_runtime::WASM_BINARY.expect("Wasm binary must be built for testing"), Default::default(), ) + .with_name("Local Testnet") + .with_id("local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(polkadot_local_testnet_genesis()) + .with_protocol_id(DEFAULT_PROTOCOL_ID) + .with_properties(polkadot_chain_spec_properties()) + .build() } /// Local testnet genesis config (multivalidator Alice + Bob) -pub fn polkadot_local_testnet_genesis() -> polkadot_test_runtime::RuntimeGenesisConfig { +pub fn polkadot_local_testnet_genesis() -> serde_json::Value { polkadot_testnet_genesis( vec![get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")], get_account_id_from_seed::("Alice"), @@ -114,7 +114,7 @@ fn polkadot_testnet_genesis( )>, root_key: AccountId, endowed_accounts: Option>, -) -> polkadot_test_runtime::RuntimeGenesisConfig { +) -> serde_json::Value { use polkadot_test_runtime as runtime; let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(testnet_accounts); @@ -122,17 +122,12 @@ fn polkadot_testnet_genesis( const ENDOWMENT: u128 = 1_000_000 * DOTS; const STASH: u128 = 100 * DOTS; - runtime::RuntimeGenesisConfig { - system: runtime::SystemConfig { - code: runtime::WASM_BINARY.expect("Wasm binary must be built for testing").to_vec(), - ..Default::default() - }, - indices: runtime::IndicesConfig { indices: vec![] }, - balances: runtime::BalancesConfig { - balances: endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().map(|k| (k.clone(), ENDOWMENT)).collect::>(), }, - session: runtime::SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -149,47 +144,41 @@ fn polkadot_testnet_genesis( }) .collect::>(), }, - staking: runtime::StakingConfig { - minimum_validator_count: 1, - validator_count: 2, - stakers: initial_authorities + "staking": { + "minimumValidatorCount": 1, + "validatorCount": 2, + "stakers": initial_authorities .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, runtime::StakerStatus::Validator)) - .collect(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), - force_era: Forcing::NotForcing, - slash_reward_fraction: Perbill::from_percent(10), - ..Default::default() - }, - babe: runtime::BabeConfig { - authorities: vec![], - epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() + .map(|x| (x.0.clone(), x.0.clone(), STASH, runtime::StakerStatus::::Validator)) + .collect::>(), + "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + "forceEra": Forcing::NotForcing, + "slashRewardFraction": Perbill::from_percent(10), }, - grandpa: Default::default(), - authority_discovery: runtime::AuthorityDiscoveryConfig { - keys: vec![], - ..Default::default() + "babe": { + "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG), }, - claims: runtime::ClaimsConfig { claims: vec![], vesting: vec![] }, - vesting: runtime::VestingConfig { vesting: vec![] }, - sudo: runtime::SudoConfig { key: Some(root_key) }, - configuration: runtime::ConfigurationConfig { - config: polkadot_runtime_parachains::configuration::HostConfiguration { + "sudo": { "key": Some(root_key) }, + "configuration": { + "config": polkadot_runtime_parachains::configuration::HostConfiguration { validation_upgrade_cooldown: 10u32, validation_upgrade_delay: 5, code_retention_period: 1200, max_code_size: MAX_CODE_SIZE, max_pov_size: MAX_POV_SIZE, max_head_data_size: 32 * 1024, - group_rotation_frequency: 20, - paras_availability_period: 4, no_show_slots: 10, minimum_validation_upgrade_delay: 5, + max_downward_message_size: 1024, + scheduler_params: SchedulerParams { + group_rotation_frequency: 20, + paras_availability_period: 4, + ..Default::default() + }, ..Default::default() }, - }, - } + } + }) } /// Can be called for a `Configuration` to check if it is a configuration for the `Test` network. diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index be2746daf32121651d7d5c1bbf395ff3e243976a..ca904bae28db2dcc2f0a2e50e1cf92528a135003 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -28,7 +28,9 @@ use polkadot_overseer::Handle; use polkadot_primitives::{Balance, CollatorPair, HeadData, Id as ParaId, ValidationCode}; use polkadot_runtime_common::BlockHashCount; use polkadot_runtime_parachains::paras::{ParaGenesisArgs, ParaKind}; -use polkadot_service::{Error, FullClient, IsParachainNode, NewFull, PrometheusConfig}; +use polkadot_service::{ + Error, FullClient, IsParachainNode, NewFull, OverseerGen, PrometheusConfig, +}; use polkadot_test_runtime::{ ParasCall, ParasSudoWrapperCall, Runtime, SignedExtra, SignedPayload, SudoCall, UncheckedExtrinsic, VERSION, @@ -42,8 +44,8 @@ use sc_network::{ }; use sc_service::{ config::{ - DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, WasmExecutionMethod, - WasmtimeInstantiationStrategy, + DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, RpcBatchRequestConfig, + WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, BlocksPruning, Configuration, Role, RpcHandlers, TaskManager, }; @@ -69,10 +71,11 @@ pub use polkadot_service::{FullBackend, GetLastTimestamp}; /// Create a new full node. #[sc_tracing::logging::prefix_logs_with(config.network.node_name.as_str())] -pub fn new_full( +pub fn new_full( config: Configuration, is_parachain_node: IsParachainNode, workers_path: Option, + overseer_gen: OverseerGenerator, ) -> Result { let workers_path = Some(workers_path.unwrap_or_else(get_relative_workers_path_for_test)); @@ -80,14 +83,15 @@ pub fn new_full( config, polkadot_service::NewFullParams { is_parachain_node, - grandpa_pause: None, enable_beefy: true, + force_authoring_backoff: false, jaeger_agent: None, telemetry_worker_handle: None, node_version: None, + secure_validator_mode: false, workers_path, workers_names: None, - overseer_gen: polkadot_service::RealOverseerGen, + overseer_gen, overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, @@ -181,6 +185,9 @@ pub fn node_config( rpc_id_provider: None, rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, + rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -205,9 +212,13 @@ pub fn run_validator_node( worker_program_path: Option, ) -> PolkadotTestNode { let multiaddr = config.network.listen_addresses[0].clone(); - let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = - new_full(config, IsParachainNode::No, worker_program_path) - .expect("could not create Polkadot test service"); + let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = new_full( + config, + IsParachainNode::No, + worker_program_path, + polkadot_service::ValidatorOverseerGen, + ) + .expect("could not create Polkadot test service"); let overseer_handle = overseer_handle.expect("test node must have an overseer handle"); let peer_id = network.local_peer_id(); @@ -237,9 +248,13 @@ pub fn run_collator_node( ) -> PolkadotTestNode { let config = node_config(storage_update_func, tokio_handle, key, boot_nodes, false); let multiaddr = config.network.listen_addresses[0].clone(); - let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = - new_full(config, IsParachainNode::Collator(collator_pair), None) - .expect("could not create Polkadot test service"); + let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = new_full( + config, + IsParachainNode::Collator(collator_pair), + None, + polkadot_service::CollatorOverseerGen, + ) + .expect("could not create Polkadot test service"); let overseer_handle = overseer_handle.expect("test node must have an overseer handle"); let peer_id = network.local_peer_id(); diff --git a/polkadot/node/tracking-allocator/Cargo.toml b/polkadot/node/tracking-allocator/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d98377e53759c9df35e5eb96f2de3860555a87b8 --- /dev/null +++ b/polkadot/node/tracking-allocator/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "staging-tracking-allocator" +description = "Tracking allocator to control the amount of memory consumed by the process" +version = "2.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 new file mode 100644 index 0000000000000000000000000000000000000000..33f110ce711978c83474550b0f436ca712a413ed --- /dev/null +++ b/polkadot/node/tracking-allocator/src/lib.rs @@ -0,0 +1,247 @@ +// 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 . + +//! Tracking/limiting global allocator. Calculates the peak allocation between two checkpoints for +//! the whole process. Accepts an optional limit and a failure handler which is called if the limit +//! is overflown. + +use core::{ + alloc::{GlobalAlloc, Layout}, + ops::{Deref, DerefMut}, +}; +use std::{ + cell::UnsafeCell, + ptr::null_mut, + sync::atomic::{AtomicBool, Ordering}, +}; + +struct Spinlock { + lock: AtomicBool, + data: UnsafeCell, +} + +struct SpinlockGuard<'a, T: 'a> { + lock: &'a Spinlock, +} + +// SAFETY: We require that the data inside of the `SpinLock` is `Send`, so it can be sent +// and accessed by any thread as long as it's accessed by only one thread at a time. +// The `SpinLock` provides an exclusive lock over it, so it guarantees that multiple +// threads cannot access it at the same time, hence it implements `Sync` (that is, it can be +// accessed concurrently from multiple threads, even though the `T` itself might not +// necessarily be `Sync` too). +unsafe impl Sync for Spinlock {} + +impl Spinlock { + pub const fn new(t: T) -> Spinlock { + Spinlock { lock: AtomicBool::new(false), data: UnsafeCell::new(t) } + } + + #[inline] + pub fn lock(&self) -> SpinlockGuard { + loop { + // Try to acquire the lock. + if self + .lock + .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + return SpinlockGuard { lock: self } + } + // We failed to acquire the lock; wait until it's unlocked. + // + // In theory this should result in less coherency traffic as unlike `compare_exchange` + // it is a read-only operation, so multiple cores can execute it simultaneously + // without taking an exclusive lock over the cache line. + while self.lock.load(Ordering::Relaxed) { + std::hint::spin_loop(); + } + } + } + + // SAFETY: It should be only called from the guard's destructor. Calling it explicitly while + // the guard is alive is undefined behavior, as it breaks the security contract of `Deref` and + // `DerefMut`, which implies that lock is held at the moment of dereferencing. + #[inline] + unsafe fn unlock(&self) { + self.lock.store(false, Ordering::Release); + } +} + +impl Deref for SpinlockGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + // SAFETY: It is safe to dereference a guard to the `UnsafeCell` underlying data as the + // presence of the guard means the data is already locked. + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for SpinlockGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + // SAFETY: Same as for `Deref::deref`. + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for SpinlockGuard<'_, T> { + fn drop(&mut self) { + // SAFETY: Calling `unlock` is only safe when it's guaranteed no guard outlives the + // unlocking point; here, the guard is dropped, so it is safe. + unsafe { self.lock.unlock() } + } +} + +struct TrackingAllocatorData { + current: isize, + peak: isize, + limit: isize, + failure_handler: Option>, +} + +impl TrackingAllocatorData { + fn start_tracking( + mut guard: SpinlockGuard, + limit: isize, + failure_handler: Option>, + ) { + guard.current = 0; + guard.peak = 0; + guard.limit = limit; + // Cannot drop it yet, as it would trigger a deallocation + let old_handler = guard.failure_handler.take(); + guard.failure_handler = failure_handler; + drop(guard); + drop(old_handler); + } + + fn end_tracking(mut guard: SpinlockGuard) -> isize { + let peak = guard.peak; + guard.limit = 0; + // Cannot drop it yet, as it would trigger a deallocation + let old_handler = guard.failure_handler.take(); + drop(guard); + drop(old_handler); + peak + } + + #[inline] + fn track_and_check_limits( + mut guard: SpinlockGuard, + alloc: isize, + ) -> Option> { + guard.current += alloc; + if guard.current > guard.peak { + guard.peak = guard.current; + } + if guard.limit == 0 || guard.peak <= guard.limit { + None + } else { + Some(guard) + } + } +} + +static ALLOCATOR_DATA: Spinlock = + Spinlock::new(TrackingAllocatorData { current: 0, peak: 0, limit: 0, failure_handler: None }); + +pub struct TrackingAllocator(pub A); + +impl TrackingAllocator { + /// Start tracking memory allocations and deallocations. + /// + /// # Safety + /// + /// Failure handler is called with the allocator being in the locked state. Thus, no + /// allocations or deallocations are allowed inside the failure handler; otherwise, a + /// deadlock will occur. + pub unsafe fn start_tracking( + &self, + limit: Option, + failure_handler: Option>, + ) { + TrackingAllocatorData::start_tracking( + ALLOCATOR_DATA.lock(), + limit.unwrap_or(0), + failure_handler, + ); + } + + /// End tracking and return the peak allocation value in bytes (as `isize`). Peak allocation + /// value is not guaranteed to be neither non-zero nor positive. + pub fn end_tracking(&self) -> isize { + TrackingAllocatorData::end_tracking(ALLOCATOR_DATA.lock()) + } +} + +#[cold] +#[inline(never)] +unsafe fn fail_allocation(guard: SpinlockGuard) -> *mut u8 { + if let Some(failure_handler) = &guard.failure_handler { + failure_handler() + } + null_mut() +} + +unsafe impl GlobalAlloc for TrackingAllocator { + // SAFETY: + // * The wrapped methods are as safe as the underlying allocator implementation is + + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let guard = ALLOCATOR_DATA.lock(); + if let Some(guard) = + TrackingAllocatorData::track_and_check_limits(guard, layout.size() as isize) + { + fail_allocation(guard) + } else { + self.0.alloc(layout) + } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let guard = ALLOCATOR_DATA.lock(); + if let Some(guard) = + TrackingAllocatorData::track_and_check_limits(guard, layout.size() as isize) + { + fail_allocation(guard) + } else { + self.0.alloc_zeroed(layout) + } + } + + #[inline] + 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) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let guard = ALLOCATOR_DATA.lock(); + if let Some(guard) = TrackingAllocatorData::track_and_check_limits( + guard, + (new_size as isize) - (layout.size() as isize), + ) { + fail_allocation(guard) + } else { + self.0.realloc(ptr, layout, new_size) + } + } +} diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index 9bf56b550bbcd7803806bf37d2a40b9ac5a92e01..fa99490a997434eedee977121f13d8dcc1a9b5a1 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -8,15 +8,18 @@ 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" tokio-tungstenite = "0.17" -futures-util = "0.3.23" +futures-util = "0.3.30" lazy_static = "1.4.0" parity-scale-codec = { version = "3.6.1", features = ["derive"] } reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false } -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../gum" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index 681bee3c32738a46feb99117516712d96fa44969..d8c3cea7ad8b16062f346c12842d5f652936797c 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -4,31 +4,33 @@ description = "Types and utilities for creating and working with parachains" authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "6.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 # various unnecessary Substrate-specific endpoints. -parity-scale-codec = { version = "3.6.1", default-features = false, features = [ "derive" ] } +parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-std = { path = "../../substrate/primitives/std", default-features = false } sp-runtime = { path = "../../substrate/primitives/runtime", default-features = false, features = ["serde"] } sp-core = { path = "../../substrate/primitives/core", default-features = false, features = ["serde"] } -frame-support = { path = "../../substrate/frame/support", default-features = false } +sp-weights = { path = "../../substrate/primitives/weights", default-features = false } polkadot-core-primitives = { path = "../core-primitives", default-features = false } derive_more = "0.99.11" -bounded-collections = { version = "0.1.8", default-features = false, features = ["serde"] } +bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } # all optional crates. -serde = { version = "1.0.188", default-features = false, features = ["derive", "alloc"] } +serde = { features = ["alloc", "derive"], workspace = true } [features] -default = [ "std" ] +default = ["std"] wasm-api = [] std = [ "bounded-collections/std", - "frame-support/std", "parity-scale-codec/std", "polkadot-core-primitives/std", "scale-info/std", @@ -36,8 +38,6 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", + "sp-weights/std", ] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] +runtime-benchmarks = ["sp-runtime/runtime-benchmarks"] diff --git a/polkadot/parachain/src/lib.rs b/polkadot/parachain/src/lib.rs index 913d887e4a8a73a4a35aea9dd5b62edc579efce3..bd75296bf837128c7b10b8a89a823cb66dc1ca8b 100644 --- a/polkadot/parachain/src/lib.rs +++ b/polkadot/parachain/src/lib.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#![warn(unused_crate_dependencies)] - //! Defines primitive types for creating or validating a parachain. //! //! When compiled with standard library support, this crate exports a `wasm` diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs index 3247e841422ec55c937d66cf0db85f69a66f2294..5a1efdf898217a18972cab8c9ea49a41e5a2280f 100644 --- a/polkadot/parachain/src/primitives.rs +++ b/polkadot/parachain/src/primitives.rs @@ -20,12 +20,12 @@ use sp_std::vec::Vec; use bounded_collections::{BoundedVec, ConstU32}; -use frame_support::weights::Weight; use parity_scale_codec::{CompactAs, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_core::{bytes, RuntimeDebug, TypeId}; use sp_runtime::traits::Hash as _; +use sp_weights::Weight; use polkadot_core_primitives::{Hash, OutboundHrmpMessage}; @@ -333,7 +333,7 @@ impl DmpMessageHandler for () { } /// The aggregate XCMP message format. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, RuntimeDebug)] pub enum XcmpMessageFormat { /// Encoded `VersionedXcm` messages, all concatenated. ConcatenatedVersionedXcm, diff --git a/polkadot/parachain/test-parachains/Cargo.toml b/polkadot/parachain/test-parachains/Cargo.toml index a30be9c678af83871182389e2c391e041c61bdc1..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"] } @@ -18,5 +21,5 @@ halt = { package = "test-parachain-halt", path = "halt" } sp-core = { path = "../../../substrate/primitives/core" } [features] -default = [ "std" ] -std = [ "adder/std", "halt/std" ] +default = ["std"] +std = ["adder/std", "halt/std", "parity-scale-codec/std"] diff --git a/polkadot/parachain/test-parachains/adder/Cargo.toml b/polkadot/parachain/test-parachains/adder/Cargo.toml index f22b5ccfdcc9894bd1e6cd29212648a1cdd58e16..eec19ef788aad510d7ea9ef6d2ab61d7c6aeb8f9 100644 --- a/polkadot/parachain/test-parachains/adder/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/Cargo.toml @@ -8,19 +8,22 @@ 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" ] } +parachain = { package = "polkadot-parachain-primitives", path = "../..", default-features = false, features = ["wasm-api"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } sp-std = { path = "../../../../substrate/primitives/std", default-features = false } tiny-keccak = { version = "2.0.2", features = ["keccak"] } -dlmalloc = { version = "0.2.4", features = [ "global" ] } +dlmalloc = { version = "0.2.4", features = ["global"] } # We need to make sure the global allocator is disabled until we have support of full substrate externalities -sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = [ "disable_allocator" ] } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = ["disable_allocator"] } [build-dependencies] substrate-wasm-builder = { path = "../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] -std = [ "parachain/std", "sp-io/std", "sp-std/std" ] +default = ["std"] +std = ["parachain/std", "parity-scale-codec/std", "sp-io/std", "sp-std/std"] diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 70f2ae769a8f4cd5f59aaf2cc707053f0d3f35e2..cb283c271191f80a0f29275fa44eb883586aeb8b 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -7,16 +7,19 @@ 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.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-adder = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/parachain/test-parachains/adder/collator/src/cli.rs b/polkadot/parachain/test-parachains/adder/collator/src/cli.rs index 14b259706835f3649d93af8b3b814f19755f8e62..5e9b4f584d44fda7c2a973d2a2c17b4a23952bf2 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/cli.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/cli.rs @@ -18,26 +18,35 @@ use clap::Parser; use sc_cli::SubstrateCli; +use std::path::PathBuf; /// Sub-commands supported by the collator. #[derive(Debug, Parser)] 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, +} /// Command for exporting the genesis wasm file. #[derive(Debug, Parser)] -pub struct ExportGenesisWasmCommand {} +pub struct ExportGenesisWasmCommand { + /// Output file name or stdout if unspecified. + #[arg()] + pub output: Option, +} #[allow(missing_docs)] #[derive(Debug, Parser)] diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs index dfaa1973206c24b27f87cec591bd249c83754e43..fec90fc41cdb160f1a90a8ceab7a15da81e96bb2 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs @@ -22,6 +22,10 @@ use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProt use polkadot_primitives::Id as ParaId; use sc_cli::{Error as SubstrateCliError, SubstrateCli}; use sp_core::hexdisplay::HexDisplay; +use std::{ + fs, + io::{self, Write}, +}; use test_parachain_adder_collator::Collator; /// The parachain ID to collate for in case it wasn't set explicitly through CLI. @@ -34,15 +38,29 @@ fn main() -> Result<()> { let cli = Cli::from_args(); match cli.subcommand { - Some(cli::Subcommand::ExportGenesisState(_params)) => { + Some(cli::Subcommand::ExportGenesisState(params)) => { let collator = Collator::new(); - println!("0x{:?}", HexDisplay::from(&collator.genesis_head())); + let output_buf = + format!("0x{:?}", HexDisplay::from(&collator.genesis_head())).into_bytes(); + + if let Some(output) = params.output { + std::fs::write(output, output_buf)?; + } else { + std::io::stdout().write_all(&output_buf)?; + } Ok::<_, Error>(()) }, - Some(cli::Subcommand::ExportGenesisWasm(_params)) => { + Some(cli::Subcommand::ExportGenesisWasm(params)) => { let collator = Collator::new(); - println!("0x{:?}", HexDisplay::from(&collator.validation_code())); + let output_buf = + format!("0x{:?}", HexDisplay::from(&collator.validation_code())).into_bytes(); + + if let Some(output) = params.output { + fs::write(output, output_buf)?; + } else { + io::stdout().write_all(&output_buf)?; + } Ok(()) }, @@ -62,17 +80,18 @@ fn main() -> Result<()> { is_parachain_node: polkadot_service::IsParachainNode::Collator( collator.collator_key(), ), - grandpa_pause: None, enable_beefy: false, + force_authoring_backoff: false, jaeger_agent: None, telemetry_worker_handle: None, // Collators don't spawn PVF workers, so we can disable version checks. node_version: None, + secure_validator_mode: false, workers_path: None, workers_names: None, - overseer_gen: polkadot_service::RealOverseerGen, + overseer_gen: polkadot_service::CollatorOverseerGen, overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, diff --git a/polkadot/parachain/test-parachains/halt/Cargo.toml b/polkadot/parachain/test-parachains/halt/Cargo.toml index cb2918273eb03a8db6cb76fa72d7e6507f263ae5..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] @@ -15,5 +18,5 @@ substrate-wasm-builder = { path = "../../../../substrate/utils/wasm-builder" } rustversion = "1.0.6" [features] -default = [ "std" ] +default = ["std"] std = [] diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index 8690da5a4bdd36ffce083032bed0200d2c9960d7..82ceebcf4eee99f36140908580b41c13b04ea87e 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -8,20 +8,29 @@ 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" ] } +parachain = { package = "polkadot-parachain-primitives", path = "../..", default-features = false, features = ["wasm-api"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } sp-std = { path = "../../../../substrate/primitives/std", default-features = false } tiny-keccak = { version = "2.0.2", features = ["keccak"] } -dlmalloc = { version = "0.2.4", features = [ "global" ] } -log = { version = "0.4.17", default-features = false } +dlmalloc = { version = "0.2.4", features = ["global"] } +log = { workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities -sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = [ "disable_allocator" ] } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = ["disable_allocator"] } [build-dependencies] substrate-wasm-builder = { path = "../../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] -std = [ "parachain/std", "sp-io/std", "sp-std/std" ] +default = ["std"] +std = [ + "log/std", + "parachain/std", + "parity-scale-codec/std", + "sp-io/std", + "sp-std/std", +] diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 4569d4e153b19b6858f26acc77089d027e18be13..238b98a66801c7b6ec9ef1bda9248621a5f4332c 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -7,16 +7,19 @@ 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.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-undying = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs index cd16133dbf197019bac15e0bca381759eabce5b6..9572887a51a2a195a01e6ceced60ee711288ead2 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs @@ -18,22 +18,27 @@ use clap::Parser; use sc_cli::SubstrateCli; +use std::path::PathBuf; /// Sub-commands supported by the collator. #[derive(Debug, Parser)] 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, + /// Id of the parachain this collator collates for. #[arg(long, default_value_t = 100)] pub parachain_id: u32, @@ -50,7 +55,11 @@ pub struct ExportGenesisStateCommand { /// Command for exporting the genesis wasm file. #[derive(Debug, Parser)] -pub struct ExportGenesisWasmCommand {} +pub struct ExportGenesisWasmCommand { + /// Output file name or stdout if unspecified. + #[arg()] + pub output: Option, +} #[allow(missing_docs)] #[derive(Debug, Parser)] diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index e564e221f01376ff8b573ec6a1fd1f2937156f77..45f21e7b859631ec8763304c18f01cf6f3d04d10 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -22,6 +22,10 @@ use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProt use polkadot_primitives::Id as ParaId; use sc_cli::{Error as SubstrateCliError, SubstrateCli}; use sp_core::hexdisplay::HexDisplay; +use std::{ + fs, + io::{self, Write}, +}; use test_parachain_undying_collator::Collator; mod cli; @@ -35,14 +39,30 @@ fn main() -> Result<()> { // `pov_size` and `pvf_complexity` need to match the ones that we start the collator // with. let collator = Collator::new(params.pov_size, params.pvf_complexity); - println!("0x{:?}", HexDisplay::from(&collator.genesis_head())); + + let output_buf = + format!("0x{:?}", HexDisplay::from(&collator.genesis_head())).into_bytes(); + + if let Some(output) = params.output { + std::fs::write(output, output_buf)?; + } else { + std::io::stdout().write_all(&output_buf)?; + } Ok::<_, Error>(()) }, - Some(cli::Subcommand::ExportGenesisWasm(_params)) => { + Some(cli::Subcommand::ExportGenesisWasm(params)) => { // We pass some dummy values for `pov_size` and `pvf_complexity` as these don't // matter for `wasm` export. - println!("0x{:?}", HexDisplay::from(&Collator::default().validation_code())); + let collator = Collator::default(); + let output_buf = + format!("0x{:?}", HexDisplay::from(&collator.validation_code())).into_bytes(); + + if let Some(output) = params.output { + fs::write(output, output_buf)?; + } else { + io::stdout().write_all(&output_buf)?; + } Ok(()) }, @@ -62,17 +82,18 @@ fn main() -> Result<()> { is_parachain_node: polkadot_service::IsParachainNode::Collator( collator.collator_key(), ), - grandpa_pause: None, enable_beefy: false, + force_authoring_backoff: false, jaeger_agent: None, telemetry_worker_handle: None, // Collators don't spawn PVF workers, so we can disable version checks. node_version: None, + secure_validator_mode: false, workers_path: None, workers_names: None, - overseer_gen: polkadot_service::RealOverseerGen, + overseer_gen: polkadot_service::CollatorOverseerGen, overseer_message_channel_capacity_override: None, malus_finality_delay: None, hwbench: None, diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index c7c081e2f0ee641efed87c72a29758ffb87b2d2c..e63fb621c7880c03fe0282a5500df69d20528f99 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -1,16 +1,21 @@ [package] name = "polkadot-primitives" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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"] } +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.188", default-features = false, features = ["derive", "alloc"] } +log = { workspace = true, default-features = false } +serde = { features = ["alloc", "derive"], workspace = true } application-crypto = { package = "sp-application-crypto", path = "../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } inherents = { package = "sp-inherents", path = "../../substrate/primitives/inherents", default-features = false } @@ -29,11 +34,12 @@ polkadot-core-primitives = { path = "../core-primitives", default-features = fal polkadot-parachain-primitives = { path = "../parachain", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "application-crypto/std", "bitvec/std", "inherents/std", + "log/std", "parity-scale-codec/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 4ba8b8b031fcc330bb66d1ec4bd329809958db07..2ddd9b58dfe45d6560e9dc5c62f3d047ad8d9850 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -48,17 +48,17 @@ pub use v6::{ HorizontalMessages, HrmpChannelId, Id, InboundDownwardMessage, InboundHrmpMessage, IndexedVec, InherentData, InvalidDisputeStatementKind, Moment, MultiDisputeStatementSet, Nonce, OccupiedCore, OccupiedCoreAssumption, OutboundHrmpMessage, ParathreadClaim, ParathreadEntry, - PersistedValidationData, PvfCheckStatement, PvfExecTimeoutKind, PvfPrepTimeoutKind, - RuntimeMetricLabel, RuntimeMetricLabelValue, RuntimeMetricLabelValues, RuntimeMetricLabels, - RuntimeMetricOp, RuntimeMetricUpdate, ScheduledCore, ScrapedOnChainVotes, SessionIndex, - SessionInfo, Signature, Signed, SignedAvailabilityBitfield, SignedAvailabilityBitfields, - SignedStatement, SigningContext, Slot, UncheckedSigned, UncheckedSignedAvailabilityBitfield, + PersistedValidationData, PvfCheckStatement, PvfExecKind, PvfPrepKind, RuntimeMetricLabel, + RuntimeMetricLabelValue, RuntimeMetricLabelValues, RuntimeMetricLabels, RuntimeMetricOp, + RuntimeMetricUpdate, ScheduledCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signature, + Signed, SignedAvailabilityBitfield, SignedAvailabilityBitfields, SignedStatement, + SigningContext, Slot, UncheckedSigned, UncheckedSignedAvailabilityBitfield, UncheckedSignedAvailabilityBitfields, UncheckedSignedStatement, UpgradeGoAhead, UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, ValidityError, ASSIGNMENT_KEY_TYPE_ID, LEGACY_MIN_BACKING_VOTES, LOWEST_PUBLIC_ID, - MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, - PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, + MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, MIN_CODE_SIZE, + ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, }; #[cfg(feature = "std")] diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 5ec897c8cbb40a7b3fe6a36ead8c72d3479b13e2..d661005e32ffc9ce146e96ad984fb3f60025ca4c 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -114,13 +114,14 @@ //! separated from the stable primitives. use crate::{ - async_backing, slashing, AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateEvent, - CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, - GroupRotationInfo, 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 parity_scale_codec::{Decode, Encode}; + use polkadot_core_primitives as pcp; use polkadot_parachain_primitives::primitives as ppp; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -128,18 +129,18 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; sp_api::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. #[api_version(5)] - pub trait ParachainHost { + pub trait ParachainHost { /// Get the current validators. fn validators() -> Vec; /// Returns the validator groups and rotation info localized based on the hypothetical child /// of a block whose state this is invoked on. Note that `now` in the `GroupRotationInfo` /// should be the successor of the number of the block. - fn validator_groups() -> (Vec>, GroupRotationInfo); + fn validator_groups() -> (Vec>, GroupRotationInfo); /// Yields information on all availability cores as relevant to the child block. /// Cores are either free or occupied. Free cores can have paras assigned to them. - fn availability_cores() -> Vec>; + fn availability_cores() -> Vec>; /// Yields the persisted validation data for the given `ParaId` along with an assumption that /// should be used if the para currently occupies a core. @@ -147,15 +148,15 @@ sp_api::decl_runtime_apis! { /// Returns `None` if either the para is not registered or the assumption is `Freed` /// and the para already occupies a core. fn persisted_validation_data(para_id: ppp::Id, assumption: OccupiedCoreAssumption) - -> Option>; + -> Option>; /// Returns the persisted validation data for the given `ParaId` along with the corresponding /// validation code hash. Instead of accepting assumption about the para, matches the validation /// data hash against an expected one and yields `None` if they're not equal. fn assumed_validation_data( para_id: ppp::Id, - expected_persisted_validation_data_hash: pcp::v2::Hash, - ) -> Option<(PersistedValidationData, ppp::ValidationCodeHash)>; + expected_persisted_validation_data_hash: Hash, + ) -> Option<(PersistedValidationData, ppp::ValidationCodeHash)>; /// Checks if the given validation outputs pass the acceptance criteria. fn check_validation_outputs(para_id: ppp::Id, outputs: CandidateCommitments) -> bool; @@ -169,30 +170,34 @@ sp_api::decl_runtime_apis! { /// /// Returns `None` if either the para is not registered or the assumption is `Freed` /// and the para already occupies a core. - fn validation_code(para_id: ppp::Id, assumption: OccupiedCoreAssumption) - -> Option; + fn validation_code( + para_id: ppp::Id, + assumption: OccupiedCoreAssumption, + ) -> Option; /// Get the receipt of a candidate pending availability. This returns `Some` for any paras /// assigned to occupied cores in `availability_cores` and `None` otherwise. - fn candidate_pending_availability(para_id: ppp::Id) -> Option>; + fn candidate_pending_availability(para_id: ppp::Id) -> Option>; /// Get a vector of events concerning candidates that occurred within a block. - fn candidate_events() -> Vec>; + fn candidate_events() -> Vec>; /// Get all the pending inbound messages in the downward message queue for a para. fn dmq_contents( recipient: ppp::Id, - ) -> Vec>; + ) -> Vec>; /// Get the contents of all channels addressed to the given recipient. Channels that have no /// messages in them are also included. - fn inbound_hrmp_channels_contents(recipient: ppp::Id) -> BTreeMap>>; + fn inbound_hrmp_channels_contents( + recipient: ppp::Id, + ) -> BTreeMap>>; /// Get the validation code from its hash. fn validation_code_by_hash(hash: ppp::ValidationCodeHash) -> Option; /// Scrape dispute relevant from on-chain, backing votes and resolved disputes. - fn on_chain_votes() -> Option>; + fn on_chain_votes() -> Option>; /***** Added in v2 *****/ @@ -253,7 +258,7 @@ sp_api::decl_runtime_apis! { /// Returns the state of parachain backing for a given para. #[api_version(7)] - fn para_backing_state(_: ppp::Id) -> Option>; + fn para_backing_state(_: ppp::Id) -> Option>; /// Returns candidate's acceptance limitations for asynchronous backing for a relay parent. #[api_version(7)] @@ -264,5 +269,17 @@ sp_api::decl_runtime_apis! { /// Returns a list of all disabled validators at the given block. #[api_version(8)] fn disabled_validators() -> Vec; + + /***** Added in v9 *****/ + + /// Get node features. + /// 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/executor_params.rs b/polkadot/primitives/src/v6/executor_params.rs index 8ea0889bd23b043f7a89e2dae756a6033243421d..1e19f3b23fec9b9889d5976380f52ab6b66072b4 100644 --- a/polkadot/primitives/src/v6/executor_params.rs +++ b/polkadot/primitives/src/v6/executor_params.rs @@ -21,7 +21,7 @@ //! by the first element of the vector). Decoding to a usable semantics structure is //! done in `polkadot-node-core-pvf`. -use crate::{BlakeTwo256, HashT as _, PvfExecTimeoutKind, PvfPrepTimeoutKind}; +use crate::{BlakeTwo256, HashT as _, PvfExecKind, PvfPrepKind}; use parity_scale_codec::{Decode, Encode}; use polkadot_core_primitives::Hash; use scale_info::TypeInfo; @@ -45,7 +45,7 @@ pub const PRECHECK_MEM_MAX_LO: u64 = 256 * 1024 * 1024; pub const PRECHECK_MEM_MAX_HI: u64 = 16 * 1024 * 1024 * 1024; // Default PVF timeouts. Must never be changed! Use executor environment parameters to adjust them. -// See also `PvfPrepTimeoutKind` and `PvfExecTimeoutKind` docs. +// See also `PvfPrepKind` and `PvfExecKind` docs. /// Default PVF preparation timeout for prechecking requests. pub const DEFAULT_PRECHECK_PREPARATION_TIMEOUT: Duration = Duration::from_secs(60); @@ -99,12 +99,12 @@ pub enum ExecutorParam { /// Always ensure that `precheck_timeout` < `lenient_timeout`. /// When absent, the default values will be used. #[codec(index = 5)] - PvfPrepTimeout(PvfPrepTimeoutKind, u64), + PvfPrepTimeout(PvfPrepKind, u64), /// PVF execution timeouts, in millisecond. /// Always ensure that `backing_timeout` < `approval_timeout`. /// When absent, the default values will be used. #[codec(index = 6)] - PvfExecTimeout(PvfExecTimeoutKind, u64), + PvfExecTimeout(PvfExecKind, u64), /// Enables WASM bulk memory proposal #[codec(index = 7)] WasmExtBulkMemory, @@ -159,7 +159,9 @@ impl sp_std::fmt::LowerHex for ExecutorParamsHash { // into individual fields of the structure. Thus, complex migrations shall be avoided when adding // new entries and removing old ones. At the moment, there's no mandatory parameters defined. If // they show up, they must be clearly documented as mandatory ones. -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize)] +#[derive( + Clone, Debug, Default, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize, +)] pub struct ExecutorParams(Vec); impl ExecutorParams { @@ -174,7 +176,7 @@ impl ExecutorParams { } /// Returns a PVF preparation timeout, if any - pub fn pvf_prep_timeout(&self, kind: PvfPrepTimeoutKind) -> Option { + pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option { for param in &self.0 { if let ExecutorParam::PvfPrepTimeout(k, timeout) = param { if kind == *k { @@ -186,7 +188,7 @@ impl ExecutorParams { } /// Returns a PVF execution timeout, if any - pub fn pvf_exec_timeout(&self, kind: PvfExecTimeoutKind) -> Option { + pub fn pvf_exec_timeout(&self, kind: PvfExecKind) -> Option { for param in &self.0 { if let ExecutorParam::PvfExecTimeout(k, timeout) = param { if kind == *k { @@ -197,6 +199,16 @@ impl ExecutorParams { None } + /// Returns pre-checking memory limit, if any + pub fn prechecking_max_memory(&self) -> Option { + for param in &self.0 { + if let ExecutorParam::PrecheckingMaxMemory(limit) = param { + return Some(*limit) + } + } + None + } + /// Check params coherence. pub fn check_consistency(&self) -> Result<(), ExecutorParamError> { use ExecutorParam::*; @@ -232,12 +244,12 @@ impl ExecutorParams { StackNativeMax(_) => "StackNativeMax", PrecheckingMaxMemory(_) => "PrecheckingMaxMemory", PvfPrepTimeout(kind, _) => match kind { - PvfPrepTimeoutKind::Precheck => "PvfPrepTimeoutKind::Precheck", - PvfPrepTimeoutKind::Lenient => "PvfPrepTimeoutKind::Lenient", + PvfPrepKind::Precheck => "PvfPrepKind::Precheck", + PvfPrepKind::Prepare => "PvfPrepKind::Prepare", }, PvfExecTimeout(kind, _) => match kind { - PvfExecTimeoutKind::Backing => "PvfExecTimeoutKind::Backing", - PvfExecTimeoutKind::Approval => "PvfExecTimeoutKind::Approval", + PvfExecKind::Backing => "PvfExecKind::Backing", + PvfExecKind::Approval => "PvfExecKind::Approval", }, WasmExtBulkMemory => "WasmExtBulkMemory", }; @@ -287,30 +299,23 @@ impl ExecutorParams { } if let (Some(precheck), Some(lenient)) = ( - seen.get("PvfPrepTimeoutKind::Precheck") + seen.get("PvfPrepKind::Precheck") .or(Some(&DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS)), - seen.get("PvfPrepTimeoutKind::Lenient") + seen.get("PvfPrepKind::Prepare") .or(Some(&DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS)), ) { if *precheck >= *lenient { - return Err(IncompatibleValues( - "PvfPrepTimeoutKind::Precheck", - "PvfPrepTimeoutKind::Lenient", - )) + return Err(IncompatibleValues("PvfPrepKind::Precheck", "PvfPrepKind::Prepare")) } } if let (Some(backing), Some(approval)) = ( - seen.get("PvfExecTimeoutKind::Backing") - .or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)), - seen.get("PvfExecTimeoutKind::Approval") + seen.get("PvfExecKind::Backing").or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)), + seen.get("PvfExecKind::Approval") .or(Some(&DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS)), ) { if *backing >= *approval { - return Err(IncompatibleValues( - "PvfExecTimeoutKind::Backing", - "PvfExecTimeoutKind::Approval", - )) + return Err(IncompatibleValues("PvfExecKind::Backing", "PvfExecKind::Approval")) } } @@ -331,9 +336,3 @@ impl From<&[ExecutorParam]> for ExecutorParams { ExecutorParams(arr.to_vec()) } } - -impl Default for ExecutorParams { - fn default() -> Self { - ExecutorParams(vec![]) - } -} diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index 9371b3db406b3e6247f4ac9c12dc5ba053a77f01..cab34deeb503b5a5606fef1934c0ef4238b03e31 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -16,7 +16,7 @@ //! `V6` Primitives. -use bitvec::vec::BitVec; +use bitvec::{field::BitField, slice::BitSlice, vec::BitVec}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_std::{ @@ -72,6 +72,7 @@ pub use metrics::{ /// The key type ID for a collator key. pub const COLLATOR_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"coll"); +const LOG_TARGET: &str = "runtime::primitives"; mod collator_app { use application_crypto::{app_crypto, sr25519}; @@ -362,6 +363,9 @@ pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0"; /// The key type ID for parachain assignment key. pub const ASSIGNMENT_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"asgn"); +/// Compressed or not the wasm blob can never be less than 9 bytes. +pub const MIN_CODE_SIZE: u32 = 9; + /// Maximum compressed code size we support right now. /// At the moment we have runtime upgrade on chain, which restricts scalability severely. If we want /// to have bigger values, we should fix that first. @@ -529,18 +533,6 @@ impl CandidateReceipt { } } -/// All data pertaining to the execution of a para candidate. -#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)] -pub struct FullCandidateReceipt { - /// The inner candidate receipt. - pub inner: CandidateReceipt, - /// The validation data derived from the relay-chain state at that - /// point. The hash of the persisted validation data should - /// match the `persisted_validation_data_hash` in the descriptor - /// of the receipt. - pub validation_data: PersistedValidationData, -} - /// A candidate-receipt with commitments directly included. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Hash))] @@ -703,19 +695,50 @@ pub type UncheckedSignedAvailabilityBitfields = Vec { /// The candidate referred to. - pub candidate: CommittedCandidateReceipt, + candidate: CommittedCandidateReceipt, /// The validity votes themselves, expressed as signatures. - pub validity_votes: Vec, - /// The indices of the validators within the group, expressed as a bitfield. - pub validator_indices: BitVec, + validity_votes: Vec, + /// The indices of the validators within the group, expressed as a bitfield. May be extended + /// beyond the backing group size to contain the assigned core index, if ElasticScalingMVP is + /// enabled. + validator_indices: BitVec, } impl BackedCandidate { - /// Get a reference to the descriptor of the para. + /// Constructor + pub fn new( + candidate: CommittedCandidateReceipt, + validity_votes: Vec, + validator_indices: BitVec, + core_index: Option, + ) -> Self { + let mut instance = Self { candidate, validity_votes, validator_indices }; + if let Some(core_index) = core_index { + instance.inject_core_index(core_index); + } + instance + } + + /// Get a reference to the descriptor of the candidate. pub fn descriptor(&self) -> &CandidateDescriptor { &self.candidate.descriptor } + /// Get a reference to the committed candidate receipt of the candidate. + pub fn candidate(&self) -> &CommittedCandidateReceipt { + &self.candidate + } + + /// Get a reference to the validity votes of the candidate. + pub fn validity_votes(&self) -> &[ValidityAttestation] { + &self.validity_votes + } + + /// Get a mutable reference to validity votes of the para. + pub fn validity_votes_mut(&mut self) -> &mut Vec { + &mut self.validity_votes + } + /// Compute this candidate's hash. pub fn hash(&self) -> CandidateHash where @@ -731,6 +754,48 @@ impl BackedCandidate { { self.candidate.to_plain() } + + /// Get a copy of the validator indices and the assumed core index, if any. + pub fn validator_indices_and_core_index( + &self, + core_index_enabled: bool, + ) -> (&BitSlice, Option) { + // This flag tells us if the block producers must enable Elastic Scaling MVP hack. + // It extends `BackedCandidate::validity_indices` to store a 8 bit core index. + if core_index_enabled { + let core_idx_offset = self.validator_indices.len().saturating_sub(8); + if core_idx_offset > 0 { + let (validator_indices_slice, core_idx_slice) = + self.validator_indices.split_at(core_idx_offset); + return ( + validator_indices_slice, + Some(CoreIndex(core_idx_slice.load::() as u32)), + ); + } + } + + (&self.validator_indices, None) + } + + /// Inject a core index in the validator_indices bitvec. + fn inject_core_index(&mut self, core_index: CoreIndex) { + let core_index_to_inject: BitVec = + BitVec::from_vec(vec![core_index.0 as u8]); + self.validator_indices.extend(core_index_to_inject); + } + + /// Update the validator indices and core index in the candidate. + pub fn set_validator_indices_and_core_index( + &mut self, + new_indices: BitVec, + maybe_core_index: Option, + ) { + self.validator_indices = new_indices; + + if let Some(core_index) = maybe_core_index { + self.inject_core_index(core_index); + } + } } /// Verify the backing of the given candidate. @@ -743,43 +808,65 @@ impl BackedCandidate { /// /// Returns either an error, indicating that one of the signatures was invalid or that the index /// was out-of-bounds, or the number of signatures checked. -pub fn check_candidate_backing + Clone + Encode>( - backed: &BackedCandidate, +pub fn check_candidate_backing + Clone + Encode + core::fmt::Debug>( + candidate_hash: CandidateHash, + validity_votes: &[ValidityAttestation], + validator_indices: &BitSlice, signing_context: &SigningContext, group_len: usize, validator_lookup: impl Fn(usize) -> Option, ) -> Result { - if backed.validator_indices.len() != group_len { + if validator_indices.len() != group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: indices mismatch: group_len = {} , indices_len = {}", + group_len, + validator_indices.len(), + ); return Err(()) } - if backed.validity_votes.len() > group_len { + if validity_votes.len() > group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Too many votes, expected: {}, found: {}", + group_len, + validity_votes.len(), + ); return Err(()) } - // this is known, even in runtime, to be blake2-256. - let hash = backed.candidate.hash(); - let mut signed = 0; - for ((val_in_group_idx, _), attestation) in backed - .validator_indices + for ((val_in_group_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed.validity_votes.iter()) + .zip(validity_votes.iter()) { let validator_id = validator_lookup(val_in_group_idx).ok_or(())?; - let payload = attestation.signed_payload(hash, signing_context); + let payload = attestation.signed_payload(candidate_hash, signing_context); let sig = attestation.signature(); if sig.verify(&payload[..], &validator_id) { signed += 1; } else { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Invalid signature. validator_id = {:?}, validator_index = {} ", + validator_id, + val_in_group_idx, + ); return Err(()) } } - if signed != backed.validity_votes.len() { + if signed != validity_votes.len() { + log::error!( + target: LOG_TARGET, + "Check candidate backing: Too many signatures, expected = {}, found = {}", + validity_votes.len(), + signed, + ); return Err(()) } @@ -1070,6 +1157,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 +1353,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 +1400,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(()) @@ -1303,18 +1427,15 @@ impl DisputeStatement { /// Statement is backing statement. pub fn is_backing(&self) -> bool { - match *self { - Self::Valid(ValidDisputeStatementKind::BackingSeconded(_)) | - Self::Valid(ValidDisputeStatementKind::BackingValid(_)) => true, - Self::Valid(ValidDisputeStatementKind::Explicit) | - Self::Valid(ValidDisputeStatementKind::ApprovalChecking) | + match self { + Self::Valid(s) => s.is_backing(), 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 +1449,25 @@ 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), +} + +impl ValidDisputeStatementKind { + /// Whether the statement is from the backing phase. + pub fn is_backing(&self) -> bool { + match self { + ValidDisputeStatementKind::BackingSeconded(_) | + ValidDisputeStatementKind::BackingValid(_) => true, + ValidDisputeStatementKind::Explicit | + ValidDisputeStatementKind::ApprovalChecking | + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_) => false, + } + } } /// Different kinds of statements of invalidity on a candidate. @@ -1781,36 +1921,56 @@ impl WellKnownKey { } } -/// Type discriminator for PVF preparation timeouts +/// Type discriminator for PVF preparation. #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum PvfPrepTimeoutKind { - /// For prechecking requests, the time period after which the preparation worker is considered - /// unresponsive and will be killed. +pub enum PvfPrepKind { + /// For prechecking requests. Precheck, - /// For execution and heads-up requests, the time period after which the preparation worker is - /// considered unresponsive and will be killed. More lenient than the timeout for prechecking - /// to prevent honest validators from timing out on valid PVFs. - Lenient, + /// For execution and heads-up requests. + Prepare, } -/// Type discriminator for PVF execution timeouts +/// Type discriminator for PVF execution. #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum PvfExecTimeoutKind { - /// The amount of time to spend on execution during backing. +pub enum PvfExecKind { + /// For backing requests. Backing, - - /// The amount of time to spend on execution during approval or disputes. - /// - /// This should be much longer than the backing execution timeout to ensure that in the - /// absence of extremely large disparities between hardware, blocks that pass backing are - /// considered executable by approval checkers or dispute participants. + /// For approval and dispute request. Approval, } #[cfg(test)] mod tests { use super::*; + use bitvec::bitvec; + use primitives::sr25519; + + pub fn dummy_committed_candidate_receipt() -> CommittedCandidateReceipt { + let zeros = Hash::zero(); + + CommittedCandidateReceipt { + descriptor: CandidateDescriptor { + para_id: 0.into(), + relay_parent: zeros, + collator: CollatorId::from(sr25519::Public::from_raw([0; 32])), + persisted_validation_data_hash: zeros, + pov_hash: zeros, + erasure_root: zeros, + signature: CollatorSignature::from(sr25519::Signature([0u8; 64])), + para_head: zeros, + validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(), + }, + commitments: CandidateCommitments { + head_data: HeadData(vec![]), + upward_messages: vec![].try_into().expect("empty vec fits within bounds"), + new_validation_code: None, + horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"), + processed_downward_messages: 0, + hrmp_watermark: 0_u32, + }, + } + } #[test] fn group_rotation_info_calculations() { @@ -1885,4 +2045,73 @@ mod tests { assert!(zero_b.leading_zeros() >= zero_u.leading_zeros()); } + + #[test] + fn test_backed_candidate_injected_core_index() { + let initial_validator_indices = bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]; + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + initial_validator_indices.clone(), + None, + ); + + // No core index supplied, ElasticScalingMVP is off. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + // No core index supplied, ElasticScalingMVP is on. Still, decoding will be ok if backing + // group size is <= 8, to give a chance to parachains that don't have multiple cores + // assigned. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + + // No core index supplied, ElasticScalingMVP is on. Decoding is corrupted if backing group + // size larger than 8. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0], + None, + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0].as_bitslice()); + assert!(core_index.is_some()); + + // Core index supplied, ElasticScalingMVP is off. Core index will be treated as normal + // validator indices. Runtime will check against this. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!( + validator_indices, + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0] + ); + assert!(core_index.is_none()); + + // Core index supplied, ElasticScalingMVP is on. + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]); + assert_eq!(core_index, Some(CoreIndex(10))); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + } } diff --git a/polkadot/primitives/src/v6/slashing.rs b/polkadot/primitives/src/v6/slashing.rs index efffb932cdc0d997e08f9d195e2c42b857a7c8f5..bcd7d0c2fc4455e2ebf01fab027dbf0618f63dba 100644 --- a/polkadot/primitives/src/v6/slashing.rs +++ b/polkadot/primitives/src/v6/slashing.rs @@ -54,7 +54,7 @@ impl DisputesTimeSlot { /// is required to identify and verify it. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)] pub struct DisputeProof { - /// Time slot when the dispute occured. + /// Time slot when the dispute occurred. pub time_slot: DisputesTimeSlot, /// The dispute outcome. pub kind: SlashingOffenceKind, diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 1429b0c326aceef4b9088bd4ddef6828f8dcfbd8..bd2a5106c44de5895fb327957161c84f13a983b0 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -17,3 +17,136 @@ //! 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; +use sp_arithmetic::Perbill; + +/// 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 } + } +} + +/// Scheduler configuration parameters. All coretime/ondemand parameters are here. +#[derive( + RuntimeDebug, + Copy, + Clone, + PartialEq, + Encode, + Decode, + TypeInfo, + serde::Serialize, + serde::Deserialize, +)] +pub struct SchedulerParams { + /// How often parachain groups should be rotated across parachains. + /// + /// Must be non-zero. + pub group_rotation_frequency: BlockNumber, + /// Availability timeout for a block on a core, measured in blocks. + /// + /// This is the maximum amount of blocks after a core became occupied that validators have time + /// to make the block available. + /// + /// This value only has effect on group rotations. If backers backed something at the end of + /// their rotation, the occupied core affects the backing group that comes afterwards. We limit + /// the effect one backing group can have on the next to `paras_availability_period` blocks. + /// + /// Within a group rotation there is no timeout as backers are only affecting themselves. + /// + /// Must be at least 1. With a value of 1, the previous group will not be able to negatively + /// affect the following group at the expense of a tight availability timeline at group + /// rotation boundaries. + pub paras_availability_period: BlockNumber, + /// The maximum number of validators to have per core. + /// + /// `None` means no maximum. + pub max_validators_per_core: Option, + /// The amount of blocks ahead to schedule paras. + pub lookahead: u32, + /// How many cores are managed by the coretime chain. + pub num_cores: u32, + /// The max number of times a claim can time out in availability. + pub max_availability_timeouts: u32, + /// The maximum queue size of the pay as you go module. + pub on_demand_queue_max_size: u32, + /// The target utilization of the spot price queue in percentages. + pub on_demand_target_queue_utilization: Perbill, + /// How quickly the fee rises in reaction to increased utilization. + /// The lower the number the slower the increase. + pub on_demand_fee_variability: Perbill, + /// The minimum amount needed to claim a slot in the spot pricing queue. + pub on_demand_base_fee: Balance, + /// The number of blocks a claim stays in the scheduler's claimqueue before getting cleared. + /// This number should go reasonably higher than the number of blocks in the async backing + /// lookahead. + pub ttl: BlockNumber, +} + +impl> Default for SchedulerParams { + fn default() -> Self { + Self { + group_rotation_frequency: 1u32.into(), + paras_availability_period: 1u32.into(), + max_validators_per_core: Default::default(), + lookahead: 1, + num_cores: Default::default(), + max_availability_timeouts: Default::default(), + on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + on_demand_target_queue_utilization: Perbill::from_percent(25), + on_demand_fee_variability: Perbill::from_percent(3), + on_demand_base_fee: 10_000_000u128, + ttl: 5u32.into(), + } + } +} + +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, + /// This feature enables the extension of `BackedCandidate::validator_indices` by 8 bits. + /// The value stored there represents the assumed core index where the candidates + /// are backed. This is needed for the elastic scaling MVP. + ElasticScalingMVP = 1, + /// First unassigned feature bit. + /// Every time a new feature flag is assigned it should take this value. + /// and this should be incremented. + FirstUnassigned = 2, + } +} 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/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs index d532d6ff57f4a34c53d7099d011f1ca68bf6cf5f..c0118f5960a47eabe205c4d2cf6bcf6d295ce347 100644 --- a/polkadot/primitives/test-helpers/src/lib.rs +++ b/polkadot/primitives/test-helpers/src/lib.rs @@ -126,7 +126,7 @@ pub fn dummy_candidate_descriptor>(relay_parent: H) -> CandidateD /// Create meaningless validation code. pub fn dummy_validation_code() -> ValidationCode { - ValidationCode(vec![1, 2, 3]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } /// Create meaningless head data. diff --git a/polkadot/roadmap/implementers-guide/README.md b/polkadot/roadmap/implementers-guide/README.md index e03c0c45ddba009821861cc5ba39a77c7c03dd59..abff017138c06120a9a1e7a85a393eb156ad2181 100644 --- a/polkadot/roadmap/implementers-guide/README.md +++ b/polkadot/roadmap/implementers-guide/README.md @@ -8,7 +8,7 @@ This is available [here](https://paritytech.github.io/polkadot-sdk/book/). ## Local build -To view it locally from the repo root: +To view it locally, run the following (from the `polkadot/` directory): Ensure graphviz is installed: diff --git a/polkadot/roadmap/implementers-guide/book.toml b/polkadot/roadmap/implementers-guide/book.toml index 1e6680f6f4b7e3b356af787fe0412fbab177e902..f91591ff1708c6b7e1eb54721a3c50345bc4d1f5 100644 --- a/polkadot/roadmap/implementers-guide/book.toml +++ b/polkadot/roadmap/implementers-guide/book.toml @@ -15,8 +15,8 @@ renderer = ["html"] [output.html] additional-css = ["last-changed.css"] -additional-js = ["mermaid.min.js", "mermaid-init.js"] +additional-js = ["mermaid-init.js", "mermaid.min.js"] # Repository URL used in the last-changed link. -git-repository-url = "https://github.com/paritytech/polkadot" +git-repository-url = "https://github.com/paritytech/polkadot-sdk" [output.linkcheck] diff --git a/polkadot/roadmap/implementers-guide/src/disputes-flow.md b/polkadot/roadmap/implementers-guide/src/disputes-flow.md index f9fd8dcce351e2b7a4461c05c35a647a69cfb4fe..b5cc5611c6ff897826e128d240c38761991e0a5c 100644 --- a/polkadot/roadmap/implementers-guide/src/disputes-flow.md +++ b/polkadot/roadmap/implementers-guide/src/disputes-flow.md @@ -52,7 +52,7 @@ stateDiagram-v2 IncomingRequestDisputeAvailabilityData --> RespondUnavailable IncomingRequestDisputeAvailabilityData --> DisputeDataAvail DisputeDataAvail --> RespondWithDisputeAvailabilityData: Send - VoteGossipReceived --> Track: implies source peer has
dispute availablity data + VoteGossipReceived --> Track: implies source peer has
dispute availability data ``` --- diff --git a/polkadot/roadmap/implementers-guide/src/glossary.md b/polkadot/roadmap/implementers-guide/src/glossary.md index b2365ba51c5ce80fd0d60b53c592b1037f540a52..ac2392b14d2ae1a75a6690567f32407ebc3a1637 100644 --- a/polkadot/roadmap/implementers-guide/src/glossary.md +++ b/polkadot/roadmap/implementers-guide/src/glossary.md @@ -48,10 +48,13 @@ has exactly one downward message queue. - **Proof-of-Validity (PoV):** A stateless-client proof that a parachain candidate is valid, with respect to some validation function. - **PVF:** Parachain Validation Function. The validation code that is run by validators on parachains. -- **PVF Prechecking:** This is the process of initially checking the PVF when it is first added. We attempt preparation - of the PVF and make sure it succeeds within a given timeout, plus some additional checks. +- **PVF Prechecking:** This is the process of checking a PVF when it appears + on-chain, either when the parachain is onboarded or when it signalled an + upgrade of its validation code. We attempt preparation of the PVF and make + sure it that succeeds within a given timeout, plus some additional checks. - **PVF Preparation:** This is the process of preparing the WASM blob and includes both prevalidation and compilation. - As there is no prevalidation right now, preparation just consists of compilation. +- **PVF Prevalidation:** Some basic checks for correctness of the PVF blob. The + first step of PVF preparation, before compilation. - **Relay Parent:** A block in the relay chain, referred to in a context where work is being done in the context of the state at this block. - **Runtime:** The relay-chain state machine. 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 6da2f5d9b883a08877928bf6e7c2507ec1f38604..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 @@ -75,22 +78,27 @@ struct TrancheEntry { assignments: Vec<(ValidatorIndex, Tick)>, } -struct OurAssignment { - cert: AssignmentCert, - tranche: DelayTranche, - validator_index: ValidatorIndex, - triggered: bool, +pub struct OurAssignment { + /// Our assignment certificate. + cert: AssignmentCertV2, + /// The tranche for which the assignment refers to. + tranche: DelayTranche, + /// Our validator index for the session in which the candidates were included. + validator_index: ValidatorIndex, + /// Whether the assignment has been triggered already. + triggered: bool, } -struct ApprovalEntry { - tranches: Vec, // sorted ascending by tranche number. - backing_group: GroupIndex, - our_assignment: Option, - our_approval_sig: Option, - assignments: Bitfield, // n_validators bits - approved: bool, +pub struct ApprovalEntry { + tranches: Vec, // sorted ascending by tranche number. + backing_group: GroupIndex, + our_assignment: Option, + our_approval_sig: Option, + assigned_validators: Bitfield, // `n_validators` bits. + approved: bool, } + struct CandidateEntry { candidate: CandidateReceipt, session: SessionIndex, @@ -115,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 @@ -264,19 +279,25 @@ entry. The cert itself contains information necessary to determine the candidate * Check the assignment cert * If the cert kind is `RelayVRFModulo`, then the certificate is valid as long as `sample < session_info.relay_vrf_samples` and the VRF is valid for the validator's key with the input - `block_entry.relay_vrf_story ++ sample.encode()` as described with [the approvals protocol - section](../../protocol-approval.md#assignment-criteria). We set `core_index = vrf.make_bytes().to_u32() % - session_info.n_cores`. If the `BlockEntry` causes inclusion of a candidate at `core_index`, then this is a valid - assignment for the candidate at `core_index` and has delay tranche 0. Otherwise, it can be ignored. - * If the cert kind is `RelayVRFDelay`, then we check if the VRF is valid for the validator's key with the input - `block_entry.relay_vrf_story ++ cert.core_index.encode()` as described in [the approvals protocol - section](../../protocol-approval.md#assignment-criteria). The cert can be ignored if the block did not cause - inclusion of a candidate on that core index. Otherwise, this is a valid assignment for the included candidate. The - delay tranche for the assignment is determined by reducing `(vrf.make_bytes().to_u64() % - (session_info.n_delay_tranches + - session_info.zeroth_delay_tranche_width)).saturating_sub(session_info.zeroth_delay_tranche_width)`. - * We also check that the core index derived by the output is covered by the `VRFProof` by means of an auxiliary - signature. + `block_entry.relay_vrf_story ++ sample.encode()` as described with + [the approvals protocol section](../../protocol-approval.md#assignment-criteria). We set + `core_index = vrf.make_bytes().to_u32() % session_info.n_cores`. If the `BlockEntry` causes + inclusion of a candidate at `core_index`, then this is a valid assignment for the candidate + at `core_index` and has delay tranche 0. Otherwise, it can be ignored. + * If the cert kind is `RelayVRFModuloCompact`, then the certificate is valid as long as the VRF + is valid for the validator's key with the input `block_entry.relay_vrf_story ++ relay_vrf_samples.encode()` + as described with [the approvals protocol section](../../protocol-approval.md#assignment-criteria). + We enforce that all `core_bitfield` indices are included in the set of the core indices sampled from the + VRF Output. The assignment is considered a valid tranche0 assignment for all claimed candidates if all + `core_bitfield` indices match the core indices where the claimed candidates were included at. + + * If the cert kind is `RelayVRFDelay`, then we check if the VRF is valid for the validator's key with the + input `block_entry.relay_vrf_story ++ cert.core_index.encode()` as described in [the approvals protocol + section](../../protocol-approval.md#assignment-criteria). The cert can be ignored if the block did not + cause inclusion of a candidate on that core index. Otherwise, this is a valid assignment for the included + candidate. The delay tranche for the assignment is determined by reducing + `(vrf.make_bytes().to_u64() % (session_info.n_delay_tranches + session_info.zeroth_delay_tranche_width)).saturating_sub(session_info.zeroth_delay_tranche_width)`. + * We also check that the core index derived by the output is covered by the `VRFProof` by means of an auxiliary signature. * If the delay tranche is too far in the future, return `AssignmentCheckResult::TooFarInFuture`. * Import the assignment. * Load the candidate in question and access the `approval_entry` for the block hash the cert references. @@ -292,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` @@ -391,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/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 286aeddb986d3db3c7077d8d6227ebcf979073cb..8f00ff084941cc260dea6a9e76c0ff30d3770caf 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -92,9 +92,10 @@ prospective validation data. This is unlikely to change. been backed. - Sent by the Backing Subsystem after it successfully imports a statement giving a candidate the necessary quorum of backing votes. -- `ProspectiveParachainsMessage::GetBackableCandidate` - - Get a backable candidate hash along with its relay parent for a given parachain, - under a given relay-parent (leaf) hash, which is a descendant of given candidate hashes. +- `ProspectiveParachainsMessage::GetBackableCandidates` + - Get the requested number of backable candidate hashes along with their relay parent for a given + parachain,under a given relay-parent (leaf) hash, which are descendants of given candidate + hashes. - Sent by the Provisioner when requesting backable candidates, when selecting candidates for a given relay-parent. - `ProspectiveParachainsMessage::GetHypotheticalFrontier` diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution-legacy.md b/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution-legacy.md index e10a55010b91ca1dab6d3cf09aed409530c2bba5..710055144665cc25b1aed270fc91a7d32b3458f1 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution-legacy.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution-legacy.md @@ -149,7 +149,7 @@ The receiver of such a message needs to request the actual payload via request/r This is necessary as distribution of a large payload (mega bytes) via gossip would make the network collapse and timely distribution of statements would no longer be possible. By using request/response it is ensured that each peer only -transferes large data once. We only take good care to detect an overloaded peer early and immediately move on to a +transfers large data once. We only take good care to detect an overloaded peer early and immediately move on to a different peer for fetching the data. This mechanism should result in a good load distribution and therefore a rather optimal distribution path. diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution.md b/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution.md index 24f2785f8294d32ff3a890f2781d13be3a232d4e..e6e597c531787f46ced0a6f9e38e05817f2323d7 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/statement-distribution.md @@ -3,7 +3,7 @@ This subsystem is responsible for distributing signed statements that we have generated and forwarding statements generated by our peers. Received candidate receipts and statements are passed to the [Candidate Backing subsystem](candidate-backing.md) to handle producing local statements. On receiving -`StatementDistributionMessage::Share`, this subsystem distributes the message across the network with redundency to +`StatementDistributionMessage::Share`, this subsystem distributes the message across the network with redundancy to ensure a fast backing process. ## Overview @@ -123,6 +123,31 @@ only send "importable" statements to the backing subsystem itself. backable and part of the hypothetical frontier. - Note that requesting is not an implicit acknowledgement, and an explicit acknowledgement must be sent upon receipt. +### Disabled validators + +After a validator is disabled in the runtime, other validators should no longer +accept statements from it. Filtering out of statements from disabled validators +on the node side is purely an optimization, as it will be done in the runtime +as well. + +Because we use the state of the active leaves to +check whether a validator is disabled instead of the relay parent, the notion +of being disabled is inherently racy: +- the responder has learned about the disabled validator before the requester +- the receiver has witnessed the disabled validator after sending the request + +We could have sent a manifest to a peer, then received information about +disabling, and then receive a request. This can break an invariant of the grid +mode: +- the response is required to indicate quorum + +Due to the above, there should be no response at all for grid requests when +the backing threshold is no longer met as a result of disabled validators. +In addition to that, we add disabled validators to the request's unwanted +mask. This ensures that the sender will not send statements from disabled +validators (at least from the perspective of the receiver at the moment of the +request). This doesn't fully avoid race conditions, but tries to minimize them. + ## Messages ### Incoming diff --git a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md index a9cb2741b0838fb9859435d3e780f16c1025100f..e0738e219d1b6f02e20d5bd360b9ed9f72b3cbd7 100644 --- a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md +++ b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md @@ -13,6 +13,7 @@ In particular the dispute-coordinator is responsible for: - Ensuring backing votes will never get overridden by explicit votes. - Coordinating actual participation in a dispute, ensuring that the node participates in any justified dispute in a way that ensures resolution of disputes on the network even in the case of many disputes raised (flood/DoS scenario). +- Ensuring disabled validators are not able to spam disputes. - Ensuring disputes resolve, even for candidates on abandoned forks as much as reasonably possible, to rule out "free tries" and thus guarantee our gambler's ruin property. - Providing an API for chain selection, so we can prevent finalization of any chain which has included candidates for @@ -243,6 +244,9 @@ if any of the following holds true: - The dispute is already confirmed: Meaning that 1/3+1 nodes already participated, as this suggests in our threat model that there was at least one honest node that already voted, so the dispute must be genuine. +In addition to that, we only participate in a non-confirmed dispute if at least one vote against the candidate is from +a non-disabled validator. + Note: A node might be out of sync with the chain and we might only learn about a block, including a candidate, after we learned about the dispute. This means, we have to re-evaluate participation decisions on block import! @@ -301,6 +305,7 @@ conditions are satisfied: - the candidate under dispute was not seen included nor backed on any chain - the dispute is not confirmed - we haven't cast a vote for the dispute +- at least one vote against the candidate is from a non-disabled validator Whenever any vote on a dispute is imported these conditions are checked. If the dispute is found not to be potential spam, then spam slots for the disputed candidate hash are cleared. This decrements the spam count for every validator @@ -318,6 +323,23 @@ approval-voting), but we also don't import them until a dispute already conclude opposing votes, so there must be an explicit `invalid` vote in the import. Only a third of the validators can be malicious, so spam disk usage is limited to `2*vote_size*n/3*NUM_SPAM_SLOTS`, with `n` being the number of validators. +### Disabling + +Once a validator has committed an offence (e.g. losing a dispute), it is considered disabled for the rest of the era. +In addition to using the on-chain state of disabled validators, we also keep track of validators who lost a dispute +off-chain. The reason for this is a dispute can be raised for a candidate in a previous era, which means that a +validator that is going to be slashed for it might not even be in the current active set. That means it can't be +disabled on-chain. We need a way to prevent someone from disputing all valid candidates in the previous era. We do this +by keeping track of the validators who lost a dispute in the past few sessions and use that list in addition to the +on-chain disabled validators state. In addition to past session misbehavior, this also heps in case a slash is delayed. + +When we receive a dispute statements set, we do the following: +1. Take the on-chain state of disabled validators at the relay parent block. +1. Take a list of those who lost a dispute in that session in the order that prioritizes the biggest and newest offence. +1. Combine the two lists and take the first byzantine threshold validators from it. +1. If the dispute is unconfimed, check if all votes against the candidate are from disabled validators. +If so, we don't participate in the dispute, but record the votes. + ### Backing Votes Backing votes are in some way special. For starters they are the only valid votes that are guaranteed to exist for any diff --git a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-distribution.md b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-distribution.md index 4547e02352ec1c44eecd81c6e03d1b92bb1e26f1..e916d246060e68731fdbf909bf46c0946ca94723 100644 --- a/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-distribution.md +++ b/polkadot/roadmap/implementers-guide/src/node/disputes/dispute-distribution.md @@ -297,7 +297,7 @@ is `500ms` and above `RATE_LIMIT` is `100ms`. 1/3 of validators are malicious, so for 1000 this means around 330 malicious actors worst case. All those actors can send a message every `100ms`, that is 10 per second. This -means at the begining of an attack they can open up around 3300 batches. Each +means at the beginning of an attack they can open up around 3300 batches. Each containing two votes. So memory usage is still negligible. In reality it is even less, as we also demand 10 new votes to trickle in per batch in order to keep it alive, every `500ms`. Hence for the first second, each batch requires 20 votes diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md index 376fa187de1bf4b18849ba8c8b134352fbe21630..1a3ff1c6aff061ad845dcaaba4258129eb1673bb 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md @@ -5,6 +5,31 @@ This subsystem is responsible for handling candidate validation requests. It is A variety of subsystems want to know if a parachain block candidate is valid. None of them care about the detailed mechanics of how a candidate gets validated, just the results. This subsystem handles those details. +## High-Level Flow + +```dot process +digraph { + rankdir="LR"; + + pre [label = "Pvf-Checker"; shape = square] + bac [label = "Backing"; shape = square] + app [label = "Approval\nVoting"; shape = square] + dis [label = "Dispute\nCoordinator"; shape = square] + + can [label = "Candidate\nValidation"; shape = square] + + pvf [label = "PVF Host"; shape = square] + + pre -> can [style = dashed] + bac -> can + app -> can + dis -> can + + can -> pvf [label = "Precheck"; style = dashed] + can -> pvf [label = "Validate"] +} +``` + ## Protocol Input: [`CandidateValidationMessage`](../../types/overseer-protocol.md#validation-request-type) @@ -31,7 +56,7 @@ all the necessary parameters to the validation function. These are: * The [`ValidationData`](../../types/candidate.md#validationdata). * The [`PoV`](../../types/availability.md#proofofvalidity). -The second category is for PVF pre-checking. This is primarly used by the [PVF pre-checker](pvf-prechecker.md) +The second category is for PVF pre-checking. This is primarily used by the [PVF pre-checker](pvf-prechecker.md) subsystem. ### Determining Parameters diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md index 0b4fe6a458732de10c2dcd23466ba30f9a46924c..b017259da8c0863e47deb87e916076d20ed95996 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md @@ -187,16 +187,16 @@ this process is a vector of `CandidateHash`s, sorted in order of their core inde #### Required Path -Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidate`, which the provisioner sends in +Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidates`, which the provisioner sends in candidate selection. -An empty required path indicates that the requested candidate should be a direct child of the most recently included +An empty required path indicates that the requested candidate chain should start with the most recently included parablock for the given `para_id` as of the given relay parent. In contrast, a required path with one or more entries prompts [prospective parachains](../backing/prospective-parachains.md) to step forward through its fragment tree for the given `para_id` and -relay parent until the desired parablock is reached. We then select a direct child of that parablock to pass to the -provisioner. +relay parent until the desired parablock is reached. We then select the chain starting with the direct child of that +parablock to pass to the provisioner. The parablocks making up a required path do not need to have been previously seen as included in relay chain blocks. Thus the ability to provision backable candidates based on a required path effectively decouples backing from inclusion. diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/pvf-host-and-workers.md b/polkadot/roadmap/implementers-guide/src/node/utility/pvf-host-and-workers.md index 6a14a3a013d45f2e823be5ef8cf35617e2614dbe..39a317a07c847aa734e78f85de524f5541c8ddf6 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/pvf-host-and-workers.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/pvf-host-and-workers.md @@ -1,9 +1,83 @@ # PVF Host and Workers The PVF host is responsible for handling requests to prepare and execute PVF -code blobs, which it sends to PVF workers running in their own child processes. +code blobs, which it sends to PVF **workers** running in their own child +processes. These workers are spawned from the `polkadot-prepare-worker` and +`polkadot-execute-worker` binaries. -This system has two high-levels goals that we will touch on here: *determinism* +While the workers are generally long-living, they also spawn one-off secure +**job processes** that perform the jobs. See "Job Processes" section below. + +## High-Level Flow + +```dot process +digraph { + rankdir="LR"; + + can [label = "Candidate\nValidation\nSubsystem"; shape = square] + + pvf [label = "PVF Host"; shape = square] + + pq [label = "Prepare\nQueue"; shape = square] + eq [label = "Execute\nQueue"; shape = square] + pp [label = "Prepare\nPool"; shape = square] + + subgraph "cluster partial_sandbox_prep" { + label = "polkadot-prepare-worker\n(Partial Sandbox)\n\n\n"; + labelloc = "t"; + + pw [label = "Prepare\nWorker"; shape = square] + + subgraph "cluster full_sandbox_prep" { + label = "Fully Isolated Sandbox\n\n\n"; + labelloc = "t"; + + pj [label = "Prepare\nJob"; shape = square] + } + } + + subgraph "cluster partial_sandbox_exec" { + label = "polkadot-execute-worker\n(Partial Sandbox)\n\n\n"; + labelloc = "t"; + + ew [label = "Execute\nWorker"; shape = square] + + subgraph "cluster full_sandbox_exec" { + label = "Fully Isolated Sandbox\n\n\n"; + labelloc = "t"; + + ej [label = "Execute\nJob"; shape = square] + } + } + + can -> pvf [label = "Precheck"; style = dashed] + can -> pvf [label = "Validate"] + + pvf -> pq [label = "Prepare"; style = dashed] + pvf -> eq [label = "Execute";] + pvf -> pvf [label = "see (2) and (3)"; style = dashed] + pq -> pp [style = dashed] + + pp -> pw [style = dashed] + eq -> ew + + pw -> pj [style = dashed] + ew -> ej +} +``` + +Some notes about the graph: + +1. Once a job has finished, the response will flow back up the way it came. +2. In the case of execution, the host will send a request for preparation to the + Prepare Queue if needed. In that case, only after the preparation succeeds + does the Execute Queue continue with validation. +3. Multiple requests for preparing the same artifact are coalesced, so that the + work is only done once. + +## Goals + +This system has two high-level goals that we will touch on here: *determinism* and *security*. ## Determinism @@ -27,24 +101,45 @@ hopefully resolve. We use a more brief delay here (1 second as opposed to 15 minutes for preparation (see above)), because a successful execution must happen in a short amount of time. +If the execution fails during the backing phase, we won't retry to reduce the chance of +supporting nondeterministic candidates. This reduces the chance of nondeterministic blocks +getting backed and honest backers getting slashed. + We currently know of the following specific cases that will lead to a retried execution request: -1. **OOM:** The host might have been temporarily low on memory due to other - processes running on the same machine. **NOTE:** This case will lead to - voting against the candidate (and possibly a dispute) if the retry is still - not successful. -2. **Artifact missing:** The prepared artifact might have been deleted due to +1. **OOM:** We have memory limits to try to prevent attackers from exhausting + host memory. If the memory limit is hit, we kill the job process and retry + the job. Alternatively, the host might have been temporarily low on memory + due to other processes running on the same machine. **NOTE:** This case will + lead to voting against the candidate (and possibly a dispute) if the retry is + still not successful. +2. **Syscall violations:** If the job attempts a system call that is blocked by + the sandbox's security policy, the job process is immediately killed and we + retry. **NOTE:** In the future, if we have a proper way to detect that the + job died due to a security violation, it might make sense not to retry in + this case. +3. **Artifact missing:** The prepared artifact might have been deleted due to operator error or some bug in the system. -3. **Panic:** The worker thread panicked for some indeterminate reason, which - may or may not be independent of the candidate or PVF. +4. **Job errors:** For example, the job process panicked for some indeterminate + reason, which may or may not be independent of the candidate or PVF. +5. **Internal errors:** See "Internal Errors" section. In this case, after the + retry we abstain from voting. +6. **RuntimeConstruction** error. The precheck handles a general case of a wrong + artifact but doesn't guarantee its consistency between the preparation and + the execution. If something happened with the artifact between + the preparation of the artifact and its execution (e.g. the artifact was + corrupted on disk or a dirty node upgrade happened when the prepare worker + has a wasmtime version different from the execute worker's wasmtime version). + We treat such an error as possibly transient due to local issues and retry + one time. ### Preparation timeouts We use timeouts for both preparation and execution jobs to limit the amount of time they can take. As the time for a job can vary depending on the machine and load on the machine, this can potentially lead to disputes where some validators -successfuly execute a PVF and others don't. +successfully execute a PVF and others don't. One dispute mitigation we have in place is a more lenient timeout for preparation during execution than during pre-checking. The rationale is that the @@ -62,10 +157,16 @@ more than the CPU time. ### Internal errors +An internal, or local, error is one that we treat as independent of the PVF +and/or candidate, i.e. local to the running machine. If this happens, then we +will first retry the job and if the errors persists, then we simply do not vote. +This prevents slashes, since otherwise our vote may not agree with that of the +other validators. + In general, for errors not raising a dispute we have to be very careful. This is -only sound, if we either: +only sound, if either: -1. Ruled out that error in pre-checking. If something is not checked in +1. We ruled out that error in pre-checking. If something is not checked in pre-checking, even if independent of the candidate and PVF, we must raise a dispute. 2. We are 100% confident that it is a hardware/local issue: Like corrupted file, @@ -75,11 +176,11 @@ Reasoning: Otherwise it would be possible to register a PVF where candidates can not be checked, but we don't get a dispute - so nobody gets punished. Second, we end up with a finality stall that is not going to resolve! -There are some error conditions where we can't be sure whether the candidate is -really invalid or some internal glitch occurred, e.g. panics. Whenever we are -unsure, we can never treat an error as internal as we would abstain from voting. -So we will first retry the candidate, and if the issue persists we are forced to -vote invalid. +Note that any error from the job process we cannot treat as internal. The job +runs untrusted code and an attacker can therefore return arbitrary errors. If +they were to return errors that we treat as internal, they could make us abstain +from voting. Since we are unsure if such errors are legitimate, we will first +retry the candidate, and if the issue persists we are forced to vote invalid. ## Security @@ -119,6 +220,25 @@ So what are we actually worried about? Things that come to mind: 6. **Intercepting and manipulating packages** - Effect very similar to the above, hard to do without also being able to do 4 or 5. +We do not protect against (1), (2), and (3), because there are too many sources +of randomness for an attacker to exploit. + +We provide very good protection against (4), (5), and (6). + +### Job Processes + +As mentioned above, our architecture includes long-living **worker processes** +and one-off **job processes**. This separation is important so that the handling +of untrusted code can be limited to the job processes. A hijacked job process +can therefore not interfere with other jobs running in separate processes. + +Furthermore, if an unexpected execution error occurred in the execution worker +and not the job itself, we generally can be confident that it has nothing to do +with the candidate, so we can abstain from voting. On the other hand, a hijacked +job is able to send back erroneous responses for candidates, so we know that we +should not abstain from voting on such errors from jobs. Otherwise, an attacker +could trigger a finality stall. (See "Internal Errors" section above.) + ### Restricting file-system access A basic security mechanism is to make sure that any process directly interfacing @@ -126,6 +246,17 @@ with untrusted code does not have unnecessary access to the file-system. This provides some protection against attackers accessing sensitive data or modifying data on the host machine. +*Currently this is only supported on Linux.* + +### Restricting networking + +We also disable networking on PVF threads by disabling certain syscalls, such as +the creation of sockets. This prevents attackers from either downloading +payloads or communicating sensitive data from the validator's machine to the +outside world. + +*Currently this is only supported on Linux.* + ### Clearing env vars We clear environment variables before handling untrusted code, because why give diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/pvf-prechecker.md b/polkadot/roadmap/implementers-guide/src/node/utility/pvf-prechecker.md index b722bdc6539a6cf34c5870459180dedd590d7ec5..7f6fef7ddf631f10eadd7c1cc5f4d7d4b7f9cd04 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/pvf-prechecker.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/pvf-prechecker.md @@ -8,9 +8,9 @@ pre-checking. Head over to [overview] for the PVF pre-checking process overview. There is no dedicated input mechanism for PVF pre-checker. Instead, PVF pre-checker looks on the `ActiveLeavesUpdate` event stream for work. -This subsytem does not produce any output messages either. The subsystem will, however, send messages to the [Runtime -API] subsystem to query for the pending PVFs and to submit votes. In addition to that, it will also communicate with -[Candidate Validation] Subsystem to request PVF pre-check. +This subsytem does not produce any output messages either. The subsystem will, however, send messages to the +[Runtime API] subsystem to query for the pending PVFs and to submit votes. In addition to that, it will also +communicate with [Candidate Validation] Subsystem to request PVF pre-check. ## Functionality @@ -24,7 +24,7 @@ relevant. When a PVF just becomes relevant, the subsystem will send a message to the [Candidate Validation] subsystem asking for the pre-check. -Upon receving a message from the candidate-validation subsystem, the pre-checker will note down that the PVF has its +Upon receiving a message from the candidate-validation subsystem, the pre-checker will note down that the PVF has its judgement and will also sign and submit a [`PvfCheckStatement`][PvfCheckStatement] via the [`submit_pvf_check_statement` runtime API][PVF pre-checking runtime API]. In case, a judgement was received for a PVF that is no longer in view it is ignored. diff --git a/polkadot/roadmap/implementers-guide/src/protocol-approval.md b/polkadot/roadmap/implementers-guide/src/protocol-approval.md index 9ba7f6da17340ab786df423c248905db7ca76b94..b6aa16646ad25f339af3a6b3db9d7b2d65997c4d 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-approval.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-approval.md @@ -189,9 +189,10 @@ Assignment criteria compute actual assignments using stories and the validators' Assignment criteria output a `Position` consisting of both a `ParaId` to be checked, as well as a precedence `DelayTranche` for when the assignment becomes valid. -Assignment criteria come in three flavors, `RelayVRFModulo`, `RelayVRFDelay` and `RelayEquivocation`. Among these, both -`RelayVRFModulo` and `RelayVRFDelay` run a VRF whose input is the output of a `RelayVRFStory`, while `RelayEquivocation` -runs a VRF whose input is the output of a `RelayEquivocationStory`. +Assignment criteria come in four flavors, `RelayVRFModuloCompact`, `RelayVRFDelay`, `RelayEquivocation` and the +deprecated `RelayVRFModulo`. Among these, `RelayVRFModulo`, `RelayVRFModuloCompact` and `RelayVRFDelay` run a +VRF whose input is the output of a `RelayVRFStory`, while `RelayEquivocation` runs a VRF whose input is the +output of a `RelayEquivocationStory`. Among these, we have two distinct VRF output computations: @@ -203,6 +204,12 @@ sampled availability core in this relay chain block. We choose three samples in secure and efficient by increasing this to four or five, and reducing the backing checks accordingly. All successful `RelayVRFModulo` samples are assigned delay tranche zero. +`RelayVRFModuloCompact` runs a single samples whose VRF input is the `RelayVRFStory` and the sample count. Similar +to `RelayVRFModulo` introduces multiple core assignments for tranche zero. It computes the VRF output with +`schnorrkel::vrf::VRFInOut::make_bytes` using the context "A&V Core v2" and samples up to 160 bytes of the output +as an array of `u32`. Then reduces each `u32` modulo the number of availability cores, and outputs up +to `relay_vrf_modulo_samples` availability core indices. + There is no sampling process for `RelayVRFDelay` and `RelayEquivocation`. We instead run them on specific candidates and they compute a delay from their VRF output. `RelayVRFDelay` runs for all candidates included under, aka declared available by, a relay chain block, and inputs the associated VRF output via `RelayVRFStory`. `RelayEquivocation` runs @@ -223,14 +230,14 @@ We track all validators' announced approval assignments for each candidate assoc tells us which validators were assigned to which candidates. We permit at most one assignment per candidate per story per validator, so one validator could be assigned under both -the `RelayVRFDelay` and `RelayEquivocation` criteria, but not under both `RelayVRFModulo` and `RelayVRFDelay` criteria, -since those both use the same story. We permit only one approval vote per candidate per validator, which counts for any -applicable criteria. +the `RelayVRFDelay` and `RelayEquivocation` criteria, but not under both `RelayVRFModulo/RelayVRFModuloCompact` +and `RelayVRFDelay` criteria, since those both use the same story. We permit only one approval vote per candidate per +validator, which counts for any applicable criteria. We announce, and start checking for, our own assignments when the delay of their tranche is reached, but only if the -tracker says the assignee candidate requires more approval checkers. We never announce an assignment we believe -unnecessary because early announcements gives an adversary information. All delay tranche zero assignments always get -announced, which includes all `RelayVRFModulo` assignments. +tracker says the assignee candidate requires more approval checkers. We never announce an assignment we believe unnecessary +because early announcements gives an adversary information. All delay tranche zero assignments always get announced, +which includes all `RelayVRFModulo` and `RelayVRFModuloCompact` assignments. In other words, if some candidate `C` needs more approval checkers by the time we reach round `t` then any validators with an assignment to `C` in delay tranche `t` gossip their send assignment notice for `C`, and begin reconstruction and @@ -289,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 @@ -318,12 +337,12 @@ finality. We might explore limits on postponement too, but this sounds much har ## Parameters -We prefer doing approval checkers assignments under `RelayVRFModulo` as opposed to `RelayVRFDelay` because -`RelayVRFModulo` avoids giving individual checkers too many assignments and tranche zero assignments benefit security -the most. We suggest assigning at least 16 checkers under `RelayVRFModulo` although assignment levels have never been -properly analyzed. +We prefer doing approval checkers assignments under `RelayVRFModulo` or `RelayVRFModuloCompact` as opposed to +`RelayVRFDelay` because `RelayVRFModulo` avoids giving individual checkers too many assignments and tranche zero +assignments benefit security the most. We suggest assigning at least 16 checkers under `RelayVRFModulo` or +`RelayVRFModuloCompact` although assignment levels have never been properly analyzed. -Our delay criteria `RelayVRFDelay` and `RelayEquivocation` both have two primary paramaters, expected checkers per +Our delay criteria `RelayVRFDelay` and `RelayEquivocation` both have two primary parameters, expected checkers per tranche and the zeroth delay tranche width. We require expected checkers per tranche to be less than three because otherwise an adversary with 1/3 stake could force diff --git a/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md b/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md index aa31491d72f6c6e142059e7bcfbe881516772d25..69d33ca8670db942ecd8f5cb564f8d973ef6e47b 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/hrmp.md @@ -108,7 +108,7 @@ HrmpEgressChannelsIndex: map ParaId => Vec; /// Invariant: cannot be non-empty if the corresponding channel in `HrmpChannels` is `None`. HrmpChannelContents: map HrmpChannelId => Vec; /// Maintains a mapping that can be used to answer the question: -/// What paras sent a message at the given block number for a given reciever. +/// What paras sent a message at the given block number for a given receiver. /// Invariants: /// - The inner `Vec` is never empty. /// - The inner `Vec` cannot store two same `ParaId`. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index f73df209981af32acf3e99d8023da0d9bed9115d..f6a32a01d5025e2fb824b94f804fd1e89fd0392b 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md @@ -133,7 +133,7 @@ All failed checks should lead to an unrecoverable error making the block invalid [`UpwardMessage`s](../types/messages.md#upward-message) from the [`CandidateCommitments`](../types/candidate.md#candidate-commitments). 1. call `Dmp::prune_dmq` with the para id of the candidate and the candidate's `processed_downward_messages`. - 1. call `Hrmp::prune_hrmp` with the para id of the candiate and the candidate's `hrmp_watermark`. + 1. call `Hrmp::prune_hrmp` with the para id of the candidate and the candidate's `hrmp_watermark`. 1. call `Hrmp::queue_outbound_hrmp` with the para id of the candidate and the list of horizontal messages taken from the commitment, 1. Call `Paras::note_new_head` using the `HeadData` from the receipt and `relay_parent_number`. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/parainherent.md b/polkadot/roadmap/implementers-guide/src/runtime/parainherent.md index 4a771f1df6441696c6bff95d74bb531047e17ace..5419ddae83d4a58222bc405e41a58ca0fd8315f3 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/parainherent.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/parainherent.md @@ -60,3 +60,35 @@ processing it, so the processed inherent data is simply dropped. This also means that the `enter` function keeps data around for no good reason. This seems acceptable though as the size of a block is rather limited. Nevertheless if we ever wanted to optimize this we can easily implement an inherent collector that has two implementations, where one clones and stores the data and the other just passes it on. + +## Sanitization + +`ParasInherent` with the entry point of `create_inherent` sanitizes the input data, while the `enter` entry point +enforces already sanitized input data. If unsanitized data is provided the module generates an error. + +Disputes are included in the block with a priority for a security reasons. It's important to include as many dispute +votes onchain as possible so that disputes conclude faster and the offenders are punished. However if there are too many +disputes to include in a block the dispute set is trimmed so that it respects max block weight. + +Dispute data is first deduplicated and sorted by block number (older first) and dispute location (local then remote). +Concluded and ancient (disputes initiated before the post conclusion acceptance period) disputes are filtered out. +Votes with invalid signatures or from unknown validators (not found in the active set for the current session) are also +filtered out. + +All dispute statements are included in the order described in the previous paragraph until the available block weight is +exhausted. After the dispute data is included all remaining weight is filled in with candidates and availability +bitfields. Bitfields are included with priority, then candidates containing code updates and finally any backed +candidates. If there is not enough weight for all backed candidates they are trimmed by random selection. Disputes are +processed in three separate functions - `deduplicate_and_sort_dispute_data`, `filter_dispute_data` and +`limit_and_sanitize_disputes`. + +Availability bitfields are also sanitized by dropping malformed ones, containing disputed cores or bad signatures. Refer +to `sanitize_bitfields` function for implementation details. + +Backed candidates sanitization removes malformed ones, candidates which have got concluded invalid disputes against them +or candidates produced by unassigned cores. Furthermore any backing votes from disabled validators for a candidate are +dropped. This is part of the validator disabling strategy. After filtering the statements from disabled validators a +backed candidate may end up with votes count less than `minimum_backing_votes` (a parameter from `HostConfiguiration`). +In this case the whole candidate is dropped otherwise it will be rejected by `process_candidates` from pallet inclusion. +All checks related to backed candidates are implemented in `sanitize_backed_candidates` and +`filter_backed_statements_from_disabled_validators`. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/paras.md b/polkadot/roadmap/implementers-guide/src/runtime/paras.md index ac9b1520c3dfb588e06142d6e40b5fe286e0b6ac..e6e0b53512fe306e59b38812fba2fb015f9a093c 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/paras.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/paras.md @@ -189,7 +189,7 @@ UpgradeGoAheadSignal: map hasher(twox_64_concat) ParaId => Option)`: indicate previosuly-occupied cores which are to be considered returned +- `free_cores(Vec<(CoreIndex, FreedReason)>)`: indicate previously-occupied cores which are to be considered returned and why they are being returned. - All freed lease holding parachain cores should be assigned to their respective parachain - All freed on-demand parachain cores whose reason for freeing was `FreedReason::Concluded` should have the claim diff --git a/polkadot/roadmap/implementers-guide/src/types/README.md b/polkadot/roadmap/implementers-guide/src/types/README.md index 87092bf3a005353b09d375b89f599c2918295ef5..5fd3050f94906f04e876e9018698edb1a5218e2f 100644 --- a/polkadot/roadmap/implementers-guide/src/types/README.md +++ b/polkadot/roadmap/implementers-guide/src/types/README.md @@ -55,17 +55,6 @@ digraph { CandidateCommitmentsHash [label = "Hash", shape="doublecircle", fill="gray90"] CandidateCommitmentsHash -> CandidateCommitments:name - FullCandidateReceipt [label = < - - - - -
FullCandidateReceipt<H = Hash, N = BlockNumber>
innerCandidateReceipt<H>
validation_dataValidationData<N>
- >] - - FullCandidateReceipt:inner -> CandidateReceipt:name - FullCandidateReceipt:validation_data -> ValidationData:name - CommittedCandidateReceipt [label = < diff --git a/polkadot/roadmap/implementers-guide/src/types/approval.md b/polkadot/roadmap/implementers-guide/src/types/approval.md index bc33f024426fefcf0809feeeddef2f83882c1914..c19ffa53762a5fc9e2ac0766604efc22cf20ef4d 100644 --- a/polkadot/roadmap/implementers-guide/src/types/approval.md +++ b/polkadot/roadmap/implementers-guide/src/types/approval.md @@ -22,6 +22,35 @@ enum AssignmentCertKind { } } +enum AssignmentCertKindV2 { + /// Multiple assignment stories based on the VRF that authorized the relay-chain block where the + /// candidates were included. + /// + /// The context is [`v2::RELAY_VRF_MODULO_CONTEXT`] + RelayVRFModuloCompact { + /// A bitfield representing the core indices claimed by this assignment. + core_bitfield: CoreBitfield, + }, + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with the index of a particular core. + /// + /// The context is [`v2::RELAY_VRF_DELAY_CONTEXT`] + RelayVRFDelay { + /// The core index chosen in this cert. + core_index: CoreIndex, + }, + /// Deprectated assignment. Soon to be removed. + /// + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with a sample number. + /// + /// The context used to produce bytes is [`v1::RELAY_VRF_MODULO_CONTEXT`] + RelayVRFModulo { + /// The sample number used in this cert. + sample: u32, + }, +} + struct AssignmentCert { // The criterion which is claimed to be met by this cert. kind: AssignmentCertKind, diff --git a/polkadot/roadmap/implementers-guide/src/types/backing.md b/polkadot/roadmap/implementers-guide/src/types/backing.md index 7e43325ec5cdadcbd7d233d00f9a59dd56dd22df..bfe182383fd5136d6fc3b98c72b270354307e2e8 100644 --- a/polkadot/roadmap/implementers-guide/src/types/backing.md +++ b/polkadot/roadmap/implementers-guide/src/types/backing.md @@ -30,7 +30,7 @@ work, we extract a signed wrapper. ```rust,ignore /// A signed type which encapsulates the common desire to sign some data and validate a signature. /// -/// Note that the internal fields are not public; they are all accessable by immutable getters. +/// Note that the internal fields are not public; they are all accessible by immutable getters. /// This reduces the chance that they are accidentally mutated, invalidating the signature. struct Signed { /// The payload is part of the signed data. The rest is the signing context, diff --git a/polkadot/roadmap/implementers-guide/src/types/candidate.md b/polkadot/roadmap/implementers-guide/src/types/candidate.md index 00176229e5a4356a9f88ae91c16eb46617b47216..399d7854ac4a057f49435702adb1a815c960c593 100644 --- a/polkadot/roadmap/implementers-guide/src/types/candidate.md +++ b/polkadot/roadmap/implementers-guide/src/types/candidate.md @@ -20,12 +20,8 @@ struct ParaId(u32); ## Candidate Receipt -Much info in a [`FullCandidateReceipt`](#full-candidate-receipt) is duplicated from the relay-chain state. When the -corresponding relay-chain state is considered widely available, the Candidate Receipt should be favored over the -`FullCandidateReceipt`. - -Examples of situations where the state is readily available includes within the scope of work done by subsystems working -on a given relay-parent, or within the logic of the runtime importing a backed candidate. +Compact representation of the result of a validation. This is what validators +receive from collators, together with the PoV. ```rust /// A candidate-receipt. @@ -37,24 +33,6 @@ struct CandidateReceipt { } ``` -## Full Candidate Receipt - -This is the full receipt type. The `PersistedValidationData` are technically redundant with the `inner.relay_parent`, -which uniquely describes the block in the blockchain from whose state these values are derived. The -[`CandidateReceipt`](#candidate-receipt) variant is often used instead for this reason. - -However, the Full Candidate Receipt type is useful as a means of avoiding the implicit dependency on availability of old -blockchain state. In situations such as availability and approval, having the full description of the candidate within a -self-contained struct is convenient. - -```rust -/// All data pertaining to the execution of a para candidate. -struct FullCandidateReceipt { - inner: CandidateReceipt, - validation_data: PeristedValidationData, -} -``` - ## Committed Candidate Receipt This is a variant of the candidate receipt which includes the commitments of the candidate receipt alongside the diff --git a/polkadot/roadmap/implementers-guide/src/types/runtime.md b/polkadot/roadmap/implementers-guide/src/types/runtime.md index 79da899bd35eae6afff6dd8e6fb53bf670f2c4a2..1dfabd96db7cdb929c416201eeeb233af11f5dbb 100644 --- a/polkadot/roadmap/implementers-guide/src/types/runtime.md +++ b/polkadot/roadmap/implementers-guide/src/types/runtime.md @@ -4,106 +4,24 @@ Types used within the runtime exclusively and pervasively. ## Host Configuration -The internal-to-runtime configuration of the parachain host. This is expected to be altered only by governance procedures. - -```rust -struct HostConfiguration { - /// The minimum period, in blocks, between which parachains can update their validation code. - pub validation_upgrade_cooldown: BlockNumber, - /// The delay, in blocks, before a validation upgrade is applied. - pub validation_upgrade_delay: BlockNumber, - /// How long to keep code on-chain, in blocks. This should be sufficiently long that disputes - /// have concluded. - pub code_retention_period: BlockNumber, - /// The maximum validation code size, in bytes. - pub max_code_size: u32, - /// The maximum head-data size, in bytes. - pub max_head_data_size: u32, - /// The amount of availability cores to dedicate to parathreads (on-demand parachains). - pub parathread_cores: u32, - /// The number of retries that a parathread (on-demand parachain) author has to submit their block. - pub parathread_retries: u32, - /// How often parachain groups should be rotated across parachains. - pub group_rotation_frequency: BlockNumber, - /// The availability period, in blocks, for parachains. This is the amount of blocks - /// after inclusion that validators have to make the block available and signal its availability to - /// the chain. Must be at least 1. - pub chain_availability_period: BlockNumber, - /// The availability period, in blocks, for parathreads (on-demand parachains). Same as the `chain_availability_period`, - /// but a differing timeout due to differing requirements. Must be at least 1. - pub thread_availability_period: BlockNumber, - /// The amount of blocks ahead to schedule on-demand parachains. - pub scheduling_lookahead: u32, - /// The maximum number of validators to have per core. `None` means no maximum. - pub max_validators_per_core: Option, - /// The maximum number of validators to use for parachains, in total. `None` means no maximum. - pub max_validators: Option, - /// The amount of sessions to keep for disputes. - pub dispute_period: SessionIndex, - /// How long after dispute conclusion to accept statements. - pub dispute_post_conclusion_acceptance_period: BlockNumber, - /// The maximum number of dispute spam slots - pub dispute_max_spam_slots: u32, - /// The amount of consensus slots that must pass between submitting an assignment and - /// submitting an approval vote before a validator is considered a no-show. - /// Must be at least 1. - pub no_show_slots: u32, - /// The number of delay tranches in total. - pub n_delay_tranches: u32, - /// The width of the zeroth delay tranche for approval assignments. This many delay tranches - /// beyond 0 are all consolidated to form a wide 0 tranche. - pub zeroth_delay_tranche_width: u32, - /// The number of validators needed to approve a block. - pub needed_approvals: u32, - /// The number of samples to do of the RelayVRFModulo approval assignment criterion. - pub relay_vrf_modulo_samples: u32, - /// Total number of individual messages allowed in the parachain -> relay-chain message queue. - pub max_upward_queue_count: u32, - /// Total size of messages allowed in the parachain -> relay-chain message queue before which - /// no further messages may be added to it. If it exceeds this then the queue may contain only - /// a single message. - pub max_upward_queue_size: u32, - /// The maximum size of an upward message that can be sent by a candidate. - /// - /// This parameter affects the upper bound of size of `CandidateCommitments`. - pub max_upward_message_size: u32, - /// The maximum number of messages that a candidate can contain. - /// - /// This parameter affects the upper bound of size of `CandidateCommitments`. - pub max_upward_message_num_per_candidate: u32, - /// The maximum size of a message that can be put in a downward message queue. - /// - /// Since we require receiving at least one DMP message the obvious upper bound of the size is - /// the PoV size. Of course, there is a lot of other different things that a parachain may - /// decide to do with its PoV so this value in practice will be picked as a fraction of the PoV - /// size. - pub max_downward_message_size: u32, - /// The deposit that the sender should provide for opening an HRMP channel. - pub hrmp_sender_deposit: u32, - /// The deposit that the recipient should provide for accepting opening an HRMP channel. - pub hrmp_recipient_deposit: u32, - /// The maximum number of messages allowed in an HRMP channel at once. - pub hrmp_channel_max_capacity: u32, - /// The maximum total size of messages in bytes allowed in an HRMP channel at once. - pub hrmp_channel_max_total_size: u32, - /// The maximum number of inbound HRMP channels a parachain is allowed to accept. - pub hrmp_max_parachain_inbound_channels: u32, - /// The maximum number of inbound HRMP channels a parathread (on-demand parachain) is allowed to accept. - pub hrmp_max_parathread_inbound_channels: u32, - /// The maximum size of a message that could ever be put into an HRMP channel. - /// - /// This parameter affects the upper bound of size of `CandidateCommitments`. - pub hrmp_channel_max_message_size: u32, - /// The maximum number of outbound HRMP channels a parachain is allowed to open. - pub hrmp_max_parachain_outbound_channels: u32, - /// The maximum number of outbound HRMP channels a parathread (on-demand parachain) is allowed to open. - pub hrmp_max_parathread_outbound_channels: u32, - /// The maximum number of outbound HRMP messages can be sent by a candidate. - /// - /// This parameter affects the upper bound of size of `CandidateCommitments`. - pub hrmp_max_message_num_per_candidate: u32, -} -``` +The internal-to-runtime configuration of the parachain host is kept in `struct HostConfiguration`. This is expected to +be altered only by governance procedures or via migrations from the Polkadot-SDK codebase. The latest definition of +`HostConfiguration` can be found in the project repo +[here](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/runtime/parachains/src/configuration.rs). Each +parameter has got a doc comment so for any details please refer to the code. + +Some related parameters in `HostConfiguration` are grouped together so that they can be managed easily. These are: +* `async_backing_params` in `struct AsyncBackingParams` +* `executor_params` in `struct ExecutorParams` +* `approval_voting_params` in `struct ApprovalVotingParams` +* `scheduler_params` in `struct SchedulerParams` + +Check the definitions of these structs for further details. + +### Configuration migrations +Modifying `HostConfiguration` requires a storage migration. These migrations are located in the +[`migrations`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/runtime/parachains/src/configuration.rs) +subfolder of Polkadot-SDK repo. ## ParaInherentData diff --git a/polkadot/roadmap/phase-1.toml b/polkadot/roadmap/phase-1.toml index 50ef1f741fe9d448a29e9a4018746305b3fb2586..3a5f0d752debee41c51a2124f8e91f3e03dc9d68 100644 --- a/polkadot/roadmap/phase-1.toml +++ b/polkadot/roadmap/phase-1.toml @@ -14,7 +14,7 @@ requires = ["phase-0"] items = [ { label = "Buffer submitted parachain candidate until considered available." }, { label = "Validators submit signed bitfields re: availability of parachains" }, - { label = "relay chain fully includes candidate once considered available" } + { label = "relay chain fully includes candidate once considered available" }, ] [[group]] @@ -23,8 +23,8 @@ label = "Secondary checks and self-selection by validators" requires = ["two-phase-inclusion"] items = [ { label = "Extract #VCheck for all checkable candidates" }, - { label = "Maintain a frontier of candidates that are likely to be checked soon" }, { label = "Listen for new reports on candidates and new checks to update frontier" }, + { label = "Maintain a frontier of candidates that are likely to be checked soon" }, ] [[group]] @@ -32,8 +32,8 @@ name = "runtime-availability-validity-slashing" label = "Availability and Validity slashing in the runtime" requires = ["two-phase-inclusion"] items = [ + { label = "Submit secondary checks to runtime", port = "submitsecondary", requires = ["secondary-checking"] }, { label = "Track all candidates within the slash period as well as their session" }, - { label = "Submit secondary checks to runtime", port = "submitsecondary", requires = ["secondary-checking"]}, { label = "Track reports and attestatations for candidates" }, ] @@ -41,10 +41,10 @@ items = [ name = "non-direct-ancestor" label = "Allow candidates with non-direct ancestor" items = [ - { label = "Extend GlobalValidationData with random seed and session index"}, { label = "Block author can provide minimally-attested candidate with older relay parent" }, - { label = "Runtime can accept and process candidates with older relay-parent" }, + { label = "Extend GlobalValidationData with random seed and session index" }, { label = "Revise availability-store pruning to ensure only needed data is kept" }, + { label = "Runtime can accept and process candidates with older relay-parent" }, ] [[group]] @@ -52,13 +52,13 @@ name = "grandpa-voting-rule" label = "GRANDPA voting rule to follow valid/available chains" requires = ["runtime-availability-validity-slashing"] items = [ - { label = "Add a utility to flag a block and all of its ancestors as abandoned" }, { label = "Accept new blocks on abandoned but mark them abandoned as well." }, + { label = "Add a utility to flag a block and all of its ancestors as abandoned" }, { label = "Do not vote or build on abandoned chains" }, ] [[group]] name = "phase-1" label = "Phase 1: Availability and Validity" -requires = ["non-direct-ancestor", "grandpa-voting-rule", "runtime-availability-validity-slashing"] +requires = ["grandpa-voting-rule", "non-direct-ancestor", "runtime-availability-validity-slashing"] items = [] diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index b5f155b5a658fe25aada71fdc454966050c519f1..5af5e63b175380f7e695deb973ea48df52c326e4 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "polkadot-rpc" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +description = "Polkadot specific RPC functionality." + +[lints] +workspace = true [dependencies] -jsonrpsee = { version = "0.16.2", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } polkadot-primitives = { path = "../primitives" } sc-client-api = { path = "../../substrate/client/api" } sp-blockchain = { path = "../../substrate/primitives/blockchain" } @@ -17,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 18332156d99a5eae6a61f1975bb9c0f30109872e..eae5d4fb2ef90a0acb515b863dfb0013c45bac89 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -1,35 +1,40 @@ [package] name = "polkadot-runtime-common" -version = "1.0.0" +version = "7.0.0" description = "Pallets and constants used in Relay Chain networks." 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"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", default-features = false, features = ["alloc"] } -serde_derive = { version = "1.0.117" } +serde = { features = ["alloc"], workspace = true } +serde_derive = { workspace = true } static_assertions = "1.1.0" sp-api = { path = "../../../substrate/primitives/api", default-features = false } inherents = { package = "sp-inherents", path = "../../../substrate/primitives/inherents", default-features = false } sp-std = { package = "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 , features=["serde"]} +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } sp-session = { path = "../../../substrate/primitives/session", default-features = false } -sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features=["serde"] } -sp-core = { path = "../../../substrate/primitives/core", default-features = false , features=["serde"]} -sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", default-features = false, features=["serde"] } +sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features = ["serde"] } +sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] } +sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", default-features = false, features = ["serde"] } 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 } frame-support = { path = "../../../substrate/frame/support", default-features = false } pallet-staking = { path = "../../../substrate/frame/staking", default-features = false } @@ -39,7 +44,7 @@ pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-featur pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = false } pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } pallet-treasury = { path = "../../../substrate/frame/treasury", default-features = false } -pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false } +pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false, optional = true } pallet-election-provider-multi-phase = { path = "../../../substrate/frame/election-provider-multi-phase", default-features = false } frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support", default-features = false } @@ -53,8 +58,6 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac slot-range-helper = { path = "slot_range_helper", default-features = false } xcm = { package = "staging-xcm", path = "../../xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false, optional = true } - -pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-features = false, optional = true } xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false } [dev-dependencies] @@ -64,12 +67,12 @@ 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.107" +serde_json = { workspace = true, default-features = true } libsecp256k1 = "0.7.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } [features] -default = [ "std" ] +default = ["std"] no_std = [] std = [ "bitvec/std", @@ -80,11 +83,13 @@ std = [ "inherents/std", "libsecp256k1/std", "log/std", - "pallet-asset-rate/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", "pallet-session/std", "pallet-staking-reward-fn/std", "pallet-staking/std", @@ -92,7 +97,6 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-vesting/std", - "pallet-xcm-benchmarks/std", "parity-scale-codec/std", "primitives/std", "runtime-parachains/std", @@ -122,13 +126,14 @@ 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", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", "primitives/runtime-benchmarks", "runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -145,8 +150,10 @@ 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", "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml index f65717519d5e1e715f478e37381e943e20bbdca4..cacafd8ed3b746d35b5b064ca3d6662b313f8b86 100644 --- a/polkadot/runtime/common/slot_range_helper/Cargo.toml +++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml @@ -1,9 +1,13 @@ [package] name = "slot-range-helper" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +description = "Helper crate for generating slot ranges for the Polkadot runtime." + +[lints] +workspace = true [dependencies] paste = "1.0" @@ -13,5 +17,5 @@ sp-std = { package = "sp-std", path = "../../../../substrate/primitives/std", de sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } [features] -default = [ "std" ] -std = [ "parity-scale-codec/std", "sp-runtime/std", "sp-std/std" ] +default = ["std"] +std = ["parity-scale-codec/std", "sp-runtime/std", "sp-std/std"] diff --git a/polkadot/runtime/common/src/assigned_slots/migration.rs b/polkadot/runtime/common/src/assigned_slots/migration.rs index 0e88b27a1ff8ae7bc1c7fba7fbeb4b057fdaa5fe..def6bad692a235d3282774b0db3fabdaf33c89b3 100644 --- a/polkadot/runtime/common/src/assigned_slots/migration.rs +++ b/polkadot/runtime/common/src/assigned_slots/migration.rs @@ -25,18 +25,18 @@ use sp_std::vec::Vec; pub mod v1 { use super::*; - pub struct MigrateToV1(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for MigrateToV1 { + pub struct VersionUncheckedMigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for VersionUncheckedMigrateToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - let onchain_version = Pallet::::on_chain_storage_version(); - ensure!(onchain_version < 1, "assigned_slots::MigrateToV1 migration can be deleted"); + let on_chain_version = Pallet::::on_chain_storage_version(); + ensure!(on_chain_version < 1, "assigned_slots::MigrateToV1 migration can be deleted"); Ok(Default::default()) } fn on_runtime_upgrade() -> frame_support::weights::Weight { - let onchain_version = Pallet::::on_chain_storage_version(); - if onchain_version < 1 { + let on_chain_version = Pallet::::on_chain_storage_version(); + if on_chain_version < 1 { const MAX_PERMANENT_SLOTS: u32 = 100; const MAX_TEMPORARY_SLOTS: u32 = 100; @@ -52,21 +52,21 @@ pub mod v1 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - let onchain_version = Pallet::::on_chain_storage_version(); - ensure!(onchain_version == 1, "assigned_slots::MigrateToV1 needs to be run"); + let on_chain_version = Pallet::::on_chain_storage_version(); + ensure!(on_chain_version == 1, "assigned_slots::MigrateToV1 needs to be run"); assert_eq!(>::get(), 100); assert_eq!(>::get(), 100); Ok(()) } } - /// [`MigrateToV1`] wrapped in a + /// [`VersionUncheckedMigrateToV1`] wrapped in a /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the /// migration is only performed when on-chain version is 0. - pub type VersionCheckedMigrateToV1 = frame_support::migrations::VersionedMigration< + pub type MigrateToV1 = frame_support::migrations::VersionedMigration< 0, 1, - MigrateToV1, + VersionUncheckedMigrateToV1, Pallet, ::DbWeight, >; diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index cc8ec339c11847b60aaf74e909ab798a6cb45b51..5f1bc73db96397426040cc0b873e5f660577a793 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -107,7 +107,7 @@ type LeasePeriodOf = <::Leaser as Leaser>>::Le pub mod pallet { use super::*; - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] @@ -636,7 +636,7 @@ mod tests { use crate::{assigned_slots, mock::TestRegistrar, slots}; use ::test_helpers::{dummy_head_data, dummy_validation_code}; - use frame_support::{assert_noop, assert_ok, parameter_types}; + use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use frame_system::EnsureRoot; use pallet_balances; use primitives::BlockNumber; @@ -658,13 +658,13 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Configuration: parachains_configuration::{Pallet, Call, Storage, Config}, - ParasShared: parachains_shared::{Pallet, Call, Storage}, - Parachains: parachains_paras::{Pallet, Call, Storage, Config, Event}, - Slots: slots::{Pallet, Call, Storage, Event}, - AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Configuration: parachains_configuration, + ParasShared: parachains_shared, + Parachains: parachains_paras, + Slots: slots, + AssignedSlots: assigned_slots, } ); @@ -679,6 +679,8 @@ mod tests { parameter_types! { pub const BlockHashCount: u32 = 250; } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -720,8 +722,8 @@ mod tests { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } @@ -740,9 +742,12 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } - impl parachains_shared::Config for Test {} + impl parachains_shared::Config for Test { + type DisabledValidators = (); + } parameter_types! { pub const LeasePeriod: BlockNumber = 3; diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs index e35303912fa1dca33d175646dfe75ace3f719d19..6914fef99d5264c41d93c8a4d3559b31caf621b0 100644 --- a/polkadot/runtime/common/src/auctions.rs +++ b/polkadot/runtime/common/src/auctions.rs @@ -677,7 +677,8 @@ mod tests { use crate::{auctions, mock::TestRegistrar}; use ::test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, ord_parameter_types, parameter_types, + assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types, + parameter_types, traits::{ConstU32, EitherOfDiverse, OnFinalize, OnInitialize}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; @@ -696,15 +697,17 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Auctions: auctions::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Auctions: auctions, } ); parameter_types! { pub const BlockHashCount: u32 = 250; } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -747,8 +750,8 @@ mod tests { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs index 0c736a632842336e30d12381697e4d638a65d74a..49ac4eb894108bd4ab4dc28209deb08c505f42cb 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 { @@ -591,11 +591,9 @@ impl Pallet { /// otherwise free to place on chain. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct PrevalidateAttests(sp_std::marker::PhantomData) -where - ::RuntimeCall: IsSubType>; +pub struct PrevalidateAttests(core::marker::PhantomData); -impl Debug for PrevalidateAttests +impl Debug for PrevalidateAttests where ::RuntimeCall: IsSubType>, { @@ -610,7 +608,7 @@ where } } -impl PrevalidateAttests +impl PrevalidateAttests where ::RuntimeCall: IsSubType>, { @@ -620,7 +618,7 @@ where } } -impl SignedExtension for PrevalidateAttests +impl SignedExtension for PrevalidateAttests where ::RuntimeCall: IsSubType>, { @@ -704,24 +702,20 @@ mod tests { use secp_utils::*; use parity_scale_codec::Encode; - use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use crate::claims; use claims::Call as ClaimsCall; use frame_support::{ - assert_err, assert_noop, assert_ok, + assert_err, assert_noop, assert_ok, derive_impl, dispatch::{GetDispatchInfo, Pays}, ord_parameter_types, parameter_types, traits::{ConstU32, ExistenceRequirement, WithdrawReasons}, }; use pallet_balances; use sp_runtime::{ - traits::{BlakeTwo256, Identity, IdentityLookup}, - transaction_validity::TransactionLongevity, - BuildStorage, - DispatchError::BadOrigin, - TokenError, + traits::Identity, transaction_validity::TransactionLongevity, BuildStorage, + DispatchError::BadOrigin, TokenError, }; type Block = frame_system::mocking::MockBlock; @@ -729,39 +723,20 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Vesting: pallet_vesting::{Pallet, Call, Storage, Config, Event}, - Claims: claims::{Pallet, Call, Storage, Config, Event, ValidateUnsigned}, + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Claims: claims, } ); - parameter_types! { - pub const BlockHashCount: u32 = 250; - } + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] 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 = BlockHashCount; - 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>; } @@ -780,8 +755,8 @@ mod tests { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } @@ -798,6 +773,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/crowdloan/migration.rs b/polkadot/runtime/common/src/crowdloan/migration.rs index 5133c14ada9276a56ae91f38e8a67b4846979866..3afd6b3fbc94b5b30bad162cfcf8236a029b77da 100644 --- a/polkadot/runtime/common/src/crowdloan/migration.rs +++ b/polkadot/runtime/common/src/crowdloan/migration.rs @@ -24,9 +24,9 @@ use frame_support::{ pub struct MigrateToTrackInactiveV2(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToTrackInactiveV2 { fn on_runtime_upgrade() -> Weight { - let onchain_version = Pallet::::on_chain_storage_version(); + let on_chain_version = Pallet::::on_chain_storage_version(); - if onchain_version == 1 { + if on_chain_version == 1 { let mut translated = 0u64; for item in Funds::::iter_values() { let b = diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 5a2939145925c453f9c86b9c6e5f7c9f71de9757..d405278411b7e4aa1f1133fb7356bf6929a370e6 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -60,7 +60,7 @@ use frame_support::{ pallet_prelude::{DispatchResult, Weight}, storage::{child, ChildTriePrefixIterator}, traits::{ - Currency, + Currency, Defensive, ExistenceRequirement::{self, AllowDeath, KeepAlive}, Get, ReservableCurrency, }, @@ -180,7 +180,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] @@ -563,6 +563,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + let pot = Self::fund_account_id(fund.fund_index); let now = frame_system::Pallet::::block_number(); // Only allow dissolution when the raised funds goes to zero, @@ -576,7 +577,10 @@ pub mod pallet { // can take care of that. debug_assert!(Self::contribution_iterator(fund.fund_index).count().is_zero()); - frame_system::Pallet::::dec_providers(&Self::fund_account_id(fund.fund_index))?; + // Crowdloan over, burn all funds. + let _imba = CurrencyOf::::make_free_balance_be(&pot, Zero::zero()); + let _ = frame_system::Pallet::::dec_providers(&pot).defensive(); + CurrencyOf::::unreserve(&fund.depositor, fund.deposit); Funds::::remove(index); Self::deposit_event(Event::::Dissolved { para_id: index }); @@ -863,7 +867,7 @@ mod tests { use super::*; use frame_support::{ - assert_noop, assert_ok, parameter_types, + assert_noop, assert_ok, derive_impl, parameter_types, traits::{ConstU32, OnFinalize, OnInitialize}, }; use primitives::Id as ParaId; @@ -888,9 +892,9 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Crowdloan: crowdloan::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Crowdloan: crowdloan, } ); @@ -900,6 +904,7 @@ mod tests { type BlockNumber = u64; + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -941,8 +946,8 @@ mod tests { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } @@ -1608,6 +1613,7 @@ mod tests { new_test_ext().execute_with(|| { let para = new_para(); let index = NextFundIndex::::get(); + let issuance = Balances::total_issuance(); // Set up a crowdloan assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None)); @@ -1628,9 +1634,10 @@ mod tests { // Some funds are left over assert_eq!(Balances::free_balance(&account_id), 10); - // They wil be left in the account at the end + // Remaining funds will be burned assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para)); - assert_eq!(Balances::free_balance(&account_id), 10); + assert_eq!(Balances::free_balance(&account_id), 0); + assert_eq!(Balances::total_issuance(), issuance - 10); }); } @@ -1739,6 +1746,41 @@ mod tests { }); } + // Regression test to check that a pot account with just one provider can be dissolved. + #[test] + fn dissolve_provider_refs_total_issuance_works() { + new_test_ext().execute_with(|| { + let para = new_para(); + let issuance = Balances::total_issuance(); + + // Set up a crowdloan + assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None)); + assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); + assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); + + run_to_block(10); + + // We test the historic case where crowdloan accounts only have one provider: + { + let fund = Crowdloan::funds(para).unwrap(); + let pot = Crowdloan::fund_account_id(fund.fund_index); + System::dec_providers(&pot).unwrap(); + assert_eq!(System::providers(&pot), 1); + } + + // All funds are refunded + assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para)); + + // Now that `fund.raised` is zero, it can be dissolved. + assert_ok!(Crowdloan::dissolve(RuntimeOrigin::signed(1), para)); + + assert_eq!(Balances::free_balance(1), 1000); + assert_eq!(Balances::free_balance(2), 2000); + assert_eq!(Balances::free_balance(3), 3000); + assert_eq!(Balances::total_issuance(), issuance); + }); + } + #[test] fn dissolve_works() { new_test_ext().execute_with(|| { diff --git a/polkadot/runtime/common/src/identity_migrator.rs b/polkadot/runtime/common/src/identity_migrator.rs new file mode 100644 index 0000000000000000000000000000000000000000..bf334a63e9588411f57ecc8b6ee2fca72861d7aa --- /dev/null +++ b/polkadot/runtime/common/src/identity_migrator.rs @@ -0,0 +1,306 @@ +// 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 is designed to go into a source chain and destination chain to migrate data. The +//! design motivations are: +//! +//! - Call some function on the source chain that executes some migration (clearing state, +//! forwarding an XCM program). +//! - Call some function (probably from an XCM program) on the destination chain. +//! - Avoid cluttering the source pallet with new dispatchables that are unrelated to its +//! functionality and only used for migration. +//! +//! After the migration is complete, the pallet may be removed from both chains' runtimes as well as +//! the `polkadot-runtime-common` crate. + +use frame_support::{dispatch::DispatchResult, traits::Currency, weights::Weight}; +pub use pallet::*; +use pallet_identity; +use sp_core::Get; + +#[cfg(feature = "runtime-benchmarks")] +use frame_benchmarking::{account, v2::*, BenchmarkError}; + +pub trait WeightInfo { + fn reap_identity(r: u32, s: u32) -> Weight; + fn poke_deposit() -> Weight; +} + +impl WeightInfo for () { + fn reap_identity(_r: u32, _s: u32) -> Weight { + Weight::MAX + } + fn poke_deposit() -> Weight { + Weight::MAX + } +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn reap_identity(_r: u32, _s: u32) -> Weight { + Weight::zero() + } + fn poke_deposit() -> Weight { + Weight::zero() + } +} + +// Must use the same `Balance` as `T`'s Identity pallet to handle deposits. +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + dispatch::{DispatchResultWithPostInfo, PostDispatchInfo}, + pallet_prelude::*, + traits::EnsureOrigin, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_identity::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The origin that can reap identities. Expected to be `EnsureSigned` on the + /// source chain such that anyone can all this function. + type Reaper: EnsureOrigin; + + /// A handler for what to do when an identity is reaped. + type ReapIdentityHandler: OnReapIdentity; + + /// Weight information for the extrinsics in the pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The identity and all sub accounts were reaped for `who`. + IdentityReaped { who: T::AccountId }, + /// The deposits held for `who` were updated. `identity` is the new deposit held for + /// identity info, and `subs` is the new deposit held for the sub-accounts. + DepositUpdated { who: T::AccountId, identity: BalanceOf, subs: BalanceOf }, + } + + #[pallet::call] + impl Pallet { + /// Reap the `IdentityInfo` of `who` from the Identity pallet of `T`, unreserving any + /// deposits held and removing storage items associated with `who`. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::reap_identity( + T::MaxRegistrars::get(), + T::MaxSubAccounts::get() + ))] + pub fn reap_identity( + origin: OriginFor, + who: T::AccountId, + ) -> DispatchResultWithPostInfo { + T::Reaper::ensure_origin(origin)?; + // - number of registrars (required to calculate weight) + // - byte size of `IdentityInfo` (required to calculate remote deposit) + // - number of sub accounts (required to calculate both weight and remote deposit) + let (registrars, bytes, subs) = pallet_identity::Pallet::::reap_identity(&who)?; + T::ReapIdentityHandler::on_reap_identity(&who, bytes, subs)?; + Self::deposit_event(Event::IdentityReaped { who }); + let post = PostDispatchInfo { + actual_weight: Some(::WeightInfo::reap_identity( + registrars, subs, + )), + pays_fee: Pays::No, + }; + Ok(post) + } + + /// Update the deposit of `who`. Meant to be called by the system with an XCM `Transact` + /// Instruction. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::poke_deposit())] + pub fn poke_deposit(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + let (id_deposit, subs_deposit) = pallet_identity::Pallet::::poke_deposit(&who)?; + Self::deposit_event(Event::DepositUpdated { + who, + identity: id_deposit, + subs: subs_deposit, + }); + Ok(Pays::No.into()) + } + } +} + +/// Trait to handle reaping identity from state. +pub trait OnReapIdentity { + /// What to do when an identity is reaped. For example, the implementation could send an XCM + /// program to another chain. Concretely, a type implementing this trait in the Polkadot + /// runtime would teleport enough DOT to the People Chain to cover the Identity deposit there. + /// + /// This could also directly include `Transact { poke_deposit(..), ..}`. + /// + /// Inputs + /// - `who`: Whose identity was reaped. + /// - `bytes`: The byte size of `IdentityInfo`. + /// - `subs`: The number of sub-accounts they had. + fn on_reap_identity(who: &AccountId, bytes: u32, subs: u32) -> DispatchResult; +} + +impl OnReapIdentity for () { + fn on_reap_identity(_who: &AccountId, _bytes: u32, _subs: u32) -> DispatchResult { + Ok(()) + } +} + +#[cfg(feature = "runtime-benchmarks")] +#[benchmarks] +mod benchmarks { + use super::*; + use frame_support::traits::EnsureOrigin; + use frame_system::RawOrigin; + use pallet_identity::{Data, IdentityInformationProvider, Judgement, Pallet as Identity}; + use parity_scale_codec::Encode; + use sp_runtime::{ + traits::{Bounded, Hash, StaticLookup}, + Saturating, + }; + use sp_std::{boxed::Box, vec::Vec, *}; + + const SEED: u32 = 0; + + fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); + } + + #[benchmark] + fn reap_identity( + r: Linear<0, { T::MaxRegistrars::get() }>, + s: Linear<0, { T::MaxSubAccounts::get() }>, + ) -> Result<(), BenchmarkError> { + // set up target + let target: T::AccountId = account("target", 0, SEED); + let target_origin = + ::RuntimeOrigin::from(RawOrigin::Signed(target.clone())); + let target_lookup = T::Lookup::unlookup(target.clone()); + let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); + + // set identity + let info = ::IdentityInformation::create_identity_info(); + Identity::::set_identity( + RawOrigin::Signed(target.clone()).into(), + Box::new(info.clone()), + )?; + + // create and set subs + let mut subs = Vec::new(); + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + for ii in 0..s { + let sub_account = account("sub", ii, SEED); + subs.push((sub_account, data.clone())); + } + Identity::::set_subs(target_origin.clone(), subs.clone())?; + + // add registrars and provide judgements + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); + for ii in 0..r { + // registrar account + let registrar: T::AccountId = account("registrar", ii, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); + let _ = ::Currency::make_free_balance_be( + ®istrar, + ::Currency::minimum_balance(), + ); + + // add registrar + Identity::::add_registrar(registrar_origin.clone(), registrar_lookup)?; + Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), ii, 10u32.into())?; + let fields = ::IdentityInformation::all_fields(); + Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), ii, fields)?; + + // request and provide judgement + Identity::::request_judgement(target_origin.clone(), ii, 10u32.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(registrar).into(), + ii, + target_lookup.clone(), + Judgement::Reasonable, + ::Hashing::hash_of(&info), + )?; + } + + let origin = T::Reaper::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, target.clone()); + + assert_last_event::(Event::::IdentityReaped { who: target.clone() }.into()); + + let fields = ::IdentityInformation::all_fields(); + assert!(!Identity::::has_identity(&target, fields)); + assert_eq!(Identity::::subs(&target).len(), 0); + + Ok(()) + } + + #[benchmark] + fn poke_deposit() -> Result<(), BenchmarkError> { + let target: T::AccountId = account("target", 0, SEED); + let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); + let info = ::IdentityInformation::create_identity_info(); + + let _ = Identity::::set_identity_no_deposit(&target, info.clone()); + + let sub_account: T::AccountId = account("sub", 0, SEED); + 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() + .saturating_add( + ::ByteDeposit::get() + .saturating_mul(>::from(info.encoded_size() as u32)), + ); + // only 1 sub + let expected_sub_deposit = ::SubAccountDeposit::get(); + + #[extrinsic_call] + _(RawOrigin::Root, target.clone()); + + assert_last_event::( + Event::::DepositUpdated { + who: target, + identity: expected_id_deposit, + subs: expected_sub_deposit, + } + .into(), + ); + + Ok(()) + } + + impl_benchmark_test_suite!( + Pallet, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index 590593745ed040652c79e5aab86877862aeff0e4..acf5a701a62d0e7a5b60eee00d85528f5d9214b5 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -21,7 +21,7 @@ use frame_support::traits::{Currency, Imbalance, OnUnbalanced}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::Balance; use sp_runtime::{traits::TryConvert, Perquintill, RuntimeDebug}; -use xcm::VersionedMultiLocation; +use xcm::VersionedLocation; /// Logic for the author to get a portion of fees. pub struct ToAuthor(sp_std::marker::PhantomData); @@ -107,12 +107,9 @@ pub fn era_payout( )] pub enum VersionedLocatableAsset { #[codec(index = 3)] - V3 { - /// The (relative) location in which the asset ID is meaningful. - location: xcm::v3::MultiLocation, - /// The asset's ID. - asset_id: xcm::v3::AssetId, - }, + V3 { location: xcm::v3::Location, asset_id: xcm::v3::AssetId }, + #[codec(index = 4)] + V4 { location: xcm::v4::Location, asset_id: xcm::v4::AssetId }, } /// Converts the [`VersionedLocatableAsset`] to the [`xcm_builder::LocatableAssetId`]. @@ -125,22 +122,29 @@ impl TryConvert ) -> Result { match asset { VersionedLocatableAsset::V3 { location, asset_id } => - Ok(xcm_builder::LocatableAssetId { asset_id, location }), + Ok(xcm_builder::LocatableAssetId { + location: location.try_into().map_err(|_| asset.clone())?, + asset_id: asset_id.try_into().map_err(|_| asset.clone())?, + }), + VersionedLocatableAsset::V4 { location, asset_id } => + Ok(xcm_builder::LocatableAssetId { location, asset_id }), } } } -/// Converts the [`VersionedMultiLocation`] to the [`xcm::latest::MultiLocation`]. -pub struct VersionedMultiLocationConverter; -impl TryConvert<&VersionedMultiLocation, xcm::latest::MultiLocation> - for VersionedMultiLocationConverter -{ +/// Converts the [`VersionedLocation`] to the [`xcm::latest::Location`]. +pub struct VersionedLocationConverter; +impl TryConvert<&VersionedLocation, xcm::latest::Location> for VersionedLocationConverter { fn try_convert( - location: &VersionedMultiLocation, - ) -> Result { + location: &VersionedLocation, + ) -> Result { let latest = match location.clone() { - VersionedMultiLocation::V2(l) => l.try_into().map_err(|_| location)?, - VersionedMultiLocation::V3(l) => l, + VersionedLocation::V2(l) => { + let v3: xcm::v3::Location = l.try_into().map_err(|_| location)?; + v3.try_into().map_err(|_| location)? + }, + VersionedLocation::V3(l) => l.try_into().map_err(|_| location)?, + VersionedLocation::V4(l) => l, }; Ok(latest) } @@ -149,8 +153,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`]. @@ -158,11 +165,14 @@ pub mod benchmarks { pub struct AssetRateArguments; impl AssetKindFactory for AssetRateArguments { fn create_asset_kind(seed: u32) -> VersionedLocatableAsset { - VersionedLocatableAsset::V3 { - location: xcm::v3::MultiLocation::new(0, X1(Parachain(seed))), - asset_id: xcm::v3::MultiLocation::new( + VersionedLocatableAsset::V4 { + location: xcm::v4::Location::new(0, [xcm::v4::Junction::Parachain(seed)]), + asset_id: xcm::v4::Location::new( 0, - X2(PalletInstance(seed.try_into().unwrap()), GeneralIndex(seed.into())), + [ + xcm::v4::Junction::PalletInstance(seed.try_into().unwrap()), + xcm::v4::Junction::GeneralIndex(seed.into()), + ], ) .into(), } @@ -170,19 +180,35 @@ 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 + /// [`VersionedLocation`]. 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::Location::new( + Parents::get(), + [xcm::v3::Junction::Parachain(ParaId::get())], + ), + asset_id: xcm::v3::Location::new( + 0, + [ + xcm::v3::Junction::PalletInstance(seed.try_into().unwrap()), + xcm::v3::Junction::GeneralIndex(seed.into()), + ], + ) + .into(), + } } - fn create_beneficiary(seed: [u8; 32]) -> VersionedMultiLocation { - VersionedMultiLocation::V3(xcm::v3::MultiLocation::new( + fn create_beneficiary(seed: [u8; 32]) -> VersionedLocation { + VersionedLocation::V4(xcm::v4::Location::new( 0, - X1(AccountId32 { network: None, id: seed }), + [xcm::v4::Junction::AccountId32 { network: None, id: seed }], )) } } @@ -192,6 +218,7 @@ pub mod benchmarks { mod tests { use super::*; use frame_support::{ + derive_impl, dispatch::DispatchClass, parameter_types, traits::{ @@ -215,10 +242,10 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Storage}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + System: frame_system, + Authorship: pallet_authorship, + Balances: pallet_balances, + Treasury: pallet_treasury, } ); @@ -237,6 +264,7 @@ mod tests { pub const AvailableBlockRatio: Perbill = Perbill::one(); } + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; @@ -274,8 +302,8 @@ mod tests { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index f14db68267d5b550553bd6f48819890adf011c48..dfd0d356638c0b5525368bb81b268baada334f22 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -17,7 +17,7 @@ //! Mocking utilities for testing with real pallets. use crate::{ - auctions, crowdloan, + auctions, crowdloan, identity_migrator, mock::{conclude_pvf_checking, validators_public_keys}, paras_registrar, slot_range::SlotRange, @@ -25,16 +25,18 @@ use crate::{ traits::{AuctionStatus, Auctioneer, Leaser, Registrar as RegistrarT}, }; use frame_support::{ - assert_noop, assert_ok, parameter_types, + assert_noop, assert_ok, derive_impl, parameter_types, traits::{ConstU32, Currency, OnFinalize, OnInitialize}, weights::Weight, PalletId, }; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; +use pallet_identity::{self, legacy::IdentityInfo}; use parity_scale_codec::Encode; use primitives::{ BlockNumber, HeadData, Id as ParaId, SessionIndex, ValidationCode, LOWEST_PUBLIC_ID, + MAX_CODE_SIZE, }; use runtime_parachains::{ configuration, origin, paras, shared, Origin as ParaOrigin, ParaLifecycle, @@ -44,9 +46,9 @@ use sp_io::TestExternalities; use sp_keyring::Sr25519Keyring; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup, One}, + traits::{BlakeTwo256, IdentityLookup, One, Verify}, transaction_validity::TransactionPriority, - AccountId32, BuildStorage, + AccountId32, BuildStorage, MultiSignature, }; use sp_std::sync::Arc; @@ -73,21 +75,25 @@ frame_support::construct_runtime!( pub enum Test { // System Stuff - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned}, + System: frame_system, + Balances: pallet_balances, + Babe: pallet_babe, // Parachains Runtime - Configuration: configuration::{Pallet, Call, Storage, Config}, - Paras: paras::{Pallet, Call, Storage, Event, Config}, - ParasShared: shared::{Pallet, Call, Storage}, - ParachainsOrigin: origin::{Pallet, Origin}, + Configuration: configuration, + Paras: paras, + ParasShared: shared, + ParachainsOrigin: origin, // Para Onboarding Pallets - Registrar: paras_registrar::{Pallet, Call, Storage, Event}, - Auctions: auctions::{Pallet, Call, Storage, Event}, - Crowdloan: crowdloan::{Pallet, Call, Storage, Event}, - Slots: slots::{Pallet, Call, Storage, Event}, + Registrar: paras_registrar, + Auctions: auctions, + Crowdloan: crowdloan, + Slots: slots, + + // Migrators + Identity: pallet_identity, + IdentityMigrator: identity_migrator, } ); @@ -109,6 +115,7 @@ parameter_types! { ); } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = BlockWeights; @@ -181,8 +188,8 @@ impl pallet_balances::Config for Test { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -190,7 +197,9 @@ impl configuration::Config for Test { type WeightInfo = configuration::TestWeightInfo; } -impl shared::Config for Test {} +impl shared::Config for Test { + type DisabledValidators = (); +} impl origin::Config for Test {} @@ -205,6 +214,7 @@ impl paras::Config for Test { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } parameter_types! { @@ -273,12 +283,40 @@ impl crowdloan::Config for Test { type WeightInfo = crate::crowdloan::TestWeightInfo; } +impl pallet_identity::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Slashed = (); + type BasicDeposit = ConstU32<100>; + type ByteDeposit = ConstU32<10>; + type SubAccountDeposit = ConstU32<100>; + type MaxSubAccounts = ConstU32<2>; + type IdentityInformation = IdentityInfo>; + type MaxRegistrars = ConstU32<20>; + type RegistrarOrigin = EnsureRoot; + type ForceOrigin = EnsureRoot; + type OffchainSignature = MultiSignature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<100>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; + type WeightInfo = (); +} + +impl identity_migrator::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Reaper = EnsureRoot; + type ReapIdentityHandler = (); + type WeightInfo = crate::identity_migrator::TestWeightInfo; +} + /// Create a new set of test externalities. pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, @@ -892,8 +930,18 @@ fn basic_swap_works() { // Deposit is appropriately taken // ----------------------------------------- para deposit --- crowdloan - assert_eq!(Balances::reserved_balance(&account_id(1)), (500 + 10 * 2 * 1) + 100); - assert_eq!(Balances::reserved_balance(&account_id(2)), 500 + 20 * 2 * 1); + let crowdloan_deposit = 100; + let para_id_deposit = ::ParaDeposit::get(); + let code_deposit = configuration::Pallet::::config().max_code_size * + ::DataDepositPerByte::get(); + + // Para 2000 has a genesis head size of 10. + assert_eq!( + Balances::reserved_balance(&account_id(1)), + crowdloan_deposit + para_id_deposit + code_deposit + 10 + ); + // Para 2001 has a genesis head size of 20. + assert_eq!(Balances::reserved_balance(&account_id(2)), para_id_deposit + code_deposit + 20); assert_eq!(Balances::reserved_balance(&crowdloan_account), total); // Crowdloan is appropriately set assert!(Crowdloan::funds(ParaId::from(2000)).is_some()); @@ -935,8 +983,8 @@ fn basic_swap_works() { // Deregister on-demand parachain assert_ok!(Registrar::deregister(para_origin(2000).into(), ParaId::from(2000))); // Correct deposit is unreserved - assert_eq!(Balances::reserved_balance(&account_id(1)), 100); // crowdloan deposit left over - assert_eq!(Balances::reserved_balance(&account_id(2)), 500 + 20 * 2 * 1); + assert_eq!(Balances::reserved_balance(&account_id(1)), crowdloan_deposit); + assert_eq!(Balances::reserved_balance(&account_id(2)), para_id_deposit + code_deposit + 20); // Crowdloan ownership is swapped assert!(Crowdloan::funds(ParaId::from(2000)).is_none()); assert!(Crowdloan::funds(ParaId::from(2001)).is_some()); @@ -967,7 +1015,7 @@ fn basic_swap_works() { // Dissolve returns the balance of the person who put a deposit for crowdloan assert_ok!(Crowdloan::dissolve(signed(1), ParaId::from(2001))); assert_eq!(Balances::reserved_balance(&account_id(1)), 0); - assert_eq!(Balances::reserved_balance(&account_id(2)), 500 + 20 * 2 * 1); + assert_eq!(Balances::reserved_balance(&account_id(2)), para_id_deposit + code_deposit + 20); // Final deregister sets everything back to the start assert_ok!(Registrar::deregister(para_origin(2001).into(), ParaId::from(2001))); diff --git a/polkadot/runtime/common/src/lib.rs b/polkadot/runtime/common/src/lib.rs index 46a9f7d12cd4149839547df0f3d98d42f60af9e1..bd49d3cccc9cac99f3a11b698629dfd51b847816 100644 --- a/polkadot/runtime/common/src/lib.rs +++ b/polkadot/runtime/common/src/lib.rs @@ -23,6 +23,7 @@ pub mod auctions; pub mod claims; pub mod crowdloan; pub mod elections; +pub mod identity_migrator; pub mod impls; pub mod paras_registrar; pub mod paras_sudo_wrapper; @@ -246,7 +247,8 @@ impl sp_runtime::traits::Convert for U256ToBalance { } /// Macro to set a value (e.g. when using the `parameter_types` macro) to either a production value -/// or to an environment variable or testing value (in case the `fast-runtime` feature is selected). +/// or to an environment variable or testing value (in case the `fast-runtime` feature is selected) +/// or one of two testing values depending on feature. /// Note that the environment variable is evaluated _at compile time_. /// /// Usage: @@ -255,6 +257,8 @@ impl sp_runtime::traits::Convert for U256ToBalance { /// // Note that the env variable version parameter cannot be const. /// pub LaunchPeriod: BlockNumber = prod_or_fast!(7 * DAYS, 1, "KSM_LAUNCH_PERIOD"); /// pub const VotingPeriod: BlockNumber = prod_or_fast!(7 * DAYS, 1 * MINUTES); +/// pub const EpochDuration: BlockNumber = +/// prod_or_fast!(1 * HOURS, "fast-runtime", 1 * MINUTES, "fast-runtime-10m", 10 * MINUTES); /// } /// ``` #[macro_export] diff --git a/polkadot/runtime/common/src/paras_registrar/migration.rs b/polkadot/runtime/common/src/paras_registrar/migration.rs index b767985489d3622e6da01e689609c8913929e78a..f977674a1e4e22254bcac6e68cc7bdf20711a043 100644 --- a/polkadot/runtime/common/src/paras_registrar/migration.rs +++ b/polkadot/runtime/common/src/paras_registrar/migration.rs @@ -60,11 +60,10 @@ impl> OnRuntimeUpgrade } } -pub type VersionCheckedMigrateToV1 = - frame_support::migrations::VersionedMigration< - 0, - 1, - VersionUncheckedMigrateToV1, - super::Pallet, - ::DbWeight, - >; +pub type MigrateToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + VersionUncheckedMigrateToV1, + super::Pallet, + ::DbWeight, +>; diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 8b8c6d89d0185c9eb2b1f620f6809890859b37d8..5b2098388d81cbc0028f9a1062b5ec9302a802ec 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -26,7 +26,7 @@ use frame_support::{ traits::{Currency, Get, ReservableCurrency}, }; use frame_system::{self, ensure_root, ensure_signed}; -use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID}; +use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID, MIN_CODE_SIZE}; use runtime_parachains::{ configuration, ensure_parachain, paras::{self, ParaGenesisArgs, SetGoAhead}, @@ -106,7 +106,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] @@ -182,8 +182,8 @@ pub mod pallet { ParaLocked, /// The ID given for registration has not been reserved. NotReserved, - /// Registering parachain with empty code is not allowed. - EmptyCode, + /// The validation code is invalid. + InvalidCode, /// Cannot perform a parachain slot / lifecycle swap. Check that the state of both paras /// are correct for the swap to work. CannotSwap, @@ -239,7 +239,13 @@ pub mod pallet { /// - `validation_code`: The initial validation code of the parachain/thread. /// /// ## Deposits/Fees - /// The origin signed account must reserve a corresponding deposit for the registration. + /// The account with the originating signature must reserve a deposit. + /// + /// The deposit is required to cover the costs associated with storing the genesis head + /// data and the validation code. + /// This accounts for the potential to store validation code of a size up to the + /// `max_code_size`, as defined in the configuration pallet + /// /// Anything already reserved previously for this para ID is accounted for. /// /// ## Events @@ -651,7 +657,7 @@ impl Pallet { para_kind: ParaKind, ) -> Result<(ParaGenesisArgs, BalanceOf), sp_runtime::DispatchError> { let config = configuration::Pallet::::config(); - ensure!(validation_code.0.len() > 0, Error::::EmptyCode); + ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::::CodeTooLarge); ensure!( genesis_head.0.len() <= config.max_head_data_size as usize, @@ -661,7 +667,7 @@ impl Pallet { let per_byte_fee = T::DataDepositPerByte::get(); let deposit = T::ParaDeposit::get() .saturating_add(per_byte_fee.saturating_mul((genesis_head.0.len() as u32).into())) - .saturating_add(per_byte_fee.saturating_mul((validation_code.0.len() as u32).into())); + .saturating_add(per_byte_fee.saturating_mul(config.max_code_size.into())); Ok((ParaGenesisArgs { genesis_head, validation_code, para_kind }, deposit)) } @@ -699,14 +705,14 @@ mod tests { mock::conclude_pvf_checking, paras_registrar, traits::Registrar as RegistrarTrait, }; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, derive_impl, error::BadOrigin, parameter_types, traits::{ConstU32, OnFinalize, OnInitialize}, }; use frame_system::limits; use pallet_balances::Error as BalancesError; - use primitives::{Balance, BlockNumber, SessionIndex}; + use primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; use runtime_parachains::{configuration, origin, shared}; use sp_core::H256; use sp_io::TestExternalities; @@ -724,13 +730,13 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Configuration: configuration::{Pallet, Call, Storage, Config}, - Parachains: paras::{Pallet, Call, Storage, Config, Event}, - ParasShared: shared::{Pallet, Call, Storage}, - Registrar: paras_registrar::{Pallet, Call, Storage, Event}, - ParachainsOrigin: origin::{Pallet, Origin}, + System: frame_system, + Balances: pallet_balances, + Configuration: configuration, + Parachains: paras, + ParasShared: shared, + Registrar: paras_registrar, + ParachainsOrigin: origin, } ); @@ -751,6 +757,7 @@ mod tests { limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); } + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; @@ -792,12 +799,14 @@ mod tests { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } - impl shared::Config for Test {} + impl shared::Config for Test { + type DisabledValidators = (); + } impl origin::Config for Test {} @@ -812,6 +821,7 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl configuration::Config for Test { @@ -839,7 +849,7 @@ mod tests { configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, @@ -1008,10 +1018,61 @@ mod tests { run_to_session(START_SESSION_INDEX + 2); assert!(Parachains::is_parathread(para_id)); + // Even though the registered validation code has a smaller size than the maximum the + // para manager's deposit is reserved as though they registered the maximum-sized code. + // Consequently, they can upgrade their code to the maximum size at any point without + // additional cost. + let validation_code_deposit = + max_code_size() as BalanceOf * ::DataDepositPerByte::get(); + let head_deposit = 32 * ::DataDepositPerByte::get(); assert_eq!( Balances::reserved_balance(&1), - ::ParaDeposit::get() + - 64 * ::DataDepositPerByte::get() + ::ParaDeposit::get() + head_deposit + validation_code_deposit + ); + }); + } + + #[test] + fn schedule_code_upgrade_validates_code() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); + assert_ok!(Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + + let new_code = test_validation_code(0); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + + let new_code = test_validation_code(max_code_size() as usize + 1); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode ); }); } @@ -1294,7 +1355,7 @@ mod tests { RuntimeOrigin::signed(1), para_id, vec![1; 3].into(), - vec![1, 2, 3].into(), + test_validation_code(32) )); assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); @@ -1457,7 +1518,7 @@ mod benchmarking { use crate::traits::Registrar as RegistrarT; use frame_support::assert_ok; use frame_system::RawOrigin; - use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE}; + use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; use runtime_parachains::{paras, shared, Origin as ParaOrigin}; use sp_runtime::traits::Bounded; @@ -1588,7 +1649,7 @@ mod benchmarking { } schedule_code_upgrade { - let b in 1 .. MAX_CODE_SIZE; + let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; let new_code = ValidationCode(vec![0; b as usize]); let para_id = ParaId::from(1000); }: _(RawOrigin::Root, para_id, new_code) diff --git a/polkadot/runtime/common/src/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 58ca19d0288c4973fc3e7d7f325df31b785d5be6..8de93bd68b02e17f858759baf2850f1a773968bc 100644 --- a/polkadot/runtime/common/src/purchase.rs +++ b/polkadot/runtime/common/src/purchase.rs @@ -484,7 +484,7 @@ mod tests { // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use crate::purchase; use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, + assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{Currency, WithdrawReasons}, }; use sp_runtime::{ @@ -499,10 +499,10 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Vesting: pallet_vesting::{Pallet, Call, Storage, Config, Event}, - Purchase: purchase::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Purchase: purchase, } ); @@ -511,6 +511,8 @@ mod tests { parameter_types! { pub const BlockHashCount: u32 = 250; } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -552,8 +554,8 @@ mod tests { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } @@ -570,6 +572,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 a3efd5bfa30a6c4bd8455834534514712f6458ae..a54aaa53f8c9255752587f741fac794eaf0d47f5 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( @@ -505,7 +505,7 @@ mod tests { use crate::{mock::TestRegistrar, slots}; use ::test_helpers::{dummy_head_data, dummy_validation_code}; - use frame_support::{assert_noop, assert_ok, parameter_types}; + use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use frame_system::EnsureRoot; use pallet_balances; use primitives::BlockNumber; @@ -520,15 +520,17 @@ mod tests { frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Slots: slots::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + Slots: slots, } ); parameter_types! { pub const BlockHashCount: u32 = 250; } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -570,8 +572,8 @@ mod tests { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 4d31c92cdd3a9500fdb3000e7a8f14891dee7087..46d09afcfb2c80215ed7ab18eb7be5750de1fcf6 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -35,13 +35,13 @@ pub trait PriceForMessageDelivery { /// Type used for charging different prices to different destinations type Id; /// Return the assets required to deliver `message` to the given `para` destination. - fn price_for_delivery(id: Self::Id, message: &Xcm<()>) -> MultiAssets; + fn price_for_delivery(id: Self::Id, message: &Xcm<()>) -> Assets; } impl PriceForMessageDelivery for () { type Id = (); - fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> MultiAssets { - MultiAssets::new() + fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets { + Assets::new() } } @@ -49,17 +49,17 @@ pub struct NoPriceForMessageDelivery(PhantomData); impl PriceForMessageDelivery for NoPriceForMessageDelivery { type Id = Id; - fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> MultiAssets { - MultiAssets::new() + fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets { + Assets::new() } } /// Implementation of [`PriceForMessageDelivery`] which returns a fixed price. pub struct ConstantPrice(sp_std::marker::PhantomData); -impl> PriceForMessageDelivery for ConstantPrice { +impl> PriceForMessageDelivery for ConstantPrice { type Id = (); - fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> MultiAssets { + fn price_for_delivery(_: Self::Id, _: &Xcm<()>) -> Assets { T::get() } } @@ -84,7 +84,7 @@ impl, B: Get, M: Get, F: FeeTracker> PriceForMessage { type Id = F::Id; - fn price_for_delivery(id: Self::Id, msg: &Xcm<()>) -> MultiAssets { + fn price_for_delivery(id: Self::Id, msg: &Xcm<()>) -> Assets { let msg_fee = (msg.encoded_size() as u128).saturating_mul(M::get()); let fee_sum = B::get().saturating_add(msg_fee); let amount = F::get_fee_factor(id).saturating_mul_int(fee_sum); @@ -103,11 +103,11 @@ where type Ticket = (HostConfiguration>, ParaId, Vec); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, ) -> SendResult<(HostConfiguration>, ParaId, Vec)> { let d = dest.take().ok_or(MissingArgument)?; - let id = if let MultiLocation { parents: 0, interior: X1(Parachain(id)) } = &d { + let id = if let (0, [Parachain(id)]) = d.unpack() { *id } else { *dest = Some(d); @@ -136,7 +136,7 @@ where } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// `ParaId` parachain (sibling or child). Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -160,11 +160,11 @@ pub struct ToParachainDeliveryHelper< #[cfg(feature = "runtime-benchmarks")] impl< XcmConfig: xcm_executor::Config, - ExistentialDeposit: Get>, + ExistentialDeposit: Get>, PriceForDelivery: PriceForMessageDelivery, Parachain: Get, ToParachainHelper: EnsureForParachain, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParachainDeliveryHelper< XcmConfig, ExistentialDeposit, @@ -174,15 +174,24 @@ impl< > { fn ensure_successful_delivery( - origin_ref: &MultiLocation, - _dest: &MultiLocation, + origin_ref: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, - ) -> (Option, Option) { + ) -> (Option, Option) { use xcm_executor::{ traits::{FeeManager, TransactAsset}, FeesMode, }; + // check if the destination matches the expected `Parachain`. + if let Some(Parachain(para_id)) = dest.first_interior() { + if ParaId::from(*para_id) != Parachain::get().into() { + return (None, None) + } + } else { + return (None, None) + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees @@ -234,7 +243,7 @@ mod tests { parameter_types! { pub const BaseDeliveryFee: u128 = 300_000_000; pub const TransactionByteFee: u128 = 1_000_000; - pub FeeAssetId: AssetId = Concrete(Here.into()); + pub FeeAssetId: AssetId = AssetId(Here.into()); } struct TestFeeTracker; diff --git a/polkadot/runtime/metrics/Cargo.toml b/polkadot/runtime/metrics/Cargo.toml index cfa6bf3dafb2aa02e29d3ee5865aa314d0e7436b..481627542865e1e79e4be790c3ccf46cc5878c07 100644 --- a/polkadot/runtime/metrics/Cargo.toml +++ b/polkadot/runtime/metrics/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "polkadot-runtime-metrics" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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-std = { package = "sp-std", path = "../../../substrate/primitives/std", default-features = false } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } parity-scale-codec = { version = "3.6.1", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } @@ -15,7 +19,7 @@ frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-f bs58 = { version = "0.5.0", default-features = false, features = ["alloc"] } [features] -default = [ "std" ] +default = ["std"] std = [ "bs58/std", "frame-benchmarking?/std", @@ -24,4 +28,4 @@ std = [ "sp-std/std", "sp-tracing/std", ] -runtime-metrics = [ "frame-benchmarking", "sp-tracing/with-tracing" ] +runtime-metrics = ["frame-benchmarking", "sp-tracing/with-tracing"] diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index a395008337323aa01e0fcaf7a1d1d8bddd967b8f..6104014547638dbf13c5b080a280e7cf369293c9 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "polkadot-runtime-parachains" -version = "1.0.0" +version = "7.0.0" +description = "Relay Chain runtime code responsible for Parachains." 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"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", default-features = false, features = ["derive", "alloc"] } +serde = { features = ["alloc", "derive"], workspace = true } derive_more = "0.99.17" bitflags = "1.3.2" @@ -20,18 +24,20 @@ sp-api = { path = "../../../substrate/primitives/api", default-features = false inherents = { package = "sp-inherents", path = "../../../substrate/primitives/inherents", default-features = false } sp-std = { package = "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, features=["serde"] } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false, features = ["serde"] } sp-session = { path = "../../../substrate/primitives/session", default-features = false } -sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features=["serde"] } -sp-core = { path = "../../../substrate/primitives/core", default-features = false, features=["serde"] } +sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features = ["serde"] } +sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] } 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 } @@ -49,7 +55,7 @@ rand = { version = "0.8.5", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } static_assertions = { version = "1.1.0", optional = true } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } -polkadot-runtime-metrics = { path = "../metrics", default-features = false} +polkadot-runtime-metrics = { path = "../metrics", default-features = false } polkadot-core-primitives = { path = "../../core-primitives", default-features = false } [dev-dependencies] @@ -60,12 +66,14 @@ frame-support-test = { path = "../../../substrate/frame/support/test" } sc-keystore = { path = "../../../substrate/client/keystore" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } sp-tracing = { path = "../../../substrate/primitives/tracing" } +sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } thousands = "0.2.0" assert_matches = "1" -serde_json = "1.0.107" +rstest = "0.18.2" +serde_json = { workspace = true, default-features = true } [features] -default = [ "std" ] +default = ["std"] no_std = [] std = [ "bitvec/std", @@ -78,6 +86,7 @@ std = [ "pallet-authorship/std", "pallet-babe/std", "pallet-balances/std", + "pallet-broker/std", "pallet-message-queue/std", "pallet-session/std", "pallet-staking/std", @@ -88,11 +97,14 @@ std = [ "polkadot-parachain-primitives/std", "polkadot-runtime-metrics/std", "primitives/std", + "rand/std", + "rand_chacha/std", "rustc-hex/std", "scale-info/std", "serde/std", "sp-api/std", "sp-application-crypto?/std", + "sp-arithmetic/std", "sp-core/std", "sp-io/std", "sp-keystore", @@ -109,6 +121,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", @@ -129,6 +142,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..e2ba0b4f7ea5d5ca2eb49c903afb764229e0491f --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs @@ -0,0 +1,88 @@ +// 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.scheduler_params.num_cores = self.on_demand_cores; + config.scheduler_params.on_demand_base_fee = self.on_demand_base_fee; + config.scheduler_params.on_demand_fee_variability = self.on_demand_fee_variability; + config.scheduler_params.on_demand_queue_max_size = self.on_demand_max_queue_size; + config.scheduler_params.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..e2da89fadd4800f5de32650178766e1ed85bbd17 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mod.rs @@ -0,0 +1,488 @@ +// 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}, + 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). + }, + } + } + + #[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.scheduler_params.num_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.scheduler_params.num_cores; + config.scheduler_params.num_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..8360e7a78d0a5a155f5b9d63ffae05bb85e44f32 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.scheduler_params.num_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..f8d1a894f0e4d5c619377ba4c34158c8f6d60597 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,11 +63,12 @@ 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.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; + config.scheduler_params.num_cores = self.on_demand_cores; + config.scheduler_params.on_demand_base_fee = self.on_demand_base_fee; + config.scheduler_params.on_demand_fee_variability = self.on_demand_fee_variability; + config.scheduler_params.on_demand_queue_max_size = self.on_demand_max_queue_size; + config.scheduler_params.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 { diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs index 75c29bd6fbe4f79532823468a4c0a596942491a6..bc450dc78129ad7404627e49b9882e2723c09f08 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] @@ -195,10 +201,10 @@ pub mod pallet { let old_traffic = SpotTraffic::::get(); match Self::calculate_spot_traffic( old_traffic, - config.on_demand_queue_max_size, + config.scheduler_params.on_demand_queue_max_size, Self::queue_size(), - config.on_demand_target_queue_utilization, - config.on_demand_fee_variability, + config.scheduler_params.on_demand_target_queue_utilization, + config.scheduler_params.on_demand_fee_variability, ) { Ok(new_traffic) => { // Only update storage on change @@ -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,33 +326,34 @@ 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(); // Calculate spot price - let spot_price: BalanceOf = - traffic.saturating_mul_int(config.on_demand_base_fee.saturated_into::>()); + let spot_price: BalanceOf = traffic.saturating_mul_int( + config.scheduler_params.on_demand_base_fee.saturated_into::>(), + ); // Is the current price higher than `max_amount` 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 +427,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,21 +440,24 @@ 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(); OnDemandQueue::::try_mutate(|queue| { // Abort transaction if queue is too large - ensure!(Self::queue_size() < config.on_demand_queue_max_size, Error::::QueueFull); + ensure!( + Self::queue_size() < config.scheduler_params.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(()) }) @@ -469,13 +476,14 @@ where target: LOG_TARGET, "Failed to fetch the on demand queue size, returning the max size." ); - return config.on_demand_queue_max_size + return config.scheduler_params.on_demand_queue_max_size }, } } /// Getter for the order queue. - pub fn get_queue() -> VecDeque { + #[cfg(test)] + fn get_queue() -> VecDeque { OnDemandQueue::::get() } @@ -523,12 +531,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 +539,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 +577,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..b5f342563e9763e9dba0fa8cbc7afd8e84dc59da 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}, + scheduler::common::{Assignment, AssignmentProvider}, }; -use frame_system::pallet_prelude::BlockNumberFor; + pub use pallet::*; -use primitives::{CoreIndex, Id as ParaId}; #[frame_support::pallet] pub mod pallet { @@ -38,31 +45,25 @@ 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 get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { - AssignmentProviderConfig { - // The next assignment already goes to the same [`ParaId`], no timeout tracking needed. - max_availability_timeouts: 0, - // The next assignment already goes to the same [`ParaId`], this can be any number - // that's high enough to clear the time it takes to clear backing/availability. - ttl: 10u32.into(), - } + fn push_back_assignment(_: Assignment) {} + + #[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..a46e114daeaf458fb6bc985a5bb2ebad951d7e3b --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs @@ -0,0 +1,84 @@ +// 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.scheduler_params.num_cores = self.on_demand_cores; + config.scheduler_params.on_demand_base_fee = self.on_demand_base_fee; + config.scheduler_params.on_demand_fee_variability = self.on_demand_fee_variability; + config.scheduler_params.on_demand_queue_max_size = self.on_demand_max_queue_size; + config.scheduler_params.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..23ec80e66fcc3097983bde32d1ecced0a64743cf 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -18,23 +18,20 @@ use crate::{ configuration, inclusion, initializer, paras, paras::ParaKind, paras_inherent, - scheduler::{ - self, - common::{Assignment, AssignmentProviderConfig}, - CoreOccupied, ParasEntry, - }, + scheduler::{self, common::AssignmentProvider, CoreOccupied, ParasEntry}, session_info, shared, }; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use primitives::{ - collator_signature_payload, AvailabilityBitfield, BackedCandidate, CandidateCommitments, - CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CommittedCandidateReceipt, - CompactStatement, CoreIndex, DisputeStatement, DisputeStatementSet, GroupIndex, HeadData, - Id as ParaId, IndexedVec, InherentData as ParachainsInherentData, InvalidDisputeStatementKind, - PersistedValidationData, SessionIndex, SigningContext, UncheckedSigned, - ValidDisputeStatementKind, ValidationCode, ValidatorId, ValidatorIndex, ValidityAttestation, + collator_signature_payload, vstaging::node_features::FeatureIndex, AvailabilityBitfield, + BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, + CollatorSignature, CommittedCandidateReceipt, CompactStatement, CoreIndex, DisputeStatement, + DisputeStatementSet, GroupIndex, HeadData, Id as ParaId, IndexedVec, + InherentData as ParachainsInherentData, InvalidDisputeStatementKind, PersistedValidationData, + SessionIndex, SigningContext, UncheckedSigned, ValidDisputeStatementKind, ValidationCode, + ValidatorId, ValidatorIndex, ValidityAttestation, }; use sp_core::{sr25519, H256}; use sp_runtime::{ @@ -42,7 +39,11 @@ use sp_runtime::{ traits::{Header as HeaderT, One, TrailingZeroInput, Zero}, RuntimeAppPublic, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::Vec, vec}; +use sp_std::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + prelude::Vec, + vec, +}; fn mock_validation_code() -> ValidationCode { ValidationCode(vec![1, 2, 3]) @@ -86,16 +87,23 @@ pub(crate) struct BenchBuilder { /// Optionally set the number of dispute statements for each candidate. dispute_statements: BTreeMap, /// Session index of for each dispute. Index of slice corresponds to a core, - /// which is offset by the number of entries for `backed_and_concluding_cores`. I.E. if - /// `backed_and_concluding_cores` has 3 entries, the first index of `dispute_sessions` + /// which is offset by the number of entries for `backed_and_concluding_paras`. I.E. if + /// `backed_and_concluding_paras` has 3 entries, the first index of `dispute_sessions` /// will correspond to core index 3. There must be one entry for each core with a dispute /// statement set. dispute_sessions: Vec, - /// Map from core seed to number of validity votes. - backed_and_concluding_cores: BTreeMap, + /// Map from para id to number of validity votes. Core indices are generated based on + /// `elastic_paras` configuration. Each para id in `elastic_paras` gets the + /// specified amount of consecutive cores assigned to it. If a para id is not present + /// in `elastic_paras` it get assigned to a single core. + backed_and_concluding_paras: BTreeMap, + /// Map from para id (seed) to number of chained candidates. + elastic_paras: BTreeMap, /// 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, } @@ -107,6 +115,7 @@ pub(crate) struct Bench { pub(crate) _block_number: BlockNumberFor, } +#[allow(dead_code)] impl BenchBuilder { /// Create a new `BenchBuilder` with some opinionated values that should work with the rest /// of the functions in this implementation. @@ -120,8 +129,10 @@ impl BenchBuilder { max_validators: None, dispute_statements: BTreeMap::new(), dispute_sessions: Default::default(), - backed_and_concluding_cores: Default::default(), + backed_and_concluding_paras: Default::default(), + elastic_paras: Default::default(), code_upgrade: None, + fill_claimqueue: true, _phantom: sp_std::marker::PhantomData::, } } @@ -129,7 +140,7 @@ impl BenchBuilder { /// Set the session index for each dispute statement set (in other words, set the session the /// the dispute statement set's relay chain block is from). Indexes of `dispute_sessions` /// correspond to a core, which is offset by the number of entries for - /// `backed_and_concluding_cores`. I.E. if `backed_and_concluding_cores` cores has 3 entries, + /// `backed_and_concluding_paras`. I.E. if `backed_and_concluding_paras` cores has 3 entries, /// the first index of `dispute_sessions` will correspond to core index 3. /// /// Note that there must be an entry for each core with a dispute statement set. @@ -138,12 +149,19 @@ impl BenchBuilder { self } - /// Set a map from core/para id seed to number of validity votes. - pub(crate) fn set_backed_and_concluding_cores( + /// Set a map from para id seed to number of validity votes. + pub(crate) fn set_backed_and_concluding_paras( mut self, - backed_and_concluding_cores: BTreeMap, + backed_and_concluding_paras: BTreeMap, ) -> Self { - self.backed_and_concluding_cores = backed_and_concluding_cores; + self.backed_and_concluding_paras = backed_and_concluding_paras; + self + } + + /// Set a map from para id seed to number of cores assigned to it. + #[cfg(feature = "runtime-benchmarks")] + pub(crate) fn set_elastic_paras(mut self, elastic_paras: BTreeMap) -> Self { + self.elastic_paras = elastic_paras; self } @@ -194,7 +212,10 @@ impl BenchBuilder { /// Maximum number of validators per core (a.k.a. max validators per group). This value is used /// if none is explicitly set on the builder. pub(crate) fn fallback_max_validators_per_core() -> u32 { - configuration::Pallet::::config().max_validators_per_core.unwrap_or(5) + configuration::Pallet::::config() + .scheduler_params + .max_validators_per_core + .unwrap_or(5) } /// Specify a mapping of core index/ para id to the number of dispute statements for the @@ -225,22 +246,19 @@ 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 { (Self::fallback_max_validators() / 2) + 1 } - /// Create para id, core index, and grab the associated group index from the scheduler pallet. - fn create_indexes(&self, seed: u32) -> (ParaId, CoreIndex, GroupIndex) { - let para_id = ParaId::from(seed); - let core_idx = CoreIndex(seed); - let group_idx = - scheduler::Pallet::::group_assigned_to_core(core_idx, self.block_number).unwrap(); - - (para_id, core_idx, group_idx) - } - fn mock_head_data() -> HeadData { let max_head_size = configuration::Pallet::::config().max_head_data_size; HeadData(vec![0xFF; max_head_size as usize]) @@ -311,7 +329,7 @@ impl BenchBuilder { /// Create an `AvailabilityBitfield` where `concluding` is a map where each key is a core index /// that is concluding and `cores` is the total number of cores in the system. - fn availability_bitvec(concluding: &BTreeMap, cores: u32) -> AvailabilityBitfield { + fn availability_bitvec(concluding: &BTreeMap, cores: usize) -> AvailabilityBitfield { let mut bitfields = bitvec::bitvec![u8, bitvec::order::Lsb0; 0; 0]; for i in 0..cores { if concluding.get(&(i as u32)).is_some() { @@ -342,7 +360,7 @@ impl BenchBuilder { /// /// Note that this must be called at least 2 sessions before the target session as there is a /// n+2 session delay for the scheduled actions to take effect. - fn setup_para_ids(cores: u32) { + fn setup_para_ids(cores: usize) { // make sure parachains exist prior to session change. for i in 0..cores { let para_id = ParaId::from(i as u32); @@ -397,7 +415,10 @@ impl BenchBuilder { mut self, target_session: SessionIndex, validators: Vec<(T::AccountId, ValidatorId)>, - total_cores: u32, + // Total cores used in the scenario + total_cores: usize, + // Additional cores for elastic parachains + extra_cores: usize, ) -> Self { let mut block = 1; for session in 0..=target_session { @@ -432,7 +453,8 @@ impl BenchBuilder { self.validators = Some(validators_shuffled); self.block_number = block_number; self.session = target_session; - assert_eq!(paras::Pallet::::parachains().len(), total_cores as usize); + + assert_eq!(paras::Pallet::::parachains().len(), total_cores - extra_cores); self } @@ -443,13 +465,14 @@ impl BenchBuilder { /// to the cores successfully being freed from the candidates being marked as available. fn create_availability_bitfields( &self, - concluding_cores: &BTreeMap, - total_cores: u32, + concluding_paras: &BTreeMap, + elastic_paras: &BTreeMap, + total_cores: usize, ) -> Vec> { let validators = self.validators.as_ref().expect("must have some validators prior to calling"); - let availability_bitvec = Self::availability_bitvec(concluding_cores, total_cores); + let availability_bitvec = Self::availability_bitvec(concluding_paras, total_cores); let bitfields: Vec> = validators .iter() @@ -466,16 +489,27 @@ impl BenchBuilder { }) .collect(); - for (seed, _) in concluding_cores.iter() { + let mut current_core_idx = 0u32; + + for (seed, _) in concluding_paras.iter() { // make sure the candidates that will be concluding are marked as pending availability. - let (para_id, core_idx, group_idx) = self.create_indexes(*seed); - Self::add_availability( - para_id, - core_idx, - group_idx, - Self::validator_availability_votes_yes(validators.len()), - CandidateHash(H256::from(byte32_slice_from(*seed))), - ); + let para_id = ParaId::from(*seed); + + for _chain_idx in 0..elastic_paras.get(&seed).cloned().unwrap_or(1) { + let core_idx = CoreIndex::from(current_core_idx); + let group_idx = + scheduler::Pallet::::group_assigned_to_core(core_idx, self.block_number) + .unwrap(); + + Self::add_availability( + para_id, + core_idx, + group_idx, + Self::validator_availability_votes_yes(validators.len()), + CandidateHash(H256::from(byte32_slice_from(current_core_idx))), + ); + current_core_idx += 1; + } } bitfields @@ -484,104 +518,143 @@ impl BenchBuilder { /// Create backed candidates for `cores_with_backed_candidates`. You need these cores to be /// scheduled _within_ paras inherent, which requires marking the available bitfields as fully /// available. - /// - `cores_with_backed_candidates` Mapping of `para_id`/`core_idx`/`group_idx` seed to number - /// of + /// - `cores_with_backed_candidates` Mapping of `para_id` seed to number of /// validity votes. fn create_backed_candidates( &self, cores_with_backed_candidates: &BTreeMap, + elastic_paras: &BTreeMap, includes_code_upgrade: Option, ) -> Vec> { let validators = self.validators.as_ref().expect("must have some validators prior to calling"); let config = configuration::Pallet::::config(); + let mut current_core_idx = 0u32; cores_with_backed_candidates .iter() - .map(|(seed, num_votes)| { + .flat_map(|(seed, num_votes)| { assert!(*num_votes <= validators.len() as u32); - let (para_id, _core_idx, group_idx) = self.create_indexes(*seed); - - // This generates a pair and adds it to the keystore, returning just the public. - let collator_public = CollatorId::generate_pair(None); - let header = Self::header(self.block_number); - let relay_parent = header.hash(); - let head_data = Self::mock_head_data(); - let persisted_validation_data_hash = PersistedValidationData:: { - parent_head: head_data.clone(), - relay_parent_number: self.relay_parent_number(), - relay_parent_storage_root: Default::default(), - max_pov_size: config.max_pov_size, - } - .hash(); - - let pov_hash = Default::default(); - let validation_code_hash = mock_validation_code().hash(); - let payload = collator_signature_payload( - &relay_parent, - ¶_id, - &persisted_validation_data_hash, - &pov_hash, - &validation_code_hash, - ); - let signature = collator_public.sign(&payload).unwrap(); - - // Set the head data so it can be used while validating the signatures on the - // candidate receipt. - paras::Pallet::::heads_insert(¶_id, head_data.clone()); - - let mut past_code_meta = paras::ParaPastCodeMeta::>::default(); - past_code_meta.note_replacement(0u32.into(), 0u32.into()); - - let group_validators = scheduler::Pallet::::group_validators(group_idx).unwrap(); - - let candidate = CommittedCandidateReceipt:: { - descriptor: CandidateDescriptor:: { - para_id, - relay_parent, - collator: collator_public, - persisted_validation_data_hash, - pov_hash, - erasure_root: Default::default(), - signature, - para_head: head_data.hash(), - validation_code_hash, - }, - commitments: CandidateCommitments:: { - upward_messages: Default::default(), - horizontal_messages: Default::default(), - new_validation_code: includes_code_upgrade - .map(|v| ValidationCode(vec![42u8; v as usize])), - head_data, - processed_downward_messages: 0, - hrmp_watermark: self.relay_parent_number(), - }, - }; - - let candidate_hash = candidate.hash(); - - let validity_votes: Vec<_> = group_validators - .iter() - .take(*num_votes as usize) - .map(|val_idx| { - let public = validators.get(*val_idx).unwrap(); - let sig = UncheckedSigned::::benchmark_sign( - public, - CompactStatement::Valid(candidate_hash), - &self.signing_context(), - *val_idx, + + let para_id = ParaId::from(*seed); + let mut prev_head = None; + // How many chained candidates we want to build ? + (0..elastic_paras.get(&seed).cloned().unwrap_or(1)) + .map(|chain_idx| { + let core_idx = CoreIndex::from(current_core_idx); + // Advance core index. + current_core_idx += 1; + let group_idx = scheduler::Pallet::::group_assigned_to_core( + core_idx, + self.block_number, ) - .benchmark_signature(); + .unwrap(); - ValidityAttestation::Explicit(sig.clone()) - }) - .collect(); + // This generates a pair and adds it to the keystore, returning just the + // public. + let collator_public = CollatorId::generate_pair(None); + let header = Self::header(self.block_number); + let relay_parent = header.hash(); - BackedCandidate:: { - candidate, - validity_votes, - validator_indices: bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], - } + // Set the head data so it can be used while validating the signatures on + // the candidate receipt. + let mut head_data = Self::mock_head_data(); + + if chain_idx == 0 { + // Only first parahead of the chain needs to be set in storage. + paras::Pallet::::heads_insert(¶_id, head_data.clone()); + } else { + // Make each candidate head data unique to avoid cycles. + head_data.0[0] = chain_idx; + } + + let persisted_validation_data_hash = PersistedValidationData:: { + // To form a chain we set parent head to previous block if any, or + // default to what is in storage already setup. + parent_head: prev_head.take().unwrap_or(head_data.clone()), + relay_parent_number: self.relay_parent_number(), + relay_parent_storage_root: Default::default(), + max_pov_size: config.max_pov_size, + } + .hash(); + + prev_head = Some(head_data.clone()); + + let pov_hash = Default::default(); + let validation_code_hash = mock_validation_code().hash(); + let payload = collator_signature_payload( + &relay_parent, + ¶_id, + &persisted_validation_data_hash, + &pov_hash, + &validation_code_hash, + ); + let signature = collator_public.sign(&payload).unwrap(); + + let mut past_code_meta = + paras::ParaPastCodeMeta::>::default(); + past_code_meta.note_replacement(0u32.into(), 0u32.into()); + + let group_validators = + scheduler::Pallet::::group_validators(group_idx).unwrap(); + + let candidate = CommittedCandidateReceipt:: { + descriptor: CandidateDescriptor:: { + para_id, + relay_parent, + collator: collator_public, + persisted_validation_data_hash, + pov_hash, + erasure_root: Default::default(), + signature, + para_head: head_data.hash(), + validation_code_hash, + }, + commitments: CandidateCommitments:: { + upward_messages: Default::default(), + horizontal_messages: Default::default(), + new_validation_code: includes_code_upgrade + .map(|v| ValidationCode(vec![42u8; v as usize])), + head_data, + processed_downward_messages: 0, + hrmp_watermark: self.relay_parent_number(), + }, + }; + + let candidate_hash = candidate.hash(); + + let validity_votes: Vec<_> = group_validators + .iter() + .take(*num_votes as usize) + .map(|val_idx| { + let public = validators.get(*val_idx).unwrap(); + let sig = UncheckedSigned::::benchmark_sign( + public, + CompactStatement::Valid(candidate_hash), + &self.signing_context(), + *val_idx, + ) + .benchmark_signature(); + + ValidityAttestation::Explicit(sig.clone()) + }) + .collect(); + + // Check if the elastic scaling bit is set, if so we need to supply the core + // index in the generated candidate. + let core_idx = configuration::Pallet::::config() + .node_features + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|_the_bit| core_idx); + + BackedCandidate::::new( + candidate, + validity_votes, + bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], + core_idx, + ) + }) + .collect::>() }) .collect() } @@ -598,6 +671,8 @@ impl BenchBuilder { self.validators.as_ref().expect("must have some validators prior to calling"); let dispute_sessions = dispute_sessions.as_ref(); + let mut current_core_idx = start; + (start..last) .map(|seed| { let dispute_session_idx = (seed - start) as usize; @@ -606,7 +681,14 @@ impl BenchBuilder { .cloned() .unwrap_or(self.target_session); - let (para_id, core_idx, group_idx) = self.create_indexes(seed); + let para_id = ParaId::from(seed); + let core_idx = CoreIndex::from(current_core_idx); + current_core_idx +=1; + + let group_idx = + scheduler::Pallet::::group_assigned_to_core(core_idx, self.block_number) + .unwrap(); + let candidate_hash = CandidateHash(H256::from(byte32_slice_from(seed))); let relay_parent = H256::from(byte32_slice_from(seed)); @@ -636,80 +718,142 @@ 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() } /// Build a scenario for testing or benchmarks. /// - /// Note that this API only allows building scenarios where the `backed_and_concluding_cores` + /// Note that this API only allows building scenarios where the `backed_and_concluding_paras` /// are mutually exclusive with the cores for disputes. So - /// `backed_and_concluding_cores.len() + dispute_sessions.len()` must be less than the max + /// `backed_and_concluding_paras.len() + dispute_sessions.len()` must be less than the max /// number of cores. pub(crate) fn build(self) -> Bench { // Make sure relevant storage is cleared. This is just to get the asserts to work when // running tests because it seems the storage is not cleared in between. #[allow(deprecated)] - inclusion::PendingAvailabilityCommitments::::remove_all(None); - #[allow(deprecated)] 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() as usize; + + let extra_cores = self + .elastic_paras + .values() + .map(|count| *count as usize) + .sum::() + .saturating_sub(self.elastic_paras.len() as usize); + let used_cores = - (self.dispute_sessions.len() + self.backed_and_concluding_cores.len()) as u32; - assert!(used_cores <= cores); + self.dispute_sessions.len() + self.backed_and_concluding_paras.len() + extra_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.scheduler_params.num_cores = used_cores as u32; + }); let validator_ids = Self::generate_validator_pairs(self.max_validators()); let target_session = SessionIndex::from(self.target_session); - let builder = self.setup_session(target_session, validator_ids, used_cores); + let builder = self.setup_session(target_session, validator_ids, used_cores, extra_cores); - let bitfields = - builder.create_availability_bitfields(&builder.backed_and_concluding_cores, used_cores); - let backed_candidates = builder - .create_backed_candidates(&builder.backed_and_concluding_cores, builder.code_upgrade); + let bitfields = builder.create_availability_bitfields( + &builder.backed_and_concluding_paras, + &builder.elastic_paras, + used_cores, + ); + let backed_candidates = builder.create_backed_candidates( + &builder.backed_and_concluding_paras, + &builder.elastic_paras, + builder.code_upgrade, + ); let disputes = builder.create_disputes( - builder.backed_and_concluding_cores.len() as u32, - used_cores, + builder.backed_and_concluding_paras.len() as u32, + used_cores as u32, builder.dispute_sessions.as_slice(), ); + let mut disputed_cores = (builder.backed_and_concluding_paras.len() as u32.. + used_cores as u32) + .into_iter() + .map(|idx| (idx, 0)) + .collect::>(); + + let mut all_cores = builder.backed_and_concluding_paras.clone(); + all_cores.append(&mut disputed_cores); - assert_eq!( - inclusion::PendingAvailabilityCommitments::::iter().count(), - used_cores as usize, - ); assert_eq!(inclusion::PendingAvailability::::iter().count(), used_cores as usize,); // Mark all the used cores as occupied. We expect that there are - // `backed_and_concluding_cores` that are pending availability and that there are - // `used_cores - backed_and_concluding_cores ` which are about to be disputed. + // `backed_and_concluding_paras` that are pending availability and that there are + // `used_cores - backed_and_concluding_paras ` which are about to be disputed. let now = >::block_number() + One::one(); - let cores = (0..used_cores) - .into_iter() - .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, - )) + + let mut core_idx = 0u32; + let elastic_paras = &builder.elastic_paras; + // Assign potentially multiple cores to same parachains, + let cores = all_cores + .iter() + .flat_map(|(para_id, _)| { + (0..elastic_paras.get(¶_id).cloned().unwrap_or(1)) + .map(|_para_local_core_idx| { + let ttl = configuration::Pallet::::config().scheduler_params.ttl; + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(core_idx), + ParaId::from(*para_id), + ); + core_idx += 1; + CoreOccupied::Paras(ParasEntry::new(assignment, now + ttl)) + }) + .collect::>>() }) - .collect(); + .collect::>>(); + scheduler::AvailabilityCores::::set(cores); + core_idx = 0u32; + if fill_claimqueue { + let cores = all_cores + .keys() + .flat_map(|para_id| { + (0..elastic_paras.get(¶_id).cloned().unwrap_or(1)) + .map(|_para_local_core_idx| { + let ttl = configuration::Pallet::::config().scheduler_params.ttl; + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(core_idx), + ParaId::from(*para_id), + ); + + let entry = ( + CoreIndex(core_idx), + [ParasEntry::new(assignment, now + ttl)].into(), + ); + core_idx += 1; + entry + }) + .collect::>)>>() + }) + .collect::>>>(); + + scheduler::ClaimQueue::::set(cores); + } + Bench:: { data: ParachainsInherentData { bitfields, diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index a65fda370488a5285e3c751a5a618a00a1c20ef1..364a15215d38c5eae477b3a73987a43d0216278b 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -26,9 +26,9 @@ use polkadot_parachain_primitives::primitives::{ MAX_HORIZONTAL_MESSAGE_NUM, MAX_UPWARD_MESSAGE_NUM, }; use primitives::{ + 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}; use sp_std::prelude::*; @@ -42,6 +42,7 @@ mod benchmarking; pub mod migration; pub use pallet::*; +use primitives::vstaging::SchedulerParams; const LOG_TARGET: &str = "runtime::configuration"; @@ -56,6 +57,7 @@ const LOG_TARGET: &str = "runtime::configuration"; serde::Serialize, serde::Deserialize, )] +#[serde(deny_unknown_fields)] pub struct HostConfiguration { // NOTE: This structure is used by parachains via merkle proofs. Therefore, this struct // requires special treatment. @@ -116,9 +118,9 @@ pub struct HostConfiguration { /// been completed. /// /// Note, there are situations in which `expected_at` in the past. For example, if - /// [`paras_availability_period`](Self::paras_availability_period) is less than the delay set - /// by this field or if PVF pre-check took more time than the delay. In such cases, the upgrade - /// is further at the earliest possible time determined by + /// [`paras_availability_period`](SchedulerParams::paras_availability_period) is less than the + /// delay set by this field or if PVF pre-check took more time than the delay. In such cases, + /// the upgrade is further at the earliest possible time determined by /// [`minimum_validation_upgrade_delay`](Self::minimum_validation_upgrade_delay). /// /// The rationale for this delay has to do with relay-chain reversions. In case there is an @@ -170,48 +172,7 @@ 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, - /// 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. - pub on_demand_queue_max_size: u32, - /// The target utilization of the spot price queue in percentages. - pub on_demand_target_queue_utilization: Perbill, - /// How quickly the fee rises in reaction to increased utilization. - /// The lower the number the slower the increase. - pub on_demand_fee_variability: Perbill, - /// The minimum amount needed to claim a slot in the spot pricing queue. - pub on_demand_base_fee: Balance, - /// The number of blocks an on demand claim stays in the scheduler's claimqueue before getting - /// cleared. This number should go reasonably higher than the number of blocks in the async - /// backing lookahead. - pub on_demand_ttl: BlockNumber, - /// How often parachain groups should be rotated across parachains. - /// - /// Must be non-zero. - pub group_rotation_frequency: BlockNumber, - /// The minimum availability period, in blocks. - /// - /// This is the minimum amount of blocks after a core became occupied that validators have time - /// to make the block available. - /// - /// This value only has effect on group rotations. If backers backed something at the end of - /// their rotation, the occupied core affects the backing group that comes afterwards. We limit - /// the effect one backing group can have on the next to `paras_availability_period` blocks. - /// - /// Within a group rotation there is no timeout as backers are only affecting themselves. - /// - /// Must be at least 1. With a value of 1, the previous group will not be able to negatively - /// affect the following group at the expense of a tight availability timeline at group - /// rotation boundaries. - pub paras_availability_period: BlockNumber, - /// The amount of blocks ahead to schedule paras. - pub scheduling_lookahead: u32, - /// The maximum number of validators to have per core. - /// - /// `None` means no maximum. - pub max_validators_per_core: Option, + /// The maximum number of validators to use for parachain consensus, period. /// /// `None` means no maximum. @@ -255,11 +216,17 @@ pub struct HostConfiguration { /// scheduled. This number is controlled by this field. /// /// This value should be greater than - /// [`paras_availability_period`](Self::paras_availability_period). + /// [`paras_availability_period`](SchedulerParams::paras_availability_period). pub minimum_validation_upgrade_delay: BlockNumber, /// The minimum number of valid backing statements required to consider a parachain candidate /// backable. pub minimum_backing_votes: u32, + /// Node features enablement. + pub node_features: NodeFeatures, + /// Params used by approval-voting + pub approval_voting_params: ApprovalVotingParams, + /// Scheduler parameters + pub scheduler_params: SchedulerParams, } impl> Default for HostConfiguration { @@ -269,19 +236,13 @@ impl> Default for HostConfiguration> Default for HostConfiguration { ZeroMinimumBackingVotes, /// `executor_params` are inconsistent. InconsistentExecutorParams { inner: ExecutorParamError }, + /// TTL should be bigger than lookahead + LookaheadExceedsTTL, } impl HostConfiguration @@ -365,11 +326,11 @@ where pub fn check_consistency(&self) -> Result<(), InconsistentError> { use InconsistentError::*; - if self.group_rotation_frequency.is_zero() { + if self.scheduler_params.group_rotation_frequency.is_zero() { return Err(ZeroGroupRotationFrequency) } - if self.paras_availability_period.is_zero() { + if self.scheduler_params.paras_availability_period.is_zero() { return Err(ZeroParasAvailabilityPeriod) } @@ -391,10 +352,11 @@ where return Err(MaxPovSizeExceedHardLimit { max_pov_size: self.max_pov_size }) } - if self.minimum_validation_upgrade_delay <= self.paras_availability_period { + if self.minimum_validation_upgrade_delay <= self.scheduler_params.paras_availability_period + { return Err(MinimumValidationUpgradeDelayLessThanChainAvailabilityPeriod { minimum_validation_upgrade_delay: self.minimum_validation_upgrade_delay.clone(), - paras_availability_period: self.paras_availability_period.clone(), + paras_availability_period: self.scheduler_params.paras_availability_period.clone(), }) } @@ -439,6 +401,10 @@ where return Err(InconsistentExecutorParams { inner }) } + if self.scheduler_params.ttl < self.scheduler_params.lookahead.into() { + return Err(LookaheadExceedsTTL) + } + Ok(()) } @@ -462,6 +428,8 @@ pub trait WeightInfo { fn set_hrmp_open_request_ttl() -> Weight; fn set_config_with_executor_params() -> Weight; fn set_config_with_perbill() -> Weight; + fn set_node_feature() -> Weight; + fn set_config_with_scheduler_params() -> Weight; } pub struct TestWeightInfo; @@ -487,26 +455,35 @@ impl WeightInfo for TestWeightInfo { fn set_config_with_perbill() -> Weight { Weight::MAX } + fn set_node_feature() -> Weight { + Weight::MAX + } + fn set_config_with_scheduler_params() -> Weight { + Weight::MAX + } } #[frame_support::pallet] pub mod pallet { use super::*; - /// The current storage version. + /// The in-code storage version. /// - /// v0-v1: - /// v1-v2: - /// v2-v3: - /// v3-v4: - /// v4-v5: - /// + - /// + - /// v5-v6: (remove UMP dispatch queue) - /// v6-v7: - /// v7-v8: - /// v8-v9: - const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); + /// v0-v1: + /// v1-v2: + /// v2-v3: + /// v3-v4: + /// v4-v5: + /// + + /// + + /// v5-v6: (remove UMP dispatch queue) + /// v6-v7: + /// v7-v8: + /// v8-v9: + /// v9-v10: + /// v10-11: + /// v11-12: + const STORAGE_VERSION: StorageVersion = StorageVersion::new(12); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -651,29 +628,30 @@ 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. + /// Set the max number of times a claim may timeout on a core before it is abandoned #[pallet::call_index(7)] #[pallet::weight(( T::WeightInfo::set_config_with_u32(), DispatchClass::Operational, ))] - pub fn set_on_demand_retries(origin: OriginFor, new: u32) -> DispatchResult { + pub fn set_max_availability_timeouts(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_retries = new; + config.scheduler_params.max_availability_timeouts = new; }) } @@ -689,7 +667,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.group_rotation_frequency = new; + config.scheduler_params.group_rotation_frequency = new; }) } @@ -705,7 +683,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.paras_availability_period = new; + config.scheduler_params.paras_availability_period = new; }) } @@ -718,7 +696,7 @@ pub mod pallet { pub fn set_scheduling_lookahead(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.scheduling_lookahead = new; + config.scheduler_params.lookahead = new; }) } @@ -734,7 +712,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.max_validators_per_core = new; + config.scheduler_params.max_validators_per_core = new; }) } @@ -1126,7 +1104,7 @@ pub mod pallet { pub fn set_on_demand_base_fee(origin: OriginFor, new: Balance) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_base_fee = new; + config.scheduler_params.on_demand_base_fee = new; }) } @@ -1139,7 +1117,7 @@ pub mod pallet { pub fn set_on_demand_fee_variability(origin: OriginFor, new: Perbill) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_fee_variability = new; + config.scheduler_params.on_demand_fee_variability = new; }) } @@ -1152,7 +1130,7 @@ pub mod pallet { pub fn set_on_demand_queue_max_size(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_queue_max_size = new; + config.scheduler_params.on_demand_queue_max_size = new; }) } /// Set the on demand (parathreads) fee variability. @@ -1167,7 +1145,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_target_queue_utilization = new; + config.scheduler_params.on_demand_target_queue_utilization = new; }) } /// Set the on demand (parathreads) ttl in the claimqueue. @@ -1179,9 +1157,10 @@ pub mod pallet { pub fn set_on_demand_ttl(origin: OriginFor, new: BlockNumberFor) -> DispatchResult { ensure_root(origin)?; Self::schedule_config_update(|config| { - config.on_demand_ttl = new; + config.scheduler_params.ttl = new; }) } + /// Set the minimum backing votes threshold. #[pallet::call_index(52)] #[pallet::weight(( @@ -1194,6 +1173,67 @@ pub mod pallet { config.minimum_backing_votes = new; }) } + + /// Set/Unset a node feature. + #[pallet::call_index(53)] + #[pallet::weight(( + T::WeightInfo::set_node_feature(), + DispatchClass::Operational + ))] + pub fn set_node_feature(origin: OriginFor, index: u8, value: bool) -> DispatchResult { + ensure_root(origin)?; + + Self::schedule_config_update(|config| { + let index = usize::from(index); + if config.node_features.len() <= index { + config.node_features.resize(index + 1, false); + } + 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; + }) + } + + /// Set scheduler-params. + #[pallet::call_index(55)] + #[pallet::weight(( + T::WeightInfo::set_config_with_scheduler_params(), + DispatchClass::Operational, + ))] + pub fn set_scheduler_params( + origin: OriginFor, + new: SchedulerParams>, + ) -> DispatchResult { + ensure_root(origin)?; + Self::schedule_config_update(|config| { + config.scheduler_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.scheduler_params.num_cores = new; + }) + } } #[pallet::hooks] @@ -1332,7 +1372,7 @@ impl Pallet { let base_config_consistent = base_config.check_consistency().is_ok(); // Now, we need to decide what the new configuration should be. - // We also move the `base_config` to `new_config` to empahsize that the base config was + // We also move the `base_config` to `new_config` to emphasize that the base config was // destroyed by the `updater`. updater(&mut base_config); let new_config = base_config; diff --git a/polkadot/runtime/parachains/src/configuration/benchmarking.rs b/polkadot/runtime/parachains/src/configuration/benchmarking.rs index d9d11ab56e496980e8090a99ed88452a351e970e..882b5aab096ad2f8227aa7ad0efaddfd2078c3ad 100644 --- a/polkadot/runtime/parachains/src/configuration/benchmarking.rs +++ b/polkadot/runtime/parachains/src/configuration/benchmarking.rs @@ -17,7 +17,7 @@ use crate::configuration::*; use frame_benchmarking::{benchmarks, BenchmarkError, BenchmarkResult}; use frame_system::RawOrigin; -use primitives::{ExecutorParam, ExecutorParams, PvfExecTimeoutKind, PvfPrepTimeoutKind}; +use primitives::{ExecutorParam, ExecutorParams, PvfExecKind, PvfPrepKind}; use sp_runtime::traits::One; benchmarks! { @@ -41,14 +41,18 @@ benchmarks! { ExecutorParam::StackNativeMax(256 * 1024 * 1024), ExecutorParam::WasmExtBulkMemory, ExecutorParam::PrecheckingMaxMemory(2 * 1024 * 1024 * 1024), - ExecutorParam::PvfPrepTimeout(PvfPrepTimeoutKind::Precheck, 60_000), - ExecutorParam::PvfPrepTimeout(PvfPrepTimeoutKind::Lenient, 360_000), - ExecutorParam::PvfExecTimeout(PvfExecTimeoutKind::Backing, 2_000), - ExecutorParam::PvfExecTimeout(PvfExecTimeoutKind::Approval, 12_000), + ExecutorParam::PvfPrepTimeout(PvfPrepKind::Precheck, 60_000), + ExecutorParam::PvfPrepTimeout(PvfPrepKind::Prepare, 360_000), + ExecutorParam::PvfExecTimeout(PvfExecKind::Backing, 2_000), + ExecutorParam::PvfExecTimeout(PvfExecKind::Approval, 12_000), ][..])) set_config_with_perbill {}: set_on_demand_fee_variability(RawOrigin::Root, Perbill::from_percent(100)) + set_node_feature{}: set_node_feature(RawOrigin::Root, 255, true) + + set_config_with_scheduler_params {} : set_scheduler_params(RawOrigin::Root, SchedulerParams::default()) + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(Default::default()), diff --git a/polkadot/runtime/parachains/src/configuration/migration.rs b/polkadot/runtime/parachains/src/configuration/migration.rs index 26f8a85b496d5a76385c6c85b52f66d719d88c82..87b30b177e735bdc1ec618a6e403c4c74a801533 100644 --- a/polkadot/runtime/parachains/src/configuration/migration.rs +++ b/polkadot/runtime/parachains/src/configuration/migration.rs @@ -16,6 +16,9 @@ //! A module that is responsible for migration of storage. +pub mod v10; +pub mod v11; +pub mod v12; 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 new file mode 100644 index 0000000000000000000000000000000000000000..cf228610e5c9cec1dde8cfb31880fa2b1f68821f --- /dev/null +++ b/polkadot/runtime/parachains/src/configuration/migration/v10.rs @@ -0,0 +1,381 @@ +// 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::{Config, Pallet}; +use frame_support::{pallet_prelude::*, traits::Defensive, weights::Weight}; +use frame_system::pallet_prelude::BlockNumberFor; +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, +} + +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::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V9HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V9HostConfiguration>)>, + OptionQuery, + >; +} + +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, + >; +} + +pub struct VersionUncheckedMigrateToV10(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for VersionUncheckedMigrateToV10 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV10"); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + migrate_to_v10::() + } + + #[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 MigrateToV10"); + ensure!( + Pallet::::on_chain_storage_version() >= StorageVersion::new(10), + "Storage version should be >= 10 after the migration" + ); + + Ok(()) + } +} + +pub type MigrateToV10 = frame_support::migrations::VersionedMigration< + 9, + 10, + VersionUncheckedMigrateToV10, + Pallet, + ::DbWeight, +>; + +// 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] +fn translate(pre: V9HostConfiguration>) -> V10HostConfiguration> { + V10HostConfiguration { + 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, + on_demand_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 : NodeFeatures::EMPTY + } +} + +fn migrate_to_v10() -> Weight { + let v9 = v9::ActiveConfig::::get() + .defensive_proof("Could not decode old config") + .unwrap_or_default(); + let v10 = translate::(v9); + v10::ActiveConfig::::set(Some(v10)); + + // Allowed to be empty. + let pending_v9 = v9::PendingConfigs::::get().unwrap_or_default(); + let mut pending_v10 = Vec::new(); + + for (session, v9) in pending_v9.into_iter() { + let v10 = translate::(v9); + pending_v10.push((session, v10)); + } + v10::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 super::*; + use crate::mock::{new_test_ext, Test}; + use primitives::LEGACY_MIN_BACKING_VOTES; + + #[test] + fn v10_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![" + 0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c90180969800000000000000000000000000050000001400000004000000010000000101000000000600000064000000020000001900000000000000020000000200000002000000050000000200000000" + ]; + + let v10 = + V10HostConfiguration::::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!(v10.max_code_size, 3_145_728); + assert_eq!(v10.validation_upgrade_cooldown, 2); + assert_eq!(v10.max_pov_size, 5_242_880); + assert_eq!(v10.hrmp_channel_max_message_size, 1_048_576); + assert_eq!(v10.n_delay_tranches, 25); + assert_eq!(v10.minimum_validation_upgrade_delay, 5); + assert_eq!(v10.group_rotation_frequency, 20); + assert_eq!(v10.on_demand_cores, 0); + assert_eq!(v10.on_demand_base_fee, 10_000_000); + assert_eq!(v10.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES); + assert_eq!(v10.node_features, NodeFeatures::EMPTY); + } + + // Test that `migrate_to_v10`` correctly applies the `translate` function to current and pending + // configs. + #[test] + fn test_migrate_to_v10() { + // 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 v9 = V9HostConfiguration:: { + 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, v9.clone())); + pending_configs.push((300, v9.clone())); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v9 version in the state. + v9::ActiveConfig::::set(Some(v9.clone())); + v9::PendingConfigs::::set(Some(pending_configs)); + + migrate_to_v10::(); + + let v10 = translate::(v9); + let mut configs_to_check = v10::PendingConfigs::::get().unwrap(); + configs_to_check.push((0, v10::ActiveConfig::::get().unwrap())); + + for (_, config) in configs_to_check { + assert_eq!(config, v10); + assert_eq!(config.node_features, NodeFeatures::EMPTY); + } + }); + } + + // Test that migration doesn't panic in case there're no pending configurations upgrades in + // pallet's storage. + #[test] + fn test_migrate_to_v10_no_pending() { + let v9 = V9HostConfiguration::::default(); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v9 version in the state. + v9::ActiveConfig::::set(Some(v9)); + // Ensure there're no pending configs. + v9::PendingConfigs::::set(None); + + // Shouldn't fail. + migrate_to_v10::(); + }); + } +} 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..f6e0e0431640ba271f592759308cc0d07efcfa71 --- /dev/null +++ b/polkadot/runtime/parachains/src/configuration/migration/v11.rs @@ -0,0 +1,438 @@ +// 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, AsyncBackingParams, ExecutorParams, SessionIndex, + LEGACY_MIN_BACKING_VOTES, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, +}; +use sp_std::vec::Vec; + +use frame_support::traits::OnRuntimeUpgrade; +use polkadot_core_primitives::Balance; +use primitives::vstaging::NodeFeatures; +use sp_arithmetic::Perbill; + +use super::v10::V10HostConfiguration; + +#[derive(Clone, Encode, PartialEq, Decode, Debug)] +pub struct V11HostConfiguration { + 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 coretime_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, + pub approval_voting_params: ApprovalVotingParams, +} + +impl> Default for V11HostConfiguration { + 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(), + coretime_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(), + approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 1 }, + 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 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.clone())); + 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 (_, v11) 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.on_demand_cores , v11.coretime_cores); + assert_eq!(v10.on_demand_retries , v11.on_demand_retries); + assert_eq!(v10.group_rotation_frequency , v11.group_rotation_frequency); + assert_eq!(v10.paras_availability_period , v11.paras_availability_period); + 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/v12.rs b/polkadot/runtime/parachains/src/configuration/migration/v12.rs new file mode 100644 index 0000000000000000000000000000000000000000..4295a79893e8ee52632d54772bd108b4de37cc3f --- /dev/null +++ b/polkadot/runtime/parachains/src/configuration/migration/v12.rs @@ -0,0 +1,349 @@ +// 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, migration::v11::V11HostConfiguration, Config, Pallet}; +use frame_support::{ + migrations::VersionedMigration, + pallet_prelude::*, + traits::{Defensive, OnRuntimeUpgrade}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use primitives::vstaging::SchedulerParams; +use sp_core::Get; +use sp_staking::SessionIndex; +use sp_std::vec::Vec; + +type V12HostConfiguration = configuration::HostConfiguration; + +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, + >; +} + +mod v12 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V12HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V12HostConfiguration>)>, + OptionQuery, + >; +} + +pub type MigrateToV12 = VersionedMigration< + 11, + 12, + UncheckedMigrateToV12, + Pallet, + ::DbWeight, +>; + +pub struct UncheckedMigrateToV12(sp_std::marker::PhantomData); + +impl OnRuntimeUpgrade for UncheckedMigrateToV12 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV12"); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV12 started"); + let weight_consumed = migrate_to_v12::(); + + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV12 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 MigrateToV12"); + ensure!( + StorageVersion::get::>() >= 12, + "Storage version should be >= 12 after the migration" + ); + + Ok(()) + } +} + +fn migrate_to_v12() -> 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: V11HostConfiguration>| -> + V12HostConfiguration> + { + V12HostConfiguration { + 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, + 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, + minimum_backing_votes : pre.minimum_backing_votes, + node_features : pre.node_features, + approval_voting_params : pre.approval_voting_params, + scheduler_params: SchedulerParams { + group_rotation_frequency : pre.group_rotation_frequency, + paras_availability_period : pre.paras_availability_period, + max_validators_per_core : pre.max_validators_per_core, + lookahead : pre.scheduling_lookahead, + num_cores : pre.coretime_cores, + max_availability_timeouts : pre.on_demand_retries, + on_demand_queue_max_size : pre.on_demand_queue_max_size, + on_demand_target_queue_utilization : pre.on_demand_target_queue_utilization, + on_demand_fee_variability : pre.on_demand_fee_variability, + on_demand_base_fee : pre.on_demand_base_fee, + ttl : pre.on_demand_ttl, + } + } + }; + + let v11 = v11::ActiveConfig::::get() + .defensive_proof("Could not decode old config") + .unwrap_or_default(); + let v12 = translate(v11); + v12::ActiveConfig::::set(Some(v12)); + + // Allowed to be empty. + let pending_v11 = v11::PendingConfigs::::get().unwrap_or_default(); + let mut pending_v12 = Vec::new(); + + for (session, v11) in pending_v11.into_iter() { + let v12 = translate(v11); + pending_v12.push((session, v12)); + } + v12::PendingConfigs::::set(Some(pending_v12.clone())); + + let num_configs = (pending_v12.len() + 1) as u64; + T::DbWeight::get().reads_writes(num_configs, num_configs) +} + +#[cfg(test)] +mod tests { + use primitives::LEGACY_MIN_BACKING_VOTES; + use sp_arithmetic::Perbill; + + use super::*; + use crate::mock::{new_test_ext, Test}; + + #[test] + fn v12_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![ + "0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000060000006400000002000000190000000000000002000000020000000200000005000000020000000001000000140000000400000001010000000100000001000000000000001027000080b2e60e80c3c9018096980000000000000000000000000005000000" + ]; + + let v12 = + V12HostConfiguration::::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!(v12.max_code_size, 3_145_728); + assert_eq!(v12.validation_upgrade_cooldown, 2); + assert_eq!(v12.max_pov_size, 5_242_880); + assert_eq!(v12.hrmp_channel_max_message_size, 1_048_576); + assert_eq!(v12.n_delay_tranches, 25); + assert_eq!(v12.minimum_validation_upgrade_delay, 5); + assert_eq!(v12.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES); + assert_eq!(v12.approval_voting_params.max_approval_coalesce_count, 1); + assert_eq!(v12.scheduler_params.group_rotation_frequency, 20); + assert_eq!(v12.scheduler_params.paras_availability_period, 4); + assert_eq!(v12.scheduler_params.lookahead, 1); + assert_eq!(v12.scheduler_params.num_cores, 1); + assert_eq!(v12.scheduler_params.max_availability_timeouts, 0); + assert_eq!(v12.scheduler_params.on_demand_queue_max_size, 10_000); + assert_eq!( + v12.scheduler_params.on_demand_target_queue_utilization, + Perbill::from_percent(25) + ); + assert_eq!(v12.scheduler_params.on_demand_fee_variability, Perbill::from_percent(3)); + assert_eq!(v12.scheduler_params.on_demand_base_fee, 10_000_000); + assert_eq!(v12.scheduler_params.ttl, 5); + } + + #[test] + fn test_migrate_to_v12() { + // 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 v11 = V11HostConfiguration:: { + needed_approvals: 69, + paras_availability_period: 55, + hrmp_recipient_deposit: 1337, + max_pov_size: 1111, + minimum_validation_upgrade_delay: 20, + on_demand_ttl: 3, + on_demand_retries: 10, + ..Default::default() + }; + + let mut pending_configs = Vec::new(); + pending_configs.push((100, v11.clone())); + pending_configs.push((300, v11.clone())); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v10 version in the state. + v11::ActiveConfig::::set(Some(v11.clone())); + v11::PendingConfigs::::set(Some(pending_configs)); + + migrate_to_v12::(); + + let v12 = v12::ActiveConfig::::get().unwrap(); + assert_eq!(v12.approval_voting_params.max_approval_coalesce_count, 1); + + let mut configs_to_check = v12::PendingConfigs::::get().unwrap(); + configs_to_check.push((0, v12.clone())); + + for (_, v12) in configs_to_check { + #[rustfmt::skip] + { + assert_eq!(v11.max_code_size , v12.max_code_size); + assert_eq!(v11.max_head_data_size , v12.max_head_data_size); + assert_eq!(v11.max_upward_queue_count , v12.max_upward_queue_count); + assert_eq!(v11.max_upward_queue_size , v12.max_upward_queue_size); + assert_eq!(v11.max_upward_message_size , v12.max_upward_message_size); + assert_eq!(v11.max_upward_message_num_per_candidate , v12.max_upward_message_num_per_candidate); + assert_eq!(v11.hrmp_max_message_num_per_candidate , v12.hrmp_max_message_num_per_candidate); + assert_eq!(v11.validation_upgrade_cooldown , v12.validation_upgrade_cooldown); + assert_eq!(v11.validation_upgrade_delay , v12.validation_upgrade_delay); + assert_eq!(v11.max_pov_size , v12.max_pov_size); + assert_eq!(v11.max_downward_message_size , v12.max_downward_message_size); + assert_eq!(v11.hrmp_max_parachain_outbound_channels , v12.hrmp_max_parachain_outbound_channels); + assert_eq!(v11.hrmp_sender_deposit , v12.hrmp_sender_deposit); + assert_eq!(v11.hrmp_recipient_deposit , v12.hrmp_recipient_deposit); + assert_eq!(v11.hrmp_channel_max_capacity , v12.hrmp_channel_max_capacity); + assert_eq!(v11.hrmp_channel_max_total_size , v12.hrmp_channel_max_total_size); + assert_eq!(v11.hrmp_max_parachain_inbound_channels , v12.hrmp_max_parachain_inbound_channels); + assert_eq!(v11.hrmp_channel_max_message_size , v12.hrmp_channel_max_message_size); + assert_eq!(v11.code_retention_period , v12.code_retention_period); + assert_eq!(v11.max_validators , v12.max_validators); + assert_eq!(v11.dispute_period , v12.dispute_period); + assert_eq!(v11.no_show_slots , v12.no_show_slots); + assert_eq!(v11.n_delay_tranches , v12.n_delay_tranches); + assert_eq!(v11.zeroth_delay_tranche_width , v12.zeroth_delay_tranche_width); + assert_eq!(v11.needed_approvals , v12.needed_approvals); + assert_eq!(v11.relay_vrf_modulo_samples , v12.relay_vrf_modulo_samples); + assert_eq!(v11.pvf_voting_ttl , v12.pvf_voting_ttl); + assert_eq!(v11.minimum_validation_upgrade_delay , v12.minimum_validation_upgrade_delay); + assert_eq!(v11.async_backing_params.allowed_ancestry_len, v12.async_backing_params.allowed_ancestry_len); + assert_eq!(v11.async_backing_params.max_candidate_depth , v12.async_backing_params.max_candidate_depth); + assert_eq!(v11.executor_params , v12.executor_params); + assert_eq!(v11.minimum_backing_votes , v12.minimum_backing_votes); + assert_eq!(v11.group_rotation_frequency , v12.scheduler_params.group_rotation_frequency); + assert_eq!(v11.paras_availability_period , v12.scheduler_params.paras_availability_period); + assert_eq!(v11.max_validators_per_core , v12.scheduler_params.max_validators_per_core); + assert_eq!(v11.scheduling_lookahead , v12.scheduler_params.lookahead); + assert_eq!(v11.coretime_cores , v12.scheduler_params.num_cores); + assert_eq!(v11.on_demand_retries , v12.scheduler_params.max_availability_timeouts); + assert_eq!(v11.on_demand_queue_max_size , v12.scheduler_params.on_demand_queue_max_size); + assert_eq!(v11.on_demand_target_queue_utilization , v12.scheduler_params.on_demand_target_queue_utilization); + assert_eq!(v11.on_demand_fee_variability , v12.scheduler_params.on_demand_fee_variability); + assert_eq!(v11.on_demand_base_fee , v12.scheduler_params.on_demand_base_fee); + assert_eq!(v11.on_demand_ttl , v12.scheduler_params.ttl); + }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. + } + }); + } + + // Test that migration doesn't panic in case there are no pending configurations upgrades in + // pallet's storage. + #[test] + fn test_migrate_to_v12_no_pending() { + let v11 = V11HostConfiguration::::default(); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v10 version in the state. + v11::ActiveConfig::::set(Some(v11)); + // Ensure there are no pending configs. + v12::PendingConfigs::::set(None); + + // Shouldn't fail. + migrate_to_v12::(); + }); + } +} 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/migration/v9.rs b/polkadot/runtime/parachains/src/configuration/migration/v9.rs index 64d71e628f4ead4ee207c6f74b24dd42684e0fca..ca4bbd9dacef3fb645c41a060798e0298a0efc27 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v9.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v9.rs @@ -23,14 +23,116 @@ use frame_support::{ weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; -use primitives::{SessionIndex, LEGACY_MIN_BACKING_VOTES}; +use primitives::{ + 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::v8::V8HostConfiguration; -type V9HostConfiguration = configuration::HostConfiguration; +/// All configuration of the runtime with respect to paras. +#[derive(Clone, Encode, Decode, Debug)] +pub struct V9HostConfiguration { + 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, +} + +impl> Default for V9HostConfiguration { + 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, + } + } +} mod v8 { use super::*; @@ -145,11 +247,11 @@ 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 : 10_000u32, -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(), +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 : LEGACY_MIN_BACKING_VOTES } }; diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index ea39628c95876a92b9072bd5adc18584e48df0b5..254511231cac1729981706775bb9f844ca9f1c5c 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -16,6 +16,7 @@ use super::*; use crate::mock::{new_test_ext, Configuration, ParasShared, RuntimeOrigin, Test}; +use bitvec::{bitvec, prelude::Lsb0}; use frame_support::{assert_err, assert_noop, assert_ok}; fn on_new_session(session_index: SessionIndex) -> (HostConfiguration, HostConfiguration) { @@ -37,7 +38,7 @@ fn default_is_consistent() { fn scheduled_session_is_two_sessions_from_now() { new_test_ext(Default::default()).execute_with(|| { // The logic here is really tested only with scheduled_session = 2. It should work - // with other values, but that should receive a more rigorious testing. + // with other values, but that should receive a more rigorous testing. on_new_session(1); assert_eq!(Configuration::scheduled_session(), 3); }); @@ -135,7 +136,7 @@ fn pending_next_session_but_we_upgrade_once_more() { // update. assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), @@ -178,7 +179,7 @@ fn scheduled_session_config_update_while_next_session_pending() { assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); assert_ok!(Configuration::set_code_retention_period(RuntimeOrigin::root(), 98)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), @@ -225,8 +226,11 @@ fn invariants() { ); ActiveConfig::::put(HostConfiguration { - paras_availability_period: 10, minimum_validation_upgrade_delay: 11, + scheduler_params: SchedulerParams { + paras_availability_period: 10, + ..Default::default() + }, ..Default::default() }); assert_err!( @@ -282,12 +286,6 @@ fn setting_pending_config_members() { max_code_size: 100_000, max_pov_size: 1024, max_head_data_size: 1_000, - on_demand_cores: 2, - on_demand_retries: 5, - group_rotation_frequency: 20, - paras_availability_period: 10, - scheduling_lookahead: 3, - max_validators_per_core: None, max_validators: None, dispute_period: 239, dispute_post_conclusion_acceptance_period: 10, @@ -312,12 +310,22 @@ fn setting_pending_config_members() { pvf_voting_ttl: 3, minimum_validation_upgrade_delay: 20, executor_params: Default::default(), - on_demand_queue_max_size: 10_000u32, - 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, + approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 1 }, minimum_backing_votes: 5, + node_features: bitvec![u8, Lsb0; 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], + scheduler_params: SchedulerParams { + group_rotation_frequency: 20, + paras_availability_period: 10, + max_validators_per_core: None, + lookahead: 3, + num_cores: 2, + max_availability_timeouts: 5, + on_demand_queue_max_size: 10_000u32, + on_demand_base_fee: 10_000_000u128, + on_demand_fee_variability: Perbill::from_percent(3), + on_demand_target_queue_utilization: Perbill::from_percent(25), + ttl: 5u32, + }, }; Configuration::set_validation_upgrade_cooldown( @@ -339,13 +347,19 @@ 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) - .unwrap(); - Configuration::set_on_demand_retries(RuntimeOrigin::root(), new_config.on_demand_retries) - .unwrap(); + Configuration::set_coretime_cores( + RuntimeOrigin::root(), + new_config.scheduler_params.num_cores, + ) + .unwrap(); + Configuration::set_max_availability_timeouts( + RuntimeOrigin::root(), + new_config.scheduler_params.max_availability_timeouts, + ) + .unwrap(); Configuration::set_group_rotation_frequency( RuntimeOrigin::root(), - new_config.group_rotation_frequency, + new_config.scheduler_params.group_rotation_frequency, ) .unwrap(); // This comes out of order to satisfy the validity criteria for the chain and thread @@ -357,17 +371,17 @@ fn setting_pending_config_members() { .unwrap(); Configuration::set_paras_availability_period( RuntimeOrigin::root(), - new_config.paras_availability_period, + new_config.scheduler_params.paras_availability_period, ) .unwrap(); Configuration::set_scheduling_lookahead( RuntimeOrigin::root(), - new_config.scheduling_lookahead, + new_config.scheduler_params.lookahead, ) .unwrap(); Configuration::set_max_validators_per_core( RuntimeOrigin::root(), - new_config.max_validators_per_core, + new_config.scheduler_params.max_validators_per_core, ) .unwrap(); Configuration::set_max_validators(RuntimeOrigin::root(), new_config.max_validators) @@ -473,6 +487,12 @@ fn setting_pending_config_members() { new_config.minimum_backing_votes, ) .unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 1, true).unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 1, true).unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 3, true).unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 10, true).unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 10, false).unwrap(); + Configuration::set_node_feature(RuntimeOrigin::root(), 11, true).unwrap(); assert_eq!(PendingConfigs::::get(), vec![(shared::SESSION_DELAY, new_config)],); }) 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..03fecc58570fba65df79ca48930acb3346d3f556 --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -0,0 +1,291 @@ +// 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::v4::{send_xcm, Instruction, Junction, Location, 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 ignore 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::v4::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.scheduler_params.num_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, + "There should have been enqueued at least one DMP messages." + ); + + Ok(()) + } + } + + // Migrate to Coretime. + // + // NOTE: Also migrates `num_cores` config value in configuration::ActiveConfig. + fn migrate_to_coretime< + T: Config, + SendXcm: xcm::v4::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(); + // num_cores was on_demand_cores until now: + for on_demand in 0..config.scheduler_params.num_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.scheduler_params.num_cores + legacy_count; + configuration::ActiveConfig::::mutate(|c| { + c.scheduler_params.num_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.scheduler_params.num_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::v4::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().scheduler_params.num_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, + }); + + let reservation_content = message_content.clone().chain(reservations).collect(); + let pool_content = message_content.clone().chain(pool).collect(); + let leases_content = message_content.clone().chain(leases).collect(); + let set_core_count_content = message_content.clone().chain(set_core_count).collect(); + + let messages = vec![ + Xcm(reservation_content), + Xcm(pool_content), + Xcm(leases_content), + Xcm(set_core_count_content), + ]; + + for message in messages { + send_xcm::( + Location::new(0, 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..eb9646d7e869d35763a5b0cdf8a408de66126b78 --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -0,0 +1,252 @@ +// 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::v4::{send_xcm, Instruction, Junction, Location, OriginKind, SendXcm, WeightLimit, 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.scheduler_params.num_cores; + let new_core_count = notification.new_config.scheduler_params.num_cores; + if new_core_count != old_core_count { + let core_count: u16 = new_core_count.saturated_into(); + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + mk_coretime_call(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)), + ]); + if let Err(err) = send_xcm::( + Location::new(0, [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(170_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..da95b060c14a3fad03e98f5e2a75ac54eef1928c 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::{ @@ -379,7 +379,7 @@ pub mod pallet { type WeightInfo: WeightInfo; } - /// The current storage version. + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] @@ -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/hrmp/benchmarking.rs b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs index 2cb49c88d437cb3e0a4c01a40d343a179316ec9d..c6baf2f30cf58279b58b644df76e5ea255f7ec4f 100644 --- a/polkadot/runtime/parachains/src/hrmp/benchmarking.rs +++ b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{ paras::{Pallet as Paras, ParaKind, ParachainsCache}, shared::Pallet as Shared, }; -use frame_benchmarking::{impl_benchmark_test_suite, v2::*, whitelisted_caller}; +use frame_benchmarking::{v2::*, whitelisted_caller}; use frame_support::{assert_ok, traits::Currency}; type BalanceOf = diff --git a/polkadot/runtime/parachains/src/hrmp/tests.rs b/polkadot/runtime/parachains/src/hrmp/tests.rs index 4fc0b0b448a50658758d4f76e46c7034d2edce01..7e7b67c8059dbf4ed9e2c4fc1cebb04efd44c89d 100644 --- a/polkadot/runtime/parachains/src/hrmp/tests.rs +++ b/polkadot/runtime/parachains/src/hrmp/tests.rs @@ -185,11 +185,14 @@ fn force_open_channel_works() { register_parachain(para_a); register_parachain(para_b); + let para_a_free_balance = + ::Currency::free_balance(¶_a.into_account_truncating()); let para_b_free_balance = ::Currency::free_balance(¶_b.into_account_truncating()); run_to_block(5, Some(vec![4, 5])); Hrmp::force_open_hrmp_channel(RuntimeOrigin::root(), para_a, para_b, 2, 8).unwrap(); + Hrmp::force_open_hrmp_channel(RuntimeOrigin::root(), para_b, para_a, 2, 8).unwrap(); Hrmp::assert_storage_consistency_exhaustive(); assert!(System::events().iter().any(|record| record.event == MockEvent::Hrmp(Event::HrmpChannelForceOpened { @@ -198,17 +201,30 @@ fn force_open_channel_works() { proposed_max_capacity: 2, proposed_max_message_size: 8 }))); + assert!(System::events().iter().any(|record| record.event == + MockEvent::Hrmp(Event::HrmpChannelForceOpened { + sender: para_b, + recipient: para_a, + proposed_max_capacity: 2, + proposed_max_message_size: 8 + }))); // Advance to a block 6, but without session change. That means that the channel has // not been created yet. run_to_block(6, None); assert!(!channel_exists(para_a, para_b)); + assert!(!channel_exists(para_b, para_a)); Hrmp::assert_storage_consistency_exhaustive(); // Now let the session change happen and thus open the channel. run_to_block(8, Some(vec![8])); assert!(channel_exists(para_a, para_b)); - // Because para_a is a system chain, para_b's free balance should not have changed. + assert!(channel_exists(para_b, para_a)); + // Because para_a is a system chain, their free balances should not have changed. + assert_eq!( + ::Currency::free_balance(¶_a.into_account_truncating()), + para_a_free_balance + ); assert_eq!( ::Currency::free_balance(¶_b.into_account_truncating()), para_b_free_balance @@ -216,6 +232,51 @@ fn force_open_channel_works() { }); } +#[test] +fn force_open_channel_without_free_balance_works() { + let para_a = 1.into(); + let para_b = 2003.into(); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // We need both A & B to be registered and live parachains, but they should not have any + // balance in their sovereign accounts. Even without any balance, the channel opening should + // still be successful. + register_parachain_with_balance(para_a, 0); + register_parachain_with_balance(para_b, 0); + + run_to_block(5, Some(vec![4, 5])); + Hrmp::force_open_hrmp_channel(RuntimeOrigin::root(), para_a, para_b, 2, 8).unwrap(); + Hrmp::force_open_hrmp_channel(RuntimeOrigin::root(), para_b, para_a, 2, 8).unwrap(); + Hrmp::assert_storage_consistency_exhaustive(); + assert!(System::events().iter().any(|record| record.event == + MockEvent::Hrmp(Event::HrmpChannelForceOpened { + sender: para_a, + recipient: para_b, + proposed_max_capacity: 2, + proposed_max_message_size: 8 + }))); + assert!(System::events().iter().any(|record| record.event == + MockEvent::Hrmp(Event::HrmpChannelForceOpened { + sender: para_b, + recipient: para_a, + proposed_max_capacity: 2, + proposed_max_message_size: 8 + }))); + + // Advance to a block 6, but without session change. That means that the channel has + // not been created yet. + run_to_block(6, None); + assert!(!channel_exists(para_a, para_b)); + assert!(!channel_exists(para_b, para_a)); + Hrmp::assert_storage_consistency_exhaustive(); + + // Now let the session change happen and thus open the channel. + run_to_block(8, Some(vec![8])); + assert!(channel_exists(para_a, para_b)); + assert!(channel_exists(para_b, para_a)); + }); +} + #[test] fn force_open_channel_works_with_existing_request() { let para_a = 2001.into(); diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index e34286d750ac1c89d83f1c33e606e08947f6002c..16e2e93b5617f2c85bbb308ff5d057c7ed99db9c 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -30,7 +30,7 @@ use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; use frame_support::{ defensive, pallet_prelude::*, - traits::{Defensive, EnqueueMessage}, + traits::{Defensive, EnqueueMessage, Footprint, QueueFootprint}, BoundedSlice, }; use frame_system::pallet_prelude::*; @@ -47,10 +47,7 @@ use scale_info::TypeInfo; use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating}; #[cfg(feature = "std")] use sp_std::fmt; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - prelude::*, -}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; pub use pallet::*; @@ -231,7 +228,7 @@ pub enum AggregateMessageOrigin { /// Identifies a UMP queue inside the `MessageQueue` pallet. /// -/// It is written in verbose form since future variants like `Loopback` and `Bridged` are already +/// It is written in verbose form since future variants like `Here` and `Bridged` are already /// forseeable. #[derive(Encode, Decode, Clone, MaxEncodedLen, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub enum UmpQueueId { @@ -601,18 +598,16 @@ impl Pallet { /// scheduled cores. If these conditions are not met, the execution of the function fails. pub(crate) fn process_candidates( allowed_relay_parents: &AllowedRelayParentsTracker>, - candidates: Vec>, - scheduled: &BTreeMap, + candidates: Vec<(BackedCandidate, CoreIndex)>, group_validators: GV, + core_index_enabled: bool, ) -> Result, DispatchError> where GV: Fn(GroupIndex) -> Option>, { let now = >::block_number(); - ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); - - if scheduled.is_empty() { + if candidates.is_empty() { return Ok(ProcessedCandidates::default()) } @@ -648,7 +643,7 @@ impl Pallet { // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. - for (candidate_idx, backed_candidate) in candidates.iter().enumerate() { + for (candidate_idx, (backed_candidate, core_index)) in candidates.iter().enumerate() { let relay_parent_hash = backed_candidate.descriptor().relay_parent; let para_id = backed_candidate.descriptor().para_id; @@ -663,7 +658,7 @@ impl Pallet { let relay_parent_number = match check_ctx.verify_backed_candidate( &allowed_relay_parents, candidate_idx, - backed_candidate, + backed_candidate.candidate(), )? { Err(FailedToCreatePVD) => { log::debug!( @@ -679,11 +674,22 @@ impl Pallet { Ok(rpn) => rpn, }; - let para_id = backed_candidate.descriptor().para_id; + let (validator_indices, _) = + backed_candidate.validator_indices_and_core_index(core_index_enabled); + + log::debug!( + target: LOG_TARGET, + "Candidate {:?} on {:?}, + core_index_enabled = {}", + backed_candidate.hash(), + core_index, + core_index_enabled + ); + + check_assignment_in_order(core_index)?; + let mut backers = bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; - let core_idx = *scheduled.get(¶_id).ok_or(Error::::UnscheduledCandidate)?; - check_assignment_in_order(core_idx)?; ensure!( >::get(¶_id).is_none() && >::get(¶_id).is_none(), @@ -694,7 +700,7 @@ impl Pallet { // assigned to core at block `N + 1`. Thus, `relay_parent_number + 1` // will always land in the current session. let group_idx = >::group_assigned_to_core( - core_idx, + *core_index, relay_parent_number + One::one(), ) .ok_or_else(|| { @@ -711,7 +717,9 @@ impl Pallet { // check the signatures in the backing and that it is a majority. { let maybe_amount_validated = primitives::check_candidate_backing( - &backed_candidate, + backed_candidate.candidate().hash(), + backed_candidate.validity_votes(), + validator_indices, &signing_context, group_vals.len(), |intra_group_vi| { @@ -738,16 +746,15 @@ impl Pallet { let mut backer_idx_and_attestation = Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( - backed_candidate.validator_indices.count_ones(), + validator_indices.count_ones(), ); let candidate_receipt = backed_candidate.receipt(); - for ((bit_idx, _), attestation) in backed_candidate - .validator_indices + for ((bit_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) { let val_idx = group_vals.get(bit_idx).expect("this query succeeded above; qed"); @@ -760,7 +767,7 @@ impl Pallet { } core_indices_and_backers.push(( - (core_idx, para_id), + (*core_index, para_id), backers, group_idx, relay_parent_number, @@ -772,7 +779,7 @@ impl Pallet { // one more sweep for actually writing to storage. let core_indices = core_indices_and_backers.iter().map(|(c, ..)| *c).collect(); - for (candidate, (core, backers, group, relay_parent_number)) in + for ((candidate, _), (core, backers, group, relay_parent_number)) in candidates.into_iter().zip(core_indices_and_backers) { let para_id = candidate.descriptor().para_id; @@ -782,16 +789,18 @@ impl Pallet { bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; Self::deposit_event(Event::::CandidateBacked( - candidate.candidate.to_plain(), - candidate.candidate.commitments.head_data.clone(), + candidate.candidate().to_plain(), + candidate.candidate().commitments.head_data.clone(), core.0, group, )); - let candidate_hash = candidate.candidate.hash(); + let candidate_hash = candidate.candidate().hash(); - let (descriptor, commitments) = - (candidate.candidate.descriptor, candidate.candidate.commitments); + let (descriptor, commitments) = ( + candidate.candidate().descriptor.clone(), + candidate.candidate().commitments.clone(), + ); >::insert( ¶_id, @@ -925,7 +934,7 @@ impl Pallet { pub(crate) fn relay_dispatch_queue_size(para_id: ParaId) -> (u32, u32) { let fp = T::MessageQueue::footprint(AggregateMessageOrigin::Ump(UmpQueueId::Para(para_id))); - (fp.count as u32, fp.size as u32) + (fp.storage.count as u32, fp.storage.size as u32) } /// Check that all the upward messages sent by a candidate pass the acceptance criteria. @@ -1149,10 +1158,11 @@ impl AcceptanceCheckErr { impl OnQueueChanged for Pallet { // Write back the remaining queue capacity into `relay_dispatch_queue_remaining_capacity`. - fn on_queue_changed(origin: AggregateMessageOrigin, count: u64, size: u64) { + fn on_queue_changed(origin: AggregateMessageOrigin, fp: QueueFootprint) { let para = match origin { AggregateMessageOrigin::Ump(UmpQueueId::Para(p)) => p, }; + let QueueFootprint { storage: Footprint { count, size }, .. } = fp; let (count, size) = (count.saturated_into(), size.saturated_into()); // TODO paritytech/polkadot#6283: Remove all usages of `relay_dispatch_queue_size` #[allow(deprecated)] @@ -1194,10 +1204,10 @@ impl CandidateCheckContext { &self, allowed_relay_parents: &AllowedRelayParentsTracker>, candidate_idx: usize, - backed_candidate: &BackedCandidate<::Hash>, + backed_candidate_receipt: &CommittedCandidateReceipt<::Hash>, ) -> Result, FailedToCreatePVD>, Error> { - let para_id = backed_candidate.descriptor().para_id; - let relay_parent = backed_candidate.descriptor().relay_parent; + let para_id = backed_candidate_receipt.descriptor().para_id; + let relay_parent = backed_candidate_receipt.descriptor().relay_parent; // Check that the relay-parent is one of the allowed relay-parents. let (relay_parent_storage_root, relay_parent_number) = { @@ -1222,13 +1232,13 @@ impl CandidateCheckContext { let expected = persisted_validation_data.hash(); ensure!( - expected == backed_candidate.descriptor().persisted_validation_data_hash, + expected == backed_candidate_receipt.descriptor().persisted_validation_data_hash, Error::::ValidationDataHashMismatch, ); } ensure!( - backed_candidate.descriptor().check_collator_signature().is_ok(), + backed_candidate_receipt.descriptor().check_collator_signature().is_ok(), Error::::NotCollatorSigned, ); @@ -1236,25 +1246,25 @@ impl CandidateCheckContext { // A candidate for a parachain without current validation code is not scheduled. .ok_or_else(|| Error::::UnscheduledCandidate)?; ensure!( - backed_candidate.descriptor().validation_code_hash == validation_code_hash, + backed_candidate_receipt.descriptor().validation_code_hash == validation_code_hash, Error::::InvalidValidationCodeHash, ); ensure!( - backed_candidate.descriptor().para_head == - backed_candidate.candidate.commitments.head_data.hash(), + backed_candidate_receipt.descriptor().para_head == + backed_candidate_receipt.commitments.head_data.hash(), Error::::ParaHeadMismatch, ); if let Err(err) = self.check_validation_outputs( para_id, relay_parent_number, - &backed_candidate.candidate.commitments.head_data, - &backed_candidate.candidate.commitments.new_validation_code, - backed_candidate.candidate.commitments.processed_downward_messages, - &backed_candidate.candidate.commitments.upward_messages, - BlockNumberFor::::from(backed_candidate.candidate.commitments.hrmp_watermark), - &backed_candidate.candidate.commitments.horizontal_messages, + &backed_candidate_receipt.commitments.head_data, + &backed_candidate_receipt.commitments.new_validation_code, + backed_candidate_receipt.commitments.processed_downward_messages, + &backed_candidate_receipt.commitments.upward_messages, + BlockNumberFor::::from(backed_candidate_receipt.commitments.hrmp_watermark), + &backed_candidate_receipt.commitments.horizontal_messages, ) { log::debug!( target: LOG_TARGET, @@ -1329,6 +1339,6 @@ impl QueueFootprinter for Pallet { type Origin = UmpQueueId; fn message_count(origin: Self::Origin) -> u64 { - T::MessageQueue::footprint(AggregateMessageOrigin::Ump(origin)).count + T::MessageQueue::footprint(AggregateMessageOrigin::Ump(origin)).storage.count } } diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 6bb731671f6f8afbb350dff2a2821f6ab31da5a0..3fe7d7f0c7d4698c6b8301a41dca552e9c067509 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -47,10 +47,10 @@ 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.scheduler_params.num_cores = 1; config.max_code_size = 0b100000; config.max_head_data_size = 0b100000; - config.group_rotation_frequency = u32::MAX; + config.scheduler_params.group_rotation_frequency = u32::MAX; config } @@ -120,6 +120,7 @@ pub(crate) fn back_candidate( keystore: &KeystorePtr, signing_context: &SigningContext, kind: BackingKind, + core_index: Option, ) -> BackedCandidate { let mut validator_indices = bitvec::bitvec![u8, BitOrderLsb0; 0; group.len()]; let threshold = effective_minimum_backing_votes( @@ -155,15 +156,20 @@ pub(crate) fn back_candidate( validity_votes.push(ValidityAttestation::Explicit(signature).into()); } - let backed = BackedCandidate { candidate, validity_votes, validator_indices }; + let backed = + BackedCandidate::new(candidate, validity_votes, validator_indices.clone(), core_index); - let successfully_backed = - primitives::check_candidate_backing(&backed, signing_context, group.len(), |i| { - Some(validators[group[i].0 as usize].public().into()) - }) - .ok() - .unwrap_or(0) >= - threshold; + let successfully_backed = primitives::check_candidate_backing( + backed.candidate().hash(), + backed.validity_votes(), + validator_indices.as_bitslice(), + signing_context, + group.len(), + |i| Some(validators[group[i].0 as usize].public().into()), + ) + .ok() + .unwrap_or(0) >= + threshold; match kind { BackingKind::Unanimous | BackingKind::Threshold => assert!(successfully_backed), @@ -218,7 +224,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().scheduler_params.num_cores as usize } fn default_bitfield() -> AvailabilityBitfield { @@ -380,7 +386,7 @@ fn collect_pending_cleans_up_pending() { (thread_a, ParaKind::Parathread), ]; let mut config = genesis_config(paras); - config.configuration.config.group_rotation_frequency = 3; + config.configuration.config.scheduler_params.group_rotation_frequency = 3; new_test_ext(config).execute_with(|| { let default_candidate = TestCandidateBuilder::default().build(); >::insert( @@ -919,38 +925,16 @@ fn candidate_checks() { let thread_a_assignment = (thread_a, CoreIndex::from(2)); let allowed_relay_parents = default_allowed_relay_parent_tracker(); - // unscheduled candidate. - { - let mut candidate = TestCandidateBuilder { - para_id: chain_a, - relay_parent: System::parent_hash(), - pov_hash: Hash::repeat_byte(1), - persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), - hrmp_watermark: RELAY_PARENT_NUM, - ..Default::default() - } - .build(); - collator_sign_candidate(Sr25519Keyring::One, &mut candidate); - - let backed = back_candidate( - candidate, - &validators, - group_validators(GroupIndex::from(0)).unwrap().as_ref(), - &keystore, - &signing_context, - BackingKind::Threshold, - ); - - assert_noop!( - ParaInclusion::process_candidates( - &allowed_relay_parents, - vec![backed], - &[chain_b_assignment].into_iter().collect(), - &group_validators, - ), - Error::::UnscheduledCandidate - ); - } + // no candidates. + assert_eq!( + ParaInclusion::process_candidates( + &allowed_relay_parents, + vec![], + &group_validators, + false + ), + Ok(ProcessedCandidates::default()) + ); // candidates out of order. { @@ -984,6 +968,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -993,15 +978,16 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); // out-of-order manifests as unscheduled. assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::ScheduledOutOfOrder ); @@ -1027,14 +1013,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Lacking, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InsufficientBacking ); @@ -1075,6 +1062,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1084,14 +1072,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::DisallowedRelayParent ); @@ -1122,14 +1111,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[thread_a_assignment].into_iter().collect(), + vec![(backed, thread_a_assignment.1)], &group_validators, + false ), Error::::NotCollatorSigned ); @@ -1156,6 +1146,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let candidate = TestCandidateBuilder::default().build(); @@ -1177,9 +1168,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1212,14 +1203,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1233,7 +1225,7 @@ fn candidate_checks() { para_id: chain_a, relay_parent: System::parent_hash(), pov_hash: Hash::repeat_byte(1), - new_validation_code: Some(vec![5, 6, 7, 8].into()), + new_validation_code: Some(dummy_validation_code()), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), hrmp_watermark: RELAY_PARENT_NUM, ..Default::default() @@ -1249,6 +1241,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); { @@ -1257,7 +1250,7 @@ fn candidate_checks() { assert_eq!(expected_at, 12); Paras::schedule_code_upgrade( chain_a, - vec![1, 2, 3, 4].into(), + vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into(), expected_at, &cfg, SetGoAhead::Yes, @@ -1267,9 +1260,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::PrematureCodeUpgrade ); @@ -1296,14 +1289,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_eq!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Err(Error::::ValidationDataHashMismatch.into()), ); @@ -1317,7 +1311,7 @@ fn candidate_checks() { pov_hash: Hash::repeat_byte(1), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), hrmp_watermark: RELAY_PARENT_NUM, - validation_code: ValidationCode(vec![1]), + validation_code: ValidationCode(vec![9, 8, 7, 6, 5, 4, 3, 2, 1]), ..Default::default() } .build(); @@ -1331,14 +1325,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InvalidValidationCodeHash ); @@ -1366,14 +1361,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::ParaHeadMismatch ); @@ -1486,6 +1482,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1495,6 +1492,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1504,15 +1502,20 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a.clone(), backed_b.clone(), backed_c]; + let backed_candidates = vec![ + (backed_a.clone(), chain_a_assignment.1), + (backed_b.clone(), chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; let get_backing_group_idx = { // the order defines the group implicitly for this test case let backed_candidates_with_groups = backed_candidates .iter() .enumerate() - .map(|(idx, backed_candidate)| (backed_candidate.hash(), GroupIndex(idx as _))) + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) .collect::>(); move |candidate_hash_x: CandidateHash| -> Option { @@ -1532,10 +1535,8 @@ fn backing_works() { } = ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1554,22 +1555,22 @@ fn backing_works() { CandidateHash, (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), >::new(); - backed_candidates.into_iter().for_each(|backed_candidate| { + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { let candidate_receipt_with_backers = intermediate .entry(backed_candidate.hash()) .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); - - assert_eq!( - backed_candidate.validity_votes.len(), - backed_candidate.validator_indices.count_ones() - ); + let (validator_indices, None) = + backed_candidate.validator_indices_and_core_index(false) + else { + panic!("Expected no injected core index") + }; + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); candidate_receipt_with_backers.1.extend( - backed_candidate - .validator_indices + validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) .filter_map(|((validator_index_within_group, _), attestation)| { let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); group_validators(grp_idx).map(|validator_indices| { @@ -1666,6 +1667,257 @@ fn backing_works() { }); } +#[test] +fn backing_works_with_elastic_scaling_mvp() { + let chain_a = ParaId::from(1_u32); + let chain_b = ParaId::from(2_u32); + let thread_a = ParaId::from(3_u32); + + // The block number of the relay-parent for testing. + const RELAY_PARENT_NUM: BlockNumber = 4; + + let paras = vec![ + (chain_a, ParaKind::Parachain), + (chain_b, ParaKind::Parachain), + (thread_a, ParaKind::Parathread), + ]; + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + let validator_public = validator_pubkeys(&validators); + + new_test_ext(genesis_config(paras)).execute_with(|| { + shared::Pallet::::set_active_validators_ascending(validator_public.clone()); + shared::Pallet::::set_session_index(5); + + run_to_block(5, |_| None); + + let signing_context = + SigningContext { parent_hash: System::parent_hash(), session_index: 5 }; + + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), + group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), + group_index if group_index == GroupIndex::from(2) => Some(vec![4]), + _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + } + .map(|vs| vs.into_iter().map(ValidatorIndex).collect::>()) + }; + + // When processing candidates, we compute the group index from scheduler. + let validator_groups = vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2), ValidatorIndex(3)], + vec![ValidatorIndex(4)], + ]; + Scheduler::set_validator_groups(validator_groups); + + let allowed_relay_parents = default_allowed_relay_parent_tracker(); + + let mut candidate_a = TestCandidateBuilder { + para_id: chain_a, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(1), + persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_a); + + let mut candidate_b_1 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(2), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_1); + + let mut candidate_b_2 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(3), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_2); + + let backed_a = back_candidate( + candidate_a.clone(), + &validators, + group_validators(GroupIndex::from(0)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + + let backed_b_1 = back_candidate( + candidate_b_1.clone(), + &validators, + group_validators(GroupIndex::from(1)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(1)), + ); + + let backed_b_2 = back_candidate( + candidate_b_2.clone(), + &validators, + group_validators(GroupIndex::from(2)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(2)), + ); + + let backed_candidates = vec![ + (backed_a.clone(), CoreIndex(0)), + (backed_b_1.clone(), CoreIndex(1)), + (backed_b_2.clone(), CoreIndex(2)), + ]; + let get_backing_group_idx = { + // the order defines the group implicitly for this test case + let backed_candidates_with_groups = backed_candidates + .iter() + .enumerate() + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) + .collect::>(); + + move |candidate_hash_x: CandidateHash| -> Option { + backed_candidates_with_groups.iter().find_map(|(candidate_hash, grp)| { + if *candidate_hash == candidate_hash_x { + Some(*grp) + } else { + None + } + }) + } + }; + + let ProcessedCandidates { + core_indices: occupied_cores, + candidate_receipt_with_backing_validator_indices, + } = ParaInclusion::process_candidates( + &allowed_relay_parents, + backed_candidates.clone(), + &group_validators, + true, + ) + .expect("candidates scheduled, in order, and backed"); + + // Both b candidates will be backed. However, only one will be recorded on-chain and proceed + // with being made available. + assert_eq!( + occupied_cores, + vec![ + (CoreIndex::from(0), chain_a), + (CoreIndex::from(1), chain_b), + (CoreIndex::from(2), chain_b), + ] + ); + + // Transform the votes into the setup we expect + let mut expected = std::collections::HashMap::< + CandidateHash, + (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), + >::new(); + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { + let candidate_receipt_with_backers = expected + .entry(backed_candidate.hash()) + .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); + let (validator_indices, _maybe_core_index) = + backed_candidate.validator_indices_and_core_index(true); + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); + candidate_receipt_with_backers.1.extend( + validator_indices + .iter() + .enumerate() + .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes().iter().cloned()) + .filter_map(|((validator_index_within_group, _), attestation)| { + let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); + group_validators(grp_idx).map(|validator_indices| { + (validator_indices[validator_index_within_group], attestation) + }) + }), + ); + }); + + assert_eq!( + expected, + candidate_receipt_with_backing_validator_indices + .into_iter() + .map(|c| (c.0.hash(), c)) + .collect() + ); + + let backers = { + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); + backing_bitfield(&(0..num_backers).collect::>()) + }; + assert_eq!( + >::get(&chain_a), + Some(CandidatePendingAvailability { + core: CoreIndex::from(0), + hash: candidate_a.hash(), + descriptor: candidate_a.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers, + backing_group: GroupIndex::from(0), + }) + ); + assert_eq!( + >::get(&chain_a), + Some(candidate_a.commitments), + ); + + // Only one candidate for b will be recorded on chain. + assert_eq!( + >::get(&chain_b), + Some(CandidatePendingAvailability { + core: CoreIndex::from(2), + hash: candidate_b_2.hash(), + descriptor: candidate_b_2.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers: backing_bitfield(&[4]), + backing_group: GroupIndex::from(2), + }) + ); + assert_eq!( + >::get(&chain_b), + Some(candidate_b_2.commitments), + ); + }); +} + #[test] fn can_include_candidate_with_ok_code_upgrade() { let chain_a = ParaId::from(1_u32); @@ -1726,7 +1978,7 @@ fn can_include_candidate_with_ok_code_upgrade() { relay_parent: System::parent_hash(), pov_hash: Hash::repeat_byte(1), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), - new_validation_code: Some(vec![1, 2, 3].into()), + new_validation_code: Some(vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into()), hrmp_watermark: RELAY_PARENT_NUM, ..Default::default() } @@ -1740,14 +1992,15 @@ fn can_include_candidate_with_ok_code_upgrade() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1809,7 +2062,7 @@ fn check_allowed_relay_parents() { } let validator_public = validator_pubkeys(&validators); let mut config = genesis_config(paras); - config.configuration.config.group_rotation_frequency = 1; + config.configuration.config.scheduler_params.group_rotation_frequency = 1; new_test_ext(config).execute_with(|| { shared::Pallet::::set_active_validators_ascending(validator_public.clone()); @@ -1932,6 +2185,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_a, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1941,6 +2195,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_b, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1950,17 +2205,20 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_c, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a, backed_b, backed_c]; + let backed_candidates = vec![ + (backed_a, chain_a_assignment.1), + (backed_b, chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); }); @@ -2133,7 +2391,7 @@ fn para_upgrade_delay_scheduled_from_inclusion() { shared::Pallet::::set_active_validators_ascending(validator_public.clone()); shared::Pallet::::set_session_index(5); - let new_validation_code: ValidationCode = vec![1, 2, 3, 4, 5].into(); + let new_validation_code: ValidationCode = vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into(); let new_validation_code_hash = new_validation_code.hash(); // Otherwise upgrade is no-op. @@ -2189,14 +2447,15 @@ fn para_upgrade_delay_scheduled_from_inclusion() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); diff --git a/polkadot/runtime/parachains/src/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 ded7de08e4fa95db7fd9ba7ae172514d38a86129..7ed62a392e4e532b244b628d7f02a0dea2209c34 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -17,15 +17,20 @@ //! 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, + session_info, shared, ParaId, }; +use frame_support::pallet_prelude::*; +use primitives::CoreIndex; use frame_support::{ - assert_ok, parameter_types, + assert_ok, derive_impl, parameter_types, traits::{ Currency, ProcessMessage, ProcessMessageError, ValidatorSet, ValidatorSetWithIdentification, }, @@ -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::v4::{Assets, Location, 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, @@ -94,6 +103,7 @@ parameter_types! { pub type AccountId = u64; +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = BlockWeights; @@ -135,8 +145,8 @@ impl pallet_balances::Config for Test { type AccountStore = System; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -176,13 +186,29 @@ 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 { type WeightInfo = crate::configuration::TestWeightInfo; } -impl crate::shared::Config for Test {} +pub struct MockDisabledValidators {} +impl frame_support::traits::DisabledValidators for MockDisabledValidators { + /// Returns true if the given validator is disabled. + fn is_disabled(index: u32) -> bool { + disabled_validators().iter().any(|v| *v == index) + } + + /// Returns a hardcoded list (`DISABLED_VALIDATORS`) of disabled validators + fn disabled_validators() -> Vec { + disabled_validators() + } +} + +impl crate::shared::Config for Test { + type DisabledValidators = MockDisabledValidators; +} impl origin::Config for Test {} @@ -215,6 +241,7 @@ impl crate::paras::Config for Test { type QueueFootprinter = ParaInclusion; type NextSessionRotation = TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl crate::dmp::Config for Test {} @@ -286,7 +313,7 @@ impl crate::disputes::SlashingHandler for Test { } impl crate::scheduler::Config for Test { - type AssignmentProvider = Assigner; + type AssignmentProvider = MockAssigner; } pub struct TestMessageQueueWeight; @@ -340,17 +367,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; @@ -358,6 +380,34 @@ 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(((), Assets::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; @@ -388,6 +438,83 @@ 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 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)); + } + + // 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) {} + + #[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<()> { @@ -430,6 +557,8 @@ thread_local! { pub static AVAILABILITY_REWARDS: RefCell> = RefCell::new(HashMap::new()); + + pub static DISABLED_VALIDATORS: RefCell> = RefCell::new(vec![]); } pub fn backing_rewards() -> HashMap { @@ -440,6 +569,10 @@ pub fn availability_rewards() -> HashMap { AVAILABILITY_REWARDS.with(|r| r.borrow().clone()) } +pub fn disabled_validators() -> Vec { + DISABLED_VALIDATORS.with(|r| r.borrow().clone()) +} + parameter_types! { pub static Processed: Vec<(ParaId, UpwardMessage)> = vec![]; } @@ -579,3 +712,7 @@ pub(crate) fn deregister_parachain(id: ParaId) { pub(crate) fn try_deregister_parachain(id: ParaId) -> crate::DispatchResult { frame_support::storage::transactional::with_storage_layer(|| Paras::schedule_para_cleanup(id)) } + +pub(crate) fn set_disabled_validators(disabled: Vec) { + DISABLED_VALIDATORS.with(|d| *d.borrow_mut() = disabled) +} diff --git a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs index 05c4c9c37b4d9241a64539c13a1960f93c80d7c1..53ccc35c477c093f69d06f8c9d6902e41627e0af 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs @@ -27,10 +27,10 @@ const SESSION_INDEX: SessionIndex = 1; const VALIDATOR_NUM: usize = 800; const CAUSES_NUM: usize = 100; fn validation_code() -> ValidationCode { - ValidationCode(vec![0]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } fn old_validation_code() -> ValidationCode { - ValidationCode(vec![1]) + ValidationCode(vec![9, 8, 7, 6, 5, 4, 3, 2, 1]) } /// Prepares the PVF check statement and the validator signature to pass into diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index cd73d23bdadb398d8e852b71fce3c09f5b97d94e..5acdb9683bbf17349f35f280c6a268c49ebb5f39 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -119,7 +119,7 @@ use frame_system::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use primitives::{ ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead, - UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, + UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE, }; use scale_info::{Type, TypeInfo}; use sp_core::RuntimeDebug; @@ -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] @@ -657,6 +679,8 @@ pub mod pallet { PvfCheckSubjectInvalid, /// Parachain cannot currently schedule a code upgrade. CannotUpgradeCode, + /// Invalid validation code size. + InvalidCode, } /// All currently active PVF pre-checking votes. @@ -838,6 +862,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 } @@ -1206,6 +1232,10 @@ impl Pallet { // Check that we can schedule an upgrade at all. ensure!(Self::can_upgrade_validation_code(id), Error::::CannotUpgradeCode); let config = configuration::Pallet::::config(); + // Validation code sanity checks: + ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); + ensure!(new_code.0.len() <= config.max_code_size as usize, Error::::InvalidCode); + let current_block = frame_system::Pallet::::block_number(); // Schedule the upgrade with a delay just like if a parachain triggered the upgrade. let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay); @@ -1344,7 +1374,7 @@ impl Pallet { outgoing } - // note replacement of the code of para with given `id`, which occured in the + // note replacement of the code of para with given `id`, which occurred in the // context of the given relay-chain block number. provide the replaced code. // // `at` for para-triggered replacement is the block number of the relay-chain @@ -1866,7 +1896,14 @@ impl Pallet { ) -> Weight { let mut weight = T::DbWeight::get().reads(1); - // Enacting this should be prevented by the `can_schedule_upgrade` + // Should be prevented by checks in `schedule_code_upgrade_external` + let new_code_len = new_code.0.len(); + if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize { + log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",); + return weight + } + + // Enacting this should be prevented by the `can_upgrade_validation_code` if FutureCodeHash::::contains_key(&id) { // This branch should never be reached. Signalling an upgrade is disallowed for a para // that already has one upgrade scheduled. @@ -2064,7 +2101,7 @@ impl Pallet { } /// Submits a given PVF check statement with corresponding signature as an unsigned transaction - /// into the memory pool. Ultimately, that disseminates the transaction accross the network. + /// into the memory pool. Ultimately, that disseminates the transaction across the network. /// /// This function expects an offchain context and cannot be callable from the on-chain logic. /// diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index cca200c2765e361120b4c5ea8db3dfc7c82bdc13..262ec9d3fdba95c0368d4cfe03b288ccaeb049d1 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -17,7 +17,7 @@ use super::*; use frame_support::{assert_err, assert_ok, assert_storage_noop}; use keyring::Sr25519Keyring; -use primitives::{BlockNumber, PARACHAIN_KEY_TYPE_ID}; +use primitives::{vstaging::SchedulerParams, BlockNumber, PARACHAIN_KEY_TYPE_ID}; use sc_keystore::LocalKeystore; use sp_keystore::{Keystore, KeystorePtr}; use std::sync::Arc; @@ -67,6 +67,16 @@ fn submit_super_majority_pvf_votes( .for_each(sign_and_include_pvf_check_statement); } +fn test_validation_code_1() -> ValidationCode { + let validation_code = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + ValidationCode(validation_code) +} + +fn test_validation_code_2() -> ValidationCode { + let validation_code = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; + ValidationCode(validation_code) +} + fn run_to_block(to: BlockNumber, new_session: Option>) { let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); for validator in VALIDATORS.iter() { @@ -284,7 +294,7 @@ fn para_past_code_pruning_in_initialize() { let id = ParaId::from(0u32); let at_block: BlockNumber = 10; let included_block: BlockNumber = 12; - let validation_code = ValidationCode(vec![4, 5, 6]); + let validation_code = test_validation_code_2(); Paras::increase_code_ref(&validation_code.hash(), &validation_code); PastCodeHash::::insert(&(id, at_block), &validation_code.hash()); @@ -377,8 +387,8 @@ fn note_past_code_sets_up_pruning_correctly() { let id_a = ParaId::from(0u32); let id_b = ParaId::from(1u32); - Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); - Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); + Paras::note_past_code(id_a, 10, 12, test_validation_code_1().hash()); + Paras::note_past_code(id_b, 20, 23, test_validation_code_2().hash()); assert_eq!(PastCodePruning::::get(), vec![(id_a, 12), (id_b, 23)]); assert_eq!( @@ -398,7 +408,7 @@ fn code_upgrade_applied_after_delay() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -425,7 +435,7 @@ fn code_upgrade_applied_after_delay() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -516,7 +526,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -543,7 +553,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -637,7 +647,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -662,7 +672,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -750,8 +760,8 @@ fn submit_code_change_when_not_allowed_is_err() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -832,8 +842,8 @@ fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() { new_test_ext(genesis_config).execute_with(|| { let para_id = 0u32.into(); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -880,7 +890,7 @@ fn full_parachain_cleanup_storage() { let code_retention_period = 20; let validation_upgrade_delay = 1 + 5; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -899,7 +909,10 @@ fn full_parachain_cleanup_storage() { minimum_validation_upgrade_delay: 2, // Those are not relevant to this test. However, HostConfiguration is still a // subject for the consistency check. - paras_availability_period: 1, + scheduler_params: SchedulerParams { + paras_availability_period: 1, + ..Default::default() + }, ..Default::default() }, }, @@ -910,7 +923,7 @@ fn full_parachain_cleanup_storage() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -993,8 +1006,8 @@ fn full_parachain_cleanup_storage() { fn cannot_offboard_ongoing_pvf_check() { let para_id = ParaId::from(0); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( para_id, @@ -1152,7 +1165,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { ParaGenesisArgs { para_kind: ParaKind::Parachain, genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), + validation_code: test_validation_code_1(), }, )]; @@ -1174,8 +1187,8 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { const EXPECTED_SESSION: SessionIndex = 1; let para_id = ParaId::from(0); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![4, 5, 6].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); Paras::schedule_code_upgrade( para_id, new_code.clone(), @@ -1219,7 +1232,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { #[test] fn code_ref_is_cleaned_correctly() { new_test_ext(Default::default()).execute_with(|| { - let code: ValidationCode = vec![1, 2, 3].into(); + let code = test_validation_code_1(); Paras::increase_code_ref(&code.hash(), &code); Paras::increase_code_ref(&code.hash(), &code); @@ -1244,8 +1257,8 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { let a = ParaId::from(111); let b = ParaId::from(222); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let validation_code = test_validation_code_2(); let paras = vec![( a, @@ -1320,7 +1333,7 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { fn pvf_check_onboarding_reject_on_expiry() { let pvf_voting_ttl = 2; let a = ParaId::from(111); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig { configuration: crate::configuration::GenesisConfig { @@ -1368,8 +1381,8 @@ fn pvf_check_onboarding_reject_on_expiry() { #[test] fn pvf_check_upgrade_reject() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1437,8 +1450,8 @@ fn pvf_check_upgrade_reject() { #[test] fn pvf_check_submit_vote() { - let code_a: ValidationCode = vec![3, 2, 1].into(); - let code_b: ValidationCode = vec![1, 2, 3].into(); + let code_a = test_validation_code_1(); + let code_b = test_validation_code_2(); let check = |stmt: PvfCheckStatement| -> (Result<_, _>, Result<_, _>) { let validators = &[ @@ -1554,8 +1567,8 @@ fn pvf_check_submit_vote() { #[test] fn include_pvf_check_statement_refunds_weight() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1620,7 +1633,7 @@ fn include_pvf_check_statement_refunds_weight() { fn add_trusted_validation_code_inserts_with_no_users() { // This test is to ensure that trusted validation code is inserted into the storage // with the reference count equal to 0. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1634,7 +1647,7 @@ fn add_trusted_validation_code_inserts_with_no_users() { fn add_trusted_validation_code_idempotent() { // This test makes sure that calling add_trusted_validation_code twice with the same // parameters is a no-op. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1653,7 +1666,7 @@ fn add_trusted_validation_code_idempotent() { fn poke_unused_validation_code_removes_code_cleanly() { // This test makes sure that calling poke_unused_validation_code with a code that is currently // in the storage but has no users will remove it cleanly from the storage. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1672,7 +1685,7 @@ fn poke_unused_validation_code_removes_code_cleanly() { #[test] fn poke_unused_validation_code_doesnt_remove_code_with_users() { let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { // First we add the code to the storage. assert_ok!(Paras::add_trusted_validation_code( @@ -1708,7 +1721,7 @@ fn increase_code_ref_doesnt_have_allergy_on_add_trusted_validation_code() { // to a disaster. // NOTE that this test is extra paranoid, as it is not really possible to hit // `decrease_code_ref` without calling `increase_code_ref` first. - let code = ValidationCode(vec![1, 2, 3]); + let code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), code.clone())); @@ -1732,7 +1745,7 @@ fn add_trusted_validation_code_insta_approval() { // `add_trusted_validation_code` and uses the `CodeByHash::contains_key` which is what // `add_trusted_validation_code` uses. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1779,7 +1792,7 @@ fn add_trusted_validation_code_enacts_existing_pvf_vote() { // already going through PVF pre-checking voting will conclude the voting and enact the // code upgrade. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1868,7 +1881,7 @@ fn verify_para_head_is_externally_accessible() { #[test] fn most_recent_context() { - let validation_code: ValidationCode = vec![1, 2, 3].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig::default(); diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index 3043127c3174660b72a55ce850bb9d05c2330ad2..f3365945758c19243927f465fe5527b41a32e643 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; @@ -63,7 +65,7 @@ benchmarks! { .collect(); let scenario = BenchBuilder::::new() - .set_backed_and_concluding_cores(cores_with_backed) + .set_backed_and_concluding_paras(cores_with_backed) .build(); let mut benchmark = scenario.data.clone(); @@ -108,7 +110,7 @@ benchmarks! { .collect(); let scenario = BenchBuilder::::new() - .set_backed_and_concluding_cores(cores_with_backed.clone()) + .set_backed_and_concluding_paras(cores_with_backed.clone()) .build(); let mut benchmark = scenario.data.clone(); @@ -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!( @@ -161,17 +165,20 @@ benchmarks! { .collect(); let scenario = BenchBuilder::::new() - .set_backed_and_concluding_cores(cores_with_backed.clone()) + .set_backed_and_concluding_paras(cores_with_backed.clone()) .set_code_upgrade(v) .build(); 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..723a15bdba7a88d73a2610b90910f53c6150475f 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -30,7 +30,8 @@ use crate::{ metrics::METRICS, paras, scheduler::{self, FreedReason}, - shared, ParaId, + shared::{self, AllowedRelayParentsTracker}, + ParaId, }; use bitvec::prelude::BitVec; use frame_support::{ @@ -42,15 +43,14 @@ use frame_support::{ use frame_system::pallet_prelude::*; use pallet_babe::{self, ParentBlockRandomness}; use primitives::{ - BackedCandidate, CandidateHash, CandidateReceipt, CheckedDisputeStatementSet, - CheckedMultiDisputeStatementSet, CoreIndex, DisputeStatementSet, - InherentData as ParachainsInherentData, MultiDisputeStatementSet, ScrapedOnChainVotes, - SessionIndex, SignedAvailabilityBitfields, SigningContext, UncheckedSignedAvailabilityBitfield, - UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex, ValidityAttestation, - PARACHAINS_INHERENT_IDENTIFIER, + effective_minimum_backing_votes, vstaging::node_features::FeatureIndex, BackedCandidate, + CandidateHash, CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, + CoreIndex, DisputeStatementSet, InherentData as ParachainsInherentData, + MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, SignedAvailabilityBitfields, + SigningContext, UncheckedSignedAvailabilityBitfield, UncheckedSignedAvailabilityBitfields, + ValidatorId, ValidatorIndex, ValidityAttestation, PARACHAINS_INHERENT_IDENTIFIER, }; use rand::{seq::SliceRandom, SeedableRng}; - use scale_info::TypeInfo; use sp_runtime::traits::{Header as HeaderT, One}; use sp_std::{ @@ -142,6 +142,12 @@ pub mod pallet { DisputeStatementsUnsortedOrDuplicates, /// A dispute statement was invalid. DisputeInvalid, + /// A candidate was backed by a disabled validator + BackedByDisabled, + /// A candidate was backed even though the paraid was not scheduled. + BackedOnUnscheduledCore, + /// Too many candidates supplied. + UnscheduledCandidate, } /// Whether the paras inherent was included within this block. @@ -378,6 +384,7 @@ impl Pallet { let bitfields_weight = signed_bitfields_weight::(&bitfields); let disputes_weight = multi_dispute_statement_sets_weight::(&disputes); + // Weight before filtering/sanitization let all_weight_before = candidates_weight + bitfields_weight + disputes_weight; METRICS.on_before_filter(all_weight_before.ref_time()); @@ -548,7 +555,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,15 +587,31 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); - >::update_claimqueue(freed, now); - let scheduled = >::scheduled_paras() - .map(|(core_idx, para_id)| (para_id, core_idx)) - .collect(); + >::free_cores_and_fill_claimqueue(freed, now); METRICS.on_candidates_processed_total(backed_candidates.len() as u64); - let backed_candidates = sanitize_backed_candidates::( + let core_index_enabled = configuration::Pallet::::config() + .node_features + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + let mut scheduled: BTreeMap> = BTreeMap::new(); + let mut total_scheduled_cores = 0; + + for (core_idx, para_id) in >::scheduled_paras() { + total_scheduled_cores += 1; + scheduled.entry(para_id).or_default().insert(core_idx); + } + + let SanitizedBackedCandidates { + backed_candidates_with_core, + votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, + } = sanitize_backed_candidates::( backed_candidates, + &allowed_relay_parents, |candidate_idx: usize, backed_candidate: &BackedCandidate<::Hash>| -> bool { @@ -605,13 +628,33 @@ impl Pallet { // // NOTE: this is the only place where we check the relay-parent. check_ctx - .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate) + .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate.candidate()) .is_err() }, - &scheduled, + scheduled, + core_index_enabled, ); - METRICS.on_candidates_sanitized(backed_candidates.len() as u64); + ensure!( + backed_candidates_with_core.len() <= total_scheduled_cores, + Error::::UnscheduledCandidate + ); + + METRICS.on_candidates_sanitized(backed_candidates_with_core.len() as u64); + + // In `Enter` context (invoked during execution) there should be no backing votes from + // disabled validators because they should have been filtered out during inherent data + // preparation (`ProvideInherent` context). Abort in such cases. + if context == ProcessInherentDataContext::Enter { + ensure!(!votes_from_disabled_were_dropped, Error::::BackedByDisabled); + } + + // In `Enter` context (invoked during execution) we shouldn't have filtered any candidates + // due to a para not being scheduled. They have been filtered during inherent data + // preparation (`ProvideInherent` context). Abort in such cases. + if context == ProcessInherentDataContext::Enter { + ensure!(!dropped_unscheduled_candidates, Error::::BackedOnUnscheduledCore); + } // Process backed candidates according to scheduled cores. let inclusion::ProcessedCandidates::< as HeaderT>::Hash> { @@ -619,9 +662,9 @@ impl Pallet { candidate_receipt_with_backing_validator_indices, } = >::process_candidates( &allowed_relay_parents, - backed_candidates.clone(), - &scheduled, + backed_candidates_with_core.clone(), >::group_validators, + core_index_enabled, )?; // Note which of the scheduled cores were actually occupied by a backed candidate. >::occupied(occupied.into_iter().map(|e| (e.0, e.1)).collect()); @@ -638,8 +681,15 @@ impl Pallet { let bitfields = bitfields.into_iter().map(|v| v.into_unchecked()).collect(); - let processed = - ParachainsInherentData { bitfields, backed_candidates, disputes, parent_header }; + let processed = ParachainsInherentData { + bitfields, + backed_candidates: backed_candidates_with_core + .into_iter() + .map(|(candidate, _)| candidate) + .collect(), + disputes, + parent_header, + }; Ok((processed, Some(all_weight_after).into())) } } @@ -738,7 +788,7 @@ fn random_sel Weight>( /// Assumes disputes are already filtered by the time this is called. /// /// Returns the total weight consumed by `bitfields` and `candidates`. -fn apply_weight_limit( +pub(crate) fn apply_weight_limit( candidates: &mut Vec::Hash>>, bitfields: &mut UncheckedSignedAvailabilityBitfields, max_consumable_weight: Weight, @@ -755,35 +805,71 @@ fn apply_weight_limit( return total } - // Prefer code upgrades, they tend to be large and hence stand no chance to be picked - // late while maintaining the weight bounds. - let preferred_indices = candidates + // Invariant: block author provides candidate in the order in which they form a chain + // wrt elastic scaling. If the invariant is broken, we'd fail later when filtering candidates + // which are unchained. + + let mut chained_candidates: Vec> = Vec::new(); + let mut current_para_id = None; + + for candidate in sp_std::mem::take(candidates).into_iter() { + let candidate_para_id = candidate.descriptor().para_id; + if Some(candidate_para_id) == current_para_id { + let chain = chained_candidates + .last_mut() + .expect("if the current_para_id is Some, then vec is not empty; qed"); + chain.push(candidate); + } else { + current_para_id = Some(candidate_para_id); + chained_candidates.push(vec![candidate]); + } + } + + // Elastic scaling: we prefer chains that have a code upgrade among the candidates, + // as the candidates containing the upgrade tend to be large and hence stand no chance to + // be picked late while maintaining the weight bounds. + // + // Limitations: For simplicity if total weight of a chain of candidates is larger than + // the remaining weight, the chain will still not be included while it could still be possible + // to include part of that chain. + let preferred_chain_indices = chained_candidates .iter() .enumerate() - .filter_map(|(idx, candidate)| { - candidate.candidate.commitments.new_validation_code.as_ref().map(|_code| idx) + .filter_map(|(idx, candidates)| { + // Check if any of the candidate in chain contains a code upgrade. + if candidates + .iter() + .any(|candidate| candidate.candidate().commitments.new_validation_code.is_some()) + { + Some(idx) + } else { + None + } }) .collect::>(); - // There is weight remaining to be consumed by a subset of candidates + // There is weight remaining to be consumed by a subset of chained candidates // which are going to be picked now. if let Some(max_consumable_by_candidates) = max_consumable_weight.checked_sub(&total_bitfields_weight) { - let (acc_candidate_weight, indices) = - random_sel::::Hash>, _>( + let (acc_candidate_weight, chained_indices) = + random_sel::::Hash>>, _>( rng, - &candidates, - preferred_indices, - |c| backed_candidate_weight::(c), + &chained_candidates, + preferred_chain_indices, + |candidates| backed_candidates_weight::(&candidates), max_consumable_by_candidates, ); - log::debug!(target: LOG_TARGET, "Indices Candidates: {:?}, size: {}", indices, candidates.len()); - candidates.indexed_retain(|idx, _backed_candidate| indices.binary_search(&idx).is_ok()); + log::debug!(target: LOG_TARGET, "Indices Candidates: {:?}, size: {}", chained_indices, candidates.len()); + chained_candidates + .indexed_retain(|idx, _backed_candidates| chained_indices.binary_search(&idx).is_ok()); // pick all bitfields, and // fill the remaining space with candidates let total_consumed = acc_candidate_weight.saturating_add(total_bitfields_weight); + *candidates = chained_candidates.into_iter().flatten().collect::>(); + return total_consumed } @@ -900,7 +986,25 @@ pub(crate) fn sanitize_bitfields( bitfields } -/// Filter out any candidates that have a concluded invalid dispute. +// Result from `sanitize_backed_candidates` +#[derive(Debug, PartialEq)] +struct SanitizedBackedCandidates { + // Sanitized backed candidates along with the assigned core. The `Vec` is sorted according to + // the occupied core index. + backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, + // Set to true if any votes from disabled validators were dropped from the input. + votes_from_disabled_were_dropped: bool, + // Set to true if any candidates were dropped due to filtering done in + // `map_candidates_to_cores` + dropped_unscheduled_candidates: bool, +} + +/// Filter out: +/// 1. any candidates that have a concluded invalid dispute +/// 2. any unscheduled candidates, as well as candidates whose paraid has multiple cores assigned +/// but have no injected core index. +/// 3. all backing votes from disabled validators +/// 4. any candidates that end up with less than `effective_minimum_backing_votes` backing votes /// /// `scheduled` follows the same naming scheme as provided in the /// guide: Currently `free` but might become `occupied`. @@ -910,43 +1014,55 @@ pub(crate) fn sanitize_bitfields( /// `candidate_has_concluded_invalid_dispute` must return `true` if the candidate /// is disputed, false otherwise. The passed `usize` is the candidate index. /// -/// The returned `Vec` is sorted according to the occupied core index. +/// Returns struct `SanitizedBackedCandidates` where `backed_candidates` are sorted according to the +/// occupied core index. fn sanitize_backed_candidates< T: crate::inclusion::Config, F: FnMut(usize, &BackedCandidate) -> bool, >( mut backed_candidates: Vec>, + allowed_relay_parents: &AllowedRelayParentsTracker>, mut candidate_has_concluded_invalid_dispute_or_is_invalid: F, - scheduled: &BTreeMap, -) -> Vec> { + scheduled: BTreeMap>, + core_index_enabled: bool, +) -> SanitizedBackedCandidates { // Remove any candidates that were concluded invalid. // This does not assume sorting. backed_candidates.indexed_retain(move |candidate_idx, backed_candidate| { !candidate_has_concluded_invalid_dispute_or_is_invalid(candidate_idx, backed_candidate) }); - // Assure the backed candidate's `ParaId`'s core is free. - // This holds under the assumption that `Scheduler::schedule` is called _before_. - // We don't check the relay-parent because this is done in the closure when - // constructing the inherent and during actual processing otherwise. + let initial_candidate_count = backed_candidates.len(); + // Map candidates to scheduled cores. Filter out any unscheduled candidates. + let mut backed_candidates_with_core = map_candidates_to_cores::( + &allowed_relay_parents, + scheduled, + core_index_enabled, + backed_candidates, + ); - backed_candidates.retain(|backed_candidate| { - let desc = backed_candidate.descriptor(); + let dropped_unscheduled_candidates = + initial_candidate_count != backed_candidates_with_core.len(); - scheduled.get(&desc.para_id).is_some() - }); + // Filter out backing statements from disabled validators + let votes_from_disabled_were_dropped = filter_backed_statements_from_disabled_validators::( + &mut backed_candidates_with_core, + &allowed_relay_parents, + core_index_enabled, + ); // Sort the `Vec` last, once there is a guarantee that these // `BackedCandidates` references the expected relay chain parent, // but more importantly are scheduled for a free core. // This both avoids extra work for obviously invalid candidates, // but also allows this to be done in place. - backed_candidates.sort_by(|x, y| { - // Never panics, since we filtered all panic arguments out in the previous `fn retain`. - scheduled[&x.descriptor().para_id].cmp(&scheduled[&y.descriptor().para_id]) - }); + backed_candidates_with_core.sort_by(|(_x, core_x), (_y, core_y)| core_x.cmp(&core_y)); - backed_candidates + SanitizedBackedCandidates { + dropped_unscheduled_candidates, + votes_from_disabled_were_dropped, + backed_candidates_with_core, + } } /// Derive entropy from babe provided per block randomness. @@ -1029,3 +1145,200 @@ fn limit_and_sanitize_disputes< (checked, checked_disputes_weight) } } + +// Filters statements from disabled validators in `BackedCandidate`, non-scheduled candidates and +// few more sanity checks. Returns `true` if at least one statement is removed and `false` +// otherwise. +fn filter_backed_statements_from_disabled_validators( + backed_candidates_with_core: &mut Vec<( + BackedCandidate<::Hash>, + CoreIndex, + )>, + allowed_relay_parents: &AllowedRelayParentsTracker>, + core_index_enabled: bool, +) -> bool { + let disabled_validators = + BTreeSet::<_>::from_iter(shared::Pallet::::disabled_validators().into_iter()); + + if disabled_validators.is_empty() { + // No disabled validators - nothing to do + return false + } + + let backed_len_before = backed_candidates_with_core.len(); + + // Flag which will be returned. Set to `true` if at least one vote is filtered. + let mut filtered = false; + + let minimum_backing_votes = configuration::Pallet::::config().minimum_backing_votes; + + // Process all backed candidates. `validator_indices` in `BackedCandidates` are indices within + // the validator group assigned to the parachain. To obtain this group we need: + // 1. Core index assigned to the parachain which has produced the candidate + // 2. The relay chain block number of the candidate + backed_candidates_with_core.retain_mut(|(bc, core_idx)| { + let (validator_indices, maybe_core_index) = bc.validator_indices_and_core_index(core_index_enabled); + let mut validator_indices = BitVec::<_>::from(validator_indices); + + // Get relay parent block number of the candidate. We need this to get the group index assigned to this core at this block number + let relay_parent_block_number = match allowed_relay_parents + .acquire_info(bc.descriptor().relay_parent, None) { + Some((_, block_num)) => block_num, + None => { + log::debug!(target: LOG_TARGET, "Relay parent {:?} for candidate is not in the allowed relay parents. Dropping the candidate.", bc.descriptor().relay_parent); + return false + } + }; + + // Get the group index for the core + let group_idx = match >::group_assigned_to_core( + *core_idx, + relay_parent_block_number + One::one(), + ) { + Some(group_idx) => group_idx, + None => { + log::debug!(target: LOG_TARGET, "Can't get the group index for core idx {:?}. Dropping the candidate.", core_idx); + return false + }, + }; + + // And finally get the validator group for this group index + let validator_group = match >::group_validators(group_idx) { + Some(validator_group) => validator_group, + None => { + log::debug!(target: LOG_TARGET, "Can't get the validators from group {:?}. Dropping the candidate.", group_idx); + return false + } + }; + + // Bitmask with the disabled indices within the validator group + let disabled_indices = BitVec::::from_iter(validator_group.iter().map(|idx| disabled_validators.contains(idx))); + // The indices of statements from disabled validators in `BackedCandidate`. We have to drop these. + let indices_to_drop = disabled_indices.clone() & &validator_indices; + // Apply the bitmask to drop the disabled validator from `validator_indices` + validator_indices &= !disabled_indices; + // Update the backed candidate + bc.set_validator_indices_and_core_index(validator_indices, maybe_core_index); + + // Remove the corresponding votes from `validity_votes` + for idx in indices_to_drop.iter_ones().rev() { + bc.validity_votes_mut().remove(idx); + } + + // If at least one statement was dropped we need to return `true` + if indices_to_drop.count_ones() > 0 { + filtered = true; + } + + // By filtering votes we might render the candidate invalid and cause a failure in + // [`process_candidates`]. To avoid this we have to perform a sanity check here. If there + // are not enough backing votes after filtering we will remove the whole candidate. + if bc.validity_votes().len() < effective_minimum_backing_votes( + validator_group.len(), + minimum_backing_votes + ) { + return false + } + + true + }); + + // Also return `true` if a whole candidate was dropped from the set + filtered || backed_len_before != backed_candidates_with_core.len() +} + +/// Map candidates to scheduled cores. +/// If the para only has one scheduled core and no `CoreIndex` is injected, map the candidate to the +/// single core. If the para has multiple cores scheduled, only map the candidates which have a +/// proper core injected. Filter out the rest. +/// Also returns whether or not we dropped any candidates. +fn map_candidates_to_cores( + allowed_relay_parents: &AllowedRelayParentsTracker>, + mut scheduled: BTreeMap>, + core_index_enabled: bool, + candidates: Vec>, +) -> Vec<(BackedCandidate, CoreIndex)> { + let mut backed_candidates_with_core = Vec::with_capacity(candidates.len()); + + // We keep a candidate if the parachain has only one core assigned or if + // a core index is provided by block author and it's indeed scheduled. + for backed_candidate in candidates { + let maybe_injected_core_index = get_injected_core_index::( + allowed_relay_parents, + &backed_candidate, + core_index_enabled, + ); + + let scheduled_cores = scheduled.get_mut(&backed_candidate.descriptor().para_id); + // Candidates without scheduled cores are silently filtered out. + if let Some(scheduled_cores) = scheduled_cores { + if let Some(core_idx) = maybe_injected_core_index { + if scheduled_cores.contains(&core_idx) { + scheduled_cores.remove(&core_idx); + backed_candidates_with_core.push((backed_candidate, core_idx)); + } + } else if scheduled_cores.len() == 1 { + backed_candidates_with_core + .push((backed_candidate, scheduled_cores.pop_first().expect("Length is 1"))); + } + } + } + + backed_candidates_with_core +} + +fn get_injected_core_index( + allowed_relay_parents: &AllowedRelayParentsTracker>, + candidate: &BackedCandidate, + core_index_enabled: bool, +) -> Option { + // After stripping the 8 bit extensions, the `validator_indices` field length is expected + // to be equal to backing group size. If these don't match, the `CoreIndex` is badly encoded, + // or not supported. + let (validator_indices, maybe_core_idx) = + candidate.validator_indices_and_core_index(core_index_enabled); + + let Some(core_idx) = maybe_core_idx else { return None }; + + let relay_parent_block_number = + match allowed_relay_parents.acquire_info(candidate.descriptor().relay_parent, None) { + Some((_, block_num)) => block_num, + None => { + log::debug!( + target: LOG_TARGET, + "Relay parent {:?} for candidate {:?} is not in the allowed relay parents. Dropping the candidate.", + candidate.descriptor().relay_parent, + candidate.candidate().hash(), + ); + return None + }, + }; + + // Get the backing group of the candidate backed at `core_idx`. + let group_idx = match >::group_assigned_to_core( + core_idx, + relay_parent_block_number + One::one(), + ) { + Some(group_idx) => group_idx, + None => { + log::debug!( + target: LOG_TARGET, + "Can't get the group index for core idx {:?}. Dropping the candidate {:?}.", + core_idx, + candidate.candidate().hash(), + ); + return None + }, + }; + + let group_validators = match >::group_validators(group_idx) { + Some(validators) => validators, + None => return None, + }; + + if group_validators.len() == validator_indices.len() { + Some(core_idx) + } else { + None + } +} diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 7c70fcea1943415e9882bacb75e9dfc39b23b90d..41a84175e9a4b9f86ebdb4ff6560ae1256f4fe33 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -22,14 +22,19 @@ use super::*; #[cfg(not(feature = "runtime-benchmarks"))] mod enter { - use super::*; + use super::{inclusion::tests::TestCandidateBuilder, *}; 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, AssignmentProvider}, + ParasEntry, + }, }; use assert_matches::assert_matches; use frame_support::assert_ok; use frame_system::limits; + use primitives::vstaging::SchedulerParams; use sp_runtime::Perbill; use sp_std::collections::btree_map::BTreeMap; @@ -39,6 +44,7 @@ mod enter { backed_and_concluding: BTreeMap, num_validators_per_core: u32, code_upgrade: Option, + fill_claimqueue: bool, } fn make_inherent_data( @@ -48,6 +54,7 @@ mod enter { backed_and_concluding, num_validators_per_core, code_upgrade, + fill_claimqueue, }: TestConfig, ) -> Bench { let builder = BenchBuilder::::new() @@ -57,8 +64,16 @@ 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_backed_and_concluding_paras(backed_and_concluding) + .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() @@ -73,7 +88,7 @@ mod enter { // `create_inherent` and will not cause `enter` to early. fn include_backed_candidates() { let config = MockGenesisConfig::default(); - assert!(config.configuration.config.scheduling_lookahead > 0); + assert!(config.configuration.config.scheduler_params.lookahead > 0); new_test_ext(config).execute_with(|| { let dispute_statements = BTreeMap::new(); @@ -88,6 +103,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 +254,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 +325,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 +394,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 +479,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 +564,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(); @@ -605,7 +626,7 @@ mod enter { #[test] fn limit_candidates_over_weight_1() { let config = MockGenesisConfig::default(); - assert!(config.configuration.config.scheduling_lookahead > 0); + assert!(config.configuration.config.scheduler_params.lookahead > 0); new_test_ext(config).execute_with(|| { // Create the inherent data for this block @@ -627,6 +648,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 +688,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 +700,30 @@ mod enter { Pallet::::on_chain_votes().unwrap().session, 2 ); + + // One core was scheduled. We should put the assignment back, before calling enter(). + let now = >::block_number() + 1; + let used_cores = 5; + let cores = (0..used_cores) + .into_iter() + .map(|i| { + let SchedulerParams { ttl, .. } = + >::config().scheduler_params; + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + (CoreIndex(i), [ParasEntry::new(assignment, now + ttl)].into()) + }) + .collect(); + scheduler::ClaimQueue::::set(cores); + + assert_ok!(Pallet::::enter( + frame_system::RawOrigin::None.into(), + limit_inherent_data, + )); }); } @@ -713,6 +753,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 +819,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 +883,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(); @@ -882,6 +925,129 @@ mod enter { }); } + // Helper fn that builds chained dummy candidates for elastic scaling tests + fn build_backed_candidate_chain( + para_id: ParaId, + len: usize, + start_core_index: usize, + code_upgrade_index: Option, + ) -> Vec { + if let Some(code_upgrade_index) = code_upgrade_index { + assert!(code_upgrade_index < len, "Code upgrade index out of bounds"); + } + + (0..len) + .into_iter() + .map(|idx| { + let mut builder = TestCandidateBuilder::default(); + builder.para_id = para_id; + let mut ccr = builder.build(); + + if Some(idx) == code_upgrade_index { + ccr.commitments.new_validation_code = Some(vec![1, 2, 3, 4].into()); + } + + ccr.commitments.processed_downward_messages = idx as u32; + let core_index = start_core_index + idx; + + BackedCandidate::new( + ccr.into(), + Default::default(), + Default::default(), + Some(CoreIndex(core_index as u32)), + ) + }) + .collect::>() + } + + // Ensure that overweight parachain inherents are always rejected by the runtime. + // Runtime should panic and return `InherentOverweight` error. + #[test] + fn test_backed_candidates_apply_weight_works_for_elastic_scaling() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let seed = [ + 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, + 0, 0, 0, 0, 0, 2, 92, + ]; + let mut rng = rand_chacha::ChaChaRng::from_seed(seed); + + // Create an overweight inherent and oversized block + let mut backed_and_concluding = BTreeMap::new(); + + for i in 0..30 { + backed_and_concluding.insert(i, i); + } + + let scenario = make_inherent_data(TestConfig { + dispute_statements: Default::default(), + dispute_sessions: vec![], // 3 cores with disputes + backed_and_concluding, + num_validators_per_core: 5, + code_upgrade: None, + fill_claimqueue: false, + }); + + let mut para_inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (5 validators per core, 30 backed candidates, 0 disputes + // => 5*30 = 150) + assert_eq!(para_inherent_data.bitfields.len(), 150); + // * 30 backed candidates + assert_eq!(para_inherent_data.backed_candidates.len(), 30); + + let mut input_candidates = + build_backed_candidate_chain(ParaId::from(1000), 3, 0, Some(1)); + let chained_candidates_weight = backed_candidates_weight::(&input_candidates); + + input_candidates.append(&mut para_inherent_data.backed_candidates); + let input_bitfields = para_inherent_data.bitfields; + + // Test if weight insufficient even for 1 candidate (which doesn't contain a code + // upgrade). + let max_weight = backed_candidate_weight::(&input_candidates[0]) + + signed_bitfields_weight::(&input_bitfields); + let mut backed_candidates = input_candidates.clone(); + let mut bitfields = input_bitfields.clone(); + apply_weight_limit::( + &mut backed_candidates, + &mut bitfields, + max_weight, + &mut rng, + ); + + // The chained candidates are not picked, instead a single other candidate is picked + assert_eq!(backed_candidates.len(), 1); + assert_ne!(backed_candidates[0].descriptor().para_id, ParaId::from(1000)); + + // All bitfields are kept. + assert_eq!(bitfields.len(), 150); + + // Test if para_id 1000 chained candidates make it if there is enough room for its 3 + // candidates. + let max_weight = + chained_candidates_weight + signed_bitfields_weight::(&input_bitfields); + let mut backed_candidates = input_candidates.clone(); + let mut bitfields = input_bitfields.clone(); + apply_weight_limit::( + &mut backed_candidates, + &mut bitfields, + max_weight, + &mut rng, + ); + + // Only the chained candidates should pass filter. + assert_eq!(backed_candidates.len(), 3); + // Check the actual candidates + assert_eq!(backed_candidates[0].descriptor().para_id, ParaId::from(1000)); + assert_eq!(backed_candidates[1].descriptor().para_id, ParaId::from(1000)); + assert_eq!(backed_candidates[2].descriptor().para_id, ParaId::from(1000)); + + // All bitfields are kept. + assert_eq!(bitfields.len(), 150); + }); + } + // Ensure that overweight parachain inherents are always rejected by the runtime. // Runtime should panic and return `InherentOverweight` error. #[test] @@ -905,6 +1071,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(); @@ -959,6 +1126,7 @@ mod sanitizers { AvailabilityBitfield, GroupIndex, Hash, Id as ParaId, SignedAvailabilityBitfield, ValidatorIndex, }; + use rstest::rstest; use sp_core::crypto::UncheckedFrom; use crate::mock::Test; @@ -1205,11 +1373,36 @@ mod sanitizers { } } - #[test] - fn candidates() { - new_test_ext(MockGenesisConfig::default()).execute_with(|| { + mod candidates { + use crate::{ + mock::set_disabled_validators, + scheduler::{common::Assignment, ParasEntry}, + }; + use sp_std::collections::vec_deque::VecDeque; + + use super::*; + + // Backed candidates and scheduled parachains used for `sanitize_backed_candidates` testing + struct TestData { + backed_candidates: Vec, + all_backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, + scheduled_paras: BTreeMap>, + } + + // Generate test data for the candidates and assert that the evnironment is set as expected + // (check the comments for details) + fn get_test_data(core_index_enabled: bool) -> TestData { const RELAY_PARENT_NUM: u32 = 3; + // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing + // votes) won't behave correctly + shared::Pallet::::add_allowed_relay_parent( + default_header().hash(), + Default::default(), + RELAY_PARENT_NUM, + 1, + ); + let header = default_header(); let relay_parent = header.hash(); let session_index = SessionIndex::from(0_u32); @@ -1223,6 +1416,7 @@ mod sanitizers { keyring::Sr25519Keyring::Bob, keyring::Sr25519Keyring::Charlie, keyring::Sr25519Keyring::Dave, + keyring::Sr25519Keyring::Eve, ]; for validator in validators.iter() { Keystore::sr25519_generate_new( @@ -1233,23 +1427,57 @@ mod sanitizers { .unwrap(); } - let has_concluded_invalid = - |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; + // Set active validators in `shared` pallet + let validator_ids = + validators.iter().map(|v| v.public().into()).collect::>(); + shared::Pallet::::set_active_validators_ascending(validator_ids); - let scheduled = (0_usize..2) + // Two scheduled parachains - ParaId(1) on CoreIndex(0) and ParaId(2) on CoreIndex(1) + let scheduled: BTreeMap> = (0_usize..2) .into_iter() - .map(|idx| (ParaId::from(1_u32 + idx as u32), CoreIndex::from(idx as u32))) + .map(|idx| { + ( + ParaId::from(1_u32 + idx as u32), + [CoreIndex::from(idx as u32)].into_iter().collect(), + ) + }) .collect::>(); + // Set the validator groups in `scheduler` + scheduler::Pallet::::set_validator_groups(vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2), ValidatorIndex(3)], + ]); + + // Update scheduler's claimqueue with the parachains + scheduler::Pallet::::set_claimqueue(BTreeMap::from([ + ( + CoreIndex::from(0), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(1), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(1) }, + RELAY_PARENT_NUM, + )]), + ), + ])); + + // Callback used for backing candidates let group_validators = |group_index: GroupIndex| { match group_index { group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), - _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + _ => panic!("Group index out of bounds"), } .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) }; + // One backed candidate from each parachain let backed_candidates = (0_usize..2) .into_iter() .map(|idx0| { @@ -1273,34 +1501,494 @@ mod sanitizers { &keystore, &signing_context, BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(idx0 as u32)), ); backed }) .collect::>(); - // happy path + // State sanity checks assert_eq!( - sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ), - backed_candidates + >::scheduled_paras().collect::>(), + vec![(CoreIndex(0), ParaId::from(1)), (CoreIndex(1), ParaId::from(2))] + ); + assert_eq!( + shared::Pallet::::active_validator_indices(), + vec![ + ValidatorIndex(0), + ValidatorIndex(1), + ValidatorIndex(2), + ValidatorIndex(3), + ValidatorIndex(4) + ] ); - // nothing is scheduled, so no paraids match, thus all backed candidates are skipped - { - let scheduled = &BTreeMap::new(); - assert!(sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled + let all_backed_candidates_with_core = backed_candidates + .iter() + .map(|candidate| { + // Only one entry for this test data. + ( + candidate.clone(), + scheduled + .get(&candidate.descriptor().para_id) + .unwrap() + .first() + .copied() + .unwrap(), + ) + }) + .collect(); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } + } + + // Generate test data for the candidates and assert that the evnironment is set as expected + // (check the comments for details) + // Para 1 scheduled on core 0 and core 1. Two candidates are supplied. + // Para 2 scheduled on cores 2 and 3. One candidate supplied. + // Para 3 scheduled on core 4. One candidate supplied. + // Para 4 scheduled on core 5. Two candidates supplied. + // Para 5 scheduled on core 6. No candidates supplied. + fn get_test_data_multiple_cores_per_para(core_index_enabled: bool) -> TestData { + const RELAY_PARENT_NUM: u32 = 3; + + // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing + // votes) won't behave correctly + shared::Pallet::::add_allowed_relay_parent( + default_header().hash(), + Default::default(), + RELAY_PARENT_NUM, + 1, + ); + + let header = default_header(); + let relay_parent = header.hash(); + let session_index = SessionIndex::from(0_u32); + + let keystore = LocalKeystore::in_memory(); + let keystore = Arc::new(keystore) as KeystorePtr; + let signing_context = SigningContext { parent_hash: relay_parent, session_index }; + + let validators = vec![ + keyring::Sr25519Keyring::Alice, + keyring::Sr25519Keyring::Bob, + keyring::Sr25519Keyring::Charlie, + keyring::Sr25519Keyring::Dave, + keyring::Sr25519Keyring::Eve, + keyring::Sr25519Keyring::Ferdie, + keyring::Sr25519Keyring::One, + ]; + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), ) - .is_empty()); + .unwrap(); } - // candidates that have concluded as invalid are filtered out + // Set active validators in `shared` pallet + let validator_ids = + validators.iter().map(|v| v.public().into()).collect::>(); + shared::Pallet::::set_active_validators_ascending(validator_ids); + + // Set the validator groups in `scheduler` + scheduler::Pallet::::set_validator_groups(vec![ + vec![ValidatorIndex(0)], + vec![ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3)], + vec![ValidatorIndex(4)], + vec![ValidatorIndex(5)], + vec![ValidatorIndex(6)], + ]); + + // Update scheduler's claimqueue with the parachains + scheduler::Pallet::::set_claimqueue(BTreeMap::from([ + ( + CoreIndex::from(0), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(1), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(1) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(2), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(2) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(3), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(3) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(4), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 3.into(), core_index: CoreIndex(4) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(5), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 4.into(), core_index: CoreIndex(5) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(6), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 5.into(), core_index: CoreIndex(6) }, + RELAY_PARENT_NUM, + )]), + ), + ])); + + // Callback used for backing candidates + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0]), + group_index if group_index == GroupIndex::from(1) => Some(vec![1]), + group_index if group_index == GroupIndex::from(2) => Some(vec![2]), + group_index if group_index == GroupIndex::from(3) => Some(vec![3]), + group_index if group_index == GroupIndex::from(4) => Some(vec![4]), + group_index if group_index == GroupIndex::from(5) => Some(vec![5]), + group_index if group_index == GroupIndex::from(6) => Some(vec![6]), + + _ => panic!("Group index out of bounds"), + } + .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) + }; + + let mut backed_candidates = vec![]; + let mut all_backed_candidates_with_core = vec![]; + + // Para 1 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(1 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed: BackedCandidate = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(0 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(0 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(0))); + } + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(2 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(1 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(1 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(1))); + } + } + + // Para 2 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(2), + relay_parent, + pov_hash: Hash::repeat_byte(3 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(2 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(2 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(2))); + } + } + + // Para 3 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(3), + relay_parent, + pov_hash: Hash::repeat_byte(4 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(4 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(4 as u32)), + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(4))); + } + + // Para 4 { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(5 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(5))); + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(6 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(5 as u32)), + ); + backed_candidates.push(backed.clone()); + } + + // No candidate for para 5. + + // State sanity checks + assert_eq!( + >::scheduled_paras().collect::>(), + vec![ + (CoreIndex(0), ParaId::from(1)), + (CoreIndex(1), ParaId::from(1)), + (CoreIndex(2), ParaId::from(2)), + (CoreIndex(3), ParaId::from(2)), + (CoreIndex(4), ParaId::from(3)), + (CoreIndex(5), ParaId::from(4)), + (CoreIndex(6), ParaId::from(5)), + ] + ); + let mut scheduled: BTreeMap> = BTreeMap::new(); + for (core_idx, para_id) in >::scheduled_paras() { + scheduled.entry(para_id).or_default().insert(core_idx); + } + + assert_eq!( + shared::Pallet::::active_validator_indices(), + vec![ + ValidatorIndex(0), + ValidatorIndex(1), + ValidatorIndex(2), + ValidatorIndex(3), + ValidatorIndex(4), + ValidatorIndex(5), + ValidatorIndex(6), + ] + ); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } + } + + #[rstest] + #[case(false)] + #[case(true)] + fn happy_path(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { + backed_candidates, + all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data(core_index_enabled); + + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; + + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled + ), + SanitizedBackedCandidates { + backed_candidates_with_core: all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: false + } + ); + }); + } + + #[rstest] + #[case(false)] + #[case(true)] + fn test_with_multiple_cores_per_para(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { + backed_candidates, + all_backed_candidates_with_core: expected_all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data_multiple_cores_per_para(core_index_enabled); + + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; + + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled + ), + SanitizedBackedCandidates { + backed_candidates_with_core: expected_all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: true + } + ); + }); + } + + // nothing is scheduled, so no paraids match, thus all backed candidates are skipped + #[rstest] + #[case(false, false)] + #[case(true, true)] + #[case(false, true)] + #[case(true, false)] + fn nothing_scheduled( + #[case] core_index_enabled: bool, + #[case] multiple_cores_per_para: bool, + ) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { backed_candidates, .. } = if multiple_cores_per_para { + get_test_data_multiple_cores_per_para(core_index_enabled) + } else { + get_test_data(core_index_enabled) + }; + let scheduled = BTreeMap::new(); + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; + + let SanitizedBackedCandidates { + backed_candidates_with_core: sanitized_backed_candidates, + votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, + } = sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled, + ); + + assert!(sanitized_backed_candidates.is_empty()); + assert!(!votes_from_disabled_were_dropped); + assert!(dropped_unscheduled_candidates); + }); + } + + // candidates that have concluded as invalid are filtered out + #[rstest] + #[case(false)] + #[case(true)] + fn invalid_are_filtered_out(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { backed_candidates, scheduled_paras: scheduled, .. } = + get_test_data(core_index_enabled); + // mark every second one as concluded invalid let set = { let mut set = std::collections::HashSet::new(); @@ -1313,16 +2001,146 @@ mod sanitizers { }; let has_concluded_invalid = |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); + let SanitizedBackedCandidates { + backed_candidates_with_core: sanitized_backed_candidates, + votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, + } = sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled, + ); + + assert_eq!(sanitized_backed_candidates.len(), backed_candidates.len() / 2); + assert!(!votes_from_disabled_were_dropped); + assert!(!dropped_unscheduled_candidates); + }); + } + + #[rstest] + #[case(false)] + #[case(true)] + fn disabled_non_signing_validator_doesnt_get_filtered(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); + + // Disable Eve + set_disabled_validators(vec![4]); + + let before = all_backed_candidates_with_core.clone(); + + // Eve is disabled but no backing statement is signed by it so nothing should be + // filtered + assert!(!filter_backed_statements_from_disabled_validators::( + &mut all_backed_candidates_with_core, + &>::allowed_relay_parents(), + core_index_enabled + )); + assert_eq!(all_backed_candidates_with_core, before); + }); + } + #[rstest] + #[case(false)] + #[case(true)] + fn drop_statements_from_disabled_without_dropping_candidate( + #[case] core_index_enabled: bool, + ) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); + + // Disable Alice + set_disabled_validators(vec![0]); + + // Update `minimum_backing_votes` in HostConfig. We want `minimum_backing_votes` set + // to one so that the candidate will have enough backing votes even after dropping + // Alice's one. + let mut hc = configuration::Pallet::::config(); + hc.minimum_backing_votes = 1; + configuration::Pallet::::force_set_active_config(hc); + + // Verify the initial state is as expected assert_eq!( - sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ) - .len(), - backed_candidates.len() / 2 + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 ); - } - }); + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!(validator_indices.get(0).unwrap(), true); + assert_eq!(validator_indices.get(1).unwrap(), true); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); + + assert!(filter_backed_statements_from_disabled_validators::( + &mut all_backed_candidates_with_core, + &>::allowed_relay_parents(), + core_index_enabled + )); + + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + + // there should still be two backed candidates + assert_eq!(all_backed_candidates_with_core.len(), 2); + // but the first one should have only one validity vote + assert_eq!( + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 1 + ); + // Validator 0 vote should be dropped, validator 1 - retained + assert_eq!(validator_indices.get(0).unwrap(), false); + assert_eq!(validator_indices.get(1).unwrap(), true); + // the second candidate shouldn't be modified + assert_eq!(all_backed_candidates_with_core.get(1).unwrap().0, untouched); + }); + } + + #[rstest] + #[case(false)] + #[case(true)] + fn drop_candidate_if_all_statements_are_from_disabled(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); + + // Disable Alice and Bob + set_disabled_validators(vec![0, 1]); + + // Verify the initial state is as expected + assert_eq!( + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 + ); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); + + assert!(filter_backed_statements_from_disabled_validators::( + &mut all_backed_candidates_with_core, + &>::allowed_relay_parents(), + core_index_enabled + )); + + assert_eq!(all_backed_candidates_with_core.len(), 1); + assert_eq!(all_backed_candidates_with_core.get(0).unwrap().0, untouched); + }); + } } } diff --git a/polkadot/runtime/parachains/src/paras_inherent/weights.rs b/polkadot/runtime/parachains/src/paras_inherent/weights.rs index 05cc53fae04652c188d62b57fa5281aa6bb88e22..0f4e5be572a66d16c1476ada7671c495a27bbc83 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/weights.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/weights.rs @@ -149,11 +149,11 @@ pub fn backed_candidate_weight( candidate: &BackedCandidate, ) -> Weight { set_proof_size_to_tx_size( - if candidate.candidate.commitments.new_validation_code.is_some() { + if candidate.candidate().commitments.new_validation_code.is_some() { <::WeightInfo as WeightInfo>::enter_backed_candidate_code_upgrade() } else { <::WeightInfo as WeightInfo>::enter_backed_candidates_variable( - candidate.validity_votes.len() as u32, + candidate.validity_votes().len() as u32, ) }, candidate, diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs index 35d92f71084fe64f1890f29d1ee840606e944beb..1bbd4dfb716f1f541b40bca3e3a434b21c5a7a81 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs @@ -45,6 +45,8 @@ pub fn validators() -> Vec { /// Implementation for the `validator_groups` function of the runtime API. pub fn validator_groups( ) -> (Vec>, GroupRotationInfo>) { + // This formula needs to be the same as the one we use + // when populating group_responsible in `availability_cores` let now = >::block_number() + One::one(); let groups = >::validator_groups(); @@ -62,7 +64,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(); @@ -95,6 +97,11 @@ pub fn availability_cores() -> Vec>::next_up_on_available(CoreIndex( i as u32, @@ -106,7 +113,7 @@ pub fn availability_cores() -> Vec( pub fn session_executor_params( session_index: SessionIndex, ) -> Option { - // This is to bootstrap the storage working around the runtime migration issue: - // https://github.com/paritytech/substrate/issues/9997 - // After the bootstrap is complete (no less than 7 session passed with the runtime) - // this code should be replaced with a pure - // >::session_executor_params(session_index) call. - match >::session_executor_params(session_index) { - Some(ep) => Some(ep), - None => Some(ExecutorParams::default()), - } + >::session_executor_params(session_index) } /// Implementation of `unapplied_slashes` runtime API diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index 24a076f3a44310990d61ad0076db39f4deb8f9e5..1fee1a4097d8ee465317e32274dcc6f14075a6ee 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -16,29 +16,30 @@ //! Put implementations of functions from staging APIs here. -use crate::shared; -use primitives::ValidatorIndex; -use sp_std::{collections::btree_map::BTreeMap, prelude::Vec}; +use crate::{configuration, initializer, shared}; +use primitives::{ + vstaging::{ApprovalVotingParams, NodeFeatures}, + ValidatorIndex, +}; +use sp_std::prelude::Vec; /// Implementation for `DisabledValidators` // CAVEAT: this should only be called on the node side // as it might produce incorrect results on session boundaries pub fn disabled_validators() -> Vec where - T: pallet_session::Config + shared::Config, + T: shared::Config, { - let shuffled_indices = >::active_validator_indices(); - // mapping from raw validator index to `ValidatorIndex` - // this computation is the same within a session, but should be cheap - let reverse_index = shuffled_indices - .iter() - .enumerate() - .map(|(i, v)| (v.0, ValidatorIndex(i as u32))) - .collect::>(); - - // we might have disabled validators who are not parachain validators - >::disabled_validators() - .iter() - .filter_map(|v| reverse_index.get(v).cloned()) - .collect() + >::disabled_validators() +} + +/// Returns the current state of the node features. +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 f3333d5cf8577eac6567e4cbd53f1a5433de4241..f231864a85e7261d999c1c1ec34679bb0825d56c 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -37,7 +37,7 @@ //! availability cores over time. use crate::{configuration, initializer::SessionChangeNotification, paras}; -use frame_support::pallet_prelude::*; +use frame_support::{pallet_prelude::*, traits::Defensive}; use frame_system::pallet_prelude::BlockNumberFor; pub use polkadot_core_primitives::v2::BlockNumber; use primitives::{ @@ -51,7 +51,7 @@ use sp_std::{ pub mod common; -use common::{Assignment, AssignmentProvider, AssignmentProviderConfig}; +use common::{Assignment, AssignmentProvider}; pub use pallet::*; @@ -59,13 +59,14 @@ pub use pallet::*; mod tests; const LOG_TARGET: &str = "runtime::parachains::scheduler"; + pub mod migration; #[frame_support::pallet] 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] @@ -88,10 +89,8 @@ pub mod pallet { #[pallet::getter(fn validator_groups)] pub(crate) type ValidatorGroups = StorageValue<_, Vec>, ValueQuery>; - /// One entry for each availability core. Entries are `None` if the core is not currently - /// occupied. Can be temporarily `Some` if scheduled but not occupied. - /// The i'th parachain belongs to the i'th core, with the remaining cores all being - /// parathread-multiplexers. + /// One entry for each availability core. The i'th parachain belongs to the i'th core, with the + /// remaining cores all being on demand parachain multiplexers. /// /// Bounded by the maximum of either of these two values: /// * The number of parachains and parathread multiplexers @@ -99,15 +98,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 +113,9 @@ pub mod pallet { Paras(ParasEntry), } + /// Convenience type alias for `CoreOccupied`. + pub type CoreOccupiedType = CoreOccupied>; + impl CoreOccupied { /// Is core free? pub fn is_free(&self) -> bool { @@ -143,22 +144,17 @@ pub mod pallet { pub(crate) type SessionStartBlock = StorageValue<_, BlockNumberFor, ValueQuery>; /// One entry for each availability core. The `VecDeque` represents the assignments to be - /// scheduled on that core. `None` is used to signal to not schedule the next para of the core - /// as there is one currently being scheduled. Not using `None` here would overwrite the - /// `CoreState` in the runtime API. The value contained here will not be valid after the end of - /// a block. Runtime APIs should be used to determine scheduled cores/ for the upcoming block. + /// scheduled on that core. The value contained here will not be valid after the end of + /// a block. Runtime APIs should be used to determine scheduled cores for the upcoming block. #[pallet::storage] #[pallet::getter(fn claimqueue)] - 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 +165,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 +196,6 @@ pub mod pallet { } type PositionInClaimqueue = u32; -type TimedoutParas = BTreeMap>>; -type ConcludedParas = BTreeMap; impl Pallet { /// Called by the initializer to initialize the scheduler pallet. @@ -246,22 +221,30 @@ impl Pallet { let n_cores = core::cmp::max( T::AssignmentProvider::session_core_count(), - match config.max_validators_per_core { + match config.scheduler_params.max_validators_per_core { Some(x) if x != 0 => validators.len() as u32 / x, _ => 0, }, ); AvailabilityCores::::mutate(|cores| { - cores.resize(n_cores as _, CoreOccupied::Free); + cores.resize_with(n_cores as _, || CoreOccupied::Free); }); // shuffle validators into groups. if n_cores == 0 || validators.is_empty() { ValidatorGroups::::set(Vec::new()); } else { - let group_base_size = validators.len() / n_cores as usize; - let n_larger_groups = validators.len() % n_cores as usize; + let group_base_size = validators + .len() + .checked_div(n_cores as usize) + .defensive_proof("n_cores should not be 0") + .unwrap_or(0); + let n_larger_groups = validators + .len() + .checked_rem(n_cores as usize) + .defensive_proof("n_cores should not be 0") + .unwrap_or(0); // Groups contain indices into the validators from the session change notification, // which are already shuffled. @@ -298,9 +281,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 +292,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; }) }); @@ -352,7 +335,6 @@ impl Pallet { None }, Ok((pos_in_claimqueue, pe)) => { - // is this correct? availability_cores[core_idx.0 as usize] = CoreOccupied::Paras(pe); Some((*core_idx, pos_in_claimqueue)) @@ -375,35 +357,40 @@ impl Pallet { fn drop_expired_claims_from_claimqueue() { let now = >::block_number(); let availability_cores = AvailabilityCores::::get(); + let ttl = >::config().scheduler_params.ttl; ClaimQueue::::mutate(|cq| { 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) + { + core_claimqueue.push_back(ParasEntry::new(assignment, now + ttl)); } } } @@ -447,7 +434,7 @@ impl Pallet { } let rotations_since_session_start: BlockNumberFor = - (at - session_start_block) / config.group_rotation_frequency.into(); + (at - session_start_block) / config.scheduler_params.group_rotation_frequency; let rotations_since_session_start = as TryInto>::try_into(rotations_since_session_start) @@ -479,9 +466,9 @@ impl Pallet { // Note: blocks backed in this rotation will never time out here as backed_in + // config.paras_availability_period will always be > now for these blocks, as // otherwise above condition would not be true. - pending_since + config.paras_availability_period + pending_since + config.scheduler_params.paras_availability_period } else { - next_rotation + config.paras_availability_period + next_rotation + config.scheduler_params.paras_availability_period }; AvailabilityTimeoutStatus { timed_out: time_out_at <= now, live_until: time_out_at } @@ -497,7 +484,8 @@ impl Pallet { let now = >::block_number() + One::one(); let rotation_info = Self::group_rotation_info(now); - let current_window = rotation_info.last_rotation_at() + config.paras_availability_period; + let current_window = + rotation_info.last_rotation_at() + config.scheduler_params.paras_availability_period; now < current_window } @@ -507,7 +495,7 @@ impl Pallet { ) -> GroupRotationInfo> { let session_start_block = Self::session_start_block(); let group_rotation_frequency = - >::config().group_rotation_frequency; + >::config().scheduler_params.group_rotation_frequency; GroupRotationInfo { session_start_block, now, group_rotation_frequency } } @@ -515,20 +503,20 @@ 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 } } /// Return the next thing that will be scheduled on this core assuming it is currently /// occupied and the candidate occupying it times out. pub(crate) fn next_up_on_time_out(core: CoreIndex) -> Option { + let max_availability_timeouts = + >::config().scheduler_params.max_availability_timeouts; Self::next_up_on_available(core).or_else(|| { // Or, if none, the claim currently occupying the core, // as it would be put back on the queue after timing out if number of retries is not at @@ -536,16 +524,12 @@ impl Pallet { let cores = AvailabilityCores::::get(); cores.get(core.0 as usize).and_then(|c| match c { CoreOccupied::Free => None, - CoreOccupied::Paras(pe) => { - let AssignmentProviderConfig { max_availability_timeouts, .. } = - T::AssignmentProvider::get_provider_config(core); - + CoreOccupied::Paras(pe) => if pe.availability_timeouts < max_availability_timeouts { Some(Self::paras_entry_to_scheduled_core(pe)) } else { None - } - }, + }, }) }) } @@ -553,35 +537,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); } } @@ -589,34 +571,11 @@ impl Pallet { // ClaimQueue related functions // fn claimqueue_lookahead() -> u32 { - >::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); + >::config().scheduler_params.lookahead } /// 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, ) { @@ -631,15 +590,15 @@ impl Pallet { let n_lookahead = Self::claimqueue_lookahead().max(1); let n_session_cores = T::AssignmentProvider::session_core_count(); let cq = ClaimQueue::::get(); - let ttl = >::config().on_demand_ttl; + let config = >::config(); + let max_availability_timeouts = config.scheduler_params.max_availability_timeouts; + let ttl = config.scheduler_params.ttl; for core_idx in 0..n_session_cores { let core_idx = CoreIndex::from(core_idx); // add previously timedout paras back into the queue if let Some(mut entry) = timedout_paras.remove(&core_idx) { - let AssignmentProviderConfig { max_availability_timeouts, .. } = - T::AssignmentProvider::get_provider_config(core_idx); if entry.availability_timeouts < max_availability_timeouts { // Increment the timeout counter. entry.availability_timeouts += 1; @@ -652,19 +611,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)); } } @@ -681,9 +640,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); }); } @@ -691,19 +650,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)) }) @@ -711,23 +667,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))) - } - - #[cfg(any(feature = "runtime-benchmarks", test))] - pub(crate) fn assignment_provider_config( - core_idx: CoreIndex, - ) -> AssignmentProviderConfig> { - T::AssignmentProvider::get_provider_config(core_idx) + .filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.assignment.para_id()))) } #[cfg(any(feature = "try-runtime", test))] @@ -744,4 +687,9 @@ impl Pallet { pub(crate) fn set_validator_groups(validator_groups: Vec>) { ValidatorGroups::::set(validator_groups); } + + #[cfg(test)] + pub(crate) fn set_claimqueue(claimqueue: BTreeMap>>) { + ClaimQueue::::set(claimqueue); + } } diff --git a/polkadot/runtime/parachains/src/scheduler/common.rs b/polkadot/runtime/parachains/src/scheduler/common.rs index 316e8e3b760cc6a73c022f693e12ca537bf3443b..66a4e6d30be0830650295d5311b7d7e1fc3b1d20 100644 --- a/polkadot/runtime/parachains/src/scheduler/common.rs +++ b/polkadot/runtime/parachains/src/scheduler/common.rs @@ -16,57 +16,72 @@ //! 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, + } } } -/// 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. - /// Zero timeouts still means that a collation can be provided as per the slot auction - /// assignment provider. - pub max_availability_timeouts: u32, - - /// How long the collator has to provide a collation to the backing group before being dropped. - pub ttl: BlockNumber, -} - 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; + /// + /// This is where assignments come into existence. + 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 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); + /// 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 popped 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..e6a1e66b3a531906fa6cbf763f248f6f6108a0f3 100644 --- a/polkadot/runtime/parachains/src/scheduler/tests.rs +++ b/polkadot/runtime/parachains/src/scheduler/tests.rs @@ -18,28 +18,30 @@ use super::*; use frame_support::assert_ok; use keyring::Sr25519Keyring; -use primitives::{BlockNumber, SessionIndex, ValidationCode, ValidatorId}; +use primitives::{ + vstaging::SchedulerParams, 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 +80,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,16 +105,19 @@ fn run_to_end_of_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_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 // `thread_availability_period`. minimum_validation_upgrade_delay: 6, + scheduler_params: SchedulerParams { + group_rotation_frequency: 10, + paras_availability_period: 3, + lookahead: 2, + num_cores: 3, + max_availability_timeouts: 1, + ..Default::default() + }, ..Default::default() } } @@ -124,29 +129,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,10 +150,18 @@ 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(); - config.scheduling_lookahead = 3; + config.scheduler_params.lookahead = 3; let genesis_config = genesis_config(&config); let para_id = ParaId::from(100); @@ -169,13 +169,13 @@ fn claimqueue_ttl_drop_fn_works() { let mut now = 10; new_test_ext(genesis_config).execute_with(|| { - assert!(default_config().on_demand_ttl == 5); + assert!(config.scheduler_params.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 +186,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 +201,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. @@ -213,93 +213,54 @@ fn claimqueue_ttl_drop_fn_works() { // Drop expired claim. Scheduler::drop_expired_claims_from_claimqueue(); + assert!(!claimqueue_contains_para_ids::(vec![para_id])); // 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()); Scheduler::add_to_claimqueue(core_idx, paras_entry_non_expired.clone()); let cq = Scheduler::claimqueue(); - assert!(cq.get(&core_idx).unwrap().len() == 3); + assert_eq!(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(); let cq = Scheduler::claimqueue(); let cqc = cq.get(&core_idx).unwrap(); - // Same number of claims - assert!(cqc.len() == 3); + // Same number of claims, because a new claim is popped from `MockAssigner` instead of the + // expired one + assert_eq!(cqc.len(), 3); // 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,22 +297,15 @@ 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.scheduler_params.max_validators_per_core = Some(1); config }; 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 +328,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); } @@ -383,141 +337,74 @@ fn session_change_takes_only_max_per_core() { #[test] 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 config = default_config(); + let genesis_config = genesis_config(&config); - 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 coretime_ttl = config.scheduler_params.ttl; - // 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, 2 validators for 2 cores. 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, }); - { - 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 + coretime_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 + coretime_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 + coretime_ttl }, ); } @@ -529,39 +416,32 @@ fn schedule_schedules_including_just_freed() { let mut config = default_config(); // NOTE: This test expects on demand cores to each get slotted on to a different core // and not fill up the claimqueue of each core first. - config.scheduling_lookahead = 1; + config.scheduler_params.lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_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 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 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); - - // register 2 lease holding parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + MockAssigner::set_core_count(3); - // 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); + // 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); - // 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 +449,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_eq!(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 }, + assignment: Assignment::Bulk(para_d), availability_timeouts: 0, ttl: 8 }, ); + // Although C was descheduled, the core `2` was occupied so C goes back to the queue. assert_eq!( - scheduled.get(&CoreIndex(2)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: thread_d }, - availability_timeouts: 0, - ttl: 8 - }, - ); - // Although C was descheduled, the core `4` 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])); } }); } @@ -723,31 +563,38 @@ fn schedule_schedules_including_just_freed() { #[test] fn schedule_clears_availability_cores() { let mut config = default_config(); - config.scheduling_lookahead = 1; + config.scheduler_params.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 +607,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 +619,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 +640,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 +665,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.scheduler_params.lookahead = 1; config }; - let rotation_frequency = config.group_rotation_frequency; - let on_demand_cores = config.on_demand_cores; + let rotation_frequency = config.scheduler_params.group_rotation_frequency; + 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 +702,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); @@ -906,19 +748,21 @@ fn schedule_rotates_groups() { #[test] fn on_demand_claims_are_pruned_after_timing_out() { - let max_retries = 20; + let max_timeouts = 20; let mut config = default_config(); - config.scheduling_lookahead = 1; - config.on_demand_cores = 2; - config.on_demand_retries = max_retries; + config.scheduler_params.lookahead = 1; + // Need more timeouts for this test + config.scheduler_params.max_availability_timeouts = max_timeouts; + config.scheduler_params.ttl = BlockNumber::from(5u32); 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); + schedule_blank_para(para_a); // #1 let mut now = 1; @@ -934,30 +778,27 @@ 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; // Run to block #n over the max_retries value. // In this case, both validator groups with time out on availability and // the assignment will be dropped. - for n in now..=(now + max_retries + 1) { + for n in now..=(now + max_timeouts + 1) { // #n run_to_block(n, |_| None); // Time out on core 0. @@ -966,41 +807,37 @@ 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])); + if n < max_timeouts + now { + 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; + now += max_timeouts + 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; // Run to block #n but this time have group 1 conclude the availabilty. - for n in now..=(now + max_retries + 1) { + for n in now..=(now + max_timeouts + 1) { // #n run_to_block(n, |_| None); // Time out core 0 if group 0 is assigned to it, if group 1 is assigned, conclude. @@ -1017,24 +854,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])); }); } @@ -1042,45 +878,10 @@ fn on_demand_claims_are_pruned_after_timing_out() { fn availability_predicate_works() { let genesis_config = genesis_config(&default_config()); - let HostConfiguration { group_rotation_frequency, paras_availability_period, .. } = - default_config(); - - assert!(paras_availability_period < group_rotation_frequency); - - let chain_a = ParaId::from(1_u32); - let thread_a = ParaId::from(2_u32); + let SchedulerParams { group_rotation_frequency, paras_availability_period, .. } = + default_config().scheduler_params; 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 +904,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 +932,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 +952,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 +975,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 +1001,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 +1010,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.scheduler_params.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 } - ); - } - }); -} -#[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 para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); - 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); + // Setting explicit core count + MockAssigner::set_core_count(5); + let coretime_ttl = >::config().scheduler_params.ttl; - // 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, - }); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - 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!("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 } - ); - } - }); -} - -#[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 +1090,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 +1113,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 - ))] + coretime_ttl + 2 + )] .into_iter() .collect() )] @@ -1423,8 +1131,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 +1161,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 - ))] + coretime_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 - ))] + coretime_ttl + 3 + )] .into_iter() .collect() ), diff --git a/polkadot/runtime/parachains/src/session_info/migration.rs b/polkadot/runtime/parachains/src/session_info/migration.rs index 228c1e3bb2515b14f5fe59a89578b03be065955d..ea6f81834b5db6eb7336521d3d471a6c3df49985 100644 --- a/polkadot/runtime/parachains/src/session_info/migration.rs +++ b/polkadot/runtime/parachains/src/session_info/migration.rs @@ -18,5 +18,5 @@ use frame_support::traits::StorageVersion; -/// The current storage version. +/// The in-code storage version. pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); diff --git a/polkadot/runtime/parachains/src/session_info/tests.rs b/polkadot/runtime/parachains/src/session_info/tests.rs index 727b7c79fbaeae18be2c2a40f4f38f562d1481dd..a5bfeae0745599c00de718684420fc377a9184f1 100644 --- a/polkadot/runtime/parachains/src/session_info/tests.rs +++ b/polkadot/runtime/parachains/src/session_info/tests.rs @@ -25,7 +25,7 @@ use crate::{ util::take_active_subset, }; use keyring::Sr25519Keyring; -use primitives::{BlockNumber, ValidatorId, ValidatorIndex}; +use primitives::{vstaging::SchedulerParams, BlockNumber, ValidatorId, ValidatorIndex}; fn run_to_block( to: BlockNumber, @@ -62,9 +62,9 @@ fn run_to_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_cores: 1, dispute_period: 2, needed_approvals: 3, + scheduler_params: SchedulerParams { num_cores: 1, ..Default::default() }, ..Default::default() } } diff --git a/polkadot/runtime/parachains/src/shared.rs b/polkadot/runtime/parachains/src/shared.rs index ad13c9e48448fa888573b9361d1aa59debbf781e..bdaffcd505f8e3bfc0c62d1e5f5def4fb3617abc 100644 --- a/polkadot/runtime/parachains/src/shared.rs +++ b/polkadot/runtime/parachains/src/shared.rs @@ -19,11 +19,14 @@ //! To avoid cyclic dependencies, it is important that this pallet is not //! dependent on any of the other pallets. -use frame_support::pallet_prelude::*; +use frame_support::{pallet_prelude::*, traits::DisabledValidators}; use frame_system::pallet_prelude::BlockNumberFor; use primitives::{SessionIndex, ValidatorId, ValidatorIndex}; use sp_runtime::traits::AtLeast32BitUnsigned; -use sp_std::{collections::vec_deque::VecDeque, vec::Vec}; +use sp_std::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + vec::Vec, +}; use rand::{seq::SliceRandom, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -129,7 +132,9 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: frame_system::Config { + type DisabledValidators: frame_support::traits::DisabledValidators; + } /// The current session index. #[pallet::storage] @@ -216,6 +221,25 @@ impl Pallet { Self::session_index().saturating_add(SESSION_DELAY) } + /// Fetches disabled validators list from session pallet. + /// CAVEAT: this might produce incorrect results on session boundaries + pub fn disabled_validators() -> Vec { + let shuffled_indices = Pallet::::active_validator_indices(); + // mapping from raw validator index to `ValidatorIndex` + // this computation is the same within a session, but should be cheap + let reverse_index = shuffled_indices + .iter() + .enumerate() + .map(|(i, v)| (v.0, ValidatorIndex(i as u32))) + .collect::>(); + + // we might have disabled validators who are not parachain validators + T::DisabledValidators::disabled_validators() + .iter() + .filter_map(|v| reverse_index.get(v).cloned()) + .collect() + } + /// Test function for setting the current session index. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn set_session_index(index: SessionIndex) { @@ -239,4 +263,16 @@ impl Pallet { ActiveValidatorIndices::::set(indices); ActiveValidatorKeys::::set(keys); } + + #[cfg(test)] + pub(crate) fn add_allowed_relay_parent( + relay_parent: T::Hash, + state_root: T::Hash, + number: BlockNumberFor, + max_ancestry_len: u32, + ) { + AllowedRelayParents::::mutate(|tracker| { + tracker.update(relay_parent, state_root, number, max_ancestry_len) + }) + } } diff --git a/polkadot/runtime/parachains/src/ump_tests.rs b/polkadot/runtime/parachains/src/ump_tests.rs index e96b5e9689272a40d7977a43b5d3ed961a3926f1..2ed1d64336b31e42d1fd0363143a7d17a66c7b97 100644 --- a/polkadot/runtime/parachains/src/ump_tests.rs +++ b/polkadot/runtime/parachains/src/ump_tests.rs @@ -31,8 +31,7 @@ use frame_support::{ weights::Weight, }; use primitives::{well_known_keys, Id as ParaId, UpwardMessage}; -use sp_core::twox_64; -use sp_io::hashing::blake2_256; +use sp_crypto_hashing::{blake2_256, twox_64}; use sp_runtime::traits::Bounded; use sp_std::prelude::*; @@ -386,7 +385,7 @@ fn relay_dispatch_queue_size_is_updated() { MessageQueue::service_queues(Weight::from_all(u64::MAX)); let fp = MessageQueue::footprint(AggregateMessageOrigin::Ump(para)); - let (para_queue_count, para_queue_size) = (fp.count, fp.size); + let (para_queue_count, para_queue_size) = (fp.storage.count, fp.storage.size); assert_eq!(para_queue_count, 1, "count wrong for para: {}", p); assert_eq!(para_queue_size, 8, "size wrong for para: {}", p); } @@ -400,7 +399,7 @@ fn relay_dispatch_queue_size_is_updated() { assert_queue_remaining(p.into(), cfg.max_upward_queue_count, cfg.max_upward_queue_size); let fp = MessageQueue::footprint(AggregateMessageOrigin::Ump(para)); - let (para_queue_count, para_queue_size) = (fp.count, fp.size); + let (para_queue_count, para_queue_size) = (fp.storage.count, fp.storage.size); assert_eq!(para_queue_count, 0, "count wrong for para: {}", p); assert_eq!(para_queue_size, 0, "size wrong for para: {}", p); } @@ -505,6 +504,10 @@ fn overweight_queue_works() { let a_msg_2 = (501u32, "a_msg_2").encode(); let a_msg_3 = (501u32, "a_msg_3").encode(); + let hash_1 = blake2_256(&a_msg_1[..]); + let hash_2 = blake2_256(&a_msg_2[..]); + let hash_3 = blake2_256(&a_msg_3[..]); + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { // HACK: Start with the block number 1. This is needed because should an event be // emitted during the genesis block they will be implicitly wiped. @@ -517,27 +520,24 @@ fn overweight_queue_works() { queue_upward_msg(para_a, a_msg_3.clone()); MessageQueue::service_queues(Weight::from_parts(500, 500)); - let hash_1 = blake2_256(&a_msg_1[..]); - let hash_2 = blake2_256(&a_msg_2[..]); - let hash_3 = blake2_256(&a_msg_3[..]); 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 6d0dee3e4343497f259733ffd17767e9389f2454..3dc59cc172817409bd607f5b68e52163a7b9afdb 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -1,18 +1,21 @@ [package] name = "rococo-runtime" build = "build.rs" -version = "1.0.0" +version = "7.0.0" description = "Rococo testnet Relay Chain runtime." 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.188", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +log = { workspace = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } static_assertions = "1.1.0" smallvec = "1.8.0" @@ -53,7 +56,7 @@ pallet-collective = { path = "../../../substrate/frame/collective", default-feat pallet-conviction-voting = { path = "../../../substrate/frame/conviction-voting", default-features = false } pallet-democracy = { path = "../../../substrate/frame/democracy", default-features = false } pallet-elections-phragmen = { path = "../../../substrate/frame/elections-phragmen", default-features = false } -pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false } +pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false } frame-executive = { path = "../../../substrate/frame/executive", default-features = false } pallet-grandpa = { path = "../../../substrate/frame/grandpa", default-features = false } pallet-identity = { path = "../../../substrate/frame/identity", default-features = false } @@ -86,6 +89,7 @@ pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = pallet-whitelist = { path = "../../../substrate/frame/whitelist", default-features = false } pallet-xcm = { path = "../../xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-features = false, optional = true } +pallet-root-testing = { path = "../../../substrate/frame/root-testing", default-features = false } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-try-runtime = { path = "../../../substrate/frame/try-runtime", default-features = false, optional = true } @@ -104,10 +108,10 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", [dev-dependencies] tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } -remote-externalities = { package = "frame-remote-externalities" , path = "../../../substrate/utils/frame/remote-externalities" } +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.107" +serde_json = { workspace = true, default-features = true } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } tokio = { version = "1.24.2", features = ["macros"] } @@ -115,7 +119,7 @@ tokio = { version = "1.24.2", features = ["macros"] } substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] no_std = [] std = [ "authority-discovery-primitives/std", @@ -161,6 +165,7 @@ std = [ "pallet-ranked-collective/std", "pallet-recovery/std", "pallet-referenda/std", + "pallet-root-testing/std", "pallet-scheduler/std", "pallet-session/std", "pallet-society/std", @@ -290,6 +295,7 @@ try-runtime = [ "pallet-ranked-collective/try-runtime", "pallet-recovery/try-runtime", "pallet-referenda/try-runtime", + "pallet-root-testing/try-runtime", "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-society/try-runtime", @@ -310,11 +316,11 @@ try-runtime = [ ] # Set timing constants (e.g. session period) to faster versions to speed up testing. -fast-runtime = [] +fast-runtime = ["rococo-runtime-constants/fast-runtime"] -runtime-metrics = [ "runtime-parachains/runtime-metrics", "sp-io/with-tracing" ] +runtime-metrics = ["runtime-parachains/runtime-metrics", "sp-io/with-tracing"] -# A feature that should be enabled when the runtime should be build for on-chain +# 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" ] +# to make it smaller, like logging for example. +on-chain-release-build = ["sp-api/disable-logging"] diff --git a/polkadot/runtime/rococo/build.rs b/polkadot/runtime/rococo/build.rs index ed32d33105b76c45bac68360e92646019e578b2e..0b7ee77b0d0d3b984a168f8cefe02a9e00e8f86c 100644 --- a/polkadot/runtime/rococo/build.rs +++ b/polkadot/runtime/rococo/build.rs @@ -16,16 +16,19 @@ #[cfg(feature = "std")] fn main() { - // note: needs to be synced with rococo-runtime-constants::time hard-coded string literal - const ROCOCO_EPOCH_DURATION_ENV: &str = "ROCOCO_EPOCH_DURATION"; - substrate_wasm_builder::WasmBuilder::new() .with_current_project() .import_memory() .export_heap_base() .build(); - println!("cargo:rerun-if-env-changed={}", ROCOCO_EPOCH_DURATION_ENV); + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .import_memory() + .export_heap_base() + .build(); } #[cfg(not(feature = "std"))] diff --git a/polkadot/runtime/rococo/constants/Cargo.toml b/polkadot/runtime/rococo/constants/Cargo.toml index f99dbd1233678862c1ace1463bbf4a7767870a1f..3ca3877a7650e453c25851ddb35899478a67faeb 100644 --- a/polkadot/runtime/rococo/constants/Cargo.toml +++ b/polkadot/runtime/rococo/constants/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "rococo-runtime-constants" -version = "1.0.0" +version = "7.0.0" description = "Constants used throughout the Rococo network." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] smallvec = "1.8.0" @@ -17,9 +20,10 @@ 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" ] +default = ["std"] std = [ "frame-support/std", "primitives/std", @@ -27,5 +31,9 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-weights/std", + "xcm-builder/std", "xcm/std", ] + +# Set timing constants (e.g. session period) to faster versions to speed up testing. +fast-runtime = [] diff --git a/polkadot/runtime/rococo/constants/src/lib.rs b/polkadot/runtime/rococo/constants/src/lib.rs index 84594cffcf39edb56510c5f4b2f8f32a4f65cd9c..9209045364c28bc585c548d6d2b30176bd52bb20 100644 --- a/polkadot/runtime/rococo/constants/src/lib.rs +++ b/polkadot/runtime/rococo/constants/src/lib.rs @@ -37,14 +37,15 @@ pub mod currency { /// Time and blocks. pub mod time { + use runtime_common::prod_or_fast; + use primitives::{BlockNumber, Moment}; pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; frame_support::parameter_types! { - pub storage EpochDurationInBlocks: BlockNumber = option_env!("ROCOCO_EPOCH_DURATION") - .map(|s| s.parse().expect("`ROCOCO_EPOCH_DURATION` is not a valid `BlockNumber`")) - .unwrap_or(1 * MINUTES); + pub EpochDurationInBlocks: BlockNumber = + prod_or_fast!(1 * HOURS, 1 * MINUTES, "ROCOCO_EPOCH_DURATION"); } // These time units are defined in number of blocks. @@ -102,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; @@ -110,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/governance/fellowship.rs b/polkadot/runtime/rococo/src/governance/fellowship.rs index b5df6cf2df341c5563f31ea6f49f1eeae5b81fc1..a589b768afde2c0757e74a6535509228f1613a82 100644 --- a/polkadot/runtime/rococo/src/governance/fellowship.rs +++ b/polkadot/runtime/rococo/src/governance/fellowship.rs @@ -17,7 +17,7 @@ //! Elements of governance concerning the Rococo Fellowship. use frame_support::traits::{MapSuccess, TryMapSuccess}; -use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace}; +use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace, ReplaceWithDefault}; use super::*; use crate::{CENTS, DAYS}; @@ -315,6 +315,11 @@ pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; impl pallet_ranked_collective::Config for Runtime { type WeightInfo = weights::pallet_ranked_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + // Adding is by any of: + // - Root. + // - the FellowshipAdmin origin. + // - a Fellowship origin. + type AddOrigin = MapSuccess>; // Promotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); @@ -326,6 +331,11 @@ impl pallet_ranked_collective::Config for Runtime TryMapSuccess>>, >, >; + // Removing is by any of: + // - Root can remove arbitrarily. + // - the FellowshipAdmin origin (i.e. token holder referendum); + // - a vote by the rank two above the current rank. + type RemoveOrigin = Self::DemoteOrigin; // Demotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); @@ -337,7 +347,15 @@ impl pallet_ranked_collective::Config for Runtime TryMapSuccess>>, >, >; + // Exchange is by any of: + // - Root can exchange arbitrarily. + // - the Fellows origin; + type ExchangeOrigin = + EitherOf>, Fellows>; type Polls = FellowshipReferenda; type MinRankOfClass = sp_runtime::traits::Identity; + type MemberSwappedHandler = (); type VoteWeight = pallet_ranked_collective::Geometric; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkSetup = (); } diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs new file mode 100644 index 0000000000000000000000000000000000000000..ac7100d7858377dca5991e0d0308dc64577b9350 --- /dev/null +++ b/polkadot/runtime/rococo/src/impls.rs @@ -0,0 +1,178 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::xcm_config; +use frame_support::pallet_prelude::DispatchResult; +use frame_system::RawOrigin; +use parity_scale_codec::{Decode, Encode}; +use primitives::Balance; +use rococo_runtime_constants::currency::*; +use runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; +use sp_std::{marker::PhantomData, prelude::*}; +use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_executor::traits::TransactAsset; + +/// A type containing the encoding of the People Chain pallets in its runtime. Used to construct any +/// remote calls. The codec index must correspond to the index of `IdentityMigrator` in the +/// `construct_runtime` of the remote chain. +#[derive(Encode, Decode)] +enum PeopleRuntimePallets { + #[codec(index = 248)] + IdentityMigrator(IdentityMigratorCalls), +} + +/// Call encoding for the calls needed from the Identity Migrator pallet. +#[derive(Encode, Decode)] +enum IdentityMigratorCalls { + #[codec(index = 1)] + PokeDeposit(AccountId), +} + +/// Type that implements `OnReapIdentity` that will send the deposit needed to store the same +/// information on a parachain, sends the deposit there, and then updates it. +pub struct ToParachainIdentityReaper(PhantomData<(Runtime, AccountId)>); +impl ToParachainIdentityReaper { + /// Calculate the balance needed on the remote chain based on the `IdentityInfo` and `Subs` on + /// this chain. The total includes: + /// + /// - Identity basic deposit + /// - `IdentityInfo` byte deposit + /// - Sub accounts deposit + /// - 2x existential deposit (1 for account existence, 1 such that the user can transact) + fn calculate_remote_deposit(bytes: u32, subs: u32) -> Balance { + // Remote deposit constants. Parachain uses `deposit / 100` + // Source: + // https://github.com/paritytech/polkadot-sdk/blob/a146918/cumulus/parachains/common/src/rococo.rs#L29 + // + // Parachain Deposit Configuration: + // + // pub const BasicDeposit: Balance = deposit(1, 17); + // pub const ByteDeposit: Balance = deposit(0, 1); + // pub const SubAccountDeposit: Balance = deposit(1, 53); + // pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; + 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)) + } +} + +// Note / Warning: This implementation should only be used in a transactional context. If not, then +// an error could result in assets being burned. +impl OnReapIdentity for ToParachainIdentityReaper +where + Runtime: frame_system::Config + pallet_xcm::Config, + AccountId: Into<[u8; 32]> + Clone + Encode, +{ + fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; + + let total_to_send = Self::calculate_remote_deposit(fields, subs); + + // define asset / destination from relay perspective + let roc = Asset { id: AssetId(Here.into_location()), fun: Fungible(total_to_send) }; + // People Chain: ParaId 1004 + let destination: Location = Location::new(0, Parachain(1004)); + + // Do `check_out` accounting since the XCM Executor's `InitiateTeleport` doesn't support + // unpaid teleports. + + // withdraw the asset from `who` + let who_origin = + Junction::AccountId32 { network: None, id: who.clone().into() }.into_location(); + let _withdrawn = xcm_config::LocalAssetTransactor::withdraw_asset(&roc, &who_origin, None) + .map_err(|err| { + log::error!( + target: "runtime::on_reap_identity", + "withdraw_asset(what: {:?}, who_origin: {:?}) error: {:?}", + roc, who_origin, err + ); + pallet_xcm::Error::::LowBalance + })?; + + // check out + xcm_config::LocalAssetTransactor::can_check_out( + &destination, + &roc, + // not used in AssetTransactor + &XcmContext { origin: None, message_id: [0; 32], topic: None }, + ) + .map_err(|err| { + log::error!( + target: "runtime::on_reap_identity", + "can_check_out(destination: {:?}, asset: {:?}, _) error: {:?}", + destination, roc, err + ); + pallet_xcm::Error::::CannotCheckOutTeleport + })?; + xcm_config::LocalAssetTransactor::check_out( + &destination, + &roc, + // not used in AssetTransactor + &XcmContext { origin: None, message_id: [0; 32], topic: None }, + ); + + // reanchor + let roc_reanchored: Assets = + vec![Asset { id: AssetId(Location::new(1, Here)), fun: Fungible(total_to_send) }] + .into(); + + let poke = PeopleRuntimePallets::::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::::poke_deposit().saturating_mul(2); + + // Actual program to execute on People Chain. + let program: Xcm<()> = Xcm(vec![ + // Unpaid as this is constructed by the system, once per user. The user shouldn't have + // their balance reduced by teleport fees for the favor of migrating. + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + // Receive the asset into holding. + ReceiveTeleportedAsset(roc_reanchored), + // Deposit into the user's account. + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Junction::AccountId32 { network: None, id: who.clone().into() } + .into_location() + .into(), + }, + // Poke the deposit to reserve the appropriate amount on the parachain. + Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: remote_weight_limit, + call: poke.encode().into(), + }, + ]); + + // send + let _ = >::send( + RawOrigin::Root.into(), + Box::new(VersionedLocation::V4(destination)), + Box::new(VersionedXcm::V4(program)), + )?; + Ok(()) + } +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index d2fcb74a78df887c9ff8ec01a66e23ac4132893c..9fb78bafefe1e7e09c836b79f768c1f1caa22b95 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -23,27 +23,31 @@ use pallet_nis::WithMaximumOf; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - slashing, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + 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, impl_runtime_weights, + assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights, impls::{ - LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter, + LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, }, - 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_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, disputes as parachains_disputes, + assigner_coretime as parachains_assigner_coretime, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -63,41 +67,38 @@ use beefy_primitives::{ }; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ - fungible::HoldConsideration, EitherOf, EitherOfDiverse, Everything, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage, ProcessMessageError, - StorageMapShim, WithdrawReasons, + fungible::HoldConsideration, Contains, EitherOf, EitherOfDiverse, EverythingBut, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage, + ProcessMessageError, StorageMapShim, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter}, PalletId, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureRoot, EnsureSigned}; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; -use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_identity::legacy::IdentityInfo; use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use sp_core::{ConstU128, OpaqueMetadata, H256}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ - AccountIdLookup, BlakeTwo256, Block as BlockT, ConstU32, ConvertInto, - Extrinsic as ExtrinsicT, IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion, - Verify, + BlakeTwo256, Block as BlockT, ConstU32, ConvertInto, Extrinsic as ExtrinsicT, + IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, Percent, Permill, RuntimeDebug, + ApplyExtrinsicResult, BoundToRuntimeAppPublic, FixedU128, KeyTypeId, Perbill, Percent, Permill, + RuntimeAppPublic, RuntimeDebug, }; use sp_staking::SessionIndex; #[cfg(any(feature = "std", test))] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use xcm::{ - latest::{InteriorMultiLocation, Junction, Junction::PalletInstance}, - VersionedMultiLocation, -}; +use xcm::{latest::prelude::*, VersionedLocation}; use xcm_builder::PayOverXcm; pub use frame_system::Call as SystemCall; @@ -112,6 +113,10 @@ mod weights; // XCM configurations. pub mod xcm_config; +// Implemented types. +mod impls; +use impls::ToParachainIdentityReaper; + // Governance and configurations. pub mod governance; use governance::{ @@ -130,16 +135,24 @@ impl_runtime_weights!(rococo_runtime_constants); #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// Provides the `WASM_BINARY` build with `fast-runtime` feature enabled. +/// +/// This is for example useful for local test chains. +#[cfg(feature = "std")] +pub mod fast_runtime_binary { + include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs")); +} + /// Runtime version (Rococo). #[sp_version::runtime_version] 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: 10020, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 22, + transaction_version: 24, state_version: 1, }; @@ -156,34 +169,37 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } +/// A type to identify calls to the Identity pallet. These will be filtered to prevent invocation, +/// locking the state of the pallet and preventing further updates to identities and sub-identities. +/// The locked state will be the genesis state of a new system chain and then removed from the Relay +/// Chain. +pub struct IsIdentityCall; +impl Contains for IsIdentityCall { + fn contains(c: &RuntimeCall) -> bool { + matches!(c, RuntimeCall::Identity(_)) + } +} + parameter_types! { pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::RelayChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = Everything; + type BaseCallFilter = EverythingBut; type BlockWeights = BlockWeights; type BlockLength = BlockLength; type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type AccountId = AccountId; - type Lookup = AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); type SystemWeightInfo = weights::frame_system::WeightInfo; type SS58Prefix = SS58Prefix; - type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } @@ -260,8 +276,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; } @@ -293,11 +308,11 @@ impl pallet_balances::Config for Runtime { type MaxLocks = MaxLocks; type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; - type WeightInfo = weights::pallet_balances::WeightInfo; + type WeightInfo = weights::pallet_balances_balances::WeightInfo; type FreezeIdentifier = (); - type MaxFreezes = ConstU32<1>; type RuntimeHoldReason = RuntimeHoldReason; - type MaxHolds = ConstU32<1>; + type RuntimeFreezeReason = RuntimeFreezeReason; + type MaxFreezes = ConstU32<1>; } parameter_types! { @@ -328,14 +343,53 @@ impl pallet_timestamp::Config for Runtime { impl pallet_authorship::Config for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type EventHandler = ImOnline; + type EventHandler = (); +} + +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +pub struct OldSessionKeys { + pub grandpa: ::Public, + pub babe: ::Public, + pub im_online: pallet_im_online::sr25519::AuthorityId, + pub para_validator: ::Public, + pub para_assignment: ::Public, + pub authority_discovery: ::Public, + pub beefy: ::Public, +} + +impl OpaqueKeys for OldSessionKeys { + type KeyTypeIdProviders = (); + fn key_ids() -> &'static [KeyTypeId] { + &[ + <::Public>::ID, + <::Public>::ID, + sp_core::crypto::key_types::IM_ONLINE, + <::Public>::ID, + <::Public>::ID, + <::Public>::ID, + <::Public>::ID, + ] + } + fn get_raw(&self, i: KeyTypeId) -> &[u8] { + match i { + <::Public>::ID => self.grandpa.as_ref(), + <::Public>::ID => self.babe.as_ref(), + sp_core::crypto::key_types::IM_ONLINE => self.im_online.as_ref(), + <::Public>::ID => self.para_validator.as_ref(), + <::Public>::ID => + self.para_assignment.as_ref(), + <::Public>::ID => + self.authority_discovery.as_ref(), + <::Public>::ID => self.beefy.as_ref(), + _ => &[], + } + } } impl_opaque_keys! { pub struct SessionKeys { pub grandpa: Grandpa, pub babe: Babe, - pub im_online: ImOnline, pub para_validator: Initializer, pub para_assignment: ParaSessionInfo, pub authority_discovery: AuthorityDiscovery, @@ -343,6 +397,18 @@ impl_opaque_keys! { } } +// remove this when removing `OldSessionKeys` +fn transform_session_keys(_val: AccountId, old: OldSessionKeys) -> SessionKeys { + SessionKeys { + grandpa: old.grandpa, + babe: old.babe, + para_validator: old.para_validator, + para_assignment: old.para_assignment, + authority_discovery: old.authority_discovery, + beefy: old.beefy, + } +} + /// Special `ValidatorIdOf` implementation that is just returning the input as result. pub struct ValidatorIdOf; impl sp_runtime::traits::Convert> for ValidatorIdOf { @@ -390,7 +456,7 @@ parameter_types! { pub const PayoutSpendPeriod: BlockNumber = 30 * DAYS; // The asset's interior location for the paying account. This is the Treasury // pallet instance (which sits at index 18). - pub TreasuryInteriorLocation: InteriorMultiLocation = PalletInstance(18).into(); + pub TreasuryInteriorLocation: InteriorLocation = PalletInstance(18).into(); pub const TipCountdown: BlockNumber = 1 * DAYS; pub const TipFindersFee: Percent = Percent::from_percent(20); @@ -421,7 +487,7 @@ impl pallet_treasury::Config for Runtime { type SpendFunds = Bounties; type SpendOrigin = TreasurySpender; type AssetKind = VersionedLocatableAsset; - type Beneficiary = VersionedMultiLocation; + type Beneficiary = VersionedLocation; type BeneficiaryLookup = IdentityLookup; type Paymaster = PayOverXcm< TreasuryInteriorLocation, @@ -431,7 +497,7 @@ impl pallet_treasury::Config for Runtime { Self::Beneficiary, Self::AssetKind, LocatableAssetConverter, - VersionedMultiLocationConverter, + VersionedLocationConverter, >; type BalanceConverter = AssetRate; type PayoutPeriod = PayoutSpendPeriod; @@ -467,7 +533,7 @@ impl pallet_bounties::Config for Runtime { parameter_types! { pub const MaxActiveChildBountyCount: u32 = 100; - pub const ChildBountyValueMinimum: Balance = BountyValueMinimum::get() / 10; + pub ChildBountyValueMinimum: Balance = BountyValueMinimum::get() / 10; } impl pallet_child_bounties::Config for Runtime { @@ -487,22 +553,6 @@ impl pallet_authority_discovery::Config for Runtime { type MaxAuthorities = MaxAuthorities; } -parameter_types! { - pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); -} - -impl pallet_im_online::Config for Runtime { - type AuthorityId = ImOnlineId; - type RuntimeEvent = RuntimeEvent; - type ValidatorSet = Historical; - type NextSessionRotation = Babe; - type ReportUnresponsiveness = Offences; - type UnsignedPriority = ImOnlineUnsignedPriority; - type WeightInfo = weights::pallet_im_online::WeightInfo; - type MaxKeys = MaxKeys; - type MaxPeerInHeartbeats = MaxPeerInHeartbeats; -} - parameter_types! { pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); } @@ -513,7 +563,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; } @@ -594,7 +644,7 @@ impl claims::Config for Runtime { parameter_types! { // Minimum 100 bytes/ROC deposited (1 CENT/byte) pub const BasicDeposit: Balance = 1000 * CENTS; // 258 bytes on-chain - pub const FieldDeposit: Balance = 250 * CENTS; // 66 bytes on-chain + pub const ByteDeposit: Balance = deposit(0, 1); pub const SubAccountDeposit: Balance = 200 * CENTS; // 53 bytes on-chain pub const MaxSubAccounts: u32 = 100; pub const MaxAdditionalFields: u32 = 100; @@ -605,14 +655,20 @@ impl pallet_identity::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type BasicDeposit = BasicDeposit; - type FieldDeposit = FieldDeposit; + type ByteDeposit = ByteDeposit; type SubAccountDeposit = SubAccountDeposit; type MaxSubAccounts = MaxSubAccounts; - type MaxAdditionalFields = MaxAdditionalFields; + type IdentityInformation = IdentityInfo; type MaxRegistrars = MaxRegistrars; type Slashed = Treasury; type ForceOrigin = EitherOf, GeneralAdmin>; type RegistrarOrigin = EitherOf, GeneralAdmin>; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -693,6 +749,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; } @@ -752,7 +809,6 @@ impl InstanceFilter for ProxyType { // Specifically omitting the entire Balances pallet RuntimeCall::Session(..) | RuntimeCall::Grandpa(..) | - RuntimeCall::ImOnline(..) | RuntimeCall::Treasury(..) | RuntimeCall::Bounties(..) | RuntimeCall::ChildBounties(..) | @@ -851,7 +907,9 @@ impl parachains_configuration::Config for Runtime { type WeightInfo = weights::runtime_parachains_configuration::WeightInfo; } -impl parachains_shared::Config for Runtime {} +impl parachains_shared::Config for Runtime { + type DisabledValidators = Session; +} impl parachains_session_info::Config for Runtime { type ValidatorSet = Historical; @@ -883,6 +941,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = Registrar; + type AssignCoretime = CoretimeAssignmentProvider; } parameter_types! { @@ -949,7 +1008,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! { @@ -963,17 +1037,13 @@ impl parachains_assigner_on_demand::Config for Runtime { type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; } -impl parachains_assigner_parachains::Config for Runtime {} - -impl parachains_assigner::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 { @@ -1068,6 +1138,13 @@ impl auctions::Config for Runtime { type WeightInfo = weights::runtime_common_auctions::WeightInfo; } +impl identity_migrator::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reaper = EnsureSigned; + type ReapIdentityHandler = ToParachainIdentityReaper; + type WeightInfo = weights::runtime_common_identity_migrator::WeightInfo; +} + type NisCounterpartInstance = pallet_balances::Instance2; impl pallet_balances::Config for Runtime { type Balance = Balance; @@ -1084,9 +1161,9 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type WeightInfo = weights::pallet_balances_nis_counterpart_balances::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; - type MaxFreezes = ConstU32<0>; + type MaxFreezes = ConstU32<1>; } parameter_types! { @@ -1125,7 +1202,7 @@ impl pallet_nis::Config for Runtime { } parameter_types! { - pub const BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); + pub BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); } impl pallet_beefy::Config for Runtime { @@ -1159,19 +1236,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); } @@ -1226,6 +1290,10 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = weights::pallet_sudo::WeightInfo; } +impl pallet_root_testing::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + impl pallet_asset_rate::Config for Runtime { type WeightInfo = weights::pallet_asset_rate::WeightInfo; type RuntimeEvent = RuntimeEvent; @@ -1242,130 +1310,130 @@ construct_runtime! { pub enum Runtime { // Basic stuff; balances is uncallable initially. - System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, + System: frame_system = 0, // Babe must be before session. - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned} = 1, + Babe: pallet_babe = 1, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 2, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event} = 3, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 4, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 33, + Timestamp: pallet_timestamp = 2, + Indices: pallet_indices = 3, + Balances: pallet_balances = 4, + TransactionPayment: pallet_transaction_payment = 33, // Consensus support. - // Authorship must be before session in order to note author in the correct session and era - // for im-online. - Authorship: pallet_authorship::{Pallet, Storage} = 5, - Offences: pallet_offences::{Pallet, Storage, Event} = 7, - Historical: session_historical::{Pallet} = 34, - - // BEEFY Bridges support. - Beefy: pallet_beefy::{Pallet, Call, Storage, Config, ValidateUnsigned} = 240, - // 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} = 241, - MmrLeaf: pallet_beefy_mmr::{Pallet, Storage} = 242, + // Authorship must be before session in order to note author in the correct session and era. + Authorship: pallet_authorship = 5, + Offences: pallet_offences = 7, + Historical: session_historical = 34, - Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 8, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned} = 10, - ImOnline: pallet_im_online::{Pallet, Call, Storage, Event, ValidateUnsigned, Config} = 11, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config} = 12, + Session: pallet_session = 8, + Grandpa: pallet_grandpa = 10, + AuthorityDiscovery: pallet_authority_discovery = 12, // Governance stuff; uncallable initially. - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 18, - ConvictionVoting: pallet_conviction_voting::{Pallet, Call, Storage, Event} = 20, - Referenda: pallet_referenda::{Pallet, Call, Storage, Event} = 21, + Treasury: pallet_treasury = 18, + ConvictionVoting: pallet_conviction_voting = 20, + Referenda: pallet_referenda = 21, // pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; - FellowshipCollective: pallet_ranked_collective::::{ - Pallet, Call, Storage, Event - } = 22, + FellowshipCollective: pallet_ranked_collective:: = 22, // pub type FellowshipReferendaInstance = pallet_referenda::Instance2; - FellowshipReferenda: pallet_referenda::::{ - Pallet, Call, Storage, Event - } = 23, - Origins: pallet_custom_origins::{Origin} = 43, - Whitelist: pallet_whitelist::{Pallet, Call, Storage, Event} = 44, + FellowshipReferenda: pallet_referenda:: = 23, + Origins: pallet_custom_origins = 43, + Whitelist: pallet_whitelist = 44, // Claims. Usable initially. - Claims: claims::{Pallet, Call, Storage, Event, Config, ValidateUnsigned} = 19, + Claims: claims = 19, // Utility module. - Utility: pallet_utility::{Pallet, Call, Event} = 24, + Utility: pallet_utility = 24, // Less simple identity module. - Identity: pallet_identity::{Pallet, Call, Storage, Event} = 25, + Identity: pallet_identity = 25, // Society module. - Society: pallet_society::{Pallet, Call, Storage, Event} = 26, + Society: pallet_society = 26, // Social recovery module. - Recovery: pallet_recovery::{Pallet, Call, Storage, Event} = 27, + Recovery: pallet_recovery = 27, // Vesting. Usable initially, but removed once all vesting is finished. - Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config} = 28, + Vesting: pallet_vesting = 28, // System scheduler. - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 29, + Scheduler: pallet_scheduler = 29, // Proxy module. Late addition. - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 30, + Proxy: pallet_proxy = 30, // Multisig module. Late addition. - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 31, + Multisig: pallet_multisig = 31, // Preimage registrar. - Preimage: pallet_preimage::{Pallet, Call, Storage, Event, HoldReason} = 32, + Preimage: pallet_preimage = 32, // Asset rate. - AssetRate: pallet_asset_rate::{Pallet, Call, Storage, Event} = 39, + AssetRate: pallet_asset_rate = 39, // Bounties modules. - Bounties: pallet_bounties::{Pallet, Call, Storage, Event} = 35, + Bounties: pallet_bounties = 35, ChildBounties: pallet_child_bounties = 40, // NIS pallet. - Nis: pallet_nis::{Pallet, Call, Storage, Event, HoldReason} = 38, -// pub type NisCounterpartInstance = pallet_balances::Instance2; + Nis: pallet_nis = 38, + // pub type NisCounterpartInstance = pallet_balances::Instance2; NisCounterpartBalances: pallet_balances:: = 45, // Parachains pallets. Start indices at 50 to leave room. - ParachainsOrigin: parachains_origin::{Pallet, Origin} = 50, - Configuration: parachains_configuration::{Pallet, Call, Storage, Config} = 51, - ParasShared: parachains_shared::{Pallet, Call, Storage} = 52, - ParaInclusion: parachains_inclusion::{Pallet, Call, Storage, Event} = 53, - ParaInherent: parachains_paras_inherent::{Pallet, Call, Storage, Inherent} = 54, - ParaScheduler: parachains_scheduler::{Pallet, Storage} = 55, - Paras: parachains_paras::{Pallet, Call, Storage, Event, Config, ValidateUnsigned} = 56, - Initializer: parachains_initializer::{Pallet, Call, Storage} = 57, - Dmp: parachains_dmp::{Pallet, Storage} = 58, - Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event, Config} = 60, - ParaSessionInfo: parachains_session_info::{Pallet, Storage} = 61, - 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, + ParachainsOrigin: parachains_origin = 50, + Configuration: parachains_configuration = 51, + ParasShared: parachains_shared = 52, + ParaInclusion: parachains_inclusion = 53, + ParaInherent: parachains_paras_inherent = 54, + ParaScheduler: parachains_scheduler = 55, + Paras: parachains_paras = 56, + Initializer: parachains_initializer = 57, + Dmp: parachains_dmp = 58, + Hrmp: parachains_hrmp = 60, + ParaSessionInfo: parachains_session_info = 61, + ParasDisputes: parachains_disputes = 62, + ParasSlashing: parachains_slashing = 63, + MessageQueue: pallet_message_queue = 64, + OnDemandAssignmentProvider: parachains_assigner_on_demand = 66, + CoretimeAssignmentProvider: parachains_assigner_coretime = 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, + Registrar: paras_registrar = 70, + Slots: slots = 71, + Auctions: auctions = 72, + Crowdloan: crowdloan = 73, + Coretime: coretime = 74, // Pallet for sending XCM. - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, + XcmPallet: pallet_xcm = 99, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call} = 250, - AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event, Config} = 251, + // BEEFY Bridges support. + Beefy: pallet_beefy = 240, + // MMR leaf construction must be after session in order to have a leaf's next_auth_set + // refer to block. See issue polkadot-fellows/runtimes#160 for details. + Mmr: pallet_mmr = 241, + MmrLeaf: pallet_beefy_mmr = 242, + + // Pallet for migrating Identity to a parachain. To be removed post-migration. + IdentityMigrator: identity_migrator = 248, + + ParasSudoWrapper: paras_sudo_wrapper = 250, + AssignedSlots: assigned_slots = 251, // Validator Manager pallet. - ValidatorManager: validator_manager::{Pallet, Call, Storage, Event} = 252, + ValidatorManager: validator_manager = 252, // State trie migration pallet, only temporary. StateTrieMigration: pallet_state_trie_migration = 254, + // Root testing pallet. + RootTesting: pallet_root_testing = 249, + // Sudo. - Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config} = 255, + Sudo: pallet_sudo = 255, } } @@ -1408,6 +1476,26 @@ pub mod migrations { use frame_support::traits::LockIdentifier; use frame_system::pallet_prelude::BlockNumberFor; + #[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"; @@ -1416,6 +1504,7 @@ pub mod migrations { pub const PhragmenElectionPalletName: &'static str = "PhragmenElection"; pub const TechnicalMembershipPalletName: &'static str = "TechnicalMembership"; pub const TipsPalletName: &'static str = "Tips"; + pub const ImOnlinePalletName: &'static str = "ImOnline"; pub const PhragmenElectionPalletId: LockIdentifier = *b"phrelect"; } @@ -1443,16 +1532,91 @@ pub mod migrations { type PalletName = TipsPalletName; } + /// Upgrade Session keys to exclude `ImOnline` key. + /// When this is removed, should also remove `OldSessionKeys`. + pub struct UpgradeSessionKeys; + const UPGRADE_SESSION_KEYS_FROM_SPEC: u32 = 104000; + + impl frame_support::traits::OnRuntimeUpgrade for UpgradeSessionKeys { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::warn!(target: "runtime::session_keys", "Skipping session keys migration pre-upgrade check due to spec version (already applied?)"); + return Ok(Vec::new()) + } + + log::info!(target: "runtime::session_keys", "Collecting pre-upgrade session keys state"); + let key_ids = SessionKeys::key_ids(); + frame_support::ensure!( + key_ids.into_iter().find(|&k| *k == sp_core::crypto::key_types::IM_ONLINE) == None, + "New session keys contain the ImOnline key that should have been removed", + ); + let storage_key = pallet_session::QueuedKeys::::hashed_key(); + let mut state: Vec = Vec::new(); + frame_support::storage::unhashed::get::>( + &storage_key, + ) + .ok_or::("Queued keys are not available".into())? + .into_iter() + .for_each(|(id, keys)| { + state.extend_from_slice(id.as_slice()); + for key_id in key_ids { + state.extend_from_slice(keys.get_raw(*key_id)); + } + }); + frame_support::ensure!(state.len() > 0, "Queued keys are not empty before upgrade"); + Ok(state) + } + + fn on_runtime_upgrade() -> Weight { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::info!("Skipping session keys upgrade: already applied"); + return ::DbWeight::get().reads(1) + } + log::trace!("Upgrading session keys"); + Session::upgrade_keys::(transform_session_keys); + Perbill::from_percent(50) * BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade( + old_state: sp_std::vec::Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::warn!(target: "runtime::session_keys", "Skipping session keys migration post-upgrade check due to spec version (already applied?)"); + return Ok(()) + } + + let key_ids = SessionKeys::key_ids(); + let mut new_state: Vec = Vec::new(); + pallet_session::QueuedKeys::::get().into_iter().for_each(|(id, keys)| { + new_state.extend_from_slice(id.as_slice()); + for key_id in key_ids { + new_state.extend_from_slice(keys.get_raw(*key_id)); + } + }); + frame_support::ensure!(new_state.len() > 0, "Queued keys are not empty after upgrade"); + frame_support::ensure!( + old_state == new_state, + "Pre-upgrade and post-upgrade keys do not match!" + ); + log::info!(target: "runtime::session_keys", "Session keys migrated successfully"); + Ok(()) + } + } + + // We don't have a limit in the Relay Chain. + const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX; + /// Unreleased migrations. Add new ones here: pub type Unreleased = ( - pallet_society::migrations::VersionCheckedMigrateToV2, - pallet_im_online::migration::v1::Migration, + pallet_society::migrations::MigrateToV2, parachains_configuration::migration::v7::MigrateToV7, - assigned_slots::migration::v1::VersionCheckedMigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + assigned_slots::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, - paras_registrar::migration::VersionCheckedMigrateToV1, + paras_registrar::migration::MigrateToV1, pallet_referenda::migration::v1::MigrateV0ToV1, pallet_referenda::migration::v1::MigrateV0ToV1, @@ -1469,6 +1633,25 @@ pub mod migrations { frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, + + pallet_grandpa::migrations::MigrateV4ToV5, + parachains_configuration::migration::v10::MigrateToV10, + + // Upgrade `SessionKeys` to exclude `ImOnline` + UpgradeSessionKeys, + + // Remove `im-online` pallet on-chain storage + frame_support::migrations::RemovePallet::DbWeight>, + + // Migrate Identity pallet for Usernames + pallet_identity::migration::versioned::V0ToV1, + parachains_configuration::migration::v11::MigrateToV11, + // This needs to come after the `parachains_configuration` above as we are reading the configuration. + coretime::migration::MigrateToCoretime, + parachains_configuration::migration::v12::MigrateToV12, + + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); } @@ -1494,6 +1677,7 @@ parameter_types! { impl pallet_state_trie_migration::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; type SignedDepositPerItem = MigrationSignedDepositPerItem; type SignedDepositBase = MigrationSignedDepositBase; type ControlOrigin = EnsureRoot; @@ -1517,8 +1701,10 @@ 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] [runtime_common::slots, Slots] [runtime_common::paras_registrar, Registrar] [runtime_parachains::configuration, Configuration] @@ -1538,7 +1724,6 @@ mod benches { [pallet_conviction_voting, ConvictionVoting] [pallet_nis, Nis] [pallet_identity, Identity] - [pallet_im_online, ImOnline] [pallet_indices, Indices] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] @@ -1558,7 +1743,7 @@ mod benches { [pallet_asset_rate, AssetRate] [pallet_whitelist, Whitelist] // XCM - [pallet_xcm, XcmPallet] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_xcm_benchmarks::fungible, pallet_xcm_benchmarks::fungible::Pallet::] [pallet_xcm_benchmarks::generic, pallet_xcm_benchmarks::generic::Pallet::] ); @@ -1574,7 +1759,7 @@ sp_api::impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -1626,12 +1811,18 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } - #[api_version(8)] - impl primitives::runtime_api::ParachainHost for Runtime { + #[api_version(10)] + impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() } @@ -1774,10 +1965,17 @@ 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::() } + fn node_features() -> NodeFeatures { + parachains_staging_runtime_api_impl::node_features::() + } } #[api_version(3)] @@ -2034,6 +2232,8 @@ sp_api::impl_runtime_apis! { use frame_system_benchmarking::Pallet as SystemBench; use frame_benchmarking::baseline::Pallet as Baseline; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -2051,6 +2251,7 @@ sp_api::impl_runtime_apis! { use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; use frame_system_benchmarking::Pallet as SystemBench; use frame_benchmarking::baseline::Pallet as Baseline; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; use sp_storage::TrackedStorageKey; use xcm::latest::prelude::*; use xcm_config::{ @@ -2058,43 +2259,109 @@ sp_api::impl_runtime_apis! { }; parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( + pub ExistentialDepositAsset: Option = Some(( TokenLocation::get(), ExistentialDeposit::get() ).into()); - pub ToParachain: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub AssetHubParaId: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); } impl frame_system_benchmarking::Config for Runtime {} impl frame_benchmarking::baseline::Config for Runtime {} + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + + fn reachable_dest() -> Option { + Some(crate::xcm_config::AssetHub::get()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported to/from AH. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Here.into()) + }, + crate::xcm_config::AssetHub::get(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay can reserve transfer native token to some random parachain. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Here.into()) + }, + Parachain(RandomParaId::get().into()).into(), + )) + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::here()), + fun: Fungible(ExistentialDeposit::get()), + } + } + } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = XcmConfig; type AccountIdConverter = LocationConverter; type DeliveryHelper = runtime_common::xcm_sender::ToParachainDeliveryHelper< XcmConfig, - ExistentialDepositMultiAsset, + ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; - fn valid_destination() -> Result { + fn valid_destination() -> Result { Ok(AssetHub::get()) } - fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> Assets { // Rococo only knows about ROC - vec![MultiAsset{ - id: Concrete(TokenLocation::get()), + vec![Asset{ + id: AssetId(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }].into() } } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub TrustedTeleporter: Option<(Location, Asset)> = Some(( AssetHub::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, + Asset { fun: Fungible(1 * UNITS), id: AssetId(TokenLocation::get()) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + pub TrustedReserve: Option<(Location, Asset)> = None; } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -2104,9 +2371,9 @@ sp_api::impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(TokenLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -2120,43 +2387,50 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { // Rococo doesn't support asset exchanges Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { // The XCM executor of Rococo doesn't have a configured `UniversalAliases` Err(BenchmarkError::Skip) } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { Ok((AssetHub::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(AssetHub::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { let origin = AssetHub::get(); - let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + let assets: Assets = (AssetId(TokenLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(TokenLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { // Rococo doesn't support asset locking Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { // Rococo doesn't support exporting messages Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { // The XCM executor of Rococo doesn't have a configured `Aliasers` Err(BenchmarkError::Skip) } 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 9c563a67d98b721b08cb3d84c4f5562682749b1d..7328dca9393693e335b49dc317d6754f9fd6e840 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -17,13 +17,12 @@ pub mod frame_system; pub mod pallet_asset_rate; -pub mod pallet_balances; +pub mod pallet_balances_balances; pub mod pallet_balances_nis_counterpart_balances; pub mod pallet_bounties; pub mod pallet_child_bounties; pub mod pallet_conviction_voting; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; @@ -46,10 +45,12 @@ pub mod runtime_common_assigned_slots; pub mod runtime_common_auctions; pub mod runtime_common_claims; 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_hrmp; pub mod runtime_parachains_inclusion; diff --git a/polkadot/runtime/rococo/src/weights/pallet_balances.rs b/polkadot/runtime/rococo/src/weights/pallet_balances.rs deleted file mode 100644 index 972c097372f4bf75c5891f575cb4ebd0ec59b8f2..0000000000000000000000000000000000000000 --- a/polkadot/runtime/rococo/src/weights/pallet_balances.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . -//! Autogenerated weights for `pallet_balances` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-16, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=rococo-dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_balances -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; - -/// Weight functions for `pallet_balances`. -pub struct WeightInfo(PhantomData); -impl pallet_balances::WeightInfo for WeightInfo { - // Storage: System Account (r:1 w:1) - fn transfer_allow_death() -> Weight { - // Minimum execution time: 40_106 nanoseconds. - Weight::from_parts(40_750_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn transfer_keep_alive() -> Weight { - // Minimum execution time: 30_737 nanoseconds. - Weight::from_parts(31_295_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn force_set_balance_creating() -> Weight { - // Minimum execution time: 23_902 nanoseconds. - Weight::from_parts(24_338_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn force_set_balance_killing() -> Weight { - // Minimum execution time: 26_492 nanoseconds. - Weight::from_parts(26_866_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:2 w:2) - fn force_transfer() -> Weight { - // Minimum execution time: 40_384 nanoseconds. - Weight::from_parts(41_000_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: System Account (r:1 w:1) - fn transfer_all() -> Weight { - // Minimum execution time: 35_115 nanoseconds. - Weight::from_parts(35_696_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn force_unreserve() -> Weight { - // Minimum execution time: 20_274 nanoseconds. - Weight::from_parts(20_885_000 as u64, 0) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - fn upgrade_accounts(_: u32) -> Weight { - Weight::from_parts(0, 0) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_balances.rs b/polkadot/runtime/rococo/src/weights/pallet_balances_balances.rs similarity index 72% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_balances.rs rename to polkadot/runtime/rococo/src/weights/pallet_balances_balances.rs index 51ca2e660b3b578a5d23e38eb215e2b4a5c5debc..1b0ae1eeece41b10aff0c65181fe757cfdc4dd0e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/weights/pallet_balances.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_balances_balances.rs @@ -1,43 +1,41 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Cumulus. +// This file is part of Polkadot. -// Cumulus is free software: you can redistribute it and/or modify +// 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. -// Cumulus is distributed in the hope that it will be useful, +// 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 Cumulus. If not, see . +// along with Polkadot. 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-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-20, 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-kusama-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-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-parachain +// target/production/polkadot // benchmark // pallet -// --chain=bridge-hub-kusama-dev -// --wasm-execution=compiled -// --pallet=pallet_balances -// --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-kusama/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 55_163_000 picoseconds. - Weight::from_parts(56_056_000, 0) + // Minimum execution time: 44_127_000 picoseconds. + Weight::from_parts(45_099_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_829_000 picoseconds. - Weight::from_parts(42_182_000, 0) + // Minimum execution time: 34_265_000 picoseconds. + Weight::from_parts(35_083_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 15_212_000 picoseconds. - Weight::from_parts(15_782_000, 0) + // Minimum execution time: 12_189_000 picoseconds. + Weight::from_parts(12_655_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 22_866_000 picoseconds. - Weight::from_parts(23_452_000, 0) + // Minimum execution time: 16_910_000 picoseconds. + Weight::from_parts(17_474_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 57_047_000 picoseconds. - Weight::from_parts(58_536_000, 0) + // Minimum execution time: 45_212_000 picoseconds. + Weight::from_parts(46_320_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -116,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 51_622_000 picoseconds. - Weight::from_parts(52_912_000, 0) + // Minimum execution time: 42_500_000 picoseconds. + Weight::from_parts(43_991_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 17_723_000 picoseconds. - Weight::from_parts(18_383_000, 0) + // Minimum execution time: 15_197_000 picoseconds. + Weight::from_parts(15_749_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -137,17 +135,26 @@ impl pallet_balances::WeightInfo for WeightInfo { /// 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]`. + /// The range of component `u` is `[1, 1000]`. fn upgrade_accounts(u: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + u * (136 ±0)` + // Measured: `0 + u * (135 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_089_000 picoseconds. - Weight::from_parts(17_379_000, 0) + // Minimum execution time: 14_414_000 picoseconds. + Weight::from_parts(14_685_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 17_071 - .saturating_add(Weight::from_parts(15_647_341, 0).saturating_mul(u.into())) + // Standard Error: 7_918 + .saturating_add(Weight::from_parts(13_095_420, 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())) } + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_239_000 picoseconds. + Weight::from_parts(5_617_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_balances_nis_counterpart_balances.rs b/polkadot/runtime/rococo/src/weights/pallet_balances_nis_counterpart_balances.rs index 597a67de4b99d7d67e2e221a3b1a905427339254..6cca9b9320a6558ae6f1a5192e615d62b4acf8f4 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_balances_nis_counterpart_balances.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_balances_nis_counterpart_balances.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=pallet_balances // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,129 +48,127 @@ use core::marker::PhantomData; /// Weight functions for `pallet_balances`. pub struct WeightInfo(PhantomData); impl pallet_balances::WeightInfo for WeightInfo { - /// Storage: NisCounterpartBalances TotalIssuance (r:1 w:1) - /// Proof: NisCounterpartBalances TotalIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: NisCounterpartBalances Account (r:2 w:2) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:2 w:2) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_allow_death() -> Weight { // Proof Size summary in bytes: - // Measured: `219` + // Measured: `103` // Estimated: `6164` - // Minimum execution time: 54_122_000 picoseconds. - Weight::from_parts(54_834_000, 0) + // Minimum execution time: 41_978_000 picoseconds. + Weight::from_parts(42_989_000, 0) .saturating_add(Weight::from_parts(0, 6164)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: NisCounterpartBalances TotalIssuance (r:1 w:0) - /// Proof: NisCounterpartBalances TotalIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: NisCounterpartBalances Account (r:2 w:2) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:2 w:2) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_keep_alive() -> Weight { // Proof Size summary in bytes: - // Measured: `219` + // Measured: `103` // Estimated: `6164` - // Minimum execution time: 41_749_000 picoseconds. - Weight::from_parts(42_193_000, 0) + // Minimum execution time: 32_250_000 picoseconds. + Weight::from_parts(33_074_000, 0) .saturating_add(Weight::from_parts(0, 6164)) - .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: NisCounterpartBalances Account (r:1 w:1) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:1 w:1) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) fn force_set_balance_creating() -> Weight { // Proof Size summary in bytes: - // Measured: `217` + // Measured: `103` // Estimated: `3577` - // Minimum execution time: 16_008_000 picoseconds. - Weight::from_parts(16_328_000, 0) + // Minimum execution time: 9_906_000 picoseconds. + Weight::from_parts(10_397_000, 0) .saturating_add(Weight::from_parts(0, 3577)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: NisCounterpartBalances Account (r:1 w:1) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: NisCounterpartBalances TotalIssuance (r:1 w:1) - /// Proof: NisCounterpartBalances TotalIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:1 w:1) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_killing() -> Weight { // Proof Size summary in bytes: - // Measured: `393` + // Measured: `277` // Estimated: `3593` - // Minimum execution time: 26_277_000 picoseconds. - Weight::from_parts(26_932_000, 0) + // Minimum execution time: 16_298_000 picoseconds. + Weight::from_parts(17_115_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: NisCounterpartBalances TotalIssuance (r:1 w:1) - /// Proof: NisCounterpartBalances TotalIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: NisCounterpartBalances Account (r:2 w:2) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:2 w:2) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// 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: `322` + // Measured: `206` // Estimated: `6196` - // Minimum execution time: 57_020_000 picoseconds. - Weight::from_parts(57_661_000, 0) + // Minimum execution time: 43_283_000 picoseconds. + Weight::from_parts(44_033_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: NisCounterpartBalances Account (r:2 w:2) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: NisCounterpartBalances TotalIssuance (r:1 w:0) - /// Proof: NisCounterpartBalances TotalIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:2 w:2) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_all() -> Weight { // Proof Size summary in bytes: - // Measured: `219` + // Measured: `103` // Estimated: `6164` - // Minimum execution time: 50_630_000 picoseconds. - Weight::from_parts(51_191_000, 0) + // Minimum execution time: 40_564_000 picoseconds. + Weight::from_parts(41_597_000, 0) .saturating_add(Weight::from_parts(0, 6164)) - .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: NisCounterpartBalances Account (r:1 w:1) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:1 w:1) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_unreserve() -> Weight { // Proof Size summary in bytes: - // Measured: `391` + // Measured: `277` // Estimated: `3593` - // Minimum execution time: 21_915_000 picoseconds. - Weight::from_parts(22_295_000, 0) + // Minimum execution time: 15_018_000 picoseconds. + Weight::from_parts(15_532_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: NisCounterpartBalances Account (r:999 w:999) - /// Proof: NisCounterpartBalances Account (max_values: None, max_size: Some(112), added: 2587, mode: MaxEncodedLen) - /// Storage: System Account (r:999 w:999) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `NisCounterpartBalances::Account` (r:999 w:999) + /// Proof: `NisCounterpartBalances::Account` (`max_values`: None, `max_size`: Some(112), added: 2587, mode: `MaxEncodedLen`) + /// 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]`. /// The range of component `u` is `[1, 1000]`. fn upgrade_accounts(u: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + u * (256 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 21_290_000 picoseconds. - Weight::from_parts(21_622_000, 0) + // Minimum execution time: 14_470_000 picoseconds. + Weight::from_parts(14_828_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 13_372 - .saturating_add(Weight::from_parts(15_527_611, 0).saturating_mul(u.into())) + // Standard Error: 15_515 + .saturating_add(Weight::from_parts(14_505_553, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) } + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_277_000 picoseconds. + Weight::from_parts(5_628_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_identity.rs b/polkadot/runtime/rococo/src/weights/pallet_identity.rs index e10c042dde6aa167e63b56244f7f5dcb21960915..b334e21ea03127a749ff1bf2455f69627f832922 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_identity.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_identity.rs @@ -65,8 +65,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[0, 100]`. - fn set_identity(r: u32, x: u32, ) -> Weight { + fn set_identity(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `442 + r * (5 ±0)` // Estimated: `11003` @@ -75,8 +74,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 2_307 .saturating_add(Weight::from_parts(92_753, 0).saturating_mul(r.into())) - // Standard Error: 450 - .saturating_add(Weight::from_parts(449_529, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -130,8 +127,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. - /// The range of component `x` is `[0, 100]`. - fn clear_identity(_r: u32, s: u32, x: u32, ) -> Weight { + 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` @@ -140,8 +136,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 1_353 .saturating_add(Weight::from_parts(1_074_019, 0).saturating_mul(s.into())) - // Standard Error: 1_353 - .saturating_add(Weight::from_parts(229_947, 0).saturating_mul(x.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()))) @@ -151,8 +145,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[0, 100]`. - fn request_judgement(r: u32, x: u32, ) -> Weight { + fn request_judgement(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `367 + r * (57 ±0) + x * (66 ±0)` // Estimated: `11003` @@ -161,16 +154,14 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 2_214 .saturating_add(Weight::from_parts(83_822, 0).saturating_mul(r.into())) - // Standard Error: 432 - .saturating_add(Weight::from_parts(458_801, 0).saturating_mul(x.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]`. - /// The range of component `x` is `[0, 100]`. - fn cancel_request(r: u32, x: u32, ) -> Weight { + fn cancel_request(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `398 + x * (66 ±0)` // Estimated: `11003` @@ -179,8 +170,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 2_528 .saturating_add(Weight::from_parts(85_593, 0).saturating_mul(r.into())) - // Standard Error: 493 - .saturating_add(Weight::from_parts(468_140, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -234,8 +223,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. - /// The range of component `x` is `[0, 100]`. - fn provide_judgement(r: u32, x: u32, ) -> Weight { + fn provide_judgement(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `445 + r * (57 ±0) + x * (66 ±0)` // Estimated: `11003` @@ -244,8 +232,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 2_881 .saturating_add(Weight::from_parts(109_812, 0).saturating_mul(r.into())) - // Standard Error: 533 - .saturating_add(Weight::from_parts(733_244, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -259,8 +245,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. - /// The range of component `x` is `[0, 100]`. - fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + 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` @@ -271,8 +256,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .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())) - // Standard Error: 1_275 - .saturating_add(Weight::from_parts(228_226, 0).saturating_mul(x.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()))) @@ -351,4 +334,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn add_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_873_000 picoseconds. + Weight::from_parts(13_873_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn remove_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_653_000 picoseconds. + Weight::from_parts(10_653_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:1 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_username_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `11037` + // Minimum execution time: 75_928_000 picoseconds. + Weight::from_parts(75_928_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:0 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn accept_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `11037` + // Minimum execution time: 38_157_000 picoseconds. + Weight::from_parts(38_157_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + fn remove_expired_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3542` + // Minimum execution time: 46_821_000 picoseconds. + Weight::from_parts(46_821_000, 0) + .saturating_add(Weight::from_parts(0, 3542)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:0) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_primary_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `247` + // Estimated: `11037` + // Minimum execution time: 22_515_000 picoseconds. + Weight::from_parts(22_515_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:0) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn remove_dangling_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `126` + // Estimated: `11037` + // Minimum execution time: 15_997_000 picoseconds. + Weight::from_parts(15_997_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs b/polkadot/runtime/rococo/src/weights/pallet_im_online.rs deleted file mode 100644 index b866426de52a3abc7608f96bb192a1ae4fbb1d90..0000000000000000000000000000000000000000 --- a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=rococo-dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Proof Skipped: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `394 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 132_910_000 picoseconds. - Weight::from_parts(149_854_501, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 3_317 - .saturating_add(Weight::from_parts(61_141, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} diff --git a/polkadot/runtime/rococo/src/weights/pallet_ranked_collective.rs b/polkadot/runtime/rococo/src/weights/pallet_ranked_collective.rs index 8a556c3a248ef46fdb2b5b5449c4b0c75972a9fd..ce9d5fcc0c7131b227e205a470532e8042cc93ae 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_ranked_collective.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_ranked_collective.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_ranked_collective` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-xerhrdyb-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot @@ -29,14 +29,13 @@ // --steps=50 // --repeat=20 // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_ranked_collective // --chain=rococo-dev -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -61,8 +60,8 @@ impl pallet_ranked_collective::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `42` // Estimated: `3507` - // Minimum execution time: 17_632_000 picoseconds. - Weight::from_parts(18_252_000, 0) + // Minimum execution time: 13_480_000 picoseconds. + Weight::from_parts(13_786_000, 0) .saturating_add(Weight::from_parts(0, 3507)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) @@ -71,24 +70,24 @@ impl pallet_ranked_collective::WeightInfo for WeightInf /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:11 w:11) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:11 w:11) + /// Storage: `FellowshipCollective::IdToIndex` (r:11 w:22) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IndexToId` (r:11 w:11) + /// Storage: `FellowshipCollective::IndexToId` (r:11 w:22) /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// The range of component `r` is `[0, 10]`. fn remove_member(r: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `517 + r * (281 ±0)` + // Measured: `516 + r * (281 ±0)` // Estimated: `3519 + r * (2529 ±0)` - // Minimum execution time: 27_960_000 picoseconds. - Weight::from_parts(30_632_408, 0) + // Minimum execution time: 28_771_000 picoseconds. + Weight::from_parts(29_256_825, 0) .saturating_add(Weight::from_parts(0, 3519)) - // Standard Error: 22_806 - .saturating_add(Weight::from_parts(13_000_901, 0).saturating_mul(r.into())) + // Standard Error: 21_594 + .saturating_add(Weight::from_parts(14_649_527, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes((5_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2529).saturating_mul(r.into())) } /// Storage: `FellowshipCollective::Members` (r:1 w:1) @@ -104,11 +103,11 @@ impl pallet_ranked_collective::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `214 + r * (17 ±0)` // Estimated: `3507` - // Minimum execution time: 19_900_000 picoseconds. - Weight::from_parts(20_908_316, 0) + // Minimum execution time: 16_117_000 picoseconds. + Weight::from_parts(16_978_453, 0) .saturating_add(Weight::from_parts(0, 3507)) - // Standard Error: 4_878 - .saturating_add(Weight::from_parts(330_385, 0).saturating_mul(r.into())) + // Standard Error: 4_511 + .saturating_add(Weight::from_parts(324_261, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -116,22 +115,22 @@ impl pallet_ranked_collective::WeightInfo for WeightInf /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:1) + /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:2) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IndexToId` (r:1 w:1) + /// Storage: `FellowshipCollective::IndexToId` (r:1 w:2) /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// The range of component `r` is `[0, 10]`. fn demote_member(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `532 + r * (72 ±0)` // Estimated: `3519` - // Minimum execution time: 27_697_000 picoseconds. - Weight::from_parts(30_341_815, 0) + // Minimum execution time: 28_995_000 picoseconds. + Weight::from_parts(31_343_215, 0) .saturating_add(Weight::from_parts(0, 3519)) - // Standard Error: 17_010 - .saturating_add(Weight::from_parts(642_213, 0).saturating_mul(r.into())) + // Standard Error: 16_438 + .saturating_add(Weight::from_parts(637_462, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `FellowshipCollective::Members` (r:1 w:0) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -143,10 +142,10 @@ impl pallet_ranked_collective::WeightInfo for WeightInf /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) fn vote() -> Weight { // Proof Size summary in bytes: - // Measured: `638` + // Measured: `603` // Estimated: `83866` - // Minimum execution time: 48_275_000 picoseconds. - Weight::from_parts(49_326_000, 0) + // Minimum execution time: 38_820_000 picoseconds. + Weight::from_parts(40_240_000, 0) .saturating_add(Weight::from_parts(0, 83866)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) @@ -160,16 +159,34 @@ impl pallet_ranked_collective::WeightInfo for WeightInf /// The range of component `n` is `[0, 100]`. fn cleanup_poll(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `434 + n * (50 ±0)` + // Measured: `400 + n * (50 ±0)` // Estimated: `4365 + n * (2540 ±0)` - // Minimum execution time: 15_506_000 picoseconds. - Weight::from_parts(17_634_029, 0) + // Minimum execution time: 12_972_000 picoseconds. + Weight::from_parts(15_829_333, 0) .saturating_add(Weight::from_parts(0, 4365)) - // Standard Error: 2_117 - .saturating_add(Weight::from_parts(1_126_879, 0).saturating_mul(n.into())) + // Standard Error: 1_754 + .saturating_add(Weight::from_parts(1_116_520, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2540).saturating_mul(n.into())) } + /// Storage: `FellowshipCollective::Members` (r:2 w:2) + /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::MemberCount` (r:2 w:2) + /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IdToIndex` (r:2 w:4) + /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:2) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + fn exchange_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `337` + // Estimated: `6048` + // Minimum execution time: 44_601_000 picoseconds. + Weight::from_parts(45_714_000, 0) + .saturating_add(Weight::from_parts(0, 6048)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(10)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs index e4732a2d17dca5180a8e07d5cddd19acd55f1b76..0f36dbd384df87d5a90c2a11392923e7ae8633aa 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: - // Measured: `69` + // Measured: `68` // Estimated: `1489` - // Minimum execution time: 4_741_000 picoseconds. - Weight::from_parts(4_939_000, 0) + // Minimum execution time: 2_869_000 picoseconds. + Weight::from_parts(3_109_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 4_504_000 picoseconds. - Weight::from_parts(7_569_333, 0) + // Minimum execution time: 3_326_000 picoseconds. + Weight::from_parts(5_818_563, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_818 - .saturating_add(Weight::from_parts(771_180, 0).saturating_mul(s.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(336_446, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -78,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_709_000 picoseconds. - Weight::from_parts(5_929_000, 0) + // Minimum execution time: 3_007_000 picoseconds. + Weight::from_parts(3_197_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `251 + s * (1 ±0)` // Estimated: `3716 + s * (1 ±0)` - // Minimum execution time: 20_710_000 picoseconds. - Weight::from_parts(20_918_000, 0) + // Minimum execution time: 16_590_000 picoseconds. + Weight::from_parts(16_869_000, 0) .saturating_add(Weight::from_parts(0, 3716)) // Standard Error: 9 - .saturating_add(Weight::from_parts(1_257, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(Weight::from_parts(1_308, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_262_000 picoseconds. - Weight::from_parts(7_412_000, 0) + // Minimum execution time: 4_320_000 picoseconds. + Weight::from_parts(4_594_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,90 +118,173 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_774_000 picoseconds. - Weight::from_parts(5_887_000, 0) + // Minimum execution time: 2_956_000 picoseconds. + Weight::from_parts(3_216_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_777_000 picoseconds. - Weight::from_parts(2_865_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_929_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_739_000 picoseconds. - Weight::from_parts(2_827_000, 0) + // Minimum execution time: 1_749_000 picoseconds. + Weight::from_parts(1_916_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 14_788_000 picoseconds. - Weight::from_parts(17_705_748, 0) + // Minimum execution time: 9_086_000 picoseconds. + Weight::from_parts(11_733_696, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_703 - .saturating_add(Weight::from_parts(760_991, 0).saturating_mul(s.into())) + // Standard Error: 1_362 + .saturating_add(Weight::from_parts(375_266, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 18_716_000 picoseconds. - Weight::from_parts(18_220_022, 0) + // Minimum execution time: 12_716_000 picoseconds. + Weight::from_parts(12_529_180, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_508 - .saturating_add(Weight::from_parts(1_357_835, 0).saturating_mul(s.into())) + // Standard Error: 867 + .saturating_add(Weight::from_parts(548_188, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `293 + s * (185 ±0)` + // Measured: `292 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 17_719_000 picoseconds. - Weight::from_parts(21_657_806, 0) + // Minimum execution time: 12_053_000 picoseconds. + Weight::from_parts(15_358_056, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_645 - .saturating_add(Weight::from_parts(794_184, 0).saturating_mul(s.into())) + // Standard Error: 3_176 + .saturating_add(Weight::from_parts(421_589, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `319 + s * (185 ±0)` + // Measured: `318 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 20_225_000 picoseconds. - Weight::from_parts(20_494_405, 0) + // Minimum execution time: 14_803_000 picoseconds. + Weight::from_parts(15_805_714, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_890 - .saturating_add(Weight::from_parts(1_379_025, 0).saturating_mul(s.into())) + // Standard Error: 2_597 + .saturating_add(Weight::from_parts(611_053, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `196` + // Estimated: `42428` + // Minimum execution time: 13_156_000 picoseconds. + Weight::from_parts(13_801_287, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 568 + .saturating_add(Weight::from_parts(35_441, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_sudo.rs b/polkadot/runtime/rococo/src/weights/pallet_sudo.rs index 83215af5b8aecf5a79b084f24d0cfd8506f5b559..694174954fc78b1eeb4d93f78e6b26d5bab22b83 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_sudo.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_sudo.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `pallet_sudo` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=pallet_sudo // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_sudo +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,38 +48,50 @@ use core::marker::PhantomData; /// Weight functions for `pallet_sudo`. pub struct WeightInfo(PhantomData); impl pallet_sudo::WeightInfo for WeightInfo { - /// Storage: Sudo Key (r:1 w:1) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:1) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn set_key() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 13_047_000 picoseconds. - Weight::from_parts(13_325_000, 0) + // Minimum execution time: 8_432_000 picoseconds. + Weight::from_parts(8_757_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Sudo Key (r:1 w:0) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:0) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn sudo() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 13_250_000 picoseconds. - Weight::from_parts(13_544_000, 0) + // Minimum execution time: 9_167_000 picoseconds. + Weight::from_parts(9_397_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) } - /// Storage: Sudo Key (r:1 w:0) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:0) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn sudo_as() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 13_424_000 picoseconds. - Weight::from_parts(13_801_000, 0) + // Minimum execution time: 9_133_000 picoseconds. + Weight::from_parts(9_573_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) } + /// Storage: `Sudo::Key` (r:1 w:1) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + fn remove_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `132` + // Estimated: `1517` + // Minimum execution time: 7_374_000 picoseconds. + Weight::from_parts(7_702_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_vesting.rs b/polkadot/runtime/rococo/src/weights/pallet_vesting.rs index fd820f1aea3241521e650b1e6af3af32236a465d..2596207d5837df6776177b71480c57af61ac02ce 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_vesting.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_vesting.rs @@ -187,16 +187,29 @@ impl pallet_vesting::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Vesting Vesting (r:1 w:1) - /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. + fn force_remove_vesting_schedule(l: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `555 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 41_497_000 picoseconds. + Weight::from_parts(38_763_834, 4764) + // Standard Error: 2_030 + .saturating_add(Weight::from_parts(99_580, 0).saturating_mul(l.into())) + // Standard Error: 3_750 + .saturating_add(Weight::from_parts(132_188, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `378 + l * (25 ±0) + s * (36 ±0)` diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index 43b4358b89038530ad66545e195aece5a4e0c874..5544ca44658cc09fa51cd75679ba90132eb16444 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -16,11 +16,11 @@ //! Autogenerated weights for `pallet_xcm` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-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 @@ -29,14 +29,13 @@ // --steps=50 // --repeat=20 // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm // --chain=rococo-dev -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -49,58 +48,100 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { - /// Storage: Configuration ActiveConfig (r:1 w:0) - /// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// 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`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `565` - // Estimated: `4030` - // Minimum execution time: 37_039_000 picoseconds. - Weight::from_parts(37_605_000, 0) - .saturating_add(Weight::from_parts(0, 4030)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 25_043_000 picoseconds. + Weight::from_parts(25_682_000, 0) + .saturating_add(Weight::from_parts(0, 3645)) + .saturating_add(T::DbWeight::get().reads(4)) + .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`) + /// 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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 21_646_000 picoseconds. - Weight::from_parts(22_119_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 107_570_000 picoseconds. + Weight::from_parts(109_878_000, 0) + .saturating_add(Weight::from_parts(0, 3645)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } + /// Storage: `System::Account` (r:1 w:1) + /// 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`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 21_353_000 picoseconds. - Weight::from_parts(21_768_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `232` + // Estimated: `3697` + // Minimum execution time: 106_341_000 picoseconds. + Weight::from_parts(109_135_000, 0) + .saturating_add(Weight::from_parts(0, 3697)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::Account` (r:1 w:1) + /// 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`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 108_372_000 picoseconds. + Weight::from_parts(112_890_000, 0) + .saturating_add(Weight::from_parts(0, 3645)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_942_000 picoseconds. - Weight::from_parts(10_110_000, 0) + // Minimum execution time: 6_957_000 picoseconds. + Weight::from_parts(7_417_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: XcmPallet SupportedVersion (r:0 w:1) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) + /// Proof: `XcmPallet::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_951_000 picoseconds. - Weight::from_parts(10_182_000, 0) + // Minimum execution time: 7_053_000 picoseconds. + Weight::from_parts(7_462_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -108,171 +149,201 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_163_000 picoseconds. - Weight::from_parts(3_298_000, 0) + // Minimum execution time: 1_918_000 picoseconds. + Weight::from_parts(2_037_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: XcmPallet VersionNotifiers (r:1 w:1) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet QueryCounter (r:1 w:1) - /// Proof Skipped: XcmPallet QueryCounter (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Configuration ActiveConfig (r:1 w:0) - /// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet Queries (r:0 w:1) - /// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) + /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// 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: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `565` - // Estimated: `4030` - // Minimum execution time: 41_207_000 picoseconds. - Weight::from_parts(41_879_000, 0) - .saturating_add(Weight::from_parts(0, 4030)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(6)) + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 30_417_000 picoseconds. + Weight::from_parts(31_191_000, 0) + .saturating_add(Weight::from_parts(0, 3645)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: XcmPallet VersionNotifiers (r:1 w:1) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: Configuration ActiveConfig (r:1 w:0) - /// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet Queries (r:0 w:1) - /// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `837` - // Estimated: `4302` - // Minimum execution time: 44_763_000 picoseconds. - Weight::from_parts(45_368_000, 0) - .saturating_add(Weight::from_parts(0, 4302)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `360` + // Estimated: `3825` + // Minimum execution time: 36_666_000 picoseconds. + Weight::from_parts(37_779_000, 0) + .saturating_add(Weight::from_parts(0, 3825)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: XcmPallet XcmExecutionSuspended (r:0 w:1) - /// Proof Skipped: XcmPallet XcmExecutionSuspended (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) + /// Proof: `XcmPallet::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_089_000 picoseconds. - Weight::from_parts(3_246_000, 0) + // Minimum execution time: 1_869_000 picoseconds. + Weight::from_parts(2_003_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: XcmPallet SupportedVersion (r:4 w:2) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `229` - // Estimated: `11119` - // Minimum execution time: 16_733_000 picoseconds. - Weight::from_parts(17_354_000, 0) - .saturating_add(Weight::from_parts(0, 11119)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `22` + // Estimated: `13387` + // Minimum execution time: 16_188_000 picoseconds. + Weight::from_parts(16_435_000, 0) + .saturating_add(Weight::from_parts(0, 13387)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifiers (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `233` - // Estimated: `11123` - // Minimum execution time: 16_959_000 picoseconds. - Weight::from_parts(17_306_000, 0) - .saturating_add(Weight::from_parts(0, 11123)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `26` + // Estimated: `13391` + // Minimum execution time: 16_431_000 picoseconds. + Weight::from_parts(16_935_000, 0) + .saturating_add(Weight::from_parts(0, 13391)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifyTargets (r:5 w:0) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `13608` - // Minimum execution time: 17_964_000 picoseconds. - Weight::from_parts(18_548_000, 0) - .saturating_add(Weight::from_parts(0, 13608)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `40` + // Estimated: `15880` + // Minimum execution time: 18_460_000 picoseconds. + Weight::from_parts(18_885_000, 0) + .saturating_add(Weight::from_parts(0, 15880)) + .saturating_add(T::DbWeight::get().reads(6)) } - /// Storage: XcmPallet VersionNotifyTargets (r:2 w:1) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: Configuration ActiveConfig (r:1 w:0) - /// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `635` - // Estimated: `6575` - // Minimum execution time: 39_436_000 picoseconds. - Weight::from_parts(39_669_000, 0) - .saturating_add(Weight::from_parts(0, 6575)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `216` + // Estimated: `6156` + // Minimum execution time: 29_623_000 picoseconds. + Weight::from_parts(30_661_000, 0) + .saturating_add(Weight::from_parts(0, 6156)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: XcmPallet VersionNotifyTargets (r:3 w:0) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `272` - // Estimated: `8687` - // Minimum execution time: 8_991_000 picoseconds. - Weight::from_parts(9_248_000, 0) - .saturating_add(Weight::from_parts(0, 8687)) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `69` + // Estimated: `10959` + // Minimum execution time: 12_043_000 picoseconds. + Weight::from_parts(12_360_000, 0) + .saturating_add(Weight::from_parts(0, 10959)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: XcmPallet VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `240` - // Estimated: `11130` - // Minimum execution time: 17_614_000 picoseconds. - Weight::from_parts(17_948_000, 0) - .saturating_add(Weight::from_parts(0, 11130)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `33` + // Estimated: `13398` + // Minimum execution time: 16_511_000 picoseconds. + Weight::from_parts(17_011_000, 0) + .saturating_add(Weight::from_parts(0, 13398)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: Configuration ActiveConfig (r:1 w:0) - /// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `639` - // Estimated: `11529` - // Minimum execution time: 45_531_000 picoseconds. - Weight::from_parts(46_533_000, 0) - .saturating_add(Weight::from_parts(0, 11529)) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `216` + // Estimated: `13581` + // Minimum execution time: 39_041_000 picoseconds. + Weight::from_parts(39_883_000, 0) + .saturating_add(Weight::from_parts(0, 13581)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) + /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn new_query() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 2_030_000 picoseconds. + Weight::from_parts(2_150_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `XcmPallet::Queries` (r:1 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn take_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `7576` + // Estimated: `11041` + // Minimum execution time: 22_615_000 picoseconds. + Weight::from_parts(23_008_000, 0) + .saturating_add(Weight::from_parts(0, 11041)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) + /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 34_438_000 picoseconds. + Weight::from_parts(35_514_000, 0) + .saturating_add(Weight::from_parts(0, 3488)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_identity_migrator.rs b/polkadot/runtime/rococo/src/weights/runtime_common_identity_migrator.rs new file mode 100644 index 0000000000000000000000000000000000000000..cec357453b67be400c0191ac7d5c12e6961a4bee --- /dev/null +++ b/polkadot/runtime/rococo/src/weights/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 `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=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 `runtime_common::identity_migrator`. +pub struct WeightInfo(PhantomData); +impl 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/polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs b/polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs index 29f387657786afd1e07c207d733b7cfddfd4d83a..ca0575cb1b64689b7b016d63a2cfc5080dc874a5 100644 --- a/polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs +++ b/polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs @@ -16,10 +16,10 @@ //! Autogenerated weights for `runtime_parachains::configuration` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-fljshgub-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -31,11 +31,11 @@ // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=runtime_parachains::configuration // --chain=rococo-dev -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,11 +56,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_block_number() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_051_000 picoseconds. - Weight::from_parts(9_496_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_789_000 picoseconds. + Weight::from_parts(8_269_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -72,11 +72,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_u32() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_104_000 picoseconds. - Weight::from_parts(9_403_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_851_000 picoseconds. + Weight::from_parts(8_152_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -88,11 +88,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_option_u32() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_112_000 picoseconds. - Weight::from_parts(9_495_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_960_000 picoseconds. + Weight::from_parts(8_276_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -114,11 +114,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_011_000 picoseconds. - Weight::from_parts(9_460_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_164_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -130,11 +130,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_executor_params() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_940_000 picoseconds. - Weight::from_parts(10_288_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 9_782_000 picoseconds. + Weight::from_parts(10_373_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -146,11 +146,43 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_perbill() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_192_000 picoseconds. - Weight::from_parts(9_595_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_870_000 picoseconds. + Weight::from_parts(8_274_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_node_feature() -> Weight { + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 9_960_000 picoseconds. + Weight::from_parts(10_514_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_config_with_scheduler_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_913_000 picoseconds. + Weight::from_parts(8_338_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } 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/weights/xcm/mod.rs b/polkadot/runtime/rococo/src/weights/xcm/mod.rs index cc485dfbaf7e4e29fe7fdd50960490bc9c05665c..12f3df897b1eedb6e722e8f4871eb631bd35aaa3 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/mod.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/mod.rs @@ -33,25 +33,25 @@ pub enum AssetTypes { Unknown, } -impl From<&MultiAsset> for AssetTypes { - fn from(asset: &MultiAsset) -> Self { +impl From<&Asset> for AssetTypes { + fn from(asset: &Asset) -> Self { match asset { - MultiAsset { id: Concrete(MultiLocation { parents: 0, interior: Here }), .. } => + Asset { id: AssetId(Location { parents: 0, interior: Here }), .. } => AssetTypes::Balances, _ => AssetTypes::Unknown, } } } -trait WeighMultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight; +trait WeighAssets { + fn weigh_assets(&self, balances_weight: Weight) -> Weight; } // Rococo only knows about one asset, the balances pallet. const MAX_ASSETS: u64 = 1; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, balances_weight: Weight) -> Weight { match self { Self::Definite(assets) => assets .inner() @@ -72,11 +72,11 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, balances_weight: Weight) -> Weight { self.inner() .into_iter() - .map(|m| >::from(m)) + .map(|m| >::from(m)) .map(|t| match t { AssetTypes::Balances => balances_weight, AssetTypes::Unknown => Weight::MAX, @@ -87,32 +87,28 @@ impl WeighMultiAssets for MultiAssets { pub struct RococoXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for RococoXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::withdraw_asset()) + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( _origin_kind: &OriginKind, @@ -140,45 +136,37 @@ impl XcmWeightInfo for RococoXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> 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(XcmBalancesWeight::::deposit_asset()) + fn deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { // Rococo does not currently support exchange asset operations Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmBalancesWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -193,7 +181,7 @@ impl XcmWeightInfo for RococoXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -205,13 +193,13 @@ impl XcmWeightInfo for RococoXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -246,19 +234,19 @@ impl XcmWeightInfo for RococoXcmWeight { // Rococo relay should not support export message operations Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { // Rococo does not currently support asset locking operations Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { // Rococo does not currently support asset locking operations Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { // Rococo does not currently support asset locking operations Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { // Rococo does not currently support asset locking operations Weight::MAX } @@ -271,19 +259,19 @@ impl XcmWeightInfo for RococoXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } #[test] fn all_counted_has_a_sane_weight_upper_limit() { - let assets = MultiAssetFilter::Wild(AllCounted(4294967295)); + let assets = AssetFilter::Wild(AllCounted(4294967295)); let weight = Weight::from_parts(1000, 1000); - assert_eq!(assets.weigh_multi_assets(weight), weight * MAX_ASSETS); + assert_eq!(assets.weigh_assets(weight), weight * MAX_ASSETS); } diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index fb1653c549e114aeb0d5d972c26e0e230a1b1175..328879715de328d2743fa2079817e77ef6b29778 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -24,8 +24,8 @@ use super::{ use crate::governance::StakingAdmin; use frame_support::{ - match_types, parameter_types, - traits::{Everything, Nothing}, + parameter_types, + traits::{Contains, Equals, Everything, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; @@ -39,21 +39,23 @@ use xcm::latest::prelude::*; 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, XcmFeesToAccount, + ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { - pub const TokenLocation: MultiLocation = Here.into_location(); + pub TokenLocation: Location = Here.into_location(); + pub RootLocation: Location = Location::here(); pub const ThisNetwork: NetworkId = NetworkId::Rococo; - pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); + pub UniversalLocation: InteriorLocation = ThisNetwork::get().into(); pub CheckAccount: AccountId = XcmPallet::check_account(); pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); - pub TreasuryAccount: Option = Some(Treasury::account_id()); + pub TreasuryAccount: AccountId = Treasury::account_id(); } pub type LocationConverter = ( @@ -61,20 +63,20 @@ pub type LocationConverter = ( ChildParachainConvertsVia, // We can directly alias an `AccountId32` into a local account. AccountId32Aliases, - // Allow governance body to be used as a sovereign account. - HashedDescription>, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Our asset transactor. This is what allows us to interest with the runtime facilities from the -/// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. +/// point of view of XCM-only concepts like `Location` and `Asset`. /// /// Ours is only aware of the Balances pallet, which is mapped to `RocLocation`. -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // We can convert the MultiLocations with our converter above: + // We can convert the Locations with our converter above: LocationConverter, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -96,7 +98,7 @@ parameter_types! { /// The amount of weight an XCM operation takes. This is a safe overestimate. pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(TokenLocation::get()); + pub FeeAssetId: AssetId = AssetId(TokenLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -112,21 +114,25 @@ pub type XcmRouter = WithUniqueTopic< >; parameter_types! { - pub const Roc: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); - pub const AssetHub: MultiLocation = Parachain(ASSET_HUB_ID).into_location(); - 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 Tick: MultiLocation = Parachain(100).into_location(); - pub const Trick: MultiLocation = Parachain(110).into_location(); - pub const Track: MultiLocation = Parachain(120).into_location(); - pub const RocForTick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Tick::get()); - pub const RocForTrick: (MultiAssetFilter, MultiLocation) = (Roc::get(), Trick::get()); - pub const RocForTrack: (MultiAssetFilter, MultiLocation) = (Roc::get(), Track::get()); - pub const RocForAssetHub: (MultiAssetFilter, MultiLocation) = (Roc::get(), AssetHub::get()); - 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 Roc: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(TokenLocation::get()) }); + pub AssetHub: Location = Parachain(ASSET_HUB_ID).into_location(); + pub Contracts: Location = Parachain(CONTRACTS_ID).into_location(); + pub Encointer: Location = Parachain(ENCOINTER_ID).into_location(); + pub BridgeHub: Location = Parachain(BRIDGE_HUB_ID).into_location(); + pub People: Location = Parachain(PEOPLE_ID).into_location(); + pub Broker: Location = Parachain(BROKER_ID).into_location(); + pub Tick: Location = Parachain(100).into_location(); + pub Trick: Location = Parachain(110).into_location(); + pub Track: Location = Parachain(120).into_location(); + pub RocForTick: (AssetFilter, Location) = (Roc::get(), Tick::get()); + pub RocForTrick: (AssetFilter, Location) = (Roc::get(), Trick::get()); + pub RocForTrack: (AssetFilter, Location) = (Roc::get(), Track::get()); + pub RocForAssetHub: (AssetFilter, Location) = (Roc::get(), AssetHub::get()); + pub RocForContracts: (AssetFilter, Location) = (Roc::get(), Contracts::get()); + pub RocForEncointer: (AssetFilter, Location) = (Roc::get(), Encointer::get()); + pub RocForBridgeHub: (AssetFilter, Location) = (Roc::get(), BridgeHub::get()); + pub RocForPeople: (AssetFilter, Location) = (Roc::get(), People::get()); + pub RocForBroker: (AssetFilter, Location) = (Roc::get(), Broker::get()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } @@ -138,12 +144,22 @@ 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 struct OnlyParachains; +impl Contains for OnlyParachains { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (0, [Parachain(_)])) + } +} + +pub struct LocalPlurality; +impl Contains for LocalPlurality { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (0, [Plurality { .. }])) + } } /// The barriers one of which must be passed for an XCM message to be executed. @@ -166,6 +182,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; @@ -191,12 +211,19 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = XcmPallet; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeesToAccount; + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } parameter_types! { @@ -207,31 +234,26 @@ parameter_types! { pub const FellowsBodyId: BodyId = BodyId::Technical; } -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(ASSET_HUB_ID).into()); -} - -/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// Type to convert an `Origin` type value into a `Location` value which represents an interior /// location of this chain. pub type LocalOriginToLocation = ( // And a usual Signed origin to be used in XCM as a corresponding AccountId32 SignedToAccountId32, ); -/// Type to convert the `StakingAdmin` origin to a Plurality `MultiLocation` value. +/// Type to convert the `StakingAdmin` origin to a Plurality `Location` value. pub type StakingAdminToPlurality = OriginToPluralityVoice; -/// Type to convert the Fellows origin to a Plurality `MultiLocation` value. +/// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice; -/// Type to convert a pallet `Origin` type value into a `MultiLocation` value which represents an +/// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( - // StakingAdmin origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // StakingAdmin origin to be used in XCM as a corresponding Plurality `Location` value. StakingAdminToPlurality, - // Fellows origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // Fellows origin to be used in XCM as a corresponding Plurality `Location` value. FellowsToPlurality, ); @@ -265,7 +287,5 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index db014a5d864cb4945c60fb3d97e4e9a826a109a2..9753a4093045b52c8c709cfcea4a19a15dc510d6 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -7,14 +7,17 @@ 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 } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -71,13 +74,13 @@ 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.107" +serde_json = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] no_std = [] only-staking = [] runtime-metrics = [ diff --git a/polkadot/runtime/test-runtime/constants/Cargo.toml b/polkadot/runtime/test-runtime/constants/Cargo.toml index d83e92a6ce8863cc529262b3e9c0edb666e2f618..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" @@ -17,7 +20,7 @@ sp-weights = { path = "../../../../substrate/primitives/weights", default-featur sp-core = { path = "../../../../substrate/primitives/core", default-features = false } [features] -default = [ "std" ] +default = ["std"] std = [ "frame-support/std", "primitives/std", diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 99fd2198400bf821f406345ac5daabe13d4b009a..8a3cd9309dbd84bb9f6c92d9ef0dabb13a36439a 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -42,10 +42,10 @@ use frame_election_provider_support::{ onchain, SequentialPhragmen, }; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{Everything, KeyOwnerProofSystem, WithdrawReasons}, + traits::{KeyOwnerProofSystem, WithdrawReasons}, }; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; use pallet_session::historical as session_historical; @@ -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))] @@ -139,29 +139,19 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::RelayChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = Everything; type BlockWeights = BlockWeights; type BlockLength = BlockLength; - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; type Nonce = Nonce; type Hash = HashT; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = Indices; type Block = Block; - type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); type SS58Prefix = SS58Prefix; - type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } @@ -192,7 +182,7 @@ impl pallet_babe::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type KeyOwnerProof = >::Proof; @@ -229,8 +219,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -317,7 +307,8 @@ parameter_types! { // 27 eras in which slashes can be cancelled (a bit less than 7 days). pub storage SlashDeferDuration: sp_staking::EraIndex = 27; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub storage MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxExposurePageSize: u32 = 64; + pub const MaxNominators: u32 = 256; pub storage OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const MaxAuthorities: u32 = 100_000; pub const OnChainMaxWinners: u32 = u32::MAX; @@ -353,7 +344,7 @@ impl pallet_staking::Config for Runtime { type AdminOrigin = frame_system::EnsureNever<()>; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxExposurePageSize = MaxExposurePageSize; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainExecution; @@ -364,6 +355,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 = (); @@ -379,7 +371,7 @@ impl pallet_grandpa::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; type KeyOwnerProof = sp_core::Void; @@ -471,6 +463,7 @@ impl pallet_vesting::Config for Runtime { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } @@ -484,7 +477,9 @@ impl parachains_configuration::Config for Runtime { type WeightInfo = parachains_configuration::TestWeightInfo; } -impl parachains_shared::Config for Runtime {} +impl parachains_shared::Config for Runtime { + type DisabledValidators = Session; +} impl parachains_inclusion::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -526,6 +521,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 { @@ -543,6 +539,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 {} @@ -600,7 +605,7 @@ pub mod pallet_test_notifier { pub enum Event { QueryPrepared(QueryId), NotifyQueryPrepared(QueryId), - ResponseReceived(MultiLocation, QueryId, Response), + ResponseReceived(Location, QueryId, Response), } #[pallet::error] @@ -664,52 +669,52 @@ construct_runtime! { pub enum Runtime { // Basic stuff; balances is uncallable initially. - System: frame_system::{Pallet, Call, Storage, Config, Event}, + System: frame_system, // Must be before session. - Babe: pallet_babe::{Pallet, Call, Storage, Config}, + Babe: pallet_babe, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + Timestamp: pallet_timestamp, + Indices: pallet_indices, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, // Consensus support. - Authorship: pallet_authorship::{Pallet, Storage}, - Staking: pallet_staking::{Pallet, Call, Storage, Config, Event}, - Offences: pallet_offences::{Pallet, Storage, Event}, - Historical: session_historical::{Pallet}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event}, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, + Authorship: pallet_authorship, + Staking: pallet_staking, + Offences: pallet_offences, + Historical: session_historical, + Session: pallet_session, + Grandpa: pallet_grandpa, + AuthorityDiscovery: pallet_authority_discovery, // Claims. Usable initially. - Claims: claims::{Pallet, Call, Storage, Event, Config, ValidateUnsigned}, + Claims: claims, // Vesting. Usable initially, but removed once all vesting is finished. - Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config}, + Vesting: pallet_vesting, // Parachains runtime modules - Configuration: parachains_configuration::{Pallet, Call, Storage, Config}, - ParaInclusion: parachains_inclusion::{Pallet, Call, Storage, Event}, - ParaInherent: parachains_paras_inherent::{Pallet, Call, Storage, Inherent}, - Initializer: parachains_initializer::{Pallet, Call, Storage}, - Paras: parachains_paras::{Pallet, Call, Storage, Event, ValidateUnsigned}, - ParasShared: parachains_shared::{Pallet, Call, Storage}, - Scheduler: parachains_scheduler::{Pallet, Storage}, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call}, - ParasOrigin: parachains_origin::{Pallet, Origin}, - ParaSessionInfo: parachains_session_info::{Pallet, Storage}, - Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event}, - Dmp: parachains_dmp::{Pallet, Storage}, - Xcm: pallet_xcm::{Pallet, Call, Event, Origin}, - ParasDisputes: parachains_disputes::{Pallet, Storage, Event}, - ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned}, - ParaAssignmentProvider: parachains_assigner_parachains::{Pallet}, - - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event}, - - TestNotifier: pallet_test_notifier::{Pallet, Call, Event}, + Configuration: parachains_configuration, + ParaInclusion: parachains_inclusion, + ParaInherent: parachains_paras_inherent, + Initializer: parachains_initializer, + Paras: parachains_paras, + ParasShared: parachains_shared, + Scheduler: parachains_scheduler, + ParasSudoWrapper: paras_sudo_wrapper, + ParasOrigin: parachains_origin, + ParaSessionInfo: parachains_session_info, + Hrmp: parachains_hrmp, + Dmp: parachains_dmp, + Xcm: pallet_xcm, + ParasDisputes: parachains_disputes, + ParasSlashing: parachains_slashing, + ParaAssignmentProvider: parachains_assigner_parachains, + + Sudo: pallet_sudo, + + TestNotifier: pallet_test_notifier, } } @@ -762,7 +767,7 @@ sp_api::impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -824,7 +829,7 @@ sp_api::impl_runtime_apis! { } } - impl primitives::runtime_api::ParachainHost for Runtime { + impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { runtime_impl::validators::() } diff --git a/polkadot/runtime/test-runtime/src/xcm_config.rs b/polkadot/runtime/test-runtime/src/xcm_config.rs index 400658b13863645c6c5bc7a99ce9401013fc3cf8..8411b79f2529d8e2d26815bd9ab806457e4ca99b 100644 --- a/polkadot/runtime/test-runtime/src/xcm_config.rs +++ b/polkadot/runtime/test-runtime/src/xcm_config.rs @@ -16,18 +16,20 @@ use frame_support::{ parameter_types, - traits::{Everything, Nothing}, + traits::{Everything, Get, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; +use polkadot_runtime_parachains::FeeTracker; +use runtime_common::xcm_sender::{ChildParachainRouter, PriceForMessageDelivery}; use xcm::latest::prelude::*; use xcm_builder::{ - AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, SignedAccountId32AsNative, - SignedToAccountId32, + AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, + SignedAccountId32AsNative, SignedToAccountId32, WithUniqueTopic, }; use xcm_executor::{ traits::{TransactAsset, WeightTrader}, - Assets, + AssetsInHolding, }; parameter_types! { @@ -35,49 +37,74 @@ parameter_types! { pub const AnyNetwork: Option = None; pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 16; - pub const UniversalLocation: xcm::latest::InteriorMultiLocation = xcm::latest::Junctions::Here; + pub const UniversalLocation: xcm::latest::InteriorLocation = xcm::latest::Junctions::Here; + pub TokenLocation: Location = Here.into_location(); + pub FeeAssetId: AssetId = AssetId(TokenLocation::get()); } -/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior +/// Type to convert an `Origin` type value into a `Location` value which represents an interior /// location of this chain. pub type LocalOriginToLocation = ( // And a usual Signed origin to be used in XCM as a corresponding AccountId32 SignedToAccountId32, ); -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]) +/// Implementation of [`PriceForMessageDelivery`], returning a different price +/// based on whether a message contains a reanchored asset or not. +/// This implementation ensures that messages with non-reanchored assets return higher +/// prices than messages with reanchored assets. +/// Useful for `deposit_reserve_asset_works_for_any_xcm_sender` integration test. +pub struct TestDeliveryPrice(sp_std::marker::PhantomData<(A, F)>); +impl, F: FeeTracker> PriceForMessageDelivery for TestDeliveryPrice { + type Id = F::Id; + + fn price_for_delivery(_: Self::Id, msg: &Xcm<()>) -> Assets { + let base_fee: super::Balance = 1_000_000; + + let parents = msg.iter().find_map(|xcm| match xcm { + ReserveAssetDeposited(assets) => { + let AssetId(location) = &assets.inner().first().unwrap().id; + Some(location.parents) + }, + _ => None, + }); + + // If no asset is found, price defaults to `base_fee`. + let amount = base_fee + .saturating_add(base_fee.saturating_mul(parents.unwrap_or(0) as super::Balance)); + + (A::get(), amount).into() } } +pub type PriceForChildParachainDelivery = TestDeliveryPrice; + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = WithUniqueTopic< + // Only one router so far - use DMP to communicate with child parachains. + ChildParachainRouter, +>; + pub type Barrier = AllowUnpaidExecutionFrom; pub struct DummyAssetTransactor; impl TransactAsset for DummyAssetTransactor { - fn deposit_asset( - _what: &MultiAsset, - _who: &MultiLocation, - _context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { Ok(()) } fn withdraw_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _maybe_context: Option<&XcmContext>, - ) -> Result { - let asset: MultiAsset = (Parent, 100_000).into(); + ) -> Result { + let asset: Asset = (Parent, 100_000).into(); Ok(asset.into()) } } +#[derive(Clone)] pub struct DummyWeightTrader; impl WeightTrader for DummyWeightTrader { fn new() -> Self { @@ -87,10 +114,10 @@ impl WeightTrader for DummyWeightTrader { fn buy_weight( &mut self, _weight: Weight, - _payment: Assets, + _payment: AssetsInHolding, _context: &XcmContext, - ) -> Result { - Ok(Assets::default()) + ) -> Result { + Ok(AssetsInHolding::default()) } } @@ -102,7 +129,7 @@ type OriginConverter = ( pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = super::RuntimeCall; - type XcmSender = DoNothingRouter; + type XcmSender = XcmRouter; type AssetTransactor = DummyAssetTransactor; type OriginConverter = OriginConverter; type IsReserve = (); @@ -125,11 +152,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = super::RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; -} - -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(xcm::latest::Junctions::Here.into()); + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } impl pallet_xcm::Config for crate::Runtime { @@ -140,7 +166,7 @@ impl pallet_xcm::Config for crate::Runtime { type UniversalLocation = UniversalLocation; type SendXcmOrigin = EnsureXcmOrigin; type Weigher = FixedWeightBounds; - type XcmRouter = DoNothingRouter; + type XcmRouter = XcmRouter; type XcmExecuteFilter = Everything; type XcmExecutor = xcm_executor::XcmExecutor; type XcmTeleportFilter = Everything; @@ -157,7 +183,5 @@ impl pallet_xcm::Config for crate::Runtime { type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index a4f1bfb007eca82a167966aebe351180ffb947b2..4180828bcfb14d497d9da06bc74da91fa53f8d5f 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -1,20 +1,23 @@ [package] name = "westend-runtime" build = "build.rs" -version = "1.0.0" +version = "7.0.0" description = "Westend testnet Relay Chain runtime." 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 } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } -serde = { version = "1.0.188", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -46,7 +49,7 @@ frame-support = { path = "../../../substrate/frame/support", default-features = frame-system = { path = "../../../substrate/frame/system", default-features = false } frame-system-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false } westend-runtime-constants = { package = "westend-runtime-constants", path = "constants", default-features = false } -pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false } +pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false } pallet-authority-discovery = { path = "../../../substrate/frame/authority-discovery", default-features = false } pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } pallet-babe = { path = "../../../substrate/frame/babe", default-features = false } @@ -92,6 +95,7 @@ pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = pallet-whitelist = { path = "../../../substrate/frame/whitelist", default-features = false } pallet-xcm = { path = "../../xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-features = false, optional = true } +pallet-root-testing = { path = "../../../substrate/frame/root-testing", default-features = false } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } frame-try-runtime = { path = "../../../substrate/frame/try-runtime", default-features = false, optional = true } @@ -115,8 +119,8 @@ 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.107" -remote-externalities = { package = "frame-remote-externalities" , path = "../../../substrate/utils/frame/remote-externalities" } +serde_json = { workspace = true, default-features = true } +remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } tokio = { version = "1.24.2", features = ["macros"] } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } @@ -124,7 +128,7 @@ sp-tracing = { path = "../../../substrate/primitives/tracing", default-features substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] no_std = [] only-staking = [] std = [ @@ -177,6 +181,7 @@ std = [ "pallet-proxy/std", "pallet-recovery/std", "pallet-referenda/std", + "pallet-root-testing/std", "pallet-scheduler/std", "pallet-session-benchmarking?/std", "pallet-session/std", @@ -315,6 +320,7 @@ try-runtime = [ "pallet-proxy/try-runtime", "pallet-recovery/try-runtime", "pallet-referenda/try-runtime", + "pallet-root-testing/try-runtime", "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-society/try-runtime", @@ -336,9 +342,9 @@ try-runtime = [ # Set timing constants (e.g. session period) to faster versions to speed up testing. fast-runtime = [] -runtime-metrics = [ "runtime-parachains/runtime-metrics", "sp-io/with-tracing" ] +runtime-metrics = ["runtime-parachains/runtime-metrics", "sp-io/with-tracing"] -# A feature that should be enabled when the runtime should be build for on-chain +# 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" ] +# to make it smaller, like logging for example. +on-chain-release-build = ["sp-api/disable-logging"] diff --git a/polkadot/runtime/westend/constants/Cargo.toml b/polkadot/runtime/westend/constants/Cargo.toml index 2243210975b1df6bc0193fc60b9003e2199a5121..81df8f4f024dd32f23edd679b651e2edc4d32687 100644 --- a/polkadot/runtime/westend/constants/Cargo.toml +++ b/polkadot/runtime/westend/constants/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "westend-runtime-constants" -version = "1.0.0" +version = "7.0.0" description = "Constants used throughout the Westend network." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] smallvec = "1.8.0" @@ -17,9 +20,10 @@ 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" ] +default = ["std"] std = [ "frame-support/std", "primitives/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..c98f4b114fd88241dea1e2e5ffcf694848448007 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,15 @@ pub mod system_parachain { pub const COLLECTIVES_ID: u32 = 1001; /// BridgeHub parachain ID. pub const BRIDGE_HUB_ID: u32 = 1002; - - frame_support::match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(ASSET_HUB_ID | COLLECTIVES_ID | BRIDGE_HUB_ID ))} - }; - } + /// Encointer parachain ID. + pub const ENCOINTER_ID: u32 = 1003; + /// People Chain parachain ID. + pub const PEOPLE_ID: u32 = 1004; + /// Brokerage parachain ID. + pub const BROKER_ID: u32 = 1005; + + /// All system parachains of Westend. + pub type SystemParachains = IsChildSystemParachain; } /// Westend Treasury pallet instance. @@ -126,6 +130,9 @@ pub mod xcm { const ROOT_INDEX: u32 = 0; // The bodies corresponding to the Polkadot OpenGov Origins. pub const FELLOWSHIP_ADMIN_INDEX: u32 = 1; + #[deprecated = "Will be removed after August 2024; Use `xcm::latest::BodyId::Treasury` \ + instead"] + pub const TREASURER_INDEX: u32 = 2; } } diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs new file mode 100644 index 0000000000000000000000000000000000000000..71e6b696a20a0feb89e669067d02b12e6eeb89fd --- /dev/null +++ b/polkadot/runtime/westend/src/impls.rs @@ -0,0 +1,178 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::xcm_config; +use frame_support::pallet_prelude::DispatchResult; +use frame_system::RawOrigin; +use parity_scale_codec::{Decode, Encode}; +use primitives::Balance; +use runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; +use sp_std::{marker::PhantomData, prelude::*}; +use westend_runtime_constants::currency::*; +use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_executor::traits::TransactAsset; + +/// A type containing the encoding of the People Chain pallets in its runtime. Used to construct any +/// remote calls. The codec index must correspond to the index of `IdentityMigrator` in the +/// `construct_runtime` of the remote chain. +#[derive(Encode, Decode)] +enum PeopleRuntimePallets { + #[codec(index = 248)] + IdentityMigrator(IdentityMigratorCalls), +} + +/// Call encoding for the calls needed from the Identity Migrator pallet. +#[derive(Encode, Decode)] +enum IdentityMigratorCalls { + #[codec(index = 1)] + PokeDeposit(AccountId), +} + +/// Type that implements `OnReapIdentity` that will send the deposit needed to store the same +/// information on a parachain, sends the deposit there, and then updates it. +pub struct ToParachainIdentityReaper(PhantomData<(Runtime, AccountId)>); +impl ToParachainIdentityReaper { + /// Calculate the balance needed on the remote chain based on the `IdentityInfo` and `Subs` on + /// this chain. The total includes: + /// + /// - Identity basic deposit + /// - `IdentityInfo` byte deposit + /// - Sub accounts deposit + /// - 2x existential deposit (1 for account existence, 1 such that the user can transact) + fn calculate_remote_deposit(bytes: u32, subs: u32) -> Balance { + // Remote deposit constants. Parachain uses `deposit / 100` + // Source: + // https://github.com/paritytech/polkadot-sdk/blob/a146918/cumulus/parachains/common/src/westend.rs#L28 + // + // Parachain Deposit Configuration: + // + // pub const BasicDeposit: Balance = deposit(1, 17); + // pub const ByteDeposit: Balance = deposit(0, 1); + // pub const SubAccountDeposit: Balance = deposit(1, 53); + // pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; + 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)) + } +} + +// Note / Warning: This implementation should only be used in a transactional context. If not, then +// an error could result in assets being burned. +impl OnReapIdentity for ToParachainIdentityReaper +where + Runtime: frame_system::Config + pallet_xcm::Config, + AccountId: Into<[u8; 32]> + Clone + Encode, +{ + fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; + + let total_to_send = Self::calculate_remote_deposit(fields, subs); + + // define asset / destination from relay perspective + let wnd = Asset { id: AssetId(Here.into_location()), fun: Fungible(total_to_send) }; + // People Chain: ParaId 1004 + let destination: Location = Location::new(0, Parachain(1004)); + + // Do `check_out` accounting since the XCM Executor's `InitiateTeleport` doesn't support + // unpaid teleports. + + // withdraw the asset from `who` + let who_origin = + Junction::AccountId32 { network: None, id: who.clone().into() }.into_location(); + let _withdrawn = xcm_config::LocalAssetTransactor::withdraw_asset(&wnd, &who_origin, None) + .map_err(|err| { + log::error!( + target: "runtime::on_reap_identity", + "withdraw_asset(what: {:?}, who_origin: {:?}) error: {:?}", + wnd, who_origin, err + ); + pallet_xcm::Error::::LowBalance + })?; + + // check out + xcm_config::LocalAssetTransactor::can_check_out( + &destination, + &wnd, + // not used in AssetTransactor + &XcmContext { origin: None, message_id: [0; 32], topic: None }, + ) + .map_err(|err| { + log::error!( + target: "runtime::on_reap_identity", + "can_check_out(destination: {:?}, asset: {:?}, _) error: {:?}", + destination, wnd, err + ); + pallet_xcm::Error::::CannotCheckOutTeleport + })?; + xcm_config::LocalAssetTransactor::check_out( + &destination, + &wnd, + // not used in AssetTransactor + &XcmContext { origin: None, message_id: [0; 32], topic: None }, + ); + + // reanchor + let wnd_reanchored: Assets = + vec![Asset { id: AssetId(Location::new(1, Here)), fun: Fungible(total_to_send) }] + .into(); + + let poke = PeopleRuntimePallets::::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::::poke_deposit().saturating_mul(2); + + // Actual program to execute on People Chain. + let program: Xcm<()> = Xcm(vec![ + // Unpaid as this is constructed by the system, once per user. The user shouldn't have + // their balance reduced by teleport fees for the favor of migrating. + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + // Receive the asset into holding. + ReceiveTeleportedAsset(wnd_reanchored), + // Deposit into the user's account. + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Junction::AccountId32 { network: None, id: who.clone().into() } + .into_location() + .into(), + }, + // Poke the deposit to reserve the appropriate amount on the parachain. + Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: remote_weight_limit, + call: poke.encode().into(), + }, + ]); + + // send + let _ = >::send( + RawOrigin::Root.into(), + Box::new(VersionedLocation::V4(destination)), + Box::new(VersionedXcm::V4(program)), + )?; + Ok(()) + } +} diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 3d4db3cdd76be08319be0dcfd61326f1861c3251..15d78706d3a4964841bd8ceb0a5a622f9d2d8776 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -27,25 +27,27 @@ use beefy_primitives::{ }; use frame_election_provider_support::{bounds::ElectionBoundsBuilder, onchain, SequentialPhragmen}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ - fungible::HoldConsideration, ConstU32, EitherOf, EitherOfDiverse, Everything, + fungible::HoldConsideration, ConstU32, Contains, EitherOf, EitherOfDiverse, EverythingBut, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, ProcessMessage, ProcessMessageError, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter}, PalletId, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureRoot, EnsureSigned}; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; -use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use pallet_identity::legacy::IdentityInfo; use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - slashing, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + 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, @@ -55,16 +57,19 @@ use primitives::{ use runtime_common::{ assigned_slots, auctions, crowdloan, elections::OnChainAccuracy, - impl_runtime_weights, + identity_migrator, impl_runtime_weights, impls::{ - LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter, + LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, }, - paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BalanceToU256, BlockHashCount, - BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance, + paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, + traits::Leaser, + BalanceToU256, BlockHashCount, BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, + U256ToBalance, }; use runtime_parachains::{ - assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, disputes as parachains_disputes, + assigner_coretime as parachains_assigner_coretime, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -83,11 +88,12 @@ use sp_runtime::{ curve::PiecewiseLinear, generic, impl_opaque_keys, traits::{ - AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Extrinsic as ExtrinsicT, - IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion, Verify, + BlakeTwo256, Block as BlockT, ConvertInto, Extrinsic as ExtrinsicT, IdentityLookup, + Keccak256, OpaqueKeys, SaturatedConversion, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, Percent, Permill, + ApplyExtrinsicResult, BoundToRuntimeAppPublic, FixedU128, KeyTypeId, Perbill, Percent, Permill, + RuntimeAppPublic, }; use sp_staking::SessionIndex; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -95,8 +101,8 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use sp_version::NativeVersion; use sp_version::RuntimeVersion; use xcm::{ - latest::{InteriorMultiLocation, Junction, Junction::PalletInstance}, - VersionedMultiLocation, + latest::{InteriorLocation, Junction, Junction::PalletInstance}, + VersionedLocation, }; use xcm_builder::PayOverXcm; @@ -112,12 +118,16 @@ 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; pub mod xcm_config; +// Implemented types. +mod impls; +use impls::ToParachainIdentityReaper; + // Governance and configurations. pub mod governance; use governance::{ @@ -140,10 +150,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 10020, + spec_version: 1_008_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 22, + transaction_version: 24, state_version: 1, }; @@ -160,34 +170,37 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } +/// A type to identify calls to the Identity pallet. These will be filtered to prevent invocation, +/// locking the state of the pallet and preventing further updates to identities and sub-identities. +/// The locked state will be the genesis state of a new system chain and then removed from the Relay +/// Chain. +pub struct IsIdentityCall; +impl Contains for IsIdentityCall { + fn contains(c: &RuntimeCall) -> bool { + matches!(c, RuntimeCall::Identity(_)) + } +} + parameter_types! { pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::RelayChainDefaultConfig)] impl frame_system::Config for Runtime { - type BaseCallFilter = Everything; + type BaseCallFilter = EverythingBut; type BlockWeights = BlockWeights; type BlockLength = BlockLength; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type AccountId = AccountId; - type Lookup = AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); type SystemWeightInfo = weights::frame_system::WeightInfo; type SS58Prefix = SS58Prefix; - type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } @@ -254,10 +267,9 @@ impl pallet_babe::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; - type KeyOwnerProof = - >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_babe::EquivocationReportSystem; @@ -292,9 +304,9 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type WeightInfo = weights::pallet_balances::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = ConstU32<1>; - type MaxHolds = ConstU32<1>; } parameter_types! { @@ -304,11 +316,11 @@ parameter_types! { impl pallet_beefy::Config for Runtime { type BeefyId = BeefyId; type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = BeefySetIdSessionEntries; type OnNewValidatorSet = BeefyMmrLeaf; type WeightInfo = (); - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem; } @@ -332,19 +344,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); } @@ -400,7 +399,7 @@ impl pallet_timestamp::Config for Runtime { impl pallet_authorship::Config for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type EventHandler = (Staking, ImOnline); + type EventHandler = Staking; } parameter_types! { @@ -408,14 +407,43 @@ parameter_types! { pub const Offset: BlockNumber = 0; } -impl_opaque_keys! { - pub struct OldSessionKeys { - pub grandpa: Grandpa, - pub babe: Babe, - pub im_online: ImOnline, - pub para_validator: Initializer, - pub para_assignment: ParaSessionInfo, - pub authority_discovery: AuthorityDiscovery, +#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] +pub struct OldSessionKeys { + pub grandpa: ::Public, + pub babe: ::Public, + pub im_online: pallet_im_online::sr25519::AuthorityId, + pub para_validator: ::Public, + pub para_assignment: ::Public, + pub authority_discovery: ::Public, + pub beefy: ::Public, +} + +impl OpaqueKeys for OldSessionKeys { + type KeyTypeIdProviders = (); + fn key_ids() -> &'static [KeyTypeId] { + &[ + <::Public>::ID, + <::Public>::ID, + sp_core::crypto::key_types::IM_ONLINE, + <::Public>::ID, + <::Public>::ID, + <::Public>::ID, + <::Public>::ID, + ] + } + fn get_raw(&self, i: KeyTypeId) -> &[u8] { + match i { + <::Public>::ID => self.grandpa.as_ref(), + <::Public>::ID => self.babe.as_ref(), + sp_core::crypto::key_types::IM_ONLINE => self.im_online.as_ref(), + <::Public>::ID => self.para_validator.as_ref(), + <::Public>::ID => + self.para_assignment.as_ref(), + <::Public>::ID => + self.authority_discovery.as_ref(), + <::Public>::ID => self.beefy.as_ref(), + _ => &[], + } } } @@ -423,7 +451,6 @@ impl_opaque_keys! { pub struct SessionKeys { pub grandpa: Grandpa, pub babe: Babe, - pub im_online: ImOnline, pub para_validator: Initializer, pub para_assignment: ParaSessionInfo, pub authority_discovery: AuthorityDiscovery, @@ -432,28 +459,14 @@ impl_opaque_keys! { } // remove this when removing `OldSessionKeys` -fn transform_session_keys(v: AccountId, old: OldSessionKeys) -> SessionKeys { +fn transform_session_keys(_v: AccountId, old: OldSessionKeys) -> SessionKeys { SessionKeys { grandpa: old.grandpa, babe: old.babe, - im_online: old.im_online, para_validator: old.para_validator, para_assignment: old.para_assignment, authority_discovery: old.authority_discovery, - beefy: { - // From Session::upgrade_keys(): - // - // Care should be taken that the raw versions of the - // added keys are unique for every `ValidatorId, KeyTypeId` combination. - // This is an invariant that the session pallet typically maintains internally. - // - // So, produce a dummy value that's unique for the `ValidatorId, KeyTypeId` combination. - let mut id: BeefyId = sp_application_crypto::ecdsa::Public::from_raw([0u8; 33]).into(); - let id_raw: &mut [u8] = id.as_mut(); - id_raw[1..33].copy_from_slice(v.as_ref()); - id_raw[0..4].copy_from_slice(b"beef"); - id - }, + beefy: old.beefy, } } @@ -507,7 +520,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; @@ -584,7 +596,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; @@ -643,9 +654,14 @@ parameter_types! { // 1 era in which slashes can be cancelled (6 hours). pub const SlashDeferDuration: sp_staking::EraIndex = 1; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxExposurePageSize: u32 = 64; + // Note: this is not really correct as Max Nominators is (MaxExposurePageSize * page_count) but + // this is an unbounded number. We just set it to a reasonably high value, 1 full page + // of nominators. + 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 { @@ -660,10 +676,10 @@ 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 MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxExposurePageSize = MaxExposurePageSize; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = ElectionProviderMultiPhase; @@ -673,6 +689,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 = NominationPools; type WeightInfo = weights::pallet_staking::WeightInfo; @@ -686,8 +703,6 @@ impl pallet_fast_unstake::Config for Runtime { type ControlOrigin = EnsureRoot; type Staking = Staking; type MaxErasToCheckPerBlock = ConstU32<1>; - #[cfg(feature = "runtime-benchmarks")] - type MaxBackersPerValidator = MaxNominatorRewardedPerValidator; type WeightInfo = weights::pallet_fast_unstake::WeightInfo; } @@ -701,7 +716,7 @@ parameter_types! { pub const PayoutSpendPeriod: BlockNumber = 30 * DAYS; // The asset's interior location for the paying account. This is the Treasury // pallet instance (which sits at index 37). - pub TreasuryInteriorLocation: InteriorMultiLocation = PalletInstance(37).into(); + pub TreasuryInteriorLocation: InteriorLocation = PalletInstance(37).into(); pub const TipCountdown: BlockNumber = 1 * DAYS; pub const TipFindersFee: Percent = Percent::from_percent(20); @@ -732,7 +747,7 @@ impl pallet_treasury::Config for Runtime { type SpendFunds = (); type SpendOrigin = TreasurySpender; type AssetKind = VersionedLocatableAsset; - type Beneficiary = VersionedMultiLocation; + type Beneficiary = VersionedLocation; type BeneficiaryLookup = IdentityLookup; type Paymaster = PayOverXcm< TreasuryInteriorLocation, @@ -742,7 +757,7 @@ impl pallet_treasury::Config for Runtime { Self::Beneficiary, Self::AssetKind, LocatableAssetConverter, - VersionedMultiLocationConverter, + VersionedLocationConverter, >; type BalanceConverter = AssetRate; type PayoutPeriod = PayoutSpendPeriod; @@ -762,19 +777,6 @@ impl pallet_authority_discovery::Config for Runtime { parameter_types! { pub const NposSolutionPriority: TransactionPriority = TransactionPriority::max_value() / 2; - pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); -} - -impl pallet_im_online::Config for Runtime { - type AuthorityId = ImOnlineId; - type RuntimeEvent = RuntimeEvent; - type ValidatorSet = Historical; - type NextSessionRotation = Babe; - type ReportUnresponsiveness = Offences; - type UnsignedPriority = ImOnlineUnsignedPriority; - type WeightInfo = weights::pallet_im_online::WeightInfo; - type MaxKeys = MaxKeys; - type MaxPeerInHeartbeats = MaxPeerInHeartbeats; } parameter_types! { @@ -786,10 +788,10 @@ impl pallet_grandpa::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem; @@ -859,7 +861,7 @@ where parameter_types! { // Minimum 100 bytes/KSM deposited (1 CENT/byte) pub const BasicDeposit: Balance = 1000 * CENTS; // 258 bytes on-chain - pub const FieldDeposit: Balance = 250 * CENTS; // 66 bytes on-chain + pub const ByteDeposit: Balance = deposit(0, 1); pub const SubAccountDeposit: Balance = 200 * CENTS; // 53 bytes on-chain pub const MaxSubAccounts: u32 = 100; pub const MaxAdditionalFields: u32 = 100; @@ -871,13 +873,19 @@ impl pallet_identity::Config for Runtime { type Currency = Balances; type Slashed = (); type BasicDeposit = BasicDeposit; - type FieldDeposit = FieldDeposit; + type ByteDeposit = ByteDeposit; type SubAccountDeposit = SubAccountDeposit; type MaxSubAccounts = MaxSubAccounts; - type MaxAdditionalFields = MaxAdditionalFields; + type IdentityInformation = IdentityInfo; type MaxRegistrars = MaxRegistrars; type ForceOrigin = EitherOf, GeneralAdmin>; type RegistrarOrigin = EitherOf, GeneralAdmin>; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -937,6 +945,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; } @@ -1004,7 +1013,6 @@ impl InstanceFilter for ProxyType { RuntimeCall::Staking(..) | RuntimeCall::Session(..) | RuntimeCall::Grandpa(..) | - RuntimeCall::ImOnline(..) | RuntimeCall::Utility(..) | RuntimeCall::Identity(..) | RuntimeCall::ConvictionVoting(..) | @@ -1111,7 +1119,9 @@ impl parachains_configuration::Config for Runtime { type WeightInfo = weights::runtime_parachains_configuration::WeightInfo; } -impl parachains_shared::Config for Runtime {} +impl parachains_shared::Config for Runtime { + type DisabledValidators = Session; +} impl parachains_session_info::Config for Runtime { type ValidatorSet = Historical; @@ -1136,6 +1146,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = (); + type AssignCoretime = CoretimeAssignmentProvider; } parameter_types! { @@ -1202,15 +1213,42 @@ 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 parachains_assigner_parachains::Config for Runtime {} +impl coretime::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BrokerId = BrokerId; + type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; + type SendXcm = crate::xcm_config::XcmRouter; +} + +parameter_types! { + pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1); +} + +impl parachains_assigner_on_demand::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type TrafficDefaultValue = OnDemandTrafficDefaultValue; + type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; +} + +impl parachains_assigner_coretime::Config for Runtime {} impl parachains_initializer::Config for Runtime { type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; + type CoretimeOnNewSession = Coretime; } impl paras_sudo_wrapper::Config for Runtime {} @@ -1324,6 +1362,13 @@ impl auctions::Config for Runtime { type WeightInfo = weights::runtime_common_auctions::WeightInfo; } +impl identity_migrator::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reaper = EnsureSigned; + type ReapIdentityHandler = ToParachainIdentityReaper; + type WeightInfo = weights::runtime_common_identity_migrator::WeightInfo; +} + parameter_types! { pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); pub const MaxPointsToBalance: u8 = 10; @@ -1346,6 +1391,10 @@ impl pallet_nomination_pools::Config for Runtime { type MaxPointsToBalance = MaxPointsToBalance; } +impl pallet_root_testing::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + parameter_types! { // The deposit configuration for the singed migration. Specially if you want to allow any signed account to do the migration (see `SignedFilter`, these deposits should be high) pub const MigrationSignedDepositPerItem: Balance = 1 * CENTS; @@ -1369,117 +1418,123 @@ construct_runtime! { pub enum Runtime { // Basic stuff; balances is uncallable initially. - System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, + System: frame_system = 0, // Babe must be before session. - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned} = 1, + Babe: pallet_babe = 1, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 2, - Indices: pallet_indices::{Pallet, Call, Storage, Config, Event} = 3, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 4, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 26, + Timestamp: pallet_timestamp = 2, + Indices: pallet_indices = 3, + Balances: pallet_balances = 4, + TransactionPayment: pallet_transaction_payment = 26, // Consensus support. - // Authorship must be before session in order to note author in the correct session and era - // for im-online and staking. - Authorship: pallet_authorship::{Pallet, Storage} = 5, - Staking: pallet_staking::{Pallet, Call, Storage, Config, Event} = 6, - Offences: pallet_offences::{Pallet, Storage, Event} = 7, - Historical: session_historical::{Pallet} = 27, - - // BEEFY Bridges support. - Beefy: pallet_beefy::{Pallet, Call, Storage, Config, ValidateUnsigned} = 200, - // 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} = 201, - BeefyMmrLeaf: pallet_beefy_mmr::{Pallet, Storage} = 202, + // Authorship must be before session in order to note author in the correct session and era. + Authorship: pallet_authorship = 5, + Staking: pallet_staking = 6, + Offences: pallet_offences = 7, + Historical: session_historical = 27, - Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 8, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned} = 10, - ImOnline: pallet_im_online::{Pallet, Call, Storage, Event, ValidateUnsigned, Config} = 11, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config} = 12, + Session: pallet_session = 8, + Grandpa: pallet_grandpa = 10, + AuthorityDiscovery: pallet_authority_discovery = 12, // Utility module. - Utility: pallet_utility::{Pallet, Call, Event} = 16, + Utility: pallet_utility = 16, // Less simple identity module. - Identity: pallet_identity::{Pallet, Call, Storage, Event} = 17, + Identity: pallet_identity = 17, // Social recovery module. - Recovery: pallet_recovery::{Pallet, Call, Storage, Event} = 18, + Recovery: pallet_recovery = 18, // Vesting. Usable initially, but removed once all vesting is finished. - Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config} = 19, + Vesting: pallet_vesting = 19, // System scheduler. - Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 20, + Scheduler: pallet_scheduler = 20, // Preimage registrar. - Preimage: pallet_preimage::{Pallet, Call, Storage, Event, HoldReason} = 28, + Preimage: pallet_preimage = 28, // Sudo. - Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config} = 21, + Sudo: pallet_sudo = 21, // Proxy module. Late addition. - Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 22, + Proxy: pallet_proxy = 22, // Multisig module. Late addition. - Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 23, + Multisig: pallet_multisig = 23, // Election pallet. Only works with staking, but placed here to maintain indices. - ElectionProviderMultiPhase: pallet_election_provider_multi_phase::{Pallet, Call, Storage, Event, ValidateUnsigned} = 24, + ElectionProviderMultiPhase: pallet_election_provider_multi_phase = 24, // Provides a semi-sorted list of nominators for staking. - VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event} = 25, + VoterList: pallet_bags_list:: = 25, // Nomination pools for staking. - NominationPools: pallet_nomination_pools::{Pallet, Call, Storage, Event, Config, FreezeReason} = 29, + NominationPools: pallet_nomination_pools = 29, // Fast unstake pallet: extension to staking. FastUnstake: pallet_fast_unstake = 30, // OpenGov - ConvictionVoting: pallet_conviction_voting::{Pallet, Call, Storage, Event} = 31, - Referenda: pallet_referenda::{Pallet, Call, Storage, Event} = 32, - Origins: pallet_custom_origins::{Origin} = 35, - Whitelist: pallet_whitelist::{Pallet, Call, Storage, Event} = 36, + ConvictionVoting: pallet_conviction_voting = 31, + Referenda: pallet_referenda = 32, + Origins: pallet_custom_origins = 35, + Whitelist: pallet_whitelist = 36, // Treasury - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 37, + Treasury: pallet_treasury = 37, // Parachains pallets. Start indices at 40 to leave room. - ParachainsOrigin: parachains_origin::{Pallet, Origin} = 41, - Configuration: parachains_configuration::{Pallet, Call, Storage, Config} = 42, - ParasShared: parachains_shared::{Pallet, Call, Storage} = 43, - ParaInclusion: parachains_inclusion::{Pallet, Call, Storage, Event} = 44, - ParaInherent: parachains_paras_inherent::{Pallet, Call, Storage, Inherent} = 45, - ParaScheduler: parachains_scheduler::{Pallet, Storage} = 46, - Paras: parachains_paras::{Pallet, Call, Storage, Event, Config, ValidateUnsigned} = 47, - Initializer: parachains_initializer::{Pallet, Call, Storage} = 48, - Dmp: parachains_dmp::{Pallet, Storage} = 49, + ParachainsOrigin: parachains_origin = 41, + Configuration: parachains_configuration = 42, + ParasShared: parachains_shared = 43, + ParaInclusion: parachains_inclusion = 44, + ParaInherent: parachains_paras_inherent = 45, + ParaScheduler: parachains_scheduler = 46, + Paras: parachains_paras = 47, + Initializer: parachains_initializer = 48, + Dmp: parachains_dmp = 49, // RIP Ump 50 - Hrmp: parachains_hrmp::{Pallet, Call, Storage, Event, Config} = 51, - 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, + Hrmp: parachains_hrmp = 51, + ParaSessionInfo: parachains_session_info = 52, + ParasDisputes: parachains_disputes = 53, + ParasSlashing: parachains_slashing = 54, + OnDemandAssignmentProvider: parachains_assigner_on_demand = 56, + CoretimeAssignmentProvider: parachains_assigner_coretime = 57, // Parachain Onboarding Pallets. Start indices at 60 to leave room. - Registrar: paras_registrar::{Pallet, Call, Storage, Event, Config} = 60, - Slots: slots::{Pallet, Call, Storage, Event} = 61, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call} = 62, - Auctions: auctions::{Pallet, Call, Storage, Event} = 63, - Crowdloan: crowdloan::{Pallet, Call, Storage, Event} = 64, - AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event, Config} = 65, + Registrar: paras_registrar = 60, + Slots: slots = 61, + ParasSudoWrapper: paras_sudo_wrapper = 62, + Auctions: auctions = 63, + Crowdloan: crowdloan = 64, + AssignedSlots: assigned_slots = 65, + Coretime: coretime = 66, // Pallet for sending XCM. - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, + XcmPallet: pallet_xcm = 99, // Generalized message queue - MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 100, + MessageQueue: pallet_message_queue = 100, // Asset rate. - AssetRate: pallet_asset_rate::{Pallet, Call, Storage, Event} = 101, + AssetRate: pallet_asset_rate = 101, + + // Root testing pallet. + RootTesting: pallet_root_testing = 102, + + // BEEFY Bridges support. + Beefy: pallet_beefy = 200, + // MMR leaf construction must be after session in order to have a leaf's next_auth_set + // refer to block. See issue polkadot-fellows/runtimes#160 for details. + Mmr: pallet_mmr = 201, + BeefyMmrLeaf: pallet_beefy_mmr = 202, + + // Pallet for migrating Identity to a parachain. To be removed post-migration. + IdentityMigrator: identity_migrator = 248, } } @@ -1522,30 +1577,137 @@ pub type Migrations = migrations::Unreleased; #[allow(deprecated, missing_docs)] pub mod migrations { use super::*; + #[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())) + } + } - /// Upgrade Session keys to include BEEFY key. + parameter_types! { + pub const ImOnlinePalletName: &'static str = "ImOnline"; + } + + /// Upgrade Session keys to exclude `ImOnline` key. /// When this is removed, should also remove `OldSessionKeys`. pub struct UpgradeSessionKeys; + const UPGRADE_SESSION_KEYS_FROM_SPEC: u32 = 104000; + impl frame_support::traits::OnRuntimeUpgrade for UpgradeSessionKeys { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::warn!(target: "runtime::session_keys", "Skipping session keys migration pre-upgrade check due to spec version (already applied?)"); + return Ok(Vec::new()) + } + + log::info!(target: "runtime::session_keys", "Collecting pre-upgrade session keys state"); + let key_ids = SessionKeys::key_ids(); + frame_support::ensure!( + key_ids.into_iter().find(|&k| *k == sp_core::crypto::key_types::IM_ONLINE) == None, + "New session keys contain the ImOnline key that should have been removed", + ); + let storage_key = pallet_session::QueuedKeys::::hashed_key(); + let mut state: Vec = Vec::new(); + frame_support::storage::unhashed::get::>( + &storage_key, + ) + .ok_or::("Queued keys are not available".into())? + .into_iter() + .for_each(|(id, keys)| { + state.extend_from_slice(id.as_slice()); + for key_id in key_ids { + state.extend_from_slice(keys.get_raw(*key_id)); + } + }); + frame_support::ensure!(state.len() > 0, "Queued keys are not empty before upgrade"); + Ok(state) + } + fn on_runtime_upgrade() -> Weight { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::warn!("Skipping session keys upgrade: already applied"); + return ::DbWeight::get().reads(1) + } + log::info!("Upgrading session keys"); Session::upgrade_keys::(transform_session_keys); Perbill::from_percent(50) * BlockWeights::get().max_block } + + #[cfg(feature = "try-runtime")] + fn post_upgrade( + old_state: sp_std::vec::Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + if System::last_runtime_upgrade_spec_version() > UPGRADE_SESSION_KEYS_FROM_SPEC { + log::warn!(target: "runtime::session_keys", "Skipping session keys migration post-upgrade check due to spec version (already applied?)"); + return Ok(()) + } + + let key_ids = SessionKeys::key_ids(); + let mut new_state: Vec = Vec::new(); + pallet_session::QueuedKeys::::get().into_iter().for_each(|(id, keys)| { + new_state.extend_from_slice(id.as_slice()); + for key_id in key_ids { + new_state.extend_from_slice(keys.get_raw(*key_id)); + } + }); + frame_support::ensure!(new_state.len() > 0, "Queued keys are not empty after upgrade"); + frame_support::ensure!( + old_state == new_state, + "Pre-upgrade and post-upgrade keys do not match!" + ); + log::info!(target: "runtime::session_keys", "Session keys migrated successfully"); + Ok(()) + } } + // We don't have a limit in the Relay Chain. + const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX; + /// Unreleased migrations. Add new ones here: pub type Unreleased = ( - pallet_im_online::migration::v1::Migration, parachains_configuration::migration::v7::MigrateToV7, - assigned_slots::migration::v1::VersionCheckedMigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + pallet_staking::migrations::v14::MigrateToV14, + assigned_slots::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, - UpgradeSessionKeys, parachains_configuration::migration::v9::MigrateToV9, - paras_registrar::migration::VersionCheckedMigrateToV1, - pallet_nomination_pools::migration::versioned_migrations::V5toV6, + paras_registrar::migration::MigrateToV1, pallet_referenda::migration::v1::MigrateV0ToV1, - pallet_nomination_pools::migration::versioned_migrations::V6ToV7, + pallet_grandpa::migrations::MigrateV4ToV5, + parachains_configuration::migration::v10::MigrateToV10, + pallet_nomination_pools::migration::unversioned::TotalValueLockedSync, + UpgradeSessionKeys, + frame_support::migrations::RemovePallet< + ImOnlinePalletName, + ::DbWeight, + >, + // Migrate Identity pallet for Usernames + pallet_identity::migration::versioned::V0ToV1, + parachains_configuration::migration::v11::MigrateToV11, + parachains_configuration::migration::v12::MigrateToV12, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, + // Migrate from legacy lease to coretime. Needs to run after configuration v11 + coretime::migration::MigrateToCoretime< + Runtime, + crate::xcm_config::XcmRouter, + GetLegacyLeaseImpl, + >, ); } @@ -1573,6 +1735,7 @@ mod benches { [runtime_common::assigned_slots, AssignedSlots] [runtime_common::auctions, Auctions] [runtime_common::crowdloan, Crowdloan] + [runtime_common::identity_migrator, IdentityMigrator] [runtime_common::paras_registrar, Registrar] [runtime_common::slots, Slots] [runtime_parachains::configuration, Configuration] @@ -1583,6 +1746,8 @@ mod benches { [runtime_parachains::initializer, Initializer] [runtime_parachains::paras, Paras] [runtime_parachains::paras_inherent, ParaInherent] + [runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [runtime_parachains::coretime, Coretime] // Substrate [pallet_bags_list, VoterList] [pallet_balances, Balances] @@ -1591,7 +1756,6 @@ mod benches { [frame_election_provider_support, ElectionProviderBench::] [pallet_fast_unstake, FastUnstake] [pallet_identity, Identity] - [pallet_im_online, ImOnline] [pallet_indices, Indices] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] @@ -1613,7 +1777,7 @@ mod benches { [pallet_whitelist, Whitelist] [pallet_asset_rate, AssetRate] // XCM - [pallet_xcm, XcmPallet] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] @@ -1630,7 +1794,7 @@ sp_api::impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -1682,12 +1846,18 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } - #[api_version(8)] - impl primitives::runtime_api::ParachainHost for Runtime { + #[api_version(10)] + impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() } @@ -1830,9 +2000,17 @@ 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::() } + + fn node_features() -> NodeFeatures { + parachains_staging_runtime_api_impl::node_features::() + } } impl beefy_primitives::BeefyApi for Runtime { @@ -2090,10 +2268,14 @@ sp_api::impl_runtime_apis! { } } - impl pallet_staking_runtime_api::StakingApi for Runtime { + impl pallet_staking_runtime_api::StakingApi for Runtime { fn nominations_quota(balance: Balance) -> u32 { Staking::api_nominations_quota(balance) } + + fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page { + Staking::api_eras_stakers_page_count(era, account) + } } #[cfg(feature = "try-runtime")] @@ -2128,6 +2310,7 @@ sp_api::impl_runtime_apis! { use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as ElectionProviderBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; use frame_system_benchmarking::Pallet as SystemBench; use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; @@ -2155,58 +2338,125 @@ sp_api::impl_runtime_apis! { use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; use pallet_election_provider_support_benchmarking::Pallet as ElectionProviderBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; use frame_system_benchmarking::Pallet as SystemBench; use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} - impl frame_system_benchmarking::Config for Runtime {} - impl pallet_nomination_pools_benchmarking::Config for Runtime {} - impl runtime_parachains::disputes::slashing::benchmarking::Config for Runtime {} - use xcm::latest::{ - AssetId::*, Fungibility::*, InteriorMultiLocation, Junction, Junctions::*, - MultiAsset, MultiAssets, MultiLocation, NetworkId, Response, - }; use xcm_config::{AssetHub, TokenLocation}; parameter_types! { - pub ExistentialDepositMultiAsset: Option = Some(( + pub ExistentialDepositAsset: Option = Some(( TokenLocation::get(), ExistentialDeposit::get() ).into()); - pub ToParachain: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub AssetHubParaId: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + + fn reachable_dest() -> Option { + Some(crate::xcm_config::AssetHub::get()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay/native token can be teleported to/from AH. + Some(( + Asset { fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, + crate::xcm_config::AssetHub::get(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + // Relay can reserve transfer native token to some random parachain. + Some(( + Asset { + fun: Fungible(ExistentialDeposit::get()), + id: AssetId(Here.into()) + }, + crate::Junction::Parachain(RandomParaId::get().into()).into(), + )) + } + + fn set_up_complex_asset_transfer( + ) -> Option<(Assets, u32, Location, 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 + ) + } + + fn get_asset() -> Asset { + Asset { + id: AssetId(Location::here()), + fun: Fungible(ExistentialDeposit::get()), + } + } } + impl frame_system_benchmarking::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} + impl runtime_parachains::disputes::slashing::benchmarking::Config for Runtime {} + + use xcm::latest::{ + AssetId, Fungibility::*, InteriorLocation, Junction, Junctions::*, + Asset, Assets, Location, NetworkId, Response, + }; impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationConverter; type DeliveryHelper = runtime_common::xcm_sender::ToParachainDeliveryHelper< xcm_config::XcmConfig, - ExistentialDepositMultiAsset, + ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; - fn valid_destination() -> Result { + fn valid_destination() -> Result { Ok(AssetHub::get()) } - fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + fn worst_case_holding(_depositable_count: u32) -> Assets { // Westend only knows about WND. - vec![MultiAsset{ - id: Concrete(TokenLocation::get()), + vec![Asset{ + id: AssetId(TokenLocation::get()), fun: Fungible(1_000_000 * UNITS), }].into() } } parameter_types! { - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub TrustedTeleporter: Option<(Location, Asset)> = Some(( AssetHub::get(), - MultiAsset { fun: Fungible(1 * UNITS), id: Concrete(TokenLocation::get()) }, + Asset { fun: Fungible(1 * UNITS), id: AssetId(TokenLocation::get()) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; } impl pallet_xcm_benchmarks::fungible::Config for Runtime { @@ -2216,9 +2466,9 @@ sp_api::impl_runtime_apis! { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - MultiAsset { - id: Concrete(TokenLocation::get()), + fn get_asset() -> Asset { + Asset { + id: AssetId(TokenLocation::get()), fun: Fungible(1 * UNITS), } } @@ -2232,43 +2482,50 @@ sp_api::impl_runtime_apis! { (0u64, Response::Version(Default::default())) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { // Westend doesn't support asset exchanges Err(BenchmarkError::Skip) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { // The XCM executor of Westend doesn't have a configured `UniversalAliases` Err(BenchmarkError::Skip) } - fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { Ok((AssetHub::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(AssetHub::get()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { let origin = AssetHub::get(); - let assets: MultiAssets = (Concrete(TokenLocation::get()), 1_000 * UNITS).into(); - let ticket = MultiLocation { parents: 0, interior: Here }; + let assets: Assets = (AssetId(TokenLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(TokenLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { // Westend doesn't support asset locking Err(BenchmarkError::Skip) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { // Westend doesn't support exporting messages Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { // The XCM executor of Westend doesn't have a configured `Aliasers` Err(BenchmarkError::Skip) } @@ -2340,12 +2597,11 @@ mod remote_tests { mod clean_state_migration { use super::Runtime; + #[cfg(feature = "try-runtime")] + use super::Vec; use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; use pallet_state_trie_migration::MigrationLimits; - #[cfg(not(feature = "std"))] - use sp_std::prelude::*; - #[storage_alias] type AutoLimits = StorageValue, ValueQuery>; 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 9ae6798d70b6e234c7ffbbfd99f2f97c56fb30a4..f6a9008d71876726dda2ad240ecdb4588a1f82a3 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -24,7 +24,6 @@ pub mod pallet_conviction_voting; pub mod pallet_election_provider_multi_phase; pub mod pallet_fast_unstake; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; @@ -46,9 +45,12 @@ pub mod pallet_xcm; pub mod runtime_common_assigned_slots; pub mod runtime_common_auctions; 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_balances.rs b/polkadot/runtime/westend/src/weights/pallet_balances.rs index 4508507206c27ae10137194d62d1981c6a59b23d..25626e940209d50859a57dee60f483b15a8db257 100644 --- a/polkadot/runtime/westend/src/weights/pallet_balances.rs +++ b/polkadot/runtime/westend/src/weights/pallet_balances.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-j8vvqcjr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_balances // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,104 +48,112 @@ 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) + /// 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: 57_163_000 picoseconds. - Weight::from_parts(58_105_000, 0) + // Minimum execution time: 43_680_000 picoseconds. + Weight::from_parts(45_012_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) + /// 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: 43_085_000 picoseconds. - Weight::from_parts(43_779_000, 0) + // Minimum execution time: 34_038_000 picoseconds. + Weight::from_parts(35_771_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) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_set_balance_creating() -> Weight { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 16_153_000 picoseconds. - Weight::from_parts(16_725_000, 0) + // Minimum execution time: 12_609_000 picoseconds. + Weight::from_parts(13_142_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) + /// 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: 23_335_000 picoseconds. - Weight::from_parts(23_715_000, 0) + // Minimum execution time: 17_533_000 picoseconds. + Weight::from_parts(18_061_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) + /// 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: 58_776_000 picoseconds. - Weight::from_parts(59_353_000, 0) + // Minimum execution time: 45_278_000 picoseconds. + Weight::from_parts(46_670_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) + /// 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: 52_826_000 picoseconds. - Weight::from_parts(53_816_000, 0) + // Minimum execution time: 43_125_000 picoseconds. + Weight::from_parts(43_925_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) + /// 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: 19_400_000 picoseconds. - Weight::from_parts(19_746_000, 0) + // Minimum execution time: 15_580_000 picoseconds. + Weight::from_parts(16_023_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) + /// Storage: `System::Account` (r:999 w:999) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `u` is `[1, 1000]`. fn upgrade_accounts(u: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + u * (135 ±0)` + // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 18_465_000 picoseconds. - Weight::from_parts(18_670_000, 0) + // Minimum execution time: 14_868_000 picoseconds. + Weight::from_parts(15_130_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 22_827 - .saturating_add(Weight::from_parts(17_357_501, 0).saturating_mul(u.into())) + // Standard Error: 10_719 + .saturating_add(Weight::from_parts(13_394_926, 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())) } + fn force_adjust_total_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_174_000 picoseconds. + Weight::from_parts(5_457_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_identity.rs b/polkadot/runtime/westend/src/weights/pallet_identity.rs index 8c11482ebdc13693d59cd299189b2b18753aeb06..dc7061615c952ad551c602512329b7017568b29e 100644 --- a/polkadot/runtime/westend/src/weights/pallet_identity.rs +++ b/polkadot/runtime/westend/src/weights/pallet_identity.rs @@ -68,8 +68,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[0, 100]`. - fn set_identity(r: u32, x: u32, ) -> Weight { + fn set_identity(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `442 + r * (5 ±0)` // Estimated: `11003` @@ -78,8 +77,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 7_269 .saturating_add(Weight::from_parts(250_439, 0).saturating_mul(r.into())) - // Standard Error: 1_418 - .saturating_add(Weight::from_parts(483_981, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -133,8 +130,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. - /// The range of component `x` is `[0, 100]`. - fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { + 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` @@ -145,8 +141,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(475_120, 0).saturating_mul(r.into())) // Standard Error: 4_092 .saturating_add(Weight::from_parts(1_348_869, 0).saturating_mul(s.into())) - // Standard Error: 4_092 - .saturating_add(Weight::from_parts(314_033, 0).saturating_mul(x.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()))) @@ -156,8 +150,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. - /// The range of component `x` is `[0, 100]`. - fn request_judgement(r: u32, x: u32, ) -> Weight { + fn request_judgement(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `367 + r * (57 ±0) + x * (66 ±0)` // Estimated: `11003` @@ -166,16 +159,13 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 7_973 .saturating_add(Weight::from_parts(124_283, 0).saturating_mul(r.into())) - // Standard Error: 1_555 - .saturating_add(Weight::from_parts(512_825, 0).saturating_mul(x.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]`. - /// The range of component `x` is `[0, 100]`. - fn cancel_request(r: u32, x: u32, ) -> Weight { + fn cancel_request(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `398 + x * (66 ±0)` // Estimated: `11003` @@ -184,8 +174,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 5_154 .saturating_add(Weight::from_parts(147_560, 0).saturating_mul(r.into())) - // Standard Error: 1_005 - .saturating_add(Weight::from_parts(490_754, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -239,8 +227,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Storage: Identity IdentityOf (r:1 w:1) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. - /// The range of component `x` is `[0, 100]`. - fn provide_judgement(r: u32, x: u32, ) -> Weight { + fn provide_judgement(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `445 + r * (57 ±0) + x * (66 ±0)` // Estimated: `11003` @@ -249,8 +236,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(0, 11003)) // Standard Error: 10_027 .saturating_add(Weight::from_parts(154_816, 0).saturating_mul(r.into())) - // Standard Error: 1_855 - .saturating_add(Weight::from_parts(803_084, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -264,8 +249,7 @@ impl pallet_identity::WeightInfo for WeightInfo { /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. - /// The range of component `x` is `[0, 100]`. - fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + 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` @@ -276,8 +260,6 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(Weight::from_parts(666_376, 0).saturating_mul(r.into())) // Standard Error: 4_433 .saturating_add(Weight::from_parts(1_396_065, 0).saturating_mul(s.into())) - // Standard Error: 4_433 - .saturating_add(Weight::from_parts(300_762, 0).saturating_mul(x.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()))) @@ -356,4 +338,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn add_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_873_000 picoseconds. + Weight::from_parts(13_873_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:0 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn remove_username_authority() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_653_000 picoseconds. + Weight::from_parts(10_653_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::UsernameAuthorities` (r:1 w:1) + /// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_username_for() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `11037` + // Minimum execution time: 75_928_000 picoseconds. + Weight::from_parts(75_928_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + /// Storage: `Identity::AccountOfUsername` (r:0 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn accept_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `11037` + // Minimum execution time: 38_157_000 picoseconds. + Weight::from_parts(38_157_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Identity::PendingUsernames` (r:1 w:1) + /// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`) + fn remove_expired_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3542` + // Minimum execution time: 46_821_000 picoseconds. + Weight::from_parts(46_821_000, 0) + .saturating_add(Weight::from_parts(0, 3542)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:0) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn set_primary_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `247` + // Estimated: `11037` + // Minimum execution time: 22_515_000 picoseconds. + Weight::from_parts(22_515_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Identity::AccountOfUsername` (r:1 w:1) + /// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Identity::IdentityOf` (r:1 w:0) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`) + fn remove_dangling_username() -> Weight { + // Proof Size summary in bytes: + // Measured: `126` + // Estimated: `11037` + // Minimum execution time: 15_997_000 picoseconds. + Weight::from_parts(15_997_000, 0) + .saturating_add(Weight::from_parts(0, 11037)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_im_online.rs b/polkadot/runtime/westend/src/weights/pallet_im_online.rs deleted file mode 100644 index a83cd43804dfdc1ca8827a1988221dd0c6f4ee4d..0000000000000000000000000000000000000000 --- a/polkadot/runtime/westend/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=westend-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `361 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 122_571_000 picoseconds. - Weight::from_parts(162_954_849, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 8_676 - .saturating_add(Weight::from_parts(11_122, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} diff --git a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs index 49bc687a3e4fa2afba34567769bb3b68c2d55d89..6aa5ddd1ec8fb5251ac6bbd5059938e4bad93b22 100644 --- a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs +++ b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_nomination_pools` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-nbnwcyh-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -53,7 +53,7 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -78,20 +78,22 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn join() -> Weight { // Proof Size summary in bytes: - // Measured: `3318` + // Measured: `3355` // Estimated: `8877` - // Minimum execution time: 187_795_000 picoseconds. - Weight::from_parts(193_857_000, 0) + // Minimum execution time: 173_707_000 picoseconds. + Weight::from_parts(179_920_000, 0) .saturating_add(Weight::from_parts(0, 8877)) - .saturating_add(T::DbWeight::get().reads(19)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().reads(20)) + .saturating_add(T::DbWeight::get().writes(13)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) @@ -110,22 +112,24 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `3328` + // Measured: `3365` // Estimated: `8877` - // Minimum execution time: 186_245_000 picoseconds. - Weight::from_parts(190_916_000, 0) + // Minimum execution time: 174_414_000 picoseconds. + Weight::from_parts(178_068_000, 0) .saturating_add(Weight::from_parts(0, 8877)) - .saturating_add(T::DbWeight::get().reads(16)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().reads(17)) + .saturating_add(T::DbWeight::get().writes(13)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) /// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) @@ -144,22 +148,24 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_other() -> Weight { // Proof Size summary in bytes: - // Measured: `3274` + // Measured: `3312` // Estimated: `8799` - // Minimum execution time: 217_918_000 picoseconds. - Weight::from_parts(224_772_000, 0) + // Minimum execution time: 198_864_000 picoseconds. + Weight::from_parts(203_783_000, 0) .saturating_add(Weight::from_parts(0, 8799)) - .saturating_add(T::DbWeight::get().reads(16)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().reads(17)) + .saturating_add(T::DbWeight::get().writes(13)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) /// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) @@ -168,10 +174,10 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn claim_payout() -> Weight { // Proof Size summary in bytes: - // Measured: `1137` + // Measured: `1138` // Estimated: `4182` - // Minimum execution time: 76_958_000 picoseconds. - Weight::from_parts(78_278_000, 0) + // Minimum execution time: 70_250_000 picoseconds. + Weight::from_parts(72_231_000, 0) .saturating_add(Weight::from_parts(0, 4182)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -179,7 +185,7 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) @@ -210,16 +216,16 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `3597` + // Measured: `3545` // Estimated: `8877` - // Minimum execution time: 170_992_000 picoseconds. - Weight::from_parts(179_987_000, 0) + // Minimum execution time: 155_853_000 picoseconds. + Weight::from_parts(161_032_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(20)) .saturating_add(T::DbWeight::get().writes(13)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -230,25 +236,27 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1670` + // Measured: `1744` // Estimated: `4764` - // Minimum execution time: 60_740_000 picoseconds. - Weight::from_parts(64_502_831, 0) + // Minimum execution time: 62_933_000 picoseconds. + Weight::from_parts(65_847_171, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 2_724 - .saturating_add(Weight::from_parts(37_725, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Standard Error: 1_476 + .saturating_add(Weight::from_parts(59_648, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1) /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) @@ -261,6 +269,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ClaimPermissions` (r:0 w:1) @@ -268,22 +278,22 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2098` + // Measured: `2134` // Estimated: `4764` - // Minimum execution time: 127_322_000 picoseconds. - Weight::from_parts(132_064_603, 0) + // Minimum execution time: 123_641_000 picoseconds. + Weight::from_parts(127_222_589, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 3_424 - .saturating_add(Weight::from_parts(64_590, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().writes(8)) + // Standard Error: 2_493 + .saturating_add(Weight::from_parts(83_361, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(9)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1) /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -292,16 +302,18 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:0) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::Validators` (r:1 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `Staking::Nominators` (r:1 w:0) - /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:1) @@ -323,17 +335,15 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Storage: `NominationPools::ClaimPermissions` (r:0 w:1) /// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(s: u32, ) -> Weight { + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2454` + // Measured: `2453` // Estimated: `8538` - // Minimum execution time: 236_510_000 picoseconds. - Weight::from_parts(243_943_334, 0) + // Minimum execution time: 219_469_000 picoseconds. + Weight::from_parts(227_526_000, 0) .saturating_add(Weight::from_parts(0, 8538)) - // Standard Error: 4_864 - .saturating_add(Weight::from_parts(14_974, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(23)) - .saturating_add(T::DbWeight::get().writes(19)) + .saturating_add(T::DbWeight::get().reads(24)) + .saturating_add(T::DbWeight::get().writes(20)) } /// Storage: `NominationPools::LastPoolId` (r:1 w:1) /// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -359,14 +369,12 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Staking::Ledger` (r:1 w:1) - /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::CurrentEra` (r:1 w:0) - /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) + /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForRewardPools` (r:1 w:1) @@ -376,21 +384,23 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Storage: `NominationPools::CounterForReversePoolIdLookup` (r:1 w:1) /// Proof: `NominationPools::CounterForReversePoolIdLookup` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:0 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `1222` + // Measured: `1102` // Estimated: `8538` - // Minimum execution time: 197_883_000 picoseconds. - Weight::from_parts(201_750_000, 0) + // Minimum execution time: 166_466_000 picoseconds. + Weight::from_parts(171_425_000, 0) .saturating_add(Weight::from_parts(0, 8538)) - .saturating_add(T::DbWeight::get().reads(24)) - .saturating_add(T::DbWeight::get().writes(16)) + .saturating_add(T::DbWeight::get().reads(23)) + .saturating_add(T::DbWeight::get().writes(17)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) @@ -416,36 +426,36 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1779` + // Measured: `1738` // Estimated: `4556 + n * (2520 ±0)` - // Minimum execution time: 65_505_000 picoseconds. - Weight::from_parts(67_148_657, 0) + // Minimum execution time: 59_650_000 picoseconds. + Weight::from_parts(60_620_077, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 9_115 - .saturating_add(Weight::from_parts(1_421_198, 0).saturating_mul(n.into())) + // Standard Error: 7_316 + .saturating_add(Weight::from_parts(1_467_406, 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(5)) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_state() -> Weight { // Proof Size summary in bytes: - // Measured: `1367` + // Measured: `1363` // Estimated: `4556` - // Minimum execution time: 34_157_000 picoseconds. - Weight::from_parts(35_557_000, 0) + // Minimum execution time: 31_170_000 picoseconds. + Weight::from_parts(32_217_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::Metadata` (r:1 w:1) /// Proof: `NominationPools::Metadata` (`max_values`: None, `max_size`: Some(270), added: 2745, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForMetadata` (r:1 w:1) @@ -453,13 +463,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `497` + // Measured: `498` // Estimated: `3735` - // Minimum execution time: 13_806_000 picoseconds. - Weight::from_parts(14_540_018, 0) + // Minimum execution time: 12_603_000 picoseconds. + Weight::from_parts(13_241_702, 0) .saturating_add(Weight::from_parts(0, 3735)) - // Standard Error: 123 - .saturating_add(Weight::from_parts(644, 0).saturating_mul(n.into())) + // Standard Error: 116 + .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -479,25 +489,25 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_870_000 picoseconds. - Weight::from_parts(6_253_000, 0) + // Minimum execution time: 3_608_000 picoseconds. + Weight::from_parts(3_801_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) fn update_roles() -> Weight { // Proof Size summary in bytes: - // Measured: `497` - // Estimated: `3685` - // Minimum execution time: 18_290_000 picoseconds. - Weight::from_parts(18_961_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `498` + // Estimated: `3719` + // Minimum execution time: 16_053_000 picoseconds. + Weight::from_parts(16_473_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) @@ -516,16 +526,16 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1942` + // Measured: `1901` // Estimated: `4556` - // Minimum execution time: 63_708_000 picoseconds. - Weight::from_parts(65_570_000, 0) + // Minimum execution time: 57_251_000 picoseconds. + Weight::from_parts(59_390_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) @@ -534,37 +544,49 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn set_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `736` - // Estimated: `3685` - // Minimum execution time: 34_291_000 picoseconds. - Weight::from_parts(34_767_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `770` + // Estimated: `3719` + // Minimum execution time: 29_888_000 picoseconds. + Weight::from_parts(31_056_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) /// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_commission_max() -> Weight { // Proof Size summary in bytes: - // Measured: `537` - // Estimated: `3685` - // Minimum execution time: 18_406_000 picoseconds. - Weight::from_parts(18_999_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `538` + // Estimated: `3719` + // Minimum execution time: 15_769_000 picoseconds. + Weight::from_parts(16_579_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) fn set_commission_change_rate() -> Weight { // Proof Size summary in bytes: - // Measured: `497` - // Estimated: `3685` - // Minimum execution time: 18_440_000 picoseconds. - Weight::from_parts(19_230_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `498` + // Estimated: `3719` + // Minimum execution time: 15_385_000 picoseconds. + Weight::from_parts(16_402_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `NominationPools::BondedPools` (r:1 w:1) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + fn set_commission_claim_permission() -> Weight { + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `3719` + // Minimum execution time: 14_965_000 picoseconds. + Weight::from_parts(15_548_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -576,14 +598,14 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `508` // Estimated: `4182` - // Minimum execution time: 14_310_000 picoseconds. - Weight::from_parts(14_681_000, 0) + // Minimum execution time: 13_549_000 picoseconds. + Weight::from_parts(14_307_000, 0) .saturating_add(Weight::from_parts(0, 4182)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) @@ -592,16 +614,16 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn claim_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `934` - // Estimated: `3685` - // Minimum execution time: 64_526_000 picoseconds. - Weight::from_parts(66_800_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `968` + // Estimated: `3719` + // Minimum execution time: 60_153_000 picoseconds. + Weight::from_parts(61_369_000, 0) + .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) - /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(220), added: 2695, mode: `MaxEncodedLen`) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:1) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -610,10 +632,10 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) fn adjust_pool_deposit() -> Weight { // Proof Size summary in bytes: - // Measured: `866` + // Measured: `867` // Estimated: `4764` - // Minimum execution time: 73_472_000 picoseconds. - Weight::from_parts(74_698_000, 0) + // Minimum execution time: 64_985_000 picoseconds. + Weight::from_parts(66_616_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs index 7291b98093300ef887d422051dcf6be7274c0d12..beef3796dea6e5ab3a47a62238422df652f30797 100644 --- a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `69` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_160_000, 0) + // Minimum execution time: 3_220_000 picoseconds. + Weight::from_parts(3_512_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 3_647_000 picoseconds. - Weight::from_parts(6_608_270, 0) + // Minimum execution time: 3_565_000 picoseconds. + Weight::from_parts(6_102_216, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_516 - .saturating_add(Weight::from_parts(892_866, 0).saturating_mul(s.into())) + // Standard Error: 1_413 + .saturating_add(Weight::from_parts(339_016, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_552_000 picoseconds. - Weight::from_parts(5_836_000, 0) + // Minimum execution time: 2_940_000 picoseconds. + Weight::from_parts(3_070_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `217 + s * (1 ±0)` // Estimated: `3682 + s * (1 ±0)` - // Minimum execution time: 20_583_000 picoseconds. - Weight::from_parts(20_771_000, 0) + // Minimum execution time: 16_602_000 picoseconds. + Weight::from_parts(16_834_000, 0) .saturating_add(Weight::from_parts(0, 3682)) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_250, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_307, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_271_000 picoseconds. - Weight::from_parts(7_447_000, 0) + // Minimum execution time: 4_202_000 picoseconds. + Weight::from_parts(4_383_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,90 +118,169 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_547_000 picoseconds. - Weight::from_parts(5_776_000, 0) + // Minimum execution time: 2_917_000 picoseconds. + Weight::from_parts(3_043_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_480_000 picoseconds. - Weight::from_parts(2_628_000, 0) + // Minimum execution time: 1_707_000 picoseconds. + Weight::from_parts(1_802_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_479_000 picoseconds. - Weight::from_parts(2_626_000, 0) + // Minimum execution time: 1_671_000 picoseconds. + Weight::from_parts(1_796_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 13_350_000 picoseconds. - Weight::from_parts(15_289_847, 0) + // Minimum execution time: 9_313_000 picoseconds. + Weight::from_parts(12_146_613, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_375 - .saturating_add(Weight::from_parts(974_567, 0).saturating_mul(s.into())) + // Standard Error: 1_381 + .saturating_add(Weight::from_parts(360_418, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 17_646_000 picoseconds. - Weight::from_parts(15_858_434, 0) + // Minimum execution time: 13_079_000 picoseconds. + Weight::from_parts(12_921_017, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_354 - .saturating_add(Weight::from_parts(1_697_642, 0).saturating_mul(s.into())) + // Standard Error: 1_112 + .saturating_add(Weight::from_parts(538_089, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `293 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 16_419_000 picoseconds. - Weight::from_parts(19_868_760, 0) + // Minimum execution time: 12_458_000 picoseconds. + Weight::from_parts(16_009_539, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_915 - .saturating_add(Weight::from_parts(1_010_225, 0).saturating_mul(s.into())) + // Standard Error: 2_260 + .saturating_add(Weight::from_parts(399_245, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `319 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 19_574_000 picoseconds. - Weight::from_parts(18_453_197, 0) + // Minimum execution time: 15_173_000 picoseconds. + Weight::from_parts(15_602_728, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_009 - .saturating_add(Weight::from_parts(1_707_130, 0).saturating_mul(s.into())) + // Standard Error: 1_302 + .saturating_add(Weight::from_parts(557_878, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `197` + // Estimated: `42428` + // Minimum execution time: 13_531_000 picoseconds. + Weight::from_parts(13_985_249, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 619 + .saturating_add(Weight::from_parts(39_068, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 099693d26b505817a390ddb7757a7ea38b75f812..7a641e36a126bada81cecceadd9c22f57e5b88e9 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `pallet_staking` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_staking // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_staking +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,746 +48,755 @@ use core::marker::PhantomData; /// Weight functions for `pallet_staking`. pub struct WeightInfo(PhantomData); impl pallet_staking::WeightInfo for WeightInfo { - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: `Staking::Bonded` (r:1 w:1) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:0 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `894` // Estimated: `4764` - // Minimum execution time: 51_108_000 picoseconds. - Weight::from_parts(52_521_000, 0) + // Minimum execution time: 37_340_000 picoseconds. + Weight::from_parts(38_930_000, 0) .saturating_add(Weight::from_parts(0, 4764)) - .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:3 w:3) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:2 w:2) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1959` + // Measured: `1921` // Estimated: `8877` - // Minimum execution time: 96_564_000 picoseconds. - Weight::from_parts(100_133_000, 0) + // Minimum execution time: 80_630_000 picoseconds. + Weight::from_parts(82_196_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:1 w:0) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:3 w:3) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:2 w:2) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2166` + // Measured: `2128` // Estimated: `8877` - // Minimum execution time: 97_705_000 picoseconds. - Weight::from_parts(102_055_000, 0) + // Minimum execution time: 83_523_000 picoseconds. + Weight::from_parts(86_639_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `981` + // Measured: `1075` // Estimated: `4764` - // Minimum execution time: 45_257_000 picoseconds. - Weight::from_parts(47_309_508, 0) + // Minimum execution time: 38_636_000 picoseconds. + Weight::from_parts(40_399_283, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 2_343 - .saturating_add(Weight::from_parts(61_484, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Standard Error: 869 + .saturating_add(Weight::from_parts(37_752, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::SlashingSpans` (r:1 w:1) + /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::Bonded` (r:1 w:1) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:1) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::SpanSlash` (r:0 w:100) + /// Proof: `Staking::SpanSlash` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2221 + s * (4 ±0)` + // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 94_800_000 picoseconds. - Weight::from_parts(101_763_223, 0) + // Minimum execution time: 81_301_000 picoseconds. + Weight::from_parts(88_609_205, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 6_481 - .saturating_add(Weight::from_parts(1_450_372, 0).saturating_mul(s.into())) + // Standard Error: 3_388 + .saturating_add(Weight::from_parts(1_253_692, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:1 w:0) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:1 w:0) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:1 w:1) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:1) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinValidatorBond` (r:1 w:0) + /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinCommission` (r:1 w:0) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:1) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxValidatorsCount` (r:1 w:0) + /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:1 w:1) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForValidators` (r:1 w:1) + /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1343` + // Measured: `1301` // Estimated: `4556` - // Minimum execution time: 57_763_000 picoseconds. - Weight::from_parts(59_394_000, 0) + // Minimum execution time: 47_292_000 picoseconds. + Weight::from_parts(48_566_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:128 w:128) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:128 w:128) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1248 + k * (569 ±0)` + // Measured: `1243 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 35_501_000 picoseconds. - Weight::from_parts(32_260_525, 0) + // Minimum execution time: 28_840_000 picoseconds. + Weight::from_parts(27_510_817, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 34_554 - .saturating_add(Weight::from_parts(10_625_386, 0).saturating_mul(k.into())) + // Standard Error: 6_603 + .saturating_add(Weight::from_parts(6_268_853, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:17 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:1 w:0) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:1) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxNominatorsCount` (r:1 w:0) + /// Proof: `Staking::MaxNominatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:17 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1839 + n * (102 ±0)` + // Measured: `1797 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 67_970_000 picoseconds. - Weight::from_parts(65_110_939, 0) + // Minimum execution time: 57_537_000 picoseconds. + Weight::from_parts(55_854_233, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 32_193 - .saturating_add(Weight::from_parts(4_688_614, 0).saturating_mul(n.into())) + // Standard Error: 14_427 + .saturating_add(Weight::from_parts(3_844_957, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:1) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1675` + // Measured: `1581` // Estimated: `6248` - // Minimum execution time: 59_515_000 picoseconds. - Weight::from_parts(62_462_000, 0) + // Minimum execution time: 49_997_000 picoseconds. + Weight::from_parts(51_266_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `771` + // Measured: `865` // Estimated: `4556` - // Minimum execution time: 13_943_000 picoseconds. - Weight::from_parts(14_384_000, 0) + // Minimum execution time: 15_342_000 picoseconds. + Weight::from_parts(15_970_000, 0) .saturating_add(Weight::from_parts(0, 4556)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2 w:2) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - fn set_controller() -> Weight { + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:1 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `870` - // Estimated: `8122` - // Minimum execution time: 21_212_000 picoseconds. - Weight::from_parts(22_061_000, 0) - .saturating_add(Weight::from_parts(0, 8122)) + // Measured: `932` + // Estimated: `4556` + // Minimum execution time: 20_719_000 picoseconds. + Weight::from_parts(21_373_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Staking::Bonded` (r:1 w:1) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:2) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn set_controller() -> Weight { + // Proof Size summary in bytes: + // Measured: `865` + // Estimated: `4556` + // Minimum execution time: 18_237_000 picoseconds. + Weight::from_parts(18_896_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: Staking ValidatorCount (r:0 w:1) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Staking::ValidatorCount` (r:0 w:1) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_validator_count() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_977_000 picoseconds. - Weight::from_parts(3_217_000, 0) + // Minimum execution time: 1_946_000 picoseconds. + Weight::from_parts(2_131_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: `Staking::ForceEra` (r:0 w:1) + /// Proof: `Staking::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) fn force_no_eras() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_152_000 picoseconds. - Weight::from_parts(9_949_000, 0) + // Minimum execution time: 6_840_000 picoseconds. + Weight::from_parts(7_208_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: `Staking::ForceEra` (r:0 w:1) + /// Proof: `Staking::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) fn force_new_era() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_509_000 picoseconds. - Weight::from_parts(9_838_000, 0) + // Minimum execution time: 6_812_000 picoseconds. + Weight::from_parts(7_254_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking ForceEra (r:0 w:1) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: `Staking::ForceEra` (r:0 w:1) + /// Proof: `Staking::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) fn force_new_era_always() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_480_000 picoseconds. - Weight::from_parts(9_755_000, 0) + // Minimum execution time: 6_787_000 picoseconds. + Weight::from_parts(7_206_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking Invulnerables (r:0 w:1) - /// Proof Skipped: Staking Invulnerables (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `Staking::Invulnerables` (r:0 w:1) + /// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_140_000 picoseconds. - Weight::from_parts(3_438_665, 0) + // Minimum execution time: 2_045_000 picoseconds. + Weight::from_parts(2_281_841, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 93 - .saturating_add(Weight::from_parts(15_688, 0).saturating_mul(v.into())) + // Standard Error: 70 + .saturating_add(Weight::from_parts(11_592, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:0 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) + /// 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_657_000 picoseconds. + Weight::from_parts(1_702_000, 0) + .saturating_add(Weight::from_parts(0, 990)) + // Standard Error: 20_041 + .saturating_add(Weight::from_parts(13_165_254, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(i.into()))) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) + } + /// 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) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:1) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::SpanSlash` (r:0 w:100) + /// Proof: `Staking::SpanSlash` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1947 + s * (4 ±0)` + // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 86_729_000 picoseconds. - Weight::from_parts(93_633_668, 0) + // Minimum execution time: 78_774_000 picoseconds. + Weight::from_parts(85_770_713, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 6_522 - .saturating_add(Weight::from_parts(1_421_038, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(12)) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_244_494, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - /// Storage: Staking UnappliedSlashes (r:1 w:1) - /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) + /// Storage: `Staking::UnappliedSlashes` (r:1 w:1) + /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66606` - // Estimated: `70071` - // Minimum execution time: 135_155_000 picoseconds. - Weight::from_parts(960_317_735, 0) - .saturating_add(Weight::from_parts(0, 70071)) - // Standard Error: 59_264 - .saturating_add(Weight::from_parts(4_884_888, 0).saturating_mul(s.into())) + // Measured: `66639` + // Estimated: `70104` + // Minimum execution time: 129_905_000 picoseconds. + Weight::from_parts(932_195_554, 0) + .saturating_add(Weight::from_parts(0, 70104)) + // Standard Error: 57_492 + .saturating_add(Weight::from_parts(4_826_754, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:65 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:65 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:65 w:65) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `n` is `[0, 64]`. - fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `5773 + n * (151 ±0)` - // Estimated: `8579 + n * (2603 ±0)` - // Minimum execution time: 92_788_000 picoseconds. - Weight::from_parts(129_527_249, 0) - .saturating_add(Weight::from_parts(0, 8579)) - // Standard Error: 73_346 - .saturating_add(Weight::from_parts(33_413_624, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) - } - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasValidatorReward (r:1 w:0) - /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:65 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:65 w:65) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:1 w:0) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasRewardPoints (r:1 w:0) - /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:1 w:0) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:65 w:0) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: System Account (r:65 w:65) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:65 w:65) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:65 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: `Staking::Bonded` (r:65 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:65 w:65) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersClipped` (r:1 w:0) + /// Proof: `Staking::ErasStakersClipped` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) + /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Storage: `Staking::ClaimedRewards` (r:1 w:1) + /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) + /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:65 w:65) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:65 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:65 w:65) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) + /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) + /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ErasValidatorPrefs` (r:1 w:0) + /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:65 w:0) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 64]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `8056 + n * (396 ±0)` - // Estimated: `10634 + n * (3774 ±0)` - // Minimum execution time: 118_795_000 picoseconds. - Weight::from_parts(181_663_036, 0) - .saturating_add(Weight::from_parts(0, 10634)) - // Standard Error: 132_894 - .saturating_add(Weight::from_parts(51_369_596, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(11)) + // Measured: `8249 + n * (396 ±0)` + // Estimated: `10779 + n * (3774 ±0)` + // Minimum execution time: 127_094_000 picoseconds. + Weight::from_parts(160_088_053, 0) + .saturating_add(Weight::from_parts(0, 10779)) + // Standard Error: 32_978 + .saturating_add(Weight::from_parts(39_845_710, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) } - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:3 w:3) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:1 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:2 w:2) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:3 w:3) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:2 w:2) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1960 + l * (5 ±0)` + // Measured: `1922 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 88_870_000 picoseconds. - Weight::from_parts(92_783_195, 0) + // Minimum execution time: 75_672_000 picoseconds. + Weight::from_parts(78_708_335, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 7_412 - .saturating_add(Weight::from_parts(49_785, 0).saturating_mul(l.into())) + // Standard Error: 3_387 + .saturating_add(Weight::from_parts(37_084, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: Staking Bonded (r:1 w:1) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:1 w:1) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking SlashingSpans (r:1 w:1) - /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Balances Locks (r:1 w:1) - /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) - /// Storage: Balances Freezes (r:1 w:0) - /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: Staking Payee (r:0 w:1) - /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) - /// Storage: Staking SpanSlash (r:0 w:100) - /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) + /// Storage: `Staking::Bonded` (r:1 w:1) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:1) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::SlashingSpans` (r:1 w:1) + /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:1) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::SpanSlash` (r:0 w:100) + /// Proof: `Staking::SpanSlash` (`max_values`: None, `max_size`: Some(76), added: 2551, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2221 + s * (4 ±0)` + // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 102_112_000 picoseconds. - Weight::from_parts(103_755_459, 0) + // Minimum execution time: 87_991_000 picoseconds. + Weight::from_parts(90_272_005, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 6_107 - .saturating_add(Weight::from_parts(1_436_139, 0).saturating_mul(s.into())) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_232_322, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:178 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:110 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:110 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:11 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:110 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:110 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ValidatorCount (r:1 w:0) - /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinimumValidatorCount (r:1 w:0) - /// Proof: Staking MinimumValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CurrentEra (r:1 w:1) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasStakersClipped (r:0 w:10) - /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasValidatorPrefs (r:0 w:10) - /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) - /// Storage: Staking ErasStakers (r:0 w:10) - /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) - /// Storage: Staking ErasTotalStake (r:0 w:1) - /// Proof: Staking ErasTotalStake (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) - /// Storage: Staking ErasStartSessionIndex (r:0 w:1) - /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:178 w:0) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:110 w:0) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:110 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:110 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:110 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:11 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForValidators` (r:1 w:0) + /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ValidatorCount` (r:1 w:0) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinimumValidatorCount` (r:1 w:0) + /// Proof: `Staking::MinimumValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentEra` (r:1 w:1) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasValidatorPrefs` (r:0 w:10) + /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStakersPaged` (r:0 w:20) + /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ErasStakersOverview` (r:0 w:10) + /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasTotalStake` (r:0 w:1) + /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStartSessionIndex` (r:0 w:1) + /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) + /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 547_465_000 picoseconds. - Weight::from_parts(557_541_000, 0) + // Minimum execution time: 528_862_000 picoseconds. + Weight::from_parts(534_620_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 2_380_806 - .saturating_add(Weight::from_parts(78_379_807, 0).saturating_mul(v.into())) - // Standard Error: 237_234 - .saturating_add(Weight::from_parts(22_772_283, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(185)) + // Standard Error: 2_005_553 + .saturating_add(Weight::from_parts(65_586_008, 0).saturating_mul(v.into())) + // Standard Error: 199_842 + .saturating_add(Weight::from_parts(18_155_389, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(184)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes(8)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - /// Storage: VoterList CounterForListNodes (r:1 w:0) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:178 w:0) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2000 w:0) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:2000 w:0) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1000 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: Staking Bonded (r:2000 w:0) - /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) - /// Storage: Staking Ledger (r:2000 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: Staking MinimumActiveStake (r:0 w:1) - /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:178 w:0) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2000 w:0) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:2000 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:2000 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:2000 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1000 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) + /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3151 + n * (907 ±0) + v * (391 ±0)` + // Measured: `3108 + n * (907 ±0) + v * (391 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 39_710_080_000 picoseconds. - Weight::from_parts(42_191_823_000, 0) + // Minimum execution time: 33_532_110_000 picoseconds. + Weight::from_parts(33_926_321_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 506_609 - .saturating_add(Weight::from_parts(7_688_462, 0).saturating_mul(v.into())) - // Standard Error: 506_609 - .saturating_add(Weight::from_parts(6_303_908, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(180)) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_627_629, 0).saturating_mul(v.into())) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_068_168, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(179)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - /// Storage: Staking CounterForValidators (r:1 w:0) - /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1001 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) + /// Storage: `Staking::CounterForValidators` (r:1 w:0) + /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1001 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `951 + v * (50 ±0)` + // Measured: `946 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_603_304_000 picoseconds. - Weight::from_parts(481_860_383, 0) + // Minimum execution time: 2_395_956_000 picoseconds. + Weight::from_parts(88_416_870, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 55_189 - .saturating_add(Weight::from_parts(4_786_173, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(3)) + // Standard Error: 8_731 + .saturating_add(Weight::from_parts(4_750_956, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) - .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: `Staking::MinCommission` (r:0 w:1) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinValidatorBond` (r:0 w:1) + /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) + /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `Staking::ChillThreshold` (r:0 w:1) + /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) + /// Proof: `Staking::MaxNominatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:0 w:1) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn set_staking_configs_all_set() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_453_000 picoseconds. - Weight::from_parts(6_857_000, 0) + // Minimum execution time: 3_761_000 picoseconds. + Weight::from_parts(4_013_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinValidatorBond (r:0 w:1) - /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking MaxValidatorsCount (r:0 w:1) - /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:0 w:1) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:0 w:1) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:0 w:1) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: `Staking::MinCommission` (r:0 w:1) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinValidatorBond` (r:0 w:1) + /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) + /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `Staking::ChillThreshold` (r:0 w:1) + /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) + /// Proof: `Staking::MaxNominatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:0 w:1) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn set_staking_configs_all_remove() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_037_000 picoseconds. - Weight::from_parts(6_303_000, 0) + // Minimum execution time: 3_325_000 picoseconds. + Weight::from_parts(3_519_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: Staking Ledger (r:1 w:0) - /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) - /// Storage: Staking Nominators (r:1 w:1) - /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) - /// Storage: Staking ChillThreshold (r:1 w:0) - /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: Staking MaxNominatorsCount (r:1 w:0) - /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CounterForNominators (r:1 w:1) - /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking MinNominatorBond (r:1 w:0) - /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:0) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) - /// Storage: VoterList ListNodes (r:2 w:2) - /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) - /// Storage: VoterList ListBags (r:1 w:1) - /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) - /// Storage: VoterList CounterForListNodes (r:1 w:1) - /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// 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) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::ChillThreshold` (r:1 w:0) + /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxNominatorsCount` (r:1 w:0) + /// Proof: `Staking::MaxNominatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForNominators` (r:1 w:1) + /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:1 w:0) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:2 w:2) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:1 w:1) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::CounterForListNodes` (r:1 w:1) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1798` + // Measured: `1870` // Estimated: `6248` - // Minimum execution time: 72_578_000 picoseconds. - Weight::from_parts(74_232_000, 0) + // Minimum execution time: 63_583_000 picoseconds. + Weight::from_parts(65_917_000, 0) .saturating_add(Weight::from_parts(0, 6248)) - .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) } - /// Storage: Staking MinCommission (r:1 w:0) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking Validators (r:1 w:1) - /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: `Staking::MinCommission` (r:1 w:0) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:1) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `661` + // Measured: `658` // Estimated: `3510` - // Minimum execution time: 13_066_000 picoseconds. - Weight::from_parts(13_421_000, 0) + // Minimum execution time: 10_975_000 picoseconds. + Weight::from_parts(11_328_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Staking MinCommission (r:0 w:1) - /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Staking::MinCommission` (r:0 w:1) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_min_commission() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_057_000 picoseconds. - Weight::from_parts(3_488_000, 0) + // Minimum execution time: 1_954_000 picoseconds. + Weight::from_parts(2_081_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/pallet_sudo.rs b/polkadot/runtime/westend/src/weights/pallet_sudo.rs index 8a220173ee57a45dd122c1aad020151c5bb5f1e6..e9ab3ad37a4cca6483dc7fdeb98562dd6f1937e6 100644 --- a/polkadot/runtime/westend/src/weights/pallet_sudo.rs +++ b/polkadot/runtime/westend/src/weights/pallet_sudo.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_sudo` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_sudo // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_sudo +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,38 +48,50 @@ use core::marker::PhantomData; /// Weight functions for `pallet_sudo`. pub struct WeightInfo(PhantomData); impl pallet_sudo::WeightInfo for WeightInfo { - /// Storage: Sudo Key (r:1 w:1) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:1) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn set_key() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 12_360_000 picoseconds. - Weight::from_parts(12_803_000, 0) + // Minimum execution time: 8_750_000 picoseconds. + Weight::from_parts(9_102_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Sudo Key (r:1 w:0) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:0) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn sudo() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 12_158_000 picoseconds. - Weight::from_parts(12_506_000, 0) + // Minimum execution time: 9_607_000 picoseconds. + Weight::from_parts(10_139_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) } - /// Storage: Sudo Key (r:1 w:0) - /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: `Sudo::Key` (r:1 w:0) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) fn sudo_as() -> Weight { // Proof Size summary in bytes: // Measured: `132` // Estimated: `1517` - // Minimum execution time: 12_286_000 picoseconds. - Weight::from_parts(12_664_000, 0) + // Minimum execution time: 9_886_000 picoseconds. + Weight::from_parts(10_175_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) } + /// Storage: `Sudo::Key` (r:1 w:1) + /// Proof: `Sudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + fn remove_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `132` + // Estimated: `1517` + // Minimum execution time: 7_843_000 picoseconds. + Weight::from_parts(8_152_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_vesting.rs b/polkadot/runtime/westend/src/weights/pallet_vesting.rs index 4ccd524ffc28de68a8a19f5e7e78d495f2fa00bd..dfd87d1c550eb7e75d77300df72160fb7cefb569 100644 --- a/polkadot/runtime/westend/src/weights/pallet_vesting.rs +++ b/polkadot/runtime/westend/src/weights/pallet_vesting.rs @@ -238,4 +238,28 @@ impl pallet_vesting::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } + + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `l` is `[0, 49]`. + /// The range of component `s` is `[2, 28]`. + fn force_remove_vesting_schedule(l: u32, s: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `555 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 41_497_000 picoseconds. + Weight::from_parts(38_763_834, 4764) + // Standard Error: 2_030 + .saturating_add(Weight::from_parts(99_580, 0).saturating_mul(l.into())) + // Standard Error: 3_750 + .saturating_add(Weight::from_parts(132_188, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index 7f2a1de44e93909a718c83e546024572ac7347dc..10725cecf24995e34b3254baa23535cb91dd9981 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -16,11 +16,11 @@ //! Autogenerated weights for `pallet_xcm` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot @@ -28,19 +28,14 @@ // pallet // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_xcm // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm // --chain=westend-dev -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,44 +48,86 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { - /// Storage: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Proof Skipped: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// 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`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `169` - // Estimated: `3634` - // Minimum execution time: 33_628_000 picoseconds. - Weight::from_parts(34_633_000, 0) - .saturating_add(Weight::from_parts(0, 3634)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 25_725_000 picoseconds. + Weight::from_parts(26_174_000, 0) + .saturating_add(Weight::from_parts(0, 3612)) + .saturating_add(T::DbWeight::get().reads(4)) + .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: `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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 21_535_000 picoseconds. - Weight::from_parts(21_936_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `250` + // Estimated: `6196` + // Minimum execution time: 113_140_000 picoseconds. + Weight::from_parts(116_204_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } + /// 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`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 21_576_000 picoseconds. - Weight::from_parts(21_942_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `302` + // Estimated: `6196` + // Minimum execution time: 108_571_000 picoseconds. + Weight::from_parts(110_650_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Benchmark Override (r:0 w:0) - /// Proof Skipped: Benchmark Override (max_values: None, 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: `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`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `250` + // Estimated: `6196` + // Minimum execution time: 111_836_000 picoseconds. + Weight::from_parts(114_435_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// 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` @@ -99,14 +136,14 @@ 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: XcmPallet SupportedVersion (r:0 w:1) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) + /// Proof: `XcmPallet::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_764_000 picoseconds. - Weight::from_parts(9_927_000, 0) + // Minimum execution time: 7_160_000 picoseconds. + Weight::from_parts(7_477_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -114,171 +151,201 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_824_000 picoseconds. - Weight::from_parts(2_935_000, 0) + // Minimum execution time: 1_934_000 picoseconds. + Weight::from_parts(2_053_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: XcmPallet VersionNotifiers (r:1 w:1) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet QueryCounter (r:1 w:1) - /// Proof Skipped: XcmPallet QueryCounter (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Proof Skipped: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet Queries (r:0 w:1) - /// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) + /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// 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: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `169` - // Estimated: `3634` - // Minimum execution time: 38_436_000 picoseconds. - Weight::from_parts(39_300_000, 0) - .saturating_add(Weight::from_parts(0, 3634)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(7)) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 31_123_000 picoseconds. + Weight::from_parts(31_798_000, 0) + .saturating_add(Weight::from_parts(0, 3612)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: XcmPallet VersionNotifiers (r:1 w:1) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Proof Skipped: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet Queries (r:0 w:1) - /// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `361` - // Estimated: `3826` - // Minimum execution time: 41_600_000 picoseconds. - Weight::from_parts(42_703_000, 0) - .saturating_add(Weight::from_parts(0, 3826)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(6)) + // Measured: `327` + // Estimated: `3792` + // Minimum execution time: 35_175_000 picoseconds. + Weight::from_parts(36_098_000, 0) + .saturating_add(Weight::from_parts(0, 3792)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: XcmPallet XcmExecutionSuspended (r:0 w:1) - /// Proof Skipped: XcmPallet XcmExecutionSuspended (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) + /// Proof: `XcmPallet::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_792_000 picoseconds. - Weight::from_parts(2_958_000, 0) + // Minimum execution time: 1_974_000 picoseconds. + Weight::from_parts(2_096_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: XcmPallet SupportedVersion (r:4 w:2) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `229` - // Estimated: `11119` - // Minimum execution time: 17_640_000 picoseconds. - Weight::from_parts(18_011_000, 0) - .saturating_add(Weight::from_parts(0, 11119)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `22` + // Estimated: `13387` + // Minimum execution time: 16_626_000 picoseconds. + Weight::from_parts(17_170_000, 0) + .saturating_add(Weight::from_parts(0, 13387)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifiers (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifiers (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `233` - // Estimated: `11123` - // Minimum execution time: 17_325_000 picoseconds. - Weight::from_parts(17_896_000, 0) - .saturating_add(Weight::from_parts(0, 11123)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `26` + // Estimated: `13391` + // Minimum execution time: 16_937_000 picoseconds. + Weight::from_parts(17_447_000, 0) + .saturating_add(Weight::from_parts(0, 13391)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifyTargets (r:5 w:0) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `13608` - // Minimum execution time: 19_295_000 picoseconds. - Weight::from_parts(19_840_000, 0) - .saturating_add(Weight::from_parts(0, 13608)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `40` + // Estimated: `15880` + // Minimum execution time: 19_157_000 picoseconds. + Weight::from_parts(19_659_000, 0) + .saturating_add(Weight::from_parts(0, 15880)) + .saturating_add(T::DbWeight::get().reads(6)) } - /// Storage: XcmPallet VersionNotifyTargets (r:2 w:1) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Proof Skipped: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `239` - // Estimated: `6179` - // Minimum execution time: 35_819_000 picoseconds. - Weight::from_parts(36_708_000, 0) - .saturating_add(Weight::from_parts(0, 6179)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `183` + // Estimated: `6123` + // Minimum execution time: 30_699_000 picoseconds. + Weight::from_parts(31_537_000, 0) + .saturating_add(Weight::from_parts(0, 6123)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: XcmPallet VersionNotifyTargets (r:3 w:0) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `272` - // Estimated: `8687` - // Minimum execution time: 9_572_000 picoseconds. - Weight::from_parts(9_907_000, 0) - .saturating_add(Weight::from_parts(0, 8687)) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `69` + // Estimated: `10959` + // Minimum execution time: 12_303_000 picoseconds. + Weight::from_parts(12_670_000, 0) + .saturating_add(Weight::from_parts(0, 10959)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: XcmPallet VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `240` - // Estimated: `11130` - // Minimum execution time: 17_376_000 picoseconds. - Weight::from_parts(17_870_000, 0) - .saturating_add(Weight::from_parts(0, 11130)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `33` + // Estimated: `13398` + // Minimum execution time: 17_129_000 picoseconds. + Weight::from_parts(17_668_000, 0) + .saturating_add(Weight::from_parts(0, 13398)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: XcmPallet VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: XcmPallet VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Proof Skipped: unknown `0x3a696e747261626c6f636b5f656e74726f7079` (r:1 w:1) - /// Storage: Dmp DeliveryFeeFactor (r:1 w:0) - /// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured) - /// Storage: XcmPallet SupportedVersion (r:1 w:0) - /// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueues (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured) - /// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1) - /// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// 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`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `243` - // Estimated: `11133` - // Minimum execution time: 43_468_000 picoseconds. - Weight::from_parts(44_327_000, 0) - .saturating_add(Weight::from_parts(0, 11133)) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(6)) + // Measured: `183` + // Estimated: `13548` + // Minimum execution time: 39_960_000 picoseconds. + Weight::from_parts(41_068_000, 0) + .saturating_add(Weight::from_parts(0, 13548)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) + /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::Queries` (r:0 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn new_query() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 2_333_000 picoseconds. + Weight::from_parts(2_504_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `XcmPallet::Queries` (r:1 w:1) + /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn take_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `7576` + // Estimated: `11041` + // Minimum execution time: 22_932_000 picoseconds. + Weight::from_parts(23_307_000, 0) + .saturating_add(Weight::from_parts(0, 11041)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) + /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn claim_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 34_558_000 picoseconds. + Weight::from_parts(35_299_000, 0) + .saturating_add(Weight::from_parts(0, 3488)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/westend/src/weights/runtime_common_identity_migrator.rs b/polkadot/runtime/westend/src/weights/runtime_common_identity_migrator.rs new file mode 100644 index 0000000000000000000000000000000000000000..cec357453b67be400c0191ac7d5c12e6961a4bee --- /dev/null +++ b/polkadot/runtime/westend/src/weights/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 `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=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 `runtime_common::identity_migrator`. +pub struct WeightInfo(PhantomData); +impl 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/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_configuration.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_configuration.rs index 585dc9058f21ec29b15040470d4a4c663c18d303..8fa3207c6446082a97877f488913d9c063114fca 100644 --- a/polkadot/runtime/westend/src/weights/runtime_parachains_configuration.rs +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_configuration.rs @@ -16,10 +16,10 @@ //! Autogenerated weights for `runtime_parachains::configuration` //! -//! 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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-fljshgub-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -31,11 +31,11 @@ // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=runtime_parachains::configuration // --chain=westend-dev -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,11 +56,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_block_number() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_616_000 picoseconds. - Weight::from_parts(9_961_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_775_000 picoseconds. + Weight::from_parts(8_036_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -72,11 +72,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_u32() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_587_000 picoseconds. - Weight::from_parts(9_964_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_708_000 picoseconds. + Weight::from_parts(7_971_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -88,11 +88,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_option_u32() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_650_000 picoseconds. - Weight::from_parts(9_960_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_746_000 picoseconds. + Weight::from_parts(8_028_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -114,11 +114,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_545_000 picoseconds. - Weight::from_parts(9_845_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_729_000 picoseconds. + Weight::from_parts(7_954_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -130,11 +130,11 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_executor_params() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 10_258_000 picoseconds. - Weight::from_parts(10_607_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 9_871_000 picoseconds. + Weight::from_parts(10_075_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -146,11 +146,43 @@ impl runtime_parachains::configuration::WeightInfo for /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_perbill() -> Weight { // Proof Size summary in bytes: - // Measured: `127` - // Estimated: `1612` - // Minimum execution time: 9_502_000 picoseconds. - Weight::from_parts(9_902_000, 0) - .saturating_add(Weight::from_parts(0, 1612)) + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_869_000 picoseconds. + Weight::from_parts(8_000_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_node_feature() -> Weight { + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 9_797_000 picoseconds. + Weight::from_parts(10_373_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_config_with_scheduler_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_718_000 picoseconds. + Weight::from_parts(7_984_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa65a2e9034a7c9f10a3d596db9cf8d167f561ac --- /dev/null +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs @@ -0,0 +1,84 @@ +// 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 32.0.0 +//! DATE: 2024-02-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-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_parachains::coretime +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `runtime_parachains::coretime`. +pub struct WeightInfo(PhantomData); +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn request_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_486_000 picoseconds. + Weight::from_parts(7_889_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `CoretimeAssignmentProvider::CoreDescriptors` (r:1 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CoretimeAssignmentProvider::CoreSchedules` (r:0 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `s` is `[1, 100]`. + fn assign_core(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 9_409_000 picoseconds. + Weight::from_parts(10_177_115, 0) + .saturating_add(Weight::from_parts(0, 3612)) + // Standard Error: 259 + .saturating_add(Weight::from_parts(13_932, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/polkadot/runtime/westend/src/weights/xcm/mod.rs b/polkadot/runtime/westend/src/weights/xcm/mod.rs index d5b3d8257ba54cd665933d10a5518d73382327e1..0162012825ff7e4d47c43868ac94f894b38514c7 100644 --- a/polkadot/runtime/westend/src/weights/xcm/mod.rs +++ b/polkadot/runtime/westend/src/weights/xcm/mod.rs @@ -36,25 +36,25 @@ pub enum AssetTypes { Unknown, } -impl From<&MultiAsset> for AssetTypes { - fn from(asset: &MultiAsset) -> Self { +impl From<&Asset> for AssetTypes { + fn from(asset: &Asset) -> Self { match asset { - MultiAsset { id: Concrete(MultiLocation { parents: 0, interior: Here }), .. } => + Asset { id: AssetId(Location { parents: 0, interior: Here }), .. } => AssetTypes::Balances, _ => AssetTypes::Unknown, } } } -trait WeighMultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight; +trait WeighAssets { + fn weigh_assets(&self, balances_weight: Weight) -> Weight; } // Westend only knows about one asset, the balances pallet. const MAX_ASSETS: u64 = 1; -impl WeighMultiAssets for MultiAssetFilter { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, balances_weight: Weight) -> Weight { match self { Self::Definite(assets) => assets .inner() @@ -75,11 +75,11 @@ impl WeighMultiAssets for MultiAssetFilter { } } -impl WeighMultiAssets for MultiAssets { - fn weigh_multi_assets(&self, balances_weight: Weight) -> Weight { +impl WeighAssets for Assets { + fn weigh_assets(&self, balances_weight: Weight) -> Weight { self.inner() .into_iter() - .map(|m| >::from(m)) + .map(|m| >::from(m)) .map(|t| match t { AssetTypes::Balances => balances_weight, AssetTypes::Unknown => Weight::MAX, @@ -90,32 +90,28 @@ impl WeighMultiAssets for MultiAssets { pub struct WestendXcmWeight(core::marker::PhantomData); impl XcmWeightInfo for WestendXcmWeight { - fn withdraw_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::withdraw_asset()) + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::withdraw_asset()) } - fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::reserve_asset_deposited()) + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::reserve_asset_deposited()) } - fn receive_teleported_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::receive_teleported_asset()) + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::receive_teleported_asset()) } fn query_response( _query_id: &u64, _response: &Response, _max_weight: &Weight, - _querier: &Option, + _querier: &Option, ) -> Weight { XcmGeneric::::query_response() } - fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::transfer_asset()) + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::transfer_asset()) } - fn transfer_reserve_asset( - assets: &MultiAssets, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::transfer_reserve_asset()) + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::transfer_reserve_asset()) } fn transact( _origin_kind: &OriginKind, @@ -143,45 +139,37 @@ impl XcmWeightInfo for WestendXcmWeight { fn clear_origin() -> Weight { XcmGeneric::::clear_origin() } - fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + fn descend_origin(_who: &InteriorLocation) -> Weight { XcmGeneric::::descend_origin() } fn report_error(_query_repsonse_info: &QueryResponseInfo) -> Weight { XcmGeneric::::report_error() } - fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::deposit_asset()) + fn deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::deposit_asset()) } - fn deposit_reserve_asset( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::deposit_reserve_asset()) + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::deposit_reserve_asset()) } - fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { // Westend does not currently support exchange asset operations Weight::MAX } fn initiate_reserve_withdraw( - assets: &MultiAssetFilter, - _reserve: &MultiLocation, + assets: &AssetFilter, + _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmBalancesWeight::::initiate_reserve_withdraw()) } - fn initiate_teleport( - assets: &MultiAssetFilter, - _dest: &MultiLocation, - _xcm: &Xcm<()>, - ) -> Weight { - assets.weigh_multi_assets(XcmBalancesWeight::::initiate_teleport()) + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmBalancesWeight::::initiate_teleport()) } - fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() } - fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { XcmGeneric::::buy_execution() } fn refund_surplus() -> Weight { @@ -196,7 +184,7 @@ impl XcmWeightInfo for WestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } fn trap(_code: &u64) -> Weight { @@ -208,13 +196,13 @@ impl XcmWeightInfo for WestendXcmWeight { fn unsubscribe_version() -> Weight { XcmGeneric::::unsubscribe_version() } - fn burn_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) } - fn expect_asset(assets: &MultiAssets) -> Weight { - assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) } - fn expect_origin(_origin: &Option) -> Weight { + fn expect_origin(_origin: &Option) -> Weight { XcmGeneric::::expect_origin() } fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { @@ -249,19 +237,19 @@ impl XcmWeightInfo for WestendXcmWeight { // Westend relay should not support export message operations Weight::MAX } - fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn lock_asset(_: &Asset, _: &Location) -> Weight { // Westend does not currently support asset locking operations Weight::MAX } - fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn unlock_asset(_: &Asset, _: &Location) -> Weight { // Westend does not currently support asset locking operations Weight::MAX } - fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn note_unlockable(_: &Asset, _: &Location) -> Weight { // Westend does not currently support asset locking operations Weight::MAX } - fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + fn request_unlock(_: &Asset, _: &Location) -> Weight { // Westend does not currently support asset locking operations Weight::MAX } @@ -274,19 +262,19 @@ impl XcmWeightInfo for WestendXcmWeight { fn clear_topic() -> Weight { XcmGeneric::::clear_topic() } - fn alias_origin(_: &MultiLocation) -> Weight { + fn alias_origin(_: &Location) -> Weight { // XCM Executor does not currently support alias origin operations Weight::MAX } - fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } } #[test] fn all_counted_has_a_sane_weight_upper_limit() { - let assets = MultiAssetFilter::Wild(AllCounted(4294967295)); + let assets = AssetFilter::Wild(AllCounted(4294967295)); let weight = Weight::from_parts(1000, 1000); - assert_eq!(assets.weigh_multi_assets(weight), weight * MAX_ASSETS); + assert_eq!(assets.weigh_assets(weight), weight * MAX_ASSETS); } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index dd6a29885ad6fc9749e563d53ae2d943b513029c..73127cb1efd608e51a18d9eb412353289309bbf6 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}, + parameter_types, + traits::{Contains, Equals, Everything, Nothing}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -40,23 +40,24 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, - ChildParachainConvertsVia, CurrencyAdapter as XcmCurrencyAdapter, DescribeBodyTerminal, - DescribeFamily, HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, + ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, FrameTransactionalProcessor, + FungibleAdapter, HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeesToAccount, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { - pub const TokenLocation: MultiLocation = Here.into_location(); + pub const TokenLocation: Location = Here.into_location(); + pub const RootLocation: Location = Location::here(); pub const ThisNetwork: NetworkId = Westend; - pub const UniversalLocation: InteriorMultiLocation = X1(GlobalConsensus(ThisNetwork::get())); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetwork::get())].into(); pub CheckAccount: AccountId = XcmPallet::check_account(); pub LocalCheckAccount: (AccountId, MintLocation) = (CheckAccount::get(), MintLocation::Local); - pub TreasuryAccount: Option = Some(Treasury::account_id()); + pub TreasuryAccount: AccountId = Treasury::account_id(); /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = Concrete(TokenLocation::get()); + pub FeeAssetId: AssetId = AssetId(TokenLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -66,16 +67,16 @@ pub type LocationConverter = ( ChildParachainConvertsVia, // We can directly alias an `AccountId32` into a local account. AccountId32Aliases, - // Allow governance body to be used as a sovereign account. - HashedDescription>, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: IsConcrete, - // We can convert the MultiLocations with our converter above: + // We can convert the Locations with our converter above: LocationConverter, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, @@ -108,36 +109,55 @@ pub type XcmRouter = WithUniqueTopic< >; 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 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 MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; -} - -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(ASSET_HUB_ID).into()); + pub AssetHub: Location = Parachain(ASSET_HUB_ID).into_location(); + pub Collectives: Location = Parachain(COLLECTIVES_ID).into_location(); + pub BridgeHub: Location = Parachain(BRIDGE_HUB_ID).into_location(); + pub Encointer: Location = Parachain(ENCOINTER_ID).into_location(); + pub People: Location = Parachain(PEOPLE_ID).into_location(); + pub Broker: Location = Parachain(BROKER_ID).into_location(); + pub Wnd: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(TokenLocation::get()) }); + pub WndForAssetHub: (AssetFilter, Location) = (Wnd::get(), AssetHub::get()); + pub WndForCollectives: (AssetFilter, Location) = (Wnd::get(), Collectives::get()); + pub WndForBridgeHub: (AssetFilter, Location) = (Wnd::get(), BridgeHub::get()); + pub WndForEncointer: (AssetFilter, Location) = (Wnd::get(), Encointer::get()); + pub WndForPeople: (AssetFilter, Location) = (Wnd::get(), People::get()); + pub WndForBroker: (AssetFilter, Location) = (Wnd::get(), Broker::get()); + pub MaxInstructions: u32 = 100; + pub MaxAssetsIntoHolding: u32 = 64; } pub type TrustedTeleporters = ( xcm_builder::Case, 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 CollectivesOrFellows: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(COLLECTIVES_ID)) } | - MultiLocation { parents: 0, interior: X2(Parachain(COLLECTIVES_ID), Plurality { id: BodyId::Technical, .. }) } - }; +pub struct OnlyParachains; +impl Contains for OnlyParachains { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Parachain(_)])) + } +} + +pub struct CollectivesOrFellows; +impl Contains for CollectivesOrFellows { + fn contains(location: &Location) -> bool { + matches!( + location.unpack(), + (0, [Parachain(COLLECTIVES_ID)]) | + (0, [Parachain(COLLECTIVES_ID), Plurality { id: BodyId::Technical, .. }]) + ) + } +} + +pub struct LocalPlurality; +impl Contains for LocalPlurality { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (0, [Plurality { .. }])) + } } /// The barriers one of which must be passed for an XCM message to be executed. @@ -160,6 +180,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; @@ -185,12 +209,19 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = XcmPallet; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeesToAccount; + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } parameter_types! { @@ -200,13 +231,14 @@ 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::Treasury; } -/// Type to convert the `GeneralAdmin` origin to a Plurality `MultiLocation` value. +/// Type to convert the `GeneralAdmin` origin to a Plurality `Location` value. pub type GeneralAdminToPlurality = OriginToPluralityVoice; -/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior /// location of this chain. pub type LocalOriginToLocation = ( GeneralAdminToPlurality, @@ -214,23 +246,28 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -/// Type to convert the `StakingAdmin` origin to a Plurality `MultiLocation` value. +/// Type to convert the `StakingAdmin` origin to a Plurality `Location` value. pub type StakingAdminToPlurality = OriginToPluralityVoice; -/// Type to convert the `FellowshipAdmin` origin to a Plurality `MultiLocation` value. +/// Type to convert the `FellowshipAdmin` origin to a Plurality `Location` value. pub type FellowshipAdminToPlurality = OriginToPluralityVoice; -/// Type to convert a pallet `Origin` type value into a `MultiLocation` value which represents an +/// Type to convert the `Treasurer` origin to a Plurality `Location` value. +pub type TreasurerToPlurality = OriginToPluralityVoice; + +/// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( - // GeneralAdmin origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // GeneralAdmin origin to be used in XCM as a corresponding Plurality `Location` value. GeneralAdminToPlurality, - // StakingAdmin origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // StakingAdmin origin to be used in XCM as a corresponding Plurality `Location` value. StakingAdminToPlurality, - // FellowshipAdmin origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // FellowshipAdmin origin to be used in XCM as a corresponding Plurality `Location` value. FellowshipAdminToPlurality, + // `Treasurer` origin to be used in XCM as a corresponding Plurality `Location` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { @@ -262,7 +299,5 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = crate::weights::pallet_xcm::WeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } diff --git a/polkadot/scripts/build-only-wasm.sh b/polkadot/scripts/build-only-wasm.sh index b6da3319c8214aeca3ca54b76fda87d83077eec5..50b786dab41014dd53cd3e4dea3e02f66fd8b42d 100755 --- a/polkadot/scripts/build-only-wasm.sh +++ b/polkadot/scripts/build-only-wasm.sh @@ -13,6 +13,14 @@ fi WASM_BUILDER_RUNNER="$PROJECT_ROOT/target/release/wbuild-runner/$1" +fl_cargo () { + if command -v forklift >/dev/null 2>&1; then + forklift cargo "$@"; + else + cargo "$@"; + fi +} + if [ -z "$2" ]; then export WASM_TARGET_DIRECTORY=$(pwd) else @@ -22,8 +30,8 @@ fi if [ -d $WASM_BUILDER_RUNNER ]; then export DEBUG=false export OUT_DIR="$PROJECT_ROOT/target/release/build" - cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ + fl_cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ | grep -vE "cargo:rerun-if-|Executing build command" else - cargo build --release -p $1 + fl_cargo build --release -p $1 fi diff --git a/polkadot/scripts/list-syscalls/execute-worker-syscalls b/polkadot/scripts/list-syscalls/execute-worker-syscalls index 05abe9ba7368c43b766d1c888cd631aa9903d6b2..349af783cf1a1340d9467187fde21e1829ec6248 100644 --- a/polkadot/scripts/list-syscalls/execute-worker-syscalls +++ b/polkadot/scripts/list-syscalls/execute-worker-syscalls @@ -16,6 +16,7 @@ 16 (ioctl) 19 (readv) 20 (writev) +22 (pipe) 24 (sched_yield) 25 (mremap) 28 (madvise) @@ -24,8 +25,8 @@ 42 (connect) 45 (recvfrom) 46 (sendmsg) -53 (socketpair) 56 (clone) +57 (fork) 60 (exit) 61 (wait4) 62 (kill) @@ -38,6 +39,7 @@ 89 (readlink) 96 (gettimeofday) 97 (getrlimit) +98 (getrusage) 99 (sysinfo) 102 (getuid) 110 (getppid) @@ -49,26 +51,21 @@ 158 (arch_prctl) 165 (mount) 166 (umount2) +186 (gettid) 200 (tkill) 202 (futex) 204 (sched_getaffinity) -213 (epoll_create) 217 (getdents64) 218 (set_tid_address) 228 (clock_gettime) 230 (clock_nanosleep) 231 (exit_group) -232 (epoll_wait) -233 (epoll_ctl) 257 (openat) 262 (newfstatat) 263 (unlinkat) 272 (unshare) 273 (set_robust_list) -281 (epoll_pwait) -284 (eventfd) -290 (eventfd2) -291 (epoll_create1) +293 (pipe2) 302 (prlimit64) 318 (getrandom) 319 (memfd_create) diff --git a/polkadot/scripts/list-syscalls/prepare-worker-syscalls b/polkadot/scripts/list-syscalls/prepare-worker-syscalls index f1597f20675652a130a7e23e9039b0ad153ee306..05281b61591a7f9e45efa7d7bb6514fbe118f130 100644 --- a/polkadot/scripts/list-syscalls/prepare-worker-syscalls +++ b/polkadot/scripts/list-syscalls/prepare-worker-syscalls @@ -16,6 +16,7 @@ 16 (ioctl) 19 (readv) 20 (writev) +22 (pipe) 24 (sched_yield) 25 (mremap) 28 (madvise) @@ -24,8 +25,8 @@ 42 (connect) 45 (recvfrom) 46 (sendmsg) -53 (socketpair) 56 (clone) +57 (fork) 60 (exit) 61 (wait4) 62 (kill) @@ -50,27 +51,22 @@ 158 (arch_prctl) 165 (mount) 166 (umount2) +186 (gettid) 200 (tkill) 202 (futex) 203 (sched_setaffinity) 204 (sched_getaffinity) -213 (epoll_create) 217 (getdents64) 218 (set_tid_address) 228 (clock_gettime) 230 (clock_nanosleep) 231 (exit_group) -232 (epoll_wait) -233 (epoll_ctl) 257 (openat) 262 (newfstatat) 263 (unlinkat) 272 (unshare) 273 (set_robust_list) -281 (epoll_pwait) -284 (eventfd) -290 (eventfd2) -291 (epoll_create1) +293 (pipe2) 302 (prlimit64) 309 (getcpu) 318 (getrandom) diff --git a/polkadot/src/bin/execute-worker.rs b/polkadot/src/bin/execute-worker.rs index 1deb365809860502e8e298802814a185479c09c6..c39a5a3c89de7e5d1bec3b49e275526786879f87 100644 --- a/polkadot/src/bin/execute-worker.rs +++ b/polkadot/src/bin/execute-worker.rs @@ -20,4 +20,5 @@ polkadot_node_core_pvf_common::decl_worker_main!( "execute-worker", polkadot_node_core_pvf_execute_worker::worker_entrypoint, polkadot_cli::NODE_VERSION, + env!("SUBSTRATE_CLI_COMMIT_HASH"), ); diff --git a/polkadot/src/bin/prepare-worker.rs b/polkadot/src/bin/prepare-worker.rs index d731f8a30d065f0c9abdcc438df411be7d16417e..3b42991f18baa042fb605a9859800c7da4634929 100644 --- a/polkadot/src/bin/prepare-worker.rs +++ b/polkadot/src/bin/prepare-worker.rs @@ -20,4 +20,5 @@ polkadot_node_core_pvf_common::decl_worker_main!( "prepare-worker", polkadot_node_core_pvf_prepare_worker::worker_entrypoint, polkadot_cli::NODE_VERSION, + env!("SUBSTRATE_CLI_COMMIT_HASH"), ); diff --git a/polkadot/src/main.rs b/polkadot/src/main.rs index 5986d8cea7bb5029ed34bb953f6c02aac009d1e9..1a96bf8fb00f6943ec64073be1e29365ef4a4e4e 100644 --- a/polkadot/src/main.rs +++ b/polkadot/src/main.rs @@ -24,7 +24,7 @@ use color_eyre::eyre; /// `memory_stats::MemoryAllocationTracker`. #[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))] #[global_allocator] -pub static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; fn main() -> eyre::Result<()> { color_eyre::install()?; diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index 91ca2015af3a1d766f37cf7e0ae33eb282640a53..37b8a99d640a2d23e0d7a532adc0c43b04bba1f2 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -1,11 +1,16 @@ [package] name = "polkadot-statement-table" -version = "1.0.0" +version = "7.0.0" authors.workspace = true 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" } primitives = { package = "polkadot-primitives", path = "../primitives" } +gum = { package = "tracing-gum", path = "../node/gum" } diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 22bffde5acc11b8fdbea565d054949caf40d788f..2ee6f6a4f781842866728e2105d6c23e2fa1cd70 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -36,6 +36,7 @@ use primitives::{ }; use parity_scale_codec::{Decode, Encode}; +const LOG_TARGET: &str = "parachain::statement-table"; /// Context for the statement table. pub trait Context { @@ -53,9 +54,6 @@ pub trait Context { /// get the digest of a candidate. fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest; - /// get the group of a candidate. - fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId; - /// Whether a authority is a member of a group. /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; @@ -342,13 +340,13 @@ impl Table { pub fn import_statement( &mut self, context: &Ctx, + group_id: Ctx::GroupId, statement: SignedStatement, ) -> Option> { let SignedStatement { statement, signature, sender: signer } = statement; - let res = match statement { Statement::Seconded(candidate) => - self.import_candidate(context, signer.clone(), candidate, signature), + self.import_candidate(context, signer.clone(), candidate, signature, group_id), Statement::Valid(digest) => self.validity_vote(context, signer.clone(), digest, ValidityVote::Valid(signature)), }; @@ -387,9 +385,10 @@ impl Table { authority: Ctx::AuthorityId, candidate: Ctx::Candidate, signature: Ctx::Signature, + group: Ctx::GroupId, ) -> ImportResult { - let group = Ctx::candidate_group(&candidate); if !context.is_member_of(&authority, &group) { + gum::debug!(target: LOG_TARGET, authority = ?authority, group = ?group, "New `Misbehavior::UnauthorizedStatement`, candidate backed by validator that doesn't belong to expected group" ); return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { statement: SignedStatement { signature, @@ -634,10 +633,6 @@ mod tests { Digest(candidate.1) } - fn candidate_group(candidate: &Candidate) -> GroupId { - GroupId(candidate.0) - } - fn is_member_of(&self, authority: &AuthorityId, group: &GroupId) -> bool { self.authorities.get(authority).map(|v| v == group).unwrap_or(false) } @@ -675,10 +670,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::MultipleCandidates(MultipleCandidates { @@ -711,10 +706,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -735,7 +730,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], @@ -769,7 +764,7 @@ mod tests { }; let candidate_a_digest = Digest(100); - table.import_statement(&context, candidate_a); + table.import_statement(&context, GroupId(2), candidate_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); @@ -779,7 +774,7 @@ mod tests { signature: Signature(2), sender: AuthorityId(2), }; - table.import_statement(&context, bad_validity_vote); + table.import_statement(&context, GroupId(3), bad_validity_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(2)][0], @@ -811,7 +806,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let invalid_statement = SignedStatement { @@ -820,7 +815,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, invalid_statement); + table.import_statement(&context, GroupId(2), invalid_statement); assert!(table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -842,7 +837,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let extra_vote = SignedStatement { @@ -851,7 +846,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, extra_vote); + table.import_statement(&context, GroupId(2), extra_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity( @@ -910,7 +905,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none()); @@ -921,7 +916,7 @@ mod tests { sender: AuthorityId(2), }; - table.import_statement(&context, vote); + table.import_statement(&context, GroupId(2), vote); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some()); } @@ -944,7 +939,7 @@ mod tests { }; let summary = table - .import_statement(&context, statement) + .import_statement(&context, GroupId(2), statement) .expect("candidate import to give summary"); assert_eq!(summary.candidate, Digest(100)); @@ -971,7 +966,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let vote = SignedStatement { @@ -980,8 +975,9 @@ mod tests { sender: AuthorityId(2), }; - let summary = - table.import_statement(&context, vote).expect("candidate vote to give summary"); + let summary = table + .import_statement(&context, GroupId(2), vote) + .expect("candidate vote to give summary"); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs index d4629330ac01512e2dc2b0f441d2302f0fd38624..3740d15cc4f326962b962557e61ca14e9163de7d 100644 --- a/polkadot/statement-table/src/lib.rs +++ b/polkadot/statement-table/src/lib.rs @@ -35,8 +35,8 @@ pub use generic::{Config, Context, Table}; pub mod v2 { use crate::generic; use primitives::{ - CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, Id, - ValidatorIndex, ValidatorSignature, + CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, + CoreIndex, ValidatorIndex, ValidatorSignature, }; /// Statements about candidates on the network. @@ -59,7 +59,7 @@ pub mod v2 { >; /// A summary of import of a statement. - pub type Summary = generic::Summary; + pub type Summary = generic::Summary; impl<'a> From<&'a Statement> for PrimitiveStatement { fn from(s: &'a Statement) -> PrimitiveStatement { diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index 95ca57ea728e16837843207e04d97f6b472979be..ad6d7259d2483e11e9df8c121bab75461a71c525 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "polkadot-voter-bags" -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +description = "CLI to generate voter bags for Polkadot runtimes" + +[lints] +workspace = true [dependencies] -clap = { version = "4.4.6", features = ["derive"] } +clap = { version = "4.5.3", 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 e305edc039b5ab68c91bf587f322678c91e02cc1..20e4130f888bcb1f826dab81fb7e5a721a53a225 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.6", features = ["derive"] } -log = "0.4.17" +clap = { version = "4.5.3", features = ["derive"] } +log = { workspace = true, default-features = true } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 92024e69c1b6dc1ebaafc90198e65fd688f863c7..f9ccfb9833a6b276f001491ff29daa183f0f8e22 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -1,20 +1,25 @@ [package] name = "staging-xcm" description = "The basic XCM datastructures." -version = "1.0.0" +version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -bounded-collections = { version = "0.1.8", default-features = false, features = ["serde"] } -derivative = { version = "2.2.0", default-features = false, features = [ "use_core" ] } +array-bytes = "6.1" +bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } +derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } -parity-scale-codec = { version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len" ] } +log = { workspace = true } +parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-weights = { path = "../../substrate/primitives/weights", default-features = false, features = ["serde"] } -serde = { version = "1.0.188", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive", "rc"], workspace = true } +schemars = { version = "0.8.13", default-features = true, optional = true } xcm-procedural = { path = "procedural" } environmental = { version = "1.1.4", default-features = false } @@ -24,13 +29,15 @@ hex = "0.4.3" hex-literal = "0.4.1" [features] -default = [ "std" ] +default = ["std"] wasm-api = [] std = [ "bounded-collections/std", "environmental/std", + "log/std", "parity-scale-codec/std", "scale-info/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 fb4f389b212c855bf66ca5cc3fe8cc97f647b4d9..80f2d1deedf724c28ca71508a2b21411b57096ee 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -3,7 +3,11 @@ name = "pallet-xcm-benchmarks" authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "7.0.0" +description = "Benchmarks for the XCM pallet" + +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -11,16 +15,16 @@ 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"] } -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} +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 } xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", default-features = false } -frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false} +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false } xcm = { package = "staging-xcm", path = "..", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] pallet-balances = { path = "../../../substrate/frame/balances" } @@ -35,12 +39,14 @@ polkadot-runtime-common = { path = "../../runtime/common" } polkadot-primitives = { path = "../../primitives" } [features] -default = [ "std" ] +default = ["std"] std = [ "codec/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index d32eb8d4a52f7c98473f4a427b7976410701183b..4b77199069d341320a67f196719604cedcc35157 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use sp_runtime::traits::{Bounded, Zero}; use sp_std::{prelude::*, vec}; -use xcm::latest::{prelude::*, MAX_ITEMS_IN_MULTIASSETS}; +use xcm::latest::{prelude::*, MAX_ITEMS_IN_ASSETS}; use xcm_executor::traits::{ConvertLocation, FeeReason, TransactAsset}; benchmarks_instance_pallet! { @@ -43,7 +43,7 @@ benchmarks_instance_pallet! { withdraw_asset { let (sender_account, sender_location) = account_and_location::(1); let worst_case_holding = T::worst_case_holding(0); - let asset = T::get_multi_asset(); + let asset = T::get_asset(); >::deposit_asset(&asset, &sender_location, None).unwrap(); // check the assets of origin. @@ -63,14 +63,17 @@ benchmarks_instance_pallet! { transfer_asset { let (sender_account, sender_location) = account_and_location::(1); - let asset = T::get_multi_asset(); - let assets: MultiAssets = vec![ asset.clone() ].into(); + let asset = T::get_asset(); + let assets: Assets = vec![asset.clone()].into(); // this xcm doesn't use holding let dest_location = T::valid_destination()?; let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); >::deposit_asset(&asset, &sender_location, None).unwrap(); + // We deposit the asset twice so we have enough for ED after transferring + >::deposit_asset(&asset, &sender_location, None).unwrap(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(sender_location); @@ -79,7 +82,7 @@ benchmarks_instance_pallet! { }: { executor.bench_process(xcm)?; } verify { - assert!(T::TransactAsset::balance(&sender_account).is_zero()); + assert!(T::TransactAsset::balance(&sender_account) < sender_account_balance_before); assert!(!T::TransactAsset::balance(&dest_account).is_zero()); } @@ -93,12 +96,13 @@ benchmarks_instance_pallet! { &dest_location, FeeReason::TransferReserveAsset ); - let sender_account_balance_before = T::TransactAsset::balance(&sender_account); - let asset = T::get_multi_asset(); + let asset = T::get_asset(); + >::deposit_asset(&asset, &sender_location, None).unwrap(); + // We deposit the asset twice so we have enough for ED after transferring >::deposit_asset(&asset, &sender_location, None).unwrap(); - assert!(T::TransactAsset::balance(&sender_account) > sender_account_balance_before); - let assets: MultiAssets = vec![ asset ].into(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); + let assets: Assets = vec![asset].into(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(sender_location); @@ -129,7 +133,7 @@ benchmarks_instance_pallet! { BenchmarkResult::from_weight(Weight::MAX) ))?; - let assets: MultiAssets = vec![ transferable_reserve_asset ].into(); + let assets: Assets = vec![ transferable_reserve_asset ].into(); let mut executor = new_executor::(trusted_reserve); let instruction = Instruction::ReserveAssetDeposited(assets.clone()); @@ -143,7 +147,7 @@ benchmarks_instance_pallet! { initiate_reserve_withdraw { let (sender_account, sender_location) = account_and_location::(1); let holding = T::worst_case_holding(1); - let assets_filter = MultiAssetFilter::Definite(holding.clone().into_inner().into_iter().take(MAX_ITEMS_IN_MULTIASSETS).collect::>().into()); + let assets_filter = AssetFilter::Definite(holding.clone().into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::>().into()); let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( @@ -188,7 +192,7 @@ benchmarks_instance_pallet! { )?; } - let assets: MultiAssets = vec![ teleportable_asset ].into(); + let assets: Assets = vec![ teleportable_asset ].into(); let mut executor = new_executor::(trusted_teleporter); let instruction = Instruction::ReceiveTeleportedAsset(assets.clone()); @@ -204,7 +208,7 @@ benchmarks_instance_pallet! { } deposit_asset { - let asset = T::get_multi_asset(); + let asset = T::get_asset(); let mut holding = T::worst_case_holding(1); // Add our asset to the holding. @@ -230,7 +234,7 @@ benchmarks_instance_pallet! { } deposit_reserve_asset { - let asset = T::get_multi_asset(); + let asset = T::get_asset(); let mut holding = T::worst_case_holding(1); // Add our asset to the holding. @@ -257,7 +261,7 @@ benchmarks_instance_pallet! { } initiate_teleport { - let asset = T::get_multi_asset(); + let asset = T::get_asset(); let mut holding = T::worst_case_holding(0); // Add our asset to the holding. diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index 9adc706fc18ae9ecd1540a820f79085573e52d1b..c831cd024659135fd3fd5d85a4406351945d887f 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use xcm::latest::prelude::*; -use xcm_builder::{AllowUnpaidExecutionFrom, MintLocation}; +use xcm_builder::{AllowUnpaidExecutionFrom, FrameTransactionalProcessor, MintLocation}; type Block = frame_system::mocking::MockBlock; @@ -34,9 +34,9 @@ type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - XcmBalancesBenchmark: xcm_balances_benchmark::{Pallet}, + System: frame_system, + Balances: pallet_balances, + XcmBalancesBenchmark: xcm_balances_benchmark, } ); @@ -45,6 +45,8 @@ parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); } + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -75,7 +77,7 @@ parameter_types! { pub const ExistentialDeposit: u64 = 7; } -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type AccountStore = System; @@ -91,17 +93,17 @@ parameter_types! { pub struct MatchAnyFungible; impl xcm_executor::traits::MatchesFungible for MatchAnyFungible { - fn matches_fungible(m: &MultiAsset) -> Option { + fn matches_fungible(m: &Asset) -> Option { use sp_runtime::traits::SaturatedConversion; match m { - MultiAsset { fun: Fungible(amount), .. } => Some((*amount).saturated_into::()), + Asset { fun: Fungible(amount), .. } => Some((*amount).saturated_into::()), _ => None, } } } // Use balances as the asset transactor. -pub type AssetTransactor = xcm_builder::CurrencyAdapter< +pub type AssetTransactor = xcm_builder::FungibleAdapter< Balances, MatchAnyFungible, AccountIdConverter, @@ -142,19 +144,22 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } impl crate::Config for Test { type XcmConfig = XcmConfig; type AccountIdConverter = AccountIdConverter; type DeliveryHelper = (); - fn valid_destination() -> Result { - let valid_destination: MultiLocation = - X1(AccountId32 { network: None, id: [0u8; 32] }).into(); + fn valid_destination() -> Result { + let valid_destination: Location = [AccountId32 { network: None, id: [0u8; 32] }].into(); Ok(valid_destination) } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { + fn worst_case_holding(depositable_count: u32) -> Assets { crate::mock_worst_case_holding( depositable_count, ::MaxAssetsIntoHolding::get(), @@ -167,19 +172,19 @@ pub type TrustedReserves = xcm_builder::Case; parameter_types! { pub const CheckingAccount: Option<(u64, MintLocation)> = Some((100, MintLocation::Local)); - pub const ChildTeleporter: MultiLocation = Parachain(1000).into_location(); - pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + pub ChildTeleporter: Location = Parachain(1000).into_location(); + pub TrustedTeleporter: Option<(Location, Asset)> = Some(( ChildTeleporter::get(), - MultiAsset { id: Concrete(Here.into_location()), fun: Fungible(100) }, + Asset { id: AssetId(Here.into_location()), fun: Fungible(100) }, )); - pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = Some(( + pub TrustedReserve: Option<(Location, Asset)> = Some(( ChildTeleporter::get(), - MultiAsset { id: Concrete(Here.into_location()), fun: Fungible(100) }, + Asset { id: AssetId(Here.into_location()), fun: Fungible(100) }, )); - pub const TeleportConcreteFungible: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get()); - pub const ReserveConcreteFungible: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { fun: WildFungible, id: Concrete(Here.into_location()) }), ChildTeleporter::get()); + pub TeleportConcreteFungible: (AssetFilter, Location) = + (Wild(AllOf { fun: WildFungible, id: AssetId(Here.into_location()) }), ChildTeleporter::get()); + pub ReserveConcreteFungible: (AssetFilter, Location) = + (Wild(AllOf { fun: WildFungible, id: AssetId(Here.into_location()) }), ChildTeleporter::get()); } impl xcm_balances_benchmark::Config for Test { @@ -188,10 +193,9 @@ impl xcm_balances_benchmark::Config for Test { type TrustedTeleporter = TrustedTeleporter; type TrustedReserve = TrustedReserve; - fn get_multi_asset() -> MultiAsset { - let amount = - >::minimum_balance() as u128; - MultiAsset { id: Concrete(Here.into()), fun: Fungible(amount) } + fn get_asset() -> Asset { + let amount = 1_000_000_000_000; + Asset { id: AssetId(Here.into()), fun: Fungible(amount) } } } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs index 292921eb595fb082c5c46c1c078d0346c1053970..e84355f4092bbe15459183371dc512487fd0a8a3 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mod.rs @@ -37,14 +37,14 @@ pub mod pallet { type CheckedAccount: Get>; /// A trusted location which we allow teleports from, and the asset we allow to teleport. - type TrustedTeleporter: Get>; + type TrustedTeleporter: Get>; /// A trusted location where reserve assets are stored, and the asset we allow to be /// reserves. - type TrustedReserve: Get>; + type TrustedReserve: Get>; /// Give me a fungible asset that your asset transactor is going to accept. - fn get_multi_asset() -> xcm::latest::MultiAsset; + fn get_asset() -> xcm::latest::Asset; } #[pallet::pallet] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index c6b76e0ffadebf4a76d7da03a5821fb42bb204de..8c6ed4b5d0e0245a1758820bf5ab9ec9f7d095e9 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -77,7 +77,7 @@ benchmarks! { let mut executor = new_executor::(Default::default()); executor.set_holding(holding); - let fee_asset = Concrete(Here.into()); + let fee_asset = AssetId(Here.into()); let instruction = Instruction::>::BuyExecution { fees: (fee_asset, 100_000_000u128).into(), // should be something inside of holding @@ -95,7 +95,7 @@ benchmarks! { let mut executor = new_executor::(Default::default()); let (query_id, response) = T::worst_case_response(); let max_weight = Weight::MAX; - let querier: Option = Some(Here.into()); + let querier: Option = Some(Here.into()); let instruction = Instruction::QueryResponse { query_id, response, max_weight, querier }; let xcm = Xcm(vec![instruction]); }: { @@ -125,11 +125,15 @@ benchmarks! { } refund_surplus { - let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::(Default::default()); - executor.set_holding(holding); + let holding_assets = T::worst_case_holding(1); + // We can already buy execution since we'll load the holding register manually + let asset_for_fees = T::fee_asset().unwrap(); + let previous_xcm = Xcm(vec![BuyExecution { fees: asset_for_fees, weight_limit: Limited(Weight::from_parts(1337, 1337)) }]); + executor.set_holding(holding_assets.into()); executor.set_total_surplus(Weight::from_parts(1337, 1337)); executor.set_total_refunded(Weight::zero()); + executor.bench_process(previous_xcm).expect("Holding has been loaded, so we can buy execution here"); let instruction = Instruction::>::RefundSurplus; let xcm = Xcm(vec![instruction]); @@ -174,7 +178,7 @@ benchmarks! { descend_origin { let mut executor = new_executor::(Default::default()); - let who = X2(OnlyChild, OnlyChild); + let who = Junctions::from([OnlyChild, OnlyChild]); let instruction = Instruction::DescendOrigin(who.clone()); let xcm = Xcm(vec![instruction]); } : { @@ -182,7 +186,7 @@ benchmarks! { } verify { assert_eq!( executor.origin(), - &Some(MultiLocation { + &Some(Location { parents: 0, interior: who, }), @@ -413,8 +417,9 @@ benchmarks! { executor.set_holding(expected_assets_in_holding.into()); } + let valid_pallet = T::valid_pallet(); let instruction = Instruction::QueryPallet { - module_name: b"frame_system".to_vec(), + module_name: valid_pallet.module_name.as_bytes().to_vec(), response_info: QueryResponseInfo { destination, query_id, max_weight }, }; let xcm = Xcm(vec![instruction]); @@ -428,13 +433,13 @@ benchmarks! { expect_pallet { let mut executor = new_executor::(Default::default()); - + let valid_pallet = T::valid_pallet(); let instruction = Instruction::ExpectPallet { - index: 0, - name: b"System".to_vec(), - module_name: b"frame_system".to_vec(), - crate_major: 4, - min_crate_minor: 0, + index: valid_pallet.index as u32, + name: valid_pallet.name.as_bytes().to_vec(), + module_name: valid_pallet.module_name.as_bytes().to_vec(), + crate_major: valid_pallet.crate_version.major.into(), + min_crate_minor: valid_pallet.crate_version.minor.into(), }; let xcm = Xcm(vec![instruction]); }: { @@ -537,14 +542,14 @@ 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)?; } verify { use frame_support::traits::Get; let universal_location = ::UniversalLocation::get(); - assert_eq!(executor.origin(), &Some(X1(alias).relative_to(&universal_location))); + assert_eq!(executor.origin(), &Some(Junctions::from([alias]).relative_to(&universal_location))); } export_message { @@ -560,8 +565,8 @@ benchmarks! { let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( &origin, - &destination.into(), - FeeReason::Export(network), + &destination.clone().into(), + FeeReason::Export { network, destination: destination.clone() }, ); let sender_account = T::AccountIdConverter::convert_location(&origin).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -574,7 +579,7 @@ benchmarks! { executor.set_holding(expected_assets_in_holding.into()); } let xcm = Xcm(vec![ExportMessage { - network, destination, xcm: inner_xcm, + network, destination: destination.clone(), xcm: inner_xcm, }]); }: { executor.bench_process(xcm)?; diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs index 710ff0d801927854a35a9de27a7e51a6803b809f..534f7d85ea2e9aa9759aca4bda9e4759dbf49517 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mock.rs @@ -19,18 +19,18 @@ use crate::{generic, mock::*, *}; use codec::Decode; use frame_support::{ - derive_impl, match_types, parameter_types, - traits::{Everything, OriginTrait}, + derive_impl, parameter_types, + traits::{Contains, Everything, OriginTrait}, weights::Weight, }; use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TrailingZeroInput}; use xcm_builder::{ test_utils::{ - Assets, TestAssetExchanger, TestAssetLocker, TestAssetTrap, TestSubscriptionService, - TestUniversalAliases, + AssetsInHolding, TestAssetExchanger, TestAssetLocker, TestAssetTrap, + TestSubscriptionService, TestUniversalAliases, }, - AliasForeignAccountId32, AllowUnpaidExecutionFrom, + AliasForeignAccountId32, AllowUnpaidExecutionFrom, FrameTransactionalProcessor, }; use xcm_executor::traits::ConvertOrigin; @@ -39,9 +39,9 @@ type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - XcmGenericBenchmarks: generic::{Pallet}, + System: frame_system, + Balances: pallet_balances, + XcmGenericBenchmarks: generic, } ); @@ -51,6 +51,7 @@ parameter_types! { frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -80,19 +81,15 @@ impl frame_system::Config for Test { /// The benchmarks in this pallet should never need an asset transactor to begin with. pub struct NoAssetTransactor; impl xcm_executor::traits::TransactAsset for NoAssetTransactor { - fn deposit_asset( - _: &MultiAsset, - _: &MultiLocation, - _: Option<&XcmContext>, - ) -> Result<(), XcmError> { + fn deposit_asset(_: &Asset, _: &Location, _: Option<&XcmContext>) -> Result<(), XcmError> { unreachable!(); } fn withdraw_asset( - _: &MultiAsset, - _: &MultiLocation, + _: &Asset, + _: &Location, _: Option<&XcmContext>, - ) -> Result { + ) -> Result { unreachable!(); } } @@ -102,10 +99,11 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } -match_types! { - pub type OnlyParachains: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(_)) } - }; +pub struct OnlyParachains; +impl Contains for OnlyParachains { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Parachain(_)])) + } } type Aliasers = AliasForeignAccountId32; @@ -136,13 +134,17 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Aliasers; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } parameter_types! { pub const ExistentialDeposit: u64 = 7; } -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type AccountStore = System; @@ -152,13 +154,13 @@ impl crate::Config for Test { type XcmConfig = XcmConfig; type AccountIdConverter = AccountIdConverter; type DeliveryHelper = (); - fn valid_destination() -> Result { - let valid_destination: MultiLocation = + fn valid_destination() -> Result { + let valid_destination: Location = Junction::AccountId32 { network: None, id: [0u8; 32] }.into(); Ok(valid_destination) } - fn worst_case_holding(depositable_count: u32) -> MultiAssets { + fn worst_case_holding(depositable_count: u32) -> Assets { crate::mock_worst_case_holding( depositable_count, ::MaxAssetsIntoHolding::get(), @@ -171,48 +173,51 @@ impl generic::Config for Test { type RuntimeCall = RuntimeCall; fn worst_case_response() -> (u64, Response) { - let assets: MultiAssets = (Concrete(Here.into()), 100).into(); + let assets: Assets = (AssetId(Here.into()), 100).into(); (0, Response::Assets(assets)) } - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { Ok(Default::default()) } - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { Ok((Here.into(), GlobalConsensus(ByGenesis([0; 32])))) } fn transact_origin_and_runtime_call( - ) -> Result<(MultiLocation, ::RuntimeCall), BenchmarkError> { + ) -> Result<(Location, ::RuntimeCall), BenchmarkError> { Ok((Default::default(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } - fn subscribe_origin() -> Result { + fn subscribe_origin() -> Result { Ok(Default::default()) } - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { - let assets: MultiAssets = (Concrete(Here.into()), 100).into(); - let ticket = MultiLocation { parents: 0, interior: X1(GeneralIndex(0)) }; + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let assets: Assets = (AssetId(Here.into()), 100).into(); + let ticket = Location { parents: 0, interior: [GeneralIndex(0)].into() }; Ok((Default::default(), ticket, assets)) } - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { - let assets: MultiAsset = (Concrete(Here.into()), 100).into(); + fn fee_asset() -> Result { + Ok(Asset { id: AssetId(Here.into()), fun: Fungible(1_000_000) }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + let assets: Asset = (AssetId(Here.into()), 100).into(); Ok((Default::default(), account_id_junction::(1).into(), assets)) } fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { // No MessageExporter in tests Err(BenchmarkError::Skip) } - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { - let origin: MultiLocation = - (Parachain(1), AccountId32 { network: None, id: [0; 32] }).into(); - let target: MultiLocation = AccountId32 { network: None, id: [0; 32] }.into(); + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { + let origin: Location = (Parachain(1), AccountId32 { network: None, id: [0; 32] }).into(); + let target: Location = AccountId32 { network: None, id: [0; 32] }.into(); Ok((origin, target)) } } @@ -232,9 +237,9 @@ where ::AccountId: Decode, { fn convert_origin( - _origin: impl Into, + _origin: impl Into, _kind: OriginKind, - ) -> Result { + ) -> Result { Ok(RuntimeOrigin::signed( ::AccountId::decode(&mut TrailingZeroInput::zeroes()) .expect("infinite length input; no invalid inputs for type; qed"), diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mod.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mod.rs index cbdfa8d0112cbe7fac6da7160c199f5f4eeda3d6..b514eaa4727276588724be1e74a305d54a684629 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mod.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/mod.rs @@ -26,10 +26,7 @@ pub mod pallet { use frame_benchmarking::BenchmarkError; use frame_support::{dispatch::GetDispatchInfo, pallet_prelude::Encode}; use sp_runtime::traits::Dispatchable; - use xcm::latest::{ - InteriorMultiLocation, Junction, MultiAsset, MultiAssets, MultiLocation, NetworkId, - Response, - }; + use xcm::latest::{Asset, Assets, InteriorLocation, Junction, Location, NetworkId, Response}; #[pallet::config] pub trait Config: frame_system::Config + crate::Config { @@ -53,44 +50,60 @@ pub mod pallet { /// from, whereas the second element represents the assets that are being exchanged to. /// /// If set to `Err`, benchmarks which rely on an `exchange_asset` will be skipped. - fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError>; + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError>; - /// A `(MultiLocation, Junction)` that is one of the `UniversalAliases` configured by the + /// A `(Location, Junction)` that is one of the `UniversalAliases` configured by the /// XCM executor. /// /// If set to `Err`, benchmarks which rely on a universal alias will be skipped. - fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError>; + fn universal_alias() -> Result<(Location, Junction), BenchmarkError>; - /// The `MultiLocation` and `RuntimeCall` used for successful transaction XCMs. + /// The `Location` and `RuntimeCall` used for successful transaction XCMs. /// /// If set to `Err`, benchmarks which rely on a `transact_origin_and_runtime_call` will be /// skipped. fn transact_origin_and_runtime_call( - ) -> Result<(MultiLocation, >::RuntimeCall), BenchmarkError>; + ) -> Result<(Location, >::RuntimeCall), BenchmarkError>; - /// A valid `MultiLocation` we can successfully subscribe to. + /// A valid `Location` we can successfully subscribe to. /// /// If set to `Err`, benchmarks which rely on a `subscribe_origin` will be skipped. - fn subscribe_origin() -> Result; + fn subscribe_origin() -> Result; /// Return an origin, ticket, and assets that can be trapped and claimed. - fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError>; + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError>; + + /// Asset used to pay for fees. Used to buy weight in benchmarks, for example in + /// `refund_surplus`. + fn fee_asset() -> Result; /// Return an unlocker, owner and assets that can be locked and unlocked. - fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError>; + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError>; - /// A `(MultiLocation, NetworkId, InteriorMultiLocation)` we can successfully export message + /// A `(Location, NetworkId, InteriorLocation)` we can successfully export message /// to. /// /// If set to `Err`, benchmarks which rely on `export_message` will be skipped. fn export_message_origin_and_destination( - ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError>; + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError>; - /// A `(MultiLocation, MultiLocation)` that is one of the `Aliasers` configured by the XCM + /// A `(Location, Location)` that is one of the `Aliasers` configured by the XCM /// executor. /// /// If set to `Err`, benchmarks which rely on a universal alias will be skipped. - fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError>; + fn alias_origin() -> Result<(Location, Location), BenchmarkError>; + + /// Returns a valid pallet info for `ExpectPallet` or `QueryPallet` benchmark. + /// + /// By default returns `frame_system::Pallet` info with expected pallet index `0`. + fn valid_pallet() -> frame_support::traits::PalletInfoData { + frame_support::traits::PalletInfoData { + index: as frame_support::traits::PalletInfoAccess>::index(), + name: as frame_support::traits::PalletInfoAccess>::name(), + module_name: as frame_support::traits::PalletInfoAccess>::module_name(), + crate_version: as frame_support::traits::PalletInfoAccess>::crate_version(), + } + } } #[pallet::pallet] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 3bf4aea1b25e5ca6680fd707b02493cb393087ee..63ed0ac0ca736450077a4677d29d65a81e9eaf3b 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -22,10 +22,8 @@ use codec::Encode; use frame_benchmarking::{account, BenchmarkError}; use sp_std::prelude::*; use xcm::latest::prelude::*; -use xcm_executor::{ - traits::{ConvertLocation, FeeReason}, - Config as XcmConfig, FeesMode, -}; +use xcm_builder::EnsureDelivery; +use xcm_executor::{traits::ConvertLocation, Config as XcmConfig}; pub mod fungible; pub mod generic; @@ -41,18 +39,18 @@ pub trait Config: frame_system::Config { /// `TransactAsset` is implemented. type XcmConfig: XcmConfig; - /// A converter between a multi-location to a sovereign account. + /// A converter between a location to a sovereign account. type AccountIdConverter: ConvertLocation; /// Helper that ensures successful delivery for XCM instructions which need `SendXcm`. type DeliveryHelper: EnsureDelivery; /// Does any necessary setup to create a valid destination for XCM messages. - /// Returns that destination's multi-location to be used in benchmarks. - fn valid_destination() -> Result; + /// Returns that destination's location to be used in benchmarks. + fn valid_destination() -> Result; /// Worst case scenario for a holding account in this runtime. - fn worst_case_holding(depositable_count: u32) -> MultiAssets; + fn worst_case_holding(depositable_count: u32) -> Assets; } const SEED: u32 = 0; @@ -66,21 +64,21 @@ pub type AssetTransactorOf = <::XcmConfig as XcmConfig>::AssetTr /// The call type of executor's config. Should eventually resolve to the same overarching call type. pub type XcmCallOf = <::XcmConfig as XcmConfig>::RuntimeCall; -pub fn mock_worst_case_holding(depositable_count: u32, max_assets: u32) -> MultiAssets { +pub fn mock_worst_case_holding(depositable_count: u32, max_assets: u32) -> Assets { let fungibles_amount: u128 = 100; let holding_fungibles = max_assets / 2 - depositable_count; let holding_non_fungibles = holding_fungibles; (0..holding_fungibles) .map(|i| { - MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + Asset { + id: AssetId(GeneralIndex(i as u128).into()), fun: Fungible(fungibles_amount * i as u128), } .into() }) - .chain(core::iter::once(MultiAsset { id: Concrete(Here.into()), fun: Fungible(u128::MAX) })) - .chain((0..holding_non_fungibles).map(|i| MultiAsset { - id: Concrete(GeneralIndex(i as u128).into()), + .chain(core::iter::once(Asset { id: AssetId(Here.into()), fun: Fungible(u128::MAX) })) + .chain((0..holding_non_fungibles).map(|i| Asset { + id: AssetId(GeneralIndex(i as u128).into()), fun: NonFungible(asset_instance_from(i)), })) .collect::>() @@ -94,11 +92,11 @@ pub fn asset_instance_from(x: u32) -> AssetInstance { AssetInstance::Array4(instance) } -pub fn new_executor(origin: MultiLocation) -> ExecutorOf { +pub fn new_executor(origin: Location) -> ExecutorOf { ExecutorOf::::new(origin, [0; 32]) } -/// Build a multi-location from an account id. +/// Build a location from an account id. fn account_id_junction(index: u32) -> Junction { let account: T::AccountId = account("account", index, SEED); let mut encoded = account.encode(); @@ -108,35 +106,9 @@ fn account_id_junction(index: u32) -> Junction { Junction::AccountId32 { network: None, id } } -pub fn account_and_location(index: u32) -> (T::AccountId, MultiLocation) { - let location: MultiLocation = account_id_junction::(index).into(); +pub fn account_and_location(index: u32) -> (T::AccountId, Location) { + let location: Location = account_id_junction::(index).into(); let account = T::AccountIdConverter::convert_location(&location).unwrap(); (account, location) } - -/// Trait for a type which ensures all requirements for successful delivery with XCM transport -/// layers. -pub trait EnsureDelivery { - /// Prepare all requirements for successful `XcmSender: SendXcm` passing (accounts, balances, - /// channels ...). Returns: - /// - possible `FeesMode` which is expected to be set to executor - /// - possible `MultiAssets` which are expected to be subsume to the Holding Register - fn ensure_successful_delivery( - origin_ref: &MultiLocation, - dest: &MultiLocation, - fee_reason: FeeReason, - ) -> (Option, Option); -} - -/// `()` implementation does nothing which means no special requirements for environment. -impl EnsureDelivery for () { - fn ensure_successful_delivery( - _origin_ref: &MultiLocation, - _dest: &MultiLocation, - _fee_reason: FeeReason, - ) -> (Option, Option) { - // doing nothing - (None, None) - } -} diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs index e02c5bf08615bae8d1b428768f925412025f9a06..78a9e5f8a018aad85fde699412a816fb54f30391 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/mock.rs @@ -22,8 +22,8 @@ use xcm::latest::Weight; pub struct DevNull; impl xcm::opaque::latest::SendXcm for DevNull { type Ticket = (); - fn validate(_: &mut Option, _: &mut Option>) -> SendResult<()> { - Ok(((), MultiAssets::new())) + fn validate(_: &mut Option, _: &mut Option>) -> SendResult<()> { + Ok(((), Assets::new())) } fn deliver(_: ()) -> Result { Ok([0; 32]) @@ -31,13 +31,13 @@ impl xcm::opaque::latest::SendXcm for DevNull { } impl xcm_executor::traits::OnResponse for DevNull { - fn expecting_response(_: &MultiLocation, _: u64, _: Option<&MultiLocation>) -> bool { + fn expecting_response(_: &Location, _: u64, _: Option<&Location>) -> bool { false } fn on_response( - _: &MultiLocation, + _: &Location, _: u64, - _: Option<&MultiLocation>, + _: Option<&Location>, _: Response, _: Weight, _: &XcmContext, @@ -48,9 +48,9 @@ impl xcm_executor::traits::OnResponse for DevNull { pub struct AccountIdConverter; impl xcm_executor::traits::ConvertLocation for AccountIdConverter { - fn convert_location(ml: &MultiLocation) -> Option { - match ml { - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { id, .. }) } => + fn convert_location(ml: &Location) -> Option { + match ml.unpack() { + (0, [Junction::AccountId32 { id, .. }]) => Some(::decode(&mut &*id.to_vec()).unwrap()), _ => None, } @@ -58,14 +58,14 @@ impl xcm_executor::traits::ConvertLocation for AccountIdConverter { } parameter_types! { - pub UniversalLocation: InteriorMultiLocation = Junction::Parachain(101).into(); + pub UniversalLocation: InteriorLocation = Junction::Parachain(101).into(); pub UnitWeightCost: Weight = Weight::from_parts(10, 10); - pub WeightPrice: (AssetId, u128, u128) = (Concrete(Here.into()), 1_000_000, 1024); + pub WeightPrice: (AssetId, u128, u128) = (AssetId(Here.into()), 1_000_000, 1024); } pub struct AllAssetLocationsPass; -impl ContainsPair for AllAssetLocationsPass { - fn contains(_: &MultiAsset, _: &MultiLocation) -> bool { +impl ContainsPair for AllAssetLocationsPass { + fn contains(_: &Asset, _: &Location) -> bool { true } } diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index e471e8cf2695cda4316f57ed8726f0da8517914d..4840b6127f55425de91eea85099649d4dfda0881 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -1,49 +1,58 @@ [package] name = "pallet-xcm" -version = "1.0.0" +version = "7.0.0" description = "A pallet for handling XCM programs." authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -bounded-collections = { version = "0.1.8", default-features = false } +bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.188", optional = true, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +log = { workspace = true } -frame-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-io = { path = "../../../substrate/primitives/io", default-features = false} -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false} -sp-std = { path = "../../../substrate/primitives/std", default-features = false} +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../substrate/primitives/core", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } xcm = { package = "staging-xcm", path = "..", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false } + +# marked optional, used in benchmarking +frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } +pallet-balances = { path = "../../../substrate/frame/balances", default-features = false, optional = true } [dev-dependencies] -pallet-balances = { path = "../../../substrate/frame/balances" } +pallet-assets = { path = "../../../substrate/frame/assets" } polkadot-runtime-parachains = { path = "../../runtime/parachains" } polkadot-parachain-primitives = { path = "../../parachain" } -xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ "bounded-collections/std", "codec/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", + "pallet-balances/std", "scale-info/std", "serde", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] @@ -51,6 +60,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", @@ -61,6 +71,7 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-assets/try-runtime", "pallet-balances/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index aca4bd1fb3fa92b812bc11d9957286310047a98b..ed42f93692b406ea07b2edbda0fbdb315c2b4071 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -16,15 +16,84 @@ use super::*; use bounded_collections::{ConstU32, WeakBoundedVec}; -use frame_benchmarking::{benchmarks, BenchmarkError, BenchmarkResult}; -use frame_support::weights::Weight; +use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; +use frame_support::{ + traits::fungible::{Inspect, Mutate}, + weights::Weight, +}; use frame_system::RawOrigin; use sp_std::prelude::*; use xcm::{latest::prelude::*, v2}; +use xcm_builder::EnsureDelivery; +use xcm_executor::traits::FeeReason; type RuntimeOrigin = ::RuntimeOrigin; +/// Pallet we're benchmarking here. +pub struct Pallet(crate::Pallet); + +/// Trait that must be implemented by runtime to be able to benchmark pallet properly. +pub trait Config: crate::Config { + /// Helper that ensures successful delivery for extrinsics/benchmarks which need `SendXcm`. + type DeliveryHelper: EnsureDelivery; + + /// A `Location` that can be reached via `XcmRouter`. Used only in benchmarks. + /// + /// If `None`, the benchmarks that depend on a reachable destination will be skipped. + fn reachable_dest() -> Option { + None + } + + /// A `(Asset, Location)` pair representing asset and the destination it can be + /// teleported to. Used only in benchmarks. + /// + /// Implementation should also make sure `dest` is reachable/connected. + /// + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + None + } + + /// A `(Asset, Location)` pair representing asset and the destination it can be + /// reserve-transferred to. Used only in benchmarks. + /// + /// Implementation should also make sure `dest` is reachable/connected. + /// + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + 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 + /// `(Asset, u32, Location, 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<(Assets, u32, Location, Box)> { + None + } + + /// Gets an asset that can be handled by the AssetTransactor. + /// + /// Used only in benchmarks. + /// + /// Used, for example, in the benchmark for `claim_assets`. + fn get_asset() -> Asset; +} + benchmarks! { + where_clause { + where + T: pallet_balances::Config, + ::Balance: From + Into, + } send { let send_origin = T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; @@ -32,7 +101,7 @@ benchmarks! { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let msg = Xcm(vec![ClearOrigin]); - let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( + let versioned_dest: VersionedLocation = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )? .into(); @@ -40,44 +109,111 @@ benchmarks! { }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) teleport_assets { - let asset: MultiAsset = (Here, 10).into(); - let send_origin = - T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone()) + let (asset, destination) = T::teleportable_asset_and_dest().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + + let transferred_amount = match &asset.fun { + Fungible(amount) => *amount, + _ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")), + }.into(); + let assets: Assets = asset.into(); + + let caller: T::AccountId = whitelisted_caller(); + let send_origin = RawOrigin::Signed(caller.clone()); + let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmTeleportFilter::contains(&(origin_location, vec![asset.clone()])) { + if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; - let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); - let versioned_beneficiary: VersionedMultiLocation = + let versioned_dest: VersionedLocation = destination.into(); + let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); - let versioned_assets: VersionedMultiAssets = asset.into(); - }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + let versioned_assets: VersionedAssets = assets.into(); + }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + verify { + // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) + assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); + } reserve_transfer_assets { - let asset: MultiAsset = (Here, 10).into(); - let send_origin = - T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone()) + let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + + let transferred_amount = match &asset.fun { + Fungible(amount) => *amount, + _ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")), + }.into(); + let assets: Assets = asset.into(); + + let caller: T::AccountId = whitelisted_caller(); + let send_origin = RawOrigin::Signed(caller.clone()); + let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmReserveTransferFilter::contains(&(origin_location, vec![asset.clone()])) { + if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; - let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or( + let versioned_dest: VersionedLocation = destination.into(); + let versioned_beneficiary: VersionedLocation = + AccountId32 { network: None, id: recipient.into() }.into(); + let versioned_assets: VersionedAssets = assets.into(); + }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + verify { + // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) + assert!( as Inspect<_>>::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)), - )? - .into(); - let versioned_beneficiary: VersionedMultiLocation = + )?; + let caller: T::AccountId = whitelisted_caller(); + let send_origin = RawOrigin::Signed(caller.clone()); + let recipient = [0u8; 32]; + let versioned_dest: VersionedLocation = destination.into(); + let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); - let versioned_assets: VersionedMultiAssets = asset.into(); - }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) + let versioned_assets: VersionedAssets = 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 = @@ -89,10 +225,10 @@ benchmarks! { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let versioned_msg = VersionedXcm::from(msg); - }: _>(execute_origin, Box::new(versioned_msg), Weight::zero()) + }: _>(execute_origin, Box::new(versioned_msg), Weight::MAX) force_xcm_version { - let loc = T::ReachableDest::get().ok_or( + let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )?; let xcm_version = 2; @@ -101,59 +237,59 @@ benchmarks! { force_default_xcm_version {}: _(RawOrigin::Root, Some(2)) force_subscribe_version_notify { - let versioned_loc: VersionedMultiLocation = T::ReachableDest::get().ok_or( + let versioned_loc: VersionedLocation = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )? .into(); }: _(RawOrigin::Root, Box::new(versioned_loc)) force_unsubscribe_version_notify { - let loc = T::ReachableDest::get().ok_or( + let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )?; - let versioned_loc: VersionedMultiLocation = loc.into(); - let _ = Pallet::::request_version_notify(loc); + let versioned_loc: VersionedLocation = loc.clone().into(); + let _ = crate::Pallet::::request_version_notify(loc); }: _(RawOrigin::Root, Box::new(versioned_loc)) force_suspension {}: _(RawOrigin::Root, true) migrate_supported_version { let old_version = XCM_VERSION - 1; - let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + let loc = VersionedLocation::from(Location::from(Parent)); SupportedVersion::::insert(old_version, loc, old_version); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero()); } migrate_version_notifiers { let old_version = XCM_VERSION - 1; - let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifiers::::insert(old_version, loc, 0); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero()); } already_notified_target { - let loc = T::ReachableDest::get().ok_or( + let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))), )?; - let loc = VersionedMultiLocation::from(loc); + let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), current_version)); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); } notify_current_targets { - let loc = T::ReachableDest::get().ok_or( + let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), )?; - let loc = VersionedMultiLocation::from(loc); + let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), old_version)); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); } notify_target_migration_fail { @@ -163,36 +299,108 @@ benchmarks! { part: v2::BodyPart::Voice, } .into(); - let bad_loc = VersionedMultiLocation::from(bad_loc); + let bad_loc = VersionedLocation::from(bad_loc); let current_version = T::AdvertisedXcmVersion::get(); VersionNotifyTargets::::insert(current_version, bad_loc, (0, Weight::zero(), current_version)); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); } migrate_version_notify_targets { let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; - let loc = VersionedMultiLocation::from(MultiLocation::from(Parent)); + let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), current_version)); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); } migrate_and_notify_old_targets { - let loc = T::ReachableDest::get().ok_or( + let loc = T::reachable_dest().ok_or( BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), )?; - let loc = VersionedMultiLocation::from(loc); + let loc = VersionedLocation::from(loc); let old_version = T::AdvertisedXcmVersion::get() - 1; VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), old_version)); }: { - Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + } + + new_query { + let responder = Location::from(Parent); + let timeout = 1u32.into(); + let match_querier = Location::from(Here); + }: { + crate::Pallet::::new_query(responder, timeout, match_querier); + } + + take_response { + let responder = Location::from(Parent); + let timeout = 1u32.into(); + let match_querier = Location::from(Here); + let query_id = crate::Pallet::::new_query(responder, timeout, match_querier); + let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new( + u32::MAX, + (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(), + (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(), + u32::MAX, + u32::MAX, + u32::MAX, + ).unwrap()).collect::>(); + crate::Pallet::::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap())); + }: { + as QueryHandler>::take_response(query_id); } + claim_assets { + let claim_origin = RawOrigin::Signed(whitelisted_caller()); + let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()).map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + let asset: Asset = T::get_asset(); + // Trap assets for claiming later + crate::Pallet::::drop_assets( + &claim_location, + asset.clone().into(), + &XcmContext { origin: None, message_id: [0u8; 32], topic: None } + ); + let versioned_assets = VersionedAssets::V4(asset.into()); + }: _>(claim_origin.into(), Box::new(versioned_assets), Box::new(VersionedLocation::V4(claim_location))) + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext_with_balances(Vec::new()), crate::mock::Test ); } + +pub mod helpers { + use super::*; + pub fn native_teleport_as_asset_transfer( + native_asset_location: Location, + destination: Location, + ) -> Option<(Assets, u32, Location, 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: Assets = + Asset { fun: Fungible(amount.into()), id: AssetId(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 321bb294b88d89187924a88a98302c33dd860fb9..1a1f8b402a39041ffb5852dd56e9725a550afc7c 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] -mod benchmarking; +pub mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] @@ -28,9 +28,17 @@ mod tests; pub mod migration; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use frame_support::traits::{ - Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, OriginTrait, +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, GetDispatchInfo, WithPostDispatchInfo}, + pallet_prelude::*, + traits::{ + Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, + OriginTrait, WithdrawReasons, + }, + PalletId, }; +use frame_system::pallet_prelude::{BlockNumberFor, *}; +pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -41,25 +49,27 @@ use sp_runtime::{ }; use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec}; use xcm::{latest::QueryResponseInfo, prelude::*}; -use xcm_executor::traits::{ConvertOrigin, Properties}; - -use frame_support::{ - dispatch::GetDispatchInfo, pallet_prelude::*, traits::WithdrawReasons, PalletId, +use xcm_builder::{ + ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo, + SendController, SendControllerWeightInfo, }; -use frame_system::pallet_prelude::*; -pub use pallet::*; use xcm_executor::{ traits::{ - CheckSuspension, ClaimAssets, ConvertLocation, DropAssets, MatchesFungible, OnResponse, - QueryHandler, QueryResponseStatus, VersionChangeNotifier, WeightBounds, + AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin, + DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus, + TransactAsset, TransferType, VersionChangeNotifier, WeightBounds, XcmAssetTransfers, }, - Assets, + AssetsInHolding, }; +#[cfg(any(feature = "try-runtime", test))] +use sp_runtime::TryRuntimeError; + 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; @@ -73,6 +83,9 @@ pub trait WeightInfo { fn notify_target_migration_fail() -> Weight; fn migrate_version_notify_targets() -> Weight; fn migrate_and_notify_old_targets() -> Weight; + fn new_query() -> Weight; + fn take_response() -> Weight; + fn claim_assets() -> Weight; } /// fallback implementation @@ -90,6 +103,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) } @@ -141,6 +158,18 @@ impl WeightInfo for TestWeightInfo { fn migrate_and_notify_old_targets() -> Weight { Weight::from_parts(100_000_000, 0) } + + fn new_query() -> Weight { + Weight::from_parts(100_000_000, 0) + } + + fn take_response() -> Weight { + Weight::from_parts(100_000_000, 0) + } + + fn claim_assets() -> Weight { + Weight::from_parts(100_000_000, 0) + } } #[frame_support::pallet] @@ -181,45 +210,39 @@ pub mod pallet { // TODO: We should really use a trait which can handle multiple currencies. type Currency: LockableCurrency>; - /// The `MultiAsset` matcher for `Currency`. + /// The `Asset` matcher for `Currency`. type CurrencyMatcher: MatchesFungible>; - /// Required origin for sending XCM messages. If successful, it resolves to `MultiLocation` + /// Required origin for sending XCM messages. If successful, it resolves to `Location` /// which exists as an interior location within this chain's XCM context. - type SendXcmOrigin: EnsureOrigin< - ::RuntimeOrigin, - Success = MultiLocation, - >; + type SendXcmOrigin: EnsureOrigin<::RuntimeOrigin, Success = Location>; /// The type used to actually dispatch an XCM to its destination. type XcmRouter: SendXcm; /// Required origin for executing XCM messages, including the teleport functionality. If - /// successful, then it resolves to `MultiLocation` which exists as an interior location + /// successful, then it resolves to `Location` which exists as an interior location /// within this chain's XCM context. - type ExecuteXcmOrigin: EnsureOrigin< - ::RuntimeOrigin, - Success = MultiLocation, - >; + type ExecuteXcmOrigin: EnsureOrigin<::RuntimeOrigin, Success = Location>; /// Our XCM filter which messages to be executed using `XcmExecutor` must pass. - type XcmExecuteFilter: Contains<(MultiLocation, Xcm<::RuntimeCall>)>; + type XcmExecuteFilter: Contains<(Location, Xcm<::RuntimeCall>)>; /// Something to execute an XCM message. - type XcmExecutor: ExecuteXcm<::RuntimeCall>; + type XcmExecutor: ExecuteXcm<::RuntimeCall> + XcmAssetTransfers; /// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass. - type XcmTeleportFilter: Contains<(MultiLocation, Vec)>; + type XcmTeleportFilter: Contains<(Location, Vec)>; /// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic /// must pass. - type XcmReserveTransferFilter: Contains<(MultiLocation, Vec)>; + type XcmReserveTransferFilter: Contains<(Location, Vec)>; /// Means of measuring the weight consumed by an XCM message locally. type Weigher: WeightBounds<::RuntimeCall>; /// This chain's Universal Location. - type UniversalLocation: Get; + type UniversalLocation: Get; /// The runtime `Origin` type. type RuntimeOrigin: From + From<::RuntimeOrigin>; @@ -243,9 +266,9 @@ pub mod pallet { /// The assets which we consider a given origin is trusted if they claim to have placed a /// lock. - type TrustedLockers: ContainsPair; + type TrustedLockers: ContainsPair; - /// How to get an `AccountId` value from a `MultiLocation`, useful for handling asset locks. + /// How to get an `AccountId` value from a `Location`, useful for handling asset locks. type SovereignAccountOf: ConvertLocation; /// The maximum number of local XCM locks that a single account may have. @@ -259,12 +282,109 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + } - /// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks. - /// - /// If `None`, the benchmarks that depend on a reachable destination will be skipped. - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest: Get>; + impl ExecuteControllerWeightInfo for Pallet { + fn execute() -> Weight { + T::WeightInfo::execute() + } + } + + impl ExecuteController, ::RuntimeCall> for Pallet { + type WeightInfo = Self; + fn execute( + origin: OriginFor, + message: Box::RuntimeCall>>, + max_weight: Weight, + ) -> Result { + log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); + let outcome = (|| { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let mut hash = message.using_encoded(sp_io::hashing::blake2_256); + let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; + let value = (origin_location, message); + ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); + let (origin_location, message) = value; + Ok(T::XcmExecutor::prepare_and_execute( + origin_location, + message, + &mut hash, + max_weight, + max_weight, + )) + })() + .map_err(|e: DispatchError| { + e.with_weight(::execute()) + })?; + + Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); + let weight_used = outcome.weight_used(); + outcome.ensure_complete().map_err(|error| { + log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); + Error::::LocalExecutionIncomplete.with_weight( + weight_used.saturating_add( + ::execute(), + ), + ) + })?; + Ok(weight_used) + } + } + + impl SendControllerWeightInfo for Pallet { + fn send() -> Weight { + T::WeightInfo::send() + } + } + + impl SendController> for Pallet { + type WeightInfo = Self; + fn send( + origin: OriginFor, + dest: Box, + message: Box>, + ) -> Result { + let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; + let interior: Junctions = + origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; + let dest = Location::try_from(*dest).map_err(|()| Error::::BadVersion)?; + let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; + + let message_id = Self::send_xcm(interior, dest.clone(), message.clone()) + .map_err(Error::::from)?; + let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; + Self::deposit_event(e); + Ok(message_id) + } + } + + impl QueryControllerWeightInfo for Pallet { + fn query() -> Weight { + T::WeightInfo::new_query() + } + fn take_response() -> Weight { + T::WeightInfo::take_response() + } + } + + impl QueryController, BlockNumberFor> for Pallet { + type WeightInfo = Self; + + fn query( + origin: OriginFor, + timeout: BlockNumberFor, + match_querier: VersionedLocation, + ) -> Result { + let responder = ::ExecuteXcmOrigin::ensure_origin(origin)?; + let query_id = ::new_query( + responder, + timeout, + Location::try_from(match_querier) + .map_err(|_| Into::::into(Error::::BadVersion))?, + ); + + Ok(query_id) + } } #[pallet::event] @@ -273,16 +393,11 @@ pub mod pallet { /// Execution of an XCM message was attempted. Attempted { outcome: xcm::latest::Outcome }, /// A XCM message was sent. - Sent { - origin: MultiLocation, - destination: MultiLocation, - message: Xcm<()>, - message_id: XcmHash, - }, + Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash }, /// Query response received which does not match a registered query. This may be because a /// matching query was never registered, it may be because it is a duplicate response, or /// because the query timed out. - UnexpectedResponse { origin: MultiLocation, query_id: QueryId }, + UnexpectedResponse { origin: Location, query_id: QueryId }, /// Query response has been received and is ready for taking with `take_response`. There is /// no registered notification call. ResponseReady { query_id: QueryId, response: Response }, @@ -310,9 +425,9 @@ pub mod pallet { /// not match that expected. The query remains registered for a later, valid, response to /// be received and acted upon. InvalidResponder { - origin: MultiLocation, + origin: Location, query_id: QueryId, - expected_location: Option, + expected_location: Option, }, /// Expected query response has been received but the expected origin location placed in /// storage by this runtime previously cannot be decoded. The query remains registered. @@ -321,29 +436,29 @@ pub mod pallet { /// runtime should be readable prior to query timeout) and dangerous since the possibly /// valid response will be dropped. Manual governance intervention is probably going to be /// needed. - InvalidResponderVersion { origin: MultiLocation, query_id: QueryId }, + InvalidResponderVersion { origin: Location, query_id: QueryId }, /// Received query response has been read and removed. ResponseTaken { query_id: QueryId }, /// Some assets have been placed in an asset trap. - AssetsTrapped { hash: H256, origin: MultiLocation, assets: VersionedMultiAssets }, + AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets }, /// An XCM version change notification message has been attempted to be sent. /// /// The cost of sending it (borne by the chain) is included. VersionChangeNotified { - destination: MultiLocation, + destination: Location, result: XcmVersion, - cost: MultiAssets, + cost: Assets, message_id: XcmHash, }, /// The supported version of a location has been changed. This might be through an /// automatic notification or a manual intervention. - SupportedVersionChanged { location: MultiLocation, version: XcmVersion }, + SupportedVersionChanged { location: Location, version: XcmVersion }, /// A given location which had a version change subscription was dropped owing to an error /// sending the notification to it. - NotifyTargetSendFail { location: MultiLocation, query_id: QueryId, error: XcmError }, + NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError }, /// A given location which had a version change subscription was dropped owing to an error /// migrating the location to our new XCM format. - NotifyTargetMigrationFail { location: VersionedMultiLocation, query_id: QueryId }, + NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId }, /// Expected query response has been received but the expected querier location placed in /// storage by this runtime previously cannot be decoded. The query remains registered. /// @@ -351,48 +466,42 @@ pub mod pallet { /// runtime should be readable prior to query timeout) and dangerous since the possibly /// valid response will be dropped. Manual governance intervention is probably going to be /// needed. - InvalidQuerierVersion { origin: MultiLocation, query_id: QueryId }, + InvalidQuerierVersion { origin: Location, query_id: QueryId }, /// Expected query response has been received but the querier location of the response does /// not match the expected. The query remains registered for a later, valid, response to /// be received and acted upon. InvalidQuerier { - origin: MultiLocation, + origin: Location, query_id: QueryId, - expected_querier: MultiLocation, - maybe_actual_querier: Option, + expected_querier: Location, + maybe_actual_querier: Option, }, /// A remote has requested XCM version change notification from us and we have honored it. /// A version information message is sent to them and its cost is included. - VersionNotifyStarted { destination: MultiLocation, cost: MultiAssets, message_id: XcmHash }, + VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash }, /// We have requested that a remote chain send us XCM version change notifications. - VersionNotifyRequested { - destination: MultiLocation, - cost: MultiAssets, - message_id: XcmHash, - }, + VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash }, /// We have requested that a remote chain stops sending us XCM version change /// notifications. - VersionNotifyUnrequested { - destination: MultiLocation, - cost: MultiAssets, - message_id: XcmHash, - }, + VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash }, /// Fees were paid from a location for an operation (often for using `SendXcm`). - FeesPaid { paying: MultiLocation, fees: MultiAssets }, + FeesPaid { paying: Location, fees: Assets }, /// Some assets have been claimed from an asset trap - AssetsClaimed { hash: H256, origin: MultiLocation, assets: VersionedMultiAssets }, + AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets }, + /// A XCM version migration finished. + VersionMigrationFinished { version: XcmVersion }, } #[pallet::origin] #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum Origin { /// It comes from somewhere in the XCM space wanting to transact. - Xcm(MultiLocation), + Xcm(Location), /// It comes as an expected response from an XCM location. - Response(MultiLocation), + Response(Location), } - impl From for Origin { - fn from(location: MultiLocation) -> Origin { + impl From for Origin { + fn from(location: Location) -> Origin { Origin::Xcm(location) } } @@ -409,7 +518,7 @@ pub mod pallet { Filtered, /// The message's weight could not be determined. UnweighableMessage, - /// The destination `MultiLocation` provided cannot be inverted. + /// The destination `Location` provided cannot be inverted. DestinationNotInvertible, /// The assets to be sent are empty. Empty, @@ -428,8 +537,8 @@ pub mod pallet { NoSubscription, /// The location is invalid since it already has a subscription from us. AlreadySubscribed, - /// Invalid asset for the operation. - InvalidAsset, + /// Could not check-out the assets for teleportation to the destination chain. + CannotCheckOutTeleport, /// The owner does not own (all) of the asset that they wish to do the operation on. LowBalance, /// The asset owner has too many locks on the asset. @@ -442,6 +551,16 @@ pub mod pallet { LockNotFound, /// The unlock operation cannot succeed because there are still consumers of the lock. InUse, + /// Invalid non-concrete asset. + InvalidAssetNotConcrete, + /// Invalid asset, reserve chain could not be determined for it. + InvalidAssetUnknownReserve, + /// Invalid asset, do not support remote asset reserves with different fees reserves. + InvalidAssetUnsupportedReserve, + /// Too many assets with different reserve locations have been attempted for transfer. + TooManyReserves, + /// Local XCM execution incomplete. + LocalExecutionIncomplete, } impl From for Error { @@ -454,6 +573,15 @@ pub mod pallet { } } + impl From for Error { + fn from(e: AssetTransferError) -> Self { + match e { + AssetTransferError::NotConcrete => Error::::InvalidAssetNotConcrete, + AssetTransferError::UnknownReserve => Error::::InvalidAssetUnknownReserve, + } + } + } + /// The status of a query. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum QueryStatus { @@ -461,25 +589,25 @@ pub mod pallet { Pending { /// The `QueryResponse` XCM must have this origin to be considered a reply for this /// query. - responder: VersionedMultiLocation, + responder: VersionedLocation, /// The `QueryResponse` XCM must have this value as the `querier` field to be /// considered a reply for this query. If `None` then the querier is ignored. - maybe_match_querier: Option, + maybe_match_querier: Option, maybe_notify: Option<(u8, u8)>, timeout: BlockNumber, }, /// The query is for an ongoing version notification subscription. - VersionNotifier { origin: VersionedMultiLocation, is_active: bool }, + VersionNotifier { origin: VersionedLocation, is_active: bool }, /// A response has been received. Ready { response: VersionedResponse, at: BlockNumber }, } #[derive(Copy, Clone)] - pub(crate) struct LatestVersionedMultiLocation<'a>(pub(crate) &'a MultiLocation); - impl<'a> EncodeLike for LatestVersionedMultiLocation<'a> {} - impl<'a> Encode for LatestVersionedMultiLocation<'a> { + pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location); + impl<'a> EncodeLike for LatestVersionedLocation<'a> {} + impl<'a> Encode for LatestVersionedLocation<'a> { fn encode(&self) -> Vec { - let mut r = VersionedMultiLocation::from(MultiLocation::default()).encode(); + let mut r = VersionedLocation::from(Location::default()).encode(); r.truncate(1); self.0.using_encoded(|d| r.extend_from_slice(d)); r @@ -512,7 +640,7 @@ pub mod pallet { /// The existing asset traps. /// - /// Key is the blake2 256 hash of (origin, versioned `MultiAssets`) pair. Value is the number of + /// Key is the blake2 256 hash of (origin, versioned `Assets`) pair. Value is the number of /// times this pair has been trapped (usually just 1 if it exists at all). #[pallet::storage] #[pallet::getter(fn asset_trap)] @@ -531,7 +659,7 @@ pub mod pallet { Twox64Concat, XcmVersion, Blake2_128Concat, - VersionedMultiLocation, + VersionedLocation, XcmVersion, OptionQuery, >; @@ -543,7 +671,7 @@ pub mod pallet { Twox64Concat, XcmVersion, Blake2_128Concat, - VersionedMultiLocation, + VersionedLocation, QueryId, OptionQuery, >; @@ -556,7 +684,7 @@ pub mod pallet { Twox64Concat, XcmVersion, Blake2_128Concat, - VersionedMultiLocation, + VersionedLocation, (QueryId, Weight, XcmVersion), OptionQuery, >; @@ -575,7 +703,7 @@ pub mod pallet { #[pallet::whitelist_storage] pub(super) type VersionDiscoveryQueue = StorageValue< _, - BoundedVec<(VersionedMultiLocation, u32), VersionDiscoveryQueueSize>, + BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize>, ValueQuery, >; @@ -590,9 +718,9 @@ pub mod pallet { /// Total amount of the asset held by the remote lock. pub amount: u128, /// The owner of the locked asset. - pub owner: VersionedMultiLocation, + pub owner: VersionedLocation, /// The location which holds the original lock. - pub locker: VersionedMultiLocation, + pub locker: VersionedLocation, /// Local consumers of the remote lock with a consumer identifier and the amount /// of fungible asset every consumer holds. /// Every consumer can hold up to total amount of the remote lock. @@ -626,7 +754,7 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - BoundedVec<(BalanceOf, VersionedMultiLocation), T::MaxLockers>, + BoundedVec<(BalanceOf, VersionedLocation), T::MaxLockers>, OptionQuery, >; @@ -663,6 +791,9 @@ pub mod pallet { // Consume 10% of block at most let max_weight = T::BlockWeights::get().max_block / 10; let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight); + if maybe_migration.is_none() { + Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION }); + } CurrentMigration::::set(maybe_migration); weight_used.saturating_accrue(w); } @@ -674,7 +805,7 @@ pub mod pallet { weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); q.sort_by_key(|i| i.1); while let Some((versioned_dest, _)) = q.pop() { - if let Ok(dest) = MultiLocation::try_from(versioned_dest) { + if let Ok(dest) = Location::try_from(versioned_dest) { if Self::request_version_notify(dest).is_ok() { // TODO: correct weights. weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); @@ -689,6 +820,11 @@ pub mod pallet { } weight_used } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } pub mod migrations { @@ -698,12 +834,12 @@ pub mod pallet { #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] enum QueryStatusV0 { Pending { - responder: VersionedMultiLocation, + responder: VersionedLocation, maybe_notify: Option<(u8, u8)>, timeout: BlockNumber, }, VersionNotifier { - origin: VersionedMultiLocation, + origin: VersionedLocation, is_active: bool, }, Ready { @@ -719,7 +855,7 @@ pub mod pallet { responder, maybe_notify, timeout, - maybe_match_querier: Some(MultiLocation::here().into()), + maybe_match_querier: Some(Location::here().into()), }, VersionNotifier { origin, is_active } => QueryStatus::VersionNotifier { origin, is_active }, @@ -768,19 +904,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::send())] pub fn send( origin: OriginFor, - dest: Box, + dest: Box, message: Box>, ) -> DispatchResult { - let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; - let interior: Junctions = - origin_location.try_into().map_err(|_| Error::::InvalidOrigin)?; - let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; - let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; - - let message_id = - Self::send_xcm(interior, dest, message.clone()).map_err(Error::::from)?; - let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; - Self::deposit_event(e); + >::send(origin, dest, message)?; Ok(()) } @@ -793,19 +920,19 @@ pub mod pallet { /// with all fees taken as needed from the asset. /// /// - `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. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[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. 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)] #[pallet::weight({ - let maybe_assets: Result = (*assets.clone()).try_into(); - let maybe_dest: Result = (*dest.clone()).try_into(); + 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; @@ -813,11 +940,7 @@ pub mod pallet { let mut message = Xcm(vec![ WithdrawAsset(assets), SetFeesMode { jit_withdraw: true }, - InitiateTeleport { - assets: Wild(AllCounted(count)), - dest, - xcm: Xcm(vec![]), - }, + InitiateTeleport { assets: Wild(AllCounted(count)), dest, xcm: Xcm(vec![]) }, ]); T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w)) } @@ -826,16 +949,27 @@ pub mod pallet { })] pub fn teleport_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, ) -> DispatchResult { 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.** /// @@ -844,22 +978,24 @@ pub mod pallet { /// with all fees taken as needed from the asset. /// /// - `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. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[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` 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)] #[pallet::weight({ - let maybe_assets: Result = (*assets.clone()).try_into(); - let maybe_dest: Result = (*dest.clone()).try_into(); + 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 + // transfer assets to SA, reanchor them, extend XCM program, and send onward XCM let mut message = Xcm(vec![ SetFeesMode { jit_withdraw: true }, TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } @@ -871,9 +1007,9 @@ pub mod pallet { })] pub fn reserve_transfer_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, ) -> DispatchResult { Self::do_reserve_transfer_assets( @@ -894,9 +1030,6 @@ pub mod pallet { /// No more than `max_weight` will be used in its attempted execution. If this is less than /// the maximum amount of weight that the message could take to be executed, then no /// execution attempt will be made. - /// - /// NOTE: A successful return to this does *not* imply that the `msg` was executed - /// successfully to completion; only that *some* of it was executed. #[pallet::call_index(3)] #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))] pub fn execute( @@ -904,23 +1037,9 @@ pub mod pallet { message: Box::RuntimeCall>>, max_weight: Weight, ) -> DispatchResultWithPostInfo { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let hash = message.using_encoded(sp_io::hashing::blake2_256); - let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; - let value = (origin_location, message); - ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); - let (origin_location, message) = value; - let outcome = T::XcmExecutor::execute_xcm_in_credit( - origin_location, - message, - hash, - max_weight, - max_weight, - ); - let result = - Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into()); - Self::deposit_event(Event::Attempted { outcome }); - result + let weight_used = + >::execute(origin, message, max_weight)?; + Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into()) } /// Extoll that a particular destination can be communicated with through a particular @@ -933,16 +1052,12 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_xcm_version())] pub fn force_xcm_version( origin: OriginFor, - location: Box, + location: Box, version: XcmVersion, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; let location = *location; - SupportedVersion::::insert( - XCM_VERSION, - LatestVersionedMultiLocation(&location), - version, - ); + SupportedVersion::::insert(XCM_VERSION, LatestVersionedLocation(&location), version); Self::deposit_event(Event::SupportedVersionChanged { location, version }); Ok(()) } @@ -971,10 +1086,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_subscribe_version_notify())] pub fn force_subscribe_version_notify( origin: OriginFor, - location: Box, + location: Box, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; - let location: MultiLocation = + let location: Location = (*location).try_into().map_err(|()| Error::::BadLocation)?; Self::request_version_notify(location).map_err(|e| { match e { @@ -995,10 +1110,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())] pub fn force_unsubscribe_version_notify( origin: OriginFor, - location: Box, + location: Box, ) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; - let location: MultiLocation = + let location: Location = (*location).try_into().map_err(|()| Error::::BadLocation)?; Self::unrequest_version_notify(location).map_err(|e| { match e { @@ -1009,8 +1124,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 @@ -1018,23 +1144,25 @@ pub mod pallet { /// at risk. /// /// - `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. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[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` 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. #[pallet::call_index(8)] #[pallet::weight({ - let maybe_assets: Result = (*assets.clone()).try_into(); - let maybe_dest: Result = (*dest.clone()).try_into(); + 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 + // transfer assets to SA, reanchor them, extend XCM program, and send onward XCM let mut message = Xcm(vec![ SetFeesMode { jit_withdraw: true }, TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } @@ -1046,9 +1174,9 @@ pub mod pallet { })] pub fn limited_reserve_transfer_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, weight_limit: WeightLimit, ) -> DispatchResult { @@ -1070,20 +1198,20 @@ pub mod pallet { /// at risk. /// /// - `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. + /// - `dest`: Destination context for the assets. Will typically be `[Parent, + /// Parachain(..)]` to send from parachain to parachain, or `[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. 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. #[pallet::call_index(9)] #[pallet::weight({ - let maybe_assets: Result = (*assets.clone()).try_into(); - let maybe_dest: Result = (*dest.clone()).try_into(); + 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; @@ -1100,9 +1228,9 @@ pub mod pallet { })] pub fn limited_teleport_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, weight_limit: WeightLimit, ) -> DispatchResult { @@ -1127,12 +1255,228 @@ 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: Location = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: Assets = (*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.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::DestinationReserve => + Self::destination_reserve_fees_instructions( + origin.clone(), + dest.clone(), + fees, + weight_limit, + )?, + TransferType::Teleport => Self::teleport_fees_instructions( + origin.clone(), + dest.clone(), + 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, + ) + } + + /// Claims assets trapped on this pallet because of leftover assets during XCM execution. + /// + /// - `origin`: Anyone can call this extrinsic. + /// - `assets`: The exact assets that were trapped. Use the version to specify what version + /// was the latest when they were trapped. + /// - `beneficiary`: The location/account where the claimed assets will be deposited. + #[pallet::call_index(12)] + #[pallet::weight({ + let assets_version = assets.identify_version(); + let maybe_assets: Result = (*assets.clone()).try_into(); + let maybe_beneficiary: Result = (*beneficiary.clone()).try_into(); + match (maybe_assets, maybe_beneficiary) { + (Ok(assets), Ok(beneficiary)) => { + let ticket: Location = GeneralIndex(assets_version as u128).into(); + let mut message = Xcm(vec![ + ClaimAsset { assets: assets.clone(), ticket }, + DepositAsset { assets: AllCounted(assets.len() as u32).into(), beneficiary }, + ]); + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::claim_assets().saturating_add(w)) + } + _ => Weight::MAX + } + })] + pub fn claim_assets( + origin: OriginFor, + assets: Box, + beneficiary: Box, + ) -> DispatchResult { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", origin_location, assets, beneficiary); + // Extract version from `assets`. + let assets_version = assets.identify_version(); + let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + let number_of_assets = assets.len() as u32; + let beneficiary: Location = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let ticket: Location = GeneralIndex(assets_version as u128).into(); + let mut message = Xcm(vec![ + ClaimAsset { assets, ticket }, + DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary }, + ]); + let weight = + T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; + let mut hash = message.using_encoded(sp_io::hashing::blake2_256); + let outcome = T::XcmExecutor::prepare_and_execute( + origin_location, + message, + &mut hash, + weight, + weight, + ); + outcome.ensure_complete().map_err(|error| { + log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", error); + Error::::LocalExecutionIncomplete + })?; + Ok(()) + } } } /// 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: Asset }, + /// 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; @@ -1141,18 +1485,18 @@ impl QueryHandler for Pallet { /// Attempt to create a new query ID and register it as a query that is yet to respond. fn new_query( - responder: impl Into, + responder: impl Into, timeout: BlockNumberFor, - match_querier: impl Into, + match_querier: impl Into, ) -> Self::QueryId { - Self::do_new_query(responder, None, timeout, match_querier).into() + Self::do_new_query(responder, None, timeout, match_querier) } /// To check the status of the query, use `fn query()` passing the resultant `QueryId` /// value. fn report_outcome( message: &mut Xcm<()>, - responder: impl Into, + responder: impl Into, timeout: Self::BlockNumber, ) -> Result { let responder = responder.into(); @@ -1194,93 +1538,602 @@ impl QueryHandler for Pallet { } impl Pallet { + /// 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: &[Asset], + fee_asset_item: usize, + dest: &Location, + ) -> 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)?; + if idx == fee_asset_item { + fees_transfer_type = Some(transfer_type); + } else { + 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); + } + } + } + // single asset also marked as fee item + if assets.len() == 1 { + assets_transfer_type = fees_transfer_type.clone() + } + Ok(( + fees_transfer_type.ok_or(Error::::Empty)?, + assets_transfer_type.ok_or(Error::::Empty)?, + )) + } + fn do_reserve_transfer_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, weight_limit: WeightLimit, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; - let beneficiary: MultiLocation = + let beneficiary: Location = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; - let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::do_reserve_transfer_assets", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}", + origin_location, dest, beneficiary, assets, fee_asset_item, + ); 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, assets) = value; - let context = T::UniversalLocation::get(); - let fees = assets - .get(fee_asset_item as usize) - .ok_or(Error::::Empty)? - .clone() - .reanchored(&dest, context) - .map_err(|_| Error::::CannotReanchor)?; - let max_assets = assets.len() as u32; - let assets: MultiAssets = assets.into(); - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, - DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, - ]); - let mut message = Xcm(vec![ - SetFeesMode { jit_withdraw: true }, - TransferReserveAsset { assets, dest, xcm }, - ]); - let weight = - T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; - let hash = message.using_encoded(sp_io::hashing::blake2_256); - let outcome = - T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight); - Self::deposit_event(Event::Attempted { outcome }); - Ok(()) + let (origin, assets) = value; + + 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)?; + // 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, + dest, + beneficiary, + assets, + assets_transfer_type, + FeesHandling::Batched { fees }, + weight_limit, + ) } fn do_teleport_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: Box, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, weight_limit: WeightLimit, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; - let beneficiary: MultiLocation = + let beneficiary: Location = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; - let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + let assets: Assets = (*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()); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (origin_location, assets) = value; + for asset in assets.iter() { + let transfer_type = + T::XcmExecutor::determine_for(asset, &dest).map_err(Error::::from)?; + ensure!(transfer_type == TransferType::Teleport, Error::::Filtered); + } + let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); + + Self::build_and_execute_xcm_transfer_type( + origin_location, + dest, + beneficiary, + assets, + TransferType::Teleport, + FeesHandling::Batched { fees }, + weight_limit, + ) + } + + fn build_and_execute_xcm_transfer_type( + origin: Location, + dest: Location, + beneficiary: Location, + assets: Vec, + transfer_type: TransferType, + fees: FeesHandling, + weight_limit: WeightLimit, + ) -> DispatchResult { + log::debug!( + target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ + 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.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, Some(remote)) + }, + TransferType::DestinationReserve => { + let (local, remote) = Self::destination_reserve_transfer_programs( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, Some(remote)) + }, + TransferType::RemoteReserve(reserve) => { + let fees = match fees { + FeesHandling::Batched { fees } => fees, + _ => return Err(Error::::InvalidAssetUnsupportedReserve.into()), + }; + let local = Self::remote_reserve_transfer_program( + origin.clone(), + reserve, + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, None) + }, + TransferType::Teleport => { + let (local, remote) = Self::teleport_assets_program( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, Some(remote)) + }, + }; + let weight = + T::Weigher::weight(&mut local_xcm).map_err(|()| Error::::UnweighableMessage)?; + let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256); + let outcome = T::XcmExecutor::prepare_and_execute( + origin.clone(), + local_xcm, + &mut hash, + weight, + weight, + ); + Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); + outcome.ensure_complete().map_err(|error| { + log::error!( + target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + "XCM execution failed with error {:?}", error + ); + Error::::LocalExecutionIncomplete + })?; + + if let Some(remote_xcm) = remote_xcm { + let (ticket, price) = validate_send::(dest.clone(), remote_xcm.clone()) + .map_err(Error::::from)?; + if origin != Here.into_location() { + Self::charge_fees(origin.clone(), price).map_err(|error| { + log::error!( + target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", + "Unable to charge fee with error {:?}", error + ); + Error::::FeesNotMet + })?; + } + let message_id = T::XcmRouter::deliver(ticket).map_err(Error::::from)?; + + let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id }; + Self::deposit_event(e); + } + Ok(()) + } + + fn add_fees_to_xcm( + dest: Location, + 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: Location, + dest: Location, + fees: Asset, + 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 fees = assets - .get(fee_asset_item as usize) - .ok_or(Error::::Empty)? + let reanchored_fees = fees .clone() - .reanchored(&dest, context) + .reanchored(&dest, &context) + .map_err(|_| Error::::CannotReanchor)?; + + let local_execute_xcm = Xcm(vec![ + // move `fees` to `dest`s local sovereign account + TransferAsset { assets: fees.into(), beneficiary: dest }, + ]); + let xcm_on_dest = Xcm(vec![ + // let (dest) chain know `fees` are in its SA on reserve + ReserveAssetDeposited(reanchored_fees.clone().into()), + // buy exec using `fees` in holding deposited in above instruction + BuyExecution { fees: reanchored_fees, weight_limit }, + ]); + Ok((local_execute_xcm, xcm_on_dest)) + } + + fn local_reserve_transfer_programs( + origin: Location, + dest: Location, + beneficiary: Location, + assets: Vec, + 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 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; + let assets: Assets = assets.into(); + let context = T::UniversalLocation::get(); + let mut reanchored_assets = assets.clone(); + reanchored_assets + .reanchor(&dest, &context) .map_err(|_| Error::::CannotReanchor)?; + + // 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.clone() }, + ]); + // 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, + ]); + // 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)) + } + + fn destination_reserve_fees_instructions( + origin: Location, + dest: Location, + fees: Asset, + 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() + .reanchored(&dest, &context) + .map_err(|_| Error::::CannotReanchor)?; + let fees: Assets = fees.into(); + + let local_execute_xcm = Xcm(vec![ + // withdraw reserve-based fees (derivatives) + WithdrawAsset(fees.clone()), + // burn derivatives + BurnAsset(fees), + ]); + let xcm_on_dest = Xcm(vec![ + // withdraw `fees` from origin chain's sovereign account + WithdrawAsset(reanchored_fees.clone().into()), + // buy exec using `fees` in holding withdrawn in above instruction + BuyExecution { fees: reanchored_fees, weight_limit }, + ]); + Ok((local_execute_xcm, xcm_on_dest)) + } + + fn destination_reserve_transfer_programs( + origin: Location, + dest: Location, + beneficiary: Location, + assets: Vec, + 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 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; + let assets: Assets = assets.into(); + let context = T::UniversalLocation::get(); + let mut reanchored_assets = assets.clone(); + reanchored_assets + .reanchor(&dest, &context) + .map_err(|_| Error::::CannotReanchor)?; + + // 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), + ]); + // 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, + ]); + // 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)) + } + + // function assumes fees and assets have the same remote reserve + fn remote_reserve_transfer_program( + origin: Location, + reserve: Location, + dest: Location, + beneficiary: Location, + assets: Vec, + fees: Asset, + 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 assets: MultiAssets = assets.into(); - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, + let context = T::UniversalLocation::get(); + // we spend up to half of fees for execution on reserve and other half for execution on + // destination + let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?; + // identifies fee item as seen by `reserve` - to be used at reserve chain + let reserve_fees = fees_half_1 + .reanchored(&reserve, &context) + .map_err(|_| Error::::CannotReanchor)?; + // identifies fee item as seen by `dest` - to be used at destination chain + let dest_fees = fees_half_2 + .reanchored(&dest, &context) + .map_err(|_| Error::::CannotReanchor)?; + // identifies `dest` as seen by `reserve` + let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::::CannotReanchor)?; + // xcm to be executed at dest + let xcm_on_dest = Xcm(vec![ + BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, ]); - let mut message = Xcm(vec![ - WithdrawAsset(assets), - SetFeesMode { jit_withdraw: true }, - InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm }, + // xcm to be executed on reserve + let xcm_on_reserve = Xcm(vec![ + BuyExecution { fees: reserve_fees, weight_limit }, + DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, ]); - let weight = - T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; - let hash = message.using_encoded(sp_io::hashing::blake2_256); - let outcome = - T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight); - Self::deposit_event(Event::Attempted { outcome }); - Ok(()) + Ok(Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateReserveWithdraw { + assets: Wild(AllCounted(max_assets)), + reserve, + xcm: xcm_on_reserve, + }, + ])) + } + + fn teleport_fees_instructions( + origin: Location, + dest: Location, + fees: Asset, + weight_limit: WeightLimit, + ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, vec![fees.clone()]); + ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); + + let context = T::UniversalLocation::get(); + let reanchored_fees = fees + .clone() + .reanchored(&dest, &context) + .map_err(|_| Error::::CannotReanchor)?; + + // XcmContext irrelevant in teleports checks + let dummy_context = + XcmContext { origin: None, message_id: Default::default(), topic: None }; + // 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, + &fees, + &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, + &dummy_context, + ); + + let fees: Assets = fees.into(); + let local_execute_xcm = Xcm(vec![ + // withdraw fees + WithdrawAsset(fees.clone()), + // burn fees + BurnAsset(fees), + ]); + let xcm_on_dest = Xcm(vec![ + // (dest) chain receive teleported assets burned on origin chain + ReceiveTeleportedAsset(reanchored_fees.clone().into()), + // buy exec using `fees` in holding received in above instruction + BuyExecution { fees: reanchored_fees, weight_limit }, + ]); + Ok((local_execute_xcm, xcm_on_dest)) + } + + fn teleport_assets_program( + origin: Location, + dest: Location, + beneficiary: Location, + assets: Vec, + fees: FeesHandling, + weight_limit: WeightLimit, + ) -> 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(); + let assets: Assets = 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), + ]); + // 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. + pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error> { + match fees.fun { + Fungible(amount) => { + let fee1 = amount.saturating_div(2); + let fee2 = amount.saturating_sub(fee1); + ensure!(fee1 > 0, Error::::FeesNotMet); + ensure!(fee2 > 0, Error::::FeesNotMet); + Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2)))) + }, + NonFungible(_) => Err(Error::::FeesNotMet), + } } /// Will always make progress, and will do its best not to use much more than `weight_cutoff` @@ -1341,7 +2194,7 @@ impl Pallet { }; while let Some((key, value)) = iter.next() { let (query_id, max_weight, target_xcm_version) = value; - let new_key: MultiLocation = match key.clone().try_into() { + let new_key: Location = match key.clone().try_into() { Ok(k) if target_xcm_version != xcm_version => k, _ => { // We don't early return here since we need to be certain that we @@ -1353,7 +2206,7 @@ impl Pallet { let response = Response::Version(xcm_version); let message = Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]); - let event = match send_xcm::(new_key, message) { + let event = match send_xcm::(new_key.clone(), message) { Ok((message_id, cost)) => { let value = (query_id, max_weight, xcm_version); VersionNotifyTargets::::insert(XCM_VERSION, key, value); @@ -1382,7 +2235,7 @@ impl Pallet { for v in 0..XCM_VERSION { for (old_key, value) in VersionNotifyTargets::::drain_prefix(v) { let (query_id, max_weight, target_xcm_version) = value; - let new_key = match MultiLocation::try_from(old_key.clone()) { + let new_key = match Location::try_from(old_key.clone()) { Ok(k) => k, Err(()) => { Self::deposit_event(Event::NotifyTargetMigrationFail { @@ -1397,7 +2250,7 @@ impl Pallet { }, }; - let versioned_key = LatestVersionedMultiLocation(&new_key); + let versioned_key = LatestVersionedLocation(&new_key); if target_xcm_version == xcm_version { VersionNotifyTargets::::insert(XCM_VERSION, versioned_key, value); weight_used.saturating_accrue(vnt_migrate_weight); @@ -1410,7 +2263,7 @@ impl Pallet { max_weight, querier: None, }]); - let event = match send_xcm::(new_key, message) { + let event = match send_xcm::(new_key.clone(), message) { Ok((message_id, cost)) => { VersionNotifyTargets::::insert( XCM_VERSION, @@ -1443,9 +2296,9 @@ impl Pallet { } /// Request that `dest` informs us of its version. - pub fn request_version_notify(dest: impl Into) -> XcmResult { + pub fn request_version_notify(dest: impl Into) -> XcmResult { let dest = dest.into(); - let versioned_dest = VersionedMultiLocation::from(dest); + let versioned_dest = VersionedLocation::from(dest.clone()); let already = VersionNotifiers::::contains_key(XCM_VERSION, &versioned_dest); ensure!(!already, XcmError::InvalidLocation); let query_id = QueryCounter::::mutate(|q| { @@ -1455,7 +2308,7 @@ impl Pallet { }); // TODO #3735: Correct weight. let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() }; - let (message_id, cost) = send_xcm::(dest, Xcm(vec![instruction]))?; + let (message_id, cost) = send_xcm::(dest.clone(), Xcm(vec![instruction]))?; Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id }); VersionNotifiers::::insert(XCM_VERSION, &versioned_dest, query_id); let query_status = @@ -1465,12 +2318,13 @@ impl Pallet { } /// Request that `dest` ceases informing us of its version. - pub fn unrequest_version_notify(dest: impl Into) -> XcmResult { + pub fn unrequest_version_notify(dest: impl Into) -> XcmResult { let dest = dest.into(); - let versioned_dest = LatestVersionedMultiLocation(&dest); + let versioned_dest = LatestVersionedLocation(&dest); let query_id = VersionNotifiers::::take(XCM_VERSION, versioned_dest) .ok_or(XcmError::InvalidLocation)?; - let (message_id, cost) = send_xcm::(dest, Xcm(vec![UnsubscribeVersion]))?; + let (message_id, cost) = + send_xcm::(dest.clone(), Xcm(vec![UnsubscribeVersion]))?; Self::deposit_event(Event::VersionNotifyUnrequested { destination: dest, cost, @@ -1485,13 +2339,13 @@ impl Pallet { /// are not charged (and instead borne by the chain). pub fn send_xcm( interior: impl Into, - dest: impl Into, + dest: impl Into, mut message: Xcm<()>, ) -> Result { let interior = interior.into(); let dest = dest.into(); let maybe_fee_payer = if interior != Junctions::Here { - message.0.insert(0, DescendOrigin(interior)); + message.0.insert(0, DescendOrigin(interior.clone())); Some(interior.into()) } else { None @@ -1511,10 +2365,10 @@ impl Pallet { /// Create a new expectation of a query response with the querier being here. fn do_new_query( - responder: impl Into, + responder: impl Into, maybe_notify: Option<(u8, u8)>, timeout: BlockNumberFor, - match_querier: impl Into, + match_querier: impl Into, ) -> u64 { QueryCounter::::mutate(|q| { let r = *q; @@ -1556,7 +2410,7 @@ impl Pallet { /// may be put in the overweight queue and need to be manually executed. pub fn report_outcome_notify( message: &mut Xcm<()>, - responder: impl Into, + responder: impl Into, notify: impl Into<::RuntimeCall>, timeout: BlockNumberFor, ) -> Result<(), XcmError> { @@ -1576,10 +2430,10 @@ impl Pallet { /// Attempt to create a new query ID and register it as a query that is yet to respond, and /// which will call a dispatchable when a response happens. pub fn new_notify_query( - responder: impl Into, + responder: impl Into, notify: impl Into<::RuntimeCall>, timeout: BlockNumberFor, - match_querier: impl Into, + match_querier: impl Into, ) -> u64 { let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect( "decode input is output of Call encode; Call guaranteed to have two enums; qed", @@ -1589,13 +2443,13 @@ impl Pallet { /// Note that a particular destination to whom we would like to send a message is unknown /// and queue it for version discovery. - fn note_unknown_version(dest: &MultiLocation) { + fn note_unknown_version(dest: &Location) { log::trace!( target: "xcm::pallet_xcm::note_unknown_version", "XCM version is unknown for destination: {:?}", dest, ); - let versioned_dest = VersionedMultiLocation::from(*dest); + let versioned_dest = VersionedLocation::from(dest.clone()); VersionDiscoveryQueue::::mutate(|q| { if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) { // exists - just bump the count. @@ -1611,18 +2465,60 @@ impl Pallet { /// Fails if: /// - the `assets` are not known on this chain; /// - the `assets` cannot be withdrawn with that location as the Origin. - fn charge_fees(location: MultiLocation, assets: MultiAssets) -> DispatchResult { - T::XcmExecutor::charge_fees(location, assets.clone()) + fn charge_fees(location: Location, assets: Assets) -> DispatchResult { + T::XcmExecutor::charge_fees(location.clone(), assets.clone()) .map_err(|_| Error::::FeesNotMet)?; Self::deposit_event(Event::FeesPaid { paying: location, fees: assets }); Ok(()) } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before and after each state transition of this pallet. + /// + /// ## Invariants + /// + /// All entries stored in the `SupportedVersion` / `VersionNotifiers` / `VersionNotifyTargets` + /// need to be migrated to the `XCM_VERSION`. If they are not, then `CurrentMigration` has to be + /// set. + #[cfg(any(feature = "try-runtime", test))] + pub fn do_try_state() -> Result<(), TryRuntimeError> { + // if migration has been already scheduled, everything is ok and data will be eventually + // migrated + if CurrentMigration::::exists() { + return Ok(()) + } + + // if migration has NOT been scheduled yet, we need to check all operational data + for v in 0..XCM_VERSION { + ensure!( + SupportedVersion::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifiers::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifyTargets::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`" + ) + ); + } + + Ok(()) + } } pub struct LockTicket { sovereign_account: T::AccountId, amount: BalanceOf, - unlocker: MultiLocation, + unlocker: Location, item_index: Option, } @@ -1656,7 +2552,7 @@ impl xcm_executor::traits::Enact for LockTicket { pub struct UnlockTicket { sovereign_account: T::AccountId, amount: BalanceOf, - unlocker: MultiLocation, + unlocker: Location, } impl xcm_executor::traits::Enact for UnlockTicket { @@ -1693,8 +2589,8 @@ impl xcm_executor::traits::Enact for UnlockTicket { pub struct ReduceTicket { key: (u32, T::AccountId, VersionedAssetId), amount: u128, - locker: VersionedMultiLocation, - owner: VersionedMultiLocation, + locker: VersionedLocation, + owner: VersionedLocation, } impl xcm_executor::traits::Enact for ReduceTicket { @@ -1720,9 +2616,9 @@ impl xcm_executor::traits::AssetLock for Pallet { type ReduceTicket = ReduceTicket; fn prepare_lock( - unlocker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + unlocker: Location, + asset: Asset, + owner: Location, ) -> Result, xcm_executor::traits::LockError> { use xcm_executor::traits::LockError::*; let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?; @@ -1735,9 +2631,9 @@ impl xcm_executor::traits::AssetLock for Pallet { } fn prepare_unlock( - unlocker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + unlocker: Location, + asset: Asset, + owner: Location, ) -> Result, xcm_executor::traits::LockError> { use xcm_executor::traits::LockError::*; let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?; @@ -1751,9 +2647,9 @@ impl xcm_executor::traits::AssetLock for Pallet { } fn note_unlockable( - locker: MultiLocation, - asset: MultiAsset, - mut owner: MultiLocation, + locker: Location, + asset: Asset, + mut owner: Location, ) -> Result<(), xcm_executor::traits::LockError> { use xcm_executor::traits::LockError::*; ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted); @@ -1780,9 +2676,9 @@ impl xcm_executor::traits::AssetLock for Pallet { } fn prepare_reduce_unlockable( - locker: MultiLocation, - asset: MultiAsset, - mut owner: MultiLocation, + locker: Location, + asset: Asset, + mut owner: Location, ) -> Result { use xcm_executor::traits::LockError::*; let amount = match asset.fun { @@ -1810,10 +2706,10 @@ impl xcm_executor::traits::AssetLock for Pallet { impl WrapVersion for Pallet { fn wrap_version( - dest: &MultiLocation, + dest: &Location, xcm: impl Into>, ) -> Result, ()> { - SupportedVersion::::get(XCM_VERSION, LatestVersionedMultiLocation(dest)) + Self::get_version_for(dest) .or_else(|| { Self::note_unknown_version(dest); SafeXcmVersion::::get() @@ -1830,6 +2726,12 @@ impl WrapVersion for Pallet { } } +impl GetVersion for Pallet { + fn get_version_for(dest: &Location) -> Option { + SupportedVersion::::get(XCM_VERSION, LatestVersionedLocation(dest)) + } +} + impl VersionChangeNotifier for Pallet { /// Start notifying `location` should the XCM version of this chain change. /// @@ -1840,21 +2742,21 @@ impl VersionChangeNotifier for Pallet { /// If the `location` has an ongoing notification and when this function is called, then an /// error should be returned. fn start( - dest: &MultiLocation, + dest: &Location, query_id: QueryId, max_weight: Weight, _context: &XcmContext, ) -> XcmResult { - let versioned_dest = LatestVersionedMultiLocation(dest); + let versioned_dest = LatestVersionedLocation(dest); let already = VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest); ensure!(!already, XcmError::InvalidLocation); let xcm_version = T::AdvertisedXcmVersion::get(); let response = Response::Version(xcm_version); let instruction = QueryResponse { query_id, response, max_weight, querier: None }; - let (message_id, cost) = send_xcm::(*dest, Xcm(vec![instruction]))?; + let (message_id, cost) = send_xcm::(dest.clone(), Xcm(vec![instruction]))?; Self::deposit_event(Event::::VersionNotifyStarted { - destination: *dest, + destination: dest.clone(), cost, message_id, }); @@ -1866,27 +2768,31 @@ impl VersionChangeNotifier for Pallet { /// Stop notifying `location` should the XCM change. This is a no-op if there was never a /// subscription. - fn stop(dest: &MultiLocation, _context: &XcmContext) -> XcmResult { - VersionNotifyTargets::::remove(XCM_VERSION, LatestVersionedMultiLocation(dest)); + fn stop(dest: &Location, _context: &XcmContext) -> XcmResult { + VersionNotifyTargets::::remove(XCM_VERSION, LatestVersionedLocation(dest)); Ok(()) } /// Return true if a location is subscribed to XCM version changes. - fn is_subscribed(dest: &MultiLocation) -> bool { - let versioned_dest = LatestVersionedMultiLocation(dest); + fn is_subscribed(dest: &Location) -> bool { + let versioned_dest = LatestVersionedLocation(dest); VersionNotifyTargets::::contains_key(XCM_VERSION, versioned_dest) } } impl DropAssets for Pallet { - fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight { + fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight { if assets.is_empty() { return Weight::zero() } - let versioned = VersionedMultiAssets::from(MultiAssets::from(assets)); + let versioned = VersionedAssets::from(Assets::from(assets)); let hash = BlakeTwo256::hash_of(&(&origin, &versioned)); AssetTraps::::mutate(hash, |n| *n += 1); - Self::deposit_event(Event::AssetsTrapped { hash, origin: *origin, assets: versioned }); + Self::deposit_event(Event::AssetsTrapped { + hash, + origin: origin.clone(), + assets: versioned, + }); // TODO #3735: Put the real weight in there. Weight::zero() } @@ -1894,71 +2800,75 @@ impl DropAssets for Pallet { impl ClaimAssets for Pallet { fn claim_assets( - origin: &MultiLocation, - ticket: &MultiLocation, - assets: &MultiAssets, + origin: &Location, + ticket: &Location, + assets: &Assets, _context: &XcmContext, ) -> bool { - let mut versioned = VersionedMultiAssets::from(assets.clone()); - match (ticket.parents, &ticket.interior) { - (0, X1(GeneralIndex(i))) => + let mut versioned = VersionedAssets::from(assets.clone()); + match ticket.unpack() { + (0, [GeneralIndex(i)]) => versioned = match versioned.into_version(*i as u32) { Ok(v) => v, Err(()) => return false, }, - (0, Here) => (), + (0, []) => (), _ => return false, }; - let hash = BlakeTwo256::hash_of(&(origin, versioned.clone())); + let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone())); match AssetTraps::::get(hash) { 0 => return false, 1 => AssetTraps::::remove(hash), n => AssetTraps::::insert(hash, n - 1), } - Self::deposit_event(Event::AssetsClaimed { hash, origin: *origin, assets: versioned }); + Self::deposit_event(Event::AssetsClaimed { + hash, + origin: origin.clone(), + assets: versioned, + }); return true } } impl OnResponse for Pallet { fn expecting_response( - origin: &MultiLocation, + origin: &Location, query_id: QueryId, - querier: Option<&MultiLocation>, + querier: Option<&Location>, ) -> bool { match Queries::::get(query_id) { Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) => - MultiLocation::try_from(responder).map_or(false, |r| origin == &r) && + Location::try_from(responder).map_or(false, |r| origin == &r) && maybe_match_querier.map_or(true, |match_querier| { - MultiLocation::try_from(match_querier).map_or(false, |match_querier| { + Location::try_from(match_querier).map_or(false, |match_querier| { querier.map_or(false, |q| q == &match_querier) }) }), Some(QueryStatus::VersionNotifier { origin: r, .. }) => - MultiLocation::try_from(r).map_or(false, |r| origin == &r), + Location::try_from(r).map_or(false, |r| origin == &r), _ => false, } } fn on_response( - origin: &MultiLocation, + origin: &Location, query_id: QueryId, - querier: Option<&MultiLocation>, + querier: Option<&Location>, response: Response, max_weight: Weight, _context: &XcmContext, ) -> Weight { - let origin = *origin; + let origin = origin.clone(); match (response, Queries::::get(query_id)) { ( Response::Version(v), Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }), ) => { - let origin: MultiLocation = match expected_origin.try_into() { + let origin: Location = match expected_origin.try_into() { Ok(o) if o == origin => o, Ok(o) => { Self::deposit_event(Event::InvalidResponder { - origin, + origin: origin.clone(), query_id, expected_location: Some(o), }); @@ -1966,7 +2876,7 @@ impl OnResponse for Pallet { }, _ => { Self::deposit_event(Event::InvalidResponder { - origin, + origin: origin.clone(), query_id, expected_location: None, }); @@ -1978,15 +2888,14 @@ impl OnResponse for Pallet { if !is_active { Queries::::insert( query_id, - QueryStatus::VersionNotifier { origin: origin.into(), is_active: true }, + QueryStatus::VersionNotifier { + origin: origin.clone().into(), + is_active: true, + }, ); } // We're being notified of a version change. - SupportedVersion::::insert( - XCM_VERSION, - LatestVersionedMultiLocation(&origin), - v, - ); + SupportedVersion::::insert(XCM_VERSION, LatestVersionedLocation(&origin), v); Self::deposit_event(Event::SupportedVersionChanged { location: origin, version: v, @@ -1998,16 +2907,19 @@ impl OnResponse for Pallet { Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }), ) => { if let Some(match_querier) = maybe_match_querier { - let match_querier = match MultiLocation::try_from(match_querier) { + let match_querier = match Location::try_from(match_querier) { Ok(mq) => mq, Err(_) => { - Self::deposit_event(Event::InvalidQuerierVersion { origin, query_id }); + Self::deposit_event(Event::InvalidQuerierVersion { + origin: origin.clone(), + query_id, + }); return Weight::zero() }, }; if querier.map_or(true, |q| q != &match_querier) { Self::deposit_event(Event::InvalidQuerier { - origin, + origin: origin.clone(), query_id, expected_querier: match_querier, maybe_actual_querier: querier.cloned(), @@ -2015,16 +2927,19 @@ impl OnResponse for Pallet { return Weight::zero() } } - let responder = match MultiLocation::try_from(responder) { + let responder = match Location::try_from(responder) { Ok(r) => r, Err(_) => { - Self::deposit_event(Event::InvalidResponderVersion { origin, query_id }); + Self::deposit_event(Event::InvalidResponderVersion { + origin: origin.clone(), + query_id, + }); return Weight::zero() }, }; if origin != responder { Self::deposit_event(Event::InvalidResponder { - origin, + origin: origin.clone(), query_id, expected_location: Some(responder), }); @@ -2052,7 +2967,7 @@ impl OnResponse for Pallet { Self::deposit_event(e); return Weight::zero() } - let dispatch_origin = Origin::Response(origin).into(); + let dispatch_origin = Origin::Response(origin.clone()).into(); match call.dispatch(dispatch_origin) { Ok(post_info) => { let e = Event::Notified { query_id, pallet_index, call_index }; @@ -2090,7 +3005,7 @@ impl OnResponse for Pallet { } }, _ => { - let e = Event::UnexpectedResponse { origin, query_id }; + let e = Event::UnexpectedResponse { origin: origin.clone(), query_id }; Self::deposit_event(e); Weight::zero() }, @@ -2100,7 +3015,7 @@ impl OnResponse for Pallet { impl CheckSuspension for Pallet { fn is_suspended( - _origin: &MultiLocation, + _origin: &Location, _instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -2112,7 +3027,7 @@ impl CheckSuspension for Pallet { /// Ensure that the origin `o` represents an XCM (`Transact`) origin. /// /// Returns `Ok` with the location of the XCM sender or an `Err` otherwise. -pub fn ensure_xcm(o: OuterOrigin) -> Result +pub fn ensure_xcm(o: OuterOrigin) -> Result where OuterOrigin: Into>, { @@ -2125,7 +3040,7 @@ where /// Ensure that the origin `o` represents an XCM response origin. /// /// Returns `Ok` with the location of the responder or an `Err` otherwise. -pub fn ensure_response(o: OuterOrigin) -> Result +pub fn ensure_response(o: OuterOrigin) -> Result where OuterOrigin: Into>, { @@ -2135,46 +3050,50 @@ where } } -/// Filter for `MultiLocation` to find those which represent a strict majority approval of an +/// Filter for `Location` to find those which represent a strict majority approval of an /// identified plurality. /// /// May reasonably be used with `EnsureXcm`. pub struct IsMajorityOfBody(PhantomData<(Prefix, Body)>); -impl, Body: Get> Contains +impl, Body: Get> Contains for IsMajorityOfBody { - fn contains(l: &MultiLocation) -> bool { + fn contains(l: &Location) -> bool { let maybe_suffix = l.match_and_split(&Prefix::get()); matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority()) } } -/// Filter for `MultiLocation` to find those which represent a voice of an identified plurality. +/// Filter for `Location` to find those which represent a voice of an identified plurality. /// /// May reasonably be used with `EnsureXcm`. pub struct IsVoiceOfBody(PhantomData<(Prefix, Body)>); -impl, Body: Get> Contains - for IsVoiceOfBody -{ - fn contains(l: &MultiLocation) -> bool { +impl, Body: Get> Contains for IsVoiceOfBody { + fn contains(l: &Location) -> bool { let maybe_suffix = l.match_and_split(&Prefix::get()); matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice) } } -/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter +/// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and filter /// the `Origin::Xcm` item. -pub struct EnsureXcm(PhantomData); -impl, F: Contains> EnsureOrigin for EnsureXcm +pub struct EnsureXcm(PhantomData<(F, L)>); +impl< + O: OriginTrait + From, + F: Contains, + L: TryFrom + TryInto + Clone, + > EnsureOrigin for EnsureXcm where O::PalletsOrigin: From + TryInto, { - type Success = MultiLocation; + type Success = L; fn try_origin(outer: O) -> Result { outer.try_with_caller(|caller| { caller.try_into().and_then(|o| match o { - Origin::Xcm(location) if F::contains(&location) => Ok(location), + Origin::Xcm(ref location) + if F::contains(&location.clone().try_into().map_err(|_| o.clone().into())?) => + Ok(location.clone().try_into().map_err(|_| o.clone().into())?), Origin::Xcm(location) => Err(Origin::Xcm(location).into()), o => Err(o.into()), }) @@ -2187,15 +3106,14 @@ where } } -/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter +/// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and filter /// the `Origin::Response` item. pub struct EnsureResponse(PhantomData); -impl, F: Contains> EnsureOrigin - for EnsureResponse +impl, F: Contains> EnsureOrigin for EnsureResponse where O::PalletsOrigin: From + TryInto, { - type Success = MultiLocation; + type Success = Location; fn try_origin(outer: O) -> Result { outer.try_with_caller(|caller| { @@ -2212,16 +3130,16 @@ where } } -/// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of +/// A simple passthrough where we reuse the `Location`-typed XCM origin as the inner value of /// this crate's `Origin::Xcm` value. pub struct XcmPassthrough(PhantomData); impl> ConvertOrigin for XcmPassthrough { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); match kind { OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()), diff --git a/polkadot/xcm/pallet-xcm/src/migration.rs b/polkadot/xcm/pallet-xcm/src/migration.rs index ba3cdb5c51edc82712b3bf00feedc8b670365547..018436aa3c93a9291a99820a57118d119f5a0828 100644 --- a/polkadot/xcm/pallet-xcm/src/migration.rs +++ b/polkadot/xcm/pallet-xcm/src/migration.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{Config, Pallet, VersionNotifyTargets}; +use crate::{ + pallet::CurrentMigration, Config, Pallet, VersionMigrationStage, VersionNotifyTargets, +}; use frame_support::{ pallet_prelude::*, traits::{OnRuntimeUpgrade, StorageVersion}, @@ -31,7 +33,7 @@ pub mod v1 { /// checking, the version checking is not complete as it will begin failing after the upgrade is /// enacted on-chain. /// - /// Use experimental [`VersionCheckedMigrateToV1`] instead. + /// Use experimental [`MigrateToV1`] instead. pub struct VersionUncheckedMigrateToV1(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for VersionUncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { @@ -65,7 +67,7 @@ pub mod v1 { /// /// Wrapped in [`frame_support::migrations::VersionedMigration`] so the pre/post checks don't /// begin failing after the upgrade is enacted on-chain. - pub type VersionCheckedMigrateToV1 = frame_support::migrations::VersionedMigration< + pub type MigrateToV1 = frame_support::migrations::VersionedMigration< 0, 1, VersionUncheckedMigrateToV1, @@ -73,3 +75,16 @@ pub mod v1 { ::DbWeight, >; } + +/// When adding a new XCM version, we need to run this migration for `pallet_xcm` to ensure that all +/// previously stored data with subkey prefix `XCM_VERSION-1` (and below) are migrated to the +/// `XCM_VERSION`. +/// +/// NOTE: This migration can be permanently added to the runtime migrations. +pub struct MigrateToLatestXcmVersion(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToLatestXcmVersion { + fn on_runtime_upgrade() -> Weight { + CurrentMigration::::put(VersionMigrationStage::default()); + T::DbWeight::get().writes(1) + } +} diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index a85fe0fa2af8d74451e01d78bbe900ca040799ac..b29562fc833b75a96e924d9f8cb450a58256c057 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -16,8 +16,11 @@ use codec::Encode; use frame_support::{ - construct_runtime, match_types, parameter_types, - traits::{ConstU32, Everything, EverythingBut, Nothing}, + construct_runtime, derive_impl, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstU128, ConstU32, Contains, Equals, Everything, EverythingBut, + Nothing, + }, weights::Weight, }; use frame_system::EnsureRoot; @@ -25,18 +28,20 @@ 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::*; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, - ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, XcmFeesToAccount, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, FixedRateOfFungible, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, + MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount, +}; +use xcm_executor::{ + traits::{Identity, JustTry}, + XcmExecutor, }; -use xcm_executor::XcmExecutor; use crate::{self as pallet_xcm, TestWeightInfo}; @@ -69,7 +74,7 @@ pub mod pallet_test_notifier { pub enum Event { QueryPrepared(QueryId), NotifyQueryPrepared(QueryId), - ResponseReceived(MultiLocation, QueryId, Response), + ResponseReceived(Location, QueryId, Response), } #[pallet::error] @@ -82,7 +87,7 @@ pub mod pallet_test_notifier { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))] - pub fn prepare_new_query(origin: OriginFor, querier: MultiLocation) -> DispatchResult { + pub fn prepare_new_query(origin: OriginFor, querier: Location) -> DispatchResult { let who = ensure_signed(origin)?; let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) @@ -98,10 +103,7 @@ pub mod pallet_test_notifier { #[pallet::call_index(1)] #[pallet::weight(Weight::from_parts(1_000_000, 1_000_000))] - pub fn prepare_new_notify_query( - origin: OriginFor, - querier: MultiLocation, - ) -> DispatchResult { + pub fn prepare_new_notify_query(origin: OriginFor, querier: Location) -> DispatchResult { let who = ensure_signed(origin)?; let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) @@ -135,39 +137,47 @@ pub mod pallet_test_notifier { construct_runtime!( pub enum Test { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config}, - TestNotifier: pallet_test_notifier::{Pallet, Call, Event}, + System: frame_system, + Balances: pallet_balances, + AssetsPallet: pallet_assets, + ParasOrigin: origin, + XcmPallet: pallet_xcm, + TestNotifier: pallet_test_notifier, } ); thread_local! { - pub static SENT_XCM: RefCell)>> = RefCell::new(Vec::new()); + pub static SENT_XCM: RefCell)>> = RefCell::new(Vec::new()); + pub static FAIL_SEND_XCM: RefCell = RefCell::new(false); } -pub(crate) fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> { +pub(crate) fn sent_xcm() -> Vec<(Location, Xcm<()>)> { SENT_XCM.with(|q| (*q.borrow()).clone()) } -pub(crate) fn take_sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> { +pub(crate) fn take_sent_xcm() -> Vec<(Location, Xcm<()>)> { SENT_XCM.with(|q| { let mut r = Vec::new(); std::mem::swap(&mut r, &mut *q.borrow_mut()); r }) } +pub(crate) fn set_send_xcm_artificial_failure(should_fail: bool) { + FAIL_SEND_XCM.with(|q| *q.borrow_mut() = should_fail); +} /// Sender that never returns error. pub struct TestSendXcm; impl SendXcm for TestSendXcm { - type Ticket = (MultiLocation, Xcm<()>); + type Ticket = (Location, Xcm<()>); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>)> { + ) -> SendResult<(Location, Xcm<()>)> { + if FAIL_SEND_XCM.with(|q| *q.borrow()) { + return Err(SendError::Transport("Intentional send failure used in tests")); + } let pair = (dest.take().unwrap(), msg.take().unwrap()); - Ok((pair, MultiAssets::new())) + Ok((pair, Assets::new())) } - fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + fn deliver(pair: (Location, Xcm<()>)) -> Result { let hash = fake_message_hash(&pair.1); SENT_XCM.with(|q| q.borrow_mut().push(pair)); Ok(hash) @@ -176,19 +186,19 @@ impl SendXcm for TestSendXcm { /// Sender that returns error if `X8` junction and stops routing pub struct TestSendXcmErrX8; impl SendXcm for TestSendXcmErrX8 { - type Ticket = (MultiLocation, Xcm<()>); + type Ticket = (Location, Xcm<()>); fn validate( - dest: &mut Option, - msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>)> { - let (dest, msg) = (dest.take().unwrap(), msg.take().unwrap()); - if dest.len() == 8 { + dest: &mut Option, + _: &mut Option>, + ) -> SendResult<(Location, Xcm<()>)> { + if dest.as_ref().unwrap().len() == 8 { + dest.take(); Err(SendError::Transport("Destination location full")) } else { - Ok(((dest, msg), MultiAssets::new())) + Err(SendError::NotApplicable) } } - fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + fn deliver(pair: (Location, Xcm<()>)) -> Result { let hash = fake_message_hash(&pair.1); SENT_XCM.with(|q| q.borrow_mut().push(pair)); Ok(hash) @@ -197,18 +207,18 @@ impl SendXcm for TestSendXcmErrX8 { parameter_types! { pub Para3000: u32 = 3000; - pub Para3000Location: MultiLocation = Parachain(Para3000::get()).into(); + pub Para3000Location: Location = Parachain(Para3000::get()).into(); pub Para3000PaymentAmount: u128 = 1; - pub Para3000PaymentMultiAssets: MultiAssets = MultiAssets::from(MultiAsset::from((Here, Para3000PaymentAmount::get()))); + pub Para3000PaymentAssets: Assets = Assets::from(Asset::from((Here, Para3000PaymentAmount::get()))); } /// Sender only sends to `Parachain(3000)` destination requiring payment. pub struct TestPaidForPara3000SendXcm; impl SendXcm for TestPaidForPara3000SendXcm { - type Ticket = (MultiLocation, Xcm<()>); + type Ticket = (Location, Xcm<()>); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>)> { + ) -> SendResult<(Location, Xcm<()>)> { if let Some(dest) = dest.as_ref() { if !dest.eq(&Para3000Location::get()) { return Err(SendError::NotApplicable) @@ -218,9 +228,9 @@ impl SendXcm for TestPaidForPara3000SendXcm { } let pair = (dest.take().unwrap(), msg.take().unwrap()); - Ok((pair, Para3000PaymentMultiAssets::get())) + Ok((pair, Para3000PaymentAssets::get())) } - fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + fn deliver(pair: (Location, Xcm<()>)) -> Result { let hash = fake_message_hash(&pair.1); SENT_XCM.with(|q| q.borrow_mut().push(pair)); Ok(hash) @@ -231,6 +241,7 @@ parameter_types! { pub const BlockHashCount: u64 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; @@ -274,23 +285,154 @@ impl pallet_balances::Config for Test { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } +#[cfg(feature = "runtime-benchmarks")] +/// 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) -> Location { + Location::new(1, [Parachain(id)]) + } +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = Location; + type AssetIdParameter = Location; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type WeightInfo = (); + type CallbackHandle = (); + type Extra = (); + type RemoveItemsLimit = ConstU32<5>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = XcmBenchmarkHelper; +} + +// This child parachain is a system parachain trusted to teleport native token. +pub const SOME_SYSTEM_PARA: u32 = 1001; + +// This child parachain acts as trusted reserve for its assets in tests. +// USDT allowed to teleport to/from here. +pub const FOREIGN_ASSET_RESERVE_PARA_ID: u32 = 2001; +// Inner junction of reserve asset on `FOREIGN_ASSET_RESERVE_PARA_ID`. +pub const FOREIGN_ASSET_INNER_JUNCTION: Junction = GeneralIndex(1234567); + +// This child parachain acts as trusted reserve for say.. USDC that can be used for fees. +pub const USDC_RESERVE_PARA_ID: u32 = 2002; +// Inner junction of reserve asset on `USDC_RESERVE_PARA_ID`. +pub const USDC_INNER_JUNCTION: Junction = PalletInstance(42); + +// This child parachain is a trusted teleporter for say.. USDT (T from Teleport :)). +// We'll use USDT in tests that teleport fees. +pub const USDT_PARA_ID: u32 = 2003; + +// This child parachain is not configured as trusted reserve or teleport location for any assets. +pub const OTHER_PARA_ID: u32 = 2009; + +// This child parachain is used for filtered/disallowed assets. +pub const FILTERED_PARA_ID: u32 = 2010; + parameter_types! { - pub const RelayLocation: MultiLocation = Here.into_location(); + pub const RelayLocation: Location = Here.into_location(); + pub const NativeAsset: Asset = Asset { + fun: Fungible(10), + id: AssetId(Here.into_location()), + }; + pub SystemParachainLocation: Location = Location::new( + 0, + [Parachain(SOME_SYSTEM_PARA)] + ); + pub ForeignReserveLocation: Location = Location::new( + 0, + [Parachain(FOREIGN_ASSET_RESERVE_PARA_ID)] + ); + pub ForeignAsset: Asset = Asset { + fun: Fungible(10), + id: AssetId(Location::new( + 0, + [Parachain(FOREIGN_ASSET_RESERVE_PARA_ID), FOREIGN_ASSET_INNER_JUNCTION], + )), + }; + pub UsdcReserveLocation: Location = Location::new( + 0, + [Parachain(USDC_RESERVE_PARA_ID)] + ); + pub Usdc: Asset = Asset { + fun: Fungible(10), + id: AssetId(Location::new( + 0, + [Parachain(USDC_RESERVE_PARA_ID), USDC_INNER_JUNCTION], + )), + }; + pub UsdtTeleportLocation: Location = Location::new( + 0, + [Parachain(USDT_PARA_ID)] + ); + pub Usdt: Asset = Asset { + fun: Fungible(10), + id: AssetId(Location::new( + 0, + [Parachain(USDT_PARA_ID)], + )), + }; + pub FilteredTeleportLocation: Location = Location::new( + 0, + [Parachain(FILTERED_PARA_ID)] + ); + pub FilteredTeleportAsset: Asset = Asset { + fun: Fungible(10), + id: AssetId(Location::new( + 0, + [Parachain(FILTERED_PARA_ID)], + )), + }; pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorLocation = Here; pub UnitWeightCost: u64 = 1_000; + pub CheckingAccount: AccountId = XcmPallet::check_account(); } -pub type SovereignAccountOf = - (ChildParachainConvertsVia, AccountId32Aliases); +pub type SovereignAccountOf = ( + ChildParachainConvertsVia, + AccountId32Aliases, + HashedDescription, +); -pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; +pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId< + Location, + Balance, + // Excludes relay/parent chain currency + EverythingBut<(Equals,)>, + Identity, + JustTry, +>; + +pub type AssetTransactors = ( + FungibleAdapter, SovereignAccountOf, AccountId, ()>, + FungiblesAdapter< + AssetsPallet, + ForeignAssetsConvertedConcreteId, + SovereignAccountOf, + AccountId, + NoChecking, + CheckingAccount, + >, +); type LocalOriginConverter = ( SovereignSignedViaLocation, @@ -301,18 +443,29 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (Concrete(RelayLocation::get()), 1, 1); - pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); + pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (AssetId(RelayLocation::get()), 1, 1); + pub TrustedLocal: (AssetFilter, Location) = (All.into(), Here.into()); + pub TrustedSystemPara: (AssetFilter, Location) = (NativeAsset::get().into(), SystemParachainLocation::get()); + pub TrustedUsdt: (AssetFilter, Location) = (Usdt::get().into(), UsdtTeleportLocation::get()); + pub TrustedFilteredTeleport: (AssetFilter, Location) = (FilteredTeleportAsset::get().into(), FilteredTeleportLocation::get()); + pub TeleportUsdtToForeign: (AssetFilter, Location) = (Usdt::get().into(), ForeignReserveLocation::get()); + pub TrustedForeign: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get()); + pub TrustedUsdc: (AssetFilter, Location) = (Usdc::get().into(), UsdcReserveLocation::get()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub XcmFeesTargetAccount: AccountId = AccountId::new([167u8; 32]); } pub const XCM_FEES_NOT_WAIVED_USER_ACCOUNT: [u8; 32] = [37u8; 32]; -match_types! { - pub type XcmFeesNotWaivedLocations: impl Contains = { - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 {network: None, id: XCM_FEES_NOT_WAIVED_USER_ACCOUNT})} - }; + +pub struct XcmFeesNotWaivedLocations; +impl Contains for XcmFeesNotWaivedLocations { + fn contains(location: &Location) -> bool { + matches!( + location.unpack(), + (0, [Junction::AccountId32 { network: None, id: XCM_FEES_NOT_WAIVED_USER_ACCOUNT }]) + ) + } } pub type Barrier = ( @@ -322,14 +475,22 @@ pub type Barrier = ( AllowSubscriptionsFrom, ); +pub type XcmRouter = (TestPaidForPara3000SendXcm, TestSendXcmErrX8, TestSendXcm); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; - type XcmSender = (TestPaidForPara3000SendXcm, TestSendXcm); - type AssetTransactor = LocalAssetTransactor; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; type OriginConverter = LocalOriginConverter; - type IsReserve = (); - type IsTeleporter = Case; + type IsReserve = (Case, Case); + type IsTeleporter = ( + Case, + Case, + Case, + Case, + Case, + ); type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; @@ -342,38 +503,43 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = XcmPallet; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeesToAccount< - Self, + type FeeManager = XcmFeeManagerFromComponents< EverythingBut, - AccountId, - XcmFeesTargetAccount, + XcmFeeToAccount, >; type MessageExporter = (); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type LocalOriginToLocation = SignedToAccountId32; parameter_types! { - pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 3; + pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 4; } -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(1000).into()); +pub struct XcmTeleportFiltered; +impl Contains<(Location, Vec)> for XcmTeleportFiltered { + fn contains(t: &(Location, Vec)) -> bool { + let filtered = FilteredTeleportAsset::get(); + t.1.iter().any(|asset| asset == &filtered) + } } impl pallet_xcm::Config for Test { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; - type XcmRouter = (TestSendXcmErrX8, TestPaidForPara3000SendXcm, TestSendXcm); + type XcmRouter = XcmRouter; type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; + type XcmTeleportFilter = EverythingBut; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type UniversalLocation = UniversalLocation; @@ -381,6 +547,7 @@ impl pallet_xcm::Config for Test { type RuntimeCall = RuntimeCall; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; type AdvertisedXcmVersion = AdvertisedXcmVersion; + type AdminOrigin = EnsureRoot; type TrustedLockers = (); type SovereignAccountOf = AccountId32Aliases<(), AccountId32>; type Currency = Balances; @@ -389,9 +556,6 @@ impl pallet_xcm::Config for Test { type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; - type AdminOrigin = EnsureRoot; } impl origin::Config for Test {} @@ -402,6 +566,103 @@ impl pallet_test_notifier::Config for Test { type RuntimeCall = RuntimeCall; } +#[cfg(feature = "runtime-benchmarks")] +pub struct TestDeliveryHelper; +#[cfg(feature = "runtime-benchmarks")] +impl xcm_builder::EnsureDelivery for TestDeliveryHelper { + fn ensure_successful_delivery( + origin_ref: &Location, + _dest: &Location, + _fee_reason: xcm_executor::traits::FeeReason, + ) -> (Option, Option) { + use xcm_executor::traits::ConvertLocation; + let account = SovereignAccountOf::convert_location(origin_ref).expect("Valid location"); + // Give the existential deposit at least + let balance = ExistentialDeposit::get(); + let _ = >::make_free_balance_be( + &account, balance, + ); + (None, None) + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl super::benchmarking::Config for Test { + type DeliveryHelper = TestDeliveryHelper; + + fn reachable_dest() -> Option { + Some(Parachain(1000).into()) + } + + fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { + Some((NativeAsset::get(), SystemParachainLocation::get())) + } + + fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { + Some(( + Asset { fun: Fungible(10), id: AssetId(Here.into_location()) }, + Parachain(OTHER_PARA_ID).into(), + )) + } + + fn set_up_complex_asset_transfer() -> Option<(Assets, u32, Location, Box)> { + use crate::tests::assets_transfer::{into_assets_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_location) = 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_assets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_location.clone(), fee_amount).into(), + // native asset to transfer (not used for fees) - local reserve + (Location::here(), asset_amount).into(), + ); + // verify initial balances + assert_eq!(Balances::free_balance(&caller), balance); + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), &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!( + AssetsPallet::balance(usdt_id_location, &caller), + usdt_initial_local_amount - fee_amount + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } + + fn get_asset() -> Asset { + Asset { id: AssetId(Location::here()), fun: Fungible(ExistentialDeposit::get()) } + } +} + pub(crate) fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } @@ -410,21 +671,32 @@ pub(crate) fn last_events(n: usize) -> Vec { System::events().into_iter().map(|e| e.event).rev().take(n).rev().collect() } -pub(crate) fn buy_execution(fees: impl Into) -> Instruction { +pub(crate) fn buy_execution(fees: impl Into) -> Instruction { use xcm::latest::prelude::*; BuyExecution { fees: fees.into(), weight_limit: Unlimited } } pub(crate) fn buy_limited_execution( - fees: impl Into, - weight: Weight, + fees: impl Into, + weight_limit: WeightLimit, ) -> Instruction { use xcm::latest::prelude::*; - BuyExecution { fees: fees.into(), weight_limit: Limited(weight) } + BuyExecution { fees: fees.into(), weight_limit } } 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(); @@ -432,7 +704,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 new file mode 100644 index 0000000000000000000000000000000000000000..27be5cce145859a999c9e45ec2ca4aa32de3c641 --- /dev/null +++ b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs @@ -0,0 +1,2606 @@ +// 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 . + +#![cfg(test)] + +use crate::{ + mock::*, + tests::{ALICE, BOB, FEE_AMOUNT, INITIAL_BALANCE, SEND_AMOUNT}, + DispatchResult, OriginFor, +}; +use frame_support::{ + assert_ok, + traits::{tokens::fungibles::Inspect, Currency}, + weights::Weight, +}; +use polkadot_parachain_primitives::primitives::Id as ParaId; +use sp_runtime::{traits::AccountIdConversion, DispatchError, ModuleError}; +use xcm::prelude::*; +use xcm_executor::traits::ConvertLocation; + +// Helper function to deduplicate testing different teleport types. +fn do_test_and_verify_teleport_assets( + origin_location: Location, + expected_beneficiary: Location, + call: Call, + expected_weight_limit: WeightLimit, +) { + let balances = vec![ + (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() * 2; + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); + // call extrinsic + call(); + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), + ClearOrigin, + buy_limited_execution((Here, SEND_AMOUNT), expected_weight_limit), + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: expected_beneficiary + }, + ]), + )] + ); + 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_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +/// Test `teleport_assets` +/// +/// Asserts that the sender's balance is decreased as a result of execution of +/// local effects. +#[test] +fn teleport_assets_works() { + let origin_location: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = AccountId32 { network: None, id: BOB.into() }.into(); + do_test_and_verify_teleport_assets( + origin_location.clone(), + beneficiary.clone(), + || { + assert_ok!(XcmPallet::teleport_assets( + RuntimeOrigin::signed(ALICE), + Box::new(RelayLocation::get().into()), + Box::new(beneficiary.into()), + Box::new((Here, SEND_AMOUNT).into()), + 0, + )); + }, + Unlimited, + ); +} + +/// Test `limited_teleport_assets` +/// +/// Asserts that the sender's balance is decreased as a result of execution of +/// local effects. +#[test] +fn limited_teleport_assets_works() { + let origin_location: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = 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.clone(), + beneficiary.clone(), + || { + assert_ok!(XcmPallet::limited_teleport_assets( + RuntimeOrigin::signed(ALICE), + Box::new(RelayLocation::get().into()), + Box::new(beneficiary.into()), + Box::new((Here, SEND_AMOUNT).into()), + 0, + weight_limit, + )); + }, + expected_weight_limit, + ); +} + +/// `limited_teleport_assets` should fail for filtered assets +#[test] +fn limited_teleport_filtered_assets_disallowed() { + let beneficiary: Location = AccountId32 { network: None, id: BOB.into() }.into(); + new_test_ext_with_balances(vec![(ALICE, INITIAL_BALANCE)]).execute_with(|| { + let result = XcmPallet::limited_teleport_assets( + RuntimeOrigin::signed(ALICE), + Box::new(FilteredTeleportLocation::get().into()), + Box::new(beneficiary.into()), + Box::new(FilteredTeleportAsset::get().into()), + 0, + Unlimited, + ); + assert_eq!( + result, + Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered") + })) + ); + }); +} + +/// Test `reserve_transfer_assets_with_paid_router_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 that XCM router fees (`SendXcm::validate` -> `Assets`) are withdrawn from correct +/// user account and deposited to a correct target account (`XcmFeesTargetAccount`). +#[test] +fn reserve_transfer_assets_with_paid_router_works() { + let user_account = AccountId::from(XCM_FEES_NOT_WAIVED_USER_ACCOUNT); + let paid_para_id = Para3000::get(); + let balances = vec![ + (user_account.clone(), INITIAL_BALANCE), + (ParaId::from(paid_para_id).into_account_truncating(), INITIAL_BALANCE), + (XcmFeesTargetAccount::get(), INITIAL_BALANCE), + ]; + new_test_ext_with_balances(balances).execute_with(|| { + let xcm_router_fee_amount = Para3000PaymentAmount::get(); + let weight = BaseXcmWeight::get(); + let dest: Location = + Junction::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()), + Box::new(Parachain(paid_para_id).into()), + Box::new(dest.clone().into()), + Box::new((Here, SEND_AMOUNT).into()), + 0, + )); + + // XCM_FEES_NOT_WAIVED_USER_ACCOUNT spent amount + assert_eq!( + Balances::free_balance(user_account), + INITIAL_BALANCE - SEND_AMOUNT - xcm_router_fee_amount + ); + + // Destination account (parachain account) has amount + let para_acc: AccountId = ParaId::from(paid_para_id).into_account_truncating(); + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); + + // XcmFeesTargetAccount where should lend xcm_router_fee_amount + assert_eq!( + Balances::free_balance(XcmFeesTargetAccount::get()), + INITIAL_BALANCE + xcm_router_fee_amount + ); + + let dest_para: Location = Parachain(paid_para_id).into(); + assert_eq!( + sent_xcm(), + vec![( + dest_para, + Xcm(vec![ + ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), + ClearOrigin, + buy_execution((Parent, SEND_AMOUNT)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, + ]), + )] + ); + let mut last_events = last_events(5).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + // balances events + last_events.next().unwrap(); + last_events.next().unwrap(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: dest, + fees: Para3000PaymentAssets::get(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +pub(crate) fn set_up_foreign_asset( + reserve_para_id: u32, + inner_junction: Option, + benficiary: AccountId, + initial_amount: u128, + is_sufficient: bool, +) -> (Location, AccountId, Location) { + let reserve_location = + RelayLocation::get().pushed_with_interior(Parachain(reserve_para_id)).unwrap(); + let reserve_sovereign_account = + SovereignAccountOf::convert_location(&reserve_location).unwrap(); + + let foreign_asset_id_location = if let Some(junction) = inner_junction { + reserve_location.clone().pushed_with_interior(junction).unwrap() + } else { + reserve_location.clone() + }; + + // create sufficient (to be used as fees as well) foreign asset (0 total issuance) + assert_ok!(AssetsPallet::force_create( + RuntimeOrigin::root(), + foreign_asset_id_location.clone(), + BOB, + is_sufficient, + 1 + )); + // this asset should have been teleported/reserve-transferred in, but for this test we just + // mint it locally. + assert_ok!(AssetsPallet::mint( + RuntimeOrigin::signed(BOB), + foreign_asset_id_location.clone(), + benficiary, + initial_amount + )); + + (reserve_location, reserve_sovereign_account, foreign_asset_id_location) +} + +// Helper function that provides correct `fee_index` after `sort()` done by +// `vec![Asset, Asset].into()`. +pub(crate) fn into_assets_checked( + fee_asset: Asset, + transfer_asset: Asset, +) -> (Assets, usize, Asset, Asset) { + let assets: Assets = vec![fee_asset.clone(), transfer_asset.clone()].into(); + let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 }; + (assets, fee_index, fee_asset, transfer_asset) +} + +/// 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. +/// +/// 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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::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.clone(); + let dest: Location = Parachain(OTHER_PARA_ID).into(); + + new_test_ext_with_balances(balances).execute_with(|| { + let weight = BaseXcmWeight::get(); + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); + // call extrinsic + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.clone().into()), + Box::new(beneficiary.clone().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 + let para_acc: AccountId = ParaId::from(OTHER_PARA_ID).into_account_truncating(); + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), + ClearOrigin, + buy_limited_execution((Parent, SEND_AMOUNT), expected_weight_limit), + DepositAsset { + assets: AllCounted(1).into(), + beneficiary: expected_beneficiary.clone() + }, + ]), + )] + ); + let mut last_events = last_events(3).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +/// 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. +/// +/// 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 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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (reserve_location, reserve_sovereign_account, foreign_asset_id_location) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is reserve location (no teleport trust) + let dest = reserve_location; + + let (assets, fee_index, fee_asset, xfer_asset) = into_assets_checked( + // native asset for fee - local reserve + (Location::here(), FEE_AMOUNT).into(), + // foreign asset to transfer - destination reserve + (foreign_asset_id_location.clone(), 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!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.clone().into()), + Box::new(beneficiary.clone().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 { used: weight } + }) + ); + + // Alice spent (transferred) amount + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_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 used as fee to balances + assert_eq!(Balances::free_balance(reserve_sovereign_account.clone()), FEE_AMOUNT); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), reserve_sovereign_account), + 0 + ); + // Verify total and active issuance of foreign BLA have decreased (burned on + // reserve-withdraw) + let expected_issuance = foreign_initial_amount - SEND_AMOUNT; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), 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((Parent, FEE_AMOUNT).into()), + buy_limited_execution(expected_fee, Unlimited), + WithdrawAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary: beneficiary.clone() }, + ]) + )] + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +/// 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. +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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (_, _, foreign_asset_id_location) = set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is OTHER_PARA_ID (foreign asset needs to go through its reserve + // chain) + let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); + + let (assets, fee_index, _, _) = into_assets_checked( + // native asset for fee - local reserve + (Location::here(), FEE_AMOUNT).into(), + // foreign asset to transfer - remote reserve + (foreign_asset_id_location.clone(), SEND_AMOUNT).into(), + ); + + // balances checks before + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // try 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); + + // Alice transferred nothing + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + // Alice spent native asset for fees + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Verify total and active issuance of foreign BLA asset have decreased (burned on + // reserve-withdraw) + let expected_issuance = foreign_initial_amount; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_issuance); + }); +} + +/// 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. +/// +/// 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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 142; + let (usdc_reserve_location, usdc_chain_sovereign_account, usdc_id_location) = + set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // native assets transfer to fee reserve location (no teleport trust) + let dest = usdc_reserve_location; + + let (assets, fee_index, fee_asset, xfer_asset) = into_assets_checked( + // usdc for fees (is sufficient on local chain too) - destination reserve + (usdc_id_location.clone(), FEE_AMOUNT).into(), + // native asset to transfer (not used for fees) - local reserve + (Location::here(), 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!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.clone().into()), + Box::new(beneficiary.clone().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!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location.clone(), + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + + // Alice spent (fees) amount + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount - FEE_AMOUNT + ); + // Alice used native asset for transfer + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); + // Sovereign account of dest parachain holds `SEND_AMOUNT` native asset in local reserve + assert_eq!(Balances::free_balance(usdc_chain_sovereign_account.clone()), SEND_AMOUNT); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), usdc_chain_sovereign_account), + 0 + ); + // Verify total and active issuance of USDC have decreased (burned on reserve-withdraw) + let expected_issuance = usdc_initial_local_amount - FEE_AMOUNT; + assert_eq!(AssetsPallet::total_issuance(usdc_id_location.clone()), expected_issuance); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location), expected_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + // fees are being sent through destination-reserve transfer because fee reserve + // is destination chain + WithdrawAsset(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + // transfer is through local-reserve transfer because `assets` (native asset) + // have local reserve + ReserveAssetDeposited(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + }); +} + +/// Test `transfer_assets` with local asset reserve and destination fee reserve. +#[test] +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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::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 + let foreign_initial_amount = 142; + let (reserve_location, reserve_sovereign_account, foreign_asset_id_location) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + true, + ); + + // transfer destination is reserve location + let dest = reserve_location; + let assets: Assets = vec![(foreign_asset_id_location.clone(), SEND_AMOUNT).into()].into(); + let fee_index = 0; + + // reanchor according to test-case + let mut expected_assets = assets.clone(); + expected_assets.reanchor(&dest, &UniversalLocation::get()).unwrap(); + + // balances checks before + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.clone().into()), + Box::new(beneficiary.clone().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(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location.clone(), + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + + // Alice spent (transferred) amount + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount - SEND_AMOUNT + ); + // Alice's native asset balance is untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Reserve sovereign account has same balances + assert_eq!(Balances::free_balance(reserve_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), reserve_sovereign_account), + 0 + ); + // Verify total and active issuance of foreign BLA have decreased (burned on + // reserve-withdraw) + let expected_issuance = foreign_initial_amount - SEND_AMOUNT; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + Parachain(FOREIGN_ASSET_RESERVE_PARA_ID).into(), + Xcm(vec![ + WithdrawAsset(expected_assets.clone()), + ClearOrigin, + buy_limited_execution(expected_assets.get(0).unwrap().clone(), Unlimited), + DepositAsset { assets: AllCounted(1).into(), beneficiary: beneficiary.clone() }, + ]), + )] + ); + }); +} + +/// 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. +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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 42; + let (usdc_chain, _, usdc_id_location) = set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (_, _, foreign_asset_id_location) = set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is USDC chain (foreign asset BLA needs to go through its separate + // reserve chain) + let dest = usdc_chain; + + let (assets, fee_index, _, _) = into_assets_checked( + // USDC for fees (is sufficient on local chain too) - destination reserve + (usdc_id_location.clone(), FEE_AMOUNT).into(), + // foreign asset to transfer (not used for fees) - remote reserve + (foreign_asset_id_location.clone(), SEND_AMOUNT).into(), + ); + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_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); + + // Alice native asset untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + let expected_usdc_issuance = usdc_initial_local_amount; + assert_eq!(AssetsPallet::total_issuance(usdc_id_location.clone()), expected_usdc_issuance); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location.clone()), expected_usdc_issuance); + let expected_bla_issuance = foreign_initial_amount; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_bla_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_bla_issuance); + }); +} + +/// 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. +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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 142; + let (_, usdc_chain_sovereign_account, usdc_id_location) = set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // transfer destination is some other parachain != fee reserve location (no teleport trust) + let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); + let dest_sovereign_account = SovereignAccountOf::convert_location(&dest).unwrap(); + + let (assets, fee_index, _, _) = into_assets_checked( + // USDC for fees (is sufficient on local chain too) - remote reserve + (usdc_id_location.clone(), FEE_AMOUNT).into(), + // native asset to transfer (not used for fees) - local reserve + (Location::here(), SEND_AMOUNT).into(), + ); + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_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); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Sovereign account of reserve parachain is unchanged + assert_eq!(Balances::free_balance(usdc_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), usdc_chain_sovereign_account), + 0 + ); + assert_eq!(Balances::free_balance(dest_sovereign_account), 0); + let expected_usdc_issuance = usdc_initial_local_amount; + assert_eq!(AssetsPallet::total_issuance(usdc_id_location.clone()), expected_usdc_issuance); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location), expected_usdc_issuance); + }); +} + +/// 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. +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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 42; + let (_, usdc_chain_sovereign_account, usdc_id_location) = set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (reserve_location, foreign_sovereign_account, foreign_asset_id_location) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is asset reserve location + let dest = reserve_location; + let dest_sovereign_account = foreign_sovereign_account; + + let (assets, fee_index, _, _) = into_assets_checked( + // USDC for fees (is sufficient on local chain too) - remote reserve + (usdc_id_location.clone(), FEE_AMOUNT).into(), + // foreign asset to transfer (not used for fees) - destination reserve + (foreign_asset_id_location.clone(), SEND_AMOUNT).into(), + ); + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_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); + // Alice native asset untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + assert_eq!(Balances::free_balance(usdc_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), usdc_chain_sovereign_account), + 0 + ); + assert_eq!(Balances::free_balance(dest_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), dest_sovereign_account), + 0 + ); + let expected_usdc_issuance = usdc_initial_local_amount; + assert_eq!(AssetsPallet::total_issuance(usdc_id_location.clone()), expected_usdc_issuance); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location.clone()), expected_usdc_issuance); + let expected_bla_issuance = foreign_initial_amount; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_bla_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_bla_issuance); + }); +} + +/// 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. +/// +/// ```nocompile +/// | chain `A` | chain `C` | chain `B` +/// | Here (source) | USDC_RESERVE_PARA_ID | OTHER_PARA_ID (destination) +/// | | `fees` reserve | +/// | | `assets` reserve | +/// | +/// | 1. `A` executes `InitiateReserveWithdraw(both)` dest `C` +/// | -----------------> `C` executes `DepositReserveAsset(both)` dest `B` +/// | --------------------------> `DepositAsset(both)` +/// ``` +/// +/// 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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 142; + let (usdc_chain, usdc_chain_sovereign_account, usdc_id_location) = set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // transfer destination is some other parachain + let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); + + let assets: Assets = vec![(usdc_id_location.clone(), SEND_AMOUNT).into()].into(); + let fee_index = 0; + + // reanchor according to test-case + let context = UniversalLocation::get(); + let expected_dest_on_reserve = dest.clone().reanchored(&usdc_chain, &context).unwrap(); + 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(); + let expected_fee_on_reserve = fees_half_1.reanchored(&usdc_chain, &context).unwrap(); + let expected_fee_on_dest = fees_half_2.reanchored(&dest, &context).unwrap(); + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.clone().into()), + Box::new(beneficiary.clone().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 + } + + assert!(matches!( + last_event(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete { .. } }) + )); + + // Alice spent (transferred) amount + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount - SEND_AMOUNT + ); + // Alice's native asset balance is untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Destination account (parachain account) has expected (same) balances + assert_eq!(Balances::free_balance(usdc_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), usdc_chain_sovereign_account), + 0 + ); + // Verify total and active issuance of USDC have decreased (burned on reserve-withdraw) + let expected_usdc_issuance = usdc_initial_local_amount - SEND_AMOUNT; + assert_eq!(AssetsPallet::total_issuance(usdc_id_location.clone()), expected_usdc_issuance); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location.clone()), expected_usdc_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + // first message sent to reserve chain + usdc_chain, + Xcm(vec![ + WithdrawAsset(expected_assets_on_reserve), + ClearOrigin, + BuyExecution { fees: expected_fee_on_reserve, weight_limit: Unlimited }, + DepositReserveAsset { + assets: Wild(AllCounted(1)), + // final destination is `dest` as seen by `reserve` + dest: expected_dest_on_reserve, + // message sent onward to `dest` + xcm: Xcm(vec![ + buy_limited_execution(expected_fee_on_dest, Unlimited), + DepositAsset { assets: AllCounted(1).into(), beneficiary } + ]) + } + ]) + )], + ); + }); +} + +/// 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. +/// +/// 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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (usdt_chain, usdt_chain_sovereign_account, usdt_id_location) = + 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; + + let (assets, fee_index, fee_asset, xfer_asset) = into_assets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_location.clone(), FEE_AMOUNT).into(), + // native asset to transfer (not used for fees) - local reserve + (Location::here(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), 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.clone().into()), + Box::new(beneficiary.clone().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!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location.clone(), + fees: Assets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + // Alice spent (fees) amount + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount - FEE_AMOUNT + ); + // Alice used native asset for transfer + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); + // Sovereign account of dest parachain holds `SEND_AMOUNT` native asset in local reserve + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), SEND_AMOUNT); + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), usdt_chain_sovereign_account), + 0 + ); + // Verify total and active issuance have decreased (teleported) + let expected_usdt_issuance = usdt_initial_local_amount - FEE_AMOUNT; + assert_eq!(AssetsPallet::total_issuance(usdt_id_location.clone()), expected_usdt_issuance); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location), expected_usdt_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + // fees are teleported to destination chain + ReceiveTeleportedAsset(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + // transfer is through local-reserve transfer because `assets` (native + // asset) have local reserve + ReserveAssetDeposited(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + }); +} + +/// 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. +/// +/// 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 origin_location: Location = + Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (_, usdt_chain_sovereign_account, usdt_id_location) = + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); + + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (reserve_location, foreign_sovereign_account, foreign_asset_id_location) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is asset reserve location + let dest = reserve_location; + let dest_sovereign_account = foreign_sovereign_account; + + let (assets, fee_index, fee_asset, xfer_asset) = into_assets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_location.clone(), FEE_AMOUNT).into(), + // foreign asset to transfer (not used for fees) - destination reserve + (foreign_asset_id_location.clone(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), 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.clone().into()), + Box::new(beneficiary.clone().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 { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location.clone(), + fees: Assets::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!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount - FEE_AMOUNT + ); + // Alice transferred BLA + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount - SEND_AMOUNT + ); + // Verify balances of USDT reserve parachain + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), usdt_chain_sovereign_account), + 0 + ); + // Verify balances of transferred-asset reserve parachain + assert_eq!(Balances::free_balance(dest_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), dest_sovereign_account), + 0 + ); + // Verify total and active issuance of USDT have decreased (teleported) + let expected_usdt_issuance = usdt_initial_local_amount - FEE_AMOUNT; + assert_eq!(AssetsPallet::total_issuance(usdt_id_location.clone()), expected_usdt_issuance); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location.clone()), 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 - SEND_AMOUNT; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_bla_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_bla_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + // fees are teleported to destination chain + ReceiveTeleportedAsset(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + // assets are withdrawn from origin's local SA + WithdrawAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + }); +} + +/// 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. +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: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (usdt_chain, usdt_chain_sovereign_account, usdt_id_location) = + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); + + // create non-sufficient foreign asset BLA + let foreign_initial_amount = 142; + let (_, reserve_sovereign_account, foreign_asset_id_location) = set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + false, + ); + + // transfer destination is USDT chain (foreign asset needs to go through its reserve chain) + let dest = usdt_chain; + + let (assets, fee_index, _, _) = into_assets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_location.clone(), FEE_AMOUNT).into(), + // foreign asset to transfer (not used for fees) - remote reserve + (foreign_asset_id_location.clone(), SEND_AMOUNT).into(), + ); + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // try 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); + // Alice native asset untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount + ); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), ALICE), + foreign_initial_amount + ); + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), usdt_chain_sovereign_account), + 0 + ); + assert_eq!(Balances::free_balance(reserve_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), reserve_sovereign_account), + 0 + ); + let expected_usdt_issuance = usdt_initial_local_amount; + assert_eq!(AssetsPallet::total_issuance(usdt_id_location.clone()), expected_usdt_issuance); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location.clone()), expected_usdt_issuance); + let expected_bla_issuance = foreign_initial_amount; + assert_eq!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_bla_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), expected_bla_issuance); + }); +} + +/// 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_disallowed() { + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (usdt_chain, usdt_chain_sovereign_account, usdt_id_location) = + 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; + let assets: Assets = vec![(usdt_id_location.clone(), FEE_AMOUNT).into()].into(); + let fee_index = 0; + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let res = XcmPallet::limited_reserve_transfer_assets( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!( + res, + Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered") + })) + ); + // Alice native asset is still same + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Alice USDT balance is still same + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount + ); + // No USDT moved to sovereign account of reserve parachain + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), usdt_chain_sovereign_account), + 0 + ); + // Verify total and active issuance of USDT are still the same + assert_eq!( + AssetsPallet::total_issuance(usdt_id_location.clone()), + usdt_initial_local_amount + ); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location), usdt_initial_local_amount); + }); +} + +/// Test `transfer_assets` with teleportable fee that is filtered - should fail. +#[test] +fn transfer_assets_with_filtered_teleported_fee_disallowed() { + let beneficiary: Location = AccountId32 { network: None, id: BOB.into() }.into(); + new_test_ext_with_balances(vec![(ALICE, INITIAL_BALANCE)]).execute_with(|| { + let (assets, fee_index, _, _) = into_assets_checked( + // FilteredTeleportAsset for fees - teleportable but filtered + FilteredTeleportAsset::get().into(), + // native asset to transfer (not used for fees) - local reserve + (Location::here(), SEND_AMOUNT).into(), + ); + let result = XcmPallet::transfer_assets( + RuntimeOrigin::signed(ALICE), + Box::new(FilteredTeleportLocation::get().into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!( + result, + Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered") + })) + ); + }); +} + +/// Test failure to complete execution of local XCM instructions reverts intermediate side-effects. +/// +/// Extrinsic will execute XCM to withdraw & burn reserve-based assets, then fail sending XCM to +/// reserve chain for releasing reserve assets. Assert that the previous instructions (withdraw & +/// burn) effects are reverted. +#[test] +fn intermediary_error_reverts_side_effects() { + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset USDC + let usdc_initial_local_amount = 142; + let (_, usdc_chain_sovereign_account, usdc_id_location) = set_up_foreign_asset( + USDC_RESERVE_PARA_ID, + Some(USDC_INNER_JUNCTION), + ALICE, + usdc_initial_local_amount, + true, + ); + + // transfer destination is some other parachain + let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); + + let assets: Assets = vec![(usdc_id_location.clone(), SEND_AMOUNT).into()].into(); + let fee_index = 0; + + // balances checks before + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // introduce artificial error in sending outbound XCM + set_send_xcm_artificial_failure(true); + + // do the transfer - extrinsic should completely fail on xcm send failure + assert!(XcmPallet::limited_reserve_transfer_assets( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ) + .is_err()); + + // Alice no changes + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), ALICE), + usdc_initial_local_amount + ); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Destination account (parachain account) no changes + assert_eq!(Balances::free_balance(usdc_chain_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(usdc_id_location.clone(), usdc_chain_sovereign_account), + 0 + ); + // Verify total and active issuance of USDC has not changed + assert_eq!( + AssetsPallet::total_issuance(usdc_id_location.clone()), + usdc_initial_local_amount + ); + assert_eq!(AssetsPallet::active_issuance(usdc_id_location), usdc_initial_local_amount); + // Verify no XCM program sent + 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: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = 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_location) = + 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_assets_checked( + // native asset for fee - local reserve + (Location::here(), FEE_AMOUNT).into(), + // USDT to transfer - destination reserve + (usdt_id_location.clone(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), 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.clone().into()), + Box::new(beneficiary.clone().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 { used: weight } + }) + ); + + // Alice spent (transferred) amount + assert_eq!( + AssetsPallet::balance(usdt_id_location.clone(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), 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!(AssetsPallet::total_issuance(usdt_id_location.clone()), expected_issuance); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location), 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: Assets::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: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: Location = 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_location) = + 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_location) = + 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_assets_checked( + // foreign asset BLA used for fees - destination reserve + (foreign_asset_id_location.clone(), FEE_AMOUNT).into(), + // USDT to transfer - teleported + (usdt_id_location.clone(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), 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.clone().into()), + Box::new(beneficiary.clone().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 { used: weight } + }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: Assets::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!( + AssetsPallet::balance(usdt_id_location.clone(), ALICE), + usdt_initial_local_amount - SEND_AMOUNT + ); + // Alice transferred BLA + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), 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!( + AssetsPallet::balance(usdt_id_location.clone(), usdt_chain_sovereign_account), + 0 + ); + // Verify balances of transferred-asset reserve parachain + assert_eq!(Balances::free_balance(dest_sovereign_account.clone()), 0); + assert_eq!( + AssetsPallet::balance(foreign_asset_id_location.clone(), 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!(AssetsPallet::total_issuance(usdt_id_location.clone()), expected_usdt_issuance); + assert_eq!(AssetsPallet::active_issuance(usdt_id_location), 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!( + AssetsPallet::total_issuance(foreign_asset_id_location.clone()), + expected_bla_issuance + ); + assert_eq!(AssetsPallet::active_issuance(foreign_asset_id_location), 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.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs similarity index 54% rename from polkadot/xcm/pallet-xcm/src/tests.rs rename to polkadot/xcm/pallet-xcm/src/tests/mod.rs index d267eece2c0478a404af2dc729f8d7b083dc770d..13022d9a8b1f69f528393ee8ac16e3d62151bae9 100644 --- a/polkadot/xcm/pallet-xcm/src/tests.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -14,13 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +#![cfg(test)] + +pub(crate) mod assets_transfer; + use crate::{ - mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries, - QueryStatus, VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, - VersionNotifyTargets, + mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, + ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus, + VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, + WeightInfo, }; use frame_support::{ - assert_noop, assert_ok, + assert_err_ignore_postinfo, assert_noop, assert_ok, traits::{Currency, Hooks}, weights::Weight, }; @@ -35,19 +40,21 @@ use xcm_executor::{ const ALICE: AccountId = AccountId::new([0u8; 32]); const BOB: AccountId = AccountId::new([1u8; 32]); -const PARA_ID: u32 = 2000; const INITIAL_BALANCE: u128 = 100; const SEND_AMOUNT: u128 = 10; +const FEE_AMOUNT: u128 = 2; #[test] fn report_outcome_notify_works() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); - let mut message = - Xcm(vec![TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender }]); + let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let mut message = Xcm(vec![TransferAsset { + assets: (Here, SEND_AMOUNT).into(), + beneficiary: sender.clone(), + }]); let call = pallet_test_notifier::Call::notification_received { query_id: 0, response: Default::default(), @@ -56,7 +63,7 @@ fn report_outcome_notify_works() { new_test_ext_with_balances(balances).execute_with(|| { XcmPallet::report_outcome_notify( &mut message, - Parachain(PARA_ID).into_location(), + Parachain(OTHER_PARA_ID).into_location(), notify, 100, ) @@ -72,12 +79,12 @@ fn report_outcome_notify_works() { TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender }, ]) ); - let querier: MultiLocation = Here.into(); + let querier: Location = Here.into(); let status = QueryStatus::Pending { - responder: MultiLocation::from(Parachain(PARA_ID)).into(), - maybe_notify: Some((4, 2)), + responder: Location::from(Parachain(OTHER_PARA_ID)).into(), + maybe_notify: Some((5, 2)), timeout: 100, - maybe_match_querier: Some(querier.into()), + maybe_match_querier: Some(querier.clone().into()), }; assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); @@ -87,25 +94,26 @@ fn report_outcome_notify_works() { max_weight: Weight::from_parts(1_000_000, 1_000_000), querier: Some(querier), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID), + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(OTHER_PARA_ID), message, - hash, + &mut hash, Weight::from_parts(1_000_000_000, 1_000_000_000), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) }); assert_eq!( last_events(2), vec![ RuntimeEvent::TestNotifier(pallet_test_notifier::Event::ResponseReceived( - Parachain(PARA_ID).into(), + Parachain(OTHER_PARA_ID).into(), 0, Response::ExecutionResult(None), )), RuntimeEvent::XcmPallet(crate::Event::Notified { query_id: 0, - pallet_index: 4, + pallet_index: 5, call_index: 2 }), ] @@ -118,13 +126,16 @@ fn report_outcome_notify_works() { fn report_outcome_works() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); - let mut message = - Xcm(vec![TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender }]); + let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into(); + let mut message = Xcm(vec![TransferAsset { + assets: (Here, SEND_AMOUNT).into(), + beneficiary: sender.clone(), + }]); new_test_ext_with_balances(balances).execute_with(|| { - XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into_location(), 100).unwrap(); + XcmPallet::report_outcome(&mut message, Parachain(OTHER_PARA_ID).into_location(), 100) + .unwrap(); assert_eq!( message, Xcm(vec![ @@ -136,12 +147,12 @@ fn report_outcome_works() { TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender }, ]) ); - let querier: MultiLocation = Here.into(); + let querier: Location = Here.into(); let status = QueryStatus::Pending { - responder: MultiLocation::from(Parachain(PARA_ID)).into(), + responder: Location::from(Parachain(OTHER_PARA_ID)).into(), maybe_notify: None, timeout: 100, - maybe_match_querier: Some(querier.into()), + maybe_match_querier: Some(querier.clone().into()), }; assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); @@ -151,14 +162,15 @@ fn report_outcome_works() { max_weight: Weight::zero(), querier: Some(querier), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( - Parachain(PARA_ID), + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(OTHER_PARA_ID), message, - hash, + &mut hash, Weight::from_parts(1_000_000_000, 1_000_000_000), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) }); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::ResponseReady { @@ -177,19 +189,18 @@ fn report_outcome_works() { fn custom_querier_works() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let querier: MultiLocation = - (Parent, AccountId32 { network: None, id: ALICE.into() }).into(); + let querier: Location = (Parent, AccountId32 { network: None, id: ALICE.into() }).into(); - let r = TestNotifier::prepare_new_query(RuntimeOrigin::signed(ALICE), querier); + let r = TestNotifier::prepare_new_query(RuntimeOrigin::signed(ALICE), querier.clone()); assert_eq!(r, Ok(())); let status = QueryStatus::Pending { - responder: MultiLocation::from(AccountId32 { network: None, id: ALICE.into() }).into(), + responder: Location::from(AccountId32 { network: None, id: ALICE.into() }).into(), maybe_notify: None, timeout: 100, - maybe_match_querier: Some(querier.into()), + maybe_match_querier: Some(querier.clone().into()), }; assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); @@ -200,21 +211,21 @@ fn custom_querier_works() { max_weight: Weight::zero(), querier: None, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm_in_credit( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( AccountId32 { network: None, id: ALICE.into() }, message, - hash, + &mut hash, Weight::from_parts(1_000_000_000, 1_000_000_000), Weight::from_parts(1_000, 1_000), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) }); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier { origin: AccountId32 { network: None, id: ALICE.into() }.into(), query_id: 0, - expected_querier: querier, + expected_querier: querier.clone(), maybe_actual_querier: None, }), ); @@ -224,24 +235,24 @@ fn custom_querier_works() { query_id: 0, response: Response::ExecutionResult(None), max_weight: Weight::zero(), - querier: Some(MultiLocation::here()), + querier: Some(Location::here()), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm_in_credit( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( AccountId32 { network: None, id: ALICE.into() }, message, - hash, + &mut hash, Weight::from_parts(1_000_000_000, 1_000_000_000), Weight::from_parts(1_000, 1_000), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) }); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier { origin: AccountId32 { network: None, id: ALICE.into() }.into(), query_id: 0, - expected_querier: querier, - maybe_actual_querier: Some(MultiLocation::here()), + expected_querier: querier.clone(), + maybe_actual_querier: Some(Location::here()), }), ); @@ -252,14 +263,15 @@ fn custom_querier_works() { max_weight: Weight::zero(), querier: Some(querier), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( AccountId32 { network: None, id: ALICE.into() }, message, - hash, + &mut hash, Weight::from_parts(1_000_000_000, 1_000_000_000), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(1_000, 1_000))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) }); assert_eq!( last_event(), RuntimeEvent::XcmPallet(crate::Event::ResponseReady { @@ -281,15 +293,15 @@ fn custom_querier_works() { fn send_works() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into(); let message = Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: sender }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() }, ]); let versioned_dest = Box::new(RelayLocation::get().into()); @@ -299,7 +311,7 @@ fn send_works() { versioned_dest, versioned_message )); - let sent_message = Xcm(Some(DescendOrigin(sender.try_into().unwrap())) + let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap())) .into_iter() .chain(message.0.clone().into_iter()) .collect()); @@ -325,11 +337,10 @@ fn send_works() { fn send_fails_when_xcm_router_blocks() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { - let sender: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let sender: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); let message = Xcm(vec![ ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), buy_execution((Parent, SEND_AMOUNT)), @@ -338,7 +349,7 @@ fn send_fails_when_xcm_router_blocks() { assert_noop!( XcmPallet::send( RuntimeOrigin::signed(ALICE), - Box::new(MultiLocation::ancestor(8).into()), + Box::new(Location::ancestor(8).into()), Box::new(VersionedXcm::from(message.clone())), ), crate::Error::::SendFailure @@ -346,344 +357,6 @@ fn send_fails_when_xcm_router_blocks() { }); } -/// Test `teleport_assets` -/// -/// Asserts that the sender's balance is decreased as a result of execution of -/// local effects. -#[test] -fn teleport_assets_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 3; - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); - assert_ok!(XcmPallet::teleport_assets( - RuntimeOrigin::signed(ALICE), - Box::new(RelayLocation::get().into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - )); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - RelayLocation::get().into(), - Xcm(vec![ - ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), - ClearOrigin, - buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `limited_teleport_assets` -/// -/// Asserts that the sender's balance is decreased as a result of execution of -/// local effects. -#[test] -fn limited_teleport_assets_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 3; - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); - assert_ok!(XcmPallet::limited_teleport_assets( - RuntimeOrigin::signed(ALICE), - Box::new(RelayLocation::get().into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - WeightLimit::Limited(Weight::from_parts(5000, 5000)), - )); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - RelayLocation::get().into(), - Xcm(vec![ - ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), - ClearOrigin, - buy_limited_execution((Here, SEND_AMOUNT), Weight::from_parts(5000, 5000)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `limited_teleport_assets` with unlimited weight -/// -/// Asserts that the sender's balance is decreased as a result of execution of -/// local effects. -#[test] -fn unlimited_teleport_assets_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 3; - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); - assert_ok!(XcmPallet::limited_teleport_assets( - RuntimeOrigin::signed(ALICE), - Box::new(RelayLocation::get().into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - WeightLimit::Unlimited, - )); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - RelayLocation::get().into(), - Xcm(vec![ - ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), - ClearOrigin, - buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `reserve_transfer_assets` -/// -/// 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_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 2; - let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - assert_ok!(XcmPallet::reserve_transfer_assets( - RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - )); - // Alice spent amount - assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); - // Destination account (parachain account) has amount - let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating(); - assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - Parachain(PARA_ID).into(), - Xcm(vec![ - ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), - ClearOrigin, - buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `reserve_transfer_assets_with_paid_router_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 that XCM router fees (`SendXcm::validate` -> `MultiAssets`) are withdrawn from correct -/// user account and deposited to a correct target account (`XcmFeesTargetAccount`). -#[test] -fn reserve_transfer_assets_with_paid_router_works() { - let user_account = AccountId::from(XCM_FEES_NOT_WAIVED_USER_ACCOUNT); - let paid_para_id = Para3000::get(); - let balances = vec![ - (user_account.clone(), INITIAL_BALANCE), - (ParaId::from(paid_para_id).into_account_truncating(), INITIAL_BALANCE), - (XcmFeesTargetAccount::get(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let xcm_router_fee_amount = Para3000PaymentAmount::get(); - let weight = BaseXcmWeight::get() * 2; - let dest: MultiLocation = - Junction::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()), - Box::new(Parachain(paid_para_id).into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - )); - // check event - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - - // XCM_FEES_NOT_WAIVED_USER_ACCOUNT spent amount - assert_eq!( - Balances::free_balance(user_account), - INITIAL_BALANCE - SEND_AMOUNT - xcm_router_fee_amount - ); - // Destination account (parachain account) has amount - let para_acc: AccountId = ParaId::from(paid_para_id).into_account_truncating(); - assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); - // XcmFeesTargetAccount where should lend xcm_router_fee_amount - assert_eq!( - Balances::free_balance(XcmFeesTargetAccount::get()), - INITIAL_BALANCE + xcm_router_fee_amount - ); - assert_eq!( - sent_xcm(), - vec![( - Parachain(paid_para_id).into(), - Xcm(vec![ - ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), - ClearOrigin, - buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `limited_reserve_transfer_assets` -/// -/// 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 limited_reserve_transfer_assets_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 2; - let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - assert_ok!(XcmPallet::limited_reserve_transfer_assets( - RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - WeightLimit::Limited(Weight::from_parts(5000, 5000)), - )); - // Alice spent amount - assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); - // Destination account (parachain account) has amount - let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating(); - assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - Parachain(PARA_ID).into(), - Xcm(vec![ - ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), - ClearOrigin, - buy_limited_execution((Parent, SEND_AMOUNT), Weight::from_parts(5000, 5000)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); - let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - -/// Test `limited_reserve_transfer_assets` with unlimited weight purchasing -/// -/// 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 unlimited_reserve_transfer_assets_works() { - let balances = vec![ - (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), - ]; - new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 2; - let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); - assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); - assert_ok!(XcmPallet::limited_reserve_transfer_assets( - RuntimeOrigin::signed(ALICE), - Box::new(Parachain(PARA_ID).into()), - Box::new(dest.into()), - Box::new((Here, SEND_AMOUNT).into()), - 0, - WeightLimit::Unlimited, - )); - // Alice spent amount - assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); - // Destination account (parachain account) has amount - let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating(); - assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT); - assert_eq!( - sent_xcm(), - vec![( - Parachain(PARA_ID).into(), - Xcm(vec![ - ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), - ClearOrigin, - buy_execution((Parent, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]), - )] - ); - assert_eq!( - last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) - ); - }); -} - /// Test local execution of XCM /// /// Asserts that the sender's balance is decreased and the beneficiary's balance @@ -692,11 +365,11 @@ fn unlimited_reserve_transfer_assets_works() { fn execute_withdraw_to_deposit_works() { let balances = vec![ (ALICE, INITIAL_BALANCE), - (ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE), + (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; new_test_ext_with_balances(balances).execute_with(|| { let weight = BaseXcmWeight::get() * 3; - let dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); + let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), @@ -711,7 +384,9 @@ fn execute_withdraw_to_deposit_works() { assert_eq!(Balances::total_balance(&BOB), SEND_AMOUNT); assert_eq!( last_event(), - RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: weight } + }) ); }); } @@ -722,7 +397,7 @@ fn trapped_assets_can_be_claimed() { let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)]; new_test_ext_with_balances(balances).execute_with(|| { let weight = BaseXcmWeight::get() * 6; - let dest: MultiLocation = Junction::AccountId32 { network: None, id: BOB.into() }.into(); + let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into(); assert_ok!(XcmPallet::execute( RuntimeOrigin::signed(ALICE), @@ -734,15 +409,14 @@ fn trapped_assets_can_be_claimed() { // This will make an error. Trap(0), // This would succeed, but we never get to it. - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, ]))), weight )); - let source: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); let trapped = AssetTraps::::iter().collect::>(); - let vma = VersionedMultiAssets::from(MultiAssets::from((Here, SEND_AMOUNT))); - let hash = BlakeTwo256::hash_of(&(source, vma.clone())); + let vma = VersionedAssets::from(Assets::from((Here, SEND_AMOUNT))); + let hash = BlakeTwo256::hash_of(&(source.clone(), vma.clone())); assert_eq!( last_events(2), vec![ @@ -752,7 +426,7 @@ fn trapped_assets_can_be_claimed() { assets: vma }), RuntimeEvent::XcmPallet(crate::Event::Attempted { - outcome: Outcome::Complete(BaseXcmWeight::get() * 5) + outcome: Outcome::Complete { used: BaseXcmWeight::get() * 5 } }), ] ); @@ -768,7 +442,7 @@ fn trapped_assets_can_be_claimed() { Box::new(VersionedXcm::from(Xcm(vec![ ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() }, ]))), weight )); @@ -777,51 +451,155 @@ fn trapped_assets_can_be_claimed() { assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT); assert_eq!(AssetTraps::::iter().collect::>(), vec![]); - let weight = BaseXcmWeight::get() * 3; + // Can't claim twice. + assert_err_ignore_postinfo!( + XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::from(Xcm(vec![ + ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, + buy_execution((Here, SEND_AMOUNT)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + ]))), + weight + ), + Error::::LocalExecutionIncomplete + ); + }); +} + +// Like `trapped_assets_can_be_claimed` but using the `claim_assets` extrinsic. +#[test] +fn claim_assets_works() { + let balances = vec![(ALICE, INITIAL_BALANCE)]; + new_test_ext_with_balances(balances).execute_with(|| { + // First trap some assets. + let trapping_program = + Xcm::builder_unsafe().withdraw_asset((Here, SEND_AMOUNT).into()).build(); + // Even though assets are trapped, the extrinsic returns success. assert_ok!(XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::V4(trapping_program)), + BaseXcmWeight::get() * 2, + )); + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); + + // Expected `AssetsTrapped` event info. + let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let versioned_assets = VersionedAssets::V4(Assets::from((Here, SEND_AMOUNT))); + let hash = BlakeTwo256::hash_of(&(source.clone(), versioned_assets.clone())); + + // Assets were indeed trapped. + assert_eq!( + last_events(2), + vec![ + RuntimeEvent::XcmPallet(crate::Event::AssetsTrapped { + hash, + origin: source, + assets: versioned_assets + }), + RuntimeEvent::XcmPallet(crate::Event::Attempted { + outcome: Outcome::Complete { used: BaseXcmWeight::get() * 1 } + }) + ], + ); + let trapped = AssetTraps::::iter().collect::>(); + assert_eq!(trapped, vec![(hash, 1)]); + + // Now claim them with the extrinsic. + assert_ok!(XcmPallet::claim_assets( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedAssets::V4((Here, SEND_AMOUNT).into())), + Box::new(VersionedLocation::V4( + AccountId32 { network: None, id: ALICE.clone().into() }.into() + )), + )); + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); + assert_eq!(AssetTraps::::iter().collect::>(), vec![]); + }); +} + +/// Test failure to complete execution reverts intermediate side-effects. +/// +/// XCM program will withdraw and deposit some assets, then fail execution of a further withdraw. +/// Assert that the previous instructions effects are reverted. +#[test] +fn incomplete_execute_reverts_side_effects() { + let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)]; + new_test_ext_with_balances(balances).execute_with(|| { + let weight = BaseXcmWeight::get() * 4; + let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into(); + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); + let amount_to_send = INITIAL_BALANCE - ExistentialDeposit::get(); + let assets: Assets = (Here, amount_to_send).into(); + let result = XcmPallet::execute( RuntimeOrigin::signed(ALICE), Box::new(VersionedXcm::from(Xcm(vec![ - ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, - buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + // Withdraw + BuyExec + Deposit should work + WithdrawAsset(assets.clone()), + buy_execution(assets.inner()[0].clone()), + DepositAsset { assets: assets.clone().into(), beneficiary: dest }, + // Withdrawing once more will fail because of InsufficientBalance, and we expect to + // revert the effects of the above instructions as well + WithdrawAsset(assets), ]))), - weight - )); - let outcome = Outcome::Incomplete(BaseXcmWeight::get(), XcmError::UnknownClaim); - assert_eq!(last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome })); + weight, + ); + // all effects are reverted and balances unchanged for either sender or receiver + assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); + assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE); + + assert_eq!( + result, + Err(sp_runtime::DispatchErrorWithPostInfo { + post_info: frame_support::dispatch::PostDispatchInfo { + actual_weight: Some( + as ExecuteControllerWeightInfo>::execute() + weight + ), + pays_fee: frame_support::dispatch::Pays::Yes, + }, + error: sp_runtime::DispatchError::Module(sp_runtime::ModuleError { + index: 4, + error: [24, 0, 0, 0,], + message: Some("LocalExecutionIncomplete") + }) + }) + ); }); } #[test] -fn fake_latest_versioned_multilocation_works() { +fn fake_latest_versioned_location_works() { use codec::Encode; - let remote: MultiLocation = Parachain(1000).into(); - let versioned_remote = LatestVersionedMultiLocation(&remote); + let remote: Location = Parachain(1000).into(); + let versioned_remote = LatestVersionedLocation(&remote); assert_eq!(versioned_remote.encode(), remote.into_versioned().encode()); } #[test] fn basic_subscription_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote: MultiLocation = Parachain(1000).into(); + let remote: Location = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()), + Box::new(remote.clone().into()), )); assert_eq!( Queries::::iter().collect::>(), - vec![(0, QueryStatus::VersionNotifier { origin: remote.into(), is_active: false })] + vec![( + 0, + QueryStatus::VersionNotifier { origin: remote.clone().into(), is_active: false } + )] ); assert_eq!( VersionNotifiers::::iter().collect::>(), - vec![(XCM_VERSION, remote.into(), 0)] + vec![(XCM_VERSION, remote.clone().into(), 0)] ); assert_eq!( take_sent_xcm(), vec![( - remote, + remote.clone(), Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]), ),] ); @@ -848,16 +626,16 @@ fn basic_subscription_works() { #[test] fn subscriptions_increment_id() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote: MultiLocation = Parachain(1000).into(); + let remote: Location = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()), + Box::new(remote.clone().into()), )); - let remote2: MultiLocation = Parachain(1001).into(); + let remote2: Location = Parachain(1001).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote2.into()), + Box::new(remote2.clone().into()), )); assert_eq!( @@ -885,10 +663,10 @@ fn subscriptions_increment_id() { #[test] fn double_subscription_fails() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote: MultiLocation = Parachain(1000).into(); + let remote: Location = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()), + Box::new(remote.clone().into()), )); assert_noop!( XcmPallet::force_subscribe_version_notify( @@ -903,19 +681,19 @@ fn double_subscription_fails() { #[test] fn unsubscribe_works() { new_test_ext_with_balances(vec![]).execute_with(|| { - let remote: MultiLocation = Parachain(1000).into(); + let remote: Location = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()), + Box::new(remote.clone().into()), )); assert_ok!(XcmPallet::force_unsubscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()) + Box::new(remote.clone().into()) )); assert_noop!( XcmPallet::force_unsubscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()) + Box::new(remote.clone().into()) ), Error::::NoSubscription, ); @@ -924,13 +702,13 @@ fn unsubscribe_works() { take_sent_xcm(), vec![ ( - remote, + remote.clone(), Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]), ), - (remote, Xcm(vec![UnsubscribeVersion]),), + (remote.clone(), Xcm(vec![UnsubscribeVersion]),), ] ); }); @@ -942,13 +720,19 @@ fn subscription_side_works() { new_test_ext_with_balances(vec![]).execute_with(|| { AdvertisedXcmVersion::set(1); - let remote: MultiLocation = Parachain(1000).into(); + let remote: Location = Parachain(1000).into(); let weight = BaseXcmWeight::get(); let message = Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(remote, message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + remote.clone(), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); let instr = QueryResponse { query_id: 0, @@ -956,7 +740,7 @@ fn subscription_side_works() { response: Response::Version(1), querier: None, }; - assert_eq!(take_sent_xcm(), vec![(remote, Xcm(vec![instr]))]); + assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]); // A runtime upgrade which doesn't alter the version sends no notifications. CurrentMigration::::put(VersionMigrationStage::default()); @@ -985,7 +769,7 @@ fn subscription_side_upgrades_work_with_notify() { AdvertisedXcmVersion::set(1); // An entry from a previous runtime with v2 XCM. - let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1001).into()); VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 2)); let v3_location = Parachain(1003).into_versioned(); VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 2)); @@ -1038,7 +822,7 @@ fn subscription_side_upgrades_work_with_notify() { fn subscription_side_upgrades_work_without_notify() { new_test_ext_with_balances(vec![]).execute_with(|| { // An entry from a previous runtime with v2 XCM. - let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1001).into()); VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 2)); let v3_location = Parachain(1003).into_versioned(); VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 2)); @@ -1052,8 +836,8 @@ fn subscription_side_upgrades_work_without_notify() { assert_eq!( contents, vec![ - (XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 3)), - (XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 3)), + (XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 4)), + (XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 4)), ] ); }); @@ -1061,12 +845,13 @@ fn subscription_side_upgrades_work_without_notify() { #[test] fn subscriber_side_subscription_works() { - new_test_ext_with_balances(vec![]).execute_with(|| { - let remote: MultiLocation = Parachain(1000).into(); + new_test_ext_with_balances_and_xcm_version(vec![], Some(XCM_VERSION)).execute_with(|| { + let remote: Location = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), - Box::new(remote.into()), + Box::new(remote.clone().into()), )); + assert_eq!(XcmPallet::get_version_for(&remote), None); take_sent_xcm(); // Assume subscription target is working ok. @@ -1081,10 +866,17 @@ fn subscriber_side_subscription_works() { querier: None, }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(remote, message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + remote.clone(), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: 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)]); @@ -1099,9 +891,17 @@ fn subscriber_side_subscription_works() { querier: None, }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(remote, message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + remote.clone(), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: 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!( @@ -1114,74 +914,80 @@ 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(|| { - let remote_v2: MultiLocation = Parachain(1000).into(); - let remote_v3: MultiLocation = Parachain(1001).into(); + new_test_ext_with_balances_and_xcm_version(vec![], None).execute_with(|| { + let remote_v2: Location = Parachain(1000).into(); + let remote_v4: Location = Parachain(1001).into(); assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(2))); // Wrapping a version for a destination we don't know elicits a subscription. let msg_v2 = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]); - let msg_v3 = xcm::v3::Xcm::<()>(vec![xcm::v3::Instruction::ClearTopic]); + let msg_v4 = xcm::v4::Xcm::<()>(vec![xcm::v4::Instruction::ClearTopic]); assert_eq!( XcmPallet::wrap_version(&remote_v2, msg_v2.clone()), Ok(VersionedXcm::from(msg_v2.clone())), ); - assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v3.clone()), Err(())); + assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v4.clone()), Err(())); - let expected = vec![(remote_v2.into(), 2)]; + let expected = vec![(remote_v2.clone().into(), 2)]; assert_eq!(VersionDiscoveryQueue::::get().into_inner(), expected); assert_eq!( - XcmPallet::wrap_version(&remote_v3, msg_v2.clone()), + XcmPallet::wrap_version(&remote_v4, msg_v2.clone()), Ok(VersionedXcm::from(msg_v2.clone())), ); - assert_eq!(XcmPallet::wrap_version(&remote_v3, msg_v3.clone()), Err(())); + assert_eq!(XcmPallet::wrap_version(&remote_v4, msg_v4.clone()), Err(())); - let expected = vec![(remote_v2.into(), 2), (remote_v3.into(), 2)]; + let expected = vec![(remote_v2.clone().into(), 2), (remote_v4.clone().into(), 2)]; assert_eq!(VersionDiscoveryQueue::::get().into_inner(), expected); XcmPallet::on_initialize(1); assert_eq!( take_sent_xcm(), vec![( - remote_v3, + remote_v4.clone(), Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]), )] ); - // Assume remote_v3 is working ok and XCM version 3. + // Assume remote_v4 is working ok and XCM version 4. let weight = BaseXcmWeight::get(); let message = Xcm(vec![ - // Remote supports XCM v3 + // Remote supports XCM v4 QueryResponse { query_id: 0, max_weight: Weight::zero(), - response: Response::Version(3), + response: Response::Version(4), querier: None, }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(remote_v3, message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + remote_v4.clone(), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); - // V2 messages can be sent to remote_v3 under XCM v3. + // V2 messages can be sent to remote_v4 under XCM v4. assert_eq!( - XcmPallet::wrap_version(&remote_v3, msg_v2.clone()), - Ok(VersionedXcm::from(msg_v2.clone()).into_version(3).unwrap()), + XcmPallet::wrap_version(&remote_v4, msg_v2.clone()), + Ok(VersionedXcm::from(msg_v2.clone()).into_version(4).unwrap()), ); - // This message can now be sent to remote_v3 as it's v3. + // This message can now be sent to remote_v4 as it's v4. assert_eq!( - XcmPallet::wrap_version(&remote_v3, msg_v3.clone()), - Ok(VersionedXcm::from(msg_v3.clone())) + XcmPallet::wrap_version(&remote_v4, msg_v4.clone()), + Ok(VersionedXcm::from(msg_v4.clone())) ); XcmPallet::on_initialize(2); assert_eq!( take_sent_xcm(), vec![( - remote_v2, + remote_v2.clone(), Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: Weight::zero() }]), )] ); @@ -1198,16 +1004,22 @@ fn auto_subscription_works() { querier: None, }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(remote_v2, message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + remote_v2.clone(), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); - // v3 messages cannot be sent to remote_v2... + // v4 messages cannot be sent to remote_v2... assert_eq!( XcmPallet::wrap_version(&remote_v2, msg_v2.clone()), Ok(VersionedXcm::V2(msg_v2)) ); - assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v3.clone()), Err(())); + assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v4.clone()), Err(())); }) } @@ -1217,9 +1029,9 @@ fn subscription_side_upgrades_work_with_multistage_notify() { AdvertisedXcmVersion::set(1); // An entry from a previous runtime with v0 XCM. - let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1001).into()); + let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1001).into()); VersionNotifyTargets::::insert(1, v2_location, (70, Weight::zero(), 1)); - let v2_location = VersionedMultiLocation::V2(xcm::v2::Junction::Parachain(1002).into()); + let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1002).into()); VersionNotifyTargets::::insert(2, v2_location, (71, Weight::zero(), 1)); let v3_location = Parachain(1003).into_versioned(); VersionNotifyTargets::::insert(3, v3_location, (72, Weight::zero(), 1)); @@ -1282,3 +1094,158 @@ 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: Location = Parachain(1000).into(); + let remote_b: Location = Parachain(1001).into(); + let remote_c: Location = 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.clone()), + 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.clone().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.clone().into(), 2)] + ); + + // change remote_c to `1` + assert_ok!(XcmPallet::force_xcm_version( + RuntimeOrigin::root(), + Box::new(remote_c.clone()), + 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)]); + }) +} + +#[test] +fn multistage_migration_works() { + new_test_ext_with_balances(vec![]).execute_with(|| { + // An entry from a previous runtime with v3 XCM. + let v3_location = VersionedLocation::V3(xcm::v3::Junction::Parachain(1001).into()); + let v3_version = xcm::v3::VERSION; + SupportedVersion::::insert(v3_version, v3_location.clone(), v3_version); + VersionNotifiers::::insert(v3_version, v3_location.clone(), 1); + VersionNotifyTargets::::insert( + v3_version, + v3_location, + (70, Weight::zero(), v3_version), + ); + // A version to advertise. + AdvertisedXcmVersion::set(4); + + // check `try-state` + assert!(Pallet::::do_try_state().is_err()); + + // closure simulates a multistage migration process + let migrate = |expected_cycle_count| { + // A runtime upgrade which alters the version does send notifications. + CurrentMigration::::put(VersionMigrationStage::default()); + let mut maybe_migration = CurrentMigration::::take(); + let mut counter = 0; + let mut weight_used = Weight::zero(); + while let Some(migration) = maybe_migration.take() { + counter += 1; + let (w, m) = XcmPallet::check_xcm_version_change(migration, Weight::zero()); + maybe_migration = m; + weight_used.saturating_accrue(w); + } + assert_eq!(counter, expected_cycle_count); + weight_used + }; + + // run migration for the first time + let _ = migrate(4); + + // check xcm sent + assert_eq!( + take_sent_xcm(), + vec![( + Parachain(1001).into(), + Xcm(vec![QueryResponse { + query_id: 70, + max_weight: Weight::zero(), + response: Response::Version(AdvertisedXcmVersion::get()), + querier: None, + }]) + ),] + ); + + // check migrated data + assert_eq!( + SupportedVersion::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), v3_version),] + ); + assert_eq!( + VersionNotifiers::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), 1),] + ); + assert_eq!( + VersionNotifyTargets::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 4)),] + ); + + // run migration again to check it can run multiple time without any harm or double sending + // messages. + let weight_used = migrate(1); + assert_eq!(weight_used, 1_u8 * ::WeightInfo::already_notified_target()); + + // check no xcm sent + assert_eq!(take_sent_xcm(), vec![]); + + // check `try-state` + assert!(Pallet::::do_try_state().is_ok()); + }) +} diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 56df0d94f586002cdc0e5aee58a5a9dfc7470599..ca9fb351bd3cad1f805106e405cfdcc496c9d8a8 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -1,15 +1,24 @@ [package] name = "xcm-procedural" +description = "Procedural macros for XCM" authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "7.0.0" +publish = true + +[lints] +workspace = true [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.38" +quote = { workspace = true } +syn = { workspace = true } Inflector = "0.11.4" + +[dev-dependencies] +trybuild = { version = "1.0.88", features = ["diff"] } +xcm = { package = "staging-xcm", path = ".." } diff --git a/polkadot/xcm/procedural/src/builder_pattern.rs b/polkadot/xcm/procedural/src/builder_pattern.rs new file mode 100644 index 0000000000000000000000000000000000000000..e58c51103497a23a0b97d7274deba1130bfaf28a --- /dev/null +++ b/polkadot/xcm/procedural/src/builder_pattern.rs @@ -0,0 +1,336 @@ +// 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 . + +//! Derive macro for creating XCMs with a builder pattern + +use inflector::Inflector; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{ + Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue, + Result, Variant, +}; + +pub fn derive(input: DeriveInput) -> Result { + let data_enum = match &input.data { + Data::Enum(data_enum) => data_enum, + _ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")), + }; + let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum); + let builder_impl = generate_builder_impl(&input.ident, data_enum)?; + let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?; + let output = quote! { + /// A trait for types that track state inside the XcmBuilder + pub trait XcmBuilderState {} + + /// Access to all the instructions + pub enum AnythingGoes {} + /// You need to pay for execution + pub enum PaymentRequired {} + /// The holding register was loaded, now to buy execution + pub enum LoadedHolding {} + /// Need to explicitly state it won't pay for fees + pub enum ExplicitUnpaidRequired {} + + impl XcmBuilderState for AnythingGoes {} + impl XcmBuilderState for PaymentRequired {} + impl XcmBuilderState for LoadedHolding {} + impl XcmBuilderState for ExplicitUnpaidRequired {} + + /// Type used to build XCM programs + pub struct XcmBuilder { + pub(crate) instructions: Vec>, + pub state: core::marker::PhantomData, + } + + impl Xcm { + pub fn builder() -> XcmBuilder { + XcmBuilder:: { + instructions: Vec::new(), + state: core::marker::PhantomData, + } + } + pub fn builder_unpaid() -> XcmBuilder { + XcmBuilder:: { + instructions: Vec::new(), + state: core::marker::PhantomData, + } + } + pub fn builder_unsafe() -> XcmBuilder { + XcmBuilder:: { + instructions: Vec::new(), + state: core::marker::PhantomData, + } + } + } + #builder_impl + #builder_unpaid_impl + #builder_raw_impl + }; + Ok(output) +} + +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 = match &variant.fields { + Fields::Unit => { + quote! { + pub fn #method_name(mut self) -> Self { + self.instructions.push(#name::::#variant_name); + self + } + } + }, + Fields::Unnamed(fields) => { + let arg_names: Vec<_> = fields + .unnamed + .iter() + .enumerate() + .map(|(index, _)| format_ident!("arg{}", index)) + .collect(); + let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); + quote! { + pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self { + self.instructions.push(#name::::#variant_name(#(#arg_names),*)); + self + } + } + }, + Fields::Named(fields) => { + let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); + quote! { + pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self { + self.instructions.push(#name::::#variant_name { #(#arg_names),* }); + self + } + } + }, + }; + quote! { + #(#docs)* + #method + } + }); + let output = quote! { + impl XcmBuilder { + #(#methods)* + + pub fn build(self) -> Xcm { + Xcm(self.instructions) + } + } + }; + output +} + +fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { + // We first require an instruction that load the holding register + let load_holding_variants = data_enum + .variants + .iter() + .map(|variant| { + let maybe_builder_attr = variant.attrs.iter().find(|attr| match attr.meta { + Meta::List(ref list) => list.path.is_ident("builder"), + _ => false, + }); + let builder_attr = match maybe_builder_attr { + Some(builder) => builder.clone(), + None => return Ok(None), /* It's not going to be an instruction that loads the + * holding register */ + }; + let Meta::List(ref list) = builder_attr.meta else { unreachable!("We checked before") }; + let inner_ident: Ident = syn::parse2(list.tokens.clone()).map_err(|_| { + Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`") + })?; + let ident_to_match: Ident = syn::parse_quote!(loads_holding); + if inner_ident == ident_to_match { + Ok(Some(variant)) + } else { + Err(Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`")) + } + }) + .collect::>>()?; + + let load_holding_methods = load_holding_variants + .into_iter() + .flatten() + .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 = match &variant.fields { + Fields::Unnamed(fields) => { + let arg_names: Vec<_> = fields + .unnamed + .iter() + .enumerate() + .map(|(index, _)| format_ident!("arg{}", index)) + .collect(); + let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); + quote! { + #(#docs)* + pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder { + let mut new_instructions = self.instructions; + new_instructions.push(#name::::#variant_name(#(#arg_names),*)); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + }, + Fields::Named(fields) => { + let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); + quote! { + #(#docs)* + pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder { + let mut new_instructions = self.instructions; + new_instructions.push(#name::::#variant_name { #(#arg_names),* }); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + }, + _ => + return Err(Error::new_spanned( + variant, + "Instructions that load the holding register should take operands", + )), + }; + Ok(method) + }) + .collect::, _>>()?; + + let first_impl = quote! { + impl XcmBuilder { + #(#load_holding_methods)* + } + }; + + // Then we require fees to be paid + let buy_execution_method = data_enum + .variants + .iter() + .find(|variant| variant.ident == "BuyExecution") + .map_or( + Err(Error::new_spanned(&data_enum.variants, "No BuyExecution instruction")), + |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 fields = match &variant.fields { + Fields::Named(fields) => { + let arg_names: Vec<_> = + fields.named.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = + fields.named.iter().map(|field| &field.ty).collect(); + quote! { + #(#docs)* + pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder { + let mut new_instructions = self.instructions; + new_instructions.push(#name::::#variant_name { #(#arg_names),* }); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + }, + _ => + return Err(Error::new_spanned( + variant, + "BuyExecution should have named fields", + )), + }; + Ok(fields) + }, + )?; + + let second_impl = quote! { + impl XcmBuilder { + #buy_execution_method + } + }; + + let output = quote! { + #first_impl + #second_impl + }; + + Ok(output) +} + +fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result { + let unpaid_execution_variant = data_enum + .variants + .iter() + .find(|variant| variant.ident == "UnpaidExecution") + .ok_or(Error::new_spanned(&data_enum.variants, "No UnpaidExecution instruction"))?; + let unpaid_execution_ident = &unpaid_execution_variant.ident; + let unpaid_execution_method_name = Ident::new( + &unpaid_execution_ident.to_string().to_snake_case(), + unpaid_execution_ident.span(), + ); + let docs = get_doc_comments(unpaid_execution_variant); + let fields = match &unpaid_execution_variant.fields { + Fields::Named(fields) => fields, + _ => + return Err(Error::new_spanned( + unpaid_execution_variant, + "UnpaidExecution should have named fields", + )), + }; + let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); + Ok(quote! { + impl XcmBuilder { + #(#docs)* + pub fn #unpaid_execution_method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder { + let mut new_instructions = self.instructions; + new_instructions.push(#name::::#unpaid_execution_ident { #(#arg_names),* }); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + }) +} + +fn get_doc_comments(variant: &Variant) -> Vec { + variant + .attrs + .iter() + .filter_map(|attr| match &attr.meta { + Meta::NameValue(MetaNameValue { + value: Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }), + .. + }) if attr.path().is_ident("doc") => Some(literal.value()), + _ => None, + }) + .map(|doc| syn::parse_str::(&format!("/// {}", doc)).unwrap()) + .collect() +} diff --git a/polkadot/xcm/procedural/src/lib.rs b/polkadot/xcm/procedural/src/lib.rs index 2ebccadf50be805c2f66db0a703e0f09bf43861d..4980d84d3282a02b0f910b9b3b91bc876f9249a7 100644 --- a/polkadot/xcm/procedural/src/lib.rs +++ b/polkadot/xcm/procedural/src/lib.rs @@ -17,9 +17,12 @@ //! Procedural macros used in XCM. use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; +mod builder_pattern; mod v2; mod v3; +mod v4; mod weight_info; #[proc_macro] @@ -29,6 +32,13 @@ pub fn impl_conversion_functions_for_multilocation_v2(input: TokenStream) -> Tok .into() } +#[proc_macro] +pub fn impl_conversion_functions_for_junctions_v2(input: TokenStream) -> TokenStream { + v2::junctions::generate_conversion_functions(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + #[proc_macro_derive(XcmWeightInfoTrait)] pub fn derive_xcm_weight_info(item: TokenStream) -> TokenStream { weight_info::derive(item) @@ -47,3 +57,32 @@ pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenSt .unwrap_or_else(syn::Error::into_compile_error) .into() } + +#[proc_macro] +pub fn impl_conversion_functions_for_location_v4(input: TokenStream) -> TokenStream { + v4::location::generate_conversion_functions(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro] +pub fn impl_conversion_functions_for_junctions_v4(input: TokenStream) -> TokenStream { + v4::junctions::generate_conversion_functions(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// This is called on the `Instruction` enum, not on the `Xcm` struct, +/// and allows for the following syntax for building XCMs: +/// let message = Xcm::builder() +/// .withdraw_asset(assets) +/// .buy_execution(fees, weight_limit) +/// .deposit_asset(assets, beneficiary) +/// .build(); +#[proc_macro_derive(Builder, attributes(builder))] +pub fn derive_builder(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + builder_pattern::derive(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/polkadot/xcm/procedural/src/v2.rs b/polkadot/xcm/procedural/src/v2.rs index dc2694a666f0262e961f2fe0dfd844e5651e1cd0..6878f7755cc70b9b540074a63480615338e456aa 100644 --- a/polkadot/xcm/procedural/src/v2.rs +++ b/polkadot/xcm/procedural/src/v2.rs @@ -14,10 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Result, Token}; + pub mod multilocation { - use proc_macro2::{Span, TokenStream}; - use quote::{format_ident, quote}; - use syn::{Result, Token}; + use super::*; pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { if !input.is_empty() { @@ -181,3 +183,44 @@ pub mod multilocation { } } } + +pub mod junctions { + use super::*; + + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + let from_slice_syntax = generate_conversion_from_slice_syntax(); + + Ok(quote! { + #from_slice_syntax + }) + } + + fn generate_conversion_from_slice_syntax() -> TokenStream { + quote! { + macro_rules! impl_junction { + ($count:expr, $variant:ident, ($($index:literal),+)) => { + /// Additional helper for building junctions + /// Useful for converting to future XCM versions + impl From<[Junction; $count]> for Junctions { + fn from(junctions: [Junction; $count]) -> Self { + Self::$variant($(junctions[$index].clone()),*) + } + } + }; + } + + impl_junction!(1, X1, (0)); + impl_junction!(2, X2, (0, 1)); + impl_junction!(3, X3, (0, 1, 2)); + impl_junction!(4, X4, (0, 1, 2, 3)); + impl_junction!(5, X5, (0, 1, 2, 3, 4)); + impl_junction!(6, X6, (0, 1, 2, 3, 4, 5)); + impl_junction!(7, X7, (0, 1, 2, 3, 4, 5, 6)); + impl_junction!(8, X8, (0, 1, 2, 3, 4, 5, 6, 7)); + } + } +} diff --git a/polkadot/xcm/procedural/src/v3.rs b/polkadot/xcm/procedural/src/v3.rs index 246f90a46a3e7e2ba5a60f0a95c47a52f0d34aae..f0556d5a8d447389b383dfa3ac3636f2ec77bdf5 100644 --- a/polkadot/xcm/procedural/src/v3.rs +++ b/polkadot/xcm/procedural/src/v3.rs @@ -45,9 +45,8 @@ pub mod multilocation { let interior = if num_junctions == 0 { quote!(Junctions::Here) } else { - let variant = format_ident!("X{}", num_junctions); quote! { - Junctions::#variant( #(#idents .into()),* ) + [#(#idents .into()),*].into() } }; @@ -110,7 +109,7 @@ pub mod multilocation { impl From for MultiLocation { fn from(x: Junction) -> Self { - MultiLocation { parents: 0, interior: Junctions::X1(x) } + MultiLocation { parents: 0, interior: [x].into() } } } @@ -129,10 +128,12 @@ pub mod junctions { // Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents. let from_v2 = generate_conversion_from_v2(MAX_JUNCTIONS); + let from_v4 = generate_conversion_from_v4(); let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS); Ok(quote! { #from_v2 + #from_v4 #from_tuples }) } @@ -143,12 +144,11 @@ pub mod junctions { let idents = (0..num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::>(); - let variant = &format_ident!("X{}", num_junctions); quote! { impl<#(#types : Into,)*> From<( #(#types,)* )> for Junctions { fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self { - Self::#variant( #(#idents .into()),* ) + [#(#idents .into()),*].into() } } } @@ -156,6 +156,45 @@ pub mod junctions { .collect() } + fn generate_conversion_from_v4() -> TokenStream { + let match_variants = (0..8u8) + .map(|current_number| { + let number_ancestors = current_number + 1; + let variant = format_ident!("X{}", number_ancestors); + let idents = + (0..=current_number).map(|i| format_ident!("j{}", i)).collect::>(); + let convert = idents + .iter() + .map(|ident| { + quote! { let #ident = core::convert::TryInto::try_into(#ident.clone())?; } + }) + .collect::>(); + + quote! { + crate::v4::Junctions::#variant( junctions ) => { + let [#(#idents),*] = &*junctions; + #(#convert);* + [#(#idents),*].into() + }, + } + }) + .collect::(); + + quote! { + impl core::convert::TryFrom for Junctions { + type Error = (); + + fn try_from(mut new: crate::v4::Junctions) -> core::result::Result { + use Junctions::*; + Ok(match new { + crate::v4::Junctions::Here => Here, + #match_variants + }) + } + } + } + } + fn generate_conversion_from_v2(max_junctions: usize) -> TokenStream { let match_variants = (0..max_junctions) .map(|cur_num| { diff --git a/polkadot/xcm/procedural/src/v4.rs b/polkadot/xcm/procedural/src/v4.rs new file mode 100644 index 0000000000000000000000000000000000000000..5f5e10d3081b39a3fe5e02f312ba5f487509d9e6 --- /dev/null +++ b/polkadot/xcm/procedural/src/v4.rs @@ -0,0 +1,196 @@ +// 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 proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Result, Token}; + +const MAX_JUNCTIONS: usize = 8; + +pub mod location { + use super::*; + + /// Generates conversion functions from other types to the `Location` type: + /// - [PalletInstance(50), GeneralIndex(1984)].into() + /// - (Parent, Parachain(1000), AccountId32 { .. }).into() + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + let from_tuples = generate_conversion_from_tuples(8, 8); + + Ok(quote! { + #from_tuples + }) + } + + fn generate_conversion_from_tuples(max_junctions: usize, max_parents: usize) -> TokenStream { + let mut from_tuples = (0..=max_junctions) + .map(|num_junctions| { + let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::>(); + let idents = + (0..num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); + let array_size = num_junctions; + let interior = if num_junctions == 0 { + quote!(Junctions::Here) + } else { + let variant = format_ident!("X{}", num_junctions); + quote! { + Junctions::#variant( alloc::sync::Arc::new( [#(#idents .into()),*] ) ) + } + }; + + let mut from_tuple = quote! { + impl< #(#types : Into,)* > From<( Ancestor, #( #types ),* )> for Location { + fn from( ( Ancestor(parents), #(#idents),* ): ( Ancestor, #( #types ),* ) ) -> Self { + Location { parents, interior: #interior } + } + } + + impl From<[Junction; #array_size]> for Location { + fn from(j: [Junction; #array_size]) -> Self { + let [#(#idents),*] = j; + Location { parents: 0, interior: #interior } + } + } + }; + + let from_parent_tuples = (0..=max_parents).map(|cur_parents| { + let parents = + (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl< #(#types : Into,)* > From<( #( #parents , )* #( #types , )* )> for Location { + fn from( ( #(#underscores,)* #(#idents,)* ): ( #(#parents,)* #(#types,)* ) ) -> Self { + Self { parents: #cur_parents as u8, interior: #interior } + } + } + } + }); + + from_tuple.extend(from_parent_tuples); + from_tuple + }) + .collect::(); + + let from_parent_junctions_tuples = (0..=max_parents).map(|cur_parents| { + let parents = (0..cur_parents).map(|_| format_ident!("Parent")).collect::>(); + let underscores = + (0..cur_parents).map(|_| Token![_](Span::call_site())).collect::>(); + + quote! { + impl From<( #(#parents,)* Junctions )> for Location { + fn from( (#(#underscores,)* junctions): ( #(#parents,)* Junctions ) ) -> Self { + Location { parents: #cur_parents as u8, interior: junctions } + } + } + } + }); + from_tuples.extend(from_parent_junctions_tuples); + + quote! { + impl From<(Ancestor, Junctions)> for Location { + fn from((Ancestor(parents), interior): (Ancestor, Junctions)) -> Self { + Location { parents, interior } + } + } + + impl From for Location { + fn from(x: Junction) -> Self { + Location { parents: 0, interior: [x].into() } + } + } + + #from_tuples + } + } +} + +pub mod junctions { + use super::*; + + pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result { + if !input.is_empty() { + return Err(syn::Error::new(Span::call_site(), "No arguments expected")) + } + + // Support up to 8 Parents in a tuple, assuming that most use cases don't go past 8 parents. + let from_v3 = generate_conversion_from_v3(MAX_JUNCTIONS); + let from_tuples = generate_conversion_from_tuples(MAX_JUNCTIONS); + + Ok(quote! { + #from_v3 + #from_tuples + }) + } + + fn generate_conversion_from_tuples(max_junctions: usize) -> TokenStream { + (1..=max_junctions) + .map(|num_junctions| { + let idents = + (0..num_junctions).map(|i| format_ident!("j{}", i)).collect::>(); + let types = (0..num_junctions).map(|i| format_ident!("J{}", i)).collect::>(); + + quote! { + impl<#(#types : Into,)*> From<( #(#types,)* )> for Junctions { + fn from( ( #(#idents,)* ): ( #(#types,)* ) ) -> Self { + [#(#idents .into()),*].into() + } + } + } + }) + .collect() + } + + fn generate_conversion_from_v3(max_junctions: usize) -> TokenStream { + let match_variants = (0..max_junctions) + .map(|cur_num| { + let num_ancestors = cur_num + 1; + let variant = format_ident!("X{}", num_ancestors); + let idents = (0..=cur_num).map(|i| format_ident!("j{}", i)).collect::>(); + let convert = idents + .iter() + .map(|ident| { + quote! { let #ident = core::convert::TryInto::try_into(#ident.clone())?; } + }) + .collect::>(); + + quote! { + crate::v3::Junctions::#variant( #(#idents),* ) => { + #(#convert);*; + let junctions: Junctions = [#(#idents),*].into(); + junctions + }, + } + }) + .collect::(); + + quote! { + impl core::convert::TryFrom for Junctions { + type Error = (); + fn try_from(mut old: crate::v3::Junctions) -> core::result::Result { + Ok(match old { + crate::v3::Junctions::Here => Junctions::Here, + #match_variants + }) + } + } + } + } +} diff --git a/polkadot/xcm/procedural/tests/builder_pattern.rs b/polkadot/xcm/procedural/tests/builder_pattern.rs new file mode 100644 index 0000000000000000000000000000000000000000..a9a30611dc019e494db488bc6591a1dbe49d4561 --- /dev/null +++ b/polkadot/xcm/procedural/tests/builder_pattern.rs @@ -0,0 +1,81 @@ +// 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 the methods generated by the Builder derive macro. +//! Tests directly on the actual Xcm struct and Instruction enum. + +use xcm::latest::prelude::*; + +#[test] +fn builder_pattern_works() { + let asset: Asset = (Here, 100u128).into(); + let beneficiary: Location = AccountId32 { id: [0u8; 32], network: None }.into(); + let message: Xcm<()> = Xcm::builder() + .receive_teleported_asset(asset.clone().into()) + .buy_execution(asset.clone(), Unlimited) + .deposit_asset(asset.clone().into(), beneficiary.clone()) + .build(); + assert_eq!( + message, + Xcm(vec![ + ReceiveTeleportedAsset(asset.clone().into()), + BuyExecution { fees: asset.clone(), weight_limit: Unlimited }, + DepositAsset { assets: asset.into(), beneficiary }, + ]) + ); +} + +#[test] +fn default_builder_requires_buy_execution() { + let asset: Asset = (Here, 100u128).into(); + let beneficiary: Location = AccountId32 { id: [0u8; 32], network: None }.into(); + // This is invalid, since it doesn't pay for fees. + // This is enforced by the runtime, because the build() method doesn't exist + // on the resulting type. + // let message: Xcm<()> = Xcm::builder() + // .withdraw_asset(asset.clone().into()) + // .deposit_asset(asset.into(), beneficiary) + // .build(); + + // To be able to do that, we need to use the explicitly unpaid variant + let message: Xcm<()> = Xcm::builder_unpaid() + .unpaid_execution(Unlimited, None) + .withdraw_asset(asset.clone().into()) + .deposit_asset(asset.clone().into(), beneficiary.clone()) + .build(); // This works + assert_eq!( + message, + Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + WithdrawAsset(asset.clone().into()), + DepositAsset { assets: asset.clone().into(), beneficiary: beneficiary.clone() }, + ]) + ); + + // The other option doesn't have any limits whatsoever, so it should + // only be used when you really know what you're doing. + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(asset.clone().into()) + .deposit_asset(asset.clone().into(), beneficiary.clone()) + .build(); + assert_eq!( + message, + Xcm(vec![ + WithdrawAsset(asset.clone().into()), + DepositAsset { assets: asset.clone().into(), beneficiary }, + ]) + ); +} diff --git a/polkadot/xcm/procedural/tests/conversion_functions.rs b/polkadot/xcm/procedural/tests/conversion_functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..5b6965167fcd318d3a7b10b3e56deeebf13ba7cb --- /dev/null +++ b/polkadot/xcm/procedural/tests/conversion_functions.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 . + +use xcm::v2::prelude::*; + +#[test] +fn slice_syntax_in_v2_works() { + let old_junctions = Junctions::X2(Parachain(1), PalletInstance(1)); + let new_junctions = Junctions::from([Parachain(1), PalletInstance(1)]); + assert_eq!(old_junctions, new_junctions); +} diff --git a/polkadot/xcm/procedural/tests/ui.rs b/polkadot/xcm/procedural/tests/ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..b3469b520eb77cbe1329fc5428932e3467757991 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui.rs @@ -0,0 +1,32 @@ +// 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 . + +//! UI tests for XCM procedural macros + +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + + // As trybuild is using `cargo check`, we don't need the real WASM binaries. + std::env::set_var("SKIP_WASM_BUILD", "1"); + + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/**/*.rs"); +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.rs new file mode 100644 index 0000000000000000000000000000000000000000..3a103f3ddc459dcb59a07a40931242aa1eab3c1c --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.rs @@ -0,0 +1,32 @@ +// 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 error when using a badly formatted attribute. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + #[builder(funds_holding = 2)] + WithdrawAsset(u128), + BuyExecution { fees: u128 }, + UnpaidExecution { weight_limit: (u32, u32) }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.stderr new file mode 100644 index 0000000000000000000000000000000000000000..978faf2e868d89ea47276bb5a7fed40c529e6336 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/badly_formatted_attribute.stderr @@ -0,0 +1,5 @@ +error: Expected `builder(loads_holding)` + --> tests/ui/builder_pattern/badly_formatted_attribute.rs:25:5 + | +25 | #[builder(funds_holding = 2)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc5c679a96e72b92c0095e246ef487132dad4f69 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.rs @@ -0,0 +1,30 @@ +// 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 error when the `BuyExecution` instruction doesn't take named fields. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + BuyExecution(u128), + UnpaidExecution { weight_limit: (u32, u32) }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.stderr new file mode 100644 index 0000000000000000000000000000000000000000..dc8246770ba3e10ed0df45a714dd2d9eb337cc5e --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/buy_execution_named_fields.stderr @@ -0,0 +1,5 @@ +error: BuyExecution should have named fields + --> tests/ui/builder_pattern/buy_execution_named_fields.rs:25:5 + | +25 | BuyExecution(u128), + | ^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs new file mode 100644 index 0000000000000000000000000000000000000000..070f0be6bacc995aa38a341fa9d242d395c4e045 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs @@ -0,0 +1,32 @@ +// 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 error when an instruction that loads the holding register doesn't take operands. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + #[builder(loads_holding)] + WithdrawAsset, + BuyExecution { fees: u128 }, + UnpaidExecution { weight_limit: (u32, u32) }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr new file mode 100644 index 0000000000000000000000000000000000000000..0358a35ad3dd7bb48ddd51b69e4f395642d44edf --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr @@ -0,0 +1,6 @@ +error: Instructions that load the holding register should take operands + --> tests/ui/builder_pattern/loads_holding_no_operands.rs:25:5 + | +25 | / #[builder(loads_holding)] +26 | | WithdrawAsset, + | |_________________^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.rs new file mode 100644 index 0000000000000000000000000000000000000000..1ed8dd38cbad5b32bb9ce1a38470fb579b3cb5c2 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.rs @@ -0,0 +1,29 @@ +// 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 error when there's no `BuyExecution` instruction. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + UnpaidExecution { weight_limit: (u32, u32) }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.stderr new file mode 100644 index 0000000000000000000000000000000000000000..d8798c8223f18e74e8ec6f409923da482547c9ec --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_buy_execution.stderr @@ -0,0 +1,6 @@ +error: No BuyExecution instruction + --> tests/ui/builder_pattern/no_buy_execution.rs:25:5 + | +25 | / UnpaidExecution { weight_limit: (u32, u32) }, +26 | | Transact { call: Call }, + | |____________________________^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.rs new file mode 100644 index 0000000000000000000000000000000000000000..d542102d2d35796736eb682377bc8015096186af --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.rs @@ -0,0 +1,29 @@ +// 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 error when there's no `UnpaidExecution` instruction. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + BuyExecution { fees: u128 }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.stderr new file mode 100644 index 0000000000000000000000000000000000000000..c8c0748da7220dd49fdcc196a4fa3ab353564e8c --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/no_unpaid_execution.stderr @@ -0,0 +1,6 @@ +error: No UnpaidExecution instruction + --> tests/ui/builder_pattern/no_unpaid_execution.rs:25:5 + | +25 | / BuyExecution { fees: u128 }, +26 | | Transact { call: Call }, + | |____________________________^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.rs new file mode 100644 index 0000000000000000000000000000000000000000..5808ec571ce75f3d7e25ae8489137ac90ced5687 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.rs @@ -0,0 +1,32 @@ +// 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 error when using wrong attribute. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + #[builder(funds_holding)] + WithdrawAsset(u128), + BuyExecution { fees: u128 }, + UnpaidExecution { weight_limit: (u32, u32) }, + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr new file mode 100644 index 0000000000000000000000000000000000000000..1ff9d18513686293bc56f438c2bd7fa543820c84 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr @@ -0,0 +1,5 @@ +error: Expected `builder(loads_holding)` + --> tests/ui/builder_pattern/unexpected_attribute.rs:25:5 + | +25 | #[builder(funds_holding)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb98d603fd91567406063b72d6133ba87671b4c9 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs @@ -0,0 +1,30 @@ +// 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 error when the `BuyExecution` instruction doesn't take named fields. + +use xcm_procedural::Builder; + +struct Xcm(pub Vec>); + +#[derive(Builder)] +enum Instruction { + BuyExecution { fees: u128 }, + UnpaidExecution(u32, u32), + Transact { call: Call }, +} + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr new file mode 100644 index 0000000000000000000000000000000000000000..0a3c0a40a33b0a5224f6b940c1ed18552a15fdf3 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr @@ -0,0 +1,5 @@ +error: UnpaidExecution should have named fields + --> tests/ui/builder_pattern/unpaid_execution_named_fields.rs:26:5 + | +26 | UnpaidExecution(u32, u32), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.rs b/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.rs new file mode 100644 index 0000000000000000000000000000000000000000..e4bcda572ad74ccaafb0c144cb4606690db7639b --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.rs @@ -0,0 +1,25 @@ +// 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 error when attaching the derive builder macro to something +//! other than the XCM `Instruction` enum. + +use xcm_procedural::Builder; + +#[derive(Builder)] +struct SomeStruct; + +fn main() {} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.stderr new file mode 100644 index 0000000000000000000000000000000000000000..007aa0b5ff3035761779a1220f364d980c30936b --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/wrong_target.stderr @@ -0,0 +1,5 @@ +error: Expected the `Instruction` enum + --> tests/ui/builder_pattern/wrong_target.rs:23:1 + | +23 | struct SomeStruct; + | ^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/src/double_encoded.rs b/polkadot/xcm/src/double_encoded.rs index 2dc9b012257e5bd2e47856eb7aadd97551366045..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) } } @@ -70,6 +71,11 @@ impl DoubleEncoded { pub fn as_ref(&self) -> Option<&T> { self.decoded.as_ref() } + + /// Access the encoded data. + pub fn into_encoded(self) -> Vec { + self.encoded + } } impl DoubleEncoded { diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index d804e4bf7351b3fffe5df2e4bfcba35d52b7704d..ba8d726aecff4989417373fc9e14c174d5a5277f 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -30,13 +30,14 @@ use scale_info::TypeInfo; pub mod v2; pub mod v3; +pub mod v4; pub mod lts { - pub use super::v3::*; + pub use super::v4::*; } pub mod latest { - pub use super::v3::*; + pub use super::v4::*; } mod double_encoded; @@ -79,6 +80,8 @@ macro_rules! versioned_type { ($(#[$attr:meta])* pub enum $n:ident { $(#[$index3:meta])+ V3($v3:ty), + $(#[$index4:meta])+ + V4($v4:ty), }) => { #[derive(Derivative, Encode, Decode, TypeInfo)] #[derivative( @@ -94,6 +97,8 @@ macro_rules! versioned_type { pub enum $n { $(#[$index3])* V3($v3), + $(#[$index4])* + V4($v4), } impl $n { pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { @@ -104,6 +109,15 @@ macro_rules! versioned_type { fn try_as(&self) -> Result<&$v3, ()> { match &self { Self::V3(ref x) => Ok(x), + _ => Err(()), + } + } + } + impl TryAs<$v4> for $n { + fn try_as(&self) -> Result<&$v4, ()> { + match &self { + Self::V4(ref x) => Ok(x), + _ => Err(()), } } } @@ -111,21 +125,38 @@ macro_rules! versioned_type { fn into_version(self, n: Version) -> Result { Ok(match n { 3 => Self::V3(self.try_into()?), + 4 => Self::V4(self.try_into()?), _ => return Err(()), }) } } - impl> From for $n { - fn from(x: T) -> Self { + impl From<$v3> for $n { + fn from(x: $v3) -> Self { $n::V3(x.into()) } } + impl From<$v4> for $n { + fn from(x: $v4) -> Self { + $n::V4(x.into()) + } + } impl TryFrom<$n> for $v3 { type Error = (); fn try_from(x: $n) -> Result { use $n::*; match x { V3(x) => Ok(x), + V4(x) => x.try_into(), + } + } + } + impl TryFrom<$n> for $v4 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V3(x) => x.try_into().map_err(|_| ()), + V4(x) => Ok(x), } } } @@ -134,6 +165,15 @@ macro_rules! versioned_type { <$v3>::max_encoded_len() } } + impl IdentifyVersion for $n { + fn identify_version(&self) -> Version { + use $n::*; + match self { + V3(_) => v3::VERSION, + V4(_) => v4::VERSION, + } + } + } }; ($(#[$attr:meta])* pub enum $n:ident { @@ -141,6 +181,8 @@ macro_rules! versioned_type { V2($v2:ty), $(#[$index3:meta])+ V3($v3:ty), + $(#[$index4:meta])+ + V4($v4:ty), }) => { #[derive(Derivative, Encode, Decode, TypeInfo)] #[derivative( @@ -158,6 +200,8 @@ macro_rules! versioned_type { V2($v2), $(#[$index3])* V3($v3), + $(#[$index4])* + V4($v4), } impl $n { pub fn try_as(&self) -> Result<&T, ()> where Self: TryAs { @@ -180,11 +224,20 @@ macro_rules! versioned_type { } } } + impl TryAs<$v4> for $n { + fn try_as(&self) -> Result<&$v4, ()> { + match &self { + Self::V4(ref x) => Ok(x), + _ => Err(()), + } + } + } impl IntoVersion for $n { fn into_version(self, n: Version) -> Result { Ok(match n { 1 | 2 => Self::V2(self.try_into()?), 3 => Self::V3(self.try_into()?), + 4 => Self::V4(self.try_into()?), _ => return Err(()), }) } @@ -194,9 +247,9 @@ macro_rules! versioned_type { $n::V2(x) } } - impl> From for $n { + impl> From for $n { fn from(x: T) -> Self { - $n::V3(x.into()) + $n::V4(x.into()) } } impl TryFrom<$n> for $v2 { @@ -206,6 +259,10 @@ macro_rules! versioned_type { match x { V2(x) => Ok(x), V3(x) => x.try_into(), + V4(x) => { + let v3: $v3 = x.try_into().map_err(|_| ())?; + v3.try_into() + }, } } } @@ -216,6 +273,21 @@ macro_rules! versioned_type { match x { V2(x) => x.try_into(), V3(x) => Ok(x), + V4(x) => x.try_into().map_err(|_| ()), + } + } + } + impl TryFrom<$n> for $v4 { + type Error = (); + fn try_from(x: $n) -> Result { + use $n::*; + match x { + V2(x) => { + let v3: $v3 = x.try_into().map_err(|_| ())?; + v3.try_into().map_err(|_| ()) + }, + V3(x) => x.try_into().map_err(|_| ()), + V4(x) => Ok(x), } } } @@ -224,14 +296,26 @@ macro_rules! versioned_type { <$v3>::max_encoded_len() } } + impl IdentifyVersion for $n { + fn identify_version(&self) -> Version { + use $n::*; + match self { + V2(_) => v2::VERSION, + V3(_) => v3::VERSION, + V4(_) => v4::VERSION, + } + } + } }; } versioned_type! { - /// A single version's `Response` value, together with its version code. + /// A single version's `AssetId` value, together with its version code. pub enum VersionedAssetId { #[codec(index = 3)] V3(v3::AssetId), + #[codec(index = 4)] + V4(v4::AssetId), } } @@ -242,6 +326,8 @@ versioned_type! { V2(v2::Response), #[codec(index = 3)] V3(v3::Response), + #[codec(index = 4)] + V4(v4::Response), } } @@ -252,6 +338,8 @@ versioned_type! { V2(v2::NetworkId), #[codec(index = 3)] V3(v3::NetworkId), + #[codec(index = 4)] + V4(v4::NetworkId), } } @@ -262,50 +350,72 @@ versioned_type! { V2(v2::Junction), #[codec(index = 3)] V3(v3::Junction), + #[codec(index = 4)] + V4(v4::Junction), } } versioned_type! { - /// A single `MultiLocation` value, together with its version code. + /// A single `Location` value, together with its version code. #[derive(Ord, PartialOrd)] - pub enum VersionedMultiLocation { + pub enum VersionedLocation { #[codec(index = 1)] // v2 is same as v1 and therefore re-using the v1 index V2(v2::MultiLocation), #[codec(index = 3)] V3(v3::MultiLocation), + #[codec(index = 4)] + V4(v4::Location), } } +#[deprecated(note = "Use `VersionedLocation` instead")] +pub type VersionedMultiLocation = VersionedLocation; + versioned_type! { - /// A single `InteriorMultiLocation` value, together with its version code. - pub enum VersionedInteriorMultiLocation { - #[codec(index = 2)] // while this is same as v1::Junctions, VersionedInteriorMultiLocation is introduced in v3 + /// A single `InteriorLocation` value, together with its version code. + pub enum VersionedInteriorLocation { + #[codec(index = 2)] // while this is same as v1::Junctions, VersionedInteriorLocation is introduced in v3 V2(v2::InteriorMultiLocation), #[codec(index = 3)] V3(v3::InteriorMultiLocation), + #[codec(index = 4)] + V4(v4::InteriorLocation), } } +#[deprecated(note = "Use `VersionedInteriorLocation` instead")] +pub type VersionedInteriorMultiLocation = VersionedInteriorLocation; + versioned_type! { - /// A single `MultiAsset` value, together with its version code. - pub enum VersionedMultiAsset { + /// A single `Asset` value, together with its version code. + pub enum VersionedAsset { #[codec(index = 1)] // v2 is same as v1 and therefore re-using the v1 index V2(v2::MultiAsset), #[codec(index = 3)] V3(v3::MultiAsset), + #[codec(index = 4)] + V4(v4::Asset), } } +#[deprecated(note = "Use `VersionedAsset` instead")] +pub type VersionedMultiAsset = VersionedAsset; + versioned_type! { /// A single `MultiAssets` value, together with its version code. - pub enum VersionedMultiAssets { + pub enum VersionedAssets { #[codec(index = 1)] // v2 is same as v1 and therefore re-using the v1 index V2(v2::MultiAssets), #[codec(index = 3)] V3(v3::MultiAssets), + #[codec(index = 4)] + V4(v4::Assets), } } +#[deprecated(note = "Use `VersionedAssets` instead")] +pub type VersionedMultiAssets = VersionedAssets; + /// A single XCM message, together with its version code. #[derive(Derivative, Encode, Decode, TypeInfo)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] @@ -318,6 +428,8 @@ pub enum VersionedXcm { V2(v2::Xcm), #[codec(index = 3)] V3(v3::Xcm), + #[codec(index = 4)] + V4(v4::Xcm), } impl IntoVersion for VersionedXcm { @@ -325,6 +437,7 @@ impl IntoVersion for VersionedXcm { Ok(match n { 2 => Self::V2(self.try_into()?), 3 => Self::V3(self.try_into()?), + 4 => Self::V4(self.try_into()?), _ => return Err(()), }) } @@ -342,6 +455,12 @@ impl From> for VersionedXcm { } } +impl From> for VersionedXcm { + fn from(x: v4::Xcm) -> Self { + VersionedXcm::V4(x) + } +} + impl TryFrom> for v2::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { @@ -349,6 +468,10 @@ impl TryFrom> for v2::Xcm { match x { V2(x) => Ok(x), V3(x) => x.try_into(), + V4(x) => { + let v3: v3::Xcm = x.try_into()?; + v3.try_into() + }, } } } @@ -360,24 +483,52 @@ impl TryFrom> for v3::Xcm { match x { V2(x) => x.try_into(), V3(x) => Ok(x), + V4(x) => x.try_into(), } } } -/// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `MultiLocation` which will +impl TryFrom> for v4::Xcm { + type Error = (); + fn try_from(x: VersionedXcm) -> Result { + use VersionedXcm::*; + match x { + V2(x) => { + let v3: v3::Xcm = x.try_into()?; + v3.try_into() + }, + V3(x) => x.try_into(), + V4(x) => Ok(x), + } + } +} + +/// Convert an `Xcm` datum into a `VersionedXcm`, based on a destination `Location` which will /// interpret it. pub trait WrapVersion { fn wrap_version( - dest: &latest::MultiLocation, + dest: &latest::Location, xcm: impl Into>, ) -> Result, ()>; } +/// Used to get the version out of a versioned type. +// TODO(XCMv5): This could be `GetVersion` and we change the current one to `GetVersionFor`. +pub trait IdentifyVersion { + fn identify_version(&self) -> Version; +} + +/// Check and return the `Version` that should be used for the `Xcm` datum for the destination +/// `Location`, which will interpret it. +pub trait GetVersion { + fn get_version_for(dest: &latest::Location) -> Option; +} + /// `()` implementation does nothing with the XCM, just sending with whatever version it was /// authored as. impl WrapVersion for () { fn wrap_version( - _: &latest::MultiLocation, + _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(xcm.into()) @@ -389,39 +540,66 @@ impl WrapVersion for () { pub struct AlwaysV2; impl WrapVersion for AlwaysV2 { fn wrap_version( - _: &latest::MultiLocation, + _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(VersionedXcm::::V2(xcm.into().try_into()?)) } } +impl GetVersion for AlwaysV2 { + fn get_version_for(_dest: &latest::Location) -> Option { + Some(v2::VERSION) + } +} /// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before /// wrapping it. pub struct AlwaysV3; impl WrapVersion for AlwaysV3 { fn wrap_version( - _: &latest::MultiLocation, + _: &latest::Location, xcm: impl Into>, ) -> Result, ()> { Ok(VersionedXcm::::V3(xcm.into().try_into()?)) } } +impl GetVersion for AlwaysV3 { + fn get_version_for(_dest: &latest::Location) -> Option { + Some(v3::VERSION) + } +} + +/// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before +/// wrapping it. +pub struct AlwaysV4; +impl WrapVersion for AlwaysV4 { + fn wrap_version( + _: &latest::Location, + xcm: impl Into>, + ) -> Result, ()> { + Ok(VersionedXcm::::V4(xcm.into().try_into()?)) + } +} +impl GetVersion for AlwaysV4 { + fn get_version_for(_dest: &latest::Location) -> Option { + Some(v4::VERSION) + } +} /// `WrapVersion` implementation which attempts to always convert the XCM to the latest version /// before wrapping it. -pub type AlwaysLatest = AlwaysV3; +pub type AlwaysLatest = AlwaysV4; /// `WrapVersion` implementation which attempts to always convert the XCM to the most recent Long- /// Term-Support version before wrapping it. -pub type AlwaysLts = AlwaysV3; +pub type AlwaysLts = AlwaysV4; pub mod prelude { pub use super::{ - latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV2, AlwaysV3, IntoVersion, Unsupported, - Version as XcmVersion, VersionedAssetId, VersionedInteriorMultiLocation, - VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse, - VersionedXcm, WrapVersion, + latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV2, AlwaysV3, AlwaysV4, GetVersion, + IdentifyVersion, IntoVersion, Unsupported, Version as XcmVersion, VersionedAsset, + VersionedAssetId, VersionedAssets, VersionedInteriorLocation, VersionedLocation, + VersionedResponse, VersionedXcm, WrapVersion, }; } @@ -438,13 +616,19 @@ pub mod opaque { // Then override with the opaque types in v3 pub use crate::v3::opaque::{Instruction, Xcm}; } + pub mod v4 { + // Everything from v4 + pub use crate::v4::*; + // Then override with the opaque types in v4 + pub use crate::v4::opaque::{Instruction, Xcm}; + } pub mod latest { - pub use super::v3::*; + pub use super::v4::*; } pub mod lts { - pub use super::v3::*; + pub use super::v4::*; } /// The basic `VersionedXcm` type which just uses the `Vec` as an encoded call. @@ -454,5 +638,56 @@ pub mod opaque { #[test] fn conversion_works() { use latest::prelude::*; - let _: VersionedMultiAssets = (Here, 1u128).into(); + let assets: Assets = (Here, 1u128).into(); + let _: VersionedAssets = assets.into(); +} + +#[test] +fn size_limits() { + extern crate std; + + let mut test_failed = false; + macro_rules! check_sizes { + ($(($kind:ty, $expected:expr),)+) => { + $({ + let s = core::mem::size_of::<$kind>(); + // Since the types often affect the size of other types in which they're included + // it is more convenient to check multiple types at the same time and only fail + // the test at the end. For debugging it's also useful to print out all of the sizes, + // even if they're within the expected range. + if s > $expected { + test_failed = true; + std::eprintln!( + "assertion failed: size of '{}' is {} (which is more than the expected {})", + stringify!($kind), + s, + $expected + ); + } else { + std::println!( + "type '{}' is of size {} which is within the expected {}", + stringify!($kind), + s, + $expected + ); + } + })+ + } + } + + check_sizes! { + (crate::latest::Instruction<()>, 112), + (crate::latest::Asset, 80), + (crate::latest::Location, 24), + (crate::latest::AssetId, 40), + (crate::latest::Junctions, 16), + (crate::latest::Junction, 88), + (crate::latest::Response, 40), + (crate::latest::AssetInstance, 48), + (crate::latest::NetworkId, 48), + (crate::latest::BodyId, 32), + (crate::latest::Assets, 24), + (crate::latest::BodyPart, 12), + } + assert!(!test_failed); } diff --git a/polkadot/xcm/src/tests.rs b/polkadot/xcm/src/tests.rs index a3a60f6477c798094e2d1f13327ff995fe1bdc6d..1aabbcef281d6638b4eabf3c077b5091514d07f0 100644 --- a/polkadot/xcm/src/tests.rs +++ b/polkadot/xcm/src/tests.rs @@ -59,79 +59,79 @@ fn encode_decode_versioned_response_v3() { #[test] fn encode_decode_versioned_multi_location_v2() { - let location = VersionedMultiLocation::V2(v2::MultiLocation::new(0, v2::Junctions::Here)); + let location = VersionedLocation::V2(v2::MultiLocation::new(0, v2::Junctions::Here)); let encoded = location.encode(); assert_eq!(encoded, hex_literal::hex!("010000"), "encode format changed"); assert_eq!(encoded[0], 1, "bad version number"); // this is introduced in v1 - let decoded = VersionedMultiLocation::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedLocation::decode(&mut &encoded[..]).unwrap(); assert_eq!(location, decoded); } #[test] fn encode_decode_versioned_multi_location_v3() { - let location = VersionedMultiLocation::V3(v3::MultiLocation::new(0, v3::Junctions::Here)); + let location = VersionedLocation::V3(v3::MultiLocation::new(0, v3::Junctions::Here)); let encoded = location.encode(); assert_eq!(encoded, hex_literal::hex!("030000"), "encode format changed"); assert_eq!(encoded[0], 3, "bad version number"); - let decoded = VersionedMultiLocation::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedLocation::decode(&mut &encoded[..]).unwrap(); assert_eq!(location, decoded); } #[test] fn encode_decode_versioned_interior_multi_location_v2() { - let location = VersionedInteriorMultiLocation::V2(v2::InteriorMultiLocation::Here); + let location = VersionedInteriorLocation::V2(v2::InteriorMultiLocation::Here); let encoded = location.encode(); assert_eq!(encoded, hex_literal::hex!("0200"), "encode format changed"); assert_eq!(encoded[0], 2, "bad version number"); - let decoded = VersionedInteriorMultiLocation::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedInteriorLocation::decode(&mut &encoded[..]).unwrap(); assert_eq!(location, decoded); } #[test] fn encode_decode_versioned_interior_multi_location_v3() { - let location = VersionedInteriorMultiLocation::V3(v3::InteriorMultiLocation::Here); + let location = VersionedInteriorLocation::V3(v3::InteriorMultiLocation::Here); let encoded = location.encode(); assert_eq!(encoded, hex_literal::hex!("0300"), "encode format changed"); assert_eq!(encoded[0], 3, "bad version number"); - let decoded = VersionedInteriorMultiLocation::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedInteriorLocation::decode(&mut &encoded[..]).unwrap(); assert_eq!(location, decoded); } #[test] fn encode_decode_versioned_multi_asset_v2() { - let asset = VersionedMultiAsset::V2(v2::MultiAsset::from(((0, v2::Junctions::Here), 1))); + let asset = VersionedAsset::V2(v2::MultiAsset::from(((0, v2::Junctions::Here), 1))); let encoded = asset.encode(); assert_eq!(encoded, hex_literal::hex!("010000000004"), "encode format changed"); assert_eq!(encoded[0], 1, "bad version number"); - let decoded = VersionedMultiAsset::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedAsset::decode(&mut &encoded[..]).unwrap(); assert_eq!(asset, decoded); } #[test] fn encode_decode_versioned_multi_asset_v3() { - let asset = VersionedMultiAsset::V3(v3::MultiAsset::from((v3::MultiLocation::default(), 1))); + let asset = VersionedAsset::V3(v3::MultiAsset::from((v3::MultiLocation::default(), 1))); let encoded = asset.encode(); assert_eq!(encoded, hex_literal::hex!("030000000004"), "encode format changed"); assert_eq!(encoded[0], 3, "bad version number"); - let decoded = VersionedMultiAsset::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedAsset::decode(&mut &encoded[..]).unwrap(); assert_eq!(asset, decoded); } #[test] fn encode_decode_versioned_multi_assets_v2() { - let assets = VersionedMultiAssets::V2(v2::MultiAssets::from(vec![v2::MultiAsset::from(( + let assets = VersionedAssets::V2(v2::MultiAssets::from(vec![v2::MultiAsset::from(( (0, v2::Junctions::Here), 1, ))])); @@ -140,13 +140,13 @@ fn encode_decode_versioned_multi_assets_v2() { assert_eq!(encoded, hex_literal::hex!("01040000000004"), "encode format changed"); assert_eq!(encoded[0], 1, "bad version number"); - let decoded = VersionedMultiAssets::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedAssets::decode(&mut &encoded[..]).unwrap(); assert_eq!(assets, decoded); } #[test] fn encode_decode_versioned_multi_assets_v3() { - let assets = VersionedMultiAssets::V3(v3::MultiAssets::from(vec![ + let assets = VersionedAssets::V3(v3::MultiAssets::from(vec![ (v3::MultiAsset::from((v3::MultiLocation::default(), 1))), ])); let encoded = assets.encode(); @@ -154,7 +154,7 @@ fn encode_decode_versioned_multi_assets_v3() { assert_eq!(encoded, hex_literal::hex!("03040000000004"), "encode format changed"); assert_eq!(encoded[0], 3, "bad version number"); - let decoded = VersionedMultiAssets::decode(&mut &encoded[..]).unwrap(); + let decoded = VersionedAssets::decode(&mut &encoded[..]).unwrap(); assert_eq!(assets, decoded); } @@ -187,6 +187,8 @@ fn encode_decode_versioned_xcm_v3() { #[test] fn ensure_type_info_is_correct() { let type_info = VersionedXcm::<()>::type_info(); - assert_eq!(type_info.path.segments, vec!["xcm", "VersionedXcm"]); + + let type_info = VersionedAssetId::type_info(); + assert_eq!(type_info.path.segments, vec!["xcm", "VersionedAssetId"]); } diff --git a/polkadot/xcm/src/v2/mod.rs b/polkadot/xcm/src/v2/mod.rs index 7f654ebfd9e9b242c920c7e88d02c2ce69714874..347f3f2c29206222ca546b7d543409da83cbedcf 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 @@ -1133,7 +1134,10 @@ impl TryFrom> for Instruction Self::UnsubscribeVersion, - _ => return Err(()), + i => { + log::debug!(target: "xcm::v3tov2", "`{i:?}` not supported by v2"); + return Err(()); + }, }) } } diff --git a/polkadot/xcm/src/v2/multilocation.rs b/polkadot/xcm/src/v2/multilocation.rs index 81b67eee9744bb58ff3a8303a5c7757b63642197..60aa1f6ceadf0fefb991660393dc0cdc91f07700 100644 --- a/polkadot/xcm/src/v2/multilocation.rs +++ b/polkadot/xcm/src/v2/multilocation.rs @@ -75,8 +75,8 @@ impl MultiLocation { MultiLocation { parents, interior: junctions } } - /// Consume `self` and return the equivalent `VersionedMultiLocation` value. - pub fn versioned(self) -> crate::VersionedMultiLocation { + /// Consume `self` and return the equivalent `VersionedLocation` value. + pub fn versioned(self) -> crate::VersionedLocation { self.into() } @@ -240,9 +240,9 @@ impl MultiLocation { /// # Example /// ```rust /// # use staging_xcm::v2::{Junctions::*, Junction::*, MultiLocation}; - /// let mut m = MultiLocation::new(1, X2(PalletInstance(3), OnlyChild)); + /// let mut m = MultiLocation::new(1, [PalletInstance(3), OnlyChild].into()); /// assert_eq!( - /// m.match_and_split(&MultiLocation::new(1, X1(PalletInstance(3)))), + /// m.match_and_split(&MultiLocation::new(1, [PalletInstance(3)].into())), /// Some(&OnlyChild), /// ); /// assert_eq!(m.match_and_split(&MultiLocation::new(1, Here)), None); @@ -260,10 +260,10 @@ impl MultiLocation { /// # Example /// ```rust /// # use staging_xcm::v2::{Junctions::*, Junction::*, MultiLocation}; - /// let m = MultiLocation::new(1, X3(PalletInstance(3), OnlyChild, OnlyChild)); - /// assert!(m.starts_with(&MultiLocation::new(1, X1(PalletInstance(3))))); - /// assert!(!m.starts_with(&MultiLocation::new(1, X1(GeneralIndex(99))))); - /// assert!(!m.starts_with(&MultiLocation::new(0, X1(PalletInstance(3))))); + /// let m = MultiLocation::new(1, [PalletInstance(3), OnlyChild, OnlyChild].into()); + /// assert!(m.starts_with(&MultiLocation::new(1, [PalletInstance(3)].into()))); + /// assert!(!m.starts_with(&MultiLocation::new(1, [GeneralIndex(99)].into()))); + /// assert!(!m.starts_with(&MultiLocation::new(0, [PalletInstance(3)].into()))); /// ``` pub fn starts_with(&self, prefix: &MultiLocation) -> bool { if self.parents != prefix.parents { @@ -279,9 +279,9 @@ impl MultiLocation { /// # Example /// ```rust /// # use staging_xcm::v2::{Junctions::*, Junction::*, MultiLocation}; - /// let mut m = MultiLocation::new(1, X1(Parachain(21))); - /// assert_eq!(m.append_with(X1(PalletInstance(3))), Ok(())); - /// assert_eq!(m, MultiLocation::new(1, X2(Parachain(21), PalletInstance(3)))); + /// let mut m = MultiLocation::new(1, [Parachain(21)].into()); + /// assert_eq!(m.append_with([PalletInstance(3)].into()), Ok(())); + /// assert_eq!(m, MultiLocation::new(1, [Parachain(21), PalletInstance(3)].into())); /// ``` pub fn append_with(&mut self, suffix: Junctions) -> Result<(), Junctions> { if self.interior.len().saturating_add(suffix.len()) > MAX_JUNCTIONS { @@ -300,9 +300,9 @@ impl MultiLocation { /// # Example /// ```rust /// # use staging_xcm::v2::{Junctions::*, Junction::*, MultiLocation}; - /// let mut m = MultiLocation::new(2, X1(PalletInstance(3))); - /// assert_eq!(m.prepend_with(MultiLocation::new(1, X2(Parachain(21), OnlyChild))), Ok(())); - /// assert_eq!(m, MultiLocation::new(1, X1(PalletInstance(3)))); + /// let mut m = MultiLocation::new(2, [PalletInstance(3)].into()); + /// assert_eq!(m.prepend_with(MultiLocation::new(1, [Parachain(21), OnlyChild].into())), Ok(())); + /// assert_eq!(m, MultiLocation::new(1, [PalletInstance(3)].into())); /// ``` pub fn prepend_with(&mut self, mut prefix: MultiLocation) -> Result<(), MultiLocation> { // prefix self (suffix) @@ -455,6 +455,7 @@ impl> From> for MultiLocation { } xcm_procedural::impl_conversion_functions_for_multilocation_v2!(); +xcm_procedural::impl_conversion_functions_for_junctions_v2!(); /// Maximum number of `Junction`s that a `Junctions` can contain. const MAX_JUNCTIONS: usize = 8; diff --git a/polkadot/xcm/src/v2/traits.rs b/polkadot/xcm/src/v2/traits.rs index 6453f91a1f1e2d43b5366fc60fd990f1869e2c85..9cfb9b051ab2a92f3cf0e405795fdd31460b2681 100644 --- a/polkadot/xcm/src/v2/traits.rs +++ b/polkadot/xcm/src/v2/traits.rs @@ -292,11 +292,12 @@ pub type SendResult = result::Result<(), SendError>; /// } /// } /// -/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. +/// /// A sender that accepts a message that has two junctions, otherwise stops the routing. /// struct Sender2; /// impl SendXcm for Sender2 { /// fn send_xcm(destination: impl Into, message: Xcm<()>) -> SendResult { -/// if let MultiLocation { parents: 0, interior: X2(j1, j2) } = destination.into() { +/// let destination = destination.into(); +/// if destination.parents == 0 && destination.interior.len() == 2 { /// Ok(()) /// } else { /// Err(SendError::Unroutable) diff --git a/polkadot/xcm/src/v3/junction.rs b/polkadot/xcm/src/v3/junction.rs index 47429a8c36e9a76c29b495feb0e2b765e8c7a21a..e9e51941b1ac0c50130ad5501c4b6f374656152a 100644 --- a/polkadot/xcm/src/v3/junction.rs +++ b/polkadot/xcm/src/v3/junction.rs @@ -22,7 +22,8 @@ use crate::{ BodyId as OldBodyId, BodyPart as OldBodyPart, Junction as OldJunction, NetworkId as OldNetworkId, }, - VersionedMultiLocation, + v4::{Junction as NewJunction, NetworkId as NewNetworkId}, + VersionedLocation, }; use bounded_collections::{BoundedSlice, BoundedVec, ConstU32}; use core::convert::{TryFrom, TryInto}; @@ -49,6 +50,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. @@ -75,6 +77,8 @@ pub enum NetworkId { BitcoinCore, /// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers. BitcoinCash, + /// The Polkadot Bulletin chain. + PolkadotBulletin, } impl From for Option { @@ -101,6 +105,31 @@ impl TryFrom for NetworkId { } } +impl From for Option { + fn from(new: NewNetworkId) -> Self { + Some(NetworkId::from(new)) + } +} + +impl From for NetworkId { + fn from(new: NewNetworkId) -> Self { + use NewNetworkId::*; + match new { + ByGenesis(hash) => Self::ByGenesis(hash), + ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash }, + Polkadot => Self::Polkadot, + Kusama => Self::Kusama, + Westend => Self::Westend, + Rococo => Self::Rococo, + Wococo => Self::Wococo, + Ethereum { chain_id } => Self::Ethereum { chain_id }, + BitcoinCore => Self::BitcoinCore, + BitcoinCash => Self::BitcoinCash, + PolkadotBulletin => Self::PolkadotBulletin, + } + } +} + /// An identifier of a pluralistic body. #[derive( Copy, @@ -117,6 +146,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. @@ -188,6 +218,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. @@ -264,6 +295,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. @@ -408,6 +440,29 @@ impl TryFrom for Junction { } } +impl TryFrom for Junction { + type Error = (); + + fn try_from(value: NewJunction) -> Result { + use NewJunction::*; + Ok(match value { + Parachain(id) => Self::Parachain(id), + AccountId32 { network: maybe_network, id } => + Self::AccountId32 { network: maybe_network.map(|network| network.into()), id }, + AccountIndex64 { network: maybe_network, index } => + Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index }, + AccountKey20 { network: maybe_network, key } => + Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key }, + PalletInstance(index) => Self::PalletInstance(index), + GeneralIndex(id) => Self::GeneralIndex(id), + GeneralKey { length, data } => Self::GeneralKey { length, data }, + OnlyChild => Self::OnlyChild, + Plurality { id, part } => Self::Plurality { id, part }, + GlobalConsensus(network) => Self::GlobalConsensus(network.into()), + }) + } +} + impl Junction { /// Convert `self` into a `MultiLocation` containing 0 parents. /// @@ -424,10 +479,10 @@ impl Junction { MultiLocation { parents: n, interior: Junctions::X1(self) } } - /// Convert `self` into a `VersionedMultiLocation` containing 0 parents. + /// Convert `self` into a `VersionedLocation` containing 0 parents. /// /// Similar to `Into::into`, except that this method can be used in a const evaluation context. - pub const fn into_versioned(self) -> VersionedMultiLocation { + pub const fn into_versioned(self) -> VersionedLocation { self.into_location().into_versioned() } diff --git a/polkadot/xcm/src/v3/junctions.rs b/polkadot/xcm/src/v3/junctions.rs index d1cbc2dbed42cddd4fae2eae398a120e2272679b..9748e81fa55f53c90c8a78419fce410ea1adb82d 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. @@ -66,6 +67,27 @@ pub enum Junctions { X8(Junction, Junction, Junction, Junction, Junction, Junction, Junction, Junction), } +macro_rules! impl_junction { + ($count:expr, $variant:ident, ($($index:literal),+)) => { + /// Additional helper for building junctions + /// Useful for converting to future XCM versions + impl From<[Junction; $count]> for Junctions { + fn from(junctions: [Junction; $count]) -> Self { + Self::$variant($(junctions[$index]),*) + } + } + }; +} + +impl_junction!(1, X1, (0)); +impl_junction!(2, X2, (0, 1)); +impl_junction!(3, X3, (0, 1, 2)); +impl_junction!(4, X4, (0, 1, 2, 3)); +impl_junction!(5, X5, (0, 1, 2, 3, 4)); +impl_junction!(6, X6, (0, 1, 2, 3, 4, 5)); +impl_junction!(7, X7, (0, 1, 2, 3, 4, 5, 6)); +impl_junction!(8, X8, (0, 1, 2, 3, 4, 5, 6, 7)); + pub struct JunctionsIterator(Junctions); impl Iterator for JunctionsIterator { type Item = Junction; diff --git a/polkadot/xcm/src/v3/mod.rs b/polkadot/xcm/src/v3/mod.rs index f9f31b752a96435733281a40f55363572ab8de19..d4e2da07a25ac9dbf4ca7177ed09f0abd26b46d3 100644 --- a/polkadot/xcm/src/v3/mod.rs +++ b/polkadot/xcm/src/v3/mod.rs @@ -16,9 +16,15 @@ //! Version 3 of the Cross-Consensus Message format data structures. -use super::v2::{ - Instruction as OldInstruction, Response as OldResponse, WeightLimit as OldWeightLimit, - Xcm as OldXcm, +use super::{ + v2::{ + Instruction as OldInstruction, Response as OldResponse, WeightLimit as OldWeightLimit, + Xcm as OldXcm, + }, + v4::{ + Instruction as NewInstruction, PalletInfo as NewPalletInfo, + QueryResponseInfo as NewQueryResponseInfo, Response as NewResponse, Xcm as NewXcm, + }, }; use crate::DoubleEncoded; use alloc::{vec, vec::Vec}; @@ -48,7 +54,7 @@ pub use multiasset::{ WildFungibility, WildMultiAsset, MAX_ITEMS_IN_MULTIASSETS, }; pub use multilocation::{ - Ancestor, AncestorThen, InteriorMultiLocation, MultiLocation, Parent, ParentThen, + Ancestor, AncestorThen, InteriorMultiLocation, Location, MultiLocation, Parent, ParentThen, }; pub use traits::{ send_xcm, validate_send, Error, ExecuteXcm, Outcome, PreparedMessage, Result, SendError, @@ -63,14 +69,17 @@ pub const VERSION: super::Version = 3; /// An identifier for a query. pub type QueryId = u64; -// TODO (v4): Use `BoundedVec` instead of `Vec` #[derive(Derivative, Default, Encode, TypeInfo)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] #[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. +/// +/// This is a deliberate limit - not a technical one. pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100; environmental::environmental!(instructions_count: u8); @@ -205,7 +214,7 @@ pub mod prelude { InteriorMultiLocation, Junction::{self, *}, Junctions::{self, *}, - MaybeErrorCode, MultiAsset, + Location, MaybeErrorCode, MultiAsset, MultiAssetFilter::{self, *}, MultiAssets, MultiLocation, NetworkId::{self, *}, @@ -229,26 +238,30 @@ 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)] - index: u32, - name: BoundedVec, - module_name: BoundedVec, + pub index: u32, + pub name: BoundedVec, + pub module_name: BoundedVec, #[codec(compact)] - major: u32, + pub major: u32, #[codec(compact)] - minor: u32, + pub minor: u32, #[codec(compact)] - patch: u32, + pub patch: u32, } impl PalletInfo { @@ -267,8 +280,25 @@ impl PalletInfo { } } +impl TryInto for PalletInfo { + type Error = (); + + fn try_into(self) -> result::Result { + NewPalletInfo::new( + self.index, + self.name.into_inner(), + self.module_name.into_inner(), + self.major, + self.minor, + self.patch, + ) + .map_err(|_| ()) + } +} + #[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), @@ -293,6 +323,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, @@ -314,9 +345,36 @@ impl Default for Response { } } +impl TryFrom for Response { + type Error = (); + + fn try_from(new: NewResponse) -> result::Result { + use NewResponse::*; + Ok(match new { + Null => Self::Null, + Assets(assets) => Self::Assets(assets.try_into()?), + ExecutionResult(result) => + Self::ExecutionResult(result.map(|(num, old_error)| (num, old_error.into()))), + Version(version) => Self::Version(version), + PalletsInfo(pallet_info) => { + let inner = pallet_info + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + Self::PalletsInfo( + BoundedVec::::try_from(inner).map_err(|_| ())?, + ) + }, + DispatchResult(maybe_error) => + Self::DispatchResult(maybe_error.try_into().map_err(|_| ())?), + }) + } +} + /// 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, @@ -327,9 +385,22 @@ pub struct QueryResponseInfo { pub max_weight: Weight, } +impl TryFrom for QueryResponseInfo { + type Error = (); + + fn try_from(new: NewQueryResponseInfo) -> result::Result { + Ok(Self { + destination: new.destination.try_into()?, + query_id: new.query_id, + max_weight: new.max_weight, + }) + } +} + /// 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, @@ -355,13 +426,12 @@ impl From for Option { } } -impl TryFrom for WeightLimit { - type Error = (); - fn try_from(x: OldWeightLimit) -> result::Result { +impl From for WeightLimit { + fn from(x: OldWeightLimit) -> Self { use OldWeightLimit::*; match x { - Limited(w) => Ok(Self::Limited(Weight::from_parts(w, DEFAULT_PROOF_SIZE))), - Unlimited => Ok(Self::Unlimited), + Limited(w) => Self::Limited(Weight::from_parts(w, DEFAULT_PROOF_SIZE)), + Unlimited => Self::Unlimited, } } } @@ -401,12 +471,20 @@ impl XcmContext { /// /// This is the inner XCM format and is version-sensitive. Messages are typically passed using the /// outer XCM format, known as `VersionedXcm`. -#[derive(Derivative, Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait)] +#[derive( + Derivative, + Encode, + Decode, + TypeInfo, + xcm_procedural::XcmWeightInfoTrait, + xcm_procedural::Builder, +)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] #[codec(encode_bound())] #[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. @@ -416,6 +494,7 @@ pub enum Instruction { /// Kind: *Command*. /// /// Errors: + #[builder(loads_holding)] WithdrawAsset(MultiAssets), /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` @@ -429,6 +508,7 @@ pub enum Instruction { /// Kind: *Trusted Indication*. /// /// Errors: + #[builder(loads_holding)] ReserveAssetDeposited(MultiAssets), /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should @@ -442,6 +522,7 @@ pub enum Instruction { /// Kind: *Trusted Indication*. /// /// Errors: + #[builder(loads_holding)] ReceiveTeleportedAsset(MultiAssets), /// Respond with information that the local system is expecting. @@ -766,6 +847,7 @@ pub enum Instruction { /// Kind: *Command* /// /// Errors: + #[builder(loads_holding)] ClaimAsset { assets: MultiAssets, ticket: MultiLocation }, /// Always throws an error of type `Trap`. @@ -1239,6 +1321,155 @@ impl TryFrom> for Xcm { } } +// Convert from a v4 XCM to a v3 XCM. +impl TryFrom> for Xcm { + type Error = (); + fn try_from(new_xcm: NewXcm) -> result::Result { + Ok(Xcm(new_xcm.0.into_iter().map(TryInto::try_into).collect::>()?)) + } +} + +// Convert from a v4 instruction to a v3 instruction. +impl TryFrom> for Instruction { + type Error = (); + fn try_from(new_instruction: NewInstruction) -> result::Result { + use NewInstruction::*; + Ok(match new_instruction { + WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?), + ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?), + ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?), + QueryResponse { query_id, response, max_weight, querier: Some(querier) } => + Self::QueryResponse { + query_id, + querier: querier.try_into()?, + response: response.try_into()?, + max_weight, + }, + QueryResponse { query_id, response, max_weight, querier: None } => + Self::QueryResponse { + query_id, + querier: None, + response: response.try_into()?, + max_weight, + }, + TransferAsset { assets, beneficiary } => Self::TransferAsset { + assets: assets.try_into()?, + beneficiary: beneficiary.try_into()?, + }, + TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset { + assets: assets.try_into()?, + dest: dest.try_into()?, + xcm: xcm.try_into()?, + }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + Self::HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_kind, require_weight_at_most, call } => + Self::Transact { origin_kind, require_weight_at_most, call: call.into() }, + ReportError(response_info) => Self::ReportError(QueryResponseInfo { + query_id: response_info.query_id, + destination: response_info.destination.try_into().map_err(|_| ())?, + max_weight: response_info.max_weight, + }), + DepositAsset { assets, beneficiary } => { + let beneficiary = beneficiary.try_into()?; + let assets = assets.try_into()?; + Self::DepositAsset { assets, beneficiary } + }, + DepositReserveAsset { assets, dest, xcm } => { + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + let assets = assets.try_into()?; + Self::DepositReserveAsset { assets, dest, xcm } + }, + ExchangeAsset { give, want, maximal } => { + let give = give.try_into()?; + let want = want.try_into()?; + Self::ExchangeAsset { give, want, maximal } + }, + InitiateReserveWithdraw { assets, reserve, xcm } => { + // No `max_assets` here, so if there's a connt, then we cannot translate. + let assets = assets.try_into()?; + let reserve = reserve.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateReserveWithdraw { assets, reserve, xcm } + }, + InitiateTeleport { assets, dest, xcm } => { + // No `max_assets` here, so if there's a connt, then we cannot translate. + let assets = assets.try_into()?; + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateTeleport { assets, dest, xcm } + }, + ReportHolding { response_info, assets } => { + let response_info = QueryResponseInfo { + destination: response_info.destination.try_into().map_err(|_| ())?, + query_id: response_info.query_id, + max_weight: response_info.max_weight, + }; + Self::ReportHolding { response_info, assets: assets.try_into()? } + }, + BuyExecution { fees, weight_limit } => { + let fees = fees.try_into()?; + let weight_limit = weight_limit.into(); + Self::BuyExecution { fees, weight_limit } + }, + ClearOrigin => Self::ClearOrigin, + DescendOrigin(who) => Self::DescendOrigin(who.try_into()?), + RefundSurplus => Self::RefundSurplus, + SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?), + SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?), + ClearError => Self::ClearError, + ClaimAsset { assets, ticket } => { + let assets = assets.try_into()?; + let ticket = ticket.try_into()?; + Self::ClaimAsset { assets, ticket } + }, + Trap(code) => Self::Trap(code), + SubscribeVersion { query_id, max_response_weight } => + Self::SubscribeVersion { query_id, max_response_weight }, + UnsubscribeVersion => Self::UnsubscribeVersion, + BurnAsset(assets) => Self::BurnAsset(assets.try_into()?), + ExpectAsset(assets) => Self::ExpectAsset(assets.try_into()?), + ExpectOrigin(maybe_origin) => + Self::ExpectOrigin(maybe_origin.map(|origin| origin.try_into()).transpose()?), + ExpectError(maybe_error) => Self::ExpectError(maybe_error), + ExpectTransactStatus(maybe_error_code) => Self::ExpectTransactStatus(maybe_error_code), + QueryPallet { module_name, response_info } => + Self::QueryPallet { module_name, response_info: response_info.try_into()? }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + Self::ExpectPallet { index, name, module_name, crate_major, min_crate_minor }, + ReportTransactStatus(response_info) => + Self::ReportTransactStatus(response_info.try_into()?), + ClearTransactStatus => Self::ClearTransactStatus, + UniversalOrigin(junction) => Self::UniversalOrigin(junction.try_into()?), + ExportMessage { network, destination, xcm } => Self::ExportMessage { + network: network.into(), + destination: destination.try_into()?, + xcm: xcm.try_into()?, + }, + LockAsset { asset, unlocker } => + Self::LockAsset { asset: asset.try_into()?, unlocker: unlocker.try_into()? }, + UnlockAsset { asset, target } => + Self::UnlockAsset { asset: asset.try_into()?, target: target.try_into()? }, + NoteUnlockable { asset, owner } => + Self::NoteUnlockable { asset: asset.try_into()?, owner: owner.try_into()? }, + RequestUnlock { asset, locker } => + Self::RequestUnlock { asset: asset.try_into()?, locker: locker.try_into()? }, + SetFeesMode { jit_withdraw } => Self::SetFeesMode { jit_withdraw }, + SetTopic(topic) => Self::SetTopic(topic), + ClearTopic => Self::ClearTopic, + AliasOrigin(location) => Self::AliasOrigin(location.try_into()?), + UnpaidExecution { weight_limit, check_origin } => Self::UnpaidExecution { + weight_limit, + check_origin: check_origin.map(|origin| origin.try_into()).transpose()?, + }, + }) + } +} + /// Default value for the proof size weight component when converting from V2. Set at 64 KB. /// NOTE: Make sure this is removed after we properly account for PoV weights. const DEFAULT_PROOF_SIZE: u64 = 64 * 1024; @@ -1319,10 +1550,8 @@ impl TryFrom> for Instruction { }; Self::ReportHolding { response_info, assets: assets.try_into()? } }, - BuyExecution { fees, weight_limit } => Self::BuyExecution { - fees: fees.try_into()?, - weight_limit: weight_limit.try_into()?, - }, + BuyExecution { fees, weight_limit } => + Self::BuyExecution { fees: fees.try_into()?, weight_limit: weight_limit.into() }, ClearOrigin => Self::ClearOrigin, DescendOrigin(who) => Self::DescendOrigin(who.try_into()?), RefundSurplus => Self::RefundSurplus, diff --git a/polkadot/xcm/src/v3/multiasset.rs b/polkadot/xcm/src/v3/multiasset.rs index 9d86fb8deff83cf1e42e3a19872b91f0c031a7cf..f9041ecd81bac1b383b65ea8920920ff95e14419 100644 --- a/polkadot/xcm/src/v3/multiasset.rs +++ b/polkadot/xcm/src/v3/multiasset.rs @@ -27,11 +27,18 @@ //! filtering an XCM holding account. use super::{InteriorMultiLocation, MultiLocation}; -use crate::v2::{ - AssetId as OldAssetId, AssetInstance as OldAssetInstance, Fungibility as OldFungibility, - MultiAsset as OldMultiAsset, MultiAssetFilter as OldMultiAssetFilter, - MultiAssets as OldMultiAssets, WildFungibility as OldWildFungibility, - WildMultiAsset as OldWildMultiAsset, +use crate::{ + v2::{ + AssetId as OldAssetId, AssetInstance as OldAssetInstance, Fungibility as OldFungibility, + MultiAsset as OldMultiAsset, MultiAssetFilter as OldMultiAssetFilter, + MultiAssets as OldMultiAssets, WildFungibility as OldWildFungibility, + WildMultiAsset as OldWildMultiAsset, + }, + v4::{ + Asset as NewMultiAsset, AssetFilter as NewMultiAssetFilter, AssetId as NewAssetId, + AssetInstance as NewAssetInstance, Assets as NewMultiAssets, Fungibility as NewFungibility, + WildAsset as NewWildMultiAsset, WildFungibility as NewWildFungibility, + }, }; use alloc::{vec, vec::Vec}; use bounded_collections::{BoundedVec, ConstU32}; @@ -44,9 +51,21 @@ use scale_info::TypeInfo; /// A general identifier for an instance of a non-fungible asset class. #[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, )] -#[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. @@ -85,6 +104,21 @@ impl TryFrom for AssetInstance { } } +impl TryFrom for AssetInstance { + type Error = (); + fn try_from(value: NewAssetInstance) -> Result { + use NewAssetInstance::*; + Ok(match value { + Undefined => Self::Undefined, + Index(n) => Self::Index(n), + Array4(n) => Self::Array4(n), + Array8(n) => Self::Array8(n), + Array16(n) => Self::Array16(n), + Array32(n) => Self::Array32(n), + }) + } +} + impl From<()> for AssetInstance { fn from(_: ()) -> Self { Self::Undefined @@ -241,8 +275,20 @@ impl TryFrom for u128 { /// Classification of whether an asset is fungible or not, along with a mandatory amount or /// instance. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + TypeInfo, + MaxEncodedLen, + 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. @@ -308,11 +354,34 @@ impl TryFrom for Fungibility { } } +impl TryFrom for Fungibility { + type Error = (); + fn try_from(value: NewFungibility) -> Result { + use NewFungibility::*; + Ok(match value { + Fungible(n) => Self::Fungible(n), + NonFungible(i) => Self::NonFungible(i.try_into()?), + }) + } +} + /// Classification of whether an asset is fungible or not. #[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, )] -#[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. @@ -332,11 +401,34 @@ impl TryFrom for WildFungibility { } } +impl TryFrom for WildFungibility { + type Error = (); + fn try_from(value: NewWildFungibility) -> Result { + use NewWildFungibility::*; + Ok(match value { + Fungible => Self::Fungible, + NonFungible => Self::NonFungible, + }) + } +} + /// Classification of an asset being concrete or abstract. #[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, )] -#[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. @@ -374,6 +466,13 @@ impl TryFrom for AssetId { } } +impl TryFrom for AssetId { + type Error = (); + fn try_from(new: NewAssetId) -> Result { + Ok(Self::Concrete(new.0.try_into()?)) + } +} + impl AssetId { /// Prepend a `MultiLocation` to a concrete asset, giving it a new root location. pub fn prepend_with(&mut self, prepend: &MultiLocation) -> Result<(), ()> { @@ -410,8 +509,19 @@ 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))] +#[derive( + Clone, + Eq, + PartialEq, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + 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). @@ -501,15 +611,34 @@ impl TryFrom for MultiAsset { } } +impl TryFrom for MultiAsset { + type Error = (); + fn try_from(new: NewMultiAsset) -> Result { + Ok(Self { id: new.id.try_into()?, fun: new.fun.try_into()? }) + } +} + /// A `Vec` of `MultiAsset`s. /// /// There are a number of invariants which the construction and mutation functions must ensure are -/// maintained: +/// maintained in order to maintain polynomial time complexity during iteration: /// - It may contain no items of duplicate asset class; /// - All items must be ordered; /// - 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))] +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + TypeInfo, + Default, + serde::Serialize, + serde::Deserialize, +)] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub struct MultiAssets(Vec); @@ -543,6 +672,18 @@ impl TryFrom for MultiAssets { } } +impl TryFrom for MultiAssets { + type Error = (); + fn try_from(new: NewMultiAssets) -> Result { + let v = new + .into_inner() + .into_iter() + .map(MultiAsset::try_from) + .collect::, ()>>()?; + Ok(MultiAssets(v)) + } +} + impl From> for MultiAssets { fn from(mut assets: Vec) -> Self { let mut res = Vec::with_capacity(assets.len()); @@ -689,12 +830,16 @@ impl MultiAssets { /// Mutate the location of the asset identifier if concrete, giving it the same location /// relative to a `target` context. The local context is provided as `context`. + /// + /// This will also re-sort the inner assets to preserve ordering guarantees. pub fn reanchor( &mut self, target: &MultiLocation, context: InteriorMultiLocation, ) -> Result<(), ()> { - self.0.iter_mut().try_for_each(|i| i.reanchor(target, context)) + self.0.iter_mut().try_for_each(|i| i.reanchor(target, context))?; + self.0.sort(); + Ok(()) } /// Return a reference to an item at a specific index or `None` if it doesn't exist. @@ -704,8 +849,21 @@ 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))] +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + 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. @@ -736,6 +894,20 @@ impl TryFrom for WildMultiAsset { } } +impl TryFrom for WildMultiAsset { + type Error = (); + fn try_from(new: NewWildMultiAsset) -> Result { + use NewWildMultiAsset::*; + Ok(match new { + AllOf { id, fun } => Self::AllOf { id: id.try_into()?, fun: fun.try_into()? }, + AllOfCounted { id, fun, count } => + Self::AllOfCounted { id: id.try_into()?, fun: fun.try_into()?, count }, + All => Self::All, + AllCounted(count) => Self::AllCounted(count), + }) + } +} + impl TryFrom<(OldWildMultiAsset, u32)> for WildMultiAsset { type Error = (); fn try_from(old: (OldWildMultiAsset, u32)) -> Result { @@ -817,8 +989,21 @@ 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))] +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + 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. @@ -905,6 +1090,17 @@ impl TryFrom for MultiAssetFilter { } } +impl TryFrom for MultiAssetFilter { + type Error = (); + fn try_from(new: NewMultiAssetFilter) -> Result { + use NewMultiAssetFilter::*; + Ok(match new { + Definite(x) => Self::Definite(x.try_into()?), + Wild(x) => Self::Wild(x.try_into()?), + }) + } +} + impl TryFrom<(OldMultiAssetFilter, u32)> for MultiAssetFilter { type Error = (); fn try_from(old: (OldMultiAssetFilter, u32)) -> Result { @@ -985,6 +1181,42 @@ mod tests { assert!(r.is_err()); } + #[test] + fn reanchor_preserves_sorting() { + use super::*; + use alloc::vec; + + let reanchor_context = X1(Parachain(2000)); + let dest = MultiLocation::new(1, Here); + + let asset_1: MultiAsset = + (MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1))), 10).into(); + let mut asset_1_reanchored = asset_1.clone(); + assert!(asset_1_reanchored.reanchor(&dest, reanchor_context).is_ok()); + assert_eq!( + asset_1_reanchored, + (MultiLocation::new(0, X3(Parachain(2000), PalletInstance(50), GeneralIndex(1))), 10) + .into() + ); + + let asset_2: MultiAsset = (MultiLocation::new(1, Here), 10).into(); + let mut asset_2_reanchored = asset_2.clone(); + assert!(asset_2_reanchored.reanchor(&dest, reanchor_context).is_ok()); + assert_eq!(asset_2_reanchored, (MultiLocation::new(0, Here), 10).into()); + + let asset_3: MultiAsset = (MultiLocation::new(1, X1(Parachain(1000))), 10).into(); + let mut asset_3_reanchored = asset_3.clone(); + assert!(asset_3_reanchored.reanchor(&dest, reanchor_context).is_ok()); + assert_eq!(asset_3_reanchored, (MultiLocation::new(0, X1(Parachain(1000))), 10).into()); + + let mut assets: MultiAssets = + vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into(); + assert_eq!(assets.clone(), vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into()); + + assert!(assets.reanchor(&dest, reanchor_context).is_ok()); + assert_eq!(assets, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored].into()); + } + #[test] fn decoding_respects_limit() { use super::*; diff --git a/polkadot/xcm/src/v3/multilocation.rs b/polkadot/xcm/src/v3/multilocation.rs index 8a1575d9bc950620e3f411349cdca6ef9db5deef..c588b924ac70842b72678d141a67e4279563f30a 100644 --- a/polkadot/xcm/src/v3/multilocation.rs +++ b/polkadot/xcm/src/v3/multilocation.rs @@ -17,7 +17,9 @@ //! XCM `MultiLocation` datatype. use super::{Junction, Junctions}; -use crate::{v2::MultiLocation as OldMultiLocation, VersionedMultiLocation}; +use crate::{ + v2::MultiLocation as OldMultiLocation, v4::Location as NewMultiLocation, VersionedLocation, +}; use core::{ convert::{TryFrom, TryInto}, result, @@ -66,6 +68,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, @@ -73,6 +76,9 @@ pub struct MultiLocation { pub interior: Junctions, } +/// Type alias for a better transition to V4. +pub type Location = MultiLocation; + impl Default for MultiLocation { fn default() -> Self { Self { parents: 0, interior: Junctions::Here } @@ -90,9 +96,9 @@ impl MultiLocation { MultiLocation { parents, interior: interior.into() } } - /// Consume `self` and return the equivalent `VersionedMultiLocation` value. - pub const fn into_versioned(self) -> VersionedMultiLocation { - VersionedMultiLocation::V3(self) + /// Consume `self` and return the equivalent `VersionedLocation` value. + pub const fn into_versioned(self) -> VersionedLocation { + VersionedLocation::V3(self) } /// Creates a new `MultiLocation` with 0 parents and a `Here` interior. @@ -444,6 +450,21 @@ impl MultiLocation { } } } + + /// Return the MultiLocation subsection identifying the chain that `self` points to. + pub fn chain_location(&self) -> MultiLocation { + let mut clone = *self; + // start popping junctions until we reach chain identifier + while let Some(j) = clone.last() { + if matches!(j, Junction::Parachain(_) | Junction::GlobalConsensus(_)) { + // return chain subsection + return clone + } else { + (clone, _) = clone.split_last_interior(); + } + } + MultiLocation::new(clone.parents, Junctions::Here) + } } impl TryFrom for MultiLocation { @@ -453,6 +474,23 @@ impl TryFrom for MultiLocation { } } +impl TryFrom for Option { + type Error = (); + fn try_from(new: NewMultiLocation) -> result::Result { + Ok(Some(MultiLocation::try_from(new)?)) + } +} + +impl TryFrom for MultiLocation { + type Error = (); + fn try_from(new: NewMultiLocation) -> result::Result { + Ok(MultiLocation { + parents: new.parent_count(), + interior: new.interior().clone().try_into()?, + }) + } +} + /// A unit struct which can be converted into a `MultiLocation` of `parents` value 1. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Parent; @@ -674,6 +712,57 @@ mod tests { assert_eq!(iter.next_back(), None); } + #[test] + fn chain_location_works() { + // Relay-chain or parachain context pointing to local resource, + let relay_to_local = MultiLocation::new(0, (PalletInstance(42), GeneralIndex(42))); + assert_eq!(relay_to_local.chain_location(), MultiLocation::here()); + + // Relay-chain context pointing to child parachain, + let relay_to_child = + MultiLocation::new(0, (Parachain(42), PalletInstance(42), GeneralIndex(42))); + let expected = MultiLocation::new(0, Parachain(42)); + assert_eq!(relay_to_child.chain_location(), expected); + + // Relay-chain context pointing to different consensus relay, + let relay_to_remote_relay = + MultiLocation::new(1, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42))); + let expected = MultiLocation::new(1, GlobalConsensus(Kusama)); + assert_eq!(relay_to_remote_relay.chain_location(), expected); + + // Relay-chain context pointing to different consensus parachain, + let relay_to_remote_para = MultiLocation::new( + 1, + (GlobalConsensus(Kusama), Parachain(42), PalletInstance(42), GeneralIndex(42)), + ); + let expected = MultiLocation::new(1, (GlobalConsensus(Kusama), Parachain(42))); + assert_eq!(relay_to_remote_para.chain_location(), expected); + + // Parachain context pointing to relay chain, + let para_to_relay = MultiLocation::new(1, (PalletInstance(42), GeneralIndex(42))); + assert_eq!(para_to_relay.chain_location(), MultiLocation::parent()); + + // Parachain context pointing to sibling parachain, + let para_to_sibling = + MultiLocation::new(1, (Parachain(42), PalletInstance(42), GeneralIndex(42))); + let expected = MultiLocation::new(1, Parachain(42)); + assert_eq!(para_to_sibling.chain_location(), expected); + + // Parachain context pointing to different consensus relay, + let para_to_remote_relay = + MultiLocation::new(2, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42))); + let expected = MultiLocation::new(2, GlobalConsensus(Kusama)); + assert_eq!(para_to_remote_relay.chain_location(), expected); + + // Parachain context pointing to different consensus parachain, + let para_to_remote_para = MultiLocation::new( + 2, + (GlobalConsensus(Kusama), Parachain(42), PalletInstance(42), GeneralIndex(42)), + ); + let expected = MultiLocation::new(2, (GlobalConsensus(Kusama), Parachain(42))); + assert_eq!(para_to_remote_para.chain_location(), expected); + } + #[test] fn conversion_from_other_types_works() { use crate::v2; diff --git a/polkadot/xcm/src/v3/traits.rs b/polkadot/xcm/src/v3/traits.rs index 1043d17b7106647e86f9290ff008e00f2ead4555..cfe387df1a86c3aa21da69ce37eadb307df1c51b 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. @@ -215,52 +216,6 @@ impl From for Error { pub type Result = result::Result<(), Error>; -/* -TODO: XCMv4 -/// Outcome of an XCM execution. -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] -pub enum Outcome { - /// Execution completed successfully; given weight was used. - Complete { used: Weight }, - /// Execution started, but did not complete successfully due to the given error; given weight - /// was used. - Incomplete { used: Weight, error: Error }, - /// Execution did not start due to the given error. - Error { error: Error }, -} - -impl Outcome { - pub fn ensure_complete(self) -> Result { - match self { - Outcome::Complete { .. } => Ok(()), - Outcome::Incomplete { error, .. } => Err(error), - Outcome::Error { error, .. } => Err(error), - } - } - pub fn ensure_execution(self) -> result::Result { - match self { - Outcome::Complete { used, .. } => Ok(used), - Outcome::Incomplete { used, .. } => Ok(used), - Outcome::Error { error, .. } => Err(error), - } - } - /// How much weight was used by the XCM execution attempt. - pub fn weight_used(&self) -> Weight { - match self { - Outcome::Complete { used, .. } => *used, - Outcome::Incomplete { used, .. } => *used, - Outcome::Error { .. } => Weight::zero(), - } - } -} - -impl From for Outcome { - fn from(error: Error) -> Self { - Self::Error { error, maybe_id: None } - } -} -*/ - /// Outcome of an XCM execution. #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] #[scale_info(replace_segment("staging_xcm", "xcm"))] @@ -275,9 +230,9 @@ pub enum Outcome { } impl Outcome { - pub fn ensure_complete(self) -> Result { + pub fn ensure_complete(self) -> result::Result { match self { - Outcome::Complete(_) => Ok(()), + Outcome::Complete(weight) => Ok(weight), Outcome::Incomplete(_, e) => Err(e), Outcome::Error(e) => Err(e), } @@ -336,8 +291,6 @@ pub trait ExecuteXcm { /// /// The weight limit is a basic hard-limit and the implementation may place further /// restrictions or requirements on weight and other aspects. - // TODO: XCMv4 - // #[deprecated = "Use `prepare_and_execute` instead"] fn execute_xcm( origin: impl Into, message: Xcm, @@ -360,8 +313,6 @@ pub trait ExecuteXcm { /// /// Some amount of `weight_credit` may be provided which, depending on the implementation, may /// allow execution without associated payment. - // TODO: XCMv4 - // #[deprecated = "Use `prepare_and_execute` instead"] fn execute_xcm_in_credit( origin: impl Into, message: Xcm, @@ -519,7 +470,7 @@ pub trait SendXcm { /// Intermediate value which connects the two phases of the send operation. type Ticket; - /// Check whether the given `_message` is deliverable to the given `_destination` and if so + /// Check whether the given `message` is deliverable to the given `destination` and if so /// determine the cost which will be paid by this chain to do so, returning a `Validated` token /// which can be used to enact delivery. /// diff --git a/polkadot/xcm/src/v4/asset.rs b/polkadot/xcm/src/v4/asset.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdff0c272306aced0fbf5f2372a70d66ac37e841 --- /dev/null +++ b/polkadot/xcm/src/v4/asset.rs @@ -0,0 +1,1068 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format asset data structures. +//! +//! This encompasses four types for representing assets: +//! - `Asset`: A description of a single asset, either an instance of a non-fungible or some amount +//! of a fungible. +//! - `Assets`: A collection of `Asset`s. These are stored in a `Vec` and sorted with fungibles +//! first. +//! - `Wild`: A single asset wildcard, this can either be "all" assets, or all assets of a specific +//! kind. +//! - `AssetFilter`: A combination of `Wild` and `Assets` designed for efficiently filtering an XCM +//! holding account. + +use super::{InteriorLocation, Location, Reanchorable}; +use crate::v3::{ + AssetId as OldAssetId, AssetInstance as OldAssetInstance, Fungibility as OldFungibility, + MultiAsset as OldAsset, MultiAssetFilter as OldAssetFilter, MultiAssets as OldAssets, + WildFungibility as OldWildFungibility, WildMultiAsset as OldWildAsset, +}; +use alloc::{vec, vec::Vec}; +use bounded_collections::{BoundedVec, ConstU32}; +use core::{ + cmp::Ordering, + convert::{TryFrom, TryInto}, +}; +use parity_scale_codec::{self as codec, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// A general identifier for an instance of a non-fungible asset class. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum AssetInstance { + /// Undefined - used if the non-fungible asset class has only one instance. + Undefined, + + /// A compact index. Technically this could be greater than `u128`, but this implementation + /// supports only values up to `2**128 - 1`. + Index(#[codec(compact)] u128), + + /// A 4-byte fixed-length datum. + Array4([u8; 4]), + + /// An 8-byte fixed-length datum. + Array8([u8; 8]), + + /// A 16-byte fixed-length datum. + Array16([u8; 16]), + + /// A 32-byte fixed-length datum. + Array32([u8; 32]), +} + +impl TryFrom for AssetInstance { + type Error = (); + fn try_from(value: OldAssetInstance) -> Result { + use OldAssetInstance::*; + Ok(match value { + Undefined => Self::Undefined, + Index(n) => Self::Index(n), + Array4(n) => Self::Array4(n), + Array8(n) => Self::Array8(n), + Array16(n) => Self::Array16(n), + Array32(n) => Self::Array32(n), + }) + } +} + +impl From<()> for AssetInstance { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +impl From<[u8; 4]> for AssetInstance { + fn from(x: [u8; 4]) -> Self { + Self::Array4(x) + } +} + +impl From<[u8; 8]> for AssetInstance { + fn from(x: [u8; 8]) -> Self { + Self::Array8(x) + } +} + +impl From<[u8; 16]> for AssetInstance { + fn from(x: [u8; 16]) -> Self { + Self::Array16(x) + } +} + +impl From<[u8; 32]> for AssetInstance { + fn from(x: [u8; 32]) -> Self { + Self::Array32(x) + } +} + +impl From for AssetInstance { + fn from(x: u8) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u16) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u32) -> Self { + Self::Index(x as u128) + } +} + +impl From for AssetInstance { + fn from(x: u64) -> Self { + Self::Index(x as u128) + } +} + +impl TryFrom for () { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Undefined => Ok(()), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 4] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array4(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 8] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array8(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 16] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array16(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for [u8; 32] { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Array32(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for u8 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u16 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u32 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u64 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => x.try_into().map_err(|_| ()), + _ => Err(()), + } + } +} + +impl TryFrom for u128 { + type Error = (); + fn try_from(x: AssetInstance) -> Result { + match x { + AssetInstance::Index(x) => Ok(x), + _ => Err(()), + } + } +} + +/// Classification of whether an asset is fungible or not, along with a mandatory amount or +/// instance. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum Fungibility { + /// A fungible asset; we record a number of units, as a `u128` in the inner item. + Fungible(#[codec(compact)] u128), + /// A non-fungible asset. We record the instance identifier in the inner item. Only one asset + /// of each instance identifier may ever be in existence at once. + NonFungible(AssetInstance), +} + +#[derive(Decode)] +enum UncheckedFungibility { + Fungible(#[codec(compact)] u128), + NonFungible(AssetInstance), +} + +impl Decode for Fungibility { + fn decode(input: &mut I) -> Result { + match UncheckedFungibility::decode(input)? { + UncheckedFungibility::Fungible(a) if a != 0 => Ok(Self::Fungible(a)), + UncheckedFungibility::NonFungible(i) => Ok(Self::NonFungible(i)), + UncheckedFungibility::Fungible(_) => + Err("Fungible asset of zero amount is not allowed".into()), + } + } +} + +impl Fungibility { + pub fn is_kind(&self, w: WildFungibility) -> bool { + use Fungibility::*; + use WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}; + matches!((self, w), (Fungible(_), WildFungible) | (NonFungible(_), WildNonFungible)) + } +} + +impl From for Fungibility { + fn from(amount: i32) -> Fungibility { + debug_assert_ne!(amount, 0); + Fungibility::Fungible(amount as u128) + } +} + +impl From for Fungibility { + fn from(amount: u128) -> Fungibility { + debug_assert_ne!(amount, 0); + Fungibility::Fungible(amount) + } +} + +impl> From for Fungibility { + fn from(instance: T) -> Fungibility { + Fungibility::NonFungible(instance.into()) + } +} + +impl TryFrom for Fungibility { + type Error = (); + fn try_from(value: OldFungibility) -> Result { + use OldFungibility::*; + Ok(match value { + Fungible(n) => Self::Fungible(n), + NonFungible(i) => Self::NonFungible(i.try_into()?), + }) + } +} + +/// Classification of whether an asset is fungible or not. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum WildFungibility { + /// The asset is fungible. + Fungible, + /// The asset is not fungible. + NonFungible, +} + +impl TryFrom for WildFungibility { + type Error = (); + fn try_from(value: OldWildFungibility) -> Result { + use OldWildFungibility::*; + Ok(match value { + Fungible => Self::Fungible, + NonFungible => Self::NonFungible, + }) + } +} + +/// Location to identify an asset. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct AssetId(pub Location); + +impl> From for AssetId { + fn from(x: T) -> Self { + Self(x.into()) + } +} + +impl TryFrom for AssetId { + type Error = (); + fn try_from(old: OldAssetId) -> Result { + use OldAssetId::*; + Ok(match old { + Concrete(l) => Self(l.try_into()?), + Abstract(_) => return Err(()), + }) + } +} + +impl AssetId { + /// Prepend a `Location` to an asset id, giving it a new root location. + pub fn prepend_with(&mut self, prepend: &Location) -> Result<(), ()> { + self.0.prepend_with(prepend.clone()).map_err(|_| ())?; + Ok(()) + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding + /// `Asset` value. + pub fn into_asset(self, fun: Fungibility) -> Asset { + Asset { fun, id: self } + } + + /// Use the value of `self` along with a `fun` fungibility specifier to create the corresponding + /// `WildAsset` wildcard (`AllOf`) value. + pub fn into_wild(self, fun: WildFungibility) -> WildAsset { + WildAsset::AllOf { fun, id: self } + } +} + +impl Reanchorable for AssetId { + type Error = (); + + /// Mutate the asset to represent the same value from the perspective of a new `target` + /// location. The local chain's location is provided in `context`. + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + self.0.reanchor(target, context)?; + Ok(()) + } + + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result { + match self.reanchor(target, context) { + Ok(()) => Ok(self), + Err(()) => Err(()), + } + } +} + +/// 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, + serde::Serialize, + serde::Deserialize, +)] +pub struct Asset { + /// The overall asset identity (aka *class*, in the case of a non-fungible). + pub id: AssetId, + /// The fungibility of the asset, which contains either the amount (in the case of a fungible + /// asset) or the *instance ID*, the secondary asset identifier. + pub fun: Fungibility, +} + +impl PartialOrd for Asset { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Asset { + fn cmp(&self, other: &Self) -> Ordering { + match (&self.fun, &other.fun) { + (Fungibility::Fungible(..), Fungibility::NonFungible(..)) => Ordering::Less, + (Fungibility::NonFungible(..), Fungibility::Fungible(..)) => Ordering::Greater, + _ => (&self.id, &self.fun).cmp(&(&other.id, &other.fun)), + } + } +} + +impl, B: Into> From<(A, B)> for Asset { + fn from((id, fun): (A, B)) -> Asset { + Asset { fun: fun.into(), id: id.into() } + } +} + +impl Asset { + pub fn is_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, Fungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + pub fn is_non_fungible(&self, maybe_id: Option) -> bool { + use Fungibility::*; + matches!(self.fun, NonFungible(..)) && maybe_id.map_or(true, |i| i == self.id) + } + + /// Prepend a `Location` to a concrete asset, giving it a new root location. + pub fn prepend_with(&mut self, prepend: &Location) -> Result<(), ()> { + self.id.prepend_with(prepend) + } + + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &Asset) -> bool { + use Fungibility::*; + if self.id == inner.id { + match (&self.fun, &inner.fun) { + (Fungible(a), Fungible(i)) if a >= i => return true, + (NonFungible(a), NonFungible(i)) if a == i => return true, + _ => (), + } + } + false + } +} + +impl Reanchorable for Asset { + type Error = (); + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + self.id.reanchor(target, context) + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result { + self.id.reanchor(target, context)?; + Ok(self) + } +} + +impl TryFrom for Asset { + type Error = (); + fn try_from(old: OldAsset) -> Result { + Ok(Self { id: old.id.try_into()?, fun: old.fun.try_into()? }) + } +} + +/// A `Vec` of `Asset`s. +/// +/// There are a number of invariants which the construction and mutation functions must ensure are +/// maintained: +/// - It may contain no items of duplicate asset class; +/// - All items must be ordered; +/// - The number of items should grow no larger than `MAX_ITEMS_IN_ASSETS`. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + TypeInfo, + Default, + serde::Serialize, + serde::Deserialize, +)] +pub struct Assets(Vec); + +/// Maximum number of items we expect in a single `Assets` value. Note this is not (yet) +/// enforced, and just serves to provide a sensible `max_encoded_len` for `Assets`. +pub const MAX_ITEMS_IN_ASSETS: usize = 20; + +impl MaxEncodedLen for Assets { + fn max_encoded_len() -> usize { + Asset::max_encoded_len() * MAX_ITEMS_IN_ASSETS + } +} + +impl Decode for Assets { + fn decode(input: &mut I) -> Result { + let bounded_instructions = + BoundedVec::>::decode(input)?; + Self::from_sorted_and_deduplicated(bounded_instructions.into_inner()) + .map_err(|()| "Out of order".into()) + } +} + +impl TryFrom for Assets { + type Error = (); + fn try_from(old: OldAssets) -> Result { + let v = old + .into_inner() + .into_iter() + .map(Asset::try_from) + .collect::, ()>>()?; + Ok(Assets(v)) + } +} + +impl From> for Assets { + fn from(mut assets: Vec) -> Self { + let mut res = Vec::with_capacity(assets.len()); + if !assets.is_empty() { + assets.sort(); + let mut iter = assets.into_iter(); + if let Some(first) = iter.next() { + let last = iter.fold(first, |a, b| -> Asset { + match (a, b) { + ( + Asset { fun: Fungibility::Fungible(a_amount), id: a_id }, + Asset { fun: Fungibility::Fungible(b_amount), id: b_id }, + ) if a_id == b_id => Asset { + id: a_id, + fun: Fungibility::Fungible(a_amount.saturating_add(b_amount)), + }, + ( + Asset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + Asset { fun: Fungibility::NonFungible(b_instance), id: b_id }, + ) if a_id == b_id && a_instance == b_instance => + Asset { fun: Fungibility::NonFungible(a_instance), id: a_id }, + (to_push, to_remember) => { + res.push(to_push); + to_remember + }, + } + }); + res.push(last); + } + } + Self(res) + } +} + +impl> From for Assets { + fn from(x: T) -> Self { + Self(vec![x.into()]) + } +} + +impl Assets { + /// A new (empty) value. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Create a new instance of `Assets` from a `Vec` whose contents are sorted + /// and which contain no duplicates. + /// + /// Returns `Ok` if the operation succeeds and `Err` if `r` is out of order or had duplicates. + /// If you can't guarantee that `r` is sorted and deduplicated, then use + /// `From::>::from` which is infallible. + pub fn from_sorted_and_deduplicated(r: Vec) -> Result { + if r.is_empty() { + return Ok(Self(Vec::new())) + } + r.iter().skip(1).try_fold(&r[0], |a, b| -> Result<&Asset, ()> { + if a.id < b.id || a < b && (a.is_non_fungible(None) || b.is_non_fungible(None)) { + Ok(b) + } else { + Err(()) + } + })?; + Ok(Self(r)) + } + + /// Create a new instance of `Assets` from a `Vec` whose contents are sorted + /// and which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a + /// negligible-cost operation. Generally though you should avoid using it unless you have a + /// strict proof that `r` is valid. + #[cfg(test)] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self::from_sorted_and_deduplicated(r).expect("Invalid input r is not sorted/deduped") + } + /// Create a new instance of `Assets` from a `Vec` whose contents are sorted + /// and which contain no duplicates. + /// + /// In release mode, this skips any checks to ensure that `r` is correct, making it a + /// negligible-cost operation. Generally though you should avoid using it unless you have a + /// strict proof that `r` is valid. + /// + /// In test mode, this checks anyway and panics on fail. + #[cfg(not(test))] + pub fn from_sorted_and_deduplicated_skip_checks(r: Vec) -> Self { + Self(r) + } + + /// Add some asset onto the list, saturating. This is quite a laborious operation since it + /// maintains the ordering. + pub fn push(&mut self, a: Asset) { + for asset in self.0.iter_mut().filter(|x| x.id == a.id) { + match (&a.fun, &mut asset.fun) { + (Fungibility::Fungible(amount), Fungibility::Fungible(balance)) => { + *balance = balance.saturating_add(*amount); + return + }, + (Fungibility::NonFungible(inst1), Fungibility::NonFungible(inst2)) + if inst1 == inst2 => + return, + _ => (), + } + } + self.0.push(a); + self.0.sort(); + } + + /// Returns `true` if this definitely represents no asset. + pub fn is_none(&self) -> bool { + self.0.is_empty() + } + + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &Asset) -> bool { + self.0.iter().any(|i| i.contains(inner)) + } + + /// Consume `self` and return the inner vec. + #[deprecated = "Use `into_inner()` instead"] + pub fn drain(self) -> Vec { + self.0 + } + + /// Consume `self` and return the inner vec. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Return a reference to the inner vec. + pub fn inner(&self) -> &Vec { + &self.0 + } + + /// Return the number of distinct asset instances contained. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Prepend a `Location` to any concrete asset items, giving it a new root location. + pub fn prepend_with(&mut self, prefix: &Location) -> Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.prepend_with(prefix)) + } + + /// Return a reference to an item at a specific index or `None` if it doesn't exist. + pub fn get(&self, index: usize) -> Option<&Asset> { + self.0.get(index) + } +} + +impl Reanchorable for Assets { + type Error = (); + + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + self.0.iter_mut().try_for_each(|i| i.reanchor(target, context))?; + self.0.sort(); + Ok(()) + } + + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result { + match self.reanchor(target, context) { + Ok(()) => Ok(self), + Err(()) => Err(()), + } + } +} + +/// A wildcard representing a set of assets. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum WildAsset { + /// All assets in Holding. + All, + /// All assets in Holding of a given fungibility and ID. + AllOf { id: AssetId, fun: WildFungibility }, + /// All assets in Holding, up to `u32` individual assets (different instances of non-fungibles + /// are separate assets). + AllCounted(#[codec(compact)] u32), + /// All assets in Holding of a given fungibility and ID up to `count` individual assets + /// (different instances of non-fungibles are separate assets). + AllOfCounted { + id: AssetId, + fun: WildFungibility, + #[codec(compact)] + count: u32, + }, +} + +impl TryFrom for WildAsset { + type Error = (); + fn try_from(old: OldWildAsset) -> Result { + use OldWildAsset::*; + Ok(match old { + AllOf { id, fun } => Self::AllOf { id: id.try_into()?, fun: fun.try_into()? }, + All => Self::All, + AllOfCounted { id, fun, count } => + Self::AllOfCounted { id: id.try_into()?, fun: fun.try_into()?, count }, + AllCounted(count) => Self::AllCounted(count), + }) + } +} + +impl WildAsset { + /// Returns true if `self` is a super-set of the given `inner` asset. + pub fn contains(&self, inner: &Asset) -> bool { + use WildAsset::*; + match self { + AllOfCounted { count: 0, .. } | AllCounted(0) => false, + AllOf { fun, id } | AllOfCounted { id, fun, .. } => + inner.fun.is_kind(*fun) && &inner.id == id, + All | AllCounted(_) => true, + } + } + + /// Returns true if the wild element of `self` matches `inner`. + /// + /// Note that for `Counted` variants of wildcards, then it will disregard the count except for + /// always returning `false` when equal to 0. + #[deprecated = "Use `contains` instead"] + pub fn matches(&self, inner: &Asset) -> bool { + self.contains(inner) + } + + /// Mutate the asset to represent the same value from the perspective of a new `target` + /// location. The local chain's location is provided in `context`. + pub fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + use WildAsset::*; + match self { + AllOf { ref mut id, .. } | AllOfCounted { ref mut id, .. } => + id.reanchor(target, context), + All | AllCounted(_) => Ok(()), + } + } + + /// Maximum count of assets allowed to match, if any. + pub fn count(&self) -> Option { + use WildAsset::*; + match self { + AllOfCounted { count, .. } | AllCounted(count) => Some(*count), + All | AllOf { .. } => None, + } + } + + /// Explicit limit on number of assets allowed to match, if any. + pub fn limit(&self) -> Option { + self.count() + } + + /// Consume self and return the equivalent version but counted and with the `count` set to the + /// given parameter. + pub fn counted(self, count: u32) -> Self { + use WildAsset::*; + match self { + AllOfCounted { fun, id, .. } | AllOf { fun, id } => AllOfCounted { fun, id, count }, + All | AllCounted(_) => AllCounted(count), + } + } +} + +impl, B: Into> From<(A, B)> for WildAsset { + fn from((id, fun): (A, B)) -> WildAsset { + WildAsset::AllOf { fun: fun.into(), id: id.into() } + } +} + +/// `Asset` collection, defined either by a number of `Assets` or a single wildcard. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum AssetFilter { + /// Specify the filter as being everything contained by the given `Assets` inner. + Definite(Assets), + /// Specify the filter as the given `WildAsset` wildcard. + Wild(WildAsset), +} + +impl> From for AssetFilter { + fn from(x: T) -> Self { + Self::Wild(x.into()) + } +} + +impl From for AssetFilter { + fn from(x: Asset) -> Self { + Self::Definite(vec![x].into()) + } +} + +impl From> for AssetFilter { + fn from(x: Vec) -> Self { + Self::Definite(x.into()) + } +} + +impl From for AssetFilter { + fn from(x: Assets) -> Self { + Self::Definite(x) + } +} + +impl AssetFilter { + /// Returns true if `inner` would be matched by `self`. + /// + /// Note that for `Counted` variants of wildcards, then it will disregard the count except for + /// always returning `false` when equal to 0. + pub fn matches(&self, inner: &Asset) -> bool { + match self { + AssetFilter::Definite(ref assets) => assets.contains(inner), + AssetFilter::Wild(ref wild) => wild.contains(inner), + } + } + + /// Mutate the location of the asset identifier if concrete, giving it the same location + /// relative to a `target` context. The local context is provided as `context`. + pub fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + match self { + AssetFilter::Definite(ref mut assets) => assets.reanchor(target, context), + AssetFilter::Wild(ref mut wild) => wild.reanchor(target, context), + } + } + + /// Maximum count of assets it is possible to match, if known. + pub fn count(&self) -> Option { + use AssetFilter::*; + match self { + Definite(x) => Some(x.len() as u32), + Wild(x) => x.count(), + } + } + + /// Explicit limit placed on the number of items, if any. + pub fn limit(&self) -> Option { + use AssetFilter::*; + match self { + Definite(_) => None, + Wild(x) => x.limit(), + } + } +} + +impl TryFrom for AssetFilter { + type Error = (); + fn try_from(old: OldAssetFilter) -> Result { + Ok(match old { + OldAssetFilter::Definite(x) => Self::Definite(x.try_into()?), + OldAssetFilter::Wild(x) => Self::Wild(x.try_into()?), + }) + } +} + +#[cfg(test)] +mod tests { + use super::super::prelude::*; + + #[test] + fn conversion_works() { + let _: Assets = (Here, 1u128).into(); + } + + #[test] + fn from_sorted_and_deduplicated_works() { + use super::*; + use alloc::vec; + + let empty = vec![]; + let r = Assets::from_sorted_and_deduplicated(empty); + assert_eq!(r, Ok(Assets(vec![]))); + + let dup_fun = vec![(Here, 100).into(), (Here, 10).into()]; + let r = Assets::from_sorted_and_deduplicated(dup_fun); + assert!(r.is_err()); + + let dup_nft = vec![(Here, *b"notgood!").into(), (Here, *b"notgood!").into()]; + let r = Assets::from_sorted_and_deduplicated(dup_nft); + assert!(r.is_err()); + + let good_fun = vec![(Here, 10).into(), (Parent, 10).into()]; + let r = Assets::from_sorted_and_deduplicated(good_fun.clone()); + assert_eq!(r, Ok(Assets(good_fun))); + + let bad_fun = vec![(Parent, 10).into(), (Here, 10).into()]; + let r = Assets::from_sorted_and_deduplicated(bad_fun); + assert!(r.is_err()); + + let good_nft = vec![(Here, ()).into(), (Here, *b"good").into()]; + let r = Assets::from_sorted_and_deduplicated(good_nft.clone()); + assert_eq!(r, Ok(Assets(good_nft))); + + let bad_nft = vec![(Here, *b"bad!").into(), (Here, ()).into()]; + let r = Assets::from_sorted_and_deduplicated(bad_nft); + assert!(r.is_err()); + + let mixed_good = vec![(Here, 10).into(), (Here, *b"good").into()]; + let r = Assets::from_sorted_and_deduplicated(mixed_good.clone()); + assert_eq!(r, Ok(Assets(mixed_good))); + + let mixed_bad = vec![(Here, *b"bad!").into(), (Here, 10).into()]; + let r = Assets::from_sorted_and_deduplicated(mixed_bad); + assert!(r.is_err()); + } + + #[test] + fn reanchor_preserves_sorting() { + use super::*; + use alloc::vec; + + let reanchor_context: Junctions = Parachain(2000).into(); + let dest = Location::new(1, []); + + let asset_1: Asset = (Location::new(0, [PalletInstance(50), GeneralIndex(1)]), 10).into(); + let mut asset_1_reanchored = asset_1.clone(); + assert!(asset_1_reanchored.reanchor(&dest, &reanchor_context).is_ok()); + assert_eq!( + asset_1_reanchored, + (Location::new(0, [Parachain(2000), PalletInstance(50), GeneralIndex(1)]), 10).into() + ); + + let asset_2: Asset = (Location::new(1, []), 10).into(); + let mut asset_2_reanchored = asset_2.clone(); + assert!(asset_2_reanchored.reanchor(&dest, &reanchor_context).is_ok()); + assert_eq!(asset_2_reanchored, (Location::new(0, []), 10).into()); + + let asset_3: Asset = (Location::new(1, [Parachain(1000)]), 10).into(); + let mut asset_3_reanchored = asset_3.clone(); + assert!(asset_3_reanchored.reanchor(&dest, &reanchor_context).is_ok()); + assert_eq!(asset_3_reanchored, (Location::new(0, [Parachain(1000)]), 10).into()); + + let mut assets: Assets = vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into(); + assert_eq!(assets.clone(), vec![asset_1.clone(), asset_2.clone(), asset_3.clone()].into()); + + assert!(assets.reanchor(&dest, &reanchor_context).is_ok()); + assert_eq!(assets, vec![asset_2_reanchored, asset_3_reanchored, asset_1_reanchored].into()); + } + + #[test] + fn decoding_respects_limit() { + use super::*; + + // Having lots of one asset will work since they are deduplicated + let lots_of_one_asset: Assets = + vec![(GeneralIndex(1), 1u128).into(); MAX_ITEMS_IN_ASSETS + 1].into(); + let encoded = lots_of_one_asset.encode(); + assert!(Assets::decode(&mut &encoded[..]).is_ok()); + + // Fewer assets than the limit works + let mut few_assets: Assets = Vec::new().into(); + for i in 0..MAX_ITEMS_IN_ASSETS { + few_assets.push((GeneralIndex(i as u128), 1u128).into()); + } + let encoded = few_assets.encode(); + assert!(Assets::decode(&mut &encoded[..]).is_ok()); + + // Having lots of different assets will not work + let mut too_many_different_assets: Assets = Vec::new().into(); + for i in 0..MAX_ITEMS_IN_ASSETS + 1 { + too_many_different_assets.push((GeneralIndex(i as u128), 1u128).into()); + } + let encoded = too_many_different_assets.encode(); + assert!(Assets::decode(&mut &encoded[..]).is_err()); + } +} diff --git a/polkadot/xcm/src/v4/junction.rs b/polkadot/xcm/src/v4/junction.rs new file mode 100644 index 0000000000000000000000000000000000000000..b5d10484aa021aebfc2324bf251564c6e54a111e --- /dev/null +++ b/polkadot/xcm/src/v4/junction.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 . + +//! Support data structures for `Location`, primarily the `Junction` datatype. + +use super::Location; +pub use crate::v3::{BodyId, BodyPart}; +use crate::{ + v3::{Junction as OldJunction, NetworkId as OldNetworkId}, + VersionedLocation, +}; +use bounded_collections::{BoundedSlice, BoundedVec, ConstU32}; +use core::convert::TryFrom; +use parity_scale_codec::{self, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; + +/// A single item in a path to describe the relative location of a consensus system. +/// +/// Each item assumes a pre-existing location as its context and is defined in terms of it. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +pub enum Junction { + /// An indexed parachain belonging to and operated by the context. + /// + /// Generally used when the context is a Polkadot Relay-chain. + Parachain(#[codec(compact)] u32), + /// A 32-byte identifier for an account of a specific network that is respected as a sovereign + /// endpoint within the context. + /// + /// Generally used when the context is a Substrate-based chain. + AccountId32 { network: Option, id: [u8; 32] }, + /// An 8-byte index for an account of a specific network that is respected as a sovereign + /// endpoint within the context. + /// + /// May be used when the context is a Frame-based chain and includes e.g. an indices pallet. + AccountIndex64 { + network: Option, + #[codec(compact)] + index: u64, + }, + /// A 20-byte identifier for an account of a specific network that is respected as a sovereign + /// endpoint within the context. + /// + /// May be used when the context is an Ethereum or Bitcoin chain or smart-contract. + AccountKey20 { network: Option, key: [u8; 20] }, + /// An instanced, indexed pallet that forms a constituent part of the context. + /// + /// Generally used when the context is a Frame-based chain. + // TODO XCMv4 inner should be `Compact`. + PalletInstance(u8), + /// A non-descript index within the context location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + GeneralIndex(#[codec(compact)] u128), + /// A nondescript array datum, 32 bytes, acting as a key within the context + /// location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + // Note this is implemented as an array with a length rather than using `BoundedVec` owing to + // the bound for `Copy`. + GeneralKey { length: u8, data: [u8; 32] }, + /// The unambiguous child. + /// + /// Not currently used except as a fallback when deriving context. + OnlyChild, + /// A pluralistic body existing within consensus. + /// + /// Typical to be used to represent a governance origin of a chain, but could in principle be + /// used to represent things such as multisigs also. + Plurality { id: BodyId, part: BodyPart }, + /// A global network capable of externalizing its own consensus. This is not generally + /// meaningful outside of the universal level. + GlobalConsensus(NetworkId), +} + +/// A global identifier of a data structure existing within consensus. +/// +/// Maintenance note: Networks with global consensus and which are practically bridgeable within the +/// Polkadot ecosystem are given preference over explicit naming in this enumeration. +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +pub enum NetworkId { + /// Network specified by the first 32 bytes of its genesis block. + ByGenesis([u8; 32]), + /// Network defined by the first 32-bytes of the hash and number of some block it contains. + ByFork { block_number: u64, block_hash: [u8; 32] }, + /// The Polkadot mainnet Relay-chain. + Polkadot, + /// The Kusama canary-net Relay-chain. + Kusama, + /// The Westend testnet Relay-chain. + Westend, + /// The Rococo testnet Relay-chain. + Rococo, + /// The Wococo testnet Relay-chain. + Wococo, + /// An Ethereum network specified by its chain ID. + Ethereum { + /// The EIP-155 chain ID. + #[codec(compact)] + chain_id: u64, + }, + /// The Bitcoin network, including hard-forks supported by Bitcoin Core development team. + BitcoinCore, + /// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers. + BitcoinCash, + /// The Polkadot Bulletin chain. + PolkadotBulletin, +} + +impl From for Option { + fn from(old: OldNetworkId) -> Self { + Some(NetworkId::from(old)) + } +} + +impl From for NetworkId { + fn from(old: OldNetworkId) -> Self { + use OldNetworkId::*; + match old { + ByGenesis(hash) => Self::ByGenesis(hash), + ByFork { block_number, block_hash } => Self::ByFork { block_number, block_hash }, + Polkadot => Self::Polkadot, + Kusama => Self::Kusama, + Westend => Self::Westend, + Rococo => Self::Rococo, + Wococo => Self::Wococo, + Ethereum { chain_id } => Self::Ethereum { chain_id }, + BitcoinCore => Self::BitcoinCore, + BitcoinCash => Self::BitcoinCash, + PolkadotBulletin => Self::PolkadotBulletin, + } + } +} + +impl From for Junction { + fn from(n: NetworkId) -> Self { + Self::GlobalConsensus(n) + } +} + +impl From<[u8; 32]> for Junction { + fn from(id: [u8; 32]) -> Self { + Self::AccountId32 { network: None, id } + } +} + +impl From>> for Junction { + fn from(key: BoundedVec>) -> Self { + key.as_bounded_slice().into() + } +} + +impl<'a> From>> for Junction { + fn from(key: BoundedSlice<'a, u8, ConstU32<32>>) -> Self { + let mut data = [0u8; 32]; + data[..key.len()].copy_from_slice(&key[..]); + Self::GeneralKey { length: key.len() as u8, data } + } +} + +impl<'a> TryFrom<&'a Junction> for BoundedSlice<'a, u8, ConstU32<32>> { + type Error = (); + fn try_from(key: &'a Junction) -> Result { + match key { + Junction::GeneralKey { length, data } => + BoundedSlice::try_from(&data[..data.len().min(*length as usize)]).map_err(|_| ()), + _ => Err(()), + } + } +} + +impl From<[u8; 20]> for Junction { + fn from(key: [u8; 20]) -> Self { + Self::AccountKey20 { network: None, key } + } +} + +impl From for Junction { + fn from(index: u64) -> Self { + Self::AccountIndex64 { network: None, index } + } +} + +impl From for Junction { + fn from(id: u128) -> Self { + Self::GeneralIndex(id) + } +} + +impl TryFrom for Junction { + type Error = (); + fn try_from(value: OldJunction) -> Result { + use OldJunction::*; + Ok(match value { + Parachain(id) => Self::Parachain(id), + AccountId32 { network: maybe_network, id } => + Self::AccountId32 { network: maybe_network.map(|network| network.into()), id }, + AccountIndex64 { network: maybe_network, index } => + Self::AccountIndex64 { network: maybe_network.map(|network| network.into()), index }, + AccountKey20 { network: maybe_network, key } => + Self::AccountKey20 { network: maybe_network.map(|network| network.into()), key }, + PalletInstance(index) => Self::PalletInstance(index), + GeneralIndex(id) => Self::GeneralIndex(id), + GeneralKey { length, data } => Self::GeneralKey { length, data }, + OnlyChild => Self::OnlyChild, + Plurality { id, part } => Self::Plurality { id, part }, + GlobalConsensus(network) => Self::GlobalConsensus(network.into()), + }) + } +} + +impl Junction { + /// Convert `self` into a `Location` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub fn into_location(self) -> Location { + Location::new(0, [self]) + } + + /// Convert `self` into a `Location` containing `n` parents. + /// + /// Similar to `Self::into_location`, with the added ability to specify the number of parent + /// junctions. + pub fn into_exterior(self, n: u8) -> Location { + Location::new(n, [self]) + } + + /// Convert `self` into a `VersionedLocation` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub fn into_versioned(self) -> VersionedLocation { + self.into_location().into_versioned() + } + + /// Remove the `NetworkId` value. + pub fn remove_network_id(&mut self) { + use Junction::*; + match self { + AccountId32 { ref mut network, .. } | + AccountIndex64 { ref mut network, .. } | + AccountKey20 { ref mut network, .. } => *network = None, + _ => {}, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + #[test] + fn junction_round_trip_works() { + let j = Junction::GeneralKey { length: 32, data: [1u8; 32] }; + let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap(); + assert_eq!(j, k); + + let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] }; + let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap(); + assert_eq!(j, k); + + let j = Junction::from(BoundedVec::try_from(vec![1u8, 2, 3, 4]).unwrap()); + let k = Junction::try_from(OldJunction::try_from(j).unwrap()).unwrap(); + assert_eq!(j, k); + let s: BoundedSlice<_, _> = (&k).try_into().unwrap(); + assert_eq!(s, &[1u8, 2, 3, 4][..]); + + let j = OldJunction::GeneralKey { length: 32, data: [1u8; 32] }; + let k = OldJunction::try_from(Junction::try_from(j).unwrap()).unwrap(); + assert_eq!(j, k); + } +} diff --git a/polkadot/xcm/src/v4/junctions.rs b/polkadot/xcm/src/v4/junctions.rs new file mode 100644 index 0000000000000000000000000000000000000000..48712dd74c6cdd57411409fda689ce22378b8a75 --- /dev/null +++ b/polkadot/xcm/src/v4/junctions.rs @@ -0,0 +1,723 @@ +// 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 . + +//! XCM `Junctions`/`InteriorLocation` datatype. + +use super::{Junction, Location, NetworkId}; +use alloc::sync::Arc; +use core::{convert::TryFrom, mem, ops::Range, result}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// Maximum number of `Junction`s that a `Junctions` can contain. +pub(crate) const MAX_JUNCTIONS: usize = 8; + +/// Non-parent junctions that can be constructed, up to the length of 8. This specific `Junctions` +/// implementation uses a Rust `enum` in order to make pattern matching easier. +/// +/// Parent junctions cannot be constructed with this type. Refer to `Location` for +/// instructions on constructing parent junctions. +#[derive( + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Encode, + Decode, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub enum Junctions { + /// The interpreting consensus system. + Here, + /// A relative path comprising 1 junction. + X1(Arc<[Junction; 1]>), + /// A relative path comprising 2 junctions. + X2(Arc<[Junction; 2]>), + /// A relative path comprising 3 junctions. + X3(Arc<[Junction; 3]>), + /// A relative path comprising 4 junctions. + X4(Arc<[Junction; 4]>), + /// A relative path comprising 5 junctions. + X5(Arc<[Junction; 5]>), + /// A relative path comprising 6 junctions. + X6(Arc<[Junction; 6]>), + /// A relative path comprising 7 junctions. + X7(Arc<[Junction; 7]>), + /// A relative path comprising 8 junctions. + X8(Arc<[Junction; 8]>), +} + +macro_rules! impl_junctions { + ($count:expr, $variant:ident) => { + impl From<[Junction; $count]> for Junctions { + fn from(junctions: [Junction; $count]) -> Self { + Self::$variant(Arc::new(junctions)) + } + } + impl PartialEq<[Junction; $count]> for Junctions { + fn eq(&self, rhs: &[Junction; $count]) -> bool { + self.as_slice() == rhs + } + } + }; +} + +impl_junctions!(1, X1); +impl_junctions!(2, X2); +impl_junctions!(3, X3); +impl_junctions!(4, X4); +impl_junctions!(5, X5); +impl_junctions!(6, X6); +impl_junctions!(7, X7); +impl_junctions!(8, X8); + +pub struct JunctionsIterator { + junctions: Junctions, + range: Range, +} + +impl Iterator for JunctionsIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.junctions.at(self.range.next()?).cloned() + } +} + +impl DoubleEndedIterator for JunctionsIterator { + fn next_back(&mut self) -> Option { + self.junctions.at(self.range.next_back()?).cloned() + } +} + +pub struct JunctionsRefIterator<'a> { + junctions: &'a Junctions, + range: Range, +} + +impl<'a> Iterator for JunctionsRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + self.junctions.at(self.range.next()?) + } +} + +impl<'a> DoubleEndedIterator for JunctionsRefIterator<'a> { + fn next_back(&mut self) -> Option<&'a Junction> { + self.junctions.at(self.range.next_back()?) + } +} +impl<'a> IntoIterator for &'a Junctions { + type Item = &'a Junction; + type IntoIter = JunctionsRefIterator<'a>; + fn into_iter(self) -> Self::IntoIter { + JunctionsRefIterator { junctions: self, range: 0..self.len() } + } +} + +impl IntoIterator for Junctions { + type Item = Junction; + type IntoIter = JunctionsIterator; + fn into_iter(self) -> Self::IntoIter { + JunctionsIterator { range: 0..self.len(), junctions: self } + } +} + +impl Junctions { + /// Convert `self` into a `Location` containing 0 parents. + /// + /// Similar to `Into::into`, except that this method can be used in a const evaluation context. + pub const fn into_location(self) -> Location { + Location { parents: 0, interior: self } + } + + /// Convert `self` into a `Location` containing `n` parents. + /// + /// Similar to `Self::into_location`, with the added ability to specify the number of parent + /// junctions. + pub const fn into_exterior(self, n: u8) -> Location { + Location { parents: n, interior: self } + } + + /// Casts `self` into a slice containing `Junction`s. + pub fn as_slice(&self) -> &[Junction] { + match self { + Junctions::Here => &[], + Junctions::X1(ref a) => &a[..], + Junctions::X2(ref a) => &a[..], + Junctions::X3(ref a) => &a[..], + Junctions::X4(ref a) => &a[..], + Junctions::X5(ref a) => &a[..], + Junctions::X6(ref a) => &a[..], + Junctions::X7(ref a) => &a[..], + Junctions::X8(ref a) => &a[..], + } + } + + /// Casts `self` into a mutable slice containing `Junction`s. + pub fn as_slice_mut(&mut self) -> &mut [Junction] { + match self { + Junctions::Here => &mut [], + Junctions::X1(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X2(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X3(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X4(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X5(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X6(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X7(ref mut a) => &mut Arc::make_mut(a)[..], + Junctions::X8(ref mut a) => &mut Arc::make_mut(a)[..], + } + } + + /// Remove the `NetworkId` value in any `Junction`s. + pub fn remove_network_id(&mut self) { + self.for_each_mut(Junction::remove_network_id); + } + + /// Treating `self` as the universal context, return the location of the local consensus system + /// from the point of view of the given `target`. + pub fn invert_target(&self, target: &Location) -> Result { + let mut itself = self.clone(); + let mut junctions = Self::Here; + for _ in 0..target.parent_count() { + junctions = junctions + .pushed_front_with(itself.take_last().unwrap_or(Junction::OnlyChild)) + .map_err(|_| ())?; + } + let parents = target.interior().len() as u8; + Ok(Location::new(parents, junctions)) + } + + /// Execute a function `f` on every junction. We use this since we cannot implement a mutable + /// `Iterator` without unsafe code. + pub fn for_each_mut(&mut self, x: impl FnMut(&mut Junction)) { + self.as_slice_mut().iter_mut().for_each(x) + } + + /// Extract the network ID treating this value as a universal location. + /// + /// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate + /// that this value is not a universal location. + pub fn global_consensus(&self) -> Result { + if let Some(Junction::GlobalConsensus(network)) = self.first() { + Ok(*network) + } else { + Err(()) + } + } + + /// Extract the network ID and the interior consensus location, treating this value as a + /// universal location. + /// + /// This will return an `Err` if the first item is not a `GlobalConsensus`, which would indicate + /// that this value is not a universal location. + pub fn split_global(self) -> Result<(NetworkId, Junctions), ()> { + match self.split_first() { + (location, Some(Junction::GlobalConsensus(network))) => Ok((network, location)), + _ => return Err(()), + } + } + + /// Treat `self` as a universal location and the context of `relative`, returning the universal + /// location of relative. + /// + /// This will return an error if `relative` has as many (or more) parents than there are + /// junctions in `self`, implying that relative refers into a different global consensus. + pub fn within_global(mut self, relative: Location) -> Result { + if self.len() <= relative.parent_count() as usize { + return Err(()) + } + for _ in 0..relative.parent_count() { + self.take_last(); + } + for j in relative.interior() { + self.push(*j).map_err(|_| ())?; + } + Ok(self) + } + + /// Consumes `self` and returns how `viewer` would address it locally. + pub fn relative_to(mut self, viewer: &Junctions) -> Location { + let mut i = 0; + while match (self.first(), viewer.at(i)) { + (Some(x), Some(y)) => x == y, + _ => false, + } { + self = self.split_first().0; + // NOTE: Cannot overflow as loop can only iterate at most `MAX_JUNCTIONS` times. + i += 1; + } + // AUDIT NOTES: + // - above loop ensures that `i <= viewer.len()`. + // - `viewer.len()` is at most `MAX_JUNCTIONS`, so won't overflow a `u8`. + Location::new((viewer.len() - i) as u8, self) + } + + /// Returns first junction, or `None` if the location is empty. + pub fn first(&self) -> Option<&Junction> { + self.as_slice().first() + } + + /// Returns last junction, or `None` if the location is empty. + pub fn last(&self) -> Option<&Junction> { + self.as_slice().last() + } + + /// Splits off the first junction, returning the remaining suffix (first item in tuple) and the + /// first element (second item in tuple) or `None` if it was empty. + pub fn split_first(self) -> (Junctions, Option) { + match self { + Junctions::Here => (Junctions::Here, None), + Junctions::X1(xs) => { + let [a] = *xs; + (Junctions::Here, Some(a)) + }, + Junctions::X2(xs) => { + let [a, b] = *xs; + ([b].into(), Some(a)) + }, + Junctions::X3(xs) => { + let [a, b, c] = *xs; + ([b, c].into(), Some(a)) + }, + Junctions::X4(xs) => { + let [a, b, c, d] = *xs; + ([b, c, d].into(), Some(a)) + }, + Junctions::X5(xs) => { + let [a, b, c, d, e] = *xs; + ([b, c, d, e].into(), Some(a)) + }, + Junctions::X6(xs) => { + let [a, b, c, d, e, f] = *xs; + ([b, c, d, e, f].into(), Some(a)) + }, + Junctions::X7(xs) => { + let [a, b, c, d, e, f, g] = *xs; + ([b, c, d, e, f, g].into(), Some(a)) + }, + Junctions::X8(xs) => { + let [a, b, c, d, e, f, g, h] = *xs; + ([b, c, d, e, f, g, h].into(), Some(a)) + }, + } + } + + /// Splits off the last junction, returning the remaining prefix (first item in tuple) and the + /// last element (second item in tuple) or `None` if it was empty. + pub fn split_last(self) -> (Junctions, Option) { + match self { + Junctions::Here => (Junctions::Here, None), + Junctions::X1(xs) => { + let [a] = *xs; + (Junctions::Here, Some(a)) + }, + Junctions::X2(xs) => { + let [a, b] = *xs; + ([a].into(), Some(b)) + }, + Junctions::X3(xs) => { + let [a, b, c] = *xs; + ([a, b].into(), Some(c)) + }, + Junctions::X4(xs) => { + let [a, b, c, d] = *xs; + ([a, b, c].into(), Some(d)) + }, + Junctions::X5(xs) => { + let [a, b, c, d, e] = *xs; + ([a, b, c, d].into(), Some(e)) + }, + Junctions::X6(xs) => { + let [a, b, c, d, e, f] = *xs; + ([a, b, c, d, e].into(), Some(f)) + }, + Junctions::X7(xs) => { + let [a, b, c, d, e, f, g] = *xs; + ([a, b, c, d, e, f].into(), Some(g)) + }, + Junctions::X8(xs) => { + let [a, b, c, d, e, f, g, h] = *xs; + ([a, b, c, d, e, f, g].into(), Some(h)) + }, + } + } + + /// Removes the first element from `self`, returning it (or `None` if it was empty). + pub fn take_first(&mut self) -> Option { + let mut d = Junctions::Here; + mem::swap(&mut *self, &mut d); + let (tail, head) = d.split_first(); + *self = tail; + head + } + + /// Removes the last element from `self`, returning it (or `None` if it was empty). + pub fn take_last(&mut self) -> Option { + let mut d = Junctions::Here; + mem::swap(&mut *self, &mut d); + let (head, tail) = d.split_last(); + *self = head; + tail + } + + /// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow. + pub fn push(&mut self, new: impl Into) -> result::Result<(), Junction> { + let new = new.into(); + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_with(new) { + Ok(s) => { + *self = s; + Ok(()) + }, + Err((s, j)) => { + *self = s; + Err(j) + }, + } + } + + /// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow. + pub fn push_front(&mut self, new: impl Into) -> result::Result<(), Junction> { + let new = new.into(); + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_front_with(new) { + Ok(s) => { + *self = s; + Ok(()) + }, + Err((s, j)) => { + *self = s; + Err(j) + }, + } + } + + /// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_with(self, new: impl Into) -> result::Result { + let new = new.into(); + Ok(match self { + Junctions::Here => [new].into(), + Junctions::X1(xs) => { + let [a] = *xs; + [a, new].into() + }, + Junctions::X2(xs) => { + let [a, b] = *xs; + [a, b, new].into() + }, + Junctions::X3(xs) => { + let [a, b, c] = *xs; + [a, b, c, new].into() + }, + Junctions::X4(xs) => { + let [a, b, c, d] = *xs; + [a, b, c, d, new].into() + }, + Junctions::X5(xs) => { + let [a, b, c, d, e] = *xs; + [a, b, c, d, e, new].into() + }, + Junctions::X6(xs) => { + let [a, b, c, d, e, f] = *xs; + [a, b, c, d, e, f, new].into() + }, + Junctions::X7(xs) => { + let [a, b, c, d, e, f, g] = *xs; + [a, b, c, d, e, f, g, new].into() + }, + s => Err((s, new))?, + }) + } + + /// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_front_with( + self, + new: impl Into, + ) -> result::Result { + let new = new.into(); + Ok(match self { + Junctions::Here => [new].into(), + Junctions::X1(xs) => { + let [a] = *xs; + [new, a].into() + }, + Junctions::X2(xs) => { + let [a, b] = *xs; + [new, a, b].into() + }, + Junctions::X3(xs) => { + let [a, b, c] = *xs; + [new, a, b, c].into() + }, + Junctions::X4(xs) => { + let [a, b, c, d] = *xs; + [new, a, b, c, d].into() + }, + Junctions::X5(xs) => { + let [a, b, c, d, e] = *xs; + [new, a, b, c, d, e].into() + }, + Junctions::X6(xs) => { + let [a, b, c, d, e, f] = *xs; + [new, a, b, c, d, e, f].into() + }, + Junctions::X7(xs) => { + let [a, b, c, d, e, f, g] = *xs; + [new, a, b, c, d, e, f, g].into() + }, + s => Err((s, new))?, + }) + } + + /// Mutate `self` so that it is suffixed with `suffix`. + /// + /// Does not modify `self` and returns `Err` with `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions, Junction::*, Location}; + /// # fn main() { + /// let mut m = Junctions::from([Parachain(21)]); + /// assert_eq!(m.append_with([PalletInstance(3)]), Ok(())); + /// assert_eq!(m, [Parachain(21), PalletInstance(3)]); + /// # } + /// ``` + pub fn append_with(&mut self, suffix: impl Into) -> Result<(), Junctions> { + let suffix = suffix.into(); + if self.len().saturating_add(suffix.len()) > MAX_JUNCTIONS { + return Err(suffix) + } + for j in suffix.into_iter() { + self.push(j).expect("Already checked the sum of the len()s; qed") + } + Ok(()) + } + + /// Returns the number of junctions in `self`. + pub fn len(&self) -> usize { + self.as_slice().len() + } + + /// Returns the junction at index `i`, or `None` if the location doesn't contain that many + /// elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + self.as_slice().get(i) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't + /// contain that many elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + self.as_slice_mut().get_mut(i) + } + + /// Returns a reference iterator over the junctions. + pub fn iter(&self) -> JunctionsRefIterator { + JunctionsRefIterator { junctions: self, range: 0..self.len() } + } + + /// Ensures that self begins with `prefix` and that it has a single `Junction` item following. + /// If so, returns a reference to this `Junction` item. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions, Junction::*}; + /// # fn main() { + /// let mut m = Junctions::from([Parachain(2), PalletInstance(3), OnlyChild]); + /// assert_eq!(m.match_and_split(&[Parachain(2), PalletInstance(3)].into()), Some(&OnlyChild)); + /// assert_eq!(m.match_and_split(&[Parachain(2)].into()), None); + /// # } + /// ``` + pub fn match_and_split(&self, prefix: &Junctions) -> Option<&Junction> { + if prefix.len() + 1 != self.len() { + return None + } + for i in 0..prefix.len() { + if prefix.at(i) != self.at(i) { + return None + } + } + return self.at(prefix.len()) + } + + pub fn starts_with(&self, prefix: &Junctions) -> bool { + prefix.len() <= self.len() && prefix.iter().zip(self.iter()).all(|(x, y)| x == y) + } +} + +impl TryFrom for Junctions { + type Error = Location; + fn try_from(x: Location) -> result::Result { + if x.parent_count() > 0 { + Err(x) + } else { + Ok(x.interior().clone()) + } + } +} + +impl> From for Junctions { + fn from(x: T) -> Self { + [x.into()].into() + } +} + +impl From<[Junction; 0]> for Junctions { + fn from(_: [Junction; 0]) -> Self { + Self::Here + } +} + +impl From<()> for Junctions { + fn from(_: ()) -> Self { + Self::Here + } +} + +xcm_procedural::impl_conversion_functions_for_junctions_v4!(); + +#[cfg(test)] +mod tests { + use super::{super::prelude::*, *}; + + #[test] + fn inverting_works() { + let context: InteriorLocation = (Parachain(1000), PalletInstance(42)).into(); + let target = (Parent, PalletInstance(69)).into(); + let expected = (Parent, PalletInstance(42)).into(); + let inverted = context.invert_target(&target).unwrap(); + assert_eq!(inverted, expected); + + let context: InteriorLocation = + (Parachain(1000), PalletInstance(42), GeneralIndex(1)).into(); + let target = (Parent, Parent, PalletInstance(69), GeneralIndex(2)).into(); + let expected = (Parent, Parent, PalletInstance(42), GeneralIndex(1)).into(); + let inverted = context.invert_target(&target).unwrap(); + assert_eq!(inverted, expected); + } + + #[test] + fn relative_to_works() { + use NetworkId::*; + assert_eq!( + Junctions::from([Polkadot.into()]).relative_to(&Junctions::from([Kusama.into()])), + (Parent, Polkadot).into() + ); + let base = Junctions::from([Kusama.into(), Parachain(1), PalletInstance(1)]); + + // Ancestors. + assert_eq!(Here.relative_to(&base), (Parent, Parent, Parent).into()); + assert_eq!(Junctions::from([Kusama.into()]).relative_to(&base), (Parent, Parent).into()); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(1)]).relative_to(&base), + (Parent,).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(1), PalletInstance(1)]).relative_to(&base), + Here.into() + ); + + // Ancestors with one child. + assert_eq!( + Junctions::from([Polkadot.into()]).relative_to(&base), + (Parent, Parent, Parent, Polkadot).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(2)]).relative_to(&base), + (Parent, Parent, Parachain(2)).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(1), PalletInstance(2)]).relative_to(&base), + (Parent, PalletInstance(2)).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(1), PalletInstance(1), [1u8; 32].into()]) + .relative_to(&base), + ([1u8; 32],).into() + ); + + // Ancestors with grandchildren. + assert_eq!( + Junctions::from([Polkadot.into(), Parachain(1)]).relative_to(&base), + (Parent, Parent, Parent, Polkadot, Parachain(1)).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(2), PalletInstance(1)]).relative_to(&base), + (Parent, Parent, Parachain(2), PalletInstance(1)).into() + ); + assert_eq!( + Junctions::from([Kusama.into(), Parachain(1), PalletInstance(2), [1u8; 32].into()]) + .relative_to(&base), + (Parent, PalletInstance(2), [1u8; 32]).into() + ); + assert_eq!( + Junctions::from([ + Kusama.into(), + Parachain(1), + PalletInstance(1), + [1u8; 32].into(), + 1u128.into() + ]) + .relative_to(&base), + ([1u8; 32], 1u128).into() + ); + } + + #[test] + fn global_consensus_works() { + use NetworkId::*; + assert_eq!(Junctions::from([Polkadot.into()]).global_consensus(), Ok(Polkadot)); + assert_eq!(Junctions::from([Kusama.into(), 1u64.into()]).global_consensus(), Ok(Kusama)); + assert_eq!(Here.global_consensus(), Err(())); + assert_eq!(Junctions::from([1u64.into()]).global_consensus(), Err(())); + assert_eq!(Junctions::from([1u64.into(), Kusama.into()]).global_consensus(), Err(())); + } + + #[test] + fn test_conversion() { + use super::{Junction::*, NetworkId::*}; + let x: Junctions = GlobalConsensus(Polkadot).into(); + assert_eq!(x, Junctions::from([GlobalConsensus(Polkadot)])); + let x: Junctions = Polkadot.into(); + assert_eq!(x, Junctions::from([GlobalConsensus(Polkadot)])); + let x: Junctions = (Polkadot, Kusama).into(); + assert_eq!(x, Junctions::from([GlobalConsensus(Polkadot), GlobalConsensus(Kusama)])); + } + + #[test] + fn encode_decode_junctions_works() { + let original = Junctions::from([ + Polkadot.into(), + Kusama.into(), + 1u64.into(), + GlobalConsensus(Polkadot), + Parachain(123), + PalletInstance(45), + ]); + let encoded = original.encode(); + assert_eq!(encoded, &[6, 9, 2, 9, 3, 2, 0, 4, 9, 2, 0, 237, 1, 4, 45]); + let decoded = Junctions::decode(&mut &encoded[..]).unwrap(); + assert_eq!(decoded, original); + } +} diff --git a/polkadot/xcm/src/v4/location.rs b/polkadot/xcm/src/v4/location.rs new file mode 100644 index 0000000000000000000000000000000000000000..db55c3d3034ce3f09b7f23c8acd8a69efbc6afc2 --- /dev/null +++ b/polkadot/xcm/src/v4/location.rs @@ -0,0 +1,746 @@ +// 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 . + +//! XCM `Location` datatype. + +use super::{traits::Reanchorable, Junction, Junctions}; +use crate::{v3::MultiLocation as OldLocation, VersionedLocation}; +use core::{ + convert::{TryFrom, TryInto}, + result, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// A relative path between state-bearing consensus systems. +/// +/// A location in a consensus system is defined as an *isolatable state machine* held within global +/// consensus. The location in question need not have a sophisticated consensus algorithm of its +/// own; a single account within Ethereum, for example, could be considered a location. +/// +/// A very-much non-exhaustive list of types of location include: +/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. +/// - A layer-0 super-chain, e.g. the Polkadot Relay chain. +/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. +/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based +/// Substrate chain. +/// - An account. +/// +/// A `Location` is a *relative identifier*, meaning that it can only be used to define the +/// relative path between two locations, and cannot generally be used to refer to a location +/// universally. It is comprised of an integer number of parents specifying the number of times to +/// "escape" upwards into the containing consensus system and then a number of *junctions*, each +/// diving down and specifying some interior portion of state (which may be considered a +/// "sub-consensus" system). +/// +/// This specific `Location` implementation uses a `Junctions` datatype which is a Rust `enum` +/// in order to make pattern matching easier. There are occasions where it is important to ensure +/// that a value is strictly an interior location, in those cases, `Junctions` may be used. +/// +/// The `Location` value of `Null` simply refers to the interpreting consensus system. +#[derive( + Clone, + Decode, + Encode, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct Location { + /// The number of parent junctions at the beginning of this `Location`. + pub parents: u8, + /// The interior (i.e. non-parent) junctions that this `Location` contains. + pub interior: Junctions, +} + +impl Default for Location { + fn default() -> Self { + Self { parents: 0, interior: Junctions::Here } + } +} + +/// A relative location which is constrained to be an interior location of the context. +/// +/// See also `Location`. +pub type InteriorLocation = Junctions; + +impl Location { + /// Creates a new `Location` with the given number of parents and interior junctions. + pub fn new(parents: u8, interior: impl Into) -> Location { + Location { parents, interior: interior.into() } + } + + /// Consume `self` and return the equivalent `VersionedLocation` value. + pub const fn into_versioned(self) -> VersionedLocation { + VersionedLocation::V4(self) + } + + /// Creates a new `Location` with 0 parents and a `Here` interior. + /// + /// The resulting `Location` can be interpreted as the "current consensus system". + pub const fn here() -> Location { + Location { parents: 0, interior: Junctions::Here } + } + + /// Creates a new `Location` which evaluates to the parent context. + pub const fn parent() -> Location { + Location { parents: 1, interior: Junctions::Here } + } + + /// Creates a new `Location` with `parents` and an empty (`Here`) interior. + pub const fn ancestor(parents: u8) -> Location { + Location { parents, interior: Junctions::Here } + } + + /// Whether the `Location` has no parents and has a `Here` interior. + pub fn is_here(&self) -> bool { + self.parents == 0 && self.interior.len() == 0 + } + + /// Remove the `NetworkId` value in any interior `Junction`s. + pub fn remove_network_id(&mut self) { + self.interior.remove_network_id(); + } + + /// Return a reference to the interior field. + pub fn interior(&self) -> &Junctions { + &self.interior + } + + /// Return a mutable reference to the interior field. + pub fn interior_mut(&mut self) -> &mut Junctions { + &mut self.interior + } + + /// Returns the number of `Parent` junctions at the beginning of `self`. + pub const fn parent_count(&self) -> u8 { + self.parents + } + + /// Returns the parent count and the interior [`Junctions`] as a tuple. + /// + /// To be used when pattern matching, for example: + /// + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location}; + /// fn get_parachain_id(loc: &Location) -> Option { + /// match loc.unpack() { + /// (0, [Parachain(id)]) => Some(*id), + /// _ => None + /// } + /// } + /// ``` + pub fn unpack(&self) -> (u8, &[Junction]) { + (self.parents, self.interior.as_slice()) + } + + /// Returns boolean indicating whether `self` contains only the specified amount of + /// parents and no interior junctions. + pub const fn contains_parents_only(&self, count: u8) -> bool { + matches!(self.interior, Junctions::Here) && self.parents == count + } + + /// Returns the number of parents and junctions in `self`. + pub fn len(&self) -> usize { + self.parent_count() as usize + self.interior.len() + } + + /// Returns the first interior junction, or `None` if the location is empty or contains only + /// parents. + pub fn first_interior(&self) -> Option<&Junction> { + self.interior.first() + } + + /// Returns last junction, or `None` if the location is empty or contains only parents. + pub fn last(&self) -> Option<&Junction> { + self.interior.last() + } + + /// Splits off the first interior junction, returning the remaining suffix (first item in tuple) + /// and the first element (second item in tuple) or `None` if it was empty. + pub fn split_first_interior(self) -> (Location, Option) { + let Location { parents, interior: junctions } = self; + let (suffix, first) = junctions.split_first(); + let location = Location { parents, interior: suffix }; + (location, first) + } + + /// Splits off the last interior junction, returning the remaining prefix (first item in tuple) + /// and the last element (second item in tuple) or `None` if it was empty or if `self` only + /// contains parents. + pub fn split_last_interior(self) -> (Location, Option) { + let Location { parents, interior: junctions } = self; + let (prefix, last) = junctions.split_last(); + let location = Location { parents, interior: prefix }; + (location, last) + } + + /// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_interior(&mut self, new: impl Into) -> result::Result<(), Junction> { + self.interior.push(new) + } + + /// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_front_interior( + &mut self, + new: impl Into, + ) -> result::Result<(), Junction> { + self.interior.push_front(new) + } + + /// Consumes `self` and returns a `Location` suffixed with `new`, or an `Err` with + /// theoriginal value of `self` in case of overflow. + pub fn pushed_with_interior( + self, + new: impl Into, + ) -> result::Result { + match self.interior.pushed_with(new) { + Ok(i) => Ok(Location { interior: i, parents: self.parents }), + Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)), + } + } + + /// Consumes `self` and returns a `Location` prefixed with `new`, or an `Err` with the + /// original value of `self` in case of overflow. + pub fn pushed_front_with_interior( + self, + new: impl Into, + ) -> result::Result { + match self.interior.pushed_front_with(new) { + Ok(i) => Ok(Location { interior: i, parents: self.parents }), + Err((i, j)) => Err((Location { interior: i, parents: self.parents }, j)), + } + } + + /// Returns the junction at index `i`, or `None` if the location is a parent or if the location + /// does not contain that many elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + let num_parents = self.parents as usize; + if i < num_parents { + return None + } + self.interior.at(i - num_parents) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location is a + /// parent or if it doesn't contain that many elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + let num_parents = self.parents as usize; + if i < num_parents { + return None + } + self.interior.at_mut(i - num_parents) + } + + /// Decrements the parent count by 1. + pub fn dec_parent(&mut self) { + self.parents = self.parents.saturating_sub(1); + } + + /// Removes the first interior junction from `self`, returning it + /// (or `None` if it was empty or if `self` contains only parents). + pub fn take_first_interior(&mut self) -> Option { + self.interior.take_first() + } + + /// Removes the last element from `interior`, returning it (or `None` if it was empty or if + /// `self` only contains parents). + pub fn take_last(&mut self) -> Option { + self.interior.take_last() + } + + /// Ensures that `self` has the same number of parents as `prefix`, its junctions begins with + /// the junctions of `prefix` and that it has a single `Junction` item following. + /// If so, returns a reference to this `Junction` item. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location}; + /// # fn main() { + /// let mut m = Location::new(1, [PalletInstance(3), OnlyChild]); + /// assert_eq!( + /// m.match_and_split(&Location::new(1, [PalletInstance(3)])), + /// Some(&OnlyChild), + /// ); + /// assert_eq!(m.match_and_split(&Location::new(1, Here)), None); + /// # } + /// ``` + pub fn match_and_split(&self, prefix: &Location) -> Option<&Junction> { + if self.parents != prefix.parents { + return None + } + self.interior.match_and_split(&prefix.interior) + } + + pub fn starts_with(&self, prefix: &Location) -> bool { + self.parents == prefix.parents && self.interior.starts_with(&prefix.interior) + } + + /// Mutate `self` so that it is suffixed with `suffix`. + /// + /// Does not modify `self` and returns `Err` with `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent}; + /// # fn main() { + /// let mut m: Location = (Parent, Parachain(21), 69u64).into(); + /// assert_eq!(m.append_with((Parent, PalletInstance(3))), Ok(())); + /// assert_eq!(m, Location::new(1, [Parachain(21), PalletInstance(3)])); + /// # } + /// ``` + pub fn append_with(&mut self, suffix: impl Into) -> Result<(), Self> { + let prefix = core::mem::replace(self, suffix.into()); + match self.prepend_with(prefix) { + Ok(()) => Ok(()), + Err(prefix) => Err(core::mem::replace(self, prefix)), + } + } + + /// Consume `self` and return its value suffixed with `suffix`. + /// + /// Returns `Err` with the original value of `self` and `suffix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent}; + /// # fn main() { + /// let mut m: Location = (Parent, Parachain(21), 69u64).into(); + /// let r = m.appended_with((Parent, PalletInstance(3))).unwrap(); + /// assert_eq!(r, Location::new(1, [Parachain(21), PalletInstance(3)])); + /// # } + /// ``` + pub fn appended_with(mut self, suffix: impl Into) -> Result { + match self.append_with(suffix) { + Ok(()) => Ok(self), + Err(suffix) => Err((self, suffix)), + } + } + + /// Mutate `self` so that it is prefixed with `prefix`. + /// + /// Does not modify `self` and returns `Err` with `prefix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent}; + /// # fn main() { + /// let mut m: Location = (Parent, Parent, PalletInstance(3)).into(); + /// assert_eq!(m.prepend_with((Parent, Parachain(21), OnlyChild)), Ok(())); + /// assert_eq!(m, Location::new(1, [PalletInstance(3)])); + /// # } + /// ``` + pub fn prepend_with(&mut self, prefix: impl Into) -> Result<(), Self> { + // prefix self (suffix) + // P .. P I .. I p .. p i .. i + let mut prefix = prefix.into(); + let prepend_interior = prefix.interior.len().saturating_sub(self.parents as usize); + let final_interior = self.interior.len().saturating_add(prepend_interior); + if final_interior > super::junctions::MAX_JUNCTIONS { + return Err(prefix) + } + let suffix_parents = (self.parents as usize).saturating_sub(prefix.interior.len()); + let final_parents = (prefix.parents as usize).saturating_add(suffix_parents); + if final_parents > 255 { + return Err(prefix) + } + + // cancel out the final item on the prefix interior for one of the suffix's parents. + while self.parents > 0 && prefix.take_last().is_some() { + self.dec_parent(); + } + + // now we have either removed all suffix's parents or prefix interior. + // this means we can combine the prefix's and suffix's remaining parents/interior since + // we know that with at least one empty, the overall order will be respected: + // prefix self (suffix) + // P .. P (I) p .. p i .. i => P + p .. (no I) i + // -- or -- + // P .. P I .. I (p) i .. i => P (no p) .. I + i + + self.parents = self.parents.saturating_add(prefix.parents); + for j in prefix.interior.into_iter().rev() { + self.push_front_interior(j) + .expect("final_interior no greater than MAX_JUNCTIONS; qed"); + } + Ok(()) + } + + /// Consume `self` and return its value prefixed with `prefix`. + /// + /// Returns `Err` with the original value of `self` and `prefix` in case of overflow. + /// + /// # Example + /// ```rust + /// # use staging_xcm::v4::{Junctions::*, Junction::*, Location, Parent}; + /// # fn main() { + /// let m: Location = (Parent, Parent, PalletInstance(3)).into(); + /// let r = m.prepended_with((Parent, Parachain(21), OnlyChild)).unwrap(); + /// assert_eq!(r, Location::new(1, [PalletInstance(3)])); + /// # } + /// ``` + pub fn prepended_with(mut self, prefix: impl Into) -> Result { + match self.prepend_with(prefix) { + Ok(()) => Ok(self), + Err(prefix) => Err((self, prefix)), + } + } + + /// Remove any unneeded parents/junctions in `self` based on the given context it will be + /// interpreted in. + pub fn simplify(&mut self, context: &Junctions) { + if context.len() < self.parents as usize { + // Not enough context + return + } + while self.parents > 0 { + let maybe = context.at(context.len() - (self.parents as usize)); + match (self.interior.first(), maybe) { + (Some(i), Some(j)) if i == j => { + self.interior.take_first(); + self.parents -= 1; + }, + _ => break, + } + } + } + + /// Return the Location subsection identifying the chain that `self` points to. + pub fn chain_location(&self) -> Location { + let mut clone = self.clone(); + // start popping junctions until we reach chain identifier + while let Some(j) = clone.last() { + if matches!(j, Junction::Parachain(_) | Junction::GlobalConsensus(_)) { + // return chain subsection + return clone + } else { + (clone, _) = clone.split_last_interior(); + } + } + Location::new(clone.parents, Junctions::Here) + } +} + +impl Reanchorable for Location { + type Error = Self; + + /// Mutate `self` so that it represents the same location from the point of view of `target`. + /// The context of `self` is provided as `context`. + /// + /// Does not modify `self` in case of overflow. + fn reanchor(&mut self, target: &Location, context: &InteriorLocation) -> Result<(), ()> { + // TODO: https://github.com/paritytech/polkadot/issues/4489 Optimize this. + + // 1. Use our `context` to figure out how the `target` would address us. + let inverted_target = context.invert_target(target)?; + + // 2. Prepend `inverted_target` to `self` to get self's location from the perspective of + // `target`. + self.prepend_with(inverted_target).map_err(|_| ())?; + + // 3. Given that we know some of `target` context, ensure that any parents in `self` are + // strictly needed. + self.simplify(target.interior()); + + Ok(()) + } + + /// Consume `self` and return a new value representing the same location from the point of view + /// of `target`. The context of `self` is provided as `context`. + /// + /// Returns the original `self` in case of overflow. + fn reanchored(mut self, target: &Location, context: &InteriorLocation) -> Result { + match self.reanchor(target, context) { + Ok(()) => Ok(self), + Err(()) => Err(self), + } + } +} + +impl TryFrom for Option { + type Error = (); + fn try_from(value: OldLocation) -> result::Result { + Ok(Some(Location::try_from(value)?)) + } +} + +impl TryFrom for Location { + type Error = (); + fn try_from(x: OldLocation) -> result::Result { + Ok(Location { parents: x.parents, interior: x.interior.try_into()? }) + } +} + +/// A unit struct which can be converted into a `Location` of `parents` value 1. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Parent; +impl From for Location { + fn from(_: Parent) -> Self { + Location { parents: 1, interior: Junctions::Here } + } +} + +/// A tuple struct which can be converted into a `Location` of `parents` value 1 with the inner +/// interior. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ParentThen(pub Junctions); +impl From for Location { + fn from(ParentThen(interior): ParentThen) -> Self { + Location { parents: 1, interior } + } +} + +/// A unit struct which can be converted into a `Location` of the inner `parents` value. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Ancestor(pub u8); +impl From for Location { + fn from(Ancestor(parents): Ancestor) -> Self { + Location { parents, interior: Junctions::Here } + } +} + +/// A unit struct which can be converted into a `Location` of the inner `parents` value and the +/// inner interior. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct AncestorThen(pub u8, pub Interior); +impl> From> for Location { + fn from(AncestorThen(parents, interior): AncestorThen) -> Self { + Location { parents, interior: interior.into() } + } +} + +xcm_procedural::impl_conversion_functions_for_location_v4!(); + +#[cfg(test)] +mod tests { + use crate::v4::prelude::*; + use parity_scale_codec::{Decode, Encode}; + + #[test] + fn conversion_works() { + let x: Location = Parent.into(); + assert_eq!(x, Location { parents: 1, interior: Here }); + // let x: Location = (Parent,).into(); + // assert_eq!(x, Location { parents: 1, interior: Here }); + // let x: Location = (Parent, Parent).into(); + // assert_eq!(x, Location { parents: 2, interior: Here }); + let x: Location = (Parent, Parent, OnlyChild).into(); + assert_eq!(x, Location { parents: 2, interior: OnlyChild.into() }); + let x: Location = OnlyChild.into(); + assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() }); + let x: Location = (OnlyChild,).into(); + assert_eq!(x, Location { parents: 0, interior: OnlyChild.into() }); + } + + #[test] + fn simplify_basic_works() { + let mut location: Location = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = [Parachain(1000), PalletInstance(42)].into(); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into(); + let context = [PalletInstance(42)].into(); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: Location = (Parent, PalletInstance(42), GeneralIndex(69)).into(); + let context = [Parachain(1000), PalletInstance(42)].into(); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: Location = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = [OnlyChild, Parachain(1000), PalletInstance(42)].into(); + let expected = GeneralIndex(69).into(); + location.simplify(&context); + assert_eq!(location, expected); + } + + #[test] + fn simplify_incompatible_location_fails() { + let mut location: Location = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = [Parachain(1000), PalletInstance(42), GeneralIndex(42)].into(); + let expected = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + location.simplify(&context); + assert_eq!(location, expected); + + let mut location: Location = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + let context = [Parachain(1000)].into(); + let expected = + (Parent, Parent, Parachain(1000), PalletInstance(42), GeneralIndex(69)).into(); + location.simplify(&context); + assert_eq!(location, expected); + } + + #[test] + fn reanchor_works() { + let mut id: Location = (Parent, Parachain(1000), GeneralIndex(42)).into(); + let context = Parachain(2000).into(); + let target = (Parent, Parachain(1000)).into(); + let expected = GeneralIndex(42).into(); + id.reanchor(&target, &context).unwrap(); + assert_eq!(id, expected); + } + + #[test] + fn encode_and_decode_works() { + let m = Location { + parents: 1, + interior: [Parachain(42), AccountIndex64 { network: None, index: 23 }].into(), + }; + let encoded = m.encode(); + assert_eq!(encoded, [1, 2, 0, 168, 2, 0, 92].to_vec()); + let decoded = Location::decode(&mut &encoded[..]); + assert_eq!(decoded, Ok(m)); + } + + #[test] + fn match_and_split_works() { + let m = Location { + parents: 1, + interior: [Parachain(42), AccountIndex64 { network: None, index: 23 }].into(), + }; + assert_eq!(m.match_and_split(&Location { parents: 1, interior: Here }), None); + assert_eq!( + m.match_and_split(&Location { parents: 1, interior: [Parachain(42)].into() }), + Some(&AccountIndex64 { network: None, index: 23 }) + ); + assert_eq!(m.match_and_split(&m), None); + } + + #[test] + fn append_with_works() { + let acc = AccountIndex64 { network: None, index: 23 }; + let mut m = Location { parents: 1, interior: [Parachain(42)].into() }; + assert_eq!(m.append_with([PalletInstance(3), acc]), Ok(())); + assert_eq!( + m, + Location { parents: 1, interior: [Parachain(42), PalletInstance(3), acc].into() } + ); + + // cannot append to create overly long location + let acc = AccountIndex64 { network: None, index: 23 }; + let m = Location { + parents: 254, + interior: [Parachain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild].into(), + }; + let suffix: Location = (PalletInstance(3), acc, OnlyChild, OnlyChild).into(); + assert_eq!(m.clone().append_with(suffix.clone()), Err(suffix)); + } + + #[test] + fn prepend_with_works() { + let mut m = Location { + parents: 1, + interior: [Parachain(42), AccountIndex64 { network: None, index: 23 }].into(), + }; + assert_eq!(m.prepend_with(Location { parents: 1, interior: [OnlyChild].into() }), Ok(())); + assert_eq!( + m, + Location { + parents: 1, + interior: [Parachain(42), AccountIndex64 { network: None, index: 23 }].into() + } + ); + + // cannot prepend to create overly long location + let mut m = Location { parents: 254, interior: [Parachain(42)].into() }; + let prefix = Location { parents: 2, interior: Here }; + assert_eq!(m.prepend_with(prefix.clone()), Err(prefix)); + + let prefix = Location { parents: 1, interior: Here }; + assert_eq!(m.prepend_with(prefix.clone()), Ok(())); + assert_eq!(m, Location { parents: 255, interior: [Parachain(42)].into() }); + } + + #[test] + fn double_ended_ref_iteration_works() { + let m: Junctions = [Parachain(1000), Parachain(3), PalletInstance(5)].into(); + let mut iter = m.iter(); + + let first = iter.next().unwrap(); + assert_eq!(first, &Parachain(1000)); + let third = iter.next_back().unwrap(); + assert_eq!(third, &PalletInstance(5)); + let second = iter.next_back().unwrap(); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_back(), None); + assert_eq!(second, &Parachain(3)); + + let res = Here + .pushed_with(*first) + .unwrap() + .pushed_with(*second) + .unwrap() + .pushed_with(*third) + .unwrap(); + assert_eq!(m, res); + + // make sure there's no funny business with the 0 indexing + let m = Here; + let mut iter = m.iter(); + + assert_eq!(iter.next(), None); + assert_eq!(iter.next_back(), None); + } + + #[test] + fn conversion_from_other_types_works() { + use crate::v3; + use core::convert::TryInto; + + fn takes_location>(_arg: Arg) {} + + takes_location(Parent); + takes_location(Here); + takes_location([Parachain(42)]); + takes_location((Ancestor(255), PalletInstance(8))); + takes_location((Ancestor(5), Parachain(1), PalletInstance(3))); + takes_location((Ancestor(2), Here)); + takes_location(AncestorThen( + 3, + [Parachain(43), AccountIndex64 { network: None, index: 155 }], + )); + takes_location((Parent, AccountId32 { network: None, id: [0; 32] })); + takes_location((Parent, Here)); + takes_location(ParentThen([Parachain(75)].into())); + takes_location([Parachain(100), PalletInstance(3)]); + + assert_eq!(v3::Location::from(v3::Junctions::Here).try_into(), Ok(Location::here())); + assert_eq!(v3::Location::from(v3::Parent).try_into(), Ok(Location::parent())); + assert_eq!( + v3::Location::from((v3::Parent, v3::Parent, v3::Junction::GeneralIndex(42u128),)) + .try_into(), + Ok(Location { parents: 2, interior: [GeneralIndex(42u128)].into() }), + ); + } +} diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..30ee485589a28191e61fc351eda0f91d2773d589 --- /dev/null +++ b/polkadot/xcm/src/v4/mod.rs @@ -0,0 +1,1509 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 4 of the Cross-Consensus Message format data structures. + +pub use super::v2::GetWeight; +use super::v3::{ + Instruction as OldInstruction, PalletInfo as OldPalletInfo, + QueryResponseInfo as OldQueryResponseInfo, Response as OldResponse, Xcm as OldXcm, +}; +use crate::DoubleEncoded; +use alloc::{vec, vec::Vec}; +use bounded_collections::{parameter_types, BoundedVec}; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + result, +}; +use derivative::Derivative; +use parity_scale_codec::{ + self, decode_vec_with_len, Compact, Decode, Encode, Error as CodecError, Input as CodecInput, + MaxEncodedLen, +}; +use scale_info::TypeInfo; + +mod asset; +mod junction; +pub(crate) mod junctions; +mod location; +mod traits; + +pub use asset::{ + Asset, AssetFilter, AssetId, AssetInstance, Assets, Fungibility, WildAsset, WildFungibility, + MAX_ITEMS_IN_ASSETS, +}; +pub use junction::{BodyId, BodyPart, Junction, NetworkId}; +pub use junctions::Junctions; +pub use location::{Ancestor, AncestorThen, InteriorLocation, Location, Parent, ParentThen}; +pub use traits::{ + send_xcm, validate_send, Error, ExecuteXcm, Outcome, PreparedMessage, Reanchorable, Result, + SendError, SendResult, SendXcm, Weight, XcmHash, +}; +// These parts of XCM v3 are unchanged in XCM v4, and are re-imported here. +pub use super::v3::{MaybeErrorCode, OriginKind, WeightLimit}; + +/// This module's XCM version. +pub const VERSION: super::Version = 4; + +/// An identifier for a query. +pub type QueryId = u64; + +#[derive(Derivative, Default, Encode, TypeInfo)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +#[scale_info(bounds(), skip_type_params(Call))] +pub struct Xcm(pub Vec>); + +pub const MAX_INSTRUCTIONS_TO_DECODE: u8 = 100; + +environmental::environmental!(instructions_count: u8); + +impl Decode for Xcm { + fn decode(input: &mut I) -> core::result::Result { + instructions_count::using_once(&mut 0, || { + let number_of_instructions: u32 = >::decode(input)?.into(); + instructions_count::with(|count| { + *count = count.saturating_add(number_of_instructions as u8); + if *count > MAX_INSTRUCTIONS_TO_DECODE { + return Err(CodecError::from("Max instructions exceeded")) + } + Ok(()) + }) + .expect("Called in `using` context and thus can not return `None`; qed")?; + let decoded_instructions = decode_vec_with_len(input, number_of_instructions as usize)?; + Ok(Self(decoded_instructions)) + }) + } +} + +impl Xcm { + /// Create an empty instance. + pub fn new() -> Self { + Self(vec![]) + } + + /// Return `true` if no instructions are held in `self`. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Return the number of instructions held in `self`. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Return a reference to the inner value. + pub fn inner(&self) -> &[Instruction] { + &self.0 + } + + /// Return a mutable reference to the inner value. + pub fn inner_mut(&mut self) -> &mut Vec> { + &mut self.0 + } + + /// Consume and return the inner value. + pub fn into_inner(self) -> Vec> { + self.0 + } + + /// Return an iterator over references to the items. + pub fn iter(&self) -> impl Iterator> { + self.0.iter() + } + + /// Return an iterator over mutable references to the items. + pub fn iter_mut(&mut self) -> impl Iterator> { + self.0.iter_mut() + } + + /// Consume and return an iterator over the items. + pub fn into_iter(self) -> impl Iterator> { + self.0.into_iter() + } + + /// Consume and either return `self` if it contains some instructions, or if it's empty, then + /// instead return the result of `f`. + pub fn or_else(self, f: impl FnOnce() -> Self) -> Self { + if self.0.is_empty() { + f() + } else { + self + } + } + + /// Return the first instruction, if any. + pub fn first(&self) -> Option<&Instruction> { + self.0.first() + } + + /// Return the last instruction, if any. + pub fn last(&self) -> Option<&Instruction> { + self.0.last() + } + + /// Return the only instruction, contained in `Self`, iff only one exists (`None` otherwise). + pub fn only(&self) -> Option<&Instruction> { + if self.0.len() == 1 { + self.0.first() + } else { + None + } + } + + /// Return the only instruction, contained in `Self`, iff only one exists (returns `self` + /// otherwise). + pub fn into_only(mut self) -> core::result::Result, Self> { + if self.0.len() == 1 { + self.0.pop().ok_or(self) + } else { + Err(self) + } + } +} + +impl From>> for Xcm { + fn from(c: Vec>) -> Self { + Self(c) + } +} + +impl From> for Vec> { + fn from(c: Xcm) -> Self { + c.0 + } +} + +/// A prelude for importing all types typically used when interacting with XCM messages. +pub mod prelude { + mod contents { + pub use super::super::{ + send_xcm, validate_send, Ancestor, AncestorThen, Asset, + AssetFilter::{self, *}, + AssetId, + AssetInstance::{self, *}, + Assets, BodyId, BodyPart, Error as XcmError, ExecuteXcm, + Fungibility::{self, *}, + Instruction::*, + InteriorLocation, + Junction::{self, *}, + Junctions::{self, Here}, + Location, MaybeErrorCode, + NetworkId::{self, *}, + OriginKind, Outcome, PalletInfo, Parent, ParentThen, PreparedMessage, QueryId, + QueryResponseInfo, Reanchorable, Response, Result as XcmResult, SendError, SendResult, + SendXcm, Weight, + WeightLimit::{self, *}, + WildAsset::{self, *}, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + XcmContext, XcmHash, XcmWeightInfo, VERSION as XCM_VERSION, + }; + } + pub use super::{Instruction, Xcm}; + pub use contents::*; + pub mod opaque { + pub use super::{ + super::opaque::{Instruction, Xcm}, + contents::*, + }; + } +} + +parameter_types! { + 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. + pub MaxDispatchErrorLen: u32 = 128; + pub MaxPalletsInfo: u32 = 64; +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub struct PalletInfo { + #[codec(compact)] + index: u32, + name: BoundedVec, + module_name: BoundedVec, + #[codec(compact)] + major: u32, + #[codec(compact)] + minor: u32, + #[codec(compact)] + patch: u32, +} + +impl TryInto for PalletInfo { + type Error = (); + + fn try_into(self) -> result::Result { + OldPalletInfo::new( + self.index, + self.name.into_inner(), + self.module_name.into_inner(), + self.major, + self.minor, + self.patch, + ) + .map_err(|_| ()) + } +} + +impl PalletInfo { + pub fn new( + index: u32, + name: Vec, + module_name: Vec, + major: u32, + minor: u32, + patch: u32, + ) -> result::Result { + let name = BoundedVec::try_from(name).map_err(|_| Error::Overflow)?; + let module_name = BoundedVec::try_from(module_name).map_err(|_| Error::Overflow)?; + + Ok(Self { index, name, module_name, major, minor, patch }) + } +} + +/// Response data to a query. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub enum Response { + /// No response. Serves as a neutral default. + Null, + /// Some assets. + Assets(Assets), + /// The outcome of an XCM instruction. + ExecutionResult(Option<(u32, Error)>), + /// An XCM version. + Version(super::Version), + /// The index, instance name, pallet name and version of some pallets. + PalletsInfo(BoundedVec), + /// The status of a dispatch attempt using `Transact`. + DispatchResult(MaybeErrorCode), +} + +impl Default for Response { + fn default() -> Self { + Self::Null + } +} + +impl TryFrom for Response { + type Error = (); + + fn try_from(old: OldResponse) -> result::Result { + use OldResponse::*; + Ok(match old { + Null => Self::Null, + Assets(assets) => Self::Assets(assets.try_into()?), + ExecutionResult(result) => + Self::ExecutionResult(result.map(|(num, old_error)| (num, old_error.into()))), + Version(version) => Self::Version(version), + PalletsInfo(pallet_info) => { + let inner = pallet_info + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + Self::PalletsInfo( + BoundedVec::::try_from(inner).map_err(|_| ())?, + ) + }, + DispatchResult(maybe_error) => Self::DispatchResult(maybe_error), + }) + } +} + +/// Information regarding the composition of a query response. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] +pub struct QueryResponseInfo { + /// The destination to which the query response message should be send. + pub destination: Location, + /// The `query_id` field of the `QueryResponse` message. + #[codec(compact)] + pub query_id: QueryId, + /// The `max_weight` field of the `QueryResponse` message. + pub max_weight: Weight, +} + +impl TryFrom for QueryResponseInfo { + type Error = (); + + fn try_from(old: OldQueryResponseInfo) -> result::Result { + Ok(Self { + destination: old.destination.try_into()?, + query_id: old.query_id, + max_weight: old.max_weight, + }) + } +} + +/// Contextual data pertaining to a specific list of XCM instructions. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub struct XcmContext { + /// The current value of the Origin register of the `XCVM`. + pub origin: Option, + /// The identity of the XCM; this may be a hash of its versioned encoding but could also be + /// a high-level identity set by an appropriate barrier. + pub message_id: XcmHash, + /// The current value of the Topic register of the `XCVM`. + pub topic: Option<[u8; 32]>, +} + +impl XcmContext { + /// Constructor which sets the message ID to the supplied parameter and leaves the origin and + /// topic unset. + pub fn with_message_id(message_id: XcmHash) -> XcmContext { + XcmContext { origin: None, message_id, topic: None } + } +} + +/// Cross-Consensus Message: A message from one consensus system to another. +/// +/// Consensus systems that may send and receive messages include blockchains and smart contracts. +/// +/// All messages are delivered from a known *origin*, expressed as a `Location`. +/// +/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the +/// outer XCM format, known as `VersionedXcm`. +#[derive( + Derivative, + Encode, + Decode, + TypeInfo, + xcm_procedural::XcmWeightInfoTrait, + xcm_procedural::Builder, +)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +#[scale_info(bounds(), skip_type_params(Call))] +pub enum Instruction { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the Holding + /// Register. + /// + /// - `assets`: The asset(s) to be withdrawn into holding. + /// + /// Kind: *Command*. + /// + /// Errors: + #[builder(loads_holding)] + WithdrawAsset(Assets), + + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` + /// system and equivalent derivatives should be placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they + /// may later be withdrawn should this system send a corresponding message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[builder(loads_holding)] + ReserveAssetDeposited(Assets), + + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should + /// be created and placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into the Holding Register. + /// + /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` + /// prior as a consequence of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[builder(loads_holding)] + ReceiveTeleportedAsset(Assets), + + /// Respond with information that the local system is expecting. + /// + /// - `query_id`: The identifier of the query that resulted in this message being sent. + /// - `response`: The message content. + /// - `max_weight`: The maximum weight that handling this response should take. + /// - `querier`: The location responsible for the initiation of the response, if there is one. + /// In general this will tend to be the same location as the receiver of this message. NOTE: + /// As usual, this is interpreted from the perspective of the receiving consensus system. + /// + /// Safety: Since this is information only, there are no immediate concerns. However, it should + /// be remembered that even if the Origin behaves reasonably, it can always be asked to make + /// a response to a third-party chain who may or may not be expecting the response. Therefore + /// the `querier` should be checked to match the expected value. + /// + /// Kind: *Information*. + /// + /// Errors: + QueryResponse { + #[codec(compact)] + query_id: QueryId, + response: Response, + max_weight: Weight, + querier: Option, + }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `beneficiary`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `beneficiary`: The new owner for the assets. + /// + /// Safety: No concerns. + /// + /// Kind: *Command*. + /// + /// Errors: + TransferAsset { assets: Assets, beneficiary: Location }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `dest` within this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given + /// `xcm`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The instructions that should follow the `ReserveAssetDeposited` instruction, which + /// is sent onwards to `dest`. + /// + /// Safety: No concerns. + /// + /// Kind: *Command*. + /// + /// Errors: + TransferReserveAsset { assets: Assets, dest: Location, xcm: Xcm<()> }, + + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed + /// by the kind of origin `origin_kind`. + /// + /// The Transact Status Register is set according to the result of dispatching the call. + /// + /// - `origin_kind`: The means of expressing the message origin as a dispatch origin. + /// - `require_weight_at_most`: The weight of `call`; this should be at least the chain's + /// calculated weight and will be used in the weight determination arithmetic. + /// - `call`: The encoded transaction to be applied. + /// + /// Safety: No concerns. + /// + /// Kind: *Command*. + /// + /// Errors: + Transact { origin_kind: OriginKind, require_weight_at_most: Weight, call: DoubleEncoded }, + + /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by + /// the relay-chain to a para. + /// + /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel + /// opening. + /// - `max_message_size`: The maximum size of a message proposed by the sender. + /// - `max_capacity`: The maximum number of messages that can be queued in the channel. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + HrmpNewChannelOpenRequest { + #[codec(compact)] + sender: u32, + #[codec(compact)] + max_message_size: u32, + #[codec(compact)] + max_capacity: u32, + }, + + /// A message to notify about that a previously sent open channel request has been accepted by + /// the recipient. That means that the channel will be opened during the next relay-chain + /// session change. This message is meant to be sent by the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + HrmpChannelAccepted { + // NOTE: We keep this as a structured item to a) keep it consistent with the other Hrmp + // items; and b) because the field's meaning is not obvious/mentioned from the item name. + #[codec(compact)] + recipient: u32, + }, + + /// A message to notify that the other party in an open channel decided to close it. In + /// particular, `initiator` is going to close the channel opened from `sender` to the + /// `recipient`. The close will be enacted at the next relay-chain session change. This message + /// is meant to be sent by the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + HrmpChannelClosing { + #[codec(compact)] + initiator: u32, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, + }, + + /// Clear the origin. + /// + /// This may be used by the XCM author to ensure that later instructions cannot command the + /// authority of the origin (e.g. if they are being relayed from an untrusted source, as often + /// the case with `ReserveAssetDeposited`). + /// + /// Safety: No concerns. + /// + /// Kind: *Command*. + /// + /// Errors: + ClearOrigin, + + /// Mutate the origin to some interior location. + /// + /// Kind: *Command* + /// + /// Errors: + DescendOrigin(InteriorLocation), + + /// Immediately report the contents of the Error Register to the given destination via XCM. + /// + /// A `QueryResponse` message of type `ExecutionOutcome` is sent to the described destination. + /// + /// - `response_info`: Information for making the response. + /// + /// Kind: *Command* + /// + /// Errors: + ReportError(QueryResponseInfo), + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `beneficiary` within this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `beneficiary`: The new owner for the assets. + /// + /// Kind: *Command* + /// + /// Errors: + DepositAsset { assets: AssetFilter, beneficiary: Location }, + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `dest` within this consensus system (i.e. deposit them into its sovereign + /// account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The orders that should follow the `ReserveAssetDeposited` instruction which is + /// sent onwards to `dest`. + /// + /// Kind: *Command* + /// + /// Errors: + DepositReserveAsset { assets: AssetFilter, dest: Location, xcm: Xcm<()> }, + + /// Remove the asset(s) (`want`) from the Holding Register and replace them with alternative + /// assets. + /// + /// The minimum amount of assets to be received into the Holding Register for the order not to + /// fail may be stated. + /// + /// - `give`: The maximum amount of assets to remove from holding. + /// - `want`: The minimum amount of assets which `give` should be exchanged for. + /// - `maximal`: If `true`, then prefer to give as much as possible up to the limit of `give` + /// and receive accordingly more. If `false`, then prefer to give as little as possible in + /// order to receive as little as possible while receiving at least `want`. + /// + /// Kind: *Command* + /// + /// Errors: + ExchangeAsset { give: AssetFilter, want: Assets, maximal: bool }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a + /// reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The + /// sovereign account of this consensus system *on the reserve location* will have + /// appropriate assets withdrawn and `effects` will be executed on them. There will typically + /// be only one valid location on any given asset/chain combination. + /// - `xcm`: The instructions to execute on the assets once withdrawn *on the reserve + /// location*. + /// + /// Kind: *Command* + /// + /// Errors: + InitiateReserveWithdraw { assets: AssetFilter, reserve: Location, xcm: Xcm<()> }, + + /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message + /// to a `dest` location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: A valid location that respects teleports coming from this location. + /// - `xcm`: The instructions to execute on the assets once arrived *on the destination + /// location*. + /// + /// NOTE: The `dest` location *MUST* respect this origin as a valid teleportation origin for + /// all `assets`. If it does not, then the assets may be lost. + /// + /// Kind: *Command* + /// + /// Errors: + InitiateTeleport { assets: AssetFilter, dest: Location, xcm: Xcm<()> }, + + /// Report to a given destination the contents of the Holding Register. + /// + /// A `QueryResponse` message of type `Assets` is sent to the described destination. + /// + /// - `response_info`: Information for making the response. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back + /// will be, asset-wise, *the lesser of this value and the holding register*. No wildcards + /// will be used when reporting assets back. + /// + /// Kind: *Command* + /// + /// Errors: + ReportHolding { response_info: QueryResponseInfo, assets: AssetFilter }, + + /// Pay for the execution of some XCM `xcm` and `orders` with up to `weight` + /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. + /// - `weight_limit`: The maximum amount of weight to purchase; this must be at least the + /// expected maximum weight of the total XCM to be executed for the + /// `AllowTopLevelPaidExecutionFrom` barrier to allow the XCM be executed. + /// + /// Kind: *Command* + /// + /// Errors: + BuyExecution { fees: Asset, weight_limit: WeightLimit }, + + /// Refund any surplus weight previously bought with `BuyExecution`. + /// + /// Kind: *Command* + /// + /// Errors: None. + RefundSurplus, + + /// Set the Error Handler Register. This is code that should be called in the case of an error + /// happening. + /// + /// An error occurring within execution of this code will _NOT_ result in the error register + /// being set, nor will an error handler be called due to it. The error handler and appendix + /// may each still be set. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous handler and the new + /// handler, which can reasonably be negative, which would result in a surplus. + /// + /// Kind: *Command* + /// + /// Errors: None. + SetErrorHandler(Xcm), + + /// Set the Appendix Register. This is code that should be called after code execution + /// (including the error handler if any) is finished. This will be called regardless of whether + /// an error occurred. + /// + /// Any error occurring due to execution of this code will result in the error register being + /// set, and the error handler (if set) firing. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous appendix and the new + /// appendix, which can reasonably be negative, which would result in a surplus. + /// + /// Kind: *Command* + /// + /// Errors: None. + SetAppendix(Xcm), + + /// Clear the Error Register. + /// + /// Kind: *Command* + /// + /// Errors: None. + ClearError, + + /// Create some assets which are being held on behalf of the origin. + /// + /// - `assets`: The assets which are to be claimed. This must match exactly with the assets + /// claimable by the origin of the ticket. + /// - `ticket`: The ticket of the asset; this is an abstract identifier to help locate the + /// asset. + /// + /// Kind: *Command* + /// + /// Errors: + #[builder(loads_holding)] + ClaimAsset { assets: Assets, ticket: Location }, + + /// Always throws an error of type `Trap`. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `Trap`: All circumstances, whose inner value is the same as this item's inner value. + Trap(#[codec(compact)] u64), + + /// Ask the destination system to respond with the most recent version of XCM that they + /// support in a `QueryResponse` instruction. Any changes to this should also elicit similar + /// responses when they happen. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible* + SubscribeVersion { + #[codec(compact)] + query_id: QueryId, + max_response_weight: Weight, + }, + + /// Cancel the effect of a previous `SubscribeVersion` instruction. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible* + UnsubscribeVersion, + + /// Reduce Holding by up to the given assets. + /// + /// Holding is reduced by as much as possible up to the assets in the parameter. It is not an + /// error if the Holding does not contain the assets (to make this an error, use `ExpectAsset` + /// prior). + /// + /// Kind: *Command* + /// + /// Errors: *Infallible* + BurnAsset(Assets), + + /// Throw an error if Holding does not contain at least the given assets. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `ExpectationFalse`: If Holding Register does not contain the assets in the parameter. + ExpectAsset(Assets), + + /// Ensure that the Origin Register equals some given value and throw an error if not. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `ExpectationFalse`: If Origin Register is not equal to the parameter. + ExpectOrigin(Option), + + /// Ensure that the Error Register equals some given value and throw an error if not. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `ExpectationFalse`: If the value of the Error Register is not equal to the parameter. + ExpectError(Option<(u32, Error)>), + + /// Ensure that the Transact Status Register equals some given value and throw an error if + /// not. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `ExpectationFalse`: If the value of the Transact Status Register is not equal to the + /// parameter. + ExpectTransactStatus(MaybeErrorCode), + + /// Query the existence of a particular pallet type. + /// + /// - `module_name`: The module name of the pallet to query. + /// - `response_info`: Information for making the response. + /// + /// Sends a `QueryResponse` to Origin whose data field `PalletsInfo` containing the information + /// of all pallets on the local chain whose name is equal to `name`. This is empty in the case + /// that the local chain is not based on Substrate Frame. + /// + /// Safety: No concerns. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible*. + QueryPallet { module_name: Vec, response_info: QueryResponseInfo }, + + /// Ensure that a particular pallet with a particular version exists. + /// + /// - `index: Compact`: The index which identifies the pallet. An error if no pallet exists at + /// this index. + /// - `name: Vec`: Name which must be equal to the name of the pallet. + /// - `module_name: Vec`: Module name which must be equal to the name of the module in + /// which the pallet exists. + /// - `crate_major: Compact`: Version number which must be equal to the major version of the + /// crate which implements the pallet. + /// - `min_crate_minor: Compact`: Version number which must be at most the minor version of the + /// crate which implements the pallet. + /// + /// Safety: No concerns. + /// + /// Kind: *Command* + /// + /// Errors: + /// - `ExpectationFalse`: In case any of the expectations are broken. + ExpectPallet { + #[codec(compact)] + index: u32, + name: Vec, + module_name: Vec, + #[codec(compact)] + crate_major: u32, + #[codec(compact)] + min_crate_minor: u32, + }, + + /// Send a `QueryResponse` message containing the value of the Transact Status Register to some + /// destination. + /// + /// - `query_response_info`: The information needed for constructing and sending the + /// `QueryResponse` message. + /// + /// Safety: No concerns. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible*. + ReportTransactStatus(QueryResponseInfo), + + /// Set the Transact Status Register to its default, cleared, value. + /// + /// Safety: No concerns. + /// + /// Kind: *Command* + /// + /// Errors: *Infallible*. + ClearTransactStatus, + + /// Set the Origin Register to be some child of the Universal Ancestor. + /// + /// Safety: Should only be usable if the Origin is trusted to represent the Universal Ancestor + /// child in general. In general, no Origin should be able to represent the Universal Ancestor + /// child which is the root of the local consensus system since it would by extension + /// allow it to act as any location within the local consensus. + /// + /// The `Junction` parameter should generally be a `GlobalConsensus` variant since it is only + /// these which are children of the Universal Ancestor. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible*. + UniversalOrigin(Junction), + + /// Send a message on to Non-Local Consensus system. + /// + /// This will tend to utilize some extra-consensus mechanism, the obvious one being a bridge. + /// A fee may be charged; this may be determined based on the contents of `xcm`. It will be + /// taken from the Holding register. + /// + /// - `network`: The remote consensus system to which the message should be exported. + /// - `destination`: The location relative to the remote consensus system to which the message + /// should be sent on arrival. + /// - `xcm`: The message to be exported. + /// + /// As an example, to export a message for execution on Statemine (parachain #1000 in the + /// Kusama network), you would call with `network: NetworkId::Kusama` and + /// `destination: [Parachain(1000)].into()`. Alternatively, to export a message for execution + /// on Polkadot, you would call with `network: NetworkId:: Polkadot` and `destination: Here`. + /// + /// Kind: *Command* + /// + /// Errors: *Fallible*. + ExportMessage { network: NetworkId, destination: InteriorLocation, xcm: Xcm<()> }, + + /// Lock the locally held asset and prevent further transfer or withdrawal. + /// + /// This restriction may be removed by the `UnlockAsset` instruction being called with an + /// Origin of `unlocker` and a `target` equal to the current `Origin`. + /// + /// If the locking is successful, then a `NoteUnlockable` instruction is sent to `unlocker`. + /// + /// - `asset`: The asset(s) which should be locked. + /// - `unlocker`: The value which the Origin must be for a corresponding `UnlockAsset` + /// instruction to work. + /// + /// Kind: *Command*. + /// + /// Errors: + LockAsset { asset: Asset, unlocker: Location }, + + /// Remove the lock over `asset` on this chain and (if nothing else is preventing it) allow the + /// asset to be transferred. + /// + /// - `asset`: The asset to be unlocked. + /// - `target`: The owner of the asset on the local chain. + /// + /// Safety: No concerns. + /// + /// Kind: *Command*. + /// + /// Errors: + UnlockAsset { asset: Asset, target: Location }, + + /// Asset (`asset`) has been locked on the `origin` system and may not be transferred. It may + /// only be unlocked with the receipt of the `UnlockAsset` instruction from this chain. + /// + /// - `asset`: The asset(s) which are now unlockable from this origin. + /// - `owner`: The owner of the asset on the chain in which it was locked. This may be a + /// location specific to the origin network. + /// + /// Safety: `origin` must be trusted to have locked the corresponding `asset` + /// prior as a consequence of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + NoteUnlockable { asset: Asset, owner: Location }, + + /// Send an `UnlockAsset` instruction to the `locker` for the given `asset`. + /// + /// This may fail if the local system is making use of the fact that the asset is locked or, + /// of course, if there is no record that the asset actually is locked. + /// + /// - `asset`: The asset(s) to be unlocked. + /// - `locker`: The location from which a previous `NoteUnlockable` was sent and to which an + /// `UnlockAsset` should be sent. + /// + /// Kind: *Command*. + /// + /// Errors: + RequestUnlock { asset: Asset, locker: Location }, + + /// Sets the Fees Mode Register. + /// + /// - `jit_withdraw`: The fees mode item; if set to `true` then fees for any instructions are + /// withdrawn as needed using the same mechanism as `WithdrawAssets`. + /// + /// Kind: *Command*. + /// + /// Errors: + SetFeesMode { jit_withdraw: bool }, + + /// Set the Topic Register. + /// + /// The 32-byte array identifier in the parameter is not guaranteed to be + /// unique; if such a property is desired, it is up to the code author to + /// enforce uniqueness. + /// + /// Safety: No concerns. + /// + /// Kind: *Command* + /// + /// Errors: + SetTopic([u8; 32]), + + /// Clear the Topic Register. + /// + /// Kind: *Command* + /// + /// Errors: None. + ClearTopic, + + /// Alter the current Origin to another given origin. + /// + /// Kind: *Command* + /// + /// Errors: If the existing state would not allow such a change. + AliasOrigin(Location), + + /// A directive to indicate that the origin expects free execution of the message. + /// + /// At execution time, this instruction just does a check on the Origin register. + /// However, at the barrier stage messages starting with this instruction can be disregarded if + /// the origin is not acceptable for free execution or the `weight_limit` is `Limited` and + /// insufficient. + /// + /// Kind: *Indication* + /// + /// Errors: If the given origin is `Some` and not equal to the current Origin register. + UnpaidExecution { weight_limit: WeightLimit, check_origin: Option }, +} + +impl Xcm { + pub fn into(self) -> Xcm { + Xcm::from(self) + } + pub fn from(xcm: Xcm) -> Self { + Self(xcm.0.into_iter().map(Instruction::::from).collect()) + } +} + +impl Instruction { + pub fn into(self) -> Instruction { + Instruction::from(self) + } + pub fn from(xcm: Instruction) -> Self { + use Instruction::*; + match xcm { + WithdrawAsset(assets) => WithdrawAsset(assets), + ReserveAssetDeposited(assets) => ReserveAssetDeposited(assets), + ReceiveTeleportedAsset(assets) => ReceiveTeleportedAsset(assets), + QueryResponse { query_id, response, max_weight, querier } => + QueryResponse { query_id, response, max_weight, querier }, + TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, + TransferReserveAsset { assets, dest, xcm } => + TransferReserveAsset { assets, dest, xcm }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_kind, require_weight_at_most, call } => + Transact { origin_kind, require_weight_at_most, call: call.into() }, + ReportError(response_info) => ReportError(response_info), + DepositAsset { assets, beneficiary } => DepositAsset { assets, beneficiary }, + DepositReserveAsset { assets, dest, xcm } => DepositReserveAsset { assets, dest, xcm }, + ExchangeAsset { give, want, maximal } => ExchangeAsset { give, want, maximal }, + InitiateReserveWithdraw { assets, reserve, xcm } => + InitiateReserveWithdraw { assets, reserve, xcm }, + InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, xcm }, + ReportHolding { response_info, assets } => ReportHolding { response_info, assets }, + BuyExecution { fees, weight_limit } => BuyExecution { fees, weight_limit }, + ClearOrigin => ClearOrigin, + DescendOrigin(who) => DescendOrigin(who), + RefundSurplus => RefundSurplus, + SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), + SetAppendix(xcm) => SetAppendix(xcm.into()), + ClearError => ClearError, + ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket }, + Trap(code) => Trap(code), + SubscribeVersion { query_id, max_response_weight } => + SubscribeVersion { query_id, max_response_weight }, + UnsubscribeVersion => UnsubscribeVersion, + BurnAsset(assets) => BurnAsset(assets), + ExpectAsset(assets) => ExpectAsset(assets), + ExpectOrigin(origin) => ExpectOrigin(origin), + ExpectError(error) => ExpectError(error), + ExpectTransactStatus(transact_status) => ExpectTransactStatus(transact_status), + QueryPallet { module_name, response_info } => + QueryPallet { module_name, response_info }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + ExpectPallet { index, name, module_name, crate_major, min_crate_minor }, + ReportTransactStatus(response_info) => ReportTransactStatus(response_info), + ClearTransactStatus => ClearTransactStatus, + UniversalOrigin(j) => UniversalOrigin(j), + ExportMessage { network, destination, xcm } => + ExportMessage { network, destination, xcm }, + LockAsset { asset, unlocker } => LockAsset { asset, unlocker }, + UnlockAsset { asset, target } => UnlockAsset { asset, target }, + NoteUnlockable { asset, owner } => NoteUnlockable { asset, owner }, + RequestUnlock { asset, locker } => RequestUnlock { asset, locker }, + SetFeesMode { jit_withdraw } => SetFeesMode { jit_withdraw }, + SetTopic(topic) => SetTopic(topic), + ClearTopic => ClearTopic, + AliasOrigin(location) => AliasOrigin(location), + UnpaidExecution { weight_limit, check_origin } => + UnpaidExecution { weight_limit, check_origin }, + } + } +} + +// TODO: Automate Generation +impl> GetWeight for Instruction { + fn weight(&self) -> Weight { + use Instruction::*; + match self { + WithdrawAsset(assets) => W::withdraw_asset(assets), + ReserveAssetDeposited(assets) => W::reserve_asset_deposited(assets), + ReceiveTeleportedAsset(assets) => W::receive_teleported_asset(assets), + QueryResponse { query_id, response, max_weight, querier } => + W::query_response(query_id, response, max_weight, querier), + TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary), + TransferReserveAsset { assets, dest, xcm } => + W::transfer_reserve_asset(&assets, dest, xcm), + Transact { origin_kind, require_weight_at_most, call } => + W::transact(origin_kind, require_weight_at_most, call), + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity), + HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient), + HrmpChannelClosing { initiator, sender, recipient } => + W::hrmp_channel_closing(initiator, sender, recipient), + ClearOrigin => W::clear_origin(), + DescendOrigin(who) => W::descend_origin(who), + ReportError(response_info) => W::report_error(&response_info), + DepositAsset { assets, beneficiary } => W::deposit_asset(assets, beneficiary), + DepositReserveAsset { assets, dest, xcm } => + W::deposit_reserve_asset(assets, dest, xcm), + ExchangeAsset { give, want, maximal } => W::exchange_asset(give, want, maximal), + InitiateReserveWithdraw { assets, reserve, xcm } => + W::initiate_reserve_withdraw(assets, reserve, xcm), + InitiateTeleport { assets, dest, xcm } => W::initiate_teleport(assets, dest, xcm), + ReportHolding { response_info, assets } => W::report_holding(&response_info, &assets), + BuyExecution { fees, weight_limit } => W::buy_execution(fees, weight_limit), + RefundSurplus => W::refund_surplus(), + SetErrorHandler(xcm) => W::set_error_handler(xcm), + SetAppendix(xcm) => W::set_appendix(xcm), + ClearError => W::clear_error(), + ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket), + Trap(code) => W::trap(code), + SubscribeVersion { query_id, max_response_weight } => + W::subscribe_version(query_id, max_response_weight), + UnsubscribeVersion => W::unsubscribe_version(), + BurnAsset(assets) => W::burn_asset(assets), + ExpectAsset(assets) => W::expect_asset(assets), + ExpectOrigin(origin) => W::expect_origin(origin), + ExpectError(error) => W::expect_error(error), + ExpectTransactStatus(transact_status) => W::expect_transact_status(transact_status), + QueryPallet { module_name, response_info } => + W::query_pallet(module_name, response_info), + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + W::expect_pallet(index, name, module_name, crate_major, min_crate_minor), + ReportTransactStatus(response_info) => W::report_transact_status(response_info), + ClearTransactStatus => W::clear_transact_status(), + UniversalOrigin(j) => W::universal_origin(j), + ExportMessage { network, destination, xcm } => + W::export_message(network, destination, xcm), + LockAsset { asset, unlocker } => W::lock_asset(asset, unlocker), + UnlockAsset { asset, target } => W::unlock_asset(asset, target), + NoteUnlockable { asset, owner } => W::note_unlockable(asset, owner), + RequestUnlock { asset, locker } => W::request_unlock(asset, locker), + SetFeesMode { jit_withdraw } => W::set_fees_mode(jit_withdraw), + SetTopic(topic) => W::set_topic(topic), + ClearTopic => W::clear_topic(), + AliasOrigin(location) => W::alias_origin(location), + UnpaidExecution { weight_limit, check_origin } => + W::unpaid_execution(weight_limit, check_origin), + } + } +} + +pub mod opaque { + /// The basic concrete type of `Xcm`, which doesn't make any assumptions about the + /// format of a call other than it is pre-encoded. + pub type Xcm = super::Xcm<()>; + + /// The basic concrete type of `Instruction`, which doesn't make any assumptions about the + /// format of a call other than it is pre-encoded. + pub type Instruction = super::Instruction<()>; +} + +// Convert from a v3 XCM to a v4 XCM +impl TryFrom> for Xcm { + type Error = (); + fn try_from(old_xcm: OldXcm) -> result::Result { + Ok(Xcm(old_xcm.0.into_iter().map(TryInto::try_into).collect::>()?)) + } +} + +// Convert from a v3 instruction to a v4 instruction +impl TryFrom> for Instruction { + type Error = (); + fn try_from(old_instruction: OldInstruction) -> result::Result { + use OldInstruction::*; + Ok(match old_instruction { + WithdrawAsset(assets) => Self::WithdrawAsset(assets.try_into()?), + ReserveAssetDeposited(assets) => Self::ReserveAssetDeposited(assets.try_into()?), + ReceiveTeleportedAsset(assets) => Self::ReceiveTeleportedAsset(assets.try_into()?), + QueryResponse { query_id, response, max_weight, querier: Some(querier) } => + Self::QueryResponse { + query_id, + querier: querier.try_into()?, + response: response.try_into()?, + max_weight, + }, + QueryResponse { query_id, response, max_weight, querier: None } => + Self::QueryResponse { + query_id, + querier: None, + response: response.try_into()?, + max_weight, + }, + TransferAsset { assets, beneficiary } => Self::TransferAsset { + assets: assets.try_into()?, + beneficiary: beneficiary.try_into()?, + }, + TransferReserveAsset { assets, dest, xcm } => Self::TransferReserveAsset { + assets: assets.try_into()?, + dest: dest.try_into()?, + xcm: xcm.try_into()?, + }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Self::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, + HrmpChannelClosing { initiator, sender, recipient } => + Self::HrmpChannelClosing { initiator, sender, recipient }, + Transact { origin_kind, require_weight_at_most, call } => + Self::Transact { origin_kind, require_weight_at_most, call: call.into() }, + ReportError(response_info) => Self::ReportError(QueryResponseInfo { + query_id: response_info.query_id, + destination: response_info.destination.try_into().map_err(|_| ())?, + max_weight: response_info.max_weight, + }), + DepositAsset { assets, beneficiary } => { + let beneficiary = beneficiary.try_into()?; + let assets = assets.try_into()?; + Self::DepositAsset { assets, beneficiary } + }, + DepositReserveAsset { assets, dest, xcm } => { + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + let assets = assets.try_into()?; + Self::DepositReserveAsset { assets, dest, xcm } + }, + ExchangeAsset { give, want, maximal } => { + let give = give.try_into()?; + let want = want.try_into()?; + Self::ExchangeAsset { give, want, maximal } + }, + InitiateReserveWithdraw { assets, reserve, xcm } => { + let assets = assets.try_into()?; + let reserve = reserve.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateReserveWithdraw { assets, reserve, xcm } + }, + InitiateTeleport { assets, dest, xcm } => { + let assets = assets.try_into()?; + let dest = dest.try_into()?; + let xcm = xcm.try_into()?; + Self::InitiateTeleport { assets, dest, xcm } + }, + ReportHolding { response_info, assets } => { + let response_info = QueryResponseInfo { + destination: response_info.destination.try_into().map_err(|_| ())?, + query_id: response_info.query_id, + max_weight: response_info.max_weight, + }; + Self::ReportHolding { response_info, assets: assets.try_into()? } + }, + BuyExecution { fees, weight_limit } => { + let fees = fees.try_into()?; + let weight_limit = weight_limit.into(); + Self::BuyExecution { fees, weight_limit } + }, + ClearOrigin => Self::ClearOrigin, + DescendOrigin(who) => Self::DescendOrigin(who.try_into()?), + RefundSurplus => Self::RefundSurplus, + SetErrorHandler(xcm) => Self::SetErrorHandler(xcm.try_into()?), + SetAppendix(xcm) => Self::SetAppendix(xcm.try_into()?), + ClearError => Self::ClearError, + ClaimAsset { assets, ticket } => { + let assets = assets.try_into()?; + let ticket = ticket.try_into()?; + Self::ClaimAsset { assets, ticket } + }, + Trap(code) => Self::Trap(code), + SubscribeVersion { query_id, max_response_weight } => + Self::SubscribeVersion { query_id, max_response_weight }, + UnsubscribeVersion => Self::UnsubscribeVersion, + BurnAsset(assets) => Self::BurnAsset(assets.try_into()?), + ExpectAsset(assets) => Self::ExpectAsset(assets.try_into()?), + ExpectOrigin(maybe_location) => Self::ExpectOrigin( + maybe_location.map(|location| location.try_into()).transpose().map_err(|_| ())?, + ), + ExpectError(maybe_error) => Self::ExpectError( + maybe_error.map(|error| error.try_into()).transpose().map_err(|_| ())?, + ), + ExpectTransactStatus(maybe_error_code) => Self::ExpectTransactStatus(maybe_error_code), + QueryPallet { module_name, response_info } => Self::QueryPallet { + module_name, + response_info: response_info.try_into().map_err(|_| ())?, + }, + ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => + Self::ExpectPallet { index, name, module_name, crate_major, min_crate_minor }, + ReportTransactStatus(response_info) => + Self::ReportTransactStatus(response_info.try_into().map_err(|_| ())?), + ClearTransactStatus => Self::ClearTransactStatus, + UniversalOrigin(junction) => + Self::UniversalOrigin(junction.try_into().map_err(|_| ())?), + ExportMessage { network, destination, xcm } => Self::ExportMessage { + network: network.into(), + destination: destination.try_into().map_err(|_| ())?, + xcm: xcm.try_into().map_err(|_| ())?, + }, + LockAsset { asset, unlocker } => Self::LockAsset { + asset: asset.try_into().map_err(|_| ())?, + unlocker: unlocker.try_into().map_err(|_| ())?, + }, + UnlockAsset { asset, target } => Self::UnlockAsset { + asset: asset.try_into().map_err(|_| ())?, + target: target.try_into().map_err(|_| ())?, + }, + NoteUnlockable { asset, owner } => Self::NoteUnlockable { + asset: asset.try_into().map_err(|_| ())?, + owner: owner.try_into().map_err(|_| ())?, + }, + RequestUnlock { asset, locker } => Self::RequestUnlock { + asset: asset.try_into().map_err(|_| ())?, + locker: locker.try_into().map_err(|_| ())?, + }, + SetFeesMode { jit_withdraw } => Self::SetFeesMode { jit_withdraw }, + SetTopic(topic) => Self::SetTopic(topic), + ClearTopic => Self::ClearTopic, + AliasOrigin(location) => Self::AliasOrigin(location.try_into().map_err(|_| ())?), + UnpaidExecution { weight_limit, check_origin } => Self::UnpaidExecution { + weight_limit, + check_origin: check_origin + .map(|location| location.try_into()) + .transpose() + .map_err(|_| ())?, + }, + }) + } +} + +#[cfg(test)] +mod tests { + use super::{prelude::*, *}; + use crate::v3::{ + Junctions::Here as OldHere, MultiAssetFilter as OldMultiAssetFilter, + WildMultiAsset as OldWildMultiAsset, + }; + + #[test] + fn basic_roundtrip_works() { + let xcm = Xcm::<()>(vec![TransferAsset { + assets: (Here, 1u128).into(), + beneficiary: Here.into(), + }]); + let old_xcm = OldXcm::<()>(vec![OldInstruction::TransferAsset { + assets: (OldHere, 1u128).into(), + beneficiary: OldHere.into(), + }]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn teleport_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReceiveTeleportedAsset((Here, 1u128).into()), + ClearOrigin, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm: OldXcm<()> = OldXcm::<()>(vec![ + OldInstruction::ReceiveTeleportedAsset((OldHere, 1u128).into()), + OldInstruction::ClearOrigin, + OldInstruction::DepositAsset { + assets: crate::v3::MultiAssetFilter::Wild(crate::v3::WildMultiAsset::AllCounted(1)), + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn reserve_deposit_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReserveAssetDeposited((Here, 1u128).into()), + ClearOrigin, + BuyExecution { + fees: (Here, 1u128).into(), + weight_limit: Some(Weight::from_parts(1, 1)).into(), + }, + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::ReserveAssetDeposited((OldHere, 1u128).into()), + OldInstruction::ClearOrigin, + OldInstruction::BuyExecution { + fees: (OldHere, 1u128).into(), + weight_limit: WeightLimit::Limited(Weight::from_parts(1, 1)), + }, + OldInstruction::DepositAsset { + assets: crate::v3::MultiAssetFilter::Wild(crate::v3::WildMultiAsset::AllCounted(1)), + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn deposit_asset_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Here.into() }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::DepositAsset { + assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::AllCounted(1)), + beneficiary: OldHere.into(), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn deposit_reserve_asset_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + DepositReserveAsset { + assets: Wild(AllCounted(1)), + dest: Here.into(), + xcm: Xcm::<()>(vec![]), + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::DepositReserveAsset { + assets: OldMultiAssetFilter::Wild(OldWildMultiAsset::AllCounted(1)), + dest: OldHere.into(), + xcm: OldXcm::<()>(vec![]), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn decoding_respects_limit() { + let max_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize]); + let encoded = max_xcm.encode(); + assert!(Xcm::<()>::decode(&mut &encoded[..]).is_ok()); + + let big_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize + 1]); + let encoded = big_xcm.encode(); + assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err()); + + let nested_xcm = Xcm::<()>(vec![ + DepositReserveAsset { + assets: All.into(), + dest: Here.into(), + xcm: max_xcm, + }; + (MAX_INSTRUCTIONS_TO_DECODE / 2) as usize + ]); + let encoded = nested_xcm.encode(); + assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err()); + + let even_more_nested_xcm = Xcm::<()>(vec![SetAppendix(nested_xcm); 64]); + let encoded = even_more_nested_xcm.encode(); + assert_eq!(encoded.len(), 342530); + // This should not decode since the limit is 100 + assert_eq!(MAX_INSTRUCTIONS_TO_DECODE, 100, "precondition"); + assert!(Xcm::<()>::decode(&mut &encoded[..]).is_err()); + } +} diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs new file mode 100644 index 0000000000000000000000000000000000000000..f6136c76d808f1708bcf5c6c9f8db2e49b15b232 --- /dev/null +++ b/polkadot/xcm/src/v4/traits.rs @@ -0,0 +1,312 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format data structures. + +pub use crate::v3::{Error, Result, SendError, XcmHash}; +use core::result; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; + +pub use sp_weights::Weight; + +use super::*; + +/// Outcome of an XCM execution. +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Outcome { + /// Execution completed successfully; given weight was used. + Complete { used: Weight }, + /// Execution started, but did not complete successfully due to the given error; given weight + /// was used. + Incomplete { used: Weight, error: Error }, + /// Execution did not start due to the given error. + Error { error: Error }, +} + +impl Outcome { + pub fn ensure_complete(self) -> Result { + match self { + Outcome::Complete { .. } => Ok(()), + Outcome::Incomplete { error, .. } => Err(error), + Outcome::Error { error, .. } => Err(error), + } + } + pub fn ensure_execution(self) -> result::Result { + match self { + Outcome::Complete { used, .. } => Ok(used), + Outcome::Incomplete { used, .. } => Ok(used), + Outcome::Error { error, .. } => Err(error), + } + } + /// How much weight was used by the XCM execution attempt. + pub fn weight_used(&self) -> Weight { + match self { + Outcome::Complete { used, .. } => *used, + Outcome::Incomplete { used, .. } => *used, + Outcome::Error { .. } => Weight::zero(), + } + } +} + +impl From for Outcome { + fn from(error: Error) -> Self { + Self::Error { error } + } +} + +pub trait PreparedMessage { + fn weight_of(&self) -> Weight; +} + +/// Type of XCM message executor. +pub trait ExecuteXcm { + type Prepared: PreparedMessage; + fn prepare(message: Xcm) -> result::Result>; + fn execute( + origin: impl Into, + pre: Self::Prepared, + id: &mut XcmHash, + weight_credit: Weight, + ) -> Outcome; + fn prepare_and_execute( + origin: impl Into, + message: Xcm, + id: &mut XcmHash, + weight_limit: Weight, + weight_credit: Weight, + ) -> Outcome { + let pre = match Self::prepare(message) { + Ok(x) => x, + Err(_) => return Outcome::Error { error: Error::WeightNotComputable }, + }; + let xcm_weight = pre.weight_of(); + if xcm_weight.any_gt(weight_limit) { + return Outcome::Error { error: Error::WeightLimitReached(xcm_weight) } + } + Self::execute(origin, pre, id, weight_credit) + } + + /// Deduct some `fees` to the sovereign account of the given `location` and place them as per + /// the convention for fees. + fn charge_fees(location: impl Into, fees: Assets) -> Result; +} + +pub enum Weightless {} +impl PreparedMessage for Weightless { + fn weight_of(&self) -> Weight { + unreachable!() + } +} + +impl ExecuteXcm for () { + type Prepared = Weightless; + fn prepare(message: Xcm) -> result::Result> { + Err(message) + } + fn execute(_: impl Into, _: Self::Prepared, _: &mut XcmHash, _: Weight) -> Outcome { + unreachable!() + } + fn charge_fees(_location: impl Into, _fees: Assets) -> Result { + Err(Error::Unimplemented) + } +} + +pub trait Reanchorable: Sized { + /// Type to return in case of an error. + type Error: Debug; + + /// Mutate `self` so that it represents the same location from the point of view of `target`. + /// The context of `self` is provided as `context`. + /// + /// Does not modify `self` in case of overflow. + fn reanchor( + &mut self, + target: &Location, + context: &InteriorLocation, + ) -> core::result::Result<(), ()>; + + /// Consume `self` and return a new value representing the same location from the point of view + /// of `target`. The context of `self` is provided as `context`. + /// + /// Returns the original `self` in case of overflow. + fn reanchored( + self, + target: &Location, + context: &InteriorLocation, + ) -> core::result::Result; +} + +/// Result value when attempting to send an XCM message. +pub type SendResult = result::Result<(T, Assets), SendError>; + +/// Utility for sending an XCM message to a given location. +/// +/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each +/// router might return `NotApplicable` to pass the execution to the next sender item. Note that +/// each `NotApplicable` might alter the destination and the XCM message for to the next router. +/// +/// # Example +/// ```rust +/// # use parity_scale_codec::Encode; +/// # use staging_xcm::v4::{prelude::*, Weight}; +/// # use staging_xcm::VersionedXcm; +/// # use std::convert::Infallible; +/// +/// /// A sender that only passes the message through and does nothing. +/// struct Sender1; +/// impl SendXcm for Sender1 { +/// type Ticket = Infallible; +/// fn validate(_: &mut Option, _: &mut Option>) -> SendResult { +/// Err(SendError::NotApplicable) +/// } +/// fn deliver(_: Infallible) -> Result { +/// unreachable!() +/// } +/// } +/// +/// /// A sender that accepts a message that has two junctions, otherwise stops the routing. +/// struct Sender2; +/// impl SendXcm for Sender2 { +/// type Ticket = (); +/// fn validate(destination: &mut Option, message: &mut Option>) -> SendResult<()> { +/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() { +/// (0, [j1, j2]) => Ok(((), Assets::new())), +/// _ => Err(SendError::Unroutable), +/// } +/// } +/// fn deliver(_: ()) -> Result { +/// Ok([0; 32]) +/// } +/// } +/// +/// /// A sender that accepts a message from a parent, passing through otherwise. +/// struct Sender3; +/// impl SendXcm for Sender3 { +/// type Ticket = (); +/// fn validate(destination: &mut Option, message: &mut Option>) -> SendResult<()> { +/// match destination.as_ref().ok_or(SendError::MissingArgument)?.unpack() { +/// (1, []) => Ok(((), Assets::new())), +/// _ => Err(SendError::NotApplicable), +/// } +/// } +/// fn deliver(_: ()) -> Result { +/// Ok([0; 32]) +/// } +/// } +/// +/// // A call to send via XCM. We don't really care about this. +/// # fn main() { +/// let call: Vec = ().encode(); +/// let message = Xcm(vec![Instruction::Transact { +/// origin_kind: OriginKind::Superuser, +/// require_weight_at_most: Weight::zero(), +/// call: call.into(), +/// }]); +/// let message_hash = message.using_encoded(sp_io::hashing::blake2_256); +/// +/// // Sender2 will block this. +/// assert!(send_xcm::<(Sender1, Sender2, Sender3)>(Parent.into(), message.clone()).is_err()); +/// +/// // Sender3 will catch this. +/// assert!(send_xcm::<(Sender1, Sender3)>(Parent.into(), message.clone()).is_ok()); +/// # } +/// ``` +pub trait SendXcm { + /// Intermediate value which connects the two phases of the send operation. + type Ticket; + + /// Check whether the given `_message` is deliverable to the given `_destination` and if so + /// determine the cost which will be paid by this chain to do so, returning a `Validated` token + /// which can be used to enact delivery. + /// + /// The `destination` and `message` must be `Some` (or else an error will be returned) and they + /// may only be consumed if the `Err` is not `NotApplicable`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, + /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// implementation to exit early without trying other type fields. + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult; + + /// Actually carry out the delivery operation for a previously validated message sending. + fn deliver(ticket: Self::Ticket) -> result::Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl SendXcm for Tuple { + for_tuples! { type Ticket = (#( Option ),* ); } + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let mut maybe_cost: Option = None; + let one_ticket: Self::Ticket = (for_tuples! { #( + if maybe_cost.is_some() { + None + } else { + match Tuple::validate(destination, message) { + Err(SendError::NotApplicable) => None, + Err(e) => { return Err(e) }, + Ok((v, c)) => { + maybe_cost = Some(c); + Some(v) + }, + } + } + ),* }); + if let Some(cost) = maybe_cost { + Ok((one_ticket, cost)) + } else { + Err(SendError::NotApplicable) + } + } + + fn deliver(one_ticket: Self::Ticket) -> result::Result { + for_tuples!( #( + if let Some(validated) = one_ticket.Tuple { + return Tuple::deliver(validated); + } + )* ); + Err(SendError::Unroutable) + } +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +pub fn validate_send(dest: Location, msg: Xcm<()>) -> SendResult { + T::validate(&mut Some(dest), &mut Some(msg)) +} + +/// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps +/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// +/// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message +/// could not be sent. +/// +/// Generally you'll want to validate and get the price first to ensure that the sender can pay it +/// before actually doing the delivery. +pub fn send_xcm( + dest: Location, + msg: Xcm<()>, +) -> result::Result<(XcmHash, Assets), SendError> { + let (ticket, price) = T::validate(&mut Some(dest), &mut Some(msg))?; + let hash = T::deliver(ticket)?; + Ok((hash, price)) +} diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 7d6c40eb8417e55c3bf04b8cb8c85520312386ee..10726b0f511909c5cc4509746c09b731d32b2faf 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -4,7 +4,10 @@ description = "Tools & types for building with XCM and its executor." authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "7.0.0" + +[lints] +workspace = true [dependencies] impl-trait-for-tuples = "0.2.1" @@ -20,7 +23,7 @@ sp-weights = { path = "../../../substrate/primitives/weights", default-features frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { path = "../../parachain", default-features = false } @@ -37,7 +40,7 @@ assert_matches = "1.5.0" polkadot-test-runtime = { path = "../../runtime/test-runtime" } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/polkadot/xcm/xcm-builder/src/asset_conversion.rs b/polkadot/xcm/xcm-builder/src/asset_conversion.rs index 5b76ed764a8128c80afcf7369e96628553a2b570..e38af149be541f3f85ed47109e979351d6569f0c 100644 --- a/polkadot/xcm/xcm-builder/src/asset_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/asset_conversion.rs @@ -23,39 +23,42 @@ use xcm::latest::prelude::*; use xcm_executor::traits::{Error as MatchError, MatchesFungibles, MatchesNonFungibles}; /// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be -/// `TryFrom/TryInto`) into a `GeneralIndex` junction, prefixed by some `MultiLocation` value. -/// The `MultiLocation` value will typically be a `PalletInstance` junction. -pub struct AsPrefixedGeneralIndex( - PhantomData<(Prefix, AssetId, ConvertAssetId)>, +/// `TryFrom/TryInto`) into a `GeneralIndex` junction, prefixed by some `Location` value. +/// The `Location` value will typically be a `PalletInstance` junction. +pub struct AsPrefixedGeneralIndex( + PhantomData<(Prefix, AssetId, ConvertAssetId, L)>, ); impl< - Prefix: Get, + Prefix: Get, AssetId: Clone, ConvertAssetId: MaybeEquivalence, - > MaybeEquivalence - for AsPrefixedGeneralIndex + L: TryInto + TryFrom + Clone, + > MaybeEquivalence for AsPrefixedGeneralIndex { - fn convert(id: &MultiLocation) -> Option { + fn convert(id: &L) -> Option { let prefix = Prefix::get(); - if prefix.parent_count() != id.parent_count() || - prefix + let latest_prefix: Location = prefix.try_into().ok()?; + let latest_id: Location = (*id).clone().try_into().ok()?; + if latest_prefix.parent_count() != latest_id.parent_count() || + latest_prefix .interior() .iter() .enumerate() - .any(|(index, junction)| id.interior().at(index) != Some(junction)) + .any(|(index, junction)| latest_id.interior().at(index) != Some(junction)) { return None } - match id.interior().at(prefix.interior().len()) { - Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert(id), + match latest_id.interior().at(latest_prefix.interior().len()) { + Some(Junction::GeneralIndex(id)) => ConvertAssetId::convert(&id), _ => None, } } - fn convert_back(what: &AssetId) -> Option { - let mut location = Prefix::get(); + fn convert_back(what: &AssetId) -> Option { + let location = Prefix::get(); + let mut latest_location: Location = location.try_into().ok()?; let id = ConvertAssetId::convert_back(what)?; - location.push_interior(Junction::GeneralIndex(id)).ok()?; - Some(location) + latest_location.push_interior(Junction::GeneralIndex(id)).ok()?; + latest_location.try_into().ok() } } @@ -65,14 +68,14 @@ pub struct ConvertedConcreteId( impl< AssetId: Clone, Balance: Clone, - ConvertAssetId: MaybeEquivalence, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, > MatchesFungibles for ConvertedConcreteId { - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> { let (amount, id) = match (&a.fun, &a.id) { - (Fungible(ref amount), Concrete(ref id)) => (amount, id), + (Fungible(ref amount), AssetId(ref id)) => (amount, id), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?; @@ -84,14 +87,14 @@ impl< impl< ClassId: Clone, InstanceId: Clone, - ConvertClassId: MaybeEquivalence, + ConvertClassId: MaybeEquivalence, ConvertInstanceId: MaybeEquivalence, > MatchesNonFungibles for ConvertedConcreteId { - fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { + fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { - (NonFungible(ref instance), Concrete(ref class)) => (instance, class), + (NonFungible(ref instance), AssetId(ref class)) => (instance, class), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?; @@ -101,68 +104,35 @@ impl< } } -pub struct ConvertedAbstractId( - PhantomData<(AssetId, Balance, ConvertAssetId, ConvertOther)>, -); -impl< - AssetId: Clone, - Balance: Clone, - ConvertAssetId: MaybeEquivalence<[u8; 32], AssetId>, - ConvertBalance: MaybeEquivalence, - > MatchesFungibles - for ConvertedAbstractId -{ - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { - let (amount, id) = match (&a.fun, &a.id) { - (Fungible(ref amount), Abstract(ref id)) => (amount, id), - _ => return Err(MatchError::AssetNotHandled), - }; - let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?; - let amount = - ConvertBalance::convert(amount).ok_or(MatchError::AmountToBalanceConversionFailed)?; - Ok((what, amount)) +#[deprecated = "Use `ConvertedConcreteId` instead"] +pub type ConvertedConcreteAssetId = ConvertedConcreteId; + +pub struct V4V3LocationConverter; +impl MaybeEquivalence for V4V3LocationConverter { + fn convert(old: &xcm::v4::Location) -> Option { + (*old).clone().try_into().ok() } -} -impl< - ClassId: Clone, - InstanceId: Clone, - ConvertClassId: MaybeEquivalence<[u8; 32], ClassId>, - ConvertInstanceId: MaybeEquivalence, - > MatchesNonFungibles - for ConvertedAbstractId -{ - fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { - let (instance, class) = match (&a.fun, &a.id) { - (NonFungible(ref instance), Abstract(ref class)) => (instance, class), - _ => return Err(MatchError::AssetNotHandled), - }; - let what = ConvertClassId::convert(class).ok_or(MatchError::AssetIdConversionFailed)?; - let instance = - ConvertInstanceId::convert(instance).ok_or(MatchError::InstanceConversionFailed)?; - Ok((what, instance)) + + fn convert_back(new: &xcm::v3::Location) -> Option { + (*new).try_into().ok() } } -#[deprecated = "Use `ConvertedConcreteId` instead"] -pub type ConvertedConcreteAssetId = ConvertedConcreteId; -#[deprecated = "Use `ConvertedAbstractId` instead"] -pub type ConvertedAbstractAssetId = ConvertedAbstractId; - pub struct MatchedConvertedConcreteId( PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>, ); impl< AssetId: Clone, Balance: Clone, - MatchAssetId: Contains, - ConvertAssetId: MaybeEquivalence, + MatchAssetId: Contains, + ConvertAssetId: MaybeEquivalence, ConvertBalance: MaybeEquivalence, > MatchesFungibles for MatchedConvertedConcreteId { - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), MatchError> { + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), MatchError> { let (amount, id) = match (&a.fun, &a.id) { - (Fungible(ref amount), Concrete(ref id)) if MatchAssetId::contains(id) => (amount, id), + (Fungible(ref amount), AssetId(ref id)) if MatchAssetId::contains(id) => (amount, id), _ => return Err(MatchError::AssetNotHandled), }; let what = ConvertAssetId::convert(id).ok_or(MatchError::AssetIdConversionFailed)?; @@ -174,15 +144,15 @@ impl< impl< ClassId: Clone, InstanceId: Clone, - MatchClassId: Contains, - ConvertClassId: MaybeEquivalence, + MatchClassId: Contains, + ConvertClassId: MaybeEquivalence, ConvertInstanceId: MaybeEquivalence, > MatchesNonFungibles for MatchedConvertedConcreteId { - fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(ClassId, InstanceId), MatchError> { + fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { - (NonFungible(ref instance), Concrete(ref class)) if MatchClassId::contains(class) => + (NonFungible(ref instance), AssetId(ref class)) if MatchClassId::contains(class) => (instance, class), _ => return Err(MatchError::AssetNotHandled), }; @@ -200,10 +170,10 @@ mod tests { use xcm_executor::traits::JustTry; struct OnlyParentZero; - impl Contains for OnlyParentZero { - fn contains(a: &MultiLocation) -> bool { + impl Contains for OnlyParentZero { + fn contains(a: &Location) -> bool { match a { - MultiLocation { parents: 0, .. } => true, + Location { parents: 0, .. } => true, _ => false, } } @@ -214,7 +184,7 @@ mod tests { type AssetIdForTrustBackedAssets = u32; type Balance = u128; frame_support::parameter_types! { - pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(50).into(); + pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into(); } // ConvertedConcreteId cfg @@ -231,13 +201,13 @@ mod tests { >; assert_eq!( TrustBackedAssetsPalletLocation::get(), - MultiLocation { parents: 0, interior: X1(PalletInstance(50)) } + Location { parents: 0, interior: [PalletInstance(50)].into() } ); // err - does not match assert_eq!( - Converter::matches_fungibles(&MultiAsset { - id: Concrete(MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_fungibles(&Asset { + id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])), fun: Fungible(12345), }), Err(MatchError::AssetNotHandled) @@ -245,10 +215,10 @@ mod tests { // err - matches, but convert fails assert_eq!( - Converter::matches_fungibles(&MultiAsset { - id: Concrete(MultiLocation::new( + Converter::matches_fungibles(&Asset { + id: AssetId(Location::new( 0, - X2(PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }) + [PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }] )), fun: Fungible(12345), }), @@ -257,8 +227,8 @@ mod tests { // err - matches, but NonFungible assert_eq!( - Converter::matches_fungibles(&MultiAsset { - id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_fungibles(&Asset { + id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])), fun: NonFungible(Index(54321)), }), Err(MatchError::AssetNotHandled) @@ -266,8 +236,8 @@ mod tests { // ok assert_eq!( - Converter::matches_fungibles(&MultiAsset { - id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_fungibles(&Asset { + id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])), fun: Fungible(12345), }), Ok((1, 12345)) @@ -279,7 +249,7 @@ mod tests { type ClassId = u32; type ClassInstanceId = u64; frame_support::parameter_types! { - pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(50).into(); + pub TrustBackedAssetsPalletLocation: Location = PalletInstance(50).into(); } // ConvertedConcreteId cfg @@ -303,13 +273,13 @@ mod tests { >; assert_eq!( TrustBackedAssetsPalletLocation::get(), - MultiLocation { parents: 0, interior: X1(PalletInstance(50)) } + Location { parents: 0, interior: [PalletInstance(50)].into() } ); // err - does not match assert_eq!( - Converter::matches_nonfungibles(&MultiAsset { - id: Concrete(MultiLocation::new(1, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_nonfungibles(&Asset { + id: AssetId(Location::new(1, [PalletInstance(50), GeneralIndex(1)])), fun: NonFungible(Index(54321)), }), Err(MatchError::AssetNotHandled) @@ -317,10 +287,10 @@ mod tests { // err - matches, but convert fails assert_eq!( - Converter::matches_nonfungibles(&MultiAsset { - id: Concrete(MultiLocation::new( + Converter::matches_nonfungibles(&Asset { + id: AssetId(Location::new( 0, - X2(PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }) + [PalletInstance(50), GeneralKey { length: 1, data: [1; 32] }] )), fun: NonFungible(Index(54321)), }), @@ -329,8 +299,8 @@ mod tests { // err - matches, but Fungible vs NonFungible assert_eq!( - Converter::matches_nonfungibles(&MultiAsset { - id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_nonfungibles(&Asset { + id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])), fun: Fungible(12345), }), Err(MatchError::AssetNotHandled) @@ -338,8 +308,8 @@ mod tests { // ok assert_eq!( - Converter::matches_nonfungibles(&MultiAsset { - id: Concrete(MultiLocation::new(0, X2(PalletInstance(50), GeneralIndex(1)))), + Converter::matches_nonfungibles(&Asset { + id: AssetId(Location::new(0, [PalletInstance(50), GeneralIndex(1)])), fun: NonFungible(Index(54321)), }), Ok((1, 54321)) diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index 3b13cab2c1ea84ba2422809daa4a0a6d807eb50e..80411ab5a2246bf8c7ba96c215b685f5e90aadaf 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -34,7 +34,7 @@ use xcm_executor::traits::{CheckSuspension, OnResponse, Properties, ShouldExecut pub struct TakeWeightCredit; impl ShouldExecute for TakeWeightCredit { fn should_execute( - _origin: &MultiLocation, + _origin: &Location, _instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -60,9 +60,9 @@ const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2; /// Only allows for `TeleportAsset`, `WithdrawAsset`, `ClaimAsset` and `ReserveAssetDeposit` XCMs /// because they are the only ones that place assets in the Holding Register to pay for execution. pub struct AllowTopLevelPaidExecutionFrom(PhantomData); -impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { +impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, _properties: &mut Properties, @@ -81,10 +81,15 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro instructions[..end] .matcher() .match_next_inst(|inst| match inst { - ReceiveTeleportedAsset(..) | ReserveAssetDeposited(..) => Ok(()), - WithdrawAsset(ref assets) if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => Ok(()), - ClaimAsset { ref assets, .. } if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION => - Ok(()), + ReceiveTeleportedAsset(ref assets) | + ReserveAssetDeposited(ref assets) | + WithdrawAsset(ref assets) | + ClaimAsset { ref assets, .. } => + if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION { + Ok(()) + } else { + Err(ProcessMessageError::BadFormat) + }, _ => Err(ProcessMessageError::BadFormat), })? .skip_inst_while(|inst| matches!(inst, ClearOrigin))? @@ -153,14 +158,11 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro pub struct WithComputedOrigin( PhantomData<(InnerBarrier, LocalUniversal, MaxPrefixes)>, ); -impl< - InnerBarrier: ShouldExecute, - LocalUniversal: Get, - MaxPrefixes: Get, - > ShouldExecute for WithComputedOrigin +impl, MaxPrefixes: Get> + ShouldExecute for WithComputedOrigin { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -170,7 +172,7 @@ impl< "WithComputedOrigin origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}", origin, instructions, max_weight, properties, ); - let mut actual_origin = *origin; + let mut actual_origin = origin.clone(); let skipped = Cell::new(0usize); // NOTE: We do not check the validity of `UniversalOrigin` here, meaning that a malicious // origin could place a `UniversalOrigin` in order to spoof some location which gets free @@ -185,10 +187,11 @@ impl< // Note the origin is *relative to local consensus*! So we need to escape // local consensus with the `parents` before diving in into the // `universal_location`. - actual_origin = X1(*new_global).relative_to(&LocalUniversal::get()); + actual_origin = + Junctions::from([*new_global]).relative_to(&LocalUniversal::get()); }, DescendOrigin(j) => { - let Ok(_) = actual_origin.append_with(*j) else { + let Ok(_) = actual_origin.append_with(j.clone()) else { return Err(ProcessMessageError::Unsupported) }; }, @@ -216,7 +219,7 @@ impl< pub struct TrailingSetTopicAsId(PhantomData); impl ShouldExecute for TrailingSetTopicAsId { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -245,7 +248,7 @@ where SuspensionChecker: CheckSuspension, { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -263,9 +266,9 @@ where /// Use only for executions from completely trusted origins, from which no permissionless messages /// can be sent. pub struct AllowUnpaidExecutionFrom(PhantomData); -impl> ShouldExecute for AllowUnpaidExecutionFrom { +impl> ShouldExecute for AllowUnpaidExecutionFrom { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -285,9 +288,9 @@ impl> ShouldExecute for AllowUnpaidExecutionFrom { /// /// Use only for executions from trusted origin groups. pub struct AllowExplicitUnpaidExecutionFrom(PhantomData); -impl> ShouldExecute for AllowExplicitUnpaidExecutionFrom { +impl> ShouldExecute for AllowExplicitUnpaidExecutionFrom { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, _properties: &mut Properties, @@ -309,11 +312,11 @@ impl> ShouldExecute for AllowExplicitUnpaidExecutionF /// Allows a message only if it is from a system-level child parachain. pub struct IsChildSystemParachain(PhantomData); -impl> Contains for IsChildSystemParachain { - fn contains(l: &MultiLocation) -> bool { +impl> Contains for IsChildSystemParachain { + fn contains(l: &Location) -> bool { matches!( - l.interior(), - Junctions::X1(Junction::Parachain(id)) + l.interior().as_slice(), + [Junction::Parachain(id)] if ParaId::from(*id).is_system() && l.parent_count() == 0, ) } @@ -323,7 +326,7 @@ impl> Contains for IsChildSystemPara pub struct AllowKnownQueryResponses(PhantomData); impl ShouldExecute for AllowKnownQueryResponses { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -349,9 +352,9 @@ impl ShouldExecute for AllowKnownQueryResponses(PhantomData); -impl> ShouldExecute for AllowSubscriptionsFrom { +impl> ShouldExecute for AllowSubscriptionsFrom { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -386,7 +389,7 @@ where Allow: ShouldExecute, { fn should_execute( - origin: &MultiLocation, + origin: &Location, message: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -400,7 +403,7 @@ where pub struct DenyReserveTransferToRelayChain; impl ShouldExecute for DenyReserveTransferToRelayChain { fn should_execute( - origin: &MultiLocation, + origin: &Location, message: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -409,22 +412,18 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { |_| true, |inst| match inst { InitiateReserveWithdraw { - reserve: MultiLocation { parents: 1, interior: Here }, + reserve: Location { parents: 1, interior: Here }, .. } | - DepositReserveAsset { - dest: MultiLocation { parents: 1, interior: Here }, .. - } | - TransferReserveAsset { - dest: MultiLocation { parents: 1, interior: Here }, .. - } => { + DepositReserveAsset { dest: Location { parents: 1, interior: Here }, .. } | + TransferReserveAsset { dest: Location { parents: 1, interior: Here }, .. } => { Err(ProcessMessageError::Unsupported) // Deny }, // An unexpected reserve transfer has arrived from the Relay Chain. Generally, // `IsReserve` should not allow this, but we just log it here. ReserveAssetDeposited { .. } - if matches!(origin, MultiLocation { parents: 1, interior: Here }) => + if matches!(origin, Location { parents: 1, interior: Here }) => { log::warn!( target: "xcm::barrier", diff --git a/polkadot/xcm/xcm-builder/src/controller.rs b/polkadot/xcm/xcm-builder/src/controller.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba2b1fb44b8eeb0916fdec24d97e36121bc18c8a --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/controller.rs @@ -0,0 +1,192 @@ +// 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 set of traits that define how a pallet interface with XCM. +//! Controller traits defined in this module are high-level traits that will rely on other traits +//! from `xcm-executor` to perform their tasks. + +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, WithPostDispatchInfo}, + pallet_prelude::DispatchError, +}; +use sp_std::boxed::Box; +use xcm::prelude::*; +pub use xcm_executor::traits::QueryHandler; + +/// Umbrella trait for all Controller traits. +pub trait Controller: + ExecuteController + SendController + QueryController +{ +} + +impl Controller for T where + T: ExecuteController + + SendController + + QueryController +{ +} + +/// Weight functions needed for [`ExecuteController`]. +pub trait ExecuteControllerWeightInfo { + /// Weight for [`ExecuteController::execute`] + fn execute() -> Weight; +} + +/// Execute an XCM locally, for a given origin. +/// +/// An implementation of that trait will handle the low-level details of the execution, such as: +/// - Validating and Converting the origin to a Location. +/// - Handling versioning. +/// - Calling the internal executor, which implements [`ExecuteXcm`]. +pub trait ExecuteController { + /// Weight information for ExecuteController functions. + type WeightInfo: ExecuteControllerWeightInfo; + + /// Attempt to execute an XCM locally, returns Ok with the weight consumed if the execution + /// complete successfully, Err otherwise. + /// + /// # Parameters + /// + /// - `origin`: the origin of the call. + /// - `message`: the XCM program to be executed. + /// - `max_weight`: the maximum weight that can be consumed by the execution. + fn execute( + origin: Origin, + message: Box>, + max_weight: Weight, + ) -> Result; +} + +/// Weight functions needed for [`SendController`]. +pub trait SendControllerWeightInfo { + /// Weight for [`SendController::send`] + fn send() -> Weight; +} + +/// Send an XCM from a given origin. +/// +/// An implementation of that trait will handle the low-level details of dispatching an XCM, such +/// as: +/// - Validating and Converting the origin to an interior location. +/// - Handling versioning. +/// - Calling the internal router, which implements [`SendXcm`]. +pub trait SendController { + /// Weight information for SendController functions. + type WeightInfo: SendControllerWeightInfo; + + /// Send an XCM to be executed by a remote location. + /// + /// # Parameters + /// + /// - `origin`: the origin of the call. + /// - `dest`: the destination of the message. + /// - `msg`: the XCM to be sent. + fn send( + origin: Origin, + dest: Box, + message: Box>, + ) -> Result; +} + +/// Weight functions needed for [`QueryController`]. +pub trait QueryControllerWeightInfo { + /// Weight for [`QueryController::query`] + fn query() -> Weight; + + /// Weight for [`QueryHandler::take_response`] + fn take_response() -> Weight; +} + +/// Query a remote location, from a given origin. +/// +/// An implementation of that trait will handle the low-level details of querying a remote location, +/// such as: +/// - Validating and Converting the origin to an interior location. +/// - Handling versioning. +/// - Calling the [`QueryHandler`] to register the query. +pub trait QueryController: QueryHandler { + /// Weight information for QueryController functions. + type WeightInfo: QueryControllerWeightInfo; + + /// Query a remote location. + /// + /// # Parameters + /// + /// - `origin`: the origin of the call, used to determine the responder. + /// - `timeout`: the maximum block number that the query should be responded to. + /// - `match_querier`: the querier that the query should be responded to. + fn query( + origin: Origin, + timeout: Timeout, + match_querier: VersionedLocation, + ) -> Result; +} + +impl ExecuteController for () { + type WeightInfo = (); + fn execute( + _origin: Origin, + _message: Box>, + _max_weight: Weight, + ) -> Result { + Err(DispatchError::Other("ExecuteController::execute not implemented") + .with_weight(Weight::zero())) + } +} + +impl ExecuteControllerWeightInfo for () { + fn execute() -> Weight { + Weight::zero() + } +} + +impl SendController for () { + type WeightInfo = (); + fn send( + _origin: Origin, + _dest: Box, + _message: Box>, + ) -> Result { + Ok(Default::default()) + } +} + +impl SendControllerWeightInfo for () { + fn send() -> Weight { + Weight::zero() + } +} + +impl QueryControllerWeightInfo for () { + fn query() -> Weight { + Weight::zero() + } + fn take_response() -> Weight { + Weight::zero() + } +} + +impl QueryController for () { + type WeightInfo = (); + + fn query( + _origin: Origin, + _timeout: Timeout, + _match_querier: VersionedLocation, + ) -> Result { + Ok(Default::default()) + } +} diff --git a/polkadot/xcm/xcm-builder/src/currency_adapter.rs b/polkadot/xcm/xcm-builder/src/currency_adapter.rs index 8ecf1dee72db00695a0a48ca7b2f489ec6f8bf4c..24261ac06583c4ca284f52071a4ee80afae2a6ec 100644 --- a/polkadot/xcm/xcm-builder/src/currency_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/currency_adapter.rs @@ -16,21 +16,23 @@ //! 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; use sp_std::{marker::PhantomData, result}; -use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result, XcmContext}; +use xcm::latest::{Asset, Error as XcmError, Location, Result, XcmContext}; use xcm_executor::{ traits::{ConvertLocation, MatchesFungible, TransactAsset}, - Assets, + AssetsInHolding, }; /// Asset transaction errors. enum Error { /// The given asset is not handled. (According to [`XcmError::AssetNotFound`]) AssetNotHandled, - /// `MultiLocation` to `AccountId` conversion failed. + /// `Location` to `AccountId` conversion failed. AccountIdConversionFailed, } @@ -60,7 +62,7 @@ impl From for XcmError { /// /// /// Our relay chain's location. /// parameter_types! { -/// pub RelayChain: MultiLocation = Parent.into(); +/// pub RelayChain: Location = Parent.into(); /// pub CheckingAccount: AccountId = PalletId(*b"checking").into_account_truncating(); /// } /// @@ -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) { @@ -139,7 +142,7 @@ impl< > TransactAsset for CurrencyAdapter { - fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result { + fn can_check_in(_origin: &Location, what: &Asset, _context: &XcmContext) -> Result { log::trace!(target: "xcm::currency_adapter", "can_check_in origin: {:?}, what: {:?}", _origin, what); // Check we handle this asset. let amount: Currency::Balance = @@ -153,7 +156,7 @@ impl< } } - fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + fn check_in(_origin: &Location, what: &Asset, _context: &XcmContext) { log::trace!(target: "xcm::currency_adapter", "check_in origin: {:?}, what: {:?}", _origin, what); if let Some(amount) = Matcher::matches_fungible(what) { match CheckedAccount::get() { @@ -166,8 +169,8 @@ impl< } } - fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result { - log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); + fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> Result { + log::trace!(target: "xcm::currency_adapter", "can_check_out dest: {:?}, what: {:?}", _dest, what); let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?; match CheckedAccount::get() { Some((checked_account, MintLocation::Local)) => @@ -178,7 +181,7 @@ impl< } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + fn check_out(_dest: &Location, what: &Asset, _context: &XcmContext) { log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); if let Some(amount) = Matcher::matches_fungible(what) { match CheckedAccount::get() { @@ -191,11 +194,7 @@ impl< } } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - _context: Option<&XcmContext>, - ) -> Result { + fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> Result { log::trace!(target: "xcm::currency_adapter", "deposit_asset what: {:?}, who: {:?}", what, who); // Check we handle this asset. let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotHandled)?; @@ -206,26 +205,26 @@ impl< } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, _maybe_context: Option<&XcmContext>, - ) -> result::Result { + ) -> result::Result { log::trace!(target: "xcm::currency_adapter", "withdraw_asset what: {:?}, who: {:?}", what, who); // Check we handle this asset. 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()) } fn internal_transfer_asset( - asset: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + asset: &Asset, + from: &Location, + to: &Location, _context: &XcmContext, - ) -> result::Result { + ) -> result::Result { log::trace!(target: "xcm::currency_adapter", "internal_transfer_asset asset: {:?}, from: {:?}, to: {:?}", asset, from, to); let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotHandled)?; let from = diff --git a/polkadot/xcm/xcm-builder/src/fee_handling.rs b/polkadot/xcm/xcm-builder/src/fee_handling.rs index 1386747c9778ddad88b19b21b08f41ef24c97537..e114b3601c84a45cc0400114b7aaff2bfdae4bb8 100644 --- a/polkadot/xcm/xcm-builder/src/fee_handling.rs +++ b/polkadot/xcm/xcm-builder/src/fee_handling.rs @@ -19,40 +19,94 @@ use frame_support::traits::{Contains, Get}; use xcm::prelude::*; use xcm_executor::traits::{FeeManager, FeeReason, TransactAsset}; -/// A `FeeManager` implementation that simply deposits the fees handled into a specific on-chain +/// Handles the fees that are taken by certain XCM instructions. +pub trait HandleFee { + /// Do something with the fee which has been paid. Doing nothing here silently burns the + /// fees. + /// + /// Returns any part of the fee that wasn't consumed. + fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets; +} + +// Default `HandleFee` implementation that just burns the fee. +impl HandleFee for () { + fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) -> Assets { + Assets::new() + } +} + +#[impl_trait_for_tuples::impl_for_tuples(1, 30)] +impl HandleFee for Tuple { + fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets { + let mut unconsumed_fee = fee; + for_tuples!( #( + unconsumed_fee = Tuple::handle_fee(unconsumed_fee, context, reason.clone()); + if unconsumed_fee.is_none() { + return unconsumed_fee; + } + )* ); + + unconsumed_fee + } +} + +/// A `FeeManager` implementation that permits the specified `WaivedLocations` to not pay for fees +/// and that uses the provided `HandleFee` implementation otherwise. +pub struct XcmFeeManagerFromComponents( + PhantomData<(WaivedLocations, HandleFee)>, +); +impl, FeeHandler: HandleFee> FeeManager + for XcmFeeManagerFromComponents +{ + fn is_waived(origin: Option<&Location>, _: FeeReason) -> bool { + let Some(loc) = origin else { return false }; + WaivedLocations::contains(loc) + } + + fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) { + FeeHandler::handle_fee(fee, context, reason); + } +} + +/// Try to deposit the given fee in the specified account. +/// Burns the fee in case of a failure. +pub fn deposit_or_burn_fee>( + fee: Assets, + context: Option<&XcmContext>, + receiver: AccountId, +) { + let dest = AccountId32 { network: None, id: receiver.into() }.into(); + for asset in fee.into_inner() { + if let Err(e) = AssetTransactor::deposit_asset(&asset, &dest, context) { + log::trace!( + target: "xcm::fees", + "`AssetTransactor::deposit_asset` returned error: {:?}. Burning fee: {:?}. \ + They might be burned.", + e, asset, + ); + } + } +} + +/// A `HandleFee` implementation that simply deposits the fees into a specific on-chain /// `ReceiverAccount`. /// -/// It reuses the `AssetTransactor` configured on the XCM executor to deposit fee assets, and also -/// permits specifying `WaivedLocations` for locations that are privileged to not pay for fees. If +/// It reuses the `AssetTransactor` configured on the XCM executor to deposit fee assets. If /// the `AssetTransactor` returns an error while calling `deposit_asset`, then a warning will be -/// logged. -pub struct XcmFeesToAccount( - PhantomData<(XcmConfig, WaivedLocations, AccountId, ReceiverAccount)>, +/// logged and the fee burned. +pub struct XcmFeeToAccount( + PhantomData<(AssetTransactor, AccountId, ReceiverAccount)>, ); + impl< - XcmConfig: xcm_executor::Config, - WaivedLocations: Contains, + AssetTransactor: TransactAsset, AccountId: Clone + Into<[u8; 32]>, - ReceiverAccount: Get>, - > FeeManager for XcmFeesToAccount + ReceiverAccount: Get, + > HandleFee for XcmFeeToAccount { - fn is_waived(origin: Option<&MultiLocation>, _: FeeReason) -> bool { - let Some(loc) = origin else { return false }; - WaivedLocations::contains(loc) - } + fn handle_fee(fee: Assets, context: Option<&XcmContext>, _reason: FeeReason) -> Assets { + deposit_or_burn_fee::(fee, context, ReceiverAccount::get()); - fn handle_fee(fees: MultiAssets, context: Option<&XcmContext>) { - if let Some(receiver) = ReceiverAccount::get() { - let dest = AccountId32 { network: None, id: receiver.into() }.into(); - for asset in fees.into_inner() { - if let Err(e) = XcmConfig::AssetTransactor::deposit_asset(&asset, &dest, context) { - log::trace!( - target: "xcm::fees", - "`AssetTransactor::deposit_asset` returned error: {:?}, burning fees: {:?}", - e, asset, - ); - } - } - } + Assets::new() } } diff --git a/polkadot/xcm/xcm-builder/src/filter_asset_location.rs b/polkadot/xcm/xcm-builder/src/filter_asset_location.rs index df81f536f7b7133fb6dcbaa311e40e29860deb65..d80c5d70deea8c00fbe32af758e8986cc8c6fa7a 100644 --- a/polkadot/xcm/xcm-builder/src/filter_asset_location.rs +++ b/polkadot/xcm/xcm-builder/src/filter_asset_location.rs @@ -14,28 +14,26 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Various implementations of `ContainsPair` or -//! `Contains<(MultiLocation, Vec)>`. +//! Various implementations of `ContainsPair` or +//! `Contains<(Location, Vec)>`. use frame_support::traits::{Contains, ContainsPair, Get}; use sp_std::{marker::PhantomData, vec::Vec}; -use xcm::latest::{AssetId::Concrete, MultiAsset, MultiAssetFilter, MultiLocation, WildMultiAsset}; +use xcm::latest::{Asset, AssetFilter, AssetId, Location, WildAsset}; /// Accepts an asset iff it is a native asset. pub struct NativeAsset; -impl ContainsPair for NativeAsset { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl ContainsPair for NativeAsset { + fn contains(asset: &Asset, origin: &Location) -> bool { log::trace!(target: "xcm::contains", "NativeAsset asset: {:?}, origin: {:?}", asset, origin); - matches!(asset.id, Concrete(ref id) if id == origin) + matches!(asset.id, AssetId(ref id) if id == origin) } } /// Accepts an asset if it is contained in the given `T`'s `Get` implementation. pub struct Case(PhantomData); -impl> ContainsPair - for Case -{ - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> ContainsPair for Case { + fn contains(asset: &Asset, origin: &Location) -> bool { log::trace!(target: "xcm::contains", "Case asset: {:?}, origin: {:?}", asset, origin); let (a, o) = T::get(); a.matches(asset) && &o == origin @@ -44,18 +42,18 @@ impl> ContainsPair( - sp_std::marker::PhantomData<(Location, AssetFilters)>, +/// the `AssetFilter` instances provided by the `Get` implementation of `AssetFilters`. +pub struct LocationWithAssetFilters( + sp_std::marker::PhantomData<(LocationFilter, AssetFilters)>, ); -impl, AssetFilters: Get>> - Contains<(MultiLocation, Vec)> for LocationWithAssetFilters +impl, AssetFilters: Get>> + Contains<(Location, Vec)> for LocationWithAssetFilters { - fn contains((location, assets): &(MultiLocation, Vec)) -> bool { + fn contains((location, assets): &(Location, Vec)) -> bool { log::trace!(target: "xcm::contains", "LocationWithAssetFilters location: {:?}, assets: {:?}", location, assets); // `location` must match the `Location` filter. - if !Location::contains(location) { + if !LocationFilter::contains(location) { return false } @@ -72,12 +70,12 @@ impl, AssetFilters: Get> } } -/// Implementation of `Get>` which accepts every asset. +/// Implementation of `Get>` which accepts every asset. /// (For example, it can be used with `LocationWithAssetFilters`). pub struct AllAssets; -impl Get> for AllAssets { - fn get() -> Vec { - sp_std::vec![MultiAssetFilter::Wild(WildMultiAsset::All)] +impl Get> for AllAssets { + fn get() -> Vec { + sp_std::vec![AssetFilter::Wild(WildAsset::All)] } } @@ -90,24 +88,24 @@ mod tests { #[test] fn location_with_asset_filters_works() { frame_support::parameter_types! { - pub ParaA: MultiLocation = MultiLocation::new(1, X1(Parachain(1001))); - pub ParaB: MultiLocation = MultiLocation::new(1, X1(Parachain(1002))); - pub ParaC: MultiLocation = MultiLocation::new(1, X1(Parachain(1003))); + pub ParaA: Location = Location::new(1, [Parachain(1001)]); + pub ParaB: Location = Location::new(1, [Parachain(1002)]); + pub ParaC: Location = Location::new(1, [Parachain(1003)]); - pub AssetXLocation: MultiLocation = MultiLocation::new(1, X1(GeneralIndex(1111))); - pub AssetYLocation: MultiLocation = MultiLocation::new(1, X1(GeneralIndex(2222))); - pub AssetZLocation: MultiLocation = MultiLocation::new(1, X1(GeneralIndex(3333))); + pub AssetXLocation: Location = Location::new(1, [GeneralIndex(1111)]); + pub AssetYLocation: Location = Location::new(1, [GeneralIndex(2222)]); + pub AssetZLocation: Location = Location::new(1, [GeneralIndex(3333)]); - pub OnlyAssetXOrAssetY: sp_std::vec::Vec = sp_std::vec![ - Wild(AllOf { fun: WildFungible, id: Concrete(AssetXLocation::get()) }), - Wild(AllOf { fun: WildFungible, id: Concrete(AssetYLocation::get()) }), + pub OnlyAssetXOrAssetY: sp_std::vec::Vec = sp_std::vec![ + Wild(AllOf { fun: WildFungible, id: AssetId(AssetXLocation::get()) }), + Wild(AllOf { fun: WildFungible, id: AssetId(AssetYLocation::get()) }), ]; - pub OnlyAssetZ: sp_std::vec::Vec = sp_std::vec![ - Wild(AllOf { fun: WildFungible, id: Concrete(AssetZLocation::get()) }) + pub OnlyAssetZ: sp_std::vec::Vec = sp_std::vec![ + Wild(AllOf { fun: WildFungible, id: AssetId(AssetZLocation::get()) }) ]; } - let test_data: Vec<(MultiLocation, Vec, bool)> = vec![ + let test_data: Vec<(Location, Vec, bool)> = vec![ (ParaA::get(), vec![(AssetXLocation::get(), 1).into()], true), (ParaA::get(), vec![(AssetYLocation::get(), 1).into()], true), (ParaA::get(), vec![(AssetZLocation::get(), 1).into()], false), @@ -202,7 +200,7 @@ mod tests { for (location, assets, expected_result) in test_data { assert_eq!( - Filter::contains(&(location, assets.clone())), + Filter::contains(&(location.clone(), assets.clone())), expected_result, "expected_result: {expected_result} not matched for (location, assets): ({:?}, {:?})!", location, assets, ) 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..21c828922b333e85e9332d16c768aef0d34cbd9f --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs @@ -0,0 +1,308 @@ +// 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}, + AssetsInHolding, +}; + +/// [`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: &Asset, + from: &Location, + to: &Location, + _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: &Location, what: &Asset, _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: &Location, what: &Asset, _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: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { + log::trace!( + target: "xcm::fungible_adapter", + "can_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: &Location, what: &Asset, _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: &Asset, who: &Location, _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: &Asset, + who: &Location, + _context: Option<&XcmContext>, + ) -> result::Result { + log::trace!( + target: "xcm::fungible_adapter", + "withdraw_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: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_in(origin, what, context) + } + + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_out(dest, what, context) + } + + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_out(dest, what, context) + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::deposit_asset(what, who, context) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::withdraw_asset(what, who, maybe_context) + } + + fn internal_transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> result::Result { + FungibleTransferAdapter::::internal_transfer_asset( + what, from, to, context + ) + } +} diff --git a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs index b2802c9080924239fce8b002c48c279f94b35038..b4c418ebf1c9cf7d9d26540e998f966665993585 100644 --- a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs @@ -34,15 +34,15 @@ impl< Assets: fungibles::Mutate, Matcher: MatchesFungibles, AccountIdConverter: ConvertLocation, - AccountId: Clone, // can't get away without it since Currency is generic over it. + AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */ > TransactAsset for FungiblesTransferAdapter { fn internal_transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + what: &Asset, + from: &Location, + to: &Location, _context: &XcmContext, - ) -> result::Result { + ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", "internal_transfer_asset what: {:?}, from: {:?}, to: {:?}", @@ -150,7 +150,7 @@ impl< Assets: fungibles::Mutate, Matcher: MatchesFungibles, AccountIdConverter: ConvertLocation, - AccountId: Clone, // can't get away without it since Currency is generic over it. + AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */ CheckAsset: AssetChecking, CheckingAccount: Get, > @@ -185,7 +185,7 @@ impl< Assets: fungibles::Mutate, Matcher: MatchesFungibles, AccountIdConverter: ConvertLocation, - AccountId: Clone, // can't get away without it since Currency is generic over it. + AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */ CheckAsset: AssetChecking, CheckingAccount: Get, > TransactAsset @@ -198,11 +198,7 @@ impl< CheckingAccount, > { - fn can_check_in( - _origin: &MultiLocation, - what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_in(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", "can_check_in origin: {:?}, what: {:?}", @@ -219,7 +215,7 @@ impl< } } - fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + fn check_in(_origin: &Location, what: &Asset, _context: &XcmContext) { log::trace!( target: "xcm::fungibles_adapter", "check_in origin: {:?}, what: {:?}", @@ -236,14 +232,10 @@ impl< } } - fn can_check_out( - _origin: &MultiLocation, - what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_out(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", - "can_check_in origin: {:?}, what: {:?}", + "can_check_out origin: {:?}, what: {:?}", _origin, what ); // Check we handle this asset. @@ -257,7 +249,7 @@ impl< } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + fn check_out(_dest: &Location, what: &Asset, _context: &XcmContext) { log::trace!( target: "xcm::fungibles_adapter", "check_out dest: {:?}, what: {:?}", @@ -274,11 +266,7 @@ impl< } } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - _context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", "deposit_asset what: {:?}, who: {:?}", @@ -294,10 +282,10 @@ impl< } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, _maybe_context: Option<&XcmContext>, - ) -> result::Result { + ) -> result::Result { log::trace!( target: "xcm::fungibles_adapter", "withdraw_asset what: {:?}, who: {:?}", @@ -325,13 +313,13 @@ impl< Assets: fungibles::Mutate, Matcher: MatchesFungibles, AccountIdConverter: ConvertLocation, - AccountId: Clone, // can't get away without it since Currency is generic over it. + AccountId: Eq + Clone, /* can't get away without it since Currency is generic over it. */ CheckAsset: AssetChecking, CheckingAccount: Get, > TransactAsset for FungiblesAdapter { - fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { FungiblesMutateAdapter::< Assets, Matcher, @@ -342,7 +330,7 @@ impl< >::can_check_in(origin, what, context) } - fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -353,7 +341,7 @@ impl< >::check_in(origin, what, context) } - fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { FungiblesMutateAdapter::< Assets, Matcher, @@ -364,7 +352,7 @@ impl< >::can_check_out(dest, what, context) } - fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { FungiblesMutateAdapter::< Assets, Matcher, @@ -375,11 +363,7 @@ impl< >::check_out(dest, what, context) } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { FungiblesMutateAdapter::< Assets, Matcher, @@ -391,10 +375,10 @@ impl< } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, maybe_context: Option<&XcmContext>, - ) -> result::Result { + ) -> result::Result { FungiblesMutateAdapter::< Assets, Matcher, @@ -406,11 +390,11 @@ impl< } fn internal_transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + what: &Asset, + from: &Location, + to: &Location, context: &XcmContext, - ) -> result::Result { + ) -> result::Result { FungiblesTransferAdapter::::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 34371398cdc351df0a317636d43e2751a649e2c8..e2af8187136e8eba2b42d7d4a667a69830556032 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -26,32 +26,12 @@ mod tests; #[cfg(feature = "std")] pub mod test_utils; -mod location_conversion; -#[allow(deprecated)] -pub use location_conversion::ForeignChainAliasAccount; -pub use location_conversion::{ - Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32, - ChildParachainConvertsVia, DescribeAccountId32Terminal, DescribeAccountIdTerminal, - DescribeAccountKey20Terminal, DescribeAllTerminal, DescribeBodyTerminal, DescribeFamily, - DescribeLocation, DescribePalletTerminal, DescribeTerminus, DescribeTreasuryVoiceTerminal, - GlobalConsensusConvertsFor, GlobalConsensusParachainConvertsFor, HashedDescription, - LocalTreasuryVoiceConvertsVia, ParentIsPreset, SiblingParachainConvertsVia, -}; - -mod origin_conversion; -pub use origin_conversion::{ - BackingToPlurality, ChildParachainAsNative, ChildSystemParachainAsSuperuser, EnsureXcmOrigin, - OriginToPluralityVoice, ParentAsSuperuser, RelayChainAsNative, SiblingParachainAsNative, - SiblingSystemParachainAsSuperuser, SignedAccountId32AsNative, SignedAccountKey20AsNative, - SignedToAccountId32, SovereignSignedViaLocation, -}; - mod asset_conversion; +#[allow(deprecated)] +pub use asset_conversion::ConvertedConcreteAssetId; pub use asset_conversion::{ - AsPrefixedGeneralIndex, ConvertedAbstractId, ConvertedConcreteId, MatchedConvertedConcreteId, + AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, V4V3LocationConverter, }; -#[allow(deprecated)] -pub use asset_conversion::{ConvertedAbstractAssetId, ConvertedConcreteAssetId}; mod barriers; pub use barriers::{ @@ -61,14 +41,26 @@ pub use barriers::{ WithComputedOrigin, }; -mod process_xcm_message; -pub use process_xcm_message::ProcessXcmMessage; +mod controller; +pub use controller::{ + Controller, ExecuteController, ExecuteControllerWeightInfo, QueryController, + QueryControllerWeightInfo, QueryHandler, SendController, SendControllerWeightInfo, +}; mod currency_adapter; +#[allow(deprecated)] pub use currency_adapter::CurrencyAdapter; mod fee_handling; -pub use fee_handling::XcmFeesToAccount; +pub use fee_handling::{ + deposit_or_burn_fee, HandleFee, XcmFeeManagerFromComponents, XcmFeeToAccount, +}; + +mod filter_asset_location; +pub use filter_asset_location::{AllAssets, Case, LocationWithAssetFilters, NativeAsset}; + +mod fungible_adapter; +pub use fungible_adapter::{FungibleAdapter, FungibleMutateAdapter, FungibleTransferAdapter}; mod fungibles_adapter; pub use fungibles_adapter::{ @@ -76,30 +68,59 @@ pub use fungibles_adapter::{ LocalMint, MintLocation, NoChecking, NonLocalMint, }; -mod nonfungibles_adapter; -pub use nonfungibles_adapter::{ - NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter, -}; - -mod weight; -pub use weight::{ - FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds, +mod location_conversion; +#[allow(deprecated)] +pub use location_conversion::ForeignChainAliasAccount; +pub use location_conversion::{ + Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32, + ChildParachainConvertsVia, DescribeAccountId32Terminal, DescribeAccountIdTerminal, + DescribeAccountKey20Terminal, DescribeAllTerminal, DescribeBodyTerminal, DescribeFamily, + DescribeLocation, DescribePalletTerminal, DescribeTerminus, DescribeTreasuryVoiceTerminal, + GlobalConsensusConvertsFor, GlobalConsensusParachainConvertsFor, HashedDescription, + LocalTreasuryVoiceConvertsVia, ParentIsPreset, SiblingParachainConvertsVia, }; mod matches_location; pub use matches_location::{StartsWith, StartsWithExplicitGlobalConsensus}; mod matches_token; -pub use matches_token::{IsAbstract, IsConcrete}; +pub use matches_token::IsConcrete; mod matcher; pub use matcher::{CreateMatcher, MatchXcm, Matcher}; -mod filter_asset_location; -pub use filter_asset_location::{AllAssets, Case, LocationWithAssetFilters, NativeAsset}; +mod nonfungibles_adapter; +pub use nonfungibles_adapter::{ + NonFungiblesAdapter, NonFungiblesMutateAdapter, NonFungiblesTransferAdapter, +}; + +mod nonfungible_adapter; +pub use nonfungible_adapter::{ + NonFungibleAdapter, NonFungibleMutateAdapter, NonFungibleTransferAdapter, +}; + +mod origin_aliases; +pub use origin_aliases::AliasForeignAccountId32; + +mod origin_conversion; +pub use origin_conversion::{ + BackingToPlurality, ChildParachainAsNative, ChildSystemParachainAsSuperuser, EnsureXcmOrigin, + OriginToPluralityVoice, ParentAsSuperuser, RelayChainAsNative, SiblingParachainAsNative, + SiblingSystemParachainAsSuperuser, SignedAccountId32AsNative, SignedAccountKey20AsNative, + SignedToAccountId32, SovereignSignedViaLocation, +}; + +mod pay; +pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm}; + +mod process_xcm_message; +pub use process_xcm_message::ProcessXcmMessage; mod routing; -pub use routing::{WithTopicSource, WithUniqueTopic}; +pub use routing::{EnsureDelivery, WithTopicSource, WithUniqueTopic}; + +mod transactional; +pub use transactional::FrameTransactionalProcessor; mod universal_exports; pub use universal_exports::{ @@ -108,8 +129,7 @@ pub use universal_exports::{ NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter, }; -mod origin_aliases; -pub use origin_aliases::AliasForeignAccountId32; - -mod pay; -pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm}; +mod weight; +pub use weight::{ + FixedRateOfFungible, FixedWeightBounds, TakeRevenue, UsingComponents, WeightInfoBounds, +}; diff --git a/polkadot/xcm/xcm-builder/src/location_conversion.rs b/polkadot/xcm/xcm-builder/src/location_conversion.rs index 25d16f7eb8ccc7c6bc40e4454e77be012de172b4..c9553030817a1a314022efd17dab00eef17cbe3c 100644 --- a/polkadot/xcm/xcm-builder/src/location_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/location_conversion.rs @@ -27,12 +27,12 @@ use xcm_executor::traits::ConvertLocation; pub trait DescribeLocation { /// Create a description of the given `location` if possible. No two locations should have the /// same descriptor. - fn describe_location(location: &MultiLocation) -> Option>; + fn describe_location(location: &Location) -> Option>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl DescribeLocation for Tuple { - fn describe_location(l: &MultiLocation) -> Option> { + fn describe_location(l: &Location) -> Option> { for_tuples!( #( match Tuple::describe_location(l) { Some(result) => return Some(result), @@ -45,9 +45,9 @@ impl DescribeLocation for Tuple { pub struct DescribeTerminus; impl DescribeLocation for DescribeTerminus { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, Here) => Some(Vec::new()), + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, []) => Some(Vec::new()), _ => return None, } } @@ -55,10 +55,9 @@ impl DescribeLocation for DescribeTerminus { pub struct DescribePalletTerminal; impl DescribeLocation for DescribePalletTerminal { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, X1(PalletInstance(i))) => - Some((b"Pallet", Compact::::from(*i as u32)).encode()), + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, [PalletInstance(i)]) => Some((b"Pallet", Compact::::from(*i as u32)).encode()), _ => return None, } } @@ -66,9 +65,9 @@ impl DescribeLocation for DescribePalletTerminal { pub struct DescribeAccountId32Terminal; impl DescribeLocation for DescribeAccountId32Terminal { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, X1(AccountId32 { id, .. })) => Some((b"AccountId32", id).encode()), + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, [AccountId32 { id, .. }]) => Some((b"AccountId32", id).encode()), _ => return None, } } @@ -76,9 +75,9 @@ impl DescribeLocation for DescribeAccountId32Terminal { pub struct DescribeAccountKey20Terminal; impl DescribeLocation for DescribeAccountKey20Terminal { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, X1(AccountKey20 { key, .. })) => Some((b"AccountKey20", key).encode()), + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, [AccountKey20 { key, .. }]) => Some((b"AccountKey20", key).encode()), _ => return None, } } @@ -89,9 +88,9 @@ impl DescribeLocation for DescribeAccountKey20Terminal { pub struct DescribeTreasuryVoiceTerminal; impl DescribeLocation for DescribeTreasuryVoiceTerminal { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, X1(Plurality { id: BodyId::Treasury, part: BodyPart::Voice })) => + fn describe_location(location: &Location) -> Option> { + match location.unpack() { + (0, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]) => Some((b"Treasury", b"Voice").encode()), _ => None, } @@ -102,9 +101,9 @@ pub type DescribeAccountIdTerminal = (DescribeAccountId32Terminal, DescribeAccou pub struct DescribeBodyTerminal; impl DescribeLocation for DescribeBodyTerminal { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, &l.interior) { - (0, X1(Plurality { id, part })) => Some((b"Body", id, part).encode()), + fn describe_location(l: &Location) -> Option> { + match l.unpack() { + (0, [Plurality { id, part }]) => Some((b"Body", id, part).encode()), _ => return None, } } @@ -121,20 +120,21 @@ pub type DescribeAllTerminal = ( pub struct DescribeFamily(PhantomData); impl DescribeLocation for DescribeFamily { - fn describe_location(l: &MultiLocation) -> Option> { - match (l.parents, l.interior.first()) { + fn describe_location(l: &Location) -> Option> { + match (l.parent_count(), l.first_interior()) { (0, Some(Parachain(index))) => { - let tail = l.interior.split_first().0; + let tail = l.clone().split_first_interior().0; let interior = Suffix::describe_location(&tail.into())?; Some((b"ChildChain", Compact::::from(*index), interior).encode()) }, (1, Some(Parachain(index))) => { - let tail = l.interior.split_first().0; - let interior = Suffix::describe_location(&tail.into())?; + let tail_junctions = l.interior().clone().split_first().0; + let tail = Location::new(0, tail_junctions); + let interior = Suffix::describe_location(&tail)?; Some((b"SiblingChain", Compact::::from(*index), interior).encode()) }, (1, _) => { - let tail = l.interior.into(); + let tail = l.interior().clone().into(); let interior = Suffix::describe_location(&tail)?; Some((b"ParentChain", interior).encode()) }, @@ -147,7 +147,7 @@ pub struct HashedDescription(PhantomData<(AccountId, Descri impl + Clone, Describe: DescribeLocation> ConvertLocation for HashedDescription { - fn convert_location(value: &MultiLocation) -> Option { + fn convert_location(value: &Location) -> Option { Some(blake2_256(&Describe::describe_location(value)?).into()) } } @@ -156,34 +156,26 @@ impl + Clone, Describe: DescribeLocation> ConvertLocat /// are recommended to use the more extensible `HashedDescription` type. pub struct LegacyDescribeForeignChainAccount; impl DescribeLocation for LegacyDescribeForeignChainAccount { - fn describe_location(location: &MultiLocation) -> Option> { - Some(match location { + fn describe_location(location: &Location) -> Option> { + Some(match location.unpack() { // Used on the relay chain for sending paras that use 32 byte accounts - MultiLocation { - parents: 0, - interior: X2(Parachain(para_id), AccountId32 { id, .. }), - } => LegacyDescribeForeignChainAccount::from_para_32(para_id, id, 0), + (0, [Parachain(para_id), AccountId32 { id, .. }]) => + LegacyDescribeForeignChainAccount::from_para_32(para_id, id, 0), // Used on the relay chain for sending paras that use 20 byte accounts - MultiLocation { - parents: 0, - interior: X2(Parachain(para_id), AccountKey20 { key, .. }), - } => LegacyDescribeForeignChainAccount::from_para_20(para_id, key, 0), + (0, [Parachain(para_id), AccountKey20 { key, .. }]) => + LegacyDescribeForeignChainAccount::from_para_20(para_id, key, 0), // Used on para-chain for sending paras that use 32 byte accounts - MultiLocation { - parents: 1, - interior: X2(Parachain(para_id), AccountId32 { id, .. }), - } => LegacyDescribeForeignChainAccount::from_para_32(para_id, id, 1), + (1, [Parachain(para_id), AccountId32 { id, .. }]) => + LegacyDescribeForeignChainAccount::from_para_32(para_id, id, 1), // Used on para-chain for sending paras that use 20 byte accounts - MultiLocation { - parents: 1, - interior: X2(Parachain(para_id), AccountKey20 { key, .. }), - } => LegacyDescribeForeignChainAccount::from_para_20(para_id, key, 1), + (1, [Parachain(para_id), AccountKey20 { key, .. }]) => + LegacyDescribeForeignChainAccount::from_para_20(para_id, key, 1), // Used on para-chain for sending from the relay chain - MultiLocation { parents: 1, interior: X1(AccountId32 { id, .. }) } => + (1, [AccountId32 { id, .. }]) => LegacyDescribeForeignChainAccount::from_relay_32(id, 1), // No other conversions provided @@ -278,16 +270,16 @@ pub struct Account32Hash(PhantomData<(Network, AccountId)>); impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> ConvertLocation for Account32Hash { - fn convert_location(location: &MultiLocation) -> Option { + fn convert_location(location: &Location) -> Option { Some(("multiloc", location).using_encoded(blake2_256).into()) } } -/// A [`MultiLocation`] consisting of a single `Parent` [`Junction`] will be converted to the +/// A [`Location`] consisting of a single `Parent` [`Junction`] will be converted to the /// parent `AccountId`. pub struct ParentIsPreset(PhantomData); impl ConvertLocation for ParentIsPreset { - fn convert_location(location: &MultiLocation) -> Option { + fn convert_location(location: &Location) -> Option { if location.contains_parents_only(1) { Some( b"Parent" @@ -304,10 +296,9 @@ pub struct ChildParachainConvertsVia(PhantomData<(ParaId, Acc impl + Into + AccountIdConversion, AccountId: Clone> ConvertLocation for ChildParachainConvertsVia { - fn convert_location(location: &MultiLocation) -> Option { - match location { - MultiLocation { parents: 0, interior: X1(Parachain(id)) } => - Some(ParaId::from(*id).into_account_truncating()), + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (0, [Parachain(id)]) => Some(ParaId::from(*id).into_account_truncating()), _ => None, } } @@ -317,10 +308,9 @@ pub struct SiblingParachainConvertsVia(PhantomData<(ParaId, A impl + Into + AccountIdConversion, AccountId: Clone> ConvertLocation for SiblingParachainConvertsVia { - fn convert_location(location: &MultiLocation) -> Option { - match location { - MultiLocation { parents: 1, interior: X1(Parachain(id)) } => - Some(ParaId::from(*id).into_account_truncating()), + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (1, [Parachain(id)]) => Some(ParaId::from(*id).into_account_truncating()), _ => None, } } @@ -331,15 +321,13 @@ pub struct AccountId32Aliases(PhantomData<(Network, AccountI impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> ConvertLocation for AccountId32Aliases { - fn convert_location(location: &MultiLocation) -> Option { - let id = match *location { - MultiLocation { parents: 0, interior: X1(AccountId32 { id, network: None }) } => id, - MultiLocation { parents: 0, interior: X1(AccountId32 { id, network }) } - if network == Network::get() => - id, + fn convert_location(location: &Location) -> Option { + let id = match location.unpack() { + (0, [AccountId32 { id, network: None }]) => id, + (0, [AccountId32 { id, network }]) if *network == Network::get() => id, _ => return None, }; - Some(id.into()) + Some((*id).into()) } } @@ -351,25 +339,23 @@ pub struct LocalTreasuryVoiceConvertsVia( impl, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> ConvertLocation for LocalTreasuryVoiceConvertsVia { - fn convert_location(location: &MultiLocation) -> Option { - match *location { - MultiLocation { - parents: 0, - interior: X1(Plurality { id: BodyId::Treasury, part: BodyPart::Voice }), - } => Some((TreasuryAccount::get().into() as [u8; 32]).into()), + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (0, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]) => + Some((TreasuryAccount::get().into() as [u8; 32]).into()), _ => None, } } } /// Conversion implementation which converts from a `[u8; 32]`-based `AccountId` into a -/// `MultiLocation` consisting solely of a `AccountId32` junction with a fixed value for its +/// `Location` consisting solely of a `AccountId32` junction with a fixed value for its /// network (provided by `Network`) and the `AccountId`'s `[u8; 32]` datum for the `id`. pub struct AliasesIntoAccountId32(PhantomData<(Network, AccountId)>); impl<'a, Network: Get>, AccountId: Clone + Into<[u8; 32]> + Clone> - TryConvert<&'a AccountId, MultiLocation> for AliasesIntoAccountId32 + TryConvert<&'a AccountId, Location> for AliasesIntoAccountId32 { - fn try_convert(who: &AccountId) -> Result { + fn try_convert(who: &AccountId) -> Result { Ok(AccountId32 { network: Network::get(), id: who.clone().into() }.into()) } } @@ -378,15 +364,13 @@ pub struct AccountKey20Aliases(PhantomData<(Network, Account impl>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone> ConvertLocation for AccountKey20Aliases { - fn convert_location(location: &MultiLocation) -> Option { - let key = match *location { - MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network: None }) } => key, - MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network }) } - if network == Network::get() => - key, + fn convert_location(location: &Location) -> Option { + let key = match location.unpack() { + (0, [AccountKey20 { key, network: None }]) => key, + (0, [AccountKey20 { key, network }]) if *network == Network::get() => key, _ => return None, }; - Some(key.into()) + Some((*key).into()) } } @@ -402,10 +386,10 @@ impl>, AccountId: From<[u8; 20]> + Into<[u8; 20]> pub struct GlobalConsensusConvertsFor( PhantomData<(UniversalLocation, AccountId)>, ); -impl, AccountId: From<[u8; 32]> + Clone> +impl, AccountId: From<[u8; 32]> + Clone> ConvertLocation for GlobalConsensusConvertsFor { - fn convert_location(location: &MultiLocation) -> Option { + fn convert_location(location: &Location) -> Option { let universal_source = UniversalLocation::get(); log::trace!( target: "xcm::location_conversion", @@ -413,7 +397,7 @@ impl, AccountId: From<[u8; 32]> + universal_source, location, ); let (remote_network, remote_location) = - ensure_is_remote(universal_source, *location).ok()?; + ensure_is_remote(universal_source, location.clone()).ok()?; match remote_location { Here => Some(AccountId::from(Self::from_params(&remote_network))), @@ -445,21 +429,21 @@ impl GlobalConsensusConvertsFor( PhantomData<(UniversalLocation, AccountId)>, ); -impl, AccountId: From<[u8; 32]> + Clone> +impl, AccountId: From<[u8; 32]> + Clone> ConvertLocation for GlobalConsensusParachainConvertsFor { - fn convert_location(location: &MultiLocation) -> Option { + fn convert_location(location: &Location) -> Option { let universal_source = UniversalLocation::get(); log::trace!( target: "xcm::location_conversion", "GlobalConsensusParachainConvertsFor universal_source: {:?}, location: {:?}", universal_source, location, ); - let devolved = ensure_is_remote(universal_source, *location).ok()?; + let devolved = ensure_is_remote(universal_source, location.clone()).ok()?; let (remote_network, remote_location) = devolved; - match remote_location { - X1(Parachain(remote_network_para_id)) => + match remote_location.as_slice() { + [Parachain(remote_network_para_id)] => Some(AccountId::from(Self::from_params(&remote_network, &remote_network_para_id))), _ => None, } @@ -509,12 +493,12 @@ mod tests { #[test] fn inverter_works_in_tree() { parameter_types! { - pub UniversalLocation: InteriorMultiLocation = X3(Parachain(1), account20(), account20()); + pub UniversalLocation: InteriorLocation = [Parachain(1), account20(), account20()].into(); } - let input = MultiLocation::new(3, X2(Parachain(2), account32())); + let input = Location::new(3, [Parachain(2), account32()]); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); - assert_eq!(inverted, MultiLocation::new(2, X3(Parachain(1), account20(), account20()))); + assert_eq!(inverted, Location::new(2, [Parachain(1), account20(), account20()])); } // Network Topology @@ -524,12 +508,12 @@ mod tests { #[test] fn inverter_uses_context_as_inverted_location() { parameter_types! { - pub UniversalLocation: InteriorMultiLocation = X2(account20(), account20()); + pub UniversalLocation: InteriorLocation = [account20(), account20()].into(); } - let input = MultiLocation::grandparent(); + let input = Location::new(2, Here); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); - assert_eq!(inverted, X2(account20(), account20()).into()); + assert_eq!(inverted, [account20(), account20()].into()); } // Network Topology @@ -539,10 +523,10 @@ mod tests { #[test] fn inverter_uses_only_child_on_missing_context() { parameter_types! { - pub UniversalLocation: InteriorMultiLocation = PalletInstance(5).into(); + pub UniversalLocation: InteriorLocation = PalletInstance(5).into(); } - let input = MultiLocation::grandparent(); + let input = Location::new(2, Here); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, (OnlyChild, PalletInstance(5)).into()); } @@ -550,10 +534,10 @@ mod tests { #[test] fn inverter_errors_when_location_is_too_large() { parameter_types! { - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorLocation = Here; } - let input = MultiLocation { parents: 99, interior: X1(Parachain(88)) }; + let input = Location { parents: 99, interior: [Parachain(88)].into() }; let inverted = UniversalLocation::get().invert_target(&input); assert_eq!(inverted, Err(())); } @@ -561,8 +545,8 @@ mod tests { #[test] fn global_consensus_converts_for_works() { parameter_types! { - pub UniversalLocationInNetwork1: InteriorMultiLocation = X2(GlobalConsensus(ByGenesis([1; 32])), Parachain(1234)); - pub UniversalLocationInNetwork2: InteriorMultiLocation = X2(GlobalConsensus(ByGenesis([2; 32])), Parachain(1234)); + pub UniversalLocationInNetwork1: InteriorLocation = [GlobalConsensus(ByGenesis([1; 32])), Parachain(1234)].into(); + pub UniversalLocationInNetwork2: InteriorLocation = [GlobalConsensus(ByGenesis([2; 32])), Parachain(1234)].into(); } let network_1 = UniversalLocationInNetwork1::get().global_consensus().expect("NetworkId"); let network_2 = UniversalLocationInNetwork2::get().global_consensus().expect("NetworkId"); @@ -571,17 +555,17 @@ mod tests { let network_5 = ByGenesis([5; 32]); let test_data = vec![ - (MultiLocation::parent(), false), - (MultiLocation::new(0, Here), false), - (MultiLocation::new(0, X1(GlobalConsensus(network_1))), false), - (MultiLocation::new(1, X1(GlobalConsensus(network_1))), false), - (MultiLocation::new(2, X1(GlobalConsensus(network_1))), false), - (MultiLocation::new(0, X1(GlobalConsensus(network_2))), false), - (MultiLocation::new(1, X1(GlobalConsensus(network_2))), false), - (MultiLocation::new(2, X1(GlobalConsensus(network_2))), true), - (MultiLocation::new(0, X2(GlobalConsensus(network_2), Parachain(1000))), false), - (MultiLocation::new(1, X2(GlobalConsensus(network_2), Parachain(1000))), false), - (MultiLocation::new(2, X2(GlobalConsensus(network_2), Parachain(1000))), false), + (Location::parent(), false), + (Location::new(0, Here), false), + (Location::new(0, [GlobalConsensus(network_1)]), false), + (Location::new(1, [GlobalConsensus(network_1)]), false), + (Location::new(2, [GlobalConsensus(network_1)]), false), + (Location::new(0, [GlobalConsensus(network_2)]), false), + (Location::new(1, [GlobalConsensus(network_2)]), false), + (Location::new(2, [GlobalConsensus(network_2)]), true), + (Location::new(0, [GlobalConsensus(network_2), Parachain(1000)]), false), + (Location::new(1, [GlobalConsensus(network_2), Parachain(1000)]), false), + (Location::new(2, [GlobalConsensus(network_2), Parachain(1000)]), false), ]; for (location, expected_result) in test_data { @@ -596,14 +580,14 @@ mod tests { "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location ); - match &location { - MultiLocation { interior: X1(GlobalConsensus(network)), .. } => + match location.unpack() { + (_, [GlobalConsensus(network)]) => assert_eq!( account, GlobalConsensusConvertsFor::::from_params(network), "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location ), - _ => panic!("expected_result: {}, conversion passed: {:?}, but MultiLocation does not match expected pattern, location: {:?}", expected_result, account, location) + _ => panic!("expected_result: {}, conversion passed: {:?}, but Location does not match expected pattern, location: {:?}", expected_result, account, location) } }, None => { @@ -619,32 +603,32 @@ mod tests { // all success let res_1_gc_network_3 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_3))), + &Location::new(2, [GlobalConsensus(network_3)]), ) .expect("conversion is ok"); let res_2_gc_network_3 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_3))), + &Location::new(2, [GlobalConsensus(network_3)]), ) .expect("conversion is ok"); let res_1_gc_network_4 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_4))), + &Location::new(2, [GlobalConsensus(network_4)]), ) .expect("conversion is ok"); let res_2_gc_network_4 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_4))), + &Location::new(2, [GlobalConsensus(network_4)]), ) .expect("conversion is ok"); let res_1_gc_network_5 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_5))), + &Location::new(2, [GlobalConsensus(network_5)]), ) .expect("conversion is ok"); let res_2_gc_network_5 = GlobalConsensusConvertsFor::::convert_location( - &MultiLocation::new(2, X1(GlobalConsensus(network_5))), + &Location::new(2, [GlobalConsensus(network_5)]), ) .expect("conversion is ok"); @@ -660,42 +644,30 @@ mod tests { #[test] fn global_consensus_parachain_converts_for_works() { parameter_types! { - pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ByGenesis([9; 32])), Parachain(1234)); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis([9; 32])), Parachain(1234)].into(); } let test_data = vec![ - (MultiLocation::parent(), false), - (MultiLocation::new(0, X1(Parachain(1000))), false), - (MultiLocation::new(1, X1(Parachain(1000))), false), + (Location::parent(), false), + (Location::new(0, [Parachain(1000)]), false), + (Location::new(1, [Parachain(1000)]), false), ( - MultiLocation::new( + Location::new( 2, - X3( + [ GlobalConsensus(ByGenesis([0; 32])), Parachain(1000), AccountId32 { network: None, id: [1; 32].into() }, - ), + ], ), false, ), - (MultiLocation::new(2, X1(GlobalConsensus(ByGenesis([0; 32])))), false), - ( - MultiLocation::new(0, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), - false, - ), - ( - MultiLocation::new(1, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), - false, - ), - (MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), true), - ( - MultiLocation::new(3, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), - false, - ), - ( - MultiLocation::new(9, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), - false, - ), + (Location::new(2, [GlobalConsensus(ByGenesis([0; 32]))]), false), + (Location::new(0, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]), false), + (Location::new(1, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]), false), + (Location::new(2, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]), true), + (Location::new(3, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]), false), + (Location::new(9, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]), false), ]; for (location, expected_result) in test_data { @@ -710,8 +682,8 @@ mod tests { "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location ); - match &location { - MultiLocation { interior: X2(GlobalConsensus(network), Parachain(para_id)), .. } => + match location.unpack() { + (_, [GlobalConsensus(network), Parachain(para_id)]) => assert_eq!( account, GlobalConsensusParachainConvertsFor::::from_params(network, para_id), @@ -720,7 +692,7 @@ mod tests { _ => assert_eq!( true, expected_result, - "expected_result: {}, conversion passed: {:?}, but MultiLocation does not match expected pattern, location: {:?}", expected_result, account, location + "expected_result: {}, conversion passed: {:?}, but Location does not match expected pattern, location: {:?}", expected_result, account, location ) } }, @@ -737,22 +709,22 @@ mod tests { // all success let res_gc_a_p1000 = GlobalConsensusParachainConvertsFor::::convert_location( - &MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([3; 32])), Parachain(1000))), + &Location::new(2, [GlobalConsensus(ByGenesis([3; 32])), Parachain(1000)]), ) .expect("conversion is ok"); let res_gc_a_p1001 = GlobalConsensusParachainConvertsFor::::convert_location( - &MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([3; 32])), Parachain(1001))), + &Location::new(2, [GlobalConsensus(ByGenesis([3; 32])), Parachain(1001)]), ) .expect("conversion is ok"); let res_gc_b_p1000 = GlobalConsensusParachainConvertsFor::::convert_location( - &MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([4; 32])), Parachain(1000))), + &Location::new(2, [GlobalConsensus(ByGenesis([4; 32])), Parachain(1000)]), ) .expect("conversion is ok"); let res_gc_b_p1001 = GlobalConsensusParachainConvertsFor::::convert_location( - &MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([4; 32])), Parachain(1001))), + &Location::new(2, [GlobalConsensus(ByGenesis([4; 32])), Parachain(1001)]), ) .expect("conversion is ok"); assert_ne!(res_gc_a_p1000, res_gc_a_p1001); @@ -765,9 +737,9 @@ mod tests { #[test] fn remote_account_convert_on_para_sending_para_32() { - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2(Parachain(1), AccountId32 { network: None, id: [0u8; 32] }), + interior: [Parachain(1), AccountId32 { network: None, id: [0u8; 32] }].into(), }; let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -779,19 +751,20 @@ mod tests { rem_1 ); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2( + interior: [ Parachain(1), AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }, - ), + ] + .into(), }; assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(), rem_1); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2(Parachain(2), AccountId32 { network: None, id: [0u8; 32] }), + interior: [Parachain(2), AccountId32 { network: None, id: [0u8; 32] }].into(), }; let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -808,9 +781,9 @@ mod tests { #[test] fn remote_account_convert_on_para_sending_para_20() { - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2(Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }), + interior: [Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }].into(), }; let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -822,19 +795,20 @@ mod tests { rem_1 ); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2( + interior: [ Parachain(1), AccountKey20 { network: Some(NetworkId::Polkadot), key: [0u8; 20] }, - ), + ] + .into(), }; assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(), rem_1); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X2(Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }), + interior: [Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }].into(), }; let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -851,9 +825,9 @@ mod tests { #[test] fn remote_account_convert_on_para_sending_relay() { - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X1(AccountId32 { network: None, id: [0u8; 32] }), + interior: [AccountId32 { network: None, id: [0u8; 32] }].into(), }; let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -865,16 +839,16 @@ mod tests { rem_1 ); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X1(AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }), + interior: [AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }].into(), }; assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(), rem_1); - let mul = MultiLocation { + let mul = Location { parents: 1, - interior: X1(AccountId32 { network: None, id: [1u8; 32] }), + interior: [AccountId32 { network: None, id: [1u8; 32] }].into(), }; let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -891,9 +865,9 @@ mod tests { #[test] fn remote_account_convert_on_relay_sending_para_20() { - let mul = MultiLocation { + let mul = Location { parents: 0, - interior: X2(Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }), + interior: [Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }].into(), }; let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -905,9 +879,9 @@ mod tests { rem_1 ); - let mul = MultiLocation { + let mul = Location { parents: 0, - interior: X2(Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }), + interior: [Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }].into(), }; let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -924,9 +898,9 @@ mod tests { #[test] fn remote_account_convert_on_relay_sending_para_32() { - let mul = MultiLocation { + let mul = Location { parents: 0, - interior: X2(Parachain(1), AccountId32 { network: None, id: [0u8; 32] }), + interior: [Parachain(1), AccountId32 { network: None, id: [0u8; 32] }].into(), }; let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -938,19 +912,20 @@ mod tests { rem_1 ); - let mul = MultiLocation { + let mul = Location { parents: 0, - interior: X2( + interior: [ Parachain(1), AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }, - ), + ] + .into(), }; assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(), rem_1); - let mul = MultiLocation { + let mul = Location { parents: 0, - interior: X2(Parachain(2), AccountId32 { network: None, id: [0u8; 32] }), + interior: [Parachain(2), AccountId32 { network: None, id: [0u8; 32] }].into(), }; let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).unwrap(); @@ -966,20 +941,18 @@ mod tests { } #[test] - fn remote_account_fails_with_bad_multilocation() { - let mul = MultiLocation { + fn remote_account_fails_with_bad_location() { + let mul = Location { parents: 1, - interior: X1(AccountKey20 { network: None, key: [0u8; 20] }), + interior: [AccountKey20 { network: None, key: [0u8; 20] }].into(), }; assert!(ForeignChainAliasAccount::<[u8; 32]>::convert_location(&mul).is_none()); } #[test] fn remote_account_convert_on_para_sending_from_remote_para_treasury() { - let relay_treasury_to_para_location = MultiLocation { - parents: 1, - interior: X1(Plurality { id: BodyId::Treasury, part: BodyPart::Voice }), - }; + let relay_treasury_to_para_location = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); let actual_description = ForeignChainAliasTreasuryAccount::<[u8; 32]>::convert_location( &relay_treasury_to_para_location, ) @@ -993,13 +966,10 @@ mod tests { actual_description ); - let para_to_para_treasury_location = MultiLocation { - parents: 1, - interior: X2( - Parachain(1001), - Plurality { id: BodyId::Treasury, part: BodyPart::Voice }, - ), - }; + let para_to_para_treasury_location = Location::new( + 1, + [Parachain(1001), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ); let actual_description = ForeignChainAliasTreasuryAccount::<[u8; 32]>::convert_location( ¶_to_para_treasury_location, ) @@ -1016,10 +986,8 @@ mod tests { #[test] fn local_account_convert_on_para_from_relay_treasury() { - let location = MultiLocation { - parents: 0, - interior: X1(Plurality { id: BodyId::Treasury, part: BodyPart::Voice }), - }; + let location = + Location::new(0, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); parameter_types! { pub TreasuryAccountId: AccountId = AccountId::new([42u8; 32]); diff --git a/polkadot/xcm/xcm-builder/src/matcher.rs b/polkadot/xcm/xcm-builder/src/matcher.rs index 9da135dae31ea3360b67b54558b82a93427d9cc5..eae43b290fb2c6d569f6fe0db3506e92e021e3e1 100644 --- a/polkadot/xcm/xcm-builder/src/matcher.rs +++ b/polkadot/xcm/xcm-builder/src/matcher.rs @@ -18,7 +18,7 @@ use core::ops::ControlFlow; use frame_support::traits::ProcessMessageError; -use xcm::latest::{Instruction, MultiLocation}; +use xcm::latest::{Instruction, Location}; /// Creates an instruction matcher from an XCM. Since XCM versions differ, we need to make a trait /// here to unify the interfaces among them. @@ -67,7 +67,7 @@ impl<'a, Call> CreateMatcher for &'a mut [Instruction] { pub trait MatchXcm { /// The concrete instruction type. Necessary to specify as it changes between XCM versions. type Inst; - /// The `MultiLocation` type. Necessary to specify as it changes between XCM versions. + /// The `Location` type. Necessary to specify as it changes between XCM versions. type Loc; /// The error type to throw when errors happen during matching. type Error; @@ -125,7 +125,7 @@ pub struct Matcher<'a, Call> { impl<'a, Call> MatchXcm for Matcher<'a, Call> { type Error = ProcessMessageError; type Inst = Instruction; - type Loc = MultiLocation; + type Loc = Location; fn assert_remaining_insts(self, n: usize) -> Result where diff --git a/polkadot/xcm/xcm-builder/src/matches_location.rs b/polkadot/xcm/xcm-builder/src/matches_location.rs index cfc71eafd0284b8c0c09695252fd49ac7e2a4b9c..1664c24772909a8a287cf620da308b07158270bb 100644 --- a/polkadot/xcm/xcm-builder/src/matches_location.rs +++ b/polkadot/xcm/xcm-builder/src/matches_location.rs @@ -14,37 +14,40 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Various implementations and utilities for matching and filtering `MultiLocation` and -//! `InteriorMultiLocation` types. +//! Various implementations and utilities for matching and filtering `Location` and +//! `InteriorLocation` types. use frame_support::traits::{Contains, Get}; -use xcm::latest::{InteriorMultiLocation, MultiLocation, NetworkId}; +use xcm::latest::{InteriorLocation, Location, NetworkId}; -/// An implementation of `Contains` that checks for `MultiLocation` or -/// `InteriorMultiLocation` if starts with the provided type `T`. -pub struct StartsWith(sp_std::marker::PhantomData); -impl> Contains for StartsWith { - fn contains(t: &MultiLocation) -> bool { - t.starts_with(&T::get()) +/// An implementation of `Contains` that checks for `Location` or +/// `InteriorLocation` if starts with the provided type `T`. +pub struct StartsWith(sp_std::marker::PhantomData<(T, L)>); +impl, L: TryInto + Clone> Contains for StartsWith { + fn contains(location: &L) -> bool { + let latest_location: Location = + if let Ok(location) = (*location).clone().try_into() { location } else { return false }; + let latest_t = if let Ok(location) = T::get().try_into() { location } else { return false }; + latest_location.starts_with(&latest_t) } } -impl> Contains for StartsWith { - fn contains(t: &InteriorMultiLocation) -> bool { +impl> Contains for StartsWith { + fn contains(t: &InteriorLocation) -> bool { t.starts_with(&T::get()) } } -/// An implementation of `Contains` that checks for `MultiLocation` or -/// `InteriorMultiLocation` if starts with expected `GlobalConsensus(NetworkId)` provided as type +/// An implementation of `Contains` that checks for `Location` or +/// `InteriorLocation` if starts with expected `GlobalConsensus(NetworkId)` provided as type /// `T`. pub struct StartsWithExplicitGlobalConsensus(sp_std::marker::PhantomData); -impl> Contains for StartsWithExplicitGlobalConsensus { - fn contains(location: &MultiLocation) -> bool { - matches!(location.interior.global_consensus(), Ok(requested_network) if requested_network.eq(&T::get())) +impl> Contains for StartsWithExplicitGlobalConsensus { + fn contains(location: &Location) -> bool { + matches!(location.interior().global_consensus(), Ok(requested_network) if requested_network.eq(&T::get())) } } -impl> Contains for StartsWithExplicitGlobalConsensus { - fn contains(location: &InteriorMultiLocation) -> bool { +impl> Contains for StartsWithExplicitGlobalConsensus { + fn contains(location: &InteriorLocation) -> bool { matches!(location.global_consensus(), Ok(requested_network) if requested_network.eq(&T::get())) } } diff --git a/polkadot/xcm/xcm-builder/src/matches_token.rs b/polkadot/xcm/xcm-builder/src/matches_token.rs index b6a320d89316a50ec2c9554c1e71f2ace1412792..e49fd18f88d806b09614696040fab29ead657ee2 100644 --- a/polkadot/xcm/xcm-builder/src/matches_token.rs +++ b/polkadot/xcm/xcm-builder/src/matches_token.rs @@ -19,25 +19,24 @@ use frame_support::traits::Get; use sp_std::marker::PhantomData; use xcm::latest::{ - AssetId::{Abstract, Concrete}, - AssetInstance, + Asset, AssetId, AssetInstance, Fungibility::{Fungible, NonFungible}, - MultiAsset, MultiLocation, + Location, }; use xcm_executor::traits::{MatchesFungible, MatchesNonFungible}; -/// Converts a `MultiAsset` into balance `B` if it is a concrete fungible with an id equal to that +/// Converts a `Asset` into balance `B` if its id is equal to that /// given by `T`'s `Get`. /// /// # Example /// /// ``` -/// use xcm::latest::{MultiLocation, Parent}; +/// use xcm::latest::{Location, Parent}; /// use staging_xcm_builder::IsConcrete; /// use xcm_executor::traits::MatchesFungible; /// /// frame_support::parameter_types! { -/// pub TargetLocation: MultiLocation = Parent.into(); +/// pub TargetLocation: Location = Parent.into(); /// } /// /// # fn main() { @@ -47,62 +46,18 @@ use xcm_executor::traits::{MatchesFungible, MatchesNonFungible}; /// # } /// ``` pub struct IsConcrete(PhantomData); -impl, B: TryFrom> MatchesFungible for IsConcrete { - fn matches_fungible(a: &MultiAsset) -> Option { +impl, B: TryFrom> MatchesFungible for IsConcrete { + fn matches_fungible(a: &Asset) -> Option { match (&a.id, &a.fun) { - (Concrete(ref id), Fungible(ref amount)) if id == &T::get() => - (*amount).try_into().ok(), + (AssetId(ref id), Fungible(ref amount)) if id == &T::get() => (*amount).try_into().ok(), _ => None, } } } -impl, I: TryFrom> MatchesNonFungible for IsConcrete { - fn matches_nonfungible(a: &MultiAsset) -> Option { +impl, I: TryFrom> MatchesNonFungible for IsConcrete { + fn matches_nonfungible(a: &Asset) -> Option { match (&a.id, &a.fun) { - (Concrete(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(), - _ => None, - } - } -} - -/// Same as [`IsConcrete`] but for a fungible with abstract location. -/// -/// # Example -/// -/// ``` -/// use xcm::latest::prelude::*; -/// use staging_xcm_builder::IsAbstract; -/// use xcm_executor::traits::{MatchesFungible, MatchesNonFungible}; -/// -/// frame_support::parameter_types! { -/// pub TargetLocation: [u8; 32] = [7u8; 32]; -/// } -/// -/// # fn main() { -/// let asset = ([7u8; 32], 999u128).into(); -/// // match `asset` if it is an abstract asset in `TargetLocation`. -/// assert_eq!( as MatchesFungible>::matches_fungible(&asset), Some(999)); -/// let nft = ([7u8; 32], [42u8; 4]).into(); -/// assert_eq!( -/// as MatchesNonFungible<[u8; 4]>>::matches_nonfungible(&nft), -/// Some([42u8; 4]) -/// ); -/// # } -/// ``` -pub struct IsAbstract(PhantomData); -impl, B: TryFrom> MatchesFungible for IsAbstract { - fn matches_fungible(a: &MultiAsset) -> Option { - match (&a.id, &a.fun) { - (Abstract(ref id), Fungible(ref amount)) if id == &T::get() => - (*amount).try_into().ok(), - _ => None, - } - } -} -impl, B: TryFrom> MatchesNonFungible for IsAbstract { - fn matches_nonfungible(a: &MultiAsset) -> Option { - match (&a.id, &a.fun) { - (Abstract(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(), + (AssetId(id), NonFungible(instance)) if id == &T::get() => (*instance).try_into().ok(), _ => None, } } diff --git a/polkadot/xcm/xcm-builder/src/nonfungible_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungible_adapter.rs new file mode 100644 index 0000000000000000000000000000000000000000..b69002eafc5b9945c9991a0fd3c981dd3d922338 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/nonfungible_adapter.rs @@ -0,0 +1,326 @@ +// 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::tokens::nonfungible`] through XCM. + +use crate::MintLocation; +use frame_support::{ + ensure, + traits::{tokens::nonfungible, Get}, +}; +use sp_std::{marker::PhantomData, prelude::*, result}; +use xcm::latest::prelude::*; +use xcm_executor::traits::{ + ConvertLocation, Error as MatchError, MatchesNonFungible, TransactAsset, +}; + +const LOG_TARGET: &str = "xcm::nonfungible_adapter"; + +/// [`TransactAsset`] implementation that allows the use of a [`nonfungible`] implementation for +/// handling an asset in the XCM executor. +/// Only works for transfers. +pub struct NonFungibleTransferAdapter( + PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId)>, +); +impl< + NonFungible: nonfungible::Transfer, + Matcher: MatchesNonFungible, + AccountIdConverter: ConvertLocation, + AccountId: Clone, // can't get away without it since Currency is generic over it. + > TransactAsset + for NonFungibleTransferAdapter +{ + fn transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> result::Result { + log::trace!( + target: LOG_TARGET, + "transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}", + what, + from, + to, + context, + ); + // Check we handle this asset. + let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?; + let destination = AccountIdConverter::convert_location(to) + .ok_or(MatchError::AccountIdConversionFailed)?; + NonFungible::transfer(&instance, &destination) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +/// [`TransactAsset`] implementation that allows the use of a [`nonfungible`] implementation for +/// handling an asset in the XCM executor. +/// Works for teleport bookkeeping. +pub struct NonFungibleMutateAdapter< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, +>(PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>); + +impl< + NonFungible: nonfungible::Mutate, + Matcher: MatchesNonFungible, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckingAccount: Get>, + > NonFungibleMutateAdapter +{ + fn can_accrue_checked(instance: NonFungible::ItemId) -> XcmResult { + ensure!(NonFungible::owner(&instance).is_none(), XcmError::NotDepositable); + Ok(()) + } + fn can_reduce_checked(checking_account: AccountId, instance: NonFungible::ItemId) -> XcmResult { + // This is an asset whose teleports we track. + let owner = NonFungible::owner(&instance); + ensure!(owner == Some(checking_account), XcmError::NotWithdrawable); + ensure!(NonFungible::can_transfer(&instance), XcmError::NotWithdrawable); + Ok(()) + } + fn accrue_checked(checking_account: AccountId, instance: NonFungible::ItemId) { + let ok = NonFungible::mint_into(&instance, &checking_account).is_ok(); + debug_assert!(ok, "`mint_into` cannot generally fail; qed"); + } + fn reduce_checked(instance: NonFungible::ItemId) { + let ok = NonFungible::burn(&instance, None).is_ok(); + debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed"); + } +} + +impl< + NonFungible: nonfungible::Mutate, + Matcher: MatchesNonFungible, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckingAccount: Get>, + > TransactAsset + for NonFungibleMutateAdapter +{ + fn can_check_in(_origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "can_check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, + what, + context, + ); + // Check we handle this asset. + let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?; + match CheckingAccount::get() { + // We track this asset's teleports to ensure no more come in than have gone out. + Some((checking_account, MintLocation::Local)) => + Self::can_reduce_checked(checking_account, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some((_, MintLocation::NonLocal)) => Self::can_accrue_checked(instance), + _ => Ok(()), + } + } + + fn check_in(_origin: &Location, what: &Asset, context: &XcmContext) { + log::trace!( + target: LOG_TARGET, + "check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, + what, + context, + ); + if let Some(instance) = Matcher::matches_nonfungible(what) { + match CheckingAccount::get() { + // We track this asset's teleports to ensure no more come in than have gone out. + Some((_, MintLocation::Local)) => Self::reduce_checked(instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some((checking_account, MintLocation::NonLocal)) => + Self::accrue_checked(checking_account, instance), + _ => (), + } + } + } + + fn can_check_out(_dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "can_check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, + what, + context, + ); + // Check we handle this asset. + let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?; + match CheckingAccount::get() { + // We track this asset's teleports to ensure no more come in than have gone out. + Some((_, MintLocation::Local)) => Self::can_accrue_checked(instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some((checking_account, MintLocation::NonLocal)) => + Self::can_reduce_checked(checking_account, instance), + _ => Ok(()), + } + } + + fn check_out(_dest: &Location, what: &Asset, context: &XcmContext) { + log::trace!( + target: LOG_TARGET, + "check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, + what, + context, + ); + if let Some(instance) = Matcher::matches_nonfungible(what) { + match CheckingAccount::get() { + // We track this asset's teleports to ensure no more come in than have gone out. + Some((checking_account, MintLocation::Local)) => + Self::accrue_checked(checking_account, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some((_, MintLocation::NonLocal)) => Self::reduce_checked(instance), + _ => (), + } + } + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "deposit_asset what: {:?}, who: {:?}, context: {:?}", + what, + who, + context, + ); + // Check we handle this asset. + let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?; + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + NonFungible::mint_into(&instance, &who) + .map_err(|e| XcmError::FailedToTransactAsset(e.into())) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + log::trace!( + target: LOG_TARGET, + "withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}", + what, + who, + maybe_context, + ); + // Check we handle this asset. + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + let instance = Matcher::matches_nonfungible(what).ok_or(MatchError::AssetNotHandled)?; + NonFungible::burn(&instance, Some(&who)) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +/// [`TransactAsset`] implementation that allows the use of a [`nonfungible`] implementation for +/// handling an asset in the XCM executor. +/// Works for everything. +pub struct NonFungibleAdapter( + PhantomData<(NonFungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>, +); +impl< + NonFungible: nonfungible::Mutate + nonfungible::Transfer, + Matcher: MatchesNonFungible, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckingAccount: Get>, + > TransactAsset + for NonFungibleAdapter +{ + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_in(origin, what, context) + } + + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_out(dest, what, context) + } + + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_out(dest, what, context) + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::deposit_asset(what, who, context) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + NonFungibleMutateAdapter::< + NonFungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::withdraw_asset(what, who, maybe_context) + } + + fn transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> result::Result { + NonFungibleTransferAdapter::::transfer_asset( + what, from, to, context, + ) + } +} diff --git a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs index 357dc534a5f115fd2690c4e3cc5b52606e9e239b..3fce953848ebdaf0021d84a333fdb00c346b5096 100644 --- a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs @@ -14,7 +14,7 @@ // 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::fungibles`] through XCM. +//! Adapters to work with [`frame_support::traits::tokens::nonfungibles`] through XCM. use crate::{AssetChecking, MintLocation}; use frame_support::{ @@ -29,6 +29,9 @@ use xcm_executor::traits::{ const LOG_TARGET: &str = "xcm::nonfungibles_adapter"; +/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for +/// handling an asset in the XCM executor. +/// Only works for transfers. pub struct NonFungiblesTransferAdapter( PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>, ); @@ -40,11 +43,11 @@ impl< > TransactAsset for NonFungiblesTransferAdapter { fn transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + what: &Asset, + from: &Location, + to: &Location, context: &XcmContext, - ) -> result::Result { + ) -> result::Result { log::trace!( target: LOG_TARGET, "transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}", @@ -63,6 +66,9 @@ impl< } } +/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for +/// handling an asset in the XCM executor. +/// Only works for teleport bookkeeping. pub struct NonFungiblesMutateAdapter< Assets, Matcher, @@ -131,7 +137,7 @@ impl< CheckingAccount, > { - fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_in(_origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { log::trace!( target: LOG_TARGET, "can_check_in origin: {:?}, what: {:?}, context: {:?}", @@ -150,7 +156,7 @@ impl< } } - fn check_in(_origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_in(_origin: &Location, what: &Asset, context: &XcmContext) { log::trace!( target: LOG_TARGET, "check_in origin: {:?}, what: {:?}, context: {:?}", @@ -169,7 +175,7 @@ impl< } } - fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_out(_dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { log::trace!( target: LOG_TARGET, "can_check_out dest: {:?}, what: {:?}, context: {:?}", @@ -188,7 +194,7 @@ impl< } } - fn check_out(_dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_out(_dest: &Location, what: &Asset, context: &XcmContext) { log::trace!( target: LOG_TARGET, "check_out dest: {:?}, what: {:?}, context: {:?}", @@ -207,11 +213,7 @@ impl< } } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { log::trace!( target: LOG_TARGET, "deposit_asset what: {:?}, who: {:?}, context: {:?}", @@ -228,10 +230,10 @@ impl< } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, maybe_context: Option<&XcmContext>, - ) -> result::Result { + ) -> result::Result { log::trace!( target: LOG_TARGET, "withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}", @@ -249,6 +251,9 @@ impl< } } +/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for +/// handling an asset in the XCM executor. +/// Works for everything. pub struct NonFungiblesAdapter< Assets, Matcher, @@ -267,7 +272,7 @@ impl< > TransactAsset for NonFungiblesAdapter { - fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -278,7 +283,7 @@ impl< >::can_check_in(origin, what, context) } - fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -289,7 +294,7 @@ impl< >::check_in(origin, what, context) } - fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -300,7 +305,7 @@ impl< >::can_check_out(dest, what, context) } - fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -311,11 +316,7 @@ impl< >::check_out(dest, what, context) } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -327,10 +328,10 @@ impl< } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, maybe_context: Option<&XcmContext>, - ) -> result::Result { + ) -> result::Result { NonFungiblesMutateAdapter::< Assets, Matcher, @@ -342,11 +343,11 @@ impl< } fn transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + what: &Asset, + from: &Location, + to: &Location, context: &XcmContext, - ) -> result::Result { + ) -> result::Result { NonFungiblesTransferAdapter::::transfer_asset( what, from, to, context, ) diff --git a/polkadot/xcm/xcm-builder/src/origin_aliases.rs b/polkadot/xcm/xcm-builder/src/origin_aliases.rs index 82c5f71b7a12955d341cf427e747b001fa83ef96..bbf810463a7c5054b368207774c977a49e3232aa 100644 --- a/polkadot/xcm/xcm-builder/src/origin_aliases.rs +++ b/polkadot/xcm/xcm-builder/src/origin_aliases.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Implementation for `ContainsPair`. +//! Implementation for `ContainsPair`. use frame_support::traits::{Contains, ContainsPair}; use sp_std::marker::PhantomData; @@ -25,13 +25,15 @@ use xcm::latest::prelude::*; /// /// Requires that the prefixed origin `AccountId32` matches the target `AccountId32`. pub struct AliasForeignAccountId32(PhantomData); -impl> ContainsPair +impl> ContainsPair for AliasForeignAccountId32 { - fn contains(origin: &MultiLocation, target: &MultiLocation) -> bool { - if let (prefix, Some(account_id @ AccountId32 { .. })) = origin.split_last_interior() { + fn contains(origin: &Location, target: &Location) -> bool { + if let (prefix, Some(account_id @ AccountId32 { .. })) = + origin.clone().split_last_interior() + { return Prefix::contains(&prefix) && - *target == MultiLocation { parents: 0, interior: X1(account_id) } + *target == Location { parents: 0, interior: [account_id].into() } } false } diff --git a/polkadot/xcm/xcm-builder/src/origin_conversion.rs b/polkadot/xcm/xcm-builder/src/origin_conversion.rs index cced7dedf62d234e9bec51f57292a77a6e365351..f64b5660f66748458b6eec7cc3eb02625525de14 100644 --- a/polkadot/xcm/xcm-builder/src/origin_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/origin_conversion.rs @@ -21,7 +21,7 @@ use frame_system::RawOrigin as SystemRawOrigin; use polkadot_parachain_primitives::primitives::IsSystem; use sp_runtime::traits::TryConvert; use sp_std::marker::PhantomData; -use xcm::latest::{BodyId, BodyPart, Junction, Junctions::*, MultiLocation, NetworkId, OriginKind}; +use xcm::latest::{BodyId, BodyPart, Junction, Junctions::*, Location, NetworkId, OriginKind}; use xcm_executor::traits::{ConvertLocation, ConvertOrigin}; /// Sovereign accounts use the system's `Signed` origin with an account ID derived from the @@ -35,9 +35,9 @@ where RuntimeOrigin::AccountId: Clone, { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!( target: "xcm::origin_conversion", @@ -56,9 +56,9 @@ where pub struct ParentAsSuperuser(PhantomData); impl ConvertOrigin for ParentAsSuperuser { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!(target: "xcm::origin_conversion", "ParentAsSuperuser origin: {:?}, kind: {:?}", origin, kind); if kind == OriginKind::Superuser && origin.contains_parents_only(1) { @@ -76,17 +76,16 @@ impl, RuntimeOrigin: OriginTrait> ConvertOrigin { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!(target: "xcm::origin_conversion", "ChildSystemParachainAsSuperuser origin: {:?}, kind: {:?}", origin, kind); - match (kind, origin) { - ( - OriginKind::Superuser, - MultiLocation { parents: 0, interior: X1(Junction::Parachain(id)) }, - ) if ParaId::from(id).is_system() => Ok(RuntimeOrigin::root()), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Superuser, (0, [Junction::Parachain(id)])) + if ParaId::from(*id).is_system() => + Ok(RuntimeOrigin::root()), + _ => Err(origin), } } } @@ -98,21 +97,20 @@ impl, RuntimeOrigin: OriginTrait> ConvertOrigin { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!( target: "xcm::origin_conversion", "SiblingSystemParachainAsSuperuser origin: {:?}, kind: {:?}", origin, kind, ); - match (kind, origin) { - ( - OriginKind::Superuser, - MultiLocation { parents: 1, interior: X1(Junction::Parachain(id)) }, - ) if ParaId::from(id).is_system() => Ok(RuntimeOrigin::root()), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Superuser, (1, [Junction::Parachain(id)])) + if ParaId::from(*id).is_system() => + Ok(RuntimeOrigin::root()), + _ => Err(origin), } } } @@ -124,17 +122,15 @@ impl, RuntimeOrigin: From> ConvertOr for ChildParachainAsNative { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!(target: "xcm::origin_conversion", "ChildParachainAsNative origin: {:?}, kind: {:?}", origin, kind); - match (kind, origin) { - ( - OriginKind::Native, - MultiLocation { parents: 0, interior: X1(Junction::Parachain(id)) }, - ) => Ok(RuntimeOrigin::from(ParachainOrigin::from(id))), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Native, (0, [Junction::Parachain(id)])) => + Ok(RuntimeOrigin::from(ParachainOrigin::from(*id))), + _ => Err(origin), } } } @@ -146,21 +142,19 @@ impl, RuntimeOrigin: From> ConvertOr for SiblingParachainAsNative { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!( target: "xcm::origin_conversion", "SiblingParachainAsNative origin: {:?}, kind: {:?}", origin, kind, ); - match (kind, origin) { - ( - OriginKind::Native, - MultiLocation { parents: 1, interior: X1(Junction::Parachain(id)) }, - ) => Ok(RuntimeOrigin::from(ParachainOrigin::from(id))), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Native, (1, [Junction::Parachain(id)])) => + Ok(RuntimeOrigin::from(ParachainOrigin::from(*id))), + _ => Err(origin), } } } @@ -173,9 +167,9 @@ impl, RuntimeOrigin> ConvertOrigin { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!(target: "xcm::origin_conversion", "RelayChainAsNative origin: {:?}, kind: {:?}", origin, kind); if kind == OriginKind::Native && origin.contains_parents_only(1) { @@ -193,22 +187,20 @@ where RuntimeOrigin::AccountId: From<[u8; 32]>, { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!( target: "xcm::origin_conversion", "SignedAccountId32AsNative origin: {:?}, kind: {:?}", origin, kind, ); - match (kind, origin) { - ( - OriginKind::Native, - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { id, network }) }, - ) if matches!(network, None) || network == Network::get() => - Ok(RuntimeOrigin::signed(id.into())), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Native, (0, [Junction::AccountId32 { id, network }])) + if matches!(network, None) || *network == Network::get() => + Ok(RuntimeOrigin::signed((*id).into())), + _ => Err(origin), } } } @@ -222,34 +214,32 @@ where RuntimeOrigin::AccountId: From<[u8; 20]>, { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { let origin = origin.into(); log::trace!( target: "xcm::origin_conversion", "SignedAccountKey20AsNative origin: {:?}, kind: {:?}", origin, kind, ); - match (kind, origin) { - ( - OriginKind::Native, - MultiLocation { parents: 0, interior: X1(Junction::AccountKey20 { key, network }) }, - ) if (matches!(network, None) || network == Network::get()) => - Ok(RuntimeOrigin::signed(key.into())), - (_, origin) => Err(origin), + match (kind, origin.unpack()) { + (OriginKind::Native, (0, [Junction::AccountKey20 { key, network }])) + if (matches!(network, None) || *network == Network::get()) => + Ok(RuntimeOrigin::signed((*key).into())), + _ => Err(origin), } } } /// `EnsureOrigin` barrier to convert from dispatch origin to XCM origin, if one exists. pub struct EnsureXcmOrigin(PhantomData<(RuntimeOrigin, Conversion)>); -impl> +impl> EnsureOrigin for EnsureXcmOrigin where RuntimeOrigin::PalletsOrigin: PartialEq, { - type Success = MultiLocation; + type Success = Location; fn try_origin(o: RuntimeOrigin) -> Result { let o = match Conversion::try_convert(o) { Ok(location) => return Ok(location), @@ -282,13 +272,12 @@ impl< RuntimeOrigin: OriginTrait + Clone, AccountId: Into<[u8; 32]>, Network: Get>, - > TryConvert - for SignedToAccountId32 + > TryConvert for SignedToAccountId32 where RuntimeOrigin::PalletsOrigin: From> + TryInto, Error = RuntimeOrigin::PalletsOrigin>, { - fn try_convert(o: RuntimeOrigin) -> Result { + fn try_convert(o: RuntimeOrigin) -> Result { o.try_with_caller(|caller| match caller.try_into() { Ok(SystemRawOrigin::Signed(who)) => Ok(Junction::AccountId32 { network: Network::get(), id: who.into() }.into()), @@ -299,7 +288,7 @@ where } /// `Convert` implementation to convert from some an origin which implements `Backing` into a -/// corresponding `Plurality` `MultiLocation`. +/// corresponding `Plurality` `Location`. /// /// Typically used when configuring `pallet-xcm` for allowing a collective's Origin to dispatch an /// XCM from a `Plurality` origin. @@ -307,12 +296,12 @@ pub struct BackingToPlurality( PhantomData<(RuntimeOrigin, COrigin, Body)>, ); impl> - TryConvert for BackingToPlurality + TryConvert for BackingToPlurality where RuntimeOrigin::PalletsOrigin: From + TryInto, { - fn try_convert(o: RuntimeOrigin) -> Result { + fn try_convert(o: RuntimeOrigin) -> Result { o.try_with_caller(|caller| match caller.try_into() { Ok(co) => match co.get_backing() { Some(backing) => Ok(Junction::Plurality { @@ -333,10 +322,10 @@ pub struct OriginToPluralityVoice( PhantomData<(RuntimeOrigin, EnsureBodyOrigin, Body)>, ); impl, Body: Get> - TryConvert + TryConvert for OriginToPluralityVoice { - fn try_convert(o: RuntimeOrigin) -> Result { + fn try_convert(o: RuntimeOrigin) -> Result { match EnsureBodyOrigin::try_origin(o) { Ok(_) => Ok(Junction::Plurality { id: Body::get(), part: BodyPart::Voice }.into()), Err(o) => Err(o), diff --git a/polkadot/xcm/xcm-builder/src/pay.rs b/polkadot/xcm/xcm-builder/src/pay.rs index 4c9b9a6088de87d006ee32c67a60d3da275e3154..6b466483cfad70e52e841b39ef44ebb7362b17cf 100644 --- a/polkadot/xcm/xcm-builder/src/pay.rs +++ b/polkadot/xcm/xcm-builder/src/pay.rs @@ -30,7 +30,7 @@ use xcm_executor::traits::{QueryHandler, QueryResponseStatus}; /// ownership of some `Interior` location of the local chain to a particular `Beneficiary`. The /// `AssetKind` value is not itself bounded (to avoid the issue of needing to wrap some preexisting /// datatype), however a converter type `AssetKindToLocatableAsset` must be provided in order to -/// translate it into a `LocatableAsset`, which comprises both an XCM `MultiLocation` describing +/// translate it into a `LocatableAsset`, which comprises both an XCM `Location` describing /// the XCM endpoint on which the asset to be paid resides and an XCM `AssetId` to identify the /// specific asset at that endpoint. /// @@ -65,14 +65,14 @@ pub struct PayOverXcm< )>, ); impl< - Interior: Get, + Interior: Get, Router: SendXcm, Querier: QueryHandler, Timeout: Get, Beneficiary: Clone, AssetKind, AssetKindToLocatableAsset: TryConvert, - BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, MultiLocation>, + BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>, > Pay for PayOverXcm< Interior, @@ -105,7 +105,7 @@ impl< let beneficiary = BeneficiaryRefToLocation::try_convert(&who) .map_err(|_| xcm::latest::Error::InvalidLocation)?; - let query_id = Querier::new_query(asset_location, Timeout::get(), Interior::get()); + let query_id = Querier::new_query(asset_location.clone(), Timeout::get(), Interior::get()); let message = Xcm(vec![ DescendOrigin(Interior::get()), @@ -120,8 +120,7 @@ impl< ])), TransferAsset { beneficiary, - assets: vec![MultiAsset { id: asset_id, fun: Fungibility::Fungible(amount) }] - .into(), + assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(), }, ]); @@ -195,16 +194,16 @@ pub struct LocatableAssetId { /// The asset's ID. pub asset_id: AssetId, /// The (relative) location in which the asset ID is meaningful. - pub location: MultiLocation, + pub location: Location, } /// Adapter `struct` which implements a conversion from any `AssetKind` into a [`LocatableAssetId`] /// value using a fixed `Location` for the `location` field. -pub struct FixedLocation(sp_std::marker::PhantomData); -impl, AssetKind: Into> TryConvert - for FixedLocation +pub struct FixedLocation(sp_std::marker::PhantomData); +impl, AssetKind: Into> + TryConvert for FixedLocation { fn try_convert(value: AssetKind) -> Result { - Ok(LocatableAssetId { asset_id: value.into(), location: Location::get() }) + Ok(LocatableAssetId { asset_id: value.into(), location: FixedLocationValue::get() }) } } diff --git a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs index dba45a8310aca1cedac249a01eb0ded7435be98a..bcf91d8e68c3389377c84ea85e23ec9835006186 100644 --- a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs +++ b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs @@ -16,22 +16,21 @@ //! 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)>, ); impl< - MessageOrigin: Into + FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug, + MessageOrigin: Into + FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug, XcmExecutor: ExecuteXcm, Call, > ProcessMessage for ProcessXcmMessage @@ -45,20 +44,66 @@ 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 { used } => { + log::trace!( + target: LOG_TARGET, + "XCM message execution complete, used weight: {used}", + ); + (used, Ok(true)) + }, + Outcome::Incomplete { used, error } => { + log::trace!( + target: LOG_TARGET, + "XCM message execution incomplete, used weight: {used}, error: {error:?}", + ); + (used, Ok(false)) + }, // In the error-case we assume the worst case and consume all possible weight. - Outcome::Error(_) => (required, Err(ProcessMessageError::Unsupported)), + Outcome::Error { error } => { + log::trace!( + target: LOG_TARGET, + "XCM message execution error: {error:?}", + ); + (required, Err(ProcessMessageError::Unsupported)) + }, }; meter.consume(consumed); result diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 39e9eab410bf14106b8817a8c2944cd0f5081f36..529ef80c15ff11d3c0d2629aeb4fa9506cb37a28 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -20,6 +20,7 @@ use frame_system::unique; use parity_scale_codec::Encode; use sp_std::{marker::PhantomData, result::Result}; use xcm::prelude::*; +use xcm_executor::{traits::FeeReason, FeesMode}; /// Wrapper router which, if the message does not already end with a `SetTopic` instruction, /// appends one to the message filled with a universally unique ID. This ID is returned from a @@ -38,7 +39,7 @@ impl SendXcm for WithUniqueTopic { type Ticket = (Inner::Ticket, [u8; 32]); fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult { let mut message = message.take().ok_or(SendError::MissingArgument)?; @@ -49,8 +50,7 @@ impl SendXcm for WithUniqueTopic { message.0.push(SetTopic(unique_id)); unique_id }; - let (ticket, assets) = Inner::validate(destination, &mut Some(message)) - .map_err(|_| SendError::NotApplicable)?; + let (ticket, assets) = Inner::validate(destination, &mut Some(message))?; Ok(((ticket, unique_id), assets)) } @@ -83,7 +83,7 @@ impl SendXcm for WithTopicSource, + destination: &mut Option, message: &mut Option>, ) -> SendResult { let mut message = message.take().ok_or(SendError::MissingArgument)?; @@ -105,3 +105,37 @@ impl SendXcm for WithTopicSource (Option, Option); +} + +/// Tuple implementation for `EnsureDelivery`. +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl EnsureDelivery for Tuple { + fn ensure_successful_delivery( + origin_ref: &Location, + dest: &Location, + fee_reason: FeeReason, + ) -> (Option, Option) { + for_tuples!( #( + // If the implementation returns something, we're done; if not, let others try. + match Tuple::ensure_successful_delivery(origin_ref, dest, fee_reason.clone()) { + r @ (Some(_), Some(_)) | r @ (Some(_), None) | r @ (None, Some(_)) => return r, + (None, None) => (), + } + )* ); + // doing nothing + (None, None) + } +} diff --git a/polkadot/xcm/xcm-builder/src/test_utils.rs b/polkadot/xcm/xcm-builder/src/test_utils.rs index d0f867ba62d6af0f642600c8b6aeabc0b9fbcf08..3131dece37570ecda2650c9e03ba44e5346b5f43 100644 --- a/polkadot/xcm/xcm-builder/src/test_utils.rs +++ b/polkadot/xcm/xcm-builder/src/test_utils.rs @@ -27,11 +27,11 @@ pub use xcm_executor::{ traits::{ AssetExchange, AssetLock, ConvertOrigin, Enact, LockError, OnResponse, TransactAsset, }, - Assets, Config, + AssetsInHolding, Config, }; parameter_types! { - pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, Weight)>)> = vec![]; + pub static SubscriptionRequests: Vec<(Location, Option<(QueryId, Weight)>)> = vec![]; pub static MaxAssetsIntoHolding: u32 = 4; } @@ -39,39 +39,39 @@ pub struct TestSubscriptionService; impl VersionChangeNotifier for TestSubscriptionService { fn start( - location: &MultiLocation, + location: &Location, query_id: QueryId, max_weight: Weight, _context: &XcmContext, ) -> XcmResult { let mut r = SubscriptionRequests::get(); - r.push((*location, Some((query_id, max_weight)))); + r.push((location.clone(), Some((query_id, max_weight)))); SubscriptionRequests::set(r); Ok(()) } - fn stop(location: &MultiLocation, _context: &XcmContext) -> XcmResult { + fn stop(location: &Location, _context: &XcmContext) -> XcmResult { let mut r = SubscriptionRequests::get(); r.retain(|(l, _q)| l != location); - r.push((*location, None)); + r.push((location.clone(), None)); SubscriptionRequests::set(r); Ok(()) } - fn is_subscribed(location: &MultiLocation) -> bool { + fn is_subscribed(location: &Location) -> bool { let r = SubscriptionRequests::get(); r.iter().any(|(l, q)| l == location && q.is_some()) } } parameter_types! { - pub static TrappedAssets: Vec<(MultiLocation, MultiAssets)> = vec![]; + pub static TrappedAssets: Vec<(Location, Assets)> = vec![]; } pub struct TestAssetTrap; impl DropAssets for TestAssetTrap { - fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight { - let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get(); - t.push((*origin, assets.into())); + fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight { + let mut t: Vec<(Location, Assets)> = TrappedAssets::get(); + t.push((origin.clone(), assets.into())); TrappedAssets::set(t); Weight::from_parts(5, 5) } @@ -79,13 +79,13 @@ impl DropAssets for TestAssetTrap { impl ClaimAssets for TestAssetTrap { fn claim_assets( - origin: &MultiLocation, - ticket: &MultiLocation, - what: &MultiAssets, + origin: &Location, + ticket: &Location, + what: &Assets, _context: &XcmContext, ) -> bool { - let mut t: Vec<(MultiLocation, MultiAssets)> = TrappedAssets::get(); - if let (0, X1(GeneralIndex(i))) = (ticket.parents, &ticket.interior) { + let mut t: Vec<(Location, Assets)> = TrappedAssets::get(); + if let (0, [GeneralIndex(i)]) = ticket.unpack() { if let Some((l, a)) = t.get(*i as usize) { if l == origin && a == what { t.swap_remove(*i as usize); @@ -102,11 +102,11 @@ pub struct TestAssetExchanger; impl AssetExchange for TestAssetExchanger { fn exchange_asset( - _origin: Option<&MultiLocation>, - _give: Assets, - want: &MultiAssets, + _origin: Option<&Location>, + _give: AssetsInHolding, + want: &Assets, _maximal: bool, - ) -> Result { + ) -> Result { Ok(want.clone().into()) } } @@ -135,17 +135,17 @@ impl PalletsInfoAccess for TestPalletsInfo { } pub struct TestUniversalAliases; -impl Contains<(MultiLocation, Junction)> for TestUniversalAliases { - fn contains(aliases: &(MultiLocation, Junction)) -> bool { +impl Contains<(Location, Junction)> for TestUniversalAliases { + fn contains(aliases: &(Location, Junction)) -> bool { &aliases.0 == &Here.into_location() && &aliases.1 == &GlobalConsensus(ByGenesis([0; 32])) } } parameter_types! { - pub static LockedAssets: Vec<(MultiLocation, MultiAsset)> = vec![]; + pub static LockedAssets: Vec<(Location, Asset)> = vec![]; } -pub struct TestLockTicket(MultiLocation, MultiAsset); +pub struct TestLockTicket(Location, Asset); impl Enact for TestLockTicket { fn enact(self) -> Result<(), LockError> { let mut locked_assets = LockedAssets::get(); @@ -154,7 +154,7 @@ impl Enact for TestLockTicket { Ok(()) } } -pub struct TestUnlockTicket(MultiLocation, MultiAsset); +pub struct TestUnlockTicket(Location, Asset); impl Enact for TestUnlockTicket { fn enact(self) -> Result<(), LockError> { let mut locked_assets = LockedAssets::get(); @@ -183,33 +183,33 @@ impl AssetLock for TestAssetLocker { type ReduceTicket = TestReduceTicket; fn prepare_lock( - unlocker: MultiLocation, - asset: MultiAsset, - _owner: MultiLocation, + unlocker: Location, + asset: Asset, + _owner: Location, ) -> Result { Ok(TestLockTicket(unlocker, asset)) } fn prepare_unlock( - unlocker: MultiLocation, - asset: MultiAsset, - _owner: MultiLocation, + unlocker: Location, + asset: Asset, + _owner: Location, ) -> Result { Ok(TestUnlockTicket(unlocker, asset)) } fn note_unlockable( - _locker: MultiLocation, - _asset: MultiAsset, - _owner: MultiLocation, + _locker: Location, + _asset: Asset, + _owner: Location, ) -> Result<(), LockError> { Ok(()) } fn prepare_reduce_unlockable( - _locker: MultiLocation, - _asset: MultiAsset, - _owner: MultiLocation, + _locker: Location, + _asset: Asset, + _owner: Location, ) -> Result { Ok(TestReduceTicket) } diff --git a/polkadot/xcm/xcm-builder/src/tests/aliases.rs b/polkadot/xcm/xcm-builder/src/tests/aliases.rs index f686926a2522fab1e1564b5ed88161ffb7558734..89c17b09396d02ceb94d8faa290f8bd7294f4737 100644 --- a/polkadot/xcm/xcm-builder/src/tests/aliases.rs +++ b/polkadot/xcm/xcm-builder/src/tests/aliases.rs @@ -66,20 +66,25 @@ fn alias_origin_should_work() { ]); let message = Xcm(vec![AliasOrigin((AccountId32 { network: None, id: [0; 32] }).into())]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( (Parachain(1), AccountId32 { network: None, id: [0; 32] }), message.clone(), - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NoPermission } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NoPermission)); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( (Parent, Parachain(1), AccountId32 { network: None, id: [0; 32] }), message.clone(), - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); } diff --git a/polkadot/xcm/xcm-builder/src/tests/assets.rs b/polkadot/xcm/xcm-builder/src/tests/assets.rs index e1d61a9d1c6daccf729318db6445e30e7bcf774d..b510eab8df53e6c63a487a69520254c08dccecc4 100644 --- a/polkadot/xcm/xcm-builder/src/tests/assets.rs +++ b/polkadot/xcm/xcm-builder/src/tests/assets.rs @@ -32,10 +32,15 @@ fn exchange_asset_should_work() { maximal: true, }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(40, 40) }); assert_eq!(asset_list(Parent), vec![(Here, 100u128).into(), (Parent, 950u128).into()]); assert_eq!(exchange_assets(), vec![(Parent, 50u128).into()].into()); } @@ -56,10 +61,15 @@ fn exchange_asset_without_maximal_should_work() { maximal: false, }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(40, 40) }); assert_eq!(asset_list(Parent), vec![(Here, 50u128).into(), (Parent, 950u128).into()]); assert_eq!(exchange_assets(), vec![(Here, 50u128).into(), (Parent, 50u128).into()].into()); } @@ -80,10 +90,18 @@ fn exchange_asset_should_fail_when_no_deal_possible() { maximal: false, }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm(Parent, message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(40, 40), XcmError::NoDeal)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(40, 40), error: XcmError::NoDeal } + ); assert_eq!(asset_list(Parent), vec![(Parent, 1000u128).into()]); assert_eq!(exchange_assets(), vec![(Here, 100u128).into()].into()); } @@ -100,32 +118,39 @@ fn paying_reserve_deposit_should_work() { BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(50, 50); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(30, 30) }); assert_eq!(asset_list(Here), vec![(Parent, 40u128).into()]); } #[test] fn transfer_should_work() { // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. add_asset(Parachain(1), (Here, 1000)); // They want to transfer 100 of them to their sibling parachain #2 let message = Xcm(vec![TransferAsset { assets: (Here, 100u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); assert_eq!( asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 100u128).into()] @@ -136,27 +161,31 @@ fn transfer_should_work() { #[test] fn reserve_transfer_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. add_asset(Parachain(1), (Here, 1000)); // The remote account owned by gav. - let three: MultiLocation = X1(AccountIndex64 { index: 3, network: None }).into(); + let three: Location = [AccountIndex64 { index: 3, network: None }].into(); // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 // and let them know to hand it to account #3. let message = Xcm(vec![TransferReserveAsset { assets: (Here, 100u128).into(), dest: Parachain(2).into(), - xcm: Xcm::<()>(vec![DepositAsset { assets: AllCounted(1).into(), beneficiary: three }]), + xcm: Xcm::<()>(vec![DepositAsset { + assets: AllCounted(1).into(), + beneficiary: three.clone(), + }]), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let expected_msg = Xcm::<()>(vec![ ReserveAssetDeposited((Parent, 100u128).into()), @@ -171,7 +200,7 @@ fn reserve_transfer_should_work() { #[test] fn burn_should_work() { // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. add_asset(Parachain(1), (Here, 1000)); // They want to burn 100 of them @@ -180,14 +209,15 @@ fn burn_should_work() { BurnAsset((Here, 100u128).into()), DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(30, 30) }); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!(sent_xcm(), vec![]); @@ -197,14 +227,15 @@ fn burn_should_work() { BurnAsset((Here, 1000u128).into()), DepositAsset { assets: Wild(AllCounted(1)), beneficiary: Parachain(1).into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(30, 30) }); assert_eq!(asset_list(Parachain(1)), vec![]); assert_eq!(sent_xcm(), vec![]); } @@ -212,7 +243,7 @@ fn burn_should_work() { #[test] fn basic_asset_trap_should_work() { // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into(), [Parachain(2)].into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. add_asset(Parachain(1), (Here, 1000)); @@ -224,14 +255,15 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(25, 25))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(25, 25) }); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); @@ -243,15 +275,19 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::UnknownClaim } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); assert_eq!(old_trapped_assets, TrappedAssets::get()); @@ -264,15 +300,19 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( Parachain(2), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::UnknownClaim } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); assert_eq!(old_trapped_assets, TrappedAssets::get()); @@ -285,15 +325,19 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let old_trapped_assets = TrappedAssets::get(); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::UnknownClaim } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![]); assert_eq!(old_trapped_assets, TrappedAssets::get()); @@ -305,14 +349,15 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(20, 20) }); assert_eq!(asset_list(Parachain(1)), vec![(Here, 900u128).into()]); assert_eq!( asset_list(AccountIndex64 { index: 3, network: None }), @@ -327,141 +372,168 @@ fn basic_asset_trap_should_work() { beneficiary: AccountIndex64 { index: 3, network: None }.into(), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(20, 20), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::UnknownClaim } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::UnknownClaim)); } #[test] fn max_assets_limit_should_work() { // we'll let them have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(Parachain(1), ([1u8; 32], 1000u128)); - add_asset(Parachain(1), ([2u8; 32], 1000u128)); - add_asset(Parachain(1), ([3u8; 32], 1000u128)); - add_asset(Parachain(1), ([4u8; 32], 1000u128)); - add_asset(Parachain(1), ([5u8; 32], 1000u128)); - add_asset(Parachain(1), ([6u8; 32], 1000u128)); - add_asset(Parachain(1), ([7u8; 32], 1000u128)); - add_asset(Parachain(1), ([8u8; 32], 1000u128)); - add_asset(Parachain(1), ([9u8; 32], 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(0)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(1)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(2)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(3)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(4)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(5)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(6)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(7)]), 1000u128)); + add_asset(Parachain(1), (Junctions::from([GeneralIndex(8)]), 1000u128)); // Attempt to withdraw 8 (=2x4)different assets. This will succeed. let message = Xcm(vec![ - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), - WithdrawAsset(([5u8; 32], 100u128).into()), - WithdrawAsset(([6u8; 32], 100u128).into()), - WithdrawAsset(([7u8; 32], 100u128).into()), - WithdrawAsset(([8u8; 32], 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(4)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(5)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(6)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(7)]), 100u128).into()), ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(100, 100), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(85, 85))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(85, 85) }); // Attempt to withdraw 9 different assets will fail. let message = Xcm(vec![ - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), - WithdrawAsset(([5u8; 32], 100u128).into()), - WithdrawAsset(([6u8; 32], 100u128).into()), - WithdrawAsset(([7u8; 32], 100u128).into()), - WithdrawAsset(([8u8; 32], 100u128).into()), - WithdrawAsset(([9u8; 32], 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(4)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(5)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(6)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(7)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(8)]), 100u128).into()), ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(100, 100), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(95, 95), + error: XcmError::HoldingWouldOverflow + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow)); // Attempt to withdraw 4 different assets and then the same 4 and then a different 4 will // succeed. let message = Xcm(vec![ - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), - WithdrawAsset(([5u8; 32], 100u128).into()), - WithdrawAsset(([6u8; 32], 100u128).into()), - WithdrawAsset(([7u8; 32], 100u128).into()), - WithdrawAsset(([8u8; 32], 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(4)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(5)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(6)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(7)]), 100u128).into()), ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(200, 200), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(125, 125))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(125, 125) }); // Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail. let message = Xcm(vec![ - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), - WithdrawAsset(([5u8; 32], 100u128).into()), - WithdrawAsset(([6u8; 32], 100u128).into()), - WithdrawAsset(([7u8; 32], 100u128).into()), - WithdrawAsset(([8u8; 32], 100u128).into()), - WithdrawAsset(([1u8; 32], 100u128).into()), - WithdrawAsset(([2u8; 32], 100u128).into()), - WithdrawAsset(([3u8; 32], 100u128).into()), - WithdrawAsset(([4u8; 32], 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(4)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(5)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(6)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(7)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(1)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(2)]), 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(3)]), 100u128).into()), ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(200, 200), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(95, 95), + error: XcmError::HoldingWouldOverflow + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(95, 95), XcmError::HoldingWouldOverflow)); // Attempt to withdraw 4 different assets and then a different 4 and then the same 4 will fail. let message = Xcm(vec![ - WithdrawAsset(MultiAssets::from(vec![ - ([1u8; 32], 100u128).into(), - ([2u8; 32], 100u128).into(), - ([3u8; 32], 100u128).into(), - ([4u8; 32], 100u128).into(), - ([5u8; 32], 100u128).into(), - ([6u8; 32], 100u128).into(), - ([7u8; 32], 100u128).into(), - ([8u8; 32], 100u128).into(), + WithdrawAsset(Assets::from(vec![ + (Junctions::from([GeneralIndex(0)]), 100u128).into(), + (Junctions::from([GeneralIndex(1)]), 100u128).into(), + (Junctions::from([GeneralIndex(2)]), 100u128).into(), + (Junctions::from([GeneralIndex(3)]), 100u128).into(), + (Junctions::from([GeneralIndex(4)]), 100u128).into(), + (Junctions::from([GeneralIndex(5)]), 100u128).into(), + (Junctions::from([GeneralIndex(6)]), 100u128).into(), + (Junctions::from([GeneralIndex(7)]), 100u128).into(), ])), - WithdrawAsset(([1u8; 32], 100u128).into()), + WithdrawAsset((Junctions::from([GeneralIndex(0)]), 100u128).into()), ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(200, 200), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(25, 25), + error: XcmError::HoldingWouldOverflow + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(25, 25), XcmError::HoldingWouldOverflow)); } diff --git a/polkadot/xcm/xcm-builder/src/tests/basic.rs b/polkadot/xcm/xcm-builder/src/tests/basic.rs index 02fcd8962dbfb98b7077bfd5ca11203fe31f2581..1482e3d700fd9eef4716417853450e55e2e49624 100644 --- a/polkadot/xcm/xcm-builder/src/tests/basic.rs +++ b/polkadot/xcm/xcm-builder/src/tests/basic.rs @@ -27,14 +27,8 @@ fn basic_setup_works() { assert_eq!(to_account(Parachain(50)), Ok(1050)); assert_eq!(to_account((Parent, Parachain(1))), Ok(2001)); assert_eq!(to_account((Parent, Parachain(50))), Ok(2050)); - assert_eq!( - to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 1, network: None }))), - Ok(1), - ); - assert_eq!( - to_account(MultiLocation::new(0, X1(AccountIndex64 { index: 42, network: None }))), - Ok(42), - ); + assert_eq!(to_account(Location::new(0, [AccountIndex64 { index: 1, network: None }])), Ok(1),); + assert_eq!(to_account(Location::new(0, [AccountIndex64 { index: 42, network: None }])), Ok(42),); assert_eq!(to_account(Here), Ok(3000)); } @@ -65,7 +59,7 @@ fn code_registers_should_work() { SetErrorHandler(Xcm(vec![ TransferAsset { assets: (Here, 2u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, // It was handled fine. ClearError, @@ -73,33 +67,45 @@ fn code_registers_should_work() { // Set the appendix - this will always fire. SetAppendix(Xcm(vec![TransferAsset { assets: (Here, 4u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }])), // First xfer always works ok TransferAsset { assets: (Here, 1u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, // Second xfer results in error on the second message - our error handler will fire. TransferAsset { assets: (Here, 8u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, ]); // Weight limit of 70 is needed. let limit = ::Weigher::weight(&mut message).unwrap(); assert_eq!(limit, Weight::from_parts(70, 70)); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(50, 50))); // We don't pay the 20 weight for the error handler. + let r = XcmExecutor::::prepare_and_execute( + Here, + message.clone(), + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(50, 50) }); // We don't pay the 20 weight for the error handler. assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 13u128).into()]); assert_eq!(asset_list(Here), vec![(Here, 8u128).into()]); assert_eq!(sent_xcm(), vec![]); - let r = XcmExecutor::::execute_xcm(Here, message, hash, limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); // We pay the full weight here. + let r = XcmExecutor::::prepare_and_execute( + Here, + message, + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(70, 70) }); // We pay the full weight here. assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 20u128).into()]); assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]); assert_eq!(sent_xcm(), vec![]); 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 406843a0fe8ae69fe9b9754890c40e9138e45701..ea584bf9d485a25c38ce0b673259b6ed8df7368f 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 @@ -21,13 +21,18 @@ use super::*; parameter_types! { - pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); - pub RemoteUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub UniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(1)].into(); + pub RemoteUniversalLocation: Junctions = [GlobalConsensus(Remote::get()), Parachain(1)].into(); + pub RemoteNetwork: Location = AncestorThen(2, GlobalConsensus(Remote::get())).into(); } type TheBridge = TestBridge>; -type Router = - TestTopic, UniversalLocation>>; +type Router = TestTopic< + UnpaidLocalExporter< + HaulBlobExporter, + UniversalLocation, + >, +>; /// ```nocompile /// local | remote @@ -44,7 +49,7 @@ fn sending_to_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get(), Parachain(1)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Price::get()); assert_eq!(TheBridge::service(), 1); assert_eq!( take_received_remote_messages(), @@ -78,7 +83,7 @@ fn sending_to_parachain_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Price::get()); assert_eq!(TheBridge::service(), 1); let expected = vec![( (Parent, Parachain(1000)).into(), @@ -110,7 +115,7 @@ fn sending_to_relay_chain_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get()).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Price::get()); assert_eq!(TheBridge::service(), 1); let expected = vec![( Parent.into(), 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 02c454bb2129184e14da7b1892de07b6aa6b7d73..38ffe2532d580a017efc21043e7313028700c046 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 @@ -21,13 +21,18 @@ use super::*; parameter_types! { - pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); - pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub UniversalLocation: Junctions = [GlobalConsensus(Local::get())].into(); + pub RemoteUniversalLocation: Junctions = [GlobalConsensus(Remote::get())].into(); + pub RemoteNetwork: Location = AncestorThen(1, GlobalConsensus(Remote::get())).into(); } type TheBridge = TestBridge>; -type Router = - TestTopic, UniversalLocation>>; +type Router = TestTopic< + UnpaidLocalExporter< + HaulBlobExporter, + UniversalLocation, + >, +>; /// ```nocompile /// local | remote @@ -41,7 +46,7 @@ fn sending_to_bridged_chain_works() { let msg = Xcm(vec![Trap(1)]); assert_eq!( send_xcm::((Parent, Remote::get()).into(), msg).unwrap().1, - (Here, 100).into() + Price::get() ); assert_eq!(TheBridge::service(), 1); let expected = vec![( @@ -68,7 +73,7 @@ fn sending_to_parachain_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Remote::get(), Parachain(1000)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, (Here, 100).into()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Price::get()); assert_eq!(TheBridge::service(), 1); let expected = vec![( Parachain(1000).into(), diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs index 45630dbfc2484c23c14a1cbc283c5403a9f56d62..767575e7f2dd9efa29cc1441a8cc2bf2cdaf3d19 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, @@ -32,11 +33,12 @@ mod paid_remote_relay_relay; mod remote_para_para; mod remote_para_para_via_relay; mod remote_relay_relay; +mod universal_exports; parameter_types! { pub Local: NetworkId = ByGenesis([0; 32]); pub Remote: NetworkId = ByGenesis([1; 32]); - pub Price: MultiAssets = MultiAssets::from((Here, 100u128)); + pub Price: Assets = Assets::from((Here, 100u128)); pub static UsingTopic: bool = false; } @@ -91,7 +93,7 @@ impl SendXcm for TestTopic { } } fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult { Ok(if UsingTopic::get() { @@ -119,26 +121,26 @@ impl HaulBlob for TestBridge { } std::thread_local! { - static REMOTE_INCOMING_XCM: RefCell)>> = RefCell::new(Vec::new()); + static REMOTE_INCOMING_XCM: RefCell)>> = RefCell::new(Vec::new()); } struct TestRemoteIncomingRouter; impl SendXcm for TestRemoteIncomingRouter { - type Ticket = (MultiLocation, Xcm<()>); + type Ticket = (Location, Xcm<()>); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>)> { + ) -> SendResult<(Location, Xcm<()>)> { let pair = (dest.take().unwrap(), msg.take().unwrap()); - Ok((pair, MultiAssets::new())) + Ok((pair, Assets::new())) } - fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + fn deliver(pair: (Location, Xcm<()>)) -> Result { let hash = fake_id(); REMOTE_INCOMING_XCM.with(|q| q.borrow_mut().push(pair)); Ok(hash) } } -fn take_received_remote_messages() -> Vec<(MultiLocation, Xcm<()>)> { +fn take_received_remote_messages() -> Vec<(Location, Xcm<()>)> { REMOTE_INCOMING_XCM.with(|r| r.replace(vec![])) } @@ -151,18 +153,18 @@ struct UnpaidExecutingRouter( fn price( n: NetworkId, c: u32, - s: &InteriorMultiLocation, - d: &InteriorMultiLocation, + s: &InteriorLocation, + d: &InteriorLocation, m: &Xcm<()>, -) -> Result { - Ok(validate_export::(n, c, *s, *d, m.clone())?.1) +) -> Result { + Ok(validate_export::(n, c, s.clone(), d.clone(), m.clone())?.1) } fn deliver( n: NetworkId, c: u32, - s: InteriorMultiLocation, - d: InteriorMultiLocation, + s: InteriorLocation, + d: InteriorLocation, m: Xcm<()>, ) -> Result { export_xcm::(n, c, s, d, m).map(|(hash, _)| hash) @@ -188,7 +190,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S type Ticket = Xcm<()>; fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult> { let expect_dest = Remote::get().relative_to(&Local::get()); @@ -196,7 +198,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S return Err(NotApplicable) } let message = message.take().ok_or(MissingArgument)?; - Ok((message, MultiAssets::new())) + Ok((message, Assets::new())) } fn deliver(message: Xcm<()>) -> Result { @@ -205,7 +207,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S // though it is `Remote`. ExecutorUniversalLocation::set(Remote::get()); let origin = Local::get().relative_to(&Remote::get()); - AllowUnpaidFrom::set(vec![origin]); + AllowUnpaidFrom::set(vec![origin.clone()]); set_exporter_override(price::, deliver::); // The we execute it: let mut id = fake_id(); @@ -221,9 +223,9 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S let entry = LogEntry { local, remote, id, message, outcome: outcome.clone(), paid: false }; RoutingLog::mutate(|l| l.push(entry)); match outcome { - Outcome::Complete(..) => Ok(id), - Outcome::Incomplete(..) => Err(Transport("Error executing")), - Outcome::Error(..) => Err(Transport("Unable to execute")), + Outcome::Complete { .. } => Ok(id), + Outcome::Incomplete { .. } => Err(Transport("Error executing")), + Outcome::Error { .. } => Err(Transport("Unable to execute")), } } } @@ -238,7 +240,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S type Ticket = Xcm<()>; fn validate( - destination: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult> { let expect_dest = Remote::get().relative_to(&Local::get()); @@ -246,7 +248,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S return Err(NotApplicable) } let message = message.take().ok_or(MissingArgument)?; - Ok((message, MultiAssets::new())) + Ok((message, Assets::new())) } fn deliver(message: Xcm<()>) -> Result { @@ -255,7 +257,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S // though it is `Remote`. ExecutorUniversalLocation::set(Remote::get()); let origin = Local::get().relative_to(&Remote::get()); - AllowPaidFrom::set(vec![origin]); + AllowPaidFrom::set(vec![origin.clone()]); set_exporter_override(price::, deliver::); // Then we execute it: let mut id = fake_id(); @@ -271,9 +273,9 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S let entry = LogEntry { local, remote, id, message, outcome: outcome.clone(), paid: true }; RoutingLog::mutate(|l| l.push(entry)); match outcome { - Outcome::Complete(..) => Ok(id), - Outcome::Incomplete(..) => Err(Transport("Error executing")), - Outcome::Error(..) => Err(Transport("Unable to execute")), + Outcome::Complete { .. } => Ok(id), + Outcome::Incomplete { .. } => Err(Transport("Error executing")), + Outcome::Error { .. } => Err(Transport("Unable to execute")), } } } 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 45dc2d4a3b9a44c075874e945b5f25453e344912..f9fa0c18c1f51c6682f8b49be9fe51c613e65350 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 @@ -24,23 +24,25 @@ use super::*; 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())); + // 100 to use the bridge (export) and 80 for the remote execution weight (5 instructions x (10 + + // 10) weight each). + pub SendOverBridgePrice: u128 = 200u128 + if UsingTopic::get() { 20 } else { 0 }; + pub UniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(100)].into(); + pub RelayUniversalLocation: Junctions = [GlobalConsensus(Local::get())].into(); + pub RemoteUniversalLocation: Junctions = [GlobalConsensus(Remote::get())].into(); + pub RemoteNetwork: Location = AncestorThen(1, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), None, - MultiLocation::parent(), - Some((Parent, 200u128 + if UsingTopic::get() { 20 } else { 0 }).into()) + Location::parent(), + Some((Parent, SendOverBridgePrice::get()).into()) ) ]; - // ^^^ 100 to use the bridge (export) and 100 for the remote execution weight (5 instructions - // x (10 + 10) weight each). } type TheBridge = TestBridge>; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = ExecutingRouter; type LocalBridgeRouter = SovereignPaidRemoteExporter< NetworkExportTable, @@ -62,13 +64,13 @@ type LocalRouter = TestTopic<(LocalInnerRouter, LocalBridgeRouter)>; #[test] fn sending_to_bridged_chain_works() { maybe_with_topic(|| { - let dest: MultiLocation = (Parent, Parent, Remote::get()).into(); + let dest: Location = (Parent, Parent, Remote::get()).into(); // Initialize the local relay so that our parachain has funds to pay for export. clear_assets(Parachain(100)); add_asset(Parachain(100), (Here, 1000u128)); - let price = 200u128 + if UsingTopic::get() { 20 } else { 0 }; + let price = SendOverBridgePrice::get(); let msg = Xcm(vec![Trap(1)]); assert_eq!(send_xcm::(dest, msg).unwrap().1, (Parent, price).into()); @@ -86,7 +88,7 @@ fn sending_to_bridged_chain_works() { )]; assert_eq!(take_received_remote_messages(), expected); - // The export cost 50 ref time and 50 proof size weight units (and thus 100 units of + // The export cost 40 ref time and 40 proof size weight units (and thus 80 units of // balance). assert_eq!(asset_list(Parachain(100)), vec![(Here, 1000u128 - price).into()]); @@ -97,18 +99,20 @@ fn sending_to_bridged_chain_works() { message: xcm_with_topic( maybe_forward_id_for(&[0; 32]), vec![ - WithdrawAsset(MultiAsset::from((Here, price)).into()), + WithdrawAsset(Asset::from((Here, price)).into()), BuyExecution { fees: (Here, price).into(), weight_limit: Unlimited }, + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Parachain(100).into(), + }])), ExportMessage { network: ByGenesis([1; 32]), destination: Here, xcm: xcm_with_topic([0; 32], vec![Trap(1)]), }, - RefundSurplus, - DepositAsset { assets: Wild(All), beneficiary: Parachain(100).into() }, ], ), - outcome: Outcome::Complete(test_weight(5)), + outcome: Outcome::Complete { used: test_weight(5) }, paid: true, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -116,7 +120,7 @@ fn sending_to_bridged_chain_works() { } #[test] fn sending_to_bridged_chain_without_funds_fails() { - let dest: MultiLocation = (Parent, Parent, Remote::get()).into(); + let dest: Location = (Parent, Parent, Remote::get()).into(); // Routing won't work if we don't have enough funds. assert_eq!( send_xcm::(dest, Xcm(vec![Trap(1)])), @@ -137,13 +141,13 @@ fn sending_to_bridged_chain_without_funds_fails() { #[test] fn sending_to_parachain_of_bridged_chain_works() { maybe_with_topic(|| { - let dest: MultiLocation = (Parent, Parent, Remote::get(), Parachain(100)).into(); + let dest: Location = (Parent, Parent, Remote::get(), Parachain(100)).into(); // Initialize the local relay so that our parachain has funds to pay for export. clear_assets(Parachain(100)); add_asset(Parachain(100), (Here, 1000u128)); - let price = 200u128 + if UsingTopic::get() { 20 } else { 0 }; + let price = SendOverBridgePrice::get(); let msg = Xcm(vec![Trap(1)]); assert_eq!(send_xcm::(dest, msg).unwrap().1, (Parent, price).into()); @@ -161,7 +165,7 @@ fn sending_to_parachain_of_bridged_chain_works() { )]; assert_eq!(take_received_remote_messages(), expected); - // The export cost 50 ref time and 50 proof size weight units (and thus 100 units of + // The export cost 40 ref time and 40 proof size weight units (and thus 80 units of // balance). assert_eq!(asset_list(Parachain(100)), vec![(Here, 1000u128 - price).into()]); @@ -172,18 +176,20 @@ fn sending_to_parachain_of_bridged_chain_works() { message: xcm_with_topic( maybe_forward_id_for(&[0; 32]), vec![ - WithdrawAsset(MultiAsset::from((Here, price)).into()), + WithdrawAsset(Asset::from((Here, price)).into()), BuyExecution { fees: (Here, price).into(), weight_limit: Unlimited }, + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: Parachain(100).into(), + }])), ExportMessage { network: ByGenesis([1; 32]), destination: Parachain(100).into(), xcm: xcm_with_topic([0; 32], vec![Trap(1)]), }, - RefundSurplus, - DepositAsset { assets: Wild(All), beneficiary: Parachain(100).into() }, ], ), - outcome: Outcome::Complete(test_weight(5)), + outcome: Outcome::Complete { used: test_weight(5) }, paid: true, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -191,7 +197,7 @@ fn sending_to_parachain_of_bridged_chain_works() { } #[test] fn sending_to_parachain_of_bridged_chain_without_funds_fails() { - let dest: MultiLocation = (Parent, Parent, Remote::get(), Parachain(100)).into(); + let dest: Location = (Parent, Parent, Remote::get(), Parachain(100)).into(); // Routing won't work if we don't have enough funds. assert_eq!( send_xcm::(dest, Xcm(vec![Trap(1)])), 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..b92c59281c65df0fec769d234cdd881de1ecdbe7 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 @@ -21,9 +21,10 @@ use super::*; 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 UniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(1000)].into(); + pub ParaBridgeUniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(1)].into(); + pub RemoteParaBridgeUniversalLocation: Junctions = [GlobalConsensus(Remote::get()), Parachain(1)].into(); + pub RemoteNetwork: Location = 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 = @@ -61,7 +62,7 @@ fn sending_to_bridged_chain_works() { send_xcm::((Parent, Parent, Remote::get(), Parachain(1)).into(), msg) .unwrap() .1, - MultiAssets::new() + Assets::new() ); assert_eq!(TheBridge::service(), 1); assert_eq!( @@ -93,7 +94,7 @@ fn sending_to_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -115,7 +116,7 @@ fn sending_to_sibling_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Assets::new()); assert_eq!(TheBridge::service(), 1); let expected = vec![( (Parent, Parachain(1000)).into(), @@ -144,7 +145,7 @@ fn sending_to_sibling_of_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -166,7 +167,7 @@ fn sending_to_relay_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get()).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Assets::new()); assert_eq!(TheBridge::service(), 1); let expected = vec![( Parent.into(), @@ -195,7 +196,7 @@ fn sending_to_relay_of_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); 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..1d433628825ded4e1ecf0582f9b0aa4f6a84f260 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 @@ -21,9 +21,10 @@ use super::*; 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 UniversalLocation: Junctions = [GlobalConsensus(Local::get())].into(); + pub ParaBridgeUniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(1)].into(); + pub RemoteParaBridgeUniversalLocation: Junctions = [GlobalConsensus(Remote::get()), Parachain(1)].into(); + pub RemoteNetwork: Location = 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 = @@ -61,7 +62,7 @@ fn sending_to_bridged_chain_works() { send_xcm::((Parent, Remote::get(), Parachain(1)).into(), msg) .unwrap() .1, - MultiAssets::new() + Assets::new() ); assert_eq!(TheBridge::service(), 1); let expected = vec![( @@ -84,7 +85,7 @@ fn sending_to_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -106,7 +107,7 @@ fn sending_to_sibling_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Remote::get(), Parachain(1000)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Assets::new()); assert_eq!(TheBridge::service(), 1); let expected = vec![( (Parent, Parachain(1000)).into(), @@ -128,7 +129,7 @@ fn sending_to_sibling_of_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -150,7 +151,7 @@ fn sending_to_relay_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Remote::get()).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Assets::new()); assert_eq!(TheBridge::service(), 1); let expected = vec![( Parent.into(), @@ -172,7 +173,7 @@ fn sending_to_relay_of_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); 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..d40a941c791643186d5950f0d126d7222ec638d7 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 @@ -21,21 +21,22 @@ use super::*; 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 UniversalLocation: Junctions = [GlobalConsensus(Local::get()), Parachain(1000)].into(); + pub RelayUniversalLocation: Junctions = [GlobalConsensus(Local::get())].into(); + pub RemoteUniversalLocation: Junctions = [GlobalConsensus(Remote::get())].into(); + pub RemoteNetwork: Location = AncestorThen(1, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), None, - MultiLocation::parent(), + Location::parent(), None ) ]; } type TheBridge = TestBridge>; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = UnpaidExecutingRouter; type LocalBridgeRouter = @@ -58,7 +59,7 @@ fn sending_to_bridged_chain_works() { let msg = Xcm(vec![Trap(1)]); assert_eq!( send_xcm::((Parent, Parent, Remote::get()).into(), msg).unwrap().1, - MultiAssets::new() + Assets::new() ); assert_eq!(TheBridge::service(), 1); assert_eq!( @@ -90,7 +91,7 @@ fn sending_to_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); @@ -112,7 +113,7 @@ fn sending_to_parachain_of_bridged_chain_works() { maybe_with_topic(|| { let msg = Xcm(vec![Trap(1)]); let dest = (Parent, Parent, Remote::get(), Parachain(1000)).into(); - assert_eq!(send_xcm::(dest, msg).unwrap().1, MultiAssets::new()); + assert_eq!(send_xcm::(dest, msg).unwrap().1, Assets::new()); assert_eq!(TheBridge::service(), 1); let expected = vec![( Parachain(1000).into(), @@ -141,7 +142,7 @@ fn sending_to_parachain_of_bridged_chain_works() { }, ], ), - outcome: Outcome::Complete(test_weight(2)), + outcome: Outcome::Complete { used: test_weight(2) }, paid: false, }; assert_eq!(RoutingLog::take(), vec![entry]); diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/universal_exports.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/universal_exports.rs new file mode 100644 index 0000000000000000000000000000000000000000..160c6c9af50fd3b966fa70ac504e674e0fa175e9 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/universal_exports.rs @@ -0,0 +1,108 @@ +// 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::test_utils::TrappedAssets; + +#[test] +fn sovereign_paid_remote_exporter_produces_xcm_which_does_not_trap_assets() { + frame_support::parameter_types! { + pub BridgeFeeAsset: Location = Parent.into(); + pub LocalNetwork: NetworkId = ExecutorUniversalLocation::get().global_consensus().expect("valid `NetworkId`"); + pub LocalBridgeLocation: Location = match &ExecutorUniversalLocation::get().split_global() { + Ok((_, junctions)) => Location::new(1, junctions.clone()), + _ => panic!("unexpected location format") + }; + pub RemoteNetwork: NetworkId = ByGenesis([1; 32]); + pub SendOverBridgePrice: u128 = 333; + pub BridgeTable: Vec = vec![ + NetworkExportTableItem::new( + RemoteNetwork::get(), + None, + LocalBridgeLocation::get(), + Some((BridgeFeeAsset::get(), SendOverBridgePrice::get()).into()) + ) + ]; + pub static SenderUniversalLocation: InteriorLocation = (LocalNetwork::get(), Parachain(50)).into(); + } + + // `SovereignPaidRemoteExporter` e.g. used on sibling of `ExecutorUniversalLocation` + type Exporter = SovereignPaidRemoteExporter< + NetworkExportTable, + TestMessageSender, + SenderUniversalLocation, + >; + + // prepare message on sending chain with tested `Exporter` and translate it to the executor + // message type + let message = Exporter::validate( + &mut Some(Location::new(2, [GlobalConsensus(RemoteNetwork::get())])), + &mut Some(Xcm(vec![])), + ) + .expect("valid message"); + let message = Xcm::::from(message.0 .1); + let mut message_id = message.using_encoded(sp_io::hashing::blake2_256); + + // allow origin to pass barrier + let origin = Location::new(1, Parachain(50)); + AllowPaidFrom::set(vec![origin.clone()]); + + // fund origin + add_asset(origin.clone(), (AssetId(BridgeFeeAsset::get()), SendOverBridgePrice::get() * 2)); + WeightPrice::set((BridgeFeeAsset::get().into(), 1_000_000_000_000, 1024 * 1024)); + + // check before + assert!(TrappedAssets::get().is_empty()); + assert_eq!(exported_xcm(), vec![]); + + // execute XCM with overrides for `MessageExporter` behavior to return `Unroutable` error on + // validate + set_exporter_override( + |_, _, _, _, _| Err(SendError::Unroutable), + |_, _, _, _, _| Err(SendError::Transport("not allowed to call here")), + ); + let r = XcmExecutor::::prepare_and_execute( + origin.clone(), + message.clone(), + &mut message_id, + Weight::from_parts(2_000_000_000_000, 2_000_000_000_000), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(50, 50), error: XcmError::Unroutable } + ); + // check empty trapped assets + assert!(TrappedAssets::get().is_empty()); + // no xcm exported + assert_eq!(exported_xcm(), vec![]); + + // execute XCM again with clear `MessageExporter` overrides behavior to expect delivery + clear_exporter_override(); + let r = XcmExecutor::::prepare_and_execute( + origin.clone(), + message, + &mut message_id, + Weight::from_parts(2_000_000_000_000, 2_000_000_000_000), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(50, 50) }); + + // check empty trapped assets + assert!(TrappedAssets::get().is_empty()); + // xcm exported + assert_eq!(exported_xcm().len(), 1); +} diff --git a/polkadot/xcm/xcm-builder/src/tests/expecting.rs b/polkadot/xcm/xcm-builder/src/tests/expecting.rs index 6d5e0ff47b51f11f13e1139c3b1f75479bd70960..1b36ef4517c977d7e5efe2f5f10cf7385a3a5d2d 100644 --- a/polkadot/xcm/xcm-builder/src/tests/expecting.rs +++ b/polkadot/xcm/xcm-builder/src/tests/expecting.rs @@ -18,7 +18,7 @@ use super::*; #[test] fn expect_pallet_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 // and let them know to hand it to account #3. let message = Xcm(vec![ExpectPallet { @@ -28,14 +28,15 @@ fn expect_pallet_should_work() { crate_major: 1, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let message = Xcm(vec![ExpectPallet { index: 1, @@ -44,19 +45,20 @@ fn expect_pallet_should_work() { crate_major: 1, min_crate_minor: 41, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); } #[test] fn expect_pallet_should_fail_correctly() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); let message = Xcm(vec![ExpectPallet { index: 1, name: b"Balances".as_ref().into(), @@ -64,14 +66,21 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 60, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(10, 10), + error: XcmError::VersionIncompatible + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); let message = Xcm(vec![ExpectPallet { index: 1, @@ -80,14 +89,18 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NameMismatch } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); let message = Xcm(vec![ExpectPallet { index: 1, @@ -96,14 +109,18 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NameMismatch } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); let message = Xcm(vec![ExpectPallet { index: 0, @@ -112,14 +129,18 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NameMismatch } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NameMismatch)); let message = Xcm(vec![ExpectPallet { index: 2, @@ -128,14 +149,18 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::PalletNotFound } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::PalletNotFound)); let message = Xcm(vec![ExpectPallet { index: 1, @@ -144,14 +169,21 @@ fn expect_pallet_should_fail_correctly() { crate_major: 2, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(10, 10), + error: XcmError::VersionIncompatible + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); let message = Xcm(vec![ExpectPallet { index: 1, @@ -160,14 +192,21 @@ fn expect_pallet_should_fail_correctly() { crate_major: 0, min_crate_minor: 42, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(10, 10), + error: XcmError::VersionIncompatible + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); let message = Xcm(vec![ExpectPallet { index: 1, @@ -176,12 +215,19 @@ fn expect_pallet_should_fail_correctly() { crate_major: 1, min_crate_minor: 43, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { + used: Weight::from_parts(10, 10), + error: XcmError::VersionIncompatible + } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::VersionIncompatible)); } diff --git a/polkadot/xcm/xcm-builder/src/tests/locking.rs b/polkadot/xcm/xcm-builder/src/tests/locking.rs index f4ef618ac0e73bc6427b29b77bef491397367350..75160e311551e3dd9f0c5066429b04b243b6bf51 100644 --- a/polkadot/xcm/xcm-builder/src/tests/locking.rs +++ b/polkadot/xcm/xcm-builder/src/tests/locking.rs @@ -34,10 +34,15 @@ fn lock_roundtrip_should_work() { ), LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(40, 40) }); assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); let expected_msg = Xcm::<()>(vec![NoteUnlockable { @@ -58,14 +63,15 @@ fn lock_roundtrip_should_work() { // Now we'll unlock it. let message = Xcm(vec![UnlockAsset { asset: (Parent, 100u128).into(), target: (3u64,).into() }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( (Parent, Parachain(1)), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); } #[test] @@ -82,10 +88,15 @@ fn auto_fee_paying_should_work() { SetFeesMode { jit_withdraw: true }, LockAsset { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into() }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(20, 20) }); assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); } @@ -100,10 +111,18 @@ fn lock_should_fail_correctly() { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into(), }]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::LockError } + ); assert_eq!(sent_xcm(), vec![]); assert_eq!(take_lock_trace(), vec![]); @@ -118,10 +137,18 @@ fn lock_should_fail_correctly() { asset: (Parent, 100u128).into(), unlocker: (Parent, Parachain(1)).into(), }]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NotHoldingFees } + ); assert_eq!(sent_xcm(), vec![]); assert_eq!(take_lock_trace(), vec![]); } @@ -140,14 +167,15 @@ fn remote_unlock_roundtrip_should_work() { // This caused Parachain #1 to send us the NoteUnlockable instruction. let message = Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( (Parent, Parachain(1)), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); assert_eq!( take_lock_trace(), vec![Note { @@ -165,10 +193,15 @@ fn remote_unlock_roundtrip_should_work() { ), RequestUnlock { asset: (Parent, 100u128).into(), locker: (Parent, Parachain(1)).into() }, ]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(40, 40) }); assert_eq!(asset_list((3u64,)), vec![(Parent, 990u128).into()]); let expected_msg = Xcm::<()>(vec![UnlockAsset { @@ -201,24 +234,33 @@ fn remote_unlock_should_fail_correctly() { asset: (Parent, 100u128).into(), locker: (Parent, Parachain(1)).into(), }]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::LockError)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::LockError } + ); assert_eq!(sent_xcm(), vec![]); assert_eq!(take_lock_trace(), vec![]); // We have been told by Parachain #1 that Account #3 has locked funds which we can unlock. let message = Xcm(vec![NoteUnlockable { asset: (Parent, 100u128).into(), owner: (3u64,).into() }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( (Parent, Parachain(1)), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let _discard = take_lock_trace(); // We want to unlock 100 of the native parent tokens which were locked for us on parachain. @@ -228,10 +270,18 @@ fn remote_unlock_should_fail_correctly() { asset: (Parent, 100u128).into(), locker: (Parent, Parachain(1)).into(), }]); - let hash = fake_message_hash(&message); - let r = - XcmExecutor::::execute_xcm((3u64,), message, hash, Weight::from_parts(50, 50)); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotHoldingFees)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + (3u64,), + message, + &mut hash, + Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NotHoldingFees } + ); assert_eq!(sent_xcm(), vec![]); assert_eq!(take_lock_trace(), vec![]); diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index 7a7c8837fc1cd7943470b73187aea10df932260b..4bf347ea7713e91b301b8642e1c2d1c8121cee84 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, - }, - ensure, match_types, parameter_types, + dispatch::{DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo}, + ensure, 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}; @@ -49,9 +45,10 @@ pub use xcm_executor::{ AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager, FeeReason, LockError, OnResponse, TransactAsset, }, - Assets, Config, + AssetsInHolding, Config, }; +#[derive(Debug)] pub enum TestOrigin { Root, Relay, @@ -113,52 +110,52 @@ impl GetDispatchInfo for TestCall { } thread_local! { - pub static SENT_XCM: RefCell, XcmHash)>> = RefCell::new(Vec::new()); + pub static SENT_XCM: RefCell, XcmHash)>> = RefCell::new(Vec::new()); pub static EXPORTED_XCM: RefCell< - Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> + Vec<(NetworkId, u32, InteriorLocation, InteriorLocation, Xcm<()>, XcmHash)> > = RefCell::new(Vec::new()); pub static EXPORTER_OVERRIDE: RefCell, - ) -> Result, + ) -> Result, fn( NetworkId, u32, - InteriorMultiLocation, - InteriorMultiLocation, + InteriorLocation, + InteriorLocation, Xcm<()>, ) -> Result, )>> = RefCell::new(None); - pub static SEND_PRICE: RefCell = RefCell::new(MultiAssets::new()); + pub static SEND_PRICE: RefCell = RefCell::new(Assets::new()); pub static SUSPENDED: Cell = Cell::new(false); } -pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { +pub fn sent_xcm() -> Vec<(Location, opaque::Xcm, XcmHash)> { SENT_XCM.with(|q| (*q.borrow()).clone()) } -pub fn set_send_price(p: impl Into) { +pub fn set_send_price(p: impl Into) { SEND_PRICE.with(|l| l.replace(p.into().into())); } pub fn exported_xcm( -) -> Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, opaque::Xcm, XcmHash)> { +) -> Vec<(NetworkId, u32, InteriorLocation, InteriorLocation, opaque::Xcm, XcmHash)> { EXPORTED_XCM.with(|q| (*q.borrow()).clone()) } pub fn set_exporter_override( price: fn( NetworkId, u32, - &InteriorMultiLocation, - &InteriorMultiLocation, + &InteriorLocation, + &InteriorLocation, &Xcm<()>, - ) -> Result, + ) -> Result, deliver: fn( NetworkId, u32, - InteriorMultiLocation, - InteriorMultiLocation, + InteriorLocation, + InteriorLocation, Xcm<()>, ) -> Result, ) { @@ -170,17 +167,17 @@ pub fn clear_exporter_override() { } pub struct TestMessageSender; impl SendXcm for TestMessageSender { - type Ticket = (MultiLocation, Xcm<()>, XcmHash); + type Ticket = (Location, Xcm<()>, XcmHash); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { + ) -> SendResult<(Location, Xcm<()>, XcmHash)> { let msg = msg.take().unwrap(); let hash = fake_message_hash(&msg); let triplet = (dest.take().unwrap(), msg, hash); Ok((triplet, SEND_PRICE.with(|l| l.borrow().clone()))) } - fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { + fn deliver(triplet: (Location, Xcm<()>, XcmHash)) -> Result { let hash = triplet.2; SENT_XCM.with(|q| q.borrow_mut().push(triplet)); Ok(hash) @@ -188,21 +185,20 @@ impl SendXcm for TestMessageSender { } pub struct TestMessageExporter; impl ExportXcm for TestMessageExporter { - type Ticket = (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash); + type Ticket = (NetworkId, u32, InteriorLocation, InteriorLocation, Xcm<()>, XcmHash); fn validate( network: NetworkId, channel: u32, - uni_src: &mut Option, - dest: &mut Option, + uni_src: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> - { + ) -> SendResult<(NetworkId, u32, InteriorLocation, InteriorLocation, Xcm<()>, XcmHash)> { let (s, d, m) = (uni_src.take().unwrap(), dest.take().unwrap(), msg.take().unwrap()); - let r: Result = EXPORTER_OVERRIDE.with(|e| { + let r: Result = EXPORTER_OVERRIDE.with(|e| { if let Some((ref f, _)) = &*e.borrow() { f(network, channel, &s, &d, &m) } else { - Ok(MultiAssets::new()) + Ok(Assets::new()) } }); let h = fake_message_hash(&m); @@ -217,7 +213,7 @@ impl ExportXcm for TestMessageExporter { } } fn deliver( - tuple: (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash), + tuple: (NetworkId, u32, InteriorLocation, InteriorLocation, Xcm<()>, XcmHash), ) -> Result { EXPORTER_OVERRIDE.with(|e| { if let Some((_, ref f)) = &*e.borrow() { @@ -233,37 +229,42 @@ impl ExportXcm for TestMessageExporter { } thread_local! { - pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); + pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); } -pub fn assets(who: impl Into) -> Assets { +pub fn assets(who: impl Into) -> AssetsInHolding { ASSETS.with(|a| a.borrow().get(&who.into()).cloned()).unwrap_or_default() } -pub fn asset_list(who: impl Into) -> Vec { - MultiAssets::from(assets(who)).into_inner() +pub fn asset_list(who: impl Into) -> Vec { + Assets::from(assets(who)).into_inner() } -pub fn add_asset(who: impl Into, what: impl Into) { - ASSETS.with(|a| a.borrow_mut().entry(who.into()).or_insert(Assets::new()).subsume(what.into())); +pub fn add_asset(who: impl Into, what: impl Into) { + ASSETS.with(|a| { + a.borrow_mut() + .entry(who.into()) + .or_insert(AssetsInHolding::new()) + .subsume(what.into()) + }); } -pub fn clear_assets(who: impl Into) { +pub fn clear_assets(who: impl Into) { ASSETS.with(|a| a.borrow_mut().remove(&who.into())); } pub struct TestAssetTransactor; impl TransactAsset for TestAssetTransactor { fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, _context: Option<&XcmContext>, ) -> Result<(), XcmError> { - add_asset(*who, what.clone()); + add_asset(who.clone(), what.clone()); Ok(()) } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, _maybe_context: Option<&XcmContext>, - ) -> Result { + ) -> Result { ASSETS.with(|a| { a.borrow_mut() .get_mut(who) @@ -274,19 +275,20 @@ impl TransactAsset for TestAssetTransactor { } } -pub fn to_account(l: impl Into) -> Result { - Ok(match l.into() { +pub fn to_account(l: impl Into) -> Result { + let l = l.into(); + Ok(match l.unpack() { // Siblings at 2000+id - MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64, + (1, [Parachain(id)]) => 2000 + *id as u64, // Accounts are their number - MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index, + (0, [AccountIndex64 { index, .. }]) => *index, // Children at 1000+id - MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64, + (0, [Parachain(id)]) => 1000 + *id as u64, // Self at 3000 - MultiLocation { parents: 0, interior: Here } => 3000, + (0, []) => 3000, // Parent at 3001 - MultiLocation { parents: 1, interior: Here } => 3001, - l => { + (1, []) => 3001, + _ => { // Is it a foreign-consensus? let uni = ExecutorUniversalLocation::get(); if l.parents as usize != uni.len() { @@ -304,36 +306,35 @@ pub fn to_account(l: impl Into) -> Result { pub struct TestOriginConverter; impl ConvertOrigin for TestOriginConverter { fn convert_origin( - origin: impl Into, + origin: impl Into, kind: OriginKind, - ) -> Result { + ) -> Result { use OriginKind::*; - match (kind, origin.into()) { + let origin = origin.into(); + match (kind, origin.unpack()) { (Superuser, _) => Ok(TestOrigin::Root), - (SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)), - (Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) => - Ok(TestOrigin::Parachain(id)), - (Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay), - (Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) => - Ok(TestOrigin::Signed(index)), - (_, origin) => Err(origin), + (SovereignAccount, _) => Ok(TestOrigin::Signed(to_account(origin)?)), + (Native, (0, [Parachain(id)])) => Ok(TestOrigin::Parachain(*id)), + (Native, (1, [])) => Ok(TestOrigin::Relay), + (Native, (0, [AccountIndex64 { index, .. }])) => Ok(TestOrigin::Signed(*index)), + _ => Err(origin), } } } thread_local! { - pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); - pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); - pub static UNIVERSAL_ALIASES: RefCell> = RefCell::new(BTreeSet::new()); + pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); + pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); + pub static UNIVERSAL_ALIASES: RefCell> = RefCell::new(BTreeSet::new()); } -pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) { +pub fn add_reserve(from: Location, asset: AssetFilter) { IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } #[allow(dead_code)] -pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) { +pub fn add_teleporter(from: Location, asset: AssetFilter) { IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } -pub fn add_universal_alias(bridge: impl Into, consensus: impl Into) { +pub fn add_universal_alias(bridge: impl Into, consensus: impl Into) { UNIVERSAL_ALIASES.with(|r| r.borrow_mut().insert((bridge.into(), consensus.into()))); } pub fn clear_universal_aliases() { @@ -341,29 +342,29 @@ pub fn clear_universal_aliases() { } pub struct TestIsReserve; -impl ContainsPair for TestIsReserve { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl ContainsPair for TestIsReserve { + fn contains(asset: &Asset, origin: &Location) -> bool { IS_RESERVE .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) } } pub struct TestIsTeleporter; -impl ContainsPair for TestIsTeleporter { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl ContainsPair for TestIsTeleporter { + fn contains(asset: &Asset, origin: &Location) -> bool { IS_TELEPORTER .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) } } pub struct TestUniversalAliases; -impl Contains<(MultiLocation, Junction)> for TestUniversalAliases { - fn contains(t: &(MultiLocation, Junction)) -> bool { +impl Contains<(Location, Junction)> for TestUniversalAliases { + fn contains(t: &(Location, Junction)) -> bool { UNIVERSAL_ALIASES.with(|r| r.borrow().contains(t)) } } pub enum ResponseSlot { - Expecting(MultiLocation), + Expecting(Location), Received(Response), } thread_local! { @@ -371,20 +372,16 @@ thread_local! { } pub struct TestResponseHandler; impl OnResponse for TestResponseHandler { - fn expecting_response( - origin: &MultiLocation, - query_id: u64, - _querier: Option<&MultiLocation>, - ) -> bool { + fn expecting_response(origin: &Location, query_id: u64, _querier: Option<&Location>) -> bool { QUERIES.with(|q| match q.borrow().get(&query_id) { Some(ResponseSlot::Expecting(ref l)) => l == origin, _ => false, }) } fn on_response( - _origin: &MultiLocation, + _origin: &Location, query_id: u64, - _querier: Option<&MultiLocation>, + _querier: Option<&Location>, response: xcm::latest::Response, _max_weight: Weight, _context: &XcmContext, @@ -399,7 +396,7 @@ impl OnResponse for TestResponseHandler { Weight::from_parts(10, 10) } } -pub fn expect_response(query_id: u64, from: MultiLocation) { +pub fn expect_response(query_id: u64, from: Location) { QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from))); } pub fn response(query_id: u64) -> Option { @@ -414,7 +411,7 @@ pub fn response(query_id: u64) -> Option { /// Mock implementation of the [`QueryHandler`] trait for creating XCM success queries and expecting /// responses. pub struct TestQueryHandler(core::marker::PhantomData<(T, BlockNumber)>); -impl QueryHandler +impl QueryHandler for TestQueryHandler { type QueryId = u64; @@ -423,9 +420,9 @@ impl QueryHandler type UniversalLocation = T::UniversalLocation; fn new_query( - responder: impl Into, + responder: impl Into, _timeout: Self::BlockNumber, - _match_querier: impl Into, + _match_querier: impl Into, ) -> Self::QueryId { let query_id = 1; expect_response(query_id, responder.into()); @@ -434,7 +431,7 @@ impl QueryHandler fn report_outcome( message: &mut Xcm<()>, - responder: impl Into, + responder: impl Into, timeout: Self::BlockNumber, ) -> Result { let responder = responder.into(); @@ -469,16 +466,16 @@ impl QueryHandler } parameter_types! { - pub static ExecutorUniversalLocation: InteriorMultiLocation + pub static ExecutorUniversalLocation: InteriorLocation = (ByGenesis([0; 32]), Parachain(42)).into(); pub UnitWeightCost: Weight = Weight::from_parts(10, 10); } parameter_types! { // Nothing is allowed to be paid/unpaid by default. - pub static AllowExplicitUnpaidFrom: Vec = vec![]; - pub static AllowUnpaidFrom: Vec = vec![]; - pub static AllowPaidFrom: Vec = vec![]; - pub static AllowSubsFrom: Vec = vec![]; + pub static AllowExplicitUnpaidFrom: Vec = vec![]; + pub static AllowUnpaidFrom: Vec = vec![]; + pub static AllowPaidFrom: Vec = vec![]; + pub static AllowSubsFrom: Vec = vec![]; // 1_000_000_000_000 => 1 unit of asset for 1 unit of ref time weight. // 1024 * 1024 => 1 unit of asset for 1 unit of proof size weight. pub static WeightPrice: (AssetId, u128, u128) = @@ -489,7 +486,7 @@ parameter_types! { pub struct TestSuspender; impl CheckSuspension for TestSuspender { fn is_suspended( - _origin: &MultiLocation, + _origin: &Location, _instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, @@ -523,33 +520,34 @@ pub fn set_fee_waiver(waived: Vec) { pub struct TestFeeManager; impl FeeManager for TestFeeManager { - fn is_waived(_: Option<&MultiLocation>, r: FeeReason) -> bool { + fn is_waived(_: Option<&Location>, r: FeeReason) -> bool { IS_WAIVED.with(|l| l.borrow().contains(&r)) } - fn handle_fee(_: MultiAssets, _: Option<&XcmContext>) {} + + fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} } #[derive(Clone, Eq, PartialEq, Debug)] pub enum LockTraceItem { - Lock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, - Unlock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, - Note { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, - Reduce { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, + Lock { unlocker: Location, asset: Asset, owner: Location }, + Unlock { unlocker: Location, asset: Asset, owner: Location }, + Note { locker: Location, asset: Asset, owner: Location }, + Reduce { locker: Location, asset: Asset, owner: Location }, } thread_local! { pub static NEXT_INDEX: RefCell = RefCell::new(0); pub static LOCK_TRACE: RefCell> = RefCell::new(Vec::new()); - pub static ALLOWED_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); - pub static ALLOWED_REQUEST_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); + pub static ALLOWED_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); + pub static ALLOWED_REQUEST_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); } pub fn take_lock_trace() -> Vec { LOCK_TRACE.with(|l| l.replace(Vec::new())) } pub fn allow_unlock( - unlocker: impl Into, - asset: impl Into, - owner: impl Into, + unlocker: impl Into, + asset: impl Into, + owner: impl Into, ) { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() @@ -559,9 +557,9 @@ pub fn allow_unlock( }); } pub fn disallow_unlock( - unlocker: impl Into, - asset: impl Into, - owner: impl Into, + unlocker: impl Into, + asset: impl Into, + owner: impl Into, ) { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() @@ -570,17 +568,17 @@ pub fn disallow_unlock( .saturating_take(asset.into().into()) }); } -pub fn unlock_allowed(unlocker: &MultiLocation, asset: &MultiAsset, owner: &MultiLocation) -> bool { +pub fn unlock_allowed(unlocker: &Location, asset: &Asset, owner: &Location) -> bool { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() - .get(&(*owner, *unlocker)) + .get(&(owner.clone(), unlocker.clone())) .map_or(false, |x| x.contains_asset(asset)) }) } pub fn allow_request_unlock( - locker: impl Into, - asset: impl Into, - owner: impl Into, + locker: impl Into, + asset: impl Into, + owner: impl Into, ) { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() @@ -590,9 +588,9 @@ pub fn allow_request_unlock( }); } pub fn disallow_request_unlock( - locker: impl Into, - asset: impl Into, - owner: impl Into, + locker: impl Into, + asset: impl Into, + owner: impl Into, ) { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() @@ -601,14 +599,10 @@ pub fn disallow_request_unlock( .saturating_take(asset.into().into()) }); } -pub fn request_unlock_allowed( - locker: &MultiLocation, - asset: &MultiAsset, - owner: &MultiLocation, -) -> bool { +pub fn request_unlock_allowed(locker: &Location, asset: &Asset, owner: &Location) -> bool { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() - .get(&(*owner, *locker)) + .get(&(owner.clone(), locker.clone())) .map_or(false, |x| x.contains_asset(asset)) }) } @@ -618,11 +612,11 @@ impl Enact for TestTicket { fn enact(self) -> Result<(), LockError> { match &self.0 { LockTraceItem::Lock { unlocker, asset, owner } => - allow_unlock(*unlocker, asset.clone(), *owner), + allow_unlock(unlocker.clone(), asset.clone(), owner.clone()), LockTraceItem::Unlock { unlocker, asset, owner } => - disallow_unlock(*unlocker, asset.clone(), *owner), + disallow_unlock(unlocker.clone(), asset.clone(), owner.clone()), LockTraceItem::Reduce { locker, asset, owner } => - disallow_request_unlock(*locker, asset.clone(), *owner), + disallow_request_unlock(locker.clone(), asset.clone(), owner.clone()), _ => {}, } LOCK_TRACE.with(move |l| l.borrow_mut().push(self.0)); @@ -637,38 +631,34 @@ impl AssetLock for TestAssetLock { type ReduceTicket = TestTicket; fn prepare_lock( - unlocker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + unlocker: Location, + asset: Asset, + owner: Location, ) -> Result { - ensure!(assets(owner).contains_asset(&asset), LockError::AssetNotOwned); + ensure!(assets(owner.clone()).contains_asset(&asset), LockError::AssetNotOwned); Ok(TestTicket(LockTraceItem::Lock { unlocker, asset, owner })) } fn prepare_unlock( - unlocker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + unlocker: Location, + asset: Asset, + owner: Location, ) -> Result { ensure!(unlock_allowed(&unlocker, &asset, &owner), LockError::NotLocked); Ok(TestTicket(LockTraceItem::Unlock { unlocker, asset, owner })) } - fn note_unlockable( - locker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, - ) -> Result<(), LockError> { - allow_request_unlock(locker, asset.clone(), owner); + fn note_unlockable(locker: Location, asset: Asset, owner: Location) -> Result<(), LockError> { + allow_request_unlock(locker.clone(), asset.clone(), owner.clone()); let item = LockTraceItem::Note { locker, asset, owner }; LOCK_TRACE.with(move |l| l.borrow_mut().push(item)); Ok(()) } fn prepare_reduce_unlockable( - locker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + locker: Location, + asset: Asset, + owner: Location, ) -> Result { ensure!(request_unlock_allowed(&locker, &asset, &owner), LockError::NotLocked); Ok(TestTicket(LockTraceItem::Reduce { locker, asset, owner })) @@ -676,26 +666,26 @@ impl AssetLock for TestAssetLock { } thread_local! { - pub static EXCHANGE_ASSETS: RefCell = RefCell::new(Assets::new()); + pub static EXCHANGE_ASSETS: RefCell = RefCell::new(AssetsInHolding::new()); } -pub fn set_exchange_assets(assets: impl Into) { +pub fn set_exchange_assets(assets: impl Into) { EXCHANGE_ASSETS.with(|a| a.replace(assets.into().into())); } -pub fn exchange_assets() -> MultiAssets { +pub fn exchange_assets() -> Assets { EXCHANGE_ASSETS.with(|a| a.borrow().clone().into()) } pub struct TestAssetExchange; impl AssetExchange for TestAssetExchange { fn exchange_asset( - _origin: Option<&MultiLocation>, - give: Assets, - want: &MultiAssets, + _origin: Option<&Location>, + give: AssetsInHolding, + want: &Assets, maximal: bool, - ) -> Result { + ) -> Result { let mut have = EXCHANGE_ASSETS.with(|l| l.borrow().clone()); ensure!(have.contains_assets(want), give); let get = if maximal { - std::mem::replace(&mut have, Assets::new()) + std::mem::replace(&mut have, AssetsInHolding::new()) } else { have.saturating_take(want.clone().into()) }; @@ -705,16 +695,25 @@ impl AssetExchange for TestAssetExchange { } } -match_types! { - pub type SiblingPrefix: impl Contains = { - MultiLocation { parents: 1, interior: X1(Parachain(_)) } - }; - pub type ChildPrefix: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(_)) } - }; - pub type ParentPrefix: impl Contains = { - MultiLocation { parents: 1, interior: Here } - }; +pub struct SiblingPrefix; +impl Contains for SiblingPrefix { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (1, [Parachain(_)])) + } +} + +pub struct ChildPrefix; +impl Contains for ChildPrefix { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (0, [Parachain(_)])) + } +} + +pub struct ParentPrefix; +impl Contains for ParentPrefix { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (1, [])) + } } pub struct TestConfig; @@ -743,9 +742,13 @@ impl Config for TestConfig { type CallDispatcher = TestCall; type SafeCallFilter = Everything; type Aliasers = AliasForeignAccountId32; + type TransactionalProcessor = (); + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } -pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { +pub fn fungible_multi_asset(location: Location, amount: u128) -> Asset { (AssetId::from(location), Fungibility::Fungible(amount)).into() } diff --git a/polkadot/xcm/xcm-builder/src/tests/origins.rs b/polkadot/xcm/xcm-builder/src/tests/origins.rs index d3d6278eff8eb19f36b2e5c2964b8cd341595a1b..c717d1e2af8a2f09e910dbf83fe51e6b821a6872 100644 --- a/polkadot/xcm/xcm-builder/src/tests/origins.rs +++ b/polkadot/xcm/xcm-builder/src/tests/origins.rs @@ -18,7 +18,7 @@ use super::*; #[test] fn universal_origin_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into(), X1(Parachain(2)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into(), [Parachain(2)].into()]); clear_universal_aliases(); // Parachain 1 may represent Kusama to us add_universal_alias(Parachain(1), Kusama); @@ -29,48 +29,57 @@ fn universal_origin_should_work() { UniversalOrigin(GlobalConsensus(Kusama)), TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(2), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::InvalidLocation } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::InvalidLocation)); let message = Xcm(vec![ UniversalOrigin(GlobalConsensus(Kusama)), TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(20, 20), error: XcmError::NotWithdrawable } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable)); add_asset((Ancestor(2), GlobalConsensus(Kusama)), (Parent, 100)); let message = Xcm(vec![ UniversalOrigin(GlobalConsensus(Kusama)), TransferAsset { assets: (Parent, 100u128).into(), beneficiary: Here.into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(20, 20) }); assert_eq!(asset_list((Ancestor(2), GlobalConsensus(Kusama))), vec![]); } #[test] fn export_message_should_work() { // Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Local parachain #1 issues a transfer asset on Polkadot Relay-chain, transfering 100 Planck to // Polkadot parachain #2. let expected_message = Xcm(vec![TransferAsset { @@ -83,14 +92,15 @@ fn export_message_should_work() { destination: Here, xcm: expected_message.clone(), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let uni_src = (ByGenesis([0; 32]), Parachain(42), Parachain(1)).into(); assert_eq!( exported_xcm(), @@ -101,40 +111,46 @@ fn export_message_should_work() { #[test] fn unpaid_execution_should_work() { // Bridge chain (assumed to be Relay) lets Parachain #1 have message execution for free. - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // Bridge chain (assumed to be Relay) lets Parachain #2 have message execution for free if it // asks. - AllowExplicitUnpaidFrom::set(vec![X1(Parachain(2)).into()]); + AllowExplicitUnpaidFrom::set(vec![[Parachain(2)].into()]); // Asking for unpaid execution of up to 9 weight on the assumption it is origin of #2. let message = Xcm(vec![UnpaidExecution { weight_limit: Limited(Weight::from_parts(9, 9)), check_origin: Some(Parachain(2).into()), }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message.clone(), - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::BadOrigin } ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::BadOrigin)); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( Parachain(2), message.clone(), - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); let message = Xcm(vec![UnpaidExecution { weight_limit: Limited(Weight::from_parts(10, 10)), check_origin: Some(Parachain(2).into()), }]); - let r = XcmExecutor::::execute_xcm( + let r = XcmExecutor::::prepare_and_execute( Parachain(2), message.clone(), - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 5b6fa3ee5a0b05fbfb868f813b673e749dfaaf18..019113a12b2fb127142c9e59d00ef0faca84b688 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -40,7 +40,7 @@ pub type BlockNumber = u32; pub type AccountId = AccountId32; construct_runtime!( - pub struct Test { + pub enum Test { System: frame_system, Balances: pallet_balances, Assets: pallet_assets, @@ -49,7 +49,7 @@ construct_runtime!( } ); -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; type AccountData = pallet_balances::AccountData; @@ -75,8 +75,8 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -114,14 +114,14 @@ impl pallet_assets::Config for Test { } parameter_types! { - pub const RelayLocation: MultiLocation = Here.into_location(); + pub const RelayLocation: Location = Here.into_location(); pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorMultiLocation = (ByGenesis([0; 32]), Parachain(42)).into(); + pub UniversalLocation: InteriorLocation = (ByGenesis([0; 32]), Parachain(42)).into(); pub UnitWeightCost: u64 = 1_000; pub static AdvertisedXcmVersion: u32 = 3; pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (Concrete(RelayLocation::get()), 1, 1); - pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); + pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (AssetId(RelayLocation::get()), 1, 1); + pub TrustedAssets: (AssetFilter, Location) = (All.into(), Here.into()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub CheckingAccount: AccountId = XcmPallet::check_account(); @@ -129,28 +129,25 @@ parameter_types! { type AssetIdForAssets = u128; -pub struct FromMultiLocationToAsset( - core::marker::PhantomData<(MultiLocation, AssetId)>, -); -impl MaybeEquivalence - for FromMultiLocationToAsset +pub struct FromLocationToAsset(core::marker::PhantomData<(Location, AssetId)>); +impl MaybeEquivalence + for FromLocationToAsset { - fn convert(value: &MultiLocation) -> Option { - match value { - MultiLocation { parents: 0, interior: Here } => Some(0 as AssetIdForAssets), - MultiLocation { parents: 1, interior: Here } => Some(1 as AssetIdForAssets), - MultiLocation { parents: 0, interior: X2(PalletInstance(1), GeneralIndex(index)) } - if ![0, 1].contains(index) => + fn convert(value: &Location) -> Option { + match value.unpack() { + (0, []) => Some(0 as AssetIdForAssets), + (1, []) => Some(1 as AssetIdForAssets), + (0, [PalletInstance(1), GeneralIndex(index)]) if ![0, 1].contains(index) => Some(*index as AssetIdForAssets), _ => None, } } - fn convert_back(value: &AssetIdForAssets) -> Option { + fn convert_back(value: &AssetIdForAssets) -> Option { match value { - 0u128 => Some(MultiLocation { parents: 1, interior: Here }), + 0u128 => Some(Location { parents: 1, interior: Here }), para_id @ 1..=1000 => - Some(MultiLocation { parents: 1, interior: X1(Parachain(*para_id as u32)) }), + Some(Location { parents: 1, interior: [Parachain(*para_id as u32)].into() }), _ => None, } } @@ -162,7 +159,7 @@ pub type LocalAssetsTransactor = FungiblesAdapter< ConvertedConcreteId< AssetIdForAssets, Balance, - FromMultiLocationToAsset, + FromLocationToAsset, JustTry, >, SovereignAccountOf, @@ -177,6 +174,7 @@ type OriginConverter = ( ); type Barrier = AllowUnpaidExecutionFrom; +#[derive(Clone)] pub struct DummyWeightTrader; impl WeightTrader for DummyWeightTrader { fn new() -> Self { @@ -186,10 +184,10 @@ impl WeightTrader for DummyWeightTrader { fn buy_weight( &mut self, _weight: Weight, - _payment: xcm_executor::Assets, + _payment: xcm_executor::AssetsInHolding, _context: &XcmContext, - ) -> Result { - Ok(xcm_executor::Assets::default()) + ) -> Result { + Ok(xcm_executor::AssetsInHolding::default()) } } @@ -219,6 +217,10 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = (); + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } parameter_types! { @@ -227,13 +229,10 @@ parameter_types! { pub struct TreasuryToAccount; impl ConvertLocation for TreasuryToAccount { - fn convert_location(location: &MultiLocation) -> Option { - match location { - MultiLocation { - parents: 1, - interior: - X2(Parachain(42), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }), - } => Some(TreasuryAccountId::get()), // Hardcoded test treasury account id + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (1, [Parachain(42), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]) => + Some(TreasuryAccountId::get()), // Hardcoded test treasury account id _ => None, } } @@ -245,11 +244,6 @@ type SovereignAccountOf = ( HashedDescription>, ); -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(1000).into()); -} - impl pallet_xcm::Config for Test { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -273,8 +267,6 @@ impl pallet_xcm::Config for Test { type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } @@ -283,7 +275,7 @@ pub const INITIAL_BALANCE: Balance = 100 * UNITS; pub const MINIMUM_BALANCE: Balance = 1 * UNITS; pub fn sibling_chain_account_id(para_id: u32, account: [u8; 32]) -> AccountId { - let location: MultiLocation = + let location: Location = (Parent, Parachain(para_id), Junction::AccountId32 { id: account, network: None }).into(); SovereignAccountOf::convert_location(&location).unwrap() } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs index 178b93842736a789913a7fc297d4441432414c03..062faee2abd96a07afda49883adf86e9b2107e5a 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs @@ -22,9 +22,9 @@ use frame_support::{assert_ok, traits::tokens::Pay}; /// Type representing both a location and an asset that is held at that location. /// The id of the held asset is relative to the location where it is being held. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq)] +#[derive(Encode, Decode, Clone, PartialEq, Eq)] pub struct AssetKind { - destination: MultiLocation, + destination: Location, asset_id: AssetId, } @@ -37,8 +37,8 @@ impl sp_runtime::traits::TryConvert for LocatableAs parameter_types! { pub SenderAccount: AccountId = AccountId::new([3u8; 32]); - pub InteriorAccount: InteriorMultiLocation = AccountId32 { id: SenderAccount::get().into(), network: None }.into(); - pub InteriorBody: InteriorMultiLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into(); + pub InteriorAccount: InteriorLocation = AccountId32 { id: SenderAccount::get().into(), network: None }.into(); + pub InteriorBody: InteriorLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into(); pub Timeout: BlockNumber = 5; // 5 blocks } @@ -91,13 +91,19 @@ fn pay_over_xcm_works() { vec![((Parent, Parachain(2)).into(), expected_message, expected_hash)] ); - let (_, message, hash) = sent_xcm()[0].clone(); + let (_, message, mut hash) = sent_xcm()[0].clone(); let message = Xcm::<::RuntimeCall>::from(message.clone()); // Execute message in parachain 2 with parachain 42's origin let origin = (Parent, Parachain(42)); - XcmExecutor::::execute_xcm(origin, message, hash, Weight::MAX); + XcmExecutor::::prepare_and_execute( + origin, + message, + &mut hash, + Weight::MAX, + Weight::zero(), + ); assert_eq!(mock::Assets::balance(0, &recipient), amount); }); } @@ -152,13 +158,19 @@ fn pay_over_xcm_governance_body() { vec![((Parent, Parachain(2)).into(), expected_message, expected_hash)] ); - let (_, message, hash) = sent_xcm()[0].clone(); + let (_, message, mut hash) = sent_xcm()[0].clone(); let message = Xcm::<::RuntimeCall>::from(message.clone()); // Execute message in parachain 2 with parachain 42's origin let origin = (Parent, Parachain(42)); - XcmExecutor::::execute_xcm(origin, message, hash, Weight::MAX); + XcmExecutor::::prepare_and_execute( + origin, + message, + &mut hash, + Weight::MAX, + Weight::zero(), + ); assert_eq!(mock::Assets::balance(relay_asset_index, &recipient), amount); }); } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/salary.rs b/polkadot/xcm/xcm-builder/src/tests/pay/salary.rs index e490fe326b372371dd31f9e554effa986901057a..6a2945c6a9b9b6f4c7ca97dd3a30f9c7be768b9e 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/salary.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/salary.rs @@ -25,9 +25,9 @@ use frame_support::{ use sp_runtime::{traits::ConvertToValue, DispatchResult}; parameter_types! { - pub Interior: InteriorMultiLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into(); + pub Interior: InteriorLocation = Plurality { id: BodyId::Treasury, part: BodyPart::Voice }.into(); pub Timeout: BlockNumber = 5; - pub AssetHub: MultiLocation = (Parent, Parachain(1)).into(); + pub AssetHub: Location = (Parent, Parachain(1)).into(); pub AssetIdGeneralIndex: u128 = 100; pub AssetHubAssetId: AssetId = (PalletInstance(1), GeneralIndex(AssetIdGeneralIndex::get())).into(); pub LocatableAsset: LocatableAssetId = LocatableAssetId { asset_id: AssetHubAssetId::get(), location: AssetHub::get() }; @@ -140,7 +140,7 @@ fn salary_pay_over_xcm_works() { assert_ok!(Salary::payout(RuntimeOrigin::signed(recipient.clone()))); // Get message from mock transport layer - let (_, message, hash) = sent_xcm()[0].clone(); + let (_, message, mut hash) = sent_xcm()[0].clone(); // Change type from `Xcm<()>` to `Xcm` to be able to execute later let message = Xcm::<::RuntimeCall>::from(message.clone()); @@ -164,7 +164,13 @@ fn salary_pay_over_xcm_works() { assert_eq!(message, expected_message); // Execute message as the asset hub - XcmExecutor::::execute_xcm((Parent, Parachain(42)), message, hash, Weight::MAX); + XcmExecutor::::prepare_and_execute( + (Parent, Parachain(42)), + message, + &mut hash, + Weight::MAX, + Weight::zero(), + ); // Recipient receives the payment assert_eq!( diff --git a/polkadot/xcm/xcm-builder/src/tests/querying.rs b/polkadot/xcm/xcm-builder/src/tests/querying.rs index 8fbb55eb25423908194cf891794b6632348c5bd8..3b47073d53df1c092d7cae430285bd05f1a3a294 100644 --- a/polkadot/xcm/xcm-builder/src/tests/querying.rs +++ b/polkadot/xcm/xcm-builder/src/tests/querying.rs @@ -18,7 +18,7 @@ use super::*; #[test] fn pallet_query_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 // and let them know to hand it to account #3. let message = Xcm(vec![QueryPallet { @@ -29,14 +29,15 @@ fn pallet_query_should_work() { max_weight: Weight::from_parts(50, 50), }, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let expected_msg = Xcm::<()>(vec![QueryResponse { query_id: 1, @@ -50,7 +51,7 @@ fn pallet_query_should_work() { #[test] fn pallet_query_with_results_should_work() { - AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); + AllowUnpaidFrom::set(vec![[Parachain(1)].into()]); // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 // and let them know to hand it to account #3. let message = Xcm(vec![QueryPallet { @@ -61,14 +62,15 @@ fn pallet_query_with_results_should_work() { max_weight: Weight::from_parts(50, 50), }, }]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( Parachain(1), message, - hash, + &mut hash, Weight::from_parts(50, 50), + Weight::zero(), ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); let expected_msg = Xcm::<()>(vec![QueryResponse { query_id: 1, @@ -106,15 +108,27 @@ fn prepaid_result_of_query_should_get_free_execution() { max_weight: Weight::from_parts(10, 10), querier: Some(Here.into()), }]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(10, 10); // First time the response gets through since we're expecting it... - let r = XcmExecutor::::execute_xcm(Parent, message.clone(), hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message.clone(), + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); assert_eq!(response(query_id).unwrap(), the_response); // Second time it doesn't, since we're not. - let r = XcmExecutor::::execute_xcm(Parent, message.clone(), hash, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message.clone(), + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); } diff --git a/polkadot/xcm/xcm-builder/src/tests/transacting.rs b/polkadot/xcm/xcm-builder/src/tests/transacting.rs index 743ad7039f7ff30497fd14b4feed6775c6450d1c..a85c8b9986c85b079c4edcecb014e1609b49393b 100644 --- a/polkadot/xcm/xcm-builder/src/tests/transacting.rs +++ b/polkadot/xcm/xcm-builder/src/tests/transacting.rs @@ -25,10 +25,16 @@ fn transacting_should_work() { require_weight_at_most: Weight::from_parts(50, 50), call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), }]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(60, 60) }); } #[test] @@ -40,10 +46,19 @@ fn transacting_should_respect_max_weight_requirement() { require_weight_at_most: Weight::from_parts(40, 40), call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), }]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(50, 50), XcmError::MaxWeightInvalid)); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(50, 50), error: XcmError::MaxWeightInvalid } + ); } #[test] @@ -57,20 +72,26 @@ fn transacting_should_refund_weight() { .encode() .into(), }]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(40, 40))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(40, 40) }); } #[test] fn paid_transacting_should_refund_payment_for_unused_weight() { - let one: MultiLocation = AccountIndex64 { index: 1, network: None }.into(); - AllowPaidFrom::set(vec![one]); + let one: Location = AccountIndex64 { index: 1, network: None }.into(); + AllowPaidFrom::set(vec![one.clone()]); add_asset(AccountIndex64 { index: 1, network: None }, (Parent, 200u128)); WeightPrice::set((Parent.into(), 1_000_000_000_000, 1024 * 1024)); - let origin = one; + let origin = one.clone(); let fees = (Parent, 200u128).into(); let message = Xcm::(vec![ WithdrawAsset((Parent, 200u128).into()), // enough for 200 units of weight. @@ -86,10 +107,16 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { RefundSurplus, DepositAsset { assets: AllCounted(1).into(), beneficiary: one }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(100, 100); - let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(60, 60))); + let r = XcmExecutor::::prepare_and_execute( + origin, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(60, 60) }); assert_eq!( asset_list(AccountIndex64 { index: 1, network: None }), vec![(Parent, 80u128).into()] @@ -112,10 +139,16 @@ fn report_successful_transact_status_should_work() { max_weight: Weight::from_parts(5000, 5000), }), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(70, 70) }); let expected_msg = Xcm(vec![QueryResponse { response: Response::DispatchResult(MaybeErrorCode::Success), query_id: 42, @@ -142,10 +175,16 @@ fn report_failed_transact_status_should_work() { max_weight: Weight::from_parts(5000, 5000), }), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(70, 70) }); let expected_msg = Xcm(vec![QueryResponse { response: Response::DispatchResult(vec![2].into()), query_id: 42, @@ -168,10 +207,16 @@ fn expect_successful_transact_status_should_work() { }, ExpectTransactStatus(MaybeErrorCode::Success), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(70, 70) }); let message = Xcm::(vec![ Transact { @@ -181,10 +226,19 @@ fn expect_successful_transact_status_should_work() { }, ExpectTransactStatus(MaybeErrorCode::Success), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(70, 70), XcmError::ExpectationFalse)); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(70, 70), error: XcmError::ExpectationFalse } + ); } #[test] @@ -199,10 +253,16 @@ fn expect_failed_transact_status_should_work() { }, ExpectTransactStatus(vec![2].into()), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(70, 70))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(70, 70) }); let message = Xcm::(vec![ Transact { @@ -212,10 +272,19 @@ fn expect_failed_transact_status_should_work() { }, ExpectTransactStatus(vec![2].into()), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(70, 70); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(70, 70), XcmError::ExpectationFalse)); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(70, 70), error: XcmError::ExpectationFalse } + ); } #[test] @@ -235,10 +304,16 @@ fn clear_transact_status_should_work() { max_weight: Weight::from_parts(5000, 5000), }), ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(80, 80); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(80, 80))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(80, 80) }); let expected_msg = Xcm(vec![QueryResponse { response: Response::DispatchResult(MaybeErrorCode::Success), query_id: 42, diff --git a/polkadot/xcm/xcm-builder/src/tests/version_subscriptions.rs b/polkadot/xcm/xcm-builder/src/tests/version_subscriptions.rs index 44ab7d34c51bbfc619bb66b08c094775191029d7..e29e3a546615b3aa725d30418e2e173fe67db920 100644 --- a/polkadot/xcm/xcm-builder/src/tests/version_subscriptions.rs +++ b/polkadot/xcm/xcm-builder/src/tests/version_subscriptions.rs @@ -25,23 +25,41 @@ fn simple_version_subscriptions_should_work() { SetAppendix(Xcm(vec![])), SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(20, 20); - let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + let r = XcmExecutor::::prepare_and_execute( + origin, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); let origin = Parachain(1000); let message = Xcm::(vec![SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000), }]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(10, 10); - let r = XcmExecutor::::execute_xcm(origin, message.clone(), hash, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + let r = XcmExecutor::::prepare_and_execute( + origin, + message.clone(), + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); assert_eq!( SubscriptionRequests::get(), @@ -53,33 +71,36 @@ fn simple_version_subscriptions_should_work() { fn version_subscription_instruction_should_work() { let origin = Parachain(1000); let message = Xcm::(vec![ - DescendOrigin(X1(AccountIndex64 { index: 1, network: None })), + DescendOrigin([AccountIndex64 { index: 1, network: None }].into()), SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(20, 20); - let r = XcmExecutor::::execute_xcm_in_credit( + let r = XcmExecutor::::prepare_and_execute( origin, message, - hash, + &mut hash, weight_limit, weight_limit, ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin)); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(20, 20), error: XcmError::BadOrigin } + ); let message = Xcm::(vec![ SetAppendix(Xcm(vec![])), SubscribeVersion { query_id: 42, max_response_weight: Weight::from_parts(5000, 5000) }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm_in_credit( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( origin, message, - hash, + &mut hash, weight_limit, weight_limit, ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(20, 20) }); assert_eq!( SubscriptionRequests::get(), @@ -93,20 +114,38 @@ fn simple_version_unsubscriptions_should_work() { let origin = Parachain(1000); let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(20, 20); - let r = XcmExecutor::::execute_xcm(origin, message, hash, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + let r = XcmExecutor::::prepare_and_execute( + origin, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); let origin = Parachain(1000); let message = Xcm::(vec![UnsubscribeVersion]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(10, 10); - let r = XcmExecutor::::execute_xcm(origin, message.clone(), hash, weight_limit); - assert_eq!(r, Outcome::Error(XcmError::Barrier)); + let r = XcmExecutor::::prepare_and_execute( + origin, + message.clone(), + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Error { error: XcmError::Barrier }); - let r = XcmExecutor::::execute_xcm(Parent, message, hash, weight_limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(10, 10))); + let r = XcmExecutor::::prepare_and_execute( + Parent, + message, + &mut hash, + weight_limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(10, 10) }); assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]); assert_eq!(sent_xcm(), vec![]); @@ -118,31 +157,34 @@ fn version_unsubscription_instruction_should_work() { // Not allowed to do it when origin has been changed. let message = Xcm::(vec![ - DescendOrigin(X1(AccountIndex64 { index: 1, network: None })), + DescendOrigin([AccountIndex64 { index: 1, network: None }].into()), UnsubscribeVersion, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(20, 20); - let r = XcmExecutor::::execute_xcm_in_credit( + let r = XcmExecutor::::prepare_and_execute( origin, message, - hash, + &mut hash, weight_limit, weight_limit, ); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::BadOrigin)); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(20, 20), error: XcmError::BadOrigin } + ); // Fine to do it when origin is untouched. let message = Xcm::(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm_in_credit( + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( origin, message, - hash, + &mut hash, weight_limit, weight_limit, ); - assert_eq!(r, Outcome::Complete(Weight::from_parts(20, 20))); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(20, 20) }); assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]); assert_eq!(sent_xcm(), vec![]); diff --git a/polkadot/xcm/xcm-builder/src/tests/weight.rs b/polkadot/xcm/xcm-builder/src/tests/weight.rs index a2fb265413f546f519e2f11661579fb93b078baa..637e30cce998b88aa8cd53dfb9720f392f8f96d1 100644 --- a/polkadot/xcm/xcm-builder/src/tests/weight.rs +++ b/polkadot/xcm/xcm-builder/src/tests/weight.rs @@ -74,45 +74,78 @@ fn errors_should_return_unused_weight() { // First xfer results in an error on the last message only TransferAsset { assets: (Here, 1u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, // Second xfer results in error third message and after TransferAsset { assets: (Here, 2u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, // Third xfer results in error second message and after TransferAsset { assets: (Here, 4u128).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: None }).into(), + beneficiary: [AccountIndex64 { index: 3, network: None }].into(), }, ]); // Weight limit of 70 is needed. let limit = ::Weigher::weight(&mut message).unwrap(); assert_eq!(limit, Weight::from_parts(30, 30)); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); - assert_eq!(r, Outcome::Complete(Weight::from_parts(30, 30))); + let r = XcmExecutor::::prepare_and_execute( + Here, + message.clone(), + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: Weight::from_parts(30, 30) }); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 7u128).into()]); assert_eq!(asset_list(Here), vec![(Here, 4u128).into()]); assert_eq!(sent_xcm(), vec![]); - let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(30, 30), XcmError::NotWithdrawable)); + let r = XcmExecutor::::prepare_and_execute( + Here, + message.clone(), + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(30, 30), error: XcmError::NotWithdrawable } + ); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 10u128).into()]); assert_eq!(asset_list(Here), vec![(Here, 1u128).into()]); assert_eq!(sent_xcm(), vec![]); - let r = XcmExecutor::::execute_xcm(Here, message.clone(), hash, limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(20, 20), XcmError::NotWithdrawable)); + let r = XcmExecutor::::prepare_and_execute( + Here, + message.clone(), + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(20, 20), error: XcmError::NotWithdrawable } + ); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]); assert_eq!(asset_list(Here), vec![]); assert_eq!(sent_xcm(), vec![]); - let r = XcmExecutor::::execute_xcm(Here, message, hash, limit); - assert_eq!(r, Outcome::Incomplete(Weight::from_parts(10, 10), XcmError::NotWithdrawable)); + let r = XcmExecutor::::prepare_and_execute( + Here, + message, + &mut hash, + limit, + Weight::zero(), + ); + assert_eq!( + r, + Outcome::Incomplete { used: Weight::from_parts(10, 10), error: XcmError::NotWithdrawable } + ); assert_eq!(asset_list(AccountIndex64 { index: 3, network: None }), vec![(Here, 11u128).into()]); assert_eq!(asset_list(Here), vec![]); assert_eq!(sent_xcm(), vec![]); @@ -148,8 +181,8 @@ fn weight_bounds_should_respect_instructions_limit() { #[test] fn weight_trader_tuple_should_work() { - let para_1: MultiLocation = Parachain(1).into(); - let para_2: MultiLocation = Parachain(2).into(); + let para_1: Location = Parachain(1).into(); + let para_2: Location = Parachain(2).into(); parameter_types! { pub static HereWeightPrice: (AssetId, u128, u128) = @@ -186,7 +219,11 @@ fn weight_trader_tuple_should_work() { let mut traders = Traders::new(); // trader one failed; trader two buys weight assert_eq!( - traders.buy_weight(Weight::from_parts(5, 5), fungible_multi_asset(para_1, 10).into(), &ctx), + traders.buy_weight( + Weight::from_parts(5, 5), + fungible_multi_asset(para_1.clone(), 10).into(), + &ctx + ), Ok(vec![].into()), ); // trader two refunds diff --git a/polkadot/xcm/xcm-builder/src/transactional.rs b/polkadot/xcm/xcm-builder/src/transactional.rs new file mode 100644 index 0000000000000000000000000000000000000000..ffe379b0ed41daa299a3ffc01b58176684628b14 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/transactional.rs @@ -0,0 +1,40 @@ +// Copyright 2021 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 frame_support::storage::{with_transaction, TransactionOutcome}; +use sp_runtime::DispatchError; +use xcm::latest::prelude::*; +use xcm_executor::traits::ProcessTransaction; + +/// Transactional processor implementation using frame transactional layers. +pub struct FrameTransactionalProcessor; +impl ProcessTransaction for FrameTransactionalProcessor { + const IS_TRANSACTIONAL: bool = true; + + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>, + { + with_transaction(|| -> TransactionOutcome> { + let output = f(); + match &output { + Ok(()) => TransactionOutcome::Commit(Ok(output)), + _ => TransactionOutcome::Rollback(Ok(output)), + } + }) + .map_err(|_| XcmError::ExceedsStackLimit)? + } +} diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index dbe9571d461ad66f604af593ec7bc66fc9bf3cb2..6e031cdbc270d73c49fcd7960430473a9bb9c9d5 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -28,18 +28,18 @@ use SendError::*; /// chain, itself situated at `universal_local` within the consensus universe. If /// `dest` is not a location in remote consensus, then an error is returned. pub fn ensure_is_remote( - universal_local: impl Into, - dest: impl Into, -) -> Result<(NetworkId, InteriorMultiLocation), MultiLocation> { + universal_local: impl Into, + dest: impl Into, +) -> Result<(NetworkId, InteriorLocation), Location> { let dest = dest.into(); let universal_local = universal_local.into(); let local_net = match universal_local.global_consensus() { Ok(x) => x, Err(_) => return Err(dest), }; - let universal_destination: InteriorMultiLocation = universal_local + let universal_destination: InteriorLocation = universal_local .into_location() - .appended_with(dest) + .appended_with(dest.clone()) .map_err(|x| x.1)? .try_into()?; let (remote_dest, remote_net) = match universal_destination.split_first() { @@ -59,18 +59,18 @@ pub fn ensure_is_remote( pub struct UnpaidLocalExporter( PhantomData<(Exporter, UniversalLocation)>, ); -impl> SendXcm +impl> SendXcm for UnpaidLocalExporter { type Ticket = Exporter::Ticket; fn validate( - dest: &mut Option, + dest: &mut Option, xcm: &mut Option>, ) -> SendResult { let d = dest.take().ok_or(MissingArgument)?; let universal_source = UniversalLocation::get(); - let devolved = match ensure_is_remote(universal_source, d) { + let devolved = match ensure_is_remote(universal_source.clone(), d) { Ok(x) => x, Err(d) => { *dest = Some(d); @@ -96,18 +96,18 @@ pub trait ExporterFor { /// the bridge chain as well as payment for the use of the `ExportMessage` instruction. fn exporter_for( network: &NetworkId, - remote_location: &InteriorMultiLocation, + remote_location: &InteriorLocation, message: &Xcm<()>, - ) -> Option<(MultiLocation, Option)>; + ) -> Option<(Location, Option)>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl ExporterFor for Tuple { fn exporter_for( network: &NetworkId, - remote_location: &InteriorMultiLocation, + remote_location: &InteriorLocation, message: &Xcm<()>, - ) -> Option<(MultiLocation, Option)> { + ) -> Option<(Location, Option)> { for_tuples!( #( if let Some(r) = Tuple::exporter_for(network, remote_location, message) { return Some(r); @@ -125,21 +125,21 @@ pub struct NetworkExportTableItem { /// If `Some`, the requested remote location must be equal to one of the items in the vector. /// These are locations in the remote network. /// If `None`, then the check is skipped. - pub remote_location_filter: Option>, + pub remote_location_filter: Option>, /// Locally-routable bridge with bridging capabilities to the `remote_network` and /// `remote_location`. See [`ExporterFor`] for more details. - pub bridge: MultiLocation, + pub bridge: Location, /// The local payment. /// See [`ExporterFor`] for more details. - pub payment: Option, + pub payment: Option, } impl NetworkExportTableItem { pub fn new( remote_network: NetworkId, - remote_location_filter: Option>, - bridge: MultiLocation, - payment: Option, + remote_location_filter: Option>, + bridge: Location, + payment: Option, ) -> Self { Self { remote_network, remote_location_filter, bridge, payment } } @@ -152,9 +152,9 @@ pub struct NetworkExportTable(sp_std::marker::PhantomData); impl>> ExporterFor for NetworkExportTable { fn exporter_for( network: &NetworkId, - remote_location: &InteriorMultiLocation, + remote_location: &InteriorLocation, _: &Xcm<()>, - ) -> Option<(MultiLocation, Option)> { + ) -> Option<(Location, Option)> { T::get() .into_iter() .find(|item| { @@ -194,16 +194,16 @@ pub fn forward_id_for(original_id: &XcmHash) -> XcmHash { pub struct UnpaidRemoteExporter( PhantomData<(Bridges, Router, UniversalLocation)>, ); -impl> SendXcm +impl> SendXcm for UnpaidRemoteExporter { type Ticket = Router::Ticket; fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, ) -> SendResult { - let d = dest.ok_or(MissingArgument)?; + let d = dest.clone().ok_or(MissingArgument)?; let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -261,17 +261,18 @@ impl( PhantomData<(Bridges, Router, UniversalLocation)>, ); -impl> SendXcm +impl> SendXcm for SovereignPaidRemoteExporter { type Ticket = Router::Ticket; fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, ) -> SendResult { - let d = *dest.as_ref().ok_or(MissingArgument)?; - let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; + let d = dest.as_ref().ok_or(MissingArgument)?; + let devolved = + ensure_is_remote(UniversalLocation::get(), d.clone()).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -299,14 +300,19 @@ impl, } @@ -387,8 +393,8 @@ pub struct BridgeBlobDispatcher( ); impl< Router: SendXcm, - OurPlace: Get, - OurPlaceBridgeInstance: Get>, + OurPlace: Get, + OurPlaceBridgeInstance: Get>, > DispatchBlob for BridgeBlobDispatcher { fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError> { @@ -397,7 +403,7 @@ impl< our_universal.global_consensus().map_err(|()| DispatchBlobError::Unbridgable)?; let BridgeMessage { universal_dest, message } = Decode::decode(&mut &blob[..]).map_err(|_| DispatchBlobError::InvalidEncoding)?; - let universal_dest: InteriorMultiLocation = universal_dest + let universal_dest: InteriorLocation = universal_dest .try_into() .map_err(|_| DispatchBlobError::UnsupportedLocationVersion)?; // `universal_dest` is the desired destination within the universe: first we need to check @@ -423,32 +429,64 @@ 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); fn validate( network: NetworkId, _channel: u32, - universal_source: &mut Option, - destination: &mut Option, + universal_source: &mut Option, + destination: &mut Option, message: &mut Option>, - ) -> Result<((Vec, XcmHash), MultiAssets), SendError> { - let bridged_network = BridgedNetwork::get(); + ) -> Result<((Vec, XcmHash), Assets), SendError> { + let (bridged_network, bridged_network_location_parents) = { + let Location { 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(&Location::from( + AncestorThen(bridged_network_location_parents, d.clone()), + )) + .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)? @@ -463,7 +501,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 = VersionedInteriorLocation::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())) @@ -507,10 +555,10 @@ mod tests { type Ticket = (); fn validate( - _destination: &mut Option, + _destination: &mut Option, _message: &mut Option>, ) -> SendResult { - Ok(((), MultiAssets::new())) + Ok(((), Assets::new())) } fn deliver(_ticket: Self::Ticket) -> Result { @@ -521,10 +569,10 @@ mod tests { /// Generic test case asserting that dest and msg is not consumed by `validate` implementation /// of `SendXcm` in case of expected result. fn ensure_validate_does_not_consume_dest_or_msg( - dest: MultiLocation, + dest: Location, assert_result: impl Fn(SendResult), ) { - let mut dest_wrapper = Some(dest); + let mut dest_wrapper = Some(dest.clone()); let msg = Xcm::<()>::new(); let mut msg_wrapper = Some(msg.clone()); @@ -539,19 +587,19 @@ mod tests { fn remote_exporters_does_not_consume_dest_or_msg_on_not_applicable() { frame_support::parameter_types! { pub Local: NetworkId = ByGenesis([0; 32]); - pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Local::get()), Parachain(1234)); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into(); pub DifferentRemote: NetworkId = ByGenesis([22; 32]); // no routers pub BridgeTable: Vec = vec![]; } // check with local destination (should be remote) - let local_dest = (Parent, Parachain(5678)).into(); - assert!(ensure_is_remote(UniversalLocation::get(), local_dest).is_err()); + let local_dest: Location = (Parent, Parachain(5678)).into(); + assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); ensure_validate_does_not_consume_dest_or_msg::< UnpaidRemoteExporter, OkSender, UniversalLocation>, - >(local_dest, |result| assert_eq!(Err(NotApplicable), result)); + >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< @@ -562,12 +610,12 @@ mod tests { >(local_dest, |result| assert_eq!(Err(NotApplicable), result)); // check with not applicable destination - let remote_dest = (Parent, Parent, DifferentRemote::get()).into(); - assert!(ensure_is_remote(UniversalLocation::get(), remote_dest).is_ok()); + let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into(); + assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); ensure_validate_does_not_consume_dest_or_msg::< UnpaidRemoteExporter, OkSender, UniversalLocation>, - >(remote_dest, |result| assert_eq!(Err(NotApplicable), result)); + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< @@ -582,15 +630,15 @@ mod tests { fn network_export_table_works() { frame_support::parameter_types! { pub NetworkA: NetworkId = ByGenesis([0; 32]); - pub Parachain1000InNetworkA: InteriorMultiLocation = X1(Parachain(1000)); - pub Parachain2000InNetworkA: InteriorMultiLocation = X1(Parachain(2000)); + pub Parachain1000InNetworkA: InteriorLocation = [Parachain(1000)].into(); + pub Parachain2000InNetworkA: InteriorLocation = [Parachain(2000)].into(); pub NetworkB: NetworkId = ByGenesis([1; 32]); - pub BridgeToALocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1234))); - pub BridgeToBLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(4321))); + pub BridgeToALocation: Location = Location::new(1, [Parachain(1234)]); + pub BridgeToBLocation: Location = Location::new(1, [Parachain(4321)]); - pub PaymentForNetworkAAndParachain2000: MultiAsset = (MultiLocation::parent(), 150).into(); + pub PaymentForNetworkAAndParachain2000: Asset = (Location::parent(), 150).into(); pub BridgeTable: sp_std::vec::Vec = sp_std::vec![ // NetworkA allows `Parachain(1000)` as remote location WITHOUT payment. @@ -617,19 +665,19 @@ mod tests { ]; } - let test_data = vec![ - (NetworkA::get(), X1(Parachain(1000)), Some((BridgeToALocation::get(), None))), - (NetworkA::get(), X2(Parachain(1000), GeneralIndex(1)), None), + let test_data: Vec<(NetworkId, InteriorLocation, Option<(Location, Option)>)> = vec![ + (NetworkA::get(), [Parachain(1000)].into(), Some((BridgeToALocation::get(), None))), + (NetworkA::get(), [Parachain(1000), GeneralIndex(1)].into(), None), ( NetworkA::get(), - X1(Parachain(2000)), + [Parachain(2000)].into(), Some((BridgeToALocation::get(), Some(PaymentForNetworkAAndParachain2000::get()))), ), - (NetworkA::get(), X2(Parachain(2000), GeneralIndex(1)), None), - (NetworkA::get(), X1(Parachain(3000)), None), - (NetworkB::get(), X1(Parachain(1000)), Some((BridgeToBLocation::get(), None))), - (NetworkB::get(), X1(Parachain(2000)), Some((BridgeToBLocation::get(), None))), - (NetworkB::get(), X1(Parachain(3000)), Some((BridgeToBLocation::get(), None))), + (NetworkA::get(), [Parachain(2000), GeneralIndex(1)].into(), None), + (NetworkA::get(), [Parachain(3000)].into(), None), + (NetworkB::get(), [Parachain(1000)].into(), Some((BridgeToBLocation::get(), None))), + (NetworkB::get(), [Parachain(2000)].into(), Some((BridgeToBLocation::get(), None))), + (NetworkB::get(), [Parachain(3000)].into(), Some((BridgeToBLocation::get(), None))), ]; for (network, remote_location, expected_result) in test_data { diff --git a/polkadot/xcm/xcm-builder/src/weight.rs b/polkadot/xcm/xcm-builder/src/weight.rs index c16c52939a38bfb62434e7d7c9a994ac54f35dd9..6026218f55316ea950ae5482bb72bfda86c8f511 100644 --- a/polkadot/xcm/xcm-builder/src/weight.rs +++ b/polkadot/xcm/xcm-builder/src/weight.rs @@ -25,10 +25,10 @@ use frame_support::{ use parity_scale_codec::Decode; use sp_runtime::traits::{SaturatedConversion, Saturating, Zero}; use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::{prelude::*, Weight}; +use xcm::latest::{prelude::*, GetWeight, Weight}; use xcm_executor::{ traits::{WeightBounds, WeightTrader}, - Assets, + AssetsInHolding, }; pub struct FixedWeightBounds(PhantomData<(T, C, M)>); @@ -114,16 +114,16 @@ where } /// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but -/// for a `MultiAsset`. Sensible implementations will deposit the asset in some known treasury or +/// for a `Asset`. Sensible implementations will deposit the asset in some known treasury or /// block-author account. pub trait TakeRevenue { - /// Do something with the given `revenue`, which is a single non-wildcard `MultiAsset`. - fn take_revenue(revenue: MultiAsset); + /// Do something with the given `revenue`, which is a single non-wildcard `Asset`. + fn take_revenue(revenue: Asset); } /// Null implementation just burns the revenue. impl TakeRevenue for () { - fn take_revenue(_revenue: MultiAsset) {} + fn take_revenue(_revenue: Asset) {} } /// Simple fee calculator that requires payment in a single fungible at a fixed rate. @@ -143,9 +143,9 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOf fn buy_weight( &mut self, weight: Weight, - payment: Assets, + payment: AssetsInHolding, context: &XcmContext, - ) -> Result { + ) -> Result { log::trace!( target: "xcm::weight", "FixedRateOfFungible::buy_weight weight: {:?}, payment: {:?}, context: {:?}", @@ -165,7 +165,7 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOf Ok(unused) } - fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { log::trace!(target: "xcm::weight", "FixedRateOfFungible::refund_weight weight: {:?}, context: {:?}", weight, context); let (id, units_per_second, units_per_mb) = T::get(); let weight = weight.min(self.0); @@ -194,22 +194,22 @@ impl, R: TakeRevenue> Drop for FixedRateOfFungible /// places any weight bought into the right account. pub struct UsingComponents< WeightToFee: WeightToFeeT, - AssetId: Get, + AssetIdValue: Get, AccountId, Currency: CurrencyT, OnUnbalanced: OnUnbalancedT, >( Weight, Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, + PhantomData<(WeightToFee, AssetIdValue, AccountId, Currency, OnUnbalanced)>, ); impl< WeightToFee: WeightToFeeT, - AssetId: Get, + AssetIdValue: Get, AccountId, Currency: CurrencyT, OnUnbalanced: OnUnbalancedT, - > WeightTrader for UsingComponents + > WeightTrader for UsingComponents { fn new() -> Self { Self(Weight::zero(), Zero::zero(), PhantomData) @@ -218,28 +218,29 @@ impl< fn buy_weight( &mut self, weight: Weight, - payment: Assets, + payment: AssetsInHolding, context: &XcmContext, - ) -> Result { + ) -> Result { log::trace!(target: "xcm::weight", "UsingComponents::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context); let amount = WeightToFee::weight_to_fee(&weight); let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?; - let required = (Concrete(AssetId::get()), u128_amount).into(); + let required = (AssetId(AssetIdValue::get()), u128_amount).into(); let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) } - fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { - log::trace!(target: "xcm::weight", "UsingComponents::refund_weight weight: {:?}, context: {:?}", weight, context); + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + log::trace!(target: "xcm::weight", "UsingComponents::refund_weight weight: {:?}, context: {:?}, available weight: {:?}, available amount: {:?}", weight, context, self.0, self.1); let weight = weight.min(self.0); let amount = WeightToFee::weight_to_fee(&weight); self.0 -= weight; self.1 = self.1.saturating_sub(amount); let amount: u128 = amount.saturated_into(); + log::trace!(target: "xcm::weight", "UsingComponents::refund_weight amount to refund: {:?}", amount); if amount > 0 { - Some((AssetId::get(), amount).into()) + Some((AssetIdValue::get(), amount).into()) } else { None } @@ -247,7 +248,7 @@ impl< } impl< WeightToFee: WeightToFeeT, - AssetId: Get, + AssetId: Get, AccountId, Currency: CurrencyT, OnUnbalanced: OnUnbalancedT, diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 19ced5919517c98b8813cdb2674d6b45f30a7a1b..f3cf5ab264902df83a40948fa3ad28c40571cd3f 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{ConstU32, Everything, Nothing}, weights::Weight, }; @@ -35,33 +35,33 @@ use staging_xcm_builder as xcm_builder; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, - IsChildSystemParachain, IsConcrete, MintLocation, RespectSuspension, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsChildSystemParachain, IsConcrete, + MintLocation, RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, }; pub type AccountId = AccountId32; pub type Balance = u128; thread_local! { - pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); } -pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { +pub fn sent_xcm() -> Vec<(Location, opaque::Xcm, XcmHash)> { SENT_XCM.with(|q| (*q.borrow()).clone()) } pub struct TestSendXcm; impl SendXcm for TestSendXcm { - type Ticket = (MultiLocation, Xcm<()>, XcmHash); + type Ticket = (Location, Xcm<()>, XcmHash); fn validate( - dest: &mut Option, + dest: &mut Option, msg: &mut Option>, - ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { + ) -> SendResult<(Location, Xcm<()>, XcmHash)> { let msg = msg.take().unwrap(); let hash = fake_message_hash(&msg); let triplet = (dest.take().unwrap(), msg, hash); - Ok((triplet, MultiAssets::new())) + Ok((triplet, Assets::new())) } - fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { + fn deliver(triplet: (Location, Xcm<()>, XcmHash)) -> Result { let hash = triplet.2; SENT_XCM.with(|q| q.borrow_mut().push(triplet)); Ok(hash) @@ -76,6 +76,7 @@ parameter_types! { pub const BlockHashCount: u64 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; @@ -119,12 +120,14 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } -impl shared::Config for Runtime {} +impl shared::Config for Runtime { + type DisabledValidators = (); +} impl configuration::Config for Runtime { type WeightInfo = configuration::TestWeightInfo; @@ -132,22 +135,17 @@ impl configuration::Config for Runtime { // aims to closely emulate the Kusama XcmConfig parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::here(); + pub const KsmLocation: Location = Location::here(); pub const KusamaNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorLocation = Here; pub CheckAccount: (AccountId, MintLocation) = (XcmPallet::check_account(), MintLocation::Local); } pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); -pub type LocalCurrencyAdapter = XcmCurrencyAdapter< - Balances, - IsConcrete, - SovereignAccountOf, - AccountId, - CheckAccount, ->; +pub type LocalCurrencyAdapter = + FungibleAdapter, SovereignAccountOf, AccountId, CheckAccount>; pub type LocalAssetTransactor = (LocalCurrencyAdapter,); @@ -171,8 +169,8 @@ pub type Barrier = ( ); parameter_types! { - pub KusamaForAssetHub: (MultiAssetFilter, MultiLocation) = - (Wild(AllOf { id: Concrete(Here.into()), fun: WildFungible }), Parachain(1000).into()); + pub KusamaForAssetHub: (AssetFilter, Location) = + (Wild(AllOf { id: AssetId(Here.into()), fun: WildFungible }), Parachain(1000).into()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 4; } @@ -205,15 +203,14 @@ impl xcm_executor::Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = (); + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type LocalOriginToLocation = SignedToAccountId32; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Here.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type UniversalLocation = UniversalLocation; @@ -238,8 +235,6 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } @@ -250,10 +245,10 @@ type Block = frame_system::mocking::MockBlock; construct_runtime!( pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + System: frame_system, + Balances: pallet_balances, + ParasOrigin: origin, + XcmPallet: pallet_xcm, } ); diff --git a/polkadot/xcm/xcm-builder/tests/scenarios.rs b/polkadot/xcm/xcm-builder/tests/scenarios.rs index 36780b9f0078786a7e061858f30e76eaeac34a96..db37f85acdbbac2edfaa278d33ac7654b0efcee5 100644 --- a/polkadot/xcm/xcm-builder/tests/scenarios.rs +++ b/polkadot/xcm/xcm-builder/tests/scenarios.rs @@ -55,9 +55,15 @@ fn withdraw_and_deposit_works() { beneficiary: Parachain(other_para_id).into(), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); let other_para_acc: AccountId = ParaId::from(other_para_id).into_account_truncating(); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); assert_eq!(Balances::free_balance(other_para_acc), amount); @@ -79,19 +85,19 @@ fn transfer_asset_works() { assets: (Here, amount).into(), beneficiary: AccountId32 { network: None, id: bob.clone().into() }.into(), }]); - let hash = fake_message_hash(&message); - // Use `execute_xcm_in_credit` here to pass through the barrier - let r = XcmExecutor::::execute_xcm_in_credit( + let mut hash = fake_message_hash(&message); + // Use `prepare_and_execute` here to pass through the barrier + let r = XcmExecutor::::prepare_and_execute( AccountId32 { network: None, id: ALICE.into() }, message, - hash, + &mut hash, weight, weight, ); System::assert_last_event( pallet_balances::Event::Transfer { from: ALICE, to: bob.clone(), amount }.into(), ); - assert_eq!(r, Outcome::Complete(weight)); + assert_eq!(r, Outcome::Complete { used: weight }); assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - amount); assert_eq!(Balances::free_balance(bob), INITIAL_BALANCE + amount); }); @@ -129,14 +135,20 @@ fn report_holding_works() { // is not triggered becasue the deposit fails ReportHolding { response_info: response_info.clone(), assets: All.into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); assert_eq!( r, - Outcome::Incomplete( - weight - BaseXcmWeight::get(), - XcmError::FailedToTransactAsset("AccountIdConversionFailed") - ) + Outcome::Incomplete { + used: weight - BaseXcmWeight::get(), + error: XcmError::FailedToTransactAsset("AccountIdConversionFailed") + } ); // there should be no query response sent for the failed deposit assert_eq!(mock::sent_xcm(), vec![]); @@ -153,9 +165,15 @@ fn report_holding_works() { // used to get a notification in case of success ReportHolding { response_info: response_info.clone(), assets: AllCounted(1).into() }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); let other_para_acc: AccountId = ParaId::from(other_para_id).into_account_truncating(); assert_eq!(Balances::free_balance(other_para_acc), amount); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); @@ -209,9 +227,15 @@ fn teleport_to_asset_hub_works() { xcm: Xcm(teleport_effects.clone()), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] .into_iter() .chain(teleport_effects.clone().into_iter()) @@ -232,9 +256,15 @@ fn teleport_to_asset_hub_works() { xcm: Xcm(teleport_effects.clone()), }, ]); - let hash = fake_message_hash(&message); - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let mut hash = fake_message_hash(&message); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); // 2 * amount because of the other teleport above assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] @@ -282,10 +312,16 @@ fn reserve_based_transfer_works() { xcm: Xcm(transfer_effects.clone()), }, ]); - let hash = fake_message_hash(&message); + let mut hash = fake_message_hash(&message); let weight = BaseXcmWeight::get() * 3; - let r = XcmExecutor::::execute_xcm(Parachain(PARA_ID), message, hash, weight); - assert_eq!(r, Outcome::Complete(weight)); + let r = XcmExecutor::::prepare_and_execute( + Parachain(PARA_ID), + message, + &mut hash, + weight, + Weight::zero(), + ); + assert_eq!(r, Outcome::Complete { used: weight }); assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); let expected_msg = Xcm(vec![ReserveAssetDeposited((Parent, amount).into()), ClearOrigin] .into_iter() diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index 902f55901d6c8195ac6af2a44ddb68a9ab177768..71bd58073db6d7247cf5048d4a684aa19ea79bfd 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -4,12 +4,16 @@ description = "An abstract and configurable XCM message executor." authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "7.0.0" + +[lints] +workspace = true [dependencies] impl-trait-for-tuples = "0.2.2" environmental = { version = "1.1.4", default-features = false } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } xcm = { package = "staging-xcm", path = "..", default-features = false } sp-std = { path = "../../../substrate/primitives/std", default-features = false } sp-io = { path = "../../../substrate/primitives/io", default-features = false } @@ -18,21 +22,23 @@ sp-core = { path = "../../../substrate/primitives/core", default-features = fals sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } sp-weights = { path = "../../../substrate/primitives/weights", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } [features] -default = [ "std" ] +default = ["std"] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] std = [ + "environmental/std", "frame-benchmarking/std", "frame-support/std", "log/std", "parity-scale-codec/std", + "scale-info/std", "sp-arithmetic/std", "sp-core/std", "sp-io/std", diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index d869fc6f2dcd2a72d0e20d80a890ebf89a9114cf..1e572e6210a27a7469f827bbe5d076c01c84f032 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -7,15 +7,20 @@ 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 } frame-system = { path = "../../../../substrate/frame/system" } futures = "0.3.21" +pallet-transaction-payment = { path = "../../../../substrate/frame/transaction-payment" } pallet-xcm = { path = "../../pallet-xcm" } polkadot-test-client = { path = "../../../node/test/client" } polkadot-test-runtime = { path = "../../../runtime/test-runtime" } polkadot-test-service = { path = "../../../node/test/service" } +polkadot-service = { path = "../../../node/service" } sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } @@ -23,7 +28,8 @@ sp-state-machine = { path = "../../../../substrate/primitives/state-machine" } xcm = { package = "staging-xcm", path = "../..", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = ".." } sp-tracing = { path = "../../../../substrate/primitives/tracing" } +sp-core = { path = "../../../../substrate/primitives/core" } [features] -default = [ "std" ] -std = [ "frame-support/std", "sp-runtime/std", "xcm/std" ] +default = ["std"] +std = ["frame-support/std", "sp-runtime/std", "xcm/std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index d8c77f8317e1ff961e3a221b01b7ea45db4be80a..da7fc0d97825f24e1eea91e441cecef11a136e5f 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -19,12 +19,14 @@ use codec::Encode; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use polkadot_service::chain_spec::get_account_id_from_seed; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; use polkadot_test_service::construct_extrinsic; +use sp_core::sr25519; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; @@ -65,7 +67,7 @@ fn basic_buy_fees_message_executes() { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { - outcome: Outcome::Complete(_) + outcome: Outcome::Complete { .. } }), ))); }); @@ -76,33 +78,59 @@ fn transact_recursion_limit_works() { sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new().build(); - let mut msg = Xcm(vec![ClearOrigin]); - let max_weight = ::Weigher::weight(&mut msg).unwrap(); - let mut call = polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { - message: Box::new(VersionedXcm::from(msg)), - max_weight, - }); - - for _ in 0..11 { - let mut msg = Xcm(vec![ - WithdrawAsset((Parent, 1_000).into()), - BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, + let base_xcm = |call: polkadot_test_runtime::RuntimeCall| { + Xcm(vec![ + WithdrawAsset((Here, 1_000).into()), + BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited }, Transact { origin_kind: OriginKind::Native, require_weight_at_most: call.get_dispatch_info().weight, call: call.encode().into(), }, - ]); + ]) + }; + let mut call: Option = None; + // set up transacts with recursive depth of 11 + for depth in (1..12).rev() { + let mut msg; + match depth { + // this one should fail with `XcmError::ExceedsStackLimit` + 11 => { + msg = Xcm(vec![ClearOrigin]); + }, + // this one checks that the inner one (depth 11) fails as expected, + // itself should not fail => should have outcome == Complete + 10 => { + let inner_call = call.take().unwrap(); + let expected_transact_status = + sp_runtime::DispatchError::Module(sp_runtime::ModuleError { + index: 27, + error: [24, 0, 0, 0], + message: Some("LocalExecutionIncomplete"), + }) + .encode() + .into(); + msg = base_xcm(inner_call); + msg.inner_mut().push(ExpectTransactStatus(expected_transact_status)); + }, + // these are the outer 9 calls that expect `ExpectTransactStatus(Success)` + d if d >= 1 && d <= 9 => { + let inner_call = call.take().unwrap(); + msg = base_xcm(inner_call); + msg.inner_mut().push(ExpectTransactStatus(MaybeErrorCode::Success)); + }, + _ => unreachable!(), + } let max_weight = ::Weigher::weight(&mut msg).unwrap(); - call = polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { - message: Box::new(VersionedXcm::from(msg)), + call = Some(polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg.clone())), max_weight, - }); + })); } let mut block_builder = client.init_polkadot_block_builder(); - let execute = construct_extrinsic(&client, call, sp_keyring::Sr25519Keyring::Alice, 0); + let execute = construct_extrinsic(&client, call.unwrap(), sp_keyring::Sr25519Keyring::Alice, 0); block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); @@ -113,11 +141,29 @@ fn transact_recursion_limit_works() { .expect("imports the block"); client.state_at(block_hash).expect("state should exist").inspect_state(|| { - assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( - r.event, - polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { - outcome: Outcome::Incomplete(_, XcmError::ExceedsStackLimit) - }), + let events = polkadot_test_runtime::System::events(); + // verify 10 pallet_xcm calls were successful + assert_eq!( + polkadot_test_runtime::System::events() + .iter() + .filter(|r| matches!( + r.event, + polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { .. } + }), + )) + .count(), + 10 + ); + // verify transaction fees have been paid + assert!(events.iter().any(|r| matches!( + &r.event, + polkadot_test_runtime::RuntimeEvent::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: payer, + .. + } + ) if *payer == sp_keyring::Sr25519Keyring::Alice.into(), ))); }); } @@ -198,7 +244,7 @@ fn query_response_fires() { assert_eq!( polkadot_test_runtime::Xcm::query(query_id), Some(QueryStatus::Ready { - response: VersionedResponse::V3(Response::ExecutionResult(None)), + response: VersionedResponse::V4(Response::ExecutionResult(None)), at: 2u32.into() }), ) @@ -270,12 +316,93 @@ fn query_response_elicits_handler() { client.state_at(block_hash).expect("state should exist").inspect_state(|| { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( - r.event, + &r.event, TestNotifier(ResponseReceived( - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, + location, q, Response::ExecutionResult(None), - )) if q == query_id, + )) if *q == query_id && matches!(location.unpack(), (0, [Junction::AccountId32 { .. }])), + ))); + }); +} + +/// Simulates a cross-chain message from Parachain to Parachain through Relay Chain +/// that deposits assets into the reserve of the destination. +/// Regression test for `DepostiReserveAsset` changes in +/// +#[test] +fn deposit_reserve_asset_works_for_any_xcm_sender() { + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new().build(); + + // Init values for the simulated origin Parachain + let amount_to_send: u128 = 1_000_000_000_000; + let assets: Assets = (Parent, amount_to_send).into(); + let fee_asset_item = 0; + let max_assets = assets.len() as u32; + let fees = assets.get(fee_asset_item as usize).unwrap().clone(); + let weight_limit = Unlimited; + let reserve = Location::parent(); + let dest = Location::new(1, [Parachain(2000)]); + let beneficiary_id = get_account_id_from_seed::("Alice"); + let beneficiary = Location::new(0, [AccountId32 { network: None, id: beneficiary_id.into() }]); + + // spends up to half of fees for execution on reserve and other half for execution on + // destination + let fee1 = amount_to_send.saturating_div(2); + let fee2 = amount_to_send.saturating_sub(fee1); + let fees_half_1 = Asset::from((fees.id.clone(), Fungible(fee1))); + let fees_half_2 = Asset::from((fees.id.clone(), Fungible(fee2))); + + let reserve_context = ::UniversalLocation::get(); + // identifies fee item as seen by `reserve` - to be used at reserve chain + let reserve_fees = fees_half_1.reanchored(&reserve, &reserve_context).unwrap(); + // identifies fee item as seen by `dest` - to be used at destination chain + let dest_fees = fees_half_2.reanchored(&dest, &reserve_context).unwrap(); + // identifies assets as seen by `reserve` - to be used at reserve chain + let assets_reanchored = assets.reanchored(&reserve, &reserve_context).unwrap(); + // identifies `dest` as seen by `reserve` + let dest = dest.reanchored(&reserve, &reserve_context).unwrap(); + // xcm to be executed at dest + let xcm_on_dest = Xcm(vec![ + BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + // xcm to be executed at reserve + let msg = Xcm(vec![ + WithdrawAsset(assets_reanchored), + ClearOrigin, + BuyExecution { fees: reserve_fees, weight_limit }, + DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, + ]); + + let mut block_builder = client.init_polkadot_block_builder(); + + // Simulate execution of an incoming XCM message at the reserve chain + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg)), + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + }), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client.state_at(block_hash).expect("state should exist").inspect_state(|| { + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { .. } + }), ))); }); } diff --git a/polkadot/xcm/xcm-executor/src/assets.rs b/polkadot/xcm/xcm-executor/src/assets.rs index 33f2ff218c732ccc04c2950f1798a1ec65a89330..4407752f7024273a28883b695d895bcd6a9e7d35 100644 --- a/polkadot/xcm/xcm-executor/src/assets.rs +++ b/polkadot/xcm/xcm-executor/src/assets.rs @@ -21,16 +21,16 @@ use sp_std::{ prelude::*, }; use xcm::latest::{ - AssetId, AssetInstance, + Asset, AssetFilter, AssetId, AssetInstance, Assets, Fungibility::{Fungible, NonFungible}, - InteriorMultiLocation, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, + InteriorLocation, Location, Reanchorable, + WildAsset::{All, AllCounted, AllOf, AllOfCounted}, WildFungibility::{Fungible as WildFungible, NonFungible as WildNonFungible}, - WildMultiAsset::{All, AllCounted, AllOf, AllOfCounted}, }; -/// List of non-wildcard fungible and non-fungible assets. +/// Map of non-wildcard fungible and non-fungible assets held in the holding register. #[derive(Default, Clone, RuntimeDebug, Eq, PartialEq)] -pub struct Assets { +pub struct AssetsInHolding { /// The fungible assets. pub fungible: BTreeMap, @@ -40,16 +40,16 @@ pub struct Assets { pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, } -impl From for Assets { - fn from(asset: MultiAsset) -> Assets { +impl From for AssetsInHolding { + fn from(asset: Asset) -> AssetsInHolding { let mut result = Self::default(); result.subsume(asset); result } } -impl From> for Assets { - fn from(assets: Vec) -> Assets { +impl From> for AssetsInHolding { + fn from(assets: Vec) -> AssetsInHolding { let mut result = Self::default(); for asset in assets.into_iter() { result.subsume(asset) @@ -58,21 +58,21 @@ impl From> for Assets { } } -impl From for Assets { - fn from(assets: MultiAssets) -> Assets { +impl From for AssetsInHolding { + fn from(assets: Assets) -> AssetsInHolding { assets.into_inner().into() } } -impl From for Vec { - fn from(a: Assets) -> Self { +impl From for Vec { + fn from(a: AssetsInHolding) -> Self { a.into_assets_iter().collect() } } -impl From for MultiAssets { - fn from(a: Assets) -> Self { - a.into_assets_iter().collect::>().into() +impl From for Assets { + fn from(a: AssetsInHolding) -> Self { + a.into_assets_iter().collect::>().into() } } @@ -80,10 +80,10 @@ impl From for MultiAssets { #[derive(Debug)] pub enum TakeError { /// There was an attempt to take an asset without saturating (enough of) which did not exist. - AssetUnderflow(MultiAsset), + AssetUnderflow(Asset), } -impl Assets { +impl AssetsInHolding { /// New value, containing no assets. pub fn new() -> Self { Self::default() @@ -100,41 +100,41 @@ impl Assets { } /// A borrowing iterator over the fungible assets. - pub fn fungible_assets_iter(&self) -> impl Iterator + '_ { + pub fn fungible_assets_iter(&self) -> impl Iterator + '_ { self.fungible .iter() - .map(|(id, &amount)| MultiAsset { fun: Fungible(amount), id: *id }) + .map(|(id, &amount)| Asset { fun: Fungible(amount), id: id.clone() }) } /// A borrowing iterator over the non-fungible assets. - pub fn non_fungible_assets_iter(&self) -> impl Iterator + '_ { + pub fn non_fungible_assets_iter(&self) -> impl Iterator + '_ { self.non_fungible .iter() - .map(|(id, instance)| MultiAsset { fun: NonFungible(*instance), id: *id }) + .map(|(id, instance)| Asset { fun: NonFungible(*instance), id: id.clone() }) } /// A consuming iterator over all assets. - pub fn into_assets_iter(self) -> impl Iterator { + pub fn into_assets_iter(self) -> impl Iterator { self.fungible .into_iter() - .map(|(id, amount)| MultiAsset { fun: Fungible(amount), id }) + .map(|(id, amount)| Asset { fun: Fungible(amount), id }) .chain( self.non_fungible .into_iter() - .map(|(id, instance)| MultiAsset { fun: NonFungible(instance), id }), + .map(|(id, instance)| Asset { fun: NonFungible(instance), id }), ) } /// A borrowing iterator over all assets. - pub fn assets_iter(&self) -> impl Iterator + '_ { + pub fn assets_iter(&self) -> impl Iterator + '_ { self.fungible_assets_iter().chain(self.non_fungible_assets_iter()) } /// Mutate `self` to contain all given `assets`, saturating if necessary. /// - /// NOTE: [`Assets`] are always sorted, allowing us to optimize this function from `O(n^2)` to - /// `O(n)`. - pub fn subsume_assets(&mut self, mut assets: Assets) { + /// NOTE: [`AssetsInHolding`] are always sorted, allowing us to optimize this function from + /// `O(n^2)` to `O(n)`. + pub fn subsume_assets(&mut self, mut assets: AssetsInHolding) { let mut f_iter = assets.fungible.iter_mut(); let mut g_iter = self.fungible.iter_mut(); if let (Some(mut f), Some(mut g)) = (f_iter.next(), g_iter.next()) { @@ -166,7 +166,7 @@ impl Assets { /// Mutate `self` to contain the given `asset`, saturating if necessary. /// /// Wildcard values of `asset` do nothing. - pub fn subsume(&mut self, asset: MultiAsset) { + pub fn subsume(&mut self, asset: Asset) { match asset.fun { Fungible(amount) => { self.fungible @@ -180,18 +180,18 @@ impl Assets { } } - /// Swaps two mutable Assets, without deinitializing either one. - pub fn swapped(&mut self, mut with: Assets) -> Self { + /// Swaps two mutable AssetsInHolding, without deinitializing either one. + pub fn swapped(&mut self, mut with: AssetsInHolding) -> Self { mem::swap(&mut *self, &mut with); with } - /// Alter any concretely identified assets by prepending the given `MultiLocation`. + /// Alter any concretely identified assets by prepending the given `Location`. /// /// WARNING: For now we consider this infallible and swallow any errors. It is thus the caller's /// responsibility to ensure that any internal asset IDs are able to be prepended without /// overflow. - pub fn prepend_location(&mut self, prepend: &MultiLocation) { + pub fn prepend_location(&mut self, prepend: &Location) { let mut fungible = Default::default(); mem::swap(&mut self.fungible, &mut fungible); self.fungible = fungible @@ -218,8 +218,8 @@ impl Assets { /// Any assets which were unable to be reanchored are introduced into `failed_bin`. pub fn reanchor( &mut self, - target: &MultiLocation, - context: InteriorMultiLocation, + target: &Location, + context: &InteriorLocation, mut maybe_failed_bin: Option<&mut Self>, ) { let mut fungible = Default::default(); @@ -249,22 +249,22 @@ impl Assets { } /// Returns `true` if `asset` is contained within `self`. - pub fn contains_asset(&self, asset: &MultiAsset) -> bool { + pub fn contains_asset(&self, asset: &Asset) -> bool { match asset { - MultiAsset { fun: Fungible(amount), id } => + Asset { fun: Fungible(amount), id } => self.fungible.get(id).map_or(false, |a| a >= amount), - MultiAsset { fun: NonFungible(instance), id } => - self.non_fungible.contains(&(*id, *instance)), + Asset { fun: NonFungible(instance), id } => + self.non_fungible.contains(&(id.clone(), *instance)), } } /// Returns `true` if all `assets` are contained within `self`. - pub fn contains_assets(&self, assets: &MultiAssets) -> bool { + pub fn contains_assets(&self, assets: &Assets) -> bool { assets.inner().iter().all(|a| self.contains_asset(a)) } /// Returns `true` if all `assets` are contained within `self`. - pub fn contains(&self, assets: &Assets) -> bool { + pub fn contains(&self, assets: &AssetsInHolding) -> bool { assets .fungible .iter() @@ -274,16 +274,16 @@ impl Assets { /// Returns an error unless all `assets` are contained in `self`. In the case of an error, the /// first asset in `assets` which is not wholly in `self` is returned. - pub fn ensure_contains(&self, assets: &MultiAssets) -> Result<(), TakeError> { + pub fn ensure_contains(&self, assets: &Assets) -> Result<(), TakeError> { for asset in assets.inner().iter() { match asset { - MultiAsset { fun: Fungible(amount), id } => { + Asset { fun: Fungible(amount), id } => { if self.fungible.get(id).map_or(true, |a| a < amount) { - return Err(TakeError::AssetUnderflow((*id, *amount).into())) + return Err(TakeError::AssetUnderflow((id.clone(), *amount).into())) } }, - MultiAsset { fun: NonFungible(instance), id } => { - let id_instance = (*id, *instance); + Asset { fun: NonFungible(instance), id } => { + let id_instance = (id.clone(), *instance); if !self.non_fungible.contains(&id_instance) { return Err(TakeError::AssetUnderflow(id_instance.into())) } @@ -308,16 +308,16 @@ impl Assets { /// of) a definite asset to be removed. fn general_take( &mut self, - mask: MultiAssetFilter, + mask: AssetFilter, saturate: bool, - ) -> Result { - let mut taken = Assets::new(); + ) -> Result { + let mut taken = AssetsInHolding::new(); let maybe_limit = mask.limit().map(|x| x as usize); match mask { // TODO: Counted variants where we define `limit`. - MultiAssetFilter::Wild(All) | MultiAssetFilter::Wild(AllCounted(_)) => { + AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { if maybe_limit.map_or(true, |l| self.len() <= l) { - return Ok(self.swapped(Assets::new())) + return Ok(self.swapped(AssetsInHolding::new())) } else { let fungible = mem::replace(&mut self.fungible, Default::default()); fungible.into_iter().for_each(|(c, amount)| { @@ -337,15 +337,15 @@ impl Assets { }); } }, - MultiAssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | - MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => + AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildFungible, id }) => if maybe_limit.map_or(true, |l| l >= 1) { if let Some((id, amount)) = self.fungible.remove_entry(&id) { taken.fungible.insert(id, amount); } }, - MultiAssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | - MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { + AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => { let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); non_fungible.into_iter().for_each(|(c, instance)| { if c == id && maybe_limit.map_or(true, |l| taken.len() < l) { @@ -355,13 +355,13 @@ impl Assets { } }); }, - MultiAssetFilter::Definite(assets) => { + AssetFilter::Definite(assets) => { if !saturate { self.ensure_contains(&assets)?; } for asset in assets.into_inner().into_iter() { match asset { - MultiAsset { fun: Fungible(amount), id } => { + Asset { fun: Fungible(amount), id } => { let (remove, amount) = match self.fungible.get_mut(&id) { Some(self_amount) => { let amount = amount.min(*self_amount); @@ -374,10 +374,10 @@ impl Assets { self.fungible.remove(&id); } if amount > 0 { - taken.subsume(MultiAsset::from((id, amount)).into()); + taken.subsume(Asset::from((id, amount)).into()); } }, - MultiAsset { fun: NonFungible(instance), id } => { + Asset { fun: NonFungible(instance), id } => { let id_instance = (id, instance); if self.non_fungible.remove(&id_instance) { taken.subsume(id_instance.into()) @@ -395,7 +395,7 @@ impl Assets { /// /// Returns `Ok` with the non-wildcard equivalence of `mask` taken and mutates `self` to its /// value minus `mask` if `self` contains `asset`, and return `Err` otherwise. - pub fn saturating_take(&mut self, asset: MultiAssetFilter) -> Assets { + pub fn saturating_take(&mut self, asset: AssetFilter) -> AssetsInHolding { self.general_take(asset, true) .expect("general_take never results in error when saturating") } @@ -405,13 +405,13 @@ impl Assets { /// /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its /// value minus `asset` if `self` contains `asset`, and return `Err` otherwise. - pub fn try_take(&mut self, mask: MultiAssetFilter) -> Result { + pub fn try_take(&mut self, mask: AssetFilter) -> Result { self.general_take(mask, false) } /// Consumes `self` and returns its original value excluding `asset` iff it contains at least /// `asset`. - pub fn checked_sub(mut self, asset: MultiAsset) -> Result { + pub fn checked_sub(mut self, asset: Asset) -> Result { match asset.fun { Fungible(amount) => { let remove = if let Some(balance) = self.fungible.get_mut(&asset.id) { @@ -446,66 +446,66 @@ impl Assets { /// Example: /// /// ``` - /// use staging_xcm_executor::Assets; + /// use staging_xcm_executor::AssetsInHolding; /// use xcm::latest::prelude::*; - /// let assets_i_have: Assets = vec![ (Here, 100).into(), ([0; 32], 100).into() ].into(); - /// let assets_they_want: MultiAssetFilter = vec![ (Here, 200).into(), ([0; 32], 50).into() ].into(); + /// let assets_i_have: AssetsInHolding = vec![ (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 100).into() ].into(); + /// let assets_they_want: AssetFilter = vec![ (Here, 200).into(), (Junctions::from([GeneralIndex(0)]), 50).into() ].into(); /// - /// let assets_we_can_trade: Assets = assets_i_have.min(&assets_they_want); + /// let assets_we_can_trade: AssetsInHolding = assets_i_have.min(&assets_they_want); /// assert_eq!(assets_we_can_trade.into_assets_iter().collect::>(), vec![ - /// (Here, 100).into(), ([0; 32], 50).into(), + /// (Here, 100).into(), (Junctions::from([GeneralIndex(0)]), 50).into(), /// ]); /// ``` - pub fn min(&self, mask: &MultiAssetFilter) -> Assets { - let mut masked = Assets::new(); + pub fn min(&self, mask: &AssetFilter) -> AssetsInHolding { + let mut masked = AssetsInHolding::new(); let maybe_limit = mask.limit().map(|x| x as usize); if maybe_limit.map_or(false, |l| l == 0) { return masked } match mask { - MultiAssetFilter::Wild(All) | MultiAssetFilter::Wild(AllCounted(_)) => { + AssetFilter::Wild(All) | AssetFilter::Wild(AllCounted(_)) => { if maybe_limit.map_or(true, |l| self.len() <= l) { return self.clone() } else { - for (&c, &amount) in self.fungible.iter() { - masked.fungible.insert(c, amount); + for (c, &amount) in self.fungible.iter() { + masked.fungible.insert(c.clone(), amount); if maybe_limit.map_or(false, |l| masked.len() >= l) { return masked } } for (c, instance) in self.non_fungible.iter() { - masked.non_fungible.insert((*c, *instance)); + masked.non_fungible.insert((c.clone(), *instance)); if maybe_limit.map_or(false, |l| masked.len() >= l) { return masked } } } }, - MultiAssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | - MultiAssetFilter::Wild(AllOf { fun: WildFungible, id }) => + AssetFilter::Wild(AllOfCounted { fun: WildFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildFungible, id }) => if let Some(&amount) = self.fungible.get(&id) { - masked.fungible.insert(*id, amount); + masked.fungible.insert(id.clone(), amount); }, - MultiAssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | - MultiAssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => + AssetFilter::Wild(AllOfCounted { fun: WildNonFungible, id, .. }) | + AssetFilter::Wild(AllOf { fun: WildNonFungible, id }) => for (c, instance) in self.non_fungible.iter() { if c == id { - masked.non_fungible.insert((*c, *instance)); + masked.non_fungible.insert((c.clone(), *instance)); if maybe_limit.map_or(false, |l| masked.len() >= l) { return masked } } }, - MultiAssetFilter::Definite(assets) => + AssetFilter::Definite(assets) => for asset in assets.inner().iter() { match asset { - MultiAsset { fun: Fungible(amount), id } => { + Asset { fun: Fungible(amount), id } => { if let Some(m) = self.fungible.get(id) { - masked.subsume((*id, Fungible(*amount.min(m))).into()); + masked.subsume((id.clone(), Fungible(*amount.min(m))).into()); } }, - MultiAsset { fun: NonFungible(instance), id } => { - let id_instance = (*id, *instance); + Asset { fun: NonFungible(instance), id } => { + let id_instance = (id.clone(), *instance); if self.non_fungible.contains(&id_instance) { masked.subsume(id_instance.into()); } @@ -522,30 +522,18 @@ mod tests { use super::*; use xcm::latest::prelude::*; #[allow(non_snake_case)] - /// Abstract fungible constructor - fn AF(id: u8, amount: u128) -> MultiAsset { - ([id; 32], amount).into() - } - #[allow(non_snake_case)] - /// Abstract non-fungible constructor - fn ANF(class: u8, instance_id: u8) -> MultiAsset { - ([class; 32], [instance_id; 4]).into() - } - #[allow(non_snake_case)] /// Concrete fungible constructor - fn CF(amount: u128) -> MultiAsset { + fn CF(amount: u128) -> Asset { (Here, amount).into() } #[allow(non_snake_case)] /// Concrete non-fungible constructor - fn CNF(instance_id: u8) -> MultiAsset { + fn CNF(instance_id: u8) -> Asset { (Here, [instance_id; 4]).into() } - fn test_assets() -> Assets { - let mut assets = Assets::new(); - assets.subsume(AF(1, 100)); - assets.subsume(ANF(2, 20)); + fn test_assets() -> AssetsInHolding { + let mut assets = AssetsInHolding::new(); assets.subsume(CF(300)); assets.subsume(CNF(40)); assets @@ -554,9 +542,7 @@ mod tests { #[test] fn subsume_assets_works() { let t1 = test_assets(); - let mut t2 = Assets::new(); - t2.subsume(AF(1, 50)); - t2.subsume(ANF(2, 10)); + let mut t2 = AssetsInHolding::new(); t2.subsume(CF(300)); t2.subsume(CNF(50)); let mut r1 = t1.clone(); @@ -571,63 +557,48 @@ mod tests { #[test] fn checked_sub_works() { let t = test_assets(); - let t = t.checked_sub(AF(1, 50)).unwrap(); - let t = t.checked_sub(AF(1, 51)).unwrap_err(); - let t = t.checked_sub(AF(1, 50)).unwrap(); - let t = t.checked_sub(AF(1, 1)).unwrap_err(); let t = t.checked_sub(CF(150)).unwrap(); let t = t.checked_sub(CF(151)).unwrap_err(); let t = t.checked_sub(CF(150)).unwrap(); let t = t.checked_sub(CF(1)).unwrap_err(); - let t = t.checked_sub(ANF(2, 21)).unwrap_err(); - let t = t.checked_sub(ANF(2, 20)).unwrap(); - let t = t.checked_sub(ANF(2, 20)).unwrap_err(); let t = t.checked_sub(CNF(41)).unwrap_err(); let t = t.checked_sub(CNF(40)).unwrap(); let t = t.checked_sub(CNF(40)).unwrap_err(); - assert_eq!(t, Assets::new()); + assert_eq!(t, AssetsInHolding::new()); } #[test] fn into_assets_iter_works() { let assets = test_assets(); let mut iter = assets.into_assets_iter(); - // Order defined by implementation: CF, AF, CNF, ANF + // Order defined by implementation: CF, CNF assert_eq!(Some(CF(300)), iter.next()); - assert_eq!(Some(AF(1, 100)), iter.next()); assert_eq!(Some(CNF(40)), iter.next()); - assert_eq!(Some(ANF(2, 20)), iter.next()); assert_eq!(None, iter.next()); } #[test] fn assets_into_works() { - let mut assets_vec: Vec = Vec::new(); - assets_vec.push(AF(1, 100)); - assets_vec.push(ANF(2, 20)); + let mut assets_vec: Vec = Vec::new(); assets_vec.push(CF(300)); assets_vec.push(CNF(40)); // Push same group of tokens again - assets_vec.push(AF(1, 100)); - assets_vec.push(ANF(2, 20)); assets_vec.push(CF(300)); assets_vec.push(CNF(40)); - let assets: Assets = assets_vec.into(); + let assets: AssetsInHolding = assets_vec.into(); let mut iter = assets.into_assets_iter(); // Fungibles add assert_eq!(Some(CF(600)), iter.next()); - assert_eq!(Some(AF(1, 200)), iter.next()); // Non-fungibles collapse assert_eq!(Some(CNF(40)), iter.next()); - assert_eq!(Some(ANF(2, 20)), iter.next()); assert_eq!(None, iter.next()); } #[test] fn min_all_and_none_works() { let assets = test_assets(); - let none = MultiAssets::new().into(); + let none = Assets::new().into(); let all = All.into(); let none_min = assets.min(&none); @@ -638,43 +609,15 @@ mod tests { #[test] fn min_counted_works() { - let mut assets = Assets::new(); - assets.subsume(AF(1, 100)); - assets.subsume(ANF(2, 20)); + let mut assets = AssetsInHolding::new(); assets.subsume(CNF(40)); - assets.subsume(AF(10, 50)); - assets.subsume(ANF(2, 40)); - assets.subsume(ANF(2, 30)); assets.subsume(CF(3000)); assets.subsume(CNF(80)); - assets.subsume(ANF(3, 10)); - let fungible = WildMultiAsset::from(([1u8; 32], WildFungible)).counted(2).into(); - let non_fungible = WildMultiAsset::from(([2u8; 32], WildNonFungible)).counted(2).into(); - let all = WildMultiAsset::AllCounted(6).into(); + let all = WildAsset::AllCounted(6).into(); - let fungible = assets.min(&fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![AF(1, 100)]); - let non_fungible = assets.min(&non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![ANF(2, 20), ANF(2, 30)]); let all = assets.min(&all); let all = all.assets_iter().collect::>(); - assert_eq!(all, vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 20),]); - } - - #[test] - fn min_all_abstract_works() { - let assets = test_assets(); - let fungible = Wild(([1u8; 32], WildFungible).into()); - let non_fungible = Wild(([2u8; 32], WildNonFungible).into()); - - let fungible = assets.min(&fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![AF(1, 100)]); - let non_fungible = assets.min(&non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![ANF(2, 20)]); + assert_eq!(all, vec![CF(3000), CNF(40), CNF(80)]); } #[test] @@ -695,20 +638,16 @@ mod tests { fn min_basic_works() { let assets1 = test_assets(); - let mut assets2 = Assets::new(); - // This is less than 100, so it will decrease to 50 - assets2.subsume(AF(1, 50)); - // This asset does not exist, so not included - assets2.subsume(ANF(2, 40)); + let mut assets2 = AssetsInHolding::new(); // This is more then 300, so it should stay at 300 assets2.subsume(CF(600)); // This asset should be included assets2.subsume(CNF(40)); - let assets2: MultiAssets = assets2.into(); + let assets2: Assets = assets2.into(); let assets_min = assets1.min(&assets2.into()); let assets_min = assets_min.into_assets_iter().collect::>(); - assert_eq!(assets_min, vec![CF(300), AF(1, 50), CNF(40)]); + assert_eq!(assets_min, vec![CF(300), CNF(40)]); } #[test] @@ -724,23 +663,6 @@ mod tests { assert!(all_iter.eq(test_assets().assets_iter())); } - #[test] - fn saturating_take_all_abstract_works() { - let mut assets = test_assets(); - let fungible = Wild(([1u8; 32], WildFungible).into()); - let non_fungible = Wild(([2u8; 32], WildNonFungible).into()); - - let fungible = assets.saturating_take(fungible); - let fungible = fungible.assets_iter().collect::>(); - assert_eq!(fungible, vec![AF(1, 100)]); - let non_fungible = assets.saturating_take(non_fungible); - let non_fungible = non_fungible.assets_iter().collect::>(); - assert_eq!(non_fungible, vec![ANF(2, 20)]); - // Assets drained of abstract - let final_assets = assets.assets_iter().collect::>(); - assert_eq!(final_assets, vec![CF(300), CNF(40)]); - } - #[test] fn saturating_take_all_concrete_works() { let mut assets = test_assets(); @@ -753,102 +675,49 @@ mod tests { let non_fungible = assets.saturating_take(non_fungible); let non_fungible = non_fungible.assets_iter().collect::>(); assert_eq!(non_fungible, vec![CNF(40)]); - // Assets drained of concrete - let assets = assets.assets_iter().collect::>(); - assert_eq!(assets, vec![AF(1, 100), ANF(2, 20)]); } #[test] fn saturating_take_basic_works() { let mut assets1 = test_assets(); - let mut assets2 = Assets::new(); - // We should take 50 - assets2.subsume(AF(1, 50)); - // This asset should not be taken - assets2.subsume(ANF(2, 40)); + let mut assets2 = AssetsInHolding::new(); // This is more then 300, so it takes everything assets2.subsume(CF(600)); // This asset should be taken assets2.subsume(CNF(40)); - let assets2: MultiAssets = assets2.into(); + let assets2: Assets = assets2.into(); let taken = assets1.saturating_take(assets2.into()); let taken = taken.into_assets_iter().collect::>(); - assert_eq!(taken, vec![CF(300), AF(1, 50), CNF(40)]); - - let assets = assets1.into_assets_iter().collect::>(); - assert_eq!(assets, vec![AF(1, 50), ANF(2, 20)]); + assert_eq!(taken, vec![CF(300), CNF(40)]); } #[test] fn try_take_all_counted_works() { - let mut assets = Assets::new(); - assets.subsume(AF(1, 100)); - assets.subsume(ANF(2, 20)); + let mut assets = AssetsInHolding::new(); assets.subsume(CNF(40)); - assets.subsume(AF(10, 50)); - assets.subsume(ANF(2, 40)); - assets.subsume(ANF(2, 30)); assets.subsume(CF(3000)); assets.subsume(CNF(80)); - assets.subsume(ANF(3, 10)); - let all = assets.try_take(WildMultiAsset::AllCounted(6).into()).unwrap(); - assert_eq!( - MultiAssets::from(all).inner(), - &vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 20),] - ); - assert_eq!(MultiAssets::from(assets).inner(), &vec![ANF(2, 30), ANF(2, 40), ANF(3, 10),]); + let all = assets.try_take(WildAsset::AllCounted(6).into()).unwrap(); + assert_eq!(Assets::from(all).inner(), &vec![CF(3000), CNF(40), CNF(80)]); } #[test] fn try_take_fungibles_counted_works() { - let mut assets = Assets::new(); - assets.subsume(AF(1, 100)); - assets.subsume(ANF(2, 20)); + let mut assets = AssetsInHolding::new(); assets.subsume(CNF(40)); - assets.subsume(AF(10, 50)); - assets.subsume(ANF(2, 40)); - assets.subsume(ANF(2, 30)); assets.subsume(CF(3000)); assets.subsume(CNF(80)); - assets.subsume(ANF(3, 10)); - let mask = WildMultiAsset::from(([1u8; 32], WildFungible)).counted(2).into(); - let taken = assets.try_take(mask).unwrap(); - assert_eq!(MultiAssets::from(taken).inner(), &vec![AF(1, 100)]); - assert_eq!( - MultiAssets::from(assets).inner(), - &vec![ - CF(3000), - AF(10, 50), - CNF(40), - CNF(80), - ANF(2, 20), - ANF(2, 30), - ANF(2, 40), - ANF(3, 10), - ] - ); + assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80),]); } #[test] fn try_take_non_fungibles_counted_works() { - let mut assets = Assets::new(); - assets.subsume(AF(1, 100)); - assets.subsume(ANF(2, 20)); + let mut assets = AssetsInHolding::new(); assets.subsume(CNF(40)); - assets.subsume(AF(10, 50)); - assets.subsume(ANF(2, 40)); - assets.subsume(ANF(2, 30)); assets.subsume(CF(3000)); assets.subsume(CNF(80)); - assets.subsume(ANF(3, 10)); - let mask = WildMultiAsset::from(([2u8; 32], WildNonFungible)).counted(2).into(); - let taken = assets.try_take(mask).unwrap(); - assert_eq!(MultiAssets::from(taken).inner(), &vec![ANF(2, 20), ANF(2, 30),]); - assert_eq!( - MultiAssets::from(assets).inner(), - &vec![CF(3000), AF(1, 100), AF(10, 50), CNF(40), CNF(80), ANF(2, 40), ANF(3, 10),] - ); + assert_eq!(Assets::from(assets).inner(), &vec![CF(3000), CNF(40), CNF(80)]); } } diff --git a/polkadot/xcm/xcm-executor/src/config.rs b/polkadot/xcm/xcm-executor/src/config.rs index 2ff12cd7a5399f442fc1882e534e914703a91df6..b296d32ca2adb6761f815aa74397211e3bc17d5f 100644 --- a/polkadot/xcm/xcm-executor/src/config.rs +++ b/polkadot/xcm/xcm-executor/src/config.rs @@ -16,8 +16,9 @@ use crate::traits::{ AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, DropAssets, ExportXcm, - FeeManager, OnResponse, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, - WeightTrader, + FeeManager, HandleHrmpChannelAccepted, HandleHrmpChannelClosing, + HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, ShouldExecute, TransactAsset, + VersionChangeNotifier, WeightBounds, WeightTrader, }; use frame_support::{ dispatch::{GetDispatchInfo, Parameter, PostDispatchInfo}, @@ -41,17 +42,17 @@ pub trait Config { type OriginConverter: ConvertOrigin<::RuntimeOrigin>; /// Combinations of (Asset, Location) pairs which we trust as reserves. - type IsReserve: ContainsPair; + type IsReserve: ContainsPair; /// Combinations of (Asset, Location) pairs which we trust as teleporters. - type IsTeleporter: ContainsPair; + type IsTeleporter: ContainsPair; /// A list of (Origin, Target) pairs allowing a given Origin to be substituted with its /// corresponding Target pair. - type Aliasers: ContainsPair; + type Aliasers: ContainsPair; /// This chain's Universal Location. - type UniversalLocation: Get; + type UniversalLocation: Get; /// Whether we should execute the given XCM at all. type Barrier: ShouldExecute; @@ -98,7 +99,7 @@ pub trait Config { /// The origin locations and specific universal junctions to which they are allowed to elevate /// themselves. - type UniversalAliases: Contains<(MultiLocation, Junction)>; + type UniversalAliases: Contains<(Location, Junction)>; /// The call dispatcher used by XCM. /// @@ -111,4 +112,14 @@ pub trait Config { /// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a /// temporary measure until we properly account for proof size weights for XCM instructions. type SafeCallFilter: Contains; + + /// Transactional processor for XCM instructions. + type TransactionalProcessor: ProcessTransaction; + + /// Allows optional logic execution for the `HrmpNewChannelOpenRequest` XCM notification. + type HrmpNewChannelOpenRequestHandler: HandleHrmpNewChannelOpenRequest; + /// Allows optional logic execution for the `HrmpChannelAccepted` XCM notification. + type HrmpChannelAcceptedHandler: HandleHrmpChannelAccepted; + /// Allows optional logic execution for the `HrmpChannelClosing` XCM notification. + type HrmpChannelClosingHandler: HandleHrmpChannelClosing; } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index e11ec2630e4350ae740fbca8591c2e34d752a34b..81b81fe6a17154c74e03ebbddf8bfd81e4f7181c 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -19,24 +19,26 @@ use frame_support::{ dispatch::GetDispatchInfo, ensure, - traits::{Contains, ContainsPair, Get, PalletsInfoAccess}, + traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess}, }; 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::*; pub mod traits; use traits::{ validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin, - DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, Properties, ShouldExecute, - TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + DropAssets, Enact, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted, + HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction, + Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + XcmAssetTransfers, }; mod assets; -pub use assets::Assets; +pub use assets::AssetsInHolding; mod config; pub use config::Config; @@ -56,10 +58,10 @@ environmental::environmental!(recursion_count: u8); /// The XCM executor. pub struct XcmExecutor { - holding: Assets, + holding: AssetsInHolding, holding_limit: usize, context: XcmContext, - original_origin: MultiLocation, + original_origin: Location, trader: Config::Trader, /// The most recent error result and instruction index into the fragment in which it occurred, /// if any. @@ -81,10 +83,10 @@ pub struct XcmExecutor { #[cfg(feature = "runtime-benchmarks")] impl XcmExecutor { - pub fn holding(&self) -> &Assets { + pub fn holding(&self) -> &AssetsInHolding { &self.holding } - pub fn set_holding(&mut self, v: Assets) { + pub fn set_holding(&mut self, v: AssetsInHolding) { self.holding = v } pub fn holding_limit(&self) -> &usize { @@ -93,16 +95,16 @@ impl XcmExecutor { pub fn set_holding_limit(&mut self, v: usize) { self.holding_limit = v } - pub fn origin(&self) -> &Option { + pub fn origin(&self) -> &Option { &self.context.origin } - pub fn set_origin(&mut self, v: Option) { + pub fn set_origin(&mut self, v: Option) { self.context.origin = v } - pub fn original_origin(&self) -> &MultiLocation { + pub fn original_origin(&self) -> &Location { &self.original_origin } - pub fn set_original_origin(&mut self, v: MultiLocation) { + pub fn set_original_origin(&mut self, v: Location) { self.original_origin = v } pub fn trader(&self) -> &Config::Trader { @@ -191,18 +193,15 @@ impl ExecuteXcm for XcmExecutor, + origin: impl Into, WeighedMessage(xcm_weight, mut message): WeighedMessage, id: &mut XcmHash, weight_credit: Weight, ) -> Outcome { let origin = origin.into(); log::trace!( - target: "xcm::execute_xcm_in_credit", - "origin: {:?}, message: {:?}, weight_credit: {:?}", - origin, - message, - weight_credit, + target: "xcm::execute", + "origin: {origin:?}, message: {message:?}, weight_credit: {weight_credit:?}", ); let mut properties = Properties { weight_credit, message_id: None }; if let Err(e) = Config::Barrier::should_execute( @@ -212,14 +211,11 @@ impl ExecuteXcm for XcmExecutor ExecuteXcm for XcmExecutor ExecuteXcm for XcmExecutor, fees: MultiAssets) -> XcmResult { + fn charge_fees(origin: impl Into, fees: Assets) -> XcmResult { let origin = origin.into(); if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) { for asset in fees.inner() { Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?; } - Config::FeeManager::handle_fee(fees, None); + Config::FeeManager::handle_fee(fees, None, FeeReason::ChargeFees); } Ok(()) } } +impl XcmAssetTransfers for XcmExecutor { + type IsReserve = Config::IsReserve; + type IsTeleporter = Config::IsTeleporter; + type AssetTransactor = Config::AssetTransactor; +} + #[derive(Debug)] pub struct ExecutorError { pub index: u32, @@ -275,12 +277,12 @@ impl From for frame_benchmarking::BenchmarkError { } impl XcmExecutor { - pub fn new(origin: impl Into, message_id: XcmHash) -> Self { + pub fn new(origin: impl Into, message_id: XcmHash) -> Self { let origin = origin.into(); Self { - holding: Assets::new(), + holding: AssetsInHolding::new(), holding_limit: Config::MaxAssetsIntoHolding::get() as usize, - context: XcmContext { origin: Some(origin), message_id, topic: None }, + context: XcmContext { origin: Some(origin.clone()), message_id, topic: None }, original_origin: origin, trader: Config::Trader::new(), error: None, @@ -296,65 +298,6 @@ impl 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. @@ -368,7 +311,7 @@ impl XcmExecutor { if !self.holding.is_empty() { log::trace!( - target: "xcm::execute_xcm_in_credit", + target: "xcm::post_process", "Trapping assets in holding register: {:?}, context: {:?} (original_origin: {:?})", self.holding, self.context, self.original_origin, ); @@ -379,28 +322,28 @@ impl XcmExecutor { }; match self.error { - None => Outcome::Complete(weight_used), + None => Outcome::Complete { used: weight_used }, // TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following // the error which didn't end up being executed. Some((_i, e)) => { - log::trace!(target: "xcm::execute_xcm_in_credit", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); - Outcome::Incomplete(weight_used, e) + log::trace!(target: "xcm::post_process", "Execution errored at {:?}: {:?} (original_origin: {:?})", _i, e, self.original_origin); + Outcome::Incomplete { used: weight_used, error: e } }, } } - fn origin_ref(&self) -> Option<&MultiLocation> { + fn origin_ref(&self) -> Option<&Location> { self.context.origin.as_ref() } - fn cloned_origin(&self) -> Option { - self.context.origin + fn cloned_origin(&self) -> Option { + self.context.origin.clone() } /// Send an XCM, charging fees from Holding as needed. fn send( &mut self, - dest: MultiLocation, + dest: Location, msg: Xcm<()>, reason: FeeReason, ) -> Result { @@ -432,36 +375,195 @@ impl XcmExecutor { r } - fn subsume_asset(&mut self, asset: MultiAsset) -> Result<(), XcmError> { - // worst-case, holding.len becomes 2 * holding_limit. - ensure!(self.holding.len() < self.holding_limit * 2, XcmError::HoldingWouldOverflow); - self.holding.subsume(asset); - Ok(()) - } - - fn subsume_assets(&mut self, assets: Assets) -> Result<(), XcmError> { + fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> { // worst-case, holding.len becomes 2 * holding_limit. - // this guarantees that if holding.len() == holding_limit and you have holding_limit more - // items (which has a best case outcome of holding.len() == holding_limit), then you'll - // be guaranteed of making the operation. - let worst_case_holding_len = self.holding.len() + assets.len(); + // this guarantees that if holding.len() == holding_limit and you have more than + // `holding_limit` items (which has a best case outcome of holding.len() == holding_limit), + // then the operation is guaranteed to succeed. + let worst_case_holding_len = self.holding.len() + assets_length; + log::trace!(target: "xcm::ensure_can_subsume_assets", "worst_case_holding_len: {:?}, holding_limit: {:?}", worst_case_holding_len, self.holding_limit); ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow); - self.holding.subsume_assets(assets); Ok(()) } /// Refund any unused weight. fn refund_surplus(&mut self) -> Result<(), XcmError> { let current_surplus = self.total_surplus.saturating_sub(self.total_refunded); + log::trace!( + target: "xcm::refund_surplus", + "total_surplus: {:?}, total_refunded: {:?}, current_surplus: {:?}", + self.total_surplus, + self.total_refunded, + current_surplus, + ); if current_surplus.any_gt(Weight::zero()) { - self.total_refunded.saturating_accrue(current_surplus); if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) { - self.subsume_asset(w)?; + if !self.holding.contains_asset(&(w.id.clone(), 1).into()) && + self.ensure_can_subsume_assets(1).is_err() + { + let _ = self + .trader + .buy_weight(current_surplus, w.into(), &self.context) + .defensive_proof( + "refund_weight returned an asset capable of buying weight; qed", + ); + log::error!( + target: "xcm::refund_surplus", + "error: HoldingWouldOverflow", + ); + return Err(XcmError::HoldingWouldOverflow) + } + self.total_refunded.saturating_accrue(current_surplus); + self.holding.subsume_assets(w.into()); } } + log::trace!( + target: "xcm::refund_surplus", + "total_refunded: {:?}", + self.total_refunded, + ); + Ok(()) + } + + fn take_fee(&mut self, fee: Assets, reason: FeeReason) -> XcmResult { + if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) { + 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: &Location, + ) -> 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( + reanchorable: T, + destination: &Location, + ) -> Result<(T, InteriorLocation), XcmError> { + let reanchor_context = Config::UniversalLocation::get(); + let reanchored = + reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| { + log::error!(target: "xcm::reanchor", "Failed reanchoring with error {error:?}"); + XcmError::ReanchorFailed + })?; + Ok((reanchored, reanchor_context)) + } + + /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. + fn reanchored( + mut assets: AssetsInHolding, + dest: &Location, + maybe_failed_bin: Option<&mut AssetsInHolding>, + ) -> Assets { + 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, @@ -474,93 +576,181 @@ impl XcmExecutor { ); match instr { WithdrawAsset(assets) => { - // Take `assets` from the origin account (on-chain) and place in holding. - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.into_inner().into_iter() { - Config::AssetTransactor::withdraw_asset(&asset, &origin, Some(&self.context))?; - self.subsume_asset(asset)?; - } - Ok(()) + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain)... + for asset in assets.inner() { + Config::AssetTransactor::withdraw_asset( + asset, + origin, + Some(&self.context), + )?; + } + Ok(()) + }) + .and_then(|_| { + // ...and place into holding. + self.holding.subsume_assets(assets.into()); + Ok(()) + }) }, ReserveAssetDeposited(assets) => { // check whether we trust origin to be our reserve location for this asset. - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.into_inner().into_iter() { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + for asset in assets.inner() { // Must ensure that we recognise the asset as being managed by the origin. ensure!( - Config::IsReserve::contains(&asset, &origin), + Config::IsReserve::contains(asset, origin), XcmError::UntrustedReserveLocation ); - self.subsume_asset(asset)?; } + self.holding.subsume_assets(assets.into()); Ok(()) }, TransferAsset { assets, beneficiary } => { - // Take `assets` from the origin account (on-chain) and place into dest account. - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset( - &asset, - origin, - &beneficiary, - &self.context, - )?; - } - Ok(()) + Config::TransactionalProcessor::process(|| { + // Take `assets` from the origin account (on-chain) and place into dest account. + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset( + &asset, + origin, + &beneficiary, + &self.context, + )?; + } + Ok(()) + }) }, TransferReserveAsset { mut assets, dest, xcm } => { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - // Take `assets` from the origin account (on-chain) and place into dest account. - for asset in assets.inner() { - Config::AssetTransactor::transfer_asset(asset, origin, &dest, &self.context)?; - } - let reanchor_context = Config::UniversalLocation::get(); - assets.reanchor(&dest, reanchor_context).map_err(|()| XcmError::LocationFull)?; - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; - Ok(()) + Config::TransactionalProcessor::process(|| { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + // Take `assets` from the origin account (on-chain) and place into dest account. + for asset in assets.inner() { + Config::AssetTransactor::transfer_asset( + asset, + origin, + &dest, + &self.context, + )?; + } + let reanchor_context = Config::UniversalLocation::get(); + assets + .reanchor(&dest, &reanchor_context) + .map_err(|()| XcmError::LocationFull)?; + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?; + Ok(()) + }) }, ReceiveTeleportedAsset(assets) => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - // check whether we trust origin to teleport this asset to us via config trait. - for asset in assets.inner() { - // We only trust the origin to send us assets that they identify as their - // sovereign assets. - ensure!( - Config::IsTeleporter::contains(asset, &origin), - XcmError::UntrustedTeleportLocation - ); - // We should check that the asset can actually be teleported in (for this to be - // in error, there would need to be an accounting violation by one of the - // trusted chains, so it's unlikely, but we don't want to punish a possibly - // innocent chain/user). - Config::AssetTransactor::can_check_in(&origin, asset, &self.context)?; - } - for asset in assets.into_inner().into_iter() { - Config::AssetTransactor::check_in(&origin, &asset, &self.context); - self.subsume_asset(asset)?; - } - Ok(()) + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; + Config::TransactionalProcessor::process(|| { + // check whether we trust origin to teleport this asset to us via config trait. + for asset in assets.inner() { + // We only trust the origin to send us assets that they identify as their + // sovereign assets. + ensure!( + Config::IsTeleporter::contains(asset, origin), + XcmError::UntrustedTeleportLocation + ); + // We should check that the asset can actually be teleported in (for this to + // be in error, there would need to be an accounting violation by one of the + // trusted chains, so it's unlikely, but we don't want to punish a possibly + // innocent chain/user). + Config::AssetTransactor::can_check_in(origin, asset, &self.context)?; + Config::AssetTransactor::check_in(origin, asset, &self.context); + } + Ok(()) + }) + .and_then(|_| { + self.holding.subsume_assets(assets.into()); + Ok(()) + }) }, 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.cloned_origin().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 dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind) - .map_err(|_| XcmError::BadOrigin)?; + 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.clone(), origin_kind).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 }, @@ -613,62 +803,108 @@ impl XcmExecutor { Ok(()) }, DepositAsset { assets, beneficiary } => { - let deposited = self.holding.saturating_take(assets); - for asset in deposited.into_assets_iter() { - Config::AssetTransactor::deposit_asset( - &asset, - &beneficiary, - Some(&self.context), - )?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let deposited = self.holding.saturating_take(assets); + for asset in deposited.into_assets_iter() { + Config::AssetTransactor::deposit_asset( + &asset, + &beneficiary, + Some(&self.context), + )?; + } + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, DepositReserveAsset { assets, dest, xcm } => { - let deposited = self.holding.saturating_take(assets); - for asset in deposited.assets_iter() { - Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + // we need to do this take/put cycle to solve wildcards and get exact assets to + // be weighed + let to_weigh = self.holding.saturating_take(assets.clone()); + self.holding.subsume_assets(to_weigh.clone()); + let to_weigh_reanchored = Self::reanchored(to_weigh, &dest, None); + let mut message_to_weigh = + vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; + message_to_weigh.extend(xcm.0.clone().into_iter()); + let (_, fee) = + validate_send::(dest.clone(), Xcm(message_to_weigh))?; + // set aside fee to be charged by XcmSender + let transport_fee = self.holding.saturating_take(fee.into()); + + // now take assets to deposit (excluding transport_fee) + let deposited = self.holding.saturating_take(assets); + for asset in deposited.assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &dest, Some(&self.context))?; + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which + // cannot be reanchored because we have already called `deposit_asset` on all + // assets. + let assets = Self::reanchored(deposited, &dest, None); + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + // put back transport_fee in holding register to be charged by XcmSender + self.holding.subsume_assets(transport_fee); + self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot - // be reanchored because we have already called `deposit_asset` on all assets. - let assets = Self::reanchored(deposited, &dest, None); - let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?; - Ok(()) + result }, InitiateReserveWithdraw { assets, reserve, xcm } => { - // Note that here we are able to place any assets which could not be reanchored - // back into Holding. - let assets = Self::reanchored( - self.holding.saturating_take(assets), - &reserve, - Some(&mut self.holding), - ); - let mut message = vec![WithdrawAsset(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + // Note that here we are able to place any assets which could not be reanchored + // back into Holding. + let assets = Self::reanchored( + self.holding.saturating_take(assets), + &reserve, + Some(&mut self.holding), + ); + let mut message = vec![WithdrawAsset(assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, InitiateTeleport { assets, dest, xcm } => { - // We must do this first in order to resolve wildcards. - let assets = self.holding.saturating_take(assets); - for asset in assets.assets_iter() { - // 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. - Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; - } - for asset in assets.assets_iter() { - Config::AssetTransactor::check_out(&dest, &asset, &self.context); + let old_holding = self.holding.clone(); + let result = (|| -> Result<(), XcmError> { + // We must do this first in order to resolve wildcards. + let assets = self.holding.saturating_take(assets); + for asset in assets.assets_iter() { + // 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. + Config::AssetTransactor::can_check_out(&dest, &asset, &self.context)?; + } + // Note that we pass `None` as `maybe_failed_bin` and drop any assets which + // cannot be reanchored because we have already checked all assets out. + let reanchored_assets = Self::reanchored(assets.clone(), &dest, None); + let mut message = vec![ReceiveTeleportedAsset(reanchored_assets), ClearOrigin]; + message.extend(xcm.0.into_iter()); + self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?; + + for asset in assets.assets_iter() { + Config::AssetTransactor::check_out(&dest, &asset, &self.context); + } + Ok(()) + })(); + if result.is_err() { + self.holding = old_holding; } - // Note that we pass `None` as `maybe_failed_bin` and drop any assets which cannot - // be reanchored because we have already checked all assets out. - let assets = Self::reanchored(assets, &dest, None); - let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin]; - message.extend(xcm.0.into_iter()); - self.send(dest, Xcm(message), FeeReason::InitiateTeleport)?; - Ok(()) + result }, ReportHolding { response_info, assets } => { // Note that we pass `None` as `maybe_failed_bin` since no assets were ever removed @@ -684,18 +920,24 @@ impl XcmExecutor { Ok(()) }, BuyExecution { fees, weight_limit } => { - // There is no need to buy any weight is `weight_limit` is `Unlimited` since it + // There is no need to buy any weight if `weight_limit` is `Unlimited` since it // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution // and thus there is some other reason why it has been determined that this XCM // should be executed. - if let Some(weight) = Option::::from(weight_limit) { - // pay for `weight` using up to `fees` of the holding register. - let max_fee = - self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + let Some(weight) = Option::::from(weight_limit) else { return Ok(()) }; + let old_holding = self.holding.clone(); + // pay for `weight` using up to `fees` of the holding register. + let max_fee = + self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + let result = || -> Result<(), XcmError> { let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; - self.subsume_assets(unspent)?; + self.holding.subsume_assets(unspent); + Ok(()) + }(); + if result.is_err() { + self.holding = old_holding; } - Ok(()) + result }, RefundSurplus => self.refund_surplus(), SetErrorHandler(mut handler) => { @@ -720,11 +962,10 @@ impl XcmExecutor { }, ClaimAsset { assets, ticket } => { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + self.ensure_can_subsume_assets(assets.len())?; let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context); ensure!(ok, XcmError::UnknownClaim); - for asset in assets.into_inner().into_iter() { - self.subsume_asset(asset)?; - } + self.holding.subsume_assets(assets.into()); Ok(()) }, Trap(code) => Err(XcmError::Trap(code)), @@ -816,12 +1057,12 @@ impl XcmExecutor { UniversalOrigin(new_global) => { let universal_location = Config::UniversalLocation::get(); ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation); - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; let origin_xform = (origin, new_global); let ok = Config::UniversalAliases::contains(&origin_xform); ensure!(ok, XcmError::InvalidLocation); let (_, new_global) = origin_xform; - let new_origin = X1(new_global).relative_to(&universal_location); + let new_origin = Junctions::from([new_global]).relative_to(&universal_location); self.context.origin = Some(new_origin); Ok(()) }, @@ -835,77 +1076,114 @@ impl XcmExecutor { // // This only works because the remote chain empowers the bridge // to speak for the local network. - let origin = self.context.origin.ok_or(XcmError::BadOrigin)?; + let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone(); let universal_source = Config::UniversalLocation::get() .within_global(origin) .map_err(|()| XcmError::Unanchored)?; let hash = (self.origin_ref(), &destination).using_encoded(blake2_128); let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); // Hash identifies the lane on the exporter which we use. We use the pairwise - // combination of the origin and destination to ensure origin/destination pairs will - // generally have their own lanes. + // combination of the origin and destination to ensure origin/destination pairs + // will generally have their own lanes. let (ticket, fee) = validate_export::( network, channel, universal_source, - destination, + destination.clone(), xcm, )?; - self.take_fee(fee, FeeReason::Export(network))?; - Config::MessageExporter::deliver(ticket)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + self.take_fee(fee, FeeReason::Export { network, destination })?; + let _ = Config::MessageExporter::deliver(ticket).defensive_proof( + "`deliver` called immediately after `validate_export`; \ + `take_fee` does not affect the validity of the ticket; qed", + ); + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, LockAsset { asset, unlocker } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; - let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; - let lock_ticket = Config::AssetLocker::prepare_lock(unlocker, asset, origin)?; - let owner = - origin.reanchored(&unlocker, context).map_err(|_| XcmError::ReanchorFailed)?; - let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); - let (ticket, price) = validate_send::(unlocker, msg)?; - self.take_fee(price, FeeReason::LockAsset)?; - lock_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; + let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; + let lock_ticket = + Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?; + let owner = origin + .reanchored(&unlocker, &context) + .map_err(|_| XcmError::ReanchorFailed)?; + let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); + let (ticket, price) = validate_send::(unlocker, msg)?; + self.take_fee(price, FeeReason::LockAsset)?; + lock_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, UnlockAsset { asset, target } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?; Ok(()) }, NoteUnlockable { asset, owner } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; Config::AssetLocker::note_unlockable(origin, asset, owner)?; Ok(()) }, RequestUnlock { asset, locker } => { - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?; let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0; - let remote_target = Self::try_reanchor_multilocation(origin, &locker)?.0; - let reduce_ticket = - Config::AssetLocker::prepare_reduce_unlockable(locker, asset, origin)?; + let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0; + let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable( + locker.clone(), + asset, + origin.clone(), + )?; let msg = Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]); let (ticket, price) = validate_send::(locker, msg)?; - self.take_fee(price, FeeReason::RequestUnlock)?; - reduce_ticket.enact()?; - Config::XcmSender::deliver(ticket)?; - Ok(()) + let old_holding = self.holding.clone(); + let result = Config::TransactionalProcessor::process(|| { + self.take_fee(price, FeeReason::RequestUnlock)?; + reduce_ticket.enact()?; + Config::XcmSender::deliver(ticket)?; + Ok(()) + }); + if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { + self.holding = old_holding; + } + result }, ExchangeAsset { give, want, maximal } => { + let old_holding = self.holding.clone(); let give = self.holding.saturating_take(give); - let r = - Config::AssetExchanger::exchange_asset(self.origin_ref(), give, &want, maximal); - let completed = r.is_ok(); - let received = r.unwrap_or_else(|a| a); - for asset in received.into_assets_iter() { - self.holding.subsume(asset); - } - if completed { - Ok(()) - } else { - Err(XcmError::NoDeal) + let result = (|| -> Result<(), XcmError> { + self.ensure_can_subsume_assets(want.len())?; + let exchange_result = Config::AssetExchanger::exchange_asset( + self.origin_ref(), + give, + &want, + maximal, + ); + if let Ok(received) = exchange_result { + self.holding.subsume_assets(received.into()); + Ok(()) + } else { + Err(XcmError::NoDeal) + } + })(); + if result.is_err() { + self.holding = old_holding; } + result }, SetFeesMode { jit_withdraw } => { self.fees_mode = FeesMode { jit_withdraw }; @@ -935,98 +1213,21 @@ impl XcmExecutor { ); Ok(()) }, - HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), - HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented), - HrmpChannelClosing { .. } => Err(XcmError::Unimplemented), + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Config::TransactionalProcessor::process(|| { + Config::HrmpNewChannelOpenRequestHandler::handle( + sender, + max_message_size, + max_capacity, + ) + }), + HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| { + Config::HrmpChannelAcceptedHandler::handle(recipient) + }), + HrmpChannelClosing { initiator, sender, recipient } => + Config::TransactionalProcessor::process(|| { + Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient) + }), } } - - 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)); - 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-executor/src/traits/asset_exchange.rs b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs index 0cb188d348de7b4ff7e7cd3ce3a3d99e83976907..432a7498ed4cf9b700649ed987c8050041e7de08 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_exchange.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::Assets; +use crate::AssetsInHolding; use xcm::prelude::*; /// A service for exchanging assets. @@ -32,21 +32,21 @@ pub trait AssetExchange { /// least want must be in the set. Some assets originally in `give` may also be in this set. In /// the case of returning an `Err`, then `give` is returned. fn exchange_asset( - origin: Option<&MultiLocation>, - give: Assets, - want: &MultiAssets, + origin: Option<&Location>, + give: AssetsInHolding, + want: &Assets, maximal: bool, - ) -> Result; + ) -> Result; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl AssetExchange for Tuple { fn exchange_asset( - origin: Option<&MultiLocation>, - give: Assets, - want: &MultiAssets, + origin: Option<&Location>, + give: AssetsInHolding, + want: &Assets, maximal: bool, - ) -> Result { + ) -> Result { for_tuples!( #( let give = match Tuple::exchange_asset(origin, give, want, maximal) { Ok(r) => return Ok(r), diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_lock.rs b/polkadot/xcm/xcm-executor/src/traits/asset_lock.rs index b5a2b22f5fc5b2eae80a5f5a42b22cd99d82901a..b6270c529452133c011fef24b9ac63e057acb91a 100644 --- a/polkadot/xcm/xcm-executor/src/traits/asset_lock.rs +++ b/polkadot/xcm/xcm-executor/src/traits/asset_lock.rs @@ -79,9 +79,9 @@ pub trait AssetLock { /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or /// `Self::UnlockTicket`. fn prepare_lock( - unlocker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + unlocker: Location, + asset: Asset, + owner: Location, ) -> Result; /// Prepare to unlock an asset. On success, a `Self::UnlockTicket` it returned, which can be @@ -90,9 +90,9 @@ pub trait AssetLock { /// WARNING: Don't call this with an undropped instance of `Self::LockTicket` or /// `Self::UnlockTicket`. fn prepare_unlock( - locker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + locker: Location, + asset: Asset, + owner: Location, ) -> Result; /// Handler for when a location reports to us that an asset has been locked for us to unlock @@ -102,11 +102,7 @@ pub trait AssetLock { /// sending chain can ensure the lock does not remain. /// /// We should only act upon this message if we believe that the `origin` is honest. - fn note_unlockable( - locker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, - ) -> Result<(), LockError>; + fn note_unlockable(locker: Location, asset: Asset, owner: Location) -> Result<(), LockError>; /// Handler for when an owner wishes to unlock an asset on a remote chain. /// @@ -115,9 +111,9 @@ pub trait AssetLock { /// /// WARNING: Don't call this with an undropped instance of `Self::ReduceTicket`. fn prepare_reduce_unlockable( - locker: MultiLocation, - asset: MultiAsset, - owner: MultiLocation, + locker: Location, + asset: Asset, + owner: Location, ) -> Result; } @@ -125,27 +121,19 @@ impl AssetLock for () { type LockTicket = Infallible; type UnlockTicket = Infallible; type ReduceTicket = Infallible; - fn prepare_lock( - _: MultiLocation, - _: MultiAsset, - _: MultiLocation, - ) -> Result { + fn prepare_lock(_: Location, _: Asset, _: Location) -> Result { Err(LockError::NotApplicable) } - fn prepare_unlock( - _: MultiLocation, - _: MultiAsset, - _: MultiLocation, - ) -> Result { + fn prepare_unlock(_: Location, _: Asset, _: Location) -> Result { Err(LockError::NotApplicable) } - fn note_unlockable(_: MultiLocation, _: MultiAsset, _: MultiLocation) -> Result<(), LockError> { + fn note_unlockable(_: Location, _: Asset, _: Location) -> Result<(), LockError> { Err(LockError::NotApplicable) } fn prepare_reduce_unlockable( - _: MultiLocation, - _: MultiAsset, - _: MultiLocation, + _: Location, + _: Asset, + _: Location, ) -> Result { Err(LockError::NotApplicable) } diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs new file mode 100644 index 0000000000000000000000000000000000000000..5da3d1da37c8117d2f0f754c4cfea1b3e638cb06 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs @@ -0,0 +1,96 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use crate::traits::TransactAsset; +use frame_support::traits::ContainsPair; +use scale_info::TypeInfo; +use sp_runtime::codec::{Decode, Encode}; +use xcm::prelude::*; + +/// Errors related to determining asset transfer support. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum Error { + /// Invalid non-concrete asset. + NotConcrete, + /// Reserve chain could not be determined for assets. + UnknownReserve, +} + +/// Specify which type of asset transfer is required for a particular `(asset, dest)` combination. +#[derive(Clone, PartialEq, Debug)] +pub enum TransferType { + /// should teleport `asset` to `dest` + Teleport, + /// should reserve-transfer `asset` to `dest`, using local chain as reserve + LocalReserve, + /// should reserve-transfer `asset` to `dest`, using `dest` as reserve + DestinationReserve, + /// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve + RemoteReserve(Location), +} + +/// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve` +/// configurations. +pub trait XcmAssetTransfers { + /// Combinations of (Asset, Location) pairs which we trust as reserves. Meaning + /// reserve-based-transfers are to be used for assets matching this filter. + type IsReserve: ContainsPair; + + /// Combinations of (Asset, Location) pairs which we trust as teleporters. Meaning teleports are + /// to be used for assets matching this filter. + type IsTeleporter: ContainsPair; + + /// How to withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + /// Determine transfer type to be used for transferring `asset` from local chain to `dest`. + fn determine_for(asset: &Asset, dest: &Location) -> Result { + if Self::IsTeleporter::contains(asset, dest) { + // we trust destination for teleporting asset + return Ok(TransferType::Teleport) + } else if Self::IsReserve::contains(asset, dest) { + // we trust destination as asset reserve location + return Ok(TransferType::DestinationReserve) + } + + // try to determine reserve location based on asset id/location + let asset_location = asset.id.0.chain_location(); + if asset_location == Location::here() || + Self::IsTeleporter::contains(asset, &asset_location) + { + // if the asset is local, then it's a local reserve + // it's also a local reserve if the asset's location is not `here` but it's a location + // where it can be teleported to `here` => local reserve + Ok(TransferType::LocalReserve) + } else if Self::IsReserve::contains(asset, &asset_location) { + // remote location that is recognized as reserve location for asset + Ok(TransferType::RemoteReserve(asset_location)) + } else { + // remote location that is not configured either as teleporter or reserve => cannot + // determine asset reserve + Err(Error::UnknownReserve) + } + } +} + +impl XcmAssetTransfers for () { + type IsReserve = (); + type IsTeleporter = (); + type AssetTransactor = (); + fn determine_for(_: &Asset, _: &Location) -> Result { + return Err(Error::UnknownReserve); + } +} diff --git a/polkadot/xcm/xcm-executor/src/traits/conversion.rs b/polkadot/xcm/xcm-executor/src/traits/conversion.rs index 1fcdf21405784860f39a51f2e7b1a7b1e0baa459..9e2f4c83997ac2b370536822454bcfbea41c4896 100644 --- a/polkadot/xcm/xcm-executor/src/traits/conversion.rs +++ b/polkadot/xcm/xcm-executor/src/traits/conversion.rs @@ -22,12 +22,12 @@ use xcm::latest::prelude::*; /// Means of converting a location into an account identifier. pub trait ConvertLocation { /// Convert the `location` into `Some` account ID, or `None` if not possible. - fn convert_location(location: &MultiLocation) -> Option; + fn convert_location(location: &Location) -> Option; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl ConvertLocation for Tuple { - fn convert_location(l: &MultiLocation) -> Option { + fn convert_location(l: &Location) -> Option { for_tuples!( #( match Tuple::convert_location(l) { Some(result) => return Some(result), @@ -45,15 +45,15 @@ impl ConvertLocation for Tuple { /// different `origin` of type `Origin` which is passed to the next convert item. /// /// ```rust -/// # use xcm::latest::{MultiLocation, Junctions, Junction, OriginKind}; +/// # use xcm::latest::{Location, Junctions, Junction, OriginKind}; /// # use staging_xcm_executor::traits::ConvertOrigin; /// // A convertor that will bump the para id and pass it to the next one. /// struct BumpParaId; /// impl ConvertOrigin for BumpParaId { -/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { -/// match origin.into() { -/// MultiLocation { parents: 0, interior: Junctions::X1(Junction::Parachain(id)) } => { -/// Err(Junctions::X1(Junction::Parachain(id + 1)).into()) +/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { +/// match origin.into().unpack() { +/// (0, [Junction::Parachain(id)]) => { +/// Err([Junction::Parachain(id + 1)].into()) /// } /// _ => unreachable!() /// } @@ -62,17 +62,18 @@ impl ConvertLocation for Tuple { /// /// struct AcceptPara7; /// impl ConvertOrigin for AcceptPara7 { -/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { -/// match origin.into() { -/// MultiLocation { parents: 0, interior: Junctions::X1(Junction::Parachain(id)) } if id == 7 => { +/// fn convert_origin(origin: impl Into, _: OriginKind) -> Result { +/// let origin = origin.into(); +/// match origin.unpack() { +/// (0, [Junction::Parachain(id)]) if *id == 7 => { /// Ok(7) /// } -/// o => Err(o) +/// _ => Err(origin) /// } /// } /// } /// # fn main() { -/// let origin: MultiLocation = Junctions::X1(Junction::Parachain(6)).into(); +/// let origin: Location = [Junction::Parachain(6)].into(); /// assert!( /// <(BumpParaId, AcceptPara7) as ConvertOrigin>::convert_origin(origin, OriginKind::Native) /// .is_ok() @@ -81,18 +82,12 @@ impl ConvertLocation for Tuple { /// ``` pub trait ConvertOrigin { /// Attempt to convert `origin` to the generic `Origin` whilst consuming it. - fn convert_origin( - origin: impl Into, - kind: OriginKind, - ) -> Result; + fn convert_origin(origin: impl Into, kind: OriginKind) -> Result; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl ConvertOrigin for Tuple { - fn convert_origin( - origin: impl Into, - kind: OriginKind, - ) -> Result { + fn convert_origin(origin: impl Into, kind: OriginKind) -> Result { for_tuples!( #( let origin = match Tuple::convert_origin(origin, kind) { Err(o) => o, diff --git a/polkadot/xcm/xcm-executor/src/traits/drop_assets.rs b/polkadot/xcm/xcm-executor/src/traits/drop_assets.rs index 9753f3a4213fd98b903bd9dbc3496f32445a714b..339d485d9795bee9d077ac91b680f341c61cca05 100644 --- a/polkadot/xcm/xcm-executor/src/traits/drop_assets.rs +++ b/polkadot/xcm/xcm-executor/src/traits/drop_assets.rs @@ -14,28 +14,28 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::Assets; +use crate::AssetsInHolding; use core::marker::PhantomData; use frame_support::traits::Contains; -use xcm::latest::{MultiAssets, MultiLocation, Weight, XcmContext}; +use xcm::latest::{Assets, Location, Weight, XcmContext}; -/// Define a handler for when some non-empty `Assets` value should be dropped. +/// Define a handler for when some non-empty `AssetsInHolding` value should be dropped. pub trait DropAssets { /// Handler for receiving dropped assets. Returns the weight consumed by this operation. - fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight; + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight; } impl DropAssets for () { - fn drop_assets(_origin: &MultiLocation, _assets: Assets, _context: &XcmContext) -> Weight { + fn drop_assets(_origin: &Location, _assets: AssetsInHolding, _context: &XcmContext) -> Weight { Weight::zero() } } /// Morph a given `DropAssets` implementation into one which can filter based on assets. This can -/// be used to ensure that `Assets` values which hold no value are ignored. +/// be used to ensure that `AssetsInHolding` values which hold no value are ignored. pub struct FilterAssets(PhantomData<(D, A)>); -impl> DropAssets for FilterAssets { - fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight { +impl> DropAssets for FilterAssets { + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { if A::contains(&assets) { D::drop_assets(origin, assets, context) } else { @@ -49,8 +49,8 @@ impl> DropAssets for FilterAssets { /// asset trap facility don't get to use it. pub struct FilterOrigin(PhantomData<(D, O)>); -impl> DropAssets for FilterOrigin { - fn drop_assets(origin: &MultiLocation, assets: Assets, context: &XcmContext) -> Weight { +impl> DropAssets for FilterOrigin { + fn drop_assets(origin: &Location, assets: AssetsInHolding, context: &XcmContext) -> Weight { if O::contains(origin) { D::drop_assets(origin, assets, context) } else { @@ -64,9 +64,9 @@ pub trait ClaimAssets { /// Claim any assets available to `origin` and return them in a single `Assets` value, together /// with the weight used by this operation. fn claim_assets( - origin: &MultiLocation, - ticket: &MultiLocation, - what: &MultiAssets, + origin: &Location, + ticket: &Location, + what: &Assets, context: &XcmContext, ) -> bool; } @@ -74,9 +74,9 @@ pub trait ClaimAssets { #[impl_trait_for_tuples::impl_for_tuples(30)] impl ClaimAssets for Tuple { fn claim_assets( - origin: &MultiLocation, - ticket: &MultiLocation, - what: &MultiAssets, + origin: &Location, + ticket: &Location, + what: &Assets, context: &XcmContext, ) -> bool { for_tuples!( #( diff --git a/polkadot/xcm/xcm-executor/src/traits/export.rs b/polkadot/xcm/xcm-executor/src/traits/export.rs index 7aeccd44566a67732617e70276751210c7da1ce4..78aa68ce2644a8bd6d4ab1075d9d93923b31c3cc 100644 --- a/polkadot/xcm/xcm-executor/src/traits/export.rs +++ b/polkadot/xcm/xcm-executor/src/traits/export.rs @@ -51,8 +51,8 @@ pub trait ExportXcm { fn validate( network: NetworkId, channel: u32, - universal_source: &mut Option, - destination: &mut Option, + universal_source: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult; @@ -71,11 +71,11 @@ impl ExportXcm for Tuple { fn validate( network: NetworkId, channel: u32, - universal_source: &mut Option, - destination: &mut Option, + universal_source: &mut Option, + destination: &mut Option, message: &mut Option>, ) -> SendResult { - let mut maybe_cost: Option = None; + let mut maybe_cost: Option = None; let one_ticket: Self::Ticket = (for_tuples! { #( if maybe_cost.is_some() { None @@ -112,8 +112,8 @@ impl ExportXcm for Tuple { pub fn validate_export( network: NetworkId, channel: u32, - universal_source: InteriorMultiLocation, - dest: InteriorMultiLocation, + universal_source: InteriorLocation, + dest: InteriorLocation, msg: Xcm<()>, ) -> SendResult { T::validate(network, channel, &mut Some(universal_source), &mut Some(dest), &mut Some(msg)) @@ -130,10 +130,10 @@ pub fn validate_export( pub fn export_xcm( network: NetworkId, channel: u32, - universal_source: InteriorMultiLocation, - dest: InteriorMultiLocation, + universal_source: InteriorLocation, + dest: InteriorLocation, msg: Xcm<()>, -) -> Result<(XcmHash, MultiAssets), SendError> { +) -> Result<(XcmHash, Assets), SendError> { let (ticket, price) = T::validate( network, channel, diff --git a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs index 2b2f21927f2e91d663184833813151fa70fb31bc..b6e303daaad891fd98918fee4729941458236dba 100644 --- a/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs +++ b/polkadot/xcm/xcm-executor/src/traits/fee_manager.rs @@ -18,16 +18,16 @@ use xcm::prelude::*; /// Handle stuff to do with taking fees in certain XCM instructions. pub trait FeeManager { - /// Determine if a fee which would normally payable should be waived. - fn is_waived(origin: Option<&MultiLocation>, r: FeeReason) -> bool; + /// Determine if a fee should be waived. + fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool; /// Do something with the fee which has been paid. Doing nothing here silently burns the /// fees. - fn handle_fee(fee: MultiAssets, context: Option<&XcmContext>); + fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason); } /// Context under which a fee is paid. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum FeeReason { /// When a reporting instruction is called. Report, @@ -42,7 +42,7 @@ pub enum FeeReason { /// When the `QueryPallet` instruction is called. QueryPallet, /// When the `ExportMessage` instruction is called (and includes the network ID). - Export(NetworkId), + Export { network: NetworkId, destination: InteriorLocation }, /// The `charge_fees` API. ChargeFees, /// When the `LockAsset` instruction is called. @@ -52,8 +52,9 @@ pub enum FeeReason { } impl FeeManager for () { - fn is_waived(_: Option<&MultiLocation>, _: FeeReason) -> bool { + fn is_waived(_: Option<&Location>, _: FeeReason) -> bool { false } - fn handle_fee(_: MultiAssets, _: Option<&XcmContext>) {} + + fn handle_fee(_: Assets, _: Option<&XcmContext>, _: FeeReason) {} } diff --git a/polkadot/xcm/xcm-executor/src/traits/filter_asset_location.rs b/polkadot/xcm/xcm-executor/src/traits/filter_asset_location.rs index b162a8b0729ded0d8b75b663fcf03edd6f229429..5d0c32890be98ffcb388a2dc9359a013b886fc52 100644 --- a/polkadot/xcm/xcm-executor/src/traits/filter_asset_location.rs +++ b/polkadot/xcm/xcm-executor/src/traits/filter_asset_location.rs @@ -15,21 +15,21 @@ // along with Polkadot. If not, see . use frame_support::traits::ContainsPair; -use xcm::latest::{MultiAsset, MultiLocation}; +use xcm::latest::{Asset, Location}; /// Filters assets/location pairs. /// /// Can be amalgamated into tuples. If any item returns `true`, it short-circuits, else `false` is /// returned. -#[deprecated = "Use `frame_support::traits::ContainsPair` instead"] +#[deprecated = "Use `frame_support::traits::ContainsPair` instead"] pub trait FilterAssetLocation { /// A filter to distinguish between asset/location pairs. - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool; + fn contains(asset: &Asset, origin: &Location) -> bool; } #[allow(deprecated)] -impl> FilterAssetLocation for T { - fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { +impl> FilterAssetLocation for T { + fn contains(asset: &Asset, origin: &Location) -> bool { T::contains(asset, origin) } } diff --git a/polkadot/xcm/xcm-executor/src/traits/hrmp.rs b/polkadot/xcm/xcm-executor/src/traits/hrmp.rs new file mode 100644 index 0000000000000000000000000000000000000000..b6bbb9316d7537559e895b379b855bf10f0d303d --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/traits/hrmp.rs @@ -0,0 +1,56 @@ +// 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 xcm::latest::Result as XcmResult; + +/// Executes logic when a `HrmpNewChannelOpenRequest` XCM notification is received. +pub trait HandleHrmpNewChannelOpenRequest { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult; +} + +/// Executes optional logic when a `HrmpChannelAccepted` XCM notification is received. +pub trait HandleHrmpChannelAccepted { + fn handle(recipient: u32) -> XcmResult; +} + +/// Executes optional logic when a `HrmpChannelClosing` XCM notification is received. +pub trait HandleHrmpChannelClosing { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpNewChannelOpenRequest for Tuple { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(sender, max_message_size, max_capacity)?; )* ); + Ok(()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpChannelAccepted for Tuple { + fn handle(recipient: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(recipient)?; )* ); + Ok(()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl HandleHrmpChannelClosing for Tuple { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult { + for_tuples!( #( Tuple::handle(initiator, sender, recipient)?; )* ); + Ok(()) + } +} diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index a9439968fa6ca5b2d1bfae6097d985ae5a887f19..aa3f0d26e3025781eacefcf4b6cfdfe140cb39a5 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -20,10 +20,12 @@ mod conversion; pub use conversion::{CallDispatcher, ConvertLocation, ConvertOrigin, WithOriginFilter}; mod drop_assets; pub use drop_assets::{ClaimAssets, DropAssets}; -mod asset_lock; -pub use asset_lock::{AssetLock, Enact, LockError}; mod asset_exchange; pub use asset_exchange::AssetExchange; +mod asset_lock; +pub use asset_lock::{AssetLock, Enact, LockError}; +mod asset_transfer; +pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTransfers}; mod export; pub use export::{export_xcm, validate_export, ExportXcm}; mod fee_manager; @@ -37,10 +39,16 @@ pub use token_matching::{ }; mod on_response; pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier}; +mod process_transaction; +pub use process_transaction::ProcessTransaction; mod should_execute; pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; mod transact_asset; pub use transact_asset::TransactAsset; +mod hrmp; +pub use hrmp::{ + HandleHrmpChannelAccepted, HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, +}; mod weight; #[deprecated = "Use `sp_runtime::traits::` instead"] pub use sp_runtime::traits::{Identity, TryConvertInto as JustTry}; @@ -50,8 +58,9 @@ pub mod prelude { pub use super::{ export_xcm, validate_export, AssetExchange, AssetLock, ClaimAssets, ConvertOrigin, DropAssets, Enact, Error, ExportXcm, FeeManager, FeeReason, LockError, MatchesFungible, - MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, OnResponse, ShouldExecute, - TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, WithOriginFilter, + MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, OnResponse, ProcessTransaction, + ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, + WithOriginFilter, }; #[allow(deprecated)] pub use super::{Identity, JustTry}; diff --git a/polkadot/xcm/xcm-executor/src/traits/on_response.rs b/polkadot/xcm/xcm-executor/src/traits/on_response.rs index 3558160dc87d37acc03833518875d980a458f4e2..952bd2d0040aca89f21d7a1eabbb6667ccb8a68c 100644 --- a/polkadot/xcm/xcm-executor/src/traits/on_response.rs +++ b/polkadot/xcm/xcm-executor/src/traits/on_response.rs @@ -14,49 +14,44 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::Xcm; +use crate::{Junctions::Here, Xcm}; use core::result; -use frame_support::pallet_prelude::{Get, TypeInfo}; -use parity_scale_codec::{FullCodec, MaxEncodedLen}; +use frame_support::{ + pallet_prelude::{Get, TypeInfo}, + parameter_types, +}; +use parity_scale_codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::Zero; use sp_std::fmt::Debug; use xcm::latest::{ - Error as XcmError, InteriorMultiLocation, MultiLocation, QueryId, Response, - Result as XcmResult, Weight, XcmContext, + Error as XcmError, InteriorLocation, Location, QueryId, Response, Result as XcmResult, Weight, + XcmContext, }; /// Define what needs to be done upon receiving a query response. pub trait OnResponse { /// Returns `true` if we are expecting a response from `origin` for query `query_id` that was /// queried by `querier`. - fn expecting_response( - origin: &MultiLocation, - query_id: u64, - querier: Option<&MultiLocation>, - ) -> bool; + fn expecting_response(origin: &Location, query_id: u64, querier: Option<&Location>) -> bool; /// Handler for receiving a `response` from `origin` relating to `query_id` initiated by /// `querier`. fn on_response( - origin: &MultiLocation, + origin: &Location, query_id: u64, - querier: Option<&MultiLocation>, + querier: Option<&Location>, response: Response, max_weight: Weight, context: &XcmContext, ) -> Weight; } impl OnResponse for () { - fn expecting_response( - _origin: &MultiLocation, - _query_id: u64, - _querier: Option<&MultiLocation>, - ) -> bool { + fn expecting_response(_origin: &Location, _query_id: u64, _querier: Option<&Location>) -> bool { false } fn on_response( - _origin: &MultiLocation, + _origin: &Location, _query_id: u64, - _querier: Option<&MultiLocation>, + _querier: Option<&Location>, _response: Response, _max_weight: Weight, _context: &XcmContext, @@ -76,7 +71,7 @@ pub trait VersionChangeNotifier { /// If the `location` has an ongoing notification and when this function is called, then an /// error should be returned. fn start( - location: &MultiLocation, + location: &Location, query_id: QueryId, max_weight: Weight, context: &XcmContext, @@ -84,26 +79,26 @@ pub trait VersionChangeNotifier { /// Stop notifying `location` should the XCM change. Returns an error if there is no existing /// notification set up. - fn stop(location: &MultiLocation, context: &XcmContext) -> XcmResult; + fn stop(location: &Location, context: &XcmContext) -> XcmResult; /// Return true if a location is subscribed to XCM version changes. - fn is_subscribed(location: &MultiLocation) -> bool; + fn is_subscribed(location: &Location) -> bool; } impl VersionChangeNotifier for () { - fn start(_: &MultiLocation, _: QueryId, _: Weight, _: &XcmContext) -> XcmResult { + fn start(_: &Location, _: QueryId, _: Weight, _: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } - fn stop(_: &MultiLocation, _: &XcmContext) -> XcmResult { + fn stop(_: &Location, _: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } - fn is_subscribed(_: &MultiLocation) -> bool { + fn is_subscribed(_: &Location) -> bool { false } } /// The possible state of an XCM query response. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Encode, Decode)] pub enum QueryResponseStatus { /// The response has arrived, and includes the inner Response and the block number it arrived /// at. @@ -129,15 +124,15 @@ pub trait QueryHandler { + PartialEq + Debug + Copy; - type BlockNumber: Zero; + type BlockNumber: Zero + Encode; type Error; - type UniversalLocation: Get; + type UniversalLocation: Get; /// Attempt to create a new query ID and register it as a query that is yet to respond. fn new_query( - responder: impl Into, + responder: impl Into, timeout: Self::BlockNumber, - match_querier: impl Into, + match_querier: impl Into, ) -> QueryId; /// Consume `message` and return another which is equivalent to it except that it reports @@ -154,7 +149,7 @@ pub trait QueryHandler { /// The response can be queried with `take_response`. fn report_outcome( message: &mut Xcm<()>, - responder: impl Into, + responder: impl Into, timeout: Self::BlockNumber, ) -> result::Result; @@ -165,3 +160,36 @@ pub trait QueryHandler { #[cfg(feature = "runtime-benchmarks")] fn expect_response(id: Self::QueryId, response: Response); } + +parameter_types! { + pub UniversalLocation: InteriorLocation = Here; +} + +impl QueryHandler for () { + type BlockNumber = u64; + type Error = (); + type QueryId = u64; + type UniversalLocation = UniversalLocation; + + fn take_response(_query_id: Self::QueryId) -> QueryResponseStatus { + QueryResponseStatus::NotFound + } + fn new_query( + _responder: impl Into, + _timeout: Self::BlockNumber, + _match_querier: impl Into, + ) -> Self::QueryId { + 0u64 + } + + fn report_outcome( + _message: &mut Xcm<()>, + _responder: impl Into, + _timeout: Self::BlockNumber, + ) -> Result { + Err(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn expect_response(_id: Self::QueryId, _response: crate::Response) {} +} diff --git a/polkadot/xcm/xcm-executor/src/traits/process_transaction.rs b/polkadot/xcm/xcm-executor/src/traits/process_transaction.rs new file mode 100644 index 0000000000000000000000000000000000000000..22ad8755b9c90fac13fd414c52ee44d6e6792c70 --- /dev/null +++ b/polkadot/xcm/xcm-executor/src/traits/process_transaction.rs @@ -0,0 +1,57 @@ +// Copyright 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 xcm::latest::prelude::*; + +/// Provides mechanisms for transactional processing of XCM instructions. +/// +/// This trait defines the behavior required to process XCM instructions in a transactional +/// manner. Implementers of this trait can ensure that XCM instructions are executed +/// atomically, meaning they either fully succeed or fully fail without any partial effects. +/// +/// Implementers of this trait can also choose to not process XCM instructions transactionally. +/// This is useful for cases where the implementer is not able to provide transactional guarantees. +/// In this case the `IS_TRANSACTIONAL` constant should be set to `false`. +/// The `()` type implements this trait in a non-transactional manner. +pub trait ProcessTransaction { + /// Whether or not the implementor of the this trait is actually transactional. + const IS_TRANSACTIONAL: bool; + + /// Processes an XCM instruction encapsulated within the provided closure. Responsible for + /// processing an XCM instruction transactionally. If the closure returns an error, any + /// changes made during its execution should be rolled back. In the case where the + /// implementer is not able to provide transactional guarantees, the closure should be + /// executed as is. + /// # Parameters + /// - `f`: A closure that encapsulates the XCM instruction being processed. It will return a + /// `Result` indicating the success or failure of the instruction. + /// + /// # Returns + /// - A `Result` indicating the overall success or failure of the transactional process. + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>; +} + +impl ProcessTransaction for () { + const IS_TRANSACTIONAL: bool = false; + fn process(f: F) -> Result<(), XcmError> + where + F: FnOnce() -> Result<(), XcmError>, + { + f() + } +} diff --git a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs index d85458b54709d0a9c80dcf661723a198fac85c88..449e82b5a6e2c3ecaf43b35608222b88ae6c8bcc 100644 --- a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs +++ b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs @@ -16,7 +16,7 @@ use frame_support::traits::ProcessMessageError; use sp_std::result::Result; -use xcm::latest::{Instruction, MultiLocation, Weight, XcmHash}; +use xcm::latest::{Instruction, Location, Weight, XcmHash}; /// Properyies of an XCM message and its imminent execution. #[derive(Clone, Eq, PartialEq, Debug)] @@ -43,7 +43,7 @@ pub trait ShouldExecute { /// - `properties`: Various pre-established properties of the message which may be mutated by /// this API. fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -53,7 +53,7 @@ pub trait ShouldExecute { #[impl_trait_for_tuples::impl_for_tuples(30)] impl ShouldExecute for Tuple { fn should_execute( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -87,7 +87,7 @@ impl ShouldExecute for Tuple { /// if any of the tuple elements returns true. pub trait CheckSuspension { fn is_suspended( - origin: &MultiLocation, + origin: &Location, instructions: &mut [Instruction], max_weight: Weight, properties: &mut Properties, @@ -97,7 +97,7 @@ pub trait CheckSuspension { #[impl_trait_for_tuples::impl_for_tuples(30)] impl CheckSuspension for Tuple { fn is_suspended( - origin: &MultiLocation, + origin: &Location, instruction: &mut [Instruction], max_weight: Weight, properties: &mut Properties, diff --git a/polkadot/xcm/xcm-executor/src/traits/token_matching.rs b/polkadot/xcm/xcm-executor/src/traits/token_matching.rs index ad65a8630217248c97a1fdaef94cde48aef1b42f..e9a7e3ad845daf2f3f0a8da05c7d9d3d3711a291 100644 --- a/polkadot/xcm/xcm-executor/src/traits/token_matching.rs +++ b/polkadot/xcm/xcm-executor/src/traits/token_matching.rs @@ -18,12 +18,12 @@ use sp_std::result; use xcm::latest::prelude::*; pub trait MatchesFungible { - fn matches_fungible(a: &MultiAsset) -> Option; + fn matches_fungible(a: &Asset) -> Option; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl MatchesFungible for Tuple { - fn matches_fungible(a: &MultiAsset) -> Option { + fn matches_fungible(a: &Asset) -> Option { for_tuples!( #( match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } )* ); @@ -33,12 +33,12 @@ impl MatchesFungible for Tuple { } pub trait MatchesNonFungible { - fn matches_nonfungible(a: &MultiAsset) -> Option; + fn matches_nonfungible(a: &Asset) -> Option; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl MatchesNonFungible for Tuple { - fn matches_nonfungible(a: &MultiAsset) -> Option { + fn matches_nonfungible(a: &Asset) -> Option { for_tuples!( #( match Tuple::matches_nonfungible(a) { o @ Some(_) => return o, _ => () } )* ); @@ -52,11 +52,11 @@ impl MatchesNonFungible for Tuple { pub enum Error { /// The given asset is not handled. (According to [`XcmError::AssetNotFound`]) AssetNotHandled, - /// `MultiLocation` to `AccountId` conversion failed. + /// `Location` to `AccountId` conversion failed. AccountIdConversionFailed, /// `u128` amount to currency `Balance` conversion failed. AmountToBalanceConversionFailed, - /// `MultiLocation` to `AssetId`/`ClassId` conversion failed. + /// `Location` to `AssetId`/`ClassId` conversion failed. AssetIdConversionFailed, /// `AssetInstance` to non-fungibles instance ID conversion failed. InstanceConversionFailed, @@ -77,12 +77,12 @@ impl From for XcmError { } pub trait MatchesFungibles { - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), Error>; + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl MatchesFungibles for Tuple { - fn matches_fungibles(a: &MultiAsset) -> result::Result<(AssetId, Balance), Error> { + fn matches_fungibles(a: &Asset) -> result::Result<(AssetId, Balance), Error> { for_tuples!( #( match Tuple::matches_fungibles(a) { o @ Ok(_) => return o, _ => () } )* ); @@ -92,12 +92,12 @@ impl MatchesFungibles for Tuple { } pub trait MatchesNonFungibles { - fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(AssetId, Instance), Error>; + fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error>; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl MatchesNonFungibles for Tuple { - fn matches_nonfungibles(a: &MultiAsset) -> result::Result<(AssetId, Instance), Error> { + fn matches_nonfungibles(a: &Asset) -> result::Result<(AssetId, Instance), Error> { for_tuples!( #( match Tuple::matches_nonfungibles(a) { o @ Ok(_) => return o, _ => () } )* ); diff --git a/polkadot/xcm/xcm-executor/src/traits/transact_asset.rs b/polkadot/xcm/xcm-executor/src/traits/transact_asset.rs index c51befff88a61d5a4c40422a66165c2f34daf8c3..e8a52d8256851b4baf9565ba5ddeb47ae28667a7 100644 --- a/polkadot/xcm/xcm-executor/src/traits/transact_asset.rs +++ b/polkadot/xcm/xcm-executor/src/traits/transact_asset.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::Assets; +use crate::AssetsInHolding; use sp_std::result::Result; -use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result as XcmResult, XcmContext}; +use xcm::latest::{Asset, Error as XcmError, Location, Result as XcmResult, XcmContext}; /// Facility for asset transacting. /// /// This should work with as many asset/location combinations as possible. Locations to support may -/// include non-account locations such as a `MultiLocation::X1(Junction::Parachain)`. Different +/// include non-account locations such as a `[Junction::Parachain]`. Different /// chains may handle them in different ways. /// /// Can be amalgamated as a tuple of items that implement this trait. In such executions, if any of @@ -31,11 +31,7 @@ pub trait TransactAsset { /// Ensure that `check_in` will do as expected. /// /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. - fn can_check_in( - _origin: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } @@ -56,16 +52,12 @@ pub trait TransactAsset { /// When composed as a tuple, all type-items are called. It is up to the implementer that there /// exists no value for `_what` which can cause side-effects for more than one of the /// type-items. - fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {} + fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {} /// Ensure that `check_out` will do as expected. /// /// When composed as a tuple, all type-items are called and at least one must result in `Ok`. - fn can_check_out( - _dest: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::Unimplemented) } @@ -82,16 +74,12 @@ pub trait TransactAsset { /// When composed as a tuple, all type-items are called. It is up to the implementer that there /// exists no value for `_what` which can cause side-effects for more than one of the /// type-items. - fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {} + fn check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) {} /// Deposit the `what` asset into the account of `who`. /// /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. - fn deposit_asset( - _what: &MultiAsset, - _who: &MultiLocation, - _context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { Err(XcmError::Unimplemented) } @@ -104,10 +92,10 @@ pub trait TransactAsset { /// /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. fn withdraw_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _maybe_context: Option<&XcmContext>, - ) -> Result { + ) -> Result { Err(XcmError::Unimplemented) } @@ -121,11 +109,11 @@ pub trait TransactAsset { /// turn has a default implementation that calls `internal_transfer_asset`. As such, **please /// do not call this method directly unless you know what you're doing**. fn internal_transfer_asset( - _asset: &MultiAsset, - _from: &MultiLocation, - _to: &MultiLocation, + _asset: &Asset, + _from: &Location, + _to: &Location, _context: &XcmContext, - ) -> Result { + ) -> Result { Err(XcmError::Unimplemented) } @@ -134,11 +122,11 @@ pub trait TransactAsset { /// Attempts to use `internal_transfer_asset` and if not available then falls back to using a /// two-part withdraw/deposit. fn transfer_asset( - asset: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + asset: &Asset, + from: &Location, + to: &Location, context: &XcmContext, - ) -> Result { + ) -> Result { match Self::internal_transfer_asset(asset, from, to, context) { Err(XcmError::AssetNotFound | XcmError::Unimplemented) => { let assets = Self::withdraw_asset(asset, from, Some(context))?; @@ -153,7 +141,7 @@ pub trait TransactAsset { #[impl_trait_for_tuples::impl_for_tuples(30)] impl TransactAsset for Tuple { - fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { for_tuples!( #( match Tuple::can_check_in(origin, what, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), @@ -170,13 +158,13 @@ impl TransactAsset for Tuple { Err(XcmError::AssetNotFound) } - fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { for_tuples!( #( Tuple::check_in(origin, what, context); )* ); } - fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { for_tuples!( #( match Tuple::can_check_out(dest, what, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), @@ -193,17 +181,13 @@ impl TransactAsset for Tuple { Err(XcmError::AssetNotFound) } - fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { for_tuples!( #( Tuple::check_out(dest, what, context); )* ); } - fn deposit_asset( - what: &MultiAsset, - who: &MultiLocation, - context: Option<&XcmContext>, - ) -> XcmResult { + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { for_tuples!( #( match Tuple::deposit_asset(what, who, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), @@ -221,10 +205,10 @@ impl TransactAsset for Tuple { } fn withdraw_asset( - what: &MultiAsset, - who: &MultiLocation, + what: &Asset, + who: &Location, maybe_context: Option<&XcmContext>, - ) -> Result { + ) -> Result { for_tuples!( #( match Tuple::withdraw_asset(what, who, maybe_context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), @@ -242,11 +226,11 @@ impl TransactAsset for Tuple { } fn internal_transfer_asset( - what: &MultiAsset, - from: &MultiLocation, - to: &MultiLocation, + what: &Asset, + from: &Location, + to: &Location, context: &XcmContext, - ) -> Result { + ) -> Result { for_tuples!( #( match Tuple::internal_transfer_asset(what, from, to, context) { Err(XcmError::AssetNotFound) | Err(XcmError::Unimplemented) => (), @@ -275,133 +259,109 @@ mod tests { pub struct NotFoundTransactor; impl TransactAsset for NotFoundTransactor { - fn can_check_in( - _origin: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::AssetNotFound) } - fn can_check_out( - _dest: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::AssetNotFound) } fn deposit_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, ) -> XcmResult { Err(XcmError::AssetNotFound) } fn withdraw_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, - ) -> Result { + ) -> Result { Err(XcmError::AssetNotFound) } fn internal_transfer_asset( - _what: &MultiAsset, - _from: &MultiLocation, - _to: &MultiLocation, + _what: &Asset, + _from: &Location, + _to: &Location, _context: &XcmContext, - ) -> Result { + ) -> Result { Err(XcmError::AssetNotFound) } } pub struct OverflowTransactor; impl TransactAsset for OverflowTransactor { - fn can_check_in( - _origin: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::Overflow) } - fn can_check_out( - _dest: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Err(XcmError::Overflow) } fn deposit_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, ) -> XcmResult { Err(XcmError::Overflow) } fn withdraw_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, - ) -> Result { + ) -> Result { Err(XcmError::Overflow) } fn internal_transfer_asset( - _what: &MultiAsset, - _from: &MultiLocation, - _to: &MultiLocation, + _what: &Asset, + _from: &Location, + _to: &Location, _context: &XcmContext, - ) -> Result { + ) -> Result { Err(XcmError::Overflow) } } pub struct SuccessfulTransactor; impl TransactAsset for SuccessfulTransactor { - fn can_check_in( - _origin: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Ok(()) } - fn can_check_out( - _dest: &MultiLocation, - _what: &MultiAsset, - _context: &XcmContext, - ) -> XcmResult { + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { Ok(()) } fn deposit_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, ) -> XcmResult { Ok(()) } fn withdraw_asset( - _what: &MultiAsset, - _who: &MultiLocation, + _what: &Asset, + _who: &Location, _context: Option<&XcmContext>, - ) -> Result { - Ok(Assets::default()) + ) -> Result { + Ok(AssetsInHolding::default()) } fn internal_transfer_asset( - _what: &MultiAsset, - _from: &MultiLocation, - _to: &MultiLocation, + _what: &Asset, + _from: &Location, + _to: &Location, _context: &XcmContext, - ) -> Result { - Ok(Assets::default()) + ) -> Result { + Ok(AssetsInHolding::default()) } } diff --git a/polkadot/xcm/xcm-executor/src/traits/weight.rs b/polkadot/xcm/xcm-executor/src/traits/weight.rs index bc40c10074f504fa9752d6a01fb5308c84948128..efb9a2dfb6efdf65a074f631c691c0bfec88d600 100644 --- a/polkadot/xcm/xcm-executor/src/traits/weight.rs +++ b/polkadot/xcm/xcm-executor/src/traits/weight.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::Assets; +use crate::AssetsInHolding; use sp_std::result::Result; use xcm::latest::{prelude::*, Weight}; @@ -33,7 +33,7 @@ pub trait WeightBounds { /// message. pub trait UniversalWeigher { /// Get the upper limit of weight required for `dest` to execute `message`. - fn weigh(dest: impl Into, message: Xcm<()>) -> Result; + fn weigh(dest: impl Into, message: Xcm<()>) -> Result; } /// Charge for weight in order to execute XCM. @@ -52,15 +52,15 @@ pub trait WeightTrader: Sized { fn buy_weight( &mut self, weight: Weight, - payment: Assets, + payment: AssetsInHolding, context: &XcmContext, - ) -> Result; + ) -> Result; /// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight /// was purchased using `buy_weight`. /// /// Default implementation refunds nothing. - fn refund_weight(&mut self, _weight: Weight, _context: &XcmContext) -> Option { + fn refund_weight(&mut self, _weight: Weight, _context: &XcmContext) -> Option { None } } @@ -74,9 +74,9 @@ impl WeightTrader for Tuple { fn buy_weight( &mut self, weight: Weight, - payment: Assets, + payment: AssetsInHolding, context: &XcmContext, - ) -> Result { + ) -> Result { let mut too_expensive_error_found = false; let mut last_error = None; for_tuples!( #( @@ -102,7 +102,7 @@ impl WeightTrader for Tuple { }) } - fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { + fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option { for_tuples!( #( if let Some(asset) = Tuple.refund_weight(weight, context) { return Some(asset); diff --git a/polkadot/xcm/xcm-simulator/Cargo.toml b/polkadot/xcm/xcm-simulator/Cargo.toml index eedcfa0032af41d2cb69844c54048d8abb5137c9..c1c48b6d4c5eb6195a60152f29bdb61e2ad80a79 100644 --- a/polkadot/xcm/xcm-simulator/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "xcm-simulator" description = "Test kit to simulate cross-chain message passing and XCM execution" -version = "1.0.0" +version = "7.0.0" 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..af471df60aba4dbc4d03692e7bfb84d99eb16420 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -4,12 +4,15 @@ description = "Examples of xcm-simulator usage." authors.workspace = true edition.workspace = true license.workspace = true -version = "1.0.0" +version = "7.0.0" + +[lints] +workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs index 85b8ad1c5cb7bceb03ac42320432900d19471e55..d134957fbc1c58a2fa59cfb73860d93d7ba74d5b 100644 --- a/polkadot/xcm/xcm-simulator/example/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs @@ -148,7 +148,7 @@ mod tests { use xcm_simulator::TestExt; // Helper function for forming buy execution message - fn buy_execution(fees: impl Into) -> Instruction { + fn buy_execution(fees: impl Into) -> Instruction { BuyExecution { fees: fees.into(), weight_limit: Unlimited } } @@ -642,7 +642,7 @@ mod tests { parachain::MsgQueue::received_dmp(), vec![Xcm(vec![QueryResponse { query_id: query_id_set, - response: Response::Assets(MultiAssets::new()), + response: Response::Assets(Assets::new()), max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), querier: Some(Here.into()), }])], diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain.rs b/polkadot/xcm/xcm-simulator/example/src/parachain.rs index bc7cba313828b76e6079dcaa9d7ba7f9342a046f..86401d756af3b7d86c34e394ae89c677684c0fb8 100644 --- a/polkadot/xcm/xcm-simulator/example/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/example/src/parachain.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{ContainsPair, EnsureOrigin, EnsureOriginWithArg, Everything, EverythingBut, Nothing}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; @@ -40,8 +40,8 @@ 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, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, + FungibleAdapter, IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; @@ -63,6 +63,7 @@ parameter_types! { pub const BlockHashCount: u64 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; @@ -106,16 +107,16 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } #[cfg(feature = "runtime-benchmarks")] pub struct UniquesHelper; #[cfg(feature = "runtime-benchmarks")] -impl pallet_uniques::BenchmarkHelper for UniquesHelper { - fn collection(i: u16) -> MultiLocation { +impl pallet_uniques::BenchmarkHelper for UniquesHelper { + fn collection(i: u16) -> Location { GeneralIndex(i as u128).into() } fn item(i: u16) -> AssetInstance { @@ -125,7 +126,7 @@ impl pallet_uniques::BenchmarkHelper for UniquesHe impl pallet_uniques::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type CollectionId = MultiLocation; + type CollectionId = Location; type ItemId = AssetInstance; type Currency = Balances; type CreateOrigin = ForeignCreators; @@ -147,12 +148,12 @@ impl pallet_uniques::Config for Runtime { // `EnsureOriginWithArg` impl for `CreateOrigin` which allows only XCM origins // which are locations containing the class location. pub struct ForeignCreators; -impl EnsureOriginWithArg for ForeignCreators { +impl EnsureOriginWithArg for ForeignCreators { type Success = AccountId; fn try_origin( o: RuntimeOrigin, - a: &MultiLocation, + a: &Location, ) -> sp_std::result::Result { let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; if !a.starts_with(&origin_location) { @@ -162,7 +163,7 @@ impl EnsureOriginWithArg for ForeignCreators { } #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin(a: &MultiLocation) -> Result { + fn try_successful_origin(a: &Location) -> Result { Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) } } @@ -173,9 +174,9 @@ parameter_types! { } parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const KsmLocation: Location = Location::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = Parachain(MsgQueue::parachain_id().into()).into(); } pub type LocationToAccountId = ( @@ -193,17 +194,17 @@ pub type XcmOriginToCallOrigin = ( parameter_types! { pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); - pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1, 1); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; - pub ForeignPrefix: MultiLocation = (Parent,).into(); + pub ForeignPrefix: Location = (Parent,).into(); } pub type LocalAssetTransactor = ( - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + FungibleAdapter, LocationToAccountId, AccountId, ()>, NonFungiblesAdapter< ForeignUniques, - ConvertedConcreteId, + ConvertedConcreteId, SovereignAccountOf, AccountId, NoChecking, @@ -215,9 +216,9 @@ pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; parameter_types! { - pub NftCollectionOne: MultiAssetFilter - = Wild(AllOf { fun: WildNonFungible, id: Concrete((Parent, GeneralIndex(1)).into()) }); - pub NftCollectionOneForRelay: (MultiAssetFilter, MultiLocation) + pub NftCollectionOne: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (AssetFilter, Location) = (NftCollectionOne::get(), (Parent,).into()); } pub type TrustedTeleporters = xcm_builder::Case; @@ -249,6 +250,10 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } #[frame_support::pallet] @@ -320,16 +325,23 @@ pub mod mock_msg_queue { max_weight: Weight, ) -> Result { let hash = Encode::using_encoded(&xcm, T::Hashing::hash); - let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let mut message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); let (result, event) = match Xcm::::try_from(xcm) { Ok(xcm) => { let location = (Parent, Parachain(sender.into())); - match T::XcmExecutor::execute_xcm(location, xcm, message_hash, max_weight) { - Outcome::Error(e) => (Err(e), Event::Fail(Some(hash), e)), - Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), + match T::XcmExecutor::prepare_and_execute( + location, + xcm, + &mut message_hash, + max_weight, + Weight::zero(), + ) { + Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), + Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), // As far as the caller is concerned, this was dispatched without error, so // we just report the weight used. - Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + Outcome::Incomplete { used, error } => + (Ok(used), Event::Fail(Some(hash), error)), } }, Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), @@ -370,7 +382,7 @@ pub mod mock_msg_queue { limit: Weight, ) -> Weight { for (_i, (_sent_at, data)) in iter.enumerate() { - let id = sp_io::hashing::blake2_256(&data[..]); + let mut id = sp_io::hashing::blake2_256(&data[..]); let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); match maybe_versioned { Err(_) => { @@ -379,7 +391,13 @@ pub mod mock_msg_queue { Ok(versioned) => match Xcm::try_from(versioned) { Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), Ok(x) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); + let outcome = T::XcmExecutor::prepare_and_execute( + Parent, + x.clone(), + &mut id, + limit, + Weight::zero(), + ); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); }, @@ -398,23 +416,16 @@ impl mock_msg_queue::Config for Runtime { pub type LocalOriginToLocation = SignedToAccountId32; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - pub struct TrustedLockerCase(PhantomData); -impl> ContainsPair - for TrustedLockerCase -{ - fn contains(origin: &MultiLocation, asset: &MultiAsset) -> bool { +impl> ContainsPair for TrustedLockerCase { + fn contains(origin: &Location, asset: &Asset) -> bool { let (o, a) = T::get(); a.matches(asset) && &o == origin } } parameter_types! { - pub RelayTokenForRelay: (MultiLocation, MultiAssetFilter) = (Parent.into(), Wild(AllOf { id: Concrete(Parent.into()), fun: WildFungible })); + pub RelayTokenForRelay: (Location, AssetFilter) = (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); } pub type TrustedLockers = TrustedLockerCase; @@ -442,8 +453,6 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } @@ -452,10 +461,10 @@ type Block = frame_system::mocking::MockBlock; construct_runtime!( pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, - ForeignUniques: pallet_uniques::{Pallet, Call, Storage, Event}, + System: frame_system, + Balances: pallet_balances, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, + ForeignUniques: pallet_uniques, } ); diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs index 4e9195a8454f5091dde7bcbf2c841eaaeb148285..377c77f30a473d178b5fa3c56a79abe7e2c2766a 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs @@ -17,7 +17,7 @@ //! Relay chain runtime mock. use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, Everything, Nothing, ProcessMessage, ProcessMessageError}, weights::{Weight, WeightMeter}, }; @@ -36,8 +36,8 @@ use xcm::latest::prelude::*; use xcm_builder::{ Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, + ConvertedConcreteId, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, + FungibleAdapter, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -49,6 +49,7 @@ parameter_types! { pub const BlockHashCount: u64 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; @@ -92,8 +93,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -118,17 +119,19 @@ impl pallet_uniques::Config for Runtime { type Helper = (); } -impl shared::Config for Runtime {} +impl shared::Config for Runtime { + type DisabledValidators = (); +} impl configuration::Config for Runtime { type WeightInfo = configuration::TestWeightInfo; } parameter_types! { - pub const TokenLocation: MultiLocation = Here.into_location(); + pub const TokenLocation: Location = Here.into_location(); pub RelayNetwork: NetworkId = ByGenesis([0; 32]); pub const AnyNetwork: Option = None; - pub UniversalLocation: InteriorMultiLocation = Here; + pub UniversalLocation: InteriorLocation = Here; pub UnitWeightCost: u64 = 1_000; } @@ -139,7 +142,7 @@ pub type LocationToAccountId = ( ); pub type LocalAssetTransactor = ( - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + FungibleAdapter, LocationToAccountId, AccountId, ()>, NonFungiblesAdapter< Uniques, ConvertedConcreteId, JustTry>, @@ -160,7 +163,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); pub TokensPerSecondPerByte: (AssetId, u128, u128) = - (Concrete(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); + (AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } @@ -194,15 +197,14 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type LocalOriginToLocation = SignedToAccountId32; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(1).into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; @@ -227,8 +229,6 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } @@ -284,11 +284,11 @@ impl pallet_message_queue::Config for Runtime { construct_runtime!( pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, - Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, - MessageQueue: pallet_message_queue::{Pallet, Event}, + System: frame_system, + Balances: pallet_balances, + ParasOrigin: origin, + XcmPallet: pallet_xcm, + Uniques: pallet_uniques, + MessageQueue: pallet_message_queue, } ); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index acf28bec4f19480ae8135cb4e7f1845fc8d76566..30644dc0e0a53fe485261f9d0e52533ba0641603 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -7,14 +7,19 @@ 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" -arbitrary = "1.2.0" +arbitrary = "1.3.2" scale-info = { version = "2.10.0", features = ["derive"] } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } +frame-executive = { path = "../../../../substrate/frame/executive" } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime" } pallet-balances = { path = "../../../../substrate/frame/balances" } pallet-message-queue = { path = "../../../../substrate/frame/message-queue" } sp-std = { path = "../../../../substrate/primitives/std" } @@ -32,6 +37,17 @@ polkadot-runtime-parachains = { path = "../../../runtime/parachains" } polkadot-parachain-primitives = { path = "../../../parachain" } [features] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-xcm/try-runtime", + "polkadot-runtime-parachains/try-runtime", + "sp-runtime/try-runtime", +] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/polkadot/xcm/xcm-simulator/fuzzer/README.md b/polkadot/xcm/xcm-simulator/fuzzer/README.md index 0b3fdd8ec776d5e78d37aa5b57e6c0762af21320..9c15ee881c1b6f5f9a1c30215a16e74abdda2b62 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/README.md +++ b/polkadot/xcm/xcm-simulator/fuzzer/README.md @@ -14,7 +14,7 @@ cargo install honggfuzz In this directory, run this command: ``` -cargo hfuzz run xcm-fuzzer +HFUZZ_BUILD_ARGS="--features=try-runtime" cargo hfuzz run xcm-fuzzer ``` ## Run a single input @@ -22,7 +22,7 @@ cargo hfuzz run xcm-fuzzer In this directory, run this command: ``` -cargo hfuzz run-debug xcm-fuzzer hfuzz_workspace/xcm-fuzzer/fuzzer_input_file +cargo run --features=try-runtime -- hfuzz_workspace/xcm-fuzzer/fuzzer_input_file ``` ## Generate coverage @@ -31,7 +31,7 @@ In this directory, run these four commands: ``` RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" \ -CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build +CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build --features=try-runtime ../../../target/debug/xcm-fuzzer hfuzz_workspace/xcm-fuzzer/input/ zip -0 ccov.zip `find ../../../target/ \( -name "*.gc*" -o -name "test-*.gc*" \) -print` grcov ccov.zip -s ../../../ -t html --llvm --branch --ignore-not-existing -o ./coverage diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index 0893c7c086f8bc0d7e57647eadcdf87e9c732da0..adf6cacd278b9f25a02f9199fbdabc0af3434414 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -23,7 +23,9 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::{traits::AccountIdConversion, BuildStorage}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; -use frame_support::assert_ok; +#[cfg(feature = "try-runtime")] +use frame_support::traits::{TryState, TryStateSelect::All}; +use frame_support::{assert_ok, traits::IntegrityTest}; use xcm::{latest::prelude::*, MAX_XCM_DECODE_DEPTH}; use arbitrary::{Arbitrary, Error, Unstructured}; @@ -98,7 +100,7 @@ impl<'a> Arbitrary<'a> for XcmMessage { if let Ok(message) = DecodeLimit::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut encoded_message) { - return Ok(XcmMessage { source, destination, message }) + return Ok(XcmMessage { source, destination, message }); } Err(Error::IncorrectFormat) } @@ -148,6 +150,21 @@ pub fn relay_ext() -> sp_io::TestExternalities { pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; +// We check XCM messages recursively for blocklisted messages +fn recursively_matches_blocklisted_messages(message: &Instruction<()>) -> bool { + match message { + DepositReserveAsset { xcm, .. } | + ExportMessage { xcm, .. } | + InitiateReserveWithdraw { xcm, .. } | + InitiateTeleport { xcm, .. } | + TransferReserveAsset { xcm, .. } | + SetErrorHandler(xcm) | + SetAppendix(xcm) => xcm.iter().any(recursively_matches_blocklisted_messages), + // The blocklisted message is the Transact instruction. + m => matches!(m, Transact { .. }), + } +} + fn run_input(xcm_messages: [XcmMessage; 5]) { MockNet::reset(); @@ -155,10 +172,15 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { println!(); for xcm_message in xcm_messages { + if xcm_message.message.iter().any(recursively_matches_blocklisted_messages) { + println!(" skipping message\n"); + continue; + } + if xcm_message.source % 4 == 0 { // We get the destination for the message let parachain_id = (xcm_message.destination % 3) + 1; - let destination: MultiLocation = Parachain(parachain_id).into(); + let destination: Location = Parachain(parachain_id).into(); #[cfg(not(fuzzing))] { println!(" source: Relay Chain"); @@ -176,7 +198,7 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { _ => ParaC::execute_with, }; // We get the destination for the message - let destination: MultiLocation = match xcm_message.destination % 4 { + let destination: Location = match xcm_message.destination % 4 { n @ 1..=3 => (Parent, Parachain(n)).into(), _ => Parent.into(), }; @@ -197,8 +219,22 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { } #[cfg(not(fuzzing))] println!(); + // We run integrity tests and try_runtime invariants + [ParaA::execute_with, ParaB::execute_with, ParaC::execute_with].iter().for_each( + |execute_with| { + execute_with(|| { + #[cfg(feature = "try-runtime")] + parachain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + parachain::AllPalletsWithSystem::integrity_test(); + }); + }, + ); + Relay::execute_with(|| { + #[cfg(feature = "try-runtime")] + relay_chain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + relay_chain::AllPalletsWithSystem::integrity_test(); + }); } - Relay::execute_with(|| {}); } fn main() { diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index 95f875eca06e5c6fcb7d0d5610e1719a5f1b15b5..cadfc1e7200c075433ebf295982cc49f8a0d06b1 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -18,16 +18,17 @@ use codec::{Decode, Encode}; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{Everything, Nothing}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; +use sp_core::ConstU32; use sp_runtime::{ - traits::{Hash, IdentityLookup}, - AccountId32, + generic, + traits::{AccountIdLookup, BlakeTwo256, Hash, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, }; use sp_std::prelude::*; @@ -38,44 +39,36 @@ use polkadot_parachain_primitives::primitives::{ }; use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ - AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, NativeAsset, + AccountId32Aliases, AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { @@ -95,8 +88,8 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } @@ -106,9 +99,9 @@ parameter_types! { } parameter_types! { - pub const KsmLocation: MultiLocation = MultiLocation::parent(); + pub const KsmLocation: Location = Location::parent(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; - pub UniversalLocation: InteriorMultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); + pub UniversalLocation: InteriorLocation = Parachain(MsgQueue::parachain_id().into()).into(); } pub type LocationToAccountId = ( @@ -125,13 +118,13 @@ pub type XcmOriginToCallOrigin = ( parameter_types! { pub const UnitWeightCost: Weight = Weight::from_parts(1, 1); - pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(Parent.into()), 1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1, 1); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + FungibleAdapter, LocationToAccountId, AccountId, ()>; pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; @@ -162,6 +155,10 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } #[frame_support::pallet] @@ -233,16 +230,23 @@ pub mod mock_msg_queue { max_weight: Weight, ) -> Result { let hash = Encode::using_encoded(&xcm, T::Hashing::hash); - let message_hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let mut message_hash = xcm.using_encoded(sp_io::hashing::blake2_256); let (result, event) = match Xcm::::try_from(xcm) { Ok(xcm) => { - let location = MultiLocation::new(1, X1(Parachain(sender.into()))); - match T::XcmExecutor::execute_xcm(location, xcm, message_hash, max_weight) { - Outcome::Error(e) => (Err(e), Event::Fail(Some(hash), e)), - Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), + let location = Location::new(1, [Parachain(sender.into())]); + match T::XcmExecutor::prepare_and_execute( + location, + xcm, + &mut message_hash, + max_weight, + Weight::zero(), + ) { + Outcome::Error { error } => (Err(error), Event::Fail(Some(hash), error)), + Outcome::Complete { used } => (Ok(used), Event::Success(Some(hash))), // As far as the caller is concerned, this was dispatched without error, so // we just report the weight used. - Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + Outcome::Incomplete { used, error } => + (Ok(used), Event::Fail(Some(hash), error)), } }, Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), @@ -283,7 +287,7 @@ pub mod mock_msg_queue { limit: Weight, ) -> Weight { for (_i, (_sent_at, data)) in iter.enumerate() { - let id = sp_io::hashing::blake2_256(&data[..]); + let mut id = sp_io::hashing::blake2_256(&data[..]); let maybe_msg = VersionedXcm::::decode(&mut &data[..]) .map(Xcm::::try_from); match maybe_msg { @@ -294,7 +298,13 @@ pub mod mock_msg_queue { Self::deposit_event(Event::UnsupportedVersion(id)); }, Ok(Ok(x)) => { - let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); + let outcome = T::XcmExecutor::prepare_and_execute( + Parent, + x.clone(), + &mut id, + limit, + Weight::zero(), + ); >::append(x); Self::deposit_event(Event::ExecutedDownward(id, outcome)); }, @@ -312,11 +322,6 @@ impl mock_msg_queue::Config for Runtime { pub type LocalOriginToLocation = SignedToAccountId32; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = EnsureXcmOrigin; @@ -340,19 +345,15 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } -type Block = frame_system::mocking::MockBlock; - construct_runtime!( pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + System: frame_system, + Balances: pallet_balances, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, } ); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index a29ead9e6c3beaed1ef5e625012e3866ef5808f1..3224df66cbe56fd50ecdc5931274f41332143134 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -17,14 +17,18 @@ //! Relay chain runtime mock. use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{Everything, Nothing, ProcessMessage, ProcessMessageError}, weights::{Weight, WeightMeter}, }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; -use sp_runtime::{traits::IdentityLookup, AccountId32}; +use sp_core::ConstU32; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, +}; use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{ @@ -35,43 +39,35 @@ use polkadot_runtime_parachains::{ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, - ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, FixedRateOfFungible, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = sp_runtime::traits::AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -91,29 +87,31 @@ impl pallet_balances::Config for Runtime { type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); - type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } -impl shared::Config for Runtime {} +impl shared::Config for Runtime { + type DisabledValidators = (); +} impl configuration::Config for Runtime { type WeightInfo = configuration::TestWeightInfo; } parameter_types! { - pub const TokenLocation: MultiLocation = Here.into_location(); + pub const TokenLocation: Location = Here.into_location(); pub const ThisNetwork: NetworkId = NetworkId::ByGenesis([0; 32]); pub const AnyNetwork: Option = None; - pub const UniversalLocation: InteriorMultiLocation = Here; + pub const UniversalLocation: InteriorLocation = Here; } pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; type LocalOriginConverter = ( SovereignSignedViaLocation, @@ -124,7 +122,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); - pub KsmPerSecondPerByte: (AssetId, u128, u128) = (Concrete(TokenLocation::get()), 1, 1); + pub KsmPerSecondPerByte: (AssetId, u128, u128) = (AssetId(TokenLocation::get()), 1, 1); pub const MaxInstructions: u32 = u32::MAX; pub const MaxAssetsIntoHolding: u32 = 64; } @@ -158,15 +156,14 @@ impl Config for XcmConfig { type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); } pub type LocalOriginToLocation = SignedToAccountId32; -#[cfg(feature = "runtime-benchmarks")] -parameter_types! { - pub ReachableDest: Option = Some(Parachain(1).into()); -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; @@ -191,8 +188,6 @@ impl pallet_xcm::Config for Runtime { type MaxRemoteLockConsumers = ConstU32<0>; type RemoteLockConsumerIdentifier = (); type WeightInfo = pallet_xcm::TestWeightInfo; - #[cfg(feature = "runtime-benchmarks")] - type ReachableDest = ReachableDest; type AdminOrigin = EnsureRoot; } @@ -202,8 +197,6 @@ parameter_types! { impl origin::Config for Runtime {} -type Block = frame_system::mocking::MockBlock; - parameter_types! { /// Amount of weight that can be spent per block to service messages. pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000); @@ -252,10 +245,10 @@ impl pallet_message_queue::Config for Runtime { construct_runtime!( pub enum Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, - MessageQueue: pallet_message_queue::{Pallet, Event}, + System: frame_system, + Balances: pallet_balances, + ParasOrigin: origin, + XcmPallet: pallet_xcm, + MessageQueue: pallet_message_queue, } ); diff --git a/polkadot/xcm/xcm-simulator/src/lib.rs b/polkadot/xcm/xcm-simulator/src/lib.rs index b38465b3d4a2cb9d2a83b35b2f9f0c2fc484c4a4..7efbc658bbfb8bc3fadfa037ae966ca935816ba6 100644 --- a/polkadot/xcm/xcm-simulator/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/src/lib.rs @@ -258,9 +258,9 @@ macro_rules! __impl_ext { } thread_local! { - pub static PARA_MESSAGE_BUS: RefCell)>> + pub static PARA_MESSAGE_BUS: RefCell)>> = RefCell::new(VecDeque::new()); - pub static RELAY_MESSAGE_BUS: RefCell)>> + pub static RELAY_MESSAGE_BUS: RefCell)>> = RefCell::new(VecDeque::new()); } @@ -318,8 +318,8 @@ macro_rules! decl_test_network { while let Some((para_id, destination, message)) = $crate::PARA_MESSAGE_BUS.with( |b| b.borrow_mut().pop_front()) { - match destination.interior() { - $crate::Junctions::Here if destination.parent_count() == 1 => { + match destination.unpack() { + (1, []) => { let encoded = $crate::encode_xcm(message, $crate::MessageKind::Ump); let mut _id = [0; 32]; let r = <$relay_chain>::process_message( @@ -336,7 +336,7 @@ macro_rules! decl_test_network { } }, $( - $crate::X1($crate::Parachain(id)) if *id == $para_id && destination.parent_count() == 1 => { + (1, [$crate::Parachain(id)]) if *id == $para_id => { let encoded = $crate::encode_xcm(message, $crate::MessageKind::Xcmp); let messages = vec![(para_id, 1, &encoded[..])]; let _weight = <$parachain>::handle_xcmp_messages( @@ -360,9 +360,9 @@ macro_rules! decl_test_network { while let Some((destination, message)) = $crate::RELAY_MESSAGE_BUS.with( |b| b.borrow_mut().pop_front()) { - match destination.interior() { + match destination.unpack() { $( - $crate::X1($crate::Parachain(id)) if *id == $para_id && destination.parent_count() == 0 => { + (0, [$crate::Parachain(id)]) if *id == $para_id => { let encoded = $crate::encode_xcm(message, $crate::MessageKind::Dmp); // NOTE: RelayChainBlockNumber is hard-coded to 1 let messages = vec![(1, encoded)]; @@ -382,18 +382,18 @@ macro_rules! decl_test_network { pub struct ParachainXcmRouter($crate::PhantomData); impl> $crate::SendXcm for ParachainXcmRouter { - type Ticket = ($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>); + type Ticket = ($crate::ParaId, $crate::Location, $crate::Xcm<()>); fn validate( - destination: &mut Option<$crate::MultiLocation>, + destination: &mut Option<$crate::Location>, message: &mut Option<$crate::Xcm<()>>, - ) -> $crate::SendResult<($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>)> { + ) -> $crate::SendResult<($crate::ParaId, $crate::Location, $crate::Xcm<()>)> { use $crate::XcmpMessageHandlerT; let d = destination.take().ok_or($crate::SendError::MissingArgument)?; - match (d.interior(), d.parent_count()) { - ($crate::Junctions::Here, 1) => {}, + match d.unpack() { + (1, []) => {}, $( - ($crate::X1($crate::Parachain(id)), 1) if id == &$para_id => {} + (1, [$crate::Parachain(id)]) if id == &$para_id => {} )* _ => { *destination = Some(d); @@ -401,10 +401,10 @@ macro_rules! decl_test_network { }, } let m = message.take().ok_or($crate::SendError::MissingArgument)?; - Ok(((T::get(), d, m), $crate::MultiAssets::new())) + Ok(((T::get(), d, m), $crate::Assets::new())) } fn deliver( - triple: ($crate::ParaId, $crate::MultiLocation, $crate::Xcm<()>), + triple: ($crate::ParaId, $crate::Location, $crate::Xcm<()>), ) -> Result<$crate::XcmHash, $crate::SendError> { let hash = $crate::fake_message_hash(&triple.2); $crate::PARA_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(triple)); @@ -415,17 +415,17 @@ macro_rules! decl_test_network { /// XCM router for relay chain. pub struct RelayChainXcmRouter; impl $crate::SendXcm for RelayChainXcmRouter { - type Ticket = ($crate::MultiLocation, $crate::Xcm<()>); + type Ticket = ($crate::Location, $crate::Xcm<()>); fn validate( - destination: &mut Option<$crate::MultiLocation>, + destination: &mut Option<$crate::Location>, message: &mut Option<$crate::Xcm<()>>, - ) -> $crate::SendResult<($crate::MultiLocation, $crate::Xcm<()>)> { + ) -> $crate::SendResult<($crate::Location, $crate::Xcm<()>)> { use $crate::DmpMessageHandlerT; let d = destination.take().ok_or($crate::SendError::MissingArgument)?; - match (d.interior(), d.parent_count()) { + match d.unpack() { $( - ($crate::X1($crate::Parachain(id)), 0) if id == &$para_id => {}, + (0, [$crate::Parachain(id)]) if id == &$para_id => {}, )* _ => { *destination = Some(d); @@ -433,10 +433,10 @@ macro_rules! decl_test_network { }, } let m = message.take().ok_or($crate::SendError::MissingArgument)?; - Ok(((d, m), $crate::MultiAssets::new())) + Ok(((d, m), $crate::Assets::new())) } fn deliver( - pair: ($crate::MultiLocation, $crate::Xcm<()>), + pair: ($crate::Location, $crate::Xcm<()>), ) -> Result<$crate::XcmHash, $crate::SendError> { let hash = $crate::fake_message_hash(&pair.1); $crate::RELAY_MESSAGE_BUS.with(|b| b.borrow_mut().push_back(pair)); diff --git a/polkadot/zombienet_tests/functional/0001-parachains-pvf.toml b/polkadot/zombienet_tests/functional/0001-parachains-pvf.toml index 9ae4d899e69024651580e446b96f457f1fdb885c..53205867fd12c7e3f310e9c2c65d896855bdb5c0 100644 --- a/polkadot/zombienet_tests/functional/0001-parachains-pvf.toml +++ b/polkadot/zombienet_tests/functional/0001-parachains-pvf.toml @@ -4,7 +4,6 @@ timeout = 1000 [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" chain = "rococo-local" -chain_spec_command = "polkadot build-spec --chain rococo-local --disable-default-bootnode" [relaychain.default_resources] limits = { memory = "4G", cpu = "2" } @@ -133,4 +132,4 @@ genesis_state_generator = "undying-collator export-genesis-state --pov-size=1000 [types.Header] number = "u64" parent_hash = "Hash" -post_state = "Hash" \ No newline at end of file +post_state = "Hash" diff --git a/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl b/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl index 46bb8bcdf72b103a9b72be9a68ff90134a54466f..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 @@ -54,8 +56,8 @@ one: reports histogram polkadot_pvf_preparation_time has 0 samples in buckets [" two: reports histogram polkadot_pvf_preparation_time has 0 samples in buckets ["20", "30", "60", "120", "+Inf"] within 10 seconds # Check execution time. -# There are two different timeout conditions: BACKING_EXECUTION_TIMEOUT(2s) and -# APPROVAL_EXECUTION_TIMEOUT(6s). Currently these are not differentiated by metrics +# There are two different timeout conditions: DEFAULT_BACKING_EXECUTION_TIMEOUT(2s) and +# DEFAULT_APPROVAL_EXECUTION_TIMEOUT(12s). Currently these are not differentiated by metrics # because the metrics are defined in `polkadot-node-core-pvf` which is a level below # the relevant subsystems. # That being said, we will take the simplifying assumption of testing only the diff --git a/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml b/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml index e6aeb8e245c2bab3e53628498aeb2c7caa626e0f..2561661de1f8408b2ed2808f1f5e41b4286ce97e 100644 --- a/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml +++ b/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml @@ -1,14 +1,19 @@ [settings] timeout = 1000 -[relaychain.genesis.runtime.configuration.config] - max_validators_per_core = 5 +[relaychain.genesis.runtimeGenesis.patch.configuration.config] needed_approvals = 8 +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 5 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + max_approval_coalesce_count = 5 + + [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" chain = "rococo-local" -chain_spec_command = "polkadot build-spec --chain rococo-local --disable-default-bootnode" default_command = "polkadot" [relaychain.default_resources] diff --git a/polkadot/zombienet_tests/functional/0004-parachains-garbage-candidate.toml b/polkadot/zombienet_tests/functional/0004-parachains-garbage-candidate.toml index ef27d7b92f029d7978c9b7ab1a175c882fbb1d70..a2a2621f8426d2e8deb32f524d23e666ab1b5c00 100644 --- a/polkadot/zombienet_tests/functional/0004-parachains-garbage-candidate.toml +++ b/polkadot/zombienet_tests/functional/0004-parachains-garbage-candidate.toml @@ -2,14 +2,15 @@ timeout = 1000 bootnode = true -[relaychain.genesis.runtime.configuration.config] +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] max_validators_per_core = 1 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] needed_approvals = 2 [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" chain = "rococo-local" -chain_spec_command = "polkadot build-spec --chain rococo-local --disable-default-bootnode" default_command = "polkadot" [relaychain.default_resources] diff --git a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.toml b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.toml index 25d922bb6827cbf8e129a07f64b537391bc72d7a..a3bbc82e74ba6d9d5c442742afc2dd18f31c7305 100644 --- a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.toml +++ b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.toml @@ -2,9 +2,11 @@ timeout = 1000 bootnode = true -[relaychain.genesis.runtime.configuration.config] - max_validators_per_core = 1 +[relaychain.genesis.runtimeGenesis.patch.configuration.config] needed_approvals = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 group_rotation_frequency = 2 [relaychain] 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/0006-parachains-max-tranche0.toml b/polkadot/zombienet_tests/functional/0006-parachains-max-tranche0.toml new file mode 100644 index 0000000000000000000000000000000000000000..858f87b9cfe52c10c9cff9885dbeb57b0dcba2f5 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0006-parachains-max-tranche0.toml @@ -0,0 +1,41 @@ +[settings] +timeout = 1000 +bootnode = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 7 + relay_vrf_modulo_samples = 5 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "some-validator" + count = 8 + args = ["-lparachain=debug,runtime=debug"] + +{% for id in range(2000,2005) %} +[[parachains]] +id = {{id}} +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size={{10000*(id-1999)}} --pvf-complexity={{id - 1999}}" + [parachains.collator] + image = "{{COL_IMAGE}}" + name = "collator" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size={{10000*(id-1999)}}", "--parachain-id={{id}}", "--pvf-complexity={{id - 1999}}"] +{% endfor %} + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" diff --git a/polkadot/zombienet_tests/functional/0006-parachains-max-tranche0.zndsl b/polkadot/zombienet_tests/functional/0006-parachains-max-tranche0.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..6cec6c66797ac574be35a44a0e107d79f6dd71cd --- /dev/null +++ b/polkadot/zombienet_tests/functional/0006-parachains-max-tranche0.zndsl @@ -0,0 +1,27 @@ +Description: Test if parachains make progress with most of approvals being tranch0 +Network: ./0006-parachains-max-tranche0.toml +Creds: config + +# Check authority status. +some-validator-0: reports node_roles is 4 +some-validator-1: reports node_roles is 4 +some-validator-3: reports node_roles is 4 +some-validator-4: reports node_roles is 4 +some-validator-5: reports node_roles is 4 +some-validator-6: reports node_roles is 4 +some-validator-7: reports node_roles is 4 + +some-validator-0: parachain 2000 block height is at least 5 within 180 seconds +some-validator-1: parachain 2001 block height is at least 5 within 180 seconds +some-validator-2: parachain 2002 block height is at least 5 within 180 seconds +some-validator-3: parachain 2003 block height is at least 5 within 180 seconds +some-validator-4: parachain 2004 block height is at least 5 within 180 seconds + +some-validator-0: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-1: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-2: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-3: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-4: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-5: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-6: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 +some-validator-7: reports polkadot_parachain_approval_checking_finality_lag is lower than 2 diff --git a/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.toml b/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.toml new file mode 100644 index 0000000000000000000000000000000000000000..573ccf961385fa2c721caa0bb67ed97b94deb5a1 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.toml @@ -0,0 +1,42 @@ +[settings] +timeout = 1000 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 1 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "honest" + count = 6 + args = ["-lparachain=debug"] + + [[relaychain.nodes]] + image = "{{MALUS_IMAGE}}" + name = "malus" + command = "malus dispute-finalized-candidates" + args = [ "--alice", "-lparachain=debug,MALUS=trace", "--dispute-offset=3" ] + +[[parachains]] +id = 2000 + + [parachains.collator] + image = "{{COL_IMAGE}}" + name = "collator" + command = "undying-collator" + args = ["-lparachain=debug"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" diff --git a/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.zndsl b/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..62d5a9768f9ebd0b6eee8e6ef92dae7735fbf791 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0007-dispute-freshly-finalized.zndsl @@ -0,0 +1,29 @@ +Description: Test if disputes triggered on finalized blocks within scope always end as valid. +Network: ./0007-dispute-freshly-finalized.toml +Creds: config + +# Check authority status and peers. +malus: reports node_roles is 4 +honest: reports node_roles is 4 + +# Ensure parachains are registered. +honest: parachain 2000 is registered within 30 seconds + +# Ensure parachains made progress. +honest: parachain 2000 block height is at least 10 within 200 seconds + +# Ensure that malus is already attempting to dispute +malus: log line contains "😈 Disputing candidate with hash:" within 180 seconds + +# Check if disputes are initiated and concluded. +honest: reports polkadot_parachain_candidate_disputes_total is at least 2 within 100 seconds +honest: reports polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 2 within 100 seconds +honest: reports polkadot_parachain_candidate_dispute_concluded{validity="invalid"} is 0 within 100 seconds + +# Check lag - approval +honest: reports polkadot_parachain_approval_checking_finality_lag is 0 + +# Check lag - dispute conclusion +honest: reports polkadot_parachain_disputes_finality_lag is 0 + + diff --git a/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.toml b/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.toml new file mode 100644 index 0000000000000000000000000000000000000000..ea1c93a1403fb837b45a6da01d0920c516e43ae6 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.toml @@ -0,0 +1,42 @@ +[settings] +timeout = 1000 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 1 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "honest" + count = 6 + args = ["-lparachain=debug"] + + [[relaychain.nodes]] + image = "{{MALUS_IMAGE}}" + name = "malus" + command = "malus dispute-finalized-candidates" + args = [ "--alice", "-lparachain=debug,MALUS=trace", "--dispute-offset=14" ] + +[[parachains]] +id = 2000 + + [parachains.collator] + image = "{{COL_IMAGE}}" + name = "collator" + command = "undying-collator" + args = ["-lparachain=debug"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" diff --git a/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.zndsl b/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..b30c5801a1dac291d8496967b27d2d976b92ce15 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0008-dispute-old-finalized.zndsl @@ -0,0 +1,21 @@ +Description: Test if disputes triggered on finalized blocks out of scope never get to be confirmed and concluded. +Network: ./0008-dispute-old-finalized.toml +Creds: config + +# Check authority status and peers. +malus: reports node_roles is 4 +honest: reports node_roles is 4 + + +# Ensure parachains are registered. +honest: parachain 2000 is registered within 30 seconds + +# Ensure parachains made progress. +honest: parachain 2000 block height is at least 20 within 300 seconds + +# Ensure that malus is already attempting to dispute +malus: log line contains "😈 Disputing candidate with hash:" within 180 seconds + +# Ensure that honest nodes don't participate and conclude any disputes +honest: count of log lines containing "Dispute on candidate concluded" is 0 within 100 seconds + 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/functional/0010-validator-disabling.toml b/polkadot/zombienet_tests/functional/0010-validator-disabling.toml new file mode 100644 index 0000000000000000000000000000000000000000..c9d79c5f8f236918cf409fd684b7d0b8d9792d33 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0010-validator-disabling.toml @@ -0,0 +1,41 @@ +[settings] +timeout = 1000 +bootnode = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + group_rotation_frequency = 10 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "westend-local" # for the disabling to take an effect +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "honest-validator" + count = 3 + args = ["-lparachain=debug"] + + [[relaychain.node_groups]] + image = "{{MALUS_IMAGE}}" + name = "malus-validator" + command = "malus suggest-garbage-candidate" + args = ["-lMALUS=trace"] + count = 1 + +[[parachains]] +id = 1000 +cumulus_based = true + + [parachains.collator] + name = "alice" + command = "polkadot-parachain" + image = "{{CUMULUS_IMAGE}}" + args = ["-lparachain=debug"] diff --git a/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl b/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..c810266102061e63392dfa2b5ad50ca6f4ce3fde --- /dev/null +++ b/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl @@ -0,0 +1,21 @@ +Description: Test validator disabling effects +Network: ./0010-validator-disabling.toml +Creds: config + +# Ensure nodes are up and running +honest-validator: reports node_roles is 4 + +# Ensure parachain is registered +honest-validator: parachain 1000 is registered within 100 seconds + +# Ensure parachain made progress +honest-validator: parachain 1000 block height is at least 1 within 300 seconds + +# Wait for the dispute +honest-validator-1: reports parachain_candidate_disputes_total is at least 1 within 600 seconds + +# Disputes should conclude +honest-validator: reports polkadot_parachain_candidate_dispute_concluded{validity="invalid"} is at least 1 within 200 seconds + +# Wait for a few blocks for the disabling to take place. +honest-validator: log line contains "Disabled validators detected" within 180 seconds diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml new file mode 100644 index 0000000000000000000000000000000000000000..b776622fdce33df2e9a78debc31ee3e62ae4805d --- /dev/null +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml @@ -0,0 +1,54 @@ +[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.async_backing_params] + max_candidate_depth = 3 + allowed_ancestry_len = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + lookahead = 2 + group_rotation_frequency = 4 + + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "alice" + args = [ "-lparachain=debug" ] + count = 12 + +[[parachains]] +id = 2000 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" + + [parachains.collator] + name = "collator01" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] + +[[parachains]] +id = 2001 +cumulus_based = true + + [parachains.collator] + name = "collator02" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..0d01af82833e36afd3c38b2e00e9d604ace46797 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl @@ -0,0 +1,20 @@ +Description: Test we are producing blocks at 6 seconds clip +Network: ./0011-async-backing-6-seconds-rate.toml +Creds: config + +# Check authority status. +alice: reports node_roles is 4 + +# Ensure parachains are registered. +alice: parachain 2000 is registered within 60 seconds +alice: parachain 2001 is registered within 60 seconds + +# Ensure parachains made progress. +alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds + +# This parachains should produce blocks at 6s clip, let's assume an 8s rate, allowing for +# some slots to be missed on slower machines +alice: parachain 2000 block height is at least 30 within 240 seconds +# This should already have produced the needed blocks +alice: parachain 2001 block height is at least 30 within 6 seconds + diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml new file mode 100644 index 0000000000000000000000000000000000000000..9b3576eaa3c212bd9490095c12ae4bca65f6fa54 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml @@ -0,0 +1,40 @@ +[settings] +timeout = 1000 +bootnode = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 4 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 2 + num_cores = 2 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.nodes]] + name = "alice" + validator = "true" + + [[relaychain.node_groups]] + name = "validator" + count = 3 + args = [ "-lparachain=debug,runtime=debug"] + +[[parachains]] +id = 2000 +default_command = "polkadot-parachain" +add_to_genesis = false +register_para = true +onboard_as_parachain = false + + [parachains.collator] + name = "collator2000" + command = "polkadot-parachain" + args = [ "-lparachain=debug" ] diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..edc87c802a0965b6fd685db44ccbf50e807f1ed3 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl @@ -0,0 +1,19 @@ +Description: Test that a paraid acquiring multiple cores does not brick itself if ElasticScalingMVP feature is enabled in genesis +Network: ./0012-elastic-scaling-mvp.toml +Creds: config + +# Check authority status. +validator: reports node_roles is 4 + +validator: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds + +# Ensure parachain was able to make progress. +validator: parachain 2000 block height is at least 10 within 200 seconds + +# Register the second core assigned to this parachain. +alice: js-script ./0012-register-para.js return is 0 within 600 seconds + +validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds + +# Ensure parachain is now making progress. +validator: parachain 2000 block height is at least 30 within 200 seconds diff --git a/polkadot/zombienet_tests/functional/0012-register-para.js b/polkadot/zombienet_tests/functional/0012-register-para.js new file mode 100644 index 0000000000000000000000000000000000000000..25c7e4f5ffddcf087edab4df37e696af92fe6d3f --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-register-para.js @@ -0,0 +1,37 @@ +async function run(nodeName, networkInfo, _jsArgs) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + await new Promise(async (resolve, reject) => { + const unsub = await api.tx.sudo + .sudo(api.tx.coretime.assignCore(0, 35, [[{ task: 2000 }, 57600]], null)) + .signAndSend(alice, ({ status, isError }) => { + if (status.isInBlock) { + console.log( + `Transaction included at blockhash ${status.asInBlock}`, + ); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}`, + ); + unsub(); + return resolve(); + } else if (isError) { + console.log(`Transaction error`); + reject(`Transaction error`); + } + }); + }); + + + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/misc/0001-paritydb.toml b/polkadot/zombienet_tests/misc/0001-paritydb.toml index 99dc9c66e26ee6b04bfea1951d7818e23be70118..b3ce2081b1119ec237c1b3d6e14e1e4aed8322c5 100644 --- a/polkadot/zombienet_tests/misc/0001-paritydb.toml +++ b/polkadot/zombienet_tests/misc/0001-paritydb.toml @@ -2,14 +2,15 @@ timeout = 1000 bootnode = true -[relaychain.genesis.runtime.configuration.config] - max_validators_per_core = 1 +[relaychain.genesis.runtimeGenesis.patch.configuration.config] needed_approvals = 3 +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 1 + [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" chain = "rococo-local" -chain_spec_command = "polkadot build-spec --chain rococo-local" default_command = "polkadot" [relaychain.default_resources] diff --git a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl index 4a22311de764c1936be2f5812ffb32a7471b5f8e..e0260cb9fdde18882bd9e1cb1ec88a12cdb7a3b9 100644 --- a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl +++ b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl @@ -31,28 +31,28 @@ validator-0: parachain 2008 is registered validator-0: parachain 2009 is registered # Ensure parachains made some progress. -validator-0: parachain 2000 block height is at least 3 within 30 seconds -validator-0: parachain 2001 block height is at least 3 within 30 seconds -validator-0: parachain 2002 block height is at least 3 within 30 seconds -validator-0: parachain 2003 block height is at least 3 within 30 seconds -validator-0: parachain 2004 block height is at least 3 within 30 seconds -validator-0: parachain 2005 block height is at least 3 within 30 seconds -validator-0: parachain 2006 block height is at least 3 within 30 seconds -validator-0: parachain 2007 block height is at least 3 within 30 seconds -validator-0: parachain 2008 block height is at least 3 within 30 seconds -validator-0: parachain 2009 block height is at least 3 within 30 seconds +validator-0: parachain 2000 block height is at least 3 within 60 seconds +validator-0: parachain 2001 block height is at least 3 within 60 seconds +validator-0: parachain 2002 block height is at least 3 within 60 seconds +validator-0: parachain 2003 block height is at least 3 within 60 seconds +validator-0: parachain 2004 block height is at least 3 within 60 seconds +validator-0: parachain 2005 block height is at least 3 within 60 seconds +validator-0: parachain 2006 block height is at least 3 within 60 seconds +validator-0: parachain 2007 block height is at least 3 within 60 seconds +validator-0: parachain 2008 block height is at least 3 within 60 seconds +validator-0: parachain 2009 block height is at least 3 within 60 seconds # Check lag - approval -validator-0: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-1: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-2: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-3: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-4: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-5: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-6: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-7: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-8: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-9: reports polkadot_parachain_approval_checking_finality_lag is 0 +validator-0: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-1: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-2: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-3: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-4: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-5: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-6: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-7: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-8: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-9: reports polkadot_parachain_approval_checking_finality_lag <= 1 # Check lag - dispute conclusion validator-0: reports polkadot_parachain_candidate_disputes_total is 0 diff --git a/polkadot/zombienet_tests/misc/0002-update-cmd.sh b/polkadot/zombienet_tests/misc/0002-update-cmd.sh new file mode 100755 index 0000000000000000000000000000000000000000..7d0dc53ca0df264f9926b0038137c3586a340adc --- /dev/null +++ b/polkadot/zombienet_tests/misc/0002-update-cmd.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euxo pipefail + +if [[ $(grep "insecure-validator-i-know-what-i-do" /cfg/zombie.cmd) ]]; then + echo "insecure flag is already part of the cmd"; +else + echo -n " --insecure-validator-i-know-what-i-do" >> /cfg/zombie.cmd; +fi; + +echo "update-cmd" > /tmp/zombiepipe; \ No newline at end of file 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/misc/0002-upgrade-node.zndsl b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl index 9191fb027de0d0240805d6cb65e68adfc4f15942..5fe1b2ad2f1a7589a28f8b1b8da05d6f35d3d59b 100644 --- a/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl +++ b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl @@ -10,9 +10,13 @@ dave: parachain 2001 block height is at least 10 within 200 seconds # POLKADOT_PR_ARTIFACTS_URL=https://gitlab.parity.io/parity/mirrors/polkadot/-/jobs/1842869/artifacts/raw/artifacts # with the version of polkadot you want to download. -# avg 30s in our infra -alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds -bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds +alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds +bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds +# update the cmd to add the flag '--insecure-validator-i-know-what-i-do' +# once the base image include the version with this flag we can remove this logic. +alice: run ./0002-update-cmd.sh within 60 seconds +bob: run ./0002-update-cmd.sh within 60 seconds +# restart alice: restart after 5 seconds bob: restart after 5 seconds 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..52a32b8a7c802e33ed41acceb155024be2da7ca0 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-broker.js @@ -0,0 +1,69 @@ +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: 5, + interludeLength: 1, + leadinLength: 1, + regionLength: 1, + idealBulkProportion: 100, + limitCoresOffered: null, + renewalBump: 10, + contributionTimeout: 5, + }), + // We need MOARE cores. + api.tx.broker.requestCoreCount(2), + // Set a lease for the broker chain itself. + api.tx.broker.setLease( + 1005, + 1000, + ), + // Set a lease for parachain 100 + api.tx.broker.setLease( + 100, + 1000, + ), + // Start sale to make the broker "work", but we don't offer any cores + // as we have fixed leases only anyway. + api.tx.broker.startSales(1, 0), + ]; + 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) { + // Probably happens because of: https://github.com/paritytech/polkadot-sdk/issues/1202. + console.log(`Transaction error`); + // We ignore the error because it is very likely misleading, because of the issue mentioned above. + unsub(); + return resolve(); + } + }); + }); + + 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..724d1b537a366ef2ab87a8ca23af2347c5fa5ae3 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-relay.js @@ -0,0 +1,62 @@ +const assert = require("assert"); + +async function run(nodeName, networkInfo, _jsArgs) { + const init = networkInfo.nodesByName[nodeName]; + let wsUri = init.wsUri; + let userDefinedTypes = init.userDefinedTypes; + const api = await zombie.connect(wsUri, userDefinedTypes); + + const sec = networkInfo.nodesByName["collator-para-100"]; + wsUri = sec.wsUri; + userDefinedTypes = sec.userDefinedTypes; + + const api_collator = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // Get the genesis header and the validation code of parachain 100 + const genesis_header = await api_collator.rpc.chain.getHeader(); + const validation_code = await api_collator.rpc.state.getStorage("0x3A636F6465"); + + // 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), + api.tx.registrar.forceRegister( + alice.address, + 0, + 100, + genesis_header.toHex(), + validation_code.toHex(), + ) + ]; + 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..0bdb58fa1ef45a495a3e724ec6bbc543045082ff --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml @@ -0,0 +1,41 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +command = "polkadot" + + [[relaychain.nodes]] + name = "alice" + args = ["-lruntime=debug,xcm=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 = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = [ "-lruntime=debug,xcm=trace" ] + +[[parachains]] +id = 100 +add_to_genesis = false +register_para = false +onboard_as_parachain = false + + [parachains.collator] + name = "collator-para-100" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lruntime=debug,parachain=trace,aura=trace", "--force-authoring"] 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..cfb1ce7d98215f3bf6d1f01361077bb041ca90ee --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl @@ -0,0 +1,15 @@ +Description: Bulk core assignment Smoke +Network: ./0004-coretime-smoke-test.toml +Creds: config + +alice: is up +coretime-collator: is up + +# configure relay chain +alice: js-script ./0004-configure-relay.js with "" return is 0 within 600 secs + +# configure broker chain +coretime-collator: js-script ./0004-configure-broker.js with "" return is 0 within 600 secs + +# Ensure that parachain 100 got onboarded +alice: parachain 100 block height is at least 5 within 900 seconds diff --git a/prdoc/.template.prdoc b/prdoc/.template.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..097741f388c4f2be73c9aed9d2b1dcc7dde32cf2 --- /dev/null +++ b/prdoc/.template.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: ... + +doc: + - audience: Node Dev + description: | + ... + +crates: [ ] diff --git a/prdoc/pr_1234.prdoc b/prdoc/1.3.0/pr_1234.prdoc similarity index 83% rename from prdoc/pr_1234.prdoc rename to prdoc/1.3.0/pr_1234.prdoc index cc22a02d88b9d7a105bfe50dd548e9a9e98a34e9..e1e5d71050a86022431ec6fd4a2aefa6928dd0b5 100644 --- a/prdoc/pr_1234.prdoc +++ b/prdoc/1.3.0/pr_1234.prdoc @@ -4,17 +4,10 @@ title: Introduce XcmFeesToAccount fee manager doc: - - audience: Builder + - audience: Runtime User description: | Now all XCM sending, unless done by the system for the system, will be charged delivery fees. All runtimes are now configured to send these delivery fees to a treasury account. The fee formula is `delivery_fee_factor * (base_fee + encoded_msg_len * per_byte_fee)`. -migrations: - db: [] - - runtime: [] - -crates: [] - -host_functions: [] +crates: [ ] diff --git a/prdoc/1.3.0/pr_1255.prdoc b/prdoc/1.3.0/pr_1255.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c00a7c307e99c177338fc7a2735966a0a3429d67 --- /dev/null +++ b/prdoc/1.3.0/pr_1255.prdoc @@ -0,0 +1,21 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Fix for Reward Deficit in the pool + +doc: + - audience: Runtime Dev + description: | + Instead of fragile calculation of current balance by looking at free balance - ED, Nomination Pool now freezes ED in the pool reward account to restrict an account from going below minimum balance. This also has a nice side effect that if ED changes, we know how much is the imbalance in ED frozen in the pool and the current required ED. A pool operator can diligently top up the pool with the deficit in ED or vice versa, withdraw the excess they transferred to the pool. + + notes: + - Introduces new call `adjust_pool_deposit` that allows to top up the deficit or withdraw the excess deposit for the pool. + - Switch to using Fungible trait from Currency trait. + +migrations: + runtime: + - reference: pallet-nomination-pools + description: One time migration of freezing ED from each of the existing pools. + +crates: + - name: pallet-nomination-pools diff --git a/prdoc/pr_1818.prdoc b/prdoc/1.3.0/pr_1818.prdoc similarity index 63% rename from prdoc/pr_1818.prdoc rename to prdoc/1.3.0/pr_1818.prdoc index cbafa02f9af563e88f38a4fb4f74e4ae7c13fc1a..0f59a0f9124c512fcb9c5766fc2968faac07346b 100644 --- a/prdoc/pr_1818.prdoc +++ b/prdoc/1.3.0/pr_1818.prdoc @@ -1,16 +1,9 @@ title: FRAME pallets warning for unchecked weight witness doc: - - audience: Core Dev + - audience: Runtime Dev description: | FRAME pallets now emit a warning when a call uses a function argument that starts with an underscore in its weight declaration. -migrations: - db: [ ] - runtime: [ ] - -host_functions: [] - crates: -- name: "frame-support-procedural" - semver: minor + - name: frame-support-procedural diff --git a/prdoc/pr_1873.prdoc b/prdoc/1.3.0/pr_1873.prdoc similarity index 66% rename from prdoc/pr_1873.prdoc rename to prdoc/1.3.0/pr_1873.prdoc index 6f3bc7646db2a95d8ce8c15093317ed2d2b5af61..c22b732c72f90428f5683357b67fb5308b10c939 100644 --- a/prdoc/pr_1873.prdoc +++ b/prdoc/1.3.0/pr_1873.prdoc @@ -1,15 +1,9 @@ title: Message Queue use proper overweight limit doc: - - audience: Core Dev + - audience: Node Dev description: | Changed the overweight cutoff limit from the full `Config::ServiceWeight` to a lower value that is calculated based on the weight of the functions being called. -migrations: - db: [] - - runtime: [] - -crates: ["pallet-message-queue", patch] - -host_functions: [] +crates: + - name: pallet-message-queue diff --git a/prdoc/pr_1913.prdoc b/prdoc/1.3.0/pr_1913.prdoc similarity index 87% rename from prdoc/pr_1913.prdoc rename to prdoc/1.3.0/pr_1913.prdoc index 155057054eb5c5686805bfa7b970b99b401d0850..c2e7627c9acc4fc8f6ff243b010008cc94de383c 100644 --- a/prdoc/pr_1913.prdoc +++ b/prdoc/1.3.0/pr_1913.prdoc @@ -7,13 +7,6 @@ doc: If experiencing stability issues caused by BEEFY, it can be disabled using `--no-beefy` flag. BEEFY doesn't (yet) support warp sync. So, attempting to Warp sync as a validator will throw an error. -migrations: - db: [] - - runtime: [] - crates: - name: polkadot-cli - name: polkadot-service - -host_functions: [] diff --git a/prdoc/pr_1921.prdoc b/prdoc/1.3.0/pr_1921.prdoc similarity index 67% rename from prdoc/pr_1921.prdoc rename to prdoc/1.3.0/pr_1921.prdoc index 5ed0137cd5f9ee850265fe816060d34fbdf9fb38..e71a68fa829c0d74f27c8886f072ab3fcb96e6c4 100644 --- a/prdoc/pr_1921.prdoc +++ b/prdoc/1.3.0/pr_1921.prdoc @@ -1,19 +1,14 @@ title: Fix para-scheduler migration doc: - - audience: Core Dev + - audience: Runtime Dev description: | Changing the `MigrateToV1` migration in the `ParachainScheduler` pallet to be truly idempotent. It is achieved by wrapping it in a `VersionedMigration`. migrations: - db: [] - runtime: - - pallet: "ParachainScheduler" + - reference: ParachainScheduler description: Non-critical fixup for `MigrateToV1`. crates: - - name: "polkadot-runtime-parachains" - semver: patch - -host_functions: [] + - name: polkadot-runtime-parachains 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/1.4.0/pr_1178.prdoc b/prdoc/1.4.0/pr_1178.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..528e523c4328c3f18551ea4ce3d6c0955dd5a63a --- /dev/null +++ b/prdoc/1.4.0/pr_1178.prdoc @@ -0,0 +1,22 @@ +title: tranche0 assignments in one certificate part1 + +doc: + - audience: Node Operator + description: | + 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. + 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. + 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/1.4.0/pr_1246.prdoc b/prdoc/1.4.0/pr_1246.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3b5c2017f22acfba25471595c50015ae14c3e5cd --- /dev/null +++ b/prdoc/1.4.0/pr_1246.prdoc @@ -0,0 +1,17 @@ +title: Use the `Message Queue` Pallet for DMP and XCMP dispatch queueing + +doc: + - 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: + - + 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 + note: Pallet config must be altered according to the MR description. + +host_functions: [] diff --git a/prdoc/1.4.0/pr_1256.prdoc b/prdoc/1.4.0/pr_1256.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f486786fec3522def1a0c7989c37827bf64c60b8 --- /dev/null +++ b/prdoc/1.4.0/pr_1256.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "`chain-spec`: getting ready for native-runtime-free world" + +doc: + - audience: Node Dev + description: | + - [`ChainSpec::from_genesis`](https://github.com/paritytech/polkadot-sdk/blob/3df6b4d00eb310900de6f4858114baf68239412c/substrate/client/chain-spec/src/chain_spec.rs#L525) becomes deprecated in favor of [`ChainSpec::builder()`](https://github.com/paritytech/polkadot-sdk/blob/3df6b4d00eb310900de6f4858114baf68239412c/substrate/client/chain-spec/src/chain_spec.rs#L432), + - The signature of [`ChainSpec::from_genesis`] method was changed by extending it with `code` argument. + +migrations: + db: [] + + runtime: [] + +crates: [] + +host_functions: [] diff --git a/prdoc/1.4.0/pr_1805.prdoc b/prdoc/1.4.0/pr_1805.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..30f0fbea3070f1e819df2f12f866d6a6f5a2a78f --- /dev/null +++ b/prdoc/1.4.0/pr_1805.prdoc @@ -0,0 +1,17 @@ +title: Introduce state decoding check after runtime upgrades. + +doc: + - 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`. + +migrations: + db: [] + + runtime: [] + +crates: + - name: frame-support + - name: frame-support-procedural + +host_functions: [] diff --git a/prdoc/1.4.0/pr_1926.prdoc b/prdoc/1.4.0/pr_1926.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e7c4293d9fd452017af8e1fe9cd4e623558a8d88 --- /dev/null +++ b/prdoc/1.4.0/pr_1926.prdoc @@ -0,0 +1,24 @@ +title: Adds syntax for marking calls feeless + +doc: + - 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 + 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. + +migrations: + db: [] + + runtime: [] + +crates: + - 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/1.4.0/pr_2086.prdoc b/prdoc/1.4.0/pr_2086.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3bd568cc139883d70fe7556c18b017f1fc12965a --- /dev/null +++ b/prdoc/1.4.0/pr_2086.prdoc @@ -0,0 +1,12 @@ +title: "Contracts: Add XCM traits to interface with contracts" + +doc: + - 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. + They enable other pallets, like `pallet_contracts`, to rely on these traits instead of tight coupling to `pallet-xcm` itself. + +crates: + - name: pallet-xcm + - name: xcm-executor diff --git a/prdoc/1.4.0/pr_2107.prdoc b/prdoc/1.4.0/pr_2107.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..be71828cbad5cf76e4ab9c1a5c9d11e2aa61a48c --- /dev/null +++ b/prdoc/1.4.0/pr_2107.prdoc @@ -0,0 +1,26 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Add a builder pattern to create XCM programs + +doc: + - 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: [] + + runtime: [] + +crates: + - name: xcm + +host_functions: [] diff --git a/prdoc/1.4.0/pr_2165.prdoc b/prdoc/1.4.0/pr_2165.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3b10bcfe6a5210bf9dc60ce1d1edf6b653e71b82 --- /dev/null +++ b/prdoc/1.4.0/pr_2165.prdoc @@ -0,0 +1,16 @@ +title: Add `sudo::remove_key` + +doc: + - 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. + +migrations: + db: [] + + runtime: [] + +crates: + - name: pallet-sudo + +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 76% rename from prdoc/pr_1408_prodc-introduction.prdoc rename to prdoc/1.5.0/pr_1408_prodc-introduction.prdoc index 4b10e0fe2e8139e973ab31cabc7fb181c75f4fba..46f56068e271b6ee1d2810c0434ad8d7f363eb9e 100644 --- a/prdoc/pr_1408_prodc-introduction.prdoc +++ b/prdoc/1.5.0/pr_1408_prodc-introduction.prdoc @@ -1,19 +1,15 @@ # This PR does not need a prdoc but it is provided in order to test title: PRdoc check +author: chevdor +topic: documentation + doc: - - audience: Core Dev + - audience: Node Dev description: | This PRdoc is an **example**. This PR brings support and automated checks for documentation in the form of a [`prdoc`](https://github.com/paritytech/prdoc/) file. -migrations: - db: [] - - runtime: [] - -crates: [] - -host_functions: [] +crates: [ ] 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/1.5.0/pr_1946_prdoc_new_schema.prdoc b/prdoc/1.5.0/pr_1946_prdoc_new_schema.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fae063f6b1ecd349b9d252d6014a52bdd424051a --- /dev/null +++ b/prdoc/1.5.0/pr_1946_prdoc_new_schema.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: New PRDoc Schema + +author: chevdor +topic: documentation + +doc: + - audience: Node Dev + description: &desc | + The new version of prdoc and the new schema is activated in this PR. + + - audience: Runtime Dev + description: *desc + +crates: [] 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/1.5.0/pr_2142.prdoc b/prdoc/1.5.0/pr_2142.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9cd1b23906d05734adddbb4cad7f146ba52481da --- /dev/null +++ b/prdoc/1.5.0/pr_2142.prdoc @@ -0,0 +1,17 @@ +title: Cleanup XCMP `QueueConfigData` + +author: serban300 +topic: runtime + +doc: + - 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: + - 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: [] + +host_functions: [] 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/1.5.0/pr_2253.prdoc b/prdoc/1.5.0/pr_2253.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3f69bc2461e410518bf63ba168afdc82b8f7188f --- /dev/null +++ b/prdoc/1.5.0/pr_2253.prdoc @@ -0,0 +1,27 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Different builder pattern constructors for XCM + +author: franciscoaguirre +topic: runtime + +doc: + - 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. + Another constructor, `builder_unpaid()` requires an explicit `UnpaidExecution` instruction before + anything else. + For building messages without any restriction, `builder_unsafe` can be used. + This has been named like that since in general the other two should be used instead, but it's okay + to use it for teaching purposes or for experimenting. + +migrations: + db: [] + + runtime: [] + +crates: [] + +host_functions: [] 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/1.5.0/pr_2486.prdoc b/prdoc/1.5.0/pr_2486.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c716f71c34e5a3a62a2fcba48faaf0fc804d307f --- /dev/null +++ b/prdoc/1.5.0/pr_2486.prdoc @@ -0,0 +1,25 @@ +title: "PVF: Add Secure Validator Mode" + +author: mrcnski +topic: node + +doc: + - audience: Node Operator + description: | + Secure Validator Mode has been enabled for Polkadot validators by default. + This enforces PVF validation security, and prevents starting a validator node if some security features are missing on the machine. + SVM can be disabled using the `--insecure-validator-i-know-what-i-do` flag. + +migrations: + db: [] + + runtime: [] + +crates: + - name: polkadot-cli + - name: polkadot-node-core-pvf + - name: polkadot-node-core-pvf-common + - name: polkadot-node-core-pvf-prepare-worker + - name: polkadot-node-core-pvf-execute-worker + +host_functions: [] 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/1.6.0/pr_1191.prdoc b/prdoc/1.6.0/pr_1191.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..26626731be46864e1fc383a02aab1646364c1966 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1226.prdoc b/prdoc/1.6.0/pr_1226.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..caef324bfd0d35f258af51a7c857fa965dc11bcd --- /dev/null +++ b/prdoc/1.6.0/pr_1226.prdoc @@ -0,0 +1,12 @@ +title: Removed deprecated `Balances::transfer` and `Balances::set_balance_deprecated` functions. + +doc: + - audience: Runtime User + description: | + The Balances pallet's dispatchables `set_balance_deprecated` and `transfer` were deprecated in [paritytech/substrate#12951](https://github.com/paritytech/substrate/pull/12951) and have now been removed. + + notes: + - Use `set_balance_deprecated` instead `force_set_balance` and `transfer_allow_death` instead of `transfer`. + +crates: + - name: pallet-balances diff --git a/prdoc/1.6.0/pr_1289.prdoc b/prdoc/1.6.0/pr_1289.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..059d7608ba6380b5b8d1b0c9edf93b2c619aa34a --- /dev/null +++ b/prdoc/1.6.0/pr_1289.prdoc @@ -0,0 +1,29 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Supporting paged rewards allowing all nominators to be rewarded + +doc: + - 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. + +migrations: + db: [] + + runtime: + - reference: pallet-staking + description: "v14: Migration of era exposure storage items to paged exposures." + +crates: + - name: pallet-staking + +host_functions: [] diff --git a/prdoc/1.6.0/pr_1343.prdoc b/prdoc/1.6.0/pr_1343.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..84168230e0afba483db2f653ad5bfe0a934ebf69 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1454.prdoc b/prdoc/1.6.0/pr_1454.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c96bfcde6cfe83067695ee04654f5a106d23286a --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1479.prdoc b/prdoc/1.6.0/pr_1479.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..33b798290f8327780f4eb172ecba1daa96e38460 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1677.prdoc b/prdoc/1.6.0/pr_1677.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9c5bee386ae34adf26be30d44ecb42697c94cc62 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1694.prdoc b/prdoc/1.6.0/pr_1694.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..24797630efc992224d81ce06b4e6e22c8de1249f --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_1841.prdoc b/prdoc/1.6.0/pr_1841.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c99583e6dc3092ef783cedfbdaf0ea340f66356a --- /dev/null +++ b/prdoc/1.6.0/pr_1841.prdoc @@ -0,0 +1,18 @@ +title: Validator disabling in Statement Distribution. + +doc: + - audience: Node Operator + description: | + Once a validator has been disabled for misbehavior, other validators + should no longer gossip its backing statements in the current era. + If they do, it might result in disconnects from the network due to low + reputation. + +migrations: + db: [] + runtime: [] + +crates: + - name: polkadot-statement-distribution + +host_functions: [] diff --git a/prdoc/1.6.0/pr_2031.prdoc b/prdoc/1.6.0/pr_2031.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fc2695df52e1b9205e77b22069938c4df99ec773 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2033.prdoc b/prdoc/1.6.0/pr_2033.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eeb7ff2b4eed162ae435ff93e9a14f4442a5aacb --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2281.prdoc b/prdoc/1.6.0/pr_2281.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c5453a08f2a352e63e1ea0321218b41375689fa2 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2331.prdoc b/prdoc/1.6.0/pr_2331.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e3daf4c45bd414337535edc4c620a8d553c17f80 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2403.prdoc b/prdoc/1.6.0/pr_2403.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f1c4d3ecbaf10785a63df49a606644a06e9e2d73 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2481.prdoc b/prdoc/1.6.0/pr_2481.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d8736b1afd6eb76c340b1a4f86434be4a8c3d6df --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2522.prdoc b/prdoc/1.6.0/pr_2522.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9a98f984bacb510bfd10b888a49e711d5ccfad3a --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2532.prdoc b/prdoc/1.6.0/pr_2532.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d0df0ee4aca95527dc4cc4b8ee77365d9b0ffdbb --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2597.prdoc b/prdoc/1.6.0/pr_2597.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..33d8505318416b331c686c1d95ca108f55c46cdd --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2637.prdoc b/prdoc/1.6.0/pr_2637.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a7ab4f93222e5e7fcf9d069e668e45e6fd3dc168 --- /dev/null +++ b/prdoc/1.6.0/pr_2637.prdoc @@ -0,0 +1,18 @@ +title: Validator disabling in Dispute Participation. + +doc: + - audience: Node Operator + description: | + Once a validator has been disabled for misbehavior, other validators + should no longer participate in disputes initiated by it. + This feature is needed to ensure robust spam protection against + malicious actors. + +migrations: + db: [] + runtime: [] + +crates: + - name: polkadot-node-core-dispute-coordinator + +host_functions: [] diff --git a/prdoc/1.6.0/pr_2651.prdoc b/prdoc/1.6.0/pr_2651.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e28013d4330eec8e2cae824450144d10817a030b --- /dev/null +++ b/prdoc/1.6.0/pr_2651.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: Unique Usernames for Identity + +doc: + - audience: Runtime User + description: | + Adds the ability to add unique usernames for an account with reverse lookup (as in `AccountId` + to `Username` and `Username` to `AccountId`). + +crates: [ ] diff --git a/prdoc/1.6.0/pr_2656.prdoc b/prdoc/1.6.0/pr_2656.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..563218dbde62bbb2c6cd565a2e7e427c9d8845d4 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2663-fix-could-not-create-temporary-drectory.prdoc b/prdoc/1.6.0/pr_2663-fix-could-not-create-temporary-drectory.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2119599fce11cdf7070499d6e0be0fd4e859d14a --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2666.prdoc b/prdoc/1.6.0/pr_2666.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d7fbbda510853eff129629f89c2abbfa688c18b6 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2682.prdoc b/prdoc/1.6.0/pr_2682.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eaa5f5a4a9a69782806c0805d3accddd6a50d182 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2684.prdoc b/prdoc/1.6.0/pr_2684.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8960b6460f0dafe8cdf0c382aa6b1a510a74c5d3 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2687.prdoc b/prdoc/1.6.0/pr_2687.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..90e635d80529c0693254e6ffa07db5a6c31cbc3e --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2689.prdoc b/prdoc/1.6.0/pr_2689.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5d3081e3a4ce71d872c8eb8a96fda5b2e273b684 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2694.prdoc b/prdoc/1.6.0/pr_2694.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c393dcfeb9a8008d1e8921a2bfa88db79e6a414c --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2758.prdoc b/prdoc/1.6.0/pr_2758.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d8cb0557e9b65888bd67801580cb5c8c50b9ee13 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2764.prdoc b/prdoc/1.6.0/pr_2764.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..adfa4f47c93d853418b9bf097e7917f1ee477033 --- /dev/null +++ b/prdoc/1.6.0/pr_2764.prdoc @@ -0,0 +1,16 @@ +title: Validator disabling in Backing. + +doc: + - audience: Node Operator + description: | + Once a validator has been disabled for misbehavior, it will no longer + sign backing statements in the current era. + +migrations: + db: [] + runtime: [] + +crates: + - name: polkadot-node-core-backing + +host_functions: [] diff --git a/prdoc/1.6.0/pr_2767.prdoc b/prdoc/1.6.0/pr_2767.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c2cd466c0097ac17882909c1f010a6fee54cecb3 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2771.prdoc b/prdoc/1.6.0/pr_2771.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..50fb99556ecddf39adbcece77d9b83e5d98651b4 --- /dev/null +++ b/prdoc/1.6.0/pr_2771.prdoc @@ -0,0 +1,9 @@ +title: Add fallback request for req-response protocols + +doc: + - audience: Node Dev + description: | + Enable better req-response protocol versioning, by allowing for fallback requests on different protocols. + +crates: + - name: sc-network diff --git a/prdoc/1.6.0/pr_2783.prdoc b/prdoc/1.6.0/pr_2783.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0e4c9906541494b1e61984ed8576600f8fd53cb4 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2799.prdoc b/prdoc/1.6.0/pr_2799.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..436dea643e20b3d132105fc25cb003ffe2261830 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2803.prdoc b/prdoc/1.6.0/pr_2803.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ddd3dd677a11e96385e238be991f9bbe5ff91e8 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2804.prdoc b/prdoc/1.6.0/pr_2804.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..456120741d93b3012018ee9c1066e6e7475eb718 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2811.prdoc b/prdoc/1.6.0/pr_2811.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..647fb4c8ccd42005b482c274b2eb4c4d3f849de4 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2813.prdoc b/prdoc/1.6.0/pr_2813.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ff6e5cf5cf6bfd56670c1ed9b8001fc446d3d694 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2823.prdoc b/prdoc/1.6.0/pr_2823.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..64a309969efb353fe7d4d93b6a3e485e024f11f7 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2834.prdoc b/prdoc/1.6.0/pr_2834.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3a5881659de657dcf55254792bf6db61451ff09d --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2835.prdoc b/prdoc/1.6.0/pr_2835.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..037e9b8ec7701de0ab5db102de3fcc8f24604f49 --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2862.prdoc b/prdoc/1.6.0/pr_2862.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fa136b5d98aca8e41ba3a6f6d993640ce3c6a50d --- /dev/null +++ b/prdoc/1.6.0/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/1.6.0/pr_2886.prdoc b/prdoc/1.6.0/pr_2886.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9fd97c11e11145168cdd84a5e799e38a53628562 --- /dev/null +++ b/prdoc/1.6.0/pr_2886.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove bounds from `PrevalidateAttests` struct definition + +doc: + - audience: Runtime Dev + description: | + Minimal change to `PrevalidateAssets` to remove some trait bounds on the struct itself while + keeping all its capabilities. + +crates: + - name: polkadot-runtime-common diff --git a/prdoc/1.6.0/pr_2899.prdoc b/prdoc/1.6.0/pr_2899.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0c7afc0ad088a40a000a86086f32be1853bbfc1a --- /dev/null +++ b/prdoc/1.6.0/pr_2899.prdoc @@ -0,0 +1,10 @@ +title: Improve storage monitor API + +doc: + - audience: Node Dev + description: | + This removes the need to unnecessarily provide a very specific data structure DatabaseSource and removes huge + sc-client-db dependency from storage monitor. It is now possible to use storage monitor with any path. + +crates: + - name: sc-storage-monitor diff --git a/prdoc/1.7.0/pr_1222.prdoc b/prdoc/1.7.0/pr_1222.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..82eb341649bc27d6dbd03fde384587414d12b990 --- /dev/null +++ b/prdoc/1.7.0/pr_1222.prdoc @@ -0,0 +1,33 @@ +title: Transactional processing for XCM + +doc: + - audience: Runtime Dev + description: | + Transactional processing was introduced for certain XCM instructions. They are: + - WithdrawAsset + - ReserveAssetDeposited + - TransferAsset + - TransferReserveAsset + - ReceiveTeleportedAsset + - DepositAsset + - DepositReserveAsset + - InitiateReserveWithdraw + - InitiateTeleport + - BuyExecution + - ClaimAsset + - ExportMessage + - LockAsset + - UnlockAsset + - RequestUnlock + Developers must specify a `TransactionalProcessor` when configuring their XCM executor. + FRAME-based runtimes would simply need to configure it with `FrameTransactionalProcessor` to + enable transactional processing. To disable transactional processing of XCMs, `()` may also be + specified as the type for `TransactionalProcessor`. + For runtimes that are not FRAME-based but would like to still harness transactional processing + of XCMs, a type implementing the `ProcessTransaction` trait must be specified as the type for + `TransactionalProcessor`. This trait is for the purpose of connecting the chain's runtime + transactional processor with the XCM executor -- any implementation of `ProcessTransaction` is + possible to be assigned as the `TransactionalProcessor` for the XCM executor. + +crates: + - name: staging-xcm-executor diff --git a/prdoc/1.7.0/pr_1230.prdoc b/prdoc/1.7.0/pr_1230.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8eea1816cb5aba8570a236e16c38df4a05dbed19 --- /dev/null +++ b/prdoc/1.7.0/pr_1230.prdoc @@ -0,0 +1,20 @@ +title: XCMv4 + +doc: + - audience: Runtime Dev + description: | + A new version of the XCM format. + The main changes are: + - Removed `Multi` prefix from types + - Removed `Abstract` asset id + - `Outcome` is now a named fields struct + - Added `Reanchorable` trait, implemented for both `Location` and `Asset` + - New syntax for building `Location`s and `Junction`s using slices. + You build them like `let location = Location::new(1, [Parachain(1000), PalletInstance(50), GeneralIndex(1984)]);` + and match on them like `match location.unpack() { + (1, [Parachain(id)]) => ... + (0, Here) => ..., + (1, [_]) => ..., + }` + +crates: [] diff --git a/prdoc/1.7.0/pr_1296.prdoc b/prdoc/1.7.0/pr_1296.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b7ef4288a57a6bdcad6150a98f7ddc3a0d538038 --- /dev/null +++ b/prdoc/1.7.0/pr_1296.prdoc @@ -0,0 +1,16 @@ +title: fungible fixes and more conformance tests + +doc: + - audience: Runtime Dev + description: | + Adds conformance tests for the Balanced and Unbalanced fungible traits + Fixes Unbalanced::decrease_balance not respecting preservation + Fixes Balanced::pair possibly returning pairs of imbalances which do not cancel each other out. Method now returns a Result instead (breaking change). + Fixes Balances pallet active_issuance possible 'underflow' + Refactors the conformance test file structure to match the fungible file structure: tests for traits in regular.rs go into a test file named regular.rs, tests for traits in freezes.rs go into a test file named freezes.rs, etc. + Improve doc comments + Simplify macros + +crates: + - name: pallet-balances + - name: frame-support diff --git a/prdoc/1.7.0/pr_1313.prdoc b/prdoc/1.7.0/pr_1313.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0ee91da41a9abe3251a041e503377660eb448002 --- /dev/null +++ b/prdoc/1.7.0/pr_1313.prdoc @@ -0,0 +1,18 @@ +# 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: backpressured JSON-RPC server (upgrade jsonrpsee) + +doc: + - audience: Node Operator + description: | + Modifies the jsonrpc server to be "backpressured" and it's possible to configure + how many messages can be "buffered" via the CLI `--rpc_message_buffer_capacity`. + + Major changes in this PR: + - The subscriptions are now bounded and if subscription can't keep up with the server it is dropped + - CLI: add parameter to configure the jsonrpc server bounded message buffer (default is 64) + - Add our own subscription helper to deal with the unbounded streams in substrate + +crates: +- name: sc-rpc-server diff --git a/prdoc/1.7.0/pr_1845.prdoc b/prdoc/1.7.0/pr_1845.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cf6cd1feadf4eefc8df893f36ba66fdb20bc5816 --- /dev/null +++ b/prdoc/1.7.0/pr_1845.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "XCM WeightTrader: Swap Fee Asset for Native Asset" + +doc: + - audience: Runtime Dev + description: | + Implements an XCM executor `WeightTrader`, facilitating fee payments in any asset that can be exchanged for a native asset. + + A few constraints need to be observed: + - `buy_weight` and `refund` operations must be atomic, as another weight trader implementation might be attempted in case of failure. + - swap credit must be utilized since there isn’t an account to which an asset of some class can be deposited with a guarantee to meet the existential deposit requirement. + +crates: + - name: cumulus-primitives-utility diff --git a/prdoc/1.7.0/pr_1871.prdoc b/prdoc/1.7.0/pr_1871.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d1850509afbbe7e57826683f9d24de00f7018dd3 --- /dev/null +++ b/prdoc/1.7.0/pr_1871.prdoc @@ -0,0 +1,12 @@ +title: Adding `try-state` hook to tips pallet + +doc: + - audience: Runtime User + description: | + Enforces the following invariants; + 1. The number of entries in Tips should be equal to Reasons. + 2. If OpenTip.finders_fee is true, then OpenTip.deposit should be greater than zero. + 3. Reasons exists for each Tip[OpenTip.reason], implying equal length of storage. + +crates: +- name: pallet-tips diff --git a/prdoc/1.7.0/pr_2125.prdoc b/prdoc/1.7.0/pr_2125.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ee81975d2d07d5b665426d23b9f9993e55397778 --- /dev/null +++ b/prdoc/1.7.0/pr_2125.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: Introduce bounds for the number of candidate validation subsystem simultaneously processed tasks + +doc: + - audience: Node Dev + description: | + Makes it possible for the candidate validation subsystem to create backpressure on subsystems + requesting to validate a candidate through limiting the number of simultaneously processed + validation tasks. + +crates: + - name: polkadot-node-core-candidate-validation diff --git a/prdoc/1.7.0/pr_2467.prdoc b/prdoc/1.7.0/pr_2467.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..db88ff1fa579ac8eff9fc5ddb5198f30676cda42 --- /dev/null +++ b/prdoc/1.7.0/pr_2467.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Extract warp sync strategy from `ChainSync` + +doc: + - audience: Node Dev + description: | + `WarpSync`, and `StateSync` as the logical part of warp sync, are extracted from `ChainSync` + as independent syncing strategies. `SyncingStrategy` enum is introduced as a proxy between + `SyncingEngine` and specific strategies. `SyncingStrategy` may be replaced by a trait in a + follow-up PRs. + +crates: + - name: sc-network-sync diff --git a/prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc b/prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ac9a0a501b6c63f8d79ff2447ce484503608acb7 --- /dev/null +++ b/prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc @@ -0,0 +1,22 @@ +title: "Use clone instead of fork on pvf" + +doc: + - audience: Node Operator + description: | + For validators: Adds a new, optional security capability. + Most modern Linux machines should support it, otherwise you will get a warning like: + "- Optional: Cannot call clone with all sandboxing flags, a Linux-specific kernel security features: not available" + If you are already running in a secure environment such as a container, this may conflict with our security features; your only option may be to ignore the warning. + Otherwise, it is recommended to upgrade your Linux version! + +migrations: + db: [] + + runtime: [] + +crates: + - name: polkadot-node-core-pvf + - name: polkadot-node-core-pvf-prepare-worker + - name: polkadot-node-core-pvf-execute-worker + +host_functions: [] diff --git a/prdoc/1.7.0/pr_2587.prdoc b/prdoc/1.7.0/pr_2587.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ea23d5cca7ba88df9fb2a106fbff11bd7367716 --- /dev/null +++ b/prdoc/1.7.0/pr_2587.prdoc @@ -0,0 +1,9 @@ +title: Allow fellowship members to swap their AccountIds. + +doc: + - audience: Runtime User + description: | + Add a `exchange_member` extrinsic to the `ranked-collective` pallet that allows to vote on + swapping the AccountId of a member. This can be used to recover lost access to a collective. + +crates: [ ] diff --git a/prdoc/1.7.0/pr_2657.prdoc b/prdoc/1.7.0/pr_2657.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9c5edf4ceee633c00583248fe380aa5f66a59358 --- /dev/null +++ b/prdoc/1.7.0/pr_2657.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: "[frame] `#[pallet::composite_enum]` improved variant count handling + removed `pallet_balances`'s `MaxHolds` config" + +doc: + - audience: Runtime Dev + description: | + The implementation of the `VariantCount` trait for aggregate composite enums, + such as `RuntimeHoldReason` and `RuntimeFreezeReason`, has been fixed. + It is now calculated as the sum of `VariantCount::VARIANT_COUNT` for all corresponding `#[pallet::composite_enum]`. + The `Balances` pallet's `Config` item `type MaxHolds` has been removed, + and `type Holds` is now bound to the variant count of the composite enum `RuntimeHoldReason`. + Consequently, the runtime does not need to consider setting the correct value for `MaxHolds`. + + notes: + - Remove `type MaxHolds` from the `impl pallet_balances::Config for Runtime` in the runtime. + - When holds are expected to be used, ensure that `type RuntimeHoldReason = RuntimeHoldReason` is set for `impl pallet_balances::Config for Runtime`. + +crates: + - name: pallet-balances diff --git a/prdoc/1.7.0/pr_2796.prdoc b/prdoc/1.7.0/pr_2796.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0530c9ad7140f9fc5d0b9b41129910b09f074c48 --- /dev/null +++ b/prdoc/1.7.0/pr_2796.prdoc @@ -0,0 +1,12 @@ +title: "Rococo and Westend Asset-Hub: XCM Transfers with Pallet-Uniques" + +doc: + - audience: Runtime User + description: | + With the added `UniquesTransactor` Rococo and Westend Asset-Hub are now capable of handling + XCM transfers with pallet-uniques. + +crates: + - name: "asset-hub-rococo-runtime" + - name: "asset-hub-westend-runtime" + - name: "assets-common" diff --git a/prdoc/1.7.0/pr_2826.prdoc b/prdoc/1.7.0/pr_2826.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..70de795a100757e653082610935ed0439020ac8c --- /dev/null +++ b/prdoc/1.7.0/pr_2826.prdoc @@ -0,0 +1,10 @@ +title: Enable async backing on asset-hub-rococo + +doc: + - audience: Runtime User + description: | + Async backing has been enabled on Asset Hub Rococo, which now targets 6s block times. + +crates: + - name: asset-hub-rococo-runtime + - name: polkadot-parachain-bin \ No newline at end of file diff --git a/prdoc/1.7.0/pr_2889.prdoc b/prdoc/1.7.0/pr_2889.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..cbb8aafa9979d7a2f6eca9c719c6abcff7b0b45d --- /dev/null +++ b/prdoc/1.7.0/pr_2889.prdoc @@ -0,0 +1,10 @@ +title: Filter backing votes from disabled validators in paras_inherent + +doc: + - audience: Runtime User + description: | + paras_inherent drops any backing votes from disabled validators on block import and asserts + that no votes from disabled validators are included in a block during execution + +crates: + - name: polkadot-runtime-parachains diff --git a/prdoc/1.7.0/pr_2920.prdoc b/prdoc/1.7.0/pr_2920.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d41227ee8b8a4abb8f9c4bf7c2d1946278e89ecd --- /dev/null +++ b/prdoc/1.7.0/pr_2920.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: "Contracts: Stabilize sr25519_verify host function" + +doc: + - audience: Runtime Dev + description: | + Removed the `#[unstable]` attrribute on `sr25519_verify` host function. + +crates: + - name: "pallet-contracts" diff --git a/prdoc/1.7.0/pr_2924.prdoc b/prdoc/1.7.0/pr_2924.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f22c5365b46de1f6cc6a55564bc61dabb5402997 --- /dev/null +++ b/prdoc/1.7.0/pr_2924.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: Add NonFungibleAdapter + +doc: + - audience: Runtime Dev + description: | + Introduces a new adapter, `NonFungibleAdapter`, to work with `frame_support::traits::tokens::nonfungible` + through XCM. + +crates: + - name: staging-xcm-builder diff --git a/prdoc/1.7.0/pr_2942.prdoc b/prdoc/1.7.0/pr_2942.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..caa276c099b395371025a23aa76734b186a1675e --- /dev/null +++ b/prdoc/1.7.0/pr_2942.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix pallet-nomination-pools v6 to v7 migration + +doc: + - audience: Node Dev + description: | + Restores the behaviour of the nomination pools `V6ToV7` migration so that it still works when + the pallet will be upgraded to V8 afterwards. + +crates: + - name: "pallet-nomination-pools" diff --git a/prdoc/1.7.0/pr_2970.prdoc b/prdoc/1.7.0/pr_2970.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1db8f7bb334d5b9106f4594f61ccef780db2a313 --- /dev/null +++ b/prdoc/1.7.0/pr_2970.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add `availability-distribution` and `biftield-distribution` subsystem benchmark + +doc: + - audience: Node Dev + description: | + The new subsystem benchmark test objective (`DataAvailabilityWrite`) is designed to stress + test the part of the pipeline that takes as input a backed candidate and then distributes + it as erasure coded chunks to other validators. The test pulls in the `av-store`, + `bitfield-distribution` and `availability-distribution` subsystems while the whole network and rest + of the node subsystems are emulated. + +crates: [ ] diff --git a/prdoc/1.7.0/pr_3001.prdoc b/prdoc/1.7.0/pr_3001.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..32f5a6d05af55fa842584b4d715510ffc23fd964 --- /dev/null +++ b/prdoc/1.7.0/pr_3001.prdoc @@ -0,0 +1,9 @@ +title: "Introduce `Balances::force_adjust_total_issuance`" + +doc: + - audience: Runtime Dev + description: | + Introduce a root extrinsic to forcefully adjust the Total Issuance. Should only be used to fix historic errors. + +crates: + - name: "pallet-balances" diff --git a/prdoc/1.7.0/pr_3009.prdoc b/prdoc/1.7.0/pr_3009.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2a55f3d7d32389691051495b42bbbf5e487a94fa --- /dev/null +++ b/prdoc/1.7.0/pr_3009.prdoc @@ -0,0 +1,10 @@ +title: "sc-informant: Respect `--disable-log-color`" + +doc: + - audience: Node Operator + description: | + Fixes some places that weren't respecting the `--disable-log-color` CLI flag. + +crates: + - name: "sc-informant" + - name: "sc-service" diff --git a/prdoc/1.7.0/pr_3020.prdoc b/prdoc/1.7.0/pr_3020.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b605a2f2f0ff5f22fdd3ce0edcb75ff5f1bf1d45 --- /dev/null +++ b/prdoc/1.7.0/pr_3020.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: Para registration deposit covering max code size + +doc: + - audience: Runtime User + description: | + With this PR all newly registered parachains must pay a deposit equivalent to the cost of + registering validation code of the maximum size. Consequently, they can upgrade their code + to the maximum size at any point without additional cost. + +crates: + - name: polkadot-runtime-common diff --git a/prdoc/1.7.0/pr_3029.prdoc b/prdoc/1.7.0/pr_3029.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f17bd8be18fc7e694c35e17956b2a94588c84883 --- /dev/null +++ b/prdoc/1.7.0/pr_3029.prdoc @@ -0,0 +1,10 @@ +title: "Snowbridge Ethereum Deneb fork preparation" + +doc: + - audience: Runtime Dev + description: | + Changes the Rococo runtime Ethereum fork config to include the Deneb hard-fork. Updates the Snowbridge + subtree with preparation for Deneb. Removes the `beacon-minimal-spec` feature. + +crates: + - name: bridge-hub-rococo-runtime diff --git a/prdoc/1.7.0/pr_3040.prdoc b/prdoc/1.7.0/pr_3040.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..01f3d731c69c8757a5e7e565c66f7ca2ae35bcb7 --- /dev/null +++ b/prdoc/1.7.0/pr_3040.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: Rename transaction to transactionWatch + +doc: + - audience: Node Dev + description: | + Renamed `transaction_unstable_submitAndWatch` to `transactionWatch_unstable_submitAndWatch`, + `transaction_unstable_watchEvent` to `transactionWatch_unstable_watchEvent` and + `transaction_unstable_unwatch` to `transactionWatch_unstable_unwatch`. + +crates: +- name: sc-rpc-spec-v2 diff --git a/prdoc/1.7.0/pr_3057.prdoc b/prdoc/1.7.0/pr_3057.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2e0da0078cde89b0beb15f2d6c63990a0e854de3 --- /dev/null +++ b/prdoc/1.7.0/pr_3057.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: Serialize/Deserialize trait implemented in no-std for XCM assets types + +doc: + - audience: Runtime Dev + description: | + Serialize/Deserialize trait implemented in no-std for XCM v3 and v4 + assets types + +crates: + - name: staging-xcm diff --git a/prdoc/1.7.0/pr_3061.prdoc b/prdoc/1.7.0/pr_3061.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..977a9b6fd799280da528832b8cb4309e4b178664 --- /dev/null +++ b/prdoc/1.7.0/pr_3061.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Do not run unneeded subsystems on collator and its alongside node + +doc: + - audience: Node Dev + description: | + Optimizes overseer building strategy to only include subsystems needed to run the given + type of node. Reduces overseer overhead and also solves the problem with unused subsystems + getting stalled from time to time. + +crates: + - name: polkadot-overseer + - name: polkadot-service diff --git a/prdoc/1.7.0/pr_3077.prdoc b/prdoc/1.7.0/pr_3077.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d82ecffb937f88c0c9615a48d9d016564655e607 --- /dev/null +++ b/prdoc/1.7.0/pr_3077.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: Enable cross-chain Coretime region transfers on Rococo Coretime chain + +doc: + - audience: Runtime User + description: | + This PR allows Coretime regions to be cross-chain transferred from the Rococo Coretime chain. + +crates: + - name: pallet-broker + - name: coretime-rococo-runtime diff --git a/prdoc/1.7.0/pr_3108.prdoc b/prdoc/1.7.0/pr_3108.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3f3259b1004f7498f84606b95b48aa90cd47d0e7 --- /dev/null +++ b/prdoc/1.7.0/pr_3108.prdoc @@ -0,0 +1,18 @@ +# 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: revert paritytech/polkadot#6577 & related changes + +doc: + - audience: Node Dev + description: | + Moves BEEFY related pallets behind `session_pallet` for Rococo and Westend runtimes. + Effects that each `MmrLeaf` in the MMR generated by `mmr_pallet` for `block` references the `next_auth_set` of `block` and not `block`. + Breaking change for proofs generated by `mmr_generateProof` + - audience: Runtime Dev + description: | + Moves BEEFY related pallets behind `session_pallet` for Rococo and Westend runtimes. + Effects that each `MmrLeaf` in the MMR generated by `mmr_pallet` for `block` references the `next_auth_set` of `block` and not `block`. + + +crates: [] diff --git a/prdoc/1.7.0/pr_3110.prdoc b/prdoc/1.7.0/pr_3110.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..06c0fafea04f2f8ef88b648889336b3abe598d71 --- /dev/null +++ b/prdoc/1.7.0/pr_3110.prdoc @@ -0,0 +1,12 @@ +title: Nomination pools Fix payout destination in permissionless unbond + +doc: + - audience: Runtime Dev + description: | + This PR fixes an issue whereby when a nomination pool allowed for permissionless unbonding of + funds, the implicit claimed rewards were mistakenly sent to the caller of the `unbond`, and + not the actual member. A nomination pool only allows permissionless unbonding when its state + was set into `Destroying` by the operator + +crates: + - name: pallet-nomination-pools diff --git a/prdoc/1.7.0/pr_3156.prdoc b/prdoc/1.7.0/pr_3156.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c456b0a67332b55f06ff6f859e93202fb49d1e77 --- /dev/null +++ b/prdoc/1.7.0/pr_3156.prdoc @@ -0,0 +1,13 @@ +title: "Adapt core-fellowship and salary pallets for swapped members" + +doc: + - audience: Runtime Dev + description: | + The ranked-collective pallet got the ability to swap members but the core-fellowship and + salary pallets would not be notified of this change. This MR adds `MemberSwappedHandler` to + the collective that is implemented by both pallets. + +crates: + - name: "pallet-ranked-collective" + - name: "pallet-core-fellowship" + - name: "pallet-salary" diff --git a/prdoc/1.7.0/pr_3228.prdoc b/prdoc/1.7.0/pr_3228.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..42a4893af3620057bbb4351459f26bebdefe79f9 --- /dev/null +++ b/prdoc/1.7.0/pr_3228.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_xcm] Forgotten migration to XCMv4 + added `try-state` to the `pallet_xcm`" + +doc: + - audience: Runtime Dev + description: | + The current release includes the new `XCMv4`, so the runtimes must incorporate + a migration `pallet_xcm::migration::MigrateToLatestXcmVersion` to ensure + proper data migration in `pallet_xcm`. + +crates: + - name: pallet-xcm diff --git a/prdoc/1.8.0/pr_1660.prdoc b/prdoc/1.8.0/pr_1660.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dda38c9549595526bb438e9358ed792c6bc1406c --- /dev/null +++ b/prdoc/1.8.0/pr_1660.prdoc @@ -0,0 +1,12 @@ +title: Implements a percentage cap on staking rewards from era inflation + +doc: + - audience: Runtime Dev + description: | + The `pallet-staking` exposes a new perbill configuration, `MaxStakersRewards`, which caps the + amount of era inflation that is distributed to the stakers. The remainder of the era + inflation is minted directly into `T::RewardRemainder` account. This allows the runtime to be + configured to assign a minimum inflation value per era to a specific account (e.g. treasury). + +crates: + - name: pallet-staking diff --git a/prdoc/1.8.0/pr_2061.prdoc b/prdoc/1.8.0/pr_2061.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..07df11ca0543a5c90a60996f73e96b059a7e8fe4 --- /dev/null +++ b/prdoc/1.8.0/pr_2061.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Parameters Pallet + +doc: + - audience: Runtime Dev + description: | + Adds `pallet-parameters` that allows to have parameters for pallet configs that dynamically change at runtime. Allows to be permissioned on a per-key basis and is compatible with ORML macros. + +crates: + - name: "pallet-parameters" + - name: "frame-support" + - name: "frame-support-procedural" diff --git a/prdoc/1.8.0/pr_2290.prdoc b/prdoc/1.8.0/pr_2290.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9f0476e9152641fa0bacd3e71af77ca92bcd142f --- /dev/null +++ b/prdoc/1.8.0/pr_2290.prdoc @@ -0,0 +1,10 @@ +title: im-online pallet offcain storage cleanup + +doc: + - audience: Runtime Dev + description: | + Adds a function `clear_offchain_storage` to `pallet-im-online`. This function can be used + after the pallet was removed to clear its offchain storage. + +crates: + - name: pallet-im-online diff --git a/prdoc/1.8.0/pr_2903.prdoc b/prdoc/1.8.0/pr_2903.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dd22fa0eea3754436e1dca0a9657eb1be8fb8151 --- /dev/null +++ b/prdoc/1.8.0/pr_2903.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Implement `ConversionToAssetBalance` in asset-rate" + +doc: + - audience: Runtime Dev + description: | + Implements the `ConversionToAssetBalance` trait to the asset-rate pallet. + + Previously only the `ConversionFromAssetBalance` trait was implemented, which would allow to convert an asset balance into the corresponding native balance. + + The `ConversionToAssetBalance` allows to use pallet-asset-rate, e.g., as a mechanism to charge XCM fees in an asset that is not the native. +crates: [ ] + diff --git a/prdoc/1.8.0/pr_2949-async-backing-on-all-testnet-system-chains.prdoc b/prdoc/1.8.0/pr_2949-async-backing-on-all-testnet-system-chains.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..265940815bad021bf28d549ea9a23e2fd8ce584c --- /dev/null +++ b/prdoc/1.8.0/pr_2949-async-backing-on-all-testnet-system-chains.prdoc @@ -0,0 +1,23 @@ +title: Enable async backing on all testnet system chains + +doc: + - audience: Runtime User + description: | + Async backing has been enabled on all testnet system chains: asset-hub-westend, + bridge-hub-westend, bridge-hub-rococo, collectives-westend, contracts-rococo, + coretime-westend, coretime-rococo, people-westend, and people-rococo. These now target 6s + block times. + For the running coretime chains, that requires updating the configuration after the runtime + upgrade but before the end of the current region. + +crates: + - name: asset-hub-westend-runtime + - name: bridge-hub-westend-runtime + - name: bridge-hub-rococo-runtime + - name: collectives-westend-runtime + - name: contracts-rococo-runtime + - name: coretime-westend-runtime + - name: coretime-rococo-runtime + - name: people-westend-runtime + - name: people-rococo-runtime + - name: polkadot-parachain-bin diff --git a/prdoc/1.8.0/pr_3007.prdoc b/prdoc/1.8.0/pr_3007.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..17870469a21988f46a8338599f53562e4cd9acff --- /dev/null +++ b/prdoc/1.8.0/pr_3007.prdoc @@ -0,0 +1,16 @@ +title: Try State Hook for Ranked Collective. + +doc: + - audience: Runtime User + description: | + Invariants for storage items in the ranked collective pallet. Enforces the following Invariants: + 1. Total number of `Members` in storage should be >= [`MemberIndex`] of a [`Rank`] in `MemberCount`. + 2. `Rank` in Members should be in `MemberCount`. + 3.`Sum` of `MemberCount` index should be the same as the sum of all the index attained for + rank possessed by `Members` + 4. `Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`. + 5. `Rank` in `IdToIndex` should be the same as the the `Rank` in `IndexToId`. + 6. `Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of + the member `who` in `Members` +crates: +- name: pallet-ranked-collective diff --git a/prdoc/1.8.0/pr_3052.prdoc b/prdoc/1.8.0/pr_3052.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..09f3cf59e4da467e277aeb3d36c737cd8e6a2a85 --- /dev/null +++ b/prdoc/1.8.0/pr_3052.prdoc @@ -0,0 +1,15 @@ +title: "Fixes a scenario where a nomination pool's `TotalValueLocked` is out of sync due to staking's implicit withdraw" + +doc: + - audience: Runtime Dev + description: | + The nomination pools pallet `TotalValueLocked` may get out of sync if the staking pallet + does implicit withdrawal of unlocking chunks belonging to a bonded pool stash. This fix + is based on a new method on the `OnStakingUpdate` traits, `on_withdraw`, which allows the + nomination pools pallet to adjust the `TotalValueLocked` every time there is an implicit or + explicit withdrawal from a bonded pool's stash. + +crates: + - name: "pallet-nomination-pools" + - name: "pallet-staking" + - name: "sp-staking" diff --git a/prdoc/1.8.0/pr_3060.prdoc b/prdoc/1.8.0/pr_3060.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4cd6674ebb2e91b03ed0f4c5d1626bae58c82286 --- /dev/null +++ b/prdoc/1.8.0/pr_3060.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add retry mechanics to `pallet-scheduler` + +doc: + - audience: Runtime Dev + description: | + This PR adds retry mechanics to pallet-scheduler, as described in the issue above. + Users can now set a retry configuration for a task so that, in case its scheduled run fails, it will be retried after a number of blocks, for a specified number of times or until it succeeds. + If a retried task runs successfully before running out of retries, its remaining retry counter will be reset to the initial value. If a retried task runs out of retries, it will be removed from the schedule. + Tasks which need to be scheduled for a retry are still subject to weight metering and agenda space, same as a regular task. Periodic tasks will have their periodic schedule put on hold while the task is retrying. + +crates: + - name: pallet-scheduler diff --git a/prdoc/1.8.0/pr_3079.prdoc b/prdoc/1.8.0/pr_3079.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c745c1ffbfe5775f205dc5510a637dbfd4e6f56c --- /dev/null +++ b/prdoc/1.8.0/pr_3079.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement transaction_unstable_broadcast and transaction_unstable_stop + +doc: + - audience: Node Dev + description: | + A new RPC class is added to handle transactions. The `transaction_unstable_broadcast` broadcasts + the provided transaction to the peers of the node, until the `transaction_unstable_stop` is called. + The APIs are marked as unstable and subject to change in the future. + To know if the transaction was added to the chain, users can decode the bodies of announced finalized blocks. + This is a low-level approach for `transactionWatch_unstable_submitAndWatch`. + +crates: [ ] diff --git a/prdoc/1.8.0/pr_3154.prdoc b/prdoc/1.8.0/pr_3154.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2dfc1b27810cf75683a374cd15f4bcb7b11105f0 --- /dev/null +++ b/prdoc/1.8.0/pr_3154.prdoc @@ -0,0 +1,9 @@ +title: "Contracts: Stabilize caller_is_root API" + +doc: + - audience: Runtime Dev + description: | + Removed the `#[unstable]` attrribute on `caller_is_root` host function. + +crates: + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3160.prdoc b/prdoc/1.8.0/pr_3160.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..22305b6635aa505f4416b88d5b9f83ebc6163f28 --- /dev/null +++ b/prdoc/1.8.0/pr_3160.prdoc @@ -0,0 +1,12 @@ +title: "prospective-parachains: allow requesting a chain of backable candidates" + +topic: Node + +doc: + - audience: Node Dev + description: | + Enable requesting a chain of multiple backable candidates. Will be used by the provisioner + to build paras inherent data for elastic scaling. + +crates: + - name: polkadot-node-core-prospective-parachains diff --git a/prdoc/1.8.0/pr_3166.prdoc b/prdoc/1.8.0/pr_3166.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..adf7f50e4e965325cfc2cbc25d9f66467483f802 --- /dev/null +++ b/prdoc/1.8.0/pr_3166.prdoc @@ -0,0 +1,9 @@ +title: Expose internal functions used by `spawn_tasks` + +doc: + - audience: Node Dev + description: | + This allows to build a custom version of `spawn_tasks` with less copy-paste required + +crates: + - name: sc-service diff --git a/prdoc/1.8.0/pr_3184.prdoc b/prdoc/1.8.0/pr_3184.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..36ba92bcf5a79ac75934f4fa08e1e02d78b6175e --- /dev/null +++ b/prdoc/1.8.0/pr_3184.prdoc @@ -0,0 +1,10 @@ +title: "Contracts: Remove no longer enforced limits from the Schedule" + +doc: + - audience: Runtime Dev + description: | + The limits are no longer in use and do nothing. Every builder overwritting them + can just adapt their code to remove them without any consequence. + +crates: + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3212.prdoc b/prdoc/1.8.0/pr_3212.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a9de1e28cdfe2294948fb3c954b9b417c09d149b --- /dev/null +++ b/prdoc/1.8.0/pr_3212.prdoc @@ -0,0 +1,9 @@ +title: "Ranked collective introduce `Add` and `Remove` origins" + +doc: + - audience: Runtime Dev + description: | + Add two new origins to the ranked-collective pallet. One to add new members and one to remove members, named `AddOrigin` and `RemoveOrigin` respectively. + +crates: + - name: pallet-ranked-collective diff --git a/prdoc/1.8.0/pr_3225.prdoc b/prdoc/1.8.0/pr_3225.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1dec40ccfbe96f5457aeea6235143b95f1ec9754 --- /dev/null +++ b/prdoc/1.8.0/pr_3225.prdoc @@ -0,0 +1,11 @@ +title: "Introduce submit_finality_proof_ex call to bridges GRANDPA pallet" + +doc: + - audience: Runtime Dev + description: | + New call has been added to pallet-bridge-grandpa: submit_finality_proof_ex. It should be + used instead of deprecated submit_finality_proof. submit_finality_proof will be removed + later. + +crates: + - name: "pallet-bridge-grandpa" diff --git a/prdoc/1.8.0/pr_3230.prdoc b/prdoc/1.8.0/pr_3230.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e6d32f918d3911d691e90ff83fd6bc6695da4c25 --- /dev/null +++ b/prdoc/1.8.0/pr_3230.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server remove prometheus metrics `substrate_rpc_requests_started/finished` and refactor WS ping/pongs. + +doc: + - audience: Node Operator + description: | + This PR updates the rpc server library to `jsonrpsee v0.22` to utilize new APIs. + + Breaking changes: + - Remove prometheus RPC metrics `substrate_rpc_requests_started` and `substrate_rpc_requests_finished`. + - The RPC server now disconnects inactive peers that didn't acknowledge WebSocket + pings more than three times in time. + + Added: + - Add prometheus RPC `substrate_rpc_sessions_time` to collect the duration for each WebSocket + session. +crates: [ ] diff --git a/prdoc/1.8.0/pr_3232.prdoc b/prdoc/1.8.0/pr_3232.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c6a339931c6f56772e0fbaa3a4a8992e7b3050c9 --- /dev/null +++ b/prdoc/1.8.0/pr_3232.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Validate code when scheduling uprades + +doc: + - audience: Runtime User + description: | + Adds checks to ensure that the validation code is valid before scheduling + a code upgrade. + +crates: + - name: polkadot-runtime-parachains diff --git a/prdoc/1.8.0/pr_3243.prdoc b/prdoc/1.8.0/pr_3243.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5bad985a2672453b56ee3828483d7035b71d4b79 --- /dev/null +++ b/prdoc/1.8.0/pr_3243.prdoc @@ -0,0 +1,13 @@ +title: Don't fail fast if the weight limit of a cross contract call is too big + +doc: + - audience: Runtime Dev + description: | + Cross contracts calls will now be executed even if the supplied weight + limit is bigger than the reamining weight. If the **actual** weight is too low + they will fail in the cross contract call and roll back. This is different from the + old behaviour where the limit for the cross contract call must be smaller than + the remaining weight. + +crates: + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3244.prdoc b/prdoc/1.8.0/pr_3244.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3d851c8afe0d2453a943caa1f0342d7ec76fe53c --- /dev/null +++ b/prdoc/1.8.0/pr_3244.prdoc @@ -0,0 +1,18 @@ +title: "Make the `benchmark pallet` command only require a Hasher" + +doc: + - audience: Node Dev + description: | + Currently the `benchmark pallet` command requires a `Block` type, while only using its hasher. + Now this is changed to only require the hasher. This means to use `HashingFor` in the + place where `Block` was required. + Example patch for your node with `cmd` being `BenchmarkCmd::Pallet(cmd)`: + + ```patch + - cmd.run::(config) + + cmd.run::, ()>(config) + ``` + +crates: + - name: sc-client-db + - name: frame-benchmarking-cli diff --git a/prdoc/1.8.0/pr_3272.prdoc b/prdoc/1.8.0/pr_3272.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a03113cf9739b741a37c5d8b4fb41dd59ede0d9f --- /dev/null +++ b/prdoc/1.8.0/pr_3272.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Westend Coretime Runtime + +doc: + - audience: Runtime User + description: | + Add the Broker pallet to the Westend Coretime Chain runtime for the main functionality and sales to start. + +crates: [ ] diff --git a/prdoc/1.8.0/pr_3301.prdoc b/prdoc/1.8.0/pr_3301.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..19d1c5f1c18907cd1996603aade66990c358efeb --- /dev/null +++ b/prdoc/1.8.0/pr_3301.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server add rate limiting. + +doc: + - audience: Node Operator + description: | + Add rate limiting for RPC server which can be utilized by the CLI `--rpc-rate-limit ` + The rate-limiting is disabled by default. +crates: [ ] diff --git a/prdoc/1.8.0/pr_3308.prdoc b/prdoc/1.8.0/pr_3308.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..386cbd6230b42144741f9a85490cddb009221ab2 --- /dev/null +++ b/prdoc/1.8.0/pr_3308.prdoc @@ -0,0 +1,14 @@ +title: "Parachains-Aura: Only produce once per slot" + +doc: + - audience: Node Dev + description: | + With the introduction of asynchronous backing the relay chain allows parachain to include blocks every 6 seconds. + The Cumulus Aura implementations, besides the lookahead collator, are building blocks when there is a free slot for + the parachain in the relay chain. Most parachains are still running with a 12s slot duration and not allowing + to build multiple blocks per slot. But, the block production logic will be triggered every 6s, resulting in error + logs like: "no space left for the block in the unincluded segment". This is solved by ensuring that we don't build + multiple blocks per slot. + +crates: + - name: "cumulus-client-consensus-aura" diff --git a/prdoc/1.8.0/pr_3319.prdoc b/prdoc/1.8.0/pr_3319.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b84ec25a22eda2a561fb44eb1e732a3c36921418 --- /dev/null +++ b/prdoc/1.8.0/pr_3319.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Coretime to Westend + +doc: + - audience: Runtime User + description: | + Add the on demand and coretime assigners and migrate from legacy parachain auctions to coretime. + +crates: [ ] diff --git a/prdoc/1.8.0/pr_3325.prdoc b/prdoc/1.8.0/pr_3325.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eb8126dc912520c049f44fb760bb759caf6336f9 --- /dev/null +++ b/prdoc/1.8.0/pr_3325.prdoc @@ -0,0 +1,10 @@ +title: "Ensure `TracksInfo` tracks are sorted by ID." + +doc: + - audience: Runtime Dev + description: | + Add a `integrity_check` function to trait `TracksInfo` and explicitly state that tracks must + always be sorted by ID. The referenda pallet now also uses this check in its `integrity_test`. + +crates: + - name: pallet-referenda diff --git a/prdoc/1.8.0/pr_3358.prdoc b/prdoc/1.8.0/pr_3358.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b6a03b3872f4e7d24305dfd924ccb8424f6e39b2 --- /dev/null +++ b/prdoc/1.8.0/pr_3358.prdoc @@ -0,0 +1,11 @@ +title: Do not stall finality on spam disputes + +doc: + - audience: Node Operator + description: | + This PR fixes the issue that periodically caused + finality stalls on Kusama due to disputes happening + there in combination with disputes spam protection mechanism. + See: https://github.com/paritytech/polkadot-sdk/issues/3345 + +crates: [ ] diff --git a/prdoc/1.8.0/pr_3361.prdoc b/prdoc/1.8.0/pr_3361.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..65baa9e94a0e38a4af16bf6f69aab7526c4ad920 --- /dev/null +++ b/prdoc/1.8.0/pr_3361.prdoc @@ -0,0 +1,10 @@ +title: Fix double charge of host function weight + +doc: + - audience: Runtime Dev + description: | + Fixed a double charge which can lead to quadratic gas consumption of + the `call` and `instantiate` host functions. + +crates: + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3364.prdoc b/prdoc/1.8.0/pr_3364.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1e7a6a5278d730a71a0bf5a928815812d8576386 --- /dev/null +++ b/prdoc/1.8.0/pr_3364.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server expose batch request configuration + +doc: + - audience: Node Operator + description: | + Add functionality to limit RPC batch requests by two new CLI options: + --rpc-disable-batch-request - disable batch requests on the server + --rpc-max-batch-request-len - limit batches to LEN on the server +crates: [ ] diff --git a/prdoc/1.8.0/pr_3370.prdoc b/prdoc/1.8.0/pr_3370.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f40d3821b9fed3eae49803128a9ff61058cd5d02 --- /dev/null +++ b/prdoc/1.8.0/pr_3370.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove `key` getter from pallet-sudo" + +doc: + - audience: Runtime Dev + description: | + Removed the `key` getter function from the sudo pallet. There is no replacement for getting + the key currently. + +crates: + - name: pallet-sudo diff --git a/prdoc/1.8.0/pr_3384.prdoc b/prdoc/1.8.0/pr_3384.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c19464977582a37b11760c81491a7ea7cf3ebced --- /dev/null +++ b/prdoc/1.8.0/pr_3384.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_contracts] stabilize `call_v2`, `instantiate_v2`, `lock_dependency` and `unlock_dependency`" + +doc: + - audience: Runtime Dev + description: | + These APIs are currently unstable and are being stabilized in this PR. + Note: `add_delegate_dependency` and `remove_delegate_dependency` have been renamed to `lock_dependency` and `unlock_dependency` respectively. + +crates: + - name: pallet-contracts-uapi + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3395.prdoc b/prdoc/1.8.0/pr_3395.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a10fb84fd7f56211602fadaf656a25ea5d382255 --- /dev/null +++ b/prdoc/1.8.0/pr_3395.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "`benchmarking-cli` `pallet` subcommand: refactor `--list` and add `--all` option" + +doc: + - audience: Node Dev + description: | + `pallet` subcommand's `--list` now accepts two values: "all" and "pallets". The former will list all available benchmarks, the latter will list only pallets. + Also adds `--all` to run all the available benchmarks and `--no-csv-header` to omit the csv-style header in the output. + NOTE: changes are backward compatible. + +crates: + - name: frame-benchmarking-cli diff --git a/prdoc/1.8.0/pr_3415.prdoc b/prdoc/1.8.0/pr_3415.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c56a5d3ffaa129d4aba36e1e11905e9d72b936cb --- /dev/null +++ b/prdoc/1.8.0/pr_3415.prdoc @@ -0,0 +1,9 @@ +title: "[pallet-contracts] Add APIVersion to the config." + +doc: + - audience: Runtime Dev + description: | + Add `APIVersion` to the config to communicate the state of the Host functions exposed by the pallet. + +crates: + - name: pallet-contracts diff --git a/prdoc/1.8.0/pr_3435.prdoc b/prdoc/1.8.0/pr_3435.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7d7896bff7d405a0a1dd34f09bc2aff0834b9ff2 --- /dev/null +++ b/prdoc/1.8.0/pr_3435.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix BEEFY-related gossip messages error logs + +doc: + - audience: Node Operator + description: | + Added logic to pump the gossip engine while waiting for other things + to make sure gossiped messages get consumed (practically discarded + until worker is fully initialized). + This fixes an issue where node operators saw error logs, and fixes + potential RAM bloat when BEEFY initialization takes a long time + (i.e. during clean sync). +crates: + - name: sc-consensus-beefy diff --git a/prdoc/1.8.0/pr_3477.prdoc b/prdoc/1.8.0/pr_3477.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..26e96d3635b14634ca523797d741ddaf224aa9ae --- /dev/null +++ b/prdoc/1.8.0/pr_3477.prdoc @@ -0,0 +1,11 @@ +title: Allow parachain which acquires multiple coretime cores to make progress + +doc: + - audience: Node Operator + description: | + Adds the needed changes so that parachains which acquire multiple coretime cores can still make progress. + Only one of the cores will be able to be occupied at a time. + Only works if the ElasticScalingMVP node feature is enabled in the runtime and the block author validator is + updated to include this change. + +crates: [ ] diff --git a/prdoc/pr_1226.prdoc b/prdoc/pr_1226.prdoc deleted file mode 100644 index df7a425b538496d22040f2b5d9151623ea534585..0000000000000000000000000000000000000000 --- a/prdoc/pr_1226.prdoc +++ /dev/null @@ -1,17 +0,0 @@ -title: Removed deprecated `Balances::transfer` and `Balances::set_balance_deprecated` functions. - -doc: - - audience: Builder - description: The Balances pallet's dispatchables `set_balance_deprecated` and `transfer` were deprecated in [paritytech/substrate#12951](https://github.com/paritytech/substrate/pull/12951) and have now been removed. - notes: - - Use `set_balance_deprecated` instead `force_set_balance` and `transfer_allow_death` instead of `transfer`. - -migrations: - db: [] - - runtime: [] - -crates: - - name: pallet-balances - -host_functions: [] diff --git a/prdoc/pr_1255.prdoc b/prdoc/pr_1255.prdoc deleted file mode 100644 index 793b5c3c8597a785276637a796eb83be5738f42a..0000000000000000000000000000000000000000 --- a/prdoc/pr_1255.prdoc +++ /dev/null @@ -1,22 +0,0 @@ -# Schema: Parity PR Documentation Schema (prdoc) -# See doc at https://github.com/paritytech/prdoc - -title: Fix for Reward Deficit in the pool - -doc: - - audience: Core Dev - description: Instead of fragile calculation of current balance by looking at free balance - ED, Nomination Pool now freezes ED in the pool reward account to restrict an account from going below minimum balance. This also has a nice side effect that if ED changes, we know how much is the imbalance in ED frozen in the pool and the current required ED. A pool operator can diligently top up the pool with the deficit in ED or vice versa, withdraw the excess they transferred to the pool. - notes: - - Introduces new call `adjust_pool_deposit` that allows to top up the deficit or withdraw the excess deposit for the pool. - - Switch to using Fungible trait from Currency trait. - -migrations: - db: [] - - runtime: - - { pallet: "pallet-nomination-pools", description: "One time migration of freezing ED from each of the existing pools."} - -crates: - - name: pallet-nomination-pools - -host_functions: [] \ No newline at end of file diff --git a/prdoc/pr_1378.prdoc b/prdoc/pr_1378.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6533dcb663030718fdcd3ffc353b2ae8b2a92407 --- /dev/null +++ b/prdoc/pr_1378.prdoc @@ -0,0 +1,27 @@ +title: Construct Runtime V2 - An outer macro approach to define the runtime + +doc: + - audience: Runtime Dev + description: | + Introduces `#[frame_support::runtime]` that can be attached to a mod to define a runtime. The items + in this mod can be attached to the following attributes to define the key components of the runtime. + 1. `#[runtime::runtime]` attached to a struct defines the main runtime + 2. `#[runtime::derive]` attached to the runtime struct defines the types generated by the runtime + 3. `#[runtime::pallet_index]` must be attached to a pallet to define its index + 4. `#[runtime::disable_call]` can be optionally attached to a pallet to disable its calls + 5. `#[runtime::disable_unsigned]` can be optionally attached to a pallet to disable unsigned calls + 6. A pallet instance can be defined as `TemplateModule: pallet_template` + An optional attribute can be defined as `#[frame_support::runtime(legacy_ordering)]` to ensure that + the order of hooks is same as the order of pallets (and not based on the pallet_index). This is to support + legacy runtimes and should be avoided for new ones. + +migrations: + db: [] + + runtime: [] + +crates: + - name: frame-support + - name: frame-support-procedural + +host_functions: [] diff --git a/prdoc/pr_1554.prdoc b/prdoc/pr_1554.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bfce7c5edafd9df39ff9235f18536591b1d47158 --- /dev/null +++ b/prdoc/pr_1554.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: Runtime Upgrade ref docs and Single Block Migration example pallet + +doc: + - audience: Runtime Dev + description: | + `frame_support::traits::GetStorageVersion::current_storage_version` has been renamed `frame_support::traits::GetStorageVersion::in_code_storage_version`. + A simple find-replace is sufficient to handle this change. + +crates: + - name: "frame-support" + diff --git a/prdoc/pr_1781.prdoc b/prdoc/pr_1781.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e3560842d15ab249539ac2de172dc5914e92cc19 --- /dev/null +++ b/prdoc/pr_1781.prdoc @@ -0,0 +1,45 @@ +# 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: "Multi-Block-Migrations, `poll` hook and new System Callbacks" + +doc: + - audience: Runtime Dev + description: | + The major things that this MR touches are: + + **Multi-Block-Migrations**: `pallet-migrations` is introduced that can be configured in the + `System` of a runtime to act as multi-block migrator. The `migrations` pallet then in turn + receives the list of MBMs as config parameter. The list of migrations can be an aggregated + tuple of `SteppedMigration` trait implementation. + It is paramount that the `migrations` pallet is configured in `System` once it is deployed. A + test is in place to double check this. + + To integrate this into your runtime, it is only necessary to change the return type of + `initialize_block` to `RuntimeExecutiveMode`. For extended info please see + https://github.com/paritytech/polkadot-sdk/pull/1781. + + **poll**: a new pallet hook named `poll` is added. This can be used for places where the code + that should be executed is not deadline critical. Runtime devs are advised to skim their usage + of `on_initialize` and `on_finalize` to see whether they can be replace with `poll`. `poll` is + not guaranteed to be called each block. In fact it will not be called when MBMs are ongoing. + + **System Callbacks**: The `system` pallet gets five new config items - all of which can be + safely set to `()` as default. They are: + - `SingleBlockMigrations`: replaces the `Executive` now for configuring migrations. + - `MultiBlockMigrator`: the `pallet-migrations` would be set here, if deployed. + - `PreInherents`: a hook that runs before any inherent. + - `PostInherents`: a hook to run between inherents and `poll`/MBM logic. + - `PostTransactions`: a hook to run after all transactions but before `on_idle`. + +crates: + - name: frame-executive + - name: frame-system + - name: frame-support + - name: frame-support-procedural + - name: pallet-migrations + - name: sc-basic-authorship + - name: sc-block-builder + - name: sp-api + - name: sp-api-proc-macro + - name: sp-runtime diff --git a/prdoc/pr_2393.prdoc b/prdoc/pr_2393.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..708d017fafd50d103c0470997c3a7571e0ff30c1 --- /dev/null +++ b/prdoc/pr_2393.prdoc @@ -0,0 +1,16 @@ +title: "[XCMP] Use the number of 'ready' pages in XCMP suspend logic" + +doc: + - audience: Runtime Dev + description: | + Semantics of the suspension logic in the XCMP queue pallet change from using the number of + total pages to the number of 'ready' pages. The number of ready pages is now also exposed by + the `MessageQueue` pallet to downstream via the queue `footprint`. + +crates: + - name: cumulus-pallet-xcmp-queue + bump: patch + - name: pallet-message-queue + bump: patch + - name: frame-support + bump: major diff --git a/prdoc/pr_3002.prdoc b/prdoc/pr_3002.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..511a07e39c47d5055bd161fda0e748f96e4a9997 --- /dev/null +++ b/prdoc/pr_3002.prdoc @@ -0,0 +1,29 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: PoV Reclaim Runtime Side +author: skunert +topic: runtime +doc: + - audience: Runtime Dev + description: | + Adds a mechanism to reclaim proof size weight. + 1. Introduces a new `SignedExtension` that reclaims the difference + between benchmarked proof size weight and actual consumed proof size weight. + 2. Introduces a manual mechanism, `StorageWeightReclaimer`, to reclaim excess storage weight for situations + that require manual weight management. The most prominent case is the `on_idle` hook. + 3. Adds the `storage_proof_size` host function to the PVF. Parachain nodes should add it to ensure compatibility. + + To enable proof size reclaiming, add the host `storage_proof_size` host function to the parachain node. Add the + `StorageWeightReclaim` `SignedExtension` to your runtime and enable proof recording during block import. + + +crates: + - name: "cumulus-primitives-storage-weight-reclaim" +host_functions: + - name: "storage_proof_size" + description: | + This host function is used to pass the current size of the storage proof to the runtime. + It was introduced before but becomes relevant now. + Note: This host function is intended to be used through `cumulus_primitives_storage_weight_reclaim::get_proof_size`. + Direct usage is not recommended. diff --git a/prdoc/pr_3187.prdoc b/prdoc/pr_3187.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bda41142d86c8f9c1af8398e0cf21cdfeebebfa7 --- /dev/null +++ b/prdoc/pr_3187.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Retrying an execution on failed runtime construction + +doc: + - audience: Node Dev + description: | + If a runtime construction error happened during the execution request, then the artifact is re-prepared + and the execution request is retried at most once. See also the related issue. + +crates: + - name: polkadot-node-core-candidate-validation + - name: polkadot-node-core-pvf + - name: polkadot-node-core-pvf-execute-worker + - name: polkadot-node-core-pvf-common diff --git a/prdoc/pr_3231.prdoc b/prdoc/pr_3231.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..26e96d3635b14634ca523797d741ddaf224aa9ae --- /dev/null +++ b/prdoc/pr_3231.prdoc @@ -0,0 +1,11 @@ +title: Allow parachain which acquires multiple coretime cores to make progress + +doc: + - audience: Node Operator + description: | + Adds the needed changes so that parachains which acquire multiple coretime cores can still make progress. + Only one of the cores will be able to be occupied at a time. + Only works if the ElasticScalingMVP node feature is enabled in the runtime and the block author validator is + updated to include this change. + +crates: [ ] diff --git a/prdoc/pr_3233.prdoc b/prdoc/pr_3233.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ed4e8cce31f75b4bd98f91f7d15ec3fae0761354 --- /dev/null +++ b/prdoc/pr_3233.prdoc @@ -0,0 +1,12 @@ +title: "provisioner: allow multiple cores assigned to the same para" + +topic: Node + +doc: + - audience: Node Dev + description: | + Enable supplying multiple backable candidates to the paras_inherent pallet for the same paraid. + +crates: + - name: polkadot-node-core-prospective-parachains + - name: polkadot-node-core-provisioner diff --git a/prdoc/pr_3302.prdoc b/prdoc/pr_3302.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a2d93fc607355542c39785742231e6da705e27da --- /dev/null +++ b/prdoc/pr_3302.prdoc @@ -0,0 +1,15 @@ +title: Collator protocol changes for elastic scaling + +doc: + - audience: Node Dev + description: | + This PR introduces changes to the collator protocol to support elastic scaling. + Namely, a new variant added to the collation response to include parent head-data + along with the collation. Currently, the new variant is not being used. + - audience: Node Operator + description: | + Validators are required to upgrade to this version before collators in order to + support the elastic scaling of parachains. + +crates: + - name: polkadot-collator-protocol diff --git a/prdoc/pr_3324.prdoc b/prdoc/pr_3324.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0425fbf317c8620125e9fe64f96a0eb99e4595f0 --- /dev/null +++ b/prdoc/pr_3324.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Fix weight calculation and event emission in pallet-membership" + +doc: + - audience: Runtime Dev + description: | + Bug fix for the membership pallet to use correct weights. Also no event will be emitted + anymore when `change_key` is called with identical accounts. + +crates: + - name: pallet-membership diff --git a/prdoc/pr_3350.prdoc b/prdoc/pr_3350.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1171614d67f65e8d75a2d3b4f6545a87bfb0ac0e --- /dev/null +++ b/prdoc/pr_3350.prdoc @@ -0,0 +1,31 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: removed `pallet::getter` from Pallet AURA + +doc: + - audience: Runtime Dev + description: | + This PR removes all the declarations of macro `pallet::getter` in the Pallet AURA and replaces the use of storage getter functions authorities() & current_slot() with `StorageItem::get()` API across the crates as listed bellow. + The purpose is to discourage developers to use this macro, that is currently being removed and soon will be deprecated. + +crates: + - name: pallet-aura + - name: solochain-template-runtime + - name: cumulus-pallet-aura-ext + - name: parachain-template-runtime + - name: people-westend-runtime + - name: people-rococo-runtime + - name: bridge-hub-rococo-runtime + - name: bridge-hub-westend-runtime + - name: rococo-parachain-runtime + - name: penpal-runtime + - name: glutton-westend-runtime + - name: shell-runtime + - name: seedling-runtime + - name: collectives-westend-runtime + - name: asset-hub-rococo-runtime + - name: asset-hub-westend-runtime + - name: contracts-rococo-runtime + - name: coretime-westend-runtime + - name: coretime-rococo-runtime diff --git a/prdoc/pr_3371.prdoc b/prdoc/pr_3371.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..605d540772f0b249962088463237dda2e6690d6c --- /dev/null +++ b/prdoc/pr_3371.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: removed `pallet::getter` from example pallets + +doc: + - audience: Runtime Dev + description: | + This PR removes all the `pallet::getter` usages from the template pallets found in the Substrate and Cumulus template nodes, and from the Substrate example pallets. + The purpose is to discourage developers to use this macro, that is currently being removed and soon will be deprecated. + +crates: + - name: pallet-template + - name: pallet-parachain-template + - name: pallet-example-basic + - name: pallet-example-kitchensink + - name: pallet-example-offchain-worker + - name: pallet-example-split + diff --git a/prdoc/pr_3377.prdoc b/prdoc/pr_3377.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8e5b3935512b1b2dd12836e940a5d95040df8cf2 --- /dev/null +++ b/prdoc/pr_3377.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: Permissioned contract deployment + +doc: + - audience: Runtime Dev + description: | + This PR introduces two new config types that specify the origins allowed to + upload and instantiate contract code. However, this check is not enforced when + a contract instantiates another contract. + +crates: +- name: pallet-contracts diff --git a/prdoc/pr_3378.prdoc b/prdoc/pr_3378.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2470fc158519e31e297a7cbefcae3bc54eb8f708 --- /dev/null +++ b/prdoc/pr_3378.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove deprecated GenesisConfig + +doc: + - audience: Runtime Dev + description: | + Removes deprecated type `GenesisConfig`, it was replaced by `RuntimeGenesisConfig` on May 24 of 2023. + The type `GenesisConfig` was deprecated on May 24 of 2023 [#14210](https://github.com/paritytech/substrate/pull/14210) + +crates: + - name: frame-support-procedural \ No newline at end of file diff --git a/prdoc/pr_3403.prdoc b/prdoc/pr_3403.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..086e769da3c39c4d3def42791c18a82948676386 --- /dev/null +++ b/prdoc/pr_3403.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: "Add `claim_assets` extrinsic to `pallet-xcm`" + +doc: + - audience: Runtime User + description: | + There's a new extrinsic in `pallet-xcm` for claiming assets. + This means that if your assets ever get trapped while teleporting or doing reserve asset transfers, + you can easily claim them by calling this new extrinsic. + - audience: Runtime Dev + description: | + There's a new extrinsic in `pallet-xcm` that needs a new configuration item for its benchmarks. + It's a simple function in `pallet_xcm::benchmarking::Config`, `get_asset`, that returns a valid asset + handled by the AssetTransactor of the chain. + If you're already using `pallet-xcm-benchmarks`, then you already have this function there and can + just copy and paste it. + +crates: + - name: pallet-xcm + - name: staging-xcm diff --git a/prdoc/pr_3411.prdoc b/prdoc/pr_3411.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d847d6756ac7f019cf12e4d9e7993ebeda1273ab --- /dev/null +++ b/prdoc/pr_3411.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: add Encointer as trusted teleporter for Westend + +doc: + - audience: Runtime Dev + description: | + add Encointer as trusted teleporter for Westend with ParaId 1003 + +crates: [ ] diff --git a/prdoc/pr_3412.prdoc b/prdoc/pr_3412.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ee6edfeb837f81646d32633ed009d2cded4dcfe --- /dev/null +++ b/prdoc/pr_3412.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[FRAME] Add genesis test and remove some checks" + +doc: + - audience: Runtime Dev + description: | + The construct_runtime macro now generates a test to assert that all `GenesisConfig`s of all + pallets can be build within the runtime. This ensures that the `BuildGenesisConfig` runtime + API works. + Further, some checks from a few pallets were removed to make this pass. + +crates: + - name: pallet-babe + - name: cumulus-pallet-aura-ext + - name: pallet-session diff --git a/prdoc/pr_3447.prdoc b/prdoc/pr_3447.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1d8d4f409f77cf29247d35d196bccff390896881 --- /dev/null +++ b/prdoc/pr_3447.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use generic hash for runtime wasm in resolve_state_version_from_wasm + +doc: + - audience: Node Dev + description: | + Changes the runtime hash algorithm used in resolve_state_version_from_wasm from DefaultHasher to a caller-provided + one (usually HashingFor). Fixes a bug where the runtime wasm was being compiled again when it was not + needed, because the hash did not match +crates: + - name: sc-chain-spec diff --git a/prdoc/pr_3453.prdoc b/prdoc/pr_3453.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1f01440f722fb9984231f690a20c517b9d4bf0a1 --- /dev/null +++ b/prdoc/pr_3453.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: "[pallet-nomination-pools]: `chill` is permissionless if depositor's stake is less than `min_nominator_bond`" + +doc: + - audience: Runtime Dev + description: | + Nomination pools currently have an issue whereby member funds cannot be unbonded if the depositor bonded amount is less than `MinNominatorBond`. + This PR makes the `chill` function permissionless if this condition is met. + Consequently, `nominate` function also checks for `depositor` to have at least `MinNominatorBond`, and returns `MinimumBondNotMet` error if not. +crates: + - name: pallet-nomination-pools diff --git a/prdoc/pr_3454.prdoc b/prdoc/pr_3454.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7f564258f23a57f3784c2ee055176153f006734f --- /dev/null +++ b/prdoc/pr_3454.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Introduce storage attr macro #[disable_try_decode_storage] and set it on System::Events and ParachainSystem::HostConfiguration" + +doc: + - audience: Runtime Dev + description: | + Allows marking storage items with \#[disable_try_decode_storage], which disables that storage item from being decoded + during try_decode_entire_state calls. + + Applied the attribute to System::Events to close https://github.com/paritytech/polkadot-sdk/issues/2560. + Applied the attribute to ParachainSystem::HostConfiguration to resolve periodic issues with it. + +crates: + - name: frame-support-procedural + - name: frame-system + - name: cumulus-pallet-parachain-system + diff --git a/prdoc/pr_3456.prdoc b/prdoc/pr_3456.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c7327e17e57d74614fd1b593cc222faf4a87192d --- /dev/null +++ b/prdoc/pr_3456.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Removed `pallet::getter` usage from `pallet-collective` + +doc: + - audience: Runtime Dev + description: | + This PR removes `pallet::getter` usage from `pallet-collective`, and updates dependant code accordingly. + The syntax `StorageItem::::get()` should be used instead. + +crates: + - name: pallet-collective + - name: kitchensink-runtime + - name: collectives-westend-runtime diff --git a/prdoc/pr_3460.prdoc b/prdoc/pr_3460.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1f16fe0890836dd25bfcaa75d5fc647688230dd8 --- /dev/null +++ b/prdoc/pr_3460.prdoc @@ -0,0 +1,26 @@ +# 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: Repot all templates + +doc: + - audience: Runtime Dev + description: | + This PR moves all templates into a single folder in the polkadot-sdk repo (`/templates`) and + unifies their crate names as well. Most notably, the crate name for what was formerly known + as `node-template` is no `solochain-template-node`. The other two crates in the template are + consequently called: `solochain-runtime-template` and `pallet-solochain-template`. + The other two template crate names follow a similar patter, just replacing `solochain` with + `parachain` or `minimal`. + + This PR is part of a bigger step toward automating the template repositories, see the + following: https://github.com/paritytech/polkadot-sdk/issues/3155 + +# the following crates are removed and renamed, although none are released. +crates: + - name: minimal-template-runtime # formerly called minimal-runtime + - name: minimal-template-node # formerly called minimal-node + - name: solochain-template-node # formerly called node-template + - name: solochain-template-runtime # formerly called node-template-runtime + - name: parachain-template-runtime # formerly called parachain-runtime + - name: parachain-template-runtime # formerly called parachain-node diff --git a/prdoc/pr_3471.prdoc b/prdoc/pr_3471.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0c16587fc9086c6173e6917cdbf9c434599cdcd6 --- /dev/null +++ b/prdoc/pr_3471.prdoc @@ -0,0 +1,33 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: removed `pallet::getter` from cumulus pallets + +doc: + - audience: Runtime Dev + description: | + This PR removes all the `pallet::getter` usages from cumulus pallets, and updates depdendant runtimes accordingly. + The ParaId can be retrieved using `>::get()`. + For other storage items, the syntax `StorageItem::::get()` should be used instead. + +crates: + - name: cumulus-pallet-aura-ext + - name: pallet-collator-selection + - name: cumulus-pallet-parachain-system + - name: staging-parachain-info + - name: parachain-template-runtime + - name: asset-hub-rococo-runtime + - name: asset-hub-westend-runtime + - name: bridge-hub-rococo-runtime + - name: bridge-hub-westend-runtime + - name: collectives-westend-runtime + - name: contracts-rococo-runtime + - name: coretime-rococo-runtime + - name: coretime-westend-runtime + - name: glutton-westend-runtime + - name: people-rococo-runtime + - name: people-westend-runtime + - name: shell-runtime + - name: penpal-runtime + - name: rococo-parachain-runtime + diff --git a/prdoc/pr_3491.prdoc b/prdoc/pr_3491.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e36afb916a6226c3609b1e2eb8aacf6af8127d32 --- /dev/null +++ b/prdoc/pr_3491.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: Remove Deprecated OldWeight + +doc: + - audience: Runtime Dev + description: | + Removed deprecated sp_weights::OldWeight type. Use [`weight_v2::Weight`] instead. + +crates: + - name: frame-support diff --git a/prdoc/pr_3504.prdoc b/prdoc/pr_3504.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..90a8ddd38b8f7f8159199ac6423ffee3dc645291 --- /dev/null +++ b/prdoc/pr_3504.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: add prometheus label "is_rate_limited" to rpc calls + +doc: + - audience: Node Operator + description: | + This PR adds a label "is_rate_limited" to the prometheus metrics "substrate_rpc_calls_time" and "substrate_rpc_calls_finished" + than can be used to distinguish rate-limited RPC calls from other RPC calls. Because rate-limited RPC calls may take + tens of seconds. + +crates: [ ] diff --git a/prdoc/pr_3505.prdoc b/prdoc/pr_3505.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b55972927c33002c918aa4b1cd147e9ee01a3e52 --- /dev/null +++ b/prdoc/pr_3505.prdoc @@ -0,0 +1,36 @@ +# 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: Removes `as [disambiguation_path]` from the required syntax in `derive_impl` + +doc: + - audience: Runtime Dev + description: | + This PR removes the need to specify `as [disambiguation_path]` for cases where the trait + definition resides within the same scope as default impl path. + + For example, in the following macro invocation + ```rust + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + ... + } + ``` + the trait `DefaultConfig` lies within the `frame_system` scope and `TestDefaultConfig` impls + the `DefaultConfig` trait. + + Using this information, we can compute the disambiguation path internally, thus removing the + need of an explicit specification: + ```rust + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Runtime { + ... + } + ``` + + In cases where the trait lies outside this scope, we would still need to specify it explicitly, + but this should take care of most (if not all) uses of `derive_impl` within FRAME's context. + +crates: + - name: frame-support-procedural + - name: pallet-default-config-example diff --git a/prdoc/pr_3510.prdoc b/prdoc/pr_3510.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6ee2f9a81f750ef0c64a2b8d8197e1e44d7e9f0c --- /dev/null +++ b/prdoc/pr_3510.prdoc @@ -0,0 +1,13 @@ +title: "Fix multi-collator parachain transition to async backing" + +doc: + - audience: Node Operator + description: | + The dynamic Aura slot duration, introduced in PR#3211, didn't take the block import pipeline + into account. The result was the parachain backed by multiple collators not being able to + keep producing blocks after its runtime was upgraded to support async backing, requiring to + restart all the collator nodes. This change fixes the issue, introducing the dynamic Aura + slot duration into the block import pipeline. + +crates: + - name: "polkadot-parachain-bin" diff --git a/prdoc/pr_3513.prdoc b/prdoc/pr_3513.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e1f2afe280a5bc2e8d341f9ab5ac73315f1676e8 --- /dev/null +++ b/prdoc/pr_3513.prdoc @@ -0,0 +1,18 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix call enum's metadata regression + +doc: + - audience: Runtime Dev + - audience: Runtime User + description: | + This PR fixes an issue where in the metadata of all FRAME-based chains, the documentation for + all extrinsic/call/transactions has been replaced by a single line saying "see Pallet::name". + + Many wallets display the metadata of transactions to the user before signing, and this bug + might have affected this process. + +crates: + - name: frame + - name: frame-support diff --git a/prdoc/pr_3523.prdoc b/prdoc/pr_3523.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c31d9d096dc00797254f7946fd44984636cb1547 --- /dev/null +++ b/prdoc/pr_3523.prdoc @@ -0,0 +1,19 @@ +title: Fix crash of synced parachain node run with `--sync=warp` + +doc: + - audience: Node Operator + description: | + Fix crash of `SyncingEngine` when an already synced parachain node is run with `--sync=warp` + (issue https://github.com/paritytech/polkadot-sdk/issues/3496). + The issue manifests itself by errors in the logs: + ``` + [Parachain] Cannot set warp sync target block: no warp sync strategy is active. + [Parachain] Failed to set warp sync target block header, terminating `SyncingEngine`. + ``` + Followed by a stream of messages: + ``` + [Parachain] Protocol command streams have been shut down + ``` + +crates: + - name: sc-network-sync diff --git a/prdoc/pr_3532.prdoc b/prdoc/pr_3532.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d47c8d1d49156aca4ac1b7683ee7fbd9d8e9e690 --- /dev/null +++ b/prdoc/pr_3532.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove deprecated `trait Store` + +doc: + - audience: Runtime Dev + description: | + The deprecated `trait Store` feature has been removed from the codebase. Please remove usages of `generate_store` + macro from your pallets and access the storage through generics. For example, + `::StoredRange::mutate` will need to be updated to `StoredRange::::mutate`. + +crates: + - name: frame-support + - name: frame \ No newline at end of file diff --git a/prdoc/pr_3540.prdoc b/prdoc/pr_3540.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d0a91882b6bf4e357fde899c2d939ddb7de62f34 --- /dev/null +++ b/prdoc/pr_3540.prdoc @@ -0,0 +1,10 @@ +title: "[pallet-contracts] Only allow non-deterministic code to be uploaded with Determinism::Relaxed" + +doc: + - audience: Runtime Dev + description: | + The `upload_code` extrinsic, will now only allow non-deterministic code to be uploaded with the `Determinism::Relaxed` flag. + This prevent an attacker from uploading "deterministic" code with the `Determinism::Relaxed` flag, preventing the code to be instantiated for on-chain execution. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3574.prdoc b/prdoc/pr_3574.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..99868ea8a132a6440b9daff0d9f7deaa329ca4b3 --- /dev/null +++ b/prdoc/pr_3574.prdoc @@ -0,0 +1,23 @@ +title: Generate test functions for each benchmark with benchmarking v2 + +doc: + - audience: Runtime Dev + description: | + This PR fixes an issue where using `impl_benchmark_test_suite` macro + within modules that use the benchmarking v2 macros (`#[benchmarks]` + and `#[instance_benchmarks]`) always produced a single test called + `test_benchmarks` instead of a separate benchmark test for every + benchmark (noted with the `#[benchmark]` macro). + + By using this macro from now on, new tests will be created named + `test_benchmark_{name}` where `name` is the name of the benchmark + function. Those tests will be nested inside the module intended for + benchmark functions. + + Also, when using `impl_benchmark_test_suite` inside the module, + the import of such marco will not be necessary, so any explicit + import of it will be marked as unused, the same way it works for + v1 macros so far. + +crates: + - name: frame-support-procedural diff --git a/prdoc/pr_3589.prdoc b/prdoc/pr_3589.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..98b9b1039ab49f1dc62580d56327bf7944ed3372 --- /dev/null +++ b/prdoc/pr_3589.prdoc @@ -0,0 +1,10 @@ +title: Removed `pallet::getter` usage from `pallet-im-online` + +doc: + - audience: Runtime Dev + description: | + This PR removes `pallet::getter` usage from `pallet-im-online`, and updates dependant code accordingly. + The syntax `StorageItem::::get()` should be used instead. + +crates: + - name: pallet-im-online diff --git a/prdoc/pr_3606.prdoc b/prdoc/pr_3606.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..18b71de9477ea516de2bcd5ecfab52a6a5479afb --- /dev/null +++ b/prdoc/pr_3606.prdoc @@ -0,0 +1,9 @@ +title: "[pallet_contracts] mark lock/unlock_delegate_dependency as stable" + +doc: + - audience: Runtime Dev + description: | + Lock and unlock delegate dependency are stable now, so we can mark them as such. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3616.prdoc b/prdoc/pr_3616.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fcf068dcd1739365f9cf04782efea3503327f3d4 --- /dev/null +++ b/prdoc/pr_3616.prdoc @@ -0,0 +1,28 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Benchmarking pallet V2 syntax extension: pov_mode attribute" + +doc: + - audience: Runtime Dev + description: | + Adds the `pov_mode` attribute from the V1 benchmarking syntax to the V2 syntax. This allows to + override the default PoV mode (`MaxEncodedLen`) to either `Measured` or `Ignored`. It can be + overridden for a whole benchmark, a key prefix of a specific key itself. + + Example syntax looks like this: + ```rust + #[benchmark(pov_mode = Measured { + Pallet: Measured, + Pallet::Storage: MaxEncodedLen, + })] + fn do_some() { + .. + } + ``` + +crates: + - name: frame-support-procedural + bump: minor + - name: frame-support + bump: minor diff --git a/prdoc/pr_3636.prdoc b/prdoc/pr_3636.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..934158ac1022dc14309696bea43492db8f7a0e88 --- /dev/null +++ b/prdoc/pr_3636.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_broker] Fix `Linear::adapt_price` behavior at zero" + +doc: + - audience: Runtime Dev + description: | + This fixes the behaviour of `Linear` which is the default implementation of the `AdaptPrice` + trait in the broker pallet. Previously if cores were offered but not sold in only one sale, + the price would be set to zero and due to the logic being purely multiplicative, the price + would stay at 0 indefinitely. + +crates: + - name: pallet-broker diff --git a/prdoc/pr_3639.prdoc b/prdoc/pr_3639.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..46e3f6f4d76394ded0c7c76458e1a71148ea5cb4 --- /dev/null +++ b/prdoc/pr_3639.prdoc @@ -0,0 +1,19 @@ +title: Prevents staking controllers from becoming stashes of different ledgers; Ensures that no ledger in bad state is mutated. + +doc: + - audience: Runtime User + description: | + This PR introduces a fix to the staking logic which prevents an existing controller from bonding as a stash of another ledger, which + lead to staking ledger inconsistencies down the line. In addition, it adds a few (temporary) gates to prevent ledgers that are already + in a bad state from mutating its state. + + In summary: + * Checks if stash is already a controller when calling `Call::bond` and fails if that's the case; + * Ensures that all fetching ledgers from storage are done through the `StakingLedger` API; + * Ensures that a `Error::BadState` is returned if the ledger bonding is in a bad state. This prevents bad ledgers from mutating (e.g. + `bond_extra`, `set_controller`, etc) its state and avoid further data inconsistencies. + * Prevents stashes which are controllers or another ledger from calling `set_controller`, since that may lead to a bad state. + * Adds further try-state runtime checks that check if there are ledgers in a bad state based on their bonded metadata. + +crates: +- name: pallet-staking diff --git a/prdoc/pr_3643.prdoc b/prdoc/pr_3643.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6e85af8d99a07f397ce03d4b7c936f7560f36c8f --- /dev/null +++ b/prdoc/pr_3643.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: Fix weight refund for `pallet_collator_selection::set_candidacy_bond` + +doc: + - audience: Runtime Dev + description: | + This PR implements the weight refund of `pallet_collator_selection::set_candidacy_bond` to + account for no iterations over the candidate list when the candidacy bond is decreased. +crates: + - name: pallet-collator-selection diff --git a/prdoc/pr_3654.prdoc b/prdoc/pr_3654.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..de4a68d23569d130e5df0d0400edbe498e3875e4 --- /dev/null +++ b/prdoc/pr_3654.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 `experimental` feature from `pallet-aura` + +doc: + - audience: Runtime Dev + description: | + The `experimental` feature in `pallet-aura`, previously used to gate different experimental + changes, became redundant with the introduction of the async backing which relies on the + mentioned changes, therefore, it is removed. + +crates: + - name: pallet-aura diff --git a/prdoc/pr_3665.prdoc b/prdoc/pr_3665.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..67725d24d185502871baf41ba65b856449c4dfc3 --- /dev/null +++ b/prdoc/pr_3665.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: Revert "FRAME Create TransactionExtension as a replacement for SignedExtension (#2280)" + +doc: + - audience: Runtime Dev + description: | + This PR reverts the PR which introduced `TransactionExtension` to replace `SignedExtension`. + +crates: [ ] diff --git a/prdoc/pr_3696.prdoc b/prdoc/pr_3696.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f3371d1734adf3e9281a83f900950129817ccc46 --- /dev/null +++ b/prdoc/pr_3696.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: Add HRMP notification handlers to the xcm-executor + +doc: + - audience: Runtime Dev + description: | + Adds optional HRMP notification handlers to the xcm-executor. These handlers are 3 new config types on the xcm-executor `Config` trait: + - `HrmpNewChannelOpenRequestHandler` + - `HrmpChannelAcceptedHandler` + - `HrmpChannelClosingHandler` + + The traits of these config types are implemented on tuples, and on `()` for the default case. + +crates: + - name: staging-xcm-executor diff --git a/prdoc/pr_3722.prdoc b/prdoc/pr_3722.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7e2d7d38795b70213a4fb737a7c7cf4703035272 --- /dev/null +++ b/prdoc/pr_3722.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix kusama 0 backing rewards when entering active set + +doc: + - audience: Runtime Dev + description: | + This PR fixes getting 0 backing rewards the first session when + a node enters the active set. + +crates: + - name: pallet-authority-discovery diff --git a/prdoc/schema_user.json b/prdoc/schema_user.json new file mode 100644 index 0000000000000000000000000000000000000000..1bd0b3b93ee45ea75d9781ef1485898dd86c7ff3 --- /dev/null +++ b/prdoc/schema_user.json @@ -0,0 +1,247 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema#", + "$id": "https://raw.githubusercontent.com/paritytech/prdoc/master/prdoc_schema_user.json", + "version": { + "major": 1, + "minor": 1, + "patch": 0 + }, + "title": "Polkadot SDK PRDoc Schema", + "description": "JSON Schema definition for the Polkadot SDK PR documentation", + "type": "object", + "additionalProperties": false, + "properties": { + "title": { + "title": "Title of the change", + "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", + "title": "Documentation adapted to the audience(s)", + "description": "Description of the PR. Provide a description for each relevant audience.\nSee the `audience` property for more documentation about audiences", + "items": { + "$ref": "#/$defs/doc" + }, + "minItems": 1 + }, + + "crates": { + "title": "Crates", + "description": "You have the option to provide a hint about the crates that have noticeable changes.\n This is used during the crate publishing to crates.io and to help users understand the impact of the changes introduced in your PR.", + "type": "array", + "items": { + "$ref": "#/$defs/crate" + } + }, + + "migrations": { + "title": "Migrations (DB & Runtime)", + "description": "It is important for users to be aware of migrations.\nMake sure to mention any migrations in the appropriate sub-properties:\n- db\n- runtime", + "type": "object", + "properties": { + "db": { + "type": "array", + "nullable": false, + "title": "Database Migration", + "description": "List of the Database Migrations or empty array: []", + "items": { + "$ref": "#/$defs/migration_db" + }, + "minItems": 0, + "required": [ + "name", + "description" + ] + }, + "runtime": { + "type": "array", + "title": "Runtime Migration", + "nullable": false, + "description": "List of the Runtime Migrations or empty array: []", + "minItems": 0, + "items": { + "$ref": "#/$defs/migration_runtime" + }, + "required": [ + "db", + "runtime" + ] + } + } + }, + "host_functions": { + "title": "Host Functions", + "description": "List of the host functions involved in this PR.", + "type": "array", + "items": { + "$ref": "#/$defs/host_function" + } + } + }, + "required": [ + "title", + "doc", + "crates" + ], + "$defs": { + "audience": { + "description": "You may pick one or more audiences and address those users with appropriate documentation, information and warning related to the PR.", + "oneOf": [ + {"const": "Node Dev", + "title": "Node Dev", + "description": "Those who build around the client side code. Alternative client builders, SMOLDOT, those who consume RPCs. These are people who are oblivious to the runtime changes. They only care about the meta-protocol, not the protocol itself."}, + + {"const": "Runtime Dev", + "title": "Runtime Dev", + "description": "All of those who rely on the runtime. A parachain team that is using a pallet. A DApp that is using a pallet. These are people who care about the protocol (WASM), not the meta-protocol (client)."}, + + {"const": "Node Operator", + "title": "Node Operator", + "description": "Those who don't write any code and only run code."}, + + {"const": "Runtime User", + "title": "Runtime User", + "description": "Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain."} + ] + }, + "crate": { + "type": "object", + "description": "You have the option here to provide a hint about a crate that has changed to help with the publishing of crates.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "bump": { + "$ref": "#/$defs/semver_bump" + }, + "note": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "migration_db": { + "type": "object", + "description": "This property allows the documentation of database migrations.", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "name", + "description" + ] + }, + "migration_runtime": { + "type": "object", + "description": "This property allows the documentation of runtime migrations.", + "properties": { + "reference": { + "title": "Migration reference", + "description": "Reference to the runtime migration", + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "description" + ] + }, + "semver_bump": { + "description": "The type of bump to apply to the crate version according to Cargo SemVer: https://doc.rust-lang.org/cargo/reference/semver.html. Please check docs/RELEASE.md for more information.", + "oneOf": [ + { + "const": "major", + "title": "Major", + "description": "A bump to the leftmost non-zero digit of the version number." + }, + { + "const": "minor", + "title": "Minor", + "description": "A bump to the second leftmost non-zero digit of the version number." + }, + { + "const": "patch", + "title": "Patch", + "description": "A bump to the third leftmost non-zero digit of the version number." + } + ] + }, + "doc": { + "type": "object", + "description": "You have the the option to provide different description of your PR for different audiences.", + "additionalProperties": false, + "properties": { + "audience": { + "description": "The selected audience", + "$ref": "#/$defs/audience" + }, + "title": { + "type": "string", + "title": "Title for the audience", + "description": "Optional title override for the PR and for the current audience" + }, + "description": { + "title": "Description for the audience", + "description": "Description of the change", + "type": "string" + } + } + }, + "array_of_strings": { + "description": "An array of strings that can be empty", + "type": "array", + "items": { + "type": "string" + } + }, + "host_function": { + "type": "object", + "additionalProperties": false, + "title": "Host Functions", + "description": "List of host functions and their descriptions", + "properties": { + "name": { + "title": "Host function name", + "description": "Name or identifier to find the host function in the codebase", + "type": "string" + }, + "description": { + "title": "Host function description", + "description": "Short description of the host function", + "type": "string" + }, + "notes": { + "type": "string" + } + }, + "required": [ + "name", + "description" + ] + } + } + } 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/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh index 785cc7bd3291ccdaff1d80558e9f3d52418068c3..dedee8e641f8a88ef7af36fecc3672944c2f477c 100755 --- a/scripts/update-ui-tests.sh +++ b/scripts/update-ui-tests.sh @@ -34,7 +34,7 @@ export SKIP_WASM_BUILD=1 export TRYBUILD=overwrite # ./substrate -$RUSTUP_RUN cargo test -p sp-runtime-interface ui +$RUSTUP_RUN cargo test --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui $RUSTUP_RUN cargo test -p sp-api-test ui $RUSTUP_RUN cargo test -p frame-election-provider-solution-type ui -$RUSTUP_RUN cargo test -p frame-support-test ui +$RUSTUP_RUN cargo test -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml deleted file mode 100644 index d77f02c60603150ce2627ae5594d1fc96d1bfac8..0000000000000000000000000000000000000000 --- a/substrate/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "substrate" -description = "Next-generation framework for blockchain innovation" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.io" -repository.workspace = true -authors.workspace = true -edition.workspace = true -version = "1.0.0" - -# The dependencies are only needed for docs. -[dependencies] -aquamarine = "0.3.2" - -subkey = { path = "bin/utils/subkey" } -chain-spec-builder = { path = "bin/utils/chain-spec-builder" } - -sc-service = { path = "client/service" } -sc-cli = { path = "client/cli" } -sc-consensus-aura = { path = "client/consensus/aura" } -sc-consensus-babe = { path = "client/consensus/babe" } -sc-consensus-grandpa = { path = "client/consensus/grandpa" } -sc-consensus-beefy = { path = "client/consensus/beefy" } -sc-consensus-manual-seal = { path = "client/consensus/manual-seal" } -sc-consensus-pow = { path = "client/consensus/pow" } - -sp-runtime = { path = "primitives/runtime" } -frame-support = { path = "frame/support" } diff --git a/substrate/README.md b/substrate/README.md index f7afa7a894d88f7bf57c8c7f6fb870a3e60c1b73..f69612772627430099e27dc07385c908431cbcb5 100644 --- a/substrate/README.md +++ b/substrate/README.md @@ -3,7 +3,7 @@ [![GitHub license](https://img.shields.io/badge/license-GPL3%2FApache2-blue)](#LICENSE) [![GitLab Status](https://gitlab.parity.io/parity/mirrors/polkadot-sdk/badges/master/pipeline.svg)](https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/pipelines) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/CONTRIBUTING.md) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](docs/contributor/CONTRIBUTING.md) [![Stack Exchange](https://img.shields.io/badge/Substrate-Community%20&%20Support-24CC85?logo=stackexchange)](https://substrate.stackexchange.com/)

@@ -26,15 +26,13 @@ here](https://github.com/paritytech/polkadot-sdk/issues) for anything you suspec ## Contributions & Code of Conduct -Please follow the contributions guidelines as outlined in -[`docs/CONTRIBUTING.md`](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CONTRIBUTING.md). In all -communications and contributions, this project follows the [Contributor Covenant Code of -Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CODE_OF_CONDUCT.md). +Please follow the contributions guidelines as outlined in [`docs/contributor/CONTRIBUTING.md`](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md). +In all communications and contributions, this project follows the [Contributor Covenant Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). ## Security The security policy and procedures can be found in -[`docs/SECURITY.md`](https://github.com/paritytech/polkadot-sdk/blob/master/docs/SECURITY.md). +[`docs/contributor/SECURITY.md`](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md). ## License diff --git a/substrate/bin/node-template/.editorconfig b/substrate/bin/node-template/.editorconfig deleted file mode 100644 index 5adac74ca24b3422222b2cb886b2a50cd22b6d1b..0000000000000000000000000000000000000000 --- a/substrate/bin/node-template/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style=space -indent_size=2 -tab_width=2 -end_of_line=lf -charset=utf-8 -trim_trailing_whitespace=true -insert_final_newline = true - -[*.{rs,toml}] -indent_style=tab -indent_size=tab -tab_width=4 -max_line_length=100 diff --git a/substrate/bin/node-template/.envrc b/substrate/bin/node-template/.envrc deleted file mode 100644 index 3550a30f2de389e537ee40ca5e64a77dc185c79b..0000000000000000000000000000000000000000 --- a/substrate/bin/node-template/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml deleted file mode 100644 index 23840cce2229ba0b83057584fa6657f8424f5c8a..0000000000000000000000000000000000000000 --- a/substrate/bin/node-template/node/Cargo.toml +++ /dev/null @@ -1,88 +0,0 @@ -[package] -name = "node-template" -version = "4.0.0-dev" -description = "A fresh FRAME-based Substrate node, ready for hacking." -authors = ["Substrate DevHub "] -homepage = "https://substrate.io/" -edition.workspace = true -license = "MIT-0" -publish = false -repository = "https://github.com/substrate-developer-hub/substrate-node-template/" -build = "build.rs" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[[bin]] -name = "node-template" - -[dependencies] -clap = { version = "4.4.6", features = ["derive"] } -futures = { version = "0.3.21", features = ["thread-pool"]} - -sc-cli = { path = "../../../client/cli" } -sp-core = { path = "../../../primitives/core" } -sc-executor = { path = "../../../client/executor" } -sc-network = { path = "../../../client/network" } -sc-service = { path = "../../../client/service" } -sc-telemetry = { path = "../../../client/telemetry" } -sc-transaction-pool = { path = "../../../client/transaction-pool" } -sc-transaction-pool-api = { path = "../../../client/transaction-pool/api" } -sc-offchain = { path = "../../../client/offchain" } -sc-consensus-aura = { path = "../../../client/consensus/aura" } -sp-consensus-aura = { path = "../../../primitives/consensus/aura" } -sc-consensus = { path = "../../../client/consensus/common" } -sc-consensus-grandpa = { path = "../../../client/consensus/grandpa" } -sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } -sc-client-api = { path = "../../../client/api" } -sp-runtime = { path = "../../../primitives/runtime" } -sp-io = { path = "../../../primitives/io" } -sp-timestamp = { path = "../../../primitives/timestamp" } -sp-inherents = { path = "../../../primitives/inherents" } -sp-keyring = { path = "../../../primitives/keyring" } -frame-system = { path = "../../../frame/system" } -pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false} - -# These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.16.2", features = ["server"] } -sp-api = { path = "../../../primitives/api" } -sc-rpc-api = { path = "../../../client/rpc-api" } -sp-blockchain = { path = "../../../primitives/blockchain" } -sp-block-builder = { path = "../../../primitives/block-builder" } -sc-basic-authorship = { path = "../../../client/basic-authorship" } -substrate-frame-rpc-system = { path = "../../../utils/frame/rpc/system" } -pallet-transaction-payment-rpc = { path = "../../../frame/transaction-payment/rpc" } - -# These dependencies are used for runtime benchmarking -frame-benchmarking = { path = "../../../frame/benchmarking" } -frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli" } - -# Local Dependencies -node-template-runtime = { path = "../runtime" } - -# CLI-specific dependencies -try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true} - -[build-dependencies] -substrate-build-script-utils = { path = "../../../utils/build-script-utils" } - -[features] -default = [] -# Dependencies that are only required if runtime benchmarking should be build. -runtime-benchmarks = [ - "frame-benchmarking-cli/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "node-template-runtime/runtime-benchmarks", - "sc-service/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -# Enable features that allow the runtime to be tried and debugged. Name might be subject to change -# in the near future. -try-runtime = [ - "frame-system/try-runtime", - "node-template-runtime/try-runtime", - "pallet-transaction-payment/try-runtime", - "sp-runtime/try-runtime", - "try-runtime-cli/try-runtime", -] diff --git a/substrate/bin/node-template/node/src/chain_spec.rs b/substrate/bin/node-template/node/src/chain_spec.rs deleted file mode 100644 index 2cd2d0729302631699b895609538cb148fc41de4..0000000000000000000000000000000000000000 --- a/substrate/bin/node-template/node/src/chain_spec.rs +++ /dev/null @@ -1,158 +0,0 @@ -use node_template_runtime::{ - AccountId, AuraConfig, BalancesConfig, GrandpaConfig, RuntimeGenesisConfig, Signature, - SudoConfig, SystemConfig, WASM_BINARY, -}; -use sc_service::ChainType; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_consensus_grandpa::AuthorityId as GrandpaId; -use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; - -// The URL for the telemetry server. -// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; - -/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. -pub type ChainSpec = sc_service::GenericChainSpec; - -/// Generate a crypto pair from seed. -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -type AccountPublic = ::Signer; - -/// Generate an account ID from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Generate an Aura authority key. -pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { - (get_from_seed::(s), get_from_seed::(s)) -} - -pub fn development_config() -> Result { - let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; - - Ok(ChainSpec::from_genesis( - // Name - "Development", - // ID - "dev", - ChainType::Development, - move || { - testnet_genesis( - wasm_binary, - // Initial PoA authorities - vec![authority_keys_from_seed("Alice")], - // Sudo account - get_account_id_from_seed::("Alice"), - // Pre-funded accounts - 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"), - ], - true, - ) - }, - // Bootnodes - vec![], - // Telemetry - None, - // Protocol ID - None, - None, - // Properties - None, - // Extensions - None, - )) -} - -pub fn local_testnet_config() -> Result { - let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; - - Ok(ChainSpec::from_genesis( - // Name - "Local Testnet", - // ID - "local_testnet", - ChainType::Local, - move || { - testnet_genesis( - wasm_binary, - // Initial PoA authorities - vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], - // Sudo account - get_account_id_from_seed::("Alice"), - // Pre-funded accounts - 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"), - ], - true, - ) - }, - // Bootnodes - vec![], - // Telemetry - None, - // Protocol ID - None, - // Properties - None, - None, - // Extensions - None, - )) -} - -/// Configure initial storage state for FRAME modules. -fn testnet_genesis( - wasm_binary: &[u8], - initial_authorities: Vec<(AuraId, GrandpaId)>, - root_key: AccountId, - endowed_accounts: Vec, - _enable_println: bool, -) -> RuntimeGenesisConfig { - RuntimeGenesisConfig { - system: SystemConfig { - // Add Wasm runtime to storage. - code: wasm_binary.to_vec(), - ..Default::default() - }, - balances: BalancesConfig { - // Configure endowed accounts with initial balance of 1 << 60. - balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), - }, - aura: AuraConfig { - authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(), - }, - grandpa: GrandpaConfig { - authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), - ..Default::default() - }, - sudo: SudoConfig { - // Assign network admin rights. - key: Some(root_key), - }, - transaction_payment: Default::default(), - } -} diff --git a/substrate/bin/node-template/runtime/Cargo.toml b/substrate/bin/node-template/runtime/Cargo.toml deleted file mode 100644 index c6d751484237d20295179467474453d6e9afeebb..0000000000000000000000000000000000000000 --- a/substrate/bin/node-template/runtime/Cargo.toml +++ /dev/null @@ -1,120 +0,0 @@ -[package] -name = "node-template-runtime" -version = "4.0.0-dev" -description = "A fresh FRAME-based Substrate node, ready for hacking." -authors = ["Substrate DevHub "] -homepage = "https://substrate.io/" -edition.workspace = true -license = "MIT-0" -publish = false -repository = "https://github.com/substrate-developer-hub/substrate-node-template/" - -[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"] } - -pallet-aura = { path = "../../../frame/aura", default-features = false} -pallet-balances = { path = "../../../frame/balances", default-features = false} -frame-support = { path = "../../../frame/support", default-features = false} -pallet-grandpa = { path = "../../../frame/grandpa", default-features = false} -pallet-sudo = { path = "../../../frame/sudo", default-features = false} -frame-system = { path = "../../../frame/system", default-features = false} -frame-try-runtime = { path = "../../../frame/try-runtime", default-features = false, optional = true } -pallet-timestamp = { path = "../../../frame/timestamp", default-features = false} -pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false} -frame-executive = { path = "../../../frame/executive", default-features = false} -sp-api = { path = "../../../primitives/api", default-features = false} -sp-block-builder = { path = "../../../primitives/block-builder", default-features = false} -sp-consensus-aura = { path = "../../../primitives/consensus/aura", default-features = false} -sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa", default-features = false} -sp-core = { path = "../../../primitives/core", default-features = false} -sp-inherents = { path = "../../../primitives/inherents", default-features = false} -sp-offchain = { path = "../../../primitives/offchain", default-features = false} -sp-runtime = { path = "../../../primitives/runtime", default-features = false} -sp-session = { path = "../../../primitives/session", default-features = false} -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} -sp-genesis-builder = { default-features = false, path = "../../../primitives/genesis-builder" } - -# Used for the node template's RPCs -frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api", default-features = false} -pallet-transaction-payment-rpc-runtime-api = { path = "../../../frame/transaction-payment/rpc/runtime-api", default-features = false} - -# Used for runtime benchmarking -frame-benchmarking = { path = "../../../frame/benchmarking", default-features = false, optional = true } -frame-system-benchmarking = { path = "../../../frame/system/benchmarking", default-features = false, optional = true } - -# Local Dependencies -pallet-template = { path = "../pallets/template", default-features = false} - -[build-dependencies] -substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } - -[features] -default = [ "std" ] -std = [ - "codec/std", - "frame-benchmarking?/std", - "frame-executive/std", - "frame-support/std", - "frame-system-benchmarking?/std", - "frame-system-rpc-runtime-api/std", - "frame-system/std", - "frame-try-runtime?/std", - "pallet-aura/std", - "pallet-balances/std", - "pallet-grandpa/std", - "pallet-sudo/std", - "pallet-template/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "scale-info/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-consensus-grandpa/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", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-grandpa/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", - "pallet-template/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -try-runtime = [ - "frame-executive/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "frame-try-runtime/try-runtime", - "pallet-aura/try-runtime", - "pallet-balances/try-runtime", - "pallet-grandpa/try-runtime", - "pallet-sudo/try-runtime", - "pallet-template/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "sp-runtime/try-runtime", -] -experimental = [ "pallet-aura/experimental" ] diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index c111d345623d584c2748d7659bc034e1a66e6087..d5de1dbe6c44e16980d79a75c54cec8ae42a4a7f 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -9,20 +9,23 @@ 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.6", features = ["derive"] } -log = "0.4.17" +clap = { version = "4.5.3", features = ["derive"] } +log = { workspace = true, default-features = true } node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } kitchensink-runtime = { path = "../runtime" } sc-client-api = { path = "../../../client/api" } sp-runtime = { path = "../../../primitives/runtime" } sp-state-machine = { path = "../../../primitives/state-machine" } -serde = "1.0.188" -serde_json = "1.0.107" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" kvdb-rocksdb = "0.19.0" @@ -31,14 +34,14 @@ sp-core = { path = "../../../primitives/core" } sp-consensus = { path = "../../../primitives/consensus/common" } sc-basic-authorship = { path = "../../../client/basic-authorship" } sp-inherents = { path = "../../../primitives/inherents" } -sp-timestamp = { path = "../../../primitives/timestamp", default-features = false} +sp-timestamp = { path = "../../../primitives/timestamp", default-features = false } sp-tracing = { path = "../../../primitives/tracing" } hash-db = "0.16.0" tempfile = "3.1.0" fs_extra = "1" rand = { version = "0.8.5", features = ["small_rng"] } lazy_static = "1.4.0" -parity-db = "0.4.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 49dc39099be03b9470d712245c8d3642c3d8aaaa..8bddbbe04828f19ae437db60c8a6f5480c4e847f 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "node-cli" +name = "staging-node-cli" version = "3.0.0-dev" authors.workspace = true description = "Generic Substrate node implementation in Rust." @@ -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,17 +41,18 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies array-bytes = "6.1" -clap = { version = "4.4.6", features = ["derive"], optional = true } +clap = { version = "4.5.3", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } -serde = { version = "1.0.188", features = ["derive"] } -jsonrpsee = { version = "0.16.2", features = ["server"] } +serde = { features = ["derive"], workspace = true, default-features = true } +jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = "0.8" # primitives 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,10 +81,12 @@ 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} +sc-service = { path = "../../../client/service", default-features = false } sc-telemetry = { path = "../../../client/telemetry" } sc-executor = { path = "../../../client/executor" } sc-authority-discovery = { path = "../../../client/authority-discovery" } @@ -90,25 +97,26 @@ sc-storage-monitor = { path = "../../../client/storage-monitor" } sc-offchain = { path = "../../../client/offchain" } # frame dependencies +frame-benchmarking = { path = "../../../frame/benchmarking" } frame-system = { path = "../../../frame/system" } frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api" } pallet-assets = { path = "../../../frame/assets" } pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment" } pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment" } -pallet-im-online = { path = "../../../frame/im-online", default-features = false} +pallet-im-online = { path = "../../../frame/im-online", default-features = false } +pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment", default-features = false } # node-specific dependencies kitchensink-runtime = { path = "../runtime" } node-rpc = { path = "../rpc" } node-primitives = { path = "../primitives" } -node-executor = { path = "../executor" } # CLI-specific dependencies -sc-cli = { path = "../../../client/cli", optional = true} -frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true} -node-inspect = { path = "../inspect", optional = true} -try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true} -serde_json = "1.0.107" +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 = { workspace = true, default-features = true } [dev-dependencies] sc-keystore = { path = "../../../client/keystore" } @@ -120,36 +128,56 @@ sc-service-test = { path = "../../../client/service/test" } sc-block-builder = { path = "../../../client/block-builder" } sp-tracing = { path = "../../../primitives/tracing" } sp-blockchain = { path = "../../../primitives/blockchain" } +sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } futures = "0.3.21" tempfile = "3.1.0" assert_cmd = "2.0.2" nix = { version = "0.26.1", features = ["signal"] } -serde_json = "1.0" regex = "1.6.0" platforms = "3.0" soketto = "0.7.1" criterion = { version = "0.4.0", features = ["async_tokio"] } -tokio = { version = "1.22.0", features = ["macros", "time", "parking_lot"] } +tokio = { version = "1.22.0", features = ["macros", "parking_lot", "time"] } tokio-util = { version = "0.7.4", features = ["compat"] } wait-timeout = "0.2" substrate-rpc-client = { path = "../../../utils/frame/rpc/client" } pallet-timestamp = { path = "../../../frame/timestamp" } substrate-cli-test-utils = { path = "../../../test-utils/cli" } +wat = "1.0" +frame-support = { path = "../../../frame/support" } +node-testing = { path = "../testing" } +pallet-balances = { path = "../../../frame/balances" } +pallet-contracts = { path = "../../../frame/contracts" } +pallet-glutton = { path = "../../../frame/glutton" } +pallet-sudo = { path = "../../../frame/sudo" } +pallet-treasury = { path = "../../../frame/treasury" } +pallet-transaction-payment = { path = "../../../frame/transaction-payment" } +sp-application-crypto = { path = "../../../primitives/application-crypto" } +pallet-root-testing = { path = "../../../frame/root-testing" } +sp-consensus-babe = { path = "../../../primitives/consensus/babe" } +sp-externalities = { path = "../../../primitives/externalities" } +sp-keyring = { path = "../../../primitives/keyring" } +sp-runtime = { path = "../../../primitives/runtime" } +serde_json = { workspace = true, default-features = true } +scale-info = { version = "2.10.0", features = ["derive", "serde"] } +sp-trie = { path = "../../../primitives/trie" } +sp-state-machine = { path = "../../../primitives/state-machine" } + [build-dependencies] -clap = { version = "4.4.6", optional = true } +clap = { version = "4.5.3", optional = true } clap_complete = { version = "4.0.2", optional = true } -node-inspect = { path = "../inspect", optional = true} -frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true} -substrate-build-script-utils = { path = "../../../utils/build-script-utils", optional = true} -substrate-frame-cli = { path = "../../../utils/frame/frame-utilities-cli", optional = true} -try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true} +node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } +frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } +substrate-build-script-utils = { path = "../../../utils/build-script-utils", optional = true } +substrate-frame-cli = { path = "../../../utils/frame/frame-utilities-cli", optional = true } +try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true } sc-cli = { path = "../../../client/cli", optional = true } pallet-balances = { path = "../../../frame/balances" } sc-storage-monitor = { path = "../../../client/storage-monitor" } [features] -default = [ "cli" ] +default = ["cli"] cli = [ "clap", "clap_complete", @@ -163,13 +191,21 @@ cli = [ ] runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "kitchensink-runtime/runtime-benchmarks", + "node-inspect?/runtime-benchmarks", "pallet-asset-tx-payment/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-contracts/runtime-benchmarks", + "pallet-glutton/runtime-benchmarks", "pallet-im-online/runtime-benchmarks", + "pallet-skip-feeless-payment/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", "sc-client-db/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -177,14 +213,22 @@ runtime-benchmarks = [ # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. try-runtime = [ + "frame-support/try-runtime", "frame-system/try-runtime", "kitchensink-runtime/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-assets/try-runtime", "pallet-balances/try-runtime", + "pallet-contracts/try-runtime", + "pallet-glutton/try-runtime", "pallet-im-online/try-runtime", + "pallet-root-testing/try-runtime", + "pallet-skip-feeless-payment/try-runtime", + "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-treasury/try-runtime", "sp-runtime/try-runtime", "substrate-cli-test-utils/try-runtime", "try-runtime-cli/try-runtime", @@ -197,3 +241,7 @@ harness = false [[bench]] name = "block_production" harness = false + +[[bench]] +name = "executor" +harness = false diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index 246de8f3e925df6e3d91994af1467975a1669f4c..23a62cc0bd243ebd1c8ade712b3ded2361f9e4c0 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -20,7 +20,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughpu use kitchensink_runtime::{constants::currency::*, BalancesCall}; use node_cli::service::{create_extrinsic, FullClient}; -use sc_block_builder::{BlockBuilderProvider, BuiltBlock, RecordProof}; +use sc_block_builder::{BlockBuilderBuilder, BuiltBlock}; use sc_consensus::{ block_import::{BlockImportParams, ForkChoiceStrategy}, BlockImport, StateAction, @@ -28,7 +28,7 @@ use sc_consensus::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, + PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, Configuration, Role, }; @@ -39,6 +39,7 @@ use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidityError}, AccountId32, MultiAddress, OpaqueExtrinsic, }; +use staging_node_cli as node_cli; use tokio::runtime::Handle; fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { @@ -82,6 +83,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_id_provider: Default::default(), rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, + rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -126,7 +130,11 @@ fn prepare_benchmark(client: &FullClient) -> (usize, Vec) { let mut max_transfer_count = 0; let mut extrinsics = Vec::new(); - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap(); // Every block needs one timestamp extrinsic. let extrinsic_set_time = extrinsic_set_time(1 + MINIMUM_PERIOD_FOR_BLOCKS); @@ -173,7 +181,11 @@ fn block_production(c: &mut Criterion) { // Buliding the very first block is around ~30x slower than any subsequent one, // so let's make sure it's built and imported before we benchmark anything. - let mut block_builder = client.new_block(Default::default()).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap(); block_builder.push(extrinsic_set_time(1)).unwrap(); import_block(client, block_builder.build().unwrap()); @@ -185,14 +197,19 @@ fn block_production(c: &mut Criterion) { group.sample_size(10); group.throughput(Throughput::Elements(max_transfer_count as u64)); - let best_hash = client.chain_info().best_hash; + let chain = client.chain_info(); + let best_hash = chain.best_hash; + let best_number = chain.best_number; group.bench_function(format!("{} transfers (no proof)", max_transfer_count), |b| { b.iter_batched( || extrinsics.clone(), |extrinsics| { - let mut block_builder = - client.new_block_at(best_hash, Default::default(), RecordProof::No).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(best_hash) + .with_parent_block_number(best_number) + .build() + .unwrap(); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } @@ -206,8 +223,11 @@ fn block_production(c: &mut Criterion) { b.iter_batched( || extrinsics.clone(), |extrinsics| { - let mut block_builder = - client.new_block_at(best_hash, Default::default(), RecordProof::Yes).unwrap(); + let mut block_builder = BlockBuilderBuilder::new(client) + .on_parent_block(best_hash) + .with_parent_block_number(best_number) + .build() + .unwrap(); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } diff --git a/substrate/bin/node/executor/benches/bench.rs b/substrate/bin/node/cli/benches/executor.rs similarity index 69% rename from substrate/bin/node/executor/benches/bench.rs rename to substrate/bin/node/cli/benches/executor.rs index 1c9c002492cf5c2bffef3e017fb6a7a4cf5f05c7..a326e1a79ea347f169e372581d07dc4f43848e24 100644 --- a/substrate/bin/node/executor/benches/bench.rs +++ b/substrate/bin/node/cli/benches/executor.rs @@ -22,19 +22,16 @@ use kitchensink_runtime::{ constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, RuntimeCall, RuntimeGenesisConfig, UncheckedExtrinsic, }; -use node_executor::ExecutorDispatch; use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; -use sc_executor::{ - Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod, WasmExecutor, - WasmtimeInstantiationStrategy, -}; +use sc_executor::{Externalities, RuntimeVersionOf}; use sp_core::{ storage::well_known_keys, traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; +use staging_node_cli::service::RuntimeExecutor; criterion_group!(benches, bench_execute_block); criterion_main!(benches); @@ -57,12 +54,6 @@ const HEAP_PAGES: u64 = 20; type TestExternalities = CoreTestExternalities; -#[derive(Debug)] -enum ExecutionMethod { - Native, - Wasm(WasmExecutionMethod), -} - fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH) } @@ -79,7 +70,7 @@ fn new_test_ext(genesis_config: &RuntimeGenesisConfig) -> TestExternalities( - executor: &NativeElseWasmExecutor, + executor: &RuntimeExecutor, ext: &mut E, number: BlockNumber, parent_hash: Hash, @@ -112,14 +103,7 @@ fn construct_block( // execute the block to get the real header. executor - .call( - ext, - &runtime_code, - "Core_initialize_block", - &header.encode(), - true, - CallContext::Offchain, - ) + .call(ext, &runtime_code, "Core_initialize_block", &header.encode(), CallContext::Offchain) .0 .unwrap(); @@ -130,7 +114,6 @@ fn construct_block( &runtime_code, "BlockBuilder_apply_extrinsic", &i.encode(), - true, CallContext::Offchain, ) .0 @@ -144,7 +127,6 @@ fn construct_block( &runtime_code, "BlockBuilder_finalize_block", &[0u8; 0], - true, CallContext::Offchain, ) .0 @@ -158,7 +140,7 @@ fn construct_block( fn test_blocks( genesis_config: &RuntimeGenesisConfig, - executor: &NativeElseWasmExecutor, + executor: &RuntimeExecutor, ) -> Vec<(Vec, Hash)> { let mut test_ext = new_test_ext(genesis_config); let mut block1_extrinsics = vec![CheckedExtrinsic { @@ -180,56 +162,42 @@ fn test_blocks( fn bench_execute_block(c: &mut Criterion) { let mut group = c.benchmark_group("execute blocks"); - let execution_methods = vec![ - ExecutionMethod::Native, - ExecutionMethod::Wasm(WasmExecutionMethod::Compiled { - instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - }), - ]; - - for strategy in execution_methods { - group.bench_function(format!("{:?}", strategy), |b| { - let genesis_config = node_testing::genesis::config(Some(compact_code_unwrap())); - let use_native = match strategy { - ExecutionMethod::Native => true, - ExecutionMethod::Wasm(..) => false, - }; - - let executor = - NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()); - let runtime_code = RuntimeCode { - code_fetcher: &sp_core::traits::WrappedRuntimeCode(compact_code_unwrap().into()), - hash: vec![1, 2, 3], - heap_pages: None, - }; - - // Get the runtime version to initialize the runtimes cache. - { - let mut test_ext = new_test_ext(&genesis_config); - executor.runtime_version(&mut test_ext.ext(), &runtime_code).unwrap(); - } - - let blocks = test_blocks(&genesis_config, &executor); - - b.iter_batched_ref( - || new_test_ext(&genesis_config), - |test_ext| { - for block in blocks.iter() { - executor - .call( - &mut test_ext.ext(), - &runtime_code, - "Core_execute_block", - &block.0, - use_native, - CallContext::Offchain, - ) - .0 - .unwrap(); - } - }, - BatchSize::LargeInput, - ); - }); - } + + group.bench_function("wasm", |b| { + let genesis_config = node_testing::genesis::config(); + + let executor = RuntimeExecutor::builder().build(); + let runtime_code = RuntimeCode { + code_fetcher: &sp_core::traits::WrappedRuntimeCode(compact_code_unwrap().into()), + hash: vec![1, 2, 3], + heap_pages: None, + }; + + // Get the runtime version to initialize the runtimes cache. + { + let mut test_ext = new_test_ext(&genesis_config); + executor.runtime_version(&mut test_ext.ext(), &runtime_code).unwrap(); + } + + let blocks = test_blocks(&genesis_config, &executor); + + b.iter_batched_ref( + || new_test_ext(&genesis_config), + |test_ext| { + for block in blocks.iter() { + executor + .call( + &mut test_ext.ext(), + &runtime_code, + "Core_execute_block", + &block.0, + CallContext::Offchain, + ) + .0 + .unwrap(); + } + }, + BatchSize::LargeInput, + ); + }); } diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 47f890574151d8f8023f901cacd6832d0dea69e5..de4eef1944d41bc6f37d06819ad70ca0a6dad109 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -26,7 +26,7 @@ use node_primitives::AccountId; use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, TransactionPoolOptions, + PruningMode, RpcBatchRequestConfig, TransactionPoolOptions, }, BasePath, Configuration, Role, }; @@ -35,6 +35,7 @@ use sc_transaction_pool_api::{TransactionPool as _, TransactionSource, Transacti use sp_core::{crypto::Pair, sr25519}; use sp_keyring::Sr25519Keyring; use sp_runtime::OpaqueExtrinsic; +use staging_node_cli as node_cli; use tokio::runtime::Handle; fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { @@ -54,7 +55,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { impl_name: "BenchmarkImpl".into(), impl_version: "1.0".into(), role: Role::Authority, - tokio_handle, + tokio_handle: tokio_handle.clone(), transaction_pool: TransactionPoolOptions { ready: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, future: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, @@ -78,6 +79,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_id_provider: Default::default(), rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, + rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -96,7 +100,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { wasm_runtime_overrides: None, }; - node_cli::service::new_full_base(config, None, false, |_, _| ()).expect("Creates node") + tokio_handle.block_on(async move { + node_cli::service::new_full_base(config, None, false, |_, _| ()).expect("Creates node") + }) } fn create_accounts(num: usize) -> Vec { diff --git a/substrate/bin/node/cli/bin/main.rs b/substrate/bin/node/cli/bin/main.rs index 4b434a3e6dad53f6bbbb47eed4702d57bf1ed405..ccc7d7b6b112558832549bf7e5435f68ac0fe944 100644 --- a/substrate/bin/node/cli/bin/main.rs +++ b/substrate/bin/node/cli/bin/main.rs @@ -20,6 +20,8 @@ #![warn(missing_docs)] +use staging_node_cli as node_cli; + fn main() -> sc_cli::Result<()> { node_cli::run() } diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 52b480925aa941895397538081f25c0680d9bc7e..b6e8fb8a14edfa22a4d221515185b2748bc733c7 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -18,12 +18,10 @@ //! 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, BabeConfig, BalancesConfig, Block, CouncilConfig, - DemocracyConfig, ElectionsConfig, ImOnlineConfig, IndicesConfig, MaxNominations, - NominationPoolsConfig, SessionConfig, SessionKeys, SocietyConfig, StakerStatus, StakingConfig, - SudoConfig, SystemConfig, TechnicalCommitteeConfig, + constants::currency::*, wasm_binary_unwrap, Block, MaxNominations, SessionKeys, StakerStatus, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -45,6 +43,8 @@ pub use node_primitives::{AccountId, Balance, Signature}; type AccountPublic = ::Signer; const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; +const ENDOWMENT: Balance = 10_000_000 * DOLLARS; +const STASH: Balance = ENDOWMENT / 1000; /// Node `ChainSpec` extensions. /// @@ -74,19 +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 staging_testnet_config_genesis() -> RuntimeGenesisConfig { +fn configure_accounts_for_staging_testnet() -> ( + 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, @@ -96,6 +114,7 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { ImOnlineId, AuthorityDiscoveryId, MixnetId, + BeefyId, )> = vec![ ( // 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy @@ -117,6 +136,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106") .unchecked_into(), + // 5DMLFcDdLLQbw696YfHaWBpQR99HwR456ycSCfr6L7KXGYK8 + array_bytes::hex2array_unchecked("035560fafa241739869360aa4b32bc98953172ceb41a19c6cc1a27962fb3d1ecec") + .unchecked_into(), ), ( // 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2 @@ -138,6 +160,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e") .unchecked_into(), + // 5FYk11kNtB4178wLKJ2RNoUzzcjgRUciFe3SJDVZXhqX4dzG + array_bytes::hex2array_unchecked("02da1ab255ed888ee3e19b73d335fc13160b3eb10456c2d17c6a8ea7de403d2445") + .unchecked_into(), ), ( // 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp @@ -159,6 +184,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a") .unchecked_into(), + // 5GQx4FToRBPqfani6o7owFJE1UstiviqbPP7HPWyvtXWWukn + array_bytes::hex2array_unchecked("036a818b3f59579c5fbbe4fede64f49dbf090ba883eb2a175d5ca90e5adb5f0b3e") + .unchecked_into(), ), ( // 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9 @@ -180,6 +208,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378") .unchecked_into(), + // 5FCu2pY928VVHPgnNVJssvxFJZECyNe1CyH3WTG79Wisx58B + array_bytes::hex2array_unchecked("020ce02b963548f9f8ade8765f7a4a06638c17819c78422a1cc35b647873583eef") + .unchecked_into(), ), ]; @@ -190,28 +221,27 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { ); let endowed_accounts: Vec = vec![root_key.clone()]; + (initial_authorities, root_key, endowed_accounts) +} +fn staging_testnet_config_genesis() -> serde_json::Value { + let (initial_authorities, root_key, endowed_accounts) = + configure_accounts_for_staging_testnet(); testnet_genesis(initial_authorities, vec![], root_key, Some(endowed_accounts)) } /// Staging testnet config. pub fn staging_testnet_config() -> ChainSpec { - let boot_nodes = vec![]; - ChainSpec::from_genesis( - "Staging Testnet", - "staging_testnet", - ChainType::Live, - staging_testnet_config_genesis, - boot_nodes, - Some( + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("Staging Testnet") + .with_id("staging_testnet") + .with_chain_type(ChainType::Live) + .with_genesis_config_patch(staging_testnet_config_genesis()) + .with_telemetry_endpoints( TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)]) .expect("Staging telemetry url is valid; qed"), - ), - None, - None, - None, - Default::default(), - ) + ) + .build() } /// Helper function to generate a crypto pair from seed. @@ -232,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), @@ -241,11 +272,11 @@ pub fn authority_keys_from_seed( get_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), + get_from_seed::(seed), ) } -/// Helper function to create RuntimeGenesisConfig for testing. -pub fn testnet_genesis( +fn configure_accounts( initial_authorities: Vec<( AccountId, AccountId, @@ -254,11 +285,26 @@ pub fn testnet_genesis( ImOnlineId, AuthorityDiscoveryId, MixnetId, + BeefyId, )>, initial_nominators: Vec, - root_key: AccountId, endowed_accounts: Option>, -) -> RuntimeGenesisConfig { + stash: Balance, +) -> ( + Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + MixnetId, + BeefyId, + )>, + Vec, + usize, + Vec<(AccountId, AccountId, Balance, StakerStatus)>, +) { let mut endowed_accounts: Vec = endowed_accounts.unwrap_or_else(|| { vec![ get_account_id_from_seed::("Alice"), @@ -290,7 +336,7 @@ pub fn testnet_genesis( let mut rng = rand::thread_rng(); let stakers = initial_authorities .iter() - .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::Validator)) + .map(|x| (x.0.clone(), x.0.clone(), stash, StakerStatus::Validator)) .chain(initial_nominators.iter().map(|x| { use rand::{seq::SliceRandom, Rng}; let limit = (MaxNominations::get() as usize).min(initial_authorities.len()); @@ -301,23 +347,40 @@ pub fn testnet_genesis( .into_iter() .map(|choice| choice.0.clone()) .collect::>(); - (x.clone(), x.clone(), STASH, StakerStatus::Nominator(nominations)) + (x.clone(), x.clone(), stash, StakerStatus::Nominator(nominations)) })) .collect::>(); let num_endowed_accounts = endowed_accounts.len(); - const ENDOWMENT: Balance = 10_000_000 * DOLLARS; - const STASH: Balance = ENDOWMENT / 1000; + (initial_authorities, endowed_accounts, num_endowed_accounts, stakers) +} + +/// Helper function to create RuntimeGenesisConfig json patch for testing. +pub fn testnet_genesis( + initial_authorities: Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + MixnetId, + BeefyId, + )>, + initial_nominators: Vec, + root_key: AccountId, + endowed_accounts: Option>, +) -> serde_json::Value { + let (initial_authorities, endowed_accounts, num_endowed_accounts, stakers) = + configure_accounts(initial_authorities, initial_nominators, endowed_accounts, STASH); - RuntimeGenesisConfig { - system: SystemConfig { code: wasm_binary_unwrap().to_vec(), ..Default::default() }, - balances: BalancesConfig { - balances: endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect(), + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect::>(), }, - indices: IndicesConfig { indices: vec![] }, - session: SessionConfig { - keys: initial_authorities + "session": { + "keys": initial_authorities .iter() .map(|x| { ( @@ -329,72 +392,51 @@ pub fn testnet_genesis( x.4.clone(), x.5.clone(), x.6.clone(), + x.7.clone(), ), ) }) .collect::>(), }, - staking: StakingConfig { - validator_count: initial_authorities.len() as u32, - minimum_validator_count: initial_authorities.len() as u32, - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), - slash_reward_fraction: Perbill::from_percent(10), - stakers, - ..Default::default() + "staking": { + "validatorCount": initial_authorities.len() as u32, + "minimumValidatorCount": initial_authorities.len() as u32, + "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + "slashRewardFraction": Perbill::from_percent(10), + "stakers": stakers.clone(), }, - democracy: DemocracyConfig::default(), - elections: ElectionsConfig { - members: endowed_accounts + "elections": { + "members": endowed_accounts .iter() .take((num_endowed_accounts + 1) / 2) .cloned() .map(|member| (member, STASH)) - .collect(), + .collect::>(), }, - council: CouncilConfig::default(), - technical_committee: TechnicalCommitteeConfig { - members: endowed_accounts + "technicalCommittee": { + "members": endowed_accounts .iter() .take((num_endowed_accounts + 1) / 2) .cloned() - .collect(), - phantom: Default::default(), + .collect::>(), }, - sudo: SudoConfig { key: Some(root_key) }, - babe: BabeConfig { - epoch_config: Some(kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() + "sudo": { "key": Some(root_key.clone()) }, + "babe": { + "epochConfig": Some(kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG), }, - im_online: ImOnlineConfig { keys: vec![] }, - authority_discovery: Default::default(), - grandpa: Default::default(), - technical_membership: Default::default(), - treasury: Default::default(), - society: SocietyConfig { pot: 0 }, - vesting: Default::default(), - assets: pallet_assets::GenesisConfig { + "society": { "pot": 0 }, + "assets": { // This asset is used by the NIS pallet as counterpart currency. - assets: vec![(9, get_account_id_from_seed::("Alice"), true, 1)], - ..Default::default() + "assets": vec![(9, get_account_id_from_seed::("Alice"), true, 1)], }, - pool_assets: Default::default(), - transaction_storage: Default::default(), - transaction_payment: Default::default(), - alliance: Default::default(), - safe_mode: Default::default(), - tx_pause: Default::default(), - alliance_motion: Default::default(), - nomination_pools: NominationPoolsConfig { - min_create_bond: 10 * DOLLARS, - min_join_bond: 1 * DOLLARS, - ..Default::default() + "nominationPools": { + "minCreateBond": 10 * DOLLARS, + "minJoinBond": 1 * DOLLARS, }, - glutton: Default::default(), - mixnet: Default::default(), - } + }) } -fn development_config_genesis() -> RuntimeGenesisConfig { +fn development_config_genesis_json() -> serde_json::Value { testnet_genesis( vec![authority_keys_from_seed("Alice")], vec![], @@ -405,21 +447,15 @@ fn development_config_genesis() -> RuntimeGenesisConfig { /// Development config (single validator Alice). pub fn development_config() -> ChainSpec { - ChainSpec::from_genesis( - "Development", - "dev", - ChainType::Development, - development_config_genesis, - vec![], - None, - None, - None, - None, - Default::default(), - ) + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("Development") + .with_id("dev") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(development_config_genesis_json()) + .build() } -fn local_testnet_genesis() -> RuntimeGenesisConfig { +fn local_testnet_genesis() -> serde_json::Value { testnet_genesis( vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], vec![], @@ -430,18 +466,12 @@ fn local_testnet_genesis() -> RuntimeGenesisConfig { /// Local testnet config (multivalidator Alice + Bob). pub fn local_testnet_config() -> ChainSpec { - ChainSpec::from_genesis( - "Local Testnet", - "local_testnet", - ChainType::Local, - local_testnet_genesis, - vec![], - None, - None, - None, - None, - Default::default(), - ) + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("Local Testnet") + .with_id("local_testnet") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(local_testnet_genesis()) + .build() } #[cfg(test)] @@ -451,45 +481,29 @@ pub(crate) mod tests { use sc_service_test; use sp_runtime::BuildStorage; - fn local_testnet_genesis_instant_single() -> RuntimeGenesisConfig { - testnet_genesis( - vec![authority_keys_from_seed("Alice")], - vec![], - get_account_id_from_seed::("Alice"), - None, - ) - } - /// Local testnet config (single validator - Alice). pub fn integration_test_config_with_single_authority() -> ChainSpec { - ChainSpec::from_genesis( - "Integration Test", - "test", - ChainType::Development, - local_testnet_genesis_instant_single, - vec![], - None, - None, - None, - None, - Default::default(), - ) + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("Integration Test") + .with_id("test") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(testnet_genesis( + vec![authority_keys_from_seed("Alice")], + vec![], + get_account_id_from_seed::("Alice"), + None, + )) + .build() } /// Local testnet config (multivalidator Alice + Bob). pub fn integration_test_config_with_two_authorities() -> ChainSpec { - ChainSpec::from_genesis( - "Integration Test", - "test", - ChainType::Development, - local_testnet_genesis, - vec![], - None, - None, - None, - None, - Default::default(), - ) + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("Integration Test") + .with_id("test") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(local_testnet_genesis()) + .build() } #[test] diff --git a/substrate/bin/node/cli/src/cli.rs b/substrate/bin/node/cli/src/cli.rs index f3c0435fd32dcaa71f9f94b78dcae01ff07c8b81..3345afae4fd2b181f147fd6fb6d5597d36db7885 100644 --- a/substrate/bin/node/cli/src/cli.rs +++ b/substrate/bin/node/cli/src/cli.rs @@ -63,7 +63,7 @@ pub enum Subcommand { /// Try-runtime has migrated to a standalone CLI /// (). The subcommand exists as a stub and - /// deprecation notice. It will be removed entirely some time after Janurary 2024. + /// deprecation notice. It will be removed entirely some time after January 2024. TryRuntime, /// Key management cli utilities diff --git a/substrate/bin/node/cli/src/command.rs b/substrate/bin/node/cli/src/command.rs index 16d0415ff2637fe4f613cb302bec81a7966224e8..9645173202866a7334ab3eaacd948132d9b3c9d7 100644 --- a/substrate/bin/node/cli/src/command.rs +++ b/substrate/bin/node/cli/src/command.rs @@ -24,11 +24,11 @@ use crate::{ }; use frame_benchmarking_cli::*; use kitchensink_runtime::{ExistentialDeposit, RuntimeApi}; -use node_executor::ExecutorDispatch; use node_primitives::Block; use sc_cli::{Result, SubstrateCli}; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::HashingFor; use std::sync::Arc; @@ -89,7 +89,7 @@ pub fn run() -> Result<()> { Some(Subcommand::Inspect(cmd)) => { let runner = cli.create_runner(cmd)?; - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::(config)) }, Some(Subcommand::Benchmark(cmd)) => { let runner = cli.create_runner(cmd)?; @@ -107,7 +107,7 @@ pub fn run() -> Result<()> { ) } - cmd.run::(config) + cmd.run::, sp_statement_store::runtime_api::HostFunctions>(config) }, BenchmarkCmd::Block(cmd) => { // ensure that we keep the task manager alive diff --git a/substrate/bin/node/cli/src/lib.rs b/substrate/bin/node/cli/src/lib.rs index 2fe238ef316e60a7544b0f828e53b228503df7e6..0ff544932a9a144e82cf9e147c1d0b8718022c47 100644 --- a/substrate/bin/node/cli/src/lib.rs +++ b/substrate/bin/node/cli/src/lib.rs @@ -30,16 +30,14 @@ #![warn(missing_docs)] -pub mod chain_spec; - -#[macro_use] -pub mod service; #[cfg(feature = "cli")] mod benchmarking; +pub mod chain_spec; #[cfg(feature = "cli")] mod cli; #[cfg(feature = "cli")] mod command; +pub mod service; #[cfg(feature = "cli")] pub use cli::*; diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5a85f4cde0ae0ac22c4edecec32691d9b781e877..8f2aba6b44cd0a980771ec7d84eb383421551a6b 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -26,13 +26,11 @@ use frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE; use frame_system_rpc_runtime_api::AccountNonceApi; use futures::prelude::*; use kitchensink_runtime::RuntimeApi; -use node_executor::ExecutorDispatch; use node_primitives::Block; use sc_client_api::{Backend, BlockBackend}; use sc_consensus_babe::{self, SlotProportion}; -use sc_executor::NativeElseWasmExecutor; use sc_network::{event::Event, NetworkEventStream, NetworkService}; -use sc_network_sync::{warp::WarpSyncParams, SyncingService}; +use sc_network_sync::{strategy::warp::WarpSyncParams, SyncingService}; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; use sc_statement_store::Store as StatementStore; use sc_telemetry::{Telemetry, TelemetryWorker}; @@ -40,15 +38,33 @@ use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::ProvideRuntimeApi; use sp_core::crypto::Pair; use sp_runtime::{generic, traits::Block as BlockT, SaturatedConversion}; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; + +/// Host functions required for kitchensink runtime and Substrate node. +#[cfg(not(feature = "runtime-benchmarks"))] +pub type HostFunctions = + (sp_io::SubstrateHostFunctions, sp_statement_store::runtime_api::HostFunctions); + +/// Host functions required for kitchensink runtime and Substrate node. +#[cfg(feature = "runtime-benchmarks")] +pub type HostFunctions = ( + sp_io::SubstrateHostFunctions, + sp_statement_store::runtime_api::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); + +/// A specialized `WasmExecutor` intended to use accross substrate node. It provides all required +/// HostFunctions. +pub type RuntimeExecutor = sc_executor::WasmExecutor; /// The full client type definition. -pub type FullClient = - sc_service::TFullClient>; +pub type FullClient = sc_service::TFullClient; 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; @@ -91,21 +107,24 @@ pub fn create_extrinsic( .map(|c| c / 2) .unwrap_or(2) as u64; let tip = 0; - let extra: kitchensink_runtime::SignedExtra = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(generic::Era::mortal( - period, - best_block.saturated_into(), - )), - frame_system::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from( - tip, None, - ), - ); + let extra: kitchensink_runtime::SignedExtra = + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_skip_feeless_payment::SkipCheckIfFeeless::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::< + kitchensink_runtime::Runtime, + >::from(tip, None), + ), + ); let raw_payload = kitchensink_runtime::SignedPayload::from_raw( function.clone(), @@ -148,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, @@ -171,7 +195,7 @@ pub fn new_partial( }) .transpose()?; - let executor = sc_service::new_native_or_wasm_executor(&config); + let executor = sc_service::new_wasm_executor(&config); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( @@ -205,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(), )?; @@ -236,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, @@ -251,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(); @@ -271,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) }; @@ -341,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); @@ -367,28 +423,46 @@ pub fn new_full_base( let shared_voter_state = rpc_setup; let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht; let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); - let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); let grandpa_protocol_name = grandpa::protocol_standard_name(&genesis_hash, &config.chain_spec); - net_config.add_notification_protocol(grandpa::grandpa_peers_set_config( - grandpa_protocol_name.clone(), - )); + let (grandpa_protocol_config, grandpa_notification_service) = + 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 statement_handler_proto = sc_network_statement::StatementHandlerPrototype::new( - genesis_hash, - config.chain_spec.fork_id(), - ); - net_config.add_notification_protocol(statement_handler_proto.set_config()); + 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, + config.chain_spec.fork_id(), + ); + net_config.add_notification_protocol(statement_config); let mixnet_protocol_name = sc_mixnet::protocol_name(genesis_hash.as_ref(), config.chain_spec.fork_id()); - if let Some(mixnet_config) = &mixnet_config { - net_config.add_notification_protocol(sc_mixnet::peers_set_config( - mixnet_protocol_name.clone(), - mixnet_config, - )); - } + let mixnet_notification_service = mixnet_config.as_ref().map(|mixnet_config| { + let (config, notification_service) = + sc_mixnet::peers_set_config(mixnet_protocol_name.clone(), mixnet_config); + net_config.add_notification_protocol(config); + notification_service + }); let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new( backend.clone(), @@ -419,19 +493,12 @@ pub fn new_full_base( mixnet_protocol_name, transaction_pool.clone(), Some(keystore_container.keystore()), + mixnet_notification_service + .expect("`NotificationService` exists since mixnet was enabled; qed"), ); 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(), @@ -449,10 +516,14 @@ pub fn new_full_base( if let Some(hwbench) = hwbench { sc_sysinfo::print_hwbench(&hwbench); - if !SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) && role.is_authority() { - log::warn!( - "⚠️ The hardware does not meet the minimal requirements for role 'Authority'." - ); + match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) { + Err(err) if role.is_authority() => { + log::warn!( + "⚠️ The hardware does not meet the minimal requirements {} for role 'Authority'.", + err + ); + }, + _ => {}, } if let Some(ref mut telemetry) = telemetry { @@ -465,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); @@ -559,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), @@ -583,6 +695,7 @@ pub fn new_full_base( link: grandpa_link, network: network.clone(), sync: Arc::new(sync_service.clone()), + notification_service: grandpa_notification_service, telemetry: telemetry.as_ref().map(|x| x.handle()), voting_rule: grandpa::VotingRulesBuilder::default().build(), prometheus_registry: prometheus_registry.clone(), @@ -656,16 +769,18 @@ pub fn new_full_base( /// Builds a new service for a full client. pub fn new_full(config: Configuration, cli: Cli) -> Result { let mixnet_config = cli.mixnet_params.config(config.role.is_authority()); - let database_source = config.database.clone(); + let database_path = config.database.path().map(Path::to_path_buf); let task_manager = new_full_base(config, mixnet_config, cli.no_hardware_benchmarks, |_, _| ()) .map(|NewFullBase { task_manager, .. }| task_manager)?; - sc_storage_monitor::StorageMonitorService::try_spawn( - cli.storage_monitor, - database_source, - &task_manager.spawn_essential_handle(), - ) - .map_err(|e| ServiceError::Application(e.into()))?; + if let Some(database_path) = database_path { + sc_storage_monitor::StorageMonitorService::try_spawn( + cli.storage_monitor, + database_path, + &task_manager.spawn_essential_handle(), + ) + .map_err(|e| ServiceError::Application(e.into()))?; + } Ok(task_manager) } @@ -875,8 +990,9 @@ mod tests { let check_era = frame_system::CheckEra::from(Era::Immortal); let check_nonce = frame_system::CheckNonce::from(index); let check_weight = frame_system::CheckWeight::new(); - let tx_payment = - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None); + let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None), + ); let extra = ( check_non_zero_sender, check_spec_version, diff --git a/substrate/bin/node/executor/tests/basic.rs b/substrate/bin/node/cli/tests/basic.rs similarity index 92% rename from substrate/bin/node/executor/tests/basic.rs rename to substrate/bin/node/cli/tests/basic.rs index a2f46e9fdbe997cd71e2817f95f894bec74002de..525ab2e39c1287edcf235fe3ac6381e3f0fc8e10 100644 --- a/substrate/bin/node/executor/tests/basic.rs +++ b/substrate/bin/node/cli/tests/basic.rs @@ -154,7 +154,7 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { // session change => consensus authorities change => authorities change digest item appears let digest = Header::decode(&mut &block2.0[..]).unwrap().digest; - assert_eq!(digest.logs().len(), 1 /* Just babe slot */); + assert_eq!(digest.logs().len(), 2 /* Just babe and BEEFY slots */); (block1, block2) } @@ -193,11 +193,9 @@ fn panic_execution_with_foreign_code_gives_error() { t.insert(>::hashed_key().to_vec(), 69_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = - executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); - let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true) + let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())) .0 .unwrap(); let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap(); @@ -219,11 +217,9 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() { t.insert(>::hashed_key().to_vec(), 69u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = - executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); - let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true) + let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())) .0 .unwrap(); let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap(); @@ -256,14 +252,12 @@ fn successful_execution_with_native_equivalent_code_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = - executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true).0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())).0; assert!(r.is_ok()); t.execute_with(|| { @@ -298,14 +292,12 @@ fn successful_execution_with_foreign_code_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = - executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), true).0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())).0; assert!(r.is_ok()); t.execute_with(|| { @@ -337,7 +329,7 @@ fn full_native_block_import_works() { .base_extrinsic, ); - executor_call(&mut t, "Core_execute_block", &block1.0, true).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0).0.unwrap(); t.execute_with(|| { assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees); @@ -412,7 +404,7 @@ fn full_native_block_import_works() { fees = t.execute_with(|| transfer_fee(&xt())); let pot = t.execute_with(|| Treasury::pot()); - executor_call(&mut t, "Core_execute_block", &block2.0, true).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0).0.unwrap(); t.execute_with(|| { assert_eq!( @@ -554,7 +546,7 @@ fn full_wasm_block_import_works() { let mut alice_last_known_balance: Balance = Default::default(); let mut fees = t.execute_with(|| transfer_fee(&xt())); - executor_call(&mut t, "Core_execute_block", &block1.0, false).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0).0.unwrap(); t.execute_with(|| { assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees); @@ -564,7 +556,7 @@ fn full_wasm_block_import_works() { fees = t.execute_with(|| transfer_fee(&xt())); - executor_call(&mut t, "Core_execute_block", &block2.0, false).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0).0.unwrap(); t.execute_with(|| { assert_eq!( @@ -717,7 +709,7 @@ fn deploying_wasm_contract_should_work() { let mut t = new_test_ext(compact_code_unwrap()); - executor_call(&mut t, "Core_execute_block", &b.0, false).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &b.0).0.unwrap(); t.execute_with(|| { // Verify that the contract does exist by querying some of its storage items @@ -732,8 +724,7 @@ fn wasm_big_block_import_fails() { set_heap_pages(&mut t.ext(), 4); - let result = - executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, false).0; + let result = executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0).0; assert!(result.is_err()); // Err(Wasmi(Trap(Trap { kind: Host(AllocatorOutOfSpace) }))) } @@ -741,7 +732,7 @@ fn wasm_big_block_import_fails() { fn native_big_block_import_succeeds() { let mut t = new_test_ext(compact_code_unwrap()); - executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, true) + executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0) .0 .unwrap(); } @@ -754,11 +745,9 @@ fn native_big_block_import_fails_on_fallback() { // block. set_heap_pages(&mut t.ext(), 8); - assert!( - executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0, false,) - .0 - .is_err() - ); + assert!(executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0) + .0 + .is_err()); } #[test] @@ -775,15 +764,9 @@ fn panic_execution_gives_error() { t.insert(>::hashed_key().to_vec(), 0_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - false, - ) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); - let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), false) + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())) .0 .unwrap(); let r = ApplyExtrinsicResult::decode(&mut &r[..]).unwrap(); @@ -816,13 +799,7 @@ fn successful_execution_gives_ok() { ); t.insert(>::hashed_key_for(0), vec![0u8; 32]); - let r = executor_call( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - false, - ) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); t.execute_with(|| { assert_eq!(Balances::total_balance(&alice()), 111 * DOLLARS); @@ -830,7 +807,7 @@ fn successful_execution_gives_ok() { let fees = t.execute_with(|| transfer_fee(&xt())); - let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()), false) + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())) .0 .unwrap(); ApplyExtrinsicResult::decode(&mut &r[..]) @@ -857,3 +834,19 @@ fn should_import_block_with_test_client() { futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); } + +#[test] +fn default_config_as_json_works() { + let mut t = new_test_ext(compact_code_unwrap()); + let r = executor_call(&mut t, "GenesisBuilder_create_default_config", &vec![]) + .0 + .unwrap(); + let r = Vec::::decode(&mut &r[..]).unwrap(); + let json = String::from_utf8(r.into()).expect("returned value is json. qed."); + let expected = include_str!("res/default_genesis_config.json").to_string(); + + assert_eq!( + serde_json::from_str::(&expected).unwrap(), + serde_json::from_str::(&json).unwrap() + ); +} diff --git a/substrate/bin/node/executor/tests/common.rs b/substrate/bin/node/cli/tests/common.rs similarity index 90% rename from substrate/bin/node/executor/tests/common.rs rename to substrate/bin/node/cli/tests/common.rs index 6ce9ea3a010909209834b071a710ce52a9cfe584..2d74cdd5a0418aa7f93d7ebadfe3598036fb38ab 100644 --- a/substrate/bin/node/executor/tests/common.rs +++ b/substrate/bin/node/cli/tests/common.rs @@ -18,7 +18,7 @@ use codec::{Decode, Encode}; use frame_support::Hashable; use frame_system::offchain::AppCrypto; -use sc_executor::{error::Result, NativeElseWasmExecutor, WasmExecutor}; +use sc_executor::error::Result; use sp_consensus_babe::{ digests::{PreDigest, SecondaryPlainPreDigest}, Slot, BABE_ENGINE_ID, @@ -38,10 +38,10 @@ use kitchensink_runtime::{ constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, Runtime, UncheckedExtrinsic, }; -use node_executor::ExecutorDispatch; use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; use sp_externalities::Externalities; +use staging_node_cli::service::RuntimeExecutor; pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); @@ -97,15 +97,14 @@ pub fn from_block_number(n: u32) -> Header { Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default()) } -pub fn executor() -> NativeElseWasmExecutor { - NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()) +pub fn executor() -> RuntimeExecutor { + RuntimeExecutor::builder().build() } pub fn executor_call( t: &mut TestExternalities, method: &str, data: &[u8], - use_native: bool, ) -> (Result>, bool) { let mut t = t.ext(); @@ -113,17 +112,17 @@ pub fn executor_call( let heap_pages = t.storage(sp_core::storage::well_known_keys::HEAP_PAGES); let runtime_code = RuntimeCode { code_fetcher: &sp_core::traits::WrappedRuntimeCode(code.as_slice().into()), - hash: sp_core::blake2_256(&code).to_vec(), + hash: sp_crypto_hashing::blake2_256(&code).to_vec(), heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()), }; sp_tracing::try_init_simple(); - executor().call(&mut t, &runtime_code, method, data, use_native, CallContext::Onchain) + executor().call(&mut t, &runtime_code, method, data, CallContext::Onchain) } pub fn new_test_ext(code: &[u8]) -> TestExternalities { let ext = TestExternalities::new_with_code( code, - node_testing::genesis::config(Some(code)).build_storage().unwrap(), + node_testing::genesis::config().build_storage().unwrap(), ); ext } @@ -168,12 +167,12 @@ pub fn construct_block( }; // execute the block to get the real header. - executor_call(env, "Core_initialize_block", &header.encode(), true).0.unwrap(); + executor_call(env, "Core_initialize_block", &header.encode()).0.unwrap(); for extrinsic in extrinsics.iter() { // Try to apply the `extrinsic`. It should be valid, in the sense that it passes // all pre-inclusion checks. - let r = executor_call(env, "BlockBuilder_apply_extrinsic", &extrinsic.encode(), true) + let r = executor_call(env, "BlockBuilder_apply_extrinsic", &extrinsic.encode()) .0 .expect("application of an extrinsic failed"); @@ -186,7 +185,7 @@ pub fn construct_block( } let header = Header::decode( - &mut &executor_call(env, "BlockBuilder_finalize_block", &[0u8; 0], true).0.unwrap()[..], + &mut &executor_call(env, "BlockBuilder_finalize_block", &[0u8; 0]).0.unwrap()[..], ) .unwrap(); diff --git a/substrate/bin/node/executor/tests/fees.rs b/substrate/bin/node/cli/tests/fees.rs similarity index 95% rename from substrate/bin/node/executor/tests/fees.rs rename to substrate/bin/node/cli/tests/fees.rs index 7519ce6e8b1b47b9cfc6eee9d87c52ef8ae25cc3..8c7b3c873157770a8f156a019aa7e43e66460bae 100644 --- a/substrate/bin/node/executor/tests/fees.rs +++ b/substrate/bin/node/cli/tests/fees.rs @@ -95,7 +95,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { ); // execute a big block. - executor_call(&mut t, "Core_execute_block", &block1.0, true).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block1.0).0.unwrap(); // weight multiplier is increased for next block. t.execute_with(|| { @@ -106,7 +106,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { }); // execute a big block. - executor_call(&mut t, "Core_execute_block", &block2.0, true).0.unwrap(); + executor_call(&mut t, "Core_execute_block", &block2.0).0.unwrap(); // weight multiplier is increased for next block. t.execute_with(|| { @@ -151,12 +151,10 @@ fn transaction_fee_is_correct() { function: RuntimeCall::Balances(default_transfer_call()), }); - let r = - executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32)), true) - .0; + let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0; assert!(r.is_ok()); - let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt.clone()), true).0; + let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt.clone())).0; assert!(r.is_ok()); t.execute_with(|| { @@ -247,7 +245,7 @@ fn block_weight_capacity_report() { len / 1024 / 1024, ); - let r = executor_call(&mut t, "Core_execute_block", &block.0, true).0; + let r = executor_call(&mut t, "Core_execute_block", &block.0).0; println!(" || Result = {:?}", r); assert!(r.is_ok()); @@ -310,7 +308,7 @@ fn block_length_capacity_report() { len / 1024 / 1024, ); - let r = executor_call(&mut t, "Core_execute_block", &block.0, true).0; + let r = executor_call(&mut t, "Core_execute_block", &block.0).0; println!(" || Result = {:?}", r); assert!(r.is_ok()); diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json new file mode 100644 index 0000000000000000000000000000000000000000..e21fbb47da8c4619e0923c85bf3470828cd80b23 --- /dev/null +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -0,0 +1,118 @@ +{ + "system": {}, + "babe": { + "authorities": [], + "epochConfig": { + "allowed_slots": "PrimaryAndSecondaryVRFSlots", + "c": [ + 1, + 4 + ] + } + }, + "indices": { + "indices": [] + }, + "balances": { + "balances": [] + }, + "transactionPayment": { + "multiplier": "1000000000000000000" + }, + "staking": { + "validatorCount": 0, + "minimumValidatorCount": 0, + "invulnerables": [], + "forceEra": "NotForcing", + "slashRewardFraction": 0, + "canceledPayout": 0, + "stakers": [], + "minNominatorBond": 0, + "minValidatorBond": 0, + "maxValidatorCount": null, + "maxNominatorCount": null + }, + "session": { + "keys": [] + }, + "democracy": {}, + "council": { + "members": [] + }, + "technicalCommittee": { + "members": [] + }, + "elections": { + "members": [] + }, + "technicalMembership": { + "members": [] + }, + "grandpa": { + "authorities": [] + }, + "beefy": { + "authorities": [], + "genesisBlock": 1 + }, + "treasury": {}, + "sudo": { + "key": null + }, + "imOnline": { + "keys": [] + }, + "authorityDiscovery": { + "keys": [] + }, + "society": { + "pot": 0 + }, + "vesting": { + "vesting": [] + }, + "glutton": { + "compute": "0", + "storage": "0", + "trashDataCount": 0 + }, + "assets": { + "assets": [], + "metadata": [], + "accounts": [] + }, + "poolAssets": { + "assets": [], + "metadata": [], + "accounts": [] + }, + "transactionStorage": { + "byteFee": 10, + "entryFee": 1000, + "storagePeriod": 100800 + }, + "allianceMotion": { + "members": [] + }, + "alliance": { + "fellows": [], + "allies": [] + }, + "mixnet": { + "mixnodes": [] + }, + "nominationPools": { + "minJoinBond": 0, + "minCreateBond": 0, + "maxPools": 16, + "maxMembersPerPool": 32, + "maxMembers": 512, + "globalMaxCommission": null + }, + "txPause": { + "paused": [] + }, + "safeMode": { + "enteredUntil": null + } +} diff --git a/substrate/bin/node/executor/tests/submit_transaction.rs b/substrate/bin/node/cli/tests/submit_transaction.rs similarity index 100% rename from substrate/bin/node/executor/tests/submit_transaction.rs rename to substrate/bin/node/cli/tests/submit_transaction.rs 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/executor/Cargo.toml b/substrate/bin/node/executor/Cargo.toml deleted file mode 100644 index f73d97eb8cf1927e3500d416f69a11a6a4374ad2..0000000000000000000000000000000000000000 --- a/substrate/bin/node/executor/Cargo.toml +++ /dev/null @@ -1,56 +0,0 @@ -[package] -name = "node-executor" -version = "3.0.0-dev" -authors.workspace = true -description = "Substrate node implementation in Rust." -edition.workspace = true -license = "Apache-2.0" -homepage = "https://substrate.io" -repository.workspace = true -publish = false - -[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", features = ["derive"] } -frame-benchmarking = { path = "../../../frame/benchmarking" } -node-primitives = { path = "../primitives" } -kitchensink-runtime = { path = "../runtime" } -sc-executor = { path = "../../../client/executor" } -sp-core = { path = "../../../primitives/core" } -sp-keystore = { path = "../../../primitives/keystore" } -sp-state-machine = { path = "../../../primitives/state-machine" } -sp-tracing = { path = "../../../primitives/tracing" } -sp-trie = { path = "../../../primitives/trie" } -sp-statement-store = { path = "../../../primitives/statement-store" } - -[dev-dependencies] -criterion = "0.4.0" -futures = "0.3.21" -wat = "1.0" -frame-support = { path = "../../../frame/support" } -frame-system = { path = "../../../frame/system" } -node-testing = { path = "../testing" } -pallet-balances = { path = "../../../frame/balances" } -pallet-contracts = { path = "../../../frame/contracts" } -pallet-im-online = { path = "../../../frame/im-online" } -pallet-glutton = { path = "../../../frame/glutton" } -pallet-sudo = { path = "../../../frame/sudo" } -pallet-timestamp = { path = "../../../frame/timestamp" } -pallet-treasury = { path = "../../../frame/treasury" } -pallet-transaction-payment = { path = "../../../frame/transaction-payment" } -sp-application-crypto = { path = "../../../primitives/application-crypto" } -pallet-root-testing = { path = "../../../frame/root-testing" } -sp-consensus-babe = { path = "../../../primitives/consensus/babe" } -sp-externalities = { path = "../../../primitives/externalities" } -sp-keyring = { path = "../../../primitives/keyring" } -sp-runtime = { path = "../../../primitives/runtime" } - -[features] -stress-test = [] - -[[bench]] -name = "bench" -harness = false diff --git a/substrate/bin/node/executor/src/lib.rs b/substrate/bin/node/executor/src/lib.rs deleted file mode 100644 index 3557a16740b8a6407d485a74dc422985805e063b..0000000000000000000000000000000000000000 --- a/substrate/bin/node/executor/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! A `CodeExecutor` specialization which uses natively compiled runtime when the wasm to be -//! executed is equivalent to the natively compiled code. - -pub use sc_executor::NativeElseWasmExecutor; - -// Declare an instance of the native executor named `ExecutorDispatch`. Include the wasm binary as -// the equivalent wasm code. -pub struct ExecutorDispatch; - -impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { - type ExtendHostFunctions = ( - frame_benchmarking::benchmarking::HostFunctions, - sp_statement_store::runtime_api::HostFunctions, - ); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - kitchensink_runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - kitchensink_runtime::native_version() - } -} diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index 4a92db2918589da64edbeef74ca1f04bb620a354..8453aa3cdeb18715afede5d93fb52f3c6b39be7e 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -1,24 +1,34 @@ [package] -name = "node-inspect" -version = "0.9.0-dev" +name = "staging-node-inspect" +version = "0.12.0" authors.workspace = true description = "Substrate node block inspection tool." edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository.workspace = true -publish = false + +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } -thiserror = "1.0" +thiserror = { workspace = true } sc-cli = { path = "../../../client/cli" } sc-client-api = { path = "../../../client/api" } -sc-service = { path = "../../../client/service", default-features = false} +sc-service = { path = "../../../client/service", default-features = false } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core" } +sp-io = { path = "../../../primitives/io" } sp-runtime = { path = "../../../primitives/runtime" } +sp-statement-store = { path = "../../../primitives/statement-store" } + +[features] +runtime-benchmarks = [ + "sc-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/substrate/bin/node/inspect/src/command.rs b/substrate/bin/node/inspect/src/command.rs index dcecfd788264466b408e4ec466d675cb97781905..e0e25707e31b06b7d53f31bde9a0b809211d8dce 100644 --- a/substrate/bin/node/inspect/src/command.rs +++ b/substrate/bin/node/inspect/src/command.rs @@ -23,18 +23,20 @@ use crate::{ Inspector, }; use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; -use sc_service::{Configuration, NativeExecutionDispatch}; +use sc_service::Configuration; use sp_runtime::traits::Block; +type HostFunctions = + (sp_io::SubstrateHostFunctions, sp_statement_store::runtime_api::HostFunctions); + impl InspectCmd { /// Run the inspect command, passing the inspector. - pub fn run(&self, config: Configuration) -> Result<()> + pub fn run(&self, config: Configuration) -> Result<()> where B: Block, RA: Send + Sync + 'static, - D: NativeExecutionDispatch + 'static, { - let executor = sc_service::new_native_or_wasm_executor::(&config); + let executor = sc_service::new_wasm_executor::(&config); let client = sc_service::new_full_client::(&config, None, executor)?; let inspect = Inspector::::new(client); diff --git a/substrate/bin/node/primitives/Cargo.toml b/substrate/bin/node/primitives/Cargo.toml index 77bf7ad467614e7df0fdd68a601a2d614fa9bc0e..24279ad09c3d9f4576a212d7c67ac24be27b8e22 100644 --- a/substrate/bin/node/primitives/Cargo.toml +++ b/substrate/bin/node/primitives/Cargo.toml @@ -9,13 +9,16 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { path = "../../../primitives/core", default-features = false} -sp-runtime = { path = "../../../primitives/runtime", default-features = false} +sp-core = { path = "../../../primitives/core", default-features = false } +sp-runtime = { path = "../../../primitives/runtime", default-features = false } [features] -default = [ "std" ] -std = [ "sp-core/std", "sp-runtime/std" ] +default = ["std"] +std = ["sp-core/std", "sp-runtime/std"] diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 43db4ab9d34f709c43809e5bf909b979eebba6c5..894dbf0da85ca56d412087adf01fa12c7983ae7a 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.16.2", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } node-primitives = { path = "../primitives" } pallet-transaction-payment-rpc = { path = "../../../frame/transaction-payment/rpc" } mmr-rpc = { path = "../../../client/merkle-mountain-range/rpc" } @@ -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 bf6c540774c1a122d0b26e12f50196a7d3e354ec..4d342ceb460a3f3a5c12b502915f2b679c2b469c 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"] @@ -20,127 +23,134 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", "max-encoded-len", ] } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +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 } +log = { workspace = true } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } # pallet-asset-conversion: turn on "num-traits" feature -primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info", "num-traits"] } +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } # primitives -sp-authority-discovery = { path = "../../../primitives/authority-discovery", default-features = false} -sp-consensus-babe = { path = "../../../primitives/consensus/babe", default-features = false} -sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa", default-features = false} -sp-block-builder = { path = "../../../primitives/block-builder", default-features = false} +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" } -sp-inherents = { path = "../../../primitives/inherents", default-features = false} -node-primitives = { path = "../primitives", default-features = false} +sp-inherents = { path = "../../../primitives/inherents", default-features = false } +node-primitives = { path = "../primitives", default-features = false } sp-mixnet = { path = "../../../primitives/mixnet", default-features = false } -sp-offchain = { path = "../../../primitives/offchain", default-features = false} -sp-core = { path = "../../../primitives/core", default-features = false} -sp-std = { path = "../../../primitives/std", default-features = false} -sp-api = { path = "../../../primitives/api", default-features = false} -sp-runtime = { path = "../../../primitives/runtime", default-features = false} -sp-staking = { path = "../../../primitives/staking", default-features = false} -sp-storage = { path = "../../../primitives/storage", default-features = false} -sp-session = { path = "../../../primitives/session", default-features = false} -sp-transaction-pool = { path = "../../../primitives/transaction-pool", default-features = false} -sp-statement-store = { path = "../../../primitives/statement-store", default-features = false} -sp-version = { path = "../../../primitives/version", default-features = false} -sp-io = { path = "../../../primitives/io", default-features = false} +sp-offchain = { path = "../../../primitives/offchain", default-features = false } +sp-core = { path = "../../../primitives/core", default-features = false, features = ["serde"] } +sp-std = { path = "../../../primitives/std", default-features = false } +sp-api = { path = "../../../primitives/api", default-features = false } +sp-runtime = { path = "../../../primitives/runtime", default-features = false, features = ["serde"] } +sp-staking = { path = "../../../primitives/staking", default-features = false, features = ["serde"] } +sp-storage = { path = "../../../primitives/storage", default-features = false } +sp-session = { path = "../../../primitives/session", default-features = false } +sp-transaction-pool = { path = "../../../primitives/transaction-pool", default-features = false } +sp-statement-store = { path = "../../../primitives/statement-store", default-features = false, features = ["serde"] } +sp-version = { path = "../../../primitives/version", default-features = false, features = ["serde"] } +sp-io = { path = "../../../primitives/io", default-features = false } # frame dependencies -frame-executive = { path = "../../../frame/executive", default-features = false} -frame-benchmarking = { path = "../../../frame/benchmarking", default-features = false} -frame-benchmarking-pallet-pov = { path = "../../../frame/benchmarking/pov", default-features = false} -frame-support = { path = "../../../frame/support", default-features = false, features = ["tuples-96"] } -frame-system = { path = "../../../frame/system", default-features = false} +frame-executive = { path = "../../../frame/executive", default-features = false } +frame-benchmarking = { path = "../../../frame/benchmarking", default-features = false } +frame-benchmarking-pallet-pov = { path = "../../../frame/benchmarking/pov", default-features = false } +frame-support = { path = "../../../frame/support", default-features = false, features = ["experimental", "tuples-96"] } +frame-system = { path = "../../../frame/system", default-features = false } frame-system-benchmarking = { path = "../../../frame/system/benchmarking", default-features = false, optional = true } -frame-election-provider-support = { path = "../../../frame/election-provider-support", default-features = false} -frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api", default-features = false} +frame-election-provider-support = { path = "../../../frame/election-provider-support", default-features = false } +frame-system-rpc-runtime-api = { path = "../../../frame/system/rpc/runtime-api", default-features = false } frame-try-runtime = { path = "../../../frame/try-runtime", default-features = false, optional = true } -pallet-alliance = { path = "../../../frame/alliance", default-features = false} -pallet-asset-conversion = { path = "../../../frame/asset-conversion", default-features = false} -pallet-asset-rate = { path = "../../../frame/asset-rate", default-features = false} -pallet-assets = { path = "../../../frame/assets", default-features = false} -pallet-authority-discovery = { path = "../../../frame/authority-discovery", default-features = false} -pallet-authorship = { path = "../../../frame/authorship", default-features = false} -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-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} -pallet-collective = { path = "../../../frame/collective", default-features = false} -pallet-contracts = { path = "../../../frame/contracts", default-features = false} -pallet-contracts-primitives = { path = "../../../frame/contracts/primitives", default-features = false} -pallet-conviction-voting = { path = "../../../frame/conviction-voting", default-features = false} -pallet-core-fellowship = { path = "../../../frame/core-fellowship", default-features = false} -pallet-democracy = { path = "../../../frame/democracy", default-features = false} -pallet-election-provider-multi-phase = { path = "../../../frame/election-provider-multi-phase", default-features = false} +pallet-alliance = { path = "../../../frame/alliance", default-features = false } +pallet-asset-conversion = { path = "../../../frame/asset-conversion", default-features = false } +pallet-asset-rate = { path = "../../../frame/asset-rate", default-features = false } +pallet-assets = { path = "../../../frame/assets", default-features = false } +pallet-authority-discovery = { path = "../../../frame/authority-discovery", default-features = false } +pallet-authorship = { path = "../../../frame/authorship", default-features = false } +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 } +pallet-collective = { path = "../../../frame/collective", default-features = false } +pallet-contracts = { path = "../../../frame/contracts", default-features = false } +pallet-conviction-voting = { path = "../../../frame/conviction-voting", default-features = false } +pallet-core-fellowship = { path = "../../../frame/core-fellowship", default-features = false } +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-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} -pallet-im-online = { path = "../../../frame/im-online", default-features = false} -pallet-indices = { path = "../../../frame/indices", default-features = false} -pallet-identity = { path = "../../../frame/identity", default-features = false} -pallet-lottery = { path = "../../../frame/lottery", default-features = false} -pallet-membership = { path = "../../../frame/membership", default-features = false} -pallet-message-queue = { path = "../../../frame/message-queue", default-features = false} +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-migrations = { path = "../../../frame/migrations", default-features = false } +pallet-nis = { path = "../../../frame/nis", default-features = false } +pallet-grandpa = { path = "../../../frame/grandpa", default-features = false } +pallet-im-online = { path = "../../../frame/im-online", default-features = false } +pallet-indices = { path = "../../../frame/indices", default-features = false } +pallet-identity = { path = "../../../frame/identity", default-features = false } +pallet-lottery = { path = "../../../frame/lottery", default-features = false } +pallet-membership = { path = "../../../frame/membership", default-features = false } +pallet-message-queue = { path = "../../../frame/message-queue", default-features = false } pallet-mixnet = { path = "../../../frame/mixnet", default-features = false } -pallet-mmr = { path = "../../../frame/merkle-mountain-range", default-features = false} -pallet-multisig = { path = "../../../frame/multisig", default-features = false} -pallet-nfts = { path = "../../../frame/nfts", default-features = false} -pallet-nfts-runtime-api = { path = "../../../frame/nfts/runtime-api", default-features = false} -pallet-nft-fractionalization = { path = "../../../frame/nft-fractionalization", default-features = false} -pallet-nomination-pools = { path = "../../../frame/nomination-pools", default-features = false} -pallet-nomination-pools-benchmarking = { path = "../../../frame/nomination-pools/benchmarking", default-features = false, optional = true} -pallet-nomination-pools-runtime-api = { path = "../../../frame/nomination-pools/runtime-api", default-features = false} -pallet-offences = { path = "../../../frame/offences", default-features = false} +pallet-mmr = { path = "../../../frame/merkle-mountain-range", default-features = false } +pallet-multisig = { path = "../../../frame/multisig", default-features = false } +pallet-nfts = { path = "../../../frame/nfts", default-features = false } +pallet-nfts-runtime-api = { path = "../../../frame/nfts/runtime-api", default-features = false } +pallet-nft-fractionalization = { path = "../../../frame/nft-fractionalization", default-features = false } +pallet-nomination-pools = { path = "../../../frame/nomination-pools", default-features = false } +pallet-nomination-pools-benchmarking = { path = "../../../frame/nomination-pools/benchmarking", default-features = false, optional = true } +pallet-nomination-pools-runtime-api = { path = "../../../frame/nomination-pools/runtime-api", default-features = false } +pallet-offences = { path = "../../../frame/offences", default-features = false } pallet-offences-benchmarking = { path = "../../../frame/offences/benchmarking", default-features = false, optional = true } -pallet-glutton = { path = "../../../frame/glutton", default-features = false} -pallet-preimage = { path = "../../../frame/preimage", default-features = false} -pallet-proxy = { path = "../../../frame/proxy", default-features = false} -pallet-insecure-randomness-collective-flip = { path = "../../../frame/insecure-randomness-collective-flip", default-features = false} -pallet-ranked-collective = { path = "../../../frame/ranked-collective", default-features = false} -pallet-recovery = { path = "../../../frame/recovery", default-features = false} -pallet-referenda = { path = "../../../frame/referenda", default-features = false} -pallet-remark = { path = "../../../frame/remark", default-features = false} -pallet-root-testing = { path = "../../../frame/root-testing", default-features = false} -pallet-salary = { path = "../../../frame/salary", default-features = false} -pallet-session = { path = "../../../frame/session", default-features = false , features = [ "historical" ]} +pallet-glutton = { path = "../../../frame/glutton", default-features = false } +pallet-preimage = { path = "../../../frame/preimage", default-features = false } +pallet-proxy = { path = "../../../frame/proxy", default-features = false } +pallet-insecure-randomness-collective-flip = { path = "../../../frame/insecure-randomness-collective-flip", default-features = false } +pallet-ranked-collective = { path = "../../../frame/ranked-collective", default-features = false } +pallet-recovery = { path = "../../../frame/recovery", default-features = false } +pallet-referenda = { path = "../../../frame/referenda", default-features = false } +pallet-remark = { path = "../../../frame/remark", default-features = false } +pallet-root-testing = { path = "../../../frame/root-testing", default-features = false } +pallet-salary = { path = "../../../frame/salary", default-features = false } +pallet-session = { path = "../../../frame/session", default-features = false, features = ["historical"] } pallet-session-benchmarking = { path = "../../../frame/session/benchmarking", default-features = false, optional = true } -pallet-staking = { path = "../../../frame/staking", default-features = false} -pallet-staking-reward-curve = { path = "../../../frame/staking/reward-curve", default-features = false} -pallet-staking-runtime-api = { path = "../../../frame/staking/runtime-api", default-features = false} -pallet-state-trie-migration = { path = "../../../frame/state-trie-migration", default-features = false} -pallet-statement = { path = "../../../frame/statement", default-features = false} -pallet-scheduler = { path = "../../../frame/scheduler", default-features = false} -pallet-society = { path = "../../../frame/society", default-features = false} -pallet-sudo = { path = "../../../frame/sudo", default-features = false} -pallet-timestamp = { path = "../../../frame/timestamp", default-features = false} -pallet-tips = { path = "../../../frame/tips", default-features = false} -pallet-treasury = { path = "../../../frame/treasury", default-features = false} -pallet-utility = { path = "../../../frame/utility", default-features = false} -pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false} -pallet-transaction-payment-rpc-runtime-api = { path = "../../../frame/transaction-payment/rpc/runtime-api", default-features = false} -pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment", default-features = false} -pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment", default-features = false} -pallet-transaction-storage = { path = "../../../frame/transaction-storage", default-features = false} -pallet-uniques = { path = "../../../frame/uniques", default-features = false} -pallet-vesting = { path = "../../../frame/vesting", default-features = false} -pallet-whitelist = { path = "../../../frame/whitelist", default-features = false} -pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false} -pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false} +pallet-staking = { path = "../../../frame/staking", default-features = false } +pallet-staking-reward-curve = { path = "../../../frame/staking/reward-curve", default-features = false } +pallet-staking-runtime-api = { path = "../../../frame/staking/runtime-api", default-features = false } +pallet-state-trie-migration = { path = "../../../frame/state-trie-migration", default-features = false } +pallet-statement = { path = "../../../frame/statement", default-features = false } +pallet-scheduler = { path = "../../../frame/scheduler", default-features = false } +pallet-society = { path = "../../../frame/society", default-features = false } +pallet-sudo = { path = "../../../frame/sudo", default-features = false } +pallet-timestamp = { path = "../../../frame/timestamp", default-features = false } +pallet-tips = { path = "../../../frame/tips", default-features = false } +pallet-treasury = { path = "../../../frame/treasury", default-features = false } +pallet-utility = { path = "../../../frame/utility", default-features = false } +pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../../../frame/transaction-payment/rpc/runtime-api", default-features = false } +pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment", default-features = false } +pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment", default-features = false } +pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment", default-features = false } +pallet-transaction-storage = { path = "../../../frame/transaction-storage", default-features = false } +pallet-uniques = { path = "../../../frame/uniques", default-features = false } +pallet-vesting = { path = "../../../frame/vesting", default-features = false } +pallet-whitelist = { path = "../../../frame/whitelist", default-features = false } +pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false } +pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false } +pallet-parameters = { path = "../../../frame/parameters", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } [features] -default = [ "std" ] -with-tracing = [ "frame-executive/with-tracing" ] +default = ["std"] +with-tracing = ["frame-executive/with-tracing"] std = [ "codec/std", "frame-benchmarking-pallet-pov/std", @@ -165,11 +175,12 @@ 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", "pallet-collective/std", - "pallet-contracts-primitives/std", "pallet-contracts/std", "pallet-conviction-voting/std", "pallet-core-fellowship/std", @@ -177,6 +188,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", @@ -187,6 +199,7 @@ std = [ "pallet-lottery/std", "pallet-membership/std", "pallet-message-queue/std", + "pallet-migrations/std", "pallet-mixnet/std", "pallet-mmr/std", "pallet-multisig/std", @@ -199,6 +212,7 @@ std = [ "pallet-nomination-pools/std", "pallet-offences-benchmarking?/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-ranked-collective/std", @@ -211,6 +225,7 @@ std = [ "pallet-scheduler/std", "pallet-session-benchmarking?/std", "pallet-session/std", + "pallet-skip-feeless-payment/std", "pallet-society/std", "pallet-staking-runtime-api/std", "pallet-staking/std", @@ -228,11 +243,14 @@ std = [ "pallet-utility/std", "pallet-vesting/std", "pallet-whitelist/std", + "primitive-types/std", "scale-info/std", + "serde_json/std", "sp-api/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", @@ -276,6 +294,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", @@ -285,6 +304,7 @@ runtime-benchmarks = [ "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-migrations/runtime-benchmarks", "pallet-mixnet/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", @@ -295,6 +315,7 @@ runtime-benchmarks = [ "pallet-nomination-pools/runtime-benchmarks", "pallet-offences-benchmarking/runtime-benchmarks", "pallet-offences/runtime-benchmarks", + "pallet-parameters/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", @@ -305,6 +326,7 @@ runtime-benchmarks = [ "pallet-salary/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-session-benchmarking/runtime-benchmarks", + "pallet-skip-feeless-payment/runtime-benchmarks", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", @@ -339,6 +361,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", @@ -349,6 +373,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", @@ -359,6 +384,7 @@ try-runtime = [ "pallet-lottery/try-runtime", "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", + "pallet-migrations/try-runtime", "pallet-mixnet/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", @@ -367,6 +393,7 @@ try-runtime = [ "pallet-nis/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-ranked-collective/try-runtime", @@ -378,6 +405,7 @@ try-runtime = [ "pallet-salary/try-runtime", "pallet-scheduler/try-runtime", "pallet-session/try-runtime", + "pallet-skip-feeless-payment/try-runtime", "pallet-society/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", @@ -395,3 +423,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/impls.rs b/substrate/bin/node/runtime/src/impls.rs index 430a1ac2824b806acf7d95715c72cf5448d2a5a6..34f043b33a4edfe2d8cdbe154896e937826110ca 100644 --- a/substrate/bin/node/runtime/src/impls.rs +++ b/substrate/bin/node/runtime/src/impls.rs @@ -26,11 +26,12 @@ use frame_support::{ }; use pallet_alliance::{IdentityVerifier, ProposalIndex, ProposalProvider}; use pallet_asset_tx_payment::HandleCredit; +use pallet_identity::legacy::IdentityField; use sp_std::prelude::*; use crate::{ - AccountId, AllianceMotion, Assets, Authorship, Balances, Hash, NegativeImbalance, Runtime, - RuntimeCall, + AccountId, AllianceCollective, AllianceMotion, Assets, Authorship, Balances, Hash, + NegativeImbalance, Runtime, RuntimeCall, }; pub struct Author; @@ -56,14 +57,14 @@ impl HandleCredit for CreditToBlockAuthor { pub struct AllianceIdentityVerifier; impl IdentityVerifier for AllianceIdentityVerifier { - fn has_identity(who: &AccountId, fields: u64) -> bool { - crate::Identity::has_identity(who, fields) + fn has_required_identities(who: &AccountId) -> bool { + crate::Identity::has_identity(who, (IdentityField::Display | IdentityField::Web).bits()) } fn has_good_judgement(who: &AccountId) -> bool { use pallet_identity::Judgement; crate::Identity::identity(who) - .map(|registration| registration.judgements) + .map(|(registration, _)| registration.judgements) .map_or(false, |judgements| { judgements .iter() @@ -106,7 +107,7 @@ impl ProposalProvider for AllianceProposalProvider } fn proposal_of(proposal_hash: Hash) -> Option { - AllianceMotion::proposal_of(proposal_hash) + pallet_collective::ProposalOf::::get(proposal_hash) } } @@ -275,7 +276,7 @@ mod multiplier_tests { let next = runtime_multiplier_update(fm); fm = next; if fm == min_multiplier() { - break + break; } iterations += 1; } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index b2e625d7830efc14578a8b2c6e67eb3d9192d985..38e5c266d9579511e9d1d758ef6cf06edbfabe53 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -28,20 +28,26 @@ use frame_election_provider_support::{ onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight, }; use frame_support::{ - construct_runtime, + derive_impl, dispatch::DispatchClass, + dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_config, create_default_config}, instances::{Instance1, Instance2}, ord_parameter_types, 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, - WithdrawReasons, + EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -57,9 +63,10 @@ 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; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; @@ -69,6 +76,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}; @@ -125,7 +136,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")] @@ -282,34 +293,33 @@ impl pallet_safe_mode::Config for Runtime { type WeightInfo = pallet_safe_mode::weights::SubstrateWeight; } +#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] impl frame_system::Config for Runtime { type BaseCallFilter = InsideBoth; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; type Nonce = Nonce; type Hash = Hash; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = Indices; type Block = Block; - type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); type SystemWeightInfo = frame_system::weights::SubstrateWeight; type SS58Prefix = ConstU16<42>; - type OnSetCode = (); type MaxConsumers = ConstU32<16>; + type MultiBlockMigrator = MultiBlockMigrations; } 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; @@ -449,9 +459,6 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; - // One cent: $10,000 / MB - pub const PreimageByteDeposit: Balance = 1 * CENTS; pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } @@ -464,7 +471,11 @@ impl pallet_preimage::Config for Runtime { AccountId, Balances, PreimageHoldReason, - LinearStoragePrice, + LinearStoragePrice< + dynamic_params::storage::BaseDeposit, + dynamic_params::storage::ByteDeposit, + Balance, + >, >; } @@ -484,7 +495,7 @@ impl pallet_babe::Config for Runtime { type DisabledValidators = Session; type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type KeyOwnerProof = >::Proof; type EquivocationReportSystem = @@ -512,6 +523,8 @@ parameter_types! { } impl pallet_balances::Config for Runtime { + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type MaxLocks = MaxLocks; type MaxReserves = MaxReserves; type ReserveIdentifier = [u8; 8]; @@ -523,8 +536,6 @@ impl pallet_balances::Config for Runtime { type WeightInfo = pallet_balances::weights::SubstrateWeight; type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = ConstU32<1>; - type RuntimeHoldReason = RuntimeHoldReason; - type MaxHolds = ConstU32<2>; } parameter_types! { @@ -563,8 +574,15 @@ 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 { + type RuntimeEvent = RuntimeEvent; } parameter_types! { @@ -590,6 +608,7 @@ impl_opaque_keys! { pub im_online: ImOnline, pub authority_discovery: AuthorityDiscovery, pub mixnet: Mixnet, + pub beefy: Beefy, } } @@ -626,8 +645,9 @@ parameter_types! { pub const BondingDuration: sp_staking::EraIndex = 24 * 28; pub const SlashDeferDuration: sp_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 256; + 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; } @@ -661,7 +681,7 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxExposurePageSize = ConstU32<256>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; @@ -670,6 +690,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; @@ -684,8 +705,6 @@ impl pallet_fast_unstake::Config for Runtime { type Currency = Balances; type Staking = Staking; type MaxErasToCheckPerBlock = ConstU32<1>; - #[cfg(feature = "runtime-benchmarks")] - type MaxBackersPerValidator = MaxNominatorRewardedPerValidator; type WeightInfo = (); } @@ -700,8 +719,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() @@ -818,7 +835,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; @@ -865,7 +881,7 @@ parameter_types! { pub const MaxPointsToBalance: u8 = 10; } -use sp_runtime::traits::Convert; +use sp_runtime::traits::{Convert, Keccak256}; pub struct BalanceToU256; impl Convert for BalanceToU256 { fn convert(balance: Balance) -> sp_core::U256 { @@ -1000,11 +1016,17 @@ impl pallet_referenda::Config for Runtime { impl pallet_ranked_collective::Config for Runtime { type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight; type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRoot; + type RemoveOrigin = Self::DemoteOrigin; type PromoteOrigin = EnsureRootWithSuccess>; type DemoteOrigin = EnsureRootWithSuccess>; + type ExchangeOrigin = EnsureRootWithSuccess>; type Polls = RankedPolls; type MinRankOfClass = traits::Identity; type VoteWeight = pallet_ranked_collective::Geometric; + type MemberSwappedHandler = (CoreFellowship, Salary); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkSetup = (CoreFellowship, Salary); } impl pallet_remark::Config for Runtime { @@ -1012,7 +1034,9 @@ impl pallet_remark::Config for Runtime { type RuntimeEvent = RuntimeEvent; } -impl pallet_root_testing::Config for Runtime {} +impl pallet_root_testing::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -1307,9 +1331,6 @@ impl pallet_tips::Config for Runtime { } parameter_types! { - pub const DepositPerItem: Balance = deposit(1, 0); - pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } @@ -1327,9 +1348,9 @@ impl pallet_contracts::Config for Runtime { /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; + type DepositPerItem = dynamic_params::contracts::DepositPerItem; + type DepositPerByte = dynamic_params::contracts::DepositPerByte; + type DefaultDepositLimit = dynamic_params::contracts::DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; @@ -1339,6 +1360,8 @@ impl pallet_contracts::Config for Runtime { type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; type RuntimeHoldReason = RuntimeHoldReason; #[cfg(not(feature = "runtime-benchmarks"))] @@ -1349,6 +1372,8 @@ impl pallet_contracts::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Environment = (); + type ApiVersion = (); + type Xcm = (); } impl pallet_sudo::Config for Runtime { @@ -1394,7 +1419,11 @@ where frame_system::CheckEra::::from(era), frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), + pallet_skip_feeless_payment::SkipCheckIfFeeless::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from( + tip, None, + ), + ), ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { @@ -1451,7 +1480,7 @@ impl pallet_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type MaxAuthorities = MaxAuthorities; - type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; type KeyOwnerProof = >::Proof; type EquivocationReportSystem = @@ -1459,8 +1488,10 @@ impl pallet_grandpa::Config for Runtime { } parameter_types! { - pub const BasicDeposit: Balance = 10 * DOLLARS; // 258 bytes on-chain - pub const FieldDeposit: Balance = 250 * CENTS; // 66 bytes on-chain + // difference of 26 bytes on-chain for the registration and 9 bytes on-chain for the identity + // information, already accounted for by the byte deposit + pub const BasicDeposit: Balance = deposit(1, 17); + pub const ByteDeposit: Balance = deposit(0, 1); pub const SubAccountDeposit: Balance = 2 * DOLLARS; // 53 bytes on-chain pub const MaxSubAccounts: u32 = 100; pub const MaxAdditionalFields: u32 = 100; @@ -1471,14 +1502,20 @@ impl pallet_identity::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type BasicDeposit = BasicDeposit; - type FieldDeposit = FieldDeposit; + type ByteDeposit = ByteDeposit; type SubAccountDeposit = SubAccountDeposit; type MaxSubAccounts = MaxSubAccounts; - type MaxAdditionalFields = MaxAdditionalFields; + type IdentityInformation = IdentityInfo; type MaxRegistrars = MaxRegistrars; type Slashed = Treasury; type ForceOrigin = EnsureRootOrHalfCouncil; type RegistrarOrigin = EnsureRootOrHalfCouncil; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = pallet_identity::weights::SubstrateWeight; } @@ -1543,6 +1580,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; @@ -1550,12 +1588,23 @@ impl pallet_vesting::Config for Runtime { impl pallet_mmr::Config for Runtime { const INDEXING_PREFIX: &'static [u8] = b"mmr"; - type Hashing = ::Hashing; + type Hashing = Keccak256; type LeafData = pallet_mmr::ParentNumberAndHash; - type OnNewRoot = (); + type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; 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; @@ -1635,33 +1684,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 = (); } @@ -1832,6 +1882,7 @@ impl pallet_nfts::Config for Runtime { impl pallet_transaction_storage::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; type RuntimeCall = RuntimeCall; type FeeDestination = (); type WeightInfo = pallet_transaction_storage::weights::SubstrateWeight; @@ -1860,6 +1911,7 @@ impl pallet_state_trie_migration::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ControlOrigin = EnsureRoot; type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; type MaxKeyLen = MigrationMaxKeyLen; type SignedDepositPerItem = MigrationSignedDepositPerItem; type SignedDepositBase = MigrationSignedDepositBase; @@ -1960,6 +2012,25 @@ impl pallet_statement::Config for Runtime { type MaxAllowedBytes = MaxAllowedBytes; } +parameter_types! { + pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_migrations::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = (); + // Benchmarks need mocked migrations to guarantee that they succeed. + #[cfg(feature = "runtime-benchmarks")] + type Migrations = pallet_migrations::mock_helpers::MockedMigrations; + type CursorMaxLen = ConstU32<65_536>; + type IdentifierMaxLen = ConstU32<256>; + type MigrationStatusHandler = (); + type FailedMigrationHandler = frame_support::migrations::FreezeChainOnFailedMigration; + type MaxServiceWeight = MbmServiceWeight; + type WeightInfo = pallet_migrations::weights::SubstrateWeight; +} + parameter_types! { pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); } @@ -1974,7 +2045,6 @@ impl OnUnbalanced> for IntoAuthor { } parameter_types! { - pub storage CoreCount: Option = None; pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; } @@ -1982,36 +2052,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 RelayChainBlockNumberProvider = 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))); } } @@ -2054,85 +2112,335 @@ impl pallet_mixnet::Config for Runtime { type MinMixnodes = ConstU32<7>; // Low to allow small testing networks } -construct_runtime!( - pub struct Runtime - { - System: frame_system, - Utility: pallet_utility, - Babe: pallet_babe, - Timestamp: pallet_timestamp, - // Authorship must be before session in order to note author in the correct session and era - // for im-online and staking. - Authorship: pallet_authorship, - Indices: pallet_indices, - Balances: pallet_balances, - TransactionPayment: pallet_transaction_payment, - AssetTxPayment: pallet_asset_tx_payment, - AssetConversionTxPayment: pallet_asset_conversion_tx_payment, - ElectionProviderMultiPhase: pallet_election_provider_multi_phase, - Staking: pallet_staking, - Session: pallet_session, - Democracy: pallet_democracy, - Council: pallet_collective::, - TechnicalCommittee: pallet_collective::, - Elections: pallet_elections_phragmen, - TechnicalMembership: pallet_membership::, - Grandpa: pallet_grandpa, - Treasury: pallet_treasury, - AssetRate: pallet_asset_rate, - Contracts: pallet_contracts, - Sudo: pallet_sudo, - ImOnline: pallet_im_online, - AuthorityDiscovery: pallet_authority_discovery, - Offences: pallet_offences, - Historical: pallet_session_historical::{Pallet}, - RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip, - Identity: pallet_identity, - Society: pallet_society, - Recovery: pallet_recovery, - Vesting: pallet_vesting, - Scheduler: pallet_scheduler, - Glutton: pallet_glutton, - Preimage: pallet_preimage, - Proxy: pallet_proxy, - Multisig: pallet_multisig, - Bounties: pallet_bounties, - Tips: pallet_tips, - Assets: pallet_assets::, - PoolAssets: pallet_assets::, - Mmr: pallet_mmr, - Lottery: pallet_lottery, - Nis: pallet_nis, - Uniques: pallet_uniques, - Nfts: pallet_nfts, - NftFractionalization: pallet_nft_fractionalization, - Salary: pallet_salary, - CoreFellowship: pallet_core_fellowship, - TransactionStorage: pallet_transaction_storage, - VoterList: pallet_bags_list::, - StateTrieMigration: pallet_state_trie_migration, - ChildBounties: pallet_child_bounties, - Referenda: pallet_referenda, - Remark: pallet_remark, - RootTesting: pallet_root_testing, - ConvictionVoting: pallet_conviction_voting, - Whitelist: pallet_whitelist, - AllianceMotion: pallet_collective::, - Alliance: pallet_alliance, - NominationPools: pallet_nomination_pools, - RankedPolls: pallet_referenda::, - RankedCollective: pallet_ranked_collective, - AssetConversion: pallet_asset_conversion, - FastUnstake: pallet_fast_unstake, - MessageQueue: pallet_message_queue, - Pov: frame_benchmarking_pallet_pov, - TxPause: pallet_tx_pause, - SafeMode: pallet_safe_mode, - Statement: pallet_statement, - Broker: pallet_broker, - Mixnet: pallet_mixnet, +/// Dynamic parameters that can be changed at runtime through the +/// `pallet_parameters::set_parameter`. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod storage { + /// Configures the base deposit of storing some data. + #[codec(index = 0)] + pub static BaseDeposit: Balance = 1 * DOLLARS; + + /// Configures the per-byte deposit of storing some data. + #[codec(index = 1)] + pub static ByteDeposit: Balance = 1 * CENTS; } -); + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod contracts { + #[codec(index = 0)] + pub static DepositPerItem: Balance = deposit(1, 0); + + #[codec(index = 1)] + pub static DepositPerByte: Balance = deposit(0, 1); + + #[codec(index = 2)] + pub static DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Storage(dynamic_params::storage::Parameters::BaseDeposit( + dynamic_params::storage::BaseDeposit, + Some(1 * DOLLARS), + )) + } +} + +pub struct DynamicParametersManagerOrigin; +impl EnsureOriginWithArg for DynamicParametersManagerOrigin { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + match key { + RuntimeParametersKey::Storage(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + RuntimeParametersKey::Contract(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::root()) + } +} + +impl pallet_parameters::Config for Runtime { + type RuntimeParameters = RuntimeParameters; + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = DynamicParametersManagerOrigin; + type WeightInfo = (); +} + +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + + #[runtime::pallet_index(1)] + pub type Utility = pallet_utility; + + #[runtime::pallet_index(2)] + pub type Babe = pallet_babe; + + #[runtime::pallet_index(3)] + pub type Timestamp = pallet_timestamp; + + // Authorship must be before session in order to note author in the correct session and era + // for im-online and staking. + #[runtime::pallet_index(4)] + pub type Authorship = pallet_authorship; + + #[runtime::pallet_index(5)] + pub type Indices = pallet_indices; + + #[runtime::pallet_index(6)] + pub type Balances = pallet_balances; + + #[runtime::pallet_index(7)] + pub type TransactionPayment = pallet_transaction_payment; + + #[runtime::pallet_index(8)] + pub type AssetTxPayment = pallet_asset_tx_payment; + + #[runtime::pallet_index(9)] + pub type AssetConversionTxPayment = pallet_asset_conversion_tx_payment; + + #[runtime::pallet_index(10)] + pub type ElectionProviderMultiPhase = pallet_election_provider_multi_phase; + + #[runtime::pallet_index(11)] + pub type Staking = pallet_staking; + + #[runtime::pallet_index(12)] + pub type Session = pallet_session; + + #[runtime::pallet_index(13)] + pub type Democracy = pallet_democracy; + + #[runtime::pallet_index(14)] + pub type Council = pallet_collective; + + #[runtime::pallet_index(15)] + pub type TechnicalCommittee = pallet_collective; + + #[runtime::pallet_index(16)] + pub type Elections = pallet_elections_phragmen; + + #[runtime::pallet_index(17)] + pub type TechnicalMembership = pallet_membership; + + #[runtime::pallet_index(18)] + pub type Grandpa = pallet_grandpa; + + #[runtime::pallet_index(19)] + pub type Treasury = pallet_treasury; + + #[runtime::pallet_index(20)] + pub type AssetRate = pallet_asset_rate; + + #[runtime::pallet_index(21)] + pub type Contracts = pallet_contracts; + + #[runtime::pallet_index(22)] + pub type Sudo = pallet_sudo; + + #[runtime::pallet_index(23)] + pub type ImOnline = pallet_im_online; + + #[runtime::pallet_index(24)] + pub type AuthorityDiscovery = pallet_authority_discovery; + + #[runtime::pallet_index(25)] + pub type Offences = pallet_offences; + + #[runtime::pallet_index(26)] + pub type Historical = pallet_session_historical; + + #[runtime::pallet_index(27)] + pub type RandomnessCollectiveFlip = pallet_insecure_randomness_collective_flip; + + #[runtime::pallet_index(28)] + pub type Identity = pallet_identity; + + #[runtime::pallet_index(29)] + pub type Society = pallet_society; + + #[runtime::pallet_index(30)] + pub type Recovery = pallet_recovery; + + #[runtime::pallet_index(31)] + pub type Vesting = pallet_vesting; + + #[runtime::pallet_index(32)] + pub type Scheduler = pallet_scheduler; + + #[runtime::pallet_index(33)] + pub type Glutton = pallet_glutton; + + #[runtime::pallet_index(34)] + pub type Preimage = pallet_preimage; + + #[runtime::pallet_index(35)] + pub type Proxy = pallet_proxy; + + #[runtime::pallet_index(36)] + pub type Multisig = pallet_multisig; + + #[runtime::pallet_index(37)] + pub type Bounties = pallet_bounties; + + #[runtime::pallet_index(38)] + pub type Tips = pallet_tips; + + #[runtime::pallet_index(39)] + pub type Assets = pallet_assets; + + #[runtime::pallet_index(40)] + pub type PoolAssets = pallet_assets; + + #[runtime::pallet_index(41)] + pub type Beefy = pallet_beefy; + + // MMR leaf construction must be after session in order to have a leaf's next_auth_set + // refer to block. See issue polkadot-fellows/runtimes#160 for details. + #[runtime::pallet_index(42)] + pub type Mmr = pallet_mmr; + + #[runtime::pallet_index(43)] + pub type MmrLeaf = pallet_beefy_mmr; + + #[runtime::pallet_index(44)] + pub type Lottery = pallet_lottery; + + #[runtime::pallet_index(45)] + pub type Nis = pallet_nis; + + #[runtime::pallet_index(46)] + pub type Uniques = pallet_uniques; + + #[runtime::pallet_index(47)] + pub type Nfts = pallet_nfts; + + #[runtime::pallet_index(48)] + pub type NftFractionalization = pallet_nft_fractionalization; + + #[runtime::pallet_index(49)] + pub type Salary = pallet_salary; + + #[runtime::pallet_index(50)] + pub type CoreFellowship = pallet_core_fellowship; + + #[runtime::pallet_index(51)] + pub type TransactionStorage = pallet_transaction_storage; + + #[runtime::pallet_index(52)] + pub type VoterList = pallet_bags_list; + + #[runtime::pallet_index(53)] + pub type StateTrieMigration = pallet_state_trie_migration; + + #[runtime::pallet_index(54)] + pub type ChildBounties = pallet_child_bounties; + + #[runtime::pallet_index(55)] + pub type Referenda = pallet_referenda; + + #[runtime::pallet_index(56)] + pub type Remark = pallet_remark; + + #[runtime::pallet_index(57)] + pub type RootTesting = pallet_root_testing; + + #[runtime::pallet_index(58)] + pub type ConvictionVoting = pallet_conviction_voting; + + #[runtime::pallet_index(59)] + pub type Whitelist = pallet_whitelist; + + #[runtime::pallet_index(60)] + pub type AllianceMotion = pallet_collective; + + #[runtime::pallet_index(61)] + pub type Alliance = pallet_alliance; + + #[runtime::pallet_index(62)] + pub type NominationPools = pallet_nomination_pools; + + #[runtime::pallet_index(63)] + pub type RankedPolls = pallet_referenda; + + #[runtime::pallet_index(64)] + pub type RankedCollective = pallet_ranked_collective; + + #[runtime::pallet_index(65)] + pub type AssetConversion = pallet_asset_conversion; + + #[runtime::pallet_index(66)] + pub type FastUnstake = pallet_fast_unstake; + + #[runtime::pallet_index(67)] + pub type MessageQueue = pallet_message_queue; + + #[runtime::pallet_index(68)] + pub type Pov = frame_benchmarking_pallet_pov; + + #[runtime::pallet_index(69)] + pub type TxPause = pallet_tx_pause; + + #[runtime::pallet_index(70)] + pub type SafeMode = pallet_safe_mode; + + #[runtime::pallet_index(71)] + pub type Statement = pallet_statement; + + #[runtime::pallet_index(72)] + pub type MultiBlockMigrations = pallet_migrations; + + #[runtime::pallet_index(73)] + pub type Broker = pallet_broker; + + #[runtime::pallet_index(74)] + pub type TasksExample = pallet_example_tasks; + + #[runtime::pallet_index(75)] + pub type Mixnet = pallet_mixnet; + + #[runtime::pallet_index(76)] + pub type Parameters = pallet_parameters; + + #[runtime::pallet_index(77)] + pub type SkipFeelessPayment = pallet_skip_feeless_payment; +} /// The address format for describing accounts. pub type Address = sp_runtime::MultiAddress; @@ -2157,7 +2465,10 @@ pub type SignedExtra = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, + pallet_skip_feeless_payment::SkipCheckIfFeeless< + Runtime, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, + >, ); /// Unchecked extrinsic type as expected by this runtime. @@ -2177,12 +2488,17 @@ pub type Executive = frame_executive::Executive< Migrations, >; +// We don't have a limit in the Relay Chain. +const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX; + // All migrations executed on runtime upgrade as a nested tuple of types implementing -// `OnRuntimeUpgrade`. +// `OnRuntimeUpgrade`. Note: These are examples and do not need to be run directly +// after the genesis block. type Migrations = ( - pallet_nomination_pools::migration::v2::MigrateToV2, + pallet_nomination_pools::migration::versioned::V6ToV7, pallet_alliance::migration::Migration, pallet_contracts::Migration, + pallet_identity::migration::versioned::V0ToV1, ); type EventRecord = frame_system::EventRecord< @@ -2190,6 +2506,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; @@ -2217,6 +2549,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] @@ -2224,6 +2557,7 @@ mod benches { [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] [pallet_nis, Nis] + [pallet_parameters, Parameters] [pallet_grandpa, Grandpa] [pallet_identity, Identity] [pallet_im_online, ImOnline] @@ -2231,6 +2565,7 @@ mod benches { [pallet_lottery, Lottery] [pallet_membership, TechnicalMembership] [pallet_message_queue, MessageQueue] + [pallet_migrations, MultiBlockMigrations] [pallet_mmr, Mmr] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] @@ -2276,7 +2611,7 @@ impl_runtime_apis! { Executive::execute_block(block); } - fn initialize_block(header: &::Header) { + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } @@ -2388,10 +2723,14 @@ impl_runtime_apis! { } } - impl pallet_staking_runtime_api::StakingApi for Runtime { + impl pallet_staking_runtime_api::StakingApi for Runtime { fn nominations_quota(balance: Balance) -> u32 { Staking::api_nominations_quota(balance) } + + fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page { + Staking::api_eras_stakers_page_count(era, account) + } } impl sp_consensus_babe::BabeApi for Runtime { @@ -2476,7 +2815,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_contracts_primitives::ContractExecResult { + ) -> pallet_contracts::ContractExecResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_call( origin, @@ -2496,10 +2835,10 @@ impl_runtime_apis! { value: Balance, gas_limit: Option, storage_deposit_limit: Option, - code: pallet_contracts_primitives::Code, + code: pallet_contracts::Code, data: Vec, salt: Vec, - ) -> pallet_contracts_primitives::ContractInstantiateResult + ) -> pallet_contracts::ContractInstantiateResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_instantiate( @@ -2520,7 +2859,7 @@ impl_runtime_apis! { code: Vec, storage_deposit_limit: Option, determinism: pallet_contracts::Determinism, - ) -> pallet_contracts_primitives::CodeUploadResult + ) -> pallet_contracts::CodeUploadResult { Contracts::bare_upload_code( origin, @@ -2533,7 +2872,7 @@ impl_runtime_apis! { fn get_storage( address: AccountId, key: Vec, - ) -> pallet_contracts_primitives::GetStorageResult { + ) -> pallet_contracts::GetStorageResult { Contracts::get_storage( address, key @@ -2562,20 +2901,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() } } @@ -2629,10 +2967,10 @@ impl_runtime_apis! { fn system_attribute( collection: u32, - item: u32, + item: Option, key: Vec, ) -> Option> { - >::system_attribute(&collection, &item, &key) + >::system_attribute(&collection, item.as_ref(), &key) } fn collection_attribute(collection: u32, key: Vec) -> Option> { @@ -2640,6 +2978,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 f5a39693301ce6443e9bef6ba20edf5183357b6c..31f8689d46ca30546482d438938c09457a433bf5 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"] @@ -16,33 +19,32 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } fs_extra = "1" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } tempfile = "3.1.0" frame-system = { path = "../../../frame/system" } -node-executor = { path = "../executor" } +node-cli = { package = "staging-node-cli", path = "../cli" } node-primitives = { path = "../primitives" } kitchensink-runtime = { path = "../runtime" } pallet-asset-conversion = { path = "../../../frame/asset-conversion" } pallet-assets = { path = "../../../frame/assets" } pallet-asset-conversion-tx-payment = { path = "../../../frame/transaction-payment/asset-conversion-tx-payment" } pallet-asset-tx-payment = { path = "../../../frame/transaction-payment/asset-tx-payment" } +pallet-skip-feeless-payment = { path = "../../../frame/transaction-payment/skip-feeless-payment" } sc-block-builder = { path = "../../../client/block-builder" } sc-client-api = { path = "../../../client/api" } -sc-client-db = { path = "../../../client/db", features = ["rocksdb"]} +sc-client-db = { path = "../../../client/db", features = ["rocksdb"] } sc-consensus = { path = "../../../client/consensus/common" } sc-executor = { path = "../../../client/executor" } -sc-service = { path = "../../../client/service", features = [ - "test-helpers", - "rocksdb", -]} +sc-service = { path = "../../../client/service", features = ["rocksdb", "test-helpers"] } sp-api = { path = "../../../primitives/api" } sp-block-builder = { path = "../../../primitives/block-builder" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-consensus = { path = "../../../primitives/consensus/common" } sp-core = { path = "../../../primitives/core" } +sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-inherents = { path = "../../../primitives/inherents" } sp-io = { path = "../../../primitives/io" } sp-keyring = { path = "../../../primitives/keyring" } sp-runtime = { path = "../../../primitives/runtime" } -sp-timestamp = { path = "../../../primitives/timestamp", default-features = false} +sp-timestamp = { path = "../../../primitives/timestamp", default-features = false } substrate-test-client = { path = "../../../test-utils/client" } diff --git a/substrate/bin/node/testing/src/bench.rs b/substrate/bin/node/testing/src/bench.rs index f1ab2212239b1937361af8941c89e254c6485a84..df302a6453b9ffbef603afc36fc860a1535a7ca2 100644 --- a/substrate/bin/node/testing/src/bench.rs +++ b/substrate/bin/node/testing/src/bench.rs @@ -39,15 +39,16 @@ use kitchensink_runtime::{ RuntimeCall, Signature, SystemCall, UncheckedExtrinsic, }; use node_primitives::Block; -use sc_block_builder::BlockBuilderProvider; -use sc_client_api::execution_extensions::ExecutionExtensions; +use sc_block_builder::BlockBuilderBuilder; +use sc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider}; use sc_client_db::PruningMode; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux}; -use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod, WasmtimeInstantiationStrategy}; +use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy}; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_consensus::BlockOrigin; -use sp_core::{blake2_256, ed25519, sr25519, traits::SpawnNamed, Pair, Public}; +use sp_core::{ed25519, sr25519, traits::SpawnNamed, Pair, Public}; +use sp_crypto_hashing::blake2_256; use sp_inherents::InherentData; use sp_runtime::{ traits::{Block as BlockT, IdentifyAccount, Verify}, @@ -388,17 +389,15 @@ impl BenchDb { let task_executor = TaskExecutor::new(); let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); - let executor = NativeElseWasmExecutor::new_with_wasm_executor( - sc_executor::WasmExecutor::builder() - .with_execution_method(WasmExecutionMethod::Compiled { - instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - }) - .build(), - ); + let executor = sc_executor::WasmExecutor::builder() + .with_execution_method(WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }) + .build(); let client_config = sc_service::ClientConfig::default(); let genesis_block_builder = sc_service::GenesisBlockBuilder::new( - &keyring.generate_genesis(), + keyring.as_storage_builder(), !client_config.no_genesis, backend.clone(), executor.clone(), @@ -455,8 +454,13 @@ impl BenchDb { /// Generate new block using this database. pub fn generate_block(&mut self, content: BlockContent) -> Block { let client = self.client(); + let chain = client.usage_info().chain; - let mut block = client.new_block(Default::default()).expect("Block creation failed"); + let mut block = BlockBuilderBuilder::new(&client) + .on_parent_block(chain.best_hash) + .with_parent_block_number(chain.best_number) + .build() + .expect("Failed to create block builder."); for extrinsic in self.generate_inherents(&client) { block.push(extrinsic).expect("Push inherent failed"); @@ -571,7 +575,7 @@ impl BenchKeyring { let key = self.accounts.get(&signed).expect("Account id not found in keyring"); let signature = payload.using_encoded(|b| { if b.len() > 256 { - key.sign(&sp_io::hashing::blake2_256(b)) + key.sign(&blake2_256(b)) } else { key.sign(b) } @@ -585,12 +589,20 @@ impl BenchKeyring { } } - /// Generate genesis with accounts from this keyring endowed with some balance. - pub fn generate_genesis(&self) -> kitchensink_runtime::RuntimeGenesisConfig { - crate::genesis::config_endowed( - Some(kitchensink_runtime::wasm_binary_unwrap()), - self.collect_account_ids(), - ) + /// Generate genesis with accounts from this keyring endowed with some balance and + /// kitchensink_runtime code blob. + pub fn as_storage_builder(&self) -> &dyn sp_runtime::BuildStorage { + self + } +} + +impl sp_runtime::BuildStorage for BenchKeyring { + fn assimilate_storage(&self, storage: &mut sp_core::storage::Storage) -> Result<(), String> { + storage.top.insert( + sp_core::storage::well_known_keys::CODE.to_vec(), + kitchensink_runtime::wasm_binary_unwrap().into(), + ); + crate::genesis::config_endowed(self.collect_account_ids()).assimilate_storage(storage) } } diff --git a/substrate/bin/node/testing/src/client.rs b/substrate/bin/node/testing/src/client.rs index c55867360bd62c7e225f6fcddfb333f259bdb19f..07ba1cdbbfb56d6c13dbd76d61703bb347d19a6f 100644 --- a/substrate/bin/node/testing/src/client.rs +++ b/substrate/bin/node/testing/src/client.rs @@ -23,7 +23,7 @@ use sp_runtime::BuildStorage; pub use substrate_test_client::*; /// Call executor for `kitchensink-runtime` `TestClient`. -pub type ExecutorDispatch = sc_executor::NativeElseWasmExecutor; +use node_cli::service::RuntimeExecutor; /// Default backend type. pub type Backend = sc_client_db::Backend; @@ -31,7 +31,7 @@ pub type Backend = sc_client_db::Backend; /// Test client type. pub type Client = client::Client< Backend, - client::LocalCallExecutor, + client::LocalCallExecutor, node_primitives::Block, kitchensink_runtime::RuntimeApi, >; @@ -42,7 +42,12 @@ pub struct GenesisParameters; impl substrate_test_client::GenesisInit for GenesisParameters { fn genesis_storage(&self) -> Storage { - crate::genesis::config(None).build_storage().unwrap() + let mut storage = crate::genesis::config().build_storage().unwrap(); + storage.top.insert( + sp_core::storage::well_known_keys::CODE.to_vec(), + kitchensink_runtime::wasm_binary_unwrap().into(), + ); + storage } } @@ -58,7 +63,7 @@ pub trait TestClientBuilderExt: Sized { impl TestClientBuilderExt for substrate_test_client::TestClientBuilder< node_primitives::Block, - client::LocalCallExecutor, + client::LocalCallExecutor, Backend, GenesisParameters, > @@ -66,8 +71,17 @@ impl TestClientBuilderExt fn new() -> Self { Self::default() } - fn build(self) -> Client { - self.build_with_native_executor(None).0 + let executor = RuntimeExecutor::builder().build(); + use sc_service::client::LocalCallExecutor; + use std::sync::Arc; + let executor = LocalCallExecutor::new( + self.backend().clone(), + executor.clone(), + Default::default(), + ExecutionExtensions::new(None, Arc::new(executor)), + ) + .expect("Creates LocalCallExecutor"); + self.build_with_executor(executor).0 } } diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index ab5311751a55fe39b994f8131e9131bdd3ae2227..c79612d68444c8bd64ad18c3b0a74ceed176eff8 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -20,22 +20,20 @@ use crate::keyring::*; use kitchensink_runtime::{ - constants::currency::*, wasm_binary_unwrap, AccountId, AssetsConfig, BabeConfig, - BalancesConfig, GluttonConfig, GrandpaConfig, IndicesConfig, RuntimeGenesisConfig, - SessionConfig, SocietyConfig, StakerStatus, StakingConfig, SystemConfig, - BABE_GENESIS_EPOCH_CONFIG, + constants::currency::*, AccountId, AssetsConfig, BalancesConfig, IndicesConfig, + RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, }; -use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; +use sp_keyring::Ed25519Keyring; use sp_runtime::Perbill; /// Create genesis runtime configuration for tests. -pub fn config(code: Option<&[u8]>) -> RuntimeGenesisConfig { - config_endowed(code, Default::default()) +pub fn config() -> RuntimeGenesisConfig { + config_endowed(Default::default()) } /// Create genesis runtime configuration for tests with some extra /// endowed accounts. -pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> RuntimeGenesisConfig { +pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { let mut endowed = vec![ (alice(), 111 * DOLLARS), (bob(), 100 * DOLLARS), @@ -48,21 +46,13 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Run endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); RuntimeGenesisConfig { - system: SystemConfig { - code: code.map(|x| x.to_vec()).unwrap_or_else(|| wasm_binary_unwrap().to_vec()), - ..Default::default() - }, indices: IndicesConfig { indices: vec![] }, balances: BalancesConfig { balances: endowed }, 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 { @@ -77,38 +67,8 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Run invulnerables: vec![alice(), bob(), charlie()], ..Default::default() }, - babe: BabeConfig { - authorities: vec![], - epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - grandpa: GrandpaConfig { authorities: vec![], _config: Default::default() }, - im_online: Default::default(), - authority_discovery: Default::default(), - democracy: Default::default(), - council: Default::default(), - technical_committee: Default::default(), - technical_membership: Default::default(), - elections: Default::default(), - sudo: Default::default(), - treasury: Default::default(), society: SocietyConfig { pot: 0 }, - vesting: Default::default(), assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, - pool_assets: Default::default(), - transaction_storage: Default::default(), - transaction_payment: Default::default(), - alliance: Default::default(), - alliance_motion: Default::default(), - nomination_pools: Default::default(), - safe_mode: Default::default(), - tx_pause: Default::default(), - glutton: GluttonConfig { - compute: Default::default(), - storage: Default::default(), - trash_data_count: Default::default(), - ..Default::default() - }, - mixnet: Default::default(), + ..Default::default() } } diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index 22a8f5deb19f737537227ae21b4c0c20770f7759..f712191bed695031275cfb11c5e22c8fa2a26f78 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -20,8 +20,11 @@ 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_crypto_hashing::blake2_256; +use sp_keyring::AccountKeyring; use sp_runtime::generic::Era; /// Alice's account id. @@ -55,16 +58,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(), } } @@ -78,7 +79,9 @@ pub fn signed_extra(nonce: Nonce, extra_fee: Balance) -> SignedExtra { frame_system::CheckEra::from(Era::mortal(256, 0)), frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), + pallet_skip_feeless_payment::SkipCheckIfFeeless::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), + ), ) } @@ -94,15 +97,16 @@ pub fn sign( let payload = (xt.function, extra.clone(), spec_version, tx_version, genesis_hash, genesis_hash); let key = AccountKeyring::from_account_id(&signed).unwrap(); - let signature = payload - .using_encoded(|b| { - if b.len() > 256 { - key.sign(&sp_io::hashing::blake2_256(b)) - } else { - key.sign(b) - } - }) - .into(); + let signature = + payload + .using_encoded(|b| { + if b.len() > 256 { + key.sign(&blake2_256(b)) + } else { + key.sign(b) + } + }) + .into(); UncheckedExtrinsic { signature: Some((sp_runtime::MultiAddress::Id(signed), signature, extra)), function: payload.0, diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index c7690faf7d065dd941f71aaf7f74d0166f8ee7ce..c126a76b763f28cbc29b844b0435d9d68858f283 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "chain-spec-builder" +name = "staging-chain-spec-builder" version = "2.0.0" authors.workspace = true edition.workspace = true @@ -7,9 +7,11 @@ build = "build.rs" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository.workspace = true -readme = "README.md" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,11 +23,8 @@ name = "chain-spec-builder" crate-type = ["rlib"] [dependencies] -ansi_term = "0.12.1" -clap = { version = "4.4.6", features = ["derive"] } -rand = "0.8" -node-cli = { path = "../../node/cli" } +clap = { version = "4.5.3", features = ["derive"] } +log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } -sc-keystore = { path = "../../../client/keystore" } -sp-core = { path = "../../../primitives/core" } -sp-keystore = { path = "../../../primitives/keystore" } +serde_json = { workspace = true, default-features = true } +sp-tracing = { path = "../../../primitives/tracing" } diff --git a/substrate/bin/utils/chain-spec-builder/bin/main.rs b/substrate/bin/utils/chain-spec-builder/bin/main.rs index 53e11abbf628206a78590696f6d376416b9d9ddf..986293179a91540a2b020aeb39d9285fefef4453 100644 --- a/substrate/bin/utils/chain-spec-builder/bin/main.rs +++ b/substrate/bin/utils/chain-spec-builder/bin/main.rs @@ -17,73 +17,60 @@ // along with this program. If not, see . use chain_spec_builder::{ - generate_authority_keys_and_store, generate_chain_spec, print_seeds, ChainSpecBuilder, + generate_chain_spec_for_runtime, ChainSpecBuilder, ChainSpecBuilderCmd, ConvertToRawCmd, + UpdateCodeCmd, VerifyCmd, }; use clap::Parser; -use node_cli::chain_spec; -use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; -use sp_core::{crypto::Ss58Codec, sr25519}; +use sc_chain_spec::{update_code_in_json_chain_spec, GenericChainSpec}; +use staging_chain_spec_builder as chain_spec_builder; use std::fs; fn main() -> Result<(), String> { - #[cfg(build_type = "debug")] - println!( - "The chain spec builder builds a chain specification that includes a Substrate runtime \ - compiled as WASM. To ensure proper functioning of the included runtime compile (or run) \ - the chain spec builder binary in `--release` mode.\n", - ); + sp_tracing::try_init_simple(); let builder = ChainSpecBuilder::parse(); - let chain_spec_path = builder.chain_spec_path().to_path_buf(); + let chain_spec_path = builder.chain_spec_path.to_path_buf(); - let (authority_seeds, nominator_accounts, endowed_accounts, sudo_account) = match builder { - ChainSpecBuilder::Generate { authorities, nominators, endowed, keystore_path, .. } => { - let authorities = authorities.max(1); - let rand_str = || -> String { - OsRng.sample_iter(&Alphanumeric).take(32).map(char::from).collect() - }; - - let authority_seeds = (0..authorities).map(|_| rand_str()).collect::>(); - let nominator_seeds = (0..nominators).map(|_| rand_str()).collect::>(); - let endowed_seeds = (0..endowed).map(|_| rand_str()).collect::>(); - let sudo_seed = rand_str(); - - print_seeds(&authority_seeds, &nominator_seeds, &endowed_seeds, &sudo_seed); - - if let Some(keystore_path) = keystore_path { - generate_authority_keys_and_store(&authority_seeds, &keystore_path)?; - } - - let nominator_accounts = nominator_seeds - .into_iter() - .map(|seed| { - chain_spec::get_account_id_from_seed::(&seed).to_ss58check() - }) - .collect(); - - let endowed_accounts = endowed_seeds - .into_iter() - .map(|seed| { - chain_spec::get_account_id_from_seed::(&seed).to_ss58check() - }) - .collect(); + match builder.command { + ChainSpecBuilderCmd::Create(cmd) => { + let chain_spec_json = generate_chain_spec_for_runtime(&cmd)?; + fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?; + }, + ChainSpecBuilderCmd::UpdateCode(UpdateCodeCmd { + ref input_chain_spec, + ref runtime_wasm_path, + }) => { + let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?; + + let mut chain_spec_json = + serde_json::from_str::(&chain_spec.as_json(false)?) + .map_err(|e| format!("Conversion to json failed: {e}"))?; + update_code_in_json_chain_spec( + &mut chain_spec_json, + &fs::read(runtime_wasm_path.as_path()) + .map_err(|e| format!("Wasm blob file could not be read: {e}"))?[..], + ); + + let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json) + .map_err(|e| format!("to pretty failed: {e}"))?; + fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?; + }, + ChainSpecBuilderCmd::ConvertToRaw(ConvertToRawCmd { ref input_chain_spec }) => { + let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?; - let sudo_account = - chain_spec::get_account_id_from_seed::(&sudo_seed).to_ss58check(); + let chain_spec_json = + serde_json::from_str::(&chain_spec.as_json(true)?) + .map_err(|e| format!("Conversion to json failed: {e}"))?; - (authority_seeds, nominator_accounts, endowed_accounts, sudo_account) + let chain_spec_json = serde_json::to_string_pretty(&chain_spec_json) + .map_err(|e| format!("Conversion to pretty failed: {e}"))?; + fs::write(chain_spec_path, chain_spec_json).map_err(|err| err.to_string())?; + }, + ChainSpecBuilderCmd::Verify(VerifyCmd { ref input_chain_spec }) => { + let chain_spec = GenericChainSpec::<()>::from_json_file(input_chain_spec.clone())?; + let _ = serde_json::from_str::(&chain_spec.as_json(true)?) + .map_err(|e| format!("Conversion to json failed: {e}"))?; }, - ChainSpecBuilder::New { - authority_seeds, - nominator_accounts, - endowed_accounts, - sudo_account, - .. - } => (authority_seeds, nominator_accounts, endowed_accounts, sudo_account), }; - - let json = - generate_chain_spec(authority_seeds, nominator_accounts, endowed_accounts, sudo_account)?; - - fs::write(chain_spec_path, json).map_err(|err| err.to_string()) + Ok(()) } diff --git a/substrate/bin/utils/chain-spec-builder/src/lib.rs b/substrate/bin/utils/chain-spec-builder/src/lib.rs index 2b88e40ef74fd804a086f83b2903572aef373904..8c78030c885484b7d1f9ba3213dbfa76faa36c52 100644 --- a/substrate/bin/utils/chain-spec-builder/src/lib.rs +++ b/substrate/bin/utils/chain-spec-builder/src/lib.rs @@ -21,223 +21,226 @@ //! A chain-spec is short for `chain-configuration`. See the [`sc-chain-spec`] for more information. //! //! Note that this binary is analogous to the `build-spec` subcommand, contained in typical -//! substrate-based nodes. This particular binary is capable of building a more sophisticated chain -//! specification that can be used with the substrate-node, ie. [`node-cli`]. +//! substrate-based nodes. This particular binary is capable of interacting with +//! [`sp-genesis-builder`] implementation of any provided runtime allowing to build chain-spec JSON +//! files. //! -//! See [`ChainSpecBuilder`] for a list of available commands. +//! See [`ChainSpecBuilderCmd`] for a list of available commands. +//! +//! ## 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. 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. +//! +//! +//! ##### 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: +//! +//! ```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: +//! +//! ```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. +//! ```bash +//! chain-spec-builder create -r runtime.wasm patch patch.json +//! ``` +//! +//! ##### Generate human readable chain spec using provided full genesis config. +//! +//! ```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`]. //! //! [`sc-chain-spec`]: ../sc_chain_spec/index.html //! [`node-cli`]: ../node_cli/index.html +//! [`sp-genesis-builder`]: ../sp_genesis_builder/index.html +//! [sp-genesis-builder-create]: ../sp_genesis_builder/trait.GenesisBuilder.html#method.create_default_config +//! [sp-genesis-builder-build]: ../sp_genesis_builder/trait.GenesisBuilder.html#method.build_config -use std::path::{Path, PathBuf}; - -use ansi_term::Style; -use clap::Parser; +use std::{fs, path::PathBuf}; -use node_cli::chain_spec::{self, AccountId}; -use sc_keystore::LocalKeystore; -use sp_core::crypto::{ByteArray, Ss58Codec}; -use sp_keystore::KeystorePtr; +use clap::{Parser, Subcommand}; +use sc_chain_spec::{GenericChainSpec, GenesisConfigBuilderRuntimeCaller}; +use serde_json::Value; -/// A utility to easily create a testnet chain spec definition with a given set -/// of authorities and endowed accounts and/or generate random accounts. -#[derive(Parser)] +/// A utility to easily create a chain spec definition. +#[derive(Debug, Parser)] #[command(rename_all = "kebab-case")] -pub enum ChainSpecBuilder { - /// Create a new chain spec with the given authorities, endowed and sudo - /// accounts. - New { - /// Authority key seed. - #[arg(long, short, required = true)] - authority_seeds: Vec, - /// Active nominators (SS58 format), each backing a random subset of the aforementioned - /// authorities. - #[arg(long, short, default_value = "0")] - nominator_accounts: Vec, - /// Endowed account address (SS58 format). - #[arg(long, short)] - endowed_accounts: Vec, - /// Sudo account address (SS58 format). - #[arg(long, short)] - sudo_account: String, - /// The path where the chain spec should be saved. - #[arg(long, short, default_value = "./chain_spec.json")] - chain_spec_path: PathBuf, - }, - /// Create a new chain spec with the given number of authorities and endowed - /// accounts. Random keys will be generated as required. - Generate { - /// The number of authorities. - #[arg(long, short)] - authorities: usize, - /// The number of nominators backing the aforementioned authorities. - /// - /// Will nominate a random subset of `authorities`. - #[arg(long, short, default_value_t = 0)] - nominators: usize, - /// The number of endowed accounts. - #[arg(long, short, default_value_t = 0)] - endowed: usize, - /// The path where the chain spec should be saved. - #[arg(long, short, default_value = "./chain_spec.json")] - chain_spec_path: PathBuf, - /// Path to use when saving generated keystores for each authority. - /// - /// At this path, a new folder will be created for each authority's - /// keystore named `auth-$i` where `i` is the authority index, i.e. - /// `auth-0`, `auth-1`, etc. - #[arg(long, short)] - keystore_path: Option, - }, +pub struct ChainSpecBuilder { + #[command(subcommand)] + pub command: ChainSpecBuilderCmd, + /// The path where the chain spec should be saved. + #[arg(long, short, default_value = "./chain_spec.json")] + pub chain_spec_path: PathBuf, } -impl ChainSpecBuilder { - /// Returns the path where the chain spec should be saved. - pub fn chain_spec_path(&self) -> &Path { - match self { - ChainSpecBuilder::New { chain_spec_path, .. } => chain_spec_path.as_path(), - ChainSpecBuilder::Generate { chain_spec_path, .. } => chain_spec_path.as_path(), - } - } +#[derive(Debug, Subcommand)] +#[command(rename_all = "kebab-case")] +pub enum ChainSpecBuilderCmd { + Create(CreateCmd), + Verify(VerifyCmd), + UpdateCode(UpdateCodeCmd), + ConvertToRaw(ConvertToRawCmd), } -fn genesis_constructor( - authority_seeds: &[String], - nominator_accounts: &[AccountId], - endowed_accounts: &[AccountId], - sudo_account: &AccountId, -) -> chain_spec::RuntimeGenesisConfig { - let authorities = authority_seeds - .iter() - .map(AsRef::as_ref) - .map(chain_spec::authority_keys_from_seed) - .collect::>(); - - chain_spec::testnet_genesis( - authorities, - nominator_accounts.to_vec(), - sudo_account.clone(), - Some(endowed_accounts.to_vec()), - ) +/// Create a new chain spec by interacting with the provided runtime wasm blob. +#[derive(Parser, Debug)] +pub struct CreateCmd { + /// The name of chain. + #[arg(long, short = 'n', default_value = "Custom")] + chain_name: String, + /// The chain id. + #[arg(long, short = 'i', default_value = "custom")] + chain_id: String, + /// The path to runtime wasm blob. + #[arg(long, short)] + runtime_wasm_path: PathBuf, + /// Export chainspec as raw storage. + #[arg(long, short = 's')] + raw_storage: bool, + /// Verify the genesis config. This silently generates the raw storage from genesis config. Any + /// errors will be reported. + #[arg(long, short = 'v')] + verify: bool, + #[command(subcommand)] + action: GenesisBuildAction, } -/// Generate the chain spec using the given seeds and accounts. -pub fn generate_chain_spec( - authority_seeds: Vec, - nominator_accounts: Vec, - endowed_accounts: Vec, - sudo_account: String, -) -> Result { - let parse_account = |address: String| { - AccountId::from_string(&address) - .map_err(|err| format!("Failed to parse account address: {:?}", err)) - }; - - let nominator_accounts = nominator_accounts - .into_iter() - .map(parse_account) - .collect::, String>>()?; - - let endowed_accounts = endowed_accounts - .into_iter() - .map(parse_account) - .collect::, String>>()?; - - let sudo_account = parse_account(sudo_account)?; - - let chain_spec = chain_spec::ChainSpec::from_genesis( - "Custom", - "custom", - sc_chain_spec::ChainType::Live, - move || { - genesis_constructor( - &authority_seeds, - &nominator_accounts, - &endowed_accounts, - &sudo_account, - ) - }, - vec![], - None, - None, - None, - None, - Default::default(), - ); - - chain_spec.as_json(false) +#[derive(Subcommand, Debug, Clone)] +enum GenesisBuildAction { + Patch(PatchCmd), + Full(FullCmd), + Default(DefaultCmd), } -/// Generate the authority keys and store them in the given `keystore_path`. -pub fn generate_authority_keys_and_store( - seeds: &[String], - keystore_path: &Path, -) -> Result<(), String> { - for (n, seed) in seeds.iter().enumerate() { - let keystore: KeystorePtr = - LocalKeystore::open(keystore_path.join(format!("auth-{}", n)), None) - .map_err(|err| err.to_string())? - .into(); - - let (_, _, grandpa, babe, im_online, authority_discovery, mixnet) = - chain_spec::authority_keys_from_seed(seed); - - let insert_key = |key_type, public| { - keystore - .insert(key_type, &format!("//{}", seed), public) - .map_err(|_| format!("Failed to insert key: {}", grandpa)) - }; - - insert_key(sp_core::crypto::key_types::BABE, babe.as_slice())?; - - insert_key(sp_core::crypto::key_types::GRANDPA, grandpa.as_slice())?; - - insert_key(sp_core::crypto::key_types::IM_ONLINE, im_online.as_slice())?; - - insert_key( - sp_core::crypto::key_types::AUTHORITY_DISCOVERY, - authority_discovery.as_slice(), - )?; - - insert_key(sp_core::crypto::key_types::MIXNET, mixnet.as_slice())?; - } - - Ok(()) +/// Patches the runtime's default genesis config with provided patch. +#[derive(Parser, Debug, Clone)] +struct PatchCmd { + /// The path to the runtime genesis config patch. + patch_path: PathBuf, } -/// Print the given seeds -pub fn print_seeds( - authority_seeds: &[String], - nominator_seeds: &[String], - endowed_seeds: &[String], - sudo_seed: &str, -) { - let header = Style::new().bold().underline(); - let entry = Style::new().bold(); +/// Build the genesis config for runtime using provided json file. No defaults will be used. +#[derive(Parser, Debug, Clone)] +struct FullCmd { + /// The path to the full runtime genesis config json file. + config_path: PathBuf, +} - println!("{}", header.paint("Authority seeds")); +/// Gets the default genesis config for the runtime and uses it in ChainSpec. Please note that +/// default genesis config may not be valid. For some runtimes initial values should be added there +/// (e.g. session keys, babe epoch). +#[derive(Parser, Debug, Clone)] +struct DefaultCmd { + /// If provided stores the default genesis config json file at given path (in addition to + /// chain-spec). + default_config_path: Option, +} - for (n, seed) in authority_seeds.iter().enumerate() { - println!("{} //{}", entry.paint(format!("auth-{}:", n)), seed); - } +/// Updates the code in the provided input chain spec. +/// +/// The code field of the chain spec will be updated with the runtime provided in the +/// command line. This operation supports both plain and raw formats. +#[derive(Parser, Debug, Clone)] +pub struct UpdateCodeCmd { + /// Chain spec to be updated. + pub input_chain_spec: PathBuf, + /// The path to new runtime wasm blob to be stored into chain-spec. + pub runtime_wasm_path: PathBuf, +} - println!("{}", header.paint("Nominator seeds")); +/// Converts the given chain spec into the raw format. +#[derive(Parser, Debug, Clone)] +pub struct ConvertToRawCmd { + /// Chain spec to be converted. + pub input_chain_spec: PathBuf, +} - for (n, seed) in nominator_seeds.iter().enumerate() { - println!("{} //{}", entry.paint(format!("nom-{}:", n)), seed); - } +/// Verifies the provided input chain spec. +/// +/// Silently checks if given input chain spec can be converted to raw. It allows to check if all +/// RuntimeGenesisConfig fiels are properly initialized and if the json does not contain invalid +/// fields. +#[derive(Parser, Debug, Clone)] +pub struct VerifyCmd { + /// Chain spec to be verified. + pub input_chain_spec: PathBuf, +} - println!(); +/// Processes `CreateCmd` and returns JSON version of `ChainSpec`. +pub fn generate_chain_spec_for_runtime(cmd: &CreateCmd) -> Result { + let code = fs::read(cmd.runtime_wasm_path.as_path()) + .map_err(|e| format!("wasm blob shall be readable {e}"))?; + + let builder = GenericChainSpec::<()>::builder(&code[..], Default::default()) + .with_name(&cmd.chain_name[..]) + .with_id(&cmd.chain_id[..]) + .with_chain_type(sc_chain_spec::ChainType::Live); + + let builder = match cmd.action { + GenesisBuildAction::Patch(PatchCmd { ref patch_path }) => { + let patch = fs::read(patch_path.as_path()) + .map_err(|e| format!("patch file {patch_path:?} shall be readable: {e}"))?; + builder.with_genesis_config_patch(serde_json::from_slice::(&patch[..]).map_err( + |e| format!("patch file {patch_path:?} shall contain a valid json: {e}"), + )?) + }, + GenesisBuildAction::Full(FullCmd { ref config_path }) => { + let config = fs::read(config_path.as_path()) + .map_err(|e| format!("config file {config_path:?} shall be readable: {e}"))?; + builder.with_genesis_config(serde_json::from_slice::(&config[..]).map_err( + |e| format!("config file {config_path:?} shall contain a valid json: {e}"), + )?) + }, + GenesisBuildAction::Default(DefaultCmd { ref default_config_path }) => { + let caller: GenesisConfigBuilderRuntimeCaller = + GenesisConfigBuilderRuntimeCaller::new(&code[..]); + let default_config = caller + .get_default_config() + .map_err(|e| format!("getting default config from runtime should work: {e}"))?; + default_config_path.clone().map(|path| { + fs::write(path.as_path(), serde_json::to_string_pretty(&default_config).unwrap()) + .map_err(|err| err.to_string()) + }); + builder.with_genesis_config(default_config) + }, + }; - if !endowed_seeds.is_empty() { - println!("{}", header.paint("Endowed seeds")); - for (n, seed) in endowed_seeds.iter().enumerate() { - println!("{} //{}", entry.paint(format!("endowed-{}:", n)), seed); - } + let chain_spec = builder.build(); - println!(); + match (cmd.verify, cmd.raw_storage) { + (_, true) => chain_spec.as_json(true), + (true, false) => { + chain_spec.as_json(true)?; + println!("Genesis config verification: OK"); + chain_spec.as_json(false) + }, + (false, false) => chain_spec.as_json(false), } - - println!("{}", header.paint("Sudo seed")); - println!("//{}", sudo_seed); } diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index 6606d8ac365f9f9e68805808ef6db62662fbeac5..8dc4bf254b2d44e1f6b5c96ca16a2e8586e0333c 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subkey" -version = "3.0.0" +version = "9.0.0" authors.workspace = true description = "Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing number of parachains and Substrate based projects." edition.workspace = true @@ -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.6", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/utils/subkey/README.md b/substrate/bin/utils/subkey/README.md index 60e5a9ca935008e81518263b2802b3eb1e90d636..a5f27cfd3707d6278ac9f78b727021a305a18457 100644 --- a/substrate/bin/utils/subkey/README.md +++ b/substrate/bin/utils/subkey/README.md @@ -74,7 +74,7 @@ The output above shows a **secret phrase** (also called **mnemonic phrase**) and **Private Key**). Those 2 secrets are the pieces of information you MUST keep safe and secret. All the other information below can be derived from those secrets. -The output above also show the **public key** and the **Account ID**. Those are the independant from the network where +The output above also show the **public key** and the **Account ID**. Those are the independent from the network where you will use the key. The **SS58 address** (or **Public Address**) of a new account is a reprensentation of the public keys of an account for @@ -91,7 +91,7 @@ SS58 addresses are: ### Json output -`subkey` can calso generate the output as *json*. This is useful for automation. +`subkey` can also generate the output as *json*. This is useful for automation. command: @@ -163,7 +163,7 @@ This time, we properly recovered `5He5pZpc7AJ8evPuab37vJF6KkFDqq9uDq2WXh877Qw6ia ### Inspecting a key -If you have *some data* about a key, `subkey inpsect` will help you discover more information about it. +If you have *some data* about a key, `subkey inspect` will help you discover more information about it. If you have **secrets** that you would like to verify for instance, you can use: diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index 31c714180ce57b0684ebfb7a8e27f7d2b42857ea..2c268b548ea9c32fabdc82226c77e82a7ef59cea 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-allocator" -version = "4.1.0-dev" +version = "23.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" @@ -10,11 +10,14 @@ 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"] [dependencies] -log = "0.4.17" -thiserror = "1.0.48" +log = { workspace = true, default-features = true } +thiserror = { workspace = true } sp-core = { path = "../../primitives/core" } sp-wasm-interface = { path = "../../primitives/wasm-interface" } diff --git a/substrate/client/allocator/src/lib.rs b/substrate/client/allocator/src/lib.rs index e50d7d54c8e97659a60a391d40561f58d13d4d13..70ed764bef8c166177137cf79f80825320ef3cdb 100644 --- a/substrate/client/allocator/src/lib.rs +++ b/substrate/client/allocator/src/lib.rs @@ -18,7 +18,7 @@ //! Collection of allocator implementations. //! //! This crate provides the following allocator implementations: -//! - A freeing-bump allocator: [`FreeingBumpHeapAllocator`](freeing_bump::FreeingBumpHeapAllocator) +//! - A freeing-bump allocator: [`FreeingBumpHeapAllocator`] #![warn(missing_docs)] diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 2b64c86038dda3ad5a37962c6dfc4a3230f7a4b2..cd7b613e277f3952304d70c3bf63c36d09432bc2 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-client-api" -version = "4.0.0-dev" +version = "28.0.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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"] @@ -19,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } fnv = "1.0.6" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-executor = { path = "../executor" } @@ -28,16 +31,16 @@ sc-utils = { path = "../utils" } sp-api = { path = "../../primitives/api" } sp-blockchain = { path = "../../primitives/blockchain" } sp-consensus = { path = "../../primitives/consensus/common" } -sp-core = { path = "../../primitives/core", default-features = false} +sp-core = { path = "../../primitives/core", default-features = false } sp-database = { path = "../../primitives/database" } sp-externalities = { path = "../../primitives/externalities" } -sp-runtime = { path = "../../primitives/runtime", default-features = false} +sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-state-machine = { path = "../../primitives/state-machine" } sp-statement-store = { path = "../../primitives/statement-store" } sp-storage = { path = "../../primitives/storage" } sp-trie = { path = "../../primitives/trie" } [dev-dependencies] -thiserror = "1.0.48" +thiserror = { workspace = true } sp-test-primitives = { path = "../../primitives/test-primitives" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/api/src/call_executor.rs b/substrate/client/api/src/call_executor.rs index 49b51ccc943edab005390a0726426f106e55b458..d131cbcec00262d8dc65616ab03c31c75d744972 100644 --- a/substrate/client/api/src/call_executor.rs +++ b/substrate/client/api/src/call_executor.rs @@ -21,12 +21,12 @@ use sc_executor::{RuntimeVersion, RuntimeVersionOf}; use sp_core::traits::CallContext; use sp_externalities::Extensions; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::traits::{Block as BlockT, HashingFor}; use sp_state_machine::{OverlayedChanges, StorageProof}; use std::cell::RefCell; use crate::execution_extensions::ExecutionExtensions; -use sp_api::{HashingFor, ProofRecorder}; +use sp_api::ProofRecorder; /// Executor Provider pub trait ExecutorProvider { diff --git a/substrate/client/api/src/client.rs b/substrate/client/api/src/client.rs index e334f2f9fb4f68aa55cb7ebcd7440f9462342fb9..2de09840e4dfdc15e1f1d1acaa5f3e438de0caca 100644 --- a/substrate/client/api/src/client.rs +++ b/substrate/client/api/src/client.rs @@ -25,7 +25,11 @@ use sp_runtime::{ traits::{Block as BlockT, NumberFor}, Justifications, }; -use std::{collections::HashSet, fmt, sync::Arc}; +use std::{ + collections::HashSet, + fmt::{self, Debug}, + sync::Arc, +}; use crate::{blockchain::Info, notifications::StorageEventStream, FinalizeSummary, ImportSummary}; @@ -271,18 +275,23 @@ impl fmt::Display for UsageInfo { } /// Sends a message to the pinning-worker once dropped to unpin a block in the backend. -#[derive(Debug)] pub struct UnpinHandleInner { /// Hash of the block pinned by this handle hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, +} + +impl Debug for UnpinHandleInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnpinHandleInner").field("pinned_block", &self.hash).finish() + } } impl UnpinHandleInner { /// Create a new [`UnpinHandleInner`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, unpin_worker_sender } } @@ -290,12 +299,25 @@ impl UnpinHandleInner { impl Drop for UnpinHandleInner { fn drop(&mut self) { - if let Err(err) = self.unpin_worker_sender.unbounded_send(self.hash) { + if let Err(err) = + self.unpin_worker_sender.unbounded_send(UnpinWorkerMessage::Unpin(self.hash)) + { log::debug!(target: "db", "Unable to unpin block with hash: {}, error: {:?}", self.hash, err); }; } } +/// Message that signals notification-based pinning actions to the pinning-worker. +/// +/// When the notification is dropped, an `Unpin` message should be sent to the worker. +#[derive(Debug)] +pub enum UnpinWorkerMessage { + /// Should be sent when a import or finality notification is created. + AnnouncePin(Block::Hash), + /// Should be sent when a import or finality notification is dropped. + Unpin(Block::Hash), +} + /// Keeps a specific block pinned while the handle is alive. /// Once the last handle instance for a given block is dropped, the /// block is unpinned in the [`Backend`](crate::backend::Backend::unpin_block). @@ -306,7 +328,7 @@ impl UnpinHandle { /// Create a new [`UnpinHandle`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> UnpinHandle { UnpinHandle(Arc::new(UnpinHandleInner::new(hash, unpin_worker_sender))) } @@ -344,7 +366,7 @@ impl BlockImportNotification { header: Block::Header, is_new_best: bool, tree_route: Option>>, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, @@ -403,7 +425,7 @@ impl FinalityNotification { /// Create finality notification from finality summary. pub fn from_summary( mut summary: FinalizeSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> FinalityNotification { let hash = summary.finalized.pop().unwrap_or_default(); FinalityNotification { @@ -427,7 +449,7 @@ impl BlockImportNotification { /// Create finality notification from finality summary. pub fn from_summary( summary: ImportSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> BlockImportNotification { let hash = summary.hash; BlockImportNotification { diff --git a/substrate/client/api/src/execution_extensions.rs b/substrate/client/api/src/execution_extensions.rs index 6f927105df0bf46927356e4b897c72c4d5e0e19b..26d3ae73f69f26c0923aa700397d09863003989e 100644 --- a/substrate/client/api/src/execution_extensions.rs +++ b/substrate/client/api/src/execution_extensions.rs @@ -91,7 +91,6 @@ impl ExtensionsFactory /// /// This crate aggregates extensions available for the offchain calls /// and is responsible for producing a correct `Extensions` object. -/// for each call, based on required `Capabilities`. pub struct ExecutionExtensions { extensions_factory: RwLock>>, read_runtime_version: Arc, @@ -116,8 +115,7 @@ impl ExecutionExtensions { *self.extensions_factory.write() = Box::new(maker); } - /// Based on the execution context and capabilities it produces - /// the extensions object to support desired set of APIs. + /// Produces default extensions based on the input parameters. pub fn extensions( &self, block_hash: Block::Hash, @@ -127,7 +125,6 @@ impl ExecutionExtensions { self.extensions_factory.read().extensions_for(block_hash, block_number); extensions.register(ReadRuntimeVersionExt::new(self.read_runtime_version.clone())); - extensions } } diff --git a/substrate/client/api/src/in_mem.rs b/substrate/client/api/src/in_mem.rs index 807bdf0e334725eba64d99eb4edb88215436c073..b933ed1f17e01a8822b958ac6a064fe2f3503926 100644 --- a/substrate/client/api/src/in_mem.rs +++ b/substrate/client/api/src/in_mem.rs @@ -812,9 +812,8 @@ pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { #[cfg(test)] mod tests { use crate::{in_mem::Blockchain, NewBlockState}; - use sp_api::HeaderT; use sp_blockchain::Backend; - use sp_runtime::{ConsensusEngineId, Justifications}; + use sp_runtime::{traits::Header as HeaderT, ConsensusEngineId, Justifications}; use substrate_test_runtime::{Block, Header, H256}; pub const ID1: ConsensusEngineId = *b"TST1"; diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index ef2fdcfd485f1b08539e83c9deb82ba82232d58c..cdd4052f0b0998f283ede885f7e7de7509ecd169 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-authority-discovery" -version = "0.10.0-dev" +version = "0.34.0" authors.workspace = true edition.workspace = true build = "build.rs" @@ -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,12 +24,15 @@ 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 = ["kad", "ed25519"] } -multihash = { version = "0.17.0", default-features = false, features = ["std", "sha2"] } -log = "0.4.17" -prost = "0.11" +libp2p = { version = "0.51.4", features = ["ed25519", "kad"] } +multihash = { version = "0.18.1", default-features = false, features = [ + "sha2", + "std", +] } +log = { workspace = true, default-features = true } +prost = "0.12" rand = "0.8.5" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } sc-network = { path = "../network" } @@ -36,7 +42,12 @@ 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", + "sha2", +] } [dev-dependencies] quickcheck = { version = "1.0.3", default-features = false } diff --git a/substrate/client/authority-discovery/src/worker.rs b/substrate/client/authority-discovery/src/worker.rs index a29e74df9accc98c50bdf1608612543299936efa..9bccb96ff378f262e9b66b4268d2ed95befe2bdb 100644 --- a/substrate/client/authority-discovery/src/worker.rs +++ b/substrate/client/authority-discovery/src/worker.rs @@ -35,7 +35,7 @@ use addr_cache::AddrCache; use codec::{Decode, Encode}; use ip_network::IpNetwork; use libp2p::{core::multiaddr, identity::PublicKey, multihash::Multihash, Multiaddr, PeerId}; -use multihash::{Code, MultihashDigest}; +use multihash_codetable::{Code, MultihashDigest}; use log::{debug, error, log_enabled}; use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64}; @@ -306,29 +306,30 @@ where fn addresses_to_publish(&self) -> impl Iterator { let peer_id: Multihash = self.network.local_peer_id().into(); let publish_non_global_ips = self.publish_non_global_ips; - self.network - .external_addresses() - .into_iter() - .filter(move |a| { - if publish_non_global_ips { - return true - } + let addresses = self.network.external_addresses().into_iter().filter(move |a| { + if publish_non_global_ips { + return true + } - a.iter().all(|p| match p { - // The `ip_network` library is used because its `is_global()` method is stable, - // while `is_global()` in the standard library currently isn't. - multiaddr::Protocol::Ip4(ip) if !IpNetwork::from(ip).is_global() => false, - multiaddr::Protocol::Ip6(ip) if !IpNetwork::from(ip).is_global() => false, - _ => true, - }) - }) - .map(move |a| { - if a.iter().any(|p| matches!(p, multiaddr::Protocol::P2p(_))) { - a - } else { - a.with(multiaddr::Protocol::P2p(peer_id)) - } + a.iter().all(|p| match p { + // The `ip_network` library is used because its `is_global()` method is stable, + // while `is_global()` in the standard library currently isn't. + multiaddr::Protocol::Ip4(ip) if !IpNetwork::from(ip).is_global() => false, + multiaddr::Protocol::Ip6(ip) if !IpNetwork::from(ip).is_global() => false, + _ => true, }) + }); + + debug!(target: LOG_TARGET, "Authority DHT record peer_id='{:?}' addresses='{:?}'", peer_id, addresses.clone().collect::>()); + + // The address must include the peer id if not already set. + addresses.map(move |a| { + if a.iter().any(|p| matches!(p, multiaddr::Protocol::P2p(_))) { + a + } else { + a.with(multiaddr::Protocol::P2p(peer_id)) + } + }) } /// Publish own public addresses. diff --git a/substrate/client/basic-authorship/Cargo.toml b/substrate/client/basic-authorship/Cargo.toml index b65a591795549115f7f22e4fa9cf409fcf3ecfbd..51a06464d0d6d5df0ccf960509f4df46d8b12207 100644 --- a/substrate/client/basic-authorship/Cargo.toml +++ b/substrate/client/basic-authorship/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-basic-authorship" -version = "0.10.0-dev" +version = "0.34.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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"] @@ -16,10 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-block-builder = { path = "../block-builder" } -sc-client-api = { path = "../api" } sc-proposer-metrics = { path = "../proposer-metrics" } sc-telemetry = { path = "../telemetry" } sc-transaction-pool-api = { path = "../transaction-pool/api" } @@ -32,5 +34,6 @@ sp-runtime = { path = "../../primitives/runtime" } [dev-dependencies] parking_lot = "0.12.1" +sc-client-api = { path = "../api" } sc-transaction-pool = { path = "../transaction-pool" } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 57c2996ab4063143694f3c2b44af93aae74bf775..932287ac86577b0e1a9b2c057320aeda9d12319a 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -28,18 +28,17 @@ use futures::{ select, }; use log::{debug, error, info, trace, warn}; -use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; -use sc_client_api::backend; +use sc_block_builder::{BlockBuilderApi, BlockBuilderBuilder}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_api::{ApiExt, CallApiAt, ProvideRuntimeApi}; use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed, HeaderBackend}; use sp_consensus::{DisableProofRecording, EnableProofRecording, ProofRecording, Proposal}; use sp_core::traits::SpawnNamed; use sp_inherents::InherentData; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT}, - Digest, Percent, SaturatedConversion, + Digest, ExtrinsicInclusionMode, Percent, SaturatedConversion, }; use std::{marker::PhantomData, pin::Pin, sync::Arc, time}; @@ -60,7 +59,7 @@ const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); const LOG_TARGET: &'static str = "basic-authorship"; /// [`Proposer`] factory. -pub struct ProposerFactory { +pub struct ProposerFactory { spawn_handle: Box, /// The client instance. client: Arc, @@ -84,11 +83,27 @@ pub struct ProposerFactory { telemetry: Option, /// When estimating the block size, should the proof be included? include_proof_in_block_size_estimation: bool, - /// phantom member to pin the `Backend`/`ProofRecording` type. - _phantom: PhantomData<(B, PR)>, + /// phantom member to pin the `ProofRecording` type. + _phantom: PhantomData, } -impl ProposerFactory { +impl Clone for ProposerFactory { + fn clone(&self) -> Self { + Self { + spawn_handle: self.spawn_handle.clone(), + client: self.client.clone(), + transaction_pool: self.transaction_pool.clone(), + metrics: self.metrics.clone(), + default_block_size_limit: self.default_block_size_limit, + soft_deadline_percent: self.soft_deadline_percent, + telemetry: self.telemetry.clone(), + include_proof_in_block_size_estimation: self.include_proof_in_block_size_estimation, + _phantom: self._phantom, + } + } +} + +impl ProposerFactory { /// Create a new proposer factory. /// /// Proof recording will be disabled when using proposers built by this instance to build @@ -114,7 +129,7 @@ impl ProposerFactory { } } -impl ProposerFactory { +impl ProposerFactory { /// Create a new proposer factory with proof recording enabled. /// /// Each proposer created by this instance will record a proof while building a block. @@ -147,7 +162,7 @@ impl ProposerFactory { } } -impl ProposerFactory { +impl ProposerFactory { /// Set the default block size limit in bytes. /// /// The default value for the block size limit is: @@ -176,29 +191,23 @@ impl ProposerFactory { } } -impl ProposerFactory +impl ProposerFactory where A: TransactionPool + 'static, - B: backend::Backend + Send + Sync + 'static, Block: BlockT, - C: BlockBuilderProvider - + HeaderBackend - + ProvideRuntimeApi - + Send - + Sync - + 'static, + C: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, C::Api: ApiExt + BlockBuilderApi, { fn init_with_now( &mut self, parent_header: &::Header, now: Box time::Instant + Send + Sync>, - ) -> Proposer { + ) -> Proposer { let parent_hash = parent_header.hash(); info!("🙌 Starting consensus session on top of parent {:?}", parent_hash); - let proposer = Proposer::<_, _, _, _, PR> { + let proposer = Proposer::<_, _, _, PR> { spawn_handle: self.spawn_handle.clone(), client: self.client.clone(), parent_hash, @@ -217,22 +226,16 @@ where } } -impl sp_consensus::Environment for ProposerFactory +impl sp_consensus::Environment for ProposerFactory where A: TransactionPool + 'static, - B: backend::Backend + Send + Sync + 'static, Block: BlockT, - C: BlockBuilderProvider - + HeaderBackend - + ProvideRuntimeApi - + Send - + Sync - + 'static, + C: HeaderBackend + ProvideRuntimeApi + CallApiAt + Send + Sync + 'static, C::Api: ApiExt + BlockBuilderApi, PR: ProofRecording, { type CreateProposer = future::Ready>; - type Proposer = Proposer; + type Proposer = Proposer; type Error = sp_blockchain::Error; fn init(&mut self, parent_header: &::Header) -> Self::CreateProposer { @@ -241,7 +244,7 @@ where } /// The proposer logic. -pub struct Proposer { +pub struct Proposer { spawn_handle: Box, client: Arc, parent_hash: Block::Hash, @@ -253,20 +256,14 @@ pub struct Proposer { include_proof_in_block_size_estimation: bool, soft_deadline_percent: Percent, telemetry: Option, - _phantom: PhantomData<(B, PR)>, + _phantom: PhantomData, } -impl sp_consensus::Proposer for Proposer +impl sp_consensus::Proposer for Proposer where A: TransactionPool + 'static, - B: backend::Backend + Send + Sync + 'static, Block: BlockT, - C: BlockBuilderProvider - + HeaderBackend - + ProvideRuntimeApi - + Send - + Sync - + 'static, + C: HeaderBackend + ProvideRuntimeApi + CallApiAt + Send + Sync + 'static, C::Api: ApiExt + BlockBuilderApi, PR: ProofRecording, { @@ -313,17 +310,11 @@ where /// It allows us to increase block utilization. const MAX_SKIPPED_TRANSACTIONS: usize = 8; -impl Proposer +impl Proposer where A: TransactionPool, - B: backend::Backend + Send + Sync + 'static, Block: BlockT, - C: BlockBuilderProvider - + HeaderBackend - + ProvideRuntimeApi - + Send - + Sync - + 'static, + C: HeaderBackend + ProvideRuntimeApi + CallApiAt + Send + Sync + 'static, C::Api: ApiExt + BlockBuilderApi, PR: ProofRecording, { @@ -334,32 +325,36 @@ where deadline: time::Instant, block_size_limit: Option, ) -> Result, sp_blockchain::Error> { - let propose_with_timer = time::Instant::now(); - let mut block_builder = - self.client.new_block_at(self.parent_hash, inherent_digests, PR::ENABLED)?; + let block_timer = time::Instant::now(); + let mut block_builder = BlockBuilderBuilder::new(&*self.client) + .on_parent_block(self.parent_hash) + .with_parent_block_number(self.parent_number) + .with_proof_recording(PR::ENABLED) + .with_inherent_digests(inherent_digests) + .build()?; self.apply_inherents(&mut block_builder, inherent_data)?; - // TODO call `after_inherents` and check if we should apply extrinsincs here - // - - let block_timer = time::Instant::now(); - let end_reason = - self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?; + let mode = block_builder.extrinsic_inclusion_mode(); + let end_reason = match mode { + ExtrinsicInclusionMode::AllExtrinsics => + self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?, + ExtrinsicInclusionMode::OnlyInherents => EndProposingReason::TransactionForbidden, + }; let (block, storage_changes, proof) = block_builder.build()?.into_inner(); let block_took = block_timer.elapsed(); let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; - self.print_summary(&block, end_reason, block_took, propose_with_timer.elapsed()); + self.print_summary(&block, end_reason, block_took, block_timer.elapsed()); Ok(Proposal { block, proof, storage_changes }) } /// Apply all inherents to the block. fn apply_inherents( &self, - block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C>, inherent_data: InherentData, ) -> Result<(), sp_blockchain::Error> { let create_inherents_start = time::Instant::now(); @@ -403,7 +398,7 @@ where /// Apply as many extrinsics as possible to the block. async fn apply_extrinsics( &self, - block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C>, deadline: time::Instant, block_size_limit: Option, ) -> Result { @@ -443,6 +438,11 @@ where let pending_tx = if let Some(pending_tx) = pending_iterator.next() { pending_tx } else { + debug!( + target: LOG_TARGET, + "No more transactions, proceeding with proposing." + ); + break EndProposingReason::NoMoreTransactions }; @@ -539,19 +539,24 @@ where } /// Prints a summary and does telemetry + metrics. + /// + /// - `block`: The block that was build. + /// - `end_reason`: Why did we stop producing the block? + /// - `block_took`: How long did it took to produce the actual block? + /// - `propose_took`: How long did the entire proposing took? fn print_summary( &self, block: &Block, end_reason: EndProposingReason, block_took: time::Duration, - propose_with_took: time::Duration, + propose_took: time::Duration, ) { let extrinsics = block.extrinsics(); self.metrics.report(|metrics| { metrics.number_of_transactions.set(extrinsics.len() as u64); metrics.block_constructed.observe(block_took.as_secs_f64()); metrics.report_end_proposing_reason(end_reason); - metrics.create_block_proposal_time.observe(propose_with_took.as_secs_f64()); + metrics.create_block_proposal_time.observe(propose_took.as_secs_f64()); }); let extrinsics_summary = if extrinsics.is_empty() { @@ -967,8 +972,12 @@ mod tests { // Exact block_limit, which includes: // 99 (header_size) + 718 (proof@initialize_block) + 246 (one Transfer extrinsic) let block_limit = { - let builder = - client.new_block_at(genesis_header.hash(), Default::default(), true).unwrap(); + let builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(genesis_header.hash()) + .with_parent_block_number(0) + .enable_proof_recording() + .build() + .unwrap(); builder.estimate_block_size(true) + extrinsics[0].encoded_size() }; let block = block_on(proposer.propose( diff --git a/substrate/client/block-builder/Cargo.toml b/substrate/client/block-builder/Cargo.toml index ff2f9635b7a2fae718d7a47deaa5e6e280ec2091..e74d587d9b40fc57e0575b0fae7ecb0c5a177c15 100644 --- a/substrate/client/block-builder/Cargo.toml +++ b/substrate/client/block-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-block-builder" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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"] @@ -16,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", ] } -sc-client-api = { path = "../api" } sp-api = { path = "../../primitives/api" } sp-block-builder = { path = "../../primitives/block-builder" } sp-blockchain = { path = "../../primitives/blockchain" } sp-core = { path = "../../primitives/core" } +sp-trie = { path = "../../primitives/trie" } sp-inherents = { path = "../../primitives/inherents" } sp-runtime = { path = "../../primitives/runtime" } diff --git a/substrate/client/block-builder/src/lib.rs b/substrate/client/block-builder/src/lib.rs index 1878e762748040c158fdbe7875af1eb205b6048b..2f22cd42591fc60bf1665d00a1c5b2b548ad3aea 100644 --- a/substrate/client/block-builder/src/lib.rs +++ b/substrate/client/block-builder/src/lib.rs @@ -29,56 +29,148 @@ use codec::Encode; use sp_api::{ - ApiExt, ApiRef, Core, ProvideRuntimeApi, StorageChanges, StorageProof, TransactionOutcome, + ApiExt, ApiRef, CallApiAt, Core, ProvideRuntimeApi, StorageChanges, StorageProof, + TransactionOutcome, }; -use sp_blockchain::{ApplyExtrinsicFailed, Error}; +use sp_blockchain::{ApplyExtrinsicFailed, Error, HeaderBackend}; use sp_core::traits::CallContext; use sp_runtime::{ legacy, traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One}, - Digest, + Digest, ExtrinsicInclusionMode, }; +use std::marker::PhantomData; -use sc_client_api::backend; pub use sp_block_builder::BlockBuilder as BlockBuilderApi; +use sp_trie::proof_size_extension::ProofSizeExt; -/// Used as parameter to [`BlockBuilderProvider`] to express if proof recording should be enabled. -/// -/// When `RecordProof::Yes` is given, all accessed trie nodes should be saved. These recorded -/// trie nodes can be used by a third party to proof this proposal without having access to the -/// full storage. -#[derive(Copy, Clone, PartialEq)] -pub enum RecordProof { - /// `Yes`, record a proof. - Yes, - /// `No`, don't record any proof. - No, +/// A builder for creating an instance of [`BlockBuilder`]. +pub struct BlockBuilderBuilder<'a, B, C> { + call_api_at: &'a C, + _phantom: PhantomData, } -impl RecordProof { - /// Returns if `Self` == `Yes`. - pub fn yes(&self) -> bool { - matches!(self, Self::Yes) +impl<'a, B, C> BlockBuilderBuilder<'a, B, C> +where + B: BlockT, +{ + /// Create a new instance of the builder. + /// + /// `call_api_at`: Something that implements [`CallApiAt`]. + pub fn new(call_api_at: &'a C) -> Self { + Self { call_api_at, _phantom: PhantomData } } -} -/// Will return [`RecordProof::No`] as default value. -impl Default for RecordProof { - fn default() -> Self { - Self::No + /// Specify the parent block to build on top of. + pub fn on_parent_block(self, parent_block: B::Hash) -> BlockBuilderBuilderStage1<'a, B, C> { + BlockBuilderBuilderStage1 { call_api_at: self.call_api_at, parent_block } } } -impl From for RecordProof { - fn from(val: bool) -> Self { - if val { - Self::Yes - } else { - Self::No +/// The second stage of the [`BlockBuilderBuilder`]. +/// +/// This type can not be instantiated directly. To get an instance of it +/// [`BlockBuilderBuilder::new`] needs to be used. +pub struct BlockBuilderBuilderStage1<'a, B: BlockT, C> { + call_api_at: &'a C, + parent_block: B::Hash, +} + +impl<'a, B, C> BlockBuilderBuilderStage1<'a, B, C> +where + B: BlockT, +{ + /// Fetch the parent block number from the given `header_backend`. + /// + /// The parent block number is used to initialize the block number of the new block. + /// + /// Returns an error if the parent block specified in + /// [`on_parent_block`](BlockBuilderBuilder::on_parent_block) does not exist. + pub fn fetch_parent_block_number>( + self, + header_backend: &H, + ) -> Result, Error> { + let parent_number = header_backend.number(self.parent_block)?.ok_or_else(|| { + Error::Backend(format!( + "Could not fetch block number for block: {:?}", + self.parent_block + )) + })?; + + Ok(BlockBuilderBuilderStage2 { + call_api_at: self.call_api_at, + enable_proof_recording: false, + inherent_digests: Default::default(), + parent_block: self.parent_block, + parent_number, + }) + } + + /// Provide the block number for the parent block directly. + /// + /// The parent block is specified in [`on_parent_block`](BlockBuilderBuilder::on_parent_block). + /// The parent block number is used to initialize the block number of the new block. + pub fn with_parent_block_number( + self, + parent_number: NumberFor, + ) -> BlockBuilderBuilderStage2<'a, B, C> { + BlockBuilderBuilderStage2 { + call_api_at: self.call_api_at, + enable_proof_recording: false, + inherent_digests: Default::default(), + parent_block: self.parent_block, + parent_number, } } } +/// The second stage of the [`BlockBuilderBuilder`]. +/// +/// This type can not be instantiated directly. To get an instance of it +/// [`BlockBuilderBuilder::new`] needs to be used. +pub struct BlockBuilderBuilderStage2<'a, B: BlockT, C> { + call_api_at: &'a C, + enable_proof_recording: bool, + inherent_digests: Digest, + parent_block: B::Hash, + parent_number: NumberFor, +} + +impl<'a, B: BlockT, C> BlockBuilderBuilderStage2<'a, B, C> { + /// Enable proof recording for the block builder. + pub fn enable_proof_recording(mut self) -> Self { + self.enable_proof_recording = true; + self + } + + /// Enable/disable proof recording for the block builder. + pub fn with_proof_recording(mut self, enable: bool) -> Self { + self.enable_proof_recording = enable; + self + } + + /// Build the block with the given inherent digests. + pub fn with_inherent_digests(mut self, inherent_digests: Digest) -> Self { + self.inherent_digests = inherent_digests; + self + } + + /// Create the instance of the [`BlockBuilder`]. + pub fn build(self) -> Result, Error> + where + C: CallApiAt + ProvideRuntimeApi, + C::Api: BlockBuilderApi, + { + BlockBuilder::new( + self.call_api_at, + self.parent_block, + self.parent_number, + self.enable_proof_recording, + self.inherent_digests, + ) + } +} + /// A block that was build by [`BlockBuilder`] plus some additional data. /// /// This additional data includes the `storage_changes`, these changes can be applied to the @@ -101,63 +193,36 @@ impl BuiltBlock { } } -/// Block builder provider -pub trait BlockBuilderProvider -where - Block: BlockT, - B: backend::Backend, - Self: Sized, - RA: ProvideRuntimeApi, -{ - /// Create a new block, built on top of `parent`. - /// - /// When proof recording is enabled, all accessed trie nodes are saved. - /// These recorded trie nodes can be used by a third party to proof the - /// output of this block builder without having access to the full storage. - fn new_block_at>( - &self, - parent: Block::Hash, - inherent_digests: Digest, - record_proof: R, - ) -> sp_blockchain::Result>; - - /// Create a new block, built on the head of the chain. - fn new_block( - &self, - inherent_digests: Digest, - ) -> sp_blockchain::Result>; -} - /// Utility for building new (valid) blocks from a stream of extrinsics. -pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { +pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi + 'a> { extrinsics: Vec, - api: ApiRef<'a, A::Api>, + api: ApiRef<'a, C::Api>, + call_api_at: &'a C, + /// Version of the [`BlockBuilderApi`] runtime API. version: u32, parent_hash: Block::Hash, - backend: &'a B, /// The estimated size of the block header. estimated_header_size: usize, + extrinsic_inclusion_mode: ExtrinsicInclusionMode, } -impl<'a, Block, A, B> BlockBuilder<'a, Block, A, B> +impl<'a, Block, C> BlockBuilder<'a, Block, C> where Block: BlockT, - A: ProvideRuntimeApi + 'a, - A::Api: BlockBuilderApi + ApiExt, - B: backend::Backend, + C: CallApiAt + ProvideRuntimeApi + 'a, + C::Api: BlockBuilderApi, { /// Create a new instance of builder based on the given `parent_hash` and `parent_number`. /// /// While proof recording is enabled, all accessed trie nodes are saved. /// These recorded trie nodes can be used by a third party to prove the /// output of this block builder without having access to the full storage. - pub fn new( - api: &'a A, + fn new( + call_api_at: &'a C, parent_hash: Block::Hash, parent_number: NumberFor, - record_proof: RecordProof, + record_proof: bool, inherent_digests: Digest, - backend: &'a B, ) -> Result { let header = <::Header as HeaderT>::new( parent_number + One::one(), @@ -169,17 +234,31 @@ where let estimated_header_size = header.encoded_size(); - let mut api = api.runtime_api(); + let mut api = call_api_at.runtime_api(); - if record_proof.yes() { + if record_proof { api.record_proof(); + let recorder = api + .proof_recorder() + .expect("Proof recording is enabled in the line above; qed."); + api.register_extension(ProofSizeExt::new(recorder)); } api.set_call_context(CallContext::Onchain); - api.initialize_block(parent_hash, &header)?; + let core_version = api + .api_version::>(parent_hash)? + .ok_or_else(|| Error::VersionInvalid("Core".to_string()))?; - let version = api + let extrinsic_inclusion_mode = if core_version >= 5 { + api.initialize_block(parent_hash, &header)? + } else { + #[allow(deprecated)] + api.initialize_block_before_version_5(parent_hash, &header)?; + ExtrinsicInclusionMode::AllExtrinsics + }; + + let bb_version = api .api_version::>(parent_hash)? .ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?; @@ -187,12 +266,18 @@ where parent_hash, extrinsics: Vec::new(), api, - version, - backend, + version: bb_version, estimated_header_size, + call_api_at, + extrinsic_inclusion_mode, }) } + /// The extrinsic inclusion mode of the runtime for this block. + pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode { + self.extrinsic_inclusion_mode + } + /// Push onto the block's list of extrinsics. /// /// This will ensure the extrinsic can be validly executed (by executing it). @@ -241,7 +326,7 @@ where let proof = self.api.extract_proof(); - let state = self.backend.state_at(self.parent_hash)?; + let state = self.call_api_at.state_at(self.parent_hash)?; let storage_changes = self .api @@ -300,22 +385,18 @@ mod tests { #[test] fn block_building_storage_proof_does_not_include_runtime_by_default() { let builder = substrate_test_runtime_client::TestClientBuilder::new(); - let backend = builder.backend(); let client = builder.build(); let genesis_hash = client.info().best_hash; - let block = BlockBuilder::new( - &client, - genesis_hash, - client.info().best_number, - RecordProof::Yes, - Default::default(), - &*backend, - ) - .unwrap() - .build() - .unwrap(); + let block = BlockBuilderBuilder::new(&client) + .on_parent_block(genesis_hash) + .with_parent_block_number(0) + .enable_proof_recording() + .build() + .unwrap() + .build() + .unwrap(); let proof = block.proof.expect("Proof is build on request"); let genesis_state_root = client.header(genesis_hash).unwrap().unwrap().state_root; @@ -333,18 +414,15 @@ mod tests { #[test] fn failing_extrinsic_rolls_back_changes_in_storage_proof() { let builder = substrate_test_runtime_client::TestClientBuilder::new(); - let backend = builder.backend(); let client = builder.build(); + let genesis_hash = client.info().best_hash; - let mut block_builder = BlockBuilder::new( - &client, - client.info().best_hash, - client.info().best_number, - RecordProof::Yes, - Default::default(), - &*backend, - ) - .unwrap(); + let mut block_builder = BlockBuilderBuilder::new(&client) + .on_parent_block(genesis_hash) + .with_parent_block_number(0) + .enable_proof_recording() + .build() + .unwrap(); block_builder.push(ExtrinsicBuilder::new_read_and_panic(8).build()).unwrap_err(); @@ -352,15 +430,12 @@ mod tests { let proof_with_panic = block.proof.expect("Proof is build on request").encoded_size(); - let mut block_builder = BlockBuilder::new( - &client, - client.info().best_hash, - client.info().best_number, - RecordProof::Yes, - Default::default(), - &*backend, - ) - .unwrap(); + let mut block_builder = BlockBuilderBuilder::new(&client) + .on_parent_block(genesis_hash) + .with_parent_block_number(0) + .enable_proof_recording() + .build() + .unwrap(); block_builder.push(ExtrinsicBuilder::new_read(8).build()).unwrap(); @@ -368,17 +443,14 @@ mod tests { let proof_without_panic = block.proof.expect("Proof is build on request").encoded_size(); - let block = BlockBuilder::new( - &client, - client.info().best_hash, - client.info().best_number, - RecordProof::Yes, - Default::default(), - &*backend, - ) - .unwrap() - .build() - .unwrap(); + let block = BlockBuilderBuilder::new(&client) + .on_parent_block(genesis_hash) + .with_parent_block_number(0) + .enable_proof_recording() + .build() + .unwrap() + .build() + .unwrap(); let proof_empty_block = block.proof.expect("Proof is build on request").encoded_size(); diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index e326d84ae50c6c6172965160a8ecd203319f3c0a..f2138c07d71ad4c524e12d33ffb73d453d707fbb 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-chain-spec" -version = "4.0.0-dev" +version = "27.0.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -9,19 +9,35 @@ repository.workspace = true description = "Substrate chain configurations." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -memmap2 = "0.5.0" -serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.107" +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +memmap2 = "0.9.3" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-chain-spec-derive = { path = "derive" } sc-executor = { path = "../executor" } +sp-io = { default-features = false, path = "../../primitives/io" } sc-network = { path = "../network" } sc-telemetry = { path = "../telemetry" } sp-blockchain = { path = "../../primitives/blockchain" } sp-core = { path = "../../primitives/core" } +sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } +sp-genesis-builder = { path = "../../primitives/genesis-builder" } sp-runtime = { path = "../../primitives/runtime" } sp-state-machine = { path = "../../primitives/state-machine" } +log = { workspace = true } +array-bytes = { version = "6.1" } +docify = "0.2.7" + +[dev-dependencies] +substrate-test-runtime = { path = "../../test-utils/runtime" } +sp-keyring = { path = "../../primitives/keyring" } +sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } +sp-consensus-babe = { default-features = false, path = "../../primitives/consensus/babe", features = ["serde"] } diff --git a/substrate/client/chain-spec/README.md b/substrate/client/chain-spec/README.md index dad1662d3230ec11b1bafa39fd78bf7d64d1ac5f..9f746d2d4ce7b5db0614e6864650be87c7d98836 100644 --- a/substrate/client/chain-spec/README.md +++ b/substrate/client/chain-spec/README.md @@ -1,92 +1,6 @@ Substrate chain configurations. -This crate contains structs and utilities to declare -a runtime-specific configuration file (a.k.a chain spec). - -Basic chain spec type containing all required parameters is -[`ChainSpec`](https://docs.rs/sc-chain-spec/latest/sc_chain_spec/struct.GenericChainSpec.html). It can be extended with -additional options that contain configuration specific to your chain. -Usually the extension is going to be an amalgamate of types exposed -by Substrate core modules. To allow the core modules to retrieve -their configuration from your extension you should use `ChainSpecExtension` -macro exposed by this crate. - -```rust -use std::collections::HashMap; -use sc_chain_spec::{GenericChainSpec, ChainSpecExtension}; - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecExtension)] -pub struct MyExtension { - pub known_blocks: HashMap, -} - -pub type MyChainSpec = GenericChainSpec; -``` - -Some parameters may require different values depending on the -current blockchain height (a.k.a. forks). You can use `ChainSpecGroup` -macro and provided [`Forks`](https://docs.rs/sc-chain-spec/latest/sc_chain_spec/struct.Forks.html) structure to put -such parameters to your chain spec. -This will allow to override a single parameter starting at specific -block number. - -```rust -use sc_chain_spec::{Forks, ChainSpecGroup, ChainSpecExtension, GenericChainSpec}; - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] -pub struct ClientParams { - max_block_size: usize, - max_extrinsic_size: usize, -} - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] -pub struct PoolParams { - max_transaction_size: usize, -} - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup, ChainSpecExtension)] -pub struct Extension { - pub client: ClientParams, - pub pool: PoolParams, -} - -pub type BlockNumber = u64; - -/// A chain spec supporting forkable `ClientParams`. -pub type MyChainSpec1 = GenericChainSpec>; - -/// A chain spec supporting forkable `Extension`. -pub type MyChainSpec2 = GenericChainSpec>; -``` - -It's also possible to have a set of parameters that is allowed to change -with block numbers (i.e. is forkable), and another set that is not subject to changes. -This is also possible by declaring an extension that contains `Forks` within it. - - -```rust -use serde::{Serialize, Deserialize}; -use sc_chain_spec::{Forks, GenericChainSpec, ChainSpecGroup, ChainSpecExtension}; - -#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] -pub struct ClientParams { - max_block_size: usize, - max_extrinsic_size: usize, -} - -#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] -pub struct PoolParams { - max_transaction_size: usize, -} - -#[derive(Clone, Debug, Serialize, Deserialize, ChainSpecExtension)] -pub struct Extension { - pub client: ClientParams, - #[forks] - pub pool: Forks, -} - -pub type MyChainSpec = GenericChainSpec; -``` +This crate contains structs and utilities to declare a runtime-specific configuration file (a.k.a chain spec). +Refer to crate documentation for details. License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/substrate/client/chain-spec/derive/Cargo.toml b/substrate/client/chain-spec/derive/Cargo.toml index 74b8b656a4042bdd348cf2997fa3b9ae686fe2cd..521eee578ecae3b03cf86a3b4e3630bb7cd22f02 100644 --- a/substrate/client/chain-spec/derive/Cargo.toml +++ b/substrate/client/chain-spec/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-chain-spec-derive" -version = "4.0.0-dev" +version = "11.0.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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 = "1.1.3" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.38" +quote = { workspace = true } +syn = { workspace = true } diff --git a/substrate/client/chain-spec/res/raw_no_code.json b/substrate/client/chain-spec/res/raw_no_code.json new file mode 100644 index 0000000000000000000000000000000000000000..bac7c25582f965c0b4684777c55a87c54cc1cd57 --- /dev/null +++ b/substrate/client/chain-spec/res/raw_no_code.json @@ -0,0 +1,18 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x3a636f6465": "0x010101" + }, + "childrenDefault": {} + } + } +} diff --git a/substrate/client/chain-spec/res/raw_with_code.json b/substrate/client/chain-spec/res/raw_with_code.json new file mode 100644 index 0000000000000000000000000000000000000000..c12cec57ba7a276516e228e97c27a749aef54d65 --- /dev/null +++ b/substrate/client/chain-spec/res/raw_with_code.json @@ -0,0 +1,19 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x3a636f6465": "0x010101" + }, + "childrenDefault": {} + } + }, + "code": "0x060708" +} diff --git a/substrate/client/chain-spec/res/substrate_test_runtime_from_config.json b/substrate/client/chain-spec/res/substrate_test_runtime_from_config.json new file mode 100644 index 0000000000000000000000000000000000000000..d16f26d12540ccaf74442b97002e658d2231eb4d --- /dev/null +++ b/substrate/client/chain-spec/res/substrate_test_runtime_from_config.json @@ -0,0 +1,128 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "config": { + "babe": { + "authorities": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1 + ] + ], + "epochConfig": { + "allowed_slots": "PrimaryAndSecondaryPlainSlots", + "c": [ + 3, + 10 + ] + } + }, + "balances": { + "balances": [ + [ + "5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH", + 100000000000000000 + ], + [ + "5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o", + 100000000000000000 + ], + [ + "5Dfis6XL8J2P6JHUnUtArnFWndn62SydeP8ee8sG2ky9nfm9", + 100000000000000000 + ], + [ + "5F4H97f7nQovyrbiq4ZetaaviNwThSVcFobcA5aGab6167dK", + 100000000000000000 + ], + [ + "5DiDShBWa1fQx6gLzpf3SFBhMinCoyvHM1BWjPNsmXS8hkrW", + 100000000000000000 + ], + [ + "5EFb84yH9tpcFuiKUcsmdoF7xeeY3ajG1ZLQimxQoFt9HMKR", + 100000000000000000 + ], + [ + "5DZLHESsfGrJ5YzT3HuRPXsSNb589xQ4Unubh1mYLodzKdVY", + 100000000000000000 + ], + [ + "5GHJzqvG6tXnngCpG7B12qjUvbo5e4e9z8Xjidk3CQZHxTPZ", + 100000000000000000 + ], + [ + "5CUnSsgAyLND3bxxnfNhgWXSe9Wn676JzLpGLgyJv858qhoX", + 100000000000000000 + ], + [ + "5CVKn7HAZW1Ky4r7Vkgsr7VEW88C2sHgUNDiwHY9Ct2hjU8q", + 100000000000000000 + ], + [ + "5H673aukQ4PeDe1U2nuv1bi32xDEziimh3PZz7hDdYUB7TNz", + 100000000000000000 + ], + [ + "5HTe9L15LJryjUAt1jZXZCBPnzbbGnpvFwbjE3NwCWaAqovf", + 100000000000000000 + ], + [ + "5D7LFzGpMwHPyDBavkRbWSKWTtJhCaPPZ379wWLT23bJwXJz", + 100000000000000000 + ], + [ + "5CLepMARnEgtVR1EkUuJVUvKh97gzergpSxUU3yKGx1v6EwC", + 100000000000000000 + ], + [ + "5Chb2UhfvZpmjjEziHbFbotM4quX32ZscRV6QJBt1rUKzz51", + 100000000000000000 + ], + [ + "5HmRp3i3ZZk7xsAvbi8hyXVP6whSMnBJGebVC4FsiZVhx52e", + 100000000000000000 + ], + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 100000000000000000 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 100000000000000000 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 100000000000000000 + ] + ] + }, + "substrateTest": { + "authorities": [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" + ] + }, + "system": {} + }, + "code": "0x0" + } + } +} diff --git a/substrate/client/chain-spec/res/substrate_test_runtime_from_config_raw.json b/substrate/client/chain-spec/res/substrate_test_runtime_from_config_raw.json new file mode 100644 index 0000000000000000000000000000000000000000..f033163ab19bcd942170d116831c6cff0a8c5abc --- /dev/null +++ b/substrate/client/chain-spec/res/substrate_test_runtime_from_config_raw.json @@ -0,0 +1,53 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe220100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe220100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x03000000000000000a0000000000000001", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da901cae4e3edfbb32c91ed3f01ab964f4eeeab50338d8e5176d3141802d7b010a55dadcd5f23cf8aaafa724627e967e90e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da91b614bd4a126f2d5d294e9a8af9da25248d7e931307afb4b68d8d565d4c66e00d856c6d65f5fed6bb82dcfb60e936c67": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94b21aff9fe1e8b2fc4b0775b8cbeff28ba8e2c7594dd74730f3ca835e95455d199261897edc9735d602ea29615e2b10b": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95786a2916fcb81e1bd5dcd81e0d2452884617f575372edb5a36d85c04cdf2e4699f96fe33eb5f94a28c041b88e398d0c": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95b8542d9672c7b7e779cc7c1e6b605691c2115d06120ea2bee32dd601d02f36367564e7ddf84ae2717ca3f097459652e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da996c30bdbfab640838e6b6d3c33ab4adb4211b79e34ee8072eab506edd4b93a7b85a14c9a05e5cdd056d98e7dbca87730": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99dc65b1339ec388fbf2ca0cdef51253512c6cfd663203ea16968594f24690338befd906856c4d2f4ef32dad578dba20c": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99e6eb5abd62f5fd54793da91a47e6af6125d57171ff9241f07acaa1bb6a6103517965cf2cd00e643b27e7599ebccba70": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d0052993b6f3bd0544fd1f5e4125b9fbde3e789ecd53431fe5c06c12b72137153496dace35c695b5f4d7b41f7ed5763b": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d6b7e9a5f12bc571053265dade10d3b4b606fc73f57f03cdb4c932d475ab426043e429cecc2ffff0d2672b0df8398c48": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e1a35f56ee295d39287cbffcfc60c4b346f136b564e1fad55031404dd84e5cd3fa76bfe7cc7599b39d38fd06663bbc0a": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e2c1dc507e2035edbbd8776c440d870460c57f0008067cc01c5ff9eb2e2f9b3a94299a915a91198bd1021a6c55596f57": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9eca0e653a94f4080f6311b4e7b6934eb2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ee8bf7ef90fc56a8aa3b90b344c599550c29b161e27ff8ba45bf6bad4711f326fc506a8803453a4d7e3158e993495f10": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f5d6f1c082fe63eec7a71fcad00f4a892e3d43b7b0d04e776e69e7be35247cecdac65504c579195731eaf64b7940966e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9fbf0818841edf110e05228a6379763c4fc3c37459d9bdc61f58a5ebc01e9e2305a19d390c0543dc733861ec3cf1de01f": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x0000", + "0x3a636f6465": "0x0", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00003ef1ee275e1a" + }, + "childrenDefault": {} + } + } +} diff --git a/substrate/client/chain-spec/res/substrate_test_runtime_from_patch.json b/substrate/client/chain-spec/res/substrate_test_runtime_from_patch.json new file mode 100644 index 0000000000000000000000000000000000000000..6cef1dac7ac3d56df16b9e65f83d0908971c1c86 --- /dev/null +++ b/substrate/client/chain-spec/res/substrate_test_runtime_from_patch.json @@ -0,0 +1,32 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "patch": { + "babe": { + "epochConfig": { + "allowed_slots": "PrimaryAndSecondaryPlainSlots", + "c": [ + 7, + 10 + ] + } + }, + "substrateTest": { + "authorities": [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + ] + } + }, + "code": "0x0" + } + } +} diff --git a/substrate/client/chain-spec/res/substrate_test_runtime_from_patch_raw.json b/substrate/client/chain-spec/res/substrate_test_runtime_from_patch_raw.json new file mode 100644 index 0000000000000000000000000000000000000000..9f4b2731649f346d33a8f91d448840956786ac72 --- /dev/null +++ b/substrate/client/chain-spec/res/substrate_test_runtime_from_patch_raw.json @@ -0,0 +1,32 @@ +{ + "name": "TestName", + "id": "test_id", + "chainType": "Local", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d": "0x081cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x07000000000000000a0000000000000001", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x0000", + "0x3a636f6465": "0x0", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0000000000000000" + }, + "childrenDefault": {} + } + } +} diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index 96e36d8399ed5de6328553200d181c0a661c422d..78e81e10d2b613d83c98e8b689cba2fd1356287c 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -18,8 +18,10 @@ //! Substrate chain configurations. #![warn(missing_docs)] - -use crate::{extension::GetExtension, ChainType, Properties, RuntimeGenesis}; +use crate::{ + extension::GetExtension, genesis_config_builder::HostFunctions, ChainType, + GenesisConfigBuilderRuntimeCaller as RuntimeCaller, Properties, RuntimeGenesis, +}; use sc_network::config::MultiaddrWithPeerId; use sc_telemetry::TelemetryEndpoints; use serde::{Deserialize, Serialize}; @@ -29,13 +31,32 @@ use sp_core::{ Bytes, }; use sp_runtime::BuildStorage; -use std::{borrow::Cow, collections::BTreeMap, fs::File, path::PathBuf, sync::Arc}; +use std::{ + borrow::Cow, + collections::{BTreeMap, VecDeque}, + fs::File, + marker::PhantomData, + path::PathBuf, + sync::Arc, +}; + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +enum GenesisBuildAction { + Patch(json::Value), + Full(json::Value), +} +#[allow(deprecated)] enum GenesisSource { File(PathBuf), Binary(Cow<'static, [u8]>), - Factory(Arc G + Send + Sync>), + /// factory function + code + //Factory and G type parameter shall be removed togheter with `ChainSpec::from_genesis` + Factory(Arc G + Send + Sync>, Vec), Storage(Storage), + /// build action + code + GenesisBuilderApi(GenesisBuildAction, Vec), } impl Clone for GenesisSource { @@ -43,14 +64,17 @@ impl Clone for GenesisSource { match *self { Self::File(ref path) => Self::File(path.clone()), Self::Binary(ref d) => Self::Binary(d.clone()), - Self::Factory(ref f) => Self::Factory(f.clone()), + Self::Factory(ref f, ref c) => Self::Factory(f.clone(), c.clone()), Self::Storage(ref s) => Self::Storage(s.clone()), + Self::GenesisBuilderApi(ref s, ref c) => Self::GenesisBuilderApi(s.clone(), c.clone()), } } } impl GenesisSource { fn resolve(&self) -> Result, String> { + /// helper container for deserializing genesis from the JSON file (ChainSpec JSON file is + /// also supported here) #[derive(Serialize, Deserialize)] struct GenesisContainer { genesis: Genesis, @@ -79,39 +103,43 @@ impl GenesisSource { .map_err(|e| format!("Error parsing embedded file: {}", e))?; Ok(genesis.genesis) }, - Self::Factory(f) => Ok(Genesis::Runtime(f())), - Self::Storage(storage) => { - let top = storage - .top - .iter() - .map(|(k, v)| (StorageKey(k.clone()), StorageData(v.clone()))) - .collect(); - - let children_default = storage - .children_default - .iter() - .map(|(k, child)| { - ( - StorageKey(k.clone()), - child - .data - .iter() - .map(|(k, v)| (StorageKey(k.clone()), StorageData(v.clone()))) - .collect(), - ) - }) - .collect(); - - Ok(Genesis::Raw(RawGenesis { top, children_default })) - }, + Self::Factory(f, code) => Ok(Genesis::RuntimeAndCode(RuntimeInnerWrapper { + runtime: f(), + code: code.clone(), + })), + Self::Storage(storage) => Ok(Genesis::Raw(RawGenesis::from(storage.clone()))), + Self::GenesisBuilderApi(GenesisBuildAction::Full(config), code) => + Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Config(config.clone()), + code: code.clone(), + })), + Self::GenesisBuilderApi(GenesisBuildAction::Patch(patch), code) => + Ok(Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Patch(patch.clone()), + code: code.clone(), + })), } } } -impl BuildStorage for ChainSpec { +impl BuildStorage for ChainSpec +where + EHF: HostFunctions, +{ fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { match self.genesis.resolve()? { - Genesis::Runtime(gc) => gc.assimilate_storage(storage), + #[allow(deprecated)] + Genesis::Runtime(runtime_genesis_config) => { + runtime_genesis_config.assimilate_storage(storage)?; + }, + #[allow(deprecated)] + Genesis::RuntimeAndCode(RuntimeInnerWrapper { + runtime: runtime_genesis_config, + code, + }) => { + runtime_genesis_config.assimilate_storage(storage)?; + storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code); + }, Genesis::Raw(RawGenesis { top: map, children_default: children_map }) => { storage.top.extend(map.into_iter().map(|(k, v)| (k.0, v.0))); children_map.into_iter().for_each(|(k, v)| { @@ -123,13 +151,37 @@ impl BuildStorage for ChainSpec { .data .extend(v.into_iter().map(|(k, v)| (k.0, v.0))); }); - Ok(()) }, // The `StateRootHash` variant exists as a way to keep note that other clients support // it, but Substrate itself isn't capable of loading chain specs with just a hash at the // moment. - Genesis::StateRootHash(_) => Err("Genesis storage in hash format not supported".into()), - } + Genesis::StateRootHash(_) => + return Err("Genesis storage in hash format not supported".into()), + Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Config(config), + code, + }) => { + RuntimeCaller::::new(&code[..]) + .get_storage_for_config(config)? + .assimilate_storage(storage)?; + storage + .top + .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone()); + }, + Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Patch(patch), + code, + }) => { + RuntimeCaller::::new(&code[..]) + .get_storage_for_patch(patch)? + .assimilate_storage(storage)?; + storage + .top + .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone()); + }, + }; + + Ok(()) } } @@ -144,20 +196,98 @@ pub struct RawGenesis { pub children_default: BTreeMap, } +impl From for RawGenesis { + fn from(value: sp_core::storage::Storage) -> Self { + Self { + top: value.top.into_iter().map(|(k, v)| (StorageKey(k), StorageData(v))).collect(), + children_default: value + .children_default + .into_iter() + .map(|(sk, child)| { + ( + StorageKey(sk), + child + .data + .into_iter() + .map(|(k, v)| (StorageKey(k), StorageData(v))) + .collect(), + ) + }) + .collect(), + } + } +} + +/// Inner representation of [`Genesis::RuntimeGenesis`] format +#[derive(Serialize, Deserialize, Debug)] +struct RuntimeGenesisInner { + /// Runtime wasm code, expected to be hex-encoded in JSON. + /// The code shall be capable of parsing `json_blob`. + #[serde(default, with = "sp_core::bytes")] + code: Vec, + /// The patch or full representation of runtime's `RuntimeGenesisConfig` struct. + #[serde(flatten)] + json_blob: RuntimeGenesisConfigJson, +} + +/// Represents two possible variants of the contained JSON blob for the +/// [`Genesis::RuntimeGenesis`] format. +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +enum RuntimeGenesisConfigJson { + /// Represents the explicit and comprehensive runtime genesis config in JSON format. + /// The contained object is a JSON blob that can be parsed by a compatible runtime. + /// + /// Using a full config is useful for when someone wants to ensure that a change in the runtime + /// makes the deserialization fail and not silently add some default values. + Config(json::Value), + /// Represents a patch for the default runtime genesis config in JSON format which is + /// essentially a list of keys that are to be customized in runtime genesis config. + /// The contained value is a JSON blob that can be parsed by a compatible runtime. + Patch(json::Value), +} + +/// Inner variant wrapper for deprecated runtime. +#[derive(Serialize, Deserialize, Debug)] +struct RuntimeInnerWrapper { + /// The native `RuntimeGenesisConfig` struct. + runtime: G, + /// Runtime code. + #[serde(with = "sp_core::bytes")] + code: Vec, +} + +/// Represents the different formats of the genesis state within chain spec JSON blob. #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] enum Genesis { + /// (Deprecated) Contains the JSON representation of G (the native type representing the + /// runtime's `RuntimeGenesisConfig` struct) (will be removed with `ChainSpec::from_genesis`) + /// without the runtime code. It is required to deserialize the legacy chainspecs genereted + /// with `ChainsSpec::from_genesis` method. Runtime(G), + /// (Deprecated) Contains the JSON representation of G (the native type representing the + /// runtime's `RuntimeGenesisConfig` struct) (will be removed with `ChainSpec::from_genesis`) + /// and the runtime code. It is required to create and deserialize JSON chainspecs created with + /// deprecated `ChainSpec::from_genesis` method. + RuntimeAndCode(RuntimeInnerWrapper), + /// The genesis storage as raw data. Typically raw key-value entries in state. Raw(RawGenesis), /// State root hash of the genesis storage. StateRootHash(StorageData), + /// Represents the runtime genesis config in JSON format toghether with runtime code. + RuntimeGenesis(RuntimeGenesisInner), } /// A configuration of a client. Does not include runtime storage initialization. +/// Note: `genesis` field is ignored due to way how the chain specification is serialized into +/// JSON file. Refer to [`ChainSpecJsonContainer`], which flattens [`ClientSpec`] and denies uknown +/// fields. #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] +// we cannot #[serde(deny_unknown_fields)]. Otherwise chain-spec-builder will fail on any +// non-standard spec struct ClientSpec { name: String, id: String, @@ -194,19 +324,160 @@ struct ClientSpec { /// We use `Option` here since `()` is not flattenable by serde. pub type NoExtension = Option<()>; +/// Builder for creating [`ChainSpec`] instances. +pub struct ChainSpecBuilder { + code: Vec, + extensions: E, + name: String, + id: String, + chain_type: ChainType, + genesis_build_action: GenesisBuildAction, + boot_nodes: Option>, + telemetry_endpoints: Option, + protocol_id: Option, + fork_id: Option, + properties: Option, + _genesis: PhantomData<(G, EHF)>, +} + +impl ChainSpecBuilder { + /// Creates a new builder instance with no defaults. + pub fn new(code: &[u8], extensions: E) -> Self { + Self { + code: code.into(), + extensions, + name: "Development".to_string(), + id: "dev".to_string(), + chain_type: ChainType::Local, + genesis_build_action: GenesisBuildAction::Patch(Default::default()), + boot_nodes: None, + telemetry_endpoints: None, + protocol_id: None, + fork_id: None, + properties: None, + _genesis: Default::default(), + } + } + + /// Sets the spec name. + pub fn with_name(mut self, name: &str) -> Self { + self.name = name.into(); + self + } + + /// Sets the spec ID. + pub fn with_id(mut self, id: &str) -> Self { + self.id = id.into(); + self + } + + /// Sets the type of the chain. + pub fn with_chain_type(mut self, chain_type: ChainType) -> Self { + self.chain_type = chain_type; + self + } + + /// Sets a list of bootnode addresses. + pub fn with_boot_nodes(mut self, boot_nodes: Vec) -> Self { + self.boot_nodes = Some(boot_nodes); + self + } + + /// Sets telemetry endpoints. + pub fn with_telemetry_endpoints(mut self, telemetry_endpoints: TelemetryEndpoints) -> Self { + self.telemetry_endpoints = Some(telemetry_endpoints); + self + } + + /// Sets the network protocol ID. + pub fn with_protocol_id(mut self, protocol_id: &str) -> Self { + self.protocol_id = Some(protocol_id.into()); + self + } + + /// Sets an optional network fork identifier. + pub fn with_fork_id(mut self, fork_id: &str) -> Self { + self.fork_id = Some(fork_id.into()); + self + } + + /// Sets additional loosely-typed properties of the chain. + pub fn with_properties(mut self, properties: Properties) -> Self { + self.properties = Some(properties); + self + } + + /// Sets chain spec extensions. + pub fn with_extensions(mut self, extensions: E) -> Self { + self.extensions = extensions; + self + } + + /// Sets the code. + pub fn with_code(mut self, code: &[u8]) -> Self { + self.code = code.into(); + self + } + + /// Sets the JSON patch for runtime's GenesisConfig. + pub fn with_genesis_config_patch(mut self, patch: json::Value) -> Self { + self.genesis_build_action = GenesisBuildAction::Patch(patch); + self + } + + /// Sets the full runtime's GenesisConfig JSON. + pub fn with_genesis_config(mut self, config: json::Value) -> Self { + self.genesis_build_action = GenesisBuildAction::Full(config); + self + } + + /// Builds a [`ChainSpec`] instance using the provided settings. + pub fn build(self) -> ChainSpec { + let client_spec = ClientSpec { + name: self.name, + id: self.id, + chain_type: self.chain_type, + boot_nodes: self.boot_nodes.unwrap_or_default(), + telemetry_endpoints: self.telemetry_endpoints, + protocol_id: self.protocol_id, + fork_id: self.fork_id, + properties: self.properties, + extensions: self.extensions, + consensus_engine: (), + genesis: Default::default(), + code_substitutes: BTreeMap::new(), + }; + + ChainSpec { + client_spec, + genesis: GenesisSource::GenesisBuilderApi(self.genesis_build_action, self.code.into()), + _host_functions: Default::default(), + } + } +} + /// A configuration of a chain. Can be used to build a genesis block. -pub struct ChainSpec { +/// +/// The chain spec is generic over the native `RuntimeGenesisConfig` struct (`G`). It is also +/// possible to parametrize chain spec over the extended host functions (EHF). It should be use if +/// runtime is using the non-standard host function during genesis state creation. +pub struct ChainSpec { client_spec: ClientSpec, genesis: GenesisSource, + _host_functions: PhantomData, } -impl Clone for ChainSpec { +impl Clone for ChainSpec { fn clone(&self) -> Self { - ChainSpec { client_spec: self.client_spec.clone(), genesis: self.genesis.clone() } + ChainSpec { + client_spec: self.client_spec.clone(), + genesis: self.genesis.clone(), + _host_functions: self._host_functions, + } } } -impl ChainSpec { +impl ChainSpec { /// A list of bootnode addresses. pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] { &self.client_spec.boot_nodes @@ -260,6 +531,10 @@ impl ChainSpec { } /// Create hardcoded spec. + #[deprecated( + note = "`from_genesis` is planned to be removed in May 2024. Use `builder()` instead." + )] + // deprecated note: Genesis::Runtime + GenesisSource::Factory shall also be removed pub fn from_genesis G + 'static + Send + Sync>( name: &str, id: &str, @@ -271,6 +546,7 @@ impl ChainSpec { fork_id: Option<&str>, properties: Option, extensions: E, + code: &[u8], ) -> Self { let client_spec = ClientSpec { name: name.to_owned(), @@ -287,22 +563,36 @@ impl ChainSpec { code_substitutes: BTreeMap::new(), }; - ChainSpec { client_spec, genesis: GenesisSource::Factory(Arc::new(constructor)) } + ChainSpec { + client_spec, + genesis: GenesisSource::Factory(Arc::new(constructor), code.into()), + _host_functions: Default::default(), + } } /// Type of the chain. fn chain_type(&self) -> ChainType { self.client_spec.chain_type.clone() } + + /// Provides a `ChainSpec` builder. + pub fn builder(code: &[u8], extensions: E) -> ChainSpecBuilder { + ChainSpecBuilder::new(code, extensions) + } } -impl ChainSpec { +impl ChainSpec { /// Parse json content into a `ChainSpec` pub fn from_json_bytes(json: impl Into>) -> Result { let json = json.into(); let client_spec = json::from_slice(json.as_ref()) .map_err(|e| format!("Error parsing spec file: {}", e))?; - Ok(ChainSpec { client_spec, genesis: GenesisSource::Binary(json) }) + + Ok(ChainSpec { + client_spec, + genesis: GenesisSource::Binary(json), + _host_functions: Default::default(), + }) } /// Parse json file into a `ChainSpec` @@ -318,60 +608,94 @@ impl ChainSpec { memmap2::Mmap::map(&file) .map_err(|e| format!("Error mmaping spec file `{}`: {}", path.display(), e))? }; - let client_spec = json::from_slice(&bytes).map_err(|e| format!("Error parsing spec file: {}", e))?; - Ok(ChainSpec { client_spec, genesis: GenesisSource::File(path) }) + + Ok(ChainSpec { + client_spec, + genesis: GenesisSource::File(path), + _host_functions: Default::default(), + }) } } +/// Helper structure for serializing (and only serializing) the ChainSpec into JSON file. It +/// represents the layout of `ChainSpec` JSON file. #[derive(Serialize, Deserialize)] -struct JsonContainer { +// we cannot #[serde(deny_unknown_fields)]. Otherwise chain-spec-builder will fail on any +// non-standard spec. +struct ChainSpecJsonContainer { #[serde(flatten)] client_spec: ClientSpec, genesis: Genesis, } -impl ChainSpec { - fn json_container(&self, raw: bool) -> Result, String> { - let genesis = match (raw, self.genesis.resolve()?) { +impl ChainSpec +where + EHF: HostFunctions, +{ + fn json_container(&self, raw: bool) -> Result, String> { + let raw_genesis = match (raw, self.genesis.resolve()?) { + ( + true, + Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Config(config), + code, + }), + ) => { + let mut storage = + RuntimeCaller::::new(&code[..]).get_storage_for_config(config)?; + storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code); + RawGenesis::from(storage) + }, + ( + true, + Genesis::RuntimeGenesis(RuntimeGenesisInner { + json_blob: RuntimeGenesisConfigJson::Patch(patch), + code, + }), + ) => { + let mut storage = + RuntimeCaller::::new(&code[..]).get_storage_for_patch(patch)?; + storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code); + RawGenesis::from(storage) + }, + + #[allow(deprecated)] + (true, Genesis::RuntimeAndCode(RuntimeInnerWrapper { runtime: g, code })) => { + let mut storage = g.build_storage()?; + storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code); + RawGenesis::from(storage) + }, + #[allow(deprecated)] (true, Genesis::Runtime(g)) => { let storage = g.build_storage()?; - let top = - storage.top.into_iter().map(|(k, v)| (StorageKey(k), StorageData(v))).collect(); - let children_default = storage - .children_default - .into_iter() - .map(|(sk, child)| { - ( - StorageKey(sk), - child - .data - .into_iter() - .map(|(k, v)| (StorageKey(k), StorageData(v))) - .collect(), - ) - }) - .collect(); - - Genesis::Raw(RawGenesis { top, children_default }) + RawGenesis::from(storage) }, - (_, genesis) => genesis, + (true, Genesis::Raw(raw)) => raw, + + (_, genesis) => + return Ok(ChainSpecJsonContainer { client_spec: self.client_spec.clone(), genesis }), }; - Ok(JsonContainer { client_spec: self.client_spec.clone(), genesis }) + + Ok(ChainSpecJsonContainer { + client_spec: self.client_spec.clone(), + genesis: Genesis::Raw(raw_genesis), + }) } - /// Dump to json string. + /// Dump the chain specification to JSON string. pub fn as_json(&self, raw: bool) -> Result { let container = self.json_container(raw)?; json::to_string_pretty(&container).map_err(|e| format!("Error generating spec json: {}", e)) } } -impl crate::ChainSpec for ChainSpec +impl crate::ChainSpec for ChainSpec where G: RuntimeGenesis + 'static, E: GetExtension + serde::Serialize + Clone + Send + Sync + 'static, + EHF: HostFunctions, { fn boot_nodes(&self) -> &[MultiaddrWithPeerId] { ChainSpec::boot_nodes(self) @@ -442,9 +766,85 @@ where } } +/// The `fun` will be called with the value at `path`. +/// +/// If exists, the value at given `path` will be passed to the `fun` and the result of `fun` +/// call will be returned. Otherwise false is returned. +/// `path` will be modified. +/// +/// # Examples +/// ```ignore +/// use serde_json::{from_str, json, Value}; +/// let doc = json!({"a":{"b":{"c":"5"}}}); +/// let mut path = ["a", "b", "c"].into(); +/// assert!(json_eval_value_at_key(&doc, &mut path, &|v| { assert_eq!(v,"5"); true })); +/// ``` +fn json_eval_value_at_key( + doc: &json::Value, + path: &mut VecDeque<&str>, + fun: &dyn Fn(&json::Value) -> bool, +) -> bool { + 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))) + } else { + doc.as_object() + .map_or(false, |o| o.get(key).map_or(false, |v| json_eval_value_at_key(v, path, fun))) + } +} + +macro_rules! json_path { + [ $($x:expr),+ ] => { + VecDeque::<&str>::from([$($x),+]) + }; +} + +fn json_contains_path(doc: &json::Value, path: &mut VecDeque<&str>) -> bool { + json_eval_value_at_key(doc, path, &|_| true) +} + +/// This function updates the code in given chain spec. +/// +/// Function support updating the runtime code in provided JSON chain spec blob. `Genesis::Raw` +/// and `Genesis::RuntimeGenesis` formats are supported. +/// +/// If update was successful `true` is returned, otherwise `false`. Chain spec JSON is modified in +/// place. +pub fn update_code_in_json_chain_spec(chain_spec: &mut json::Value, code: &[u8]) -> bool { + let mut path = json_path!["genesis", "runtimeGenesis", "code"]; + let mut raw_path = json_path!["genesis", "raw", "top"]; + + if json_contains_path(&chain_spec, &mut path) { + #[derive(Serialize)] + struct Container<'a> { + #[serde(with = "sp_core::bytes")] + code: &'a [u8], + } + let code_patch = json::json!({"genesis":{"runtimeGenesis": Container { code }}}); + crate::json_patch::merge(chain_spec, code_patch); + true + } else if json_contains_path(&chain_spec, &mut raw_path) { + #[derive(Serialize)] + struct Container<'a> { + #[serde(with = "sp_core::bytes", rename = "0x3a636f6465")] + code: &'a [u8], + } + let code_patch = json::json!({"genesis":{"raw":{"top": Container { code }}}}); + crate::json_patch::merge(chain_spec, code_patch); + true + } else { + false + } +} + #[cfg(test)] mod tests { use super::*; + use serde_json::{from_str, json, Value}; + use sp_application_crypto::Ss58Codec; + use sp_core::storage::well_known_keys; + use sp_keyring::AccountKeyring; #[derive(Debug, Serialize, Deserialize)] struct Genesis(BTreeMap); @@ -536,4 +936,328 @@ mod tests { ); } } + + #[test] + // some tests for json path utils + fn test_json_eval_value_at_key() { + let doc = json!({"a":{"b1":"20","b":{"c":{"d":"10"}}}}); + + assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b1"], &|v| { *v == "20" })); + assert!(json_eval_value_at_key(&doc, &mut json_path!["a", "b", "c", "d"], &|v| { + *v == "10" + })); + assert!(!json_eval_value_at_key(&doc, &mut json_path!["a", "c", "d"], &|_| { true })); + assert!(!json_eval_value_at_key(&doc, &mut json_path!["d"], &|_| { true })); + + assert!(json_contains_path(&doc, &mut json_path!["a", "b1"])); + assert!(json_contains_path(&doc, &mut json_path!["a", "b"])); + assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c"])); + assert!(json_contains_path(&doc, &mut json_path!["a", "b", "c", "d"])); + assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "c", "d", "e"])); + assert!(!json_contains_path(&doc, &mut json_path!["a", "b", "b1"])); + assert!(!json_contains_path(&doc, &mut json_path!["d"])); + } + + fn zeroize_code_key_in_json(encoded: bool, json: &str) -> Value { + let mut json = from_str::(json).unwrap(); + let (zeroing_patch, mut path) = if encoded { + ( + json!({"genesis":{"raw":{"top":{"0x3a636f6465":"0x0"}}}}), + json_path!["genesis", "raw", "top", "0x3a636f6465"], + ) + } else { + ( + json!({"genesis":{"runtimeGenesis":{"code":"0x0"}}}), + json_path!["genesis", "runtimeGenesis", "code"], + ) + }; + assert!(json_contains_path(&json, &mut path)); + crate::json_patch::merge(&mut json, zeroing_patch); + json + } + + #[docify::export] + #[test] + fn build_chain_spec_with_patch_works() { + let output = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(json!({ + "babe": { + "epochConfig": { + "c": [ + 7, + 10 + ], + "allowed_slots": "PrimaryAndSecondaryPlainSlots" + } + }, + "substrateTest": { + "authorities": [ + AccountKeyring::Ferdie.public().to_ss58check(), + AccountKeyring::Alice.public().to_ss58check() + ], + } + })) + .build(); + + let raw_chain_spec = output.as_json(true); + assert!(raw_chain_spec.is_ok()); + } + + #[docify::export] + #[test] + fn generate_chain_spec_with_patch_works() { + let output = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(json!({ + "babe": { + "epochConfig": { + "c": [ + 7, + 10 + ], + "allowed_slots": "PrimaryAndSecondaryPlainSlots" + } + }, + "substrateTest": { + "authorities": [ + AccountKeyring::Ferdie.public().to_ss58check(), + AccountKeyring::Alice.public().to_ss58check() + ], + } + })) + .build(); + + let actual = output.as_json(false).unwrap(); + let actual_raw = output.as_json(true).unwrap(); + + let expected = + from_str::(include_str!("../res/substrate_test_runtime_from_patch.json")) + .unwrap(); + let expected_raw = + from_str::(include_str!("../res/substrate_test_runtime_from_patch_raw.json")) + .unwrap(); + + //wasm blob may change overtime so let's zero it. Also ensure it is there: + let actual = zeroize_code_key_in_json(false, actual.as_str()); + let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str()); + + assert_eq!(actual, expected); + assert_eq!(actual_raw, expected_raw); + } + + #[test] + fn generate_chain_spec_with_full_config_works() { + let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json"); + let output = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config(from_str(j).unwrap()) + .build(); + + let actual = output.as_json(false).unwrap(); + let actual_raw = output.as_json(true).unwrap(); + + let expected = + from_str::(include_str!("../res/substrate_test_runtime_from_config.json")) + .unwrap(); + let expected_raw = + from_str::(include_str!("../res/substrate_test_runtime_from_config_raw.json")) + .unwrap(); + + //wasm blob may change overtime so let's zero it. Also ensure it is there: + let actual = zeroize_code_key_in_json(false, actual.as_str()); + let actual_raw = zeroize_code_key_in_json(true, actual_raw.as_str()); + + assert_eq!(actual, expected); + assert_eq!(actual_raw, expected_raw); + } + + #[test] + fn chain_spec_as_json_fails_with_invalid_config() { + let j = + include_str!("../../../test-utils/runtime/res/default_genesis_config_invalid_2.json"); + let output = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config(from_str(j).unwrap()) + .build(); + + assert_eq!( + output.as_json(true), + Err("Invalid JSON blob: unknown field `babex`, expected one of `system`, `babe`, `substrateTest`, `balances` at line 1 column 8".to_string()) + ); + } + + #[test] + fn chain_spec_as_json_fails_with_invalid_patch() { + let output = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(json!({ + "invalid_pallet": {}, + "substrateTest": { + "authorities": [ + AccountKeyring::Ferdie.public().to_ss58check(), + AccountKeyring::Alice.public().to_ss58check() + ], + } + })) + .build(); + + assert!(output.as_json(true).unwrap_err().contains("Invalid JSON blob: unknown field `invalid_pallet`, expected one of `system`, `babe`, `substrateTest`, `balances`")); + } + + #[test] + fn check_if_code_is_valid_for_raw_without_code() { + let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned( + include_bytes!("../res/raw_no_code.json").to_vec(), + )) + .unwrap(); + + let j = from_str::(&spec.as_json(true).unwrap()).unwrap(); + + assert!(json_eval_value_at_key( + &j, + &mut json_path!["genesis", "raw", "top", "0x3a636f6465"], + &|v| { *v == "0x010101" } + )); + assert!(!json_contains_path(&j, &mut json_path!["code"])); + } + + #[test] + fn check_code_in_assimilated_storage_for_raw_without_code() { + let spec = ChainSpec::<()>::from_json_bytes(Cow::Owned( + include_bytes!("../res/raw_no_code.json").to_vec(), + )) + .unwrap(); + + let storage = spec.build_storage().unwrap(); + assert!(storage + .top + .get(&well_known_keys::CODE.to_vec()) + .map(|v| *v == vec![1, 1, 1]) + .unwrap()) + } + + #[test] + fn update_code_works_with_runtime_genesis_config() { + let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json"); + let chain_spec = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config(from_str(j).unwrap()) + .build(); + + let mut chain_spec_json = from_str::(&chain_spec.as_json(false).unwrap()).unwrap(); + assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6])); + + assert!(json_eval_value_at_key( + &chain_spec_json, + &mut json_path!["genesis", "runtimeGenesis", "code"], + &|v| { *v == "0x000102040506" } + )); + } + + #[test] + fn update_code_works_for_raw() { + let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json"); + let chain_spec = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config(from_str(j).unwrap()) + .build(); + + let mut chain_spec_json = from_str::(&chain_spec.as_json(true).unwrap()).unwrap(); + assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6])); + + assert!(json_eval_value_at_key( + &chain_spec_json, + &mut json_path!["genesis", "raw", "top", "0x3a636f6465"], + &|v| { *v == "0x000102040506" } + )); + } + + #[test] + fn update_code_works_with_runtime_genesis_patch() { + let chain_spec = ChainSpec::<()>::builder( + substrate_test_runtime::wasm_binary_unwrap().into(), + Default::default(), + ) + .with_name("TestName") + .with_id("test_id") + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(json!({})) + .build(); + + let mut chain_spec_json = from_str::(&chain_spec.as_json(false).unwrap()).unwrap(); + assert!(update_code_in_json_chain_spec(&mut chain_spec_json, &[0, 1, 2, 4, 5, 6])); + + assert!(json_eval_value_at_key( + &chain_spec_json, + &mut json_path!["genesis", "runtimeGenesis", "code"], + &|v| { *v == "0x000102040506" } + )); + } + + #[test] + fn generate_from_genesis_is_still_supported() { + #[allow(deprecated)] + let chain_spec: ChainSpec = ChainSpec::from_genesis( + "TestName", + "test", + ChainType::Local, + || Default::default(), + Vec::new(), + None, + None, + None, + None, + Default::default(), + &vec![0, 1, 2, 4, 5, 6], + ); + + let chain_spec_json = from_str::(&chain_spec.as_json(false).unwrap()).unwrap(); + assert!(json_eval_value_at_key( + &chain_spec_json, + &mut json_path!["genesis", "runtimeAndCode", "code"], + &|v| { *v == "0x000102040506" } + )); + let chain_spec_json = from_str::(&chain_spec.as_json(true).unwrap()).unwrap(); + assert!(json_eval_value_at_key( + &chain_spec_json, + &mut json_path!["genesis", "raw", "top", "0x3a636f6465"], + &|v| { *v == "0x000102040506" } + )); + } } 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.rs b/substrate/client/chain-spec/src/genesis_block.rs similarity index 90% rename from substrate/client/chain-spec/src/genesis.rs rename to substrate/client/chain-spec/src/genesis_block.rs index 6aa156a620a7933034643f8a7bdbf8e0d617ad97..3c7b9f64dcd6bc0babff662747571f9d31853eec 100644 --- a/substrate/client/chain-spec/src/genesis.rs +++ b/substrate/client/chain-spec/src/genesis_block.rs @@ -18,23 +18,25 @@ //! Tool for creating the genesis block. -use std::{collections::hash_map::DefaultHasher, marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc}; +use codec::Encode; use sc_client_api::{backend::Backend, BlockImportOperation}; use sc_executor::RuntimeVersionOf; use sp_core::storage::{well_known_keys, StateVersion, Storage}; use sp_runtime::{ - traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, Zero}, BuildStorage, }; /// Return the state version given the genesis storage and executor. -pub fn resolve_state_version_from_wasm( +pub fn resolve_state_version_from_wasm( storage: &Storage, executor: &E, ) -> sp_blockchain::Result where E: RuntimeVersionOf, + H: HashT, { if let Some(wasm) = storage.top.get(well_known_keys::CODE) { let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. @@ -43,12 +45,7 @@ where let runtime_code = sp_core::traits::RuntimeCode { code_fetcher: &code_fetcher, heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, + hash: ::hash(wasm).encode(), }; let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; @@ -129,7 +126,8 @@ impl, E: RuntimeVersionOf> BuildGenesisBlock sp_blockchain::Result<(Block, Self::BlockImportOperation)> { let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; - let genesis_state_version = resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let genesis_state_version = + resolve_state_version_from_wasm::<_, HashingFor>(&genesis_storage, &executor)?; let mut op = backend.begin_operation()?; let state_root = op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..c8b54f66be6f52ffb6900fafd2e8ec1652030976 --- /dev/null +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -0,0 +1,197 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! A helper module for calling the GenesisBuilder API from arbitrary runtime wasm blobs. + +use codec::{Decode, Encode}; +pub use sc_executor::sp_wasm_interface::HostFunctions; +use sc_executor::{error::Result, WasmExecutor}; +use serde_json::{from_slice, Value}; +use sp_core::{ + storage::Storage, + traits::{CallContext, CodeExecutor, Externalities, FetchRuntimeCode, RuntimeCode}, +}; +use sp_genesis_builder::Result as BuildResult; +use sp_state_machine::BasicExternalities; +use std::borrow::Cow; + +/// A utility that facilitates calling the GenesisBuilder API from the runtime wasm code blob. +/// +/// `EHF` type allows to specify the extended host function required for building runtime's genesis +/// config. The type will be compbined with default `sp_io::SubstrateHostFunctions`. +pub struct GenesisConfigBuilderRuntimeCaller<'a, EHF = ()> +where + EHF: HostFunctions, +{ + code: Cow<'a, [u8]>, + code_hash: Vec, + executor: WasmExecutor<(sp_io::SubstrateHostFunctions, EHF)>, +} + +impl<'a, EHF> FetchRuntimeCode for GenesisConfigBuilderRuntimeCaller<'a, EHF> +where + EHF: HostFunctions, +{ + fn fetch_runtime_code(&self) -> Option> { + Some(self.code.as_ref().into()) + } +} + +impl<'a, EHF> GenesisConfigBuilderRuntimeCaller<'a, EHF> +where + EHF: HostFunctions, +{ + /// Creates new instance using the provided code blob. + /// + /// This code is later referred to as `runtime`. + pub fn new(code: &'a [u8]) -> Self { + GenesisConfigBuilderRuntimeCaller { + code: code.into(), + code_hash: sp_crypto_hashing::blake2_256(code).to_vec(), + executor: WasmExecutor::<(sp_io::SubstrateHostFunctions, EHF)>::builder() + .with_allow_missing_host_functions(true) + .build(), + } + } + + fn call(&self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result> { + self.executor + .call( + ext, + &RuntimeCode { heap_pages: None, code_fetcher: self, hash: self.code_hash.clone() }, + method, + data, + CallContext::Offchain, + ) + .0 + } + + /// Returns a json representation of the default `RuntimeGenesisConfig` provided by the + /// `runtime`. + /// + /// Calls [`GenesisBuilder::create_default_config`](sp_genesis_builder::GenesisBuilder::create_default_config) in the `runtime`. + pub fn get_default_config(&self) -> core::result::Result { + let mut t = BasicExternalities::new_empty(); + let call_result = self + .call(&mut t, "GenesisBuilder_create_default_config", &[]) + .map_err(|e| format!("wasm call error {e}"))?; + let default_config = Vec::::decode(&mut &call_result[..]) + .map_err(|e| format!("scale codec error: {e}"))?; + Ok(from_slice(&default_config[..]).expect("returned value is json. qed.")) + } + + /// Builds `RuntimeGenesisConfig` from given json blob and returns the genesis state. + /// + /// Calls [`GenesisBuilder::build_config`](sp_genesis_builder::GenesisBuilder::build_config) + /// provided by the `runtime`. + pub fn get_storage_for_config(&self, config: Value) -> core::result::Result { + let mut ext = BasicExternalities::new_empty(); + + let call_result = self + .call(&mut ext, "GenesisBuilder_build_config", &config.to_string().encode()) + .map_err(|e| format!("wasm call error {e}"))?; + + BuildResult::decode(&mut &call_result[..]) + .map_err(|e| format!("scale codec error: {e}"))??; + + Ok(ext.into_storages()) + } + + /// Creates the genesis state by patching the default `RuntimeGenesisConfig`. + /// + /// This function generates the `RuntimeGenesisConfig` for the runtime by applying a provided + /// JSON patch. The patch modifies the default `RuntimeGenesisConfig` allowing customization of + /// the specific keys. The resulting `RuntimeGenesisConfig` is then deserialized from the + /// patched JSON representation and stored in the storage. + /// + /// If the provided JSON patch is incorrect or the deserialization fails the error will be + /// returned. + /// + /// The patching process modifies the default `RuntimeGenesisConfig` according to the following + /// rules: + /// 1. Existing keys in the default configuration will be overridden by the corresponding values + /// in the patch. + /// 2. If a key exists in the patch but not in the default configuration, it will be added to + /// the resulting `RuntimeGenesisConfig`. + /// 3. Keys in the default configuration that have null values in the patch will be removed from + /// the resulting `RuntimeGenesisConfig`. This is helpful for changing enum variant value. + /// + /// Please note that the patch may contain full `RuntimeGenesisConfig`. + pub fn get_storage_for_patch(&self, patch: Value) -> core::result::Result { + let mut config = self.get_default_config()?; + crate::json_patch::merge(&mut config, patch); + self.get_storage_for_config(config) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::{from_str, json}; + pub use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration}; + + #[test] + fn get_default_config_works() { + let config = + ::new(substrate_test_runtime::wasm_binary_unwrap()) + .get_default_config() + .unwrap(); + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; + assert_eq!(from_str::(expected).unwrap(), config); + } + + #[test] + fn get_storage_for_patch_works() { + let patch = json!({ + "babe": { + "epochConfig": { + "c": [ + 69, + 696 + ], + "allowed_slots": "PrimaryAndSecondaryPlainSlots" + } + }, + }); + + let storage = + ::new(substrate_test_runtime::wasm_binary_unwrap()) + .get_storage_for_patch(patch) + .unwrap(); + + //Babe|Authorities + let value: Vec = storage + .top + .get( + &array_bytes::hex2bytes( + "1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef", + ) + .unwrap(), + ) + .unwrap() + .clone(); + + assert_eq!( + BabeEpochConfiguration::decode(&mut &value[..]).unwrap(), + BabeEpochConfiguration { + c: (69, 696), + allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots + } + ); + } +} diff --git a/substrate/client/chain-spec/src/json_patch.rs b/substrate/client/chain-spec/src/json_patch.rs new file mode 100644 index 0000000000000000000000000000000000000000..c3930069a60d029114c42fedaaeb1cffa7aa3319 --- /dev/null +++ b/substrate/client/chain-spec/src/json_patch.rs @@ -0,0 +1,191 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! A helper module providing json patching functions. + +use serde_json::Value; + +/// Recursively merges two JSON objects, `a` and `b`, into a single object. +/// +/// If a key exists in both objects, the value from `b` will override the value from `a`. +/// If a key exists in `b` with a `null` value, it will be removed from `a`. +/// If a key exists only in `b` and not in `a`, it will be added to `a`. +/// +/// # Arguments +/// +/// * `a` - A mutable reference to the target JSON object to merge into. +/// * `b` - The JSON object to merge with `a`. +pub fn merge(a: &mut Value, b: Value) { + match (a, b) { + (Value::Object(a), Value::Object(b)) => + for (k, v) in b { + if v.is_null() { + a.remove(&k); + } else { + merge(a.entry(k).or_insert(Value::Null), v); + } + }, + (a, b) => *a = b, + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test1_simple_merge() { + let mut j1 = json!({ "a":123 }); + merge(&mut j1, json!({ "b":256 })); + assert_eq!(j1, json!({ "a":123, "b":256 })); + } + + #[test] + fn test2_patch_simple_merge_nested() { + let mut j1 = json!({ + "a": { + "name": "xxx", + "value": 123 + }, + "b": { "c" : { "inner_name": "yyy" } } + }); + + let j2 = json!({ + "a": { + "keys": ["a", "b", "c" ] + } + }); + + merge(&mut j1, j2); + assert_eq!( + j1, + json!({"a":{"keys":["a","b","c"],"name":"xxx","value":123}, "b": { "c" : { "inner_name": "yyy" } }}) + ); + } + + #[test] + fn test3_patch_overrides_existing_keys() { + let mut j1 = json!({ + "a": { + "name": "xxx", + "value": 123, + "keys": ["d"] + + } + }); + + let j2 = json!({ + "a": { + "keys": ["a", "b", "c" ] + } + }); + + merge(&mut j1, j2); + assert_eq!(j1, json!({"a":{"keys":["a","b","c"],"name":"xxx","value":123}})); + } + + #[test] + fn test4_patch_overrides_existing_keys() { + let mut j1 = json!({ + "a": { + "name": "xxx", + "value": 123, + "b" : { + "inner_name": "yyy" + } + } + }); + + let j2 = json!({ + "a": { + "name": "new_name", + "b" : { + "inner_name": "inner_new_name" + } + } + }); + + merge(&mut j1, j2); + assert_eq!( + j1, + json!({ "a": {"name":"new_name", "value":123, "b" : { "inner_name": "inner_new_name" }} }) + ); + } + + #[test] + fn test5_patch_overrides_existing_nested_keys() { + let mut j1 = json!({ + "a": { + "name": "xxx", + "value": 123, + "b": { + "c": { + "d": { + "name": "yyy", + "value": 256 + } + } + } + }, + }); + + let j2 = json!({ + "a": { + "value": 456, + "b": { + "c": { + "d": { + "name": "new_name" + } + } + } + } + }); + + merge(&mut j1, j2); + assert_eq!( + j1, + json!({ "a": {"name":"xxx", "value":456, "b": { "c": { "d": { "name": "new_name", "value": 256 }}}}}) + ); + } + + #[test] + fn test6_patch_removes_keys_if_null() { + let mut j1 = json!({ + "a": { + "name": "xxx", + "value": 123, + "enum_variant_1": { + "name": "yyy", + } + }, + }); + + let j2 = json!({ + "a": { + "value": 456, + "enum_variant_1": null, + "enum_variant_2": 32, + } + }); + + merge(&mut j1, j2); + assert_eq!(j1, json!({ "a": {"name":"xxx", "value":456, "enum_variant_2": 32 }})); + } +} diff --git a/substrate/client/chain-spec/src/lib.rs b/substrate/client/chain-spec/src/lib.rs index 341c5f28e4a443129986042013366007346d7f58..eab5f789f29a0589d68c3c0aa86952c75f112afa 100644 --- a/substrate/client/chain-spec/src/lib.rs +++ b/substrate/client/chain-spec/src/lib.rs @@ -16,38 +16,253 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Substrate chain configurations. +//! This crate includes structs and utilities for defining configuration files (known as chain +//! specification) for both runtime and node. +//! +//! # Intro: Chain Specification +//! +//! The chain specification comprises parameters and settings that define the properties and an +//! initial state of a chain. Users typically interact with the JSON representation of the chain +//! spec. Internally, the chain spec is embodied by the [`GenericChainSpec`] struct, and specific +//! properties can be accessed using the [`ChainSpec`] trait. +//! +//! In summary, although not restricted to, the primary role of the chain spec is to provide a list +//! of well-known boot nodes for the blockchain network and the means for initializing the genesis +//! storage. This initialization is necessary for creating a genesis block upon which subsequent +//! blocks are built. When the node is launched for the first time, it reads the chain spec, +//! initializes the genesis block, and establishes connections with the boot nodes. +//! +//! The JSON chain spec is divided into two main logical sections: +//! - one section details general chain properties, +//! - second explicitly or indirectly defines the genesis storage, which, in turn, determines the +//! genesis hash of the chain, +//! +//! The chain specification consists of the following fields: +//! +//!

CommittedCandidateReceipt<H = Hash>
+//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
Chain spec keyDescription
nameThe human readable name of the chain.
idThe id of the chain.
chainTypeThe chain type of this chain +//! (refer to +//! +//! ChainType +//! ). +//!
bootNodesA list of +//! multi addresses +//! that belong to boot nodes of the chain.
telemetryEndpointsOptional list of multi address, verbosity of telemetry endpoints. The +//! verbosity goes from 0 to 9. With 0 being the mode with the lowest verbosity.
protocolIdOptional networking protocol id that identifies the chain.
forkIdOptional fork id. Should most likely be left empty. Can be used to signal a fork on +//! the network level when two chains have the same genesis hash.
propertiesCustom properties. Shall be provided in the form of +//! key-value json object. +//!
consensusEngineDeprecated field. Should be ignored.
codeSubstitutesOptional map of block_number to wasm_code. More details in +//! material to follow.
genesisDefines the initial state of the runtime. More details in material to follow.
+//! +//! # `genesis`: Initial Runtime State +//! +//! All nodes in the network must build subsequent blocks upon exactly the same genesis block. +//! +//! The information configured in the `genesis` section of a chain specification is used to build +//! the genesis storage, which is essential for creating the genesis block, since the block header +//! includes the storage root hash. +//! +//! The `genesis` key of the chain specification definition describes the +//! initial state of the runtime. For example, it may contain: +//! - an initial list of funded accounts, +//! - the administrative account that controls the sudo key, +//! - an initial authorities set for consensus, etc. +//! +//! As the compiled WASM blob of the runtime code is stored in the chain's state, the initial +//! runtime must also be provided within the chain specification. +//! +//! In essence, the most important formats of genesis initial state are: +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
FormatDescription
+//! runtime / full config +//! A JSON object that provides an explicit and comprehensive representation of the +//! RuntimeGenesisConfig struct, which is generated by frame::runtime::prelude::construct_runtime macro (example of generated struct). Must contain all the keys of +//! the genesis config, no defaults will be used. +//! +//! This format explicitly provides the code of the runtime. +//!
+//! patch +//! A JSON object that offers a partial representation of the +//! RuntimeGenesisConfig provided by the runtime. It contains a patch, which is +//! essentially a list of key-value pairs to customize in the default runtime's +//! RuntimeGenesisConfig. +//! This format explicitly provides the code of the runtime. +//!
+//! raw +//! A JSON object with two fields: top and children_default. +//! Each field is a map of key => value pairs representing entries in a genesis storage +//! trie. The runtime code is one of such entries.
+//! +//! For production or long-lasting blockchains, using the `raw` format in the chain specification is +//! recommended. Only the `raw` format guarantees that storage root hash will remain unchanged when +//! the `RuntimeGenesisConfig` format changes due to software upgrade. +//! +//! JSON examples in the [following section](#json-chain-specification-example) illustrate the `raw` +//! `patch` and full genesis fields. +//! +//! # From Initial State to Raw Genesis. +//! +//! To generate a raw genesis storage from the JSON representation of the runtime genesis config, +//! the node needs to interact with the runtime. +//! +//! This interaction involves passing the runtime genesis config JSON blob to the runtime using the +//! [`sp_genesis_builder::GenesisBuilder::build_config`] function. During this operation, the +//! runtime converts the JSON representation of the genesis config into [`sp_io::storage`] items. It +//! is a crucial step for computing the storage root hash, which is a key component in determining +//! the genesis hash. +//! +//! Consequently, the runtime must support the [`sp_genesis_builder::GenesisBuilder`] API to +//! utilize either `patch` or `full` formats. +//! +//! This entire process is encapsulated within the implementation of the [`BuildStorage`] trait, +//! which can be accessed through the [`ChainSpec::as_storage_builder`] method. There is an +//! intermediate internal helper that facilitates this interaction, +//! [`GenesisConfigBuilderRuntimeCaller`], which serves as a straightforward wrapper for +//! [`sc_executor::WasmExecutor`]. +//! +//! In case of `raw` genesis state the node does not interact with the runtime regarding the +//! computation of initial state. +//! +//! The plain and `raw` chain specification JSON blobs can be found in +//! [JSON examples](#json-chain-specification-example) section. +//! +//! # Optional Code Mapping +//! +//! Optional map of `block_number` to `wasm_code`. +//! +//! The given `wasm_code` will be used to substitute the on-chain wasm code starting with the +//! given block number until the `spec_version` on-chain changes. The given `wasm_code` should +//! be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug +//! that cannot be fixed with a runtime upgrade, if for example the runtime is constantly +//! panicking. Introducing new runtime APIs isn't supported, because the node +//! will read the runtime version from the on-chain wasm code. +//! +//! Use this functionality only when there is no other way around it, and only patch the problematic +//! bug; the rest should be done with an on-chain runtime upgrade. +//! +//! # Building a Chain Specification +//! +//! The [`ChainSpecBuilder`] should be used to create an instance of a chain specification. Its API +//! allows configuration of all fields of the chain spec. To generate a JSON representation of the +//! specification, use [`ChainSpec::as_json`]. +//! +//! The sample code to generate a chain spec is as follows: +#![doc = docify::embed!("src/chain_spec.rs", build_chain_spec_with_patch_works)] +//! # JSON chain specification example +//! +//! The following are the plain and `raw` versions of the chain specification JSON files, resulting +//! from executing of the above [example](#building-a-chain-specification): +//! ```ignore +#![doc = include_str!("../res/substrate_test_runtime_from_patch.json")] +//! ``` +//! ```ignore +#![doc = include_str!("../res/substrate_test_runtime_from_patch_raw.json")] +//! ``` +//! The following example shows the plain full config version of chain spec: +//! ```ignore +#![doc = include_str!("../res/substrate_test_runtime_from_config.json")] +//! ``` +//! The [`ChainSpec`] trait represents the API to access values defined in the JSON chain specification. +//! //! -//! This crate contains structs and utilities to declare -//! a runtime-specific configuration file (a.k.a chain spec). +//! # Custom Chain Spec Extensions //! -//! Basic chain spec type containing all required parameters is -//! [`GenericChainSpec`]. It can be extended with -//! additional options that contain configuration specific to your chain. -//! Usually the extension is going to be an amalgamate of types exposed -//! by Substrate core modules. To allow the core modules to retrieve -//! their configuration from your extension you should use `ChainSpecExtension` -//! macro exposed by this crate. +//! The basic chain spec type containing all required parameters is [`GenericChainSpec`]. It can be +//! extended with additional options containing configuration specific to your chain. Usually, the +//! extension will be a combination of types exposed by Substrate core modules. //! +//! To allow the core modules to retrieve their configuration from your extension, you should use +//! `ChainSpecExtension` macro exposed by this crate. //! ```rust //! use std::collections::HashMap; //! use sc_chain_spec::{GenericChainSpec, ChainSpecExtension}; //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecExtension)] //! pub struct MyExtension { -//! pub known_blocks: HashMap, +//! pub known_blocks: HashMap, //! } //! //! pub type MyChainSpec = GenericChainSpec; //! ``` -//! -//! Some parameters may require different values depending on the -//! current blockchain height (a.k.a. forks). You can use `ChainSpecGroup` -//! macro and provided [`Forks`](./struct.Forks.html) structure to put -//! such parameters to your chain spec. -//! This will allow to override a single parameter starting at specific -//! block number. -//! +//! Some parameters may require different values depending on the current blockchain height (a.k.a. +//! forks). You can use the [`ChainSpecGroup`](macro@ChainSpecGroup) macro and the provided [`Forks`] +//! structure to add such parameters to your chain spec. This will allow overriding a single +//! parameter starting at a specific block number. //! ```rust //! use sc_chain_spec::{Forks, ChainSpecGroup, ChainSpecExtension, GenericChainSpec}; //! @@ -76,12 +291,9 @@ //! /// A chain spec supporting forkable `Extension`. //! pub type MyChainSpec2 = GenericChainSpec>; //! ``` -//! -//! It's also possible to have a set of parameters that is allowed to change -//! with block numbers (i.e. is forkable), and another set that is not subject to changes. -//! This is also possible by declaring an extension that contains `Forks` within it. -//! -//! +//! It's also possible to have a set of parameters that are allowed to change with block numbers +//! (i.e., they are forkable), and another set that is not subject to changes. This can also be +//! achieved by declaring an extension that contains [`Forks`] within it. //! ```rust //! use serde::{Serialize, Deserialize}; //! use sc_chain_spec::{Forks, GenericChainSpec, ChainSpecGroup, ChainSpecExtension}; @@ -106,98 +318,27 @@ //! //! pub type MyChainSpec = GenericChainSpec; //! ``` -//! -//! # Substrate chain specification format -//! -//! The Substrate chain specification is a `json` file that describes the basics of a chain. Most -//! importantly it lays out the genesis storage which leads to the genesis hash. The default -//! Substrate chain specification format is the following: -//! -//! ```json -//! // The human readable name of the chain. -//! "name": "Flaming Fir", -//! -//! // The id of the chain. -//! "id": "flamingfir9", -//! -//! // The chain type of this chain. -//! // Possible values are `Live`, `Development`, `Local`. -//! "chainType": "Live", -//! -//! // A list of multi addresses that belong to boot nodes of the chain. -//! "bootNodes": [ -//! "/dns/0.flamingfir.paritytech.net/tcp/30333/p2p/12D3KooWLK2gMLhWsYJzjW3q35zAs9FDDVqfqVfVuskiGZGRSMvR", -//! ], -//! -//! // Optional list of "multi address, verbosity" of telemetry endpoints. -//! // The verbosity goes from `0` to `9`. With `0` being the mode with the lowest verbosity. -//! "telemetryEndpoints": [ -//! [ -//! "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", -//! 0 -//! ] -//! ], -//! -//! // Optional networking protocol id that identifies the chain. -//! "protocolId": "fir9", -//! -//! // Optional fork id. Should most likely be left empty. -//! // Can be used to signal a fork on the network level when two chains have the -//! // same genesis hash. -//! "forkId": "random_fork", -//! -//! // Custom properties. -//! "properties": { -//! "tokenDecimals": 15, -//! "tokenSymbol": "FIR" -//! }, -//! -//! // Deprecated field. Should be ignored. -//! "consensusEngine": null, -//! -//! // The genesis declaration of the chain. -//! // -//! // `runtime`, `raw`, `stateRootHash` denote the type of the genesis declaration. -//! // -//! // These declarations are in the following formats: -//! // - `runtime` is a `json` object that can be parsed by a compatible `GenesisConfig`. This -//! // `GenesisConfig` is declared by a runtime and opaque to the node. -//! // - `raw` is a `json` object with two fields `top` and `children_default`. Each of these -//! // fields is a map of `key => value`. These key/value pairs represent the genesis storage. -//! // - `stateRootHash` is a single hex encoded hash that represents the genesis hash. The hash -//! // type depends on the hash used by the chain. -//! // -//! "genesis": { "runtime": {} }, -//! -//! /// Optional map of `block_number` to `wasm_code`. -//! /// -//! /// The given `wasm_code` will be used to substitute the on-chain wasm code starting with the -//! /// given block number until the `spec_version` on-chain changes. The given `wasm_code` should -//! /// be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug -//! /// that can not be fixed with a runtime upgrade, if for example the runtime is constantly -//! /// panicking. Introducing new runtime apis isn't supported, because the node -//! /// will read the runtime version from the on-chain wasm code. Use this functionality only when -//! /// there is no other way around it and only patch the problematic bug, the rest should be done -//! /// with a on-chain runtime upgrade. -//! "codeSubstitutes": [], -//! ``` -//! -//! See [`ChainSpec`] for a trait representation of the above. -//! //! The chain spec can be extended with other fields that are opaque to the default chain spec. //! Specific node implementations will need to be able to deserialize these extensions. mod chain_spec; mod extension; -mod genesis; +mod genesis_block; +mod genesis_config_builder; +mod json_patch; pub use self::{ - chain_spec::{ChainSpec as GenericChainSpec, NoExtension}, + chain_spec::{ + update_code_in_json_chain_spec, ChainSpec as GenericChainSpec, ChainSpecBuilder, + NoExtension, + }, extension::{get_extension, get_extension_mut, Extension, Fork, Forks, GetExtension, Group}, - genesis::{ + genesis_block::{ construct_genesis_block, resolve_state_version_from_wasm, BuildGenesisBlock, GenesisBlockBuilder, }, + genesis_config_builder::GenesisConfigBuilderRuntimeCaller, + json_patch::merge as json_merge, }; pub use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup}; diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index 98928700328fc9837fb6beed721faee473b3f1ba..47b29555e6603e9e72f8dca634f7beb20069b5e7 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-cli" -version = "0.10.0-dev" +version = "0.36.0" authors.workspace = true description = "Substrate CLI interface." edition.workspace = true @@ -9,33 +9,38 @@ 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.6", features = ["derive", "string"] } -fdlimit = "0.2.1" +chrono = "0.4.31" +clap = { version = "4.5.3", features = ["derive", "string", "wrap_help"] } +fdlimit = "0.3.0" futures = "0.3.21" -libp2p-identity = { version = "0.1.3", features = ["peerid", "ed25519"]} -log = "0.4.17" -names = { version = "0.13.0", default-features = false } +itertools = "0.10.3" +libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } +log = { workspace = true, default-features = true } +names = { version = "0.14.0", default-features = false } parity-scale-codec = "3.6.1" rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" -serde = "1.0.188" -serde_json = "1.0.107" -thiserror = "1.0.48" -tiny-bip39 = "1.0.0" -tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_lot"] } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } +# personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 +bip39 = { package = "parity-bip39", version = "2.0.1", features = ["rand"] } +tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "signal"] } sc-client-api = { path = "../api" } -sc-client-db = { path = "../db", default-features = false} +sc-client-db = { path = "../db", default-features = false } sc-keystore = { path = "../keystore" } sc-mixnet = { path = "../mixnet" } sc-network = { path = "../network" } -sc-service = { path = "../service", default-features = false} +sc-service = { path = "../service", default-features = false } sc-telemetry = { path = "../telemetry" } sc-tracing = { path = "../tracing" } sc-utils = { path = "../utils" } @@ -53,5 +58,5 @@ futures-timer = "3.0.1" sp-tracing = { path = "../../primitives/tracing" } [features] -default = [ "rocksdb" ] -rocksdb = [ "sc-client-db/rocksdb" ] +default = ["rocksdb"] +rocksdb = ["sc-client-db/rocksdb"] diff --git a/substrate/client/cli/src/arg_enums.rs b/substrate/client/cli/src/arg_enums.rs index c55b97675da25d871736d517081280508ce5ed1e..d436673cb9de77deac7625e0e401cfbd34f5a04f 100644 --- a/substrate/client/cli/src/arg_enums.rs +++ b/substrate/client/cli/src/arg_enums.rs @@ -19,6 +19,7 @@ //! Definitions of [`ValueEnum`] types. use clap::ValueEnum; +use std::str::FromStr; /// The instantiation strategy to use in compiled mode. #[derive(Debug, Clone, Copy, ValueEnum)] @@ -177,6 +178,50 @@ impl Into for RpcMethods { } } +/// CORS setting +/// +/// The type is introduced to overcome `Option>` handling of `clap`. +#[derive(Clone, Debug)] +pub enum Cors { + /// All hosts allowed. + All, + /// Only hosts on the list are allowed. + List(Vec), +} + +impl From for Option> { + fn from(cors: Cors) -> Self { + match cors { + Cors::All => None, + Cors::List(list) => Some(list), + } + } +} + +impl FromStr for Cors { + type Err = crate::Error; + + fn from_str(s: &str) -> Result { + let mut is_all = false; + let mut origins = Vec::new(); + for part in s.split(',') { + match part { + "all" | "*" => { + is_all = true; + break + }, + other => origins.push(other.to_owned()), + } + } + + if is_all { + Ok(Cors::All) + } else { + Ok(Cors::List(origins)) + } + } +} + /// Database backend #[derive(Debug, Clone, PartialEq, Copy, clap::ValueEnum)] #[value(rename_all = "lower")] @@ -225,7 +270,7 @@ pub enum OffchainWorkerEnabled { #[derive(Debug, Clone, Copy, ValueEnum, PartialEq)] #[value(rename_all = "kebab-case")] pub enum SyncMode { - /// Full sync. Download end verify all blocks. + /// Full sync. Download and verify all blocks. Full, /// Download blocks without executing them. Download latest state with proofs. Fast, diff --git a/substrate/client/cli/src/commands/generate.rs b/substrate/client/cli/src/commands/generate.rs index 93b83fcbef51eefe44a6f4e66e3d46cca3ccd689..94769279e21940993785b2a4eeb6fed5d08ddeb6 100644 --- a/substrate/client/cli/src/commands/generate.rs +++ b/substrate/client/cli/src/commands/generate.rs @@ -20,8 +20,9 @@ use crate::{ utils::print_from_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, NetworkSchemeFlag, OutputTypeFlag, }; -use bip39::{Language, Mnemonic, MnemonicType}; +use bip39::Mnemonic; use clap::Parser; +use itertools::Itertools; /// The `generate` command #[derive(Debug, Clone, Parser)] @@ -52,20 +53,22 @@ impl GenerateCmd { /// Run the command pub fn run(&self) -> Result<(), Error> { let words = match self.words { - Some(words) => MnemonicType::for_word_count(words).map_err(|_| { - Error::Input( - "Invalid number of words given for phrase: must be 12/15/18/21/24".into(), - ) - })?, - None => MnemonicType::Words12, - }; - let mnemonic = Mnemonic::new(words, Language::English); + Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count), + Some(_) => Err(Error::Input( + "Invalid number of words given for phrase: must be 12/15/18/21/24".into(), + )), + None => Ok(12), + }?; + let mnemonic = Mnemonic::generate(words) + .map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?; let password = self.keystore_params.read_password()?; let output = self.output_scheme.output_type; + let phrase = mnemonic.words().join(" "); + with_crypto_scheme!( self.crypto_scheme.scheme, - print_from_uri(mnemonic.phrase(), password, self.network_scheme.network, output) + print_from_uri(&phrase, password, self.network_scheme.network, output) ); Ok(()) } diff --git a/substrate/client/cli/src/commands/insert_key.rs b/substrate/client/cli/src/commands/insert_key.rs index 732d874319a83e51477354e7ba8addbad3e1acb2..3d89610b28b1bd0a031ffad6e94fef7fd604e20d 100644 --- a/substrate/client/cli/src/commands/insert_key.rs +++ b/substrate/client/cli/src/commands/insert_key.rs @@ -126,18 +126,14 @@ mod tests { } fn load_spec(&self, _: &str) -> std::result::Result, String> { - Ok(Box::new(GenericChainSpec::from_genesis( - "test", - "test_id", - ChainType::Development, - || unimplemented!("Not required in tests"), - Vec::new(), - None, - None, - None, - None, - NoExtension::None, - ))) + Ok(Box::new( + GenericChainSpec::<()>::builder(Default::default(), NoExtension::None) + .with_name("test") + .with_id("test_id") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(Default::default()) + .build(), + )) } } diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index 5dda488b1333052078d104ddc8371719219784f6..221c32affd5a91d680f23ed11eb5f7a9a4051da2 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - arg_enums::RpcMethods, + arg_enums::{Cors, RpcMethods}, error::{Error, Result}, params::{ ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, SharedParams, @@ -25,50 +25,56 @@ use crate::{ }, CliConfiguration, PrometheusParams, RuntimeParams, TelemetryParams, RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, - RPC_DEFAULT_MAX_SUBS_PER_CONN, + RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN, }; use clap::Parser; use regex::Regex; use sc_service::{ - config::{BasePath, PrometheusConfig, TransactionPoolOptions}, + config::{BasePath, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions}, ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + num::NonZeroU32, +}; /// The `run` command used to run a node. #[derive(Debug, Clone, Parser)] pub struct RunCmd { /// Enable validator mode. + /// /// The node will be started with the authority role and actively /// participate in any consensus task that it can (e.g. depending on /// availability of local keys). #[arg(long)] pub validator: bool, - /// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA + /// Disable GRANDPA. + /// + /// Disables voter when running in validator mode, otherwise disable the GRANDPA /// observer. #[arg(long)] pub no_grandpa: bool, - /// Listen to all RPC interfaces. - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC - /// proxy server to filter out dangerous methods. More details: - /// . + /// Listen to all RPC interfaces (default: local). + /// + /// Not all RPC methods are safe to be exposed publicly. + /// + /// Use an RPC proxy server to filter out dangerous methods. More details: + /// . + /// /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. #[arg(long)] pub rpc_external: bool, /// Listen to all RPC interfaces. + /// /// Same as `--rpc-external`. #[arg(long)] pub unsafe_rpc_external: bool, /// RPC methods to expose. - /// - `unsafe`: Exposes every RPC method. - /// - `safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. - /// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--rpc--external` is - /// passed, otherwise acts as `unsafe`. #[arg( long, value_name = "METHOD SET", @@ -79,15 +85,24 @@ pub struct RunCmd { )] pub rpc_methods: RpcMethods, - /// Set the the maximum RPC request payload size for both HTTP and WS in megabytes. + /// RPC rate limiting (calls/minute) for each connection. + /// + /// This is disabled by default. + /// + /// For example `--rpc-rate-limit 10` will maximum allow + /// 10 calls per minute per connection. + #[arg(long)] + pub rpc_rate_limit: Option, + + /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] pub rpc_max_request_size: u32, - /// Set the the maximum RPC response payload size for both HTTP and WS in megabytes. + /// Set the maximum RPC response payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)] pub rpc_max_response_size: u32, - /// Set the the maximum concurrent subscriptions per connection. + /// Set the maximum concurrent subscriptions per connection. #[arg(long, default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN)] pub rpc_max_subscriptions_per_connection: u32, @@ -99,15 +114,36 @@ pub struct RunCmd { #[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)] pub rpc_max_connections: u32, - /// Specify browser Origins allowed to access the HTTP & WS RPC servers. + /// The number of messages the RPC server is allowed to keep in memory. + /// + /// If the buffer becomes full then the server will not process + /// new messages until the connected client start reading the + /// underlying messages. + /// + /// This applies per connection which includes both + /// JSON-RPC methods calls and subscriptions. + #[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)] + pub rpc_message_buffer_capacity_per_connection: u32, + + /// Disable RPC batch requests + #[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])] + pub rpc_disable_batch_requests: bool, + + /// Limit the max length per RPC batch request + #[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")] + pub rpc_max_batch_request_len: Option, + + /// Specify browser *origins* allowed to access the HTTP & WS RPC servers. + /// /// A comma-separated list of origins (protocol://domain or special `null` /// value). Value of `all` will disable origin validation. Default is to /// allow localhost and origins. When running in - /// --dev mode the default is to allow all origins. - #[arg(long, value_name = "ORIGINS", value_parser = parse_cors)] + /// `--dev` mode the default is to allow all origins. + #[arg(long, value_name = "ORIGINS")] pub rpc_cors: Option, /// The human-readable name for this node. + /// /// It's used as network node name. #[arg(long, value_name = "NAME")] pub name: Option, @@ -148,36 +184,51 @@ pub struct RunCmd { #[clap(flatten)] pub keystore_params: KeystoreParams, - /// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore. + /// Shortcut for `--name Alice --validator`. + /// + /// Session keys for `Alice` are added to keystore. #[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub alice: bool, - /// Shortcut for `--name Bob --validator` with session keys for `Bob` added to keystore. + /// Shortcut for `--name Bob --validator`. + /// + /// Session keys for `Bob` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub bob: bool, - /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to - /// keystore. + /// Shortcut for `--name Charlie --validator`. + /// + /// Session keys for `Charlie` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] pub charlie: bool, - /// Shortcut for `--name Dave --validator` with session keys for `Dave` added to keystore. + /// Shortcut for `--name Dave --validator`. + /// + /// Session keys for `Dave` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])] pub dave: bool, - /// Shortcut for `--name Eve --validator` with session keys for `Eve` added to keystore. + /// Shortcut for `--name Eve --validator`. + /// + /// Session keys for `Eve` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])] pub eve: bool, - /// Shortcut for `--name Ferdie --validator` with session keys for `Ferdie` added to keystore. + /// Shortcut for `--name Ferdie --validator`. + /// + /// Session keys for `Ferdie` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])] pub ferdie: bool, - /// Shortcut for `--name One --validator` with session keys for `One` added to keystore. + /// Shortcut for `--name One --validator`. + /// + /// Session keys for `One` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])] pub one: bool, - /// Shortcut for `--name Two --validator` with session keys for `Two` added to keystore. + /// Shortcut for `--name Two --validator`. + /// + /// Session keys for `Two` are added to keystore. #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])] pub two: bool, @@ -186,10 +237,13 @@ pub struct RunCmd { pub force_authoring: bool, /// Run a temporary node. + /// /// A temporary directory will be created to store the configuration and will be deleted /// at the end of the process. + /// /// Note: the directory is random per process execution. This directory is used as base path /// which includes: database, node key and keystore. + /// /// When `--dev` is given and no explicit `--base-path`, this option is implied. #[arg(long, conflicts_with = "base_path")] pub tmp: bool, @@ -365,6 +419,26 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> Result { + Ok(self.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> Result { + let cfg = if self.rpc_disable_batch_requests { + RpcBatchRequestConfig::Disabled + } else if let Some(l) = self.rpc_max_batch_request_len { + RpcBatchRequestConfig::Limit(l) + } else { + RpcBatchRequestConfig::Unlimited + }; + + Ok(cfg) + } + + fn rpc_rate_limit(&self) -> Result> { + Ok(self.rpc_rate_limit) + } + fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } @@ -447,47 +521,6 @@ fn rpc_interface( } } -/// CORS setting -/// -/// The type is introduced to overcome `Option>` handling of `clap`. -#[derive(Clone, Debug)] -pub enum Cors { - /// All hosts allowed. - All, - /// Only hosts on the list are allowed. - List(Vec), -} - -impl From for Option> { - fn from(cors: Cors) -> Self { - match cors { - Cors::All => None, - Cors::List(list) => Some(list), - } - } -} - -/// Parse cors origins. -fn parse_cors(s: &str) -> Result { - let mut is_all = false; - let mut origins = Vec::new(); - for part in s.split(',') { - match part { - "all" | "*" => { - is_all = true; - break - }, - other => origins.push(other.to_owned()), - } - } - - if is_all { - Ok(Cors::All) - } else { - Ok(Cors::List(origins)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 4d218da6aa89891d3d51c28882978c0b232a8d53..5def9ce9b72620eb7942ac6ee68b16493f6b8053 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -27,13 +27,14 @@ use names::{Generator, Name}; use sc_service::{ config::{ BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, - NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode, Role, RpcMethods, - TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, + NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, + RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, + WasmExecutionMethod, }, BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; -use std::{net::SocketAddr, path::PathBuf}; +use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; @@ -52,8 +53,11 @@ pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024; pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15; /// The default max response size in MB. pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15; -/// The default number of connection.. +/// The default concurrent connection limit. pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100; +/// The default number of messages the RPC server +/// is allowed to keep in memory per connection. +pub const RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN: u32 = 64; /// Default configuration values used by Substrate /// @@ -330,6 +334,21 @@ pub trait CliConfiguration: Sized { Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN) } + /// The number of messages the RPC server is allowed to keep in memory per connection. + fn rpc_buffer_capacity_per_connection(&self) -> Result { + Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN) + } + + /// RPC server batch request configuration. + fn rpc_batch_config(&self) -> Result { + Ok(RpcBatchRequestConfig::Unlimited) + } + + /// RPC rate limit configuration. + fn rpc_rate_limit(&self) -> Result> { + Ok(None) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -501,6 +520,9 @@ pub trait CliConfiguration: Sized { rpc_id_provider: None, rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, rpc_port: DCV::rpc_listen_port(), + rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, + rpc_batch_config: self.rpc_batch_config()?, + rpc_rate_limit: self.rpc_rate_limit()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, @@ -516,7 +538,7 @@ pub trait CliConfiguration: Sized { announce_block: self.announce_block()?, role, base_path, - informant_output_format: Default::default(), + informant_output_format: OutputFormat { enable_color: !self.disable_log_color()? }, runtime_cache_size, }) } @@ -605,14 +627,25 @@ pub trait CliConfiguration: Sized { logger.init()?; - if let Some(new_limit) = fdlimit::raise_fd_limit() { - if new_limit < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT { + match fdlimit::raise_fd_limit() { + Ok(fdlimit::Outcome::LimitRaised { to, .. }) => + if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT { + warn!( + "Low open file descriptor limit configured for the process. \ + Current value: {:?}, recommended value: {:?}.", + to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT, + ); + }, + Ok(fdlimit::Outcome::Unsupported) => { + // Unsupported platform (non-Linux) + }, + Err(error) => { warn!( - "Low open file descriptor limit configured for the process. \ - Current value: {:?}, recommended value: {:?}.", - new_limit, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT, + "Failed to configure file descriptor limit for the process: \ + {}, recommended value: {:?}.", + error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT, ); - } + }, } Ok(()) diff --git a/substrate/client/cli/src/params/import_params.rs b/substrate/client/cli/src/params/import_params.rs index bfa54a35058f6214fc3da604af15e5b32abf45a8..add7cb4f850500bbd0d8762d2695b0b4062cee01 100644 --- a/substrate/client/cli/src/params/import_params.rs +++ b/substrate/client/cli/src/params/import_params.rs @@ -48,6 +48,7 @@ pub struct ImportParams { pub wasm_method: WasmExecutionMethod, /// The WASM instantiation method to use. + /// /// Only has an effect when `wasm-execution` is set to `compiled`. /// The copy-on-write strategies are only supported on Linux. /// If the copy-on-write variant of a strategy is unsupported @@ -65,6 +66,7 @@ pub struct ImportParams { pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, /// Specify the path where local WASM runtimes are stored. + /// /// These runtimes will override on-chain runtimes when the version matches. #[arg(long, value_name = "PATH")] pub wasm_runtime_overrides: Option, @@ -74,12 +76,12 @@ pub struct ImportParams { pub execution_strategies: ExecutionStrategiesParams, /// Specify the state cache size. + /// /// Providing `0` will disable the cache. #[arg(long, value_name = "Bytes", default_value_t = 67108864)] pub trie_cache_size: usize, - /// DEPRECATED - /// Switch to `--trie-cache-size`. + /// DEPRECATED: switch to `--trie-cache-size`. #[arg(long)] state_cache_size: Option, } @@ -115,26 +117,23 @@ impl ImportParams { /// Execution strategies parameters. #[derive(Debug, Clone, Args)] pub struct ExecutionStrategiesParams { - /// The means of execution used when calling into the runtime for importing blocks as - /// part of an initial sync. + /// Runtime execution strategy for importing blocks during initial sync. #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_syncing: Option, - /// The means of execution used when calling into the runtime for general block import - /// (including locally authored blocks). + /// Runtime execution strategy for general block import (including locally authored blocks). #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_import_block: Option, - /// The means of execution used when calling into the runtime while constructing blocks. + /// Runtime execution strategy for constructing blocks. #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_block_construction: Option, - /// The means of execution used when calling into the runtime while using an off-chain worker. + /// Runtime execution strategy for offchain workers. #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_offchain_worker: Option, - /// The means of execution used when calling into the runtime while not syncing, importing or - /// constructing blocks. + /// Runtime execution strategy when not syncing, importing or constructing blocks. #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] pub execution_other: Option, diff --git a/substrate/client/cli/src/params/keystore_params.rs b/substrate/client/cli/src/params/keystore_params.rs index 87210c3390cae3ee3860398ce74f041465f76caf..5a5d0f94999596b86a8b0a38dc2048399fcfe9b8 100644 --- a/substrate/client/cli/src/params/keystore_params.rs +++ b/substrate/client/cli/src/params/keystore_params.rs @@ -39,8 +39,9 @@ pub struct KeystoreParams { #[arg(long, conflicts_with_all = &["password", "password_filename"])] pub password_interactive: bool, - /// Password used by the keystore. This allows appending an extra user-defined secret to the - /// seed. + /// Password used by the keystore. + /// + /// This allows appending an extra user-defined secret to the seed. #[arg( long, value_parser = secret_string_from_str, diff --git a/substrate/client/cli/src/params/network_params.rs b/substrate/client/cli/src/params/network_params.rs index 84db218cc51da04fd382fa69c6e5c3bf6ad91ded..12f19df2a68549be318bedf1cc86875a292ee86b 100644 --- a/substrate/client/cli/src/params/network_params.rs +++ b/substrate/client/cli/src/params/network_params.rs @@ -42,6 +42,7 @@ pub struct NetworkParams { pub reserved_nodes: Vec, /// Whether to only synchronize the chain with reserved nodes. + /// /// Also disables automatic peer discovery. /// TCP connections might still be established with non-reserved nodes. /// In particular, if you are a validator your node might still connect to other @@ -50,7 +51,8 @@ pub struct NetworkParams { #[arg(long)] pub reserved_only: bool, - /// The public address that other nodes will use to connect to it. + /// Public address that other nodes will use to connect to this node. + /// /// This can be used if there's a proxy in front of this node. #[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)] pub public_addr: Vec, @@ -67,20 +69,28 @@ pub struct NetworkParams { #[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])] pub port: Option, - /// Always forbid connecting to private IPv4/IPv6 addresses (as specified in - /// [RFC1918](https://tools.ietf.org/html/rfc1918)), unless the address was passed with - /// `--reserved-nodes` or `--bootnodes`. Enabled by default for chains marked as "live" in - /// their chain specifications. + /// Always forbid connecting to private IPv4/IPv6 addresses. + /// + /// The option doesn't apply to addresses passed with `--reserved-nodes` or + /// `--bootnodes`. Enabled by default for chains marked as "live" in their chain + /// specifications. + /// + /// Address allocation for private networks is specified by + /// [RFC1918](https://tools.ietf.org/html/rfc1918)). #[arg(long, alias = "no-private-ipv4", conflicts_with_all = &["allow_private_ip"])] pub no_private_ip: bool, - /// Always accept connecting to private IPv4/IPv6 addresses (as specified in - /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Enabled by default for chains marked as - /// "local" in their chain specifications, or when `--dev` is passed. + /// Always accept connecting to private IPv4/IPv6 addresses. + /// + /// Enabled by default for chains marked as "local" in their chain specifications, + /// or when `--dev` is passed. + /// + /// Address allocation for private networks is specified by + /// [RFC1918](https://tools.ietf.org/html/rfc1918)). #[arg(long, alias = "allow-private-ipv4", conflicts_with_all = &["no_private_ip"])] pub allow_private_ip: bool, - /// Specify the number of outgoing connections we're trying to maintain. + /// Number of outgoing connections we're trying to maintain. #[arg(long, value_name = "COUNT", default_value_t = 8)] pub out_peers: u32, @@ -92,15 +102,17 @@ pub struct NetworkParams { #[arg(long, value_name = "COUNT", default_value_t = 100)] pub in_peers_light: u32, - /// Disable mDNS discovery. + /// Disable mDNS discovery (default: true). + /// /// By default, the network will use mDNS to discover other nodes on the /// local network. This disables it. Automatically implied when using --dev. #[arg(long)] pub no_mdns: bool, /// Maximum number of peers from which to ask for the same blocks in parallel. - /// This allows downloading announced blocks from multiple peers. Decrease to save - /// traffic and risk increased latency. + /// + /// This allows downloading announced blocks from multiple peers. + /// Decrease to save traffic and risk increased latency. #[arg(long, value_name = "COUNT", default_value_t = 5)] pub max_parallel_downloads: u32, @@ -109,19 +121,24 @@ pub struct NetworkParams { pub node_key_params: NodeKeyParams, /// Enable peer discovery on local networks. + /// /// By default this option is `true` for `--dev` or when the chain type is /// `Local`/`Development` and false otherwise. #[arg(long)] pub discover_local: bool, - /// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in - /// the presence of potentially adversarial nodes. + /// Require iterative Kademlia DHT queries to use disjoint paths. + /// + /// Disjoint paths increase resiliency in the presence of potentially adversarial nodes. + /// /// See the S/Kademlia paper for more information on the high level design as well as its /// security improvements. #[arg(long)] pub kademlia_disjoint_query_paths: bool, - /// Kademlia replication factor determines to how many closest peers a record is replicated to. + /// Kademlia replication factor. + /// + /// Determines to how many closest peers a record is replicated to. /// /// Discovery mechanism requires successful replication to all /// `kademlia_replication_factor` peers to consider record successfully put. diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs index 8c5579eaec4947d0ae18581e77b577186b3d7b9c..53f19f58e1fb0d657d387fd50a8a2346c238ead4 100644 --- a/substrate/client/cli/src/params/node_key_params.rs +++ b/substrate/client/cli/src/params/node_key_params.rs @@ -32,39 +32,49 @@ const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; /// used for libp2p networking. #[derive(Debug, Clone, Args)] pub struct NodeKeyParams { - /// The secret key to use for libp2p networking. + /// Secret key to use for p2p networking. + /// /// The value is a string that is parsed according to the choice of /// `--node-key-type` as follows: - /// `ed25519`: - /// The value is parsed as a hex-encoded Ed25519 32 byte secret key, - /// i.e. 64 hex characters. + /// + /// - `ed25519`: the value is parsed as a hex-encoded Ed25519 32 byte secret key (64 hex + /// chars) + /// /// The value of this option takes precedence over `--node-key-file`. + /// /// WARNING: Secrets provided as command-line arguments are easily exposed. /// Use of this option should be limited to development and testing. To use /// an externally managed secret key, use `--node-key-file` instead. #[arg(long, value_name = "KEY")] pub node_key: Option, - /// The type of secret key to use for libp2p networking. + /// Crypto primitive to use for p2p networking. + /// /// The secret key of the node is obtained as follows: - /// * If the `--node-key` option is given, the value is parsed as a secret key according to - /// the type. See the documentation for `--node-key`. - /// * If the `--node-key-file` option is given, the secret key is read from the specified - /// file. See the documentation for `--node-key-file`. - /// * Otherwise, the secret key is read from a file with a predetermined, type-specific name - /// from the chain-specific network config directory inside the base directory specified by - /// `--base-dir`. If this file does not exist, it is created with a newly generated secret - /// key of the chosen type. + /// + /// - If the `--node-key` option is given, the value is parsed as a secret key according to the + /// type. See the documentation for `--node-key`. + /// + /// - If the `--node-key-file` option is given, the secret key is read from the specified file. + /// See the documentation for `--node-key-file`. + /// + /// - Otherwise, the secret key is read from a file with a predetermined, type-specific name + /// from the chain-specific network config directory inside the base directory specified by + /// `--base-dir`. If this file does not exist, it is created with a newly generated secret + /// key of the chosen type. + /// /// The node's secret key determines the corresponding public key and hence the /// node's peer ID in the context of libp2p. #[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)] pub node_key_type: NodeKeyType, - /// The file from which to read the node's secret key to use for libp2p networking. + /// File from which to read the node's secret key to use for p2p networking. + /// /// The contents of the file are parsed according to the choice of `--node-key-type` /// as follows: - /// `ed25519`: - /// The file must contain an unencoded 32 byte or hex encoded Ed25519 secret key. + /// + /// - `ed25519`: the file must contain an unencoded 32 byte or hex encoded Ed25519 secret key. + /// /// If the file does not exist, it is created with a newly generated secret key of /// the chosen type. #[arg(long, value_name = "FILE")] diff --git a/substrate/client/cli/src/params/offchain_worker_params.rs b/substrate/client/cli/src/params/offchain_worker_params.rs index d1fedab4cb2eba46e8c56c2043224e90a1dc597b..3583d85c00a7a2d73c9b392201a38dc5c6fadef3 100644 --- a/substrate/client/cli/src/params/offchain_worker_params.rs +++ b/substrate/client/cli/src/params/offchain_worker_params.rs @@ -32,8 +32,7 @@ use crate::{error, OffchainWorkerEnabled}; /// Offchain worker related parameters. #[derive(Debug, Clone, Args)] pub struct OffchainWorkerParams { - /// Should execute offchain workers on every block. - /// By default it's only enabled for nodes that are authoring new blocks. + /// Execute offchain workers on every block. #[arg( long = "offchain-worker", value_name = "ENABLED", @@ -43,8 +42,9 @@ pub struct OffchainWorkerParams { )] pub enabled: OffchainWorkerEnabled, - /// Enable Offchain Indexing API, which allows block import to write to Offchain DB. - /// Enables a runtime to write directly to a offchain workers DB during block import. + /// Enable offchain indexing API. + /// + /// Allows the runtime to write directly to offchain workers DB during block import. #[arg(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING", default_value_t = false, action = ArgAction::Set)] pub indexing_enabled: bool, } diff --git a/substrate/client/cli/src/params/prometheus_params.rs b/substrate/client/cli/src/params/prometheus_params.rs index 4d234ea33c20d0a1ea523636474581638852c63d..69199ad5b260329bb49f4b8e8f0781daa17e15fb 100644 --- a/substrate/client/cli/src/params/prometheus_params.rs +++ b/substrate/client/cli/src/params/prometheus_params.rs @@ -27,10 +27,12 @@ pub struct PrometheusParams { #[arg(long, value_name = "PORT")] pub prometheus_port: Option, /// Expose Prometheus exporter on all interfaces. + /// /// Default is local. #[arg(long)] pub prometheus_external: bool, /// Do not expose a Prometheus exporter endpoint. + /// /// Prometheus metric endpoint is enabled by default. #[arg(long)] pub no_prometheus: bool, diff --git a/substrate/client/cli/src/params/pruning_params.rs b/substrate/client/cli/src/params/pruning_params.rs index 1b5bf247d942e66fa521aa867aace58db53cca1e..25b17b53289818ba02e839e98b9738f380fc1067 100644 --- a/substrate/client/cli/src/params/pruning_params.rs +++ b/substrate/client/cli/src/params/pruning_params.rs @@ -24,6 +24,7 @@ use sc_service::{BlocksPruning, PruningMode}; #[derive(Debug, Clone, Args)] pub struct PruningParams { /// Specify the state pruning mode. + /// /// This mode specifies when the block's state (ie, storage) /// should be pruned (ie, removed) from the database. /// This setting can only be set on the first creation of the database. Every subsequent run @@ -38,6 +39,7 @@ pub struct PruningParams { pub state_pruning: Option, /// Specify the blocks pruning mode. + /// /// This mode specifies when the block's body (including justifications) /// should be pruned (ie, removed) from the database. /// Possible values: diff --git a/substrate/client/cli/src/params/runtime_params.rs b/substrate/client/cli/src/params/runtime_params.rs index 07009a96ee6e6c9dbc0cc3bcf9aa087c58f58909..a130d808418e629a1e222f790dd2977172814f96 100644 --- a/substrate/client/cli/src/params/runtime_params.rs +++ b/substrate/client/cli/src/params/runtime_params.rs @@ -22,7 +22,9 @@ use std::str::FromStr; /// Parameters used to config runtime. #[derive(Debug, Clone, Args)] pub struct RuntimeParams { - /// The size of the instances cache for each runtime. The values higher than 32 are illegal. + /// The size of the instances cache for each runtime [max: 32]. + /// + /// Values higher than 32 are illegal. #[arg(long, default_value_t = 8, value_parser = parse_max_runtime_instances)] pub max_runtime_instances: usize, diff --git a/substrate/client/cli/src/params/shared_params.rs b/substrate/client/cli/src/params/shared_params.rs index 3d20ca504a6915207f37bda71ac8116fba3dd025..465372fba17d4bb8036477d587e37f12cdd414d6 100644 --- a/substrate/client/cli/src/params/shared_params.rs +++ b/substrate/client/cli/src/params/shared_params.rs @@ -25,14 +25,17 @@ use std::path::PathBuf; #[derive(Debug, Clone, Args)] pub struct SharedParams { /// Specify the chain specification. - /// It can be one of the predefined ones (dev, local, or staging) or it can be a path to a file - /// with the chainspec (such as one exported by the `build-spec` subcommand). + /// + /// It can be one of the predefined ones (dev, local, or staging) or it can be a path to + /// a file with the chainspec (such as one exported by the `build-spec` subcommand). #[arg(long, value_name = "CHAIN_SPEC")] pub chain: Option, /// Specify the development chain. + /// /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, /// `--alice`, and `--tmp` flags, unless explicitly overridden. + /// It also disables local peer discovery (see --no-mdns and --discover-local) #[arg(long, conflicts_with_all = &["chain"])] pub dev: bool, @@ -40,14 +43,23 @@ pub struct SharedParams { #[arg(long, short = 'd', value_name = "PATH")] pub base_path: Option, - /// Sets a custom logging filter. Syntax is `=`, e.g. -lsync=debug. - /// Log levels (least to most verbose) are error, warn, info, debug, and trace. + /// Sets a custom logging filter (syntax: `=`). + /// + /// Log levels (least to most verbose) are `error`, `warn`, `info`, `debug`, and `trace`. + /// /// By default, all targets log `info`. The global log level can be set with `-l`. + /// + /// Multiple `=` entries can be specified and separated by a comma. + /// + /// *Example*: `--log error,sync=debug,grandpa=warn`. + /// Sets Global log level to `error`, sets `sync` target to debug and grandpa target to `warn`. #[arg(short = 'l', long, value_name = "LOG_PATTERN", num_args = 1..)] pub log: Vec, /// Enable detailed log output. - /// This includes displaying the log target, log level and thread name. + /// + /// Includes displaying the log target, log level and thread name. + /// /// This is automatically enabled when something is logged with any higher level than `info`. #[arg(long)] pub detailed_log_output: bool, @@ -57,14 +69,18 @@ pub struct SharedParams { pub disable_log_color: bool, /// Enable feature to dynamically update and reload the log filter. + /// /// Be aware that enabling this feature can lead to a performance decrease up to factor six or /// more. Depending on the global logging level the performance decrease changes. + /// /// The `system_addLogFilter` and `system_resetLogFilter` RPCs will have no effect with this /// option not being set. #[arg(long)] pub enable_log_reloading: bool, - /// Sets a custom profiling filter. Syntax is the same as for logging: `=`. + /// Sets a custom profiling filter. + /// + /// Syntax is the same as for logging (`--log`). #[arg(long, value_name = "TARGETS")] pub tracing_targets: Option, diff --git a/substrate/client/cli/src/params/telemetry_params.rs b/substrate/client/cli/src/params/telemetry_params.rs index 67f441071410cae7c6f78be6e18944d3201b8f0d..3b3d91e6b04ed1c6891d56be47ec4da27e8a633f 100644 --- a/substrate/client/cli/src/params/telemetry_params.rs +++ b/substrate/client/cli/src/params/telemetry_params.rs @@ -22,14 +22,17 @@ use clap::Args; #[derive(Debug, Clone, Args)] pub struct TelemetryParams { /// Disable connecting to the Substrate telemetry server. + /// /// Telemetry is on by default on global chains. #[arg(long)] pub no_telemetry: bool, /// The URL of the telemetry server to connect to. + /// /// This flag can be passed multiple times as a means to specify multiple /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting /// the least verbosity. + /// /// Expected format is 'URL VERBOSITY', e.g. `--telemetry-url 'wss://foo/bar 0'`. #[arg(long = "telemetry-url", value_name = "URL VERBOSITY", value_parser = parse_telemetry_endpoints)] pub telemetry_endpoints: Vec<(String, u8)>, diff --git a/substrate/client/cli/src/params/transaction_pool_params.rs b/substrate/client/cli/src/params/transaction_pool_params.rs index b2bf0b9b364c801c12d6aee8ee1d28007b24cc4a..48b2e5b1572baa0942e695993d149531cc7e4358 100644 --- a/substrate/client/cli/src/params/transaction_pool_params.rs +++ b/substrate/client/cli/src/params/transaction_pool_params.rs @@ -30,7 +30,9 @@ pub struct TransactionPoolParams { #[arg(long, value_name = "COUNT", default_value_t = 20480)] pub pool_kbytes: usize, - /// How long a transaction is banned for, if it is considered invalid. Defaults to 1800s. + /// How long a transaction is banned for. + /// + /// If it is considered invalid. Defaults to 1800s. #[arg(long, value_name = "SECONDS")] pub tx_ban_seconds: Option, } diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index 59f53200a192bce80a0e5b151ce78db26f87c195..4201a0f4062fb0063621d85dd02feb0deade24bc 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -251,18 +251,14 @@ mod tests { trie_cache_maximum_size: None, state_pruning: None, blocks_pruning: sc_client_db::BlocksPruning::KeepAll, - chain_spec: Box::new(GenericChainSpec::from_genesis( - "test", - "test_id", - ChainType::Development, - || unimplemented!("Not required in tests"), - Vec::new(), - None, - None, - None, - None, - NoExtension::None, - )), + chain_spec: Box::new( + GenericChainSpec::<()>::builder(Default::default(), NoExtension::None) + .with_name("test") + .with_id("test_id") + .with_chain_type(ChainType::Development) + .with_genesis_config_patch(Default::default()) + .build(), + ), wasm_method: Default::default(), wasm_runtime_overrides: None, rpc_addr: None, @@ -273,7 +269,10 @@ mod tests { rpc_max_response_size: Default::default(), rpc_id_provider: Default::default(), rpc_max_subs_per_conn: Default::default(), + rpc_message_buffer_capacity: Default::default(), rpc_port: 9944, + rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index bc9648f683a880afe7eded0a079efdbcafcd5cc3..213f75974da0c99cc5c39248a8a789c4989d8047 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-aura" -version = "0.10.0-dev" +version = "0.34.0" authors.workspace = true description = "Aura consensus algorithm for substrate" edition.workspace = true @@ -9,15 +9,18 @@ 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" -thiserror = "1.0" +log = { workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/consensus/aura/src/lib.rs b/substrate/client/consensus/aura/src/lib.rs index 2ed451ef663ed87b6c61efbd6e1714118e0235ac..1be7be8eeeaa60a75ae8b6ef95fd8cbbd5b3998e 100644 --- a/substrate/client/consensus/aura/src/lib.rs +++ b/substrate/client/consensus/aura/src/lib.rs @@ -548,7 +548,7 @@ where mod tests { use super::*; use parking_lot::Mutex; - use sc_block_builder::BlockBuilderProvider; + use sc_block_builder::BlockBuilderBuilder; use sc_client_api::BlockchainEvents; use sc_consensus::BoxJustificationImport; use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker}; @@ -604,7 +604,14 @@ mod tests { _: Duration, _: Option, ) -> Self::Proposal { - let r = self.1.new_block(digests).unwrap().build().map_err(|e| e.into()); + let r = BlockBuilderBuilder::new(&*self.1) + .on_parent_block(self.1.chain_info().best_hash) + .fetch_parent_block_number(&*self.1) + .unwrap() + .with_inherent_digests(digests) + .build() + .unwrap() + .build(); future::ready(r.map(|b| Proposal { block: b.block, diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index c8cff0981b36f89e0b6f9120f4c6378f7c6793e7..c98fb7112b7c25bd39d980cab20c188f30b42eff 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-babe" -version = "0.10.0-dev" +version = "0.34.0" authors.workspace = true description = "BABE consensus algorithm for substrate" edition.workspace = true @@ -10,19 +10,22 @@ 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" +log = { workspace = true, default-features = true } 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" +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } @@ -39,6 +42,7 @@ sp-consensus = { path = "../../../primitives/consensus/common" } sp-consensus-babe = { path = "../../../primitives/consensus/babe" } sp-consensus-slots = { path = "../../../primitives/consensus/slots" } sp-core = { path = "../../../primitives/core" } +sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-inherents = { path = "../../../primitives/inherents" } sp-keystore = { path = "../../../primitives/keystore" } sp-runtime = { path = "../../../primitives/runtime" } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index f54edb296842ddf9b9f4390aa8e307c0bef69398..043b566673e7bd5aa581e3043dfa1280aa04d53f 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-babe-rpc" -version = "0.10.0-dev" +version = "0.34.0" authors.workspace = true description = "RPC extensions for the BABE consensus algorithm" edition.workspace = true @@ -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] -jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" -serde = { version = "1.0.188", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-babe = { path = ".." } sc-consensus-epochs = { path = "../../epochs" } sc-rpc-api = { path = "../../../rpc-api" } @@ -30,7 +33,7 @@ sp-keystore = { path = "../../../../primitives/keystore" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.107" +serde_json = { workspace = true, default-features = true } tokio = "1.22.0" sc-consensus = { path = "../../common" } sc-keystore = { path = "../../../keystore" } diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index bffe026ea6ef6bd9efa282dd71b587aaf03471b3..a3e811baecffd6de1b9f94952be40d00d8b61de3 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -22,15 +22,15 @@ use std::{collections::HashMap, sync::Arc}; use futures::TryFutureExt; use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, + core::async_trait, proc_macros::rpc, - types::{error::CallError, ErrorObject}, + types::{ErrorObject, ErrorObjectOwned}, }; use serde::{Deserialize, Serialize}; use sc_consensus_babe::{authorship, BabeWorkerHandle}; use sc_consensus_epochs::Epoch as EpochT; -use sc_rpc_api::DenyUnsafe; +use sc_rpc_api::{DenyUnsafe, UnsafeRpcError}; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppCrypto; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; @@ -48,7 +48,7 @@ pub trait BabeApi { /// Returns data about which slots (primary or secondary) can be claimed in the current epoch /// with the keys in the keystore. #[method(name = "babe_epochAuthorship")] - async fn epoch_authorship(&self) -> RpcResult>; + async fn epoch_authorship(&self) -> Result, Error>; } /// Provides RPC methods for interacting with Babe. @@ -89,7 +89,7 @@ where C::Api: BabeRuntimeApi, SC: SelectChain + Clone + 'static, { - async fn epoch_authorship(&self) -> RpcResult> { + async fn epoch_authorship(&self) -> Result, Error> { self.deny_unsafe.check_if_safe()?; let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?; @@ -147,7 +147,7 @@ where } /// Holds information about the `slot`'s that can be claimed by a given key. -#[derive(Default, Debug, Deserialize, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Serialize)] pub struct EpochAuthorship { /// the array of primary slots that can be claimed primary: Vec, @@ -166,20 +166,26 @@ pub enum Error { /// Failed to fetch epoch data. #[error("Failed to fetch epoch data")] FetchEpoch, + /// Consensus error + #[error(transparent)] + Consensus(#[from] ConsensusError), + /// Errors that can be formatted as a String + #[error("{0}")] + StringError(String), + /// Call to an unsafe RPC was denied. + #[error(transparent)] + UnsafeRpcCalled(#[from] UnsafeRpcError), } -impl From for JsonRpseeError { +impl From for ErrorObjectOwned { fn from(error: Error) -> Self { - let error_code = match error { - Error::SelectChain(_) => 1, - Error::FetchEpoch => 2, - }; - - JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - BABE_ERROR + error_code, - error.to_string(), - Some(format!("{:?}", error)), - ))) + match error { + Error::SelectChain(e) => ErrorObject::owned(BABE_ERROR + 1, e.to_string(), None::<()>), + Error::FetchEpoch => ErrorObject::owned(BABE_ERROR + 2, error.to_string(), None::<()>), + Error::Consensus(e) => ErrorObject::owned(BABE_ERROR + 3, e.to_string(), None::<()>), + Error::StringError(e) => ErrorObject::owned(BABE_ERROR + 4, e, None::<()>), + Error::UnsafeRpcCalled(e) => e.into(), + } } } @@ -251,10 +257,10 @@ mod tests { let api = babe_rpc.into_rpc(); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; - let (response, _) = api.raw_json_request(request).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; + let (response, _) = api.raw_json_request(request, 1).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } #[tokio::test] @@ -263,9 +269,9 @@ mod tests { let api = babe_rpc.into_rpc(); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#; - let (response, _) = api.raw_json_request(request).await.unwrap(); + let (response, _) = api.raw_json_request(request, 1).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } } diff --git a/substrate/client/consensus/babe/src/authorship.rs b/substrate/client/consensus/babe/src/authorship.rs index 3580caba746141a8a9faf9e73216cb8903667acf..11f5233abc6b384ef034acaa1f26a1833aee124a 100644 --- a/substrate/client/consensus/babe/src/authorship.rs +++ b/substrate/client/consensus/babe/src/authorship.rs @@ -27,7 +27,6 @@ use sp_consensus_babe::{ make_vrf_sign_data, AuthorityId, BabeAuthorityWeight, Randomness, Slot, }; use sp_core::{ - blake2_256, crypto::{ByteArray, Wraps}, U256, }; @@ -109,7 +108,7 @@ pub(super) fn secondary_slot_author( return None } - let rand = U256::from((randomness, slot).using_encoded(blake2_256)); + let rand = U256::from((randomness, slot).using_encoded(sp_crypto_hashing::blake2_256)); let authorities_len = U256::from(authorities.len()); let idx = rand % authorities_len; @@ -249,7 +248,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 02882d8baaeda2d8cf8ed2fdf2682c31989d1ab3..38c9e1ff6ac25cc26151c668902838ec65ab6189 100644 --- a/substrate/client/consensus/babe/src/tests.rs +++ b/substrate/client/consensus/babe/src/tests.rs @@ -20,7 +20,7 @@ use super::*; use authorship::claim_slot; -use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; +use sc_block_builder::{BlockBuilder, BlockBuilderBuilder}; use sc_client_api::{BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; use sc_consensus_epochs::{EpochIdentifier, EpochIdentifierPosition}; @@ -98,8 +98,13 @@ impl DummyProposer { &mut self, pre_digests: Digest, ) -> future::Ready, Error>> { - let block_builder = - self.factory.client.new_block_at(self.parent_hash, pre_digests, false).unwrap(); + let block_builder = BlockBuilderBuilder::new(&*self.factory.client) + .on_parent_block(self.parent_hash) + .fetch_parent_block_number(&*self.factory.client) + .unwrap() + .with_inherent_digests(pre_digests) + .build() + .unwrap(); let mut block = match block_builder.build().map_err(|e| e.into()) { Ok(b) => b.block, @@ -297,7 +302,7 @@ impl TestNetFactory for BabeTestNet { async fn rejects_empty_block() { sp_tracing::try_init_simple(); let mut net = BabeTestNet::new(3); - let block_builder = |builder: BlockBuilder<_, _, _>| builder.build().unwrap().block; + let block_builder = |builder: BlockBuilder<_, _>| builder.build().unwrap().block; net.mut_peers(|peer| { peer[0].generate_blocks(1, BlockOrigin::NetworkInitialSync, block_builder); }) @@ -406,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); } } @@ -575,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 { @@ -584,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); @@ -596,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); @@ -608,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 aae5a44d7fa26da98d90b02d1dcb9dbd22faae06..8552a49002239aaeaf232c0aebf4180cec207337 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-beefy" -version = "4.0.0-dev" +version = "13.0.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -8,16 +8,19 @@ 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" -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } wasm-timer = "0.2.5" prometheus = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } @@ -33,17 +36,28 @@ sp-blockchain = { path = "../../../primitives/blockchain" } sp-consensus = { path = "../../../primitives/consensus/common" } sp-consensus-beefy = { path = "../../../primitives/consensus/beefy" } sp-core = { path = "../../../primitives/core" } +sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } 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.188" +serde = { workspace = true, default-features = true } 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" } sp-keyring = { path = "../../../primitives/keyring" } sp-tracing = { path = "../../../primitives/tracing" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" } + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-application-crypto/bls-experimental", + "sp-consensus-beefy/bls-experimental", + "sp-core/bls-experimental", +] diff --git a/substrate/client/consensus/beefy/README.md b/substrate/client/consensus/beefy/README.md index 1a5a9667fdbb74d9dbd872629b19974ed6b08497..13f88303a972ac35f2bbcb11a443619b603149a7 100644 --- a/substrate/client/consensus/beefy/README.md +++ b/substrate/client/consensus/beefy/README.md @@ -104,7 +104,7 @@ shortcuts: ## Mental Model BEEFY should be considered as an extra voting round done by GRANDPA validators for the current -best finalized block. Similarily to how GRANDPA is lagging behind best produced (non-finalized) +best finalized block. Similarly to how GRANDPA is lagging behind best produced (non-finalized) block, BEEFY is going to lag behind best GRANDPA (finalized) block. ``` @@ -302,7 +302,7 @@ periodically on the global topic. Let's now dive into description of the message ## Misbehavior -Similarily to other PoS protocols, BEEFY considers casting two different votes in the same round a +Similarly to other PoS protocols, BEEFY considers casting two different votes in the same round a misbehavior. I.e. for a particular `round_number`, the validator produces signatures for 2 different `Commitment`s and broadcasts them. This is called **equivocation**. diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index 74733ea9edd9d274a9578248f607b68fe1aacee4..bb2ae4a08966707fd93afd48b625ccc4f760762b 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-beefy-rpc" -version = "4.0.0-dev" +version = "13.0.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -8,14 +8,17 @@ 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", "server", "macros"] } -log = "0.4" +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.188", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-beefy = { path = ".." } sp-consensus-beefy = { path = "../../../../primitives/consensus/beefy" } sc-rpc = { path = "../../../rpc" } @@ -23,7 +26,7 @@ sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.107" -sc-rpc = { path = "../../../rpc", features = ["test-helpers"]} +serde_json = { workspace = true, default-features = true } +sc-rpc = { path = "../../../rpc", features = ["test-helpers"] } substrate-test-runtime-client = { path = "../../../../test-utils/runtime/client" } tokio = { version = "1.22.0", features = ["macros"] } diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs index f5c0ff32627d5e1a7b67d05e1d836e23c0ba9402..f01baee2d6ece9a9d1dd36e1524a1d004e9b0401 100644 --- a/substrate/client/consensus/beefy/rpc/src/lib.rs +++ b/substrate/client/consensus/beefy/rpc/src/lib.rs @@ -23,15 +23,15 @@ use parking_lot::RwLock; use std::sync::Arc; -use sc_rpc::SubscriptionTaskExecutor; +use sc_rpc::{utils::pipe_from_stream, SubscriptionTaskExecutor}; use sp_runtime::traits::Block as BlockT; use futures::{task::SpawnError, FutureExt, StreamExt}; use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, + core::async_trait, proc_macros::rpc, - types::{error::CallError, ErrorObject, SubscriptionResult}, - SubscriptionSink, + types::{ErrorObject, ErrorObjectOwned}, + PendingSubscriptionSink, }; use log::warn; @@ -69,15 +69,11 @@ impl From for ErrorCode { } } -impl From for JsonRpseeError { +impl From for ErrorObjectOwned { fn from(error: Error) -> Self { let message = error.to_string(); let code = ErrorCode::from(error); - JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - code as i32, - message, - None::<()>, - ))) + ErrorObject::owned(code as i32, message, None::<()>) } } @@ -98,7 +94,7 @@ pub trait BeefyApi { /// in the network or if the client is still initializing or syncing with the network. /// In such case an error would be returned. #[method(name = "beefy_getFinalizedHead")] - async fn latest_finalized(&self) -> RpcResult; + async fn latest_finalized(&self) -> Result; } /// Implements the BeefyApi RPC trait for interacting with BEEFY. @@ -138,27 +134,17 @@ impl BeefyApiServer SubscriptionResult { + fn subscribe_justifications(&self, pending: PendingSubscriptionSink) { let stream = self .finality_proof_stream .subscribe(100_000) .map(|vfp| notification::EncodedVersionedFinalityProof::new::(vfp)); - let fut = async move { - sink.pipe_from_stream(stream).await; - }; - - self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); - Ok(()) + sc_rpc::utils::spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream)); } - async fn latest_finalized(&self) -> RpcResult { - self.beefy_best_block - .read() - .as_ref() - .cloned() - .ok_or(Error::EndpointNotReady) - .map_err(Into::into) + async fn latest_finalized(&self) -> Result { + self.beefy_best_block.read().as_ref().cloned().ok_or(Error::EndpointNotReady) } } @@ -167,7 +153,7 @@ mod tests { use super::*; use codec::{Decode, Encode}; - use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; + use jsonrpsee::{core::EmptyServerParams as EmptyParams, RpcModule}; use sc_consensus_beefy::{ communication::notification::BeefyVersionedFinalityProofSender, justification::BeefyVersionedFinalityProof, @@ -198,10 +184,10 @@ mod tests { async fn uninitialized_rpc_handler() { let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); - let (response, _) = rpc.raw_json_request(&request).await.unwrap(); + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#; + let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -219,24 +205,22 @@ mod tests { \"jsonrpc\":\"2.0\",\ \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ \"id\":1\ - }" - .to_string(); + }"; let not_ready = "{\ \"jsonrpc\":\"2.0\",\ \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ \"id\":1\ - }" - .to_string(); + }"; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { - let (response, _) = io.raw_json_request(request).await.expect("RPC requests work"); - if response.result != not_ready { - assert_eq!(response.result, expected); + let (response, _) = io.raw_json_request(request, 1).await.expect("RPC requests work"); + if response != not_ready { + assert_eq!(response, expected); // Success return } - std::thread::sleep(std::time::Duration::from_millis(50)) + tokio::time::sleep(std::time::Duration::from_millis(50)).await; } panic!( @@ -249,7 +233,7 @@ mod tests { let (rpc, _) = setup_io_handler(); // Subscribe call. let _sub = rpc - .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .subscribe_unbounded("beefy_subscribeJustifications", EmptyParams::new()) .await .unwrap(); @@ -257,12 +241,13 @@ mod tests { let (response, _) = rpc .raw_json_request( r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#, + 1, ) .await .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_finality_proof() -> BeefyVersionedFinalityProof { @@ -284,7 +269,7 @@ mod tests { // Subscribe let mut sub = rpc - .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .subscribe_unbounded("beefy_subscribeJustifications", EmptyParams::new()) .await .unwrap(); diff --git a/substrate/client/consensus/beefy/src/aux_schema.rs b/substrate/client/consensus/beefy/src/aux_schema.rs index 409eb30d09ab947f3beb997f81f926eb337efec1..534f668ae69c2996064bef086e2958b23f48caf0 100644 --- a/substrate/client/consensus/beefy/src/aux_schema.rs +++ b/substrate/client/consensus/beefy/src/aux_schema.rs @@ -18,11 +18,10 @@ //! Schema for BEEFY state persisted in the aux-db. -use crate::{worker::PersistedState, LOG_TARGET}; +use crate::{error::Error, worker::PersistedState, LOG_TARGET}; use codec::{Decode, Encode}; -use log::{info, trace}; +use log::{debug, trace}; use sc_client_api::{backend::AuxStore, Backend}; -use sp_blockchain::{Error as ClientError, Result as ClientResult}; use sp_runtime::traits::Block as BlockT; const VERSION_KEY: &[u8] = b"beefy_auxschema_version"; @@ -30,31 +29,33 @@ const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state"; const CURRENT_VERSION: u32 = 4; -pub(crate) fn write_current_version(backend: &BE) -> ClientResult<()> { - info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); +pub(crate) fn write_current_version(backend: &BE) -> Result<(), Error> { + debug!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) + .map_err(|e| Error::Backend(e.to_string())) } /// Write voter state. pub(crate) fn write_voter_state( backend: &BE, state: &PersistedState, -) -> ClientResult<()> { +) -> Result<(), Error> { trace!(target: LOG_TARGET, "🥩 persisting {:?}", state); AuxStore::insert_aux(backend, &[(WORKER_STATE_KEY, state.encode().as_slice())], &[]) + .map_err(|e| Error::Backend(e.to_string())) } -fn load_decode(backend: &BE, key: &[u8]) -> ClientResult> { - match backend.get_aux(key)? { +fn load_decode(backend: &BE, key: &[u8]) -> Result, Error> { + match backend.get_aux(key).map_err(|e| Error::Backend(e.to_string()))? { None => Ok(None), Some(t) => T::decode(&mut &t[..]) - .map_err(|e| ClientError::Backend(format!("BEEFY DB is corrupted: {}", e))) + .map_err(|e| Error::Backend(format!("BEEFY DB is corrupted: {}", e))) .map(Some), } } /// Load or initialize persistent data from backend. -pub(crate) fn load_persistent(backend: &BE) -> ClientResult>> +pub(crate) fn load_persistent(backend: &BE) -> Result>, Error> where B: BlockT, BE: Backend, @@ -65,8 +66,7 @@ where None => (), Some(1) | Some(2) | Some(3) => (), // versions 1, 2 & 3 are obsolete and should be ignored Some(4) => return load_decode::<_, PersistedState>(backend, WORKER_STATE_KEY), - other => - return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), + other => return Err(Error::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), } // No persistent state found in DB. diff --git a/substrate/client/consensus/beefy/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 342cd0511a511bc0fc520c438fc23abbbd3a9897..eb43c9173d751bf75bc4973aca248d80fb68ca48 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -56,6 +56,8 @@ pub(super) enum Action { Keep(H, ReputationChange), // discard, applying cost/benefit to originator. Discard(ReputationChange), + // ignore, no cost/benefit applied to originator. + DiscardNoReport, } /// An outcome of examining a message. @@ -68,7 +70,7 @@ enum Consider { /// Message is from the future. Reject. RejectFuture, /// Message cannot be evaluated. Reject. - RejectOutOfScope, + CannotEvaluate, } /// BEEFY gossip message type that gets encoded and sent on the network. @@ -168,18 +170,14 @@ impl Filter { .as_ref() .map(|f| // only from current set and only [filter.start, filter.end] - if set_id < f.validator_set.id() { + if set_id < f.validator_set.id() || round < f.start { Consider::RejectPast - } else if set_id > f.validator_set.id() { - Consider::RejectFuture - } else if round < f.start { - Consider::RejectPast - } else if round > f.end { + } else if set_id > f.validator_set.id() || round > f.end { Consider::RejectFuture } else { Consider::Accept }) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Return true if `round` is >= than `max(session_start, best_beefy)`, @@ -199,7 +197,7 @@ impl Filter { Consider::Accept } ) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Add new _known_ `round` to the set of seen valid justifications. @@ -244,7 +242,7 @@ where pub(crate) fn new( known_peers: Arc>>, ) -> (GossipValidator, TracingUnboundedReceiver) { - let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 10_000); + let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 100_000); let val = GossipValidator { votes_topic: votes_topic::(), justifs_topic: proofs_topic::(), @@ -260,7 +258,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); } @@ -285,7 +287,9 @@ where match filter.consider_vote(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the vote without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -326,7 +330,9 @@ where match guard.consider_finality_proof(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -353,7 +359,9 @@ where Action::Keep(self.justifs_topic, benefit::VALIDATED_PROOF) } }) - .unwrap_or(Action::Discard(cost::OUT_OF_SCOPE_MESSAGE)) + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + .unwrap_or(Action::DiscardNoReport) }; if matches!(action, Action::Keep(_, _)) { self.gossip_filter.write().mark_round_as_proven(round); @@ -400,6 +408,7 @@ where self.report(*sender, cb); ValidationResult::Discard }, + Action::DiscardNoReport => ValidationResult::Discard, } } @@ -481,8 +490,8 @@ pub(crate) mod tests { use sc_network_test::Block; use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE; use sp_consensus_beefy::{ - ecdsa_crypto::Signature, known_payloads, Commitment, Keyring, MmrRootHash, Payload, - SignedCommitment, VoteMessage, + ecdsa_crypto::Signature, known_payloads, test_utils::Keyring, Commitment, MmrRootHash, + Payload, SignedCommitment, VoteMessage, }; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -503,10 +512,13 @@ pub(crate) mod tests { } } - pub fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + pub fn sign_commitment( + who: &Keyring, + commitment: &Commitment, + ) -> Signature { let store = MemoryKeystore::new(); store.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store.into()).into(); + let beefy_keystore: BeefyKeystore = Some(store.into()).into(); beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() } @@ -534,7 +546,10 @@ pub(crate) mod tests { .validators() .iter() .map(|validator: &AuthorityId| { - Some(sign_commitment(&Keyring::from_public(validator).unwrap(), &commitment)) + Some(sign_commitment( + &Keyring::::from_public(validator).unwrap(), + &commitment, + )) }) .collect(); @@ -543,7 +558,7 @@ pub(crate) mod tests { #[test] fn should_validate_messages() { - let keys = vec![Keyring::Alice.public()]; + let keys = vec![Keyring::::Alice.public()]; let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); let (gv, mut report_stream) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); @@ -569,8 +584,8 @@ pub(crate) mod tests { // filter not initialized let res = gv.validate(&mut context, &sender, &encoded); assert!(matches!(res, ValidationResult::Discard)); - expected_report.cost_benefit = cost::OUT_OF_SCOPE_MESSAGE; - assert_eq!(report_stream.try_recv().unwrap(), expected_report); + // nothing reported + assert!(report_stream.try_recv().is_err()); gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); // nothing in cache first time diff --git a/substrate/client/consensus/beefy/src/communication/mod.rs b/substrate/client/consensus/beefy/src/communication/mod.rs index 10a6071aae658d2988874092dcaade873870ca37..6fda63688e6952839c18e1bbc9ff50ff0c5f7c21 100644 --- a/substrate/client/consensus/beefy/src/communication/mod.rs +++ b/substrate/client/consensus/beefy/src/communication/mod.rs @@ -67,10 +67,16 @@ pub(crate) mod beefy_protocol_name { /// For standard protocol name see [`beefy_protocol_name::gossip_protocol_name`]. pub fn beefy_peers_set_config( gossip_protocol_name: sc_network::ProtocolName, -) -> sc_network::config::NonDefaultSetConfig { - let mut cfg = sc_network::config::NonDefaultSetConfig::new(gossip_protocol_name, 1024 * 1024); +) -> (sc_network::config::NonDefaultSetConfig, Box) { + let (mut cfg, notification_service) = sc_network::config::NonDefaultSetConfig::new( + gossip_protocol_name, + Vec::new(), + 1024 * 1024, + None, + Default::default(), + ); cfg.allow_non_reserved(25, 25); - cfg + (cfg, notification_service) } // cost scalars for reporting peers. @@ -84,8 +90,6 @@ mod cost { pub(super) const BAD_SIGNATURE: Rep = Rep::new(-100, "BEEFY: Bad signature"); // Message received with vote from voter not in validator set. pub(super) const UNKNOWN_VOTER: Rep = Rep::new(-150, "BEEFY: Unknown voter"); - // A message received that cannot be evaluated relative to our current state. - pub(super) const OUT_OF_SCOPE_MESSAGE: Rep = Rep::new(-500, "BEEFY: Out-of-scope message"); // Message containing invalid proof. pub(super) const INVALID_PROOF: Rep = Rep::new(-5000, "BEEFY: Invalid commit"); // Reputation cost per signature checked for invalid proof. diff --git a/substrate/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs b/substrate/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs index 71c5c49b3690ccfbd3cb06242d6733528a880964..d856e9748a101b4ad71a39957579c41c0c75aaf4 100644 --- a/substrate/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs +++ b/substrate/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs @@ -201,7 +201,7 @@ where let peer = request.peer; match self.handle_request(request) { Ok(()) => { - metric_inc!(self, beefy_successful_justification_responses); + metric_inc!(self.metrics, beefy_successful_justification_responses); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 Handled BEEFY justification request from {:?}.", peer @@ -209,7 +209,7 @@ where }, Err(e) => { // peer reputation changes already applied in `self.handle_request()` - metric_inc!(self, beefy_failed_justification_responses); + metric_inc!(self.metrics, beefy_failed_justification_responses); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 Failed to handle BEEFY justification request from {:?}: {}", peer, e, diff --git a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs index ef462a54fca5b8c656b601a9296346cb1532433c..992b9fa08c093cfbb6bc65e83915997253c6c1dd 100644 --- a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs +++ b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -43,7 +43,7 @@ use crate::{ }; /// Response type received from network. -type Response = Result, RequestFailure>; +type Response = Result<(Vec, ProtocolName), RequestFailure>; /// Used to receive a response from the network. type ResponseReceiver = oneshot::Receiver; @@ -125,6 +125,7 @@ impl OnDemandJustificationsEngine { peer, self.protocol_name.clone(), payload, + None, tx, IfDisconnected::ImmediateError, ); @@ -147,7 +148,7 @@ impl OnDemandJustificationsEngine { if let Some(peer) = self.try_next_peer() { self.request_from_peer(peer, RequestInfo { block, active_set }); } else { - metric_inc!(self, beefy_on_demand_justification_no_peer_to_request_from); + metric_inc!(self.metrics, beefy_on_demand_justification_no_peer_to_request_from); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 no good peers to request justif #{:?} from", block @@ -193,25 +194,25 @@ impl OnDemandJustificationsEngine { ); match e { RequestFailure::Refused => { - metric_inc!(self, beefy_on_demand_justification_peer_refused); + metric_inc!(self.metrics, beefy_on_demand_justification_peer_refused); let peer_report = PeerReport { who: *peer, cost_benefit: cost::REFUSAL_RESPONSE }; Error::InvalidResponse(peer_report) }, _ => { - metric_inc!(self, beefy_on_demand_justification_peer_error); + metric_inc!(self.metrics, beefy_on_demand_justification_peer_error); Error::ResponseError }, } }) - .and_then(|encoded| { + .and_then(|(encoded, _)| { decode_and_verify_finality_proof::( &encoded[..], req_info.block, &req_info.active_set, ) .map_err(|(err, signatures_checked)| { - metric_inc!(self, beefy_on_demand_justification_invalid_proof); + metric_inc!(self.metrics, beefy_on_demand_justification_invalid_proof); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}", @@ -260,7 +261,7 @@ impl OnDemandJustificationsEngine { } }, Ok(proof) => { - metric_inc!(self, beefy_on_demand_justification_good_proof); + metric_inc!(self.metrics, beefy_on_demand_justification_good_proof); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 received valid on-demand justif #{:?} from {:?}", block, peer diff --git a/substrate/client/consensus/beefy/src/import.rs b/substrate/client/consensus/beefy/src/import.rs index 5b2abb20acede2764502bfadb1948550c5c7a8b2..ed8ed68c4e8d0d378728ba87d1ffe726f6c4c11a 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) { @@ -149,13 +159,13 @@ where // The proof is valid and the block is imported and final, we can import. debug!( target: LOG_TARGET, - "🥩 import justif {:?} for block number {:?}.", proof, number + "🥩 import justif {} for block number {:?}.", proof, number ); // Send the justification to the BEEFY voter for processing. self.justification_sender .notify(|| Ok::<_, ()>(proof)) .expect("the closure always returns Ok; qed."); - metric_inc!(self, beefy_good_justification_imports); + metric_inc!(self.metrics, beefy_good_justification_imports); }, Err(err) => { debug!( @@ -164,7 +174,7 @@ where number, err, ); - metric_inc!(self, beefy_bad_justification_imports); + metric_inc!(self.metrics, beefy_bad_justification_imports); }, } }, diff --git a/substrate/client/consensus/beefy/src/justification.rs b/substrate/client/consensus/beefy/src/justification.rs index 483184e2374a2b9239554e4ce2709cd7a294cb8c..7f1b9e5237c3970eb8cba4936debd18c77117288 100644 --- a/substrate/client/consensus/beefy/src/justification.rs +++ b/substrate/client/consensus/beefy/src/justification.rs @@ -76,7 +76,7 @@ pub(crate) fn verify_with_validator_set( .as_ref() .map(|sig| { signatures_checked += 1; - BeefyKeystore::verify(id, sig, &message[..]) + BeefyKeystore::verify(*id, sig, &message[..]) }) .unwrap_or(false) }) @@ -93,7 +93,8 @@ pub(crate) fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { use sp_consensus_beefy::{ - known_payloads, Commitment, Keyring, Payload, SignedCommitment, VersionedFinalityProof, + known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment, + VersionedFinalityProof, }; use substrate_test_runtime_client::runtime::Block; @@ -103,7 +104,7 @@ pub(crate) mod tests { pub(crate) fn new_finality_proof( block_num: NumberFor, validator_set: &ValidatorSet, - keys: &[Keyring], + keys: &[Keyring], ) -> BeefyVersionedFinalityProof { let commitment = Commitment { payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), @@ -174,7 +175,7 @@ pub(crate) mod tests { }; // change a signature to a different key *bad_signed_commitment.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); + Some(Keyring::::Dave.sign(&bad_signed_commitment.commitment.encode())); match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { Err((ConsensusError::InvalidJustification, 3)) => (), e => assert!(false, "Got unexpected {:?}", e), diff --git a/substrate/client/consensus/beefy/src/keystore.rs b/substrate/client/consensus/beefy/src/keystore.rs index 925bb08828220fa6720f222e71979721c26ffc62..2ddc938fbc6ce33aa74c62d051cf77a519c634ee 100644 --- a/substrate/client/consensus/beefy/src/keystore.rs +++ b/substrate/client/consensus/beefy/src/keystore.rs @@ -16,41 +16,45 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, RuntimeAppPublic}; -use sp_core::keccak_256; +use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, AppCrypto, RuntimeAppPublic}; +use sp_consensus_beefy::{AuthorityIdBound, BeefyAuthorityId, BeefySignatureHasher}; +use sp_core::ecdsa; +#[cfg(feature = "bls-experimental")] +use sp_core::ecdsa_bls377; +use sp_crypto_hashing::keccak_256; use sp_keystore::KeystorePtr; +use codec::Decode; use log::warn; - -use sp_consensus_beefy::{ - ecdsa_crypto::{Public, Signature}, - BeefyAuthorityId, -}; +use std::marker::PhantomData; use crate::{error, LOG_TARGET}; -/// Hasher used for BEEFY signatures. -pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256; - /// A BEEFY specific keystore implemented as a `Newtype`. This is basically a /// wrapper around [`sp_keystore::Keystore`] and allows to customize /// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); +pub(crate) struct BeefyKeystore( + Option, + PhantomData AuthorityId>, +); -impl BeefyKeystore { +impl BeefyKeystore { /// Check if the keystore contains a private key for one of the public keys /// contained in `keys`. A public key with a matching private key is known /// as a local authority id. /// /// Return the public key for which we also do have a private key. If no /// matching private key is found, `None` will be returned. - pub fn authority_id(&self, keys: &[Public]) -> Option { + pub fn authority_id(&self, keys: &[AuthorityId]) -> Option { let store = self.0.clone()?; // we do check for multiple private keys as a key store sanity check. - let public: Vec = keys + let public: Vec = keys .iter() - .filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)])) + .filter(|k| { + store + .has_keys(&[(::to_raw_vec(k), BEEFY_KEY_TYPE)]) + }) .cloned() .collect(); @@ -71,55 +75,125 @@ impl BeefyKeystore { /// Note that `message` usually will be pre-hashed before being signed. /// /// Return the message signature or an error in case of failure. - pub fn sign(&self, public: &Public, message: &[u8]) -> Result { + pub fn sign( + &self, + public: &AuthorityId, + message: &[u8], + ) -> Result<::Signature, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let msg = keccak_256(message); - let public = public.as_ref(); - - let sig = store - .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, public, &msg) - .map_err(|e| error::Error::Keystore(e.to_string()))? - .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; - - // check that `sig` has the expected result type - let sig = sig.clone().try_into().map_err(|_| { - error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) + // ECDSA should use ecdsa_sign_prehashed since it needs to be hashed by keccak_256 instead + // of blake2. As such we need to deal with producing the signatures case-by-case + let signature_byte_array: Vec = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let msg_hash = keccak_256(message); + let public: ecdsa::Public = ecdsa::Public::try_from(public.as_slice()).unwrap(); + + let sig = store + .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| { + error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()) + })?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let public: ecdsa_bls377::Public = + ecdsa_bls377::Public::try_from(public.as_slice()).unwrap(); + let sig = store + .ecdsa_bls377_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &message) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| error::Error::Signature("bls377_sign() failed".to_string()))?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into()))?, + }; + + //check that `sig` has the expected result type + let signature = ::Signature::decode( + &mut signature_byte_array.as_slice(), + ) + .map_err(|_| { + error::Error::Signature(format!( + "invalid signature {:?} for key {:?}", + signature_byte_array, public + )) })?; - Ok(sig) + Ok(signature) } /// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently /// supported (i.e. found in the keystore). - pub fn public_keys(&self) -> Result, error::Error> { + pub fn public_keys(&self) -> Result, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let pk: Vec = - store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect(); - - Ok(pk) + let pk = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => store + .ecdsa_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => store + .ecdsa_bls377_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into())), + }; + + pk } /// Use the `public` key to verify that `sig` is a valid signature for `message`. /// /// Return `true` if the signature is authentic, `false` otherwise. - pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { + pub fn verify( + public: &AuthorityId, + sig: &::Signature, + message: &[u8], + ) -> bool { BeefyAuthorityId::::verify(public, sig, message) } } -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { - BeefyKeystore(store) +impl From> for BeefyKeystore +where + ::Signature: Send + Sync, +{ + fn from(store: Option) -> BeefyKeystore { + BeefyKeystore(store, PhantomData) } } #[cfg(test)] pub mod tests { - use sp_consensus_beefy::{ecdsa_crypto, Keyring}; - use sp_core::{ecdsa, Pair}; - use sp_keystore::testing::MemoryKeystore; + #[cfg(feature = "bls-experimental")] + use sp_consensus_beefy::ecdsa_bls_crypto; + use sp_consensus_beefy::{ + ecdsa_crypto, + test_utils::{BeefySignerAuthority, Keyring}, + }; + use sp_core::Pair as PairT; + use sp_keystore::{testing::MemoryKeystore, Keystore}; use super::*; use crate::error::Error; @@ -128,152 +202,265 @@ pub mod tests { MemoryKeystore::new().into() } - #[test] - fn verify_should_work() { - let msg = keccak_256(b"I am Alice!"); - let sig = Keyring::Alice.sign(b"I am Alice!"); - - assert!(ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Alice.public().into(), + fn pair_verify_should_work< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let msg = b"I am Alice!"; + let sig = Keyring::::Alice.sign(b"I am Alice!"); + + assert!(>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), )); // different public key -> fail - assert!(!ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Bob.public().into(), + assert!(!>::verify( + &Keyring::Bob.public(), + &sig, + &msg.as_slice(), )); - let msg = keccak_256(b"I am not Alice!"); + let msg = b"I am not Alice!"; // different msg -> fail - assert!( - !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) - ); + assert!(!>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), + )); + } + + /// Generate key pair in the given store using the provided seed + fn generate_in_store( + store: KeystorePtr, + key_type: sp_application_crypto::KeyTypeId, + owner: Option>, + ) -> AuthorityId + where + AuthorityId: + AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + { + let optional_seed: Option = owner.map(|owner| owner.to_seed()); + + match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let pk = store.ecdsa_generate_new(key_type, optional_seed.as_deref()).ok().unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let pk = store + .ecdsa_bls377_generate_new(key_type, optional_seed.as_deref()) + .ok() + .unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + _ => panic!("Requested CRYPTO_ID is not supported by the BEEFY Keyring"), + } } #[test] - fn pair_works() { - let want = ecdsa_crypto::Pair::from_string("//Alice", None) + fn pair_verify_should_work_ecdsa() { + pair_verify_should_work::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn pair_verify_should_work_ecdsa_n_bls() { + pair_verify_should_work::(); + } + + fn pair_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let want = ::Pair::from_string("//Alice", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Alice.pair().to_raw_vec(); + let got = Keyring::::Alice.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Bob", None) + let want = ::Pair::from_string("//Bob", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Bob.pair().to_raw_vec(); + let got = Keyring::::Bob.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Charlie", None) + let want = ::Pair::from_string("//Charlie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Charlie.pair().to_raw_vec(); + let got = Keyring::::Charlie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Dave", None) + let want = ::Pair::from_string("//Dave", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Dave.pair().to_raw_vec(); + let got = Keyring::::Dave.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Eve", None) + let want = ::Pair::from_string("//Eve", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Eve.pair().to_raw_vec(); + let got = Keyring::::Eve.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Ferdie", None) + let want = ::Pair::from_string("//Ferdie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Ferdie.pair().to_raw_vec(); + let got = Keyring::::Ferdie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//One", None) + let want = ::Pair::from_string("//One", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::One.pair().to_raw_vec(); + let got = Keyring::::One.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Two", None) + let want = ::Pair::from_string("//Two", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Two.pair().to_raw_vec(); + let got = Keyring::::Two.pair().to_raw_vec(); assert_eq!(want, got); } #[test] - fn authority_id_works() { + fn ecdsa_pair_works() { + pair_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn ecdsa_n_bls_pair_works() { + pair_works::(); + } + + fn authority_id_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); + + let alice = Keyring::::Alice.public(); let bob = Keyring::Bob.public(); let charlie = Keyring::Charlie.public(); - let store: BeefyKeystore = Some(store).into(); + let beefy_store: BeefyKeystore = Some(store).into(); let mut keys = vec![bob, charlie]; - let id = store.authority_id(keys.as_slice()); + let id = beefy_store.authority_id(keys.as_slice()); assert!(id.is_none()); keys.push(alice.clone()); - let id = store.authority_id(keys.as_slice()).unwrap(); + let id = beefy_store.authority_id(keys.as_slice()).unwrap(); assert_eq!(id, alice); } #[test] - fn sign_works() { + fn authority_id_works_for_ecdsa() { + authority_id_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn authority_id_works_for_ecdsa_n_bls() { + authority_id_works::(); + } + + fn sign_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let alice = Keyring::Alice.public(); + + let store: BeefyKeystore = Some(store).into(); let msg = b"are you involved or commited?"; let sig1 = store.sign(&alice, msg).unwrap(); - let sig2 = Keyring::Alice.sign(msg); + let sig2 = Keyring::::Alice.sign(msg); assert_eq!(sig1, sig2); } #[test] - fn sign_error() { + fn sign_works_for_ecdsa() { + sign_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_works_for_ecdsa_n_bls() { + sign_works::(); + } + + fn sign_error< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >( + expected_error_message: &str, + ) where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited?"; let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); + let err = Error::Signature(expected_error_message.to_string()); assert_eq!(sig, err); } + #[test] + fn sign_error_for_ecdsa() { + sign_error::("ecdsa_sign_prehashed() failed"); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_error_for_ecdsa_n_bls() { + sign_error::("bls377_sign() failed"); + } + #[test] fn sign_no_keystore() { - let store: BeefyKeystore = None.into(); + let store: BeefyKeystore = None.into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited"; @@ -283,17 +470,21 @@ pub mod tests { assert_eq!(sig, err); } - #[test] - fn verify_works() { + fn verify_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); + + let alice = Keyring::Alice.public(); // `msg` and `sig` match let msg = b"are you involved or commited?"; @@ -305,32 +496,48 @@ pub mod tests { assert!(!BeefyKeystore::verify(&alice, &sig, msg)); } - // Note that we use keys with and without a seed for this test. #[test] - fn public_keys_works() { + fn verify_works_for_ecdsa() { + verify_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn verify_works_for_ecdsa_n_bls() { + verify_works::(); + } + + // Note that we use keys with and without a seed for this test. + fn public_keys_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { const TEST_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"test"); let store = keystore(); - let add_key = - |key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap(); - // test keys - let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); - let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); - - let _ = add_key(TEST_TYPE, None); - let _ = add_key(TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Alice)); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Bob)); // BEEFY keys - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); + let _ = + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave)); + let _ = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve)); + + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); - let key1: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); - let key2: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); + let key1 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); + let key2 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let keys = store.public_keys().ok().unwrap(); @@ -340,4 +547,16 @@ pub mod tests { assert!(keys.contains(&key1)); assert!(keys.contains(&key2)); } + + #[test] + fn public_keys_works_for_ecdsa_keystore() { + public_keys_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn public_keys_works_for_ecdsa_n_bls() { + public_keys_works::(); + } } diff --git a/substrate/client/consensus/beefy/src/lib.rs b/substrate/client/consensus/beefy/src/lib.rs index 89a5d51c88702df76f7e3f0e7bb4f65076531066..323af1bc8305c38bf7a34b4690b212954b7b0864 100644 --- a/substrate/client/consensus/beefy/src/lib.rs +++ b/substrate/client/consensus/beefy/src/lib.rs @@ -27,35 +27,33 @@ use crate::{ outgoing_requests_engine::OnDemandJustificationsEngine, BeefyJustifsRequestHandler, }, }, + error::Error, import::BeefyBlockImport, metrics::register_metrics, - round::Rounds, - worker::PersistedState, }; -use futures::{stream::Fuse, StreamExt}; -use log::{debug, error, info}; +use futures::{stream::Fuse, FutureExt, StreamExt}; +use log::{debug, error, info, warn}; use parking_lot::Mutex; use prometheus::Registry; use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer}; use sc_consensus::BlockImport; -use sc_network::{NetworkRequest, ProtocolName}; +use sc_network::{NetworkRequest, NotificationService, ProtocolName}; use sc_network_gossip::{GossipEngine, Network as GossipNetwork, Syncing as GossipSyncing}; -use sp_api::{HeaderT, NumberFor, ProvideRuntimeApi}; -use sp_blockchain::{ - Backend as BlockchainBackend, Error as ClientError, HeaderBackend, Result as ClientResult, -}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_consensus::{Error as ConsensusError, SyncOracle}; use sp_consensus_beefy::{ - ecdsa_crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, + ecdsa_crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID, }; use sp_keystore::KeystorePtr; use sp_mmr_primitives::MmrApi; -use sp_runtime::traits::{Block, Zero}; +use sp_runtime::traits::{Block, Header as HeaderT, NumberFor, Zero}; use std::{ collections::{BTreeMap, VecDeque}, marker::PhantomData, sync::Arc, + time::Duration, }; mod aux_schema; @@ -69,15 +67,27 @@ pub mod communication; pub mod import; pub mod justification; +use crate::{ + communication::{gossip::GossipValidator, peers::PeerReport}, + justification::BeefyVersionedFinalityProof, + keystore::BeefyKeystore, + metrics::VoterMetrics, + round::Rounds, + worker::{BeefyWorker, PersistedState}, +}; pub use communication::beefy_protocol_name::{ gossip_protocol_name, justifications_protocol_name as justifs_protocol_name, }; +use sc_utils::mpsc::TracingUnboundedReceiver; +use sp_runtime::generic::OpaqueDigestItemId; #[cfg(test)] mod tests; 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 @@ -178,6 +188,8 @@ pub struct BeefyNetworkParams { pub network: Arc, /// Syncing service implementing a sync oracle and an event stream for peers. pub sync: Arc, + /// Handle for receiving notification events. + pub notification_service: Box, /// Chain specific BEEFY gossip protocol name. See /// [`communication::beefy_protocol_name::gossip_protocol_name`]. pub gossip_protocol_name: ProtocolName, @@ -211,6 +223,247 @@ pub struct BeefyParams { /// Handler for incoming BEEFY justifications requests from a remote peer. pub on_demand_justifications_handler: BeefyJustifsRequestHandler, } +/// Helper object holding BEEFY worker communication/gossip components. +/// +/// These are created once, but will be reused if worker is restarted/reinitialized. +pub(crate) struct BeefyComms { + pub gossip_engine: GossipEngine, + pub gossip_validator: Arc>, + pub gossip_report_stream: TracingUnboundedReceiver, + pub on_demand_justifications: OnDemandJustificationsEngine, +} + +/// Helper builder object for building [worker::BeefyWorker]. +/// +/// It has to do it in two steps: initialization and build, because the first step can sleep waiting +/// for certain chain and backend conditions, and while sleeping we still need to pump the +/// GossipEngine. Once initialization is done, the GossipEngine (and other pieces) are added to get +/// the complete [worker::BeefyWorker] object. +pub(crate) struct BeefyWorkerBuilder { + // utilities + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + // voter metrics + metrics: Option, + persisted_state: PersistedState, +} + +impl BeefyWorkerBuilder +where + B: Block + codec::Codec, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + /// This will wait for the chain to enable BEEFY (if not yet enabled) and also wait for the + /// backend to sync all headers required by the voter to build a contiguous chain of mandatory + /// justifications. Then it builds the initial voter state using a combination of previously + /// persisted state in AUX DB and latest chain information/progress. + /// + /// Returns a sane `BeefyWorkerBuilder` that can build the `BeefyWorker`. + pub async fn async_initialize( + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + metrics: Option, + min_block_delta: u32, + gossip_validator: Arc>, + finality_notifications: &mut Fuse>, + ) -> Result { + // Wait for BEEFY pallet to be active before starting voter. + let (beefy_genesis, best_grandpa) = + wait_for_runtime_pallet(&*runtime, finality_notifications).await?; + + let persisted_state = Self::load_or_init_state( + beefy_genesis, + best_grandpa, + min_block_delta, + backend.clone(), + runtime.clone(), + &key_store, + &metrics, + ) + .await?; + // Update the gossip validator with the right starting round and set id. + persisted_state + .gossip_filter_config() + .map(|f| gossip_validator.update_filter(f))?; + + Ok(BeefyWorkerBuilder { backend, runtime, key_store, metrics, persisted_state }) + } + + /// Takes rest of missing pieces as params and builds the `BeefyWorker`. + pub fn build( + self, + payload_provider: P, + sync: Arc, + comms: BeefyComms, + links: BeefyVoterLinks, + pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, + ) -> BeefyWorker { + BeefyWorker { + backend: self.backend, + runtime: self.runtime, + key_store: self.key_store, + metrics: self.metrics, + persisted_state: self.persisted_state, + payload_provider, + sync, + comms, + links, + pending_justifications, + } + } + + // If no persisted state present, walk back the chain from first GRANDPA notification to either: + // - latest BEEFY finalized block, or if none found on the way, + // - BEEFY pallet genesis; + // Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to + // finalize. + async fn init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + ) -> Result, Error> { + let blockchain = backend.blockchain(); + + let beefy_genesis = runtime + .runtime_api() + .beefy_genesis(best_grandpa.hash()) + .ok() + .flatten() + .filter(|genesis| *genesis == beefy_genesis) + .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?; + // Walk back the imported blocks and initialize voter either, at the last block with + // a BEEFY justification, or at pallet genesis block; voter will resume from there. + let mut sessions = VecDeque::new(); + let mut header = best_grandpa.clone(); + let state = loop { + if let Some(true) = blockchain + .justifications(header.hash()) + .ok() + .flatten() + .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) + { + debug!( + target: LOG_TARGET, + "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", + *header.number() + ); + let best_beefy = *header.number(); + // If no session boundaries detected so far, just initialize new rounds here. + if sessions.is_empty() { + let active_set = + expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?; + let mut rounds = Rounds::new(best_beefy, active_set); + // Mark the round as already finalized. + rounds.conclude(best_beefy); + sessions.push_front(rounds); + } + let state = PersistedState::checked_new( + best_grandpa, + best_beefy, + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?; + break state + } + + if *header.number() == beefy_genesis { + // We've reached BEEFY genesis, initialize voter here. + let genesis_set = + expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?; + info!( + target: LOG_TARGET, + "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ + Starting voting rounds at block {:?}, genesis validator set {:?}.", + beefy_genesis, + genesis_set, + ); + + sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); + break PersistedState::checked_new( + best_grandpa, + Zero::zero(), + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))? + } + + if let Some(active) = find_authorities_change::(&header) { + debug!( + target: LOG_TARGET, + "🥩 Marking block {:?} as BEEFY Mandatory.", + *header.number() + ); + sessions.push_front(Rounds::new(*header.number(), active)); + } + + // Move up the chain. + header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; + }; + + aux_schema::write_current_version(backend.as_ref())?; + aux_schema::write_voter_state(backend.as_ref(), &state)?; + Ok(state) + } + + async fn load_or_init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + key_store: &BeefyKeystore, + metrics: &Option, + ) -> Result, Error> { + // Initialize voter state from AUX DB if compatible. + if let Some(mut state) = crate::aux_schema::load_persistent(backend.as_ref())? + // Verify state pallet genesis matches runtime. + .filter(|state| state.pallet_genesis() == beefy_genesis) + { + // Overwrite persisted state with current best GRANDPA block. + state.set_best_grandpa(best_grandpa.clone()); + // Overwrite persisted data with newly provided `min_block_delta`. + state.set_min_block_delta(min_block_delta); + debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); + + // Make sure that all the headers that we need have been synced. + let mut new_sessions = vec![]; + let mut header = best_grandpa.clone(); + while *header.number() > state.best_beefy() { + if state.voting_oracle().can_add_session(*header.number()) { + if let Some(active) = find_authorities_change::(&header) { + new_sessions.push((active, *header.number())); + } + } + header = + wait_for_parent_header(backend.blockchain(), header, HEADER_SYNC_DELAY).await?; + } + + // Make sure we didn't miss any sessions during node restart. + for (validator_set, new_session_start) in new_sessions.drain(..).rev() { + debug!( + target: LOG_TARGET, + "🥩 Handling missed BEEFY session after node restart: {:?}.", + new_session_start + ); + state.init_session_at(new_session_start, validator_set, key_store, metrics); + } + return Ok(state) + } + + // No valid voter-state persisted, re-initialize from pallet genesis. + Self::init_state(beefy_genesis, best_grandpa, min_block_delta, backend, runtime).await + } +} /// Start the BEEFY gadget. /// @@ -243,6 +496,7 @@ pub async fn start_beefy_gadget( let BeefyNetworkParams { network, sync, + notification_service, gossip_protocol_name, justifications_protocol_name, .. @@ -264,6 +518,7 @@ pub async fn start_beefy_gadget( let gossip_engine = GossipEngine::new( network.clone(), sync.clone(), + notification_service, gossip_protocol_name.clone(), gossip_validator.clone(), None, @@ -277,7 +532,7 @@ pub async fn start_beefy_gadget( known_peers, prometheus_registry.clone(), ); - let mut beefy_comms = worker::BeefyComms { + let mut beefy_comms = BeefyComms { gossip_engine, gossip_validator, gossip_report_stream, @@ -287,49 +542,45 @@ pub async fn start_beefy_gadget( // We re-create and re-run the worker in this loop in order to quickly reinit and resume after // select recoverable errors. loop { - // Wait for BEEFY pallet to be active before starting voter. - let persisted_state = 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(state) => state, - Err(e) => { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - }, + // Make sure to pump gossip engine while waiting for initialization conditions. + let worker_builder = loop { + futures::select! { + builder_init_result = BeefyWorkerBuilder::async_initialize( + backend.clone(), + runtime.clone(), + key_store.clone().into(), + metrics.clone(), + min_block_delta, + beefy_comms.gossip_validator.clone(), + &mut finality_notifications, + ).fuse() => { + match builder_init_result { + Ok(builder) => break builder, + Err(e) => { + error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", e); + return + }, + } + }, + // Pump peer reports + _ = &mut beefy_comms.gossip_report_stream.next() => { + continue + }, + // Pump gossip engine. + _ = &mut beefy_comms.gossip_engine => { + error!(target: LOG_TARGET, "🥩 Gossip engine has unexpectedly terminated."); + return + } + } }; - // Update the gossip validator with the right starting round and set id. - if let Err(e) = persisted_state - .gossip_filter_config() - .map(|f| beefy_comms.gossip_validator.update_filter(f)) - { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - } - let worker = worker::BeefyWorker { - backend: backend.clone(), - payload_provider: payload_provider.clone(), - runtime: runtime.clone(), - sync: sync.clone(), - key_store: key_store.clone().into(), - comms: beefy_comms, - links: links.clone(), - metrics: metrics.clone(), - pending_justifications: BTreeMap::new(), - persisted_state, - }; + let worker = worker_builder.build( + payload_provider.clone(), + sync.clone(), + beefy_comms, + links.clone(), + BTreeMap::new(), + ); match futures::future::select( Box::pin(worker.run(&mut block_import_justif, &mut finality_notifications)), @@ -353,145 +604,51 @@ pub async fn start_beefy_gadget( } } -fn load_or_init_voter_state( - backend: &BE, - runtime: &R, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, -) -> ClientResult> -where - B: Block, - BE: Backend, - R: ProvideRuntimeApi, - R::Api: BeefyApi, -{ - // Initialize voter state from AUX DB if compatible. - crate::aux_schema::load_persistent(backend)? - // Verify state pallet genesis matches runtime. - .filter(|state| state.pallet_genesis() == beefy_genesis) - .and_then(|mut state| { - // Overwrite persisted state with current best GRANDPA block. - state.set_best_grandpa(best_grandpa.clone()); - // Overwrite persisted data with newly provided `min_block_delta`. - state.set_min_block_delta(min_block_delta); - info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); - Some(Ok(state)) - }) - // No valid voter-state persisted, re-initialize from pallet genesis. - .unwrap_or_else(|| { - initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta) - }) -} - -// If no persisted state present, walk back the chain from first GRANDPA notification to either: -// - latest BEEFY finalized block, or if none found on the way, -// - BEEFY pallet genesis; -// Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to finalize. -fn initialize_voter_state( - backend: &BE, - runtime: &R, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, -) -> ClientResult> +/// 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, +) -> Result<::Header, Error> where B: Block, - BE: Backend, - R: ProvideRuntimeApi, - R::Api: BeefyApi, + BC: BlockchainBackend, { - let beefy_genesis = runtime - .runtime_api() - .beefy_genesis(best_grandpa.hash()) - .ok() - .flatten() - .filter(|genesis| *genesis == beefy_genesis) - .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?; - // Walk back the imported blocks and initialize voter either, at the last block with - // a BEEFY justification, or at pallet genesis block; voter will resume from there. - let blockchain = backend.blockchain(); - let mut sessions = VecDeque::new(); - let mut header = best_grandpa.clone(); - let state = loop { - if let Some(true) = blockchain - .justifications(header.hash()) - .ok() - .flatten() - .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some()) + 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(Error::Backend(msg)); + } + loop { + match blockchain + .header(*current.parent_hash()) + .map_err(|e| Error::Backend(e.to_string()))? { - info!( - target: LOG_TARGET, - "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.", - *header.number() - ); - let best_beefy = *header.number(); - // If no session boundaries detected so far, just initialize new rounds here. - if sessions.is_empty() { - let active_set = expect_validator_set(runtime, backend, &header)?; - let mut rounds = Rounds::new(best_beefy, active_set); - // Mark the round as already finalized. - rounds.conclude(best_beefy); - sessions.push_front(rounds); - } - let state = PersistedState::checked_new( - best_grandpa, - best_beefy, - sessions, - min_block_delta, - beefy_genesis, - ) - .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?; - break state - } - - if *header.number() == beefy_genesis { - // We've reached BEEFY genesis, initialize voter here. - let genesis_set = expect_validator_set(runtime, backend, &header)?; - info!( - target: LOG_TARGET, - "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ - Starting voting rounds at block {:?}, genesis validator set {:?}.", - beefy_genesis, - genesis_set, - ); - - sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); - break PersistedState::checked_new( - best_grandpa, - Zero::zero(), - sessions, - min_block_delta, - beefy_genesis, - ) - .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))? - } - - if let Some(active) = worker::find_authorities_change::(&header) { - info!( - target: LOG_TARGET, - "🥩 Marking block {:?} as BEEFY Mandatory.", - *header.number() - ); - sessions.push_front(Rounds::new(*header.number(), active)); + 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; + }, } - - // Move up the chain. - header = blockchain.expect_header(*header.parent_hash())?; - }; - - aux_schema::write_current_version(backend)?; - aux_schema::write_voter_state(backend, &state)?; - Ok(state) + } } /// Wait for BEEFY runtime pallet to be available, return active validator set. /// Should be called only once during worker initialization. async fn wait_for_runtime_pallet( runtime: &R, - mut gossip_engine: &mut GossipEngine, finality: &mut Fuse>, -) -> ClientResult<(NumberFor, ::Header)> +) -> Result<(NumberFor, ::Header), Error> where B: Block, R: ProvideRuntimeApi, @@ -499,65 +656,81 @@ where { info!(target: LOG_TARGET, "🥩 BEEFY gadget waiting for BEEFY pallet to become available..."); loop { - futures::select! { - notif = finality.next() => { - let notif = match notif { - Some(notif) => notif, - None => break - }; - let at = notif.header.hash(); - if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { - if *notif.header.number() >= start { - // Beefy pallet available, return header for best grandpa at the time. - info!( - target: LOG_TARGET, - "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", - notif.header.number(), start - ); - return Ok((start, notif.header)) - } - } - }, - _ = gossip_engine => { - break + let notif = finality.next().await.ok_or_else(|| { + let err_msg = "🥩 Finality stream has unexpectedly terminated.".into(); + error!(target: LOG_TARGET, "{}", err_msg); + Error::Backend(err_msg) + })?; + let at = notif.header.hash(); + if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { + if *notif.header.number() >= start { + // Beefy pallet available, return header for best grandpa at the time. + info!( + target: LOG_TARGET, + "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", + notif.header.number(), start + ); + return Ok((start, notif.header)) } } } - let err_msg = "🥩 Gossip engine has unexpectedly terminated.".into(); - error!(target: LOG_TARGET, "{}", err_msg); - Err(ClientError::Backend(err_msg)) } -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, -) -> ClientResult> +) -> Result, Error> where B: Block, BE: Backend, 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(number {:?}, hash {:?})", + at_header.number(), + at_header.hash() + ); + let mut header = at_header.clone(); + loop { + debug!(target: LOG_TARGET, "🥩 Looking for auth set change at block number: {:?}", *header.number()); + if let Ok(Some(active)) = runtime.runtime_api().validator_set(header.hash()) { + return Ok(active) + } else { + match 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 + .map_err(|e| Error::Backend(e.to_string()))?, } - }) - .ok_or_else(|| ClientError::Backend("Could not find initial validator set".into())) + } + } +} + +/// Scan the `header` digest log for a BEEFY validator set change. Return either the new +/// validator set or `None` in case no validator set change has been signaled. +pub(crate) fn find_authorities_change(header: &B::Header) -> Option> +where + B: Block, +{ + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) } diff --git a/substrate/client/consensus/beefy/src/metrics.rs b/substrate/client/consensus/beefy/src/metrics.rs index 031748bdceab5d3553f35b27fca4b6a2afdf70c9..ef3928d79faaaee7f0aacf0af583347e825a30bb 100644 --- a/substrate/client/consensus/beefy/src/metrics.rs +++ b/substrate/client/consensus/beefy/src/metrics.rs @@ -305,10 +305,10 @@ pub(crate) fn register_metrics( // if expr does not derive `Display`. #[macro_export] macro_rules! metric_set { - ($self:ident, $m:ident, $v:expr) => {{ + ($metrics:expr, $m:ident, $v:expr) => {{ let val: u64 = format!("{}", $v).parse().unwrap(); - if let Some(metrics) = $self.metrics.as_ref() { + if let Some(metrics) = $metrics.as_ref() { metrics.$m.set(val); } }}; @@ -316,8 +316,8 @@ macro_rules! metric_set { #[macro_export] macro_rules! metric_inc { - ($self:ident, $m:ident) => {{ - if let Some(metrics) = $self.metrics.as_ref() { + ($metrics:expr, $m:ident) => {{ + if let Some(metrics) = $metrics.as_ref() { metrics.$m.inc(); } }}; @@ -325,8 +325,8 @@ macro_rules! metric_inc { #[macro_export] macro_rules! metric_get { - ($self:ident, $m:ident) => {{ - $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) + ($metrics:expr, $m:ident) => {{ + $metrics.as_ref().map(|metrics| metrics.$m.clone()) }}; } diff --git a/substrate/client/consensus/beefy/src/round.rs b/substrate/client/consensus/beefy/src/round.rs index 6f400ce47843cb9e6579c35330767104419f5728..0045dc70c260ee43ca1137d7d7f92b097220aeaf 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); + } } } @@ -203,7 +207,7 @@ mod tests { use sc_network_test::Block; use sp_consensus_beefy::{ - known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload, + known_payloads::MMR_ROOT_ID, test_utils::Keyring, Commitment, EquivocationProof, Payload, SignedCommitment, ValidatorSet, VoteMessage, }; @@ -222,7 +226,7 @@ mod tests { #[test] fn round_tracker() { let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let bob_vote = (Keyring::Bob.public(), Keyring::::Bob.sign(b"I am committed")); let threshold = 2; // adding new vote allowed @@ -233,7 +237,8 @@ mod tests { // vote is not done assert!(!rt.is_done(threshold)); - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + let alice_vote = + (Keyring::Alice.public(), Keyring::::Alice.sign(b"I am committed")); // adding new vote (self vote this time) allowed assert!(rt.add_vote(alice_vote)); @@ -267,7 +272,11 @@ mod tests { assert_eq!(42, rounds.validator_set_id()); assert_eq!(1, rounds.session_start()); assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + &vec![ + Keyring::::Alice.public(), + Keyring::::Bob.public(), + Keyring::::Charlie.public() + ], rounds.validators() ); } @@ -297,7 +306,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add 1st good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); @@ -306,26 +315,26 @@ mod tests { assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Dave.public(); - vote.signature = Keyring::Dave.sign(b"I am committed"); + vote.signature = Keyring::::Dave.sign(b"I am committed"); // invalid vote (Dave is not a validator) assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid); vote.id = Keyring::Bob.public(); - vote.signature = Keyring::Bob.sign(b"I am committed"); + vote.signature = Keyring::::Bob.sign(b"I am committed"); // add 2nd good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Charlie.public(); - vote.signature = Keyring::Charlie.sign(b"I am committed"); + vote.signature = Keyring::::Charlie.sign(b"I am committed"); // add 3rd good vote -> round concluded -> signatures present assert_eq!( rounds.add_vote(vote.clone()), VoteImportResult::RoundConcluded(SignedCommitment { commitment, signatures: vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), None, ] }) @@ -333,7 +342,7 @@ mod tests { rounds.conclude(block_number); vote.id = Keyring::Eve.public(); - vote.signature = Keyring::Eve.sign(b"I am committed"); + vote.signature = Keyring::::Eve.sign(b"I am committed"); // Eve is a validator, but round was concluded, adding vote disallowed assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale); } @@ -360,7 +369,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add vote for previous session, should fail assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); @@ -403,22 +412,22 @@ mod tests { let mut alice_vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut bob_vote = VoteMessage { id: Keyring::Bob.public(), commitment: commitment.clone(), - signature: Keyring::Bob.sign(b"I am committed"), + signature: Keyring::::Bob.sign(b"I am committed"), }; let mut charlie_vote = VoteMessage { id: Keyring::Charlie.public(), commitment, - signature: Keyring::Charlie.sign(b"I am committed"), + signature: Keyring::::Charlie.sign(b"I am committed"), }; let expected_signatures = vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), ]; // round 1 - only 2 out of 3 vote @@ -480,7 +489,7 @@ mod tests { let alice_vote1 = VoteMessage { id: Keyring::Alice.public(), commitment: commitment1, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut alice_vote2 = alice_vote1.clone(); alice_vote2.commitment = commitment2; diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 902feca163983c594baa54f01688ad183c171a03..d106c9dcd88165f929085972483e090dbf78c559 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -28,13 +28,16 @@ use crate::{ }, request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler}, }, + error::Error, gossip_protocol_name, justification::*, - load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers, - PersistedState, + wait_for_runtime_pallet, + worker::PersistedState, + BeefyRPCLinks, BeefyVoterLinks, BeefyWorkerBuilder, KnownPeers, }; use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt}; use parking_lot::Mutex; +use sc_block_builder::BlockBuilderBuilder; use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend}; use sc_consensus::{ BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, @@ -54,9 +57,10 @@ use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, known_payloads, mmr::{find_mmr_root_digest, MmrRootProvider}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash, - OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + test_utils::Keyring as BeefyKeyring, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, MmrRootHash, OpaqueKeyOwnershipProof, + Payload, SignedCommitment, ValidatorSet, ValidatorSetId, VersionedFinalityProof, VoteMessage, + BEEFY_ENGINE_ID, }; use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; @@ -71,7 +75,7 @@ use substrate_test_runtime_client::{BlockBuilderExt, ClientExt}; use tokio::time::Duration; const GENESIS_HASH: H256 = H256::zero(); -fn beefy_gossip_proto_name() -> ProtocolName { +pub(crate) fn beefy_gossip_proto_name() -> ProtocolName { gossip_protocol_name(GENESIS_HASH, None) } @@ -346,11 +350,11 @@ fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: Beef .unwrap(); } -pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { - keys.iter().map(|&key| key.public().into()).collect() +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|key| key.public().into()).collect() } -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> KeystorePtr { +pub(crate) fn create_beefy_keystore(authority: &BeefyKeyring) -> KeystorePtr { let keystore = MemoryKeystore::new(); keystore .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&authority.to_seed())) @@ -362,27 +366,27 @@ async fn voter_init_setup( net: &mut BeefyTestNet, finality: &mut futures::stream::Fuse>, api: &TestApi, -) -> sp_blockchain::Result> { +) -> Result, Error> { let backend = net.peer(0).client().as_backend(); - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - "/beefy/whatever", - gossip_validator, - None, - ); - 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) + let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(api, finality).await.unwrap(); + let key_store = None.into(); + let metrics = None; + BeefyWorkerBuilder::load_or_init_state( + beefy_genesis, + best_grandpa, + 1, + backend, + Arc::new(api.clone()), + &key_store, + &metrics, + ) + .await } // Spawns beefy voters. Returns a future to spawn on the runtime. fn initialize_beefy( net: &mut BeefyTestNet, - peers: Vec<(usize, &BeefyKeyring, Arc)>, + peers: Vec<(usize, &BeefyKeyring, Arc)>, min_block_delta: u32, ) -> impl Future where @@ -391,10 +395,18 @@ where { let tasks = FuturesUnordered::new(); + let mut notification_services = peers + .iter() + .map(|(peer_id, _, _)| { + let peer = &mut net.peers[*peer_id]; + (*peer_id, peer.take_notification_service(&beefy_gossip_proto_name()).unwrap()) + }) + .collect::>(); + for (peer_id, key, api) in peers.into_iter() { let peer = &net.peers[peer_id]; - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (_, _, peer_data) = net.make_block_import(peer.client().clone()); let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data; @@ -408,6 +420,7 @@ where let network_params = crate::BeefyNetworkParams { network: peer.network_service().clone(), sync: peer.sync_service().clone(), + notification_service: notification_services.remove(&peer_id).unwrap(), gossip_protocol_name: beefy_gossip_proto_name(), justifications_protocol_name: on_demand_justif_handler.protocol_name(), _phantom: PhantomData, @@ -451,7 +464,7 @@ async fn run_for(duration: Duration, net: &Arc>) { pub(crate) fn get_beefy_streams( net: &mut BeefyTestNet, // peer index and key - peers: impl Iterator, + peers: impl Iterator)>, ) -> (Vec>, Vec>>) { let mut best_block_streams = Vec::new(); @@ -549,7 +562,7 @@ async fn streams_empty_after_timeout( async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key - peers: impl Iterator + Clone, + peers: impl Iterator)> + Clone, finalize_target: &H256, expected_beefy: &[u64], ) { @@ -741,7 +754,6 @@ async fn correct_beefy_payload() { #[tokio::test] async fn beefy_importing_justifications() { use futures::{future::poll_fn, task::Poll}; - use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockBackend; sp_tracing::try_init_simple(); @@ -774,8 +786,10 @@ async fn beefy_importing_justifications() { .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()) }; - let builder = full_client - .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) + let builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(full_client.genesis_hash()) + .with_parent_block_number(0) + .build() .unwrap(); let block = builder.build().unwrap().block; let hashof1 = block.header.hash(); @@ -792,7 +806,11 @@ async fn beefy_importing_justifications() { // Import block 2 with "valid" justification (beefy pallet genesis block not yet reached). let block_num = 2; - let builder = full_client.new_block_at(hashof1, Default::default(), false).unwrap(); + let builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(hashof1) + .with_parent_block_number(1) + .build() + .unwrap(); let block = builder.build().unwrap().block; let hashof2 = block.header.hash(); @@ -824,7 +842,11 @@ async fn beefy_importing_justifications() { // Import block 3 with valid justification. let block_num = 3; - let builder = full_client.new_block_at(hashof2, Default::default(), false).unwrap(); + let builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(hashof2) + .with_parent_block_number(2) + .build() + .unwrap(); let block = builder.build().unwrap().block; let hashof3 = block.header.hash(); let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys); @@ -858,7 +880,11 @@ async fn beefy_importing_justifications() { // Import block 4 with invalid justification (incorrect validator set). let block_num = 4; - let builder = full_client.new_block_at(hashof3, Default::default(), false).unwrap(); + let builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(hashof3) + .with_parent_block_number(3) + .build() + .unwrap(); let block = builder.build().unwrap().block; let hashof4 = block.header.hash(); let keys = &[BeefyKeyring::Alice]; @@ -1002,7 +1028,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)); @@ -1043,7 +1069,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)); @@ -1072,7 +1098,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)); @@ -1125,7 +1151,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)); @@ -1178,7 +1204,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)); @@ -1226,7 +1252,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)); @@ -1236,6 +1262,68 @@ async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() { assert_eq!(state, persisted_state); } +#[tokio::test] +async fn should_catch_up_when_loading_saved_voter_state() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1); + let backend = net.peer(0).client().as_backend(); + + // push 30 blocks with `AuthorityChange` digests every 10 blocks + let hashes = net.generate_blocks_and_sync(30, 10, &validator_set, false).await; + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + // finalize 13 without justifications + net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); + + let api = TestApi::with_validator_set(&validator_set); + + // load persistent state - nothing in DB, should init at genesis + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); + + // Test initialization at session boundary. + // verify voter initialized with two sessions starting at blocks 1 and 10 + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 2); + assert_eq!(sessions[0].session_start(), 1); + assert_eq!(sessions[1].session_start(), 10); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 1); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is mandatory block 1 + assert_eq!(persisted_state.best_beefy(), 0); + assert_eq!(persisted_state.best_grandpa_number(), 13); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1)); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); + + // now let's consider that the node goes offline, and then it restarts after a while + + // finalize 25 without justifications + net.peer(0).client().as_client().finalize_block(hashes[25], None).unwrap(); + // load persistent state - state preset in DB + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); + + // Verify voter initialized with old sessions plus a new one starting at block 20. + // There shouldn't be any duplicates. + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 3); + assert_eq!(sessions[0].session_start(), 1); + assert_eq!(sessions[1].session_start(), 10); + assert_eq!(sessions[2].session_start(), 20); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), 1); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is mandatory block 1 + assert_eq!(persisted_state.best_beefy(), 0); + assert_eq!(persisted_state.best_grandpa_number(), 25); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1)); +} + #[tokio::test] async fn beefy_finalizing_after_pallet_genesis() { sp_tracing::try_init_simple(); @@ -1357,7 +1445,7 @@ async fn gossipped_finality_proofs() { let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - let charlie = &net.peers[2]; + let charlie = &mut net.peers[2]; let known_peers = Arc::new(Mutex::new(KnownPeers::::new())); // Charlie will run just the gossip engine and not the full voter. let (gossip_validator, _) = GossipValidator::new(known_peers); @@ -1370,6 +1458,7 @@ async fn gossipped_finality_proofs() { let mut charlie_gossip_engine = sc_network_gossip::GossipEngine::new( charlie.network_service().clone(), charlie.sync_service().clone(), + charlie.take_notification_service(&beefy_gossip_proto_name()).unwrap(), beefy_gossip_proto_name(), charlie_gossip_validator.clone(), None, diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index 309d8c5135bef96447bc87b1485775fb96f6b255..c8eb19621ba5a6e45ea2be93e1e2e830492a9b81 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -18,35 +18,35 @@ use crate::{ communication::{ - gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage, GossipValidator}, + gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage}, peers::PeerReport, - request_response::outgoing_requests_engine::{OnDemandJustificationsEngine, ResponseInfo}, + request_response::outgoing_requests_engine::ResponseInfo, }, error::Error, + find_authorities_change, justification::BeefyVersionedFinalityProof, - keystore::{BeefyKeystore, BeefySignatureHasher}, + keystore::BeefyKeystore, metric_inc, metric_set, metrics::VoterMetrics, round::{Rounds, VoteImportResult}, - BeefyVoterLinks, LOG_TARGET, + BeefyComms, BeefyVoterLinks, LOG_TARGET, }; use codec::{Codec, Decode, DecodeAll, Encode}; use futures::{stream::Fuse, FutureExt, StreamExt}; use log::{debug, error, info, log_enabled, trace, warn}; use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; -use sc_network_gossip::GossipEngine; -use sc_utils::{mpsc::TracingUnboundedReceiver, notification::NotificationReceiver}; -use sp_api::{BlockId, ProvideRuntimeApi}; +use sc_utils::notification::NotificationReceiver; +use sp_api::ProvideRuntimeApi; use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; use sp_consensus::SyncOracle; use sp_consensus_beefy::{ check_equivocation_proof, ecdsa_crypto::{AuthorityId, Signature}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, ValidatorSet, + BeefyApi, BeefySignatureHasher, Commitment, EquivocationProof, PayloadProvider, ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, }; use sp_runtime::{ - generic::OpaqueDigestItemId, + generic::BlockId, traits::{Block, Header, NumberFor, Zero}, SaturatedConversion, }; @@ -176,6 +176,13 @@ impl VoterOracle { } } + /// Check if an observed session can be added to the Oracle. + pub fn can_add_session(&self, session_start: NumberFor) -> bool { + let latest_known_session_start = + self.sessions.back().map(|session| session.session_start()); + Some(session_start) > latest_known_session_start + } + /// Add new observed session to the Oracle. pub fn add_session(&mut self, rounds: Rounds) { self.sessions.push_back(rounds); @@ -212,7 +219,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. @@ -236,12 +243,10 @@ impl VoterOracle { /// Return `Some(number)` if we should be voting on block `number`, /// return `None` if there is no block we should vote on. pub fn voting_target(&self) -> Option> { - let rounds = if let Some(r) = self.sessions.front() { - r - } else { + let rounds = self.sessions.front().or_else(|| { debug!(target: LOG_TARGET, "🥩 No voting round started"); - return None - }; + None + })?; let best_grandpa = *self.best_grandpa_block_header.number(); let best_beefy = self.best_beefy_block; @@ -298,6 +303,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; } @@ -306,31 +315,66 @@ impl PersistedState { self.voting_oracle.best_grandpa_block_header = best_grandpa; } + pub fn voting_oracle(&self) -> &VoterOracle { + &self.voting_oracle + } + pub(crate) fn gossip_filter_config(&self) -> Result, Error> { let (start, end) = self.voting_oracle.accepted_interval()?; let validator_set = self.voting_oracle.current_validator_set()?; Ok(GossipFilterCfg { start, end, validator_set }) } -} -/// Helper object holding BEEFY worker communication/gossip components. -/// -/// These are created once, but will be reused if worker is restarted/reinitialized. -pub(crate) struct BeefyComms { - pub gossip_engine: GossipEngine, - pub gossip_validator: Arc>, - pub gossip_report_stream: TracingUnboundedReceiver, - pub on_demand_justifications: OnDemandJustificationsEngine, + /// Handle session changes by starting new voting round for mandatory blocks. + pub fn init_session_at( + &mut self, + new_session_start: NumberFor, + validator_set: ValidatorSet, + key_store: &BeefyKeystore, + metrics: &Option, + ) { + debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); + + // BEEFY should finalize a mandatory block during each session. + if let Ok(active_session) = self.voting_oracle.active_rounds() { + if !active_session.mandatory_done() { + debug!( + target: LOG_TARGET, + "🥩 New session {} while active session {} is still lagging.", + validator_set.id(), + active_session.validator_set_id(), + ); + metric_inc!(metrics, beefy_lagging_sessions); + } + } + + if log_enabled!(target: LOG_TARGET, log::Level::Debug) { + // verify the new validator set - only do it if we're also logging the warning + if verify_validator_set::(&new_session_start, &validator_set, key_store).is_err() { + metric_inc!(metrics, beefy_no_authority_found_in_store); + } + } + + let id = validator_set.id(); + self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); + metric_set!(metrics, beefy_validator_set_id, id); + info!( + target: LOG_TARGET, + "🥩 New Rounds for validator set id: {:?} with session_start {:?}", + id, + new_session_start + ); + } } -/// A BEEFY worker plays the BEEFY protocol +/// A BEEFY worker/voter that follows the BEEFY protocol pub(crate) struct BeefyWorker { // utilities pub backend: Arc, - pub payload_provider: P, pub runtime: Arc, + pub key_store: BeefyKeystore, + pub payload_provider: P, pub sync: Arc, - pub key_store: BeefyKeystore, // communication (created once, but returned and reused if worker is restarted/reinitialized) pub comms: BeefyComms, @@ -340,12 +384,12 @@ pub(crate) struct BeefyWorker { pub links: BeefyVoterLinks, // voter state - /// BEEFY client metrics. - pub metrics: Option, /// Buffer holding justifications for future processing. pub pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, /// Persisted voter state. pub persisted_state: PersistedState, + /// BEEFY voter metrics + pub metrics: Option, } impl BeefyWorker @@ -365,74 +409,22 @@ where &self.persisted_state.voting_oracle } + #[cfg(test)] fn active_rounds(&mut self) -> Result<&Rounds, Error> { self.persisted_state.voting_oracle.active_rounds() } - /// Verify `active` validator set for `block` against the key store - /// - /// We want to make sure that we have _at least one_ key in our keystore that - /// is part of the validator set, that's because if there are no local keys - /// then we can't perform our job as a validator. - /// - /// Note that for a non-authority node there will be no keystore, and we will - /// return an error and don't check. The error can usually be ignored. - fn verify_validator_set( - &self, - block: &NumberFor, - active: &ValidatorSet, - ) -> Result<(), Error> { - let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); - - let public_keys = self.key_store.public_keys()?; - let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); - - if store.intersection(&active).count() == 0 { - let msg = "no authority public key found in store".to_string(); - debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); - metric_inc!(self, beefy_no_authority_found_in_store); - Err(Error::Keystore(msg)) - } else { - Ok(()) - } - } - /// Handle session changes by starting new voting round for mandatory blocks. fn init_session_at( &mut self, validator_set: ValidatorSet, new_session_start: NumberFor, ) { - debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); - - // BEEFY should finalize a mandatory block during each session. - if let Ok(active_session) = self.active_rounds() { - if !active_session.mandatory_done() { - debug!( - target: LOG_TARGET, - "🥩 New session {} while active session {} is still lagging.", - validator_set.id(), - active_session.validator_set_id(), - ); - metric_inc!(self, beefy_lagging_sessions); - } - } - - if log_enabled!(target: LOG_TARGET, log::Level::Debug) { - // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&new_session_start, &validator_set); - } - - let id = validator_set.id(); - self.persisted_state - .voting_oracle - .add_session(Rounds::new(new_session_start, validator_set)); - metric_set!(self, beefy_validator_set_id, id); - info!( - target: LOG_TARGET, - "🥩 New Rounds for validator set id: {:?} with session_start {:?}", - id, - new_session_start + self.persisted_state.init_session_at( + new_session_start, + validator_set, + &self.key_store, + &self.metrics, ); } @@ -440,13 +432,14 @@ where &mut self, notification: &FinalityNotification, ) -> Result<(), Error> { + let header = ¬ification.header; debug!( target: LOG_TARGET, - "🥩 Finality notification: header {:?} tree_route {:?}", - notification.header, + "🥩 Finality notification: header(number {:?}, hash {:?}) tree_route {:?}", + header.number(), + header.hash(), notification.tree_route, ); - let header = ¬ification.header; self.runtime .runtime_api() @@ -456,6 +449,7 @@ where .filter(|genesis| *genesis == self.persisted_state.pallet_genesis) .ok_or(Error::ConsensusReset)?; + let mut new_session_added = false; if *header.number() > self.best_grandpa_block() { // update best GRANDPA finalized block we have seen self.persisted_state.set_best_grandpa(header.clone()); @@ -475,9 +469,15 @@ where { if let Some(new_validator_set) = find_authorities_change::(&header) { self.init_session_at(new_validator_set, *header.number()); + new_session_added = true; } } + if new_session_added { + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; + } + // Update gossip validator votes filter. if let Err(e) = self .persisted_state @@ -508,7 +508,7 @@ where true, ); }, - RoundAction::Drop => metric_inc!(self, beefy_stale_votes), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_votes), RoundAction::Enqueue => error!(target: LOG_TARGET, "🥩 unexpected vote: {:?}.", vote), }; Ok(()) @@ -528,23 +528,23 @@ where match self.voting_oracle().triage_round(block_num)? { RoundAction::Process => { debug!(target: LOG_TARGET, "🥩 Process justification for round: {:?}.", block_num); - metric_inc!(self, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); self.finalize(justification)? }, RoundAction::Enqueue => { debug!(target: LOG_TARGET, "🥩 Buffer justification for round: {:?}.", block_num); if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { self.pending_justifications.entry(block_num).or_insert(justification); - metric_inc!(self, beefy_buffered_justifications); + metric_inc!(self.metrics, beefy_buffered_justifications); } else { - metric_inc!(self, beefy_buffered_justifications_dropped); + metric_inc!(self.metrics, beefy_buffered_justifications_dropped); warn!( target: LOG_TARGET, "🥩 Buffer justification dropped for round: {:?}.", block_num ); } }, - RoundAction::Drop => metric_inc!(self, beefy_stale_justifications), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_justifications), }; Ok(()) } @@ -559,14 +559,14 @@ where match rounds.add_vote(vote) { VoteImportResult::RoundConcluded(signed_commitment) => { let finality_proof = VersionedFinalityProof::V1(signed_commitment); - info!( + debug!( target: LOG_TARGET, "🥩 Round #{} concluded, finality_proof: {:?}.", block_number, finality_proof ); // We created the `finality_proof` and know to be valid. // New state is persisted after finalization. self.finalize(finality_proof.clone())?; - metric_inc!(self, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); return Ok(Some(finality_proof)) }, VoteImportResult::Ok => { @@ -580,14 +580,14 @@ where crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; } - metric_inc!(self, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); }, VoteImportResult::Equivocation(proof) => { - metric_inc!(self, beefy_equivocation_votes); + metric_inc!(self.metrics, beefy_equivocation_votes); self.report_equivocation(proof)?; }, - VoteImportResult::Invalid => metric_inc!(self, beefy_invalid_votes), - VoteImportResult::Stale => metric_inc!(self, beefy_stale_votes), + VoteImportResult::Invalid => metric_inc!(self.metrics, beefy_invalid_votes), + VoteImportResult::Stale => metric_inc!(self.metrics, beefy_stale_votes), }; Ok(None) } @@ -617,7 +617,7 @@ where crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; - metric_set!(self, beefy_best_block, block_num); + metric_set!(self.metrics, beefy_best_block, block_num); self.comms.on_demand_justifications.cancel_requests_older_than(block_num); @@ -668,12 +668,16 @@ where for (num, justification) in justifs_to_process.into_iter() { debug!(target: LOG_TARGET, "🥩 Handle buffered justification for: {:?}.", num); - metric_inc!(self, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); if let Err(err) = self.finalize(justification) { error!(target: LOG_TARGET, "🥩 Error finalizing block: {}", err); } } - metric_set!(self, beefy_buffered_justifications, self.pending_justifications.len()); + metric_set!( + self.metrics, + beefy_buffered_justifications, + self.pending_justifications.len() + ); } Ok(()) } @@ -682,7 +686,7 @@ where fn try_to_vote(&mut self) -> Result<(), Error> { // Vote if there's now a new vote target. if let Some(target) = self.voting_oracle().voting_target() { - metric_set!(self, beefy_should_vote_on, target); + metric_set!(self.metrics, beefy_should_vote_on, target); if target > self.persisted_state.best_voted { self.do_vote(target)?; } @@ -772,7 +776,7 @@ where .gossip_engine .gossip_message(proofs_topic::(), encoded_proof, true); } else { - metric_inc!(self, beefy_votes_sent); + metric_inc!(self.metrics, beefy_votes_sent); debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", vote); let encoded_vote = GossipMessage::::Vote(vote).encode(); self.comms.gossip_engine.gossip_message(votes_topic::(), encoded_vote, false); @@ -780,7 +784,7 @@ where // Persist state after vote to avoid double voting in case of voter restarts. self.persisted_state.best_voted = target_number; - metric_set!(self, beefy_best_voted, target_number); + metric_set!(self.metrics, beefy_best_voted, target_number); crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string())) } @@ -848,15 +852,10 @@ where .fuse(), ); + self.process_new_state(); let error = loop { - // Act on changed 'state'. - self.process_new_state(); - // Mutable reference used to drive the gossip engine. let mut gossip_engine = &mut self.comms.gossip_engine; - // Use temp val and report after async section, - // to avoid having to Mutex-wrap `gossip_engine`. - let mut gossip_report: Option = None; // Wait for, and handle external events. // The branches below only change 'state', actual voting happens afterwards, @@ -884,10 +883,15 @@ where if let Err(err) = self.triage_incoming_justif(justif) { debug!(target: LOG_TARGET, "🥩 {}", err); } - gossip_report = Some(peer_report); + self.comms.gossip_engine.report(peer_report.who, peer_report.cost_benefit); + }, + ResponseInfo::PeerReport(peer_report) => { + self.comms.gossip_engine.report(peer_report.who, peer_report.cost_benefit); + continue; + }, + ResponseInfo::Pending => { + continue; }, - ResponseInfo::PeerReport(peer_report) => gossip_report = Some(peer_report), - ResponseInfo::Pending => (), } }, justif = block_import_justif.next() => { @@ -924,12 +928,15 @@ where }, // Process peer reports. report = self.comms.gossip_report_stream.next() => { - gossip_report = report; + if let Some(PeerReport { who, cost_benefit }) = report { + self.comms.gossip_engine.report(who, cost_benefit); + } + continue; }, } - if let Some(PeerReport { who, cost_benefit }) = gossip_report { - self.comms.gossip_engine.report(who, cost_benefit); - } + + // Act on changed 'state'. + self.process_new_state(); }; // return error _and_ `comms` that can be reused @@ -954,7 +961,7 @@ where return Ok(()) } else if let Some(local_id) = self.key_store.authority_id(validators) { if offender_id == local_id { - debug!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); + warn!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); return Ok(()) } } @@ -997,24 +1004,9 @@ where } } -/// Scan the `header` digest log for a BEEFY validator set change. Return either the new -/// validator set or `None` in case no validator set change has been signaled. -pub(crate) fn find_authorities_change(header: &B::Header) -> Option> -where - B: Block, -{ - let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); - - let filter = |log: ConsensusLog| match log { - ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), - _ => None, - }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) -} - /// Calculate next block number to vote on. /// -/// Return `None` if there is no voteable target yet. +/// Return `None` if there is no votable target yet. fn vote_target(best_grandpa: N, best_beefy: N, session_start: N, min_delta: u32) -> Option where N: AtLeast32Bit + Copy + Debug, @@ -1022,7 +1014,7 @@ where // if the mandatory block (session_start) does not have a beefy justification yet, // we vote on it let target = if best_beefy < session_start { - debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start,); + debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start); session_start } else { let diff = best_grandpa.saturating_sub(best_beefy) + 1u32.into(); @@ -1048,11 +1040,42 @@ where } } +/// Verify `active` validator set for `block` against the key store +/// +/// We want to make sure that we have _at least one_ key in our keystore that +/// is part of the validator set, that's because if there are no local keys +/// then we can't perform our job as a validator. +/// +/// Note that for a non-authority node there will be no keystore, and we will +/// return an error and don't check. The error can usually be ignored. +fn verify_validator_set( + block: &NumberFor, + active: &ValidatorSet, + key_store: &BeefyKeystore, +) -> Result<(), Error> { + let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); + + let public_keys = key_store.public_keys()?; + let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); + + if store.intersection(&active).count() == 0 { + let msg = "no authority public key found in store".to_string(); + debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); + Err(Error::Keystore(msg)) + } else { + Ok(()) + } +} + #[cfg(test)] pub(crate) mod tests { use super::*; use crate::{ - communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + communication::{ + gossip::GossipValidator, + notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + request_response::outgoing_requests_engine::OnDemandJustificationsEngine, + }, tests::{ create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet, TestApi, @@ -1062,33 +1085,28 @@ pub(crate) mod tests { use futures::{future::poll_fn, task::Poll}; use parking_lot::Mutex; use sc_client_api::{Backend as BackendT, HeaderBackend}; + use sc_network_gossip::GossipEngine; use sc_network_sync::SyncingService; use sc_network_test::TestNetFactory; - use sp_api::HeaderT; use sp_blockchain::Backend as BlockchainBackendT; use sp_consensus_beefy::{ - generate_equivocation_proof, known_payloads, known_payloads::MMR_ROOT_ID, - mmr::MmrRootProvider, Keyring, Payload, SignedCommitment, + known_payloads, + known_payloads::MMR_ROOT_ID, + mmr::MmrRootProvider, + test_utils::{generate_equivocation_proof, Keyring}, + ConsensusLog, Payload, SignedCommitment, }; - use sp_runtime::traits::One; + use sp_runtime::traits::{Header as HeaderT, One}; use substrate_test_runtime_client::{ runtime::{Block, Digest, DigestItem, Header}, Backend, }; impl PersistedState { - pub fn voting_oracle(&self) -> &VoterOracle { - &self.voting_oracle - } - pub fn active_round(&self) -> Result<&Rounds, Error> { 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() } @@ -1102,7 +1120,7 @@ pub(crate) mod tests { fn create_beefy_worker( peer: &mut BeefyPeer, - key: &Keyring, + key: &Keyring, min_block_delta: u32, genesis_validator_set: ValidatorSet, ) -> BeefyWorker< @@ -1112,7 +1130,7 @@ pub(crate) mod tests { TestApi, Arc>, > { - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (to_rpc_justif_sender, from_voter_justif_stream) = BeefyVersionedFinalityProofStream::::channel(); @@ -1136,12 +1154,16 @@ pub(crate) mod tests { let api = Arc::new(TestApi::with_validator_set(&genesis_validator_set)); let network = peer.network_service().clone(); let sync = peer.sync_service().clone(); + let notification_service = peer + .take_notification_service(&crate::tests::beefy_gossip_proto_name()) + .unwrap(); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); let (gossip_validator, gossip_report_stream) = GossipValidator::new(known_peers.clone()); let gossip_validator = Arc::new(gossip_validator); let gossip_engine = GossipEngine::new( network.clone(), sync.clone(), + notification_service, "/beefy/1", gossip_validator.clone(), None, @@ -1177,13 +1199,13 @@ pub(crate) mod tests { }; BeefyWorker { backend, - payload_provider, runtime: api, key_store: Some(keystore).into(), - links, - comms, metrics, + payload_provider, sync: Arc::new(sync), + links, + comms, pending_justifications: BTreeMap::new(), persisted_state, } @@ -1457,19 +1479,22 @@ pub(crate) mod tests { let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); // keystore doesn't contain other keys than validators' - assert_eq!(worker.verify_validator_set(&1, &validator_set), Ok(())); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), Ok(())); // unknown `Bob` key let keys = &[Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let err_msg = "no authority public key found in store".to_string(); let expected = Err(Error::Keystore(err_msg)); - assert_eq!(worker.verify_validator_set(&1, &validator_set), expected); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), expected); // worker has no keystore worker.key_store = None.into(); let expected_err = Err(Error::Keystore("no Keystore".into())); - assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); + assert_eq!( + verify_validator_set::(&1, &validator_set, &worker.key_store), + expected_err + ); } #[tokio::test] @@ -1498,7 +1523,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); @@ -1521,7 +1546,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) { @@ -1558,7 +1583,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 f269e3752d435488e9c843260454adbccd325b7b..7f36dfc09ef8451927eb32ebd651258647abee3b 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -9,19 +9,22 @@ 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 = ["peerid", "ed25519"] } -log = "0.4.17" +libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } +log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-utils = { path = "../../utils" } diff --git a/substrate/client/consensus/common/src/block_import.rs b/substrate/client/consensus/common/src/block_import.rs index a451692ad478e41fb48a055cd2a5a5ae3c12c510..d91851aea62cf4564464b67bcd0bdcd5d712e139 100644 --- a/substrate/client/consensus/common/src/block_import.rs +++ b/substrate/client/consensus/common/src/block_import.rs @@ -43,7 +43,7 @@ pub enum ImportResult { } /// Auxiliary data associated with an imported block result. -#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ImportedAux { /// Only the header has been imported. Block body verification was skipped. pub header_only: bool, diff --git a/substrate/client/consensus/epochs/Cargo.toml b/substrate/client/consensus/epochs/Cargo.toml index 07de83980bcf766b55b97a9c8826b26c0978e839..ff6bf86a6a441d49a8950c38d2333bd0e067724d 100644 --- a/substrate/client/consensus/epochs/Cargo.toml +++ b/substrate/client/consensus/epochs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-epochs" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true description = "Generic epochs-based utilities for consensus" edition.workspace = true @@ -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 472bdd1c5b820e5590d6d38c1e15add9c94e6fd6..1ab953525220b82a500e5a23be0b94a673be51a2 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-grandpa" -version = "0.10.0-dev" +version = "0.19.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -10,23 +10,26 @@ 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" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" -serde_json = "1.0.107" -thiserror = "1.0" +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } @@ -37,6 +40,7 @@ sc-consensus = { path = "../common" } sc-network = { path = "../../network" } sc-network-gossip = { path = "../../network-gossip" } sc-network-common = { path = "../../network/common" } +sc-network-sync = { path = "../../network/sync" } sc-telemetry = { path = "../../telemetry" } sc-utils = { path = "../../utils" } sp-api = { path = "../../../primitives/api" } @@ -45,6 +49,7 @@ sp-arithmetic = { path = "../../../primitives/arithmetic" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-consensus = { path = "../../../primitives/consensus/common" } sp-core = { path = "../../../primitives/core" } +sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } sp-keystore = { path = "../../../primitives/keystore" } sp-runtime = { path = "../../../primitives/runtime" } @@ -52,7 +57,7 @@ sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec", "test-helpers"] } -serde = "1.0.188" +serde = { workspace = true, default-features = true } tokio = "1.22.0" sc-network = { path = "../../network" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index e2f9e40afb2da661cd5003b4755d7ee425b5ee1a..f7e87415448e8c44952b957cf365fb36b089e4b0 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-grandpa-rpc" -version = "0.10.0-dev" +version = "0.19.0" authors.workspace = true description = "RPC extensions for the GRANDPA finality gadget" repository.workspace = true @@ -9,14 +9,17 @@ 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", "server", "macros"] } -log = "0.4.8" +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } -serde = { version = "1.0.188", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-client-api = { path = "../../../api" } sc-consensus-grandpa = { path = ".." } sc-rpc = { path = "../../../rpc" } @@ -26,7 +29,7 @@ sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] sc-block-builder = { path = "../../../block-builder" } -sc-rpc = { path = "../../../rpc", features = ["test-helpers"]} +sc-rpc = { path = "../../../rpc", features = ["test-helpers"] } sp-core = { path = "../../../../primitives/core" } sp-consensus-grandpa = { path = "../../../../primitives/consensus/grandpa" } sp-keyring = { path = "../../../../primitives/keyring" } diff --git a/substrate/client/consensus/grandpa/rpc/src/error.rs b/substrate/client/consensus/grandpa/rpc/src/error.rs index 4884380cd22d0797c8ab2174ec21fdec3026f72e..795077804a4b05dc94ec93c4f06915eaa3da35ac 100644 --- a/substrate/client/consensus/grandpa/rpc/src/error.rs +++ b/substrate/client/consensus/grandpa/rpc/src/error.rs @@ -16,10 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use jsonrpsee::{ - core::Error as JsonRpseeError, - types::error::{CallError, ErrorObject}, -}; +use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned}; #[derive(Debug, thiserror::Error)] /// Top-level error type for the RPC handler @@ -61,15 +58,11 @@ impl From for ErrorCode { } } -impl From for JsonRpseeError { +impl From for ErrorObjectOwned { fn from(error: Error) -> Self { let message = error.to_string(); let code = ErrorCode::from(error); - JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - code as i32, - message, - None::<()>, - ))) + ErrorObject::owned(code as i32, message, None::<()>) } } diff --git a/substrate/client/consensus/grandpa/rpc/src/finality.rs b/substrate/client/consensus/grandpa/rpc/src/finality.rs index f8ec01781ac6b4bedd86a71103ca1d8fa0d0901b..93f6c46e411ec7659b49aca73035b4a1657511cd 100644 --- a/substrate/client/consensus/grandpa/rpc/src/finality.rs +++ b/substrate/client/consensus/grandpa/rpc/src/finality.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; use sc_consensus_grandpa::FinalityProofProvider; use sp_runtime::traits::{Block as BlockT, NumberFor}; -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub struct EncodedFinalityProof(pub sp_core::Bytes); /// Local trait mainly to allow mocking in tests. diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index c6298bff969bd8db2818a255436e8993954addf0..0557eab93e2956b56956f50edea118c9c30cbd76 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -19,15 +19,13 @@ //! RPC API for GRANDPA. #![warn(missing_docs)] -use futures::{FutureExt, StreamExt}; +use futures::StreamExt; use log::warn; use std::sync::Arc; use jsonrpsee::{ - core::{async_trait, RpcResult}, + core::{async_trait, server::PendingSubscriptionSink}, proc_macros::rpc, - types::SubscriptionResult, - SubscriptionSink, }; mod error; @@ -35,13 +33,13 @@ mod finality; mod notification; mod report; -use sc_consensus_grandpa::GrandpaJustificationStream; -use sc_rpc::SubscriptionTaskExecutor; -use sp_runtime::traits::{Block as BlockT, NumberFor}; - +use error::Error; use finality::{EncodedFinalityProof, RpcFinalityProofProvider}; use notification::JustificationNotification; use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates}; +use sc_consensus_grandpa::GrandpaJustificationStream; +use sc_rpc::{utils::pipe_from_stream, SubscriptionTaskExecutor}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; /// Provides RPC methods for interacting with GRANDPA. #[rpc(client, server)] @@ -49,7 +47,7 @@ pub trait GrandpaApi { /// Returns the state of the current best round state as well as the /// ongoing background rounds. #[method(name = "grandpa_roundState")] - async fn round_state(&self) -> RpcResult; + async fn round_state(&self) -> Result; /// Returns the block most recently finalized by Grandpa, alongside /// side its justification. @@ -63,7 +61,7 @@ pub trait GrandpaApi { /// Prove finality for the given block number by returning the Justification for the last block /// in the set and all the intermediary headers to link them together. #[method(name = "grandpa_proveFinality")] - async fn prove_finality(&self, block: Number) -> RpcResult>; + async fn prove_finality(&self, block: Number) -> Result, Error>; } /// Provides RPC methods for interacting with GRANDPA. @@ -99,36 +97,28 @@ where Block: BlockT, ProofProvider: RpcFinalityProofProvider + Send + Sync + 'static, { - async fn round_state(&self) -> RpcResult { - ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into) + async fn round_state(&self) -> Result { + ReportedRoundStates::from(&self.authority_set, &self.voter_state) } - fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { + fn subscribe_justifications(&self, pending: PendingSubscriptionSink) { let stream = self.justification_stream.subscribe(100_000).map( |x: sc_consensus_grandpa::GrandpaJustification| { JustificationNotification::from(x) }, ); - let fut = async move { - sink.pipe_from_stream(stream).await; - }; - - self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); - Ok(()) + sc_rpc::utils::spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream)); } async fn prove_finality( &self, block: NumberFor, - ) -> RpcResult> { - self.finality_proof_provider - .rpc_prove_finality(block) - .map_err(|e| { - warn!("Error proving finality: {}", e); - error::Error::ProveFinalityFailed(e) - }) - .map_err(Into::into) + ) -> Result, Error> { + self.finality_proof_provider.rpc_prove_finality(block).map_err(|e| { + warn!("Error proving finality: {}", e); + error::Error::ProveFinalityFailed(e) + }) } } @@ -137,17 +127,15 @@ mod tests { use super::*; use std::{collections::HashSet, convert::TryInto, sync::Arc}; - use jsonrpsee::{ - types::{EmptyServerParams as EmptyParams, SubscriptionId}, - RpcModule, - }; + use jsonrpsee::{core::EmptyServerParams as EmptyParams, types::SubscriptionId, RpcModule}; use parity_scale_codec::{Decode, Encode}; - use sc_block_builder::{BlockBuilder, RecordProof}; + use sc_block_builder::BlockBuilderBuilder; use sc_consensus_grandpa::{ report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender, }; + use sc_rpc::testing::test_executor; use sp_blockchain::HeaderBackend; - use sp_core::{crypto::ByteArray, testing::TaskExecutor}; + use sp_core::crypto::ByteArray; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use substrate_test_runtime_client::{ @@ -264,7 +252,7 @@ mod tests { { let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof }); - let executor = Arc::new(TaskExecutor::default()); + let executor = test_executor(); let rpc = Grandpa::new( executor, @@ -283,9 +271,9 @@ mod tests { let (rpc, _) = setup_io_handler(EmptyVoterState); let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; - let (response, _) = rpc.raw_json_request(&request).await.unwrap(); + let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -306,8 +294,8 @@ mod tests { },\"id\":0}".to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; - let (response, _) = rpc.raw_json_request(&request).await.unwrap(); - assert_eq!(expected_response, response.result); + let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); + assert_eq!(expected_response, response); } #[tokio::test] @@ -315,7 +303,7 @@ mod tests { let (rpc, _) = setup_io_handler(TestVoterState); // Subscribe call. let _sub = rpc - .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new()) .await .unwrap(); @@ -323,33 +311,29 @@ mod tests { let (response, _) = rpc .raw_json_request( r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#, + 1, ) .await .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_justification() -> GrandpaJustification { let peers = &[Ed25519Keyring::Alice]; let builder = TestClientBuilder::new(); - let backend = builder.backend(); let client = builder.build(); let client = Arc::new(client); - let built_block = BlockBuilder::new( - &*client, - client.info().best_hash, - client.info().best_number, - RecordProof::No, - Default::default(), - &*backend, - ) - .unwrap() - .build() - .unwrap(); + let built_block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.info().best_hash) + .with_parent_block_number(client.info().best_number) + .build() + .unwrap() + .build() + .unwrap(); let block = built_block.block; let block_hash = block.hash(); @@ -390,7 +374,7 @@ mod tests { let (rpc, justification_sender) = setup_io_handler(TestVoterState); let mut sub = rpc - .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new()) .await .unwrap(); diff --git a/substrate/client/consensus/grandpa/rpc/src/report.rs b/substrate/client/consensus/grandpa/rpc/src/report.rs index ae4fd76d2857a49f29cd807ee257fe3bac3e7f27..b41d090afac8528a902ebfbf20cb3fef4c10dc12 100644 --- a/substrate/client/consensus/grandpa/rpc/src/report.rs +++ b/substrate/client/consensus/grandpa/rpc/src/report.rs @@ -57,21 +57,21 @@ impl ReportVoterState for SharedVoterState { } } -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct Prevotes { current_weight: u32, missing: BTreeSet, } -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct Precommits { current_weight: u32, missing: BTreeSet, } -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct RoundState { round: u32, @@ -111,7 +111,7 @@ impl RoundState { /// The state of the current best round, as well as the background rounds in a /// form suitable for serialization. -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReportedRoundStates { set_id: u32, diff --git a/substrate/client/consensus/grandpa/src/communication/mod.rs b/substrate/client/consensus/grandpa/src/communication/mod.rs index c0749858568f5a09bb25243de7c0c983fcecbd13..5c7e1276297a988b3109ae650e121331f57f930d 100644 --- a/substrate/client/consensus/grandpa/src/communication/mod.rs +++ b/substrate/client/consensus/grandpa/src/communication/mod.rs @@ -46,7 +46,7 @@ use finality_grandpa::{ Message::{Precommit, Prevote, PrimaryPropose}, }; use parity_scale_codec::{Decode, DecodeAll, Encode}; -use sc_network::{NetworkBlock, NetworkSyncForkRequest, ReputationChange}; +use sc_network::{NetworkBlock, NetworkSyncForkRequest, NotificationService, ReputationChange}; use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sp_keystore::KeystorePtr; @@ -59,7 +59,7 @@ use crate::{ use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, }; -use sc_network_common::sync::SyncEventStream; +use sc_network_sync::SyncEventStream; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, RoundNumber, SetId as SetIdNumber}; @@ -247,6 +247,7 @@ impl, S: Syncing> NetworkBridge { pub(crate) fn new( service: N, sync: S, + notification_service: Box, config: crate::Config, set_state: crate::environment::SharedVoterSetState, prometheus_registry: Option<&Registry>, @@ -260,6 +261,7 @@ impl, S: Syncing> NetworkBridge { let gossip_engine = Arc::new(Mutex::new(GossipEngine::new( service.clone(), sync.clone(), + notification_service, protocol, validator.clone(), prometheus_registry, diff --git a/substrate/client/consensus/grandpa/src/communication/tests.rs b/substrate/client/consensus/grandpa/src/communication/tests.rs index 10c4772fc76d64078c1f0d5ac413d09c0da91bac..fe24fb3cb20ea055e8ebfe385a974f57af5e4396 100644 --- a/substrate/client/consensus/grandpa/src/communication/tests.rs +++ b/substrate/client/consensus/grandpa/src/communication/tests.rs @@ -24,20 +24,19 @@ use super::{ }; use crate::{communication::grandpa_protocol_name, environment::SharedVoterSetState}; use futures::prelude::*; -use parity_scale_codec::Encode; +use parity_scale_codec::{DecodeAll, Encode}; use sc_network::{ config::{MultiaddrWithPeerId, Role}, event::Event as NetworkEvent, + service::traits::{Direction, MessageSink, NotificationEvent, NotificationService}, types::ProtocolName, Multiaddr, NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, NetworkSyncForkRequest, NotificationSenderError, NotificationSenderT as NotificationSender, PeerId, ReputationChange, }; -use sc_network_common::{ - role::ObservedRole, - sync::{SyncEvent as SyncStreamEvent, SyncEventStream}, -}; +use sc_network_common::role::{ObservedRole, Roles}; use sc_network_gossip::Validator; +use sc_network_sync::{SyncEvent as SyncStreamEvent, SyncEventStream}; use sc_network_test::{Block, Hash}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_consensus_grandpa::AuthorityList; @@ -76,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 peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() } - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) {} + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {} fn accept_unreserved_peers(&self) { unimplemented!(); @@ -125,6 +128,12 @@ impl NetworkPeers for TestNetwork { fn sync_num_connected(&self) -> usize { unimplemented!(); } + + fn peer_role(&self, _peer_id: PeerId, handshake: Vec) -> Option { + Roles::decode_all(&mut &handshake[..]) + .ok() + .and_then(|role| Some(ObservedRole::from(role))) + } } impl NetworkEventStream for TestNetwork { @@ -213,10 +222,70 @@ impl NetworkSyncForkRequest> for TestSync { fn set_sync_fork_request(&self, _peers: Vec, _hash: Hash, _number: NumberFor) {} } +#[derive(Debug)] +pub(crate) struct TestNotificationService { + sender: TracingUnboundedSender, + rx: TracingUnboundedReceiver, +} + +#[async_trait::async_trait] +impl NotificationService for TestNotificationService { + /// Instruct `Notifications` to open a new substream for `peer`. + async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> { + unimplemented!(); + } + + /// Instruct `Notifications` to close substream for `peer`. + async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> { + unimplemented!(); + } + + /// Send synchronous `notification` to `peer`. + fn send_sync_notification(&self, peer: &PeerId, notification: Vec) { + let _ = self.sender.unbounded_send(Event::WriteNotification(*peer, notification)); + } + + /// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure. + async fn send_async_notification( + &self, + _peer: &PeerId, + _notification: Vec, + ) -> Result<(), sc_network::error::Error> { + unimplemented!(); + } + + /// Set handshake for the notification protocol replacing the old handshake. + async fn set_handshake(&mut self, _handshake: Vec) -> Result<(), ()> { + unimplemented!(); + } + + fn try_set_handshake(&mut self, _handshake: Vec) -> Result<(), ()> { + unimplemented!(); + } + + /// Get next event from the `Notifications` event stream. + async fn next_event(&mut self) -> Option { + self.rx.next().await + } + + fn clone(&mut self) -> Result, ()> { + unimplemented!(); + } + + fn protocol(&self) -> &ProtocolName { + unimplemented!(); + } + + fn message_sink(&self, _peer: &PeerId) -> Option> { + unimplemented!(); + } +} + pub(crate) struct Tester { pub(crate) net_handle: super::NetworkBridge, gossip_validator: Arc>, pub(crate) events: TracingUnboundedReceiver, + pub(crate) notification_tx: TracingUnboundedSender, } impl Tester { @@ -281,6 +350,9 @@ fn voter_set_state() -> SharedVoterSetState { // needs to run in a tokio runtime. pub(crate) fn make_test_network() -> (impl Future, TestNetwork) { let (tx, rx) = tracing_unbounded("test", 100_000); + let (notification_tx, notification_rx) = tracing_unbounded("test-notification", 100_000); + + let notification_service = TestNotificationService { rx: notification_rx, sender: tx.clone() }; let net = TestNetwork { sender: tx }; let sync = TestSync {}; @@ -295,14 +367,22 @@ pub(crate) fn make_test_network() -> (impl Future, TestNetwork) } } - let bridge = - super::NetworkBridge::new(net.clone(), sync, config(), voter_set_state(), None, None); + let bridge = super::NetworkBridge::new( + net.clone(), + sync, + Box::new(notification_service), + config(), + voter_set_state(), + None, + None, + ); ( futures::future::ready(Tester { gossip_validator: bridge.validator.clone(), net_handle: bridge, events: rx, + notification_tx, }), net, ) @@ -387,63 +467,62 @@ fn good_commit_leads_to_relay() { let commit_to_send = encoded_commit.clone(); let network_bridge = tester.net_handle.clone(); - // asking for global communication will cause the test network - // to send us an event asking us for a stream. use it to - // send a message. + // `NetworkBridge` will be operational as soon as it's created and it's + // waiting for events from the network. Send it events that inform that + // a notification stream was opened and that a notification was received. + // + // Since each protocol has its own notification stream, events need not be filtered. let sender_id = id; - let send_message = tester.filter_network_events(move |event| match event { - Event::EventStream(sender) => { - // Add the sending peer and send the commit - let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: sender_id, - protocol: grandpa_protocol_name::NAME.into(), + + let send_message = async move { + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationStreamOpened { + peer: sender_id, + direction: Direction::Inbound, negotiated_fallback: None, - role: ObservedRole::Full, - received_handshake: vec![], + handshake: Roles::FULL.encode(), + }, + ); + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationReceived { + peer: sender_id, + notification: commit_to_send.clone(), + }, + ); + + // Add a random peer which will be the recipient of this message + let receiver_id = PeerId::random(); + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationStreamOpened { + peer: receiver_id, + direction: Direction::Inbound, + negotiated_fallback: None, + handshake: Roles::FULL.encode(), + }, + ); + + // Announce its local set being on the current set id through a neighbor + // packet, otherwise it won't be eligible to receive the commit + let _ = { + let update = gossip::VersionedNeighborPacket::V1(gossip::NeighborPacket { + round: Round(round), + set_id: SetId(set_id), + commit_finalized_height: 1, }); - let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { - remote: sender_id, - messages: vec![( - grandpa_protocol_name::NAME.into(), - commit_to_send.clone().into(), - )], - }); + let msg = gossip::GossipMessage::::Neighbor(update); - // Add a random peer which will be the recipient of this message - let receiver_id = PeerId::random(); - let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: receiver_id, - protocol: grandpa_protocol_name::NAME.into(), - negotiated_fallback: None, - role: ObservedRole::Full, - received_handshake: vec![], - }); + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationReceived { + peer: receiver_id, + notification: msg.encode(), + }, + ); + }; - // Announce its local set has being on the current set id through a neighbor - // packet, otherwise it won't be eligible to receive the commit - let _ = { - let update = gossip::VersionedNeighborPacket::V1(gossip::NeighborPacket { - round: Round(round), - set_id: SetId(set_id), - commit_finalized_height: 1, - }); - - let msg = gossip::GossipMessage::::Neighbor(update); - - sender.unbounded_send(NetworkEvent::NotificationsReceived { - remote: receiver_id, - messages: vec![( - grandpa_protocol_name::NAME.into(), - msg.encode().into(), - )], - }) - }; - - true - }, - _ => false, - }); + tester + } + .boxed(); // when the commit comes in, we'll tell the callback it was good. let handle_commit = commits_in.into_future().map(|(item, _)| match item.unwrap() { @@ -539,31 +618,32 @@ fn bad_commit_leads_to_report() { let commit_to_send = encoded_commit.clone(); let network_bridge = tester.net_handle.clone(); - // asking for global communication will cause the test network - // to send us an event asking us for a stream. use it to - // send a message. + // `NetworkBridge` will be operational as soon as it's created and it's + // waiting for events from the network. Send it events that inform that + // a notification stream was opened and that a notification was received. + // + // Since each protocol has its own notification stream, events need not be filtered. let sender_id = id; - let send_message = tester.filter_network_events(move |event| match event { - Event::EventStream(sender) => { - let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { - remote: sender_id, - protocol: grandpa_protocol_name::NAME.into(), + + let send_message = async move { + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationStreamOpened { + peer: sender_id, + direction: Direction::Inbound, negotiated_fallback: None, - role: ObservedRole::Full, - received_handshake: vec![], - }); - let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { - remote: sender_id, - messages: vec![( - grandpa_protocol_name::NAME.into(), - commit_to_send.clone().into(), - )], - }); + handshake: Roles::FULL.encode(), + }, + ); + let _ = tester.notification_tx.unbounded_send( + NotificationEvent::NotificationReceived { + peer: sender_id, + notification: commit_to_send.clone(), + }, + ); - true - }, - _ => false, - }); + tester + } + .boxed(); // when the commit comes in, we'll tell the callback it was bad. let handle_commit = commits_in.into_future().map(|(item, _)| match item.unwrap() { diff --git a/substrate/client/consensus/grandpa/src/finality_proof.rs b/substrate/client/consensus/grandpa/src/finality_proof.rs index 8a8a688583e34740b4bf49ca500ff15ee7239b1c..80b6249ade86ca9d44e9ed1284c0e6189731c86c 100644 --- a/substrate/client/consensus/grandpa/src/finality_proof.rs +++ b/substrate/client/consensus/grandpa/src/finality_proof.rs @@ -261,7 +261,7 @@ mod tests { use super::*; use crate::{authorities::AuthoritySetChanges, BlockNumberOps, ClientError, SetId}; use futures::executor::block_on; - use sc_block_builder::BlockBuilderProvider; + use sc_block_builder::BlockBuilderBuilder; use sc_client_api::{apply_aux, LockImportRun}; use sp_consensus::BlockOrigin; use sp_consensus_grandpa::GRANDPA_ENGINE_ID as ID; @@ -323,7 +323,14 @@ mod tests { let mut blocks = Vec::new(); for _ in 0..number_of_blocks { - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap() + .build() + .unwrap() + .block; block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); blocks.push(block); } diff --git a/substrate/client/consensus/grandpa/src/import.rs b/substrate/client/consensus/grandpa/src/import.rs index ca5b7c400bfb2f83be8c1b22459ee0730072ada0..bc2983569c533bcefbac2d98cad908e5ee6bcd01 100644 --- a/substrate/client/consensus/grandpa/src/import.rs +++ b/substrate/client/consensus/grandpa/src/import.rs @@ -32,7 +32,6 @@ use sp_api::{Core, RuntimeApiInfo}; use sp_blockchain::BlockStatus; use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; use sp_consensus_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID}; -use sp_core::hashing::twox_128; use sp_runtime::{ generic::OpaqueDigestItemId, traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, @@ -438,7 +437,11 @@ where // The new API is not supported in this runtime. Try reading directly from storage. // This code may be removed once warp sync to an old runtime is no longer needed. for prefix in ["GrandpaFinality", "Grandpa"] { - let k = [twox_128(prefix.as_bytes()), twox_128(b"CurrentSetId")].concat(); + let k = [ + sp_crypto_hashing::twox_128(prefix.as_bytes()), + sp_crypto_hashing::twox_128(b"CurrentSetId"), + ] + .concat(); if let Ok(Some(id)) = self.inner.storage(hash, &sc_client_api::StorageKey(k.to_vec())) { diff --git a/substrate/client/consensus/grandpa/src/lib.rs b/substrate/client/consensus/grandpa/src/lib.rs index da621abd254caf90abf5dfab0511d5e868218168..b7cfc9f5b6019a5cea789dfcf385d7efc4e196e7 100644 --- a/substrate/client/consensus/grandpa/src/lib.rs +++ b/substrate/client/consensus/grandpa/src/lib.rs @@ -67,7 +67,7 @@ use sc_client_api::{ BlockchainEvents, CallExecutor, ExecutorProvider, Finalizer, LockImportRun, StorageProvider, }; use sc_consensus::BlockImport; -use sc_network::types::ProtocolName; +use sc_network::{types::ProtocolName, NotificationService}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; @@ -471,9 +471,6 @@ where Client: ExecutorProvider + HeaderBackend, { fn get(&self) -> Result { - // This implementation uses the Grandpa runtime API instead of reading directly from the - // `GRANDPA_AUTHORITIES_KEY` as the data may have been migrated since the genesis block of - // the chain, whereas the runtime API is backwards compatible. self.executor() .call( self.expect_block_hash_from_id(&BlockId::Number(Zero::zero()))?, @@ -690,6 +687,8 @@ pub struct GrandpaParams { pub network: N, /// Event stream for syncing-related events. pub sync: S, + /// Handle for interacting with `Notifications`. + pub notification_service: Box, /// A voting rule used to potentially restrict target votes. pub voting_rule: VR, /// The prometheus metrics registry. @@ -710,21 +709,21 @@ pub struct GrandpaParams { /// For standard protocol name see [`crate::protocol_standard_name`]. pub fn grandpa_peers_set_config( protocol_name: ProtocolName, -) -> sc_network::config::NonDefaultSetConfig { +) -> (sc_network::config::NonDefaultSetConfig, Box) { use communication::grandpa_protocol_name; - sc_network::config::NonDefaultSetConfig { - notifications_protocol: protocol_name, - fallback_names: grandpa_protocol_name::LEGACY_NAMES.iter().map(|&n| n.into()).collect(), + sc_network::config::NonDefaultSetConfig::new( + protocol_name, + grandpa_protocol_name::LEGACY_NAMES.iter().map(|&n| n.into()).collect(), // Notifications reach ~256kiB in size at the time of writing on Kusama and Polkadot. - max_notification_size: 1024 * 1024, - handshake: None, - set_config: sc_network::config::SetConfig { + 1024 * 1024, + None, + sc_network::config::SetConfig { in_peers: 0, out_peers: 0, reserved_nodes: Vec::new(), non_reserved_mode: sc_network::config::NonReservedPeerMode::Deny, }, - } + ) } /// Run a GRANDPA voter as a task. Provide configuration and a link to a @@ -747,6 +746,7 @@ where link, network, sync, + notification_service, voting_rule, prometheus_registry, shared_voter_state, @@ -773,6 +773,7 @@ where let network = NetworkBridge::new( network, sync, + notification_service, config.clone(), persistent_data.set_state.clone(), prometheus_registry.as_ref(), diff --git a/substrate/client/consensus/grandpa/src/observer.rs b/substrate/client/consensus/grandpa/src/observer.rs index 8541baa822bb44aeac0613f6870884f904cbca2d..608ff5e46a0e825a6b7480a29258574f121e4aa2 100644 --- a/substrate/client/consensus/grandpa/src/observer.rs +++ b/substrate/client/consensus/grandpa/src/observer.rs @@ -28,6 +28,7 @@ use futures::prelude::*; use log::{debug, info, warn}; use sc_client_api::backend::Backend; +use sc_network::NotificationService; use sc_telemetry::TelemetryHandle; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; @@ -168,6 +169,7 @@ pub fn run_grandpa_observer( link: LinkHalf, network: N, sync: S, + notification_service: Box, ) -> sp_blockchain::Result + Send> where BE: Backend + Unpin + 'static, @@ -189,6 +191,7 @@ where let network = NetworkBridge::new( network, sync, + notification_service, config.clone(), persistent_data.set_state.clone(), None, @@ -414,14 +417,14 @@ mod tests { use futures::executor; - /// Ensure `Future` implementation of `ObserverWork` is polling its `NetworkBridge`. Regression - /// test for bug introduced in d4fbb897c and fixed in b7af8b339. + /// Ensure `Future` implementation of `ObserverWork` is polling its `NetworkBridge`. + /// Regression test for bug introduced in d4fbb897c and fixed in b7af8b339. /// - /// When polled, `NetworkBridge` forwards reputation change requests from the `GossipValidator` - /// to the underlying `dyn Network`. This test triggers a reputation change by calling - /// `GossipValidator::validate` with an invalid gossip message. After polling the `ObserverWork` - /// which should poll the `NetworkBridge`, the reputation change should be forwarded to the test - /// network. + /// When polled, `NetworkBridge` forwards reputation change requests from the + /// `GossipValidator` to the underlying `dyn Network`. This test triggers a reputation change + /// by calling `GossipValidator::validate` with an invalid gossip message. After polling the + /// `ObserverWork` which should poll the `NetworkBridge`, the reputation change should be + /// forwarded to the test network. #[test] fn observer_work_polls_underlying_network_bridge() { // Create a test network. @@ -463,12 +466,6 @@ mod tests { // validator to the test network. assert!(observer.now_or_never().is_none()); - // Ignore initial event stream request by gossip engine. - match tester.events.next().now_or_never() { - Some(Some(Event::EventStream(_))) => {}, - _ => panic!("expected event stream request"), - }; - assert_matches!(tester.events.next().now_or_never(), Some(Some(Event::Report(_, _)))); }); } diff --git a/substrate/client/consensus/grandpa/src/tests.rs b/substrate/client/consensus/grandpa/src/tests.rs index 0175f7d1b473cb8be88da088df4792099f015a31..7e42c2d45c733b8cbc32a599680202399fadaed6 100644 --- a/substrate/client/consensus/grandpa/src/tests.rs +++ b/substrate/client/consensus/grandpa/src/tests.rs @@ -54,7 +54,7 @@ use tokio::runtime::Handle; use authorities::AuthoritySet; use communication::grandpa_protocol_name; -use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; +use sc_block_builder::{BlockBuilder, BlockBuilderBuilder}; use sc_consensus::LongestChain; use sp_application_crypto::key_types::GRANDPA; @@ -317,6 +317,9 @@ fn initialize_grandpa( (net.peers[peer_id].network_service().clone(), link) }; let sync = net.peers[peer_id].sync_service().clone(); + let notification_service = net.peers[peer_id] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(); let grandpa_params = GrandpaParams { config: Config { @@ -332,6 +335,7 @@ fn initialize_grandpa( link, network: net_service, sync, + notification_service, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -472,6 +476,9 @@ async fn finalize_3_voters_1_full_observer() { let net_service = net.peers[peer_id].network_service().clone(); let sync = net.peers[peer_id].sync_service().clone(); let link = net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); + let notification_service = net.peers[peer_id] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(); let grandpa_params = GrandpaParams { config: Config { @@ -487,6 +494,7 @@ async fn finalize_3_voters_1_full_observer() { link, network: net_service, sync, + notification_service, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -557,14 +565,17 @@ async fn transition_3_voters_twice_1_full_observer() { for (peer_id, local_key) in all_peers.clone().into_iter().enumerate() { let keystore = create_keystore(local_key); - let (net_service, link, sync) = { - let net = net.lock(); + let (net_service, link, sync, notification_service) = { + let mut net = net.lock(); let link = net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); ( net.peers[peer_id].network_service().clone(), link, net.peers[peer_id].sync_service().clone(), + net.peers[peer_id] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(), ) }; @@ -582,6 +593,7 @@ async fn transition_3_voters_twice_1_full_observer() { link, network: net_service, sync, + notification_service, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -897,8 +909,11 @@ async fn allows_reimporting_change_blocks() { let (mut block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); - let mut builder = full_client - .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) + let mut builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(full_client.chain_info().best_hash) + .fetch_parent_block_number(&*full_client) + .unwrap() + .build() .unwrap(); add_scheduled_change( @@ -942,8 +957,11 @@ async fn test_bad_justification() { let (mut block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); - let mut builder = full_client - .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) + let mut builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(full_client.chain_info().best_hash) + .fetch_parent_block_number(&*full_client) + .unwrap() + .build() .unwrap(); add_scheduled_change( @@ -1019,6 +1037,9 @@ async fn voter_persists_its_votes() { communication::NetworkBridge::new( net.peers[1].network_service().clone(), net.peers[1].sync_service().clone(), + net.peers[1] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(), config.clone(), set_state, None, @@ -1037,6 +1058,9 @@ async fn voter_persists_its_votes() { (net.peers[0].network_service().clone(), link) }; let sync = net.peers[0].sync_service().clone(); + let notification_service = net.peers[0] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(); let grandpa_params = GrandpaParams { config: Config { @@ -1052,6 +1076,7 @@ async fn voter_persists_its_votes() { link, network: net_service, sync, + notification_service, voting_rule: VotingRulesBuilder::default().build(), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -1076,6 +1101,9 @@ async fn voter_persists_its_votes() { net.add_authority_peer(); let net_service = net.peers[2].network_service().clone(); let sync = net.peers[2].sync_service().clone(); + let notification_service = net.peers[2] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(); // but we'll reuse the client from the first peer (alice_voter1) // since we want to share the same database, so that we can // read the persisted state after aborting alice_voter1. @@ -1098,6 +1126,7 @@ async fn voter_persists_its_votes() { link, network: net_service, sync, + notification_service, voting_rule: VotingRulesBuilder::default().build(), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -1249,6 +1278,9 @@ async fn finalize_3_voters_1_light_observer() { let mut net = GrandpaTestNet::new(TestApi::new(voters), 3, 1); let voters = initialize_grandpa(&mut net, authorities); + let notification_service = net.peers[3] + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(); let observer = observer::run_grandpa_observer( Config { gossip_duration: TEST_GOSSIP_DURATION, @@ -1263,6 +1295,7 @@ async fn finalize_3_voters_1_light_observer() { net.peers[3].data.lock().take().expect("link initialized at startup; qed"), net.peers[3].network_service().clone(), net.peers[3].sync_service().clone(), + notification_service, ) .unwrap(); net.peer(0).push_blocks(20, false); @@ -1311,6 +1344,10 @@ async fn voter_catches_up_to_latest_round_when_behind() { link, network: net.peer(peer_id).network_service().clone(), sync: net.peer(peer_id).sync_service().clone(), + notification_service: net + .peer(peer_id) + .take_notification_service(&grandpa_protocol_name::NAME.into()) + .unwrap(), voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), @@ -1403,6 +1440,7 @@ fn test_environment_with_select_chain( keystore: Option, network_service: N, sync_service: S, + notification_service: Box, select_chain: SC, voting_rule: VR, ) -> TestEnvironment @@ -1427,6 +1465,7 @@ where let network = NetworkBridge::new( network_service.clone(), sync_service, + notification_service, config.clone(), set_state.clone(), None, @@ -1456,6 +1495,7 @@ fn test_environment( keystore: Option, network_service: N, sync_service: S, + notification_service: Box, voting_rule: VR, ) -> TestEnvironment, VR> where @@ -1468,6 +1508,7 @@ where keystore, network_service, sync_service, + notification_service, link.select_chain.clone(), voting_rule, ) @@ -1484,14 +1525,22 @@ async fn grandpa_environment_respects_voting_rules() { let peer = net.peer(0); let network_service = peer.network_service().clone(); let sync_service = peer.sync_service().clone(); + let mut notification_service = + peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap(); let link = peer.data.lock().take().unwrap(); // add 21 blocks let hashes = peer.push_blocks(21, false); // create an environment with no voting rule restrictions - let unrestricted_env = - test_environment(&link, None, network_service.clone(), sync_service.clone(), ()); + let unrestricted_env = test_environment( + &link, + None, + network_service.clone(), + sync_service.clone(), + notification_service.clone().unwrap(), + (), + ); // another with 3/4 unfinalized chain voting rule restriction let three_quarters_env = test_environment( @@ -1499,6 +1548,7 @@ async fn grandpa_environment_respects_voting_rules() { None, network_service.clone(), sync_service.clone(), + notification_service.clone().unwrap(), voting_rule::ThreeQuartersOfTheUnfinalizedChain, ); @@ -1509,6 +1559,7 @@ async fn grandpa_environment_respects_voting_rules() { None, network_service.clone(), sync_service, + notification_service, VotingRulesBuilder::default().build(), ); @@ -1602,6 +1653,8 @@ async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { let peer = net.peer(0); let network_service = peer.network_service().clone(); let sync_service = peer.sync_service().clone(); + let notification_service = + peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap(); let link = peer.data.lock().take().unwrap(); let client = peer.client().as_client().clone(); let select_chain = MockSelectChain::default(); @@ -1616,6 +1669,7 @@ async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { None, network_service.clone(), sync_service, + notification_service, select_chain.clone(), voting_rule::BeforeBestBlockBy(5), ); @@ -1663,6 +1717,8 @@ async fn grandpa_environment_checks_if_best_block_is_descendent_of_finality_targ let peer = net.peer(0); let network_service = peer.network_service().clone(); let sync_service = peer.sync_service().clone(); + let notification_service = + peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap(); let link = peer.data.lock().take().unwrap(); let client = peer.client().as_client().clone(); let select_chain = MockSelectChain::default(); @@ -1672,6 +1728,7 @@ async fn grandpa_environment_checks_if_best_block_is_descendent_of_finality_targ None, network_service.clone(), sync_service.clone(), + notification_service, select_chain.clone(), voting_rule.clone(), ); @@ -1774,11 +1831,19 @@ async fn grandpa_environment_never_overwrites_round_voter_state() { let peer = net.peer(0); let network_service = peer.network_service().clone(); let sync_service = peer.sync_service().clone(); + let notification_service = + peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap(); let link = peer.data.lock().take().unwrap(); let keystore = create_keystore(peers[0]); - let environment = - test_environment(&link, Some(keystore), network_service.clone(), sync_service, ()); + let environment = test_environment( + &link, + Some(keystore), + network_service.clone(), + sync_service, + notification_service, + (), + ); let round_state = || finality_grandpa::round::State::genesis(Default::default()); let base = || Default::default(); @@ -1913,7 +1978,12 @@ async fn imports_justification_for_regular_blocks_on_import() { // create a new block (without importing it) let generate_block = |parent| { - let builder = full_client.new_block_at(parent, Default::default(), false).unwrap(); + let builder = BlockBuilderBuilder::new(&*full_client) + .on_parent_block(parent) + .fetch_parent_block_number(&*full_client) + .unwrap() + .build() + .unwrap(); builder.build().unwrap().block }; @@ -2001,9 +2071,18 @@ async fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { let peer = net.peer(0); let network_service = peer.network_service().clone(); let sync_service = peer.sync_service().clone(); + let notification_service = + peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap(); let link = peer.data.lock().take().unwrap(); let keystore = create_keystore(alice); - test_environment(&link, Some(keystore), network_service.clone(), sync_service, ()) + test_environment( + &link, + Some(keystore), + network_service.clone(), + sync_service, + notification_service, + (), + ) }; let signed_prevote = { @@ -2042,8 +2121,7 @@ async fn revert_prunes_authority_changes() { let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; - type TestBlockBuilder<'a> = - BlockBuilder<'a, Block, PeersFullClient, substrate_test_runtime_client::Backend>; + type TestBlockBuilder<'a> = BlockBuilder<'a, Block, PeersFullClient>; let edit_block = |mut builder: TestBlockBuilder| { add_scheduled_change( &mut builder, diff --git a/substrate/client/consensus/grandpa/src/voting_rule.rs b/substrate/client/consensus/grandpa/src/voting_rule.rs index 27a91d54783705e1ff2beeac72bfa1621062a04e..e09780739c7353418a81488a25060eba0231ebfc 100644 --- a/substrate/client/consensus/grandpa/src/voting_rule.rs +++ b/substrate/client/consensus/grandpa/src/voting_rule.rs @@ -330,7 +330,7 @@ where #[cfg(test)] mod tests { use super::*; - use sc_block_builder::BlockBuilderProvider; + use sc_block_builder::BlockBuilderBuilder; use sp_consensus::BlockOrigin; use sp_runtime::traits::Header as _; @@ -371,7 +371,14 @@ mod tests { let mut hashes = Vec::with_capacity(200); for _ in 0..200 { - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap() + .build() + .unwrap() + .block; hashes.push(block.hash()); futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); @@ -414,7 +421,14 @@ mod tests { let n = 5; let mut hashes = Vec::with_capacity(n); for _ in 0..n { - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap() + .build() + .unwrap() + .block; hashes.push(block.hash()); futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); diff --git a/substrate/client/consensus/grandpa/src/warp_proof.rs b/substrate/client/consensus/grandpa/src/warp_proof.rs index ea8114eafd789ec00a909b5535f0659ceea1fbfe..29111712ec382ec6758fddcfa670d4edd2a9a7eb 100644 --- a/substrate/client/consensus/grandpa/src/warp_proof.rs +++ b/substrate/client/consensus/grandpa/src/warp_proof.rs @@ -23,7 +23,7 @@ use crate::{ BlockNumberOps, GrandpaJustification, SharedAuthoritySet, }; use sc_client_api::Backend as ClientBackend; -use sc_network_common::sync::warp::{EncodedProof, VerificationResult, WarpSyncProvider}; +use sc_network_sync::strategy::warp::{EncodedProof, VerificationResult, WarpSyncProvider}; use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_consensus_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ @@ -322,7 +322,7 @@ mod tests { use crate::{AuthoritySetChanges, GrandpaJustification}; use parity_scale_codec::Encode; use rand::prelude::*; - use sc_block_builder::BlockBuilderProvider; + use sc_block_builder::BlockBuilderBuilder; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_consensus_grandpa::GRANDPA_ENGINE_ID; @@ -348,7 +348,11 @@ mod tests { let mut authority_set_changes = Vec::new(); for n in 1..=100 { - let mut builder = client.new_block(Default::default()).unwrap(); + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().best_hash) + .with_parent_block_number(client.chain_info().best_number) + .build() + .unwrap(); let mut new_authorities = None; // we will trigger an authority set change every 10 blocks diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index a6430fdf1deece639a443e78ee8c08d627f8e6c3..ac32fed72289fd6a296de2720a75fb36c65ad1f8 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-manual-seal" -version = "0.10.0-dev" +version = "0.35.0" authors.workspace = true description = "Manual sealing engine for Substrate" edition.workspace = true @@ -9,19 +9,22 @@ 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", "server", "macros"] } +jsonrpsee = { version = "0.22", 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" -log = "0.4.17" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0" +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } @@ -43,7 +46,7 @@ sp-runtime = { path = "../../../primitives/runtime" } sp-timestamp = { path = "../../../primitives/timestamp" } [dev-dependencies] -tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } sc-basic-authorship = { path = "../../basic-authorship" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "../../../test-utils/runtime/transaction-pool" } diff --git a/substrate/client/consensus/manual-seal/src/error.rs b/substrate/client/consensus/manual-seal/src/error.rs index eeae1d153e81bcdd0ebf25bb117749c52ef128fc..d7bb00eff6b73f8cd7c0c98bf62fcada613f14b1 100644 --- a/substrate/client/consensus/manual-seal/src/error.rs +++ b/substrate/client/consensus/manual-seal/src/error.rs @@ -20,10 +20,7 @@ //! This is suitable for a testing environment. use futures::channel::{mpsc::SendError, oneshot}; -use jsonrpsee::{ - core::Error as JsonRpseeError, - types::error::{CallError, ErrorObject}, -}; +use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned}; use sc_consensus::ImportResult; use sp_blockchain::Error as BlockchainError; use sp_consensus::Error as ConsensusError; @@ -106,8 +103,8 @@ impl Error { } } -impl From for JsonRpseeError { +impl From for ErrorObjectOwned { fn from(err: Error) -> Self { - CallError::Custom(ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)).into() + ErrorObject::owned(err.to_code(), err.to_string(), None::<()>) } } diff --git a/substrate/client/consensus/manual-seal/src/lib.rs b/substrate/client/consensus/manual-seal/src/lib.rs index 41cd5f3127e8ead7508bb2a7c375aab9bc355892..c3d360f071974ab38c6f1d112c23d6705f937bd3 100644 --- a/substrate/client/consensus/manual-seal/src/lib.rs +++ b/substrate/client/consensus/manual-seal/src/lib.rs @@ -236,7 +236,7 @@ pub async fn run_instant_seal( // instant-seal creates blocks as soon as transactions are imported // into the transaction pool. let commands_stream = pool.import_notification_stream().map(|_| EngineCommand::SealNewBlock { - create_empty: false, + create_empty: true, finalize: false, parent_hash: None, sender: None, @@ -429,7 +429,9 @@ mod tests { sender, } }); - let future = run_manual_seal(ManualSealParams { + + // spawn the background authorship task + tokio::spawn(run_manual_seal(ManualSealParams { block_import: client.clone(), env, client: client.clone(), @@ -438,12 +440,8 @@ mod tests { select_chain, create_inherent_data_providers: |_, _| async { Ok(()) }, consensus_data_provider: None, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - // spawn the background authorship task - rt.block_on(future); - }); + })); + // submit a transaction to pool. let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported @@ -469,7 +467,10 @@ mod tests { assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1) } - #[tokio::test] + // TODO: enable once the flakiness is fixed + // See https://github.com/paritytech/polkadot-sdk/issues/3603 + //#[tokio::test] + #[allow(unused)] async fn instant_seal_delayed_finalize() { let builder = TestClientBuilder::new(); let (client, select_chain) = builder.build_with_longest_chain(); @@ -507,7 +508,8 @@ mod tests { } }); - let future_instant_seal = run_manual_seal(ManualSealParams { + // spawn the background authorship task + tokio::spawn(run_manual_seal(ManualSealParams { block_import: client.clone(), commands_stream, env, @@ -516,24 +518,16 @@ mod tests { select_chain, create_inherent_data_providers: |_, _| async { Ok(()) }, consensus_data_provider: None, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - // spawn the background authorship task - rt.block_on(future_instant_seal); - }); + })); let delay_sec = 5; - let future_delayed_finalize = run_delayed_finalize(DelayedFinalizeParams { + + // spawn the background finality task + tokio::spawn(run_delayed_finalize(DelayedFinalizeParams { client: client.clone(), delay_sec, spawn_handle: spawner, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - // spawn the background authorship task - rt.block_on(future_delayed_finalize); - }); + })); let mut finality_stream = client.finality_notification_stream(); // submit a transaction to pool. @@ -589,7 +583,9 @@ mod tests { // this test checks that blocks are created as soon as an engine command is sent over the // stream. let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); - let future = run_manual_seal(ManualSealParams { + + // spawn the background authorship task + tokio::spawn(run_manual_seal(ManualSealParams { block_import: client.clone(), env, client: client.clone(), @@ -598,12 +594,8 @@ mod tests { select_chain, consensus_data_provider: None, create_inherent_data_providers: |_, _| async { Ok(()) }, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - // spawn the background authorship task - rt.block_on(future); - }); + })); + // submit a transaction to pool. let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported @@ -675,7 +667,9 @@ mod tests { // this test checks that blocks are created as soon as an engine command is sent over the // stream. let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); - let future = run_manual_seal(ManualSealParams { + + // spawn the background authorship task + tokio::spawn(run_manual_seal(ManualSealParams { block_import: client.clone(), env, client: client.clone(), @@ -684,12 +678,8 @@ mod tests { select_chain, consensus_data_provider: None, create_inherent_data_providers: |_, _| async { Ok(()) }, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - // spawn the background authorship task - rt.block_on(future); - }); + })); + // submit a transaction to pool. let result = pool.submit_one(genesis_hash, SOURCE, uxt(Alice, 0)).await; // assert that it was successfully imported @@ -781,7 +771,9 @@ mod tests { let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); - let future = run_manual_seal(ManualSealParams { + + // spawn the background authorship task + tokio::spawn(run_manual_seal(ManualSealParams { block_import: client.clone(), env, client: client.clone(), @@ -791,11 +783,8 @@ mod tests { // use a provider that pushes some post digest data consensus_data_provider: Some(Box::new(TestDigestProvider { _client: client.clone() })), create_inherent_data_providers: |_, _| async { Ok(()) }, - }); - std::thread::spawn(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(future); - }); + })); + let (tx, rx) = futures::channel::oneshot::channel(); sink.send(EngineCommand::SealNewBlock { parent_hash: None, diff --git a/substrate/client/consensus/manual-seal/src/rpc.rs b/substrate/client/consensus/manual-seal/src/rpc.rs index c0b3af69bedf40884598c19eea16defbdf1ae679..6018c3ab092a1de0c9a9ff847ac7c437511fa540 100644 --- a/substrate/client/consensus/manual-seal/src/rpc.rs +++ b/substrate/client/consensus/manual-seal/src/rpc.rs @@ -23,10 +23,7 @@ use futures::{ channel::{mpsc, oneshot}, SinkExt, }; -use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, - proc_macros::rpc, -}; +use jsonrpsee::{core::async_trait, proc_macros::rpc}; use sc_consensus::ImportedAux; use serde::{Deserialize, Serialize}; use sp_runtime::EncodedJustification; @@ -74,7 +71,7 @@ pub trait ManualSealApi { create_empty: bool, finalize: bool, parent_hash: Option, - ) -> RpcResult>; + ) -> Result, Error>; /// Instructs the manual-seal authorship task to finalize a block #[method(name = "engine_finalizeBlock")] @@ -82,7 +79,7 @@ pub trait ManualSealApi { &self, hash: Hash, justification: Option, - ) -> RpcResult; + ) -> Result; } /// A struct that implements the [`ManualSealApiServer`]. @@ -91,7 +88,7 @@ pub struct ManualSeal { } /// return type of `engine_createBlock` -#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct CreatedBlock { /// hash of the created block. pub hash: Hash, @@ -115,7 +112,7 @@ impl ManualSealApiServer for ManualSeal { create_empty: bool, finalize: bool, parent_hash: Option, - ) -> RpcResult> { + ) -> Result, Error> { let mut sink = self.import_block_channel.clone(); let (sender, receiver) = oneshot::channel(); // NOTE: this sends a Result over the channel. @@ -131,7 +128,7 @@ impl ManualSealApiServer for ManualSeal { match receiver.await { Ok(Ok(rx)) => Ok(rx), Ok(Err(e)) => Err(e.into()), - Err(e) => Err(JsonRpseeError::to_call_error(e)), + Err(e) => Err(e.into()), } } @@ -139,12 +136,12 @@ impl ManualSealApiServer for ManualSeal { &self, hash: Hash, justification: Option, - ) -> RpcResult { + ) -> Result { let mut sink = self.import_block_channel.clone(); let (sender, receiver) = oneshot::channel(); let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }; sink.send(command).await?; - receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e)) + receiver.await.map(|_| true).map_err(Into::into) } } diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index ef32425685b6f127fc67d01ec0acfc5598501897..0791514035b43c83492175bcac67e2a6eb6aa9c6 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-pow" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true description = "PoW consensus algorithm for substrate" edition.workspace = true @@ -9,17 +9,20 @@ 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" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } diff --git a/substrate/client/consensus/slots/Cargo.toml b/substrate/client/consensus/slots/Cargo.toml index 52c528c3028a80edbba90e13a48d22a24fd45d38..75f8b29a2fd755c18d68b817499e2dfb75ea232c 100644 --- a/substrate/client/consensus/slots/Cargo.toml +++ b/substrate/client/consensus/slots/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-consensus-slots" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true description = "Generic slots-based utilities for consensus" edition.workspace = true @@ -10,15 +10,18 @@ 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" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } sc-telemetry = { path = "../../telemetry" } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index cb9560b6cb62fbd9cab7d335eee1a0a352d69ef2..57ee1a8ad3315036f44e5b560fb55fa3f31c97e5 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-client-db" -version = "0.10.0-dev" +version = "0.35.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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"] @@ -21,8 +24,8 @@ kvdb = "0.13.0" kvdb-memorydb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } linked-hash-map = "0.5.4" -log = "0.4.17" -parity-db = "0.4.8" +log = { workspace = true, default-features = true } +parity-db = "0.4.12" parking_lot = "0.12.1" sc-client-api = { path = "../api" } sc-state-db = { path = "../state-db" } @@ -53,7 +56,7 @@ runtime-benchmarks = [ "kitchensink-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] -rocksdb = [ "kvdb-rocksdb" ] +rocksdb = ["kvdb-rocksdb"] [[bench]] name = "state_access" diff --git a/substrate/client/db/src/bench.rs b/substrate/client/db/src/bench.rs index 03ad4817b53bcea2cb349d4d05c190bb239530fa..32503cf63c0ad7eeae1ab502d38b09aa210d9bc9 100644 --- a/substrate/client/db/src/bench.rs +++ b/substrate/client/db/src/bench.rs @@ -19,7 +19,7 @@ //! State backend that's useful for benchmarking use crate::{DbState, DbStateBuilder}; -use hash_db::{Hasher, Prefix}; +use hash_db::{Hasher as DbHasher, Prefix}; use kvdb::{DBTransaction, KeyValueDB}; use linked_hash_map::LinkedHashMap; use parking_lot::Mutex; @@ -27,10 +27,7 @@ use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, TrackedStorageKey}, }; -use sp_runtime::{ - traits::{Block as BlockT, HashingFor}, - StateVersion, Storage, -}; +use sp_runtime::{traits::Hash, StateVersion, Storage}; use sp_state_machine::{ backend::Backend as StateBackend, BackendTransaction, ChildStorageCollection, DBValue, IterArgs, StorageCollection, StorageIterator, StorageKey, StorageValue, @@ -45,16 +42,16 @@ use std::{ sync::Arc, }; -type State = DbState; +type State = DbState; -struct StorageDb { +struct StorageDb { db: Arc, - _block: std::marker::PhantomData, + _phantom: std::marker::PhantomData, } -impl sp_state_machine::Storage> for StorageDb { - fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { - let prefixed_key = prefixed_key::>(key, prefix); +impl sp_state_machine::Storage for StorageDb { + fn get(&self, key: &Hasher::Output, prefix: Prefix) -> Result, String> { + let prefixed_key = prefixed_key::(key, prefix); self.db .get(0, &prefixed_key) .map_err(|e| format!("Database backend error: {:?}", e)) @@ -75,29 +72,29 @@ struct KeyTracker { } /// State that manages the backend database reference. Allows runtime to control the database. -pub struct BenchmarkingState { - root: Cell, - genesis_root: B::Hash, - state: RefCell>>, +pub struct BenchmarkingState { + root: Cell, + genesis_root: Hasher::Output, + state: RefCell>>, db: Cell>>, genesis: HashMap, (Vec, i32)>, record: Cell>>, key_tracker: Arc>, whitelist: RefCell>, - proof_recorder: Option>>, - proof_recorder_root: Cell, - shared_trie_cache: SharedTrieCache>, + proof_recorder: Option>, + proof_recorder_root: Cell, + shared_trie_cache: SharedTrieCache, } /// A raw iterator over the `BenchmarkingState`. -pub struct RawIter { - inner: as StateBackend>>::RawIter, +pub struct RawIter { + inner: as StateBackend>::RawIter, child_trie: Option>, key_tracker: Arc>, } -impl StorageIterator> for RawIter { - type Backend = BenchmarkingState; +impl StorageIterator for RawIter { + type Backend = BenchmarkingState; type Error = String; fn next_key(&mut self, backend: &Self::Backend) -> Option> { @@ -128,7 +125,7 @@ impl StorageIterator> for RawIter { } } -impl BenchmarkingState { +impl BenchmarkingState { /// Create a new instance that creates a database in a temporary dir. pub fn new( genesis: Storage, @@ -137,9 +134,9 @@ impl BenchmarkingState { enable_tracking: bool, ) -> Result { let state_version = sp_runtime::StateVersion::default(); - let mut root = B::Hash::default(); - let mut mdb = MemoryDB::>::default(); - sp_trie::trie_types::TrieDBMutBuilderV1::>::new(&mut mdb, &mut root).build(); + let mut root = Default::default(); + let mut mdb = MemoryDB::::default(); + sp_trie::trie_types::TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); let mut state = BenchmarkingState { state: RefCell::new(None), @@ -169,7 +166,7 @@ impl BenchmarkingState { child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), ) }); - let (root, transaction): (B::Hash, _) = + let (root, transaction): (Hasher::Output, _) = state.state.borrow().as_ref().unwrap().full_storage_root( genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, @@ -193,9 +190,9 @@ impl BenchmarkingState { recorder.reset(); self.proof_recorder_root.set(self.root.get()); } - let storage_db = Arc::new(StorageDb:: { db, _block: Default::default() }); + let storage_db = Arc::new(StorageDb:: { db, _phantom: Default::default() }); *self.state.borrow_mut() = Some( - DbStateBuilder::::new(storage_db, self.root.get()) + DbStateBuilder::::new(storage_db, self.root.get()) .with_optional_recorder(self.proof_recorder.clone()) .with_cache(self.shared_trie_cache.local_cache()) .build(), @@ -341,17 +338,17 @@ fn state_err() -> String { "State is not open".into() } -impl StateBackend> for BenchmarkingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; - type RawIter = RawIter; +impl StateBackend for BenchmarkingState { + type Error = as StateBackend>::Error; + type TrieBackendStorage = as StateBackend>::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage(key) } - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage_hash(key) } @@ -373,7 +370,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.add_read_key(Some(child_info.storage_key()), key); self.state .borrow() @@ -385,7 +382,7 @@ impl StateBackend> for BenchmarkingState { fn closest_merkle_value( &self, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.closest_merkle_value(key) } @@ -394,7 +391,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state .borrow() @@ -443,7 +440,7 @@ impl StateBackend> for BenchmarkingState { &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, BackendTransaction>) { + ) -> (Hasher::Output, BackendTransaction) { self.state .borrow() .as_ref() @@ -455,7 +452,7 @@ impl StateBackend> for BenchmarkingState { child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, BackendTransaction>) { + ) -> (Hasher::Output, bool, BackendTransaction) { self.state .borrow() .as_ref() @@ -479,8 +476,8 @@ impl StateBackend> for BenchmarkingState { fn commit( &self, - storage_root: as Hasher>::Out, - mut transaction: BackendTransaction>, + storage_root: ::Out, + mut transaction: BackendTransaction, main_storage_changes: StorageCollection, child_storage_changes: ChildStorageCollection, ) -> Result<(), Self::Error> { @@ -634,8 +631,7 @@ impl StateBackend> for BenchmarkingState { log::debug!(target: "benchmark", "Some proof size: {}", &proof_size); proof_size } else { - if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) - { + if let Some(size) = proof.encoded_compact_size::(proof_recorder_root) { size as u32 } else if proof_recorder_root == self.root.get() { log::debug!(target: "benchmark", "No changes - no proof"); @@ -654,7 +650,7 @@ impl StateBackend> for BenchmarkingState { } } -impl std::fmt::Debug for BenchmarkingState { +impl std::fmt::Debug for BenchmarkingState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Bench DB") } @@ -663,6 +659,7 @@ impl std::fmt::Debug for BenchmarkingState { #[cfg(test)] mod test { use crate::bench::BenchmarkingState; + use sp_runtime::traits::HashingFor; use sp_state_machine::backend::Backend as _; fn hex(hex: &str) -> Vec { @@ -681,7 +678,8 @@ mod test { ..sp_runtime::Storage::default() }; let bench_state = - BenchmarkingState::::new(storage, None, false, true).unwrap(); + BenchmarkingState::>::new(storage, None, false, true) + .unwrap(); assert_eq!(bench_state.read_write_count(), (0, 0, 0, 0)); assert_eq!(bench_state.keys(Default::default()).unwrap().count(), 1); @@ -690,9 +688,13 @@ mod test { #[test] fn read_to_main_and_child_tries() { - let bench_state = - BenchmarkingState::::new(Default::default(), None, false, true) - .unwrap(); + let bench_state = BenchmarkingState::>::new( + Default::default(), + None, + false, + true, + ) + .unwrap(); for _ in 0..2 { let child1 = sp_core::storage::ChildInfo::new_default(b"child1"); diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 194bec8a88eb46fd90537bf61a65c12dc02982c8..0faa90dfc4f925db8776c6914fcf864813b790a5 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -101,14 +101,11 @@ pub use bench::BenchmarkingState; const CACHE_HEADERS: usize = 8; /// DB-backed patricia trie state, transaction type is an overlay of changes to commit. -pub type DbState = - sp_state_machine::TrieBackend>>, HashingFor>; +pub type DbState = sp_state_machine::TrieBackend>, H>; /// Builder for [`DbState`]. -pub type DbStateBuilder = sp_state_machine::TrieBackendBuilder< - Arc>>, - HashingFor, ->; +pub type DbStateBuilder = + sp_state_machine::TrieBackendBuilder>, Hasher>; /// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; @@ -135,13 +132,17 @@ enum DbExtrinsic { /// It makes sure that the hash we are using stays pinned in storage /// until this structure is dropped. pub struct RefTrackingState { - state: DbState, + state: DbState>, storage: Arc>, parent_hash: Option, } impl RefTrackingState { - fn new(state: DbState, storage: Arc>, parent_hash: Option) -> Self { + fn new( + state: DbState>, + storage: Arc>, + parent_hash: Option, + ) -> Self { RefTrackingState { state, parent_hash, storage } } } @@ -162,12 +163,12 @@ impl std::fmt::Debug for RefTrackingState { /// A raw iterator over the `RefTrackingState`. pub struct RawIter { - inner: as StateBackend>>::RawIter, + inner: > as StateBackend>>::RawIter, } impl StorageIterator> for RawIter { type Backend = RefTrackingState; - type Error = as StateBackend>>::Error; + type Error = > as StateBackend>>::Error; fn next_key(&mut self, backend: &Self::Backend) -> Option> { self.inner.next_key(&backend.state) @@ -186,8 +187,9 @@ impl StorageIterator> for RawIter { } impl StateBackend> for RefTrackingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type Error = > as StateBackend>>::Error; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { @@ -284,7 +286,8 @@ impl StateBackend> for RefTrackingState { } impl AsTrieBackend> for RefTrackingState { - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; fn as_trie_backend( &self, @@ -320,6 +323,16 @@ pub enum BlocksPruning { Some(u32), } +impl BlocksPruning { + /// True if this is an archive pruning mode (either KeepAll or KeepFinalized). + pub fn is_archive(&self) -> bool { + match *self { + BlocksPruning::KeepAll | BlocksPruning::KeepFinalized => true, + BlocksPruning::Some(_) => false, + } + } +} + /// Where to find the database.. #[derive(Debug, Clone)] pub enum DatabaseSource { @@ -1926,7 +1939,7 @@ impl Backend { fn empty_state(&self) -> RecordStatsState, Block> { let root = EmptyStorage::::new().0; // Empty trie - let db_state = DbStateBuilder::::new(self.storage.clone(), root) + let db_state = DbStateBuilder::>::new(self.storage.clone(), root) .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); @@ -2418,9 +2431,12 @@ impl sc_client_api::backend::Backend for Backend { if hash == self.blockchain.meta.read().genesis_hash { if let Some(genesis_state) = &*self.genesis_state.read() { let root = genesis_state.root; - let db_state = DbStateBuilder::::new(genesis_state.clone(), root) - .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) - .build(); + let db_state = + DbStateBuilder::>::new(genesis_state.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); return Ok(RecordStatsState::new(state, None, self.state_usage.clone())) @@ -2439,11 +2455,12 @@ impl sc_client_api::backend::Backend for Backend { self.storage.state_db.pin(&hash, hdr.number.saturated_into::(), hint) { let root = hdr.state_root; - let db_state = DbStateBuilder::::new(self.storage.clone(), root) - .with_optional_cache( - self.shared_trie_cache.as_ref().map(|c| c.local_cache()), - ) - .build(); + let db_state = + DbStateBuilder::>::new(self.storage.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone())) } else { @@ -2514,7 +2531,7 @@ impl sc_client_api::backend::Backend for Backend { self.storage.state_db.pin(&hash, number.saturated_into::(), hint).map_err( |_| { sp_blockchain::Error::UnknownBlock(format!( - "State already discarded for `{:?}`", + "Unable to pin: state already discarded for `{:?}`", hash )) }, diff --git a/substrate/client/db/src/pinned_blocks_cache.rs b/substrate/client/db/src/pinned_blocks_cache.rs index 46c9287fb19ac1361153dbb65f4f0b353170042f..ac4aad07765cfbaa4d9c66efa4b44730b0b0a04c 100644 --- a/substrate/client/db/src/pinned_blocks_cache.rs +++ b/substrate/client/db/src/pinned_blocks_cache.rs @@ -20,7 +20,7 @@ use schnellru::{Limiter, LruMap}; use sp_runtime::{traits::Block as BlockT, Justifications}; const LOG_TARGET: &str = "db::pin"; -const PINNING_CACHE_SIZE: usize = 1024; +const PINNING_CACHE_SIZE: usize = 2048; /// Entry for pinned blocks cache. struct PinnedBlockCacheEntry { diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index 9f41b74237374334e079970f25e3e14526636c35..7fad7e3a2a0ebaf3a8ecde19ff9517e47ff28e75 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor" -version = "0.10.0-dev" +version = "0.32.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -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"] @@ -20,6 +23,7 @@ tracing = "0.1.29" codec = { package = "parity-scale-codec", version = "3.6.1" } sc-executor-common = { path = "common" } +sc-executor-polkavm = { path = "polkavm" } sc-executor-wasmtime = { path = "wasmtime" } sp-api = { path = "../../primitives/api" } sp-core = { path = "../../primitives/core" } @@ -37,6 +41,7 @@ assert_matches = "1.3.0" wat = "1.0" sc-runtime-test = { path = "runtime-test" } substrate-test-runtime = { path = "../../test-utils/runtime" } +sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } sp-state-machine = { path = "../../primitives/state-machine" } sp-runtime = { path = "../../primitives/runtime" } sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } @@ -55,7 +60,7 @@ name = "bench" harness = false [features] -default = [ "std" ] +default = ["std"] # This crate does not have `no_std` support, we just require this for tests std = [ "sc-runtime-test/std", diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 5118279b43b44ad75df3b8882592640c5ec9bd11..8ff34c3709a5e486fb9036a638788a532c6f296c 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor-common" -version = "0.10.0-dev" +version = "0.29.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -10,15 +10,19 @@ 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"] [dependencies] -thiserror = "1.0.48" -wasm-instrument = "0.3" +thiserror = { workspace = true } +wasm-instrument = "0.4" sc-allocator = { path = "../../allocator" } sp-maybe-compressed-blob = { path = "../../../primitives/maybe-compressed-blob" } sp-wasm-interface = { path = "../../../primitives/wasm-interface" } +polkavm = { workspace = true } [features] default = [] diff --git a/substrate/client/executor/common/src/error.rs b/substrate/client/executor/common/src/error.rs index 2a0dc364b4103ebc53ee767300bf25cc7211fe8b..b7c7e2b701984e37d43741ecb3cc2a1efdab9bdc 100644 --- a/substrate/client/executor/common/src/error.rs +++ b/substrate/client/executor/common/src/error.rs @@ -150,6 +150,24 @@ pub enum WasmError { Other(String), } +impl From for WasmError { + fn from(error: polkavm::ProgramParseError) -> Self { + WasmError::Other(error.to_string()) + } +} + +impl From for WasmError { + fn from(error: polkavm::Error) -> Self { + WasmError::Other(error.to_string()) + } +} + +impl From for Error { + fn from(error: polkavm::Error) -> Self { + Error::Other(error.to_string()) + } +} + /// An error message with an attached backtrace. #[derive(Debug)] pub struct MessageWithBacktrace { diff --git a/substrate/client/executor/common/src/lib.rs b/substrate/client/executor/common/src/lib.rs index 751801fb30da18e48e05daedbde644ad8699ce10..ff9fab2c8889fbbc9040740cd5f5a49f158d618c 100644 --- a/substrate/client/executor/common/src/lib.rs +++ b/substrate/client/executor/common/src/lib.rs @@ -25,3 +25,7 @@ pub mod error; pub mod runtime_blob; pub mod util; pub mod wasm_runtime; + +pub(crate) fn is_polkavm_enabled() -> bool { + std::env::var_os("SUBSTRATE_ENABLE_POLKAVM").map_or(false, |value| value == "1") +} diff --git a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs index becf9e219b0b7a60f97bb08b4044897b143bd006..d689083b2f85cd7f3fc16f220c1b45a474dd87f5 100644 --- a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -17,23 +17,23 @@ // along with this program. If not, see . use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; -use wasm_instrument::{ - export_mutable_globals, - parity_wasm::elements::{ - deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, - Module, Section, - }, +use wasm_instrument::parity_wasm::elements::{ + deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, + Module, Section, }; -/// A bunch of information collected from a WebAssembly module. +/// A program blob containing a Substrate runtime. #[derive(Clone)] -pub struct RuntimeBlob { - raw_module: Module, +pub struct RuntimeBlob(BlobKind); + +#[derive(Clone)] +enum BlobKind { + WebAssembly(Module), + PolkaVM(polkavm::ProgramBlob<'static>), } impl RuntimeBlob { - /// Create `RuntimeBlob` from the given wasm code. Will attempt to decompress the code before - /// deserializing it. + /// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob. /// /// See [`sp_maybe_compressed_blob`] for details about decompression. pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result { @@ -43,31 +43,26 @@ impl RuntimeBlob { Self::new(&wasm_code) } - /// Create `RuntimeBlob` from the given wasm code. + /// Create `RuntimeBlob` from the given WASM or PolkaVM program blob. /// - /// Returns `Err` if the wasm code cannot be deserialized. - pub fn new(wasm_code: &[u8]) -> Result { - let raw_module: Module = deserialize_buffer(wasm_code) - .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?; - Ok(Self { raw_module }) - } - - /// The number of globals defined in locally in this module. - pub fn declared_globals_count(&self) -> u32 { - self.raw_module - .global_section() - .map(|gs| gs.entries().len() as u32) - .unwrap_or(0) - } - - /// The number of imports of globals. - pub fn imported_globals_count(&self) -> u32 { - self.raw_module.import_section().map(|is| is.globals() as u32).unwrap_or(0) - } + /// Returns `Err` if the blob cannot be deserialized. + /// + /// Will only accept a PolkaVM program if the `SUBSTRATE_ENABLE_POLKAVM` environment + /// variable is set to `1`. + pub fn new(raw_blob: &[u8]) -> Result { + if raw_blob.starts_with(b"PVM\0") { + if crate::is_polkavm_enabled() { + return Ok(Self(BlobKind::PolkaVM( + polkavm::ProgramBlob::parse(raw_blob)?.into_owned(), + ))); + } else { + return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string())); + } + } - /// Perform an instrumentation that makes sure that the mutable globals are exported. - pub fn expose_mutable_globals(&mut self) { - export_mutable_globals(&mut self.raw_module, "exported_internal_global"); + let raw_module: Module = deserialize_buffer(raw_blob) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?; + Ok(Self(BlobKind::WebAssembly(raw_module))) } /// Run a pass that instrument this module so as to introduce a deterministic stack height @@ -80,26 +75,16 @@ impl RuntimeBlob { /// /// The stack cost of a function is computed based on how much locals there are and the maximum /// depth of the wasm operand stack. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result { let injected_module = - wasm_instrument::inject_stack_limiter(self.raw_module, stack_depth_limit).map_err( - |e| WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)), - )?; - - Ok(Self { raw_module: injected_module }) - } + wasm_instrument::inject_stack_limiter(self.into_webassembly_blob()?, stack_depth_limit) + .map_err(|e| { + WasmError::Other(format!("cannot inject the stack limiter: {:?}", e)) + })?; - /// Perform an instrumentation that makes sure that a specific function `entry_point` is - /// exported - pub fn entry_point_exists(&self, entry_point: &str) -> bool { - self.raw_module - .export_section() - .map(|e| { - e.entries().iter().any(|e| { - matches!(e.internal(), Internal::Function(_)) && e.field() == entry_point - }) - }) - .unwrap_or_default() + Ok(Self(BlobKind::WebAssembly(injected_module))) } /// Converts a WASM memory import into a memory section and exports it. @@ -107,8 +92,11 @@ impl RuntimeBlob { /// Does nothing if there's no memory import. /// /// May return an error in case the WASM module is invalid. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> { - let import_section = match self.raw_module.import_section_mut() { + let raw_module = self.as_webassembly_blob_mut()?; + let import_section = match raw_module.import_section_mut() { Some(import_section) => import_section, None => return Ok(()), }; @@ -124,7 +112,7 @@ impl RuntimeBlob { let memory_name = entry.field().to_owned(); import_entries.remove(index); - self.raw_module + raw_module .insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty]))) .map_err(|error| { WasmError::Other(format!( @@ -133,14 +121,14 @@ impl RuntimeBlob { )) })?; - if self.raw_module.export_section_mut().is_none() { + if raw_module.export_section_mut().is_none() { // A module without an export section is somewhat unrealistic, but let's do this // just in case to cover all of our bases. - self.raw_module + raw_module .insert_section(Section::Export(Default::default())) .expect("an export section can be always inserted if it doesn't exist; qed"); } - self.raw_module + raw_module .export_section_mut() .expect("export section already existed or we just added it above, so it always exists; qed") .entries_mut() @@ -156,12 +144,14 @@ impl RuntimeBlob { /// /// Will return an error in case there is no memory section present, /// or if the memory section is empty. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn setup_memory_according_to_heap_alloc_strategy( &mut self, heap_alloc_strategy: HeapAllocStrategy, ) -> Result<(), WasmError> { - let memory_section = self - .raw_module + let raw_module = self.as_webassembly_blob_mut()?; + let memory_section = raw_module .memory_section_mut() .ok_or_else(|| WasmError::Other("no memory section found".into()))?; @@ -187,8 +177,11 @@ impl RuntimeBlob { /// Scans the wasm blob for the first section with the name that matches the given. Returns the /// contents of the custom section if found or `None` otherwise. + /// + /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program. pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> { - self.raw_module + self.as_webassembly_blob() + .ok()? .custom_sections() .find(|cs| cs.name() == section_name) .map(|cs| cs.payload()) @@ -196,11 +189,45 @@ impl RuntimeBlob { /// Consumes this runtime blob and serializes it. pub fn serialize(self) -> Vec { - serialize(self.raw_module).expect("serializing into a vec should succeed; qed") + match self.0 { + BlobKind::WebAssembly(raw_module) => + serialize(raw_module).expect("serializing into a vec should succeed; qed"), + BlobKind::PolkaVM(ref blob) => blob.as_bytes().to_vec(), + } + } + + fn as_webassembly_blob(&self) -> Result<&Module, WasmError> { + match self.0 { + BlobKind::WebAssembly(ref raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } } - /// Destructure this structure into the underlying parity-wasm Module. - pub fn into_inner(self) -> Module { - self.raw_module + fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> { + match self.0 { + BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } + } + + fn into_webassembly_blob(self) -> Result { + match self.0 { + BlobKind::WebAssembly(raw_module) => Ok(raw_module), + BlobKind::PolkaVM(..) => Err(WasmError::Other( + "expected a WebAssembly program; found a PolkaVM program blob".into(), + )), + } + } + + /// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program. + pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> { + match self.0 { + BlobKind::WebAssembly(..) => None, + BlobKind::PolkaVM(ref blob) => Some(blob), + } } } diff --git a/substrate/client/executor/common/src/wasm_runtime.rs b/substrate/client/executor/common/src/wasm_runtime.rs index d8e142b9d55905e3e31d2247f5a48e8e2b0f987a..e8f429a3dbb2a0451769d427a81bdf5eaef492ff 100644 --- a/substrate/client/executor/common/src/wasm_runtime.rs +++ b/substrate/client/executor/common/src/wasm_runtime.rs @@ -19,7 +19,6 @@ //! Definitions for a wasm runtime. use crate::error::Error; -use sp_wasm_interface::Value; pub use sc_allocator::AllocationStats; @@ -30,46 +29,6 @@ pub const DEFAULT_HEAP_ALLOC_STRATEGY: HeapAllocStrategy = /// Default heap allocation pages. pub const DEFAULT_HEAP_ALLOC_PAGES: u32 = 2048; -/// A method to be used to find the entrypoint when calling into the runtime -/// -/// Contains variants on how to resolve wasm function that will be invoked. -pub enum InvokeMethod<'a> { - /// Call function exported with this name. - /// - /// Located function should have (u32, u32) -> u64 signature. - Export(&'a str), - /// Call a function found in the exported table found under the given index. - /// - /// Located function should have (u32, u32) -> u64 signature. - Table(u32), - /// Call function by reference from table through a wrapper. - /// - /// Invoked function (`dispatcher_ref`) function - /// should have (u32, u32, u32) -> u64 signature. - /// - /// `func` will be passed to the invoked function as a first argument. - TableWithWrapper { - /// Wrapper for the call. - /// - /// Function pointer, index into runtime exported table. - dispatcher_ref: u32, - /// Extra argument for dispatch. - /// - /// Common usage would be to use it as an actual wasm function pointer - /// that should be invoked, but can be used as any extra argument on the - /// callee side. - /// - /// This is typically generated and invoked by the runtime itself. - func: u32, - }, -} - -impl<'a> From<&'a str> for InvokeMethod<'a> { - fn from(val: &'a str) -> InvokeMethod<'a> { - InvokeMethod::Export(val) - } -} - /// A trait that defines an abstract WASM runtime module. /// /// This can be implemented by an execution engine. @@ -87,7 +46,7 @@ pub trait WasmInstance: Send { /// Before execution, instance is reset. /// /// Returns the encoded result on success. - fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error> { + fn call(&mut self, method: &str, data: &[u8]) -> Result, Error> { self.call_with_allocation_stats(method, data).0 } @@ -98,7 +57,7 @@ pub trait WasmInstance: Send { /// Returns the encoded result on success. fn call_with_allocation_stats( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], ) -> (Result, Error>, Option); @@ -110,11 +69,6 @@ pub trait WasmInstance: Send { fn call_export(&mut self, method: &str, data: &[u8]) -> Result, Error> { self.call(method.into(), data) } - - /// Get the value from a global with the given `name`. - /// - /// This method is only suitable for getting immutable globals. - fn get_global_const(&mut self, name: &str) -> Result, Error>; } /// Defines the heap pages allocation strategy the wasm runtime should use. diff --git a/substrate/client/executor/polkavm/Cargo.toml b/substrate/client/executor/polkavm/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9d0eb8ccf0ee072c86195068a589309edd132ba4 --- /dev/null +++ b/substrate/client/executor/polkavm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sc-executor-polkavm" +version = "0.29.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "PolkaVM executor for Substrate" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { workspace = true } +polkavm = { workspace = true } + +sc-executor-common = { path = "../common" } +sp-wasm-interface = { path = "../../../primitives/wasm-interface" } diff --git a/substrate/client/executor/polkavm/README.md b/substrate/client/executor/polkavm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..64fc2fa0c28485566fc60329ad8cfc0057112bc7 --- /dev/null +++ b/substrate/client/executor/polkavm/README.md @@ -0,0 +1 @@ +License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/substrate/client/executor/polkavm/src/lib.rs b/substrate/client/executor/polkavm/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bd72eb33d309997832de5fca9250ca651090ce7 --- /dev/null +++ b/substrate/client/executor/polkavm/src/lib.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use polkavm::{Caller, Reg}; +use sc_executor_common::{ + error::{Error, WasmError}, + wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, +}; +use sp_wasm_interface::{ + Function, FunctionContext, HostFunctions, Pointer, Value, ValueType, WordSize, +}; + +#[repr(transparent)] +pub struct InstancePre(polkavm::InstancePre<()>); + +#[repr(transparent)] +pub struct Instance(polkavm::Instance<()>); + +impl WasmModule for InstancePre { + fn new_instance(&self) -> Result, Error> { + Ok(Box::new(Instance(self.0.instantiate()?))) + } +} + +impl WasmInstance for Instance { + fn call_with_allocation_stats( + &mut self, + name: &str, + raw_data: &[u8], + ) -> (Result, Error>, Option) { + let Some(method_index) = self.0.module().lookup_export(name) else { + return ( + Err(format!("cannot call into the runtime: export not found: '{name}'").into()), + None, + ); + }; + + let Ok(raw_data_length) = u32::try_from(raw_data.len()) else { + return ( + Err(format!("cannot call runtime method '{name}': input payload is too big").into()), + None, + ); + }; + + // TODO: This will leak guest memory; find a better solution. + let mut state_args = polkavm::StateArgs::new(); + + // Make sure the memory is cleared... + state_args.reset_memory(true); + // ...and allocate space for the input payload. + state_args.sbrk(raw_data_length); + + match self.0.update_state(state_args) { + Ok(()) => {}, + Err(polkavm::ExecutionError::Trap(trap)) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {trap}").into()), None); + }, + Err(polkavm::ExecutionError::Error(error)) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {error}").into()), None); + }, + Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + } + + // Grab the address of where the guest's heap starts; that's where we've just allocated + // the memory for the input payload. + let data_pointer = self.0.module().memory_map().heap_base(); + + if let Err(error) = self.0.write_memory(data_pointer, raw_data) { + return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {error}").into()), None); + } + + let mut state = (); + let mut call_args = polkavm::CallArgs::new(&mut state, method_index); + call_args.args_untyped(&[data_pointer, raw_data_length]); + + match self.0.call(Default::default(), call_args) { + Ok(()) => {}, + Err(polkavm::ExecutionError::Trap(trap)) => { + return ( + Err(format!("call into the runtime method '{name}' failed: {trap}").into()), + None, + ); + }, + Err(polkavm::ExecutionError::Error(error)) => { + return ( + Err(format!("call into the runtime method '{name}' failed: {error}").into()), + None, + ); + }, + Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + } + + let result_pointer = self.0.get_reg(Reg::A0); + let result_length = self.0.get_reg(Reg::A1); + let output = match self.0.read_memory_into_vec(result_pointer, result_length) { + Ok(output) => output, + Err(error) => { + return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None) + }, + }; + + (Ok(output), None) + } +} + +struct Context<'r, 'a>(&'r mut polkavm::Caller<'a, ()>); + +impl<'r, 'a> FunctionContext for Context<'r, 'a> { + fn read_memory_into( + &self, + address: Pointer, + dest: &mut [u8], + ) -> sp_wasm_interface::Result<()> { + self.0 + .read_memory_into_slice(u32::from(address), dest) + .map_err(|error| error.to_string()) + .map(|_| ()) + } + + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { + self.0.write_memory(u32::from(address), data).map_err(|error| error.to_string()) + } + + fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { + let pointer = self.0.sbrk(0).expect("fetching the current heap pointer never fails"); + + // TODO: This will leak guest memory; find a better solution. + self.0.sbrk(size).ok_or_else(|| String::from("allocation failed"))?; + + Ok(Pointer::new(pointer)) + } + + fn deallocate_memory(&mut self, _ptr: Pointer) -> sp_wasm_interface::Result<()> { + // This is only used by the allocator host function, which is unused under PolkaVM. + unimplemented!("'deallocate_memory' is never used when running under PolkaVM"); + } + + fn register_panic_error_message(&mut self, _message: &str) { + unimplemented!("'register_panic_error_message' is never used when running under PolkaVM"); + } +} + +fn call_host_function( + caller: &mut Caller<()>, + function: &dyn Function, +) -> Result<(), polkavm::Trap> { + let mut args = [Value::I64(0); Reg::ARG_REGS.len()]; + let mut nth_reg = 0; + for (nth_arg, kind) in function.signature().args.iter().enumerate() { + match kind { + ValueType::I32 => { + args[nth_arg] = Value::I32(caller.get_reg(Reg::ARG_REGS[nth_reg]) as i32); + nth_reg += 1; + }, + ValueType::F32 => { + args[nth_arg] = Value::F32(caller.get_reg(Reg::ARG_REGS[nth_reg])); + nth_reg += 1; + }, + ValueType::I64 => { + let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = + Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); + }, + ValueType::F64 => { + let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); + }, + } + } + + log::trace!( + "Calling host function: '{}', args = {:?}", + function.name(), + &args[..function.signature().args.len()] + ); + + let value = match function + .execute(&mut Context(caller), &mut args.into_iter().take(function.signature().args.len())) + { + Ok(value) => value, + Err(error) => { + log::warn!("Call into the host function '{}' failed: {error}", function.name()); + return Err(polkavm::Trap::default()); + }, + }; + + if let Some(value) = value { + match value { + Value::I32(value) => { + caller.set_reg(Reg::A0, value as u32); + }, + Value::F32(value) => { + caller.set_reg(Reg::A0, value); + }, + Value::I64(value) => { + caller.set_reg(Reg::A0, value as u32); + caller.set_reg(Reg::A1, (value >> 32) as u32); + }, + Value::F64(value) => { + caller.set_reg(Reg::A0, value as u32); + caller.set_reg(Reg::A1, (value >> 32) as u32); + }, + } + } + + Ok(()) +} + +pub fn create_runtime(blob: &polkavm::ProgramBlob) -> Result, WasmError> +where + H: HostFunctions, +{ + static ENGINE: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + let engine = ENGINE.get_or_init(|| { + let config = polkavm::Config::from_env()?; + polkavm::Engine::new(&config) + }); + + let engine = match engine { + Ok(ref engine) => engine, + Err(ref error) => { + return Err(WasmError::Other(error.to_string())); + }, + }; + + let module = polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob)?; + let mut linker = polkavm::Linker::new(&engine); + for function in H::host_functions() { + linker.func_new(function.name(), |mut caller| call_host_function(&mut caller, function))?; + } + + let instance_pre = linker.instantiate_pre(&module)?; + Ok(Box::new(InstancePre(instance_pre))) +} diff --git a/substrate/client/executor/runtime-test/Cargo.toml b/substrate/client/executor/runtime-test/Cargo.toml index 046e59c08e0251890e83ea818e90c28e652e8f70..82610c4f50c2841fea13c1f859cc242f8ae427c7 100644 --- a/substrate/client/executor/runtime-test/Cargo.toml +++ b/substrate/client/executor/runtime-test/Cargo.toml @@ -9,21 +9,24 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { path = "../../../primitives/core", default-features = false} -sp-io = { path = "../../../primitives/io", default-features = false, features = ["improved_panic_error_reporting"]} -sp-runtime = { path = "../../../primitives/runtime", default-features = false} -sp-runtime-interface = { path = "../../../primitives/runtime-interface", default-features = false} -sp-std = { path = "../../../primitives/std", default-features = false} +sp-core = { path = "../../../primitives/core", default-features = false } +sp-io = { path = "../../../primitives/io", default-features = false, features = ["improved_panic_error_reporting"] } +sp-runtime = { path = "../../../primitives/runtime", default-features = false } +sp-runtime-interface = { path = "../../../primitives/runtime-interface", default-features = false } +sp-std = { path = "../../../primitives/std", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } [features] -default = [ "std" ] +default = ["std"] std = [ "sp-core/std", "sp-io/std", diff --git a/substrate/client/executor/src/executor.rs b/substrate/client/executor/src/executor.rs index 7c292a83da0895e670e203edbad24cf592164446..d56a3b389ef42868d0543303c5fbd63ea8d2d884 100644 --- a/substrate/client/executor/src/executor.rs +++ b/substrate/client/executor/src/executor.rs @@ -492,7 +492,6 @@ where runtime_code: &RuntimeCode, method: &str, data: &[u8], - _use_native: bool, context: CallContext, ) -> (Result>, bool) { tracing::trace!( @@ -519,7 +518,7 @@ where runtime_code, ext, heap_alloc_strategy, - |_, mut instance, _onchain_version, mut ext| { + |_, mut instance, _on_chain_version, mut ext| { with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) }, ); @@ -565,6 +564,8 @@ pub struct NativeElseWasmExecutor { /// Fallback wasm executor. wasm: WasmExecutor>, + + use_native: bool, } impl NativeElseWasmExecutor { @@ -601,7 +602,7 @@ impl NativeElseWasmExecutor { .with_runtime_cache_size(runtime_cache_size) .build(); - NativeElseWasmExecutor { native_version: D::native_version(), wasm } + NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true } } /// Create a new instance using the given [`WasmExecutor`]. @@ -610,7 +611,14 @@ impl NativeElseWasmExecutor { ExtendedHostFunctions, >, ) -> Self { - Self { native_version: D::native_version(), wasm: executor } + Self { native_version: D::native_version(), wasm: executor, use_native: true } + } + + /// Disable to use native runtime when possible just behave like `WasmExecutor`. + /// + /// Default to enabled. + pub fn disable_use_native(&mut self) { + self.use_native = false; } /// Ignore missing function imports if set true. @@ -645,9 +653,10 @@ impl CodeExecutor for NativeElseWasmExecut runtime_code: &RuntimeCode, method: &str, data: &[u8], - use_native: bool, context: CallContext, ) -> (Result>, bool) { + let use_native = self.use_native; + tracing::trace!( target: "executor", function = %method, @@ -673,18 +682,18 @@ impl CodeExecutor for NativeElseWasmExecut runtime_code, ext, heap_alloc_strategy, - |_, mut instance, onchain_version, mut ext| { - let onchain_version = - onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; + |_, mut instance, on_chain_version, mut ext| { + let on_chain_version = + on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; let can_call_with = - onchain_version.can_call_with(&self.native_version.runtime_version); + on_chain_version.can_call_with(&self.native_version.runtime_version); if use_native && can_call_with { tracing::trace!( target: "executor", native = %self.native_version.runtime_version, - chain = %onchain_version, + chain = %on_chain_version, "Request for native execution succeeded", ); @@ -696,7 +705,7 @@ impl CodeExecutor for NativeElseWasmExecut tracing::trace!( target: "executor", native = %self.native_version.runtime_version, - chain = %onchain_version, + chain = %on_chain_version, "Request for native execution failed", ); } @@ -711,7 +720,11 @@ impl CodeExecutor for NativeElseWasmExecut impl Clone for NativeElseWasmExecutor { fn clone(&self) -> Self { - NativeElseWasmExecutor { native_version: D::native_version(), wasm: self.wasm.clone() } + NativeElseWasmExecutor { + native_version: D::native_version(), + wasm: self.wasm.clone(), + use_native: self.use_native, + } } } diff --git a/substrate/client/executor/src/integration_tests/mod.rs b/substrate/client/executor/src/integration_tests/mod.rs index 0bd080c243574c8af93e8f2bd66190bef35ebe24..7f91b3ffe7644e37ebee0bc035faafbf38e21148 100644 --- a/substrate/client/executor/src/integration_tests/mod.rs +++ b/substrate/client/executor/src/integration_tests/mod.rs @@ -25,12 +25,13 @@ use sc_executor_common::{ }; use sc_runtime_test::wasm_binary_unwrap; use sp_core::{ - blake2_128, blake2_256, ed25519, map, + ed25519, map, offchain::{testing, OffchainDbExt, OffchainWorkerExt}, sr25519, traits::Externalities, Pair, }; +use sp_crypto_hashing::{blake2_128, blake2_256, sha2_256, twox_128, twox_256}; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; use sp_trie::{LayoutV1 as Layout, TrieConfiguration}; @@ -224,12 +225,12 @@ fn blake2_256_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_blake2_256", &[0], wasm_method, &mut ext,).unwrap(), - blake2_256(&b""[..]).to_vec().encode(), + blake2_256(b"").to_vec().encode(), ); assert_eq!( call_in_wasm("test_blake2_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - blake2_256(&b"Hello world!"[..]).to_vec().encode(), + blake2_256(b"Hello world!").to_vec().encode(), ); } @@ -239,12 +240,12 @@ fn blake2_128_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_blake2_128", &[0], wasm_method, &mut ext,).unwrap(), - blake2_128(&b""[..]).to_vec().encode(), + blake2_128(b"").to_vec().encode(), ); assert_eq!( call_in_wasm("test_blake2_128", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - blake2_128(&b"Hello world!"[..]).to_vec().encode(), + blake2_128(b"Hello world!").to_vec().encode(), ); } @@ -254,18 +255,12 @@ fn sha2_256_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_sha2_256", &[0], wasm_method, &mut ext,).unwrap(), - array_bytes::hex2bytes_unchecked( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ) - .encode(), + sha2_256(b"").to_vec().encode(), ); assert_eq!( call_in_wasm("test_sha2_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - array_bytes::hex2bytes_unchecked( - "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a" - ) - .encode(), + sha2_256(b"Hello world!").to_vec().encode(), ); } @@ -275,18 +270,12 @@ fn twox_256_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_twox_256", &[0], wasm_method, &mut ext,).unwrap(), - array_bytes::hex2bytes_unchecked( - "99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a" - ) - .encode(), + twox_256(b"").to_vec().encode() ); assert_eq!( call_in_wasm("test_twox_256", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - array_bytes::hex2bytes_unchecked( - "b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74" - ) - .encode(), + twox_256(b"Hello world!").to_vec().encode() ); } @@ -296,12 +285,12 @@ fn twox_128_should_work(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); assert_eq!( call_in_wasm("test_twox_128", &[0], wasm_method, &mut ext,).unwrap(), - array_bytes::hex2bytes_unchecked("99e9d85137db46ef4bbea33613baafd5").encode(), + twox_128(b"").to_vec().encode() ); assert_eq!( call_in_wasm("test_twox_128", &b"Hello world!".to_vec().encode(), wasm_method, &mut ext,) .unwrap(), - array_bytes::hex2bytes_unchecked("b27dfd7f223f177f2a13647b533599af").encode(), + twox_128(b"Hello world!").to_vec().encode() ); } diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index 6ee0ab3512ac0071d51b08a7071d1aaeac53fd2a..6b99f0a6ee03b303d2d97ce05040828e3248312b 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -29,7 +29,6 @@ //! wasm engine used, instance cache. #![warn(missing_docs)] -#![recursion_limit = "128"] #[macro_use] mod executor; @@ -58,7 +57,7 @@ pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStra /// Extracts the runtime version of a given runtime code. pub trait RuntimeVersionOf { - /// Extract [`RuntimeVersion`](sp_version::RuntimeVersion) of the given `runtime_code`. + /// Extract [`RuntimeVersion`] of the given `runtime_code`. fn runtime_version( &self, ext: &mut dyn Externalities, diff --git a/substrate/client/executor/src/wasm_runtime.rs b/substrate/client/executor/src/wasm_runtime.rs index 6dec3abdb20cf5ff50ca8cc3c09102dd7f2a6573..be8344ba79b7d78f8d49880ea73754788f33ff3e 100644 --- a/substrate/client/executor/src/wasm_runtime.rs +++ b/substrate/client/executor/src/wasm_runtime.rs @@ -297,6 +297,10 @@ pub fn create_wasm_runtime_with_code( where H: HostFunctions, { + if let Some(blob) = blob.as_polkavm_blob() { + return sc_executor_polkavm::create_runtime::(blob); + } + match wasm_method { WasmExecutionMethod::Compiled { instantiation_strategy } => sc_executor_wasmtime::create_runtime::( @@ -441,6 +445,7 @@ mod tests { use codec::Encode; use sp_api::{Core, RuntimeApiInfo}; use sp_runtime::RuntimeString; + use sp_version::{create_apis_vec, RuntimeVersion}; use sp_wasm_interface::HostFunctions; use substrate_test_runtime::Block; @@ -470,7 +475,7 @@ mod tests { authoring_version: 1, spec_version: 1, impl_version: 1, - apis: sp_api::create_apis_vec!([(>::ID, 1)]), + apis: create_apis_vec!([(>::ID, 1)]), }; let version = decode_version(&old_runtime_version.encode()).unwrap(); @@ -486,7 +491,7 @@ mod tests { authoring_version: 1, spec_version: 1, impl_version: 1, - apis: sp_api::create_apis_vec!([(>::ID, 3)]), + apis: create_apis_vec!([(>::ID, 3)]), }; decode_version(&old_runtime_version.encode()).unwrap_err(); @@ -494,13 +499,13 @@ mod tests { #[test] fn new_runtime_version_decodes() { - let old_runtime_version = sp_api::RuntimeVersion { + let old_runtime_version = RuntimeVersion { spec_name: "test".into(), impl_name: "test".into(), authoring_version: 1, spec_version: 1, impl_version: 1, - apis: sp_api::create_apis_vec!([(>::ID, 3)]), + apis: create_apis_vec!([(>::ID, 3)]), transaction_version: 3, state_version: 4, }; @@ -509,13 +514,13 @@ mod tests { assert_eq!(3, version.transaction_version); assert_eq!(0, version.state_version); - let old_runtime_version = sp_api::RuntimeVersion { + let old_runtime_version = RuntimeVersion { spec_name: "test".into(), impl_name: "test".into(), authoring_version: 1, spec_version: 1, impl_version: 1, - apis: sp_api::create_apis_vec!([(>::ID, 4)]), + apis: create_apis_vec!([(>::ID, 4)]), transaction_version: 3, state_version: 4, }; @@ -538,7 +543,7 @@ mod tests { authoring_version: 100, spec_version: 100, impl_version: 100, - apis: sp_api::create_apis_vec!([(>::ID, 4)]), + apis: create_apis_vec!([(>::ID, 4)]), transaction_version: 100, state_version: 1, }; diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index 261d52c0ede3292292cff4f47b1196f7ea55d8ff..75cc76a235430fa33b735e8b234493a24a3e2961 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-executor-wasmtime" -version = "0.10.0-dev" +version = "0.29.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -9,13 +9,16 @@ 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"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } cfg-if = "1.0" -libc = "0.2.121" +libc = "0.2.152" parking_lot = "0.12.1" # When bumping wasmtime do not forget to also bump rustix @@ -25,7 +28,7 @@ wasmtime = { version = "8.0.1", default-features = false, features = [ "cranelift", "jitdump", "parallel-compilation", - "pooling-allocator" + "pooling-allocator", ] } anyhow = "1.0.68" sc-allocator = { path = "../../allocator" } @@ -39,7 +42,7 @@ sp-wasm-interface = { path = "../../../primitives/wasm-interface", features = [" # By default rustix directly calls the appropriate syscalls completely bypassing libc; # this doesn't have any actual benefits for us besides making it harder to debug memory # problems (since then `mmap` etc. cannot be easily hooked into). -rustix = { version = "0.36.7", default-features = false, features = ["std", "mm", "fs", "param", "use-libc"] } +rustix = { version = "0.36.7", default-features = false, features = ["fs", "mm", "param", "std", "use-libc"] } [dev-dependencies] wat = "1.0" diff --git a/substrate/client/executor/wasmtime/src/instance_wrapper.rs b/substrate/client/executor/wasmtime/src/instance_wrapper.rs index 8852532adbcaa40293f2d7dfd0a326572d3c1ca4..4f67d1df9c5f3193d508b408c48c5c1230de2b54 100644 --- a/substrate/client/executor/wasmtime/src/instance_wrapper.rs +++ b/substrate/client/executor/wasmtime/src/instance_wrapper.rs @@ -22,36 +22,12 @@ use std::sync::Arc; use crate::runtime::{InstanceCounter, ReleaseInstanceHandle, Store, StoreData}; -use sc_executor_common::{ - error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}, - wasm_runtime::InvokeMethod, -}; -use sp_wasm_interface::{Pointer, Value, WordSize}; -use wasmtime::{ - AsContext, AsContextMut, Engine, Extern, Instance, InstancePre, Memory, Table, Val, -}; - -/// Invoked entrypoint format. -pub enum EntryPointType { - /// Direct call. - /// - /// Call is made by providing only payload reference and length. - Direct { entrypoint: wasmtime::TypedFunc<(u32, u32), u64> }, - /// Indirect call. - /// - /// Call is made by providing payload reference and length, and extra argument - /// for advanced routing. - Wrapped { - /// The extra argument passed to the runtime. It is typically a wasm function pointer. - func: u32, - dispatcher: wasmtime::TypedFunc<(u32, u32, u32), u64>, - }, -} +use sc_executor_common::error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}; +use sp_wasm_interface::{Pointer, WordSize}; +use wasmtime::{AsContext, AsContextMut, Engine, Instance, InstancePre, Memory}; /// Wasm blob entry point. -pub struct EntryPoint { - call_type: EntryPointType, -} +pub struct EntryPoint(wasmtime::TypedFunc<(u32, u32), u64>); impl EntryPoint { /// Call this entry point. @@ -64,13 +40,7 @@ impl EntryPoint { let data_ptr = u32::from(data_ptr); let data_len = u32::from(data_len); - match self.call_type { - EntryPointType::Direct { ref entrypoint } => - entrypoint.call(&mut *store, (data_ptr, data_len)), - EntryPointType::Wrapped { func, ref dispatcher } => - dispatcher.call(&mut *store, (func, data_ptr, data_len)), - } - .map_err(|trap| { + self.0.call(&mut *store, (data_ptr, data_len)).map_err(|trap| { let host_state = store .data_mut() .host_state @@ -99,18 +69,7 @@ impl EntryPoint { let entrypoint = func .typed::<(u32, u32), u64>(ctx) .map_err(|_| "Invalid signature for direct entry point")?; - Ok(Self { call_type: EntryPointType::Direct { entrypoint } }) - } - - pub fn wrapped( - dispatcher: wasmtime::Func, - func: u32, - ctx: impl AsContext, - ) -> std::result::Result { - let dispatcher = dispatcher - .typed::<(u32, u32, u32), u64>(ctx) - .map_err(|_| "Invalid signature for wrapped entry point")?; - Ok(Self { call_type: EntryPointType::Wrapped { func, dispatcher } }) + Ok(Self(entrypoint)) } } @@ -178,10 +137,8 @@ impl InstanceWrapper { })?; let memory = get_linear_memory(&instance, &mut store)?; - let table = get_table(&instance, &mut store); store.data_mut().memory = Some(memory); - store.data_mut().table = table; Ok(InstanceWrapper { instance, store, _release_instance_handle }) } @@ -190,61 +147,17 @@ impl InstanceWrapper { /// /// An entrypoint must have a signature `(i32, i32) -> i64`, otherwise this function will return /// an error. - pub fn resolve_entrypoint(&mut self, method: InvokeMethod) -> Result { - Ok(match method { - InvokeMethod::Export(method) => { - // Resolve the requested method and verify that it has a proper signature. - let export = - self.instance.get_export(&mut self.store, method).ok_or_else(|| { - Error::from(format!("Exported method {} is not found", method)) - })?; - let func = export - .into_func() - .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; - EntryPoint::direct(func, &self.store).map_err(|_| { - Error::from(format!("Exported function '{}' has invalid signature.", method)) - })? - }, - InvokeMethod::Table(func_ref) => { - let table = self - .instance - .get_table(&mut self.store, "__indirect_function_table") - .ok_or(Error::NoTable)?; - let val = table - .get(&mut self.store, func_ref) - .ok_or(Error::NoTableEntryWithIndex(func_ref))?; - let func = val - .funcref() - .ok_or(Error::TableElementIsNotAFunction(func_ref))? - .ok_or(Error::FunctionRefIsNull(func_ref))?; - - EntryPoint::direct(*func, &self.store).map_err(|_| { - Error::from(format!( - "Function @{} in exported table has invalid signature for direct call.", - func_ref, - )) - })? - }, - InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { - let table = self - .instance - .get_table(&mut self.store, "__indirect_function_table") - .ok_or(Error::NoTable)?; - let val = table - .get(&mut self.store, dispatcher_ref) - .ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?; - let dispatcher = val - .funcref() - .ok_or(Error::TableElementIsNotAFunction(dispatcher_ref))? - .ok_or(Error::FunctionRefIsNull(dispatcher_ref))?; - - EntryPoint::wrapped(*dispatcher, func, &self.store).map_err(|_| { - Error::from(format!( - "Function @{} in exported table has invalid signature for wrapped call.", - dispatcher_ref, - )) - })? - }, + pub fn resolve_entrypoint(&mut self, method: &str) -> Result { + // Resolve the requested method and verify that it has a proper signature. + let export = self + .instance + .get_export(&mut self.store, method) + .ok_or_else(|| Error::from(format!("Exported method {} is not found", method)))?; + let func = export + .into_func() + .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; + EntryPoint::direct(func, &self.store).map_err(|_| { + Error::from(format!("Exported function '{}' has invalid signature.", method)) }) } @@ -268,24 +181,6 @@ impl InstanceWrapper { Ok(heap_base as u32) } - - /// Get the value from a global with the given `name`. - pub fn get_global_val(&mut self, name: &str) -> Result> { - let global = match self.instance.get_export(&mut self.store, name) { - Some(global) => global, - None => return Ok(None), - }; - - let global = global.into_global().ok_or_else(|| format!("`{}` is not a global", name))?; - - match global.get(&mut self.store) { - Val::I32(val) => Ok(Some(Value::I32(val))), - Val::I64(val) => Ok(Some(Value::I64(val))), - Val::F32(val) => Ok(Some(Value::F32(val))), - Val::F64(val) => Ok(Some(Value::F64(val))), - _ => Err("Unknown value type".into()), - } - } } /// Extract linear memory instance from the given instance. @@ -301,15 +196,6 @@ fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result Option { - instance - .get_export(ctx, "__indirect_function_table") - .as_ref() - .cloned() - .and_then(Extern::into_table) -} - /// Functions related to memory. impl InstanceWrapper { pub(crate) fn store(&self) -> &Store { diff --git a/substrate/client/executor/wasmtime/src/runtime.rs b/substrate/client/executor/wasmtime/src/runtime.rs index ac88663f4e7915e49262ee8186bfaf7d92f6064d..595cc503272ebea7d249262c66b206cab5697d36 100644 --- a/substrate/client/executor/wasmtime/src/runtime.rs +++ b/substrate/client/executor/wasmtime/src/runtime.rs @@ -30,10 +30,10 @@ use sc_executor_common::{ error::{Error, Result, WasmError}, runtime_blob::RuntimeBlob, util::checked_range, - wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; +use sp_wasm_interface::{HostFunctions, Pointer, WordSize}; use std::{ path::{Path, PathBuf}, sync::{ @@ -41,7 +41,7 @@ use std::{ Arc, }, }; -use wasmtime::{AsContext, Engine, Memory, Table}; +use wasmtime::{AsContext, Engine, Memory}; const MAX_INSTANCE_COUNT: u32 = 64; @@ -51,8 +51,6 @@ pub(crate) struct StoreData { pub(crate) host_state: Option, /// This will be always set once the store is initialized. pub(crate) memory: Option, - /// This will be set only if the runtime actually contains a table. - pub(crate) table: Option
, } impl StoreData { @@ -164,7 +162,7 @@ pub struct WasmtimeInstance { impl WasmtimeInstance { fn call_impl( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], allocation_stats: &mut Option, ) -> Result> { @@ -184,20 +182,13 @@ impl WasmtimeInstance { impl WasmInstance for WasmtimeInstance { fn call_with_allocation_stats( &mut self, - method: InvokeMethod, + method: &str, data: &[u8], ) -> (Result>, Option) { let mut allocation_stats = None; let result = self.call_impl(method, data, &mut allocation_stats); (result, allocation_stats) } - - fn get_global_const(&mut self, name: &str) -> Result> { - match &mut self.strategy { - Strategy::RecreateInstance(ref mut instance_creator) => - instance_creator.instantiate()?.get_global_val(name), - } - } } /// Prepare a directory structure and a config file to enable wasmtime caching. diff --git a/substrate/client/executor/wasmtime/src/tests.rs b/substrate/client/executor/wasmtime/src/tests.rs index e185754b07696fde80f61787c659c869ff112842..1c06da1e3c142f58d4fa3ec64cf87577190218f8 100644 --- a/substrate/client/executor/wasmtime/src/tests.rs +++ b/substrate/client/executor/wasmtime/src/tests.rs @@ -384,7 +384,9 @@ fn test_max_memory_pages( ) (i32.const -1) ) - (unreachable) + (then + (unreachable) + ) ) (i64.const 0) @@ -421,7 +423,9 @@ fn test_max_memory_pages( ) (i32.const -1) ) - (unreachable) + (then + (unreachable) + ) ) (i64.const 0) diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index e077f4e11a59f532861e7d8b72c5eda31c1ef970..bd15e94ebafab26c89f76927d9d2f9c576ca1541 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sc-informant" -version = "0.10.0-dev" +version = "0.33.0" authors.workspace = true description = "Substrate informant." edition.workspace = true @@ -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"] @@ -16,9 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-network-common = { path = "../network/common" } +sc-network-sync = { path = "../network/sync" } sc-network = { path = "../network" } sp-blockchain = { path = "../../primitives/blockchain" } sp-runtime = { path = "../../primitives/runtime" } diff --git a/substrate/client/informant/src/display.rs b/substrate/client/informant/src/display.rs index 722cf56d778d64d44631c6bc8e530c455f81b7f6..cdbb83b6596c2f235c5513d6e11a04fe4c6a793f 100644 --- a/substrate/client/informant/src/display.rs +++ b/substrate/client/informant/src/display.rs @@ -21,10 +21,7 @@ use ansi_term::Colour; use log::info; use sc_client_api::ClientInfo; use sc_network::NetworkStatus; -use sc_network_common::sync::{ - warp::{WarpSyncPhase, WarpSyncProgress}, - SyncState, SyncStatus, -}; +use sc_network_sync::{SyncState, SyncStatus, WarpSyncPhase, WarpSyncProgress}; use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; use std::{fmt, time::Instant}; @@ -130,9 +127,10 @@ impl InformantDisplay { ), (_, Some(state), _) => ( "⚙️ ", - "Downloading state".into(), + "State sync".into(), format!( - ", {}%, {:.2} Mib", + ", {}, {}%, {:.2} Mib", + state.phase, state.percentage, (state.size as f32) / (1024f32 * 1024f32) ), @@ -144,37 +142,20 @@ impl InformantDisplay { ("⚙️ ", format!("Preparing{}", speed), format!(", target=#{target}")), }; - if self.format.enable_color { - info!( - target: "substrate", - "{} {}{} ({} peers), best: #{} ({}), finalized #{} ({}), {} {}", - level, - Colour::White.bold().paint(&status), - target, - Colour::White.bold().paint(format!("{}", num_connected_peers)), - Colour::White.bold().paint(format!("{}", best_number)), - best_hash, - Colour::White.bold().paint(format!("{}", finalized_number)), - info.chain.finalized_hash, - Colour::Green.paint(format!("⬇ {}", TransferRateFormat(avg_bytes_per_sec_inbound))), - Colour::Red.paint(format!("⬆ {}", TransferRateFormat(avg_bytes_per_sec_outbound))), - ) - } else { - info!( - target: "substrate", - "{} {}{} ({} peers), best: #{} ({}), finalized #{} ({}), ⬇ {} ⬆ {}", - level, - status, - target, - num_connected_peers, - best_number, - best_hash, - finalized_number, - info.chain.finalized_hash, - TransferRateFormat(avg_bytes_per_sec_inbound), - TransferRateFormat(avg_bytes_per_sec_outbound), - ) - } + info!( + target: "substrate", + "{} {}{} ({} peers), best: #{} ({}), finalized #{} ({}), {} {}", + level, + self.format.print_with_color(Colour::White.bold(), status), + target, + self.format.print_with_color(Colour::White.bold(), num_connected_peers), + self.format.print_with_color(Colour::White.bold(), best_number), + best_hash, + self.format.print_with_color(Colour::White.bold(), finalized_number), + info.chain.finalized_hash, + self.format.print_with_color(Colour::Green, format!("⬇ {}", TransferRateFormat(avg_bytes_per_sec_inbound))), + self.format.print_with_color(Colour::Red, format!("⬆ {}", TransferRateFormat(avg_bytes_per_sec_outbound))), + ) } } diff --git a/substrate/client/informant/src/lib.rs b/substrate/client/informant/src/lib.rs index 03f9075055e2ff98346d84fe6f6c362c468f45b6..7db80bb2d972591aae5944cd1cafd5cba876ecbc 100644 --- a/substrate/client/informant/src/lib.rs +++ b/substrate/client/informant/src/lib.rs @@ -18,13 +18,13 @@ //! Console informant. Prints sync progress and block events. Runs on the calling thread. -use ansi_term::Colour; +use ansi_term::{Colour, Style}; use futures::prelude::*; use futures_timer::Delay; use log::{debug, info, trace}; use sc_client_api::{BlockchainEvents, UsageProvider}; use sc_network::NetworkStatusProvider; -use sc_network_common::sync::SyncStatusProvider; +use sc_network_sync::SyncStatusProvider; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -51,6 +51,47 @@ impl Default for OutputFormat { } } +enum ColorOrStyle { + Color(Colour), + Style(Style), +} + +impl From for ColorOrStyle { + fn from(value: Colour) -> Self { + Self::Color(value) + } +} + +impl From